// 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/GlobalTypeOptimization.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.
///|
fn gto_scan_exposure(
instrs : Array[@lib.Instruction],
exposure : Array[Bool],
first_field_read : Array[Bool],
) -> Unit {
for instr in instrs {
match instr {
@lib.ExternConvertAny => exposure[0] = true
@lib.StructGet(_, @lib.U32(0U))
| @lib.StructGetS(_, @lib.U32(0U))
| @lib.StructGetU(_, @lib.U32(0U)) => first_field_read[0] = true
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
gto_scan_exposure(body, exposure, first_field_read)
@lib.TryTable(_, _, @lib.Expr(body)) =>
gto_scan_exposure(body, exposure, first_field_read)
@lib.Try(_, @lib.Expr(body), catches, _) => {
gto_scan_exposure(body, exposure, first_field_read)
for catch_ in catches {
match catch_ {
@lib.LegacyCatch(_, @lib.Expr(body))
| @lib.LegacyCatchAll(@lib.Expr(body)) =>
gto_scan_exposure(body, exposure, first_field_read)
}
}
}
@lib.If(_, then_body, else_body) => {
gto_scan_exposure(then_body, exposure, first_field_read)
match else_body {
Some(body) => gto_scan_exposure(body, exposure, first_field_read)
None => ()
}
}
_ => ()
}
}
}
///|
fn gto_field_can_configure_prototype(field : @lib.FieldType) -> Bool {
match field.unpack() {
@lib.RefTypeValType(ref_type) =>
ref_type.get_heap_type()
is @lib.AbsHeapTypeHeapType(@lib.ExternAbsHeapType)
_ => false
}
}
///|
fn gto_placeholder_field() -> @lib.FieldType {
@lib.FieldType::new(
@lib.StorageType::pack_type(@lib.PackType::i8()),
@lib.Mut::const_(),
)
}
///|
fn gto_type_plan(plans : Array[Int32], type_idx : @lib.TypeIdx) -> Int32 {
match type_idx {
@lib.TypeIdx(raw) => {
let index = raw.to_int()
if index >= 0 && index < plans.length() {
plans[index]
} else {
0
}
}
_ => 0
}
}
///|
fn gto_rewrite_subtype(subtype : @lib.SubType, plan : Int32) -> @lib.SubType {
if plan == 0 {
return subtype
}
let fields = match subtype.get_comptype() {
@lib.StructCompType(fields) => fields
_ => return subtype
}
let next_fields = fields.copy()
if plan == 1 {
next_fields[0] = gto_placeholder_field()
} else {
next_fields.insert(0, gto_placeholder_field())
}
let comp_type = @lib.CompType::struct_(next_fields)
match subtype {
@lib.SubType(final_, supers, metadata, _) =>
@lib.SubType::new(final_, supers, comp_type, metadata~)
@lib.CompTypeSubType(metadata, _) =>
@lib.SubType::comp_type(comp_type, metadata~)
}
}
///|
fn gto_rewrite_field_access(
instr : @lib.Instruction,
plans : Array[Int32],
) -> @lib.Instruction {
let (type_idx, @lib.U32(field)) = match gto_field_access(instr) {
Some((type_idx, field, _, _)) => (type_idx, field)
None => return instr
}
if gto_type_plan(plans, type_idx) != 2 {
return instr
}
let shifted = @lib.U32(field + 1U)
match instr {
@lib.StructGet(_, _) => @lib.Instruction::struct_get(type_idx, shifted)
@lib.StructGetS(_, _) => @lib.Instruction::struct_get_s(type_idx, shifted)
@lib.StructGetU(_, _) => @lib.Instruction::struct_get_u(type_idx, shifted)
@lib.StructSet(_, _) => @lib.Instruction::struct_set(type_idx, shifted)
@lib.StructAtomicGet(order, _, _) =>
@lib.Instruction::struct_atomic_get(order, type_idx, shifted)
@lib.StructAtomicGetS(order, _, _) =>
@lib.Instruction::struct_atomic_get_s(order, type_idx, shifted)
@lib.StructAtomicGetU(order, _, _) =>
@lib.Instruction::struct_atomic_get_u(order, type_idx, shifted)
@lib.StructAtomicSet(order, _, _) =>
@lib.Instruction::struct_atomic_set(order, type_idx, shifted)
@lib.StructAtomicRmw(order, op, _, _) =>
@lib.Instruction::struct_atomic_rmw(op, type_idx, shifted, order~)
@lib.StructAtomicCmpxchg(order, _, _) =>
@lib.Instruction::struct_atomic_cmpxchg(type_idx, shifted, order~)
@lib.StructWait(_, _) => @lib.Instruction::struct_wait(type_idx, shifted)
_ => instr
}
}
///|
fn gto_rewrite_expr(
expr : @lib.Expr,
plans : Array[Int32],
old_subtypes : Array[@lib.SubType],
env : @validate.Env,
failed : Array[Bool],
) -> @lib.Expr {
let @lib.Expr(instrs) = expr
let out : Array[@lib.Instruction] = []
for instr in instrs {
match instr {
@lib.Block(block_type, body) =>
out.push(
@lib.Instruction::block(
block_type,
gto_rewrite_expr(body, plans, old_subtypes, env, failed),
),
)
@lib.Loop(block_type, body) =>
out.push(
@lib.Instruction::loop_(
block_type,
gto_rewrite_expr(body, plans, old_subtypes, env, failed),
),
)
@lib.If(block_type, then_body, else_body) => {
let @lib.Expr(next_then) = gto_rewrite_expr(
@lib.Expr::new(then_body),
plans,
old_subtypes,
env,
failed,
)
let next_else = match else_body {
Some(body) => {
let @lib.Expr(next_body) = gto_rewrite_expr(
@lib.Expr::new(body),
plans,
old_subtypes,
env,
failed,
)
Some(next_body)
}
None => None
}
out.push(@lib.Instruction::if_(block_type, next_then, next_else))
}
@lib.TryTable(block_type, catches, body) =>
out.push(
@lib.Instruction::try_table(
block_type,
catches,
gto_rewrite_expr(body, plans, old_subtypes, env, failed),
),
)
@lib.Try(block_type, body, catches, delegate) =>
out.push(
@lib.Instruction::try_(
block_type,
gto_rewrite_expr(body, plans, old_subtypes, env, failed),
catches.map(fn(c) {
match c {
@lib.LegacyCatch(tag, expr) =>
@lib.LegacyCatch::tagged(
tag,
gto_rewrite_expr(expr, plans, old_subtypes, env, failed),
)
@lib.LegacyCatchAll(expr) =>
@lib.LegacyCatch::all(
gto_rewrite_expr(expr, plans, old_subtypes, env, failed),
)
}
}),
delegate~,
),
)
@lib.StructNew(type_idx) => {
let plan = gto_type_plan(plans, type_idx)
if plan != 0 {
let field_count = match type_idx {
@lib.TypeIdx(raw) =>
match old_subtypes[raw.to_int()].get_comptype() {
@lib.StructCompType(fields) => fields.length()
_ => 0
}
_ => 0
}
let mut operand_end = out.length() - 1
let mut first_start = -1
for operand = 0; operand < field_count; operand = operand + 1 {
first_start = gsi_find_operand_start(env, out, operand_end)
if first_start < 0 {
failed[0] = true
break
}
operand_end = first_start - 1
}
if first_start >= 0 && !failed[0] {
if plan == 2 {
out.insert(first_start, @lib.Instruction::i32_const(@lib.I32(0)))
} else if out[first_start] is @lib.RefNull(_) {
out[first_start] = @lib.Instruction::i32_const(@lib.I32(0))
} else {
failed[0] = true
}
}
}
out.push(instr)
}
_ => out.push(gto_rewrite_field_access(instr, plans))
}
}
@lib.Expr::new(out)
}
///|
fn global_type_optimization_run_module_pass(
mod_ : @lib.Module,
closed_world : Bool,
) -> @lib.Module {
if !closed_world ||
pass_module_has_offset_sensitive_metadata(mod_) ||
mod_.raw_name_sec_payload is Some(_) {
return mod_
}
let mod_ = gto_optimize_fields(mod_)
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
let old_subtypes = cl_module_subtypes(mod_)
let exposure = [false]
let first_field_read = [false]
for func in funcs {
let @lib.Func(_, @lib.Expr(body)) = func
gto_scan_exposure(body, exposure, first_field_read)
}
if !exposure[0] {
return mod_
}
let plans = Array::make(old_subtypes.length(), 0)
for index, subtype in old_subtypes {
if subtype.describes_type() is None {
continue
}
match subtype.get_comptype() {
@lib.StructCompType(fields) if fields.length() >= 2 &&
!gto_field_can_configure_prototype(fields[0]) =>
plans[index] = if first_field_read[0] { 2 } else { 1 }
_ => ()
}
}
if plans.all(fn(plan) { plan == 0 }) {
return mod_
}
let rec_types = match mod_.type_sec {
Some(@lib.TypeSec(rec_types)) => rec_types
None => return mod_
}
let rewritten_types : Array[@lib.RecType] = []
let mut flat_index = 0
for rec_type in rec_types {
match rec_type {
@lib.SingleRecType(subtype) => {
rewritten_types.push(
@lib.RecType::new(gto_rewrite_subtype(subtype, plans[flat_index])),
)
flat_index += 1
}
@lib.GroupRecType(group) => {
let next_group : Array[@lib.SubType] = []
for subtype in group {
next_group.push(gto_rewrite_subtype(subtype, plans[flat_index]))
flat_index += 1
}
rewritten_types.push(@lib.RecType::group(next_group))
}
}
}
let env = @validate.Env::new().with_module(mod_)
let failed = [false]
let rewritten_funcs : Array[@lib.Func] = []
for func in funcs {
let @lib.Func(locals, body) = func
rewritten_funcs.push(
@lib.Func::new(
locals,
gto_rewrite_expr(body, plans, old_subtypes, env, failed),
),
)
}
if failed[0] {
return mod_
}
let candidate = mod_
.with_type_sec(@lib.TypeSec::new(rewritten_types))
.with_code_sec(@lib.CodeSec::new(rewritten_funcs))
if @validate.validate_module(candidate).is_ok() {
candidate
} else {
mod_
}
}