///|
pub struct IrTestHotFixture {
mod_ : @lib.Module
code_idx : Int32
hot_func : HotFunc
}
///|
fn ir_test_module_from_wat(text : String) -> @lib.Module {
let mod_ = match @wast.wast_to_binary_module(text, filename="ir-test.wat") {
Ok(mod_) => mod_
Err(err) => abort("unexpected IR test fixture parse failure: \{err}")
}
match @validate.validate_module(mod_) {
Ok(_) => mod_
Err(err) => abort("unexpected invalid IR test fixture: \{err}")
}
}
///|
fn ir_test_imported_func_count(mod_ : @lib.Module) -> Int32 {
let mut count = 0
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
match import_ {
@lib.Import(_, _, @lib.FuncExternType(_)) => count += 1
_ => ()
}
}
None => ()
}
count
}
///|
pub fn ir_test_build_hot_from_wat(
text : String,
code_idx~ : Int32 = 0,
) -> IrTestHotFixture {
let mod_ = ir_test_module_from_wat(text)
match mod_.code_sec {
Some(@lib.CodeSec(defs)) => {
if code_idx < 0 || code_idx >= defs.length() {
abort("IR test hot fixture requires a valid defined-function index")
}
let hot_func = hot_lift_func(
mod_,
@lib.FuncIdx::new(
(ir_test_imported_func_count(mod_) + code_idx).reinterpret_as_uint(),
),
)
match hot_verify_core(hot_func) {
Ok(_) => ()
Err(err) =>
abort("unexpected lifted IR test fixture core failure: \{err}")
}
match hot_verify_control(hot_func) {
Ok(_) => ()
Err(err) =>
abort("unexpected lifted IR test fixture control failure: \{err}")
}
{ mod_, code_idx, hot_func, }
}
None => abort("IR test hot fixture requires a module with a code section")
}
}
///|
pub fn ir_test_assert_verify_and_lower(
fixture : IrTestHotFixture,
) -> @lib.Module {
match hot_verify_core(fixture.hot_func) {
Ok(_) => ()
Err(err) => abort("expected hot fixture to pass core verification: \{err}")
}
match hot_verify_control(fixture.hot_func) {
Ok(_) => ()
Err(err) =>
abort("expected hot fixture to pass control verification: \{err}")
}
let lowered = hot_lower_func(fixture.hot_func)
let next_code_sec = match fixture.mod_.code_sec {
Some(@lib.CodeSec(defs)) => {
if fixture.code_idx < 0 || fixture.code_idx >= defs.length() {
abort("IR test lower helper requires a valid defined-function index")
}
let next_defs = defs.copy()
next_defs[fixture.code_idx] = lowered
@lib.CodeSec::new(next_defs)
}
None => abort("IR test lower helper requires a code section")
}
let out = fixture.mod_.with_code_sec(next_code_sec)
match @validate.validate_module(out) {
Ok(_) => out
Err(err) => abort("expected lowered IR test fixture to validate: \{err}")
}
}
///|
fn ir_test_join_lines(lines : Array[String]) -> String {
let out = StringBuilder::new()
for i = 0; i < lines.length(); i = i + 1 {
if i > 0 {
out.write_string("\n")
}
out.write_string(lines[i])
}
out.to_string()
}
///|
fn ir_test_join_csv(parts : Array[String]) -> String {
let out = StringBuilder::new()
for i = 0; i < parts.length(); i = i + 1 {
if i > 0 {
out.write_string(", ")
}
out.write_string(parts[i])
}
out.to_string()
}
///|
fn ir_test_edge_kind_name(kind : CfgEdgeKind) -> String {
match kind {
CfgEdgeKind::FallthroughEdge => "fallthrough"
CfgEdgeKind::BranchEdge => "branch"
CfgEdgeKind::ReturnEdge => "return"
CfgEdgeKind::ExceptionalEdge => "exceptional"
CfgEdgeKind::UnreachableExitEdge => "unreachable"
}
}
///|
fn ir_test_dump_edges(edges : Array[CfgEdge]) -> String {
let parts : Array[String] = []
for edge in edges {
parts.push("\{ir_test_edge_kind_name(edge.kind)}:\{edge.block}")
}
let body = ir_test_join_csv(parts)
"[\{body}]"
}
///|
fn ir_test_dump_bitset(bitset : Bitset) -> String {
let parts : Array[String] = []
bitset_for_each_set_bit(bitset, fn(bit) { parts.push(bit.to_string()) })
let body = ir_test_join_csv(parts)
"[\{body}]"
}
///|
fn ir_test_dump_value_origin(origin : HotSsaValueOrigin) -> String {
match origin {
HotSsaValueOrigin::EntryParamDef(local_id) => "param(\{local_id})"
HotSsaValueOrigin::EntryDefaultInitDef(local_id) => "init(\{local_id})"
HotSsaValueOrigin::LocalSetDef(node_id) => "set(\{node_id})"
HotSsaValueOrigin::LocalTeeDef(node_id) => "tee(\{node_id})"
HotSsaValueOrigin::PhiDef(phi_id) => "phi(\{phi_id})"
}
}
///|
fn ir_test_dump_use_origin(origin : HotSsaUseOrigin) -> String {
match origin {
HotSsaUseOrigin::LocalGetUse(node_id) => "get(\{node_id})"
HotSsaUseOrigin::PhiInputUse(phi_id, predecessor) =>
"phi(\{phi_id}<-\{predecessor})"
}
}
///|
pub fn ir_test_dump_cfg(func : HotFunc) -> String {
let cfg = cfg_build(func)
let exceptional = match cfg_exceptional_exit_block(cfg) {
Some(block_id) => block_id.to_string()
None => "-"
}
let lines = [
"entry=\{cfg_entry_block(cfg)} exit=\{cfg_exit_block(cfg)} exceptional=\{exceptional}",
]
for block_id = 0; block_id < cfg_block_count(cfg); block_id = block_id + 1 {
let block = cfg_block_get(cfg, block_id)
lines.push(
"block=\{block_id} synthetic=\{block.is_synthetic} holder=\{block.region_holder} span=\{block.start_slot}..\{block.end_slot} nodes=\{block.nodes} preds=\{ir_test_dump_edges(block.preds)} succs=\{ir_test_dump_edges(block.succs)}",
)
}
ir_test_join_lines(lines)
}
///|
pub fn ir_test_dump_dom(func : HotFunc) -> String {
dom_debug_dump(dom_build(cfg_build(func)))
}
///|
pub fn ir_test_dump_liveness(func : HotFunc) -> String {
let cfg = cfg_build(func)
let use_def = use_def_build(func, cfg=Some(cfg))
let liveness = liveness_build(cfg, use_def)
let lines = [
"include_exceptional_edges=\{liveness.include_exceptional_edges}",
]
for block_id = 0; block_id < cfg_block_count(cfg); block_id = block_id + 1 {
lines.push(
"block=\{block_id} in=\{ir_test_dump_bitset(live_in(liveness, block_id))} out=\{ir_test_dump_bitset(live_out(liveness, block_id))}",
)
}
ir_test_join_lines(lines)
}
///|
pub fn ir_test_dump_ssa(func : HotFunc) -> String {
let cfg = cfg_build(func)
let dom = dom_build(cfg)
let use_def = use_def_build(func, cfg=Some(cfg))
let liveness = liveness_build(cfg, use_def)
let ssa = ssa_build_local(func, cfg, dom, liveness, use_def)
let lines = ["entry_defs=\{ssa.entry_defs}"]
for value_id = 0; value_id < ssa.value_types.length(); value_id = value_id + 1 {
let uses = ssa_uses_of_value(ssa, value_id)
let use_parts : Array[String] = []
for use_origin in uses {
use_parts.push(ir_test_dump_use_origin(use_origin))
}
let use_body = ir_test_join_csv(use_parts)
lines.push(
"value=\{value_id} origin=\{ir_test_dump_value_origin(ssa_value_origin(ssa, value_id))} uses=[\{use_body}]",
)
}
for block_id = 0; block_id < cfg_block_count(cfg); block_id = block_id + 1 {
let phis = ssa_phis_for_block(ssa, block_id)
lines.push("block=\{block_id} phis=\{phis}")
for phi_id in phis {
let inputs = ssa_phi_inputs(ssa, phi_id)
let input_parts : Array[String] = []
for input in inputs {
input_parts.push("\{input.predecessor}->\{input.value_id}")
}
let input_body = ir_test_join_csv(input_parts)
lines.push(
"phi=\{phi_id} block=\{ssa_phi_block(ssa, phi_id)} local=\{ssa_phi_local(ssa, phi_id)} inputs=[\{input_body}]",
)
}
}
for node_id = 0; node_id < hot_node_count(func); node_id = node_id + 1 {
if !hot_node_is_live(func, node_id) {
continue
}
match hot_node_get(func, node_id).op {
HotOp::LocalGet =>
lines.push(
"node=\{node_id} get=\{ssa_value_for_local_get(ssa, node_id)}",
)
HotOp::LocalSet | HotOp::LocalTee =>
lines.push("node=\{node_id} def=\{ssa_def_for_local_set(ssa, node_id)}")
_ => ()
}
}
ir_test_join_lines(lines)
}