mirror of
https://github.com/odin-lang/Odin.git
synced 2026-08-14 01:34:35 +00:00
Support [base]:u8 type interpretations for memory operands; And determine the correct suffix to use
This commit is contained in:
@@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user