asm: Add support for constraint checks such as division by zero or overshifting

This commit is contained in:
gingerBill committed 2026-08-24 18:18:28 +01:00
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")
+1
View File
@@ -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.
+14
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+11
View File
@@ -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
View File
@@ -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";