Support [base]:u8 type interpretations for memory operands; And determine the correct suffix to use

This commit is contained in:
gingerBill
2026-08-12 09:30:55 +01:00
parent c80fedc070
commit 658fa54ee4
3 changed files with 119 additions and 46 deletions

View File

@@ -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")
}
}

View File

@@ -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) {

View File

@@ -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;
}
}