qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Jan Bobek <jan.bobek@gmail.com>
To: qemu-devel@nongnu.org
Cc: "Jan Bobek" <jan.bobek@gmail.com>,
	"Alex Bennée" <alex.bennee@linaro.org>,
	"Richard Henderson" <richard.henderson@linaro.org>
Subject: [Qemu-devel] [RISU PATCH v3 06/18] risugen_x86: add module
Date: Thu, 11 Jul 2019 18:32:48 -0400	[thread overview]
Message-ID: <20190711223300.6061-7-jan.bobek@gmail.com> (raw)
In-Reply-To: <20190711223300.6061-1-jan.bobek@gmail.com>

risugen_x86.pm is the main backend module for Intel i386 and x86_64
architectures; it orchestrates generation of the test code with
support from the rest of risugen_x86_* modules.

Signed-off-by: Jan Bobek <jan.bobek@gmail.com>
---
 risugen_x86.pm | 518 +++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 518 insertions(+)
 create mode 100644 risugen_x86.pm

diff --git a/risugen_x86.pm b/risugen_x86.pm
new file mode 100644
index 0000000..ae11843
--- /dev/null
+++ b/risugen_x86.pm
@@ -0,0 +1,518 @@
+#!/usr/bin/perl -w
+###############################################################################
+# Copyright (c) 2019 Jan Bobek
+# 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:
+#     Jan Bobek - initial implementation
+###############################################################################
+
+# risugen_x86 -- risugen module for Intel i386/x86_64 architectures
+package risugen_x86;
+
+use strict;
+use warnings;
+
+use risugen_common;
+use risugen_x86_asm;
+use risugen_x86_constraints;
+use risugen_x86_memory;
+
+require Exporter;
+
+our @ISA    = qw(Exporter);
+our @EXPORT = qw(write_test_code);
+
+use constant {
+    RISUOP_COMPARE     => 0,        # compare registers
+    RISUOP_TESTEND     => 1,        # end of test, stop
+    RISUOP_SETMEMBLOCK => 2,        # eax is address of memory block (8192 bytes)
+    RISUOP_GETMEMBLOCK => 3,        # add the address of memory block to eax
+    RISUOP_COMPAREMEM  => 4,        # compare memory block
+
+    # Maximum alignment restriction permitted for a memory op.
+    MAXALIGN => 64,
+    MEMBLOCK_LEN => 8192,
+};
+
+my $periodic_reg_random = 1;
+my $is_x86_64 = 0;
+
+sub wrap_int32($)
+{
+    my ($x) = @_;
+    my $r = 1 << 31;
+    return ($x + $r) % (2 * $r) - $r;
+}
+
+sub asm_insn_risuop($)
+{
+    my ($op) = @_;
+    asm_insn_ud1(reg => REG_RAX, reg2 => $op);
+}
+
+sub asm_insn_movT(%)
+{
+    my (%args) = @_;
+
+    if ($is_x86_64) {
+        asm_insn_mov64(%args);
+    } else {
+        asm_insn_mov(%args);
+    }
+}
+
+sub asm_insn_movT_imm(%)
+{
+    my (%args) = @_;
+    my $imm = $args{imm}; delete $args{imm};
+
+    my $is_sint32 = (-0x80000000 <= $imm && $imm <= 0x7fffffff);
+    my $is_uint32 = (0 <= $imm && $imm <= 0xffffffff);
+
+    $args{$is_sint32 || $is_uint32 ? 'imm32' : 'imm64'} = $imm;
+    asm_insn_movT(%args);
+}
+
+sub asm_insn_addT(%)
+{
+    my (%args) = @_;
+
+    if ($is_x86_64) {
+        asm_insn_add64(%args);
+    } else {
+        asm_insn_add(%args);
+    }
+}
+
+sub asm_insn_negT(%)
+{
+    my (%args) = @_;
+
+    if ($is_x86_64) {
+        asm_insn_neg64(%args);
+    } else {
+        asm_insn_neg(%args);
+    }
+}
+
+sub asm_insn_xchgT(%)
+{
+    my (%args) = @_;
+
+    if ($is_x86_64) {
+        asm_insn_xchg64(%args);
+    } else {
+        asm_insn_xchg(%args);
+    }
+}
+
+sub write_random_regdata()
+{
+    my $reg_cnt = $is_x86_64 ? 16 : 8;
+    my $reg_width = $is_x86_64 ? 64 : 32;
+
+    # initialize flags register
+    asm_insn_xor(reg => REG_RAX, reg2 => REG_RAX);
+    asm_insn_sahf();
+
+    # general purpose registers
+    for (my $reg = 0; $reg < $reg_cnt; $reg++) {
+        if ($reg != REG_RSP) {
+            my $imm = randint(width => $reg_width, signed => 1);
+            asm_insn_movT_imm(reg => $reg, imm => $imm);
+        }
+    }
+}
+
+# At the end of this function, we can emit $datalen data-bytes which
+# will be skipped over at runtime, but whose address will be present
+# in EAX and optionally aligned.
+sub prepare_datablock(%)
+{
+    my (%args) = @_;
+    $args{align} = 0 unless defined $args{align} && $args{align} > 1;
+
+    # First, load current EIP/RIP into EAX/RAX. Easy to do on x86_64
+    # thanks to RIP-relative addressing, but on i386 we need to play
+    # some well-known tricks with the CALL instruction. Then, AND the
+    # EAX/RAX register with correct mask to obtain the aligned
+    # address.
+    my $reg = REG_RAX;
+
+    if ($is_x86_64) {
+        my $disp32 = 5;         # 5-byte JMP
+        $disp32 += 4 + ($args{align} - 1) if $args{align}; # 4-byte AND
+
+        asm_insn_lea64(reg => $reg, disp32 => $disp32);
+        asm_insn_and64(reg2 => $reg, imm8 => ~($args{align} - 1))
+            if $args{align};
+    } else {
+        my $imm8 = 1 + 3 + 5;   # 1-byte POP + 3-byte ADD + 5-byte JMP
+        $imm8 += 3 + ($args{align} - 1) if $args{align}; # 3-byte AND
+
+        # displacement = next instruction
+        asm_insn_call(imm32 => 0x00000000);
+        asm_insn_pop(reg => $reg);
+        asm_insn_add(reg2 => $reg, imm8 => $imm8);
+        asm_insn_and(reg2 => $reg, imm8 => ~($args{align} - 1))
+            if $args{align};
+    }
+
+    # JMP over the data blob.
+    asm_insn_jmp(imm32 => $args{datalen});
+}
+
+# Write a block of random data, $datalen bytes long, optionally
+# aligned, and load its address into EAX/RAX.
+sub write_random_datablock(%)
+{
+    my (%args) = @_;
+    prepare_datablock(%args);
+
+    # Generate the random data
+    my $datalen = $args{datalen};
+    for (my $w = 8; 0 < $w; $w /= 2) {
+        for (; $w <= $datalen; $datalen -= $w) {
+            my $value = randint(width => 8 * $w);
+            insnv(value => $value, width => 8 * $w);
+        }
+    }
+}
+
+sub write_random_vregdata(%)
+{
+    my (%args) = @_;
+    $args{ymm} = 0          unless defined $args{ymm};
+    $args{xmm} = $args{ymm} unless defined $args{xmm};
+    $args{mm}  = 0          unless defined $args{mm};
+
+    die "cannot initialize YMM registers only\n"
+        if $args{ymm} && !$args{xmm};
+
+    my $datalen = 0;
+
+    my $mmreg_count = 8;
+    my $mmreg_size  = 8;
+    $datalen += $mmreg_count * $mmreg_size if $args{mm};
+
+    my $xmmreg_count = $is_x86_64 ? 16 : 8;
+    my $xmmreg_size  = 16;
+    $datalen += $xmmreg_count * $xmmreg_size if $args{xmm};
+
+    my $ymmreg_count = $xmmreg_count;
+    my $ymmreg_size  = 32 - $xmmreg_size;
+    $datalen += $ymmreg_count * $ymmreg_size if $args{ymm};
+
+    return unless $datalen > 0;
+
+    # Generate random data blob
+    write_random_datablock(datalen => $datalen + MAXALIGN - 1,
+                           align => MAXALIGN);
+
+    # Load the random data into vector regs.
+    my $offset = 0;
+
+    if ($args{mm}) {
+        for (my $mmreg = 0; $mmreg < $mmreg_count; $mmreg += 1) {
+            asm_insn_movq(reg => $mmreg,
+                          base => REG_RAX,
+                          disp32 => $offset);
+            $offset += $mmreg_size;
+        }
+    }
+    if ($args{ymm}) {
+        for (my $ymmreg = 0; $ymmreg < $ymmreg_count; $ymmreg += 1) {
+            asm_insn_vmovaps(l => ($xmmreg_size + $ymmreg_size) * 8,
+                             reg => $ymmreg,
+                             base => REG_RAX,
+                             disp32 => $offset);
+            $offset += $xmmreg_size + $ymmreg_size;
+        }
+    } elsif ($args{xmm}) {
+        for (my $xmmreg = 0; $xmmreg < $xmmreg_count; $xmmreg += 1) {
+            asm_insn_movaps(reg => $xmmreg,
+                            base => REG_RAX,
+                            disp32 => $offset);
+            $offset += $xmmreg_size;
+        }
+    }
+}
+
+sub write_memblock_setup()
+{
+    # Generate random data blob
+    write_random_datablock(datalen => MEMBLOCK_LEN + MAXALIGN - 1,
+                           align => MAXALIGN);
+
+    # Pointer is in EAX/RAX; set the memblock
+    asm_insn_risuop(RISUOP_SETMEMBLOCK);
+}
+
+sub write_random_register_data(%)
+{
+    my (%args) = @_;
+    write_random_vregdata(%{$args{vregs}}) if defined $args{vregs};
+    write_random_regdata();
+    asm_insn_risuop(RISUOP_COMPARE);
+}
+
+sub write_mem_getoffset(%)
+{
+    my (%args) = @_;
+
+    my @tokens;
+    push @tokens, "BASE"   if defined $args{base};
+    push @tokens, "INDEX"  if defined $args{index};
+    push @tokens, "VINDEX" if defined $args{vindex};
+    push @tokens, "END";
+
+    # (BASE (INDEX | VINDEX)?)? END
+    my $token = shift @tokens;
+
+    if ($token eq "BASE") {
+        $token = shift @tokens;
+        # We must not modify RSP during tests, therefore it cannot be a
+        # base register.
+        return 0 if $args{base} == REG_RSP;
+
+        if ($token eq "VINDEX") {
+            $token = shift @tokens;
+
+            die "VSIB requested, but addrw undefined"
+                unless defined $args{addrw};
+            die "VSIB requested, but count undefined"
+                unless defined $args{count};
+
+            write_mem_getoffset_base_vindex(%args);
+        } elsif ($token eq "INDEX") {
+            $token = shift @tokens;
+            # RSP cannot be used as an index in regular SIB... And we may
+            # not modify it anyway.
+            return 0 if $args{index} == REG_RSP;
+            # If index and base registers are the same, we may not be able
+            # to honor the alignment requirements.
+            return 0 if $args{index} == $args{base};
+
+            write_mem_getoffset_base_index(%args);
+        } else {
+            write_mem_getoffset_base(%args);
+        }
+    }
+
+    die "unexpected junk at the end of getoffset tokens: $token @tokens\n"
+        unless $token eq "END";
+}
+
+sub write_mem_getoffset_base(%)
+{
+    my (%args) = @_;
+
+    if ($args{mask}) {
+        die "size $args{size} is too large for masking"
+            unless $args{size} <= 8;
+        die "simultaneous alignment and masking not supported"
+            if $args{align} > 1;
+
+        prepare_datablock(datalen => $args{size});
+
+        my $width = $args{size} * 8;
+        my $value = randint(width => $width);
+        $value = ($value & ~$args{mask}) | ($args{value} & $args{mask});
+        insnv(value => $value, width => $width, bigendian => 0);
+
+        my $offset = -$args{disp};
+        $offset = wrap_int32($offset) if !$is_x86_64;
+
+        asm_insn_movT_imm(reg => REG_RDX, imm => $offset);
+        asm_insn_addT(reg2 => REG_RAX, reg => REG_RDX);
+    } else {
+        my $offset = int(rand(MEMBLOCK_LEN - $args{size}));
+        $offset &= ~($args{align} - 1);
+
+        $offset -= $args{disp};
+        $offset = wrap_int32($offset) if !$is_x86_64;
+
+        asm_insn_movT_imm(reg => REG_RAX, imm => $offset);
+        asm_insn_risuop(RISUOP_GETMEMBLOCK);
+    }
+
+    asm_insn_xchgT(reg => $args{base}, reg2 => REG_RAX)
+        unless $args{base} == REG_RAX;
+}
+
+sub write_mem_getoffset_base_index(%)
+{
+    my (%args) = @_;
+
+    my $addrw = ($is_x86_64 ? 64 : 32) - $args{ss} - 1;
+    my $index = randint(width => $addrw, signed => 1);
+    $args{disp} += $index * (1 << $args{ss});
+
+    write_mem_getoffset_base(%args);
+    asm_insn_movT_imm(reg => $args{index}, imm => $index);
+}
+
+sub write_mem_getoffset_base_vindex(%)
+{
+    my (%args) = @_;
+
+    my $addrw = $args{addrw} - $args{ss} - 1;
+    my $base = randint(width => $addrw, signed => 1);
+    $args{disp} += $base * (1 << $args{ss});
+
+    my $datalen = $args{addrw} * $args{count} / 8;
+    prepare_datablock(datalen => $datalen);
+
+    for(my $i = 0; $i < $args{count}; ++$i) {
+        my $index = int(rand(MEMBLOCK_LEN - $args{size}));
+        $index &= ~($args{align} - 1);
+        $index >>= $args{ss};
+
+        insnv(value => $base + $index,
+              width => $args{addrw},
+              bigendian => 0);
+    }
+
+    asm_insn_vmovdqu(l => $args{addrw} * $args{count},
+                     reg => $args{vindex},
+                     base => REG_RAX);
+
+    write_mem_getoffset_base(%args, size => MEMBLOCK_LEN);
+}
+
+sub write_mem_getoffset_rollback(%)
+{
+    my (%args) = @_;
+
+    # The base register contains an address of the form &memblock +
+    # offset. We need to turn it into just offset, otherwise we may
+    # get value mismatches since the memory layout can be different.
+    asm_insn_xchgT(reg => $args{base}, reg2 => REG_RAX)
+        unless $args{base} == REG_RAX;
+    asm_insn_negT(reg2 => REG_RAX);
+    asm_insn_risuop(RISUOP_GETMEMBLOCK);
+
+    # I didn't originally think this was neccessary, but there were
+    # random sign-flag mismatch failures on 32-bit, probably due to
+    # the absolute address being randomly in the positive/negative
+    # range of int32 -- the first NEG would then pollute the EFLAGS
+    # register with this information. Using another NEG is a neat
+    # way of overwriting all this information with consistent values.
+    asm_insn_negT(reg2 => REG_RAX);
+}
+
+sub gen_one_insn($)
+{
+    # Given an instruction-details array, generate an instruction
+    my ($rec) = @_;
+    my $insnname = $rec->{name};
+    my $insnwidth = $rec->{width};
+
+    my $constraintfailures = 0;
+
+    my %insn;
+    my %memopts;
+    INSN: while(1) {
+        my $opcode = randint(width => 32);
+        $opcode &= ~$rec->{fixedbitmask};
+        $opcode |= $rec->{fixedbits};
+
+        # This is not 100 % correct, since $opcode is still padded to
+        # 32-bit width. This is necessary so that extract_fields in
+        # eval_constraints_block and eval_memory_block works
+        # correctly, but we need to fix it up before calling asm_insn.
+        %insn                = ();
+        $insn{opcode}{value} = $opcode;
+        $insn{opcode}{width} = $insnwidth;
+
+        my $v = eval_constraints_block(rec => $rec, insn => \%insn,
+                                       is_x86_64 => $is_x86_64);
+        if ($v && !$is_x86_64 && defined $insn{rex}) {
+            # REX.W is part of the opcode; we will never be able to
+            # generate this instruction in 32-bit mode.
+            return 0 if defined $insn{rex}{w} && $insn{rex}{w};
+            $v = 0;
+        }
+        if ($v) {
+            %memopts = eval_memory_block(rec => $rec, insn => \%insn);
+            $v = write_mem_getoffset(%memopts);
+        }
+        if (!$v) {
+            $constraintfailures++;
+            if ($constraintfailures > 10000) {
+                print "10000 consecutive constraint failures for $insnname constraints\n";
+                exit (1);
+            }
+            next INSN;
+        }
+
+        # OK, we got a good one
+        $constraintfailures = 0;
+
+        # Get rid of the extra padding before calling asm_insn; see
+        # above for details.
+        $insn{opcode}{value} >>= 32 - $insnwidth;
+
+        asm_insn(%insn);
+        write_mem_getoffset_rollback(%memopts) if $memopts{rollback};
+        asm_insn_risuop(RISUOP_COMPAREMEM)     if $memopts{is_write};
+        asm_insn_risuop(RISUOP_COMPARE);
+
+        return 1;
+    }
+}
+
+sub write_test_code($)
+{
+    my ($params) = @_;
+
+    my $numinsns = $params->{ 'numinsns' };
+    my $outfile = $params->{ 'outfile' };
+
+    my %insn_details = %{ $params->{ 'details' } };
+    my @keys = @{ $params->{ 'keys' } };
+
+    $is_x86_64 = $params->{ 'x86_64' };
+    my $xfeatures = $params->{ 'xfeatures' };
+
+    my %vregs   = ();
+    $vregs{ymm} = $xfeatures eq 'avx';
+    $vregs{xmm} = $vregs{ymm} || $xfeatures eq 'sse';
+    $vregs{mm}  = $vregs{xmm} || $xfeatures eq 'mmx';
+
+    open_bin($outfile);
+
+    # TODO better random number generator?
+    srand(0);
+
+    print "Generating code using patterns: @keys...\n";
+    progress_start(78, $numinsns);
+
+    write_memblock_setup();
+
+    # memblock setup doesn't clean its registers, so this must come afterwards.
+    write_random_register_data(vregs => \%vregs);
+
+    for (my $i = 0; $i < $numinsns;) {
+        my $insn_enc = $keys[int rand (@keys)];
+
+        next if !gen_one_insn($insn_details{$insn_enc});
+        $i += 1;
+
+        # 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(vregs => \%vregs);
+        }
+        progress_update($i);
+    }
+    asm_insn_risuop(RISUOP_TESTEND);
+    progress_end();
+    close_bin();
+}
+
+1;
-- 
2.20.1



  parent reply	other threads:[~2019-07-11 22:34 UTC|newest]

