From 9367fa2ec2227965e407654a1e55625e966eaf4c Mon Sep 17 00:00:00 2001 From: gingerBill Date: Mon, 24 Aug 2026 16:58:37 +0100 Subject: [PATCH] Even more general clean ups --- src/check_asm.cpp | 226 ++++++++++++++++++++++------------------------ 1 file changed, 109 insertions(+), 117 deletions(-) diff --git a/src/check_asm.cpp b/src/check_asm.cpp index 1e9adc310..7f5a5ab0a 100644 --- a/src/check_asm.cpp +++ b/src/check_asm.cpp @@ -384,10 +384,13 @@ gb_internal bool check_asm_addr_register(Operand const *operand, AsmAddrRole rol return false; } if (role == AsmAddr_Index && reg_name.len != 0) { - // rsp/esp cannot be encoded as an index register. - if (reg_name == "rsp" || reg_name == "esp") { - error(operand->expr, "%%%.*s cannot be used as an index register", LIT(reg_name)); - return false; + if (build_context.metrics.arch == TargetArch_amd64) { + // TODO(bill): This is a complete bodge for AMD64, and I need to see if regnalize this further + // rsp/esp cannot be encoded as an index register. + if (reg_name == "rsp" || reg_name == "esp") { + error(operand->expr, "%%%.*s cannot be used as an index register", LIT(reg_name)); + return false; + } } } return true; @@ -447,7 +450,6 @@ gb_internal Type *check_asm_template_signature_params(CheckerContext *ctx, Scope Token name_token = name->Ident.token; Entity *entity = alloc_entity_param(scope, name_token, type, false, /*is_value*/true); - // entity->flags |= EntityFlag_Used; if (is_poly_name) { entity->flags |= EntityFlag_PolyConst; if (is_type_internally_pointer_like(type)) { @@ -456,33 +458,33 @@ gb_internal Type *check_asm_template_signature_params(CheckerContext *ctx, Scope } Entity *found = scope_insert(scope, entity); - if (found == nullptr) { - array_add(&variables, entity); - - AsmTemplateEntityDecl ed = asm_template_entity_decl_default(entity); - if (is_poly_name) { - ed.kind = AsmTemplateEntityDecl_Immediate; - } - if (input_parameters) { - ed.param_group = AsmTemplateEntityDeclParamGroup_Input; - ed.param_index = param_index++; - ed.result_index = -1; - } else { - ed.param_group = AsmTemplateEntityDeclParamGroup_Output; - ed.param_index = -1; - ed.result_index = param_index++; - } - - ed.total_index = cast(i32)asm_template_entity_decls->count; - array_add(asm_template_entity_decls, ed); - } else { + if (found != nullptr) { TokenPos pos = found->token.pos; error(name_token, "Redeclaration of '%.*s' in this scope\n" "\tat %s", LIT(name_token.string), token_pos_to_string(pos)); entity = found; + continue; } + array_add(&variables, entity); + + AsmTemplateEntityDecl ed = asm_template_entity_decl_default(entity); + if (is_poly_name) { + ed.kind = AsmTemplateEntityDecl_Immediate; + } + if (input_parameters) { + ed.param_group = AsmTemplateEntityDeclParamGroup_Input; + ed.param_index = param_index++; + ed.result_index = -1; + } else { + ed.param_group = AsmTemplateEntityDeclParamGroup_Output; + ed.param_index = -1; + ed.result_index = param_index++; + } + + ed.total_index = cast(i32)asm_template_entity_decls->count; + array_add(asm_template_entity_decls, ed); } } @@ -863,8 +865,8 @@ gb_internal bool check_register(AsmCtx *asm_ctx, Operand *operand, AstAsmRegiste operand->type = alloc_type_simd_vector(16, t_f32); break; default: - GB_PANIC("Unhandled register width size: %d", width_in_bits); - break; + error(asm_reg->name, "%d-bit width asm registers are not supported", width_in_bits); + return false; } return true; @@ -1025,7 +1027,9 @@ struct AsmMnemonicAccumulator { PtrSet defined_params; // Union of registers implicitly clobbered by matched forms (for redundant-#clobber hints). - u16 implicit_clobbered_regs; + u16 implicit_clobbered_regs; + u16 explicitly_produced_regs; + u16 stale_outputs; bool straight_line; @@ -1037,9 +1041,6 @@ struct AsmMnemonicAccumulator { // Did the template contain any instructions at all? An empty diverging body can't diverge. bool saw_any_instructions; - u16 explicitly_produced_regs; - u16 stale_outputs; - // Related to #align_stack // any call/branch (CONTROL) or memory effect that could require the stack // to be realigned. If none occurred, #align_stack is redundant. @@ -1555,9 +1556,8 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm LIT(name), rname, role, LIT(owner), rname, LIT(owner)); } else { error(instr->name, - "'%.*s' implicitly reads %%%s, but nothing in this template produces " - "a value for it; pin an input parameter to %%%s, or write %%%s before " - "this instruction", + "'%.*s' implicitly reads %%%s, but nothing in this template produces a value for it; " + "pin an input parameter to %%%s, or write %%%s before this instruction", LIT(name), rname, rname, rname); } } @@ -1576,44 +1576,46 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm auto const &op = operands[i]; Ast *e = op.expr; - if (e && e->kind == Ast_AsmRegister) { + if (e != nullptr && e->kind == Ast_AsmRegister) { u16 b = asm_ctx->clobber_bit_for_reg_name(e->AsmRegister.name.string); produced |= b; explicit_writes |= b; - } else { - // NOTE(bill): A write through a pinned parameter (or a width-view of one) - // defines that parameter's physical register for the read-before-write check only - auto written_pinned_reg_bit = [&](Operand const &op) -> u16 { - Entity *pe = entity_of_node(op.expr); - if (pe == nullptr || pe->kind != Entity_Variable) { - return 0; + continue; + } + + // NOTE(bill): A write through a pinned parameter (or a width-view of one) + // defines that parameter's physical register for the read-before-write check only + auto written_pinned_reg_bit = [&](Operand const &op) -> u16 { + Entity *pe = entity_of_node(op.expr); + if (pe == nullptr || pe->kind != Entity_Variable) { + return 0; + } + auto const &decls = tmpl_entity->AsmTemplate.decls; + for_array(di, decls) { + auto const &ed = decls[di]; + if (ed.entity != pe) { + continue; } - auto const &decls = tmpl_entity->AsmTemplate.decls; - for_array(di, decls) { - auto const &ed = decls[di]; - if (ed.entity != pe) { - continue; + if (ed.pin.len != 0) { + return asm_ctx->clobber_bit_for_reg_name(ed.pin); + } + // NOTE(bill): A width-view carries no pin of its own and thus it aliases its source's register. + if (ed.view_of >= 0 && ed.view_of < cast(i32)decls.count) { + String src_pin = decls[ed.view_of].pin; + if (src_pin.len != 0) { + return asm_ctx->clobber_bit_for_reg_name(src_pin); } - if (ed.pin.len != 0) { - return asm_ctx->clobber_bit_for_reg_name(ed.pin); - } - // NOTE(bill): A width-view carries no pin of its own and thus it aliases its source's register. - if (ed.view_of >= 0 && ed.view_of < cast(i32)decls.count) { - String src_pin = decls[ed.view_of].pin; - if (src_pin.len != 0) { - return asm_ctx->clobber_bit_for_reg_name(src_pin); - } - } - return 0; } return 0; - }; + } + return 0; + }; - pinned_param_writes |= written_pinned_reg_bit(operands[i]); - } + pinned_param_writes |= written_pinned_reg_bit(operands[i]); } - if (is_pseudo) { + if (is_pseudo && + build_context.metrics.arch == TargetArch_riscv64) { // Synthesized register sources (e.g. ra in `jal off` -> `jal ra, off`) also // write a physical register; record them so ra is treated as produced/clobbered. u16 synth = 0; @@ -1631,10 +1633,10 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm } asm_acc->defined_regs |= produced | pinned_param_writes; - // NOTE(bill): check for read-before-write (use of undefined value) - // Only really meaningful in straight-line code, so a label above means a branch - // could have defined the value out of the textual order if (asm_acc->straight_line) { + // NOTE(bill): check for read-before-write (use of undefined value) + // Only really meaningful in straight-line code, so a label above means a branch + // could have defined the value out of the textual order for_array(i, operands) { auto const &op = operands[i]; int slot = user_operand_target_index(cast(int)i); @@ -1665,42 +1667,40 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm } } - // Registers this form clobbers implicitly (RDTSC->RAX:RDX, etc.), for the - // redundant-#clobber hint. Union across the template; pinned regs excluded - // so a legitimate output pin is never called "redundant". { + // Registers this form clobbers implicitly (RDTSC->RAX:RDX, etc.), for the + // redundant-#clobber hint. Union across the template; pinned regs excluded + // so a legitimate output pin is never called "redundant". u16 implicit_wr = cast(u16)clobber.implicit_wr & asm_ctx->CLOBBER_REGS_NAMED; asm_acc->implicit_clobbered_regs |= implicit_wr & ~pinned_mask; - } - // Approximate staleness. An output that was explicitly produced (literal %reg write) - // and is later implicitly clobbered — without this same instruction re-producing it — - // is marked stale. Explicit re-production clears it. Implicitly-produced outputs - // (RDTSC->RDX) are never tracked, so they never false-fire. - { - u16 implicit_clobber = cast(u16)clobber.implicit_wr & asm_ctx->CLOBBER_REGS_NAMED; + // Approximate staleness. An output that was explicitly produced (literal %reg write) + // and is later implicitly clobbered — without this same instruction re-producing it — + // is marked stale. Explicit re-production clears it. Implicitly-produced outputs + // (RDTSC->RDX) are never tracked, so they never false-fire. asm_acc->explicitly_produced_regs |= explicit_writes; asm_acc->stale_outputs &= ~explicit_writes; - asm_acc->stale_outputs |= implicit_clobber & asm_acc->explicitly_produced_regs & ~explicit_writes; + asm_acc->stale_outputs |= implicit_wr & asm_acc->explicitly_produced_regs & ~explicit_writes; } - // Terminality for a #diverging template: this instruction ends straight-line - // flow off the end (jmp/ret/etc. -> CONTROL, hlt/ud2 -> HALT). A conditional - // branch does NOT terminate (it can fall through), so require that the form - // is not merely CONTROL-with-fallthrough. We approximate "unconditional" as - // CONTROL|HALT with no explicit label/operand fallthrough below. { + // Terminality for a #diverging template: this instruction ends straight-line + // flow off the end (jmp/ret/etc. -> CONTROL, hlt/ud2 -> HALT). A conditional + // branch does NOT terminate (it can fall through), so require that the form + // is not merely CONTROL-with-fallthrough. We approximate "unconditional" as + // CONTROL|HALT with no explicit label/operand fallthrough below. + bool control = clobber.has_control(); bool halt = clobber.has_halt(); // A conditional branch reads a flag and can fall through -> not terminal. bool conditional = clobber.is_conditional(); asm_acc->last_is_terminal = halt || (control && !conditional); - } - // A branch/call inside the template means subsequent instructions may be reached - // out of textual order; stop trusting the linear def model past this point. - if (clobber.has_control()) { - asm_acc->straight_line = false; + if (control) { + // A branch/call inside the template means subsequent instructions may be reached + // out of textual order; stop trusting the linear def model past this point. + asm_acc->straight_line = false; + } } asm_ctx->clobber_implicit_regs(&tmpl_entity->AsmTemplate.clobber_registers_set, produced); @@ -1751,11 +1751,13 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm auto type = operand_slot_type(form, cast(int)i); AsmOperandKind dst = asm_ctx->kind_from_operand_type(type); AsmOperandKind src = determine_asm_operand_kind(&operands[i]); - possible_kinds[i] = dst; + + possible_kinds [i] = dst; possible_class_kinds[i] = asm_ctx->reg_class_from_operand_type(type); bool kind_ok = (dst == src) || - (dst == AsmOperand_Register_Or_Memory && (src == AsmOperand_Register || src == AsmOperand_Memory)); + (dst == AsmOperand_Register_Or_Memory + && (src == AsmOperand_Register || src == AsmOperand_Memory)); if (!kind_ok) { valid_spots[i] = false; } else { @@ -1798,7 +1800,6 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm begin_error_block(); if (m == AsmMismatch_ImmRange) { - ExactValue ev = operands[i].value; gbString vs = exact_value_to_string(ev); i32 bits_required = 0; @@ -1893,20 +1894,22 @@ gb_internal void check_asm_instruction_operand(AsmCtx *asm_ctx, CheckerContext * return; } found = scope_lookup(param_scope->parent, i->interned, i->hash); - if (found != nullptr) { - if (found->kind == Entity_Constant) { - i->entity = found; - operand->mode = Addressing_Constant; - operand->value = found->Constant.value; - operand->type = found->type; - - add_type_and_value(ctx, expr, operand->mode, operand->type, operand->value); - } else { - error(expr, "Only asm parameters or constants are allowed to be used within an 'asm' template"); - } - } else { + if (found == nullptr) { error(expr, "Undeclared asm parameter or constant '%.*s'", LIT(i->token.string)); + return; } + + if (found->kind != Entity_Constant) { + error(expr, "Only asm parameters or constants are allowed to be used within an 'asm' template"); + return; + } + + add_entity_use(ctx, expr, entity); + operand->mode = Addressing_Constant; + operand->value = found->Constant.value; + operand->type = found->type; + + add_type_and_value(ctx, expr, operand->mode, operand->type, operand->value); return; case_end; case_ast_node(bl, BasicLit, expr); @@ -2221,12 +2224,10 @@ gb_internal void check_asm_instruction_operand(AsmCtx *asm_ctx, CheckerContext * Entity *found = scope_lookup_current(label_scope, name->interned, name->hash); if (found == nullptr) { error(expr, "Undeclared asm label '.%.*s'", LIT(name->token.string)); + return; } - name->entity = found; - if (found != nullptr) { - found->flags |= EntityFlag_Used; - add_type_and_value(ctx, expr, Addressing_Value, found->type, {}); - } + add_entity_use(ctx, label->name, found); + add_type_and_value(ctx, expr, Addressing_Value, found->type, {}); return; case_end; } @@ -2544,21 +2545,12 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity check_asm_instruction_operand(asm_ctx, ctx, entity, &operand, expr, /*allow_memory_operands*/true); array_add(&operands, operand); } - for (auto const &op : operands) { + for (auto &op : operands) { if (op.mode != Addressing_Constant) { error(op.expr, "Expected an integer for the asm directive #%.*s", LIT(name)); continue; } - ExactValue ev = exact_value_to_integer(op.value); - if (ev.kind != ExactValue_Integer) { - error(op.expr, "Expected an integer for the asm directive #%.*s", LIT(name)); - continue; - } - i64 i = exact_value_to_i64(ev); - if (i < 0 || i > 255) { - error(op.expr, "Expected an integer within 0..<256 for the asm directive #%.*s, got %lld", LIT(name), cast(long long)i); - continue; - } + check_assignment(ctx, &op, t_u8, str_lit("asm '#byte' directive")); } } else if (name == "align") { if (dir->operands.count != 1) {