// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Conservative attribution from pinned Binaryen version_130 counterpart:
// Copyright 2018 WebAssembly Community Group participants
// Counterpart: src/passes/DeadArgumentElimination.cpp (5d704ad5); origin remains unconfirmed.
// 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.
///|
pub fn dae_summary() -> String {
"Remove private dead scalar parameters across owned direct call boundaries."
}
///|
pub fn dae_optimizing_summary() -> String {
"Run Binaryen-compatible dead argument elimination and nested cleanup on touched functions."
}
///|
priv enum DaeDecisionReason {
DaeCandidateApplied
DaeCandidateProduced
DaeNoStrictImprovement
DaeVisibleBoundary
DaeExportedFunction
DaeStartFunction
DaeEscapedThroughRefFunc
DaeActiveElementTableExposure
DaePassiveElementExposure
DaeUnknownIndirectCaller
DaeUnknownCallRefCaller
DaeDirectTailDependency
DaeIndirectTailDependency
DaeReferenceTailDependency
DaeParameterStillRead
DaeResultStillObserved
DaeUniformActualsDisagree
DaeActualValueSliceUnavailable
DaeOperandDirectlyDroppable
DaeOperandEffectsMustBeReplayed
DaeOperandRequiresLocalization
DaeOperandContainsEscapingControl
DaeOperandMayTrap
DaeUnknownObservability
DaeGcLubUnavailable
DaeGcRefinementNotStrict
DaeStaleCandidateFacts
DaeCallCountChanged
DaeTypeReconstructionUnavailable
DaeValidationRollback
DaeProfitabilityRejection
DaeWorkBudgetRejection
} derive(Eq, Debug)
///|
#warnings("-unused_constructor")
priv enum DaeBoundarySurface {
DaePrivateDirectBoundary
DaeExportedBoundary
DaeImportedBoundary
DaeStartBoundary
DaeRefFuncBoundary
DaeActiveElementBoundary
DaePassiveElementBoundary
DaeUnknownIndirectBoundary
DaeUnknownCallRefBoundary
DaeDirectReturnCallBoundary
DaeIndirectReturnCallBoundary
DaeReferenceReturnCallBoundary
}
///|
priv struct DaeDiagnostics {
reason_counts : Array[Int32]
mut boundaries_analyzed : Int32
mut candidates_produced : Int32
mut candidates_applied : Int32
mut parameters_removed : Int32
mut parameters_materialized : Int32
mut parameter_types_refined : Int32
mut result_types_refined : Int32
mut results_removed : Int32
mut callsites_rewritten : Int32
mut return_call_sites_rewritten : Int32
mut effectful_operands_replayed : Int32
mut localization_retries : Int32
mut scratch_locals_added : Int32
mut scratch_locals_reused : Int32
mut type_entries_reused : Int32
mut type_entries_appended : Int32
mut candidate_requeues : Int32
mut candidate_stale_retries : Int32
mut validation_rollbacks : Int32
mut selected_only_rewrites : Int32
mut guard_based_skips : Int32
mut nested_functions_touched : Int32
mut nested_passes_executed : Int32
}
///|
fn dae_decision_reason_index(reason : DaeDecisionReason) -> Int32 {
match reason {
DaeCandidateApplied => 0
DaeCandidateProduced => 1
DaeNoStrictImprovement => 2
DaeVisibleBoundary => 3
DaeExportedFunction => 4
DaeStartFunction => 5
DaeEscapedThroughRefFunc => 6
DaeActiveElementTableExposure => 7
DaePassiveElementExposure => 8
DaeUnknownIndirectCaller => 9
DaeUnknownCallRefCaller => 10
DaeDirectTailDependency => 11
DaeIndirectTailDependency => 12
DaeReferenceTailDependency => 13
DaeParameterStillRead => 14
DaeResultStillObserved => 15
DaeUniformActualsDisagree => 16
DaeActualValueSliceUnavailable => 17
DaeOperandDirectlyDroppable => 18
DaeOperandEffectsMustBeReplayed => 19
DaeOperandRequiresLocalization => 20
DaeOperandContainsEscapingControl => 21
DaeOperandMayTrap => 22
DaeUnknownObservability => 23
DaeGcLubUnavailable => 24
DaeGcRefinementNotStrict => 25
DaeStaleCandidateFacts => 26
DaeCallCountChanged => 27
DaeTypeReconstructionUnavailable => 28
DaeValidationRollback => 29
DaeProfitabilityRejection => 30
DaeWorkBudgetRejection => 31
}
}
///|
fn dae_decision_reason_code(reason : DaeDecisionReason) -> String {
match reason {
DaeCandidateApplied => "candidate-applied"
DaeCandidateProduced => "candidate-produced"
DaeNoStrictImprovement => "no-strict-improvement"
DaeVisibleBoundary => "visible-boundary"
DaeExportedFunction => "exported-function"
DaeStartFunction => "start-function"
DaeEscapedThroughRefFunc => "escaped-through-ref.func"
DaeActiveElementTableExposure => "active-element-table-exposure"
DaePassiveElementExposure => "passive-element-exposure"
DaeUnknownIndirectCaller => "unknown-indirect-caller"
DaeUnknownCallRefCaller => "unknown-call_ref-caller"
DaeDirectTailDependency => "direct-tail-dependency"
DaeIndirectTailDependency => "indirect-tail-dependency"
DaeReferenceTailDependency => "reference-tail-dependency"
DaeParameterStillRead => "parameter-still-read"
DaeResultStillObserved => "result-still-observed"
DaeUniformActualsDisagree => "uniform-actuals-disagree"
DaeActualValueSliceUnavailable => "actual-value-slice-unavailable"
DaeOperandDirectlyDroppable => "operand-directly-droppable"
DaeOperandEffectsMustBeReplayed => "operand-effects-must-be-replayed"
DaeOperandRequiresLocalization => "operand-requires-localization"
DaeOperandContainsEscapingControl => "operand-contains-escaping-control"
DaeOperandMayTrap => "operand-may-trap"
DaeUnknownObservability => "unknown-observability"
DaeGcLubUnavailable => "gc-lub-unavailable"
DaeGcRefinementNotStrict => "gc-refinement-not-strict"
DaeStaleCandidateFacts => "stale-candidate-facts"
DaeCallCountChanged => "call-count-changed"
DaeTypeReconstructionUnavailable => "type-reconstruction-unavailable"
DaeValidationRollback => "validation-rollback"
DaeProfitabilityRejection => "profitability-rejection"
DaeWorkBudgetRejection => "work-budget-rejection"
}
}
///|
#warnings("-unused_value")
fn dae_boundary_surface_reason(
surface : DaeBoundarySurface,
) -> DaeDecisionReason {
match surface {
DaePrivateDirectBoundary => DaeCandidateProduced
DaeExportedBoundary => DaeExportedFunction
DaeImportedBoundary => DaeVisibleBoundary
DaeStartBoundary => DaeStartFunction
DaeRefFuncBoundary => DaeEscapedThroughRefFunc
DaeActiveElementBoundary => DaeActiveElementTableExposure
DaePassiveElementBoundary => DaePassiveElementExposure
DaeUnknownIndirectBoundary => DaeUnknownIndirectCaller
DaeUnknownCallRefBoundary => DaeUnknownCallRefCaller
DaeDirectReturnCallBoundary => DaeDirectTailDependency
DaeIndirectReturnCallBoundary => DaeIndirectTailDependency
DaeReferenceReturnCallBoundary => DaeReferenceTailDependency
}
}
// [O4Z-DAE-BOUNDARY]001 scanner inventory (do not delete consumers blindly):
// - dae_collect_exported_funcs / dae_mark_elem_escapes / dae_scan_escapes_instrs /
// dae_collect_escapes → original escape booleans; now also feed
// dae_collect_original_exposure / DaeFunctionExposure reason flags.
// - dae_collect_current_function_analysis / dae_scan_current_call_facts_instrs →
// current direct/return_call/drop/ref.func facts (DaeCurrentCallFacts +
// DaeFunctionInfo); extended for legacy Try and barrier flags.
// - dae_scan_local_use_ignoring_instrs / dae_function_info_refresh_unused_params →
// syntactic local.get/set still used for writes/repair; gets are no longer the
// authoritative incoming live-in oracle for signature changes.
// - dae_hot_param_entry_liveness / dae_scan_param_entry_liveness_instrs →
// consumed by dae_incoming_parameter_liveness; private scanners remain for
// specialized families until later workstreams (TODO [O4Z-DAE-FLOW]001 /
// [O4Z-DAE-TYPES]001 / [O4Z-DAE-RESULTS]001).
// - dae_call_boundary_candidate / dae_tail_call_boundary_evidence → wrapped by
// dae_can_change_parameters (incoming ownership) and
// dae_can_remove_function_result (result-tail legality) as the authoritative
// final guards. Type equality alone never creates exposure.
///|
/// Exact incoming-call exposure for one absolute function index.
/// Type-only carriers (shared tag/table/`call_indirect`/`call_ref` types) are
/// intentionally absent: Binaryen v131 only marks unseen calls from exports and
/// actual `ref.func` / element references to that exact function.
priv struct DaeFunctionExposure {
mut imported : Bool
mut exported : Bool
mut start : Bool
mut ref_func_body : Bool
mut ref_func_module : Bool
mut active_element : Bool
mut passive_element : Bool
mut declarative_element : Bool
}
///|
fn dae_function_exposure_empty() -> DaeFunctionExposure {
{
imported: false,
exported: false,
start: false,
ref_func_body: false,
ref_func_module: false,
active_element: false,
passive_element: false,
declarative_element: false,
}
}
///|
fn dae_function_exposure_blocks_boundary(
exposure : DaeFunctionExposure,
) -> Bool {
exposure.imported ||
exposure.exported ||
exposure.start ||
exposure.ref_func_body ||
exposure.ref_func_module ||
exposure.active_element ||
exposure.passive_element ||
exposure.declarative_element
}
///|
fn dae_function_exposure_primary_reason(
exposure : DaeFunctionExposure,
) -> DaeDecisionReason {
if exposure.imported {
DaeVisibleBoundary
} else if exposure.exported {
DaeExportedFunction
} else if exposure.start {
DaeStartFunction
} else if exposure.ref_func_body || exposure.ref_func_module {
DaeEscapedThroughRefFunc
} else if exposure.active_element {
DaeActiveElementTableExposure
} else if exposure.passive_element {
DaePassiveElementExposure
} else if exposure.declarative_element {
DaePassiveElementExposure
} else {
DaeCandidateProduced
}
}
///|
/// Topology arrays and edge values use absolute function indices.
/// Outgoing call/tail facts describe instructions in a function body; they do
/// not expose that function as an unseen incoming-call target.
priv struct DaeCurrentTopologyExtras {
direct_return_callers : Array[Array[Int32]]
direct_return_callees_by_caller : Array[Array[Int32]]
adjacency_membership : @hashmap.HashMap[DaeAdjacencyKey, Bool]
has_outgoing_call_indirect : Array[Bool]
has_outgoing_call_ref : Array[Bool]
has_outgoing_return_call : Array[Bool]
has_outgoing_return_call_indirect : Array[Bool]
has_outgoing_return_call_ref : Array[Bool]
}
///|
/// Authoritative boundary facts. Exposure lives only on `original`; topology
/// lives only on `graph`. Do not reintroduce duplicate mutable copies.
#warnings("-unused_field")
priv struct DaeBoundaryFacts {
original : DaeOriginalBoundarySnapshot
graph : DaeCurrentBoundaryGraph
mut incoming_param_live_by_def : Array[Array[Bool]]
mut incoming_param_live_body_epochs : Array[Int32]
}
///|
fn dae_boundary_facts_exposure(
facts : DaeBoundaryFacts,
) -> Array[DaeFunctionExposure] {
facts.original.exposure
}
///|
fn dae_boundary_facts_topology(
facts : DaeBoundaryFacts,
) -> DaeCurrentTopologyExtras {
facts.graph.topology
}
///|
fn dae_abs_func_idx(imported : Int32, def_idx : Int32) -> Int32 {
imported + def_idx
}
///|
fn dae_def_idx_of_abs(imported : Int32, func_abs : Int32) -> Int32? {
if func_abs < imported {
None
} else {
Some(func_abs - imported)
}
}
///|
#warnings("-unused_value")
fn dae_operand_decision_reason(
operand : DaeOperandDisposition,
effects_must_be_replayed : Bool,
may_trap : Bool,
) -> DaeDecisionReason {
if operand.has_branch {
DaeOperandContainsEscapingControl
} else if may_trap {
DaeOperandMayTrap
} else if operand.droppable_without_repair {
DaeOperandDirectlyDroppable
} else if effects_must_be_replayed || operand.preserve_effects_on_removal {
DaeOperandEffectsMustBeReplayed
} else {
DaeOperandRequiresLocalization
}
}
///|
#warnings("-unused_value")
fn dae_gc_refinement_decision_reason(
lub_available : Bool,
strict_improvement : Bool,
) -> DaeDecisionReason {
if !lub_available {
DaeGcLubUnavailable
} else if !strict_improvement {
DaeGcRefinementNotStrict
} else {
DaeCandidateApplied
}
}
///|
fn dae_diagnostics_new() -> DaeDiagnostics {
{
reason_counts: Array::make(32, 0),
boundaries_analyzed: 0,
candidates_produced: 0,
candidates_applied: 0,
parameters_removed: 0,
parameters_materialized: 0,
parameter_types_refined: 0,
result_types_refined: 0,
results_removed: 0,
callsites_rewritten: 0,
return_call_sites_rewritten: 0,
effectful_operands_replayed: 0,
localization_retries: 0,
scratch_locals_added: 0,
scratch_locals_reused: 0,
type_entries_reused: 0,
type_entries_appended: 0,
candidate_requeues: 0,
candidate_stale_retries: 0,
validation_rollbacks: 0,
selected_only_rewrites: 0,
guard_based_skips: 0,
nested_functions_touched: 0,
nested_passes_executed: 0,
}
}
///|
fn dae_diagnostics_record_reason(
diagnostics : DaeDiagnostics,
reason : DaeDecisionReason,
) -> Unit {
let index = dae_decision_reason_index(reason)
diagnostics.reason_counts[index] = diagnostics.reason_counts[index] + 1
}
///|
fn dae_diagnostics_reason_count(
diagnostics : DaeDiagnostics,
reason : DaeDecisionReason,
) -> Int32 {
diagnostics.reason_counts[dae_decision_reason_index(reason)]
}
///|
#warnings("-unused_value")
fn dae_diagnostics_record_return_callsite_rewrite(
diagnostics : DaeDiagnostics,
) -> Unit {
diagnostics.return_call_sites_rewritten += 1
}
///|
fn dae_diagnostics_all_reasons() -> Array[DaeDecisionReason] {
[
DaeCandidateApplied,
DaeCandidateProduced,
DaeNoStrictImprovement,
DaeVisibleBoundary,
DaeExportedFunction,
DaeStartFunction,
DaeEscapedThroughRefFunc,
DaeActiveElementTableExposure,
DaePassiveElementExposure,
DaeUnknownIndirectCaller,
DaeUnknownCallRefCaller,
DaeDirectTailDependency,
DaeIndirectTailDependency,
DaeReferenceTailDependency,
DaeParameterStillRead,
DaeResultStillObserved,
DaeUniformActualsDisagree,
DaeActualValueSliceUnavailable,
DaeOperandDirectlyDroppable,
DaeOperandEffectsMustBeReplayed,
DaeOperandRequiresLocalization,
DaeOperandContainsEscapingControl,
DaeOperandMayTrap,
DaeUnknownObservability,
DaeGcLubUnavailable,
DaeGcRefinementNotStrict,
DaeStaleCandidateFacts,
DaeCallCountChanged,
DaeTypeReconstructionUnavailable,
DaeValidationRollback,
DaeProfitabilityRejection,
DaeWorkBudgetRejection,
]
}
///|
fn dae_trace_diagnostics(
trace : (String) -> Unit,
pass_name : String,
diagnostics : DaeDiagnostics,
) -> Unit {
let out = StringBuilder::new()
out.write_string(
"pass[\{pass_name}]:diagnostics boundaries_analyzed=\{diagnostics.boundaries_analyzed} candidates_produced=\{diagnostics.candidates_produced} candidates_applied=\{diagnostics.candidates_applied} parameters_removed=\{diagnostics.parameters_removed} parameters_materialized=\{diagnostics.parameters_materialized} parameter_types_refined=\{diagnostics.parameter_types_refined} result_types_refined=\{diagnostics.result_types_refined} results_removed=\{diagnostics.results_removed} callsites_rewritten=\{diagnostics.callsites_rewritten} return_callsites_rewritten=\{diagnostics.return_call_sites_rewritten} effectful_operands_replayed=\{diagnostics.effectful_operands_replayed} localization_retries=\{diagnostics.localization_retries} scratch_locals_added=\{diagnostics.scratch_locals_added} scratch_locals_reused=\{diagnostics.scratch_locals_reused} type_entries_reused=\{diagnostics.type_entries_reused} type_entries_appended=\{diagnostics.type_entries_appended} candidate_requeues=\{diagnostics.candidate_requeues} candidate_stale_retries=\{diagnostics.candidate_stale_retries} validation_rollbacks=\{diagnostics.validation_rollbacks} selected_only_rewrites=\{diagnostics.selected_only_rewrites} guard_based_skips=\{diagnostics.guard_based_skips} nested_functions_touched=\{diagnostics.nested_functions_touched} nested_passes_executed=\{diagnostics.nested_passes_executed}",
)
for reason in dae_diagnostics_all_reasons() {
out.write_string(
" reason.\{dae_decision_reason_code(reason)}=\{dae_diagnostics_reason_count(diagnostics, reason)}",
)
}
trace(out.to_string())
}
///|
fn dae_diagnostics_record_applied_effects(
diagnostics : DaeDiagnostics,
effects : DaeCommitEffects,
) -> Unit {
let applied = if effects.candidates_applied > 0 {
effects.candidates_applied
} else {
1
}
diagnostics.candidates_applied += applied
let mut idx = 0
while idx < applied {
dae_diagnostics_record_reason(diagnostics, DaeCandidateApplied)
idx += 1
}
}
///|
fn dae_diagnostics_record_commit_effects(
diagnostics : DaeDiagnostics,
effects : DaeCommitEffects,
) -> Unit {
let produced = if effects.candidates_produced > 0 {
effects.candidates_produced
} else {
1
}
diagnostics.candidates_produced += produced
let mut produced_idx = 0
while produced_idx < produced {
dae_diagnostics_record_reason(diagnostics, DaeCandidateProduced)
produced_idx += 1
}
let mut def_idx = 0
while def_idx < effects.parameter_changed.length() {
if effects.parameter_changed[def_idx] {
if def_idx < effects.parameter_type_refined.length() &&
effects.parameter_type_refined[def_idx] {
diagnostics.parameter_types_refined += 1
} else {
diagnostics.parameters_removed += 1
}
}
if def_idx < effects.body_materialized.length() &&
effects.body_materialized[def_idx] {
diagnostics.parameters_materialized += 1
}
if def_idx < effects.result_changed.length() &&
effects.result_changed[def_idx] {
if def_idx < effects.result_removed.length() &&
effects.result_removed[def_idx] {
diagnostics.results_removed += 1
} else {
diagnostics.result_types_refined += 1
}
}
if def_idx < effects.caller_drop_removed.length() &&
effects.caller_drop_removed[def_idx] {
diagnostics.callsites_rewritten += 1
}
if def_idx < effects.caller_bottom_repaired.length() &&
effects.caller_bottom_repaired[def_idx] {
diagnostics.callsites_rewritten += 1
}
if def_idx < effects.body_localized.length() &&
effects.body_localized[def_idx] {
diagnostics.localization_retries += 1
}
def_idx += 1
}
diagnostics.effectful_operands_replayed += effects.effectful_operands_replayed
diagnostics.scratch_locals_added += effects.scratch_locals_added
diagnostics.scratch_locals_reused += effects.scratch_locals_reused
diagnostics.type_entries_reused += effects.type_entries_reused
diagnostics.type_entries_appended += effects.type_entries_appended
diagnostics.selected_only_rewrites += effects.selected_only_rewrites
diagnostics.guard_based_skips += effects.guard_based_skips
}
///|
priv struct DaeCallShape {
start : Int32
end : Int32
pure : Bool
}
///|
/// Producer category for a recovered value slice. Used by the ordinary
/// unused-parameter classifier; do not re-derive purity from opcode names
/// in multiple places.
#warnings("-unused_value")
priv enum DaeValueProducerKind {
DaeProducerConstantOrPureLeaf
DaeProducerLocalRead
DaeProducerGlobalRead
DaeProducerOrdinaryExpression
DaeProducerDirectCall
DaeProducerIndirectCall
DaeProducerCallRef
DaeProducerStructuredControl
DaeProducerUnreachable
DaeProducerUnknown
} derive(Eq, Debug)
///|
priv enum DaeDirectCallKind {
DaeDirectCall
DaeDirectReturnCall
} derive(Eq, Debug)
///|
priv enum DaeOperandRewriteAction {
DaeDropOperandDirectly
DaePreserveOperandEffectsAndDropValue
DaeLocalizeOperandAndRetry
DaeRejectEscapingOperandRewrite
}
///|
priv struct DaeOperandDisposition {
start : Int32
end : Int32
droppable_without_repair : Bool
preserve_effects_on_removal : Bool
has_branch : Bool
has_effect_or_branch : Bool
}
///|
priv struct DaeInstructionObservabilityEvidence {
has_observable_effect : Bool
has_branch_control : Bool
may_trap_or_unknown : Bool
erasable_before_unreachable : Bool
}
///|
#warnings("-unused_field")
priv struct DaeOperandRewriteDisposition {
operand : DaeOperandDisposition
localization_available : Bool
has_escaping_branch : Bool
action : DaeOperandRewriteAction
reason : DaeDecisionReason
}
///|
priv struct DaeOperandRemovalPlan {
start : Int32
end : Int32
keep_operand : Bool
replay_operand : Bool
append_drop : Bool
requires_localization_retry : Bool
rejects_direct_removal : Bool
}
///|
/// Canonical direct-call and typed-select operand/value carrier
/// ([O4Z-DAE-PLAN]001). Specialized GC/control/result paths still construct
/// slices through shared helpers; migrate them in later workstreams rather than
/// inventing a parallel carrier.
#warnings("-unused_field")
priv struct DaeValueSlice {
start : Int32
end : Int32
pure : Bool
has_branch : Bool
has_effect_or_branch : Bool
disposition : DaeOperandDisposition
input_arity : Int32
output_arity : Int32
value_type : @lib.ValType?
producer_kind : DaeValueProducerKind
has_observable_effect : Bool
may_trap : Bool
has_escaping_branch : Bool
produces_unreachable : Bool
call_kind : DaeDirectCallKind?
call_idx : Int32
caller_def : Int32
callee_abs : Int32
module_epoch : Int32
call_epoch : Int32
type_epoch : Int32
signature_epoch : Int32
body_epoch : Int32
}
///|
#warnings("-unused_field")
priv struct DaeDirectCallOperandSlices {
call_idx : Int32
call_kind : DaeDirectCallKind
callee_abs : Int32
slices : Array[DaeValueSlice]
cursor : Int32
module_epoch : Int32
call_epoch : Int32
type_epoch : Int32
signature_epoch : Int32
body_epoch : Int32
}
///|
priv struct DaeCallsiteValueSliceEvidence {
call_idx : Int32
arg_count : Int32
entry_stack_values : Int32
slices : Array[DaeValueSlice]
cursor : Int32
}
///|
priv struct DaeMultiResultSuffixBoundsEvidence {
suffix_start : Int32
operand_end : Int32
has_terminal_return : Bool
}
///|
priv struct DaeMultiResultOperandSequenceEvidence {
bounds : DaeMultiResultSuffixBoundsEvidence
operand_starts : Array[Int32]
value_instr_indices : Array[Int32]
specialized_slots : Array[Bool]
}
///|
priv struct DaeFlatMultiResultSuffixEvidence {
start : Int32
end : Int32
result_count : Int32
has_terminal_return : Bool
}
///|
priv struct DaeDroppedOperandPairSkipEvidence {
start : Int32
end : Int32
next : Int32
skipped_pairs : Int32
}
///|
priv struct DaeMultiResultOperandSlotEvidence {
start : Int32
end : Int32
operand_start : Int32
next : Int32
skipped_pairs : Int32
consumed_single_producer : Bool
}
///|
priv struct DaeLocalGetRangeEvidence {
start : Int32
end : Int32
has_local_get : Bool
local_free : Bool
singleton : Bool
range_allows_cleanup : Bool
}
///|
priv struct DaeRedundantAccessCleanupEvidence {
cleanup_kind : Int32
pop_previous : Bool
skip_current : Bool
replacement : Array[@lib.Instruction]
}
///|
priv struct DaeDroppedLocalTeeSuffixCleanupEvidence {
cleanup_kind : Int32
start : Int32
end : Int32
next_idx : Int32
replacement : Array[@lib.Instruction]
}
///|
priv struct DaeDroppablePureStackEvidence {
start : Int32
end : Int32
has_local_get : Bool
local_free : Bool
local_range : DaeLocalGetRangeEvidence
}
///|
priv struct DaeLiveLocalCopyCleanupEvidence {
start : Int32
end : Int32
next_idx : Int32
copy_count : Int32
requires_local_free_stack : Bool
replacement : Array[@lib.Instruction]
}
///|
priv struct DaeDroppedPureLeafSubjectEvidence {
start : Int32
end : Int32
prefix_end : Int32
instr : @lib.Instruction
range_allows_cleanup : Bool
prefix_has_observable_effect : Bool
}
///|
priv struct DaeDroppedPureLeafCleanupEvidence {
range_allows_cleanup : Bool
preserve_integer_const_debris : Bool
preserve_local_get_after_same_set : Bool
preserve_local_get_after_effect : Bool
}
///|
priv struct DaeDroppedPureExpressionCleanupEvidence {
cleanup_kind : Int32
start : Int32
end : Int32
next_idx : Int32
operand_count : Int32
operator_count : Int32
uses_leaf_preservation_policy : Bool
}
///|
priv struct DaeDroppablePureControlValueEvidence {
input_count : Int32
output_count : Int32
condition_arity : Int32
body_count : Int32
primary_body_length : Int32
secondary_body_length : Int32
catch_count : Int32
}
///|
priv struct DaeEmptyZeroResultControlCleanupEvidence {
input_count : Int32
output_count : Int32
rewritten_body_length : Int32
primary_body_length : Int32
secondary_body_length : Int32
body_count : Int32
drops_empty_control : Bool
}
///|
priv struct DaeControlEntryStackEvidence {
entry_stack_values : Int32
result_stack_values : Int32
}
///|
priv struct DaeNestedControlEntryEvidence {
entry_stack_values : Int32
result_stack_values : Int32
control_kind : Int32
has_else_arm : Bool
}
///|
priv struct DaeNestedControlChildrenEvidence {
entry_stack_values : Int32
result_stack_values : Int32
control_kind : Int32
has_else_arm : Bool
primary_body : Array[@lib.Instruction]
secondary_body : Array[@lib.Instruction]?
}
///|
priv struct DaeControlBlockTypeSignatureEvidence {
params : Array[@lib.ValType]
results : Array[@lib.ValType]
input_count : Int32
output_count : Int32
type_idx : @lib.TypeIdx?
}
///|
priv struct DaeI32ParamZeroResultLoopEvidence {
signature : DaeControlBlockTypeSignatureEvidence
body : Array[@lib.Instruction]
body_length : Int32
iteration_local : @lib.LocalIdx?
}
///|
#warnings("-unused_field")
priv struct DaeControlBlockTypeGcResultEvidence {
signature : DaeControlBlockTypeSignatureEvidence
result_index : Int32
candidate : @lib.ValType
}
///|
priv enum DaeControlBlockTypeGcResultSlot {
DaeSingleControlGcResultSlot
DaeLastControlGcResultSlot
}
///|
priv struct DaeStackOperandWindowEvidence {
start : Int32
end : Int32
requested_operands : Int32
available_operands : Int32
}
///|
priv struct DaeStackEffectEvidence {
inputs : Int32
outputs : Int32
}
///|
priv struct DaeStackSliceSimulationEvidence {
start : Int32
end : Int32
initial_stack_values : Int32
final_stack_values : Int32
max_stack_values : Int32
saw_effect_or_branch : Bool
}
///|
priv struct DaeStackSliceTransitionEvidence {
simulation : DaeStackSliceSimulationEvidence
expected_final_stack_values : Int32
}
///|
priv struct DaeStackSliceLocalizationPolicy {
initial_stack_values : Int32
expected_final_stack_values : Int32
depth : Int32
instr_allowed : (@lib.Instruction) -> Bool
}
///|
priv struct DaeStackSliceLocalizationRequest {
body : Array[@lib.Instruction]
start : Int32
end : Int32
initial_stack_values : Int32
expected_final_stack_values : Int32
depth : Int32
instr_allowed : (@lib.Instruction) -> Bool
}
///|
priv struct DaeEscapingBranchEvidence {
start : Int32
end : Int32
depth : Int32
instr_count : Int32
has_escaping_branch : Bool
}
///|
priv struct DaeStackSliceLocalizationEvidence {
transition : DaeStackSliceTransitionEvidence
no_escape_branch : DaeEscapingBranchEvidence
copied_slice : DaeNoEscapeSliceEvidence
disposition : DaeOperandDisposition
rewrite_disposition : DaeOperandRewriteDisposition
}
///|
priv struct DaeSelectConditionLocalizationEvidence {
start : Int32
end : Int32
instr_count : Int32
singleton_trivially_droppable : Bool
singleton_producer : DaeStackEffectEvidence?
stack_slice : DaeStackSliceLocalizationEvidence?
}
///|
priv struct DaeNoEscapeSliceEvidence {
branch : DaeEscapingBranchEvidence
instrs : Array[@lib.Instruction]
}
///|
priv struct DaeTerminalNoEscapePrefixEvidence {
prefix : DaeNoEscapeSliceEvidence
terminal : @lib.Instruction
terminal_idx : Int32
}
///|
priv struct DaeTerminalIfNoEscapeCarrierEvidence {
condition_prefix : DaeNoEscapeSliceEvidence
then_body : Array[@lib.Instruction]
else_body : DaeNoEscapeSliceEvidence?
}
///|
priv struct DaeDeadSuffixEvidence {
root_idx : Int32
suffix_start : Int32
body : Array[@lib.Instruction]
}
///|
priv struct DaeCallsiteSingleInstructionActualEvidence {
param_idx : Int32
actual_idx : Int32
instr : @lib.Instruction
}
///|
priv struct DaeSelectValueSlices {
left : DaeValueSlice
right : DaeValueSlice
condition : DaeSelectConditionLocalizationEvidence
select_idx : Int32
}
///|
priv struct DaeControlResultSlice {
body : Array[@lib.Instruction]
slice : DaeValueSlice
result_count : Int32
carrier_kind : Int32
flattened : Bool
terminal_return : Bool
}
///|
priv struct DaeEffectFreeBodyEvidence {
body : Array[@lib.Instruction]
body_length : Int32
has_observable_effect_or_branch : Bool
effect_free : Bool
}
///|
priv struct DaeTerminalReturnBodyEvidence {
body : Array[@lib.Instruction]
body_length : Int32
return_idx : Int32
prefix_length : Int32
prefix_has_observable_effect_or_branch : Bool
terminal_return_only : Bool
}
///|
priv struct DaeSingleBlockResultCarrierEvidence {
body : Array[@lib.Instruction]
result_count : Int32
body_has_effect_or_branch : Bool
effect_free_body : DaeEffectFreeBodyEvidence
}
///|
priv struct DaeGcSelectValueEvidence {
candidate : @lib.ValType
refined_candidate : @lib.ValType?
left_rewrite : @lib.Instruction?
right_rewrite : @lib.Instruction?
}
///|
priv struct DaeGcSelectValueSlicesEvidence {
slices : DaeSelectValueSlices
gc : DaeGcSelectValueEvidence
}
///|
priv struct DaeIndirectGcResultSliceEvidence {
candidate : @lib.ValType
param_count : Int32
stack_value_count : Int32
}
///|
priv struct DaeDirectGcCallResultEvidence {
candidate : @lib.ValType
refined_candidate : @lib.ValType?
param_count : Int32
}
///|
priv struct DaeGcTypeEvidence {
candidate : @lib.ValType
refined_candidate : @lib.ValType?
rewrite : @lib.Instruction?
}
///|
priv struct DaeSingleResultControlPairGcEvidence {
candidate : @lib.ValType
rewritten_then_body : Array[@lib.Instruction]
rewritten_else_body : Array[@lib.Instruction]
}
///|
priv struct DaeSingleResultControlPairApplicationEvidence {
result_is_void : Bool
then_body_length : Int32
else_body_length : Int32
}
///|
priv struct DaeIndexedInstructionRewriteApplicationEvidence {
target_count : Int32
rewrite_count : Int32
body_changed : Bool
}
///|
priv struct DaeRecursiveBodyRewriteApplicationEvidence {
applied_to_current_body : Bool
target_count : Int32
rewrite_count : Int32
body_changed : Bool
}
///|
priv struct DaeBodyRewriteFixpointEvidence {
max_iters : Int32
iteration_count : Int32
reached_limit : Bool
body_changed : Bool
}
///|
priv struct DaeSingleResultControlPairRepairEvidence {
pair : DaeSingleResultControlPairGcEvidence
application : DaeSingleResultControlPairApplicationEvidence
rewritten : @lib.Instruction
}
///|
priv struct DaeSingleBodyControlShapeEvidence {
control_kind : Int32
block_type : @lib.BlockType
catches : Array[@lib.Catch]
body : Array[@lib.Instruction]
}
///|
priv struct DaeSingleBodyControlRewriteEvidence {
shape : DaeSingleBodyControlShapeEvidence
replacement_block_type : @lib.BlockType
rewritten_body : Array[@lib.Instruction]
body_changed : Bool
}
///|
priv struct DaeSingleResultControlGcCandidateEvidence {
candidate : @lib.ValType
rewrite : DaeSingleBodyControlRewriteEvidence
}
///|
priv struct DaeTypeidxControlRewritePlan {
control_kind : Int32
params : Array[@lib.ValType]
results : Array[@lib.ValType]
body : Array[@lib.Instruction]
else_body : Array[@lib.Instruction]?
}
///|
priv struct DaeTypeidxControlRewriteApplication {
type_sec : @lib.TypeSec
rewritten : @lib.Instruction
}
///|
priv struct DaeTypeidxControlBodyRewriteApplication {
type_sec : @lib.TypeSec
body : Array[@lib.Instruction]
}
///|
priv enum DaeGcActualShapeKind {
DaeDirectGcActualShape
DaeBlockGcActualShape
DaeLoopGcActualShape
DaeIfGcActualShape
DaeSelectGcActualShape(DaeSelectValueSlices)
}
///|
priv struct DaeGcActualShapeDiscoveryEvidence {
start : Int32
end : Int32
kind : DaeGcActualShapeKind
}
///|
priv struct DaeGcActualTypeEvidence {
candidate : @lib.ValType
refined_candidate : @lib.ValType?
select : DaeGcSelectValueSlicesEvidence?
terminal_rewrite : @lib.Instruction?
}
///|
priv struct DaeControlResultGcEvidence {
candidates : Array[@lib.ValType?]
refined_candidates : Array[@lib.ValType?]
rewrites : Array[@lib.Instruction?]
body : Array[@lib.Instruction]
result_start : Int32
rewritten_body : Array[@lib.Instruction]
body_changed : Bool
saw_candidate : Bool
}
///|
priv struct DaeControlResultPairGcEvidence {
refined_results : Array[@lib.ValType]
rewritten_then_body : Array[@lib.Instruction]
rewritten_else_body : Array[@lib.Instruction]
then_body_changed : Bool
else_body_changed : Bool
saw_refinement : Bool
}
///|
priv struct DaeOptionalRefinedResultSlotsApplicationEvidence {
result_count : Int32
applied_count : Int32
next_results_length : Int32
refined_results_length : Int32
}
///|
priv struct DaeControlResultPairRefinementApplicationEvidence {
result_count : Int32
next_results_length : Int32
refined_results_length : Int32
saw_refinement : Bool
}
///|
priv struct DaeControlResultSingleRefinementApplicationEvidence {
result_count : Int32
next_results_length : Int32
refined_results_length : Int32
body_changed : Bool
saw_refinement : Bool
}
///|
priv struct DaeResultRewriteApplicationEvidence {
start : Int32
result_count : Int32
rewrite_count : Int32
body_changed : Bool
}
///|
priv struct DaeSelectValueRewriteApplicationEvidence {
left_idx : Int32
right_idx : Int32
select_idx : Int32
rewrite_count : Int32
body_changed : Bool
}
///|
priv struct DaeMultiResultIfPairGcRefinementEvidence {
then_slice : DaeControlResultSlice
else_slice : DaeControlResultSlice
pair : DaeControlResultPairGcEvidence
if_body_changed : Bool
}
///|
priv struct DaeMixedSingleResultReturnArmGcEvidence {
candidate : @lib.ValType
rewritten_body : Array[@lib.Instruction]?
body_changed : Bool
has_return : Bool
effect_free_fallthrough : Bool
}
///|
priv struct DaeMixedMultiResultReturnArmGcEvidence {
body : Array[@lib.Instruction]
result_start : Int32
flattened : Bool
has_return : Bool
effect_free_fallthrough : Bool
control_result : DaeControlResultGcEvidence?
}
///|
priv struct DaeEffectFreeFallthroughEvidence {
body : Array[@lib.Instruction]
body_length : Int32
has_observable_effect_or_branch : Bool
effect_free_fallthrough : Bool
}
///|
priv struct DaeMixedMultiResultReturnArmRewriteEvidence {
body : Array[@lib.Instruction]
body_changed : Bool
}
///|
priv struct DaeRewriteResult {
instrs : Array[@lib.Instruction]
calls_rewritten : Int32
ok : Bool
}
///|
priv struct DaeCallsiteRewritePlan {
callsite : DaeCallsiteValueSliceEvidence
operand_plans : Array[DaeOperandRemovalPlan]
scratch_local_types_by_param : Array[@lib.ValType?]
localized_params : Array[Bool]
}
///|
priv struct DaeCallsiteRewriteApplicationEvidence {
instrs : Array[@lib.Instruction]
new_call_idx : Int32
scratch_local_types : Array[@lib.ValType]
localized_params : Array[Bool]
localization_count : Int32
}
///|
priv struct DaeRemovedParamLocalProjectionEvidence {
valid : Bool
preserve_as_body_local : Bool
body_local_type : @lib.ValType?
}
///|
priv struct DaeRemovedParamCalleeRewriteEvidence {
kept_params : Array[@lib.ValType]
locals : @lib.Locals
body : Array[@lib.Instruction]
}
///|
priv struct DaeBoundaryParamBatchPlan {
def_idx : Int32
callee_abs : Int32
params : Array[@lib.ValType]
results : Array[@lib.ValType]
remove : Array[Bool]
const_params : Array[@lib.Instruction?]
local_use : DaeLocalUse
direct_call_count : Int32
direct_caller_defs : Array[Int32]
}
///|
priv struct DaeDroppedResultBatchPlan {
def_idx : Int32
callee_abs : Int32
params : Array[@lib.ValType]
direct_call_count : Int32
direct_caller_defs : Array[Int32]
}
///|
priv struct DaeGcParameterLubEvidence {
next_params : Array[@lib.ValType]
refined_params : Array[Bool]
complete : Bool
changed : Bool
}
///|
priv struct DaeGcParameterLubActualCollection {
candidates : Array[@lib.ValType?]
rejected : Array[Bool]
calls_seen : Int32
collect_ok : Bool
}
///|
priv struct DaeCallsiteLocalizationContext {
first_scratch_local : Int32
param_types : Array[@lib.ValType]
scratch_local_types : Array[@lib.ValType]
localized_params : Array[Bool]
}
///|
priv struct DaeLocalUse {
gets : Array[Bool]
writes : Array[Bool]
}
///| Fixed-module cache used only while collecting uniform actuals. A caller's
///|
/// local-use relation is body-static for the complete parameter batch.
priv struct DaeUniformResolutionCache {
local_uses : Array[DaeLocalUse?]
local_use_ready : Array[Bool]
local_use_scans : Array[Int32]?
forwarded_values : Array[Array[@lib.Instruction?]]
forwarded_ready : Array[Array[Bool]]
forwarded_resolving : Array[Array[Bool]]
forwarded_resolution_scans : Array[Int32]?
function_infos : Array[DaeFunctionInfo]?
stable_callsite_resolutions : Array[Int32]?
mut callsite_operands : @hashmap.HashMap[
(Int32, Int32),
DaeUniformCallsiteOperands,
]?
visited : Array[Bool]
}
///|
fn DaeUniformResolutionCache::new(
defined : Int32,
total : Int32,
local_use_scans~ : Array[Int32]? = None,
forwarded_resolution_scans~ : Array[Int32]? = None,
function_infos~ : Array[DaeFunctionInfo]? = None,
stable_callsite_resolutions~ : Array[Int32]? = None,
visited_allocations~ : Array[Int32]? = None,
) -> DaeUniformResolutionCache {
let forwarded_values : Array[Array[@lib.Instruction?]] = []
let forwarded_ready : Array[Array[Bool]] = []
let forwarded_resolving : Array[Array[Bool]] = []
for _def_idx = 0; _def_idx < defined; _def_idx = _def_idx + 1 {
forwarded_values.push([])
forwarded_ready.push([])
forwarded_resolving.push([])
}
match visited_allocations {
Some(allocations) => allocations[0] += 1
None => ()
}
{
local_uses: Array::make(defined, None),
local_use_ready: Array::make(defined, false),
local_use_scans,
forwarded_values,
forwarded_ready,
forwarded_resolving,
forwarded_resolution_scans,
function_infos,
stable_callsite_resolutions,
callsite_operands: None,
visited: Array::make(total, false),
}
}
///|
priv struct DaeLocalUsageEvidence {
target_local : @lib.LocalIdx
get_count : Int32
write_count : Int32
has_try_table : Bool
}
///|
priv struct DaeLocalReferenceExtentEvidence {
limit : Int32
max_referenced_local_slot : Int32
references_at_or_above_limit : Bool
}
///|
priv struct DaeLocalCopyTargetEvidence {
temp_local : @lib.LocalIdx
target_local : @lib.LocalIdx
protected_local_slots : Int32
}
///|
priv struct DaePassResult {
mod_out : @lib.Module
touched : Array[Bool]
changed : Bool
primary_def : Int32
}
///|
priv enum DaeInvalidationDomain {
DaeInvalidateCalls
DaeInvalidateSignature
DaeInvalidateResult
DaeInvalidateBody
DaeInvalidateControl
DaeInvalidateLocals
DaeInvalidateTypes
DaeInvalidateMetadata
DaeInvalidateNestedCleanup
} derive(Eq, Debug)
///|
#warnings("-unused_value")
priv struct DaeCommitEffects {
structurally_touched : Array[Bool]
worth_decided : Array[Bool]
worth_optimizing : Array[Bool]
parameter_changed : Array[Bool]
parameter_type_refined : Array[Bool]
result_changed : Array[Bool]
result_removed : Array[Bool]
body_materialized : Array[Bool]
body_localized : Array[Bool]
caller_drop_removed : Array[Bool]
caller_bottom_repaired : Array[Bool]
control_refinalization_required : Array[Bool]
local_projection_changed : Array[Bool]
candidates_produced : Int32
candidates_applied : Int32
effectful_operands_replayed : Int32
scratch_locals_added : Int32
scratch_locals_reused : Int32
type_entries_reused : Int32
type_entries_appended : Int32
selected_only_rewrites : Int32
guard_based_skips : Int32
type_section_changed : Bool
metadata_changed : Bool
invalidation_domains : Array[DaeInvalidationDomain]
decision_reasons : Array[DaeDecisionReason]
} derive(Eq, Debug)
///|
fn dae_commit_effects_empty(defined : Int32) -> DaeCommitEffects {
{
structurally_touched: Array::make(defined, false),
worth_decided: Array::make(defined, false),
worth_optimizing: Array::make(defined, false),
parameter_changed: Array::make(defined, false),
parameter_type_refined: Array::make(defined, false),
result_changed: Array::make(defined, false),
result_removed: Array::make(defined, false),
body_materialized: Array::make(defined, false),
body_localized: Array::make(defined, false),
caller_drop_removed: Array::make(defined, false),
caller_bottom_repaired: Array::make(defined, false),
control_refinalization_required: Array::make(defined, false),
local_projection_changed: Array::make(defined, false),
candidates_produced: 0,
candidates_applied: 0,
effectful_operands_replayed: 0,
scratch_locals_added: 0,
scratch_locals_reused: 0,
type_entries_reused: 0,
type_entries_appended: 0,
selected_only_rewrites: 0,
guard_based_skips: 0,
type_section_changed: false,
metadata_changed: false,
invalidation_domains: [],
decision_reasons: [],
}
}
///|
fn dae_commit_effects_parameter_change(
defined : Int32,
target_def : Int32,
structurally_touched_defs : Array[Int32],
body_materialized : Bool,
body_localized_callers : Array[Int32],
) -> DaeCommitEffects {
let effects = dae_commit_effects_empty(defined)
for def_idx in structurally_touched_defs {
if def_idx >= 0 && def_idx < defined {
effects.structurally_touched[def_idx] = true
effects.worth_decided[def_idx] = true
}
}
if target_def >= 0 && target_def < defined {
effects.worth_optimizing[target_def] = true
effects.parameter_changed[target_def] = true
effects.local_projection_changed[target_def] = true
effects.body_materialized[target_def] = body_materialized
}
for caller_def in body_localized_callers {
if caller_def >= 0 && caller_def < defined {
effects.worth_optimizing[caller_def] = true
effects.body_localized[caller_def] = true
}
}
{
..effects,
type_section_changed: true,
metadata_changed: true,
invalidation_domains: [
DaeInvalidateCalls,
DaeInvalidateSignature,
DaeInvalidateBody,
DaeInvalidateLocals,
DaeInvalidateTypes,
DaeInvalidateMetadata,
DaeInvalidateNestedCleanup,
],
decision_reasons: [DaeCandidateProduced],
}
}
///|
fn dae_commit_effects_with_structural_touched(
effects : DaeCommitEffects,
structurally_touched : Array[Bool],
) -> DaeCommitEffects {
{ ..effects, structurally_touched: structurally_touched.copy(), }
}
///|
fn dae_commit_effects_merge(
left : DaeCommitEffects,
right : DaeCommitEffects,
) -> DaeCommitEffects {
let defined = if left.structurally_touched.length() >=
right.structurally_touched.length() {
left.structurally_touched.length()
} else {
right.structurally_touched.length()
}
let merged = dae_commit_effects_empty(defined)
let mut idx = 0
while idx < defined {
let left_has = fn(values : Array[Bool]) {
idx < values.length() && values[idx]
}
let right_has = fn(values : Array[Bool]) {
idx < values.length() && values[idx]
}
merged.structurally_touched[idx] = left_has(left.structurally_touched) ||
right_has(right.structurally_touched)
merged.worth_decided[idx] = left_has(left.worth_decided) ||
right_has(right.worth_decided)
merged.worth_optimizing[idx] = left_has(left.worth_optimizing) ||
right_has(right.worth_optimizing)
merged.parameter_changed[idx] = left_has(left.parameter_changed) ||
right_has(right.parameter_changed)
merged.parameter_type_refined[idx] = left_has(left.parameter_type_refined) ||
right_has(right.parameter_type_refined)
merged.result_changed[idx] = left_has(left.result_changed) ||
right_has(right.result_changed)
merged.result_removed[idx] = left_has(left.result_removed) ||
right_has(right.result_removed)
merged.body_materialized[idx] = left_has(left.body_materialized) ||
right_has(right.body_materialized)
merged.body_localized[idx] = left_has(left.body_localized) ||
right_has(right.body_localized)
merged.caller_drop_removed[idx] = left_has(left.caller_drop_removed) ||
right_has(right.caller_drop_removed)
merged.caller_bottom_repaired[idx] = left_has(left.caller_bottom_repaired) ||
right_has(right.caller_bottom_repaired)
merged.control_refinalization_required[idx] = left_has(
left.control_refinalization_required,
) ||
right_has(right.control_refinalization_required)
merged.local_projection_changed[idx] = left_has(
left.local_projection_changed,
) ||
right_has(right.local_projection_changed)
idx += 1
}
let invalidation_domains = left.invalidation_domains.copy()
for domain in right.invalidation_domains {
if !invalidation_domains.contains(domain) {
invalidation_domains.push(domain)
}
}
let decision_reasons = left.decision_reasons.copy()
for reason in right.decision_reasons {
if !decision_reasons.contains(reason) {
decision_reasons.push(reason)
}
}
{
..merged,
candidates_produced: left.candidates_produced + right.candidates_produced,
candidates_applied: left.candidates_applied + right.candidates_applied,
effectful_operands_replayed: left.effectful_operands_replayed +
right.effectful_operands_replayed,
scratch_locals_added: left.scratch_locals_added + right.scratch_locals_added,
scratch_locals_reused: left.scratch_locals_reused +
right.scratch_locals_reused,
type_entries_reused: left.type_entries_reused + right.type_entries_reused,
type_entries_appended: left.type_entries_appended +
right.type_entries_appended,
selected_only_rewrites: left.selected_only_rewrites +
right.selected_only_rewrites,
guard_based_skips: left.guard_based_skips + right.guard_based_skips,
type_section_changed: left.type_section_changed ||
right.type_section_changed,
metadata_changed: left.metadata_changed || right.metadata_changed,
invalidation_domains,
decision_reasons,
}
}
///|
#warnings("-unused_field")
priv struct DaeCoreRunResult {
mod_out : @lib.Module
touched : Array[Bool]
changed : Bool
primary_def : Int32
effects : DaeCommitEffects
diagnostics : DaeDiagnostics
single_caller_throttle_consumed : Bool
}
///|
priv struct DaeCoreResultApplication {
mod_out : @lib.Module
touched : Array[Bool]
changed : Bool
applied : Bool
primary_def : Int32
effects : DaeCommitEffects
}
///|
priv struct DaeDroppedResultBatchApplication {
mod_out : @lib.Module
touched : Array[Bool]
changed : Bool
applied : Bool
primary_def : Int32
effects : DaeCommitEffects
post_graph : DaeCurrentBoundaryGraph?
}
///|
priv struct DaeEffectFrontierRunResult {
mod_out : @lib.Module
touched : Array[Bool]
changed : Bool
effects : DaeCommitEffects
diagnostics : DaeDiagnostics
}
///|
priv struct DaeExactLiteralWorklistApplication {
mod_out : @lib.Module
touched : Array[Bool]
changed : Bool
productive_defs : Array[Int32]
effects : DaeCommitEffects
}
///|
priv struct DaeDroppedResultPayoffChainCandidate {
wrapper_def : Int32
callee_def : Int32
payoff : Int32
}
///|
priv struct DaeBroadExactParamAdjacentCleanupCandidate {
root_def : Int32
first_def : Int32
second_def : Int32
payoff : Int32
}
///|
priv struct DaeBroadExactParamAdjacentCleanupApplication {
mod_out : @lib.Module
touched : Array[Bool]
candidate : DaeBroadExactParamAdjacentCleanupCandidate?
effects : DaeCommitEffects
}
///|
priv struct DaeForwardedParamNode {
callee_abs : Int32
param_idx : Int32
}
///|
#warnings("-unused_value")
priv enum DaeParameterFlowState {
DaeParameterUnseen
DaeParameterForwardedOnly
DaeParameterUniform(@lib.Instruction)
DaeParameterConflict
DaeParameterBlocked
} derive(Eq, Debug)
///|
priv struct DaeParameterFlowFact {
def_idx : Int32
param_idx : Int32
mut state : DaeParameterFlowState
}
///|
priv struct DaeParameterFlowSolution {
facts : Array[DaeParameterFlowFact]
}
///|
priv struct DaeForwardedParamSccApplication {
mod_out : @lib.Module
touched : Array[Bool]
defs : Array[Int32]
param_count : Int32
}
///|
#warnings("-unused_field")
priv struct DaeForwardedParamDeadCalleeApplication {
mod_out : @lib.Module
touched : Array[Bool]
sink_defs : Array[Int32]
cycle_defs : Array[Int32]
}
///|
priv struct DaeOriginalCallSummary {
call_counts : Array[Int32]
prior_call_counts : Array[Int32]
nonself_undropped_call_counts : Array[Int32]
active_nonself_call_counts : Array[Int32]
active_parameterized_nonself_call_counts : Array[Int32]
active_parameterized_result_nonself_call_counts : Array[Int32]
prior_undropped_call_counts : Array[Int32]
self_call_counts : Array[Int32]
}
///|
#warnings("-unused_field")
priv struct DaeOriginalBoundarySnapshot {
imported : Int32
defined : Int32
total : Int32
type_indices : Array[@lib.TypeIdx]
escaped : Array[Bool]
exposure : Array[DaeFunctionExposure]
param_counts : Array[Int32]
large_unreachable_prefixes : Array[Bool]
call_summary : DaeOriginalCallSummary
private_result_dead_suffix_call_counts : Array[Int32]
private_result_same_operand_call_counts : Array[Int32]
escaped_result_operand_call_facts : DaeOriginalEscapedResultOperandCallFacts
private_result_dead_suffix_calls_from_escaped_operand_callees : Array[Int32]
escaped_dead_suffix_call_counts : Array[Int32]
escaped_undropped_dead_suffix_call_counts : Array[Int32]
dead_suffix_other_calls : Array[Bool]
dead_suffix_undropped_self_calls : Array[Bool]
dead_suffix_dropped_self_calls : Array[Bool]
dead_suffix_block_undropped_self_calls : Array[Bool]
dead_suffix_dropped_self_call_full_param_forwards : Array[Bool]
dead_suffix_result_calls_from_undropped_self_callers : Array[Int32]
dead_suffix_result_calls_from_no_param_undropped_self_callers : Array[Int32]
dead_suffix_undropped_calls_from_parameterized_dropped_self_callers : Array[
Int32,
]
dead_suffix_dropped_calls_from_parameterized_self_callers : Array[Int32]
dead_suffix_dropped_calls_from_no_param_result_callers : Array[Int32]
dead_suffix_mixed_dropped_undropped_call_counts : Array[Int32]
dead_suffix_dropped_earlier_result_calls : Array[Bool]
later_dead_suffix_dropped_call_counts : Array[Int32]
dead_suffix_same_self_result_operand_calls : Array[Bool]
dead_suffix_undropped_self_call_param_operands : Array[Bool]
dead_suffix_calls_from_param_operand_self_result_callers : Array[Int32]
dead_suffix_result_other_calls : Array[Bool]
dead_suffix_self_recursive_void_side_calls : Array[Bool]
dead_suffix_self_recursive_result_side_calls : Array[Bool]
dead_suffix_escaped_result_call_facts : DaeOriginalDeadSuffixEscapedResultCallFacts
dead_suffix_mutual_calls : Array[Bool]
}
///|
#valtype
priv struct DaeStablePathNode {
parent : Int32
value : Int32
depth : Int32
first_child : Int32
}
///|
priv struct DaeStablePathWorkspace {
nodes : Array[DaeStablePathNode]
mut children : @hashmap.HashMap[UInt64, Int32]?
mut root_first_child : Int32
active_stamps : Array[Int32]
call_stamps : Array[Int32]
mut generation : Int32
}
///|
#valtype
priv struct DaeStablePath {
workspace : DaeStablePathWorkspace
tail : Int32
length : Int32
}
///|
fn DaeStablePath::empty() -> DaeStablePath {
{
workspace: {
nodes: [],
children: None,
root_first_child: -1,
active_stamps: [],
call_stamps: [],
generation: 0,
},
tail: -1,
length: 0,
}
}
///|
#inline
fn dae_stable_path_key(parent : Int32, value : Int32) -> UInt64 {
(parent.reinterpret_as_uint().to_uint64() << 32) |
value.reinterpret_as_uint().to_uint64()
}
///|
/// Shared lookup for canonical path construction and sparse rewrite selection.
#inline
fn dae_stable_path_child(
workspace : DaeStablePathWorkspace,
parent : Int32,
value : Int32,
) -> Int32? {
let first = if parent == -1 {
workspace.root_first_child
} else if parent >= 0 && parent < workspace.nodes.length() {
workspace.nodes[parent].first_child
} else {
-1
}
if first >= 0 && workspace.nodes[first].value == value {
return Some(first)
}
match workspace.children {
Some(children) => children.get(dae_stable_path_key(parent, value))
None => None
}
}
///|
fn DaeStablePath::append(self : DaeStablePath, value : Int32) -> DaeStablePath {
let tail = match dae_stable_path_child(self.workspace, self.tail, value) {
Some(node) => node
None => {
let node = self.workspace.nodes.length()
self.workspace.nodes.push({
parent: self.tail,
value,
depth: self.length + 1,
first_child: -1,
})
self.workspace.active_stamps.push(0)
self.workspace.call_stamps.push(0)
let first = if self.tail == -1 {
if self.workspace.root_first_child < 0 {
self.workspace.root_first_child = node
true
} else {
false
}
} else if self.tail >= 0 && self.tail < node {
let parent = self.workspace.nodes[self.tail]
if parent.first_child < 0 {
// Nodes are values. Replace the owned array slot; changing a copied
// parent value would leave the canonical first-child link unset.
self.workspace.nodes[self.tail] = { ..parent, first_child: node, }
true
} else {
false
}
} else {
false
}
if !first {
let children = match self.workspace.children {
Some(children) => children
None => {
let children = @hashmap.new()
self.workspace.children = Some(children)
children
}
}
children.set(dae_stable_path_key(self.tail, value), node)
}
node
}
}
{ workspace: self.workspace, tail, length: self.length + 1, }
}
///|
fn DaeStablePath::length(self : DaeStablePath) -> Int32 {
self.length
}
///|
fn DaeStablePath::append_to(self : DaeStablePath, out : Array[Int32]) -> Unit {
let base = out.length()
let length = self.length()
out.resize(base + length, 0)
let mut cursor = self.tail
let mut index = base + length - 1
while index >= base {
if cursor < 0 || cursor >= self.workspace.nodes.length() {
abort("dae stable path length disagrees with its links")
}
let node = self.workspace.nodes[cursor]
out[index] = node.value
cursor = node.parent
index -= 1
}
if cursor >= 0 {
abort("dae stable path length disagrees with its links")
}
}
///|
fn DaeStablePath::to_array(self : DaeStablePath) -> Array[Int32] {
let out = Array::new(capacity=self.length())
self.append_to(out)
out
}
///|
fn dae_stable_path_from_array(path : Array[Int32]) -> DaeStablePath {
let mut stable = DaeStablePath::empty()
for value in path {
stable = stable.append(value)
}
stable
}
///|
fn dae_stable_path_equal(left : DaeStablePath, right : DaeStablePath) -> Bool {
if left.length != right.length {
return false
}
if physical_equal(left.workspace, right.workspace) && left.tail == right.tail {
return true
}
let mut left_tail = left.tail
let mut right_tail = right.tail
let mut remaining = left.length
while remaining > 0 {
if left_tail < 0 ||
left_tail >= left.workspace.nodes.length() ||
right_tail < 0 ||
right_tail >= right.workspace.nodes.length() {
return false
}
let left_node = left.workspace.nodes[left_tail]
let right_node = right.workspace.nodes[right_tail]
if left_node.value != right_node.value {
return false
}
left_tail = left_node.parent
right_tail = right_node.parent
remaining -= 1
}
left_tail < 0 && right_tail < 0
}
///|
#valtype
priv struct DaeStableCallsite {
caller_def : Int32
path : DaeStablePath
callee_abs : Int32
active : Bool
dropped : Bool
tail : Bool
}
///|
priv struct DaeFunctionInfo {
mut stale : Bool
mut unused_params : Array[Bool]
calls : Array[DaeStableCallsite]
dropped_calls : Array[DaeStableCallsite]
mut has_tail_calls : Bool
tail_callees : Array[Int32]
loop_type_indices : Array[@lib.TypeIdx]
mut has_adjacent_local_set_get : Bool
mut has_unseen_calls : Bool
}
///|
priv struct DaeCurrentCallFacts {
direct_calls : Array[Int32]
tail_calls : Array[Bool]
direct_callers : Array[Array[Int32]]
dropped_calls : Array[Int32]
escaped : Array[Bool]
}
///|
priv struct DaeFunctionAnalysis {
call_facts : DaeCurrentCallFacts
function_infos : Array[DaeFunctionInfo]
exposure : Array[DaeFunctionExposure]
parameter_loop_free : Array[Bool]
}
///|
priv struct DaeCallBoundaryFacts {
escaped : Array[Bool]
tail_calls : Array[Bool]
direct_callers : Array[Array[Int32]]
direct_calls : Array[Int32]
dropped_calls : Array[Int32]
}
///|
priv struct DaeCallBoundaryCandidate {
owned : Bool
tail_callee : Bool
decision_reason : DaeDecisionReason
direct_call_count : Int32
dropped_call_count : Int32
direct_callers : Array[Int32]
has_self_direct_caller : Bool
allows_nonself_direct_rewrite : Bool
}
///|
priv struct DaeDirectCallerRewriteEvidence {
func : @lib.Func
calls_rewritten : Int32
changed : Bool
ok : Bool
}
///|
priv struct DaeDirectCallerScopeApplication {
funcs : Array[@lib.Func]
calls_rewritten : Int32
changed_defs : Array[Bool]
changed : Bool
ok : Bool
}
///|
priv enum DaeTargetCallsiteMode {
DaeTargetDirectCallOnly
DaeTargetDirectOrReturnCall
}
///|
priv struct DaeTargetCallsiteEvidence {
callee_abs : Int32
is_return_call : Bool
}
///|
priv struct DaeAdjacentParamForwardEvidence {
start : Int32
end : Int32
param_count : Int32
}
///|
priv struct DaeModuleBoundarySnapshot {
imported : Int32
defined : Int32
total : Int32
type_indices : Array[@lib.TypeIdx]
function_types : Array[@lib.FuncType?]
funcs : Array[@lib.Func]
call_facts : DaeCurrentCallFacts
boundary_facts : DaeCallBoundaryFacts
function_infos : Array[DaeFunctionInfo]
exposure : Array[DaeFunctionExposure]
parameter_loop_free : Array[Bool]
}
///|
priv struct DaeFunctionEpochs {
mut signature_epoch : Int32
mut body_epoch : Int32
}
///|
priv struct DaeCandidateEpoch {
def_idx : Int32
module_epoch : Int32
call_epoch : Int32
type_epoch : Int32
signature_epoch : Int32
body_epoch : Int32
}
///|
priv struct DaeCurrentBoundaryGraph {
mut mod_ : @lib.Module
mut snapshot : DaeModuleBoundarySnapshot
mut topology : DaeCurrentTopologyExtras
mut function_epochs : Array[DaeFunctionEpochs]
mut module_epoch : Int32
mut call_epoch : Int32
mut type_epoch : Int32
mut dependency_cache : DaeBoundaryDependencyGraph?
}
///|
#warnings("-unused_value")
priv struct DaeParameterForwardingEdge {
caller_def : Int32
caller_param_idx : Int32
callee_def : Int32
callee_param_idx : Int32
direct_tail : Bool
source_type : @lib.ValType
caller_signature_epoch : Int32
caller_body_epoch : Int32
callee_signature_epoch : Int32
callee_body_epoch : Int32
} derive(Eq, Debug)
///|
#warnings("-unused_field")
priv struct DaeBoundaryDependencyGraph {
callers_by_def : Array[Array[Int32]]
callees_by_def : Array[Array[Int32]]
return_callers_by_def : Array[Array[Int32]]
parameter_forwarding_edges : Array[DaeParameterForwardingEdge]
parameter_forwarders_by_def : Array[Array[Int32]]
uniform_value_forwarders_by_def : Array[Array[Int32]]
result_forwarders_by_def : Array[Array[Int32]]
result_dependencies_by_def : Array[Array[Int32]]
result_observers_by_def : Array[Array[Int32]]
tail_result_dependencies_by_def : Array[Array[Int32]]
type_dependencies_by_def : Array[Array[Int32]]
body_use_dependencies_by_def : Array[Array[Int32]]
nested_cleanup_dependencies_by_def : Array[Array[Int32]]
component_by_def : Array[Int32]
component_members : Array[Array[Int32]]
forwarding_component_by_def : Array[Int32]
forwarding_component_members : Array[Array[Int32]]
}
///|
priv enum DaeWorkFamily {
DaeUnusedParameterWork
DaeUniformActualWork
DaeForwardingWork
DaeLocalizationRetryWork
DaeGcParameterRefinementWork
DaeGcResultRefinementWork
DaeDroppedResultWork
DaeControlReconstructionWork
DaeTypeRepairRetryWork
DaePostCleanupBoundaryRevisitWork
DaeBodyCleanupWork
} derive(Eq, Debug)
///|
#warnings("-unused_value")
priv struct DaeWorkItem {
target_def : Int32
component_id : Int32
component_members : Array[Int32]
family : DaeWorkFamily
uniform_values : Array[@lib.Instruction?]
allow_immutable_global_get : Bool
allow_immediate_i32_suffix_fallback : Bool
expected_module_epoch : Int32
expected_call_epoch : Int32
expected_type_epoch : Int32
expected_signature_epoch : Int32
expected_body_epoch : Int32
} derive(Eq, Debug)
///|
#warnings("-unused_field")
priv enum DaeWorkOutcome {
DaeWorkNoChange(DaeDecisionReason)
DaeWorkDeferred(DaeDecisionReason)
DaeWorkRequeue(Array[DaeWorkItem])
DaeWorkCommitted(DaeCoreResultApplication)
}
///|
priv struct DaeBoundaryWorklist {
queue : Array[Int32]
queued : Array[Bool]
mut cursor : Int32
family_queue : Array[DaeWorkItem]
family_queued : Array[Array[Bool]]
mut family_cursor : Int32
}
///|
#warnings("-unused_value")
priv enum DaeParameterAction {
DaeKeepParameter
DaeRemovePureParameter
DaeRemoveParameterReplayEffects
DaeLocalizeParameterAndRetry
DaeMaterializeConstantParameter(@lib.Instruction)
DaeRefineParameterType(@lib.ValType)
} derive(Eq, Debug)
///|
#warnings("-unused_value")
priv enum DaeResultAction {
DaeKeepResult
DaeRemoveResult
DaeRefineResult(@lib.ValType)
} derive(Eq, Debug)
///|
#warnings("-unused_value")
priv struct DaeDirectCallIdentity {
caller_def : Int32
path : DaeStablePath
call_kind : DaeDirectCallKind
callee_abs : Int32
}
///|
fn dae_direct_call_identity_equal(
left : DaeDirectCallIdentity,
right : DaeDirectCallIdentity,
) -> Bool {
left.caller_def == right.caller_def &&
left.call_kind == right.call_kind &&
left.callee_abs == right.callee_abs &&
dae_stable_path_equal(left.path, right.path)
}
///|
fn dae_direct_call_identities_equal(
left : Array[DaeDirectCallIdentity],
right : Array[DaeDirectCallIdentity],
) -> Bool {
if left.length() != right.length() {
return false
}
for index in 0.. DaeCallBoundaryFacts {
{ escaped, tail_calls, direct_callers, direct_calls: [], dropped_calls: [], }
}
///|
fn dae_call_boundary_facts_from_current(
current : DaeCurrentCallFacts,
) -> DaeCallBoundaryFacts {
{
escaped: current.escaped,
tail_calls: current.tail_calls,
direct_callers: current.direct_callers,
direct_calls: current.direct_calls,
dropped_calls: current.dropped_calls,
}
}
///|
fn dae_collect_module_boundary_snapshot(
mod_ : @lib.Module,
perf~ : HotPerfSession? = None,
topology_out~ : DaeCurrentTopologyExtras? = None,
) -> DaeModuleBoundarySnapshot {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let total = imported + defined
let analysis_start_us = perf_start_timer_opt(
perf, "detail:dae:boundary-snapshot:function-analysis",
)
let analysis = dae_collect_current_function_analysis(
mod_,
total,
perf~,
topology_out~,
)
perf_stop_timer_opt(
perf, "detail:dae:boundary-snapshot:function-analysis", analysis_start_us,
)
let call_facts = analysis.call_facts
let type_indices_start_us = perf_start_timer_opt(
perf, "detail:dae:boundary-snapshot:type-indices",
)
let type_indices = dae_collect_func_type_indices(mod_)
perf_stop_timer_opt(
perf, "detail:dae:boundary-snapshot:type-indices", type_indices_start_us,
)
let function_types_start_us = perf_start_timer_opt(
perf, "detail:dae:boundary-snapshot:function-types",
)
let function_types = dae_snapshot_function_types(mod_, type_indices)
perf_stop_timer_opt(
perf, "detail:dae:boundary-snapshot:function-types", function_types_start_us,
)
{
imported,
defined,
total,
type_indices,
function_types,
funcs: match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => []
},
call_facts,
boundary_facts: dae_call_boundary_facts_from_current(call_facts),
function_infos: analysis.function_infos,
exposure: analysis.exposure,
parameter_loop_free: analysis.parameter_loop_free,
}
}
///|
fn dae_collect_defined_function_types(
mod_ : @lib.Module,
imported : Int32,
defined : Int32,
type_indices : Array[@lib.TypeIdx],
) -> Array[@lib.FuncType?] {
if defined <= 0 {
return []
}
let function_types = dae_snapshot_function_types(mod_, type_indices)
let out : Array[@lib.FuncType?] = []
let mut def_idx = 0
while def_idx < defined {
out.push(
match function_types.get(imported + def_idx) {
Some(signature) => signature
None => None
},
)
def_idx += 1
}
out
}
///|
fn dae_current_call_facts_equal(
left : DaeCurrentCallFacts,
right : DaeCurrentCallFacts,
) -> Bool {
left.direct_calls == right.direct_calls &&
left.tail_calls == right.tail_calls &&
left.direct_callers == right.direct_callers &&
left.dropped_calls == right.dropped_calls &&
left.escaped == right.escaped
}
///|
fn dae_make_function_epochs(
count : Int32,
signature_epoch : Int32,
body_epoch : Int32,
) -> Array[DaeFunctionEpochs] {
let epochs : Array[DaeFunctionEpochs] = []
let mut def_idx = 0
while def_idx < count {
epochs.push({ signature_epoch, body_epoch, })
def_idx += 1
}
epochs
}
///|
fn dae_current_topology_extras_shape_matches(
topology : DaeCurrentTopologyExtras,
total : Int32,
) -> Bool {
topology.direct_return_callers.length() == total &&
topology.direct_return_callees_by_caller.length() == total &&
topology.has_outgoing_call_indirect.length() == total &&
topology.has_outgoing_call_ref.length() == total &&
topology.has_outgoing_return_call.length() == total &&
topology.has_outgoing_return_call_indirect.length() == total &&
topology.has_outgoing_return_call_ref.length() == total
}
///|
fn dae_current_boundary_graph_new(
mod_ : @lib.Module,
topology~ : DaeCurrentTopologyExtras? = None,
topology_walk_visits~ : Array[Int32]? = None,
) -> DaeCurrentBoundaryGraph {
ignore(topology_walk_visits)
let total = dae_imported_func_count(mod_) + dae_defined_func_count(mod_)
let supplied = match topology {
Some(value) if dae_current_topology_extras_shape_matches(value, total) =>
Some(value)
_ => None
}
let resolved_topology = match supplied {
Some(value) => value
None => dae_empty_topology_extras(total)
}
let snapshot = dae_collect_module_boundary_snapshot(
mod_,
topology_out=if supplied is None { Some(resolved_topology) } else { None },
)
{
mod_,
snapshot,
topology: resolved_topology,
function_epochs: dae_make_function_epochs(snapshot.defined, 0, 0),
module_epoch: 0,
call_epoch: 0,
type_epoch: 0,
dependency_cache: None,
}
}
///| Boundary-only graph for the typed-loop safe parameter/result transaction.
///| These batches consume snapshot call, exposure, type, and loop facts but no
///|
/// direct-return or indirect/reference topology extras.
fn dae_typed_batch_boundary_graph_new(
mod_ : @lib.Module,
perf~ : HotPerfSession? = None,
) -> DaeCurrentBoundaryGraph {
let snapshot = dae_collect_module_boundary_snapshot(mod_, perf~)
{
mod_,
snapshot,
topology: dae_empty_topology_extras(snapshot.total),
function_epochs: dae_make_function_epochs(snapshot.defined, 0, 0),
module_epoch: 0,
call_epoch: 0,
type_epoch: 0,
dependency_cache: None,
}
}
///|
fn dae_boundary_candidate_epoch(
graph : DaeCurrentBoundaryGraph,
def_idx : Int32,
) -> DaeCandidateEpoch {
if def_idx < 0 || def_idx >= graph.function_epochs.length() {
return {
def_idx,
module_epoch: graph.module_epoch,
call_epoch: graph.call_epoch,
type_epoch: graph.type_epoch,
signature_epoch: -1,
body_epoch: -1,
}
}
let epochs = graph.function_epochs[def_idx]
{
def_idx,
module_epoch: graph.module_epoch,
call_epoch: graph.call_epoch,
type_epoch: graph.type_epoch,
signature_epoch: epochs.signature_epoch,
body_epoch: epochs.body_epoch,
}
}
///|
fn dae_candidate_epoch_is_current(
graph : DaeCurrentBoundaryGraph,
candidate : DaeCandidateEpoch,
) -> Bool {
if candidate.def_idx < 0 ||
candidate.def_idx >= graph.function_epochs.length() {
return false
}
let epochs = graph.function_epochs[candidate.def_idx]
candidate.module_epoch == graph.module_epoch &&
candidate.call_epoch == graph.call_epoch &&
candidate.type_epoch == graph.type_epoch &&
candidate.signature_epoch == epochs.signature_epoch &&
candidate.body_epoch == epochs.body_epoch
}
///|
fn dae_current_boundary_graph_refresh_full(
graph : DaeCurrentBoundaryGraph,
mod_ : @lib.Module,
topology_walk_visits~ : Array[Int32]? = None,
) -> Unit {
let previous_mod = graph.mod_
let previous = graph.snapshot
ignore(topology_walk_visits)
let total = dae_imported_func_count(mod_) + dae_defined_func_count(mod_)
let next_topology = dae_empty_topology_extras(total)
let next = dae_collect_module_boundary_snapshot(
mod_,
topology_out=Some(next_topology),
)
let shape_changed = previous.imported != next.imported ||
previous.defined != next.defined ||
previous.total != next.total ||
graph.function_epochs.length() != next.defined
let mut changed = shape_changed
if shape_changed {
graph.function_epochs = dae_make_function_epochs(
next.defined,
graph.module_epoch + 1,
graph.module_epoch + 1,
)
} else {
let mut def_idx = 0
while def_idx < next.defined {
let previous_abs = previous.imported + def_idx
let next_abs = next.imported + def_idx
let signature_changed = previous_abs < 0 ||
previous_abs >= previous.function_types.length() ||
next_abs < 0 ||
next_abs >= next.function_types.length() ||
previous.function_types[previous_abs] != next.function_types[next_abs]
if signature_changed {
graph.function_epochs[def_idx].signature_epoch += 1
changed = true
}
let body_changed = def_idx >= previous.funcs.length() ||
def_idx >= next.funcs.length() ||
!dae_snapshot_func_unchanged(
previous.funcs[def_idx],
next.funcs[def_idx],
)
if body_changed {
graph.function_epochs[def_idx].body_epoch += 1
changed = true
}
def_idx += 1
}
}
if !dae_current_call_facts_equal(previous.call_facts, next.call_facts) {
graph.call_epoch += 1
changed = true
}
if previous_mod.type_sec != mod_.type_sec ||
previous_mod.func_sec != mod_.func_sec {
graph.type_epoch += 1
changed = true
}
if changed {
graph.module_epoch += 1
}
graph.dependency_cache = None
graph.mod_ = mod_
graph.snapshot = next
graph.topology = next_topology
}
///|
fn dae_empty_topology_extras(total_funcs : Int32) -> DaeCurrentTopologyExtras {
{
direct_return_callers: dae_make_direct_callers(total_funcs),
direct_return_callees_by_caller: dae_make_direct_callers(total_funcs),
adjacency_membership: @hashmap.new(),
has_outgoing_call_indirect: Array::make(total_funcs, false),
has_outgoing_call_ref: Array::make(total_funcs, false),
has_outgoing_return_call: Array::make(total_funcs, false),
has_outgoing_return_call_indirect: Array::make(total_funcs, false),
has_outgoing_return_call_ref: Array::make(total_funcs, false),
}
}
///|
#valtype
priv struct DaeAdjacencyKey {
kind : Int32
row : Int32
value : Int32
} derive(Eq, Hash)
///|
fn DaeAdjacencyKey::new(
kind : Int32,
row : Int32,
value : Int32,
) -> DaeAdjacencyKey {
{ kind, row, value, }
}
///|
fn dae_adjacency_add_unique(
rows : Array[Array[Int32]],
row : Int32,
value : Int32,
kind : Int32,
membership : @hashmap.HashMap[DaeAdjacencyKey, Bool],
) -> Unit {
if row < 0 || row >= rows.length() {
return
}
let key = DaeAdjacencyKey::new(kind, row, value)
if membership.get(key) is None {
membership.set(key, true)
rows[row].push(value)
}
}
///|
fn dae_scan_topology_extras_instrs(
instrs : Array[@lib.Instruction],
caller_abs : Int32,
topology : DaeCurrentTopologyExtras,
active : Bool,
walk_visits~ : Array[Int32]? = None,
) -> Unit {
let mut idx = 0
let mut current_active = active
while idx < instrs.length() {
match walk_visits {
Some(count) => count[0] += 1
None => ()
}
if current_active && dae_instr_is_unreachable_root(instrs[idx]) {
current_active = false
}
match instrs[idx] {
@lib.ReturnCall(func_idx) =>
if current_active {
if caller_abs >= 0 &&
caller_abs < topology.has_outgoing_return_call.length() {
topology.has_outgoing_return_call[caller_abs] = true
}
let slot = dae_func_idx_slot(func_idx)
if slot >= 0 && slot < topology.direct_return_callers.length() {
dae_adjacency_add_unique(
topology.direct_return_callers,
slot,
caller_abs,
0,
topology.adjacency_membership,
)
if caller_abs >= 0 &&
caller_abs < topology.direct_return_callees_by_caller.length() {
dae_adjacency_add_unique(
topology.direct_return_callees_by_caller,
caller_abs,
slot,
1,
topology.adjacency_membership,
)
}
}
}
@lib.ReturnCallIndirect(_, _) =>
if current_active &&
caller_abs >= 0 &&
caller_abs < topology.has_outgoing_return_call_indirect.length() {
topology.has_outgoing_return_call_indirect[caller_abs] = true
}
@lib.ReturnCallRef(_) =>
if current_active &&
caller_abs >= 0 &&
caller_abs < topology.has_outgoing_return_call_ref.length() {
topology.has_outgoing_return_call_ref[caller_abs] = true
}
@lib.CallIndirect(_, _) =>
if current_active &&
caller_abs >= 0 &&
caller_abs < topology.has_outgoing_call_indirect.length() {
topology.has_outgoing_call_indirect[caller_abs] = true
}
@lib.CallRef(_) =>
if current_active &&
caller_abs >= 0 &&
caller_abs < topology.has_outgoing_call_ref.length() {
topology.has_outgoing_call_ref[caller_abs] = true
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_scan_topology_extras_instrs(
body,
caller_abs,
topology,
current_active,
walk_visits~,
)
@lib.Try(_, @lib.Expr(body), catches, _) => {
dae_scan_topology_extras_instrs(
body,
caller_abs,
topology,
current_active,
walk_visits~,
)
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(_, @lib.Expr(catch_body))
| @lib.LegacyCatchAll(@lib.Expr(catch_body)) =>
dae_scan_topology_extras_instrs(
catch_body,
caller_abs,
topology,
current_active,
walk_visits~,
)
}
}
}
@lib.If(_, then_body, Some(else_body)) => {
dae_scan_topology_extras_instrs(
then_body,
caller_abs,
topology,
current_active,
walk_visits~,
)
dae_scan_topology_extras_instrs(
else_body,
caller_abs,
topology,
current_active,
walk_visits~,
)
}
@lib.If(_, then_body, None) =>
dae_scan_topology_extras_instrs(
then_body,
caller_abs,
topology,
current_active,
walk_visits~,
)
_ => ()
}
idx += 1
}
}
///|
fn dae_collect_current_topology_extras(
mod_ : @lib.Module,
snapshot : DaeModuleBoundarySnapshot,
walk_visits~ : Array[Int32]? = None,
) -> DaeCurrentTopologyExtras {
let topology = dae_empty_topology_extras(snapshot.total)
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
let mut caller_def = 0
while caller_def < funcs.length() {
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
dae_scan_topology_extras_instrs(
body,
dae_abs_func_idx(snapshot.imported, caller_def),
topology,
true,
walk_visits~,
)
caller_def += 1
}
}
None => ()
}
topology
}
///|
fn dae_incoming_parameter_liveness_fail_closed(
param_count : Int32,
) -> Array[Bool] {
Array::make(param_count, true)
}
///|
fn dae_incoming_parameter_liveness(
mod_ : @lib.Module,
callee_abs : Int32,
param_count : Int32,
syntactic_use~ : DaeLocalUse? = None,
liveness_work~ : Array[Int32]? = None,
) -> Array[Bool] {
match liveness_work {
Some(work) => work[0] += 1
None => ()
}
if param_count <= 0 {
return []
}
let analyzed_live = match
dae_fast_param_entry_liveness(mod_, callee_abs, param_count) {
Some(live) => Some(live)
None => dae_hot_param_entry_liveness(mod_, callee_abs, param_count)
}
match analyzed_live {
Some(live) => {
// HOT may return a longer array when locals exist; only parameters matter.
// Preserve a syntactic read of an unwritten parameter as authoritative
// incoming use. This fail-closed supplement covers nested result carriers
// that the current HOT projection can flatten away, while still allowing
// write-before-read parameters to be removed when HOT proves them dead.
// Supplied facts belong to this unchanged function and still contain
// syntactic gets; callers replace those gets only after this returns.
let syntactic_use = match syntactic_use {
Some(usage) => Some(usage)
None =>
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
let def_idx = callee_abs - dae_imported_func_count(mod_)
if def_idx >= 0 && def_idx < funcs.length() {
let @lib.Func(locals, _) = funcs[def_idx]
match liveness_work {
Some(work) => work[1] += 1
None => ()
}
Some(
dae_local_use_for_callee(
funcs[def_idx],
param_count + locals.length(),
callee_abs,
param_count,
),
)
} else {
None
}
}
None => None
}
}
let out = Array::make(param_count, true)
let mut idx = 0
while idx < param_count && idx < live.length() {
let syntactic_live = match syntactic_use {
Some(usage) => usage.gets[idx] && !usage.writes[idx]
None => false
}
out[idx] = live[idx] || syntactic_live
idx += 1
}
out
}
None => dae_incoming_parameter_liveness_fail_closed(param_count)
}
}
///|
fn dae_collect_incoming_param_live_for_module(
mod_ : @lib.Module,
snapshot : DaeModuleBoundarySnapshot,
) -> Array[Array[Bool]] {
let out : Array[Array[Bool]] = []
let mut def_idx = 0
while def_idx < snapshot.defined {
let callee_abs = dae_abs_func_idx(snapshot.imported, def_idx)
let param_count = match snapshot.function_types[callee_abs] {
Some(@lib.FuncType(params, _)) => params.length()
None => 0
}
out.push(dae_incoming_parameter_liveness(mod_, callee_abs, param_count))
def_idx += 1
}
out
}
///|
fn dae_collect_incoming_param_live_body_epochs(
graph : DaeCurrentBoundaryGraph,
) -> Array[Int32] {
let out : Array[Int32] = []
let mut def_idx = 0
while def_idx < graph.snapshot.defined {
if def_idx < graph.function_epochs.length() {
out.push(graph.function_epochs[def_idx].body_epoch)
} else {
out.push(-1)
}
def_idx += 1
}
out
}
///|
fn dae_boundary_facts_from_snapshots(
original : DaeOriginalBoundarySnapshot,
graph : DaeCurrentBoundaryGraph,
) -> DaeBoundaryFacts {
{
original,
graph,
incoming_param_live_by_def: [],
incoming_param_live_body_epochs: [],
}
}
///|
fn dae_boundary_facts_new(mod_ : @lib.Module) -> DaeBoundaryFacts {
let original = dae_collect_original_boundary_snapshot(mod_)
let graph = dae_current_boundary_graph_new(mod_)
{
original,
graph,
incoming_param_live_by_def: dae_collect_incoming_param_live_for_module(
mod_,
graph.snapshot,
),
incoming_param_live_body_epochs: dae_collect_incoming_param_live_body_epochs(
graph,
),
}
}
///|
#warnings("-unused_value")
fn dae_boundary_facts_refresh_current(
facts : DaeBoundaryFacts,
mod_ : @lib.Module,
) -> Unit {
dae_current_boundary_graph_refresh(facts.graph, mod_)
// Original exposure remains immutable after the first snapshot.
// Topology is authoritative only on facts.graph.topology after refresh.
facts.incoming_param_live_by_def = dae_collect_incoming_param_live_for_module(
mod_,
facts.graph.snapshot,
)
facts.incoming_param_live_body_epochs = dae_collect_incoming_param_live_body_epochs(
facts.graph,
)
}
///|
/// Incoming-boundary ownership for parameter and parameter-type changes.
/// Rejects only when the target itself has an unseen incoming call or other
/// exact exposure. Outgoing call_indirect / call_ref / return_call* do not
/// block parameter removal.
fn dae_can_change_parameters(
facts : DaeBoundaryFacts,
func_abs : Int32,
) -> Bool {
let exposure = dae_boundary_facts_exposure(facts)
if func_abs < 0 || func_abs >= exposure.length() {
return false
}
if dae_function_exposure_blocks_boundary(exposure[func_abs]) {
return false
}
if func_abs < facts.graph.snapshot.call_facts.escaped.length() &&
facts.graph.snapshot.call_facts.escaped[func_abs] {
return false
}
match
dae_call_boundary_candidate(facts.graph.snapshot.boundary_facts, func_abs) {
Some(candidate) => candidate.owned
None => false
}
}
///|
fn dae_can_change_family_signature(
facts : DaeBoundaryFacts,
func_abs : Int32,
) -> Bool {
if dae_can_change_parameters(facts, func_abs) {
return true
}
let exposure = dae_boundary_facts_exposure(facts)
if func_abs < 0 ||
func_abs >= exposure.length() ||
dae_function_exposure_blocks_boundary(exposure[func_abs]) ||
(
func_abs < facts.graph.snapshot.call_facts.escaped.length() &&
facts.graph.snapshot.call_facts.escaped[func_abs]
) {
return false
}
dae_collect_target_call_identities(facts.graph.snapshot, func_abs).length() ==
0
}
///|
fn dae_can_change_parameters_reason(
facts : DaeBoundaryFacts,
func_abs : Int32,
) -> DaeDecisionReason {
let exposure = dae_boundary_facts_exposure(facts)
if func_abs < 0 || func_abs >= exposure.length() {
return DaeVisibleBoundary
}
if dae_function_exposure_blocks_boundary(exposure[func_abs]) {
return dae_function_exposure_primary_reason(exposure[func_abs])
}
if func_abs < facts.graph.snapshot.call_facts.escaped.length() &&
facts.graph.snapshot.call_facts.escaped[func_abs] {
return DaeVisibleBoundary
}
match
dae_call_boundary_candidate(facts.graph.snapshot.boundary_facts, func_abs) {
Some(candidate) =>
if candidate.owned {
DaeCandidateProduced
} else {
candidate.decision_reason
}
None => DaeVisibleBoundary
}
}
///|
/// Dropped-result legality. Requires owned incoming boundaries, then rejects
/// when the function itself has any outgoing tail, is a direct return_call
/// callee, or existing dropped-result observers do not all drop the result.
fn dae_can_remove_function_result_with_exposure(
exposure : Array[DaeFunctionExposure],
graph : DaeCurrentBoundaryGraph,
func_abs : Int32,
) -> Bool {
if func_abs < 0 || func_abs >= exposure.length() {
return false
}
if dae_function_exposure_blocks_boundary(exposure[func_abs]) {
return false
}
if func_abs < graph.snapshot.call_facts.escaped.length() &&
graph.snapshot.call_facts.escaped[func_abs] {
return false
}
match dae_call_boundary_candidate(graph.snapshot.boundary_facts, func_abs) {
Some(candidate) if candidate.owned => ()
_ => return false
}
let topology = graph.topology
if func_abs < topology.has_outgoing_return_call.length() &&
topology.has_outgoing_return_call[func_abs] {
return false
}
if func_abs < topology.has_outgoing_return_call_indirect.length() &&
topology.has_outgoing_return_call_indirect[func_abs] {
return false
}
if func_abs < topology.has_outgoing_return_call_ref.length() &&
topology.has_outgoing_return_call_ref[func_abs] {
return false
}
if func_abs < graph.snapshot.call_facts.tail_calls.length() &&
graph.snapshot.call_facts.tail_calls[func_abs] {
return false
}
match
dae_dropped_result_candidate_from_boundary_facts(
graph.snapshot.boundary_facts,
func_abs,
) {
Some(candidate) => candidate.owned_dropped_result_candidate
None => false
}
}
///|
fn dae_can_remove_function_result(
facts : DaeBoundaryFacts,
func_abs : Int32,
) -> Bool {
dae_can_remove_function_result_with_exposure(
dae_boundary_facts_exposure(facts),
facts.graph,
func_abs,
)
}
///|
#warnings("-unused_value")
fn dae_can_remove_function_result_reason(
facts : DaeBoundaryFacts,
func_abs : Int32,
) -> DaeDecisionReason {
if !dae_can_change_parameters(facts, func_abs) {
return dae_can_change_parameters_reason(facts, func_abs)
}
let topology = dae_boundary_facts_topology(facts)
if func_abs < topology.has_outgoing_return_call.length() &&
topology.has_outgoing_return_call[func_abs] {
return DaeDirectTailDependency
}
if func_abs < topology.has_outgoing_return_call_indirect.length() &&
topology.has_outgoing_return_call_indirect[func_abs] {
return DaeIndirectTailDependency
}
if func_abs < topology.has_outgoing_return_call_ref.length() &&
topology.has_outgoing_return_call_ref[func_abs] {
return DaeReferenceTailDependency
}
if func_abs < facts.graph.snapshot.call_facts.tail_calls.length() &&
facts.graph.snapshot.call_facts.tail_calls[func_abs] {
return DaeDirectTailDependency
}
match
dae_dropped_result_candidate_from_boundary_facts(
facts.graph.snapshot.boundary_facts,
func_abs,
) {
Some(candidate) if candidate.owned_dropped_result_candidate =>
DaeCandidateProduced
Some(_) => DaeVisibleBoundary
None => DaeVisibleBoundary
}
}
///|
/// Compatibility alias: parameter ownership only (not result-tail legality).
fn dae_can_change_function_boundary(
facts : DaeBoundaryFacts,
func_abs : Int32,
) -> Bool {
dae_can_change_parameters(facts, func_abs)
}
///|
#warnings("-unused_value")
fn dae_can_change_function_boundary_reason(
facts : DaeBoundaryFacts,
func_abs : Int32,
) -> DaeDecisionReason {
dae_can_change_parameters_reason(facts, func_abs)
}
///|
#warnings("-unused_value")
fn dae_is_incoming_parameter_live(
facts : DaeBoundaryFacts,
func_abs : Int32,
param_idx : Int32,
) -> Bool {
match dae_def_idx_of_abs(facts.graph.snapshot.imported, func_abs) {
None => true
Some(def_idx) => {
if def_idx < 0 ||
def_idx >= facts.incoming_param_live_by_def.length() ||
param_idx < 0 {
return true
}
let live = facts.incoming_param_live_by_def[def_idx]
if param_idx >= live.length() {
return true
}
live[param_idx]
}
}
}
///|
fn dae_dependency_adjacency_new(defined : Int32) -> Array[Array[Int32]] {
let out : Array[Array[Int32]] = []
let mut def_idx = 0
while def_idx < defined {
out.push([])
def_idx += 1
}
out
}
///|
fn dae_dependency_adjacency_copy(
source : Array[Array[Int32]],
) -> Array[Array[Int32]] {
let out : Array[Array[Int32]] = []
for row in source {
out.push(row.copy())
}
out
}
///|
fn dae_dependency_adjacency_union(
left : Array[Array[Int32]],
right : Array[Array[Int32]],
) -> Array[Array[Int32]] {
let count = if left.length() > right.length() {
left.length()
} else {
right.length()
}
let out = dae_dependency_adjacency_new(count)
let membership : @hashmap.HashMap[DaeAdjacencyKey, Bool] = @hashmap.new()
let mut def_idx = 0
while def_idx < count {
if def_idx < left.length() {
for dependency in left[def_idx] {
dae_adjacency_add_unique(out, def_idx, dependency, 0, membership)
}
}
if def_idx < right.length() {
for dependency in right[def_idx] {
dae_adjacency_add_unique(out, def_idx, dependency, 0, membership)
}
}
def_idx += 1
}
out
}
///|
fn dae_boundary_scc_visit(
node : Int32,
edges : Array[Array[Int32]],
next_index : Array[Int32],
indices : Array[Int32],
lowlinks : Array[Int32],
stack : Array[Int32],
on_stack : Array[Bool],
raw_component_by_def : Array[Int32],
raw_component_count : Array[Int32],
) -> Unit {
indices[node] = next_index[0]
lowlinks[node] = next_index[0]
next_index[0] += 1
stack.push(node)
on_stack[node] = true
for dependency in edges[node] {
if dependency < 0 || dependency >= edges.length() {
continue
}
if indices[dependency] < 0 {
dae_boundary_scc_visit(
dependency, edges, next_index, indices, lowlinks, stack, on_stack, raw_component_by_def,
raw_component_count,
)
if lowlinks[dependency] < lowlinks[node] {
lowlinks[node] = lowlinks[dependency]
}
} else if on_stack[dependency] && indices[dependency] < lowlinks[node] {
lowlinks[node] = indices[dependency]
}
}
if lowlinks[node] != indices[node] {
return
}
while stack.length() > 0 {
let component_def = stack.pop().unwrap()
on_stack[component_def] = false
raw_component_by_def[component_def] = raw_component_count[0]
if component_def == node {
break
}
}
raw_component_count[0] += 1
}
///|
fn dae_boundary_components(
callees_by_def : Array[Array[Int32]],
) -> (Array[Int32], Array[Array[Int32]]) {
let defined = callees_by_def.length()
let indices = Array::make(defined, -1)
let lowlinks = Array::make(defined, -1)
let on_stack = Array::make(defined, false)
let stack : Array[Int32] = []
let raw_component_by_def = Array::make(defined, -1)
let next_index = [0]
let raw_component_count = [0]
let mut def_idx = 0
while def_idx < defined {
if indices[def_idx] < 0 {
dae_boundary_scc_visit(
def_idx, callees_by_def, next_index, indices, lowlinks, stack, on_stack,
raw_component_by_def, raw_component_count,
)
}
def_idx += 1
}
let normalized_by_raw = Array::make(raw_component_count[0], -1)
let component_by_def = Array::make(defined, -1)
let component_members : Array[Array[Int32]] = []
def_idx = 0
while def_idx < defined {
let raw_component = raw_component_by_def[def_idx]
if normalized_by_raw[raw_component] < 0 {
normalized_by_raw[raw_component] = component_members.length()
component_members.push([])
}
let component_id = normalized_by_raw[raw_component]
component_by_def[def_idx] = component_id
component_members[component_id].push(def_idx)
def_idx += 1
}
(component_by_def, component_members)
}
///|
priv struct DaeForwardingMembership {
edges : @hashmap.HashMap[(Int32, Int32, Int32, Int32, Bool), Bool]
callers : @hashmap.HashMap[(Int32, Int32), Bool]
}
///|
fn DaeForwardingMembership::new(
edges : Array[DaeParameterForwardingEdge],
callers : Array[Array[Int32]],
) -> DaeForwardingMembership {
let edge_keys : @hashmap.HashMap[(Int32, Int32, Int32, Int32, Bool), Bool] = @hashmap.new()
for edge in edges {
edge_keys.set(
(
edge.caller_def,
edge.caller_param_idx,
edge.callee_def,
edge.callee_param_idx,
edge.direct_tail,
),
true,
)
}
let caller_keys : @hashmap.HashMap[(Int32, Int32), Bool] = @hashmap.new()
for target in 0.. 4 {
for caller in callers[target] {
caller_keys.set((target, caller), true)
}
}
}
{ edges: edge_keys, callers: caller_keys, }
}
///|
fn dae_forwarding_func_type(
mod_ : @lib.Module,
indices : Array[@lib.TypeIdx],
abs : Int32,
snapshot : Array[@lib.FuncType?]?,
resolutions : Array[Int32]?,
) -> @lib.FuncType? {
match snapshot {
Some(types) =>
if abs >= 0 && abs < types.length() {
types[abs]
} else {
None
}
None => {
match resolutions {
Some(count) => count[0] += 1
None => ()
}
dae_resolve_func_type(mod_, indices, abs)
}
}
}
///|
fn dae_record_parameter_forwarding_dependency(
instrs : Array[@lib.Instruction],
call_idx : Int32,
entry_stack_values : Int32,
current : @lib.Module,
caller_def : Int32,
caller_param_count : Int32,
type_indices : Array[@lib.TypeIdx],
target_abs : Int32,
direct_tail : Bool,
function_epochs : Array[DaeFunctionEpochs],
edges : Array[DaeParameterForwardingEdge],
out : Array[Array[Int32]],
membership : DaeForwardingMembership,
function_types~ : Array[@lib.FuncType?]? = None,
type_resolutions~ : Array[Int32]? = None,
edge_probes~ : Array[Int32]? = None,
) -> Unit {
let imported = dae_imported_func_count(current)
let target_def = target_abs - imported
if target_def < 0 ||
target_def >= out.length() ||
caller_def < 0 ||
caller_def >= function_epochs.length() ||
target_def >= function_epochs.length() {
return
}
let target_params = match
dae_forwarding_func_type(
current, type_indices, target_abs, function_types, type_resolutions,
) {
Some(@lib.FuncType(params, _)) => params
None => return
}
let caller_params = match
dae_forwarding_func_type(
current,
type_indices,
imported + caller_def,
function_types,
type_resolutions,
) {
Some(@lib.FuncType(params, _)) => params
None => return
}
if target_params.length() == 0 {
return
}
let evidence = match
dae_callsite_value_slice_evidence(
instrs,
call_idx,
target_params.length(),
entry_stack_values,
current,
type_indices,
) {
Some(value) => value
None => return
}
let mut param_idx = 0
while param_idx < target_params.length() {
match dae_callsite_single_instruction_actual(evidence, param_idx, instrs) {
Some(actual) =>
match actual.instr {
@lib.LocalGet(local_idx) => {
let local_slot = dae_local_idx_slot(local_idx)
if local_slot >= 0 &&
local_slot < caller_param_count &&
local_slot < caller_params.length() &&
caller_params[local_slot] == target_params[param_idx] {
let key = (
caller_def, local_slot, target_def, param_idx, direct_tail,
)
match edge_probes {
Some(count) => count[0] += 1
None => ()
}
if membership.edges.get(key) is None {
membership.edges.set(key, true)
let caller_epochs = function_epochs[caller_def]
let callee_epochs = function_epochs[target_def]
edges.push({
caller_def,
caller_param_idx: local_slot,
callee_def: target_def,
callee_param_idx: param_idx,
direct_tail,
source_type: caller_params[local_slot],
caller_signature_epoch: caller_epochs.signature_epoch,
caller_body_epoch: caller_epochs.body_epoch,
callee_signature_epoch: callee_epochs.signature_epoch,
callee_body_epoch: callee_epochs.body_epoch,
})
}
if out[target_def].length() <= 4 {
if !out[target_def].contains(caller_def) {
out[target_def].push(caller_def)
if out[target_def].length() == 5 {
for caller in out[target_def] {
membership.callers.set((target_def, caller), true)
}
}
}
} else if membership.callers.get((target_def, caller_def)) is None {
membership.callers.set((target_def, caller_def), true)
out[target_def].push(caller_def)
}
}
}
_ => ()
}
None => ()
}
param_idx += 1
}
}
///|
fn dae_collect_parameter_forwarding_dependencies_instrs(
instrs : Array[@lib.Instruction],
entry_stack_values : Int32,
current : @lib.Module,
caller_def : Int32,
caller_param_count : Int32,
type_indices : Array[@lib.TypeIdx],
function_epochs : Array[DaeFunctionEpochs],
edges : Array[DaeParameterForwardingEdge],
out : Array[Array[Int32]],
visits~ : Array[Int32]? = None,
membership~ : DaeForwardingMembership? = None,
function_types~ : Array[@lib.FuncType?]? = None,
type_resolutions~ : Array[Int32]? = None,
edge_probes~ : Array[Int32]? = None,
) -> Unit {
// Only formal parameter reads can create forwarding edges. Calls and
// result observers are collected independently by the boundary graph.
if caller_param_count <= 0 {
return
}
let membership = match membership {
Some(index) => index
None => DaeForwardingMembership::new(edges, out)
}
let mut idx = 0
while idx < instrs.length() {
match visits {
Some(count) => count[0] += 1
None => ()
}
match instrs[idx] {
@lib.Call(func_idx) =>
dae_record_parameter_forwarding_dependency(
instrs,
idx,
entry_stack_values,
current,
caller_def,
caller_param_count,
type_indices,
dae_func_idx_slot(func_idx),
false,
function_epochs,
edges,
out,
membership,
function_types~,
type_resolutions~,
edge_probes~,
)
@lib.ReturnCall(func_idx) =>
dae_record_parameter_forwarding_dependency(
instrs,
idx,
entry_stack_values,
current,
caller_def,
caller_param_count,
type_indices,
dae_func_idx_slot(func_idx),
true,
function_epochs,
edges,
out,
membership,
function_types~,
type_resolutions~,
edge_probes~,
)
@lib.Block(_, @lib.Expr(_))
| @lib.Loop(_, @lib.Expr(_))
| @lib.TryTable(_, _, @lib.Expr(_))
| @lib.If(_, _, _) =>
match dae_nested_control_children_evidence(instrs[idx], current) {
Some(children) => {
dae_collect_parameter_forwarding_dependencies_instrs(
children.primary_body,
children.entry_stack_values,
current,
caller_def,
caller_param_count,
type_indices,
function_epochs,
edges,
out,
visits~,
membership=Some(membership),
function_types~,
type_resolutions~,
edge_probes~,
)
match children.secondary_body {
Some(body) =>
dae_collect_parameter_forwarding_dependencies_instrs(
body,
children.entry_stack_values,
current,
caller_def,
caller_param_count,
type_indices,
function_epochs,
edges,
out,
visits~,
membership=Some(membership),
function_types~,
type_resolutions~,
edge_probes~,
)
None => ()
}
}
None => ()
}
_ => ()
}
idx += 1
}
}
///|
fn dae_boundary_dependency_graph_new(
graph : DaeCurrentBoundaryGraph,
builds~ : Array[Int32]? = None,
) -> DaeBoundaryDependencyGraph {
match graph.dependency_cache {
Some(dependencies) => dependencies
None => {
let dependencies = dae_boundary_dependency_graph_build(graph, builds~)
graph.dependency_cache = Some(dependencies)
dependencies
}
}
}
///|
fn dae_boundary_dependency_graph_build(
graph : DaeCurrentBoundaryGraph,
builds~ : Array[Int32]? = None,
result_edge_work~ : Array[Int32]? = None,
) -> DaeBoundaryDependencyGraph {
match builds {
Some(count) => count[0] += 1
None => ()
}
let imported = graph.snapshot.imported
let defined = graph.snapshot.defined
let callers_by_def = dae_dependency_adjacency_new(defined)
let callees_by_def = dae_dependency_adjacency_new(defined)
let return_callers_by_def = dae_dependency_adjacency_new(defined)
let result_forwarders_by_def = dae_dependency_adjacency_new(defined)
let result_dependencies_by_def = dae_dependency_adjacency_new(defined)
let result_observers_by_def = dae_dependency_adjacency_new(defined)
let direct_callers = graph.snapshot.call_facts.direct_callers
let mut callee_def = 0
while callee_def < defined {
let callee_abs = imported + callee_def
if callee_abs >= 0 && callee_abs < direct_callers.length() {
for caller_def in direct_callers[callee_abs] {
if caller_def >= 0 && caller_def < defined {
// `direct_callers` is already unique per callee, and this outer loop
// visits each callee once, so both insertions are unique by construction.
callers_by_def[callee_def].push(caller_def)
callees_by_def[caller_def].push(callee_def)
}
}
}
callee_def += 1
}
callee_def = 0
while callee_def < defined {
let callee_abs = imported + callee_def
if callee_abs >= 0 && callee_abs < graph.snapshot.function_infos.length() {
// Each callee owns a fresh sparse map. No retained-capacity clear or
// function-count-sized table is needed. Masks: tail=1, observer=2, both=3.
let mut caller_kinds : @hashmap.HashMap[Int32, Int32]? = None
let mut last_caller = -1
let mut last_mask = 0
for callsite in graph.snapshot.function_infos[callee_abs].calls {
let caller_def = callsite.caller_def
if callsite.active &&
caller_def >= 0 &&
caller_def < defined &&
(callsite.tail || !callsite.dropped) {
if caller_def != last_caller {
last_caller = caller_def
last_mask = match caller_kinds {
Some(kinds) => {
match result_edge_work {
Some(count) => count[1] += 1
None => ()
}
kinds.get(caller_def).unwrap_or(0)
}
None => 0
}
}
let new_kind = if callsite.tail {
last_mask == 0 || last_mask == 2
} else {
last_mask == 0 || last_mask == 1
}
if new_kind {
if callsite.tail {
return_callers_by_def[callee_def].push(caller_def)
} else {
result_observers_by_def[callee_def].push(caller_def)
}
if last_mask == 0 {
result_forwarders_by_def[callee_def].push(caller_def)
result_dependencies_by_def[caller_def].push(callee_def)
}
last_mask += if callsite.tail { 1 } else { 2 }
match result_edge_work {
Some(count) => count[2] += 1
None => ()
}
match caller_kinds {
Some(kinds) => kinds.set(caller_def, last_mask)
None => {
let kinds : @hashmap.HashMap[Int32, Int32] = @hashmap.new()
kinds.set(caller_def, last_mask)
caller_kinds = Some(kinds)
match result_edge_work {
Some(count) => count[3] += 1
None => ()
}
}
}
}
}
}
}
callee_def += 1
}
let parameter_forwarding_edges : Array[DaeParameterForwardingEdge] = []
let parameter_forwarders_by_def = dae_dependency_adjacency_new(defined)
let membership = DaeForwardingMembership::new(
parameter_forwarding_edges, parameter_forwarders_by_def,
)
let mut caller_def = 0
while caller_def < defined && caller_def < graph.snapshot.funcs.length() {
let caller_abs = imported + caller_def
let caller_param_count = match graph.snapshot.function_types[caller_abs] {
Some(@lib.FuncType(params, _)) => params.length()
None => 0
}
let @lib.Func(_, @lib.Expr(body)) = graph.snapshot.funcs[caller_def]
dae_collect_parameter_forwarding_dependencies_instrs(
body,
0,
graph.mod_,
caller_def,
caller_param_count,
graph.snapshot.type_indices,
graph.function_epochs,
parameter_forwarding_edges,
parameter_forwarders_by_def,
function_types=Some(graph.snapshot.function_types),
membership=Some(membership),
)
caller_def += 1
}
let (component_by_def, component_members) = dae_boundary_components(
callees_by_def,
)
let forwarding_adjacency = dae_dependency_adjacency_new(defined)
let mut target_def = 0
while target_def < parameter_forwarders_by_def.length() {
for forwarder_def in parameter_forwarders_by_def[target_def] {
if forwarder_def >= 0 && forwarder_def < defined {
// Parameter forwarders are unique per target. Each undirected pair is
// therefore emitted once in each direction; a self-edge needs one copy.
forwarding_adjacency[target_def].push(forwarder_def)
if forwarder_def != target_def {
forwarding_adjacency[forwarder_def].push(target_def)
}
}
}
target_def += 1
}
let (forwarding_component_by_def, forwarding_component_members) = dae_boundary_components(
forwarding_adjacency,
)
let caller_callee_dependencies = dae_dependency_adjacency_union(
callers_by_def, callees_by_def,
)
{
callers_by_def,
callees_by_def,
return_callers_by_def,
parameter_forwarding_edges,
parameter_forwarders_by_def,
// Uniform-value evidence is invalidated by the bodies of every exact
// current direct caller, but not by unrelated callees of the target.
uniform_value_forwarders_by_def: dae_dependency_adjacency_copy(
callers_by_def,
),
result_forwarders_by_def,
result_dependencies_by_def,
result_observers_by_def,
tail_result_dependencies_by_def: dae_dependency_adjacency_copy(
return_callers_by_def,
),
type_dependencies_by_def: dae_dependency_adjacency_copy(
caller_callee_dependencies,
),
body_use_dependencies_by_def: dae_dependency_adjacency_copy(
caller_callee_dependencies,
),
nested_cleanup_dependencies_by_def: dae_dependency_adjacency_copy(
caller_callee_dependencies,
),
component_by_def,
component_members,
forwarding_component_by_def,
forwarding_component_members,
}
}
///|
fn dae_mark_dependency_row(
rows : Array[Array[Int32]],
def_idx : Int32,
invalidated : Array[Bool],
) -> Unit {
if def_idx >= 0 && def_idx < rows.length() {
for dependency in rows[def_idx] {
if dependency >= 0 && dependency < invalidated.length() {
invalidated[dependency] = true
}
}
}
}
///|
fn dae_mark_dependency_domain(
graph : DaeBoundaryDependencyGraph,
def_idx : Int32,
domain : DaeInvalidationDomain,
invalidated : Array[Bool],
) -> Unit {
match domain {
DaeInvalidateCalls | DaeInvalidateSignature => {
dae_mark_dependency_row(graph.callers_by_def, def_idx, invalidated)
dae_mark_dependency_row(graph.callees_by_def, def_idx, invalidated)
}
DaeInvalidateResult => {
dae_mark_dependency_row(
graph.result_forwarders_by_def,
def_idx,
invalidated,
)
dae_mark_dependency_row(
graph.result_observers_by_def,
def_idx,
invalidated,
)
dae_mark_dependency_row(
graph.tail_result_dependencies_by_def,
def_idx,
invalidated,
)
}
DaeInvalidateBody =>
dae_mark_dependency_row(
graph.body_use_dependencies_by_def,
def_idx,
invalidated,
)
DaeInvalidateControl => {
dae_mark_dependency_row(
graph.body_use_dependencies_by_def,
def_idx,
invalidated,
)
dae_mark_dependency_row(
graph.result_forwarders_by_def,
def_idx,
invalidated,
)
dae_mark_dependency_row(
graph.tail_result_dependencies_by_def,
def_idx,
invalidated,
)
}
DaeInvalidateLocals => {
dae_mark_dependency_row(
graph.body_use_dependencies_by_def,
def_idx,
invalidated,
)
dae_mark_dependency_row(
graph.nested_cleanup_dependencies_by_def,
def_idx,
invalidated,
)
}
DaeInvalidateTypes =>
dae_mark_dependency_row(
graph.type_dependencies_by_def,
def_idx,
invalidated,
)
DaeInvalidateMetadata => ()
DaeInvalidateNestedCleanup =>
dae_mark_dependency_row(
graph.nested_cleanup_dependencies_by_def,
def_idx,
invalidated,
)
}
}
///|
fn dae_boundary_dependency_invalidations(
before : DaeBoundaryDependencyGraph,
after : DaeBoundaryDependencyGraph,
touched : Array[Bool],
primary_def : Int32,
effects : DaeCommitEffects,
) -> Array[Int32] {
let defined = if before.callers_by_def.length() >
after.callers_by_def.length() {
before.callers_by_def.length()
} else {
after.callers_by_def.length()
}
let invalidated = Array::make(defined, false)
let producer_domains = effects.invalidation_domains
let mut def_idx = 0
while def_idx < defined {
let is_touched = def_idx == primary_def ||
(def_idx < touched.length() && touched[def_idx])
if is_touched {
invalidated[def_idx] = true
for domain in producer_domains {
dae_mark_dependency_domain(before, def_idx, domain, invalidated)
dae_mark_dependency_domain(after, def_idx, domain, invalidated)
}
}
def_idx += 1
}
let out : Array[Int32] = []
def_idx = 0
while def_idx < defined {
if invalidated[def_idx] {
out.push(def_idx)
}
def_idx += 1
}
out
}
///|
fn dae_work_family_slot(family : DaeWorkFamily) -> Int32 {
match family {
DaeUnusedParameterWork => 0
DaeUniformActualWork => 1
DaeForwardingWork => 2
DaeLocalizationRetryWork => 3
DaeGcParameterRefinementWork => 4
DaeGcResultRefinementWork => 5
DaeDroppedResultWork => 6
DaeControlReconstructionWork => 7
DaeTypeRepairRetryWork => 8
DaePostCleanupBoundaryRevisitWork => 9
DaeBodyCleanupWork => 10
}
}
///|
fn dae_work_item_new(
graph : DaeCurrentBoundaryGraph,
dependencies : DaeBoundaryDependencyGraph,
target_def : Int32,
family : DaeWorkFamily,
) -> DaeWorkItem {
let epoch = dae_boundary_candidate_epoch(graph, target_def)
let component_id = if family == DaeForwardingWork &&
target_def >= 0 &&
target_def < dependencies.forwarding_component_by_def.length() {
dependencies.forwarding_component_by_def[target_def]
} else if target_def >= 0 &&
target_def < dependencies.component_by_def.length() {
dependencies.component_by_def[target_def]
} else {
-1
}
let component_members = if family == DaeForwardingWork &&
component_id >= 0 &&
component_id < dependencies.forwarding_component_members.length() {
dependencies.forwarding_component_members[component_id].copy()
} else if component_id >= 0 &&
component_id < dependencies.component_members.length() {
dependencies.component_members[component_id].copy()
} else {
[]
}
{
target_def,
component_id,
component_members,
family,
uniform_values: [],
allow_immutable_global_get: false,
allow_immediate_i32_suffix_fallback: false,
expected_module_epoch: epoch.module_epoch,
expected_call_epoch: epoch.call_epoch,
expected_type_epoch: epoch.type_epoch,
expected_signature_epoch: epoch.signature_epoch,
expected_body_epoch: epoch.body_epoch,
}
}
///|
fn dae_boundary_dependency_graph_empty() -> DaeBoundaryDependencyGraph {
{
callers_by_def: [],
callees_by_def: [],
return_callers_by_def: [],
parameter_forwarding_edges: [],
parameter_forwarders_by_def: [],
uniform_value_forwarders_by_def: [],
result_forwarders_by_def: [],
result_dependencies_by_def: [],
result_observers_by_def: [],
tail_result_dependencies_by_def: [],
type_dependencies_by_def: [],
body_use_dependencies_by_def: [],
nested_cleanup_dependencies_by_def: [],
component_by_def: [],
component_members: [],
forwarding_component_by_def: [],
forwarding_component_members: [],
}
}
///|
fn dae_nonforwarding_work_item_new(
graph : DaeCurrentBoundaryGraph,
target_def : Int32,
family : DaeWorkFamily,
) -> DaeWorkItem {
let epoch = dae_boundary_candidate_epoch(graph, target_def)
{
target_def,
component_id: -1,
component_members: [],
family,
uniform_values: [],
allow_immutable_global_get: false,
allow_immediate_i32_suffix_fallback: false,
expected_module_epoch: epoch.module_epoch,
expected_call_epoch: epoch.call_epoch,
expected_type_epoch: epoch.type_epoch,
expected_signature_epoch: epoch.signature_epoch,
expected_body_epoch: epoch.body_epoch,
}
}
///|
fn dae_body_cleanup_work_item_new(
graph : DaeCurrentBoundaryGraph,
target_def : Int32,
) -> DaeWorkItem {
dae_nonforwarding_work_item_new(graph, target_def, DaeBodyCleanupWork)
}
///|
fn dae_work_item_epoch_is_current(
graph : DaeCurrentBoundaryGraph,
item : DaeWorkItem,
) -> Bool {
dae_candidate_epoch_is_current(graph, {
def_idx: item.target_def,
module_epoch: item.expected_module_epoch,
call_epoch: item.expected_call_epoch,
type_epoch: item.expected_type_epoch,
signature_epoch: item.expected_signature_epoch,
body_epoch: item.expected_body_epoch,
})
}
///|
fn dae_family_fallback_candidate_is_body_only(
graph : DaeCurrentBoundaryGraph,
candidate : @lib.Module,
) -> Bool {
if dae_defined_func_count(candidate) != graph.snapshot.defined ||
dae_imported_func_count(candidate) != graph.snapshot.imported {
return false
}
let next_type_indices = dae_collect_func_type_indices(candidate)
let mut def_idx = 0
while def_idx < graph.snapshot.defined {
let func_abs = graph.snapshot.imported + def_idx
if func_abs < 0 ||
func_abs >= graph.snapshot.function_types.length() ||
graph.snapshot.function_types[func_abs] !=
dae_resolve_func_type(candidate, next_type_indices, func_abs) {
return false
}
def_idx += 1
}
true
}
///|
fn dae_family_plan_requires_call_identity_validation(
body_only_transaction : Bool,
) -> Bool {
!body_only_transaction
}
///|
fn dae_build_family_rewrite_plan(
original : DaeOriginalBoundarySnapshot,
current : @lib.Module,
graph : DaeCurrentBoundaryGraph,
target_def : Int32,
candidate : DaePassResult,
effects~ : DaeCommitEffects? = None,
body_only_transaction~ : Bool = false,
) -> DaeFamilyRewritePlan? {
if target_def < 0 ||
target_def >= graph.snapshot.defined ||
!candidate.changed ||
dae_snapshot_module_unchanged(candidate.mod_out, current) ||
candidate.primary_def != target_def ||
candidate.touched.length() != graph.snapshot.defined ||
dae_defined_func_count(candidate.mod_out) != graph.snapshot.defined ||
dae_imported_func_count(candidate.mod_out) != graph.snapshot.imported {
return None
}
let next_type_indices = dae_collect_func_type_indices(candidate.mod_out)
let validate_call_identities = dae_family_plan_requires_call_identity_validation(
body_only_transaction,
)
let expected_functions : Array[DaeComponentMemberExpectation] = []
let expected_call_identities : Array[DaeDirectCallIdentity] = []
let shared_facts : DaeBoundaryFacts = {
original,
graph,
incoming_param_live_by_def: [],
incoming_param_live_body_epochs: [],
}
let mut def_idx = 0
while def_idx < graph.snapshot.defined {
let func_abs = graph.snapshot.imported + def_idx
let old_signature = if func_abs >= 0 &&
func_abs < graph.snapshot.function_types.length() {
graph.snapshot.function_types[func_abs]
} else {
None
}
let next_signature = dae_resolve_func_type(
candidate.mod_out,
next_type_indices,
func_abs,
)
let signature_changed = old_signature != next_signature
if signature_changed &&
!dae_can_change_family_signature(shared_facts, func_abs) {
return None
}
if candidate.touched[def_idx] || signature_changed || def_idx == target_def {
if def_idx >= graph.function_epochs.length() {
return None
}
let epochs = graph.function_epochs[def_idx]
let (direct_calls, return_calls) = if validate_call_identities {
dae_count_component_target_call_kinds(graph.snapshot.funcs, func_abs)
} else {
(0, 0)
}
expected_functions.push({
def_idx,
signature_epoch: epochs.signature_epoch,
body_epoch: epochs.body_epoch,
direct_calls,
return_calls,
})
if validate_call_identities {
for
identity in dae_collect_target_call_identities(
graph.snapshot,
func_abs,
) {
expected_call_identities.push(identity)
}
}
}
def_idx += 1
}
let producer_effects = match effects {
Some(value) => {
if value.structurally_touched.length() != graph.snapshot.defined ||
value.worth_decided.length() != graph.snapshot.defined ||
value.worth_optimizing.length() != graph.snapshot.defined ||
value.parameter_changed.length() != graph.snapshot.defined ||
value.body_materialized.length() != graph.snapshot.defined ||
value.body_localized.length() != graph.snapshot.defined {
return None
}
dae_commit_effects_with_structural_touched(value, candidate.touched)
}
None =>
dae_commit_effects_with_structural_touched(
dae_commit_effects_empty(graph.snapshot.defined),
candidate.touched,
)
}
Some({
effects: producer_effects,
target_def,
expected_module_epoch: graph.module_epoch,
expected_call_epoch: graph.call_epoch,
expected_type_epoch: graph.type_epoch,
expected_functions,
expected_call_identities,
validate_call_identities,
source: current,
candidate,
})
}
///|
fn dae_apply_family_rewrite_plan(
current : @lib.Module,
graph : DaeCurrentBoundaryGraph,
plan : DaeFamilyRewritePlan,
) -> DaeRewritePlanApplication {
if !dae_snapshot_module_unchanged(current, plan.source) ||
!dae_snapshot_module_unchanged(graph.mod_, current) ||
plan.expected_module_epoch != graph.module_epoch ||
plan.expected_call_epoch != graph.call_epoch ||
plan.expected_type_epoch != graph.type_epoch {
return dae_rewrite_plan_rejection(current, DaeStaleCandidateFacts)
}
let actual_call_identities : Array[DaeDirectCallIdentity] = []
for expected in plan.expected_functions {
if expected.def_idx < 0 ||
expected.def_idx >= graph.function_epochs.length() ||
expected.def_idx >= graph.snapshot.defined {
return dae_rewrite_plan_rejection(current, DaeStaleCandidateFacts)
}
let epochs = graph.function_epochs[expected.def_idx]
if epochs.signature_epoch != expected.signature_epoch ||
epochs.body_epoch != expected.body_epoch {
return dae_rewrite_plan_rejection(current, DaeStaleCandidateFacts)
}
if plan.validate_call_identities {
let func_abs = graph.snapshot.imported + expected.def_idx
let (direct_calls, return_calls) = dae_count_component_target_call_kinds(
graph.snapshot.funcs,
func_abs,
)
if direct_calls != expected.direct_calls ||
return_calls != expected.return_calls {
return dae_rewrite_plan_rejection(current, DaeCallCountChanged)
}
for
identity in dae_collect_target_call_identities(graph.snapshot, func_abs) {
actual_call_identities.push(identity)
}
}
}
if plan.validate_call_identities &&
!dae_direct_call_identities_equal(
actual_call_identities,
plan.expected_call_identities,
) {
return dae_rewrite_plan_rejection(current, DaeCallCountChanged)
}
if plan.candidate.primary_def != plan.target_def ||
!plan.candidate.changed ||
dae_snapshot_module_unchanged(plan.candidate.mod_out, current) {
return dae_rewrite_plan_rejection(current, DaeNoStrictImprovement)
}
match @validate.validate_module(plan.candidate.mod_out) {
Ok(_) =>
{
result: plan.candidate,
applied: true,
reason: DaeCandidateApplied,
effects: {
..plan.effects,
candidates_produced: 1,
candidates_applied: 1,
decision_reasons: plan.effects.decision_reasons +
[DaeCandidateApplied],
},
}
Err(_) => dae_rewrite_plan_rejection(current, DaeValidationRollback)
}
}
///|
fn dae_apply_family_result_transaction(
original : DaeOriginalBoundarySnapshot,
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
graph : DaeCurrentBoundaryGraph,
candidate : DaePassResult,
effects~ : DaeCommitEffects? = None,
allow_signature_restoration~ : Bool = false,
body_only_transaction~ : Bool = false,
checked_candidate~ : (@lib.Module, @lib.Module)? = None,
rewrite_validation_counts~ : Array[Int32]? = None,
) -> DaeCoreResultApplication {
let candidate_funcs = match candidate.mod_out.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => []
}
let candidate_type_indices = dae_collect_func_type_indices(candidate.mod_out)
let normalized_touched = Array::make(graph.snapshot.defined, false)
let mut def_idx = 0
while def_idx < graph.snapshot.defined && def_idx < candidate_funcs.length() {
let func_abs = graph.snapshot.imported + def_idx
let next_signature = dae_resolve_func_type(
candidate.mod_out,
candidate_type_indices,
func_abs,
)
if graph.snapshot.funcs[def_idx] != candidate_funcs[def_idx] ||
graph.snapshot.function_types[func_abs] != next_signature {
normalized_touched[def_idx] = true
}
def_idx += 1
}
let mut inferred_target_def = -1
let mut touched_idx = 0
while touched_idx < normalized_touched.length() {
if normalized_touched[touched_idx] {
inferred_target_def = touched_idx
break
}
touched_idx += 1
}
let target_def = if candidate.primary_def >= 0 &&
candidate.primary_def < normalized_touched.length() &&
normalized_touched[candidate.primary_def] {
candidate.primary_def
} else {
inferred_target_def
}
let target_abs = graph.snapshot.imported + target_def
let transaction_mod = if !body_only_transaction &&
target_def >= 0 &&
dae_collect_target_call_identities(graph.snapshot, target_abs).length() > 0 {
dae_cleanup_dropped_unreachable_debris_touched(
candidate.mod_out,
normalized_touched,
).0
} else {
candidate.mod_out
}
let normalized_candidate = {
mod_out: transaction_mod,
touched: normalized_touched,
changed: candidate.changed,
primary_def: target_def,
}
if effects is None &&
!body_only_transaction &&
!dae_family_fallback_candidate_is_body_only(
graph,
normalized_candidate.mod_out,
) {
return {
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(graph.snapshot.defined),
}
}
let signature_effects = dae_commit_effects_with_structural_touched(
match effects {
Some(effects) => effects
None => dae_commit_effects_empty(graph.snapshot.defined)
},
normalized_touched,
)
if !body_only_transaction {
match
dae_build_signature_rewrite_plan_from_candidate(
original, current, graph, target_def, normalized_candidate, signature_effects,
) {
Some(plan) => {
let application = dae_apply_rewrite_plan(
current,
graph,
plan,
checked_candidate~,
rewrite_validation_counts~,
)
if application.applied ||
application.reason != DaeTypeReconstructionUnavailable {
return {
mod_out: application.result.mod_out,
touched: if application.applied {
dae_merge_touched(touched, application.result.touched)
} else {
touched
},
changed: changed || application.result.changed,
applied: application.applied,
primary_def: application.result.primary_def,
effects: application.effects,
}
}
}
None => ()
}
}
if !dae_family_fallback_candidate_is_body_only(
graph,
normalized_candidate.mod_out,
) &&
!allow_signature_restoration {
return {
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(graph.snapshot.defined),
}
}
let plan = match
dae_build_family_rewrite_plan(
original,
current,
graph,
target_def,
normalized_candidate,
effects~,
body_only_transaction~,
) {
Some(plan) => plan
None =>
return {
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(graph.snapshot.defined),
}
}
let application = dae_apply_family_rewrite_plan(current, graph, plan)
if !application.applied {
return {
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: application.effects,
}
}
{
mod_out: application.result.mod_out,
touched: dae_merge_touched(touched, application.result.touched),
changed: changed || application.result.changed,
applied: true,
primary_def: application.result.primary_def,
effects: application.effects,
}
}
///|
fn dae_commit_effects_body_only_cleanup(
current : @lib.Module,
candidate : DaePassResult,
) -> DaeCommitEffects {
let defined = dae_defined_func_count(current)
let effects = dae_commit_effects_empty(defined)
let current_funcs = match current.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => []
}
let candidate_funcs = match candidate.mod_out.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => []
}
let mut locals_changed = false
let mut def_idx = 0
while def_idx < defined && def_idx < candidate.touched.length() {
if candidate.touched[def_idx] {
effects.structurally_touched[def_idx] = true
if def_idx < current_funcs.length() && def_idx < candidate_funcs.length() {
let @lib.Func(current_locals, _) = current_funcs[def_idx]
let @lib.Func(candidate_locals, _) = candidate_funcs[def_idx]
if current_locals != candidate_locals {
effects.local_projection_changed[def_idx] = true
locals_changed = true
}
}
}
def_idx += 1
}
let metadata_changed = current.name_sec != candidate.mod_out.name_sec
let invalidation_domains = [DaeInvalidateBody]
if locals_changed {
invalidation_domains.push(DaeInvalidateLocals)
}
if metadata_changed {
invalidation_domains.push(DaeInvalidateMetadata)
}
{
..effects,
metadata_changed,
invalidation_domains,
decision_reasons: [DaeCandidateProduced],
}
}
///|
fn dae_body_cleanup_transaction_original_snapshot(
original : DaeOriginalBoundarySnapshot,
_current : @lib.Module,
) -> DaeOriginalBoundarySnapshot {
original
}
///|
fn dae_process_precomputed_body_work_item(
original : DaeOriginalBoundarySnapshot,
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
graph : DaeCurrentBoundaryGraph,
item : DaeWorkItem,
candidate : DaePassResult,
diagnostics : DaeDiagnostics,
) -> DaeWorkOutcome {
if !candidate.changed {
return DaeWorkNoChange(DaeNoStrictImprovement)
}
diagnostics.boundaries_analyzed += 1
if item.family != DaeBodyCleanupWork {
dae_diagnostics_record_reason(diagnostics, DaeNoStrictImprovement)
return DaeWorkNoChange(DaeNoStrictImprovement)
}
if !dae_work_item_epoch_is_current(graph, item) {
dae_diagnostics_record_reason(diagnostics, DaeStaleCandidateFacts)
diagnostics.candidate_stale_retries += 1
diagnostics.candidate_requeues += 1
let dependencies = dae_boundary_dependency_graph_new(graph)
return DaeWorkRequeue([
dae_work_item_new(
graph,
dependencies,
item.target_def,
DaeBodyCleanupWork,
),
])
}
let effects = dae_commit_effects_body_only_cleanup(current, candidate)
let application = dae_apply_family_result_transaction(
dae_body_cleanup_transaction_original_snapshot(original, current),
current,
touched,
changed,
graph,
candidate,
effects=Some(effects),
body_only_transaction=true,
)
if !application.applied {
dae_diagnostics_record_reason(diagnostics, DaeNoStrictImprovement)
return DaeWorkNoChange(DaeNoStrictImprovement)
}
dae_diagnostics_record_commit_effects(diagnostics, application.effects)
dae_diagnostics_record_applied_effects(diagnostics, application.effects)
DaeWorkCommitted(application)
}
///|
fn dae_work_family_for_commit_effects(
effects : DaeCommitEffects,
target_def : Int32,
) -> DaeWorkFamily {
if target_def >= 0 &&
target_def < effects.result_removed.length() &&
effects.result_removed[target_def] {
return DaeDroppedResultWork
}
if target_def >= 0 &&
target_def < effects.result_changed.length() &&
effects.result_changed[target_def] {
return DaeGcResultRefinementWork
}
if target_def >= 0 &&
target_def < effects.parameter_type_refined.length() &&
effects.parameter_type_refined[target_def] {
return DaeGcParameterRefinementWork
}
if effects.body_localized.any(fn(value) { value }) {
return DaeLocalizationRetryWork
}
if target_def >= 0 &&
target_def < effects.body_materialized.length() &&
effects.body_materialized[target_def] {
return DaeUniformActualWork
}
DaeUnusedParameterWork
}
///|
fn dae_process_precomputed_signature_work_item(
original : DaeOriginalBoundarySnapshot,
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
graph : DaeCurrentBoundaryGraph,
item : DaeWorkItem,
candidate : DaePassResult,
effects : DaeCommitEffects,
allow_signature_restoration~ : Bool = false,
checked_candidate~ : (@lib.Module, @lib.Module)? = None,
rewrite_validation_counts~ : Array[Int32]? = None,
) -> DaeWorkOutcome {
if !candidate.changed {
return DaeWorkNoChange(DaeNoStrictImprovement)
}
if !dae_work_item_epoch_is_current(graph, item) {
let dependencies = dae_boundary_dependency_graph_new(graph)
return DaeWorkRequeue([
dae_work_item_new(graph, dependencies, item.target_def, item.family),
])
}
let application = dae_apply_family_result_transaction(
original,
current,
touched,
changed,
graph,
candidate,
effects=Some(effects),
allow_signature_restoration~,
checked_candidate~,
rewrite_validation_counts~,
)
if application.applied {
DaeWorkCommitted(application)
} else {
DaeWorkNoChange(DaeNoStrictImprovement)
}
}
///|
fn dae_body_only_candidate_requires_transaction(
candidate : DaePassResult,
) -> Bool {
candidate.changed
}
///|
fn dae_body_only_candidate_preserves_nonbody_sections(
current : @lib.Module,
candidate : @lib.Module,
) -> Bool {
current.custom_secs == candidate.custom_secs &&
current.raw_name_sec_payload == candidate.raw_name_sec_payload &&
current.type_sec == candidate.type_sec &&
current.import_sec == candidate.import_sec &&
current.func_annotation_sec == candidate.func_annotation_sec &&
current.func_sec == candidate.func_sec &&
current.table_sec == candidate.table_sec &&
current.mem_sec == candidate.mem_sec &&
current.tag_sec == candidate.tag_sec &&
current.stringrefs_sec == candidate.stringrefs_sec &&
current.global_sec == candidate.global_sec &&
current.export_sec == candidate.export_sec &&
current.start_sec == candidate.start_sec &&
current.elem_sec == candidate.elem_sec &&
current.data_cnt_sec == candidate.data_cnt_sec &&
current.data_sec == candidate.data_sec
}
///|
fn dae_body_only_boundary_graph_new(
mod_ : @lib.Module,
) -> DaeCurrentBoundaryGraph {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let total = imported + defined
let type_indices = dae_collect_func_type_indices(mod_)
let function_types : Array[@lib.FuncType?] = []
let mut func_abs = 0
while func_abs < total {
function_types.push(dae_resolve_func_type(mod_, type_indices, func_abs))
func_abs += 1
}
let escaped = Array::make(total, false)
let call_facts : DaeCurrentCallFacts = {
direct_calls: Array::make(total, 0),
tail_calls: Array::make(total, false),
direct_callers: dae_make_direct_callers(total),
dropped_calls: Array::make(total, 0),
escaped,
}
let snapshot : DaeModuleBoundarySnapshot = {
imported,
defined,
total,
type_indices,
function_types,
funcs: match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => []
},
call_facts,
boundary_facts: dae_call_boundary_facts_from_current(call_facts),
function_infos: dae_make_function_infos(total, escaped),
exposure: Array::make(total, dae_function_exposure_empty()),
parameter_loop_free: Array::make(defined, true),
}
{
mod_,
snapshot,
topology: {
direct_return_callers: dae_make_direct_callers(total),
direct_return_callees_by_caller: dae_make_direct_callers(total),
adjacency_membership: @hashmap.new(),
has_outgoing_call_indirect: Array::make(total, false),
has_outgoing_call_ref: Array::make(total, false),
has_outgoing_return_call: Array::make(total, false),
has_outgoing_return_call_indirect: Array::make(total, false),
has_outgoing_return_call_ref: Array::make(total, false),
},
function_epochs: dae_make_function_epochs(defined, 0, 0),
module_epoch: 0,
call_epoch: 0,
type_epoch: 0,
dependency_cache: None,
}
}
///|
fn dae_apply_body_only_family_result_transaction(
_original : DaeOriginalBoundarySnapshot,
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
candidate : DaePassResult,
diagnostics : DaeDiagnostics,
) -> DaeCoreResultApplication {
if !dae_body_only_candidate_requires_transaction(candidate) {
return {
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(dae_defined_func_count(current)),
}
}
if !dae_body_only_candidate_preserves_nonbody_sections(
current,
candidate.mod_out,
) {
return {
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(dae_defined_func_count(current)),
}
}
let graph = dae_body_only_boundary_graph_new(current)
match
dae_process_precomputed_body_work_item(
_original,
current,
touched,
changed,
graph,
dae_body_cleanup_work_item_new(graph, candidate.primary_def),
candidate,
diagnostics,
) {
DaeWorkCommitted(application) => application
DaeWorkNoChange(_) | DaeWorkDeferred(_) | DaeWorkRequeue(_) =>
{
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(dae_defined_func_count(current)),
}
}
}
///|
fn dae_invalidation_domains_for_work_family(
family : DaeWorkFamily,
) -> Array[DaeInvalidationDomain] {
match family {
DaeUnusedParameterWork
| DaeUniformActualWork
| DaeLocalizationRetryWork
| DaeGcParameterRefinementWork
| DaePostCleanupBoundaryRevisitWork =>
[
DaeInvalidateCalls,
DaeInvalidateSignature,
DaeInvalidateBody,
DaeInvalidateLocals,
DaeInvalidateTypes,
DaeInvalidateMetadata,
DaeInvalidateNestedCleanup,
]
DaeGcResultRefinementWork | DaeDroppedResultWork | DaeTypeRepairRetryWork =>
[
DaeInvalidateCalls,
DaeInvalidateSignature,
DaeInvalidateResult,
DaeInvalidateBody,
DaeInvalidateControl,
DaeInvalidateTypes,
DaeInvalidateMetadata,
DaeInvalidateNestedCleanup,
]
DaeControlReconstructionWork =>
[
DaeInvalidateBody,
DaeInvalidateControl,
DaeInvalidateMetadata,
DaeInvalidateNestedCleanup,
]
DaeBodyCleanupWork =>
[
DaeInvalidateBody,
DaeInvalidateLocals,
DaeInvalidateMetadata,
DaeInvalidateNestedCleanup,
]
DaeForwardingWork =>
[
DaeInvalidateCalls,
DaeInvalidateSignature,
DaeInvalidateBody,
DaeInvalidateLocals,
DaeInvalidateTypes,
DaeInvalidateMetadata,
DaeInvalidateNestedCleanup,
]
}
}
///|
fn dae_should_scan_work_item_candidate_effects(
candidate : DaePassResult,
) -> Bool {
candidate.changed
}
///|
fn dae_work_item_candidate_effects(
current : @lib.Module,
graph : DaeCurrentBoundaryGraph,
item : DaeWorkItem,
candidate : DaePassResult,
) -> DaeCommitEffects {
let defined = graph.snapshot.defined
let effects = dae_commit_effects_empty(defined)
if !dae_should_scan_work_item_candidate_effects(candidate) {
return effects
}
// Effects need body ownership, signatures and aggregate call facts only.
// Keep these inputs local; full graph records and epochs are unused here.
let after_funcs = match candidate.mod_out.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => []
}
let after_type_indices = dae_collect_func_type_indices(candidate.mod_out)
let after_function_types = dae_snapshot_function_types(
candidate.mod_out,
after_type_indices,
)
let after_call_facts = dae_collect_current_call_facts(
candidate.mod_out,
after_type_indices.length(),
)
let mut any_body_changed = false
let mut any_locals_changed = false
let mut def_idx = 0
while def_idx < defined {
let structurally_touched = def_idx < candidate.touched.length() &&
candidate.touched[def_idx]
if structurally_touched {
effects.structurally_touched[def_idx] = true
}
if def_idx < graph.snapshot.funcs.length() && def_idx < after_funcs.length() {
let @lib.Func(before_locals, before_expr) = graph.snapshot.funcs[def_idx]
let @lib.Func(after_locals, after_expr) = after_funcs[def_idx]
if before_locals != after_locals {
effects.local_projection_changed[def_idx] = true
any_locals_changed = true
}
if !dae_snapshot_expr_unchanged(before_expr, after_expr) {
any_body_changed = true
}
}
def_idx += 1
}
let target = item.target_def
let mut parameter_changed = false
let mut parameter_refined = false
let mut result_changed = false
let mut result_removed = false
if target >= 0 && target < defined {
let target_abs = graph.snapshot.imported + target
let before_signature = if target_abs >= 0 &&
target_abs < graph.snapshot.function_types.length() {
graph.snapshot.function_types[target_abs]
} else {
None
}
let after_signature = if target_abs >= 0 &&
target_abs < after_function_types.length() {
after_function_types[target_abs]
} else {
None
}
match (before_signature, after_signature) {
(
Some(@lib.FuncType(before_params, before_results)),
Some(@lib.FuncType(after_params, after_results)),
) => {
parameter_changed = before_params != after_params
parameter_refined = before_params.length() == after_params.length() &&
parameter_changed
result_changed = before_results != after_results
result_removed = after_results.length() < before_results.length()
}
_ => ()
}
effects.parameter_changed[target] = parameter_changed
effects.parameter_type_refined[target] = parameter_refined
effects.result_changed[target] = result_changed
effects.result_removed[target] = result_removed
effects.control_refinalization_required[target] = result_changed ||
item.family == DaeControlReconstructionWork ||
item.family == DaeTypeRepairRetryWork
let target_productive = parameter_changed ||
result_changed ||
effects.local_projection_changed[target] ||
(
target < graph.snapshot.funcs.length() &&
target < after_funcs.length() &&
graph.snapshot.funcs[target] != after_funcs[target]
)
match item.family {
DaeUnusedParameterWork
| DaeUniformActualWork
| DaeLocalizationRetryWork
| DaeGcParameterRefinementWork
| DaeGcResultRefinementWork
| DaeDroppedResultWork
| DaePostCleanupBoundaryRevisitWork => {
effects.worth_decided[target] = true
effects.worth_optimizing[target] = target_productive
}
DaeForwardingWork
| DaeControlReconstructionWork
| DaeTypeRepairRetryWork
| DaeBodyCleanupWork => ()
}
if item.family == DaeUniformActualWork && parameter_changed {
effects.body_materialized[target] = true
}
}
if item.family == DaeLocalizationRetryWork ||
item.family == DaeGcResultRefinementWork ||
item.family == DaeDroppedResultWork {
def_idx = 0
while def_idx < defined {
let body_changed = def_idx != target &&
def_idx < graph.snapshot.funcs.length() &&
def_idx < after_funcs.length() &&
graph.snapshot.funcs[def_idx] != after_funcs[def_idx]
if def_idx != target && body_changed {
match item.family {
DaeLocalizationRetryWork => {
effects.worth_decided[def_idx] = true
effects.worth_optimizing[def_idx] = true
effects.body_localized[def_idx] = true
}
DaeGcResultRefinementWork => {
effects.worth_decided[def_idx] = true
effects.worth_optimizing[def_idx] = true
}
DaeDroppedResultWork => {
effects.worth_decided[def_idx] = true
effects.worth_optimizing[def_idx] = true
effects.caller_drop_removed[def_idx] = true
}
_ => ()
}
}
def_idx += 1
}
}
let type_section_changed = current.type_sec != candidate.mod_out.type_sec
let metadata_changed = current.name_sec != candidate.mod_out.name_sec
let call_facts_changed = graph.snapshot.call_facts.direct_calls !=
after_call_facts.direct_calls ||
graph.snapshot.call_facts.tail_calls != after_call_facts.tail_calls ||
graph.snapshot.call_facts.direct_callers != after_call_facts.direct_callers ||
graph.snapshot.call_facts.dropped_calls != after_call_facts.dropped_calls ||
graph.snapshot.call_facts.escaped != after_call_facts.escaped
let invalidation_domains : Array[DaeInvalidationDomain] = []
if call_facts_changed {
invalidation_domains.push(DaeInvalidateCalls)
}
if parameter_changed || result_changed {
invalidation_domains.push(DaeInvalidateSignature)
}
if result_changed {
invalidation_domains.push(DaeInvalidateResult)
}
if any_body_changed {
invalidation_domains.push(DaeInvalidateBody)
}
if any_locals_changed || parameter_changed {
invalidation_domains.push(DaeInvalidateLocals)
}
if result_changed ||
item.family == DaeControlReconstructionWork ||
item.family == DaeTypeRepairRetryWork {
invalidation_domains.push(DaeInvalidateControl)
}
if type_section_changed {
invalidation_domains.push(DaeInvalidateTypes)
}
if metadata_changed {
invalidation_domains.push(DaeInvalidateMetadata)
}
if any_body_changed {
invalidation_domains.push(DaeInvalidateNestedCleanup)
}
let before_type_count = match current.type_sec {
Some(type_sec) => dae_type_sec_flattened_length(type_sec)
None => 0
}
let after_type_count = match candidate.mod_out.type_sec {
Some(type_sec) => dae_type_sec_flattened_length(type_sec)
None => 0
}
{
..effects,
type_section_changed,
metadata_changed,
type_entries_reused: if (parameter_changed || result_changed) &&
!type_section_changed {
1
} else {
0
},
type_entries_appended: if after_type_count > before_type_count {
after_type_count - before_type_count
} else {
0
},
invalidation_domains,
decision_reasons: [DaeCandidateProduced],
}
}
///|
fn dae_record_work_family_candidate_effects(
current : @lib.Module,
graph : DaeCurrentBoundaryGraph,
target_def : Int32,
family : DaeWorkFamily,
candidate : DaePassResult,
producer_effects_report : Array[DaeCommitEffects],
) -> Unit {
let dependencies = dae_boundary_dependency_graph_new(graph)
dae_record_producer_effects(
producer_effects_report,
dae_work_item_candidate_effects(
current,
graph,
dae_work_item_new(graph, dependencies, target_def, family),
candidate,
),
)
}
///|
fn dae_record_dead_suffix_parameter_candidate_effects(
current : @lib.Module,
graph : DaeCurrentBoundaryGraph,
target_def : Int32,
candidate : DaePassResult,
producer_effects_report : Array[DaeCommitEffects],
) -> Unit {
let dependencies = dae_boundary_dependency_graph_new(graph)
let effects = dae_work_item_candidate_effects(
current,
graph,
dae_work_item_new(graph, dependencies, target_def, DaeUnusedParameterWork),
candidate,
)
let mut def_idx = 0
while def_idx < effects.structurally_touched.length() {
if def_idx != target_def && effects.structurally_touched[def_idx] {
effects.worth_decided[def_idx] = true
effects.worth_optimizing[def_idx] = true
effects.caller_drop_removed[def_idx] = true
}
def_idx += 1
}
dae_record_producer_effects(producer_effects_report, effects)
}
///|
fn dae_process_work_item(
original : DaeOriginalBoundarySnapshot,
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
graph : DaeCurrentBoundaryGraph,
dependencies : DaeBoundaryDependencyGraph,
item : DaeWorkItem,
diagnostics : DaeDiagnostics,
rewrite_validation_counts~ : Array[Int32]? = None,
numeric_result_validation_chain~ : DaeScalarValidationChain? = None,
numeric_result_validation_work~ : DaeScalarValidationWork? = None,
) -> DaeWorkOutcome {
diagnostics.boundaries_analyzed += 1
if !dae_work_item_epoch_is_current(graph, item) {
dae_diagnostics_record_reason(diagnostics, DaeStaleCandidateFacts)
if item.target_def < 0 || item.target_def >= graph.snapshot.defined {
return DaeWorkNoChange(DaeStaleCandidateFacts)
}
diagnostics.candidate_stale_retries += 1
diagnostics.candidate_requeues += 1
return DaeWorkRequeue([
{
..dae_work_item_new(graph, dependencies, item.target_def, item.family),
allow_immutable_global_get: item.allow_immutable_global_get,
allow_immediate_i32_suffix_fallback: item.allow_immediate_i32_suffix_fallback,
},
])
}
if item.family == DaeForwardingWork {
match
dae_run_forwarded_param_component_transaction_with_graph(
original,
current,
touched,
item.component_members,
graph,
dependencies,
) {
Some(commit) => {
dae_diagnostics_record_commit_effects(diagnostics, commit.effects)
dae_diagnostics_record_applied_effects(diagnostics, commit.effects)
return DaeWorkCommitted({
mod_out: commit.candidate.mod_out,
touched: dae_merge_touched(touched, commit.candidate.touched),
changed: true,
applied: true,
primary_def: item.target_def,
effects: commit.effects,
})
}
None => {
let component_touched = Array::make(graph.snapshot.defined, false)
for def_idx in item.component_members {
if def_idx >= 0 && def_idx < component_touched.length() {
component_touched[def_idx] = true
}
}
match
dae_run_forwarded_param_scc_transaction(current, component_touched) {
Some(candidate) => {
let result : DaePassResult = {
mod_out: candidate.mod_out,
touched: candidate.touched,
changed: true,
primary_def: item.target_def,
}
let mut effects = dae_commit_effects_empty(graph.snapshot.defined)
for def_idx in candidate.defs {
effects = dae_commit_effects_merge(
effects,
dae_work_item_candidate_effects(
current,
graph,
dae_work_item_new(
graph,
dependencies,
def_idx,
DaeUnusedParameterWork,
),
result,
),
)
}
dae_diagnostics_record_commit_effects(diagnostics, effects)
dae_diagnostics_record_applied_effects(diagnostics, effects)
return DaeWorkCommitted({
mod_out: candidate.mod_out,
touched: dae_merge_touched(touched, candidate.touched),
changed: true,
applied: true,
primary_def: item.target_def,
effects,
})
}
None => {
dae_diagnostics_record_reason(diagnostics, DaeNoStrictImprovement)
return DaeWorkNoChange(DaeNoStrictImprovement)
}
}
}
}
}
let producer_effects_report : Array[DaeCommitEffects] = []
let mut checked_candidate : (@lib.Module, @lib.Module)? = None
let produced : (DaePassResult, DaeCommitEffects)? = match item.family {
DaeUnusedParameterWork
| DaeLocalizationRetryWork
| DaePostCleanupBoundaryRevisitWork => {
let candidate = dae_try_rewrite_unread_params_for_def_with_facts(
current,
item.target_def,
graph.snapshot.call_facts,
allow_stack_carried_void_effects=true,
require_localization_retry=item.family == DaeLocalizationRetryWork,
type_indices_override=Some(graph.snapshot.type_indices),
original=Some(original),
graph=Some(graph),
producer_effects_report~,
)
match producer_effects_report.get(0) {
Some(producer_effects) => Some((candidate, producer_effects))
None if !candidate.changed =>
Some((candidate, dae_commit_effects_empty(graph.snapshot.defined)))
None => None
}
}
DaeUniformActualWork => {
let candidate = if item.uniform_values.length() > 0 {
let report : Array[(@lib.Module, @lib.Module)] = []
let candidate = dae_try_rewrite_uniform_values_def_with_facts_once(
current,
original,
graph,
item.target_def,
item.uniform_values,
checked_candidate_report=Some(report),
)
checked_candidate = report.get(0)
candidate
} else {
let report : Array[(@lib.Module, @lib.Module)] = []
let candidate = dae_try_rewrite_exact_literal_def_with_facts_once(
current,
item.target_def,
graph.snapshot.call_facts,
allow_immutable_global_get=item.allow_immutable_global_get,
allow_immediate_i32_suffix_fallback=item.allow_immediate_i32_suffix_fallback,
original=Some(original),
graph=Some(graph),
checked_candidate_report=Some(report),
rewrite_validation_counts~,
)
checked_candidate = report.get(0)
candidate
}
Some(
(
candidate,
dae_work_item_candidate_effects(current, graph, item, candidate),
),
)
}
DaeGcParameterRefinementWork => {
let candidate = dae_try_refine_direct_gc_types_for_def_with_facts(
current,
item.target_def,
graph.snapshot.call_facts,
true,
true,
)
Some(
(
candidate,
dae_work_item_candidate_effects(current, graph, item, candidate),
),
)
}
DaeGcResultRefinementWork => {
let candidate = dae_try_refine_direct_gc_types_for_def_with_facts(
current,
item.target_def,
graph.snapshot.call_facts,
false,
true,
)
Some(
(
candidate,
dae_work_item_candidate_effects(current, graph, item, candidate),
),
)
}
DaeDroppedResultWork => {
let candidate = dae_try_remove_dropped_result_def_with_facts(
current,
item.target_def,
graph.snapshot.call_facts,
boundary_facts=Some({
original,
graph,
incoming_param_live_by_def: [],
incoming_param_live_body_epochs: [],
}),
)
Some(
(
candidate,
dae_work_item_candidate_effects(current, graph, item, candidate),
),
)
}
DaeControlReconstructionWork | DaeTypeRepairRetryWork => {
let candidate = dae_try_refine_direct_gc_types_for_def_with_facts(
current,
item.target_def,
graph.snapshot.call_facts,
false,
true,
)
Some(
(
candidate,
dae_work_item_candidate_effects(current, graph, item, candidate),
),
)
}
DaeForwardingWork | DaeBodyCleanupWork => None
}
match produced {
None => {
dae_diagnostics_record_reason(diagnostics, DaeNoStrictImprovement)
DaeWorkDeferred(DaeNoStrictImprovement)
}
Some((candidate, _)) if !dae_should_scan_work_item_candidate_effects(
candidate,
) => {
dae_diagnostics_record_reason(diagnostics, DaeNoStrictImprovement)
DaeWorkNoChange(DaeNoStrictImprovement)
}
Some((candidate, producer_effects)) => {
let allows_family_fallback = item.family == DaeControlReconstructionWork ||
item.family == DaeTypeRepairRetryWork
let signature_plan = dae_build_signature_rewrite_plan_from_candidate(
original,
current,
graph,
item.target_def,
candidate,
producer_effects,
)
let plan_application = match signature_plan {
Some(plan) =>
dae_apply_rewrite_plan(
current,
graph,
plan,
checked_candidate~,
rewrite_validation_counts~,
numeric_result_validation_chain=if item.family ==
DaeDroppedResultWork {
numeric_result_validation_chain
} else {
None
},
numeric_result_validation_work~,
)
None => {
if !allows_family_fallback {
dae_diagnostics_record_reason(diagnostics, DaeNoStrictImprovement)
return DaeWorkNoChange(DaeNoStrictImprovement)
}
if !dae_family_fallback_candidate_is_body_only(
graph,
candidate.mod_out,
) {
dae_diagnostics_record_reason(
diagnostics,
DaeTypeReconstructionUnavailable,
)
return DaeWorkNoChange(DaeTypeReconstructionUnavailable)
}
let plan = match
dae_build_family_rewrite_plan(
original,
current,
graph,
item.target_def,
candidate,
effects=Some(producer_effects),
) {
Some(plan) => plan
None => {
dae_diagnostics_record_reason(diagnostics, DaeNoStrictImprovement)
return DaeWorkNoChange(DaeNoStrictImprovement)
}
}
dae_apply_family_rewrite_plan(current, graph, plan)
}
}
if plan_application.applied {
dae_diagnostics_record_commit_effects(
diagnostics,
plan_application.effects,
)
dae_diagnostics_record_applied_effects(
diagnostics,
plan_application.effects,
)
DaeWorkCommitted({
mod_out: plan_application.result.mod_out,
touched: dae_merge_touched(touched, plan_application.result.touched),
changed: changed || plan_application.result.changed,
applied: true,
primary_def: plan_application.result.primary_def,
effects: plan_application.effects,
})
} else {
if plan_application.reason == DaeValidationRollback {
diagnostics.validation_rollbacks += 1
}
dae_diagnostics_record_reason(diagnostics, plan_application.reason)
DaeWorkNoChange(plan_application.reason)
}
}
}
}
///|
fn dae_boundary_worklist_all(defined : Int32) -> DaeBoundaryWorklist {
let queue : Array[Int32] = []
let queued = Array::make(defined, false)
let mut def_idx = 0
while def_idx < defined {
queue.push(def_idx)
queued[def_idx] = true
def_idx += 1
}
{
queue,
queued,
cursor: 0,
family_queue: [],
family_queued: [],
family_cursor: 0,
}
}
///|
fn dae_boundary_worklist_families(
graph : DaeCurrentBoundaryGraph,
dependencies : DaeBoundaryDependencyGraph,
families : Array[DaeWorkFamily],
seed_defs~ : Array[Bool]? = None,
) -> DaeBoundaryWorklist {
let family_queue : Array[DaeWorkItem] = []
let family_queued : Array[Array[Bool]] = []
let mut def_idx = 0
while def_idx < graph.snapshot.defined {
family_queued.push(Array::make(11, false))
def_idx += 1
}
let mut component_id = 0
while component_id < dependencies.component_members.length() {
for member_def in dependencies.component_members[component_id] {
let seeded = match seed_defs {
Some(seeds) =>
seeds.length() == graph.snapshot.defined && seeds[member_def]
None => true
}
if !seeded {
continue
}
for family in families {
let slot = dae_work_family_slot(family)
if !family_queued[member_def][slot] {
family_queue.push(
dae_work_item_new(graph, dependencies, member_def, family),
)
family_queued[member_def][slot] = true
}
}
}
component_id += 1
}
{
queue: [],
queued: Array::make(graph.snapshot.defined, false),
cursor: 0,
family_queue,
family_queued,
family_cursor: 0,
}
}
///|
fn dae_boundary_worklist_take(worklist : DaeBoundaryWorklist) -> Int32? {
if worklist.cursor >= worklist.queue.length() {
return None
}
let def_idx = worklist.queue[worklist.cursor]
worklist.cursor += 1
if def_idx >= 0 && def_idx < worklist.queued.length() {
worklist.queued[def_idx] = false
}
Some(def_idx)
}
///|
fn dae_boundary_worklist_requeue(
worklist : DaeBoundaryWorklist,
defs : Array[Int32],
) -> Int32 {
let mut requeued = 0
for def_idx in defs {
if def_idx >= 0 &&
def_idx < worklist.queued.length() &&
!worklist.queued[def_idx] {
worklist.queue.push(def_idx)
worklist.queued[def_idx] = true
requeued += 1
}
}
requeued
}
///|
fn dae_boundary_worklist_take_item(
worklist : DaeBoundaryWorklist,
) -> DaeWorkItem? {
if worklist.family_cursor >= worklist.family_queue.length() {
return None
}
let item = worklist.family_queue[worklist.family_cursor]
worklist.family_cursor += 1
if item.target_def >= 0 && item.target_def < worklist.family_queued.length() {
let slot = dae_work_family_slot(item.family)
worklist.family_queued[item.target_def][slot] = false
}
Some(item)
}
///|
fn dae_boundary_worklist_requeue_items(
worklist : DaeBoundaryWorklist,
items : Array[DaeWorkItem],
) -> Int32 {
let mut requeued = 0
for item in items {
if item.target_def < 0 || item.target_def >= worklist.family_queued.length() {
continue
}
let slot = dae_work_family_slot(item.family)
if !worklist.family_queued[item.target_def][slot] {
worklist.family_queue.push(item)
worklist.family_queued[item.target_def][slot] = true
requeued += 1
}
}
requeued
}
///|
fn dae_work_items_for_defs(
graph : DaeCurrentBoundaryGraph,
dependencies : DaeBoundaryDependencyGraph,
defs : Array[Int32],
family : DaeWorkFamily,
) -> Array[DaeWorkItem] {
let items : Array[DaeWorkItem] = []
for def_idx in defs {
if def_idx >= 0 && def_idx < graph.snapshot.defined {
items.push(dae_work_item_new(graph, dependencies, def_idx, family))
}
}
items
}
///|
fn dae_followup_work_items_for_commit(
graph : DaeCurrentBoundaryGraph,
dependencies : DaeBoundaryDependencyGraph,
committed_item : DaeWorkItem,
effects : DaeCommitEffects,
) -> Array[DaeWorkItem] {
let target = committed_item.target_def
if target < 0 || target >= graph.snapshot.defined {
return []
}
match committed_item.family {
DaeGcParameterRefinementWork
| DaeGcResultRefinementWork
| DaeDroppedResultWork => {
let followups : Array[DaeWorkItem] = []
if target < effects.control_refinalization_required.length() &&
effects.control_refinalization_required[target] {
followups.push(
dae_work_item_new(
graph,
dependencies,
target,
DaeControlReconstructionWork,
),
)
}
if effects.type_section_changed {
followups.push(
dae_work_item_new(graph, dependencies, target, DaeTypeRepairRetryWork),
)
}
followups
}
_ => []
}
}
///|
fn dae_apply_commit_followups(
original : DaeOriginalBoundarySnapshot,
application : DaeCoreResultApplication,
committed_item : DaeWorkItem,
diagnostics : DaeDiagnostics,
) -> DaeCoreResultApplication {
let mut current = application.mod_out
let mut touched = application.touched
let mut changed = application.changed
let mut effects = application.effects
let graph = dae_current_boundary_graph_new(current)
let mut dependencies = dae_boundary_dependency_graph_new(graph)
let queue = dae_followup_work_items_for_commit(
graph,
dependencies,
committed_item,
application.effects,
)
let mut cursor = 0
while cursor < queue.length() {
let item = queue[cursor]
cursor += 1
match
dae_process_work_item(
original, current, touched, changed, graph, dependencies, item, diagnostics,
) {
DaeWorkCommitted(repair) => {
current = repair.mod_out
touched = repair.touched
changed = repair.changed
effects = dae_commit_effects_merge(effects, repair.effects)
dae_current_boundary_graph_refresh(graph, current)
dependencies = dae_boundary_dependency_graph_new(graph)
}
DaeWorkRequeue(items) =>
for refreshed in items {
queue.push(refreshed)
}
DaeWorkNoChange(_) | DaeWorkDeferred(_) => ()
}
}
{
mod_out: current,
touched,
changed,
applied: application.applied,
primary_def: application.primary_def,
effects,
}
}
///|
fn dae_boundary_facts_for_epoch(
graph : DaeCurrentBoundaryGraph,
expected_call_epoch : Int32,
facts_override : DaeCallBoundaryFacts?,
) -> DaeCallBoundaryFacts {
if expected_call_epoch >= 0 && expected_call_epoch != graph.call_epoch {
return graph.snapshot.boundary_facts
}
match facts_override {
Some(facts) => facts
None => graph.snapshot.boundary_facts
}
}
///|
fn dae_call_boundary_allows_forwarding(
facts : DaeCallBoundaryFacts,
caller_abs : Int32,
) -> Bool {
if caller_abs < 0 {
return false
}
if caller_abs < facts.escaped.length() && facts.escaped[caller_abs] {
return false
}
if caller_abs < facts.tail_calls.length() && facts.tail_calls[caller_abs] {
return false
}
true
}
///|
fn dae_call_boundary_direct_callers(
facts : DaeCallBoundaryFacts,
callee_abs : Int32,
) -> Array[Int32] {
if callee_abs >= 0 && callee_abs < facts.direct_callers.length() {
facts.direct_callers[callee_abs]
} else {
[]
}
}
///|
fn dae_call_boundary_count_at(
counts : Array[Int32],
callee_abs : Int32,
) -> Int32 {
if callee_abs >= 0 && callee_abs < counts.length() {
counts[callee_abs]
} else {
0
}
}
///|
fn dae_target_callsite_evidence_is_consistent(
evidence : DaeTargetCallsiteEvidence,
callee_abs : Int32,
mode : DaeTargetCallsiteMode,
) -> Bool {
evidence.callee_abs == callee_abs &&
evidence.callee_abs >= 0 &&
(match mode {
DaeTargetDirectCallOnly => !evidence.is_return_call
DaeTargetDirectOrReturnCall => true
})
}
///|
fn dae_target_callsite_evidence(
instr : @lib.Instruction,
callee_abs : Int32,
mode : DaeTargetCallsiteMode,
) -> DaeTargetCallsiteEvidence? {
if callee_abs < 0 {
return None
}
let make = fn(is_return_call : Bool) -> DaeTargetCallsiteEvidence? {
let evidence = { callee_abs, is_return_call, }
if dae_target_callsite_evidence_is_consistent(evidence, callee_abs, mode) {
Some(evidence)
} else {
None
}
}
match instr {
@lib.Call(func_idx) if dae_func_idx_slot(func_idx) == callee_abs =>
make(false)
@lib.ReturnCall(func_idx) if dae_func_idx_slot(func_idx) == callee_abs =>
make(true)
_ => None
}
}
///|
fn dae_call_boundary_direct_callers_contain_def(
direct_callers : Array[Int32],
def_idx : Int32,
) -> Bool {
if def_idx < 0 {
return false
}
for direct_caller in direct_callers {
if direct_caller == def_idx {
return true
}
}
false
}
///|
fn dae_forwarded_uniform_caller_is_recursive(
facts : DaeCallBoundaryFacts,
caller_abs : Int32,
imported : Int32,
) -> Bool {
if caller_abs < 0 || caller_abs >= facts.direct_callers.length() {
return false
}
dae_call_boundary_direct_callers_contain_def(
facts.direct_callers[caller_abs],
caller_abs - imported,
)
}
///|
fn dae_call_boundary_candidate(
facts : DaeCallBoundaryFacts,
callee_abs : Int32,
def_idx~ : Int32 = -1,
) -> DaeCallBoundaryCandidate? {
if callee_abs < 0 ||
callee_abs >= facts.escaped.length() ||
callee_abs >= facts.tail_calls.length() ||
callee_abs >= facts.direct_callers.length() {
return None
}
let escaped = facts.escaped[callee_abs]
let tail_call = facts.tail_calls[callee_abs]
let direct_callers = facts.direct_callers[callee_abs]
let direct_call_count = dae_call_boundary_count_at(
facts.direct_calls,
callee_abs,
)
let has_self_direct_caller = dae_call_boundary_direct_callers_contain_def(
direct_callers, def_idx,
)
Some({
owned: !escaped,
tail_callee: tail_call,
decision_reason: if escaped {
DaeVisibleBoundary
} else if tail_call {
DaeDirectTailDependency
} else {
DaeCandidateProduced
},
direct_call_count,
dropped_call_count: dae_call_boundary_count_at(
facts.dropped_calls,
callee_abs,
),
direct_callers,
has_self_direct_caller,
allows_nonself_direct_rewrite: direct_call_count > 0 &&
!has_self_direct_caller,
})
}
///|
fn dae_dropped_result_candidate_from_boundary_facts(
facts : DaeCallBoundaryFacts,
callee_abs : Int32,
) -> DaeDroppedResultCandidate? {
let boundary_candidate = match
dae_call_boundary_candidate(facts, callee_abs) {
Some(candidate) => candidate
None => return None
}
let all_direct_calls_dropped = boundary_candidate.direct_call_count > 0 &&
boundary_candidate.dropped_call_count ==
boundary_candidate.direct_call_count
Some({
callee_abs,
direct_call_count: boundary_candidate.direct_call_count,
dropped_call_count: boundary_candidate.dropped_call_count,
direct_callers: boundary_candidate.direct_callers,
owned: boundary_candidate.owned && !boundary_candidate.tail_callee,
all_direct_calls_dropped,
owned_dropped_result_candidate: boundary_candidate.owned &&
!boundary_candidate.tail_callee &&
all_direct_calls_dropped,
})
}
///|
fn dae_dropped_result_candidate_from_current_call_facts(
call_facts : DaeCurrentCallFacts,
callee_abs : Int32,
) -> DaeDroppedResultCandidate? {
dae_dropped_result_candidate_from_boundary_facts(
dae_call_boundary_facts_from_current(call_facts),
callee_abs,
)
}
///|
fn dae_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
}
///|
fn dae_defined_func_count(mod_ : @lib.Module) -> Int32 {
match mod_.func_sec {
Some(@lib.FuncSec(type_idxs)) => type_idxs.length()
None => 0
}
}
///|
fn dae_func_idx_slot(idx : @lib.FuncIdx) -> Int32 {
let @lib.FuncIdx(raw) = idx
raw.reinterpret_as_int()
}
///|
fn dae_global_idx_slot(idx : @lib.GlobalIdx) -> Int32 {
let @lib.GlobalIdx(raw) = idx
raw.reinterpret_as_int()
}
///|
fn dae_local_idx_slot(idx : @lib.LocalIdx) -> Int32 {
let @lib.LocalIdx(raw) = idx
raw.reinterpret_as_int()
}
///|
fn dae_rewrite_local_idx(
raw : Int32,
local_map : Array[Int32],
) -> @lib.LocalIdx {
@lib.LocalIdx::new(local_map[raw].reinterpret_as_uint())
}
///|
fn dae_collect_func_type_indices(mod_ : @lib.Module) -> Array[@lib.TypeIdx] {
let out : Array[@lib.TypeIdx] = []
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
match import_ {
@lib.Import(_, _, @lib.FuncExternType(type_idx)) => out.push(type_idx)
_ => ()
}
}
None => ()
}
match mod_.func_sec {
Some(@lib.FuncSec(type_idxs)) => out.append(type_idxs)
None => ()
}
out
}
///|
fn dae_type_sec_flattened_length(type_sec : @lib.TypeSec) -> Int32 {
let @lib.TypeSec(rec_types) = type_sec
let mut length = 0
for rec_type in rec_types {
match rec_type {
@lib.SingleRecType(_) => length += 1
@lib.GroupRecType(group) => length += group.length()
}
}
length
}
///|
fn dae_type_sec_rec_entry_location(
type_sec : @lib.TypeSec,
type_idx : @lib.TypeIdx,
) -> (Int32, Int32)? {
let target = match type_idx {
@lib.TypeIdx(raw) => raw.reinterpret_as_int()
@lib.RecIdx(_) => return None
}
if target < 0 {
return None
}
let @lib.TypeSec(rec_types) = type_sec
let mut flat_idx = 0
let mut entry_idx = 0
while entry_idx < rec_types.length() {
match rec_types[entry_idx] {
@lib.SingleRecType(_) => {
if flat_idx == target {
return Some((entry_idx, 0))
}
flat_idx += 1
}
@lib.GroupRecType(group) => {
let group_end = flat_idx + group.length()
if target >= flat_idx && target < group_end {
return Some((entry_idx, target - flat_idx))
}
flat_idx = group_end
}
}
entry_idx += 1
}
None
}
///|
fn dae_type_sec_subtype(
type_sec : @lib.TypeSec,
type_idx : @lib.TypeIdx,
type_visits~ : Array[Int32]? = None,
) -> @lib.SubType? {
let target = match type_idx {
@lib.TypeIdx(raw) => raw.reinterpret_as_int()
@lib.RecIdx(_) => return None
}
if target < 0 {
return None
}
let @lib.TypeSec(rec_types) = type_sec
let mut flat_idx = 0
for rec_type in rec_types {
match type_visits {
Some(count) => count[0] += 1
None => ()
}
match rec_type {
@lib.SingleRecType(subtype) => {
if flat_idx == target {
return Some(subtype)
}
flat_idx += 1
}
@lib.GroupRecType(group) => {
let group_end = flat_idx + group.length()
if target >= flat_idx && target < group_end {
return Some(group[target - flat_idx])
}
flat_idx = group_end
}
}
}
None
}
///|
fn dae_type_sec_func_type(
type_sec : @lib.TypeSec,
type_idx : @lib.TypeIdx,
type_visits~ : Array[Int32]? = None,
) -> @lib.FuncType? {
match dae_type_sec_subtype(type_sec, type_idx, type_visits~) {
Some(subtype) =>
match subtype.get_comptype() {
@lib.FuncCompType(params, results) =>
Some(@lib.FuncType::new(params, results))
_ => None
}
None => None
}
}
///|
fn dae_control_block_type_signature_evidence_from_type_sec(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
) -> DaeControlBlockTypeSignatureEvidence? {
match block_type {
@lib.VoidBlockType =>
Some({
params: [],
results: [],
input_count: 0,
output_count: 0,
type_idx: None,
})
@lib.ValTypeBlockType(result) =>
Some({
params: [],
results: [result],
input_count: 0,
output_count: 1,
type_idx: None,
})
@lib.TypeIdxBlockType(type_idx) =>
match type_sec {
Some(type_sec) =>
match dae_type_sec_func_type(type_sec, type_idx) {
Some(@lib.FuncType(params, results)) =>
Some({
params,
results,
input_count: params.length(),
output_count: results.length(),
type_idx: Some(type_idx),
})
None => None
}
None => None
}
}
}
///|
fn dae_control_block_type_signature_evidence(
block_type : @lib.BlockType,
mod_ : @lib.Module,
) -> DaeControlBlockTypeSignatureEvidence? {
dae_control_block_type_signature_evidence_from_type_sec(
block_type,
mod_.type_sec,
)
}
///|
fn dae_control_block_type_stack_shape_evidence(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
input_count : Int32,
output_count : Int32,
) -> DaeControlBlockTypeSignatureEvidence? {
match
dae_control_block_type_signature_evidence_from_type_sec(
block_type, type_sec,
) {
Some(evidence) if evidence.input_count == input_count &&
evidence.output_count == output_count => Some(evidence)
_ => None
}
}
///|
fn dae_control_block_type_stack_shape_accepts(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
input_count : Int32,
output_count : Int32,
) -> Bool {
match
dae_control_block_type_stack_shape_evidence(
block_type, type_sec, input_count, output_count,
) {
Some(_) => true
None => false
}
}
///|
fn dae_control_block_type_exact_signature_evidence(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
params : Array[@lib.ValType],
results : Array[@lib.ValType],
) -> DaeControlBlockTypeSignatureEvidence? {
match
dae_control_block_type_stack_shape_evidence(
block_type,
type_sec,
params.length(),
results.length(),
) {
Some(evidence) if evidence.params == params && evidence.results == results =>
Some(evidence)
_ => None
}
}
///|
fn dae_control_block_type_exact_signature_accepts(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
params : Array[@lib.ValType],
results : Array[@lib.ValType],
) -> Bool {
match
dae_control_block_type_exact_signature_evidence(
block_type, type_sec, params, results,
) {
Some(_) => true
None => false
}
}
///|
fn dae_control_single_result_block_type_evidence(
block_type : @lib.BlockType,
mod_ : @lib.Module,
) -> DaeControlBlockTypeSignatureEvidence? {
dae_control_block_type_stack_shape_evidence(block_type, mod_.type_sec, 0, 1)
}
///|
fn dae_control_block_type_zero_input_evidence(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
) -> DaeControlBlockTypeSignatureEvidence? {
match
dae_control_block_type_signature_evidence_from_type_sec(
block_type, type_sec,
) {
Some(evidence) if evidence.input_count == 0 => Some(evidence)
_ => None
}
}
///|
fn dae_control_block_type_zero_input_output_count_evidence(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
output_count : Int32,
) -> DaeControlBlockTypeSignatureEvidence? {
if output_count < 0 {
return None
}
dae_control_block_type_stack_shape_evidence(
block_type, type_sec, 0, output_count,
)
}
///|
fn dae_val_type_is_non_null_concrete_gc_ref_candidate(
vt : @lib.ValType,
) -> Bool {
match vt {
@lib.RefTypeValType(@lib.HeapTypeRefType(false, _, @lib.HeapType(_))) =>
true
_ => false
}
}
///|
fn dae_control_block_type_non_null_concrete_gc_result_from_signature_evidence(
signature : DaeControlBlockTypeSignatureEvidence,
result_index : Int32,
) -> DaeControlBlockTypeGcResultEvidence? {
if result_index < 0 || result_index >= signature.output_count {
return None
}
let candidate = signature.results[result_index]
if dae_val_type_is_non_null_concrete_gc_ref_candidate(candidate) {
Some({ signature, result_index, candidate, })
} else {
None
}
}
///|
fn dae_control_block_type_non_null_concrete_gc_result_evidence(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
result_index : Int32,
) -> DaeControlBlockTypeGcResultEvidence? {
match
dae_control_block_type_signature_evidence_from_type_sec(
block_type, type_sec,
) {
Some(signature) =>
dae_control_block_type_non_null_concrete_gc_result_from_signature_evidence(
signature, result_index,
)
None => None
}
}
///|
fn dae_control_block_type_non_null_concrete_gc_result_slot_evidence(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
slot : DaeControlBlockTypeGcResultSlot,
) -> DaeControlBlockTypeGcResultEvidence? {
let signature = match
dae_control_block_type_signature_evidence_from_type_sec(
block_type, type_sec,
) {
Some(signature) => signature
None => return None
}
let result_index = match slot {
DaeSingleControlGcResultSlot =>
if signature.output_count == 1 {
0
} else {
return None
}
DaeLastControlGcResultSlot =>
if signature.output_count > 0 {
signature.output_count - 1
} else {
return None
}
}
dae_control_block_type_non_null_concrete_gc_result_evidence(
block_type, type_sec, result_index,
)
}
///|
fn dae_control_block_type_single_non_null_concrete_gc_result_evidence(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
) -> DaeControlBlockTypeGcResultEvidence? {
dae_control_block_type_non_null_concrete_gc_result_slot_evidence(
block_type,
type_sec,
DaeSingleControlGcResultSlot,
)
}
///|
fn dae_control_block_type_last_non_null_concrete_gc_result_evidence(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
) -> DaeControlBlockTypeGcResultEvidence? {
dae_control_block_type_non_null_concrete_gc_result_slot_evidence(
block_type,
type_sec,
DaeLastControlGcResultSlot,
)
}
///|
fn dae_resolve_func_type(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_abs : Int32,
) -> @lib.FuncType? {
if func_abs < 0 || func_abs >= func_type_indices.length() {
return None
}
match mod_.type_sec {
Some(type_sec) =>
dae_type_sec_func_type(type_sec, func_type_indices[func_abs])
None => None
}
}
///|
#warnings("-unused_value")
fn dae_collect_exported_funcs(
mod_ : @lib.Module,
total_funcs : Int32,
) -> Array[Bool] {
let escaped = Array::make(total_funcs, false)
match mod_.export_sec {
Some(@lib.ExportSec(exports)) =>
for export_ in exports {
match export_ {
@lib.Export(_, @lib.FuncExternIdx(func_idx)) => {
let slot = dae_func_idx_slot(func_idx)
if slot >= 0 && slot < escaped.length() {
escaped[slot] = true
}
}
_ => ()
}
}
None => ()
}
match mod_.start_sec {
Some(@lib.StartSec(func_idx)) => {
let slot = dae_func_idx_slot(func_idx)
if slot >= 0 && slot < escaped.length() {
escaped[slot] = true
}
}
None => ()
}
escaped
}
///|
#warnings("-unused_value")
fn dae_mark_elem_escapes(kind : @lib.ElemKind, escaped : Array[Bool]) -> Unit {
match kind {
@lib.FuncsElemKind(funcs) =>
for func_idx in funcs {
let slot = dae_func_idx_slot(func_idx)
if slot >= 0 && slot < escaped.length() {
escaped[slot] = true
}
}
@lib.FuncExprsElemKind(exprs) | @lib.TypedExprsElemKind(_, exprs) =>
for expr in exprs {
let @lib.Expr(instrs) = expr
dae_scan_escapes_instrs(instrs, escaped)
}
}
}
///|
fn dae_scan_escapes_instrs(
instrs : Array[@lib.Instruction],
escaped : Array[Bool],
) -> Unit {
for instr in instrs {
match instr {
@lib.RefFunc(func_idx) => {
let slot = dae_func_idx_slot(func_idx)
if slot >= 0 && slot < escaped.length() {
escaped[slot] = true
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_scan_escapes_instrs(body, escaped)
@lib.Try(_, @lib.Expr(body), catches, _) => {
dae_scan_escapes_instrs(body, escaped)
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(_, @lib.Expr(catch_body))
| @lib.LegacyCatchAll(@lib.Expr(catch_body)) =>
dae_scan_escapes_instrs(catch_body, escaped)
}
}
}
@lib.If(_, then_body, Some(else_body)) => {
dae_scan_escapes_instrs(then_body, escaped)
dae_scan_escapes_instrs(else_body, escaped)
}
@lib.If(_, then_body, None) => dae_scan_escapes_instrs(then_body, escaped)
_ => ()
}
}
}
///|
fn dae_mark_ref_func_exposure_instrs(
instrs : Array[@lib.Instruction],
exposure : Array[DaeFunctionExposure],
from_module_code : Bool,
walk_visits~ : Array[Int32]? = None,
) -> Unit {
for instr in instrs {
match walk_visits {
Some(visits) => visits[0] += 1
None => ()
}
match instr {
@lib.RefFunc(func_idx) => {
let slot = dae_func_idx_slot(func_idx)
if slot >= 0 && slot < exposure.length() {
if from_module_code {
exposure[slot].ref_func_module = true
} else {
exposure[slot].ref_func_body = true
}
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_mark_ref_func_exposure_instrs(
body,
exposure,
from_module_code,
walk_visits~,
)
@lib.Try(_, @lib.Expr(body), catches, _) => {
dae_mark_ref_func_exposure_instrs(
body,
exposure,
from_module_code,
walk_visits~,
)
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(_, @lib.Expr(catch_body))
| @lib.LegacyCatchAll(@lib.Expr(catch_body)) =>
dae_mark_ref_func_exposure_instrs(
catch_body,
exposure,
from_module_code,
walk_visits~,
)
}
}
}
@lib.If(_, then_body, Some(else_body)) => {
dae_mark_ref_func_exposure_instrs(
then_body,
exposure,
from_module_code,
walk_visits~,
)
dae_mark_ref_func_exposure_instrs(
else_body,
exposure,
from_module_code,
walk_visits~,
)
}
@lib.If(_, then_body, None) =>
dae_mark_ref_func_exposure_instrs(
then_body,
exposure,
from_module_code,
walk_visits~,
)
_ => ()
}
}
}
///|
fn dae_mark_elem_exposure(
mode : @lib.ElemMode,
kind : @lib.ElemKind,
exposure : Array[DaeFunctionExposure],
) -> Unit {
let mark_slot = fn(slot : Int32) {
if slot < 0 || slot >= exposure.length() {
return
}
match mode {
@lib.Active(_, _) => exposure[slot].active_element = true
@lib.Passive => exposure[slot].passive_element = true
@lib.Declarative => exposure[slot].declarative_element = true
}
}
match kind {
@lib.FuncsElemKind(funcs) =>
for func_idx in funcs {
mark_slot(dae_func_idx_slot(func_idx))
}
@lib.FuncExprsElemKind(exprs) | @lib.TypedExprsElemKind(_, exprs) =>
for expr in exprs {
let @lib.Expr(instrs) = expr
for instr in instrs {
match instr {
@lib.RefFunc(func_idx) => mark_slot(dae_func_idx_slot(func_idx))
_ => ()
}
}
dae_mark_ref_func_exposure_instrs(instrs, exposure, true)
}
}
}
///|
fn dae_collect_original_exposure(
mod_ : @lib.Module,
total_funcs : Int32,
type_indices : Array[@lib.TypeIdx],
body_ref_walk_visits~ : Array[Int32]? = None,
include_body~ : Bool = true,
) -> Array[DaeFunctionExposure] {
let exposure : Array[DaeFunctionExposure] = []
let mut idx = 0
while idx < total_funcs {
exposure.push(dae_function_exposure_empty())
idx += 1
}
let imported = dae_imported_func_count(mod_)
let mut import_idx = 0
while import_idx < imported && import_idx < exposure.length() {
exposure[import_idx].imported = true
import_idx += 1
}
match mod_.export_sec {
Some(@lib.ExportSec(exports)) =>
for export_ in exports {
match export_ {
@lib.Export(_, @lib.FuncExternIdx(func_idx)) => {
let slot = dae_func_idx_slot(func_idx)
if slot >= 0 && slot < exposure.length() {
exposure[slot].exported = true
}
}
_ => ()
}
}
None => ()
}
match mod_.start_sec {
Some(@lib.StartSec(func_idx)) => {
let slot = dae_func_idx_slot(func_idx)
if slot >= 0 && slot < exposure.length() {
exposure[slot].start = true
}
}
None => ()
}
if include_body {
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
for func in funcs {
let @lib.Func(_, @lib.Expr(body)) = func
dae_mark_ref_func_exposure_instrs(
body,
exposure,
false,
walk_visits=body_ref_walk_visits,
)
}
None => ()
}
}
match mod_.global_sec {
Some(@lib.GlobalSec(globals)) =>
for global_ in globals {
let @lib.Global(_, @lib.Expr(init)) = global_
dae_mark_ref_func_exposure_instrs(init, exposure, true)
}
None => ()
}
match mod_.table_sec {
Some(@lib.TableSec(tables)) =>
for table in tables {
match table {
@lib.Table(_, Some(@lib.Expr(init))) =>
dae_mark_ref_func_exposure_instrs(init, exposure, true)
_ => ()
}
}
None => ()
}
match mod_.elem_sec {
Some(@lib.ElemSec(elems)) =>
for elem in elems {
let @lib.Elem(mode, kind) = elem
dae_mark_elem_exposure(mode, kind, exposure)
}
None => ()
}
ignore(type_indices)
exposure
}
///|
fn dae_exposure_to_escaped(
exposure : Array[DaeFunctionExposure],
) -> Array[Bool] {
let escaped = Array::make(exposure.length(), false)
let mut idx = 0
while idx < exposure.length() {
escaped[idx] = dae_function_exposure_blocks_boundary(exposure[idx])
idx += 1
}
escaped
}
///|
fn dae_collect_escapes(mod_ : @lib.Module, total_funcs : Int32) -> Array[Bool] {
let type_indices = dae_collect_func_type_indices(mod_)
dae_exposure_to_escaped(
dae_collect_original_exposure(mod_, total_funcs, type_indices),
)
}
///|
fn dae_make_direct_callers(total_funcs : Int32) -> Array[Array[Int32]] {
let direct_callers : Array[Array[Int32]] = []
let mut idx = 0
while idx < total_funcs {
direct_callers.push([])
idx += 1
}
direct_callers
}
///|
fn dae_new_function_info(has_unseen_calls : Bool) -> DaeFunctionInfo {
{
stale: true,
unused_params: [],
calls: [],
dropped_calls: [],
has_tail_calls: false,
tail_callees: [],
loop_type_indices: [],
has_adjacent_local_set_get: false,
has_unseen_calls,
}
}
///|
fn dae_make_function_infos(
total_funcs : Int32,
escaped : Array[Bool],
) -> Array[DaeFunctionInfo] {
let infos : Array[DaeFunctionInfo] = []
let mut idx = 0
while idx < total_funcs {
infos.push(dae_new_function_info(idx < escaped.length() && escaped[idx]))
idx += 1
}
infos
}
///|
fn dae_callsite_child_path(
path : Array[Int32],
instr_idx : Int32,
child_kind : Int32,
) -> Array[Int32] {
let child = path.copy()
child.push(instr_idx)
child.push(child_kind)
child
}
///|
fn dae_callsite_path(path : Array[Int32], instr_idx : Int32) -> DaeStablePath {
dae_stable_path_from_array(path).append(instr_idx)
}
///|
fn dae_function_info_add_tail_callee(
info : DaeFunctionInfo,
callee_abs : Int32,
) -> Unit {
if !info.tail_callees.contains(callee_abs) {
info.tail_callees.push(callee_abs)
}
}
///|
fn dae_function_info_add_loop_type_index(
info : DaeFunctionInfo,
type_idx : @lib.TypeIdx,
) -> Unit {
if !info.loop_type_indices.contains(type_idx) {
info.loop_type_indices.push(type_idx)
}
}
///|
fn dae_scan_current_call_facts_instrs(
instrs : Array[@lib.Instruction],
caller_def : Int32,
caller_abs : Int32,
path : Array[Int32],
direct_calls : Array[Int32],
tail_calls : Array[Bool],
direct_callers : Array[Array[Int32]],
dropped_calls : Array[Int32],
escaped : Array[Bool],
function_infos : Array[DaeFunctionInfo],
caller_seen_stamps : Array[Int32],
active : Bool,
topology_out~ : DaeCurrentTopologyExtras? = None,
body_exposure_out~ : Array[DaeFunctionExposure]? = None,
stable_path~ : DaeStablePath? = None,
) -> Bool {
let mut idx = 0
let mut current_active = active
let singleton = instrs.length() == 1
let mut singleton_trap = false
while idx < instrs.length() {
if idx + 1 < instrs.length() &&
caller_abs >= 0 &&
caller_abs < function_infos.length() {
match (instrs[idx], instrs[idx + 1]) {
(@lib.LocalSet(target), @lib.LocalGet(read)) if target == read =>
function_infos[caller_abs].has_adjacent_local_set_get = true
_ => ()
}
}
match instrs[idx] {
@lib.Unreachable => {
current_active = false
singleton_trap = singleton
}
@lib.Call(func_idx) => {
let slot = dae_func_idx_slot(func_idx)
if slot >= 0 && slot < direct_calls.length() {
let dropped = current_active &&
idx + 1 < instrs.length() &&
instrs[idx + 1] == @lib.Instruction::drop()
if slot < function_infos.length() {
let callsite = {
caller_def,
path: match stable_path {
Some(prefix) => prefix.append(idx)
None => dae_callsite_path(path, idx)
},
callee_abs: slot,
active: current_active,
dropped,
tail: false,
}
function_infos[slot].calls.push(callsite)
if dropped {
function_infos[slot].dropped_calls.push(callsite)
}
}
if current_active {
direct_calls[slot] = direct_calls[slot] + 1
if dropped {
dropped_calls[slot] = dropped_calls[slot] + 1
}
}
if caller_seen_stamps[slot] != caller_def {
direct_callers[slot].push(caller_def)
caller_seen_stamps[slot] = caller_def
}
}
}
@lib.ReturnCall(func_idx) =>
if current_active {
let slot = dae_func_idx_slot(func_idx)
match topology_out {
Some(topology) => {
if caller_abs >= 0 &&
caller_abs < topology.has_outgoing_return_call.length() {
topology.has_outgoing_return_call[caller_abs] = true
}
if slot >= 0 && slot < topology.direct_return_callers.length() {
dae_adjacency_add_unique(
topology.direct_return_callers,
slot,
caller_abs,
0,
topology.adjacency_membership,
)
if caller_abs >= 0 &&
caller_abs < topology.direct_return_callees_by_caller.length() {
dae_adjacency_add_unique(
topology.direct_return_callees_by_caller,
caller_abs,
slot,
1,
topology.adjacency_membership,
)
}
}
}
None => ()
}
if slot >= 0 && slot < direct_calls.length() {
direct_calls[slot] = direct_calls[slot] + 1
tail_calls[slot] = true
if slot < function_infos.length() {
let callsite = {
caller_def,
path: match stable_path {
Some(prefix) => prefix.append(idx)
None => dae_callsite_path(path, idx)
},
callee_abs: slot,
active: true,
dropped: false,
tail: true,
}
function_infos[slot].calls.push(callsite)
}
if caller_abs >= 0 && caller_abs < function_infos.length() {
function_infos[caller_abs].has_tail_calls = true
dae_function_info_add_tail_callee(
function_infos[caller_abs],
slot,
)
}
if caller_seen_stamps[slot] != caller_def {
direct_callers[slot].push(caller_def)
caller_seen_stamps[slot] = caller_def
}
}
}
@lib.ReturnCallIndirect(_, _) => {
if current_active {
match topology_out {
Some(topology) =>
if caller_abs >= 0 &&
caller_abs < topology.has_outgoing_return_call_indirect.length() {
topology.has_outgoing_return_call_indirect[caller_abs] = true
}
None => ()
}
}
if current_active &&
caller_abs >= 0 &&
caller_abs < function_infos.length() {
function_infos[caller_abs].has_tail_calls = true
}
}
@lib.ReturnCallRef(_) => {
if current_active {
match topology_out {
Some(topology) =>
if caller_abs >= 0 &&
caller_abs < topology.has_outgoing_return_call_ref.length() {
topology.has_outgoing_return_call_ref[caller_abs] = true
}
None => ()
}
}
if current_active &&
caller_abs >= 0 &&
caller_abs < function_infos.length() {
function_infos[caller_abs].has_tail_calls = true
}
}
@lib.CallIndirect(_, _) =>
if current_active {
match topology_out {
Some(topology) =>
if caller_abs >= 0 &&
caller_abs < topology.has_outgoing_call_indirect.length() {
topology.has_outgoing_call_indirect[caller_abs] = true
}
None => ()
}
}
@lib.CallRef(_) =>
if current_active {
match topology_out {
Some(topology) =>
if caller_abs >= 0 &&
caller_abs < topology.has_outgoing_call_ref.length() {
topology.has_outgoing_call_ref[caller_abs] = true
}
None => ()
}
}
@lib.RefFunc(func_idx) => {
let slot = dae_func_idx_slot(func_idx)
if slot >= 0 && slot < escaped.length() {
escaped[slot] = true
match body_exposure_out {
Some(exposure) if slot < exposure.length() =>
exposure[slot].ref_func_body = true
_ => ()
}
if slot < function_infos.length() {
function_infos[slot].has_unseen_calls = true
}
}
}
@lib.Block(_, @lib.Expr(body)) => {
let child_trap = dae_scan_current_call_facts_child(
body,
caller_def,
caller_abs,
path,
idx,
0,
direct_calls,
tail_calls,
direct_callers,
dropped_calls,
escaped,
function_infos,
caller_seen_stamps,
current_active,
topology_out~,
body_exposure_out~,
stable_path~,
)
// A singleton Block chain ending in Unreachable contains no calls;
// its summary is available after this required child scan. Other
// structured controls retain their existing conservative admission.
if child_trap {
current_active = false
singleton_trap = singleton
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
match block_type {
@lib.TypeIdxBlockType(type_idx) =>
if caller_abs >= 0 && caller_abs < function_infos.length() {
dae_function_info_add_loop_type_index(
function_infos[caller_abs],
type_idx,
)
}
_ => ()
}
ignore(
dae_scan_current_call_facts_child(
body,
caller_def,
caller_abs,
path,
idx,
1,
direct_calls,
tail_calls,
direct_callers,
dropped_calls,
escaped,
function_infos,
caller_seen_stamps,
current_active,
topology_out~,
body_exposure_out~,
stable_path~,
),
)
}
@lib.If(_, then_body, Some(else_body)) => {
ignore(
dae_scan_current_call_facts_child(
then_body,
caller_def,
caller_abs,
path,
idx,
2,
direct_calls,
tail_calls,
direct_callers,
dropped_calls,
escaped,
function_infos,
caller_seen_stamps,
current_active,
topology_out~,
body_exposure_out~,
stable_path~,
),
)
ignore(
dae_scan_current_call_facts_child(
else_body,
caller_def,
caller_abs,
path,
idx,
3,
direct_calls,
tail_calls,
direct_callers,
dropped_calls,
escaped,
function_infos,
caller_seen_stamps,
current_active,
topology_out~,
body_exposure_out~,
stable_path~,
),
)
}
@lib.If(_, then_body, None) =>
ignore(
dae_scan_current_call_facts_child(
then_body,
caller_def,
caller_abs,
path,
idx,
2,
direct_calls,
tail_calls,
direct_callers,
dropped_calls,
escaped,
function_infos,
caller_seen_stamps,
current_active,
topology_out~,
body_exposure_out~,
stable_path~,
),
)
@lib.TryTable(_, _, @lib.Expr(body)) =>
ignore(
dae_scan_current_call_facts_child(
body,
caller_def,
caller_abs,
path,
idx,
4,
direct_calls,
tail_calls,
direct_callers,
dropped_calls,
escaped,
function_infos,
caller_seen_stamps,
current_active,
topology_out~,
body_exposure_out~,
stable_path~,
),
)
@lib.Try(_, @lib.Expr(body), catches, _) => {
ignore(
dae_scan_current_call_facts_child(
body,
caller_def,
caller_abs,
path,
idx,
4,
direct_calls,
tail_calls,
direct_callers,
dropped_calls,
escaped,
function_infos,
caller_seen_stamps,
current_active,
topology_out~,
body_exposure_out~,
stable_path~,
),
)
let mut catch_idx = 0
while catch_idx < catches.length() {
match catches[catch_idx] {
@lib.LegacyCatch(_, @lib.Expr(catch_body))
| @lib.LegacyCatchAll(@lib.Expr(catch_body)) =>
ignore(
dae_scan_current_call_facts_child(
catch_body,
caller_def,
caller_abs,
path,
idx,
5 + catch_idx,
direct_calls,
tail_calls,
direct_callers,
dropped_calls,
escaped,
function_infos,
caller_seen_stamps,
current_active,
topology_out~,
body_exposure_out~,
stable_path~,
),
)
}
catch_idx += 1
}
}
_ => ()
}
idx += 1
}
singleton_trap
}
///|
fn dae_call_facts_from_function_infos(
function_infos : Array[DaeFunctionInfo],
) -> DaeCurrentCallFacts {
let total_funcs = function_infos.length()
let direct_calls = Array::make(total_funcs, 0)
let tail_calls = Array::make(total_funcs, false)
let direct_callers = dae_make_direct_callers(total_funcs)
let dropped_calls = Array::make(total_funcs, 0)
let escaped = Array::make(total_funcs, false)
let mut callee_abs = 0
while callee_abs < total_funcs {
let info = function_infos[callee_abs]
escaped[callee_abs] = info.has_unseen_calls
for callsite in info.calls {
if callsite.callee_abs != callee_abs || callsite.path.length() == 0 {
continue
}
if callsite.active {
direct_calls[callee_abs] += 1
if callsite.dropped {
dropped_calls[callee_abs] += 1
}
if callsite.tail {
tail_calls[callee_abs] = true
}
}
if (callsite.active || !callsite.tail) &&
!direct_callers[callee_abs].contains(callsite.caller_def) {
direct_callers[callee_abs].push(callsite.caller_def)
}
}
if info.dropped_calls.any(fn(callsite) {
callsite.active && callsite.dropped && callsite.callee_abs == callee_abs
}) {
// The exact dropped-call list is intentionally redundant with `calls`:
// callers that remove results consume locations, while aggregate guards
// consume the count reconstructed above.
ignore(info.dropped_calls.length())
}
if info.has_tail_calls {
for tail_callee in info.tail_callees {
if tail_callee >= 0 && tail_callee < tail_calls.length() {
tail_calls[tail_callee] = true
}
}
}
callee_abs += 1
}
{ direct_calls, tail_calls, direct_callers, dropped_calls, escaped, }
}
///|
fn dae_collect_current_function_analysis(
mod_ : @lib.Module,
total_funcs : Int32,
perf~ : HotPerfSession? = None,
call_fact_rebuilds~ : Array[Int32]? = None,
exposure_body_walk_visits~ : Array[Int32]? = None,
topology_out~ : DaeCurrentTopologyExtras? = None,
) -> DaeFunctionAnalysis {
ignore(call_fact_rebuilds)
let direct_calls = Array::make(total_funcs, 0)
let tail_calls = Array::make(total_funcs, false)
let direct_callers = dae_make_direct_callers(total_funcs)
let dropped_calls = Array::make(total_funcs, 0)
let type_indices = dae_collect_func_type_indices(mod_)
let exposure_start_us = perf_start_timer_opt(
perf, "detail:dae:boundary-snapshot:exposure",
)
// The required body scanner records body refs into these fresh rows.
let exposure = dae_collect_original_exposure(
mod_,
total_funcs,
type_indices,
body_ref_walk_visits=exposure_body_walk_visits,
include_body=false,
)
perf_stop_timer_opt(
perf, "detail:dae:boundary-snapshot:exposure", exposure_start_us,
)
let body_exposure_out = Some(exposure)
let escaped = dae_exposure_to_escaped(exposure)
let function_infos = dae_make_function_infos(total_funcs, escaped)
let caller_seen_stamps = Array::make(total_funcs, -1)
let imported = dae_imported_func_count(mod_)
let body_scan_start_us = perf_start_timer_opt(
perf, "detail:dae:boundary-snapshot:body-scan",
)
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
let mut caller_def = 0
while caller_def < funcs.length() {
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
ignore(
dae_scan_current_call_facts_instrs(
body,
caller_def,
imported + caller_def,
[],
direct_calls,
tail_calls,
direct_callers,
dropped_calls,
escaped,
function_infos,
caller_seen_stamps,
true,
topology_out~,
body_exposure_out~,
stable_path=Some(DaeStablePath::empty()),
),
)
caller_def += 1
}
}
None => ()
}
perf_stop_timer_opt(
perf, "detail:dae:boundary-snapshot:body-scan", body_scan_start_us,
)
let mut escaped_idx = 0
while escaped_idx < escaped.length() && escaped_idx < function_infos.length() {
function_infos[escaped_idx].has_unseen_calls = escaped[escaped_idx] ||
function_infos[escaped_idx].has_unseen_calls
escaped_idx += 1
}
let defined = total_funcs - imported
let loop_admission_start_us = perf_start_timer_opt(
perf, "detail:dae:boundary-snapshot:loop-admission",
)
let parameter_loop_free = dae_parameter_loop_admission(
mod_, imported, defined, function_infos,
)
perf_stop_timer_opt(
perf, "detail:dae:boundary-snapshot:loop-admission", loop_admission_start_us,
)
let call_facts_start_us = perf_start_timer_opt(
perf, "detail:dae:boundary-snapshot:call-facts",
)
let call_facts : DaeCurrentCallFacts = {
direct_calls,
tail_calls,
direct_callers,
dropped_calls,
escaped,
}
perf_stop_timer_opt(
perf, "detail:dae:boundary-snapshot:call-facts", call_facts_start_us,
)
{ call_facts, function_infos, exposure, parameter_loop_free, }
}
///|
fn dae_collect_current_call_facts(
mod_ : @lib.Module,
total_funcs : Int32,
function_info_builds~ : Array[Int32]? = None,
) -> DaeCurrentCallFacts {
// Aggregate guards need counts, caller order and escape flags, not operand
// paths, loop signatures or mutable per-function analysis records.
ignore(function_info_builds)
let direct_calls = Array::make(total_funcs, 0)
let tail_calls = Array::make(total_funcs, false)
let direct_callers = dae_make_direct_callers(total_funcs)
let dropped_calls = Array::make(total_funcs, 0)
let types = dae_collect_func_type_indices(mod_)
let escaped = dae_exposure_to_escaped(
dae_collect_original_exposure(mod_, total_funcs, types),
)
let imported = dae_imported_func_count(mod_)
let caller_seen_stamps = Array::make(total_funcs, -1)
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
for caller_def = 0
caller_def < funcs.length()
caller_def = caller_def + 1 {
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
ignore(
dae_scan_current_call_facts_instrs(
body,
caller_def,
imported + caller_def,
[],
direct_calls,
tail_calls,
direct_callers,
dropped_calls,
escaped,
[],
caller_seen_stamps,
true,
),
)
}
None => ()
}
{ direct_calls, tail_calls, direct_callers, dropped_calls, escaped, }
}
///|
fn dae_adjacent_param_forward_evidence(
instrs : Array[@lib.Instruction],
call_idx : Int32,
param_count : Int32,
) -> DaeAdjacentParamForwardEvidence? {
if call_idx < 0 || call_idx > instrs.length() || param_count < 0 {
return None
}
let start = call_idx - param_count
if start < 0 {
return None
}
let mut p = 0
while p < param_count {
match instrs[start + p] {
@lib.LocalGet(local_idx) if dae_local_idx_slot(local_idx) == p => ()
_ => return None
}
p += 1
}
Some({ start, end: call_idx, param_count, })
}
///|
fn dae_mark_self_forwarded_param_gets(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
param_count : Int32,
ignored : Array[Bool],
) -> Unit {
if param_count == 0 {
return
}
let mut idx = 0
while idx < instrs.length() {
match
dae_target_callsite_evidence(
instrs[idx],
callee_abs,
DaeTargetDirectCallOnly,
) {
Some(_) =>
match dae_adjacent_param_forward_evidence(instrs, idx, param_count) {
Some(evidence) => {
let mut operand_idx = evidence.start
while operand_idx < evidence.start + evidence.param_count {
ignored[operand_idx] = true
operand_idx += 1
}
}
None => ()
}
None => ()
}
idx += 1
}
}
///|
fn dae_scan_local_use_ignoring_instrs(
instrs : Array[@lib.Instruction],
local_use : DaeLocalUse,
callee_abs : Int32,
param_count : Int32,
) -> Unit {
let ignored = Array::make(instrs.length(), false)
dae_mark_self_forwarded_param_gets(instrs, callee_abs, param_count, ignored)
let mut idx = 0
while idx < instrs.length() {
let terminal_root_transfer = match instrs[idx] {
@lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef => true
_ => false
}
if dae_instr_is_unreachable_root(instrs[idx]) || terminal_root_transfer {
break
}
if !ignored[idx] {
match instrs[idx] {
@lib.LocalGet(local_idx) => {
let slot = dae_local_idx_slot(local_idx)
if slot >= 0 && slot < local_use.gets.length() {
local_use.gets[slot] = true
}
}
@lib.LocalSet(local_idx) | @lib.LocalTee(local_idx) => {
let slot = dae_local_idx_slot(local_idx)
if slot >= 0 && slot < local_use.writes.length() {
local_use.writes[slot] = true
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_scan_local_use_ignoring_instrs(
body, local_use, callee_abs, param_count,
)
@lib.Try(_, @lib.Expr(body), catches, _) => {
dae_scan_local_use_ignoring_instrs(
body, local_use, callee_abs, param_count,
)
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
dae_scan_local_use_ignoring_instrs(
catch_body, local_use, callee_abs, param_count,
)
}
}
@lib.If(_, then_body, Some(else_body)) => {
dae_scan_local_use_ignoring_instrs(
then_body, local_use, callee_abs, param_count,
)
dae_scan_local_use_ignoring_instrs(
else_body, local_use, callee_abs, param_count,
)
}
@lib.If(_, then_body, None) =>
dae_scan_local_use_ignoring_instrs(
then_body, local_use, callee_abs, param_count,
)
_ => ()
}
}
idx += 1
}
}
///|
fn dae_instr_supports_param_entry_liveness(instr : @lib.Instruction) -> Bool {
match instr {
@lib.Br(_) | @lib.BrIf(_) | @lib.BrTable(_, _) => false
@lib.Throw(_) | @lib.ThrowRef => false
@lib.Try(_, _, _, _) | @lib.TryTable(_, _, _) => false
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
body.all(fn(child) { dae_instr_supports_param_entry_liveness(child) })
@lib.If(_, then_body, Some(else_body)) =>
then_body.all(fn(child) { dae_instr_supports_param_entry_liveness(child) }) &&
else_body.all(fn(child) { dae_instr_supports_param_entry_liveness(child) })
@lib.If(_, then_body, None) =>
then_body.all(fn(child) { dae_instr_supports_param_entry_liveness(child) })
_ => true
}
}
///|
fn dae_instr_contains_call(instr : @lib.Instruction) -> Bool {
match instr {
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_) => true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
body.any(fn(child) { dae_instr_contains_call(child) })
@lib.Try(_, @lib.Expr(body), catches, _) => {
if body.any(dae_instr_contains_call) {
return true
}
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
if catch_body.any(dae_instr_contains_call) {
return true
}
}
false
}
@lib.If(_, then_body, Some(else_body)) =>
then_body.any(fn(child) { dae_instr_contains_call(child) }) ||
else_body.any(fn(child) { dae_instr_contains_call(child) })
@lib.If(_, then_body, None) =>
then_body.any(fn(child) { dae_instr_contains_call(child) })
_ => false
}
}
///|
fn dae_instrs_support_param_entry_liveness(
instrs : Array[@lib.Instruction],
) -> Bool {
instrs.all(fn(instr) { dae_instr_supports_param_entry_liveness(instr) }) &&
!instrs.any(fn(instr) { dae_instr_contains_call(instr) })
}
///|
fn dae_instr_supports_hot_param_entry_liveness(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Suspend(_)
| @lib.Resume(_, _)
| @lib.ResumeThrow(_, _, _)
| @lib.ResumeThrowRef(_, _)
| @lib.StackSwitch(_, _) => false
// Legacy Try is not yet lifted by HOT; callers fail closed via None.
@lib.Try(_, _, _, _) => false
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
body.all(fn(child) { dae_instr_supports_hot_param_entry_liveness(child) })
@lib.TryTable(_, _, @lib.Expr(body)) =>
body.all(fn(child) { dae_instr_supports_hot_param_entry_liveness(child) })
@lib.If(_, then_body, Some(else_body)) =>
then_body.all(fn(child) {
dae_instr_supports_hot_param_entry_liveness(child)
}) &&
else_body.all(fn(child) {
dae_instr_supports_hot_param_entry_liveness(child)
})
@lib.If(_, then_body, None) =>
then_body.all(fn(child) {
dae_instr_supports_hot_param_entry_liveness(child)
})
_ => true
}
}
///|
fn dae_fast_param_entry_liveness(
mod_ : @lib.Module,
callee_abs : Int32,
param_count : Int32,
) -> Array[Bool]? {
if callee_abs < 0 || param_count < 0 {
return None
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return None
}
let def_idx = callee_abs - dae_imported_func_count(mod_)
if def_idx < 0 || def_idx >= funcs.length() {
return None
}
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
if !body.all(fn(instr) {
dae_instr_supports_param_entry_liveness(instr) &&
dae_instr_supports_hot_param_entry_liveness(instr)
}) {
return None
}
let live = Array::make(param_count, false)
dae_scan_param_entry_liveness_instrs(body, live, callee_abs, param_count)
Some(live)
}
///|
fn dae_hot_param_entry_liveness(
mod_ : @lib.Module,
callee_abs : Int32,
param_count : Int32,
) -> Array[Bool]? {
if callee_abs < 0 || param_count < 0 {
return None
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return None
}
let def_idx = callee_abs - dae_imported_func_count(mod_)
if def_idx < 0 || def_idx >= funcs.length() {
return None
}
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
if !body.all(fn(instr) { dae_instr_supports_hot_param_entry_liveness(instr) }) {
return None
}
let ctx = @ir.hot_module_context_from_module(mod_)
let hot = match
@ir.hot_lift_func_with_context_result(
mod_,
@lib.FuncIdx::new(callee_abs.reinterpret_as_uint()),
ctx,
) {
Ok(func) => func
Err(_) => return None
}
// Capturing a carried block or if value can place its control beneath a
// local/global write. Its branch payloads still read parameter entry values.
let cfg = @ir.cfg_build(
hot,
conservative_exception_entries=false,
expand_operand_control=true,
)
let use_def = @ir.use_def_build(hot, cfg=Some(cfg))
let liveness = @ir.liveness_build(
cfg,
use_def,
include_exceptional_edges=true,
)
let entry = @ir.cfg_entry_block(cfg)
let live = Array::make(param_count, false)
let mut param_idx = 0
while param_idx < param_count {
live[param_idx] = @ir.local_live_in(liveness, entry, param_idx)
param_idx += 1
}
Some(live)
}
///|
fn dae_scan_param_entry_liveness_instrs(
instrs : Array[@lib.Instruction],
live : Array[Bool],
callee_abs : Int32,
param_count : Int32,
) -> Unit {
let ignored = Array::make(instrs.length(), false)
dae_mark_self_forwarded_param_gets(instrs, callee_abs, param_count, ignored)
let mut idx = instrs.length()
while idx > 0 {
idx -= 1
match instrs[idx] {
@lib.LocalGet(local_idx) => {
let slot = dae_local_idx_slot(local_idx)
if !ignored[idx] && slot >= 0 && slot < live.length() {
live[slot] = true
}
}
@lib.LocalSet(local_idx) | @lib.LocalTee(local_idx) => {
let slot = dae_local_idx_slot(local_idx)
if slot >= 0 && slot < live.length() {
live[slot] = false
}
}
@lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Unreachable => {
let mut local_idx = 0
while local_idx < live.length() {
live[local_idx] = false
local_idx += 1
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_scan_param_entry_liveness_instrs(
body, live, callee_abs, param_count,
)
@lib.If(_, then_body, else_body) => {
let then_live = live.copy()
dae_scan_param_entry_liveness_instrs(
then_body, then_live, callee_abs, param_count,
)
let else_live = live.copy()
match else_body {
Some(body) =>
dae_scan_param_entry_liveness_instrs(
body, else_live, callee_abs, param_count,
)
None => ()
}
let mut local_idx = 0
while local_idx < live.length() {
live[local_idx] = then_live[local_idx] || else_live[local_idx]
local_idx += 1
}
}
_ => ()
}
}
}
///|
fn dae_strip_function_terminal_suffix_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
let rewritten = match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, nested_changed) = dae_strip_function_terminal_suffix_instrs(
body,
)
changed = changed || nested_changed
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, nested_changed) = dae_strip_function_terminal_suffix_instrs(
body,
)
changed = changed || nested_changed
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = dae_strip_function_terminal_suffix_instrs(
then_body,
)
changed = changed || then_changed
let next_else = match else_body {
Some(body) => {
let (next_body, else_changed) = dae_strip_function_terminal_suffix_instrs(
body,
)
changed = changed || else_changed
Some(next_body)
}
None => None
}
@lib.Instruction::if_(block_type, next_then, next_else)
}
_ => instrs[idx]
}
out.push(rewritten)
match instrs[idx] {
@lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Unreachable => {
if idx + 1 < instrs.length() {
changed = true
}
break
}
_ => ()
}
idx += 1
}
(out, changed)
}
///|
fn dae_strip_function_terminal_suffix_for_def(
mod_ : @lib.Module,
def_idx : Int32,
) -> @lib.Module {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
if def_idx < 0 || def_idx >= funcs.length() {
return mod_
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
let (next_body, changed) = dae_strip_function_terminal_suffix_instrs(body)
if !changed {
return mod_
}
let next_funcs = funcs.copy()
next_funcs[def_idx] = @lib.Func::new(locals, @lib.Expr::new(next_body))
mod_.with_code_sec(@lib.CodeSec::new(next_funcs))
}
///|
fn dae_function_info_refresh_unused_params(
function_infos : Array[DaeFunctionInfo],
callee_abs : Int32,
local_use : DaeLocalUse,
param_count : Int32,
) -> Array[Bool] {
if callee_abs < 0 || callee_abs >= function_infos.length() {
let unused = Array::make(param_count, false)
let mut param_idx = 0
while param_idx < param_count {
unused[param_idx] = !local_use.gets[param_idx]
param_idx += 1
}
return unused
}
let info = function_infos[callee_abs]
if info.stale || info.unused_params.length() != param_count {
let unused = Array::make(param_count, false)
let mut param_idx = 0
while param_idx < param_count {
unused[param_idx] = !local_use.gets[param_idx]
param_idx += 1
}
info.unused_params = unused
info.stale = false
}
info.unused_params
}
///|
fn dae_local_use_for_callee(
func : @lib.Func,
total_locals : Int32,
callee_abs : Int32,
param_count : Int32,
) -> DaeLocalUse {
let local_use = {
gets: Array::make(total_locals, false),
writes: Array::make(total_locals, false),
}
let @lib.Func(_, @lib.Expr(body)) = func
dae_scan_local_use_ignoring_instrs(body, local_use, callee_abs, param_count)
local_use
}
///|
fn dae_instr_result_arity_from_block_type(
block_type : @lib.BlockType,
) -> Int32? {
match block_type {
@lib.VoidBlockType => Some(0)
@lib.ValTypeBlockType(_) => Some(1)
@lib.TypeIdxBlockType(_) => None
}
}
///|
fn dae_instr_stack_effect_from_block_type(
block_type : @lib.BlockType,
mod_ : @lib.Module,
) -> (Int32, Int32)? {
match dae_control_block_type_signature_evidence(block_type, mod_) {
Some(evidence) => Some((evidence.input_count, evidence.output_count))
None => None
}
}
///|
fn dae_instr_stack_effect(
instr : @lib.Instruction,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> (Int32, Int32)? {
match instr {
@lib.Unreachable | @lib.Nop => Some((0, 0))
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.V128Const(_)
| @lib.RefNull(_)
| @lib.RefFunc(_)
| @lib.StringConst(_)
| @lib.StructNewDefault(_)
| @lib.GlobalGet(_)
| @lib.LocalGet(_)
| @lib.TableSize(_)
| @lib.MemorySize(_) => Some((0, 1))
@lib.StructNew(type_idx)
| @lib.StructNewDesc(type_idx) as allocation_instr =>
match mod_.type_sec {
Some(type_sec) =>
match dae_type_sec_subtype(type_sec, type_idx) {
Some(subtype) =>
match subtype.get_comptype() {
@lib.StructCompType(fields) => {
let descriptor_count = match allocation_instr {
@lib.StructNewDesc(_) => 1
_ => 0
}
Some((fields.length() + descriptor_count, 1))
}
_ => None
}
None => None
}
None => None
}
@lib.StructNewDefaultDesc(_) => Some((1, 1))
@lib.ArrayNew(_) => Some((2, 1))
@lib.ArrayNewDefault(_) => Some((1, 1))
@lib.ArrayNewFixed(_, @lib.U32(raw_len)) =>
Some((raw_len.reinterpret_as_int(), 1))
@lib.LocalSet(_) | @lib.GlobalSet(_) => Some((1, 0))
@lib.LocalTee(_) => Some((1, 1))
@lib.Drop => Some((1, 0))
@lib.Select(_)
| @lib.StringViewWtf16Slice
| @lib.StringNewUtf8Array
| @lib.StringNewWtf16Array
| @lib.StringNewLossyUtf8Array
| @lib.StringNewWtf8Array => Some((3, 1))
@lib.I32Eqz
| @lib.I64Eqz
| @lib.RefIsNull
| @lib.RefAsNonNull
| @lib.RefGetDesc(_)
| @lib.RefCast(_, _, _)
| @lib.StringAsWtf16
| @lib.I32Clz
| @lib.I32Ctz
| @lib.I32Popcnt
| @lib.I64Clz
| @lib.I64Ctz
| @lib.I64Popcnt
| @lib.F32Abs
| @lib.F32Neg
| @lib.F32Ceil
| @lib.F32Floor
| @lib.F32Trunc
| @lib.F32Nearest
| @lib.F32Sqrt
| @lib.F64Abs
| @lib.F64Neg
| @lib.F64Ceil
| @lib.F64Floor
| @lib.F64Trunc
| @lib.F64Nearest
| @lib.F64Sqrt
| @lib.I32WrapI64
| @lib.I64ExtendI32S
| @lib.I64ExtendI32U
| @lib.F32ConvertI32S
| @lib.F32ConvertI32U
| @lib.F32ConvertI64S
| @lib.F32ConvertI64U
| @lib.F32DemoteF64
| @lib.F64ConvertI32S
| @lib.F64ConvertI32U
| @lib.F64ConvertI64S
| @lib.F64ConvertI64U
| @lib.F64PromoteF32
| @lib.I32ReinterpretF32
| @lib.I64ReinterpretF64
| @lib.F32ReinterpretI32
| @lib.F64ReinterpretI64
| @lib.I32Extend8S
| @lib.I32Extend16S
| @lib.I64Extend8S
| @lib.I64Extend16S
| @lib.I64Extend32S
| @lib.AnyConvertExtern
| @lib.ExternConvertAny
| @lib.RefI31
| @lib.I31GetS
| @lib.I31GetU
| @lib.I32TruncSatF32S
| @lib.I32TruncSatF32U
| @lib.I32TruncSatF64S
| @lib.I32TruncSatF64U
| @lib.I64TruncSatF32S
| @lib.I64TruncSatF32U
| @lib.I64TruncSatF64S
| @lib.I64TruncSatF64U
| @lib.I32Load(_)
| @lib.I64Load(_)
| @lib.F32Load(_)
| @lib.F64Load(_)
| @lib.I32Load8S(_)
| @lib.I32Load8U(_)
| @lib.I32Load16S(_)
| @lib.I32Load16U(_)
| @lib.I64Load8S(_)
| @lib.I64Load8U(_)
| @lib.I64Load16S(_)
| @lib.I64Load16U(_)
| @lib.I64Load32S(_)
| @lib.I64Load32U(_)
| @lib.StructGet(_, _)
| @lib.StructGetS(_, _)
| @lib.StructGetU(_, _)
| @lib.StructAtomicGet(_, _, _)
| @lib.StructAtomicGetS(_, _, _)
| @lib.StructAtomicGetU(_, _, _)
| @lib.MemoryGrow(_)
| @lib.TableGet(_) => Some((1, 1))
@lib.RefEq
| @lib.I32Eq
| @lib.I32Ne
| @lib.I32LtS
| @lib.I32LtU
| @lib.I32GtS
| @lib.I32GtU
| @lib.I32LeS
| @lib.I32LeU
| @lib.I32GeS
| @lib.I32GeU
| @lib.I64Eq
| @lib.I64Ne
| @lib.I64LtS
| @lib.I64LtU
| @lib.I64GtS
| @lib.I64GtU
| @lib.I64LeS
| @lib.I64LeU
| @lib.I64GeS
| @lib.I64GeU
| @lib.F32Eq
| @lib.F32Ne
| @lib.F32Lt
| @lib.F32Gt
| @lib.F32Le
| @lib.F32Ge
| @lib.F64Eq
| @lib.F64Ne
| @lib.F64Lt
| @lib.F64Gt
| @lib.F64Le
| @lib.F64Ge
| @lib.I32Add
| @lib.I32Sub
| @lib.I32Mul
| @lib.I32DivS
| @lib.I32DivU
| @lib.I32RemS
| @lib.I32RemU
| @lib.I32And
| @lib.I32Or
| @lib.I32Xor
| @lib.I32Shl
| @lib.I32ShrS
| @lib.I32ShrU
| @lib.I32Rotl
| @lib.I32Rotr
| @lib.I64Add
| @lib.I64Sub
| @lib.I64Mul
| @lib.I64DivS
| @lib.I64DivU
| @lib.I64RemS
| @lib.I64RemU
| @lib.I64And
| @lib.I64Or
| @lib.I64Xor
| @lib.I64Shl
| @lib.I64ShrS
| @lib.I64ShrU
| @lib.I64Rotl
| @lib.I64Rotr
| @lib.F32Add
| @lib.F32Sub
| @lib.F32Mul
| @lib.F32Div
| @lib.F32Min
| @lib.F32Max
| @lib.F32Copysign
| @lib.F64Add
| @lib.F64Sub
| @lib.F64Mul
| @lib.F64Div
| @lib.F64Min
| @lib.F64Max
| @lib.F64Copysign
| @lib.ArrayGet(_)
| @lib.ArrayGetS(_)
| @lib.ArrayGetU(_)
| @lib.ArrayAtomicGet(_, _)
| @lib.ArrayAtomicGetS(_, _)
| @lib.ArrayAtomicGetU(_, _)
| @lib.RefCastDescEq(_, _, _)
| @lib.StringConcat
| @lib.TableGrow(_) => Some((2, 1))
@lib.Call(func_idx) | @lib.ReturnCall(func_idx) => {
let slot = dae_func_idx_slot(func_idx)
match dae_resolve_func_type(mod_, func_type_indices, slot) {
Some(@lib.FuncType(params, results)) =>
Some((params.length(), results.length()))
None => None
}
}
@lib.CallIndirect(type_idx, _) | @lib.ReturnCallIndirect(type_idx, _) =>
match mod_.type_sec {
Some(type_sec) =>
match dae_type_sec_func_type(type_sec, type_idx) {
Some(@lib.FuncType(params, results)) =>
Some((params.length() + 1, results.length()))
None => None
}
None => None
}
@lib.CallRef(type_idx) | @lib.ReturnCallRef(type_idx) =>
match mod_.type_sec {
Some(type_sec) =>
match dae_type_sec_func_type(type_sec, type_idx) {
Some(@lib.FuncType(params, results)) =>
Some((params.length() + 1, results.length()))
None => None
}
None => None
}
@lib.Block(block_type, _)
| @lib.Loop(block_type, _)
| @lib.TryTable(block_type, _, _)
| @lib.Try(block_type, _, _, _) =>
dae_instr_stack_effect_from_block_type(block_type, mod_)
@lib.If(block_type, _, _) =>
match dae_instr_stack_effect_from_block_type(block_type, mod_) {
Some((inputs, outputs)) => Some((inputs + 1, outputs))
None => None
}
_ => None
}
}
///|
fn dae_control_entry_stack_evidence(
block_type : @lib.BlockType,
mod_ : @lib.Module,
) -> DaeControlEntryStackEvidence? {
match dae_control_block_type_signature_evidence(block_type, mod_) {
Some(signature) => {
let evidence = {
entry_stack_values: signature.input_count,
result_stack_values: signature.output_count,
}
if evidence.entry_stack_values < 0 || evidence.result_stack_values < 0 {
None
} else {
Some(evidence)
}
}
None => None
}
}
///|
fn dae_control_entry_stack_values(
block_type : @lib.BlockType,
mod_ : @lib.Module,
) -> Int32? {
match dae_control_entry_stack_evidence(block_type, mod_) {
Some(evidence) => Some(evidence.entry_stack_values)
None => None
}
}
///|
fn dae_nested_control_entry_evidence_is_consistent(
evidence : DaeNestedControlEntryEvidence,
) -> Bool {
evidence.entry_stack_values >= 0 &&
evidence.result_stack_values >= 0 &&
evidence.control_kind >= 0 &&
evidence.control_kind <= 3 &&
(!evidence.has_else_arm || evidence.control_kind == 3)
}
///|
fn dae_nested_control_entry_evidence(
instr : @lib.Instruction,
mod_ : @lib.Module,
) -> DaeNestedControlEntryEvidence? {
let (block_type, control_kind, has_else_arm) = match instr {
@lib.Block(block_type, _) => (block_type, 0, false)
@lib.Loop(block_type, _) => (block_type, 1, false)
@lib.TryTable(block_type, _, _) => (block_type, 2, false)
@lib.If(block_type, _, else_body) => (block_type, 3, else_body is Some(_))
_ => return None
}
let entry_stack_values = match
dae_control_entry_stack_values(block_type, mod_) {
Some(value) => value
None => return None
}
match dae_control_entry_stack_evidence(block_type, mod_) {
Some(evidence) => {
if evidence.entry_stack_values != entry_stack_values {
return None
}
let nested = {
entry_stack_values,
result_stack_values: evidence.result_stack_values,
control_kind,
has_else_arm,
}
if dae_nested_control_entry_evidence_is_consistent(nested) {
Some(nested)
} else {
None
}
}
None => None
}
}
///|
fn dae_nested_control_children_evidence_is_consistent(
evidence : DaeNestedControlChildrenEvidence,
) -> Bool {
evidence.entry_stack_values >= 0 &&
evidence.result_stack_values >= 0 &&
evidence.control_kind >= 0 &&
evidence.control_kind <= 3 &&
evidence.primary_body.length() >= 0 &&
evidence.has_else_arm == (evidence.secondary_body is Some(_)) &&
(!evidence.has_else_arm || evidence.control_kind == 3)
}
///|
fn dae_nested_control_children_evidence(
instr : @lib.Instruction,
mod_ : @lib.Module,
) -> DaeNestedControlChildrenEvidence? {
let nested = match dae_nested_control_entry_evidence(instr, mod_) {
Some(evidence) => evidence
None => return None
}
let (primary_body, secondary_body, expected_kind, expected_else) = match
instr {
@lib.Block(_, @lib.Expr(body)) => (body, None, 0, false)
@lib.Loop(_, @lib.Expr(body)) => (body, None, 1, false)
@lib.TryTable(_, _, @lib.Expr(body)) => (body, None, 2, false)
@lib.If(_, then_body, else_body) =>
(then_body, else_body, 3, else_body is Some(_))
_ => return None
}
if nested.control_kind != expected_kind ||
nested.has_else_arm != expected_else {
return None
}
let evidence = {
entry_stack_values: nested.entry_stack_values,
result_stack_values: nested.result_stack_values,
control_kind: nested.control_kind,
has_else_arm: nested.has_else_arm,
primary_body,
secondary_body,
}
if dae_nested_control_children_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
///|
fn dae_rebuild_nested_control_children(
instr : @lib.Instruction,
evidence : DaeNestedControlChildrenEvidence,
primary_body : Array[@lib.Instruction],
secondary_body : Array[@lib.Instruction]?,
) -> @lib.Instruction? {
if !dae_nested_control_children_evidence_is_consistent(evidence) {
return None
}
match instr {
@lib.Block(block_type, _) if evidence.control_kind == 0 &&
!evidence.has_else_arm &&
secondary_body is None =>
Some(@lib.Instruction::block(block_type, @lib.Expr::new(primary_body)))
@lib.Loop(block_type, _) if evidence.control_kind == 1 &&
!evidence.has_else_arm &&
secondary_body is None =>
Some(@lib.Instruction::loop_(block_type, @lib.Expr::new(primary_body)))
@lib.TryTable(block_type, catches, _) if evidence.control_kind == 2 &&
!evidence.has_else_arm &&
secondary_body is None =>
Some(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(primary_body),
),
)
@lib.If(block_type, _, _) if evidence.control_kind == 3 =>
if evidence.has_else_arm {
match secondary_body {
Some(body) =>
Some(@lib.Instruction::if_(block_type, primary_body, Some(body)))
None => None
}
} else {
match secondary_body {
Some(_) => None
None => Some(@lib.Instruction::if_(block_type, primary_body, None))
}
}
_ => None
}
}
///|
fn dae_instr_is_trivially_droppable(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.V128Const(_)
| @lib.RefNull(_)
| @lib.RefFunc(_)
| @lib.StringConst(_)
| @lib.LocalGet(_)
| @lib.GlobalGet(_)
| @lib.StructNew(_)
| @lib.StructNewDefault(_)
| @lib.ArrayNew(_)
| @lib.ArrayNewDefault(_)
| @lib.ArrayNewFixed(_, _) => true
_ => false
}
}
///|
fn dae_operand_rewrite_action(
disposition : DaeOperandDisposition,
localization_available : Bool,
has_escaping_branch : Bool,
) -> DaeOperandRewriteAction {
if disposition.droppable_without_repair {
DaeDropOperandDirectly
} else if has_escaping_branch {
DaeRejectEscapingOperandRewrite
} else if localization_available {
DaeLocalizeOperandAndRetry
} else {
DaePreserveOperandEffectsAndDropValue
}
}
///|
fn dae_operand_rewrite_disposition(
operand : DaeOperandDisposition,
localization_available~ : Bool,
has_escaping_branch~ : Bool,
) -> DaeOperandRewriteDisposition {
let action = dae_operand_rewrite_action(
operand, localization_available, has_escaping_branch,
)
let reason = match action {
DaeDropOperandDirectly => DaeOperandDirectlyDroppable
DaePreserveOperandEffectsAndDropValue => DaeOperandEffectsMustBeReplayed
DaeLocalizeOperandAndRetry => DaeOperandRequiresLocalization
DaeRejectEscapingOperandRewrite => DaeOperandContainsEscapingControl
}
{ operand, localization_available, has_escaping_branch, action, reason, }
}
///|
fn dae_operand_rewrite_drops_directly(
disposition : DaeOperandRewriteDisposition,
) -> Bool {
match disposition.action {
DaeDropOperandDirectly => true
_ => false
}
}
///|
fn dae_operand_rewrite_preserves_effects_and_drops_value(
disposition : DaeOperandRewriteDisposition,
) -> Bool {
match disposition.action {
DaePreserveOperandEffectsAndDropValue => true
_ => false
}
}
///|
fn dae_operand_rewrite_localizes_and_retries(
disposition : DaeOperandRewriteDisposition,
) -> Bool {
match disposition.action {
DaeLocalizeOperandAndRetry => true
_ => false
}
}
///|
fn dae_operand_rewrite_rejects_escaping_branch(
disposition : DaeOperandRewriteDisposition,
) -> Bool {
match disposition.action {
DaeRejectEscapingOperandRewrite => true
_ => false
}
}
///|
fn dae_operand_rewrite_disposition_is_consistent(
disposition : DaeOperandRewriteDisposition,
) -> Bool {
let operand = disposition.operand
if operand.start < 0 ||
operand.end < operand.start ||
operand.preserve_effects_on_removal == operand.droppable_without_repair ||
(disposition.has_escaping_branch && !operand.has_branch) {
return false
}
match
(
disposition.action,
dae_operand_rewrite_action(
operand,
disposition.localization_available,
disposition.has_escaping_branch,
),
) {
(DaeDropOperandDirectly, DaeDropOperandDirectly)
| (
DaePreserveOperandEffectsAndDropValue,
DaePreserveOperandEffectsAndDropValue,
)
| (DaeLocalizeOperandAndRetry, DaeLocalizeOperandAndRetry)
| (DaeRejectEscapingOperandRewrite, DaeRejectEscapingOperandRewrite) => true
_ => false
}
}
///|
fn dae_operand_removal_plan(
disposition : DaeOperandRewriteDisposition,
keep : Bool,
) -> DaeOperandRemovalPlan {
let preserve_effects = dae_operand_rewrite_preserves_effects_and_drops_value(
disposition,
)
let requires_localization_retry = !keep &&
dae_operand_rewrite_localizes_and_retries(disposition)
let rejects_direct_removal = !keep &&
dae_operand_rewrite_rejects_escaping_branch(disposition)
let replay_operand = keep || preserve_effects || rejects_direct_removal
{
start: disposition.operand.start,
end: disposition.operand.end,
keep_operand: keep,
replay_operand,
append_drop: !keep && replay_operand,
requires_localization_retry,
rejects_direct_removal,
}
}
///|
fn dae_operand_removal_plan_is_consistent(
disposition : DaeOperandRewriteDisposition,
plan : DaeOperandRemovalPlan,
) -> Bool {
if !dae_operand_rewrite_disposition_is_consistent(disposition) ||
plan.start != disposition.operand.start ||
plan.end != disposition.operand.end {
return false
}
let expected = dae_operand_removal_plan(disposition, plan.keep_operand)
plan.replay_operand == expected.replay_operand &&
plan.append_drop == expected.append_drop &&
plan.requires_localization_retry == expected.requires_localization_retry &&
plan.rejects_direct_removal == expected.rejects_direct_removal
}
///|
fn dae_operand_removal_plan_drops_without_repair(
plan : DaeOperandRemovalPlan,
) -> Bool {
!plan.keep_operand &&
!plan.replay_operand &&
!plan.append_drop &&
!plan.requires_localization_retry &&
!plan.rejects_direct_removal
}
///|
// Shared operand-range disposition helper. Ordinary unused-parameter analysis
// must consume canonical `DaeValueSlice` facts via `dae_direct_call_operand_slices`
// / `dae_classify_parameter_slot` instead of calling this directly.
// Remaining specialized GC/control/result consumers are migrated under later
// [O4Z-DAE-TYPES]001 / [O4Z-DAE-RESULTS]001 workstreams.
fn dae_operand_disposition_from_range(
instrs : Array[@lib.Instruction],
start : Int32,
end : Int32,
) -> DaeOperandDisposition? {
if start < 0 || end < start || end > instrs.length() {
return None
}
let mut droppable_without_repair = true
let mut has_branch = false
let mut has_effect_or_branch = false
let mut idx = start
while idx < end {
let instr = instrs[idx]
if !dae_instr_is_trivially_droppable(instr) {
droppable_without_repair = false
}
if dae_instr_has_branch(instr) {
has_branch = true
}
if dae_instr_has_observable_effect_or_branch(instr) {
has_effect_or_branch = true
}
idx += 1
}
Some({
start,
end,
droppable_without_repair,
preserve_effects_on_removal: !droppable_without_repair,
has_branch,
has_effect_or_branch,
})
}
///|
fn dae_operand_disposition_from_call_shape(
instrs : Array[@lib.Instruction],
shape : DaeCallShape,
) -> DaeOperandDisposition {
let base = match
dae_operand_disposition_from_range(instrs, shape.start, shape.end) {
Some(disposition) => disposition
None =>
{
start: shape.start,
end: shape.end,
droppable_without_repair: shape.pure,
preserve_effects_on_removal: !shape.pure,
has_branch: false,
has_effect_or_branch: !shape.pure,
}
}
{
start: shape.start,
end: shape.end,
droppable_without_repair: shape.pure,
preserve_effects_on_removal: !shape.pure,
has_branch: base.has_branch,
has_effect_or_branch: base.has_effect_or_branch,
}
}
///|
fn dae_append_operand_with_removal_plan(
out : Array[@lib.Instruction],
instrs : Array[@lib.Instruction],
plan : DaeOperandRemovalPlan,
) -> Unit {
if plan.replay_operand {
let mut idx = plan.start
while idx < plan.end {
out.push(instrs[idx])
idx += 1
}
if plan.append_drop {
out.push(@lib.Instruction::drop())
}
}
}
///|
fn dae_slice_is_trivially_droppable(
instrs : Array[@lib.Instruction],
start : Int32,
end : Int32,
) -> Bool {
match dae_operand_disposition_from_range(instrs, start, end) {
Some(disposition) =>
dae_operand_removal_plan_drops_without_repair(
dae_operand_removal_plan(
dae_operand_rewrite_disposition(
disposition,
localization_available=false,
has_escaping_branch=disposition.has_branch,
),
false,
),
)
None => false
}
}
///|
fn dae_trivially_dropped_prefix_evidence(
instrs : Array[@lib.Instruction],
end : Int32,
) -> DaeDroppedOperandPairSkipEvidence? {
let skipped = match dae_dropped_operand_pair_skip_evidence(instrs, 0, end) {
Some(evidence) => evidence
None => return None
}
if skipped.next == end {
Some(skipped)
} else {
None
}
}
///|
fn dae_find_value_slice(
instrs : Array[@lib.Instruction],
end : Int32,
entry_stack_values : Int32,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
allow_stack_carried_void_effects~ : Bool = false,
) -> (DaeCallShape, Int32, Int32)? {
let mut need = 1
let mut idx = end - 1
while idx >= 0 {
let effect = match
dae_stack_effect_evidence(instrs[idx], mod_, func_type_indices) {
Some(effect) => effect
None => return None
}
let ins = effect.inputs
let outs = effect.outputs
if outs == 0 && need > 0 && !allow_stack_carried_void_effects {
if need <= entry_stack_values {
return Some(
(
{ start: idx + 1, end, pure: false, },
idx + 1,
entry_stack_values - need,
),
)
}
return None
}
need = need - outs + ins
if need <= 0 {
return Some(
(
{
start: idx,
end,
pure: dae_slice_is_trivially_droppable(instrs, idx, end),
},
idx,
entry_stack_values,
),
)
}
idx -= 1
}
if need <= entry_stack_values {
return Some(({ start: 0, end, pure: false, }, 0, entry_stack_values - need))
}
None
}
///|
fn dae_instr_producer_kind(instr : @lib.Instruction) -> DaeValueProducerKind {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.V128Const(_)
| @lib.RefNull(_)
| @lib.RefFunc(_)
| @lib.StringConst(_) => DaeProducerConstantOrPureLeaf
@lib.LocalGet(_) => DaeProducerLocalRead
@lib.GlobalGet(_) => DaeProducerGlobalRead
@lib.Call(_) | @lib.ReturnCall(_) => DaeProducerDirectCall
@lib.CallIndirect(_, _) | @lib.ReturnCallIndirect(_, _) =>
DaeProducerIndirectCall
@lib.CallRef(_) | @lib.ReturnCallRef(_) => DaeProducerCallRef
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _) => DaeProducerStructuredControl
@lib.Unreachable => DaeProducerUnreachable
_ => DaeProducerOrdinaryExpression
}
}
///|
fn dae_value_producer_kind_from_range(
instrs : Array[@lib.Instruction],
start : Int32,
end : Int32,
) -> DaeValueProducerKind {
if start < 0 || end <= start || end > instrs.length() {
return DaeProducerUnknown
}
if end == start + 1 {
return dae_instr_producer_kind(instrs[start])
}
let mut kind = dae_instr_producer_kind(instrs[start])
let mut idx = start + 1
while idx < end {
let next = dae_instr_producer_kind(instrs[idx])
match (kind, next) {
(DaeProducerUnknown, _) | (_, DaeProducerUnknown) =>
return DaeProducerUnknown
(_, DaeProducerUnreachable) | (DaeProducerUnreachable, _) =>
kind = DaeProducerUnreachable
(_, DaeProducerStructuredControl) | (DaeProducerStructuredControl, _) =>
kind = DaeProducerStructuredControl
(_, DaeProducerIndirectCall) | (DaeProducerIndirectCall, _) =>
kind = DaeProducerIndirectCall
(_, DaeProducerCallRef) | (DaeProducerCallRef, _) =>
kind = DaeProducerCallRef
(_, DaeProducerDirectCall) | (DaeProducerDirectCall, _) =>
kind = DaeProducerDirectCall
(_, DaeProducerOrdinaryExpression)
| (DaeProducerOrdinaryExpression, _)
| (_, DaeProducerLocalRead)
| (DaeProducerLocalRead, _)
| (_, DaeProducerGlobalRead)
| (DaeProducerGlobalRead, _)
| (_, DaeProducerConstantOrPureLeaf) =>
kind = DaeProducerOrdinaryExpression
}
idx += 1
}
kind
}
///|
fn dae_range_observability_facts(
instrs : Array[@lib.Instruction],
start : Int32,
end : Int32,
) -> (Bool, Bool, Bool, Bool) {
let mut has_observable_effect = false
let mut may_trap = false
let mut has_escaping_branch = false
let mut produces_unreachable = false
if start < 0 || end < start || end > instrs.length() {
return (true, true, true, false)
}
let mut idx = start
while idx < end {
let evidence = dae_instruction_observability_evidence(instrs[idx])
if evidence.has_observable_effect {
has_observable_effect = true
}
if evidence.may_trap_or_unknown {
may_trap = true
}
if evidence.has_branch_control || dae_instr_has_branch(instrs[idx]) {
has_escaping_branch = true
}
match instrs[idx] {
@lib.Unreachable => produces_unreachable = true
_ => ()
}
idx += 1
}
(has_observable_effect, may_trap, has_escaping_branch, produces_unreachable)
}
///|
fn dae_value_slice_from_operand_disposition_with_purity(
disposition : DaeOperandDisposition,
pure : Bool,
) -> DaeValueSlice {
{
start: disposition.start,
end: disposition.end,
pure,
has_branch: disposition.has_branch,
has_effect_or_branch: disposition.has_effect_or_branch,
disposition,
input_arity: 0,
output_arity: 1,
value_type: None,
producer_kind: DaeProducerUnknown,
has_observable_effect: disposition.has_effect_or_branch &&
!disposition.has_branch,
may_trap: false,
has_escaping_branch: disposition.has_branch,
produces_unreachable: false,
call_kind: None,
call_idx: -1,
caller_def: -1,
callee_abs: -1,
module_epoch: -1,
call_epoch: -1,
type_epoch: -1,
signature_epoch: -1,
body_epoch: -1,
}
}
///|
fn dae_value_slice_enrich_from_instrs(
slice : DaeValueSlice,
instrs : Array[@lib.Instruction],
value_type : @lib.ValType?,
call_kind : DaeDirectCallKind?,
call_idx : Int32,
caller_def : Int32,
callee_abs : Int32,
module_epoch : Int32,
call_epoch : Int32,
type_epoch : Int32,
signature_epoch : Int32,
body_epoch : Int32,
) -> DaeValueSlice {
let producer_kind = dae_value_producer_kind_from_range(
instrs,
slice.start,
slice.end,
)
let (
has_observable_effect,
may_trap,
has_escaping_branch,
produces_unreachable,
) = dae_range_observability_facts(instrs, slice.start, slice.end)
{
..slice,
value_type,
producer_kind,
// Keep actual observable effects distinct from trap preservation. The
// operand disposition still records whether ordinary-mode removal must
// replay the slice, while traps-never-happen may ignore trap-only work.
has_observable_effect,
may_trap,
has_escaping_branch: has_escaping_branch || slice.has_branch,
produces_unreachable,
call_kind,
call_idx,
caller_def,
callee_abs,
module_epoch,
call_epoch,
type_epoch,
signature_epoch,
body_epoch,
input_arity: 0,
output_arity: 1,
}
}
///|
fn dae_value_slice_from_operand_disposition(
disposition : DaeOperandDisposition,
) -> DaeValueSlice {
dae_value_slice_from_operand_disposition_with_purity(
disposition,
disposition.droppable_without_repair,
)
}
///|
fn dae_value_slice_from_call_shape(
instrs : Array[@lib.Instruction],
shape : DaeCallShape,
) -> DaeValueSlice {
dae_value_slice_enrich_from_instrs(
dae_value_slice_from_operand_disposition(
dae_operand_disposition_from_call_shape(instrs, shape),
),
instrs,
None,
None,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
)
}
///|
fn dae_value_slice_from_range(
instrs : Array[@lib.Instruction],
start : Int32,
end : Int32,
) -> DaeValueSlice {
let disposition = match
dae_operand_disposition_from_range(instrs, start, end) {
Some(disposition) => disposition
None =>
{
start,
end,
droppable_without_repair: true,
preserve_effects_on_removal: false,
has_branch: false,
has_effect_or_branch: false,
}
}
dae_value_slice_enrich_from_instrs(
dae_value_slice_from_operand_disposition_with_purity(
disposition,
!disposition.has_effect_or_branch,
),
instrs,
None,
None,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
-1,
)
}
///|
fn dae_call_shape_from_value_slice(slice : DaeValueSlice) -> DaeCallShape {
{ start: slice.start, end: slice.end, pure: slice.pure, }
}
///|
fn dae_value_slice_single_instruction_index(slice : DaeValueSlice) -> Int32? {
if slice.end == slice.start + 1 {
Some(slice.start)
} else {
None
}
}
///|
fn dae_find_select_value_slices(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
body : Array[@lib.Instruction],
select_idx : Int32,
required_left_start~ : Int32 = -1,
require_dropped_prefix~ : Bool = false,
) -> DaeSelectValueSlices? {
if select_idx < 0 || select_idx >= body.length() {
return None
}
match body[select_idx] {
@lib.Select(_) => ()
_ => return None
}
let mut condition_start = select_idx - 1
while condition_start >= 2 {
let condition = match
dae_select_condition_localization_evidence(
mod_, body, condition_start, select_idx, func_type_indices,
) {
Some(evidence) => evidence
None => {
condition_start -= 1
continue
}
}
let (value_slices, cursor) = match
dae_collect_callsite_value_slices(
body, condition_start, 2, 0, mod_, func_type_indices,
) {
Some(evidence) => evidence
None => {
condition_start -= 1
continue
}
}
let left = value_slices[0]
let right = value_slices[1]
if required_left_start >= 0 && left.start != required_left_start {
condition_start -= 1
continue
}
if require_dropped_prefix &&
dae_trivially_dropped_prefix_evidence(body, cursor) is None {
condition_start -= 1
continue
}
return Some({ left, right, condition, select_idx, })
}
None
}
///|
fn dae_value_slice_disposition_is_consistent(slice : DaeValueSlice) -> Bool {
if slice.start != slice.disposition.start ||
slice.end != slice.disposition.end {
return false
}
if slice.has_branch != slice.disposition.has_branch ||
slice.has_effect_or_branch != slice.disposition.has_effect_or_branch {
return false
}
let direct_rewrite = dae_operand_rewrite_disposition(
slice.disposition,
localization_available=false,
has_escaping_branch=slice.disposition.has_branch,
)
if !dae_operand_rewrite_disposition_is_consistent(direct_rewrite) ||
slice.disposition.preserve_effects_on_removal ==
dae_operand_rewrite_drops_directly(direct_rewrite) {
return false
}
if slice.pure && slice.has_effect_or_branch {
return false
}
if slice.has_branch && !slice.has_effect_or_branch {
return false
}
true
}
///|
fn dae_select_value_slices_are_consistent(
slices : DaeSelectValueSlices,
select_idx : Int32,
) -> Bool {
slices.select_idx == select_idx &&
slices.left.start < slices.left.end &&
slices.left.end == slices.right.start &&
slices.right.start < slices.right.end &&
slices.right.end <= slices.condition.start &&
slices.condition.end == slices.select_idx &&
dae_select_condition_localization_evidence_is_consistent(slices.condition) &&
dae_value_slice_disposition_is_consistent(slices.left) &&
dae_value_slice_disposition_is_consistent(slices.right)
}
///|
fn dae_control_result_slice_is_consistent(
result_slice : DaeControlResultSlice,
) -> Bool {
if result_slice.result_count <= 0 {
return false
}
if result_slice.carrier_kind < 0 || result_slice.carrier_kind > 4 {
return false
}
if result_slice.slice.start < 0 ||
result_slice.slice.end > result_slice.body.length() {
return false
}
if result_slice.slice.end - result_slice.slice.start !=
result_slice.result_count {
return false
}
if !dae_value_slice_disposition_is_consistent(result_slice.slice) {
return false
}
if result_slice.terminal_return {
if result_slice.slice.end >= result_slice.body.length() {
return false
}
match result_slice.body[result_slice.body.length() - 1] {
@lib.Return => ()
_ => return false
}
}
true
}
///|
fn dae_make_multi_result_control_slice(
body : Array[@lib.Instruction],
start : Int32,
result_count : Int32,
carrier_kind : Int32,
flattened : Bool,
terminal_return : Bool,
) -> DaeControlResultSlice? {
if start < 0 || result_count <= 0 {
return None
}
let end = start + result_count
let result_slice = {
body,
slice: dae_value_slice_from_range(body, start, end),
result_count,
carrier_kind,
flattened,
terminal_return,
}
if dae_control_result_slice_is_consistent(result_slice) {
Some(result_slice)
} else {
None
}
}
///|
fn dae_effect_free_body_evidence_is_consistent(
evidence : DaeEffectFreeBodyEvidence,
) -> Bool {
evidence.body_length == evidence.body.length() &&
evidence.effect_free == !evidence.has_observable_effect_or_branch &&
evidence.effect_free
}
///|
fn dae_effect_free_body_evidence(
body : Array[@lib.Instruction],
) -> DaeEffectFreeBodyEvidence? {
let has_observable_effect_or_branch = dae_instrs_have_observable_effect_or_branch(
body,
)
let evidence = {
body,
body_length: body.length(),
has_observable_effect_or_branch,
effect_free: !has_observable_effect_or_branch,
}
if dae_effect_free_body_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
///|
fn dae_terminal_return_body_evidence_is_consistent(
evidence : DaeTerminalReturnBodyEvidence,
) -> Bool {
if evidence.body_length != evidence.body.length() || evidence.body_length == 0 {
return false
}
if evidence.return_idx != evidence.body_length - 1 ||
evidence.prefix_length != evidence.return_idx {
return false
}
match evidence.body[evidence.return_idx] {
@lib.Return => ()
_ => return false
}
evidence.terminal_return_only ==
!evidence.prefix_has_observable_effect_or_branch &&
evidence.terminal_return_only
}
///|
fn dae_terminal_return_body_evidence(
body : Array[@lib.Instruction],
) -> DaeTerminalReturnBodyEvidence? {
if body.length() == 0 {
return None
}
let return_idx = body.length() - 1
match body[return_idx] {
@lib.Return => ()
_ => return None
}
let mut prefix_has_observable_effect_or_branch = false
let mut idx = 0
while idx < return_idx {
if dae_instr_has_observable_effect_or_branch(body[idx]) {
prefix_has_observable_effect_or_branch = true
}
idx += 1
}
let evidence = {
body,
body_length: body.length(),
return_idx,
prefix_length: return_idx,
prefix_has_observable_effect_or_branch,
terminal_return_only: !prefix_has_observable_effect_or_branch,
}
if dae_terminal_return_body_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
///|
fn dae_single_block_result_carrier_evidence(
mod_ : @lib.Module,
body : Array[@lib.Instruction],
result_count : Int32,
) -> DaeSingleBlockResultCarrierEvidence? {
if result_count <= 0 {
return None
}
match body {
[@lib.Block(block_type, @lib.Expr(block_body))] => {
match
dae_control_block_type_zero_input_output_count_evidence(
block_type,
mod_.type_sec,
result_count,
) {
Some(_) => ()
None => return None
}
match dae_effect_free_body_evidence(block_body) {
Some(effect_free_body) =>
Some({
body: block_body,
result_count,
body_has_effect_or_branch: effect_free_body.has_observable_effect_or_branch,
effect_free_body,
})
None => None
}
}
_ => None
}
}
///|
fn dae_multi_result_control_carrier_chain_slice(
mod_ : @lib.Module,
body : Array[@lib.Instruction],
result_count : Int32,
terminal_return~ : Bool,
match_terminal_return~ : Bool,
append_terminal_return~ : Bool,
) -> DaeControlResultSlice? {
if result_count <= 0 {
return None
}
let mut candidate_body = body
let mut flattened = false
let mut carrier_kind = 0
let mut keep_unwrapping = true
while keep_unwrapping {
match
dae_flat_multi_result_suffix_evidence(
candidate_body, result_count, match_terminal_return,
) {
Some(flat_suffix) => {
let result_body = if append_terminal_return {
let flattened_body = candidate_body.copy()
flattened_body.push(@lib.Instruction::return_())
flattened_body
} else {
candidate_body
}
return dae_make_multi_result_control_slice(
result_body,
flat_suffix.start,
flat_suffix.result_count,
carrier_kind,
flattened,
terminal_return,
)
}
None => ()
}
keep_unwrapping = false
match
dae_single_block_result_carrier_evidence(
mod_, candidate_body, result_count,
) {
Some(evidence) =>
if evidence.result_count == result_count &&
!evidence.body_has_effect_or_branch &&
evidence.effect_free_body.effect_free {
candidate_body = evidence.body
flattened = true
carrier_kind = 1
keep_unwrapping = true
}
None => ()
}
}
None
}
///|
fn dae_next_free_local_idx(
instrs : Array[@lib.Instruction],
declared_total : Int32,
) -> Int32 {
let mut next = if declared_total < 0 { 0 } else { declared_total }
for instr in instrs {
match instr {
@lib.LocalGet(idx) | @lib.LocalSet(idx) | @lib.LocalTee(idx) => {
let candidate = dae_local_idx_slot(idx) + 1
if candidate > next {
next = candidate
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) => {
let candidate = dae_next_free_local_idx(body, next)
if candidate > next {
next = candidate
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
next = dae_next_free_local_idx(body, next)
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
next = dae_next_free_local_idx(catch_body, next)
}
}
@lib.If(_, then_body, else_body) => {
let then_candidate = dae_next_free_local_idx(then_body, next)
if then_candidate > next {
next = then_candidate
}
match else_body {
Some(body) => {
let else_candidate = dae_next_free_local_idx(body, next)
if else_candidate > next {
next = else_candidate
}
}
None => ()
}
}
_ => ()
}
}
next
}
///|
fn dae_local_indices_fit_instrs(
instrs : Array[@lib.Instruction],
total_locals : Int32,
) -> Bool {
total_locals >= 0 && dae_next_free_local_idx(instrs, 0) <= total_locals
}
///|
fn dae_rewrite_instruction_locals(
instr : @lib.Instruction,
local_map : Array[Int32],
) -> @lib.Instruction {
match instr {
@lib.LocalGet(idx) =>
@lib.Instruction::local_get(
dae_rewrite_local_idx(dae_local_idx_slot(idx), local_map),
)
@lib.LocalSet(idx) =>
@lib.Instruction::local_set(
dae_rewrite_local_idx(dae_local_idx_slot(idx), local_map),
)
@lib.LocalTee(idx) =>
@lib.Instruction::local_tee(
dae_rewrite_local_idx(dae_local_idx_slot(idx), local_map),
)
@lib.Block(block_type, @lib.Expr(body)) =>
@lib.Instruction::block(
block_type,
@lib.Expr::new(dae_rewrite_locals_instrs(body, local_map)),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(dae_rewrite_locals_instrs(body, local_map)),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(dae_rewrite_locals_instrs(body, local_map)),
)
@lib.Try(block_type, @lib.Expr(body), catches, delegate) =>
@lib.Instruction::try_(
block_type,
@lib.Expr::new(dae_rewrite_locals_instrs(body, local_map)),
catches.map(fn(catch_) {
match catch_ {
@lib.LegacyCatch(tag, @lib.Expr(catch_body)) =>
@lib.LegacyCatch::tagged(
tag,
@lib.Expr::new(dae_rewrite_locals_instrs(catch_body, local_map)),
)
@lib.LegacyCatchAll(@lib.Expr(catch_body)) =>
@lib.LegacyCatch::all(
@lib.Expr::new(dae_rewrite_locals_instrs(catch_body, local_map)),
)
}
}),
delegate~,
)
@lib.If(block_type, then_body, else_body) =>
@lib.Instruction::if_(
block_type,
dae_rewrite_locals_instrs(then_body, local_map),
match else_body {
Some(body) => Some(dae_rewrite_locals_instrs(body, local_map))
None => None
},
)
_ => instr
}
}
///|
fn dae_rewrite_locals_instrs(
instrs : Array[@lib.Instruction],
local_map : Array[Int32],
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
for instr in instrs {
out.push(dae_rewrite_instruction_locals(instr, local_map))
}
out
}
///|
fn dae_negate_compare_instr(instr : @lib.Instruction) -> @lib.Instruction? {
match instr {
@lib.I32Eq => Some(@lib.Instruction::i32_ne())
@lib.I32Ne => Some(@lib.Instruction::i32_eq())
@lib.I32LtS => Some(@lib.Instruction::i32_ge_s())
@lib.I32LtU => Some(@lib.Instruction::i32_ge_u())
@lib.I32LeS => Some(@lib.Instruction::i32_gt_s())
@lib.I32LeU => Some(@lib.Instruction::i32_gt_u())
@lib.I32GtS => Some(@lib.Instruction::i32_le_s())
@lib.I32GtU => Some(@lib.Instruction::i32_le_u())
@lib.I32GeS => Some(@lib.Instruction::i32_lt_s())
@lib.I32GeU => Some(@lib.Instruction::i32_lt_u())
@lib.I64Eq => Some(@lib.Instruction::i64_ne())
@lib.I64Ne => Some(@lib.Instruction::i64_eq())
@lib.I64LtS => Some(@lib.Instruction::i64_ge_s())
@lib.I64LtU => Some(@lib.Instruction::i64_ge_u())
@lib.I64LeS => Some(@lib.Instruction::i64_gt_s())
@lib.I64LeU => Some(@lib.Instruction::i64_gt_u())
@lib.I64GtS => Some(@lib.Instruction::i64_le_s())
@lib.I64GtU => Some(@lib.Instruction::i64_le_u())
@lib.I64GeS => Some(@lib.Instruction::i64_lt_s())
@lib.I64GeU => Some(@lib.Instruction::i64_lt_u())
// Ordered floating comparisons are not complements when either operand is NaN.
@lib.F32Eq => Some(@lib.Instruction::f32_ne())
@lib.F32Ne => Some(@lib.Instruction::f32_eq())
@lib.F64Eq => Some(@lib.Instruction::f64_ne())
@lib.F64Ne => Some(@lib.Instruction::f64_eq())
_ => None
}
}
///|
fn dae_collect_written_locals_instr(
instr : @lib.Instruction,
written : Array[Bool],
) -> Unit {
match instr {
@lib.LocalSet(idx) | @lib.LocalTee(idx) => {
let slot = dae_local_idx_slot(idx)
if slot >= 0 && slot < written.length() {
written[slot] = true
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
for child in body {
dae_collect_written_locals_instr(child, written)
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
for child in body {
dae_collect_written_locals_instr(child, written)
}
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
for child in catch_body {
dae_collect_written_locals_instr(child, written)
}
}
}
@lib.If(_, then_body, Some(else_body)) => {
for child in then_body {
dae_collect_written_locals_instr(child, written)
}
for child in else_body {
dae_collect_written_locals_instr(child, written)
}
}
@lib.If(_, then_body, None) =>
for child in then_body {
dae_collect_written_locals_instr(child, written)
}
_ => ()
}
}
///|
fn dae_collect_written_locals(
instrs : Array[@lib.Instruction],
total_locals : Int32,
) -> Array[Bool] {
let written = Array::make(total_locals, false)
for instr in instrs {
dae_collect_written_locals_instr(instr, written)
}
written
}
///|
fn dae_default_zero_high_guard_is_false(
instrs : Array[@lib.Instruction],
param_count : Int32,
written : Array[Bool],
) -> Bool {
match instrs {
[@lib.LocalGet(local_idx), @lib.I32Const(@lib.I32(57)), @lib.I32GtS] => {
let slot = dae_local_idx_slot(local_idx)
slot >= param_count && slot < written.length() && !written[slot]
}
_ => false
}
}
///|
fn dae_simplify_default_zero_high_guard_instr(
instr : @lib.Instruction,
param_count : Int32,
written : Array[Bool],
) -> @lib.Instruction {
match instr {
@lib.Block(block_type, @lib.Expr(body)) =>
@lib.Instruction::block(
block_type,
@lib.Expr::new(
dae_simplify_default_zero_high_guard_instrs(
body, param_count, written,
),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
dae_simplify_default_zero_high_guard_instrs(
body, param_count, written,
),
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
dae_simplify_default_zero_high_guard_instrs(
body, param_count, written,
),
),
)
@lib.If(block_type, then_body, Some(else_body)) => {
let simplified_then = dae_simplify_default_zero_high_guard_instrs(
then_body, param_count, written,
)
let simplified_else = dae_simplify_default_zero_high_guard_instrs(
else_body, param_count, written,
)
if block_type != @lib.BlockType::void_() &&
simplified_then.length() == 1 &&
simplified_then[0] == @lib.Instruction::i32_const(@lib.I32(1)) &&
dae_default_zero_high_guard_is_false(
simplified_else, param_count, written,
) {
@lib.Instruction::if_(
block_type,
simplified_then,
Some([@lib.Instruction::i32_const(@lib.I32(0))]),
)
} else {
@lib.Instruction::if_(
block_type,
simplified_then,
Some(simplified_else),
)
}
}
@lib.If(block_type, then_body, None) =>
@lib.Instruction::if_(
block_type,
dae_simplify_default_zero_high_guard_instrs(
then_body, param_count, written,
),
None,
)
_ => instr
}
}
///|
fn dae_simplify_default_zero_high_guard_instrs(
instrs : Array[@lib.Instruction],
param_count : Int32,
written : Array[Bool],
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
for instr in instrs {
out.push(
dae_simplify_default_zero_high_guard_instr(instr, param_count, written),
)
}
out
}
///|
fn dae_known_i32_const_for_local_before(
instrs : Array[@lib.Instruction],
local_idx : @lib.LocalIdx,
) -> Int32? {
let local_slot = dae_local_idx_slot(local_idx)
let mut idx = instrs.length() - 1
while idx >= 0 {
if dae_instr_references_local(instrs[idx], local_idx) {
match instrs[idx] {
@lib.LocalSet(set_idx) if dae_local_idx_slot(set_idx) == local_slot &&
idx > 0 =>
match instrs[idx - 1] {
@lib.I32Const(@lib.I32(value)) => return Some(value)
_ => return None
}
_ => return None
}
}
idx -= 1
}
None
}
///|
fn dae_simplify_rewritten_control_instrs(
instrs : Array[@lib.Instruction],
invert_zero_eq_if : Bool,
) -> Array[@lib.Instruction] {
let rewritten : Array[@lib.Instruction] = []
for instr in instrs {
rewritten.push(
match instr {
@lib.Block(block_type, @lib.Expr(body)) =>
@lib.Instruction::block(
block_type,
@lib.Expr::new(
dae_simplify_rewritten_control_instrs(body, invert_zero_eq_if),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
dae_simplify_rewritten_control_instrs(body, invert_zero_eq_if),
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
dae_simplify_rewritten_control_instrs(body, invert_zero_eq_if),
),
)
@lib.If(block_type, then_body, Some(else_body)) =>
@lib.Instruction::if_(
block_type,
dae_simplify_rewritten_control_instrs(then_body, invert_zero_eq_if),
Some(
dae_simplify_rewritten_control_instrs(
else_body, invert_zero_eq_if,
),
),
)
@lib.If(block_type, then_body, None) =>
@lib.Instruction::if_(
block_type,
dae_simplify_rewritten_control_instrs(then_body, invert_zero_eq_if),
None,
)
_ => instr
},
)
}
let out : Array[@lib.Instruction] = []
let mut idx = 0
while idx < rewritten.length() {
if idx + 1 < rewritten.length() {
match (rewritten[idx], rewritten[idx + 1]) {
(@lib.I32Const(@lib.I32(condition)), @lib.If(_, then_body, else_body)) => {
let selected_body = if condition == 0 {
match else_body {
Some(body) => body
None => []
}
} else {
then_body
}
// Removing the selected `if` label is safe only when the retained arm
// has no label-sensitive branch. The discarded arm is unreachable
// under the materialized constant and may contain arbitrary effects.
if !selected_body.any(fn(instr) { dae_instr_has_branch(instr) }) {
for selected_instr in selected_body {
out.push(selected_instr)
}
idx += 2
continue
}
}
_ => ()
}
}
if idx + 1 < rewritten.length() {
match (rewritten[idx], rewritten[idx + 1]) {
(@lib.LocalGet(local_idx), @lib.If(_, then_body, else_body)) =>
match dae_known_i32_const_for_local_before(out, local_idx) {
Some(condition) => {
let selected_body = if condition == 0 {
match else_body {
Some(body) => body
None => []
}
} else {
then_body
}
if !selected_body.any(fn(instr) { dae_instr_has_branch(instr) }) {
for selected_instr in selected_body {
out.push(selected_instr)
}
idx += 2
continue
}
}
None => ()
}
_ => ()
}
}
if invert_zero_eq_if && idx + 2 < rewritten.length() {
match (rewritten[idx], rewritten[idx + 1], rewritten[idx + 2]) {
(
@lib.I32Const(@lib.I32(0)),
@lib.I32Eq,
@lib.If(block_type, then_body, Some(else_body)),
) => {
out.push(
@lib.Instruction::if_(block_type, else_body, Some(then_body)),
)
idx += 3
continue
}
(
condition,
@lib.If(bool_block_type, bool_then, Some(bool_else)),
@lib.If(use_block_type, use_then, None),
) if dae_block_type_is_zero_param_single_result(bool_block_type, None) &&
dae_block_type_is_zero_param_zero_result(use_block_type, None) &&
bool_then.length() == 1 &&
bool_then[0] == @lib.Instruction::i32_const(@lib.I32(1)) &&
bool_else.length() == 1 &&
bool_else[0] == @lib.Instruction::i32_const(@lib.I32(0)) => {
out.push(condition)
out.push(@lib.Instruction::if_(use_block_type, use_then, None))
idx += 3
continue
}
_ => ()
}
}
if idx + 1 < rewritten.length() {
match (dae_negate_compare_instr(rewritten[idx]), rewritten[idx + 1]) {
(Some(negated), @lib.If(block_type, then_body, Some(else_body))) if block_type ==
@lib.BlockType::void_() &&
then_body.length() == 0 &&
else_body.length() == 1 &&
else_body[0] == @lib.Instruction::unreachable_() => {
out.push(negated)
out.push(
@lib.Instruction::if_(
@lib.BlockType::void_(),
[@lib.Instruction::unreachable_()],
None,
),
)
idx += 2
continue
}
(Some(negated), @lib.I32Eqz) => {
out.push(negated)
idx += 2
continue
}
_ => ()
}
}
out.push(rewritten[idx])
idx += 1
}
out
}
///|
fn dae_simplify_rewritten_add_zero_instrs(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
let rewritten : Array[@lib.Instruction] = []
for instr in instrs {
rewritten.push(
match instr {
@lib.Block(block_type, @lib.Expr(body)) =>
@lib.Instruction::block(
block_type,
@lib.Expr::new(dae_simplify_rewritten_add_zero_instrs(body)),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(dae_simplify_rewritten_add_zero_instrs(body)),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(dae_simplify_rewritten_add_zero_instrs(body)),
)
@lib.If(block_type, then_body, else_body) =>
@lib.Instruction::if_(
block_type,
dae_simplify_rewritten_add_zero_instrs(then_body),
match else_body {
Some(body) => Some(dae_simplify_rewritten_add_zero_instrs(body))
None => None
},
)
_ => instr
},
)
}
let out : Array[@lib.Instruction] = []
let mut idx = 0
while idx < rewritten.length() {
if idx + 2 < rewritten.length() {
match (rewritten[idx], rewritten[idx + 1], rewritten[idx + 2]) {
(@lib.I32Const(_), @lib.LocalGet(_), @lib.I32Add)
| (@lib.I64Const(_), @lib.LocalGet(_), @lib.I64Add) => {
out.push(rewritten[idx + 1])
out.push(rewritten[idx])
out.push(rewritten[idx + 2])
idx += 3
continue
}
_ => ()
}
}
if idx + 1 < rewritten.length() {
match (rewritten[idx], rewritten[idx + 1]) {
(@lib.I32Const(@lib.I32(0)), @lib.I32Add)
| (@lib.I64Const(@lib.I64(0L)), @lib.I64Add) => {
idx += 2
continue
}
_ => ()
}
}
out.push(rewritten[idx])
idx += 1
}
out
}
///|
fn dae_instrs_are_materializable_ref_const_expr(
instrs : Array[@lib.Instruction],
) -> Bool {
if instrs.length() < 2 {
return false
}
match (instrs[0], instrs[1]) {
(@lib.I32Const(_), @lib.RefI31) => ()
_ => return false
}
let mut externalized = false
let mut idx = 2
while idx < instrs.length() {
match (externalized, instrs[idx]) {
(false, @lib.ExternConvertAny) => externalized = true
(true, @lib.AnyConvertExtern) => externalized = false
_ => return false
}
idx += 1
}
true
}
///|
fn dae_instr_is_materializable_const(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.V128Const(_)
| @lib.RefNull(_)
| @lib.RefFunc(_)
| @lib.StringConst(_) => true
@lib.Block(_, @lib.Expr(body)) =>
dae_instrs_are_materializable_ref_const_expr(body)
_ => false
}
}
///|
fn dae_global_get_is_immutable(
mod_ : @lib.Module,
global_idx : @lib.GlobalIdx,
) -> Bool {
let slot = dae_global_idx_slot(global_idx)
let mut imported_globals = 0
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
match import_ {
@lib.Import(_, _, @lib.GlobalExternType(@lib.GlobalType(_, mutable))) => {
if imported_globals == slot {
return !mutable
}
imported_globals += 1
}
_ => ()
}
}
None => ()
}
let defined_slot = slot - imported_globals
if defined_slot < 0 {
return false
}
match mod_.global_sec {
Some(@lib.GlobalSec(globals)) if defined_slot < globals.length() =>
match globals[defined_slot] {
@lib.Global(@lib.GlobalType(_, mutable), _) => !mutable
}
_ => false
}
}
///|
fn dae_instr_is_materializable_const_with_immutable_global(
mod_ : @lib.Module,
instr : @lib.Instruction,
) -> Bool {
if dae_instr_is_materializable_const(instr) {
return true
}
match instr {
@lib.GlobalGet(global_idx) => dae_global_get_is_immutable(mod_, global_idx)
_ => false
}
}
///|
fn dae_instruction_node_count(instrs : Array[@lib.Instruction]) -> Int32 {
let mut count = instrs.length()
for instr in instrs {
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
count += dae_instruction_node_count(body)
@lib.If(_, then_body, else_body) => {
count += dae_instruction_node_count(then_body)
match else_body {
Some(body) => count += dae_instruction_node_count(body)
None => ()
}
}
_ => ()
}
}
count
}
///|
fn dae_collect_immediate_materializable_call_targets_instrs(
mod_ : @lib.Module,
instrs : Array[@lib.Instruction],
imported : Int32,
large_defs : Array[Bool],
selected : Array[Bool],
) -> Unit {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match instrs[idx] {
@lib.Call(func_idx) if idx > 0 => {
let def_idx = dae_func_idx_slot(func_idx) - imported
if def_idx >= 0 && def_idx < selected.length() {
let immediate = instrs[idx - 1]
let immutable_global = match immediate {
@lib.GlobalGet(global_idx) =>
dae_global_get_is_immutable(mod_, global_idx)
_ => false
}
if immutable_global ||
(
large_defs[def_idx] &&
dae_instr_is_materializable_const(immediate)
) {
selected[def_idx] = true
}
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_collect_immediate_materializable_call_targets_instrs(
mod_, body, imported, large_defs, selected,
)
@lib.If(_, then_body, else_body) => {
dae_collect_immediate_materializable_call_targets_instrs(
mod_, then_body, imported, large_defs, selected,
)
match else_body {
Some(body) =>
dae_collect_immediate_materializable_call_targets_instrs(
mod_, body, imported, large_defs, selected,
)
None => ()
}
}
_ => ()
}
}
}
///|
fn dae_collect_large_uniform_const_revisit_defs(
mod_ : @lib.Module,
) -> Array[Bool] {
let imported = dae_imported_func_count(mod_)
let selected = Array::make(dae_defined_func_count(mod_), false)
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
let large_defs = Array::make(funcs.length(), false)
for def_idx = 0; def_idx < funcs.length(); def_idx = def_idx + 1 {
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
large_defs[def_idx] = dae_instruction_node_count(body) >= 64
}
for func in funcs {
let @lib.Func(_, @lib.Expr(body)) = func
dae_collect_immediate_materializable_call_targets_instrs(
mod_, body, imported, large_defs, selected,
)
}
}
None => ()
}
selected
}
///|
fn dae_materializable_dropped_const_prefix_leaf(
body : Array[@lib.Instruction],
resolve_leaf : (@lib.Instruction) -> @lib.Instruction?,
) -> @lib.Instruction? {
if body.length() == 1 {
return resolve_leaf(body[0])
}
if body.length() < 3 || body.length() % 2 == 0 {
return None
}
let mut idx = 0
while idx + 1 < body.length() {
match resolve_leaf(body[idx]) {
Some(_) => ()
None => return None
}
match body[idx + 1] {
@lib.Drop => ()
_ => return None
}
idx += 2
}
resolve_leaf(body[body.length() - 1])
}
///|
fn dae_materializable_control_const(
mod_ : @lib.Module,
instr : @lib.Instruction,
allow_immutable_global_get : Bool,
) -> @lib.Instruction? {
let resolve_leaf = fn(candidate : @lib.Instruction) -> @lib.Instruction? {
if allow_immutable_global_get {
if dae_instr_is_materializable_const_with_immutable_global(
mod_, candidate,
) {
Some(candidate)
} else {
None
}
} else if dae_instr_is_materializable_const(candidate) {
Some(candidate)
} else {
None
}
}
match instr {
@lib.Block(_, @lib.Expr(body)) if body.length() == 1 =>
match resolve_leaf(body[0]) {
Some(leaf) => Some(leaf)
None =>
dae_materializable_control_const(
mod_,
body[0],
allow_immutable_global_get,
)
}
@lib.Block(_, @lib.Expr(body)) => {
let resolve_control_leaf = fn(
candidate : @lib.Instruction,
) -> @lib.Instruction? {
match resolve_leaf(candidate) {
Some(leaf) => Some(leaf)
None =>
dae_materializable_control_const(
mod_, candidate, allow_immutable_global_get,
)
}
}
dae_materializable_dropped_const_prefix_leaf(body, resolve_control_leaf)
}
@lib.Loop(_, @lib.Expr(body)) if body.length() == 1 =>
match resolve_leaf(body[0]) {
Some(leaf) => Some(leaf)
None =>
dae_materializable_control_const(
mod_,
body[0],
allow_immutable_global_get,
)
}
@lib.Loop(_, @lib.Expr(body)) => {
let resolve_control_leaf = fn(
candidate : @lib.Instruction,
) -> @lib.Instruction? {
match resolve_leaf(candidate) {
Some(leaf) => Some(leaf)
None =>
dae_materializable_control_const(
mod_, candidate, allow_immutable_global_get,
)
}
}
dae_materializable_dropped_const_prefix_leaf(body, resolve_control_leaf)
}
@lib.TryTable(_, _, @lib.Expr(body)) if body.length() == 1 =>
match resolve_leaf(body[0]) {
Some(leaf) => Some(leaf)
None =>
dae_materializable_control_const(
mod_,
body[0],
allow_immutable_global_get,
)
}
@lib.TryTable(_, _, @lib.Expr(body)) => {
let resolve_control_leaf = fn(
candidate : @lib.Instruction,
) -> @lib.Instruction? {
match resolve_leaf(candidate) {
Some(leaf) => Some(leaf)
None =>
dae_materializable_control_const(
mod_, candidate, allow_immutable_global_get,
)
}
}
dae_materializable_dropped_const_prefix_leaf(body, resolve_control_leaf)
}
@lib.If(_, then_body, Some(else_body)) => {
let resolve_arm_leaf = fn(
candidate : @lib.Instruction,
) -> @lib.Instruction? {
match resolve_leaf(candidate) {
Some(leaf) => Some(leaf)
None =>
dae_materializable_control_const(
mod_, candidate, allow_immutable_global_get,
)
}
}
match
(
dae_materializable_dropped_const_prefix_leaf(
then_body, resolve_arm_leaf,
),
dae_materializable_dropped_const_prefix_leaf(
else_body, resolve_arm_leaf,
),
) {
(Some(then_const), Some(else_const)) =>
if dae_materialized_const_equal(then_const, else_const) {
Some(then_const)
} else {
None
}
_ => None
}
}
_ => None
}
}
///|
fn dae_rewrite_instruction_param_consts(
instr : @lib.Instruction,
const_params : Array[@lib.Instruction?],
) -> @lib.Instruction {
match instr {
@lib.LocalGet(local_idx) => {
let slot = dae_local_idx_slot(local_idx)
if slot >= 0 && slot < const_params.length() {
match const_params[slot] {
Some(const_instr) => const_instr
None => instr
}
} else {
instr
}
}
@lib.Block(block_type, @lib.Expr(body)) =>
@lib.Instruction::block(
block_type,
@lib.Expr::new(dae_rewrite_param_const_instrs(body, const_params)),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(dae_rewrite_param_const_instrs(body, const_params)),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(dae_rewrite_param_const_instrs(body, const_params)),
)
@lib.Try(block_type, @lib.Expr(body), catches, delegate) =>
@lib.Instruction::try_(
block_type,
@lib.Expr::new(dae_rewrite_param_const_instrs(body, const_params)),
catches.map(fn(catch_) {
match catch_ {
@lib.LegacyCatch(tag, @lib.Expr(catch_body)) =>
@lib.LegacyCatch::tagged(
tag,
@lib.Expr::new(
dae_rewrite_param_const_instrs(catch_body, const_params),
),
)
@lib.LegacyCatchAll(@lib.Expr(catch_body)) =>
@lib.LegacyCatch::all(
@lib.Expr::new(
dae_rewrite_param_const_instrs(catch_body, const_params),
),
)
}
}),
delegate~,
)
@lib.If(block_type, then_body, else_body) =>
@lib.Instruction::if_(
block_type,
dae_rewrite_param_const_instrs(then_body, const_params),
match else_body {
Some(body) => Some(dae_rewrite_param_const_instrs(body, const_params))
None => None
},
)
_ => instr
}
}
///|
fn dae_rewrite_param_const_instrs(
instrs : Array[@lib.Instruction],
const_params : Array[@lib.Instruction?],
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
for instr in instrs {
out.push(dae_rewrite_instruction_param_consts(instr, const_params))
}
out
}
///|
fn dae_build_local_map(
param_types : Array[@lib.ValType],
body_local_count : Int32,
remove : Array[Bool],
local_use : DaeLocalUse,
preserve_removed_params~ : Array[Bool]? = None,
) -> Array[Int32] {
let param_count = param_types.length()
let local_map = Array::make(param_count + body_local_count, -1)
let mut next_param = 0
let mut removed_local_count = 0
for idx = 0; idx < param_count; idx = idx + 1 {
if remove[idx] {
let localized = match preserve_removed_params {
Some(preserve) if idx < preserve.length() => preserve[idx]
_ => false
}
let projection = dae_removed_param_local_projection_evidence(
param_types[idx],
written=local_use.writes[idx],
localized~,
)
if projection.preserve_as_body_local {
// Writes and caller-side localization both preserve the removed param's
// former slot as a body local until later cleanup owns its deletion.
local_map[idx] = -1 - removed_local_count
removed_local_count += 1
}
} else {
local_map[idx] = next_param
next_param += 1
}
}
let mut idx = 0
while idx < param_count {
if local_map[idx] < 0 {
local_map[idx] = next_param + (-1 - local_map[idx])
}
idx += 1
}
let mut body_idx = 0
while body_idx < body_local_count {
local_map[param_count + body_idx] = next_param +
removed_local_count +
body_idx
body_idx += 1
}
local_map
}
///|
fn dae_collect_callsite_value_slices(
instrs : Array[@lib.Instruction],
call_idx : Int32,
arg_count : Int32,
entry_stack_values : Int32,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
allow_stack_carried_void_effects~ : Bool = false,
) -> (Array[DaeValueSlice], Int32)? {
let empty_disposition = {
start: 0,
end: 0,
droppable_without_repair: false,
preserve_effects_on_removal: true,
has_branch: false,
has_effect_or_branch: false,
}
let slices = Array::make(
arg_count,
dae_value_slice_from_operand_disposition_with_purity(
empty_disposition, false,
),
)
let mut cursor = call_idx
let mut ambient_remaining = entry_stack_values
let mut arg_idx = arg_count - 1
while arg_idx >= 0 {
match
dae_find_value_slice(
instrs,
cursor,
ambient_remaining,
mod_,
func_type_indices,
allow_stack_carried_void_effects~,
) {
Some((shape, next_cursor, next_ambient_remaining)) => {
slices[arg_idx] = dae_value_slice_from_call_shape(instrs, shape)
cursor = next_cursor
ambient_remaining = next_ambient_remaining
}
None => return None
}
arg_idx -= 1
}
Some((slices, cursor))
}
///|
fn dae_callsite_value_slice_evidence_is_consistent(
evidence : DaeCallsiteValueSliceEvidence,
call_idx : Int32,
arg_count : Int32,
entry_stack_values : Int32,
) -> Bool {
if evidence.call_idx != call_idx ||
evidence.arg_count != arg_count ||
evidence.entry_stack_values != entry_stack_values ||
evidence.slices.length() != arg_count ||
evidence.cursor < 0 ||
evidence.cursor > call_idx {
return false
}
let mut idx = 0
while idx < evidence.slices.length() {
if !dae_value_slice_disposition_is_consistent(evidence.slices[idx]) {
return false
}
idx += 1
}
true
}
///|
fn dae_callsite_value_slice_evidence(
instrs : Array[@lib.Instruction],
call_idx : Int32,
arg_count : Int32,
entry_stack_values : Int32,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
allow_stack_carried_void_effects~ : Bool = false,
) -> DaeCallsiteValueSliceEvidence? {
match
dae_collect_callsite_value_slices(
instrs,
call_idx,
arg_count,
entry_stack_values,
mod_,
func_type_indices,
allow_stack_carried_void_effects~,
) {
Some((slices, cursor)) => {
let evidence = {
call_idx,
arg_count,
entry_stack_values,
slices,
cursor,
}
if dae_callsite_value_slice_evidence_is_consistent(
evidence, call_idx, arg_count, entry_stack_values,
) {
Some(evidence)
} else {
None
}
}
None => None
}
}
///|
fn dae_direct_call_kind_at(
instrs : Array[@lib.Instruction],
call_idx : Int32,
callee_abs : Int32,
expected_kind : DaeDirectCallKind,
) -> Bool {
if call_idx < 0 || call_idx >= instrs.length() {
return false
}
match (instrs[call_idx], expected_kind) {
(@lib.Call(func_idx), DaeDirectCall) =>
dae_func_idx_slot(func_idx) == callee_abs
(@lib.ReturnCall(func_idx), DaeDirectReturnCall) =>
dae_func_idx_slot(func_idx) == callee_abs
_ => false
}
}
///|
/// Prove recovered parameter slices are non-overlapping, source-ordered,
/// end before the call, stay in parameter order, and exclude ambient stack.
fn dae_direct_call_operand_slices_are_ordered(
slices : Array[DaeValueSlice],
call_idx : Int32,
cursor : Int32,
) -> Bool {
if call_idx < 0 || cursor < 0 || cursor > call_idx {
return false
}
if slices.length() == 0 {
return cursor <= call_idx
}
if slices[0].start < cursor {
return false
}
let mut idx = 0
while idx < slices.length() {
let slice = slices[idx]
if slice.start < 0 || slice.end < slice.start || slice.end > call_idx {
return false
}
if idx > 0 {
let prev = slices[idx - 1]
// Non-overlapping and source-ordered; void-effect gaps are allowed.
if slice.start < prev.end {
return false
}
}
idx += 1
}
true
}
///|
/// Authoritative recovery of one ordered operand slice per callee parameter
/// for an owned direct `call` / `return_call`. Fails closed on ambiguous
/// boundaries, call-kind mismatch, or stale epoch tokens.
fn dae_direct_call_operand_slices(
mod_ : @lib.Module,
instrs : Array[@lib.Instruction],
call_idx : Int32,
callee_abs : Int32,
param_types : Array[@lib.ValType],
call_kind : DaeDirectCallKind,
type_indices : Array[@lib.TypeIdx],
epoch : DaeCandidateEpoch,
entry_stack_values~ : Int32 = 0,
allow_stack_carried_void_effects~ : Bool = false,
) -> DaeDirectCallOperandSlices? {
if !dae_direct_call_kind_at(instrs, call_idx, callee_abs, call_kind) {
return None
}
let arg_count = param_types.length()
let evidence = match
dae_callsite_value_slice_evidence(
instrs,
call_idx,
arg_count,
entry_stack_values,
mod_,
type_indices,
allow_stack_carried_void_effects~,
) {
Some(evidence) => evidence
None => return None
}
if !dae_direct_call_operand_slices_are_ordered(
evidence.slices,
call_idx,
evidence.cursor,
) {
return None
}
let slices : Array[DaeValueSlice] = []
let mut param_idx = 0
while param_idx < evidence.slices.length() {
let value_type = if param_idx < param_types.length() {
Some(param_types[param_idx])
} else {
None
}
slices.push(
dae_value_slice_enrich_from_instrs(
evidence.slices[param_idx],
instrs,
value_type,
Some(call_kind),
call_idx,
epoch.def_idx,
callee_abs,
epoch.module_epoch,
epoch.call_epoch,
epoch.type_epoch,
epoch.signature_epoch,
epoch.body_epoch,
),
)
param_idx += 1
}
if !dae_direct_call_operand_slices_are_ordered(
slices,
call_idx,
evidence.cursor,
) {
return None
}
Some({
call_idx,
call_kind,
callee_abs,
slices,
cursor: evidence.cursor,
module_epoch: epoch.module_epoch,
call_epoch: epoch.call_epoch,
type_epoch: epoch.type_epoch,
signature_epoch: epoch.signature_epoch,
body_epoch: epoch.body_epoch,
})
}
///|
#warnings("-unused_value")
fn dae_direct_call_operand_slices_epoch_is_current(
graph : DaeCurrentBoundaryGraph,
slices : DaeDirectCallOperandSlices,
def_idx : Int32,
) -> Bool {
dae_candidate_epoch_is_current(graph, {
def_idx,
module_epoch: slices.module_epoch,
call_epoch: slices.call_epoch,
type_epoch: slices.type_epoch,
signature_epoch: slices.signature_epoch,
body_epoch: slices.body_epoch,
})
}
///|
fn dae_parameter_action_removes(action : DaeParameterAction) -> Bool {
match action {
DaeRemovePureParameter
| DaeRemoveParameterReplayEffects
| DaeLocalizeParameterAndRetry
| DaeMaterializeConstantParameter(_) => true
DaeKeepParameter | DaeRefineParameterType(_) => false
}
}
///|
/// Classify one callee parameter slot from shared boundary/liveness facts and
/// the operand slice at every owned direct callsite. Ordinary unused-parameter
/// analysis emits keep / remove-pure / remove-replay / localize-and-retry.
/// Constant materialization and GC refinement variants are reserved for later
/// workstreams and are not produced here.
fn dae_classify_parameter_slot(
facts : DaeBoundaryFacts,
def_idx : Int32,
param_idx : Int32,
incoming_live : Bool,
callsite_param_slices : Array[DaeValueSlice],
traps_never_happen~ : Bool = false,
) -> DaeParameterAction {
let imported = facts.graph.snapshot.imported
let func_abs = dae_abs_func_idx(imported, def_idx)
if def_idx < 0 || param_idx < 0 || !dae_can_change_parameters(facts, func_abs) {
return DaeKeepParameter
}
match dae_def_idx_of_abs(imported, func_abs) {
Some(resolved) if resolved == def_idx => ()
_ => return DaeKeepParameter
}
if incoming_live {
return DaeKeepParameter
}
if callsite_param_slices.length() == 0 {
// A private, uncalled function has no actual operands whose effects need
// replaying. Binaryen removes an entry-dead parameter in that case, while
// an empty slice set for a function that does have calls remains invalid
// evidence and must fail closed.
match
dae_call_boundary_candidate(
facts.graph.snapshot.boundary_facts,
func_abs,
def_idx~,
) {
Some(candidate) if candidate.owned && candidate.direct_call_count == 0 =>
return DaeRemovePureParameter
_ => return DaeKeepParameter
}
}
let mut all_pure = true
let mut any_localize = false
let mut any_replay = false
for slice in callsite_param_slices {
if slice.call_idx < 0 ||
slice.callee_abs != func_abs ||
slice.producer_kind == DaeProducerUnknown {
return DaeKeepParameter
}
if slice.module_epoch >= 0 &&
(
slice.caller_def < 0 ||
!dae_candidate_epoch_is_current(facts.graph, {
def_idx: slice.caller_def,
module_epoch: slice.module_epoch,
call_epoch: slice.call_epoch,
type_epoch: slice.type_epoch,
signature_epoch: slice.signature_epoch,
body_epoch: slice.body_epoch,
})
) {
return DaeKeepParameter
}
if slice.has_escaping_branch ||
slice.produces_unreachable ||
slice.producer_kind == DaeProducerStructuredControl ||
slice.producer_kind == DaeProducerIndirectCall ||
slice.producer_kind == DaeProducerCallRef ||
slice.producer_kind == DaeProducerDirectCall {
any_localize = true
continue
}
if traps_never_happen &&
slice.may_trap &&
!slice.has_observable_effect &&
!slice.has_escaping_branch &&
!slice.produces_unreachable {
()
} else if slice.disposition.droppable_without_repair &&
!slice.may_trap &&
!slice.has_observable_effect {
()
} else if slice.disposition.has_branch || slice.has_escaping_branch {
any_localize = true
} else {
all_pure = false
any_replay = true
}
}
if any_localize {
return DaeLocalizeParameterAndRetry
}
if all_pure {
return DaeRemovePureParameter
}
if any_replay {
return DaeRemoveParameterReplayEffects
}
DaeKeepParameter
}
///|
fn dae_collect_owned_callsite_param_slices_instrs(
facts : DaeBoundaryFacts,
mod_ : @lib.Module,
instrs : Array[@lib.Instruction],
callee_abs : Int32,
param_types : Array[@lib.ValType],
param_idx : Int32,
epoch : DaeCandidateEpoch,
entry_stack_values : Int32,
out : Array[DaeValueSlice],
allow_stack_carried_void_effects~ : Bool = false,
legacy_tag_entries~ : Array[Int32]? = None,
) -> Bool {
let tag_entries = match legacy_tag_entries {
Some(entries) => entries
None => []
}
let mut call_idx = 0
while call_idx < instrs.length() {
let call_kind = match instrs[call_idx] {
@lib.Call(func_idx) if dae_func_idx_slot(func_idx) == callee_abs =>
Some(DaeDirectCall)
@lib.ReturnCall(func_idx) if dae_func_idx_slot(func_idx) == callee_abs =>
Some(DaeDirectReturnCall)
_ => None
}
match call_kind {
Some(kind) =>
match
dae_direct_call_operand_slices(
mod_,
instrs,
call_idx,
callee_abs,
param_types,
kind,
facts.graph.snapshot.type_indices,
epoch,
entry_stack_values~,
allow_stack_carried_void_effects~,
) {
Some(slices) => {
if param_idx >= slices.slices.length() {
return false
}
out.push(slices.slices[param_idx])
}
None => return false
}
None => ()
}
match instrs[call_idx] {
@lib.Try(block_type, @lib.Expr(body), catches, _) => {
let body_entry = match
dae_control_entry_stack_values(block_type, mod_) {
Some(count) => count
None => return false
}
if !dae_collect_owned_callsite_param_slices_instrs(
facts,
mod_,
body,
callee_abs,
param_types,
param_idx,
epoch,
body_entry,
out,
allow_stack_carried_void_effects~,
legacy_tag_entries=Some(tag_entries),
) {
return false
}
for catch_ in catches {
let catch_entry = match
dae_legacy_catch_entry_stack_values(catch_, mod_, tag_entries) {
Some(count) => count
None => return false
}
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
if !dae_collect_owned_callsite_param_slices_instrs(
facts,
mod_,
catch_body,
callee_abs,
param_types,
param_idx,
epoch,
catch_entry,
out,
allow_stack_carried_void_effects~,
legacy_tag_entries=Some(tag_entries),
) {
return false
}
}
call_idx += 1
continue
}
_ => ()
}
match dae_nested_control_children_evidence(instrs[call_idx], mod_) {
Some(children) => {
if !dae_collect_owned_callsite_param_slices_instrs(
facts,
mod_,
children.primary_body,
callee_abs,
param_types,
param_idx,
epoch,
children.entry_stack_values,
out,
allow_stack_carried_void_effects~,
legacy_tag_entries=Some(tag_entries),
) {
return false
}
match children.secondary_body {
Some(body) =>
if !dae_collect_owned_callsite_param_slices_instrs(
facts,
mod_,
body,
callee_abs,
param_types,
param_idx,
epoch,
children.entry_stack_values,
out,
allow_stack_carried_void_effects~,
legacy_tag_entries=Some(tag_entries),
) {
return false
}
None => ()
}
}
None => ()
}
call_idx += 1
}
true
}
///|
fn dae_collect_owned_callsite_param_slices(
facts : DaeBoundaryFacts,
mod_ : @lib.Module,
callee_abs : Int32,
param_types : Array[@lib.ValType],
direct_caller_defs : Array[Int32],
param_idx : Int32,
allow_stack_carried_void_effects~ : Bool = false,
) -> Array[DaeValueSlice]? {
if param_idx < 0 || param_idx >= param_types.length() {
return None
}
let out : Array[DaeValueSlice] = []
for caller_def in direct_caller_defs {
if caller_def < 0 || caller_def >= facts.graph.snapshot.funcs.length() {
return None
}
let epoch = dae_boundary_candidate_epoch(facts.graph, caller_def)
let @lib.Func(_, @lib.Expr(body)) = facts.graph.snapshot.funcs[caller_def]
if !dae_collect_owned_callsite_param_slices_instrs(
facts,
mod_,
body,
callee_abs,
param_types,
param_idx,
epoch,
0,
out,
allow_stack_carried_void_effects~,
) {
return None
}
}
Some(out)
}
///|
#warnings("-unused_value")
fn dae_classify_unused_parameter_actions(
facts : DaeBoundaryFacts,
mod_ : @lib.Module,
def_idx : Int32,
callee_abs : Int32,
param_types : Array[@lib.ValType],
entry_live : Array[Bool],
direct_caller_defs : Array[Int32],
traps_never_happen~ : Bool = false,
allow_stack_carried_void_effects~ : Bool = false,
) -> Array[DaeParameterAction]? {
if entry_live.length() != param_types.length() {
return None
}
let actions : Array[DaeParameterAction] = []
let mut param_idx = 0
while param_idx < param_types.length() {
let slices = match
dae_collect_owned_callsite_param_slices(
facts,
mod_,
callee_abs,
param_types,
direct_caller_defs,
param_idx,
allow_stack_carried_void_effects~,
) {
Some(slices) => slices
None => return None
}
actions.push(
dae_classify_parameter_slot(
facts,
def_idx,
param_idx,
entry_live[param_idx],
slices,
traps_never_happen~,
),
)
param_idx += 1
}
Some(actions)
}
///|
fn dae_classify_unused_parameter_actions_from_function_infos(
facts : DaeBoundaryFacts,
mod_ : @lib.Module,
def_idx : Int32,
callee_abs : Int32,
param_types : Array[@lib.ValType],
entry_live : Array[Bool],
direct_caller_defs : Array[Int32],
function_infos : Array[DaeFunctionInfo],
traps_never_happen~ : Bool = false,
allow_stack_carried_void_effects~ : Bool = false,
) -> Array[DaeParameterAction]? {
if entry_live.length() != param_types.length() ||
callee_abs < 0 ||
callee_abs >= function_infos.length() {
return None
}
let slices_by_param : Array[Array[DaeValueSlice]] = []
for _ in param_types {
slices_by_param.push([])
}
let tag_entries : Array[Int32] = []
let mut matched_calls = 0
for callsite in function_infos[callee_abs].calls {
if callsite.callee_abs != callee_abs ||
callsite.path.length() == 0 ||
!direct_caller_defs.contains(callsite.caller_def) {
continue
}
if callsite.caller_def < 0 ||
callsite.caller_def >= facts.graph.snapshot.funcs.length() {
return None
}
let @lib.Func(_, @lib.Expr(body)) = facts.graph.snapshot.funcs[callsite.caller_def]
let (scope, call_idx, entry_stack_values) = match
dae_resolve_stable_callsite_scope(
body,
callsite.path.to_array(),
0,
0,
mod_,
legacy_tag_entries=Some(tag_entries),
) {
Some(resolved) => resolved
None => return None
}
let call_kind = match scope[call_idx] {
@lib.Call(func_idx) if dae_func_idx_slot(func_idx) == callee_abs =>
DaeDirectCall
@lib.ReturnCall(func_idx) if dae_func_idx_slot(func_idx) == callee_abs =>
DaeDirectReturnCall
_ => return None
}
let epoch = dae_boundary_candidate_epoch(facts.graph, callsite.caller_def)
let evidence = match
dae_direct_call_operand_slices(
mod_,
scope,
call_idx,
callee_abs,
param_types,
call_kind,
facts.graph.snapshot.type_indices,
epoch,
entry_stack_values~,
allow_stack_carried_void_effects~,
) {
Some(evidence) => evidence
None => return None
}
if evidence.slices.length() != param_types.length() {
return None
}
let mut param_idx = 0
while param_idx < param_types.length() {
slices_by_param[param_idx].push(evidence.slices[param_idx])
param_idx += 1
}
matched_calls += 1
}
if matched_calls == 0 && direct_caller_defs.length() > 0 {
return None
}
let actions : Array[DaeParameterAction] = []
let mut param_idx = 0
while param_idx < param_types.length() {
actions.push(
dae_classify_parameter_slot(
facts,
def_idx,
param_idx,
entry_live[param_idx],
slices_by_param[param_idx],
traps_never_happen~,
),
)
param_idx += 1
}
Some(actions)
}
///|
fn dae_callsite_operand_removal_plan(
evidence : DaeCallsiteValueSliceEvidence,
param_idx : Int32,
remove : Bool,
) -> DaeOperandRemovalPlan? {
if param_idx < 0 || param_idx >= evidence.slices.length() {
return None
}
let disposition = evidence.slices[param_idx].disposition
let rewrite_disposition = dae_operand_rewrite_disposition(
disposition,
localization_available=false,
has_escaping_branch=disposition.has_branch,
)
let plan = dae_operand_removal_plan(rewrite_disposition, !remove)
if dae_operand_removal_plan_is_consistent(rewrite_disposition, plan) {
Some(plan)
} else {
None
}
}
///|
fn dae_callsite_single_instruction_actual_is_consistent(
actual : DaeCallsiteSingleInstructionActualEvidence,
param_idx : Int32,
instrs : Array[@lib.Instruction],
) -> Bool {
actual.param_idx == param_idx &&
actual.actual_idx >= 0 &&
actual.actual_idx < instrs.length() &&
actual.instr == instrs[actual.actual_idx]
}
///|
fn dae_callsite_single_instruction_actual(
evidence : DaeCallsiteValueSliceEvidence,
param_idx : Int32,
instrs : Array[@lib.Instruction],
) -> DaeCallsiteSingleInstructionActualEvidence? {
if param_idx < 0 || param_idx >= evidence.slices.length() {
return None
}
match dae_value_slice_single_instruction_index(evidence.slices[param_idx]) {
Some(actual_idx) if actual_idx >= 0 && actual_idx < instrs.length() => {
let actual = { param_idx, actual_idx, instr: instrs[actual_idx], }
if dae_callsite_single_instruction_actual_is_consistent(
actual, param_idx, instrs,
) {
Some(actual)
} else {
None
}
}
_ => None
}
}
///| Exact fast path for flat calls whose complete operand suffix consists of
///| one zero-input, one-output root per parameter. Any computed, shared-stack,
///|
/// or effect-interleaved operand falls back to full stack slicing.
fn dae_callsite_contiguous_single_root_actual_start(
instrs : Array[@lib.Instruction],
call_idx : Int32,
param_count : Int32,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Int32? {
if param_count < 0 || call_idx < param_count || call_idx > instrs.length() {
return None
}
let start = call_idx - param_count
let mut param_idx = 0
while param_idx < param_count {
let actual_idx = start + param_idx
if dae_stack_single_value_producer_evidence(
instrs[actual_idx],
mod_,
func_type_indices,
)
is None {
return None
}
param_idx += 1
}
Some(start)
}
///|
fn dae_callsite_rewrite_application_evidence_is_consistent(
evidence : DaeCallsiteRewriteApplicationEvidence,
) -> Bool {
evidence.new_call_idx >= 0 &&
evidence.new_call_idx < evidence.instrs.length() &&
evidence.localization_count == dae_count_touched(evidence.localized_params) &&
evidence.scratch_local_types.all(fn(vt) { @lib.has_default(vt) })
}
///|
fn dae_localization_scratch_type(param_type : @lib.ValType) -> @lib.ValType? {
if @lib.has_default(param_type) {
return Some(param_type)
}
match param_type {
@lib.RefTypeValType(ref_type) =>
Some(@lib.ValType::ref_type(ref_type.make_nullable()))
_ => None
}
}
///|
fn dae_localization_scratch_slot(
first_scratch_local : Int32,
scratch_local_types : Array[@lib.ValType],
scratch_type : @lib.ValType,
) -> Int32 {
if first_scratch_local < 0 || !@lib.has_default(scratch_type) {
return -1
}
let mut scratch_offset = 0
while scratch_offset < scratch_local_types.length() {
if scratch_local_types[scratch_offset] == scratch_type {
return first_scratch_local + scratch_offset
}
scratch_offset += 1
}
let scratch_idx = first_scratch_local + scratch_local_types.length()
scratch_local_types.push(scratch_type)
scratch_idx
}
///|
fn dae_removed_param_local_projection_evidence(
param_type : @lib.ValType,
written~ : Bool,
localized~ : Bool,
) -> DaeRemovedParamLocalProjectionEvidence {
let body_local_type = if localized {
dae_localization_scratch_type(param_type)
} else if written {
Some(param_type)
} else {
None
}
match body_local_type {
Some(_) => { valid: true, preserve_as_body_local: true, body_local_type, }
None =>
{
valid: !localized,
preserve_as_body_local: false,
body_local_type: None,
}
}
}
///|
fn dae_removed_param_callee_rewrite_evidence(
func : @lib.Func,
params : Array[@lib.ValType],
remove : Array[Bool],
const_params : Array[@lib.Instruction?],
local_use : DaeLocalUse,
localized_params : Array[Bool],
) -> DaeRemovedParamCalleeRewriteEvidence? {
if remove.length() != params.length() ||
const_params.length() != params.length() ||
local_use.writes.length() < params.length() ||
localized_params.length() != params.length() {
return None
}
let @lib.Func(old_locals, @lib.Expr(old_body)) = func
let kept_params : Array[@lib.ValType] = []
let removed_local_types : Array[@lib.ValType] = []
let mut p = 0
while p < params.length() {
if remove[p] {
let exact_usage = dae_local_usage_evidence(
old_body,
@lib.LocalIdx::new(p.reinterpret_as_uint()),
)
if exact_usage.get_count > 0 &&
!local_use.gets[p] &&
!local_use.writes[p] &&
const_params[p] is None &&
!localized_params[p] {
// Exact body evidence is authoritative when a cached/local analysis
// claims the removed slot is unread. Without a replacement value or a
// typed local projection, remapping would alias the read to another
// local and can change its type.
return None
}
let projection = dae_removed_param_local_projection_evidence(
params[p],
written=local_use.writes[p],
localized=localized_params[p],
)
if !projection.valid ||
(
local_use.gets[p] &&
const_params[p] is None &&
!projection.preserve_as_body_local
) {
// A removed slot that is still read must be replaced by a proved
// constant or preserved as a typed body local. Never let the local map
// alias such a slot onto an unrelated existing local.
return None
}
match projection.body_local_type {
Some(local_type) => removed_local_types.push(local_type)
None => ()
}
} else {
kept_params.push(params[p])
}
p += 1
}
if !dae_local_indices_fit_instrs(
old_body,
params.length() + old_locals.length(),
) {
return None
}
let local_map = dae_build_local_map(
params,
old_locals.length(),
remove,
local_use,
preserve_removed_params=Some(localized_params),
)
let direct_const_params = const_params.copy()
let entry_initializers : Array[@lib.Instruction] = []
p = 0
while p < params.length() {
if remove[p] && local_use.writes[p] {
match const_params[p] {
Some(value) => {
if local_map[p] < 0 {
return None
}
direct_const_params[p] = None
entry_initializers.push(value)
entry_initializers.push(
@lib.Instruction::local_set(
@lib.LocalIdx::new(local_map[p].reinterpret_as_uint()),
),
)
}
None => ()
}
}
p += 1
}
let body = entry_initializers
body.append(
dae_rewrite_locals_instrs(
dae_rewrite_param_const_instrs(old_body, direct_const_params),
local_map,
),
)
let locals = @lib.Locals::from_types(removed_local_types)
locals.append(old_locals)
if !dae_local_indices_fit_instrs(body, kept_params.length() + locals.length()) {
return None
}
Some({ kept_params, locals, body, })
}
///|
fn dae_callsite_rewrite_plan_is_consistent(
plan : DaeCallsiteRewritePlan,
) -> Bool {
let callsite = plan.callsite
if !dae_callsite_value_slice_evidence_is_consistent(
callsite,
callsite.call_idx,
callsite.arg_count,
callsite.entry_stack_values,
) ||
plan.operand_plans.length() != callsite.arg_count ||
plan.scratch_local_types_by_param.length() != callsite.arg_count ||
plan.localized_params.length() != callsite.arg_count {
return false
}
let mut cursor = callsite.cursor
let mut p = 0
while p < callsite.arg_count {
let operand_plan = plan.operand_plans[p]
let slice = callsite.slices[p]
if operand_plan.start != slice.start ||
operand_plan.end != slice.end ||
operand_plan.start != cursor ||
operand_plan.end > callsite.call_idx {
return false
}
cursor = operand_plan.end
match plan.scratch_local_types_by_param[p] {
Some(scratch_type) =>
if !operand_plan.requires_localization_retry ||
operand_plan.rejects_direct_removal ||
!plan.localized_params[p] ||
!@lib.has_default(scratch_type) {
return false
}
None =>
if operand_plan.requires_localization_retry ||
operand_plan.rejects_direct_removal ||
plan.localized_params[p] {
return false
}
}
p += 1
}
cursor == callsite.call_idx
}
///|
fn dae_callsite_rewrite_plan(
callsite : DaeCallsiteValueSliceEvidence,
remove : Array[Bool],
localization_param_types : Array[@lib.ValType]?,
traps_never_happen~ : Bool = false,
) -> DaeCallsiteRewritePlan? {
if remove.length() != callsite.arg_count {
return None
}
match localization_param_types {
Some(param_types) if param_types.length() != callsite.arg_count =>
return None
_ => ()
}
let operand_plans : Array[DaeOperandRemovalPlan] = []
let scratch_local_types_by_param : Array[@lib.ValType?] = []
let localized_params = Array::make(callsite.arg_count, false)
let mut p = 0
while p < callsite.arg_count {
let slice = callsite.slices[p]
let tnh_drops_trap_only_slice = traps_never_happen &&
remove[p] &&
slice.may_trap &&
!slice.has_observable_effect &&
!slice.has_escaping_branch &&
!slice.produces_unreachable
let (operand_plan, scratch_type) = if tnh_drops_trap_only_slice {
(
{
start: slice.start,
end: slice.end,
keep_operand: false,
replay_operand: false,
append_drop: false,
requires_localization_retry: false,
rejects_direct_removal: false,
},
None,
)
} else {
match localization_param_types {
Some(param_types) => {
let disposition = callsite.slices[p].disposition
let rewrite_disposition = dae_operand_rewrite_disposition(
disposition,
localization_available=remove[p],
has_escaping_branch=disposition.has_branch,
)
let operand_plan = dae_operand_removal_plan(
rewrite_disposition,
!remove[p],
)
if !dae_operand_removal_plan_is_consistent(
rewrite_disposition, operand_plan,
) {
return None
}
let scratch_type = if operand_plan.requires_localization_retry {
match dae_localization_scratch_type(param_types[p]) {
Some(scratch_type) => Some(scratch_type)
None => return None
}
} else {
None
}
(operand_plan, scratch_type)
}
None => {
let operand_plan = match
dae_callsite_operand_removal_plan(callsite, p, remove[p]) {
Some(operand_plan) => operand_plan
None => return None
}
(operand_plan, None)
}
}
}
if operand_plan.rejects_direct_removal {
return None
}
localized_params[p] = scratch_type.is_some()
operand_plans.push(operand_plan)
scratch_local_types_by_param.push(scratch_type)
p += 1
}
let plan = {
callsite,
operand_plans,
scratch_local_types_by_param,
localized_params,
}
if dae_callsite_rewrite_plan_is_consistent(plan) {
Some(plan)
} else {
None
}
}
///|
fn dae_apply_callsite_rewrite_plan(
instrs : Array[@lib.Instruction],
plan : DaeCallsiteRewritePlan,
first_scratch_local : Int32,
existing_scratch_local_types~ : Array[@lib.ValType] = [],
) -> DaeCallsiteRewriteApplicationEvidence? {
if first_scratch_local < 0 ||
!dae_callsite_rewrite_plan_is_consistent(plan) ||
plan.callsite.call_idx < 0 ||
plan.callsite.call_idx >= instrs.length() ||
plan.callsite.cursor < 0 ||
plan.callsite.cursor > plan.callsite.call_idx ||
!existing_scratch_local_types.all(fn(vt) { @lib.has_default(vt) }) {
return None
}
let out : Array[@lib.Instruction] = []
let scratch_local_types = existing_scratch_local_types.copy()
let mut localized_params = plan.localized_params
let mut localization_changed = false
let mut idx = 0
while idx < plan.callsite.cursor {
out.push(instrs[idx])
idx += 1
}
let mut p = 0
while p < plan.callsite.arg_count {
let operand_plan = plan.operand_plans[p]
match plan.scratch_local_types_by_param[p] {
Some(scratch_type) => {
// Localize non-leaf expressions only when the complete pure suffix
// proof cannot drop this owned range. Effects and possible traps keep
// their original producer and scratch write.
if !operand_plan.keep_operand &&
dae_find_droppable_pure_stack_expr_start_in_range(
instrs,
None,
operand_plan.start,
operand_plan.end,
None,
) ==
Some(operand_plan.start) {
if !localization_changed {
localized_params = localized_params.copy()
localization_changed = true
}
localized_params[p] = false
p += 1
continue
}
let mut operand_idx = operand_plan.start
while operand_idx < operand_plan.end {
out.push(instrs[operand_idx])
operand_idx += 1
}
let scratch_idx = dae_localization_scratch_slot(
first_scratch_local, scratch_local_types, scratch_type,
)
if scratch_idx < 0 {
return None
}
out.push(
@lib.Instruction::local_set(
@lib.LocalIdx::new(scratch_idx.reinterpret_as_uint()),
),
)
}
None => dae_append_operand_with_removal_plan(out, instrs, operand_plan)
}
p += 1
}
let new_call_idx = out.length()
out.push(instrs[plan.callsite.call_idx])
idx = plan.callsite.call_idx + 1
while idx < instrs.length() {
out.push(instrs[idx])
idx += 1
}
let evidence = {
instrs: out,
new_call_idx,
scratch_local_types,
localized_params,
localization_count: dae_count_touched(localized_params),
}
if dae_callsite_rewrite_application_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
///|
fn dae_rewrite_callsite_with_localization_at(
instrs : Array[@lib.Instruction],
call_idx : Int32,
remove : Array[Bool],
param_types : Array[@lib.ValType],
entry_stack_values : Int32,
first_scratch_local : Int32,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
existing_scratch_local_types~ : Array[@lib.ValType] = [],
allow_stack_carried_void_effects~ : Bool = false,
traps_never_happen~ : Bool = false,
) -> DaeCallsiteRewriteApplicationEvidence? {
if remove.length() != param_types.length() || first_scratch_local < 0 {
return None
}
let callsite = match
dae_callsite_value_slice_evidence(
instrs,
call_idx,
remove.length(),
entry_stack_values,
mod_,
func_type_indices,
allow_stack_carried_void_effects~,
) {
Some(evidence) => evidence
None => return None
}
let plan = match
dae_callsite_rewrite_plan(
callsite,
remove,
Some(param_types),
traps_never_happen~,
) {
Some(plan) => plan
None => return None
}
dae_apply_callsite_rewrite_plan(
instrs,
plan,
first_scratch_local,
existing_scratch_local_types~,
)
}
///|
fn dae_rewrite_callsite_at(
instrs : Array[@lib.Instruction],
call_idx : Int32,
remove : Array[Bool],
entry_stack_values : Int32,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
traps_never_happen~ : Bool = false,
) -> (Array[@lib.Instruction], Int32, Bool) {
let evidence = match
dae_callsite_value_slice_evidence(
instrs,
call_idx,
remove.length(),
entry_stack_values,
mod_,
func_type_indices,
) {
Some(result) => result
None => return ([], -1, false)
}
let plan = match
dae_callsite_rewrite_plan(evidence, remove, None, traps_never_happen~) {
Some(plan) => plan
None => return ([], -1, false)
}
match dae_apply_callsite_rewrite_plan(instrs, plan, 0) {
Some(application) => (application.instrs, application.new_call_idx, true)
None => ([], -1, false)
}
}
///|
fn dae_rewrite_refined_gc_actual_slice(
instrs : Array[@lib.Instruction],
slice : DaeValueSlice,
refined_type : @lib.ValType,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Bool {
let shape = match
dae_gc_actual_value_slice_shape_evidence(
instrs, slice, mod_, func_type_indices,
) {
Some(evidence) => evidence
None => return true
}
match
dae_gc_actual_type_evidence(
instrs,
shape,
mod_,
refined_type,
func_type_indices,
rewrite_type=Some(refined_type),
) {
Some(evidence) => {
match evidence.select {
Some(select) => {
let slices = select.slices
let (rewritten, _) = match
dae_apply_select_value_rewrites_to_body(
instrs,
slices.left.start,
slices.right.start,
slices.select_idx,
select.gc.left_rewrite,
select.gc.right_rewrite,
evidence.terminal_rewrite,
) {
Some(application) => application
None => return false
}
instrs.clear()
instrs.append(rewritten)
}
None =>
match evidence.terminal_rewrite {
Some(instr) => instrs[slice.end - 1] = instr
None => ()
}
}
true
}
None => true
}
}
///|
fn dae_rewrite_refined_gc_actuals_instr(
instr : @lib.Instruction,
callee_abs : Int32,
refine_params : Array[Bool],
next_params : Array[@lib.ValType],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> (@lib.Instruction, Int32, Bool, Bool) {
let children = match dae_nested_control_children_evidence(instr, mod_) {
Some(evidence) => evidence
None =>
match instr {
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.TryTable(_, _, _)
| @lib.If(_, _, _) => return (instr, 0, false, false)
_ => return (instr, 0, true, false)
}
}
let entry_stack_values = children.entry_stack_values
let (rewritten_primary, primary_calls, primary_ok, primary_changed) = dae_rewrite_refined_gc_actuals_instrs(
children.primary_body,
callee_abs,
refine_params,
next_params,
entry_stack_values,
mod_,
func_type_indices,
)
if !primary_ok {
return (instr, primary_calls, false, primary_changed)
}
let mut calls = primary_calls
let mut changed = primary_changed
let rewritten_secondary = match children.secondary_body {
Some(body) => {
let (next_body, secondary_calls, secondary_ok, secondary_changed) = dae_rewrite_refined_gc_actuals_instrs(
body, callee_abs, refine_params, next_params, entry_stack_values, mod_, func_type_indices,
)
calls += secondary_calls
changed = changed || secondary_changed
if !secondary_ok {
return (instr, calls, false, changed)
}
Some(next_body)
}
None => None
}
if !changed {
return (instr, calls, true, false)
}
match
dae_rebuild_nested_control_children(
instr, children, rewritten_primary, rewritten_secondary,
) {
Some(rewritten) => (rewritten, calls, true, true)
None => (instr, calls, false, true)
}
}
///|
fn dae_rewrite_refined_gc_actuals_instrs(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
refine_params : Array[Bool],
next_params : Array[@lib.ValType],
entry_stack_values : Int32,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> (Array[@lib.Instruction], Int32, Bool, Bool) {
let current = instrs.copy()
let mut idx = 0
let mut calls = 0
let mut changed = false
while idx < current.length() {
if dae_instr_is_unreachable_root(current[idx]) {
break
}
match
dae_target_callsite_evidence(
current[idx],
callee_abs,
DaeTargetDirectOrReturnCall,
) {
Some(_) => {
let evidence = match
dae_callsite_value_slice_evidence(
current,
idx,
refine_params.length(),
entry_stack_values,
mod_,
func_type_indices,
) {
Some(result) => result
None => return ([], calls, false, changed)
}
let mut p = 0
while p < refine_params.length() {
if refine_params[p] {
if !dae_rewrite_refined_gc_actual_slice(
current,
evidence.slices[p],
next_params[p],
mod_,
func_type_indices,
) {
return ([], calls, false, changed)
}
}
p += 1
}
calls += 1
changed = true
idx += 1
}
None => {
let (rewritten, nested_calls, ok, nested_changed) = dae_rewrite_refined_gc_actuals_instr(
current[idx],
callee_abs,
refine_params,
next_params,
mod_,
func_type_indices,
)
if !ok {
return ([], calls + nested_calls, false, changed || nested_changed)
}
calls += nested_calls
if nested_changed {
current[idx] = rewritten
changed = true
}
idx += 1
}
}
}
(current, calls, true, changed)
}
///|
// One transient arity table per caller rewrite, filled only when a tagged
// legacy catch needs it. Flatten type arities once; do not rescan all types
// for each handler or construct a whole validation environment.
fn dae_legacy_tag_payload_count(
mod_ : @lib.Module,
tag : @lib.TagIdx,
entries : Array[Int32],
) -> Int32? {
if entries.is_empty() {
let arities : Array[Int32] = []
match mod_.type_sec {
Some(@lib.TypeSec(types)) =>
for rec_type in types {
match rec_type {
@lib.SingleRecType(subtype) =>
arities.push(
match subtype.get_comptype() {
@lib.FuncCompType(params, results) if results.is_empty() =>
params.length()
_ => -1
},
)
@lib.GroupRecType(group) =>
for subtype in group {
arities.push(
match subtype.get_comptype() {
@lib.FuncCompType(params, results) if results.is_empty() =>
params.length()
_ => -1
},
)
}
}
}
None => ()
}
let append_tag = fn(tag_type : @lib.TagType) {
let @lib.TagType(type_idx) = tag_type
let count = match type_idx {
@lib.TypeIdx(raw) => arities.get(raw.reinterpret_as_int()).unwrap_or(-1)
@lib.RecIdx(_) => -1
}
entries.push(count)
}
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
match import_ {
@lib.Import(_, _, @lib.TagExternType(tag_type)) =>
append_tag(tag_type)
_ => ()
}
}
None => ()
}
match mod_.tag_sec {
Some(@lib.TagSec(tags)) =>
for tag_type in tags {
append_tag(tag_type)
}
None => ()
}
}
let @lib.TagIdx(raw) = tag
match entries.get(raw.reinterpret_as_int()) {
Some(count) if count >= 0 => Some(count)
_ => None
}
}
///|
fn dae_legacy_catch_entry_stack_values(
catch_ : @lib.LegacyCatch,
mod_ : @lib.Module,
tag_entries : Array[Int32],
) -> Int32? {
match catch_ {
@lib.LegacyCatch(tag, _) =>
dae_legacy_tag_payload_count(mod_, tag, tag_entries)
@lib.LegacyCatchAll(_) => Some(0)
}
}
///|
fn dae_rewrite_calls_instr(
instr : @lib.Instruction,
callee_abs : Int32,
remove : Array[Bool],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
localization~ : DaeCallsiteLocalizationContext? = None,
allow_stack_carried_void_effects~ : Bool = false,
traps_never_happen~ : Bool = false,
legacy_tag_entries~ : Array[Int32]? = None,
) -> (@lib.Instruction, Int32, Bool) {
let tag_entries = match legacy_tag_entries {
Some(entries) => entries
None => []
}
match instr {
@lib.Try(block_type, @lib.Expr(body), catches, delegate) => {
let entry_values = match
dae_control_entry_stack_values(block_type, mod_) {
Some(count) => count
None => return (instr, 0, false)
}
let primary = dae_rewrite_calls_instrs(
body,
callee_abs,
remove,
entry_values,
mod_,
func_type_indices,
localization~,
allow_stack_carried_void_effects~,
traps_never_happen~,
legacy_tag_entries=Some(tag_entries),
)
if !primary.ok {
return (instr, primary.calls_rewritten, false)
}
let mut calls = primary.calls_rewritten
let next_catches : Array[@lib.LegacyCatch] = []
for catch_ in catches {
let catch_entry = match
dae_legacy_catch_entry_stack_values(catch_, mod_, tag_entries) {
Some(count) => count
None => return (instr, calls, false)
}
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
let rewritten = dae_rewrite_calls_instrs(
catch_body,
callee_abs,
remove,
catch_entry,
mod_,
func_type_indices,
localization~,
allow_stack_carried_void_effects~,
traps_never_happen~,
legacy_tag_entries=Some(tag_entries),
)
calls += rewritten.calls_rewritten
if !rewritten.ok {
return (instr, calls, false)
}
next_catches.push(
match catch_ {
@lib.LegacyCatch(tag, _) =>
@lib.LegacyCatch::tagged(tag, @lib.Expr::new(rewritten.instrs))
@lib.LegacyCatchAll(_) =>
@lib.LegacyCatch::all(@lib.Expr::new(rewritten.instrs))
},
)
}
return (
@lib.Instruction::try_(
block_type,
@lib.Expr::new(primary.instrs),
next_catches,
delegate~,
),
calls,
true,
)
}
_ => ()
}
let children = match dae_nested_control_children_evidence(instr, mod_) {
Some(evidence) => evidence
None =>
match instr {
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.TryTable(_, _, _)
| @lib.If(_, _, _) => return (instr, 0, false)
_ => return (instr, 0, true)
}
}
let entry_stack_values = children.entry_stack_values
let primary_result = dae_rewrite_calls_instrs(
children.primary_body,
callee_abs,
remove,
entry_stack_values,
mod_,
func_type_indices,
localization~,
allow_stack_carried_void_effects~,
traps_never_happen~,
legacy_tag_entries=Some(tag_entries),
)
if !primary_result.ok {
return (instr, primary_result.calls_rewritten, false)
}
let mut calls = primary_result.calls_rewritten
let rewritten_secondary = match children.secondary_body {
Some(body) => {
let secondary_result = dae_rewrite_calls_instrs(
body,
callee_abs,
remove,
entry_stack_values,
mod_,
func_type_indices,
localization~,
allow_stack_carried_void_effects~,
traps_never_happen~,
legacy_tag_entries=Some(tag_entries),
)
calls += secondary_result.calls_rewritten
if !secondary_result.ok {
return (instr, calls, false)
}
Some(secondary_result.instrs)
}
None => None
}
match
dae_rebuild_nested_control_children(
instr,
children,
primary_result.instrs,
rewritten_secondary,
) {
Some(rewritten) => (rewritten, calls, true)
None => (instr, calls, false)
}
}
///|
fn dae_rewrite_calls_instrs(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
remove : Array[Bool],
entry_stack_values : Int32,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
localization~ : DaeCallsiteLocalizationContext? = None,
allow_stack_carried_void_effects~ : Bool = false,
traps_never_happen~ : Bool = false,
legacy_tag_entries~ : Array[Int32]? = None,
) -> DaeRewriteResult {
let tag_entries = match legacy_tag_entries {
Some(entries) => entries
None => []
}
let current = instrs.copy()
let mut idx = 0
let mut calls = 0
while idx < current.length() {
if dae_instr_is_unreachable_root(current[idx]) {
break
}
match
dae_target_callsite_evidence(
current[idx],
callee_abs,
DaeTargetDirectOrReturnCall,
) {
Some(_) => {
let (rewritten, new_call_idx, ok) = match localization {
Some(context) =>
match
dae_rewrite_callsite_with_localization_at(
current,
idx,
remove,
context.param_types,
entry_stack_values,
context.first_scratch_local,
mod_,
func_type_indices,
existing_scratch_local_types=context.scratch_local_types,
allow_stack_carried_void_effects~,
traps_never_happen~,
) {
Some(evidence) => {
context.scratch_local_types.clear()
context.scratch_local_types.append(evidence.scratch_local_types)
let mut p = 0
while p < evidence.localized_params.length() &&
p < context.localized_params.length() {
context.localized_params[p] = context.localized_params[p] ||
evidence.localized_params[p]
p += 1
}
(evidence.instrs, evidence.new_call_idx, true)
}
None => ([], -1, false)
}
None =>
dae_rewrite_callsite_at(
current,
idx,
remove,
entry_stack_values,
mod_,
func_type_indices,
traps_never_happen~,
)
}
if !ok {
return { instrs: [], calls_rewritten: calls, ok: false, }
}
current.clear()
current.append(rewritten)
calls += 1
idx = new_call_idx + 1
}
None => {
let (rewritten, nested_calls, ok) = dae_rewrite_calls_instr(
current[idx],
callee_abs,
remove,
mod_,
func_type_indices,
localization~,
allow_stack_carried_void_effects~,
traps_never_happen~,
legacy_tag_entries=Some(tag_entries),
)
if !ok {
return {
instrs: [],
calls_rewritten: calls + nested_calls,
ok: false,
}
}
calls += nested_calls
current[idx] = rewritten
idx += 1
}
}
}
{ instrs: current, calls_rewritten: calls, ok: true, }
}
///|
fn dae_materialized_const_equal(
lhs : @lib.Instruction,
rhs : @lib.Instruction,
) -> Bool {
match (lhs, rhs) {
(@lib.I32Const(a), @lib.I32Const(b)) => a == b
(@lib.I64Const(a), @lib.I64Const(b)) => a == b
(@lib.F32Const(_), @lib.F32Const(_)) =>
pass_const_instruction_bits_equal(lhs, rhs)
(@lib.F64Const(_), @lib.F64Const(_)) =>
pass_const_instruction_bits_equal(lhs, rhs)
(@lib.V128Const(_), @lib.V128Const(_)) => lhs == rhs
(@lib.RefNull(a), @lib.RefNull(b)) => a == b
(@lib.RefFunc(a), @lib.RefFunc(b)) => a == b
(@lib.StringConst(a), @lib.StringConst(b)) => a == b
(@lib.Block(_, @lib.Expr(a)), @lib.Block(_, @lib.Expr(b))) if dae_instrs_are_materializable_ref_const_expr(
a,
) &&
dae_instrs_are_materializable_ref_const_expr(b) =>
pass_const_instruction_bits_equal(lhs, rhs)
(@lib.GlobalGet(a), @lib.GlobalGet(b)) => a == b
_ => false
}
}
///|
fn dae_record_uniform_const_candidate(
seen : Array[Bool],
invalid : Array[Bool],
candidates : Array[@lib.Instruction?],
param_idx : Int32,
candidate : @lib.Instruction,
) -> Unit {
if param_idx < 0 ||
param_idx >= seen.length() ||
param_idx >= invalid.length() ||
param_idx >= candidates.length() ||
invalid[param_idx] {
return
}
if !seen[param_idx] {
seen[param_idx] = true
candidates[param_idx] = Some(candidate)
return
}
match candidates[param_idx] {
Some(existing) =>
if !dae_materialized_const_equal(existing, candidate) {
invalid[param_idx] = true
}
None => invalid[param_idx] = true
}
}
///|
fn dae_resolve_forwarded_uniform_const_local_get(
mod_ : @lib.Module,
caller_abs : Int32,
local_slot : Int32,
func_type_indices : Array[@lib.TypeIdx],
boundary_facts : DaeCallBoundaryFacts,
visited : Array[Bool],
allow_immutable_global_get~ : Bool = false,
uniform_resolution_cache~ : DaeUniformResolutionCache? = None,
syntax_query~ : DaeUniformSyntaxQuery? = None,
) -> @lib.Instruction? {
if caller_abs < 0 ||
caller_abs >= func_type_indices.length() ||
caller_abs >= visited.length() ||
visited[caller_abs] {
return None
}
if !dae_call_boundary_allows_forwarding(boundary_facts, caller_abs) {
return None
}
let imported = dae_imported_func_count(mod_)
let def_idx = caller_abs - imported
if def_idx < 0 {
return None
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return None
}
if def_idx >= funcs.length() {
return None
}
let func_type = match
dae_resolve_func_type(mod_, func_type_indices, caller_abs) {
Some(ft) => ft
None => return None
}
let @lib.FuncType(params, _) = func_type
if local_slot < 0 || local_slot >= params.length() {
return None
}
let func = funcs[def_idx]
if dae_forwarded_uniform_caller_is_recursive(
boundary_facts, caller_abs, imported,
) {
return None
}
let @lib.Func(locals, _) = func
let local_use = match uniform_resolution_cache {
Some(cache) if def_idx < cache.local_uses.length() => {
if !cache.local_use_ready[def_idx] {
cache.local_uses[def_idx] = Some(
dae_local_use_for_callee(
func,
params.length() + locals.length(),
caller_abs,
params.length(),
),
)
cache.local_use_ready[def_idx] = true
match cache.local_use_scans {
Some(scans) => scans[0] += 1
None => ()
}
}
match cache.local_uses[def_idx] {
Some(value) => value
None => return None
}
}
_ =>
dae_local_use_for_callee(
func,
params.length() + locals.length(),
caller_abs,
params.length(),
)
}
if local_use.writes[local_slot] {
return None
}
match uniform_resolution_cache {
Some(cache) if def_idx < cache.forwarded_values.length() => {
if cache.forwarded_values[def_idx].length() != params.length() {
cache.forwarded_values[def_idx] = Array::make(params.length(), None)
cache.forwarded_ready[def_idx] = Array::make(params.length(), false)
cache.forwarded_resolving[def_idx] = Array::make(params.length(), false)
}
if cache.forwarded_ready[def_idx][local_slot] {
return cache.forwarded_values[def_idx][local_slot]
}
if cache.forwarded_resolving[def_idx][local_slot] {
return None
}
cache.forwarded_resolving[def_idx][local_slot] = true
match cache.forwarded_resolution_scans {
Some(scans) => scans[0] += 1
None => ()
}
}
_ => ()
}
visited[caller_abs] = true
let collect_params = Array::make(params.length(), false)
collect_params[local_slot] = true
let resolved_values = match uniform_resolution_cache {
Some(cache) =>
match cache.function_infos {
Some(function_infos) =>
dae_collect_uniform_const_actuals_from_function_infos(
mod_,
caller_abs,
params.length(),
func_type_indices,
boundary_facts,
function_infos,
allow_immutable_global_get~,
collect_params=Some(collect_params),
stable_callsite_resolutions=cache.stable_callsite_resolutions,
uniform_resolution_cache~,
syntax_query~,
)
None =>
dae_collect_uniform_const_actuals_visited(
mod_,
caller_abs,
params.length(),
func_type_indices,
boundary_facts,
visited,
allow_immutable_global_get~,
collect_params=Some(collect_params),
uniform_resolution_cache~,
syntax_query~,
)
}
None =>
dae_collect_uniform_const_actuals_visited(
mod_,
caller_abs,
params.length(),
func_type_indices,
boundary_facts,
visited,
allow_immutable_global_get~,
collect_params=Some(collect_params),
syntax_query~,
)
}
let resolved = match resolved_values {
Some(values) => values[local_slot]
None => None
}
visited[caller_abs] = false
match uniform_resolution_cache {
Some(cache) if def_idx < cache.forwarded_values.length() &&
cache.forwarded_values[def_idx].length() == params.length() => {
cache.forwarded_values[def_idx][local_slot] = resolved
cache.forwarded_ready[def_idx][local_slot] = true
cache.forwarded_resolving[def_idx][local_slot] = false
}
_ => ()
}
resolved
}
///|
fn dae_resolve_local_set_const_carrier(
instrs : Array[@lib.Instruction],
end : Int32,
local_slot : Int32,
mod_ : @lib.Module,
allow_immutable_global_get : Bool,
) -> @lib.Instruction? {
let mut idx = 0
let mut found : @lib.Instruction? = None
while idx < end {
match instrs[idx] {
@lib.LocalGet(local_idx) if dae_local_idx_slot(local_idx) == local_slot =>
return None
@lib.LocalTee(local_idx) if dae_local_idx_slot(local_idx) == local_slot =>
return None
@lib.LocalSet(local_idx) if dae_local_idx_slot(local_idx) == local_slot => {
if idx == 0 {
return None
}
let candidate = instrs[idx - 1]
let resolved_candidate = match
dae_materializable_control_const(
mod_, candidate, allow_immutable_global_get,
) {
Some(inner) => inner
None => {
let materializable = if allow_immutable_global_get {
dae_instr_is_materializable_const_with_immutable_global(
mod_, candidate,
)
} else {
dae_instr_is_materializable_const(candidate)
}
if !materializable {
return None
}
candidate
}
}
match found {
Some(_) => return None
None => found = Some(resolved_candidate)
}
}
@lib.LocalSet(local_idx) | @lib.LocalTee(local_idx) if dae_local_idx_slot(
local_idx,
) ==
local_slot => return None
_ =>
if dae_instr_writes_local(
instrs[idx],
@lib.LocalIdx::new(local_slot.reinterpret_as_uint()),
) {
// A write in a nested block, loop, arm, or catch invalidates the
// earlier literal just as a write in this instruction list does.
return None
}
}
idx += 1
}
found
}
///|
fn dae_resolve_uniform_const_candidate(
mod_ : @lib.Module,
caller_abs : Int32,
candidate : @lib.Instruction,
func_type_indices : Array[@lib.TypeIdx],
boundary_facts : DaeCallBoundaryFacts,
visited : Array[Bool],
allow_immutable_global_get~ : Bool = false,
uniform_resolution_cache~ : DaeUniformResolutionCache? = None,
syntax_query~ : DaeUniformSyntaxQuery? = None,
) -> @lib.Instruction? {
match
dae_materializable_control_const(
mod_, candidate, allow_immutable_global_get,
) {
Some(materialized) => return Some(materialized)
None => ()
}
if allow_immutable_global_get {
if dae_instr_is_materializable_const_with_immutable_global(mod_, candidate) {
return Some(candidate)
}
} else if dae_instr_is_materializable_const(candidate) {
return Some(candidate)
}
match candidate {
@lib.LocalGet(local_idx) =>
dae_resolve_forwarded_uniform_const_local_get(
mod_,
caller_abs,
dae_local_idx_slot(local_idx),
func_type_indices,
boundary_facts,
visited,
allow_immutable_global_get~,
uniform_resolution_cache~,
syntax_query~,
)
_ => None
}
}
///|
fn dae_materializable_uniform_slice_candidate(
instrs : Array[@lib.Instruction],
slice : DaeValueSlice,
param_type : @lib.ValType,
) -> @lib.Instruction? {
let body : Array[@lib.Instruction] = []
for idx in slice.start.. Bool {
let tag_entries = match legacy_tag_entries {
Some(entries) => entries
None => []
}
let param_count = param_types.length()
let active_params = match collect_params {
Some(active) if active.length() == param_count => active
Some(_) => return false
None => Array::make(param_count, true)
}
if single_call_idx is None && callee_abs >= 0 {
match syntax_query {
Some(query) if dae_uniform_syntax_query_matches(
query, mod_, func_type_indices, boundary_facts,
) &&
dae_uniform_syntax_target_admitted(query, callee_abs) =>
return dae_collect_uniform_const_actuals_indexed(
query, instrs, callee_abs, param_types, entry_stack_values, caller_abs,
mod_, func_type_indices, boundary_facts, visited, seen, invalid, candidates,
active_params, allow_immutable_global_get, uniform_resolution_cache, tag_entries,
)
_ => ()
}
}
let mut idx = match single_call_idx {
Some(index) => {
if index < 0 ||
index >= instrs.length() ||
dae_target_callsite_evidence(
instrs[index],
callee_abs,
DaeTargetDirectOrReturnCall,
)
is None {
return false
}
index
}
None => 0
}
let end = match single_call_idx {
Some(_) => idx + 1
None => instrs.length()
}
while idx < end {
if single_call_idx is None {
match syntax_query {
Some(query) =>
match query.syntax_visits {
Some(visits) => visits[0] += 1
None => ()
}
None => ()
}
}
match
dae_target_callsite_evidence(
instrs[idx],
callee_abs,
DaeTargetDirectOrReturnCall,
) {
Some(_) =>
match
dae_uniform_callsite_operands_at(
mod_, instrs, idx, param_count, entry_stack_values, func_type_indices,
) {
Some(operands) => {
let mut p = 0
while p < param_count {
if active_params[p] && !invalid[p] {
let actual = match operands.direct_actual_start {
Some(start) => {
let actual_idx = start + p
Some({
param_idx: p,
actual_idx,
instr: instrs[actual_idx],
})
}
None =>
match operands.evidence {
Some(evidence) =>
dae_callsite_single_instruction_actual(
evidence, p, instrs,
)
None => None
}
}
match actual {
Some(actual) =>
match
dae_resolve_uniform_const_candidate(
mod_,
caller_abs,
actual.instr,
func_type_indices,
boundary_facts,
visited,
allow_immutable_global_get~,
uniform_resolution_cache~,
syntax_query~,
) {
Some(candidate) =>
dae_record_uniform_const_candidate(
seen, invalid, candidates, p, candidate,
)
None =>
match actual.instr {
@lib.LocalGet(local_idx) =>
match
dae_resolve_local_set_const_carrier(
instrs,
actual.actual_idx,
dae_local_idx_slot(local_idx),
mod_,
allow_immutable_global_get,
) {
Some(candidate) =>
dae_record_uniform_const_candidate(
seen, invalid, candidates, p, candidate,
)
None => invalid[p] = true
}
_ => invalid[p] = true
}
}
None =>
match
(match operands.evidence {
Some(evidence) =>
dae_materializable_uniform_slice_candidate(
instrs,
evidence.slices[p],
param_types[p],
)
None => None
}) {
Some(candidate) =>
dae_record_uniform_const_candidate(
seen, invalid, candidates, p, candidate,
)
None => invalid[p] = true
}
}
}
p += 1
}
}
None => return false
}
None =>
match instrs[idx] {
@lib.Try(block_type, @lib.Expr(body), catches, _) => {
let body_entry = match
dae_control_entry_stack_values(block_type, mod_) {
Some(count) => count
None => return false
}
if !dae_collect_uniform_const_actuals_instrs(
body,
callee_abs,
param_types,
body_entry,
caller_abs,
mod_,
func_type_indices,
boundary_facts,
visited,
seen,
invalid,
candidates,
allow_immutable_global_get~,
collect_params=Some(active_params),
uniform_resolution_cache~,
syntax_query~,
legacy_tag_entries=Some(tag_entries),
) {
return false
}
for catch_ in catches {
let catch_entry = match
dae_legacy_catch_entry_stack_values(catch_, mod_, tag_entries) {
Some(count) => count
None => return false
}
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
if !dae_collect_uniform_const_actuals_instrs(
catch_body,
callee_abs,
param_types,
catch_entry,
caller_abs,
mod_,
func_type_indices,
boundary_facts,
visited,
seen,
invalid,
candidates,
allow_immutable_global_get~,
collect_params=Some(active_params),
uniform_resolution_cache~,
syntax_query~,
legacy_tag_entries=Some(tag_entries),
) {
return false
}
}
}
@lib.Block(_, @lib.Expr(_))
| @lib.Loop(_, @lib.Expr(_))
| @lib.TryTable(_, _, @lib.Expr(_))
| @lib.If(_, _, _) => {
let child_evidence = match
dae_nested_control_children_evidence(instrs[idx], mod_) {
Some(evidence) => evidence
None => return false
}
if !dae_collect_uniform_const_actuals_instrs(
child_evidence.primary_body,
callee_abs,
param_types,
child_evidence.entry_stack_values,
caller_abs,
mod_,
func_type_indices,
boundary_facts,
visited,
seen,
invalid,
candidates,
allow_immutable_global_get~,
collect_params=Some(active_params),
uniform_resolution_cache~,
syntax_query~,
legacy_tag_entries=Some(tag_entries),
) {
return false
}
match child_evidence.secondary_body {
Some(body) =>
if !dae_collect_uniform_const_actuals_instrs(
body,
callee_abs,
param_types,
child_evidence.entry_stack_values,
caller_abs,
mod_,
func_type_indices,
boundary_facts,
visited,
seen,
invalid,
candidates,
allow_immutable_global_get~,
collect_params=Some(active_params),
uniform_resolution_cache~,
syntax_query~,
legacy_tag_entries=Some(tag_entries),
) {
return false
}
None => ()
}
}
_ => ()
}
}
idx += 1
}
true
}
///|
fn dae_collect_uniform_const_actuals_visited(
mod_ : @lib.Module,
callee_abs : Int32,
param_count : Int32,
func_type_indices : Array[@lib.TypeIdx],
boundary_facts : DaeCallBoundaryFacts,
visited : Array[Bool],
allow_immutable_global_get~ : Bool = false,
collect_params~ : Array[Bool]? = None,
uniform_resolution_cache~ : DaeUniformResolutionCache? = None,
syntax_query~ : DaeUniformSyntaxQuery? = None,
) -> Array[@lib.Instruction?]? {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Some(Array::make(param_count, None))
}
let param_types = match
dae_resolve_func_type(mod_, func_type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) if params.length() == param_count => params
_ => return None
}
let imported = dae_imported_func_count(mod_)
let seen = Array::make(param_count, false)
let invalid = Array::make(param_count, false)
let candidates : Array[@lib.Instruction?] = Array::make(param_count, None)
let caller_defs = dae_call_boundary_direct_callers(boundary_facts, callee_abs)
let mut caller_idx = 0
while caller_idx < caller_defs.length() {
let def_idx = caller_defs[caller_idx]
if def_idx >= 0 && def_idx < funcs.length() {
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
if !dae_collect_uniform_const_actuals_instrs(
body,
callee_abs,
param_types,
0,
imported + def_idx,
mod_,
func_type_indices,
boundary_facts,
visited,
seen,
invalid,
candidates,
allow_immutable_global_get~,
collect_params~,
uniform_resolution_cache~,
syntax_query~,
) {
return None
}
}
caller_idx += 1
}
let out : Array[@lib.Instruction?] = []
let mut p = 0
while p < param_count {
if invalid[p] || !seen[p] {
out.push(None)
} else {
out.push(candidates[p])
}
p += 1
}
Some(out)
}
///|
fn dae_collect_uniform_const_actuals_from_boundary_facts(
mod_ : @lib.Module,
callee_abs : Int32,
param_count : Int32,
func_type_indices : Array[@lib.TypeIdx],
boundary_facts : DaeCallBoundaryFacts,
allow_immutable_global_get~ : Bool = false,
collect_params~ : Array[Bool]? = None,
uniform_resolution_cache~ : DaeUniformResolutionCache? = None,
syntax_query~ : DaeUniformSyntaxQuery? = None,
syntax_stats~ : Array[Int32]? = None,
) -> Array[@lib.Instruction?]? {
// Only an explicit fixed-source owner can share a syntax index. A normal
// query does not create one before a stable-path or single-query result.
let _ = syntax_stats
let total = func_type_indices.length()
dae_collect_uniform_const_actuals_visited(
mod_,
callee_abs,
param_count,
func_type_indices,
boundary_facts,
Array::make(total, false),
allow_immutable_global_get~,
collect_params~,
uniform_resolution_cache~,
syntax_query~,
)
}
///|
fn dae_resolve_stable_callsite_scope(
instrs : Array[@lib.Instruction],
path : Array[Int32],
path_idx : Int32,
entry_stack_values : Int32,
mod_ : @lib.Module,
legacy_tag_entries~ : Array[Int32]? = None,
) -> (Array[@lib.Instruction], Int32, Int32)? {
if path_idx < 0 || path_idx >= path.length() {
return None
}
let instr_idx = path[path_idx]
if instr_idx < 0 || instr_idx >= instrs.length() {
return None
}
if path_idx + 1 == path.length() {
return Some((instrs, instr_idx, entry_stack_values))
}
if path_idx + 2 >= path.length() {
return None
}
let child_kind = path[path_idx + 1]
let tag_entries = match legacy_tag_entries {
Some(entries) => entries
None => []
}
match instrs[instr_idx] {
@lib.Try(block_type, @lib.Expr(body), catches, _) => {
let (child_body, child_entry) = if child_kind == 4 {
let count = match dae_control_entry_stack_values(block_type, mod_) {
Some(count) => count
None => return None
}
(body, count)
} else if child_kind >= 5 {
let catch_ = match catches.get(child_kind - 5) {
Some(catch_) => catch_
None => return None
}
let count = match
dae_legacy_catch_entry_stack_values(catch_, mod_, tag_entries) {
Some(count) => count
None => return None
}
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
(catch_body, count)
} else {
return None
}
return dae_resolve_stable_callsite_scope(
child_body,
path,
path_idx + 2,
child_entry,
mod_,
legacy_tag_entries=Some(tag_entries),
)
}
_ => ()
}
let child_evidence = match
dae_nested_control_children_evidence(instrs[instr_idx], mod_) {
Some(evidence) => evidence
None => return None
}
let child_body = match (instrs[instr_idx], child_kind) {
(@lib.Block(_, _), 0)
| (@lib.Loop(_, _), 1)
| (@lib.TryTable(_, _, _), 4) => child_evidence.primary_body
(@lib.If(_, _, _), 2) => child_evidence.primary_body
(@lib.If(_, _, _), 3) =>
match child_evidence.secondary_body {
Some(body) => body
None => return None
}
_ => return None
}
dae_resolve_stable_callsite_scope(
child_body,
path,
path_idx + 2,
child_evidence.entry_stack_values,
mod_,
legacy_tag_entries=Some(tag_entries),
)
}
///|
priv struct DaeUniformCallsiteOperands {
param_count : Int32
scope : Array[@lib.Instruction]
direct_actual_start : Int32?
evidence : DaeCallsiteValueSliceEvidence?
}
///|
fn dae_resolve_uniform_callsite_operands(
mod_ : @lib.Module,
body : Array[@lib.Instruction],
callsite : DaeStableCallsite,
callee_abs : Int32,
param_count : Int32,
func_type_indices : Array[@lib.TypeIdx],
cache : @hashmap.HashMap[(Int32, Int32), DaeUniformCallsiteOperands]?,
callsite_index : Int32,
resolutions : Array[Int32]?,
legacy_tag_entries~ : Array[Int32]? = None,
) -> DaeUniformCallsiteOperands? {
match cache {
Some(entries) =>
if entries.get((callee_abs, callsite_index)) is Some(value) &&
value.param_count == param_count {
return Some(value)
}
None => ()
}
match resolutions {
Some(count) => count[0] += 1
None => ()
}
let (scope, call_idx, entry_stack_values) = match
dae_resolve_stable_callsite_scope(
body,
callsite.path.to_array(),
0,
0,
mod_,
legacy_tag_entries~,
) {
Some(resolved) => resolved
None => return None
}
if dae_target_callsite_evidence(
scope[call_idx],
callee_abs,
DaeTargetDirectOrReturnCall,
)
is None {
return None
}
let value = match
dae_uniform_callsite_operands_at(
mod_, scope, call_idx, param_count, entry_stack_values, func_type_indices,
) {
Some(value) => value
None => return None
}
match resolutions {
Some(count) if count.length() >= 3 =>
match value.direct_actual_start {
Some(_) => count[1] += 1
None => count[2] += 1
}
_ => ()
}
match cache {
Some(entries) => entries.set((callee_abs, callsite_index), value)
None => ()
}
Some(value)
}
///|
fn dae_collect_uniform_const_actuals_from_function_infos(
mod_ : @lib.Module,
callee_abs : Int32,
param_count : Int32,
func_type_indices : Array[@lib.TypeIdx],
boundary_facts : DaeCallBoundaryFacts,
function_infos : Array[DaeFunctionInfo],
allow_immutable_global_get~ : Bool = false,
collect_params~ : Array[Bool]? = None,
stable_callsite_resolutions~ : Array[Int32]? = None,
uniform_resolution_cache~ : DaeUniformResolutionCache? = None,
syntax_query~ : DaeUniformSyntaxQuery? = None,
syntax_stats~ : Array[Int32]? = None,
) -> Array[@lib.Instruction?]? {
// Only an explicit fixed-source owner can share a syntax index. A normal
// query does not create one before a stable-path or single-query result.
let _ = syntax_stats
if callee_abs < 0 || callee_abs >= function_infos.length() {
return None
}
let active_params = match collect_params {
Some(active) if active.length() == param_count => active
Some(_) => return None
None => Array::make(param_count, true)
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Some(Array::make(param_count, None))
}
let imported = dae_imported_func_count(mod_)
let seen = Array::make(param_count, false)
let invalid = Array::make(param_count, false)
let candidates : Array[@lib.Instruction?] = Array::make(param_count, None)
let mut unresolved_count = 0
for active in active_params {
if active {
unresolved_count += 1
}
}
let visited = match uniform_resolution_cache {
Some(cache) if cache.visited.length() == func_type_indices.length() =>
cache.visited
_ => Array::make(func_type_indices.length(), false)
}
// Only narrowed, multi-parameter queries repeat operand recovery. Keep the
// common full-parameter scan allocation-free with respect to this cache.
let operand_cache = match (uniform_resolution_cache, collect_params) {
(Some(cache), Some(_)) if param_count > 1 => {
let entries = match cache.callsite_operands {
Some(entries) => entries
None => {
let entries = @hashmap.new()
cache.callsite_operands = Some(entries)
entries
}
}
Some(entries)
}
_ => None
}
// A tail call supplies the same parameter boundary as an ordinary call.
// Its arguments must join the constant proof, including recursive updates.
let tag_entries : Array[Int32] = []
let callsites = function_infos[callee_abs].calls
for callsite_index = 0
callsite_index < callsites.length()
callsite_index = callsite_index + 1 {
let callsite = callsites[callsite_index]
if !callsite.active || callsite.callee_abs != callee_abs {
continue
}
let caller_def = callsite.caller_def
if caller_def < 0 || caller_def >= funcs.length() {
return None
}
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
let operands = match
dae_resolve_uniform_callsite_operands(
mod_,
body,
callsite,
callee_abs,
param_count,
func_type_indices,
operand_cache,
callsite_index,
stable_callsite_resolutions,
legacy_tag_entries=Some(tag_entries),
) {
Some(operands) => operands
None => return None
}
let scope = operands.scope
let direct_actual_start = operands.direct_actual_start
let evidence = operands.evidence
let mut p = 0
while p < param_count {
if active_params[p] && !invalid[p] {
let actual = match direct_actual_start {
Some(start) => {
let actual_idx = start + p
Some({ param_idx: p, actual_idx, instr: scope[actual_idx], })
}
None =>
match evidence {
Some(value) =>
dae_callsite_single_instruction_actual(value, p, scope)
None => None
}
}
match actual {
Some(value) => {
match stable_callsite_resolutions {
Some(resolutions) if resolutions.length() >= 5 =>
match value.instr {
@lib.LocalGet(_) => resolutions[4] += 1
_ => resolutions[3] += 1
}
_ => ()
}
match
dae_resolve_uniform_const_candidate(
mod_,
imported + caller_def,
value.instr,
func_type_indices,
boundary_facts,
visited,
allow_immutable_global_get~,
uniform_resolution_cache~,
syntax_query~,
) {
Some(candidate) =>
dae_record_uniform_const_candidate(
seen, invalid, candidates, p, candidate,
)
None =>
match value.instr {
@lib.LocalGet(local_idx) =>
match
dae_resolve_local_set_const_carrier(
scope,
value.actual_idx,
dae_local_idx_slot(local_idx),
mod_,
allow_immutable_global_get,
) {
Some(candidate) =>
dae_record_uniform_const_candidate(
seen, invalid, candidates, p, candidate,
)
None => invalid[p] = true
}
_ => invalid[p] = true
}
}
}
None => {
let candidate = match evidence {
Some(value) =>
match
dae_resolve_func_type(mod_, func_type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) if p < params.length() =>
dae_materializable_uniform_slice_candidate(
scope,
value.slices[p],
params[p],
)
_ => None
}
None => None
}
match candidate {
Some(candidate) =>
dae_record_uniform_const_candidate(
seen, invalid, candidates, p, candidate,
)
None => invalid[p] = true
}
}
}
if invalid[p] {
unresolved_count -= 1
}
}
p += 1
}
if unresolved_count == 0 {
break
}
}
let out : Array[@lib.Instruction?] = []
let mut p = 0
while p < param_count {
if invalid[p] || !seen[p] {
out.push(None)
} else {
out.push(candidates[p])
}
p += 1
}
Some(out)
}
///|
fn dae_collect_uniform_const_actuals_from_current_call_facts(
mod_ : @lib.Module,
callee_abs : Int32,
param_count : Int32,
func_type_indices : Array[@lib.TypeIdx],
boundary_facts : DaeCallBoundaryFacts,
function_infos : Array[DaeFunctionInfo],
allow_immutable_global_get~ : Bool = false,
collect_params~ : Array[Bool]? = None,
prefer_stable_paths~ : Bool = true,
stable_callsite_resolutions~ : Array[Int32]? = None,
uniform_resolution_cache~ : DaeUniformResolutionCache? = None,
) -> Array[@lib.Instruction?]? {
if prefer_stable_paths {
let result = dae_collect_uniform_const_actuals_from_function_infos(
mod_,
callee_abs,
param_count,
func_type_indices,
boundary_facts,
function_infos,
allow_immutable_global_get~,
collect_params~,
stable_callsite_resolutions~,
uniform_resolution_cache~,
)
if result is Some(_) {
return result
}
}
let mut has_inactive_direct_call = false
if callee_abs >= 0 && callee_abs < function_infos.length() {
for callsite in function_infos[callee_abs].calls {
if !callsite.active && !callsite.tail {
has_inactive_direct_call = true
break
}
}
}
// Lightweight batch adoption may retain shifted paths. If exact resolution
// fails, the original body scan remains authoritative for active-only calls.
// Inactive suffix calls must still use the scope-aware proof.
if has_inactive_direct_call {
if prefer_stable_paths {
return None
}
dae_collect_uniform_const_actuals_from_function_infos(
mod_,
callee_abs,
param_count,
func_type_indices,
boundary_facts,
function_infos,
allow_immutable_global_get~,
collect_params~,
stable_callsite_resolutions~,
uniform_resolution_cache~,
)
} else {
dae_collect_uniform_const_actuals_from_boundary_facts(
mod_,
callee_abs,
param_count,
func_type_indices,
boundary_facts,
allow_immutable_global_get~,
collect_params~,
uniform_resolution_cache~,
)
}
}
///|
fn dae_type_idx_slot(idx : @lib.TypeIdx) -> Int32? {
match idx {
@lib.TypeIdx(raw) => Some(raw.reinterpret_as_int())
@lib.RecIdx(_) => None
}
}
///|
fn dae_simple_func_type_section(rec_types : Array[@lib.RecType]) -> Bool {
for rec_type in rec_types {
match rec_type {
@lib.SingleRecType(subtype) =>
if subtype.super_types().length() > 0 ||
subtype.describes_type() is Some(_) ||
subtype.descriptor_type() is Some(_) {
return false
}
@lib.GroupRecType(_) => return false
}
}
true
}
///|
fn dae_mark_used_type_idx(idx : @lib.TypeIdx, used : Array[Bool]) -> Unit {
match dae_type_idx_slot(idx) {
Some(slot) => if slot >= 0 && slot < used.length() { used[slot] = true }
None => ()
}
}
///|
fn dae_mark_used_heap_type(ht : @lib.HeapType, used : Array[Bool]) -> Unit {
match ht {
@lib.HeapType(type_idx) => dae_mark_used_type_idx(type_idx, used)
@lib.DefTypeHeapType(_) => {
let mut idx = 0
while idx < used.length() {
used[idx] = true
idx += 1
}
}
_ => ()
}
}
///|
fn dae_mark_used_ref_type(rt : @lib.RefType, used : Array[Bool]) -> Unit {
match rt {
@lib.HeapTypeRefType(_, _, ht) => dae_mark_used_heap_type(ht, used)
_ => ()
}
}
///|
fn dae_mark_used_val_type(vt : @lib.ValType, used : Array[Bool]) -> Unit {
match vt {
@lib.RefTypeValType(rt) => dae_mark_used_ref_type(rt, used)
_ => ()
}
}
///|
fn dae_mark_used_table_type(tt : @lib.TableType, used : Array[Bool]) -> Unit {
let @lib.TableType(rt, _) = tt
dae_mark_used_ref_type(rt, used)
}
///|
fn dae_mark_used_global_type(gt : @lib.GlobalType, used : Array[Bool]) -> Unit {
let @lib.GlobalType(vt, _) = gt
dae_mark_used_val_type(vt, used)
}
///|
fn dae_mark_used_block_type(bt : @lib.BlockType, used : Array[Bool]) -> Unit {
match bt {
@lib.TypeIdxBlockType(type_idx) => dae_mark_used_type_idx(type_idx, used)
@lib.ValTypeBlockType(val_type) => dae_mark_used_val_type(val_type, used)
_ => ()
}
}
///|
fn dae_collect_used_type_idx_instrs(
instrs : Array[@lib.Instruction],
used : Array[Bool],
) -> Unit {
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body))
| @lib.Loop(block_type, @lib.Expr(body))
| @lib.TryTable(block_type, _, @lib.Expr(body)) => {
dae_mark_used_block_type(block_type, used)
dae_collect_used_type_idx_instrs(body, used)
}
@lib.Try(block_type, @lib.Expr(body), catches, _) => {
dae_mark_used_block_type(block_type, used)
dae_collect_used_type_idx_instrs(body, used)
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, catch_body) | @lib.LegacyCatchAll(catch_body) =>
catch_body
}
dae_collect_used_type_idx_instrs(catch_body, used)
}
}
@lib.If(block_type, then_body, else_body) => {
dae_mark_used_block_type(block_type, used)
dae_collect_used_type_idx_instrs(then_body, used)
match else_body {
Some(body) => dae_collect_used_type_idx_instrs(body, used)
None => ()
}
}
@lib.CallIndirect(type_idx, _)
| @lib.ReturnCallIndirect(type_idx, _)
| @lib.CallRef(type_idx)
| @lib.ReturnCallRef(type_idx)
| @lib.ContNew(type_idx)
| @lib.Resume(type_idx, _)
| @lib.ResumeThrow(type_idx, _, _)
| @lib.ResumeThrowRef(type_idx, _)
| @lib.StackSwitch(type_idx, _)
| @lib.StructNew(type_idx)
| @lib.StructNewDefault(type_idx)
| @lib.StructNewDesc(type_idx)
| @lib.StructNewDefaultDesc(type_idx)
| @lib.ArrayNew(type_idx)
| @lib.ArrayNewDefault(type_idx)
| @lib.ArrayNewFixed(type_idx, _)
| @lib.ArrayNewData(type_idx, _)
| @lib.ArrayNewElem(type_idx, _)
| @lib.ArrayGet(type_idx)
| @lib.ArrayGetS(type_idx)
| @lib.ArrayGetU(type_idx)
| @lib.ArraySet(type_idx)
| @lib.ArrayFill(type_idx)
| @lib.ArrayInitData(type_idx, _)
| @lib.ArrayInitElem(type_idx, _)
| @lib.RefGetDesc(type_idx)
| @lib.StructGet(type_idx, _)
| @lib.StructGetS(type_idx, _)
| @lib.StructGetU(type_idx, _)
| @lib.StructWait(type_idx, _)
| @lib.StructAtomicSet(_, type_idx, _)
| @lib.ArrayAtomicSet(_, type_idx)
| @lib.StructAtomicGet(_, type_idx, _)
| @lib.StructAtomicGetS(_, type_idx, _)
| @lib.StructAtomicGetU(_, type_idx, _)
| @lib.StructAtomicRmw(_, _, type_idx, _)
| @lib.StructAtomicCmpxchg(_, type_idx, _)
| @lib.StructSet(type_idx, _)
| @lib.ArrayAtomicGet(_, type_idx)
| @lib.ArrayAtomicGetS(_, type_idx)
| @lib.ArrayAtomicGetU(_, type_idx)
| @lib.ArrayAtomicRmw(_, _, type_idx)
| @lib.ArrayAtomicCmpxchg(_, type_idx)
| @lib.ArrayLoad(_, type_idx, _)
| @lib.ArrayStore(_, type_idx, _) =>
dae_mark_used_type_idx(type_idx, used)
@lib.ContBind(source_type, target_type)
| @lib.ArrayCopy(source_type, target_type) => {
dae_mark_used_type_idx(source_type, used)
dae_mark_used_type_idx(target_type, used)
}
@lib.Select(Some(types)) =>
for val_type in types {
dae_mark_used_val_type(val_type, used)
}
@lib.RefNull(rt) => dae_mark_used_ref_type(rt, used)
@lib.RefTest(_, _, ht)
| @lib.RefCast(_, _, ht)
| @lib.RefTestDesc(_, ht)
| @lib.RefCastDescEq(_, _, ht) => dae_mark_used_heap_type(ht, used)
@lib.BrOnCast(_, _, source_ht, target_ht)
| @lib.BrOnCastFail(_, _, source_ht, target_ht) => {
dae_mark_used_heap_type(source_ht, used)
dae_mark_used_heap_type(target_ht, used)
}
@lib.BrOnCastDescEq(_, source_type, target_type)
| @lib.BrOnCastDescEqFail(_, source_type, target_type) => {
dae_mark_used_ref_type(source_type, used)
dae_mark_used_ref_type(target_type, used)
}
_ => ()
}
}
}
///|
fn dae_rewrite_pruned_heap_type(
ht : @lib.HeapType,
remap : Array[Int32],
) -> @lib.HeapType {
match ht {
@lib.HeapType(type_idx) =>
@lib.HeapType::new(dae_rewrite_pruned_type_idx(type_idx, remap))
_ => ht
}
}
///|
fn dae_rewrite_pruned_ref_type(
rt : @lib.RefType,
remap : Array[Int32],
) -> @lib.RefType {
match rt {
@lib.HeapTypeRefType(nullable, exact, heap_type) =>
@lib.RefType::new(
nullable,
dae_rewrite_pruned_heap_type(heap_type, remap),
exact~,
)
_ => rt
}
}
///|
fn dae_rewrite_pruned_val_type(
vt : @lib.ValType,
remap : Array[Int32],
) -> @lib.ValType {
match vt {
@lib.RefTypeValType(rt) =>
@lib.ValType::ref_type(dae_rewrite_pruned_ref_type(rt, remap))
_ => vt
}
}
///|
fn dae_rewrite_pruned_table_type(
tt : @lib.TableType,
remap : Array[Int32],
) -> @lib.TableType {
let @lib.TableType(rt, limits) = tt
@lib.TableType::new(dae_rewrite_pruned_ref_type(rt, remap), limits)
}
///|
fn dae_rewrite_pruned_global_type(
gt : @lib.GlobalType,
remap : Array[Int32],
) -> @lib.GlobalType {
let @lib.GlobalType(vt, mutable) = gt
@lib.GlobalType::new(dae_rewrite_pruned_val_type(vt, remap), mutable)
}
///|
fn dae_rewrite_pruned_type_idx(
idx : @lib.TypeIdx,
remap : Array[Int32],
) -> @lib.TypeIdx {
match idx {
@lib.TypeIdx(raw) => {
let slot = raw.reinterpret_as_int()
if slot >= 0 && slot < remap.length() && remap[slot] >= 0 {
@lib.TypeIdx::new(remap[slot].reinterpret_as_uint())
} else {
idx
}
}
@lib.RecIdx(_) => idx
}
}
///|
fn dae_pruned_type_idx_unchanged(
idx : @lib.TypeIdx,
remap : Array[Int32],
) -> Bool {
match idx {
@lib.TypeIdx(raw) => {
let slot = raw.reinterpret_as_int()
slot < 0 ||
slot >= remap.length() ||
remap[slot] < 0 ||
remap[slot] == slot
}
@lib.RecIdx(_) => true
}
}
///|
fn dae_pruned_heap_type_unchanged(
ht : @lib.HeapType,
remap : Array[Int32],
) -> Bool {
match ht {
@lib.HeapType(idx) => dae_pruned_type_idx_unchanged(idx, remap)
_ => true
}
}
///|
fn dae_pruned_ref_type_unchanged(
rt : @lib.RefType,
remap : Array[Int32],
) -> Bool {
match rt {
@lib.HeapTypeRefType(_, _, ht) => dae_pruned_heap_type_unchanged(ht, remap)
_ => true
}
}
///|
fn dae_pruned_val_type_unchanged(
vt : @lib.ValType,
remap : Array[Int32],
) -> Bool {
match vt {
@lib.RefTypeValType(rt) => dae_pruned_ref_type_unchanged(rt, remap)
_ => true
}
}
///|
fn dae_pruned_block_type_unchanged(
bt : @lib.BlockType,
remap : Array[Int32],
) -> Bool {
match bt {
@lib.TypeIdxBlockType(idx) => dae_pruned_type_idx_unchanged(idx, remap)
@lib.ValTypeBlockType(vt) => dae_pruned_val_type_unchanged(vt, remap)
_ => true
}
}
///|
fn dae_rewrite_pruned_type_idx_instr(
instr : @lib.Instruction,
remap : Array[Int32],
) -> @lib.Instruction {
let rewrite_block_type = fn(block_type : @lib.BlockType) -> @lib.BlockType {
match block_type {
@lib.TypeIdxBlockType(type_idx) =>
@lib.BlockType::type_idx(dae_rewrite_pruned_type_idx(type_idx, remap))
@lib.ValTypeBlockType(val_type) =>
@lib.BlockType::val_type(dae_rewrite_pruned_val_type(val_type, remap))
_ => block_type
}
}
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let next_body = dae_rewrite_pruned_type_idx_instrs(body, remap)
if physical_equal(next_body, body) &&
dae_pruned_block_type_unchanged(block_type, remap) {
return instr
}
return @lib.Instruction::block(
rewrite_block_type(block_type),
@lib.Expr::new(next_body),
)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let next_body = dae_rewrite_pruned_type_idx_instrs(body, remap)
if physical_equal(next_body, body) &&
dae_pruned_block_type_unchanged(block_type, remap) {
return instr
}
return @lib.Instruction::loop_(
rewrite_block_type(block_type),
@lib.Expr::new(next_body),
)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let next_body = dae_rewrite_pruned_type_idx_instrs(body, remap)
if physical_equal(next_body, body) &&
dae_pruned_block_type_unchanged(block_type, remap) {
return instr
}
return @lib.Instruction::try_table(
rewrite_block_type(block_type),
catches,
@lib.Expr::new(next_body),
)
}
@lib.Try(block_type, @lib.Expr(body), catches, delegate) => {
let next_body = dae_rewrite_pruned_type_idx_instrs(body, remap)
let mut rewritten_catches : Array[@lib.LegacyCatch]? = None
for index = 0; index < catches.length(); index = index + 1 {
let catch_ = catches[index]
let catch_body = match catch_ {
@lib.LegacyCatch(_, @lib.Expr(catch_body))
| @lib.LegacyCatchAll(@lib.Expr(catch_body)) => catch_body
}
let next_catch = dae_rewrite_pruned_type_idx_instrs(catch_body, remap)
if !physical_equal(next_catch, catch_body) {
let rewritten = match catch_ {
@lib.LegacyCatch(tag_idx, _) =>
@lib.LegacyCatch::tagged(tag_idx, @lib.Expr::new(next_catch))
@lib.LegacyCatchAll(_) =>
@lib.LegacyCatch::all(@lib.Expr::new(next_catch))
}
match rewritten_catches {
Some(copy) => copy[index] = rewritten
None => {
let copy = catches.copy()
copy[index] = rewritten
rewritten_catches = Some(copy)
}
}
}
}
if physical_equal(next_body, body) &&
rewritten_catches is None &&
dae_pruned_block_type_unchanged(block_type, remap) {
return instr
}
return @lib.Instruction::try_(
rewrite_block_type(block_type),
@lib.Expr::new(next_body),
rewritten_catches.unwrap_or(catches),
delegate~,
)
}
@lib.If(block_type, then_body, else_body) => {
let next_then = dae_rewrite_pruned_type_idx_instrs(then_body, remap)
let mut else_changed = false
let next_else = match else_body {
Some(body) => {
let next = dae_rewrite_pruned_type_idx_instrs(body, remap)
if physical_equal(next, body) {
else_body
} else {
else_changed = true
Some(next)
}
}
None => None
}
if physical_equal(next_then, then_body) &&
!else_changed &&
dae_pruned_block_type_unchanged(block_type, remap) {
return instr
}
return @lib.Instruction::if_(
rewrite_block_type(block_type),
next_then,
next_else,
)
}
_ => ()
}
// These arms inspect only the fields that this remapper rewrites. Embedded
// definitions and every unrelated opcode keep their existing ownership.
let unchanged = match instr {
@lib.CallIndirect(idx, _)
| @lib.ReturnCallIndirect(idx, _)
| @lib.CallRef(idx)
| @lib.ReturnCallRef(idx)
| @lib.ContNew(idx)
| @lib.Resume(idx, _)
| @lib.ResumeThrow(idx, _, _)
| @lib.ResumeThrowRef(idx, _)
| @lib.StackSwitch(idx, _)
| @lib.RefGetDesc(idx) => dae_pruned_type_idx_unchanged(idx, remap)
@lib.ContBind(source_type, target_type) =>
dae_pruned_type_idx_unchanged(source_type, remap) &&
dae_pruned_type_idx_unchanged(target_type, remap)
@lib.Select(Some(types)) =>
types.all(fn(vt) { dae_pruned_val_type_unchanged(vt, remap) })
@lib.RefNull(rt) => dae_pruned_ref_type_unchanged(rt, remap)
@lib.RefTest(_, _, ht)
| @lib.RefCast(_, _, ht)
| @lib.RefTestDesc(_, ht)
| @lib.RefCastDescEq(_, _, ht) => dae_pruned_heap_type_unchanged(ht, remap)
@lib.BrOnCast(_, _, source_type, target_type)
| @lib.BrOnCastFail(_, _, source_type, target_type) =>
dae_pruned_heap_type_unchanged(source_type, remap) &&
dae_pruned_heap_type_unchanged(target_type, remap)
@lib.BrOnCastDescEq(_, source_type, target_type)
| @lib.BrOnCastDescEqFail(_, source_type, target_type) =>
dae_pruned_ref_type_unchanged(source_type, remap) &&
dae_pruned_ref_type_unchanged(target_type, remap)
_ => true
}
if unchanged {
return instr
}
match instr {
@lib.CallIndirect(type_idx, table_idx) =>
@lib.Instruction::call_indirect(
dae_rewrite_pruned_type_idx(type_idx, remap),
table_idx,
)
@lib.ReturnCallIndirect(type_idx, table_idx) =>
@lib.Instruction::return_call_indirect(
dae_rewrite_pruned_type_idx(type_idx, remap),
table_idx,
)
@lib.CallRef(type_idx) =>
@lib.Instruction::call_ref(dae_rewrite_pruned_type_idx(type_idx, remap))
@lib.ReturnCallRef(type_idx) =>
@lib.Instruction::return_call_ref(
dae_rewrite_pruned_type_idx(type_idx, remap),
)
@lib.ContNew(type_idx) =>
@lib.Instruction::cont_new(dae_rewrite_pruned_type_idx(type_idx, remap))
@lib.ContBind(source_type, target_type) =>
@lib.Instruction::cont_bind(
dae_rewrite_pruned_type_idx(source_type, remap),
dae_rewrite_pruned_type_idx(target_type, remap),
)
@lib.Resume(type_idx, handlers) =>
@lib.Instruction::resume_(
dae_rewrite_pruned_type_idx(type_idx, remap),
handlers,
)
@lib.ResumeThrow(type_idx, tag, handlers) =>
@lib.Instruction::resume_throw(
dae_rewrite_pruned_type_idx(type_idx, remap),
tag,
handlers,
)
@lib.ResumeThrowRef(type_idx, handlers) =>
@lib.Instruction::resume_throw_ref(
dae_rewrite_pruned_type_idx(type_idx, remap),
handlers,
)
@lib.StackSwitch(type_idx, tag) =>
@lib.Instruction::stack_switch(
dae_rewrite_pruned_type_idx(type_idx, remap),
tag,
)
@lib.Select(Some(types)) =>
@lib.Instruction::select(
types=Some(types.map(fn(vt) { dae_rewrite_pruned_val_type(vt, remap) })),
)
@lib.RefNull(rt) =>
@lib.Instruction::ref_null_type(dae_rewrite_pruned_ref_type(rt, remap))
@lib.RefGetDesc(type_idx) =>
@lib.Instruction::ref_get_desc(
dae_rewrite_pruned_type_idx(type_idx, remap),
)
@lib.RefTest(nullable, exact, ht) =>
@lib.Instruction::ref_test_type(
@lib.RefType::new(
nullable,
dae_rewrite_pruned_heap_type(ht, remap),
exact~,
),
)
@lib.RefCast(nullable, exact, ht) =>
@lib.Instruction::ref_cast_type(
@lib.RefType::new(
nullable,
dae_rewrite_pruned_heap_type(ht, remap),
exact~,
),
)
@lib.RefTestDesc(nullable, ht) =>
@lib.Instruction::ref_test_desc(
nullable,
dae_rewrite_pruned_heap_type(ht, remap),
)
@lib.RefCastDescEq(nullable, exact, ht) =>
@lib.Instruction::ref_cast_desc_eq_type(
@lib.RefType::new(
nullable,
dae_rewrite_pruned_heap_type(ht, remap),
exact~,
),
)
@lib.BrOnCast(
label,
@lib.CastOp(source_nullable, target_nullable, source_exact, target_exact),
source_type,
target_type
) =>
@lib.Instruction::br_on_cast(
label,
source_nullable,
dae_rewrite_pruned_heap_type(source_type, remap),
target_nullable,
dae_rewrite_pruned_heap_type(target_type, remap),
source_exact~,
target_exact~,
)
@lib.BrOnCastFail(
label,
@lib.CastOp(source_nullable, target_nullable, source_exact, target_exact),
source_type,
target_type
) =>
@lib.Instruction::br_on_cast_fail(
label,
source_nullable,
dae_rewrite_pruned_heap_type(source_type, remap),
target_nullable,
dae_rewrite_pruned_heap_type(target_type, remap),
source_exact~,
target_exact~,
)
@lib.BrOnCastDescEq(label, source_type, target_type) =>
@lib.Instruction::br_on_cast_desc_eq(
label,
dae_rewrite_pruned_ref_type(source_type, remap),
dae_rewrite_pruned_ref_type(target_type, remap),
)
@lib.BrOnCastDescEqFail(label, source_type, target_type) =>
@lib.Instruction::br_on_cast_desc_eq_fail(
label,
dae_rewrite_pruned_ref_type(source_type, remap),
dae_rewrite_pruned_ref_type(target_type, remap),
)
_ => instr
}
}
///|
fn dae_rewrite_pruned_type_idx_instrs(
instrs : Array[@lib.Instruction],
remap : Array[Int32],
) -> Array[@lib.Instruction] {
let mut out : Array[@lib.Instruction]? = None
for index = 0; index < instrs.length(); index = index + 1 {
let instr = instrs[index]
let next = dae_rewrite_pruned_type_idx_instr(instr, remap)
match out {
Some(copy) => copy.push(next)
None =>
if !physical_equal(next, instr) {
let copy = Array::new(capacity=instrs.length())
for prefix = 0; prefix < index; prefix = prefix + 1 {
copy.push(instrs[prefix])
}
copy.push(next)
out = Some(copy)
}
}
}
out.unwrap_or(instrs)
}
///|
fn dae_val_type_has_indexed_heap_type(vt : @lib.ValType) -> Bool {
match vt {
@lib.RefTypeValType(@lib.HeapTypeRefType(_, _, @lib.HeapType(_))) => true
@lib.RefTypeValType(@lib.HeapTypeRefType(_, _, @lib.DefTypeHeapType(_))) =>
true
_ => false
}
}
///|
fn dae_storage_type_has_indexed_heap_type(storage : @lib.StorageType) -> Bool {
match storage {
@lib.ValTypeStorageType(vt) => dae_val_type_has_indexed_heap_type(vt)
_ => false
}
}
///|
fn dae_comp_type_has_indexed_heap_type(comp_type : @lib.CompType) -> Bool {
match comp_type {
@lib.FuncCompType(params, results) =>
params.any(dae_val_type_has_indexed_heap_type) ||
results.any(dae_val_type_has_indexed_heap_type)
@lib.StructCompType(fields) =>
fields.any(fn(field) {
let @lib.FieldType(storage, _) = field
dae_storage_type_has_indexed_heap_type(storage)
})
@lib.ArrayCompType(field) => {
let @lib.FieldType(storage, _) = field
dae_storage_type_has_indexed_heap_type(storage)
}
@lib.ContCompType(_) => true
}
}
///|
fn dae_prune_unused_simple_func_types(
mod_ : @lib.Module,
preserve_names~ : Bool = false,
) -> @lib.Module {
let rec_types = match mod_.type_sec {
Some(@lib.TypeSec(rec_types)) => rec_types
None => return mod_
}
if !dae_simple_func_type_section(rec_types) {
return mod_
}
let used = Array::make(rec_types.length(), false)
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
match import_ {
@lib.Import(_, _, @lib.FuncExternType(type_idx)) =>
dae_mark_used_type_idx(type_idx, used)
@lib.Import(_, _, @lib.TableExternType(table_type)) =>
dae_mark_used_table_type(table_type, used)
@lib.Import(_, _, @lib.GlobalExternType(global_type)) =>
dae_mark_used_global_type(global_type, used)
@lib.Import(_, _, @lib.TagExternType(tag_type)) => {
let @lib.TagType(type_idx) = tag_type
dae_mark_used_type_idx(type_idx, used)
}
_ => ()
}
}
None => ()
}
match mod_.func_sec {
Some(@lib.FuncSec(type_idxs)) =>
for type_idx in type_idxs {
dae_mark_used_type_idx(type_idx, used)
}
None => ()
}
match mod_.tag_sec {
Some(@lib.TagSec(tags)) =>
for tag in tags {
let @lib.TagType(type_idx) = tag
dae_mark_used_type_idx(type_idx, used)
}
None => ()
}
match mod_.table_sec {
Some(@lib.TableSec(tables)) =>
for table in tables {
let @lib.Table(table_type, initializer) = table
dae_mark_used_table_type(table_type, used)
match initializer {
Some(@lib.Expr(body)) => dae_collect_used_type_idx_instrs(body, used)
None => ()
}
}
None => ()
}
match mod_.global_sec {
Some(@lib.GlobalSec(globals)) =>
for global in globals {
let @lib.Global(global_type, @lib.Expr(body)) = global
dae_mark_used_global_type(global_type, used)
dae_collect_used_type_idx_instrs(body, used)
}
None => ()
}
match mod_.elem_sec {
Some(@lib.ElemSec(elems)) =>
for elem in elems {
let @lib.Elem(_, kind) = elem
match kind {
@lib.TypedExprsElemKind(ref_type, exprs) => {
dae_mark_used_ref_type(ref_type, used)
for expr in exprs {
let @lib.Expr(body) = expr
dae_collect_used_type_idx_instrs(body, used)
}
}
@lib.FuncExprsElemKind(exprs) =>
for expr in exprs {
let @lib.Expr(body) = expr
dae_collect_used_type_idx_instrs(body, used)
}
_ => ()
}
}
None => ()
}
// Marks only move false -> true. Advance this witness at most T times
// across the invocation, with one false-cell check per max-scanned Func.
let mut first_unused = 0
while first_unused < used.length() && used[first_unused] {
first_unused += 1
}
let mut body_maxima : Array[Int32]? = None
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
if !funcs.is_empty() && first_unused < used.length() {
body_maxima = Some(Array::new(capacity=funcs.length()))
}
for func in funcs {
let @lib.Func(locals, @lib.Expr(body)) = func
for local_run in locals.runs() {
dae_mark_used_val_type(local_run.val_type(), used)
}
match body_maxima {
Some(maxima) => {
maxima.push(dae_prune_collect_used_type_idx_max(body, used))
while first_unused < used.length() && used[first_unused] {
first_unused += 1
}
if first_unused == used.length() {
// All types are retained. Keep the old checks and Unit walks.
body_maxima = None
}
}
None => dae_collect_used_type_idx_instrs(body, used)
}
}
}
None => ()
}
// The current remapper rewrites every module-level reference but does not
// reconstruct retained type definitions. Prune a mixed flat section only
// when each retained definition is internally index-free; unused function
// signatures may still reference GC types and disappear safely.
for old_idx = 0; old_idx < rec_types.length(); old_idx = old_idx + 1 {
if !used[old_idx] {
continue
}
match rec_types[old_idx] {
@lib.SingleRecType(subtype) =>
if dae_comp_type_has_indexed_heap_type(subtype.get_comptype()) {
return mod_
}
@lib.GroupRecType(_) => return mod_
}
}
let remap = Array::make(rec_types.length(), -1)
let next_types : Array[@lib.RecType] = []
let mut old_idx = 0
let mut first_moved = rec_types.length()
while old_idx < rec_types.length() {
if used[old_idx] {
remap[old_idx] = next_types.length()
if first_moved == rec_types.length() && remap[old_idx] != old_idx {
first_moved = old_idx
}
next_types.push(rec_types[old_idx])
}
old_idx += 1
}
if next_types.length() == rec_types.length() {
return mod_
}
// Type-index-bearing GC instructions are conservatively left untouched by
// this compact remapper. Require every retained non-function definition to
// keep its original slot; function types may shift because all function,
// block, tag, call-ref, import, and signature references are rewritten.
for old_idx = 0; old_idx < rec_types.length(); old_idx = old_idx + 1 {
if !used[old_idx] || remap[old_idx] == old_idx {
continue
}
match rec_types[old_idx] {
@lib.SingleRecType(subtype) =>
match subtype.get_comptype() {
@lib.FuncCompType(_, _) => ()
_ => return mod_
}
@lib.GroupRecType(_) => return mod_
}
}
let mut next = mod_
.with_type_sec(@lib.TypeSec::new(next_types))
.without_name_sec()
if preserve_names {
match mod_.name_sec {
Some(names) => {
let type_names = match names.type_names {
Some(names) => rl_rewrite_name_map(names, remap)
None => None
}
let field_names = match names.field_names {
Some(@lib.IndirectNameMap(entries)) => {
let kept : Array[@lib.IndirectNameAssoc] = []
for entry in entries {
let @lib.IndirectNameAssoc(type_idx, fields) = entry
let old_idx = type_idx.to_int()
if old_idx >= 0 && old_idx < remap.length() && remap[old_idx] >= 0 {
kept.push(
@lib.IndirectNameAssoc::new(
remap[old_idx].reinterpret_as_uint(),
fields,
),
)
}
}
if kept.is_empty() {
None
} else {
Some(@lib.IndirectNameMap::new(kept))
}
}
None => None
}
next = next.with_name_sec(
@lib.NameSec::new(
module_name=names.module_name,
func_names=names.func_names,
local_names=names.local_names,
label_names=names.label_names,
type_names~,
table_names=names.table_names,
memory_names=names.memory_names,
global_names=names.global_names,
elem_names=names.elem_names,
data_names=names.data_names,
field_names~,
tag_names=names.tag_names,
),
)
}
None => ()
}
}
match mod_.import_sec {
Some(@lib.ImportSec(imports)) => {
let rewritten : Array[@lib.Import] = []
for import_ in imports {
match import_ {
@lib.Import(module_name, base_name, @lib.FuncExternType(type_idx)) =>
rewritten.push(
@lib.Import::new(
module_name,
base_name,
@lib.ExternType::func(
dae_rewrite_pruned_type_idx(type_idx, remap),
),
),
)
@lib.Import(module_name, base_name, @lib.TableExternType(table_type)) =>
rewritten.push(
@lib.Import::new(
module_name,
base_name,
@lib.ExternType::table(
dae_rewrite_pruned_table_type(table_type, remap),
),
),
)
@lib.Import(
module_name,
base_name,
@lib.GlobalExternType(global_type)
) =>
rewritten.push(
@lib.Import::new(
module_name,
base_name,
@lib.ExternType::global(
dae_rewrite_pruned_global_type(global_type, remap),
),
),
)
@lib.Import(module_name, base_name, @lib.TagExternType(tag_type)) => {
let @lib.TagType(type_idx) = tag_type
rewritten.push(
@lib.Import::new(
module_name,
base_name,
@lib.ExternType::tag(
@lib.TagType::new(
dae_rewrite_pruned_type_idx(type_idx, remap),
),
),
),
)
}
_ => rewritten.push(import_)
}
}
next = next.with_import_sec(@lib.ImportSec::new(rewritten))
}
None => ()
}
match mod_.func_sec {
Some(@lib.FuncSec(type_idxs)) => {
let rewritten : Array[@lib.TypeIdx] = []
for type_idx in type_idxs {
rewritten.push(dae_rewrite_pruned_type_idx(type_idx, remap))
}
next = next.with_func_sec(@lib.FuncSec::new(rewritten))
}
None => ()
}
match mod_.tag_sec {
Some(@lib.TagSec(tags)) => {
let rewritten : Array[@lib.TagType] = []
for tag in tags {
let @lib.TagType(type_idx) = tag
rewritten.push(
@lib.TagType::new(dae_rewrite_pruned_type_idx(type_idx, remap)),
)
}
next = next.with_tag_sec(@lib.TagSec::new(rewritten))
}
None => ()
}
match mod_.table_sec {
Some(@lib.TableSec(tables)) => {
let rewritten : Array[@lib.Table] = []
for table in tables {
let @lib.Table(table_type, init) = table
rewritten.push(
@lib.Table::new(
dae_rewrite_pruned_table_type(table_type, remap),
match init {
Some(@lib.Expr(body)) =>
Some(
@lib.Expr::new(
dae_rewrite_pruned_type_idx_instrs(body, remap),
),
)
None => None
},
),
)
}
next = next.with_table_sec(@lib.TableSec::new(rewritten))
}
None => ()
}
match mod_.global_sec {
Some(@lib.GlobalSec(globals)) => {
let rewritten : Array[@lib.Global] = []
for global in globals {
let @lib.Global(global_type, @lib.Expr(body)) = global
rewritten.push(
@lib.Global::new(
dae_rewrite_pruned_global_type(global_type, remap),
@lib.Expr::new(dae_rewrite_pruned_type_idx_instrs(body, remap)),
),
)
}
next = next.with_global_sec(@lib.GlobalSec::new(rewritten))
}
None => ()
}
match mod_.elem_sec {
Some(@lib.ElemSec(elems)) => {
let rewritten : Array[@lib.Elem] = []
for elem in elems {
let @lib.Elem(mode, kind) = elem
let next_kind = match kind {
@lib.TypedExprsElemKind(ref_type, exprs) => {
let next_exprs : Array[@lib.Expr] = []
for expr in exprs {
let @lib.Expr(body) = expr
next_exprs.push(
@lib.Expr::new(dae_rewrite_pruned_type_idx_instrs(body, remap)),
)
}
@lib.ElemKind::typed_exprs(
dae_rewrite_pruned_ref_type(ref_type, remap),
next_exprs,
)
}
@lib.FuncExprsElemKind(exprs) => {
let next_exprs : Array[@lib.Expr] = []
for expr in exprs {
let @lib.Expr(body) = expr
next_exprs.push(
@lib.Expr::new(dae_rewrite_pruned_type_idx_instrs(body, remap)),
)
}
@lib.ElemKind::func_exprs(next_exprs)
}
_ => kind
}
rewritten.push(@lib.Elem::new(mode, next_kind))
}
next = next.with_elem_sec(@lib.ElemSec::new(rewritten))
}
None => ()
}
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
let rewritten : Array[@lib.Func] = []
for func_index = 0
func_index < funcs.length()
func_index = func_index + 1 {
let @lib.Func(locals, @lib.Expr(body)) = funcs[func_index]
let next_runs : Array[@lib.LocalRun] = []
for run in locals.runs() {
next_runs.push(
@lib.LocalRun::new(
run.count(),
dae_rewrite_pruned_val_type(run.val_type(), remap),
),
)
}
rewritten.push(
@lib.Func::new(
@lib.Locals::new(next_runs),
@lib.Expr::new(
match body_maxima {
Some(maxima) if maxima[func_index] < first_moved => body
_ => dae_rewrite_pruned_type_idx_instrs(body, remap)
},
),
),
)
}
next = next.with_code_sec(@lib.CodeSec::new(rewritten))
}
None => ()
}
next
}
///|
fn dae_append_new_func_type(
type_sec : @lib.TypeSec,
params : Array[@lib.ValType],
results : Array[@lib.ValType],
) -> (@lib.TypeSec, @lib.TypeIdx) {
let @lib.TypeSec(rec_types) = type_sec
let next = rec_types.copy()
let idx = @lib.TypeIdx::new(
dae_type_sec_flattened_length(type_sec).reinterpret_as_uint(),
)
next.push(
@lib.RecType::new(
@lib.SubType::comp_type(@lib.CompType::func(params, results)),
),
)
(@lib.TypeSec::new(next), idx)
}
///|
fn dae_simple_func_type_key(
params : Array[@lib.ValType],
results : Array[@lib.ValType],
) -> String {
params.to_string() + "->" + results.to_string()
}
///|
fn dae_simple_func_subtype_type(subtype : @lib.SubType) -> @lib.FuncType? {
if !subtype.super_types().is_empty() ||
subtype.describes_type() is Some(_) ||
subtype.descriptor_type() is Some(_) {
return None
}
match subtype.get_comptype() {
@lib.FuncCompType(params, results) =>
Some(@lib.FuncType::new(params, results))
_ => None
}
}
///|
fn dae_simple_func_type_lookup(
type_sec : @lib.TypeSec,
) -> @hashmap.HashMap[String, @lib.TypeIdx] {
let @lib.TypeSec(rec_types) = type_sec
let lookup : @hashmap.HashMap[String, @lib.TypeIdx] = @hashmap.new(
capacity=dae_type_sec_flattened_length(type_sec) * 2 + 1,
)
let mut flat_idx = 0
for rec_type in rec_types {
let group = match rec_type {
@lib.SingleRecType(subtype) => [subtype]
@lib.GroupRecType(group) => group
}
for subtype in group {
match dae_simple_func_subtype_type(subtype) {
Some(@lib.FuncType(params, results)) => {
let owner = @lib.TypeIdx::new(flat_idx.reinterpret_as_uint())
let (params, results) = dae_absolutize_signature(
type_sec, owner, params, results,
)
let key = dae_simple_func_type_key(params, results)
if lookup.get(key) is None {
lookup.set(key, owner)
}
}
None => ()
}
flat_idx += 1
}
}
lookup
}
///|
priv struct DaeTypeUpdateIndex {
signatures : @hashmap.HashMap[String, @lib.TypeIdx]
groups : Array[@lib.RecType]
mut flat_count : Int32
}
///|
fn DaeTypeUpdateIndex::new(type_sec : @lib.TypeSec) -> DaeTypeUpdateIndex {
let @lib.TypeSec(groups) = type_sec
{
signatures: dae_simple_func_type_lookup(type_sec),
groups: groups.copy(),
flat_count: dae_type_sec_flattened_length(type_sec),
}
}
///|
fn DaeTypeUpdateIndex::append_signature(
self : DaeTypeUpdateIndex,
params : Array[@lib.ValType],
results : Array[@lib.ValType],
) -> (@lib.TypeSec, @lib.TypeIdx) {
let next_type_idx = @lib.TypeIdx::new(self.flat_count.reinterpret_as_uint())
self.groups.push(
@lib.RecType::new(
@lib.SubType::comp_type(@lib.CompType::func(params, results)),
),
)
self.flat_count += 1
(@lib.TypeSec::new(self.groups), next_type_idx)
}
///|
fn dae_find_equivalent_simple_func_type(
type_sec : @lib.TypeSec,
params : Array[@lib.ValType],
results : Array[@lib.ValType],
) -> @lib.TypeIdx? {
let @lib.TypeSec(rec_types) = type_sec
let mut flat_idx = 0
for rec_type in rec_types {
let group = match rec_type {
@lib.SingleRecType(subtype) => [subtype]
@lib.GroupRecType(group) => group
}
for subtype in group {
match dae_simple_func_subtype_type(subtype) {
Some(@lib.FuncType(existing_params, existing_results)) if existing_params ==
params &&
existing_results == results =>
return Some(@lib.TypeIdx::new(flat_idx.reinterpret_as_uint()))
_ => ()
}
flat_idx += 1
}
}
None
}
///|
fn dae_absolutize_signature_val_type(
type_sec : @lib.TypeSec,
owner : @lib.TypeIdx,
value : @lib.ValType,
) -> @lib.ValType {
match value {
@lib.RefTypeValType(
@lib.HeapTypeRefType(nullable, exact, @lib.HeapType(reference))
) => {
let absolute = match
dae_type_sec_resolve_member_reference(type_sec, owner, reference) {
Some(index) => index
None =>
abort(
"DAE batch signature contains an invalid recursive type reference",
)
}
@lib.ValType::ref_type(
@lib.RefType::new(nullable, @lib.HeapType::new(absolute), exact~),
)
}
_ => value
}
}
///|
fn dae_absolutize_signature(
type_sec : @lib.TypeSec,
owner : @lib.TypeIdx,
params : Array[@lib.ValType],
results : Array[@lib.ValType],
) -> (Array[@lib.ValType], Array[@lib.ValType]) {
(
params.map(fn(value) {
dae_absolutize_signature_val_type(type_sec, owner, value)
}),
results.map(fn(value) {
dae_absolutize_signature_val_type(type_sec, owner, value)
}),
)
}
///|
fn dae_type_idx_equal(lhs : @lib.TypeIdx, rhs : @lib.TypeIdx) -> Bool {
lhs == rhs
}
///|
fn dae_type_idx_references_slot(
type_idx : @lib.TypeIdx,
target_slot : Int32,
rec_group_base : Int32,
) -> Bool {
match type_idx {
@lib.TypeIdx(raw) => raw.reinterpret_as_int() == target_slot
@lib.RecIdx(raw) =>
rec_group_base >= 0 &&
rec_group_base + raw.reinterpret_as_int() == target_slot
}
}
///|
fn dae_heap_type_references_slot(
heap_type : @lib.HeapType,
target_slot : Int32,
rec_group_base : Int32,
) -> Bool {
match heap_type {
@lib.HeapType(type_idx) =>
dae_type_idx_references_slot(type_idx, target_slot, rec_group_base)
@lib.DefTypeHeapType(_) => true
_ => false
}
}
///|
fn dae_ref_type_references_slot(
ref_type : @lib.RefType,
target_slot : Int32,
rec_group_base : Int32,
) -> Bool {
match ref_type {
@lib.HeapTypeRefType(_, _, heap_type) =>
dae_heap_type_references_slot(heap_type, target_slot, rec_group_base)
_ => false
}
}
///|
fn dae_val_type_references_slot(
val_type : @lib.ValType,
target_slot : Int32,
rec_group_base : Int32,
) -> Bool {
match val_type {
@lib.RefTypeValType(ref_type) =>
dae_ref_type_references_slot(ref_type, target_slot, rec_group_base)
_ => false
}
}
///|
fn dae_storage_type_references_slot(
storage_type : @lib.StorageType,
target_slot : Int32,
rec_group_base : Int32,
) -> Bool {
match storage_type {
@lib.ValTypeStorageType(val_type) =>
dae_val_type_references_slot(val_type, target_slot, rec_group_base)
_ => false
}
}
///|
fn dae_field_type_references_slot(
field_type : @lib.FieldType,
target_slot : Int32,
rec_group_base : Int32,
) -> Bool {
let @lib.FieldType(storage_type, _) = field_type
dae_storage_type_references_slot(storage_type, target_slot, rec_group_base)
}
///|
fn dae_comp_type_references_slot(
comp_type : @lib.CompType,
target_slot : Int32,
rec_group_base : Int32,
) -> Bool {
match comp_type {
@lib.ArrayCompType(field_type) =>
dae_field_type_references_slot(field_type, target_slot, rec_group_base)
@lib.StructCompType(fields) =>
fields.any(fn(field_type) {
dae_field_type_references_slot(field_type, target_slot, rec_group_base)
})
@lib.FuncCompType(params, results) =>
params.any(fn(param) {
dae_val_type_references_slot(param, target_slot, rec_group_base)
}) ||
results.any(fn(result) {
dae_val_type_references_slot(result, target_slot, rec_group_base)
})
@lib.ContCompType(func_type) =>
dae_type_idx_references_slot(func_type, target_slot, rec_group_base)
}
}
///|
fn dae_subtype_references_slot(
subtype : @lib.SubType,
target_slot : Int32,
rec_group_base : Int32,
) -> Bool {
let (supers, metadata, comp_type) = match subtype {
@lib.SubType(_, supers, metadata, comp_type) =>
(supers, metadata, comp_type)
@lib.CompTypeSubType(metadata, comp_type) => ([], metadata, comp_type)
}
for super_type in supers {
if dae_type_idx_references_slot(super_type, target_slot, rec_group_base) {
return true
}
}
let { describes, descriptor, .. } = metadata
match describes {
Some(type_idx) =>
if dae_type_idx_references_slot(type_idx, target_slot, rec_group_base) {
return true
}
None => ()
}
match descriptor {
Some(type_idx) =>
if dae_type_idx_references_slot(type_idx, target_slot, rec_group_base) {
return true
}
None => ()
}
dae_comp_type_references_slot(comp_type, target_slot, rec_group_base)
}
///|
fn dae_type_sec_references_slot(
type_sec : @lib.TypeSec,
target_slot : Int32,
) -> Bool {
let @lib.TypeSec(rec_types) = type_sec
let mut flat_idx = 0
for rec_type in rec_types {
match rec_type {
@lib.SingleRecType(subtype) => {
if dae_subtype_references_slot(subtype, target_slot, flat_idx) {
return true
}
flat_idx += 1
}
@lib.GroupRecType(group) => {
for subtype in group {
if dae_subtype_references_slot(subtype, target_slot, flat_idx) {
return true
}
}
flat_idx += group.length()
}
}
}
false
}
///|
fn dae_instrs_reference_type_slot(
instrs : Array[@lib.Instruction],
target_slot : Int32,
) -> Bool {
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body))
| @lib.Loop(block_type, @lib.Expr(body))
| @lib.TryTable(block_type, _, @lib.Expr(body)) => {
match block_type {
@lib.ValTypeBlockType(val_type) =>
if dae_val_type_references_slot(val_type, target_slot, -1) {
return true
}
@lib.TypeIdxBlockType(type_idx) =>
if dae_type_idx_references_slot(type_idx, target_slot, -1) {
return true
}
_ => ()
}
if dae_instrs_reference_type_slot(body, target_slot) {
return true
}
}
@lib.Try(block_type, @lib.Expr(body), catches, _) => {
match block_type {
@lib.ValTypeBlockType(val_type) =>
if dae_val_type_references_slot(val_type, target_slot, -1) {
return true
}
@lib.TypeIdxBlockType(type_idx) =>
if dae_type_idx_references_slot(type_idx, target_slot, -1) {
return true
}
_ => ()
}
if dae_instrs_reference_type_slot(body, target_slot) {
return true
}
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, catch_body) | @lib.LegacyCatchAll(catch_body) =>
catch_body
}
if dae_instrs_reference_type_slot(catch_body, target_slot) {
return true
}
}
}
@lib.If(block_type, then_body, else_body) => {
match block_type {
@lib.ValTypeBlockType(val_type) =>
if dae_val_type_references_slot(val_type, target_slot, -1) {
return true
}
@lib.TypeIdxBlockType(type_idx) =>
if dae_type_idx_references_slot(type_idx, target_slot, -1) {
return true
}
_ => ()
}
if dae_instrs_reference_type_slot(then_body, target_slot) {
return true
}
match else_body {
Some(body) =>
if dae_instrs_reference_type_slot(body, target_slot) {
return true
}
None => ()
}
}
@lib.CallIndirect(type_idx, _)
| @lib.ReturnCallIndirect(type_idx, _)
| @lib.CallRef(type_idx)
| @lib.ReturnCallRef(type_idx)
| @lib.ContNew(type_idx)
| @lib.Resume(type_idx, _)
| @lib.ResumeThrow(type_idx, _, _)
| @lib.ResumeThrowRef(type_idx, _)
| @lib.StackSwitch(type_idx, _)
| @lib.StructNew(type_idx)
| @lib.StructNewDefault(type_idx)
| @lib.StructNewDesc(type_idx)
| @lib.StructNewDefaultDesc(type_idx)
| @lib.ArrayNew(type_idx)
| @lib.ArrayNewDefault(type_idx)
| @lib.ArrayNewFixed(type_idx, _)
| @lib.ArrayNewData(type_idx, _)
| @lib.ArrayNewElem(type_idx, _)
| @lib.ArrayGet(type_idx)
| @lib.ArrayGetS(type_idx)
| @lib.ArrayGetU(type_idx)
| @lib.ArraySet(type_idx)
| @lib.ArrayFill(type_idx)
| @lib.ArrayInitData(type_idx, _)
| @lib.ArrayInitElem(type_idx, _)
| @lib.RefGetDesc(type_idx)
| @lib.StructGet(type_idx, _)
| @lib.StructGetS(type_idx, _)
| @lib.StructGetU(type_idx, _)
| @lib.StructWait(type_idx, _)
| @lib.StructAtomicSet(_, type_idx, _)
| @lib.ArrayAtomicSet(_, type_idx)
| @lib.StructAtomicGet(_, type_idx, _)
| @lib.StructAtomicGetS(_, type_idx, _)
| @lib.StructAtomicGetU(_, type_idx, _)
| @lib.StructAtomicRmw(_, _, type_idx, _)
| @lib.StructAtomicCmpxchg(_, type_idx, _)
| @lib.StructSet(type_idx, _)
| @lib.ArrayAtomicGet(_, type_idx)
| @lib.ArrayAtomicGetS(_, type_idx)
| @lib.ArrayAtomicGetU(_, type_idx)
| @lib.ArrayAtomicRmw(_, _, type_idx)
| @lib.ArrayAtomicCmpxchg(_, type_idx)
| @lib.ArrayLoad(_, type_idx, _)
| @lib.ArrayStore(_, type_idx, _) =>
if dae_type_idx_references_slot(type_idx, target_slot, -1) {
return true
}
@lib.ContBind(source_type, target_type)
| @lib.ArrayCopy(source_type, target_type) =>
if dae_type_idx_references_slot(source_type, target_slot, -1) ||
dae_type_idx_references_slot(target_type, target_slot, -1) {
return true
}
@lib.Select(Some(types)) =>
if types.any(fn(val_type) {
dae_val_type_references_slot(val_type, target_slot, -1)
}) {
return true
}
@lib.RefNull(ref_type) =>
if dae_ref_type_references_slot(ref_type, target_slot, -1) {
return true
}
@lib.RefTest(_, _, heap_type)
| @lib.RefCast(_, _, heap_type)
| @lib.RefTestDesc(_, heap_type)
| @lib.RefCastDescEq(_, _, heap_type) =>
if dae_heap_type_references_slot(heap_type, target_slot, -1) {
return true
}
@lib.BrOnCast(_, _, source_type, target_type)
| @lib.BrOnCastFail(_, _, source_type, target_type) =>
if dae_heap_type_references_slot(source_type, target_slot, -1) ||
dae_heap_type_references_slot(target_type, target_slot, -1) {
return true
}
@lib.BrOnCastDescEq(_, source_type, target_type)
| @lib.BrOnCastDescEqFail(_, source_type, target_type) =>
if dae_ref_type_references_slot(source_type, target_slot, -1) ||
dae_ref_type_references_slot(target_type, target_slot, -1) {
return true
}
_ => ()
}
}
false
}
///|
fn dae_non_funcsec_references_type_slot(
mod_ : @lib.Module,
target_slot : Int32,
) -> Bool {
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
match import_ {
@lib.Import(_, _, @lib.TableExternType(@lib.TableType(ref_type, _))) =>
if dae_ref_type_references_slot(ref_type, target_slot, -1) {
return true
}
@lib.Import(_, _, @lib.GlobalExternType(@lib.GlobalType(val_type, _))) =>
if dae_val_type_references_slot(val_type, target_slot, -1) {
return true
}
@lib.Import(_, _, @lib.TagExternType(@lib.TagType(type_idx))) =>
if dae_type_idx_references_slot(type_idx, target_slot, -1) {
return true
}
_ => ()
}
}
None => ()
}
match mod_.tag_sec {
Some(@lib.TagSec(tags)) =>
for tag in tags {
let @lib.TagType(type_idx) = tag
if dae_type_idx_references_slot(type_idx, target_slot, -1) {
return true
}
}
None => ()
}
match mod_.table_sec {
Some(@lib.TableSec(tables)) =>
for table in tables {
let @lib.Table(@lib.TableType(ref_type, _), init) = table
if dae_ref_type_references_slot(ref_type, target_slot, -1) {
return true
}
match init {
Some(@lib.Expr(body)) =>
if dae_instrs_reference_type_slot(body, target_slot) {
return true
}
None => ()
}
}
None => ()
}
match mod_.global_sec {
Some(@lib.GlobalSec(globals)) =>
for global in globals {
let @lib.Global(@lib.GlobalType(val_type, _), @lib.Expr(body)) = global
if dae_val_type_references_slot(val_type, target_slot, -1) ||
dae_instrs_reference_type_slot(body, target_slot) {
return true
}
}
None => ()
}
match mod_.elem_sec {
Some(@lib.ElemSec(elems)) =>
for elem in elems {
let @lib.Elem(_, kind) = elem
match kind {
@lib.TypedExprsElemKind(ref_type, exprs) => {
if dae_ref_type_references_slot(ref_type, target_slot, -1) {
return true
}
for expr in exprs {
let @lib.Expr(body) = expr
if dae_instrs_reference_type_slot(body, target_slot) {
return true
}
}
}
@lib.FuncExprsElemKind(exprs) =>
for expr in exprs {
let @lib.Expr(body) = expr
if dae_instrs_reference_type_slot(body, target_slot) {
return true
}
}
_ => ()
}
}
None => ()
}
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
for func in funcs {
let @lib.Func(locals, @lib.Expr(body)) = func
for local_run in locals.runs() {
if dae_val_type_references_slot(local_run.val_type(), target_slot, -1) {
return true
}
}
if dae_instrs_reference_type_slot(body, target_slot) {
return true
}
}
None => ()
}
false
}
///|
fn dae_can_replace_unique_defined_func_type(
mod_ : @lib.Module,
func_types : Array[@lib.TypeIdx],
def_idx : Int32,
old_type_idx : @lib.TypeIdx,
) -> Bool {
let old_slot = match dae_type_idx_slot(old_type_idx) {
Some(slot) => slot
None => return false
}
let type_sec = match mod_.type_sec {
Some(type_sec) => type_sec
None => return false
}
let flat_len = dae_type_sec_flattened_length(type_sec)
if old_slot < 0 || old_slot >= flat_len {
return false
}
if dae_type_sec_rec_entry_location(type_sec, old_type_idx) is None {
return false
}
match dae_type_sec_subtype(type_sec, old_type_idx) {
Some(@lib.CompTypeSubType(metadata, @lib.FuncCompType(_, _))) => {
let { describes, descriptor, .. } = metadata
if describes is Some(_) || descriptor is Some(_) {
return false
}
}
_ => return false
}
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
match import_ {
@lib.Import(_, _, @lib.FuncExternType(type_idx)) if dae_type_idx_equal(
type_idx, old_type_idx,
) => return false
_ => ()
}
}
None => ()
}
let mut user_count = 0
let mut idx = 0
while idx < func_types.length() {
if dae_type_idx_equal(func_types[idx], old_type_idx) {
user_count += 1
if idx != def_idx {
return false
}
}
idx += 1
}
if user_count != 1 {
return false
}
!dae_non_funcsec_references_type_slot(mod_, old_slot) &&
!dae_type_sec_references_slot(type_sec, old_slot)
}
///|
fn dae_replace_defined_func_type_in_place(
type_sec : @lib.TypeSec,
type_idx : @lib.TypeIdx,
params : Array[@lib.ValType],
results : Array[@lib.ValType],
) -> @lib.TypeSec? {
let (entry_slot, subtype_slot) = match
dae_type_sec_rec_entry_location(type_sec, type_idx) {
Some(location) => location
None => return None
}
let @lib.TypeSec(rec_types) = type_sec
if entry_slot < 0 || entry_slot >= rec_types.length() {
return None
}
let replacement = @lib.SubType::comp_type(
@lib.CompType::func(params, results),
)
let next = rec_types.copy()
match rec_types[entry_slot] {
@lib.SingleRecType(_) => {
if subtype_slot != 0 {
return None
}
next[entry_slot] = @lib.RecType::new(replacement)
}
@lib.GroupRecType(group) => {
if subtype_slot < 0 || subtype_slot >= group.length() {
return None
}
let rewritten_group = group.copy()
rewritten_group[subtype_slot] = replacement
next[entry_slot] = @lib.RecType::group(rewritten_group)
}
}
Some(@lib.TypeSec::new(next))
}
///|
fn dae_update_defined_func_type(
mod_ : @lib.Module,
type_sec : @lib.TypeSec,
func_types : Array[@lib.TypeIdx],
def_idx : Int32,
params : Array[@lib.ValType],
results : Array[@lib.ValType],
type_update_index~ : DaeTypeUpdateIndex? = None,
unique_scan_count~ : Array[Int32]? = None,
) -> (@lib.TypeSec, @lib.TypeIdx) {
let old_type_idx = func_types[def_idx]
let (params, results) = match type_update_index {
Some(_) => dae_absolutize_signature(type_sec, old_type_idx, params, results)
None => (params, results)
}
let key = dae_simple_func_type_key(params, results)
let equivalent = match type_update_index {
Some(index) => index.signatures.get(key)
None => dae_find_equivalent_simple_func_type(type_sec, params, results)
}
match equivalent {
Some(existing_idx) => return (type_sec, existing_idx)
None => ()
}
// Batch callers keep a signature index and deliberately append. In-place
// replacement requires whole-module reference scans for each candidate;
// appending preserves all existing identities and makes admission O(1).
if type_update_index is None {
match unique_scan_count {
Some(count) => count[0] += 1
None => ()
}
}
if type_update_index is None &&
dae_can_replace_unique_defined_func_type(
mod_, func_types, def_idx, old_type_idx,
) {
match
dae_replace_defined_func_type_in_place(
type_sec, old_type_idx, params, results,
) {
Some(next_type_sec) => return (next_type_sec, old_type_idx)
None => ()
}
}
let (next_type_sec, next_type_idx) = match type_update_index {
Some(index) => index.append_signature(params, results)
None => dae_append_new_func_type(type_sec, params, results)
}
match type_update_index {
Some(index) => index.signatures.set(key, next_type_idx)
None => ()
}
(next_type_sec, next_type_idx)
}
///|
fn dae_type_sec_resolve_member_reference(
type_sec : @lib.TypeSec,
owner : @lib.TypeIdx,
reference : @lib.TypeIdx,
) -> @lib.TypeIdx? {
match reference {
@lib.TypeIdx(_) => Some(reference)
@lib.RecIdx(raw) => {
let (owner_entry, _) = match
dae_type_sec_rec_entry_location(type_sec, owner) {
Some(location) => location
None => return None
}
let @lib.TypeSec(rec_types) = type_sec
if owner_entry < 0 || owner_entry >= rec_types.length() {
return None
}
let mut group_base = 0
let mut entry_idx = 0
while entry_idx < owner_entry {
group_base += match rec_types[entry_idx] {
@lib.SingleRecType(_) => 1
@lib.GroupRecType(group) => group.length()
}
entry_idx += 1
}
let member_count = match rec_types[owner_entry] {
@lib.SingleRecType(_) => 1
@lib.GroupRecType(group) => group.length()
}
let member_idx = raw.reinterpret_as_int()
if member_idx < 0 || member_idx >= member_count {
return None
}
Some(@lib.TypeIdx::new((group_base + member_idx).reinterpret_as_uint()))
}
}
}
///|
fn dae_type_idx_is_subtype_of_visit(
mod_ : @lib.Module,
candidate : @lib.TypeIdx,
target : @lib.TypeIdx,
visited : Array[Bool],
) -> Bool {
if candidate == target {
return true
}
let slot = match dae_type_idx_slot(candidate) {
Some(slot) => slot
None => return false
}
if slot < 0 || slot >= visited.length() || visited[slot] {
return false
}
visited[slot] = true
match mod_.type_sec {
Some(type_sec) =>
match dae_type_sec_subtype(type_sec, candidate) {
Some(subtype) => {
for super_reference in subtype.super_types() {
match
dae_type_sec_resolve_member_reference(
type_sec, candidate, super_reference,
) {
Some(super_type) =>
if dae_type_idx_is_subtype_of_visit(
mod_, super_type, target, visited,
) {
return true
}
None => ()
}
}
false
}
None => false
}
None => false
}
}
///|
fn dae_type_idx_is_subtype_of(
mod_ : @lib.Module,
candidate : @lib.TypeIdx,
target : @lib.TypeIdx,
) -> Bool {
let rec_len = match mod_.type_sec {
Some(type_sec) => dae_type_sec_flattened_length(type_sec)
None => return false
}
dae_type_idx_is_subtype_of_visit(
mod_,
candidate,
target,
Array::make(rec_len, false),
)
}
///|
fn dae_collect_type_ancestors_visit(
mod_ : @lib.Module,
type_idx : @lib.TypeIdx,
out : Array[@lib.TypeIdx],
visited : Array[Bool],
) -> Unit {
let slot = match dae_type_idx_slot(type_idx) {
Some(slot) => slot
None => return
}
if slot < 0 || slot >= visited.length() || visited[slot] {
return
}
visited[slot] = true
out.push(type_idx)
match mod_.type_sec {
Some(type_sec) =>
match dae_type_sec_subtype(type_sec, type_idx) {
Some(subtype) =>
for super_reference in subtype.super_types() {
match
dae_type_sec_resolve_member_reference(
type_sec, type_idx, super_reference,
) {
Some(super_type) =>
dae_collect_type_ancestors_visit(mod_, super_type, out, visited)
None => ()
}
}
None => ()
}
None => ()
}
}
///|
fn dae_collect_type_ancestors(
mod_ : @lib.Module,
type_idx : @lib.TypeIdx,
) -> Array[@lib.TypeIdx] {
let rec_len = match mod_.type_sec {
Some(type_sec) => dae_type_sec_flattened_length(type_sec)
None => return []
}
let out : Array[@lib.TypeIdx] = []
dae_collect_type_ancestors_visit(
mod_,
type_idx,
out,
Array::make(rec_len, false),
)
out
}
///|
fn dae_common_supertype_idx(
mod_ : @lib.Module,
left : @lib.TypeIdx,
right : @lib.TypeIdx,
old : @lib.TypeIdx,
) -> @lib.TypeIdx? {
let left_ancestors = dae_collect_type_ancestors(mod_, left)
let mut best : @lib.TypeIdx? = None
for candidate in left_ancestors {
if dae_type_idx_is_subtype_of(mod_, right, candidate) &&
dae_type_idx_is_subtype_of(mod_, candidate, old) {
match best {
Some(best_idx) =>
if dae_type_idx_is_subtype_of(mod_, candidate, best_idx) {
best = Some(candidate)
}
None => best = Some(candidate)
}
}
}
best
}
///|
fn dae_lub_gc_candidate_ref_type(
mod_ : @lib.Module,
left_type : @lib.ValType,
right_type : @lib.ValType,
old_type : @lib.ValType,
) -> @lib.ValType? {
match (left_type, right_type, old_type) {
(
@lib.RefTypeValType(
@lib.HeapTypeRefType(left_nullable, left_exact, @lib.HeapType(left_idx))
),
@lib.RefTypeValType(
@lib.HeapTypeRefType(
right_nullable,
right_exact,
@lib.HeapType(right_idx)
)
),
@lib.RefTypeValType(
@lib.HeapTypeRefType(old_nullable, _, @lib.HeapType(old_idx))
),
) => {
let nullable = left_nullable || right_nullable
if nullable && !old_nullable {
return None
}
if left_idx == right_idx {
if !dae_type_idx_is_subtype_of(mod_, left_idx, old_idx) {
return None
}
let exact = left_exact && right_exact
return Some(
@lib.ValType::ref_type(
@lib.RefType::new(nullable, @lib.HeapType::new(left_idx), exact~),
),
)
}
match dae_common_supertype_idx(mod_, left_idx, right_idx, old_idx) {
Some(common_idx) =>
Some(
@lib.ValType::ref_type(
@lib.RefType::new(nullable, @lib.HeapType::new(common_idx)),
),
)
None => None
}
}
(
@lib.RefTypeValType(left_ref),
@lib.RefTypeValType(right_ref),
@lib.RefTypeValType(old_ref),
) => {
let module_ctx = @ir.hot_module_context_from_module(mod_)
let candidate = match ls_ref_pair_lub(module_ctx, left_ref, right_ref) {
Some(ref_type) => ref_type
None => return None
}
if ls_ref_is_subtype(module_ctx, candidate, old_ref) {
Some(@lib.ValType::ref_type(candidate))
} else {
None
}
}
_ => None
}
}
///|
// Exact allocation facts are useful internally, but new annotations must stay
// within the input module's reference-type capability.
fn dae_gc_emitted_type(
mod_ : @lib.Module,
candidate_type : @lib.ValType,
) -> @lib.ValType {
match candidate_type {
@lib.RefTypeValType(ref_type) if ref_type.is_exact() &&
!@lib.module_uses_custom_descriptors(mod_) =>
@lib.ValType::ref_type(
@lib.RefType::new(ref_type.is_nullable(), ref_type.get_heap_type()),
)
_ => candidate_type
}
}
///|
fn dae_refined_gc_candidate_ref_type(
mod_ : @lib.Module,
old_type : @lib.ValType,
candidate_type : @lib.ValType,
) -> @lib.ValType? {
let candidate_type = dae_gc_emitted_type(mod_, candidate_type)
match (old_type, candidate_type) {
(
@lib.RefTypeValType(
@lib.HeapTypeRefType(old_nullable, _, @lib.HeapType(old_idx))
),
@lib.RefTypeValType(
@lib.HeapTypeRefType(
candidate_nullable,
exact,
@lib.HeapType(candidate_idx)
)
),
) => {
if candidate_nullable && !old_nullable {
return None
}
if !dae_type_idx_is_subtype_of(mod_, candidate_idx, old_idx) {
return None
}
let next = @lib.ValType::ref_type(
@lib.RefType::new(
candidate_nullable,
@lib.HeapType::new(candidate_idx),
exact~,
),
)
if next == old_type {
None
} else {
Some(next)
}
}
(@lib.RefTypeValType(old_ref), @lib.RefTypeValType(candidate_ref)) => {
let next = @lib.ValType::ref_type(candidate_ref)
if next == old_type {
return None
}
let module_ctx = @ir.hot_module_context_from_module(mod_)
if ls_ref_is_subtype(module_ctx, candidate_ref, old_ref) {
Some(next)
} else {
None
}
}
_ => None
}
}
///|
fn dae_block_type_single_non_null_concrete_gc_result_candidate(
block_type : @lib.BlockType,
mod_ : @lib.Module,
) -> @lib.ValType? {
dae_control_block_type_single_non_null_concrete_gc_result_evidence(
block_type,
mod_.type_sec,
).map(fn(evidence) { evidence.candidate })
}
///|
fn dae_block_type_last_non_null_concrete_gc_result_candidate(
block_type : @lib.BlockType,
mod_ : @lib.Module,
) -> @lib.ValType? {
dae_control_block_type_last_non_null_concrete_gc_result_evidence(
block_type,
mod_.type_sec,
).map(fn(evidence) { evidence.candidate })
}
///|
fn dae_direct_call_gc_result_signature_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_idx : @lib.FuncIdx,
) -> DaeDirectGcCallResultEvidence? {
let slot = dae_func_idx_slot(func_idx)
match dae_resolve_func_type(mod_, func_type_indices, slot) {
Some(@lib.FuncType(params, [result])) =>
match result {
@lib.RefTypeValType(_) =>
Some({
candidate: result,
refined_candidate: None,
param_count: params.length(),
})
_ => None
}
_ => None
}
}
///|
fn dae_direct_call_gc_result_refinement_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_idx : @lib.FuncIdx,
old_type : @lib.ValType,
) -> DaeDirectGcCallResultEvidence? {
let signature = match
dae_direct_call_gc_result_signature_evidence(
mod_, func_type_indices, func_idx,
) {
Some(evidence) => evidence
None => return None
}
match dae_refined_gc_candidate_ref_type(mod_, old_type, signature.candidate) {
Some(refined_type) =>
Some({
candidate: signature.candidate,
refined_candidate: Some(refined_type),
param_count: signature.param_count,
})
None => None
}
}
///|
fn dae_direct_gc_actual_candidate_type(
instr : @lib.Instruction,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> @lib.ValType? {
match instr {
@lib.StructNew(type_idx) | @lib.StructNewDefault(type_idx) =>
Some(
@lib.ValType::ref_type(
@lib.RefType::new(false, @lib.HeapType::new(type_idx), exact=true),
),
)
@lib.RefFunc(func_idx) => {
let slot = dae_func_idx_slot(func_idx)
if slot < 0 || slot >= func_type_indices.length() {
None
} else {
Some(
@lib.ValType::ref_type(
@lib.RefType::new(
false,
@lib.HeapType::new(func_type_indices[slot]),
exact=true,
),
),
)
}
}
@lib.Call(func_idx) | @lib.ReturnCall(func_idx) =>
dae_direct_call_gc_result_signature_evidence(
mod_, func_type_indices, func_idx,
).map(fn(evidence) { evidence.candidate })
@lib.CallIndirect(type_idx, _)
| @lib.ReturnCallIndirect(type_idx, _)
| @lib.CallRef(type_idx)
| @lib.ReturnCallRef(type_idx) =>
match mod_.type_sec {
Some(type_sec) =>
match dae_type_sec_func_type(type_sec, type_idx) {
Some(@lib.FuncType(_, [result])) => Some(result)
_ => None
}
None => None
}
@lib.Block(block_type, _)
| @lib.Loop(block_type, _)
| @lib.If(block_type, _, _) =>
dae_block_type_single_non_null_concrete_gc_result_candidate(
block_type, mod_,
)
_ => None
}
}
///|
fn dae_nullable_gc_candidate_ref_type(
candidate : @lib.ValType,
old_type : @lib.ValType,
mod_ : @lib.Module,
) -> @lib.ValType? {
match (candidate, old_type) {
(
@lib.RefTypeValType(
@lib.HeapTypeRefType(_, exact, @lib.HeapType(candidate_idx))
),
@lib.RefTypeValType(@lib.HeapTypeRefType(_, _, @lib.HeapType(old_idx))),
) =>
if dae_type_idx_is_subtype_of(mod_, candidate_idx, old_idx) {
Some(
@lib.ValType::ref_type(
@lib.RefType::new(true, @lib.HeapType::new(candidate_idx), exact~),
),
)
} else {
None
}
(@lib.RefTypeValType(candidate_ref), @lib.RefTypeValType(old_ref)) => {
let nullable_ref = candidate_ref.make_nullable()
let module_ctx = @ir.hot_module_context_from_module(mod_)
if ls_ref_is_subtype(module_ctx, nullable_ref, old_ref) {
Some(@lib.ValType::ref_type(nullable_ref))
} else {
None
}
}
_ => None
}
}
///|
fn dae_gc_select_actual_value_slices_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
body : Array[@lib.Instruction],
slices : DaeSelectValueSlices,
old_type : @lib.ValType,
require_refined~ : Bool = false,
rewrite_type~ : @lib.ValType? = None,
) -> DaeGcSelectValueSlicesEvidence? {
if !dae_select_value_slices_are_consistent(slices, slices.select_idx) ||
slices.select_idx >= body.length() ||
slices.right.end != slices.condition.start {
return None
}
match body[slices.select_idx] {
@lib.Select(_) => ()
_ => return None
}
let left_idx = match dae_value_slice_single_instruction_index(slices.left) {
Some(idx) if idx >= 0 && idx < body.length() => idx
_ => return None
}
let right_idx = match dae_value_slice_single_instruction_index(slices.right) {
Some(idx) if idx >= 0 && idx < body.length() => idx
_ => return None
}
let candidate = match (body[left_idx], body[right_idx]) {
(@lib.RefNull(_), @lib.RefNull(_)) => return None
(@lib.RefNull(_), right_instr) =>
match
dae_direct_gc_actual_candidate_type(
right_instr, mod_, func_type_indices,
) {
Some(right_candidate) =>
match
dae_nullable_gc_candidate_ref_type(right_candidate, old_type, mod_) {
Some(nullable_candidate) => nullable_candidate
None => return None
}
None => return None
}
(left_instr, @lib.RefNull(_)) =>
match
dae_direct_gc_actual_candidate_type(left_instr, mod_, func_type_indices) {
Some(left_candidate) =>
match
dae_nullable_gc_candidate_ref_type(left_candidate, old_type, mod_) {
Some(nullable_candidate) => nullable_candidate
None => return None
}
None => return None
}
(left_instr, right_instr) => {
let left_candidate = match
dae_direct_gc_actual_candidate_type(left_instr, mod_, func_type_indices) {
Some(result) => result
None => return None
}
let right_candidate = match
dae_direct_gc_actual_candidate_type(
right_instr, mod_, func_type_indices,
) {
Some(result) => result
None => return None
}
match
dae_lub_gc_candidate_ref_type(
mod_, left_candidate, right_candidate, old_type,
) {
Some(result) => result
None => return None
}
}
}
let refined_candidate = dae_refined_gc_candidate_ref_type(
mod_, old_type, candidate,
)
if require_refined && refined_candidate.is_none() {
return None
}
let left_rewrite = match (body[left_idx], rewrite_type) {
(@lib.RefNull(_), Some(@lib.RefTypeValType(rt))) =>
Some(@lib.Instruction::ref_null_type(rt))
_ => None
}
let right_rewrite = match (body[right_idx], rewrite_type) {
(@lib.RefNull(_), Some(@lib.RefTypeValType(rt))) =>
Some(@lib.Instruction::ref_null_type(rt))
_ => None
}
Some({
slices,
gc: { candidate, refined_candidate, left_rewrite, right_rewrite, },
})
}
///|
fn dae_gc_actual_shape_discovery_evidence(
instrs : Array[@lib.Instruction],
shape : DaeCallShape,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> DaeGcActualShapeDiscoveryEvidence? {
if shape.start < 0 || shape.start >= shape.end || shape.end > instrs.length() {
return None
}
let kind = match instrs[shape.end - 1] {
@lib.Block(_, _) => DaeBlockGcActualShape
@lib.Loop(_, _) => DaeLoopGcActualShape
@lib.If(_, _, _) => DaeIfGcActualShape
@lib.Select(_) => {
if shape.start + 4 > shape.end {
return None
}
let slices = match
dae_find_select_value_slices(
mod_,
func_type_indices,
instrs,
shape.end - 1,
required_left_start=shape.start,
) {
Some(slices) => slices
None => return None
}
DaeSelectGcActualShape(slices)
}
_ if shape.start + 1 == shape.end => DaeDirectGcActualShape
_ => return None
}
Some({ start: shape.start, end: shape.end, kind, })
}
///|
fn dae_gc_actual_type_evidence(
instrs : Array[@lib.Instruction],
shape : DaeGcActualShapeDiscoveryEvidence,
mod_ : @lib.Module,
old_type : @lib.ValType,
func_type_indices : Array[@lib.TypeIdx],
require_refined~ : Bool = false,
rewrite_type~ : @lib.ValType? = None,
) -> DaeGcActualTypeEvidence? {
if shape.start < 0 || shape.start >= shape.end || shape.end > instrs.length() {
return None
}
let terminal_idx = shape.end - 1
let candidate = match shape.kind {
DaeDirectGcActualShape => {
if shape.start + 1 != shape.end {
return None
}
match instrs[terminal_idx] {
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.Select(_) => return None
instr =>
dae_direct_gc_actual_candidate_type(instr, mod_, func_type_indices)
}
}
DaeBlockGcActualShape =>
match instrs[terminal_idx] {
@lib.Block(block_type, _) =>
dae_block_type_last_non_null_concrete_gc_result_candidate(
block_type, mod_,
)
_ => return None
}
DaeLoopGcActualShape =>
match instrs[terminal_idx] {
@lib.Loop(block_type, _) =>
dae_block_type_last_non_null_concrete_gc_result_candidate(
block_type, mod_,
)
_ => return None
}
DaeIfGcActualShape =>
match instrs[terminal_idx] {
@lib.If(block_type, _, _) =>
dae_block_type_last_non_null_concrete_gc_result_candidate(
block_type, mod_,
)
_ => return None
}
DaeSelectGcActualShape(slices) => {
if slices.left.start != shape.start || slices.select_idx != terminal_idx {
return None
}
match
dae_gc_select_actual_value_slices_evidence(
mod_,
func_type_indices,
instrs,
slices,
old_type,
require_refined~,
rewrite_type~,
) {
Some(evidence) =>
return Some({
candidate: evidence.gc.candidate,
refined_candidate: evidence.gc.refined_candidate,
select: Some(evidence),
terminal_rewrite: rewrite_type.map(fn(refined) {
@lib.Instruction::select(types=Some([refined]))
}),
})
None => return None
}
}
}
match candidate {
Some(candidate_type) => {
let refined_candidate = dae_refined_gc_candidate_ref_type(
mod_, old_type, candidate_type,
)
if require_refined && refined_candidate.is_none() {
None
} else {
Some({
candidate: candidate_type,
refined_candidate,
select: None,
terminal_rewrite: None,
})
}
}
None => None
}
}
///|
fn dae_gc_actual_value_slice_shape_evidence(
instrs : Array[@lib.Instruction],
slice : DaeValueSlice,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> DaeGcActualShapeDiscoveryEvidence? {
dae_gc_actual_shape_discovery_evidence(
instrs,
dae_call_shape_from_value_slice(slice),
mod_,
func_type_indices,
)
}
///|
fn dae_direct_gc_actual_candidate_type_for_value_slice(
instrs : Array[@lib.Instruction],
slice : DaeValueSlice,
mod_ : @lib.Module,
old_type : @lib.ValType,
func_type_indices : Array[@lib.TypeIdx],
caller_locals : @lib.FunctionLocals?,
) -> @lib.ValType? {
if slice.start >= 0 &&
slice.start + 1 == slice.end &&
slice.end <= instrs.length() {
match instrs[slice.start] {
@lib.LocalGet(local_idx) =>
return dae_gc_candidate_local_get_type(
mod_, caller_locals, local_idx, old_type,
)
_ => ()
}
}
let shape = match
dae_gc_actual_value_slice_shape_evidence(
instrs, slice, mod_, func_type_indices,
) {
Some(evidence) => evidence
None => return None
}
match
dae_gc_actual_type_evidence(
instrs, shape, mod_, old_type, func_type_indices,
) {
Some(evidence) => {
let _ = evidence.refined_candidate
Some(evidence.candidate)
}
None => None
}
}
///|
fn dae_collect_direct_struct_default_actuals_instr(
instr : @lib.Instruction,
callee_abs : Int32,
params : Array[@lib.ValType],
local_use : DaeLocalUse,
candidates : Array[@lib.ValType?],
rejected : Array[Bool],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
caller_locals : @lib.FunctionLocals?,
) -> (Int32, Bool) {
match instr {
@lib.Block(_, @lib.Expr(_))
| @lib.Loop(_, @lib.Expr(_))
| @lib.TryTable(_, _, @lib.Expr(_))
| @lib.If(_, _, _) => {
let child_evidence = match
dae_nested_control_children_evidence(instr, mod_) {
Some(evidence) => evidence
None => return (0, false)
}
let (primary_count, primary_ok) = dae_collect_direct_struct_default_actuals_instrs(
child_evidence.primary_body,
callee_abs,
params,
local_use,
candidates,
rejected,
child_evidence.entry_stack_values,
mod_,
func_type_indices,
caller_locals,
)
if !primary_ok {
return (primary_count, false)
}
match child_evidence.secondary_body {
Some(body) => {
let (secondary_count, secondary_ok) = dae_collect_direct_struct_default_actuals_instrs(
body,
callee_abs,
params,
local_use,
candidates,
rejected,
child_evidence.entry_stack_values,
mod_,
func_type_indices,
caller_locals,
)
(primary_count + secondary_count, secondary_ok)
}
None => (primary_count, true)
}
}
_ => (0, true)
}
}
///|
fn dae_collect_direct_struct_default_actuals_instrs(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
params : Array[@lib.ValType],
local_use : DaeLocalUse,
candidates : Array[@lib.ValType?],
rejected : Array[Bool],
entry_stack_values : Int32,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
caller_locals : @lib.FunctionLocals?,
) -> (Int32, Bool) {
let mut calls = 0
let mut idx = 0
while idx < instrs.length() {
if dae_instr_is_unreachable_root(instrs[idx]) {
break
}
match
dae_target_callsite_evidence(
instrs[idx],
callee_abs,
DaeTargetDirectCallOnly,
) {
Some(_) => {
let evidence = match
dae_callsite_value_slice_evidence(
instrs,
idx,
params.length(),
entry_stack_values,
mod_,
func_type_indices,
) {
Some(result) => result
None => return (calls, false)
}
let mut p = 0
while p < params.length() {
if !rejected[p] {
match params[p] {
@lib.RefTypeValType(_) =>
match
dae_direct_gc_actual_candidate_type_for_value_slice(
instrs,
evidence.slices[p],
mod_,
params[p],
func_type_indices,
caller_locals,
) {
Some(candidate) =>
match candidates[p] {
Some(prev) =>
match
dae_lub_gc_candidate_ref_type(
mod_,
prev,
candidate,
params[p],
) {
Some(lub) => candidates[p] = Some(lub)
None => {
candidates[p] = None
rejected[p] = true
}
}
None => candidates[p] = Some(candidate)
}
None => {
candidates[p] = None
rejected[p] = true
}
}
_ => ()
}
}
p += 1
}
calls += 1
}
None => {
let (nested_calls, ok) = dae_collect_direct_struct_default_actuals_instr(
instrs[idx],
callee_abs,
params,
local_use,
candidates,
rejected,
mod_,
func_type_indices,
caller_locals,
)
calls += nested_calls
if !ok {
return (calls, false)
}
}
}
idx += 1
}
(calls, true)
}
///|
fn dae_exact_struct_default_ref_type(type_idx : @lib.TypeIdx) -> @lib.ValType {
@lib.ValType::ref_type(
@lib.RefType::new(false, @lib.HeapType::new(type_idx), exact=true),
)
}
///|
fn dae_gc_candidate_ref_get_desc_type(
mod_ : @lib.Module,
type_idx : @lib.TypeIdx,
exact : Bool,
old_type : @lib.ValType,
) -> @lib.ValType? {
let type_sec = match mod_.type_sec {
Some(type_sec) => type_sec
None => return None
}
let descriptor_type = match dae_type_sec_subtype(type_sec, type_idx) {
Some(subtype) =>
match subtype.metadata().descriptor {
Some(descriptor_type) => descriptor_type
None => return None
}
None => return None
}
dae_refined_gc_candidate_ref_type(
mod_,
old_type,
@lib.ValType::ref_type(
@lib.RefType::new(false, @lib.HeapType::new(descriptor_type), exact~),
),
)
}
///|
fn dae_gc_candidate_array_get_type(
mod_ : @lib.Module,
type_idx : @lib.TypeIdx,
old_type : @lib.ValType,
) -> @lib.ValType? {
let type_sec = match mod_.type_sec {
Some(type_sec) => type_sec
None => return None
}
let field = match dae_type_sec_subtype(type_sec, type_idx) {
Some(subtype) =>
match subtype.get_comptype() {
@lib.ArrayCompType(field) => field
_ => return None
}
None => return None
}
let @lib.FieldType(storage_type, _) = field
match storage_type {
@lib.ValTypeStorageType(vt) =>
dae_refined_gc_candidate_ref_type(mod_, old_type, vt)
_ => None
}
}
///|
fn dae_gc_candidate_struct_get_type(
mod_ : @lib.Module,
type_idx : @lib.TypeIdx,
field_idx : @lib.U32,
old_type : @lib.ValType,
) -> @lib.ValType? {
let type_sec = match mod_.type_sec {
Some(type_sec) => type_sec
None => return None
}
let fields = match dae_type_sec_subtype(type_sec, type_idx) {
Some(subtype) =>
match subtype.get_comptype() {
@lib.StructCompType(fields) => fields
_ => return None
}
None => return None
}
let @lib.U32(raw_field_idx) = field_idx
let slot = raw_field_idx.reinterpret_as_int()
if slot < 0 || slot >= fields.length() {
return None
}
let @lib.FieldType(storage_type, _) = fields[slot]
match storage_type {
@lib.ValTypeStorageType(vt) =>
dae_refined_gc_candidate_ref_type(mod_, old_type, vt)
_ => None
}
}
///|
fn dae_gc_candidate_table_get_type(
mod_ : @lib.Module,
idx : @lib.TableIdx,
old_type : @lib.ValType,
) -> @lib.ValType? {
let @lib.TableIdx(raw_slot) = idx
let slot = raw_slot.reinterpret_as_int()
let mut imported_tables = 0
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
match import_ {
@lib.Import(_, _, @lib.TableExternType(@lib.TableType(ref_type, _))) => {
if imported_tables == slot {
return dae_refined_gc_candidate_ref_type(
mod_,
old_type,
@lib.ValType::ref_type(ref_type),
)
}
imported_tables += 1
}
_ => ()
}
}
None => ()
}
let defined_slot = slot - imported_tables
if defined_slot < 0 {
return None
}
match mod_.table_sec {
Some(@lib.TableSec(tables)) if defined_slot < tables.length() => {
let @lib.Table(@lib.TableType(ref_type, _), _) = tables[defined_slot]
dae_refined_gc_candidate_ref_type(
mod_,
old_type,
@lib.ValType::ref_type(ref_type),
)
}
_ => None
}
}
///|
fn dae_gc_candidate_global_get_type(
mod_ : @lib.Module,
idx : @lib.GlobalIdx,
old_type : @lib.ValType,
) -> @lib.ValType? {
let slot = dae_global_idx_slot(idx)
let mut imported_globals = 0
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
match import_ {
@lib.Import(_, _, @lib.GlobalExternType(@lib.GlobalType(vt, _))) => {
if imported_globals == slot {
return dae_refined_gc_candidate_ref_type(mod_, old_type, vt)
}
imported_globals += 1
}
_ => ()
}
}
None => ()
}
let defined_slot = slot - imported_globals
if defined_slot < 0 {
return None
}
match mod_.global_sec {
Some(@lib.GlobalSec(globals)) if defined_slot < globals.length() => {
let @lib.Global(@lib.GlobalType(vt, _), _) = globals[defined_slot]
dae_refined_gc_candidate_ref_type(mod_, old_type, vt)
}
_ => None
}
}
///|
fn dae_gc_candidate_local_get_type(
mod_ : @lib.Module,
func_locals : @lib.FunctionLocals?,
idx : @lib.LocalIdx,
old_type : @lib.ValType,
) -> @lib.ValType? {
let candidate_type = match func_locals {
Some(locals) =>
match locals.local_type(idx) {
Some(vt) => vt
None => return None
}
None => return None
}
match (old_type, candidate_type) {
(
@lib.RefTypeValType(@lib.HeapTypeRefType(_, _, @lib.HeapType(old_idx))),
@lib.RefTypeValType(
@lib.HeapTypeRefType(_, _, @lib.HeapType(candidate_idx))
),
) =>
if dae_type_idx_is_subtype_of(mod_, candidate_idx, old_idx) {
Some(candidate_type)
} else {
None
}
(@lib.RefTypeValType(old_ref), @lib.RefTypeValType(candidate_ref)) => {
let module_ctx = @ir.hot_module_context_from_module(mod_)
if ls_ref_is_subtype(module_ctx, candidate_ref, old_ref) {
Some(candidate_type)
} else {
None
}
}
_ => None
}
}
///|
fn dae_single_result_control_pair_gc_evidence(
mod_ : @lib.Module,
then_body : Array[@lib.Instruction],
then_candidate : @lib.ValType,
then_rewrite : Array[@lib.Instruction]?,
else_body : Array[@lib.Instruction],
else_candidate : @lib.ValType,
else_rewrite : Array[@lib.Instruction]?,
old_type : @lib.ValType,
) -> DaeSingleResultControlPairGcEvidence? {
match
dae_lub_gc_candidate_ref_type(
mod_, then_candidate, else_candidate, old_type,
) {
Some(candidate) => {
let rewritten_then_body = match then_rewrite {
Some(body) => body
None => then_body
}
let rewritten_else_body = match else_rewrite {
Some(body) => body
None => else_body
}
Some({ candidate, rewritten_then_body, rewritten_else_body, })
}
None => None
}
}
///|
fn dae_single_result_control_pair_application_is_consistent(
application : DaeSingleResultControlPairApplicationEvidence,
evidence : DaeSingleResultControlPairGcEvidence,
result_is_void : Bool,
) -> Bool {
application.result_is_void == result_is_void &&
application.then_body_length == evidence.rewritten_then_body.length() &&
application.else_body_length == evidence.rewritten_else_body.length()
}
///|
fn dae_apply_single_result_control_pair_gc_evidence(
evidence : DaeSingleResultControlPairGcEvidence,
result_is_void : Bool,
annotation_type~ : @lib.ValType? = None,
) -> (@lib.Instruction, DaeSingleResultControlPairApplicationEvidence)? {
let block_type = if result_is_void {
@lib.BlockType::void_()
} else {
@lib.BlockType::val_type(annotation_type.unwrap_or(evidence.candidate))
}
let application = {
result_is_void,
then_body_length: evidence.rewritten_then_body.length(),
else_body_length: evidence.rewritten_else_body.length(),
}
if dae_single_result_control_pair_application_is_consistent(
application, evidence, result_is_void,
) {
Some(
(
@lib.Instruction::if_(
block_type,
evidence.rewritten_then_body,
Some(evidence.rewritten_else_body),
),
application,
),
)
} else {
None
}
}
///|
fn dae_single_result_control_pair_repair_evidence_is_consistent(
repair : DaeSingleResultControlPairRepairEvidence,
result_is_void : Bool,
) -> Bool {
dae_single_result_control_pair_application_is_consistent(
repair.application,
repair.pair,
result_is_void,
)
}
///|
fn dae_rewrite_single_result_null_body(
body : Array[@lib.Instruction],
candidate : @lib.ValType,
) -> Array[@lib.Instruction]? {
let ref_type = match candidate {
@lib.RefTypeValType(ref_type) => ref_type
_ => return None
}
if body.length() == 0 {
return None
}
let null_idx = if body[body.length() - 1] is @lib.RefNull(_) {
body.length() - 1
} else if body[body.length() - 1] is @lib.Return &&
body.length() >= 2 &&
body[body.length() - 2] is @lib.RefNull(_) {
body.length() - 2
} else {
return None
}
let rewritten = body.copy()
rewritten[null_idx] = @lib.Instruction::ref_null_type(ref_type)
Some(rewritten)
}
///|
fn dae_single_result_control_pair_repair_evidence(
mod_ : @lib.Module,
then_body : Array[@lib.Instruction],
else_body : Array[@lib.Instruction],
old_type : @lib.ValType,
result_is_void : Bool,
arm_candidate : (Array[@lib.Instruction]) -> (
@lib.ValType,
Array[@lib.Instruction]?,
)?,
) -> DaeSingleResultControlPairRepairEvidence? {
let then_result = arm_candidate(then_body)
let else_result = arm_candidate(else_body)
let pair = match (then_result, else_result) {
(Some((then_candidate, then_rewrite)), Some((else_candidate, else_rewrite))) =>
match
dae_single_result_control_pair_gc_evidence(
mod_, then_body, then_candidate, then_rewrite, else_body, else_candidate,
else_rewrite, old_type,
) {
Some(evidence) => evidence
None => return None
}
(Some((candidate, then_rewrite)), None) => {
let nullable = match
dae_nullable_gc_candidate_ref_type(candidate, old_type, mod_) {
Some(nullable) => nullable
None => return None
}
let rewritten_else_body = match
dae_rewrite_single_result_null_body(
else_body,
dae_gc_emitted_type(mod_, nullable),
) {
Some(body) => body
None => return None
}
{
candidate: nullable,
rewritten_then_body: match then_rewrite {
Some(body) => body
None => then_body
},
rewritten_else_body,
}
}
(None, Some((candidate, else_rewrite))) => {
let nullable = match
dae_nullable_gc_candidate_ref_type(candidate, old_type, mod_) {
Some(nullable) => nullable
None => return None
}
let rewritten_then_body = match
dae_rewrite_single_result_null_body(
then_body,
dae_gc_emitted_type(mod_, nullable),
) {
Some(body) => body
None => return None
}
{
candidate: nullable,
rewritten_then_body,
rewritten_else_body: match else_rewrite {
Some(body) => body
None => else_body
},
}
}
(None, None) => return None
}
let (rewritten, application) = match
dae_apply_single_result_control_pair_gc_evidence(
pair,
result_is_void,
annotation_type=Some(dae_gc_emitted_type(mod_, pair.candidate)),
) {
Some(result) => result
None => return None
}
let repair = { pair, application, rewritten, }
if dae_single_result_control_pair_repair_evidence_is_consistent(
repair, result_is_void,
) {
Some(repair)
} else {
None
}
}
///|
fn dae_single_body_control_shape_evidence(
instr : @lib.Instruction,
) -> DaeSingleBodyControlShapeEvidence? {
match instr {
@lib.Block(block_type, @lib.Expr(body)) =>
Some({ control_kind: 0, block_type, catches: [], body, })
@lib.Loop(block_type, @lib.Expr(body)) =>
Some({ control_kind: 1, block_type, catches: [], body, })
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
Some({ control_kind: 2, block_type, catches, body, })
_ => None
}
}
///|
fn dae_single_body_control_rewrite_evidence(
shape : DaeSingleBodyControlShapeEvidence,
replacement_block_type : @lib.BlockType,
rewritten_body_opt : Array[@lib.Instruction]?,
) -> DaeSingleBodyControlRewriteEvidence {
let (rewritten_body, body_changed) = match rewritten_body_opt {
Some(rewritten_body) => (rewritten_body, true)
None => (shape.body, false)
}
{ shape, replacement_block_type, rewritten_body, body_changed, }
}
///|
fn dae_apply_single_body_control_rewrite_evidence(
evidence : DaeSingleBodyControlRewriteEvidence,
) -> @lib.Instruction? {
let body = if evidence.body_changed {
evidence.rewritten_body
} else {
evidence.shape.body
}
let expr = @lib.Expr::new(body)
match evidence.shape.control_kind {
0 => Some(@lib.Instruction::block(evidence.replacement_block_type, expr))
1 => Some(@lib.Instruction::loop_(evidence.replacement_block_type, expr))
2 =>
Some(
@lib.Instruction::try_table(
evidence.replacement_block_type,
evidence.shape.catches,
expr,
),
)
_ => None
}
}
///|
fn dae_single_body_control_rewrite_changes_instruction(
evidence : DaeSingleBodyControlRewriteEvidence,
) -> Bool {
evidence.body_changed ||
evidence.shape.block_type != evidence.replacement_block_type
}
///|
fn dae_direct_gc_result_explicit_return_candidate_instr(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
instr : @lib.Instruction,
old_type : @lib.ValType,
) -> (@lib.ValType, @lib.Instruction?)? {
match instr {
@lib.Block(_, _) | @lib.Loop(_, _) | @lib.TryTable(_, _, _) => {
let shape = match dae_single_body_control_shape_evidence(instr) {
Some(shape) => shape
None => return None
}
match
dae_control_block_type_zero_input_evidence(
shape.block_type,
mod_.type_sec,
) {
Some(_) => ()
None => return None
}
match
dae_direct_gc_result_explicit_return_candidate_body(
mod_,
func_type_indices,
func_locals,
shape.body,
old_type,
) {
Some((candidate_type, rewritten_body_opt)) => {
let rewrite = dae_single_body_control_rewrite_evidence(
shape,
@lib.BlockType::void_(),
rewritten_body_opt,
)
let rewritten = if dae_single_body_control_rewrite_changes_instruction(
rewrite,
) {
dae_apply_single_body_control_rewrite_evidence(rewrite)
} else {
None
}
Some((candidate_type, rewritten))
}
None => None
}
}
@lib.If(block_type, then_body, Some(else_body)) =>
match
dae_control_block_type_zero_input_evidence(block_type, mod_.type_sec) {
Some(_) =>
match
dae_single_result_control_pair_repair_evidence(
mod_,
then_body,
else_body,
old_type,
true,
fn(body) {
dae_direct_gc_result_explicit_return_candidate_body(
mod_, func_type_indices, func_locals, body, old_type,
)
},
) {
Some(repair) =>
Some((repair.pair.candidate, Some(repair.rewritten)))
None => None
}
None => None
}
_ => None
}
}
///|
fn dae_direct_gc_result_explicit_return_candidate_body(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
body : Array[@lib.Instruction],
old_type : @lib.ValType,
) -> (@lib.ValType, Array[@lib.Instruction]?)? {
if body.length() == 0 {
return None
}
match body[body.length() - 1] {
@lib.Return => {
if body.length() < 2 {
return None
}
let value_end = body.length() - 1
let shape = match
dae_find_value_slice(
body,
value_end,
0,
mod_,
func_type_indices,
allow_stack_carried_void_effects=true,
) {
Some((shape, cursor, _)) if shape.start == cursor => shape
_ => return None
}
match
dae_gc_result_candidate_slice(
mod_,
func_type_indices,
func_locals,
body,
shape.start,
value_end,
old_type,
) {
Some((candidate_type, Some(rewritten_value))) => {
let rewritten = body.copy()
rewritten[value_end - 1] = rewritten_value
Some((candidate_type, Some(rewritten)))
}
Some((candidate_type, None)) => Some((candidate_type, None))
None => None
}
}
_ =>
dae_direct_gc_result_explicit_return_candidate_instr(
mod_,
func_type_indices,
func_locals,
body[body.length() - 1],
old_type,
).map(fn(result) {
let (candidate_type, rewritten_instr) = result
match rewritten_instr {
Some(instr) => {
let rewritten = body.copy()
rewritten[rewritten.length() - 1] = instr
(candidate_type, Some(rewritten))
}
None => (candidate_type, None)
}
})
}
}
///|
fn dae_single_result_control_gc_candidate_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
instr : @lib.Instruction,
old_type : @lib.ValType,
) -> DaeSingleResultControlGcCandidateEvidence? {
let shape = match dae_single_body_control_shape_evidence(instr) {
Some(shape) => shape
None => return None
}
match dae_control_single_result_block_type_evidence(shape.block_type, mod_) {
Some(_) => ()
None => return None
}
match
dae_direct_gc_result_candidate_body(
mod_,
func_type_indices,
func_locals,
shape.body,
old_type,
) {
Some((candidate, rewritten_body_opt)) => {
let rewrite = dae_single_body_control_rewrite_evidence(
shape,
@lib.BlockType::val_type(dae_gc_emitted_type(mod_, candidate)),
rewritten_body_opt,
)
Some({ candidate, rewrite, })
}
None => None
}
}
///|
fn dae_single_result_control_gc_candidate_instr_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
instr : @lib.Instruction,
old_type : @lib.ValType,
) -> (@lib.ValType, @lib.Instruction?)? {
let evidence = match
dae_single_result_control_gc_candidate_evidence(
mod_, func_type_indices, func_locals, instr, old_type,
) {
Some(evidence) => evidence
None => return None
}
match dae_apply_single_body_control_rewrite_evidence(evidence.rewrite) {
Some(rewritten) => Some((evidence.candidate, Some(rewritten)))
None => None
}
}
///|
fn dae_typed_call_gc_result_candidate(
mod_ : @lib.Module,
type_idx : @lib.TypeIdx,
old_type : @lib.ValType,
) -> @lib.ValType? {
let type_sec = match mod_.type_sec {
Some(type_sec) => type_sec
None => return None
}
let candidate = match dae_type_sec_func_type(type_sec, type_idx) {
Some(@lib.FuncType(_, [result])) => result
_ => return None
}
dae_refined_gc_candidate_ref_type(mod_, old_type, candidate)
}
///|
fn dae_legacy_catches_have_no_fallthrough(
catches : Array[@lib.LegacyCatch],
) -> Bool {
catches.all(fn(catch_) {
match catch_ {
@lib.LegacyCatch(_, @lib.Expr(body))
| @lib.LegacyCatchAll(@lib.Expr(body)) =>
dae_body_has_no_fallthrough(body)
}
})
}
///|
fn dae_direct_gc_result_candidate_instr(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
instr : @lib.Instruction,
old_type : @lib.ValType,
) -> (@lib.ValType, @lib.Instruction?)? {
match instr {
@lib.ContNew(type_idx)
| @lib.ContBind(_, type_idx)
| @lib.StructNew(type_idx)
| @lib.StructNewDefault(type_idx)
| @lib.StructNewDesc(type_idx)
| @lib.StructNewDefaultDesc(type_idx)
| @lib.ArrayNew(type_idx)
| @lib.ArrayNewDefault(type_idx)
| @lib.ArrayNewFixed(type_idx, _)
| @lib.ArrayNewData(type_idx, _)
| @lib.ArrayNewElem(type_idx, _) =>
Some((dae_exact_struct_default_ref_type(type_idx), None))
@lib.RefNull(ref_type) => {
let candidate = @lib.ValType::ref_type(ref_type)
match dae_refined_gc_candidate_ref_type(mod_, old_type, candidate) {
Some(_) => Some((candidate, None))
None => None
}
}
@lib.RefI31 => {
let candidate = @lib.ValType::ref_type(
@lib.RefType::new(false, @lib.HeapType::abs(@lib.AbsHeapType::i31())),
)
match dae_refined_gc_candidate_ref_type(mod_, old_type, candidate) {
Some(refined) => Some((refined, None))
None => None
}
}
@lib.StringConst(_)
| @lib.StringConcat
| @lib.StringAsWtf16
| @lib.StringViewWtf16Slice
| @lib.StringNewUtf8Array
| @lib.StringNewWtf16Array
| @lib.StringNewLossyUtf8Array
| @lib.StringNewWtf8Array => {
let candidate = @lib.ValType::stringref()
match dae_refined_gc_candidate_ref_type(mod_, old_type, candidate) {
Some(refined) => Some((refined, None))
None => None
}
}
@lib.TableGet(table_idx) =>
dae_gc_candidate_table_get_type(mod_, table_idx, old_type).map(fn(
candidate,
) {
(candidate, None)
})
@lib.ArrayGet(type_idx) | @lib.ArrayAtomicGet(_, type_idx) =>
dae_gc_candidate_array_get_type(mod_, type_idx, old_type).map(fn(
candidate,
) {
(candidate, None)
})
@lib.StructGet(type_idx, field_idx)
| @lib.StructAtomicGet(_, type_idx, field_idx) =>
dae_gc_candidate_struct_get_type(mod_, type_idx, field_idx, old_type).map(fn(
candidate,
) {
(candidate, None)
},
)
@lib.GlobalGet(idx) =>
dae_gc_candidate_global_get_type(mod_, idx, old_type).map(fn(candidate) {
(candidate, None)
})
@lib.LocalGet(idx) | @lib.LocalTee(idx) =>
dae_gc_candidate_local_get_type(mod_, func_locals, idx, old_type).map(fn(
candidate_type,
) {
(candidate_type, None)
})
@lib.RefCast(nullable, exact, heap_type)
| @lib.RefCastDescEq(nullable, exact, heap_type) => {
let candidate = @lib.ValType::ref_type(
@lib.RefType::new(nullable, heap_type, exact~),
)
match dae_refined_gc_candidate_ref_type(mod_, old_type, candidate) {
Some(refined) => Some((refined, None))
None => None
}
}
@lib.RefFunc(func_idx) => {
let slot = dae_func_idx_slot(func_idx)
if slot < 0 || slot >= func_type_indices.length() {
None
} else {
let candidate = @lib.ValType::ref_type(
@lib.RefType::new(
false,
@lib.HeapType::new(func_type_indices[slot]),
exact=true,
),
)
match dae_refined_gc_candidate_ref_type(mod_, old_type, candidate) {
Some(refined) => Some((refined, None))
None => None
}
}
}
@lib.Call(func_idx) | @lib.ReturnCall(func_idx) =>
match
dae_direct_call_gc_result_refinement_evidence(
mod_, func_type_indices, func_idx, old_type,
) {
Some(evidence) =>
match evidence.refined_candidate {
Some(refined_type) => Some((refined_type, None))
None => None
}
None => None
}
@lib.CallIndirect(type_idx, _)
| @lib.ReturnCallIndirect(type_idx, _)
| @lib.CallRef(type_idx)
| @lib.ReturnCallRef(type_idx) =>
dae_typed_call_gc_result_candidate(mod_, type_idx, old_type).map(fn(
candidate,
) {
(candidate, None)
})
@lib.Try(block_type, @lib.Expr(body), catches, delegate) => {
if !dae_legacy_catches_have_no_fallthrough(catches) ||
dae_control_single_result_block_type_evidence(block_type, mod_) is None {
return None
}
match
dae_direct_gc_result_candidate_body(
mod_, func_type_indices, func_locals, body, old_type,
) {
Some((candidate, rewritten_body_opt)) => {
let rewritten_body = match rewritten_body_opt {
Some(rewritten_body) => rewritten_body
None => body
}
Some(
(
candidate,
Some(
@lib.Instruction::try_(
@lib.BlockType::val_type(dae_gc_emitted_type(mod_, candidate)),
@lib.Expr::new(rewritten_body),
catches,
delegate~,
),
),
),
)
}
None => None
}
}
@lib.Block(_, _) | @lib.Loop(_, _) | @lib.TryTable(_, _, _) =>
dae_single_result_control_gc_candidate_instr_evidence(
mod_, func_type_indices, func_locals, instr, old_type,
)
@lib.If(block_type, then_body, Some(else_body)) =>
match dae_control_single_result_block_type_evidence(block_type, mod_) {
Some(_) =>
match
dae_single_result_control_pair_repair_evidence(
mod_,
then_body,
else_body,
old_type,
false,
fn(body) {
dae_direct_gc_result_candidate_body(
mod_, func_type_indices, func_locals, body, old_type,
)
},
) {
Some(repair) =>
Some((repair.pair.candidate, Some(repair.rewritten)))
None => None
}
_ => None
}
_ => None
}
}
///|
fn dae_gc_result_slot_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
instr : @lib.Instruction,
old_type : @lib.ValType,
require_refined~ : Bool = false,
) -> DaeGcTypeEvidence? {
let (candidate, rewrite) = match
dae_direct_gc_result_candidate_instr(
mod_, func_type_indices, func_locals, instr, old_type,
) {
Some(result) => result
None => return None
}
let refined_candidate = dae_refined_gc_candidate_ref_type(
mod_, old_type, candidate,
)
if require_refined && refined_candidate.is_none() {
return None
}
Some({ candidate, refined_candidate, rewrite, })
}
///|
fn dae_indexed_instruction_rewrite_application_is_consistent(
application : DaeIndexedInstructionRewriteApplicationEvidence,
body : Array[@lib.Instruction],
indices : Array[Int32],
rewrites : Array[@lib.Instruction?],
) -> Bool {
if application.target_count != indices.length() ||
indices.length() != rewrites.length() ||
application.body_changed != (application.rewrite_count > 0) {
return false
}
let mut expected_rewrites = 0
let mut idx = 0
while idx < indices.length() {
let target = indices[idx]
if target < 0 || target >= body.length() {
return false
}
let mut previous = 0
while previous < idx {
if indices[previous] == target {
return false
}
previous += 1
}
match rewrites[idx] {
Some(_) => expected_rewrites += 1
None => ()
}
idx += 1
}
application.rewrite_count == expected_rewrites
}
///|
fn dae_apply_indexed_instruction_rewrites_to_body(
body : Array[@lib.Instruction],
indices : Array[Int32],
rewrites : Array[@lib.Instruction?],
) -> (Array[@lib.Instruction], DaeIndexedInstructionRewriteApplicationEvidence)? {
if indices.length() != rewrites.length() {
return None
}
let rewritten = body.copy()
let mut rewrite_count = 0
let mut idx = 0
while idx < indices.length() {
let target = indices[idx]
if target < 0 || target >= body.length() {
return None
}
let mut previous = 0
while previous < idx {
if indices[previous] == target {
return None
}
previous += 1
}
match rewrites[idx] {
Some(instr) => {
rewritten[target] = instr
rewrite_count += 1
}
None => ()
}
idx += 1
}
let application = {
target_count: indices.length(),
rewrite_count,
body_changed: rewrite_count > 0,
}
if dae_indexed_instruction_rewrite_application_is_consistent(
application, body, indices, rewrites,
) {
Some((if application.body_changed { rewritten } else { body }, application))
} else {
None
}
}
///|
fn dae_apply_first_instruction_rewrite_to_body(
body : Array[@lib.Instruction],
rewrite_instr : (@lib.Instruction) -> @lib.Instruction?,
) -> (Array[@lib.Instruction], DaeIndexedInstructionRewriteApplicationEvidence)? {
let mut idx = 0
while idx < body.length() {
match rewrite_instr(body[idx]) {
Some(rewritten_instr) =>
return dae_apply_indexed_instruction_rewrites_to_body(body, [idx], [
Some(rewritten_instr),
])
None => ()
}
idx += 1
}
None
}
///|
fn dae_try_rewrite_first_control_child_body(
instr : @lib.Instruction,
rewrite_body : (Array[@lib.Instruction]) -> Array[@lib.Instruction]?,
) -> @lib.Instruction? {
match instr {
@lib.Block(block_type, @lib.Expr(body)) =>
match rewrite_body(body) {
Some(rewritten_body) =>
Some(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body)),
)
None => None
}
@lib.Loop(block_type, @lib.Expr(body)) =>
match rewrite_body(body) {
Some(rewritten_body) =>
Some(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body)),
)
None => None
}
@lib.If(block_type, then_body, Some(else_body)) =>
match rewrite_body(then_body) {
Some(rewritten_then) =>
Some(
@lib.Instruction::if_(block_type, rewritten_then, Some(else_body)),
)
None =>
match rewrite_body(else_body) {
Some(rewritten_else) =>
Some(
@lib.Instruction::if_(
block_type,
then_body,
Some(rewritten_else),
),
)
None => None
}
}
@lib.If(block_type, then_body, None) =>
match rewrite_body(then_body) {
Some(rewritten_then) =>
Some(@lib.Instruction::if_(block_type, rewritten_then, None))
None => None
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
match rewrite_body(body) {
Some(rewritten_body) =>
Some(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
),
)
None => None
}
_ => None
}
}
///|
fn dae_recursive_body_rewrite_application_is_consistent(
application : DaeRecursiveBodyRewriteApplicationEvidence,
body : Array[@lib.Instruction],
rewritten : Array[@lib.Instruction],
) -> Bool {
if application.body_changed != (rewritten != body) {
return false
}
if application.applied_to_current_body {
application.target_count == 0 &&
application.rewrite_count == (if application.body_changed { 1 } else { 0 })
} else {
application.target_count == 1 &&
application.rewrite_count == 1 &&
application.body_changed
}
}
///|
fn dae_apply_first_recursive_instruction_body_rewrite(
body : Array[@lib.Instruction],
rewrite_instr : (@lib.Instruction) -> @lib.Instruction?,
) -> (Array[@lib.Instruction], DaeRecursiveBodyRewriteApplicationEvidence)? {
match dae_apply_first_instruction_rewrite_to_body(body, rewrite_instr) {
Some((rewritten, indexed)) => {
let application = {
applied_to_current_body: false,
target_count: indexed.target_count,
rewrite_count: indexed.rewrite_count,
body_changed: indexed.body_changed,
}
if dae_recursive_body_rewrite_application_is_consistent(
application, body, rewritten,
) {
Some((rewritten, application))
} else {
None
}
}
None => None
}
}
///|
fn dae_apply_first_recursive_instruction_body_rewrite_to_body(
body : Array[@lib.Instruction],
rewrite_instr : (@lib.Instruction) -> @lib.Instruction?,
) -> Array[@lib.Instruction]? {
match
dae_apply_first_recursive_instruction_body_rewrite(body, rewrite_instr) {
Some((rewritten, _)) => Some(rewritten)
None => None
}
}
///|
fn dae_apply_first_recursive_control_child_body_rewrite(
body : Array[@lib.Instruction],
rewrite_child_body : (Array[@lib.Instruction]) -> Array[@lib.Instruction]?,
) -> (Array[@lib.Instruction], DaeRecursiveBodyRewriteApplicationEvidence)? {
let rewrite_instr = fn(instr : @lib.Instruction) -> @lib.Instruction? {
dae_try_rewrite_first_control_child_body(instr, rewrite_child_body)
}
dae_apply_first_recursive_instruction_body_rewrite(body, rewrite_instr)
}
///|
fn dae_apply_first_recursive_body_rewrite(
body : Array[@lib.Instruction],
rewrite_current_body : (Array[@lib.Instruction]) -> Array[@lib.Instruction]?,
rewrite_child_body : (Array[@lib.Instruction]) -> Array[@lib.Instruction]?,
) -> (Array[@lib.Instruction], DaeRecursiveBodyRewriteApplicationEvidence)? {
match rewrite_current_body(body) {
Some(rewritten) => {
let body_changed = rewritten != body
let application = {
applied_to_current_body: true,
target_count: 0,
rewrite_count: if body_changed {
1
} else {
0
},
body_changed,
}
if dae_recursive_body_rewrite_application_is_consistent(
application, body, rewritten,
) {
return Some((rewritten, application))
}
return None
}
None => ()
}
dae_apply_first_recursive_control_child_body_rewrite(body, rewrite_child_body)
}
///|
fn dae_apply_first_current_or_control_child_body_rewrite(
body : Array[@lib.Instruction],
rewrite_current_body : (Array[@lib.Instruction]) -> Array[@lib.Instruction]?,
rewrite_child_body : (Array[@lib.Instruction]) -> Array[@lib.Instruction]?,
) -> Array[@lib.Instruction]? {
match
dae_apply_first_recursive_body_rewrite(
body, rewrite_current_body, rewrite_child_body,
) {
Some((rewritten, _)) => Some(rewritten)
None => None
}
}
///|
fn dae_body_rewrite_fixpoint_evidence_is_consistent(
evidence : DaeBodyRewriteFixpointEvidence,
body : Array[@lib.Instruction],
rewritten : Array[@lib.Instruction],
) -> Bool {
evidence.max_iters > 0 &&
evidence.iteration_count > 0 &&
evidence.iteration_count <= evidence.max_iters &&
evidence.reached_limit == (evidence.iteration_count == evidence.max_iters) &&
evidence.body_changed == (rewritten != body)
}
///|
fn dae_apply_body_rewrite_until_stable(
body : Array[@lib.Instruction],
rewrite_once : (Array[@lib.Instruction]) -> Array[@lib.Instruction]?,
max_iters : Int32,
) -> (Array[@lib.Instruction], DaeBodyRewriteFixpointEvidence)? {
if max_iters <= 0 {
return None
}
let mut current = body
let mut iteration_count = 0
while iteration_count < max_iters {
match rewrite_once(current) {
Some(next) => {
if next == current {
break
}
current = next
iteration_count += 1
continue
}
None => break
}
}
if iteration_count == 0 {
return None
}
let evidence = {
max_iters,
iteration_count,
reached_limit: iteration_count == max_iters,
body_changed: current != body,
}
if dae_body_rewrite_fixpoint_evidence_is_consistent(evidence, body, current) {
Some((current, evidence))
} else {
None
}
}
///|
fn dae_apply_body_rewrite_until_stable_or_original(
body : Array[@lib.Instruction],
rewrite_once : (Array[@lib.Instruction]) -> Array[@lib.Instruction]?,
max_iters : Int32,
) -> Array[@lib.Instruction] {
match dae_apply_body_rewrite_until_stable(body, rewrite_once, max_iters) {
Some((rewritten, _)) => rewritten
None => body
}
}
///|
fn dae_result_rewrite_application_is_consistent(
application : DaeResultRewriteApplicationEvidence,
body : Array[@lib.Instruction],
start : Int32,
rewrites : Array[@lib.Instruction?],
active~ : Array[Bool]? = None,
) -> Bool {
if application.start != start ||
application.result_count != rewrites.length() ||
application.body_changed != (application.rewrite_count > 0) ||
start < 0 ||
start + rewrites.length() > body.length() {
return false
}
match active {
Some(mask) => mask.length() == rewrites.length()
None => true
}
}
///|
fn dae_apply_result_rewrites_to_body(
body : Array[@lib.Instruction],
start : Int32,
rewrites : Array[@lib.Instruction?],
active~ : Array[Bool]? = None,
) -> (Array[@lib.Instruction], DaeResultRewriteApplicationEvidence)? {
if start < 0 || start + rewrites.length() > body.length() {
return None
}
match active {
Some(mask) => if mask.length() != rewrites.length() { return None }
None => ()
}
let indices = []
let effective_rewrites : Array[@lib.Instruction?] = []
let mut result_idx = 0
while result_idx < rewrites.length() {
indices.push(start + result_idx)
let slot_active = match active {
Some(mask) => mask[result_idx]
None => true
}
effective_rewrites.push(
if slot_active {
rewrites[result_idx]
} else {
None
},
)
result_idx += 1
}
let (rewritten, shared_application) = match
dae_apply_indexed_instruction_rewrites_to_body(
body, indices, effective_rewrites,
) {
Some(result) => result
None => return None
}
let application = {
start,
result_count: rewrites.length(),
rewrite_count: shared_application.rewrite_count,
body_changed: shared_application.body_changed,
}
if dae_result_rewrite_application_is_consistent(
application,
body,
start,
rewrites,
active~,
) {
Some((if application.body_changed { rewritten } else { body }, application))
} else {
None
}
}
///|
fn dae_select_value_rewrite_application_is_consistent(
application : DaeSelectValueRewriteApplicationEvidence,
body : Array[@lib.Instruction],
left_idx : Int32,
right_idx : Int32,
select_idx : Int32,
left_rewrite : @lib.Instruction?,
right_rewrite : @lib.Instruction?,
select_rewrite : @lib.Instruction?,
) -> Bool {
if application.left_idx != left_idx ||
application.right_idx != right_idx ||
application.select_idx != select_idx ||
application.body_changed != (application.rewrite_count > 0) ||
left_idx < 0 ||
left_idx >= right_idx ||
right_idx >= select_idx ||
select_idx >= body.length() {
return false
}
match body[select_idx] {
@lib.Select(_) => ()
_ => return false
}
let expected_rewrites = (if left_rewrite is Some(_) { 1 } else { 0 }) +
(if right_rewrite is Some(_) { 1 } else { 0 }) +
(if select_rewrite is Some(_) { 1 } else { 0 })
application.rewrite_count == expected_rewrites
}
///|
fn dae_apply_select_value_rewrites_to_body(
body : Array[@lib.Instruction],
left_idx : Int32,
right_idx : Int32,
select_idx : Int32,
left_rewrite : @lib.Instruction?,
right_rewrite : @lib.Instruction?,
select_rewrite : @lib.Instruction?,
) -> (Array[@lib.Instruction], DaeSelectValueRewriteApplicationEvidence)? {
if left_idx < 0 ||
left_idx >= right_idx ||
right_idx >= select_idx ||
select_idx >= body.length() {
return None
}
match body[select_idx] {
@lib.Select(_) => ()
_ => return None
}
let rewrites : Array[@lib.Instruction?] = [
left_rewrite, right_rewrite, select_rewrite,
]
let (rewritten, shared_application) = match
dae_apply_indexed_instruction_rewrites_to_body(
body,
[left_idx, right_idx, select_idx],
rewrites,
) {
Some(result) => result
None => return None
}
let application = {
left_idx,
right_idx,
select_idx,
rewrite_count: shared_application.rewrite_count,
body_changed: shared_application.body_changed,
}
if dae_select_value_rewrite_application_is_consistent(
application, body, left_idx, right_idx, select_idx, left_rewrite, right_rewrite,
select_rewrite,
) {
Some((if application.body_changed { rewritten } else { body }, application))
} else {
None
}
}
///|
fn dae_control_result_slice_gc_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
result_slice : DaeControlResultSlice,
results : Array[@lib.ValType],
require_refined~ : Bool = false,
) -> DaeControlResultGcEvidence? {
if results.length() != result_slice.result_count ||
!dae_control_result_slice_is_consistent(result_slice) {
return None
}
let candidates : Array[@lib.ValType?] = Array::make(results.length(), None)
let refined_candidates : Array[@lib.ValType?] = Array::make(
results.length(),
None,
)
let rewrites : Array[@lib.Instruction?] = Array::make(results.length(), None)
let mut saw_candidate = false
let mut result_idx = 0
while result_idx < results.length() {
let instr_idx = result_slice.slice.start + result_idx
match
dae_gc_result_slot_evidence(
mod_,
func_type_indices,
func_locals,
result_slice.body[instr_idx],
results[result_idx],
require_refined~,
) {
Some(evidence) => {
candidates[result_idx] = Some(evidence.candidate)
refined_candidates[result_idx] = evidence.refined_candidate
saw_candidate = true
rewrites[result_idx] = evidence.rewrite
}
None => ()
}
result_idx += 1
}
if saw_candidate {
match
dae_apply_result_rewrites_to_body(
result_slice.body,
result_slice.slice.start,
rewrites,
) {
Some((rewritten_body, application)) =>
Some({
candidates,
refined_candidates,
rewrites,
body: result_slice.body,
result_start: result_slice.slice.start,
rewritten_body,
body_changed: application.body_changed,
saw_candidate,
})
None => None
}
} else {
None
}
}
///|
fn dae_control_result_pair_gc_evidence(
mod_ : @lib.Module,
then_evidence : DaeControlResultGcEvidence,
else_evidence : DaeControlResultGcEvidence,
results : Array[@lib.ValType],
) -> DaeControlResultPairGcEvidence? {
if then_evidence.candidates.length() != results.length() ||
else_evidence.candidates.length() != results.length() ||
then_evidence.rewrites.length() != results.length() ||
else_evidence.rewrites.length() != results.length() ||
then_evidence.result_start < 0 ||
else_evidence.result_start < 0 ||
then_evidence.result_start + results.length() > then_evidence.body.length() ||
else_evidence.result_start + results.length() > else_evidence.body.length() {
return None
}
let refined_results = results.copy()
let refined_mask = Array::make(results.length(), false)
let mut saw_refinement = false
let mut result_idx = 0
while result_idx < results.length() {
match
(
then_evidence.candidates[result_idx],
else_evidence.candidates[result_idx],
) {
(Some(then_candidate), Some(else_candidate)) =>
match
dae_lub_gc_candidate_ref_type(
mod_,
then_candidate,
else_candidate,
results[result_idx],
) {
Some(lub) =>
match
dae_refined_gc_candidate_ref_type(mod_, results[result_idx], lub) {
Some(refined) => {
refined_results[result_idx] = refined
refined_mask[result_idx] = true
saw_refinement = true
}
None => ()
}
None => ()
}
_ => ()
}
result_idx += 1
}
if saw_refinement {
let (rewritten_then_body, then_application) = match
dae_apply_result_rewrites_to_body(
then_evidence.body,
then_evidence.result_start,
then_evidence.rewrites,
active=Some(refined_mask),
) {
Some(result) => result
None => return None
}
let (rewritten_else_body, else_application) = match
dae_apply_result_rewrites_to_body(
else_evidence.body,
else_evidence.result_start,
else_evidence.rewrites,
active=Some(refined_mask),
) {
Some(result) => result
None => return None
}
Some({
refined_results,
rewritten_then_body,
rewritten_else_body,
then_body_changed: then_application.body_changed,
else_body_changed: else_application.body_changed,
saw_refinement,
})
} else {
None
}
}
///|
fn dae_optional_refined_result_slots_application_is_consistent(
application : DaeOptionalRefinedResultSlotsApplicationEvidence,
refined_slots : Array[@lib.ValType?],
next_results : Array[@lib.ValType],
refined_results_length : Int32,
) -> Bool {
if application.result_count != refined_slots.length() ||
application.next_results_length != next_results.length() ||
application.refined_results_length != refined_results_length ||
application.result_count == 0 ||
application.next_results_length != application.result_count ||
application.applied_count <= 0 {
return false
}
let mut expected_applied = 0
for slot in refined_slots {
if slot is Some(_) {
expected_applied += 1
}
}
application.applied_count == expected_applied
}
///|
fn dae_apply_optional_refined_result_slots(
refined_slots : Array[@lib.ValType?],
next_results : Array[@lib.ValType],
refined_results~ : Array[@lib.ValType]? = None,
) -> DaeOptionalRefinedResultSlotsApplicationEvidence? {
let result_count = refined_slots.length()
if result_count == 0 || next_results.length() != result_count {
return None
}
let refined_results_length = match refined_results {
Some(refined) => {
if refined.length() != result_count {
return None
}
refined.length()
}
None => 0
}
let mut applied_count = 0
for slot in refined_slots {
if slot is Some(_) {
applied_count += 1
}
}
if applied_count == 0 {
return None
}
let mut result_idx = 0
while result_idx < result_count {
match refined_slots[result_idx] {
Some(refined) => {
next_results[result_idx] = refined
match refined_results {
Some(refined_results) => refined_results[result_idx] = refined
None => ()
}
}
None => ()
}
result_idx += 1
}
let application = {
result_count,
applied_count,
next_results_length: next_results.length(),
refined_results_length,
}
if dae_optional_refined_result_slots_application_is_consistent(
application, refined_slots, next_results, refined_results_length,
) {
Some(application)
} else {
None
}
}
///|
fn dae_control_result_pair_refinement_application_is_consistent(
application : DaeControlResultPairRefinementApplicationEvidence,
pair : DaeControlResultPairGcEvidence,
next_results : Array[@lib.ValType],
refined_results_length : Int32,
) -> Bool {
application.result_count == pair.refined_results.length() &&
application.next_results_length == next_results.length() &&
application.refined_results_length == refined_results_length &&
application.saw_refinement == pair.saw_refinement
}
///|
fn dae_apply_control_result_pair_refinements(
pair : DaeControlResultPairGcEvidence,
next_results : Array[@lib.ValType],
refined_results~ : Array[@lib.ValType]? = None,
) -> DaeControlResultPairRefinementApplicationEvidence? {
let result_count = pair.refined_results.length()
if result_count == 0 || next_results.length() != result_count {
return None
}
let refined_results_length = match refined_results {
Some(refined) => {
if refined.length() != result_count {
return None
}
refined.length()
}
None => 0
}
let refined_slots : Array[@lib.ValType?] = []
for refined in pair.refined_results {
refined_slots.push(Some(refined))
}
let shared_application = match
dae_apply_optional_refined_result_slots(
refined_slots,
next_results,
refined_results~,
) {
Some(application) => application
None => return None
}
let application = {
result_count: shared_application.result_count,
next_results_length: shared_application.next_results_length,
refined_results_length: shared_application.refined_results_length,
saw_refinement: pair.saw_refinement,
}
if dae_control_result_pair_refinement_application_is_consistent(
application, pair, next_results, refined_results_length,
) {
Some(application)
} else {
None
}
}
///|
fn dae_control_result_single_refinement_application_is_consistent(
application : DaeControlResultSingleRefinementApplicationEvidence,
evidence : DaeControlResultGcEvidence,
next_results : Array[@lib.ValType],
refined_results_length : Int32,
) -> Bool {
application.result_count == evidence.refined_candidates.length() &&
application.next_results_length == next_results.length() &&
application.refined_results_length == refined_results_length &&
application.body_changed == evidence.body_changed &&
application.saw_refinement
}
///|
fn dae_apply_control_result_single_refinements(
evidence : DaeControlResultGcEvidence,
next_results : Array[@lib.ValType],
refined_results~ : Array[@lib.ValType]? = None,
) -> DaeControlResultSingleRefinementApplicationEvidence? {
let result_count = evidence.refined_candidates.length()
if result_count == 0 || next_results.length() != result_count {
return None
}
if evidence.candidates.length() != result_count ||
evidence.rewrites.length() != result_count {
return None
}
let refined_results_length = match refined_results {
Some(refined) => {
if refined.length() != result_count {
return None
}
refined.length()
}
None => 0
}
let shared_application = match
dae_apply_optional_refined_result_slots(
evidence.refined_candidates,
next_results,
refined_results~,
) {
Some(application) => application
None => return None
}
let application = {
result_count: shared_application.result_count,
next_results_length: shared_application.next_results_length,
refined_results_length: shared_application.refined_results_length,
body_changed: evidence.body_changed,
saw_refinement: shared_application.applied_count > 0,
}
if dae_control_result_single_refinement_application_is_consistent(
application, evidence, next_results, refined_results_length,
) {
Some(application)
} else {
None
}
}
///|
fn dae_multi_result_if_pair_gc_refinement_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
then_body : Array[@lib.Instruction],
else_body : Array[@lib.Instruction],
results : Array[@lib.ValType],
require_terminal_return~ : Bool = false,
) -> DaeMultiResultIfPairGcRefinementEvidence? {
if results.length() == 0 {
return None
}
let then_slice = match
dae_find_multi_result_control_slice(
mod_,
then_body,
results.length(),
require_terminal_return~,
) {
Some(slice) => slice
None => return None
}
let else_slice = match
dae_find_multi_result_control_slice(
mod_,
else_body,
results.length(),
require_terminal_return~,
) {
Some(slice) => slice
None => return None
}
let then_evidence = match
dae_control_result_slice_gc_evidence(
mod_, func_type_indices, func_locals, then_slice, results,
) {
Some(evidence) => evidence
None => return None
}
let else_evidence = match
dae_control_result_slice_gc_evidence(
mod_, func_type_indices, func_locals, else_slice, results,
) {
Some(evidence) => evidence
None => return None
}
let pair = match
dae_control_result_pair_gc_evidence(
mod_, then_evidence, else_evidence, results,
) {
Some(evidence) => evidence
None => return None
}
if !pair.saw_refinement {
return None
}
let if_body_changed = then_slice.flattened ||
else_slice.flattened ||
pair.then_body_changed ||
pair.else_body_changed
Some({ then_slice, else_slice, pair, if_body_changed, })
}
///|
fn dae_indirect_call_gc_result_candidate_signature(
mod_ : @lib.Module,
type_idx : @lib.TypeIdx,
old_type : @lib.ValType,
) -> (Int32, @lib.ValType)? {
match mod_.type_sec {
Some(type_sec) =>
match dae_type_sec_func_type(type_sec, type_idx) {
Some(@lib.FuncType(params, [candidate_type])) =>
match
dae_refined_gc_candidate_ref_type(mod_, old_type, candidate_type) {
Some(refined_type) => Some((params.length(), refined_type))
None => None
}
_ => None
}
None => None
}
}
///|
fn dae_indirect_call_gc_result_slice_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
body : Array[@lib.Instruction],
start : Int32,
argument_end : Int32,
stack_value_end : Int32,
type_idx : @lib.TypeIdx,
old_type : @lib.ValType,
) -> DaeIndirectGcResultSliceEvidence? {
if start < 0 ||
argument_end < start ||
stack_value_end < argument_end ||
stack_value_end > body.length() {
return None
}
let (param_count, candidate_type) = match
dae_indirect_call_gc_result_candidate_signature(mod_, type_idx, old_type) {
Some(result) => result
None => return None
}
let (target_shape, target_cursor, _) = match
dae_find_value_slice(
body,
stack_value_end,
0,
mod_,
func_type_indices,
allow_stack_carried_void_effects=true,
) {
Some(evidence) => evidence
None => return None
}
if target_shape.start != argument_end ||
target_cursor != argument_end ||
dae_operand_disposition_from_call_shape(body, target_shape).has_branch {
return None
}
let (argument_slices, argument_cursor) = match
dae_collect_callsite_value_slices(
body,
argument_end,
param_count,
0,
mod_,
func_type_indices,
allow_stack_carried_void_effects=true,
) {
Some(evidence) => evidence
None => return None
}
if argument_cursor != start ||
argument_slices.any(fn(slice) { slice.has_branch }) {
return None
}
Some({
candidate: candidate_type,
param_count,
stack_value_count: param_count + 1,
})
}
///|
fn dae_call_ref_gc_result_candidate_slice(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
body : Array[@lib.Instruction],
start : Int32,
end : Int32,
old_type : @lib.ValType,
) -> (@lib.ValType, @lib.Instruction?)? {
if start + 2 > end {
return None
}
match body[end - 1] {
@lib.CallRef(type_idx) => {
let target_start = match
dae_find_value_slice(
body,
end - 1,
0,
mod_,
func_type_indices,
allow_stack_carried_void_effects=true,
) {
Some((shape, _, _)) => shape.start
None => return None
}
match
dae_indirect_call_gc_result_slice_evidence(
mod_,
func_type_indices,
body,
start,
target_start,
end - 1,
type_idx,
old_type,
) {
Some(evidence) => {
if evidence.stack_value_count != evidence.param_count + 1 {
return None
}
Some((evidence.candidate, None))
}
None => None
}
}
_ => None
}
}
///|
fn dae_call_indirect_gc_result_candidate_slice(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
body : Array[@lib.Instruction],
start : Int32,
end : Int32,
old_type : @lib.ValType,
) -> (@lib.ValType, @lib.Instruction?)? {
if start + 2 > end {
return None
}
match body[end - 1] {
@lib.CallIndirect(type_idx, _) => {
let table_start = match
dae_find_value_slice(
body,
end - 1,
0,
mod_,
func_type_indices,
allow_stack_carried_void_effects=true,
) {
Some((shape, _, _)) => shape.start
None => return None
}
match
dae_indirect_call_gc_result_slice_evidence(
mod_,
func_type_indices,
body,
start,
table_start,
end - 1,
type_idx,
old_type,
) {
Some(evidence) => {
if evidence.stack_value_count != evidence.param_count + 1 {
return None
}
Some((evidence.candidate, None))
}
None => None
}
}
_ => None
}
}
///|
fn dae_gc_result_candidate_slice(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
body : Array[@lib.Instruction],
start : Int32,
end : Int32,
old_type : @lib.ValType,
) -> (@lib.ValType, @lib.Instruction?)? {
if start < 0 || end > body.length() || start >= end {
return None
}
if start + 1 == end {
return dae_direct_gc_result_candidate_instr(
mod_,
func_type_indices,
func_locals,
body[start],
old_type,
)
}
match body[end - 1] {
@lib.RefGetDesc(type_idx) => {
let input_old_type = @lib.ValType::ref_type(
@lib.RefType::new(true, @lib.HeapType::new(type_idx)),
)
match
dae_gc_result_candidate_slice(
mod_,
func_type_indices,
func_locals,
body,
start,
end - 1,
input_old_type,
) {
Some((@lib.RefTypeValType(ref_type), None)) =>
return dae_gc_candidate_ref_get_desc_type(
mod_,
type_idx,
ref_type.is_exact(),
old_type,
).map(fn(candidate) { (candidate, None) })
_ =>
return dae_gc_candidate_ref_get_desc_type(
mod_, type_idx, false, old_type,
).map(fn(candidate) { (candidate, None) })
}
}
@lib.ExternConvertAny | @lib.AnyConvertExtern as conversion => {
let (input_abs, output_abs) = match conversion {
@lib.ExternConvertAny =>
(@lib.AbsHeapType::any(), @lib.AbsHeapType::extern_())
@lib.AnyConvertExtern =>
(@lib.AbsHeapType::extern_(), @lib.AbsHeapType::any())
_ => abort("unreachable external conversion")
}
for shared in [false, true] {
let input_heap = if shared {
@lib.HeapType::shared_abs(input_abs)
} else {
@lib.HeapType::abs(input_abs)
}
let input_old_type = @lib.ValType::ref_type(
@lib.RefType::new(true, input_heap),
)
match
dae_gc_result_candidate_slice(
mod_,
func_type_indices,
func_locals,
body,
start,
end - 1,
input_old_type,
) {
Some((@lib.RefTypeValType(ref_type), None)) if ref_type
.get_heap_type()
.is_shared() ==
shared => {
let output_heap = if shared {
@lib.HeapType::shared_abs(output_abs)
} else {
@lib.HeapType::abs(output_abs)
}
let candidate = @lib.ValType::ref_type(
@lib.RefType::new(ref_type.is_nullable(), output_heap),
)
match dae_refined_gc_candidate_ref_type(mod_, old_type, candidate) {
Some(refined) => return Some((refined, None))
None => ()
}
}
_ => ()
}
}
}
@lib.RefAsNonNull =>
match
dae_gc_result_candidate_slice(
mod_,
func_type_indices,
func_locals,
body,
start,
end - 1,
old_type,
) {
Some((@lib.RefTypeValType(ref_type), None)) => {
let candidate = @lib.ValType::ref_type(
@lib.RefType::new(
false,
ref_type.get_heap_type(),
exact=ref_type.is_exact(),
),
)
match dae_refined_gc_candidate_ref_type(mod_, old_type, candidate) {
Some(refined) => return Some((refined, None))
None => ()
}
}
_ => ()
}
_ => ()
}
match
dae_find_value_slice(
body,
end,
0,
mod_,
func_type_indices,
allow_stack_carried_void_effects=true,
) {
Some((shape, cursor, _)) if shape.start == start &&
cursor == start &&
!dae_operand_disposition_from_call_shape(body, shape).has_branch =>
match
dae_direct_gc_result_candidate_instr(
mod_,
func_type_indices,
func_locals,
body[end - 1],
old_type,
) {
Some((candidate, None)) => return Some((candidate, None))
_ => ()
}
_ => ()
}
match
dae_call_ref_gc_result_candidate_slice(
mod_, func_type_indices, body, start, end, old_type,
) {
Some(result) => return Some(result)
None => ()
}
dae_call_indirect_gc_result_candidate_slice(
mod_, func_type_indices, body, start, end, old_type,
)
}
///|
fn dae_gc_select_value_slices_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
body : Array[@lib.Instruction],
slices : DaeSelectValueSlices,
old_type : @lib.ValType,
require_refined~ : Bool = false,
) -> DaeGcSelectValueEvidence? {
if !dae_select_value_slices_are_consistent(slices, slices.select_idx) ||
slices.select_idx >= body.length() {
return None
}
match body[slices.select_idx] {
@lib.Select(_) => ()
_ => return None
}
let (left_candidate, left_rewrite) = match
dae_gc_result_candidate_slice(
mod_,
func_type_indices,
func_locals,
body,
slices.left.start,
slices.right.start,
old_type,
) {
Some(result) => result
None => return None
}
let (right_candidate, right_rewrite) = match
dae_gc_result_candidate_slice(
mod_,
func_type_indices,
func_locals,
body,
slices.right.start,
slices.condition.start,
old_type,
) {
Some(result) => result
None => return None
}
let candidate = match
dae_lub_gc_candidate_ref_type(
mod_, left_candidate, right_candidate, old_type,
) {
Some(vt) => vt
None => return None
}
let refined_candidate = dae_refined_gc_candidate_ref_type(
mod_, old_type, candidate,
)
if require_refined && refined_candidate.is_none() {
return None
}
Some({ candidate, refined_candidate, left_rewrite, right_rewrite, })
}
///|
fn dae_gc_select_value_slices_candidate_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
body : Array[@lib.Instruction],
select_idx : Int32,
old_type : @lib.ValType,
required_left_start~ : Int32 = -1,
require_dropped_prefix~ : Bool = false,
require_refined~ : Bool = false,
) -> DaeGcSelectValueSlicesEvidence? {
let slices = match
dae_find_select_value_slices(
mod_,
func_type_indices,
body,
select_idx,
required_left_start~,
require_dropped_prefix~,
) {
Some(slices) => slices
None => return None
}
if !dae_select_value_slices_are_consistent(slices, select_idx) {
return None
}
match
dae_gc_select_value_slices_evidence(
mod_,
func_type_indices,
func_locals,
body,
slices,
old_type,
require_refined~,
) {
Some(gc) => Some({ slices, gc, })
None => None
}
}
///|
fn dae_legacy_catches_have_function_return(
catches : Array[@lib.LegacyCatch],
) -> Bool {
for catch_ in catches {
let @lib.Expr(body) = match catch_ {
@lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
}
if body.any(fn(child) { dae_instr_has_function_return(child) }) {
return true
}
}
false
}
///|
fn dae_instr_has_function_return(instr : @lib.Instruction) -> Bool {
match instr {
@lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_) => true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
body.any(fn(child) { dae_instr_has_function_return(child) })
@lib.Try(_, @lib.Expr(body), catches, _) =>
body.any(fn(child) { dae_instr_has_function_return(child) }) ||
dae_legacy_catches_have_function_return(catches)
@lib.If(_, then_body, Some(else_body)) =>
then_body.any(fn(child) { dae_instr_has_function_return(child) }) ||
else_body.any(fn(child) { dae_instr_has_function_return(child) })
@lib.If(_, then_body, None) =>
then_body.any(fn(child) { dae_instr_has_function_return(child) })
_ => false
}
}
///|
fn dae_body_prefix_has_function_return(
body : Array[@lib.Instruction],
end_exclusive : Int32,
) -> Bool {
let mut idx = 0
while idx < end_exclusive && idx < body.length() {
if dae_instr_has_function_return(body[idx]) {
return true
}
idx += 1
}
false
}
///|
fn dae_effect_free_fallthrough_evidence_is_consistent(
evidence : DaeEffectFreeFallthroughEvidence,
) -> Bool {
evidence.body_length == evidence.body.length() &&
evidence.effect_free_fallthrough == !evidence.has_observable_effect_or_branch &&
evidence.effect_free_fallthrough
}
///|
fn dae_effect_free_fallthrough_evidence(
arm_body : Array[@lib.Instruction],
) -> DaeEffectFreeFallthroughEvidence? {
match dae_effect_free_body_evidence(arm_body) {
Some(body_evidence) => {
let evidence = {
body: body_evidence.body,
body_length: body_evidence.body_length,
has_observable_effect_or_branch: body_evidence.has_observable_effect_or_branch,
effect_free_fallthrough: body_evidence.effect_free,
}
if dae_effect_free_fallthrough_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
None => None
}
}
///|
fn dae_mixed_single_result_return_arm_gc_evidence_is_consistent(
evidence : DaeMixedSingleResultReturnArmGcEvidence,
) -> Bool {
evidence.body_changed == (evidence.rewritten_body is Some(_)) &&
(evidence.has_return || evidence.effect_free_fallthrough) &&
!(evidence.has_return && evidence.effect_free_fallthrough)
}
///|
fn dae_mixed_single_result_return_arm_gc_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
arm_body : Array[@lib.Instruction],
old_type : @lib.ValType,
current_candidate : @lib.ValType,
) -> DaeMixedSingleResultReturnArmGcEvidence? {
match
dae_direct_gc_result_explicit_return_candidate_body(
mod_, func_type_indices, func_locals, arm_body, old_type,
) {
Some((return_candidate, arm_rewrite)) => {
let candidate = match
dae_lub_gc_candidate_ref_type(
mod_, return_candidate, current_candidate, old_type,
) {
Some(lub) => lub
None => return None
}
let evidence = {
candidate,
rewritten_body: arm_rewrite,
body_changed: arm_rewrite is Some(_),
has_return: true,
effect_free_fallthrough: false,
}
if dae_mixed_single_result_return_arm_gc_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
None =>
match dae_effect_free_fallthrough_evidence(arm_body) {
Some(fallthrough) => {
let evidence = {
candidate: current_candidate,
rewritten_body: None,
body_changed: false,
has_return: false,
effect_free_fallthrough: fallthrough.effect_free_fallthrough,
}
if dae_mixed_single_result_return_arm_gc_evidence_is_consistent(
evidence,
) {
Some(evidence)
} else {
None
}
}
None => None
}
}
}
///|
fn dae_direct_gc_result_mixed_if_fallthrough_candidate_body(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
body : Array[@lib.Instruction],
old_type : @lib.ValType,
) -> (@lib.ValType, Array[@lib.Instruction]?)? {
if body.length() < 2 {
return None
}
let terminal_idx = body.length() - 1
let if_idx = body.length() - 2
let (block_type, then_body, else_body_opt) = match body[if_idx] {
@lib.If(block_type, then_body, else_body_opt) =>
(block_type, then_body, else_body_opt)
_ => return None
}
match
dae_control_block_type_zero_input_output_count_evidence(
block_type,
mod_.type_sec,
0,
) {
Some(_) => ()
None => return None
}
let (terminal_candidate, terminal_rewrite) = match
dae_direct_gc_result_candidate_instr(
mod_,
func_type_indices,
func_locals,
body[terminal_idx],
old_type,
) {
Some(result) => result
None => return None
}
let rewritten = body.copy()
let mut next_then_body = then_body
let mut next_else_body : Array[@lib.Instruction]? = else_body_opt
let mut combined_candidate = terminal_candidate
let mut changed = false
let mut saw_return_path = false
let then_evidence = match
dae_mixed_single_result_return_arm_gc_evidence(
mod_, func_type_indices, func_locals, then_body, old_type, combined_candidate,
) {
Some(evidence) => evidence
None => return None
}
combined_candidate = then_evidence.candidate
if then_evidence.has_return {
saw_return_path = true
}
match then_evidence.rewritten_body {
Some(rewritten_then) => {
next_then_body = rewritten_then
changed = true
}
None => ()
}
match else_body_opt {
Some(else_body) => {
let else_evidence = match
dae_mixed_single_result_return_arm_gc_evidence(
mod_, func_type_indices, func_locals, else_body, old_type, combined_candidate,
) {
Some(evidence) => evidence
None => return None
}
combined_candidate = else_evidence.candidate
if else_evidence.has_return {
saw_return_path = true
}
match else_evidence.rewritten_body {
Some(rewritten_else) => {
next_else_body = Some(rewritten_else)
changed = true
}
None => ()
}
}
None => ()
}
if !saw_return_path {
return None
}
match terminal_rewrite {
Some(next_terminal) => {
rewritten[terminal_idx] = next_terminal
changed = true
}
None => ()
}
if changed {
rewritten[if_idx] = @lib.Instruction::if_(
block_type, next_then_body, next_else_body,
)
Some((combined_candidate, Some(rewritten)))
} else {
Some((combined_candidate, None))
}
}
///|
fn dae_instr_has_branch(instr : @lib.Instruction) -> Bool {
match instr {
@lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _)
| @lib.Resume(_, _)
| @lib.ResumeThrow(_, _, _)
| @lib.ResumeThrowRef(_, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _)
| @lib.Rethrow(_)
| @lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef => true
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
body.any(fn(child) { dae_instr_has_branch(child) })
@lib.If(_, then_body, Some(else_body)) =>
then_body.any(fn(child) { dae_instr_has_branch(child) }) ||
else_body.any(fn(child) { dae_instr_has_branch(child) })
@lib.If(_, then_body, None) =>
then_body.any(fn(child) { dae_instr_has_branch(child) })
_ => false
}
}
///|
fn dae_select_condition_stack_slice_instr_allowed(
instr : @lib.Instruction,
) -> Bool {
if dae_instr_has_branch(instr) {
return false
}
// A branch-free `if (result i32)` is still a stack-local condition
// producer for typed-select refinement: changing the select/result type
// does not move or delete the condition control. Keep escaping branches,
// loops, try_table, and local mutation outside this bounded recognizer.
match instr {
@lib.LocalSet(_)
| @lib.LocalTee(_)
| @lib.Loop(_, _)
| @lib.TryTable(_, _, _) => false
_ => true
}
}
///|
fn dae_select_condition_localization_evidence_is_consistent(
evidence : DaeSelectConditionLocalizationEvidence,
) -> Bool {
if evidence.start < 0 ||
evidence.end <= evidence.start ||
evidence.instr_count != evidence.end - evidence.start {
return false
}
if evidence.instr_count == 1 {
match evidence.stack_slice {
Some(_) => return false
None => ()
}
if evidence.singleton_trivially_droppable {
return true
}
match evidence.singleton_producer {
Some(producer) => dae_stack_effect_evidence_is_consistent(producer)
None => false
}
} else {
if evidence.singleton_trivially_droppable {
return false
}
match evidence.singleton_producer {
Some(_) => return false
None => ()
}
match evidence.stack_slice {
Some(slice) =>
dae_stack_slice_localization_evidence_is_consistent(slice) &&
slice.transition.simulation.start == evidence.start &&
slice.transition.simulation.end == evidence.end
None => false
}
}
}
///|
fn dae_select_condition_localization_evidence(
mod_ : @lib.Module,
body : Array[@lib.Instruction],
start : Int32,
end : Int32,
func_type_indices : Array[@lib.TypeIdx],
) -> DaeSelectConditionLocalizationEvidence? {
if start < 0 || start >= end || end > body.length() {
return None
}
let instr_count = end - start
if instr_count == 1 {
let instr = body[start]
let singleton_trivially_droppable = dae_instr_is_trivially_droppable(instr)
if !singleton_trivially_droppable && dae_instr_has_branch(instr) {
return None
}
let singleton_producer = dae_stack_single_value_producer_evidence(
instr, mod_, func_type_indices,
)
if !singleton_trivially_droppable && singleton_producer is None {
return None
}
let evidence = {
start,
end,
instr_count,
singleton_trivially_droppable,
singleton_producer,
stack_slice: None,
}
if dae_select_condition_localization_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
} else {
let stack_slice = match
dae_stack_slice_localization_evidence_from_policy(
mod_,
func_type_indices,
body,
start,
end,
dae_select_condition_localization_policy(),
) {
Some(evidence) => evidence
None => return None
}
let evidence = {
start,
end,
instr_count,
singleton_trivially_droppable: false,
singleton_producer: None,
stack_slice: Some(stack_slice),
}
if dae_select_condition_localization_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
}
///|
fn dae_direct_gc_result_select_candidate_body(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
body : Array[@lib.Instruction],
old_type : @lib.ValType,
) -> (@lib.ValType, Array[@lib.Instruction]?)? {
let mut mode = 0
while mode < 2 {
let has_terminal_return = mode == 1
let terminal_extra = if has_terminal_return { 1 } else { 0 }
if body.length() < 4 + terminal_extra {
mode += 1
continue
}
if has_terminal_return {
match body[body.length() - 1] {
@lib.Return => ()
_ => {
mode += 1
continue
}
}
}
let select_idx = body.length() - 1 - terminal_extra
match
dae_gc_select_value_slices_candidate_evidence(
mod_,
func_type_indices,
func_locals,
body,
select_idx,
old_type,
require_dropped_prefix=true,
) {
Some(evidence) => {
let slices = evidence.slices
let gc = evidence.gc
let (rewritten, _) = match
dae_apply_select_value_rewrites_to_body(
body,
slices.left.start,
slices.right.start,
slices.select_idx,
gc.left_rewrite,
gc.right_rewrite,
Some(@lib.Instruction::select(types=Some([gc.candidate]))),
) {
Some(application) => application
None => {
mode += 1
continue
}
}
return Some((gc.candidate, Some(rewritten)))
}
None => ()
}
mode += 1
}
None
}
///|
fn dae_collect_explicit_gc_result_candidates_instrs(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
body : Array[@lib.Instruction],
old_type : @lib.ValType,
candidates : Array[@lib.ValType],
failed : Array[Bool],
) -> Int32 {
let mut count = 0
let mut idx = 0
while idx < body.length() && !failed[0] {
if dae_instr_is_unreachable_root(body[idx]) {
break
}
match body[idx] {
@lib.Return => {
if idx == 0 {
failed[0] = true
break
}
let shape = match
dae_find_value_slice(
body,
idx,
0,
mod_,
func_type_indices,
allow_stack_carried_void_effects=true,
) {
Some((shape, cursor, _)) if shape.start == cursor => shape
_ => {
failed[0] = true
break
}
}
match
dae_gc_result_candidate_slice(
mod_,
func_type_indices,
func_locals,
body,
shape.start,
idx,
old_type,
) {
Some((candidate, None)) => {
candidates.push(candidate)
count += 1
}
_ => failed[0] = true
}
}
@lib.Block(_, @lib.Expr(child))
| @lib.Loop(_, @lib.Expr(child))
| @lib.TryTable(_, _, @lib.Expr(child)) =>
count += dae_collect_explicit_gc_result_candidates_instrs(
mod_, func_type_indices, func_locals, child, old_type, candidates, failed,
)
@lib.Try(_, @lib.Expr(child), catches, _) => {
count += dae_collect_explicit_gc_result_candidates_instrs(
mod_, func_type_indices, func_locals, child, old_type, candidates, failed,
)
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
}
count += dae_collect_explicit_gc_result_candidates_instrs(
mod_, func_type_indices, func_locals, catch_body, old_type, candidates,
failed,
)
}
}
@lib.If(_, then_body, else_body) => {
count += dae_collect_explicit_gc_result_candidates_instrs(
mod_, func_type_indices, func_locals, then_body, old_type, candidates,
failed,
)
match else_body {
Some(child) =>
count += dae_collect_explicit_gc_result_candidates_instrs(
mod_, func_type_indices, func_locals, child, old_type, candidates,
failed,
)
None => ()
}
}
_ => ()
}
idx += 1
}
count
}
///|
fn dae_body_has_no_fallthrough(body : Array[@lib.Instruction]) -> Bool {
body.length() > 0 && dae_instr_has_no_fallthrough(body[body.length() - 1])
}
///|
fn dae_instr_has_no_fallthrough(instr : @lib.Instruction) -> Bool {
match instr {
@lib.Return
| @lib.Unreachable
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Throw(_)
| @lib.ThrowRef => true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) => dae_body_has_no_fallthrough(body)
@lib.If(_, then_body, Some(else_body)) =>
dae_body_has_no_fallthrough(then_body) &&
dae_body_has_no_fallthrough(else_body)
@lib.Try(_, @lib.Expr(body), catches, _) => {
if !dae_body_has_no_fallthrough(body) {
return false
}
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
}
if !dae_body_has_no_fallthrough(catch_body) {
return false
}
}
true
}
_ => false
}
}
///|
fn dae_direct_gc_result_multiple_return_candidate_body(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
body : Array[@lib.Instruction],
old_type : @lib.ValType,
) -> @lib.ValType? {
let candidates : Array[@lib.ValType] = []
let failed = [false]
let explicit_count = dae_collect_explicit_gc_result_candidates_instrs(
mod_, func_type_indices, func_locals, body, old_type, candidates, failed,
)
if failed[0] || explicit_count == 0 || candidates.length() == 0 {
return None
}
match body[body.length() - 1] {
@lib.Return | @lib.Unreachable => ()
terminal if dae_instr_has_no_fallthrough(terminal) => ()
_ => {
let shape = match
dae_find_value_slice(
body,
body.length(),
0,
mod_,
func_type_indices,
allow_stack_carried_void_effects=true,
) {
Some((shape, cursor, _)) if shape.start == cursor => shape
_ => return None
}
match
dae_gc_result_candidate_slice(
mod_,
func_type_indices,
func_locals,
body,
shape.start,
body.length(),
old_type,
) {
Some((candidate, None)) => candidates.push(candidate)
_ => return None
}
}
}
let mut joined = candidates[0]
let mut idx = 1
while idx < candidates.length() {
joined = match
dae_lub_gc_candidate_ref_type(mod_, joined, candidates[idx], old_type) {
Some(candidate) => candidate
None => return None
}
idx += 1
}
Some(joined)
}
///|
fn dae_direct_gc_result_candidate_body(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
body : Array[@lib.Instruction],
old_type : @lib.ValType,
) -> (@lib.ValType, Array[@lib.Instruction]?)? {
match
dae_direct_gc_result_select_candidate_body(
mod_, func_type_indices, func_locals, body, old_type,
) {
Some(result) => return Some(result)
None => ()
}
match
dae_direct_gc_result_multiple_return_candidate_body(
mod_, func_type_indices, func_locals, body, old_type,
) {
Some(candidate) => return Some((candidate, None))
None => ()
}
match
dae_direct_gc_result_explicit_return_candidate_body(
mod_, func_type_indices, func_locals, body, old_type,
) {
Some(result) => return Some(result)
None => ()
}
if body.length() >= 2 {
match body[body.length() - 1] {
@lib.Unreachable =>
match
dae_direct_gc_result_explicit_return_candidate_instr(
mod_,
func_type_indices,
func_locals,
body[body.length() - 2],
old_type,
) {
Some((candidate_type, Some(rewritten_terminal))) => {
let rewritten = body.copy()
rewritten[rewritten.length() - 2] = rewritten_terminal
return Some((candidate_type, Some(rewritten)))
}
Some((candidate_type, None)) => return Some((candidate_type, None))
None => ()
}
_ => ()
}
match
dae_direct_gc_result_mixed_if_fallthrough_candidate_body(
mod_, func_type_indices, func_locals, body, old_type,
) {
Some(result) => return Some(result)
None =>
if dae_body_prefix_has_function_return(body, body.length() - 1) {
return None
}
}
}
match body {
[@lib.StructNewDefault(type_idx)] =>
Some((dae_exact_struct_default_ref_type(type_idx), None))
[@lib.Block(@lib.ValTypeBlockType(_), @lib.Expr(block_body))] =>
match block_body {
[@lib.StructNewDefault(type_idx)] =>
Some(
(
dae_exact_struct_default_ref_type(type_idx),
Some([@lib.Instruction::struct_new_default(type_idx)]),
),
)
_ =>
dae_direct_gc_result_candidate_instr(
mod_,
func_type_indices,
func_locals,
body[0],
old_type,
).map(fn(result) {
let (candidate_type, rewritten_instr) = result
let rewritten_body = match rewritten_instr {
Some(instr) => Some([instr])
None => None
}
(candidate_type, rewritten_body)
})
}
_ => {
if body.length() == 0 {
return None
}
match
dae_find_value_slice(
body,
body.length(),
0,
mod_,
func_type_indices,
allow_stack_carried_void_effects=true,
) {
Some((shape, cursor, _)) if shape.start == cursor =>
match
dae_gc_result_candidate_slice(
mod_,
func_type_indices,
func_locals,
body,
shape.start,
body.length(),
old_type,
) {
Some((candidate_type, None)) => return Some((candidate_type, None))
_ => ()
}
_ => ()
}
let terminal = body[body.length() - 1]
match
dae_direct_gc_result_candidate_instr(
mod_, func_type_indices, func_locals, terminal, old_type,
) {
Some((candidate_type, Some(rewritten_terminal))) => {
let rewritten = body.copy()
rewritten[rewritten.length() - 1] = rewritten_terminal
Some((candidate_type, Some(rewritten)))
}
Some((candidate_type, None)) => Some((candidate_type, None))
None => None
}
}
}
}
///|
fn dae_module_signature_context(
mod_ : @lib.Module,
def_idx : Int32,
copy_funcs~ : Bool,
) -> DaeModuleSignatureContext? {
match (mod_.func_sec, mod_.code_sec, mod_.type_sec) {
(Some(@lib.FuncSec(type_idxs)), Some(@lib.CodeSec(funcs)), Some(type_sec)) => {
if def_idx < 0 ||
def_idx >= funcs.length() ||
def_idx >= type_idxs.length() {
return None
}
Some({
def_idx,
type_idxs: type_idxs.copy(),
funcs: if copy_funcs {
funcs.copy()
} else {
funcs
},
type_sec,
})
}
_ => None
}
}
///|
fn dae_apply_module_boundary_finalization(
mod_ : @lib.Module,
context : DaeModuleSignatureContext,
rewritten_funcs : Array[@lib.Func],
plan : DaeModuleCodeFinalizationPlan,
perf~ : HotPerfSession? = None,
type_update_timer_name~ : String = "",
) -> DaePassResult {
if rewritten_funcs.length() != context.funcs.length() {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let type_update_start_us = if type_update_timer_name.length() > 0 {
perf_start_timer_opt(perf, type_update_timer_name)
} else {
0UL
}
let (next_type_sec, new_type_idx) = dae_update_defined_func_type(
mod_,
context.type_sec,
context.type_idxs,
context.def_idx,
plan.next_params,
plan.next_results,
)
if type_update_timer_name.length() > 0 {
perf_stop_timer_opt(perf, type_update_timer_name, type_update_start_us)
}
context.type_idxs[context.def_idx] = new_type_idx
let touched = Array::make(context.funcs.length(), false)
touched[context.def_idx] = true
match plan.touched_policy {
DaeTouchModuleCodeCalleeOnly => ()
DaeTouchModuleCodeChangedFuncs => {
let mut def_idx = 0
while def_idx < context.funcs.length() {
if context.funcs[def_idx] != rewritten_funcs[def_idx] {
touched[def_idx] = true
}
def_idx += 1
}
}
}
{
mod_out: mod_
.with_type_sec(next_type_sec)
.with_func_sec(@lib.FuncSec::new(context.type_idxs))
.with_code_sec(@lib.CodeSec::new(rewritten_funcs))
.without_name_sec(),
touched,
changed: true,
primary_def: context.def_idx,
}
}
///|
fn dae_try_update_signature_only(
mod_ : @lib.Module,
def_idx : Int32,
params : Array[@lib.ValType],
results : Array[@lib.ValType],
) -> DaePassResult {
let context = match
dae_module_signature_context(mod_, def_idx, copy_funcs=false) {
Some(context) => context
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
dae_apply_module_boundary_finalization(
mod_,
context,
context.funcs,
dae_module_code_finalization_plan(
params,
results,
DaeTouchModuleCodeCalleeOnly,
),
)
}
///|
fn dae_flat_multi_result_suffix_evidence_is_consistent(
evidence : DaeFlatMultiResultSuffixEvidence,
body : Array[@lib.Instruction],
) -> Bool {
evidence.result_count > 0 &&
evidence.start >= 0 &&
evidence.end == evidence.start + evidence.result_count &&
evidence.end <= body.length() &&
(
!evidence.has_terminal_return ||
(
evidence.end + 1 == body.length() &&
body[body.length() - 1] is @lib.Return
)
) &&
(evidence.has_terminal_return || evidence.end == body.length())
}
///|
fn dae_flat_multi_result_suffix_evidence(
body : Array[@lib.Instruction],
result_count : Int32,
match_terminal_return : Bool,
) -> DaeFlatMultiResultSuffixEvidence? {
let terminal_extra = if match_terminal_return { 1 } else { 0 }
if result_count <= 0 || body.length() < result_count + terminal_extra {
return None
}
if match_terminal_return {
match body[body.length() - 1] {
@lib.Return => ()
_ => return None
}
}
let candidate_start = body.length() - terminal_extra - result_count
match dae_trivially_dropped_prefix_evidence(body, candidate_start) {
Some(_) => {
let evidence = {
start: candidate_start,
end: candidate_start + result_count,
result_count,
has_terminal_return: match_terminal_return,
}
if dae_flat_multi_result_suffix_evidence_is_consistent(evidence, body) {
Some(evidence)
} else {
None
}
}
None => None
}
}
///|
fn dae_find_or_append_func_type(
type_sec : @lib.TypeSec,
params : Array[@lib.ValType],
results : Array[@lib.ValType],
) -> (@lib.TypeSec, @lib.TypeIdx) {
match dae_find_equivalent_simple_func_type(type_sec, params, results) {
Some(existing) => (type_sec, existing)
None => dae_append_new_func_type(type_sec, params, results)
}
}
///|
fn dae_typeidx_control_rewrite_plan(
control_kind : Int32,
params : Array[@lib.ValType],
results : Array[@lib.ValType],
body : Array[@lib.Instruction],
else_body : Array[@lib.Instruction]?,
) -> DaeTypeidxControlRewritePlan? {
if results.length() == 0 {
return None
}
match (control_kind, else_body) {
(0 | 1, None) =>
Some({
control_kind,
params: params.copy(),
results: results.copy(),
body: body.copy(),
else_body: None,
})
(2, Some(else_body)) =>
Some({
control_kind,
params: params.copy(),
results: results.copy(),
body: body.copy(),
else_body: Some(else_body.copy()),
})
_ => None
}
}
///|
fn dae_apply_typeidx_control_rewrite(
type_sec : @lib.TypeSec,
plan : DaeTypeidxControlRewritePlan,
) -> DaeTypeidxControlRewriteApplication? {
let (next_type_sec, block_type_idx) = dae_find_or_append_func_type(
type_sec,
plan.params,
plan.results,
)
let block_type = @lib.BlockType::type_idx(block_type_idx)
let rewritten = match (plan.control_kind, plan.else_body) {
(0, None) => @lib.Instruction::block(block_type, @lib.Expr::new(plan.body))
(1, None) => @lib.Instruction::loop_(block_type, @lib.Expr::new(plan.body))
(2, Some(else_body)) =>
@lib.Instruction::if_(block_type, plan.body, Some(else_body))
_ => return None
}
Some({ type_sec: next_type_sec, rewritten, })
}
///|
fn dae_rebuild_typeidx_control(
type_sec : @lib.TypeSec,
control_kind : Int32,
params : Array[@lib.ValType],
results : Array[@lib.ValType],
body : Array[@lib.Instruction],
else_body : Array[@lib.Instruction]?,
) -> DaeTypeidxControlRewriteApplication? {
let plan = match
dae_typeidx_control_rewrite_plan(
control_kind, params, results, body, else_body,
) {
Some(plan) => plan
None => return None
}
dae_apply_typeidx_control_rewrite(type_sec, plan)
}
///|
fn dae_rebuild_typeidx_control_in_body(
type_sec : @lib.TypeSec,
outer_body : Array[@lib.Instruction],
control_idx : Int32,
control_kind : Int32,
params : Array[@lib.ValType],
results : Array[@lib.ValType],
control_body : Array[@lib.Instruction],
else_body : Array[@lib.Instruction]?,
) -> DaeTypeidxControlBodyRewriteApplication? {
if control_idx < 0 || control_idx >= outer_body.length() {
return None
}
let control_application = match
dae_rebuild_typeidx_control(
type_sec, control_kind, params, results, control_body, else_body,
) {
Some(application) => application
None => return None
}
let body = outer_body.copy()
body[control_idx] = control_application.rewritten
Some({ type_sec: control_application.type_sec, body, })
}
///|
fn dae_dropped_operand_pair_skip_evidence_is_consistent(
evidence : DaeDroppedOperandPairSkipEvidence,
) -> Bool {
evidence.start >= 0 &&
evidence.end >= evidence.start &&
evidence.next >= evidence.start &&
evidence.next <= evidence.end &&
evidence.skipped_pairs >= 0 &&
evidence.next == evidence.start + evidence.skipped_pairs * 2
}
///|
fn dae_dropped_operand_pair_skip_evidence(
body : Array[@lib.Instruction],
start : Int32,
end : Int32,
) -> DaeDroppedOperandPairSkipEvidence? {
if start < 0 || end < start || end > body.length() {
return None
}
let mut next = start
let mut skipped_pairs = 0
while next + 1 < end && dae_instr_is_trivially_droppable(body[next]) {
match body[next + 1] {
@lib.Drop => {
next += 2
skipped_pairs += 1
}
_ => break
}
}
let evidence = { start, end, next, skipped_pairs, }
if dae_dropped_operand_pair_skip_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
///|
fn dae_multi_result_operand_slot_evidence_is_consistent(
evidence : DaeMultiResultOperandSlotEvidence,
body : Array[@lib.Instruction],
) -> Bool {
evidence.start >= 0 &&
evidence.end >= evidence.start &&
evidence.end <= body.length() &&
evidence.operand_start >= evidence.start &&
evidence.operand_start < evidence.end &&
evidence.next >= evidence.operand_start &&
evidence.next <= evidence.end &&
evidence.skipped_pairs >= 0 &&
evidence.operand_start == evidence.start + evidence.skipped_pairs * 2 &&
(
!evidence.consumed_single_producer ||
evidence.next == evidence.operand_start + 1
) &&
(evidence.consumed_single_producer || evidence.next == evidence.operand_start)
}
///|
fn dae_multi_result_operand_slot_prefix_evidence(
body : Array[@lib.Instruction],
start : Int32,
end : Int32,
) -> DaeMultiResultOperandSlotEvidence? {
let skipped = match dae_dropped_operand_pair_skip_evidence(body, start, end) {
Some(evidence) => evidence
None => return None
}
if skipped.next >= end {
return None
}
let evidence = {
start,
end,
operand_start: skipped.next,
next: skipped.next,
skipped_pairs: skipped.skipped_pairs,
consumed_single_producer: false,
}
if dae_multi_result_operand_slot_evidence_is_consistent(evidence, body) {
Some(evidence)
} else {
None
}
}
///|
fn dae_multi_result_operand_slot_single_producer_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
body : Array[@lib.Instruction],
prefix : DaeMultiResultOperandSlotEvidence,
) -> DaeMultiResultOperandSlotEvidence? {
if !dae_multi_result_operand_slot_evidence_is_consistent(prefix, body) ||
prefix.consumed_single_producer {
return None
}
match
dae_stack_single_value_producer_evidence(
body[prefix.operand_start],
mod_,
func_type_indices,
) {
Some(_) => {
let evidence = {
start: prefix.start,
end: prefix.end,
operand_start: prefix.operand_start,
next: prefix.operand_start + 1,
skipped_pairs: prefix.skipped_pairs,
consumed_single_producer: true,
}
if dae_multi_result_operand_slot_evidence_is_consistent(evidence, body) {
Some(evidence)
} else {
None
}
}
None => None
}
}
///|
fn dae_multi_result_suffix_bounds_evidence(
body : Array[@lib.Instruction],
result_count : Int32,
min_extra_instrs : Int32,
) -> Array[DaeMultiResultSuffixBoundsEvidence] {
let out : Array[DaeMultiResultSuffixBoundsEvidence] = []
if result_count <= 0 || min_extra_instrs < 0 {
return out
}
let mut mode = 0
while mode < 2 {
let has_terminal_return = mode == 1
let terminal_extra = if has_terminal_return { 1 } else { 0 }
if body.length() < result_count + min_extra_instrs + terminal_extra {
mode += 1
continue
}
if has_terminal_return {
match body[body.length() - 1] {
@lib.Return => ()
_ => {
mode += 1
continue
}
}
}
let operand_end = body.length() - terminal_extra
let max_start = operand_end - result_count
let mut suffix_start = 0
while suffix_start <= max_start {
if dae_trivially_dropped_prefix_evidence(body, suffix_start) is Some(_) {
out.push({ suffix_start, operand_end, has_terminal_return, })
}
suffix_start += 1
}
mode += 1
}
out
}
///|
fn dae_multi_result_operand_sequence_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
body : Array[@lib.Instruction],
result_count : Int32,
min_extra_instrs : Int32,
specialize : (Array[@lib.Instruction], Int32, Int32, Int32) -> (Int32, Int32)?,
) -> DaeMultiResultOperandSequenceEvidence? {
if result_count <= 0 {
return None
}
let suffix_bounds = dae_multi_result_suffix_bounds_evidence(
body, result_count, min_extra_instrs,
)
for bounds in suffix_bounds {
let operand_starts = Array::make(result_count, -1)
let value_instr_indices = Array::make(result_count, -1)
let specialized_slots = Array::make(result_count, false)
let mut instr_idx = bounds.suffix_start
let mut result_idx = 0
let mut saw_specialized = false
let mut suffix_ok = true
while suffix_ok && result_idx < result_count {
let slot_prefix = match
dae_multi_result_operand_slot_prefix_evidence(
body,
instr_idx,
bounds.operand_end,
) {
Some(evidence) => evidence
None => {
suffix_ok = false
break
}
}
let operand_start = slot_prefix.operand_start
operand_starts[result_idx] = operand_start
let mut consumed = false
match specialize(body, operand_start, bounds.operand_end, result_idx) {
Some((value_instr_idx, next)) =>
if value_instr_idx >= operand_start &&
value_instr_idx < next &&
next > operand_start &&
next <= bounds.operand_end {
value_instr_indices[result_idx] = value_instr_idx
specialized_slots[result_idx] = true
instr_idx = next
saw_specialized = true
consumed = true
}
None => ()
}
if !consumed {
match
dae_multi_result_operand_slot_single_producer_evidence(
mod_, func_type_indices, body, slot_prefix,
) {
Some(evidence) => {
value_instr_indices[result_idx] = evidence.operand_start
instr_idx = evidence.next
consumed = true
}
None => ()
}
}
if consumed {
result_idx += 1
} else {
suffix_ok = false
}
}
if suffix_ok &&
result_idx == result_count &&
instr_idx == bounds.operand_end &&
saw_specialized {
return Some({
bounds,
operand_starts,
value_instr_indices,
specialized_slots,
})
}
}
None
}
///|
fn dae_try_refine_multi_result_single_if_operand_body(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
body : Array[@lib.Instruction],
results : Array[@lib.ValType],
next_results : Array[@lib.ValType],
) -> Array[@lib.Instruction]? {
if results.length() < 2 {
return None
}
let specialize_if = fn(
candidate_body : Array[@lib.Instruction],
operand_start : Int32,
operand_end : Int32,
_ : Int32,
) -> (Int32, Int32)? {
if operand_start + 1 >= operand_end {
return None
}
match candidate_body[operand_start + 1] {
@lib.If(block_type, _, Some(_)) =>
match dae_control_single_result_block_type_evidence(block_type, mod_) {
Some(_) =>
match
dae_stack_single_value_producer_evidence(
candidate_body[operand_start],
mod_,
func_type_indices,
) {
Some(_) => Some((operand_start + 1, operand_start + 2))
None => None
}
None => None
}
_ => None
}
}
let sequence = match
dae_multi_result_operand_sequence_evidence(
mod_,
func_type_indices,
body,
results.length(),
1,
specialize_if,
) {
Some(evidence) => evidence
None => return None
}
ignore(sequence.bounds.has_terminal_return)
let rewritten_body = body.copy()
let mut changed = false
let mut result_idx = 0
while result_idx < results.length() {
let candidate_instr_idx = sequence.value_instr_indices[result_idx]
match
dae_gc_result_slot_evidence(
mod_,
func_type_indices,
func_locals,
body[candidate_instr_idx],
results[result_idx],
require_refined=true,
) {
Some(evidence) => {
let refined = match evidence.refined_candidate {
Some(vt) => vt
None => return None
}
next_results[result_idx] = refined
changed = true
match evidence.rewrite {
Some(instr) => rewritten_body[candidate_instr_idx] = instr
None => ()
}
}
None => ()
}
result_idx += 1
}
if changed {
Some(rewritten_body)
} else {
None
}
}
///|
fn dae_try_refine_multi_result_select_operand_body(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
body : Array[@lib.Instruction],
results : Array[@lib.ValType],
next_results : Array[@lib.ValType],
) -> Array[@lib.Instruction]? {
if results.length() == 0 {
return None
}
let specialize_select = fn(
candidate_body : Array[@lib.Instruction],
operand_start : Int32,
operand_end : Int32,
result_idx : Int32,
) -> (Int32, Int32)? {
let remaining_results = results.length() - result_idx - 1
let max_select_idx = operand_end - remaining_results - 1
let mut select_idx = operand_start + 3
while select_idx <= max_select_idx {
match
dae_gc_select_value_slices_candidate_evidence(
mod_,
func_type_indices,
func_locals,
candidate_body,
select_idx,
results[result_idx],
required_left_start=operand_start,
require_refined=true,
) {
Some(evidence) =>
return Some(
(evidence.slices.select_idx, evidence.slices.select_idx + 1),
)
None => ()
}
select_idx += 1
}
None
}
let sequence = match
dae_multi_result_operand_sequence_evidence(
mod_,
func_type_indices,
body,
results.length(),
3,
specialize_select,
) {
Some(evidence) => evidence
None => return None
}
ignore(sequence.bounds.has_terminal_return)
let rewritten_body = body.copy()
let mut changed = false
let mut result_idx = 0
while result_idx < results.length() {
if sequence.specialized_slots[result_idx] {
let select_idx = sequence.value_instr_indices[result_idx]
let operand_start = sequence.operand_starts[result_idx]
let evidence = match
dae_gc_select_value_slices_candidate_evidence(
mod_,
func_type_indices,
func_locals,
body,
select_idx,
results[result_idx],
required_left_start=operand_start,
require_refined=true,
) {
Some(evidence) => evidence
None => return None
}
match evidence.gc.refined_candidate {
Some(refined) => {
let slices = evidence.slices
next_results[result_idx] = refined
changed = true
let (next_body, _) = match
dae_apply_select_value_rewrites_to_body(
rewritten_body,
slices.left.start,
slices.right.start,
slices.select_idx,
evidence.gc.left_rewrite,
evidence.gc.right_rewrite,
Some(@lib.Instruction::select(types=Some([refined]))),
) {
Some(application) => application
None => return None
}
rewritten_body.clear()
rewritten_body.append(next_body)
}
None => ()
}
}
result_idx += 1
}
if changed {
Some(rewritten_body)
} else {
None
}
}
///|
fn dae_instr_is_parametric_typeidx_stack_safe_effectful_prefix(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Call(_)
| @lib.I32Load(_)
| @lib.I64Load(_)
| @lib.F32Load(_)
| @lib.F64Load(_)
| @lib.I32Load8S(_)
| @lib.I32Load8U(_)
| @lib.I32Load16S(_)
| @lib.I32Load16U(_)
| @lib.I64Load8S(_)
| @lib.I64Load8U(_)
| @lib.I64Load16S(_)
| @lib.I64Load16U(_)
| @lib.I64Load32S(_)
| @lib.I64Load32U(_) => true
_ => false
}
}
///|
fn dae_parametric_typeidx_stack_slice_instr_allowed(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Drop => true
_ =>
dae_instr_is_droppable_pure_leaf(instr) ||
dae_instr_is_nontrapping_pure_unary(instr) ||
dae_instr_is_nontrapping_pure_binary(instr) ||
dae_instr_is_parametric_typeidx_stack_safe_effectful_prefix(instr)
}
}
///|
fn dae_parametric_typeidx_prefix_consumes_params_stack_safe(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
arm_body : Array[@lib.Instruction],
end : Int32,
param_count : Int32,
) -> Bool {
dae_stack_slice_localization_policy_accepts(
mod_,
func_type_indices,
arm_body,
0,
end,
dae_parametric_typeidx_prefix_localization_policy(param_count),
)
}
///|
fn dae_parametric_typeidx_multi_result_arm_gc_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
arm_body : Array[@lib.Instruction],
param_count : Int32,
results : Array[@lib.ValType],
) -> DaeControlResultGcEvidence? {
if results.length() == 0 || arm_body.length() < results.length() {
return None
}
let result_start = arm_body.length() - results.length()
if !dae_parametric_typeidx_prefix_consumes_params_stack_safe(
mod_, func_type_indices, arm_body, result_start, param_count,
) {
return None
}
let result_slice = match
dae_make_multi_result_control_slice(
arm_body,
result_start,
results.length(),
0,
false,
false,
) {
Some(slice) => slice
None => return None
}
match
dae_control_result_slice_gc_evidence(
mod_, func_type_indices, func_locals, result_slice, results,
) {
Some(evidence) => if evidence.saw_candidate { Some(evidence) } else { None }
None => None
}
}
///|
fn dae_try_refine_parametric_typeidx_multi_result_terminal_control_body(
mod_ : @lib.Module,
type_sec : @lib.TypeSec,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
body : Array[@lib.Instruction],
results : Array[@lib.ValType],
next_results : Array[@lib.ValType],
) -> (@lib.TypeSec, Array[@lib.Instruction])? {
if results.length() < 2 || body.length() == 0 {
return None
}
let terminal_idx = body.length() - 1
if dae_body_prefix_has_function_return(body, terminal_idx) {
return None
}
let instr = body[terminal_idx]
let (block_type, arm_body, else_body_opt, kind) = match instr {
@lib.Block(block_type, @lib.Expr(block_body)) =>
(block_type, block_body, None, 0)
@lib.Loop(block_type, @lib.Expr(loop_body)) =>
(block_type, loop_body, None, 1)
@lib.If(block_type, then_body, Some(else_body)) =>
(block_type, then_body, Some(else_body), 2)
_ => return None
}
let control_signature = match
dae_control_block_type_signature_evidence(block_type, mod_) {
Some(signature) =>
match signature.type_idx {
Some(_) => signature
None => return None
}
None => return None
}
let params = control_signature.params
let control_results = control_signature.results
if params.length() == 0 || control_results.length() != results.length() {
return None
}
let arm_evidence = match
dae_parametric_typeidx_multi_result_arm_gc_evidence(
mod_,
func_type_indices,
func_locals,
arm_body,
params.length(),
results,
) {
Some(evidence) => evidence
None => return None
}
let refined_results = results.copy()
let mut rewritten_arm = arm_evidence.rewritten_body
let mut changed = false
let mut body_changed = arm_evidence.body_changed
let mut next_else_body : Array[@lib.Instruction]? = else_body_opt
match else_body_opt {
Some(else_body) => {
let else_evidence = match
dae_parametric_typeidx_multi_result_arm_gc_evidence(
mod_,
func_type_indices,
func_locals,
else_body,
params.length(),
results,
) {
Some(evidence) => evidence
None => return None
}
let pair_evidence = match
dae_control_result_pair_gc_evidence(
mod_, arm_evidence, else_evidence, results,
) {
Some(evidence) => evidence
None => return None
}
match
dae_apply_control_result_pair_refinements(
pair_evidence,
next_results,
refined_results=Some(refined_results),
) {
Some(_) => ()
None => return None
}
rewritten_arm = pair_evidence.rewritten_then_body
next_else_body = Some(pair_evidence.rewritten_else_body)
body_changed = body_changed ||
pair_evidence.then_body_changed ||
pair_evidence.else_body_changed
changed = true
}
None =>
match
dae_apply_control_result_single_refinements(
arm_evidence,
next_results,
refined_results=Some(refined_results),
) {
Some(_) => changed = true
None => ()
}
}
if !changed {
return None
}
let application = match
dae_rebuild_typeidx_control_in_body(
type_sec, body, terminal_idx, kind, params, refined_results, rewritten_arm,
next_else_body,
) {
Some(application) => application
None => return None
}
ignore(body_changed)
Some((application.type_sec, application.body))
}
///|
fn dae_find_multi_result_control_slice(
mod_ : @lib.Module,
body : Array[@lib.Instruction],
result_count : Int32,
require_terminal_return~ : Bool = false,
) -> DaeControlResultSlice? {
if result_count <= 0 {
return None
}
if !require_terminal_return {
return dae_multi_result_control_carrier_chain_slice(
mod_,
body,
result_count,
terminal_return=false,
match_terminal_return=false,
append_terminal_return=false,
)
}
match
dae_multi_result_control_carrier_chain_slice(
mod_,
body,
result_count,
terminal_return=true,
match_terminal_return=true,
append_terminal_return=false,
) {
Some(result_slice) => return Some(result_slice)
None => ()
}
match body {
[@lib.Block(block_type, @lib.Expr(block_body))] =>
match
dae_control_block_type_zero_input_output_count_evidence(
block_type,
mod_.type_sec,
0,
) {
Some(_) if dae_terminal_return_body_evidence(block_body) is Some(_) =>
match
dae_multi_result_control_carrier_chain_slice(
mod_,
block_body,
result_count,
terminal_return=true,
match_terminal_return=true,
append_terminal_return=false,
) {
Some(result_slice) => return Some(result_slice)
None => ()
}
_ => ()
}
_ => ()
}
match body {
[block_instr, @lib.Return] =>
dae_multi_result_control_carrier_chain_slice(
mod_,
[block_instr],
result_count,
terminal_return=true,
match_terminal_return=false,
append_terminal_return=true,
)
_ => None
}
}
///|
fn dae_multi_result_if_arm_return_candidate_body(
mod_ : @lib.Module,
body : Array[@lib.Instruction],
result_count : Int32,
) -> DaeControlResultSlice? {
dae_find_multi_result_control_slice(
mod_,
body,
result_count,
require_terminal_return=true,
)
}
///|
fn dae_mixed_multi_result_return_arm_gc_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
arm_body : Array[@lib.Instruction],
results : Array[@lib.ValType],
combined_candidates : Array[@lib.ValType?],
rejected : Array[Bool],
) -> DaeMixedMultiResultReturnArmGcEvidence? {
if combined_candidates.length() != results.length() ||
rejected.length() != results.length() {
return None
}
match
dae_multi_result_if_arm_return_candidate_body(
mod_,
arm_body,
results.length(),
) {
Some(result_slice) => {
let control_result = match
dae_control_result_slice_gc_evidence(
mod_, func_type_indices, func_locals, result_slice, results,
) {
Some(evidence) => evidence
None => return None
}
let mut result_idx = 0
while result_idx < results.length() {
match combined_candidates[result_idx] {
Some(current_candidate) =>
match control_result.candidates[result_idx] {
Some(return_candidate) =>
match
dae_lub_gc_candidate_ref_type(
mod_,
return_candidate,
current_candidate,
results[result_idx],
) {
Some(lub) => combined_candidates[result_idx] = Some(lub)
None => rejected[result_idx] = true
}
None => rejected[result_idx] = true
}
None => ()
}
result_idx += 1
}
Some({
body: control_result.body,
result_start: control_result.result_start,
flattened: result_slice.flattened,
has_return: true,
effect_free_fallthrough: false,
control_result: Some(control_result),
})
}
None =>
match dae_effect_free_fallthrough_evidence(arm_body) {
Some(fallthrough) =>
Some({
body: fallthrough.body,
result_start: -1,
flattened: false,
has_return: false,
effect_free_fallthrough: fallthrough.effect_free_fallthrough,
control_result: None,
})
None => None
}
}
}
///|
fn dae_apply_mixed_multi_result_return_arm_refinements(
evidence : DaeMixedMultiResultReturnArmGcEvidence,
refined : Array[Bool],
) -> DaeMixedMultiResultReturnArmRewriteEvidence? {
if !evidence.has_return {
if evidence.control_result is Some(_) {
return None
}
return Some({ body: evidence.body, body_changed: false, })
}
let control_result = match evidence.control_result {
Some(control_result) => control_result
None => return None
}
if refined.length() != control_result.candidates.length() ||
control_result.rewrites.length() != refined.length() ||
evidence.result_start != control_result.result_start ||
evidence.body != control_result.body {
return None
}
match
dae_apply_result_rewrites_to_body(
control_result.body,
control_result.result_start,
control_result.rewrites,
active=Some(refined),
) {
Some((rewritten, application)) => {
let body_changed = evidence.flattened || application.body_changed
Some({
body: if body_changed {
rewritten
} else {
evidence.body
},
body_changed,
})
}
None => None
}
}
///|
fn dae_try_refine_mixed_multi_result_if_fallthrough_body(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_locals : @lib.FunctionLocals?,
body : Array[@lib.Instruction],
results : Array[@lib.ValType],
next_results : Array[@lib.ValType],
) -> Array[@lib.Instruction]? {
if results.length() == 0 || body.length() <= results.length() {
return None
}
let terminal_start = body.length() - results.length()
let if_idx = terminal_start - 1
let (block_type, then_body, else_body_opt) = match body[if_idx] {
@lib.If(block_type, then_body, else_body_opt) =>
(block_type, then_body, else_body_opt)
_ => return None
}
match
dae_control_block_type_zero_input_output_count_evidence(
block_type,
mod_.type_sec,
0,
) {
Some(_) => ()
None => return None
}
let combined_candidates : Array[@lib.ValType?] = Array::make(
results.length(),
None,
)
let terminal_rewrites : Array[@lib.Instruction?] = Array::make(
results.length(),
None,
)
let rejected = Array::make(results.length(), false)
let refined = Array::make(results.length(), false)
let mut result_idx = 0
while result_idx < results.length() {
let instr_idx = terminal_start + result_idx
match
dae_direct_gc_result_candidate_instr(
mod_,
func_type_indices,
func_locals,
body[instr_idx],
results[result_idx],
) {
Some((candidate_type, rewritten_instr)) => {
combined_candidates[result_idx] = Some(candidate_type)
terminal_rewrites[result_idx] = rewritten_instr
}
None => ()
}
result_idx += 1
}
let then_evidence = match
dae_mixed_multi_result_return_arm_gc_evidence(
mod_, func_type_indices, func_locals, then_body, results, combined_candidates,
rejected,
) {
Some(evidence) => evidence
None => return None
}
let else_evidence : DaeMixedMultiResultReturnArmGcEvidence? = match
else_body_opt {
Some(else_body) =>
match
dae_mixed_multi_result_return_arm_gc_evidence(
mod_, func_type_indices, func_locals, else_body, results, combined_candidates,
rejected,
) {
Some(evidence) => Some(evidence)
None => return None
}
None => None
}
ignore(then_evidence.effect_free_fallthrough)
let else_has_return = match else_evidence {
Some(evidence) => {
ignore(evidence.effect_free_fallthrough)
evidence.has_return
}
None => false
}
if !then_evidence.has_return && !else_has_return {
return None
}
let mut changed = false
result_idx = 0
while result_idx < results.length() {
if !rejected[result_idx] {
match combined_candidates[result_idx] {
Some(candidate_type) =>
match
dae_refined_gc_candidate_ref_type(
mod_,
results[result_idx],
candidate_type,
) {
Some(refined_type) => {
next_results[result_idx] = refined_type
refined[result_idx] = true
changed = true
}
None => ()
}
None => ()
}
}
result_idx += 1
}
if !changed {
return None
}
let (rewritten_body, terminal_application) = match
dae_apply_result_rewrites_to_body(
body,
terminal_start,
terminal_rewrites,
active=Some(refined),
) {
Some(result) => result
None => return None
}
let mut body_changed = terminal_application.body_changed
let mut next_then_body = then_body
let mut if_body_changed = false
match
dae_apply_mixed_multi_result_return_arm_refinements(then_evidence, refined) {
Some(rewrite) =>
if rewrite.body_changed {
next_then_body = rewrite.body
if_body_changed = true
}
None => return None
}
let mut next_else_body = else_body_opt
match else_evidence {
Some(evidence) =>
match
dae_apply_mixed_multi_result_return_arm_refinements(evidence, refined) {
Some(rewrite) =>
if rewrite.body_changed {
next_else_body = Some(rewrite.body)
if_body_changed = true
}
None => return None
}
None => ()
}
if if_body_changed {
rewritten_body[if_idx] = @lib.Instruction::if_(
block_type, next_then_body, next_else_body,
)
body_changed = true
}
ignore(body_changed)
Some(rewritten_body)
}
///|
fn dae_apply_direct_caller_scope_rewrite(
funcs : Array[@lib.Func],
direct_caller_defs : Array[Int32],
rewrite_caller : (@lib.Func, Int32) -> DaeDirectCallerRewriteEvidence,
) -> DaeDirectCallerScopeApplication {
let rewritten_funcs = funcs.copy()
let changed_defs = Array::make(funcs.length(), false)
let mut calls_rewritten = 0
let mut changed = false
for caller_def in direct_caller_defs {
if caller_def >= 0 && caller_def < rewritten_funcs.length() {
let evidence = rewrite_caller(rewritten_funcs[caller_def], caller_def)
if !evidence.ok {
return {
funcs,
calls_rewritten: 0,
changed_defs: Array::make(funcs.length(), false),
changed: false,
ok: false,
}
}
calls_rewritten += evidence.calls_rewritten
if evidence.changed {
rewritten_funcs[caller_def] = evidence.func
changed_defs[caller_def] = true
changed = true
}
}
}
{ funcs: rewritten_funcs, calls_rewritten, changed_defs, changed, ok: true, }
}
///|
fn dae_signature_has_heap_ref(
params : Array[@lib.ValType],
results : Array[@lib.ValType],
) -> Bool {
for val_type in params {
match val_type {
@lib.RefTypeValType(_) => return true
_ => ()
}
}
for val_type in results {
match val_type {
@lib.RefTypeValType(_) => return true
_ => ()
}
}
false
}
///|
fn dae_collect_gc_parameter_lub_actuals_for_callers(
mod_ : @lib.Module,
funcs : Array[@lib.Func],
direct_caller_defs : Array[Int32],
type_indices : Array[@lib.TypeIdx],
imported : Int32,
callee_abs : Int32,
params : Array[@lib.ValType],
local_use : DaeLocalUse,
) -> DaeGcParameterLubActualCollection {
let candidates : Array[@lib.ValType?] = Array::make(params.length(), None)
let rejected = Array::make(params.length(), false)
let mut calls_seen = 0
for caller_def in direct_caller_defs {
if caller_def < 0 || caller_def >= funcs.length() {
return { candidates, rejected, calls_seen, collect_ok: false, }
}
let @lib.Func(caller_body_locals, @lib.Expr(caller_body)) = funcs[caller_def]
let caller_func_locals = match
dae_resolve_func_type(mod_, type_indices, imported + caller_def) {
Some(@lib.FuncType(caller_params, _)) =>
Some(@lib.FunctionLocals::new(caller_params, caller_body_locals))
None => None
}
let (caller_calls, ok) = dae_collect_direct_struct_default_actuals_instrs(
caller_body, callee_abs, params, local_use, candidates, rejected, 0, mod_,
type_indices, caller_func_locals,
)
calls_seen += caller_calls
if !ok {
return { candidates, rejected, calls_seen, collect_ok: false, }
}
}
{ candidates, rejected, calls_seen, collect_ok: true, }
}
///|
fn dae_apply_gc_parameter_lub_evidence(
mod_ : @lib.Module,
params : Array[@lib.ValType],
candidates : Array[@lib.ValType?],
rejected : Array[Bool],
calls_seen : Int32,
expected_call_count : Int32,
) -> DaeGcParameterLubEvidence {
let next_params = params.copy()
let refined_params = Array::make(params.length(), false)
if candidates.length() != params.length() ||
rejected.length() != params.length() ||
calls_seen != expected_call_count {
return { next_params, refined_params, complete: false, changed: false, }
}
let mut changed = false
let mut param_idx = 0
while param_idx < params.length() {
if !rejected[param_idx] {
match candidates[param_idx] {
Some(candidate_type) =>
match
dae_refined_gc_candidate_ref_type(
mod_,
params[param_idx],
candidate_type,
) {
Some(refined) => {
next_params[param_idx] = refined
refined_params[param_idx] = true
changed = true
}
None => ()
}
None => ()
}
}
param_idx += 1
}
{ next_params, refined_params, complete: true, changed, }
}
///|
fn dae_rewrite_nondefaultable_fixup_gets(
instrs : Array[@lib.Instruction],
first_fixup_idx : Int32,
needs_non_null : Array[Bool],
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
for instr in instrs {
match instr {
@lib.LocalGet(local_idx) | @lib.LocalTee(local_idx) => {
out.push(instr)
let slot = dae_local_idx_slot(local_idx) - first_fixup_idx
if slot >= 0 && slot < needs_non_null.length() && needs_non_null[slot] {
out.push(@lib.Instruction::ref_as_non_null())
}
}
@lib.Block(block_type, @lib.Expr(body)) =>
out.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(
dae_rewrite_nondefaultable_fixup_gets(
body, first_fixup_idx, needs_non_null,
),
),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
out.push(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
dae_rewrite_nondefaultable_fixup_gets(
body, first_fixup_idx, needs_non_null,
),
),
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
out.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
dae_rewrite_nondefaultable_fixup_gets(
body, first_fixup_idx, needs_non_null,
),
),
),
)
@lib.If(block_type, then_body, else_body) =>
out.push(
@lib.Instruction::if_(
block_type,
dae_rewrite_nondefaultable_fixup_gets(
then_body, first_fixup_idx, needs_non_null,
),
match else_body {
Some(body) =>
Some(
dae_rewrite_nondefaultable_fixup_gets(
body, first_fixup_idx, needs_non_null,
),
)
None => None
},
),
)
_ => out.push(instr)
}
}
out
}
///|
fn dae_apply_reused_gc_param_fixups(
params : Array[@lib.ValType],
refined_params : Array[Bool],
local_use : DaeLocalUse,
locals : @lib.Locals,
body : Array[@lib.Instruction],
) -> (@lib.Locals, Array[@lib.Instruction], Bool)? {
let local_map = Array::make(params.length() + locals.length(), 0)
let mut idx = 0
while idx < local_map.length() {
local_map[idx] = idx
idx += 1
}
let fixup_types : Array[@lib.ValType] = []
let fixup_needs_non_null : Array[Bool] = []
let fixup_params : Array[Int32] = []
let mut param_idx = 0
while param_idx < params.length() {
if param_idx < refined_params.length() &&
refined_params[param_idx] &&
param_idx < local_use.writes.length() &&
local_use.writes[param_idx] {
match params[param_idx] {
@lib.RefTypeValType(@lib.HeapTypeRefType(nullable, exact, heap_type)) => {
let fixup_idx = params.length() +
locals.length() +
fixup_types.length()
local_map[param_idx] = fixup_idx
fixup_types.push(
if nullable {
params[param_idx]
} else {
@lib.ValType::ref_type(@lib.RefType::new(true, heap_type, exact~))
},
)
fixup_needs_non_null.push(!nullable)
fixup_params.push(param_idx)
}
_ => return None
}
}
param_idx += 1
}
if fixup_types.length() == 0 {
return Some((locals, body, false))
}
let first_fixup_idx = params.length() + locals.length()
let remapped_body = dae_rewrite_locals_instrs(body, local_map)
let rewritten_body = dae_rewrite_nondefaultable_fixup_gets(
remapped_body, first_fixup_idx, fixup_needs_non_null,
)
let next_body : Array[@lib.Instruction] = []
idx = 0
while idx < fixup_params.length() {
let fixup_idx = first_fixup_idx + idx
next_body.push(
@lib.Instruction::local_get(
@lib.LocalIdx::new(fixup_params[idx].reinterpret_as_uint()),
),
)
next_body.push(
@lib.Instruction::local_set(
@lib.LocalIdx::new(fixup_idx.reinterpret_as_uint()),
),
)
idx += 1
}
for instr in rewritten_body {
next_body.push(instr)
}
let next_locals = @lib.Locals::from_types([])
next_locals.append(locals)
next_locals.append(@lib.Locals::from_types(fixup_types))
Some((next_locals, next_body, true))
}
///|
fn dae_try_refine_direct_gc_types(
mod_ : @lib.Module,
callee_abs : Int32,
direct_call_count : Int32,
direct_caller_defs : Array[Int32],
refine_params~ : Bool = true,
refine_results~ : Bool = true,
) -> DaePassResult {
if direct_call_count == 0 {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let imported = dae_imported_func_count(mod_)
let def_idx = callee_abs - imported
let type_indices = dae_collect_func_type_indices(mod_)
let func_type = match dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(ft) => ft
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let @lib.FuncType(params, results) = func_type
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if def_idx < 0 || def_idx >= funcs.length() {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
let local_use = dae_local_use_for_callee(
funcs[def_idx],
params.length() + locals.length(),
callee_abs,
params.length(),
)
let mut next_params = params.copy()
let next_results = results.copy()
let mut refined_params = Array::make(params.length(), false)
let mut rewritten_funcs = funcs.copy()
let mut caller_body_changed = false
let mut next_body = body
let mut next_locals = locals
let mut param_fixup_body_changed = false
let mut result_body_changed = false
let mut rewrite_type_sec : @lib.TypeSec? = None
let mut changed = false
if refine_params && params.length() > 0 {
let actuals = dae_collect_gc_parameter_lub_actuals_for_callers(
mod_, funcs, direct_caller_defs, type_indices, imported, callee_abs, params,
local_use,
)
let parameter_evidence = dae_apply_gc_parameter_lub_evidence(
mod_,
params,
actuals.candidates,
actuals.rejected,
actuals.calls_seen,
if actuals.collect_ok {
direct_call_count
} else {
-1
},
)
if parameter_evidence.complete {
next_params = parameter_evidence.next_params
refined_params = parameter_evidence.refined_params
}
if parameter_evidence.changed {
changed = true
let rewrite_caller = fn(caller_func, _) {
let @lib.Func(caller_locals, @lib.Expr(caller_body)) = caller_func
let (rewritten_body, calls, ok, body_changed) = dae_rewrite_refined_gc_actuals_instrs(
caller_body, callee_abs, refined_params, next_params, 0, mod_, type_indices,
)
let rewritten_func = if body_changed {
@lib.Func::new(caller_locals, @lib.Expr::new(rewritten_body))
} else {
caller_func
}
{
func: rewritten_func,
calls_rewritten: calls,
changed: body_changed,
ok,
}
}
let application = dae_apply_direct_caller_scope_rewrite(
funcs, direct_caller_defs, rewrite_caller,
)
if !application.ok || application.calls_rewritten != direct_call_count {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
rewritten_funcs = application.funcs
caller_body_changed = application.changed
if def_idx >= 0 &&
def_idx < application.changed_defs.length() &&
application.changed_defs[def_idx] {
let @lib.Func(_, @lib.Expr(rewritten_body)) = rewritten_funcs[def_idx]
next_body = rewritten_body
}
match
dae_apply_reused_gc_param_fixups(
params, refined_params, local_use, locals, next_body,
) {
Some((fixed_locals, fixed_body, body_changed)) => {
next_locals = fixed_locals
next_body = fixed_body
param_fixup_body_changed = body_changed
}
None =>
return {
mod_out: mod_,
touched: [],
changed: false,
primary_def: -1,
}
}
}
}
if refine_results {
let result_func_locals = Some(@lib.FunctionLocals::new(next_params, locals))
if results.length() == 1 {
match
dae_direct_gc_result_candidate_body(
mod_,
type_indices,
result_func_locals,
body,
results[0],
) {
Some((candidate_type, rewritten_body)) =>
match
dae_refined_gc_candidate_ref_type(mod_, results[0], candidate_type) {
Some(refined) => {
next_results[0] = refined
changed = true
match rewritten_body {
Some(instrs) => {
next_body = instrs
result_body_changed = true
}
None => ()
}
}
None => ()
}
None => ()
}
} else {
match
dae_try_refine_mixed_multi_result_if_fallthrough_body(
mod_, type_indices, result_func_locals, body, results, next_results,
) {
Some(rewritten_body) => {
next_body = rewritten_body
result_body_changed = true
changed = true
}
None => ()
}
match
dae_try_refine_multi_result_single_if_operand_body(
mod_, type_indices, result_func_locals, body, results, next_results,
) {
Some(rewritten_body) => {
next_body = rewritten_body
result_body_changed = true
changed = true
}
None => ()
}
match
dae_try_refine_multi_result_select_operand_body(
mod_, type_indices, result_func_locals, body, results, next_results,
) {
Some(rewritten_body) => {
next_body = rewritten_body
result_body_changed = true
changed = true
}
None => ()
}
match mod_.type_sec {
Some(type_sec) => {
let active_type_sec = match rewrite_type_sec {
Some(current_type_sec) => current_type_sec
None => type_sec
}
match
dae_try_refine_parametric_typeidx_multi_result_terminal_control_body(
mod_, active_type_sec, type_indices, result_func_locals, body, results,
next_results,
) {
Some((next_type_sec, rewritten_body)) => {
rewrite_type_sec = Some(next_type_sec)
next_body = rewritten_body
result_body_changed = true
changed = true
}
None => ()
}
}
None => ()
}
if body.length() > 0 {
match body[body.length() - 1] {
@lib.If(block_type, then_body, Some(else_body)) =>
match
dae_control_block_type_zero_input_output_count_evidence(
block_type,
mod_.type_sec,
results.length(),
) {
Some(_) =>
match
dae_multi_result_if_pair_gc_refinement_evidence(
mod_, type_indices, result_func_locals, then_body, else_body,
results,
) {
Some(evidence) => {
let pair_evidence = evidence.pair
match
dae_apply_control_result_pair_refinements(
pair_evidence, next_results,
) {
Some(_) => changed = true
None => ()
}
match mod_.type_sec {
Some(type_sec) => {
let active_type_sec = match rewrite_type_sec {
Some(current_type_sec) => current_type_sec
None => type_sec
}
let application = match
dae_rebuild_typeidx_control_in_body(
active_type_sec,
body,
body.length() - 1,
2,
[],
pair_evidence.refined_results,
pair_evidence.rewritten_then_body,
Some(pair_evidence.rewritten_else_body),
) {
Some(application) => application
None =>
return {
mod_out: mod_,
touched: [],
changed: false,
primary_def: -1,
}
}
rewrite_type_sec = Some(application.type_sec)
next_body = application.body
result_body_changed = true
}
None => ()
}
ignore(evidence.then_slice)
ignore(evidence.else_slice)
}
None => ()
}
_ => ()
}
_ => ()
}
}
if body.length() >= 2 {
match (body[body.length() - 2], body[body.length() - 1]) {
(@lib.If(block_type, then_body, Some(else_body)), @lib.Unreachable) =>
match
dae_control_block_type_zero_input_output_count_evidence(
block_type,
mod_.type_sec,
0,
) {
Some(_) =>
match
dae_multi_result_if_pair_gc_refinement_evidence(
mod_,
type_indices,
result_func_locals,
then_body,
else_body,
results,
require_terminal_return=true,
) {
Some(evidence) => {
let pair_evidence = evidence.pair
match
dae_apply_control_result_pair_refinements(
pair_evidence, next_results,
) {
Some(_) => changed = true
None => ()
}
if evidence.if_body_changed {
let rewritten = @lib.Instruction::if_(
block_type,
pair_evidence.rewritten_then_body,
Some(pair_evidence.rewritten_else_body),
)
let rewritten_body = body.copy()
rewritten_body[rewritten_body.length() - 2] = rewritten
next_body = rewritten_body
result_body_changed = true
}
ignore(evidence.then_slice)
ignore(evidence.else_slice)
}
None => ()
}
_ => ()
}
_ => ()
}
}
let mut candidate_body = body
let mut flatten_block_carrier = false
let mut preserve_loop_carrier = false
match body {
[@lib.Block(block_type, @lib.Expr(block_body))] =>
match
dae_control_block_type_zero_input_output_count_evidence(
block_type,
mod_.type_sec,
results.length(),
) {
Some(_) => {
candidate_body = block_body
flatten_block_carrier = true
}
None => ()
}
[@lib.Loop(block_type, @lib.Expr(loop_body))] =>
match
dae_control_block_type_zero_input_output_count_evidence(
block_type,
mod_.type_sec,
results.length(),
) {
Some(_) => {
candidate_body = loop_body
preserve_loop_carrier = true
}
None => ()
}
_ => ()
}
if flatten_block_carrier && !preserve_loop_carrier {
let mut keep_unwrapping = true
while keep_unwrapping {
keep_unwrapping = false
match candidate_body {
[@lib.Block(block_type, @lib.Expr(block_body))] =>
match
dae_control_block_type_zero_input_output_count_evidence(
block_type,
mod_.type_sec,
results.length(),
) {
Some(_) if dae_effect_free_body_evidence(block_body) is Some(_) ||
dae_terminal_return_body_evidence(block_body) is Some(_) => {
candidate_body = block_body
keep_unwrapping = true
}
_ => ()
}
_ => ()
}
}
}
let mut result_start = -1
let mut result_end = candidate_body.length()
if candidate_body.length() > 0 {
match candidate_body[candidate_body.length() - 1] {
@lib.Return => result_end = candidate_body.length() - 1
_ => ()
}
}
if result_end >= results.length() {
let candidate_start = result_end - results.length()
if dae_trivially_dropped_prefix_evidence(
candidate_body, candidate_start,
)
is Some(_) {
result_start = candidate_start
}
}
if result_start >= 0 &&
dae_body_prefix_has_function_return(candidate_body, result_start) {
result_start = -1
}
if result_start >= 0 &&
!(preserve_loop_carrier && result_end != candidate_body.length()) {
let result_slice = match
dae_make_multi_result_control_slice(
candidate_body,
result_start,
results.length(),
0,
flatten_block_carrier,
false,
) {
Some(slice) => slice
None =>
return {
mod_out: mod_,
touched: [],
changed: false,
primary_def: -1,
}
}
let mut rewritten_body = candidate_body
let mut multi_result_changed = false
let mut multi_result_body_changed = false
match
dae_control_result_slice_gc_evidence(
mod_,
type_indices,
result_func_locals,
result_slice,
results,
require_refined=true,
) {
Some(evidence) => {
rewritten_body = evidence.rewritten_body
multi_result_body_changed = evidence.body_changed
match
dae_apply_control_result_single_refinements(
evidence, next_results,
) {
Some(_) => {
changed = true
multi_result_changed = true
}
None => ()
}
}
None => ()
}
if multi_result_changed &&
(
multi_result_body_changed ||
flatten_block_carrier ||
preserve_loop_carrier
) {
if preserve_loop_carrier {
match mod_.type_sec {
Some(type_sec) => {
let active_type_sec = match rewrite_type_sec {
Some(current_type_sec) => current_type_sec
None => type_sec
}
let application = match
dae_rebuild_typeidx_control_in_body(
active_type_sec,
body,
0,
1,
[],
next_results,
rewritten_body,
None,
) {
Some(application) => application
None =>
return {
mod_out: mod_,
touched: [],
changed: false,
primary_def: -1,
}
}
rewrite_type_sec = Some(application.type_sec)
next_body = application.body
result_body_changed = true
}
None => ()
}
} else {
next_body = rewritten_body
result_body_changed = true
}
}
}
}
}
if !changed {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let update_mod = match rewrite_type_sec {
Some(type_sec) => mod_.with_type_sec(type_sec)
None => mod_
}
if result_body_changed || param_fixup_body_changed || caller_body_changed {
if result_body_changed || param_fixup_body_changed {
rewritten_funcs[def_idx] = @lib.Func::new(
next_locals,
@lib.Expr::new(next_body),
)
}
dae_finalize_module_code_rewrite(
update_mod,
def_idx,
rewritten_funcs,
next_params,
next_results,
DaeTouchModuleCodeCalleeOnly,
)
} else {
dae_try_update_signature_only(
update_mod, def_idx, next_params, next_results,
)
}
}
///|
fn dae_try_refine_direct_gc_types_for_def_with_facts(
mod_ : @lib.Module,
def_idx : Int32,
call_facts : DaeCurrentCallFacts,
refine_params : Bool,
refine_results : Bool,
) -> DaePassResult {
let imported = dae_imported_func_count(mod_)
let callee_abs = imported + def_idx
let boundary_facts = dae_call_boundary_facts_from_current(call_facts)
let candidate = match
dae_call_boundary_candidate(boundary_facts, callee_abs, def_idx~) {
Some(candidate) => candidate
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if !candidate.owned ||
(
refine_params &&
(
candidate.has_self_direct_caller ||
!candidate.allows_nonself_direct_rewrite
)
) {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
dae_try_refine_direct_gc_types(
mod_,
callee_abs,
candidate.direct_call_count,
candidate.direct_callers,
refine_params~,
refine_results~,
)
}
///|
fn dae_rewrite_materialized_const_suffix_calls_instrs(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
expected_suffix : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Int32, Bool) {
let out : Array[@lib.Instruction] = []
let mut calls = 0
let mut ok = true
let mut idx = 0
while idx < instrs.length() && ok {
match instrs[idx] {
@lib.Call(func_idx) if dae_func_idx_slot(func_idx) == callee_abs =>
if out.length() < expected_suffix.length() {
ok = false
} else {
let suffix_start = out.length() - expected_suffix.length()
let mut suffix_idx = 0
while suffix_idx < expected_suffix.length() && ok {
if !dae_materialized_const_equal(
out[suffix_start + suffix_idx],
expected_suffix[suffix_idx],
) {
ok = false
}
suffix_idx += 1
}
if ok {
suffix_idx = 0
while suffix_idx < expected_suffix.length() {
ignore(out.pop())
suffix_idx += 1
}
out.push(instrs[idx])
calls += 1
}
}
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten, nested_calls, nested_ok) = dae_rewrite_materialized_const_suffix_calls_instrs(
body, callee_abs, expected_suffix,
)
ok = nested_ok
calls += nested_calls
out.push(@lib.Instruction::block(block_type, @lib.Expr::new(rewritten)))
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten, nested_calls, nested_ok) = dae_rewrite_materialized_const_suffix_calls_instrs(
body, callee_abs, expected_suffix,
)
ok = nested_ok
calls += nested_calls
out.push(@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten)))
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten, nested_calls, nested_ok) = dae_rewrite_materialized_const_suffix_calls_instrs(
body, callee_abs, expected_suffix,
)
ok = nested_ok
calls += nested_calls
out.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten),
),
)
}
@lib.If(block_type, then_body, else_body) => {
let (then_rewritten, then_calls, then_ok) = dae_rewrite_materialized_const_suffix_calls_instrs(
then_body, callee_abs, expected_suffix,
)
ok = then_ok
calls += then_calls
let rewritten_else = match else_body {
Some(body) if ok => {
let (else_rewritten, else_calls, else_ok) = dae_rewrite_materialized_const_suffix_calls_instrs(
body, callee_abs, expected_suffix,
)
ok = else_ok
calls += else_calls
Some(else_rewritten)
}
Some(body) => Some(body)
None => None
}
out.push(
@lib.Instruction::if_(block_type, then_rewritten, rewritten_else),
)
}
_ => out.push(instrs[idx])
}
idx += 1
}
if !ok {
return ([], calls, false)
}
(out, calls, true)
}
///|
fn dae_try_rewrite_verified_materialized_const_suffix_candidate(
mod_ : @lib.Module,
callee_abs : Int32,
params : Array[@lib.ValType],
expected_suffix : Array[@lib.Instruction],
local_use : DaeLocalUse,
verified_calls : Int32,
verified_callers : Array[Int32],
) -> DaePassResult {
if verified_calls <= 0 ||
expected_suffix.length() <= 0 ||
expected_suffix.length() > params.length() ||
expected_suffix.any(fn(instr) { !dae_instr_is_materializable_const(instr) }) {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let graph = dae_current_boundary_graph_new(mod_)
if callee_abs < 0 || callee_abs >= graph.snapshot.function_types.length() {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let current_params = match graph.snapshot.function_types[callee_abs] {
Some(@lib.FuncType(current_params, _)) => current_params
_ => return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if current_params != params {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let suffix_start = params.length() - expected_suffix.length()
let remove = Array::make(params.length(), false)
let const_params : Array[@lib.Instruction?] = Array::make(
params.length(),
None,
)
let mut suffix_idx = 0
while suffix_idx < expected_suffix.length() {
remove[suffix_start + suffix_idx] = true
const_params[suffix_start + suffix_idx] = Some(expected_suffix[suffix_idx])
suffix_idx += 1
}
match
dae_build_scalar_unused_param_rewrite_plan(
mod_,
dae_collect_original_boundary_snapshot(mod_),
graph,
callee_abs,
remove,
local_use,
verified_calls,
verified_callers,
materialized_params=Some(const_params),
verified_materialized_suffix=Some(expected_suffix),
) {
Some(plan) => {
let application = dae_apply_rewrite_plan(mod_, graph, plan)
if application.applied {
application.result
} else {
{ mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
}
None => { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
}
///|
fn dae_direct_caller_cycle_flags(
facts : DaeCallBoundaryFacts,
imported : Int32,
defined : Int32,
) -> Array[Bool] {
let callers_by_def = dae_dependency_adjacency_new(defined)
let mut callee_def = 0
while callee_def < defined {
let callee_abs = imported + callee_def
if callee_abs >= 0 && callee_abs < facts.direct_callers.length() {
for caller_def in facts.direct_callers[callee_abs] {
if caller_def >= 0 && caller_def < defined {
// Direct callers are already unique per callee.
callers_by_def[callee_def].push(caller_def)
}
}
}
callee_def += 1
}
let (component_by_def, component_members) = dae_boundary_components(
callers_by_def,
)
let cycles = Array::make(defined, false)
let mut def_idx = 0
while def_idx < defined {
let component = component_by_def[def_idx]
if component >= 0 && component < component_members.length() {
cycles[def_idx] = component_members[component].length() > 1 ||
callers_by_def[def_idx].contains(def_idx)
}
def_idx += 1
}
cycles
}
///|
fn dae_boundary_has_direct_caller_cycle(
facts : DaeCallBoundaryFacts,
imported : Int32,
callee_abs : Int32,
) -> Bool {
if callee_abs < 0 || callee_abs >= facts.direct_callers.length() {
return false
}
let seen = Array::make(facts.direct_callers.length(), false)
let pending : Array[Int32] = []
for caller_def in facts.direct_callers[callee_abs] {
pending.push(imported + caller_def)
}
while pending.length() > 0 {
let current = pending.pop().unwrap()
if current == callee_abs {
return true
}
if current < 0 || current >= seen.length() || seen[current] {
continue
}
seen[current] = true
for caller_def in facts.direct_callers[current] {
pending.push(imported + caller_def)
}
}
false
}
///|
fn dae_batchable_uniform_literal(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.V128Const(_)
| @lib.RefFunc(_) => true
_ => false
}
}
///|
fn dae_try_apply_unread_param_batch(
mod_ : @lib.Module,
active_defs~ : Array[Bool]? = None,
require_active_callers~ : Bool = false,
allow_uniform_params~ : Bool = false,
scalar_callee_cleanup~ : Bool = false,
boundary_snapshot~ : DaeModuleBoundarySnapshot? = None,
cleanup_targets~ : Array[Bool]? = None,
perf~ : HotPerfSession? = None,
stable_callsite_resolutions~ : Array[Int32]? = None,
forwarded_local_use_scans~ : Array[Int32]? = None,
forwarded_uniform_resolutions~ : Array[Int32]? = None,
uniform_visited_allocations~ : Array[Int32]? = None,
) -> DaePassResult {
let snapshot_start_us = perf_start_timer_opt(
perf, "detail:dae:parameter-batch:snapshot",
)
let snapshot = match boundary_snapshot {
Some(snapshot) => snapshot
None => dae_collect_module_boundary_snapshot(mod_)
}
perf_stop_timer_opt(
perf, "detail:dae:parameter-batch:snapshot", snapshot_start_us,
)
let imported = snapshot.imported
let defined = snapshot.defined
// Definition-count-independent batch candidate construction. The family
// transaction remains the only production commit boundary.
let boundary_facts = snapshot.boundary_facts
let uniform_resolution_cache = DaeUniformResolutionCache::new(
defined,
snapshot.total,
local_use_scans=forwarded_local_use_scans,
forwarded_resolution_scans=forwarded_uniform_resolutions,
function_infos=Some(snapshot.function_infos),
stable_callsite_resolutions~,
visited_allocations=uniform_visited_allocations,
)
let mut type_sec = match mod_.type_sec {
Some(type_sec) => type_sec
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let type_idxs = match mod_.func_sec {
Some(@lib.FuncSec(type_idxs)) => type_idxs.copy()
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let current_type_indices = snapshot.type_indices.copy()
let type_lookup = dae_simple_func_type_lookup(type_sec)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if type_idxs.length() != funcs.length() {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let tail_boundary = dae_tail_call_boundary_evidence(funcs)
if tail_boundary.blocks_direct_call_rewrite {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let plans : Array[DaeBoundaryParamBatchPlan] = []
let candidate_start_us = perf_start_timer_opt(
perf, "detail:dae:parameter-batch:candidates",
)
let cycle_start_us = perf_start_timer_opt(
perf, "detail:dae:parameter-batch:cycle-check",
)
let direct_caller_cycles = dae_direct_caller_cycle_flags(
boundary_facts, imported, defined,
)
let cycle_check_us = perf_elapsed_since_start_opt(perf, cycle_start_us)
let mut local_use_us = 0UL
let mut uniform_actual_us = 0UL
for def_idx = 0; def_idx < defined; def_idx = def_idx + 1 {
match active_defs {
Some(active) if def_idx >= active.length() || !active[def_idx] => continue
_ => ()
}
let callee_abs = imported + def_idx
let boundary = match
dae_call_boundary_candidate(boundary_facts, callee_abs, def_idx~) {
Some(candidate) => candidate
None => continue
}
if !boundary.owned ||
!boundary.allows_nonself_direct_rewrite ||
boundary.has_self_direct_caller {
continue
}
if def_idx < direct_caller_cycles.length() && direct_caller_cycles[def_idx] {
continue
}
if require_active_callers {
let active = match active_defs {
Some(active) => active
None => continue
}
let mut callers_active = true
for caller_def in boundary.direct_callers {
if caller_def < 0 ||
caller_def >= active.length() ||
!active[caller_def] {
callers_active = false
break
}
}
if !callers_active {
continue
}
}
let @lib.FuncType(params, results) = match
dae_resolve_func_type(mod_, snapshot.type_indices, callee_abs) {
Some(func_type) => func_type
None => continue
}
if params.length() == 0 {
continue
}
let @lib.Func(locals, _) = funcs[def_idx]
let local_use_start_us = perf_start_timer_opt(
perf, "detail:dae:parameter-batch:local-use",
)
let local_use = dae_local_use_for_callee(
funcs[def_idx],
params.length() + locals.length(),
callee_abs,
params.length(),
)
local_use_us += perf_elapsed_since_start_opt(perf, local_use_start_us)
let remove = Array::make(params.length(), false)
let const_params : Array[@lib.Instruction?] = Array::make(
params.length(),
None,
)
let collect_const_params = Array::make(params.length(), false)
let mut has_collect_const_param = false
for param_idx = 0; param_idx < params.length(); param_idx = param_idx + 1 {
if allow_uniform_params &&
local_use.gets[param_idx] &&
!local_use.writes[param_idx] {
collect_const_params[param_idx] = true
has_collect_const_param = true
}
}
let uniform_start_us = perf_start_timer_opt(
perf, "detail:dae:parameter-batch:uniform-actuals",
)
let uniform_const_params = if has_collect_const_param {
dae_collect_uniform_const_actuals_from_current_call_facts(
mod_,
callee_abs,
params.length(),
snapshot.type_indices,
boundary_facts,
snapshot.function_infos,
collect_params=Some(collect_const_params),
prefer_stable_paths=true,
stable_callsite_resolutions~,
uniform_resolution_cache=Some(uniform_resolution_cache),
)
} else {
None
}
uniform_actual_us += perf_elapsed_since_start_opt(perf, uniform_start_us)
let mut any = false
for param_idx = 0; param_idx < params.length(); param_idx = param_idx + 1 {
if !local_use.gets[param_idx] {
remove[param_idx] = true
any = true
} else if allow_uniform_params && !local_use.writes[param_idx] {
match uniform_const_params {
Some(values) =>
match values[param_idx] {
Some(value) if dae_batchable_uniform_literal(value) => {
remove[param_idx] = true
const_params[param_idx] = Some(value)
any = true
}
_ => ()
}
None => ()
}
}
}
if any {
plans.push({
def_idx,
callee_abs,
params,
results,
remove,
const_params,
local_use,
direct_call_count: boundary.direct_call_count,
direct_caller_defs: boundary.direct_callers,
})
}
}
perf_stop_timer_opt(
perf, "detail:dae:parameter-batch:candidates", candidate_start_us,
)
perf_record_timer_elapsed_opt(
perf, "detail:dae:parameter-batch:cycle-check", cycle_check_us,
)
perf_record_timer_elapsed_opt(
perf, "detail:dae:parameter-batch:local-use", local_use_us,
)
perf_record_timer_elapsed_opt(
perf, "detail:dae:parameter-batch:uniform-actuals", uniform_actual_us,
)
if plans.length() == 0 {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let commit_start_us = perf_start_timer_opt(
perf, "detail:dae:parameter-batch:commit",
)
let rewritten_funcs = funcs.copy()
let touched = Array::make(defined, false)
let mut applied = 0
let mut first_def = -1
for plan in plans {
let current_view = mod_
.with_type_sec(type_sec)
.with_func_sec(@lib.FuncSec::new(type_idxs))
.with_code_sec(@lib.CodeSec::new(rewritten_funcs))
let localized_params = Array::make(plan.params.length(), false)
let caller_rewrites : Array[(Int32, @lib.Func)] = []
let mut calls_rewritten = 0
let mut ok = true
for caller_def in plan.direct_caller_defs {
if caller_def < 0 || caller_def >= rewritten_funcs.length() {
ok = false
break
}
let caller_params = match
dae_type_sec_func_type(type_sec, type_idxs[caller_def]) {
Some(@lib.FuncType(params, _)) => params
None => {
ok = false
break
}
}
let @lib.Func(caller_locals, @lib.Expr(caller_body)) = rewritten_funcs[caller_def]
let localization = {
first_scratch_local: dae_next_free_local_idx(
caller_body,
caller_params.length() + caller_locals.length(),
),
param_types: plan.params,
scratch_local_types: [],
localized_params,
}
let rewrite = dae_rewrite_calls_instrs(
caller_body,
plan.callee_abs,
plan.remove,
0,
current_view,
current_type_indices,
localization=Some(localization),
allow_stack_carried_void_effects=true,
)
if !rewrite.ok {
ok = false
break
}
calls_rewritten += rewrite.calls_rewritten
if rewrite.calls_rewritten > 0 {
let next_locals = caller_locals.copy()
next_locals.append_types(localization.scratch_local_types)
caller_rewrites.push(
(
caller_def,
@lib.Func::new(next_locals, @lib.Expr::new(rewrite.instrs)),
),
)
}
}
if !ok || calls_rewritten != plan.direct_call_count {
continue
}
let callee_rewrite = match
dae_removed_param_callee_rewrite_evidence(
rewritten_funcs[plan.def_idx],
plan.params,
plan.remove,
plan.const_params,
plan.local_use,
localized_params,
) {
Some(evidence) => evidence
None => continue
}
// Scalar work can replace a uniquely owned signature in place. Check the
// same pre-rewrite module here before publishing any caller/body changes.
let scalar_type_update = if scalar_callee_cleanup {
Some(
dae_update_defined_func_type(
current_view,
type_sec,
type_idxs,
plan.def_idx,
callee_rewrite.kept_params,
plan.results,
),
)
} else {
None
}
for caller_rewrite in caller_rewrites {
let (caller_def, caller_func) = caller_rewrite
rewritten_funcs[caller_def] = caller_func
if caller_def >= 0 && caller_def < touched.length() {
touched[caller_def] = true
}
}
rewritten_funcs[plan.def_idx] = dae_finalize_removed_parameter_callee(
callee_rewrite, scalar_callee_cleanup,
)
if scalar_callee_cleanup {
rewritten_funcs[plan.def_idx] = dae_trim_parameter_rewrite_func(
rewritten_funcs[plan.def_idx],
)
for caller_rewrite in caller_rewrites {
let (caller_def, _) = caller_rewrite
rewritten_funcs[caller_def] = dae_trim_parameter_rewrite_func(
rewritten_funcs[caller_def],
)
}
}
let (next_type_sec, next_type_idx) = match scalar_type_update {
Some(update) => update
None => {
let (next_params, next_results) = dae_absolutize_signature(
type_sec,
type_idxs[plan.def_idx],
callee_rewrite.kept_params,
plan.results,
)
let type_key = dae_simple_func_type_key(next_params, next_results)
match type_lookup.get(type_key) {
Some(existing) => (type_sec, existing)
None => {
let (next_type_sec, next_idx) = dae_append_new_func_type(
type_sec, next_params, next_results,
)
type_lookup.set(type_key, next_idx)
(next_type_sec, next_idx)
}
}
}
}
type_sec = next_type_sec
type_idxs[plan.def_idx] = next_type_idx
current_type_indices[imported + plan.def_idx] = next_type_idx
touched[plan.def_idx] = true
match cleanup_targets {
Some(targets) if plan.def_idx < targets.length() =>
targets[plan.def_idx] = true
_ => ()
}
if first_def < 0 {
first_def = plan.def_idx
}
applied += 1
}
perf_stop_timer_opt(
perf, "detail:dae:parameter-batch:commit", commit_start_us,
)
if applied == 0 {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
{
mod_out: mod_
.with_type_sec(type_sec)
.with_func_sec(@lib.FuncSec::new(type_idxs))
.with_code_sec(@lib.CodeSec::new(rewritten_funcs))
.without_name_sec(),
touched,
changed: true,
primary_def: first_def,
}
}
///|
fn dae_apply_unread_param_batch_transaction(
original : DaeOriginalBoundarySnapshot,
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
) -> DaeCoreResultApplication {
let mut current_mod = current
let mut merged_touched = touched
let mut merged_changed = changed
let mut merged_effects = dae_commit_effects_empty(
dae_current_boundary_graph_new(current).snapshot.defined,
)
let mut first_def = -1
let mut applied = false
let initial_defined = merged_effects.structurally_touched.length()
for def_idx = 0; def_idx < initial_defined; def_idx = def_idx + 1 {
let graph = dae_current_boundary_graph_new(current_mod)
if def_idx >= graph.snapshot.defined {
continue
}
let active = Array::make(graph.snapshot.defined, false)
active[def_idx] = true
let candidate = dae_try_apply_unread_param_batch(
current_mod,
active_defs=Some(active),
)
if !candidate.changed {
continue
}
let candidate_funcs = match candidate.mod_out.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => continue
}
let candidate_effects = dae_commit_effects_empty(graph.snapshot.defined)
let mut touched_def = 0
while touched_def < candidate.touched.length() &&
touched_def < graph.snapshot.defined {
if candidate.touched[touched_def] {
candidate_effects.structurally_touched[touched_def] = true
candidate_effects.worth_decided[touched_def] = true
candidate_effects.worth_optimizing[touched_def] = true
if touched_def != def_idx && touched_def < candidate_funcs.length() {
let @lib.Func(old_locals, _) = graph.snapshot.funcs[touched_def]
let @lib.Func(next_locals, _) = candidate_funcs[touched_def]
if next_locals.length() > old_locals.length() {
candidate_effects.body_localized[touched_def] = true
}
}
}
touched_def += 1
}
let candidate_effects = {
..candidate_effects,
type_section_changed: true,
metadata_changed: true,
invalidation_domains: dae_invalidation_domains_for_work_family(
DaeUnusedParameterWork,
),
decision_reasons: [DaeCandidateProduced],
}
let item = dae_work_item_new(
graph,
dae_boundary_dependency_graph_new(graph),
def_idx,
dae_work_family_for_commit_effects(candidate_effects, def_idx),
)
let application = match
dae_process_precomputed_signature_work_item(
original, current_mod, merged_touched, merged_changed, graph, item, candidate,
candidate_effects,
) {
DaeWorkCommitted(value) => value
DaeWorkNoChange(_) | DaeWorkDeferred(_) | DaeWorkRequeue(_) => continue
}
current_mod = application.mod_out
merged_touched = application.touched
merged_changed = application.changed
merged_effects = dae_commit_effects_merge(
merged_effects,
application.effects,
)
if first_def < 0 {
first_def = def_idx
}
applied = true
}
{
mod_out: current_mod,
touched: merged_touched,
changed: merged_changed,
applied,
primary_def: first_def,
effects: merged_effects,
}
}
///|
fn dae_current_graph_has_dropped_result_candidate(
original : DaeOriginalBoundarySnapshot,
graph : DaeCurrentBoundaryGraph,
) -> Bool {
let snapshot = graph.snapshot
let facts = dae_boundary_facts_from_snapshots(original, graph)
let mut def_idx = 0
while def_idx < snapshot.defined {
let callee_abs = snapshot.imported + def_idx
let candidate = match
dae_dropped_result_candidate_from_boundary_facts(
snapshot.boundary_facts,
callee_abs,
) {
Some(candidate) => candidate
None => {
def_idx += 1
continue
}
}
let self_calls = if callee_abs >= 0 &&
callee_abs < snapshot.function_infos.length() {
dae_self_call_counts_from_function_info(
snapshot.function_infos[callee_abs],
def_idx,
).0
} else {
dae_count_self_calls(snapshot.funcs[def_idx], callee_abs)
}
if candidate.owned &&
candidate.direct_call_count != self_calls &&
dae_can_remove_function_result(facts, callee_abs) {
match snapshot.function_types[callee_abs] {
Some(@lib.FuncType(_, results)) if results.length() == 1 => return true
_ => ()
}
}
def_idx += 1
}
false
}
///|
fn dae_dropped_result_followup_defs(
graph : DaeCurrentBoundaryGraph,
effects : DaeCommitEffects,
) -> Array[Bool] {
let snapshot = graph.snapshot
let active = Array::make(snapshot.defined, false)
let mut callee_def = 0
while callee_def < snapshot.defined {
let callee_abs = snapshot.imported + callee_def
if callee_abs >= 0 && callee_abs < snapshot.function_infos.length() {
for callsite in snapshot.function_infos[callee_abs].calls {
let caller_def = callsite.caller_def
if callsite.active &&
caller_def >= 0 &&
caller_def < effects.result_changed.length() &&
effects.result_changed[caller_def] {
active[callee_def] = true
break
}
}
}
callee_def += 1
}
active
}
///|
fn dae_try_apply_dropped_result_batch(
mod_ : @lib.Module,
original_exposure : Array[DaeFunctionExposure],
graph : DaeCurrentBoundaryGraph,
active_defs~ : Array[Bool]? = None,
topology_preserved_report~ : Array[Bool] = [],
) -> DaePassResult {
let snapshot = graph.snapshot
let imported = snapshot.imported
let defined = snapshot.defined
// Definition-count-independent batch candidate construction. Exact
// result-observer work remains authoritative through the dispatcher.
let mut type_sec = match mod_.type_sec {
Some(type_sec) => type_sec
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let type_idxs = match mod_.func_sec {
Some(@lib.FuncSec(type_idxs)) => type_idxs.copy()
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let current_type_indices = snapshot.type_indices.copy()
let type_lookup = dae_simple_func_type_lookup(type_sec)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let void_callers : Array[Bool] = []
for def_idx = 0; def_idx < defined; def_idx = def_idx + 1 {
let callee_abs = imported + def_idx
void_callers.push(
match dae_resolve_func_type(mod_, snapshot.type_indices, callee_abs) {
Some(@lib.FuncType(_, results)) => results.length() == 0
None => false
},
)
}
let plans : Array[DaeDroppedResultBatchPlan] = []
for def_idx = 0; def_idx < defined; def_idx = def_idx + 1 {
match active_defs {
Some(active) if def_idx >= active.length() || !active[def_idx] => continue
_ => ()
}
let callee_abs = imported + def_idx
let candidate = match
dae_dropped_result_candidate_from_boundary_facts(
snapshot.boundary_facts,
callee_abs,
) {
Some(candidate) => candidate
None => continue
}
if !candidate.owned ||
candidate.direct_call_count ==
dae_count_self_calls(funcs[def_idx], callee_abs) ||
!dae_can_remove_function_result_with_exposure(
original_exposure, graph, callee_abs,
) {
continue
}
let @lib.FuncType(params, results) = match
dae_resolve_func_type(mod_, snapshot.type_indices, callee_abs) {
Some(func_type) => func_type
None => continue
}
if results.length() != 1 {
continue
}
plans.push({
def_idx,
callee_abs,
params,
direct_call_count: candidate.direct_call_count,
direct_caller_defs: candidate.direct_callers,
})
}
if plans.length() == 0 {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let rewritten_funcs = funcs.copy()
let touched = Array::make(defined, false)
let mut first_def = -1
let mut applied = 0
let mut topology_changed = false
for plan in plans {
let scope = dae_dropped_result_scope_from_optional(
rewritten_funcs.length(),
Some(plan.direct_caller_defs),
)
let current_evidence = dae_dropped_result_scope_direct_call_evidence_with_void_returns(
rewritten_funcs,
mod_.type_sec,
void_callers,
scope,
plan.callee_abs,
)
if current_evidence.direct_call_count != plan.direct_call_count ||
current_evidence.dropped_direct_call_count != plan.direct_call_count ||
current_evidence.has_live_undropped_direct_call {
continue
}
let mut rewritten_call_count = 0
for caller_def in scope.caller_defs {
if caller_def >= 0 && caller_def < rewritten_funcs.length() {
let @lib.Func(caller_locals, @lib.Expr(caller_body)) = rewritten_funcs[caller_def]
let (next_body, count) = dae_rewrite_dropped_result_calls_with_void_returns_instrs(
caller_body,
plan.callee_abs,
mod_.type_sec,
caller_def < void_callers.length() && void_callers[caller_def],
true,
)
if count > 0 {
rewritten_funcs[caller_def] = @lib.Func::new(
caller_locals,
@lib.Expr::new(next_body),
)
}
rewritten_call_count += count
}
}
if rewritten_call_count != plan.direct_call_count {
continue
}
topology_changed = dae_repair_dead_suffix_calls_in_no_param_scope_except(
rewritten_funcs,
current_type_indices,
type_sec,
imported,
plan.callee_abs,
plan.def_idx,
scope,
) ||
topology_changed
let @lib.Func(callee_locals, @lib.Expr(callee_body)) = rewritten_funcs[plan.def_idx]
let next_body = callee_body.copy()
next_body.push(@lib.Instruction::drop())
rewritten_funcs[plan.def_idx] = dae_simplify_to_unreachable_when_prefix_unobservable(
@lib.Func::new(callee_locals, @lib.Expr::new(next_body)),
)
let (next_params, next_results) = dae_absolutize_signature(
type_sec,
type_idxs[plan.def_idx],
plan.params,
[],
)
let type_key = dae_simple_func_type_key(next_params, next_results)
let next_type_idx = match type_lookup.get(type_key) {
Some(existing) => existing
None => {
let (next_type_sec, next_idx) = dae_append_new_func_type(
type_sec, next_params, next_results,
)
type_sec = next_type_sec
type_lookup.set(type_key, next_idx)
next_idx
}
}
type_idxs[plan.def_idx] = next_type_idx
current_type_indices[imported + plan.def_idx] = next_type_idx
touched[plan.def_idx] = true
for caller_def in plan.direct_caller_defs {
if caller_def >= 0 && caller_def < touched.length() {
touched[caller_def] = true
}
}
if first_def < 0 {
first_def = plan.def_idx
}
applied += 1
}
if applied == 0 {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
topology_preserved_report.push(!topology_changed)
{
mod_out: mod_
.with_type_sec(type_sec)
.with_func_sec(@lib.FuncSec::new(type_idxs))
.with_code_sec(@lib.CodeSec::new(rewritten_funcs))
.without_name_sec(),
touched,
changed: true,
primary_def: first_def,
}
}
///|
fn dae_merge_rebased_stable_calls(
previous : Array[DaeStableCallsite],
refreshed : Array[DaeStableCallsite],
changed_defs : Array[Bool],
) -> Array[DaeStableCallsite] {
let merged : Array[DaeStableCallsite] = []
let mut previous_idx = 0
let mut refreshed_idx = 0
while previous_idx < previous.length() || refreshed_idx < refreshed.length() {
while previous_idx < previous.length() {
let caller_def = previous[previous_idx].caller_def
if caller_def >= 0 &&
caller_def < changed_defs.length() &&
changed_defs[caller_def] {
previous_idx += 1
} else {
break
}
}
while refreshed_idx < refreshed.length() {
let caller_def = refreshed[refreshed_idx].caller_def
if caller_def < 0 ||
caller_def >= changed_defs.length() ||
!changed_defs[caller_def] {
refreshed_idx += 1
} else {
break
}
}
if previous_idx >= previous.length() {
while refreshed_idx < refreshed.length() {
merged.push(refreshed[refreshed_idx])
refreshed_idx += 1
}
break
}
if refreshed_idx >= refreshed.length() {
while previous_idx < previous.length() {
let callsite = previous[previous_idx]
let caller_def = callsite.caller_def
if caller_def < 0 ||
caller_def >= changed_defs.length() ||
!changed_defs[caller_def] {
merged.push(callsite)
}
previous_idx += 1
}
break
}
if previous[previous_idx].caller_def < refreshed[refreshed_idx].caller_def {
merged.push(previous[previous_idx])
previous_idx += 1
} else {
merged.push(refreshed[refreshed_idx])
refreshed_idx += 1
}
}
merged
}
///|
fn dae_refresh_changed_caller_function_infos(
mod_ : @lib.Module,
previous_infos : Array[DaeFunctionInfo],
previous_escaped : Array[Bool],
changed_defs : Array[Bool],
imported : Int32,
defined : Int32,
) -> Array[DaeFunctionInfo]? {
if changed_defs.length() != defined ||
previous_infos.length() != imported + defined ||
previous_escaped.length() != previous_infos.length() {
return None
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) if funcs.length() == defined => funcs
_ => return None
}
let total = previous_infos.length()
let refreshed_infos = dae_make_function_infos(total, previous_escaped)
let direct_calls = Array::make(total, 0)
let tail_calls = Array::make(total, false)
let direct_callers = dae_make_direct_callers(total)
let dropped_calls = Array::make(total, 0)
let refreshed_escaped = previous_escaped.copy()
let caller_seen_stamps = Array::make(total, -1)
let mut caller_def = 0
while caller_def < defined {
if changed_defs[caller_def] {
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
ignore(
dae_scan_current_call_facts_instrs(
body,
caller_def,
imported + caller_def,
[],
direct_calls,
tail_calls,
direct_callers,
dropped_calls,
refreshed_escaped,
refreshed_infos,
caller_seen_stamps,
true,
stable_path=Some(DaeStablePath::empty()),
),
)
}
caller_def += 1
}
let merged_infos : Array[DaeFunctionInfo] = []
let mut callee_abs = 0
while callee_abs < total {
let previous = previous_infos[callee_abs]
let refreshed = refreshed_infos[callee_abs]
let calls = dae_merge_rebased_stable_calls(
previous.calls,
refreshed.calls,
changed_defs,
)
let refreshed_dropped_calls : Array[DaeStableCallsite] = []
for callsite in calls {
if callsite.dropped {
refreshed_dropped_calls.push(callsite)
}
}
let caller_def_for_info = callee_abs - imported
let caller_changed = caller_def_for_info >= 0 &&
caller_def_for_info < changed_defs.length() &&
changed_defs[caller_def_for_info]
merged_infos.push({
stale: if caller_changed {
true
} else {
previous.stale
},
unused_params: if caller_changed {
[]
} else {
previous.unused_params
},
calls,
dropped_calls: refreshed_dropped_calls,
has_tail_calls: if caller_changed {
refreshed.has_tail_calls
} else {
previous.has_tail_calls
},
tail_callees: if caller_changed {
refreshed.tail_callees
} else {
previous.tail_callees
},
loop_type_indices: if caller_changed {
refreshed.loop_type_indices
} else {
previous.loop_type_indices
},
has_adjacent_local_set_get: previous.has_adjacent_local_set_get ||
refreshed.has_adjacent_local_set_get,
has_unseen_calls: previous.has_unseen_calls || refreshed.has_unseen_calls,
})
callee_abs += 1
}
Some(merged_infos)
}
///|
fn dae_current_boundary_graph_adopt_parameter_batch_lightweight(
graph : DaeCurrentBoundaryGraph,
mod_ : @lib.Module,
) -> Bool {
let imported = graph.snapshot.imported
let defined = graph.snapshot.defined
if dae_imported_func_count(mod_) != imported ||
dae_defined_func_count(mod_) != defined {
return false
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) if funcs.length() == defined => funcs
_ => return false
}
let type_indices = dae_collect_func_type_indices(mod_)
if type_indices.length() != graph.snapshot.total {
return false
}
let function_types = graph.snapshot.function_types.copy()
let defined_function_types = dae_collect_defined_function_types(
mod_, imported, defined, type_indices,
)
for def_idx = 0; def_idx < defined; def_idx = def_idx + 1 {
function_types[imported + def_idx] = defined_function_types[def_idx]
}
graph.dependency_cache = None
graph.mod_ = mod_
graph.snapshot = {
imported,
defined,
total: graph.snapshot.total,
type_indices,
function_types,
funcs,
// Parameter removal preserves call targets, caller sets, tail status,
// result-drop status, and escape ownership. Stable paths may shift as call
// operands disappear, but the following results-only waves do not consume
// those paths for unchanged callers.
call_facts: graph.snapshot.call_facts,
boundary_facts: graph.snapshot.boundary_facts,
function_infos: graph.snapshot.function_infos,
exposure: graph.snapshot.exposure,
parameter_loop_free: graph.snapshot.parameter_loop_free,
}
graph.function_epochs = dae_make_function_epochs(defined, 0, 0)
graph.module_epoch = 0
graph.call_epoch = 0
graph.type_epoch = 0
true
}
///|
fn dae_current_boundary_graph_adopt_dropped_result_batch_lightweight(
graph : DaeCurrentBoundaryGraph,
mod_ : @lib.Module,
effects : DaeCommitEffects,
type_indices : Array[@lib.TypeIdx],
defined_function_types : Array[@lib.FuncType?],
scan_function_infos_out~ : Array[Array[DaeFunctionInfo]]? = None,
) -> Bool {
let imported = graph.snapshot.imported
let defined = graph.snapshot.defined
if dae_imported_func_count(mod_) != imported ||
dae_defined_func_count(mod_) != defined ||
effects.result_removed.length() != defined ||
type_indices.length() != graph.snapshot.total ||
defined_function_types.length() != defined {
return false
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) if funcs.length() == defined => funcs
_ => return false
}
let function_types = graph.snapshot.function_types.copy()
for def_idx = 0; def_idx < defined; def_idx = def_idx + 1 {
function_types[imported + def_idx] = defined_function_types[def_idx]
}
// Removing a result may insert a drop inside the changed callee body, making
// one of its own callees eligible in the next wave. Update only dropped-call
// counts: the transaction has already proven that call topology is stable.
let dropped_calls = graph.snapshot.call_facts.dropped_calls.copy()
let mut callee_abs = 0
while callee_abs < graph.snapshot.total {
for callsite in graph.snapshot.function_infos[callee_abs].dropped_calls {
let caller_def = callsite.caller_def
if caller_def >= 0 &&
caller_def < effects.result_changed.length() &&
effects.result_changed[caller_def] &&
dropped_calls[callee_abs] > 0 {
dropped_calls[callee_abs] -= 1
}
}
callee_abs += 1
}
// This scan only updates aggregate counts. Stable callsite records remain
// owned by the original graph and are not consumed from this scan.
let refreshed_infos : Array[DaeFunctionInfo] = []
let refreshed_direct_calls = Array::make(graph.snapshot.total, 0)
let refreshed_tail_calls = Array::make(graph.snapshot.total, false)
let refreshed_direct_callers = dae_make_direct_callers(graph.snapshot.total)
let refreshed_dropped_calls = Array::make(graph.snapshot.total, 0)
let refreshed_escaped = graph.snapshot.call_facts.escaped.copy()
let caller_seen_stamps = Array::make(graph.snapshot.total, -1)
for caller_def = 0; caller_def < defined; caller_def = caller_def + 1 {
if effects.result_changed[caller_def] {
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
ignore(
dae_scan_current_call_facts_instrs(
body,
caller_def,
imported + caller_def,
[],
refreshed_direct_calls,
refreshed_tail_calls,
refreshed_direct_callers,
refreshed_dropped_calls,
refreshed_escaped,
refreshed_infos,
caller_seen_stamps,
true,
),
)
}
}
match scan_function_infos_out {
Some(out) => out.push(refreshed_infos)
None => ()
}
callee_abs = 0
while callee_abs < graph.snapshot.total {
dropped_calls[callee_abs] += refreshed_dropped_calls[callee_abs]
callee_abs += 1
}
for def_idx = 0; def_idx < defined; def_idx = def_idx + 1 {
if effects.result_removed[def_idx] {
// Every admitted caller lost its now-redundant drop.
dropped_calls[imported + def_idx] = 0
}
}
let call_facts : DaeCurrentCallFacts = {
direct_calls: graph.snapshot.call_facts.direct_calls,
tail_calls: graph.snapshot.call_facts.tail_calls,
direct_callers: graph.snapshot.call_facts.direct_callers,
dropped_calls,
escaped: graph.snapshot.call_facts.escaped,
}
graph.dependency_cache = None
graph.mod_ = mod_
graph.snapshot = {
imported,
defined,
total: graph.snapshot.total,
type_indices,
function_types,
funcs,
call_facts,
boundary_facts: dae_call_boundary_facts_from_current(call_facts),
// Each result-changing definition can participate only once. Future waves
// scan a previously unchanged result-changing body, so stable paths for
// already changed callers need not be globally rebased here.
function_infos: graph.snapshot.function_infos,
exposure: graph.snapshot.exposure,
parameter_loop_free: graph.snapshot.parameter_loop_free,
}
graph.function_epochs = dae_make_function_epochs(defined, 0, 0)
graph.module_epoch = 0
graph.call_epoch = 0
graph.type_epoch = 0
true
}
///|
fn dae_current_boundary_graph_adopt_dropped_result_batch(
graph : DaeCurrentBoundaryGraph,
mod_ : @lib.Module,
effects : DaeCommitEffects,
) -> Bool {
let imported = graph.snapshot.imported
let defined = graph.snapshot.defined
if dae_imported_func_count(mod_) != imported ||
dae_defined_func_count(mod_) != defined ||
effects.result_removed.length() != defined {
return false
}
let type_indices = dae_collect_func_type_indices(mod_)
let function_types = dae_collect_defined_function_types(
mod_,
0,
graph.snapshot.total,
type_indices,
)
let function_infos = match
dae_refresh_changed_caller_function_infos(
mod_,
graph.snapshot.function_infos,
graph.snapshot.call_facts.escaped,
effects.structurally_touched,
imported,
defined,
) {
Some(infos) => infos
None => return false
}
let call_facts = dae_call_facts_from_function_infos(function_infos)
if call_facts.direct_calls != graph.snapshot.call_facts.direct_calls ||
call_facts.tail_calls != graph.snapshot.call_facts.tail_calls ||
call_facts.direct_callers != graph.snapshot.call_facts.direct_callers ||
call_facts.escaped != graph.snapshot.call_facts.escaped {
return false
}
graph.dependency_cache = None
graph.mod_ = mod_
graph.snapshot = {
imported,
defined,
total: graph.snapshot.total,
type_indices,
function_types,
funcs: match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return false
},
call_facts,
boundary_facts: dae_call_boundary_facts_from_current(call_facts),
function_infos,
exposure: graph.snapshot.exposure,
parameter_loop_free: graph.snapshot.parameter_loop_free,
}
graph.function_epochs = dae_make_function_epochs(defined, 0, 0)
graph.module_epoch = 0
graph.call_epoch = 0
graph.type_epoch = 0
true
}
///|
fn dae_apply_dropped_result_batch_transaction(
original : DaeOriginalBoundarySnapshot,
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
retain_post_graph~ : Bool = false,
perf~ : HotPerfSession? = None,
) -> DaeDroppedResultBatchApplication {
dae_apply_dropped_result_batch_transaction_with_exposure(
original.exposure,
current,
touched,
changed,
retain_post_graph~,
perf~,
)
}
///|
fn dae_apply_dropped_result_batch_transaction_with_exposure(
original_exposure : Array[DaeFunctionExposure],
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
retain_post_graph~ : Bool = false,
perf~ : HotPerfSession? = None,
) -> DaeDroppedResultBatchApplication {
let graph_start_us = perf_start_timer_opt(
perf, "detail:dae:dropped-result-transaction:graph",
)
let graph = dae_current_boundary_graph_new(current)
perf_stop_timer_opt(
perf, "detail:dae:dropped-result-transaction:graph", graph_start_us,
)
dae_apply_dropped_result_batch_transaction_on_graph(
original_exposure,
current,
touched,
changed,
graph,
retain_post_graph~,
perf~,
)
}
///|
fn dae_apply_dropped_result_batch_transaction_on_graph(
original_exposure : Array[DaeFunctionExposure],
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
graph : DaeCurrentBoundaryGraph,
retain_post_graph~ : Bool = false,
perf~ : HotPerfSession? = None,
validate_candidate~ : Bool = true,
lightweight_post_graph~ : Bool = false,
active_defs~ : Array[Bool]? = None,
) -> DaeDroppedResultBatchApplication {
let defined = graph.snapshot.defined
let topology_preserved_report : Array[Bool] = []
let candidate_start_us = perf_start_timer_opt(
perf, "detail:dae:dropped-result-transaction:candidate",
)
let candidate = dae_try_apply_dropped_result_batch(
current,
original_exposure,
graph,
active_defs~,
topology_preserved_report~,
)
perf_stop_timer_opt(
perf, "detail:dae:dropped-result-transaction:candidate", candidate_start_us,
)
if !candidate.changed {
return {
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(defined),
post_graph: None,
}
}
let candidate_defined = dae_defined_func_count(candidate.mod_out)
if candidate_defined != defined || candidate.touched.length() != defined {
return {
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(defined),
post_graph: None,
}
}
let candidate_type_indices = dae_collect_func_type_indices(candidate.mod_out)
let candidate_function_types = dae_collect_defined_function_types(
candidate.mod_out,
graph.snapshot.imported,
defined,
candidate_type_indices,
)
let effects = dae_commit_effects_empty(defined)
let mut first_def = -1
let mut candidate_count = 0
let mut def_idx = 0
while def_idx < defined {
if candidate.touched[def_idx] {
effects.structurally_touched[def_idx] = true
}
let callee_abs = graph.snapshot.imported + def_idx
let before_signature = graph.snapshot.function_types[callee_abs]
let after_signature = candidate_function_types[def_idx]
match (before_signature, after_signature) {
(
Some(@lib.FuncType(before_params, before_results)),
Some(@lib.FuncType(after_params, after_results)),
) if before_results != after_results => {
if before_params != after_params {
return {
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(defined),
post_graph: None,
}
}
effects.result_changed[def_idx] = true
effects.result_removed[def_idx] = after_results.length() <
before_results.length()
effects.control_refinalization_required[def_idx] = true
effects.worth_decided[def_idx] = true
effects.worth_optimizing[def_idx] = true
candidate_count += 1
if first_def < 0 {
first_def = def_idx
}
}
_ => ()
}
def_idx += 1
}
if candidate_count == 0 {
return {
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(defined),
post_graph: None,
}
}
def_idx = 0
while def_idx < defined {
if candidate.touched[def_idx] && !effects.result_changed[def_idx] {
effects.worth_decided[def_idx] = true
effects.worth_optimizing[def_idx] = true
effects.caller_drop_removed[def_idx] = true
}
def_idx += 1
}
let decision_reasons : Array[DaeDecisionReason] = []
let mut reason_idx = 0
while reason_idx < candidate_count {
decision_reasons.push(DaeCandidateProduced)
reason_idx += 1
}
let before_type_count = match current.type_sec {
Some(type_sec) => dae_type_sec_flattened_length(type_sec)
None => 0
}
let after_type_count = match candidate.mod_out.type_sec {
Some(type_sec) => dae_type_sec_flattened_length(type_sec)
None => 0
}
let finalized_effects = {
..effects,
candidates_produced: candidate_count,
candidates_applied: candidate_count,
type_entries_reused: if after_type_count == before_type_count {
candidate_count
} else {
0
},
type_entries_appended: if after_type_count > before_type_count {
after_type_count - before_type_count
} else {
0
},
type_section_changed: current.type_sec != candidate.mod_out.type_sec,
metadata_changed: current.name_sec != candidate.mod_out.name_sec,
invalidation_domains: dae_invalidation_domains_for_work_family(
DaeDroppedResultWork,
),
decision_reasons,
}
let validation = if validate_candidate {
let validation_start_us = perf_start_timer_opt(
perf, "detail:dae:dropped-result-transaction:validate",
)
let result = @validate.validate_module(candidate.mod_out)
perf_stop_timer_opt(
perf, "detail:dae:dropped-result-transaction:validate", validation_start_us,
)
result
} else {
Ok(())
}
match validation {
Ok(_) => {
let graph_adopted = if retain_post_graph &&
topology_preserved_report.get(0) == Some(true) {
if lightweight_post_graph {
dae_current_boundary_graph_adopt_dropped_result_batch_lightweight(
graph,
candidate.mod_out,
finalized_effects,
candidate_type_indices,
candidate_function_types,
)
} else {
dae_current_boundary_graph_adopt_dropped_result_batch(
graph,
candidate.mod_out,
finalized_effects,
)
}
} else {
false
}
{
mod_out: candidate.mod_out,
touched: dae_merge_touched(touched, candidate.touched),
changed: changed || candidate.changed,
applied: true,
primary_def: first_def,
effects: finalized_effects,
post_graph: if graph_adopted {
Some(graph)
} else {
None
},
}
}
Err(_) =>
{
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(defined),
post_graph: None,
}
}
}
///|
fn dae_apply_direct_gc_batch_transaction(
original : DaeOriginalBoundarySnapshot,
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
active_defs~ : Array[Bool]? = None,
graph_builds~ : Array[Int32]? = None,
) -> DaeCoreResultApplication {
let mut current_mod = current
let mut merged_touched = touched
let mut merged_changed = changed
let mut merged_effects = dae_commit_effects_empty(
dae_defined_func_count(current),
)
let mut cached_graph : DaeCurrentBoundaryGraph? = None
let mut first_def = -1
let mut applied = false
let initial_defined = merged_effects.structurally_touched.length()
for def_idx = 0; def_idx < initial_defined; def_idx = def_idx + 1 {
match active_defs {
Some(active) if def_idx >= active.length() || !active[def_idx] => continue
_ => ()
}
let graph = match cached_graph {
Some(graph) => graph
None => {
match graph_builds {
Some(count) => count[0] += 1
None => ()
}
let graph = dae_current_boundary_graph_new(current_mod)
cached_graph = Some(graph)
graph
}
}
if def_idx >= graph.snapshot.defined {
continue
}
let candidate = dae_try_refine_direct_gc_types_for_def_with_facts(
current_mod,
def_idx,
graph.snapshot.call_facts,
true,
true,
)
if !candidate.changed {
continue
}
// Only rejected candidates reuse immutable module facts. Transaction
// attempts may update graph epochs/caches even when no commit escapes.
cached_graph = None
let candidate_effects = dae_commit_effects_empty(graph.snapshot.defined)
let mut touched_def = 0
while touched_def < candidate.touched.length() &&
touched_def < graph.snapshot.defined {
if candidate.touched[touched_def] {
candidate_effects.structurally_touched[touched_def] = true
candidate_effects.worth_decided[touched_def] = true
candidate_effects.worth_optimizing[touched_def] = true
}
touched_def += 1
}
let candidate_effects = {
..candidate_effects,
type_section_changed: true,
metadata_changed: true,
invalidation_domains: [
DaeInvalidateCalls,
DaeInvalidateSignature,
DaeInvalidateResult,
DaeInvalidateBody,
DaeInvalidateControl,
DaeInvalidateLocals,
DaeInvalidateTypes,
DaeInvalidateMetadata,
DaeInvalidateNestedCleanup,
],
decision_reasons: [DaeCandidateProduced],
}
let item = dae_work_item_new(
graph,
dae_boundary_dependency_graph_new(graph),
def_idx,
dae_work_family_for_commit_effects(candidate_effects, def_idx),
)
let application = match
dae_process_precomputed_signature_work_item(
original, current_mod, merged_touched, merged_changed, graph, item, candidate,
candidate_effects,
) {
DaeWorkCommitted(value) => value
DaeWorkNoChange(_) | DaeWorkDeferred(_) | DaeWorkRequeue(_) => continue
}
current_mod = application.mod_out
merged_touched = application.touched
merged_changed = application.changed
merged_effects = dae_commit_effects_merge(
merged_effects,
application.effects,
)
if first_def < 0 {
first_def = def_idx
}
applied = true
}
{
mod_out: current_mod,
touched: merged_touched,
changed: merged_changed,
applied,
primary_def: first_def,
effects: merged_effects,
}
}
///|
fn dae_count_target_call_kinds_instrs(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
) -> (Int32, Int32) {
let mut direct_calls = 0
let mut return_calls = 0
for instr in instrs {
match instr {
@lib.Call(func_idx) if dae_func_idx_slot(func_idx) == callee_abs =>
direct_calls += 1
@lib.ReturnCall(func_idx) if dae_func_idx_slot(func_idx) == callee_abs => {
direct_calls += 1
return_calls += 1
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) => {
let (nested_direct, nested_return) = dae_count_target_call_kinds_instrs(
body, callee_abs,
)
direct_calls += nested_direct
return_calls += nested_return
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
let (body_direct, body_return) = dae_count_target_call_kinds_instrs(
body, callee_abs,
)
direct_calls += body_direct
return_calls += body_return
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
let (catch_direct, catch_return) = dae_count_target_call_kinds_instrs(
catch_body, callee_abs,
)
direct_calls += catch_direct
return_calls += catch_return
}
}
@lib.If(_, then_body, else_body) => {
let (then_direct, then_return) = dae_count_target_call_kinds_instrs(
then_body, callee_abs,
)
direct_calls += then_direct
return_calls += then_return
match else_body {
Some(body) => {
let (else_direct, else_return) = dae_count_target_call_kinds_instrs(
body, callee_abs,
)
direct_calls += else_direct
return_calls += else_return
}
None => ()
}
}
_ => ()
}
}
(direct_calls, return_calls)
}
///|
fn dae_count_active_target_call_kinds_instrs(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
) -> (Int32, Int32) {
let mut direct_calls = 0
let mut return_calls = 0
for instr in instrs {
if dae_instr_is_unreachable_root(instr) {
break
}
match instr {
@lib.Call(func_idx) if dae_func_idx_slot(func_idx) == callee_abs =>
direct_calls += 1
@lib.ReturnCall(func_idx) if dae_func_idx_slot(func_idx) == callee_abs => {
direct_calls += 1
return_calls += 1
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) => {
let (nested_direct, nested_return) = dae_count_active_target_call_kinds_instrs(
body, callee_abs,
)
direct_calls += nested_direct
return_calls += nested_return
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
let (body_direct, body_return) = dae_count_active_target_call_kinds_instrs(
body, callee_abs,
)
direct_calls += body_direct
return_calls += body_return
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
let (catch_direct, catch_return) = dae_count_active_target_call_kinds_instrs(
catch_body, callee_abs,
)
direct_calls += catch_direct
return_calls += catch_return
}
}
@lib.If(_, then_body, else_body) => {
let (then_direct, then_return) = dae_count_active_target_call_kinds_instrs(
then_body, callee_abs,
)
direct_calls += then_direct
return_calls += then_return
match else_body {
Some(body) => {
let (else_direct, else_return) = dae_count_active_target_call_kinds_instrs(
body, callee_abs,
)
direct_calls += else_direct
return_calls += else_return
}
None => ()
}
}
_ => ()
}
}
(direct_calls, return_calls)
}
///|
fn dae_collect_target_call_identities(
snapshot : DaeModuleBoundarySnapshot,
callee_abs : Int32,
) -> Array[DaeDirectCallIdentity] {
let identities : Array[DaeDirectCallIdentity] = []
if callee_abs < 0 || callee_abs >= snapshot.function_infos.length() {
return identities
}
for callsite in snapshot.function_infos[callee_abs].calls {
if callsite.active && callsite.callee_abs == callee_abs {
identities.push({
caller_def: callsite.caller_def,
path: callsite.path,
call_kind: if callsite.tail {
DaeDirectReturnCall
} else {
DaeDirectCall
},
callee_abs,
})
}
}
identities
}
///|
fn dae_count_call_identities_for_caller(
identities : Array[DaeDirectCallIdentity],
caller_def : Int32,
) -> (Int32, Int32) {
let mut direct_calls = 0
let mut return_calls = 0
for identity in identities {
if identity.caller_def == caller_def {
direct_calls += 1
if identity.call_kind == DaeDirectReturnCall {
return_calls += 1
}
}
}
(direct_calls, return_calls)
}
///|
fn dae_rewrite_plan_rejection(
mod_ : @lib.Module,
reason : DaeDecisionReason,
) -> DaeRewritePlanApplication {
{
result: { mod_out: mod_, touched: [], changed: false, primary_def: -1, },
applied: false,
reason,
effects: dae_commit_effects_empty(dae_defined_func_count(mod_)),
}
}
///|
fn dae_rewrite_call_at_stable_path(
instrs : Array[@lib.Instruction],
path : Array[Int32],
path_idx : Int32,
callee_abs : Int32,
remove : Array[Bool],
entry_stack_values : Int32,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
localization : DaeCallsiteLocalizationContext,
traps_never_happen~ : Bool = false,
allow_stack_carried_void_effects~ : Bool = false,
) -> DaeRewriteResult {
if path_idx < 0 || path_idx >= path.length() {
return { instrs: [], calls_rewritten: 0, ok: false, }
}
let instr_idx = path[path_idx]
if instr_idx < 0 || instr_idx >= instrs.length() {
return { instrs: [], calls_rewritten: 0, ok: false, }
}
if path_idx + 1 == path.length() {
match
dae_target_callsite_evidence(
instrs[instr_idx],
callee_abs,
DaeTargetDirectOrReturnCall,
) {
Some(_) => ()
None => return { instrs: [], calls_rewritten: 0, ok: false, }
}
let evidence = match
dae_rewrite_callsite_with_localization_at(
instrs,
instr_idx,
remove,
localization.param_types,
entry_stack_values,
localization.first_scratch_local,
mod_,
func_type_indices,
existing_scratch_local_types=localization.scratch_local_types,
allow_stack_carried_void_effects~,
traps_never_happen~,
) {
Some(evidence) => evidence
None => return { instrs: [], calls_rewritten: 0, ok: false, }
}
localization.scratch_local_types.clear()
localization.scratch_local_types.append(evidence.scratch_local_types)
let mut param_idx = 0
while param_idx < evidence.localized_params.length() &&
param_idx < localization.localized_params.length() {
localization.localized_params[param_idx] = localization.localized_params[param_idx] ||
evidence.localized_params[param_idx]
param_idx += 1
}
return { instrs: evidence.instrs, calls_rewritten: 1, ok: true, }
}
if path_idx + 2 >= path.length() {
return { instrs: [], calls_rewritten: 0, ok: false, }
}
let child_kind = path[path_idx + 1]
let children = match
dae_nested_control_children_evidence(instrs[instr_idx], mod_) {
Some(children) => children
None => return { instrs: [], calls_rewritten: 0, ok: false, }
}
let child_body = match child_kind {
0 | 1 | 2 | 4 => children.primary_body
3 =>
match children.secondary_body {
Some(body) => body
None => return { instrs: [], calls_rewritten: 0, ok: false, }
}
_ => return { instrs: [], calls_rewritten: 0, ok: false, }
}
let child_result = dae_rewrite_call_at_stable_path(
child_body,
path,
path_idx + 2,
callee_abs,
remove,
children.entry_stack_values,
mod_,
func_type_indices,
localization,
traps_never_happen~,
allow_stack_carried_void_effects~,
)
if !child_result.ok {
return child_result
}
let rewritten_primary = if child_kind == 3 {
children.primary_body
} else {
child_result.instrs
}
let rewritten_secondary = if child_kind == 3 {
Some(child_result.instrs)
} else {
children.secondary_body
}
let rewritten_instr = match
dae_rebuild_nested_control_children(
instrs[instr_idx],
children,
rewritten_primary,
rewritten_secondary,
) {
Some(instr) => instr
None => return { instrs: [], calls_rewritten: 0, ok: false, }
}
let rewritten = instrs.copy()
rewritten[instr_idx] = rewritten_instr
{
instrs: rewritten,
calls_rewritten: child_result.calls_rewritten,
ok: true,
}
}
///|
priv struct DaeStableRewriteSelection {
workspace : DaeStablePathWorkspace
generation : Int32
selected_nodes : Int32
}
///|
priv struct DaeStableRewriteStats {
mut scopes : Int32
mut structured_rebuilds : Int32
mut calls : Int32
}
///|
fn DaeStableRewriteStats::new() -> DaeStableRewriteStats {
{ scopes: 0, structured_rebuilds: 0, calls: 0, }
}
///|
fn dae_stable_rewrite_selection(
paths : Array[DaeStablePath],
) -> DaeStableRewriteSelection? {
if paths.is_empty() {
return None
}
let workspace = paths[0].workspace
let generation = if workspace.generation == 2147483647 {
workspace.active_stamps.fill(0)
workspace.call_stamps.fill(0)
1
} else {
workspace.generation + 1
}
workspace.generation = generation
let mut selected_nodes = 0
for path in paths {
if !physical_equal(path.workspace, workspace) ||
path.tail < 0 ||
path.tail >= workspace.nodes.length() ||
path.length <= 0 ||
path.length % 2 == 0 {
return None
}
workspace.call_stamps[path.tail] = generation
let mut node_idx = path.tail
while node_idx >= 0 && workspace.active_stamps[node_idx] != generation {
let node = workspace.nodes[node_idx]
if node.depth % 2 == 0 && (node.value < 0 || node.value > 4) {
// Legacy catch arms are intentionally left to the conservative full
// traversal, whose child representation includes those handler bodies.
return None
}
workspace.active_stamps[node_idx] = generation
selected_nodes += 1
node_idx = node.parent
}
}
Some({ workspace, generation, selected_nodes, })
}
///|
fn dae_stable_rewrite_selected_child(
selection : DaeStableRewriteSelection,
parent : Int32,
value : Int32,
) -> Int32? {
match dae_stable_path_child(selection.workspace, parent, value) {
Some(node) if node >= 0 &&
node < selection.workspace.active_stamps.length() &&
selection.workspace.active_stamps[node] == selection.generation =>
Some(node)
_ => None
}
}
///|
fn dae_rewrite_calls_from_stable_selection_scope(
instrs : Array[@lib.Instruction],
scope : Int32,
selection : DaeStableRewriteSelection,
callee_abs : Int32,
remove : Array[Bool],
entry_stack_values : Int32,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
localization : DaeCallsiteLocalizationContext,
traps_never_happen : Bool,
allow_stack_carried_void_effects : Bool,
stats~ : DaeStableRewriteStats? = None,
) -> DaeRewriteResult {
match stats {
Some(value) => value.scopes += 1
None => ()
}
let mut current = instrs.copy()
let mut calls_rewritten = 0
let mut instr_idx = instrs.length()
while instr_idx > 0 {
instr_idx -= 1
let instr_node = match
dae_stable_rewrite_selected_child(selection, scope, instr_idx) {
Some(node) => node
None => continue
}
if selection.workspace.call_stamps[instr_node] == selection.generation {
match
dae_target_callsite_evidence(
current[instr_idx],
callee_abs,
DaeTargetDirectOrReturnCall,
) {
Some(_) => ()
None => return { instrs: [], calls_rewritten, ok: false, }
}
let evidence = match
dae_rewrite_callsite_with_localization_at(
current,
instr_idx,
remove,
localization.param_types,
entry_stack_values,
localization.first_scratch_local,
mod_,
func_type_indices,
existing_scratch_local_types=localization.scratch_local_types,
allow_stack_carried_void_effects~,
traps_never_happen~,
) {
Some(value) => value
None => return { instrs: [], calls_rewritten, ok: false, }
}
localization.scratch_local_types.clear()
localization.scratch_local_types.append(evidence.scratch_local_types)
let mut param_idx = 0
while param_idx < evidence.localized_params.length() &&
param_idx < localization.localized_params.length() {
localization.localized_params[param_idx] = localization.localized_params[param_idx] ||
evidence.localized_params[param_idx]
param_idx += 1
}
current = evidence.instrs
calls_rewritten += 1
match stats {
Some(value) => value.calls += 1
None => ()
}
continue
}
let children = match
dae_nested_control_children_evidence(current[instr_idx], mod_) {
Some(value) => value
None => return { instrs: [], calls_rewritten, ok: false, }
}
let primary_kind = match current[instr_idx] {
@lib.Block(_, _) => 0
@lib.Loop(_, _) => 1
@lib.If(_, _, _) => 2
@lib.TryTable(_, _, _) => 4
_ => return { instrs: [], calls_rewritten, ok: false, }
}
let primary_node = dae_stable_rewrite_selected_child(
selection, instr_node, primary_kind,
)
let secondary_node = dae_stable_rewrite_selected_child(
selection, instr_node, 3,
)
if primary_node is None && secondary_node is None {
return { instrs: [], calls_rewritten, ok: false, }
}
let secondary = match secondary_node {
Some(node) => {
let body = match children.secondary_body {
Some(body) => body
None => return { instrs: [], calls_rewritten, ok: false, }
}
let result = dae_rewrite_calls_from_stable_selection_scope(
body,
node,
selection,
callee_abs,
remove,
children.entry_stack_values,
mod_,
func_type_indices,
localization,
traps_never_happen,
allow_stack_carried_void_effects,
stats~,
)
if !result.ok {
return {
instrs: [],
calls_rewritten: calls_rewritten + result.calls_rewritten,
ok: false,
}
}
calls_rewritten += result.calls_rewritten
Some(result.instrs)
}
None => children.secondary_body
}
let primary = match primary_node {
Some(node) => {
let result = dae_rewrite_calls_from_stable_selection_scope(
children.primary_body,
node,
selection,
callee_abs,
remove,
children.entry_stack_values,
mod_,
func_type_indices,
localization,
traps_never_happen,
allow_stack_carried_void_effects,
stats~,
)
if !result.ok {
return {
instrs: [],
calls_rewritten: calls_rewritten + result.calls_rewritten,
ok: false,
}
}
calls_rewritten += result.calls_rewritten
result.instrs
}
None => children.primary_body
}
current[instr_idx] = match
dae_rebuild_nested_control_children(
current[instr_idx],
children,
primary,
secondary,
) {
Some(value) => value
None => return { instrs: [], calls_rewritten, ok: false, }
}
match stats {
Some(value) => value.structured_rebuilds += 1
None => ()
}
}
{ instrs: current, calls_rewritten, ok: true, }
}
///|
fn dae_rewrite_calls_from_stable_callsites(
body : Array[@lib.Instruction],
callee_abs : Int32,
remove : Array[Bool],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
info : DaeFunctionInfo,
caller_def : Int32,
localization : DaeCallsiteLocalizationContext,
traps_never_happen~ : Bool = false,
allow_stack_carried_void_effects~ : Bool = false,
rewrite_stats~ : DaeStableRewriteStats? = None,
) -> DaeRewriteResult {
let initial_scratch = localization.scratch_local_types.copy()
let initial_localized = localization.localized_params.copy()
let selected_paths : Array[DaeStablePath] = []
for callsite in info.calls {
if callsite.callee_abs != callee_abs ||
callsite.caller_def != caller_def ||
!callsite.active ||
callsite.path.length() == 0 {
continue
}
selected_paths.push(callsite.path)
}
if selected_paths.is_empty() {
return { instrs: body, calls_rewritten: 0, ok: true, }
}
let result = match dae_stable_rewrite_selection(selected_paths) {
Some(selection) =>
dae_rewrite_calls_from_stable_selection_scope(
body,
-1,
selection,
callee_abs,
remove,
0,
mod_,
func_type_indices,
localization,
traps_never_happen,
allow_stack_carried_void_effects,
stats=rewrite_stats,
)
None => { instrs: [], calls_rewritten: 0, ok: false, }
}
if result.ok {
return result
}
localization.scratch_local_types.clear()
localization.scratch_local_types.append(initial_scratch)
localization.localized_params.clear()
localization.localized_params.append(initial_localized)
dae_rewrite_calls_instrs(
body,
callee_abs,
remove,
0,
mod_,
func_type_indices,
localization=Some(localization),
traps_never_happen~,
allow_stack_carried_void_effects~,
)
}
///|
fn dae_should_skip_stale_scalar_result_tail(
scalar_plan_code_snapshot_current : Bool,
has_current_dropped_result_candidate : Bool,
) -> Bool {
!scalar_plan_code_snapshot_current && !has_current_dropped_result_candidate
}
///|
fn dae_should_collect_low_exact_literal_candidates(
optimize : Bool,
restored_application_applied : Bool,
scalar_plan_code_snapshot_current : Bool,
) -> Bool {
optimize || restored_application_applied || scalar_plan_code_snapshot_current
}
///|
fn dae_scalar_plan_code_snapshot_is_current(
mod_ : @lib.Module,
graph : DaeCurrentBoundaryGraph,
) -> Bool {
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
dae_snapshot_funcs_unchanged(funcs, graph.snapshot.funcs)
None => graph.snapshot.funcs.length() == 0
}
}
///|
fn dae_scalar_rewrite_changed_defs(
target_def : Int32,
caller_changed_defs : Array[Bool],
) -> Array[Int32] {
let changed_defs : Array[Int32] = []
if target_def >= 0 && target_def < caller_changed_defs.length() {
changed_defs.push(target_def)
}
let mut def_idx = 0
while def_idx < caller_changed_defs.length() {
if caller_changed_defs[def_idx] && def_idx != target_def {
changed_defs.push(def_idx)
}
def_idx += 1
}
changed_defs
}
///|
fn dae_build_scalar_unused_param_rewrite_plan(
mod_ : @lib.Module,
original : DaeOriginalBoundarySnapshot,
graph : DaeCurrentBoundaryGraph,
callee_abs : Int32,
remove : Array[Bool],
local_use : DaeLocalUse,
direct_call_count : Int32,
direct_caller_defs : Array[Int32],
callee_source~ : @lib.Func? = None,
entry_live~ : Array[Bool]? = None,
liveness_work~ : Array[Int32]? = None,
materialized_params~ : Array[@lib.Instruction?]? = None,
verified_materialized_suffix~ : Array[@lib.Instruction]? = None,
traps_never_happen~ : Bool = false,
allow_stack_carried_void_effects~ : Bool = false,
) -> DaeRewritePlan? {
let def_idx = callee_abs - graph.snapshot.imported
if graph.snapshot.imported != original.imported ||
def_idx < 0 ||
def_idx >= original.defined ||
def_idx >= graph.snapshot.defined ||
def_idx >= graph.snapshot.funcs.length() ||
remove.length() == 0 ||
!remove.any(fn(value) { value }) {
return None
}
let old_signature = match graph.snapshot.function_types[callee_abs] {
Some(signature) => signature
None => return None
}
let @lib.FuncType(params, results) = old_signature
if remove.length() != params.length() ||
local_use.writes.length() < params.length() ||
local_use.gets.length() < params.length() {
return None
}
let boundary_candidate = match
dae_call_boundary_candidate(
graph.snapshot.boundary_facts,
callee_abs,
def_idx~,
) {
Some(candidate) if candidate.owned => candidate
_ => return None
}
// Authoritative final boundary guard for every signature-changing family.
let shared_facts : DaeBoundaryFacts = {
original,
graph,
incoming_param_live_by_def: [],
incoming_param_live_body_epochs: [],
}
if !dae_can_change_parameters(shared_facts, callee_abs) {
return None
}
if boundary_candidate.direct_call_count != direct_call_count ||
boundary_candidate.direct_callers != direct_caller_defs {
return None
}
let requested_remove = remove.copy()
let materialized_suffix = match verified_materialized_suffix {
Some(values) => values.copy()
None => []
}
// Ordinary unused-parameter analysis derives actions from canonical operand
// slices. A separately verified immediate suffix is already exact callsite
// evidence for only those trailing slots, so keep every other slot without
// requiring reconstruction of older stack-carried operands.
let classified = if materialized_suffix.length() > 0 {
Array::make(params.length(), DaeKeepParameter)
} else {
let active_entry_live = match entry_live {
Some(entry_live) if entry_live.length() == params.length() => entry_live
Some(_) => return None
None =>
dae_incoming_parameter_liveness(
mod_,
callee_abs,
params.length(),
liveness_work~,
)
}
match
dae_classify_unused_parameter_actions_from_function_infos(
shared_facts,
mod_,
def_idx,
callee_abs,
params,
active_entry_live,
direct_caller_defs,
graph.snapshot.function_infos,
traps_never_happen~,
allow_stack_carried_void_effects~,
) {
Some(actions) => actions
None => return None
}
}
let const_params : Array[@lib.Instruction?] = match materialized_params {
Some(values) if values.length() == params.length() => values.copy()
Some(_) => return None
None => Array::make(params.length(), None)
}
let collect_materialized = Array::make(params.length(), false)
let mut allow_immutable_global_get = false
let mut has_materialized = false
let mut param_idx = 0
while param_idx < params.length() {
match const_params[param_idx] {
Some(value) => {
if !requested_remove[param_idx] ||
!dae_instr_is_materializable_const_with_immutable_global(mod_, value) {
return None
}
collect_materialized[param_idx] = true
has_materialized = true
match value {
@lib.GlobalGet(_) => allow_immutable_global_get = true
_ => ()
}
}
None => ()
}
param_idx += 1
}
if materialized_suffix.length() > params.length() ||
materialized_suffix.any(fn(value) {
!dae_instr_is_materializable_const_with_immutable_global(mod_, value)
}) {
return None
}
if materialized_suffix.length() > 0 {
let suffix_start = params.length() - materialized_suffix.length()
param_idx = 0
while param_idx < params.length() {
if param_idx < suffix_start {
if const_params[param_idx].is_some() {
return None
}
} else {
match const_params[param_idx] {
Some(value) if dae_materialized_const_equal(
value,
materialized_suffix[param_idx - suffix_start],
) => ()
_ => return None
}
}
param_idx += 1
}
if dae_tail_call_boundary_evidence(graph.snapshot.funcs).blocks_direct_call_rewrite {
return None
}
} else if has_materialized {
let observed = match
dae_collect_uniform_const_actuals_from_current_call_facts(
mod_,
callee_abs,
params.length(),
graph.snapshot.type_indices,
graph.snapshot.boundary_facts,
graph.snapshot.function_infos,
allow_immutable_global_get~,
collect_params=Some(collect_materialized),
) {
Some(values) => values
None => return None
}
param_idx = 0
while param_idx < params.length() {
match const_params[param_idx] {
Some(expected) =>
match observed[param_idx] {
Some(actual) if dae_materialized_const_equal(actual, expected) => ()
_ => return None
}
None => ()
}
param_idx += 1
}
}
param_idx = 0
while param_idx < params.length() {
match const_params[param_idx] {
Some(value) => {
classified[param_idx] = DaeMaterializeConstantParameter(value)
remove[param_idx] = true
}
None =>
match classified[param_idx] {
DaeRemovePureParameter | DaeRemoveParameterReplayEffects =>
remove[param_idx] = true
DaeLocalizeParameterAndRetry => remove[param_idx] = true
DaeKeepParameter | DaeRefineParameterType(_) =>
remove[param_idx] = false
DaeMaterializeConstantParameter(_) => return None
}
}
param_idx += 1
}
if !remove.any(fn(value) { value }) {
return None
}
let localized_params = Array::make(params.length(), false)
let scratch_locals_added = [0]
let scratch_locals_reused = [0]
let rewrite_caller = fn(caller_func, caller_def) {
let @lib.Func(locals, @lib.Expr(body)) = caller_func
let caller_abs = graph.snapshot.imported + caller_def
let caller_params = match graph.snapshot.function_types[caller_abs] {
Some(@lib.FuncType(caller_params, _)) => caller_params
None =>
return {
func: caller_func,
calls_rewritten: 0,
changed: false,
ok: false,
}
}
let localization = {
first_scratch_local: dae_next_free_local_idx(
body,
caller_params.length() + locals.length(),
),
param_types: params,
scratch_local_types: [],
localized_params,
}
let result = dae_rewrite_calls_from_stable_callsites(
body,
callee_abs,
remove,
mod_,
graph.snapshot.type_indices,
graph.snapshot.function_infos[callee_abs],
caller_def,
localization,
traps_never_happen~,
allow_stack_carried_void_effects~,
)
if !result.ok {
return {
func: caller_func,
calls_rewritten: 0,
changed: false,
ok: false,
}
}
if result.calls_rewritten > 0 {
let localized_occurrences = dae_count_touched(localized_params) *
result.calls_rewritten
let added = localization.scratch_local_types.length()
scratch_locals_added[0] += added
if localized_occurrences > added {
scratch_locals_reused[0] += localized_occurrences - added
}
let rewritten_locals = locals.copy()
rewritten_locals.append_types(localization.scratch_local_types)
{
func: @lib.Func::new(rewritten_locals, @lib.Expr::new(result.instrs)),
calls_rewritten: result.calls_rewritten,
changed: true,
ok: true,
}
} else {
{ func: caller_func, calls_rewritten: 0, changed: false, ok: true, }
}
}
let caller_application = if materialized_suffix.length() > 0 {
let rewrite_suffix_caller = fn(caller_func, _) {
let @lib.Func(locals, @lib.Expr(body)) = caller_func
let (rewritten_body, calls_rewritten, ok) = dae_rewrite_materialized_const_suffix_calls_instrs(
body, callee_abs, materialized_suffix,
)
if !ok {
return {
func: caller_func,
calls_rewritten: 0,
changed: false,
ok: false,
}
}
if calls_rewritten > 0 {
{
func: @lib.Func::new(locals, @lib.Expr::new(rewritten_body)),
calls_rewritten,
changed: true,
ok: true,
}
} else {
{ func: caller_func, calls_rewritten: 0, changed: false, ok: true, }
}
}
dae_apply_direct_caller_scope_rewrite(
graph.snapshot.funcs,
direct_caller_defs,
rewrite_suffix_caller,
)
} else {
dae_apply_direct_caller_scope_rewrite(
graph.snapshot.funcs,
direct_caller_defs,
rewrite_caller,
)
}
if !caller_application.ok ||
caller_application.calls_rewritten != direct_call_count {
return None
}
match callee_source {
Some(_) if caller_application.changed_defs[def_idx] => return None
Some(source) => caller_application.funcs[def_idx] = source
None => ()
}
let callee_evidence = match
dae_removed_param_callee_rewrite_evidence(
caller_application.funcs[def_idx],
params,
remove,
const_params,
local_use,
localized_params,
) {
Some(evidence) => evidence
None => return None
}
let mut callee_rewrite = dae_finalize_removed_parameter_callee(
callee_evidence, true,
)
let rewritten_funcs = caller_application.funcs
rewritten_funcs[def_idx] = callee_rewrite
let structurally_changed_defs = dae_scalar_rewrite_changed_defs(
def_idx,
caller_application.changed_defs,
)
for cleanup_idx in structurally_changed_defs {
rewritten_funcs[cleanup_idx] = dae_trim_parameter_rewrite_func(
rewritten_funcs[cleanup_idx],
)
}
callee_rewrite = rewritten_funcs[def_idx]
let context = match
dae_module_signature_context(mod_, def_idx, copy_funcs=false) {
Some(context) => context
None => return None
}
let (next_type_sec, replacement_type_idx) = dae_update_defined_func_type(
mod_,
context.type_sec,
context.type_idxs,
def_idx,
callee_evidence.kept_params,
results,
)
let next_type_idxs = context.type_idxs.copy()
next_type_idxs[def_idx] = replacement_type_idx
let parameter_actions : Array[DaeParameterAction] = []
let preserved_removed_params = Array::make(params.length(), false)
let mut param_idx = 0
while param_idx < params.length() {
let action = classified[param_idx]
parameter_actions.push(action)
if dae_parameter_action_removes(action) {
preserved_removed_params[param_idx] = local_use.writes[param_idx] ||
localized_params[param_idx]
}
param_idx += 1
}
let result_actions : Array[DaeResultAction] = []
for _ in results {
result_actions.push(DaeKeepResult)
}
let expected_callers : Array[DaeCallerEpochExpectation] = []
let caller_rewrites : Array[DaeCallerRewritePlan] = []
let expected_call_identities = dae_collect_target_call_identities(
graph.snapshot,
callee_abs,
)
if expected_call_identities.length() != direct_call_count {
return None
}
let mut expected_return_call_count = 0
for caller_def in direct_caller_defs {
if caller_def < 0 ||
caller_def >= graph.function_epochs.length() ||
caller_def >= graph.snapshot.funcs.length() {
return None
}
let epochs = graph.function_epochs[caller_def]
let (caller_direct_calls, caller_return_calls) = dae_count_call_identities_for_caller(
expected_call_identities, caller_def,
)
expected_return_call_count += caller_return_calls
expected_callers.push({
def_idx: caller_def,
signature_epoch: epochs.signature_epoch,
body_epoch: epochs.body_epoch,
direct_calls: caller_direct_calls,
return_calls: caller_return_calls,
})
if caller_application.changed_defs[caller_def] {
caller_rewrites.push({
def_idx: caller_def,
func: rewritten_funcs[caller_def],
calls_rewritten: caller_direct_calls,
return_calls_rewritten: caller_return_calls,
})
}
}
let touched = Array::make(graph.snapshot.defined, false)
let invalidated_dependencies : Array[Int32] = []
for touched_idx in structurally_changed_defs {
touched[touched_idx] = true
invalidated_dependencies.push(touched_idx)
}
let target_epochs = graph.function_epochs[def_idx]
let candidate_with_sections = mod_
.with_type_sec(next_type_sec)
.with_func_sec(@lib.FuncSec::new(next_type_idxs))
.with_code_sec(@lib.CodeSec::new(rewritten_funcs))
let candidate = candidate_with_sections.without_name_sec()
let structurally_touched_defs : Array[Int32] = []
let localized_caller_defs : Array[Int32] = []
let mut effect_def = 0
while effect_def < touched.length() {
if touched[effect_def] {
structurally_touched_defs.push(effect_def)
}
effect_def += 1
}
if localized_params.any(fn(value) { value }) {
for caller_def in direct_caller_defs {
if !localized_caller_defs.contains(caller_def) {
localized_caller_defs.push(caller_def)
}
}
}
let mut effectful_operands_replayed = 0
for action in classified {
if action == DaeRemoveParameterReplayEffects {
effectful_operands_replayed += direct_call_count
}
}
let type_entry_reused = next_type_sec == context.type_sec
let effects = {
..dae_commit_effects_parameter_change(
graph.snapshot.defined,
def_idx,
structurally_touched_defs,
has_materialized,
localized_caller_defs,
),
effectful_operands_replayed,
scratch_locals_added: scratch_locals_added[0],
scratch_locals_reused: scratch_locals_reused[0],
type_entries_reused: if type_entry_reused {
1
} else {
0
},
type_entries_appended: if type_entry_reused {
0
} else {
1
},
}
Some({
effects,
target_def: def_idx,
target_abs: callee_abs,
expected_module_epoch: graph.module_epoch,
expected_call_epoch: graph.call_epoch,
expected_type_epoch: graph.type_epoch,
expected_target_signature_epoch: target_epochs.signature_epoch,
expected_target_body_epoch: target_epochs.body_epoch,
expected_callers,
expected_call_identities,
expected_direct_call_count: direct_call_count,
expected_return_call_count,
parameter_actions,
result_actions,
old_signature,
replacement_signature: @lib.FuncType::new(
callee_evidence.kept_params,
results,
),
caller_rewrites,
callee_rewrite,
local_projection: {
local_map: dae_build_local_map(
params,
match graph.snapshot.funcs[def_idx] {
@lib.Func(locals, _) => locals.length()
},
remove,
local_use,
preserve_removed_params=Some(localized_params),
),
preserved_removed_params,
},
type_section_rewrite: {
expected: context.type_sec,
replacement: next_type_sec,
replacement_type_idx,
},
function_section_rewrite: {
expected: context.type_idxs,
replacement: next_type_idxs,
},
touched_functions: touched,
invalidated_dependencies,
rewritten_funcs,
candidate,
decision_reason: DaeCandidateProduced,
})
}
///|
fn dae_build_result_projection_actions(
old_results : Array[@lib.ValType],
next_results : Array[@lib.ValType],
) -> Array[DaeResultAction]? {
if next_results.length() > old_results.length() {
return None
}
let actions : Array[DaeResultAction] = []
let mut old_idx = 0
let mut next_idx = 0
while old_idx < old_results.length() {
if next_idx < next_results.length() &&
old_results[old_idx] == next_results[next_idx] {
actions.push(DaeKeepResult)
old_idx += 1
next_idx += 1
continue
}
let mut exact_later = false
if next_idx < next_results.length() {
let mut probe = old_idx + 1
while probe < old_results.length() {
if old_results[probe] == next_results[next_idx] {
exact_later = true
break
}
probe += 1
}
}
if exact_later {
actions.push(DaeRemoveResult)
old_idx += 1
continue
}
let remaining_old = old_results.length() - old_idx
let remaining_next = next_results.length() - next_idx
if next_idx < next_results.length() && remaining_old == remaining_next {
actions.push(DaeRefineResult(next_results[next_idx]))
old_idx += 1
next_idx += 1
continue
}
actions.push(DaeRemoveResult)
old_idx += 1
}
if next_idx != next_results.length() {
return None
}
Some(actions)
}
///|
fn dae_build_signature_rewrite_plan_from_candidate(
original : DaeOriginalBoundarySnapshot,
current : @lib.Module,
graph : DaeCurrentBoundaryGraph,
target_def : Int32,
candidate : DaePassResult,
effects : DaeCommitEffects,
) -> DaeRewritePlan? {
if target_def < 0 ||
target_def >= graph.snapshot.defined ||
candidate.primary_def != target_def ||
!candidate.changed ||
dae_snapshot_module_unchanged(candidate.mod_out, current) ||
candidate.touched.length() != graph.snapshot.defined ||
target_def >= candidate.touched.length() ||
!candidate.touched[target_def] {
return None
}
let target_abs = graph.snapshot.imported + target_def
let old_signature = match graph.snapshot.function_types[target_abs] {
Some(signature) => signature
None => return None
}
let next_type_indices = dae_collect_func_type_indices(candidate.mod_out)
let replacement_signature = match
dae_resolve_func_type(candidate.mod_out, next_type_indices, target_abs) {
Some(signature) => signature
None => return None
}
let @lib.FuncType(old_params, old_results) = old_signature
let @lib.FuncType(next_params, next_results) = replacement_signature
if next_params.length() > old_params.length() ||
next_results.length() > old_results.length() {
return None
}
let shared_facts : DaeBoundaryFacts = {
original,
graph,
incoming_param_live_by_def: [],
incoming_param_live_body_epochs: [],
}
if !dae_can_change_family_signature(shared_facts, target_abs) {
return None
}
let parameter_actions : Array[DaeParameterAction] = []
let mut parameter_changed = false
let mut parameter_refined = false
let mut old_parameter_idx = 0
let mut next_parameter_idx = 0
while old_parameter_idx < old_params.length() {
let remaining_old = old_params.length() - old_parameter_idx
let remaining_next = next_params.length() - next_parameter_idx
let maps_to_next = next_parameter_idx < next_params.length() &&
(
old_params[old_parameter_idx] == next_params[next_parameter_idx] ||
remaining_old == remaining_next
)
if maps_to_next {
if old_params[old_parameter_idx] == next_params[next_parameter_idx] {
parameter_actions.push(DaeKeepParameter)
} else {
parameter_actions.push(
DaeRefineParameterType(next_params[next_parameter_idx]),
)
parameter_changed = true
parameter_refined = true
}
next_parameter_idx += 1
} else {
parameter_actions.push(DaeRemoveParameterReplayEffects)
parameter_changed = true
}
old_parameter_idx += 1
}
if next_parameter_idx != next_params.length() {
return None
}
let result_actions = match
dae_build_result_projection_actions(old_results, next_results) {
Some(actions) => actions
None => return None
}
let mut result_changed = false
let mut result_removed = false
for action in result_actions {
match action {
DaeKeepResult => ()
DaeRefineResult(_) => result_changed = true
DaeRemoveResult => {
result_changed = true
result_removed = true
}
}
}
if !parameter_changed && !result_changed {
return None
}
let (expected_type_sec, expected_type_idxs, expected_funcs) = match
(current.type_sec, current.func_sec, current.code_sec) {
(Some(type_sec), Some(@lib.FuncSec(type_idxs)), Some(@lib.CodeSec(funcs))) =>
(type_sec, type_idxs, funcs)
_ => return None
}
let (replacement_type_sec, replacement_type_idxs, rewritten_funcs) = match
(
candidate.mod_out.type_sec,
candidate.mod_out.func_sec,
candidate.mod_out.code_sec,
) {
(Some(type_sec), Some(@lib.FuncSec(type_idxs)), Some(@lib.CodeSec(funcs))) =>
(type_sec, type_idxs, funcs)
_ => return None
}
if expected_type_idxs.length() != replacement_type_idxs.length() ||
expected_funcs.length() != rewritten_funcs.length() ||
target_def >= replacement_type_idxs.length() ||
target_def >= rewritten_funcs.length() {
return None
}
let expected_call_identities = dae_collect_target_call_identities(
graph.snapshot,
target_abs,
)
let caller_defs : Array[Int32] = []
for identity in expected_call_identities {
if !caller_defs.contains(identity.caller_def) {
caller_defs.push(identity.caller_def)
}
}
let expected_callers : Array[DaeCallerEpochExpectation] = []
let caller_rewrites : Array[DaeCallerRewritePlan] = []
let mut expected_direct_call_count = 0
let mut expected_return_call_count = 0
for caller_def in caller_defs {
if caller_def < 0 ||
caller_def >= graph.snapshot.funcs.length() ||
caller_def >= graph.function_epochs.length() {
return None
}
let @lib.Func(_, @lib.Expr(body)) = graph.snapshot.funcs[caller_def]
let (direct_calls, return_calls) = dae_count_active_target_call_kinds_instrs(
body, target_abs,
)
let epochs = graph.function_epochs[caller_def]
expected_callers.push({
def_idx: caller_def,
signature_epoch: epochs.signature_epoch,
body_epoch: epochs.body_epoch,
direct_calls,
return_calls,
})
expected_direct_call_count += direct_calls
expected_return_call_count += return_calls
if expected_funcs[caller_def] != rewritten_funcs[caller_def] {
caller_rewrites.push({
def_idx: caller_def,
func: rewritten_funcs[caller_def],
calls_rewritten: direct_calls + return_calls,
return_calls_rewritten: return_calls,
})
}
}
let @lib.Func(old_locals, _) = expected_funcs[target_def]
let local_map : Array[Int32] = []
let mut local_idx = 0
while local_idx < old_params.length() + old_locals.length() {
local_map.push(local_idx)
local_idx += 1
}
if parameter_changed {
effects.parameter_changed[target_def] = true
effects.local_projection_changed[target_def] = true
}
if parameter_refined {
effects.parameter_type_refined[target_def] = true
}
if result_changed {
effects.result_changed[target_def] = true
effects.control_refinalization_required[target_def] = true
}
if result_removed {
effects.result_removed[target_def] = true
}
let invalidation_domains = effects.invalidation_domains.copy()
if parameter_changed && !invalidation_domains.contains(DaeInvalidateLocals) {
invalidation_domains.push(DaeInvalidateLocals)
}
let plan_effects = {
..effects,
type_section_changed: expected_type_sec != replacement_type_sec,
metadata_changed: current.name_sec != candidate.mod_out.name_sec,
invalidation_domains,
}
// The scheduler computes dependency invalidations after the commit. This
// private plan row records changed ownership and its required target only.
let invalidated_dependencies : Array[Int32] = [target_def]
for def_idx = 0; def_idx < graph.snapshot.defined; def_idx = def_idx + 1 {
if def_idx != target_def &&
def_idx < plan_effects.structurally_touched.length() &&
plan_effects.structurally_touched[def_idx] {
invalidated_dependencies.push(def_idx)
}
}
let target_epochs = graph.function_epochs[target_def]
Some({
effects: plan_effects,
target_def,
target_abs,
expected_module_epoch: graph.module_epoch,
expected_call_epoch: graph.call_epoch,
expected_type_epoch: graph.type_epoch,
expected_target_signature_epoch: target_epochs.signature_epoch,
expected_target_body_epoch: target_epochs.body_epoch,
expected_callers,
expected_call_identities,
expected_direct_call_count,
expected_return_call_count,
parameter_actions,
result_actions,
old_signature,
replacement_signature,
caller_rewrites,
callee_rewrite: rewritten_funcs[target_def],
local_projection: {
local_map,
preserved_removed_params: Array::make(old_params.length(), false),
},
type_section_rewrite: {
expected: expected_type_sec,
replacement: replacement_type_sec,
replacement_type_idx: replacement_type_idxs[target_def],
},
function_section_rewrite: {
expected: expected_type_idxs,
replacement: replacement_type_idxs,
},
touched_functions: candidate.touched,
invalidated_dependencies,
rewritten_funcs,
candidate: candidate.mod_out,
decision_reason: DaeCandidateProduced,
})
}
///|
fn dae_rewrite_plan_candidate_caller_boundaries_match(
plan : DaeRewritePlan,
) -> Bool {
if plan.effects.caller_drop_removed.any(fn(value) { value }) {
return true
}
let candidate_funcs = match plan.candidate.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return false
}
for rewrite in plan.caller_rewrites {
if rewrite.def_idx < 0 ||
rewrite.def_idx >= candidate_funcs.length() ||
rewrite.def_idx >= plan.rewritten_funcs.length() {
return false
}
let @lib.Func(candidate_locals, @lib.Expr(candidate_body)) = candidate_funcs[rewrite.def_idx]
let @lib.Func(rewritten_locals, @lib.Expr(rewritten_body)) = plan.rewritten_funcs[rewrite.def_idx]
if candidate_locals.length() != rewritten_locals.length() ||
candidate_body.length() != rewritten_body.length() {
return false
}
if candidate_body.length() > 0 &&
(
!dae_snapshot_instr_unchanged(candidate_body[0], rewritten_body[0]) ||
!dae_snapshot_instr_unchanged(
candidate_body[candidate_body.length() - 1],
rewritten_body[rewritten_body.length() - 1],
)
) {
return false
}
}
true
}
///|
fn dae_rewrite_plan_is_consistent(plan : DaeRewritePlan) -> Bool {
if !dae_rewrite_plan_candidate_caller_boundaries_match(plan) {
return false
}
let @lib.FuncType(old_params, old_results) = plan.old_signature
let @lib.FuncType(next_params, next_results) = plan.replacement_signature
if plan.parameter_actions.length() != old_params.length() ||
plan.result_actions.length() != old_results.length() ||
plan.local_projection.preserved_removed_params.length() !=
old_params.length() ||
plan.local_projection.local_map.length() < old_params.length() ||
plan.target_def < 0 ||
plan.target_def >= plan.rewritten_funcs.length() ||
plan.target_def >= plan.touched_functions.length() ||
!dae_snapshot_func_unchanged(
plan.rewritten_funcs[plan.target_def],
plan.callee_rewrite,
) ||
plan.candidate.type_sec != Some(plan.type_section_rewrite.replacement) ||
plan.candidate.func_sec !=
Some(@lib.FuncSec::new(plan.function_section_rewrite.replacement)) ||
(
!dae_snapshot_code_unchanged(
plan.candidate.code_sec,
Some(@lib.CodeSec::new(plan.rewritten_funcs)),
) &&
// The dead-suffix producer owns the exact inactive caller cleanup that
// was already reflected in its candidate and effects.
!plan.effects.caller_drop_removed.any(fn(value) { value })
) ||
plan.function_section_rewrite.replacement[plan.target_def] !=
plan.type_section_rewrite.replacement_type_idx ||
dae_type_sec_func_type(
plan.type_section_rewrite.replacement,
plan.type_section_rewrite.replacement_type_idx,
) !=
Some(plan.replacement_signature) ||
!plan.touched_functions[plan.target_def] ||
!plan.invalidated_dependencies.contains(plan.target_def) ||
plan.decision_reason != DaeCandidateProduced {
return false
}
let mut next_param_idx = 0
let mut old_param_idx = 0
for action in plan.parameter_actions {
match action {
DaeKeepParameter => {
if next_param_idx >= next_params.length() ||
next_params[next_param_idx] != old_params[old_param_idx] {
return false
}
next_param_idx += 1
}
DaeRefineParameterType(refined_type) => {
if next_param_idx >= next_params.length() ||
next_params[next_param_idx] != refined_type {
return false
}
next_param_idx += 1
}
DaeRemovePureParameter
| DaeRemoveParameterReplayEffects
| DaeLocalizeParameterAndRetry
| DaeMaterializeConstantParameter(_) => ()
}
old_param_idx += 1
}
if next_param_idx != next_params.length() {
return false
}
let mut next_result_idx = 0
let mut old_result_idx = 0
for action in plan.result_actions {
match action {
DaeKeepResult => {
if next_result_idx >= next_results.length() ||
next_results[next_result_idx] != old_results[old_result_idx] {
return false
}
next_result_idx += 1
}
DaeRefineResult(refined_type) => {
if next_result_idx >= next_results.length() ||
next_results[next_result_idx] != refined_type {
return false
}
next_result_idx += 1
}
DaeRemoveResult => ()
}
old_result_idx += 1
}
if next_result_idx != next_results.length() {
return false
}
for rewrite in plan.caller_rewrites {
if rewrite.def_idx < 0 ||
rewrite.def_idx >= plan.rewritten_funcs.length() ||
!dae_snapshot_func_unchanged(
rewrite.func,
plan.rewritten_funcs[rewrite.def_idx],
) ||
rewrite.calls_rewritten <= 0 ||
rewrite.return_calls_rewritten < 0 ||
rewrite.return_calls_rewritten > rewrite.calls_rewritten {
return false
}
}
true
}
///|
fn dae_apply_rewrite_plan(
mod_ : @lib.Module,
graph : DaeCurrentBoundaryGraph,
plan : DaeRewritePlan,
checked_candidate~ : (@lib.Module, @lib.Module)? = None,
rewrite_validation_counts~ : Array[Int32]? = None,
scalar_validation_chain~ : DaeScalarValidationChain? = None,
scalar_validation_work~ : DaeScalarValidationWork? = None,
numeric_result_validation_chain~ : DaeScalarValidationChain? = None,
numeric_result_validation_work~ : DaeScalarValidationWork? = None,
) -> DaeRewritePlanApplication {
if !dae_rewrite_plan_is_consistent(plan) {
return dae_rewrite_plan_rejection(mod_, DaeTypeReconstructionUnavailable)
}
if plan.expected_module_epoch != graph.module_epoch {
return dae_rewrite_plan_rejection(mod_, DaeStaleCandidateFacts)
}
if plan.expected_call_epoch != graph.call_epoch {
return dae_rewrite_plan_rejection(mod_, DaeCallCountChanged)
}
if plan.expected_type_epoch != graph.type_epoch {
return dae_rewrite_plan_rejection(mod_, DaeStaleCandidateFacts)
}
if plan.target_def < 0 ||
plan.target_def >= graph.function_epochs.length() ||
plan.target_abs != graph.snapshot.imported + plan.target_def {
return dae_rewrite_plan_rejection(mod_, DaeStaleCandidateFacts)
}
let target_epochs = graph.function_epochs[plan.target_def]
if target_epochs.signature_epoch != plan.expected_target_signature_epoch ||
target_epochs.body_epoch != plan.expected_target_body_epoch {
return dae_rewrite_plan_rejection(mod_, DaeStaleCandidateFacts)
}
if plan.target_abs < 0 ||
plan.target_abs >= graph.snapshot.function_types.length() ||
graph.snapshot.function_types[plan.target_abs] != Some(plan.old_signature) {
return dae_rewrite_plan_rejection(mod_, DaeTypeReconstructionUnavailable)
}
let current_call_identities = dae_collect_target_call_identities(
graph.snapshot,
plan.target_abs,
)
if !dae_direct_call_identities_equal(
current_call_identities,
plan.expected_call_identities,
) {
return dae_rewrite_plan_rejection(mod_, DaeCallCountChanged)
}
let mut direct_calls = 0
let mut return_calls = 0
for expected in plan.expected_callers {
if expected.def_idx < 0 ||
expected.def_idx >= graph.function_epochs.length() ||
expected.def_idx >= graph.snapshot.funcs.length() {
return dae_rewrite_plan_rejection(mod_, DaeStaleCandidateFacts)
}
let epochs = graph.function_epochs[expected.def_idx]
if epochs.signature_epoch != expected.signature_epoch ||
epochs.body_epoch != expected.body_epoch {
return dae_rewrite_plan_rejection(mod_, DaeStaleCandidateFacts)
}
let @lib.Func(_, @lib.Expr(body)) = graph.snapshot.funcs[expected.def_idx]
let (caller_direct, caller_return) = dae_count_active_target_call_kinds_instrs(
body,
plan.target_abs,
)
if caller_direct != expected.direct_calls ||
caller_return != expected.return_calls {
return dae_rewrite_plan_rejection(mod_, DaeCallCountChanged)
}
direct_calls += caller_direct
return_calls += caller_return
}
if direct_calls != plan.expected_direct_call_count ||
return_calls != plan.expected_return_call_count {
return dae_rewrite_plan_rejection(mod_, DaeCallCountChanged)
}
match (mod_.type_sec, mod_.func_sec, mod_.code_sec) {
(Some(type_sec), Some(@lib.FuncSec(type_idxs)), Some(@lib.CodeSec(funcs))) =>
if type_sec != plan.type_section_rewrite.expected ||
type_idxs != plan.function_section_rewrite.expected ||
(
!dae_snapshot_funcs_unchanged(funcs, graph.snapshot.funcs) &&
// A producer-owned inactive caller cleanup may have rewritten the
// aliased source bodies while constructing this exact transaction.
!plan.effects.caller_drop_removed.any(fn(value) { value })
) ||
plan.rewritten_funcs.length() != funcs.length() {
return dae_rewrite_plan_rejection(
mod_,
DaeTypeReconstructionUnavailable,
)
}
_ =>
return dae_rewrite_plan_rejection(mod_, DaeTypeReconstructionUnavailable)
}
let expected_candidate = mod_
.with_type_sec(plan.type_section_rewrite.replacement)
.with_func_sec(@lib.FuncSec::new(plan.function_section_rewrite.replacement))
.with_code_sec(@lib.CodeSec::new(plan.rewritten_funcs))
let expected_candidate = expected_candidate.without_name_sec()
let producer_owns_inactive_call_cleanup = plan.effects.caller_drop_removed.any(fn(
value,
) {
value
},
)
if !dae_snapshot_module_unchanged(expected_candidate, plan.candidate) &&
!producer_owns_inactive_call_cleanup {
return dae_rewrite_plan_rejection(mod_, DaeTypeReconstructionUnavailable)
}
let committed_candidate = if producer_owns_inactive_call_cleanup {
expected_candidate
} else {
plan.candidate
}
let scalar_validation_chain = match scalar_validation_chain {
Some(chain) if dae_scalar_numeric_plan(plan) => Some(chain)
Some(chain) => {
dae_scalar_validation_barrier(chain)
None
}
None => None
}
// This report is issued by the successful inner validation gate in the uniform-value
// scheduler branch and consumed in that same call. Only IR readers run
// between those gates. Never retain the report across a rewrite or callback.
let validation = match checked_candidate {
Some((source, checked)) if physical_equal(source, mod_) &&
physical_equal(checked, committed_candidate) => {
match rewrite_validation_counts {
Some(counts) => counts[1] += 1
None => ()
}
Ok(())
}
_ => {
match rewrite_validation_counts {
Some(counts) => counts[0] += 1
None => ()
}
match numeric_result_validation_chain {
Some(chain) =>
dae_numeric_result_validation_check_candidate(
chain,
mod_,
committed_candidate,
plan,
work=numeric_result_validation_work,
)
None =>
match scalar_validation_chain {
Some(chain) =>
dae_scalar_validation_check_candidate(
chain,
mod_,
committed_candidate,
work=scalar_validation_work,
)
None => @validate.validate_module(committed_candidate)
}
}
}
}
match validation {
Ok(_) =>
{
result: {
mod_out: committed_candidate,
touched: plan.touched_functions,
changed: true,
primary_def: plan.target_def,
},
applied: true,
reason: DaeCandidateApplied,
effects: {
..plan.effects,
candidates_produced: 1,
candidates_applied: 1,
decision_reasons: [DaeCandidateProduced, DaeCandidateApplied],
},
}
Err(_) => dae_rewrite_plan_rejection(mod_, DaeValidationRollback)
}
}
///|
#warnings("-unused_value")
fn dae_rewrite_plan_with_callee(
plan : DaeRewritePlan,
callee : @lib.Func,
) -> DaeRewritePlan {
let rewritten_funcs = plan.rewritten_funcs.copy()
if plan.target_def >= 0 && plan.target_def < rewritten_funcs.length() {
rewritten_funcs[plan.target_def] = callee
}
let candidate = plan.candidate.with_code_sec(
@lib.CodeSec::new(rewritten_funcs),
)
{
effects: plan.effects,
target_def: plan.target_def,
target_abs: plan.target_abs,
expected_module_epoch: plan.expected_module_epoch,
expected_call_epoch: plan.expected_call_epoch,
expected_type_epoch: plan.expected_type_epoch,
expected_target_signature_epoch: plan.expected_target_signature_epoch,
expected_target_body_epoch: plan.expected_target_body_epoch,
expected_callers: plan.expected_callers,
expected_call_identities: plan.expected_call_identities,
expected_direct_call_count: plan.expected_direct_call_count,
expected_return_call_count: plan.expected_return_call_count,
parameter_actions: plan.parameter_actions,
result_actions: plan.result_actions,
old_signature: plan.old_signature,
replacement_signature: plan.replacement_signature,
caller_rewrites: plan.caller_rewrites,
callee_rewrite: callee,
local_projection: plan.local_projection,
type_section_rewrite: plan.type_section_rewrite,
function_section_rewrite: plan.function_section_rewrite,
touched_functions: plan.touched_functions,
invalidated_dependencies: plan.invalidated_dependencies,
rewritten_funcs,
candidate,
decision_reason: plan.decision_reason,
}
}
///|
fn dae_try_rewrite_scalar_unused_param_candidate(
mod_ : @lib.Module,
original : DaeOriginalBoundarySnapshot,
graph : DaeCurrentBoundaryGraph,
callee_abs : Int32,
remove : Array[Bool],
local_use : DaeLocalUse,
direct_call_count : Int32,
direct_caller_defs : Array[Int32],
callee_source~ : @lib.Func? = None,
entry_live~ : Array[Bool]? = None,
liveness_work~ : Array[Int32]? = None,
traps_never_happen~ : Bool = false,
allow_stack_carried_void_effects~ : Bool = false,
) -> DaeRewritePlanApplication {
match
dae_build_scalar_unused_param_rewrite_plan(
mod_,
original,
graph,
callee_abs,
remove,
local_use,
direct_call_count,
direct_caller_defs,
callee_source~,
entry_live~,
liveness_work~,
traps_never_happen~,
allow_stack_carried_void_effects~,
) {
Some(plan) => dae_apply_rewrite_plan(mod_, graph, plan)
None => dae_rewrite_plan_rejection(mod_, DaeActualValueSliceUnavailable)
}
}
///|
fn dae_try_rewrite_candidate(
mod_ : @lib.Module,
callee_abs : Int32,
remove : Array[Bool],
const_params : Array[@lib.Instruction?],
local_use : DaeLocalUse,
direct_call_count : Int32,
direct_caller_defs : Array[Int32],
allow_stack_carried_void_effects~ : Bool = false,
original~ : DaeOriginalBoundarySnapshot? = None,
graph~ : DaeCurrentBoundaryGraph? = None,
entry_live~ : Array[Bool]? = None,
liveness_work~ : Array[Int32]? = None,
checked_candidate_report~ : Array[(@lib.Module, @lib.Module)]? = None,
rewrite_validation_counts~ : Array[Int32]? = None,
scalar_validation_chain~ : DaeScalarValidationChain? = None,
scalar_validation_work~ : DaeScalarValidationWork? = None,
) -> DaePassResult {
let active_original = match original {
Some(original) => original
None => dae_collect_original_boundary_snapshot(mod_)
}
let active_graph = match graph {
Some(graph) => graph
None => dae_current_boundary_graph_new(mod_)
}
match
dae_build_scalar_unused_param_rewrite_plan(
mod_,
active_original,
active_graph,
callee_abs,
remove,
local_use,
direct_call_count,
direct_caller_defs,
entry_live~,
liveness_work~,
materialized_params=Some(const_params),
allow_stack_carried_void_effects~,
) {
Some(plan) => {
let application = dae_apply_rewrite_plan(
mod_,
active_graph,
plan,
rewrite_validation_counts~,
scalar_validation_chain~,
scalar_validation_work~,
)
if application.applied {
match checked_candidate_report {
Some(report) => report.push((mod_, application.result.mod_out))
None => ()
}
}
application.result
}
None => { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
}
///|
#warnings("-unused_value")
fn dae_try_rewrite_verified_uniform_const_candidate(
mod_ : @lib.Module,
callee_abs : Int32,
params : Array[@lib.ValType],
remove : Array[Bool],
expected_const_params : Array[@lib.Instruction?],
local_use : DaeLocalUse,
verified_calls : Int32,
verified_callers : Array[Int32],
) -> DaePassResult {
if verified_calls <= 0 ||
remove.length() != params.length() ||
expected_const_params.length() != params.length() {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let boundary_snapshot = dae_collect_module_boundary_snapshot(mod_)
if callee_abs < 0 || callee_abs >= boundary_snapshot.total {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let manual_direct_callers = dae_make_direct_callers(boundary_snapshot.total)
manual_direct_callers[callee_abs] = verified_callers
let uniform_const_params = match
dae_collect_uniform_const_actuals_from_boundary_facts(
mod_,
callee_abs,
params.length(),
boundary_snapshot.type_indices,
dae_make_call_boundary_facts(
boundary_snapshot.call_facts.escaped,
boundary_snapshot.call_facts.tail_calls,
manual_direct_callers,
),
collect_params=Some(remove),
) {
Some(values) => values
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let mut p = 0
while p < params.length() {
if remove[p] {
match (uniform_const_params[p], expected_const_params[p]) {
(Some(actual), Some(expected)) if dae_materialized_const_equal(
actual, expected,
) => ()
_ =>
return {
mod_out: mod_,
touched: [],
changed: false,
primary_def: -1,
}
}
}
p += 1
}
let mut suffix_start = 0
while suffix_start < remove.length() && !remove[suffix_start] {
suffix_start += 1
}
let mut is_materialized_suffix = suffix_start < remove.length()
let expected_suffix : Array[@lib.Instruction] = []
p = suffix_start
while p < remove.length() && is_materialized_suffix {
if !remove[p] {
is_materialized_suffix = false
} else {
match expected_const_params[p] {
Some(expected) if dae_instr_is_materializable_const(expected) =>
expected_suffix.push(expected)
_ => is_materialized_suffix = false
}
}
p += 1
}
if is_materialized_suffix {
let suffix_result = dae_try_rewrite_verified_materialized_const_suffix_candidate(
mod_, callee_abs, params, expected_suffix, local_use, verified_calls, verified_callers,
)
if suffix_result.changed {
return suffix_result
}
}
dae_try_rewrite_candidate(
mod_, callee_abs, remove, expected_const_params, local_use, verified_calls, verified_callers,
)
}
///|
fn dae_try_rewrite_uniform_ref_null_callee_from_selected_callers_with_facts(
mod_ : @lib.Module,
caller_defs : Array[Int32],
imported : Int32,
defined : Int32,
total : Int32,
type_indices : Array[@lib.TypeIdx],
boundary_facts : DaeCallBoundaryFacts,
original~ : DaeOriginalBoundarySnapshot? = None,
graph~ : DaeCurrentBoundaryGraph? = None,
) -> DaePassResult {
if caller_defs.length() == 0 {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let candidates = Array::make(defined, false)
for caller_def in caller_defs {
if caller_def < 0 || caller_def >= funcs.length() {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
let callees : Array[Int32] = []
dae_collect_direct_call_targets_anywhere(body, imported, defined, callees)
for callee_def in callees {
if callee_def >= 0 && callee_def < candidates.length() {
candidates[callee_def] = true
}
}
}
for callee_def = 0
callee_def < candidates.length()
callee_def = callee_def + 1 {
if !candidates[callee_def] {
continue
}
let callee_abs = imported + callee_def
if callee_abs < 0 ||
callee_abs >= total ||
boundary_facts.escaped[callee_abs] ||
boundary_facts.tail_calls[callee_abs] ||
boundary_facts.direct_calls[callee_abs] <= 0 {
continue
}
let func_type = match
dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(func_type) => func_type
None => continue
}
let @lib.FuncType(params, _) = func_type
if params.length() == 0 {
continue
}
let @lib.Func(locals, _) = funcs[callee_def]
let local_use = dae_local_use_for_callee(
funcs[callee_def],
params.length() + locals.length(),
callee_abs,
params.length(),
)
let uniform = match
dae_collect_uniform_const_actuals_from_boundary_facts(
mod_,
callee_abs,
params.length(),
type_indices,
boundary_facts,
) {
Some(values) => values
None => continue
}
let final_param = params.length() - 1
if local_use.writes[final_param] {
continue
}
let expected = match uniform[final_param] {
Some(@lib.RefNull(_) as value) => value
_ => continue
}
let remove = Array::make(params.length(), false)
remove[final_param] = true
let const_params : Array[@lib.Instruction?] = Array::make(
params.length(),
None,
)
const_params[final_param] = Some(expected)
let (plan_original, plan_graph) = match (original, graph) {
(Some(original), Some(graph)) => (original, graph)
_ =>
(
dae_collect_original_boundary_snapshot(mod_),
dae_current_boundary_graph_new(mod_),
)
}
let result = match
dae_build_scalar_unused_param_rewrite_plan(
mod_,
plan_original,
plan_graph,
callee_abs,
remove,
local_use,
boundary_facts.direct_calls[callee_abs],
boundary_facts.direct_callers[callee_abs],
materialized_params=Some(const_params),
verified_materialized_suffix=Some([expected]),
) {
Some(plan) => {
let plan_application = dae_apply_rewrite_plan(mod_, plan_graph, plan)
if plan_application.applied {
plan_application.result
} else {
{ mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
}
None => { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if result.changed {
if result.primary_def < 0 || result.primary_def >= result.touched.length() {
return result
}
let cleanup_defs = Array::make(result.touched.length(), false)
cleanup_defs[result.primary_def] = true
let folded = dae_try_fold_selected_ref_null_is_null_ifs_once(
result.mod_out,
cleanup_defs,
)
if !folded.changed {
return result
}
let combined_touched = result.touched.copy()
for def_idx = 0; def_idx < folded.touched.length(); def_idx = def_idx + 1 {
if folded.touched[def_idx] && def_idx < combined_touched.length() {
combined_touched[def_idx] = true
}
}
let sentinel_folded = dae_try_fold_selected_i64_sentinel_wrap_ifs_once(
folded.mod_out,
cleanup_defs,
)
if !sentinel_folded.changed {
return {
mod_out: folded.mod_out,
touched: combined_touched,
changed: true,
primary_def: result.primary_def,
}
}
for def_idx = 0
def_idx < sentinel_folded.touched.length()
def_idx = def_idx + 1 {
if sentinel_folded.touched[def_idx] &&
def_idx < combined_touched.length() {
combined_touched[def_idx] = true
}
}
return {
mod_out: sentinel_folded.mod_out,
touched: combined_touched,
changed: true,
primary_def: result.primary_def,
}
}
}
{ mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
///|
fn dae_instruction_observability_evidence_is_consistent(
evidence : DaeInstructionObservabilityEvidence,
) -> Bool {
evidence.erasable_before_unreachable ==
(
!evidence.has_observable_effect &&
!evidence.has_branch_control &&
!evidence.may_trap_or_unknown
)
}
///|
fn dae_instruction_observability_evidence(
instr : @lib.Instruction,
) -> DaeInstructionObservabilityEvidence {
let mut has_observable_effect = false
let mut has_branch_control = false
let mut child_may_trap_or_unknown = false
match instr {
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.GlobalSet(_)
| @lib.TableSet(_)
| @lib.MemoryGrow(_)
| @lib.I32Store(_)
| @lib.I64Store(_)
| @lib.F32Store(_)
| @lib.F64Store(_)
| @lib.I32Store8(_)
| @lib.I32Store16(_)
| @lib.I64Store8(_)
| @lib.I64Store16(_)
| @lib.I64Store32(_)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.AtomicFence(_)
| @lib.MemoryAtomicNotify(_)
| @lib.MemoryAtomicWait32(_)
| @lib.MemoryAtomicWait64(_)
| @lib.I32AtomicStore(_, _)
| @lib.I64AtomicStore(_, _)
| @lib.I32AtomicStore8(_, _)
| @lib.I32AtomicStore16(_, _)
| @lib.I64AtomicStore8(_, _)
| @lib.I64AtomicStore16(_, _)
| @lib.I64AtomicStore32(_, _)
| @lib.AtomicRmw(_, _, _)
| @lib.AtomicCmpxchg(_, _, _) => has_observable_effect = true
@lib.Br(_) | @lib.BrIf(_) | @lib.BrTable(_, _) | @lib.Return =>
has_branch_control = true
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
for child in body {
let child_evidence = dae_instruction_observability_evidence(child)
has_observable_effect = has_observable_effect ||
child_evidence.has_observable_effect
has_branch_control = has_branch_control ||
child_evidence.has_branch_control
child_may_trap_or_unknown = child_may_trap_or_unknown ||
child_evidence.may_trap_or_unknown
}
@lib.If(_, then_body, else_body) => {
for child in then_body {
let child_evidence = dae_instruction_observability_evidence(child)
has_observable_effect = has_observable_effect ||
child_evidence.has_observable_effect
has_branch_control = has_branch_control ||
child_evidence.has_branch_control
child_may_trap_or_unknown = child_may_trap_or_unknown ||
child_evidence.may_trap_or_unknown
}
match else_body {
Some(body) =>
for child in body {
let child_evidence = dae_instruction_observability_evidence(child)
has_observable_effect = has_observable_effect ||
child_evidence.has_observable_effect
has_branch_control = has_branch_control ||
child_evidence.has_branch_control
child_may_trap_or_unknown = child_may_trap_or_unknown ||
child_evidence.may_trap_or_unknown
}
None => ()
}
}
_ => ()
}
let known_nontrapping = match instr {
@lib.Nop
| @lib.Drop
| @lib.LocalSet(_)
| @lib.LocalTee(_)
| @lib.Select(_)
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return => true
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.TryTable(_, _, _) => !child_may_trap_or_unknown
_ =>
dae_instr_is_droppable_pure_leaf(instr) ||
dae_instr_is_nontrapping_pure_unary(instr) ||
dae_instr_is_nontrapping_pure_binary(instr)
}
let may_trap_or_unknown = !known_nontrapping
let evidence = {
has_observable_effect,
has_branch_control,
may_trap_or_unknown,
erasable_before_unreachable: !has_observable_effect &&
!has_branch_control &&
!may_trap_or_unknown,
}
if dae_instruction_observability_evidence_is_consistent(evidence) {
evidence
} else {
{
has_observable_effect: true,
has_branch_control: true,
may_trap_or_unknown: true,
erasable_before_unreachable: false,
}
}
}
///|
fn dae_instruction_is_erasable_before_unreachable_at(
instrs : Array[@lib.Instruction],
idx : Int32,
) -> Bool {
if idx < 0 || idx >= instrs.length() {
return false
}
let evidence = dae_instruction_observability_evidence(instrs[idx])
if evidence.has_observable_effect {
return false
}
if evidence.has_branch_control {
match instrs[idx] {
@lib.Loop(_, _) => return false
@lib.Block(_, _) | @lib.If(_, _, _) | @lib.TryTable(_, _, _) =>
if dae_instr_has_escaping_branch(instrs[idx], 0) {
return false
}
_ => return false
}
}
if !evidence.may_trap_or_unknown {
return true
}
if idx >= 1 &&
dae_instr_is_const_safe_float_to_int_unary(instrs[idx - 1], instrs[idx]) {
return true
}
idx >= 2 &&
dae_instr_is_const_safe_numeric_binary(
instrs[idx - 2],
instrs[idx - 1],
instrs[idx],
)
}
///|
fn dae_instr_has_observable_effect(instr : @lib.Instruction) -> Bool {
dae_instruction_observability_evidence(instr).has_observable_effect
}
///|
fn dae_instr_has_observable_effect_or_branch(instr : @lib.Instruction) -> Bool {
let evidence = dae_instruction_observability_evidence(instr)
evidence.has_observable_effect || evidence.has_branch_control
}
///|
fn dae_instrs_have_observable_effect_or_branch(
instrs : Array[@lib.Instruction],
) -> Bool {
instrs.any(fn(instr) { dae_instr_has_observable_effect_or_branch(instr) })
}
///|
fn dae_instr_is_unreachable_root(instr : @lib.Instruction) -> Bool {
match instr {
@lib.Unreachable => true
@lib.Block(_, @lib.Expr(body)) =>
body.length() == 1 && dae_instr_is_unreachable_root(body[0])
_ => false
}
}
///|
fn dae_dead_suffix_evidence(
instrs : Array[@lib.Instruction],
) -> DaeDeadSuffixEvidence? {
let mut idx = 0
while idx < instrs.length() {
if dae_instr_is_unreachable_root(instrs[idx]) {
// Borrow this immutable source body; every consumer starts at the explicit
// suffix boundary, including stack-origin queries with empty entry state.
return Some({ root_idx: idx, suffix_start: idx + 1, body: instrs, })
}
idx += 1
}
None
}
///|
fn dae_original_first_root_index(
body : Array[@lib.Instruction],
first_roots : Array[Int32]?,
def_idx : Int32,
) -> Int32 {
match first_roots {
Some(roots) if def_idx >= 0 && def_idx < roots.length() => {
let known = roots[def_idx]
if known != -2 {
return known
}
let first_root = match dae_dead_suffix_evidence(body) {
Some(evidence) => evidence.root_idx
None => -1
}
roots[def_idx] = first_root
first_root
}
_ => -2
}
}
///|
fn dae_dead_suffix_evidence_from_first_root(
body : Array[@lib.Instruction],
known_first_root : Int32,
) -> DaeDeadSuffixEvidence? {
if known_first_root == -1 {
return None
}
if known_first_root < 0 || known_first_root >= body.length() {
return dae_dead_suffix_evidence(body)
}
Some({ root_idx: known_first_root, suffix_start: known_first_root + 1, body, })
}
///|
fn dae_simplify_to_unreachable_when_prefix_unobservable(
func : @lib.Func,
) -> @lib.Func {
let @lib.Func(locals, @lib.Expr(body)) = func
let mut idx = 0
while idx < body.length() {
if dae_instr_is_unreachable_root(body[idx]) {
let mut prefix_idx = 0
while prefix_idx < idx {
if !dae_instruction_is_erasable_before_unreachable_at(body, prefix_idx) {
return func
}
prefix_idx += 1
}
return @lib.Func::new(
locals,
@lib.Expr::new([@lib.Instruction::unreachable_()]),
)
}
idx += 1
}
func
}
///|
fn dae_rewrite_dropped_result_calls_with_policy_instrs(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
stop_at_unreachable_root : Bool,
preserve_uninhabitable_control~ : Bool = false,
) -> (Array[@lib.Instruction], Int32) {
let mut out : Array[@lib.Instruction]? = None
let mut rewrites = 0
let mut idx = 0
while idx < instrs.length() {
if stop_at_unreachable_root && dae_instr_is_unreachable_root(instrs[idx]) {
// Normalizing a wrapped trap or discarding its suffix is a change even
// when it does not increment the dropped-call rewrite count.
if out is None &&
idx + 1 == instrs.length() &&
instrs[idx] is @lib.Unreachable {
return (instrs, rewrites)
}
if out is None {
out = Some(dae_copy_instr_prefix(instrs, idx))
}
out.unwrap().push(@lib.Instruction::unreachable_())
break
}
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(@lib.Call(func_idx), @lib.Drop) if dae_func_idx_slot(func_idx) ==
callee_abs => {
if out is None {
out = Some(dae_copy_instr_prefix(instrs, idx))
}
out.unwrap().push(instrs[idx])
if preserve_uninhabitable_control {
out.unwrap().push(@lib.Instruction::unreachable_())
}
rewrites += 1
idx += 2
if preserve_uninhabitable_control {
return (out.unwrap(), rewrites)
}
continue
}
_ => ()
}
}
let next = match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten, nested) = dae_rewrite_dropped_result_calls_with_policy_instrs(
body,
callee_abs,
stop_at_unreachable_root,
preserve_uninhabitable_control~,
)
rewrites += nested
if physical_equal(rewritten, body) {
instrs[idx]
} else {
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten))
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten, nested) = dae_rewrite_dropped_result_calls_with_policy_instrs(
body,
callee_abs,
stop_at_unreachable_root,
preserve_uninhabitable_control~,
)
rewrites += nested
if physical_equal(rewritten, body) {
instrs[idx]
} else {
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten))
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten, nested) = dae_rewrite_dropped_result_calls_with_policy_instrs(
body,
callee_abs,
stop_at_unreachable_root,
preserve_uninhabitable_control~,
)
rewrites += nested
if physical_equal(rewritten, body) {
instrs[idx]
} else {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten),
)
}
}
@lib.If(block_type, then_body, else_body) => {
let (then_rewritten, then_nested) = dae_rewrite_dropped_result_calls_with_policy_instrs(
then_body,
callee_abs,
stop_at_unreachable_root,
preserve_uninhabitable_control~,
)
rewrites += then_nested
let mut else_changed = false
let rewritten_else = match else_body {
Some(body) => {
let (rewritten, nested) = dae_rewrite_dropped_result_calls_with_policy_instrs(
body,
callee_abs,
stop_at_unreachable_root,
preserve_uninhabitable_control~,
)
rewrites += nested
if physical_equal(rewritten, body) {
else_body
} else {
else_changed = true
Some(rewritten)
}
}
None => None
}
if !else_changed && physical_equal(then_rewritten, then_body) {
instrs[idx]
} else {
@lib.Instruction::if_(block_type, then_rewritten, rewritten_else)
}
}
_ => instrs[idx]
}
match out {
Some(body) => body.push(next)
None =>
if !physical_equal(next, instrs[idx]) {
let body = dae_copy_instr_prefix(instrs, idx)
body.push(next)
out = Some(body)
}
}
idx += 1
}
(out.unwrap_or(instrs), rewrites)
}
///|
fn dae_dropped_result_scope_from_optional(
func_count : Int32,
caller_defs : Array[Int32]?,
) -> DaeDroppedResultScope {
match caller_defs {
Some(callers) => { caller_defs: callers.copy(), }
None => {
let defs : Array[Int32] = []
let mut def_idx = 0
while def_idx < func_count {
defs.push(def_idx)
def_idx += 1
}
{ caller_defs: defs, }
}
}
}
///|
fn dae_dropped_result_scope_direct_call_evidence(
funcs : Array[@lib.Func],
scope : DaeDroppedResultScope,
callee_abs : Int32,
skip_def : Int32,
) -> DaeDroppedResultScopeDirectCallEvidence {
let mut direct_call_count = 0
let mut dropped_direct_call_count = 0
let mut has_direct_call = false
let mut has_undropped_direct_call = false
let mut has_live_undropped_direct_call = false
for def_idx in scope.caller_defs {
if def_idx != skip_def && def_idx >= 0 && def_idx < funcs.length() {
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
let evidence = dae_direct_call_target_evidence(body, callee_abs)
direct_call_count += evidence.direct_call_count
dropped_direct_call_count += evidence.dropped_direct_call_count
has_direct_call = has_direct_call || evidence.has_direct_call
has_undropped_direct_call = has_undropped_direct_call ||
evidence.has_undropped_direct_call
has_live_undropped_direct_call = has_live_undropped_direct_call ||
evidence.has_live_undropped_direct_call
}
}
{
direct_call_count,
dropped_direct_call_count,
has_direct_call,
has_undropped_direct_call,
has_live_undropped_direct_call,
}
}
///|
fn dae_instrs_end_in_direct_call(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
) -> Bool {
if instrs.length() == 0 {
return false
}
match instrs[instrs.length() - 1] {
@lib.Call(func_idx) => dae_func_idx_slot(func_idx) == callee_abs
_ => false
}
}
///|
fn dae_void_caller_dropped_result_evidence_instrs(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
type_sec : @lib.TypeSec?,
caller_void : Bool,
allow_terminal : Bool,
) -> DaeDroppedResultScopeDirectCallEvidence {
let mut direct_call_count = 0
let mut dropped_direct_call_count = 0
let mut has_live_undropped_direct_call = false
let mut idx = 0
while idx < instrs.length() {
match instrs[idx] {
@lib.Call(func_idx) if dae_func_idx_slot(func_idx) == callee_abs => {
direct_call_count += 1
if (
idx + 1 < instrs.length() &&
instrs[idx + 1] == @lib.Instruction::drop()
) ||
(
caller_void &&
idx + 1 < instrs.length() &&
instrs[idx + 1] == @lib.Instruction::return_()
) ||
(caller_void && allow_terminal && idx + 1 == instrs.length()) {
dropped_direct_call_count += 1
} else {
has_live_undropped_direct_call = true
}
}
@lib.Block(block_type, @lib.Expr(body)) => {
let dropped_single_result_block = match
dae_control_block_type_signature_evidence_from_type_sec(
block_type, type_sec,
) {
Some(signature) =>
signature.input_count == 0 &&
signature.output_count == 1 &&
idx + 1 < instrs.length() &&
instrs[idx + 1] == @lib.Instruction::drop() &&
dae_instrs_end_in_direct_call(body, callee_abs)
None => false
}
let nested = dae_void_caller_dropped_result_evidence_instrs(
body, callee_abs, type_sec, caller_void, dropped_single_result_block,
)
direct_call_count += nested.direct_call_count
dropped_direct_call_count += nested.dropped_direct_call_count
has_live_undropped_direct_call = has_live_undropped_direct_call ||
nested.has_live_undropped_direct_call
}
@lib.Loop(_, @lib.Expr(body)) | @lib.TryTable(_, _, @lib.Expr(body)) => {
let nested = dae_void_caller_dropped_result_evidence_instrs(
body, callee_abs, type_sec, caller_void, false,
)
direct_call_count += nested.direct_call_count
dropped_direct_call_count += nested.dropped_direct_call_count
has_live_undropped_direct_call = has_live_undropped_direct_call ||
nested.has_live_undropped_direct_call
}
@lib.If(_, then_body, else_body) => {
let then_evidence = dae_void_caller_dropped_result_evidence_instrs(
then_body, callee_abs, type_sec, caller_void, false,
)
direct_call_count += then_evidence.direct_call_count
dropped_direct_call_count += then_evidence.dropped_direct_call_count
has_live_undropped_direct_call = has_live_undropped_direct_call ||
then_evidence.has_live_undropped_direct_call
match else_body {
Some(body) => {
let else_evidence = dae_void_caller_dropped_result_evidence_instrs(
body, callee_abs, type_sec, caller_void, false,
)
direct_call_count += else_evidence.direct_call_count
dropped_direct_call_count += else_evidence.dropped_direct_call_count
has_live_undropped_direct_call = has_live_undropped_direct_call ||
else_evidence.has_live_undropped_direct_call
}
None => ()
}
}
_ => ()
}
idx += 1
}
{
direct_call_count,
dropped_direct_call_count,
has_direct_call: direct_call_count > 0,
has_undropped_direct_call: dropped_direct_call_count < direct_call_count,
has_live_undropped_direct_call,
}
}
///|
fn dae_dropped_result_scope_direct_call_evidence_with_void_returns(
funcs : Array[@lib.Func],
type_sec : @lib.TypeSec?,
void_callers : Array[Bool],
scope : DaeDroppedResultScope,
callee_abs : Int32,
) -> DaeDroppedResultScopeDirectCallEvidence {
let mut result : DaeDroppedResultScopeDirectCallEvidence = {
direct_call_count: 0,
dropped_direct_call_count: 0,
has_direct_call: false,
has_undropped_direct_call: false,
has_live_undropped_direct_call: false,
}
for def_idx in scope.caller_defs {
if def_idx >= 0 && def_idx < funcs.length() {
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
let caller_void = def_idx < void_callers.length() && void_callers[def_idx]
let evidence = dae_void_caller_dropped_result_evidence_instrs(
body, callee_abs, type_sec, caller_void, true,
)
result = {
direct_call_count: result.direct_call_count + evidence.direct_call_count,
dropped_direct_call_count: result.dropped_direct_call_count +
evidence.dropped_direct_call_count,
has_direct_call: result.has_direct_call || evidence.has_direct_call,
has_undropped_direct_call: result.has_undropped_direct_call ||
evidence.has_undropped_direct_call,
has_live_undropped_direct_call: result.has_live_undropped_direct_call ||
evidence.has_live_undropped_direct_call,
}
}
}
result
}
///|
fn dae_rewrite_dropped_result_calls_with_void_returns_instrs(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
type_sec : @lib.TypeSec?,
caller_void : Bool,
allow_terminal : Bool,
) -> (Array[@lib.Instruction], Int32) {
let out : Array[@lib.Instruction] = []
let mut rewrites = 0
let mut idx = 0
while idx < instrs.length() {
match instrs[idx] {
@lib.Call(func_idx) if dae_func_idx_slot(func_idx) == callee_abs => {
out.push(instrs[idx])
if idx + 1 < instrs.length() &&
instrs[idx + 1] == @lib.Instruction::drop() {
rewrites += 1
idx += 2
continue
}
if caller_void &&
(
(
idx + 1 < instrs.length() &&
instrs[idx + 1] == @lib.Instruction::return_()
) ||
(allow_terminal && idx + 1 == instrs.length())
) {
rewrites += 1
}
}
@lib.Block(bt, @lib.Expr(body)) => {
let dropped_single_result_block = match
dae_control_block_type_signature_evidence_from_type_sec(bt, type_sec) {
Some(signature) =>
signature.input_count == 0 &&
signature.output_count == 1 &&
idx + 1 < instrs.length() &&
instrs[idx + 1] == @lib.Instruction::drop() &&
dae_instrs_end_in_direct_call(body, callee_abs)
None => false
}
let (next, count) = dae_rewrite_dropped_result_calls_with_void_returns_instrs(
body, callee_abs, type_sec, caller_void, dropped_single_result_block,
)
if dropped_single_result_block {
out.push(
@lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new(next),
),
)
idx += 2
rewrites += count
continue
}
out.push(@lib.Instruction::block(bt, @lib.Expr::new(next)))
rewrites += count
}
@lib.Loop(bt, @lib.Expr(body)) => {
let (next, count) = dae_rewrite_dropped_result_calls_with_void_returns_instrs(
body, callee_abs, type_sec, caller_void, false,
)
out.push(@lib.Instruction::loop_(bt, @lib.Expr::new(next)))
rewrites += count
}
@lib.TryTable(bt, catches, @lib.Expr(body)) => {
let (next, count) = dae_rewrite_dropped_result_calls_with_void_returns_instrs(
body, callee_abs, type_sec, caller_void, false,
)
out.push(@lib.Instruction::try_table(bt, catches, @lib.Expr::new(next)))
rewrites += count
}
@lib.If(bt, then_body, else_body) => {
let (next_then, then_count) = dae_rewrite_dropped_result_calls_with_void_returns_instrs(
then_body, callee_abs, type_sec, caller_void, false,
)
let next_else = match else_body {
Some(body) => {
let (next, count) = dae_rewrite_dropped_result_calls_with_void_returns_instrs(
body, callee_abs, type_sec, caller_void, false,
)
rewrites += count
Some(next)
}
None => None
}
out.push(@lib.Instruction::if_(bt, next_then, next_else))
rewrites += then_count
}
_ => out.push(instrs[idx])
}
idx += 1
}
(out, rewrites)
}
///|
fn dae_dropped_result_removal_legality_evidence(
funcs : Array[@lib.Func],
scope : DaeDroppedResultScope,
callee_abs : Int32,
skip_def : Int32,
results : Array[@lib.ValType],
) -> DaeDroppedResultRemovalLegalityEvidence {
let result_count = results.length()
let repairs_results = result_count > 0
let skip_large_result_cleanup = repairs_results &&
skip_def >= 0 &&
skip_def < funcs.length() &&
dae_should_skip_large_result_unreachable_cleanup(funcs[skip_def], results)
let target_tail_call_boundary = if skip_def >= 0 && skip_def < funcs.length() {
let @lib.Func(_, @lib.Expr(body)) = funcs[skip_def]
dae_tail_call_boundary_evidence_instrs(body)
} else {
// Callers without an exact target definition retain the historical
// fail-closed aggregate barrier.
dae_tail_call_boundary_evidence(funcs)
}
let has_live_tail_call = repairs_results &&
target_tail_call_boundary.blocks_result_removal
let scope_direct_calls = dae_dropped_result_scope_direct_call_evidence(
funcs, scope, callee_abs, skip_def,
)
let has_any_direct_call = scope_direct_calls.has_direct_call ||
scope_direct_calls.direct_call_count > 0 ||
scope_direct_calls.dropped_direct_call_count > 0 ||
scope_direct_calls.has_undropped_direct_call
let has_undropped_direct_call = repairs_results &&
has_any_direct_call &&
scope_direct_calls.has_live_undropped_direct_call
{
result_count,
skip_large_result_cleanup,
has_live_tail_call,
has_undropped_direct_call,
blocks_result_removal: skip_large_result_cleanup ||
has_live_tail_call ||
has_undropped_direct_call,
}
}
///|
fn dae_dropped_result_removal_legality_blocks(
evidence : DaeDroppedResultRemovalLegalityEvidence,
) -> Bool {
evidence.result_count > 0 &&
(
evidence.blocks_result_removal ||
evidence.skip_large_result_cleanup ||
evidence.has_live_tail_call ||
evidence.has_undropped_direct_call
)
}
///|
fn dae_dropped_result_module_context(
mod_ : @lib.Module,
callee_abs : Int32,
caller_defs~ : Array[Int32]? = None,
perf~ : HotPerfSession? = None,
timer_prefix~ : String = "",
boundary_facts~ : DaeBoundaryFacts? = None,
cached_precheck~ : DaeDroppedResultCachedPrecheck? = None,
) -> DaeDroppedResultModuleContext? {
let imported = match boundary_facts {
Some(facts) => facts.graph.snapshot.imported
None => dae_imported_func_count(mod_)
}
let def_idx = callee_abs - imported
let type_indices_timer_name = "\{timer_prefix}:type-indices"
let type_indices_start_us = if timer_prefix.length() > 0 {
perf_start_timer_opt(perf, type_indices_timer_name)
} else {
0UL
}
let type_indices = match boundary_facts {
Some(facts) => facts.graph.snapshot.type_indices
None => dae_collect_func_type_indices(mod_)
}
if timer_prefix.length() > 0 {
perf_stop_timer_opt(perf, type_indices_timer_name, type_indices_start_us)
}
let func_type = match dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(ft) => ft
None => return None
}
let @lib.FuncType(params, results) = func_type
let section_copy_timer_name = "\{timer_prefix}:section-copy"
let section_copy_start_us = if timer_prefix.length() > 0 {
perf_start_timer_opt(perf, section_copy_timer_name)
} else {
0UL
}
let signature = match
dae_module_signature_context(mod_, def_idx, copy_funcs=true) {
Some(signature) => {
if timer_prefix.length() > 0 {
perf_stop_timer_opt(
perf, section_copy_timer_name, section_copy_start_us,
)
}
signature
}
None => {
if timer_prefix.length() > 0 {
perf_stop_timer_opt(
perf, section_copy_timer_name, section_copy_start_us,
)
}
return None
}
}
let scope = dae_dropped_result_scope_from_optional(
signature.funcs.length(),
caller_defs,
)
let undropped_scan_timer_name = "\{timer_prefix}:undropped-scan"
let undropped_scan_start_us = if timer_prefix.length() > 0 {
perf_start_timer_opt(perf, undropped_scan_timer_name)
} else {
0UL
}
let result_removal_legality = match cached_precheck {
Some(precheck) => precheck.result_removal_legality
None => {
let scoped = dae_dropped_result_removal_legality_evidence(
signature.funcs,
scope,
callee_abs,
def_idx,
results,
)
let original = match boundary_facts {
Some(facts) => facts.original
None => dae_collect_original_boundary_snapshot(mod_)
}
let original_exposure_blocks = callee_abs < 0 ||
callee_abs >= original.exposure.length() ||
dae_function_exposure_blocks_boundary(original.exposure[callee_abs])
if results.length() > 0 && original_exposure_blocks {
{ ..scoped, blocks_result_removal: true, }
} else {
scoped
}
}
}
if timer_prefix.length() > 0 {
perf_stop_timer_opt(
perf, undropped_scan_timer_name, undropped_scan_start_us,
)
}
Some({
signature,
imported,
type_indices,
params,
results,
scope,
result_removal_legality,
})
}
///|
fn dae_specialized_result_facts_new(
original : DaeOriginalBoundarySnapshot,
graph : DaeCurrentBoundaryGraph,
callee_abs : Int32,
caller_defs~ : Array[Int32]? = None,
) -> DaeSpecializedResultFacts? {
let context = match
dae_dropped_result_module_context(graph.mod_, callee_abs, caller_defs~) {
Some(context) => context
None => return None
}
let target_def = context.signature.def_idx
if target_def < 0 ||
target_def >= graph.snapshot.defined ||
target_def >= graph.function_epochs.length() ||
callee_abs < 0 ||
callee_abs >= context.type_indices.length() {
return None
}
let dependencies = dae_boundary_dependency_graph_new(graph)
let topology = graph.topology
let epochs = graph.function_epochs[target_def]
let original_owned = callee_abs >= 0 &&
callee_abs < original.exposure.length() &&
!dae_function_exposure_blocks_boundary(original.exposure[callee_abs])
let result_observers = if target_def <
dependencies.result_observers_by_def.length() {
dependencies.result_observers_by_def[target_def].copy()
} else {
[]
}
let direct_tail_callers = if target_def <
dependencies.tail_result_dependencies_by_def.length() {
dependencies.tail_result_dependencies_by_def[target_def].copy()
} else {
[]
}
let has_outgoing_tail = (
callee_abs < topology.has_outgoing_return_call.length() &&
topology.has_outgoing_return_call[callee_abs]
) ||
(
callee_abs < topology.has_outgoing_return_call_indirect.length() &&
topology.has_outgoing_return_call_indirect[callee_abs]
) ||
(
callee_abs < topology.has_outgoing_return_call_ref.length() &&
topology.has_outgoing_return_call_ref[callee_abs]
)
let direct_tail_callee = direct_tail_callers.length() > 0 ||
(
callee_abs < graph.snapshot.call_facts.tail_calls.length() &&
graph.snapshot.call_facts.tail_calls[callee_abs]
)
Some({
original,
graph,
context,
target_abs: callee_abs,
target_def,
original_owned,
expected_type_idx: context.type_indices[callee_abs],
expected_results: context.results.copy(),
expected_module_epoch: graph.module_epoch,
expected_call_epoch: graph.call_epoch,
expected_type_epoch: graph.type_epoch,
expected_signature_epoch: epochs.signature_epoch,
expected_body_epoch: epochs.body_epoch,
result_epoch: epochs.signature_epoch,
active_call_identities: dae_collect_target_call_identities(
graph.snapshot,
callee_abs,
),
result_observers,
direct_tail_callers,
has_outgoing_tail,
direct_tail_callee,
})
}
///|
fn dae_specialized_result_facts_are_current(
facts : DaeSpecializedResultFacts,
graph : DaeCurrentBoundaryGraph,
) -> Bool {
if !dae_snapshot_module_unchanged(graph.mod_, facts.graph.mod_) ||
facts.expected_module_epoch != graph.module_epoch ||
facts.expected_call_epoch != graph.call_epoch ||
facts.expected_type_epoch != graph.type_epoch ||
facts.target_def < 0 ||
facts.target_def >= graph.function_epochs.length() ||
facts.target_abs != graph.snapshot.imported + facts.target_def {
return false
}
let epochs = graph.function_epochs[facts.target_def]
if facts.expected_signature_epoch != epochs.signature_epoch ||
facts.expected_body_epoch != epochs.body_epoch ||
facts.result_epoch != epochs.signature_epoch {
return false
}
if facts.target_abs < 0 ||
facts.target_abs >= graph.snapshot.type_indices.length() ||
graph.snapshot.type_indices[facts.target_abs] != facts.expected_type_idx {
return false
}
let current_results = match
dae_resolve_func_type(
graph.mod_,
graph.snapshot.type_indices,
facts.target_abs,
) {
Some(@lib.FuncType(_, results)) => results
None => return false
}
if current_results != facts.expected_results ||
!dae_direct_call_identities_equal(
dae_collect_target_call_identities(graph.snapshot, facts.target_abs),
facts.active_call_identities,
) {
return false
}
let dependencies = dae_boundary_dependency_graph_new(graph)
let result_observers = if facts.target_def <
dependencies.result_observers_by_def.length() {
dependencies.result_observers_by_def[facts.target_def]
} else {
[]
}
let direct_tail_callers = if facts.target_def <
dependencies.tail_result_dependencies_by_def.length() {
dependencies.tail_result_dependencies_by_def[facts.target_def]
} else {
[]
}
result_observers == facts.result_observers &&
direct_tail_callers == facts.direct_tail_callers
}
///|
fn dae_specialized_result_facts_block_change(
facts : DaeSpecializedResultFacts,
) -> Bool {
!facts.original_owned ||
facts.has_outgoing_tail ||
facts.direct_tail_callee ||
facts.result_observers.length() > 0 ||
dae_dropped_result_removal_legality_blocks(
facts.context.result_removal_legality,
)
}
///|
fn dae_specialized_result_commit_effects(
facts : DaeSpecializedResultFacts,
candidate : DaePassResult,
) -> DaeCommitEffects {
let defined = facts.graph.snapshot.defined
let effects = dae_commit_effects_empty(defined)
let mut def_idx = 0
while def_idx < candidate.touched.length() && def_idx < defined {
if candidate.touched[def_idx] {
effects.structurally_touched[def_idx] = true
effects.worth_decided[def_idx] = true
effects.worth_optimizing[def_idx] = true
if def_idx != facts.target_def {
effects.caller_drop_removed[def_idx] = true
}
}
def_idx += 1
}
if facts.target_def >= 0 && facts.target_def < defined {
effects.worth_decided[facts.target_def] = true
effects.worth_optimizing[facts.target_def] = true
effects.result_changed[facts.target_def] = true
effects.result_removed[facts.target_def] = true
effects.control_refinalization_required[facts.target_def] = true
let next_type_indices = dae_collect_func_type_indices(candidate.mod_out)
match
dae_resolve_func_type(
candidate.mod_out,
next_type_indices,
facts.target_abs,
) {
Some(@lib.FuncType(next_params, _)) if next_params != facts.context.params => {
effects.parameter_changed[facts.target_def] = true
effects.local_projection_changed[facts.target_def] = true
}
_ => ()
}
}
let repairs_bottom = facts.expected_results.length() == 1 &&
dae_result_type_is_uninhabitable(facts.expected_results[0])
if repairs_bottom {
def_idx = 0
while def_idx < candidate.touched.length() && def_idx < defined {
if def_idx != facts.target_def && candidate.touched[def_idx] {
effects.caller_bottom_repaired[def_idx] = true
effects.control_refinalization_required[def_idx] = true
}
def_idx += 1
}
}
{
..effects,
type_section_changed: true,
metadata_changed: true,
invalidation_domains: dae_invalidation_domains_for_work_family(
DaeDroppedResultWork,
),
decision_reasons: [DaeCandidateProduced],
}
}
///|
#warnings("-unused_value")
fn dae_apply_specialized_result_family_plan(
current : @lib.Module,
graph : DaeCurrentBoundaryGraph,
facts : DaeSpecializedResultFacts,
candidate : DaePassResult,
) -> DaeRewritePlanApplication {
if !dae_specialized_result_facts_are_current(facts, graph) {
return dae_rewrite_plan_rejection(current, DaeStaleCandidateFacts)
}
if dae_specialized_result_facts_block_change(facts) {
return dae_rewrite_plan_rejection(current, DaeVisibleBoundary)
}
let effects = dae_specialized_result_commit_effects(facts, candidate)
match
dae_build_signature_rewrite_plan_from_candidate(
facts.original,
current,
graph,
facts.target_def,
candidate,
effects,
) {
Some(plan) => dae_apply_rewrite_plan(current, graph, plan)
None =>
dae_rewrite_plan_rejection(current, DaeTypeReconstructionUnavailable)
}
}
///|
fn dae_record_producer_effects(
producer_effects_report : Array[DaeCommitEffects],
effects : DaeCommitEffects,
) -> Unit {
if producer_effects_report.length() == 0 {
producer_effects_report.push(effects)
} else {
producer_effects_report[0] = effects
}
}
///|
fn dae_rewrite_dropped_result_calls_in_scope_with_policy(
funcs : Array[@lib.Func],
scope : DaeDroppedResultScope,
callee_abs : Int32,
stop_at_unreachable_root~ : Bool,
preserve_uninhabitable_control~ : Bool = false,
) -> Int32 {
let mut rewritten_call_count = 0
for caller_def in scope.caller_defs {
if caller_def >= 0 && caller_def < funcs.length() {
let @lib.Func(locals, @lib.Expr(body)) = funcs[caller_def]
let (rewritten_body, rewrites) = dae_rewrite_dropped_result_calls_with_policy_instrs(
body,
callee_abs,
stop_at_unreachable_root,
preserve_uninhabitable_control~,
)
if rewrites > 0 {
funcs[caller_def] = @lib.Func::new(
locals,
@lib.Expr::new(rewritten_body),
)
rewritten_call_count += rewrites
}
}
}
rewritten_call_count
}
///|
#warnings("-unused_value")
fn dae_rewrite_dropped_result_calls_in_scope(
funcs : Array[@lib.Func],
scope : DaeDroppedResultScope,
callee_abs : Int32,
) -> Int32 {
dae_rewrite_dropped_result_calls_in_scope_with_policy(
funcs,
scope,
callee_abs,
stop_at_unreachable_root=true,
)
}
///|
fn dae_remove_dropped_result_call_drops_in_scope(
funcs : Array[@lib.Func],
callee_abs : Int32,
scope : DaeDroppedResultScope,
) -> Unit {
ignore(
dae_rewrite_dropped_result_calls_in_scope_with_policy(
funcs,
scope,
callee_abs,
stop_at_unreachable_root=false,
),
)
}
///|
fn dae_dropped_result_repair_plan(
scope : DaeDroppedResultScope,
callee_abs : Int32,
skip_def : Int32,
repair_kind : DaeDroppedResultDeadSuffixRepairKind,
remove_dropped_call_drops~ : Bool,
remove_dropped_call_drops_before_repair~ : Bool,
) -> DaeDroppedResultRepairPlan {
{
scope,
callee_abs,
skip_def,
repair_kind,
remove_dropped_call_drops,
remove_dropped_call_drops_before_repair,
}
}
///|
fn dae_dropped_result_repair_plan_removes_call_drops(
plan : DaeDroppedResultRepairPlan,
) -> Bool {
plan.remove_dropped_call_drops
}
///|
fn dae_apply_dropped_result_drop_cleanup(
funcs : Array[@lib.Func],
plan : DaeDroppedResultRepairPlan,
) -> Unit {
dae_remove_dropped_result_call_drops_in_scope(
funcs,
plan.callee_abs,
plan.scope,
)
}
///|
fn dae_apply_dropped_result_repair_plan(
funcs : Array[@lib.Func],
type_indices : Array[@lib.TypeIdx],
type_sec : @lib.TypeSec,
imported : Int32,
plan : DaeDroppedResultRepairPlan,
) -> Unit {
if dae_dropped_result_repair_plan_removes_call_drops(plan) &&
plan.remove_dropped_call_drops_before_repair {
dae_apply_dropped_result_drop_cleanup(funcs, plan)
}
match plan.repair_kind {
DaeRepairDeadSuffixAllCallers =>
dae_repair_dead_suffix_calls_in_scope_except(
funcs,
plan.callee_abs,
plan.skip_def,
plan.scope,
)
DaeRepairDeadSuffixNoParamCallers =>
ignore(
dae_repair_dead_suffix_calls_in_no_param_scope_except(
funcs,
type_indices,
type_sec,
imported,
plan.callee_abs,
plan.skip_def,
plan.scope,
),
)
}
if dae_dropped_result_repair_plan_removes_call_drops(plan) &&
!plan.remove_dropped_call_drops_before_repair {
dae_apply_dropped_result_drop_cleanup(funcs, plan)
}
}
///|
fn dae_module_code_finalization_plan(
next_params : Array[@lib.ValType],
next_results : Array[@lib.ValType],
touched_policy : DaeModuleCodeTouchedPolicy,
) -> DaeModuleCodeFinalizationPlan {
{
next_params: next_params.copy(),
next_results: next_results.copy(),
touched_policy,
}
}
///|
fn dae_finalize_module_code_rewrite(
mod_ : @lib.Module,
def_idx : Int32,
rewritten_funcs : Array[@lib.Func],
next_params : Array[@lib.ValType],
next_results : Array[@lib.ValType],
touched_policy : DaeModuleCodeTouchedPolicy,
perf~ : HotPerfSession? = None,
type_update_timer_name~ : String = "",
) -> DaePassResult {
let context = match
dae_module_signature_context(mod_, def_idx, copy_funcs=false) {
Some(context) => context
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if rewritten_funcs.length() != context.funcs.length() {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let plan = dae_module_code_finalization_plan(
next_params, next_results, touched_policy,
)
dae_apply_module_boundary_finalization(
mod_,
context,
rewritten_funcs,
plan,
perf~,
type_update_timer_name~,
)
}
///|
fn dae_apply_dropped_result_callee_rewrite(
mod_ : @lib.Module,
context : DaeModuleSignatureContext,
type_indices : Array[@lib.TypeIdx],
imported : Int32,
rewritten_funcs : Array[@lib.Func],
rewritten_callee : @lib.Func,
repair : DaeDroppedResultRepairPlan,
finalization : DaeModuleCodeFinalizationPlan,
perf~ : HotPerfSession? = None,
repair_timer_name~ : String = "",
type_update_timer_name~ : String = "",
) -> DaePassResult {
let def_idx = context.def_idx
if def_idx < 0 || def_idx >= rewritten_funcs.length() {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let repair_start_us = if repair_timer_name.length() > 0 {
perf_start_timer_opt(perf, repair_timer_name)
} else {
0UL
}
dae_apply_dropped_result_repair_plan(
rewritten_funcs,
type_indices,
context.type_sec,
imported,
repair,
)
if repair_timer_name.length() > 0 {
perf_stop_timer_opt(perf, repair_timer_name, repair_start_us)
}
rewritten_funcs[def_idx] = rewritten_callee
dae_apply_module_boundary_finalization(
mod_,
context,
rewritten_funcs,
finalization,
perf~,
type_update_timer_name~,
)
}
///|
fn dae_dropped_result_unreachable_callee_rewrite_plan(
context : DaeDroppedResultModuleContext,
repair_kind : DaeDroppedResultDeadSuffixRepairKind,
next_params : Array[@lib.ValType],
) -> DaeDroppedResultUnreachableCalleeRewritePlan {
{
repair: dae_dropped_result_repair_plan(
context.scope,
context.imported + context.signature.def_idx,
context.signature.def_idx,
repair_kind,
remove_dropped_call_drops=true,
remove_dropped_call_drops_before_repair=false,
),
finalization: dae_module_code_finalization_plan(
next_params,
[],
DaeTouchModuleCodeChangedFuncs,
),
}
}
///|
fn dae_apply_dropped_result_unreachable_callee_rewrite(
mod_ : @lib.Module,
context : DaeDroppedResultModuleContext,
plan : DaeDroppedResultUnreachableCalleeRewritePlan,
) -> DaePassResult {
dae_apply_dropped_result_callee_rewrite(
mod_,
context.signature,
context.type_indices,
context.imported,
context.signature.funcs.copy(),
@lib.Func::new(
@lib.Locals::empty(),
@lib.Expr::new([@lib.Instruction::unreachable_()]),
),
plan.repair,
plan.finalization,
)
}
///|
fn dae_make_tail_call_boundary_evidence(
has_direct_tail_call : Bool,
has_indirect_tail_call : Bool,
) -> DaeTailCallBoundaryEvidence {
{
has_direct_tail_call,
has_indirect_tail_call,
blocks_direct_call_rewrite: has_indirect_tail_call,
blocks_result_removal: has_direct_tail_call || has_indirect_tail_call,
}
}
///|
fn dae_merge_tail_call_boundary_evidence(
left : DaeTailCallBoundaryEvidence,
right : DaeTailCallBoundaryEvidence,
) -> DaeTailCallBoundaryEvidence {
dae_make_tail_call_boundary_evidence(
left.has_direct_tail_call || right.has_direct_tail_call,
left.has_indirect_tail_call || right.has_indirect_tail_call,
)
}
///|
fn dae_tail_call_boundary_evidence_instrs(
instrs : Array[@lib.Instruction],
) -> DaeTailCallBoundaryEvidence {
let mut evidence = dae_make_tail_call_boundary_evidence(false, false)
for instr in instrs {
let instr_evidence = match instr {
@lib.ReturnCall(_) => dae_make_tail_call_boundary_evidence(true, false)
@lib.ReturnCallIndirect(_, _) | @lib.ReturnCallRef(_) =>
dae_make_tail_call_boundary_evidence(false, true)
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_tail_call_boundary_evidence_instrs(body)
@lib.Try(_, @lib.Expr(body), catches, _) => {
let mut evidence = dae_tail_call_boundary_evidence_instrs(body)
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(_, @lib.Expr(catch_body))
| @lib.LegacyCatchAll(@lib.Expr(catch_body)) =>
evidence = dae_merge_tail_call_boundary_evidence(
evidence,
dae_tail_call_boundary_evidence_instrs(catch_body),
)
}
}
evidence
}
@lib.If(_, then_body, Some(else_body)) =>
dae_merge_tail_call_boundary_evidence(
dae_tail_call_boundary_evidence_instrs(then_body),
dae_tail_call_boundary_evidence_instrs(else_body),
)
@lib.If(_, then_body, None) =>
dae_tail_call_boundary_evidence_instrs(then_body)
_ => dae_make_tail_call_boundary_evidence(false, false)
}
evidence = dae_merge_tail_call_boundary_evidence(evidence, instr_evidence)
if evidence.has_direct_tail_call && evidence.has_indirect_tail_call {
return evidence
}
}
evidence
}
///|
fn dae_tail_call_boundary_evidence(
funcs : Array[@lib.Func],
) -> DaeTailCallBoundaryEvidence {
let mut evidence = dae_make_tail_call_boundary_evidence(false, false)
for func in funcs {
let @lib.Func(_, @lib.Expr(body)) = func
evidence = dae_merge_tail_call_boundary_evidence(
evidence,
dae_tail_call_boundary_evidence_instrs(body),
)
if evidence.has_direct_tail_call && evidence.has_indirect_tail_call {
return evidence
}
}
evidence
}
///|
fn dae_result_type_is_uninhabitable(result : @lib.ValType) -> Bool {
match result {
@lib.RefTypeValType(ref_type) if ref_type.is_non_nullable() =>
match ref_type.get_heap_type() {
@lib.AbsHeapTypeHeapType(abs_type)
| @lib.SharedAbsHeapTypeHeapType(abs_type) =>
match abs_type {
@lib.NoneAbsHeapType
| @lib.NoExternAbsHeapType
| @lib.NoFuncAbsHeapType
| @lib.NoExnAbsHeapType
| @lib.NoContAbsHeapType => true
_ => false
}
_ => false
}
_ => false
}
}
///|
fn dae_try_remove_dropped_results(
mod_ : @lib.Module,
callee_abs : Int32,
direct_call_count : Int32,
dropped_call_count : Int32,
perf~ : HotPerfSession? = None,
caller_defs~ : Array[Int32]? = None,
boundary_facts~ : DaeBoundaryFacts? = None,
cached_precheck~ : DaeDroppedResultCachedPrecheck? = None,
) -> DaePassResult {
if direct_call_count == 0 || dropped_call_count != direct_call_count {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let context = match
dae_dropped_result_module_context(
mod_,
callee_abs,
caller_defs~,
perf~,
timer_prefix="detail:dae:core:selected-lanes:dropped-results:selected-defs:helper",
boundary_facts~,
cached_precheck~,
) {
Some(context) => context
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let imported = context.imported
let def_idx = context.signature.def_idx
let type_indices = context.type_indices
let params = context.params
let results = context.results
if results.length() != 1 {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let funcs = context.signature.funcs
let rewrite_scope = context.scope
if dae_dropped_result_removal_legality_blocks(context.result_removal_legality) {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let self_call_count_start_us = perf_start_timer_opt(
perf, "detail:dae:core:selected-lanes:dropped-results:selected-defs:helper:self-call-count",
)
let self_call_count = match cached_precheck {
Some(precheck) => precheck.self_call_count
None => dae_count_self_calls(funcs[def_idx], callee_abs)
}
perf_stop_timer_opt(
perf, "detail:dae:core:selected-lanes:dropped-results:selected-defs:helper:self-call-count",
self_call_count_start_us,
)
if direct_call_count == self_call_count {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let rewritten_funcs = funcs.copy()
let rewrite_calls_start_us = perf_start_timer_opt(
perf, "detail:dae:core:selected-lanes:dropped-results:selected-defs:helper:rewrite-calls",
)
let preserve_uninhabitable_control = dae_result_type_is_uninhabitable(
results[0],
)
let rewritten_call_count = dae_rewrite_dropped_result_calls_in_scope_with_policy(
rewritten_funcs,
rewrite_scope,
callee_abs,
stop_at_unreachable_root=true,
preserve_uninhabitable_control~,
)
perf_stop_timer_opt(
perf, "detail:dae:core:selected-lanes:dropped-results:selected-defs:helper:rewrite-calls",
rewrite_calls_start_us,
)
if rewritten_call_count != direct_call_count {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let repair_plan = dae_dropped_result_repair_plan(
rewrite_scope,
callee_abs,
def_idx,
DaeRepairDeadSuffixNoParamCallers,
remove_dropped_call_drops=true,
remove_dropped_call_drops_before_repair=true,
)
let @lib.Func(callee_locals, @lib.Expr(callee_body)) = rewritten_funcs[def_idx]
let next_callee_body = callee_body.copy()
next_callee_body.push(@lib.Instruction::drop())
let callee_rewrite_start_us = perf_start_timer_opt(
perf, "detail:dae:core:selected-lanes:dropped-results:selected-defs:helper:callee-rewrite",
)
let rewritten_callee = dae_simplify_to_unreachable_when_prefix_unobservable(
@lib.Func::new(callee_locals, @lib.Expr::new(next_callee_body)),
)
perf_stop_timer_opt(
perf, "detail:dae:core:selected-lanes:dropped-results:selected-defs:helper:callee-rewrite",
callee_rewrite_start_us,
)
let finalization_plan = dae_module_code_finalization_plan(
params,
[],
DaeTouchModuleCodeChangedFuncs,
)
dae_apply_dropped_result_callee_rewrite(
mod_,
context.signature,
type_indices,
imported,
rewritten_funcs,
rewritten_callee,
repair_plan,
finalization_plan,
perf~,
repair_timer_name="detail:dae:core:selected-lanes:dropped-results:selected-defs:helper:repair-plan",
type_update_timer_name="detail:dae:core:selected-lanes:dropped-results:selected-defs:helper:type-update",
)
}
///|
fn dae_func_is_unreachable_only(func : @lib.Func) -> Bool {
let @lib.Func(_, @lib.Expr(body)) = func
body.length() == 1 && dae_instr_is_unreachable_root(body[0])
}
///|
fn dae_should_skip_large_result_unreachable_cleanup(
_func : @lib.Func,
_results : Array[@lib.ValType],
) -> Bool {
false
}
///|
fn dae_collect_original_large_unreachable_prefixes(
mod_ : @lib.Module,
) -> Array[Bool] {
Array::make(dae_defined_func_count(mod_), false)
}
///|
fn dae_should_skip_original_large_result_unreachable_cleanup(
original_large_unreachable_prefixes : Array[Bool],
def_idx : Int32,
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
callee_abs : Int32,
) -> Bool {
if def_idx < 0 ||
def_idx >= original_large_unreachable_prefixes.length() ||
!original_large_unreachable_prefixes[def_idx] {
return false
}
match dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(_, results)) => results.length() > 0
None => false
}
}
///|
fn dae_empty_direct_call_target_evidence() -> DaeDirectCallTargetEvidence {
{
direct_call_count: 0,
dropped_direct_call_count: 0,
has_direct_call: false,
has_undropped_direct_call: false,
has_live_undropped_direct_call: false,
}
}
///|
fn dae_merge_direct_call_target_evidence(
dst : DaeDirectCallTargetEvidence,
src : DaeDirectCallTargetEvidence,
include_live : Bool,
) -> DaeDirectCallTargetEvidence {
{
direct_call_count: dst.direct_call_count + src.direct_call_count,
dropped_direct_call_count: dst.dropped_direct_call_count +
src.dropped_direct_call_count,
has_direct_call: dst.has_direct_call || src.has_direct_call,
has_undropped_direct_call: dst.has_undropped_direct_call ||
src.has_undropped_direct_call,
has_live_undropped_direct_call: dst.has_live_undropped_direct_call ||
(include_live && src.has_live_undropped_direct_call),
}
}
///|
fn dae_direct_call_target_evidence(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
start_idx~ : Int32 = 0,
) -> DaeDirectCallTargetEvidence {
let mut evidence = dae_empty_direct_call_target_evidence()
let mut live_prefix = true
let mut idx = start_idx
while idx < instrs.length() {
if live_prefix && dae_instr_is_unreachable_root(instrs[idx]) {
live_prefix = false
}
let instr_evidence : DaeDirectCallTargetEvidence = match instrs[idx] {
@lib.Call(func_idx) if dae_func_idx_slot(func_idx) == callee_abs => {
let dropped = idx + 1 < instrs.length() &&
instrs[idx + 1] == @lib.Instruction::drop()
{
direct_call_count: 1,
dropped_direct_call_count: if dropped {
1
} else {
0
},
has_direct_call: true,
has_undropped_direct_call: !dropped,
has_live_undropped_direct_call: live_prefix && !dropped,
}
}
@lib.ReturnCall(func_idx) if dae_func_idx_slot(func_idx) == callee_abs =>
{
direct_call_count: 0,
dropped_direct_call_count: 0,
has_direct_call: false,
has_undropped_direct_call: true,
has_live_undropped_direct_call: live_prefix,
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_direct_call_target_evidence(body, callee_abs)
@lib.If(_, then_body, Some(else_body)) =>
dae_merge_direct_call_target_evidence(
dae_direct_call_target_evidence(then_body, callee_abs),
dae_direct_call_target_evidence(else_body, callee_abs),
true,
)
@lib.If(_, then_body, None) =>
dae_direct_call_target_evidence(then_body, callee_abs)
_ => dae_empty_direct_call_target_evidence()
}
evidence = dae_merge_direct_call_target_evidence(
evidence, instr_evidence, live_prefix,
)
idx += 1
}
evidence
}
///|
fn dae_instrs_contain_direct_call_to(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
start_idx~ : Int32 = 0,
) -> Bool {
dae_direct_call_target_evidence(instrs, callee_abs, start_idx~).has_direct_call
}
///|
fn dae_repair_dead_suffix_calls_instr(
instr : @lib.Instruction,
callee_abs : Int32,
) -> (@lib.Instruction, Bool) {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten, changed) = dae_repair_dead_suffix_calls_instrs(
body, callee_abs,
)
(@lib.Instruction::block(block_type, @lib.Expr::new(rewritten)), changed)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten, changed) = dae_repair_dead_suffix_calls_instrs(
body, callee_abs,
)
(@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten)), changed)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten, changed) = dae_repair_dead_suffix_calls_instrs(
body, callee_abs,
)
(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten),
),
changed,
)
}
@lib.If(block_type, then_body, else_body) => {
let (then_rewritten, then_changed) = dae_repair_dead_suffix_calls_instrs(
then_body, callee_abs,
)
let mut changed = then_changed
let rewritten_else = match else_body {
Some(body) => {
let (else_rewritten, else_changed) = dae_repair_dead_suffix_calls_instrs(
body, callee_abs,
)
changed = changed || else_changed
Some(else_rewritten)
}
None => None
}
(
@lib.Instruction::if_(block_type, then_rewritten, rewritten_else),
changed,
)
}
_ => (instr, false)
}
}
///|
fn dae_repair_dead_suffix_calls_instrs(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let dead_suffix = dae_dead_suffix_evidence(instrs)
let mut idx = 0
while idx < instrs.length() {
match dead_suffix {
Some(evidence) if evidence.root_idx == idx &&
evidence.suffix_start == idx + 1 =>
if dae_instrs_contain_direct_call_to(
evidence.body,
callee_abs,
start_idx=evidence.suffix_start,
) {
out.push(@lib.Instruction::unreachable_())
return (out, true)
}
_ => ()
}
let (rewritten, nested_changed) = dae_repair_dead_suffix_calls_instr(
instrs[idx],
callee_abs,
)
changed = changed || nested_changed
out.push(rewritten)
idx += 1
}
(out, changed)
}
///|
fn dae_repair_dead_suffix_calls_func(
func : @lib.Func,
callee_abs : Int32,
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(body)) = func
let (rewritten, changed) = dae_repair_dead_suffix_calls_instrs(
body, callee_abs,
)
(@lib.Func::new(locals, @lib.Expr::new(rewritten)), changed)
}
///|
fn dae_repair_dead_suffix_calls_in_scope_except(
funcs : Array[@lib.Func],
callee_abs : Int32,
skip_def : Int32,
scope : DaeDroppedResultScope,
) -> Unit {
for func_idx in scope.caller_defs {
if func_idx != skip_def && func_idx >= 0 && func_idx < funcs.length() {
let (repaired, _) = dae_repair_dead_suffix_calls_func(
funcs[func_idx],
callee_abs,
)
funcs[func_idx] = repaired
}
}
}
///|
fn dae_repair_dead_suffix_calls_in_no_param_func_except(
funcs : Array[@lib.Func],
type_indices : Array[@lib.TypeIdx],
type_sec : @lib.TypeSec,
imported : Int32,
callee_abs : Int32,
skip_def : Int32,
func_idx : Int32,
) -> Bool {
if func_idx == skip_def || func_idx < 0 || func_idx >= funcs.length() {
return false
}
let abs_idx = imported + func_idx
let has_no_params = if abs_idx >= 0 && abs_idx < type_indices.length() {
match dae_type_sec_func_type(type_sec, type_indices[abs_idx]) {
Some(@lib.FuncType(params, _)) => params.length() == 0
None => false
}
} else {
false
}
if has_no_params {
let (repaired, repair_changed) = dae_repair_dead_suffix_calls_func(
funcs[func_idx],
callee_abs,
)
funcs[func_idx] = repaired
return repair_changed
}
false
}
///|
fn dae_repair_dead_suffix_calls_in_no_param_scope_except(
funcs : Array[@lib.Func],
type_indices : Array[@lib.TypeIdx],
type_sec : @lib.TypeSec,
imported : Int32,
callee_abs : Int32,
skip_def : Int32,
scope : DaeDroppedResultScope,
) -> Bool {
let mut changed = false
for func_idx in scope.caller_defs {
changed = dae_repair_dead_suffix_calls_in_no_param_func_except(
funcs, type_indices, type_sec, imported, callee_abs, skip_def, func_idx,
) ||
changed
}
changed
}
///|
fn dae_count_active_direct_calls_to(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
) -> Int32 {
let mut count = 0
let mut idx = 0
while idx < instrs.length() {
if dae_instr_is_unreachable_root(instrs[idx]) {
break
}
match instrs[idx] {
@lib.Call(func_idx) | @lib.ReturnCall(func_idx) =>
if dae_func_idx_slot(func_idx) == callee_abs {
count += 1
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
count += dae_count_active_direct_calls_to(body, callee_abs)
@lib.If(_, then_body, Some(else_body)) => {
count += dae_count_active_direct_calls_to(then_body, callee_abs)
count += dae_count_active_direct_calls_to(else_body, callee_abs)
}
@lib.If(_, then_body, None) =>
count += dae_count_active_direct_calls_to(then_body, callee_abs)
_ => ()
}
idx += 1
}
count
}
///|
fn dae_self_call_counts_from_function_info(
info : DaeFunctionInfo,
caller_def : Int32,
) -> (Int32, Int32) {
let mut active = 0
let mut anywhere = 0
for callsite in info.calls {
if callsite.caller_def == caller_def {
anywhere += 1
if callsite.active {
active += 1
}
}
}
(active, anywhere)
}
///|
fn dae_count_self_calls(func : @lib.Func, callee_abs : Int32) -> Int32 {
let @lib.Func(_, @lib.Expr(body)) = func
dae_count_active_direct_calls_to(body, callee_abs)
}
///|
fn dae_count_direct_call_anywhere_to(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
start_idx~ : Int32 = 0,
) -> Int32 {
dae_direct_call_target_evidence(instrs, callee_abs, start_idx~).direct_call_count
}
///|
fn dae_count_self_calls_anywhere(func : @lib.Func, callee_abs : Int32) -> Int32 {
let @lib.Func(_, @lib.Expr(body)) = func
dae_count_direct_call_anywhere_to(body, callee_abs)
}
///|
fn dae_count_module_calls_anywhere_to(
funcs : Array[@lib.Func],
callee_abs : Int32,
) -> Int32 {
let mut count = 0
for func in funcs {
let @lib.Func(_, @lib.Expr(body)) = func
count += dae_count_direct_call_anywhere_to(body, callee_abs)
}
count
}
///|
fn dae_has_undropped_direct_call_to(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
) -> Bool {
dae_direct_call_target_evidence(instrs, callee_abs).has_undropped_direct_call
}
///|
fn dae_control_result_undropped_direct_call_evidence_from_parts(
block_type : @lib.BlockType,
primary_body : Array[@lib.Instruction],
secondary_body : Array[@lib.Instruction]?,
callee_abs : Int32,
) -> DaeControlResultUndroppedDirectCallEvidence {
let arity = dae_instr_result_arity_from_block_type(block_type)
let result_arity_known = arity is Some(_)
let result_arity = match arity {
Some(count) => count
None => 0
}
let has_result = match arity {
Some(count) => count > 0
None => true
}
let primary_has_undropped_direct_call = dae_has_undropped_direct_call_to(
primary_body, callee_abs,
)
let secondary_has_undropped_direct_call = match secondary_body {
Some(body) => dae_has_undropped_direct_call_to(body, callee_abs)
None => false
}
let has_undropped_direct_call = primary_has_undropped_direct_call ||
secondary_has_undropped_direct_call
{
result_arity_known,
result_arity,
has_result,
primary_has_undropped_direct_call,
secondary_has_undropped_direct_call,
has_undropped_direct_call,
blocks_result_removal: has_result && has_undropped_direct_call,
}
}
///|
fn dae_control_result_undropped_direct_call_evidence(
instr : @lib.Instruction,
callee_abs : Int32,
) -> DaeControlResultUndroppedDirectCallEvidence? {
match instr {
@lib.Block(block_type, @lib.Expr(body))
| @lib.Loop(block_type, @lib.Expr(body))
| @lib.TryTable(block_type, _, @lib.Expr(body)) =>
Some(
dae_control_result_undropped_direct_call_evidence_from_parts(
block_type,
body,
None,
callee_abs,
),
)
@lib.If(block_type, then_body, else_body) =>
Some(
dae_control_result_undropped_direct_call_evidence_from_parts(
block_type, then_body, else_body, callee_abs,
),
)
_ => None
}
}
///|
fn dae_control_result_undropped_direct_call_evidence_is_consistent(
evidence : DaeControlResultUndroppedDirectCallEvidence,
) -> Bool {
evidence.result_arity >= 0 &&
evidence.has_result ==
(!evidence.result_arity_known || evidence.result_arity > 0) &&
evidence.has_undropped_direct_call ==
(
evidence.primary_has_undropped_direct_call ||
evidence.secondary_has_undropped_direct_call
) &&
evidence.blocks_result_removal ==
(evidence.has_result && evidence.has_undropped_direct_call)
}
///|
fn dae_suffix_has_top_level_dropped_direct_call_to(
suffix : Array[@lib.Instruction],
callee_abs : Int32,
start_idx~ : Int32 = 0,
) -> Bool {
let mut suffix_idx = start_idx
while suffix_idx + 1 < suffix.length() {
match (suffix[suffix_idx], suffix[suffix_idx + 1]) {
(@lib.Call(func_idx), @lib.Drop) if dae_func_idx_slot(func_idx) ==
callee_abs => return true
_ => ()
}
suffix_idx += 1
}
false
}
///|
fn dae_dead_suffix_direct_call_evidence(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
known_first_root~ : Int32 = -2,
) -> DaeDeadSuffixDirectCallEvidence? {
match dae_dead_suffix_evidence_from_first_root(instrs, known_first_root) {
Some(dead_suffix) => {
let target = dae_direct_call_target_evidence(
dead_suffix.body,
callee_abs,
start_idx=dead_suffix.suffix_start,
)
Some({
has_dropped_direct_call: dae_suffix_has_top_level_dropped_direct_call_to(
dead_suffix.body,
callee_abs,
start_idx=dead_suffix.suffix_start,
),
has_undropped_direct_call: target.has_undropped_direct_call,
})
}
None => None
}
}
///|
fn dae_dropped_result_callee_rewrite_evidence(
func : @lib.Func,
params : Array[@lib.ValType],
results : Array[@lib.ValType],
legality : DaeDroppedResultRemovalLegalityEvidence,
callee_abs : Int32,
) -> DaeDroppedResultCalleeRewriteEvidence {
let @lib.Func(_, @lib.Expr(body)) = func
let suffix = dae_dead_suffix_direct_call_evidence(body, callee_abs)
let has_dropped_dead_suffix_call = match suffix {
Some(evidence) => evidence.has_dropped_direct_call
None => false
}
let has_undropped_dead_suffix_call = match suffix {
Some(evidence) => evidence.has_undropped_direct_call
None => false
}
{
param_count: params.length(),
result_count: results.length(),
original_unreachable_only: dae_func_is_unreachable_only(func),
simplified_unreachable_only: dae_func_is_unreachable_only(
dae_simplify_to_unreachable_when_prefix_unobservable(func),
),
has_dropped_dead_suffix_call,
has_undropped_dead_suffix_call,
missing_dropped_dead_suffix_call: !has_dropped_dead_suffix_call,
missing_undropped_dead_suffix_call: !has_undropped_dead_suffix_call,
blocks_result_removal: dae_dropped_result_removal_legality_blocks(legality),
}
}
///|
fn dae_dropped_result_callee_rewrite_missing_dropped_suffix(
evidence : DaeDroppedResultCalleeRewriteEvidence,
) -> Bool {
!evidence.has_dropped_dead_suffix_call &&
evidence.missing_dropped_dead_suffix_call
}
///|
fn dae_dropped_result_callee_rewrite_missing_undropped_suffix(
evidence : DaeDroppedResultCalleeRewriteEvidence,
) -> Bool {
!evidence.has_undropped_dead_suffix_call &&
evidence.missing_undropped_dead_suffix_call
}
///|
fn dae_dead_suffix_has_other_call(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
known_first_root~ : Int32 = -2,
) -> Bool {
match dae_dead_suffix_evidence_from_first_root(instrs, known_first_root) {
Some(evidence) => {
let mut suffix_idx = evidence.suffix_start
while suffix_idx < evidence.body.length() {
match evidence.body[suffix_idx] {
@lib.Call(func_idx) if dae_func_idx_slot(func_idx) != callee_abs =>
return true
_ => ()
}
suffix_idx += 1
}
false
}
None => false
}
}
///|
fn dae_dead_suffix_has_dropped_direct_call_to(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
known_first_root~ : Int32 = -2,
) -> Bool {
match
dae_dead_suffix_direct_call_evidence(instrs, callee_abs, known_first_root~) {
Some(evidence) => evidence.has_dropped_direct_call
None => false
}
}
///|
fn dae_dead_suffix_has_undropped_direct_call_to(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
known_first_root~ : Int32 = -2,
) -> Bool {
match
dae_dead_suffix_direct_call_evidence(instrs, callee_abs, known_first_root~) {
Some(evidence) => evidence.has_undropped_direct_call
None => false
}
}
///|
fn dae_append_targets(dst : Array[Int32], src : Array[Int32]) -> Unit {
for target in src {
dst.push(target)
}
}
///|
fn dae_defined_direct_target(
func_idx : @lib.FuncIdx,
imported : Int32,
defined : Int32,
) -> Int32? {
let slot = dae_func_idx_slot(func_idx)
let def_idx = slot - imported
if slot >= imported && def_idx >= 0 && def_idx < defined {
Some(def_idx)
} else {
None
}
}
///|
fn dae_collect_dead_suffix_direct_target_lists(
instrs : Array[@lib.Instruction],
imported : Int32,
defined : Int32,
collect_top_level_dropped : Bool,
direct_targets : Array[Int32],
dropped_direct_targets : Array[Int32],
undropped_direct_targets : Array[Int32],
start_idx~ : Int32 = 0,
) -> Unit {
let mut idx = start_idx
while idx < instrs.length() {
match instrs[idx] {
@lib.Call(func_idx) =>
match dae_defined_direct_target(func_idx, imported, defined) {
Some(def_idx) => {
direct_targets.push(def_idx)
let dropped = idx + 1 < instrs.length() &&
instrs[idx + 1] == @lib.Instruction::drop()
if dropped {
if collect_top_level_dropped {
dropped_direct_targets.push(def_idx)
}
} else {
undropped_direct_targets.push(def_idx)
}
}
None => ()
}
@lib.ReturnCall(func_idx) =>
match dae_defined_direct_target(func_idx, imported, defined) {
Some(def_idx) => undropped_direct_targets.push(def_idx)
None => ()
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_collect_dead_suffix_direct_target_lists(
body, imported, defined, false, direct_targets, dropped_direct_targets,
undropped_direct_targets,
)
@lib.If(_, then_body, Some(else_body)) => {
dae_collect_dead_suffix_direct_target_lists(
then_body, imported, defined, false, direct_targets, dropped_direct_targets,
undropped_direct_targets,
)
dae_collect_dead_suffix_direct_target_lists(
else_body, imported, defined, false, direct_targets, dropped_direct_targets,
undropped_direct_targets,
)
}
@lib.If(_, then_body, None) =>
dae_collect_dead_suffix_direct_target_lists(
then_body, imported, defined, false, direct_targets, dropped_direct_targets,
undropped_direct_targets,
)
_ => ()
}
idx += 1
}
}
///|
fn dae_dead_suffix_direct_target_evidence(
instrs : Array[@lib.Instruction],
imported : Int32,
defined : Int32,
known_first_root~ : Int32 = -2,
) -> DaeDeadSuffixDirectTargetEvidence? {
match dae_dead_suffix_evidence_from_first_root(instrs, known_first_root) {
Some(evidence) => {
let direct_targets : Array[Int32] = []
let dropped_direct_targets : Array[Int32] = []
let undropped_direct_targets : Array[Int32] = []
dae_collect_dead_suffix_direct_target_lists(
evidence.body,
imported,
defined,
true,
direct_targets,
dropped_direct_targets,
undropped_direct_targets,
start_idx=evidence.suffix_start,
)
Some({ direct_targets, dropped_direct_targets, undropped_direct_targets, })
}
None => None
}
}
///|
fn dae_collect_dead_suffix_undropped_direct_targets(
instrs : Array[@lib.Instruction],
imported : Int32,
defined : Int32,
targets : Array[Int32],
known_first_root~ : Int32 = -2,
) -> Unit {
match
dae_dead_suffix_direct_target_evidence(
instrs,
imported,
defined,
known_first_root~,
) {
Some(evidence) =>
dae_append_targets(targets, evidence.undropped_direct_targets)
None => ()
}
}
///|
fn dae_collect_dead_suffix_dropped_direct_targets(
instrs : Array[@lib.Instruction],
imported : Int32,
defined : Int32,
targets : Array[Int32],
known_first_root~ : Int32 = -2,
) -> Unit {
match
dae_dead_suffix_direct_target_evidence(
instrs,
imported,
defined,
known_first_root~,
) {
Some(evidence) =>
dae_append_targets(targets, evidence.dropped_direct_targets)
None => ()
}
}
///|
fn dae_self_call_has_adjacent_param_forward(
instrs : Array[@lib.Instruction],
call_idx : Int32,
param_count : Int32,
) -> Bool {
dae_adjacent_param_forward_evidence(instrs, call_idx, param_count) is Some(_)
}
///|
fn dae_empty_param_origins(param_count : Int32) -> Array[Bool] {
Array::make(param_count, false)
}
///|
fn dae_single_param_origin(param_count : Int32, slot : Int32) -> Array[Bool] {
let origins = dae_empty_param_origins(param_count)
if slot >= 0 && slot < origins.length() {
origins[slot] = true
}
origins
}
///|
fn dae_union_param_origins(dst : Array[Bool], src : Array[Bool]) -> Unit {
let mut idx = 0
while idx < dst.length() && idx < src.length() {
dst[idx] = dst[idx] || src[idx]
idx += 1
}
}
///|
fn dae_param_origins_any(origins : Array[Bool]) -> Bool {
for origin in origins {
if origin {
return true
}
}
false
}
///|
fn dae_stack_operand_window_evidence_is_consistent(
evidence : DaeStackOperandWindowEvidence,
stack_length : Int32,
) -> Bool {
evidence.start >= 0 &&
evidence.end == stack_length &&
evidence.start <= evidence.end &&
evidence.available_operands == evidence.end - evidence.start &&
evidence.available_operands >= 0 &&
(
evidence.requested_operands >= evidence.available_operands ||
evidence.requested_operands < 0
)
}
///|
fn dae_stack_operand_window_evidence(
stack_length : Int32,
arg_count : Int32,
) -> DaeStackOperandWindowEvidence? {
if stack_length < 0 {
return None
}
let requested = if arg_count < 0 { 0 } else { arg_count }
let start = if stack_length > requested {
stack_length - requested
} else {
0
}
let evidence = {
start,
end: stack_length,
requested_operands: arg_count,
available_operands: stack_length - start,
}
if dae_stack_operand_window_evidence_is_consistent(evidence, stack_length) {
Some(evidence)
} else {
None
}
}
///|
fn dae_stack_bool_has_true_operand(
stack : Array[Bool],
arg_count : Int32,
) -> Bool {
let window = match
dae_stack_operand_window_evidence(stack.length(), arg_count) {
Some(window) => window
None => return false
}
let mut idx = window.start
while idx < window.end {
if idx >= 0 && idx < stack.length() && stack[idx] {
return true
}
idx += 1
}
false
}
///|
fn dae_stack_bool_apply_effect(
stack : Array[Bool],
inputs : Int32,
outputs : Int32,
pushed_value : Bool,
) -> Unit {
let mut pop = 0
while pop < inputs && stack.length() > 0 {
ignore(stack.pop())
pop += 1
}
let mut push = 0
while push < outputs {
stack.push(pushed_value)
push += 1
}
}
///|
fn dae_stack_effect_evidence_is_consistent(
evidence : DaeStackEffectEvidence,
) -> Bool {
evidence.inputs >= 0 && evidence.outputs >= 0
}
///|
fn dae_stack_effect_evidence(
instr : @lib.Instruction,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> DaeStackEffectEvidence? {
match dae_instr_stack_effect(instr, mod_, func_type_indices) {
Some((inputs, outputs)) => {
let evidence = { inputs, outputs, }
if dae_stack_effect_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
None => None
}
}
///|
fn dae_stack_single_value_producer_evidence(
instr : @lib.Instruction,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> DaeStackEffectEvidence? {
match dae_stack_effect_evidence(instr, mod_, func_type_indices) {
Some(evidence) if evidence.inputs == 0 && evidence.outputs == 1 =>
Some(evidence)
_ => None
}
}
///|
fn dae_stack_slice_simulation_evidence_is_consistent(
evidence : DaeStackSliceSimulationEvidence,
) -> Bool {
evidence.start >= 0 &&
evidence.end >= evidence.start &&
evidence.initial_stack_values >= 0 &&
evidence.final_stack_values >= 0 &&
evidence.max_stack_values >= evidence.initial_stack_values &&
evidence.max_stack_values >= evidence.final_stack_values &&
(!evidence.saw_effect_or_branch || evidence.end > evidence.start)
}
///|
fn dae_stack_slice_simulation_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
body : Array[@lib.Instruction],
start : Int32,
end : Int32,
initial_stack_values : Int32,
instr_allowed : (@lib.Instruction) -> Bool,
) -> DaeStackSliceSimulationEvidence? {
if start < 0 || end < start || end > body.length() || initial_stack_values < 0 {
return None
}
let mut stack_values = initial_stack_values
let mut max_stack_values = initial_stack_values
let mut saw_effect_or_branch = false
let mut idx = start
while idx < end {
let instr = body[idx]
if !instr_allowed(instr) {
return None
}
if dae_instr_has_observable_effect_or_branch(instr) {
saw_effect_or_branch = true
}
let effect = match
dae_stack_effect_evidence(instr, mod_, func_type_indices) {
Some(effect) => effect
None => return None
}
if stack_values < effect.inputs {
return None
}
stack_values = stack_values - effect.inputs + effect.outputs
if stack_values > max_stack_values {
max_stack_values = stack_values
}
idx += 1
}
let evidence = {
start,
end,
initial_stack_values,
final_stack_values: stack_values,
max_stack_values,
saw_effect_or_branch,
}
if dae_stack_slice_simulation_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
///|
fn dae_stack_slice_transition_evidence_is_consistent(
evidence : DaeStackSliceTransitionEvidence,
) -> Bool {
evidence.expected_final_stack_values >= 0 &&
dae_stack_slice_simulation_evidence_is_consistent(evidence.simulation) &&
evidence.simulation.final_stack_values == evidence.expected_final_stack_values
}
///|
fn dae_stack_slice_transition_evidence(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
body : Array[@lib.Instruction],
start : Int32,
end : Int32,
initial_stack_values : Int32,
expected_final_stack_values : Int32,
instr_allowed : (@lib.Instruction) -> Bool,
) -> DaeStackSliceTransitionEvidence? {
if expected_final_stack_values < 0 {
return None
}
match
dae_stack_slice_simulation_evidence(
mod_, func_type_indices, body, start, end, initial_stack_values, instr_allowed,
) {
Some(simulation) => {
let evidence = { simulation, expected_final_stack_values, }
if dae_stack_slice_transition_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
None => None
}
}
///|
fn dae_stack_bool_apply_instr_effect(
stack : Array[Bool],
instr : @lib.Instruction,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
pushed_value : Bool,
) -> Bool {
match dae_stack_effect_evidence(instr, mod_, func_type_indices) {
Some(evidence) => {
dae_stack_bool_apply_effect(
stack,
evidence.inputs,
evidence.outputs,
pushed_value,
)
true
}
None => false
}
}
///|
fn dae_stack_int_has_operand_value(
stack : Array[Int32],
arg_count : Int32,
value : Int32,
) -> Bool {
let window = match
dae_stack_operand_window_evidence(stack.length(), arg_count) {
Some(window) => window
None => return false
}
let mut idx = window.start
while idx < window.end {
if idx >= 0 && idx < stack.length() && stack[idx] == value {
return true
}
idx += 1
}
false
}
///|
fn dae_stack_int_has_known_operand(
stack : Array[Int32],
arg_count : Int32,
) -> Bool {
let window = match
dae_stack_operand_window_evidence(stack.length(), arg_count) {
Some(window) => window
None => return false
}
let mut idx = window.start
while idx < window.end {
if idx >= 0 && idx < stack.length() && stack[idx] >= 0 {
return true
}
idx += 1
}
false
}
///|
fn dae_stack_int_apply_effect(
stack : Array[Int32],
inputs : Int32,
outputs : Int32,
pushed_value : Int32,
) -> Unit {
let mut pop = 0
while pop < inputs && stack.length() > 0 {
ignore(stack.pop())
pop += 1
}
let mut push = 0
while push < outputs {
stack.push(pushed_value)
push += 1
}
}
///|
fn dae_stack_int_apply_instr_effect(
stack : Array[Int32],
instr : @lib.Instruction,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
pushed_value : Int32,
) -> Bool {
match dae_stack_effect_evidence(instr, mod_, func_type_indices) {
Some(evidence) => {
dae_stack_int_apply_effect(
stack,
evidence.inputs,
evidence.outputs,
pushed_value,
)
true
}
None => false
}
}
///|
fn dae_collect_self_call_operand_param_gets(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
param_count : Int32,
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
escaped : Array[Bool],
preserve : Array[Bool],
preserve_direct_fallback : Bool,
start_idx~ : Int32 = 0,
) -> Unit {
let stack : Array[Int32] = []
let stack_from_call : Array[Bool] = []
let stack_origins : Array[Array[Bool]] = []
let mut idx = start_idx
while idx < instrs.length() {
match instrs[idx] {
@lib.LocalGet(local_idx) => {
let slot = dae_local_idx_slot(local_idx)
if slot >= 0 && slot < param_count {
stack.push(slot)
stack_origins.push(dae_single_param_origin(param_count, slot))
} else {
stack.push(-1)
stack_origins.push(dae_empty_param_origins(param_count))
}
stack_from_call.push(false)
}
@lib.Call(func_idx) if dae_func_idx_slot(func_idx) == callee_abs => {
let mut has_call_operand = false
let call_operand_origins = dae_empty_param_origins(param_count)
let direct_operand_origins = dae_empty_param_origins(param_count)
let mut p = 0
while p < param_count {
let stack_idx = stack.length() - param_count + p
if stack_idx >= 0 && stack_idx < stack_from_call.length() {
if stack_from_call[stack_idx] {
has_call_operand = true
dae_union_param_origins(
call_operand_origins,
stack_origins[stack_idx],
)
} else {
dae_union_param_origins(
direct_operand_origins,
stack_origins[stack_idx],
)
}
}
p += 1
}
// Binaryen preserves params stranded under a side-call result that is
// later used by a dead-suffix self call, but it still prunes params that
// are also supplied as direct local operands to that same self call.
if has_call_operand &&
!dae_self_call_has_adjacent_param_forward(instrs, idx, param_count) {
let call_origins_seen = dae_param_origins_any(call_operand_origins)
let self_call_is_dropped = idx + 1 < instrs.length() &&
instrs[idx + 1] == @lib.Instruction::drop()
p = 0
while p < param_count && p < preserve.length() {
if call_origins_seen {
if !self_call_is_dropped &&
call_operand_origins[p] &&
!direct_operand_origins[p] {
preserve[p] = true
}
} else if preserve_direct_fallback && direct_operand_origins[p] {
preserve[p] = true
}
p += 1
}
}
let mut pop = 0
while pop < param_count && stack.length() > 0 {
ignore(stack.pop())
ignore(stack_from_call.pop())
ignore(stack_origins.pop())
pop += 1
}
match dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(_, results)) => {
let mut r = 0
while r < results.length() {
stack.push(-1)
stack_from_call.push(true)
stack_origins.push(dae_empty_param_origins(param_count))
r += 1
}
}
None => ()
}
}
@lib.Call(func_idx) =>
match dae_stack_effect_evidence(instrs[idx], mod_, type_indices) {
Some(effect) => {
let inputs = effect.inputs
let outputs = effect.outputs
let popped_origins = dae_empty_param_origins(param_count)
let mut pop = 0
while pop < inputs && stack.length() > 0 {
ignore(stack.pop())
ignore(stack_from_call.pop())
match stack_origins.pop() {
Some(origins) =>
dae_union_param_origins(popped_origins, origins)
None => ()
}
pop += 1
}
let slot = dae_func_idx_slot(func_idx)
let output_origins = if slot >= 0 &&
slot < escaped.length() &&
escaped[slot] {
popped_origins
} else {
dae_empty_param_origins(param_count)
}
let mut push = 0
while push < outputs {
stack.push(-1)
stack_from_call.push(true)
stack_origins.push(output_origins.copy())
push += 1
}
}
None => ()
}
_ =>
match dae_stack_effect_evidence(instrs[idx], mod_, type_indices) {
Some(effect) => {
let inputs = effect.inputs
let outputs = effect.outputs
let mut pop = 0
while pop < inputs && stack.length() > 0 {
ignore(stack.pop())
ignore(stack_from_call.pop())
ignore(stack_origins.pop())
pop += 1
}
let mut push = 0
while push < outputs {
stack.push(-1)
stack_from_call.push(false)
stack_origins.push(dae_empty_param_origins(param_count))
push += 1
}
}
None => ()
}
}
idx += 1
}
}
///|
fn dae_collect_dead_suffix_self_operand_params(
func : @lib.Func,
callee_abs : Int32,
param_count : Int32,
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
escaped : Array[Bool],
preserve_direct_fallback : Bool,
) -> Array[Bool] {
let preserve = Array::make(param_count, false)
let @lib.Func(_, @lib.Expr(body)) = func
match dae_dead_suffix_evidence(body) {
Some(evidence) =>
dae_collect_self_call_operand_param_gets(
evidence.body,
callee_abs,
param_count,
mod_,
type_indices,
escaped,
preserve,
preserve_direct_fallback,
start_idx=evidence.suffix_start,
)
None => ()
}
preserve
}
///|
fn dae_filter_params(
params : Array[@lib.ValType],
keep : Array[Bool],
) -> Array[@lib.ValType] {
let out : Array[@lib.ValType] = []
let mut idx = 0
while idx < params.length() {
if idx < keep.length() && keep[idx] {
out.push(params[idx])
}
idx += 1
}
out
}
///|
fn dae_try_remove_dead_suffix_only_unreachable_params(
mod_ : @lib.Module,
callee_abs : Int32,
original_call_count : Int32,
allow_unreachable_only_cleanup : Bool,
current_calls_anywhere~ : Int32 = -1,
) -> DaePassResult {
let imported = dae_imported_func_count(mod_)
let def_idx = callee_abs - imported
let type_indices = dae_collect_func_type_indices(mod_)
let func_type = match dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(ft) => ft
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let @lib.FuncType(params, results) = func_type
if params.length() == 0 {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let source_funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if def_idx < 0 || def_idx >= source_funcs.length() {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let observed_current_calls_anywhere = if current_calls_anywhere >= 0 {
current_calls_anywhere
} else {
dae_count_module_calls_anywhere_to(source_funcs, callee_abs)
}
if observed_current_calls_anywhere == 0 && original_call_count == 0 {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if observed_current_calls_anywhere == 0 &&
dae_func_is_unreachable_only(source_funcs[def_idx]) &&
!allow_unreachable_only_cleanup {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let simplified = dae_simplify_to_unreachable_when_prefix_unobservable(
source_funcs[def_idx],
)
if !dae_func_is_unreachable_only(simplified) {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let signature = match
dae_module_signature_context(mod_, def_idx, copy_funcs=true) {
Some(signature) => signature
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let funcs = signature.funcs.copy()
let type_sec = signature.type_sec
let full_scope = dae_dropped_result_scope_from_optional(funcs.length(), None)
let repair_plan = dae_dropped_result_repair_plan(
full_scope,
callee_abs,
def_idx,
DaeRepairDeadSuffixAllCallers,
remove_dropped_call_drops=false,
remove_dropped_call_drops_before_repair=false,
)
dae_apply_dropped_result_repair_plan(
funcs, type_indices, type_sec, imported, repair_plan,
)
let escaped = dae_collect_escapes(mod_, type_indices.length())
let keep_params = dae_collect_dead_suffix_self_operand_params(
funcs[def_idx],
callee_abs,
params.length(),
mod_,
type_indices,
escaped,
true,
)
let next_params = dae_filter_params(params, keep_params)
funcs[def_idx] = @lib.Func::new(
@lib.Locals::empty(),
@lib.Expr::new([@lib.Instruction::unreachable_()]),
)
let plan = dae_module_code_finalization_plan(
next_params,
results,
DaeTouchModuleCodeChangedFuncs,
)
dae_apply_module_boundary_finalization(mod_, signature, funcs, plan)
}
///|
fn dae_try_remove_unobserved_unreachable_function_with_facts(
mod_ : @lib.Module,
facts : DaeSpecializedResultFacts,
) -> DaePassResult {
if dae_specialized_result_facts_block_change(facts) {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let context = facts.context
let def_idx = context.signature.def_idx
let funcs = context.signature.funcs
let rewrite_evidence = dae_dropped_result_callee_rewrite_evidence(
funcs[def_idx],
context.params,
context.results,
context.result_removal_legality,
facts.target_abs,
)
if rewrite_evidence.param_count == 0 && rewrite_evidence.result_count == 0 {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let module_calls_anywhere = dae_count_module_calls_anywhere_to(
funcs,
facts.target_abs,
)
if module_calls_anywhere == 0 {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if rewrite_evidence.blocks_result_removal {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if !rewrite_evidence.simplified_unreachable_only ||
(
rewrite_evidence.result_count > 0 &&
dae_dropped_result_callee_rewrite_missing_undropped_suffix(
rewrite_evidence,
)
) {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let plan = dae_dropped_result_unreachable_callee_rewrite_plan(
context,
DaeRepairDeadSuffixAllCallers,
[],
)
dae_apply_dropped_result_unreachable_callee_rewrite(mod_, context, plan)
}
///|
#warnings("-unused_value")
fn dae_try_remove_unobserved_unreachable_function(
mod_ : @lib.Module,
callee_abs : Int32,
) -> DaePassResult {
let original = dae_collect_original_boundary_snapshot(mod_)
let graph = dae_current_boundary_graph_new(mod_)
match dae_specialized_result_facts_new(original, graph, callee_abs) {
Some(facts) =>
dae_try_remove_unobserved_unreachable_function_with_facts(mod_, facts)
None => { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
}
///|
fn dae_try_remove_dead_self_suffix_result_with_facts(
mod_ : @lib.Module,
facts : DaeSpecializedResultFacts,
) -> DaePassResult {
if dae_specialized_result_facts_block_change(facts) {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let context = facts.context
let def_idx = context.signature.def_idx
let funcs = context.signature.funcs
let rewrite_evidence = dae_dropped_result_callee_rewrite_evidence(
funcs[def_idx],
context.params,
context.results,
context.result_removal_legality,
facts.target_abs,
)
if rewrite_evidence.param_count != 0 || rewrite_evidence.result_count != 1 {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if rewrite_evidence.original_unreachable_only ||
dae_dropped_result_callee_rewrite_missing_dropped_suffix(rewrite_evidence) {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if rewrite_evidence.blocks_result_removal {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let plan = dae_dropped_result_unreachable_callee_rewrite_plan(
context,
DaeRepairDeadSuffixNoParamCallers,
[],
)
dae_apply_dropped_result_unreachable_callee_rewrite(mod_, context, plan)
}
///|
#warnings("-unused_value")
fn dae_try_remove_dead_self_suffix_result(
mod_ : @lib.Module,
callee_abs : Int32,
) -> DaePassResult {
let original = dae_collect_original_boundary_snapshot(mod_)
let graph = dae_current_boundary_graph_new(mod_)
match dae_specialized_result_facts_new(original, graph, callee_abs) {
Some(facts) =>
dae_try_remove_dead_self_suffix_result_with_facts(mod_, facts)
None => { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
}
///|
fn dae_try_remove_uncalled_results_with_facts(
mod_ : @lib.Module,
facts : DaeSpecializedResultFacts,
) -> DaePassResult {
if dae_specialized_result_facts_block_change(facts) {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let context = facts.context
let def_idx = context.signature.def_idx
let funcs = context.signature.funcs
let rewrite_evidence = dae_dropped_result_callee_rewrite_evidence(
funcs[def_idx],
context.params,
context.results,
context.result_removal_legality,
facts.target_abs,
)
if rewrite_evidence.param_count != 0 || rewrite_evidence.result_count == 0 {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if rewrite_evidence.blocks_result_removal {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if !rewrite_evidence.simplified_unreachable_only {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let plan = dae_dropped_result_unreachable_callee_rewrite_plan(
context,
DaeRepairDeadSuffixNoParamCallers,
context.params,
)
dae_apply_dropped_result_unreachable_callee_rewrite(mod_, context, plan)
}
///|
#warnings("-unused_value")
fn dae_try_remove_uncalled_results(
mod_ : @lib.Module,
callee_abs : Int32,
) -> DaePassResult {
let original = dae_collect_original_boundary_snapshot(mod_)
let graph = dae_current_boundary_graph_new(mod_)
match dae_specialized_result_facts_new(original, graph, callee_abs) {
Some(facts) => dae_try_remove_uncalled_results_with_facts(mod_, facts)
None => { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
}
///|
fn dae_scan_original_call_summary_instrs(
instrs : Array[@lib.Instruction],
imported : Int32,
defined : Int32,
targets : Array[Int32],
undropped_targets : Array[Int32],
) -> Unit {
let mut idx = 0
while idx < instrs.length() {
match instrs[idx] {
@lib.Call(func_idx) => {
let slot = dae_func_idx_slot(func_idx)
let def_idx = slot - imported
if slot >= imported && def_idx >= 0 && def_idx < defined {
targets.push(def_idx)
if idx + 1 >= instrs.length() ||
instrs[idx + 1] != @lib.Instruction::drop() {
undropped_targets.push(def_idx)
}
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_scan_original_call_summary_instrs(
body, imported, defined, targets, undropped_targets,
)
@lib.If(_, then_body, Some(else_body)) => {
dae_scan_original_call_summary_instrs(
then_body, imported, defined, targets, undropped_targets,
)
dae_scan_original_call_summary_instrs(
else_body, imported, defined, targets, undropped_targets,
)
}
@lib.If(_, then_body, None) =>
dae_scan_original_call_summary_instrs(
then_body, imported, defined, targets, undropped_targets,
)
_ => ()
}
idx += 1
}
}
///|
fn dae_collect_module_call_counts_anywhere(
funcs : Array[@lib.Func],
imported : Int32,
defined : Int32,
) -> Array[Int32] {
let counts = Array::make(defined, 0)
for func in funcs {
let targets : Array[Int32] = []
let undropped_targets : Array[Int32] = []
let @lib.Func(_, @lib.Expr(body)) = func
dae_scan_original_call_summary_instrs(
body, imported, defined, targets, undropped_targets,
)
for target in targets {
if target >= 0 && target < defined {
counts[target] = counts[target] + 1
}
}
}
counts
}
///|
fn dae_collect_original_call_summary_with_context(
mod_ : @lib.Module,
imported : Int32,
defined : Int32,
type_indices : Array[@lib.TypeIdx],
escaped : Array[Bool],
) -> DaeOriginalCallSummary {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => {
let empty = Array::make(defined, 0)
return {
call_counts: empty.copy(),
prior_call_counts: empty.copy(),
nonself_undropped_call_counts: empty.copy(),
active_nonself_call_counts: empty.copy(),
active_parameterized_nonself_call_counts: empty.copy(),
active_parameterized_result_nonself_call_counts: empty.copy(),
prior_undropped_call_counts: empty.copy(),
self_call_counts: empty.copy(),
}
}
}
let call_targets_by_caller : Array[Array[Int32]] = []
let call_counts = Array::make(defined, 0)
let prior_call_counts = Array::make(defined, 0)
let nonself_undropped_call_counts = Array::make(defined, 0)
let prior_undropped_call_counts = Array::make(defined, 0)
let self_call_counts = Array::make(defined, 0)
let seen_undropped_stamp = Array::make(defined, -1)
let mut caller_def = 0
while caller_def < funcs.length() && caller_def < defined {
let targets : Array[Int32] = []
let undropped_targets : Array[Int32] = []
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
dae_scan_original_call_summary_instrs(
body, imported, defined, targets, undropped_targets,
)
call_targets_by_caller.push(targets)
for target in targets {
call_counts[target] = call_counts[target] + 1
if target == caller_def {
self_call_counts[target] = self_call_counts[target] + 1
}
if target > caller_def {
prior_call_counts[target] = prior_call_counts[target] + 1
}
}
for target in undropped_targets {
if seen_undropped_stamp[target] != caller_def {
seen_undropped_stamp[target] = caller_def
if target != caller_def {
nonself_undropped_call_counts[target] = nonself_undropped_call_counts[target] +
1
}
if target > caller_def {
prior_undropped_call_counts[target] = prior_undropped_call_counts[target] +
1
}
}
}
caller_def += 1
}
while call_targets_by_caller.length() < defined {
call_targets_by_caller.push([])
}
let active_nonself_call_counts = Array::make(defined, 0)
let active_parameterized_nonself_call_counts = Array::make(defined, 0)
let active_parameterized_result_nonself_call_counts = Array::make(defined, 0)
caller_def = 0
while caller_def < defined {
let caller_abs = imported + caller_def
let caller_is_active = (
caller_abs < escaped.length() && escaped[caller_abs]
) ||
call_counts[caller_def] > 0
let (caller_has_params, caller_has_params_and_result) = match
dae_resolve_func_type(mod_, type_indices, caller_abs) {
Some(@lib.FuncType(params, results)) =>
(params.length() > 0, params.length() > 0 && results.length() > 0)
None => (false, false)
}
if caller_is_active {
for target in call_targets_by_caller[caller_def] {
if target != caller_def {
active_nonself_call_counts[target] = active_nonself_call_counts[target] +
1
if caller_has_params {
active_parameterized_nonself_call_counts[target] = active_parameterized_nonself_call_counts[target] +
1
}
if caller_has_params_and_result {
active_parameterized_result_nonself_call_counts[target] = active_parameterized_result_nonself_call_counts[target] +
1
}
}
}
}
caller_def += 1
}
{
call_counts,
prior_call_counts,
nonself_undropped_call_counts,
active_nonself_call_counts,
active_parameterized_nonself_call_counts,
active_parameterized_result_nonself_call_counts,
prior_undropped_call_counts,
self_call_counts,
}
}
///|
fn dae_collect_original_param_counts_with_type_indices(
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
) -> Array[Int32] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let counts : Array[Int32] = []
let mut def_idx = 0
while def_idx < defined {
match dae_resolve_func_type(mod_, type_indices, imported + def_idx) {
Some(@lib.FuncType(params, _)) => counts.push(params.length())
None => counts.push(0)
}
def_idx += 1
}
counts
}
///|
fn dae_collect_original_boundary_snapshot(
mod_ : @lib.Module,
) -> DaeOriginalBoundarySnapshot {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let total = imported + defined
let type_indices = dae_collect_func_type_indices(mod_)
let exposure = dae_collect_original_exposure(mod_, total, type_indices)
let escaped = dae_exposure_to_escaped(exposure)
let param_counts = dae_collect_original_param_counts_with_type_indices(
mod_, type_indices,
)
let call_summary = dae_collect_original_call_summary_with_context(
mod_, imported, defined, type_indices, escaped,
)
// This temporary belongs only to the borrowed bodies of this invocation.
// Discover roots lazily so the existing predicate and failure order stays.
let first_roots : Array[Int32]? = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) if funcs.length() == defined =>
Some(Array::make(defined, -2))
_ => None
}
let private_result_dead_suffix_call_counts = dae_collect_original_private_result_dead_suffix_call_counts_with_escaped(
mod_,
call_summary.call_counts,
escaped,
first_roots~,
)
let private_result_same_operand_call_counts = dae_collect_original_private_result_same_operand_call_counts_with_escaped(
mod_, escaped,
)
let dead_suffix_escaped_result_call_facts = dae_collect_original_dead_suffix_escaped_result_call_facts_with_escaped(
mod_,
escaped,
first_roots~,
)
let escaped_result_operand_call_facts = dae_collect_original_escaped_result_operand_call_facts_with_escaped(
mod_, escaped, dead_suffix_escaped_result_call_facts,
)
let private_result_dead_suffix_calls_from_escaped_operand_callees = dae_collect_original_private_result_dead_suffix_calls_from_escaped_operand_callees(
mod_,
escaped_result_operand_call_facts.dead_suffix_escaped_result_operand_call_counts,
first_roots~,
)
let escaped_dead_suffix_call_counts = dae_collect_original_escaped_dead_suffix_call_counts_with_escaped(
mod_,
escaped,
first_roots~,
)
let escaped_undropped_dead_suffix_call_counts = dae_collect_original_escaped_undropped_dead_suffix_call_counts_with_escaped(
mod_,
escaped,
first_roots~,
)
let dead_suffix_other_calls = dae_collect_original_dead_suffix_other_calls(
mod_,
first_roots~,
)
let dead_suffix_undropped_self_calls = dae_collect_original_dead_suffix_undropped_self_calls(
mod_,
first_roots~,
)
let dead_suffix_dropped_self_calls = dae_collect_original_dead_suffix_dropped_self_calls(
mod_,
first_roots~,
)
let dead_suffix_block_undropped_self_calls = dae_collect_original_dead_suffix_block_undropped_self_calls(
mod_,
)
let dead_suffix_dropped_self_call_full_param_forwards = dae_collect_original_dead_suffix_dropped_self_call_full_param_forwards(
mod_,
)
let dead_suffix_result_calls_from_undropped_self_callers = dae_collect_original_dead_suffix_result_calls_from_undropped_self_callers_with_escaped(
mod_,
call_summary.nonself_undropped_call_counts,
escaped,
first_roots~,
)
let dead_suffix_result_calls_from_no_param_undropped_self_callers = dae_collect_original_dead_suffix_result_calls_from_no_param_undropped_self_callers(
mod_,
first_roots~,
)
let dead_suffix_undropped_calls_from_parameterized_dropped_self_callers = dae_collect_original_dead_suffix_undropped_calls_from_parameterized_dropped_self_callers(
mod_,
first_roots~,
)
let dead_suffix_dropped_calls_from_parameterized_self_callers = dae_collect_original_dead_suffix_dropped_calls_from_parameterized_self_callers(
mod_,
first_roots~,
)
let dead_suffix_dropped_calls_from_no_param_result_callers = dae_collect_original_dead_suffix_dropped_calls_from_no_param_result_callers(
mod_,
first_roots~,
)
let dead_suffix_mixed_dropped_undropped_call_counts = dae_collect_original_dead_suffix_mixed_dropped_undropped_call_counts_with_escaped(
mod_,
escaped,
first_roots~,
)
let dead_suffix_dropped_earlier_result_calls = dae_collect_original_dead_suffix_dropped_earlier_result_calls(
mod_,
first_roots~,
)
let later_dead_suffix_dropped_call_counts = dae_collect_original_later_dead_suffix_dropped_call_counts(
mod_,
first_roots~,
)
let dead_suffix_same_self_result_operand_calls = dae_collect_original_dead_suffix_same_self_result_operand_calls(
mod_,
)
let dead_suffix_undropped_self_call_param_operands = dae_collect_original_dead_suffix_undropped_self_call_param_operands(
mod_,
first_roots~,
)
let dead_suffix_result_other_calls = dae_collect_original_dead_suffix_result_other_calls_with_escaped(
mod_,
escaped,
first_roots~,
)
let dead_suffix_self_recursive_void_side_calls = dae_collect_original_dead_suffix_self_recursive_void_side_calls(
mod_,
first_roots~,
original_self_call_counts=Some(call_summary.self_call_counts),
)
let dead_suffix_self_recursive_result_side_calls = dae_collect_original_dead_suffix_self_recursive_result_side_calls(
mod_,
first_roots~,
)
let dead_suffix_mutual_calls = dae_collect_original_dead_suffix_mutual_calls(
mod_,
first_roots~,
)
let dead_suffix_calls_from_param_operand_self_result_callers = dae_collect_original_dead_suffix_calls_from_param_operand_self_result_callers(
mod_,
param_counts,
call_summary.call_counts,
call_summary.active_nonself_call_counts,
call_summary.active_parameterized_nonself_call_counts,
call_summary.nonself_undropped_call_counts,
call_summary.self_call_counts,
escaped_dead_suffix_call_counts,
dead_suffix_undropped_self_calls,
dead_suffix_undropped_self_call_param_operands,
dead_suffix_result_other_calls,
dead_suffix_mutual_calls,
first_roots~,
)
{
imported,
defined,
total,
type_indices,
escaped,
exposure,
param_counts,
large_unreachable_prefixes: dae_collect_original_large_unreachable_prefixes(
mod_,
),
call_summary,
private_result_dead_suffix_call_counts,
private_result_same_operand_call_counts,
escaped_result_operand_call_facts,
private_result_dead_suffix_calls_from_escaped_operand_callees,
escaped_dead_suffix_call_counts,
escaped_undropped_dead_suffix_call_counts,
dead_suffix_other_calls,
dead_suffix_undropped_self_calls,
dead_suffix_dropped_self_calls,
dead_suffix_block_undropped_self_calls,
dead_suffix_dropped_self_call_full_param_forwards,
dead_suffix_result_calls_from_undropped_self_callers,
dead_suffix_result_calls_from_no_param_undropped_self_callers,
dead_suffix_undropped_calls_from_parameterized_dropped_self_callers,
dead_suffix_dropped_calls_from_parameterized_self_callers,
dead_suffix_dropped_calls_from_no_param_result_callers,
dead_suffix_mixed_dropped_undropped_call_counts,
dead_suffix_dropped_earlier_result_calls,
later_dead_suffix_dropped_call_counts,
dead_suffix_same_self_result_operand_calls,
dead_suffix_undropped_self_call_param_operands,
dead_suffix_calls_from_param_operand_self_result_callers,
dead_suffix_result_other_calls,
dead_suffix_self_recursive_void_side_calls,
dead_suffix_self_recursive_result_side_calls,
dead_suffix_escaped_result_call_facts,
dead_suffix_mutual_calls,
}
}
///|
fn dae_collect_original_self_call_counts(mod_ : @lib.Module) -> Array[Int32] {
let imported = dae_imported_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
let counts : Array[Int32] = []
let mut def_idx = 0
while def_idx < funcs.length() {
counts.push(
dae_count_self_calls_anywhere(funcs[def_idx], imported + def_idx),
)
def_idx += 1
}
counts
}
///|
fn dae_collect_direct_call_target_anywhere_instr(
instr : @lib.Instruction,
imported : Int32,
defined : Int32,
targets : Array[Int32],
) -> Unit {
match instr {
@lib.Call(func_idx) => {
let slot = dae_func_idx_slot(func_idx)
let def_idx = slot - imported
if slot >= imported && def_idx >= 0 && def_idx < defined {
targets.push(def_idx)
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_collect_direct_call_targets_anywhere(body, imported, defined, targets)
@lib.If(_, then_body, Some(else_body)) => {
dae_collect_direct_call_targets_anywhere(
then_body, imported, defined, targets,
)
dae_collect_direct_call_targets_anywhere(
else_body, imported, defined, targets,
)
}
@lib.If(_, then_body, None) =>
dae_collect_direct_call_targets_anywhere(
then_body, imported, defined, targets,
)
_ => ()
}
}
///|
fn dae_collect_direct_call_targets_anywhere(
instrs : Array[@lib.Instruction],
imported : Int32,
defined : Int32,
targets : Array[Int32],
) -> Unit {
for instr in instrs {
dae_collect_direct_call_target_anywhere_instr(
instr, imported, defined, targets,
)
}
}
///|
fn dae_collect_dead_suffix_direct_targets(
instrs : Array[@lib.Instruction],
imported : Int32,
defined : Int32,
targets : Array[Int32],
known_first_root~ : Int32 = -2,
) -> Unit {
match
dae_dead_suffix_direct_target_evidence(
instrs,
imported,
defined,
known_first_root~,
) {
Some(evidence) => dae_append_targets(targets, evidence.direct_targets)
None => ()
}
}
///|
fn dae_count_dead_suffix_direct_call_to(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
known_first_root~ : Int32 = -2,
) -> Int32 {
match dae_dead_suffix_evidence_from_first_root(instrs, known_first_root) {
Some(evidence) =>
dae_count_direct_call_anywhere_to(
evidence.body,
callee_abs,
start_idx=evidence.suffix_start,
)
None => 0
}
}
///|
fn dae_empty_escaped_result_direct_call_evidence() -> DaeEscapedResultDirectCallEvidence {
{
has_dropped_escaped_result_call: false,
has_undropped_escaped_result_call: false,
}
}
///|
fn dae_merge_escaped_result_direct_call_evidence(
left : DaeEscapedResultDirectCallEvidence,
right : DaeEscapedResultDirectCallEvidence,
) -> DaeEscapedResultDirectCallEvidence {
{
has_dropped_escaped_result_call: left.has_dropped_escaped_result_call ||
right.has_dropped_escaped_result_call,
has_undropped_escaped_result_call: left.has_undropped_escaped_result_call ||
right.has_undropped_escaped_result_call,
}
}
///|
fn dae_call_escaped_result_direct_call_evidence(
func_idx : @lib.FuncIdx,
dropped : Bool,
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
escaped : Array[Bool],
) -> DaeEscapedResultDirectCallEvidence {
let slot = dae_func_idx_slot(func_idx)
if slot >= 0 && slot < escaped.length() && escaped[slot] {
match dae_resolve_func_type(mod_, type_indices, slot) {
Some(@lib.FuncType(_, results)) if results.length() > 0 =>
return {
has_dropped_escaped_result_call: dropped,
has_undropped_escaped_result_call: !dropped,
}
_ => ()
}
}
dae_empty_escaped_result_direct_call_evidence()
}
///|
fn dae_escaped_result_direct_call_evidence(
instrs : Array[@lib.Instruction],
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
escaped : Array[Bool],
start_idx~ : Int32 = 0,
) -> DaeEscapedResultDirectCallEvidence {
let mut evidence = dae_empty_escaped_result_direct_call_evidence()
let mut idx = start_idx
while idx < instrs.length() {
let instr_evidence = match instrs[idx] {
@lib.Call(func_idx) =>
dae_call_escaped_result_direct_call_evidence(
func_idx,
idx + 1 < instrs.length() &&
instrs[idx + 1] == @lib.Instruction::drop(),
mod_,
type_indices,
escaped,
)
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_escaped_result_direct_call_evidence(
body, mod_, type_indices, escaped,
)
@lib.If(_, then_body, Some(else_body)) =>
dae_merge_escaped_result_direct_call_evidence(
dae_escaped_result_direct_call_evidence(
then_body, mod_, type_indices, escaped,
),
dae_escaped_result_direct_call_evidence(
else_body, mod_, type_indices, escaped,
),
)
@lib.If(_, then_body, None) =>
dae_escaped_result_direct_call_evidence(
then_body, mod_, type_indices, escaped,
)
_ => dae_empty_escaped_result_direct_call_evidence()
}
evidence = dae_merge_escaped_result_direct_call_evidence(
evidence, instr_evidence,
)
idx += 1
}
evidence
}
///|
fn dae_dead_suffix_escaped_result_direct_call_evidence(
instrs : Array[@lib.Instruction],
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
escaped : Array[Bool],
known_first_root~ : Int32 = -2,
) -> DaeEscapedResultDirectCallEvidence? {
match dae_dead_suffix_evidence_from_first_root(instrs, known_first_root) {
Some(evidence) =>
Some(
dae_escaped_result_direct_call_evidence(
evidence.body,
mod_,
type_indices,
escaped,
start_idx=evidence.suffix_start,
),
)
None => None
}
}
///|
fn dae_dead_suffix_has_dropped_escaped_result_call(
instrs : Array[@lib.Instruction],
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
escaped : Array[Bool],
known_first_root~ : Int32 = -2,
) -> Bool {
match
dae_dead_suffix_escaped_result_direct_call_evidence(
instrs,
mod_,
type_indices,
escaped,
known_first_root~,
) {
Some(evidence) => evidence.has_dropped_escaped_result_call
None => false
}
}
///|
fn dae_collect_original_dead_suffix_escaped_result_call_facts_with_escaped(
mod_ : @lib.Module,
escaped : Array[Bool],
first_roots~ : Array[Int32]? = None,
) -> DaeOriginalDeadSuffixEscapedResultCallFacts {
let defined = dae_defined_func_count(mod_)
let dropped_calls = Array::make(defined, false)
let undropped_calls = Array::make(defined, false)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return { dropped_calls, undropped_calls, }
}
let type_indices = dae_collect_func_type_indices(mod_)
let mut def_idx = 0
while def_idx < defined {
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
match
dae_dead_suffix_escaped_result_direct_call_evidence(
body,
mod_,
type_indices,
escaped,
known_first_root=dae_original_first_root_index(
body, first_roots, def_idx,
),
) {
Some(evidence) => {
dropped_calls[def_idx] = evidence.has_dropped_escaped_result_call
undropped_calls[def_idx] = evidence.has_undropped_escaped_result_call
}
None => ()
}
def_idx += 1
}
{ dropped_calls, undropped_calls, }
}
///|
fn dae_mark_dead_suffix_escaped_result_operand_targets(
instrs : Array[@lib.Instruction],
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
escaped : Array[Bool],
imported : Int32,
defined : Int32,
caller_stamps : Array[Int32],
caller_def : Int32,
counts : Array[Int32],
dropped_calls : Array[Bool],
require_dropped : Bool,
) -> Unit {
let stack_from_escaped_call : Array[Bool] = []
let mut in_suffix = false
let mut idx = 0
while idx < instrs.length() {
let instr = instrs[idx]
if !in_suffix {
if dae_instr_is_unreachable_root(instr) {
in_suffix = true
}
idx += 1
continue
}
match instr {
@lib.Call(func_idx) => {
let slot = dae_func_idx_slot(func_idx)
let def_idx = slot - imported
if def_idx >= 0 && def_idx < defined && def_idx < caller_stamps.length() {
match dae_resolve_func_type(mod_, type_indices, slot) {
Some(@lib.FuncType(params, _)) =>
if dae_stack_bool_has_true_operand(
stack_from_escaped_call,
params.length(),
) {
if caller_stamps[def_idx] != caller_def {
caller_stamps[def_idx] = caller_def
if def_idx != caller_def &&
(
!require_dropped ||
(
def_idx < dropped_calls.length() &&
dropped_calls[def_idx]
)
) {
counts[def_idx] = counts[def_idx] + 1
}
}
}
None => ()
}
}
let from_escaped = slot >= 0 && slot < escaped.length() && escaped[slot]
ignore(
dae_stack_bool_apply_instr_effect(
stack_from_escaped_call, instr, mod_, type_indices, from_escaped,
),
)
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) => {
dae_mark_dead_suffix_escaped_result_operand_targets(
body, mod_, type_indices, escaped, imported, defined, caller_stamps, caller_def,
counts, dropped_calls, require_dropped,
)
ignore(
dae_stack_bool_apply_instr_effect(
stack_from_escaped_call, instr, mod_, type_indices, false,
),
)
}
@lib.If(_, then_body, Some(else_body)) => {
dae_mark_dead_suffix_escaped_result_operand_targets(
then_body, mod_, type_indices, escaped, imported, defined, caller_stamps,
caller_def, counts, dropped_calls, require_dropped,
)
dae_mark_dead_suffix_escaped_result_operand_targets(
else_body, mod_, type_indices, escaped, imported, defined, caller_stamps,
caller_def, counts, dropped_calls, require_dropped,
)
ignore(
dae_stack_bool_apply_instr_effect(
stack_from_escaped_call, instr, mod_, type_indices, false,
),
)
}
@lib.If(_, then_body, None) => {
dae_mark_dead_suffix_escaped_result_operand_targets(
then_body, mod_, type_indices, escaped, imported, defined, caller_stamps,
caller_def, counts, dropped_calls, require_dropped,
)
ignore(
dae_stack_bool_apply_instr_effect(
stack_from_escaped_call, instr, mod_, type_indices, false,
),
)
}
_ =>
ignore(
dae_stack_bool_apply_instr_effect(
stack_from_escaped_call, instr, mod_, type_indices, false,
),
)
}
idx += 1
}
}
///|
fn dae_mark_dead_suffix_same_result_operand_targets(
instrs : Array[@lib.Instruction],
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
imported : Int32,
defined : Int32,
caller_stamps : Array[Int32],
caller_def : Int32,
counts : Array[Int32],
) -> Unit {
let stack_sources : Array[Int32] = []
let mut in_suffix = false
let mut idx = 0
while idx < instrs.length() {
let instr = instrs[idx]
if !in_suffix {
if dae_instr_is_unreachable_root(instr) {
in_suffix = true
}
idx += 1
continue
}
match instr {
@lib.Call(func_idx) => {
let slot = dae_func_idx_slot(func_idx)
let def_idx = slot - imported
let source = if slot >= imported && def_idx >= 0 && def_idx < defined {
def_idx
} else {
-1
}
if source >= 0 && source < caller_stamps.length() {
match dae_resolve_func_type(mod_, type_indices, slot) {
Some(@lib.FuncType(params, _)) =>
if dae_stack_int_has_operand_value(
stack_sources,
params.length(),
source,
) {
if caller_stamps[source] != caller_def {
caller_stamps[source] = caller_def
if source != caller_def {
counts[source] = counts[source] + 1
}
}
}
None => ()
}
}
ignore(
dae_stack_int_apply_instr_effect(
stack_sources, instr, mod_, type_indices, source,
),
)
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) => {
dae_mark_dead_suffix_same_result_operand_targets(
body, mod_, type_indices, imported, defined, caller_stamps, caller_def,
counts,
)
ignore(
dae_stack_int_apply_instr_effect(
stack_sources, instr, mod_, type_indices, -1,
),
)
}
@lib.If(_, then_body, Some(else_body)) => {
dae_mark_dead_suffix_same_result_operand_targets(
then_body, mod_, type_indices, imported, defined, caller_stamps, caller_def,
counts,
)
dae_mark_dead_suffix_same_result_operand_targets(
else_body, mod_, type_indices, imported, defined, caller_stamps, caller_def,
counts,
)
ignore(
dae_stack_int_apply_instr_effect(
stack_sources, instr, mod_, type_indices, -1,
),
)
}
@lib.If(_, then_body, None) => {
dae_mark_dead_suffix_same_result_operand_targets(
then_body, mod_, type_indices, imported, defined, caller_stamps, caller_def,
counts,
)
ignore(
dae_stack_int_apply_instr_effect(
stack_sources, instr, mod_, type_indices, -1,
),
)
}
_ =>
ignore(
dae_stack_int_apply_instr_effect(
stack_sources, instr, mod_, type_indices, -1,
),
)
}
idx += 1
}
}
///|
fn dae_dead_suffix_call_to_has_same_result_operand(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
) -> Bool {
let (param_count, result_count) = match
dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(params, results)) => (params.length(), results.length())
None => return false
}
let stack_from_target_call : Array[Bool] = []
let mut in_suffix = false
let mut idx = 0
while idx < instrs.length() {
let instr = instrs[idx]
if !in_suffix {
if dae_instr_is_unreachable_root(instr) {
in_suffix = true
}
idx += 1
continue
}
match instr {
@lib.Call(func_idx) if dae_func_idx_slot(func_idx) == callee_abs => {
if dae_stack_bool_has_true_operand(stack_from_target_call, param_count) {
return true
}
dae_stack_bool_apply_effect(
stack_from_target_call, param_count, result_count, true,
)
}
@lib.Call(_) =>
ignore(
dae_stack_bool_apply_instr_effect(
stack_from_target_call, instr, mod_, type_indices, false,
),
)
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) => {
if dae_dead_suffix_call_to_has_same_result_operand(
body, callee_abs, mod_, type_indices,
) {
return true
}
ignore(
dae_stack_bool_apply_instr_effect(
stack_from_target_call, instr, mod_, type_indices, false,
),
)
}
@lib.If(_, then_body, Some(else_body)) => {
if dae_dead_suffix_call_to_has_same_result_operand(
then_body, callee_abs, mod_, type_indices,
) ||
dae_dead_suffix_call_to_has_same_result_operand(
else_body, callee_abs, mod_, type_indices,
) {
return true
}
ignore(
dae_stack_bool_apply_instr_effect(
stack_from_target_call, instr, mod_, type_indices, false,
),
)
}
@lib.If(_, then_body, None) => {
if dae_dead_suffix_call_to_has_same_result_operand(
then_body, callee_abs, mod_, type_indices,
) {
return true
}
ignore(
dae_stack_bool_apply_instr_effect(
stack_from_target_call, instr, mod_, type_indices, false,
),
)
}
_ =>
ignore(
dae_stack_bool_apply_instr_effect(
stack_from_target_call, instr, mod_, type_indices, false,
),
)
}
idx += 1
}
false
}
///|
fn dae_collect_original_private_result_same_operand_call_counts_with_escaped(
mod_ : @lib.Module,
escaped : Array[Bool],
) -> Array[Int32] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, 0)
}
let type_indices = dae_collect_func_type_indices(mod_)
let counts = Array::make(defined, 0)
// A defined caller index is a unique nonnegative stamp in this invocation.
// Allocate only at the first eligible caller; no row clear is needed later.
let mut caller_stamps : Array[Int32]? = None
let mut caller_def = 0
while caller_def < funcs.length() {
let caller_abs = imported + caller_def
let caller_is_private_result = match
dae_resolve_func_type(mod_, type_indices, caller_abs) {
Some(@lib.FuncType(_, results)) => results.length() > 0
None => false
}
if caller_is_private_result && !escaped[caller_abs] {
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
let stamps = match caller_stamps {
Some(row) => row
None => {
let row = Array::make(defined, -1)
caller_stamps = Some(row)
row
}
}
dae_mark_dead_suffix_same_result_operand_targets(
body, mod_, type_indices, imported, defined, stamps, caller_def, counts,
)
}
caller_def += 1
}
counts
}
///|
fn dae_collect_original_dead_suffix_result_calls_from_undropped_self_callers_with_escaped(
mod_ : @lib.Module,
original_nonself_undropped_call_counts : Array[Int32],
escaped : Array[Bool],
first_roots~ : Array[Int32]? = None,
) -> Array[Int32] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, 0)
}
let type_indices = dae_collect_func_type_indices(mod_)
let counts = Array::make(defined, 0)
let mut caller_def = 0
while caller_def < funcs.length() {
let caller_abs = imported + caller_def
let caller_has_params_and_result = match
dae_resolve_func_type(mod_, type_indices, caller_abs) {
Some(@lib.FuncType(params, results)) =>
params.length() > 0 && results.length() > 0
None => false
}
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
let caller_has_undropped_self_suffix = dae_dead_suffix_has_undropped_direct_call_to(
body,
caller_abs,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
let caller_has_dropped_escaped_result_suffix = dae_dead_suffix_has_dropped_escaped_result_call(
body,
mod_,
type_indices,
escaped,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
let caller_has_undropped_nonself_caller = caller_def <
original_nonself_undropped_call_counts.length() &&
original_nonself_undropped_call_counts[caller_def] > 0
if caller_has_params_and_result &&
caller_has_undropped_self_suffix &&
caller_has_undropped_nonself_caller &&
!caller_has_dropped_escaped_result_suffix &&
!escaped[caller_abs] {
let targets : Array[Int32] = []
dae_collect_dead_suffix_direct_targets(
body,
imported,
defined,
targets,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
for target in targets {
if target != caller_def {
counts[target] = counts[target] + 1
}
}
}
caller_def += 1
}
counts
}
///|
fn dae_collect_original_dead_suffix_result_calls_from_no_param_undropped_self_callers(
mod_ : @lib.Module,
first_roots~ : Array[Int32]? = None,
) -> Array[Int32] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, 0)
}
let type_indices = dae_collect_func_type_indices(mod_)
let counts = Array::make(defined, 0)
let mut caller_def = 0
while caller_def < funcs.length() {
let caller_abs = imported + caller_def
let caller_is_no_param_result = match
dae_resolve_func_type(mod_, type_indices, caller_abs) {
Some(@lib.FuncType(params, results)) =>
params.length() == 0 && results.length() > 0
None => false
}
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
if caller_is_no_param_result &&
dae_dead_suffix_has_undropped_direct_call_to(
body,
caller_abs,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
) {
let targets : Array[Int32] = []
dae_collect_dead_suffix_direct_targets(
body,
imported,
defined,
targets,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
for target in targets {
if target != caller_def {
counts[target] = counts[target] + 1
}
}
}
caller_def += 1
}
counts
}
///|
fn dae_collect_original_dead_suffix_undropped_calls_from_parameterized_dropped_self_callers(
mod_ : @lib.Module,
first_roots~ : Array[Int32]? = None,
) -> Array[Int32] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, 0)
}
let type_indices = dae_collect_func_type_indices(mod_)
let counts = Array::make(defined, 0)
let counted_stamp = Array::make(defined, -1)
let mut caller_def = 0
while caller_def < funcs.length() {
let caller_abs = imported + caller_def
let caller_has_params_and_result = match
dae_resolve_func_type(mod_, type_indices, caller_abs) {
Some(@lib.FuncType(params, results)) =>
params.length() > 0 && results.length() > 0
None => false
}
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
if caller_has_params_and_result &&
dae_dead_suffix_has_dropped_direct_call_to(
body,
caller_abs,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
) {
let targets : Array[Int32] = []
dae_collect_dead_suffix_undropped_direct_targets(
body,
imported,
defined,
targets,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
for target in targets {
if target != caller_def && counted_stamp[target] != caller_def {
counts[target] = counts[target] + 1
counted_stamp[target] = caller_def
}
}
}
caller_def += 1
}
counts
}
///|
fn dae_collect_original_dead_suffix_mixed_dropped_undropped_call_counts_with_escaped(
mod_ : @lib.Module,
escaped : Array[Bool],
first_roots~ : Array[Int32]? = None,
) -> Array[Int32] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, 0)
}
let counts = Array::make(defined, 0)
let dropped_stamp = Array::make(defined, -1)
let counted_stamp = Array::make(defined, -1)
let mut caller_def = 0
while caller_def < funcs.length() {
let caller_abs = imported + caller_def
if escaped[caller_abs] {
caller_def += 1
continue
}
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
let dropped_targets : Array[Int32] = []
dae_collect_dead_suffix_dropped_direct_targets(
body,
imported,
defined,
dropped_targets,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
for target in dropped_targets {
dropped_stamp[target] = caller_def
}
let undropped_targets : Array[Int32] = []
dae_collect_dead_suffix_undropped_direct_targets(
body,
imported,
defined,
undropped_targets,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
for target in undropped_targets {
if target != caller_def &&
dropped_stamp[target] == caller_def &&
counted_stamp[target] != caller_def {
counts[target] = counts[target] + 1
counted_stamp[target] = caller_def
}
}
caller_def += 1
}
counts
}
///|
fn dae_collect_original_dead_suffix_dropped_earlier_result_calls(
mod_ : @lib.Module,
first_roots~ : Array[Int32]? = None,
) -> Array[Bool] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let type_indices = dae_collect_func_type_indices(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, false)
}
let result_flags = Array::make(defined, false)
let mut callee_def = 0
while callee_def < defined {
let callee_abs = imported + callee_def
result_flags[callee_def] = match
dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(_, results)) => results.length() > 0
None => false
}
callee_def += 1
}
let flags = Array::make(defined, false)
let mut caller_def = 0
while caller_def < funcs.length() {
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
let dropped_targets : Array[Int32] = []
dae_collect_dead_suffix_dropped_direct_targets(
body,
imported,
defined,
dropped_targets,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
for target in dropped_targets {
if target < caller_def && result_flags[target] {
flags[caller_def] = true
}
}
caller_def += 1
}
flags
}
///|
fn dae_collect_original_later_dead_suffix_dropped_call_counts(
mod_ : @lib.Module,
first_roots~ : Array[Int32]? = None,
) -> Array[Int32] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, 0)
}
let counts = Array::make(defined, 0)
let counted_stamp = Array::make(defined, -1)
let mut caller_def = 0
while caller_def < funcs.length() {
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
let dropped_targets : Array[Int32] = []
dae_collect_dead_suffix_dropped_direct_targets(
body,
imported,
defined,
dropped_targets,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
for target in dropped_targets {
if target < caller_def && counted_stamp[target] != caller_def {
counts[target] = counts[target] + 1
counted_stamp[target] = caller_def
}
}
caller_def += 1
}
counts
}
///|
fn dae_collect_original_dead_suffix_dropped_calls_from_parameterized_self_callers(
mod_ : @lib.Module,
first_roots~ : Array[Int32]? = None,
) -> Array[Int32] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, 0)
}
let type_indices = dae_collect_func_type_indices(mod_)
let counts = Array::make(defined, 0)
let counted_stamp = Array::make(defined, -1)
let mut caller_def = 0
while caller_def < funcs.length() {
let caller_abs = imported + caller_def
let caller_has_params_and_result = match
dae_resolve_func_type(mod_, type_indices, caller_abs) {
Some(@lib.FuncType(params, results)) =>
params.length() > 0 && results.length() > 0
None => false
}
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
if caller_has_params_and_result &&
dae_dead_suffix_has_undropped_direct_call_to(
body,
caller_abs,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
) {
let targets : Array[Int32] = []
dae_collect_dead_suffix_dropped_direct_targets(
body,
imported,
defined,
targets,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
for target in targets {
if target != caller_def && counted_stamp[target] != caller_def {
counts[target] = counts[target] + 1
counted_stamp[target] = caller_def
}
}
}
caller_def += 1
}
counts
}
///|
fn dae_collect_original_dead_suffix_dropped_calls_from_no_param_result_callers(
mod_ : @lib.Module,
first_roots~ : Array[Int32]? = None,
) -> Array[Int32] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, 0)
}
let type_indices = dae_collect_func_type_indices(mod_)
let counts = Array::make(defined, 0)
let counted_stamp = Array::make(defined, -1)
let mut caller_def = 0
while caller_def < funcs.length() {
let caller_abs = imported + caller_def
let caller_has_no_params_and_result = match
dae_resolve_func_type(mod_, type_indices, caller_abs) {
Some(@lib.FuncType(params, results)) =>
params.length() == 0 && results.length() > 0
None => false
}
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
if caller_has_no_params_and_result {
let targets : Array[Int32] = []
dae_collect_dead_suffix_dropped_direct_targets(
body,
imported,
defined,
targets,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
for target in targets {
if target != caller_def && counted_stamp[target] != caller_def {
counts[target] = counts[target] + 1
counted_stamp[target] = caller_def
}
}
}
caller_def += 1
}
counts
}
///|
fn dae_collect_original_escaped_result_operand_call_facts_with_escaped(
mod_ : @lib.Module,
escaped : Array[Bool],
dead_suffix_escaped_result_calls : DaeOriginalDeadSuffixEscapedResultCallFacts,
) -> DaeOriginalEscapedResultOperandCallFacts {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let dead_suffix_escaped_result_operand_call_counts = Array::make(defined, 0)
let private_result_escaped_operand_call_counts = Array::make(defined, 0)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return {
dead_suffix_escaped_result_operand_call_counts,
private_result_escaped_operand_call_counts,
}
}
let type_indices = dae_collect_func_type_indices(mod_)
// A defined caller index is a unique nonnegative stamp in this invocation.
// Allocate only at the first eligible caller; no row clear is needed later.
let mut caller_stamps : Array[Int32]? = None
let mut caller_def = 0
while caller_def < funcs.length() {
let caller_abs = imported + caller_def
let caller_is_escaped = escaped[caller_abs]
let caller_has_result = match
dae_resolve_func_type(mod_, type_indices, caller_abs) {
Some(@lib.FuncType(_, results)) => results.length() > 0
None => false
}
let caller_is_private_result = !caller_is_escaped && caller_has_result
if caller_is_escaped || caller_is_private_result {
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
let stamps = match caller_stamps {
Some(row) => row
None => {
let row = Array::make(defined, -1)
caller_stamps = Some(row)
row
}
}
// These caller roles are exclusive, so only one output row is selected.
let counts = if caller_is_escaped {
dead_suffix_escaped_result_operand_call_counts
} else {
private_result_escaped_operand_call_counts
}
dae_mark_dead_suffix_escaped_result_operand_targets(
body,
mod_,
type_indices,
escaped,
imported,
defined,
stamps,
caller_def,
counts,
dead_suffix_escaped_result_calls.dropped_calls,
caller_is_private_result,
)
}
caller_def += 1
}
{
dead_suffix_escaped_result_operand_call_counts,
private_result_escaped_operand_call_counts,
}
}
///|
fn dae_collect_original_private_result_dead_suffix_calls_from_escaped_operand_callees(
mod_ : @lib.Module,
escaped_operand_counts : Array[Int32],
first_roots~ : Array[Int32]? = None,
) -> Array[Int32] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, 0)
}
let type_indices = dae_collect_func_type_indices(mod_)
let counts = Array::make(defined, 0)
let mut caller_def = 0
while caller_def < funcs.length() {
let caller_abs = imported + caller_def
let caller_has_params_and_result = match
dae_resolve_func_type(mod_, type_indices, caller_abs) {
Some(@lib.FuncType(params, results)) =>
params.length() > 0 && results.length() > 0
None => false
}
if caller_has_params_and_result &&
caller_def < escaped_operand_counts.length() &&
escaped_operand_counts[caller_def] > 0 {
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
let targets : Array[Int32] = []
dae_collect_dead_suffix_direct_targets(
body,
imported,
defined,
targets,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
for target in targets {
if target != caller_def {
counts[target] = counts[target] + 1
}
}
}
caller_def += 1
}
counts
}
///|
fn dae_collect_original_dead_suffix_same_self_result_operand_calls(
mod_ : @lib.Module,
) -> Array[Bool] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let type_indices = dae_collect_func_type_indices(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, false)
}
let flags = Array::make(defined, false)
let mut def_idx = 0
while def_idx < funcs.length() {
let callee_abs = imported + def_idx
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
flags[def_idx] = dae_dead_suffix_call_to_has_same_result_operand(
body, callee_abs, mod_, type_indices,
)
def_idx += 1
}
flags
}
///|
fn dae_collect_original_private_result_dead_suffix_call_counts_with_escaped(
mod_ : @lib.Module,
original_call_counts : Array[Int32],
escaped : Array[Bool],
first_roots~ : Array[Int32]? = None,
) -> Array[Int32] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, 0)
}
let type_indices = dae_collect_func_type_indices(mod_)
let counts = Array::make(defined, 0)
let mut caller_def = 0
while caller_def < funcs.length() {
let caller_abs = imported + caller_def
let caller_has_params_and_result = match
dae_resolve_func_type(mod_, type_indices, caller_abs) {
Some(@lib.FuncType(params, results)) =>
params.length() > 0 && results.length() > 0
None => false
}
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
let caller_has_undropped_self_suffix = dae_dead_suffix_has_undropped_direct_call_to(
body,
caller_abs,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
let caller_has_dropped_escaped_result_suffix = dae_dead_suffix_has_dropped_escaped_result_call(
body,
mod_,
type_indices,
escaped,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
if caller_has_params_and_result &&
!caller_has_undropped_self_suffix &&
!caller_has_dropped_escaped_result_suffix &&
!escaped[caller_abs] &&
caller_def < original_call_counts.length() &&
original_call_counts[caller_def] > 0 {
let targets : Array[Int32] = []
dae_collect_dead_suffix_direct_targets(
body,
imported,
defined,
targets,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
for target in targets {
counts[target] = counts[target] + 1
}
}
caller_def += 1
}
counts
}
///|
fn dae_collect_original_escaped_dead_suffix_call_counts_with_escaped(
mod_ : @lib.Module,
escaped : Array[Bool],
first_roots~ : Array[Int32]? = None,
) -> Array[Int32] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, 0)
}
let counts = Array::make(defined, 0)
let mut caller_def = 0
while caller_def < funcs.length() {
let caller_abs = imported + caller_def
if escaped[caller_abs] {
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
let targets : Array[Int32] = []
dae_collect_dead_suffix_direct_targets(
body,
imported,
defined,
targets,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
for target in targets {
counts[target] = counts[target] + 1
}
}
caller_def += 1
}
counts
}
///|
fn dae_collect_original_escaped_undropped_dead_suffix_call_counts_with_escaped(
mod_ : @lib.Module,
escaped : Array[Bool],
first_roots~ : Array[Int32]? = None,
) -> Array[Int32] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, 0)
}
let counts = Array::make(defined, 0)
let counted_stamp = Array::make(defined, -1)
let mut caller_def = 0
while caller_def < funcs.length() {
let caller_abs = imported + caller_def
if escaped[caller_abs] {
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
let targets : Array[Int32] = []
dae_collect_dead_suffix_undropped_direct_targets(
body,
imported,
defined,
targets,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
for target in targets {
if counted_stamp[target] != caller_def {
counts[target] = counts[target] + 1
counted_stamp[target] = caller_def
}
}
}
caller_def += 1
}
counts
}
///|
fn dae_collect_original_dead_suffix_other_calls(
mod_ : @lib.Module,
first_roots~ : Array[Int32]? = None,
) -> Array[Bool] {
let imported = dae_imported_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
let flags : Array[Bool] = []
let mut def_idx = 0
while def_idx < funcs.length() {
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
flags.push(
dae_dead_suffix_has_other_call(
body,
imported + def_idx,
known_first_root=dae_original_first_root_index(
body, first_roots, def_idx,
),
),
)
def_idx += 1
}
flags
}
///|
fn dae_collect_original_dead_suffix_undropped_self_calls(
mod_ : @lib.Module,
first_roots~ : Array[Int32]? = None,
) -> Array[Bool] {
let imported = dae_imported_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
let flags : Array[Bool] = []
let mut def_idx = 0
while def_idx < funcs.length() {
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
flags.push(
dae_dead_suffix_has_undropped_direct_call_to(
body,
imported + def_idx,
known_first_root=dae_original_first_root_index(
body, first_roots, def_idx,
),
),
)
def_idx += 1
}
flags
}
///|
fn dae_instrs_have_undropped_call_to_with_param_operand(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
param_count : Int32,
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
start_idx~ : Int32 = 0,
) -> Bool {
let stack : Array[Int32] = []
let mut idx = start_idx
while idx < instrs.length() {
match instrs[idx] {
@lib.LocalGet(local_idx) => {
let slot = dae_local_idx_slot(local_idx)
if slot >= 0 && slot < param_count {
stack.push(slot)
} else {
stack.push(-1)
}
}
@lib.Call(func_idx) if dae_func_idx_slot(func_idx) == callee_abs => {
if dae_stack_int_has_known_operand(stack, param_count) &&
!(idx + 1 < instrs.length() &&
instrs[idx + 1] == @lib.Instruction::drop()) {
return true
}
let result_count = match
dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(_, results)) => results.length()
None => 0
}
dae_stack_int_apply_effect(stack, param_count, result_count, -1)
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) => {
if dae_instrs_have_undropped_call_to_with_param_operand(
body, callee_abs, param_count, mod_, type_indices,
) {
return true
}
ignore(
dae_stack_int_apply_instr_effect(
stack,
instrs[idx],
mod_,
type_indices,
-1,
),
)
}
@lib.If(_, then_body, Some(else_body)) => {
if dae_instrs_have_undropped_call_to_with_param_operand(
then_body, callee_abs, param_count, mod_, type_indices,
) ||
dae_instrs_have_undropped_call_to_with_param_operand(
else_body, callee_abs, param_count, mod_, type_indices,
) {
return true
}
ignore(
dae_stack_int_apply_instr_effect(
stack,
instrs[idx],
mod_,
type_indices,
-1,
),
)
}
@lib.If(_, then_body, None) => {
if dae_instrs_have_undropped_call_to_with_param_operand(
then_body, callee_abs, param_count, mod_, type_indices,
) {
return true
}
ignore(
dae_stack_int_apply_instr_effect(
stack,
instrs[idx],
mod_,
type_indices,
-1,
),
)
}
_ =>
ignore(
dae_stack_int_apply_instr_effect(
stack,
instrs[idx],
mod_,
type_indices,
-1,
),
)
}
idx += 1
}
false
}
///|
fn dae_dead_suffix_has_undropped_self_call_param_operand(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
param_count : Int32,
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
known_first_root~ : Int32 = -2,
) -> Bool {
match dae_dead_suffix_evidence_from_first_root(instrs, known_first_root) {
Some(evidence) =>
dae_instrs_have_undropped_call_to_with_param_operand(
evidence.body,
callee_abs,
param_count,
mod_,
type_indices,
start_idx=evidence.suffix_start,
)
None => false
}
}
///|
fn dae_collect_original_dead_suffix_undropped_self_call_param_operands(
mod_ : @lib.Module,
first_roots~ : Array[Int32]? = None,
) -> Array[Bool] {
let imported = dae_imported_func_count(mod_)
let type_indices = dae_collect_func_type_indices(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
let flags : Array[Bool] = []
let mut def_idx = 0
while def_idx < funcs.length() {
let callee_abs = imported + def_idx
let param_count = match
dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params.length()
None => 0
}
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
flags.push(
param_count > 0 &&
dae_dead_suffix_has_undropped_self_call_param_operand(
body,
callee_abs,
param_count,
mod_,
type_indices,
known_first_root=dae_original_first_root_index(
body, first_roots, def_idx,
),
),
)
def_idx += 1
}
flags
}
///|
fn dae_dead_suffix_has_block_undropped_direct_call_to(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
) -> Bool {
let mut idx = 0
while idx < instrs.length() {
if dae_instr_is_unreachable_root(instrs[idx]) {
let mut suffix_idx = idx + 1
while suffix_idx < instrs.length() {
match
dae_control_result_undropped_direct_call_evidence(
instrs[suffix_idx],
callee_abs,
) {
Some(evidence) if dae_control_result_undropped_direct_call_evidence_is_consistent(
evidence,
) &&
evidence.blocks_result_removal => return true
_ => ()
}
suffix_idx += 1
}
return false
}
idx += 1
}
false
}
///|
fn dae_collect_original_dead_suffix_block_undropped_self_calls(
mod_ : @lib.Module,
) -> Array[Bool] {
let imported = dae_imported_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
let flags : Array[Bool] = []
let mut def_idx = 0
while def_idx < funcs.length() {
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
flags.push(
dae_dead_suffix_has_block_undropped_direct_call_to(
body,
imported + def_idx,
),
)
def_idx += 1
}
flags
}
///|
fn dae_collect_original_dead_suffix_dropped_self_calls(
mod_ : @lib.Module,
first_roots~ : Array[Int32]? = None,
) -> Array[Bool] {
let imported = dae_imported_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
let flags : Array[Bool] = []
let mut def_idx = 0
while def_idx < funcs.length() {
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
flags.push(
dae_dead_suffix_has_dropped_direct_call_to(
body,
imported + def_idx,
known_first_root=dae_original_first_root_index(
body, first_roots, def_idx,
),
),
)
def_idx += 1
}
flags
}
///|
fn dae_dead_suffix_has_dropped_self_call_full_param_forward(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
param_count : Int32,
) -> Bool {
if param_count == 0 {
return false
}
let mut idx = 0
while idx < instrs.length() {
if dae_instr_is_unreachable_root(instrs[idx]) {
let mut suffix_idx = idx + 1
while suffix_idx + 1 < instrs.length() {
match (instrs[suffix_idx], instrs[suffix_idx + 1]) {
(@lib.Call(func_idx), @lib.Drop) if dae_func_idx_slot(func_idx) ==
callee_abs =>
match
dae_adjacent_param_forward_evidence(
instrs, suffix_idx, param_count,
) {
Some(evidence) if evidence.start >= idx + 1 &&
evidence.end == suffix_idx => return true
_ => ()
}
_ => ()
}
suffix_idx += 1
}
return false
}
idx += 1
}
false
}
///|
fn dae_collect_original_dead_suffix_dropped_self_call_full_param_forwards(
mod_ : @lib.Module,
) -> Array[Bool] {
let imported = dae_imported_func_count(mod_)
let type_indices = dae_collect_func_type_indices(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
let flags : Array[Bool] = []
let mut def_idx = 0
while def_idx < funcs.length() {
let callee_abs = imported + def_idx
let param_count = match
dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params.length()
None => 0
}
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
flags.push(
dae_dead_suffix_has_dropped_self_call_full_param_forward(
body, callee_abs, param_count,
),
)
def_idx += 1
}
flags
}
///|
fn dae_instrs_have_private_result_direct_call_other_than(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
escaped : Array[Bool],
start_idx~ : Int32 = 0,
) -> Bool {
for index = start_idx; index < instrs.length(); index = index + 1 {
let instr = instrs[index]
match instr {
@lib.Call(func_idx) => {
let slot = dae_func_idx_slot(func_idx)
if slot != callee_abs &&
slot >= 0 &&
slot < escaped.length() &&
!escaped[slot] {
match dae_resolve_func_type(mod_, type_indices, slot) {
Some(@lib.FuncType(_, results)) if results.length() > 0 =>
return true
_ => ()
}
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if dae_instrs_have_private_result_direct_call_other_than(
body, callee_abs, mod_, type_indices, escaped,
) {
return true
}
@lib.If(_, then_body, Some(else_body)) =>
if dae_instrs_have_private_result_direct_call_other_than(
then_body, callee_abs, mod_, type_indices, escaped,
) ||
dae_instrs_have_private_result_direct_call_other_than(
else_body, callee_abs, mod_, type_indices, escaped,
) {
return true
}
@lib.If(_, then_body, None) =>
if dae_instrs_have_private_result_direct_call_other_than(
then_body, callee_abs, mod_, type_indices, escaped,
) {
return true
}
_ => ()
}
}
false
}
///|
fn dae_dead_suffix_has_private_result_call_other_than(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
escaped : Array[Bool],
known_first_root~ : Int32 = -2,
) -> Bool {
match dae_dead_suffix_evidence_from_first_root(instrs, known_first_root) {
Some(evidence) =>
dae_instrs_have_private_result_direct_call_other_than(
evidence.body,
callee_abs,
mod_,
type_indices,
escaped,
start_idx=evidence.suffix_start,
)
None => false
}
}
///|
fn dae_collect_original_dead_suffix_self_recursive_result_side_calls(
mod_ : @lib.Module,
first_roots~ : Array[Int32]? = None,
) -> Array[Bool] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let type_indices = dae_collect_func_type_indices(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, false)
}
let undropped_result_self_suffix = Array::make(defined, false)
let mut def_idx = 0
while def_idx < funcs.length() {
let callee_abs = imported + def_idx
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
let has_result = match
dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(_, results)) => results.length() > 0
None => false
}
undropped_result_self_suffix[def_idx] = has_result &&
dae_dead_suffix_has_undropped_direct_call_to(
body,
callee_abs,
known_first_root=dae_original_first_root_index(
body, first_roots, def_idx,
),
)
def_idx += 1
}
let flags = Array::make(defined, false)
def_idx = 0
while def_idx < funcs.length() {
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
let targets : Array[Int32] = []
dae_collect_dead_suffix_direct_targets(
body,
imported,
defined,
targets,
known_first_root=dae_original_first_root_index(body, first_roots, def_idx),
)
for target in targets {
if target != def_idx &&
target < def_idx &&
undropped_result_self_suffix[target] {
flags[def_idx] = true
}
}
def_idx += 1
}
flags
}
///|
fn dae_collect_original_dead_suffix_self_recursive_void_side_calls(
mod_ : @lib.Module,
first_roots~ : Array[Int32]? = None,
original_self_call_counts~ : Array[Int32]? = None,
) -> Array[Bool] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let type_indices = dae_collect_func_type_indices(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, false)
}
let original_self_calls = match original_self_call_counts {
Some(counts) if counts.length() == funcs.length() &&
funcs.length() == defined => counts
_ => dae_collect_original_self_call_counts(mod_)
}
let self_recursive_void = Array::make(defined, false)
let mut def_idx = 0
while def_idx < funcs.length() {
let callee_abs = imported + def_idx
let is_void = match dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(_, results)) => results.length() == 0
None => false
}
self_recursive_void[def_idx] = is_void &&
def_idx < original_self_calls.length() &&
original_self_calls[def_idx] > 0
def_idx += 1
}
let flags = Array::make(defined, false)
def_idx = 0
while def_idx < funcs.length() {
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
let targets : Array[Int32] = []
dae_collect_dead_suffix_direct_targets(
body,
imported,
defined,
targets,
known_first_root=dae_original_first_root_index(body, first_roots, def_idx),
)
for target in targets {
if target != def_idx && self_recursive_void[target] {
flags[def_idx] = true
}
}
def_idx += 1
}
flags
}
///|
fn dae_collect_original_dead_suffix_result_other_calls_with_escaped(
mod_ : @lib.Module,
escaped : Array[Bool],
first_roots~ : Array[Int32]? = None,
) -> Array[Bool] {
let imported = dae_imported_func_count(mod_)
let type_indices = dae_collect_func_type_indices(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
let flags : Array[Bool] = []
let mut def_idx = 0
while def_idx < funcs.length() {
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
flags.push(
dae_dead_suffix_has_private_result_call_other_than(
body,
imported + def_idx,
mod_,
type_indices,
escaped,
known_first_root=dae_original_first_root_index(
body, first_roots, def_idx,
),
),
)
def_idx += 1
}
flags
}
///|
fn dae_collect_original_dead_suffix_mutual_calls(
mod_ : @lib.Module,
first_roots~ : Array[Int32]? = None,
) -> Array[Bool] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, false)
}
let flags = Array::make(defined, false)
let counted_stamp = Array::make(defined, -1)
let mut def_idx = 0
while def_idx < funcs.length() {
let callee_abs = imported + def_idx
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
let targets : Array[Int32] = []
dae_collect_dead_suffix_direct_targets(
body,
imported,
defined,
targets,
known_first_root=dae_original_first_root_index(body, first_roots, def_idx),
)
for target in targets {
if target != def_idx && counted_stamp[target] != def_idx {
counted_stamp[target] = def_idx
if target >= 0 && target < funcs.length() {
let @lib.Func(_, @lib.Expr(other_body)) = funcs[target]
if dae_count_direct_call_anywhere_to(other_body, callee_abs) > 0 {
flags[def_idx] = true
}
}
}
}
def_idx += 1
}
flags
}
///|
fn dae_original_result_removal_prune_row_from_preservation_row(
preservation : DaeOriginalResultPreservationRow,
active_parameterized_result_nonself_call_count : Int32,
dead_suffix_dropped_self_call_full_param_forward : Bool,
dead_suffix_dropped_earlier_result_call : Bool,
later_dead_suffix_dropped_call_count : Int32,
private_result_dead_suffix_call_count : Int32,
dead_suffix_dropped_calls_from_no_param_result_caller : Int32,
dead_suffix_mixed_dropped_undropped_call_count : Int32,
) -> DaeOriginalResultRemovalPruneRow {
{
param_count: preservation.param_count,
call_count: preservation.call_count,
active_nonself_call_count: preservation.active_nonself_call_count,
active_parameterized_nonself_call_count: preservation.active_parameterized_nonself_call_count,
active_parameterized_result_nonself_call_count,
nonself_undropped_call_count: preservation.nonself_undropped_call_count,
self_call_count: preservation.self_call_count,
escaped_dead_suffix_call_count: preservation.escaped_dead_suffix_call_count,
dead_suffix_undropped_self_call: preservation.dead_suffix_undropped_self_call,
dead_suffix_dropped_self_call: preservation.dead_suffix_dropped_self_call,
dead_suffix_block_undropped_self_call: preservation.dead_suffix_block_undropped_self_call,
dead_suffix_same_self_result_operand_call: preservation.dead_suffix_same_self_result_operand_call,
dead_suffix_dropped_self_call_full_param_forward,
dead_suffix_dropped_escaped_result_call: preservation.dead_suffix_dropped_escaped_result_call,
dead_suffix_dropped_earlier_result_call,
later_dead_suffix_dropped_call_count,
dead_suffix_mutual_call: preservation.dead_suffix_mutual_call,
private_result_dead_suffix_call_count,
dead_suffix_result_other_call: preservation.dead_suffix_result_other_call,
dead_suffix_dropped_calls_from_no_param_result_caller,
dead_suffix_mixed_dropped_undropped_call_count,
}
}
///|
fn dae_should_prune_later_mutual_dropped_result_from_row(
row : DaeOriginalResultRemovalPruneRow,
) -> Bool {
row.param_count > 0 &&
row.self_call_count == 0 &&
row.nonself_undropped_call_count == 0 &&
row.escaped_dead_suffix_call_count == 0 &&
!row.dead_suffix_undropped_self_call &&
!row.dead_suffix_dropped_escaped_result_call &&
row.dead_suffix_dropped_earlier_result_call &&
row.later_dead_suffix_dropped_call_count > 0
}
///|
fn dae_should_prune_dropped_self_result_with_void_side_callers_from_row(
row : DaeOriginalResultRemovalPruneRow,
) -> Bool {
row.param_count > 0 &&
row.self_call_count > 0 &&
row.call_count > row.self_call_count &&
row.active_nonself_call_count > 0 &&
row.active_parameterized_nonself_call_count > 0 &&
row.active_parameterized_result_nonself_call_count == 0 &&
row.nonself_undropped_call_count == 0 &&
row.escaped_dead_suffix_call_count == 0 &&
!row.dead_suffix_undropped_self_call &&
row.dead_suffix_dropped_self_call &&
!row.dead_suffix_dropped_self_call_full_param_forward &&
!row.dead_suffix_mutual_call
}
///|
fn dae_should_prune_mixed_dropped_undropped_caller_from_row(
row : DaeOriginalResultRemovalPruneRow,
) -> Bool {
row.param_count > 0 &&
row.self_call_count == 0 &&
row.dead_suffix_mixed_dropped_undropped_call_count > 0 &&
row.escaped_dead_suffix_call_count > 0
}
///|
fn dae_should_preserve_mixed_self_suffix_from_row(
row : DaeOriginalResultRemovalPruneRow,
) -> Bool {
row.param_count > 0 &&
row.dead_suffix_undropped_self_call &&
row.dead_suffix_dropped_self_call &&
row.escaped_dead_suffix_call_count == 0 &&
(
row.call_count == row.self_call_count ||
row.active_nonself_call_count > 0 ||
row.nonself_undropped_call_count > 0
)
}
///|
fn dae_should_preserve_same_self_result_operand_from_row(
row : DaeOriginalResultRemovalPruneRow,
) -> Bool {
row.param_count > 0 &&
row.dead_suffix_same_self_result_operand_call &&
row.nonself_undropped_call_count > 0
}
///|
fn dae_original_dead_suffix_side_call_evidence(
row : DaeOriginalResultRemovalPruneRow,
side_call : DaeOriginalResultSideCallRow,
) -> DaeOriginalDeadSuffixSideCallEvidence {
let originally_had_params = row.param_count > 0
let void_dead_suffix_side_call = side_call.dead_suffix_other_call &&
!side_call.dead_suffix_result_other_call
let void_side_cleanup_preservation = originally_had_params &&
row.self_call_count == 0 &&
void_dead_suffix_side_call &&
side_call.dead_suffix_self_recursive_void_side_call &&
row.escaped_dead_suffix_call_count == 0 &&
row.call_count > 0 &&
row.active_nonself_call_count > 0 &&
row.nonself_undropped_call_count == 0
let result_side_chain_preservation = originally_had_params &&
row.self_call_count > 0 &&
row.dead_suffix_undropped_self_call &&
side_call.dead_suffix_mutual_call &&
side_call.dead_suffix_self_recursive_result_side_call &&
row.call_count > row.self_call_count
let same_self_result_operand_preservation = originally_had_params &&
row.self_call_count > 0 &&
row.dead_suffix_same_self_result_operand_call &&
row.nonself_undropped_call_count > 0 &&
row.call_count >= row.self_call_count
{
dead_suffix_undropped_self_call: row.dead_suffix_undropped_self_call,
dead_suffix_mutual_call: side_call.dead_suffix_mutual_call,
void_side_cleanup_preservation,
result_side_chain_preservation,
same_self_result_operand_preservation,
}
}
///|
fn dae_should_preserve_escaped_block_self_result_from_row(
row : DaeOriginalResultRemovalPruneRow,
require_self_call : Bool,
) -> Bool {
row.param_count > 0 &&
(!require_self_call || row.self_call_count > 0) &&
row.escaped_dead_suffix_call_count > 0 &&
row.dead_suffix_undropped_self_call &&
row.dead_suffix_dropped_self_call &&
row.dead_suffix_block_undropped_self_call
}
///|
fn dae_should_prune_dropped_only_private_result_side_caller_from_row(
row : DaeOriginalResultRemovalPruneRow,
) -> Bool {
row.param_count > 0 &&
row.self_call_count == 0 &&
row.call_count > 0 &&
row.nonself_undropped_call_count == 0 &&
row.escaped_dead_suffix_call_count == 0 &&
row.private_result_dead_suffix_call_count > 0
}
///|
fn dae_should_prune_dropped_only_no_param_result_side_caller_from_row(
row : DaeOriginalResultRemovalPruneRow,
) -> Bool {
row.param_count > 0 &&
row.self_call_count == 0 &&
row.call_count > 0 &&
row.nonself_undropped_call_count == 0 &&
row.escaped_dead_suffix_call_count == 0 &&
row.dead_suffix_result_other_call &&
row.dead_suffix_dropped_calls_from_no_param_result_caller > 0
}
///|
fn dae_original_result_removal_prune_evidence_from_row(
row : DaeOriginalResultRemovalPruneRow,
) -> DaeOriginalResultRemovalPruneEvidence {
let dropped_self_result_with_void_side_callers = dae_should_prune_dropped_self_result_with_void_side_callers_from_row(
row,
)
let later_mutual_dropped_result = dae_should_prune_later_mutual_dropped_result_from_row(
row,
)
let mixed_dropped_undropped_caller = dae_should_prune_mixed_dropped_undropped_caller_from_row(
row,
)
let dropped_only_private_result_side_caller = dae_should_prune_dropped_only_private_result_side_caller_from_row(
row,
)
let dropped_only_no_param_result_side_caller = dae_should_prune_dropped_only_no_param_result_side_caller_from_row(
row,
)
{
dropped_self_result_with_void_side_callers,
later_mutual_dropped_result,
mixed_dropped_undropped_caller,
dropped_only_private_result_side_caller,
dropped_only_no_param_result_side_caller,
params_absent_result_cleanup_prune: later_mutual_dropped_result ||
mixed_dropped_undropped_caller ||
dropped_only_private_result_side_caller,
uncalled_after_param_cleanup_prune: dropped_self_result_with_void_side_callers ||
later_mutual_dropped_result ||
mixed_dropped_undropped_caller ||
dropped_only_no_param_result_side_caller,
}
}
///|
fn dae_should_prune_params_absent_result_cleanup(
evidence : DaeOriginalResultRemovalPruneEvidence,
) -> Bool {
evidence.params_absent_result_cleanup_prune &&
(
evidence.later_mutual_dropped_result ||
evidence.mixed_dropped_undropped_caller ||
evidence.dropped_only_private_result_side_caller
)
}
///|
fn dae_should_block_uncalled_after_param_cleanup_from_prune_evidence(
evidence : DaeOriginalResultRemovalPruneEvidence,
) -> Bool {
evidence.uncalled_after_param_cleanup_prune &&
(
evidence.dropped_self_result_with_void_side_callers ||
evidence.later_mutual_dropped_result ||
evidence.mixed_dropped_undropped_caller ||
evidence.dropped_only_no_param_result_side_caller
)
}
///|
fn dae_original_int_fact_at(values : Array[Int32], idx : Int32) -> Int32 {
if idx >= 0 && idx < values.length() {
values[idx]
} else {
0
}
}
///|
fn dae_original_bool_fact_at(values : Array[Bool], idx : Int32) -> Bool {
if idx >= 0 && idx < values.length() {
values[idx]
} else {
false
}
}
///|
fn dae_should_preserve_parameterized_undropped_self_side_chain_evidence(
evidence : DaeOriginalParameterizedUndroppedSelfSideChainEvidence,
) -> Bool {
evidence.param_count > 0 &&
evidence.self_call_count > 0 &&
evidence.call_count > evidence.self_call_count &&
evidence.active_parameterized_nonself_call_count > 0 &&
evidence.dead_suffix_undropped_self_call &&
evidence.dead_suffix_result_other_call &&
evidence.escaped_dead_suffix_call_count == 0
}
///|
fn dae_original_no_param_dropped_caller_guard_evidence(
def_idx : Int32,
original_param_counts : Array[Int32],
original_call_counts : Array[Int32],
original_active_nonself_call_counts : Array[Int32],
original_active_parameterized_nonself_call_counts : Array[Int32],
original_nonself_undropped_call_counts : Array[Int32],
original_self_call_counts : Array[Int32],
original_escaped_dead_suffix_call_counts : Array[Int32],
original_dead_suffix_undropped_self_calls : Array[Bool],
original_dead_suffix_undropped_self_call_param_operands : Array[Bool],
original_dead_suffix_result_other_calls : Array[Bool],
original_dead_suffix_mutual_calls : Array[Bool],
) -> DaeOriginalNoParamDroppedCallerGuardEvidence {
{
param_count: dae_original_int_fact_at(original_param_counts, def_idx),
call_count: dae_original_int_fact_at(original_call_counts, def_idx),
active_nonself_call_count: dae_original_int_fact_at(
original_active_nonself_call_counts, def_idx,
),
active_parameterized_nonself_call_count: dae_original_int_fact_at(
original_active_parameterized_nonself_call_counts, def_idx,
),
nonself_undropped_call_count: dae_original_int_fact_at(
original_nonself_undropped_call_counts, def_idx,
),
self_call_count: dae_original_int_fact_at(
original_self_call_counts, def_idx,
),
escaped_dead_suffix_call_count: dae_original_int_fact_at(
original_escaped_dead_suffix_call_counts, def_idx,
),
dead_suffix_undropped_self_call: dae_original_bool_fact_at(
original_dead_suffix_undropped_self_calls, def_idx,
),
dead_suffix_undropped_self_call_param_operand: dae_original_bool_fact_at(
original_dead_suffix_undropped_self_call_param_operands, def_idx,
),
dead_suffix_result_other_call: dae_original_bool_fact_at(
original_dead_suffix_result_other_calls, def_idx,
),
dead_suffix_mutual_call: dae_original_bool_fact_at(
original_dead_suffix_mutual_calls, def_idx,
),
}
}
///|
fn dae_should_preserve_undropped_self_result_from_no_param_dropped_caller_evidence(
evidence : DaeOriginalNoParamDroppedCallerGuardEvidence,
) -> Bool {
evidence.param_count > 0 &&
evidence.self_call_count > 0 &&
evidence.call_count > evidence.self_call_count &&
evidence.dead_suffix_undropped_self_call &&
evidence.dead_suffix_undropped_self_call_param_operand &&
evidence.dead_suffix_result_other_call &&
evidence.active_nonself_call_count > 0 &&
evidence.active_parameterized_nonself_call_count == 0 &&
evidence.nonself_undropped_call_count == 0 &&
evidence.escaped_dead_suffix_call_count == 0 &&
!evidence.dead_suffix_mutual_call
}
///|
fn dae_should_preserve_undropped_self_result_from_no_param_dropped_caller(
def_idx : Int32,
original_param_counts : Array[Int32],
original_call_counts : Array[Int32],
original_active_nonself_call_counts : Array[Int32],
original_active_parameterized_nonself_call_counts : Array[Int32],
original_nonself_undropped_call_counts : Array[Int32],
original_self_call_counts : Array[Int32],
original_escaped_dead_suffix_call_counts : Array[Int32],
original_dead_suffix_undropped_self_calls : Array[Bool],
original_dead_suffix_undropped_self_call_param_operands : Array[Bool],
original_dead_suffix_result_other_calls : Array[Bool],
original_dead_suffix_mutual_calls : Array[Bool],
) -> Bool {
dae_should_preserve_undropped_self_result_from_no_param_dropped_caller_evidence(
dae_original_no_param_dropped_caller_guard_evidence(
def_idx, original_param_counts, original_call_counts, original_active_nonself_call_counts,
original_active_parameterized_nonself_call_counts, original_nonself_undropped_call_counts,
original_self_call_counts, original_escaped_dead_suffix_call_counts, original_dead_suffix_undropped_self_calls,
original_dead_suffix_undropped_self_call_param_operands, original_dead_suffix_result_other_calls,
original_dead_suffix_mutual_calls,
),
)
}
///|
fn dae_original_result_preservation_context(
original_param_counts : Array[Int32],
original_call_counts : Array[Int32],
original_active_nonself_call_counts : Array[Int32],
original_active_parameterized_nonself_call_counts : Array[Int32],
original_nonself_undropped_call_counts : Array[Int32],
original_self_call_counts : Array[Int32],
original_private_result_dead_suffix_calls_from_escaped_operand_callees : Array[
Int32,
],
original_escaped_dead_suffix_call_counts : Array[Int32],
original_dead_suffix_undropped_self_calls : Array[Bool],
original_dead_suffix_dropped_self_calls : Array[Bool],
original_dead_suffix_block_undropped_self_calls : Array[Bool],
original_dead_suffix_result_calls_from_undropped_self_callers : Array[Int32],
original_dead_suffix_result_calls_from_no_param_undropped_self_callers : Array[
Int32,
],
original_dead_suffix_same_self_result_operand_calls : Array[Bool],
original_dead_suffix_undropped_self_call_param_operands : Array[Bool],
original_dead_suffix_result_other_calls : Array[Bool],
original_dead_suffix_dropped_escaped_result_calls : Array[Bool],
original_dead_suffix_mutual_calls : Array[Bool],
original_dead_suffix_calls_from_param_operand_self_result_callers : Array[
Int32,
],
original_dead_suffix_escaped_result_operand_call_counts : Array[Int32],
original_dead_suffix_dropped_calls_from_parameterized_self_callers : Array[
Int32,
],
) -> DaeOriginalResultPreservationContext {
{
original_param_counts,
original_call_counts,
original_active_nonself_call_counts,
original_active_parameterized_nonself_call_counts,
original_nonself_undropped_call_counts,
original_self_call_counts,
original_private_result_dead_suffix_calls_from_escaped_operand_callees,
original_escaped_dead_suffix_call_counts,
original_dead_suffix_undropped_self_calls,
original_dead_suffix_dropped_self_calls,
original_dead_suffix_block_undropped_self_calls,
original_dead_suffix_result_calls_from_undropped_self_callers,
original_dead_suffix_result_calls_from_no_param_undropped_self_callers,
original_dead_suffix_same_self_result_operand_calls,
original_dead_suffix_undropped_self_call_param_operands,
original_dead_suffix_result_other_calls,
original_dead_suffix_dropped_escaped_result_calls,
original_dead_suffix_mutual_calls,
original_dead_suffix_calls_from_param_operand_self_result_callers,
original_dead_suffix_escaped_result_operand_call_counts,
original_dead_suffix_dropped_calls_from_parameterized_self_callers,
}
}
///|
fn dae_original_result_boundary_context(
preservation : DaeOriginalResultPreservationContext,
original_private_result_escaped_operand_call_counts : Array[Int32],
original_dead_suffix_undropped_escaped_result_calls : Array[Bool],
original_dead_suffix_other_calls : Array[Bool],
original_dead_suffix_self_recursive_void_side_calls : Array[Bool],
original_dead_suffix_self_recursive_result_side_calls : Array[Bool],
original_active_parameterized_result_nonself_call_counts : Array[Int32],
original_dead_suffix_dropped_self_call_full_param_forwards : Array[Bool],
original_dead_suffix_dropped_earlier_result_calls : Array[Bool],
original_later_dead_suffix_dropped_call_counts : Array[Int32],
original_private_result_dead_suffix_call_counts : Array[Int32],
original_dead_suffix_dropped_calls_from_no_param_result_callers : Array[Int32],
original_dead_suffix_mixed_dropped_undropped_call_counts : Array[Int32],
) -> DaeOriginalResultBoundaryContext {
{
preservation,
original_private_result_escaped_operand_call_counts,
original_dead_suffix_undropped_escaped_result_calls,
original_dead_suffix_other_calls,
original_dead_suffix_self_recursive_void_side_calls,
original_dead_suffix_self_recursive_result_side_calls,
original_active_parameterized_result_nonself_call_counts,
original_dead_suffix_dropped_self_call_full_param_forwards,
original_dead_suffix_dropped_earlier_result_calls,
original_later_dead_suffix_dropped_call_counts,
original_private_result_dead_suffix_call_counts,
original_dead_suffix_dropped_calls_from_no_param_result_callers,
original_dead_suffix_mixed_dropped_undropped_call_counts,
}
}
///|
fn dae_original_result_preservation_row_from_context(
def_idx : Int32,
context : DaeOriginalResultPreservationContext,
) -> DaeOriginalResultPreservationRow {
{
param_count: dae_original_int_fact_at(
context.original_param_counts,
def_idx,
),
call_count: dae_original_int_fact_at(context.original_call_counts, def_idx),
active_nonself_call_count: dae_original_int_fact_at(
context.original_active_nonself_call_counts,
def_idx,
),
active_parameterized_nonself_call_count: dae_original_int_fact_at(
context.original_active_parameterized_nonself_call_counts,
def_idx,
),
nonself_undropped_call_count: dae_original_int_fact_at(
context.original_nonself_undropped_call_counts,
def_idx,
),
self_call_count: dae_original_int_fact_at(
context.original_self_call_counts,
def_idx,
),
private_result_dead_suffix_calls_from_escaped_operand_callees: dae_original_int_fact_at(
context.original_private_result_dead_suffix_calls_from_escaped_operand_callees,
def_idx,
),
escaped_dead_suffix_call_count: dae_original_int_fact_at(
context.original_escaped_dead_suffix_call_counts,
def_idx,
),
dead_suffix_undropped_self_call: dae_original_bool_fact_at(
context.original_dead_suffix_undropped_self_calls,
def_idx,
),
dead_suffix_dropped_self_call: dae_original_bool_fact_at(
context.original_dead_suffix_dropped_self_calls,
def_idx,
),
dead_suffix_block_undropped_self_call: dae_original_bool_fact_at(
context.original_dead_suffix_block_undropped_self_calls,
def_idx,
),
dead_suffix_result_calls_from_undropped_self_caller: dae_original_int_fact_at(
context.original_dead_suffix_result_calls_from_undropped_self_callers,
def_idx,
),
dead_suffix_result_calls_from_no_param_undropped_self_caller: dae_original_int_fact_at(
context.original_dead_suffix_result_calls_from_no_param_undropped_self_callers,
def_idx,
),
dead_suffix_same_self_result_operand_call: dae_original_bool_fact_at(
context.original_dead_suffix_same_self_result_operand_calls,
def_idx,
),
dead_suffix_undropped_self_call_param_operand: dae_original_bool_fact_at(
context.original_dead_suffix_undropped_self_call_param_operands,
def_idx,
),
dead_suffix_result_other_call: dae_original_bool_fact_at(
context.original_dead_suffix_result_other_calls,
def_idx,
),
dead_suffix_dropped_escaped_result_call: dae_original_bool_fact_at(
context.original_dead_suffix_dropped_escaped_result_calls,
def_idx,
),
dead_suffix_mutual_call: dae_original_bool_fact_at(
context.original_dead_suffix_mutual_calls,
def_idx,
),
dead_suffix_calls_from_param_operand_self_result_caller: dae_original_int_fact_at(
context.original_dead_suffix_calls_from_param_operand_self_result_callers,
def_idx,
),
dead_suffix_escaped_result_operand_call_count: dae_original_int_fact_at(
context.original_dead_suffix_escaped_result_operand_call_counts,
def_idx,
),
dead_suffix_dropped_calls_from_parameterized_self_caller: dae_original_int_fact_at(
context.original_dead_suffix_dropped_calls_from_parameterized_self_callers,
def_idx,
),
}
}
///|
fn dae_original_result_boundary_row_from_context(
def_idx : Int32,
context : DaeOriginalResultBoundaryContext,
) -> DaeOriginalResultBoundaryRow {
let preservation = dae_original_result_preservation_row_from_context(
def_idx,
context.preservation,
)
{
preservation,
escape: {
private_result_escaped_operand_call_count: dae_original_int_fact_at(
context.original_private_result_escaped_operand_call_counts,
def_idx,
),
dead_suffix_undropped_escaped_result_call: dae_original_bool_fact_at(
context.original_dead_suffix_undropped_escaped_result_calls,
def_idx,
),
private_result_dead_suffix_calls_from_escaped_operand_callees: preservation.private_result_dead_suffix_calls_from_escaped_operand_callees,
escaped_dead_suffix_call_count: preservation.escaped_dead_suffix_call_count,
dead_suffix_result_calls_from_undropped_self_caller: preservation.dead_suffix_result_calls_from_undropped_self_caller,
dead_suffix_result_calls_from_no_param_undropped_self_caller: preservation.dead_suffix_result_calls_from_no_param_undropped_self_caller,
dead_suffix_calls_from_param_operand_self_result_caller: preservation.dead_suffix_calls_from_param_operand_self_result_caller,
dead_suffix_escaped_result_operand_call_count: preservation.dead_suffix_escaped_result_operand_call_count,
},
side_call: {
dead_suffix_other_call: dae_original_bool_fact_at(
context.original_dead_suffix_other_calls,
def_idx,
),
dead_suffix_result_other_call: preservation.dead_suffix_result_other_call,
dead_suffix_self_recursive_void_side_call: dae_original_bool_fact_at(
context.original_dead_suffix_self_recursive_void_side_calls,
def_idx,
),
dead_suffix_self_recursive_result_side_call: dae_original_bool_fact_at(
context.original_dead_suffix_self_recursive_result_side_calls,
def_idx,
),
dead_suffix_mutual_call: preservation.dead_suffix_mutual_call,
},
prune: dae_original_result_removal_prune_row_from_preservation_row(
preservation,
dae_original_int_fact_at(
context.original_active_parameterized_result_nonself_call_counts,
def_idx,
),
dae_original_bool_fact_at(
context.original_dead_suffix_dropped_self_call_full_param_forwards,
def_idx,
),
dae_original_bool_fact_at(
context.original_dead_suffix_dropped_earlier_result_calls,
def_idx,
),
dae_original_int_fact_at(
context.original_later_dead_suffix_dropped_call_counts,
def_idx,
),
dae_original_int_fact_at(
context.original_private_result_dead_suffix_call_counts,
def_idx,
),
dae_original_int_fact_at(
context.original_dead_suffix_dropped_calls_from_no_param_result_callers,
def_idx,
),
dae_original_int_fact_at(
context.original_dead_suffix_mixed_dropped_undropped_call_counts,
def_idx,
),
),
}
}
///|
fn dae_original_no_param_dropped_caller_guard_evidence_from_row(
row : DaeOriginalResultPreservationRow,
) -> DaeOriginalNoParamDroppedCallerGuardEvidence {
{
param_count: row.param_count,
call_count: row.call_count,
active_nonself_call_count: row.active_nonself_call_count,
active_parameterized_nonself_call_count: row.active_parameterized_nonself_call_count,
nonself_undropped_call_count: row.nonself_undropped_call_count,
self_call_count: row.self_call_count,
escaped_dead_suffix_call_count: row.escaped_dead_suffix_call_count,
dead_suffix_undropped_self_call: row.dead_suffix_undropped_self_call,
dead_suffix_undropped_self_call_param_operand: row.dead_suffix_undropped_self_call_param_operand,
dead_suffix_result_other_call: row.dead_suffix_result_other_call,
dead_suffix_mutual_call: row.dead_suffix_mutual_call,
}
}
///|
fn dae_original_parameterized_undropped_self_side_chain_evidence_from_row(
row : DaeOriginalResultPreservationRow,
) -> DaeOriginalParameterizedUndroppedSelfSideChainEvidence {
{
param_count: row.param_count,
call_count: row.call_count,
active_parameterized_nonself_call_count: row.active_parameterized_nonself_call_count,
self_call_count: row.self_call_count,
escaped_dead_suffix_call_count: row.escaped_dead_suffix_call_count,
dead_suffix_undropped_self_call: row.dead_suffix_undropped_self_call,
dead_suffix_result_other_call: row.dead_suffix_result_other_call,
}
}
///|
fn dae_original_result_preservation_evidence_from_row(
row : DaeOriginalResultPreservationRow,
) -> DaeOriginalResultPreservationEvidence {
let same_self_result_operand = row.param_count > 0 &&
row.self_call_count > 0 &&
row.call_count >= row.self_call_count &&
row.dead_suffix_same_self_result_operand_call &&
row.nonself_undropped_call_count > 0
let result_from_undropped_self_caller = row.param_count > 0 &&
row.self_call_count == 0 &&
row.dead_suffix_result_calls_from_undropped_self_caller > 0 &&
row.dead_suffix_result_calls_from_no_param_undropped_self_caller == 0 &&
row.escaped_dead_suffix_call_count == 0 &&
row.active_nonself_call_count > 0
let escaped_result_operand_chain = (
row.param_count > 0 &&
row.dead_suffix_escaped_result_operand_call_count > 0
) ||
(
row.param_count == 0 &&
row.private_result_dead_suffix_calls_from_escaped_operand_callees > 0
)
let mixed_self_result = row.param_count > 0 &&
row.dead_suffix_undropped_self_call &&
row.dead_suffix_dropped_self_call &&
row.escaped_dead_suffix_call_count == 0
let escaped_block_self_result = row.param_count > 0 &&
row.dead_suffix_undropped_self_call &&
row.dead_suffix_dropped_self_call &&
row.dead_suffix_block_undropped_self_call &&
row.escaped_dead_suffix_call_count > 0
let param_operand_self_result_side_callee = row.dead_suffix_calls_from_param_operand_self_result_caller >
0
let undropped_self_result_from_no_param_dropped_caller = dae_should_preserve_undropped_self_result_from_no_param_dropped_caller_evidence(
dae_original_no_param_dropped_caller_guard_evidence_from_row(row),
)
let dropped_escaped_result_under_self_caller = row.param_count > 0 &&
row.self_call_count == 0 &&
row.dead_suffix_dropped_escaped_result_call &&
row.dead_suffix_dropped_calls_from_parameterized_self_caller > 0 &&
row.escaped_dead_suffix_call_count == 0
let parameterized_undropped_self_side_chain = dae_should_preserve_parameterized_undropped_self_side_chain_evidence(
dae_original_parameterized_undropped_self_side_chain_evidence_from_row(row),
)
let result_side_chain_cleanup_preservation = undropped_self_result_from_no_param_dropped_caller ||
dropped_escaped_result_under_self_caller ||
parameterized_undropped_self_side_chain
{
same_self_result_operand,
result_from_undropped_self_caller,
escaped_result_operand_chain,
mixed_self_result,
escaped_block_self_result,
param_operand_self_result_side_callee,
undropped_self_result_from_no_param_dropped_caller,
dropped_escaped_result_under_self_caller,
parameterized_undropped_self_side_chain,
result_side_chain_cleanup_preservation,
}
}
///|
fn dae_original_result_cleanup_preservation_evidence(
row : DaeOriginalResultRemovalPruneRow,
preservation : DaeOriginalResultPreservationEvidence,
) -> DaeOriginalResultCleanupPreservationEvidence {
{
mixed_self_suffix: dae_should_preserve_mixed_self_suffix_from_row(row),
result_side_chain_cleanup_preservation: preservation.result_side_chain_cleanup_preservation,
}
}
///|
fn dae_original_result_cleanup_preservation_any(
evidence : DaeOriginalResultCleanupPreservationEvidence,
) -> Bool {
evidence.mixed_self_suffix || evidence.result_side_chain_cleanup_preservation
}
///|
fn dae_original_result_boundary_evidence_from_row(
row : DaeOriginalResultBoundaryRow,
) -> DaeOriginalResultBoundaryEvidence {
let preservation = dae_original_result_preservation_evidence_from_row(
row.preservation,
)
{
preservation,
cleanup_preservation: dae_original_result_cleanup_preservation_evidence(
row.prune,
preservation,
),
prune: dae_original_result_removal_prune_evidence_from_row(row.prune),
}
}
///|
fn dae_original_result_preservation_evidence_from_context(
def_idx : Int32,
context : DaeOriginalResultPreservationContext,
) -> DaeOriginalResultPreservationEvidence {
dae_original_result_preservation_evidence_from_row(
dae_original_result_preservation_row_from_context(def_idx, context),
)
}
///|
fn dae_original_result_preservation_any(
evidence : DaeOriginalResultPreservationEvidence,
) -> Bool {
evidence.same_self_result_operand ||
evidence.result_from_undropped_self_caller ||
evidence.escaped_result_operand_chain ||
evidence.mixed_self_result ||
evidence.escaped_block_self_result ||
evidence.param_operand_self_result_side_callee ||
evidence.undropped_self_result_from_no_param_dropped_caller ||
evidence.dropped_escaped_result_under_self_caller ||
evidence.parameterized_undropped_self_side_chain
}
///|
fn dae_original_result_preservation_blocks_argument_side_result_repair(
evidence : DaeOriginalResultPreservationEvidence,
preserve_parameterized_side_chain : Bool,
) -> Bool {
evidence.same_self_result_operand ||
evidence.result_from_undropped_self_caller ||
evidence.escaped_result_operand_chain ||
evidence.mixed_self_result ||
evidence.escaped_block_self_result ||
evidence.undropped_self_result_from_no_param_dropped_caller ||
evidence.dropped_escaped_result_under_self_caller ||
(
preserve_parameterized_side_chain &&
evidence.parameterized_undropped_self_side_chain
)
}
///|
fn dae_collect_original_dead_suffix_calls_from_param_operand_self_result_callers(
mod_ : @lib.Module,
original_param_counts : Array[Int32],
original_call_counts : Array[Int32],
original_active_nonself_call_counts : Array[Int32],
original_active_parameterized_nonself_call_counts : Array[Int32],
original_nonself_undropped_call_counts : Array[Int32],
original_self_call_counts : Array[Int32],
original_escaped_dead_suffix_call_counts : Array[Int32],
original_dead_suffix_undropped_self_calls : Array[Bool],
original_dead_suffix_undropped_self_call_param_operands : Array[Bool],
original_dead_suffix_result_other_calls : Array[Bool],
original_dead_suffix_mutual_calls : Array[Bool],
first_roots~ : Array[Int32]? = None,
) -> Array[Int32] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return Array::make(defined, 0)
}
let counts = Array::make(defined, 0)
let mut caller_def = 0
while caller_def < funcs.length() {
if dae_should_preserve_undropped_self_result_from_no_param_dropped_caller(
caller_def, original_param_counts, original_call_counts, original_active_nonself_call_counts,
original_active_parameterized_nonself_call_counts, original_nonself_undropped_call_counts,
original_self_call_counts, original_escaped_dead_suffix_call_counts, original_dead_suffix_undropped_self_calls,
original_dead_suffix_undropped_self_call_param_operands, original_dead_suffix_result_other_calls,
original_dead_suffix_mutual_calls,
) {
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
let mut callee_def = 0
while callee_def < defined {
if callee_def != caller_def {
counts[callee_def] = counts[callee_def] +
dae_count_dead_suffix_direct_call_to(
body,
imported + callee_def,
known_first_root=dae_original_first_root_index(
body, first_roots, caller_def,
),
)
}
callee_def += 1
}
}
caller_def += 1
}
counts
}
///|
fn dae_try_process_core_work_item(
original : DaeOriginalBoundarySnapshot,
current : @lib.Module,
graph : DaeCurrentBoundaryGraph,
target_def : Int32,
family : DaeWorkFamily,
dependencies~ : DaeBoundaryDependencyGraph? = None,
apply_followups~ : Bool = true,
numeric_result_validation_chain~ : DaeScalarValidationChain? = None,
numeric_result_validation_work~ : DaeScalarValidationWork? = None,
) -> DaeCoreResultApplication? {
let active_dependencies = if family == DaeForwardingWork {
match dependencies {
Some(dependencies) => dependencies
None => dae_boundary_dependency_graph_new(graph)
}
} else {
dae_boundary_dependency_graph_empty()
}
let item = if family == DaeForwardingWork {
dae_work_item_new(graph, active_dependencies, target_def, family)
} else {
dae_nonforwarding_work_item_new(graph, target_def, family)
}
let diagnostics = dae_diagnostics_new()
match
dae_process_work_item(
original,
current,
[],
false,
graph,
active_dependencies,
item,
diagnostics,
numeric_result_validation_chain~,
numeric_result_validation_work~,
) {
DaeWorkCommitted(application) =>
Some(
if apply_followups {
dae_apply_commit_followups(original, application, item, diagnostics)
} else {
application
},
)
_ => None
}
}
///|
fn dae_core_pass_result_from_work_application(
application : DaeCoreResultApplication,
producer_effects_report : Array[DaeCommitEffects],
committed_application_report~ : Array[DaeCoreResultApplication] = [],
) -> DaePassResult {
if committed_application_report.length() == 0 {
committed_application_report.push(application)
} else {
committed_application_report[0] = application
}
if producer_effects_report.length() == 0 {
producer_effects_report.push(application.effects)
} else {
producer_effects_report[0] = application.effects
}
{
mod_out: application.mod_out,
touched: application.touched,
changed: application.changed,
primary_def: application.primary_def,
}
}
///|
fn dae_should_run_scalar_parameter_candidate(
_defined : Int32,
has_direct_caller_cycle~ : Bool,
) -> Bool {
let _ = has_direct_caller_cycle
true
}
///|
fn dae_should_run_broad_uniform_parameter_flow(
_defined : Int32,
optimize~ : Bool,
) -> Bool {
let _ = optimize
false
}
///|
fn dae_should_run_reverse_exact_literal_phase(
_defined : Int32,
optimize~ : Bool,
) -> Bool {
optimize
}
///|
fn dae_should_run_computed_unread_dependency_worklist(
_defined : Int32,
optimize~ : Bool,
) -> Bool {
optimize
}
///|
fn dae_should_run_forwarded_constant_revisit(
_defined : Int32,
optimize~ : Bool,
) -> Bool {
optimize
}
///|
fn dae_should_run_low_wrapper_callee_phase(optimize~ : Bool) -> Bool {
optimize
}
///|
fn dae_should_run_fact_dropped_result_revisit(optimize~ : Bool) -> Bool {
optimize
}
///|
fn dae_candidate_worklist_requires_facts(candidate_defs : Array[Int32]) -> Bool {
candidate_defs.length() > 0
}
///|
fn dae_should_collect_phase_local_uniform_constants(
_defined : Int32,
optimize~ : Bool,
allow_written_uniform_const~ : Bool,
self_calls_anywhere~ : Int32,
has_collect_const_param~ : Bool,
has_direct_caller_cycle~ : Bool,
) -> Bool {
(optimize || allow_written_uniform_const) &&
self_calls_anywhere == 0 &&
has_collect_const_param &&
!has_direct_caller_cycle
}
///|
fn dae_should_scan_unobserved_shape(
current_param_count : Int32,
current_result_count : Int32,
current_calls_anywhere : Int32,
) -> Bool {
(current_param_count > 0 || current_result_count > 0) &&
current_calls_anywhere > 0
}
///|
fn dae_run_core_once(
mod_ : @lib.Module,
already_rewritten_defs : Array[Bool],
current_call_counts_anywhere : Array[Int32],
optimize : Bool,
skip_param_removal : Bool,
original : DaeOriginalBoundarySnapshot,
current_graph : DaeCurrentBoundaryGraph,
expected_call_epoch~ : Int32 = -1,
max_def_exclusive~ : Int32 = -1,
start_def_inclusive~ : Int32 = 0,
boundary_facts_override~ : DaeCallBoundaryFacts? = None,
dependencies~ : DaeBoundaryDependencyGraph? = None,
producer_effects_report~ : Array[DaeCommitEffects] = [],
liveness_work~ : Array[Int32]? = None,
committed_application_report~ : Array[DaeCoreResultApplication] = [],
checked_candidate_report~ : Array[(@lib.Module, @lib.Module)]? = None,
scalar_validation_chain~ : DaeScalarValidationChain? = None,
scalar_validation_work~ : DaeScalarValidationWork? = None,
numeric_result_validation_work~ : DaeScalarValidationWork? = None,
scalar_plan_code_snapshot_current~ : Bool = true,
has_current_dropped_result_candidate~ : Bool = true,
traps_never_happen~ : Bool = false,
) -> DaePassResult {
let original_param_counts = original.param_counts
let original_large_unreachable_prefixes = original.large_unreachable_prefixes
let original_call_counts = original.call_summary.call_counts
let original_prior_call_counts = original.call_summary.prior_call_counts
let original_prior_undropped_call_counts = original.call_summary.prior_undropped_call_counts
let original_active_nonself_call_counts = original.call_summary.active_nonself_call_counts
let original_active_parameterized_nonself_call_counts = original.call_summary.active_parameterized_nonself_call_counts
let original_active_parameterized_result_nonself_call_counts = original.call_summary.active_parameterized_result_nonself_call_counts
let original_nonself_undropped_call_counts = original.call_summary.nonself_undropped_call_counts
let original_self_call_counts = original.call_summary.self_call_counts
let original_private_result_dead_suffix_call_counts = original.private_result_dead_suffix_call_counts
let original_private_result_same_operand_call_counts = original.private_result_same_operand_call_counts
let original_escaped_result_operand_call_facts = original.escaped_result_operand_call_facts
let original_private_result_escaped_operand_call_counts = original_escaped_result_operand_call_facts.private_result_escaped_operand_call_counts
let original_dead_suffix_escaped_result_operand_call_counts = original_escaped_result_operand_call_facts.dead_suffix_escaped_result_operand_call_counts
let original_private_result_dead_suffix_calls_from_escaped_operand_callees = original.private_result_dead_suffix_calls_from_escaped_operand_callees
let original_escaped_dead_suffix_call_counts = original.escaped_dead_suffix_call_counts
let original_escaped_undropped_dead_suffix_call_counts = original.escaped_undropped_dead_suffix_call_counts
let original_dead_suffix_other_calls = original.dead_suffix_other_calls
let original_dead_suffix_undropped_self_calls = original.dead_suffix_undropped_self_calls
let original_dead_suffix_dropped_self_calls = original.dead_suffix_dropped_self_calls
let original_dead_suffix_block_undropped_self_calls = original.dead_suffix_block_undropped_self_calls
let original_dead_suffix_dropped_self_call_full_param_forwards = original.dead_suffix_dropped_self_call_full_param_forwards
let original_dead_suffix_result_calls_from_undropped_self_callers = original.dead_suffix_result_calls_from_undropped_self_callers
let original_dead_suffix_result_calls_from_no_param_undropped_self_callers = original.dead_suffix_result_calls_from_no_param_undropped_self_callers
let original_dead_suffix_undropped_calls_from_parameterized_dropped_self_callers = original.dead_suffix_undropped_calls_from_parameterized_dropped_self_callers
let original_dead_suffix_dropped_calls_from_parameterized_self_callers = original.dead_suffix_dropped_calls_from_parameterized_self_callers
let original_dead_suffix_dropped_calls_from_no_param_result_callers = original.dead_suffix_dropped_calls_from_no_param_result_callers
let original_dead_suffix_mixed_dropped_undropped_call_counts = original.dead_suffix_mixed_dropped_undropped_call_counts
let original_dead_suffix_dropped_earlier_result_calls = original.dead_suffix_dropped_earlier_result_calls
let original_later_dead_suffix_dropped_call_counts = original.later_dead_suffix_dropped_call_counts
let original_dead_suffix_same_self_result_operand_calls = original.dead_suffix_same_self_result_operand_calls
let original_dead_suffix_undropped_self_call_param_operands = original.dead_suffix_undropped_self_call_param_operands
let original_dead_suffix_calls_from_param_operand_self_result_callers = original.dead_suffix_calls_from_param_operand_self_result_callers
let original_dead_suffix_result_other_calls = original.dead_suffix_result_other_calls
let original_dead_suffix_self_recursive_void_side_calls = original.dead_suffix_self_recursive_void_side_calls
let original_dead_suffix_self_recursive_result_side_calls = original.dead_suffix_self_recursive_result_side_calls
let original_dead_suffix_escaped_result_call_facts = original.dead_suffix_escaped_result_call_facts
let original_dead_suffix_undropped_escaped_result_calls = original_dead_suffix_escaped_result_call_facts.undropped_calls
let original_dead_suffix_dropped_escaped_result_calls = original_dead_suffix_escaped_result_call_facts.dropped_calls
let original_dead_suffix_mutual_calls = original.dead_suffix_mutual_calls
let boundary_snapshot = current_graph.snapshot
let imported = boundary_snapshot.imported
let defined = boundary_snapshot.defined
let boundary_facts = dae_boundary_facts_for_epoch(
current_graph, expected_call_epoch, boundary_facts_override,
)
let active_dependencies = match dependencies {
Some(dependencies) => dependencies
None => dae_boundary_dependency_graph_new(current_graph)
}
let shared_boundary_facts : DaeBoundaryFacts = {
original,
graph: current_graph,
incoming_param_live_by_def: [],
incoming_param_live_body_epochs: [],
}
let function_infos = boundary_snapshot.function_infos
let direct_calls = boundary_facts.direct_calls
let direct_callers = boundary_facts.direct_callers
let type_indices = boundary_snapshot.type_indices
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let original_result_preservation_context = dae_original_result_preservation_context(
original_param_counts, original_call_counts, original_active_nonself_call_counts,
original_active_parameterized_nonself_call_counts, original_nonself_undropped_call_counts,
original_self_call_counts, original_private_result_dead_suffix_calls_from_escaped_operand_callees,
original_escaped_dead_suffix_call_counts, original_dead_suffix_undropped_self_calls,
original_dead_suffix_dropped_self_calls, original_dead_suffix_block_undropped_self_calls,
original_dead_suffix_result_calls_from_undropped_self_callers, original_dead_suffix_result_calls_from_no_param_undropped_self_callers,
original_dead_suffix_same_self_result_operand_calls, original_dead_suffix_undropped_self_call_param_operands,
original_dead_suffix_result_other_calls, original_dead_suffix_dropped_escaped_result_calls,
original_dead_suffix_mutual_calls, original_dead_suffix_calls_from_param_operand_self_result_callers,
original_dead_suffix_escaped_result_operand_call_counts, original_dead_suffix_dropped_calls_from_parameterized_self_callers,
)
let original_result_boundary_context = dae_original_result_boundary_context(
original_result_preservation_context, original_private_result_escaped_operand_call_counts,
original_dead_suffix_undropped_escaped_result_calls, original_dead_suffix_other_calls,
original_dead_suffix_self_recursive_void_side_calls, original_dead_suffix_self_recursive_result_side_calls,
original_active_parameterized_result_nonself_call_counts, original_dead_suffix_dropped_self_call_full_param_forwards,
original_dead_suffix_dropped_earlier_result_calls, original_later_dead_suffix_dropped_call_counts,
original_private_result_dead_suffix_call_counts, original_dead_suffix_dropped_calls_from_no_param_result_callers,
original_dead_suffix_mixed_dropped_undropped_call_counts,
)
let scan_limit = if max_def_exclusive >= 0 && max_def_exclusive < defined {
max_def_exclusive
} else {
defined
}
let mut def_idx = if start_def_inclusive > 0 {
start_def_inclusive
} else {
0
}
while def_idx < scan_limit {
let callee_abs = imported + def_idx
let candidate_epoch = dae_boundary_candidate_epoch(current_graph, def_idx)
if !dae_candidate_epoch_is_current(current_graph, candidate_epoch) {
def_idx += 1
continue
}
if def_idx < already_rewritten_defs.length() &&
already_rewritten_defs[def_idx] {
def_idx += 1
continue
}
let boundary_candidate = match
dae_call_boundary_candidate(boundary_facts, callee_abs) {
Some(candidate) => candidate
None => {
def_idx += 1
continue
}
}
if !boundary_candidate.owned ||
!dae_can_change_function_boundary(shared_boundary_facts, callee_abs) {
def_idx += 1
continue
}
let (self_calls, self_calls_anywhere) = if callee_abs >= 0 &&
callee_abs < function_infos.length() {
dae_self_call_counts_from_function_info(
function_infos[callee_abs],
def_idx,
)
} else {
(
dae_count_self_calls(funcs[def_idx], callee_abs),
dae_count_self_calls_anywhere(funcs[def_idx], callee_abs),
)
}
let skip_original_large_result_cleanup = dae_should_skip_original_large_result_unreachable_cleanup(
original_large_unreachable_prefixes, def_idx, mod_, type_indices, callee_abs,
)
let original_result_boundary_row = dae_original_result_boundary_row_from_context(
def_idx, original_result_boundary_context,
)
let original_result_boundary_for_def = dae_original_result_boundary_evidence_from_row(
original_result_boundary_row,
)
let original_result_preservation_row = original_result_boundary_row.preservation
let original_result_removal_prune_row = original_result_boundary_row.prune
let original_result_preservation_for_def = original_result_boundary_for_def.preservation
let original_result_cleanup_preservation_for_def = original_result_boundary_for_def.cleanup_preservation
let original_result_removal_prune_for_def = original_result_boundary_for_def.prune
let original_result_escape_row = original_result_boundary_row.escape
let original_escaped_block_self_result_for_unobserved = original_result_preservation_for_def.escaped_block_self_result
let original_same_self_result_operand_for_unobserved = dae_should_preserve_same_self_result_operand_from_row(
original_result_removal_prune_row,
)
let (current_param_count, current_result_count) = match
dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(current_params, current_results)) =>
(current_params.length(), current_results.length())
None => (0, 0)
}
let @lib.Func(_, @lib.Expr(current_body)) = funcs[def_idx]
let current_calls_anywhere = dae_original_int_fact_at(
current_call_counts_anywhere, def_idx,
)
let unobserved_shape_candidate = if dae_should_scan_unobserved_shape(
current_param_count, current_result_count, current_calls_anywhere,
) {
dae_func_is_unreachable_only(
dae_simplify_to_unreachable_when_prefix_unobservable(funcs[def_idx]),
) &&
(
current_result_count == 0 ||
dae_dead_suffix_has_undropped_direct_call_to(current_body, callee_abs)
)
} else {
false
}
if unobserved_shape_candidate &&
boundary_candidate.direct_call_count == self_calls &&
!skip_original_large_result_cleanup &&
!dae_original_result_cleanup_preservation_any(
original_result_cleanup_preservation_for_def,
) &&
!original_escaped_block_self_result_for_unobserved &&
!original_same_self_result_operand_for_unobserved &&
(
original_result_escape_row.dead_suffix_result_calls_from_undropped_self_caller ==
0 ||
original_result_escape_row.dead_suffix_result_calls_from_no_param_undropped_self_caller >
0
) &&
original_result_escape_row.dead_suffix_calls_from_param_operand_self_result_caller ==
0 &&
original_result_escape_row.dead_suffix_escaped_result_operand_call_count ==
0 &&
original_result_escape_row.private_result_dead_suffix_calls_from_escaped_operand_callees ==
0 &&
original_result_escape_row.private_result_escaped_operand_call_count == 0 &&
!(original_result_escape_row.dead_suffix_undropped_escaped_result_call &&
original_result_escape_row.escaped_dead_suffix_call_count == 0) {
match
dae_specialized_result_facts_new(original, current_graph, callee_abs) {
Some(specialized_facts) => {
dae_scalar_validation_barrier_opt(scalar_validation_chain)
let unobserved = dae_try_remove_unobserved_unreachable_function_with_facts(
mod_, specialized_facts,
)
if unobserved.changed {
dae_record_producer_effects(
producer_effects_report,
dae_specialized_result_commit_effects(
specialized_facts, unobserved,
),
)
return unobserved
}
}
None => ()
}
}
if boundary_candidate.direct_call_count == 0 {
let original_result_side_call_row = original_result_boundary_row.side_call
let original_self_calls = original_result_preservation_row.self_call_count
let originally_had_params = original_result_preservation_row.param_count >
0
let original_call_count = original_result_preservation_row.call_count
let original_prior_calls = if def_idx <
original_prior_call_counts.length() {
original_prior_call_counts[def_idx]
} else {
0
}
let original_prior_undropped_calls = if def_idx <
original_prior_undropped_call_counts.length() {
original_prior_undropped_call_counts[def_idx]
} else {
0
}
let original_active_nonself_calls = original_result_preservation_row.active_nonself_call_count
let original_active_parameterized_nonself_calls = original_result_preservation_row.active_parameterized_nonself_call_count
let original_active_parameterized_result_nonself_calls = original_result_removal_prune_row.active_parameterized_result_nonself_call_count
let original_nonself_undropped_calls = original_result_preservation_row.nonself_undropped_call_count
let original_private_result_dead_suffix_calls = original_result_removal_prune_row.private_result_dead_suffix_call_count
let original_private_result_same_operand_calls = if def_idx <
original_private_result_same_operand_call_counts.length() {
original_private_result_same_operand_call_counts[def_idx]
} else {
0
}
let original_escaped_undropped_dead_suffix_calls = if def_idx <
original_escaped_undropped_dead_suffix_call_counts.length() {
original_escaped_undropped_dead_suffix_call_counts[def_idx]
} else {
0
}
let original_dead_suffix_other_call = original_result_side_call_row.dead_suffix_other_call
let original_dead_suffix_undropped_calls_from_parameterized_dropped_self_callers = if def_idx <
original_dead_suffix_undropped_calls_from_parameterized_dropped_self_callers.length() {
original_dead_suffix_undropped_calls_from_parameterized_dropped_self_callers[def_idx]
} else {
0
}
let original_dead_suffix_result_other_call = original_result_side_call_row.dead_suffix_result_other_call
let original_dropped_only_result_side_caller_prune = original_result_removal_prune_for_def.dropped_only_no_param_result_side_caller
let original_escaped_block_self_result = dae_should_preserve_escaped_block_self_result_from_row(
original_result_removal_prune_row, true,
)
let original_dead_suffix_side_call_evidence = dae_original_dead_suffix_side_call_evidence(
original_result_removal_prune_row, original_result_side_call_row,
)
let original_dead_suffix_undropped_self_call = original_dead_suffix_side_call_evidence.dead_suffix_undropped_self_call
let original_dead_suffix_mutual_call = original_dead_suffix_side_call_evidence.dead_suffix_mutual_call
let original_parameterized_undropped_self_side_chain = dae_should_preserve_parameterized_undropped_self_side_chain_evidence(
dae_original_parameterized_undropped_self_side_chain_evidence_from_row(
original_result_preservation_row,
),
)
let allow_unreachable_only_param_cleanup = (
original_result_escape_row.dead_suffix_undropped_escaped_result_call &&
original_result_escape_row.escaped_dead_suffix_call_count == 0
) ||
original_dead_suffix_side_call_evidence.void_side_cleanup_preservation ||
original_dead_suffix_side_call_evidence.result_side_chain_preservation ||
original_dead_suffix_side_call_evidence.same_self_result_operand_preservation ||
original_result_cleanup_preservation_for_def.result_side_chain_cleanup_preservation ||
original_escaped_block_self_result ||
original_dropped_only_result_side_caller_prune
dae_scalar_validation_barrier_opt(scalar_validation_chain)
let dead_suffix_params = dae_try_remove_dead_suffix_only_unreachable_params(
mod_,
callee_abs,
original_call_count,
allow_unreachable_only_param_cleanup,
current_calls_anywhere=dae_original_int_fact_at(
current_call_counts_anywhere, def_idx,
),
)
if dead_suffix_params.changed {
dae_record_dead_suffix_parameter_candidate_effects(
mod_, current_graph, def_idx, dead_suffix_params, producer_effects_report,
)
return dead_suffix_params
}
let @lib.Func(_, @lib.Expr(current_body)) = funcs[def_idx]
let has_undropped_self_suffix = self_calls_anywhere > 0 &&
dae_dead_suffix_has_undropped_direct_call_to(current_body, callee_abs)
let no_self_nonmutual_side_call = original_prior_calls == 0 &&
original_self_calls == 0 &&
original_dead_suffix_other_call &&
!original_dead_suffix_mutual_call
let prune_private_result_side_chain = originally_had_params &&
original_self_calls == 0 &&
original_private_result_dead_suffix_calls > 0 &&
original_dead_suffix_other_call &&
original_private_result_same_operand_calls > 0 &&
original_active_nonself_calls > original_nonself_undropped_calls &&
original_nonself_undropped_calls > 0
let skip_uncalled_after_param_cleanup = !prune_private_result_side_chain &&
!dae_should_block_uncalled_after_param_cleanup_from_prune_evidence(
original_result_removal_prune_for_def,
) &&
dae_func_is_unreachable_only(
dae_simplify_to_unreachable_when_prefix_unobservable(funcs[def_idx]),
) &&
(
has_undropped_self_suffix ||
dae_original_result_cleanup_preservation_any(
original_result_cleanup_preservation_for_def,
) ||
original_dead_suffix_side_call_evidence.same_self_result_operand_preservation ||
original_escaped_block_self_result ||
original_dead_suffix_side_call_evidence.result_side_chain_preservation ||
(
originally_had_params &&
original_self_calls == 0 &&
original_private_result_dead_suffix_calls > 0 &&
original_result_escape_row.dead_suffix_result_calls_from_no_param_undropped_self_caller ==
0 &&
original_result_escape_row.escaped_dead_suffix_call_count == 0
) ||
(
originally_had_params &&
original_self_calls == 0 &&
original_result_escape_row.dead_suffix_escaped_result_operand_call_count >
0
) ||
(
!originally_had_params &&
original_result_escape_row.private_result_dead_suffix_calls_from_escaped_operand_callees >
0
) ||
(
originally_had_params &&
original_self_calls == 0 &&
original_result_escape_row.dead_suffix_result_calls_from_undropped_self_caller >
0 &&
original_result_escape_row.dead_suffix_result_calls_from_no_param_undropped_self_caller ==
0 &&
original_result_escape_row.escaped_dead_suffix_call_count == 0 &&
original_active_nonself_calls > 0
) ||
original_result_escape_row.dead_suffix_calls_from_param_operand_self_result_caller >
0 ||
(
originally_had_params &&
original_self_calls == 0 &&
original_result_escape_row.private_result_escaped_operand_call_count >
0
) ||
(
originally_had_params &&
original_self_calls == 0 &&
original_result_escape_row.dead_suffix_undropped_escaped_result_call &&
original_result_escape_row.escaped_dead_suffix_call_count == 0 &&
original_call_count > 0 &&
original_nonself_undropped_calls == 0
) ||
(
originally_had_params &&
original_self_calls > 0 &&
original_result_preservation_row.dead_suffix_dropped_self_call &&
!original_dead_suffix_undropped_self_call &&
original_call_count > original_self_calls &&
original_active_nonself_calls > 0 &&
original_active_parameterized_result_nonself_calls > 0 &&
original_result_escape_row.escaped_dead_suffix_call_count == 0 &&
!original_dead_suffix_mutual_call
) ||
(
originally_had_params &&
original_self_calls == 0 &&
original_dead_suffix_result_other_call &&
original_result_escape_row.escaped_dead_suffix_call_count == 0 &&
!original_dead_suffix_mutual_call &&
(
original_active_nonself_calls > 0 ||
original_nonself_undropped_calls > 0
)
) ||
original_dead_suffix_side_call_evidence.void_side_cleanup_preservation ||
(
originally_had_params &&
original_result_escape_row.dead_suffix_result_calls_from_no_param_undropped_self_caller ==
0 &&
(
original_call_count == original_self_calls ||
original_active_nonself_calls > 0 ||
original_nonself_undropped_calls > 0
) &&
(original_prior_calls == 0 || original_prior_undropped_calls > 0) &&
!no_self_nonmutual_side_call &&
!(original_self_calls > 0 &&
original_call_count > original_self_calls &&
original_active_parameterized_nonself_calls == 0 &&
original_nonself_undropped_calls == 0)
) ||
(
!originally_had_params &&
original_prior_undropped_calls > 0 &&
(
original_dead_suffix_undropped_calls_from_parameterized_dropped_self_callers ==
0 ||
original_escaped_undropped_dead_suffix_calls > 0
)
) ||
(
!originally_had_params &&
original_nonself_undropped_calls > 0 &&
original_result_escape_row.escaped_dead_suffix_call_count == 0 &&
original_dead_suffix_undropped_calls_from_parameterized_dropped_self_callers ==
0
) ||
(
!originally_had_params &&
original_dead_suffix_undropped_self_call &&
original_call_count >= original_self_calls
)
)
let skip_self_suffix_result = original_self_calls > 0 &&
skip_uncalled_after_param_cleanup
let dead_self_suffix_shape_candidate = current_param_count == 0 &&
current_result_count == 1 &&
!dae_func_is_unreachable_only(funcs[def_idx]) &&
dae_dead_suffix_has_dropped_direct_call_to(current_body, callee_abs)
if dead_self_suffix_shape_candidate &&
self_calls_anywhere > 0 &&
!skip_self_suffix_result &&
!skip_original_large_result_cleanup &&
original_call_count == original_self_calls {
match
dae_specialized_result_facts_new(original, current_graph, callee_abs) {
Some(specialized_facts) => {
dae_scalar_validation_barrier_opt(scalar_validation_chain)
let self_suffix = dae_try_remove_dead_self_suffix_result_with_facts(
mod_, specialized_facts,
)
if self_suffix.changed {
dae_record_producer_effects(
producer_effects_report,
dae_specialized_result_commit_effects(
specialized_facts, self_suffix,
),
)
return self_suffix
}
}
None => ()
}
}
let uncalled_result_shape_candidate = match
dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(current_params, current_results)) =>
current_params.length() == 0 &&
current_results.length() > 0 &&
dae_func_is_unreachable_only(
dae_simplify_to_unreachable_when_prefix_unobservable(funcs[def_idx]),
)
None => false
}
if uncalled_result_shape_candidate &&
!skip_uncalled_after_param_cleanup &&
!skip_original_large_result_cleanup &&
original_call_count > 0 &&
(
self_calls_anywhere == 0 ||
dae_original_int_fact_at(current_call_counts_anywhere, def_idx) !=
self_calls_anywhere
) {
match
dae_specialized_result_facts_new(original, current_graph, callee_abs) {
Some(specialized_facts) => {
dae_scalar_validation_barrier_opt(scalar_validation_chain)
let result = dae_try_remove_uncalled_results_with_facts(
mod_, specialized_facts,
)
if result.changed {
dae_record_producer_effects(
producer_effects_report,
dae_specialized_result_commit_effects(specialized_facts, result),
)
return result
}
}
None => ()
}
}
if self_calls_anywhere == 0 {
let func_type = match
dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(ft) => ft
None => {
def_idx += 1
continue
}
}
let @lib.FuncType(params, _) = func_type
if params.length() > 0 &&
original_parameterized_undropped_self_side_chain &&
dae_func_is_unreachable_only(funcs[def_idx]) {
let @lib.Func(locals, _) = funcs[def_idx]
let local_use = dae_local_use_for_callee(
funcs[def_idx],
params.length() + locals.length(),
callee_abs,
params.length(),
)
let remove = Array::make(params.length(), false)
let const_params : Array[@lib.Instruction?] = Array::make(
params.length(),
None,
)
let collect_const_params = Array::make(params.length(), false)
let mut has_collect_const_param = false
let mut collect_p = 0
while collect_p < params.length() {
if local_use.gets[collect_p] && !local_use.writes[collect_p] {
collect_const_params[collect_p] = true
has_collect_const_param = true
}
collect_p += 1
}
let has_direct_caller_cycle = dae_boundary_has_direct_caller_cycle(
boundary_facts, imported, callee_abs,
)
let uniform_const_params = if dae_should_collect_phase_local_uniform_constants(
defined,
optimize~,
allow_written_uniform_const=false,
self_calls_anywhere~,
has_collect_const_param~,
has_direct_caller_cycle~,
) {
dae_collect_uniform_const_actuals_from_current_call_facts(
mod_,
callee_abs,
params.length(),
type_indices,
boundary_facts,
function_infos,
collect_params=Some(collect_const_params),
)
} else {
None
}
let mut any = false
let mut p = 0
while p < params.length() {
if !local_use.gets[p] {
remove[p] = true
any = true
} else if !local_use.writes[p] {
match uniform_const_params {
Some(values) =>
match values[p] {
Some(value) if dae_batchable_uniform_literal(value) => {
remove[p] = true
const_params[p] = Some(value)
any = true
}
_ => ()
}
None => ()
}
}
p += 1
}
if any && !skip_param_removal {
dae_scalar_validation_barrier_opt(scalar_validation_chain)
let result = dae_try_rewrite_candidate(
mod_,
callee_abs,
remove,
const_params,
local_use,
boundary_candidate.direct_call_count,
boundary_candidate.direct_callers,
)
if result.changed {
dae_record_work_family_candidate_effects(
mod_,
current_graph,
def_idx,
if const_params.any(fn(value) { value is Some(_) }) {
DaeUniformActualWork
} else {
DaeUnusedParameterWork
},
result,
producer_effects_report,
)
return result
}
}
}
// Binaryen admits the zero-call family in both variants only for a
// private root-terminal unreachable target. Ordinary returning bodies
// stay outside this specialized boundary scan.
if original_result_preservation_row.dead_suffix_dropped_self_call ||
current_body.length() == 0 ||
current_body[current_body.length() - 1] !=
@lib.Instruction::unreachable_() {
def_idx += 1
continue
}
}
}
let func_type = match
dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(ft) => ft
None => {
def_idx += 1
continue
}
}
let @lib.FuncType(params, results) = func_type
if self_calls_anywhere == 0 &&
boundary_candidate.direct_call_count > 0 &&
dae_signature_has_heap_ref(params, results) {
dae_scalar_validation_barrier_opt(scalar_validation_chain)
match
dae_try_process_core_work_item(
original,
mod_,
current_graph,
def_idx,
DaeGcParameterRefinementWork,
dependencies=Some(active_dependencies),
apply_followups=false,
) {
Some(application) =>
return dae_core_pass_result_from_work_application(
application,
producer_effects_report,
committed_application_report~,
)
None => ()
}
}
if self_calls_anywhere > 0 &&
boundary_candidate.direct_call_count > 0 &&
dae_signature_has_heap_ref([], results) {
dae_scalar_validation_barrier_opt(scalar_validation_chain)
match
dae_try_process_core_work_item(
original,
mod_,
current_graph,
def_idx,
DaeGcResultRefinementWork,
dependencies=Some(active_dependencies),
apply_followups=false,
) {
Some(application) =>
return dae_core_pass_result_from_work_application(
application,
producer_effects_report,
committed_application_report~,
)
None => ()
}
}
if params.length() > 0 &&
dae_should_skip_stale_scalar_result_tail(
scalar_plan_code_snapshot_current, has_current_dropped_result_candidate,
) {
def_idx += 1
continue
}
if params.length() == 0 {
// The dropped-result producer rejects a zero-result signature before
// any IR write. Keep the checked source across this empty boundary.
if results.is_empty() {
def_idx += 1
continue
}
let preserve_original_result = original_result_preservation_for_def
if original_result_removal_prune_for_def.dropped_self_result_with_void_side_callers &&
!skip_original_large_result_cleanup {
dae_numeric_result_validation_before_producer(
scalar_validation_chain, current_graph, def_idx,
)
match
dae_try_process_core_work_item(
original,
mod_,
current_graph,
def_idx,
DaeDroppedResultWork,
dependencies=Some(active_dependencies),
apply_followups=false,
numeric_result_validation_chain=scalar_validation_chain,
numeric_result_validation_work~,
) {
Some(application) =>
return dae_core_pass_result_from_work_application(
application,
producer_effects_report,
committed_application_report~,
)
None => ()
}
}
if !skip_original_large_result_cleanup &&
dae_should_prune_params_absent_result_cleanup(
original_result_removal_prune_for_def,
) {
dae_numeric_result_validation_before_producer(
scalar_validation_chain, current_graph, def_idx,
)
match
dae_try_process_core_work_item(
original,
mod_,
current_graph,
def_idx,
DaeDroppedResultWork,
dependencies=Some(active_dependencies),
apply_followups=false,
numeric_result_validation_chain=scalar_validation_chain,
numeric_result_validation_work~,
) {
Some(application) =>
return dae_core_pass_result_from_work_application(
application,
producer_effects_report,
committed_application_report~,
)
None => ()
}
}
if !skip_original_large_result_cleanup &&
!dae_original_result_preservation_any(preserve_original_result) {
dae_numeric_result_validation_before_producer(
scalar_validation_chain, current_graph, def_idx,
)
match
dae_try_process_core_work_item(
original,
mod_,
current_graph,
def_idx,
DaeDroppedResultWork,
dependencies=Some(active_dependencies),
apply_followups=false,
numeric_result_validation_chain=scalar_validation_chain,
numeric_result_validation_work~,
) {
Some(application) =>
return dae_core_pass_result_from_work_application(
application,
producer_effects_report,
committed_application_report~,
)
None => ()
}
}
def_idx += 1
continue
}
let @lib.Func(locals, @lib.Expr(candidate_body)) = funcs[def_idx]
let local_use = dae_local_use_for_callee(
funcs[def_idx],
params.length() + locals.length(),
callee_abs,
params.length(),
)
let mut rewrite_mod = mod_
let mut has_rewrite_mod = false
// Shared incoming-parameter live-in analysis ([O4Z-DAE-BOUNDARY]001).
// Reuse the unchanged source scan before gets becomes the live-in mask.
let entry_live = dae_incoming_parameter_liveness(
mod_,
callee_abs,
params.length(),
syntactic_use=Some(local_use),
liveness_work~,
)
if params.length() > 0 {
let mut found_entry_dead_param = false
let mut param_idx = 0
while param_idx < params.length() {
if !entry_live[param_idx] {
local_use.gets[param_idx] = false
if !optimize || local_use.writes[param_idx] {
found_entry_dead_param = true
}
} else {
local_use.gets[param_idx] = true
}
param_idx += 1
}
if found_entry_dead_param {
dae_scalar_validation_barrier_opt(scalar_validation_chain)
rewrite_mod = dae_strip_function_terminal_suffix_for_def(mod_, def_idx)
has_rewrite_mod = true
}
}
let unused_params = dae_function_info_refresh_unused_params(
function_infos,
callee_abs,
local_use,
params.length(),
)
let remove = Array::make(params.length(), false)
let const_params : Array[@lib.Instruction?] = Array::make(
params.length(),
None,
)
let has_direct_caller_cycle = dae_boundary_has_direct_caller_cycle(
boundary_facts, imported, callee_abs,
)
let run_scalar_parameter_candidate = dae_should_run_scalar_parameter_candidate(
defined,
has_direct_caller_cycle~,
)
let allow_written_uniform_const = params.length() > 0 &&
(
dae_instrs_support_param_entry_liveness(candidate_body) ||
(optimize && pass_raw_instrs_have_legacy_try(candidate_body))
)
let collect_const_params = Array::make(params.length(), false)
let mut has_collect_const_param = false
let mut collect_p = 0
while collect_p < params.length() {
if local_use.gets[collect_p] &&
(!local_use.writes[collect_p] || allow_written_uniform_const) {
collect_const_params[collect_p] = true
has_collect_const_param = true
}
collect_p += 1
}
let uniform_const_params = if dae_should_collect_phase_local_uniform_constants(
defined,
optimize~,
allow_written_uniform_const~,
self_calls_anywhere~,
has_collect_const_param~,
has_direct_caller_cycle=!run_scalar_parameter_candidate,
) {
dae_collect_uniform_const_actuals_from_current_call_facts(
mod_,
callee_abs,
params.length(),
type_indices,
boundary_facts,
function_infos,
allow_immutable_global_get=allow_written_uniform_const,
collect_params=Some(collect_const_params),
)
} else {
None
}
let mut any = false
let mut p = 0
while p < params.length() {
if run_scalar_parameter_candidate && unused_params[p] {
remove[p] = true
any = true
} else if run_scalar_parameter_candidate &&
(!local_use.writes[p] || allow_written_uniform_const) {
match uniform_const_params {
Some(values) =>
match values[p] {
Some(value) if dae_batchable_uniform_literal(value) ||
(
allow_written_uniform_const &&
dae_instr_is_materializable_const_with_immutable_global(
mod_, value,
)
) => {
remove[p] = true
const_params[p] = Some(value)
any = true
}
_ => ()
}
None => ()
}
}
p += 1
}
let preserve_current_result = original_result_preservation_for_def
if any && !skip_param_removal {
if !scalar_plan_code_snapshot_current {
def_idx += 1
continue
}
let scalar_unused_only = const_params.all(fn(value) { value is None })
if scalar_unused_only {
let callee_source = if !has_rewrite_mod {
None
} else {
match rewrite_mod.code_sec {
Some(@lib.CodeSec(rewrite_funcs)) if def_idx >= 0 &&
def_idx < rewrite_funcs.length() => Some(rewrite_funcs[def_idx])
_ => None
}
}
dae_scalar_validation_barrier_opt(scalar_validation_chain)
let planned = dae_try_rewrite_scalar_unused_param_candidate(
mod_,
original,
current_graph,
callee_abs,
remove,
local_use,
direct_calls[callee_abs],
direct_callers[callee_abs],
callee_source~,
entry_live=Some(entry_live),
liveness_work~,
traps_never_happen~,
)
if planned.applied {
dae_record_producer_effects(producer_effects_report, planned.effects)
return planned.result
}
if planned.reason != DaeActualValueSliceUnavailable {
def_idx += 1
continue
}
}
if !dae_scalar_numeric_materialization(const_params) {
dae_scalar_validation_barrier_opt(scalar_validation_chain)
}
let result = dae_try_rewrite_candidate(
rewrite_mod,
callee_abs,
remove,
const_params,
local_use,
direct_calls[callee_abs],
direct_callers[callee_abs],
original=Some(original),
graph=Some(current_graph),
entry_live=Some(entry_live),
liveness_work~,
checked_candidate_report~,
scalar_validation_chain~,
scalar_validation_work~,
)
if result.changed {
dae_record_work_family_candidate_effects(
mod_,
current_graph,
def_idx,
if const_params.any(fn(value) { value is Some(_) }) {
DaeUniformActualWork
} else {
DaeUnusedParameterWork
},
result,
producer_effects_report,
)
return result
}
} else if !skip_original_large_result_cleanup &&
!dae_original_result_preservation_blocks_argument_side_result_repair(
preserve_current_result, true,
) {
// Use the exact graph facts passed to the dropped-result producer, not
// the optional boundary-facts override. Its first gate rejects zero
// direct calls and any undropped call before a candidate can be written.
let producer_call_facts = current_graph.snapshot.call_facts
let producer_direct_calls = dae_call_boundary_count_at(
producer_call_facts.direct_calls,
callee_abs,
)
if producer_direct_calls == 0 ||
dae_call_boundary_count_at(
producer_call_facts.dropped_calls,
callee_abs,
) !=
producer_direct_calls {
def_idx += 1
continue
}
dae_numeric_result_validation_before_producer(
scalar_validation_chain, current_graph, def_idx,
)
match
dae_try_process_core_work_item(
original,
mod_,
current_graph,
def_idx,
DaeDroppedResultWork,
dependencies=Some(active_dependencies),
apply_followups=false,
numeric_result_validation_chain=scalar_validation_chain,
numeric_result_validation_work~,
) {
Some(application) =>
return dae_core_pass_result_from_work_application(
application,
producer_effects_report,
committed_application_report~,
)
None => ()
}
}
def_idx += 1
}
{ mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
///|
fn dae_try_rewrite_reverse_exact_literal_once(
mod_ : @lib.Module,
original_param_counts : Array[Int32],
original_call_counts : Array[Int32],
original_active_nonself_call_counts : Array[Int32],
original_active_parameterized_nonself_call_counts : Array[Int32],
original_nonself_undropped_call_counts : Array[Int32],
original_self_call_counts : Array[Int32],
original_private_result_dead_suffix_calls_from_escaped_operand_callees : Array[
Int32,
],
original_escaped_dead_suffix_call_counts : Array[Int32],
original_dead_suffix_undropped_self_calls : Array[Bool],
original_dead_suffix_dropped_self_calls : Array[Bool],
original_dead_suffix_block_undropped_self_calls : Array[Bool],
original_dead_suffix_result_calls_from_undropped_self_callers : Array[Int32],
original_dead_suffix_result_calls_from_no_param_undropped_self_callers : Array[
Int32,
],
original_dead_suffix_same_self_result_operand_calls : Array[Bool],
original_dead_suffix_undropped_self_call_param_operands : Array[Bool],
original_dead_suffix_result_other_calls : Array[Bool],
original_dead_suffix_escaped_result_call_facts : DaeOriginalDeadSuffixEscapedResultCallFacts,
original_dead_suffix_mutual_calls : Array[Bool],
original_dead_suffix_calls_from_param_operand_self_result_callers : Array[
Int32,
],
original_dead_suffix_escaped_result_operand_call_counts : Array[Int32],
original_dead_suffix_dropped_calls_from_parameterized_self_callers : Array[
Int32,
],
supplied_graph~ : DaeCurrentBoundaryGraph? = None,
graph_builds~ : Array[Int32]? = None,
original_snapshot_builds~ : Array[Int32]? = None,
type_resolutions~ : Array[Int32]? = None,
reuse_function_types~ : Bool = true,
reverse~ : Bool = true,
max_def_exclusive~ : Int32 = -1,
) -> DaePassResult {
let original_dead_suffix_undropped_escaped_result_calls = original_dead_suffix_escaped_result_call_facts.undropped_calls
let original_dead_suffix_dropped_escaped_result_calls = original_dead_suffix_escaped_result_call_facts.dropped_calls
// Replay facts remain fixed for this candidate scan. Build them
// only when the first candidate reaches the rewrite planner.
let mut rewrite_original : DaeOriginalBoundarySnapshot? = None
let rewrite_graph = match supplied_graph {
// The scan and its surrounding commit planning share exactly this owner.
// A graph from another module, including a changed body/type owner, rebuilds.
Some(graph) if physical_equal(graph.mod_, mod_) => graph
_ => {
match graph_builds {
Some(count) => count[0] += 1
None => ()
}
dae_current_boundary_graph_new(mod_)
}
}
let boundary_snapshot = rewrite_graph.snapshot
let imported = boundary_snapshot.imported
let defined = boundary_snapshot.defined
let boundary_facts = boundary_snapshot.boundary_facts
let type_indices = boundary_snapshot.type_indices
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let original_result_preservation_context = dae_original_result_preservation_context(
original_param_counts, original_call_counts, original_active_nonself_call_counts,
original_active_parameterized_nonself_call_counts, original_nonself_undropped_call_counts,
original_self_call_counts, original_private_result_dead_suffix_calls_from_escaped_operand_callees,
original_escaped_dead_suffix_call_counts, original_dead_suffix_undropped_self_calls,
original_dead_suffix_dropped_self_calls, original_dead_suffix_block_undropped_self_calls,
original_dead_suffix_result_calls_from_undropped_self_callers, original_dead_suffix_result_calls_from_no_param_undropped_self_callers,
original_dead_suffix_same_self_result_operand_calls, original_dead_suffix_undropped_self_call_param_operands,
original_dead_suffix_result_other_calls, original_dead_suffix_dropped_escaped_result_calls,
original_dead_suffix_mutual_calls, original_dead_suffix_calls_from_param_operand_self_result_callers,
original_dead_suffix_escaped_result_operand_call_counts, original_dead_suffix_dropped_calls_from_parameterized_self_callers,
)
let scan_limit = if max_def_exclusive >= 0 && max_def_exclusive < defined {
max_def_exclusive
} else {
defined
}
let mut def_idx = if reverse { scan_limit - 1 } else { 0 }
while (if reverse { def_idx >= 0 } else { def_idx < scan_limit }) {
let callee_abs = imported + def_idx
let boundary_candidate = match
dae_call_boundary_candidate(boundary_facts, callee_abs) {
Some(candidate) => candidate
None => {
if reverse {
def_idx -= 1
} else {
def_idx += 1
}
continue
}
}
if !boundary_candidate.owned {
if reverse {
def_idx -= 1
} else {
def_idx += 1
}
continue
}
let self_calls_anywhere = dae_count_self_calls_anywhere(
funcs[def_idx],
callee_abs,
)
if self_calls_anywhere > 0 {
if reverse {
def_idx -= 1
} else {
def_idx += 1
}
continue
}
// The candidate graph already owns a signature snapshot for this exact
// module. Read it directly instead of walking the type section per callee.
let resolved_type = if reuse_function_types {
boundary_snapshot.function_types[callee_abs]
} else {
match type_resolutions {
Some(count) => count[0] += 1
None => ()
}
dae_resolve_func_type(mod_, type_indices, callee_abs)
}
let func_type = match resolved_type {
Some(ft) => ft
None => {
if reverse {
def_idx -= 1
} else {
def_idx += 1
}
continue
}
}
let @lib.FuncType(params, _) = func_type
if params.length() == 0 || boundary_candidate.direct_call_count == 0 {
if reverse {
def_idx -= 1
} else {
def_idx += 1
}
continue
}
let @lib.Func(locals, _) = funcs[def_idx]
let local_use = dae_local_use_for_callee(
funcs[def_idx],
params.length() + locals.length(),
callee_abs,
params.length(),
)
let uniform_const_params = dae_collect_uniform_const_actuals_from_current_call_facts(
mod_,
callee_abs,
params.length(),
type_indices,
boundary_facts,
boundary_snapshot.function_infos,
)
let remove = Array::make(params.length(), false)
let const_params : Array[@lib.Instruction?] = Array::make(
params.length(),
None,
)
let mut any_const = false
let mut has_dead_param = false
let mut p = 0
while p < params.length() {
if !local_use.gets[p] {
has_dead_param = true
} else if !local_use.writes[p] {
match uniform_const_params {
Some(values) =>
match values[p] {
Some(value) => {
remove[p] = true
const_params[p] = Some(value)
any_const = true
}
None => ()
}
None => ()
}
}
p += 1
}
let preserve_current_result = dae_original_result_preservation_evidence_from_context(
def_idx, original_result_preservation_context,
)
if any_const &&
!has_dead_param &&
!dae_original_result_preservation_blocks_argument_side_result_repair(
preserve_current_result, false,
) &&
def_idx <
original_dead_suffix_calls_from_param_operand_self_result_callers.length() &&
original_dead_suffix_calls_from_param_operand_self_result_callers[def_idx] ==
0 &&
def_idx < original_dead_suffix_undropped_escaped_result_calls.length() &&
!original_dead_suffix_undropped_escaped_result_calls[def_idx] {
let active_original = match rewrite_original {
Some(original) => original
None => {
match original_snapshot_builds {
Some(count) => count[0] += 1
None => ()
}
let original = dae_collect_original_boundary_snapshot(mod_)
rewrite_original = Some(original)
original
}
}
let result = dae_try_rewrite_candidate(
mod_,
callee_abs,
remove,
const_params,
local_use,
boundary_candidate.direct_call_count,
boundary_candidate.direct_callers,
original=Some(active_original),
graph=Some(rewrite_graph),
)
if result.changed {
return result
}
}
if reverse {
def_idx -= 1
} else {
def_idx += 1
}
}
{ mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
///|
fn dae_merge_touched(dst : Array[Bool], src : Array[Bool]) -> Array[Bool] {
if dst.length() == 0 {
// `src` is the one-shot touched bitmap returned by a just-finished rewrite
// lane. Adopt it as the scheduler-local accumulator instead of allocating a
// full defined-function copy before the next merge mutates it in place.
return src
}
// `touched` is a scheduler-local accumulation bitmap. Mutate it in place so
// large artifact lanes do not allocate and copy a full defined-function bitmap
// after every selected rewrite.
let mut idx = 0
while idx < src.length() && idx < dst.length() {
dst[idx] = dst[idx] || src[idx]
idx += 1
}
dst
}
///|
fn dae_apply_core_result(
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
result : DaePassResult,
) -> DaeCoreResultApplication {
if !result.changed || dae_snapshot_module_unchanged(result.mod_out, current) {
return {
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(dae_defined_func_count(current)),
}
}
{
mod_out: result.mod_out,
touched: dae_merge_touched(touched, result.touched),
changed: true,
applied: true,
primary_def: result.primary_def,
effects: dae_commit_effects_empty(dae_defined_func_count(current)),
}
}
///|
#warnings("-unused_value")
fn dae_apply_cleanup_result_preserving_touched(
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
result : DaePassResult,
) -> DaeCoreResultApplication {
let application_result : DaePassResult = {
mod_out: result.mod_out,
touched: [],
changed: result.changed,
primary_def: result.primary_def,
}
dae_apply_core_result(current, touched, changed, application_result)
}
///|
fn dae_apply_selected_core_result(
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
selected_defs : Array[Bool],
selected_def : Int32,
result : DaePassResult,
) -> DaeCoreResultApplication {
if selected_def >= 0 && selected_def < selected_defs.length() {
selected_defs[selected_def] = false
}
dae_apply_core_result(current, touched, changed, result)
}
///|
fn dae_run_singleton_selected_core_rewrite(
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
selected_defs : Array[Bool],
selected_def : Int32,
rewrite_func : (@lib.Module, Array[Bool]) -> DaePassResult,
) -> DaeCoreResultApplication {
if selected_def >= 0 && selected_def < selected_defs.length() {
selected_defs[selected_def] = true
}
let result = rewrite_func(current, selected_defs)
dae_apply_selected_core_result(
current, touched, changed, selected_defs, selected_def, result,
)
}
///|
#warnings("-unused_value")
fn dae_run_bounded_singleton_selected_core_rewrite(
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
selected_defs : Array[Bool],
selected_def : Int32,
max_iterations : Int32,
rewrite_func : (@lib.Module, Array[Bool]) -> DaePassResult,
on_applied : (Int32, DaeCoreResultApplication) -> Unit,
) -> DaeCoreResultApplication {
let mut application : DaeCoreResultApplication = {
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(dae_defined_func_count(current)),
}
let mut iteration = 0
while iteration < max_iterations {
application = dae_run_singleton_selected_core_rewrite(
application.mod_out,
application.touched,
application.changed,
selected_defs,
selected_def,
rewrite_func,
)
if !application.applied {
break
}
on_applied(iteration, application)
iteration += 1
}
application
}
///|
#warnings("-unused_value")
fn dae_apply_primary_selected_core_result(
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
selected_defs : Array[Bool],
result : DaePassResult,
) -> DaeCoreResultApplication {
let application = dae_apply_core_result(current, touched, changed, result)
if application.applied &&
application.primary_def >= 0 &&
application.primary_def < selected_defs.length() {
selected_defs[application.primary_def] = false
}
application
}
///|
fn dae_try_apply_selected_def_func_rewrite_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
def_idx : Int32,
rewrite_func : (@lib.Func, @lib.TypeSec?) -> @lib.Func?,
) -> DaePassResult {
if def_idx < 0 || def_idx >= selected_defs.length() || !selected_defs[def_idx] {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if def_idx >= funcs.length() {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let current_func = funcs[def_idx]
let rewritten = match rewrite_func(current_func, mod_.type_sec) {
Some(func) => func
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if dae_snapshot_func_unchanged(rewritten, current_func) {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let rewritten_funcs = funcs.copy()
rewritten_funcs[def_idx] = rewritten
let touched = Array::make(funcs.length(), false)
touched[def_idx] = true
{
mod_out: mod_
.with_code_sec(@lib.CodeSec::new(rewritten_funcs))
.without_name_sec(),
touched,
changed: true,
primary_def: def_idx,
}
}
///|
#warnings("-unused_value")
fn dae_try_apply_selected_def_typed_func_rewrite_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
def_idx : Int32,
rewrite_func : (@lib.Func, @lib.FuncType, @lib.TypeSec?) -> @lib.Func?,
) -> DaePassResult {
let rewrite_selected = fn(
func : @lib.Func,
type_sec : @lib.TypeSec?,
) -> @lib.Func? {
let func_type = match
dae_resolve_func_type(
mod_,
dae_collect_func_type_indices(mod_),
dae_imported_func_count(mod_) + def_idx,
) {
Some(func_type) => func_type
None => return None
}
rewrite_func(func, func_type, type_sec)
}
dae_try_apply_selected_def_func_rewrite_once(
mod_, selected_defs, def_idx, rewrite_selected,
)
}
///|
#warnings("-unused_value")
fn dae_try_apply_first_selected_typed_func_rewrite_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
rewrite_func : (@lib.Func, Int32, @lib.FuncType, @lib.TypeSec?) -> @lib.Func?,
) -> DaePassResult {
let imported = dae_imported_func_count(mod_)
let type_indices = dae_collect_func_type_indices(mod_)
let rewrite_selected = fn(
func : @lib.Func,
def_idx : Int32,
type_sec : @lib.TypeSec?,
) -> @lib.Func? {
let func_type = match
dae_resolve_func_type(mod_, type_indices, imported + def_idx) {
Some(func_type) => func_type
None => return None
}
rewrite_func(func, def_idx, func_type, type_sec)
}
dae_try_apply_first_selected_func_rewrite_once(
mod_, selected_defs, rewrite_selected,
)
}
///|
fn dae_apply_selected_typed_func_rewrites_to_fixpoint(
mod_ : @lib.Module,
selected_defs : Array[Bool],
rewrite_func : (@lib.Func, Int32, @lib.FuncType, @lib.TypeSec?) -> @lib.Func?,
) -> DaePassResult {
let imported = dae_imported_func_count(mod_)
let type_indices = dae_collect_func_type_indices(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let rewritten_funcs = funcs.copy()
let touched = Array::make(funcs.length(), false)
let mut primary_def = -1
let mut def_idx = 0
while def_idx < funcs.length() && def_idx < selected_defs.length() {
if selected_defs[def_idx] {
let func_type = match
dae_resolve_func_type(mod_, type_indices, imported + def_idx) {
Some(func_type) => func_type
None => {
def_idx += 1
continue
}
}
let mut current_func = rewritten_funcs[def_idx]
while true {
let next_func = match
rewrite_func(current_func, def_idx, func_type, mod_.type_sec) {
Some(next_func) if !dae_snapshot_func_unchanged(
next_func, current_func,
) => next_func
_ => break
}
current_func = next_func
}
if !dae_snapshot_func_unchanged(current_func, funcs[def_idx]) {
rewritten_funcs[def_idx] = current_func
touched[def_idx] = true
if primary_def < 0 {
primary_def = def_idx
}
}
}
def_idx += 1
}
if primary_def < 0 {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
{
mod_out: mod_
.with_code_sec(@lib.CodeSec::new(rewritten_funcs))
.without_name_sec(),
touched,
changed: true,
primary_def,
}
}
///|
fn dae_try_apply_selected_def_funcsec_func_rewrite_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
def_idx : Int32,
rewrite_func : (@lib.Func, @lib.FuncType, @lib.TypeSec) -> @lib.Func?,
) -> DaePassResult {
if def_idx < 0 || def_idx >= selected_defs.length() || !selected_defs[def_idx] {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let (type_idxs, funcs, type_sec) = match
(mod_.func_sec, mod_.code_sec, mod_.type_sec) {
(Some(@lib.FuncSec(type_idxs)), Some(@lib.CodeSec(funcs)), Some(type_sec)) =>
(type_idxs, funcs, type_sec)
_ => return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if def_idx >= funcs.length() || def_idx >= type_idxs.length() {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let func_type = match dae_type_sec_func_type(type_sec, type_idxs[def_idx]) {
Some(func_type) => func_type
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let current_func = funcs[def_idx]
let rewritten = match rewrite_func(current_func, func_type, type_sec) {
Some(func) => func
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if dae_snapshot_func_unchanged(rewritten, current_func) {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let @lib.FuncType(params, results) = func_type
let canonical_type_idx = match
dae_find_first_matching_func_type_idx(type_sec, params, results) {
Some(type_idx) => type_idx
None => type_idxs[def_idx]
}
let rewritten_type_idxs = type_idxs.copy()
rewritten_type_idxs[def_idx] = canonical_type_idx
let rewritten_funcs = funcs.copy()
rewritten_funcs[def_idx] = rewritten
let touched = Array::make(funcs.length(), false)
touched[def_idx] = true
{
mod_out: mod_
.with_func_sec(@lib.FuncSec::new(rewritten_type_idxs))
.with_code_sec(@lib.CodeSec::new(rewritten_funcs))
.without_name_sec(),
touched,
changed: true,
primary_def: def_idx,
}
}
///|
fn dae_try_apply_first_selected_func_rewrite_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
rewrite_func : (@lib.Func, Int32, @lib.TypeSec?) -> @lib.Func?,
) -> DaePassResult {
let defined = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.length()
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let mut def_idx = 0
while def_idx < defined && def_idx < selected_defs.length() {
if selected_defs[def_idx] {
let selected_def = def_idx
let rewrite_selected = fn(
func : @lib.Func,
type_sec : @lib.TypeSec?,
) -> @lib.Func? {
rewrite_func(func, selected_def, type_sec)
}
let result = dae_try_apply_selected_def_func_rewrite_once(
mod_, selected_defs, selected_def, rewrite_selected,
)
if result.changed {
return result
}
}
def_idx += 1
}
{ mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
///|
fn dae_try_apply_selected_def_body_rewrite_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
def_idx : Int32,
rewrite_body : (Array[@lib.Instruction], @lib.TypeSec?) -> Array[
@lib.Instruction,
]?,
) -> DaePassResult {
let rewrite_func = fn(
func : @lib.Func,
type_sec : @lib.TypeSec?,
) -> @lib.Func? {
let @lib.Func(locals, @lib.Expr(body)) = func
match rewrite_body(body, type_sec) {
Some(rewritten) => Some(@lib.Func::new(locals, @lib.Expr::new(rewritten)))
None => None
}
}
dae_try_apply_selected_def_func_rewrite_once(
mod_, selected_defs, def_idx, rewrite_func,
)
}
///|
#warnings("-unused_value")
fn dae_collect_direct_callee_defs_from_caller_prefix(
mod_ : @lib.Module,
max_caller_def_exclusive : Int32,
) -> Array[Bool] {
let imported = dae_imported_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
let defined = funcs.length()
let caller_limit = if max_caller_def_exclusive >= 0 &&
max_caller_def_exclusive < defined {
max_caller_def_exclusive
} else {
defined
}
let selected = Array::make(defined, false)
let mut caller_def = 0
while caller_def < caller_limit {
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
let callees : Array[Int32] = []
dae_collect_direct_call_targets_anywhere(body, imported, defined, callees)
for callee_def in callees {
if callee_def >= caller_limit && callee_def < defined {
selected[callee_def] = true
}
}
caller_def += 1
}
selected
}
///|
fn dae_mark_direct_callee_defs_anywhere_instr(
instr : @lib.Instruction,
imported : Int32,
defined : Int32,
selected_callees : Array[Bool],
) -> Unit {
match instr {
@lib.Call(func_idx) | @lib.ReturnCall(func_idx) => {
let slot = dae_func_idx_slot(func_idx)
let def_idx = slot - imported
if slot >= imported &&
def_idx >= 0 &&
def_idx < defined &&
def_idx < selected_callees.length() &&
!selected_callees[def_idx] {
selected_callees[def_idx] = true
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_mark_direct_callee_defs_anywhere(
body, imported, defined, selected_callees,
)
@lib.If(_, then_body, Some(else_body)) => {
dae_mark_direct_callee_defs_anywhere(
then_body, imported, defined, selected_callees,
)
dae_mark_direct_callee_defs_anywhere(
else_body, imported, defined, selected_callees,
)
}
@lib.If(_, then_body, None) =>
dae_mark_direct_callee_defs_anywhere(
then_body, imported, defined, selected_callees,
)
@lib.Try(_, @lib.Expr(body), catches, _) => {
dae_mark_direct_callee_defs_anywhere(
body, imported, defined, selected_callees,
)
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(_, @lib.Expr(catch_body))
| @lib.LegacyCatchAll(@lib.Expr(catch_body)) =>
dae_mark_direct_callee_defs_anywhere(
catch_body, imported, defined, selected_callees,
)
}
}
}
_ => ()
}
}
///|
fn dae_mark_direct_callee_defs_anywhere(
instrs : Array[@lib.Instruction],
imported : Int32,
defined : Int32,
selected_callees : Array[Bool],
) -> Unit {
for instr in instrs {
dae_mark_direct_callee_defs_anywhere_instr(
instr, imported, defined, selected_callees,
)
}
}
///|
fn dae_collect_direct_callee_defs_from_selected_callers(
mod_ : @lib.Module,
selected_callers : Array[Bool],
) -> Array[Bool] {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
let imported = dae_imported_func_count(mod_)
let defined = funcs.length()
let selected = Array::make(defined, false)
let mut caller_def = 0
while caller_def < defined && caller_def < selected_callers.length() {
if selected_callers[caller_def] {
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
dae_mark_direct_callee_defs_anywhere(body, imported, defined, selected)
}
caller_def += 1
}
selected
}
///|
fn dae_collect_changed_direct_callee_defs(
before : @lib.Module,
current : @lib.Module,
) -> Array[Int32] {
let before_funcs = match before.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
let current_funcs = match current.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
if before_funcs.length() != current_funcs.length() {
return []
}
let imported = dae_imported_func_count(current)
let defined = current_funcs.length()
let seeds : Array[Int32] = []
let mut def_idx = 0
while def_idx < defined {
if before_funcs[def_idx] != current_funcs[def_idx] {
let @lib.Func(_, @lib.Expr(body)) = current_funcs[def_idx]
let callees : Array[Int32] = []
dae_collect_direct_call_targets_anywhere(body, imported, defined, callees)
for callee_def in callees {
if callee_def >= 0 &&
callee_def < defined &&
!seeds.contains(callee_def) {
seeds.push(callee_def)
}
}
}
def_idx += 1
}
seeds
}
///|
fn dae_should_run_changed_direct_callee_uniform_frontier(
phase_changed : Bool,
) -> Bool {
phase_changed
}
///|
fn dae_run_changed_direct_callee_uniform_frontier(
before : @lib.Module,
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
) -> DaeExactLiteralWorklistApplication {
let seed_defs = dae_collect_changed_direct_callee_defs(before, current)
if seed_defs.length() == 0 {
return {
mod_out: current,
touched,
changed,
productive_defs: [],
effects: dae_commit_effects_empty(dae_defined_func_count(current)),
}
}
let total = dae_imported_func_count(current) + dae_defined_func_count(current)
let call_facts = dae_collect_current_call_facts(current, total)
let all_candidates = dae_collect_exact_literal_candidate_defs_with_facts(
current,
call_facts,
-1,
allow_immutable_global_get=true,
)
let candidates : Array[Int32] = []
for candidate_def in all_candidates {
if seed_defs.contains(candidate_def) {
candidates.push(candidate_def)
}
}
dae_run_exact_literal_candidate_worklist_with_facts(
current,
touched,
changed,
candidates,
allow_immutable_global_get=true,
)
}
///|
fn dae_collect_shifted_forward_wrapper_callee_order(
mod_ : @lib.Module,
min_caller_def_inclusive : Int32,
max_caller_def_exclusive : Int32,
) -> Array[Int32] {
let defined = dae_defined_func_count(mod_)
let caller_start = if min_caller_def_inclusive > 0 &&
min_caller_def_inclusive < defined {
min_caller_def_inclusive
} else if min_caller_def_inclusive >= defined {
defined
} else {
0
}
let caller_limit = if max_caller_def_exclusive >= caller_start &&
max_caller_def_exclusive < defined {
max_caller_def_exclusive
} else {
defined
}
let type_indices = dae_collect_func_type_indices(mod_)
let ordered : Array[Int32] = []
let mut caller_def = caller_start
while caller_def < caller_limit {
let callee_def = dae_find_shifted_forward_wrapper_callee_def(
mod_, type_indices, caller_def,
)
if callee_def >= 0 && !ordered.contains(callee_def) {
ordered.push(callee_def)
}
caller_def += 1
}
ordered
}
///|
#warnings("-unused_value")
fn dae_collect_shifted_forward_wrapper_callee_defs(
mod_ : @lib.Module,
min_caller_def_inclusive : Int32,
max_caller_def_exclusive : Int32,
) -> Array[Bool] {
let imported = dae_imported_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
let defined = funcs.length()
let caller_start = if min_caller_def_inclusive > 0 &&
min_caller_def_inclusive < defined {
min_caller_def_inclusive
} else if min_caller_def_inclusive >= defined {
defined
} else {
0
}
let caller_limit = if max_caller_def_exclusive >= caller_start &&
max_caller_def_exclusive < defined {
max_caller_def_exclusive
} else {
defined
}
let type_indices = dae_collect_func_type_indices(mod_)
let selected = Array::make(defined, false)
let mut caller_def = caller_start
while caller_def < caller_limit {
let wrapper_abs = imported + caller_def
let wrapper_type = match
dae_resolve_func_type(mod_, type_indices, wrapper_abs) {
Some(ft) => ft
None => {
caller_def += 1
continue
}
}
let @lib.FuncType(wrapper_params, wrapper_results) = wrapper_type
let wrapper_param_count = wrapper_params.length()
if wrapper_param_count <= 1 {
caller_def += 1
continue
}
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
if body.length() != wrapper_param_count {
caller_def += 1
continue
}
let mut forwards = true
let mut param_idx = 0
while param_idx + 1 < wrapper_param_count {
match body[param_idx] {
@lib.LocalGet(local_idx) if dae_local_idx_slot(local_idx) ==
param_idx + 1 => ()
_ => forwards = false
}
param_idx += 1
}
if !forwards {
caller_def += 1
continue
}
match body[wrapper_param_count - 1] {
@lib.Call(func_idx) => {
let callee_abs = dae_func_idx_slot(func_idx)
let callee_def = callee_abs - imported
if callee_def >= caller_limit && callee_def < defined {
match dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(callee_params, callee_results)) if callee_params.length() +
1 ==
wrapper_param_count &&
callee_results == wrapper_results => selected[callee_def] = true
_ => ()
}
}
}
_ => ()
}
caller_def += 1
}
selected
}
///|
fn dae_find_shifted_forward_wrapper_callee_def(
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
wrapper_def : Int32,
) -> Int32 {
let imported = dae_imported_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return -1
}
let defined = funcs.length()
if wrapper_def < 0 || wrapper_def >= defined {
return -1
}
let caller_limit = wrapper_def + 1
let wrapper_abs = imported + wrapper_def
let wrapper_type = match
dae_resolve_func_type(mod_, type_indices, wrapper_abs) {
Some(ft) => ft
None => return -1
}
let @lib.FuncType(wrapper_params, wrapper_results) = wrapper_type
let wrapper_param_count = wrapper_params.length()
if wrapper_param_count <= 1 {
return -1
}
let @lib.Func(_, @lib.Expr(body)) = funcs[wrapper_def]
if body.length() != wrapper_param_count {
return -1
}
let mut param_idx = 0
while param_idx + 1 < wrapper_param_count {
match body[param_idx] {
@lib.LocalGet(local_idx) if dae_local_idx_slot(local_idx) == param_idx + 1 =>
()
_ => return -1
}
param_idx += 1
}
match body[wrapper_param_count - 1] {
@lib.Call(func_idx) => {
let callee_abs = dae_func_idx_slot(func_idx)
let callee_def = callee_abs - imported
if callee_def < caller_limit || callee_def >= defined {
return -1
}
match dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(callee_params, callee_results)) if callee_params.length() +
1 ==
wrapper_param_count &&
callee_results == wrapper_results => callee_def
_ => -1
}
}
_ => -1
}
}
///|
// Dispatcher-backed unread and localization-retry work uses the canonical
// scalar classifier and immutable rewrite plan. Legacy selected scans retain
// the older mutation fallback until their phase-local callers are retired.
fn dae_try_rewrite_selected_defs_unread_params_with_facts_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
call_facts : DaeCurrentCallFacts,
allow_stack_carried_void_effects~ : Bool = false,
require_localization_retry~ : Bool = false,
only_def~ : Int32 = -1,
type_indices_override~ : Array[@lib.TypeIdx]? = None,
original~ : DaeOriginalBoundarySnapshot? = None,
graph~ : DaeCurrentBoundaryGraph? = None,
producer_effects_report~ : Array[DaeCommitEffects] = [],
liveness_work~ : Array[Int32]? = None,
) -> DaePassResult {
let imported = dae_imported_func_count(mod_)
let boundary_facts = dae_call_boundary_facts_from_current(call_facts)
let type_indices = match type_indices_override {
Some(indices) => indices
None => dae_collect_func_type_indices(mod_)
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let selected_guard_facts = match (original, graph) {
(Some(original), Some(graph)) =>
{
original,
graph,
incoming_param_live_by_def: [],
incoming_param_live_body_epochs: [],
}
_ => dae_boundary_facts_new(mod_)
}
let scan_limit = if only_def >= 0 && only_def < funcs.length() {
only_def + 1
} else if only_def >= funcs.length() {
funcs.length()
} else if selected_defs.length() < funcs.length() {
selected_defs.length()
} else {
funcs.length()
}
let mut def_idx = if only_def >= 0 { only_def } else { 0 }
while def_idx < scan_limit {
if only_def < 0 && !selected_defs[def_idx] {
def_idx += 1
continue
}
let callee_abs = imported + def_idx
let boundary_candidate = match
dae_call_boundary_candidate(boundary_facts, callee_abs, def_idx~) {
Some(candidate) => candidate
None => {
def_idx += 1
continue
}
}
if !boundary_candidate.owned ||
boundary_candidate.has_self_direct_caller ||
!boundary_candidate.allows_nonself_direct_rewrite ||
!dae_can_change_function_boundary(selected_guard_facts, callee_abs) {
def_idx += 1
continue
}
let func_type = match
dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(ft) => ft
None => {
def_idx += 1
continue
}
}
let @lib.FuncType(params, _) = func_type
if params.length() == 0 {
def_idx += 1
continue
}
let @lib.Func(locals, _) = funcs[def_idx]
let local_use = dae_local_use_for_callee(
funcs[def_idx],
params.length() + locals.length(),
callee_abs,
params.length(),
)
let entry_live = dae_incoming_parameter_liveness(
mod_,
callee_abs,
params.length(),
syntactic_use=Some(local_use),
liveness_work~,
)
let mut param_idx = 0
while param_idx < params.length() {
local_use.gets[param_idx] = entry_live[param_idx]
param_idx += 1
}
let remove = Array::make(params.length(), false)
let const_params : Array[@lib.Instruction?] = Array::make(
params.length(),
None,
)
let mut any = false
let mut p = 0
while p < params.length() {
if !local_use.gets[p] {
remove[p] = true
any = true
}
p += 1
}
if any {
match (original, graph) {
(Some(original), Some(graph)) => {
let planned = dae_try_rewrite_scalar_unused_param_candidate(
mod_,
original,
graph,
callee_abs,
remove,
local_use,
boundary_candidate.direct_call_count,
boundary_candidate.direct_callers,
allow_stack_carried_void_effects~,
entry_live=Some(entry_live),
liveness_work~,
)
if planned.applied {
if !require_localization_retry ||
planned.effects.body_localized.any(fn(value) { value }) {
dae_record_producer_effects(
producer_effects_report,
planned.effects,
)
return planned.result
}
def_idx += 1
continue
}
def_idx += 1
continue
}
_ => ()
}
let result = dae_try_rewrite_candidate(
mod_,
callee_abs,
remove,
const_params,
local_use,
boundary_candidate.direct_call_count,
boundary_candidate.direct_callers,
entry_live=Some(entry_live),
liveness_work~,
)
if result.changed && !require_localization_retry {
return result
}
let direct_rewrite_succeeded = result.changed
let mut kept_count = 0
let mut removed_param_written = false
p = 0
while p < params.length() {
if remove[p] {
removed_param_written = removed_param_written || local_use.writes[p]
} else {
kept_count += 1
}
p += 1
}
if allow_stack_carried_void_effects &&
!direct_rewrite_succeeded &&
kept_count == 1 &&
!removed_param_written {
let stack_carried = dae_try_rewrite_candidate(
mod_,
callee_abs,
remove,
const_params,
local_use,
boundary_candidate.direct_call_count,
boundary_candidate.direct_callers,
allow_stack_carried_void_effects=true,
entry_live=Some(entry_live),
liveness_work~,
)
if stack_carried.changed {
return stack_carried
}
}
}
def_idx += 1
}
{ mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
///|
fn dae_try_rewrite_unread_params_for_def_with_facts(
mod_ : @lib.Module,
def_idx : Int32,
call_facts : DaeCurrentCallFacts,
allow_stack_carried_void_effects~ : Bool = false,
require_localization_retry~ : Bool = false,
type_indices_override~ : Array[@lib.TypeIdx]? = None,
original~ : DaeOriginalBoundarySnapshot? = None,
graph~ : DaeCurrentBoundaryGraph? = None,
producer_effects_report~ : Array[DaeCommitEffects] = [],
) -> DaePassResult {
dae_try_rewrite_selected_defs_unread_params_with_facts_once(
mod_,
[],
call_facts,
allow_stack_carried_void_effects~,
require_localization_retry~,
only_def=def_idx,
type_indices_override~,
original~,
graph~,
producer_effects_report~,
)
}
///|
fn dae_fold_const_i32_eq_if_instrs_once(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
if idx + 3 < instrs.length() {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2], instrs[idx + 3]) {
(
@lib.I32Const(@lib.I32(lhs)),
@lib.I32Const(@lib.I32(rhs)),
@lib.I32Eq,
@lib.If(block_type, then_body, else_body),
) => {
if lhs == rhs {
dae_append_selected_if_body(out, block_type, then_body)
} else {
match else_body {
Some(body) => dae_append_selected_if_body(out, block_type, body)
None => ()
}
}
changed = true
idx += 4
continue
}
(
@lib.I64Const(@lib.I64(lhs)),
@lib.I64Const(@lib.I64(rhs)),
@lib.I64Eq,
@lib.If(block_type, then_body, else_body),
) => {
if lhs == rhs {
dae_append_selected_if_body(out, block_type, then_body)
} else {
match else_body {
Some(body) => dae_append_selected_if_body(out, block_type, body)
None => ()
}
}
changed = true
idx += 4
continue
}
_ => ()
}
}
out.push(instrs[idx])
idx += 1
}
(out, changed)
}
///|
fn dae_fold_const_eq_if_instrs_until_stable(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
let mut current = instrs
while true {
let (next, changed) = dae_fold_const_i32_eq_if_instrs_once(current)
if !changed {
return current
}
current = next
}
current
}
///|
fn dae_instr_is_droppable_pure_leaf(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _)
| @lib.RefNull(_)
| @lib.LocalGet(_)
| @lib.GlobalGet(_)
| @lib.MemorySize(_)
| @lib.TableSize(_) => true
_ => false
}
}
///|
fn dae_instr_is_nontrapping_pure_unary(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I32Eqz
| @lib.I64Eqz
| @lib.I32Clz
| @lib.I32Ctz
| @lib.I32Popcnt
| @lib.I64Clz
| @lib.I64Ctz
| @lib.I64Popcnt
| @lib.F32Abs
| @lib.F32Neg
| @lib.F32Ceil
| @lib.F32Floor
| @lib.F32Trunc
| @lib.F32Nearest
| @lib.F32Sqrt
| @lib.F64Abs
| @lib.F64Neg
| @lib.F64Ceil
| @lib.F64Floor
| @lib.F64Trunc
| @lib.F64Nearest
| @lib.F64Sqrt
| @lib.I32WrapI64
| @lib.I64ExtendI32S
| @lib.I64ExtendI32U
| @lib.F32ConvertI32S
| @lib.F32ConvertI32U
| @lib.F32ConvertI64S
| @lib.F32ConvertI64U
| @lib.F32DemoteF64
| @lib.F64ConvertI32S
| @lib.F64ConvertI32U
| @lib.F64ConvertI64S
| @lib.F64ConvertI64U
| @lib.F64PromoteF32
| @lib.I32ReinterpretF32
| @lib.I64ReinterpretF64
| @lib.F32ReinterpretI32
| @lib.F64ReinterpretI64
| @lib.I32Extend8S
| @lib.I32Extend16S
| @lib.I64Extend8S
| @lib.I64Extend16S
| @lib.I64Extend32S
| @lib.I32TruncSatF32S
| @lib.I32TruncSatF32U
| @lib.I32TruncSatF64S
| @lib.I32TruncSatF64U
| @lib.I64TruncSatF32S
| @lib.I64TruncSatF32U
| @lib.I64TruncSatF64S
| @lib.I64TruncSatF64U
| @lib.V128Not
| @lib.I8x16Splat
| @lib.I16x8Splat
| @lib.I32x4Splat
| @lib.I64x2Splat
| @lib.F32x4Splat
| @lib.F64x2Splat
| @lib.I16x8ExtaddPairwiseI8x16S
| @lib.I16x8ExtaddPairwiseI8x16U
| @lib.I32x4ExtaddPairwiseI16x8S
| @lib.I32x4ExtaddPairwiseI16x8U
| @lib.I16x8ExtendLowI8x16S
| @lib.I16x8ExtendHighI8x16S
| @lib.I16x8ExtendLowI8x16U
| @lib.I16x8ExtendHighI8x16U
| @lib.I32x4ExtendLowI16x8S
| @lib.I32x4ExtendHighI16x8S
| @lib.I32x4ExtendLowI16x8U
| @lib.I32x4ExtendHighI16x8U
| @lib.I64x2ExtendLowI32x4S
| @lib.I64x2ExtendHighI32x4S
| @lib.I64x2ExtendLowI32x4U
| @lib.I64x2ExtendHighI32x4U
| @lib.I8x16ExtractLaneS(_)
| @lib.I8x16ExtractLaneU(_)
| @lib.I16x8ExtractLaneS(_)
| @lib.I16x8ExtractLaneU(_)
| @lib.I32x4ExtractLane(_)
| @lib.I64x2ExtractLane(_)
| @lib.F32x4ExtractLane(_)
| @lib.F64x2ExtractLane(_)
| @lib.I8x16Abs
| @lib.I8x16Neg
| @lib.I8x16Popcnt
| @lib.I16x8Abs
| @lib.I16x8Neg
| @lib.I32x4Abs
| @lib.I32x4Neg
| @lib.I64x2Abs
| @lib.I64x2Neg
| @lib.F32x4Abs
| @lib.F32x4Neg
| @lib.F32x4Sqrt
| @lib.F32x4Ceil
| @lib.F32x4Floor
| @lib.F32x4Trunc
| @lib.F32x4Nearest
| @lib.F64x2Abs
| @lib.F64x2Neg
| @lib.F64x2Sqrt
| @lib.F64x2Ceil
| @lib.F64x2Floor
| @lib.F64x2Trunc
| @lib.F64x2Nearest
| @lib.F32x4DemoteF64x2Zero
| @lib.F64x2PromoteLowF32x4
| @lib.I32x4TruncSatF32x4S
| @lib.I32x4TruncSatF32x4U
| @lib.F32x4ConvertI32x4S
| @lib.F32x4ConvertI32x4U
| @lib.I32x4TruncSatF64x2SZero
| @lib.I32x4TruncSatF64x2UZero
| @lib.F64x2ConvertLowI32x4S
| @lib.F64x2ConvertLowI32x4U
| @lib.V128AnyTrue
| @lib.I8x16AllTrue
| @lib.I8x16Bitmask
| @lib.I16x8AllTrue
| @lib.I16x8Bitmask
| @lib.I32x4AllTrue
| @lib.I32x4Bitmask
| @lib.I64x2AllTrue
| @lib.I64x2Bitmask
| @lib.RefIsNull
| @lib.RefTest(_, _, _)
| @lib.RefTestDesc(_, _)
| @lib.RefI31
| @lib.AnyConvertExtern
| @lib.ExternConvertAny => true
_ => false
}
}
///|
fn dae_f64_const_can_trunc_to_i32_s(value : Double) -> Bool {
if @ir.compat_is_f64_non_finite(value) {
return false
}
let truncated = value.trunc()
truncated >= -2147483648.0 && truncated < 2147483648.0
}
///|
fn dae_f64_const_can_trunc_to_i32_u(value : Double) -> Bool {
if @ir.compat_is_f64_non_finite(value) {
return false
}
let truncated = value.trunc()
truncated >= 0.0 && truncated < 4294967296.0
}
///|
fn dae_const_can_trunc_to_i64_s(value : Double) -> Bool {
match @ir.compat_trunc_f64_to_i64_s(value) {
Some(_) => true
None => false
}
}
///|
fn dae_const_can_trunc_to_i64_u(value : Double) -> Bool {
match @ir.compat_trunc_f64_to_i64_u(value) {
Some(_) => true
None => false
}
}
///|
fn dae_instr_is_const_safe_float_to_int_unary(
input : @lib.Instruction,
instr : @lib.Instruction,
) -> Bool {
match (input, instr) {
(@lib.F32Const(@lib.F32(value)), @lib.I32TruncF32S) =>
!@ir.compat_is_f32_nan(value) &&
dae_f64_const_can_trunc_to_i32_s(value.to_double())
(@lib.F32Const(@lib.F32(value)), @lib.I32TruncF32U) =>
!@ir.compat_is_f32_nan(value) &&
dae_f64_const_can_trunc_to_i32_u(value.to_double())
(@lib.F32Const(@lib.F32(value)), @lib.I64TruncF32S) =>
!@ir.compat_is_f32_nan(value) &&
dae_const_can_trunc_to_i64_s(value.to_double())
(@lib.F32Const(@lib.F32(value)), @lib.I64TruncF32U) =>
!@ir.compat_is_f32_nan(value) &&
dae_const_can_trunc_to_i64_u(value.to_double())
(@lib.F64Const(@lib.F64(value)), @lib.I32TruncF64S) =>
dae_f64_const_can_trunc_to_i32_s(value)
(@lib.F64Const(@lib.F64(value)), @lib.I32TruncF64U) =>
dae_f64_const_can_trunc_to_i32_u(value)
(@lib.F64Const(@lib.F64(value)), @lib.I64TruncF64S) =>
dae_const_can_trunc_to_i64_s(value)
(@lib.F64Const(@lib.F64(value)), @lib.I64TruncF64U) =>
dae_const_can_trunc_to_i64_u(value)
_ => false
}
}
///|
fn dae_heap_type_accepts_i31_ref(ht : @lib.HeapType) -> Bool {
match ht {
@lib.AbsHeapTypeHeapType(
@lib.I31AbsHeapType
| @lib.EqAbsHeapType
| @lib.AnyAbsHeapType
) => true
_ => false
}
}
///|
fn dae_instr_is_i31_success_ref_cast(instr : @lib.Instruction) -> Bool {
match instr {
@lib.RefCast(_, _, ht) => dae_heap_type_accepts_i31_ref(ht)
_ => false
}
}
///|
fn dae_instr_is_nullable_ref_cast(instr : @lib.Instruction) -> Bool {
match instr {
@lib.RefCast(true, _, _) => true
_ => false
}
}
///|
fn dae_instr_is_nontrapping_pure_binary(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I32Eq
| @lib.I32Ne
| @lib.I32LtS
| @lib.I32LtU
| @lib.I32GtS
| @lib.I32GtU
| @lib.I32LeS
| @lib.I32LeU
| @lib.I32GeS
| @lib.I32GeU
| @lib.I64Eq
| @lib.I64Ne
| @lib.I64LtS
| @lib.I64LtU
| @lib.I64GtS
| @lib.I64GtU
| @lib.I64LeS
| @lib.I64LeU
| @lib.I64GeS
| @lib.I64GeU
| @lib.F32Eq
| @lib.F32Ne
| @lib.F32Lt
| @lib.F32Gt
| @lib.F32Le
| @lib.F32Ge
| @lib.F64Eq
| @lib.F64Ne
| @lib.F64Lt
| @lib.F64Gt
| @lib.F64Le
| @lib.F64Ge
| @lib.I32Add
| @lib.I32Sub
| @lib.I32Mul
| @lib.I32And
| @lib.I32Or
| @lib.I32Xor
| @lib.I32Shl
| @lib.I32ShrS
| @lib.I32ShrU
| @lib.I32Rotl
| @lib.I32Rotr
| @lib.I64Add
| @lib.I64Sub
| @lib.I64Mul
| @lib.I64And
| @lib.I64Or
| @lib.I64Xor
| @lib.I64Shl
| @lib.I64ShrS
| @lib.I64ShrU
| @lib.I64Rotl
| @lib.I64Rotr
| @lib.F32Add
| @lib.F32Sub
| @lib.F32Mul
| @lib.F32Div
| @lib.F32Min
| @lib.F32Max
| @lib.F32Copysign
| @lib.F64Add
| @lib.F64Sub
| @lib.F64Mul
| @lib.F64Div
| @lib.F64Min
| @lib.F64Max
| @lib.F64Copysign
| @lib.V128And
| @lib.V128Andnot
| @lib.V128Or
| @lib.V128Xor
| @lib.I8x16Shuffle(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _)
| @lib.I8x16Swizzle
| @lib.I8x16NarrowI16x8S
| @lib.I8x16NarrowI16x8U
| @lib.I16x8NarrowI32x4S
| @lib.I16x8NarrowI32x4U
| @lib.I16x8ExtmulLowI8x16S
| @lib.I16x8ExtmulHighI8x16S
| @lib.I16x8ExtmulLowI8x16U
| @lib.I16x8ExtmulHighI8x16U
| @lib.I32x4DotI16x8S
| @lib.I32x4ExtmulLowI16x8S
| @lib.I32x4ExtmulHighI16x8S
| @lib.I32x4ExtmulLowI16x8U
| @lib.I32x4ExtmulHighI16x8U
| @lib.I64x2ExtmulLowI32x4S
| @lib.I64x2ExtmulHighI32x4S
| @lib.I64x2ExtmulLowI32x4U
| @lib.I64x2ExtmulHighI32x4U
| @lib.I8x16ReplaceLane(_)
| @lib.I16x8ReplaceLane(_)
| @lib.I32x4ReplaceLane(_)
| @lib.I64x2ReplaceLane(_)
| @lib.F32x4ReplaceLane(_)
| @lib.F64x2ReplaceLane(_)
| @lib.I8x16Shl
| @lib.I8x16ShrS
| @lib.I8x16ShrU
| @lib.I16x8Shl
| @lib.I16x8ShrS
| @lib.I16x8ShrU
| @lib.I32x4Shl
| @lib.I32x4ShrS
| @lib.I32x4ShrU
| @lib.I64x2Shl
| @lib.I64x2ShrS
| @lib.I64x2ShrU
| @lib.I8x16Eq
| @lib.I8x16Ne
| @lib.I8x16LtS
| @lib.I8x16LtU
| @lib.I8x16GtS
| @lib.I8x16GtU
| @lib.I8x16LeS
| @lib.I8x16LeU
| @lib.I8x16GeS
| @lib.I8x16GeU
| @lib.I16x8Eq
| @lib.I16x8Ne
| @lib.I16x8LtS
| @lib.I16x8LtU
| @lib.I16x8GtS
| @lib.I16x8GtU
| @lib.I16x8LeS
| @lib.I16x8LeU
| @lib.I16x8GeS
| @lib.I16x8GeU
| @lib.I32x4Eq
| @lib.I32x4Ne
| @lib.I32x4LtS
| @lib.I32x4LtU
| @lib.I32x4GtS
| @lib.I32x4GtU
| @lib.I32x4LeS
| @lib.I32x4LeU
| @lib.I32x4GeS
| @lib.I32x4GeU
| @lib.I64x2Eq
| @lib.I64x2Ne
| @lib.I64x2LtS
| @lib.I64x2GtS
| @lib.I64x2LeS
| @lib.I64x2GeS
| @lib.F32x4Eq
| @lib.F32x4Ne
| @lib.F32x4Lt
| @lib.F32x4Gt
| @lib.F32x4Le
| @lib.F32x4Ge
| @lib.F64x2Eq
| @lib.F64x2Ne
| @lib.F64x2Lt
| @lib.F64x2Gt
| @lib.F64x2Le
| @lib.F64x2Ge
| @lib.I8x16Add
| @lib.I8x16AddSatS
| @lib.I8x16AddSatU
| @lib.I8x16Sub
| @lib.I8x16SubSatS
| @lib.I8x16SubSatU
| @lib.I8x16MinS
| @lib.I8x16MinU
| @lib.I8x16MaxS
| @lib.I8x16MaxU
| @lib.I8x16AvgrU
| @lib.I16x8Add
| @lib.I16x8AddSatS
| @lib.I16x8AddSatU
| @lib.I16x8Sub
| @lib.I16x8SubSatS
| @lib.I16x8SubSatU
| @lib.I16x8Mul
| @lib.I16x8MinS
| @lib.I16x8MinU
| @lib.I16x8MaxS
| @lib.I16x8MaxU
| @lib.I16x8AvgrU
| @lib.I16x8Q15mulrSatS
| @lib.I32x4Add
| @lib.I32x4Sub
| @lib.I32x4Mul
| @lib.I32x4MinS
| @lib.I32x4MinU
| @lib.I32x4MaxS
| @lib.I32x4MaxU
| @lib.I64x2Add
| @lib.I64x2Sub
| @lib.I64x2Mul
| @lib.F32x4Add
| @lib.F32x4Sub
| @lib.F32x4Mul
| @lib.F32x4Div
| @lib.F32x4Min
| @lib.F32x4Max
| @lib.F32x4Pmin
| @lib.F32x4Pmax
| @lib.F64x2Add
| @lib.F64x2Sub
| @lib.F64x2Mul
| @lib.F64x2Div
| @lib.F64x2Min
| @lib.F64x2Max
| @lib.F64x2Pmin
| @lib.F64x2Pmax
| @lib.RefEq => true
_ => false
}
}
///|
fn dae_instr_is_const_safe_numeric_binary(
lhs : @lib.Instruction,
rhs : @lib.Instruction,
instr : @lib.Instruction,
) -> Bool {
match (lhs, rhs, instr) {
(@lib.I32Const(@lib.I32(a)), @lib.I32Const(@lib.I32(b)), @lib.I32DivS) =>
b != 0 && !(a == -2147483648 && b == -1)
(@lib.I32Const(_), @lib.I32Const(@lib.I32(b)), @lib.I32DivU) => b != 0
(@lib.I32Const(_), @lib.I32Const(@lib.I32(b)), @lib.I32RemS) => b != 0
(@lib.I32Const(_), @lib.I32Const(@lib.I32(b)), @lib.I32RemU) => b != 0
(@lib.I64Const(@lib.I64(a)), @lib.I64Const(@lib.I64(b)), @lib.I64DivS) =>
b != 0L && !(a == -9223372036854775808L && b == -1L)
(@lib.I64Const(_), @lib.I64Const(@lib.I64(b)), @lib.I64DivU) => b != 0L
(@lib.I64Const(_), @lib.I64Const(@lib.I64(b)), @lib.I64RemS) => b != 0L
(@lib.I64Const(_), @lib.I64Const(@lib.I64(b)), @lib.I64RemU) => b != 0L
_ => false
}
}
///|
fn dae_instr_is_integer_right_constant_identity(
rhs : @lib.Instruction,
instr : @lib.Instruction,
) -> Bool {
match (rhs, instr) {
(@lib.I32Const(@lib.I32(0)), @lib.I32Add)
| (@lib.I32Const(@lib.I32(0)), @lib.I32Sub)
| (@lib.I32Const(@lib.I32(0)), @lib.I32Or)
| (@lib.I32Const(@lib.I32(0)), @lib.I32Xor)
| (@lib.I32Const(@lib.I32(0)), @lib.I32Shl)
| (@lib.I32Const(@lib.I32(0)), @lib.I32ShrS)
| (@lib.I32Const(@lib.I32(0)), @lib.I32ShrU)
| (@lib.I32Const(@lib.I32(0)), @lib.I32Rotl)
| (@lib.I32Const(@lib.I32(0)), @lib.I32Rotr)
| (@lib.I32Const(@lib.I32(1)), @lib.I32Mul)
| (@lib.I32Const(@lib.I32(-1)), @lib.I32And)
| (@lib.I64Const(@lib.I64(0L)), @lib.I64Add)
| (@lib.I64Const(@lib.I64(0L)), @lib.I64Sub)
| (@lib.I64Const(@lib.I64(0L)), @lib.I64Or)
| (@lib.I64Const(@lib.I64(0L)), @lib.I64Xor)
| (@lib.I64Const(@lib.I64(0L)), @lib.I64Shl)
| (@lib.I64Const(@lib.I64(0L)), @lib.I64ShrS)
| (@lib.I64Const(@lib.I64(0L)), @lib.I64ShrU)
| (@lib.I64Const(@lib.I64(0L)), @lib.I64Rotl)
| (@lib.I64Const(@lib.I64(0L)), @lib.I64Rotr)
| (@lib.I64Const(@lib.I64(1L)), @lib.I64Mul)
| (@lib.I64Const(@lib.I64(-1L)), @lib.I64And) => true
_ => false
}
}
///|
fn dae_instr_is_stack_neutral_value_leaf(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.V128Const(_)
| @lib.RefNull(_)
| @lib.LocalGet(_)
| @lib.GlobalGet(_)
| @lib.MemorySize(_)
| @lib.TableSize(_) => true
_ => false
}
}
///|
fn dae_instr_is_integer_left_constant_identity(
lhs : @lib.Instruction,
instr : @lib.Instruction,
) -> Bool {
match (lhs, instr) {
(@lib.I32Const(@lib.I32(0)), @lib.I32Add)
| (@lib.I32Const(@lib.I32(0)), @lib.I32Or)
| (@lib.I32Const(@lib.I32(0)), @lib.I32Xor)
| (@lib.I32Const(@lib.I32(1)), @lib.I32Mul)
| (@lib.I32Const(@lib.I32(-1)), @lib.I32And)
| (@lib.I64Const(@lib.I64(0L)), @lib.I64Add)
| (@lib.I64Const(@lib.I64(0L)), @lib.I64Or)
| (@lib.I64Const(@lib.I64(0L)), @lib.I64Xor)
| (@lib.I64Const(@lib.I64(1L)), @lib.I64Mul)
| (@lib.I64Const(@lib.I64(-1L)), @lib.I64And) => true
_ => false
}
}
///|
fn dae_instr_integer_zero_compare_replacement(
zero : @lib.Instruction,
instr : @lib.Instruction,
) -> Array[@lib.Instruction]? {
match (zero, instr) {
// Binaryen keeps equality-to-zero in this raw-cleanup frontier as an
// explicit compare, while inequality-to-zero is commonly booleanized.
// Preserve that split to avoid introducing smaller but non-parity `eqz`
// shape in live value/control positions.
(@lib.I32Const(@lib.I32(0)), @lib.I32Ne) =>
Some([@lib.Instruction::i32_eqz(), @lib.Instruction::i32_eqz()])
(@lib.I64Const(@lib.I64(0L)), @lib.I64Ne) =>
Some([@lib.Instruction::i64_eqz(), @lib.Instruction::i32_eqz()])
_ => None
}
}
///|
fn dae_push_instrs(
out : Array[@lib.Instruction],
instrs : Array[@lib.Instruction],
) -> Unit {
for instr in instrs {
out.push(instr)
}
}
///|
fn dae_instrs_read_same_local_or_global(
lhs : @lib.Instruction,
rhs : @lib.Instruction,
) -> Bool {
match (lhs, rhs) {
(@lib.LocalGet(a), @lib.LocalGet(b)) => dae_local_idx_equal(a, b)
(@lib.GlobalGet(a), @lib.GlobalGet(b)) => dae_global_idx_equal(a, b)
_ => false
}
}
///|
fn dae_instr_self_comparison_replacement(
lhs : @lib.Instruction,
rhs : @lib.Instruction,
instr : @lib.Instruction,
) -> @lib.Instruction? {
if !dae_instrs_read_same_local_or_global(lhs, rhs) {
return None
}
match instr {
@lib.I32Eq
| @lib.I32LeS
| @lib.I32LeU
| @lib.I32GeS
| @lib.I32GeU
| @lib.I64Eq
| @lib.I64LeS
| @lib.I64LeU
| @lib.I64GeS
| @lib.I64GeU
| @lib.RefEq => Some(@lib.Instruction::i32_const(@lib.I32(1)))
@lib.I32Ne
| @lib.I32LtS
| @lib.I32LtU
| @lib.I32GtS
| @lib.I32GtU
| @lib.I64Ne
| @lib.I64LtS
| @lib.I64LtU
| @lib.I64GtS
| @lib.I64GtU => Some(@lib.Instruction::i32_const(@lib.I32(0)))
_ => None
}
}
///|
fn dae_instr_self_integer_binary_replacement(
lhs : @lib.Instruction,
rhs : @lib.Instruction,
instr : @lib.Instruction,
) -> Array[@lib.Instruction]? {
if !dae_instrs_read_same_local_or_global(lhs, rhs) {
return None
}
match instr {
@lib.I32And | @lib.I32Or | @lib.I64And | @lib.I64Or => Some([lhs])
@lib.I32Xor | @lib.I32Sub =>
Some([@lib.Instruction::i32_const(@lib.I32(0))])
@lib.I64Xor | @lib.I64Sub =>
Some([@lib.Instruction::i64_const(@lib.I64(0L))])
_ => None
}
}
///|
fn dae_instr_is_nontrapping_pure_tee_suffix_binary(
rhs : @lib.Instruction,
instr : @lib.Instruction,
) -> Bool {
dae_instr_is_droppable_pure_leaf(rhs) &&
dae_instr_is_nontrapping_pure_binary(instr)
}
///|
fn dae_block_type_zero_param_result_count(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
) -> Int32? {
match block_type {
@lib.VoidBlockType => Some(0)
@lib.ValTypeBlockType(_) => Some(1)
@lib.TypeIdxBlockType(type_idx) =>
match type_sec {
Some(type_sec) =>
match dae_type_sec_func_type(type_sec, type_idx) {
Some(@lib.FuncType(params, results)) if params.length() == 0 =>
Some(results.length())
_ => None
}
None => None
}
}
}
///|
fn dae_block_type_is_zero_param_single_result(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
) -> Bool {
dae_control_block_type_stack_shape_accepts(block_type, type_sec, 0, 1)
}
///|
fn dae_block_type_is_zero_param_zero_result(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
) -> Bool {
dae_control_block_type_stack_shape_accepts(block_type, type_sec, 0, 0)
}
///|
fn dae_droppable_pure_control_body_accepts(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Bool {
match dae_find_droppable_pure_stack_expr_start(body, type_sec) {
Some(0) => true
_ => false
}
}
///|
fn dae_droppable_pure_control_value_evidence_is_consistent(
evidence : DaeDroppablePureControlValueEvidence,
) -> Bool {
evidence.input_count == 0 &&
evidence.output_count == 1 &&
(evidence.condition_arity == 0 || evidence.condition_arity == 1) &&
evidence.body_count > 0 &&
evidence.primary_body_length >= 0 &&
evidence.secondary_body_length >= 0 &&
evidence.catch_count >= 0
}
///|
fn dae_droppable_pure_control_value_evidence(
instr : @lib.Instruction,
type_sec : @lib.TypeSec?,
) -> DaeDroppablePureControlValueEvidence? {
match instr {
@lib.Block(block_type, @lib.Expr(body))
| @lib.Loop(block_type, @lib.Expr(body)) => {
let signature = match
dae_control_block_type_zero_input_output_count_evidence(
block_type, type_sec, 1,
) {
Some(evidence) => evidence
None => return None
}
if !dae_droppable_pure_control_body_accepts(body, type_sec) {
return None
}
let evidence = {
input_count: signature.input_count,
output_count: signature.output_count,
condition_arity: 0,
body_count: 1,
primary_body_length: body.length(),
secondary_body_length: 0,
catch_count: 0,
}
if dae_droppable_pure_control_value_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let signature = match
dae_control_block_type_zero_input_output_count_evidence(
block_type, type_sec, 1,
) {
Some(evidence) => evidence
None => return None
}
if catches.length() != 0 ||
!dae_droppable_pure_control_body_accepts(body, type_sec) {
return None
}
let evidence = {
input_count: signature.input_count,
output_count: signature.output_count,
condition_arity: 0,
body_count: 1,
primary_body_length: body.length(),
secondary_body_length: 0,
catch_count: catches.length(),
}
if dae_droppable_pure_control_value_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
@lib.If(block_type, then_body, Some(else_body)) => {
let signature = match
dae_control_block_type_zero_input_output_count_evidence(
block_type, type_sec, 1,
) {
Some(evidence) => evidence
None => return None
}
if !dae_droppable_pure_control_body_accepts(then_body, type_sec) ||
!dae_droppable_pure_control_body_accepts(else_body, type_sec) {
return None
}
let evidence = {
input_count: signature.input_count,
output_count: signature.output_count,
condition_arity: 1,
body_count: 2,
primary_body_length: then_body.length(),
secondary_body_length: else_body.length(),
catch_count: 0,
}
if dae_droppable_pure_control_value_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
_ => None
}
}
///|
fn dae_instr_droppable_pure_if_value(
instr : @lib.Instruction,
type_sec : @lib.TypeSec?,
) -> Bool {
match dae_droppable_pure_control_value_evidence(instr, type_sec) {
Some(evidence) => evidence.condition_arity == 1
None => false
}
}
///|
fn dae_instr_dropped_pure_value_arity(
instr : @lib.Instruction,
type_sec : @lib.TypeSec?,
) -> Int32? {
if dae_instr_is_droppable_pure_leaf(instr) {
return Some(0)
}
match dae_droppable_pure_control_value_evidence(instr, type_sec) {
Some(evidence) => return Some(evidence.condition_arity)
None => ()
}
if dae_instr_is_nontrapping_pure_unary(instr) {
return Some(1)
}
if dae_instr_is_nontrapping_pure_binary(instr) {
return Some(2)
}
match instr {
@lib.Select(_) | @lib.V128Bitselect => Some(3)
_ => None
}
}
///|
#inline
fn dae_find_droppable_pure_stack_expr_start(
out : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Int32? {
dae_find_droppable_pure_stack_expr_start_with_scalar_needs(out, type_sec)
}
///|
fn dae_find_droppable_pure_stack_expr_start_with_scalar_needs(
out : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
need_stacks~ : Array[Int32]? = None,
) -> Int32? {
dae_find_droppable_pure_stack_expr_start_from_end(
out,
type_sec,
out.length(),
need_stacks,
)
}
///|
// Borrow an operand range ending before the call without allocating a prefix
// copy. The caller accepts the proof only when its start equals the owned slice.
#inline
fn dae_find_droppable_pure_stack_expr_start_from_end(
out : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
end : Int32,
need_stacks : Array[Int32]?,
) -> Int32? {
dae_find_droppable_pure_stack_expr_start_in_range(
out, type_sec, 0, end, need_stacks,
)
}
///|
fn dae_find_droppable_pure_stack_expr_start_in_range(
out : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
minimum_start : Int32,
end : Int32,
need_stacks : Array[Int32]?,
) -> Int32? {
if minimum_start < 0 || minimum_start >= end || end > out.length() {
return None
}
// Ordinary arithmetic needs only the pending operand count. Allocate an
// ordered marker stack when reference trap proofs require a specific root.
let mut pending = 1
for start = end - 1; start >= minimum_start; start = start - 1 {
pending -= 1
if dae_instr_is_droppable_pure_leaf(out[start]) {
if pending == 0 {
return Some(start)
}
continue
}
if pending == 0 &&
start > minimum_start &&
dae_instr_is_const_safe_float_to_int_unary(out[start - 1], out[start]) {
return Some(start - 1)
}
if pending == 0 &&
start - minimum_start >= 2 &&
dae_instr_is_const_safe_numeric_binary(
out[start - 2],
out[start - 1],
out[start],
) {
return Some(start - 2)
}
let marker = match out[start] {
@lib.I31GetS | @lib.I31GetU | @lib.RefAsNonNull => 1
@lib.RefCast(true, _, ht) if dae_heap_type_accepts_i31_ref(ht) => 3
instr if dae_instr_is_i31_success_ref_cast(instr) => 1
instr if dae_instr_is_nullable_ref_cast(instr) => 2
_ => 0
}
if marker != 0 {
let needs = Array::make(pending + 1, 0)
needs[pending] = marker
match need_stacks {
Some(count) => count[0] += 1
None => ()
}
return dae_find_droppable_marked_stack_expr_start(
out,
type_sec,
start - 1,
needs,
minimum_start~,
)
}
pending += match dae_instr_dropped_pure_value_arity(out[start], type_sec) {
Some(arity) => arity
None => return None
}
if pending == 0 {
return Some(start)
}
}
None
}
///|
// The scalar scan hands over its owned ordered stack exactly once. This keeps
// reference proofs on the original array path without an optional-array branch
// at every producer, and preserves the pending arithmetic sibling operands.
fn dae_find_droppable_marked_stack_expr_start(
out : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
first : Int32,
needs : Array[Int32],
minimum_start~ : Int32 = 0,
) -> Int32? {
if minimum_start < 0 || first >= out.length() {
return None
}
for start = first; start >= minimum_start; start = start - 1 {
let need = match needs.pop() {
Some(marker) => marker
None => return None
}
if need == 1 {
match out[start] {
@lib.RefI31 => needs.push(0)
instr if dae_instr_is_i31_success_ref_cast(instr) => needs.push(1)
_ => return None
}
} else if need == 2 {
match out[start] {
@lib.RefNull(_) => ()
instr if dae_instr_is_nullable_ref_cast(instr) => needs.push(2)
_ => return None
}
} else if need == 3 {
match out[start] {
@lib.RefNull(_) => ()
@lib.RefI31 => needs.push(0)
@lib.RefCast(true, _, ht) if dae_heap_type_accepts_i31_ref(ht) =>
needs.push(3)
instr if dae_instr_is_nullable_ref_cast(instr) => needs.push(2)
_ => return None
}
} else if dae_instr_is_droppable_pure_leaf(out[start]) {
if needs.is_empty() {
return Some(start)
}
continue
} else {
if needs.is_empty() &&
start > minimum_start &&
dae_instr_is_const_safe_float_to_int_unary(out[start - 1], out[start]) {
return Some(start - 1)
}
if needs.is_empty() &&
start - minimum_start >= 2 &&
dae_instr_is_const_safe_numeric_binary(
out[start - 2],
out[start - 1],
out[start],
) {
return Some(start - 2)
}
match out[start] {
@lib.I31GetS | @lib.I31GetU | @lib.RefAsNonNull => needs.push(1)
@lib.RefCast(true, _, ht) if dae_heap_type_accepts_i31_ref(ht) =>
needs.push(3)
instr if dae_instr_is_i31_success_ref_cast(instr) => needs.push(1)
instr if dae_instr_is_nullable_ref_cast(instr) => needs.push(2)
_ => {
let arity = match
dae_instr_dropped_pure_value_arity(out[start], type_sec) {
Some(arity) => arity
None => return None
}
for _ in 0.. Bool {
evidence.start >= 0 &&
evidence.start <= evidence.end &&
evidence.singleton == (evidence.end - evidence.start == 1) &&
evidence.local_free == !evidence.has_local_get &&
evidence.range_allows_cleanup == (evidence.singleton || evidence.local_free)
}
///|
fn dae_instr_dropped_pure_multivalue_block_prefix(
instr : @lib.Instruction,
type_sec : @lib.TypeSec?,
) -> (Int32, Array[@lib.Instruction])? {
let (block_type, body) = match instr {
@lib.Block(block_type, @lib.Expr(body)) => (block_type, body)
_ => return None
}
let result_count = match
dae_block_type_zero_param_result_count(block_type, type_sec) {
Some(count) if count > 1 => count
_ => return None
}
let mut remaining = body.length()
let mut values = 0
while remaining > 0 && values < result_count {
remaining = match
dae_find_droppable_pure_stack_expr_start_in_range(
body,
type_sec,
0,
remaining,
None,
) {
Some(start) => start
None => return None
}
values += 1
}
if values != result_count {
return None
}
let prefix : Array[@lib.Instruction] = []
for idx = 0; idx < remaining; idx = idx + 1 {
if dae_instr_has_branch(body[idx]) {
return None
}
prefix.push(body[idx])
}
Some((result_count, prefix))
}
///|
fn dae_strip_dropped_pure_multivalue_blocks(
instrs : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
let mut idx = 0
while idx < instrs.length() {
let prefix = dae_instr_dropped_pure_multivalue_block_prefix(
instrs[idx],
type_sec,
)
match prefix {
Some((result_count, prefix)) =>
if idx + result_count < instrs.length() {
let mut all_dropped = true
for offset = 1; offset <= result_count; offset = offset + 1 {
if instrs[idx + offset] != @lib.Instruction::drop() {
all_dropped = false
break
}
}
if all_dropped {
dae_push_instrs(out, prefix)
idx += result_count + 1
continue
}
}
None => ()
}
out.push(instrs[idx])
idx += 1
}
out
}
///|
fn dae_strip_dropped_pure_multivalue_blocks_recursive(
instrs : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction] {
let rewritten : Array[@lib.Instruction] = []
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) =>
rewritten.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(
dae_strip_dropped_pure_multivalue_blocks_recursive(body, type_sec),
),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
rewritten.push(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
dae_strip_dropped_pure_multivalue_blocks_recursive(body, type_sec),
),
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
dae_strip_dropped_pure_multivalue_blocks_recursive(body, type_sec),
),
),
)
@lib.If(block_type, then_body, else_body) =>
rewritten.push(
@lib.Instruction::if_(
block_type,
dae_strip_dropped_pure_multivalue_blocks_recursive(
then_body, type_sec,
),
match else_body {
Some(body) =>
Some(
dae_strip_dropped_pure_multivalue_blocks_recursive(
body, type_sec,
),
)
None => None
},
),
)
_ => rewritten.push(instr)
}
}
dae_strip_dropped_pure_multivalue_blocks(rewritten, type_sec)
}
///|
fn dae_cleanup_touched_dropped_pure_multivalue_blocks(
mod_ : @lib.Module,
touched : Array[Bool],
) -> @lib.Module {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
let mut next_funcs : Array[@lib.Func]? = None
for def_idx = 0; def_idx < funcs.length(); def_idx = def_idx + 1 {
if def_idx >= touched.length() || !touched[def_idx] {
continue
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
let rewritten = dae_strip_dropped_pure_multivalue_blocks_recursive(
body,
mod_.type_sec,
)
if rewritten != body {
if next_funcs == None {
next_funcs = Some(funcs.copy())
}
next_funcs.unwrap()[def_idx] = @lib.Func::new(
locals,
@lib.Expr::new(rewritten),
)
}
}
match next_funcs {
Some(funcs) => mod_.with_code_sec(@lib.CodeSec::new(funcs))
None => mod_
}
}
///|
fn dae_local_get_range_evidence(
instrs : Array[@lib.Instruction],
start : Int32,
) -> DaeLocalGetRangeEvidence? {
if start < 0 || start > instrs.length() {
return None
}
let mut idx = start
let mut has_local_get = false
while idx < instrs.length() {
match instrs[idx] {
@lib.LocalGet(_) => has_local_get = true
_ => ()
}
idx += 1
}
let evidence = {
start,
end: instrs.length(),
has_local_get,
local_free: !has_local_get,
singleton: instrs.length() - start == 1,
range_allows_cleanup: instrs.length() - start == 1 || !has_local_get,
}
if dae_local_get_range_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
///|
fn dae_redundant_access_cleanup_evidence_is_consistent(
evidence : DaeRedundantAccessCleanupEvidence,
) -> Bool {
evidence.cleanup_kind >= 0 &&
evidence.cleanup_kind <= 3 &&
evidence.skip_current &&
(evidence.pop_previous || evidence.replacement.length() == 0) &&
evidence.replacement.length() <= 1
}
///|
fn dae_redundant_access_cleanup_evidence(
previous : @lib.Instruction,
current : @lib.Instruction,
) -> DaeRedundantAccessCleanupEvidence? {
let evidence = match (previous, current) {
(@lib.LocalGet(lhs), @lib.LocalSet(rhs)) if dae_local_idx_equal(lhs, rhs) =>
Some({
cleanup_kind: 0,
pop_previous: true,
skip_current: true,
replacement: [],
})
(@lib.LocalGet(lhs), @lib.LocalTee(rhs)) if dae_local_idx_equal(lhs, rhs) =>
Some({
cleanup_kind: 1,
pop_previous: false,
skip_current: true,
replacement: [],
})
(@lib.GlobalGet(lhs), @lib.GlobalSet(rhs)) if dae_global_idx_equal(lhs, rhs) =>
Some({
cleanup_kind: 2,
pop_previous: true,
skip_current: true,
replacement: [],
})
(@lib.LocalTee(lhs), @lib.LocalSet(rhs)) if dae_local_idx_equal(lhs, rhs) =>
Some({
cleanup_kind: 3,
pop_previous: true,
skip_current: true,
replacement: [@lib.Instruction::local_set(lhs)],
})
_ => None
}
match evidence {
Some(evidence) if dae_redundant_access_cleanup_evidence_is_consistent(
evidence,
) => Some(evidence)
_ => None
}
}
///|
fn dae_apply_redundant_access_cleanup_evidence(
out : Array[@lib.Instruction],
evidence : DaeRedundantAccessCleanupEvidence,
) -> Unit {
if evidence.pop_previous {
ignore(out.pop())
}
dae_push_instrs(out, evidence.replacement)
}
///|
fn dae_dropped_local_tee_suffix_cleanup_evidence_is_consistent(
evidence : DaeDroppedLocalTeeSuffixCleanupEvidence,
) -> Bool {
evidence.cleanup_kind >= 0 &&
evidence.cleanup_kind <= 3 &&
evidence.start >= 0 &&
evidence.start < evidence.end &&
evidence.end == evidence.next_idx &&
evidence.replacement.length() == 1
}
///|
fn dae_dropped_local_tee_suffix_cleanup_evidence(
instrs : Array[@lib.Instruction],
idx : Int32,
) -> DaeDroppedLocalTeeSuffixCleanupEvidence? {
if idx < 0 || idx >= instrs.length() {
return None
}
let evidence = match instrs[idx] {
@lib.LocalTee(local_idx) =>
if idx + 1 < instrs.length() &&
instrs[idx + 1] == @lib.Instruction::drop() {
Some({
cleanup_kind: 0,
start: idx,
end: idx + 2,
next_idx: idx + 2,
replacement: [@lib.Instruction::local_set(local_idx)],
})
} else if idx + 2 < instrs.length() &&
dae_instr_is_nontrapping_pure_unary(instrs[idx + 1]) &&
instrs[idx + 2] == @lib.Instruction::drop() {
Some({
cleanup_kind: 1,
start: idx,
end: idx + 3,
next_idx: idx + 3,
replacement: [@lib.Instruction::local_set(local_idx)],
})
} else if idx + 3 < instrs.length() &&
dae_instr_is_nontrapping_pure_tee_suffix_binary(
instrs[idx + 1],
instrs[idx + 2],
) &&
instrs[idx + 3] == @lib.Instruction::drop() {
Some({
cleanup_kind: 2,
start: idx,
end: idx + 4,
next_idx: idx + 4,
replacement: [@lib.Instruction::local_set(local_idx)],
})
} else if idx + 5 < instrs.length() &&
dae_instr_is_nontrapping_pure_tee_suffix_binary(
instrs[idx + 1],
instrs[idx + 2],
) &&
dae_instr_is_nontrapping_pure_tee_suffix_binary(
instrs[idx + 3],
instrs[idx + 4],
) &&
instrs[idx + 5] == @lib.Instruction::drop() {
Some({
cleanup_kind: 3,
start: idx,
end: idx + 6,
next_idx: idx + 6,
replacement: [@lib.Instruction::local_set(local_idx)],
})
} else {
None
}
_ => None
}
match evidence {
Some(evidence) if dae_dropped_local_tee_suffix_cleanup_evidence_is_consistent(
evidence,
) => Some(evidence)
_ => None
}
}
///|
fn dae_truncate_instrs(
out : Array[@lib.Instruction],
new_length : Int32,
) -> Unit {
while out.length() > new_length {
ignore(out.pop())
}
}
///|
fn dae_droppable_pure_stack_evidence(
out : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> DaeDroppablePureStackEvidence? {
match dae_find_droppable_pure_stack_expr_start(out, type_sec) {
Some(start) =>
match dae_local_get_range_evidence(out, start) {
Some(local_range) =>
Some({
start: local_range.start,
end: local_range.end,
has_local_get: local_range.has_local_get,
local_free: local_range.local_free,
local_range,
})
None => None
}
None => None
}
}
///|
fn dae_droppable_pure_stack_evidence_is_consistent(
evidence : DaeDroppablePureStackEvidence,
) -> Bool {
dae_local_get_range_evidence_is_consistent(evidence.local_range) &&
evidence.start == evidence.local_range.start &&
evidence.end == evidence.local_range.end &&
evidence.has_local_get == evidence.local_range.has_local_get &&
evidence.local_free == evidence.local_range.local_free
}
///|
fn dae_droppable_pure_stack_evidence_allows_live_local_copy_cleanup(
evidence : DaeDroppablePureStackEvidence?,
) -> Bool {
match evidence {
Some(evidence) =>
dae_droppable_pure_stack_evidence_is_consistent(evidence) &&
evidence.local_free
None => false
}
}
///|
fn dae_live_local_copy_cleanup_evidence_is_consistent(
evidence : DaeLiveLocalCopyCleanupEvidence,
) -> Bool {
evidence.start >= 0 &&
evidence.start < evidence.end &&
evidence.end == evidence.next_idx &&
evidence.copy_count > 0 &&
evidence.replacement.length() > 0 &&
(
evidence.requires_local_free_stack ||
(evidence.copy_count == 2 && evidence.replacement.length() == 1)
)
}
///|
fn dae_live_local_copy_cleanup_evidence(
instrs : Array[@lib.Instruction],
idx : Int32,
stack_evidence : DaeDroppablePureStackEvidence?,
) -> DaeLiveLocalCopyCleanupEvidence? {
if idx < 0 {
return None
}
if idx + 2 < instrs.length() {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2]) {
(@lib.LocalSet(lhs), @lib.LocalGet(mid), @lib.LocalSet(rhs)) if dae_local_idx_equal(
lhs, mid,
) &&
dae_local_idx_equal(lhs, rhs) =>
return Some({
start: idx,
end: idx + 3,
next_idx: idx + 3,
copy_count: 2,
requires_local_free_stack: false,
replacement: [@lib.Instruction::local_set(lhs)],
})
_ => ()
}
}
if !dae_droppable_pure_stack_evidence_allows_live_local_copy_cleanup(
stack_evidence,
) {
return None
}
if idx + 4 < instrs.length() {
match
(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 4],
) {
(
@lib.LocalSet(first),
@lib.LocalGet(first_read),
@lib.LocalSet(second),
@lib.LocalGet(second_read),
@lib.LocalSet(third),
) if dae_local_idx_equal(first, first_read) &&
dae_local_idx_equal(second, second_read) =>
return Some({
start: idx,
end: idx + 5,
next_idx: idx + 5,
copy_count: 3,
requires_local_free_stack: true,
replacement: [
@lib.Instruction::local_tee(first),
@lib.Instruction::local_tee(second),
@lib.Instruction::local_set(third),
],
})
_ => ()
}
}
if idx + 2 < instrs.length() {
match (instrs[idx], instrs[idx + 1], instrs[idx + 2]) {
(@lib.LocalSet(lhs), @lib.LocalGet(mid), @lib.LocalSet(rhs)) if dae_local_idx_equal(
lhs, mid,
) =>
return Some({
start: idx,
end: idx + 3,
next_idx: idx + 3,
copy_count: 2,
requires_local_free_stack: true,
replacement: [
@lib.Instruction::local_tee(lhs),
@lib.Instruction::local_set(rhs),
],
})
_ => ()
}
}
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(@lib.LocalSet(lhs), @lib.LocalGet(rhs)) if dae_local_idx_equal(lhs, rhs) =>
return Some({
start: idx,
end: idx + 2,
next_idx: idx + 2,
copy_count: 1,
requires_local_free_stack: true,
replacement: [@lib.Instruction::local_tee(lhs)],
})
(@lib.LocalTee(lhs), @lib.LocalSet(rhs)) if dae_local_idx_equal(lhs, rhs) =>
return Some({
start: idx,
end: idx + 2,
next_idx: idx + 2,
copy_count: 1,
requires_local_free_stack: true,
replacement: [@lib.Instruction::local_set(lhs)],
})
_ => ()
}
}
None
}
///|
fn dae_instr_is_integer_const_leaf(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I32Const(_) | @lib.I64Const(_) => true
_ => false
}
}
///|
fn dae_instr_prefix_has_observable_effect(
instrs : Array[@lib.Instruction],
end : Int32,
) -> Bool {
let mut idx = 0
while idx < end {
if dae_instr_has_observable_effect(instrs[idx]) {
return true
}
idx += 1
}
false
}
///|
fn dae_dropped_pure_leaf_subject_evidence_from_stack(
out : Array[@lib.Instruction],
start : Int32,
) -> DaeDroppedPureLeafSubjectEvidence? {
if start < 0 || start >= out.length() {
return None
}
match dae_local_get_range_evidence(out, start) {
Some(local_range) =>
Some({
start: local_range.start,
end: local_range.end,
prefix_end: local_range.start,
instr: out[start],
range_allows_cleanup: local_range.range_allows_cleanup,
prefix_has_observable_effect: dae_instr_prefix_has_observable_effect(
out, start,
),
})
None => None
}
}
///|
fn dae_dropped_pure_leaf_subject_evidence_from_leaf(
out : Array[@lib.Instruction],
instr : @lib.Instruction,
) -> DaeDroppedPureLeafSubjectEvidence {
let start = out.length()
{
start,
end: start + 1,
prefix_end: start,
instr,
range_allows_cleanup: dae_instr_is_droppable_pure_leaf(instr),
prefix_has_observable_effect: dae_instr_prefix_has_observable_effect(
out, start,
),
}
}
///|
fn dae_dropped_pure_leaf_subject_is_singleton(
evidence : DaeDroppedPureLeafSubjectEvidence,
) -> Bool {
evidence.end - evidence.start == 1
}
///|
fn dae_dropped_pure_leaf_subject_preserves_integer_const_debris(
evidence : DaeDroppedPureLeafSubjectEvidence,
preserve_dropped_integer_const_debris : Bool,
preserve_effect_dropped_integer_const_debris : Bool,
) -> Bool {
// Current Binaryen DAE shape preserves tiny dropped integer constants after
// live calls/effects and inside live branch regions for some raw-cleanup
// lanes, while the selected func236 local-retarget lane strips that debris.
// Keep the preservation mode explicit so shape fixes can follow Binaryen's
// lane-specific cleanup rather than globally preferring smaller output.
dae_dropped_pure_leaf_subject_is_singleton(evidence) &&
dae_instr_is_integer_const_leaf(evidence.instr) &&
(
preserve_dropped_integer_const_debris ||
(
preserve_effect_dropped_integer_const_debris &&
evidence.prefix_has_observable_effect
)
)
}
///|
fn dae_dropped_pure_leaf_subject_preserves_local_get_after_same_set(
out : Array[@lib.Instruction],
evidence : DaeDroppedPureLeafSubjectEvidence,
) -> Bool {
if !dae_dropped_pure_leaf_subject_is_singleton(evidence) ||
evidence.prefix_end < 2 ||
evidence.prefix_end > out.length() {
return false
}
match
(out[evidence.prefix_end - 2], out[evidence.prefix_end - 1], evidence.instr) {
(@lib.LocalGet(source), @lib.LocalSet(written), @lib.LocalGet(read)) =>
dae_local_idx_equal(written, read) && !dae_local_idx_equal(source, read)
_ => false
}
}
///|
fn dae_dropped_pure_leaf_subject_preserves_local_get_after_effect(
evidence : DaeDroppedPureLeafSubjectEvidence,
) -> Bool {
if !dae_dropped_pure_leaf_subject_is_singleton(evidence) {
return false
}
match evidence.instr {
@lib.LocalGet(_) => evidence.prefix_has_observable_effect
_ => false
}
}
///|
fn dae_dropped_pure_leaf_cleanup_evidence_from_stack(
out : Array[@lib.Instruction],
start : Int32,
preserve_dropped_integer_const_debris : Bool,
preserve_effect_dropped_integer_const_debris : Bool,
) -> DaeDroppedPureLeafCleanupEvidence {
match dae_dropped_pure_leaf_subject_evidence_from_stack(out, start) {
Some(subject) =>
{
range_allows_cleanup: subject.range_allows_cleanup,
preserve_integer_const_debris: dae_dropped_pure_leaf_subject_preserves_integer_const_debris(
subject, preserve_dropped_integer_const_debris, preserve_effect_dropped_integer_const_debris,
),
preserve_local_get_after_same_set: dae_dropped_pure_leaf_subject_preserves_local_get_after_same_set(
out, subject,
),
preserve_local_get_after_effect: dae_dropped_pure_leaf_subject_preserves_local_get_after_effect(
subject,
),
}
None =>
{
range_allows_cleanup: false,
preserve_integer_const_debris: false,
preserve_local_get_after_same_set: false,
preserve_local_get_after_effect: false,
}
}
}
///|
fn dae_dropped_pure_leaf_cleanup_evidence_from_leaf(
out : Array[@lib.Instruction],
instr : @lib.Instruction,
preserve_dropped_integer_const_debris : Bool,
preserve_effect_dropped_integer_const_debris : Bool,
) -> DaeDroppedPureLeafCleanupEvidence {
let subject = dae_dropped_pure_leaf_subject_evidence_from_leaf(out, instr)
{
range_allows_cleanup: subject.range_allows_cleanup,
preserve_integer_const_debris: dae_dropped_pure_leaf_subject_preserves_integer_const_debris(
subject, preserve_dropped_integer_const_debris, preserve_effect_dropped_integer_const_debris,
),
preserve_local_get_after_same_set: dae_dropped_pure_leaf_subject_preserves_local_get_after_same_set(
out, subject,
),
preserve_local_get_after_effect: dae_dropped_pure_leaf_subject_preserves_local_get_after_effect(
subject,
),
}
}
///|
fn dae_dropped_pure_leaf_cleanup_allows_stack_truncation(
evidence : DaeDroppedPureLeafCleanupEvidence,
) -> Bool {
evidence.range_allows_cleanup &&
!evidence.preserve_integer_const_debris &&
!evidence.preserve_local_get_after_same_set &&
!evidence.preserve_local_get_after_effect
}
///|
fn dae_dropped_pure_leaf_cleanup_allows_leaf_drop(
evidence : DaeDroppedPureLeafCleanupEvidence,
) -> Bool {
dae_dropped_pure_leaf_cleanup_allows_stack_truncation(evidence)
}
///|
fn dae_dropped_pure_expression_cleanup_evidence_is_consistent(
evidence : DaeDroppedPureExpressionCleanupEvidence,
) -> Bool {
evidence.start >= 0 &&
evidence.start < evidence.end &&
evidence.end == evidence.next_idx &&
evidence.end - evidence.start ==
evidence.operand_count + evidence.operator_count + 1 &&
(match evidence.cleanup_kind {
1 =>
evidence.operand_count == 1 &&
evidence.operator_count == 0 &&
evidence.uses_leaf_preservation_policy
2 =>
evidence.operand_count == 1 &&
evidence.operator_count == 1 &&
!evidence.uses_leaf_preservation_policy
3 =>
evidence.operand_count == 2 &&
evidence.operator_count == 1 &&
!evidence.uses_leaf_preservation_policy
_ => false
})
}
///|
fn dae_dropped_pure_expression_cleanup_evidence(
out : Array[@lib.Instruction],
instrs : Array[@lib.Instruction],
idx : Int32,
preserve_dropped_integer_const_debris : Bool,
preserve_effect_dropped_integer_const_debris : Bool,
) -> DaeDroppedPureExpressionCleanupEvidence? {
if idx < 0 || idx >= instrs.length() {
return None
}
let evidence = if idx + 1 < instrs.length() &&
instrs[idx + 1] == @lib.Instruction::drop() {
let leaf_cleanup = dae_dropped_pure_leaf_cleanup_evidence_from_leaf(
out,
instrs[idx],
preserve_dropped_integer_const_debris,
preserve_effect_dropped_integer_const_debris,
)
if dae_dropped_pure_leaf_cleanup_allows_leaf_drop(leaf_cleanup) {
Some({
cleanup_kind: 1,
start: idx,
end: idx + 2,
next_idx: idx + 2,
operand_count: 1,
operator_count: 0,
uses_leaf_preservation_policy: true,
})
} else {
None
}
} else if idx + 2 < instrs.length() &&
dae_instr_is_droppable_pure_leaf(instrs[idx]) &&
(
dae_instr_is_nontrapping_pure_unary(instrs[idx + 1]) ||
dae_instr_is_const_safe_float_to_int_unary(instrs[idx], instrs[idx + 1])
) &&
instrs[idx + 2] == @lib.Instruction::drop() {
Some({
cleanup_kind: 2,
start: idx,
end: idx + 3,
next_idx: idx + 3,
operand_count: 1,
operator_count: 1,
uses_leaf_preservation_policy: false,
})
} else if idx + 3 < instrs.length() &&
dae_instr_is_droppable_pure_leaf(instrs[idx]) &&
dae_instr_is_droppable_pure_leaf(instrs[idx + 1]) &&
(
dae_instr_is_nontrapping_pure_binary(instrs[idx + 2]) ||
dae_instr_is_const_safe_numeric_binary(
instrs[idx],
instrs[idx + 1],
instrs[idx + 2],
)
) &&
instrs[idx + 3] == @lib.Instruction::drop() {
match instrs[idx] {
@lib.LocalGet(_) => None
_ =>
Some({
cleanup_kind: 3,
start: idx,
end: idx + 4,
next_idx: idx + 4,
operand_count: 2,
operator_count: 1,
uses_leaf_preservation_policy: false,
})
}
} else {
None
}
match evidence {
Some(evidence) if dae_dropped_pure_expression_cleanup_evidence_is_consistent(
evidence,
) => Some(evidence)
_ => None
}
}
///|
fn dae_empty_zero_result_control_cleanup_evidence_from_lengths(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
primary_body_length : Int32,
secondary_body_length : Int32,
) -> DaeEmptyZeroResultControlCleanupEvidence? {
if primary_body_length < 0 || secondary_body_length < -1 {
return None
}
let body_count = if secondary_body_length >= 0 { 2 } else { 1 }
let rewritten_body_length = if body_count == 2 {
primary_body_length + secondary_body_length
} else {
primary_body_length
}
match
dae_control_block_type_zero_input_output_count_evidence(
block_type, type_sec, 0,
) {
Some(signature) =>
Some({
input_count: signature.input_count,
output_count: signature.output_count,
rewritten_body_length,
primary_body_length,
secondary_body_length,
body_count,
drops_empty_control: rewritten_body_length == 0,
})
None => None
}
}
///|
fn dae_empty_zero_result_control_cleanup_evidence(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
rewritten_body_length : Int32,
) -> DaeEmptyZeroResultControlCleanupEvidence? {
dae_empty_zero_result_control_cleanup_evidence_from_lengths(
block_type, type_sec, rewritten_body_length, -1,
)
}
///|
fn dae_empty_zero_result_if_cleanup_evidence(
block_type : @lib.BlockType,
type_sec : @lib.TypeSec?,
rewritten_then_length : Int32,
rewritten_else_length : Int32,
) -> DaeEmptyZeroResultControlCleanupEvidence? {
dae_empty_zero_result_control_cleanup_evidence_from_lengths(
block_type, type_sec, rewritten_then_length, rewritten_else_length,
)
}
///|
fn dae_empty_zero_result_control_cleanup_allows_drop(
evidence : DaeEmptyZeroResultControlCleanupEvidence,
) -> Bool {
let body_lengths_match = if evidence.body_count == 2 {
evidence.rewritten_body_length ==
evidence.primary_body_length + evidence.secondary_body_length
} else {
evidence.body_count == 1 &&
evidence.secondary_body_length == -1 &&
evidence.rewritten_body_length == evidence.primary_body_length
}
evidence.input_count == 0 &&
evidence.output_count == 0 &&
body_lengths_match &&
evidence.rewritten_body_length == 0 &&
evidence.drops_empty_control
}
///|
fn dae_rebuild_cleaned_single_body_control_or_drop(
instr : @lib.Instruction,
type_sec : @lib.TypeSec?,
rewritten_body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
let (block_type, control_kind, catches) = match instr {
@lib.Block(block_type, _) => (block_type, 0, None)
@lib.Loop(block_type, _) => (block_type, 1, None)
@lib.TryTable(block_type, catches, _) => (block_type, 2, Some(catches))
_ => return None
}
match
dae_empty_zero_result_control_cleanup_evidence(
block_type,
type_sec,
rewritten_body.length(),
) {
Some(evidence) if dae_empty_zero_result_control_cleanup_allows_drop(
evidence,
) => return Some([])
_ => ()
}
let rebuilt = match control_kind {
0 => @lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body))
1 => @lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body))
2 =>
match catches {
Some(catches) =>
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
)
None => return None
}
_ => return None
}
Some([rebuilt])
}
///|
fn dae_local_idx_equal(lhs : @lib.LocalIdx, rhs : @lib.LocalIdx) -> Bool {
dae_local_idx_slot(lhs) == dae_local_idx_slot(rhs)
}
///|
fn dae_global_idx_equal(lhs : @lib.GlobalIdx, rhs : @lib.GlobalIdx) -> Bool {
dae_global_idx_slot(lhs) == dae_global_idx_slot(rhs)
}
///|
fn dae_strip_dropped_pure_if_keep_condition(
out : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Bool {
if out.length() == 0 {
return false
}
let last_idx = out.length() - 1
if !dae_instr_droppable_pure_if_value(out[last_idx], type_sec) {
return false
}
ignore(out.pop())
match dae_find_droppable_pure_stack_expr_start(out, type_sec) {
Some(start) => dae_truncate_instrs(out, start)
None => out.push(@lib.Instruction::drop())
}
true
}
///|
fn dae_strip_or_drop_condition(
out : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Unit {
match dae_find_droppable_pure_stack_expr_start(out, type_sec) {
Some(start) => dae_truncate_instrs(out, start)
None => out.push(@lib.Instruction::drop())
}
}
///|
fn dae_simplify_live_if_condition(out : Array[@lib.Instruction]) -> Unit {
// Binaryen keeps live if conditions booleanized as `x != 0` rather than
// branching directly on the raw i32 producer. Preserve that shape for DAE
// artifact parity while still canonicalizing the double-eqz tail.
while out.length() >= 2 {
let last_idx = out.length() - 1
if out[last_idx] == @lib.Instruction::i32_eqz() &&
out[last_idx - 1] == @lib.Instruction::i32_eqz() {
ignore(out.pop())
ignore(out.pop())
out.push(@lib.Instruction::i32_const(@lib.I32(0)))
out.push(@lib.Instruction::i32_ne())
continue
}
break
}
}
///|
fn dae_strip_dropped_nontrapping_pure_wrapper_suffix(
out : Array[@lib.Instruction],
) -> Bool {
let mut changed = false
let mut keep_scanning = true
while keep_scanning {
keep_scanning = false
if out.length() > 0 {
let last_idx = out.length() - 1
if dae_instr_is_nontrapping_pure_unary(out[last_idx]) {
ignore(out.pop())
changed = true
keep_scanning = true
continue
}
}
if out.length() > 1 {
let last_idx = out.length() - 1
let rhs_idx = out.length() - 2
if dae_instr_is_droppable_pure_leaf(out[rhs_idx]) &&
dae_instr_is_nontrapping_pure_binary(out[last_idx]) {
if rhs_idx >= 1 {
match out[rhs_idx - 1] {
@lib.LocalGet(_) => break
_ => ()
}
}
ignore(out.pop())
ignore(out.pop())
changed = true
keep_scanning = true
continue
}
}
}
changed
}
///|
fn dae_strip_trailing_dropped_unreachable_before_unreachable(
out : Array[@lib.Instruction],
) -> Bool {
if out.length() < 2 {
return false
}
let drop_idx = out.length() - 1
if out[drop_idx] != @lib.Instruction::drop() {
return false
}
let value_idx = drop_idx - 1
if !dae_instr_is_unreachable_root(out[value_idx]) {
return false
}
ignore(out.pop())
ignore(out.pop())
true
}
///|
fn dae_instrs_may_have_raw_cleanup_candidate(
instrs : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
allow_live_local_copy_cleanup : Bool,
) -> Bool {
let mut idx = 0
while idx < instrs.length() {
let instr = instrs[idx]
if instr == @lib.Instruction::return_() ||
instr == @lib.Instruction::drop() ||
dae_instr_is_unreachable_root(instr) {
return true
}
if idx > 0 {
match dae_redundant_access_cleanup_evidence(instrs[idx - 1], instr) {
Some(_) => return true
None => ()
}
}
match dae_dropped_local_tee_suffix_cleanup_evidence(instrs, idx) {
Some(_) => return true
None => ()
}
if idx + 1 < instrs.length() {
if dae_instr_integer_zero_compare_replacement(instr, instrs[idx + 1])
is Some(_) ||
dae_instr_is_integer_right_constant_identity(instr, instrs[idx + 1]) {
return true
}
}
if idx > 0 && idx + 1 < instrs.length() {
let previous = instrs[idx - 1]
let next = instrs[idx + 1]
if dae_instr_self_comparison_replacement(previous, instr, next) is Some(_) ||
dae_instr_self_integer_binary_replacement(previous, instr, next)
is Some(_) ||
(
dae_instr_is_stack_neutral_value_leaf(instr) &&
dae_instr_integer_zero_compare_replacement(previous, next) is Some(_)
) ||
(
dae_instr_is_stack_neutral_value_leaf(instr) &&
dae_instr_is_integer_left_constant_identity(previous, next)
) {
return true
}
}
if allow_live_local_copy_cleanup {
match instr {
@lib.LocalGet(_) | @lib.LocalSet(_) | @lib.LocalTee(_) => return true
_ => ()
}
} else {
match dae_live_local_copy_cleanup_evidence(instrs, idx, None) {
Some(evidence) if dae_live_local_copy_cleanup_evidence_is_consistent(
evidence,
) => return true
_ => ()
}
}
match instr {
@lib.Block(block_type, @lib.Expr(body))
| @lib.Loop(block_type, @lib.Expr(body))
| @lib.TryTable(block_type, _, @lib.Expr(body)) => {
if dae_instrs_may_have_raw_cleanup_candidate(
body, type_sec, allow_live_local_copy_cleanup,
) {
return true
}
match
dae_empty_zero_result_control_cleanup_evidence(
block_type,
type_sec,
body.length(),
) {
Some(evidence) if dae_empty_zero_result_control_cleanup_allows_drop(
evidence,
) => return true
_ => ()
}
}
@lib.If(block_type, then_body, else_body) => {
if dae_instrs_may_have_raw_cleanup_candidate(
then_body, type_sec, allow_live_local_copy_cleanup,
) {
return true
}
let else_length = match else_body {
Some(body) => {
if dae_instrs_may_have_raw_cleanup_candidate(
body, type_sec, allow_live_local_copy_cleanup,
) {
return true
}
body.length()
}
None => 0
}
match
dae_empty_zero_result_if_cleanup_evidence(
block_type,
type_sec,
then_body.length(),
else_length,
) {
Some(evidence) if dae_empty_zero_result_control_cleanup_allows_drop(
evidence,
) => return true
_ => ()
}
if idx >= 2 &&
instrs[idx - 1] == @lib.Instruction::i32_eqz() &&
instrs[idx - 2] == @lib.Instruction::i32_eqz() {
return true
}
}
_ => ()
}
idx += 1
}
false
}
///|
fn dae_strip_nops_and_dropped_pure_leaf_instrs(
instrs : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
allow_live_local_copy_cleanup~ : Bool = true,
preserve_dropped_integer_const_debris~ : Bool = false,
preserve_effect_dropped_integer_const_debris~ : Bool = true,
preserve_nop_debris~ : Bool = true,
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
let mut idx = 0
// Preserve raw `nop` debris by default. Binaryen's DAE keeps those no-ops in
// several current artifact lanes, but the selected func236 local-retarget
// lane strips them, so callers can opt into the stricter cleanup shape.
while idx < instrs.length() {
if !preserve_nop_debris && instrs[idx] == @lib.Instruction::nop() {
idx += 1
continue
}
if instrs[idx] == @lib.Instruction::return_() {
out.push(instrs[idx])
break
}
if dae_instr_is_unreachable_root(instrs[idx]) {
ignore(dae_strip_trailing_dropped_unreachable_before_unreachable(out))
}
if instrs[idx] == @lib.Instruction::drop() {
if out.length() > 0 {
let last_idx = out.length() - 1
match out[last_idx] {
@lib.LocalTee(local_idx) => {
ignore(out.pop())
out.push(@lib.Instruction::local_set(local_idx))
idx += 1
continue
}
_ => ()
}
}
ignore(dae_strip_dropped_nontrapping_pure_wrapper_suffix(out))
if out.length() > 0 {
let last_idx = out.length() - 1
match out[last_idx] {
@lib.LocalTee(local_idx) => {
ignore(out.pop())
out.push(@lib.Instruction::local_set(local_idx))
idx += 1
continue
}
_ => ()
}
}
match dae_find_droppable_pure_stack_expr_start(out, type_sec) {
Some(start) => {
let cleanup_evidence = dae_dropped_pure_leaf_cleanup_evidence_from_stack(
out, start, preserve_dropped_integer_const_debris, preserve_effect_dropped_integer_const_debris,
)
if dae_dropped_pure_leaf_cleanup_allows_stack_truncation(
cleanup_evidence,
) {
dae_truncate_instrs(out, start)
idx += 1
continue
}
}
_ => ()
}
if dae_strip_dropped_pure_if_keep_condition(out, type_sec) {
idx += 1
continue
}
}
if out.length() > 0 {
let last_idx = out.length() - 1
match dae_redundant_access_cleanup_evidence(out[last_idx], instrs[idx]) {
Some(evidence) => {
dae_apply_redundant_access_cleanup_evidence(out, evidence)
idx += 1
continue
}
None => ()
}
}
match dae_dropped_local_tee_suffix_cleanup_evidence(instrs, idx) {
Some(evidence) => {
dae_push_instrs(out, evidence.replacement)
idx = evidence.next_idx
continue
}
None => ()
}
if idx + 1 < instrs.length() {
match
dae_instr_integer_zero_compare_replacement(instrs[idx], instrs[idx + 1]) {
Some(replacement) => {
dae_push_instrs(out, replacement)
idx += 2
continue
}
None => ()
}
if dae_instr_is_integer_right_constant_identity(
instrs[idx],
instrs[idx + 1],
) {
idx += 2
continue
}
}
if out.length() > 0 && idx + 1 < instrs.length() {
let last_idx = out.length() - 1
match
dae_instr_self_comparison_replacement(
out[last_idx],
instrs[idx],
instrs[idx + 1],
) {
Some(replacement) => {
ignore(out.pop())
out.push(replacement)
idx += 2
continue
}
None => ()
}
match
dae_instr_self_integer_binary_replacement(
out[last_idx],
instrs[idx],
instrs[idx + 1],
) {
Some(replacement) => {
ignore(out.pop())
dae_push_instrs(out, replacement)
idx += 2
continue
}
None => ()
}
if dae_instr_is_stack_neutral_value_leaf(instrs[idx]) {
match
dae_instr_integer_zero_compare_replacement(
out[last_idx],
instrs[idx + 1],
) {
Some(replacement) => {
ignore(out.pop())
out.push(instrs[idx])
dae_push_instrs(out, replacement)
idx += 2
continue
}
None => ()
}
}
if dae_instr_is_stack_neutral_value_leaf(instrs[idx]) &&
dae_instr_is_integer_left_constant_identity(
out[last_idx],
instrs[idx + 1],
) {
ignore(out.pop())
out.push(instrs[idx])
idx += 2
continue
}
}
let live_copy_stack_evidence = if allow_live_local_copy_cleanup {
dae_droppable_pure_stack_evidence(out, type_sec)
} else {
None
}
// Binaryen keeps local-get-sourced temp copy chains in the current DAE
// frontier; only stackify copies rooted in non-local pure values. Redundant
// same-target set/get/set chains remain removable without stack evidence.
match
dae_live_local_copy_cleanup_evidence(
instrs, idx, live_copy_stack_evidence,
) {
Some(evidence) if dae_live_local_copy_cleanup_evidence_is_consistent(
evidence,
) => {
dae_push_instrs(out, evidence.replacement)
idx = evidence.next_idx
continue
}
_ => ()
}
match
dae_dropped_pure_expression_cleanup_evidence(
out, instrs, idx, preserve_dropped_integer_const_debris, preserve_effect_dropped_integer_const_debris,
) {
Some(evidence) => {
idx = evidence.next_idx
continue
}
None => ()
}
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) => {
let rewritten_body = dae_strip_nops_and_dropped_pure_leaf_instrs(
body,
type_sec,
allow_live_local_copy_cleanup~,
preserve_dropped_integer_const_debris~,
preserve_effect_dropped_integer_const_debris~,
preserve_nop_debris~,
)
match
dae_rebuild_cleaned_single_body_control_or_drop(
instrs[idx],
type_sec,
rewritten_body,
) {
Some(rebuilt) => out.append(rebuilt)
None => out.push(instrs[idx])
}
}
@lib.If(block_type, then_body, else_body) => {
let else_has_effect_or_branch = match else_body {
Some(body) => dae_instrs_have_observable_effect_or_branch(body)
None => false
}
let preserve_branch_integer_const_debris = preserve_dropped_integer_const_debris ||
(
preserve_effect_dropped_integer_const_debris &&
(
dae_instrs_have_observable_effect_or_branch(then_body) ||
else_has_effect_or_branch
)
)
let rewritten_then = dae_strip_nops_and_dropped_pure_leaf_instrs(
then_body,
type_sec,
allow_live_local_copy_cleanup~,
preserve_dropped_integer_const_debris=preserve_branch_integer_const_debris,
preserve_effect_dropped_integer_const_debris~,
preserve_nop_debris~,
)
let rewritten_else = match else_body {
Some(body) =>
Some(
dae_strip_nops_and_dropped_pure_leaf_instrs(
body,
type_sec,
allow_live_local_copy_cleanup~,
preserve_dropped_integer_const_debris=preserve_branch_integer_const_debris,
preserve_effect_dropped_integer_const_debris~,
preserve_nop_debris~,
),
)
None => None
}
let rewritten_else_length = match rewritten_else {
Some(body) => body.length()
None => 0
}
match
dae_empty_zero_result_if_cleanup_evidence(
block_type,
type_sec,
rewritten_then.length(),
rewritten_else_length,
) {
Some(evidence) if dae_empty_zero_result_control_cleanup_allows_drop(
evidence,
) => dae_strip_or_drop_condition(out, type_sec)
_ => {
dae_simplify_live_if_condition(out)
out.push(
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else),
)
}
}
}
_ => out.push(instrs[idx])
}
idx += 1
}
out
}
///|
fn dae_strip_dropped_i32_const_pairs_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction]?, Int32, Int32) {
let mut out : Array[@lib.Instruction]? = None
let mut removed = 0
let mut stackified = 0
let mut idx = 0
while idx < instrs.length() {
if idx + 1 < instrs.length() &&
instrs[idx] is @lib.I32Const(_) &&
instrs[idx + 1] == @lib.Instruction::drop() {
if out is None {
out = Some(dae_copy_instr_prefix(instrs, idx))
}
removed += 1
idx += 2
continue
}
if idx + 1 < instrs.length() {
match (instrs[idx], instrs[idx + 1]) {
(@lib.LocalSet(target), @lib.LocalGet(read)) if target == read => {
if out is None {
out = Some(dae_copy_instr_prefix(instrs, idx))
}
out.unwrap().push(@lib.Instruction::local_tee(target))
stackified += 1
idx += 2
continue
}
_ => ()
}
}
let rewritten_instr = match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten, child_removed, child_stackified) = dae_strip_dropped_i32_const_pairs_instrs(
body,
)
removed += child_removed
stackified += child_stackified
match rewritten {
Some(body) =>
Some(@lib.Instruction::block(block_type, @lib.Expr::new(body)))
None => None
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten, child_removed, child_stackified) = dae_strip_dropped_i32_const_pairs_instrs(
body,
)
removed += child_removed
stackified += child_stackified
match rewritten {
Some(body) =>
Some(@lib.Instruction::loop_(block_type, @lib.Expr::new(body)))
None => None
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten, child_removed, child_stackified) = dae_strip_dropped_i32_const_pairs_instrs(
body,
)
removed += child_removed
stackified += child_stackified
match rewritten {
Some(body) =>
Some(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(body),
),
)
None => None
}
}
@lib.If(block_type, then_body, else_body) => {
let (rewritten_then, then_removed, then_stackified) = dae_strip_dropped_i32_const_pairs_instrs(
then_body,
)
let (rewritten_else, else_removed, else_stackified) = match else_body {
Some(body) => dae_strip_dropped_i32_const_pairs_instrs(body)
None => (None, 0, 0)
}
removed += then_removed + else_removed
stackified += then_stackified + else_stackified
if then_removed + else_removed + then_stackified + else_stackified > 0 {
Some(
@lib.Instruction::if_(
block_type,
match rewritten_then {
Some(body) => body
None => then_body
},
match (else_body, rewritten_else) {
(_, Some(body)) => Some(body)
(Some(body), None) => Some(body)
(None, None) => None
},
),
)
} else {
None
}
}
_ => None
}
match rewritten_instr {
Some(instr) => {
if out is None {
out = Some(dae_copy_instr_prefix(instrs, idx))
}
out.unwrap().push(instr)
}
None =>
match out {
Some(copy) => copy.push(instrs[idx])
None => ()
}
}
idx += 1
}
(out, removed, stackified)
}
///|
fn dae_cleanup_touched_dropped_i32_const_pairs(
mod_ : @lib.Module,
touched : Array[Bool],
) -> (@lib.Module, Int32, Int32) {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return (mod_, 0, 0)
}
let mut rewritten_funcs : Array[@lib.Func]? = None
let mut removed = 0
let mut stackified = 0
let mut def_idx = 0
while def_idx < funcs.length() {
if def_idx >= touched.length() || !touched[def_idx] {
def_idx += 1
continue
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
if locals.length() < 8 {
def_idx += 1
continue
}
let (rewritten, func_removed, func_stackified) = dae_strip_dropped_i32_const_pairs_instrs(
body,
)
match rewritten {
Some(body) => {
removed += func_removed
stackified += func_stackified
match rewritten_funcs {
Some(copy) =>
copy[def_idx] = @lib.Func::new(locals, @lib.Expr::new(body))
None => {
let copy = funcs.copy()
copy[def_idx] = @lib.Func::new(locals, @lib.Expr::new(body))
rewritten_funcs = Some(copy)
}
}
}
None => ()
}
def_idx += 1
}
match rewritten_funcs {
Some(funcs) =>
(
mod_.with_code_sec(@lib.CodeSec::new(funcs)).without_name_sec(),
removed,
stackified,
)
None => (mod_, 0, 0)
}
}
///|
fn dae_strip_module_nops_and_dropped_pure_leaf_once(
mod_ : @lib.Module,
touched_defs : Array[Bool],
return_touched~ : Bool = true,
) -> DaePassResult {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let mut has_touch = false
for touched in touched_defs {
if touched {
has_touch = true
break
}
}
if !has_touch {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let mut rewritten_funcs : Array[@lib.Func]? = None
let mut primary_def = -1
let mut def_idx = 0
while def_idx < funcs.length() {
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
let allow_live_local_copy_cleanup = def_idx < touched_defs.length() &&
touched_defs[def_idx]
if !dae_instrs_may_have_raw_cleanup_candidate(
body,
mod_.type_sec,
allow_live_local_copy_cleanup,
) {
def_idx += 1
continue
}
let rewritten = dae_strip_nops_and_dropped_pure_leaf_instrs(
body,
mod_.type_sec,
allow_live_local_copy_cleanup~,
)
if rewritten != body {
let next_func = @lib.Func::new(locals, @lib.Expr::new(rewritten))
match rewritten_funcs {
Some(copy) => copy[def_idx] = next_func
None => {
let copy = funcs.copy()
copy[def_idx] = next_func
rewritten_funcs = Some(copy)
}
}
if primary_def < 0 {
primary_def = def_idx
}
}
def_idx += 1
}
match rewritten_funcs {
Some(copy) =>
{
mod_out: mod_.with_code_sec(@lib.CodeSec::new(copy)).without_name_sec(),
touched: if return_touched {
touched_defs.copy()
} else {
[]
},
changed: true,
primary_def,
}
None => { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
}
///|
fn dae_try_strip_terminal_dead_wrapper_suffix_at(
instrs : Array[@lib.Instruction],
idx : Int32,
) -> Array[@lib.Instruction]? {
if idx < 3 || idx + 8 != instrs.length() - 1 {
return None
}
let returned_local = match instrs[idx - 3] {
@lib.LocalGet(local_idx) => local_idx
_ => return None
}
let wrapper_local = match instrs[idx + 2] {
@lib.LocalTee(local_idx) => local_idx
_ => return None
}
match
(
instrs[idx - 1],
instrs[idx],
instrs[idx + 1],
instrs[idx + 3],
instrs[idx + 4],
instrs[idx + 5],
instrs[idx + 7],
instrs[idx + 8],
) {
(
@lib.I32Store(_),
@lib.I32Const(@lib.I32(8)),
@lib.Call(_),
@lib.I32Const(@lib.I32(2097153)),
@lib.I32Store(_),
@lib.LocalGet(wrapper_addr),
@lib.I64Store(_),
@lib.LocalGet(wrapper_result),
) if dae_local_idx_equal(wrapper_addr, wrapper_local) &&
dae_local_idx_equal(wrapper_result, wrapper_local) => ()
_ => return None
}
match instrs[idx + 6] {
@lib.LocalGet(_) => ()
_ => return None
}
let out = dae_copy_instr_prefix(instrs, idx)
out.push(@lib.Instruction::local_get(returned_local))
Some(out)
}
///|
fn dae_try_strip_terminal_lowered_return_wrapper_suffix_at(
instrs : Array[@lib.Instruction],
idx : Int32,
) -> Array[@lib.Instruction]? {
if idx < 1 || idx + 11 != instrs.length() {
return None
}
let returned_local = match instrs[idx - 1] {
@lib.LocalGet(local_idx) => local_idx
_ => return None
}
let return_temp = match instrs[idx + 1] {
@lib.LocalTee(local_idx) => local_idx
_ => return None
}
let wrapper_local = match instrs[idx + 4] {
@lib.LocalTee(local_idx) => local_idx
_ => return None
}
match
(
instrs[idx],
instrs[idx + 2],
instrs[idx + 3],
instrs[idx + 5],
instrs[idx + 6],
instrs[idx + 7],
instrs[idx + 8],
instrs[idx + 9],
instrs[idx + 10],
) {
(
@lib.Return,
@lib.I32Const(@lib.I32(8)),
@lib.Call(_),
@lib.I32Const(@lib.I32(2097153)),
@lib.I32Store(_),
@lib.LocalGet(wrapper_addr),
@lib.LocalGet(stored_value),
@lib.I64Store(_),
@lib.LocalGet(wrapper_result),
) if dae_local_idx_equal(wrapper_addr, wrapper_local) &&
dae_local_idx_equal(wrapper_result, wrapper_local) &&
dae_local_idx_equal(stored_value, return_temp) => ()
_ => return None
}
let out = dae_copy_instr_prefix(instrs, idx)
ignore(returned_local)
Some(out)
}
///|
fn dae_instrs_end_without_fallthrough(instrs : Array[@lib.Instruction]) -> Bool {
if instrs.length() == 0 {
return false
}
match instrs[instrs.length() - 1] {
@lib.Return | @lib.Unreachable | @lib.Br(_) => true
_ => false
}
}
///|
fn dae_try_strip_outer_block_no_fallthrough_wrapper_suffix(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if instrs.length() != 11 {
return None
}
let inner = match instrs[0] {
@lib.Block(@lib.VoidBlockType, @lib.Expr(inner_body)) if dae_instrs_end_without_fallthrough(
inner_body,
) => instrs[0]
_ => return None
}
let stored_local = match instrs[1] {
@lib.LocalSet(local_idx) => local_idx
_ => return None
}
let wrapper_local = match instrs[4] {
@lib.LocalTee(local_idx) => local_idx
_ => return None
}
match
(
instrs[2],
instrs[3],
instrs[5],
instrs[6],
instrs[7],
instrs[8],
instrs[9],
instrs[10],
) {
(
@lib.I32Const(@lib.I32(8)),
@lib.Call(_),
@lib.I32Const(@lib.I32(2097153)),
@lib.I32Store(_),
@lib.LocalGet(wrapper_addr),
@lib.LocalGet(stored_value),
@lib.I64Store(_),
@lib.LocalGet(wrapper_result),
) if dae_local_idx_equal(wrapper_addr, wrapper_local) &&
dae_local_idx_equal(wrapper_result, wrapper_local) &&
dae_local_idx_equal(stored_value, stored_local) => Some([inner])
_ => None
}
}
///|
fn dae_strip_root_return_suffix_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
match dae_try_strip_outer_block_no_fallthrough_wrapper_suffix(instrs) {
Some(rewritten) => return (rewritten, true)
None => ()
}
let mut suffix_idx = 0
while suffix_idx < instrs.length() {
match
dae_try_strip_terminal_lowered_return_wrapper_suffix_at(
instrs, suffix_idx,
) {
Some(rewritten) => return (rewritten, true)
None => ()
}
match dae_try_strip_terminal_dead_wrapper_suffix_at(instrs, suffix_idx) {
Some(rewritten) => return (rewritten, true)
None => ()
}
suffix_idx += 1
}
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
let rewritten_instr = match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten_body, nested_changed) = dae_strip_root_return_suffix_instrs(
body,
)
if nested_changed {
changed = true
}
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body))
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten_body, nested_changed) = dae_strip_root_return_suffix_instrs(
body,
)
if nested_changed {
changed = true
}
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body))
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten_body, nested_changed) = dae_strip_root_return_suffix_instrs(
body,
)
if nested_changed {
changed = true
}
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
)
}
@lib.If(block_type, then_body, Some(else_body)) => {
let (rewritten_then, then_changed) = dae_strip_root_return_suffix_instrs(
then_body,
)
let (rewritten_else, else_changed) = dae_strip_root_return_suffix_instrs(
else_body,
)
if then_changed || else_changed {
changed = true
}
@lib.Instruction::if_(block_type, rewritten_then, Some(rewritten_else))
}
@lib.If(block_type, then_body, None) => {
let (rewritten_then, then_changed) = dae_strip_root_return_suffix_instrs(
then_body,
)
if then_changed {
changed = true
}
@lib.Instruction::if_(block_type, rewritten_then, None)
}
_ => instrs[idx]
}
out.push(rewritten_instr)
if rewritten_instr == @lib.Instruction::return_() {
if idx + 1 < instrs.length() {
changed = true
}
return (out, changed)
}
idx += 1
}
(out, changed)
}
///|
fn dae_strip_module_root_return_suffix_once(
mod_ : @lib.Module,
touched_defs : Array[Bool],
) -> DaePassResult {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
ignore(touched_defs)
let mut rewritten_funcs : Array[@lib.Func]? = None
let mut primary_def = -1
let mut def_idx = 0
while def_idx < funcs.length() {
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
let (rewritten, body_changed) = dae_strip_root_return_suffix_instrs(body)
if body_changed {
let next_func = @lib.Func::new(locals, @lib.Expr::new(rewritten))
match rewritten_funcs {
Some(copy) => copy[def_idx] = next_func
None => {
let copy = funcs.copy()
copy[def_idx] = next_func
rewritten_funcs = Some(copy)
}
}
if primary_def < 0 {
primary_def = def_idx
}
}
def_idx += 1
}
match rewritten_funcs {
Some(copy) =>
{
mod_out: mod_.with_code_sec(@lib.CodeSec::new(copy)).without_name_sec(),
touched: touched_defs.copy(),
changed: true,
primary_def,
}
None => { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
}
///|
fn dae_local_reference_extent_note_slot(
evidence : DaeLocalReferenceExtentEvidence,
slot : Int32,
) -> DaeLocalReferenceExtentEvidence {
{
limit: evidence.limit,
max_referenced_local_slot: if slot > evidence.max_referenced_local_slot {
slot
} else {
evidence.max_referenced_local_slot
},
references_at_or_above_limit: evidence.references_at_or_above_limit ||
slot >= evidence.limit,
}
}
///|
fn dae_local_reference_extent_instr(
instr : @lib.Instruction,
evidence : DaeLocalReferenceExtentEvidence,
) -> DaeLocalReferenceExtentEvidence {
match instr {
@lib.LocalGet(idx) | @lib.LocalSet(idx) | @lib.LocalTee(idx) =>
dae_local_reference_extent_note_slot(evidence, dae_local_idx_slot(idx))
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_local_reference_extent_evidence_from_seed(body, evidence)
@lib.If(_, then_body, Some(else_body)) =>
dae_local_reference_extent_evidence_from_seed(
else_body,
dae_local_reference_extent_evidence_from_seed(then_body, evidence),
)
@lib.If(_, then_body, None) =>
dae_local_reference_extent_evidence_from_seed(then_body, evidence)
_ => evidence
}
}
///|
fn dae_local_reference_extent_evidence_from_seed(
instrs : Array[@lib.Instruction],
seed : DaeLocalReferenceExtentEvidence,
) -> DaeLocalReferenceExtentEvidence {
let mut evidence = seed
for instr in instrs {
evidence = dae_local_reference_extent_instr(instr, evidence)
}
evidence
}
///|
fn dae_local_reference_extent_evidence(
instrs : Array[@lib.Instruction],
initial_max : Int32,
limit : Int32,
) -> DaeLocalReferenceExtentEvidence {
dae_local_reference_extent_evidence_from_seed(instrs, {
limit,
max_referenced_local_slot: initial_max,
references_at_or_above_limit: false,
})
}
///|
fn dae_instrs_reference_local_at_or_above(
instrs : Array[@lib.Instruction],
limit : Int32,
) -> Bool {
dae_local_reference_extent_evidence(instrs, -1, limit).references_at_or_above_limit
}
///|
fn dae_copy_instr_prefix(
instrs : Array[@lib.Instruction],
end_exclusive : Int32,
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
let mut idx = 0
while idx < end_exclusive && idx < instrs.length() {
out.push(instrs[idx])
idx += 1
}
out
}
///|
fn dae_copy_instr_suffix(
instrs : Array[@lib.Instruction],
start_inclusive : Int32,
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
let mut idx = start_inclusive
while idx < instrs.length() {
out.push(instrs[idx])
idx += 1
}
out
}
///|
fn dae_strip_trailing_local_copy_to_br(
instrs : Array[@lib.Instruction],
required_written_local : @lib.LocalIdx?,
required_label : Int32,
) -> Array[@lib.Instruction]? {
if instrs.length() < 3 {
return None
}
let get_idx = instrs.length() - 3
let set_idx = instrs.length() - 2
let br_idx = instrs.length() - 1
match (instrs[get_idx], instrs[set_idx], instrs[br_idx]) {
(@lib.LocalGet(_), @lib.LocalSet(written), @lib.Br(@lib.LabelIdx(raw))) if raw.reinterpret_as_int() ==
required_label =>
match required_written_local {
Some(required) if !dae_local_idx_equal(written, required) => None
_ => Some(dae_copy_instr_prefix(instrs, get_idx))
}
_ => None
}
}
///|
fn dae_instr_writes_local(
instr : @lib.Instruction,
target_local : @lib.LocalIdx,
) -> Bool {
match instr {
@lib.LocalSet(idx) | @lib.LocalTee(idx) =>
dae_local_idx_equal(idx, target_local)
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_instrs_write_local(body, target_local)
@lib.If(_, then_body, Some(else_body)) =>
dae_instrs_write_local(then_body, target_local) ||
dae_instrs_write_local(else_body, target_local)
@lib.If(_, then_body, None) =>
dae_instrs_write_local(then_body, target_local)
@lib.Try(_, @lib.Expr(body), catches, _) => {
if dae_instrs_write_local(body, target_local) {
return true
}
for catch_ in catches {
let catch_body = match catch_ {
@lib.LegacyCatch(_, @lib.Expr(body))
| @lib.LegacyCatchAll(@lib.Expr(body)) => body
}
if dae_instrs_write_local(catch_body, target_local) {
return true
}
}
false
}
_ => false
}
}
///|
fn dae_instrs_write_local(
instrs : Array[@lib.Instruction],
target_local : @lib.LocalIdx,
) -> Bool {
for instr in instrs {
if dae_instr_writes_local(instr, target_local) {
return true
}
}
false
}
///|
fn dae_instrs_write_local_from(
instrs : Array[@lib.Instruction],
target_local : @lib.LocalIdx,
start : Int32,
) -> Bool {
let mut idx = start
while idx < instrs.length() {
if dae_instr_writes_local(instrs[idx], target_local) {
return true
}
idx += 1
}
false
}
///|
fn dae_rewrite_single_local_instr(
instr : @lib.Instruction,
from_local : @lib.LocalIdx,
to_local : @lib.LocalIdx,
) -> @lib.Instruction {
match instr {
@lib.LocalGet(idx) if dae_local_idx_equal(idx, from_local) =>
@lib.Instruction::local_get(to_local)
@lib.LocalSet(idx) if dae_local_idx_equal(idx, from_local) =>
@lib.Instruction::local_set(to_local)
@lib.LocalTee(idx) if dae_local_idx_equal(idx, from_local) =>
@lib.Instruction::local_tee(to_local)
@lib.Block(block_type, @lib.Expr(body)) =>
@lib.Instruction::block(
block_type,
@lib.Expr::new(
dae_rewrite_single_local_instrs(body, from_local, to_local),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
dae_rewrite_single_local_instrs(body, from_local, to_local),
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
dae_rewrite_single_local_instrs(body, from_local, to_local),
),
)
@lib.Try(block_type, @lib.Expr(body), catches, delegate) => {
let rewritten_catches = catches.map(fn(catch_) {
match catch_ {
@lib.LegacyCatch(tag, @lib.Expr(body)) =>
@lib.LegacyCatch::tagged(
tag,
@lib.Expr::new(
dae_rewrite_single_local_instrs(body, from_local, to_local),
),
)
@lib.LegacyCatchAll(@lib.Expr(body)) =>
@lib.LegacyCatch::all(
@lib.Expr::new(
dae_rewrite_single_local_instrs(body, from_local, to_local),
),
)
}
})
@lib.Instruction::try_(
block_type,
@lib.Expr::new(
dae_rewrite_single_local_instrs(body, from_local, to_local),
),
rewritten_catches,
delegate~,
)
}
@lib.If(block_type, then_body, else_body) =>
@lib.Instruction::if_(
block_type,
dae_rewrite_single_local_instrs(then_body, from_local, to_local),
match else_body {
Some(body) =>
Some(dae_rewrite_single_local_instrs(body, from_local, to_local))
None => None
},
)
_ => instr
}
}
///|
fn dae_rewrite_single_local_instrs(
instrs : Array[@lib.Instruction],
from_local : @lib.LocalIdx,
to_local : @lib.LocalIdx,
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
for instr in instrs {
out.push(dae_rewrite_single_local_instr(instr, from_local, to_local))
}
out
}
///|
fn dae_try_fold_func237_default_carrier_to_local_body(
body : Array[@lib.Instruction],
param_count : Int32,
type_sec : @lib.TypeSec?,
target_local : @lib.LocalIdx,
) -> Array[@lib.Instruction]? {
if body.length() < 2 {
return None
}
let carrier = match body[1] {
@lib.LocalSet(idx) => idx
_ => return None
}
if dae_instrs_write_local_from(body, carrier, 2) {
return None
}
let (outer_block_type, outer_body) = match body[0] {
@lib.Block(block_type, @lib.Expr(instrs)) => (block_type, instrs)
_ => return None
}
if !dae_block_type_is_zero_param_single_result(outer_block_type, type_sec) ||
outer_body.length() == 0 {
return None
}
let wrapper_body = match outer_body[0] {
@lib.Block(block_type, @lib.Expr(instrs)) if dae_block_type_is_zero_param_zero_result(
block_type, type_sec,
) => instrs
_ => return None
}
if wrapper_body.length() != 2 {
return None
}
let value_body = match (wrapper_body[0], wrapper_body[1]) {
(@lib.Block(block_type, @lib.Expr(instrs)), @lib.Br(@lib.LabelIdx(1U))) if dae_block_type_is_zero_param_single_result(
block_type, type_sec,
) => instrs
_ => return None
}
if value_body.length() != 2 {
return None
}
let default_local = match value_body[1] {
@lib.LocalGet(idx) => idx
_ => return None
}
if dae_local_idx_slot(default_local) < param_count {
return None
}
let join_body = match value_body[0] {
@lib.Block(block_type, @lib.Expr(instrs)) if dae_block_type_is_zero_param_zero_result(
block_type, type_sec,
) => instrs
_ => return None
}
if join_body.length() < 2 ||
join_body[join_body.length() - 1] != @lib.Instruction::unreachable_() {
return None
}
let label_body = if join_body.length() == 2 {
match join_body[0] {
@lib.Block(block_type, @lib.Expr(instrs)) if dae_block_type_is_zero_param_zero_result(
block_type, type_sec,
) => instrs
_ => dae_copy_instr_prefix(join_body, join_body.length() - 1)
}
} else {
dae_copy_instr_prefix(join_body, join_body.length() - 1)
}
if label_body.length() == 0 {
return None
}
let if_idx = label_body.length() - 1
let (then_body, else_body) = match label_body[if_idx] {
@lib.If(block_type, then_instrs, Some(else_instrs)) if dae_block_type_is_zero_param_zero_result(
block_type, type_sec,
) => (then_instrs, else_instrs)
_ => return None
}
let rewritten_then = match
dae_strip_trailing_local_copy_to_br(then_body, Some(default_local), 0) {
Some(instrs) => instrs
None => return None
}
let rewritten_else = match
dae_strip_trailing_local_copy_to_br(else_body, None, 1) {
Some(instrs) => instrs
None => return None
}
rewritten_else.push(@lib.Instruction::br(@lib.LabelIdx::new(1U)))
let folded_block_body = dae_copy_instr_prefix(label_body, if_idx)
folded_block_body.push(
@lib.Instruction::if_(
@lib.BlockType::void_(),
rewritten_then,
Some(rewritten_else),
),
)
folded_block_body.push(@lib.Instruction::unreachable_())
let cleaned_block_body = dae_strip_nops_and_dropped_pure_leaf_instrs(
folded_block_body,
type_sec,
preserve_effect_dropped_integer_const_debris=false,
preserve_nop_debris=false,
)
let out : Array[@lib.Instruction] = [
@lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new(cleaned_block_body),
),
@lib.Instruction::i32_const(@lib.I32(0)),
@lib.Instruction::local_set(target_local),
]
let mut idx = 2
while idx < body.length() {
out.push(dae_rewrite_single_local_instr(body[idx], carrier, target_local))
idx += 1
}
Some(out)
}
///|
#warnings("-unused_value")
fn dae_try_fold_selected_func237_default_carrier_to_local0_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let def_idx = 237
if def_idx >= selected_defs.length() || !selected_defs[def_idx] {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if def_idx >= funcs.length() {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
let rewritten = match
dae_try_fold_func237_default_carrier_to_local_body(
body,
4,
mod_.type_sec,
@lib.LocalIdx::new(0U),
) {
Some(instrs) => instrs
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if rewritten == body {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let rewritten_funcs = funcs.copy()
rewritten_funcs[def_idx] = @lib.Func::new(locals, @lib.Expr::new(rewritten))
let touched = Array::make(funcs.length(), false)
touched[def_idx] = true
{
mod_out: mod_
.with_code_sec(@lib.CodeSec::new(rewritten_funcs))
.without_name_sec(),
touched,
changed: true,
primary_def: def_idx,
}
}
///|
fn dae_label_idx_as_int(label_idx : @lib.LabelIdx) -> Int32 {
let @lib.LabelIdx(raw) = label_idx
raw.reinterpret_as_int()
}
///|
fn dae_branch_targets_outside_depth(
label_idx : @lib.LabelIdx,
depth : Int32,
) -> Bool {
dae_label_idx_as_int(label_idx) >= depth
}
///|
fn dae_instr_has_escaping_branch(
instr : @lib.Instruction,
depth : Int32,
) -> Bool {
match instr {
@lib.Br(label_idx) | @lib.BrIf(label_idx) =>
dae_branch_targets_outside_depth(label_idx, depth)
@lib.BrTable(labels, fallback) => {
for label_idx in labels {
if dae_branch_targets_outside_depth(label_idx, depth) {
return true
}
}
dae_branch_targets_outside_depth(fallback, depth)
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_instrs_have_escaping_branch(body, depth + 1)
@lib.If(_, then_body, Some(else_body)) =>
dae_instrs_have_escaping_branch(then_body, depth + 1) ||
dae_instrs_have_escaping_branch(else_body, depth + 1)
@lib.If(_, then_body, None) =>
dae_instrs_have_escaping_branch(then_body, depth + 1)
_ => false
}
}
///|
fn dae_escaping_branch_evidence_is_consistent(
evidence : DaeEscapingBranchEvidence,
) -> Bool {
evidence.start >= 0 &&
evidence.end >= evidence.start &&
evidence.depth >= 0 &&
evidence.instr_count == evidence.end - evidence.start
}
///|
fn dae_escaping_branch_range_evidence(
instrs : Array[@lib.Instruction],
start : Int32,
end : Int32,
depth : Int32,
) -> DaeEscapingBranchEvidence? {
if start < 0 || end < start || end > instrs.length() || depth < 0 {
return None
}
let mut has_escaping_branch = false
let mut idx = start
while idx < end {
if dae_instr_has_escaping_branch(instrs[idx], depth) {
has_escaping_branch = true
break
}
idx += 1
}
let evidence = {
start,
end,
depth,
instr_count: end - start,
has_escaping_branch,
}
if dae_escaping_branch_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
///|
fn dae_escaping_branch_evidence(
instrs : Array[@lib.Instruction],
depth : Int32,
) -> DaeEscapingBranchEvidence? {
dae_escaping_branch_range_evidence(instrs, 0, instrs.length(), depth)
}
///|
fn dae_no_escaping_branch_range_evidence(
instrs : Array[@lib.Instruction],
start : Int32,
end : Int32,
depth : Int32,
) -> DaeEscapingBranchEvidence? {
match dae_escaping_branch_range_evidence(instrs, start, end, depth) {
Some(evidence) if !evidence.has_escaping_branch => Some(evidence)
_ => None
}
}
///|
fn dae_no_escaping_branch_prefix_evidence(
instrs : Array[@lib.Instruction],
end : Int32,
depth : Int32,
) -> DaeEscapingBranchEvidence? {
dae_no_escaping_branch_range_evidence(instrs, 0, end, depth)
}
///|
fn dae_no_escaping_branch_suffix_evidence(
instrs : Array[@lib.Instruction],
start : Int32,
depth : Int32,
) -> DaeEscapingBranchEvidence? {
dae_no_escaping_branch_range_evidence(instrs, start, instrs.length(), depth)
}
///|
fn dae_no_escaping_branch_evidence(
instrs : Array[@lib.Instruction],
depth : Int32,
) -> DaeEscapingBranchEvidence? {
match dae_escaping_branch_evidence(instrs, depth) {
Some(evidence) if !evidence.has_escaping_branch => Some(evidence)
_ => None
}
}
///|
fn dae_stack_slice_localization_evidence_is_consistent(
evidence : DaeStackSliceLocalizationEvidence,
) -> Bool {
dae_stack_slice_transition_evidence_is_consistent(evidence.transition) &&
dae_escaping_branch_evidence_is_consistent(evidence.no_escape_branch) &&
!evidence.no_escape_branch.has_escaping_branch &&
evidence.transition.simulation.start == evidence.no_escape_branch.start &&
evidence.transition.simulation.end == evidence.no_escape_branch.end &&
dae_no_escape_slice_evidence_is_consistent(evidence.copied_slice) &&
evidence.copied_slice.branch.start == evidence.no_escape_branch.start &&
evidence.copied_slice.branch.end == evidence.no_escape_branch.end &&
evidence.disposition.start == evidence.no_escape_branch.start &&
evidence.disposition.end == evidence.no_escape_branch.end &&
evidence.rewrite_disposition.operand.start == evidence.no_escape_branch.start &&
evidence.rewrite_disposition.operand.end == evidence.no_escape_branch.end &&
evidence.rewrite_disposition.localization_available &&
!evidence.rewrite_disposition.has_escaping_branch &&
dae_operand_rewrite_disposition_is_consistent(evidence.rewrite_disposition) &&
(
dae_operand_rewrite_drops_directly(evidence.rewrite_disposition) ||
dae_operand_rewrite_localizes_and_retries(evidence.rewrite_disposition)
)
}
///|
fn dae_stack_slice_localization_policy(
initial_stack_values : Int32,
expected_final_stack_values : Int32,
depth : Int32,
instr_allowed : (@lib.Instruction) -> Bool,
) -> DaeStackSliceLocalizationPolicy {
{ initial_stack_values, expected_final_stack_values, depth, instr_allowed, }
}
///|
fn dae_select_condition_localization_policy() -> DaeStackSliceLocalizationPolicy {
dae_stack_slice_localization_policy(
0, 1, 0, dae_select_condition_stack_slice_instr_allowed,
)
}
///|
fn dae_parametric_typeidx_prefix_localization_policy(
param_count : Int32,
) -> DaeStackSliceLocalizationPolicy {
dae_stack_slice_localization_policy(
param_count, 0, 0, dae_parametric_typeidx_stack_slice_instr_allowed,
)
}
///|
fn dae_stack_slice_localization_request(
body : Array[@lib.Instruction],
start : Int32,
end : Int32,
initial_stack_values : Int32,
expected_final_stack_values : Int32,
depth : Int32,
instr_allowed : (@lib.Instruction) -> Bool,
) -> DaeStackSliceLocalizationRequest {
{
body,
start,
end,
initial_stack_values,
expected_final_stack_values,
depth,
instr_allowed,
}
}
///|
fn dae_stack_slice_localization_request_from_policy(
body : Array[@lib.Instruction],
start : Int32,
end : Int32,
policy : DaeStackSliceLocalizationPolicy,
) -> DaeStackSliceLocalizationRequest {
dae_stack_slice_localization_request(
body,
start,
end,
policy.initial_stack_values,
policy.expected_final_stack_values,
policy.depth,
policy.instr_allowed,
)
}
///|
fn dae_stack_slice_localization_evidence_from_policy(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
body : Array[@lib.Instruction],
start : Int32,
end : Int32,
policy : DaeStackSliceLocalizationPolicy,
) -> DaeStackSliceLocalizationEvidence? {
dae_stack_slice_localization_evidence_from_request(
mod_,
func_type_indices,
dae_stack_slice_localization_request_from_policy(body, start, end, policy),
)
}
///|
fn dae_stack_slice_localization_policy_accepts(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
body : Array[@lib.Instruction],
start : Int32,
end : Int32,
policy : DaeStackSliceLocalizationPolicy,
) -> Bool {
match
dae_stack_slice_localization_evidence_from_policy(
mod_, func_type_indices, body, start, end, policy,
) {
Some(_) => true
None => false
}
}
///|
fn dae_stack_slice_localization_evidence_from_request(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
request : DaeStackSliceLocalizationRequest,
) -> DaeStackSliceLocalizationEvidence? {
let transition = match
dae_stack_slice_transition_evidence(
mod_,
func_type_indices,
request.body,
request.start,
request.end,
request.initial_stack_values,
request.expected_final_stack_values,
request.instr_allowed,
) {
Some(evidence) => evidence
None => return None
}
let no_escape_branch = match
dae_no_escaping_branch_range_evidence(
request.body,
request.start,
request.end,
request.depth,
) {
Some(evidence) => evidence
None => return None
}
let copied_slice = match
dae_no_escape_slice_evidence_from_branch(request.body, no_escape_branch) {
Some(evidence) => evidence
None => return None
}
let disposition = match
dae_operand_disposition_from_range(request.body, request.start, request.end) {
Some(evidence) => evidence
None => return None
}
let evidence = {
transition,
no_escape_branch,
copied_slice,
disposition,
rewrite_disposition: dae_operand_rewrite_disposition(
disposition,
localization_available=true,
has_escaping_branch=false,
),
}
if dae_stack_slice_localization_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
///|
fn dae_no_escape_slice_evidence_is_consistent(
evidence : DaeNoEscapeSliceEvidence,
) -> Bool {
dae_escaping_branch_evidence_is_consistent(evidence.branch) &&
!evidence.branch.has_escaping_branch &&
evidence.instrs.length() == evidence.branch.instr_count
}
///|
fn dae_no_escape_slice_evidence_from_branch(
instrs : Array[@lib.Instruction],
branch : DaeEscapingBranchEvidence,
) -> DaeNoEscapeSliceEvidence? {
if branch.start < 0 ||
branch.end > instrs.length() ||
!dae_escaping_branch_evidence_is_consistent(branch) ||
branch.has_escaping_branch {
return None
}
let copied : Array[@lib.Instruction] = []
let mut idx = branch.start
while idx < branch.end {
copied.push(instrs[idx])
idx += 1
}
let evidence = { branch, instrs: copied, }
if dae_no_escape_slice_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
///|
fn dae_no_escaping_branch_copied_slice_evidence(
instrs : Array[@lib.Instruction],
start : Int32,
end : Int32,
depth : Int32,
) -> DaeNoEscapeSliceEvidence? {
let branch = match
dae_no_escaping_branch_range_evidence(instrs, start, end, depth) {
Some(evidence) => evidence
None => return None
}
dae_no_escape_slice_evidence_from_branch(instrs, branch)
}
///|
fn dae_no_escaping_branch_copied_range_evidence(
instrs : Array[@lib.Instruction],
start : Int32,
end : Int32,
depth : Int32,
) -> DaeNoEscapeSliceEvidence? {
dae_no_escaping_branch_copied_slice_evidence(instrs, start, end, depth)
}
///|
fn dae_no_escaping_branch_copied_prefix_evidence(
instrs : Array[@lib.Instruction],
end : Int32,
depth : Int32,
) -> DaeNoEscapeSliceEvidence? {
dae_no_escaping_branch_copied_slice_evidence(instrs, 0, end, depth)
}
///|
fn dae_no_escaping_branch_copied_suffix_evidence(
instrs : Array[@lib.Instruction],
start : Int32,
depth : Int32,
) -> DaeNoEscapeSliceEvidence? {
let branch = match
dae_no_escaping_branch_suffix_evidence(instrs, start, depth) {
Some(evidence) => evidence
None => return None
}
dae_no_escape_slice_evidence_from_branch(instrs, branch)
}
///|
fn dae_no_escaping_branch_copied_evidence(
instrs : Array[@lib.Instruction],
depth : Int32,
) -> DaeNoEscapeSliceEvidence? {
dae_no_escaping_branch_copied_slice_evidence(
instrs,
0,
instrs.length(),
depth,
)
}
///|
fn dae_terminal_no_escape_prefix_evidence_is_consistent(
evidence : DaeTerminalNoEscapePrefixEvidence,
) -> Bool {
dae_no_escape_slice_evidence_is_consistent(evidence.prefix) &&
evidence.terminal_idx == evidence.prefix.branch.end &&
evidence.terminal_idx >= 0
}
///|
fn dae_terminal_no_escape_prefix_evidence(
instrs : Array[@lib.Instruction],
depth : Int32,
) -> DaeTerminalNoEscapePrefixEvidence? {
if instrs.length() == 0 {
return None
}
let terminal_idx = instrs.length() - 1
let prefix = match
dae_no_escaping_branch_copied_prefix_evidence(instrs, terminal_idx, depth) {
Some(evidence) => evidence
None => return None
}
let evidence = { prefix, terminal: instrs[terminal_idx], terminal_idx, }
if dae_terminal_no_escape_prefix_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
///|
fn dae_terminal_if_no_escape_carrier_evidence(
instrs : Array[@lib.Instruction],
if_idx : Int32,
type_sec : @lib.TypeSec?,
) -> DaeTerminalIfNoEscapeCarrierEvidence? {
if if_idx < 0 || if_idx >= instrs.length() {
return None
}
let condition_prefix = match
dae_no_escaping_branch_copied_prefix_evidence(instrs, if_idx, 0) {
Some(evidence) => evidence
None => return None
}
let (then_body, maybe_else_body) = match instrs[if_idx] {
@lib.If(block_type, then_body, else_body) if dae_block_type_is_zero_param_zero_result(
block_type, type_sec,
) => (then_body, else_body)
_ => return None
}
let else_body = match maybe_else_body {
Some(body) =>
match dae_no_escaping_branch_copied_evidence(body, 0) {
Some(evidence) => Some(evidence)
None => return None
}
None => None
}
Some({ condition_prefix, then_body, else_body, })
}
///|
fn dae_instrs_have_escaping_branch(
instrs : Array[@lib.Instruction],
depth : Int32,
) -> Bool {
match dae_escaping_branch_range_evidence(instrs, 0, instrs.length(), depth) {
Some(evidence) => evidence.has_escaping_branch
None => true
}
}
///|
fn dae_func237_outer_loop_then_split_next_carrier(
then_body : Array[@lib.Instruction],
carrier_local : @lib.LocalIdx,
) -> (Array[@lib.Instruction], Array[@lib.Instruction])? {
if then_body.length() < 5 {
return None
}
let value_start = then_body.length() - 5
let const_idx = then_body.length() - 4
let add_idx = then_body.length() - 3
let set_idx = then_body.length() - 2
let br_idx = then_body.length() - 1
match
(
then_body[const_idx],
then_body[add_idx],
then_body[set_idx],
then_body[br_idx],
) {
(
@lib.I32Const(@lib.I32(1)),
@lib.I32Add,
@lib.LocalSet(set_local),
@lib.Br(@lib.LabelIdx(1U)),
) if dae_local_idx_equal(set_local, carrier_local) => {
let prefix = match
dae_no_escaping_branch_copied_range_evidence(
then_body, 0, value_start, 0,
) {
Some(evidence) => evidence.instrs
None => return None
}
let value_instrs : Array[@lib.Instruction] = [
then_body[value_start],
then_body[const_idx],
then_body[add_idx],
]
Some((prefix, value_instrs))
}
_ => None
}
}
///|
fn dae_func237_condition_iteration_alias(
condition_prefix : Array[@lib.Instruction],
carrier_local : @lib.LocalIdx,
) -> @lib.LocalIdx? {
if condition_prefix.length() < 2 {
return None
}
match (condition_prefix[0], condition_prefix[1]) {
(@lib.LocalGet(get_local), @lib.LocalTee(iter_local)) if dae_local_idx_equal(
get_local, carrier_local,
) => Some(iter_local)
_ => None
}
}
///|
fn dae_rewrite_first_top_level_local_get_before_write(
instrs : Array[@lib.Instruction],
from_local : @lib.LocalIdx,
to_local : @lib.LocalIdx,
) -> Array[@lib.Instruction]? {
let mut idx = 0
while idx < instrs.length() {
match instrs[idx] {
@lib.LocalGet(get_local) if dae_local_idx_equal(get_local, from_local) => {
let rewritten = instrs.copy()
rewritten[idx] = @lib.Instruction::local_get(to_local)
return Some(rewritten)
}
_ => ()
}
if dae_instr_writes_local(instrs[idx], from_local) ||
dae_instr_writes_local(instrs[idx], to_local) {
return None
}
idx += 1
}
None
}
///|
fn dae_try_wrap_func237_outer_loop_carrier_instr(
instr : @lib.Instruction,
carrier_local : @lib.LocalIdx,
type_sec : @lib.TypeSec?,
) -> @lib.Instruction? {
let loop_body = match instr {
@lib.Loop(block_type, @lib.Expr(loop_body)) if dae_block_type_is_zero_param_zero_result(
block_type, type_sec,
) => loop_body
_ => return None
}
if loop_body.length() == 0 {
return None
}
let if_idx = loop_body.length() - 1
let condition_prefix = match
dae_no_escaping_branch_copied_range_evidence(loop_body, 0, if_idx, 0) {
Some(evidence) => evidence.instrs
None => return None
}
let (then_body, else_body) = match loop_body[if_idx] {
@lib.If(block_type, then_body, Some(else_body)) if dae_block_type_is_zero_param_zero_result(
block_type, type_sec,
) => (then_body, else_body)
_ => return None
}
if dae_no_escaping_branch_evidence(else_body, 0) is None {
return None
}
let (raw_then_prefix, next_value) = match
dae_func237_outer_loop_then_split_next_carrier(then_body, carrier_local) {
Some(parts) => parts
None => return None
}
let then_prefix = match
dae_func237_condition_iteration_alias(condition_prefix, carrier_local) {
Some(iter_local) =>
match
dae_rewrite_first_top_level_local_get_before_write(
raw_then_prefix, iter_local, carrier_local,
) {
Some(rewritten_prefix) => rewritten_prefix
None => raw_then_prefix
}
None => raw_then_prefix
}
let rewritten_then = then_prefix.copy()
for value_instr in next_value {
rewritten_then.push(value_instr)
}
rewritten_then.push(@lib.Instruction::br(@lib.LabelIdx::new(1U)))
let rewritten_else = else_body.copy()
rewritten_else.push(@lib.Instruction::br(@lib.LabelIdx::new(2U)))
let result_block_body = condition_prefix.copy()
result_block_body.push(
@lib.Instruction::if_(
@lib.BlockType::void_(),
rewritten_then,
Some(rewritten_else),
),
)
Some(
@lib.Instruction::loop_(
@lib.BlockType::void_(),
@lib.Expr::new([
@lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new([
@lib.Instruction::block(
@lib.BlockType::val_type(@lib.ValType::i32()),
@lib.Expr::new(result_block_body),
),
@lib.Instruction::local_set(carrier_local),
@lib.Instruction::br(@lib.LabelIdx::new(1U)),
]),
),
]),
),
)
}
///|
fn dae_try_wrap_func237_outer_loop_carrier_body_once(
body : Array[@lib.Instruction],
carrier_local : @lib.LocalIdx,
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let rewrite_instr = fn(instr : @lib.Instruction) -> @lib.Instruction? {
dae_try_wrap_func237_outer_loop_carrier_instr(
instr, carrier_local, type_sec,
)
}
match dae_apply_first_instruction_rewrite_to_body(body, rewrite_instr) {
Some((rewritten, _)) => Some(rewritten)
None => None
}
}
///|
#warnings("-unused_value")
fn dae_try_wrap_selected_func237_outer_loop_carrier_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_body = fn(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
dae_try_wrap_func237_outer_loop_carrier_body_once(
body,
@lib.LocalIdx::new(0U),
type_sec,
)
}
dae_try_apply_selected_def_body_rewrite_once(
mod_, selected_defs, 237, rewrite_body,
)
}
///|
fn dae_shift_label_after_removed_result_wrapper(
label_idx : @lib.LabelIdx,
cutoff : Int32,
) -> @lib.LabelIdx? {
let depth = dae_label_idx_as_int(label_idx)
if depth == cutoff {
return Some(@lib.LabelIdx::new((cutoff + 1).reinterpret_as_uint()))
}
if depth > cutoff {
return Some(@lib.LabelIdx::new((depth - 1).reinterpret_as_uint()))
}
Some(label_idx)
}
///|
fn dae_shift_instr_after_removed_result_wrapper(
instr : @lib.Instruction,
cutoff : Int32,
) -> @lib.Instruction? {
match instr {
@lib.Br(label_idx) =>
match dae_shift_label_after_removed_result_wrapper(label_idx, cutoff) {
Some(shifted) => Some(@lib.Instruction::br(shifted))
None => None
}
@lib.BrIf(label_idx) =>
match dae_shift_label_after_removed_result_wrapper(label_idx, cutoff) {
Some(shifted) => Some(@lib.Instruction::br_if(shifted))
None => None
}
@lib.BrTable(labels, fallback) => {
let shifted_labels : Array[@lib.LabelIdx] = []
for label in labels {
match dae_shift_label_after_removed_result_wrapper(label, cutoff) {
Some(shifted) => shifted_labels.push(shifted)
None => return None
}
}
match dae_shift_label_after_removed_result_wrapper(fallback, cutoff) {
Some(shifted_fallback) =>
Some(@lib.Instruction::br_table(shifted_labels, shifted_fallback))
None => None
}
}
@lib.BrOnNull(label_idx) =>
match dae_shift_label_after_removed_result_wrapper(label_idx, cutoff) {
Some(shifted) => Some(@lib.Instruction::br_on_null(shifted))
None => None
}
@lib.BrOnNonNull(label_idx) =>
match dae_shift_label_after_removed_result_wrapper(label_idx, cutoff) {
Some(shifted) => Some(@lib.Instruction::br_on_non_null(shifted))
None => None
}
@lib.BrOnCast(_, _, _, _) | @lib.BrOnCastFail(_, _, _, _) => None
@lib.Block(block_type, @lib.Expr(body)) =>
match dae_shift_instrs_after_removed_result_wrapper(body, cutoff + 1) {
Some(shifted_body) =>
Some(
@lib.Instruction::block(block_type, @lib.Expr::new(shifted_body)),
)
None => None
}
@lib.Loop(block_type, @lib.Expr(body)) =>
match dae_shift_instrs_after_removed_result_wrapper(body, cutoff + 1) {
Some(shifted_body) =>
Some(
@lib.Instruction::loop_(block_type, @lib.Expr::new(shifted_body)),
)
None => None
}
@lib.If(block_type, then_body, Some(else_body)) =>
match
dae_shift_instrs_after_removed_result_wrapper(then_body, cutoff + 1) {
Some(shifted_then) =>
match
dae_shift_instrs_after_removed_result_wrapper(else_body, cutoff + 1) {
Some(shifted_else) =>
Some(
@lib.Instruction::if_(
block_type,
shifted_then,
Some(shifted_else),
),
)
None => None
}
None => None
}
@lib.If(block_type, then_body, None) =>
match
dae_shift_instrs_after_removed_result_wrapper(then_body, cutoff + 1) {
Some(shifted_then) =>
Some(@lib.Instruction::if_(block_type, shifted_then, None))
None => None
}
@lib.TryTable(_, _, _) => None
_ => Some(instr)
}
}
///|
fn dae_shift_instrs_after_removed_result_wrapper(
instrs : Array[@lib.Instruction],
cutoff : Int32,
) -> Array[@lib.Instruction]? {
let shifted : Array[@lib.Instruction] = []
for instr in instrs {
match dae_shift_instr_after_removed_result_wrapper(instr, cutoff) {
Some(shifted_instr) => shifted.push(shifted_instr)
None => return None
}
}
Some(shifted)
}
///|
fn dae_try_flatten_func237_nested_branch_result_block_body(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
if body.length() == 0 {
return None
}
let (wrapper_block_type, wrapper_body) = match body[0] {
@lib.Block(block_type, @lib.Expr(instrs)) => (block_type, instrs)
_ => return None
}
if !dae_block_type_is_zero_param_zero_result(wrapper_block_type, type_sec) ||
wrapper_body.length() != 2 {
return None
}
let inner_body = match (wrapper_body[0], wrapper_body[1]) {
(@lib.Block(inner_block_type, @lib.Expr(instrs)), @lib.Br(@lib.LabelIdx(1U))
) if dae_block_type_is_zero_param_single_result(inner_block_type, type_sec) =>
instrs
_ => return None
}
let shifted_inner = match
dae_shift_instrs_after_removed_result_wrapper(inner_body, 0) {
Some(instrs) => instrs
None => return None
}
shifted_inner.push(@lib.Instruction::br(@lib.LabelIdx::new(1U)))
let rewritten = body.copy()
rewritten[0] = @lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new(shifted_inner),
)
Some(rewritten)
}
///|
fn dae_try_flatten_func237_nested_branch_result_instr_once(
instr : @lib.Instruction,
type_sec : @lib.TypeSec?,
) -> @lib.Instruction? {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
match
dae_try_flatten_func237_nested_branch_result_body_once(body, type_sec) {
Some(rewritten_body) =>
return Some(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body)),
)
None => ()
}
if dae_block_type_is_zero_param_single_result(block_type, type_sec) {
match
dae_try_flatten_func237_nested_branch_result_block_body(
body, type_sec,
) {
Some(rewritten_body) =>
return Some(
@lib.Instruction::block(
block_type,
@lib.Expr::new(rewritten_body),
),
)
None => ()
}
}
None
}
@lib.Loop(block_type, @lib.Expr(body)) =>
match
dae_try_flatten_func237_nested_branch_result_body_once(body, type_sec) {
Some(rewritten_body) =>
Some(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body)),
)
None => None
}
@lib.If(block_type, then_body, Some(else_body)) =>
match
dae_try_flatten_func237_nested_branch_result_body_once(
then_body, type_sec,
) {
Some(rewritten_then) =>
Some(
@lib.Instruction::if_(block_type, rewritten_then, Some(else_body)),
)
None =>
match
dae_try_flatten_func237_nested_branch_result_body_once(
else_body, type_sec,
) {
Some(rewritten_else) =>
Some(
@lib.Instruction::if_(
block_type,
then_body,
Some(rewritten_else),
),
)
None => None
}
}
@lib.If(block_type, then_body, None) =>
match
dae_try_flatten_func237_nested_branch_result_body_once(
then_body, type_sec,
) {
Some(rewritten_then) =>
Some(@lib.Instruction::if_(block_type, rewritten_then, None))
None => None
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
match
dae_try_flatten_func237_nested_branch_result_body_once(body, type_sec) {
Some(rewritten_body) =>
Some(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
),
)
None => None
}
_ => None
}
}
///|
fn dae_try_flatten_func237_nested_branch_result_body_once(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let rewrite_instr = fn(instr : @lib.Instruction) -> @lib.Instruction? {
dae_try_flatten_func237_nested_branch_result_instr_once(instr, type_sec)
}
dae_apply_first_recursive_instruction_body_rewrite_to_body(
body, rewrite_instr,
)
}
///|
#warnings("-unused_value")
fn dae_try_flatten_selected_func237_nested_branch_result_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_body = fn(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
dae_try_flatten_func237_nested_branch_result_body_once(body, type_sec)
}
dae_try_apply_selected_def_body_rewrite_once(
mod_, selected_defs, 237, rewrite_body,
)
}
///|
fn dae_try_fold_func237_dropped_result_branch_carrier_at(
body : Array[@lib.Instruction],
idx : Int32,
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
if idx + 1 >= body.length() || body[idx + 1] != @lib.Instruction::drop() {
return None
}
let (result_block_type, result_body) = match body[idx] {
@lib.Block(block_type, @lib.Expr(instrs)) => (block_type, instrs)
_ => return None
}
if !dae_block_type_is_zero_param_single_result(result_block_type, type_sec) ||
result_body.length() < 2 {
return None
}
let (inner_block_type, inner_body) = match result_body[0] {
@lib.Block(block_type, @lib.Expr(instrs)) => (block_type, instrs)
_ => return None
}
if !dae_block_type_is_zero_param_zero_result(inner_block_type, type_sec) ||
inner_body.length() < 2 {
return None
}
let final_const_idx = inner_body.length() - 2
let final_br_idx = inner_body.length() - 1
match (inner_body[final_const_idx], inner_body[final_br_idx]) {
(@lib.I32Const(@lib.I32(0)), @lib.Br(@lib.LabelIdx(1U))) => ()
_ => return None
}
// The only allowed branch to the result carrier is the final `br 1` whose
// `i32.const 0` payload is discarded by the following `drop`. Other branches
// to that carrier would need a value after the carrier becomes void.
let inner_prefix = match
dae_no_escaping_branch_copied_prefix_evidence(
inner_body, final_const_idx, 1,
) {
Some(evidence) => evidence.instrs
None => return None
}
let post_body = match
dae_no_escaping_branch_copied_suffix_evidence(result_body, 1, 0) {
Some(evidence) => evidence.instrs
None => return None
}
let rewritten_inner = inner_prefix.copy()
rewritten_inner.push(@lib.Instruction::br(@lib.LabelIdx::new(1U)))
let rewritten_result_body : Array[@lib.Instruction] = [
@lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new(rewritten_inner),
),
]
for instr in post_body {
rewritten_result_body.push(instr)
}
let rewritten : Array[@lib.Instruction] = []
let mut before = 0
while before < idx {
rewritten.push(body[before])
before += 1
}
rewritten.push(
@lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new(rewritten_result_body),
),
)
let mut after = idx + 2
while after < body.length() {
rewritten.push(body[after])
after += 1
}
Some(rewritten)
}
///|
fn dae_try_fold_func237_dropped_result_branch_carrier_body_once(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let mut idx = 0
while idx < body.length() {
match
dae_try_fold_func237_dropped_result_branch_carrier_at(body, idx, type_sec) {
Some(rewritten) => return Some(rewritten)
None => ()
}
idx += 1
}
None
}
///|
fn dae_try_fold_func237_dropped_result_branch_carrier_body_recursive_once(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let rewrite_current_body = fn(
current_body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
dae_try_fold_func237_dropped_result_branch_carrier_body_once(
current_body, type_sec,
)
}
let rewrite_child_body = fn(
child_body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
dae_try_fold_func237_dropped_result_branch_carrier_body_recursive_once(
child_body, type_sec,
)
}
dae_apply_first_current_or_control_child_body_rewrite(
body, rewrite_current_body, rewrite_child_body,
)
}
///|
#warnings("-unused_value")
fn dae_try_fold_selected_func237_dropped_result_branch_carrier_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_body = fn(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
dae_try_fold_func237_dropped_result_branch_carrier_body_recursive_once(
body, type_sec,
)
}
dae_try_apply_selected_def_body_rewrite_once(
mod_, selected_defs, 237, rewrite_body,
)
}
///|
fn dae_func237_body_ends_without_fallthrough(
body : Array[@lib.Instruction],
) -> Bool {
if body.length() == 0 {
return false
}
match body[body.length() - 1] {
@lib.Br(_) | @lib.Return | @lib.Unreachable => true
_ => false
}
}
///|
fn dae_try_fold_func237_unreachable_after_if_else_branch_at(
body : Array[@lib.Instruction],
idx : Int32,
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let has_trailing_unreachable = idx + 1 < body.length() &&
body[idx + 1] == @lib.Instruction::unreachable_()
if idx + 1 < body.length() && !has_trailing_unreachable {
return None
}
let (block_type, then_body, else_body) = match body[idx] {
@lib.If(block_type, then_body, Some(else_body)) =>
(block_type, then_body, else_body)
_ => return None
}
if !dae_block_type_is_zero_param_zero_result(block_type, type_sec) ||
!dae_func237_body_ends_without_fallthrough(then_body) ||
else_body.length() == 0 {
return None
}
let terminal_else = match
dae_terminal_no_escape_prefix_evidence(else_body, 0) {
Some(evidence) => evidence
None => return None
}
let final_label = match terminal_else.terminal {
@lib.Br(label_idx) => label_idx
_ => return None
}
let depth = dae_label_idx_as_int(final_label)
if depth < 0 {
return None
}
let else_prefix = terminal_else.prefix.instrs
let rewritten : Array[@lib.Instruction] = []
let mut before = 0
while before < idx {
rewritten.push(body[before])
before += 1
}
rewritten.push(
@lib.Instruction::if_(block_type, then_body, Some(else_prefix)),
)
let moved_depth = if depth > 0 { depth - 1 } else { 0 }
rewritten.push(
@lib.Instruction::br(@lib.LabelIdx::new(moved_depth.reinterpret_as_uint())),
)
let mut after = if has_trailing_unreachable { idx + 2 } else { idx + 1 }
while after < body.length() {
rewritten.push(body[after])
after += 1
}
Some(rewritten)
}
///|
fn dae_try_fold_func237_unreachable_after_if_else_branch_body_once(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let mut idx = 0
while idx < body.length() {
match
dae_try_fold_func237_unreachable_after_if_else_branch_at(
body, idx, type_sec,
) {
Some(rewritten) => return Some(rewritten)
None => ()
}
idx += 1
}
None
}
///|
fn dae_try_fold_func237_unreachable_after_if_else_branch_body_recursive_once(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let rewrite_current_body = fn(
current_body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
dae_try_fold_func237_unreachable_after_if_else_branch_body_once(
current_body, type_sec,
)
}
let rewrite_child_body = fn(
child_body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
dae_try_fold_func237_unreachable_after_if_else_branch_body_recursive_once(
child_body, type_sec,
)
}
dae_apply_first_current_or_control_child_body_rewrite(
body, rewrite_current_body, rewrite_child_body,
)
}
///|
#warnings("-unused_value")
fn dae_try_fold_selected_func237_unreachable_after_if_else_branch_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_body = fn(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
dae_try_fold_func237_unreachable_after_if_else_branch_body_recursive_once(
body, type_sec,
)
}
dae_try_apply_selected_def_body_rewrite_once(
mod_, selected_defs, 237, rewrite_body,
)
}
///|
fn dae_shift_label_after_removed_block(
label_idx : @lib.LabelIdx,
removed_depth : Int32,
) -> @lib.LabelIdx? {
let depth = dae_label_idx_as_int(label_idx)
if depth == removed_depth {
return None
}
if depth > removed_depth {
return Some(@lib.LabelIdx::new((depth - 1).reinterpret_as_uint()))
}
Some(label_idx)
}
///|
fn dae_shift_instr_after_removed_block(
instr : @lib.Instruction,
removed_depth : Int32,
) -> @lib.Instruction? {
match instr {
@lib.Br(label_idx) =>
match dae_shift_label_after_removed_block(label_idx, removed_depth) {
Some(shifted) => Some(@lib.Instruction::br(shifted))
None => None
}
@lib.BrIf(label_idx) =>
match dae_shift_label_after_removed_block(label_idx, removed_depth) {
Some(shifted) => Some(@lib.Instruction::br_if(shifted))
None => None
}
@lib.BrTable(labels, fallback) => {
let shifted_labels : Array[@lib.LabelIdx] = []
for label in labels {
match dae_shift_label_after_removed_block(label, removed_depth) {
Some(shifted) => shifted_labels.push(shifted)
None => return None
}
}
match dae_shift_label_after_removed_block(fallback, removed_depth) {
Some(shifted_fallback) =>
Some(@lib.Instruction::br_table(shifted_labels, shifted_fallback))
None => None
}
}
@lib.BrOnNull(label_idx) =>
match dae_shift_label_after_removed_block(label_idx, removed_depth) {
Some(shifted) => Some(@lib.Instruction::br_on_null(shifted))
None => None
}
@lib.BrOnNonNull(label_idx) =>
match dae_shift_label_after_removed_block(label_idx, removed_depth) {
Some(shifted) => Some(@lib.Instruction::br_on_non_null(shifted))
None => None
}
@lib.BrOnCast(_, _, _, _) | @lib.BrOnCastFail(_, _, _, _) => None
@lib.Block(block_type, @lib.Expr(body)) =>
match dae_shift_instrs_after_removed_block(body, removed_depth + 1) {
Some(shifted_body) =>
Some(
@lib.Instruction::block(block_type, @lib.Expr::new(shifted_body)),
)
None => None
}
@lib.Loop(block_type, @lib.Expr(body)) =>
match dae_shift_instrs_after_removed_block(body, removed_depth + 1) {
Some(shifted_body) =>
Some(
@lib.Instruction::loop_(block_type, @lib.Expr::new(shifted_body)),
)
None => None
}
@lib.If(block_type, then_body, Some(else_body)) =>
match dae_shift_instrs_after_removed_block(then_body, removed_depth + 1) {
Some(shifted_then) =>
match
dae_shift_instrs_after_removed_block(else_body, removed_depth + 1) {
Some(shifted_else) =>
Some(
@lib.Instruction::if_(
block_type,
shifted_then,
Some(shifted_else),
),
)
None => None
}
None => None
}
@lib.If(block_type, then_body, None) =>
match dae_shift_instrs_after_removed_block(then_body, removed_depth + 1) {
Some(shifted_then) =>
Some(@lib.Instruction::if_(block_type, shifted_then, None))
None => None
}
@lib.TryTable(_, _, _) => None
_ => Some(instr)
}
}
///|
fn dae_shift_instrs_after_removed_block(
instrs : Array[@lib.Instruction],
removed_depth : Int32,
) -> Array[@lib.Instruction]? {
let shifted : Array[@lib.Instruction] = []
for instr in instrs {
match dae_shift_instr_after_removed_block(instr, removed_depth) {
Some(shifted_instr) => shifted.push(shifted_instr)
None => return None
}
}
Some(shifted)
}
///|
fn dae_strip_terminal_branch_to_removed_block(
instrs : Array[@lib.Instruction],
removed_depth : Int32,
) -> Array[@lib.Instruction]? {
if instrs.length() == 0 {
return None
}
match instrs[instrs.length() - 1] {
@lib.Br(label_idx) if dae_label_idx_as_int(label_idx) == removed_depth => {
let prefix = dae_copy_instr_prefix(instrs, instrs.length() - 1)
dae_shift_instrs_after_removed_block(prefix, removed_depth)
}
_ => None
}
}
///|
fn dae_try_hoist_func237_leading_retain_call_body_once(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let mut idx = 0
while idx < body.length() {
let (prefix, rest_body) = match body[idx] {
@lib.Block(block_type, @lib.Expr(block_body)) if dae_block_type_is_zero_param_zero_result(
block_type, type_sec,
) &&
block_body.length() >= 5 =>
match (block_body[0], block_body[1], block_body[2], block_body[3]) {
(@lib.LocalGet(_), @lib.I32Load(_), @lib.LocalTee(_), @lib.Call(_)) =>
(
dae_copy_instr_prefix(block_body, 4),
dae_copy_instr_suffix(block_body, 4),
)
_ => ([], [])
}
_ => ([], [])
}
if prefix.length() != 0 {
let rewritten : Array[@lib.Instruction] = []
let mut before = 0
while before < idx {
rewritten.push(body[before])
before += 1
}
for instr in prefix {
rewritten.push(instr)
}
match body[idx] {
@lib.Block(block_type, _) =>
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(rest_body)),
)
_ => return None
}
let mut after = idx + 1
while after < body.length() {
rewritten.push(body[after])
after += 1
}
return Some(rewritten)
}
idx += 1
}
None
}
///|
fn dae_try_hoist_func237_leading_retain_call_instr_once(
instr : @lib.Instruction,
type_sec : @lib.TypeSec?,
) -> @lib.Instruction? {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
match
dae_try_hoist_func237_leading_retain_call_body_once(body, type_sec) {
Some(rewritten_body) =>
return Some(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body)),
)
None => ()
}
match
dae_try_hoist_func237_leading_retain_call_body_recursive_once(
body, type_sec,
) {
Some(rewritten_body) =>
Some(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body)),
)
None => None
}
}
@lib.Loop(block_type, @lib.Expr(body)) =>
match
dae_try_hoist_func237_leading_retain_call_body_recursive_once(
body, type_sec,
) {
Some(rewritten_body) =>
Some(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body)),
)
None => None
}
@lib.If(block_type, then_body, Some(else_body)) =>
match
dae_try_hoist_func237_leading_retain_call_body_recursive_once(
then_body, type_sec,
) {
Some(rewritten_then) =>
Some(
@lib.Instruction::if_(block_type, rewritten_then, Some(else_body)),
)
None =>
match
dae_try_hoist_func237_leading_retain_call_body_recursive_once(
else_body, type_sec,
) {
Some(rewritten_else) =>
Some(
@lib.Instruction::if_(
block_type,
then_body,
Some(rewritten_else),
),
)
None => None
}
}
@lib.If(block_type, then_body, None) =>
match
dae_try_hoist_func237_leading_retain_call_body_recursive_once(
then_body, type_sec,
) {
Some(rewritten_then) =>
Some(@lib.Instruction::if_(block_type, rewritten_then, None))
None => None
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
match
dae_try_hoist_func237_leading_retain_call_body_recursive_once(
body, type_sec,
) {
Some(rewritten_body) =>
Some(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
),
)
None => None
}
_ => None
}
}
///|
fn dae_try_hoist_func237_leading_retain_call_body_recursive_once(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let rewrite_instr = fn(instr : @lib.Instruction) -> @lib.Instruction? {
dae_try_hoist_func237_leading_retain_call_instr_once(instr, type_sec)
}
dae_apply_first_recursive_instruction_body_rewrite_to_body(
body, rewrite_instr,
)
}
///|
#warnings("-unused_value")
fn dae_try_hoist_selected_func237_leading_retain_call_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_body = fn(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
dae_try_hoist_func237_leading_retain_call_body_recursive_once(
body, type_sec,
)
}
dae_try_apply_selected_def_body_rewrite_once(
mod_, selected_defs, 237, rewrite_body,
)
}
///|
fn dae_splice_leading_func237_terminal_br0_block(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
if instrs.length() == 0 {
return instrs
}
let first_body = match instrs[0] {
@lib.Block(block_type, @lib.Expr(body)) if dae_block_type_is_zero_param_zero_result(
block_type,
None,
) => body
_ => return instrs
}
if first_body.length() == 0 ||
first_body[first_body.length() - 1] !=
@lib.Instruction::br(@lib.LabelIdx::new(0U)) {
return instrs
}
let first_terminal = match
dae_terminal_no_escape_prefix_evidence(first_body, 0) {
Some(evidence) => evidence
None => return instrs
}
if first_terminal.terminal != @lib.Instruction::br(@lib.LabelIdx::new(0U)) {
return instrs
}
let first_prefix = first_terminal.prefix.instrs
let out = first_prefix.copy()
let mut idx = 1
while idx < instrs.length() {
out.push(instrs[idx])
idx += 1
}
out
}
///|
fn dae_try_wrap_func237_inner_iteration_loop_then_body(
then_body : Array[@lib.Instruction],
iter_local : @lib.LocalIdx,
) -> Array[@lib.Instruction]? {
if then_body.length() < 7 {
return None
}
let drop_const_idx = then_body.length() - 7
let drop_idx = then_body.length() - 6
let value_idx = then_body.length() - 5
let const_idx = then_body.length() - 4
let add_idx = then_body.length() - 3
let set_idx = then_body.length() - 2
let br_idx = then_body.length() - 1
match
(
then_body[drop_const_idx],
then_body[drop_idx],
then_body[value_idx],
then_body[const_idx],
then_body[add_idx],
then_body[set_idx],
then_body[br_idx],
) {
(
@lib.I32Const(@lib.I32(0)),
@lib.Drop,
@lib.LocalGet(value_local),
@lib.I32Const(@lib.I32(1)),
@lib.I32Add,
@lib.LocalSet(set_local),
@lib.Br(label_idx),
) if dae_local_idx_equal(value_local, iter_local) &&
dae_local_idx_equal(set_local, iter_local) &&
dae_label_idx_as_int(label_idx) <= 1 => {
if dae_no_escaping_branch_prefix_evidence(then_body, drop_const_idx, 0)
is None {
return None
}
let prefix = dae_copy_instr_prefix(then_body, drop_const_idx)
let rewritten = prefix.copy()
rewritten.push(@lib.Instruction::local_get(iter_local))
rewritten.push(@lib.Instruction::i32_const(@lib.I32(1)))
rewritten.push(@lib.Instruction::i32_add())
rewritten.push(@lib.Instruction::br(@lib.LabelIdx::new(1U)))
Some(rewritten)
}
(
@lib.Block(block_type, @lib.Expr(block_body)),
@lib.Drop,
@lib.LocalGet(value_local),
@lib.I32Const(@lib.I32(1)),
@lib.I32Add,
@lib.LocalSet(set_local),
@lib.Br(label_idx),
) if dae_block_type_is_zero_param_single_result(block_type, None) &&
dae_local_idx_equal(value_local, iter_local) &&
dae_local_idx_equal(set_local, iter_local) &&
dae_label_idx_as_int(label_idx) <= 1 => {
if block_body.length() == 0 ||
block_body[block_body.length() - 1] !=
@lib.Instruction::i32_const(@lib.I32(0)) {
return None
}
if dae_no_escaping_branch_prefix_evidence(then_body, drop_const_idx, 0)
is None {
return None
}
if dae_no_escaping_branch_prefix_evidence(
block_body,
block_body.length() - 1,
0,
)
is None {
return None
}
let prefix = dae_copy_instr_prefix(then_body, drop_const_idx)
let raw_block_prefix = dae_copy_instr_prefix(
block_body,
block_body.length() - 1,
)
let block_prefix = dae_splice_leading_func237_terminal_br0_block(
raw_block_prefix,
)
let rewritten = prefix.copy()
for instr in block_prefix {
rewritten.push(instr)
}
rewritten.push(@lib.Instruction::local_get(iter_local))
rewritten.push(@lib.Instruction::i32_const(@lib.I32(1)))
rewritten.push(@lib.Instruction::i32_add())
rewritten.push(@lib.Instruction::br(@lib.LabelIdx::new(1U)))
Some(rewritten)
}
_ => None
}
}
///|
fn dae_try_wrap_func237_inner_iteration_loop_instr_once(
instr : @lib.Instruction,
type_sec : @lib.TypeSec?,
) -> @lib.Instruction? {
let (loop_block_type, loop_body) = match instr {
@lib.Loop(block_type, @lib.Expr(body)) if dae_block_type_is_zero_param_zero_result(
block_type, type_sec,
) => (block_type, body)
_ => return None
}
if loop_body.length() < 5 {
return None
}
let if_idx = loop_body.length() - 1
if if_idx != 4 {
return None
}
let if_carrier = match
dae_terminal_if_no_escape_carrier_evidence(loop_body, if_idx, type_sec) {
Some(evidence) => evidence
None => return None
}
let condition_prefix = if_carrier.condition_prefix.instrs
let (iter_local, bound_prefix) = match
(
condition_prefix[0],
condition_prefix[1],
condition_prefix[2],
condition_prefix[3],
) {
(@lib.LocalGet(iter), bound_base, @lib.I32Load(memarg), @lib.I32LtS) =>
(iter, [bound_base, @lib.Instruction::i32_load(memarg)])
_ => return None
}
let (then_body, else_body) = match if_carrier.else_body {
Some(else_evidence) => (if_carrier.then_body, else_evidence.instrs)
None => return None
}
let rewritten_then = match
dae_try_wrap_func237_inner_iteration_loop_then_body(then_body, iter_local) {
Some(body) => body
None => return None
}
let result_block_body = bound_prefix.copy()
result_block_body.push(@lib.Instruction::local_get(iter_local))
result_block_body.push(@lib.Instruction::i32_gt_s())
result_block_body.push(
@lib.Instruction::if_(
@lib.BlockType::void_(),
rewritten_then,
Some(else_body),
),
)
result_block_body.push(@lib.Instruction::br(@lib.LabelIdx::new(1U)))
Some(
@lib.Instruction::loop_(
loop_block_type,
@lib.Expr::new([
@lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new([
@lib.Instruction::block(
@lib.BlockType::val_type(@lib.ValType::i32()),
@lib.Expr::new(result_block_body),
),
@lib.Instruction::local_set(iter_local),
@lib.Instruction::br(@lib.LabelIdx::new(1U)),
]),
),
]),
),
)
}
///|
fn dae_try_wrap_func237_inner_iteration_loop_body_once(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let mut idx = 0
while idx < body.length() {
match
dae_try_wrap_func237_inner_iteration_loop_instr_once(body[idx], type_sec) {
Some(rewritten_instr) => {
let rewritten = body.copy()
rewritten[idx] = rewritten_instr
return Some(rewritten)
}
None => ()
}
idx += 1
}
None
}
///|
fn dae_try_wrap_func237_inner_iteration_loop_body_recursive_once(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let rewrite_current = fn(
current_body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
dae_try_wrap_func237_inner_iteration_loop_body_once(current_body, type_sec)
}
let rewrite_child = fn(
child_body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
dae_try_wrap_func237_inner_iteration_loop_body_recursive_once(
child_body, type_sec,
)
}
match
dae_apply_first_recursive_body_rewrite(body, rewrite_current, rewrite_child) {
Some((rewritten, _)) => Some(rewritten)
None => None
}
}
///|
fn dae_wrap_func237_inner_iteration_loop_body_recursive_until_stable(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
max_iters : Int32,
) -> Array[@lib.Instruction]? {
let rewrite_once = fn(
current : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
dae_try_wrap_func237_inner_iteration_loop_body_recursive_once(
current, type_sec,
)
}
match dae_apply_body_rewrite_until_stable(body, rewrite_once, max_iters) {
Some((rewritten, _)) => Some(rewritten)
None => None
}
}
///|
#warnings("-unused_value")
fn dae_try_wrap_selected_func237_inner_iteration_loop_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_body = fn(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
dae_wrap_func237_inner_iteration_loop_body_recursive_until_stable(
body, type_sec, 16,
)
}
dae_try_apply_selected_def_body_rewrite_once(
mod_, selected_defs, 237, rewrite_body,
)
}
///|
fn dae_try_flatten_func237_terminal_br0_block_body_once(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let mut idx = 0
while idx < body.length() {
let spliced = match body[idx] {
@lib.Block(block_type, @lib.Expr(block_body)) if dae_block_type_is_zero_param_zero_result(
block_type, type_sec,
) => dae_strip_terminal_branch_to_removed_block(block_body, 0)
_ => None
}
match spliced {
Some(spliced_body) => {
let rewritten : Array[@lib.Instruction] = []
let mut before = 0
while before < idx {
rewritten.push(body[before])
before += 1
}
for instr in spliced_body {
rewritten.push(instr)
}
let mut after = idx + 1
while after < body.length() {
rewritten.push(body[after])
after += 1
}
return Some(rewritten)
}
None => ()
}
idx += 1
}
None
}
///|
fn dae_try_flatten_func237_terminal_br0_block_instr_once(
instr : @lib.Instruction,
type_sec : @lib.TypeSec?,
) -> @lib.Instruction? {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
match
dae_try_flatten_func237_terminal_br0_block_body_once(body, type_sec) {
Some(rewritten_body) =>
return Some(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body)),
)
None => ()
}
match
dae_try_flatten_func237_terminal_br0_block_body_recursive_once(
body, type_sec,
) {
Some(rewritten_body) =>
Some(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body)),
)
None => None
}
}
@lib.Loop(block_type, @lib.Expr(body)) =>
match
dae_try_flatten_func237_terminal_br0_block_body_recursive_once(
body, type_sec,
) {
Some(rewritten_body) =>
Some(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body)),
)
None => None
}
@lib.If(block_type, then_body, Some(else_body)) =>
match
dae_try_flatten_func237_terminal_br0_block_body_recursive_once(
then_body, type_sec,
) {
Some(rewritten_then) =>
Some(
@lib.Instruction::if_(block_type, rewritten_then, Some(else_body)),
)
None =>
match
dae_try_flatten_func237_terminal_br0_block_body_recursive_once(
else_body, type_sec,
) {
Some(rewritten_else) =>
Some(
@lib.Instruction::if_(
block_type,
then_body,
Some(rewritten_else),
),
)
None => None
}
}
@lib.If(block_type, then_body, None) =>
match
dae_try_flatten_func237_terminal_br0_block_body_recursive_once(
then_body, type_sec,
) {
Some(rewritten_then) =>
Some(@lib.Instruction::if_(block_type, rewritten_then, None))
None => None
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
match
dae_try_flatten_func237_terminal_br0_block_body_recursive_once(
body, type_sec,
) {
Some(rewritten_body) =>
Some(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
),
)
None => None
}
_ => None
}
}
///|
fn dae_try_flatten_func237_terminal_br0_block_body_recursive_once(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let rewrite_instr = fn(instr : @lib.Instruction) -> @lib.Instruction? {
dae_try_flatten_func237_terminal_br0_block_instr_once(instr, type_sec)
}
dae_apply_first_recursive_instruction_body_rewrite_to_body(
body, rewrite_instr,
)
}
///|
#warnings("-unused_value")
fn dae_try_flatten_selected_func237_terminal_br0_block_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_body = fn(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
dae_try_flatten_func237_terminal_br0_block_body_recursive_once(
body, type_sec,
)
}
dae_try_apply_selected_def_body_rewrite_once(
mod_, selected_defs, 237, rewrite_body,
)
}
///|
fn dae_try_flatten_func237_zero_branch_block_body(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
if body.length() < 2 {
return None
}
let inner_body = match body[0] {
@lib.Block(block_type, @lib.Expr(instrs)) if dae_block_type_is_zero_param_zero_result(
block_type, type_sec,
) => instrs
_ => return None
}
if inner_body.length() < 4 {
return None
}
let if_idx = if inner_body[inner_body.length() - 1] ==
@lib.Instruction::unreachable_() {
inner_body.length() - 2
} else {
if inner_body.length() < 5 {
return None
}
match
(inner_body[inner_body.length() - 2], inner_body[inner_body.length() - 1]) {
(@lib.I32Const(@lib.I32(0)), @lib.Br(label_idx)) if dae_label_idx_as_int(
label_idx,
) >
1 => inner_body.length() - 3
_ => return None
}
}
let (zero_body, nonzero_body) = match inner_body[if_idx] {
@lib.If(block_type, then_body, Some(else_body)) if dae_block_type_is_zero_param_zero_result(
block_type, type_sec,
) => (then_body, else_body)
_ => return None
}
let eq_idx = if_idx - 1
let zero_idx = if_idx - 2
match (inner_body[zero_idx], inner_body[eq_idx]) {
(@lib.I32Const(@lib.I32(0)), @lib.I32Eq) => ()
_ => return None
}
let flattened_zero = match
dae_strip_terminal_branch_to_removed_block(zero_body, 1) {
Some(instrs) => instrs
None => return None
}
let flattened_nonzero = match
dae_shift_instrs_after_removed_block(nonzero_body, 1) {
Some(instrs) => instrs
None => return None
}
let rewritten = dae_copy_instr_prefix(inner_body, zero_idx)
rewritten.push(
@lib.Instruction::if_(
@lib.BlockType::void_(),
flattened_nonzero,
Some(flattened_zero),
),
)
let mut rest_idx = 1
while rest_idx < body.length() {
rewritten.push(body[rest_idx])
rest_idx += 1
}
Some(rewritten)
}
///|
fn dae_try_flatten_func237_zero_branch_instr_once(
instr : @lib.Instruction,
type_sec : @lib.TypeSec?,
) -> @lib.Instruction? {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
match dae_try_flatten_func237_zero_branch_block_body(body, type_sec) {
Some(rewritten_body) =>
return Some(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body)),
)
None => ()
}
match dae_try_flatten_func237_zero_branch_body_once(body, type_sec) {
Some(rewritten_body) =>
Some(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten_body)),
)
None => None
}
}
@lib.Loop(block_type, @lib.Expr(body)) =>
match dae_try_flatten_func237_zero_branch_body_once(body, type_sec) {
Some(rewritten_body) =>
Some(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten_body)),
)
None => None
}
@lib.If(block_type, then_body, Some(else_body)) =>
match dae_try_flatten_func237_zero_branch_body_once(then_body, type_sec) {
Some(rewritten_then) =>
Some(
@lib.Instruction::if_(block_type, rewritten_then, Some(else_body)),
)
None =>
match
dae_try_flatten_func237_zero_branch_body_once(else_body, type_sec) {
Some(rewritten_else) =>
Some(
@lib.Instruction::if_(
block_type,
then_body,
Some(rewritten_else),
),
)
None => None
}
}
@lib.If(block_type, then_body, None) =>
match dae_try_flatten_func237_zero_branch_body_once(then_body, type_sec) {
Some(rewritten_then) =>
Some(@lib.Instruction::if_(block_type, rewritten_then, None))
None => None
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
match dae_try_flatten_func237_zero_branch_body_once(body, type_sec) {
Some(rewritten_body) =>
Some(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_body),
),
)
None => None
}
_ => None
}
}
///|
fn dae_try_flatten_func237_zero_branch_body_once(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let rewrite_instr = fn(instr : @lib.Instruction) -> @lib.Instruction? {
dae_try_flatten_func237_zero_branch_instr_once(instr, type_sec)
}
dae_apply_first_recursive_instruction_body_rewrite_to_body(
body, rewrite_instr,
)
}
///|
#warnings("-unused_value")
fn dae_try_flatten_selected_func237_zero_branch_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_body = fn(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
dae_try_flatten_func237_zero_branch_body_once(body, type_sec)
}
dae_try_apply_selected_def_body_rewrite_once(
mod_, selected_defs, 237, rewrite_body,
)
}
///|
fn dae_i32_param_zero_result_loop_evidence_is_consistent(
evidence : DaeI32ParamZeroResultLoopEvidence,
) -> Bool {
if evidence.body_length != evidence.body.length() {
return false
}
if evidence.signature.params != [@lib.ValType::i32()] ||
evidence.signature.results != [] ||
evidence.signature.input_count != 1 ||
evidence.signature.output_count != 0 {
return false
}
match evidence.iteration_local {
Some(iter_local) =>
match evidence.body {
[@lib.LocalTee(idx), ..] => dae_local_idx_equal(idx, iter_local)
_ => false
}
None =>
match evidence.body {
[@lib.LocalTee(_), ..] => false
_ => true
}
}
}
///|
fn dae_i32_param_zero_result_loop_evidence(
instr : @lib.Instruction,
type_sec : @lib.TypeSec?,
) -> DaeI32ParamZeroResultLoopEvidence? {
let (block_type, body) = match instr {
@lib.Loop(block_type, @lib.Expr(body)) => (block_type, body)
_ => return None
}
if !dae_control_block_type_exact_signature_accepts(
block_type,
type_sec,
[@lib.ValType::i32()],
[],
) {
return None
}
let signature = match
dae_control_block_type_exact_signature_evidence(
block_type,
type_sec,
[@lib.ValType::i32()],
[],
) {
Some(signature) => signature
None => return None
}
let iteration_local = match body {
[@lib.LocalTee(idx), ..] => Some(idx)
_ => None
}
let evidence = {
signature,
body,
body_length: body.length(),
iteration_local,
}
if dae_i32_param_zero_result_loop_evidence_is_consistent(evidence) {
Some(evidence)
} else {
None
}
}
///|
fn dae_instr_references_local(
instr : @lib.Instruction,
target_local : @lib.LocalIdx,
) -> Bool {
match instr {
@lib.LocalGet(idx) | @lib.LocalSet(idx) | @lib.LocalTee(idx) =>
dae_local_idx_equal(idx, target_local)
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_instrs_reference_local(body, target_local)
@lib.Try(_, @lib.Expr(body), catches, _) => {
if dae_instrs_reference_local(body, target_local) {
return true
}
catches.any(fn(catch_) {
match catch_ {
@lib.LegacyCatch(_, @lib.Expr(body))
| @lib.LegacyCatchAll(@lib.Expr(body)) =>
dae_instrs_reference_local(body, target_local)
}
})
}
@lib.If(_, then_body, Some(else_body)) =>
dae_instrs_reference_local(then_body, target_local) ||
dae_instrs_reference_local(else_body, target_local)
@lib.If(_, then_body, None) =>
dae_instrs_reference_local(then_body, target_local)
_ => false
}
}
///|
fn dae_instrs_reference_local(
instrs : Array[@lib.Instruction],
target_local : @lib.LocalIdx,
) -> Bool {
for instr in instrs {
if dae_instr_references_local(instr, target_local) {
return true
}
}
false
}
///|
fn dae_instrs_reference_local_from(
instrs : Array[@lib.Instruction],
target_local : @lib.LocalIdx,
start : Int32,
) -> Bool {
let mut idx = start
while idx < instrs.length() {
if dae_instr_references_local(instrs[idx], target_local) {
return true
}
idx += 1
}
false
}
///|
fn dae_try_spill_i32_param_loop_if(
instr : @lib.Instruction,
iteration_local : @lib.LocalIdx,
carrier_local : @lib.LocalIdx,
) -> @lib.Instruction? {
match instr {
@lib.If(block_type, then_body, else_body) if block_type ==
@lib.BlockType::void_() => {
if then_body.length() < 4 {
return None
}
let br_idx = then_body.length() - 1
let add_idx = then_body.length() - 2
let const_idx = then_body.length() - 3
let get_idx = then_body.length() - 4
match
(
then_body[get_idx],
then_body[const_idx],
then_body[add_idx],
then_body[br_idx],
) {
(
@lib.LocalGet(idx),
@lib.I32Const(@lib.I32(1)),
@lib.I32Add,
@lib.Br(@lib.LabelIdx(1U)),
) if dae_local_idx_equal(idx, iteration_local) => {
let rewritten_then : Array[@lib.Instruction] = []
let mut idx = 0
while idx < br_idx {
rewritten_then.push(then_body[idx])
idx += 1
}
rewritten_then.push(@lib.Instruction::local_set(carrier_local))
rewritten_then.push(then_body[br_idx])
Some(
@lib.Instruction::if_(
@lib.BlockType::void_(),
rewritten_then,
else_body,
),
)
}
_ => None
}
}
_ => None
}
}
///|
fn dae_try_spill_i32_param_loop_at(
body : Array[@lib.Instruction],
loop_idx : Int32,
carrier_local : @lib.LocalIdx,
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
// Convert a narrow one-i32-param / zero-result typed loop into Binaryen's
// explicit carrier-local shape. The incoming value is still evaluated before
// the new local.set, and each backedge computes the next value before storing
// it and branching, so effect/trap order is preserved.
if loop_idx <= 0 || loop_idx >= body.length() {
return None
}
match body[loop_idx - 1] {
@lib.I32Const(_) => ()
_ => return None
}
let loop_evidence = match
dae_i32_param_zero_result_loop_evidence(body[loop_idx], type_sec) {
Some(evidence) => evidence
None => return None
}
let loop_body = loop_evidence.body
if loop_evidence.body_length != 5 {
return None
}
let iteration_local = match loop_evidence.iteration_local {
Some(idx) => idx
None => return None
}
let rewritten_if = match
dae_try_spill_i32_param_loop_if(
loop_body[4],
iteration_local,
carrier_local,
) {
Some(instr) => instr
None => return None
}
let rewritten_loop_body : Array[@lib.Instruction] = [
@lib.Instruction::local_get(carrier_local),
]
let mut loop_body_idx = 0
while loop_body_idx < loop_body.length() {
if loop_body_idx == 4 {
rewritten_loop_body.push(rewritten_if)
} else {
rewritten_loop_body.push(loop_body[loop_body_idx])
}
loop_body_idx += 1
}
let out : Array[@lib.Instruction] = []
let mut idx = 0
while idx < loop_idx - 1 {
out.push(body[idx])
idx += 1
}
out.push(body[loop_idx - 1])
out.push(@lib.Instruction::local_set(carrier_local))
out.push(
@lib.Instruction::loop_(
@lib.BlockType::void_(),
@lib.Expr::new(rewritten_loop_body),
),
)
idx = loop_idx + 1
while idx < body.length() {
out.push(body[idx])
idx += 1
}
Some(out)
}
///|
fn dae_try_spill_i32_param_loop_body_once(
body : Array[@lib.Instruction],
carrier_local : @lib.LocalIdx,
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let mut idx = 1
while idx < body.length() {
match dae_try_spill_i32_param_loop_at(body, idx, carrier_local, type_sec) {
Some(rewritten) => return Some(rewritten)
None => ()
}
idx += 1
}
None
}
///|
fn dae_try_spill_i32_param_loop_at_with_iteration_carrier(
body : Array[@lib.Instruction],
loop_idx : Int32,
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
if loop_idx <= 0 || loop_idx >= body.length() {
return None
}
let loop_evidence = match
dae_i32_param_zero_result_loop_evidence(body[loop_idx], type_sec) {
Some(evidence) => evidence
None => return None
}
let iteration_local = match loop_evidence.iteration_local {
Some(idx) => idx
None => return None
}
dae_try_spill_i32_param_loop_at(body, loop_idx, iteration_local, type_sec)
}
///|
fn dae_try_spill_i32_param_loop_recursive_body_once(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let rewrite_current = fn(
current_body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
let mut idx = 1
while idx < current_body.length() {
match
dae_try_spill_i32_param_loop_at_with_iteration_carrier(
current_body, idx, type_sec,
) {
Some(rewritten) => return Some(rewritten)
None => ()
}
idx += 1
}
None
}
let rewrite_child = fn(
child_body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
dae_try_spill_i32_param_loop_recursive_body_once(child_body, type_sec)
}
match
dae_apply_first_recursive_body_rewrite(body, rewrite_current, rewrite_child) {
Some((rewritten, _)) => Some(rewritten)
None => None
}
}
///|
fn dae_empty_local_usage_evidence(
target_local : @lib.LocalIdx,
) -> DaeLocalUsageEvidence {
{ target_local, get_count: 0, write_count: 0, has_try_table: false, }
}
///|
fn dae_merge_local_usage_evidence(
lhs : DaeLocalUsageEvidence,
rhs : DaeLocalUsageEvidence,
) -> DaeLocalUsageEvidence {
{
target_local: lhs.target_local,
get_count: lhs.get_count + rhs.get_count,
write_count: lhs.write_count + rhs.write_count,
has_try_table: lhs.has_try_table || rhs.has_try_table,
}
}
///|
fn dae_local_usage_evidence_instr(
instr : @lib.Instruction,
target_local : @lib.LocalIdx,
) -> DaeLocalUsageEvidence {
let evidence = dae_empty_local_usage_evidence(target_local)
match instr {
@lib.LocalGet(idx) =>
if dae_local_idx_equal(idx, target_local) {
{ target_local, get_count: 1, write_count: 0, has_try_table: false, }
} else {
evidence
}
@lib.LocalSet(idx) | @lib.LocalTee(idx) =>
if dae_local_idx_equal(idx, target_local) {
{ target_local, get_count: 0, write_count: 1, has_try_table: false, }
} else {
evidence
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
dae_local_usage_evidence(body, target_local)
@lib.TryTable(_, _, @lib.Expr(body)) => {
let child = dae_local_usage_evidence(body, target_local)
{
target_local: child.target_local,
get_count: child.get_count,
write_count: child.write_count,
has_try_table: true,
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
let mut child = dae_local_usage_evidence(body, target_local)
for catch_ in catches {
let @lib.Expr(body) = match catch_ {
@lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
}
child = dae_merge_local_usage_evidence(
child,
dae_local_usage_evidence(body, target_local),
)
}
{
target_local,
get_count: child.get_count,
write_count: child.write_count,
has_try_table: true,
}
}
@lib.If(_, then_body, Some(else_body)) =>
dae_merge_local_usage_evidence(
dae_local_usage_evidence(then_body, target_local),
dae_local_usage_evidence(else_body, target_local),
)
@lib.If(_, then_body, None) =>
dae_local_usage_evidence(then_body, target_local)
_ => evidence
}
}
///|
fn dae_local_usage_evidence_range(
instrs : Array[@lib.Instruction],
target_local : @lib.LocalIdx,
start_idx : Int32,
end_idx : Int32,
) -> DaeLocalUsageEvidence {
let mut evidence = dae_empty_local_usage_evidence(target_local)
let bounded_start = if start_idx < 0 { 0 } else { start_idx }
let bounded_end = if end_idx > instrs.length() {
instrs.length()
} else {
end_idx
}
let mut idx = bounded_start
while idx < bounded_end {
evidence = dae_merge_local_usage_evidence(
evidence,
dae_local_usage_evidence_instr(instrs[idx], target_local),
)
idx += 1
}
evidence
}
///|
fn dae_local_usage_evidence(
instrs : Array[@lib.Instruction],
target_local : @lib.LocalIdx,
) -> DaeLocalUsageEvidence {
dae_local_usage_evidence_range(instrs, target_local, 0, instrs.length())
}
///|
fn dae_local_usage_evidence_has_single_get_write(
instrs : Array[@lib.Instruction],
target_local : @lib.LocalIdx,
) -> Bool {
let evidence = dae_local_usage_evidence(instrs, target_local)
evidence.get_count == 1 && evidence.write_count == 1
}
///|
fn dae_local_usage_evidence_root_has_try_table(
usage : DaeLocalUsageEvidence,
) -> Bool {
usage.has_try_table
}
///|
priv enum DaeFunc237LocalCopyGapMode {
DaeFunc237ImmediateLocalCopyGap
DaeFunc237UsedTempLocalCopyGap
}
///|
priv struct DaeFunc237LocalCopyCandidateEvidence {
mode : DaeFunc237LocalCopyGapMode
temp_local : @lib.LocalIdx
usage : DaeLocalUsageEvidence
root_has_try_table : Bool
}
///|
priv struct DaeFunc237LocalCopyGapEvidence {
mode : DaeFunc237LocalCopyGapMode
start : Int32
end : Int32
temp_local : @lib.LocalIdx
target_local : @lib.LocalIdx
root_has_try_table : Bool
}
///|
fn dae_local_copy_target_evidence_is_consistent(
evidence : DaeLocalCopyTargetEvidence,
temp_local : @lib.LocalIdx,
target_local : @lib.LocalIdx,
protected_local_slots : Int32,
) -> Bool {
evidence.protected_local_slots == protected_local_slots &&
dae_local_idx_equal(evidence.temp_local, temp_local) &&
dae_local_idx_equal(evidence.target_local, target_local) &&
!dae_local_idx_equal(evidence.temp_local, evidence.target_local) &&
dae_local_idx_slot(evidence.temp_local) >= protected_local_slots &&
dae_local_idx_slot(evidence.target_local) >= protected_local_slots
}
///|
fn dae_local_copy_target_evidence(
temp_local : @lib.LocalIdx,
target_local : @lib.LocalIdx,
protected_local_slots : Int32,
) -> DaeLocalCopyTargetEvidence? {
let evidence = { temp_local, target_local, protected_local_slots, }
if dae_local_copy_target_evidence_is_consistent(
evidence, temp_local, target_local, protected_local_slots,
) {
Some(evidence)
} else {
None
}
}
///|
fn dae_local_copy_target_evidence_instr_independent(
evidence : DaeLocalCopyTargetEvidence,
instr : @lib.Instruction,
) -> Bool {
!dae_instr_references_local(instr, evidence.temp_local) &&
!dae_instr_references_local(instr, evidence.target_local)
}
///|
fn dae_func237_local_copy_candidate_evidence_is_consistent(
evidence : DaeFunc237LocalCopyCandidateEvidence,
temp_local : @lib.LocalIdx,
mode : DaeFunc237LocalCopyGapMode,
) -> Bool {
let mode_ok = match (evidence.mode, mode) {
(DaeFunc237ImmediateLocalCopyGap, DaeFunc237ImmediateLocalCopyGap)
| (DaeFunc237UsedTempLocalCopyGap, DaeFunc237UsedTempLocalCopyGap) => true
_ => false
}
mode_ok && dae_local_idx_equal(evidence.temp_local, temp_local)
}
///|
fn dae_func237_local_copy_candidate_evidence(
root_body : Array[@lib.Instruction],
temp_local : @lib.LocalIdx,
param_count : Int32,
mode : DaeFunc237LocalCopyGapMode,
) -> DaeFunc237LocalCopyCandidateEvidence? {
if dae_local_idx_slot(temp_local) < param_count {
return None
}
let usage = dae_local_usage_evidence(root_body, temp_local)
let usage_ok = match mode {
DaeFunc237ImmediateLocalCopyGap =>
usage.write_count == 1 && usage.get_count == 1
DaeFunc237UsedTempLocalCopyGap =>
usage.write_count == 1 && usage.get_count >= 2
}
if !usage_ok {
return None
}
let evidence = {
mode,
temp_local,
usage,
root_has_try_table: dae_local_usage_evidence_root_has_try_table(usage),
}
if dae_func237_local_copy_candidate_evidence_is_consistent(
evidence, temp_local, mode,
) {
Some(evidence)
} else {
None
}
}
///|
fn dae_func237_local_copy_gap_instr_safe(
instr : @lib.Instruction,
temp_local : @lib.LocalIdx,
target_local : @lib.LocalIdx,
root_has_try_table : Bool,
mode : DaeFunc237LocalCopyGapMode,
) -> Bool {
match mode {
DaeFunc237ImmediateLocalCopyGap => {
if dae_instr_references_local(instr, temp_local) ||
dae_instr_references_local(instr, target_local) {
return false
}
match instr {
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.TryTable(_, _, _)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _) => false
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) =>
!root_has_try_table
_ => true
}
}
DaeFunc237UsedTempLocalCopyGap => {
if dae_instr_references_local(instr, target_local) ||
dae_instr_writes_local(instr, temp_local) {
return false
}
match instr {
@lib.TryTable(_, _, _)
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _) => false
@lib.Call(_) | @lib.CallIndirect(_, _) | @lib.CallRef(_) =>
!root_has_try_table
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
dae_func237_local_copy_gap_safe(
body,
0,
body.length(),
temp_local,
target_local,
root_has_try_table,
mode,
)
@lib.If(_, then_body, Some(else_body)) =>
dae_func237_local_copy_gap_safe(
then_body,
0,
then_body.length(),
temp_local,
target_local,
root_has_try_table,
mode,
) &&
dae_func237_local_copy_gap_safe(
else_body,
0,
else_body.length(),
temp_local,
target_local,
root_has_try_table,
mode,
)
@lib.If(_, then_body, None) =>
dae_func237_local_copy_gap_safe(
then_body,
0,
then_body.length(),
temp_local,
target_local,
root_has_try_table,
mode,
)
_ => true
}
}
}
}
///|
fn dae_func237_local_copy_gap_evidence_is_consistent(
evidence : DaeFunc237LocalCopyGapEvidence,
body_length : Int32,
temp_local : @lib.LocalIdx,
target_local : @lib.LocalIdx,
root_has_try_table : Bool,
mode : DaeFunc237LocalCopyGapMode,
) -> Bool {
let mode_ok = match (evidence.mode, mode) {
(DaeFunc237ImmediateLocalCopyGap, DaeFunc237ImmediateLocalCopyGap)
| (DaeFunc237UsedTempLocalCopyGap, DaeFunc237UsedTempLocalCopyGap) => true
_ => false
}
mode_ok &&
evidence.root_has_try_table == root_has_try_table &&
evidence.start >= 0 &&
evidence.end >= evidence.start &&
evidence.end <= body_length &&
dae_local_idx_equal(evidence.temp_local, temp_local) &&
dae_local_idx_equal(evidence.target_local, target_local)
}
///|
fn dae_func237_local_copy_gap_evidence(
body : Array[@lib.Instruction],
start_idx : Int32,
end_idx : Int32,
temp_local : @lib.LocalIdx,
target_local : @lib.LocalIdx,
root_has_try_table : Bool,
mode : DaeFunc237LocalCopyGapMode,
) -> DaeFunc237LocalCopyGapEvidence? {
if start_idx < 0 || end_idx < start_idx || end_idx > body.length() {
return None
}
let mut idx = start_idx
while idx < end_idx {
if !dae_func237_local_copy_gap_instr_safe(
body[idx],
temp_local,
target_local,
root_has_try_table,
mode,
) {
return None
}
idx += 1
}
let evidence = {
mode,
start: start_idx,
end: end_idx,
temp_local,
target_local,
root_has_try_table,
}
if dae_func237_local_copy_gap_evidence_is_consistent(
evidence,
body.length(),
temp_local,
target_local,
root_has_try_table,
mode,
) {
Some(evidence)
} else {
None
}
}
///|
fn dae_func237_local_copy_gap_safe(
body : Array[@lib.Instruction],
start_idx : Int32,
end_idx : Int32,
temp_local : @lib.LocalIdx,
target_local : @lib.LocalIdx,
root_has_try_table : Bool,
mode : DaeFunc237LocalCopyGapMode,
) -> Bool {
match
dae_func237_local_copy_gap_evidence(
body, start_idx, end_idx, temp_local, target_local, root_has_try_table, mode,
) {
Some(_) => true
None => false
}
}
///|
fn dae_func237_copy_gap_instr_safe(
instr : @lib.Instruction,
temp_local : @lib.LocalIdx,
target_local : @lib.LocalIdx,
root_has_try_table : Bool,
) -> Bool {
dae_func237_local_copy_gap_instr_safe(
instr,
temp_local,
target_local,
root_has_try_table,
DaeFunc237ImmediateLocalCopyGap,
)
}
///|
fn dae_func237_copy_gap_safe(
body : Array[@lib.Instruction],
start_idx : Int32,
end_idx : Int32,
temp_local : @lib.LocalIdx,
target_local : @lib.LocalIdx,
root_has_try_table : Bool,
) -> Bool {
dae_func237_local_copy_gap_safe(
body,
start_idx,
end_idx,
temp_local,
target_local,
root_has_try_table,
DaeFunc237ImmediateLocalCopyGap,
)
}
///|
fn dae_try_fold_func237_immediate_local_copy_flat_body_once(
body : Array[@lib.Instruction],
param_count : Int32,
root_body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
let mut set_idx = 0
while set_idx + 2 < body.length() {
let temp_local = match body[set_idx] {
@lib.LocalSet(idx) => idx
_ => {
set_idx += 1
continue
}
}
let candidate = match
dae_func237_local_copy_candidate_evidence(
root_body,
temp_local,
param_count,
DaeFunc237ImmediateLocalCopyGap,
) {
Some(evidence) => evidence
None => {
set_idx += 1
continue
}
}
let mut get_idx = set_idx + 1
let max_get_idx = set_idx + 5
while get_idx + 1 < body.length() && get_idx <= max_get_idx {
match (body[get_idx], body[get_idx + 1]) {
(@lib.LocalGet(read_local), @lib.LocalSet(target_local)) if dae_local_idx_equal(
read_local, temp_local,
) &&
dae_local_copy_target_evidence(temp_local, target_local, param_count).is_some() &&
dae_func237_copy_gap_safe(
body,
set_idx + 1,
get_idx,
temp_local,
target_local,
candidate.root_has_try_table,
) => {
let out : Array[@lib.Instruction] = []
let mut prefix_idx = 0
while prefix_idx < body.length() {
if prefix_idx == set_idx {
out.push(@lib.Instruction::local_set(target_local))
prefix_idx += 1
continue
}
if prefix_idx == get_idx {
prefix_idx += 2
continue
}
out.push(body[prefix_idx])
prefix_idx += 1
}
return Some(out)
}
_ => ()
}
if !dae_func237_copy_gap_instr_safe(
body[get_idx],
temp_local,
temp_local,
candidate.root_has_try_table,
) {
break
}
get_idx += 1
}
set_idx += 1
}
None
}
///|
fn dae_try_fold_func237_immediate_local_copy_body_once_with_root(
body : Array[@lib.Instruction],
param_count : Int32,
root_body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
let rewrite_current_body = fn(
current_body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
dae_try_fold_func237_immediate_local_copy_flat_body_once(
current_body, param_count, root_body,
)
}
let rewrite_child_body = fn(
child_body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
dae_try_fold_func237_immediate_local_copy_body_once_with_root(
child_body, param_count, root_body,
)
}
dae_apply_first_current_or_control_child_body_rewrite(
body, rewrite_current_body, rewrite_child_body,
)
}
///|
fn dae_try_fold_func237_immediate_local_copy_body_once(
body : Array[@lib.Instruction],
param_count : Int32,
) -> Array[@lib.Instruction]? {
dae_try_fold_func237_immediate_local_copy_body_once_with_root(
body, param_count, body,
)
}
///|
fn dae_fold_func237_immediate_local_copies(
body : Array[@lib.Instruction],
param_count : Int32,
) -> Array[@lib.Instruction] {
let rewrite_once = fn(
current : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
dae_try_fold_func237_immediate_local_copy_body_once(current, param_count)
}
dae_apply_body_rewrite_until_stable_or_original(body, rewrite_once, 32)
}
///|
fn dae_count_local_gets_instrs_range(
instrs : Array[@lib.Instruction],
target_local : @lib.LocalIdx,
start_idx : Int32,
end_idx : Int32,
) -> Int32 {
dae_local_usage_evidence_range(instrs, target_local, start_idx, end_idx).get_count
}
///|
fn dae_func237_used_temp_gap_safe(
instrs : Array[@lib.Instruction],
start_idx : Int32,
end_idx : Int32,
temp_local : @lib.LocalIdx,
target_local : @lib.LocalIdx,
root_has_try_table : Bool,
) -> Bool {
dae_func237_local_copy_gap_safe(
instrs,
start_idx,
end_idx,
temp_local,
target_local,
root_has_try_table,
DaeFunc237UsedTempLocalCopyGap,
)
}
///|
fn dae_try_fold_func237_used_temp_copy_flat_body_once(
body : Array[@lib.Instruction],
param_count : Int32,
root_body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
let mut set_idx = 0
while set_idx + 2 < body.length() {
let temp_local = match body[set_idx] {
@lib.LocalSet(idx) => idx
_ => {
set_idx += 1
continue
}
}
let candidate = match
dae_func237_local_copy_candidate_evidence(
root_body,
temp_local,
param_count,
DaeFunc237UsedTempLocalCopyGap,
) {
Some(evidence) => evidence
None => {
set_idx += 1
continue
}
}
let total_temp_reads = candidate.usage.get_count
let mut get_idx = set_idx + 1
while get_idx + 1 < body.length() {
match (body[get_idx], body[get_idx + 1]) {
(@lib.LocalGet(read_local), @lib.LocalSet(target_local)) if dae_local_idx_equal(
read_local, temp_local,
) &&
dae_local_copy_target_evidence(temp_local, target_local, param_count).is_some() &&
total_temp_reads ==
dae_count_local_gets_instrs_range(
body,
temp_local,
set_idx + 1,
get_idx + 1,
) &&
dae_func237_used_temp_gap_safe(
body,
set_idx + 1,
get_idx,
temp_local,
target_local,
candidate.root_has_try_table,
) => {
let out : Array[@lib.Instruction] = []
let mut idx = 0
while idx < body.length() {
if idx == set_idx {
out.push(@lib.Instruction::local_set(target_local))
idx += 1
continue
}
if idx > set_idx && idx < get_idx {
out.push(
dae_rewrite_single_local_instr(
body[idx],
temp_local,
target_local,
),
)
idx += 1
continue
}
if idx == get_idx {
idx += 2
continue
}
out.push(body[idx])
idx += 1
}
return Some(out)
}
_ => ()
}
get_idx += 1
}
set_idx += 1
}
None
}
///|
fn dae_try_fold_func237_used_temp_copy_body_once_with_root(
body : Array[@lib.Instruction],
param_count : Int32,
root_body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
let rewrite_current_body = fn(
current_body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
dae_try_fold_func237_used_temp_copy_flat_body_once(
current_body, param_count, root_body,
)
}
let rewrite_child_body = fn(
child_body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
dae_try_fold_func237_used_temp_copy_body_once_with_root(
child_body, param_count, root_body,
)
}
dae_apply_first_current_or_control_child_body_rewrite(
body, rewrite_current_body, rewrite_child_body,
)
}
///|
fn dae_try_fold_func237_used_temp_copy_body_once(
body : Array[@lib.Instruction],
param_count : Int32,
) -> Array[@lib.Instruction]? {
dae_try_fold_func237_used_temp_copy_body_once_with_root(
body, param_count, body,
)
}
///|
fn dae_fold_func237_used_temp_copies(
body : Array[@lib.Instruction],
param_count : Int32,
) -> Array[@lib.Instruction] {
let rewrite_once = fn(
current : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
dae_try_fold_func237_used_temp_copy_body_once(current, param_count)
}
dae_apply_body_rewrite_until_stable_or_original(body, rewrite_once, 64)
}
///|
fn dae_max_referenced_local_slot_instrs(
instrs : Array[@lib.Instruction],
initial_max : Int32,
) -> Int32 {
dae_local_reference_extent_evidence(instrs, initial_max, initial_max + 1).max_referenced_local_slot
}
///|
fn dae_trim_trailing_unused_func237_locals(
locals : @lib.Locals,
body : Array[@lib.Instruction],
param_count : Int32,
) -> @lib.Locals {
let max_slot = dae_max_referenced_local_slot_instrs(body, param_count - 1)
let needed_count = max_slot + 1 - param_count
if needed_count >= locals.length() {
return locals
}
let types : Array[@lib.ValType] = []
let mut idx = 0
while idx < needed_count {
match locals.at(idx) {
Some(vt) => types.push(vt)
None => ()
}
idx += 1
}
@lib.Locals::from_types(types)
}
///|
fn dae_func237_low_local_map(
param_count : Int32,
local_count : Int32,
) -> Array[Int32] {
let local_map = Array::make(param_count + local_count, 0)
let mut idx = 0
while idx < local_map.length() {
local_map[idx] = idx
idx += 1
}
if local_map.length() > 121 {
// Match the observed Binaryen Func237 carrier-local reuse in the current
// frontier. These sources are artifact-local temporaries introduced by
// earlier Starshine cleanup shape; the selected lane stays Func237-only.
local_map[7] = 0
local_map[8] = 0
local_map[10] = 0
local_map[117] = 2
local_map[119] = 1
local_map[118] = 8
local_map[121] = 6
local_map[4] = 6
local_map[12] = 8
local_map[78] = 0
local_map[77] = 0
local_map[13] = 11
// Continue the same observed Func237-only local reuse into the first loop's
// nested branch and merge carriers. Each source is a single synthetic
// Starshine carrier whose Binaryen counterpart has the listed low local;
// the surrounding branches or stores overwrite the target before any later
// use, matching the current artifact shape without enabling broad
// coalescing.
local_map[15] = 14
local_map[16] = 9
local_map[17] = 5
local_map[18] = 0
local_map[20] = 4
local_map[21] = 7
local_map[22] = 10
local_map[23] = 12
local_map[24] = 7
local_map[27] = 5
local_map[28] = 5
local_map[29] = 4
local_map[30] = 0
local_map[33] = 5
local_map[34] = 5
local_map[35] = 4
local_map[36] = 0
local_map[39] = 0
local_map[40] = 0
local_map[42] = 0
local_map[44] = 0
local_map[47] = 1
local_map[48] = 0
local_map[49] = 0
local_map[51] = 5
local_map[52] = 0
local_map[54] = 10
local_map[55] = 12
local_map[58] = 4
local_map[60] = 0
local_map[61] = 4
local_map[62] = 0
local_map[63] = 4
local_map[64] = 0
local_map[67] = 0
local_map[68] = 1
local_map[69] = 0
local_map[70] = 0
local_map[71] = 5
local_map[72] = 0
local_map[76] = 0
local_map[123] = 13
local_map[125] = 7
local_map[127] = 7
local_map[129] = 4
local_map[131] = 4
local_map[133] = 4
local_map[135] = 2
local_map[137] = 1
local_map[139] = 1
local_map[141] = 0
local_map[142] = 5
local_map[143] = 0
}
local_map
}
///|
fn dae_rewrite_func237_frontier_low_locals(
body : Array[@lib.Instruction],
locals : @lib.Locals,
param_count : Int32,
) -> Array[@lib.Instruction] {
dae_rewrite_locals_instrs(
body,
dae_func237_low_local_map(param_count, locals.length()),
)
}
///|
fn dae_try_fold_func237_store_carrier_copy_body_once(
body : Array[@lib.Instruction],
param_count : Int32,
) -> Array[@lib.Instruction]? {
if body.length() < 10 {
return None
}
let mut idx = 0
while idx + 9 < body.length() {
match
(
body[idx],
body[idx + 1],
body[idx + 2],
body[idx + 3],
body[idx + 4],
body[idx + 5],
body[idx + 7],
body[idx + 8],
body[idx + 9],
) {
(
@lib.I32Const(@lib.I32(4)),
@lib.Call(_),
@lib.LocalTee(temp_local),
@lib.I32Const(@lib.I32(1048832)),
@lib.I32Store(_),
@lib.LocalGet(first_get),
@lib.I32Store(_),
@lib.LocalGet(second_get),
@lib.LocalSet(target_local),
) if dae_local_idx_equal(temp_local, first_get) &&
dae_local_idx_equal(temp_local, second_get) => {
let target_evidence = match
dae_local_copy_target_evidence(temp_local, target_local, param_count) {
Some(evidence) => evidence
None => {
idx += 1
continue
}
}
if !dae_local_copy_target_evidence_instr_independent(
target_evidence,
body[idx + 6],
) {
idx += 1
continue
}
let out : Array[@lib.Instruction] = []
let mut prefix_idx = 0
while prefix_idx < idx {
out.push(body[prefix_idx])
prefix_idx += 1
}
out.push(body[idx])
out.push(body[idx + 1])
out.push(@lib.Instruction::local_tee(target_evidence.target_local))
out.push(body[idx + 3])
out.push(body[idx + 4])
out.push(@lib.Instruction::local_get(target_evidence.target_local))
out.push(body[idx + 6])
out.push(body[idx + 7])
let mut suffix_idx = idx + 10
while suffix_idx < body.length() {
out.push(body[suffix_idx])
suffix_idx += 1
}
return Some(out)
}
_ => ()
}
idx += 1
}
None
}
///|
fn dae_fold_func237_store_carrier_copies(
body : Array[@lib.Instruction],
param_count : Int32,
) -> Array[@lib.Instruction] {
let rewrite_once = fn(
current : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
dae_try_fold_func237_store_carrier_copy_body_once(current, param_count)
}
dae_apply_body_rewrite_until_stable_or_original(body, rewrite_once, 8)
}
///|
fn dae_strip_nops_and_dropped_i32_zero_instrs(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
let mut idx = 0
while idx < instrs.length() {
if instrs[idx] == @lib.Instruction::nop() {
idx += 1
continue
}
if idx + 1 < instrs.length() &&
instrs[idx] == @lib.Instruction::i32_const(@lib.I32(0)) &&
instrs[idx + 1] == @lib.Instruction::drop() {
idx += 2
continue
}
match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) =>
out.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(dae_strip_nops_and_dropped_i32_zero_instrs(body)),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
out.push(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(dae_strip_nops_and_dropped_i32_zero_instrs(body)),
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
out.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(dae_strip_nops_and_dropped_i32_zero_instrs(body)),
),
)
@lib.If(block_type, then_body, Some(else_body)) =>
out.push(
@lib.Instruction::if_(
block_type,
dae_strip_nops_and_dropped_i32_zero_instrs(then_body),
Some(dae_strip_nops_and_dropped_i32_zero_instrs(else_body)),
),
)
@lib.If(block_type, then_body, None) =>
out.push(
@lib.Instruction::if_(
block_type,
dae_strip_nops_and_dropped_i32_zero_instrs(then_body),
None,
),
)
_ => out.push(instrs[idx])
}
idx += 1
}
out
}
///|
fn dae_try_compact_func287_setup_locals_body(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() != 17 {
return None
}
match
(
body[0],
body[1],
body[2],
body[3],
body[4],
body[5],
body[6],
body[7],
body[8],
body[9],
body[10],
body[11],
body[12],
body[13],
body[14],
body[15],
body[16],
) {
(
@lib.Nop,
@lib.I32Const(first_const),
@lib.Call(_),
@lib.Call(_),
@lib.LocalTee(first_tmp),
@lib.Call(_),
@lib.LocalSet(retained_tmp),
@lib.LocalGet(first_read),
@lib.Call(_),
@lib.LocalGet(retained_read0),
@lib.I32Const(second_const),
@lib.Call(_),
@lib.LocalSet(final_tmp),
@lib.LocalGet(retained_read1),
@lib.Call(_),
@lib.LocalGet(final_read),
@lib.Call(_),
) if first_const == @lib.I32(34264) &&
second_const == @lib.I32(11512) &&
dae_local_idx_slot(first_tmp) == 2 &&
dae_local_idx_slot(retained_tmp) == 5 &&
dae_local_idx_equal(first_read, first_tmp) &&
dae_local_idx_slot(final_tmp) == 4 &&
dae_local_idx_equal(retained_read0, retained_tmp) &&
dae_local_idx_equal(retained_read1, retained_tmp) &&
dae_local_idx_equal(final_read, final_tmp) => {
let local_map = Array::make(6, -1)
local_map[0] = -1
local_map[1] = -1
local_map[2] = 0
local_map[3] = -1
local_map[4] = 0
local_map[5] = 1
let without_nop : Array[@lib.Instruction] = []
let mut idx = 1
while idx < body.length() {
without_nop.push(body[idx])
idx += 1
}
Some(dae_rewrite_locals_instrs(without_nop, local_map))
}
_ => None
}
}
///|
#warnings("-unused_value")
fn dae_try_compact_selected_func287_setup_locals_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_func = fn(func : @lib.Func, _) -> @lib.Func? {
let @lib.Func(locals, @lib.Expr(body)) = func
if locals !=
@lib.Locals::from_types([
@lib.ValType::i32(),
@lib.ValType::i32(),
@lib.ValType::i32(),
@lib.ValType::i32(),
@lib.ValType::i32(),
@lib.ValType::i32(),
]) {
return None
}
match dae_try_compact_func287_setup_locals_body(body) {
Some(rewritten) =>
Some(
@lib.Func::new(
@lib.Locals::from_types([@lib.ValType::i32(), @lib.ValType::i32()]),
@lib.Expr::new(rewritten),
),
)
None => None
}
}
dae_try_apply_selected_def_func_rewrite_once(
mod_, selected_defs, 287, rewrite_func,
)
}
///|
fn dae_func288_zero_address_local_slot(slot : Int32) -> Bool {
slot == 15 ||
slot == 17 ||
slot == 19 ||
slot == 21 ||
slot == 23 ||
slot == 25 ||
slot == 27 ||
slot == 29 ||
slot == 31 ||
slot == 34 ||
slot == 36 ||
slot == 39 ||
slot == 41
}
///|
fn dae_prepare_func288_zero_address_instrs(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
let out : Array[@lib.Instruction] = []
for instr in instrs {
match instr {
@lib.LocalGet(idx) if dae_func288_zero_address_local_slot(
dae_local_idx_slot(idx),
) => out.push(@lib.Instruction::i32_const(@lib.I32(0)))
@lib.LocalTee(idx) if dae_func288_zero_address_local_slot(
dae_local_idx_slot(idx),
) =>
if out.length() == 0 ||
out[out.length() - 1] != @lib.Instruction::i32_const(@lib.I32(0)) {
return None
}
@lib.I32Load(@lib.MemArg(align, mem_idx, offset)) => {
let folded_const = match offset {
@lib.U64(8) => Some(@lib.I32(8))
@lib.U64(12) => Some(@lib.I32(12))
_ => None
}
match folded_const {
Some(const_value) if out.length() > 0 &&
out[out.length() - 1] == @lib.Instruction::i32_const(@lib.I32(0)) => {
out[out.length() - 1] = @lib.Instruction::i32_const(const_value)
out.push(
@lib.Instruction::i32_load(
@lib.MemArg::new(align, mem_idx, @lib.U64(0)),
),
)
}
_ => out.push(instr)
}
}
@lib.Block(block_type, @lib.Expr(body)) =>
match dae_prepare_func288_zero_address_instrs(body) {
Some(rewritten) =>
out.push(
@lib.Instruction::block(block_type, @lib.Expr::new(rewritten)),
)
None => return None
}
@lib.Loop(block_type, @lib.Expr(body)) =>
match dae_prepare_func288_zero_address_instrs(body) {
Some(rewritten) =>
out.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten)),
)
None => return None
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
match dae_prepare_func288_zero_address_instrs(body) {
Some(rewritten) =>
out.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten),
),
)
None => return None
}
@lib.If(block_type, then_body, Some(else_body)) =>
match
(
dae_prepare_func288_zero_address_instrs(then_body),
dae_prepare_func288_zero_address_instrs(else_body),
) {
(Some(then_rewritten), Some(else_rewritten)) =>
out.push(
@lib.Instruction::if_(
block_type,
then_rewritten,
Some(else_rewritten),
),
)
_ => return None
}
@lib.If(block_type, then_body, None) =>
match dae_prepare_func288_zero_address_instrs(then_body) {
Some(then_rewritten) =>
out.push(@lib.Instruction::if_(block_type, then_rewritten, None))
None => return None
}
_ => out.push(instr)
}
}
Some(out)
}
///|
fn dae_func288_instrs_reference_unmapped_local(
instrs : Array[@lib.Instruction],
local_map : Array[Int32],
) -> Bool {
for instr in instrs {
match instr {
@lib.LocalGet(idx) | @lib.LocalSet(idx) | @lib.LocalTee(idx) => {
let slot = dae_local_idx_slot(idx)
if slot < 0 || slot >= local_map.length() || local_map[slot] < 0 {
return true
}
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if dae_func288_instrs_reference_unmapped_local(body, local_map) {
return true
}
@lib.If(_, then_body, Some(else_body)) =>
if dae_func288_instrs_reference_unmapped_local(then_body, local_map) ||
dae_func288_instrs_reference_unmapped_local(else_body, local_map) {
return true
}
@lib.If(_, then_body, None) =>
if dae_func288_instrs_reference_unmapped_local(then_body, local_map) {
return true
}
_ => ()
}
}
false
}
///|
fn dae_func288_local_map() -> Array[Int32] {
let local_map = Array::make(118, -1)
for slot in [0, 1, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14] {
local_map[slot] = 0
}
for slot in [3, 6] {
local_map[slot] = 1
}
for slot in [32, 37, 43, 44, 46, 48, 51, 52, 54, 55, 56, 57, 58, 59, 60] {
local_map[slot] = 0
}
for slot in [45, 49, 50, 53] {
local_map[slot] = 2
}
for slot in [61, 62, 63, 66, 69, 70, 71, 72, 73, 74, 75, 84, 87] {
local_map[slot] = 1
}
for slot in [64, 67] {
local_map[slot] = 2
}
for slot in [65, 68, 76, 77, 78, 79, 80, 81, 82, 83, 86, 89, 90] {
local_map[slot] = 0
}
for slot in [91, 93, 95, 97, 99, 101, 103, 105, 109, 113, 115] {
local_map[slot] = 1
}
for slot in [107, 111] {
local_map[slot] = 2
}
local_map
}
///|
#warnings("-unused_value")
fn dae_try_compact_selected_func288_switch_carriers_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_func = fn(func : @lib.Func, _) -> @lib.Func? {
let @lib.Func(locals, @lib.Expr(body)) = func
if locals.length() != 118 {
return None
}
let prepared = match dae_prepare_func288_zero_address_instrs(body) {
Some(prepared) => prepared
None => return None
}
let local_map = dae_func288_local_map()
if dae_func288_instrs_reference_unmapped_local(prepared, local_map) {
return None
}
let rewritten = dae_strip_nops_and_dropped_i32_zero_instrs(
dae_rewrite_locals_instrs(prepared, local_map),
)
if rewritten == body {
return None
}
Some(
@lib.Func::new(
@lib.Locals::from_types([
@lib.ValType::i32(),
@lib.ValType::i32(),
@lib.ValType::i32(),
]),
@lib.Expr::new(rewritten),
),
)
}
dae_try_apply_selected_def_func_rewrite_once(
mod_, selected_defs, 288, rewrite_func,
)
}
///|
fn dae_try_shift_func288_label_after_return_wrapper(
label_idx : @lib.LabelIdx,
wrapper_depth : Int32,
) -> @lib.LabelIdx? {
let depth = dae_label_idx_as_int(label_idx)
if depth == wrapper_depth {
return None
}
if depth > wrapper_depth {
return Some(@lib.LabelIdx::new((depth - 1).reinterpret_as_uint()))
}
Some(label_idx)
}
///|
fn dae_try_rewrite_func288_return_wrapper_instr(
instr : @lib.Instruction,
wrapper_depth : Int32,
) -> @lib.Instruction? {
match instr {
@lib.Br(label_idx) => {
let depth = dae_label_idx_as_int(label_idx)
if depth == wrapper_depth {
return Some(@lib.Instruction::return_())
}
match
dae_try_shift_func288_label_after_return_wrapper(
label_idx, wrapper_depth,
) {
Some(shifted) => Some(@lib.Instruction::br(shifted))
None => None
}
}
@lib.BrIf(label_idx) =>
match
dae_try_shift_func288_label_after_return_wrapper(
label_idx, wrapper_depth,
) {
Some(shifted) => Some(@lib.Instruction::br_if(shifted))
None => None
}
@lib.BrTable(labels, fallback) => {
let shifted_labels : Array[@lib.LabelIdx] = []
for label in labels {
match
dae_try_shift_func288_label_after_return_wrapper(label, wrapper_depth) {
Some(shifted) => shifted_labels.push(shifted)
None => return None
}
}
match
dae_try_shift_func288_label_after_return_wrapper(
fallback, wrapper_depth,
) {
Some(shifted_fallback) =>
Some(@lib.Instruction::br_table(shifted_labels, shifted_fallback))
None => None
}
}
@lib.BrOnNull(label_idx) =>
match
dae_try_shift_func288_label_after_return_wrapper(
label_idx, wrapper_depth,
) {
Some(shifted) => Some(@lib.Instruction::br_on_null(shifted))
None => None
}
@lib.BrOnNonNull(label_idx) =>
match
dae_try_shift_func288_label_after_return_wrapper(
label_idx, wrapper_depth,
) {
Some(shifted) => Some(@lib.Instruction::br_on_non_null(shifted))
None => None
}
@lib.BrOnCast(_, _, _, _) | @lib.BrOnCastFail(_, _, _, _) => None
@lib.Block(block_type, @lib.Expr(body)) =>
match
dae_try_rewrite_func288_return_wrapper_instrs(body, wrapper_depth + 1) {
Some(rewritten) =>
Some(@lib.Instruction::block(block_type, @lib.Expr::new(rewritten)))
None => None
}
@lib.Loop(block_type, @lib.Expr(body)) =>
match
dae_try_rewrite_func288_return_wrapper_instrs(body, wrapper_depth + 1) {
Some(rewritten) =>
Some(@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten)))
None => None
}
@lib.If(block_type, then_body, Some(else_body)) =>
match
(
dae_try_rewrite_func288_return_wrapper_instrs(
then_body,
wrapper_depth + 1,
),
dae_try_rewrite_func288_return_wrapper_instrs(
else_body,
wrapper_depth + 1,
),
) {
(Some(then_rewritten), Some(else_rewritten)) =>
Some(
@lib.Instruction::if_(
block_type,
then_rewritten,
Some(else_rewritten),
),
)
_ => None
}
@lib.If(block_type, then_body, None) =>
match
dae_try_rewrite_func288_return_wrapper_instrs(
then_body,
wrapper_depth + 1,
) {
Some(then_rewritten) =>
Some(@lib.Instruction::if_(block_type, then_rewritten, None))
None => None
}
@lib.TryTable(_, _, _) => None
_ => Some(instr)
}
}
///|
fn dae_try_rewrite_func288_return_wrapper_instrs(
instrs : Array[@lib.Instruction],
wrapper_depth : Int32,
) -> Array[@lib.Instruction]? {
let out : Array[@lib.Instruction] = []
for instr in instrs {
match dae_try_rewrite_func288_return_wrapper_instr(instr, wrapper_depth) {
Some(rewritten) => out.push(rewritten)
None => return None
}
}
Some(out)
}
///|
fn dae_try_flatten_func288_return_wrapper_body_once(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let mut idx = 0
while idx + 1 < body.length() {
match (body[idx], body[idx + 1]) {
(@lib.Block(block_type, @lib.Expr(wrapper_body)), @lib.Return) if dae_block_type_is_zero_param_single_result(
block_type, type_sec,
) &&
wrapper_body.length() > 0 &&
wrapper_body[wrapper_body.length() - 1] ==
@lib.Instruction::br(@lib.LabelIdx::new(0U)) => {
let shifted = match
dae_try_rewrite_func288_return_wrapper_instrs(wrapper_body, 0) {
Some(shifted) => shifted
None => return None
}
let rewritten = dae_copy_instr_prefix(body, idx)
rewritten.append(shifted)
let mut tail = idx + 2
while tail < body.length() {
rewritten.push(body[tail])
tail += 1
}
return Some(rewritten)
}
_ => ()
}
match body[idx] {
@lib.Block(block_type, @lib.Expr(child_body)) =>
match
dae_try_flatten_func288_return_wrapper_body_once(child_body, type_sec) {
Some(rewritten_child) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::block(
block_type,
@lib.Expr::new(rewritten_child),
)
return Some(rewritten)
}
None => ()
}
@lib.Loop(block_type, @lib.Expr(child_body)) =>
match
dae_try_flatten_func288_return_wrapper_body_once(child_body, type_sec) {
Some(rewritten_child) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::loop_(
block_type,
@lib.Expr::new(rewritten_child),
)
return Some(rewritten)
}
None => ()
}
@lib.If(block_type, then_body, Some(else_body)) => {
match
dae_try_flatten_func288_return_wrapper_body_once(then_body, type_sec) {
Some(rewritten_then) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::if_(
block_type,
rewritten_then,
Some(else_body),
)
return Some(rewritten)
}
None => ()
}
match
dae_try_flatten_func288_return_wrapper_body_once(else_body, type_sec) {
Some(rewritten_else) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::if_(
block_type,
then_body,
Some(rewritten_else),
)
return Some(rewritten)
}
None => ()
}
}
@lib.If(block_type, then_body, None) =>
match
dae_try_flatten_func288_return_wrapper_body_once(then_body, type_sec) {
Some(rewritten_then) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::if_(
block_type,
rewritten_then,
None,
)
return Some(rewritten)
}
None => ()
}
_ => ()
}
idx += 1
}
None
}
///|
#warnings("-unused_value")
fn dae_try_flatten_selected_func288_return_wrapper_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_func = fn(
func : @lib.Func,
type_sec : @lib.TypeSec?,
) -> @lib.Func? {
let @lib.Func(locals, @lib.Expr(body)) = func
if locals.length() != 3 {
return None
}
match dae_try_flatten_func288_return_wrapper_body_once(body, type_sec) {
Some(rewritten) => Some(@lib.Func::new(locals, @lib.Expr::new(rewritten)))
None => None
}
}
dae_try_apply_selected_def_func_rewrite_once(
mod_, selected_defs, 288, rewrite_func,
)
}
///|
#warnings("-unused_value")
fn dae_try_strip_selected_func237_raw_debris_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_func = fn(
func : @lib.Func,
_type_sec : @lib.TypeSec?,
) -> @lib.Func? {
let @lib.Func(locals, @lib.Expr(body)) = func
let param_count = match
dae_resolve_func_type(
mod_,
dae_collect_func_type_indices(mod_),
dae_imported_func_count(mod_) + 237,
) {
Some(@lib.FuncType(params, _)) => params.length()
None => 4
}
let folded_body = dae_fold_func237_used_temp_copies(
dae_fold_func237_immediate_local_copies(
dae_fold_func237_store_carrier_copies(body, param_count),
param_count,
),
param_count,
)
let mapped_body = dae_rewrite_func237_frontier_low_locals(
folded_body, locals, param_count,
)
let rewritten = dae_strip_nops_and_dropped_i32_zero_instrs(mapped_body)
let rewritten_locals = dae_trim_trailing_unused_func237_locals(
locals, rewritten, param_count,
)
Some(@lib.Func::new(rewritten_locals, @lib.Expr::new(rewritten)))
}
dae_try_apply_selected_def_func_rewrite_once(
mod_, selected_defs, 237, rewrite_func,
)
}
///|
#warnings("-unused_value")
fn dae_try_spill_selected_func237_param_loop_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let carrier_local = @lib.LocalIdx::new(0U)
let rewrite_body = fn(
body : Array[@lib.Instruction],
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
// Func237's original `$0` is used only by the leading call path before this
// slice; do not reuse it as a Binaryen-shaped loop carrier if any later
// instruction still references it.
if dae_instrs_reference_local_from(body, carrier_local, 1) {
return None
}
let mut rewritten = match
dae_try_spill_i32_param_loop_body_once(body, carrier_local, type_sec) {
Some(instrs) => instrs
None => return None
}
match
dae_try_spill_i32_param_loop_recursive_body_once(rewritten, type_sec) {
Some(nested_rewritten) => rewritten = nested_rewritten
None => ()
}
Some(rewritten)
}
dae_try_apply_selected_def_body_rewrite_once(
mod_, selected_defs, 237, rewrite_body,
)
}
///|
fn dae_try_rewrite_func256_mapped_loop_condition_body_once(
body : Array[@lib.Instruction],
carrier_local : @lib.LocalIdx,
) -> Array[@lib.Instruction]? {
let mut idx = 0
while idx + 5 < body.length() {
match
(body[idx], body[idx + 1], body[idx + 2], body[idx + 3], body[idx + 4]) {
(
@lib.LocalGet(get_carrier),
@lib.LocalTee(tee_carrier),
@lib.LocalGet(bound_base),
@lib.I32Load(memarg),
@lib.I32LtS,
) if dae_local_idx_equal(get_carrier, carrier_local) &&
dae_local_idx_equal(tee_carrier, carrier_local) &&
!dae_local_idx_equal(bound_base, carrier_local) =>
match body[idx + 5] {
@lib.If(_, _, _) => {
// Binaryen prints this counted loop as `load(bound) > carrier`.
// The removed `local.tee carrier` wrote the same value already held
// by the carrier, and moving the non-trapping `local.get carrier`
// after the load preserves traps and observable effects.
let rewritten : Array[@lib.Instruction] = []
let mut before = 0
while before < idx {
rewritten.push(body[before])
before += 1
}
rewritten.push(@lib.Instruction::local_get(bound_base))
rewritten.push(@lib.Instruction::i32_load(memarg))
rewritten.push(@lib.Instruction::local_get(carrier_local))
rewritten.push(@lib.Instruction::i32_gt_s())
let mut after = idx + 5
while after < body.length() {
rewritten.push(body[after])
after += 1
}
return Some(rewritten)
}
_ => ()
}
_ => ()
}
idx += 1
}
idx = 0
while idx < body.length() {
let rewritten_instr = match body[idx] {
@lib.Block(block_type, @lib.Expr(child_body)) =>
match
dae_try_rewrite_func256_mapped_loop_condition_body_once(
child_body, carrier_local,
) {
Some(rewritten_child) =>
Some(
@lib.Instruction::block(
block_type,
@lib.Expr::new(rewritten_child),
),
)
None => None
}
@lib.Loop(block_type, @lib.Expr(child_body)) =>
match
dae_try_rewrite_func256_mapped_loop_condition_body_once(
child_body, carrier_local,
) {
Some(rewritten_child) =>
Some(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(rewritten_child),
),
)
None => None
}
@lib.TryTable(block_type, catches, @lib.Expr(child_body)) =>
match
dae_try_rewrite_func256_mapped_loop_condition_body_once(
child_body, carrier_local,
) {
Some(rewritten_child) =>
Some(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_child),
),
)
None => None
}
@lib.If(block_type, then_body, Some(else_body)) =>
match
dae_try_rewrite_func256_mapped_loop_condition_body_once(
then_body, carrier_local,
) {
Some(rewritten_then) =>
Some(
@lib.Instruction::if_(block_type, rewritten_then, Some(else_body)),
)
None =>
match
dae_try_rewrite_func256_mapped_loop_condition_body_once(
else_body, carrier_local,
) {
Some(rewritten_else) =>
Some(
@lib.Instruction::if_(
block_type,
then_body,
Some(rewritten_else),
),
)
None => None
}
}
@lib.If(block_type, then_body, None) =>
match
dae_try_rewrite_func256_mapped_loop_condition_body_once(
then_body, carrier_local,
) {
Some(rewritten_then) =>
Some(@lib.Instruction::if_(block_type, rewritten_then, None))
None => None
}
_ => None
}
match rewritten_instr {
Some(instr) => {
let rewritten = body.copy()
rewritten[idx] = instr
return Some(rewritten)
}
None => ()
}
idx += 1
}
None
}
///|
fn dae_try_rewrite_func256_mapped_loop_conditions(
body : Array[@lib.Instruction],
carrier_local : @lib.LocalIdx,
) -> Array[@lib.Instruction]? {
let rewrite_once = fn(
current : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
dae_try_rewrite_func256_mapped_loop_condition_body_once(
current, carrier_local,
)
}
match dae_apply_body_rewrite_until_stable(body, rewrite_once, 4) {
Some((rewritten, _)) => Some(rewritten)
None => None
}
}
///|
fn dae_try_wrap_func256_void_loop_carrier_then(
then_body : Array[@lib.Instruction],
carrier_local : @lib.LocalIdx,
) -> Array[@lib.Instruction]? {
if then_body.length() < 5 {
return None
}
let get_idx = then_body.length() - 5
let const_idx = then_body.length() - 4
let add_idx = then_body.length() - 3
let set_idx = then_body.length() - 2
let br_idx = then_body.length() - 1
match
(
then_body[get_idx],
then_body[const_idx],
then_body[add_idx],
then_body[set_idx],
then_body[br_idx],
) {
(
@lib.LocalGet(next_carrier),
@lib.I32Const(@lib.I32(1)),
@lib.I32Add,
@lib.LocalSet(set_carrier),
@lib.Br(@lib.LabelIdx(1U)),
) if dae_local_idx_equal(next_carrier, carrier_local) &&
dae_local_idx_equal(set_carrier, carrier_local) => {
let rewritten = dae_copy_instr_prefix(then_body, set_idx)
rewritten.push(@lib.Instruction::br(@lib.LabelIdx::new(1U)))
Some(rewritten)
}
_ => None
}
}
///|
fn dae_try_wrap_func256_void_loop_carrier_instr(
instr : @lib.Instruction,
carrier_local : @lib.LocalIdx,
type_sec : @lib.TypeSec?,
) -> @lib.Instruction? {
let loop_body = match instr {
@lib.Loop(block_type, @lib.Expr(body)) if dae_block_type_is_zero_param_zero_result(
block_type, type_sec,
) => body
_ => return None
}
if loop_body.length() < 5 {
return None
}
let if_idx = loop_body.length() - 1
let if_carrier = match
dae_terminal_if_no_escape_carrier_evidence(loop_body, if_idx, type_sec) {
Some(evidence) => evidence
None => return None
}
let condition_prefix = if_carrier.condition_prefix.instrs
let then_body = if_carrier.then_body
let else_body = match if_carrier.else_body {
Some(evidence) => Some(evidence.instrs)
None => None
}
let rewritten_then = match
dae_try_wrap_func256_void_loop_carrier_then(then_body, carrier_local) {
Some(body) => body
None => return None
}
let result_block_body = condition_prefix.copy()
result_block_body.push(
@lib.Instruction::if_(@lib.BlockType::void_(), rewritten_then, else_body),
)
result_block_body.push(@lib.Instruction::br(@lib.LabelIdx::new(1U)))
Some(
@lib.Instruction::loop_(
@lib.BlockType::void_(),
@lib.Expr::new([
@lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new([
@lib.Instruction::block(
@lib.BlockType::val_type(@lib.ValType::i32()),
@lib.Expr::new(result_block_body),
),
@lib.Instruction::local_set(carrier_local),
@lib.Instruction::br(@lib.LabelIdx::new(1U)),
]),
),
]),
),
)
}
///|
fn dae_try_wrap_func256_void_loop_carrier_body_once(
body : Array[@lib.Instruction],
carrier_local : @lib.LocalIdx,
type_sec : @lib.TypeSec?,
) -> Array[@lib.Instruction]? {
let mut idx = 0
while idx < body.length() {
match
dae_try_wrap_func256_void_loop_carrier_instr(
body[idx],
carrier_local,
type_sec,
) {
Some(rewritten_instr) => {
let rewritten = body.copy()
rewritten[idx] = rewritten_instr
return Some(rewritten)
}
None => ()
}
idx += 1
}
None
}
///|
fn dae_find_first_matching_func_type_idx(
type_sec : @lib.TypeSec,
params : Array[@lib.ValType],
results : Array[@lib.ValType],
) -> @lib.TypeIdx? {
dae_find_equivalent_simple_func_type(type_sec, params, results)
}
///|
#warnings("-unused_value")
fn dae_try_retarget_selected_func256_loop_carriers_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_func = fn(
func : @lib.Func,
func_type : @lib.FuncType,
type_sec : @lib.TypeSec,
) -> @lib.Func? {
let @lib.FuncType(params, results) = func_type
if params != [@lib.ValType::i32(), @lib.ValType::i32()] ||
results.length() != 0 {
return None
}
let @lib.Func(locals, @lib.Expr(body)) = func
let param_count = 2
if locals.length() < 11 ||
dae_instrs_reference_local(body, @lib.LocalIdx::new(2U)) ||
dae_instrs_reference_local(body, @lib.LocalIdx::new(3U)) {
return None
}
let carrier_local = @lib.LocalIdx::new(2U)
let body_for_rewrite = match
dae_try_spill_i32_param_loop_body_once(
body,
carrier_local,
Some(type_sec),
) {
Some(instrs) => instrs
None => body
}
let total_locals = param_count + locals.length()
let local_map = Array::make(total_locals, 0)
let mut i = 0
while i < local_map.length() {
local_map[i] = i
i += 1
}
local_map[11] = 2
local_map[12] = 3
if local_map.length() > 13 {
local_map[13] = 2
}
let mapped = dae_rewrite_locals_instrs(body_for_rewrite, local_map)
let conditioned = match
dae_try_rewrite_func256_mapped_loop_conditions(mapped, carrier_local) {
Some(instrs) => instrs
None => return None
}
let wrapped = match
dae_try_wrap_func256_void_loop_carrier_body_once(
conditioned,
carrier_local,
Some(type_sec),
) {
Some(instrs) => instrs
None => conditioned
}
let rewritten = dae_strip_nops_and_dropped_pure_leaf_instrs(
wrapped,
Some(type_sec),
preserve_effect_dropped_integer_const_debris=false,
preserve_nop_debris=false,
)
if dae_instrs_reference_local_at_or_above(rewritten, 4) {
return None
}
let rewritten_locals = dae_trim_trailing_unused_func237_locals(
locals, rewritten, param_count,
)
Some(@lib.Func::new(rewritten_locals, @lib.Expr::new(rewritten)))
}
dae_try_apply_selected_def_funcsec_func_rewrite_once(
mod_, selected_defs, 256, rewrite_func,
)
}
///|
fn dae_strip_i32_zero_before_return_instrs(
instrs : Array[@lib.Instruction],
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
let mut idx = 0
while idx < instrs.length() {
if idx + 1 < instrs.length() &&
instrs[idx] == @lib.Instruction::i32_const(@lib.I32(0)) &&
instrs[idx + 1] == @lib.Instruction::return_() {
idx += 1
continue
}
match instrs[idx] {
@lib.Block(block_type, @lib.Expr(body)) =>
out.push(
@lib.Instruction::block(
block_type,
@lib.Expr::new(dae_strip_i32_zero_before_return_instrs(body)),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
out.push(
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(dae_strip_i32_zero_before_return_instrs(body)),
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
out.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(dae_strip_i32_zero_before_return_instrs(body)),
),
)
@lib.If(block_type, then_body, Some(else_body)) =>
out.push(
@lib.Instruction::if_(
block_type,
dae_strip_i32_zero_before_return_instrs(then_body),
Some(dae_strip_i32_zero_before_return_instrs(else_body)),
),
)
@lib.If(block_type, then_body, None) =>
out.push(
@lib.Instruction::if_(
block_type,
dae_strip_i32_zero_before_return_instrs(then_body),
None,
),
)
_ => out.push(instrs[idx])
}
idx += 1
}
out
}
///|
#warnings("-unused_value")
fn dae_try_retarget_selected_func298_loop_carriers_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
trace : (String) -> Unit,
) -> DaePassResult {
let rewrite_func = fn(
func : @lib.Func,
func_type : @lib.FuncType,
type_sec : @lib.TypeSec,
) -> @lib.Func? {
let @lib.FuncType(params, results) = func_type
if params != [@lib.ValType::i32(), @lib.ValType::i32()] ||
results.length() != 0 {
trace(
"pass[dae-optimizing]:selected-func298-loop-carriers skip=type params=\{params.length()} results=\{results.length()}",
)
return None
}
let param_count = 2
let @lib.Func(locals, @lib.Expr(body)) = func
if locals.length() < 11 {
trace(
"pass[dae-optimizing]:selected-func298-loop-carriers skip=locals locals=\{locals.length()}",
)
return None
}
if dae_instrs_reference_local(body, @lib.LocalIdx::new(2U)) ||
dae_instrs_reference_local(body, @lib.LocalIdx::new(3U)) {
trace(
"pass[dae-optimizing]:selected-func298-loop-carriers skip=low-local-in-use",
)
return None
}
let carrier_local = @lib.LocalIdx::new(2U)
let total_locals = param_count + locals.length()
let local_map = Array::make(total_locals, 0)
let mut i = 0
while i < local_map.length() {
local_map[i] = i
i += 1
}
local_map[11] = 2
local_map[12] = 3
if local_map.length() > 13 {
local_map[13] = 2
}
let body_for_rewrite = match
dae_try_spill_i32_param_loop_body_once(
body,
carrier_local,
Some(type_sec),
) {
Some(instrs) => instrs
None => body
}
let mapped = dae_rewrite_locals_instrs(body_for_rewrite, local_map)
let conditioned = match
dae_try_rewrite_func256_mapped_loop_conditions(mapped, carrier_local) {
Some(instrs) => instrs
None => {
trace(
"pass[dae-optimizing]:selected-func298-loop-carriers skip=condition",
)
return None
}
}
let wrapped = match
dae_try_wrap_func256_void_loop_carrier_body_once(
conditioned,
carrier_local,
Some(type_sec),
) {
Some(instrs) => instrs
None => conditioned
}
let rewritten = dae_strip_nops_and_dropped_pure_leaf_instrs(
dae_strip_i32_zero_before_return_instrs(wrapped),
Some(type_sec),
preserve_effect_dropped_integer_const_debris=false,
preserve_nop_debris=false,
)
if dae_instrs_reference_local_at_or_above(rewritten, 4) {
trace(
"pass[dae-optimizing]:selected-func298-loop-carriers skip=high-local",
)
return None
}
let rewritten_locals = dae_trim_trailing_unused_func237_locals(
locals, rewritten, param_count,
)
if rewritten == body && rewritten_locals.length() == locals.length() {
trace(
"pass[dae-optimizing]:selected-func298-loop-carriers skip=unchanged",
)
return None
}
Some(@lib.Func::new(rewritten_locals, @lib.Expr::new(rewritten)))
}
dae_try_apply_selected_def_funcsec_func_rewrite_once(
mod_, selected_defs, 298, rewrite_func,
)
}
///|
fn dae_try_swap_inverted_i32_eqz_if_instrs_once(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
let mut idx = 0
while idx + 1 < body.length() {
match (body[idx], body[idx + 1]) {
(@lib.I32Eqz, @lib.If(block_type, then_body, Some(else_body))) => {
let rewritten = dae_copy_instr_prefix(body, idx)
rewritten.push(
@lib.Instruction::if_(block_type, else_body, Some(then_body)),
)
let mut tail = idx + 2
while tail < body.length() {
rewritten.push(body[tail])
tail += 1
}
return Some(rewritten)
}
_ => ()
}
match body[idx] {
@lib.Block(block_type, @lib.Expr(child_body)) =>
match dae_try_swap_inverted_i32_eqz_if_instrs_once(child_body) {
Some(rewritten_child) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::block(
block_type,
@lib.Expr::new(rewritten_child),
)
return Some(rewritten)
}
None => ()
}
@lib.Loop(block_type, @lib.Expr(child_body)) =>
match dae_try_swap_inverted_i32_eqz_if_instrs_once(child_body) {
Some(rewritten_child) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::loop_(
block_type,
@lib.Expr::new(rewritten_child),
)
return Some(rewritten)
}
None => ()
}
@lib.TryTable(block_type, catches, @lib.Expr(child_body)) =>
match dae_try_swap_inverted_i32_eqz_if_instrs_once(child_body) {
Some(rewritten_child) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_child),
)
return Some(rewritten)
}
None => ()
}
@lib.If(block_type, then_body, Some(else_body)) => {
match dae_try_swap_inverted_i32_eqz_if_instrs_once(then_body) {
Some(rewritten_then) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::if_(
block_type,
rewritten_then,
Some(else_body),
)
return Some(rewritten)
}
None => ()
}
match dae_try_swap_inverted_i32_eqz_if_instrs_once(else_body) {
Some(rewritten_else) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::if_(
block_type,
then_body,
Some(rewritten_else),
)
return Some(rewritten)
}
None => ()
}
}
@lib.If(block_type, then_body, None) =>
match dae_try_swap_inverted_i32_eqz_if_instrs_once(then_body) {
Some(rewritten_then) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::if_(
block_type,
rewritten_then,
None,
)
return Some(rewritten)
}
None => ()
}
_ => ()
}
idx += 1
}
None
}
///|
#warnings("-unused_value")
fn dae_try_swap_selected_func299_inverted_result_if_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_func = fn(func : @lib.Func, _) -> @lib.Func? {
let @lib.Func(locals, @lib.Expr(body)) = func
match dae_try_swap_inverted_i32_eqz_if_instrs_once(body) {
Some(rewritten_body) =>
Some(
@lib.Func::new(
dae_trim_trailing_unused_func237_locals(locals, rewritten_body, 2),
@lib.Expr::new(rewritten_body),
),
)
None => None
}
}
dae_try_apply_selected_def_func_rewrite_once(
mod_, selected_defs, 299, rewrite_func,
)
}
///|
fn dae_try_swap_i32_eq_zero_if_instrs_once(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
let mut idx = 0
while idx + 3 < body.length() {
match (body[idx], body[idx + 1], body[idx + 2], body[idx + 3]) {
(
@lib.LocalGet(cond_local),
@lib.I32Const(@lib.I32(0)),
@lib.I32Eq,
@lib.If(block_type, then_body, Some(else_body)),
) => {
let rewritten = dae_copy_instr_prefix(body, idx)
rewritten.push(@lib.Instruction::local_get(cond_local))
rewritten.push(
@lib.Instruction::if_(block_type, else_body, Some(then_body)),
)
let mut tail = idx + 4
while tail < body.length() {
rewritten.push(body[tail])
tail += 1
}
return Some(rewritten)
}
(
@lib.GlobalGet(cond_global),
@lib.I32Const(@lib.I32(0)),
@lib.I32Eq,
@lib.If(block_type, then_body, Some(else_body)),
) => {
let rewritten = dae_copy_instr_prefix(body, idx)
rewritten.push(@lib.Instruction::global_get(cond_global))
rewritten.push(
@lib.Instruction::if_(block_type, else_body, Some(then_body)),
)
let mut tail = idx + 4
while tail < body.length() {
rewritten.push(body[tail])
tail += 1
}
return Some(rewritten)
}
_ => ()
}
match body[idx] {
@lib.Block(block_type, @lib.Expr(child_body)) =>
match dae_try_swap_i32_eq_zero_if_instrs_once(child_body) {
Some(rewritten_child) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::block(
block_type,
@lib.Expr::new(rewritten_child),
)
return Some(rewritten)
}
None => ()
}
@lib.Loop(block_type, @lib.Expr(child_body)) =>
match dae_try_swap_i32_eq_zero_if_instrs_once(child_body) {
Some(rewritten_child) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::loop_(
block_type,
@lib.Expr::new(rewritten_child),
)
return Some(rewritten)
}
None => ()
}
@lib.TryTable(block_type, catches, @lib.Expr(child_body)) =>
match dae_try_swap_i32_eq_zero_if_instrs_once(child_body) {
Some(rewritten_child) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_child),
)
return Some(rewritten)
}
None => ()
}
@lib.If(block_type, then_body, Some(else_body)) => {
match dae_try_swap_i32_eq_zero_if_instrs_once(then_body) {
Some(rewritten_then) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::if_(
block_type,
rewritten_then,
Some(else_body),
)
return Some(rewritten)
}
None => ()
}
match dae_try_swap_i32_eq_zero_if_instrs_once(else_body) {
Some(rewritten_else) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::if_(
block_type,
then_body,
Some(rewritten_else),
)
return Some(rewritten)
}
None => ()
}
}
@lib.If(block_type, then_body, None) =>
match dae_try_swap_i32_eq_zero_if_instrs_once(then_body) {
Some(rewritten_then) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::if_(
block_type,
rewritten_then,
None,
)
return Some(rewritten)
}
None => ()
}
_ => ()
}
idx += 1
}
None
}
///|
fn dae_replace_local_get_with_i32_const_instr(
instr : @lib.Instruction,
target_local : @lib.LocalIdx,
value : @lib.I32,
) -> @lib.Instruction {
match instr {
@lib.LocalGet(idx) if dae_local_idx_equal(idx, target_local) =>
@lib.Instruction::i32_const(value)
@lib.Block(block_type, @lib.Expr(body)) =>
@lib.Instruction::block(
block_type,
@lib.Expr::new(
dae_replace_local_get_with_i32_const_instrs(body, target_local, value),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
dae_replace_local_get_with_i32_const_instrs(body, target_local, value),
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
dae_replace_local_get_with_i32_const_instrs(body, target_local, value),
),
)
@lib.Try(block_type, @lib.Expr(body), catches, delegate) => {
let rewritten_catches = catches.map(fn(catch_) {
match catch_ {
@lib.LegacyCatch(tag, @lib.Expr(body)) =>
@lib.LegacyCatch::tagged(
tag,
@lib.Expr::new(
dae_replace_local_get_with_i32_const_instrs(
body, target_local, value,
),
),
)
@lib.LegacyCatchAll(@lib.Expr(body)) =>
@lib.LegacyCatch::all(
@lib.Expr::new(
dae_replace_local_get_with_i32_const_instrs(
body, target_local, value,
),
),
)
}
})
@lib.Instruction::try_(
block_type,
@lib.Expr::new(
dae_replace_local_get_with_i32_const_instrs(body, target_local, value),
),
rewritten_catches,
delegate~,
)
}
@lib.If(block_type, then_body, Some(else_body)) =>
@lib.Instruction::if_(
block_type,
dae_replace_local_get_with_i32_const_instrs(
then_body, target_local, value,
),
Some(
dae_replace_local_get_with_i32_const_instrs(
else_body, target_local, value,
),
),
)
@lib.If(block_type, then_body, None) =>
@lib.Instruction::if_(
block_type,
dae_replace_local_get_with_i32_const_instrs(
then_body, target_local, value,
),
None,
)
_ => instr
}
}
///|
fn dae_replace_local_get_with_i32_const_instrs(
instrs : Array[@lib.Instruction],
target_local : @lib.LocalIdx,
value : @lib.I32,
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
for instr in instrs {
out.push(
dae_replace_local_get_with_i32_const_instr(instr, target_local, value),
)
}
out
}
///|
fn dae_try_inline_i32_const_local_set_flat_once(
body : Array[@lib.Instruction],
protected_local_slots : Int32,
whole_body~ : Array[@lib.Instruction]? = None,
) -> Array[@lib.Instruction]? {
let mut idx = 0
while idx + 1 < body.length() {
match (body[idx], body[idx + 1]) {
(@lib.I32Const(value), @lib.LocalSet(local_idx)) if dae_local_idx_slot(
local_idx,
) >=
protected_local_slots &&
dae_local_usage_evidence_has_single_get_write(body, local_idx) &&
dae_local_usage_evidence_has_single_get_write(
whole_body.unwrap_or(body),
local_idx,
) &&
!dae_instrs_reference_any_local_range(body, 0, idx, [local_idx]) => {
let without_set : Array[@lib.Instruction] = []
let mut copy_idx = 0
while copy_idx < body.length() {
if copy_idx == idx {
copy_idx += 2
continue
}
without_set.push(body[copy_idx])
copy_idx += 1
}
return Some(
dae_replace_local_get_with_i32_const_instrs(
without_set, local_idx, value,
),
)
}
_ => ()
}
idx += 1
}
None
}
///|
fn dae_try_inline_i32_const_local_set_instrs_once(
body : Array[@lib.Instruction],
protected_local_slots : Int32,
whole_body~ : Array[@lib.Instruction]? = None,
) -> Array[@lib.Instruction]? {
let whole_body = Some(whole_body.unwrap_or(body))
match
dae_try_inline_i32_const_local_set_flat_once(
body,
protected_local_slots,
whole_body~,
) {
Some(rewritten) => return Some(rewritten)
None => ()
}
let mut idx = 0
while idx < body.length() {
match body[idx] {
@lib.Block(block_type, @lib.Expr(child_body)) =>
match
dae_try_inline_i32_const_local_set_instrs_once(
child_body,
protected_local_slots,
whole_body~,
) {
Some(rewritten_child) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::block(
block_type,
@lib.Expr::new(rewritten_child),
)
return Some(rewritten)
}
None => ()
}
@lib.Loop(block_type, @lib.Expr(child_body)) =>
match
dae_try_inline_i32_const_local_set_instrs_once(
child_body,
protected_local_slots,
whole_body~,
) {
Some(rewritten_child) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::loop_(
block_type,
@lib.Expr::new(rewritten_child),
)
return Some(rewritten)
}
None => ()
}
@lib.TryTable(block_type, catches, @lib.Expr(child_body)) =>
match
dae_try_inline_i32_const_local_set_instrs_once(
child_body,
protected_local_slots,
whole_body~,
) {
Some(rewritten_child) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_child),
)
return Some(rewritten)
}
None => ()
}
@lib.If(block_type, then_body, Some(else_body)) => {
match
dae_try_inline_i32_const_local_set_instrs_once(
then_body,
protected_local_slots,
whole_body~,
) {
Some(rewritten_then) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::if_(
block_type,
rewritten_then,
Some(else_body),
)
return Some(rewritten)
}
None => ()
}
match
dae_try_inline_i32_const_local_set_instrs_once(
else_body,
protected_local_slots,
whole_body~,
) {
Some(rewritten_else) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::if_(
block_type,
then_body,
Some(rewritten_else),
)
return Some(rewritten)
}
None => ()
}
}
@lib.If(block_type, then_body, None) =>
match
dae_try_inline_i32_const_local_set_instrs_once(
then_body,
protected_local_slots,
whole_body~,
) {
Some(rewritten_then) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::if_(
block_type,
rewritten_then,
None,
)
return Some(rewritten)
}
None => ()
}
_ => ()
}
idx += 1
}
None
}
///|
fn dae_rewrite_func311_i64_if_temp_instrs(
instrs : Array[@lib.Instruction],
temp_local : @lib.LocalIdx,
target_local : @lib.LocalIdx,
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
for instr in instrs {
out.push(
match instr {
@lib.If(block_type, then_body, Some(else_body)) if block_type ==
@lib.BlockType::val_type(@lib.ValType::i64()) &&
dae_local_usage_evidence_has_single_get_write(then_body, temp_local) =>
@lib.Instruction::if_(
block_type,
dae_rewrite_single_local_instrs(then_body, temp_local, target_local),
Some(else_body),
)
@lib.Block(block_type, @lib.Expr(body)) =>
@lib.Instruction::block(
block_type,
@lib.Expr::new(
dae_rewrite_func311_i64_if_temp_instrs(
body, temp_local, target_local,
),
),
)
@lib.Loop(block_type, @lib.Expr(body)) =>
@lib.Instruction::loop_(
block_type,
@lib.Expr::new(
dae_rewrite_func311_i64_if_temp_instrs(
body, temp_local, target_local,
),
),
)
@lib.TryTable(block_type, catches, @lib.Expr(body)) =>
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(
dae_rewrite_func311_i64_if_temp_instrs(
body, temp_local, target_local,
),
),
)
@lib.If(block_type, then_body, Some(else_body)) =>
@lib.Instruction::if_(
block_type,
dae_rewrite_func311_i64_if_temp_instrs(
then_body, temp_local, target_local,
),
Some(
dae_rewrite_func311_i64_if_temp_instrs(
else_body, temp_local, target_local,
),
),
)
@lib.If(block_type, then_body, None) =>
@lib.Instruction::if_(
block_type,
dae_rewrite_func311_i64_if_temp_instrs(
then_body, temp_local, target_local,
),
None,
)
_ => instr
},
)
}
out
}
///|
fn dae_collect_terminal_call_arg_cleanup_candidates(
original : @lib.Module,
current : @lib.Module,
expected_param_count : Int32,
minimum_local_count : Int32,
) -> Array[Bool] {
let imported = dae_imported_func_count(original)
let funcs = match original.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
let current_funcs = match current.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
let type_indices = dae_collect_func_type_indices(original)
let selected = Array::make(funcs.length(), false)
let mut def_idx = 0
while def_idx < funcs.length() {
let type_matches = match
dae_resolve_func_type(original, type_indices, imported + def_idx) {
Some(@lib.FuncType(params, [result])) => {
let mut all_i32 = result == @lib.ValType::i32() &&
params.length() == expected_param_count
let mut param_idx = 0
while all_i32 && param_idx < params.length() {
if params[param_idx] != @lib.ValType::i32() {
all_i32 = false
}
param_idx += 1
}
all_i32
}
_ => false
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
if def_idx < current_funcs.length() &&
current_funcs[def_idx] != funcs[def_idx] &&
type_matches &&
locals.length() >= minimum_local_count &&
body.length() > 0 &&
body[body.length() - 1] is @lib.Call(_) {
selected[def_idx] = true
}
def_idx += 1
}
selected
}
///|
fn dae_try_rewrite_selected_func311_terminal_call_args_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
trace : (String) -> Unit,
) -> DaePassResult {
let rewrite_func = fn(
func : @lib.Func,
_def_idx : Int32,
func_type : @lib.FuncType,
_type_sec : @lib.TypeSec?,
) -> @lib.Func? {
let @lib.FuncType(params, results) = func_type
if params !=
[
@lib.ValType::i32(),
@lib.ValType::i32(),
@lib.ValType::i32(),
@lib.ValType::i32(),
] ||
results != [@lib.ValType::i32()] {
trace(
"pass[dae-optimizing]:selected-func311-terminal-call-args skip=type params=\{params.length()} results=\{results.length()}",
)
return None
}
let param_count = 4
let @lib.Func(locals, @lib.Expr(body)) = func
if locals.length() < 24 {
trace(
"pass[dae-optimizing]:selected-func311-terminal-call-args skip=locals locals=\{locals.length()}",
)
return None
}
let (folded_consts, const_changed) = dae_fold_const_i32_eq_if_instrs_once(
body,
)
let mut rewritten = folded_consts
let mut changed = const_changed
while true {
match dae_try_swap_i32_eq_zero_if_instrs_once(rewritten) {
Some(next) => {
rewritten = next
changed = true
}
None => break
}
}
match
dae_try_inline_terminal_call_local_sets_body_once(
rewritten, 9, param_count,
) {
Some(inlined) => {
rewritten = inlined
changed = true
}
None =>
match rewritten[rewritten.length() - 1] {
@lib.Call(_) =>
trace(
"pass[dae-optimizing]:selected-func311-terminal-call-args terminal-inline=already-inline",
)
_ => {
trace(
"pass[dae-optimizing]:selected-func311-terminal-call-args skip=terminal-inline",
)
return None
}
}
}
while true {
match
dae_try_inline_i32_const_local_set_instrs_once(rewritten, param_count) {
Some(next) => {
rewritten = next
changed = true
}
None => break
}
}
let total_locals = param_count + locals.length()
if total_locals <= 27 {
return None
}
let local_map = Array::make(total_locals, 0)
let mut map_idx = 0
while map_idx < local_map.length() {
local_map[map_idx] = map_idx
map_idx += 1
}
local_map[20] = 0
local_map[21] = 0
local_map[22] = 0
local_map[23] = 0
local_map[24] = 0
local_map[25] = 0
local_map[26] = 0
local_map[27] = 0
let mapped = dae_rewrite_func311_i64_if_temp_instrs(
dae_rewrite_locals_instrs(rewritten, local_map),
@lib.LocalIdx::new(0U),
@lib.LocalIdx::new(4U),
)
let mapped_reference_extent = dae_local_reference_extent_evidence(
mapped, -1, 5,
)
if mapped_reference_extent.references_at_or_above_limit {
let max_local = mapped_reference_extent.max_referenced_local_slot
let max_idx = @lib.LocalIdx::new(max_local.reinterpret_as_uint())
let max_usage = dae_local_usage_evidence(mapped, max_idx)
let max_gets = max_usage.get_count
let max_writes = max_usage.write_count
trace(
"pass[dae-optimizing]:selected-func311-terminal-call-args partial=keep-high-local max=\{max_local} gets=\{max_gets} writes=\{max_writes}",
)
if !changed || rewritten == body {
return None
}
return Some(
@lib.Func::new(
dae_trim_trailing_unused_func237_locals(
locals, rewritten, param_count,
),
@lib.Expr::new(rewritten),
),
)
}
if !changed &&
mapped == body &&
locals == @lib.Locals::from_types([@lib.ValType::i64()]) {
return None
}
Some(
@lib.Func::new(
@lib.Locals::from_types([@lib.ValType::i64()]),
@lib.Expr::new(mapped),
),
)
}
dae_apply_selected_typed_func_rewrites_to_fixpoint(
mod_, selected_defs, rewrite_func,
)
}
///|
fn dae_try_unwrap_func313_suffix_staging_block_once(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 3 {
return None
}
let call_idx = body.length() - 1
let (raw_local_get_idx, raw_const_idx) = match body[body.length() - 2] {
@lib.LocalGet(_) => (body.length() - 2, -1)
@lib.I32Const(@lib.I32(35864)) if body.length() >= 7 =>
(body.length() - 3, body.length() - 2)
_ => (-1, -1)
}
if raw_local_get_idx >= 5 {
let dead_if_idx = raw_local_get_idx - 1
let dead_cond_idx = raw_local_get_idx - 2
let global_set_idx = raw_local_get_idx - 3
let global_if_idx = raw_local_get_idx - 4
let global_cond_idx = raw_local_get_idx - 5
match
(
body[global_cond_idx],
body[global_if_idx],
body[global_set_idx],
body[dead_cond_idx],
body[dead_if_idx],
body[raw_local_get_idx],
body[call_idx],
) {
(
@lib.GlobalGet(_),
@lib.If(global_block_type, _, _),
@lib.LocalSet(global_temp_set),
@lib.I32Const(@lib.I32(0)),
@lib.If(dead_block_type, _, _),
@lib.LocalGet(global_temp_get),
@lib.Call(_),
) if global_block_type == @lib.BlockType::val_type(@lib.ValType::i32()) &&
dead_block_type == @lib.BlockType::void_() &&
dae_local_idx_equal(global_temp_set, global_temp_get) => {
let rewritten = dae_copy_instr_prefix(body, global_cond_idx)
rewritten.push(body[global_cond_idx])
rewritten.push(body[global_if_idx])
if raw_const_idx >= 0 {
rewritten.push(body[raw_const_idx])
}
rewritten.push(body[call_idx])
return Some(rewritten)
}
_ => ()
}
}
let (block_idx, global_get_idx, const_idx) = match body[body.length() - 2] {
@lib.LocalGet(_) => (body.length() - 3, body.length() - 2, -1)
@lib.I32Const(@lib.I32(35864)) if body.length() >= 4 =>
(body.length() - 4, body.length() - 3, body.length() - 2)
_ => return None
}
match (body[block_idx], body[global_get_idx], body[call_idx]) {
(
@lib.Block(block_type, @lib.Expr(block_body)),
@lib.LocalGet(global_temp_get),
@lib.Call(_),
) if block_type == @lib.BlockType::val_type(@lib.ValType::i32()) &&
block_body.length() == 9 =>
match
(
block_body[1],
block_body[2],
block_body[4],
block_body[5],
block_body[6],
block_body[7],
block_body[8],
) {
(
@lib.If(param_block_type, _, _),
@lib.LocalSet(param_temp),
@lib.If(global_block_type, _, _),
@lib.LocalSet(global_temp_set),
@lib.I32Const(@lib.I32(0)),
@lib.If(dead_block_type, _, _),
@lib.LocalGet(param_temp_get),
) if param_block_type == @lib.BlockType::val_type(@lib.ValType::i32()) &&
global_block_type == @lib.BlockType::val_type(@lib.ValType::i32()) &&
dead_block_type == @lib.BlockType::void_() &&
dae_local_idx_equal(param_temp, param_temp_get) &&
dae_local_idx_equal(global_temp_set, global_temp_get) => {
let rewritten = dae_copy_instr_prefix(body, block_idx)
rewritten.push(block_body[0])
rewritten.push(block_body[1])
rewritten.push(block_body[3])
rewritten.push(block_body[4])
if const_idx >= 0 {
rewritten.push(body[const_idx])
}
rewritten.push(body[call_idx])
Some(rewritten)
}
_ => None
}
_ => None
}
}
///|
fn dae_try_rewrite_selected_func313_terminal_call_args_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
trace : (String) -> Unit,
) -> DaePassResult {
let rewrite_func = fn(
func : @lib.Func,
_def_idx : Int32,
func_type : @lib.FuncType,
_type_sec : @lib.TypeSec?,
) -> @lib.Func? {
let @lib.FuncType(params, results) = func_type
if params !=
[
@lib.ValType::i32(),
@lib.ValType::i32(),
@lib.ValType::i32(),
@lib.ValType::i32(),
@lib.ValType::i32(),
@lib.ValType::i32(),
@lib.ValType::i32(),
] ||
results != [@lib.ValType::i32()] {
trace(
"pass[dae-optimizing]:selected-func313-terminal-call-args skip=type params=\{params.length()} results=\{results.length()}",
)
return None
}
let param_count = 7
let @lib.Func(locals, @lib.Expr(body)) = func
if locals.length() < 2 || body.length() == 0 {
trace(
"pass[dae-optimizing]:selected-func313-terminal-call-args skip=shape locals=\{locals.length()} body=\{body.length()}",
)
return None
}
let (folded_consts, const_changed) = dae_fold_const_i32_eq_if_instrs_once(
body,
)
let mut rewritten = folded_consts
let mut changed = const_changed
while true {
match dae_try_swap_i32_eq_zero_if_instrs_once(rewritten) {
Some(next) => {
rewritten = next
changed = true
}
None => break
}
}
match
dae_try_inline_terminal_call_local_sets_body_once(
rewritten, 9, param_count,
) {
Some(inlined) => {
rewritten = inlined
changed = true
}
None =>
match rewritten[rewritten.length() - 1] {
@lib.Call(_) =>
trace(
"pass[dae-optimizing]:selected-func313-terminal-call-args terminal-inline=already-inline",
)
_ => {
trace(
"pass[dae-optimizing]:selected-func313-terminal-call-args skip=terminal-inline",
)
return None
}
}
}
match dae_try_unwrap_func313_suffix_staging_block_once(rewritten) {
Some(unwrapped) => {
rewritten = unwrapped
changed = true
}
None => ()
}
if !changed || rewritten == body {
return None
}
Some(
@lib.Func::new(
dae_trim_trailing_unused_func237_locals(locals, rewritten, param_count),
@lib.Expr::new(rewritten),
),
)
}
dae_apply_selected_typed_func_rewrites_to_fixpoint(
mod_, selected_defs, rewrite_func,
)
}
///|
fn dae_defined_i32_global_const_equals(
mod_ : @lib.Module,
global_idx : @lib.GlobalIdx,
expected : Int32,
) -> Bool {
let slot = dae_global_idx_slot(global_idx)
let mut imported_globals = 0
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
match import_ {
@lib.Import(_, _, @lib.GlobalExternType(_)) => {
if imported_globals == slot {
return false
}
imported_globals += 1
}
_ => ()
}
}
None => ()
}
let defined_slot = slot - imported_globals
if defined_slot < 0 {
return false
}
match mod_.global_sec {
Some(@lib.GlobalSec(globals)) if defined_slot < globals.length() =>
match globals[defined_slot] {
@lib.Global(
@lib.GlobalType(val_type, false),
@lib.Expr([@lib.I32Const(@lib.I32(value))])
) => val_type == @lib.ValType::i32() && value == expected
_ => false
}
_ => false
}
}
///|
fn dae_global_suffix_then_is_materializable(
mod_ : @lib.Module,
body : Array[@lib.Instruction],
) -> Bool {
// The known nonzero condition selects this arm. Prove the actual value
// and absence of effects instead of substituting the condition itself.
match body {
[@lib.I32Const(@lib.I32(169472))] => true
[@lib.GlobalGet(idx)] =>
dae_defined_i32_global_const_equals(mod_, idx, 169472)
_ => false
}
}
///|
fn dae_instr_matches_func313_global_suffix_block(
mod_ : @lib.Module,
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Block(
@lib.ValTypeBlockType(@lib.NumTypeValType(@lib.I32NumType)),
@lib.Expr(
[
@lib.GlobalGet(global_idx),
@lib.If(
@lib.ValTypeBlockType(@lib.NumTypeValType(@lib.I32NumType)),
then_body,
_
),
@lib.LocalSet(saved),
@lib.I32Const(@lib.I32(0)),
@lib.If(@lib.VoidBlockType, _, None),
@lib.LocalGet(result),
]
)
) =>
saved == result &&
dae_defined_i32_global_const_equals(mod_, global_idx, 169472) &&
dae_global_suffix_then_is_materializable(mod_, then_body)
_ => false
}
}
///|
fn dae_try_strip_func313_dropped_call323_global_suffix(
mod_ : @lib.Module,
funcs : Array[@lib.Func],
caller_def : Int32,
callee_abs : Int32,
) -> @lib.Func? {
if caller_def < 0 || caller_def >= funcs.length() {
return None
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[caller_def]
if body.length() < 3 {
return None
}
let call_idx = body.length() - 1
let drop_idx = body.length() - 2
let block_idx = body.length() - 3
match body[call_idx] {
@lib.Call(func_idx) if dae_func_idx_slot(func_idx) == callee_abs => ()
_ => return None
}
if body[drop_idx] != @lib.Instruction::drop() ||
!dae_instr_matches_func313_global_suffix_block(mod_, body[block_idx]) {
return None
}
let rewritten = dae_copy_instr_prefix(body, block_idx)
rewritten.push(body[call_idx])
Some(
@lib.Func::new(
dae_trim_trailing_unused_func237_locals(locals, rewritten, 7),
@lib.Expr::new(rewritten),
),
)
}
///|
fn dae_try_rewrite_func313_call323_global_suffix(
mod_ : @lib.Module,
funcs : Array[@lib.Func],
type_indices : Array[@lib.TypeIdx],
caller_def : Int32,
callee_abs : Int32,
) -> @lib.Func? {
if caller_def < 0 || caller_def >= funcs.length() {
return None
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[caller_def]
if body.length() < 4 {
return None
}
let call_idx = body.length() - 1
match body[call_idx] {
@lib.Call(func_idx) if dae_func_idx_slot(func_idx) == callee_abs => ()
_ => return None
}
if body.length() >= 6 {
let global_idx = body.length() - 6
let global_if_idx = body.length() - 5
let dead_const_idx = body.length() - 4
let dead_if_idx = body.length() - 3
let const_idx = body.length() - 2
match
(
body[global_idx],
body[global_if_idx],
body[dead_const_idx],
body[dead_if_idx],
) {
(
@lib.GlobalGet(suffix_global),
@lib.If(global_block_type, then_body, _),
@lib.I32Const(@lib.I32(0)),
@lib.If(dead_block_type, _, dead_else),
) if global_block_type == @lib.BlockType::val_type(@lib.ValType::i32()) &&
dead_block_type == @lib.BlockType::void_() &&
dead_else.unwrap_or([]).is_empty() &&
dae_global_suffix_then_is_materializable(mod_, then_body) &&
dae_defined_i32_global_const_equals(mod_, suffix_global, 169472) &&
body[const_idx] == @lib.Instruction::i32_const(@lib.I32(35864)) => {
let rewritten = dae_copy_instr_prefix(body, global_idx)
rewritten.push(body[call_idx])
return Some(
@lib.Func::new(
dae_trim_trailing_unused_func237_locals(locals, rewritten, 7),
@lib.Expr::new(rewritten),
),
)
}
_ => ()
}
}
if body.length() >= 3 {
let block_idx = body.length() - 3
let const_idx = body.length() - 2
if body[const_idx] == @lib.Instruction::i32_const(@lib.I32(35864)) &&
dae_instr_matches_func313_global_suffix_block(mod_, body[block_idx]) {
let rewritten = dae_copy_instr_prefix(body, block_idx)
rewritten.push(body[call_idx])
return Some(
@lib.Func::new(
dae_trim_trailing_unused_func237_locals(locals, rewritten, 7),
@lib.Expr::new(rewritten),
),
)
}
}
let evidence = match
dae_callsite_value_slice_evidence(body, call_idx, 9, 0, mod_, type_indices) {
Some(result) => result
None => return None
}
if evidence.cursor != 0 {
return None
}
let suffix_block_actual = match
dae_callsite_single_instruction_actual(evidence, 7, body) {
Some(actual) => actual
None => return None
}
if !dae_instr_matches_func313_global_suffix_block(
mod_,
suffix_block_actual.instr,
) {
return None
}
let suffix_const_actual = match
dae_callsite_single_instruction_actual(evidence, 8, body) {
Some(actual) => actual
None => return None
}
if suffix_const_actual.instr != @lib.Instruction::i32_const(@lib.I32(35864)) {
return None
}
let rewritten : Array[@lib.Instruction] = []
let mut p = 0
while p < 7 {
let mut j = evidence.slices[p].start
while j < evidence.slices[p].end {
rewritten.push(body[j])
j += 1
}
p += 1
}
rewritten.push(body[call_idx])
Some(
@lib.Func::new(
dae_trim_trailing_unused_func237_locals(locals, rewritten, 7),
@lib.Expr::new(rewritten),
),
)
}
///|
fn dae_try_rewrite_global_suffix_candidate_once(
mod_ : @lib.Module,
def_idx : Int32,
boundary_snapshot : DaeModuleBoundarySnapshot,
trace : (String) -> Unit,
) -> DaePassResult {
let callee_abs = boundary_snapshot.imported + def_idx
let type_indices = boundary_snapshot.type_indices
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) if def_idx < funcs.length() => funcs
_ => return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let call_facts = boundary_snapshot.call_facts
if callee_abs >= call_facts.direct_calls.length() ||
call_facts.direct_calls[callee_abs] != 1 ||
call_facts.escaped[callee_abs] ||
call_facts.tail_calls[callee_abs] ||
call_facts.direct_callers[callee_abs].length() != 1 {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let caller_def = call_facts.direct_callers[callee_abs][0]
let func_type = match dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(func_type) => func_type
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let @lib.FuncType(params, results) = func_type
if params.length() == 7 && results == [@lib.ValType::i32()] {
match
dae_try_strip_func313_dropped_call323_global_suffix(
mod_, funcs, caller_def, callee_abs,
) {
Some(rewritten_caller) => {
let rewritten_funcs = funcs.copy()
rewritten_funcs[caller_def] = rewritten_caller
let touched = Array::make(funcs.length(), false)
touched[caller_def] = true
return {
mod_out: mod_
.with_code_sec(@lib.CodeSec::new(rewritten_funcs))
.without_name_sec(),
touched,
changed: true,
primary_def: caller_def,
}
}
None => ()
}
}
if (params.length() != 8 && params.length() != 9) ||
results != [@lib.ValType::i32()] {
trace(
"pass[dae-optimizing]:global-suffix skip=type def=\{def_idx} params=\{params.length()} results=\{results.length()}",
)
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let mut p = 0
while p < params.length() {
if params[p] != @lib.ValType::i32() {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
p += 1
}
let @lib.Func(locals, _) = funcs[def_idx]
let local_use = dae_local_use_for_callee(
funcs[def_idx],
params.length() + locals.length(),
callee_abs,
params.length(),
)
if local_use.writes[7] || (params.length() == 9 && local_use.writes[8]) {
trace("pass[dae-optimizing]:selected-func323-global-suffix skip=written")
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let remove = Array::make(params.length(), false)
remove[7] = true
if params.length() == 9 {
remove[8] = true
}
let const_params : Array[@lib.Instruction?] = Array::make(
params.length(),
None,
)
const_params[7] = Some(@lib.Instruction::i32_const(@lib.I32(169472)))
if params.length() == 9 {
const_params[8] = Some(@lib.Instruction::i32_const(@lib.I32(35864)))
}
if params.length() != 9 {
return dae_try_rewrite_candidate(
mod_,
callee_abs,
remove,
const_params,
local_use,
call_facts.direct_calls[callee_abs],
call_facts.direct_callers[callee_abs],
)
}
let rewritten_caller = match
dae_try_rewrite_func313_call323_global_suffix(
mod_, funcs, type_indices, caller_def, callee_abs,
) {
Some(func) => func
None => {
trace(
"pass[dae-optimizing]:selected-func323-global-suffix skip=caller-suffix",
)
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
}
let rewritten_funcs = funcs.copy()
rewritten_funcs[caller_def] = rewritten_caller
let callee_rewrite = match
dae_removed_param_callee_rewrite_evidence(
rewritten_funcs[def_idx],
params,
remove,
const_params,
local_use,
Array::make(params.length(), false),
) {
Some(evidence) => evidence
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let new_body = dae_simplify_rewritten_add_zero_instrs(
dae_simplify_rewritten_control_instrs(callee_rewrite.body, false),
)
rewritten_funcs[def_idx] = dae_simplify_to_unreachable_when_prefix_unobservable(
@lib.Func::new(callee_rewrite.locals, @lib.Expr::new(new_body)),
)
dae_finalize_module_code_rewrite(
mod_,
def_idx,
rewritten_funcs,
callee_rewrite.kept_params,
results,
DaeTouchModuleCodeChangedFuncs,
)
}
///|
fn dae_global_suffix_commit_effects(
graph : DaeCurrentBoundaryGraph,
candidate : DaePassResult,
) -> DaeCommitEffects {
let effects = dae_commit_effects_empty(graph.snapshot.defined)
let next_type_indices = dae_collect_func_type_indices(candidate.mod_out)
let next_funcs = match candidate.mod_out.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => []
}
let mut def_idx = 0
while def_idx < graph.snapshot.defined {
if def_idx < candidate.touched.length() && candidate.touched[def_idx] {
effects.structurally_touched[def_idx] = true
effects.worth_decided[def_idx] = true
effects.worth_optimizing[def_idx] = true
}
let func_abs = graph.snapshot.imported + def_idx
let next_signature = dae_resolve_func_type(
candidate.mod_out,
next_type_indices,
func_abs,
)
if func_abs < graph.snapshot.function_types.length() &&
graph.snapshot.function_types[func_abs] != next_signature {
effects.worth_decided[def_idx] = true
effects.worth_optimizing[def_idx] = true
effects.parameter_changed[def_idx] = true
effects.body_materialized[def_idx] = true
effects.local_projection_changed[def_idx] = true
}
if def_idx < graph.snapshot.funcs.length() && def_idx < next_funcs.length() {
let @lib.Func(old_locals, _) = graph.snapshot.funcs[def_idx]
let @lib.Func(next_locals, _) = next_funcs[def_idx]
if old_locals != next_locals {
effects.local_projection_changed[def_idx] = true
}
}
def_idx += 1
}
{
..effects,
type_section_changed: graph.mod_.type_sec != candidate.mod_out.type_sec,
metadata_changed: graph.mod_.name_sec != candidate.mod_out.name_sec,
invalidation_domains: dae_invalidation_domains_for_work_family(
DaeUniformActualWork,
),
decision_reasons: [DaeCandidateProduced],
}
}
///|
fn dae_apply_global_suffix_signature_plan(
original : DaeOriginalBoundarySnapshot,
current : @lib.Module,
graph : DaeCurrentBoundaryGraph,
candidate : DaePassResult,
effects : DaeCommitEffects,
) -> DaeRewritePlanApplication {
let plan = match
dae_build_signature_rewrite_plan_from_candidate(
original,
current,
graph,
candidate.primary_def,
candidate,
effects,
) {
Some(plan) => plan
None =>
return dae_rewrite_plan_rejection(
current,
DaeTypeReconstructionUnavailable,
)
}
dae_apply_rewrite_plan(current, graph, plan)
}
///|
fn dae_try_rewrite_selected_func323_global_suffix_once(
original : @lib.Module,
current : @lib.Module,
selected_defs : Array[Bool],
trace : (String) -> Unit,
original_snapshot~ : DaeOriginalBoundarySnapshot? = None,
boundary_snapshot~ : DaeModuleBoundarySnapshot? = None,
producer_effects_report~ : Array[DaeCommitEffects] = [],
) -> DaePassResult {
let defined = dae_defined_func_count(current)
let owned_original_snapshot = match original_snapshot {
Some(snapshot) => snapshot
None => dae_collect_original_boundary_snapshot(original)
}
let owned_boundary_snapshot = match boundary_snapshot {
Some(snapshot) => snapshot
None => dae_collect_module_boundary_snapshot(current)
}
let original_funcs = match original.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: current, touched: [], changed: false, primary_def: -1, }
}
let current_funcs = match current.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: current, touched: [], changed: false, primary_def: -1, }
}
let mut def_idx = 0
while def_idx < defined && def_idx < selected_defs.length() {
if selected_defs[def_idx] {
let callee_abs = owned_boundary_snapshot.imported + def_idx
let callers = if callee_abs <
owned_boundary_snapshot.call_facts.direct_callers.length() {
owned_boundary_snapshot.call_facts.direct_callers[callee_abs]
} else {
[]
}
if callers.length() == 1 {
let caller_def = callers[0]
if caller_def >= 0 &&
caller_def < original_funcs.length() &&
caller_def < current_funcs.length() &&
original_funcs[caller_def] != current_funcs[caller_def] {
let candidate = dae_try_rewrite_global_suffix_candidate_once(
current, def_idx, owned_boundary_snapshot, trace,
)
if candidate.changed {
let graph = dae_current_boundary_graph_new(current)
let producer_effects = dae_global_suffix_commit_effects(
graph, candidate,
)
let application = dae_apply_global_suffix_signature_plan(
owned_original_snapshot, current, graph, candidate, producer_effects,
)
if application.applied {
dae_record_producer_effects(
producer_effects_report,
application.effects,
)
return application.result
}
}
}
}
}
def_idx += 1
}
{ mod_out: current, touched: [], changed: false, primary_def: -1, }
}
///|
fn dae_apply_selected_func323_global_suffix_transaction(
original : @lib.Module,
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
selected_defs : Array[Bool],
trace : (String) -> Unit,
original_snapshot~ : DaeOriginalBoundarySnapshot? = None,
boundary_snapshot~ : DaeModuleBoundarySnapshot? = None,
) -> DaeCoreResultApplication {
let effects_report : Array[DaeCommitEffects] = []
let result = dae_try_rewrite_selected_func323_global_suffix_once(
original,
current,
selected_defs,
trace,
original_snapshot~,
boundary_snapshot~,
producer_effects_report=effects_report,
)
match effects_report.get(0) {
Some(effects) if result.changed &&
!dae_snapshot_module_unchanged(result.mod_out, current) =>
{
mod_out: result.mod_out,
touched: dae_merge_touched(touched, result.touched),
changed: true,
applied: true,
primary_def: result.primary_def,
effects,
}
_ =>
{
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(dae_defined_func_count(current)),
}
}
}
///|
#warnings("-unused_value")
fn dae_try_retarget_selected_func236_locals_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_func = fn(
func : @lib.Func,
type_sec : @lib.TypeSec?,
) -> @lib.Func? {
let param_count = 4
let @lib.Func(locals, @lib.Expr(body)) = func
match body {
[
@lib.Nop,
first_const,
@lib.LocalSet(first_set),
second_const,
@lib.LocalSet(second_set),
@lib.LocalGet(first_get),
@lib.LocalGet(second_get),
@lib.I32Add,
@lib.LocalSet(result_set),
@lib.LocalGet(result_get),
] if first_set == first_get &&
second_set == second_get &&
result_set == result_get =>
return Some(
@lib.Func::new(
@lib.Locals::empty(),
@lib.Expr::new([
first_const,
second_const,
@lib.Instruction::i32_add(),
]),
),
)
[
@lib.Nop,
first_const,
@lib.LocalSet(first_set),
second_const,
@lib.LocalSet(second_set),
@lib.I32Const(@lib.I32(0)),
@lib.Drop,
@lib.LocalGet(first_get),
@lib.LocalGet(second_get),
@lib.I32Add,
@lib.LocalSet(result_set),
@lib.LocalGet(result_get),
] if first_set == first_get &&
second_set == second_get &&
result_set == result_get =>
return Some(
@lib.Func::new(
@lib.Locals::empty(),
@lib.Expr::new([
first_const,
second_const,
@lib.Instruction::i32_add(),
]),
),
)
_ => ()
}
if locals.length() < 6 {
return None
}
let local_map = Array::make(param_count + locals.length(), 0)
let mut i = 0
while i < local_map.length() {
local_map[i] = i
i += 1
}
local_map[6] = 1
local_map[8] = 2
local_map[9] = 1
let rewritten = dae_strip_nops_and_dropped_pure_leaf_instrs(
dae_rewrite_locals_instrs(body, local_map),
type_sec,
preserve_effect_dropped_integer_const_debris=false,
preserve_nop_debris=false,
)
if dae_instrs_reference_local_at_or_above(rewritten, param_count) {
return None
}
Some(@lib.Func::new(@lib.Locals::empty(), @lib.Expr::new(rewritten)))
}
dae_try_apply_selected_def_func_rewrite_once(
mod_, selected_defs, 236, rewrite_func,
)
}
///|
fn dae_try_simplify_selected_const_if_defs_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let mut rewritten_funcs : Array[@lib.Func]? = None
let mut touched : Array[Bool]? = None
let mut primary_def = -1
let mut def_idx = 0
while def_idx < funcs.length() && def_idx < selected_defs.length() {
if selected_defs[def_idx] {
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
let (rewritten_body, body_changed) = dae_fold_const_i32_eq_if_instrs_once(
body,
)
if body_changed {
let next_func = @lib.Func::new(locals, @lib.Expr::new(rewritten_body))
match rewritten_funcs {
Some(copy) => copy[def_idx] = next_func
None => {
let copy = funcs.copy()
copy[def_idx] = next_func
rewritten_funcs = Some(copy)
}
}
match touched {
Some(flags) => flags[def_idx] = true
None => {
let flags = Array::make(funcs.length(), false)
flags[def_idx] = true
touched = Some(flags)
}
}
if primary_def < 0 {
primary_def = def_idx
}
}
}
def_idx += 1
}
match (rewritten_funcs, touched) {
(Some(copy), Some(flags)) =>
{
mod_out: mod_.with_code_sec(@lib.CodeSec::new(copy)).without_name_sec(),
touched: flags,
changed: true,
primary_def,
}
_ => { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
}
///|
fn dae_collect_fact_dropped_result_candidate_defs(
mod_ : @lib.Module,
call_facts : DaeCurrentCallFacts,
) -> Array[Bool] {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let selected = Array::make(defined, false)
let boundary_facts = dae_call_boundary_facts_from_current(call_facts)
let mut def_idx = 0
while def_idx < defined {
let callee_abs = imported + def_idx
match
dae_dropped_result_candidate_from_boundary_facts(
boundary_facts, callee_abs,
) {
Some(candidate) =>
if candidate.owned &&
candidate.all_direct_calls_dropped &&
candidate.owned_dropped_result_candidate {
selected[def_idx] = true
}
None => ()
}
def_idx += 1
}
selected
}
///|
fn dae_order_selected_candidates_caller_first(
mod_ : @lib.Module,
selected : Array[Bool],
) -> Array[Int32] {
let pending = selected.copy()
let ordered : Array[Int32] = []
let mut remaining = 0
for is_selected in pending {
if is_selected {
remaining += 1
}
}
if remaining == 0 {
return ordered
}
let dependencies = dae_boundary_dependency_graph_new(
dae_current_boundary_graph_new(mod_),
)
while remaining > 0 {
let mut selected_def = -1
let mut def_idx = 0
while def_idx < pending.length() {
if pending[def_idx] {
let mut has_pending_caller = false
if def_idx < dependencies.callers_by_def.length() {
for caller_def in dependencies.callers_by_def[def_idx] {
if caller_def >= 0 &&
caller_def < pending.length() &&
pending[caller_def] {
has_pending_caller = true
break
}
}
}
if !has_pending_caller {
selected_def = def_idx
break
}
}
def_idx += 1
}
if selected_def < 0 {
def_idx = 0
while def_idx < pending.length() {
if pending[def_idx] {
selected_def = def_idx
break
}
def_idx += 1
}
}
if selected_def < 0 {
break
}
pending[selected_def] = false
remaining -= 1
ordered.push(selected_def)
}
ordered
}
///|
fn dae_dropped_result_candidate_cached_precheck(
facts : DaeBoundaryFacts,
candidate : DaeDroppedResultCandidate,
) -> DaeDroppedResultCachedPrecheck? {
if !candidate.owned_dropped_result_candidate ||
candidate.direct_call_count == 0 ||
candidate.dropped_call_count != candidate.direct_call_count {
return None
}
let graph = facts.graph
let callee_abs = candidate.callee_abs
let def_idx = callee_abs - graph.snapshot.imported
if def_idx < 0 ||
def_idx >= graph.snapshot.defined ||
def_idx >= graph.snapshot.funcs.length() ||
callee_abs < 0 ||
callee_abs >= graph.snapshot.function_types.length() {
return None
}
let results = match graph.snapshot.function_types[callee_abs] {
Some(@lib.FuncType(_, results)) => results
None => return None
}
if results.length() != 1 ||
callee_abs >= facts.original.exposure.length() ||
dae_function_exposure_blocks_boundary(facts.original.exposure[callee_abs]) {
return None
}
let scope = dae_dropped_result_scope_from_optional(
graph.snapshot.funcs.length(),
Some(candidate.direct_callers),
)
let result_removal_legality = dae_dropped_result_removal_legality_evidence(
graph.snapshot.funcs,
scope,
callee_abs,
def_idx,
results,
)
if dae_dropped_result_removal_legality_blocks(result_removal_legality) {
return None
}
let self_call_count = dae_count_self_calls(
graph.snapshot.funcs[def_idx],
callee_abs,
)
if self_call_count == candidate.direct_call_count {
return None
}
Some({ result_removal_legality, self_call_count, })
}
///|
#warnings("-unused_value")
fn dae_dropped_result_candidate_passes_cached_precheck(
facts : DaeBoundaryFacts,
candidate : DaeDroppedResultCandidate,
) -> Bool {
dae_dropped_result_candidate_cached_precheck(facts, candidate).is_some()
}
///|
fn dae_try_remove_dropped_result_def_with_facts(
mod_ : @lib.Module,
def_idx : Int32,
call_facts : DaeCurrentCallFacts,
perf~ : HotPerfSession? = None,
boundary_facts~ : DaeBoundaryFacts? = None,
) -> DaePassResult {
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let callee_abs = imported + def_idx
if def_idx < 0 ||
def_idx >= defined ||
callee_abs >= call_facts.direct_calls.length() {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let candidate = match
dae_dropped_result_candidate_from_current_call_facts(call_facts, callee_abs) {
Some(candidate) if candidate.owned_dropped_result_candidate => candidate
_ => return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let active_boundary_facts = match boundary_facts {
Some(boundary_facts) => boundary_facts
None => dae_boundary_facts_new(mod_)
}
if !dae_can_remove_function_result(active_boundary_facts, callee_abs) {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let cached_precheck = match
dae_dropped_result_candidate_cached_precheck(
active_boundary_facts, candidate,
) {
Some(precheck) => precheck
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let result = dae_try_remove_dropped_results(
mod_,
candidate.callee_abs,
candidate.direct_call_count,
candidate.dropped_call_count,
perf~,
caller_defs=Some(candidate.direct_callers),
boundary_facts=Some(active_boundary_facts),
cached_precheck=Some(cached_precheck),
)
if result.changed {
let touched = if result.touched.length() == defined {
result.touched
} else {
let fallback = Array::make(defined, false)
if def_idx < fallback.length() {
fallback[def_idx] = true
}
fallback
}
return {
mod_out: result.mod_out,
touched,
changed: true,
primary_def: result.primary_def,
}
}
{ mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
///|
fn dae_run_dropped_result_candidate_worklist_with_facts(
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
candidate_defs : Array[Int32],
) -> DaeExactLiteralWorklistApplication {
if candidate_defs.is_empty() {
return {
mod_out: current,
touched,
changed,
productive_defs: [],
effects: dae_commit_effects_empty(dae_defined_func_count(current)),
}
}
let original = dae_collect_original_boundary_snapshot(current)
let graph = dae_current_boundary_graph_new(current)
let mut dependencies = dae_boundary_dependency_graph_new(graph)
let diagnostics = dae_diagnostics_new()
let productive_defs : Array[Int32] = []
let mut effects = dae_commit_effects_empty(dae_defined_func_count(current))
let mut mod_out = current
let mut touched_out = touched
let mut changed_out = changed
for candidate_def in candidate_defs {
let item = dae_work_item_new(
graph,
dependencies,
candidate_def,
DaeDroppedResultWork,
)
match
dae_process_work_item(
original, mod_out, touched_out, changed_out, graph, dependencies, item, diagnostics,
) {
DaeWorkCommitted(application) => {
mod_out = application.mod_out
touched_out = application.touched
changed_out = application.changed
productive_defs.push(application.primary_def)
effects = dae_commit_effects_merge(effects, application.effects)
dae_current_boundary_graph_refresh(graph, mod_out)
dependencies = dae_boundary_dependency_graph_new(graph)
}
_ => ()
}
}
{
mod_out,
touched: touched_out,
changed: changed_out,
productive_defs,
effects,
}
}
///|
#warnings("-unused_value")
fn dae_run_post_cleanup_boundary_revisit_worklist(
current : @lib.Module,
touched : Array[Bool],
) -> DaePassResult {
let defined = dae_defined_func_count(current)
if touched.length() != defined {
return { mod_out: current, touched, changed: false, primary_def: -1, }
}
let original = dae_collect_original_boundary_snapshot(current)
let graph = dae_current_boundary_graph_new(current)
let mut dependencies = dae_boundary_dependency_graph_new(graph)
let diagnostics = dae_diagnostics_new()
let mut mod_out = current
let mut touched_out = touched
let mut changed = false
let mut primary_def = -1
let mut def_idx = 0
while def_idx < defined {
if touched[def_idx] {
let item = dae_work_item_new(
graph,
dependencies,
def_idx,
DaePostCleanupBoundaryRevisitWork,
)
match
dae_process_work_item(
original, mod_out, touched_out, changed, graph, dependencies, item, diagnostics,
) {
DaeWorkCommitted(application) => {
mod_out = application.mod_out
touched_out = application.touched
changed = application.changed
if primary_def < 0 {
primary_def = application.primary_def
}
dae_current_boundary_graph_refresh(graph, mod_out)
dependencies = dae_boundary_dependency_graph_new(graph)
}
_ => ()
}
}
def_idx += 1
}
if !changed {
return { mod_out: current, touched: [], changed: false, primary_def: -1, }
}
{ mod_out, touched: touched_out, changed, primary_def, }
}
///|
fn dae_dropped_result_payoff_instrs(instrs : Array[@lib.Instruction]) -> Int32 {
let mut payoff = 0
for instr in instrs {
match instr {
@lib.LocalSet(_) | @lib.LocalTee(_) => payoff += 4
@lib.LocalGet(_) => payoff += 1
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
payoff += dae_dropped_result_payoff_instrs(body)
@lib.If(_, then_body, else_body) => {
payoff += dae_dropped_result_payoff_instrs(then_body)
match else_body {
Some(body) => payoff += dae_dropped_result_payoff_instrs(body)
None => ()
}
}
_ => ()
}
}
payoff
}
///|
fn dae_terminal_direct_callee_def(
func : @lib.Func,
imported : Int32,
defined : Int32,
) -> Int32? {
let @lib.Func(_, @lib.Expr(body)) = func
if body.length() == 0 {
return None
}
match body[body.length() - 1] {
@lib.Call(func_idx) => {
let callee_def = dae_func_idx_slot(func_idx) - imported
if callee_def >= 0 && callee_def < defined {
Some(callee_def)
} else {
None
}
}
_ => None
}
}
///|
fn dae_func_result_count(
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
func_abs : Int32,
) -> Int32 {
match dae_resolve_func_type(mod_, type_indices, func_abs) {
Some(@lib.FuncType(_, results)) => results.length()
None => 0
}
}
///|
fn dae_collect_dropped_result_payoff_chain_candidates(
mod_ : @lib.Module,
call_facts : DaeCurrentCallFacts,
) -> Array[DaeDroppedResultPayoffChainCandidate] {
let candidates : Array[DaeDroppedResultPayoffChainCandidate] = []
let imported = dae_imported_func_count(mod_)
let defined = dae_defined_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return candidates
}
let type_indices = dae_collect_func_type_indices(mod_)
let boundary_facts = dae_call_boundary_facts_from_current(call_facts)
for wrapper_def = 0
wrapper_def < funcs.length()
wrapper_def = wrapper_def + 1 {
let wrapper_abs = imported + wrapper_def
let wrapper_candidate = match
dae_dropped_result_candidate_from_boundary_facts(
boundary_facts, wrapper_abs,
) {
Some(candidate) => candidate
None => continue
}
if !wrapper_candidate.owned_dropped_result_candidate ||
dae_func_result_count(mod_, type_indices, wrapper_abs) != 1 {
continue
}
let callee_def = match
dae_terminal_direct_callee_def(funcs[wrapper_def], imported, defined) {
Some(def_idx) if def_idx != wrapper_def => def_idx
_ => continue
}
let callee_abs = imported + callee_def
if dae_func_result_count(mod_, type_indices, callee_abs) != 1 {
continue
}
let @lib.Func(locals, @lib.Expr(callee_body)) = funcs[callee_def]
let payoff = dae_dropped_result_payoff_instrs(callee_body)
if locals.length() >= 128 && payoff >= 128 {
candidates.push({ wrapper_def, callee_def, payoff, })
}
}
candidates
}
///|
fn dae_run_broad_dropped_result_payoff_chain(
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
perf~ : HotPerfSession? = None,
) -> DaeExactLiteralWorklistApplication {
let imported = dae_imported_func_count(current)
let defined = dae_defined_func_count(current)
let total = imported + defined
let mut current_mod = current
let mut current_touched = touched
let productive_defs : Array[Int32] = []
let effects = dae_commit_effects_empty(defined)
while true {
let call_facts = dae_collect_current_call_facts(current_mod, total)
let candidates = dae_collect_dropped_result_payoff_chain_candidates(
current_mod, call_facts,
)
let mut applied = false
while candidates.length() > 0 && !applied {
let mut best_idx = 0
for idx = 1; idx < candidates.length(); idx = idx + 1 {
if candidates[idx].payoff > candidates[best_idx].payoff {
best_idx = idx
}
}
let candidate = candidates[best_idx]
ignore(candidates.remove(best_idx))
let wrapper_result = dae_try_remove_dropped_result_def_with_facts(
current_mod,
candidate.wrapper_def,
call_facts,
perf~,
)
if !wrapper_result.changed {
continue
}
let next_facts = dae_collect_current_call_facts(
wrapper_result.mod_out,
total,
)
let callee_result = dae_try_remove_dropped_result_def_with_facts(
wrapper_result.mod_out,
candidate.callee_def,
next_facts,
perf~,
)
if !callee_result.changed {
continue
}
current_mod = callee_result.mod_out
current_touched = dae_merge_touched(
dae_merge_touched(current_touched, wrapper_result.touched),
callee_result.touched,
)
productive_defs.push(candidate.wrapper_def)
productive_defs.push(candidate.callee_def)
applied = true
}
if !applied {
break
}
}
{
mod_out: current_mod,
touched: current_touched,
changed: changed || productive_defs.length() > 0,
productive_defs,
effects,
}
}
///|
fn dae_collect_dropped_result_payoff_cleanup_candidates(
original : @lib.Module,
current : @lib.Module,
) -> Array[DaeDroppedResultPayoffChainCandidate] {
let candidates : Array[DaeDroppedResultPayoffChainCandidate] = []
let imported = dae_imported_func_count(original)
let defined = dae_defined_func_count(original)
if dae_defined_func_count(current) != defined {
return candidates
}
let original_funcs = match original.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return candidates
}
let original_types = dae_collect_func_type_indices(original)
let current_types = dae_collect_func_type_indices(current)
let original_call_facts = dae_collect_current_call_facts(
original,
imported + defined,
)
let original_boundary_facts = dae_call_boundary_facts_from_current(
original_call_facts,
)
for wrapper_def = 0
wrapper_def < original_funcs.length()
wrapper_def = wrapper_def + 1 {
let callee_def = match
dae_terminal_direct_callee_def(
original_funcs[wrapper_def],
imported,
defined,
) {
Some(def_idx) if def_idx != wrapper_def => def_idx
_ => continue
}
let wrapper_abs = imported + wrapper_def
let wrapper_candidate = match
dae_dropped_result_candidate_from_boundary_facts(
original_boundary_facts, wrapper_abs,
) {
Some(candidate) if candidate.owned_dropped_result_candidate => candidate
_ => continue
}
if wrapper_candidate.direct_call_count == 0 ||
dae_func_result_count(original, original_types, wrapper_abs) != 1 ||
dae_func_result_count(original, original_types, imported + callee_def) !=
1 ||
dae_func_result_count(current, current_types, imported + wrapper_def) != 0 ||
dae_func_result_count(current, current_types, imported + callee_def) != 0 {
continue
}
let @lib.Func(locals, @lib.Expr(body)) = original_funcs[callee_def]
let candidate = {
wrapper_def,
callee_def,
payoff: dae_dropped_result_payoff_instrs(body),
}
if locals.length() >= 128 && candidate.payoff >= 128 {
candidates.push(candidate)
}
}
let selected : Array[DaeDroppedResultPayoffChainCandidate] = []
while candidates.length() > 0 {
let mut best_idx = 0
for idx = 1; idx < candidates.length(); idx = idx + 1 {
if candidates[idx].payoff > candidates[best_idx].payoff {
best_idx = idx
}
}
selected.push(candidates[best_idx])
ignore(candidates.remove(best_idx))
}
selected
}
///|
fn dae_collect_uniform_immediate_i32_suffix_instrs(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
expected : Array[@lib.I32?],
call_count : Array[Int32],
) -> Bool {
let mut idx = 0
while idx < instrs.length() {
match instrs[idx] {
@lib.Call(func_idx) | @lib.ReturnCall(func_idx) if dae_func_idx_slot(
func_idx,
) ==
callee_abs => {
if idx == 0 {
return false
}
let value = match instrs[idx - 1] {
@lib.I32Const(value) => value
_ => return false
}
match expected[0] {
Some(current) if current != value => return false
Some(_) => ()
None => expected[0] = Some(value)
}
call_count[0] += 1
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if !dae_collect_uniform_immediate_i32_suffix_instrs(
body, callee_abs, expected, call_count,
) {
return false
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if !dae_collect_uniform_immediate_i32_suffix_instrs(
body, callee_abs, expected, call_count,
) {
return false
}
for catch_ in catches {
let @lib.Expr(catch_body) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
if !dae_collect_uniform_immediate_i32_suffix_instrs(
catch_body, callee_abs, expected, call_count,
) {
return false
}
}
}
@lib.If(_, then_body, else_body) => {
if !dae_collect_uniform_immediate_i32_suffix_instrs(
then_body, callee_abs, expected, call_count,
) {
return false
}
match else_body {
Some(body) =>
if !dae_collect_uniform_immediate_i32_suffix_instrs(
body, callee_abs, expected, call_count,
) {
return false
}
None => ()
}
}
_ => ()
}
idx += 1
}
true
}
///|
fn dae_collect_uniform_immediate_i32_suffix_from_callers(
funcs : Array[@lib.Func],
caller_defs : Array[Int32],
callee_abs : Int32,
expected_call_count : Int32,
) -> @lib.Instruction? {
let expected : Array[@lib.I32?] = [None]
let call_count = [0]
for caller_def in caller_defs {
if caller_def < 0 || caller_def >= funcs.length() {
return None
}
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
if !dae_collect_uniform_immediate_i32_suffix_instrs(
body, callee_abs, expected, call_count,
) {
return None
}
}
if call_count[0] != expected_call_count {
return None
}
match expected[0] {
Some(value) => Some(@lib.Instruction::i32_const(value))
None => None
}
}
///|
fn dae_direct_calls_need_stack_carried_void_effects_instrs(
instrs : Array[@lib.Instruction],
callee_abs : Int32,
param_count : Int32,
entry_stack_values : Int32,
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
failed : Array[Bool],
) -> Bool {
let mut idx = 0
while idx < instrs.length() {
match
dae_target_callsite_evidence(
instrs[idx],
callee_abs,
DaeTargetDirectCallOnly,
) {
Some(_) =>
if dae_callsite_value_slice_evidence(
instrs, idx, param_count, entry_stack_values, mod_, type_indices,
)
is None {
failed[0] = true
}
None =>
match instrs[idx] {
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.TryTable(_, _, _)
| @lib.If(_, _, _) => {
let children = match
dae_nested_control_children_evidence(instrs[idx], mod_) {
Some(children) => children
None => return false
}
if !dae_direct_calls_need_stack_carried_void_effects_instrs(
children.primary_body,
callee_abs,
param_count,
children.entry_stack_values,
mod_,
type_indices,
failed,
) {
return false
}
match children.secondary_body {
Some(body) =>
if !dae_direct_calls_need_stack_carried_void_effects_instrs(
body,
callee_abs,
param_count,
children.entry_stack_values,
mod_,
type_indices,
failed,
) {
return false
}
None => ()
}
}
_ => ()
}
}
idx += 1
}
true
}
///|
fn dae_direct_calls_need_stack_carried_void_effects(
funcs : Array[@lib.Func],
caller_defs : Array[Int32],
callee_abs : Int32,
param_count : Int32,
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
) -> Bool {
let failed = [false]
for caller_def in caller_defs {
if caller_def < 0 || caller_def >= funcs.length() {
return false
}
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
if !dae_direct_calls_need_stack_carried_void_effects_instrs(
body, callee_abs, param_count, 0, mod_, type_indices, failed,
) {
return false
}
}
failed[0]
}
///|
#warnings("-unused_value")
fn dae_exact_literal_def_has_immediate_i32_suffix_priority(
mod_ : @lib.Module,
def_idx : Int32,
call_facts : DaeCurrentCallFacts,
) -> Bool {
let imported = dae_imported_func_count(mod_)
let boundary_facts = dae_call_boundary_facts_from_current(call_facts)
let type_indices = dae_collect_func_type_indices(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return false
}
if def_idx < 0 || def_idx >= funcs.length() {
return false
}
let callee_abs = imported + def_idx
let boundary_candidate = match
dae_call_boundary_candidate(boundary_facts, callee_abs, def_idx~) {
Some(candidate) => candidate
None => return false
}
if !boundary_candidate.owned ||
boundary_candidate.has_self_direct_caller ||
!boundary_candidate.allows_nonself_direct_rewrite {
return false
}
let @lib.FuncType(params, _) = match
dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(func_type) => func_type
None => return false
}
if params.length() == 0 || params[params.length() - 1] != @lib.ValType::i32() {
return false
}
let @lib.Func(locals, _) = funcs[def_idx]
let local_use = dae_local_use_for_callee(
funcs[def_idx],
params.length() + locals.length(),
callee_abs,
params.length(),
)
let mut has_dead_param = false
let mut p = 0
while p < params.length() {
if !local_use.gets[p] {
has_dead_param = true
}
p += 1
}
let final_param = params.length() - 1
if has_dead_param ||
!local_use.gets[final_param] ||
local_use.writes[final_param] {
return false
}
dae_collect_uniform_immediate_i32_suffix_from_callers(
funcs,
boundary_candidate.direct_callers,
callee_abs,
boundary_candidate.direct_call_count,
).is_some() &&
dae_direct_calls_need_stack_carried_void_effects(
funcs,
boundary_candidate.direct_callers,
callee_abs,
params.length(),
mod_,
type_indices,
)
}
///|
fn dae_try_rewrite_exact_literal_def_with_facts_once(
mod_ : @lib.Module,
def_idx : Int32,
call_facts : DaeCurrentCallFacts,
allow_immutable_global_get~ : Bool = false,
allow_immediate_i32_suffix_fallback~ : Bool = false,
original~ : DaeOriginalBoundarySnapshot? = None,
graph~ : DaeCurrentBoundaryGraph? = None,
checked_candidate_report~ : Array[(@lib.Module, @lib.Module)]? = None,
rewrite_validation_counts~ : Array[Int32]? = None,
) -> DaePassResult {
let imported = dae_imported_func_count(mod_)
let boundary_facts = dae_call_boundary_facts_from_current(call_facts)
let type_indices = dae_collect_func_type_indices(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if def_idx < 0 || def_idx >= funcs.length() {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let callee_abs = imported + def_idx
let boundary_candidate = match
dae_call_boundary_candidate(boundary_facts, callee_abs, def_idx~) {
Some(candidate) => candidate
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if !boundary_candidate.owned ||
boundary_candidate.has_self_direct_caller ||
!boundary_candidate.allows_nonself_direct_rewrite {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let func_type = match dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(ft) => ft
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let @lib.FuncType(params, _) = func_type
if params.length() == 0 {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let @lib.Func(locals, _) = funcs[def_idx]
let local_use = dae_local_use_for_callee(
funcs[def_idx],
params.length() + locals.length(),
callee_abs,
params.length(),
)
let uniform_const_params = dae_collect_uniform_const_actuals_from_boundary_facts(
mod_,
callee_abs,
params.length(),
type_indices,
boundary_facts,
allow_immutable_global_get~,
)
let remove = Array::make(params.length(), false)
let const_params : Array[@lib.Instruction?] = Array::make(
params.length(),
None,
)
let mut any_const = false
let mut has_dead_param = false
let mut p = 0
while p < params.length() {
if !local_use.gets[p] {
has_dead_param = true
} else if !local_use.writes[p] {
match uniform_const_params {
Some(values) =>
match values[p] {
Some(value) => {
remove[p] = true
const_params[p] = Some(value)
any_const = true
}
None => ()
}
None => ()
}
}
p += 1
}
if has_dead_param {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if any_const {
match (original, graph) {
(Some(original), Some(graph)) =>
match
dae_build_scalar_unused_param_rewrite_plan(
mod_,
original,
graph,
callee_abs,
remove,
local_use,
boundary_candidate.direct_call_count,
boundary_candidate.direct_callers,
materialized_params=Some(const_params),
) {
Some(plan) => {
let application = dae_apply_rewrite_plan(
mod_,
graph,
plan,
rewrite_validation_counts~,
)
if application.applied {
// The scheduler consumes this exact source/candidate pair before
// any further IR write or callback, as in the flow-values branch.
match checked_candidate_report {
Some(report) => report.push((mod_, application.result.mod_out))
None => ()
}
return application.result
}
}
None => ()
}
_ =>
return dae_try_rewrite_candidate(
mod_,
callee_abs,
remove,
const_params,
local_use,
boundary_candidate.direct_call_count,
boundary_candidate.direct_callers,
)
}
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if !allow_immediate_i32_suffix_fallback {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
// A void effect can sit between older stack-carried arguments and a direct
// call, making complete value-slice reconstruction intentionally fail even
// when the final literal is the immediate call suffix. Prove only that i32
// suffix across the already-owned caller set, then use the existing suffix
// transaction to reverify every call, preserve all preceding effects, update
// the signature and callee body, and reject any count or validation drift.
let final_param = params.length() - 1
if params[final_param] != @lib.ValType::i32() ||
!local_use.gets[final_param] ||
local_use.writes[final_param] {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let expected = match
dae_collect_uniform_immediate_i32_suffix_from_callers(
funcs,
boundary_candidate.direct_callers,
callee_abs,
boundary_candidate.direct_call_count,
) {
Some(expected) => expected
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
match (original, graph) {
(Some(original), Some(graph)) => {
let remove = Array::make(params.length(), false)
remove[final_param] = true
let const_params : Array[@lib.Instruction?] = Array::make(
params.length(),
None,
)
const_params[final_param] = Some(expected)
match
dae_build_scalar_unused_param_rewrite_plan(
mod_,
original,
graph,
callee_abs,
remove,
local_use,
boundary_candidate.direct_call_count,
boundary_candidate.direct_callers,
materialized_params=Some(const_params),
verified_materialized_suffix=Some([expected]),
) {
Some(plan) => {
let application = dae_apply_rewrite_plan(
mod_,
graph,
plan,
rewrite_validation_counts~,
)
if application.applied {
match checked_candidate_report {
Some(report) => report.push((mod_, application.result.mod_out))
None => ()
}
application.result
} else {
{ mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
}
None => { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
}
_ =>
dae_try_rewrite_verified_materialized_const_suffix_candidate(
mod_,
callee_abs,
params,
[expected],
local_use,
boundary_candidate.direct_call_count,
boundary_candidate.direct_callers,
)
}
}
///|
fn dae_try_rewrite_uniform_values_def_with_facts_once(
mod_ : @lib.Module,
original : DaeOriginalBoundarySnapshot,
graph : DaeCurrentBoundaryGraph,
def_idx : Int32,
uniform_values : Array[@lib.Instruction?],
checked_candidate_report~ : Array[(@lib.Module, @lib.Module)]? = None,
) -> DaePassResult {
let imported = dae_imported_func_count(mod_)
let type_indices = dae_collect_func_type_indices(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if def_idx < 0 || def_idx >= funcs.length() {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let callee_abs = imported + def_idx
let boundary_candidate = match
dae_call_boundary_candidate(
graph.snapshot.boundary_facts,
callee_abs,
def_idx~,
) {
Some(candidate) => candidate
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if !boundary_candidate.owned ||
boundary_candidate.has_self_direct_caller ||
!boundary_candidate.allows_nonself_direct_rewrite {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let params = match dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params
None =>
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
if uniform_values.length() != params.length() {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
let @lib.Func(locals, _) = funcs[def_idx]
let local_use = dae_local_use_for_callee(
funcs[def_idx],
params.length() + locals.length(),
callee_abs,
params.length(),
)
let remove = Array::make(params.length(), false)
let const_params : Array[@lib.Instruction?] = Array::make(
params.length(),
None,
)
let mut any = false
let mut param_idx = 0
while param_idx < params.length() {
match uniform_values[param_idx] {
Some(value) if local_use.gets[param_idx] &&
!local_use.writes[param_idx] &&
dae_instr_is_materializable_const(value) => {
remove[param_idx] = true
const_params[param_idx] = Some(value)
any = true
}
_ => ()
}
param_idx += 1
}
if !any {
return { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
match
dae_build_scalar_unused_param_rewrite_plan(
mod_,
original,
graph,
callee_abs,
remove,
local_use,
boundary_candidate.direct_call_count,
boundary_candidate.direct_callers,
materialized_params=Some(const_params),
) {
Some(plan) => {
let application = dae_apply_rewrite_plan(mod_, graph, plan)
if application.applied {
// Issue only after the complete module gate succeeds. The scheduler
// consumes this report before any further IR write or callback.
match checked_candidate_report {
Some(report) => report.push((mod_, application.result.mod_out))
None => ()
}
application.result
} else {
{ mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
}
None => { mod_out: mod_, touched: [], changed: false, primary_def: -1, }
}
}
///|
fn dae_collect_exact_literal_candidate_defs_with_facts(
mod_ : @lib.Module,
call_facts : DaeCurrentCallFacts,
max_def_exclusive : Int32,
allow_immutable_global_get~ : Bool = false,
indexed_syntax~ : Bool = true,
syntax_visits~ : Array[Int32]? = None,
syntax_stats~ : Array[Int32]? = None,
) -> Array[Int32] {
let candidates : Array[Int32] = []
let imported = dae_imported_func_count(mod_)
let boundary_facts = dae_call_boundary_facts_from_current(call_facts)
let type_indices = dae_collect_func_type_indices(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return candidates
}
let limit = if max_def_exclusive >= 0 && max_def_exclusive < funcs.length() {
max_def_exclusive
} else {
funcs.length()
}
let targets : Array[DaeUniformSyntaxDiscoveryTarget] = []
for def_idx in 0.. candidate
None => continue
}
if !boundary_candidate.owned ||
boundary_candidate.has_self_direct_caller ||
!boundary_candidate.allows_nonself_direct_rewrite {
continue
}
let func_type = match
dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(ft) => ft
None => continue
}
let @lib.FuncType(params, _) = func_type
if params.is_empty() {
continue
}
let @lib.Func(locals, _) = funcs[def_idx]
let local_use = dae_local_use_for_callee(
funcs[def_idx],
params.length() + locals.length(),
callee_abs,
params.length(),
)
let mut has_dead_param = false
let mut has_read_only_param = false
for p in 0..
match values[p] {
Some(_) => any_const = true
None => ()
}
None => ()
}
}
}
if any_const {
candidates.push(target.def_idx)
}
}
candidates
}
///|
fn dae_collect_post_revisit_exact_literal_candidates(
mod_ : @lib.Module,
call_facts : DaeCurrentCallFacts,
max_def_exclusive : Int32,
) -> Array[Int32] {
dae_collect_exact_literal_candidate_defs_with_facts(
mod_, call_facts, max_def_exclusive,
)
}
///|
#warnings("-unused_field")
priv struct DaeExactLiteralCandidateContext {
mod_ : @lib.Module
imported : Int32
type_indices : Array[@lib.TypeIdx]
funcs : Array[@lib.Func]
}
///|
fn dae_exact_literal_candidate_context_new(
mod_ : @lib.Module,
) -> DaeExactLiteralCandidateContext? {
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
Some({
mod_,
imported: dae_imported_func_count(mod_),
type_indices: dae_collect_func_type_indices(mod_),
funcs,
})
None => None
}
}
///|
fn dae_exact_literal_candidate_is_single_param_identity(
mod_ : @lib.Module,
def_idx : Int32,
context~ : DaeExactLiteralCandidateContext? = None,
) -> Bool {
let context = match context {
Some(context) => context
None =>
match dae_exact_literal_candidate_context_new(mod_) {
Some(context) => context
None => return false
}
}
let imported = context.imported
let type_indices = context.type_indices
let funcs = context.funcs
if def_idx < 0 || def_idx >= funcs.length() {
return false
}
match dae_resolve_func_type(mod_, type_indices, imported + def_idx) {
Some(@lib.FuncType([param], [result])) if param == result => {
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
locals.length() == 0 &&
body == [@lib.Instruction::local_get(@lib.LocalIdx::new(0U))]
}
_ => false
}
}
///|
fn dae_exact_literal_candidate_is_single_param_call_sink(
mod_ : @lib.Module,
def_idx : Int32,
context~ : DaeExactLiteralCandidateContext? = None,
) -> Bool {
let context = match context {
Some(context) => context
None =>
match dae_exact_literal_candidate_context_new(mod_) {
Some(context) => context
None => return false
}
}
let imported = context.imported
let type_indices = context.type_indices
let funcs = context.funcs
if def_idx < 0 || def_idx >= funcs.length() {
return false
}
let param = match
dae_resolve_func_type(mod_, type_indices, imported + def_idx) {
Some(@lib.FuncType([param], [])) => param
_ => return false
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
if locals.length() != 0 ||
body.length() != 2 ||
body[0] != @lib.Instruction::local_get(@lib.LocalIdx::new(0U)) {
return false
}
match body[1] {
@lib.Call(target_idx) =>
match
dae_resolve_func_type(mod_, type_indices, dae_func_idx_slot(target_idx)) {
Some(@lib.FuncType([target_param], [])) => target_param == param
_ => false
}
_ => false
}
}
///|
fn dae_exact_literal_candidate_is_single_param_pure_expression(
mod_ : @lib.Module,
def_idx : Int32,
context~ : DaeExactLiteralCandidateContext? = None,
) -> Bool {
let context = match context {
Some(context) => context
None =>
match dae_exact_literal_candidate_context_new(mod_) {
Some(context) => context
None => return false
}
}
let imported = context.imported
let type_indices = context.type_indices
let funcs = context.funcs
if def_idx < 0 || def_idx >= funcs.length() {
return false
}
match dae_resolve_func_type(mod_, type_indices, imported + def_idx) {
Some(@lib.FuncType([_], [_])) => {
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
locals.length() == 0 &&
body.length() == 3 &&
body[0] == @lib.Instruction::local_get(@lib.LocalIdx::new(0U)) &&
dae_instr_is_materializable_const(body[1]) &&
dae_instr_is_nontrapping_pure_binary(body[2])
}
_ => false
}
}
///|
fn dae_exact_literal_candidate_has_scalar_constructor_shape(
mod_ : @lib.Module,
def_idx : Int32,
context~ : DaeExactLiteralCandidateContext? = None,
) -> Bool {
let context = match context {
Some(context) => context
None =>
match dae_exact_literal_candidate_context_new(mod_) {
Some(context) => context
None => return false
}
}
let imported = context.imported
let type_indices = context.type_indices
let funcs = context.funcs
if def_idx < 0 || def_idx >= funcs.length() {
return false
}
let callee_abs = imported + def_idx
let params = match dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params
None => return false
}
if params.length() < 2 ||
!params.all(fn(param) {
param == @lib.ValType::i32() ||
param == @lib.ValType::i64() ||
param == @lib.ValType::f32() ||
param == @lib.ValType::f64() ||
param == @lib.ValType::v128()
}) {
return false
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
if locals.length() != 1 || body.length() < 6 {
return false
}
let constructor_local = @lib.LocalIdx::new(
params.length().reinterpret_as_uint(),
)
match (body[0], body[1], body[2], body[body.length() - 1]) {
(
@lib.I32Const(_),
@lib.Call(_),
@lib.LocalTee(allocated_local),
@lib.LocalGet(returned_local),
) if dae_local_idx_equal(allocated_local, constructor_local) &&
dae_local_idx_equal(returned_local, constructor_local) => ()
_ => return false
}
let mut store_count = 0
for instr in body {
match instr {
@lib.I32Store(_)
| @lib.I64Store(_)
| @lib.F32Store(_)
| @lib.F64Store(_)
| @lib.I32Store8(_)
| @lib.I32Store16(_)
| @lib.I64Store8(_)
| @lib.I64Store16(_)
| @lib.I64Store32(_) => store_count += 1
_ => ()
}
}
if store_count < params.length() {
return false
}
let local_use = dae_local_use_for_callee(
funcs[def_idx],
params.length() + locals.length(),
callee_abs,
params.length(),
)
let mut param_idx = 0
while param_idx < params.length() {
let usage = dae_local_usage_evidence(
body,
@lib.LocalIdx::new(param_idx.reinterpret_as_uint()),
)
if !local_use.gets[param_idx] ||
local_use.writes[param_idx] ||
usage.get_count != 1 ||
usage.write_count != 0 {
return false
}
param_idx += 1
}
true
}
///|
fn dae_run_exact_literal_candidate_worklist_with_facts(
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
candidate_defs : Array[Int32],
allow_immutable_global_get~ : Bool = false,
allow_immediate_i32_suffix_fallback~ : Bool = false,
original~ : DaeOriginalBoundarySnapshot? = None,
graph~ : DaeCurrentBoundaryGraph? = None,
dependencies~ : DaeBoundaryDependencyGraph? = None,
allow_composite_batch~ : Bool = true,
) -> DaeExactLiteralWorklistApplication {
let mut mod_out = current
let mut touched_out = touched
let mut changed_out = changed
let productive_defs : Array[Int32] = []
let mut effects = dae_commit_effects_empty(dae_defined_func_count(current))
if !dae_candidate_worklist_requires_facts(candidate_defs) {
return { mod_out: current, touched, changed, productive_defs, effects, }
}
let original = match original {
Some(original) => original
None => dae_collect_original_boundary_snapshot(current)
}
let active_graph = match graph {
Some(graph) => graph
None => dae_current_boundary_graph_new(current)
}
// Keep composite work in this exact-literal phase. Definitions that require
// other operand proofs retain the scalar path below and its options.
let composite = if allow_composite_batch && candidate_defs.length() > 1 {
dae_apply_composite_exact_literal_batch(
original, mod_out, touched_out, changed_out, active_graph, candidate_defs,
)
} else {
{
mod_out,
touched: touched_out,
changed: changed_out,
productive_defs: [],
effects: dae_commit_effects_empty(active_graph.snapshot.defined),
}
}
if !composite.productive_defs.is_empty() {
mod_out = composite.mod_out
touched_out = composite.touched
changed_out = composite.changed
effects = dae_commit_effects_merge(effects, composite.effects)
// Keep exact current call paths and rescan every committed structural touch.
dae_current_boundary_graph_refresh(
active_graph,
mod_out,
forced_changed_defs=Some(composite.effects.structurally_touched),
)
}
let composite_defs = Array::make(active_graph.snapshot.defined, false)
for def_idx in composite.productive_defs {
composite_defs[def_idx] = true
}
let was_composite = fn(def_idx) {
def_idx >= 0 && def_idx < composite_defs.length() && composite_defs[def_idx]
}
// Bound admission to the initial attempt and one retry. An unsupported first
// member must keep its scalar position before later literal work can batch.
let mut suffix_retry_available = allow_composite_batch &&
candidate_defs.length() > 2 &&
composite.productive_defs.is_empty()
let residual_defs = candidate_defs.filter(fn(def_idx) {
!was_composite(def_idx)
})
let mut active_dependencies = if residual_defs.is_empty() {
dae_boundary_dependency_graph_empty()
} else if !composite.productive_defs.is_empty() {
dae_boundary_dependency_graph_new(active_graph)
} else {
match dependencies {
Some(dependencies) => dependencies
None => dae_boundary_dependency_graph_new(active_graph)
}
}
let mut flow = if allow_immutable_global_get || residual_defs.is_empty() {
{ facts: [], }
} else {
dae_solve_parameter_flow(
active_graph,
active_dependencies,
seed_defs=residual_defs,
)
}
let diagnostics = dae_diagnostics_new()
for candidate_index in 0.. {
mod_out = application.mod_out
touched_out = application.touched
changed_out = application.changed
productive_defs.push(application.primary_def)
effects = dae_commit_effects_merge(effects, application.effects)
dae_current_boundary_graph_refresh(
active_graph,
mod_out,
forced_changed_defs=Some(application.effects.structurally_touched),
)
if suffix_retry_available &&
candidate_index < candidate_defs.length() - 2 {
suffix_retry_available = false
let suffix = dae_apply_composite_literal_suffix_after_scalar(
original, mod_out, touched_out, changed_out, active_graph, candidate_defs,
candidate_index,
)
if !suffix.productive_defs.is_empty() {
mod_out = suffix.mod_out
touched_out = suffix.touched
changed_out = suffix.changed
effects = dae_commit_effects_merge(effects, suffix.effects)
for def_idx in suffix.productive_defs {
composite_defs[def_idx] = true
}
dae_current_boundary_graph_refresh(
active_graph,
mod_out,
forced_changed_defs=Some(suffix.effects.structurally_touched),
)
}
}
let mut has_scalar_remainder = false
for later_index in (candidate_index + 1).. ()
}
}
{
mod_out,
touched: touched_out,
changed: changed_out,
productive_defs,
effects,
}
}
///|
fn dae_exact_param_aggregate_constructor_count_instrs(
instrs : Array[@lib.Instruction],
) -> Int32 {
let mut count = 0
for instr in instrs {
match instr {
@lib.StructNew(_)
| @lib.StructNewDefault(_)
| @lib.ArrayNew(_)
| @lib.ArrayNewDefault(_)
| @lib.ArrayNewFixed(_, _)
| @lib.ArrayNewData(_, _)
| @lib.ArrayNewElem(_, _) => count += 1
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
count += dae_exact_param_aggregate_constructor_count_instrs(body)
@lib.If(_, then_body, else_body) => {
count += dae_exact_param_aggregate_constructor_count_instrs(then_body)
match else_body {
Some(body) =>
count += dae_exact_param_aggregate_constructor_count_instrs(body)
None => ()
}
}
_ => ()
}
}
count
}
///|
fn dae_exact_param_aggregate_constructor_count(
mod_ : @lib.Module,
def_idx : Int32,
) -> Int32 {
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) if def_idx >= 0 && def_idx < funcs.length() => {
let @lib.Func(_, @lib.Expr(body)) = funcs[def_idx]
dae_exact_param_aggregate_constructor_count_instrs(body)
}
_ => 0
}
}
///|
fn dae_func_directly_calls_def(
func : @lib.Func,
callee_def : Int32,
imported : Int32,
defined : Int32,
) -> Bool {
let @lib.Func(_, @lib.Expr(body)) = func
let callees : Array[Int32] = []
dae_collect_direct_call_targets_anywhere(body, imported, defined, callees)
callees.contains(callee_def)
}
///|
fn dae_collect_broad_exact_param_adjacent_cleanup_candidates(
mod_ : @lib.Module,
root_defs : Array[Int32],
) -> Array[DaeBroadExactParamAdjacentCleanupCandidate] {
let candidates : Array[DaeBroadExactParamAdjacentCleanupCandidate] = []
let imported = dae_imported_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return candidates
}
let defined = funcs.length()
let type_indices = dae_collect_func_type_indices(mod_)
let first_limit = defined - 1
let mut first_def = 0
while first_def < first_limit {
let second_def = first_def + 1
let @lib.Func(first_locals, @lib.Expr(first_body)) = funcs[first_def]
let @lib.Func(second_locals, @lib.Expr(second_body)) = funcs[second_def]
if first_locals.length() >= 128 &&
second_locals.length() >= 128 &&
dae_resolve_func_type(mod_, type_indices, imported + first_def) ==
dae_resolve_func_type(mod_, type_indices, imported + second_def) &&
dae_func_directly_calls_def(
funcs[second_def],
first_def,
imported,
defined,
) {
let payoff = dae_dropped_result_payoff_instrs(first_body) +
dae_dropped_result_payoff_instrs(second_body)
for root_def in root_defs {
if root_def >= 0 &&
root_def < defined &&
root_def != first_def &&
root_def != second_def &&
!candidates.any(fn(candidate) {
candidate.root_def == root_def &&
candidate.first_def == first_def &&
candidate.second_def == second_def
}) {
candidates.push({ root_def, first_def, second_def, payoff, })
}
}
}
first_def += 1
}
candidates
}
///|
fn dae_forwarded_param_node_index(
nodes : Array[DaeForwardedParamNode],
callee_abs : Int32,
param_idx : Int32,
) -> Int32 {
let mut idx = 0
while idx < nodes.length() {
if nodes[idx].callee_abs == callee_abs && nodes[idx].param_idx == param_idx {
return idx
}
idx += 1
}
-1
}
///|
fn dae_record_forwarded_param_scc_const(
candidate : @lib.Instruction,
constant : Array[@lib.Instruction?],
flags : Array[Bool],
) -> Bool {
if !dae_instr_is_materializable_const(candidate) {
return false
}
match constant[0] {
Some(existing) => dae_materialized_const_equal(existing, candidate)
None => {
constant[0] = Some(candidate)
flags[2] = true
true
}
}
}
///|
fn dae_forwarded_param_suffix_single_instruction_actual(
instrs : Array[@lib.Instruction],
call_idx : Int32,
param_count : Int32,
param_idx : Int32,
mod_ : @lib.Module,
type_indices : Array[@lib.TypeIdx],
) -> @lib.Instruction? {
let suffix_count = param_count - param_idx
let start = call_idx - suffix_count
if suffix_count <= 0 || start < 0 {
return None
}
let mut idx = start
while idx < call_idx {
match dae_instr_stack_effect(instrs[idx], mod_, type_indices) {
Some((0, 1)) => ()
_ => return None
}
idx += 1
}
Some(instrs[start])
}
///|
fn dae_collect_forwarded_param_scc_actuals_instrs(
instrs : Array[@lib.Instruction],
entry_stack_values : Int32,
mod_ : @lib.Module,
callee_abs : Int32,
param_count : Int32,
param_idx : Int32,
caller_abs : Int32,
caller_param_count : Int32,
type_indices : Array[@lib.TypeIdx],
boundary_facts : DaeCallBoundaryFacts,
nodes : Array[DaeForwardedParamNode],
states : Array[Int32],
cycle_nodes : Array[DaeForwardedParamNode],
constant : Array[@lib.Instruction?],
flags : Array[Bool],
seen_calls : Array[Int32],
) -> Bool {
let mut idx = 0
while idx < instrs.length() {
match
dae_target_callsite_evidence(
instrs[idx],
callee_abs,
DaeTargetDirectCallOnly,
) {
Some(_) => {
let actual = match
dae_callsite_value_slice_evidence(
instrs, idx, param_count, entry_stack_values, mod_, type_indices,
) {
Some(evidence) =>
match
dae_callsite_single_instruction_actual(
evidence, param_idx, instrs,
) {
Some(actual) => Some(actual.instr)
None => None
}
None => None
}
let actual = match actual {
Some(actual) => actual
None =>
match
dae_forwarded_param_suffix_single_instruction_actual(
instrs, idx, param_count, param_idx, mod_, type_indices,
) {
Some(actual) => actual
None => return false
}
}
seen_calls[0] += 1
if dae_instr_is_materializable_const(actual) {
if !dae_record_forwarded_param_scc_const(actual, constant, flags) {
return false
}
} else {
match actual {
@lib.LocalGet(local_idx) => {
let local_slot = dae_local_idx_slot(local_idx)
if local_slot < 0 ||
local_slot >= caller_param_count ||
!dae_collect_forwarded_param_scc_node(
mod_, caller_abs, local_slot, type_indices, boundary_facts, nodes,
states, cycle_nodes, constant, flags,
) {
return false
}
}
_ => return false
}
}
}
None =>
match instrs[idx] {
@lib.Block(_, @lib.Expr(_))
| @lib.Loop(_, @lib.Expr(_))
| @lib.TryTable(_, _, @lib.Expr(_))
| @lib.If(_, _, _) => {
let children = match
dae_nested_control_children_evidence(instrs[idx], mod_) {
Some(children) => children
None => return false
}
if !dae_collect_forwarded_param_scc_actuals_instrs(
children.primary_body,
children.entry_stack_values,
mod_,
callee_abs,
param_count,
param_idx,
caller_abs,
caller_param_count,
type_indices,
boundary_facts,
nodes,
states,
cycle_nodes,
constant,
flags,
seen_calls,
) {
return false
}
match children.secondary_body {
Some(body) =>
if !dae_collect_forwarded_param_scc_actuals_instrs(
body,
children.entry_stack_values,
mod_,
callee_abs,
param_count,
param_idx,
caller_abs,
caller_param_count,
type_indices,
boundary_facts,
nodes,
states,
cycle_nodes,
constant,
flags,
seen_calls,
) {
return false
}
None => ()
}
}
_ => ()
}
}
idx += 1
}
true
}
///|
fn dae_collect_forwarded_param_scc_node(
mod_ : @lib.Module,
callee_abs : Int32,
param_idx : Int32,
type_indices : Array[@lib.TypeIdx],
boundary_facts : DaeCallBoundaryFacts,
nodes : Array[DaeForwardedParamNode],
states : Array[Int32],
cycle_nodes : Array[DaeForwardedParamNode],
constant : Array[@lib.Instruction?],
flags : Array[Bool],
) -> Bool {
let existing_idx = dae_forwarded_param_node_index(
nodes, callee_abs, param_idx,
)
if existing_idx >= 0 {
if states[existing_idx] == 1 {
flags[1] = true
if cycle_nodes.length() == 0 {
let mut cycle_idx = existing_idx
while cycle_idx < nodes.length() {
cycle_nodes.push(nodes[cycle_idx])
cycle_idx += 1
}
}
}
return states[existing_idx] != 0
}
let imported = dae_imported_func_count(mod_)
let def_idx = callee_abs - imported
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return false
}
if def_idx < 0 ||
def_idx >= funcs.length() ||
!dae_call_boundary_allows_forwarding(boundary_facts, callee_abs) ||
callee_abs >= boundary_facts.direct_calls.length() ||
boundary_facts.direct_calls[callee_abs] <= 0 {
return false
}
let params = match dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params
None => return false
}
if param_idx < 0 || param_idx >= params.length() {
return false
}
let @lib.Func(locals, _) = funcs[def_idx]
let local_use = dae_local_use_for_callee(
funcs[def_idx],
params.length() + locals.length(),
callee_abs,
params.length(),
)
if local_use.writes[param_idx] {
return false
}
let node_idx = nodes.length()
nodes.push({ callee_abs, param_idx, })
states.push(1)
let caller_defs = dae_call_boundary_direct_callers(boundary_facts, callee_abs)
let seen_calls = [0]
for caller_def in caller_defs {
if caller_def < 0 || caller_def >= funcs.length() {
return false
}
let caller_abs = imported + caller_def
let caller_params = match
dae_resolve_func_type(mod_, type_indices, caller_abs) {
Some(@lib.FuncType(params, _)) => params
None => return false
}
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
if !dae_collect_forwarded_param_scc_actuals_instrs(
body,
0,
mod_,
callee_abs,
params.length(),
param_idx,
caller_abs,
caller_params.length(),
type_indices,
boundary_facts,
nodes,
states,
cycle_nodes,
constant,
flags,
seen_calls,
) {
return false
}
}
if seen_calls[0] != boundary_facts.direct_calls[callee_abs] {
return false
}
states[node_idx] = 2
true
}
///|
fn dae_parameter_flow_edge_epochs_are_current(
graph : DaeCurrentBoundaryGraph,
edge : DaeParameterForwardingEdge,
) -> Bool {
if edge.caller_def < 0 ||
edge.caller_def >= graph.function_epochs.length() ||
edge.callee_def < 0 ||
edge.callee_def >= graph.function_epochs.length() {
return false
}
let caller = graph.function_epochs[edge.caller_def]
let callee = graph.function_epochs[edge.callee_def]
caller.signature_epoch == edge.caller_signature_epoch &&
caller.body_epoch == edge.caller_body_epoch &&
callee.signature_epoch == edge.callee_signature_epoch &&
callee.body_epoch == edge.callee_body_epoch
}
///|
fn dae_parameter_flow_fact_index(
facts : Array[DaeParameterFlowFact],
def_idx : Int32,
param_idx : Int32,
) -> Int32 {
let mut fact_idx = 0
while fact_idx < facts.length() {
if facts[fact_idx].def_idx == def_idx &&
facts[fact_idx].param_idx == param_idx {
return fact_idx
}
fact_idx += 1
}
-1
}
///|
fn dae_parameter_flow_record(
facts : Array[DaeParameterFlowFact],
def_idx : Int32,
param_idx : Int32,
state : DaeParameterFlowState,
) -> Unit {
let fact_idx = dae_parameter_flow_fact_index(facts, def_idx, param_idx)
if fact_idx < 0 {
facts.push({ def_idx, param_idx, state, })
return
}
let existing = facts[fact_idx].state
facts[fact_idx].state = match (existing, state) {
(DaeParameterBlocked, _) | (_, DaeParameterBlocked) => DaeParameterBlocked
(DaeParameterConflict, _) | (_, DaeParameterConflict) =>
DaeParameterConflict
(DaeParameterUnseen, next) => next
(current, DaeParameterUnseen) => current
(DaeParameterUniform(left), DaeParameterUniform(right)) =>
if dae_materialized_const_equal(left, right) {
DaeParameterUniform(left)
} else {
DaeParameterConflict
}
(DaeParameterUniform(value), DaeParameterForwardedOnly)
| (DaeParameterForwardedOnly, DaeParameterUniform(value)) =>
DaeParameterUniform(value)
(DaeParameterForwardedOnly, DaeParameterForwardedOnly) =>
DaeParameterForwardedOnly
}
}
///|
fn dae_solve_parameter_flow(
graph : DaeCurrentBoundaryGraph,
dependencies : DaeBoundaryDependencyGraph,
seed_defs~ : Array[Int32] = [],
) -> DaeParameterFlowSolution {
for edge in dependencies.parameter_forwarding_edges {
if !dae_parameter_flow_edge_epochs_are_current(graph, edge) {
return { facts: [], }
}
}
let facts : Array[DaeParameterFlowFact] = []
let boundary_facts = graph.snapshot.boundary_facts
let imported = graph.snapshot.imported
let defs : Array[Int32] = []
if seed_defs.length() == 0 {
let mut def_idx = 0
while def_idx < graph.snapshot.defined {
defs.push(def_idx)
def_idx += 1
}
} else {
for def_idx in seed_defs {
if def_idx >= 0 &&
def_idx < graph.snapshot.defined &&
!defs.contains(def_idx) {
defs.push(def_idx)
}
}
}
for def_idx in defs {
let callee_abs = imported + def_idx
let params = match
dae_resolve_func_type(graph.mod_, graph.snapshot.type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params
None => continue
}
let mut param_idx = 0
while param_idx < params.length() {
let nodes : Array[DaeForwardedParamNode] = []
let states : Array[Int32] = []
let cycle_nodes : Array[DaeForwardedParamNode] = []
let constant : Array[@lib.Instruction?] = [None]
let flags = [true, false, false]
dae_parameter_flow_record(facts, def_idx, param_idx, DaeParameterUnseen)
let collected = dae_collect_forwarded_param_scc_node(
graph.mod_,
callee_abs,
param_idx,
graph.snapshot.type_indices,
boundary_facts,
nodes,
states,
cycle_nodes,
constant,
flags,
)
if collected {
let state = match constant[0] {
Some(value) => DaeParameterUniform(value)
None => DaeParameterForwardedOnly
}
for node in nodes {
let node_def = node.callee_abs - imported
if node_def >= 0 && node_def < graph.snapshot.defined {
dae_parameter_flow_record(facts, node_def, node.param_idx, state)
}
}
} else {
let blocked_state = if flags[2] && constant[0] is Some(_) {
DaeParameterConflict
} else {
DaeParameterBlocked
}
dae_parameter_flow_record(facts, def_idx, param_idx, blocked_state)
}
param_idx += 1
}
}
{ facts, }
}
///|
fn dae_parameter_flow_state_at(
solution : DaeParameterFlowSolution,
def_idx : Int32,
param_idx : Int32,
) -> DaeParameterFlowState {
let fact_idx = dae_parameter_flow_fact_index(
solution.facts,
def_idx,
param_idx,
)
if fact_idx < 0 {
DaeParameterUnseen
} else {
solution.facts[fact_idx].state
}
}
///|
fn dae_parameter_flow_uniform_values_for_def(
graph : DaeCurrentBoundaryGraph,
solution : DaeParameterFlowSolution,
def_idx : Int32,
) -> Array[@lib.Instruction?] {
if def_idx < 0 || def_idx >= graph.snapshot.defined {
return []
}
let callee_abs = graph.snapshot.imported + def_idx
let params = match
dae_resolve_func_type(graph.mod_, graph.snapshot.type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params
None => return []
}
let values : Array[@lib.Instruction?] = Array::make(params.length(), None)
let mut any = false
let mut param_idx = 0
while param_idx < params.length() {
match dae_parameter_flow_state_at(solution, def_idx, param_idx) {
DaeParameterUniform(value) => {
values[param_idx] = Some(value)
any = true
}
_ => ()
}
param_idx += 1
}
if any {
values
} else {
[]
}
}
///|
fn dae_apply_forwarded_param_scc_transaction(
mod_ : @lib.Module,
touched : Array[Bool],
nodes : Array[DaeForwardedParamNode],
constant : @lib.Instruction,
boundary_facts : DaeCallBoundaryFacts,
) -> DaeForwardedParamSccApplication? {
let imported = dae_imported_func_count(mod_)
let (original_type_idxs, funcs, original_type_sec) = match
(mod_.func_sec, mod_.code_sec, mod_.type_sec) {
(Some(@lib.FuncSec(type_idxs)), Some(@lib.CodeSec(funcs)), Some(type_sec)) =>
(type_idxs, funcs, type_sec)
_ => return None
}
let type_indices = dae_collect_func_type_indices(mod_)
let defs : Array[Int32] = []
let removes : Array[Array[Bool]] = []
let const_params_by_def : Array[Array[@lib.Instruction?]] = []
let localized_by_def : Array[Array[Bool]] = []
for node in nodes {
let def_idx = node.callee_abs - imported
if def_idx < 0 || def_idx >= funcs.length() {
return None
}
let mut def_slot = -1
let mut existing_def_slot = 0
while existing_def_slot < defs.length() {
if defs[existing_def_slot] == def_idx {
def_slot = existing_def_slot
break
}
existing_def_slot += 1
}
if def_slot < 0 {
let params = match
dae_resolve_func_type(mod_, type_indices, node.callee_abs) {
Some(@lib.FuncType(params, _)) => params
None => return None
}
def_slot = defs.length()
defs.push(def_idx)
removes.push(Array::make(params.length(), false))
const_params_by_def.push(Array::make(params.length(), None))
localized_by_def.push(Array::make(params.length(), false))
}
if node.param_idx < 0 || node.param_idx >= removes[def_slot].length() {
return None
}
removes[def_slot][node.param_idx] = true
const_params_by_def[def_slot][node.param_idx] = Some(constant)
}
let rewritten_funcs = funcs.copy()
let touched_out = if touched.length() == funcs.length() {
touched.copy()
} else {
Array::make(funcs.length(), false)
}
let mut def_slot = 0
while def_slot < defs.length() {
let def_idx = defs[def_slot]
let callee_abs = imported + def_idx
let remove = removes[def_slot]
let params = match dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params
None => return None
}
let mut rewritten_calls = 0
for
caller_def in dae_call_boundary_direct_callers(boundary_facts, callee_abs) {
if caller_def < 0 || caller_def >= rewritten_funcs.length() {
return None
}
let caller_abs = imported + caller_def
let caller_params = match
dae_resolve_func_type(mod_, type_indices, caller_abs) {
Some(@lib.FuncType(caller_params, _)) => caller_params
None => return None
}
let @lib.Func(caller_locals, @lib.Expr(caller_body)) = rewritten_funcs[caller_def]
let localization = {
first_scratch_local: dae_next_free_local_idx(
caller_body,
caller_params.length() + caller_locals.length(),
),
param_types: params,
scratch_local_types: [],
localized_params: localized_by_def[def_slot],
}
let rewrite = dae_rewrite_calls_instrs(
caller_body,
callee_abs,
remove,
0,
mod_,
type_indices,
localization=Some(localization),
)
if !rewrite.ok {
return None
}
rewritten_calls += rewrite.calls_rewritten
if rewrite.calls_rewritten > 0 {
let next_locals = caller_locals.copy()
next_locals.append_types(localization.scratch_local_types)
rewritten_funcs[caller_def] = @lib.Func::new(
next_locals,
@lib.Expr::new(rewrite.instrs),
)
touched_out[caller_def] = true
}
}
if callee_abs >= boundary_facts.direct_calls.length() ||
rewritten_calls != boundary_facts.direct_calls[callee_abs] ||
localized_by_def[def_slot].any(fn(localized) { localized }) {
return None
}
def_slot += 1
}
let type_idxs = original_type_idxs.copy()
let mut type_sec = original_type_sec
let type_update_index = DaeTypeUpdateIndex::new(type_sec)
def_slot = 0
while def_slot < defs.length() {
let def_idx = defs[def_slot]
let callee_abs = imported + def_idx
let (params, results) = match
dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(params, results)) => (params, results)
None => return None
}
let local_use = {
gets: Array::make(params.length(), false),
writes: Array::make(params.length(), false),
}
let rewrite = match
dae_removed_param_callee_rewrite_evidence(
rewritten_funcs[def_idx],
params,
removes[def_slot],
const_params_by_def[def_slot],
local_use,
localized_by_def[def_slot],
) {
Some(rewrite) => rewrite
None => return None
}
rewritten_funcs[def_idx] = @lib.Func::new(
rewrite.locals,
@lib.Expr::new(rewrite.body),
)
let (next_type_sec, next_type_idx) = dae_update_defined_func_type(
mod_,
type_sec,
type_idxs,
def_idx,
rewrite.kept_params,
results,
type_update_index=Some(type_update_index),
)
type_sec = next_type_sec
type_idxs[def_idx] = next_type_idx
touched_out[def_idx] = true
def_slot += 1
}
Some({
mod_out: mod_
.with_type_sec(type_sec)
.with_func_sec(@lib.FuncSec::new(type_idxs))
.with_code_sec(@lib.CodeSec::new(rewritten_funcs))
.without_name_sec(),
touched: touched_out,
defs,
param_count: nodes.length(),
})
}
///|
fn dae_collect_forwarded_param_cycle_defs_with_facts(
current : @lib.Module,
touched : Array[Bool],
preferred_defs : Array[Int32],
excluded_defs : Array[Int32],
imported : Int32,
defined : Int32,
type_indices : Array[@lib.TypeIdx],
boundary_facts : DaeCallBoundaryFacts,
) -> Array[Int32] {
let seed_defs : Array[Int32] = []
for preferred_def in preferred_defs {
if preferred_def >= 0 &&
preferred_def < defined &&
touched[preferred_def] &&
!excluded_defs.contains(preferred_def) &&
!seed_defs.contains(preferred_def) {
seed_defs.push(preferred_def)
}
}
for seed_def = 0; seed_def < defined; seed_def = seed_def + 1 {
if touched[seed_def] &&
!excluded_defs.contains(seed_def) &&
!seed_defs.contains(seed_def) {
seed_defs.push(seed_def)
}
}
for seed_def in seed_defs {
let callee_abs = imported + seed_def
let params = match
dae_resolve_func_type(current, type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params
None => continue
}
let mut seed_param = 0
while seed_param < params.length() {
let nodes : Array[DaeForwardedParamNode] = []
let states : Array[Int32] = []
let cycle_nodes : Array[DaeForwardedParamNode] = []
let constant : Array[@lib.Instruction?] = [None]
let flags = [true, false, false]
ignore(
dae_collect_forwarded_param_scc_node(
current, callee_abs, seed_param, type_indices, boundary_facts, nodes, states,
cycle_nodes, constant, flags,
),
)
if flags[1] && cycle_nodes.length() > 1 {
let defs : Array[Int32] = []
for node in cycle_nodes {
let def_idx = node.callee_abs - imported
if def_idx >= 0 && def_idx < defined && !defs.contains(def_idx) {
defs.push(def_idx)
}
}
if defs.length() > 1 &&
!defs.any(fn(def_idx) { excluded_defs.contains(def_idx) }) {
return defs
}
}
seed_param += 1
}
}
[]
}
///|
#warnings("-unused_value")
fn dae_collect_forwarded_param_cycle_batches(
current : @lib.Module,
touched : Array[Bool],
preferred_defs~ : Array[Int32] = [],
excluded_defs~ : Array[Int32] = [],
limit~ : Int32 = -1,
) -> Array[Array[Int32]] {
let imported = dae_imported_func_count(current)
let defined = dae_defined_func_count(current)
if defined == 0 || touched.length() != defined || limit == 0 {
return []
}
let total = imported + defined
let call_facts = dae_collect_current_call_facts(current, total)
let boundary_facts = dae_call_boundary_facts_from_current(call_facts)
let type_indices = dae_collect_func_type_indices(current)
let batches : Array[Array[Int32]] = []
let processed_defs = excluded_defs.copy()
let ordered_preferred_defs = preferred_defs.copy()
while limit < 0 || batches.length() < limit {
let defs = dae_collect_forwarded_param_cycle_defs_with_facts(
current, touched, ordered_preferred_defs, processed_defs, imported, defined,
type_indices, boundary_facts,
)
ordered_preferred_defs.clear()
if defs.length() < 2 {
break
}
batches.push(defs)
for def_idx in defs {
if !processed_defs.contains(def_idx) {
processed_defs.push(def_idx)
}
}
}
batches
}
///|
fn dae_forwarded_param_direct_call_target_after_get(
instrs : Array[@lib.Instruction],
get_idx : Int32,
current : @lib.Module,
type_indices : Array[@lib.TypeIdx],
) -> DaeForwardedParamNode? {
if get_idx < 0 || get_idx >= instrs.length() {
return None
}
let mut values_above = 0
let mut idx = get_idx + 1
while idx < instrs.length() {
match instrs[idx] {
@lib.Call(func_idx) => {
let callee_abs = dae_func_idx_slot(func_idx)
let params = match
dae_resolve_func_type(current, type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params
None => return None
}
if values_above < params.length() {
return Some({
callee_abs,
param_idx: params.length() - 1 - values_above,
})
}
}
@lib.ReturnCall(_) => return None
_ => ()
}
match dae_instr_stack_effect(instrs[idx], current, type_indices) {
Some((pop_count, push_count)) => {
if pop_count > values_above {
return None
}
values_above = values_above - pop_count + push_count
}
None => return None
}
idx += 1
}
None
}
///|
fn dae_collect_forwarded_param_component_uses_instrs(
instrs : Array[@lib.Instruction],
entry_stack_values : Int32,
current : @lib.Module,
caller_param_count : Int32,
source_param_idx : Int32,
type_indices : Array[@lib.TypeIdx],
targets : Array[DaeForwardedParamNode],
covered_gets : Array[Int32],
) -> Bool {
let source_local = @lib.LocalIdx::new(source_param_idx.reinterpret_as_uint())
let mut idx = 0
while idx < instrs.length() {
match instrs[idx] {
@lib.LocalGet(local_idx) if dae_local_idx_equal(local_idx, source_local) =>
match
dae_forwarded_param_direct_call_target_after_get(
instrs, idx, current, type_indices,
) {
Some(target) => {
covered_gets[0] += 1
targets.push(target)
}
None => ()
}
@lib.Block(_, @lib.Expr(_))
| @lib.Loop(_, @lib.Expr(_))
| @lib.TryTable(_, _, @lib.Expr(_))
| @lib.If(_, _, _) => {
let children = match
dae_nested_control_children_evidence(instrs[idx], current) {
Some(children) => children
None => return false
}
if !dae_collect_forwarded_param_component_uses_instrs(
children.primary_body,
children.entry_stack_values,
current,
caller_param_count,
source_param_idx,
type_indices,
targets,
covered_gets,
) {
return false
}
match children.secondary_body {
Some(body) =>
if !dae_collect_forwarded_param_component_uses_instrs(
body,
children.entry_stack_values,
current,
caller_param_count,
source_param_idx,
type_indices,
targets,
covered_gets,
) {
return false
}
None => ()
}
}
_ => ()
}
idx += 1
}
entry_stack_values >= 0 && caller_param_count >= 0
}
///|
fn dae_collect_forwarded_param_component_node(
current : @lib.Module,
callee_abs : Int32,
param_idx : Int32,
type_indices : Array[@lib.TypeIdx],
boundary_facts : DaeCallBoundaryFacts,
nodes : Array[DaeForwardedParamNode],
states : Array[Int32],
flags : Array[Bool],
) -> Bool {
let existing_idx = dae_forwarded_param_node_index(
nodes, callee_abs, param_idx,
)
if existing_idx >= 0 {
if states[existing_idx] == 1 {
flags[0] = true
}
return states[existing_idx] != 0
}
let imported = dae_imported_func_count(current)
let def_idx = callee_abs - imported
let funcs = match current.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return false
}
if def_idx < 0 ||
def_idx >= funcs.length() ||
!dae_call_boundary_allows_forwarding(boundary_facts, callee_abs) ||
callee_abs >= boundary_facts.direct_calls.length() ||
boundary_facts.direct_calls[callee_abs] <= 0 {
return false
}
let params = match dae_resolve_func_type(current, type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params
None => return false
}
if param_idx < 0 || param_idx >= params.length() {
return false
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
if !dae_local_indices_fit_instrs(body, params.length() + locals.length()) {
return false
}
let target_local = @lib.LocalIdx::new(param_idx.reinterpret_as_uint())
let usage = dae_local_usage_evidence(body, target_local)
if usage.write_count != 0 {
return false
}
let node_idx = nodes.length()
nodes.push({ callee_abs, param_idx, })
states.push(1)
if usage.get_count == 0 {
if flags.length() > 1 {
flags[1] = true
}
states[node_idx] = 2
return true
}
let targets : Array[DaeForwardedParamNode] = []
let covered_gets = [0]
let scanned = dae_collect_forwarded_param_component_uses_instrs(
body,
0,
current,
params.length(),
param_idx,
type_indices,
targets,
covered_gets,
)
if !scanned || covered_gets[0] != usage.get_count || targets.length() == 0 {
states[node_idx] = 0
return false
}
for target in targets {
if !dae_collect_forwarded_param_component_node(
current,
target.callee_abs,
target.param_idx,
type_indices,
boundary_facts,
nodes,
states,
flags,
) {
states[node_idx] = 0
return false
}
}
states[node_idx] = 2
true
}
///|
fn dae_collect_forwarded_param_component_sources_instrs(
instrs : Array[@lib.Instruction],
entry_stack_values : Int32,
current : @lib.Module,
callee_abs : Int32,
param_count : Int32,
param_idx : Int32,
caller_abs : Int32,
caller_param_count : Int32,
type_indices : Array[@lib.TypeIdx],
sources : Array[DaeForwardedParamNode],
seen_calls : Array[Int32],
) -> Bool {
let mut idx = 0
while idx < instrs.length() {
match
dae_target_callsite_evidence(
instrs[idx],
callee_abs,
DaeTargetDirectCallOnly,
) {
Some(_) => {
let actual = match
dae_callsite_value_slice_evidence(
instrs, idx, param_count, entry_stack_values, current, type_indices,
) {
Some(evidence) =>
match
dae_callsite_single_instruction_actual(
evidence, param_idx, instrs,
) {
Some(actual) => Some(actual.instr)
None => None
}
None => None
}
let actual = match actual {
Some(actual) => actual
None =>
match
dae_forwarded_param_suffix_single_instruction_actual(
instrs, idx, param_count, param_idx, current, type_indices,
) {
Some(actual) => actual
None => return false
}
}
seen_calls[0] += 1
match actual {
@lib.LocalGet(local_idx) => {
let local_slot = dae_local_idx_slot(local_idx)
if local_slot >= 0 && local_slot < caller_param_count {
sources.push({ callee_abs: caller_abs, param_idx: local_slot, })
}
}
_ => ()
}
}
None =>
match instrs[idx] {
@lib.Block(_, @lib.Expr(_))
| @lib.Loop(_, @lib.Expr(_))
| @lib.TryTable(_, _, @lib.Expr(_))
| @lib.If(_, _, _) => {
let children = match
dae_nested_control_children_evidence(instrs[idx], current) {
Some(children) => children
None => return false
}
if !dae_collect_forwarded_param_component_sources_instrs(
children.primary_body,
children.entry_stack_values,
current,
callee_abs,
param_count,
param_idx,
caller_abs,
caller_param_count,
type_indices,
sources,
seen_calls,
) {
return false
}
match children.secondary_body {
Some(body) =>
if !dae_collect_forwarded_param_component_sources_instrs(
body,
children.entry_stack_values,
current,
callee_abs,
param_count,
param_idx,
caller_abs,
caller_param_count,
type_indices,
sources,
seen_calls,
) {
return false
}
None => ()
}
}
_ => ()
}
}
idx += 1
}
true
}
///|
fn dae_collect_forwarded_param_component_sources(
current : @lib.Module,
node : DaeForwardedParamNode,
type_indices : Array[@lib.TypeIdx],
boundary_facts : DaeCallBoundaryFacts,
) -> Array[DaeForwardedParamNode] {
let sources : Array[DaeForwardedParamNode] = []
let imported = dae_imported_func_count(current)
let funcs = match current.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return sources
}
let params = match
dae_resolve_func_type(current, type_indices, node.callee_abs) {
Some(@lib.FuncType(params, _)) => params
None => return sources
}
let seen_calls = [0]
for
caller_def in dae_call_boundary_direct_callers(
boundary_facts,
node.callee_abs,
) {
if caller_def < 0 || caller_def >= funcs.length() {
return []
}
let caller_abs = imported + caller_def
let caller_params = match
dae_resolve_func_type(current, type_indices, caller_abs) {
Some(@lib.FuncType(caller_params, _)) => caller_params
None => return []
}
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
if !dae_collect_forwarded_param_component_sources_instrs(
body,
0,
current,
node.callee_abs,
params.length(),
node.param_idx,
caller_abs,
caller_params.length(),
type_indices,
sources,
seen_calls,
) {
return []
}
}
if node.callee_abs >= boundary_facts.direct_calls.length() ||
seen_calls[0] != boundary_facts.direct_calls[node.callee_abs] {
return []
}
sources
}
///|
fn dae_forwarded_param_node_calls_rewritable(
current : @lib.Module,
node : DaeForwardedParamNode,
type_indices : Array[@lib.TypeIdx],
boundary_facts : DaeCallBoundaryFacts,
) -> Bool {
let imported = dae_imported_func_count(current)
let funcs = match current.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return false
}
let def_idx = node.callee_abs - imported
if def_idx < 0 || def_idx >= funcs.length() {
return false
}
let params = match
dae_resolve_func_type(current, type_indices, node.callee_abs) {
Some(@lib.FuncType(params, _)) => params
None => return false
}
if node.param_idx < 0 || node.param_idx >= params.length() {
return false
}
let remove = Array::make(params.length(), false)
remove[node.param_idx] = true
let localized = Array::make(params.length(), false)
let mut rewritten_calls = 0
for
caller_def in dae_call_boundary_direct_callers(
boundary_facts,
node.callee_abs,
) {
if caller_def < 0 || caller_def >= funcs.length() {
return false
}
let caller_abs = imported + caller_def
let caller_params = match
dae_resolve_func_type(current, type_indices, caller_abs) {
Some(@lib.FuncType(caller_params, _)) => caller_params
None => return false
}
let @lib.Func(caller_locals, @lib.Expr(caller_body)) = funcs[caller_def]
let localization = {
first_scratch_local: dae_next_free_local_idx(
caller_body,
caller_params.length() + caller_locals.length(),
),
param_types: params,
scratch_local_types: [],
localized_params: localized,
}
let rewrite = dae_rewrite_calls_instrs(
caller_body,
node.callee_abs,
remove,
0,
current,
type_indices,
localization=Some(localization),
)
if !rewrite.ok {
return false
}
rewritten_calls += rewrite.calls_rewritten
}
node.callee_abs < boundary_facts.direct_calls.length() &&
rewritten_calls == boundary_facts.direct_calls[node.callee_abs]
}
///|
fn dae_collect_forwarded_param_component(
current : @lib.Module,
cycle_defs : Array[Int32],
type_indices : Array[@lib.TypeIdx],
boundary_facts : DaeCallBoundaryFacts,
) -> Array[DaeForwardedParamNode] {
let imported = dae_imported_func_count(current)
let defined = dae_defined_func_count(current)
let mut seed_slot = 0
while seed_slot < cycle_defs.length() {
let seed_def = cycle_defs[seed_slot]
if seed_def < 0 || seed_def >= defined {
seed_slot += 1
continue
}
let seed_abs = imported + seed_def
let params = match dae_resolve_func_type(current, type_indices, seed_abs) {
Some(@lib.FuncType(params, _)) => params
None => {
seed_slot += 1
continue
}
}
let mut seed_param = 0
while seed_param < params.length() {
let nodes : Array[DaeForwardedParamNode] = []
let states : Array[Int32] = []
let flags = [false, false]
let collected = dae_collect_forwarded_param_component_node(
current, seed_abs, seed_param, type_indices, boundary_facts, nodes, states,
flags,
)
let component_defs : Array[Int32] = []
if collected && (flags[0] || flags[1]) {
for node in nodes {
let def_idx = node.callee_abs - imported
if def_idx >= 0 &&
def_idx < defined &&
!component_defs.contains(def_idx) {
component_defs.push(def_idx)
}
}
}
let mut cycle_member_defs = 0
for def_idx in cycle_defs {
if component_defs.contains(def_idx) {
cycle_member_defs += 1
}
}
if collected && (flags[0] || flags[1]) && cycle_member_defs >= 2 {
let mut changed = true
while changed {
changed = false
let frontier = nodes.copy()
for target in frontier {
let sources = dae_collect_forwarded_param_component_sources(
current, target, type_indices, boundary_facts,
)
for source in sources {
if dae_forwarded_param_node_index(
nodes,
source.callee_abs,
source.param_idx,
) >=
0 ||
!dae_forwarded_param_node_calls_rewritable(
current, source, type_indices, boundary_facts,
) {
continue
}
let trial_nodes = nodes.copy()
let trial_states = Array::make(trial_nodes.length(), 2)
let trial_flags = [true, true]
if dae_collect_forwarded_param_component_node(
current,
source.callee_abs,
source.param_idx,
type_indices,
boundary_facts,
trial_nodes,
trial_states,
trial_flags,
) {
nodes.clear()
nodes.append(trial_nodes)
states.clear()
states.append(trial_states)
changed = true
}
}
}
}
return nodes
}
seed_param += 1
}
seed_slot += 1
}
[]
}
///|
fn dae_build_forwarded_param_component_candidate(
current : @lib.Module,
touched : Array[Bool],
nodes : Array[DaeForwardedParamNode],
boundary_facts : DaeCallBoundaryFacts,
) -> DaeForwardedParamSccApplication? {
if nodes.length() < 2 {
return None
}
let imported = dae_imported_func_count(current)
let (original_type_idxs, funcs, original_type_sec) = match
(current.func_sec, current.code_sec, current.type_sec) {
(Some(@lib.FuncSec(type_idxs)), Some(@lib.CodeSec(funcs)), Some(type_sec)) =>
(type_idxs, funcs, type_sec)
_ => return None
}
let type_indices = dae_collect_func_type_indices(current)
let defs : Array[Int32] = []
let removes : Array[Array[Bool]] = []
let localized_by_def : Array[Array[Bool]] = []
for node in nodes {
let def_idx = node.callee_abs - imported
if def_idx < 0 || def_idx >= funcs.length() {
return None
}
let mut def_slot = -1
let mut existing_slot = 0
while existing_slot < defs.length() {
if defs[existing_slot] == def_idx {
def_slot = existing_slot
break
}
existing_slot += 1
}
if def_slot < 0 {
let params = match
dae_resolve_func_type(current, type_indices, node.callee_abs) {
Some(@lib.FuncType(params, _)) => params
None => return None
}
def_slot = defs.length()
defs.push(def_idx)
removes.push(Array::make(params.length(), false))
localized_by_def.push(Array::make(params.length(), false))
}
if node.param_idx < 0 || node.param_idx >= removes[def_slot].length() {
return None
}
removes[def_slot][node.param_idx] = true
}
let rewritten_funcs = funcs.copy()
let touched_out = if touched.length() == funcs.length() {
touched.copy()
} else {
Array::make(funcs.length(), false)
}
let mut def_slot = 0
while def_slot < defs.length() {
let def_idx = defs[def_slot]
let callee_abs = imported + def_idx
let remove = removes[def_slot]
let params = match
dae_resolve_func_type(current, type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params
None => return None
}
let mut rewritten_calls = 0
for
caller_def in dae_call_boundary_direct_callers(boundary_facts, callee_abs) {
if caller_def < 0 || caller_def >= rewritten_funcs.length() {
return None
}
let caller_abs = imported + caller_def
let caller_params = match
dae_resolve_func_type(current, type_indices, caller_abs) {
Some(@lib.FuncType(caller_params, _)) => caller_params
None => return None
}
let @lib.Func(caller_locals, @lib.Expr(caller_body)) = rewritten_funcs[caller_def]
let localization = {
first_scratch_local: dae_next_free_local_idx(
caller_body,
caller_params.length() + caller_locals.length(),
),
param_types: params,
scratch_local_types: [],
localized_params: localized_by_def[def_slot],
}
let rewrite = dae_rewrite_calls_instrs(
caller_body,
callee_abs,
remove,
0,
current,
type_indices,
localization=Some(localization),
)
if !rewrite.ok {
return None
}
rewritten_calls += rewrite.calls_rewritten
if rewrite.calls_rewritten > 0 {
let next_locals = caller_locals.copy()
next_locals.append_types(localization.scratch_local_types)
rewritten_funcs[caller_def] = @lib.Func::new(
next_locals,
@lib.Expr::new(rewrite.instrs),
)
touched_out[caller_def] = true
}
}
if callee_abs >= boundary_facts.direct_calls.length() ||
rewritten_calls != boundary_facts.direct_calls[callee_abs] {
return None
}
def_slot += 1
}
let type_idxs = original_type_idxs.copy()
let mut type_sec = original_type_sec
let type_update_index = DaeTypeUpdateIndex::new(type_sec)
def_slot = 0
while def_slot < defs.length() {
let def_idx = defs[def_slot]
let callee_abs = imported + def_idx
let (params, results) = match
dae_resolve_func_type(current, type_indices, callee_abs) {
Some(@lib.FuncType(params, results)) => (params, results)
None => return None
}
let @lib.Func(locals, @lib.Expr(body)) = rewritten_funcs[def_idx]
let remove = removes[def_slot]
let mut param_idx = 0
while param_idx < remove.length() {
if remove[param_idx] {
let usage = dae_local_usage_evidence(
body,
@lib.LocalIdx::new(param_idx.reinterpret_as_uint()),
)
if usage.get_count != 0 || usage.write_count != 0 {
return None
}
}
param_idx += 1
}
let local_use = dae_local_use_for_callee(
rewritten_funcs[def_idx],
params.length() + locals.length(),
callee_abs,
params.length(),
)
let rewrite = match
dae_removed_param_callee_rewrite_evidence(
rewritten_funcs[def_idx],
params,
remove,
Array::make(params.length(), None),
local_use,
Array::make(params.length(), false),
) {
Some(rewrite) => rewrite
None => return None
}
rewritten_funcs[def_idx] = @lib.Func::new(
rewrite.locals,
@lib.Expr::new(rewrite.body),
)
let (next_type_sec, next_type_idx) = dae_update_defined_func_type(
current,
type_sec,
type_idxs,
def_idx,
rewrite.kept_params,
results,
type_update_index=Some(type_update_index),
)
type_sec = next_type_sec
type_idxs[def_idx] = next_type_idx
touched_out[def_idx] = true
def_slot += 1
}
Some({
mod_out: current
.with_type_sec(type_sec)
.with_func_sec(@lib.FuncSec::new(type_idxs))
.with_code_sec(@lib.CodeSec::new(rewritten_funcs))
.without_name_sec(),
touched: touched_out,
defs,
param_count: nodes.length(),
})
}
///|
fn dae_count_component_target_call_kinds(
funcs : Array[@lib.Func],
target_abs : Int32,
) -> (Int32, Int32) {
let mut direct_calls = 0
let mut return_calls = 0
for func in funcs {
let @lib.Func(_, @lib.Expr(body)) = func
let (body_direct, body_return) = dae_count_target_call_kinds_instrs(
body, target_abs,
)
direct_calls += body_direct
return_calls += body_return
}
(direct_calls, return_calls)
}
///|
fn dae_build_forwarded_param_component_rewrite_plan(
current : @lib.Module,
graph : DaeCurrentBoundaryGraph,
dependencies : DaeBoundaryDependencyGraph,
touched : Array[Bool],
nodes : Array[DaeForwardedParamNode],
boundary_facts : DaeCallBoundaryFacts,
original~ : DaeOriginalBoundarySnapshot? = None,
) -> DaeComponentRewritePlan? {
let base_candidate = match
dae_build_forwarded_param_component_candidate(
current, touched, nodes, boundary_facts,
) {
Some(value) => value
None => return None
}
let original_snapshot = match original {
Some(snapshot) => snapshot
None => dae_collect_original_boundary_snapshot(current)
}
let candidate_graph = dae_current_boundary_graph_new(base_candidate.mod_out)
let candidate_boundary_facts = candidate_graph.snapshot.boundary_facts
let ref_null_followup = dae_try_rewrite_uniform_ref_null_callee_from_selected_callers_with_facts(
base_candidate.mod_out,
base_candidate.defs,
candidate_graph.snapshot.imported,
candidate_graph.snapshot.defined,
candidate_graph.snapshot.imported + candidate_graph.snapshot.defined,
candidate_graph.snapshot.type_indices,
candidate_boundary_facts,
original=Some(original_snapshot),
graph=Some(candidate_graph),
)
let candidate = if ref_null_followup.changed {
{
mod_out: ref_null_followup.mod_out,
touched: dae_merge_touched(
base_candidate.touched,
ref_null_followup.touched,
),
defs: base_candidate.defs,
param_count: base_candidate.param_count,
}
} else {
base_candidate
}
let (expected_type_sec, expected_type_idxs, expected_funcs) = match
(current.type_sec, current.func_sec, current.code_sec) {
(Some(type_sec), Some(@lib.FuncSec(type_idxs)), Some(@lib.CodeSec(funcs))) =>
(type_sec, type_idxs, funcs)
_ => return None
}
if candidate.defs.length() < 2 {
return None
}
let component_id = if candidate.defs[0] >= 0 &&
candidate.defs[0] < dependencies.component_by_def.length() {
dependencies.component_by_def[candidate.defs[0]]
} else {
return None
}
let expected_members : Array[DaeComponentMemberExpectation] = []
let expected_callers : Array[DaeComponentCallerExpectation] = []
let expected_call_identities : Array[DaeDirectCallIdentity] = []
let shared_facts : DaeBoundaryFacts = {
original: original_snapshot,
graph,
incoming_param_live_by_def: [],
incoming_param_live_body_epochs: [],
}
for def_idx in candidate.defs {
if def_idx < 0 ||
def_idx >= graph.function_epochs.length() ||
def_idx >= dependencies.component_by_def.length() {
return None
}
let epochs = graph.function_epochs[def_idx]
let target_abs = graph.snapshot.imported + def_idx
if !dae_can_change_parameters(shared_facts, target_abs) {
return None
}
for
identity in dae_collect_target_call_identities(graph.snapshot, target_abs) {
expected_call_identities.push(identity)
}
let (direct_calls, return_calls) = dae_count_component_target_call_kinds(
graph.snapshot.funcs,
target_abs,
)
expected_members.push({
def_idx,
signature_epoch: epochs.signature_epoch,
body_epoch: epochs.body_epoch,
direct_calls,
return_calls,
})
if target_abs >= 0 &&
target_abs < graph.snapshot.call_facts.direct_callers.length() {
for caller_def in graph.snapshot.call_facts.direct_callers[target_abs] {
if caller_def < 0 || caller_def >= graph.function_epochs.length() {
return None
}
if !expected_callers.any(fn(expected) { expected.def_idx == caller_def }) {
let caller_epochs = graph.function_epochs[caller_def]
expected_callers.push({
def_idx: caller_def,
signature_epoch: caller_epochs.signature_epoch,
body_epoch: caller_epochs.body_epoch,
})
}
}
}
}
let effects = dae_commit_effects_empty(graph.snapshot.defined)
let mut effect_def = 0
while effect_def < candidate.touched.length() {
if candidate.touched[effect_def] {
effects.structurally_touched[effect_def] = true
effects.worth_decided[effect_def] = true
}
effect_def += 1
}
// The component transaction owns caller localization. Record scratch-local
// growth here rather than asking the DAEO pass manager to infer semantic worth
// from arbitrary before/after local counts.
match candidate.mod_out.code_sec {
Some(@lib.CodeSec(candidate_funcs)) => {
effect_def = 0
while effect_def < candidate.touched.length() &&
effect_def < expected_funcs.length() &&
effect_def < candidate_funcs.length() {
if candidate.touched[effect_def] && !candidate.defs.contains(effect_def) {
let @lib.Func(before_locals, _) = expected_funcs[effect_def]
let @lib.Func(after_locals, _) = candidate_funcs[effect_def]
if after_locals.length() > before_locals.length() {
effects.worth_optimizing[effect_def] = true
effects.body_localized[effect_def] = true
}
}
effect_def += 1
}
}
None => return None
}
for member_def in candidate.defs {
effects.worth_optimizing[member_def] = true
effects.parameter_changed[member_def] = true
effects.local_projection_changed[member_def] = true
}
if ref_null_followup.changed &&
ref_null_followup.primary_def >= 0 &&
ref_null_followup.primary_def < graph.snapshot.defined {
effects.worth_optimizing[ref_null_followup.primary_def] = true
effects.body_materialized[ref_null_followup.primary_def] = true
}
let effects = {
..effects,
type_section_changed: true,
metadata_changed: true,
invalidation_domains: [
DaeInvalidateCalls,
DaeInvalidateSignature,
DaeInvalidateBody,
DaeInvalidateLocals,
DaeInvalidateTypes,
DaeInvalidateMetadata,
DaeInvalidateNestedCleanup,
],
decision_reasons: [DaeCandidateProduced],
}
Some({
effects,
component_id,
member_defs: candidate.defs.copy(),
expected_module_epoch: graph.module_epoch,
expected_call_epoch: graph.call_epoch,
expected_type_epoch: graph.type_epoch,
expected_call_identities,
expected_members,
expected_callers,
expected_type_sec,
expected_type_idxs: expected_type_idxs.copy(),
expected_funcs: expected_funcs.copy(),
candidate,
})
}
///|
fn dae_apply_component_rewrite_plan(
current : @lib.Module,
graph : DaeCurrentBoundaryGraph,
plan : DaeComponentRewritePlan,
) -> DaeComponentRewritePlanApplication {
if plan.expected_module_epoch != graph.module_epoch ||
plan.expected_call_epoch != graph.call_epoch ||
plan.expected_type_epoch != graph.type_epoch {
return DaeComponentPlanRejected(current, DaeStaleCandidateFacts)
}
match (current.type_sec, current.func_sec, current.code_sec) {
(Some(type_sec), Some(@lib.FuncSec(type_idxs)), Some(@lib.CodeSec(funcs))) =>
if type_sec != plan.expected_type_sec ||
type_idxs != plan.expected_type_idxs ||
!dae_snapshot_funcs_unchanged(funcs, plan.expected_funcs) {
return DaeComponentPlanRejected(current, DaeStaleCandidateFacts)
}
_ =>
return DaeComponentPlanRejected(current, DaeTypeReconstructionUnavailable)
}
for expected in plan.expected_members {
if expected.def_idx < 0 ||
expected.def_idx >= graph.function_epochs.length() ||
expected.def_idx >= graph.snapshot.defined {
return DaeComponentPlanRejected(current, DaeStaleCandidateFacts)
}
let epochs = graph.function_epochs[expected.def_idx]
if epochs.signature_epoch != expected.signature_epoch ||
epochs.body_epoch != expected.body_epoch {
return DaeComponentPlanRejected(current, DaeStaleCandidateFacts)
}
let target_abs = graph.snapshot.imported + expected.def_idx
let (direct_calls, return_calls) = dae_count_component_target_call_kinds(
graph.snapshot.funcs,
target_abs,
)
if direct_calls != expected.direct_calls ||
return_calls != expected.return_calls {
return DaeComponentPlanRejected(current, DaeCallCountChanged)
}
if expected.def_idx >= plan.candidate.touched.length() ||
!plan.candidate.touched[expected.def_idx] {
return DaeComponentPlanRejected(current, DaeTypeReconstructionUnavailable)
}
}
let actual_call_identities : Array[DaeDirectCallIdentity] = []
for member_def in plan.member_defs {
let target_abs = graph.snapshot.imported + member_def
for
identity in dae_collect_target_call_identities(graph.snapshot, target_abs) {
actual_call_identities.push(identity)
}
}
if !dae_direct_call_identities_equal(
actual_call_identities,
plan.expected_call_identities,
) {
return DaeComponentPlanRejected(current, DaeCallCountChanged)
}
for expected in plan.expected_callers {
if expected.def_idx < 0 ||
expected.def_idx >= graph.function_epochs.length() {
return DaeComponentPlanRejected(current, DaeStaleCandidateFacts)
}
let epochs = graph.function_epochs[expected.def_idx]
if epochs.signature_epoch != expected.signature_epoch ||
epochs.body_epoch != expected.body_epoch {
return DaeComponentPlanRejected(current, DaeStaleCandidateFacts)
}
}
match @validate.validate_module(plan.candidate.mod_out) {
Ok(_) =>
DaeComponentPlanCommitted({
candidate: plan.candidate,
effects: {
..plan.effects,
candidates_produced: 1,
candidates_applied: 1,
decision_reasons: [DaeCandidateProduced, DaeCandidateApplied],
},
})
Err(_) => DaeComponentPlanRejected(current, DaeValidationRollback)
}
}
///|
fn dae_run_forwarded_param_component_transaction_with_graph(
original : DaeOriginalBoundarySnapshot,
current : @lib.Module,
touched : Array[Bool],
cycle_defs : Array[Int32],
graph : DaeCurrentBoundaryGraph,
dependencies : DaeBoundaryDependencyGraph,
flow_solves~ : Array[Int32]? = None,
) -> DaeComponentRewritePlanCommit? {
if cycle_defs.length() < 2 {
return None
}
let boundary_facts = graph.snapshot.boundary_facts
let nodes = dae_collect_forwarded_param_component(
current,
cycle_defs,
graph.snapshot.type_indices,
boundary_facts,
)
// The candidate builder rejects this shape independently of flow facts.
if nodes.length() < 2 {
return None
}
match flow_solves {
Some(count) if !count.is_empty() => count[0] += 1
_ => ()
}
let flow = dae_solve_parameter_flow(graph, dependencies, seed_defs=cycle_defs)
for node in nodes {
let node_def = node.callee_abs - graph.snapshot.imported
match dae_parameter_flow_state_at(flow, node_def, node.param_idx) {
DaeParameterConflict => return None
DaeParameterUnseen
| DaeParameterForwardedOnly
| DaeParameterUniform(_)
| DaeParameterBlocked => ()
}
}
let plan = match
dae_build_forwarded_param_component_rewrite_plan(
current,
graph,
dependencies,
touched,
nodes,
boundary_facts,
original=Some(original),
) {
Some(plan) => plan
None => return None
}
let application = match
dae_apply_component_rewrite_plan(current, graph, plan) {
DaeComponentPlanCommitted(application) => application
DaeComponentPlanRejected(_, _) => return None
}
Some(application)
}
///|
fn dae_collect_forwarded_param_dead_callee_candidates_instrs(
instrs : Array[@lib.Instruction],
entry_stack_values : Int32,
current : @lib.Module,
caller_param_count : Int32,
cycle_selected : Array[Bool],
type_indices : Array[@lib.TypeIdx],
candidates : Array[Array[Bool]?],
) -> Unit {
let imported = dae_imported_func_count(current)
let defined = dae_defined_func_count(current)
let mut idx = 0
while idx < instrs.length() {
match instrs[idx] {
@lib.Call(func_idx) => {
let callee_abs = dae_func_idx_slot(func_idx)
let callee_def = callee_abs - imported
if callee_def >= 0 &&
callee_def < defined &&
callee_def < candidates.length() &&
(callee_def >= cycle_selected.length() || !cycle_selected[callee_def]) {
let params = match
dae_resolve_func_type(current, type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params
None => []
}
if params.length() > 0 {
match
dae_callsite_value_slice_evidence(
instrs,
idx,
params.length(),
entry_stack_values,
current,
type_indices,
) {
Some(evidence) => {
let mut param_idx = 0
while param_idx < params.length() {
match
dae_callsite_single_instruction_actual(
evidence, param_idx, instrs,
) {
Some(actual) =>
match actual.instr {
@lib.LocalGet(local_idx) => {
let local_slot = dae_local_idx_slot(local_idx)
if local_slot >= 0 && local_slot < caller_param_count {
let mask = match candidates[callee_def] {
Some(mask) => mask
None => Array::make(params.length(), false)
}
if param_idx < mask.length() {
mask[param_idx] = true
candidates[callee_def] = Some(mask)
}
}
}
_ => ()
}
None => ()
}
param_idx += 1
}
}
None => ()
}
}
}
}
@lib.Block(_, @lib.Expr(_))
| @lib.Loop(_, @lib.Expr(_))
| @lib.TryTable(_, _, @lib.Expr(_))
| @lib.If(_, _, _) =>
match dae_nested_control_children_evidence(instrs[idx], current) {
Some(children) => {
dae_collect_forwarded_param_dead_callee_candidates_instrs(
children.primary_body,
children.entry_stack_values,
current,
caller_param_count,
cycle_selected,
type_indices,
candidates,
)
match children.secondary_body {
Some(body) =>
dae_collect_forwarded_param_dead_callee_candidates_instrs(
body,
children.entry_stack_values,
current,
caller_param_count,
cycle_selected,
type_indices,
candidates,
)
None => ()
}
}
None => ()
}
_ => ()
}
idx += 1
}
}
///|
fn dae_collect_forwarded_param_dead_callee_defs(
current : @lib.Module,
cycle_defs : Array[Int32],
) -> Array[Int32] {
let defined = dae_defined_func_count(current)
if defined == 0 || cycle_defs.length() == 0 {
return []
}
let imported = dae_imported_func_count(current)
let funcs = match current.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
let type_indices = dae_collect_func_type_indices(current)
let cycle_selected = Array::make(defined, false)
for def_idx in cycle_defs {
if def_idx >= 0 && def_idx < defined {
cycle_selected[def_idx] = true
}
}
let candidates : Array[Array[Bool]?] = Array::make(defined, None)
for caller_def in cycle_defs {
if caller_def < 0 || caller_def >= funcs.length() {
continue
}
let caller_abs = imported + caller_def
let caller_params = match
dae_resolve_func_type(current, type_indices, caller_abs) {
Some(@lib.FuncType(params, _)) => params
None => continue
}
let @lib.Func(_, @lib.Expr(body)) = funcs[caller_def]
dae_collect_forwarded_param_dead_callee_candidates_instrs(
body,
0,
current,
caller_params.length(),
cycle_selected,
type_indices,
candidates,
)
}
let sink_defs : Array[Int32] = []
let mut def_idx = 0
while def_idx < defined {
match candidates[def_idx] {
Some(mask) => {
let callee_abs = imported + def_idx
let params = match
dae_resolve_func_type(current, type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params
None => []
}
let @lib.Func(locals, _) = funcs[def_idx]
let local_use = dae_local_use_for_callee(
funcs[def_idx],
params.length() + locals.length(),
callee_abs,
params.length(),
)
let mut param_idx = 0
let mut has_dead_sink = false
while param_idx < mask.length() && param_idx < local_use.gets.length() {
if mask[param_idx] && !local_use.gets[param_idx] {
has_dead_sink = true
}
param_idx += 1
}
if has_dead_sink {
sink_defs.push(def_idx)
}
}
None => ()
}
def_idx += 1
}
sink_defs
}
///|
#warnings("-unused_value")
fn dae_run_forwarded_param_dead_callee_convergence(
current : @lib.Module,
touched : Array[Bool],
cycle_defs : Array[Int32],
) -> DaeForwardedParamDeadCalleeApplication? {
let defined = dae_defined_func_count(current)
if defined == 0 || cycle_defs.length() < 2 {
return None
}
let total = dae_imported_func_count(current) + defined
let mut mod_out = current
let mut touched_out = if touched.length() == defined {
touched.copy()
} else {
Array::make(defined, false)
}
let sink_defs : Array[Int32] = []
while true {
let sink_candidates = dae_collect_forwarded_param_dead_callee_defs(
mod_out, cycle_defs,
)
if sink_candidates.length() == 0 {
break
}
let sink_selected = Array::make(defined, false)
for def_idx in sink_candidates {
if def_idx >= 0 && def_idx < defined {
sink_selected[def_idx] = true
}
}
let mut round_changed = false
while true {
let call_facts = dae_collect_current_call_facts(mod_out, total)
let result = dae_try_rewrite_selected_defs_unread_params_with_facts_once(
mod_out, sink_selected, call_facts,
)
if !result.changed {
break
}
mod_out = result.mod_out
touched_out = dae_merge_touched(touched_out, result.touched)
if result.primary_def >= 0 && !sink_defs.contains(result.primary_def) {
sink_defs.push(result.primary_def)
}
round_changed = true
}
if !round_changed {
break
}
}
if sink_defs.length() == 0 {
return None
}
let cycle_selected = Array::make(defined, false)
for def_idx in cycle_defs {
if def_idx >= 0 && def_idx < defined {
cycle_selected[def_idx] = true
}
}
let productive_cycle_defs : Array[Int32] = []
while true {
let call_facts = dae_collect_current_call_facts(mod_out, total)
let result = dae_try_rewrite_selected_defs_unread_params_with_facts_once(
mod_out, cycle_selected, call_facts,
)
if !result.changed {
break
}
mod_out = result.mod_out
touched_out = dae_merge_touched(touched_out, result.touched)
if result.primary_def >= 0 &&
!productive_cycle_defs.contains(result.primary_def) {
productive_cycle_defs.push(result.primary_def)
}
}
Some({
mod_out,
touched: touched_out,
sink_defs,
cycle_defs: productive_cycle_defs,
})
}
///|
fn dae_run_forwarded_param_scc_transaction(
current : @lib.Module,
touched : Array[Bool],
) -> DaeForwardedParamSccApplication? {
let imported = dae_imported_func_count(current)
let defined = dae_defined_func_count(current)
if defined == 0 || touched.length() != defined {
return None
}
let total = imported + defined
let call_facts = dae_collect_current_call_facts(current, total)
let boundary_facts = dae_call_boundary_facts_from_current(call_facts)
let type_indices = dae_collect_func_type_indices(current)
let mut seed_def = 0
while seed_def < defined {
if !touched[seed_def] {
seed_def += 1
continue
}
let callee_abs = imported + seed_def
let params = match
dae_resolve_func_type(current, type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params
None => {
seed_def += 1
continue
}
}
let mut seed_param = 0
while seed_param < params.length() {
let nodes : Array[DaeForwardedParamNode] = []
let states : Array[Int32] = []
let cycle_nodes : Array[DaeForwardedParamNode] = []
let constant : Array[@lib.Instruction?] = [None]
let flags = [true, false, false]
let collected = dae_collect_forwarded_param_scc_node(
current, callee_abs, seed_param, type_indices, boundary_facts, nodes, states,
cycle_nodes, constant, flags,
)
if collected && flags[1] && flags[2] {
match constant[0] {
Some(value) =>
match
dae_apply_forwarded_param_scc_transaction(
current, touched, nodes, value, boundary_facts,
) {
Some(application) => return Some(application)
None => ()
}
None => ()
}
}
seed_param += 1
}
seed_def += 1
}
None
}
///|
fn dae_collect_broad_uniform_candidate_defs(
candidate_source : @lib.Module,
) -> Array[Int32] {
let candidates : Array[Int32] = []
let context = match
dae_exact_literal_candidate_context_new(candidate_source) {
Some(context) => context
None => return candidates
}
let mut candidate_def = 0
while candidate_def < context.funcs.length() {
if dae_exact_literal_candidate_is_single_param_identity(
candidate_source,
candidate_def,
context=Some(context),
) ||
dae_exact_literal_candidate_is_single_param_call_sink(
candidate_source,
candidate_def,
context=Some(context),
) ||
dae_exact_literal_candidate_is_single_param_pure_expression(
candidate_source,
candidate_def,
context=Some(context),
) ||
dae_exact_literal_candidate_has_scalar_constructor_shape(
candidate_source,
candidate_def,
context=Some(context),
) {
candidates.push(candidate_def)
}
candidate_def += 1
}
candidates
}
///|
fn dae_run_broad_uniform_parameter_flow(
current : @lib.Module,
candidate_source : @lib.Module,
touched : Array[Bool],
changed : Bool,
) -> DaeExactLiteralWorklistApplication {
let candidates = dae_collect_broad_uniform_candidate_defs(candidate_source)
dae_run_exact_literal_candidate_worklist_with_facts(
current, touched, changed, candidates,
)
}
///|
fn dae_collect_applied_call_sink_uniform_defs(
candidate_source : @lib.Module,
current : @lib.Module,
) -> Array[Int32] {
let candidates : Array[Int32] = []
let defined = dae_defined_func_count(current)
if defined == 0 || dae_defined_func_count(candidate_source) == 0 {
return candidates
}
// Discovery borrows one immutable source snapshot; no rewrite runs here.
let context = match
dae_exact_literal_candidate_context_new(candidate_source) {
Some(context) => context
None => return candidates
}
let source_imported = context.imported
let current_imported = dae_imported_func_count(current)
let source_types = context.type_indices
let current_types = dae_collect_func_type_indices(current)
let source_defined = context.funcs.length()
let mut def_idx = 0
while def_idx < defined && def_idx < source_defined {
if dae_exact_literal_candidate_is_single_param_call_sink(
candidate_source,
def_idx,
context=Some(context),
) {
match
(
dae_resolve_func_type(
candidate_source,
source_types,
source_imported + def_idx,
),
dae_resolve_func_type(
current,
current_types,
current_imported + def_idx,
),
) {
(
Some(@lib.FuncType(source_params, _)),
Some(@lib.FuncType(current_params, _)),
) if current_params.length() < source_params.length() =>
candidates.push(def_idx)
_ => ()
}
}
def_idx += 1
}
candidates
}
///|
fn dae_run_call_sink_uniform_parameter_flow(
current : @lib.Module,
candidate_source : @lib.Module,
touched : Array[Bool],
changed : Bool,
) -> DaeExactLiteralWorklistApplication {
let defined = dae_defined_func_count(current)
let candidates : Array[Int32] = []
let source_defined = dae_defined_func_count(candidate_source)
// The worklist mutates current only after this discovery loop is complete.
let context = if defined > 0 && source_defined > 0 {
dae_exact_literal_candidate_context_new(candidate_source)
} else {
None
}
let mut candidate_def = 0
while candidate_def < defined && candidate_def < source_defined {
if dae_exact_literal_candidate_is_single_param_call_sink(
candidate_source,
candidate_def,
context~,
) {
candidates.push(candidate_def)
}
candidate_def += 1
}
dae_run_exact_literal_candidate_worklist_with_facts(
current, touched, changed, candidates,
)
}
///|
fn dae_apply_exact_literal_family_once(
original_source : @lib.Module,
current : @lib.Module,
def_idx : Int32,
touched : Array[Bool],
changed : Bool,
allow_immutable_global_get~ : Bool = false,
allow_immediate_i32_suffix_fallback~ : Bool = false,
) -> DaeCoreResultApplication {
let original = dae_collect_original_boundary_snapshot(original_source)
let graph = dae_current_boundary_graph_new(current)
let dependencies = dae_boundary_dependency_graph_new(graph)
let item = {
..dae_work_item_new(graph, dependencies, def_idx, DaeUniformActualWork),
allow_immutable_global_get,
allow_immediate_i32_suffix_fallback,
}
let diagnostics = dae_diagnostics_new()
match
dae_process_work_item(
original, current, touched, changed, graph, dependencies, item, diagnostics,
) {
DaeWorkCommitted(application) => application
_ =>
{
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(graph.snapshot.defined),
}
}
}
///|
fn dae_run_broad_exact_param_adjacent_cleanup_root(
candidate_source : @lib.Module,
current : @lib.Module,
root_defs : Array[Int32],
touched : Array[Bool],
changed : Bool,
trace : (String) -> Unit,
) -> DaeBroadExactParamAdjacentCleanupApplication {
let defined = dae_defined_func_count(current)
if root_defs.is_empty() {
trace(
"pass[dae-optimizing]:broad-exact-param-adjacent-candidates roots=0 pairs=0",
)
return {
mod_out: current,
touched,
candidate: None,
effects: dae_commit_effects_empty(defined),
}
}
let imported = dae_imported_func_count(current)
let total = imported + defined
let call_facts = dae_collect_current_call_facts(current, total)
let source_imported = dae_imported_func_count(candidate_source)
let source_total = source_imported + dae_defined_func_count(candidate_source)
let source_call_facts = dae_collect_current_call_facts(
candidate_source, source_total,
)
let source_type_indices = dae_collect_func_type_indices(candidate_source)
let current_type_indices = dae_collect_func_type_indices(current)
let original = dae_collect_original_boundary_snapshot(candidate_source)
let current_graph = dae_current_boundary_graph_new(current)
let source_original = dae_collect_original_boundary_snapshot(candidate_source)
let source_graph = dae_current_boundary_graph_new(candidate_source)
let neighborhood_candidates = dae_collect_broad_exact_param_adjacent_cleanup_candidates(
current, root_defs,
)
let successful_roots = Array::make(defined, false)
let successful_pairs = Array::make(defined, false)
let mut root_count = 0
let mut pair_count = 0
let mut best_root = -1
let mut best_aggregates = -1
let mut best_payoff = -1
let mut best_candidate : DaeBroadExactParamAdjacentCleanupCandidate? = None
let mut best_root_already_applied = false
for candidate in neighborhood_candidates {
let root_result = dae_try_rewrite_exact_literal_def_with_facts_once(
current,
candidate.root_def,
call_facts,
original=Some(original),
graph=Some(current_graph),
)
let root_already_applied = if !root_result.changed {
let source_result = dae_try_rewrite_exact_literal_def_with_facts_once(
candidate_source,
candidate.root_def,
source_call_facts,
original=Some(source_original),
graph=Some(source_graph),
)
let source_abs = source_imported + candidate.root_def
let current_abs = imported + candidate.root_def
source_result.changed &&
(match
(
dae_resolve_func_type(
candidate_source, source_type_indices, source_abs,
),
dae_resolve_func_type(current, current_type_indices, current_abs),
) {
(
Some(@lib.FuncType(source_params, _)),
Some(@lib.FuncType(current_params, _)),
) => current_params.length() < source_params.length()
_ => false
})
} else {
false
}
if !root_result.changed && !root_already_applied {
continue
}
if !successful_roots[candidate.root_def] {
successful_roots[candidate.root_def] = true
root_count += 1
}
if !successful_pairs[candidate.first_def] {
successful_pairs[candidate.first_def] = true
pair_count += 1
}
let aggregates = dae_exact_param_aggregate_constructor_count(
current,
candidate.root_def,
)
if aggregates > best_aggregates ||
(aggregates == best_aggregates && candidate.payoff > best_payoff) {
best_root = candidate.root_def
best_aggregates = aggregates
best_payoff = candidate.payoff
best_candidate = Some(candidate)
best_root_already_applied = root_already_applied
}
}
trace(
"pass[dae-optimizing]:broad-exact-param-adjacent-candidates roots=\{root_count} pairs=\{pair_count}",
)
if best_root < 0 {
return {
mod_out: current,
touched,
candidate: None,
effects: dae_commit_effects_empty(defined),
}
}
if best_root_already_applied {
let effects = dae_commit_effects_empty(defined)
match best_candidate {
Some(candidate) =>
for cleanup_def in [candidate.first_def, candidate.second_def] {
if cleanup_def >= 0 && cleanup_def < defined {
effects.worth_decided[cleanup_def] = true
effects.worth_optimizing[cleanup_def] = true
}
}
None => ()
}
return { mod_out: current, touched, candidate: best_candidate, effects, }
}
let root_application = dae_apply_exact_literal_family_once(
candidate_source, current, best_root, touched, changed,
)
if !root_application.applied {
return {
mod_out: current,
touched,
candidate: None,
effects: dae_commit_effects_empty(defined),
}
}
let effects = root_application.effects
match best_candidate {
Some(candidate) =>
for cleanup_def in [candidate.first_def, candidate.second_def] {
if cleanup_def >= 0 && cleanup_def < defined {
effects.worth_decided[cleanup_def] = true
effects.worth_optimizing[cleanup_def] = true
}
}
None => ()
}
{
mod_out: root_application.mod_out,
touched: root_application.touched,
candidate: best_candidate,
effects,
}
}
///|
fn dae_append_result_dependency_postorder(
dependencies : DaeBoundaryDependencyGraph,
def_idx : Int32,
queued : Array[Bool],
queue : Array[Int32],
) -> Unit {
if def_idx < 0 ||
def_idx >= dependencies.result_dependencies_by_def.length() ||
queued[def_idx] {
return
}
queued[def_idx] = true
for dependency_def in dependencies.result_dependencies_by_def[def_idx] {
if dependency_def != def_idx {
dae_append_result_dependency_postorder(
dependencies, dependency_def, queued, queue,
)
}
}
queue.push(def_idx)
}
///|
fn dae_run_direct_gc_result_refinement_dependency_closure(
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
seed_defs : Array[Int32],
) -> DaeExactLiteralWorklistApplication {
let defined = dae_defined_func_count(current)
let productive_defs : Array[Int32] = []
let mut effects = dae_commit_effects_empty(defined)
let mut mod_out = current
let mut touched_out = touched
let mut changed_out = changed
if !dae_candidate_worklist_requires_facts(seed_defs) {
return { mod_out: current, touched, changed, productive_defs, effects, }
}
let original = dae_collect_original_boundary_snapshot(current)
let graph = dae_current_boundary_graph_new(current)
let mut dependencies = dae_boundary_dependency_graph_new(graph)
let queue : Array[Int32] = []
let queued = Array::make(defined, false)
for seed_def in seed_defs {
dae_append_result_dependency_postorder(
dependencies, seed_def, queued, queue,
)
}
let seen = Array::make(defined, false)
let diagnostics = dae_diagnostics_new()
let mut queue_idx = 0
while queue_idx < queue.length() {
let def_idx = queue[queue_idx]
queue_idx += 1
if def_idx < 0 || def_idx >= defined || seen[def_idx] {
continue
}
seen[def_idx] = true
let before_forwarders = if def_idx <
dependencies.result_forwarders_by_def.length() {
dependencies.result_forwarders_by_def[def_idx].copy()
} else {
[]
}
let item = dae_work_item_new(
graph,
dependencies,
def_idx,
DaeGcResultRefinementWork,
)
match
dae_process_work_item(
original, mod_out, touched_out, changed_out, graph, dependencies, item, diagnostics,
) {
DaeWorkCommitted(application) => {
let settled = dae_apply_commit_followups(
original, application, item, diagnostics,
)
mod_out = settled.mod_out
touched_out = settled.touched
changed_out = settled.changed
productive_defs.push(settled.primary_def)
effects = dae_commit_effects_merge(effects, settled.effects)
dae_current_boundary_graph_refresh(graph, mod_out)
dependencies = dae_boundary_dependency_graph_new(graph)
let forwarders = before_forwarders
if def_idx < dependencies.result_forwarders_by_def.length() {
for forwarder_def in dependencies.result_forwarders_by_def[def_idx] {
if !forwarders.contains(forwarder_def) {
forwarders.push(forwarder_def)
}
}
}
for forwarder_def in forwarders {
if forwarder_def >= 0 &&
forwarder_def < defined &&
!seen[forwarder_def] {
queue.push(forwarder_def)
}
}
}
_ => ()
}
}
{
mod_out,
touched: touched_out,
changed: changed_out,
productive_defs,
effects,
}
}
///|
fn dae_instr_references_any_local(
instr : @lib.Instruction,
locals : Array[@lib.LocalIdx],
) -> Bool {
for local_idx in locals {
if dae_instr_references_local(instr, local_idx) {
return true
}
}
false
}
///|
fn dae_instrs_reference_any_local_range(
instrs : Array[@lib.Instruction],
start : Int32,
end_exclusive : Int32,
locals : Array[@lib.LocalIdx],
) -> Bool {
let mut idx = start
while idx < end_exclusive && idx < instrs.length() {
if dae_instr_references_any_local(instrs[idx], locals) {
return true
}
idx += 1
}
false
}
///|
fn dae_local_idx_array_contains(
locals : Array[@lib.LocalIdx],
needle : @lib.LocalIdx,
) -> Bool {
for local_idx in locals {
if dae_local_idx_equal(local_idx, needle) {
return true
}
}
false
}
///|
fn dae_copy_instr_range(
instrs : Array[@lib.Instruction],
start : Int32,
end_exclusive : Int32,
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
let mut idx = start
while idx < end_exclusive && idx < instrs.length() {
out.push(instrs[idx])
idx += 1
}
out
}
///|
fn dae_fold_ref_null_is_null_if_instrs(
body : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < body.length() {
if idx + 2 < body.length() {
match (body[idx], body[idx + 1], body[idx + 2]) {
(@lib.RefNull(_), @lib.RefIsNull, @lib.If(block_type, then_body, _)) => {
dae_append_selected_if_body(out, block_type, then_body)
changed = true
idx += 3
continue
}
_ => ()
}
}
out.push(body[idx])
idx += 1
}
(out, changed)
}
///|
fn dae_fold_i64_sentinel_wrap_if_instrs(
body : Array[@lib.Instruction],
local_gets : Array[Int32],
local_sets : Array[Int32],
local_tees : Array[Int32],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < body.length() {
if idx + 4 < body.length() {
match
(body[idx], body[idx + 1], body[idx + 2], body[idx + 3], body[idx + 4]) {
(
@lib.LocalGet(source),
@lib.I64Const(@lib.I64(4294967296L)),
@lib.I64Eq,
@lib.If(
@lib.ValTypeBlockType(@lib.NumTypeValType(@lib.I32NumType)),
[@lib.I32Const(@lib.I32(0))],
Some(
[
@lib.LocalGet(else_source),
@lib.LocalTee(@lib.LocalIdx(tee_raw)),
@lib.I32WrapI64,
]
)
),
@lib.LocalSet(output),
) if source == else_source => {
let tee_idx = tee_raw.reinterpret_as_int()
if tee_idx >= 0 &&
tee_idx < local_gets.length() &&
local_gets[tee_idx] == 0 &&
local_sets[tee_idx] == 0 &&
local_tees[tee_idx] == 1 {
out.push(@lib.Instruction::local_get(source))
out.push(@lib.Instruction::i32_wrap_i64())
out.push(@lib.Instruction::i32_const(@lib.I32(0)))
out.push(@lib.Instruction::local_get(source))
out.push(@lib.Instruction::i64_const(@lib.I64(4294967296L)))
out.push(@lib.Instruction::i64_ne())
out.push(@lib.Instruction::select())
out.push(@lib.Instruction::local_set(output))
local_tees[tee_idx] = 0
changed = true
idx += 5
continue
}
}
_ => ()
}
}
out.push(body[idx])
idx += 1
}
(out, changed)
}
///|
fn dae_try_sink_terminal_default_aggregate_local_body_once(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 11 {
return None
}
let start = body.length() - 11
let aggregate = match
(
body[start],
body[start + 1],
body[start + 2],
body[start + 3],
body[start + 4],
body[start + 5],
body[start + 6],
body[start + 7],
body[start + 8],
body[start + 9],
body[start + 10],
) {
(
@lib.ArrayNewFixed(_, @lib.U32(0U)),
@lib.I32Const(@lib.I32(0)),
@lib.StructNew(_),
@lib.LocalSet(aggregate),
@lib.LocalGet(arg0),
@lib.LocalGet(arg1),
@lib.LocalGet(arg2),
@lib.LocalGet(arg3),
@lib.LocalGet(arg4),
@lib.LocalGet(aggregate_read),
@lib.Call(_),
) if aggregate == aggregate_read &&
aggregate != arg0 &&
aggregate != arg1 &&
aggregate != arg2 &&
aggregate != arg3 &&
aggregate != arg4 => aggregate
_ => return None
}
let local_count = dae_next_free_local_idx(body, 0)
let local_gets : Array[Int32] = Array::make(local_count, 0)
let local_sets : Array[Int32] = Array::make(local_count, 0)
let local_tees : Array[Int32] = Array::make(local_count, 0)
run_hot_pipeline_raw_collect_local_ref_counts(
body, local_gets, local_sets, local_tees,
)
let aggregate_idx = dae_local_idx_slot(aggregate)
if aggregate_idx < 0 ||
aggregate_idx >= local_count ||
local_gets[aggregate_idx] != 1 ||
local_sets[aggregate_idx] != 1 ||
local_tees[aggregate_idx] != 0 {
return None
}
let out = dae_copy_instr_range(body, 0, start)
out.append(dae_copy_instr_range(body, start + 4, start + 9))
out.append(dae_copy_instr_range(body, start, start + 3))
out.push(body[start + 10])
Some(out)
}
///|
fn dae_terminal_sentinel_select_producer_source(
body : Array[@lib.Instruction],
start : Int32,
) -> @lib.LocalIdx? {
if start < 0 || start + 6 >= body.length() {
return None
}
match
(
body[start],
body[start + 1],
body[start + 2],
body[start + 3],
body[start + 4],
body[start + 5],
body[start + 6],
) {
(
@lib.LocalGet(source),
@lib.I32WrapI64,
@lib.I32Const(@lib.I32(0)),
@lib.LocalGet(source_read),
@lib.I64Const(@lib.I64(4294967296L)),
@lib.I64Ne,
@lib.Select(_),
) if source == source_read => Some(source)
_ => None
}
}
///|
fn dae_try_sink_terminal_first_sentinel_local_body_once(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 25 {
return None
}
let start = body.length() - 25
match dae_terminal_sentinel_select_producer_source(body, start) {
Some(_) => ()
None => return None
}
match dae_terminal_sentinel_select_producer_source(body, start + 8) {
Some(_) => ()
None => return None
}
let first = match body[start + 7] {
@lib.LocalSet(local_idx) => local_idx
_ => return None
}
let second = match body[start + 15] {
@lib.LocalSet(local_idx) => local_idx
_ => return None
}
match
(
body[start + 16],
body[start + 17],
body[start + 18],
body[start + 19],
body[start + 20],
body[start + 21],
body[start + 22],
body[start + 23],
body[start + 24],
) {
(
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(_),
@lib.LocalGet(first_read),
@lib.LocalGet(second_read),
@lib.ArrayNewFixed(_, @lib.U32(0U)),
@lib.I32Const(@lib.I32(0)),
@lib.StructNew(_),
@lib.Call(_),
) if first == first_read && second == second_read && first != second => ()
_ => return None
}
let local_count = dae_next_free_local_idx(body, 0)
let local_gets : Array[Int32] = Array::make(local_count, 0)
let local_sets : Array[Int32] = Array::make(local_count, 0)
let local_tees : Array[Int32] = Array::make(local_count, 0)
run_hot_pipeline_raw_collect_local_ref_counts(
body, local_gets, local_sets, local_tees,
)
let first_idx = dae_local_idx_slot(first)
if first_idx < 0 ||
first_idx >= local_count ||
local_gets[first_idx] != 1 ||
local_sets[first_idx] != 1 ||
local_tees[first_idx] != 0 {
return None
}
let out = dae_copy_instr_range(body, 0, start)
out.append(dae_copy_instr_range(body, start + 16, start + 19))
out.append(dae_copy_instr_range(body, start, start + 7))
out.append(dae_copy_instr_range(body, start + 8, start + 16))
out.push(body[start + 20])
out.append(dae_copy_instr_range(body, start + 21, start + 24))
out.push(body[start + 24])
Some(out)
}
///|
fn dae_try_sink_terminal_second_sentinel_local_body_once(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
if body.length() < 23 {
return None
}
let start = body.length() - 23
match (body[start], body[start + 1], body[start + 2]) {
(@lib.LocalGet(_), @lib.LocalGet(_), @lib.LocalGet(_)) => ()
_ => return None
}
match dae_terminal_sentinel_select_producer_source(body, start + 3) {
Some(_) => ()
None => return None
}
match dae_terminal_sentinel_select_producer_source(body, start + 10) {
Some(_) => ()
None => return None
}
let second = match (body[start + 17], body[start + 18]) {
(@lib.LocalSet(second), @lib.LocalGet(second_read)) if second == second_read =>
second
_ => return None
}
match
(body[start + 19], body[start + 20], body[start + 21], body[start + 22]) {
(
@lib.ArrayNewFixed(_, @lib.U32(0U)),
@lib.I32Const(@lib.I32(0)),
@lib.StructNew(_),
@lib.Call(_),
) => ()
_ => return None
}
let local_count = dae_next_free_local_idx(body, 0)
let local_gets : Array[Int32] = Array::make(local_count, 0)
let local_sets : Array[Int32] = Array::make(local_count, 0)
let local_tees : Array[Int32] = Array::make(local_count, 0)
run_hot_pipeline_raw_collect_local_ref_counts(
body, local_gets, local_sets, local_tees,
)
let second_idx = dae_local_idx_slot(second)
if second_idx < 0 ||
second_idx >= local_count ||
local_gets[second_idx] != 1 ||
local_sets[second_idx] != 1 ||
local_tees[second_idx] != 0 {
return None
}
let out = dae_copy_instr_range(body, 0, start + 17)
out.append(dae_copy_instr_range(body, start + 19, start + 22))
out.push(body[start + 22])
Some(out)
}
///|
fn dae_try_inline_terminal_call_local_sets_body_once(
body : Array[@lib.Instruction],
arg_count : Int32,
protected_local_slots : Int32,
) -> Array[@lib.Instruction]? {
if arg_count <= 0 || body.length() <= arg_count {
return None
}
match body[body.length() - 1] {
@lib.Call(_) => ()
_ => return None
}
let arg_start = body.length() - 1 - arg_count
let temps : Array[@lib.LocalIdx] = []
let mut arg_idx = arg_start
while arg_idx < body.length() - 1 {
match body[arg_idx] {
@lib.LocalGet(local_idx) if dae_local_idx_slot(local_idx) >=
protected_local_slots &&
!dae_local_idx_array_contains(temps, local_idx) => temps.push(local_idx)
_ => return None
}
arg_idx += 1
}
let segments : Array[Array[@lib.Instruction]] = []
let mut segment_start = 0
for temp in temps {
let mut set_idx = segment_start
let mut found = false
while set_idx < arg_start {
match body[set_idx] {
@lib.LocalSet(local_idx) if dae_local_idx_equal(local_idx, temp) => {
found = true
break
}
_ => ()
}
set_idx += 1
}
if !found || set_idx == segment_start {
return None
}
if dae_instrs_reference_any_local_range(body, segment_start, set_idx, temps) {
return None
}
segments.push(dae_copy_instr_range(body, segment_start, set_idx))
segment_start = set_idx + 1
}
if segment_start != arg_start {
return None
}
let out : Array[@lib.Instruction] = []
for segment in segments {
out.append(segment)
}
out.push(body[body.length() - 1])
Some(out)
}
///|
fn dae_remove_nops_instr(instr : @lib.Instruction) -> (@lib.Instruction, Bool) {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (rewritten, changed) = dae_remove_nops_instrs(body)
(@lib.Instruction::block(block_type, @lib.Expr::new(rewritten)), changed)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (rewritten, changed) = dae_remove_nops_instrs(body)
(@lib.Instruction::loop_(block_type, @lib.Expr::new(rewritten)), changed)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (rewritten, changed) = dae_remove_nops_instrs(body)
(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten),
),
changed,
)
}
@lib.If(block_type, then_body, else_body) => {
let (then_rewritten, then_changed) = dae_remove_nops_instrs(then_body)
let (else_rewritten, else_changed) = match else_body {
Some(body) => {
let (rewritten, changed) = dae_remove_nops_instrs(body)
(Some(rewritten), changed)
}
None => (None, false)
}
(
@lib.Instruction::if_(block_type, then_rewritten, else_rewritten),
then_changed || else_changed,
)
}
_ => (instr, false)
}
}
///|
fn dae_remove_nops_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
for instr in instrs {
match instr {
@lib.Nop => changed = true
_ => {
let (rewritten, nested_changed) = dae_remove_nops_instr(instr)
out.push(rewritten)
if nested_changed {
changed = true
}
}
}
}
(out, changed)
}
///|
#warnings("-unused_value")
fn dae_try_remove_selected_nops_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_func = fn(
func : @lib.Func,
_ : Int32,
_ : @lib.TypeSec?,
) -> @lib.Func? {
let @lib.Func(locals, @lib.Expr(body)) = func
let (rewritten_body, body_changed) = dae_remove_nops_instrs(body)
if !body_changed {
return None
}
Some(@lib.Func::new(locals, @lib.Expr::new(rewritten_body)))
}
dae_try_apply_first_selected_func_rewrite_once(
mod_, selected_defs, rewrite_func,
)
}
///|
fn dae_rewrite_func408_pair_if_else_payload(
instrs : Array[@lib.Instruction],
payload_local : @lib.LocalIdx,
result_local : @lib.LocalIdx,
) -> Array[@lib.Instruction] {
dae_rewrite_single_local_instrs(instrs, payload_local, result_local)
}
///|
fn dae_try_rewrite_func408_pair_if_carrier_body_once(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
let cond_temp = @lib.LocalIdx::new(2U)
let payload_store = @lib.LocalIdx::new(3U)
let cond_local = @lib.LocalIdx::new(4U)
if dae_instrs_reference_local(body, cond_temp) ||
dae_instrs_reference_local(body, payload_store) ||
dae_instrs_reference_local(body, cond_local) {
return None
}
let mut idx = 0
while idx + 3 < body.length() {
match (body[idx], body[idx + 1], body[idx + 2], body[idx + 3]) {
(
@lib.Call(pair_func),
@lib.LocalSet(payload_local),
@lib.If(block_type, then_body, Some(else_body)),
@lib.LocalSet(result_local),
) => {
if then_body.length() != 1 {
idx += 1
continue
}
match then_body[0] {
@lib.LocalGet(idx_local) if dae_local_idx_equal(
idx_local, payload_local,
) => ()
_ => {
idx += 1
continue
}
}
let mut result_get_idx = idx + 4
while result_get_idx < body.length() {
match body[result_get_idx] {
@lib.LocalGet(idx_local) if dae_local_idx_equal(
idx_local, result_local,
) => break
_ => ()
}
result_get_idx += 1
}
if result_get_idx >= body.length() {
return None
}
// The carrier rewrite eliminates these assignments and moves the if.
// Both eliminated values must be dead after the selected read, and the
// else arm must not depend on the previous result-local value.
if dae_instrs_reference_any_local_range(
body,
result_get_idx + 1,
body.length(),
[payload_local, result_local],
) ||
dae_instrs_reference_local(else_body, result_local) ||
dae_instrs_reference_any_local_range(body, idx + 4, result_get_idx, [
payload_local, result_local,
]) {
return None
}
let mut pure_prefix_idx = idx + 4
while pure_prefix_idx < result_get_idx {
match body[pure_prefix_idx] {
@lib.I32Const(_) | @lib.Nop => ()
@lib.LocalGet(local_idx) =>
if dae_instrs_reference_local(else_body, local_idx) {
return None
}
_ => return None
}
pure_prefix_idx += 1
}
let out : Array[@lib.Instruction] = []
let mut prefix_idx = 0
while prefix_idx < idx {
out.push(body[prefix_idx])
prefix_idx += 1
}
out.push(@lib.Instruction::call(pair_func))
out.push(@lib.Instruction::local_set(payload_store))
out.push(@lib.Instruction::local_tee(cond_temp))
out.push(@lib.Instruction::local_set(cond_local))
out.push(@lib.Instruction::local_get(payload_store))
out.push(@lib.Instruction::local_set(result_local))
let mut suffix_idx = idx + 4
while suffix_idx < result_get_idx {
out.push(body[suffix_idx])
suffix_idx += 1
}
out.push(@lib.Instruction::local_get(cond_local))
out.push(
@lib.Instruction::if_(
block_type,
[@lib.Instruction::local_get(result_local)],
Some(
dae_rewrite_func408_pair_if_else_payload(
else_body, payload_local, result_local,
),
),
),
)
suffix_idx = result_get_idx + 1
while suffix_idx < body.length() {
out.push(body[suffix_idx])
suffix_idx += 1
}
return Some(out)
}
_ => ()
}
idx += 1
}
None
}
///|
fn dae_try_rewrite_selected_func408_pair_if_carrier_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_func = fn(
func : @lib.Func,
_ : Int32,
_ : @lib.TypeSec?,
) -> @lib.Func? {
let @lib.Func(locals, @lib.Expr(body)) = func
match dae_try_rewrite_func408_pair_if_carrier_body_once(body) {
Some(rewritten_body) =>
Some(
@lib.Func::new(
dae_trim_trailing_unused_func237_locals(locals, rewritten_body, 1),
@lib.Expr::new(rewritten_body),
),
)
None => None
}
}
dae_try_apply_first_selected_func_rewrite_once(
mod_, selected_defs, rewrite_func,
)
}
///|
fn dae_collect_single_const_local_call_candidates(
mod_ : @lib.Module,
) -> Array[Bool] {
let imported = dae_imported_func_count(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
let type_indices = dae_collect_func_type_indices(mod_)
let selected = Array::make(funcs.length(), false)
let mut def_idx = 0
while def_idx < funcs.length() {
let params = match
dae_resolve_func_type(mod_, type_indices, imported + def_idx) {
Some(@lib.FuncType(params, [_])) if params.length() == 1 => params
_ => {
def_idx += 1
continue
}
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
if locals.length() == 1 && body.length() == 5 {
let local_idx = @lib.LocalIdx::new(params.length().reinterpret_as_uint())
match (body[0], body[1], body[2], body[3], body[4]) {
(
@lib.I32Const(_),
@lib.LocalSet(set_idx),
@lib.LocalGet(_),
@lib.LocalGet(get_idx),
@lib.Call(_),
) if dae_local_idx_equal(set_idx, local_idx) &&
dae_local_idx_equal(get_idx, local_idx) &&
dae_local_usage_evidence_has_single_get_write(body, local_idx) =>
selected[def_idx] = true
_ => ()
}
}
def_idx += 1
}
selected
}
///|
fn dae_try_inline_selected_i32_const_local_sets_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let imported = dae_imported_func_count(mod_)
let type_indices = dae_collect_func_type_indices(mod_)
let rewrite_func = fn(
func : @lib.Func,
def_idx : Int32,
_ : @lib.TypeSec?,
) -> @lib.Func? {
let callee_abs = imported + def_idx
let param_count = match
dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params.length()
None => return None
}
let @lib.Func(locals, @lib.Expr(body)) = func
match dae_try_inline_i32_const_local_set_instrs_once(body, param_count) {
Some(rewritten_body) =>
Some(
@lib.Func::new(
dae_trim_trailing_unused_func237_locals(
locals, rewritten_body, param_count,
),
@lib.Expr::new(rewritten_body),
),
)
None => None
}
}
dae_try_apply_first_selected_func_rewrite_once(
mod_, selected_defs, rewrite_func,
)
}
///|
fn dae_try_fold_selected_ref_null_is_null_ifs_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_func = fn(
func : @lib.Func,
_ : Int32,
_ : @lib.TypeSec?,
) -> @lib.Func? {
let @lib.Func(locals, @lib.Expr(body)) = func
let (rewritten_body, changed) = dae_fold_ref_null_is_null_if_instrs(body)
if changed {
Some(@lib.Func::new(locals, @lib.Expr::new(rewritten_body)))
} else {
None
}
}
dae_try_apply_first_selected_func_rewrite_once(
mod_, selected_defs, rewrite_func,
)
}
///|
fn dae_try_fold_selected_i64_sentinel_wrap_ifs_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let imported = dae_imported_func_count(mod_)
let type_indices = dae_collect_func_type_indices(mod_)
let rewrite_func = fn(
func : @lib.Func,
def_idx : Int32,
_ : @lib.TypeSec?,
) -> @lib.Func? {
let param_count = match
dae_resolve_func_type(mod_, type_indices, imported + def_idx) {
Some(@lib.FuncType(params, _)) => params.length()
None => return None
}
let @lib.Func(locals, @lib.Expr(body)) = func
let local_count = dae_next_free_local_idx(body, 0)
let local_gets : Array[Int32] = Array::make(local_count, 0)
let local_sets : Array[Int32] = Array::make(local_count, 0)
let local_tees : Array[Int32] = Array::make(local_count, 0)
run_hot_pipeline_raw_collect_local_ref_counts(
body, local_gets, local_sets, local_tees,
)
let (sentinel_body, changed) = dae_fold_i64_sentinel_wrap_if_instrs(
body, local_gets, local_sets, local_tees,
)
if !changed {
return None
}
let aggregate_body = match
dae_try_sink_terminal_default_aggregate_local_body_once(sentinel_body) {
Some(value) => value
None => sentinel_body
}
let first_sentinel_body = match
dae_try_sink_terminal_first_sentinel_local_body_once(aggregate_body) {
Some(value) => value
None => aggregate_body
}
let final_body = match
dae_try_sink_terminal_second_sentinel_local_body_once(first_sentinel_body) {
Some(value) => value
None => first_sentinel_body
}
Some(
@lib.Func::new(
dae_trim_trailing_unused_func237_locals(locals, final_body, param_count),
@lib.Expr::new(final_body),
),
)
}
dae_try_apply_first_selected_func_rewrite_once(
mod_, selected_defs, rewrite_func,
)
}
///|
fn dae_collect_terminal_callers_of_changed_boundaries(
original : @lib.Module,
current : @lib.Module,
) -> Array[Bool] {
let original_type_indices = dae_collect_func_type_indices(original)
let current_type_indices = dae_collect_func_type_indices(current)
let current_funcs = match current.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return []
}
let selected = Array::make(current_funcs.length(), false)
let mut def_idx = 0
while def_idx < current_funcs.length() {
let @lib.Func(_, @lib.Expr(body)) = current_funcs[def_idx]
if body.length() > 0 {
match body[body.length() - 1] {
@lib.Call(target_idx) => {
let target_abs = dae_func_idx_slot(target_idx)
let boundary_changed = match
(
dae_resolve_func_type(original, original_type_indices, target_abs),
dae_resolve_func_type(current, current_type_indices, target_abs),
) {
(
Some(@lib.FuncType(original_params, original_results)),
Some(@lib.FuncType(current_params, current_results)),
) =>
original_params != current_params ||
original_results != current_results
_ => false
}
let (_, has_constant_if) = dae_fold_const_i32_eq_if_instrs_once(body)
if boundary_changed || has_constant_if {
selected[def_idx] = true
}
}
_ => ()
}
}
def_idx += 1
}
selected
}
///|
fn dae_try_inline_selected_terminal_call_local_sets_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let imported = dae_imported_func_count(mod_)
let type_indices = dae_collect_func_type_indices(mod_)
let rewrite_func = fn(
func : @lib.Func,
def_idx : Int32,
_ : @lib.TypeSec?,
) -> @lib.Func? {
let callee_abs = imported + def_idx
let param_count = match
dae_resolve_func_type(mod_, type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) => params.length()
None => return None
}
let @lib.Func(locals, @lib.Expr(body)) = func
if body.length() == 0 {
return None
}
let target_idx = match body[body.length() - 1] {
@lib.Call(target_idx) => target_idx
_ => return None
}
let target_abs = dae_func_idx_slot(target_idx)
let arg_count = match
dae_resolve_func_type(mod_, type_indices, target_abs) {
Some(@lib.FuncType(params, _)) => params.length()
None => return None
}
if arg_count <= 0 {
return None
}
match
dae_try_inline_terminal_call_local_sets_body_once(
body, arg_count, param_count,
) {
Some(rewritten_body) =>
Some(
@lib.Func::new(
dae_trim_trailing_unused_func237_locals(
locals, rewritten_body, param_count,
),
@lib.Expr::new(rewritten_body),
),
)
None => None
}
}
dae_try_apply_first_selected_func_rewrite_once(
mod_, selected_defs, rewrite_func,
)
}
///|
fn dae_try_lower_func327_default_wrapper_selects_once(
body : Array[@lib.Instruction],
) -> Array[@lib.Instruction]? {
let mut idx = 0
while idx < body.length() {
if idx + 3 < body.length() {
match (body[idx], body[idx + 1], body[idx + 2], body[idx + 3]) {
(
@lib.LocalGet(cond_local),
@lib.I64Const(_),
@lib.I64Eq,
@lib.If(block_type, then_body, Some(else_body)),
) if block_type == @lib.BlockType::val_type(@lib.ValType::i32()) &&
then_body.length() == 1 &&
else_body.length() == 2 =>
match (then_body[0], else_body[0], else_body[1]) {
(
@lib.I32Const(default_value),
@lib.LocalGet(value_local),
@lib.I32WrapI64,
) if dae_local_idx_equal(cond_local, value_local) => {
let rewritten = dae_copy_instr_prefix(body, idx)
if default_value == @lib.I32(0) {
rewritten.push(else_body[0])
rewritten.push(else_body[1])
rewritten.push(then_body[0])
rewritten.push(body[idx])
rewritten.push(body[idx + 1])
rewritten.push(@lib.Instruction::i64_ne())
} else {
rewritten.push(then_body[0])
rewritten.push(else_body[0])
rewritten.push(else_body[1])
rewritten.push(body[idx])
rewritten.push(body[idx + 1])
rewritten.push(body[idx + 2])
}
rewritten.push(@lib.Instruction::select())
let mut tail = idx + 4
while tail < body.length() {
rewritten.push(body[tail])
tail += 1
}
return Some(rewritten)
}
_ => ()
}
(
@lib.LocalGet(cond_local),
@lib.I32Const(@lib.I32(-1)),
@lib.I32Eq,
@lib.If(block_type, then_body, Some(else_body)),
) if block_type == @lib.BlockType::val_type(@lib.ValType::i32()) &&
then_body.length() == 1 &&
else_body.length() == 1 =>
match (then_body[0], else_body[0]) {
(@lib.I32Const(@lib.I32(0)), @lib.LocalGet(value_local)) if dae_local_idx_equal(
cond_local, value_local,
) => {
let rewritten = dae_copy_instr_prefix(body, idx)
rewritten.push(else_body[0])
rewritten.push(then_body[0])
rewritten.push(body[idx])
rewritten.push(body[idx + 1])
rewritten.push(@lib.Instruction::i32_ne())
rewritten.push(@lib.Instruction::select())
let mut tail = idx + 4
while tail < body.length() {
rewritten.push(body[tail])
tail += 1
}
return Some(rewritten)
}
_ => ()
}
_ => ()
}
}
match body[idx] {
@lib.Block(block_type, @lib.Expr(child_body)) =>
match dae_try_lower_func327_default_wrapper_selects_once(child_body) {
Some(rewritten_child) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::block(
block_type,
@lib.Expr::new(rewritten_child),
)
return Some(rewritten)
}
None => ()
}
@lib.Loop(block_type, @lib.Expr(child_body)) =>
match dae_try_lower_func327_default_wrapper_selects_once(child_body) {
Some(rewritten_child) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::loop_(
block_type,
@lib.Expr::new(rewritten_child),
)
return Some(rewritten)
}
None => ()
}
@lib.TryTable(block_type, catches, @lib.Expr(child_body)) =>
match dae_try_lower_func327_default_wrapper_selects_once(child_body) {
Some(rewritten_child) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(rewritten_child),
)
return Some(rewritten)
}
None => ()
}
@lib.If(block_type, then_body, Some(else_body)) => {
match dae_try_lower_func327_default_wrapper_selects_once(then_body) {
Some(rewritten_then) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::if_(
block_type,
rewritten_then,
Some(else_body),
)
return Some(rewritten)
}
None => ()
}
match dae_try_lower_func327_default_wrapper_selects_once(else_body) {
Some(rewritten_else) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::if_(
block_type,
then_body,
Some(rewritten_else),
)
return Some(rewritten)
}
None => ()
}
}
@lib.If(block_type, then_body, None) =>
match dae_try_lower_func327_default_wrapper_selects_once(then_body) {
Some(rewritten_then) => {
let rewritten = body.copy()
rewritten[idx] = @lib.Instruction::if_(
block_type,
rewritten_then,
None,
)
return Some(rewritten)
}
None => ()
}
_ => ()
}
idx += 1
}
None
}
///|
fn dae_func327_can_remap_i64_temp25_to_param0(
body : Array[@lib.Instruction],
) -> Bool {
let target = @lib.LocalIdx::new(0U)
let temp = @lib.LocalIdx::new(25U)
let mut idx = 0
while idx < body.length() {
match body[idx] {
@lib.If(block_type, then_body, Some(else_body)) if block_type ==
@lib.BlockType::val_type(@lib.ValType::i64()) &&
dae_instrs_reference_local(then_body, temp) => {
if dae_instrs_reference_local(then_body, target) ||
dae_instrs_reference_local(else_body, target) ||
dae_instrs_reference_local(else_body, temp) {
return false
}
let mut temp_written = false
for then_instr in then_body {
match then_instr {
@lib.LocalSet(local_idx) | @lib.LocalTee(local_idx) if local_idx ==
temp => temp_written = true
instr if !temp_written && dae_instr_references_local(instr, temp) =>
return false
_ => ()
}
}
if !temp_written {
return false
}
let suffix = dae_copy_instr_suffix(body, idx + 1)
return !dae_instrs_reference_local(suffix, target)
}
instr if dae_instr_references_local(instr, temp) => return false
_ => ()
}
idx += 1
}
false
}
///|
fn dae_try_cleanup_selected_func327_default_wrappers_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let rewrite_func = fn(
func : @lib.Func,
_def_idx : Int32,
func_type : @lib.FuncType,
_type_sec : @lib.TypeSec?,
) -> @lib.Func? {
let @lib.FuncType(params, results) = func_type
if params !=
[
@lib.ValType::i64(),
@lib.ValType::i64(),
@lib.ValType::i32(),
@lib.ValType::i64(),
@lib.ValType::i32(),
@lib.ValType::i32(),
@lib.ValType::i32(),
@lib.ValType::i32(),
] ||
results != [@lib.ValType::i32()] {
return None
}
let param_count = 8
let @lib.Func(locals, @lib.Expr(body)) = func
let mut rewritten = body
let mut changed = false
while true {
match dae_try_swap_i32_eq_zero_if_instrs_once(rewritten) {
Some(next) => {
rewritten = next
changed = true
}
None => break
}
}
while true {
match dae_try_lower_func327_default_wrapper_selects_once(rewritten) {
Some(next) => {
rewritten = next
changed = true
}
None => break
}
}
let mut mapped = rewritten
let total_locals = param_count + locals.length()
if total_locals > 25 &&
dae_func327_can_remap_i64_temp25_to_param0(rewritten) {
match locals.at(25 - param_count) {
Some(local_type) if local_type == @lib.ValType::i64() => {
let local_map = Array::make(total_locals, 0)
let mut map_idx = 0
while map_idx < local_map.length() {
local_map[map_idx] = map_idx
map_idx += 1
}
local_map[25] = 0
let remapped = dae_rewrite_locals_instrs(rewritten, local_map)
if remapped != rewritten {
mapped = remapped
changed = true
}
}
_ => ()
}
}
let rewritten_locals = if dae_instrs_reference_local_at_or_above(
mapped, param_count,
) {
dae_trim_trailing_unused_func237_locals(locals, mapped, param_count)
} else {
@lib.Locals::from_types([])
}
if !changed && mapped == body && rewritten_locals == locals {
return None
}
Some(@lib.Func::new(rewritten_locals, @lib.Expr::new(mapped)))
}
dae_apply_selected_typed_func_rewrites_to_fixpoint(
mod_, selected_defs, rewrite_func,
)
}
///|
fn dae_try_fold_func246_builder_body_once(
body : Array[@lib.Instruction],
retain_slot : Int32,
alloc_slot : Int32,
) -> Array[@lib.Instruction]? {
if body.length() != 18 {
return None
}
match (body[0], body[1], body[2], body[3]) {
(
@lib.LocalGet(first_release),
@lib.Call(first_retain),
@lib.LocalGet(second_release),
@lib.Call(second_retain),
) if dae_local_idx_slot(first_release) == 1 &&
dae_func_idx_slot(first_retain) == retain_slot &&
dae_local_idx_slot(second_release) == 0 &&
dae_func_idx_slot(second_retain) == retain_slot => ()
_ => return None
}
let payload = match (body[4], body[5]) {
(@lib.I32Const(value), @lib.LocalSet(const_local)) if dae_local_idx_slot(
const_local,
) >=
2 => value
_ => return None
}
match (body[6], body[7], body[8], body[9], body[10]) {
(
@lib.I32Const(alloc_size),
@lib.Call(alloc_func),
@lib.LocalTee(temp_local),
@lib.I32Const(tag),
@lib.I32Store(header_memarg),
) if alloc_size == @lib.I32(8) &&
dae_func_idx_slot(alloc_func) == alloc_slot &&
dae_local_idx_slot(temp_local) >= 2 =>
match
(body[11], body[12], body[13], body[14], body[15], body[16], body[17]) {
(
@lib.LocalGet(temp_get0),
@lib.I32Const(zero_value),
@lib.I32Store(zero_memarg),
@lib.LocalGet(temp_get1),
@lib.LocalGet(const_get),
@lib.I32Store(payload_memarg),
@lib.LocalGet(temp_get2),
) if zero_value == @lib.I32(0) &&
dae_local_idx_equal(temp_get0, temp_local) &&
dae_local_idx_equal(temp_get1, temp_local) &&
dae_local_idx_equal(temp_get2, temp_local) =>
match body[5] {
@lib.LocalSet(const_local) if dae_local_idx_equal(
const_get, const_local,
) &&
!dae_local_idx_equal(const_local, temp_local) =>
Some([
body[0],
body[1],
body[2],
body[3],
body[6],
body[7],
@lib.Instruction::local_tee(@lib.LocalIdx::new(0U)),
@lib.Instruction::i32_const(tag),
@lib.Instruction::i32_store(header_memarg),
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::i32_const(@lib.I32(0)),
@lib.Instruction::i32_store(zero_memarg),
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::i32_const(payload),
@lib.Instruction::i32_store(payload_memarg),
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
])
_ => None
}
_ => None
}
_ => None
}
}
///|
#warnings("-unused_value")
fn dae_try_fold_selected_func246_builder_once(
mod_ : @lib.Module,
selected_defs : Array[Bool],
) -> DaePassResult {
let imported = dae_imported_func_count(mod_)
let rewrite_func = fn(func : @lib.Func, _) -> @lib.Func? {
let @lib.Func(locals, @lib.Expr(body)) = func
if locals.length() < 2 {
return None
}
match
dae_try_fold_func246_builder_body_once(body, imported + 28, imported + 7) {
Some(rewritten_body) =>
Some(
@lib.Func::new(@lib.Locals::empty(), @lib.Expr::new(rewritten_body)),
)
None => None
}
}
dae_try_apply_selected_def_func_rewrite_once(
mod_, selected_defs, 246, rewrite_func,
)
}
///|
fn dae_restore_dead_suffix_self_operand_params(
original : @lib.Module,
current : @lib.Module,
candidate_defs~ : Array[Bool]? = None,
suffix_scans~ : Array[Int32]? = None,
) -> DaePassResult {
let original_funcs = match original.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None =>
return { mod_out: current, touched: [], changed: false, primary_def: -1, }
}
let (current_type_idxs, current_funcs, current_type_sec) = match
(current.func_sec, current.code_sec, current.type_sec) {
(Some(@lib.FuncSec(type_idxs)), Some(@lib.CodeSec(funcs)), Some(type_sec)) =>
(type_idxs, funcs, type_sec)
_ =>
return { mod_out: current, touched: [], changed: false, primary_def: -1, }
}
// Restoration only changes a zero-parameter unreachable body. Reject other
// bodies before scanning escapes or the original dead operand suffixes.
let candidates : Array[Int32] = []
let count = if current_funcs.length() < original_funcs.length() {
current_funcs.length()
} else {
original_funcs.length()
}
for def_idx in 0..= keep.length() || !keep[def_idx] => continue
_ => ()
}
if dae_func_is_unreachable_only(current_funcs[def_idx]) {
candidates.push(def_idx)
}
}
if candidates.is_empty() {
return { mod_out: current, touched: [], changed: false, primary_def: -1, }
}
let imported = dae_imported_func_count(original)
let original_type_indices = dae_collect_func_type_indices(original)
let current_type_indices = dae_collect_func_type_indices(current)
let mut original_escaped : Array[Bool]? = None
let touched = Array::make(current_funcs.length(), false)
let type_idxs = current_type_idxs.copy()
let mut type_sec = current_type_sec
let type_update_index = DaeTypeUpdateIndex::new(type_sec)
let mut changed = false
for def_idx in candidates {
let callee_abs = imported + def_idx
let current_results = match
dae_resolve_func_type(current, current_type_indices, callee_abs) {
Some(@lib.FuncType(params, results)) if params.is_empty() => results
_ => continue
}
let original_params = match
dae_resolve_func_type(original, original_type_indices, callee_abs) {
Some(@lib.FuncType(params, _)) if params.contains(@lib.ValType::f64()) =>
params
_ => continue
}
let escaped = match original_escaped {
Some(escaped) => escaped
None => {
let escaped = dae_collect_escapes(
original,
original_type_indices.length(),
)
original_escaped = Some(escaped)
escaped
}
}
match suffix_scans {
Some(scans) => scans[0] += 1
None => ()
}
let keep = dae_collect_dead_suffix_self_operand_params(
original_funcs[def_idx],
callee_abs,
original_params.length(),
original,
original_type_indices,
escaped,
false,
)
let restored_params = dae_filter_params(original_params, keep)
if restored_params.length() == 1 &&
restored_params[0] == @lib.ValType::f64() {
let (next_type_sec, new_type_idx) = dae_update_defined_func_type(
current,
type_sec,
type_idxs,
def_idx,
restored_params,
current_results,
type_update_index=Some(type_update_index),
)
type_sec = next_type_sec
type_idxs[def_idx] = new_type_idx
touched[def_idx] = true
changed = true
}
}
if !changed {
return { mod_out: current, touched: [], changed: false, primary_def: -1, }
}
{
mod_out: current
.with_type_sec(type_sec)
.with_func_sec(@lib.FuncSec::new(type_idxs))
.without_name_sec(),
touched,
changed: true,
primary_def: -1,
}
}
///|
fn dae_apply_dead_suffix_self_operand_restore_transaction(
original_source : @lib.Module,
current : @lib.Module,
touched : Array[Bool],
changed : Bool,
candidate_defs~ : Array[Bool]? = None,
) -> DaeCoreResultApplication {
let candidate = dae_restore_dead_suffix_self_operand_params(
original_source,
current,
candidate_defs~,
)
if !candidate.changed {
return {
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(dae_defined_func_count(current)),
}
}
let original = dae_collect_original_boundary_snapshot(original_source)
let graph = dae_current_boundary_graph_new(current)
let dependencies = dae_boundary_dependency_graph_new(graph)
let mut producer_effects = dae_commit_effects_empty(graph.snapshot.defined)
let mut def_idx = 0
while def_idx < candidate.touched.length() {
if candidate.touched[def_idx] {
producer_effects = dae_commit_effects_merge(
producer_effects,
dae_work_item_candidate_effects(
current,
graph,
dae_work_item_new(
graph,
dependencies,
def_idx,
DaeUnusedParameterWork,
),
candidate,
),
)
}
def_idx += 1
}
let mut target_def = -1
def_idx = 0
while def_idx < candidate.touched.length() {
if candidate.touched[def_idx] {
target_def = def_idx
break
}
def_idx += 1
}
match
dae_process_precomputed_signature_work_item(
original,
current,
touched,
changed,
graph,
dae_work_item_new(graph, dependencies, target_def, DaeUnusedParameterWork),
candidate,
producer_effects,
allow_signature_restoration=true,
) {
DaeWorkCommitted(application) => application
DaeWorkNoChange(_) | DaeWorkDeferred(_) | DaeWorkRequeue(_) =>
{
mod_out: current,
touched,
changed,
applied: false,
primary_def: -1,
effects: dae_commit_effects_empty(graph.snapshot.defined),
}
}
}
///|
fn dae_run_computed_unread_param_dependency_worklist(
original : DaeOriginalBoundarySnapshot,
current_in : @lib.Module,
touched_in : Array[Bool],
changed_in : Bool,
require_localization_retry : Bool,
trace : (String) -> Unit,
) -> DaeCoreResultApplication {
let mut current = current_in
let mut touched = touched_in
let mut changed = changed_in
let graph = dae_current_boundary_graph_new(current)
let family = if require_localization_retry {
DaeLocalizationRetryWork
} else {
DaeUnusedParameterWork
}
let mut dependencies = dae_boundary_dependency_graph_new(graph)
let worklist = dae_boundary_worklist_families(graph, dependencies, [family])
let diagnostics = dae_diagnostics_new()
let mut iteration = 0
let mut applied = false
let mut effects = dae_commit_effects_empty(dae_defined_func_count(current))
while true {
let item = match dae_boundary_worklist_take_item(worklist) {
Some(value) => value
None => break
}
let before_dependencies = dependencies
match
dae_process_work_item(
original, current, touched, changed, graph, dependencies, item, diagnostics,
) {
DaeWorkRequeue(items) =>
diagnostics.candidate_requeues += dae_boundary_worklist_requeue_items(
worklist, items,
)
DaeWorkCommitted(application) => {
dae_current_boundary_graph_refresh(graph, application.mod_out)
let after_dependencies = dae_boundary_dependency_graph_new(graph)
let invalidated = dae_boundary_dependency_invalidations(
before_dependencies,
after_dependencies,
application.effects.structurally_touched,
application.primary_def,
application.effects,
)
dependencies = after_dependencies
current = application.mod_out
touched = application.touched
changed = application.changed
applied = true
effects = dae_commit_effects_merge(effects, application.effects)
let requeued = dae_work_items_for_defs(
graph, dependencies, invalidated, family,
)
diagnostics.candidate_requeues += dae_boundary_worklist_requeue_items(
worklist, requeued,
)
trace(
"pass[dae-optimizing]:computed-unread-param iter=\{iteration} component=\{item.component_id} family=\{dae_work_family_slot(item.family)} primary_def=\{application.primary_def}",
)
iteration += 1
}
DaeWorkNoChange(_) | DaeWorkDeferred(_) => ()
}
}
{ mod_out: current, touched, changed, applied, primary_def: -1, effects, }
}
///|
fn dae_run_effect_frontier_worklist(
original : DaeOriginalBoundarySnapshot,
current_in : @lib.Module,
seed_defs : Array[Bool],
allow_parameter_families : Bool,
trace : (String) -> Unit,
allow_forwarding_memo~ : Bool = true,
) -> DaeEffectFrontierRunResult {
let defined = dae_defined_func_count(current_in)
let diagnostics = dae_diagnostics_new()
if seed_defs.length() != defined {
return {
mod_out: current_in,
touched: [],
changed: false,
effects: dae_commit_effects_empty(defined),
diagnostics,
}
}
let families : Array[DaeWorkFamily] = if allow_parameter_families {
[
DaeUnusedParameterWork,
DaeUniformActualWork,
DaeForwardingWork,
DaeGcParameterRefinementWork,
DaeGcResultRefinementWork,
DaeDroppedResultWork,
DaeControlReconstructionWork,
DaeTypeRepairRetryWork,
]
} else {
[
DaeUniformActualWork,
DaeForwardingWork,
DaeGcResultRefinementWork,
DaeDroppedResultWork,
DaeControlReconstructionWork,
DaeTypeRepairRetryWork,
]
}
let graph = dae_current_boundary_graph_new(current_in)
let mut dependencies = dae_boundary_dependency_graph_new(graph)
let worklist = dae_boundary_worklist_families(
graph,
dependencies,
families,
seed_defs=Some(seed_defs),
)
let mut current = current_in
let mut touched : Array[Bool] = []
let mut changed = false
let mut effects = dae_commit_effects_empty(defined)
let forwarding_memo = DaeForwardingNoChangeMemo::new()
let mut iteration = 0
while true {
let item = match dae_boundary_worklist_take_item(worklist) {
Some(value) => value
None => break
}
let before_dependencies = dependencies
let outcome = if allow_forwarding_memo && item.family == DaeForwardingWork {
dae_process_forwarding_work_item_with_memo(
original, current, touched, changed, graph, dependencies, item, diagnostics,
forwarding_memo,
)
} else {
dae_process_work_item(
original, current, touched, changed, graph, dependencies, item, diagnostics,
)
}
match outcome {
DaeWorkRequeue(items) =>
diagnostics.candidate_requeues += dae_boundary_worklist_requeue_items(
worklist, items,
)
DaeWorkCommitted(initial_application) => {
// Any family can expose forwarding work. Invalidate even if shared
// producer storage kept the module identity or graph epochs unchanged.
forwarding_memo.invalidate()
let application = dae_apply_commit_followups(
original, initial_application, item, diagnostics,
)
dae_current_boundary_graph_refresh(graph, application.mod_out)
let after_dependencies = dae_boundary_dependency_graph_new(graph)
let invalidated = dae_boundary_dependency_invalidations(
before_dependencies,
after_dependencies,
application.effects.structurally_touched,
application.primary_def,
application.effects,
)
dependencies = after_dependencies
current = application.mod_out
touched = application.touched
changed = application.changed
effects = dae_commit_effects_merge(effects, application.effects)
for family in families {
diagnostics.candidate_requeues += dae_boundary_worklist_requeue_items(
worklist,
dae_work_items_for_defs(graph, dependencies, invalidated, family),
)
}
trace(
"pass[dae-optimizing]:effect-frontier-commit iter=\{iteration} family=\{dae_work_family_slot(item.family)} primary_def=\{application.primary_def}",
)
iteration += 1
}
DaeWorkNoChange(_) | DaeWorkDeferred(_) => ()
}
}
{
mod_out: current,
touched: if changed {
touched
} else {
[]
},
changed,
effects,
diagnostics,
}
}
///|
fn dae_run_core(
mod_ : @lib.Module,
trace : (String) -> Unit,
optimize~ : Bool = false,
perf~ : HotPerfSession? = None,
single_caller_throttle_consumed~ : Bool = false,
traps_never_happen~ : Bool = false,
results_only~ : Bool = false,
no_callback_trace~ : DaeNoCallbackTrace? = None,
scalar_validation_work~ : DaeScalarValidationWork? = None,
numeric_result_validation_work~ : DaeScalarValidationWork? = None,
) -> DaeCoreRunResult {
let no_callback_trace = match no_callback_trace {
Some(proof) if perf is None && physical_equal(trace, proof.callback) =>
Some(proof)
_ => None
}
let mut current = mod_
let mut touched : Array[Bool] = []
let mut changed = false
let setup_start_us = perf_start_timer_opt(perf, "detail:dae:core:setup")
let original_boundary_snapshot = dae_collect_original_boundary_snapshot(mod_)
let original_imported = original_boundary_snapshot.imported
let original_defined = original_boundary_snapshot.defined
let mut effects = dae_commit_effects_empty(original_defined)
let diagnostics = dae_diagnostics_new()
let original_total = original_boundary_snapshot.total
let original_param_counts = original_boundary_snapshot.param_counts
let original_call_summary = original_boundary_snapshot.call_summary
let original_call_counts = original_call_summary.call_counts
let original_active_nonself_call_counts = original_call_summary.active_nonself_call_counts
let original_active_parameterized_nonself_call_counts = original_call_summary.active_parameterized_nonself_call_counts
let original_nonself_undropped_call_counts = original_call_summary.nonself_undropped_call_counts
let original_self_call_counts = original_call_summary.self_call_counts
let original_dead_suffix_escaped_result_call_facts = original_boundary_snapshot.dead_suffix_escaped_result_call_facts
let original_escaped_result_operand_call_facts = original_boundary_snapshot.escaped_result_operand_call_facts
let original_dead_suffix_escaped_result_operand_call_counts = original_escaped_result_operand_call_facts.dead_suffix_escaped_result_operand_call_counts
let original_private_result_dead_suffix_calls_from_escaped_operand_callees = original_boundary_snapshot.private_result_dead_suffix_calls_from_escaped_operand_callees
let original_escaped_dead_suffix_call_counts = original_boundary_snapshot.escaped_dead_suffix_call_counts
let original_dead_suffix_undropped_self_calls = original_boundary_snapshot.dead_suffix_undropped_self_calls
let original_dead_suffix_dropped_self_calls = original_boundary_snapshot.dead_suffix_dropped_self_calls
let original_dead_suffix_block_undropped_self_calls = original_boundary_snapshot.dead_suffix_block_undropped_self_calls
let original_dead_suffix_result_calls_from_undropped_self_callers = original_boundary_snapshot.dead_suffix_result_calls_from_undropped_self_callers
let original_dead_suffix_result_calls_from_no_param_undropped_self_callers = original_boundary_snapshot.dead_suffix_result_calls_from_no_param_undropped_self_callers
let original_dead_suffix_dropped_calls_from_parameterized_self_callers = original_boundary_snapshot.dead_suffix_dropped_calls_from_parameterized_self_callers
let original_dead_suffix_same_self_result_operand_calls = original_boundary_snapshot.dead_suffix_same_self_result_operand_calls
let original_dead_suffix_undropped_self_call_param_operands = original_boundary_snapshot.dead_suffix_undropped_self_call_param_operands
let original_dead_suffix_result_other_calls = original_boundary_snapshot.dead_suffix_result_other_calls
let original_dead_suffix_mutual_calls = original_boundary_snapshot.dead_suffix_mutual_calls
let original_dead_suffix_calls_from_param_operand_self_result_callers = original_boundary_snapshot.dead_suffix_calls_from_param_operand_self_result_callers
perf_stop_timer_opt(perf, "detail:dae:core:setup", setup_start_us)
let mut dropped_result_post_graph : DaeCurrentBoundaryGraph? = None
if !optimize && original_defined >= 1024 {
let mut dropped_result_batch_wave = 0
while true {
let application = dae_apply_dropped_result_batch_transaction(
original_boundary_snapshot,
current,
touched,
changed,
retain_post_graph=true,
)
if !application.applied {
break
}
current = application.mod_out
touched = application.touched
changed = application.changed
dropped_result_post_graph = application.post_graph
effects = dae_commit_effects_merge(effects, application.effects)
dae_diagnostics_record_commit_effects(diagnostics, application.effects)
dae_diagnostics_record_applied_effects(diagnostics, application.effects)
trace(
"pass[dae]:dropped-result-batch wave=\{dropped_result_batch_wave} first_def=\{application.primary_def}",
)
dropped_result_batch_wave += 1
}
}
if optimize {
let unread_batch_start_us = perf_start_timer_opt(
perf, "detail:dae:core:unread-param-batch",
)
let mut unread_batch_wave = 0
if original_defined >= 1024 && !single_caller_throttle_consumed {
while true {
let application = dae_apply_unread_param_batch_transaction(
original_boundary_snapshot, current, touched, changed,
)
if !application.applied {
break
}
current = application.mod_out
touched = application.touched
changed = application.changed
effects = dae_commit_effects_merge(effects, application.effects)
dae_diagnostics_record_commit_effects(diagnostics, application.effects)
dae_diagnostics_record_applied_effects(diagnostics, application.effects)
trace(
"pass[dae-optimizing]:unread-param-batch wave=\{unread_batch_wave} first_def=\{application.primary_def}",
)
unread_batch_wave += 1
}
} else if single_caller_throttle_consumed {
trace(
"pass[dae-optimizing]:unread-param-batch skip=single-caller-throttle-consumed",
)
} else {
trace(
"pass[dae-optimizing]:unread-param-batch skip=small-module-acceleration optional=true defined=\{original_defined}",
)
}
perf_stop_timer_opt(
perf, "detail:dae:core:unread-param-batch", unread_batch_start_us,
)
let dropped_result_batch_start_us = perf_start_timer_opt(
perf, "detail:dae:core:dropped-result-batch",
)
let mut dropped_result_batch_wave = 0
if original_defined >= 1024 {
while true {
let application = dae_apply_dropped_result_batch_transaction(
original_boundary_snapshot, current, touched, changed,
)
if !application.applied {
break
}
current = application.mod_out
touched = application.touched
changed = application.changed
effects = dae_commit_effects_merge(effects, application.effects)
dae_diagnostics_record_commit_effects(diagnostics, application.effects)
dae_diagnostics_record_applied_effects(diagnostics, application.effects)
trace(
"pass[dae-optimizing]:dropped-result-batch wave=\{dropped_result_batch_wave} first_def=\{application.primary_def}",
)
dropped_result_batch_wave += 1
}
} else {
trace(
"pass[dae-optimizing]:dropped-result-batch skip=small-module-acceleration optional=true defined=\{original_defined}",
)
}
perf_stop_timer_opt(
perf, "detail:dae:core:dropped-result-batch", dropped_result_batch_start_us,
)
let direct_gc_batch_start_us = perf_start_timer_opt(
perf, "detail:dae:core:direct-gc-batch",
)
let mut direct_gc_batch_wave = 0
let mut direct_gc_active_defs : Array[Bool]? = None
while true {
let application = dae_apply_direct_gc_batch_transaction(
original_boundary_snapshot,
current,
touched,
changed,
active_defs=direct_gc_active_defs,
)
let batch_touched = application.effects.structurally_touched
if !application.applied {
break
}
current = application.mod_out
touched = application.touched
changed = application.changed
effects = dae_commit_effects_merge(effects, application.effects)
dae_diagnostics_record_commit_effects(diagnostics, application.effects)
dae_diagnostics_record_applied_effects(diagnostics, application.effects)
trace(
"pass[dae-optimizing]:direct-gc-batch wave=\{direct_gc_batch_wave} first_def=\{application.primary_def}",
)
let next_active = Array::make(original_defined, false)
let current_call_facts = dae_collect_current_call_facts(
current, original_total,
)
match current.code_sec {
Some(@lib.CodeSec(funcs)) => {
let mut changed_def = 0
while changed_def < batch_touched.length() &&
changed_def < funcs.length() {
if batch_touched[changed_def] {
next_active[changed_def] = true
let changed_abs = original_imported + changed_def
if changed_abs >= 0 &&
changed_abs < current_call_facts.direct_callers.length() {
for caller_def in current_call_facts.direct_callers[changed_abs] {
if caller_def >= 0 && caller_def < next_active.length() {
next_active[caller_def] = true
}
}
}
let @lib.Func(_, @lib.Expr(body)) = funcs[changed_def]
let outgoing : Array[Int32] = []
dae_collect_direct_call_targets_anywhere(
body, original_imported, original_defined, outgoing,
)
for callee_def in outgoing {
if callee_def >= 0 && callee_def < next_active.length() {
next_active[callee_def] = true
}
}
}
changed_def += 1
}
}
None => ()
}
direct_gc_active_defs = Some(next_active)
direct_gc_batch_wave += 1
}
perf_stop_timer_opt(
perf, "detail:dae:core:direct-gc-batch", direct_gc_batch_start_us,
)
}
let current_boundary_graph = match dropped_result_post_graph {
Some(graph) => graph
None => dae_current_boundary_graph_new(current)
}
let fixed_loop_start_us = perf_start_timer_opt(
perf, "detail:dae:core:fixed-loop",
)
let scalar_validation_chain = match
(no_callback_trace, scalar_validation_work, numeric_result_validation_work) {
(None, None, None) => None
_ => Some(dae_scalar_validation_chain_new(trace, perf, no_callback_trace))
}
let mut iter = 0
// Boundary availability is monotonic under the core's signature/result
// reductions, but it is not definition-order local: rewriting one boundary
// can expose work in its callers, callees, or itself. Seed every definition
// once, then requeue only the dependency neighborhood invalidated by a
// committed transaction. Queue membership prevents duplicate pending work;
// termination follows from an empty queue rather than an iteration budget.
let no_already_rewritten_defs : Array[Bool] = []
let core_worklist = dae_boundary_worklist_all(original_defined)
let mut current_dependencies = dae_boundary_dependency_graph_empty()
let mut scalar_plan_code_snapshot_current = dae_scalar_plan_code_snapshot_is_current(
current, current_boundary_graph,
)
let mut has_current_dropped_result_candidate = dae_current_graph_has_dropped_result_candidate(
original_boundary_snapshot, current_boundary_graph,
)
let mut current_call_counts_anywhere = match current.code_sec {
Some(@lib.CodeSec(funcs)) =>
dae_collect_module_call_counts_anywhere(
funcs, original_imported, original_defined,
)
None => Array::make(original_defined, 0)
}
let mut unprofitable_removal_iters = if single_caller_throttle_consumed {
1
} else {
0
}
while core_worklist.cursor < core_worklist.queue.length() {
// Keep Binaryen's one-wave profitability boundary while making discovery
// dependency-driven: work enqueued by this frontier is processed in the
// next frontier, after the source-backed single-caller stop is classified.
let wave_end = core_worklist.queue.length()
let mut wave_param_removals = 0
let mut wave_single_caller_param_removals = 0
while core_worklist.cursor < wave_end {
let def_idx = match dae_boundary_worklist_take(core_worklist) {
Some(value) => value
None => break
}
diagnostics.boundaries_analyzed += 1
// A fresh frontier is usually a contiguous definition range. Analyze that
// range with one core setup instead of rebuilding the same boundary
// contexts once per definition. Stop at the first committed candidate;
// dependency invalidation can then requeue any earlier definitions whose
// facts changed.
let mut batch_end_exclusive = def_idx + 1
let mut batch_queue_end = core_worklist.cursor
while batch_queue_end < wave_end &&
core_worklist.queue[batch_queue_end] == batch_end_exclusive {
batch_end_exclusive += 1
batch_queue_end += 1
}
let before_call_facts = current_boundary_graph.snapshot.call_facts
let call_epoch = current_boundary_graph.call_epoch
let boundary_facts = current_boundary_graph.snapshot.boundary_facts
let producer_effects_report : Array[DaeCommitEffects] = []
let committed_application_report : Array[DaeCoreResultApplication] = []
let checked_candidate_report : Array[(@lib.Module, @lib.Module)]? = match
no_callback_trace {
Some(_) => Some([])
None => None
}
let result = dae_run_core_once(
current,
no_already_rewritten_defs,
current_call_counts_anywhere,
optimize,
unprofitable_removal_iters > 0,
original_boundary_snapshot,
current_boundary_graph,
expected_call_epoch=call_epoch,
max_def_exclusive=batch_end_exclusive,
start_def_inclusive=def_idx,
boundary_facts_override=Some(boundary_facts),
dependencies=Some(current_dependencies),
producer_effects_report~,
committed_application_report~,
checked_candidate_report~,
scalar_validation_chain~,
scalar_validation_work~,
numeric_result_validation_work~,
scalar_plan_code_snapshot_current~,
has_current_dropped_result_candidate~,
traps_never_happen~,
)
dae_scalar_validation_retain_exact(
scalar_validation_chain,
result.mod_out,
)
if !committed_application_report.is_empty() {
dae_numeric_result_validation_retain_report(
scalar_validation_chain,
result,
committed_application_report[0],
)
}
let consumed_end_exclusive = if result.changed &&
result.primary_def >= def_idx &&
result.primary_def < batch_end_exclusive {
result.primary_def + 1
} else {
batch_end_exclusive
}
while core_worklist.cursor < batch_queue_end &&
core_worklist.queue[core_worklist.cursor] < consumed_end_exclusive {
ignore(dae_boundary_worklist_take(core_worklist))
diagnostics.boundaries_analyzed += 1
}
let scanned = consumed_end_exclusive - def_idx
if scanned > 1 {
trace(
"pass[dae]:core-worklist-batch start=\{def_idx} primary_def=\{result.primary_def} scanned=\{scanned}",
)
for _i = 1; _i < scanned; _i = _i + 1 {
dae_diagnostics_record_reason(diagnostics, DaeNoStrictImprovement)
}
}
let before_primary_param_count = if result.primary_def >= 0 {
let before_type_indices = dae_collect_func_type_indices(current)
match
dae_resolve_func_type(
current,
before_type_indices,
original_imported + result.primary_def,
) {
Some(@lib.FuncType(params, _)) => params.length()
None => -1
}
} else {
-1
}
if !result.changed {
dae_diagnostics_record_reason(diagnostics, DaeNoStrictImprovement)
continue
}
let producer_effects = match producer_effects_report.get(0) {
Some(value) => value
None => {
dae_diagnostics_record_reason(diagnostics, DaeNoStrictImprovement)
continue
}
}
let family = dae_work_family_for_commit_effects(
producer_effects,
result.primary_def,
)
let item = dae_work_item_new(
current_boundary_graph,
current_dependencies,
result.primary_def,
family,
)
let application = match committed_application_report.get(0) {
Some(application) => application
None => {
let outcome = match checked_candidate_report {
Some(report) =>
dae_process_core_precomputed_signature_with_report(
original_boundary_snapshot, current, touched, changed, current_boundary_graph,
item, result, producer_effects, trace, no_callback_trace, report,
)
None =>
dae_process_precomputed_signature_work_item(
original_boundary_snapshot, current, touched, changed, current_boundary_graph,
item, result, producer_effects,
)
}
match outcome {
DaeWorkCommitted(value) => value
DaeWorkNoChange(_) | DaeWorkDeferred(_) | DaeWorkRequeue(_) => {
dae_scalar_validation_barrier_opt(scalar_validation_chain)
dae_diagnostics_record_reason(diagnostics, DaeNoStrictImprovement)
continue
}
}
}
}
dae_scalar_validation_retain_exact(
scalar_validation_chain,
application.mod_out,
)
dae_diagnostics_record_commit_effects(diagnostics, application.effects)
dae_diagnostics_record_applied_effects(diagnostics, application.effects)
if application.primary_def >= 0 && before_primary_param_count >= 0 {
let after_type_indices = dae_collect_func_type_indices(
application.mod_out,
)
let after_param_count = match
dae_resolve_func_type(
application.mod_out,
after_type_indices,
original_imported + application.primary_def,
) {
Some(@lib.FuncType(params, _)) => params.length()
None => before_primary_param_count
}
if after_param_count < before_primary_param_count {
wave_param_removals += 1
let callee_abs = original_imported + application.primary_def
if callee_abs >= 0 &&
callee_abs < before_call_facts.direct_calls.length() &&
before_call_facts.direct_calls[callee_abs] == 1 {
wave_single_caller_param_removals += 1
}
}
}
let before_dependencies = dae_boundary_dependency_graph_new(
current_boundary_graph,
)
dae_current_boundary_graph_refresh(
current_boundary_graph,
application.mod_out,
forced_changed_defs=Some(application.effects.structurally_touched),
)
scalar_plan_code_snapshot_current = dae_scalar_plan_code_snapshot_is_current(
application.mod_out,
current_boundary_graph,
)
has_current_dropped_result_candidate = dae_current_graph_has_dropped_result_candidate(
original_boundary_snapshot, current_boundary_graph,
)
let after_dependencies = dae_boundary_dependency_graph_new(
current_boundary_graph,
)
let invalidated = dae_boundary_dependency_invalidations(
before_dependencies,
after_dependencies,
application.touched,
application.primary_def,
application.effects,
)
current_dependencies = after_dependencies
current = application.mod_out
touched = application.touched
changed = application.changed
effects = dae_commit_effects_merge(effects, application.effects)
current_call_counts_anywhere = match current.code_sec {
Some(@lib.CodeSec(funcs)) =>
dae_collect_module_call_counts_anywhere(
funcs, original_imported, original_defined,
)
None => Array::make(original_defined, 0)
}
diagnostics.candidate_requeues += dae_boundary_worklist_requeue(
core_worklist, invalidated,
)
trace(
"pass[dae-optimizing]:core-worklist iter=\{iter} primary_def=\{application.primary_def}",
)
iter += 1
}
let unprofitable_param_wave = wave_param_removals == 1 &&
wave_single_caller_param_removals == 1
if unprofitable_param_wave && unprofitable_removal_iters == 0 {
unprofitable_removal_iters += 1
trace(
"pass[dae-optimizing]:core-worklist skip=further-param-removal unprofitable_iters=\{unprofitable_removal_iters} removals=1 single_callers=1",
)
}
}
dae_scalar_validation_barrier_opt(scalar_validation_chain)
perf_stop_timer_opt(perf, "detail:dae:core:fixed-loop", fixed_loop_start_us)
if results_only {
trace("pass[dae]:results-only-fallback stop=after-core-worklist")
return {
mod_out: current,
touched: if changed {
touched
} else {
[]
},
changed,
primary_def: -1,
effects,
diagnostics,
single_caller_throttle_consumed: unprofitable_removal_iters > 0,
}
}
let mut current_boundary_graph_stale = false
if dae_should_run_computed_unread_dependency_worklist(
original_defined,
optimize~,
) {
let computed_application = dae_run_computed_unread_param_dependency_worklist(
original_boundary_snapshot,
current,
touched,
changed,
unprofitable_removal_iters > 0,
trace,
)
current = computed_application.mod_out
if computed_application.effects.candidates_applied > 0 {
current_boundary_graph_stale = true
}
touched = computed_application.touched
changed = computed_application.changed
effects = dae_commit_effects_merge(effects, computed_application.effects)
}
let selected_lanes_start_us = perf_start_timer_opt(
perf, "detail:dae:core:selected-lanes",
)
let reverse_exact_literal_start_us = perf_start_timer_opt(
perf, "detail:dae:core:selected-lanes:reverse-exact-literal",
)
let restored_application = dae_apply_dead_suffix_self_operand_restore_transaction(
mod_,
current,
touched,
changed,
candidate_defs=if touched.length() == original_defined {
Some(touched)
} else {
None
},
)
if restored_application.applied {
current = restored_application.mod_out
current_boundary_graph_stale = true
touched = restored_application.touched
changed = restored_application.changed
effects = dae_commit_effects_merge(effects, restored_application.effects)
}
// Reuse one selected-def singleton scratch across the artifact-specific lanes
// that set at most one bit at a time. Each call clears the bit immediately
// after use, so later lanes observe the same all-false starting state they had
// with separate freshly allocated bitmaps.
let selected_scratch_defs = Array::make(original_defined, false)
let mut reverse_iter = 0
if dae_should_run_broad_uniform_parameter_flow(original_defined, optimize~) {
let broad_uniform = dae_run_broad_uniform_parameter_flow(
current, mod_, touched, changed,
)
current = broad_uniform.mod_out
if broad_uniform.productive_defs.length() > 0 {
current_boundary_graph_stale = true
}
touched = broad_uniform.touched
changed = broad_uniform.changed
effects = dae_commit_effects_merge(effects, broad_uniform.effects)
for primary_def in broad_uniform.productive_defs {
trace("pass[dae]:broad-exact-uniform primary_def=\{primary_def}")
}
}
if dae_should_run_reverse_exact_literal_phase(original_defined, optimize~) {
while true {
let reverse_graph = dae_current_boundary_graph_new(current)
let reverse_result = dae_try_rewrite_reverse_exact_literal_once(
current,
original_param_counts,
original_call_counts,
original_active_nonself_call_counts,
original_active_parameterized_nonself_call_counts,
original_nonself_undropped_call_counts,
original_self_call_counts,
original_private_result_dead_suffix_calls_from_escaped_operand_callees,
original_escaped_dead_suffix_call_counts,
original_dead_suffix_undropped_self_calls,
original_dead_suffix_dropped_self_calls,
original_dead_suffix_block_undropped_self_calls,
original_dead_suffix_result_calls_from_undropped_self_callers,
original_dead_suffix_result_calls_from_no_param_undropped_self_callers,
original_dead_suffix_same_self_result_operand_calls,
original_dead_suffix_undropped_self_call_param_operands,
original_dead_suffix_result_other_calls,
original_dead_suffix_escaped_result_call_facts,
original_dead_suffix_mutual_calls,
original_dead_suffix_calls_from_param_operand_self_result_callers,
original_dead_suffix_escaped_result_operand_call_counts,
original_dead_suffix_dropped_calls_from_parameterized_self_callers,
supplied_graph=Some(reverse_graph),
)
if !reverse_result.changed || reverse_result.primary_def < 0 {
break
}
let reverse_effects = if reverse_result.changed &&
reverse_result.primary_def >= 0 {
Some(
dae_work_item_candidate_effects(
current,
reverse_graph,
dae_work_item_new(
reverse_graph,
dae_boundary_dependency_graph_new(reverse_graph),
reverse_result.primary_def,
DaeUniformActualWork,
),
reverse_result,
),
)
} else {
None
}
let reverse_application = match reverse_effects {
Some(producer_effects) =>
match
dae_process_precomputed_signature_work_item(
original_boundary_snapshot,
current,
touched,
changed,
reverse_graph,
dae_work_item_new(
reverse_graph,
dae_boundary_dependency_graph_new(reverse_graph),
reverse_result.primary_def,
DaeUniformActualWork,
),
reverse_result,
producer_effects,
) {
DaeWorkCommitted(value) => value
_ => break
}
None => break
}
current = reverse_application.mod_out
current_boundary_graph_stale = true
touched = reverse_application.touched
changed = reverse_application.changed
effects = dae_commit_effects_merge(effects, reverse_application.effects)
trace(
"pass[dae-optimizing]:reverse-exact-literal iter=\{reverse_iter} primary_def=\{reverse_application.primary_def}",
)
reverse_iter += 1
}
}
// Phase-local profitability replay uses semantic candidate evidence rather
// than a module-size admission threshold. Each wave still commits at most one
// validated candidate, preserving the existing source-order frontier.
let mut low_revisit_iter = 0
while dae_should_run_forwarded_constant_revisit(original_defined, optimize~) {
let low_revisit_graph = dae_current_boundary_graph_new(current)
let low_revisit_result = dae_try_rewrite_reverse_exact_literal_once(
current,
original_param_counts,
original_call_counts,
original_active_nonself_call_counts,
original_active_parameterized_nonself_call_counts,
original_nonself_undropped_call_counts,
original_self_call_counts,
original_private_result_dead_suffix_calls_from_escaped_operand_callees,
original_escaped_dead_suffix_call_counts,
original_dead_suffix_undropped_self_calls,
original_dead_suffix_dropped_self_calls,
original_dead_suffix_block_undropped_self_calls,
original_dead_suffix_result_calls_from_undropped_self_callers,
original_dead_suffix_result_calls_from_no_param_undropped_self_callers,
original_dead_suffix_same_self_result_operand_calls,
original_dead_suffix_undropped_self_call_param_operands,
original_dead_suffix_result_other_calls,
original_dead_suffix_escaped_result_call_facts,
original_dead_suffix_mutual_calls,
original_dead_suffix_calls_from_param_operand_self_result_callers,
original_dead_suffix_escaped_result_operand_call_counts,
original_dead_suffix_dropped_calls_from_parameterized_self_callers,
supplied_graph=Some(low_revisit_graph),
reverse=false,
max_def_exclusive=original_defined,
)
if !low_revisit_result.changed || low_revisit_result.primary_def < 0 {
break
}
let low_revisit_effects = if low_revisit_result.changed &&
low_revisit_result.primary_def >= 0 {
Some(
dae_work_item_candidate_effects(
current,
low_revisit_graph,
dae_work_item_new(
low_revisit_graph,
dae_boundary_dependency_graph_new(low_revisit_graph),
low_revisit_result.primary_def,
DaeUniformActualWork,
),
low_revisit_result,
),
)
} else {
None
}
let low_revisit_application = match low_revisit_effects {
Some(producer_effects) =>
match
dae_process_precomputed_signature_work_item(
original_boundary_snapshot,
current,
touched,
changed,
low_revisit_graph,
dae_work_item_new(
low_revisit_graph,
dae_boundary_dependency_graph_new(low_revisit_graph),
low_revisit_result.primary_def,
DaeUniformActualWork,
),
low_revisit_result,
producer_effects,
) {
DaeWorkCommitted(value) => value
_ => break
}
None => break
}
current = low_revisit_application.mod_out
current_boundary_graph_stale = true
touched = low_revisit_application.touched
changed = low_revisit_application.changed
effects = dae_commit_effects_merge(effects, low_revisit_application.effects)
trace(
"pass[dae-optimizing]:low-forwarded-const-revisit iter=\{low_revisit_iter} primary_def=\{low_revisit_application.primary_def}",
)
low_revisit_iter += 1
}
// Exact type/call-fact admission is independent of parameter count and shape.
// Run it after the ordinary reverse phase so it cannot preempt that proof.
// Modules can contain profitable exact/default candidates that the fixed
// eight-iteration core never reaches. Build the ordered worklist
// once from one current caller-fact snapshot, then revalidate and apply each
// candidate against the evolving module. Signature and operand rewrites do
// not change direct-call edges, so the snapshot remains valid for this batch.
let low_exact_literal_candidates = if dae_should_collect_low_exact_literal_candidates(
optimize,
restored_application.applied,
scalar_plan_code_snapshot_current,
) {
let low_exact_literal_call_facts = dae_collect_current_call_facts(
current, original_total,
)
dae_collect_post_revisit_exact_literal_candidates(
current, low_exact_literal_call_facts, original_defined,
)
} else {
[]
}
let low_exact_literal_application = if !optimize &&
!restored_application.applied {
dae_run_exact_literal_candidate_worklist_with_facts(
current,
touched,
changed,
low_exact_literal_candidates,
original=if low_exact_literal_candidates.is_empty() {
None
} else {
Some(dae_collect_original_boundary_snapshot(current))
},
graph=Some(current_boundary_graph),
dependencies=Some(current_dependencies),
)
} else {
dae_run_exact_literal_candidate_worklist_with_facts(
current, touched, changed, low_exact_literal_candidates,
)
}
current = low_exact_literal_application.mod_out
if low_exact_literal_application.productive_defs.length() > 0 {
current_boundary_graph_stale = true
}
touched = low_exact_literal_application.touched
changed = low_exact_literal_application.changed
effects = dae_commit_effects_merge(
effects,
low_exact_literal_application.effects,
)
for iter, primary_def in low_exact_literal_application.productive_defs {
trace(
"pass[dae-optimizing]:low-exact-literal-worklist iter=\{iter} primary_def=\{primary_def}",
)
}
let low_exact_cleanup_defs = Array::make(original_defined, false)
for productive_def in low_exact_literal_application.productive_defs {
if productive_def >= 0 && productive_def < low_exact_cleanup_defs.length() {
low_exact_cleanup_defs[productive_def] = true
}
}
let low_exact_null_if_cleanup = dae_try_fold_selected_ref_null_is_null_ifs_once(
current, low_exact_cleanup_defs,
)
let low_exact_null_if_application = dae_apply_body_only_family_result_transaction(
original_boundary_snapshot, current, touched, changed, low_exact_null_if_cleanup,
diagnostics,
)
if low_exact_null_if_application.applied {
current = low_exact_null_if_application.mod_out
current_boundary_graph_stale = true
touched = low_exact_null_if_application.touched
changed = low_exact_null_if_application.changed
effects = dae_commit_effects_merge(
effects,
low_exact_null_if_application.effects,
)
trace(
"pass[dae-optimizing]:low-exact-null-if-cleanup primary_def=\{low_exact_null_if_application.primary_def}",
)
}
let low_exact_terminal_locals = dae_try_inline_selected_terminal_call_local_sets_once(
current, low_exact_cleanup_defs,
)
let low_exact_terminal_application = dae_apply_body_only_family_result_transaction(
original_boundary_snapshot, current, touched, changed, low_exact_terminal_locals,
diagnostics,
)
if low_exact_terminal_application.applied {
current = low_exact_terminal_application.mod_out
current_boundary_graph_stale = true
touched = low_exact_terminal_application.touched
changed = low_exact_terminal_application.changed
effects = dae_commit_effects_merge(
effects,
low_exact_terminal_application.effects,
)
trace(
"pass[dae-optimizing]:low-exact-terminal-call-locals primary_def=\{low_exact_terminal_application.primary_def}",
)
}
let low_exact_result_application = dae_run_direct_gc_result_refinement_dependency_closure(
current,
touched,
changed,
low_exact_literal_application.productive_defs,
)
current = low_exact_result_application.mod_out
if low_exact_result_application.productive_defs.length() > 0 {
current_boundary_graph_stale = true
}
touched = low_exact_result_application.touched
changed = low_exact_result_application.changed
effects = dae_commit_effects_merge(
effects,
low_exact_result_application.effects,
)
for iter, primary_def in low_exact_result_application.productive_defs {
trace(
"pass[dae-optimizing]:low-exact-result-worklist iter=\{iter} primary_def=\{primary_def}",
)
}
if optimize {
// Discover only direct call targets whose final immediate actual
// is already a materializable literal, null/string constant, or immutable
// global, then reuse the complete value-slice, ownership, escape, write,
// and call-count proof in the existing rewrite.
let large_global_defs = dae_collect_large_uniform_const_revisit_defs(
current,
)
let all_large_defs = Array::make(original_defined, true)
// Each candidate enters the common epoch-bearing uniform-value work item.
// The current graph refreshes after every commit, so newly exposed direct
// callees can join the same deterministic frontier without a selected-only
// scan-and-mutate path.
for candidate_def = 0
candidate_def < large_global_defs.length()
candidate_def = candidate_def + 1 {
if !large_global_defs[candidate_def] {
continue
}
trace(
"pass[dae-optimizing]:large-uniform-const-candidate primary_def=\{candidate_def}",
)
let global_application = dae_run_exact_literal_candidate_worklist_with_facts(
current,
touched,
changed,
[candidate_def],
allow_immutable_global_get=true,
allow_immediate_i32_suffix_fallback=true,
original=Some(original_boundary_snapshot),
)
if global_application.productive_defs.length() > 0 {
current = global_application.mod_out
current_boundary_graph_stale = true
touched = global_application.touched
changed = global_application.changed
effects = dae_commit_effects_merge(effects, global_application.effects)
trace(
"pass[dae-optimizing]:large-uniform-const-revisit primary_def=\{global_application.productive_defs[0]}",
)
// Materializing this boundary can expose an immediate literal in a
// forwarded direct callee. Admit only those newly visible call targets;
// the same exact call-slice/ownership transaction remains authoritative.
match current.code_sec {
Some(@lib.CodeSec(funcs)) => {
let primary_def = global_application.productive_defs[0]
if primary_def >= 0 && primary_def < funcs.length() {
let @lib.Func(_, @lib.Expr(body)) = funcs[primary_def]
dae_collect_immediate_materializable_call_targets_instrs(
current,
body,
dae_imported_func_count(current),
all_large_defs,
large_global_defs,
)
}
}
None => ()
}
} else {
trace(
"pass[dae-optimizing]:large-uniform-const-rejected primary_def=\{candidate_def}",
)
}
}
}
perf_stop_timer_opt(
perf, "detail:dae:core:selected-lanes:reverse-exact-literal", reverse_exact_literal_start_us,
)
let low_wrapper_callees_start_us = perf_start_timer_opt(
perf, "detail:dae:core:selected-lanes:low-wrapper-callees",
)
// Keep the phase-local forwarding seed, but derive targets from exact current
// wrapper shape rather than a fixed wrapper-definition list.
let boundary_callee_order = if dae_should_run_low_wrapper_callee_phase(
optimize~,
) {
dae_collect_shifted_forward_wrapper_callee_order(
current, 0, original_defined,
)
} else {
[]
}
let mut boundary_graph_opt : DaeCurrentBoundaryGraph? = None
let mut boundary_dependencies_opt : DaeBoundaryDependencyGraph? = None
for boundary_callee_def in boundary_callee_order {
let boundary_graph = match boundary_graph_opt {
Some(graph) => graph
None => {
let graph = dae_current_boundary_graph_new(current)
boundary_graph_opt = Some(graph)
graph
}
}
let boundary_dependencies = match boundary_dependencies_opt {
Some(dependencies) => dependencies
None => {
let dependencies = dae_boundary_dependency_graph_new(boundary_graph)
boundary_dependencies_opt = Some(dependencies)
dependencies
}
}
if boundary_callee_def < 0 ||
boundary_callee_def >= selected_scratch_defs.length() {
continue
}
selected_scratch_defs[boundary_callee_def] = true
let boundary_effects : Array[DaeCommitEffects] = []
let boundary_result = dae_try_rewrite_selected_defs_unread_params_with_facts_once(
current,
selected_scratch_defs,
boundary_graph.snapshot.call_facts,
original=Some(original_boundary_snapshot),
graph=Some(boundary_graph),
producer_effects_report=boundary_effects,
)
selected_scratch_defs[boundary_callee_def] = false
let boundary_application = match boundary_effects.get(0) {
Some(producer_effects) =>
match
dae_process_precomputed_signature_work_item(
original_boundary_snapshot,
current,
touched,
changed,
boundary_graph,
dae_work_item_new(
boundary_graph,
boundary_dependencies,
boundary_result.primary_def,
dae_work_family_for_commit_effects(
producer_effects,
boundary_result.primary_def,
),
),
boundary_result,
producer_effects,
) {
DaeWorkCommitted(value) => Some(value)
_ => None
}
None => None
}
match boundary_application {
Some(boundary_application) => {
current = boundary_application.mod_out
current_boundary_graph_stale = true
dae_current_boundary_graph_refresh(boundary_graph, current)
boundary_dependencies_opt = Some(
dae_boundary_dependency_graph_new(boundary_graph),
)
touched = boundary_application.touched
changed = boundary_application.changed
effects = dae_commit_effects_merge(
effects,
boundary_application.effects,
)
dae_diagnostics_record_commit_effects(
diagnostics,
boundary_application.effects,
)
dae_diagnostics_record_applied_effects(
diagnostics,
boundary_application.effects,
)
trace(
"pass[dae-optimizing]:boundary-wrapper-callee-core primary_def=\{boundary_application.primary_def}",
)
}
None => ()
}
}
perf_stop_timer_opt(
perf, "detail:dae:core:selected-lanes:low-wrapper-callees", low_wrapper_callees_start_us,
)
let early_shapes_start_us = perf_start_timer_opt(
perf, "detail:dae:core:selected-lanes:early-shapes",
)
perf_stop_timer_opt(
perf, "detail:dae:core:selected-lanes:early-shapes", early_shapes_start_us,
)
let dropped_results_start_us = perf_start_timer_opt(
perf, "detail:dae:core:selected-lanes:dropped-results",
)
let dropped_result_selected_defs_start_us = perf_start_timer_opt(
perf, "detail:dae:core:selected-lanes:dropped-results:selected-defs",
)
let mut fact_dropped_result_wave = 0
while dae_should_run_fact_dropped_result_revisit(optimize~) {
let fact_dropped_result_call_facts_start_us = perf_start_timer_opt(
perf, "detail:dae:core:selected-lanes:dropped-results:call-facts",
)
let fact_dropped_result_call_facts = dae_collect_current_call_facts(
current, original_total,
)
perf_stop_timer_opt(
perf, "detail:dae:core:selected-lanes:dropped-results:call-facts", fact_dropped_result_call_facts_start_us,
)
let selected = dae_collect_fact_dropped_result_candidate_defs(
current, fact_dropped_result_call_facts,
)
let candidate_defs = dae_order_selected_candidates_caller_first(
current, selected,
)
let fact_dropped_result = dae_run_dropped_result_candidate_worklist_with_facts(
current, touched, changed, candidate_defs,
)
if fact_dropped_result.productive_defs.length() == 0 {
break
}
current = fact_dropped_result.mod_out
current_boundary_graph_stale = true
touched = fact_dropped_result.touched
changed = fact_dropped_result.changed
effects = dae_commit_effects_merge(effects, fact_dropped_result.effects)
for primary_def in fact_dropped_result.productive_defs {
trace(
"pass[dae-optimizing]:fact-dropped-result wave=\{fact_dropped_result_wave} primary_def=\{primary_def}",
)
}
fact_dropped_result_wave += 1
}
perf_stop_timer_opt(
perf, "detail:dae:core:selected-lanes:dropped-results:selected-defs", dropped_result_selected_defs_start_us,
)
perf_stop_timer_opt(
perf, "detail:dae:core:selected-lanes:dropped-results", dropped_results_start_us,
)
let literal_unread_start_us = perf_start_timer_opt(
perf, "detail:dae:core:selected-lanes:literal-unread",
)
perf_stop_timer_opt(
perf, "detail:dae:core:selected-lanes:literal-unread", literal_unread_start_us,
)
let late_shapes_start_us = perf_start_timer_opt(
perf, "detail:dae:core:selected-lanes:late-shapes",
)
let late_shapes_input = current
let mut late_shapes_changed = false
let const_local_candidates = dae_collect_single_const_local_call_candidates(
mod_,
)
let mut const_local_iteration = 0
while true {
let const_local_result = dae_try_inline_selected_i32_const_local_sets_once(
current, const_local_candidates,
)
let const_local_application = dae_apply_body_only_family_result_transaction(
original_boundary_snapshot, current, touched, changed, const_local_result,
diagnostics,
)
if !const_local_application.applied {
break
}
current = const_local_application.mod_out
current_boundary_graph_stale = true
late_shapes_changed = true
touched = const_local_application.touched
changed = const_local_application.changed
effects = dae_commit_effects_merge(effects, const_local_application.effects)
trace(
"pass[dae-optimizing]:semantic-const-locals iter=\{const_local_iteration} primary_def=\{const_local_application.primary_def}",
)
const_local_iteration += 1
}
let pair_if_candidates = touched.copy()
let pair_if_result = dae_try_rewrite_selected_func408_pair_if_carrier_once(
current, pair_if_candidates,
)
let pair_if_application = dae_apply_body_only_family_result_transaction(
original_boundary_snapshot, current, touched, changed, pair_if_result, diagnostics,
)
if pair_if_application.applied {
current = pair_if_application.mod_out
current_boundary_graph_stale = true
late_shapes_changed = true
touched = pair_if_application.touched
changed = pair_if_application.changed
effects = dae_commit_effects_merge(effects, pair_if_application.effects)
trace(
"pass[dae-optimizing]:touched-pair-if-carrier primary_def=\{pair_if_application.primary_def}",
)
}
let terminal_args311_candidates = dae_collect_terminal_call_arg_cleanup_candidates(
mod_, current, 4, 24,
)
let terminal_args311_result = dae_try_rewrite_selected_func311_terminal_call_args_once(
current, terminal_args311_candidates, trace,
)
let terminal_args311_application = dae_apply_body_only_family_result_transaction(
original_boundary_snapshot, current, touched, changed, terminal_args311_result,
diagnostics,
)
if terminal_args311_application.applied {
current = terminal_args311_application.mod_out
current_boundary_graph_stale = true
late_shapes_changed = true
touched = terminal_args311_application.touched
changed = terminal_args311_application.changed
effects = dae_commit_effects_merge(
effects,
terminal_args311_application.effects,
)
trace(
"pass[dae-optimizing]:changed-terminal-call-args-a primary_def=\{terminal_args311_application.primary_def}",
)
}
let terminal_args313_candidates = dae_collect_terminal_call_arg_cleanup_candidates(
mod_, current, 7, 2,
)
let terminal_args313_result = dae_try_rewrite_selected_func313_terminal_call_args_once(
current, terminal_args313_candidates, trace,
)
let terminal_args313_application = dae_apply_body_only_family_result_transaction(
original_boundary_snapshot, current, touched, changed, terminal_args313_result,
diagnostics,
)
if terminal_args313_application.applied {
current = terminal_args313_application.mod_out
current_boundary_graph_stale = true
late_shapes_changed = true
touched = terminal_args313_application.touched
changed = terminal_args313_application.changed
effects = dae_commit_effects_merge(
effects,
terminal_args313_application.effects,
)
trace(
"pass[dae-optimizing]:changed-terminal-call-args-b primary_def=\{terminal_args313_application.primary_def}",
)
}
let global_suffix_candidates = dae_collect_direct_callee_defs_from_selected_callers(
current, touched,
)
while true {
let global_suffix_application = dae_apply_selected_func323_global_suffix_transaction(
mod_,
current,
touched,
changed,
global_suffix_candidates,
trace,
original_snapshot=Some(original_boundary_snapshot),
boundary_snapshot=if !current_boundary_graph_stale {
Some(current_boundary_graph.snapshot)
} else {
None
},
)
if !global_suffix_application.applied {
break
}
current = global_suffix_application.mod_out
current_boundary_graph_stale = true
late_shapes_changed = true
touched = global_suffix_application.touched
changed = global_suffix_application.changed
effects = dae_commit_effects_merge(
effects,
global_suffix_application.effects,
)
dae_diagnostics_record_commit_effects(
diagnostics,
global_suffix_application.effects,
)
dae_diagnostics_record_applied_effects(
diagnostics,
global_suffix_application.effects,
)
trace(
"pass[dae-optimizing]:single-caller-global-suffix primary_def=\{global_suffix_application.primary_def}",
)
}
// Shape cleanup can expose uniform operands only at direct callees of bodies
// changed by this phase. Seed that exact invalidation frontier instead of
// rescanning/admitting every module definition.
let post_shape_uniform = if dae_should_run_changed_direct_callee_uniform_frontier(
late_shapes_changed,
) {
dae_run_changed_direct_callee_uniform_frontier(
late_shapes_input, current, touched, changed,
)
} else {
{
mod_out: current,
touched,
changed,
productive_defs: [],
effects: dae_commit_effects_empty(original_defined),
}
}
current = post_shape_uniform.mod_out
touched = post_shape_uniform.touched
changed = post_shape_uniform.changed
effects = dae_commit_effects_merge(effects, post_shape_uniform.effects)
for primary_def in post_shape_uniform.productive_defs {
trace("pass[dae-optimizing]:post-shape-uniform primary_def=\{primary_def}")
}
perf_stop_timer_opt(
perf, "detail:dae:core:selected-lanes:late-shapes", late_shapes_start_us,
)
perf_stop_timer_opt(
perf, "detail:dae:core:selected-lanes", selected_lanes_start_us,
)
let raw_cleanup_start_us = perf_start_timer_opt(
perf, "detail:dae:core:raw-cleanup",
)
let module_raw_cleanup = dae_strip_module_nops_and_dropped_pure_leaf_once(
current,
touched,
return_touched=false,
)
let module_raw_cleanup_application = dae_apply_body_only_family_result_transaction(
original_boundary_snapshot, current, touched, changed, module_raw_cleanup, diagnostics,
)
if module_raw_cleanup_application.applied {
current = module_raw_cleanup_application.mod_out
touched = module_raw_cleanup_application.touched
effects = dae_commit_effects_merge(
effects,
module_raw_cleanup_application.effects,
)
// The module raw cleanup rewrites bodies but does not discover new DAE
// candidate functions; preserve the existing scheduler-owned bitmap.
trace(
"pass[dae-optimizing]:module-raw-cleanup primary_def=\{module_raw_cleanup_application.primary_def}",
)
changed = module_raw_cleanup_application.changed
}
perf_stop_timer_opt(perf, "detail:dae:core:raw-cleanup", raw_cleanup_start_us)
let post_raw_selected_lanes_start_us = perf_start_timer_opt(
perf, "detail:dae:core:post-raw-selected-lanes",
)
let changed_terminal_cleanup_input = current
let mut changed_terminal_phase_changed = false
let changed_terminal_candidates = dae_collect_terminal_callers_of_changed_boundaries(
mod_, current,
)
while true {
let const_if_result = dae_try_simplify_selected_const_if_defs_once(
current, changed_terminal_candidates,
)
let const_if_application = dae_apply_body_only_family_result_transaction(
original_boundary_snapshot, current, touched, changed, const_if_result, diagnostics,
)
if !const_if_application.applied {
break
}
current = const_if_application.mod_out
changed_terminal_phase_changed = true
touched = const_if_application.touched
changed = const_if_application.changed
effects = dae_commit_effects_merge(effects, const_if_application.effects)
trace(
"pass[dae-optimizing]:changed-terminal-const-if primary_def=\{const_if_application.primary_def}",
)
}
while true {
let terminal_local_result = dae_try_inline_selected_terminal_call_local_sets_once(
current, changed_terminal_candidates,
)
let terminal_local_application = dae_apply_body_only_family_result_transaction(
original_boundary_snapshot, current, touched, changed, terminal_local_result,
diagnostics,
)
if !terminal_local_application.applied {
break
}
current = terminal_local_application.mod_out
changed_terminal_phase_changed = true
touched = terminal_local_application.touched
changed = terminal_local_application.changed
effects = dae_commit_effects_merge(
effects,
terminal_local_application.effects,
)
trace(
"pass[dae-optimizing]:changed-terminal-call-locals primary_def=\{terminal_local_application.primary_def}",
)
}
let default_wrapper_result = dae_try_cleanup_selected_func327_default_wrappers_once(
current, changed_terminal_candidates,
)
let default_wrapper_application = dae_apply_body_only_family_result_transaction(
original_boundary_snapshot, current, touched, changed, default_wrapper_result,
diagnostics,
)
if default_wrapper_application.applied {
current = default_wrapper_application.mod_out
changed_terminal_phase_changed = true
touched = default_wrapper_application.touched
changed = default_wrapper_application.changed
effects = dae_commit_effects_merge(
effects,
default_wrapper_application.effects,
)
trace(
"pass[dae-optimizing]:changed-terminal-default-wrappers primary_def=\{default_wrapper_application.primary_def}",
)
}
let changed_terminal_uniform = if dae_should_run_changed_direct_callee_uniform_frontier(
changed_terminal_phase_changed,
) {
dae_run_changed_direct_callee_uniform_frontier(
changed_terminal_cleanup_input, current, touched, changed,
)
} else {
{
mod_out: current,
touched,
changed,
productive_defs: [],
effects: dae_commit_effects_empty(original_defined),
}
}
current = changed_terminal_uniform.mod_out
touched = changed_terminal_uniform.touched
changed = changed_terminal_uniform.changed
effects = dae_commit_effects_merge(effects, changed_terminal_uniform.effects)
for primary_def in changed_terminal_uniform.productive_defs {
trace(
"pass[dae-optimizing]:post-changed-terminal-uniform primary_def=\{primary_def}",
)
}
let final_return_suffix_cleanup = dae_strip_module_root_return_suffix_once(
current, touched,
)
let final_return_suffix_cleanup_application = dae_apply_body_only_family_result_transaction(
original_boundary_snapshot, current, touched, changed, final_return_suffix_cleanup,
diagnostics,
)
if final_return_suffix_cleanup_application.applied {
current = final_return_suffix_cleanup_application.mod_out
touched = final_return_suffix_cleanup_application.touched
effects = dae_commit_effects_merge(
effects,
final_return_suffix_cleanup_application.effects,
)
trace(
"pass[dae-optimizing]:final-return-suffix-cleanup primary_def=\{final_return_suffix_cleanup_application.primary_def}",
)
changed = final_return_suffix_cleanup_application.changed
}
perf_stop_timer_opt(
perf, "detail:dae:core:post-raw-selected-lanes", post_raw_selected_lanes_start_us,
)
{
mod_out: current,
touched,
changed,
primary_def: -1,
effects,
diagnostics,
single_caller_throttle_consumed: unprofitable_removal_iters > 0,
}
}
///|
fn dae_modules_form_single_caller_param_wave(
before : @lib.Module,
after : @lib.Module,
) -> Bool {
if after == before {
return false
}
let defined = dae_defined_func_count(before)
if dae_defined_func_count(after) != defined {
return false
}
let imported = dae_imported_func_count(before)
if dae_imported_func_count(after) != imported {
return false
}
let before_types = dae_collect_func_type_indices(before)
let after_types = dae_collect_func_type_indices(after)
let before_calls = dae_collect_current_call_facts(before, imported + defined)
let mut reduced_def = -1
for def_idx = 0; def_idx < defined; def_idx = def_idx + 1 {
let (before_params, before_results) = match
dae_resolve_func_type(before, before_types, imported + def_idx) {
Some(@lib.FuncType(params, results)) => (params, results)
None => return false
}
let (after_params, after_results) = match
dae_resolve_func_type(after, after_types, imported + def_idx) {
Some(@lib.FuncType(params, results)) => (params, results)
None => return false
}
if after_results != before_results {
return false
}
if after_params.length() < before_params.length() {
if reduced_def >= 0 {
return false
}
reduced_def = def_idx
} else if after_params != before_params {
return false
}
}
if reduced_def < 0 {
return false
}
let callee_abs = imported + reduced_def
callee_abs < before_calls.direct_calls.length() &&
before_calls.direct_calls[callee_abs] == 1
}
///|
fn dae_count_touched(touched : Array[Bool]) -> Int32 {
let mut count = 0
for item in touched {
if item {
count += 1
}
}
count
}
///|
fn dae_val_type_is_nonnull_ref(vt : @lib.ValType) -> Bool {
match vt {
@lib.RefTypeValType(ref_type) => ref_type.is_non_nullable()
_ => false
}
}
///|
fn dae_fold_nonnull_ref_is_null_func(
func : @lib.Func,
params : Array[@lib.ValType],
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(body)) = func
match body {
[@lib.LocalGet(local_idx), @lib.RefIsNull] => {
let slot = dae_local_idx_slot(local_idx)
if slot >= 0 &&
slot < params.length() &&
dae_val_type_is_nonnull_ref(params[slot]) {
return (
@lib.Func::new(
locals,
@lib.Expr::new([@lib.Instruction::i32_const(@lib.I32(0))]),
),
true,
)
}
(func, false)
}
_ => (func, false)
}
}
///|
fn dae_fold_touched_nonnull_ref_is_null(
mod_ : @lib.Module,
touched : Array[Bool],
) -> (@lib.Module, Bool) {
let (type_idxs, funcs) = match (mod_.func_sec, mod_.code_sec) {
(Some(@lib.FuncSec(type_idxs)), Some(@lib.CodeSec(funcs))) =>
(type_idxs, funcs)
_ => return (mod_, false)
}
let rewritten_funcs = funcs.copy()
let mut changed = false
for def_idx = 0; def_idx < funcs.length(); def_idx = def_idx + 1 {
if def_idx < touched.length() && touched[def_idx] {
// FuncSec contains only definitions, so this lookup uses a defined index.
let params = match dae_resolve_func_type(mod_, type_idxs, def_idx) {
Some(@lib.FuncType(params, _)) => params
None => []
}
let (rewritten, func_changed) = dae_fold_nonnull_ref_is_null_func(
funcs[def_idx],
params,
)
if func_changed {
rewritten_funcs[def_idx] = rewritten
changed = true
}
}
}
if !changed {
return (mod_, false)
}
(
mod_.with_code_sec(@lib.CodeSec::new(rewritten_funcs)).without_name_sec(),
true,
)
}
///|
fn dae_trim_instrs_after_unreachable_tail(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut idx = 0
while idx < instrs.length() {
let rewritten = match instrs[idx] {
@lib.Instruction::Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = dae_trim_instrs_after_unreachable_tail(
body,
)
if body_changed {
changed = true
}
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
}
@lib.Instruction::Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = dae_trim_instrs_after_unreachable_tail(
body,
)
if body_changed {
changed = true
}
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
}
@lib.Instruction::If(block_type, then_body, else_body) => {
let (next_then, then_changed) = dae_trim_instrs_after_unreachable_tail(
then_body,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (body2, changed2) = dae_trim_instrs_after_unreachable_tail(body)
(Some(body2), changed2)
}
None => (None, false)
}
if then_changed || else_changed {
changed = true
}
@lib.Instruction::if_(block_type, next_then, next_else)
}
@lib.Instruction::TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = dae_trim_instrs_after_unreachable_tail(
body,
)
if body_changed {
changed = true
}
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
}
other => other
}
let terminal = if dae_instr_is_unreachable_root(rewritten) {
if rewritten != @lib.Instruction::unreachable_() {
changed = true
}
@lib.Instruction::unreachable_()
} else {
rewritten
}
out.push(terminal)
if terminal == @lib.Instruction::unreachable_() && idx + 1 < instrs.length() {
changed = true
break
}
idx += 1
}
(out, changed)
}
///|
fn dae_instrs_may_have_unreachable_debris(
instrs : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
if dae_instr_is_unreachable_root(instr) {
return true
}
match instr {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
if dae_instrs_may_have_unreachable_debris(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if dae_instrs_may_have_unreachable_debris(then_body) {
return true
}
match else_body {
Some(body) =>
if dae_instrs_may_have_unreachable_debris(body) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if dae_instrs_may_have_unreachable_debris(body) {
return true
}
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(_, @lib.Expr(catch_body))
| @lib.LegacyCatchAll(@lib.Expr(catch_body)) =>
if dae_instrs_may_have_unreachable_debris(catch_body) {
return true
}
}
}
}
_ => ()
}
}
false
}
///|
fn dae_cleanup_dropped_unreachable_debris_touched(
mod_ : @lib.Module,
touched : Array[Bool],
) -> (@lib.Module, Bool) {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return (mod_, false)
}
let rewritten_funcs = funcs.copy()
let mut changed = false
for idx = 0; idx < funcs.length(); idx = idx + 1 {
if idx >= touched.length() || !touched[idx] {
continue
}
let @lib.Func(locals, @lib.Expr(instrs)) = funcs[idx]
let (debris_instrs, debris_changed) = pass_raw_remove_dropped_unreachable_debris(
instrs,
)
let (next_instrs, tail_changed) = dae_trim_instrs_after_unreachable_tail(
debris_instrs,
)
if debris_changed || tail_changed {
rewritten_funcs[idx] = @lib.Func::new(locals, @lib.Expr::new(next_instrs))
changed = true
}
}
if !changed {
return (mod_, false)
}
(
mod_.with_code_sec(@lib.CodeSec::new(rewritten_funcs)).without_name_sec(),
true,
)
}
///|
fn dae_cleanup_dropped_unreachable_debris(
mod_ : @lib.Module,
) -> (@lib.Module, Bool) {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return (mod_, false)
}
let rewritten_funcs = funcs.copy()
let mut changed = false
for idx = 0; idx < funcs.length(); idx = idx + 1 {
let @lib.Func(locals, @lib.Expr(instrs)) = funcs[idx]
if !dae_instrs_may_have_unreachable_debris(instrs) {
continue
}
let (debris_instrs, debris_changed) = pass_raw_remove_dropped_unreachable_debris(
instrs,
)
let (next_instrs, tail_changed) = dae_trim_instrs_after_unreachable_tail(
debris_instrs,
)
if debris_changed || tail_changed {
rewritten_funcs[idx] = @lib.Func::new(locals, @lib.Expr::new(next_instrs))
changed = true
}
}
if !changed {
return (mod_, false)
}
(
mod_.with_code_sec(@lib.CodeSec::new(rewritten_funcs)).without_name_sec(),
true,
)
}
///|
fn dae_cleanup_unread_local_body(
instrs : Array[@lib.Instruction],
reads : Array[Int32],
) -> (Array[@lib.Instruction], Bool) {
let out : Array[@lib.Instruction] = []
let mut changed = false
for original in instrs {
let instr = pass_lower_map_children(original, fn(body) {
let (next, child_changed) = dae_cleanup_unread_local_body(body, reads)
changed = changed || child_changed
next
})
let instr = match instr {
@lib.LocalSet(@lib.LocalIdx(index)) if reads[index.to_int()] == 0 => {
changed = true
@lib.Instruction::drop()
}
@lib.LocalTee(@lib.LocalIdx(index)) if reads[index.to_int()] == 0 => {
changed = true
continue
}
_ => instr
}
if instr is @lib.Drop && !out.is_empty() {
match out[out.length() - 1] {
@lib.LocalGet(_)
| @lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.RefNull(_)
| @lib.RefFunc(_) => {
changed = true
ignore(out.pop())
continue
}
@lib.LocalTee(index) => {
changed = true
ignore(out.pop())
out.push(@lib.Instruction::local_set(index))
continue
}
_ => ()
}
}
out.push(instr)
}
(out, changed)
}
///|
fn dae_collect_unread_simple_copy_pairs(
instrs : Array[@lib.Instruction],
reads : Array[Int32],
pairs : Array[(Int32, Int32)],
) -> Unit {
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if idx > 0 {
match (instrs[idx - 1], instrs[idx]) {
(
@lib.LocalGet(@lib.LocalIdx(source)),
@lib.LocalSet(@lib.LocalIdx(target)),
) => {
let source_idx = source.reinterpret_as_int()
let target_idx = target.reinterpret_as_int()
if source_idx >= 0 &&
source_idx < reads.length() &&
target_idx >= 0 &&
target_idx < reads.length() {
pairs.push((source_idx, target_idx))
}
}
_ => ()
}
}
match instrs[idx] {
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
dae_collect_unread_simple_copy_pairs(body, reads, pairs)
@lib.If(_, then_body, else_body) => {
dae_collect_unread_simple_copy_pairs(then_body, reads, pairs)
match else_body {
Some(body) => dae_collect_unread_simple_copy_pairs(body, reads, pairs)
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
dae_collect_unread_simple_copy_pairs(body, reads, pairs)
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(_, @lib.Expr(catch_body))
| @lib.LegacyCatchAll(@lib.Expr(catch_body)) =>
dae_collect_unread_simple_copy_pairs(catch_body, reads, pairs)
}
}
}
_ => ()
}
}
}
///|
fn dae_propagate_unread_simple_copies(
body : Array[@lib.Instruction],
reads : Array[Int32],
) -> Unit {
let pairs : Array[(Int32, Int32)] = []
dae_collect_unread_simple_copy_pairs(body, reads, pairs)
if pairs.is_empty() {
return
}
let incoming : Array[Array[Int32]] = Array::makei(reads.length(), fn(_) { [] })
for (source, target) in pairs {
incoming[target].push(source)
}
let queued = Array::make(reads.length(), false)
let queue : Array[Int32] = []
for local_idx = 0; local_idx < reads.length(); local_idx = local_idx + 1 {
if reads[local_idx] == 0 {
queued[local_idx] = true
queue.push(local_idx)
}
}
let mut cursor = 0
while cursor < queue.length() {
let target = queue[cursor]
cursor += 1
for source in incoming[target] {
reads[source] -= 1
if reads[source] == 0 && !queued[source] {
queued[source] = true
queue.push(source)
}
}
}
}
///|
fn dae_cleanup_unread_locals(
func : @lib.Func,
params : Array[@lib.ValType],
) -> (@lib.Func, Bool) {
let @lib.Func(locals, @lib.Expr(original)) = func
let count = params.length() + locals.length()
let mut body = original
let mut body_changed = false
// Removing an unused copy can make its source unread. Each changing round
// removes local references or instructions; effectful producers stay in place.
while true {
let reads = Array::make(count, 0)
let writes = Array::make(count, 0)
pass_lower_count_locals(body, reads, writes)
dae_propagate_unread_simple_copies(body, reads)
let (next, changed) = dae_cleanup_unread_local_body(body, reads)
if !changed {
break
}
body_changed = true
body = next
}
let reads = Array::make(count, 0)
let writes = Array::make(count, 0)
pass_lower_count_locals(body, reads, writes)
let indices = Array::makei(count, fn(i) { i })
let retained : Array[@lib.ValType] = []
for i = 0; i < locals.length(); i = i + 1 {
let index = params.length() + i
if reads[index] > 0 || writes[index] > 0 {
indices[index] = params.length() + retained.length()
retained.push(locals.get(i))
} else {
indices[index] = -1
}
}
if !body_changed && retained.length() == locals.length() {
return (func, false)
}
(
@lib.Func::new(
@lib.Locals::from_types(retained),
@lib.Expr::new(pass_lower_remap_locals(body, indices)),
),
true,
)
}
///|
fn dae_cleanup_touched_unread_locals(
mod_ : @lib.Module,
touched : Array[Bool],
) -> @lib.Module {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
let imported = dae_imported_func_count(mod_)
let mut flattened_subtypes : Array[@lib.SubType]? = None
let mut next_funcs : Array[@lib.Func]? = None
for i = 0; i < funcs.length(); i = i + 1 {
if i < touched.length() && touched[i] {
let subtypes = match flattened_subtypes {
Some(subtypes) => subtypes
None => {
let subtypes = run_hot_pipeline_raw_flatten_type_sec(mod_.type_sec)
flattened_subtypes = Some(subtypes)
subtypes
}
}
let params = run_hot_pipeline_defined_func_params_indexed(
mod_,
@lib.FuncIdx::new((imported + i).reinterpret_as_uint()),
Some(subtypes),
)
if params is Some(params) {
let (next, func_changed) = dae_cleanup_unread_locals(funcs[i], params)
if func_changed {
match next_funcs {
Some(copy) => copy[i] = next
None => {
let copy = funcs.copy()
copy[i] = next
next_funcs = Some(copy)
}
}
}
}
}
}
match next_funcs {
Some(funcs) =>
mod_.with_code_sec(@lib.CodeSec::new(funcs)).without_name_sec()
None => mod_
}
}
///|
// Scope checks stay flat. Each stored instruction is inspected a bounded
// number of times, even when many legacy regions are nested.
fn dae_legacy_flat_scope_can_inline(body : Array[@lib.Instruction]) -> Bool {
for instr in body {
match instr {
@lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.Try(_, _, _, _)
| @lib.TryTable(_, _, _) => return false
_ => ()
}
}
run_hot_pipeline_raw_vacuum_body_can_flatten_no_throw_exception_region(body)
}
///|
fn dae_legacy_cleanup_expr(
expr : @lib.Expr,
mod_ : @lib.Module,
tag_entries : Array[Int32],
) -> @lib.Expr {
let @lib.Expr(body) = expr
let (next, changed) = dae_flatten_proved_legacy_control(
body, mod_, tag_entries,
)
if changed {
@lib.Expr::new(next)
} else {
expr
}
}
///|
// Keep borrowed instruction and catch arrays until a child actually changes.
fn dae_legacy_cleanup_children(
instr : @lib.Instruction,
mod_ : @lib.Module,
tag_entries : Array[Int32],
) -> @lib.Instruction {
match instr {
@lib.Block(block_type, expr) => {
let next = dae_legacy_cleanup_expr(expr, mod_, tag_entries)
if physical_equal(next, expr) {
instr
} else {
@lib.Instruction::block(block_type, next)
}
}
@lib.Loop(block_type, expr) => {
let next = dae_legacy_cleanup_expr(expr, mod_, tag_entries)
if physical_equal(next, expr) {
instr
} else {
@lib.Instruction::loop_(block_type, next)
}
}
@lib.TryTable(block_type, catches, expr) => {
let next = dae_legacy_cleanup_expr(expr, mod_, tag_entries)
if physical_equal(next, expr) {
instr
} else {
@lib.Instruction::try_table(block_type, catches, next)
}
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = dae_flatten_proved_legacy_control(
then_body, mod_, tag_entries,
)
let mut else_changed = false
let next_else = match else_body {
Some(body) => {
let (next, changed) = dae_flatten_proved_legacy_control(
body, mod_, tag_entries,
)
else_changed = changed
Some(next)
}
None => None
}
if !then_changed && !else_changed {
instr
} else {
@lib.Instruction::if_(block_type, next_then, next_else)
}
}
@lib.Try(block_type, expr, catches, delegate) => {
let next_body = dae_legacy_cleanup_expr(expr, mod_, tag_entries)
let mut next_catches : Array[@lib.LegacyCatch]? = None
for idx = 0; idx < catches.length(); idx = idx + 1 {
let catch_ = catches[idx]
let expr = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
let next = dae_legacy_cleanup_expr(expr, mod_, tag_entries)
if physical_equal(next, expr) {
continue
}
let next = match catch_ {
@lib.LegacyCatch(tag, _) => @lib.LegacyCatch::tagged(tag, next)
@lib.LegacyCatchAll(_) => @lib.LegacyCatch::all(next)
}
match next_catches {
Some(copy) => copy[idx] = next
None => {
let copy = catches.copy()
copy[idx] = next
next_catches = Some(copy)
}
}
}
if physical_equal(next_body, expr) && next_catches is None {
instr
} else {
@lib.Instruction::try_(
block_type,
next_body,
next_catches.unwrap_or(catches),
delegate~,
)
}
}
_ => instr
}
}
///|
// Remove only flat legacy scopes with a proved selected path and no branch.
// A no-throw body cannot enter a catch. An exact zero-payload throw enters the
// first matching catch. Delegation and effectful exception paths keep scopes.
fn dae_flatten_proved_legacy_control(
instrs : Array[@lib.Instruction],
mod_ : @lib.Module,
tag_entries : Array[Int32],
) -> (Array[@lib.Instruction], Bool) {
let mut out : Array[@lib.Instruction]? = None
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let original = instrs[idx]
let mut replacement : Array[@lib.Instruction]? = None
match original {
@lib.Try(block_type, @lib.Expr(body), catches, None) =>
if dae_legacy_flat_scope_can_inline(body) {
replacement = Some(body)
} else {
match body {
[@lib.Throw(tag)] if dae_control_entry_stack_values(
block_type, mod_,
) ==
Some(0) &&
dae_legacy_tag_payload_count(mod_, tag, tag_entries) == Some(0) =>
for catch_ in catches {
let selected = match catch_ {
@lib.LegacyCatch(caught_tag, @lib.Expr(catch_body)) if caught_tag ==
tag => Some(catch_body)
@lib.LegacyCatchAll(@lib.Expr(catch_body)) => Some(catch_body)
_ => None
}
match selected {
Some(body) => {
if dae_legacy_flat_scope_can_inline(body) {
replacement = Some(body)
}
// Later handlers cannot replace the first matching handler.
break
}
None => ()
}
}
_ => ()
}
}
_ => ()
}
match replacement {
Some(body) => {
let copy = match out {
Some(copy) => copy
None => {
let copy = Array::new(capacity=instrs.length())
for prefix = 0; prefix < idx; prefix = prefix + 1 {
copy.push(instrs[prefix])
}
out = Some(copy)
copy
}
}
copy.append(body)
}
None => {
let next = dae_legacy_cleanup_children(original, mod_, tag_entries)
match out {
Some(copy) => copy.push(next)
None if !physical_equal(next, original) => {
let copy = Array::new(capacity=instrs.length())
for prefix = 0; prefix < idx; prefix = prefix + 1 {
copy.push(instrs[prefix])
}
copy.push(next)
out = Some(copy)
}
None => ()
}
}
}
}
match out {
Some(next) => (next, true)
None => (instrs, false)
}
}
///|
fn dae_cleanup_touched_legacy_constants(
mod_ : @lib.Module,
touched : Array[Bool],
fold_constants~ : Bool = true,
) -> @lib.Module {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
let imports = dae_imported_func_count(mod_)
let mut next_funcs : Array[@lib.Func]? = None
for def_idx = 0; def_idx < funcs.length(); def_idx = def_idx + 1 {
if def_idx >= touched.length() || !touched[def_idx] {
continue
}
let @lib.Func(locals, @lib.Expr(body)) = funcs[def_idx]
if !pass_raw_instrs_have_legacy_try(body) {
continue
}
let (flattened, changed) = dae_flatten_proved_legacy_control(body, mod_, [])
if !changed {
continue
}
let flattened = @lib.Func::new(locals, @lib.Expr::new(flattened))
let folded = if fold_constants {
let params = match
run_hot_pipeline_defined_func_params_from_sections(
mod_,
@lib.FuncIdx::new((imports + def_idx).reinterpret_as_uint()),
) {
Some(params) => params
None => continue
}
let propagated = match
precompute_propagate_run_raw_straight_line_func(flattened, params, None) {
Some((next, _)) => next
None => flattened
}
match precompute_run_raw_func(propagated, None) {
Some((next, _, _)) => next
None => propagated
}
} else {
flattened
}
match next_funcs {
Some(copy) => copy[def_idx] = folded
None => {
let copy = funcs.copy()
copy[def_idx] = folded
next_funcs = Some(copy)
}
}
}
match next_funcs {
Some(copy) => mod_.with_code_sec(@lib.CodeSec::new(copy)).without_name_sec()
None => mod_
}
}
///|
fn dae_run_nested_cleanup(
mod_ : @lib.Module,
touched : Array[Bool],
trace : (String) -> Unit,
optimize : Bool,
_traps_never_happen : Bool,
) -> @lib.Module {
if !optimize || dae_count_touched(touched) == 0 {
return mod_
}
trace(
"pass[dae-optimizing]:nested-cleanup touched=\{dae_count_touched(touched)} prefix=precompute-propagate",
)
let (folded, folded_changed) = dae_fold_touched_nonnull_ref_is_null(
mod_, touched,
)
if folded_changed {
trace("pass[dae-optimizing]:nested-cleanup nonnull-ref-is-null-fold")
}
dae_cleanup_touched_unread_locals(
dae_cleanup_touched_legacy_constants(folded, touched),
touched,
)
}
///| Validate the complete rewritten module environment, then only function
///| bodies whose signature, locals/body, or DAE touched ownership changed.
///| Unchanged definitions were valid in the input and see the same resolved
///|
/// function type; type-pruning rewrites are included by structural comparison.
fn dae_validate_typed_batch_candidate(
original : @lib.Module,
candidate : @lib.Module,
touched : Array[Bool],
) -> Bool {
let original_funcs = match original.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => []
}
let candidate_funcs = match candidate.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => []
}
if original_funcs.length() != candidate_funcs.length() ||
touched.length() != candidate_funcs.length() ||
dae_imported_func_count(original) != dae_imported_func_count(candidate) {
return false
}
let imported = dae_imported_func_count(candidate)
let original_type_indices = dae_collect_func_type_indices(original)
let candidate_type_indices = dae_collect_func_type_indices(candidate)
let original_function_types = dae_collect_defined_function_types(
original,
imported,
original_funcs.length(),
original_type_indices,
)
let candidate_function_types = dae_collect_defined_function_types(
candidate,
imported,
candidate_funcs.length(),
candidate_type_indices,
)
let changed_funcs : Array[(@lib.FuncIdx, @lib.Func)] = []
let mut def_idx = 0
while def_idx < candidate_funcs.length() {
let func_abs = imported + def_idx
let signature_changed = original_function_types[def_idx] !=
candidate_function_types[def_idx]
if touched[def_idx] ||
original_funcs[def_idx] != candidate_funcs[def_idx] ||
signature_changed {
changed_funcs.push(
(
@lib.FuncIdx::new(func_abs.reinterpret_as_uint()),
candidate_funcs[def_idx],
),
)
}
def_idx += 1
}
match
@validate.validate_defined_funcs_against_module(candidate, changed_funcs) {
Ok(errors) => errors.length() == 0
Err(_) => false
}
}
///|
fn dae_run_dropped_result_batch_only_result(
mod_ : @lib.Module,
trace : (String) -> Unit,
perf~ : HotPerfSession? = None,
safe_parameter_batch~ : Bool = false,
cleanup_dropped_pure_leaves~ : Bool = false,
) -> (@lib.Module, Array[Bool], Bool, Array[Bool]) {
let total_start_us = perf_start_timer_opt(
perf, "detail:dae:dropped-result-batch-only:total",
)
let mut current = mod_
let mut touched : Array[Bool] = []
let mut parameter_changed = false
let graph_start_us = perf_start_timer_opt(
perf, "detail:dae:dropped-result-transaction:graph",
)
let mut graph = dae_typed_batch_boundary_graph_new(current, perf~)
let original_local_set_get_candidates = Array::make(
graph.snapshot.defined,
false,
)
let mut candidate_def = 0
while candidate_def < graph.snapshot.defined {
let candidate_abs = graph.snapshot.imported + candidate_def
if candidate_abs >= 0 &&
candidate_abs < graph.snapshot.function_infos.length() &&
graph.snapshot.function_infos[candidate_abs].has_adjacent_local_set_get {
original_local_set_get_candidates[candidate_def] = true
}
candidate_def += 1
}
let mut cleanup_touched = Array::make(graph.snapshot.defined, false)
perf_stop_timer_opt(
perf, "detail:dae:dropped-result-transaction:graph", graph_start_us,
)
let exposure_start_us = perf_start_timer_opt(
perf, "detail:dae:dropped-result-batch-only:exposure",
)
let original_exposure = graph.snapshot.exposure
perf_stop_timer_opt(
perf, "detail:dae:dropped-result-batch-only:exposure", exposure_start_us,
)
if safe_parameter_batch {
let parameter_start_us = perf_start_timer_opt(
perf, "detail:dae:typed-loop-safe-batch:parameters",
)
let admission_start_us = perf_start_timer_opt(
perf, "detail:dae:typed-loop-safe-batch:admission",
)
let active_defs = graph.snapshot.parameter_loop_free
perf_stop_timer_opt(
perf, "detail:dae:typed-loop-safe-batch:admission", admission_start_us,
)
let parameter_candidate = dae_try_apply_unread_param_batch(
current,
active_defs=Some(active_defs),
require_active_callers=true,
allow_uniform_params=true,
boundary_snapshot=Some(graph.snapshot),
cleanup_targets=Some(cleanup_touched),
perf~,
)
if parameter_candidate.changed {
current = parameter_candidate.mod_out
touched = parameter_candidate.touched
parameter_changed = true
if !dae_current_boundary_graph_adopt_parameter_batch_lightweight(
graph, current,
) {
graph = dae_typed_batch_boundary_graph_new(current, perf~)
}
trace(
"pass[dae-optimizing]:typed-loop-safe-parameter-batch first_def=\{parameter_candidate.primary_def}",
)
}
perf_stop_timer_opt(
perf, "detail:dae:typed-loop-safe-batch:parameters", parameter_start_us,
)
}
let mut wave = 0
let mut active_result_defs : Array[Bool]? = None
while true {
let transaction_start_us = perf_start_timer_opt(
perf, "detail:dae:dropped-result-batch-only:transaction",
)
// This helper owns the whole fallback as one atomic transaction. Intermediate
// waves update only the private candidate graph; cleanup, pruning, and one
// final module validation decide whether any wave is published.
let application = dae_apply_dropped_result_batch_transaction_on_graph(
original_exposure,
current,
touched,
wave > 0,
graph,
retain_post_graph=true,
perf~,
validate_candidate=false,
lightweight_post_graph=true,
active_defs=active_result_defs,
)
perf_stop_timer_opt(
perf, "detail:dae:dropped-result-batch-only:transaction", transaction_start_us,
)
if !application.applied {
break
}
trace(
"pass[dae-optimizing]:dropped-result-batch-only wave=\{wave} first_def=\{application.primary_def}",
)
current = application.mod_out
touched = application.touched
active_result_defs = Some(
dae_dropped_result_followup_defs(graph, application.effects),
)
cleanup_touched = dae_merge_touched(
cleanup_touched,
application.effects.worth_optimizing,
)
wave += 1
match application.post_graph {
Some(next_graph) => graph = next_graph
None => {
let rebuild_start_us = perf_start_timer_opt(
perf, "detail:dae:dropped-result-transaction:graph",
)
graph = dae_typed_batch_boundary_graph_new(current, perf~)
perf_stop_timer_opt(
perf, "detail:dae:dropped-result-transaction:graph", rebuild_start_us,
)
}
}
}
if wave == 0 && !parameter_changed {
perf_stop_timer_opt(
perf, "detail:dae:dropped-result-batch-only:total", total_start_us,
)
return (mod_, [], false, [])
}
let cleanup_start_us = perf_start_timer_opt(
perf, "detail:dae:dropped-result-batch-only:cleanup",
)
let (debris_cleaned, _) = dae_cleanup_dropped_unreachable_debris_touched(
current, touched,
)
let raw_cleaned = if cleanup_dropped_pure_leaves {
let cleanup_owned_count = dae_count_touched(cleanup_touched)
let original_local_set_get_candidate_count = dae_count_touched(
original_local_set_get_candidates,
)
let raw_cleanup_touched = dae_merge_touched(
cleanup_touched.copy(),
original_local_set_get_candidates,
)
let (next, removed, stackified) = dae_cleanup_touched_dropped_i32_const_pairs(
debris_cleaned, raw_cleanup_touched,
)
trace(
"pass[dae]:typed-loop-safe-raw-cleanup cleanup-owned=\{cleanup_owned_count} original-local-set-get-candidates=\{original_local_set_get_candidate_count} touched=\{dae_count_touched(raw_cleanup_touched)} min-body-locals=8 dropped-i32-const-pairs=\{removed} local-set-get-pairs=\{stackified}",
)
next
} else {
debris_cleaned
}
perf_stop_timer_opt(
perf, "detail:dae:dropped-result-batch-only:cleanup", cleanup_start_us,
)
let prune_start_us = perf_start_timer_opt(
perf, "detail:dae:dropped-result-batch-only:prune",
)
let pruned = dae_prune_unused_simple_func_types(raw_cleaned)
perf_stop_timer_opt(
perf, "detail:dae:dropped-result-batch-only:prune", prune_start_us,
)
let validation_start_us = perf_start_timer_opt(
perf, "detail:dae:dropped-result-transaction:validate",
)
let validation = dae_validate_typed_batch_candidate(mod_, pruned, touched)
perf_stop_timer_opt(
perf, "detail:dae:dropped-result-transaction:validate", validation_start_us,
)
let result = if validation {
(pruned, touched, true, cleanup_touched)
} else {
(mod_, [], false, [])
}
perf_stop_timer_opt(
perf, "detail:dae:dropped-result-batch-only:total", total_start_us,
)
result
}
///|
fn dae_run_dropped_result_batch_only(
mod_ : @lib.Module,
trace : (String) -> Unit,
perf~ : HotPerfSession? = None,
safe_parameter_batch~ : Bool = false,
) -> @lib.Module {
let (mod_out, _, _, _) = dae_run_dropped_result_batch_only_result(
mod_,
trace,
perf~,
safe_parameter_batch~,
)
mod_out
}
///|
fn dae_run_typed_loop_safe_batch_result(
mod_ : @lib.Module,
trace : (String) -> Unit,
perf~ : HotPerfSession? = None,
cleanup_dropped_pure_leaves~ : Bool = false,
) -> (@lib.Module, Array[Bool], Bool, Array[Bool]) {
dae_run_dropped_result_batch_only_result(
mod_,
trace,
perf~,
safe_parameter_batch=true,
cleanup_dropped_pure_leaves~,
)
}
///|
fn dae_run_typed_loop_safe_batch(
mod_ : @lib.Module,
trace : (String) -> Unit,
perf~ : HotPerfSession? = None,
) -> @lib.Module {
let (mod_out, _, _, _) = dae_run_typed_loop_safe_batch_result(
mod_,
trace,
perf~,
)
mod_out
}
///|
fn dae_run_module_pass_with_trap_policy(
mod_ : @lib.Module,
optimize~ : Bool = false,
trace~ : ((String) -> Unit)? = None,
traps_never_happen~ : Bool = false,
perf~ : HotPerfSession? = None,
skip_param_removal~ : Bool = false,
no_callback_trace~ : DaeNoCallbackTrace? = None,
) -> @lib.Module {
if pass_module_has_offset_sensitive_metadata(mod_) {
return mod_
}
let trace_fn = match trace {
Some(cb) => cb
None => fn(_line : String) { () }
}
let no_callback_trace = if trace is None && perf is None {
Some({ callback: trace_fn, })
} else {
no_callback_trace
}
let result = dae_run_core(
mod_,
trace_fn,
optimize~,
perf~,
single_caller_throttle_consumed=skip_param_removal,
traps_never_happen~,
results_only=skip_param_removal,
no_callback_trace~,
)
dae_trace_diagnostics(
trace_fn,
if optimize {
"dae-optimizing"
} else {
"dead-argument-elimination"
},
result.diagnostics,
)
let (debris_cleaned, debris_changed) = dae_cleanup_dropped_unreachable_debris(
result.mod_out,
)
if debris_changed {
trace_fn("pass[dae]:cleanup-dropped-unreachable-debris")
}
let cleaned = dae_run_nested_cleanup(
debris_cleaned,
result.touched,
trace_fn,
optimize,
traps_never_happen,
)
dae_prune_unused_simple_func_types(cleaned)
}
///|
pub fn dae_run_module_pass(
mod_ : @lib.Module,
optimize~ : Bool = false,
trace~ : ((String) -> Unit)? = None,
) -> @lib.Module {
dae_run_module_pass_with_trap_policy(mod_, optimize~, trace~)
}
///|
fn dae_append_selected_if_body(
out : Array[@lib.Instruction],
block_type : @lib.BlockType,
body : Array[@lib.Instruction],
) -> Unit {
if body.any(dae_instr_has_branch) {
// The selected arm still owns the original if label and its result arity.
out.push(@lib.Instruction::block(block_type, @lib.Expr::new(body)))
} else {
out.append(body)
}
}
///|
fn dae_callsite_child_path_if_needed(
path : Array[Int32],
index : Int32,
arm : Int32,
needed : Bool,
) -> Array[Int32] {
if needed {
dae_callsite_child_path(path, index, arm)
} else {
path
}
}
///|
#inline
fn dae_scan_current_call_facts_child(
instrs : Array[@lib.Instruction],
caller_def : Int32,
caller_abs : Int32,
path : Array[Int32],
instr_idx : Int32,
child_kind : Int32,
direct_calls : Array[Int32],
tail_calls : Array[Bool],
direct_callers : Array[Array[Int32]],
dropped_calls : Array[Int32],
escaped : Array[Bool],
function_infos : Array[DaeFunctionInfo],
caller_seen_stamps : Array[Int32],
active : Bool,
topology_out~ : DaeCurrentTopologyExtras? = None,
body_exposure_out~ : Array[DaeFunctionExposure]? = None,
stable_path~ : DaeStablePath? = None,
) -> Bool {
let parent_length = path.length()
let collect_paths = !function_infos.is_empty()
if collect_paths {
path.push(instr_idx)
path.push(child_kind)
}
let child_stable_path = stable_path.map(fn(prefix) {
prefix.append(instr_idx).append(child_kind)
})
// Only recorded callsites copy a path. Recursive traversal borrows scratch;
// shrinking restores the caller's prefix while retaining reusable capacity.
let singleton_trap = dae_scan_current_call_facts_instrs(
instrs,
caller_def,
caller_abs,
path,
direct_calls,
tail_calls,
direct_callers,
dropped_calls,
escaped,
function_infos,
caller_seen_stamps,
active,
topology_out~,
body_exposure_out~,
stable_path=child_stable_path,
)
if collect_paths {
path.resize(parent_length, 0)
}
singleton_trap
}
///|
test "DAE retains entry parameter carried out by a branch before a later write" {
let input = pass_test_module_from_wat(
"(module (global $g (mut i32) (i32.const 1)) (func (export \"run\") (result i32) global.get $g call $callee) (func $callee (param i32) (result i32) block (result i32) local.get 0 br 0 end i32.const 5 local.set 0))",
)
let saved = @binary.encode_module(input).unwrap()
for optimize in [false, true] {
let output = dae_run_module_pass(input, optimize~)
@validate.validate_module(output).unwrap()
assert_eq(
@validate.Env::new()
.with_module(output)
.get_functype_by_funcidx(@lib.FuncIdx::new(1U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "DAE lightweight result cascade retains full refresh facts and output without discarded call records" {
fn dae_result_refresh_candidate_mask(
exposure : Array[DaeFunctionExposure],
graph : DaeCurrentBoundaryGraph,
) -> Array[Bool] {
let candidates : Array[Bool] = []
for def_idx in 0.. 0,
reference_graph,
retain_post_graph=true,
)
let actual = dae_apply_dropped_result_batch_transaction_on_graph(
original.exposure,
current,
touched,
wave > 0,
graph,
)
assert_true(actual.applied)
assert_eq(actual.primary_def, 2 - wave)
assert_eq(actual.primary_def, reference.primary_def)
assert_eq(actual.touched, reference.touched)
assert_eq(actual.effects.result_changed, reference.effects.result_changed)
assert_eq(actual.effects.result_removed, reference.effects.result_removed)
assert_eq(
actual.effects.structurally_touched,
reference.effects.structurally_touched,
)
let indices = dae_collect_func_type_indices(actual.mod_out)
assert_true(
dae_current_boundary_graph_adopt_dropped_result_batch_lightweight(
graph,
actual.mod_out,
actual.effects,
indices,
dae_collect_defined_function_types(
actual.mod_out,
graph.snapshot.imported,
graph.snapshot.defined,
indices,
),
scan_function_infos_out=Some(observed),
),
)
let fresh = dae_current_boundary_graph_new(actual.mod_out)
assert_true(
dae_current_call_facts_equal(
graph.snapshot.call_facts,
fresh.snapshot.call_facts,
),
)
assert_eq(graph.snapshot.type_indices, fresh.snapshot.type_indices)
assert_eq(graph.snapshot.function_types, fresh.snapshot.function_types)
assert_eq(
dae_result_refresh_candidate_mask(original.exposure, graph),
dae_result_refresh_candidate_mask(original.exposure, fresh),
)
assert_true(physical_equal(graph.snapshot.function_infos, original_infos))
assert_true(physical_equal(graph.mod_, actual.mod_out))
assert_eq(
@binary.encode_module(actual.mod_out).unwrap(),
@binary.encode_module(reference.mod_out).unwrap(),
)
@validate.validate_module(actual.mod_out).unwrap()
assert_eq(@binary.encode_module(current).unwrap(), before)
current = actual.mod_out
touched = actual.touched
}
assert_eq(touched, [true, true, true, true, false, false, false, false])
assert_eq(graph.snapshot.call_facts.direct_calls[0], 1)
assert_eq(graph.snapshot.call_facts.tail_calls[6], true)
assert_true(graph.snapshot.call_facts.escaped[5])
assert_eq(
original_infos[1].calls.map(fn(call) { call.path.to_array() }),
original_paths,
)
assert_eq(@binary.encode_module(input).unwrap(), saved)
let final_wave = dae_apply_dropped_result_batch_transaction_on_graph(
original.exposure,
current,
touched,
true,
graph,
)
assert_false(final_wave.applied)
assert_eq(observed.length(), 3)
// These are the actual scanner records, including their owned call paths.
// Count-only result refresh must not construct records that it discards.
for infos in observed {
assert_eq(infos.length(), 0)
}
}
///|
test "DAE raw and HOT liveness reuse syntax facts before replacing read masks" {
fn dae_liveness_handoff_fixture(hot : Bool, reads : Int32) -> @lib.Module {
let read = if hot {
"block (result i32) local.get 0 br 0 end"
} else {
"local.get 0"
}
let prefix = StringBuilder::new()
for _ in 0.. Unit raise {
assert_eq(actual.length(), expected.length())
for index in 0.. DaePassResult {
let target = graph.snapshot.funcs[0]
let @lib.Func(locals, _) = target
dae_try_rewrite_candidate(
input,
1,
[false, true],
[None, None],
dae_local_use_for_callee(target, 2 + locals.length(), 1, 2),
graph.snapshot.call_facts.direct_calls[1],
graph.snapshot.call_facts.direct_callers[1],
original=Some(dae_collect_original_boundary_snapshot(input)),
graph=Some(graph),
)
}
let direct =
#|(module
#| (import "host" "effect" (func $effect (result i32)))
#| (func $target (param i32 i32) (result i32)
#| call $effect drop local.get 0)
#| (func (export "run") (result i32)
#| i32.const 10 i32.const 20 call $target))
let references =
#|(module
#| (import "host" "effect" (func $effect (result i32)))
#| (table 1 funcref)
#| (global funcref (ref.func $module))
#| (elem declare func $escaped $inactive $module)
#| (elem (i32.const 0) $module)
#| (elem funcref (ref.func $module))
#| (func $target (param i32 i32) (result i32)
#| call $effect drop local.get 0)
#| (func $escaped (result i32) i32.const 2)
#| (func $inactive (result i32) i32.const 3)
#| (func $module (result i32) i32.const 4)
#| (func $tail (result i32) i32.const 5)
#| (func (export "run") (result i32)
#| ref.func $escaped drop
#| i32.const 10 i32.const 20 call $target)
#| (func (export "tail") (result i32) return_call $tail)
#| (func (export "inactive") unreachable ref.func $inactive drop)
#| (func (export "nested")
#| i32.const 1 if
#| block ref.func $escaped drop end
#| else
#| loop ref.func $module drop end
#| end)
#| (func (export "catch")
#| (try (do ref.func $escaped drop)
#| (catch_all ref.func $inactive drop))))
let extra_body_walks = [0]
for source in [direct, references] {
let input = pass_test_module_from_wat(source)
@validate.validate_module(input).unwrap()
let saved = @binary.encode_module(input).unwrap()
let total = dae_imported_func_count(input) + dae_defined_func_count(input)
let indices = dae_collect_func_type_indices(input)
let expected_exposure = dae_collect_original_exposure(input, total, indices)
let expected_facts = dae_collect_current_call_facts(input, total)
let actual = dae_collect_current_function_analysis(
input,
total,
exposure_body_walk_visits=Some(extra_body_walks),
)
dae_fused_exposure_assert_equal(actual.exposure, expected_exposure)
assert_true(dae_current_call_facts_equal(actual.call_facts, expected_facts))
assert_eq(actual.call_facts.direct_calls[0], 1)
assert_false(actual.exposure[1].ref_func_body)
assert_false(actual.exposure[1].ref_func_module)
if source == references {
assert_true(actual.exposure[2].ref_func_body)
assert_true(actual.exposure[3].ref_func_body)
assert_true(actual.exposure[4].ref_func_body)
assert_true(actual.exposure[4].ref_func_module)
assert_true(actual.exposure[4].active_element)
assert_true(actual.exposure[4].passive_element)
assert_true(actual.exposure[4].declarative_element)
assert_eq(actual.call_facts.tail_calls[5], true)
for target in [2, 3, 4] {
assert_true(actual.call_facts.escaped[target])
assert_true(actual.function_infos[target].has_unseen_calls)
}
}
let graph = dae_current_boundary_graph_new(input)
let previous = graph.snapshot
graph.snapshot = {
..previous,
call_facts: actual.call_facts,
boundary_facts: dae_call_boundary_facts_from_current(actual.call_facts),
function_infos: actual.function_infos,
exposure: actual.exposure,
parameter_loop_free: actual.parameter_loop_free,
}
let reference = dae_current_boundary_graph_new(input)
reference.snapshot = {
..reference.snapshot,
call_facts: expected_facts,
boundary_facts: dae_call_boundary_facts_from_current(expected_facts),
exposure: expected_exposure,
}
let original = dae_collect_original_boundary_snapshot(input)
for target in 0.. assert_eq(params.length(), 1)
None => fail("expected rewritten private helper signature")
}
assert_eq(
@binary.encode_module(candidate.mod_out).unwrap(),
@binary.encode_module(expected.mod_out).unwrap(),
)
let after = dae_collect_current_call_facts(candidate.mod_out, total)
assert_eq(after.direct_calls[0], 1)
assert_eq(after.escaped, expected_facts.escaped)
assert_eq(after.tail_calls, expected_facts.tail_calls)
@validate.validate_module(candidate.mod_out).unwrap()
assert_true(physical_equal(graph.mod_, input))
assert_eq(@binary.encode_module(input).unwrap(), saved)
dae_fused_exposure_assert_equal(previous.exposure, expected_exposure)
}
// This guards real removed work after behavior and candidate checks pass.
assert_eq(extra_body_walks[0], 0)
}
///|
test "DAE fused exposure collects a unique body reference through every structured path" {
let visits = [0]
for
body in [
"ref.func $target drop", "block ref.func $target drop end", "loop ref.func $target drop end",
"i32.const 1 if ref.func $target drop else nop end", "i32.const 1 if nop else ref.func $target drop end",
"(try (do ref.func $target drop) (catch_all))", "(try (do nop) (catch $tag ref.func $target drop))",
"(try (do nop) (catch_all ref.func $target drop))", "block try_table (catch_all 0) ref.func $target drop end end",
"unreachable ref.func $target drop",
] {
let input = pass_test_module_from_wat(
"(module (tag $tag) (elem declare func $target) (func $target) (func $unseen) (func (export \"run\") \{body}))",
)
@validate.validate_module(input).unwrap()
let saved = @binary.encode_module(input).unwrap()
let expected = dae_collect_original_exposure(
input,
3,
dae_collect_func_type_indices(input),
)
let actual = dae_collect_current_function_analysis(
input,
3,
exposure_body_walk_visits=Some(visits),
)
assert_true(actual.exposure[0].ref_func_body, msg=body)
assert_false(actual.exposure[1].ref_func_body, msg=body)
assert_true(actual.function_infos[0].has_unseen_calls, msg=body)
assert_true(
dae_current_call_facts_equal(
actual.call_facts,
dae_collect_current_call_facts(input, 3),
),
)
for index in 0..<3 {
let a = actual.exposure[index]
let e = expected[index]
assert_eq(
[
a.imported,
a.exported,
a.start,
a.ref_func_body,
a.ref_func_module,
a.active_element,
a.passive_element,
a.declarative_element,
],
[
e.imported,
e.exported,
e.start,
e.ref_func_body,
e.ref_func_module,
e.active_element,
e.passive_element,
e.declarative_element,
],
)
}
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
assert_eq(visits[0], 0)
}