From ce6346a20ff2eec8422208481d552a9bcdc6acf6 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 11 Aug 2026 14:52:21 +0100 Subject: [PATCH] Check immediates if they support the range or are not allowed (e.g. floats) --- .../x86/tablegen/cpp-compiler/cpp-gen.odin | 8 +- src/asm_tables_amd64.cpp | 8 +- src/check_asm.cpp | 129 ++++++++++++++---- src/llvm_backend_asm.cpp | 4 + 4 files changed, 124 insertions(+), 25 deletions(-) diff --git a/core/rexcode/isa/x86/tablegen/cpp-compiler/cpp-gen.odin b/core/rexcode/isa/x86/tablegen/cpp-compiler/cpp-gen.odin index a005a4258..7e30180d3 100644 --- a/core/rexcode/isa/x86/tablegen/cpp-compiler/cpp-gen.odin +++ b/core/rexcode/isa/x86/tablegen/cpp-compiler/cpp-gen.odin @@ -361,8 +361,14 @@ main :: proc() { strings.write_string(&sb, "\t\tcase OP_M128: case OP_XMM: case OP_XMM_M128: case OP_XMM0_IMPL: return 128;\n") strings.write_string(&sb, "\t\tcase OP_M256: case OP_YMM: case OP_YMM_M256: return 256;\n") strings.write_string(&sb, "\t\tcase OP_M512: case OP_ZMM: case OP_ZMM_M512: return 512;\n") + strings.write_string(&sb, "\t\tcase OP_IMM8: return 8;\n") + strings.write_string(&sb, "\t\tcase OP_IMM16: return 16;\n") + strings.write_string(&sb, "\t\tcase OP_IMM32: return 32;\n") + strings.write_string(&sb, "\t\tcase OP_IMM64: return 64;\n") + strings.write_string(&sb, "\t\tcase OP_REL8: return 8;\n") + strings.write_string(&sb, "\t\tcase OP_REL32: return 32;\n") strings.write_string(&sb, "\t\t}\n") - strings.write_string(&sb, "\t\treturn 0; // OP_M (sizeless), OP_IMM*, OP_REL*, OP_K (opmask width is data-dependent), etc.\n") + strings.write_string(&sb, "\t\treturn 0; // OP_M (sizeless), OP_K (opmask width is data-dependent), OP_ONE_IMPL, moffs, ptr, sreg/cr/dr, etc.\n") strings.write_string(&sb, "\t}\n") } strings.write_string(&sb, "\n") diff --git a/src/asm_tables_amd64.cpp b/src/asm_tables_amd64.cpp index 7db8b9b4d..6abc829b1 100644 --- a/src/asm_tables_amd64.cpp +++ b/src/asm_tables_amd64.cpp @@ -380,8 +380,14 @@ struct Asm_amd64 { case OP_M128: case OP_XMM: case OP_XMM_M128: case OP_XMM0_IMPL: return 128; case OP_M256: case OP_YMM: case OP_YMM_M256: return 256; case OP_M512: case OP_ZMM: case OP_ZMM_M512: return 512; + case OP_IMM8: return 8; + case OP_IMM16: return 16; + case OP_IMM32: return 32; + case OP_IMM64: return 64; + case OP_REL8: return 8; + case OP_REL32: return 32; } - return 0; // OP_M (sizeless), OP_IMM*, OP_REL*, OP_K (opmask width is data-dependent), etc. + return 0; // OP_M (sizeless), OP_K (opmask width is data-dependent), OP_ONE_IMPL, moffs, ptr, sreg/cr/dr, etc. } int form_explicit_slot(Encoding const &form, int explicit_index) { diff --git a/src/check_asm.cpp b/src/check_asm.cpp index e34cde6d7..fcbd7e583 100644 --- a/src/check_asm.cpp +++ b/src/check_asm.cpp @@ -55,10 +55,73 @@ gb_internal AsmRegClass check_asm_reg_class_from_type(Type *type) { enum AsmMismatch : u8 { AsmMismatch_None, - AsmMismatch_Size, - AsmMismatch_Class, + AsmMismatch_Size, // register / vector width mismatch + AsmMismatch_Class, // register class mismatch + AsmMismatch_ImmRange, // constant immediate does not fit the slot width + AsmMismatch_ImmType, // non-integer constant where an integer immediate is required }; +// Does a constant immediate value fit a slot of `bits` width (0 == unconstrained)? +// Accepts either a signed or an unsigned interpretation of the bit pattern, which +// matches how the assembler treats imm fields (e.g. both 200 and -56 fit imm8). +gb_internal bool check_asm_immediate_value_fits(ExactValue ev, i32 bits, i32 *needed_, AsmMismatch *mismatch_) { + if (ev.kind == ExactValue_Float) { + // Try to convert it if possible to an integer + ev = exact_value_to_integer(ev); + } + + switch (ev.kind) { + case ExactValue_Bool: + // Encodes as 0 or 1; fits any immediate slot with a non-zero width. + if (needed_) *needed_ = 1; + return true; + + case ExactValue_Integer: { + mp_int const *v = &ev.value_integer; + i32 mag_bits = cast(i32)mp_count_bits(v); + if (needed_) *needed_ = mag_bits; + + if (bits == 0) { + // TODO(bill): is this a decent width?! + bits = 64; // slot does not pin a width, just set a decent default + } + if (mp_iszero(v)) { + return true; + } + if (!mp_isneg(v)) { + // Non-negative: fits if the unsigned bit pattern is <= `bits` wide. + if (mag_bits <= bits) { + return true; + } + } else { + // Negative: fits signed in `bits` iff mp_count_bits(-v - 1) <= bits-1. + // (-v-1 ranges 0 .. 2^(bits-1)-1 for the representable negatives.) + mp_int tmp = {}; + mp_init(&tmp); + defer (mp_clear(&tmp)); + mp_neg(v, &tmp); // tmp = -v (positive magnitude) + mp_sub_d(&tmp, 1, &tmp); // tmp = -v - 1 + i32 nb = cast(i32)mp_count_bits(&tmp); + if (needed_) *needed_ = nb + 1; // signed bit-width, for the diagnostic + if (nb <= bits-1) { + return true; + } + } + if (mismatch_) *mismatch_ = AsmMismatch_ImmRange; + return false; + } + + case ExactValue_Float: + // TODO(bill): does any architecture support floating-point immediates? + // amd64 has no floating-point instruction immediates. + if (needed_) *needed_ = 0; + if (mismatch_) *mismatch_ = AsmMismatch_ImmType; + return false; + } + if (mismatch_) *mismatch_ = AsmMismatch_ImmType; + return false; +} + // Returns true if the operand's Odin type is size/class-compatible with the form's slot. // On mismatch, fills *mismatch_ for a precise diagnostic. `slot` here is the // resolved OperandType at the correct (implicit-skipped) slot. @@ -66,11 +129,26 @@ template gb_internal bool check_asm_operand_size_class(AsmCtx *asm_ctx, typename AsmCtx::OperandType slot, Operand const *operand, AsmMismatch *mismatch_, i32 *want_bits_, i32 *got_bits_) { if (mismatch_) *mismatch_ = AsmMismatch_None; - // Only register/memory-sized slots constrain width & class. Immediates/labels/sizeless-mem: skip here. + + AsmOperandKind slot_kind = asm_ctx->kind_from_operand_type(slot); + if (slot_kind == AsmOperand_Immediate) { + i32 want_w = asm_ctx->operand_type_bit_width(slot); // 32 for OP_IMM32 + if (want_bits_) *want_bits_ = want_w; + if (operand->mode != Addressing_Constant) { + return true; // $-immediate, bound per instantiation; defer + } + i32 needed = 0; + ExactValue ev = operand->value; + bool ok = check_asm_immediate_value_fits(ev, want_w, &needed, mismatch_); + if (got_bits_) *got_bits_ = needed; + return ok; + } + + // Register / memory-sized slots AsmRegClass want_class = asm_ctx->operand_type_reg_class(slot); i32 want_w = asm_ctx->operand_type_bit_width(slot); - // A pure-immediate or label slot imposes no reg width/class; value-range is checked elsewhere. + // A pure-label / sizeless slot imposes no reg width/class. if (want_class == AsmRegClass_Unknown && want_w == 0) { return true; } @@ -78,8 +156,15 @@ gb_internal bool check_asm_operand_size_class(AsmCtx *asm_ctx, typename AsmCtx:: AsmRegClass got_class = check_asm_reg_class_from_type(operand->type); i32 got_w = check_asm_operand_bit_width(operand->type); if (got_w < 0) { - // TODO(bill): determine the correct width from the untyped constant value - got_w = want_w; + // Untyped constant: width is a property of the value, not the type. + if (operand->mode == Addressing_Constant && operand->value.kind == ExactValue_Integer) { + got_w = cast(i32)mp_count_bits(&operand->value.value_integer); + if (got_w == 0) { + got_w = 1; // zero still occupies a slot + } + } else { + got_w = 0; // unknown; skip the width comparison rather than fake a pass + } } if (want_bits_) *want_bits_ = want_w; if (got_bits_) *got_bits_ = got_w; @@ -92,7 +177,6 @@ gb_internal bool check_asm_operand_size_class(AsmCtx *asm_ctx, typename AsmCtx:: class_ok = (got_class == AsmRegClass_Integer); break; case AsmRegClass_Vector: - // XMM/YMM/ZMM slots accept both scalar float and #simd vector operands. class_ok = (got_class == AsmRegClass_Vector || got_class == AsmRegClass_Float); break; case AsmRegClass_Mask: @@ -102,32 +186,23 @@ gb_internal bool check_asm_operand_size_class(AsmCtx *asm_ctx, typename AsmCtx:: class_ok = true; break; } - if (!class_ok) { if (mismatch_) *mismatch_ = AsmMismatch_Class; return false; } } - // Width check (only when the slot pins a width and we could size the type). + // Width check. if (want_w != 0 && got_w != 0) { if (want_class == AsmRegClass_Vector) { - // XMM/YMM/ZMM slot. A scalar float (f32/f64) uses only the low lane, so - // it is valid in any vector-register slot as long as it fits. A #simd - // vector, by contrast, must match the register width exactly (a 128-bit - // vector is not a ymm, a 256-bit vector is not an xmm). - bool width_ok = false; - if (got_class == AsmRegClass_Float) { - width_ok = (got_w <= want_w); // scalar in low lane - } else { - width_ok = (got_w == want_w); // #simd must be exact - } + // A scalar float uses only the low lane, so it is valid in any vector + // register slot as long as it fits; a #simd vector must match exactly. + bool width_ok = (got_class == AsmRegClass_Float) ? (got_w <= want_w) : (got_w == want_w); if (!width_ok) { if (mismatch_) *mismatch_ = AsmMismatch_Size; return false; } } else { - // GPR / mask / memory-sized slot: exact width. if (want_w != got_w) { if (mismatch_) *mismatch_ = AsmMismatch_Size; return false; @@ -137,7 +212,6 @@ gb_internal bool check_asm_operand_size_class(AsmCtx *asm_ctx, typename AsmCtx:: return true; } - gb_internal Type *check_asm_template_signature_params(CheckerContext *ctx, Scope *scope, Ast *_params, bool input_parameters, Array *asm_template_entity_decls) { Type *tuple = alloc_type_tuple(); if (_params == nullptr) { @@ -644,8 +718,17 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, AstAsmInst AsmMismatch m = (i < MAX_VARIANT_COUNT) ? mismatch[i] : AsmMismatch_None; - if (m == AsmMismatch_Size && want_bits[i] && got_bits[i]) { - // dst kind was right, width was wrong + if (m == AsmMismatch_ImmRange) { + gbString vs = exact_value_to_string(operands[i].value); + error(operands[i].expr, + "Operand %td of '%.*s': immediate value %s does not fit in a %d-bit immediate", + i, LIT(name), vs, cast(int)want_bits[i]); + gb_string_free(vs); + } else if (m == AsmMismatch_ImmType) { + error(operands[i].expr, + "Operand %td of '%.*s': a floating-point constant cannot be used as an immediate", + i, LIT(name)); + } else if (m == AsmMismatch_Size && want_bits[i] && got_bits[i]) { error(operands[i].expr, "Operand %td of '%.*s' has the wrong size: expected a %u-bit operand, got %u-bit", i, LIT(name), cast(unsigned)want_bits[i], cast(unsigned)got_bits[i]); diff --git a/src/llvm_backend_asm.cpp b/src/llvm_backend_asm.cpp index 24a6daef9..eae026648 100644 --- a/src/llvm_backend_asm.cpp +++ b/src/llvm_backend_asm.cpp @@ -49,6 +49,7 @@ struct lbAsmGenerate { case_end; case_ast_node(bl, BasicLit, op); + op->tav.value = exact_value_to_integer(op->tav.value); ExactValue ev = op->tav.value; GB_ASSERT(ev.kind != ExactValue_Invalid); switch (ev.kind) { @@ -61,6 +62,9 @@ struct lbAsmGenerate { gb_free(heap_allocator(), s.text); break; } + case ExactValue_Float: + error(op, "Floating-point literals that cannot be represented as an integer are not supported within asm operands"); + break; default: GB_PANIC("Unsupported asm immediate literal %s", expr_to_string(op)); break;