All of lore.kernel.org
 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.