asm: minor cleanups

This commit is contained in:
gingerBill
2026-08-25 13:40:16 +01:00
parent b4cf9b14bd
commit 337bb43948
2 changed files with 67 additions and 53 deletions

View File

@@ -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 &reg : *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 &reg : *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<Operand> 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;
}

View File

@@ -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];
}