From 7b5277d52cc9c6c293ec54986e22df4e02e7c45a Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Sun, 7 Sep 2025 00:03:49 +0200 Subject: [PATCH] Any register allowed in X86_64 inline asm address. #2463 --- releasenotes.md | 1 + src/compiler/codegen_asm.c | 7 +- src/compiler/copying.c | 2 +- src/compiler/enums.h | 26 +-- src/compiler/llvm_codegen_stmt.c | 9 + src/compiler/parse_stmt.c | 23 ++- src/compiler/sema_asm.c | 182 +++++++++++---------- src/compiler/sema_liveness.c | 5 +- src/compiler/target.h | 4 +- test/test_suite/asm/test_different_size.c3 | 10 ++ 10 files changed, 152 insertions(+), 117 deletions(-) create mode 100644 test/test_suite/asm/test_different_size.c3 diff --git a/releasenotes.md b/releasenotes.md index f5af6efa4..f617b0008 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -16,6 +16,7 @@ - Fix codegen bug in expressions like `foo(x()) ?? io::EOF?` causing irregular crashes. - Correctly silence "unsupported architecture" warning with `--quiet` #2465 - Overloading &[] should be enough for foreach. #2466 +- Any register allowed in X86_64 inline asm address. #2463 ### Stdlib changes - Added generic `InterfaceList` to store a list of values that implement a specific interface diff --git a/src/compiler/codegen_asm.c b/src/compiler/codegen_asm.c index 48b108e97..87a908177 100644 --- a/src/compiler/codegen_asm.c +++ b/src/compiler/codegen_asm.c @@ -34,6 +34,7 @@ static inline void codegen_create_x86att_arg(AsmInlineBlock *block, unsigned inp scratch_buffer_append_char('$'); scratch_buffer_append_unsigned_int(arg->index + input_offset); return; + case ASM_ARG_MEMADDR: case ASM_ARG_MEMVAR: case ASM_ARG_REGVAR: scratch_buffer_append_char('$'); @@ -82,8 +83,6 @@ static inline void codegen_create_x86att_arg(AsmInlineBlock *block, unsigned inp } scratch_buffer_append_char(')'); return; - case ASM_ARG_ADDROF: - TODO } UNREACHABLE_VOID } @@ -117,7 +116,7 @@ static inline void codegen_create_aarch64_arg(AsmInlineBlock *block, unsigned in return; case ASM_ARG_ADDR: TODO - case ASM_ARG_ADDROF: + case ASM_ARG_MEMADDR: TODO } UNREACHABLE_VOID @@ -168,7 +167,7 @@ static inline void codegen_create_riscv_arg(AsmInlineBlock *block, unsigned inpu } scratch_buffer_append_char(')'); return; - case ASM_ARG_ADDROF: + case ASM_ARG_MEMADDR: TODO } UNREACHABLE_VOID diff --git a/src/compiler/copying.c b/src/compiler/copying.c index f93bb9657..c8a28feae 100644 --- a/src/compiler/copying.c +++ b/src/compiler/copying.c @@ -467,7 +467,7 @@ Expr *copy_expr(CopyStruct *c, Expr *source_expr) switch (expr->expr_asm_arg.kind) { case ASM_ARG_REG: - case ASM_ARG_ADDROF: + case ASM_ARG_MEMADDR: case ASM_ARG_REGVAR: case ASM_ARG_INT: case ASM_ARG_MEMVAR: diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 34317fb01..96fe532bd 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -156,26 +156,26 @@ typedef enum FLAG_ATTR typedef enum { - ASM_ARG_REG, - ASM_ARG_ADDR, - ASM_ARG_REGVAR, - ASM_ARG_ADDROF, - ASM_ARG_MEMVAR, - ASM_ARG_VALUE, - ASM_ARG_INT, + ASM_ARG_REG, // A register + ASM_ARG_ADDR, // [...] + ASM_ARG_REGVAR, // foo - variable in a register, r/=r/+r + ASM_ARG_MEMADDR, // &foo - address to a variable + ASM_ARG_MEMVAR, // [&foo] + ASM_ARG_VALUE, // -1, (expr), 1, 3.0 + ASM_ARG_INT, // Converted from VALUE } AsmArgKind; typedef enum { - ASM_SCALE_1, + ASM_SCALE_1, // * ASM_SCALE_2, ASM_SCALE_4, ASM_SCALE_8, - ASM_SCALE_SHR, - ASM_SCALE_SHL, - ASM_SCALE_ASHL, - ASM_SCALE_ROR, - ASM_SCALE_RRX, + ASM_SCALE_SHR, // >> + ASM_SCALE_SHL, // << + ASM_SCALE_ASHL, // >>> + ASM_SCALE_ROR, // ror + ASM_SCALE_RRX, // rrx } AsmOffsetType; typedef enum diff --git a/src/compiler/llvm_codegen_stmt.c b/src/compiler/llvm_codegen_stmt.c index cfcd5a9f7..b577f6cb8 100644 --- a/src/compiler/llvm_codegen_stmt.c +++ b/src/compiler/llvm_codegen_stmt.c @@ -1351,6 +1351,15 @@ static inline void llvm_emit_asm_block_stmt(GenContext *c, Ast *ast) pointer_type[param_count] = NULL; switch (val->kind) { + case ASM_ARG_MEMADDR: + llvm_value_set_decl(c, &value, val->ident.ident_decl); + llvm_value_addr(c, &value); + value.kind = BE_VALUE; + pointer_type[param_count] = NULL; + value.type = type_get_ptr(value.type); + ASSERT(!val->ident.copy_output); + codegen_append_constraints(&clobber_list, "r"); + break; case ASM_ARG_MEMVAR: llvm_value_set_decl(c, &value, val->ident.ident_decl); llvm_value_addr(c, &value); diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index 22cab9afe..14c1d76f5 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -220,6 +220,10 @@ static inline bool parse_asm_scale(ParseContext *c, ExprAsmArg *asm_arg) return true; } +/* + * Asm [REG + BAR * 4], [&foo] + * asm_addr ::= '[' asm_expr + */ static inline bool parse_asm_addr(ParseContext *c, ExprAsmArg *asm_arg) { asm_arg->kind = ASM_ARG_ADDR; @@ -229,7 +233,8 @@ static inline bool parse_asm_addr(ParseContext *c, ExprAsmArg *asm_arg) // Simple case [foo] if (try_consume(c, TOKEN_RBRACKET)) { - if (base->expr_asm_arg.kind == ASM_ARG_ADDROF) + // Here we're covering [&foo] + if (base->expr_asm_arg.kind == ASM_ARG_MEMADDR) { *asm_arg = base->expr_asm_arg; asm_arg->kind = ASM_ARG_MEMVAR; @@ -318,9 +323,11 @@ static inline bool parse_asm_addr(ParseContext *c, ExprAsmArg *asm_arg) return true; } /** - * - * @param c - * @return + * asm_expr ::= asm_addr | asm_reg | asm_regvar | asm_addrof | asm_argvalue + * asm_reg = CT_IDENT | CT_CONST_IDENT (register name) + * asm_regvar = IDENT (variable) + * asm_addof = '&' IDENT (variable address) + * asm_argvalue = ('-'? INTEGER) | CONST_IDENT | FLOAT | '(' expr ')' */ static inline Expr *parse_asm_expr(ParseContext *c) { @@ -347,7 +354,7 @@ static inline Expr *parse_asm_expr(ParseContext *c) advance(c); return expr; case TOKEN_AMP: - expr->expr_asm_arg.kind = ASM_ARG_ADDROF; + expr->expr_asm_arg.kind = ASM_ARG_MEMADDR; advance(c); expr->expr_asm_arg.ident.name = c->data.string; if (!try_consume(c, TOKEN_IDENT)) @@ -379,6 +386,12 @@ static inline Expr *parse_asm_expr(ParseContext *c) } } +/* + * asm_label | asm_stmt + * asm_label ::= CONST_IDENT ':' + * asm_stmt ::= IDENT | int ('.' IDENT) )? asm_expr_list? ';' + * asm_expr_list ::= asm_expr (',' asm_expr)* ','? + */ static inline Ast *parse_asm_stmt(ParseContext *c) { Ast *asm_stmt = ast_new_curr(c, AST_ASM_STMT); diff --git a/src/compiler/sema_asm.c b/src/compiler/sema_asm.c index a1a960ba5..46d64d0b3 100644 --- a/src/compiler/sema_asm.c +++ b/src/compiler/sema_asm.c @@ -39,27 +39,6 @@ static inline Type *max_supported_imm_int(bool is_signed, AsmArgType arg) return type_int_unsigned_by_bitsize(next_highest_power_of_2(bits)); } -/* -static inline AsmArgGroup sema_ireg_for_type(Type *type) -{ - switch (type_size(type)) - { - case 1: - return AARG_R8; - case 2: - return AARG_R16; - case 4: - return AARG_R32; - case 8: - return AARG_R64; - case 16: - return AARG_R128; - default: - UNREACHABLE - } -} - */ - static inline Decl *sema_resolve_external_symbol(SemaContext *context, Expr *expr, const char *name) { Decl *decl = sema_resolve_symbol(context, name, NULL, expr->span); @@ -89,20 +68,6 @@ static inline bool sema_reg_int_supported_type(AsmArgType arg, Type *type) return next_highest_power_of_2(arg_bits_max(arg.ireg_bits, bits)) == bits; } -INLINE bool sema_reg_is_valid_in_slot(AsmRegister *reg, AsmArgType arg_type) -{ - switch (reg->type) - { - case ASM_REG_INT: - return (arg_type.ireg_bits & reg->bits) != 0; - case ASM_REG_FLOAT: - return (arg_type.float_bits & reg->bits) != 0; - case ASM_REF_FVEC: - case ASM_REG_IVEC: - return (arg_type.vec_bits & reg->bits) != 0; - } - UNREACHABLE -} static inline bool sema_reg_float_supported_type(AsmArgType arg, Type *type) { @@ -205,6 +170,7 @@ static inline bool sema_check_asm_arg(SemaContext *context, AsmInlineBlock *bloc static inline bool sema_check_asm_arg_addr(SemaContext *context, AsmInlineBlock *block, AsmInstruction *instr, AsmArgType arg_type, Expr *expr) { + // This is an argument [rdx + rsi * 2 + 1] etc if (!arg_type.is_address) { RETURN_SEMA_ERROR(expr, "An address cannot appear in this slot."); @@ -213,47 +179,56 @@ static inline bool sema_check_asm_arg_addr(SemaContext *context, AsmInlineBlock Expr *base = exprptr(asm_arg->base); ASSERT(base->expr_kind == EXPR_ASM); ExprAsmArg *base_arg = &base->expr_asm_arg; - AsmArgType any_ireg = { .ireg_bits = (AsmArgBits)0xFF }; + AsmArgType address_size = { .ireg_bits = ARG_BITS_16 | ARG_BITS_32 | ARG_BITS_64 }; // NO_LINT unsigned bit_size = 0; switch (base_arg->kind) { case ASM_ARG_REG: - if (!sema_check_asm_arg(context, block, instr, any_ireg, base)) return false; + // Here the register, or the variable in a register provides the address + // so `movl [foo], 1` which is taking the address in `foo` + if (!sema_check_asm_arg(context, block, instr, address_size, base)) return false; bit_size = arg_bits_max(base_arg->reg.ref->bits, 0); break; case ASM_ARG_REGVAR: - if (!sema_check_asm_arg(context, block, instr, any_ireg, base)) return false; + if (!sema_check_asm_arg(context, block, instr, address_size, base)) return false; bit_size = type_bit_size(base_arg->ident.ident_decl->type); break; - case ASM_ARG_ADDROF: - TODO - break; - default: - RETURN_SEMA_ERROR(expr, "Expected a register here."); + case ASM_ARG_MEMADDR: + // Here we have [&foo] BUT it's not a direct address. This is not allowed. + RETURN_SEMA_ERROR(expr, "An &foo cannot appear as parts of an address with offset. Place it in a register first."); + case ASM_ARG_ADDR: + case ASM_ARG_MEMVAR: + case ASM_ARG_VALUE: + case ASM_ARG_INT: + RETURN_SEMA_ERROR(expr, "A register was expected here."); } Expr *index = exprptrzero(asm_arg->idx); - if (index) { - unsigned index_size = 0; ExprAsmArg *index_arg = &index->expr_asm_arg; switch (index_arg->kind) { case ASM_ARG_REG: - if (!sema_check_asm_arg(context, block, instr, any_ireg, index)) return false; - index_size = arg_bits_max(base_arg->reg.ref->bits, 0); + if (!sema_check_asm_arg(context, block, instr, address_size, index)) return false; + if (bit_size != arg_bits_max(index_arg->reg.ref->bits, 0)) + { + RETURN_SEMA_ERROR(expr, "Index register size (%d) does not match base register size (%d).", bit_size, arg_bits_max(index_arg->reg.ref->bits, 0)); + } break; case ASM_ARG_REGVAR: - if (!sema_check_asm_arg(context, block, instr, any_ireg, index)) return false; - index_size = type_bit_size(index_arg->ident.ident_decl->type); + if (!sema_check_asm_arg(context, block, instr, address_size, index)) return false; + if (bit_size != type_bit_size(index_arg->ident.ident_decl->type)) + { + RETURN_SEMA_ERROR(expr, "Index size (%d) does not match base register size (%d).", bit_size, type_bit_size(index_arg->ident.ident_decl->type)); + } break; + case ASM_ARG_MEMADDR: + case ASM_ARG_ADDR: + case ASM_ARG_MEMVAR: + case ASM_ARG_VALUE: + case ASM_ARG_INT: default: - SEMA_ERROR(expr, "Expected a register here."); - return false; - } - if (bit_size != index_size) - { - RETURN_SEMA_ERROR(index, "Expected the same register size as for the base value."); + RETURN_SEMA_ERROR(expr, "Expected a register here."); } } if ((compiler.platform.arch == ARCH_TYPE_RISCV32 || @@ -271,15 +246,33 @@ static inline bool sema_check_asm_arg_addr(SemaContext *context, AsmInlineBlock return true; } +// Check if this argument is a valid register static inline bool sema_check_asm_arg_reg(SemaContext *context, AsmInlineBlock *block, AsmInstruction *instr, AsmArgType arg_type, Expr *expr) { const char *name = expr->expr_asm_arg.reg.name; AsmRegister *reg = expr->expr_asm_arg.reg.ref = asm_reg_by_name(&compiler.platform, name); if (!reg) RETURN_SEMA_ERROR(expr, "Expected a valid register name."); - if (!sema_reg_is_valid_in_slot(reg, arg_type)) + bool is_valid = false; + // Does the instruction allow a register of this size? + switch (reg->type) + { + case ASM_REG_INT: + is_valid = (arg_type.ireg_bits & reg->bits) != 0; + break; + case ASM_REG_FLOAT: + is_valid = (arg_type.float_bits & reg->bits) != 0; + break; + case ASM_REF_FVEC: + case ASM_REG_IVEC: + is_valid = (arg_type.vec_bits & reg->bits) != 0; + break; + } + // This could probably be improved to say what sizes are allowed. + if (!is_valid) { RETURN_SEMA_ERROR(expr, "'%s' is not valid in this slot.", reg->name); } + // If we're writing to the register, then it needs to be clobbered. if (arg_type.is_write) { sema_add_clobber(block, reg->clobber_index); @@ -383,8 +376,7 @@ static inline bool sema_check_asm_var(SemaContext *context, AsmInlineBlock *bloc decl->var.is_read = true; if (decl->var.out_param) { - SEMA_ERROR(expr, "An 'out' variable may not be read from."); - return false; + RETURN_SEMA_ERROR(expr, "An 'out' variable may not be read from."); } asm_reg_add_input(block, arg); } @@ -393,8 +385,7 @@ static inline bool sema_check_asm_var(SemaContext *context, AsmInlineBlock *bloc decl->var.is_written = true; if (decl->var.in_param) { - SEMA_ERROR(expr, "An 'in' variable may not be written to."); - return false; + RETURN_SEMA_ERROR(expr, "An 'in' variable may not be written to."); } asm_reg_add_output(block, arg); } @@ -409,19 +400,16 @@ static inline bool sema_check_asm_var(SemaContext *context, AsmInlineBlock *bloc { if (arg_type.is_address) { - SEMA_ERROR(expr, "You need to pass the variable by address."); - return false; + RETURN_SEMA_ERROR(expr, "You need to pass the variable by address."); } - SEMA_ERROR(expr, "An integer variable was not expected here."); - return false; + RETURN_SEMA_ERROR(expr, "An integer variable was not expected here."); } if (!sema_reg_int_supported_type(arg_type, type)) { unsigned bits = arg_bits_max(arg_type.ireg_bits, 0); ASSERT(bits); - SEMA_ERROR(expr, "%s is not supported in this position, convert it to a valid type, like %s.", + RETURN_SEMA_ERROR(expr, "%s is not supported in this position, convert it to a valid type, like %s.", type_quoted_error_string(decl->type), type_quoted_error_string(type_int_signed_by_bitsize(bits))); - return false; } return true; } @@ -429,27 +417,21 @@ static inline bool sema_check_asm_var(SemaContext *context, AsmInlineBlock *bloc { if (!arg_type.float_bits) { - if (arg_type.is_address) - { - SEMA_ERROR(expr, "You need to pass the variable by address."); - return false; - } - SEMA_ERROR(expr, "A floating point variable was not expected here."); - return false; + if (arg_type.is_address) RETURN_SEMA_ERROR(expr, "You need to pass the variable by address."); + RETURN_SEMA_ERROR(expr, "A floating point variable was not expected here."); } if (!sema_reg_float_supported_type(arg_type, type)) { - SEMA_ERROR(expr, "%s is not supported in this position, convert it to a valid type.", - type_quoted_error_string(decl->type)); - return false; + RETURN_SEMA_ERROR(expr, "%s is not supported in this position, convert it to a valid type.", + type_quoted_error_string(decl->type)); } return true; } - SEMA_ERROR(expr, "%s is not supported as an argument.", type_quoted_error_string(decl->type)); - return false; + RETURN_SEMA_ERROR(expr, "%s is not supported as an argument.", type_quoted_error_string(decl->type)); } +// This is handling [&foo] static inline bool sema_check_asm_memvar(SemaContext *context, AsmInlineBlock *block, AsmInstruction *instr, AsmArgType arg_type, Expr *expr) { ExprAsmArg *arg = &expr->expr_asm_arg; @@ -479,6 +461,30 @@ static inline bool sema_check_asm_memvar(SemaContext *context, AsmInlineBlock *b } asm_reg_add_output(block, arg); } + if (!arg_type.is_address) RETURN_SEMA_ERROR(expr, "This slot does not accept an address."); + return true; +} + +// This is pure &foo +static inline bool sema_check_asm_arg_addrof_var(SemaContext *context, AsmInlineBlock *block, AsmInstruction *instr, AsmArgType arg_type, Expr *expr) +{ + ExprAsmArg *arg = &expr->expr_asm_arg; + const char *name = arg->ident.name; + Decl *decl = sema_resolve_external_symbol(context, expr, name); + if (!decl) return false; + ASSERT(arg->kind == ASM_ARG_MEMADDR); + arg->ident.ident_decl = decl; + if (arg_type.is_write || arg_type.is_readwrite) + { + RETURN_SEMA_ERROR(expr, "This slot is written to, you can't use an address for that, maybe you intended [&foo] or similar?"); + } + arg->ident.is_input = true; + decl->var.is_read = true; + if (decl->var.out_param && !decl->var.in_param) + { + RETURN_SEMA_ERROR(expr, "An 'out' variable may not be read from."); + } + asm_reg_add_input(block, arg); if (!arg_type.is_address) { RETURN_SEMA_ERROR(expr, "This slot does not accept an address."); @@ -493,8 +499,7 @@ static inline bool sema_check_asm_arg_value(SemaContext *context, AsmInlineBlock if (expr_is_const_int(inner)) return sema_check_asm_arg_const_int(context, block, instr, arg_type, expr, inner); if (arg_type.is_write) { - SEMA_ERROR(expr, "This position is written to, you can't use an expression for that."); - return false; + RETURN_SEMA_ERROR(expr, "This position is written to, you can't use an expression for that."); } Type *type = type_flatten(inner->type); if (type_is_pointer_type(type)) type = type_uptr->canonical; @@ -502,8 +507,7 @@ static inline bool sema_check_asm_arg_value(SemaContext *context, AsmInlineBlock { if (!sema_reg_int_supported_type(arg_type, type)) { - SEMA_ERROR(expr, "%s is not valid for this slot.", type_quoted_error_string(inner->type)); - return false; + RETURN_SEMA_ERROR(expr, "%s is not valid for this slot.", type_quoted_error_string(inner->type)); } asm_reg_add_input(block, &expr->expr_asm_arg); expr->type = type; @@ -513,14 +517,13 @@ static inline bool sema_check_asm_arg_value(SemaContext *context, AsmInlineBlock { if (!sema_reg_float_supported_type(arg_type, type)) { - SEMA_ERROR(expr, "%s is not valid for this slot.", type_quoted_error_string(inner->type)); - return false; + RETURN_SEMA_ERROR(expr, "%s is not valid for this slot.", type_quoted_error_string(inner->type)); } asm_reg_add_input(block, &expr->expr_asm_arg); expr->type = type; return true; } - TODO + RETURN_SEMA_ERROR(expr, "%s is not valid for this slot.", type_quoted_error_string(inner->type)); UNREACHABLE } static inline bool sema_check_asm_arg(SemaContext *context, AsmInlineBlock *block, AsmInstruction *instr, AsmArgType arg_type, Expr *expr) @@ -528,7 +531,7 @@ static inline bool sema_check_asm_arg(SemaContext *context, AsmInlineBlock *bloc switch (expr->expr_asm_arg.kind) { case ASM_ARG_INT: - return true; + UNREACHABLE case ASM_ARG_REG: return sema_check_asm_arg_reg(context, block, instr, arg_type, expr); case ASM_ARG_ADDR: @@ -539,9 +542,8 @@ static inline bool sema_check_asm_arg(SemaContext *context, AsmInlineBlock *bloc return sema_check_asm_var(context, block, instr, arg_type, expr); case ASM_ARG_MEMVAR: return sema_check_asm_memvar(context, block, instr, arg_type, expr); - case ASM_ARG_ADDROF: - TODO - break; + case ASM_ARG_MEMADDR: + return sema_check_asm_arg_addrof_var(context, block, instr, arg_type, expr); } UNREACHABLE } @@ -551,7 +553,7 @@ bool sema_analyse_asm(SemaContext *context, AsmInlineBlock *block, Ast *asm_stmt ASSERT(compiler.platform.asm_initialized); AsmInstruction *instr = asm_instr_by_name(asm_stmt->asm_stmt.instruction); - if (!instr) RETURN_SEMA_ERROR(asm_stmt, "Unknown instruction"); + if (!instr) RETURN_SEMA_ERROR(asm_stmt, "Unknown instruction for the current target."); // Check arguments Expr **args = asm_stmt->asm_stmt.args; diff --git a/src/compiler/sema_liveness.c b/src/compiler/sema_liveness.c index 4cd15eebb..fea401856 100644 --- a/src/compiler/sema_liveness.c +++ b/src/compiler/sema_liveness.c @@ -75,11 +75,12 @@ static void sema_trace_asm_arg_list(ExprAsmArg **list) switch (asm_arg->kind) { case ASM_ARG_ADDR: - case ASM_ARG_ADDROF: TODO + return; case ASM_ARG_REG: case ASM_ARG_INT: continue; + case ASM_ARG_MEMADDR: case ASM_ARG_MEMVAR: case ASM_ARG_REGVAR: sema_trace_decl_liveness(asm_arg->ident.ident_decl); @@ -289,7 +290,7 @@ RETRY: switch (expr->expr_asm_arg.kind) { case ASM_ARG_REG: - case ASM_ARG_ADDROF: + case ASM_ARG_MEMADDR: case ASM_ARG_REGVAR: case ASM_ARG_INT: case ASM_ARG_MEMVAR: diff --git a/src/compiler/target.h b/src/compiler/target.h index 088888292..d40ee613a 100644 --- a/src/compiler/target.h +++ b/src/compiler/target.h @@ -57,9 +57,9 @@ typedef struct typedef struct { const char *name; - AsmArgType param[MAX_ASM_INSTRUCTION_PARAMS]; + AsmArgType param[MAX_ASM_INSTRUCTION_PARAMS]; // Types of arguments available in each slot unsigned param_count; - Clobbers mask; + Clobbers mask; // Which will it clobber } AsmInstruction; typedef struct diff --git a/test/test_suite/asm/test_different_size.c3 b/test/test_suite/asm/test_different_size.c3 new file mode 100644 index 000000000..659d8aad0 --- /dev/null +++ b/test/test_suite/asm/test_different_size.c3 @@ -0,0 +1,10 @@ +// #target: macos-x64 +import std; +fn int main() +{ + asm + { + movl $eax, [$ax+$ecx]; // #error: Index register size (16) does not match base register size (32) + } + return 0; +} \ No newline at end of file