From 2469b8b66e09213346071209e5631be3cbaee8e5 Mon Sep 17 00:00:00 2001 From: "H. Peter Anvin" Date: Tue, 27 Apr 2021 11:37:42 -0700 Subject: [PATCH] Add {rex} prefix, simplify prefix handling, better error messages Add a {rex} prefix to force REX encoding (typically a redundant 40h prefix). For prefix parsing, we can use t_inttwo to encode the prefix slot number. Give more verbose error messages for encoding mismatches. --- asm/assemble.c | 70 ++++++++++++++++-------- asm/parser.c | 115 ++++++++++++++-------------------------- asm/tokens.dat | 30 +++++++---- common/common.c | 7 +-- include/nasm.h | 19 ++++--- test/vex.asm | 30 +++++++++++ travis/test/vex.asm | 30 +++++++++++ travis/test/vex.bin.t | Bin 25 -> 103 bytes travis/test/vex.json | 17 ++++-- travis/test/vex.stderr | 1 - travis/test/vex1.stderr | 3 ++ travis/test/vex2.stderr | 3 ++ 12 files changed, 204 insertions(+), 121 deletions(-) delete mode 100644 travis/test/vex.stderr create mode 100644 travis/test/vex1.stderr create mode 100644 travis/test/vex2.stderr diff --git a/asm/assemble.c b/asm/assemble.c index ecf5c093..46ce9303 100644 --- a/asm/assemble.c +++ b/asm/assemble.c @@ -935,15 +935,13 @@ int64_t assemble(int32_t segment, int64_t start, int bits, insn *instruction) nasm_nonfatal("instruction not supported in %d-bit mode", bits); break; case MERR_ENCMISMATCH: - nasm_nonfatal("specific encoding scheme not available"); + nasm_nonfatal("instruction not encodable with %s prefix", + prefix_name(instruction->prefixes[PPS_REX])); break; case MERR_BADBND: - nasm_nonfatal("bnd prefix is not allowed"); - break; case MERR_BADREPNE: nasm_nonfatal("%s prefix is not allowed", - (has_prefix(instruction, PPS_REP, P_REPNE) ? - "repne" : "repnz")); + prefix_name(instruction->prefixes[PPS_REP])); break; case MERR_REGSETSIZE: nasm_nonfatal("invalid register set size"); @@ -1644,16 +1642,22 @@ static int64_t calcsize(int32_t segment, int64_t offset, int bits, ins->rex &= ~REX_P; /* Don't force REX prefix due to high reg */ } - switch (ins->prefixes[PPS_VEX]) { + switch (ins->prefixes[PPS_REX]) { case P_EVEX: if (!(ins->rex & REX_EV)) return -1; break; + case P_VEX: case P_VEX3: case P_VEX2: if (!(ins->rex & REX_V)) return -1; break; + case P_REX: + if (ins->rex & (REX_V|REX_EV)) + return -1; + ins->rex |= REX_P; /* Force REX prefix */ + break; default: break; } @@ -1687,16 +1691,19 @@ static int64_t calcsize(int32_t segment, int64_t offset, int bits, nasm_nonfatal("invalid high-16 register in non-AVX-512"); return -1; } - if (ins->rex & REX_EV) + if (ins->rex & REX_EV) { length += 4; - else if (ins->vex_cm != 1 || (ins->rex & (REX_W|REX_X|REX_B)) || - ins->prefixes[PPS_VEX] == P_VEX3) + } else if (ins->vex_cm != 1 || (ins->rex & (REX_W|REX_X|REX_B)) || + ins->prefixes[PPS_REX] == P_VEX3) { + if (ins->prefixes[PPS_REX] == P_VEX2) + nasm_nonfatal("instruction not encodable with {vex2} prefix"); length += 3; - else + } else { length += 2; + } } else if (ins->rex & REX_MASK) { if (ins->rex & REX_H) { - nasm_nonfatal("cannot use high register in rex instruction"); + nasm_nonfatal("cannot use high byte register in rex instruction"); return -1; } else if (bits == 64) { length++; @@ -1849,6 +1856,8 @@ static int emit_prefix(struct out_data *data, const int bits, insn *ins) case P_OSP: c = 0x66; break; + case P_REX: + case P_VEX: case P_EVEX: case P_VEX3: case P_VEX2: @@ -1994,7 +2003,7 @@ static void gencode(struct out_data *data, insn *ins) case 0172: { - int mask = ins->prefixes[PPS_VEX] == P_EVEX ? 7 : 15; + int mask = ins->prefixes[PPS_REX] == P_EVEX ? 7 : 15; const struct operand *opy; c = *codes++; @@ -2054,7 +2063,7 @@ static void gencode(struct out_data *data, insn *ins) case 0270: codes += 2; if (ins->vex_cm != 1 || (ins->rex & (REX_W|REX_X|REX_B)) || - ins->prefixes[PPS_VEX] == P_VEX3) { + ins->prefixes[PPS_REX] == P_VEX3) { bytes[0] = (ins->vex_cm >> 6) ? 0x8f : 0xc4; bytes[1] = (ins->vex_cm & 31) | ((~ins->rex & 7) << 5); bytes[2] = ((ins->rex & REX_W) << (7-3)) | @@ -2383,11 +2392,12 @@ static enum match_result find_match(const struct itemplate **tempp, int i; /* broadcasting uses a different data element size */ - for (i = 0; i < instruction->operands; i++) + for (i = 0; i < instruction->operands; i++) { if (i == broadcast) xsizeflags[i] = instruction->oprs[i].decoflags & BRSIZE_MASK; else xsizeflags[i] = instruction->oprs[i].type & SIZE_MASK; + } merr = MERR_INVALOP; @@ -2507,18 +2517,24 @@ static enum match_result matches(const struct itemplate *itemp, return MERR_INVALOP; /* - * {evex} available? + * {rex/vexn/evex} available? */ - switch (instruction->prefixes[PPS_VEX]) { + switch (instruction->prefixes[PPS_REX]) { case P_EVEX: if (!itemp_has(itemp, IF_EVEX)) return MERR_ENCMISMATCH; break; + case P_VEX: case P_VEX3: case P_VEX2: if (!itemp_has(itemp, IF_VEX)) return MERR_ENCMISMATCH; break; + case P_REX: + if (itemp_has(itemp, IF_VEX) || itemp_has(itemp, IF_EVEX) || + bits != 64) + return MERR_ENCMISMATCH; + break; default: break; } @@ -2667,6 +2683,9 @@ static enum match_result matches(const struct itemplate *itemp, * considered a wildcard match rather than an error. */ opsizemissing = true; + } else if (is_class(REG_HIGH, type) && + instruction->prefixes[PPS_REX]) { + return MERR_ENCMISMATCH; } } else if (is_broadcast && (brcast_num != @@ -2764,13 +2783,14 @@ static enum match_result matches(const struct itemplate *itemp, static enum ea_type process_ea(operand *input, ea *output, int bits, int rfield, opflags_t rflags, insn *ins, - const char **errmsg) + const char **errmsgp) { bool forw_ref = !!(input->opflags & OPFLAG_UNKNOWN); int addrbits = ins->addr_size; int eaflags = input->eaflags; + const char *errmsg = NULL; - *errmsg = "invalid effective address"; /* Default error message */ + errmsg = NULL; output->type = EA_SCALAR; output->rip = false; @@ -2793,7 +2813,7 @@ static enum ea_type process_ea(operand *input, ea *output, int bits, /* broadcasting is not available with a direct register operand. */ if (input->decoflags & BRDCAST_MASK) { - *errmsg = "broadcast not allowed with register operand"; + errmsg = "broadcast not allowed with register operand"; goto err; } @@ -2809,7 +2829,7 @@ static enum ea_type process_ea(operand *input, ea *output, int bits, /* Embedded rounding or SAE is not available with a mem ref operand. */ if (input->decoflags & (ER | SAE)) { - *errmsg = "embedded rounding is available only with " + errmsg = "embedded rounding is available only with " "register-register operations"; goto err; } @@ -2838,7 +2858,7 @@ static enum ea_type process_ea(operand *input, ea *output, int bits, } if (bits == 64 && !(IP_REL & ~input->type) && (eaflags & EAF_SIB)) { - *errmsg = "instruction requires SIB encoding, cannot be RIP-relative"; + errmsg = "instruction requires SIB encoding, cannot be RIP-relative"; goto err; } @@ -3224,6 +3244,14 @@ static enum ea_type process_ea(operand *input, ea *output, int bits, return output->type; err: + if (!errmsg) { + /* Default error message */ + static char invalid_address_msg[40]; + snprintf(invalid_address_msg, sizeof invalid_address_msg, + "invalid %d-bit effective address", bits); + errmsg = invalid_address_msg; + } + *errmsgp = errmsg; return output->type = EA_INVALID; } diff --git a/asm/parser.c b/asm/parser.c index 584e40c9..daafa920 100644 --- a/asm/parser.c +++ b/asm/parser.c @@ -55,50 +55,6 @@ static int end_expression_next(void); static struct tokenval tokval; -static int prefix_slot(int prefix) -{ - switch (prefix) { - case P_WAIT: - return PPS_WAIT; - case R_CS: - case R_DS: - case R_SS: - case R_ES: - case R_FS: - case R_GS: - return PPS_SEG; - case P_LOCK: - return PPS_LOCK; - case P_REP: - case P_REPE: - case P_REPZ: - case P_REPNE: - case P_REPNZ: - case P_XACQUIRE: - case P_XRELEASE: - case P_BND: - case P_NOBND: - return PPS_REP; - case P_O16: - case P_O32: - case P_O64: - case P_OSP: - return PPS_OSIZE; - case P_A16: - case P_A32: - case P_A64: - case P_ASP: - return PPS_ASIZE; - case P_EVEX: - case P_VEX3: - case P_VEX2: - return PPS_VEX; - default: - nasm_panic("Invalid value %d passed to prefix_slot()", prefix); - return -1; - } -} - static void process_size_override(insn *result, operand *op) { if (tasm_compatible_mode) { @@ -185,7 +141,7 @@ static void process_size_override(insn *result, operand *op) } /* - * Brace decorators are are parsed here. opmask and zeroing + * Braced keywords are are parsed here. opmask and zeroing * decorators can be placed in any order. e.g. zmm1 {k2}{z} or zmm2 * {z}{k3} decorator(s) are placed at the end of an operand. */ @@ -715,42 +671,51 @@ restart_parse: if (i == TOKEN_EOS) goto fail; - while (i == TOKEN_PREFIX || - (i == TOKEN_REG && IS_SREG(tokval.t_integer))) { - first = false; + while (i) { + int slot = PPS_SEG; - /* - * Handle special case: the TIMES prefix. - */ - if (i == TOKEN_PREFIX && tokval.t_integer == P_TIMES) { - expr *value; + if (i == TOKEN_PREFIX) { + slot = tokval.t_inttwo; - i = stdscan(NULL, &tokval); - value = evaluate(stdscan, NULL, &tokval, NULL, pass_stable(), NULL); - i = tokval.t_type; - if (!value) /* Error in evaluator */ - goto fail; - if (!is_simple(value)) { - nasm_nonfatal("non-constant argument supplied to TIMES"); - result->times = 1L; - } else { - result->times = value->value; - if (value->value < 0) { - nasm_nonfatalf(ERR_PASS2, "TIMES value %"PRId64" is negative", value->value); - result->times = 0; + if (slot == PPS_TIMES) { + /* TIMES is a very special prefix */ + expr *value; + + i = stdscan(NULL, &tokval); + value = evaluate(stdscan, NULL, &tokval, NULL, + pass_stable(), NULL); + i = tokval.t_type; + if (!value) /* Error in evaluator */ + goto fail; + if (!is_simple(value)) { + nasm_nonfatal("non-constant argument supplied to TIMES"); + result->times = 1; + } else { + result->times = value->value; + if (value->value < 0) { + nasm_nonfatalf(ERR_PASS2, "TIMES value %"PRId64" is negative", value->value); + result->times = 0; + } } + first = false; + continue; } + } else if (i == TOKEN_REG && IS_SREG(tokval.t_integer)) { + slot = PPS_SEG; + first = false; } else { - int slot = prefix_slot(tokval.t_integer); - if (result->prefixes[slot]) { - if (result->prefixes[slot] == tokval.t_integer) - nasm_warn(WARN_OTHER, "instruction has redundant prefixes"); - else - nasm_nonfatal("instruction has conflicting prefixes"); - } - result->prefixes[slot] = tokval.t_integer; - i = stdscan(NULL, &tokval); + break; /* Not a prefix */ } + + if (result->prefixes[slot]) { + if (result->prefixes[slot] == tokval.t_integer) + nasm_warn(WARN_OTHER, "instruction has redundant prefixes"); + else + nasm_nonfatal("instruction has conflicting prefixes"); + } + result->prefixes[slot] = tokval.t_integer; + i = stdscan(NULL, &tokval); + first = false; } if (i != TOKEN_INSN) { diff --git a/asm/tokens.dat b/asm/tokens.dat index 356b39a2..0a782464 100644 --- a/asm/tokens.dat +++ b/asm/tokens.dat @@ -1,6 +1,6 @@ ## -------------------------------------------------------------------------- ## -## Copyright 1996-2016 The NASM Authors - All Rights Reserved +## Copyright 1996-2021 The NASM Authors - All Rights Reserved ## See the file AUTHORS included with the NASM distribution for ## the specific copyright holders. ## @@ -46,28 +46,45 @@ % TOKEN_QMARK, 0, 0, 0 ? -% TOKEN_PREFIX, 0, 0, P_* +% TOKEN_PREFIX, PPS_ASIZE, 0, P_* a16 a32 a64 asp + +% TOKEN_PREFIX, PPS_LOCK, 0, P_* lock + +% TOKEN_PREFIX, PPS_OSIZE, 0, P_* o16 o32 o64 osp + +% TOKEN_PREFIX, PPS_REP, 0, P_* rep repe repne repnz repz -times -wait xacquire xrelease bnd nobnd +% TOKEN_PREFIX, PPS_TIMES, 0, P_* +times + +% TOKEN_PREFIX, PPS_WAIT, 0, P_* +wait + +% TOKEN_PREFIX, PPS_REX, TFLAG_BRC, P_* +rex +evex +vex +vex3 +vex2 + % TOKEN_SIZE, SIZE_*, 0, S_* byte word @@ -154,11 +171,6 @@ rz-sae sae z -% TOKEN_PREFIX, 0, TFLAG_BRC, P_* -evex -vex3 -vex2 - # Multi-character operators. Used in ppscan(). % TOKEN_SHR, 0, 0, 0 >> diff --git a/common/common.c b/common/common.c index 13237994..aa778055 100644 --- a/common/common.c +++ b/common/common.c @@ -1,6 +1,6 @@ /* ----------------------------------------------------------------------- * * - * Copyright 1996-2016 The NASM Authors - All Rights Reserved + * Copyright 1996-2021 The NASM Authors - All Rights Reserved * See the file AUTHORS included with the NASM distribution for * the specific copyright holders. * @@ -46,14 +46,15 @@ int globalbits = 0; /* * Common list of prefix names; ideally should be auto-generated - * from tokens.dat + * from tokens.dat. This MUST match the enum in include/nasm.h. */ const char *prefix_name(int token) { static const char *prefix_names[] = { "a16", "a32", "a64", "asp", "lock", "o16", "o32", "o64", "osp", "rep", "repe", "repne", "repnz", "repz", "times", "wait", - "xacquire", "xrelease", "bnd" + "xacquire", "xrelease", "bnd", "nobnd", "{rex}", "{evex}", "{vex}", + "{vex3}", "{vex2}" }; unsigned int prefix = token-PREFIX_ENUM_START; diff --git a/include/nasm.h b/include/nasm.h index 922a1bb2..4d0dcafd 100644 --- a/include/nasm.h +++ b/include/nasm.h @@ -619,7 +619,9 @@ enum prefixes { /* instruction prefixes */ P_XRELEASE, P_BND, P_NOBND, + P_REX, P_EVEX, + P_VEX, P_VEX3, P_VEX2, PREFIX_ENUM_LIMIT @@ -715,14 +717,15 @@ enum ea_type { * the introduction of HLE. */ enum prefix_pos { - PPS_WAIT, /* WAIT (technically not a prefix!) */ - PPS_REP, /* REP/HLE prefix */ - PPS_LOCK, /* LOCK prefix */ - PPS_SEG, /* Segment override prefix */ - PPS_OSIZE, /* Operand size prefix */ - PPS_ASIZE, /* Address size prefix */ - PPS_VEX, /* VEX type */ - MAXPREFIX /* Total number of prefix slots */ + PPS_TIMES = -1, /* TIMES (not a slot, handled separately) */ + PPS_WAIT = 0, /* WAIT (technically not a prefix!) */ + PPS_REP, /* REP/HLE prefix */ + PPS_LOCK, /* LOCK prefix */ + PPS_SEG, /* Segment override prefix */ + PPS_OSIZE, /* Operand size prefix */ + PPS_ASIZE, /* Address size prefix */ + PPS_REX, /* REX/VEX type */ + MAXPREFIX /* Total number of prefix slots */ }; /* diff --git a/test/vex.asm b/test/vex.asm index 6772c7ce..7c1b3f82 100644 --- a/test/vex.asm +++ b/test/vex.asm @@ -1,9 +1,39 @@ bits 64 + add eax,edx + {rex} add eax,edx + add al,dl + {rex} add al,dl + add ah,dl + comisd xmm0,xmm1 + {rex} comisd xmm0,xmm1 vcomisd xmm0,xmm31 vcomisd xmm0,xmm1 + {vex} vcomisd xmm0,xmm1 {vex2} vcomisd xmm0,xmm1 {vex3} vcomisd xmm0,xmm1 {evex} vcomisd xmm0,xmm1 + {vex2} vcomisd xmm0,xmm1 + {vex3} vcomisd xmm0,xmm1 + {evex} vcomisd xmm0,xmm1 + {vex} vcomisd xmm0,[r8+rax*1] + {vex3} vcomisd xmm0,[r8+rax*1] + {evex} vcomisd xmm0,[r8+rax*1] + {vex} vcomisd xmm0,[rax+r8*2] + {vex3} vcomisd xmm0,[rax+r8*2] + {evex} vcomisd xmm0,[rax+r8*2] + + ;; These errors may be caught in different passes, so + ;; some shadows the others... %ifdef ERROR + %if ERROR <= 1 + {vex2} vcomisd xmm0,[rax+r8*2] + {rex} add ah,dl + bits 32 + mov eax,[r8d] + %endif + %if ERROR <= 2 + {rex} vcomisd xmm0,xmm1 + {vex} add eax,edx {vex3} add eax,edx + %endif %endif diff --git a/travis/test/vex.asm b/travis/test/vex.asm index 6772c7ce..7c1b3f82 100644 --- a/travis/test/vex.asm +++ b/travis/test/vex.asm @@ -1,9 +1,39 @@ bits 64 + add eax,edx + {rex} add eax,edx + add al,dl + {rex} add al,dl + add ah,dl + comisd xmm0,xmm1 + {rex} comisd xmm0,xmm1 vcomisd xmm0,xmm31 vcomisd xmm0,xmm1 + {vex} vcomisd xmm0,xmm1 {vex2} vcomisd xmm0,xmm1 {vex3} vcomisd xmm0,xmm1 {evex} vcomisd xmm0,xmm1 + {vex2} vcomisd xmm0,xmm1 + {vex3} vcomisd xmm0,xmm1 + {evex} vcomisd xmm0,xmm1 + {vex} vcomisd xmm0,[r8+rax*1] + {vex3} vcomisd xmm0,[r8+rax*1] + {evex} vcomisd xmm0,[r8+rax*1] + {vex} vcomisd xmm0,[rax+r8*2] + {vex3} vcomisd xmm0,[rax+r8*2] + {evex} vcomisd xmm0,[rax+r8*2] + + ;; These errors may be caught in different passes, so + ;; some shadows the others... %ifdef ERROR + %if ERROR <= 1 + {vex2} vcomisd xmm0,[rax+r8*2] + {rex} add ah,dl + bits 32 + mov eax,[r8d] + %endif + %if ERROR <= 2 + {rex} vcomisd xmm0,xmm1 + {vex} add eax,edx {vex3} add eax,edx + %endif %endif diff --git a/travis/test/vex.bin.t b/travis/test/vex.bin.t index 2145d4cf70e58f5ab833732fc03f61c23b4839ec..4f015064c6dd92031e7a2281765077d4c644daea 100644 GIT binary patch literal 103 zcmZR);J|o+;erFh1%@kW{Q3ve9Dqd9#J?Q+$B+KhKM2D|9##T{KLUlpLP*>r2P^ei Y7$9WQMW6y8XCY9?0YWBi1PM6+03p0D?EnA( literal 25 dcmYeA_?JWf_|c#G2SNDA!%F>wNgshi2LZlc4xs=5 diff --git a/travis/test/vex.json b/travis/test/vex.json index e1db1923..ff5337d4 100644 --- a/travis/test/vex.json +++ b/travis/test/vex.json @@ -1,6 +1,6 @@ [ { - "description": "Test VEX2/VEX3/EVEX prefix", + "description": "Test explicit REX/VEX2/VEX3/EVEX prefix", "id": "vex", "format": "bin", "source": "vex.asm", @@ -10,11 +10,20 @@ ] }, { - "description": "Test VEX3 prefix error", + "description": "Test early REX/VEX prefix errors", "ref": "vex", - "option": "-Ox -DERROR -o vex.bin.err", + "option": "-Ox -DERROR=1 -o vex1.bin.err", "target": [ - { "stderr": "vex.stderr" } + { "stderr": "vex1.stderr" } + ], + "error": "expected" + }, + { + "description": "Test late REX/VEX prefix errors", + "ref": "vex", + "option": "-Ox -DERROR=2 -o vex2.bin.err", + "target": [ + { "stderr": "vex2.stderr" } ], "error": "expected" } diff --git a/travis/test/vex.stderr b/travis/test/vex.stderr deleted file mode 100644 index b7645c38..00000000 --- a/travis/test/vex.stderr +++ /dev/null @@ -1 +0,0 @@ -./travis/test/vex.asm:8: error: specific encoding scheme not available diff --git a/travis/test/vex1.stderr b/travis/test/vex1.stderr new file mode 100644 index 00000000..0e9425f6 --- /dev/null +++ b/travis/test/vex1.stderr @@ -0,0 +1,3 @@ +./travis/test/vex.asm:29: error: instruction not encodable with {vex2} prefix +./travis/test/vex.asm:30: error: cannot use high byte register in rex instruction +./travis/test/vex.asm:32: error: invalid operands in non-64-bit mode diff --git a/travis/test/vex2.stderr b/travis/test/vex2.stderr new file mode 100644 index 00000000..f50b5406 --- /dev/null +++ b/travis/test/vex2.stderr @@ -0,0 +1,3 @@ +./travis/test/vex.asm:35: error: instruction not encodable with {rex} prefix +./travis/test/vex.asm:36: error: instruction not encodable with {vex} prefix +./travis/test/vex.asm:37: error: instruction not encodable with {vex3} prefix