Reviewed-by: Richard Henderson <richard.hender...@linaro.org> Signed-off-by: Song Gao <gaos...@loongson.cn> --- risugen_loongarch64.pm | 486 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 486 insertions(+) create mode 100644 risugen_loongarch64.pm
diff --git a/risugen_loongarch64.pm b/risugen_loongarch64.pm new file mode 100644 index 0000000..98948a2 --- /dev/null +++ b/risugen_loongarch64.pm @@ -0,0 +1,486 @@ +#!/usr/bin/perl -w +############################################################################### +# Copyright (c) 2022 Loongson Technology Corporation Limited +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# +# Contributors: +# based on Peter Maydell (Linaro) - initial implementation +############################################################################### + +# risugen -- generate a test binary file for use with risu +# See 'risugen --help' for usage information. +package risugen_loongarch64; + +use strict; +use warnings; + +use risugen_common; + +require Exporter; + +our @ISA = qw(Exporter); +our @EXPORT = qw(write_test_code); + +my $periodic_reg_random = 1; + +# Maximum alignment restriction permitted for a memory op. +my $MAXALIGN = 64; + +my $OP_COMPARE = 0; # compare registers +my $OP_TESTEND = 1; # end of test, stop +my $OP_SETMEMBLOCK = 2; # r4 is address of memory block (8192 bytes) +my $OP_GETMEMBLOCK = 3; # add the address of memory block to r4 +my $OP_COMPAREMEM = 4; # compare memory block + +sub write_risuop($) +{ + my ($op) = @_; + insn32(0x000001f0 | $op); +} + +sub write_set_fcsr($) +{ + my ($fcsr) = @_; + # movgr2fcsr r0, r0 + insn32(0x0114c000); +} + +# Global used to communicate between align(x) and reg() etc. +my $alignment_restriction; + +sub set_reg_w($) +{ + my($reg)=@_; + # Set reg [0x0, 0x7FFFFFFF] + + # $reg << 33 + # slli.d $reg, $reg, 33 + insn32(0x410000 | 33 << 10 | $reg << 5 | $reg); + # $reg >> 33 + # srli.d $reg, $reg, 33 + insn32(0x450000 | 33 << 10 | $reg << 5 | $reg); + + return $reg; +} + +sub align($) +{ + my ($a) = @_; + if (!is_pow_of_2($a) || ($a < 0) || ($a > $MAXALIGN)) { + die "bad align() value $a\n"; + } + $alignment_restriction = $a; +} + +sub write_sub_rrr($$$) +{ + my ($rd, $rj, $rk) = @_; + # sub.d rd, rj, rk + insn32(0x00118000 | $rk << 10 | $rj << 5 | $rd); +} + +sub write_mov_rr($$$) +{ + my($rd, $rj, $rk) = @_; + # add.d rd, rj, r0 + insn32(0x00108000 | 0 << 10 | $rj << 5 | $rd); +} + +sub write_mov_ri($$) +{ + my ($rd, $imm) = @_; + + if ($imm >= -0x80000000 && $imm <= 0x7fffffff) { + # lu12i.w rd, si20 + insn32(0x14000000 | (($imm >> 12) & 0xfffff) << 5 | $rd); + # ori rd, rd, ui12 + insn32(0x03800000 | ($imm & 0xfff) << 10 | $rd << 5 | $rd); + } else { + die "unhandled immediate load"; + } +} + +sub write_get_offset() +{ + # Emit code to get a random offset within the memory block, of the + # right alignment, into r4 + # We require the offset to not be within 256 bytes of either + # end, to (more than) allow for the worst case data transfer, which is + # 16 * 64 bit regs + my $offset = (rand(2048 - 512) + 256) & ~($alignment_restriction - 1); + write_mov_ri(4, $offset); + write_risuop($OP_GETMEMBLOCK); +} + +sub reg_plus_reg($$@) +{ + my ($base, $idx, @trashed) = @_; + my $savedidx = 0; + if ($idx == 4) { + # Save the index into some other register for the + # moment, because the risuop will trash r4. + $idx = 5; + $idx++ if $idx == $base; + $savedidx = 1; + write_mov_rr($idx, 4, 0); + } + # Get a random offset within the memory block, of the + # right alignment. + write_get_offset(); + + write_sub_rrr($base, 4, $idx); + if ($base != 4) { + if ($savedidx) { + write_mov_rr(4, $idx, 0); + write_mov_ri($idx, 0); + } else { + write_mov_ri(4, 0); + } + } else { + if ($savedidx) { + write_mov_ri($idx, 0); + } + } + + if (grep $_ == $base, @trashed) { + return -1; + } + return $base; +} + +sub reg_plus_imm($$@) +{ + # Handle reg + immediate addressing mode + my ($base, $imm, @trashed) = @_; + + write_get_offset(); + # Now r4 is the address we want to do the access to, + # so set the basereg by doing the inverse of the + # addressing mode calculation, ie base = r4 - imm + # We could do this more cleverly with a sub immediate. + if ($base != 4) { + write_mov_ri($base, $imm); + write_sub_rrr($base, 4, $base); + # Clear r4 to avoid register compare mismatches + # when the memory block location differs between machines. + write_mov_ri(4, 0); + }else { + # We borrow r1 as a temporary (not a problem + # as long as we don't leave anything in a register + # which depends on the location of the memory block) + write_mov_ri(1, $imm); + write_sub_rrr($base, 4, 1); + } + + if (grep $_ == $base, @trashed) { + return -1; + } + return $base; +} + +sub write_pc_adr($$) +{ + my($rd, $imm) = @_; + # pcaddi (si20 | 2bit 0) + pc + insn32(0x18000000 | $imm << 5 | $rd); +} + +sub write_and($$$) +{ + my($rd, $rj, $rk) = @_; + # and rd, rj, rk + insn32(0x148000 | $rk << 10 | $rj << 5 | $rd); +} + +sub write_align_reg($$) +{ + my ($rd, $align) = @_; + # rd = rd & ~($align -1); + # use r1 as a temp register. + write_mov_ri(1, $align -1); + write_sub_rrr(1, 0, 1); + write_and($rd, $rd, 1); +} + +sub write_jump_fwd($) +{ + my($len) = @_; + # b pc + len + my ($offslo, $offshi) = (($len / 4 + 1) & 0xffff, ($len / 4 + 1) >> 16); + insn32(0x50000000 | $offslo << 10 | $offshi); +} + +sub write_memblock_setup() +{ + my $align = $MAXALIGN; + my $datalen = 8192 + $align; + if (($align > 255) || !is_pow_of_2($align) || $align < 4) { + die "bad alignment!"; + } + + # Set r4 to (datablock + (align-1)) & ~(align-1) + # datablock is at PC + (4 * 4 instructions) = PC + 16 + write_pc_adr(4, (4 * 4) + ($align - 1)); #insn 1 + write_align_reg(4, $align); #insn 2 + write_risuop($OP_SETMEMBLOCK); #insn 3 + write_jump_fwd($datalen); #insn 4 + + for(my $i = 0; $i < $datalen / 4; $i++) { + insn32(rand(0xffffffff)); + } +} + +# Write random fp value of passed precision (1=single, 2=double, 4=quad) +sub write_random_fpreg_var($) +{ + my ($precision) = @_; + my $randomize_low = 0; + + if ($precision != 1 && $precision != 2 && $precision != 4) { + die "write_random_fpreg: invalid precision.\n"; + } + + my ($low, $high); + my $r = rand(100); + if ($r < 5) { + # +-0 (5%) + $low = $high = 0; + $high |= 0x80000000 if (rand() < 0.5); + } elsif ($r < 10) { + # NaN (5%) + # (plus a tiny chance of generating +-Inf) + $randomize_low = 1; + $high = rand(0xffffffff) | 0x7ff00000; + } elsif ($r < 15) { + # Infinity (5%) + $low = 0; + $high = 0x7ff00000; + $high |= 0x80000000 if (rand() < 0.5); + } elsif ($r < 30) { + # Denormalized number (15%) + # (plus tiny chance of +-0) + $randomize_low = 1; + $high = rand(0xffffffff) & ~0x7ff00000; + } else { + # Normalized number (70%) + # (plus a small chance of the other cases) + $randomize_low = 1; + $high = rand(0xffffffff); + } + + for (my $i = 1; $i < $precision; $i++) { + if ($randomize_low) { + $low = rand(0xffffffff); + } + insn32($low); + } + insn32($high); +} + +sub write_random_loongarch64_fpdata() +{ + # Load floating point registers + my $align = 16; + my $datalen = 32 * 16 + $align; + my $off = 0; + write_pc_adr(5, (4 * 4) + $align); # insn 1 pcaddi + write_pc_adr(4, (3 * 4) + ($align - 1)); # insn 2 pcaddi + write_align_reg(4, $align); # insn 3 andi + write_jump_fwd($datalen); # insn 4 b pc + len + + # Align safety + for (my $i = 0; $i < ($align / 4); $i++) { + insn32(rand(0xffffffff)); + } + + for (my $i = 0; $i < 32; $i++) { + write_random_fpreg_var(4); # double + } + + $off = 0; + for (my $i = 0; $i < 32; $i++) { + my $tmp_reg = 6; + # r5 is fp register initial val + # r4 is aligned base address + # copy memory from r5 to r4 + # ld.d r6, r5, $off + # st.d r6, r4, $off + # $off = $off + 16 + insn32(0x28c00000 | $off << 10 | 5 << 5 | $tmp_reg); + insn32(0x29c00000 | $off << 10 | 4 << 5 | $tmp_reg); + $off = $off + 8; + insn32(0x28c00000 | $off << 10 | 5 << 5 | $tmp_reg); + insn32(0x29c00000 | $off << 10 | 4 << 5 | $tmp_reg); + $off = $off + 8; + } + + $off = 0; + for (my $i = 0; $i < 32; $i++) { + # fld.d fd, r4, $off + insn32(0x2b800000 | $off << 10 | 4 << 5 | $i); + $off = $off + 16; + } +} + +sub write_random_regdata() +{ + # General purpose registers, skip r2 + write_mov_ri(1, rand(0x7fffffff)); # init r1 + for (my $i = 3; $i < 32; $i++) { + write_mov_ri($i, rand(0x7fffffff)); + } +} + +sub write_random_register_data($) +{ + my ($fp_enabled) = @_; + + # Set fcc0 ~ fcc7 + # movgr2cf $fcc0, $zero + insn32(0x114d800); + # movgr2cf $fcc1, $zero + insn32(0x114d801); + # movgr2cf $fcc2, $zero + insn32(0x114d802); + # movgr2cf $fcc3, $zero + insn32(0x114d803); + # movgr2cf $fcc4, $zero + insn32(0x114d804); + # movgr2cf $fcc5, $zero + insn32(0x114d805); + # movgr2cf $fcc6, $zero + insn32(0x114d806); + # movgr2cf $fcc7, $zero + insn32(0x114d807); + + if ($fp_enabled) { + # Load floating point registers + write_random_loongarch64_fpdata(); + } + + write_random_regdata(); + write_risuop($OP_COMPARE); +} + +sub gen_one_insn($$) +{ + # Given an instruction-details array, generate an instruction + my $constraintfailures = 0; + + INSN: while(1) { + my ($forcecond, $rec) = @_; + my $insn = int(rand(0xffffffff)); + my $insnname = $rec->{name}; + my $insnwidth = $rec->{width}; + my $fixedbits = $rec->{fixedbits}; + my $fixedbitmask = $rec->{fixedbitmask}; + my $constraint = $rec->{blocks}{"constraints"}; + my $memblock = $rec->{blocks}{"memory"}; + + $insn &= ~$fixedbitmask; + $insn |= $fixedbits; + + if (defined $constraint) { + # User-specified constraint: evaluate in an environment + # with variables set corresponding to the variable fields. + my $v = eval_with_fields($insnname, $insn, $rec, "constraints", $constraint); + if(!$v) { + $constraintfailures++; + if ($constraintfailures > 10000) { + print "10000 consecutive constraint failures for $insnname constraints string:\n$constraint\n"; + exit (1); + } + next INSN; + } + } + + # OK, we got a good one + $constraintfailures = 0; + + my $basereg; + + if (defined $memblock) { + # This is a load or store. We simply evaluate the block, + # which is expected to be a call to a function which emits + # the code to set up the base register and returns the + # number of the base register. + # Default alignment requirement for ARM is 4 bytes, + # we use 16 for Aarch64, although often unnecessary and overkill. + align(16); + $basereg = eval_with_fields($insnname, $insn, $rec, "memory", $memblock); + } + + insn32($insn); + + if (defined $memblock) { + # Clean up following a memory access instruction: + # we need to turn the (possibly written-back) basereg + # into an offset from the base of the memory block, + # to avoid making register values depend on memory layout. + # $basereg -1 means the basereg was a target of a load + # (and so it doesn't contain a memory address after the op) + if ($basereg != -1) { + write_mov_ri($basereg, 0); + } + write_risuop($OP_COMPAREMEM); + } + return; + } +} + +sub write_test_code($) +{ + my ($params) = @_; + + my $condprob = $params->{ 'condprob' }; + my $fcsr = $params->{'fpscr'}; + my $numinsns = $params->{ 'numinsns' }; + my $fp_enabled = $params->{ 'fp_enabled' }; + my $outfile = $params->{ 'outfile' }; + + my %insn_details = %{ $params->{ 'details' } }; + my @keys = @{ $params->{ 'keys' } }; + + open_bin($outfile); + + # Convert from probability that insn will be conditional to + # probability of forcing insn to unconditional + $condprob = 1 - $condprob; + + # TODO better random number generator? + srand(0); + + print "Generating code using patterns: @keys...\n"; + progress_start(78, $numinsns); + + if ($fp_enabled) { + write_set_fcsr($fcsr); + } + + if (grep { defined($insn_details{$_}->{blocks}->{"memory"}) } @keys) { + write_memblock_setup(); + } + # Memblock setup doesn't clean its registers, so this must come afterwards. + write_random_register_data($fp_enabled); + + for my $i (1..$numinsns) { + my $insn_enc = $keys[int rand (@keys)]; + my $forcecond = (rand() < $condprob) ? 1 : 0; + gen_one_insn($forcecond, $insn_details{$insn_enc}); + write_risuop($OP_COMPARE); + # Rewrite the registers periodically. This avoids the tendency + # for the VFP registers to decay to NaNs and zeroes. + if ($periodic_reg_random && ($i % 100) == 0) { + write_random_register_data($fp_enabled); + } + progress_update($i); + } + write_risuop($OP_TESTEND); + progress_end(); + close_bin(); +} + +1; -- 2.31.1