diff --git a/src/check_asm.cpp b/src/check_asm.cpp index 4a7e0d282..6256f314d 100644 --- a/src/check_asm.cpp +++ b/src/check_asm.cpp @@ -2358,13 +2358,14 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity entity->type = type; - bool is_volatile = false; - bool is_align_stack = false; - bool is_pure_annotated = false; - auto *clobber_registers_set = &entity->AsmTemplate.clobber_registers_set; - check_asm_specs(asm_ctx, ctx, ate->param_scope, at->specs, &ate->decls); + + bool is_pure_annotated = false; { // check clobbers + bool is_volatile = false; + bool is_align_stack = false; + auto *clobber_registers_set = &entity->AsmTemplate.clobber_registers_set; + bool clobber_flags = false; bool clobber_memory = false; @@ -2432,17 +2433,18 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity entity->AsmTemplate.clobber_memory = clobber_memory; entity->AsmTemplate.is_volatile = is_volatile; entity->AsmTemplate.is_align_stack = is_align_stack; - } - // add normalizations for the reigsters too - for (String const ® : *clobber_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(clobber_registers_set, rname); + // add normalizations for the registers too + for (String const ® : *clobber_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(clobber_registers_set, rname); + } } } + // Two distinct operands pinned to the same physical register only makes sense when // they are tied (they intentionally share one register). Compared by bit so %eax // and %rax collide. Flag pins ("flags") yield bit 0 and are skipped. @@ -2471,16 +2473,6 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity } } - // NOTE(bill, 2026-08-24): Construct a control-flow graph (CFG) from the instructions - // to do further analysis which is not possible with an conservative straight-line approximation - // Using a CFG is a much sounder approach for calculating: - // * reads before writes - // * divergence - // * unreachable code - - AsmCfg cfg = {}; - asm_cfg_init(&cfg); - defer (asm_cfg_destroy(&cfg)); // collect label decls for (Ast *instruction_ : at->instructions) { @@ -2507,6 +2499,24 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity } } + // NOTE(bill, 2026-08-24): Construct a control-flow graph (CFG) from the instructions + // to do further analysis which is not possible with an conservative straight-line approximation + // Using a CFG is a much sounder approach for calculating: + // * read-before-writes + // * sub-register width checks + // * `%flags` checks + // * divergence + // * unreachable code + // * liveness checks (forward and backwards) + + // NOTE(bill): the AsmCfg structure also hold information which is used to in the linear pass + // mainly because it will be used later on by it, so it makes sense to keep them together as + // one unit rather than two separate structures. + + AsmCfg cfg = {}; + asm_cfg_init(&cfg); + defer (asm_cfg_destroy(&cfg)); + Array operands = {}; operands.allocator = heap_allocator(); array_reserve(&operands, 16); @@ -2679,10 +2689,14 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity error(previous_prefix_instr, "A prefix must be immediately followed by an instruction, but the template ended"); } + // NOTE(bill): After the linear collection pass of the mnemonics, + // now do the CFG building, analysis, and liveness checks (only if everything was correct) + check_asm_cfg_build(asm_ctx, &cfg, d->init_expr, entity); check_asm_cfg_analyse(asm_ctx, &cfg, ctx, entity); check_asm_cfg_liveness(asm_ctx, &cfg, entity, /*emit_dead_writes*/all_instructions_good); + bool vet_unused = false; { AstFile *file = ctx->file; @@ -2740,7 +2754,7 @@ gb_internal void check_asm_template(AsmCtx *asm_ctx, CheckerContext *ctx, Entity continue; } } - if (ed.tie > 0) { + if (ed.tie >= 0) { // TODO(bill): Handle this edge case? continue; } diff --git a/src/check_asm_cfg.cpp b/src/check_asm_cfg.cpp index d2b423e23..e6e134d82 100644 --- a/src/check_asm_cfg.cpp +++ b/src/check_asm_cfg.cpp @@ -463,9 +463,9 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont } for_array(bi, cfg->blocks) { - u16 gr = 0; - u16 gf = 0; - u64 gp = 0; + u16 gr = 0; + u16 gf = 0; + u64 gp = 0; AsmRegW gw = {}; AsmBlock const &b = cfg->blocks[bi]; @@ -486,10 +486,10 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont } } } - gen_regs[bi] = gr; + gen_regs [bi] = gr; gen_flags[bi] = gf; - gen_pm[bi] = gp; - gen_w[bi] = gw; + gen_pm [bi] = gp; + gen_w [bi] = gw; } // NOTE(bill): initialize the blocks @@ -499,16 +499,16 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont continue; } if (bi == 0) { - in_regs[bi] = seed_regs; - in_pm[bi] = seed_pm; + in_regs [bi] = seed_regs; + in_pm [bi] = seed_pm; in_flags[bi] = 0; // no flag is defined at the template entry point } else { - in_regs[bi] = REG_TOP; - in_pm[bi] = universe_pm; + in_regs [bi] = REG_TOP; + in_pm [bi] = universe_pm; in_flags[bi] = FLAG_TOP; } - out_regs[bi] = in_regs[bi] | gen_regs[bi]; - out_pm[bi] = in_pm[bi] | gen_pm[bi]; + out_regs [bi] = in_regs [bi] | gen_regs [bi]; + out_pm [bi] = in_pm [bi] | gen_pm [bi]; out_flags[bi] = in_flags[bi] | gen_flags[bi]; } @@ -538,17 +538,17 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont u64 nout_p = nin_p | gen_pm[bi]; u16 nout_f = nin_f | gen_flags[bi]; - if (nin_r != in_regs[bi] || - nin_p != in_pm[bi] || - nin_f != in_flags[bi] || - nout_r != out_regs[bi] || - nout_p != out_pm[bi] || + if (nin_r != in_regs [bi] || + nin_p != in_pm [bi] || + nin_f != in_flags [bi] || + nout_r != out_regs [bi] || + nout_p != out_pm [bi] || nout_f != out_flags[bi]) { - in_regs[bi] = nin_r; - in_pm[bi] = nin_p; - in_flags[bi] = nin_f; - out_regs[bi] = nout_r; - out_pm[bi] = nout_p; + in_regs [bi] = nin_r; + in_pm [bi] = nin_p; + in_flags [bi] = nin_f; + out_regs [bi] = nout_r; + out_pm [bi] = nout_p; out_flags[bi] = nout_f; changed = true; } @@ -606,9 +606,9 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont // NOTE(bill): publish the register masks and materialise the parameter sets onto the blocks for_array(bi, cfg->blocks) { AsmBlock *b = &cfg->blocks[bi]; - b->in_defs = in_regs[bi]; - b->out_defs = out_regs[bi]; - b->in_flags = in_flags[bi]; + b->in_defs = in_regs [bi]; + b->out_defs = out_regs [bi]; + b->in_flags = in_flags [bi]; b->out_flags = out_flags[bi]; if (!b->reachable) { continue; @@ -670,10 +670,10 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont continue; } - u16 run_regs = in_regs[bi]; + u16 run_regs = in_regs [bi]; u16 run_flags = in_flags[bi]; - u64 run_pm = in_pm[bi]; - AsmRegW run_w = in_w[bi]; + u64 run_pm = in_pm [bi]; + AsmRegW run_w = in_w [bi]; for (i32 ii = b.first; ii <= b.last; ii++) { @@ -790,8 +790,8 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont continue; } any_exit = true; - exit_regs &= out_regs[bi]; - exit_pm &= out_pm[bi]; + exit_regs &= out_regs [bi]; + exit_pm &= out_pm [bi]; exit_flags &= out_flags[bi]; }