Thread overview: 49+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-07-11 22:32 [Qemu-devel] [RISU PATCH v3 00/18] Support for generating x86 SIMD test images Jan Bobek
2019-07-11 22:32 ` [Qemu-devel] [RISU PATCH v3 01/18] risugen_common: add helper functions insnv, randint Jan Bobek
2019-07-12  5:48   ` Richard Henderson
2019-07-14 21:55     ` Jan Bobek
2019-07-12 12:41   ` Alex Bennée
2019-07-11 22:32 ` [Qemu-devel] [RISU PATCH v3 02/18] risugen_common: split eval_with_fields into extract_fields and eval_block Jan Bobek
2019-07-11 22:32 ` [Qemu-devel] [RISU PATCH v3 03/18] risugen_x86_asm: add module Jan Bobek
2019-07-12 14:11   ` Richard Henderson
2019-07-14 22:04     ` Jan Bobek
2019-07-11 22:32 ` [Qemu-devel] [RISU PATCH v3 04/18] risugen_x86_constraints: " Jan Bobek
2019-07-12 14:24   ` Richard Henderson
2019-07-14 22:39     ` Jan Bobek
2019-07-21  1:54   ` Richard Henderson
2019-07-22 13:41     ` Jan Bobek
2019-07-11 22:32 ` [Qemu-devel] [RISU PATCH v3 05/18] risugen_x86_memory: " Jan Bobek
2019-07-21  1:58   ` Richard Henderson
2019-07-22 13:53     ` Jan Bobek
2019-07-11 22:32 ` Jan Bobek [this message]
2019-07-21  2:02   ` [Qemu-devel] [RISU PATCH v3 06/18] risugen_x86: " Richard Henderson
2019-07-11 22:32 ` [Qemu-devel] [RISU PATCH v3 07/18] risugen: allow all byte-aligned instructions Jan Bobek
2019-07-11 22:32 ` [Qemu-devel] [RISU PATCH v3 08/18] risugen: add command-line flag --x86_64 Jan Bobek
2019-07-17 17:00   ` Richard Henderson
2019-07-11 22:32 ` [Qemu-devel] [RISU PATCH v3 09/18] risugen: add --xfeatures option for x86 Jan Bobek
2019-07-17 17:01   ` Richard Henderson
2019-07-11 22:32 ` [Qemu-devel] [RISU PATCH v3 10/18] x86.risu: add MMX instructions Jan Bobek
2019-07-20  4:30   ` Richard Henderson
2019-07-11 22:32 ` [Qemu-devel] [RISU PATCH v3 11/18] x86.risu: add SSE instructions Jan Bobek
2019-07-20 17:50   ` Richard Henderson
2019-07-22 13:57     ` Jan Bobek
2019-07-11 22:32 ` [Qemu-devel] [RISU PATCH v3 12/18] x86.risu: add SSE2 instructions Jan Bobek
2019-07-20 21:19   ` Richard Henderson
2019-07-22 14:12     ` Jan Bobek
2019-07-11 22:32 ` [Qemu-devel] [RISU PATCH v3 13/18] x86.risu: add SSE3 instructions Jan Bobek
2019-07-20 21:27   ` Richard Henderson
2019-07-11 22:32 ` [Qemu-devel] [RISU PATCH v3 14/18] x86.risu: add SSSE3 instructions Jan Bobek
2019-07-20 21:52   ` Richard Henderson
2019-07-11 22:32 ` [Qemu-devel] [RISU PATCH v3 15/18] x86.risu: add SSE4.1 and SSE4.2 instructions Jan Bobek
2019-07-20 22:28   ` Richard Henderson
2019-07-11 22:32 ` [Qemu-devel] [RISU PATCH v3 16/18] x86.risu: add AES and PCLMULQDQ instructions Jan Bobek
2019-07-20 22:35   ` Richard Henderson
2019-07-11 22:32 ` [Qemu-devel] [RISU PATCH v3 17/18] x86.risu: add AVX instructions Jan Bobek
2019-07-21  0:04   ` Richard Henderson
2019-07-22 14:23     ` Jan Bobek
2019-07-11 22:33 ` [Qemu-devel] [RISU PATCH v3 18/18] x86.risu: add AVX2 instructions Jan Bobek
2019-07-21  0:46   ` Richard Henderson
2019-07-22 14:41     ` Jan Bobek
2019-07-12 13:34 ` [Qemu-devel] [RISU PATCH v3 00/18] Support for generating x86 SIMD test images Alex Bennée
2019-07-14 23:08   ` Jan Bobek
2019-07-15 10:14     ` Alex Bennée

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20190711223300.6061-7-jan.bobek@gmail.com \
    --to=jan.bobek@gmail.com \
    --cc=alex.bennee@linaro.org \
    --cc=qemu-devel@nongnu.org \
    --cc=richard.henderson@linaro.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).