diff --git a/src/check_asm.cpp b/src/check_asm.cpp index fe2f7d585..8fb0ff861 100644 --- a/src/check_asm.cpp +++ b/src/check_asm.cpp @@ -852,12 +852,29 @@ gb_internal CheckMnemomicResult check_mnemonic_name(AsmCtx *asm_ctx, AstAsmInstr return CheckMnemomic_Invalid; } +struct AsmMnemonicAccumulator { + u16 defined_regs; + + // Union of registers implicitly clobbered by matched forms (for redundant-#clobber hints). + u16 implicit_clobbered_regs; + + bool straight_line; + + // Whether the most-recently-checked instruction terminates straight-line flow. + // Reset to false at every label (a label starts a fresh straight-line region whose + // tail we haven't seen yet). Consulted after the loop for #diverging templates. + bool last_is_terminal; + + // Did the template contain any instructions at all? An empty diverging body can't diverge. + bool saw_any_instructions; +}; + template gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tmpl_entity, AstAsmInstruction *instr, u16 mnemonic, Slice const &operands, u8 previous_prefix, Ast *previous_prefix_instr, - u16 *defined_regs, bool *straight_line) { + AsmMnemonicAccumulator *asm_acc) { GB_ASSERT(mnemonic > 0); auto forms = asm_ctx->encoding_forms(mnemonic); String name = asm_ctx->mnemonic_strings[mnemonic]; @@ -1011,9 +1028,9 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm } } - if (*straight_line) { + if (asm_acc->straight_line) { u16 wants = cast(u16)clobber.implicit_rd & asm_ctx->CLOBBER_REGS_NAMED; - u16 undefined = wants & ~*defined_regs & ~pinned_mask; + u16 undefined = wants & ~asm_acc->defined_regs & ~pinned_mask; for (u16 bit = 1; bit != 0; bit <<= 1) { if ((undefined & bit) == 0) { continue; @@ -1043,12 +1060,34 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm produced |= asm_ctx->clobber_bit_for_reg_name(e->AsmRegister.name.string); } } - *defined_regs |= produced; + asm_acc->defined_regs |= produced; + + // 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; + } + + // 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. + { + u16 se = cast(u16)clobber.side_effects; + bool control = (se & asm_ctx->SideEffectFlag_CONTROL) != 0; + bool halt = (se & asm_ctx->SideEffectFlag_HALT) != 0; + // A conditional branch reads a flag and can fall through -> not terminal. + bool conditional = control && (cast(u16)clobber.flags_rd != 0); + 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 (cast(u16)clobber.side_effects & asm_ctx->SideEffectFlag_CONTROL) { - *straight_line = false; + asm_acc->straight_line = false; } asm_ctx->clobber_implicit_regs(&tmpl_entity->AsmTemplate.clobber_registers_set, produced); @@ -1504,9 +1543,8 @@ gb_internal void check_asm_instruction_operand(AsmCtx *asm_ctx, CheckerContext * name->entity = found; if (found != nullptr) { found->flags |= EntityFlag_Used; + add_type_and_value(ctx, expr, Addressing_Value, found->type, {}); } - - add_type_and_value(ctx, expr, Addressing_Value, found->type, {}); return; case_end; } @@ -1634,10 +1672,11 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity } } + AsmMnemonicAccumulator asm_acc = {}; + // Physical registers known to hold a defined value at the current point in the // straight-line instruction stream. Seeded with input-pinned registers (they // carry their argument at entry); grows as instructions write registers. - u16 defined_regs = 0; for (auto const &ed : ate->decls) { if (ed.pin.len == 0) { continue; @@ -1646,7 +1685,7 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity // value at entry. Output/scratch pins start undefined and become defined // when an instruction writes them. if (ed.param_group == AsmTemplateEntityDeclParamGroup_Input) { - defined_regs |= asm_ctx->clobber_bit_for_reg_name(ed.pin); + asm_acc.defined_regs |= asm_ctx->clobber_bit_for_reg_name(ed.pin); } } @@ -1654,7 +1693,7 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity // runtime" while control flow is straight-line. The first label is a potential // jump target / back-edge, after which a read can precede its textual def; from // there on we stop emitting the implicit-read diagnostic. - bool straight_line = true; + asm_acc.straight_line = true; // collect label decls for (Ast *instruction_ : at->instructions) { @@ -1716,7 +1755,10 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity } else if (res == CheckMnemomic_Mnemonic) { check_mnemonic(asm_ctx, ctx, entity, instr, mnemonic, slice_from_array(operands), previous_prefix, previous_prefix_instr, - &defined_regs, &straight_line); + &asm_acc); + + asm_acc.saw_any_instructions = true; + previous_prefix = 0; previous_prefix_instr = nullptr; } else { @@ -1727,7 +1769,10 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity case_end; case_ast_node(label, AsmLabelDecl, instruction_); - straight_line = false; + asm_acc.straight_line = false; + // A new straight-line region begins here; its tail is unseen, + // so the previous instruction's terminality no longer describes the body's end. + asm_acc.last_is_terminal = false; if (previous_prefix != 0) { error(previous_prefix_instr, "A prefix must be immediately followed by an instruction, but a label declaration was found"); previous_prefix = 0; @@ -1840,7 +1885,7 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity continue; } u16 bit = asm_ctx->clobber_bit_for_reg_name(ed.pin); - if (bit && (defined_regs & bit) == 0 && straight_line) { + if (bit && (asm_acc.defined_regs & bit) == 0 && asm_acc.straight_line) { error(ed.entity->token, "Output '%.*s' is pinned to %%%.*s but nothing in this template writes it", LIT(ed.entity->token.string), LIT(ed.pin)); @@ -1864,6 +1909,17 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity "and does not #volatile in the specification block; it may be optimized away. " "Please add #volatile if the effect is intended."); } + + + if (type->Proc.diverging) { + if (!asm_acc.saw_any_instructions) { + error(entity->token, "This asm template is declared as diverging (-> !) but its body is empty and cannot diverge"); + } else if (!asm_acc.last_is_terminal) { + error(entity->token, + "This asm template is declared diverging (-> !) but its final instruction can fall through; " + "end it with an unconditional jump, return, or halt"); + } + } } gb_internal void check_asm_template_from_entity(CheckerContext *c, Entity *e, DeclInfo *d) {