diff --git a/src/check_asm.cpp b/src/check_asm.cpp index d96c33885..cbaeab8c5 100644 --- a/src/check_asm.cpp +++ b/src/check_asm.cpp @@ -153,8 +153,25 @@ gb_internal bool check_asm_operand_size_class(AsmCtx *asm_ctx, typename AsmCtx:: return true; } - AsmRegClass got_class = check_asm_reg_class_from_type(operand->type); - i32 got_w = check_asm_operand_bit_width(operand->type); + // Determine the type whose width/class we actually measure. + // + // Memory operands encode their *access* type as a pointer: `[p]:u8` -> `^u8`, + // with a bare `rawptr` meaning "unsized" (no explicit `:type` annotation). A + // register/immediate/parameter operand measures its own type directly. + Type *measured = operand->type; + bool is_memory = (determine_asm_operand_kind(operand) == AsmOperand_Memory); + if (is_memory) { + if (are_types_identical(measured, t_rawptr)) { + // Unsized memory operand: the width is inferred elsewhere (from the + // register operand or deferred), so nothing to check against here. + if (want_bits_) *want_bits_ = want_w; + return true; + } + measured = type_deref(measured); // ^u8 -> u8 + } + + AsmRegClass got_class = check_asm_reg_class_from_type(measured); + i32 got_w = check_asm_operand_bit_width(measured); if (got_w < 0) { // Untyped constant: width is a property of the value, not the type. if (operand->mode == Addressing_Constant && operand->value.kind == ExactValue_Integer) { @@ -170,13 +187,20 @@ gb_internal bool check_asm_operand_size_class(AsmCtx *asm_ctx, typename AsmCtx:: if (got_bits_) *got_bits_ = got_w; // Class check (only when the slot constrains a class). - if (want_class != AsmRegClass_Unknown) { + // + // A *memory* operand against a register-or-memory slot (e.g. OP_XMM_M64) has no + // lane semantics -- it is just N bytes of memory -- so its integer/vector class + // must not be held against the slot's register class. Only width matters for the + // memory interpretation. Register operands still get the full class check. + if (want_class != AsmRegClass_Unknown && !is_memory) { bool class_ok; switch (want_class) { case AsmRegClass_Integer: class_ok = (got_class == AsmRegClass_Integer); break; case AsmRegClass_Vector: + // A scalar float uses only the low lane, so it is valid in any vector + // register slot; a #simd vector matches the vector class exactly. class_ok = (got_class == AsmRegClass_Vector || got_class == AsmRegClass_Float); break; case AsmRegClass_Mask: @@ -194,7 +218,7 @@ gb_internal bool check_asm_operand_size_class(AsmCtx *asm_ctx, typename AsmCtx:: // Width check. if (want_w != 0 && got_w != 0) { - if (want_class == AsmRegClass_Vector) { + if (want_class == AsmRegClass_Vector && !is_memory) { // 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); @@ -203,6 +227,7 @@ gb_internal bool check_asm_operand_size_class(AsmCtx *asm_ctx, typename AsmCtx:: return false; } } else { + // Integer/mask registers, and all memory operands: exact width. if (want_w != got_w) { if (mismatch_) *mismatch_ = AsmMismatch_Size; return false; @@ -684,7 +709,9 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, AstAsmInst bool spot_ok = false; if (kind_ok) { - if (dst == AsmOperand_Register_Or_Memory && src == AsmOperand_Memory) { + bool mem_unsized = (src == AsmOperand_Memory) && are_types_identical(operand->type, t_rawptr); + + if (dst == AsmOperand_Register_Or_Memory && src == AsmOperand_Memory && mem_unsized) { spot_ok = true; // memory form accepts memory; no size check } else { AsmMismatch m = AsmMismatch_None; @@ -1047,59 +1074,51 @@ gb_internal void check_asm_instruction_operand(AsmCtx *asm_ctx, CheckerContext * // displacement: an integer that fits a signed 32-bit value for (int i = 0; disp.expr && i == 0; i++) { if (disp.expr->kind == Ast_AsmRegister) { - error(disp.expr, "A displacement must be an integer value, got a register"); + error(disp.expr, "A displacement must be a constant integer value, got a register"); + break; + } + + // A displacement must be assemble-time constant. A register-valued + // parameter belongs in the index slot, not the displacement. + if (disp.mode == Addressing_Constant && disp.value.kind == ExactValue_Integer) { + AsmMismatch m = AsmMismatch_None; + i32 needed = 0; + if (!check_asm_immediate_value_fits(disp.value, 32, &needed, &m)) { + gbString vs = exact_value_to_string(disp.value); + error(disp.expr, "A memory displacement must fit in a signed 32-bit value, got %s (needs %d bits)", vs, cast(int)needed); + gb_string_free(vs); + } break; } Entity *param_entity = entity_of_node(disp.expr); - if (disp.mode == Addressing_Constant) { - if (disp.value.kind == ExactValue_Integer) { - AsmMismatch m = AsmMismatch_None; - i32 needed = 0; - if (!check_asm_immediate_value_fits(disp.value, 32, &needed, &m)) { - gbString vs = exact_value_to_string(disp.value); - error(disp.expr, "A memory displacement must fit in a signed 32-bit value, got %s (needs %d bits)", vs, cast(int)needed); - gb_string_free(vs); - } + if (param_entity != nullptr && param_entity->kind == Entity_Variable) { + auto kind = check_asm_find_kind(param_entity, ate->decls); + if (kind == AsmTemplateEntityDecl_Immediate) { + // A $-immediate parameter is a legal (assemble-time) displacement. + break; + } + if (kind == AsmTemplateEntityDecl_Register) { + error(disp.expr, "A register parameter cannot be a displacement; use it as an index, e.g. [base + %.*s]", LIT(disp.expr->Ident.token.string)); break; } } - if (param_entity == nullptr) { - gbString s = expr_to_string(disp.expr); - error(disp.expr, "A displacement value must be an integer, got %s", s); - gb_string_free(s); - break; - } - auto kind = check_asm_find_kind(param_entity, ate->decls); - switch (kind) { - case AsmTemplateEntityDecl_Register: - case AsmTemplateEntityDecl_Immediate: - if (is_type_integer(disp.type)) { - break; - } - /*fallthrough*/ - default: - { - gbString s = expr_to_string(disp.expr); - gbString t = type_to_string(disp.type); - error(disp.expr, "A displacement must be an integer value, got %s of type %s", s, t); - gb_string_free(t); - gb_string_free(s); - } - break; - } + gbString s = expr_to_string(disp.expr); + error(disp.expr, "A displacement must be a constant integer or immediate, got %s", s); + gb_string_free(s); } if (mem_op->type) { - Type *type_interpretation = check_type(ctx, mem_op->type); - if (type_interpretation != nullptr && type_interpretation != t_invalid) { - operand->type = alloc_type_pointer(type_interpretation); - if (!is_valid_asm_parameter_type(type_interpretation) || - is_type_pointer(type_interpretation)) { // do not allow pointers even if they are valid asm parameter types - gbString s = type_to_string(type_interpretation); + Type *t = check_type(ctx, mem_op->type); + if (t != nullptr && t != t_invalid) { + if (is_valid_asm_parameter_type(t) && !is_type_pointer(t)) { + operand->type = alloc_type_pointer(t); + } else { + gbString s = type_to_string(t); error(mem_op->type, "Asm memory operands type interpretation must be either an integer, boolean, float, or #simd vector, got %s", s); gb_string_free(s); + // leave operand->type == t_rawptr ("unsized") } } diff --git a/src/llvm_backend_asm.cpp b/src/llvm_backend_asm.cpp index b8f768102..8dbff8a3b 100644 --- a/src/llvm_backend_asm.cpp +++ b/src/llvm_backend_asm.cpp @@ -121,6 +121,47 @@ struct lbAsmGenerate { } }; + // AT&T operand-size suffix ('b','w','l','q') for an annotated memory operand, + // or 0 if there is no size annotation to apply. Vector/other widths return 0, + // since those forms take no b/w/l/q suffix (the register operand fixes the size). + char size_suffix_for_operand(Ast *op) { + if (op->kind != Ast_AsmMemoryOperand) { + return 0; + } + AstAsmMemoryOperand *mem_op = &op->AsmMemoryOperand; + if (mem_op->type == nullptr) { + return 0; // unsized: rely on a register operand to fix the width + } + // The frontend stored the access type as a pointer on the node's tav: [p]:u8 -> ^u8. + Type *ptr = mem_op->type->tav.type; + if (ptr == nullptr) { + return 0; + } + Type *access = type_deref(ptr); // ^u8 -> u8 + i64 sz = type_size_of(base_type(access)); + switch (sz) { + case 1: return 'b'; + case 2: return 'w'; + case 4: return 'l'; + case 8: return 'q'; + } + return 0; + } + + // Scan an instruction's operands for an annotated memory operand and return its + // AT&T size suffix, or 0 if none. The checker has already verified the annotation + // agrees with the matched encoding form, so a suffix here can never conflict. + char instruction_size_suffix(AstAsmInstruction *instr) { + char suffix = 0; + for (Ast *operand : instr->operands) { + char s = this->size_suffix_for_operand(operand); + if (s != 0) { + suffix = s; + } + } + return suffix; + } + // LLVM type of a returned register output, taken from the proc signature's results. LLVMTypeRef output_llvm_type(lbModule *m, AsmTemplateEntityDecl const &e) { @@ -304,6 +345,19 @@ struct lbAsmGenerate_amd64 : lbAsmGenerate { asm_string = gb_string_appendc(asm_string, "\t"); String name = instr->name->Ident.token.string; asm_string = gb_string_append_length(asm_string, name.text, name.len); + + // If a memory operand carries an explicit size annotation ([p]:u8) and + // no register operand pins the width, the AT&T assembler needs the size + // encoded as a mnemonic suffix (crc32 -> crc32b). The checker has already + // verified the annotation agrees with the matched form, so an emitted + // suffix can never conflict with a register operand's implied width. + { + char suffix = this->instruction_size_suffix(instr); + if (suffix != 0) { + asm_string = gb_string_append_length(asm_string, &suffix, 1); + } + } + asm_string = gb_string_appendc(asm_string, " "); // Intel-source operand order reversed to AT&T (src, ..., dst). for (isize j = instr->operands.count-1; j >= 0; j -= 1) { diff --git a/src/parser.cpp b/src/parser.cpp index cc9905bfa..a67aafe35 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2463,7 +2463,7 @@ gb_internal Ast *parse_asm_operand(AstFile *f, bool allow_memory_operand) { disp = parse_asm_operand(f, false); } } else { - disp = possible_index; + index = possible_index; } }