// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Copyright 2023 WebAssembly Community Group participants.
// Retained historical upstream notice; current derivation remains unconfirmed.
// Conservative attribution from pinned Binaryen version_130 counterpart:
// Copyright 2021 WebAssembly Community Group participants
// Counterpart: src/passes/GlobalRefining.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 global_refining_summary() -> String {
"Refine defined global reference types from their initializer and observed writes, while preserving mutable export boundaries."
}
///|
fn gr_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 gr_imported_global_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.GlobalExternType(_)) => count += 1
_ => ()
}
}
None => ()
}
count
}
///|
fn gr_slot_for_abs_idx(slots : Array[Int32], abs_idx : UInt32) -> Int32 {
let slot = abs_idx.reinterpret_as_int()
if slot < 0 || slot >= slots.length() {
-1
} else {
slots[slot]
}
}
///|
fn gr_global_ref_type(global_type : @lib.GlobalType) -> @lib.RefType? {
match global_type {
@lib.GlobalType(@lib.RefTypeValType(ref_type), _) => Some(ref_type)
_ => None
}
}
///|
fn gr_null_bottom_type_for_comp(comp_type : @lib.CompType) -> @lib.AbsHeapType {
match comp_type {
@lib.FuncCompType(_, _) => @lib.AbsHeapType::no_func()
@lib.ContCompType(_) => @lib.AbsHeapType::no_cont()
@lib.StructCompType(_) | @lib.ArrayCompType(_) => @lib.AbsHeapType::none()
}
}
///|
fn gr_ref_func_result_type(
env : @validate.Env,
func_idx : @lib.FuncIdx,
) -> @lib.RefType? {
match env.get_functypeidx_by_funcidx(func_idx) {
Some(type_idx) =>
Some(@lib.RefType::new(false, @lib.HeapType::new(type_idx)))
None => None
}
}
///|
fn gr_ref_null_result_type(
ref_type : @lib.RefType,
env : @validate.Env,
) -> @lib.RefType {
match ref_type.get_heap_type() {
@lib.AbsHeapTypeHeapType(abs) => {
let bottom = match abs {
@lib.FuncAbsHeapType => @lib.AbsHeapType::no_func()
@lib.NoFuncAbsHeapType => @lib.AbsHeapType::no_func()
@lib.WaitqueueAbsHeapType | @lib.NoWaitqueueAbsHeapType =>
@lib.AbsHeapType::no_waitqueue()
@lib.ContAbsHeapType => @lib.AbsHeapType::no_cont()
@lib.NoContAbsHeapType => @lib.AbsHeapType::no_cont()
@lib.ExternAbsHeapType => @lib.AbsHeapType::no_extern()
@lib.NoExternAbsHeapType => @lib.AbsHeapType::no_extern()
@lib.ExnAbsHeapType => @lib.AbsHeapType::no_exn()
@lib.NoExnAbsHeapType => @lib.AbsHeapType::no_exn()
@lib.AnyAbsHeapType
| @lib.EqAbsHeapType
| @lib.I31AbsHeapType
| @lib.StructAbsHeapType
| @lib.ArrayAbsHeapType
| @lib.StringAbsHeapType
| @lib.NoneAbsHeapType => @lib.AbsHeapType::none()
}
@lib.RefType::new(true, @lib.HeapType::abs(bottom))
}
heap_type =>
match env.resolve_heaptype_subtype(heap_type) {
Some(subtype) =>
@lib.RefType::new(
true,
@lib.HeapType::abs(
gr_null_bottom_type_for_comp(subtype.get_comptype()),
),
)
None => ref_type.make_nullable()
}
}
}
///|
fn gr_expr_typechecked_result_type(
env : @validate.Env,
expr : @lib.Expr,
) -> @lib.ValType? {
match @validate.Typecheck::typecheck(expr, @validate.make_state(env, [])) {
Ok(state) =>
if state.reachable && state.stack.length() == 1 {
Some(state.stack[0])
} else {
None
}
Err(_) => None
}
}
///|
fn gr_expr_result_type(env : @validate.Env, expr : @lib.Expr) -> @lib.ValType? {
match expr {
@lib.Expr([@lib.RefNull(ref_type)]) =>
Some(@lib.ValType::ref_type(gr_ref_null_result_type(ref_type, env)))
@lib.Expr([@lib.RefFunc(func_idx)]) =>
match gr_ref_func_result_type(env, func_idx) {
Some(ref_type) => Some(@lib.ValType::ref_type(ref_type))
None => None
}
_ => gr_expr_typechecked_result_type(env, expr)
}
}
///|
priv struct GrPublicTypeScratch {
states : Array[Int32]
visited : Array[Int32]
}
///|
fn GrPublicTypeScratch::new(env : @validate.Env) -> GrPublicTypeScratch {
{ states: Array::make(env.global_types.length(), 0), visited: [], }
}
///|
fn gr_valtype_is_public(
val_type : @lib.ValType,
env : @validate.Env,
scratch : GrPublicTypeScratch,
) -> Bool {
match val_type {
@lib.RefTypeValType(ref_type) =>
gr_reftype_is_public(ref_type, env, scratch)
_ => true
}
}
///|
fn gr_storage_type_is_public(
storage_type : @lib.StorageType,
env : @validate.Env,
scratch : GrPublicTypeScratch,
) -> Bool {
match storage_type {
@lib.ValTypeStorageType(val_type) =>
gr_valtype_is_public(val_type, env, scratch)
@lib.PackTypeStorageType(_) => true
}
}
///|
fn gr_field_type_is_public(
field_type : @lib.FieldType,
env : @validate.Env,
scratch : GrPublicTypeScratch,
) -> Bool {
gr_storage_type_is_public(field_type.get_storage_type(), env, scratch)
}
///|
fn gr_comptype_is_public(
comp_type : @lib.CompType,
env : @validate.Env,
scratch : GrPublicTypeScratch,
) -> Bool {
match comp_type {
@lib.FuncCompType(params, results) => {
for param in params {
if !gr_valtype_is_public(param, env, scratch) {
return false
}
}
for result in results {
if !gr_valtype_is_public(result, env, scratch) {
return false
}
}
true
}
@lib.StructCompType(fields) => {
for field in fields {
if !gr_field_type_is_public(field, env, scratch) {
return false
}
}
true
}
@lib.ArrayCompType(field) => gr_field_type_is_public(field, env, scratch)
@lib.ContCompType(func_type) =>
gr_typeidx_is_public(func_type, env, scratch)
}
}
///|
fn gr_typeidx_is_public(
type_idx : @lib.TypeIdx,
env : @validate.Env,
scratch : GrPublicTypeScratch,
) -> Bool {
match type_idx {
@lib.TypeIdx(raw) => {
let idx = raw.reinterpret_as_int()
if idx < 0 || idx >= scratch.states.length() {
return true
}
match scratch.states[idx] {
2 => return true
3 => return false
1 => return true
_ => ()
}
scratch.states[idx] = 1
scratch.visited.push(idx)
let public = match env.resolve_subtype(type_idx) {
Some(subtype) =>
gr_comptype_is_public(subtype.get_comptype(), env, scratch)
None => true
}
scratch.states[idx] = if public { 2 } else { 3 }
public
}
@lib.RecIdx(_) =>
match env.resolve_subtype(type_idx) {
Some(subtype) =>
gr_comptype_is_public(subtype.get_comptype(), env, scratch)
None => true
}
}
}
///|
fn gr_heaptype_is_public(
heap_type : @lib.HeapType,
env : @validate.Env,
scratch : GrPublicTypeScratch,
) -> Bool {
match heap_type {
@lib.AbsHeapTypeHeapType(_) | @lib.SharedAbsHeapTypeHeapType(_) => true
@lib.HeapType(type_idx) => gr_typeidx_is_public(type_idx, env, scratch)
@lib.DefTypeHeapType(def_type) =>
match def_type.project() {
Some(subtype) =>
gr_comptype_is_public(subtype.get_comptype(), env, scratch)
None => true
}
}
}
///|
fn gr_reftype_is_public(
ref_type : @lib.RefType,
env : @validate.Env,
scratch : GrPublicTypeScratch,
) -> Bool {
if ref_type.is_exact() {
return false
}
gr_heaptype_is_public(ref_type.get_heap_type(), env, scratch)
}
///|
fn gr_custom_descriptors_enabled_locally(env : @validate.Env) -> Bool {
env.infer_exact_references
}
///|
fn gr_refined_export_type_is_public(
ref_type : @lib.RefType,
env : @validate.Env,
scratch : GrPublicTypeScratch,
) -> Bool {
if gr_custom_descriptors_enabled_locally(env) {
return true
}
let public = gr_reftype_is_public(ref_type, env, scratch)
// Reset after each root query: a cycle may have been provisionally public
// while another path in the same query established a private type.
for idx in scratch.visited {
scratch.states[idx] = 0
}
scratch.visited.clear()
public
}
///|
priv struct GrNominalAncestryStats {
mut declared_subtype_queries : Int32
mut visited_type_nodes : Int32
mut candidate_collections : Int32
mut declared_ancestor_collections : Int32
mut candidate_nodes_visited : Int32
temporary_arrays_allocated : Int32
}
///|
priv struct GrNominalAncestryScratch {
mut generation : Int32
marks : Array[Int32]
type_work : Array[@lib.TypeIdx]
non_absolute_visited : Array[@lib.TypeIdx]
heap_work : Array[@lib.HeapType]
candidates : Array[@lib.HeapType]
abstract_fallbacks : Array[@lib.HeapType]
failed_ref_slots : @hashmap.HashMap[Int32, Bool]
stats : GrNominalAncestryStats
}
///|
fn GrNominalAncestryScratch::new(
env : @validate.Env,
) -> GrNominalAncestryScratch {
{
generation: 0,
marks: Array::make(env.global_types.length(), 0),
type_work: [],
non_absolute_visited: [],
heap_work: [],
candidates: [],
abstract_fallbacks: [],
failed_ref_slots: @hashmap.new(),
stats: {
declared_subtype_queries: 0,
visited_type_nodes: 0,
candidate_collections: 0,
declared_ancestor_collections: 0,
candidate_nodes_visited: 0,
temporary_arrays_allocated: 6,
},
}
}
///|
fn GrNominalAncestryScratch::next_generation(
self : GrNominalAncestryScratch,
) -> Int32 {
if self.generation >= 0x7FFFFFFF {
for idx = 0; idx < self.marks.length(); idx = idx + 1 {
self.marks[idx] = 0
}
self.generation = 1
} else {
self.generation += 1
}
self.generation
}
///|
fn GrNominalAncestryScratch::observed_work(
self : GrNominalAncestryScratch,
) -> Int32 {
self.stats.declared_subtype_queries +
self.stats.visited_type_nodes +
self.stats.candidate_collections +
self.stats.declared_ancestor_collections +
self.stats.candidate_nodes_visited +
self.stats.temporary_arrays_allocated
}
///|
fn gr_absolute_type_slot(type_idx : @lib.TypeIdx, type_count : Int32) -> Int32 {
match type_idx {
@lib.TypeIdx(raw) => {
let slot = raw.reinterpret_as_int()
if slot >= 0 && slot < type_count {
slot
} else {
-1
}
}
@lib.RecIdx(_) => -1
}
}
///|
fn gr_append_unique_heaptype(
out : Array[@lib.HeapType],
heap_type : @lib.HeapType,
) -> Unit {
for existing in out {
if existing == heap_type {
return
}
}
out.push(heap_type)
}
///|
fn gr_append_abs_heap_supertypes(
out : Array[@lib.HeapType],
abs : @lib.AbsHeapType,
shared~ : Bool = false,
) -> Unit {
let make_heap = fn(abs) {
if shared {
@lib.HeapType::shared_abs(abs)
} else {
@lib.HeapType::abs(abs)
}
}
match abs {
@lib.I31AbsHeapType | @lib.StructAbsHeapType | @lib.ArrayAbsHeapType => {
gr_append_unique_heaptype(out, make_heap(@lib.AbsHeapType::eq()))
gr_append_unique_heaptype(out, make_heap(@lib.AbsHeapType::any()))
}
@lib.EqAbsHeapType =>
gr_append_unique_heaptype(out, make_heap(@lib.AbsHeapType::any()))
_ => ()
}
}
///|
fn gr_append_shape_heap_supertypes(
out : Array[@lib.HeapType],
comp_type : @lib.CompType,
shared~ : Bool = false,
) -> Unit {
let make_heap = fn(abs) {
if shared {
@lib.HeapType::shared_abs(abs)
} else {
@lib.HeapType::abs(abs)
}
}
match comp_type {
@lib.StructCompType(_) => {
gr_append_unique_heaptype(out, make_heap(@lib.AbsHeapType::struct_()))
gr_append_unique_heaptype(out, make_heap(@lib.AbsHeapType::eq()))
gr_append_unique_heaptype(out, make_heap(@lib.AbsHeapType::any()))
}
@lib.ArrayCompType(_) => {
gr_append_unique_heaptype(out, make_heap(@lib.AbsHeapType::array()))
gr_append_unique_heaptype(out, make_heap(@lib.AbsHeapType::eq()))
gr_append_unique_heaptype(out, make_heap(@lib.AbsHeapType::any()))
}
@lib.FuncCompType(_, _) =>
gr_append_unique_heaptype(out, make_heap(@lib.AbsHeapType::func()))
@lib.ContCompType(_) =>
gr_append_unique_heaptype(out, make_heap(@lib.AbsHeapType::cont()))
}
}
///|
fn gr_collect_heaptype_candidates(
heap_type : @lib.HeapType,
env : @validate.Env,
scratch : GrNominalAncestryScratch,
) -> Array[@lib.HeapType] {
scratch.stats.candidate_collections += 1
let generation = scratch.next_generation()
scratch.heap_work.clear()
scratch.candidates.clear()
scratch.abstract_fallbacks.clear()
scratch.non_absolute_visited.clear()
scratch.heap_work.push(heap_type)
while scratch.heap_work.length() > 0 {
let current = scratch.heap_work.pop().unwrap()
let duplicate = match current {
@lib.HeapType(type_idx) => {
let slot = gr_absolute_type_slot(type_idx, scratch.marks.length())
if slot >= 0 {
if scratch.marks[slot] == generation {
true
} else {
scratch.marks[slot] = generation
false
}
} else if scratch.non_absolute_visited.contains(type_idx) {
true
} else {
scratch.non_absolute_visited.push(type_idx)
false
}
}
@lib.AbsHeapTypeHeapType(_)
| @lib.SharedAbsHeapTypeHeapType(_)
| @lib.DefTypeHeapType(_) => scratch.candidates.contains(current)
}
if duplicate {
continue
}
scratch.stats.candidate_nodes_visited += 1
scratch.candidates.push(current)
match current {
@lib.AbsHeapTypeHeapType(abs) =>
gr_append_abs_heap_supertypes(scratch.abstract_fallbacks, abs)
@lib.SharedAbsHeapTypeHeapType(abs) =>
gr_append_abs_heap_supertypes(
scratch.abstract_fallbacks,
abs,
shared=true,
)
@lib.HeapType(type_idx) =>
match env.resolve_subtype(type_idx) {
Some(subtype) => {
for super_type in subtype.super_types().rev_iter() {
scratch.heap_work.push(@lib.HeapType::new(super_type))
}
gr_append_shape_heap_supertypes(
scratch.abstract_fallbacks,
subtype.get_comptype(),
shared=subtype.metadata().is_shared(),
)
}
None => ()
}
@lib.DefTypeHeapType(def_type) =>
match def_type.project() {
Some(subtype) => {
for super_type in subtype.super_types().rev_iter() {
scratch.heap_work.push(@lib.HeapType::new(super_type))
}
gr_append_shape_heap_supertypes(
scratch.abstract_fallbacks,
subtype.get_comptype(),
shared=subtype.metadata().is_shared(),
)
}
None => ()
}
}
}
for fallback in scratch.abstract_fallbacks {
gr_append_unique_heaptype(scratch.candidates, fallback)
}
scratch.candidates
}
///|
fn gr_typeidx_is_declared_subtype(
source : @lib.TypeIdx,
target : @lib.TypeIdx,
env : @validate.Env,
scratch : GrNominalAncestryScratch,
) -> Bool {
scratch.stats.declared_subtype_queries += 1
let generation = scratch.next_generation()
scratch.type_work.clear()
scratch.non_absolute_visited.clear()
scratch.type_work.push(source)
while scratch.type_work.length() > 0 {
let current = scratch.type_work.pop().unwrap()
let slot = gr_absolute_type_slot(current, scratch.marks.length())
if slot >= 0 {
if scratch.marks[slot] == generation {
continue
}
scratch.marks[slot] = generation
} else if scratch.non_absolute_visited.contains(current) {
continue
} else {
scratch.non_absolute_visited.push(current)
}
scratch.stats.visited_type_nodes += 1
if current == target {
return true
}
match env.resolve_subtype(current) {
Some(subtype) =>
for super_type in subtype.super_types() {
scratch.type_work.push(super_type)
}
None => ()
}
}
false
}
///|
fn gr_mark_declared_ancestors(
source : @lib.TypeIdx,
env : @validate.Env,
scratch : GrNominalAncestryScratch,
) -> Int32 {
scratch.stats.declared_ancestor_collections += 1
let generation = scratch.next_generation()
scratch.type_work.clear()
scratch.non_absolute_visited.clear()
scratch.type_work.push(source)
while scratch.type_work.length() > 0 {
let current = scratch.type_work.pop().unwrap()
let slot = gr_absolute_type_slot(current, scratch.marks.length())
if slot >= 0 {
if scratch.marks[slot] == generation {
continue
}
scratch.marks[slot] = generation
} else if scratch.non_absolute_visited.contains(current) {
continue
} else {
scratch.non_absolute_visited.push(current)
}
scratch.stats.visited_type_nodes += 1
match env.resolve_subtype(current) {
Some(subtype) =>
for super_type in subtype.super_types() {
scratch.type_work.push(super_type)
}
None => ()
}
}
generation
}
///|
fn gr_declared_ancestor_was_marked(
candidate : @lib.TypeIdx,
generation : Int32,
scratch : GrNominalAncestryScratch,
) -> Bool {
let slot = gr_absolute_type_slot(candidate, scratch.marks.length())
if slot >= 0 {
scratch.marks[slot] == generation
} else {
scratch.non_absolute_visited.contains(candidate)
}
}
///|
fn gr_heaptype_matches_join_candidate(
source : @lib.HeapType,
candidate : @lib.HeapType,
env : @validate.Env,
scratch : GrNominalAncestryScratch,
) -> Bool {
match (source, candidate) {
(@lib.HeapType(source), @lib.HeapType(candidate)) =>
gr_typeidx_is_declared_subtype(source, candidate, env, scratch)
_ => @validate.Match::matches(source, candidate, env)
}
}
///|
fn gr_join_heaptypes(
left : @lib.HeapType,
right : @lib.HeapType,
env : @validate.Env,
scratch : GrNominalAncestryScratch,
) -> @lib.HeapType? {
if gr_heaptype_matches_join_candidate(left, right, env, scratch) {
return Some(right)
}
if gr_heaptype_matches_join_candidate(right, left, env, scratch) {
return Some(left)
}
let candidates = gr_collect_heaptype_candidates(left, env, scratch)
match right {
@lib.HeapType(right_type_idx) => {
let generation = gr_mark_declared_ancestors(right_type_idx, env, scratch)
for candidate in candidates {
match candidate {
@lib.HeapType(candidate_type_idx) =>
if gr_declared_ancestor_was_marked(
candidate_type_idx, generation, scratch,
) {
return Some(candidate)
}
_ =>
if @validate.Match::matches(right, candidate, env) {
return Some(candidate)
}
}
}
}
_ =>
for candidate in candidates {
// Every collected candidate is already a supertype of `left`, so only
// the right side requires a compatibility query here.
if gr_heaptype_matches_join_candidate(right, candidate, env, scratch) {
return Some(candidate)
}
}
}
None
}
///|
fn gr_heaptype_supports_exact(heap_type : @lib.HeapType) -> Bool {
match heap_type {
@lib.HeapType(_) | @lib.DefTypeHeapType(_) => true
@lib.AbsHeapTypeHeapType(_) | @lib.SharedAbsHeapTypeHeapType(_) => false
}
}
///|
fn gr_ref_type_is_nullable_bottom_for_heap(
ref_type : @lib.RefType,
heap_type : @lib.HeapType,
env : @validate.Env,
) -> Bool {
if !ref_type.is_nullable() || ref_type.is_exact() {
return false
}
let bottom = match ref_type.get_heap_type() {
@lib.AbsHeapTypeHeapType(abs) => abs
_ => return false
}
match env.resolve_heaptype_subtype(heap_type) {
Some(subtype) =>
match subtype.get_comptype() {
@lib.FuncCompType(_, _) => bottom == @lib.AbsHeapType::no_func()
@lib.ContCompType(_) => bottom == @lib.AbsHeapType::no_cont()
@lib.StructCompType(_) | @lib.ArrayCompType(_) =>
bottom == @lib.AbsHeapType::none()
}
None => false
}
}
///|
fn gr_join_reftypes_preserves_exact(
left : @lib.RefType,
right : @lib.RefType,
heap_type : @lib.HeapType,
env : @validate.Env,
) -> Bool {
if !gr_heaptype_supports_exact(heap_type) {
return false
}
if left.is_exact() &&
right.is_exact() &&
left.get_heap_type() == heap_type &&
right.get_heap_type() == heap_type {
return true
}
if left.is_exact() &&
left.get_heap_type() == heap_type &&
gr_ref_type_is_nullable_bottom_for_heap(right, heap_type, env) {
return true
}
if right.is_exact() &&
right.get_heap_type() == heap_type &&
gr_ref_type_is_nullable_bottom_for_heap(left, heap_type, env) {
return true
}
false
}
///|
fn gr_join_reftypes(
left : @lib.RefType,
right : @lib.RefType,
env : @validate.Env,
scratch : GrNominalAncestryScratch,
) -> @lib.RefType? {
let heap_type = match
gr_join_heaptypes(left.get_heap_type(), right.get_heap_type(), env, scratch) {
Some(heap_type) => heap_type
None => return None
}
let exact = gr_join_reftypes_preserves_exact(left, right, heap_type, env)
let joined = @lib.RefType::new(
left.is_nullable() || right.is_nullable(),
heap_type,
exact~,
)
if @validate.Match::matches(left, joined, env) &&
@validate.Match::matches(right, joined, env) {
Some(joined)
} else {
None
}
}
///|
fn gr_note_ref_type(
noted : Array[@lib.RefType?],
slot : Int32,
next : @lib.RefType,
env : @validate.Env,
scratch : GrNominalAncestryScratch,
) -> Unit {
if slot < 0 ||
slot >= noted.length() ||
scratch.failed_ref_slots.contains(slot) {
return
}
match noted[slot] {
None => noted[slot] = Some(next)
Some(current) =>
match gr_join_reftypes(current, next, env, scratch) {
Some(joined) => noted[slot] = Some(joined)
None => {
noted[slot] = None
scratch.failed_ref_slots.set(slot, true)
}
}
}
}
///|
fn gr_scan_candidate_sets(
instrs : Array[@lib.Instruction],
candidate_slots : Array[Int32],
) -> Bool {
for instr in instrs {
match instr {
@lib.GlobalSet(@lib.GlobalIdx(global_idx)) =>
if gr_slot_for_abs_idx(candidate_slots, global_idx) >= 0 {
return true
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if gr_scan_candidate_sets(body, candidate_slots) {
return true
}
@lib.If(_, then_body, else_body) => {
if gr_scan_candidate_sets(then_body, candidate_slots) {
return true
}
match else_body {
Some(else_body) =>
if gr_scan_candidate_sets(else_body, candidate_slots) {
return true
}
None => ()
}
}
@lib.TryTable(_, _, @lib.Expr(body)) =>
if gr_scan_candidate_sets(body, candidate_slots) {
return true
}
_ => ()
}
}
false
}
///|
fn gr_collect_func_global_sets(
hot_func : @ir.HotFunc,
candidate_slots : Array[Int32],
noted : Array[@lib.RefType?],
env : @validate.Env,
scratch : GrNominalAncestryScratch,
) -> Unit {
let stack : Array[Int32] = []
for root_idx = @ir.hot_root_count(hot_func) - 1
root_idx >= 0
root_idx = root_idx - 1 {
stack.push(@ir.hot_root_get(hot_func, root_idx))
}
while stack.length() > 0 {
let node_id = stack.pop().unwrap()
let node = @ir.hot_node_get(hot_func, node_id)
match node.op {
@ir.HotOp::GlobalSet => {
let global_idx = node.imm0
if global_idx >= 0 && global_idx < candidate_slots.length() {
let slot = candidate_slots[global_idx]
if slot >= 0 && @ir.hot_child_count(hot_func, node_id) > 0 {
let value_id = @ir.hot_child_get(hot_func, node_id, 0)
if value_id >= 0 {
let value_node = @ir.hot_node_get(hot_func, value_id)
match value_node.op {
@ir.HotOp::RefFunc =>
if value_node.imm0 >= 0 {
match
gr_ref_func_result_type(
env,
@lib.FuncIdx::new(value_node.imm0.reinterpret_as_uint()),
) {
Some(ref_type) =>
gr_note_ref_type(noted, slot, ref_type, env, scratch)
None => ()
}
}
@ir.HotOp::RefNull =>
if value_node.imm0 >= 0 {
match @ir.hot_const_get(hot_func, value_node.imm0) {
@ir.HotConstPayload::RefNullConst(ref_type) => {
let noted_ref_type = match ref_type.get_heap_type() {
@lib.AbsHeapTypeHeapType(_) =>
gr_ref_null_result_type(ref_type, env)
_ => ref_type
}
gr_note_ref_type(
noted, slot, noted_ref_type, env, scratch,
)
}
_ => ()
}
}
_ =>
match
@ir.hot_type_results(
hot_func,
@ir.hot_node_type(hot_func, value_id),
) {
[@lib.RefTypeValType(ref_type)] =>
gr_note_ref_type(noted, slot, ref_type, env, scratch)
_ => ()
}
}
}
}
}
}
_ => ()
}
for child_idx = 0
child_idx < @ir.hot_child_count(hot_func, node_id)
child_idx = child_idx + 1 {
let child_id = @ir.hot_child_get(hot_func, node_id, child_idx)
if child_id >= 0 && @ir.hot_node_is_live(hot_func, child_id) {
stack.push(child_id)
}
}
}
}
///|
fn gr_rewrite_ref_null_set_instrs(
instrs : Array[@lib.Instruction],
refined_globals : Array[@lib.RefType?],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> (Array[@lib.Instruction], Bool) {
let mut next = instrs
let mut changed = false
let null_producers = gr_null_write_producers(instrs, mod_, func_type_indices)
for idx = 0; idx < next.length(); idx = idx + 1 {
match next[idx] {
@lib.GlobalSet(@lib.GlobalIdx(global_idx)) => {
let slot = global_idx.reinterpret_as_int()
if idx > 0 && slot >= 0 && slot < refined_globals.length() {
match refined_globals[slot] {
Some(refined_ref_type) =>
if null_producers[idx] >= 0 {
let producer = null_producers[idx]
if !changed {
next = instrs.copy()
}
next[producer] = @lib.Instruction::ref_null_type(
refined_ref_type,
)
changed = true
}
None => ()
}
}
}
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = gr_rewrite_ref_null_set_instrs(
body, refined_globals, mod_, func_type_indices,
)
if body_changed {
if !changed {
next = instrs.copy()
}
next[idx] = @lib.Instruction::block(
block_type,
@lib.Expr::new(next_body),
)
changed = true
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = gr_rewrite_ref_null_set_instrs(
body, refined_globals, mod_, func_type_indices,
)
if body_changed {
if !changed {
next = instrs.copy()
}
next[idx] = @lib.Instruction::loop_(
block_type,
@lib.Expr::new(next_body),
)
changed = true
}
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = gr_rewrite_ref_null_set_instrs(
then_body, refined_globals, mod_, func_type_indices,
)
let (next_else, else_changed) = match else_body {
Some(else_body) => {
let (rewritten_else, changed) = gr_rewrite_ref_null_set_instrs(
else_body, refined_globals, mod_, func_type_indices,
)
(Some(rewritten_else), changed)
}
None => (None, false)
}
if then_changed || else_changed {
if !changed {
next = instrs.copy()
}
next[idx] = @lib.Instruction::if_(block_type, next_then, next_else)
changed = true
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = gr_rewrite_ref_null_set_instrs(
body, refined_globals, mod_, func_type_indices,
)
if body_changed {
if !changed {
next = instrs.copy()
}
next[idx] = @lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
changed = true
}
}
_ => ()
}
}
(next, changed)
}
///|
fn gr_null_write_producers(
instrs : Array[@lib.Instruction],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Array[Int32] {
let producers = Array::make(instrs.length(), -1)
// Track the known suffix of the operand stack. An unsupported or
// polymorphic instruction clears that suffix, but later independent leaves
// can immediately begin a new one.
let stack : Array[Int32] = []
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if instrs[idx] is @lib.GlobalSet(_) && !stack.is_empty() {
producers[idx] = stack[stack.length() - 1]
}
let (pops, pushes) = match
dae_instr_stack_effect(instrs[idx], mod_, func_type_indices) {
Some(effect) => effect
None => {
stack.clear()
continue
}
}
let known_inputs = pops <= stack.length()
if known_inputs {
for pop_idx = 0; pop_idx < pops; pop_idx = pop_idx + 1 {
ignore(pop_idx)
ignore(stack.pop())
}
} else {
stack.clear()
}
let producer = if known_inputs &&
pops == 0 &&
pushes == 1 &&
instrs[idx] is @lib.RefNull(_) {
idx
} else {
-1
}
for push_idx = 0; push_idx < pushes; push_idx = push_idx + 1 {
ignore(push_idx)
stack.push(producer)
}
}
producers
}
///|
fn gr_rewrite_ref_null_sets_in_code(
mod_ : @lib.Module,
refined_globals : Array[@lib.RefType?],
) -> (@lib.Module, Bool) {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return (mod_, false)
}
let func_type_indices = dae_collect_func_type_indices(mod_)
let next_funcs = funcs.copy()
let mut changed = false
for idx = 0; idx < funcs.length(); idx = idx + 1 {
let @lib.Func(locals, @lib.Expr(body)) = funcs[idx]
let (next_body, body_changed) = gr_rewrite_ref_null_set_instrs(
body, refined_globals, mod_, func_type_indices,
)
if body_changed {
next_funcs[idx] = @lib.Func::new(locals, @lib.Expr::new(next_body))
changed = true
}
}
if changed {
(mod_.with_code_sec(@lib.CodeSec::new(next_funcs)), true)
} else {
(mod_, false)
}
}
///|
fn gr_refine_module(
mod_ : @lib.Module,
closed_world~ : Bool = false,
) -> @lib.Module {
if pass_module_has_legacy_try(mod_) {
return mod_
}
let env = @validate.Env::new().with_module(mod_)
let imported_global_count = gr_imported_global_count(mod_)
let total_globals = env.globals.length()
if imported_global_count >= total_globals {
return mod_
}
let globals = match mod_.global_sec {
Some(@lib.GlobalSec(globals)) => globals
None => return mod_
}
let exported_globals = Array::make(total_globals, false)
match mod_.export_sec {
Some(@lib.ExportSec(exports)) =>
for export_ in exports {
match export_ {
@lib.Export(_, @lib.GlobalExternIdx(@lib.GlobalIdx(global_idx))) => {
let slot = global_idx.reinterpret_as_int()
if slot >= 0 && slot < exported_globals.length() {
exported_globals[slot] = true
}
}
_ => ()
}
}
None => ()
}
let candidate_slots = Array::make(total_globals, -1)
let candidate_abs_globals : Array[Int32] = []
for defined_idx = 0
defined_idx < globals.length()
defined_idx = defined_idx + 1 {
let abs_idx = imported_global_count + defined_idx
let is_mutable = match env.globals[abs_idx] {
@lib.GlobalType(_, mutable_) => mutable_
}
if exported_globals[abs_idx] && (closed_world || is_mutable) {
continue
}
match gr_global_ref_type(env.globals[abs_idx]) {
Some(_) => {
candidate_slots[abs_idx] = candidate_abs_globals.length()
candidate_abs_globals.push(abs_idx)
}
None => ()
}
}
if candidate_abs_globals.length() == 0 {
return mod_
}
let scratch = GrNominalAncestryScratch::new(env)
let noted : Array[@lib.RefType?] = []
for _ in candidate_abs_globals {
noted.push(None)
}
for defined_idx = 0
defined_idx < globals.length()
defined_idx = defined_idx + 1 {
let abs_idx = imported_global_count + defined_idx
let slot = candidate_slots[abs_idx]
if slot < 0 {
continue
}
let @lib.Global(_, init) = globals[defined_idx]
match gr_expr_result_type(env, init) {
Some(@lib.RefTypeValType(ref_type)) =>
gr_note_ref_type(noted, slot, ref_type, env, scratch)
_ => ()
}
}
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => {
let module_ctx = @ir.hot_module_context_from_module(mod_)
let imported_func_count = gr_imported_func_count(mod_)
for defined_idx = 0
defined_idx < funcs.length()
defined_idx = defined_idx + 1 {
let @lib.Func(_, @lib.Expr(instrs)) = funcs[defined_idx]
if !gr_scan_candidate_sets(instrs, candidate_slots) {
continue
}
let abs_idx = imported_func_count + defined_idx
let hot_func = @ir.hot_lift_func_with_context(
mod_,
@lib.FuncIdx::new(abs_idx.reinterpret_as_uint()),
module_ctx,
)
gr_collect_func_global_sets(
hot_func, candidate_slots, noted, env, scratch,
)
}
}
None => ()
}
ignore(scratch.observed_work())
let next_globals = globals.copy()
let refined_globals : Array[@lib.RefType?] = Array::make(total_globals, None)
let mut public_type_scratch : GrPublicTypeScratch? = None
let mut changed = false
for defined_idx = 0
defined_idx < globals.length()
defined_idx = defined_idx + 1 {
let abs_idx = imported_global_count + defined_idx
let slot = candidate_slots[abs_idx]
if slot < 0 {
continue
}
let old_global_type = env.globals[abs_idx]
let old_ref_type = match gr_global_ref_type(old_global_type) {
Some(ref_type) => ref_type
None => continue
}
let inferred_ref_type = match noted[slot] {
Some(ref_type) => ref_type
None => continue
}
let refined_ref_type = if inferred_ref_type.is_exact() &&
!old_ref_type.is_exact() {
@lib.RefType::new(
inferred_ref_type.is_nullable(),
inferred_ref_type.get_heap_type(),
exact=false,
)
} else {
inferred_ref_type
}
if refined_ref_type == old_ref_type ||
!@validate.Match::matches(refined_ref_type, old_ref_type, env) {
continue
}
if exported_globals[abs_idx] {
let scratch = match public_type_scratch {
Some(existing) => existing
None => {
let created = GrPublicTypeScratch::new(env)
public_type_scratch = Some(created)
created
}
}
if !gr_refined_export_type_is_public(refined_ref_type, env, scratch) {
continue
}
}
let @lib.Global(@lib.GlobalType(_, mutable_), init) = next_globals[defined_idx]
let refined_init = match init {
@lib.Expr([@lib.RefNull(_)]) =>
@lib.Expr::new([@lib.Instruction::ref_null_type(refined_ref_type)])
_ => init
}
next_globals[defined_idx] = @lib.Global::new(
@lib.GlobalType::new(@lib.ValType::ref_type(refined_ref_type), mutable_),
refined_init,
)
refined_globals[abs_idx] = Some(refined_ref_type)
changed = true
}
if !changed {
return mod_
}
let rewritten = mod_.with_global_sec(@lib.GlobalSec::new(next_globals))
let (rewritten, _) = gr_rewrite_ref_null_sets_in_code(
rewritten, refined_globals,
)
rewritten
}
///|
pub fn global_refining_run_module_pass(
mod_ : @lib.Module,
closed_world~ : Bool = false,
) -> @lib.Module {
if pass_module_has_offset_sensitive_metadata(mod_) {
return mod_
}
let refined = gr_refine_module(mod_, closed_world~)
// A module without candidate globals can still carry duplicate signatures
// and fragmented numeric local runs. Reuse the guarded encoding cleanup.
if pass_module_has_legacy_try(refined) {
return refined
}
dfe_cleanup_raw_encoding(pass_cleanup_unused_type_groups(refined))
}