From 94aba2bd6b6c5a52fea506d0f35014146290296b Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 7 Sep 2026 14:43:58 +0100 Subject: [PATCH] Support `#reserve` for asm registers --- core/odin/parser/parser.odin | 4 ++-- src/check_asm.cpp | 39 +++++++++++++++++++++++++++++------- src/check_asm_cfg.cpp | 33 ++++++++++++++++++++++++++++++ src/entity.cpp | 1 + src/parser.cpp | 5 +++-- 5 files changed, 71 insertions(+), 11 deletions(-) diff --git a/core/odin/parser/parser.odin b/core/odin/parser/parser.odin index 1d9d14f8e..aabb52923 100644 --- a/core/odin/parser/parser.odin +++ b/core/odin/parser/parser.odin @@ -4265,7 +4265,7 @@ parse_asm_template :: proc(p: ^Parser) -> ^ast.Expr { clobber.token = hash clobber.name = name.text append(&clobbers_dyn, clobber) - case "clobber": + case "clobber", "preserve": value := parse_asm_operand(p, false) clobber := ast.new(ast.Asm_Clobber, hash.pos, end_pos(p.prev_tok)) clobber.token = hash @@ -4273,7 +4273,7 @@ parse_asm_template :: proc(p: ^Parser) -> ^ast.Expr { clobber.value = value append(&clobbers_dyn, clobber) case: - error(p, name.pos, "expected #clobber, #volatile, #align_stack, or #pure, got '%s'", name.text) + error(p, name.pos, "expected #clobber, #preserve, #volatile, #align_stack, or #pure, got '%s'", name.text) } case: error(p, p.curr_tok.pos, "expected an 'asm' specification which begins with an identifier, got '%s'", p.curr_tok.text) diff --git a/src/check_asm.cpp b/src/check_asm.cpp index 200c7fec5..37a9eaab2 100644 --- a/src/check_asm.cpp +++ b/src/check_asm.cpp @@ -2603,7 +2603,8 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity { // check clobbers bool is_volatile = false; bool is_align_stack = false; - auto *clobber_registers_set = &entity->AsmTemplate.clobber_registers_set; + auto *clobber_registers_set = &entity->AsmTemplate.clobber_registers_set; + auto *preserve_registers_set = &entity->AsmTemplate.preserve_registers_set; bool clobber_flags = false; bool clobber_memory = false; @@ -2633,22 +2634,36 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity continue; } + if (clobber->name.string == "preserve" || + clobber->name.string == "clobber") { + // okay + } else { + error(clobber->name, "Unknown clobber directive '#%.*s'", LIT(clobber->name.string)); + continue; + } + + bool is_preserve = clobber->name.string == "preserve"; + auto *target_set = is_preserve ? preserve_registers_set : clobber_registers_set; + switch (clobber->value->kind) { case_ast_node(asm_reg, AsmRegister, clobber->value) String reg = asm_reg->name.string; if (asm_reg->flag.string != "") { - error(asm_reg->flag, "#clobber on specific flags is not allowed"); + error(asm_reg->flag, "#%.*s on specific flags is not allowed", LIT(clobber->name.string)); } Operand operand = {}; if (check_register(asm_ctx, &operand, asm_reg)) { - if (string_set_update(clobber_registers_set, reg)) { - error(clobber->value, "#clobber %%%.*s has already been defined", LIT(reg)); + if (string_set_update(target_set, reg)) { + error(clobber->value, "#%.*s %%%.*s has already been defined", LIT(clobber->name.string), LIT(reg)); } } case_end; case_ast_node(ident, Ident, clobber->value); String str = ident->token.string; - if (str == "flags") { + if (is_preserve) { + // #preserve applies only to registers, not flags/memory. + error(clobber->value, "Expected a register for a '#preserve' specification, got '%.*s'", LIT(str)); + } else if (str == "flags") { if (clobber_flags) { error(clobber->value, "#clobber flags has already been defined"); } @@ -2659,11 +2674,13 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity } clobber_memory = true; } else { - error(clobber->value, "Expected either a register, 'flags', or 'memory' for a '#clobber' specification, got '%.*s'", LIT(str)); + error(clobber->value, "Expected either a register, 'flags', or 'memory' for a '#%.*s' specification, got '%.*s'", LIT(str), LIT(clobber->name.string)); } case_end; default: - error(clobber->value, "Expected either a register, 'flags', or 'memory' for a '#clobber' specification"); + error(clobber->value, "Expected a register%s for a '#%.*s' specification", + is_preserve ? "" : ", 'flags', or 'memory'", + LIT(clobber->name.string)); break; } } @@ -2681,6 +2698,14 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity string_set_update(clobber_registers_set, rname); } } + + for (String const ® : *preserve_registers_set) { + u16 bit = asm_ctx->clobber_bit_for_reg_name(reg); + String rname = make_string_c(asm_ctx->clobber_reg_bit_name(bit)); + if (rname != reg) { + string_set_update(preserve_registers_set, rname); + } + } } diff --git a/src/check_asm_cfg.cpp b/src/check_asm_cfg.cpp index abc68925a..f13ba2cb8 100644 --- a/src/check_asm_cfg.cpp +++ b/src/check_asm_cfg.cpp @@ -402,17 +402,21 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont // NOTE(bill): entry seed intiailization which mirrors the linear seeding of defined_regs u16 seed_regs = 0; u64 seed_pm = 0; + AsmRegW entry_seed_w = {}; for_array(i, decls) { auto const &ed = decls[i]; u16 pin_bit = cfg->decl_pin_bit[i]; + bool is_input_pin = false; if (ed.no_init) { seed_pm |= bit_of(ed.entity); seed_regs |= pin_bit; + is_input_pin = true; } switch (ed.param_group) { case AsmTemplateEntityDeclParamGroup_Input: seed_pm |= bit_of(ed.entity); seed_regs |= pin_bit; + is_input_pin = true; break; case AsmTemplateEntityDeclParamGroup_Output: if (ed.tie >= 0) { @@ -420,6 +424,30 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont } break; } + + if (is_input_pin && pin_bit != 0 && ed.entity != nullptr) { + i32 wi = asm_reg_index_from_bit(pin_bit); + if (wi >= 0) { + i32 tw = cast(i32)(type_size_of(ed.entity->type) * 8); + if (tw > 255) tw = 255; + entry_seed_w.e[wi] = gb_max(entry_seed_w.e[wi], cast(u8)tw); + } + } + } + + // NOTE(bill): #preserve + u16 preserve_bits = 0; + for (String const ® : entity->AsmTemplate.preserve_registers_set) { + preserve_bits |= asm_ctx->clobber_bit_for_reg_name(reg); + } + seed_regs |= preserve_bits; + { + u8 full = cast(u8)(build_context.metrics.ptr_size * 8); + for (i32 idx = 0; idx < ASM_WIDTH_REG_COUNT; idx++) { + if (preserve_bits & cast(u16)(1u << idx)) { + entry_seed_w.e[idx] = gb_max(entry_seed_w.e[idx], full); + } + } } isize const n = cfg->blocks.count; @@ -561,6 +589,9 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont continue; } in_w[bi] = {}; + if (bi == 0) { + in_w[bi] = entry_seed_w; + } for (i32 idx = 0; idx < ASM_WIDTH_REG_COUNT; idx++) { out_w[bi].e[idx] = gb_max(in_w[bi].e[idx], gen_w[bi].e[idx]); } @@ -591,6 +622,8 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont nin.e[idx] = gb_min(nin.e[idx], out_w[p].e[idx]); } } + } else { + nin = entry_seed_w; } for (i32 idx = 0; idx < ASM_WIDTH_REG_COUNT; idx++) { nout.e[idx] = gb_max(nin.e[idx], gen_w[bi].e[idx]); diff --git a/src/entity.cpp b/src/entity.cpp index 95b5bf6b7..42e1a32d4 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -357,6 +357,7 @@ struct Entity { bool clobber_flags; bool clobber_memory; StringSet clobber_registers_set; + StringSet preserve_registers_set; Scope *param_scope; Scope *label_scope; diff --git a/src/parser.cpp b/src/parser.cpp index c14fa1ac8..ed2a80c88 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -2922,7 +2922,8 @@ gb_internal Ast *parse_asm_template(AstFile *f) { clobber->AsmClobber.token = hash; clobber->AsmClobber.name = name; array_add(&clobbers, clobber); - } else if (name.string == "clobber") { + } else if (name.string == "clobber" || + name.string == "preserve") { Ast *value = parse_asm_operand(f, false); Ast *clobber = alloc_ast_node(f, Ast_AsmClobber); clobber->AsmClobber.token = hash; @@ -2930,7 +2931,7 @@ gb_internal Ast *parse_asm_template(AstFile *f) { clobber->AsmClobber.value = value; array_add(&clobbers, clobber); } else { - syntax_error(name, "Expected #clobber, #side_effects, or #align_stack, got '%.*s'", LIT(name.string)); + syntax_error(name, "Expected #clobber, #preserve, #side_effects, #align_stack, or #pure, got '%.*s'", LIT(name.string)); } } else { syntax_error(f->curr_token, "Expected am asm specification which begins with a identifier, got '%.*s'", LIT(f->curr_token.string));