mirror of
https://github.com/odin-lang/Odin.git
synced 2026-09-15 23:54:52 -04:00
asm: Add support for constraint checks such as division by zero or overshifting
This commit is contained in:
1 parent
9367fa2ec2
commit
9ae9a9bf99
21 files changed
+2809
-2621
No files matched your search
@@ -805,6 +805,22 @@ main :: proc() {
|
||||
}
|
||||
""")
|
||||
|
||||
strings.write_string(&sb, "\n")
|
||||
|
||||
strings.write_string(&sb, """
|
||||
AsmOperandConstraint operand_value_constraint(u16 m, int op) const {
|
||||
switch (m) {
|
||||
case M_SLLI: case M_SRLI: case M_SRAI:
|
||||
if (op == 2) return {AsmOperandConstraint_ShiftCount, /*XLEN*/-1};
|
||||
break;
|
||||
case M_DIV: case M_DIVU: case M_REM: case M_REMU:
|
||||
if (op == 2) return {AsmOperandConstraint_NonZeroDivisor, -1};
|
||||
break;
|
||||
}
|
||||
return {AsmOperandConstraint_None, -1};
|
||||
}
|
||||
""")
|
||||
|
||||
strings.write_string(&sb, "\n};\n")
|
||||
|
||||
strings.write_string(&sb, "\n\n\n")
|
||||
|
||||
@@ -53,6 +53,7 @@ Mnemonic :: enum u16 {
|
||||
SHL,
|
||||
SHR,
|
||||
SAR,
|
||||
SAL,
|
||||
ROL,
|
||||
ROR,
|
||||
RCL,
|
||||
|
||||
@@ -807,6 +807,23 @@ main :: proc() {
|
||||
}
|
||||
""")
|
||||
|
||||
strings.write_string(&sb, "\n")
|
||||
|
||||
strings.write_string(&sb, """
|
||||
AsmOperandConstraint operand_value_constraint(u16 m, int op) const {
|
||||
switch (m) {
|
||||
case M_SHL: case M_SHR: case M_SAR: case M_SAL:
|
||||
case M_ROL: case M_ROR: case M_RCL: case M_RCR:
|
||||
if (op == 1) return {AsmOperandConstraint_ShiftCount, /*width_operand*/0};
|
||||
break;
|
||||
case M_DIV: case M_IDIV:
|
||||
if (op == 0) return {AsmOperandConstraint_NonZeroDivisor, -1};
|
||||
break;
|
||||
}
|
||||
return {AsmOperandConstraint_None, -1};
|
||||
}
|
||||
""")
|
||||
|
||||
strings.write_string(&sb, "\n};\n")
|
||||
|
||||
strings.write_string(&sb, "\n\n\n")
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -462,6 +462,20 @@ INSTRUCTION_TABLE := [Mnemonic][]Form{
|
||||
{{.SAR, {.RM64, .CL_IMPL, .NONE, .NONE}, {.MR, .IMPL, .NONE, .NONE}, 0xD3, 7, {force_rex_w=true, modrm_reg_ext=true}}, {written={0}, read={0}, implicit_rd={.RCX}, flags_wr={.CF, .PF, .ZF, .SF, .OF}, flags_undef={.AF}, writes_mem=true, reads_mem=true}},
|
||||
{{.SAR, {.RM64, .IMM8, .NONE, .NONE}, {.MR, .IB, .NONE, .NONE}, 0xC1, 7, {force_rex_w=true, modrm_reg_ext=true}}, {written={0}, read={0, 1}, flags_wr={.CF, .PF, .ZF, .SF, .OF}, flags_undef={.AF}, writes_mem=true, reads_mem=true}},
|
||||
},
|
||||
.SAL = {
|
||||
{{.SAL, {.RM8, .ONE_IMPL, .NONE, .NONE}, {.MR, .IMPL, .NONE, .NONE}, 0xD0, 4, {modrm_reg_ext=true}}, {written={0}, read={0}, flags_wr={.CF, .PF, .ZF, .SF, .OF}, flags_undef={.AF}, writes_mem=true, reads_mem=true}},
|
||||
{{.SAL, {.RM8, .CL_IMPL, .NONE, .NONE}, {.MR, .IMPL, .NONE, .NONE}, 0xD2, 4, {modrm_reg_ext=true}}, {written={0}, read={0}, implicit_rd={.RCX}, flags_wr={.CF, .PF, .ZF, .SF, .OF}, flags_undef={.AF}, writes_mem=true, reads_mem=true}},
|
||||
{{.SAL, {.RM8, .IMM8, .NONE, .NONE}, {.MR, .IB, .NONE, .NONE}, 0xC0, 4, {modrm_reg_ext=true}}, {written={0}, read={0, 1}, flags_wr={.CF, .PF, .ZF, .SF, .OF}, flags_undef={.AF}, writes_mem=true, reads_mem=true}},
|
||||
{{.SAL, {.RM16, .ONE_IMPL, .NONE, .NONE}, {.MR, .IMPL, .NONE, .NONE}, 0xD1, 4, {modrm_reg_ext=true}}, {written={0}, read={0}, flags_wr={.CF, .PF, .ZF, .SF, .OF}, flags_undef={.AF}, writes_mem=true, reads_mem=true}},
|
||||
{{.SAL, {.RM16, .CL_IMPL, .NONE, .NONE}, {.MR, .IMPL, .NONE, .NONE}, 0xD3, 4, {modrm_reg_ext=true}}, {written={0}, read={0}, implicit_rd={.RCX}, flags_wr={.CF, .PF, .ZF, .SF, .OF}, flags_undef={.AF}, writes_mem=true, reads_mem=true}},
|
||||
{{.SAL, {.RM16, .IMM8, .NONE, .NONE}, {.MR, .IB, .NONE, .NONE}, 0xC1, 4, {modrm_reg_ext=true}}, {written={0}, read={0, 1}, flags_wr={.CF, .PF, .ZF, .SF, .OF}, flags_undef={.AF}, writes_mem=true, reads_mem=true}},
|
||||
{{.SAL, {.RM32, .ONE_IMPL, .NONE, .NONE}, {.MR, .IMPL, .NONE, .NONE}, 0xD1, 4, {modrm_reg_ext=true}}, {written={0}, read={0}, flags_wr={.CF, .PF, .ZF, .SF, .OF}, flags_undef={.AF}, writes_mem=true, reads_mem=true}},
|
||||
{{.SAL, {.RM32, .CL_IMPL, .NONE, .NONE}, {.MR, .IMPL, .NONE, .NONE}, 0xD3, 4, {modrm_reg_ext=true}}, {written={0}, read={0}, implicit_rd={.RCX}, flags_wr={.CF, .PF, .ZF, .SF, .OF}, flags_undef={.AF}, writes_mem=true, reads_mem=true}},
|
||||
{{.SAL, {.RM32, .IMM8, .NONE, .NONE}, {.MR, .IB, .NONE, .NONE}, 0xC1, 4, {modrm_reg_ext=true}}, {written={0}, read={0, 1}, flags_wr={.CF, .PF, .ZF, .SF, .OF}, flags_undef={.AF}, writes_mem=true, reads_mem=true}},
|
||||
{{.SAL, {.RM64, .ONE_IMPL, .NONE, .NONE}, {.MR, .IMPL, .NONE, .NONE}, 0xD1, 4, {force_rex_w=true, modrm_reg_ext=true}}, {written={0}, read={0}, flags_wr={.CF, .PF, .ZF, .SF, .OF}, flags_undef={.AF}, writes_mem=true, reads_mem=true}},
|
||||
{{.SAL, {.RM64, .CL_IMPL, .NONE, .NONE}, {.MR, .IMPL, .NONE, .NONE}, 0xD3, 4, {force_rex_w=true, modrm_reg_ext=true}}, {written={0}, read={0}, implicit_rd={.RCX}, flags_wr={.CF, .PF, .ZF, .SF, .OF}, flags_undef={.AF}, writes_mem=true, reads_mem=true}},
|
||||
{{.SAL, {.RM64, .IMM8, .NONE, .NONE}, {.MR, .IB, .NONE, .NONE}, 0xC1, 4, {force_rex_w=true, modrm_reg_ext=true}}, {written={0}, read={0, 1}, flags_wr={.CF, .PF, .ZF, .SF, .OF}, flags_undef={.AF}, writes_mem=true, reads_mem=true}},
|
||||
},
|
||||
.ROL = {
|
||||
{{.ROL, {.RM8, .ONE_IMPL, .NONE, .NONE}, {.MR, .IMPL, .NONE, .NONE}, 0xD0, 0, {modrm_reg_ext=true}}, {written={0}, read={0}, flags_wr={.CF, .OF}, writes_mem=true, reads_mem=true}},
|
||||
{{.ROL, {.RM8, .CL_IMPL, .NONE, .NONE}, {.MR, .IMPL, .NONE, .NONE}, 0xD2, 0, {modrm_reg_ext=true}}, {written={0}, read={0}, implicit_rd={.RCX}, flags_wr={.CF, .OF}, writes_mem=true, reads_mem=true}},
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
Binary file not shown.
Binary file not shown.
@@ -53,6 +53,20 @@ gb_global String const asm_operand_kind_expected_strings[AsmOperand_COUNT] = {
|
||||
str_lit("a label"),
|
||||
};
|
||||
|
||||
enum AsmOperandConstraintKind : u32 {
|
||||
AsmOperandConstraint_None = 0,
|
||||
AsmOperandConstraint_ShiftCount, // integer const must satisfy 0 <= v < width
|
||||
AsmOperandConstraint_NonZeroDivisor, // integer const must be != 0
|
||||
|
||||
AsmOperandConstraint_COUNT
|
||||
};
|
||||
|
||||
|
||||
struct AsmOperandConstraint {
|
||||
AsmOperandConstraintKind kind;
|
||||
i32 width_operand; // ShiftCount
|
||||
};
|
||||
|
||||
#include "asm_tables_amd64.cpp"
|
||||
#include "asm_tables_riscv.cpp"
|
||||
|
||||
|
||||
+791
-773
File diff suppressed because it is too large.
Load diff
@@ -745,6 +745,17 @@ struct Asm_riscv {
|
||||
// RISC-V does not have prefixes
|
||||
return false;
|
||||
}
|
||||
AsmOperandConstraint operand_value_constraint(u16 m, int op) const {
|
||||
switch (m) {
|
||||
case M_SLLI: case M_SRLI: case M_SRAI:
|
||||
if (op == 2) return {AsmOperandConstraint_ShiftCount, /*XLEN*/-1};
|
||||
break;
|
||||
case M_DIV: case M_DIVU: case M_REM: case M_REMU:
|
||||
if (op == 2) return {AsmOperandConstraint_NonZeroDivisor, -1};
|
||||
break;
|
||||
}
|
||||
return {AsmOperandConstraint_None, -1};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
+60
-2
@@ -107,7 +107,7 @@ gb_internal void check_asm_pin_type_compat(AsmRegClass reg_class, i32 reg_w, Typ
|
||||
AsmRegClass got_class = check_asm_reg_class_from_type(decl_type);
|
||||
i32 got_w = check_asm_operand_bit_width(decl_type);
|
||||
|
||||
if (!!asm_reg_class_compatible(reg_class, got_class)) {
|
||||
if (!asm_reg_class_compatible(reg_class, got_class)) {
|
||||
error(at, "Parameter '%.*s' is pinned to %%%.*s, but its type is in the wrong register class for that register",
|
||||
LIT(param_name), LIT(pin_name));
|
||||
return;
|
||||
@@ -575,7 +575,7 @@ gb_internal void check_asm_specs(AsmCtx *asm_ctx, CheckerContext *ctx, Scope *sc
|
||||
if (pin.len != 0) {
|
||||
Operand op = {};
|
||||
if (check_register(asm_ctx, &op, reg)) {
|
||||
if (reg->flag.string.len) {
|
||||
if (reg->flag.string.len != 0) {
|
||||
GB_ASSERT(pin == "flags");
|
||||
pin_flag = reg->flag.string;
|
||||
if (string_set_update(&pin_flag_set, pin_flag)) {
|
||||
@@ -1063,6 +1063,57 @@ gb_internal bool check_asm_instr_targets_internal_label(AstAsmInstruction *instr
|
||||
}
|
||||
|
||||
|
||||
template <typename AsmCtx>
|
||||
gb_internal void check_operand_constraints(AsmCtx *asm_ctx, Slice<Operand> const &operands, u16 mnemonic, String const &name) {
|
||||
i32 word_bits = cast(i32)(build_context.metrics.ptr_size * 8);
|
||||
|
||||
// Value-range constraints (shift-count in range, non-zero divisor). Every mnemonic
|
||||
// decision lives in asm_ctx->operand_value_constraint; the logic below names none.
|
||||
for_array(i, operands) {
|
||||
Operand const *op = &operands[i];
|
||||
if (op->mode != Addressing_Constant) {
|
||||
continue; // register count (cl/rs2) or $-immediate: not knowable here
|
||||
}
|
||||
ExactValue ev = exact_value_to_integer(op->value);
|
||||
if (ev.kind != ExactValue_Integer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AsmOperandConstraint c = asm_ctx->operand_value_constraint(mnemonic, cast(int)i);
|
||||
switch (c.kind) {
|
||||
case AsmOperandConstraint_ShiftCount: {
|
||||
i32 width = 0;
|
||||
if (c.width_operand < 0) {
|
||||
width = word_bits;
|
||||
} else if (c.width_operand < operands.count) {
|
||||
width = check_asm_operand_bit_width(operands[c.width_operand].type);
|
||||
}
|
||||
if (width <= 0) {
|
||||
break; // unknown target width; nothing to compare against
|
||||
}
|
||||
mp_int const *v = &ev.value_integer;
|
||||
bool too_big = mp_count_bits(v) > 63;
|
||||
i64 count = too_big ? 0 : exact_value_to_i64(ev);
|
||||
if (mp_isneg(v) || too_big || count >= width) {
|
||||
gbString vs = exact_value_to_string(ev);
|
||||
error(op->expr, "'%.*s' shift count %s is out of range for a %d-bit operand (must be 0..<%d)",
|
||||
LIT(name), vs, cast(int)width, cast(int)width);
|
||||
gb_string_free(vs);
|
||||
}
|
||||
} break;
|
||||
|
||||
case AsmOperandConstraint_NonZeroDivisor:
|
||||
if (mp_iszero(&ev.value_integer)) {
|
||||
error(op->expr, "'%.*s' divides by a constant zero", LIT(name));
|
||||
}
|
||||
break;
|
||||
|
||||
case AsmOperandConstraint_None:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename AsmCtx>
|
||||
gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tmpl_entity, AstAsmInstruction *instr,
|
||||
u16 mnemonic, u16 pseudo_mnemonic, Slice<Operand> const &operands,
|
||||
@@ -1470,6 +1521,8 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm
|
||||
instr->mnemonic = mnemonic;
|
||||
instr->valid_form_index = cast(i32)valid_form_index;
|
||||
|
||||
check_operand_constraints(asm_ctx, operands, mnemonic, name);
|
||||
|
||||
// Handle clobbering from mnemonic
|
||||
auto clobber = clobber_forms[valid_form_index];
|
||||
|
||||
@@ -2705,6 +2758,11 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (ed.tie > 0) {
|
||||
// TODO(bill): Handle this edge case
|
||||
continue;
|
||||
}
|
||||
|
||||
char const *what = is_immediate ? "immediate" :
|
||||
is_input ? "input" :
|
||||
"scratch";
|
||||
|
||||
Reference in new issue
Block a user