mirror of
https://github.com/odin-lang/Odin.git
synced 2026-08-27 23:41:31 +00:00
asm: correctly handle flags in the CFG
This commit is contained in:
@@ -120,12 +120,12 @@ main :: proc() {
|
||||
strings.write_string(&sb, "\n")
|
||||
strings.write_string(&sb, """
|
||||
|
||||
enum ClobberFFlags : u8 {
|
||||
ClobberFFlag_NV = 1<<0, // invalid operation
|
||||
ClobberFFlag_DZ = 1<<1, // divide by zero
|
||||
ClobberFFlag_OF = 1<<2, // overflow
|
||||
ClobberFFlag_UF = 1<<3, // underflow
|
||||
ClobberFFlag_NX = 1<<4, // inexact
|
||||
enum ClobberFlags : u8 {
|
||||
ClobberFlag_NV = 1<<0, // invalid operation
|
||||
ClobberFlag_DZ = 1<<1, // divide by zero
|
||||
ClobberFlag_OF = 1<<2, // overflow
|
||||
ClobberFlag_UF = 1<<3, // underflow
|
||||
ClobberFlag_NX = 1<<4, // inexact
|
||||
};
|
||||
|
||||
enum ClobberRegs : u8 {
|
||||
@@ -174,9 +174,28 @@ main :: proc() {
|
||||
return \"<reg>\";
|
||||
}
|
||||
|
||||
u16 flags_from_name(String const &name) {
|
||||
static const struct { String name; ClobberFlags flag; } table[] = {
|
||||
// flags: accrued FP exception flags (fcsr[4:0])
|
||||
{str_lit(\"nx\"), ClobberFlag_NX}, // Inexact
|
||||
{str_lit(\"uf\"), ClobberFlag_UF}, // Underflow
|
||||
{str_lit(\"of\"), ClobberFlag_OF}, // Overflow
|
||||
{str_lit(\"dz\"), ClobberFlag_DZ}, // Divide by Zero
|
||||
{str_lit(\"nv\"), ClobberFlag_NV}, // Invalid Operation
|
||||
};
|
||||
|
||||
for (auto const &t : table) {
|
||||
if (name == t.name) {
|
||||
return cast(u16)t.flag;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
i32 flag_bit_from_name(String const &name, i32 *width_) {
|
||||
static const struct { String name; i32 bit; } table[] = {
|
||||
// fflags: accrued FP exception flags (fcsr[4:0])
|
||||
// flags: accrued FP exception flags (fcsr[4:0])
|
||||
{str_lit(\"nx\"), 0}, // Inexact
|
||||
{str_lit(\"uf\"), 1}, // Underflow
|
||||
{str_lit(\"of\"), 2}, // Overflow
|
||||
@@ -207,14 +226,14 @@ main :: proc() {
|
||||
OperandSet read; // operand slots whose register/CSR/mem-base is read
|
||||
ClobberRegs implicit_wr; // implicit reg writes (ra on C.JAL/C.JALR)
|
||||
ClobberRegs implicit_rd; // implicit reg reads (sp on the *SP forms)
|
||||
ClobberFFlags fflags_wr; // accrued exception flags this op may raise
|
||||
ClobberFlags flags_wr; // accrued exception flags this op may raise
|
||||
bool reads_frm; // consumes the dynamic rounding mode from fcsr
|
||||
bool writes_mem;
|
||||
bool reads_mem;
|
||||
SideEffectFlags side_effects;
|
||||
|
||||
bool implies_clobber_flags() const {
|
||||
return (fflags_wr != 0);
|
||||
return (flags_wr != 0);
|
||||
}
|
||||
bool implies_clobber_memory() const {
|
||||
return writes_mem || reads_mem ||
|
||||
|
||||
@@ -237,6 +237,27 @@ main :: proc() {
|
||||
return \"<reg>\";
|
||||
}
|
||||
|
||||
u16 flag_from_name(String const &name) {
|
||||
static const struct {String name; ClobberFlags flag; } table[] = {
|
||||
{str_lit(\"c\"), ClobberFlag_CF}, // Carry
|
||||
{str_lit(\"p\"), ClobberFlag_PF}, // Parity
|
||||
{str_lit(\"a\"), ClobberFlag_AF}, // Auxiliary Carry
|
||||
{str_lit(\"z\"), ClobberFlag_ZF}, // Zero
|
||||
{str_lit(\"s\"), ClobberFlag_SF}, // Sign
|
||||
{str_lit(\"t\"), ClobberFlag_TF}, // Trap
|
||||
{str_lit(\"i\"), ClobberFlag_IF}, // Interrupt Enable
|
||||
{str_lit(\"d\"), ClobberFlag_DF}, // Direction
|
||||
{str_lit(\"o\"), ClobberFlag_OF}, // Overflow
|
||||
};
|
||||
|
||||
for (auto const &t : table) {
|
||||
if (name == t.name) {
|
||||
return cast(u16)t.flag;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
i32 flag_bit_from_name(String const &name, i32 *width_) {
|
||||
static const struct {String name; i32 bit; } table[] = {
|
||||
{str_lit(\"c\"), 0}, // Carry
|
||||
|
||||
@@ -240,6 +240,27 @@ struct Asm_amd64 {
|
||||
return "<reg>";
|
||||
}
|
||||
|
||||
u16 flag_from_name(String const &name) {
|
||||
static const struct {String name; ClobberFlags flag; } table[] = {
|
||||
{str_lit("c"), ClobberFlag_CF}, // Carry
|
||||
{str_lit("p"), ClobberFlag_PF}, // Parity
|
||||
{str_lit("a"), ClobberFlag_AF}, // Auxiliary Carry
|
||||
{str_lit("z"), ClobberFlag_ZF}, // Zero
|
||||
{str_lit("s"), ClobberFlag_SF}, // Sign
|
||||
{str_lit("t"), ClobberFlag_TF}, // Trap
|
||||
{str_lit("i"), ClobberFlag_IF}, // Interrupt Enable
|
||||
{str_lit("d"), ClobberFlag_DF}, // Direction
|
||||
{str_lit("o"), ClobberFlag_OF}, // Overflow
|
||||
};
|
||||
|
||||
for (auto const &t : table) {
|
||||
if (name == t.name) {
|
||||
return cast(u16)t.flag;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
i32 flag_bit_from_name(String const &name, i32 *width_) {
|
||||
static const struct {String name; i32 bit; } table[] = {
|
||||
{str_lit("c"), 0}, // Carry
|
||||
|
||||
@@ -68,12 +68,12 @@ struct Asm_riscv {
|
||||
};
|
||||
|
||||
|
||||
enum ClobberFFlags : u8 {
|
||||
ClobberFFlag_NV = 1<<0, // invalid operation
|
||||
ClobberFFlag_DZ = 1<<1, // divide by zero
|
||||
ClobberFFlag_OF = 1<<2, // overflow
|
||||
ClobberFFlag_UF = 1<<3, // underflow
|
||||
ClobberFFlag_NX = 1<<4, // inexact
|
||||
enum ClobberFlags : u8 {
|
||||
ClobberFlag_NV = 1<<0, // invalid operation
|
||||
ClobberFlag_DZ = 1<<1, // divide by zero
|
||||
ClobberFlag_OF = 1<<2, // overflow
|
||||
ClobberFlag_UF = 1<<3, // underflow
|
||||
ClobberFlag_NX = 1<<4, // inexact
|
||||
};
|
||||
|
||||
enum ClobberRegs : u8 {
|
||||
@@ -122,9 +122,28 @@ struct Asm_riscv {
|
||||
return "<reg>";
|
||||
}
|
||||
|
||||
u16 flags_from_name(String const &name) {
|
||||
static const struct { String name; ClobberFlags flag; } table[] = {
|
||||
// flags: accrued FP exception flags (fcsr[4:0])
|
||||
{str_lit("nx"), ClobberFlag_NX}, // Inexact
|
||||
{str_lit("uf"), ClobberFlag_UF}, // Underflow
|
||||
{str_lit("of"), ClobberFlag_OF}, // Overflow
|
||||
{str_lit("dz"), ClobberFlag_DZ}, // Divide by Zero
|
||||
{str_lit("nv"), ClobberFlag_NV}, // Invalid Operation
|
||||
};
|
||||
|
||||
for (auto const &t : table) {
|
||||
if (name == t.name) {
|
||||
return cast(u16)t.flag;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
i32 flag_bit_from_name(String const &name, i32 *width_) {
|
||||
static const struct { String name; i32 bit; } table[] = {
|
||||
// fflags: accrued FP exception flags (fcsr[4:0])
|
||||
// flags: accrued FP exception flags (fcsr[4:0])
|
||||
{str_lit("nx"), 0}, // Inexact
|
||||
{str_lit("uf"), 1}, // Underflow
|
||||
{str_lit("of"), 2}, // Overflow
|
||||
@@ -155,14 +174,14 @@ struct Asm_riscv {
|
||||
OperandSet read; // operand slots whose register/CSR/mem-base is read
|
||||
ClobberRegs implicit_wr; // implicit reg writes (ra on C.JAL/C.JALR)
|
||||
ClobberRegs implicit_rd; // implicit reg reads (sp on the *SP forms)
|
||||
ClobberFFlags fflags_wr; // accrued exception flags this op may raise
|
||||
ClobberFlags flags_wr; // accrued exception flags this op may raise
|
||||
bool reads_frm; // consumes the dynamic rounding mode from fcsr
|
||||
bool writes_mem;
|
||||
bool reads_mem;
|
||||
SideEffectFlags side_effects;
|
||||
|
||||
bool implies_clobber_flags() const {
|
||||
return (fflags_wr != 0);
|
||||
return (flags_wr != 0);
|
||||
}
|
||||
bool implies_clobber_memory() const {
|
||||
return writes_mem || reads_mem ||
|
||||
|
||||
@@ -1630,7 +1630,8 @@ gb_internal void check_mnemonic(AsmCtx *asm_ctx, CheckerContext *ctx, Entity *tm
|
||||
explicit_writes |= synth;
|
||||
}
|
||||
|
||||
facts->gen_regs = produced | pinned_param_writes;
|
||||
facts->gen_regs = produced | pinned_param_writes;
|
||||
facts->gen_flags = cast(u16)clobber.flags_wr;
|
||||
|
||||
// NOTE(bill): mnemonics such as `xor r, r` / `sub r, r` act as zeroing the destination
|
||||
// independent of its prior value: the read is architecturally dead, so it must not count as a use.
|
||||
@@ -2715,7 +2716,7 @@ gb_internal void check_asm_template_from_entity(CheckerContext *c, Entity *e, De
|
||||
if (build_context.metrics.arch == TargetArch_amd64) {
|
||||
check_asm_template(&g_asm_amd64, c, e, d);
|
||||
} else if (build_context.metrics.arch == TargetArch_riscv64) {
|
||||
check_asm_template(&g_asm_riscv, c, e, d);
|
||||
// check_asm_template(&g_asm_riscv, c, e, d);
|
||||
} else {
|
||||
error(e->token, "asm templates are not currently supported for this target");
|
||||
}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
struct AsmBlock {
|
||||
i32 first, last;
|
||||
Array<i32> succs;
|
||||
|
||||
u16 in_defs;
|
||||
u16 out_defs;
|
||||
|
||||
u16 in_flags;
|
||||
u16 out_flags;
|
||||
|
||||
PtrSet<Entity *> in_params;
|
||||
PtrSet<Entity *> out_params;
|
||||
|
||||
bool reachable;
|
||||
};
|
||||
|
||||
@@ -12,6 +18,7 @@ struct AsmInstructionFacts {
|
||||
AstAsmInstruction *node;
|
||||
String name;
|
||||
|
||||
u16 gen_flags; // flag bits this instruction defines
|
||||
u16 gen_regs;
|
||||
u16 read_regs;
|
||||
|
||||
@@ -105,6 +112,15 @@ gb_internal u16 asm_decl_resolve_pin_bit(AsmCtx *asm_ctx, Array<AsmTemplateEntit
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <typename AsmCtx>
|
||||
gb_internal u16 asm_decl_resolve_flag_bit(AsmCtx *asm_ctx, AsmTemplateEntityDecl const &ed) {
|
||||
if (ed.pin_flag.len == 0) {
|
||||
return 0;
|
||||
}
|
||||
auto flags = asm_ctx->flag_from_name(ed.pin_flag);
|
||||
return cast(u16)flags;
|
||||
}
|
||||
|
||||
template <typename AsmCtx>
|
||||
gb_internal void asm_cfg_populate_decls(AsmCtx *asm_ctx, AsmCfg *cfg, Entity *entity) {
|
||||
auto const &decls = entity->AsmTemplate.decls;
|
||||
@@ -305,7 +321,8 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont
|
||||
error(entity->token, "'asm' templates cannot have more than 64 total parameter declarations, got %td", decls.count);
|
||||
return;
|
||||
}
|
||||
u16 const REG_TOP = asm_ctx->CLOBBER_REGS_NAMED;
|
||||
u16 const REG_TOP = asm_ctx->CLOBBER_REGS_NAMED;
|
||||
u16 const FLAG_TOP = cast(u16)~cast(u16)0;
|
||||
|
||||
u64 const universe_pm = cfg->universe_pm;
|
||||
auto bit_of = [&](Entity *e) -> u64 {
|
||||
@@ -338,12 +355,15 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont
|
||||
|
||||
isize const n = cfg->blocks.count;
|
||||
|
||||
auto in_regs = slice_make<u16>(heap_allocator(), n); defer (slice_free(&in_regs, heap_allocator()));
|
||||
auto out_regs = slice_make<u16>(heap_allocator(), n); defer (slice_free(&out_regs, heap_allocator()));
|
||||
auto gen_regs = slice_make<u16>(heap_allocator(), n); defer (slice_free(&gen_regs, heap_allocator()));
|
||||
auto in_pm = slice_make<u64>(heap_allocator(), n); defer (slice_free(&in_pm, heap_allocator()));
|
||||
auto out_pm = slice_make<u64>(heap_allocator(), n); defer (slice_free(&out_pm, heap_allocator()));
|
||||
auto gen_pm = slice_make<u64>(heap_allocator(), n); defer (slice_free(&gen_pm, heap_allocator()));
|
||||
auto in_regs = slice_make<u16>(heap_allocator(), n); defer (slice_free(&in_regs, heap_allocator()));
|
||||
auto out_regs = slice_make<u16>(heap_allocator(), n); defer (slice_free(&out_regs, heap_allocator()));
|
||||
auto gen_regs = slice_make<u16>(heap_allocator(), n); defer (slice_free(&gen_regs, heap_allocator()));
|
||||
auto in_flags = slice_make<u16>(heap_allocator(), n); defer (slice_free(&in_flags, heap_allocator()));
|
||||
auto out_flags = slice_make<u16>(heap_allocator(), n); defer (slice_free(&out_flags, heap_allocator()));
|
||||
auto gen_flags = slice_make<u16>(heap_allocator(), n); defer (slice_free(&gen_flags, heap_allocator()));
|
||||
auto in_pm = slice_make<u64>(heap_allocator(), n); defer (slice_free(&in_pm, heap_allocator()));
|
||||
auto out_pm = slice_make<u64>(heap_allocator(), n); defer (slice_free(&out_pm, heap_allocator()));
|
||||
auto gen_pm = slice_make<u64>(heap_allocator(), n); defer (slice_free(&gen_pm, heap_allocator()));
|
||||
|
||||
// predecessors, restricted to reachable blocks
|
||||
auto preds = slice_make<Array<i32>>(heap_allocator(), n);
|
||||
@@ -371,6 +391,7 @@ 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;
|
||||
AsmBlock const &b = cfg->blocks[bi];
|
||||
for (i32 ii = b.first; ii <= b.last; ii++) {
|
||||
@@ -379,12 +400,14 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont
|
||||
continue;
|
||||
}
|
||||
gr |= f->gen_regs;
|
||||
gf |= f->gen_flags;
|
||||
for (Entity *pe : f->gen_params) {
|
||||
gp |= bit_of(pe);
|
||||
}
|
||||
}
|
||||
gen_regs[bi] = gr;
|
||||
gen_pm[bi] = gp;
|
||||
gen_regs[bi] = gr;
|
||||
gen_flags[bi] = gf;
|
||||
gen_pm[bi] = gp;
|
||||
}
|
||||
|
||||
// NOTE(bill): initialize the blocks
|
||||
@@ -394,14 +417,17 @@ 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];
|
||||
}
|
||||
|
||||
// forward must-analysis: in = AND(preds.out); out = in | gen. Iterate to fixpoint.
|
||||
@@ -414,26 +440,34 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont
|
||||
}
|
||||
|
||||
u16 nin_r = seed_regs;
|
||||
u16 nin_f = 0;
|
||||
u64 nin_p = seed_pm;
|
||||
if (bi != 0) {
|
||||
nin_r = REG_TOP;
|
||||
nin_f = FLAG_TOP;
|
||||
nin_p = universe_pm;
|
||||
for (i32 p : preds[bi]) {
|
||||
nin_r &= out_regs[p];
|
||||
nin_p &= out_pm[p];
|
||||
nin_r &= out_regs[p];
|
||||
nin_r &= out_flags[p];
|
||||
nin_p &= out_pm[p];
|
||||
}
|
||||
}
|
||||
u16 nout_r = nin_r | gen_regs[bi];
|
||||
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]) {
|
||||
in_regs[bi] = nin_r;
|
||||
in_pm[bi] = nin_p;
|
||||
out_regs[bi] = nout_r;
|
||||
out_pm[bi] = nout_p;
|
||||
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;
|
||||
out_flags[bi] = nout_f;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -442,8 +476,10 @@ 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_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;
|
||||
}
|
||||
@@ -529,8 +565,9 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont
|
||||
}
|
||||
|
||||
{ // NOTE(bill): Collect the template's return points reachable blocks that leave via the end
|
||||
u16 exit_regs = REG_TOP;
|
||||
u64 exit_pm = universe_pm;
|
||||
u16 exit_regs = REG_TOP;
|
||||
u64 exit_pm = universe_pm;
|
||||
u16 exit_flags = FLAG_TOP;
|
||||
bool any_exit = false;
|
||||
for_array(bi, cfg->blocks) {
|
||||
if (!cfg->blocks[bi].reachable) {
|
||||
@@ -540,8 +577,9 @@ 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];
|
||||
}
|
||||
|
||||
// NOTE(bill): Outputs must be assigned on every path that returns
|
||||
@@ -554,17 +592,18 @@ gb_internal void check_asm_cfg_analyse(AsmCtx *asm_ctx, AsmCfg *cfg, CheckerCont
|
||||
if (ed.tie >= 0 || ed.no_init) {
|
||||
continue;
|
||||
}
|
||||
if (ed.pin_flag.len != 0) {
|
||||
continue; // flag-pinned output: defined by a flags side effect, not a reg/param write
|
||||
}
|
||||
|
||||
bool written = false;
|
||||
u16 bit = cfg->decl_pin_bit[i];
|
||||
if (bit != 0) {
|
||||
written = (exit_regs & bit) != 0;
|
||||
u16 flag_bit = asm_decl_resolve_flag_bit(asm_ctx, ed);
|
||||
u16 reg_bit = cfg->decl_pin_bit[i];
|
||||
if (flag_bit != 0) {
|
||||
// Flag-pinned output: defined iff the pinned flag is set on every returning path.
|
||||
written = (exit_flags & flag_bit) != 0;
|
||||
} else if (reg_bit != 0) {
|
||||
written = (exit_regs & reg_bit) != 0;
|
||||
} else {
|
||||
written = (exit_pm & bit_of(ed.entity)) != 0;
|
||||
}
|
||||
}
|
||||
if (!written) {
|
||||
error(ed.entity->token,
|
||||
"'asm' output parameter '%.*s' is not assigned on all paths through this template; "
|
||||
|
||||
Reference in New Issue
Block a user