// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Potentially incorporated Binaryen expression retains Apache-2.0 terms.
// Original Starshine contributions use the scoped MIT grant in ../../LICENSE.
// Modified for Starshine through 2026: implementation and validation use
// Starshine's representations and APIs; this is not an unchanged upstream file.
// Conservative notice retention does not establish copying or independence.
///|
/// Optimizer-local policy. This is deliberately not serialized in Wasm.
pub enum CompilerFactUsePolicy {
IgnoreFacts
HintsOnly
TrustAssertions
} derive(Eq, Debug)
///|
pub fn CompilerFactUsePolicy::ignore() -> CompilerFactUsePolicy {
IgnoreFacts
}
///|
pub fn CompilerFactUsePolicy::hints_only() -> CompilerFactUsePolicy {
HintsOnly
}
///|
pub fn CompilerFactUsePolicy::trust_assertions() -> CompilerFactUsePolicy {
TrustAssertions
}
///|
pub struct CompilerFactMaterializationStats {
mut fact_sections_seen : Int32
mut function_bodies_with_facts : Int32
mut function_facts_seen : Int32
mut signature_facts_seen : Int32
mut type_facts_seen : Int32
mut global_facts_seen : Int32
mut table_facts_seen : Int32
mut value_facts_seen : Int32
mut relation_facts_seen : Int32
mut call_facts_seen : Int32
mut branch_facts_seen : Int32
mut loop_facts_seen : Int32
mut allocation_facts_seen : Int32
mut access_facts_seen : Int32
mut numeric_operation_facts_seen : Int32
mut hint_facts_seen : Int32
mut trusted_assertion_facts : Int32
mut hint_only_facts : Int32
mut candidate_rewrites : Int32
mut exact_call_target_facts_seen : Int32
mut exact_call_target_facts_accepted : Int32
mut call_ref_converted : Int32
mut return_call_ref_converted : Int32
mut integer_comparisons_folded : Int32
mut integer_zero_checks_folded : Int32
mut reference_null_checks_folded : Int32
mut structurally_incompatible_rejected : Int32
mut stale_or_unresolved_sites : Int32
mut type_mismatches : Int32
mut invalid_referenced_indices : Int32
mut unsupported_versions : Int32
mut malformed_or_impossible_facts : Int32
mut facts_blocked_by_trust : Int32
mut valid_facts_without_rewrite : Int32
mut ignored_untrusted_assertions : Int32
} derive(Eq, Debug)
///|
pub fn CompilerFactMaterializationStats::new() -> CompilerFactMaterializationStats {
{
fact_sections_seen: 0,
function_bodies_with_facts: 0,
function_facts_seen: 0,
signature_facts_seen: 0,
type_facts_seen: 0,
global_facts_seen: 0,
table_facts_seen: 0,
value_facts_seen: 0,
relation_facts_seen: 0,
call_facts_seen: 0,
branch_facts_seen: 0,
loop_facts_seen: 0,
allocation_facts_seen: 0,
access_facts_seen: 0,
numeric_operation_facts_seen: 0,
hint_facts_seen: 0,
trusted_assertion_facts: 0,
hint_only_facts: 0,
candidate_rewrites: 0,
exact_call_target_facts_seen: 0,
exact_call_target_facts_accepted: 0,
call_ref_converted: 0,
return_call_ref_converted: 0,
integer_comparisons_folded: 0,
integer_zero_checks_folded: 0,
reference_null_checks_folded: 0,
structurally_incompatible_rejected: 0,
stale_or_unresolved_sites: 0,
type_mismatches: 0,
invalid_referenced_indices: 0,
unsupported_versions: 0,
malformed_or_impossible_facts: 0,
facts_blocked_by_trust: 0,
valid_facts_without_rewrite: 0,
ignored_untrusted_assertions: 0,
}
}
///|
pub struct ApplyCompilerFactsResult {
module_ : @lib.Module
stats : CompilerFactMaterializationStats
} derive(Debug)
///|
fn compiler_fact_stats_summary(
stats : CompilerFactMaterializationStats,
) -> String {
"sections=\{stats.fact_sections_seen} bodies=\{stats.function_bodies_with_facts} categories.function=\{stats.function_facts_seen} categories.signature=\{stats.signature_facts_seen} categories.type=\{stats.type_facts_seen} categories.global=\{stats.global_facts_seen} categories.table=\{stats.table_facts_seen} categories.value=\{stats.value_facts_seen} categories.relation=\{stats.relation_facts_seen} categories.call=\{stats.call_facts_seen} categories.branch=\{stats.branch_facts_seen} categories.loop=\{stats.loop_facts_seen} categories.allocation=\{stats.allocation_facts_seen} categories.access=\{stats.access_facts_seen} categories.numeric=\{stats.numeric_operation_facts_seen} hints=\{stats.hint_facts_seen} trusted=\{stats.trusted_assertion_facts} hint_only=\{stats.hint_only_facts} candidates=\{stats.candidate_rewrites} applied.call_ref=\{stats.call_ref_converted} applied.return_call_ref=\{stats.return_call_ref_converted} applied.integer_compare=\{stats.integer_comparisons_folded} applied.integer_eqz=\{stats.integer_zero_checks_folded} applied.ref_is_null=\{stats.reference_null_checks_folded} rejected.stale=\{stats.stale_or_unresolved_sites} rejected.type=\{stats.type_mismatches} rejected.index=\{stats.invalid_referenced_indices} rejected.version=\{stats.unsupported_versions} rejected.malformed=\{stats.malformed_or_impossible_facts} rejected.structure=\{stats.structurally_incompatible_rejected} blocked.trust=\{stats.facts_blocked_by_trust} no_improvement=\{stats.valid_facts_without_rewrite}"
}
///|
/// Read-only, nonserialized index built once per optimization invocation.
pub struct CompilerFactIndex {
facts : @representation.OptimizationFactsSec
functions : @hashmap.HashMap[UInt32, @representation.FunctionFacts]
bodies : @hashmap.HashMap[UInt32, @representation.FunctionBodyFacts]
values : @hashmap.HashMap[
@representation.ValueSite,
@representation.ValueFacts,
]
calls : @hashmap.HashMap[
@representation.CodeSite,
@representation.CallSiteFacts,
]
signatures : @hashmap.HashMap[UInt32, @representation.SignatureFacts]
types : @hashmap.HashMap[UInt32, @representation.TypeFacts]
globals : @hashmap.HashMap[UInt32, @representation.GlobalFacts]
tables : @hashmap.HashMap[UInt32, @representation.TableFacts]
} derive(Debug)
///|
fn compiler_fact_index_value_map(
facts : @representation.OptimizationFactsSec,
bodies : @hashmap.HashMap[UInt32, @representation.FunctionBodyFacts],
) -> @hashmap.HashMap[@representation.ValueSite, @representation.ValueFacts] {
let values : @hashmap.HashMap[
@representation.ValueSite,
@representation.ValueFacts,
] = @hashmap.new()
for body in facts.bodies {
if bodies.get(body.function) != Some(body) {
continue
}
for entry in body.sites {
if entry.value.site.function == body.function &&
values.get(entry.value) is None {
values.set(entry.value, entry.facts)
}
}
}
values
}
///|
fn compiler_fact_index_call_map(
facts : @representation.OptimizationFactsSec,
bodies : @hashmap.HashMap[UInt32, @representation.FunctionBodyFacts],
) -> @hashmap.HashMap[@representation.CodeSite, @representation.CallSiteFacts] {
let calls : @hashmap.HashMap[
@representation.CodeSite,
@representation.CallSiteFacts,
] = @hashmap.new()
for body in facts.bodies {
if bodies.get(body.function) != Some(body) {
continue
}
for entry in body.calls {
if entry.site.function == body.function && calls.get(entry.site) is None {
calls.set(entry.site, entry)
}
}
}
calls
}
///|
pub fn CompilerFactIndex::new(
facts : @representation.OptimizationFactsSec,
) -> CompilerFactIndex {
let functions : @hashmap.HashMap[UInt32, @representation.FunctionFacts] = @hashmap.new()
for value in facts.functions {
functions.set(value.function, value)
}
let bodies : @hashmap.HashMap[UInt32, @representation.FunctionBodyFacts] = @hashmap.new()
for value in facts.bodies {
bodies.set(value.function, value)
}
let signatures : @hashmap.HashMap[UInt32, @representation.SignatureFacts] = @hashmap.new()
for value in facts.signatures {
signatures.set(value.signature, value)
}
let types : @hashmap.HashMap[UInt32, @representation.TypeFacts] = @hashmap.new()
for value in facts.types {
types.set(value.type_, value)
}
let globals : @hashmap.HashMap[UInt32, @representation.GlobalFacts] = @hashmap.new()
for value in facts.globals {
globals.set(value.global, value)
}
let tables : @hashmap.HashMap[UInt32, @representation.TableFacts] = @hashmap.new()
for value in facts.tables {
tables.set(value.table, value)
}
{
facts,
functions,
values: compiler_fact_index_value_map(facts, bodies),
calls: compiler_fact_index_call_map(facts, bodies),
bodies,
signatures,
types,
globals,
tables,
}
}
///|
fn compiler_fact_index_bounded(
facts : @representation.OptimizationFactsSec,
env : @validate.Env,
) -> CompilerFactIndex {
let functions : @hashmap.HashMap[UInt32, @representation.FunctionFacts] = @hashmap.new()
let bodies : @hashmap.HashMap[UInt32, @representation.FunctionBodyFacts] = @hashmap.new()
for value in facts.functions {
let slot = value.function.reinterpret_as_int()
if slot >= 0 && slot < env.funcs.length() {
functions.set(value.function, value)
}
}
for value in facts.bodies {
let slot = value.function.reinterpret_as_int()
if slot >= 0 && slot < env.funcs.length() {
bodies.set(value.function, value)
}
}
let signatures : @hashmap.HashMap[UInt32, @representation.SignatureFacts] = @hashmap.new()
for value in facts.signatures {
let slot = value.signature.reinterpret_as_int()
if slot >= 0 && slot < env.global_types.length() {
signatures.set(value.signature, value)
}
}
let types : @hashmap.HashMap[UInt32, @representation.TypeFacts] = @hashmap.new()
for value in facts.types {
let slot = value.type_.reinterpret_as_int()
if slot >= 0 && slot < env.global_types.length() {
types.set(value.type_, value)
}
}
let globals : @hashmap.HashMap[UInt32, @representation.GlobalFacts] = @hashmap.new()
for value in facts.globals {
let slot = value.global.reinterpret_as_int()
if slot >= 0 && slot < env.globals.length() {
globals.set(value.global, value)
}
}
let tables : @hashmap.HashMap[UInt32, @representation.TableFacts] = @hashmap.new()
for value in facts.tables {
let slot = value.table.reinterpret_as_int()
if slot >= 0 && slot < env.tables.length() {
tables.set(value.table, value)
}
}
{
facts,
functions,
values: compiler_fact_index_value_map(facts, bodies),
calls: compiler_fact_index_call_map(facts, bodies),
bodies,
signatures,
types,
globals,
tables,
}
}
///|
pub fn CompilerFactIndex::function_facts(
self : CompilerFactIndex,
function : @lib.FuncIdx,
) -> @representation.FunctionFacts? {
let @lib.FuncIdx(raw) = function
self.functions.get(raw)
}
///|
pub fn CompilerFactIndex::body_facts(
self : CompilerFactIndex,
function : @lib.FuncIdx,
) -> @representation.FunctionBodyFacts? {
let @lib.FuncIdx(raw) = function
self.bodies.get(raw)
}
///|
pub fn CompilerFactIndex::signature_facts(
self : CompilerFactIndex,
signature : @lib.TypeIdx,
) -> @representation.SignatureFacts? {
match signature {
@lib.TypeIdx(raw) => self.signatures.get(raw)
@lib.RecIdx(_) => None
}
}
///|
pub fn CompilerFactIndex::type_facts(
self : CompilerFactIndex,
type_ : @lib.TypeIdx,
) -> @representation.TypeFacts? {
match type_ {
@lib.TypeIdx(raw) => self.types.get(raw)
@lib.RecIdx(_) => None
}
}
///|
pub fn CompilerFactIndex::global_facts(
self : CompilerFactIndex,
global : @lib.GlobalIdx,
) -> @representation.GlobalFacts? {
let @lib.GlobalIdx(raw) = global
self.globals.get(raw)
}
///|
pub fn CompilerFactIndex::table_facts(
self : CompilerFactIndex,
table : @lib.TableIdx,
) -> @representation.TableFacts? {
let @lib.TableIdx(raw) = table
self.tables.get(raw)
}
///|
pub fn CompilerFactIndex::value_facts(
self : CompilerFactIndex,
site : @representation.ValueSite,
) -> @representation.ValueFacts? {
self.values.get(site)
}
///|
pub fn CompilerFactIndex::call_facts(
self : CompilerFactIndex,
site : @representation.CodeSite,
) -> @representation.CallSiteFacts? {
self.calls.get(site)
}
///|
pub fn CompilerFactIndex::allocation_facts(
self : CompilerFactIndex,
site : @representation.ValueSite,
) -> @representation.AllocationFacts? {
match self.bodies.get(site.site.function) {
Some(body) =>
for value in body.allocations {
if value.site == site {
return Some(value.facts)
}
}
_ => ()
}
None
}
///|
pub fn CompilerFactIndex::hints_for_function(
self : CompilerFactIndex,
function : @lib.FuncIdx,
) -> Array[@representation.OptimizationHint] {
let @lib.FuncIdx(raw) = function
let out : Array[@representation.OptimizationHint] = []
for hint in self.facts.hints {
match hint {
@representation.HotnessHint(@representation.FunctionHintTarget(index), _)
| @representation.InlineHint(@representation.FunctionHintTarget(index), _)
| @representation.GoalHint(@representation.FunctionHintTarget(index), _) if index ==
raw => out.push(hint)
_ => ()
}
}
out
}
///|
pub fn CompilerFactIndex::hints_for_site(
self : CompilerFactIndex,
site : @representation.CodeSite,
) -> Array[@representation.OptimizationHint] {
let out : Array[@representation.OptimizationHint] = []
for hint in self.facts.hints {
let matches = match hint {
@representation.HotnessHint(@representation.SiteHintTarget(value), _)
| @representation.InlineHint(@representation.SiteHintTarget(value), _)
| @representation.GoalHint(@representation.SiteHintTarget(value), _)
| @representation.BranchProbabilityHint(value, _)
| @representation.EstimatedLoopTripsHint(value, _)
| @representation.LikelyCallTargetHint(value, _, _) => value == site
@representation.LikelyHeapTypeHint(value, _, _) => value.site == site
_ => false
}
if matches {
out.push(hint)
}
}
out
}
///|
fn compiler_fact_exact_target_from_value_facts(
facts : @representation.ValueFacts,
) -> UInt32? {
let from_values = match facts.possible_values {
Some(
@representation.ClosedValueSet([@representation.RefFuncValue(function)])
) => Some(function)
_ => None
}
let from_targets = match facts.reference {
Some(reference) =>
match (reference.nullability, reference.function_targets) {
(
Some(@representation.NonNull),
Some(@representation.ClosedFunctionSet([function])),
) => Some(function)
_ => None
}
None => None
}
match (from_values, from_targets) {
(Some(left), Some(right)) if left == right => Some(left)
(Some(_), Some(_)) => None
(Some(value), None) | (None, Some(value)) => Some(value)
_ => None
}
}
///|
pub fn CompilerFactIndex::compiler_fact_exact_call_target(
self : CompilerFactIndex,
site : @representation.CodeSite,
target_value_site~ : @representation.ValueSite? = None,
) -> UInt32? {
let call_target = match self.call_facts(site) {
Some(facts) => facts.direct_target
None => None
}
let value_target = match target_value_site {
Some(value_site) =>
match self.value_facts(value_site) {
Some(facts) => compiler_fact_exact_target_from_value_facts(facts)
None => None
}
None => None
}
match (call_target, value_target) {
(Some(left), Some(right)) if left == right => Some(left)
(Some(_), Some(_)) => None
(Some(value), None) | (None, Some(value)) => Some(value)
_ => None
}
}
///|
fn compiler_fact_count_exact_targets(
facts : @representation.OptimizationFactsSec,
) -> Int32 {
let mut count = 0
for body in facts.bodies {
for call in body.calls {
if call.direct_target is Some(_) {
count += 1
}
}
for site in body.sites {
if compiler_fact_exact_target_from_value_facts(site.facts) is Some(_) {
count += 1
}
}
}
count
}
///|
fn compiler_fact_materializer_result_arity(
instruction : @lib.Instruction,
env : @validate.Env,
) -> UInt32? {
match instruction {
@lib.Call(function) =>
match env.get_functype_by_funcidx(function) {
Some(@lib.FuncType(_, results)) =>
Some(results.length().reinterpret_as_uint())
None => None
}
@lib.CallIndirect(type_, _) | @lib.CallRef(type_) =>
match env.resolve_functype(type_) {
Some(@lib.FuncType(_, results)) =>
Some(results.length().reinterpret_as_uint())
None => None
}
@lib.Block(type_, _)
| @lib.Loop(type_, _)
| @lib.If(type_, _, _)
| @lib.Try(type_, _, _, _)
| @lib.TryTable(type_, _, _) =>
match env.expand_blocktype(type_) {
Ok((_, results)) => Some(results.length().reinterpret_as_uint())
Err(_) => None
}
@lib.Unreachable
| @lib.Nop
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Rethrow(_)
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.LocalSet(_)
| @lib.GlobalSet(_)
| @lib.TableSet(_)
| @lib.I32Store(_)
| @lib.I64Store(_)
| @lib.F32Store(_)
| @lib.F64Store(_)
| @lib.I32Store8(_)
| @lib.I32Store16(_)
| @lib.I64Store8(_)
| @lib.I64Store16(_)
| @lib.I64Store32(_)
| @lib.AtomicFence(_)
| @lib.I32AtomicStore(_, _)
| @lib.I64AtomicStore(_, _)
| @lib.I32AtomicStore8(_, _)
| @lib.I32AtomicStore16(_, _)
| @lib.I64AtomicStore8(_, _)
| @lib.I64AtomicStore16(_, _)
| @lib.I64AtomicStore32(_, _)
| @lib.StructSet(_, _)
| @lib.ArraySet(_)
| @lib.ArrayStore(_, _, _)
| @lib.ArrayFill(_)
| @lib.ArrayCopy(_, _)
| @lib.ArrayInitData(_, _)
| @lib.ArrayInitElem(_, _)
| @lib.MemoryInit(_, _)
| @lib.DataDrop(_)
| @lib.MemoryCopy(_, _)
| @lib.MemoryFill(_)
| @lib.TableInit(_, _)
| @lib.ElemDrop(_)
| @lib.TableCopy(_, _)
| @lib.TableFill(_)
| @lib.V128Store(_)
| @lib.V128Store8Lane(_, _)
| @lib.V128Store16Lane(_, _)
| @lib.V128Store32Lane(_, _)
| @lib.V128Store64Lane(_, _)
| @lib.Drop => Some(0U)
_ => Some(1U)
}
}
///|
priv struct CompilerFactRewriteContext {
function : UInt32
sites : Array[(UInt32, @lib.Instruction)]
cursor : Ref[Int32]
query : CompilerFactQuery
stats : CompilerFactMaterializationStats
}
///|
fn compiler_fact_rewrite_nested_instruction(
instr : @lib.Instruction,
ctx : CompilerFactRewriteContext,
) -> @lib.Instruction {
match instr {
@lib.Block(type_, @lib.Expr(body)) =>
@lib.Instruction::block(
type_,
@lib.Expr::new(compiler_fact_rewrite_region(body, ctx)),
)
@lib.Loop(type_, @lib.Expr(body)) =>
@lib.Instruction::loop_(
type_,
@lib.Expr::new(compiler_fact_rewrite_region(body, ctx)),
)
@lib.If(type_, then_body, else_body) =>
@lib.Instruction::if_(
type_,
compiler_fact_rewrite_region(then_body, ctx),
match else_body {
Some(body) => Some(compiler_fact_rewrite_region(body, ctx))
None => None
},
)
@lib.Try(type_, @lib.Expr(body), catches, delegate) => {
// Site ordinals follow binary order: the protected body precedes catches.
let next_body = compiler_fact_rewrite_region(body, ctx)
let next_catches : Array[@lib.LegacyCatch] = []
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(tag, @lib.Expr(catch_body)) =>
next_catches.push(
@lib.LegacyCatch::tagged(
tag,
@lib.Expr::new(compiler_fact_rewrite_region(catch_body, ctx)),
),
)
@lib.LegacyCatchAll(@lib.Expr(catch_body)) =>
next_catches.push(
@lib.LegacyCatch::all(
@lib.Expr::new(compiler_fact_rewrite_region(catch_body, ctx)),
),
)
}
}
@lib.Instruction::try_(
type_,
@lib.Expr::new(next_body),
next_catches,
delegate~,
)
}
@lib.TryTable(type_, catches, @lib.Expr(body)) =>
@lib.Instruction::try_table(
type_,
catches,
@lib.Expr::new(compiler_fact_rewrite_region(body, ctx)),
)
_ => instr
}
}
///|
fn compiler_fact_compare_expected_type(
instruction : @lib.Instruction,
) -> @lib.ValType? {
match instruction {
@lib.I32Eq
| @lib.I32Ne
| @lib.I32LtS
| @lib.I32LtU
| @lib.I32GtS
| @lib.I32GtU
| @lib.I32LeS
| @lib.I32LeU
| @lib.I32GeS
| @lib.I32GeU => Some(@lib.ValType::i32())
@lib.I64Eq
| @lib.I64Ne
| @lib.I64LtS
| @lib.I64LtU
| @lib.I64GtS
| @lib.I64GtU
| @lib.I64LeS
| @lib.I64LeU
| @lib.I64GeS
| @lib.I64GeU => Some(@lib.ValType::i64())
_ => None
}
}
///|
fn compiler_fact_truth_instruction(
truth : CompilerFactTruth,
) -> @lib.Instruction? {
match truth {
FactProvenTrue => Some(@lib.Instruction::i32_const(@lib.I32(1)))
FactProvenFalse => Some(@lib.Instruction::i32_const(@lib.I32(0)))
FactUnknown => None
}
}
///|
// Track a known suffix of the operand stack. Unknown stack effects discard the
// suffix, rather than guessing from instruction adjacency. Nested regions start
// with no known producers; their parameters are deliberately not inferred.
fn compiler_fact_operand_pop_count(
instruction : @lib.Instruction,
env : @validate.Env,
) -> Int32? {
match instruction {
@lib.Call(function) =>
match env.get_functype_by_funcidx(function) {
Some(@lib.FuncType(params, _)) => Some(params.length())
None => None
}
@lib.CallIndirect(type_, _) | @lib.CallRef(type_) =>
match env.resolve_functype(type_) {
Some(@lib.FuncType(params, _)) => Some(params.length() + 1)
None => None
}
@lib.Block(type_, _)
| @lib.Loop(type_, _)
| @lib.If(type_, _, _)
| @lib.Try(type_, _, _, _)
| @lib.TryTable(type_, _, _) =>
match env.expand_blocktype(type_) {
Ok((params, _)) => {
let condition = if instruction is @lib.If(_, _, _) { 1 } else { 0 }
Some(params.length() + condition)
}
Err(_) => None
}
_ => directize_fast_pop_count(instruction)
}
}
///|
fn compiler_fact_integer_operand_domain(
ctx : CompilerFactRewriteContext,
root : (@representation.ValueSite, @lib.Instruction),
expected : @lib.ValType,
) -> (CompilerFactIntegerDomain?, Bool) {
match compiler_fact_integer_domain_from_const(root.1) {
Some(domain) => (Some(domain), false)
None => {
let domain = ctx.query.integer_domain(
ctx.function,
root.0,
root.1,
expected,
)
(domain, domain is Some(_))
}
}
}
///|
fn compiler_fact_push_folded_predicate(
out : Array[@lib.Instruction],
drops : Int32,
truth : CompilerFactTruth,
) -> Bool {
let constant = match compiler_fact_truth_instruction(truth) {
Some(constant) => constant
None => return false
}
for _ in 0.. Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
let values : Array[(@representation.ValueSite, @lib.Instruction)] = []
for original in instrs {
let ordinal = ctx.cursor.val
if ordinal < 0 || ordinal >= ctx.sites.length() {
out.push(original)
values.clear()
continue
}
let offset = ctx.sites[ordinal].0
ctx.cursor.val += 1
let site = @representation.CodeSite::new(ctx.function, offset)
let current = compiler_fact_rewrite_nested_instruction(original, ctx)
let right_operand = values.last()
let left_operand = values.get(values.length() - 2)
let mut rewritten = false
match current {
@lib.CallRef(_) | @lib.ReturnCallRef(_) => {
let target_value = right_operand
let target = ctx.query.exact_call_target(
ctx.function,
site,
current,
target=target_value,
)
match target {
Some(raw_target) => {
ctx.stats.candidate_rewrites += 1
let replacement = directize_exact_ref_call_replacement(
ctx.query.env,
current,
@lib.FuncIdx::new(raw_target),
)
match replacement {
Some(replacement) => {
for instruction in replacement {
out.push(instruction)
}
ctx.stats.exact_call_target_facts_accepted += 1
if current is @lib.ReturnCallRef(_) {
ctx.stats.return_call_ref_converted += 1
} else {
ctx.stats.call_ref_converted += 1
}
rewritten = true
}
None => ctx.stats.structurally_incompatible_rejected += 1
}
}
None => ()
}
}
@lib.I32Eqz | @lib.I64Eqz =>
match right_operand {
Some(root) => {
let expected = if current is @lib.I32Eqz {
@lib.ValType::i32()
} else {
@lib.ValType::i64()
}
let (domain, used_fact) = compiler_fact_integer_operand_domain(
ctx, root, expected,
)
match domain {
Some(domain) if used_fact => {
ctx.stats.candidate_rewrites += 1
let zero = compiler_fact_integer_domain_exact(domain.width, 0UL)
let truth = compiler_fact_relation_equal(domain, zero)
if compiler_fact_push_folded_predicate(out, 1, truth) {
ctx.stats.integer_zero_checks_folded += 1
rewritten = true
} else {
ctx.stats.valid_facts_without_rewrite += 1
}
}
_ => ()
}
}
None => ()
}
@lib.RefIsNull =>
match right_operand {
Some(root) =>
match
compiler_fact_query_result_type(
root.1,
root.0.result,
ctx.query.env,
) {
Some(expected) => {
let truth = ctx.query.null_test(
ctx.function,
root.0,
root.1,
expected,
)
if truth != FactUnknown {
ctx.stats.candidate_rewrites += 1
if compiler_fact_push_folded_predicate(out, 1, truth) {
ctx.stats.reference_null_checks_folded += 1
rewritten = true
}
}
}
None => ctx.stats.type_mismatches += 1
}
None => ()
}
_ =>
match compiler_fact_compare_expected_type(current) {
Some(expected) =>
match (left_operand, right_operand) {
(Some(left_root), Some(right_root)) => {
let (left, left_fact) = compiler_fact_integer_operand_domain(
ctx, left_root, expected,
)
let (right, right_fact) = compiler_fact_integer_operand_domain(
ctx, right_root, expected,
)
match (left, right) {
(Some(left), Some(right)) if left_fact || right_fact => {
ctx.stats.candidate_rewrites += 1
let truth = compiler_fact_relation_for_instruction(
current, left, right,
)
if compiler_fact_push_folded_predicate(out, 2, truth) {
ctx.stats.integer_comparisons_folded += 1
rewritten = true
} else {
ctx.stats.valid_facts_without_rewrite += 1
}
}
_ => ()
}
}
_ => ()
}
None => ()
}
}
if !rewritten {
out.push(current)
}
match
(
compiler_fact_operand_pop_count(original, ctx.query.env),
compiler_fact_materializer_result_arity(original, ctx.query.env),
) {
(Some(pops), Some(results)) => {
for _ in 0.. values.clear()
}
}
out
}
///|
fn apply_compiler_facts_trusted(
module_ : @lib.Module,
version : UInt32,
index : CompilerFactIndex,
stats : CompilerFactMaterializationStats,
) -> Result[@lib.Module, String] {
let functions = match module_.code_sec {
Some(@lib.CodeSec(functions)) => functions
None => return Ok(module_.without_compiler_fact_custom_section())
}
let sites = match @binary.compiler_fact_module_opcode_sites(module_) {
Ok(sites) => sites
Err(err) => {
stats.stale_or_unresolved_sites += 1
return Err("apply-compiler-facts could not index opcode sites: \{err}")
}
}
let imported = directize_imported_func_count(module_)
let query = CompilerFactQuery::new(
module_,
version,
CompilerFactUsePolicy::trust_assertions(),
index,
sites,
stats,
)
let rewritten : Array[@lib.Func] = []
for defined = 0; defined < functions.length(); defined = defined + 1 {
let function = functions[defined]
let @lib.Func(locals, @lib.Expr(body)) = function
let absolute = (imported + defined).reinterpret_as_uint()
if index.body_facts(@lib.FuncIdx::new(absolute)) is None {
rewritten.push(function)
continue
}
let cursor = Ref::new(0)
let context = {
function: absolute,
sites: sites[defined],
cursor,
query,
stats,
}
rewritten.push(
@lib.Func::new(
locals,
@lib.Expr::new(compiler_fact_rewrite_region(body, context)),
),
)
}
Ok(
module_
.with_code_sec(@lib.CodeSec::new(rewritten))
.without_compiler_fact_custom_section(),
)
}
///|
fn compiler_fact_materialize_function_inline_policies(
module_ : @lib.Module,
facts : @representation.OptimizationFactsSec,
) -> @lib.Module {
let mut has_concrete_policy = false
for function in facts.functions {
match function.inline_policy {
Some(@representation.NeverInline) | Some(@representation.AlwaysInline) => {
has_concrete_policy = true
break
}
_ => ()
}
}
if !has_concrete_policy {
return module_
}
let imported = directize_imported_func_count(module_)
let defined = match module_.func_sec {
Some(@lib.FuncSec(functions)) => functions.length()
None => 0
}
let total = imported + defined
let desired = Array::make(total, -1)
for function in facts.functions {
let raw = function.function.reinterpret_as_int()
if raw < 0 || raw >= total {
continue
}
match function.inline_policy {
Some(@representation.NeverInline) => desired[raw] = 0
Some(@representation.AlwaysInline) => desired[raw] = 1
// The inliner has no intermediate profitability tier. Keep Default and
// Prefer inert instead of silently strengthening either policy.
Some(@representation.DefaultInlinePolicy)
| Some(@representation.PreferInline)
| None => ()
}
}
let entries : Array[@lib.FuncAnnotationAssoc] = []
let original_entries = match module_.func_annotation_sec {
Some(@lib.FuncAnnotationSec(entries)) => entries
None => []
}
for entry in original_entries {
let @lib.FuncAnnotationAssoc(function, annotations) = entry
let raw = function.reinterpret_as_int()
if raw < 0 || raw >= total || desired[raw] < 0 {
entries.push(entry)
continue
}
let retained : Array[@lib.FuncAnnotation] = []
for annotation in annotations {
let mut recognized_policy = false
if annotation.name == "binaryen.inline" && !annotation.args.is_empty() {
let arg = annotation.args[annotation.args.length() - 1]
recognized_policy = arg == "\"\\00\"" ||
arg == "\"\\7f\"" ||
arg == "\"\\7F\""
}
if !recognized_policy {
retained.push(annotation)
}
}
if !retained.is_empty() {
entries.push(@lib.FuncAnnotationAssoc::new(function, retained))
}
}
for raw = 0; raw < total; raw = raw + 1 {
if desired[raw] >= 0 {
let arg = if desired[raw] == 0 { "\"\\00\"" } else { "\"\\7f\"" }
entries.push(
@lib.FuncAnnotationAssoc::new(raw.reinterpret_as_uint(), [
@lib.FuncAnnotation::new("binaryen.inline", args=[arg]),
]),
)
}
}
if module_.func_annotation_sec is Some(_) || !entries.is_empty() {
return module_.with_func_annotation_sec(
@lib.FuncAnnotationSec::new(entries),
)
}
module_
}
///|
fn compiler_fact_record_inventory(
facts : @representation.OptimizationFactsSec,
stats : CompilerFactMaterializationStats,
) -> Int32 {
stats.function_bodies_with_facts = facts.bodies.length()
stats.function_facts_seen = facts.functions.length()
stats.signature_facts_seen = facts.signatures.length()
stats.type_facts_seen = facts.types.length()
stats.global_facts_seen = facts.globals.length()
stats.table_facts_seen = facts.tables.length()
stats.hint_facts_seen = facts.hints.length()
let mut assertions = facts.signatures.length() +
facts.types.length() +
facts.globals.length() +
facts.tables.length()
let mut hints = facts.hints.length()
for function in facts.functions {
if function.effects is Some(_) ||
function.parameters.length() > 0 ||
function.results.length() > 0 ||
function.boundary is Some(_) {
assertions += 1
}
if function.profile is Some(_) || function.inline_policy is Some(_) {
hints += 1
}
}
for body in facts.bodies {
stats.value_facts_seen += body.sites.length()
stats.relation_facts_seen += body.relations.length()
stats.call_facts_seen += body.calls.length()
stats.branch_facts_seen += body.branches.length()
stats.loop_facts_seen += body.loops.length()
stats.allocation_facts_seen += body.allocations.length()
stats.access_facts_seen += body.accesses.length()
stats.numeric_operation_facts_seen += body.numeric_operations.length()
assertions += body.sites.length() +
body.relations.length() +
body.allocations.length() +
body.accesses.length() +
body.numeric_operations.length()
for call in body.calls {
if call.direct_target is Some(_) {
assertions += 1
}
if call.profile is Some(_) || call.inline_policy is Some(_) {
hints += 1
}
}
hints += body.branches.length() + body.loops.length()
}
stats.hint_facts_seen = hints
assertions
}
///|
fn compiler_fact_count_invalid_top_level_indices(
facts : @representation.OptimizationFactsSec,
env : @validate.Env,
) -> Int32 {
let mut invalid = 0
for value in facts.functions {
if !env.has_func(@lib.FuncIdx::new(value.function)) {
invalid += 1
}
}
for value in facts.bodies {
if !env.has_func(@lib.FuncIdx::new(value.function)) {
invalid += 1
}
}
for value in facts.signatures {
if value.signature >= env.global_types.length().reinterpret_as_uint() {
invalid += 1
}
}
for value in facts.types {
if value.type_ >= env.global_types.length().reinterpret_as_uint() {
invalid += 1
}
}
for value in facts.globals {
if env.get_global_type(@lib.GlobalIdx::new(value.global)) is None {
invalid += 1
}
}
for value in facts.tables {
if env.get_table_type(@lib.TableIdx::new(value.table)) is None {
invalid += 1
}
}
invalid + compiler_fact_query_invalid_resource_indices(facts, env)
}
///|
pub fn apply_compiler_facts(
module_ : @lib.Module,
policy : CompilerFactUsePolicy,
) -> Result[ApplyCompilerFactsResult, String] {
let stats = CompilerFactMaterializationStats::new()
if policy == TrustAssertions &&
pass_module_has_offset_sensitive_metadata(module_) {
return Ok({ module_, stats, })
}
let section = match module_.compiler_fact_custom_section {
Some(section) => section
None => return Ok({ module_, stats, })
}
stats.fact_sections_seen = 1
let assertion_count = compiler_fact_record_inventory(section.facts, stats)
match policy {
IgnoreFacts => {
stats.hint_only_facts = stats.hint_facts_seen
stats.facts_blocked_by_trust = assertion_count
stats.exact_call_target_facts_seen = compiler_fact_count_exact_targets(
section.facts,
)
stats.ignored_untrusted_assertions = stats.exact_call_target_facts_seen
return Ok({ module_, stats, })
}
HintsOnly => {
stats.hint_only_facts = stats.hint_facts_seen
stats.facts_blocked_by_trust = assertion_count
stats.exact_call_target_facts_seen = compiler_fact_count_exact_targets(
section.facts,
)
stats.ignored_untrusted_assertions = stats.exact_call_target_facts_seen
}
TrustAssertions => stats.trusted_assertion_facts = assertion_count
}
if section.version != 1U {
stats.unsupported_versions = 1
return Ok({ module_, stats, })
}
let env = @validate.Env::new().with_module(module_)
stats.invalid_referenced_indices = compiler_fact_count_invalid_top_level_indices(
section.facts,
env,
)
if stats.invalid_referenced_indices > 0 {
return Ok({ module_, stats, })
}
if policy == HintsOnly {
if pass_module_has_offset_sensitive_metadata(module_) {
return Ok({ module_, stats, })
}
return Ok({
module_: compiler_fact_materialize_function_inline_policies(
module_,
section.facts,
),
stats,
})
}
let imported = directize_imported_func_count(module_)
let defined = match module_.code_sec {
Some(@lib.CodeSec(functions)) => functions.length()
None => 0
}
for body in section.facts.bodies {
let function = body.function.reinterpret_as_int()
if function < imported || function >= imported + defined {
stats.stale_or_unresolved_sites += 1
}
}
let index = compiler_fact_index_bounded(section.facts, env)
stats.exact_call_target_facts_seen = compiler_fact_count_exact_targets(
section.facts,
)
match apply_compiler_facts_trusted(module_, section.version, index, stats) {
Ok(module_) =>
Ok({
module_: compiler_fact_materialize_function_inline_policies(
module_,
section.facts,
),
stats,
})
Err(err) => Err(err)
}
}