Signed-off-by: Richard Henderson <richard.hender...@linaro.org> --- risugen | 10 +- risugen_common.pm | 50 +++++- risugen_sparc64.pm | 385 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 443 insertions(+), 2 deletions(-) create mode 100644 risugen_sparc64.pm
diff --git a/risugen b/risugen index 2800b8b..830dfd3 100755 --- a/risugen +++ b/risugen @@ -310,6 +310,8 @@ Valid options: Useful to test before support for FP is available. --sve : enable sve floating point --be : generate instructions in Big-Endian byte order (ppc64 only). + --cross prefix : prefix for the assembler and linker + --keep : do not remove intermediate files --help : print this message EOT } @@ -322,6 +324,8 @@ sub main() my $fp_enabled = 1; my $sve_enabled = 0; my $big_endian = 0; + my $cross_prefix = ""; + my $keep = 0; my ($infile, $outfile); GetOptions( "help" => sub { usage(); exit(0); }, @@ -339,6 +343,8 @@ sub main() "be" => sub { $big_endian = 1; }, "no-fp" => sub { $fp_enabled = 0; }, "sve" => sub { $sve_enabled = 1; }, + "cross-prefix=s" => \$cross_prefix, + "keep" => sub { $keep = 1; }, ) or return 1; # allow "--pattern re,re" and "--pattern re --pattern re" @pattern_re = split(/,/,join(',',@pattern_re)); @@ -372,7 +378,9 @@ sub main() 'keys' => \@insn_keys, 'arch' => $full_arch[0], 'subarch' => $full_arch[1] || '', - 'bigendian' => $big_endian + 'bigendian' => $big_endian, + 'cross_prefix' => $cross_prefix, + 'keep' => $keep, ); write_test_code(\%params); diff --git a/risugen_common.pm b/risugen_common.pm index 5207c0e..228082f 100644 --- a/risugen_common.pm +++ b/risugen_common.pm @@ -26,7 +26,8 @@ BEGIN { our @EXPORT = qw(open_bin close_bin set_endian insn32 insn16 $bytecount progress_start progress_update progress_end eval_with_fields is_pow_of_2 sextract ctz - dump_insn_details); + dump_insn_details + open_asm close_asm assemble_and_link); } our $bytecount; @@ -66,6 +67,53 @@ sub insn16($) $bytecount += 2; } +sub open_asm($) +{ + my ($basename) = @_; + my $fname = $basename . ".s"; + open(ASM, ">", $fname) or die "can't open $fname: $!"; + select ASM; +} + +sub close_asm +{ + close(ASM) or die "can't close asm file: $!"; + select STDOUT; +} + +sub assemble_and_link($$$@) +{ + my ($basename, $cross_prefix, $keep, @asflags) = @_; + my $asmfile = $basename . ".s"; + my $ldfile = $basename . ".ld"; + my $objfile = $basename . ".o"; + + open(LD, ">", $ldfile) or die "can't open $ldfile: $!"; + print LD ' + ENTRY(start) + PHDRS { text PT_LOAD FILEHDR PHDRS; } + SECTIONS { + . = SIZEOF_HEADERS; + PROVIDE(start = .); + .text : { *(.text) } :text + .data : { *(.data) } :text + } + '; + close(LD); + + my @as = ($cross_prefix . "as", @asflags, "-o", $objfile, $asmfile); + system(@as) == 0 or die "system @as failed: $?"; + + my @ld = ($cross_prefix . "ld", "-o", $basename, "-T", $ldfile, $objfile); + system(@ld) == 0 or die "system @ld failed: $?"; + + if (!$keep) { + unlink $asmfile; + unlink $ldfile; + unlink $objfile; + } +} + # Progress bar implementation my $lastprog; my $proglen; diff --git a/risugen_sparc64.pm b/risugen_sparc64.pm new file mode 100644 index 0000000..c9f2ede --- /dev/null +++ b/risugen_sparc64.pm @@ -0,0 +1,385 @@ +#!/usr/bin/perl -w +############################################################################### +# Copyright (c) 2024 Linaro 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 +############################################################################### + +# risugen -- generate a test binary file for use with risu +# See 'risugen --help' for usage information. +package risugen_sparc64; + +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 $MAXBLOCK = 2048; +my $PARAMREG = 15; # %o7 + +my $OP_COMPARE = 0; # compare registers +my $OP_TESTEND = 1; # end of test, stop +my $OP_SETMEMBLOCK = 2; # g1 is address of memory block (8192 bytes) +my $OP_GETMEMBLOCK = 3; # add the address of memory block to g1 +my $OP_COMPAREMEM = 4; # compare memory block + +my @GREGS = ( "%g0", "%g1", "%g2", "%g3", "%g4", "%g5", "%g6", "%g7", + "%o0", "%o1", "%o2", "%o3", "%o4", "%o5", "%o6", "%o7", + "%l0", "%l1", "%l2", "%l3", "%l4", "%l5", "%l6", "%l7", + "%i0", "%i1", "%i2", "%i3", "%i4", "%i5", "%i6", "%i7" ); + +sub write_data32($) +{ + my ($val) = @_; + printf "\t.word\t%#x\n", $val; +} + +sub write_data64($) +{ + my ($val) = @_; + printf "\t.quad\t%#x\n", $val; +} + +sub write_risuop($) +{ + my ($op) = @_; + printf "\tilltrap\t%#x\n", 0xdead0 + $op; +} + +sub write_mov_rr($$) +{ + my ($rd, $rs) = @_; + printf "\tmov\t%s,%s\n", $GREGS[$rs], $GREGS[$rd]; +} + +sub write_mov_ri($$) +{ + my ($rd, $imm) = @_; + + if (-0x1000 <= $imm < 0x1000) { + printf "\tmov\t%d,%s\n", $imm, $GREGS[$rd]; + } else { + my $immhi = $imm & 0xfffff000; + my $immlo = $imm & 0x00000fff; + + if ($imm < 0) { + $immhi ^= 0xfffff000; + $immlo |= -0x1000; + } + printf "\tsethi\t%%hi(%d),%s\n", $immhi, $GREGS[$rd]; + if ($immlo != 0) { + printf "\txor\t%s,%d,%s\n", $GREGS[$rd], $immlo, $GREGS[$rd]; + } + } +} + +sub write_add_rri($$$) +{ + my ($rd, $rs, $imm) = @_; + die "bad imm!" if ($imm < -0x1000 || $imm >= 0x1000); + + printf "\txor\t%s,%d,%s\n", $GREGS[$rs], $imm, $GREGS[$rd]; +} + +sub write_sub_rrr($$$) +{ + my ($rd, $rs1, $rs2) = @_; + + printf "\tsub\t%s,%s,%s\n", $GREGS[$rs1], $GREGS[$rs2], $GREGS[$rd]; +} + +sub begin_datablock($$) +{ + my ($align, $label) = @_; + die "bad align!" if ($align < 4 || $align > 255 || !is_pow_of_2($align)); + + printf ".data\n"; + printf "\t.balign %d\n", $align; + printf "%s:\n", $label; +} + +sub end_datablock() +{ + printf ".text\n" +} + +sub write_ref_datablock($$$$) +{ + my ($rd, $offset, $scratch, $label) = @_; + + printf "\trd\t%%pc,%s\n", $GREGS[$rd]; + printf "\tsethi\t%%pc22(%s+%d),%s\n", + $label, $offset + 4, $GREGS[$scratch]; + printf "\tor\t%s,%%pc10(%s+%d),%s\n", + $GREGS[$scratch], $label, $offset + 8, $GREGS[$scratch]; + printf "\tadd\t%s,%s,%s\n", $GREGS[$scratch], $GREGS[$rd], $GREGS[$rd]; +} + +sub write_random_register_data($$) +{ + my ($fp_enabled, $fsr) = @_; + my $size = 32 * 8; + + if ($fp_enabled) { + # random data for 32 double-precision regs plus %gsr + $size += $fp_enabled ? 33 * 8 : 0; + } + + begin_datablock(8, "1"); + for (my $i = 0; $i < $size; $i += 4) { + write_data32(rand(0xffffffff)); + } + if ($fp_enabled) { + # %fsr gets constant data + write_data64($fsr); + } + end_datablock(); + + write_ref_datablock(1, 0, 2, "1b"); + + # Load floating point / SIMD registers + if ($fp_enabled) { + for (my $rt = 0; $rt < 64; $rt += 2) { + printf "\tldd\t[%s+%d],%%f%d\n", $GREGS[1], 32 * 8 + $rt * 4, $rt; + } + printf "\tldx\t[%s+%d],%s\n", $GREGS[1], 64 * 8, $GREGS[2]; + printf "\twr\t%s,0,%%gsr\n", $GREGS[2]; + printf "\tldx\t[%s+%d],%%fsr\n", $GREGS[1], 65 * 8; + } + + # Load Y + printf "\tldx\t[%s],%s\n", $GREGS[1], $GREGS[2]; + printf "\twr\t%s,0,%%y\n", $GREGS[2]; + + # Clear flags + printf "\twr\t%%g0,0,%%ccr\n"; + + # Load general purpose registers + for (my $i = 31; $i >= 1; --$i) { + if (reg_ok($i)) { + printf "\tldx\t[%s+%d],%s\n", $GREGS[1], $i * 8, $GREGS[$i]; + } + } + + write_risuop($OP_COMPARE); +} + +sub write_memblock_setup() +{ + begin_datablock($MAXALIGN, "2"); + + for (my $i = 0; $i < $MAXBLOCK; $i += 4) { + write_data32(rand(0xffffffff)); + } + + end_datablock(); + write_ref_datablock($PARAMREG, 0, 1, "2b"); + write_risuop($OP_SETMEMBLOCK); +} + +# Functions used in memory blocks to handle addressing modes. +# These all have the same basic API: they get called with parameters +# corresponding to the interesting fields of the instruction, +# and should generate code to set up the base register to be +# valid. They must return the register number of the base register. +# The last (array) parameter lists the registers which are trashed +# by the instruction (ie which are the targets of the load). +# This is used to avoid problems when the base reg is a load target. + +# Global used to communicate between align(x) and reg() etc. +my $alignment_restriction; + +sub align($) +{ + my ($a) = @_; + if (!is_pow_of_2($a) || !(0 < $a <= $MAXALIGN)) { + die "bad align() value $a\n"; + } + $alignment_restriction = $a; +} + +sub gen_memblock_offset() +{ + # Generate a random offset within the memory block, of the correct + # alignment. We require the offset to not be within 16 bytes of either + # end, to (more than) allow for the worst case data transfer. + return (rand($MAXBLOCK - 32) + 16) & ~($alignment_restriction - 1); +} + +sub reg_ok($) +{ + my ($r) = @_; + + # Avoid special registers %g7 (tp), %o6 (sp), %i6 (fp). + return $r != 7 && $r != 14 && $r != 30; +} + +sub reg_plus_imm($$@) +{ + # Handle reg + immediate addressing mode + my ($base, $imm, @trashed) = @_; + my $offset = gen_memblock_offset(); + my $scratch = $base != 1 ? 1 : 2; + + write_ref_datablock($base, $offset - $imm, $scratch, "2b"); + write_mov_ri($scratch, 0); + + if (grep $_ == $base, @trashed) { + return -1; + } + return $base; +} + +sub reg($@) +{ + my ($base, @trashed) = @_; + return reg_plus_imm($base, 0, @trashed); +} + +sub reg_plus_reg($$@) +{ + # Handle reg + reg addressing mode + my ($base, $idx, @trashed) = @_; + my $offset = gen_memblock_offset(); + my $scratch = 1; + + if ($base == $idx) { + return -1; + } + + while ($base == $scratch || $idx == $scratch) { + ++$scratch; + } + + write_ref_datablock($base, $offset, $scratch, "2b"); + write_mov_ri($scratch, 0); + write_sub_rrr($base, $base, $idx); + + if (grep $_ == $base, @trashed) { + return -1; + } + return $base; +} + +sub gen_one_insn($) +{ + my ($rec) = @_; + 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"}; + + # Given an instruction-details array, generate an instruction + my $constraintfailures = 0; + + INSN: while(1) { + my $insn = int(rand(0xffffffff)); + + $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. + align(16); + $basereg = eval_with_fields($insnname, $insn, $rec, "memory", $memblock); + } + + write_data32($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 $fp_enabled = $params->{ 'fp_enabled' }; + my $fsr = $params->{ 'fpscr' }; + my $numinsns = $params->{ 'numinsns' }; + my $outfile = $params->{ 'outfile' }; + + my %insn_details = %{ $params->{ 'details' } }; + my @keys = @{ $params->{ 'keys' } }; + + open_asm($outfile); + + # TODO better random number generator? + srand(0); + + print STDOUT "Generating code using patterns: @keys...\n"; + progress_start(78, $numinsns); + + 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, $fsr); + + for my $i (1..$numinsns) { + my $insn_enc = $keys[int rand (@keys)]; + gen_one_insn($insn_details{$insn_enc}); + write_risuop($OP_COMPARE); + # Rewrite the registers periodically. + if ($periodic_reg_random && ($i % 100) == 0) { + write_random_register_data($fp_enabled, $fsr); + } + progress_update($i); + } + write_risuop($OP_TESTEND); + progress_end(); + + close_asm(); + assemble_and_link($outfile, $params->{ 'cross_prefix' }, + $params->{ 'keep' }, "-Av9a"); +} + +1; -- 2.34.1