From c80fedc070fc2a76225874c190cbefce419c2807 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 12 Aug 2026 09:03:31 +0100 Subject: [PATCH] Support `[base + index<>scale]`; Support syntax for `[base]:u8` (not implemented yet) --- src/check_asm.cpp | 48 ++++++++++++++++++++++++++++++++++++---- src/llvm_backend_asm.cpp | 38 ++++++++++++++++++++++--------- src/parser.cpp | 27 ++++++++++++++++------ src/parser.hpp | 14 +++++++----- src/parser_pos.cpp | 3 +++ 5 files changed, 103 insertions(+), 27 deletions(-) diff --git a/src/check_asm.cpp b/src/check_asm.cpp index add1757c1..d96c33885 100644 --- a/src/check_asm.cpp +++ b/src/check_asm.cpp @@ -991,14 +991,40 @@ gb_internal void check_asm_instruction_operand(AsmCtx *asm_ctx, CheckerContext * break; } else { i64 v = exact_value_to_i64(scale.value); - switch (v) { - case 1: case 2: case 4: case 8: - // okay + + Token op = mem_op->scale_op; + switch (op.kind) { + case Token_Mul: + switch (v) { + case 1: case 2: case 4: case 8: + // okay + break; + default: + error(scale.expr, "A scale using '*' must be a constant integer or an immediate with the value 1, 2, 4, or 8, got %s", s); + break; + } + break; + case Token_Shl: + case Token_Shr: + switch (v) { + case 0: case 1: case 2: case 3: + // okay + break; + default: + error(scale.expr, "A shifting scale using '%.*s' must be a constant integer or an immediate with the value 0, 1, 2, or 3, got %s", LIT(op.string), s); + break; + } break; default: - error(scale.expr, "A scale must be a constant integer or an immediate with the value 1, 2, 4, or 8, got %s", s); + error(op, "Unknown/unhandled scaling operator '%.*s'", LIT(op.string)); break; } + + if (op.kind == Token_Shr) { + if (build_context.metrics.arch != TargetArch_arm64) { + error(op, "The target platform does not support '%.*s' for shifting scale parameters in memory operands", LIT(op.string)); + } + } } } else { Entity *param_entity = entity_of_node(scale.expr); @@ -1065,6 +1091,20 @@ gb_internal void check_asm_instruction_operand(AsmCtx *asm_ctx, CheckerContext * } } + 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); + 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); + } + + } + } + return; case_end; case_ast_node(label, AsmLabelDecl, expr); diff --git a/src/llvm_backend_asm.cpp b/src/llvm_backend_asm.cpp index 3ba6c5cd3..b8f768102 100644 --- a/src/llvm_backend_asm.cpp +++ b/src/llvm_backend_asm.cpp @@ -6,6 +6,7 @@ struct lbAsmGenerate { enum WriteOperandFlags : u32 { WriteOperandFlag_PrintPrefixes = 1<<0, WriteOperandFlag_IsScale = 1<<1, + WriteOperandFlag_IsScaleLog2 = 1<<2, WriteOperandFlag_NONE = 0, WriteOperandFlag_DEFAULT = WriteOperandFlag_PrintPrefixes, @@ -62,24 +63,33 @@ struct lbAsmGenerate { GB_ASSERT(ev.kind != ExactValue_Invalid); switch (ev.kind) { case ExactValue_Integer: { - String s = big_int_to_string(heap_allocator(), &ev.value_integer, 10); - if (flags & WriteOperandFlag_PrintPrefixes) { - asm_string = gb_string_appendc(asm_string, "$$"); - } - asm_string = gb_string_append_length(asm_string, s.text, s.len); - gb_free(heap_allocator(), s.text); - + i64 val = exact_value_to_i64(ev); if (flags & WriteOperandFlag_IsScale) { - i64 val = exact_value_to_i64(ev); switch (val) { case 1: case 2: case 4: case 8: // okay break; default: - error(op, "A scale must be a constant integer or an immediate with the value 1, 2, 4, or 8, got %.*s", LIT(s)); + error(op, "A scale must be a constant integer or an immediate with the value 1, 2, 4, or 8, got %lld", cast(long long)val); + break; + } + } else if (flags & WriteOperandFlag_IsScaleLog2) { + switch (val) { + case 0: case 1: case 2: case 3: + // NOTE(bill): AMD64 only supports full scales + val = (cast(i64)1)<write_operand(asm_string, op_number, mem_op->index, flags); if (mem_op->scale) { asm_string = gb_string_appendc(asm_string, ","); - asm_string = this->write_operand(asm_string, op_number, mem_op->scale, (flags|WriteOperandFlag_IsScale)&~WriteOperandFlag_PrintPrefixes); + switch (mem_op->scale_op.kind) { + case Token_Mul: + asm_string = this->write_operand(asm_string, op_number, mem_op->scale, (flags|WriteOperandFlag_IsScale)&~WriteOperandFlag_PrintPrefixes); + break; + case Token_Shl: + case Token_Shr: + asm_string = this->write_operand(asm_string, op_number, mem_op->scale, (flags|WriteOperandFlag_IsScaleLog2)&~WriteOperandFlag_PrintPrefixes); + break; + } } } asm_string = gb_string_appendc(asm_string, ")"); diff --git a/src/parser.cpp b/src/parser.cpp index d63c60585..cc9905bfa 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -551,6 +551,7 @@ gb_internal Ast *clone_ast(Ast *node, AstFile *f) { n->AsmMemoryOperand.index = clone_ast(n->AsmMemoryOperand.index, f); n->AsmMemoryOperand.scale = clone_ast(n->AsmMemoryOperand.scale, f); n->AsmMemoryOperand.disp = clone_ast(n->AsmMemoryOperand.disp, f); + n->AsmMemoryOperand.type = clone_ast(n->AsmMemoryOperand.type, f); break; } return n; @@ -2444,12 +2445,18 @@ gb_internal Ast *parse_asm_operand(AstFile *f, bool allow_memory_operand) { Ast *index = nullptr; Ast *scale = nullptr; Ast *disp = nullptr; + Ast *type = nullptr; + + Token scale_op = {}; base = parse_asm_operand(f, false); if (allow_token(f, Token_Add)) { Ast *possible_index = parse_asm_operand(f, false); - if (allow_token(f, Token_Mul)) { + if (allow_token(f, Token_Mul) || + allow_token(f, Token_Shl) || + allow_token(f, Token_Shr)) { + scale_op = f->prev_token; index = possible_index; scale = parse_asm_operand(f, false); if (allow_token(f, Token_Add)) { @@ -2462,13 +2469,19 @@ gb_internal Ast *parse_asm_operand(AstFile *f, bool allow_memory_operand) { Token close = expect_token(f, Token_CloseBracket); + if (allow_token(f, Token_Colon)) { + type = parse_type(f); + } + Ast *mem = alloc_ast_node(f, Ast_AsmMemoryOperand); - mem->AsmMemoryOperand.open = open; - mem->AsmMemoryOperand.base = base; - mem->AsmMemoryOperand.index = index; - mem->AsmMemoryOperand.scale = scale; - mem->AsmMemoryOperand.disp = disp; - mem->AsmMemoryOperand.close = close; + mem->AsmMemoryOperand.open = open; + mem->AsmMemoryOperand.base = base; + mem->AsmMemoryOperand.index = index; + mem->AsmMemoryOperand.scale = scale; + mem->AsmMemoryOperand.scale_op = scale_op; + mem->AsmMemoryOperand.disp = disp; + mem->AsmMemoryOperand.close = close; + mem->AsmMemoryOperand.type = type; return mem; } diff --git a/src/parser.hpp b/src/parser.hpp index 22ddc0938..aa75e3df4 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -502,12 +502,14 @@ struct AstSplitArgs { Slice operands; \ }) \ AST_KIND(AsmMemoryOperand, "asm memory operand", struct { \ - Token open; \ - Ast * base; \ - Ast * index; \ - Ast * scale; \ - Ast * disp; \ - Token close; \ + Token open; \ + Ast * base; \ + Ast * index; \ + Token scale_op; \ + Ast * scale; \ + Ast * disp; \ + Ast * type; \ + Token close; \ }) \ AST_KIND(_ExprBegin, "", bool) \ AST_KIND(BadExpr, "bad expression", struct { Token begin, end; }) \ diff --git a/src/parser_pos.cpp b/src/parser_pos.cpp index 3313d831d..4f07c8e8e 100644 --- a/src/parser_pos.cpp +++ b/src/parser_pos.cpp @@ -196,6 +196,9 @@ Token ast_end_token(Ast *node) { } return ast_end_token(node->AsmInstruction.name); case Ast_AsmMemoryOperand: + if (node->AsmMemoryOperand.type) { + return ast_end_token(node->AsmMemoryOperand.type); + } return node->AsmMemoryOperand.close; case Ast_BadExpr: return node->BadExpr.end;