From a604ddceb105a15cba4fbfb972b4919bee6a3848 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 21 Aug 2026 11:47:41 +0100 Subject: [PATCH] Support pseudo macro mnemonics (for RISC-V) in the frontend --- .../riscv/tablegen/cpp-compiler/cpp-gen.odin | 15 +++ .../x86/tablegen/cpp-compiler/cpp-gen.odin | 8 ++ src/asm_tables_amd64.cpp | 8 ++ src/asm_tables_riscv.cpp | 15 +++ src/check_asm.cpp | 93 +++++++++++++++++++ 5 files changed, 139 insertions(+) diff --git a/core/rexcode/isa/riscv/tablegen/cpp-compiler/cpp-gen.odin b/core/rexcode/isa/riscv/tablegen/cpp-compiler/cpp-gen.odin index f357e6700..73c8d6874 100644 --- a/core/rexcode/isa/riscv/tablegen/cpp-compiler/cpp-gen.odin +++ b/core/rexcode/isa/riscv/tablegen/cpp-compiler/cpp-gen.odin @@ -485,6 +485,21 @@ main :: proc() { return m; } + enum PseudoMacroMnemonic : u8 { + PseudoMacroMnemonic_INVALID, + PseudoMacroMnemonic_LI, + PseudoMacroMnemonic_LA, + PseudoMacroMnemonic_LLA, + PseudoMacroMnemonic_COUNT + }; + + PseudoMacroMnemonic pseudo_macro_mnemonic_lookup(String const &name) { + if (name == \"li\") { return PseudoMacroMnemonic_LI; } + if (name == \"la\") { return PseudoMacroMnemonic_LA; } + if (name == \"lla\") { return PseudoMacroMnemonic_LLA; } + return PseudoMacroMnemonic_INVALID; + } + Mnemonic mnemonic_lookup(String const &name) { Mnemonic *found = string_map_get(&mnemonic_map, name); return found ? *found : M_INVALID; diff --git a/core/rexcode/isa/x86/tablegen/cpp-compiler/cpp-gen.odin b/core/rexcode/isa/x86/tablegen/cpp-compiler/cpp-gen.odin index a6052c650..edc0ba60d 100644 --- a/core/rexcode/isa/x86/tablegen/cpp-compiler/cpp-gen.odin +++ b/core/rexcode/isa/x86/tablegen/cpp-compiler/cpp-gen.odin @@ -494,6 +494,14 @@ main :: proc() { return M_INVALID; } + enum PseudoMacroMnemonic : u8 { + PseudoMacroMnemonic_INVALID, + PseudoMacroMnemonic_COUNT + }; + + PseudoMacroMnemonic pseudo_macro_mnemonic_lookup(String const &name) { + return PseudoMacroMnemonic_INVALID; + } Mnemonic mnemonic_lookup(String const &name) { Mnemonic *found = string_map_get(&mnemonic_map, name); diff --git a/src/asm_tables_amd64.cpp b/src/asm_tables_amd64.cpp index d175ec0f5..2485a7bfc 100644 --- a/src/asm_tables_amd64.cpp +++ b/src/asm_tables_amd64.cpp @@ -522,6 +522,14 @@ struct Asm_amd64 { return M_INVALID; } + enum PseudoMacroMnemonic : u8 { + PseudoMacroMnemonic_INVALID, + PseudoMacroMnemonic_COUNT + }; + + PseudoMacroMnemonic pseudo_macro_mnemonic_lookup(String const &name) { + return PseudoMacroMnemonic_INVALID; + } Mnemonic mnemonic_lookup(String const &name) { Mnemonic *found = string_map_get(&mnemonic_map, name); diff --git a/src/asm_tables_riscv.cpp b/src/asm_tables_riscv.cpp index fbafea854..b861d1389 100644 --- a/src/asm_tables_riscv.cpp +++ b/src/asm_tables_riscv.cpp @@ -455,6 +455,21 @@ struct Asm_riscv { return m; } + enum PseudoMacroMnemonic : u8 { + PseudoMacroMnemonic_INVALID, + PseudoMacroMnemonic_LI, + PseudoMacroMnemonic_LA, + PseudoMacroMnemonic_LLA, + PseudoMacroMnemonic_COUNT + }; + + PseudoMacroMnemonic pseudo_macro_mnemonic_lookup(String const &name) { + if (name == "li") { return PseudoMacroMnemonic_LI; } + if (name == "la") { return PseudoMacroMnemonic_LA; } + if (name == "lla") { return PseudoMacroMnemonic_LLA; } + return PseudoMacroMnemonic_INVALID; + } + Mnemonic mnemonic_lookup(String const &name) { Mnemonic *found = string_map_get(&mnemonic_map, name); return found ? *found : M_INVALID; diff --git a/src/check_asm.cpp b/src/check_asm.cpp index 899565876..6aed94084 100644 --- a/src/check_asm.cpp +++ b/src/check_asm.cpp @@ -495,6 +495,14 @@ gb_internal AsmTemplateEntityDeclKind check_asm_find_kind(Entity *entity, Array< return AsmTemplateEntityDecl_Invalid; }; +gb_internal bool check_asm_is_immediate_param(Entity *tmpl_entity, Operand const *o) { + Entity *pe = entity_of_node(o->expr); + if (pe != nullptr && pe->kind == Entity_Variable) { + return check_asm_find_kind(pe, tmpl_entity->AsmTemplate.decls) == AsmTemplateEntityDecl_Immediate; + } + return false; +} + template gb_internal void check_asm_specs(AsmCtx *asm_ctx, CheckerContext *ctx, Scope *scope, Slice const &specs, Array *asm_template_entity_decls) { @@ -851,9 +859,81 @@ enum CheckMnemomicResult { CheckMnemomic_Invalid, CheckMnemomic_Mnemonic, CheckMnemomic_PseudoMnemonic, + CheckMnemomic_PseudoMacroMnemonic, CheckMnemomic_Prefix, }; +template +gb_internal bool check_pseudo_macro_mnemonic(AsmCtx *asm_ctx, Entity *tmpl_entity, + AstAsmInstruction *instr, Slice const &operands) { + if (build_context.metrics.arch != TargetArch_riscv64) { + return false; + } + + /* + NOTE(bill): this is probably not even a complete list when it comes to + of the pseudo macro mnemonics, but this currently covers most of them. + It just handles those edge cases directly as the LLVM assembler will + handle them directly any way. + */ + + int const XLEN = cast(int)(build_context.metrics.ptr_size*8); + + String name = instr->name->Ident.token.string; + + auto want_int_reg = [&](Operand const *o, char const *role) { + if (determine_asm_operand_kind(o) != AsmOperand_Register || + check_asm_reg_class_from_type(o->type) != AsmRegClass_Integer) { + error(o->expr, "'%.*s' %s must be an integer register", LIT(name), role); + return false; + } + int width = check_asm_operand_bit_width(o->type); + if (width > XLEN) { + error(o->expr, "'%.*s' %s is wider than the %d-bit register width, got %d-bits", LIT(name), role, XLEN, width); + return false; + } + return true; + }; + + if (name == "li") { // li rd, imm — dest reg + assemble-time integer that fits XLEN (or a $-immediate) + if (operands.count != 2) { + error(instr->name, "'%.*s' expects 2 operands, got %td", LIT(name), operands.count); + return true; // it exists but incorrectly handled + } + want_int_reg(&operands[0], "destination"); + Operand const *imm = &operands[1]; + if (imm->mode == Addressing_Constant) { + ExactValue ev = exact_value_to_integer(imm->value); + if (ev.kind != ExactValue_Integer) { + error(imm->expr, "'%.*s' immediate must be an integer constant", LIT(name)); + return true; + } + AsmMismatch m = AsmMismatch_None; i32 needed = 0; + if (!check_asm_immediate_value_fits(ev, XLEN, &needed, &m)) { + gbString vs = exact_value_to_string(ev); + error(imm->expr, "'%.*s' immediate %s does not fit in a %d-bit register (needs %d bits)", LIT(name), vs, XLEN, needed); + gb_string_free(vs); + } + } else if (!check_asm_is_immediate_param(tmpl_entity, imm)) { + error(imm->expr, "'li' source must be a constant integer or a $ immediate parameter"); + } + return true; + } else if (name == "la" || name == "lla") { // la / lla rd, symbol — dest reg + a label (or symbol, once representable) + if (operands.count != 2) { + error(instr->name, "'%.*s' expects 2 operands, got %td", LIT(name), operands.count); + // NOTE(bill): it exists but incorrectly handled + return true; + } + want_int_reg(&operands[0], "destination"); + if (determine_asm_operand_kind(&operands[1]) != AsmOperand_Label) { + error(operands[1].expr, "'%.*s' source must be a label", LIT(name)); + } + return true; + } + + return false; +} + template gb_internal CheckMnemomicResult check_mnemonic_name(AsmCtx *asm_ctx, AstAsmInstruction *instr, u16 *mnemonic_, u8 *suffix_flags_) { Token token = instr->name->Ident.token; @@ -883,6 +963,11 @@ gb_internal CheckMnemomicResult check_mnemonic_name(AsmCtx *asm_ctx, AstAsmInstr return CheckMnemomic_Mnemonic; } + auto pmm = asm_ctx->pseudo_macro_mnemonic_lookup(name); + if (pmm) { + if (mnemonic_) *mnemonic_ = cast(u16)pmm; + return CheckMnemomic_PseudoMacroMnemonic; + } ERROR_BLOCK(); if (instr->operands.count == 0) { @@ -1974,6 +2059,14 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity asm_acc.saw_any_instructions = true; + previous_prefix = 0; + previous_prefix_instr = nullptr; + } else if (res == CheckMnemomic_PseudoMacroMnemonic) { + instr->suffix_flags = suffix_flags; + check_pseudo_macro_mnemonic(asm_ctx, entity, instr, slice_from_array(operands)); + + asm_acc.saw_any_instructions = true; + previous_prefix = 0; previous_prefix_instr = nullptr; } else {