From: Florian Krohm Date: Wed, 4 Feb 2026 16:03:30 +0000 (+0000) Subject: s390: The 2nd coming of disasm-test X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1500915a838da9f7013e8721bf4bb02b0dc3072f;p=thirdparty%2Fvalgrind.git s390: The 2nd coming of disasm-test With the advent of objdump-based disassembly there is no need anymore to ensure that the disassembled insns out of valgrind match those from objdump. This is now correct by construction. In this patch the disasm-test functionality is changed to ensure that all expected specification exceptions as specified in the Principles of Operations are detected and no unexpected specification exceptions are found. For a given opcode 2 sets of testcases will now be generated: - one where every insn causes a specification exception This catches missed specification exceptions. - one where no insn causes a specification exception This catches unexpected specification exceptions. Changes: Remove command line options --generate and --verify. Those were useful in the early stages of development but not anymore. Remove command line options --show-spec-exc and --no-show-miscompares which are obsolete now. Replace command line option --check-prereq with --check-march=ARCH. Remove command line option --all-except-exrl. Add --exclude option which is more general. Add command line options --spec-exc and --no-spec-exc. Remove functions check_objdump and disasm_same. From verify_stats remove num_mismatch and num_verified members. Rename verify_stats --> test_stats and add num_generated member. Rename verify_disassembly --> verify_spec_exceptions. No longer write .vex file. Write .spec-exc file instead. Factor out functions run_opcode and choose_int_and_iterate. New functions asm_detects_spec_exc and insn_bytes_as_string. Opcode table in opcode.c: - Remove all constraints that do not cause a specification exception. - Change modelling of the "Rotate and ...." opcodes. Previously the i3, i4 and i5 fields are modelled as masks. That was a work-around in order to be able to specify interesting values and is no longer needed. Model those fields as integers instead. Update disasm-test.vgtest. Update README. Copyright year updated. --- diff --git a/none/tests/s390x/disasm-test/README b/none/tests/s390x/disasm-test/README index ef8904323..56a139125 100644 --- a/none/tests/s390x/disasm-test/README +++ b/none/tests/s390x/disasm-test/README @@ -1,8 +1,9 @@ disasm-test -The purpose of this program is to ensure that the disassembly as -generated by s390_disasm matches what objdump -d produces for any -given insn. As such the program runs as part of "make regtest". +The purpose of this program is to ensure that +(a) specification exceptions caused by invalid insn encodings as + described in Principles of Operations are detected +(b) no unexpected specification exceptions are detected How it works @@ -11,11 +12,9 @@ How it works 2) Compile the C file. 3) Run objdump -d on the object file and capture the result in a file. This file will be referred to as "the objdump file". -4) Read the objdump file, extract the insn bytes and disassembly. +4) Read the objdump file, extract the insn bytes. 5) Feed the so-extracted insn bytes into VEX's decode-and-irgen - machinery with disassembly (= tracing frontend) being enabled. -6) Intercept the so-disassembled text and compare it with the - disassembly in the objdump file. + machinery and observe whether specification exceptions are generated. Invocation @@ -26,19 +25,6 @@ disasm-test --all For all opcodes known to disasm-test, generate testcases and verify the disassembly. -disasm-test --all-except-exrl - As the option name suggests. This is how disasm-test is invoked - during regression testing. - -disasm-test --generate OPCODE_NAMEs - For each specified opcode, generate a C file with testcases, - compile it and create the objdump file. - Useful when adding new opcodes. - -disasm-test --verify OBJDUMP_FILEs - For each specified objdump file, verify that the disassembly via VEX - matches. Useful when adding new opcodes. - disasm-test --run OPCODE_NAMEs Combines --generate and --verify. Useful when adding new opcodes. @@ -52,6 +38,9 @@ Other non-debugging options Write out test generation summary. This option is only observed in combination with --all. +--exclude OPCODE_NAMEs + Do not generate testcases for the names opcodes. + --gcc=/path/to/gcc Specify which GCC to use. Default is: gcc on $PATH. @@ -65,14 +54,11 @@ Other non-debugging options Keep generated files: .c file with testcases, object file, objdump file, and .vex file ---show-exc-spec - Show generated insns that cause specification exceptions. - Because insns causing specification exceptions are typically accepted - by GCC and objdump an objdump file may contain them. But comparing - them is pointless. +--spec-exc + Generate only those testcases that cause specification exceptions. ---no-show-miscompares - Do not show miscomparing insns. +--no-spec-exc + Generate only those testcases that do not cause specification exceptions. Debugging options @@ -105,7 +91,8 @@ Why are we picking these values? Boundary cases are *always* interesting, as is 0. '1' is picked because it is odd and '2' is picked because it is even. -Note: if an opcode has more than one GPR (without specification) choose +FIXME: this is obsolete and the code should be changed accordingly +Note: if an opcode has more than one GPR (without constraint) choose different registers. We do this because we want to catch bugs due to mixed up registers. E.g. If the testcase is "brxh r1,r2,8" and s390_disasm produces @@ -116,58 +103,41 @@ need to be different. The same applies to ARs, FPRs and VRs. Adding a new opcode ------------------- See extensive documentation at the top of file opcode.c -Any opcode can be added. It is not necessary for the opcode to have -extended mnemonics. Integration into regression testing ----------------------------------- 1) Observe the exit code - disasm-test --all --no-show-miscompares - - There will be no output to stdout and stderr. If there are no - miscompares in the disassembly the exit code will be 0. Otherwise, - it will be 1. - -2) Observe stderr - disasm-test --all - Miscomparing disassembly will be written to stderr. Exit code as - described above. + There will be no output to stdout and stderr. If there all expected + specification exceptions and no unexpected ones are found the exit + code will be 0. Otherwise, it will be 1. -3) Observe testcase summary +2) Observe testcase summary - disasm-test --all --no-show-miscompares --summary + disasm-test --all --summary - Will write information about #testcases as well as failing ones - to stdout. Exit code as described above. + Will write information about #testcases and observed specification + exceptions to stdout. Exit code as described above. Status ------ -All opcodes which are implemented in valgrind are considered. +All opcodes described in 13th edition of "Principles of Operations" +which are implemented in valgrind are considered. NOTES ----- -(1) Testcase generation for the "Rotate and ..." family of opcodes needs - to be improved. Several interesting cases involving the T- and Z-bit - are not considered. - -(2) Different versions of objdump produce slightly different disassembly. - In 2.38 base register 0 is written as "%r0". In current binutils git - that register will be output as "0". - -(3) Generated testcases may cause specification exceptions. This - happens iff a constraint involves more than one opcode field. +(1) Not all constraints that cause specification exceptions can be + expressed in opcode.c. This happens iff a constraint involves more + than one opcode field. E.g.: for the VREP opcode the M4 value determines which I2 values - are allowed. This constraint cannot be expressed. However, the - disassembly for such insns is not compared. Use --show-spec-exc - to show those insns. + are allowed. This constraint cannot be expressed. -(4) In s390_decode_and_irgen the code peeks past the current insn: +(2) In s390_decode_and_irgen the code peeks past the current insn: /* If next instruction is execute, stop here */ if (dis_res->whatNext == Dis_Continue && @@ -180,9 +150,9 @@ NOTES Perhaps disable the peek-ahead when inside disasm-test by means of some global variable? Not pretty either, but explicit. -(5) For D20XB and D12XB operands add a test with B == X and B != 0 +(3) For D20XB and D12XB operands add a test with B == X and B != 0 Not exactly easy to do. -(6) For objdump-based disassembly the offset in the EXRL insn is expected - to denote a decodable insn. This is obviously not the case here and - therefore causing a miscompare. +(4) The offset in the EXRL insn is expected to denote a decodable insn. + This is obviously not the case here and therefore that opcode is + excluded. diff --git a/none/tests/s390x/disasm-test/disasm-test.post.exp b/none/tests/s390x/disasm-test/disasm-test.post.exp index 2e0d1b893..68c0b9adb 100644 --- a/none/tests/s390x/disasm-test/disasm-test.post.exp +++ b/none/tests/s390x/disasm-test/disasm-test.post.exp @@ -1,4 +1,6 @@ -Total: 148757 tests generated -Total: 148665 insns verified -Total: 0 disassembly mismatches -Total: 92 specification exceptions +Looking for missed specification exceptions +Total: 42131 tests generated +Total: 42131 specification exceptions +Looking for unexpected specification exceptions +Total: 153637 tests generated +Total: 0 specification exceptions diff --git a/none/tests/s390x/disasm-test/disasm-test.vgtest b/none/tests/s390x/disasm-test/disasm-test.vgtest index e45b61ceb..807250ba1 100644 --- a/none/tests/s390x/disasm-test/disasm-test.vgtest +++ b/none/tests/s390x/disasm-test/disasm-test.vgtest @@ -2,9 +2,25 @@ # We do not want disasm-test to be run under the auspices of valgrind. # Therefore the real test here is run in the "post" command. # -prereq: ./disasm-test --check-prereq +# Excluded opcodes: +# exrl - Offset is expected to denote a decodable insn; no such scaffolding +# kma - cannot express constraint that r3 must be different from r1 and r2 +# The following need to be fixed +# vmsl - m6 does not cause a spec exc. +# vgef - runs into an assert instead of spec. exc. +# vgeg - runs into an assert instead of spec. exc. +# vscef - runs into an assert instead of spec. exc. +# vsceg - runs into an assert instead of spec. exc. +# vstef - runs into an assert instead of spec. exc. +# popcnt - s390_insn_assert is wrong; just "may not operate correctly" +# srnmb - The compile-time checkable constraint cannot be expressed in opcode.c +# But the s390_insn_assert is wrong nevertheless because bits [0:55] are ignored. +# Need to mask d2 before checking (d2 & 0xff) +# vster - should be s390_insn_assert(m3 >= 1 && m3 <= 3); // 3 not 4 + +prereq: ./disasm-test --check-march=arch14 prog: /bin/true -post: ./disasm-test --all-except-exrl --summary +post: ./disasm-test --gcc-flags=-march=arch14 --all --exclude exrl kma vmsl vgef vgeg vscef vsceg vstef popcnt srnmb vster --summary vgopts: -q stderr_filter: stderrB_filter: diff --git a/none/tests/s390x/disasm-test/generate.c b/none/tests/s390x/disasm-test/generate.c index abde6bfef..d42983ff8 100644 --- a/none/tests/s390x/disasm-test/generate.c +++ b/none/tests/s390x/disasm-test/generate.c @@ -4,7 +4,7 @@ This file is part of Valgrind, a dynamic binary instrumentation framework. - Copyright (C) 2024-2025 Florian Krohm + Copyright (C) 2024-2026 Florian Krohm This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -108,11 +108,6 @@ random_reg(opnd_t reg_kind, int r0_allowed) } -#if 0 -/* These functions are currently unused. But may become useful in - alternate test generation strategies that use random values instead - of hardwired interesting ones. */ - static unsigned random_uint(unsigned num_bits) { @@ -141,20 +136,6 @@ random_sint(unsigned num_bits) } -static unsigned -d12_value(void) -{ - return random_uint(12); -} - - -static int -d20_value(void) -{ - return random_sint(20); -} - - static unsigned uint_value(unsigned num_bits) { @@ -171,7 +152,7 @@ sint_value(unsigned num_bits) fatal("integer operand > 32 bits not supported\n"); return random_sint(num_bits); } -#endif + /* MASK is a bitvector. For an e.g. GPR rk the k'th bit will be set. The function returns a register number which has not been used and @@ -193,6 +174,20 @@ unique_reg(opnd_t reg_kind, unsigned regno, unsigned *mask) } +/* Is VALUE an allowed value for OPERAND (which has a constraint) */ +static int +is_allowed_value(long long value, const opnd *operand) +{ + int num_val = operand->allowed_values[0]; + + for (int i = 1; i <= num_val; ++i) + if (value == operand->allowed_values[i]) + return 1; + + return 0; +} + + /* Field */ typedef struct { const opnd *operand; // the operand to which this field belongs @@ -203,11 +198,15 @@ typedef struct { } field; static void choose_reg_and_iterate(FILE *, const opcode *, const opnd *, - field [], unsigned); + field [], unsigned, int); +static void choose_int_and_iterate(FILE *, const opcode *, const opnd *, + field [], unsigned, const long long *, + unsigned, int); /* Write out a single ASM statement for OPC. */ static void -write_asm_stmt(FILE *fp, const opcode *opc, const field fields[]) +write_asm_stmt(FILE *fp, const opcode *opc, const field fields[], + int gen_spec_exc_tests) { fprintf(fp, " asm volatile(\"%s ", opc->name); @@ -293,7 +292,8 @@ write_asm_stmt(FILE *fp, const opcode *opc, const field fields[]) assert(0); } } - fprintf(fp, "\");\n"); + fprintf(fp, "\");"); + fprintf(fp, " // %s spec. exception\n", gen_spec_exc_tests ? " " : "no"); ++num_tests; } @@ -302,11 +302,12 @@ write_asm_stmt(FILE *fp, const opcode *opc, const field fields[]) /* IX identifies the element of the FIELDS array to which a value will be assigned in this iteration. */ static void -iterate(FILE *fp, const opcode *opc, field fields[], unsigned ix) +iterate(FILE *fp, const opcode *opc, field fields[], unsigned ix, + int gen_spec_exc_tests) { /* All fields are assigned. Write out the asm stmt */ if (ix == opc->num_fields) { - write_asm_stmt(fp, opc, fields); + write_asm_stmt(fp, opc, fields, gen_spec_exc_tests); return; } @@ -318,19 +319,19 @@ iterate(FILE *fp, const opcode *opc, field fields[], unsigned ix) if (operand->name[0] == 'b' || operand->name[0] == 'x') { /* Choose r0 */ f->assigned_value = 0; - iterate(fp, opc, fields, ix + 1); + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); /* Choose any GPR other than r0 */ f->assigned_value = random_reg(operand->kind, /* r0_allowed */ 0); - iterate(fp, opc, fields, ix + 1); + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); } else { - choose_reg_and_iterate(fp, opc, operand, fields, ix); + choose_reg_and_iterate(fp, opc, operand, fields, ix, gen_spec_exc_tests); } break; case OPND_AR: case OPND_FPR: case OPND_VR: - choose_reg_and_iterate(fp, opc, operand, fields, ix); + choose_reg_and_iterate(fp, opc, operand, fields, ix, gen_spec_exc_tests); break; case OPND_D12B: @@ -343,7 +344,7 @@ iterate(FILE *fp, const opcode *opc, field fields[], unsigned ix) for (int i = 0; i < sizeof values / sizeof *values; ++i) { f->assigned_value = values[i]; - iterate(fp, opc, fields, ix + 1); + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); } } else if (f->is_length) { /* Choose these interesting values */ @@ -351,20 +352,20 @@ iterate(FILE *fp, const opcode *opc, field fields[], unsigned ix) for (int i = 0; i < sizeof values / sizeof *values; ++i) { f->assigned_value = values[i]; - iterate(fp, opc, fields, ix + 1); + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); } } else if (f->is_vr) { /* v0 is not special AFAICT */ f->assigned_value = random_reg(OPND_VR, /* r0_allowed */ 11); - iterate(fp, opc, fields, ix + 1); + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); } else { /* Base or index register */ /* Choose r0 */ f->assigned_value = 0; - iterate(fp, opc, fields, ix + 1); + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); /* Choose any GPR other than r0 */ f->assigned_value = random_reg(OPND_GPR, /* r0_allowed */ 0); - iterate(fp, opc, fields, ix + 1); + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); } break; @@ -378,60 +379,36 @@ iterate(FILE *fp, const opcode *opc, field fields[], unsigned ix) for (int i = 0; i < sizeof values / sizeof *values; ++i) { f->assigned_value = values[i]; - iterate(fp, opc, fields, ix + 1); + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); } } else { /* base or index register */ f->assigned_value = 0; - iterate(fp, opc, fields, ix + 1); + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); f->assigned_value = random_reg(OPND_GPR, /* r0_allowed */ 0); - iterate(fp, opc, fields, ix + 1); + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); } break; case OPND_SINT: - case OPND_PCREL: - if (operand->allowed_values == NULL) { - /* No constraint: Choose these interesting values */ - const long long values[] = { - 0, 1, 2, -1, -2, (1LL << (operand->num_bits - 1)) - 1, - -(1LL << (operand->num_bits - 1)) - }; - - for (int i = 0; i < sizeof values / sizeof *values; ++i) { - f->assigned_value = values[i]; - iterate(fp, opc, fields, ix + 1); - } - } else { - /* Constraint. Choose only allowed values */ - unsigned num_val = operand->allowed_values[0]; - for (int i = 1; i <= num_val; ++i) { - f->assigned_value = operand->allowed_values[i]; - iterate(fp, opc, fields, ix + 1); - } - } + case OPND_PCREL: { + const long long values[] = { + 0, 1, 2, -1, -2, (1LL << (operand->num_bits - 1)) - 1, + -(1LL << (operand->num_bits - 1)) + }; + choose_int_and_iterate(fp, opc, operand, fields, ix, values, + sizeof values / sizeof *values, gen_spec_exc_tests); break; + } - case OPND_UINT: - if (operand->allowed_values == NULL) { - /* No constraint: Choose these interesting values */ - const long long values[] = { - 0, 1, 2, (1LL << operand->num_bits) - 1 - }; - - for (int i = 0; i < sizeof values / sizeof *values; ++i) { - f->assigned_value = values[i]; - iterate(fp, opc, fields, ix + 1); - } - } else { - /* Constraint. Choose only allowed values */ - unsigned num_val = operand->allowed_values[0]; - for (int i = 1; i <= num_val; ++i) { - f->assigned_value = operand->allowed_values[i]; - iterate(fp, opc, fields, ix + 1); - } - } + case OPND_UINT: { + const long long values[] = { + 0, 1, 2, (1LL << operand->num_bits) - 1 + }; + choose_int_and_iterate(fp, opc, operand, fields, ix, values, + sizeof values / sizeof *values, gen_spec_exc_tests); break; + } case OPND_MASK: if (operand->allowed_values == NULL) { @@ -439,14 +416,32 @@ iterate(FILE *fp, const opcode *opc, field fields[], unsigned ix) unsigned maxval = (1u << operand->num_bits) - 1; for (int val = 0; val <= maxval; ++val) { f->assigned_value = val; - iterate(fp, opc, fields, ix + 1); + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); } } else { - /* Constraint. Choose only allowed values */ - unsigned num_val = operand->allowed_values[0]; - for (int i = 1; i <= num_val; ++i) { - f->assigned_value = operand->allowed_values[i]; - iterate(fp, opc, fields, ix + 1); + if (gen_spec_exc_tests) { + /* Choose only disallowed values */ + if (asm_detects_spec_exc(operand)) { + /* Pick an allowed value to avoid an asm error message */ + f->assigned_value = operand->allowed_values[1]; + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); + } else { + unsigned maxval = (1 << operand->num_bits) - 1; + /* Enumerate all possible disallowed values for the operand */ + for (int val = 0; val <= maxval; ++val) { + if (! is_allowed_value(val, operand)) { + f->assigned_value = val; + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); + } + } + } + } else { + /* Constraint. Choose only allowed values */ + unsigned num_val = operand->allowed_values[0]; + for (int i = 1; i <= num_val; ++i) { + f->assigned_value = operand->allowed_values[i]; + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); + } } } break; @@ -459,7 +454,7 @@ iterate(FILE *fp, const opcode *opc, field fields[], unsigned ix) static void -generate(FILE *fp, const opcode *opc) +generate(FILE *fp, const opcode *opc, int gen_spec_exc_tests) { /* Array of opcode fields to which we need to assign values. */ field fields[opc->num_fields]; @@ -531,12 +526,12 @@ generate(FILE *fp, const opcode *opc) } assert(ix == opc->num_fields); - iterate(fp, opc, fields, 0); + iterate(fp, opc, fields, 0, gen_spec_exc_tests); } unsigned -generate_tests(const opcode *opc) +generate_tests(const opcode *opc, int gen_spec_exc_tests) { srand(42); @@ -545,8 +540,8 @@ generate_tests(const opcode *opc) num_tests = 0; - char file[strlen(opc->name) + 3]; - sprintf(file, "%s.c", opc->name); + char file[strlen(opc->name) + 10]; // large enough + sprintf(file, "%s-%s.c", opc->name, gen_spec_exc_tests ? "se" : "no-se"); FILE *fp = fopen(file, "w"); if (fp == NULL) { @@ -558,18 +553,18 @@ generate_tests(const opcode *opc) fprintf(fp, "main(void)\n"); fprintf(fp, "{\n"); fprintf(fp, " asm volatile(\"%s\");\n", MARK); - generate(fp, opc); + generate(fp, opc, gen_spec_exc_tests); fprintf(fp, " asm volatile(\"%s\");\n", MARK); fprintf(fp, "}\n"); fclose(fp); if (verbose) - printf("...%u testcases generated for '%s'\n", num_tests, - opc->name); + printf("...%u testcases generated for '%s'\n", num_tests, opc->name); - run_cmd("%s -c %s %s.c", gcc, gcc_flags, opc->name); - run_cmd("%s --disassemble=%s %s.o > %s.dump", objdump, FUNCTION, - opc->name, opc->name); + const char *ext = gen_spec_exc_tests ? "se" : "no-se"; + run_cmd("%s -c %s %s", gcc, gcc_flags, file); + run_cmd("%s --disassemble=%s %s-%s.o > %s-%s.dump", objdump, FUNCTION, + opc->name, ext, opc->name, ext); return num_tests; } @@ -577,20 +572,87 @@ generate_tests(const opcode *opc) static void choose_reg_and_iterate(FILE *fp, const opcode *opc, const opnd *operand, - field fields[], unsigned ix) + field fields[], unsigned ix, int gen_spec_exc_tests) { field *f = fields + ix; if (operand->allowed_values == NULL) { /* No constraint. Pick register at random. */ f->assigned_value = random_reg(operand->kind, /* r0_allowed */ 1); - iterate(fp, opc, fields, ix + 1); + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); } else { - /* Constraint. Choose only allowed values */ - unsigned num_val = operand->allowed_values[0]; - for (int i = 1; i <= num_val; ++i) { - f->assigned_value = operand->allowed_values[i]; - iterate(fp, opc, fields, ix + 1); + if (gen_spec_exc_tests) { + /* Choose only disallowed values */ + if (asm_detects_spec_exc(operand)) { + /* Pick an allowed value to avoid an asm error message */ + f->assigned_value = operand->allowed_values[1]; + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); + } else { + unsigned maxval = (1 << operand->num_bits) - 1; + /* Enumerate all possible disallowed values for the operand */ + for (int val = 0; val <= maxval; ++val) { + if (! is_allowed_value(val, operand)) { + f->assigned_value = val; + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); + } + } + } + } else { + /* Choose only allowed values */ + unsigned num_val = operand->allowed_values[0]; + for (int i = 1; i <= num_val; ++i) { + f->assigned_value = operand->allowed_values[i]; + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); + } + } + } +} + + +static void +choose_int_and_iterate(FILE *fp, const opcode *opc, const opnd *operand, + field fields[], unsigned ix, const long long *values, + unsigned num_values, int gen_spec_exc_tests) +{ + field *f = fields + ix; + + if (operand->allowed_values == NULL) { + /* No constraint: Choose the passed-in interesting values */ + for (int i = 0; i < num_values; ++i) { + f->assigned_value = values[i]; + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); + } + } else { + if (gen_spec_exc_tests) { + /* Choose only disallowed values */ + if (operand->num_bits <= 4) { + unsigned maxval = (1 << operand->num_bits) - 1; + /* Enumerate all possible disallowed values for the operand */ + for (int val = 0; val <= maxval; ++val) { + if (! is_allowed_value(val, operand)) { + f->assigned_value = val; + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); + } + } + } else { + /* Choose 4 random values */ + for (int count = 0; count < 4; ) { + long long val = + operand->is_unsigned ? uint_value(operand->num_bits) + : sint_value(operand->num_bits); + if (is_allowed_value(val, operand)) continue; + ++count; + f->assigned_value = val; + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); + } + } + } else { + /* Choose only allowed values */ + unsigned num_val = operand->allowed_values[0]; + for (int i = 1; i <= num_val; ++i) { + f->assigned_value = operand->allowed_values[i]; + iterate(fp, opc, fields, ix + 1, gen_spec_exc_tests); + } } } } diff --git a/none/tests/s390x/disasm-test/main.c b/none/tests/s390x/disasm-test/main.c index 6d8f8353b..286c9ef40 100644 --- a/none/tests/s390x/disasm-test/main.c +++ b/none/tests/s390x/disasm-test/main.c @@ -4,7 +4,7 @@ This file is part of Valgrind, a dynamic binary instrumentation framework. - Copyright (C) 2024-2025 Florian Krohm + Copyright (C) 2024-2026 Florian Krohm This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -25,7 +25,7 @@ #include // NULL #include // exit, malloc #include // vfprintf -#include // isdigit +#include // isalpha #include // va_list #include // strchr #include // assert @@ -33,61 +33,63 @@ #include "vex.h" // vex_init #include "main.h" -int verbose, debug, show_spec_exc, show_miscompares; +int verbose, debug; const char *gcc = "gcc"; // path to GCC const char *objdump = "objdump"; // path to objdump const char *gcc_flags = "-march=arch14"; -#define MIN_OBJDUMP_VERSION 2044000 /* 2.44 */ - #define CHECK_CLO(x, s) (strncmp(x, s, sizeof s - 1) == 0) static const char usage[] = "Usage:\n\n" - "disasm-test --generate OPCODES\n" - " Generate testcases for the given opcodes and prepare objdump files.\n\n" - "disasm-test --verify FILES\n" - " Read specified objdump files and compare with VEX disassembly.\n\n" "disasm-test --run OPCODES\n" - " Generate testcases for the given opcodes and compare the disassembly.\n\n" + " Generate testcases for the given opcodes and check for missed and unexpected specification exceptions.\n\n" "disasm-test --all\n" - " For all opcodes generate testcases and compare the disassembly.\n\n" + " For all opcodes generate testcases and check for missed and unexpected specification exceptions.\n\n" "disasm-test --unit-test\n" " Run unit tests. All other command line options are ignored.\n\n" "Additional options:\n" " --verbose\n" " --debug\n" + " --spec-exc - Only generate testcases causing spec. exceptions\n" + " --no-spec-exc - Do not generate testcases causing spec. exceptions\n" + " --exclude OPCODES - Do not generate testcases for named opcodes\n" " --gcc=/path/to/gcc\n" " --gcc-flags=FLAGS\n" " --objdump=/path/to/objdump\n" " --keep-temp - Do not remove temporary files\n" " --summary - Output test generation summary (with --all)\n" " --unit-test - Run unit tests\n" - " --show-spec-exc - Show insns causing specification exceptions\n" - " --no-show-miscompares - Do not show disassembly miscompares\n" - " --check-prereq - Check prerequisites (e.g. objdump version)\n" + " --check-march=ARCH - Check whether GCC supports -march=ARCH\n" ; -static void remove_temp_files(const char *); +static int check_march_level(const char *); +static void remove_temp_files(const char *, int); static int opcode_has_errors(const opcode *); -static int check_objdump(void); +static int is_excluded_opcode(const opcode *); +static int skip_opcode(const opcode *, int); +static test_stats run_opcode(opcode *, int); static int keep_temp = 0; static int summary = 0; +static const char **excluded_opcodes; +static unsigned num_excluded_opcodes; +#define GEN_SPEC_EXC 1 +#define GEN_NO_SPEC_EXC 2 -/* Return code: 0 no disassembly mismatches - Return code: 1 at least one disassembly mismatch - Specification exceptions do not influence the return code. */ +/* Return code: 0 No missed or unexpected specification exceptions + Return code: 1 False missed and/or unexpected specification exceptions found */ int main(int argc, char *argv[]) { - int all = 0, verify = 0, generate = 0, unit_test = 0, all_except_exrl = 0; - int num_clargs = 0; - int run = 0, check_prereq = 0; - const char *clargs[argc]; + int all = 0, unit_test = 0, run = 0; + int num_to_run = 0, check_march = 0; + int mode = 0; + const char *to_run[argc]; + const char *arch; assert(sizeof(long long) == 8); @@ -95,39 +97,29 @@ main(int argc, char *argv[]) setlinebuf(stdout); setlinebuf(stderr); - show_miscompares = 1; - /* Collect options and arguments */ for (int i = 1; i < argc; ++i) { const char *clo = argv[i]; - if (CHECK_CLO(clo, "--verify")) { - verify = 1; - } else if (CHECK_CLO(clo, "--generate")) { - generate = 1; - } else if (CHECK_CLO(clo, "--all-except-exrl")) { - all = 1; - all_except_exrl = 1; - } else if (CHECK_CLO(clo, "--all")) { + if (CHECK_CLO(clo, "--all")) { all = 1; } else if (CHECK_CLO(clo, "--verbose")) { verbose = 1; } else if (CHECK_CLO(clo, "--debug")) { debug = 1; + } else if (CHECK_CLO(clo, "--spec-exc")) { + mode |= GEN_SPEC_EXC; + } else if (CHECK_CLO(clo, "--no-spec-exc")) { + mode |= GEN_NO_SPEC_EXC; } else if (CHECK_CLO(clo, "--summary")) { summary = 1; } else if (CHECK_CLO(clo, "--unit-test")) { unit_test = 1; - } else if (CHECK_CLO(clo, "--show-spec-exc")) { - show_spec_exc = 1; - } else if (CHECK_CLO(clo, "--no-show-miscompares")) { - show_miscompares = 0; } else if (CHECK_CLO(clo, "--keep-temp")) { keep_temp = 1; - } else if (CHECK_CLO(clo, "--run")) { - run = 1; - } else if (CHECK_CLO(clo, "--check-prereq")) { - check_prereq = 1; + } else if (CHECK_CLO(clo, "--check-march=")) { + check_march = 1; + arch = strchr(clo, '=') + 1; } else if (CHECK_CLO(clo, "--help")) { printf("%s\n", usage); return 0; @@ -137,154 +129,273 @@ main(int argc, char *argv[]) gcc_flags = strchr(clo, '=') + 1; } else if (CHECK_CLO(clo, "--objdump=")) { objdump = strchr(clo, '=') + 1; + } else if (CHECK_CLO(clo, "--run")) { + run = 1; + num_to_run = 0; + int j; + for (j = i + 1; j < argc; ++j) { + if (! isalpha(argv[j][0])) + break; + if (get_opcode_by_name(argv[j]) == NULL) + error("'%s' is not a recognised opcode\n", argv[j]); + else + to_run[num_to_run++] = argv[j]; + } + if (num_to_run == 0) + error("Missing opcode(s) for --run\n"); + i = j - 1; + } else if (CHECK_CLO(clo, "--exclude")) { + num_excluded_opcodes = 0; + excluded_opcodes = mallock(argc * sizeof(char *)); + int j; + for (j = i + 1; j < argc; ++j) { + if (argv[j][0] == '-') + break; + excluded_opcodes[num_excluded_opcodes++] = argv[j]; + } + if (num_excluded_opcodes == 0) + error("Missing opcode(s) for --exclude\n"); + i = j - 1; } else { if (strncmp(clo, "--", 2) == 0) fatal("Invalid command line option '%s'\n", clo); - clargs[num_clargs++] = clo; + else + fatal("Excess arguments on command line\n"); } } - if (check_prereq) - return check_objdump(); + if (check_march) + return check_march_level(arch); /* Check consistency of command line options */ - if (verify + generate + run + all + unit_test == 0) - fatal("One of --verify, --generate, --run, --all, or --unit-test " - "is required\n"); - if (verify + generate + run + all + unit_test != 1) - fatal("At most one of --verify, --generate, --run, --all, or " - " --unit-test can be given\n"); + if (run + all + unit_test == 0) + fatal("One of --run, --all, or --unit-test is required\n"); + if (run + all + unit_test != 1) + fatal("At most one of --run, --all, or --unit-test can be given\n"); + + /* Nothing specified: look for both false positives and false negatives */ + if (mode == 0) + mode = GEN_SPEC_EXC | GEN_NO_SPEC_EXC; vex_init(); - if (generate) { - if (num_clargs == 0) - fatal("Missing opcode name[s]\n"); + if (run) { + int rc = 0; + + if (mode & GEN_SPEC_EXC) { + unsigned num_tests = 0, num_spec_exc = 0; + if (verbose) + printf("Looking for missed specification exceptions\n"); - for (int i = 0; i < num_clargs; ++i) { - const char *name = clargs[i]; + for (int i = 0; i < num_to_run; ++i) { + const char *name = to_run[i]; - opcode *opc = get_opcode_by_name(name); + opcode *opc = get_opcode_by_name(name); // never NULL - if (opc == NULL) { - error("'%s' is not a recognised opcode\n", name); - } else if (opcode_has_errors(opc)) { - error("Opcode '%s' ignored due to syntax errors\n", name); - } else { - generate_tests(opc); - release_opcode(opc); + test_stats stats = run_opcode(opc, /* gen-spec-exc-tests */ 1); + num_tests += stats.num_generated; + num_spec_exc += stats.num_spec_exc; } + rc += num_tests != num_spec_exc; } - return 0; - } + if (mode & GEN_NO_SPEC_EXC) { + unsigned num_tests = 0, num_spec_exc = 0; + if (verbose) + printf("Looking for unexpected specification exceptions\n"); - if (verify) { - if (num_clargs == 0) - fatal("Missing file name[s]\n"); + for (int i = 0; i < num_to_run; ++i) { + const char *name = to_run[i]; - int num_mismatch = 0; + opcode *opc = get_opcode_by_name(name); // never NULL - for (int i = 0; i < num_clargs; ++i) { - verify_stats stats = verify_disassembly(clargs[i]); - num_mismatch += stats.num_mismatch; + test_stats stats = run_opcode(opc, /* gen-spec-exc-tests */ 0); + num_tests += stats.num_generated; + num_spec_exc += stats.num_spec_exc; + } + rc += num_spec_exc != 0; } - return num_mismatch != 0; + return rc; } - if (run) { - if (num_clargs == 0) - fatal("Missing opcode name[s]\n"); - - unsigned num_mismatch = 0; - - for (int i = 0; i < num_clargs; ++i) { - const char *name = clargs[i]; - - opcode *opc = get_opcode_by_name(name); + if (all) { + int rc = 0; + + if (mode & GEN_SPEC_EXC) { + unsigned num_tests = 0, num_spec_exc = 0; + if (verbose || summary) + printf("Looking for missed specification exceptions\n"); + for (int i = 0; i < num_opcodes; ++i) { + opcode *opc = get_opcode_by_index(i); // never NULL + + test_stats stats = run_opcode(opc, /* gen-spec-exc-tests */ 1); + num_tests += stats.num_generated; + num_spec_exc += stats.num_spec_exc; + } + if (verbose || summary) { + printf("Total: %6u tests generated\n", num_tests); + printf("Total: %6u specification exceptions\n", num_spec_exc); + } + rc += num_tests != num_spec_exc; + } + if (mode & GEN_NO_SPEC_EXC) { + unsigned num_tests = 0, num_spec_exc = 0; + if (verbose || summary) + printf("Looking for unexpected specification exceptions\n"); + for (int i = 0; i < num_opcodes; ++i) { + opcode *opc = get_opcode_by_index(i); // never NULL + + test_stats stats = run_opcode(opc, /* gen-spec-exc-tests */ 0); + num_tests += stats.num_generated; + num_spec_exc += stats.num_spec_exc; + } + if (verbose || summary) { + printf("Total: %6u tests generated\n", num_tests); + printf("Total: %6u specification exceptions\n", num_spec_exc); + } + rc += num_spec_exc != 0; + } + return rc != 0; + } - if (opc == NULL) { - error("'%s' is not a recognised opcode\n", name); - } else if (opcode_has_errors(opc)) { - error("Opcode '%s' ignored due to syntax errors\n", name); - } else { - generate_tests(opc); + if (unit_test) + run_unit_tests(); - char file[strlen(name) + 10]; // large enough - sprintf(file, "%s.dump", name); + return 0; +} - verify_stats stats = verify_disassembly(file); - num_mismatch += stats.num_mismatch; - if (! keep_temp) - remove_temp_files(name); - release_opcode(opc); - } - } - return num_mismatch != 0; +static test_stats +run_opcode(opcode *opc, int gen_spec_exc_tests) +{ + test_stats stats = { 0, 0 }; // return value + + if (opcode_has_errors(opc)) { + error("Opcode '%s' ignored due to syntax errors\n", opc->name); + } else if (skip_opcode(opc, gen_spec_exc_tests)) { + if (verbose) + printf("Opcode '%s' skipped\n", opc->name); + } else if (is_excluded_opcode(opc)) { + if (verbose) + printf("Opcode '%s' excluded via command line\n", opc->name); + } else { + unsigned num_generated = generate_tests(opc, gen_spec_exc_tests); + + stats = verify_spec_exceptions(opc, gen_spec_exc_tests); + stats.num_generated = num_generated; + + if (! keep_temp) + remove_temp_files(opc->name, gen_spec_exc_tests); + release_opcode(opc); } + return stats; +} - if (all) { - if (num_clargs != 0) - fatal("Excess arguments on command line\n"); - unsigned num_tests, num_verified, num_mismatch, num_spec_exc; - num_tests = num_verified = num_mismatch = num_spec_exc = 0; +/* The GNU assembler detects certain insns that would cause a + specification exception, namely: + - an invalid register identifying a GPR pair is used + - an invalid register identifying a FPR pair is used + We do not want the assembler to complain and therefore need + to construct testcases accordingly. */ +int +asm_detects_spec_exc(const opnd *operand) +{ + assert(operand->allowed_values); - for (int i = 0; i < num_opcodes; ++i) { - opcode *opc = get_opcode_by_index(i); // never NULL + if (operand->kind == OPND_GPR) { + /* Check for constraint on a GPR pair. */ + int expected[8] = { 0,2,4,6,8,10,12,14 }; + if (operand->allowed_values[0] != 8) return 0; - if (all_except_exrl && strcmp(opc->name, "exrl") == 0) - continue; + for (int i = 1; i <= 8; ++i) { + if (operand->allowed_values[i] != expected[i - 1]) + return 0; + } + return 1; + } - if (opcode_has_errors(opc)) { - error("Opcode '%s' ignored due to syntax errors\n", - opc->name); - continue; - } - num_tests += generate_tests(opc); + if (operand->kind == OPND_FPR) { + /* Check for constraint on an FPR pair. */ + int expected[8] = { 0,1,4,5,8,9,12,13 }; + if (operand->allowed_values[0] != 8) return 0; - char file[strlen(opc->name) + 10]; - sprintf(file, "%s.dump", opc->name); + for (int i = 1; i <= 8; ++i) { + if (operand->allowed_values[i] != expected[i - 1]) + return 0; + } + return 1; + } - verify_stats stats = verify_disassembly(file); + return 0; +} - num_verified += stats.num_verified; - num_mismatch += stats.num_mismatch; - num_spec_exc += stats.num_spec_exc; - if (! keep_temp) - remove_temp_files(opc->name); - release_opcode(opc); - } - if (verbose || summary) { - printf("Total: %6u tests generated\n", num_tests); - printf("Total: %6u insns verified\n", num_verified); - printf("Total: %6u disassembly mismatches\n", num_mismatch); - printf("Total: %6u specification exceptions\n", num_spec_exc); +static int +skip_opcode(const opcode *opc, int gen_spec_exc_tests) +{ + if (gen_spec_exc_tests) { + /* Looking for false negatives. I.e. insns that cause spec. exception. + That means: + a) opcode must have at least one constraint (necessary condition) + b) at least one of those constraints is such that a constraint + violation is not detected by the assembler (sufficient condition) + */ + for (int i = 0; i < opc->num_opnds; ++i) { + const opnd *operand = opc->opnds + i; + + if (operand->allowed_values) + if (! asm_detects_spec_exc(operand)) + return 0; } - return num_mismatch != 0; + return 1; + } else { + /* Looking for false positives. I.e. opcodes that should not cause + a spec. exception. That is: + a) opcodes without constraints + b) opcodes with satisfied constraints */ + return 0; } +} - if (unit_test) - run_unit_tests(); - return 0; +static int +check_march_level(const char *arch) +{ + const char *cmd = "%s -c -march=%s /dev/null 2> /dev/null"; + char buf[strlen(gcc) + strlen(cmd) + strlen(arch) + 10]; + sprintf(buf, cmd, gcc, arch); + + int rc = system(buf); + return rc == 0 ? 0 : 1; } static void -remove_temp_files(const char *op) +remove_temp_files(const char *op, int gen_spec_exc_tests) { - char file[strlen(op) + 10]; // large enough - static const char *suffix[] = { ".c", ".o", ".dump", ".vex" }; + char file[strlen(op) + 20]; // large enough + static const char *suffix[] = { ".c", ".o", ".dump", ".spec-exc" }; for (int i = 0; i < sizeof suffix / sizeof *suffix; ++i) { - sprintf(file, "%s%s", op, suffix[i]); + sprintf(file, "%s-%s%s", op, gen_spec_exc_tests ? "se" : "no-se", + suffix[i]); unlink(file); } } +static int +is_excluded_opcode(const opcode *opc) +{ + if (excluded_opcodes) { + for (int i = 0; i < num_excluded_opcodes; ++i) + if (strcmp(opc->name, excluded_opcodes[i]) == 0) + return 1; + } + return 0; +} + + /* A few convenience utilities */ void error(const char *fmt, ...) @@ -349,53 +460,3 @@ opcode_has_errors(const opcode *opc) } return 0; } - - -/* Objdump version 2.44 or later is required, Return 0, if that's - the case. */ -static int -check_objdump(void) -{ - unsigned need = strlen(objdump) + 3 + 1; - char *cmd = mallock(need); - - sprintf(cmd, "%s -V", objdump); - FILE *fp = popen(cmd, "r"); - - /* The version number is expected on the first line and its - format ought to be one of X or X.Y or X.Y.Z where X,Y,Z are - positive integers. */ - int c, rc = 1; - while ((c = fgetc(fp)) != EOF) { - if (c == '\n') break; - if (! isdigit(c)) continue; - - /* Version number is expected to be X or X.Y or X.Y.Z */ - char buf[32]; // assumed large enough - int ix = 0; - do { - buf[ix++] = c; - c = fgetc(fp); - } while (isdigit(c) || c == '.'); - buf[ix] = '\0'; - - unsigned version = 0, v1, v2, v3; - if (sscanf(buf, "%u.%u.%u", &v1,&v2,&v3) == 3) { - version = v1*1000000 + v2*1000 + v3; - } else if (sscanf(buf, "%u.%u", &v1,&v2) == 2) { - version = v1*1000000 + v2*1000; - } else if (sscanf(buf, "%u", &v1) == 1) { - version = v1*1000000; - } else { - error("Could not determine objdump version\n"); - break; - } - if (version >= MIN_OBJDUMP_VERSION) - rc = 0; - break; - } - pclose(fp); - free(cmd); - - return rc; -} diff --git a/none/tests/s390x/disasm-test/main.h b/none/tests/s390x/disasm-test/main.h index cdf75d241..155bc0cb4 100644 --- a/none/tests/s390x/disasm-test/main.h +++ b/none/tests/s390x/disasm-test/main.h @@ -4,7 +4,7 @@ This file is part of Valgrind, a dynamic binary instrumentation framework. - Copyright (C) 2024-2025 Florian Krohm + Copyright (C) 2024-2026 Florian Krohm This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -66,22 +66,22 @@ typedef struct { } opcode; typedef struct { - unsigned num_verified; - unsigned num_mismatch; + unsigned num_generated; unsigned num_spec_exc; -} verify_stats; +} test_stats; __attribute__((format(printf, 1, 2))) void error(const char *, ...); __attribute__((noreturn)) __attribute__((format(printf, 1, 2))) void fatal(const char *, ...); -verify_stats verify_disassembly(const char *); -unsigned generate_tests(const opcode *); +test_stats verify_spec_exceptions(const opcode *, int); +unsigned generate_tests(const opcode *, int); opcode *get_opcode_by_name(const char *); opcode *get_opcode_by_index(unsigned); void release_opcode(opcode *); void run_unit_tests(void); +int asm_detects_spec_exc(const opnd *); void *mallock(unsigned); char *strsave(const char *); @@ -89,8 +89,6 @@ char *strnsave(const char *, unsigned); extern int verbose; extern int debug; -extern int show_spec_exc; -extern int show_miscompares; extern unsigned num_opcodes; extern const char *gcc; extern const char *gcc_flags; diff --git a/none/tests/s390x/disasm-test/objdump.c b/none/tests/s390x/disasm-test/objdump.c index cabb0af7c..40208ab5d 100644 --- a/none/tests/s390x/disasm-test/objdump.c +++ b/none/tests/s390x/disasm-test/objdump.c @@ -4,7 +4,7 @@ This file is part of Valgrind, a dynamic binary instrumentation framework. - Copyright (C) 2024-2025 Florian Krohm + Copyright (C) 2024-2026 Florian Krohm This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as diff --git a/none/tests/s390x/disasm-test/objdump.h b/none/tests/s390x/disasm-test/objdump.h index ac6f72e27..33c601cd1 100644 --- a/none/tests/s390x/disasm-test/objdump.h +++ b/none/tests/s390x/disasm-test/objdump.h @@ -4,7 +4,7 @@ This file is part of Valgrind, a dynamic binary instrumentation framework. - Copyright (C) 2024-2025 Florian Krohm + Copyright (C) 2024-2026 Florian Krohm This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -26,7 +26,7 @@ #define OBJDUMP_H /* An opcode which marks the begin and end of a sequence of insns - in a testcase whose disassembly should be verified. */ + in a testcase. */ #define MARK "csch" /* Name of the C function containing the generated insns. */ diff --git a/none/tests/s390x/disasm-test/opcode.c b/none/tests/s390x/disasm-test/opcode.c index 12af90f6a..cb863cfa5 100644 --- a/none/tests/s390x/disasm-test/opcode.c +++ b/none/tests/s390x/disasm-test/opcode.c @@ -4,7 +4,7 @@ This file is part of Valgrind, a dynamic binary instrumentation framework. - Copyright (C) 2024-2025 Florian Krohm + Copyright (C) 2024-2026 Florian Krohm This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -71,12 +71,14 @@ If a set of allowed values is specified for an operand this implies that any other value would cause a specification exception. - UNLESS otherwise noted. */ -/* List of for hardware facilities. +/* List of hardware facilities. The abbreviated name appears as structured comment in below opcode list to - indicate that the opcode is only available when the facility is installed. + indicate that + a) the opcode is only available when the facility is installed OR + b) the opcode semantics depend on the presence of the facility. E.g. a + certain opcode field is ignored when the facility is not present. dflt --> deflate-conversion facility dfp --> decimal floating point facility @@ -396,16 +398,16 @@ static const char *opcodes[] = { "cvdy r1,d20(x2,b2)", // cvdg not implemented - "cu24 r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14},m3:{0,1}", // extr3 no spec exc for m3 + "cu24 r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14},m3", // extr3 - "cu21 r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14},m3:{0,1}", // no spec exc for m3 + "cu21 r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14},m3", // extr3 - "cu42 r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14}", // extr3 - "cu41 r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14}", // extr3 + "cu42 r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14}", // extr3 + "cu41 r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14}", // extr3 - "cu12 r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14},m3:{0,1}", // no spec exc for m3 + "cu12 r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14},m3", // extr3 - "cu14 r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14},m3:{0,1}", // extr3 no spec exc for m3 + "cu14 r1:{0,2,4,6,8,10,12,14},r2:{0,2,4,6,8,10,12,14},m3", // extr3 "cpya a1,a2", @@ -692,7 +694,7 @@ static const char *opcodes[] = { "nnrk r1,r2,r3", // mi3 "nngrk r1,r2,r3", // mi3 - "niai i1:u4{0..3},i2:u4{0..3}", // exhi no spec exc for i1,i2 + "niai i1:u4,i2:u4", // exhi // ntstg not implemented @@ -730,12 +732,12 @@ static const char *opcodes[] = { "pcc", // msa4 // plo not implemented - "ppa r1,r2,m3:{1,15}", // ppa no spec exception for m3 + "ppa r1,r2,m3", // ppa "ppno r1,r2", // msa5 "prno r1,r2", // msa5 - "popcnt r1,r2,m3:{0,8}", // popc no spec exception for m3 + "popcnt r1,r2,m3", // popc "pfd m1,d20(x2,b2)", // gie "pfdrl m1,ri2:s32", // gie @@ -743,29 +745,15 @@ static const char *opcodes[] = { "rll r1,r3,d20(b2)", "rllg r1,r3,d20(b2)", - // Rotate and .... opcodes require special handling - // - // For rosbg and friends - // - Bit #0 of i3 is the T-bit and bit #1 of i3 ought to be 0. - // - i5 is optional and will not be written when 0 - // - // For risbg and friends - // - Bit #0 of i4 is the Z-bit and bit #1 of i4 ought to be 0. - // - i5 is optional and will not be written when 0 - // - // This implies that we need to model i3, i4 and i5 as masks so - // we can manipulate their value when disassembling or suppress - // the mask altogether. Note that we limit the set of allowed values - // for those masks to avoid excessively large numbers of testcases. - "rnsbg r1,r2,m3:u8{0,1,2,63,128,129,191},m4:u6{0,1,2,63},m5:u6{0,1,2,63}", // gie - "rxsbg r1,r2,m3:u8{0,1,2,63,128,129,191},m4:u6{0,1,2,63},m5:u6{0,1,2,63}", // gie - "rosbg r1,r2,m3:u8{0,1,2,63,128,129,191},m4:u6{0,1,2,63},m5:u6{0,1,2,63}", // gie - - "risbg r1,r2,m3:u6{0,1,2,63},m4:u8{0,1,2,63,128,129,191},m5:u6{0,1,2,63}", // gie - "risbgn r1,r2,m3:u6{0,1,2,63},m4:u8{0,1,2,63,128,129,191},m5:u6{0,1,2,63}", // mi1 - - "risbhg r1,r2,m3:u5{0,1,2,31},m4:u8{0,1,2,31,128,129,159},m5:u6{0,1,2,63}", // hiwo - "risblg r1,r2,m3:u5{0,1,2,31},m4:u8{0,1,2,31,128,129,159},m5:u6{0,1,2,63}", // hiwo + "rnsbg r1,r2,i3:u8,i4:u6,i5:u6", // gie + "rxsbg r1,r2,i3:u8,i4:u6,i5:u6", // gie + "rosbg r1,r2,i3:u8,i4:u6,i5:u6", // gie + + "risbg r1,r2,i3:u6,i4:u8,i5:u6", // gie + "risbgn r1,r2,i3:u6,i4:u8,i5:u6", // mi1 + + "risbhg r1,r2,i3:u5,i4:u8,i5:u6", // hiwo + "risblg r1,r2,i3:u5,i4:u8,i5:u6", // hiwo "srst r1,r2", @@ -924,10 +912,10 @@ static const char *opcodes[] = { "tre r1:{0,2,4,6,8,10,12,14},r2", - "troo r1:{0,2,4,6,8,10,12,14},r2,m3:{0,1}", // extr2 no spec exception on m3 - "trot r1:{0,2,4,6,8,10,12,14},r2,m3:{0,1}", // extr2 no spec exception on m3 - "trto r1:{0,2,4,6,8,10,12,14},r2,m3:{0,1}", // extr2 no spec exception on m3 - "trtt r1:{0,2,4,6,8,10,12,14},r2,m3:{0,1}", // extr2 no spec exception on m3 + "troo r1:{0,2,4,6,8,10,12,14},r2,m3", // extr2 + "trot r1:{0,2,4,6,8,10,12,14},r2,m3", // extr2 + "trto r1:{0,2,4,6,8,10,12,14},r2,m3", // extr2 + "trtt r1:{0,2,4,6,8,10,12,14},r2,m3", // extr2 // unpk not implemented // unpka not implemented @@ -1203,7 +1191,7 @@ static const char *opcodes[] = { "vgeg v1,d12(v2,b2),m3:{0,1}", "vgbm v1,i2:u16", "vgm v1,i2:u8,i3:u8,m4:{0..3}", - "vl v1,d12(x2,b2),m3:{0,3,4}", // no spec. exc + "vl v1,d12(x2,b2),m3", "vlr v1,v2", "vlrep v1,d12(x2,b2),m3:{0..3}", "vlebrh v1,d12(x2,b2),m3:{0..7}", // vxe2 @@ -1233,11 +1221,11 @@ static const char *opcodes[] = { "vmrh v1,v2,v3,m4:{0..3}", "vmrl v1,v2,v3,m4:{0..3}", "vpk v1,v2,v3,m4:{1..3}", - "vpks v1,v2,v3,m4:{1..3},m5:{0,1}", // no spec. exception for m5 - "vpkls v1,v2,v3,m4:{1..3},m5:{0,1}", // no spec. exception for m5 + "vpks v1,v2,v3,m4:{1..3},m5", + "vpkls v1,v2,v3,m4:{1..3},m5", "vperm v1,v2,v3,v4", - "vpdi v1,v2,v3,m4:{0,1,4,5}", // no spec. exception for m4 -// "vrep v1,v3,i2:u16,m4:{0..3}", + "vpdi v1,v2,v3,m4", +// "vrep v1,v3,i2:u16,m4:{0..3}", // cannot express constraint "vrepi v1,i2:s16,m3:{0..3}", "vscef v1,d12(v2,b2),m3:{0..3}", "vsceg v1,d12(v2,b2),m3:{0,1}", @@ -1274,9 +1262,9 @@ static const char *opcodes[] = { "vcksm v1,v2,v3", "vec v1,v2,m3:{0..3}", "vecl v1,v2,m3:{0..3}", - "vceq v1,v2,v3,m4:{0..3},m5:{0,1}", // no spec. exception for m5 - "vch v1,v2,v3,m4:{0..3},m5:{0,1}", // no spec. exception for m5 - "vchl v1,v2,v3,m4:{0..3},m5:{0,1}", // no spec. exception for m5 + "vceq v1,v2,v3,m4:{0..3},m5", + "vch v1,v2,v3,m4:{0..3},m5", + "vchl v1,v2,v3,m4:{0..3},m5", "vclz v1,v2,m3:{0..3}", "vctz v1,v2,m3:{0..3}", "vx v1,v2,v3", @@ -1302,7 +1290,7 @@ static const char *opcodes[] = { "vmle v1,v2,v3,m4:{0..2}", "vmo v1,v2,v3,m4:{0..2}", "vmlo v1,v2,v3,m4:{0..2}", - "vmsl v1,v2,v3,v4,m5:{3},m6:{0,4,8,12}", // vxe no spec. exception for m6 + "vmsl v1,v2,v3,v4,m5:{3},m6", // vxe "vnn v1,v2,v3", // vxe "vno v1,v2,v3", "vnx v1,v2,v3", // vxe @@ -1320,8 +1308,8 @@ static const char *opcodes[] = { "vesrl v1,v3,d12(b2),m4:{0..3}", "vsl v1,v2,v3", "vslb v1,v2,v3", - "vsld v1,v2,v3,i4:u8{0..7}", // vxe2 spec exc. - "vsldb v1,v2,v3,i4:u8{0..15}", // no spec. exception - otherwise unpredictable + "vsld v1,v2,v3,i4:u8{0..7}", // vxe2 + "vsldb v1,v2,v3,i4:u8", "vsra v1,v2,v3", "vsrab v1,v2,v3", "vsrd v1,v2,v3,i4:u8{0..7}", // vxe2 @@ -1384,11 +1372,11 @@ static const char *opcodes[] = { // "nnpa", // cannot express constraint // nnpa // sortl not implemented - "vclfnh v1,v2,m3:{3},m4:{0}", // nnpa no spec exc. for m3, m4 but IEEE exc. - "vclfnl v1,v2,m3:{3},m4:{0}", // nnpa no spec exc. for m3, m4 but IEEE exc. - "vcrnf v1,v2,v3,m4:{0},m5:{2}", // nnpa no spec exc. for m4, m5 but IEEE exc. - "vcfn v1,v2,m3:{1},m4:{0}", // nnpa no spec exc. for m3, m4 but IEEE exc. - "vcnf v1,v2,m3:{0},m4:{1}", // nnpa no spec exc. for m3, m4 but IEEE exc. + "vclfnh v1,v2,m3,m4", // nnpa + "vclfnl v1,v2,m3,m4", // nnpa + "vcrnf v1,v2,v3,m4,m5", // nnpa + "vcfn v1,v2,m3,m4", // nnpa + "vcnf v1,v2,m3,m4", // nnpa }; unsigned num_opcodes = sizeof opcodes / sizeof *opcodes; diff --git a/none/tests/s390x/disasm-test/verify.c b/none/tests/s390x/disasm-test/verify.c index b80e270e3..d1686ccdc 100644 --- a/none/tests/s390x/disasm-test/verify.c +++ b/none/tests/s390x/disasm-test/verify.c @@ -4,7 +4,7 @@ This file is part of Valgrind, a dynamic binary instrumentation framework. - Copyright (C) 2024-2025 Florian Krohm + Copyright (C) 2024-2026 Florian Krohm This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -29,14 +29,30 @@ #include "main.h" // verbose #include "vex.h" // vex_disasm -static int disasm_same(const char *, const char *, unsigned); +/* Watch out: returned string is allocated in a static buffer which will + be overwritten in the next invocation. */ +static const char * +insn_bytes_as_string(const unsigned char *bytes, unsigned num_bytes) +{ + static char buf[4 + 1 + 4 + 1 + 4 + 1]; + + char *p = buf; + for (int j = 0; j < num_bytes; j += 2) + p += sprintf(p, "%02X%02X ", bytes[j], bytes[j + 1]); + *--p = '\0'; + + return buf; +} -/* Return number of disassembly mismatches. */ -verify_stats -verify_disassembly(const char *file) + +test_stats +verify_spec_exceptions(const opcode *opc, int gen_spec_exc_tests) { - verify_stats stats = { 0, 0, 0 }; // return value + test_stats stats = { 0, 0 }; // return value + + char file[strlen(opc->name) + 15]; // large enough + sprintf(file, "%s-%s.dump", opc->name, gen_spec_exc_tests ? "se" : "no-se"); objdump_file *ofile = read_objdump(file); if (ofile == NULL) @@ -45,139 +61,45 @@ verify_disassembly(const char *file) if (verbose) printf("...verifying %u insns in '%s'\n", ofile->num_lines, file); - const char *p = strchr(file, '.'); - char vex_file[strlen(file) + 5]; + char se_file[strlen(opc->name) + 15]; // large enough + sprintf(se_file, "%s-%s.spec-exc", opc->name, + gen_spec_exc_tests ? "se" : "no-se"); - if (p == NULL) { - sprintf(vex_file, "%s.vex", file); - } else { - int len = p - file; - strncpy(vex_file, file, len); - strcpy(vex_file + len, ".vex"); - } - FILE *fpvex = fopen(vex_file, "w"); - if (fpvex == NULL) - error("%s: fopen failed\n", vex_file); + FILE *fpse = fopen(se_file, "w"); + if (fpse == NULL) + error("%s: fopen failed\n", se_file); for (int i = 0; i < ofile->num_lines; ++i) { const objdump_line *oline = ofile->lines + i; int spec_exc = 0; const char *disassembly_from_vex = vex_disasm(oline->insn_bytes, &spec_exc); + const char *insn_bytes = + insn_bytes_as_string(oline->insn_bytes, oline->insn_len); - if (spec_exc) { - ++stats.num_spec_exc; - - if (show_spec_exc) { - fprintf(stderr, "*** specification exception for insn "); - for (int j = 0; j < oline->insn_len; ++j) - fprintf(stderr, "%02X", oline->insn_bytes[j]); - fprintf(stderr, " in %s\n", file); - } - /* Instructions causing specification exceptions are not - compared */ + if (disassembly_from_vex == NULL) { + error("Disasm failed for %s\n", insn_bytes); continue; } - if (disassembly_from_vex == NULL) - disassembly_from_vex = "MISSING disassembly from VEX"; - if (fpvex) - fprintf(fpvex, "%s\n", disassembly_from_vex); - - /* Compare disassembled insns */ - ++stats.num_verified; - if (! disasm_same(oline->disassembled_insn, disassembly_from_vex, - oline->address)) { - ++stats.num_mismatch; - if (show_miscompares) { - int n = fprintf(stderr, "*** mismatch VEX: |%s|", - disassembly_from_vex); - fprintf(stderr, "%*c", 50 - n, ' '); - fprintf(stderr, "objdump: |%s|\n", oline->disassembled_insn); - } + if (spec_exc) { + ++stats.num_spec_exc; + + if (fpse) + fprintf(fpse, "%s %s\n", insn_bytes, disassembly_from_vex); + if (! gen_spec_exc_tests) + error("Unexpected spec. exc. detected for %s %s\n", insn_bytes, + disassembly_from_vex); + } else { + if (gen_spec_exc_tests) + error("Spec. exc. not detected for %s %s\n", insn_bytes, + disassembly_from_vex); } } - if (fpvex) - fclose(fpvex); - release_objdump(ofile); - if (verbose) { - printf("...%u insns verified\n", stats.num_verified); - printf("...%u disassembly mismatches\n", stats.num_mismatch); - printf("...%u specification exceptions\n", stats.num_spec_exc); - } + if (fpse) + fclose(fpse); + release_objdump(ofile); return stats; } - - -/* Compare two disassembled insns ignoring white space. Return 1 if - equal. */ -static int -disasm_same(const char *from_objdump, const char *from_vex, - unsigned address) -{ - const char *p1 = from_objdump; - const char *p2 = from_vex; - - while (42) { - while (isspace(*p1)) - ++p1; - while (isspace(*p2)) - ++p2; - if (*p1 == '\0' && *p2 == '\0') - return 1; - if (*p1 == '\0' || *p2 == '\0') - return 0; - - if (*p1 == *p2) { - ++p1; - ++p2; - continue; - } - - /* Consider the case where the VEX disassembly has ".+integer" - or ".-integer" and the objdump disassembly has an hex address - possibly followed by a symbolic address, e.g. . */ - if (*p2++ != '.') return 0; - - long long offset_in_bytes = 0; - unsigned long long target_address = 0; - - while (isxdigit(*p1)) { - target_address *= 16; - if (isdigit(*p1)) - target_address += *p1 - '0'; - else { - int c = tolower(*p1); - if (c >= 'a' && c <= 'f') - target_address += 10 + c - 'a'; - else - return 0; // error - } - ++p1; - } - while (isspace(*p1)) - ++p1; - if (*p1 == '<') { - while (*p1++ != '>') - ; - } - - int is_negative = 0; - if (*p2 == '-') { - is_negative = 1; - ++p2; - } else if (*p2 == '+') - ++p2; - while (isdigit(*p2)) { - offset_in_bytes *= 10; - offset_in_bytes += *p2 - '0'; - ++p2; - } - if (is_negative) - offset_in_bytes *= -1; - - if (address + offset_in_bytes != target_address) return 0; - } -} diff --git a/none/tests/s390x/disasm-test/vex.c b/none/tests/s390x/disasm-test/vex.c index bb850b8eb..3e98aa3b6 100644 --- a/none/tests/s390x/disasm-test/vex.c +++ b/none/tests/s390x/disasm-test/vex.c @@ -4,7 +4,7 @@ This file is part of Valgrind, a dynamic binary instrumentation framework. - Copyright (C) 2024-2025 Florian Krohm + Copyright (C) 2024-2026 Florian Krohm This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -147,5 +147,21 @@ vex_disasm(const unsigned char *codebuf, int *spec_exc) *spec_exc = 1; } + if (last_vex_string) { + /* Compress a sequence of spaces with a single blank. */ + int space_seen = 0; + char *p, *q; + for (p = q = last_vex_string; *p; ++p) { + if (isspace(*p)) { + if (space_seen) continue; + space_seen = 1; + } else { + space_seen = 0; + } + *q++ = *p; + } + *q = '\0'; + } + return last_vex_string; } diff --git a/none/tests/s390x/disasm-test/vex.h b/none/tests/s390x/disasm-test/vex.h index 627c1e55e..da46fb808 100644 --- a/none/tests/s390x/disasm-test/vex.h +++ b/none/tests/s390x/disasm-test/vex.h @@ -4,7 +4,7 @@ This file is part of Valgrind, a dynamic binary instrumentation framework. - Copyright (C) 2024-2025 Florian Krohm + Copyright (C) 2024-2026 Florian Krohm This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as