// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Potentially incorporated Binaryen expression retains Apache-2.0 terms.
// Original Starshine contributions use the scoped MIT grant in ../../LICENSE.
// Modified for Starshine through 2026: implementation and validation use
// Starshine's representations and APIs; this is not an unchanged upstream file.
// Conservative notice retention does not establish copying or independence.
///|
pub fn simplify_globals_optimizing_summary() -> String {
"Simplify private global state and rerun default function cleanup on changed functions."
}
///|
priv struct SgoGlobalInfo {
imported : Bool
mut exported : Bool
mut mutable_ : Bool
value_type : @lib.ValType
mut init : @lib.Expr?
mut read_count : Int32
mut write_count : Int32
}
///|
priv struct SgoRewriteState {
consts : Array[@lib.Instruction?]
aliases : Array[@lib.GlobalIdx?]
remove_sets : Array[Bool]
mut changed : Bool
}
///|
priv struct SgoGlobalInitRewriteState {
consts : Array[@lib.Instruction?]
aliases : Array[@lib.GlobalIdx?]
inlines : Array[@lib.Expr?]
mut changed : Bool
}
///|
priv struct SgoPassResult {
mod_out : @lib.Module
touched : Array[Bool]
}
///|
priv enum SgoScalarFlowKind {
SgoFlowI32
SgoFlowI64
SgoFlowF32
SgoFlowF64
} derive(Eq)
///|
priv struct SgoScalarIndependentFragment {
result_kind : SgoScalarFlowKind
start_pos : Int32
next_pos : Int32
replay_end_pos : Int32
replay_nested_block : Bool
}
///|
fn sgo_global_idx_slot(idx : @lib.GlobalIdx) -> Int32 {
let @lib.GlobalIdx(raw) = idx
raw.reinterpret_as_int()
}
///|
fn sgo_defined_global_abs_idx(
imported_globals : Int32,
defined_idx : Int32,
) -> Int32 {
imported_globals + defined_idx
}
///|
fn sgo_const_instr(instr : @lib.Instruction) -> @lib.Instruction? {
match instr {
@lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _)
| @lib.RefNull(_)
| @lib.RefFunc(_)
| @lib.StringConst(_) => Some(instr)
_ => None
}
}
///|
fn sgo_expr_single_const(expr : @lib.Expr) -> @lib.Instruction? {
match expr {
@lib.Expr([instr]) => sgo_const_instr(instr)
_ => None
}
}
///|
fn sgo_empty_const_array(length : Int32) -> Array[@lib.Instruction?] {
Array::make(length, None)
}
///|
fn sgo_empty_expr_array(length : Int32) -> Array[@lib.Expr?] {
Array::make(length, None)
}
///|
fn sgo_empty_global_idx_array(length : Int32) -> Array[@lib.GlobalIdx?] {
Array::make(length, None)
}
///|
fn sgo_scan_global_idx(
infos : Array[SgoGlobalInfo],
idx : @lib.GlobalIdx,
read : Bool,
) -> Unit {
let slot = sgo_global_idx_slot(idx)
if slot < 0 || slot >= infos.length() {
return
}
if read {
infos[slot].read_count = infos[slot].read_count + 1
} else {
infos[slot].write_count = infos[slot].write_count + 1
}
}
///|
fn sgo_scan_instr(
instr : @lib.Instruction,
infos : Array[SgoGlobalInfo],
) -> Unit {
match instr {
@lib.GlobalGet(idx) => sgo_scan_global_idx(infos, idx, true)
@lib.GlobalSet(idx) => sgo_scan_global_idx(infos, idx, false)
@lib.Block(_, body) | @lib.Loop(_, body) => sgo_scan_expr(body, infos)
@lib.If(_, then_body, else_body) => {
sgo_scan_instrs(then_body, infos)
match else_body {
Some(body) => sgo_scan_instrs(body, infos)
None => ()
}
}
@lib.TryTable(_, _, body) => sgo_scan_expr(body, infos)
_ => ()
}
}
///|
fn sgo_scan_instrs(
instrs : Array[@lib.Instruction],
infos : Array[SgoGlobalInfo],
) -> Unit {
for instr in instrs {
sgo_scan_instr(instr, infos)
}
}
///|
fn sgo_scan_expr(expr : @lib.Expr, infos : Array[SgoGlobalInfo]) -> Unit {
let @lib.Expr(instrs) = expr
sgo_scan_instrs(instrs, infos)
}
///|
fn sgo_instr_may_reference_global(instr : @lib.Instruction) -> Bool {
match instr {
@lib.GlobalGet(_) | @lib.GlobalSet(_) => true
@lib.Block(_, body) | @lib.Loop(_, body) | @lib.TryTable(_, _, body) =>
sgo_expr_may_reference_global(body)
@lib.If(_, then_body, else_body) => {
let else_refs = match else_body {
Some(body) => sgo_instrs_may_reference_global(body)
None => false
}
sgo_instrs_may_reference_global(then_body) || else_refs
}
_ => false
}
}
///|
fn sgo_instrs_may_reference_global(instrs : Array[@lib.Instruction]) -> Bool {
instrs.any(sgo_instr_may_reference_global)
}
///|
fn sgo_instr_references_global_idx(
instr : @lib.Instruction,
idx : @lib.GlobalIdx,
) -> Bool {
match instr {
@lib.GlobalGet(get_idx) | @lib.GlobalSet(get_idx) => get_idx == idx
@lib.Block(_, body) | @lib.Loop(_, body) | @lib.TryTable(_, _, body) =>
sgo_expr_references_global_idx(body, idx)
@lib.If(_, then_body, else_body) => {
let else_refs = match else_body {
Some(body) => sgo_instrs_reference_global_idx(body, idx)
None => false
}
sgo_instrs_reference_global_idx(then_body, idx) || else_refs
}
_ => false
}
}
///|
fn sgo_instrs_reference_global_idx(
instrs : Array[@lib.Instruction],
idx : @lib.GlobalIdx,
) -> Bool {
instrs.any(fn(instr) { sgo_instr_references_global_idx(instr, idx) })
}
///|
fn sgo_expr_references_global_idx(
expr : @lib.Expr,
idx : @lib.GlobalIdx,
) -> Bool {
let @lib.Expr(instrs) = expr
sgo_instrs_reference_global_idx(instrs, idx)
}
///|
fn sgo_expr_may_reference_global(expr : @lib.Expr) -> Bool {
let @lib.Expr(instrs) = expr
sgo_instrs_may_reference_global(instrs)
}
///|
fn sgo_type_sec_func_type(
type_sec : @lib.TypeSec,
type_idx : @lib.TypeIdx,
) -> @lib.FuncType? {
let raw = match type_idx {
@lib.TypeIdx(raw) => raw
_ => return None
}
let @lib.TypeSec(groups) = type_sec
let mut remaining = raw.to_uint64()
for group in groups {
let members = match group {
@lib.SingleRecType(subtype) => [subtype]
@lib.GroupRecType(subtypes) => subtypes
}
if remaining < members.length().to_uint64() {
return match members[remaining.to_int()].get_comptype() {
@lib.FuncCompType(params, results) =>
Some(@lib.FuncType::new(params, results))
_ => None
}
}
remaining -= members.length().to_uint64()
}
None
}
///|
fn sgo_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 sgo_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) =>
sgo_type_sec_func_type(type_sec, func_type_indices[func_abs])
None => None
}
}
///|
fn sgo_func_idx_slot(idx : @lib.FuncIdx) -> Int32 {
let @lib.FuncIdx(raw) = idx
raw.reinterpret_as_int()
}
///|
fn sgo_call_has_no_params_one_result(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_idx : @lib.FuncIdx,
) -> Bool {
match
sgo_resolve_func_type(mod_, func_type_indices, sgo_func_idx_slot(func_idx)) {
Some(@lib.FuncType(params, results)) =>
params.length() == 0 && results.length() == 1
None => false
}
}
///|
fn sgo_call_has_no_params_no_results(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_idx : @lib.FuncIdx,
) -> Bool {
match
sgo_resolve_func_type(mod_, func_type_indices, sgo_func_idx_slot(func_idx)) {
Some(@lib.FuncType(params, results)) =>
params.length() == 0 && results.length() == 0
None => false
}
}
///|
fn sgo_scalar_flow_kind(value_type : @lib.ValType) -> SgoScalarFlowKind? {
match value_type {
@lib.NumTypeValType(@lib.I32NumType) => Some(SgoFlowI32)
@lib.NumTypeValType(@lib.I64NumType) => Some(SgoFlowI64)
@lib.NumTypeValType(@lib.F32NumType) => Some(SgoFlowF32)
@lib.NumTypeValType(@lib.F64NumType) => Some(SgoFlowF64)
_ => None
}
}
///|
fn sgo_call_no_params_scalar_result_kind(
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
func_idx : @lib.FuncIdx,
) -> SgoScalarFlowKind? {
match
sgo_resolve_func_type(mod_, func_type_indices, sgo_func_idx_slot(func_idx)) {
Some(@lib.FuncType(params, [result])) if params.length() == 0 =>
sgo_scalar_flow_kind(result)
_ => None
}
}
///|
fn sgo_collect_global_infos(
mod_ : @lib.Module,
) -> (Array[SgoGlobalInfo], Int32) {
let infos : Array[SgoGlobalInfo] = []
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(value_type, mutable_))
) => {
infos.push({
imported: true,
exported: false,
mutable_,
value_type,
init: None,
read_count: 0,
write_count: 0,
})
imported_globals += 1
}
_ => ()
}
}
None => ()
}
match mod_.global_sec {
Some(@lib.GlobalSec(globals)) =>
for global_ in globals {
let @lib.Global(@lib.GlobalType(value_type, mutable_), init) = global_
infos.push({
imported: false,
exported: false,
mutable_,
value_type,
init: Some(init),
read_count: 0,
write_count: 0,
})
}
None => ()
}
match mod_.export_sec {
Some(@lib.ExportSec(exports)) =>
for export_ in exports {
let @lib.Export(_, idx) = export_
match idx {
@lib.GlobalExternIdx(global_idx) => {
let slot = sgo_global_idx_slot(global_idx)
if slot >= 0 && slot < infos.length() {
infos[slot].exported = true
}
}
_ => ()
}
}
None => ()
}
sgo_scan_table_sec(mod_.table_sec, infos)
sgo_scan_global_sec(mod_.global_sec, infos)
sgo_scan_elem_sec(mod_.elem_sec, infos)
sgo_scan_data_sec(mod_.data_sec, infos)
sgo_scan_code_sec(mod_.code_sec, infos)
(infos, imported_globals)
}
///|
fn sgo_scan_table_sec(
table_sec : @lib.TableSec?,
infos : Array[SgoGlobalInfo],
) -> Unit {
match table_sec {
Some(@lib.TableSec(tables)) =>
for table in tables {
match table {
@lib.Table(_, Some(init)) => sgo_scan_expr(init, infos)
_ => ()
}
}
None => ()
}
}
///|
fn sgo_scan_global_sec(
global_sec : @lib.GlobalSec?,
infos : Array[SgoGlobalInfo],
) -> Unit {
match global_sec {
Some(@lib.GlobalSec(globals)) =>
for global_ in globals {
let @lib.Global(_, init) = global_
sgo_scan_expr(init, infos)
}
None => ()
}
}
///|
fn sgo_scan_elem_mode(
mode : @lib.ElemMode,
infos : Array[SgoGlobalInfo],
) -> Unit {
match mode {
@lib.Active(_, offset) => sgo_scan_expr(offset, infos)
_ => ()
}
}
///|
fn sgo_scan_elem_kind(
kind : @lib.ElemKind,
infos : Array[SgoGlobalInfo],
) -> Unit {
match kind {
@lib.FuncExprsElemKind(exprs) | @lib.TypedExprsElemKind(_, exprs) =>
for expr in exprs {
sgo_scan_expr(expr, infos)
}
_ => ()
}
}
///|
fn sgo_scan_elem_sec(
elem_sec : @lib.ElemSec?,
infos : Array[SgoGlobalInfo],
) -> Unit {
match elem_sec {
Some(@lib.ElemSec(elems)) =>
for elem in elems {
let @lib.Elem(mode, kind) = elem
sgo_scan_elem_mode(mode, infos)
sgo_scan_elem_kind(kind, infos)
}
None => ()
}
}
///|
fn sgo_scan_data_sec(
data_sec : @lib.DataSec?,
infos : Array[SgoGlobalInfo],
) -> Unit {
match data_sec {
Some(@lib.DataSec(datas)) =>
for data in datas {
let @lib.Data(mode, _) = data
match mode {
@lib.Active(_, offset) => sgo_scan_expr(offset, infos)
_ => ()
}
}
None => ()
}
}
///|
fn sgo_scan_code_sec(
code_sec : @lib.CodeSec?,
infos : Array[SgoGlobalInfo],
) -> Unit {
match code_sec {
Some(@lib.CodeSec(funcs)) =>
for func_ in funcs {
let @lib.Func(_, body) = func_
sgo_scan_expr(body, infos)
}
None => ()
}
}
///|
fn sgo_same_init_write_flags(
code_sec : @lib.CodeSec?,
infos : Array[SgoGlobalInfo],
) -> Array[Bool] {
let init_consts = sgo_build_initial_startup_consts(infos)
let flags = Array::make(infos.length(), true)
match code_sec {
Some(@lib.CodeSec(funcs)) =>
for func_ in funcs {
let @lib.Func(_, body) = func_
if sgo_expr_may_reference_global(body) {
sgo_mark_same_init_writes_expr(body, init_consts, flags)
}
}
None => ()
}
flags
}
///|
fn sgo_mark_same_init_writes_expr(
expr : @lib.Expr,
init_consts : Array[@lib.Instruction?],
flags : Array[Bool],
) -> Unit {
let @lib.Expr(instrs) = expr
sgo_mark_same_init_writes_instrs(instrs, init_consts, flags)
}
///|
fn sgo_mark_same_init_writes_instrs(
instrs : Array[@lib.Instruction],
init_consts : Array[@lib.Instruction?],
flags : Array[Bool],
) -> Unit {
for pos = 0; pos < instrs.length(); pos = pos + 1 {
let instr = instrs[pos]
match instr {
@lib.GlobalSet(idx) =>
sgo_mark_same_init_write(idx, pos, instrs, init_consts, flags)
@lib.Block(_, body) | @lib.Loop(_, body) =>
sgo_mark_same_init_writes_expr(body, init_consts, flags)
@lib.If(_, then_body, else_body) => {
sgo_mark_same_init_writes_instrs(then_body, init_consts, flags)
match else_body {
Some(body) =>
sgo_mark_same_init_writes_instrs(body, init_consts, flags)
None => ()
}
}
@lib.TryTable(_, _, body) =>
sgo_mark_same_init_writes_expr(body, init_consts, flags)
_ => ()
}
}
}
///|
fn sgo_same_literal_bits(
lhs : @lib.Instruction,
rhs : @lib.Instruction,
) -> Bool {
match (lhs, rhs) {
(@lib.F32Const(@lib.F32(a)), @lib.F32Const(@lib.F32(b))) =>
a.reinterpret_as_uint() == b.reinterpret_as_uint()
(@lib.F64Const(@lib.F64(a)), @lib.F64Const(@lib.F64(b))) =>
a.reinterpret_as_uint64() == b.reinterpret_as_uint64()
_ => lhs == rhs
}
}
///|
fn sgo_mark_same_init_write(
idx : @lib.GlobalIdx,
pos : Int32,
instrs : Array[@lib.Instruction],
init_consts : Array[@lib.Instruction?],
flags : Array[Bool],
) -> Unit {
let slot = sgo_global_idx_slot(idx)
if slot < 0 || slot >= flags.length() || slot >= init_consts.length() {
return
}
let mut matches_init = false
if pos > 0 {
match (sgo_const_instr(instrs[pos - 1]), init_consts[slot]) {
(Some(actual), Some(expected)) if sgo_same_literal_bits(actual, expected) =>
matches_init = true
_ => ()
}
}
if !matches_init {
flags[slot] = false
}
}
///|
fn sgo_read_only_to_write_flags(
mod_ : @lib.Module,
infos : Array[SgoGlobalInfo],
) -> Array[Bool] {
let safe_reads = Array::make(infos.length(), 0)
let func_type_indices = sgo_collect_func_type_indices(mod_)
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
for func_ in funcs {
let @lib.Func(_, body) = func_
if sgo_expr_may_reference_global(body) {
sgo_count_read_only_to_write_expr(
body, safe_reads, mod_, func_type_indices,
)
sgo_count_nested_multiglobal_read_only_to_write_expr(
body, safe_reads, infos,
)
}
}
None => ()
}
let flags = Array::make(infos.length(), false)
for idx = 0; idx < infos.length(); idx = idx + 1 {
flags[idx] = infos[idx].read_count > 0 &&
safe_reads[idx] == infos[idx].read_count
}
flags
}
///|
fn sgo_count_read_only_to_write_expr(
expr : @lib.Expr,
safe_reads : Array[Int32],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Unit {
let @lib.Expr(instrs) = expr
sgo_count_read_only_to_write_instrs(
instrs, safe_reads, mod_, func_type_indices,
)
}
///|
fn sgo_count_read_only_to_write_safe_read(
idx : @lib.GlobalIdx,
safe_reads : Array[Int32],
) -> Unit {
let slot = sgo_global_idx_slot(idx)
if slot >= 0 && slot < safe_reads.length() {
safe_reads[slot] = safe_reads[slot] + 1
}
}
///|
fn sgo_guarded_scalar_pure_producer_read(
instr : @lib.Instruction,
result_kind : SgoScalarFlowKind,
) -> @lib.GlobalIdx? {
match instr {
@lib.GlobalGet(idx) => Some(idx)
@lib.Block(@lib.ValTypeBlockType(value_type), @lib.Expr(body))
| @lib.Loop(@lib.ValTypeBlockType(value_type), @lib.Expr(body))
| @lib.TryTable(@lib.ValTypeBlockType(value_type), _, @lib.Expr(body)) =>
match sgo_scalar_flow_kind(value_type) {
Some(kind) if kind == result_kind =>
sgo_guarded_scalar_pure_body_read(body, result_kind)
_ => None
}
_ => None
}
}
///|
fn sgo_guarded_scalar_pure_body_read(
body : Array[@lib.Instruction],
result_kind : SgoScalarFlowKind,
) -> @lib.GlobalIdx? {
if body.length() == 0 {
return None
}
let idx = match sgo_guarded_scalar_pure_producer_read(body[0], result_kind) {
Some(idx) => idx
None => return None
}
let mut kind = result_kind
let mut pos = 1
while pos < body.length() {
match sgo_scalar_unary_result(body[pos], kind) {
Some(next_kind) => {
kind = next_kind
pos = pos + 1
continue
}
None => ()
}
if pos + 1 < body.length() && sgo_scalar_const_kind(body[pos]) == Some(kind) {
match sgo_scalar_binary_result(body[pos + 1], kind) {
Some(next_kind) => {
kind = next_kind
pos = pos + 2
continue
}
None => ()
}
}
return None
}
if kind == result_kind {
Some(idx)
} else {
None
}
}
///|
fn sgo_structured_guarded_scalar_read(
instr : @lib.Instruction,
) -> (@lib.GlobalIdx, SgoScalarFlowKind)? {
match instr {
@lib.Loop(@lib.ValTypeBlockType(value_type), @lib.Expr(body)) =>
match sgo_scalar_flow_kind(value_type) {
Some(result_kind) =>
match sgo_guarded_scalar_pure_body_read(body, result_kind) {
Some(idx) => Some((idx, result_kind))
None => None
}
None => None
}
@lib.TryTable(@lib.ValTypeBlockType(value_type), _, @lib.Expr(body)) =>
match sgo_scalar_flow_kind(value_type) {
Some(result_kind) =>
match sgo_guarded_scalar_pure_body_read(body, result_kind) {
Some(idx) => Some((idx, result_kind))
None => None
}
None => None
}
@lib.Block(@lib.ValTypeBlockType(value_type), @lib.Expr(body)) =>
match sgo_scalar_flow_kind(value_type) {
Some(result_kind) => {
let nested_pure_idx = if body.length() > 0 {
match body[0] {
@lib.TryTable(_, _, _) =>
sgo_guarded_scalar_pure_body_read(body, result_kind)
_ => None
}
} else {
None
}
match nested_pure_idx {
Some(idx) => Some((idx, result_kind))
None =>
match body {
[
@lib.GlobalGet(idx),
@lib.LocalGet(_),
@lib.BrIf(@lib.LabelIdx(0U)),
@lib.Drop,
result,
] if sgo_scalar_const_kind(result) == Some(result_kind) =>
Some((idx, result_kind))
_ => None
}
}
}
None => None
}
_ => None
}
}
///|
fn sgo_count_structured_guarded_scalar_read(
instrs : Array[@lib.Instruction],
pos : Int32,
safe_reads : Array[Int32],
) -> Unit {
match sgo_structured_guarded_scalar_read(instrs[pos]) {
Some((idx, result_kind)) => {
let prefix_start = sgo_scalar_const_prefix_start(instrs, pos)
if sgo_scalar_pure_suffix_with_prefix_tail_matches(
instrs,
pos + 1,
idx,
result_kind,
prefix_start,
pos,
) {
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
}
}
None => ()
}
}
///|
fn sgo_count_read_only_to_write_instrs(
instrs : Array[@lib.Instruction],
safe_reads : Array[Int32],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Unit {
for pos = 0; pos < instrs.length(); pos = pos + 1 {
let instr = instrs[pos]
match instr {
@lib.GlobalGet(idx) =>
if sgo_read_only_to_write_read_at(
instrs, pos, idx, mod_, func_type_indices,
) {
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
}
@lib.Block(_, body) => {
sgo_count_structured_guarded_scalar_read(instrs, pos, safe_reads)
sgo_count_block_condition_external_pure_read(instrs, pos, safe_reads)
sgo_count_block_condition_external_pure_if_return_read(
instrs, pos, safe_reads,
)
sgo_count_block_condition_reverse_external_pure_read(
instrs, pos, safe_reads,
)
sgo_count_block_condition_reverse_external_pure_if_return_read(
instrs, pos, safe_reads,
)
if pos + 1 < instrs.length() {
sgo_count_block_condition_read_only_to_write_read(
instr,
instrs[pos + 1],
safe_reads,
)
sgo_count_block_condition_independent_call_read_only_to_write_read(
instr,
instrs[pos + 1],
safe_reads,
mod_,
func_type_indices,
)
sgo_count_block_condition_independent_memory_read_only_to_write_read(
instr,
instrs[pos + 1],
safe_reads,
)
sgo_count_block_condition_independent_table_read_only_to_write_read(
instr,
instrs[pos + 1],
safe_reads,
)
sgo_count_block_condition_independent_local_read_only_to_write_read(
instr,
instrs[pos + 1],
safe_reads,
)
sgo_count_block_condition_independent_global_set_read_only_to_write_read(
instr,
instrs[pos + 1],
safe_reads,
)
}
sgo_count_block_condition_independent_call_if_return_read(
instrs, pos, safe_reads, mod_, func_type_indices,
)
sgo_count_block_condition_independent_memory_if_return_read(
instrs, pos, safe_reads,
)
sgo_count_block_condition_independent_table_if_return_read(
instrs, pos, safe_reads,
)
sgo_count_block_condition_independent_local_if_return_read(
instrs, pos, safe_reads,
)
sgo_count_block_condition_independent_global_set_if_return_read(
instrs, pos, safe_reads,
)
if pos + 2 < instrs.length() {
if pos + 3 == instrs.length() {
sgo_count_block_condition_if_return_block_set_read(
instr,
instrs[pos + 1],
instrs[pos + 2],
safe_reads,
)
}
sgo_count_block_condition_eqz_read_only_to_write_read(
instr,
instrs[pos + 1],
instrs[pos + 2],
safe_reads,
)
sgo_count_block_condition_reverse_compare_read_only_to_write_read(
instrs, pos, safe_reads,
)
}
if pos + 3 < instrs.length() {
if pos + 4 == instrs.length() {
sgo_count_block_condition_if_return_set_read(
instr,
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
safe_reads,
)
sgo_count_block_condition_eqz_if_return_block_set_read(
instr,
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
safe_reads,
)
sgo_count_block_condition_reverse_compare_if_return_block_set_read(
instrs, pos, safe_reads,
)
}
sgo_count_block_condition_compare_read_only_to_write_read(
instr,
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
safe_reads,
)
}
if pos + 4 < instrs.length() {
if pos + 5 == instrs.length() {
sgo_count_block_condition_eqz_if_return_set_read(
instr,
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
safe_reads,
)
sgo_count_block_condition_reverse_compare_if_return_set_read(
instrs, pos, safe_reads,
)
sgo_count_block_condition_compare_if_return_block_set_read(
instr,
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
safe_reads,
)
}
}
if pos + 5 < instrs.length() {
if pos + 6 == instrs.length() {
sgo_count_block_condition_compare_if_return_set_read(
instr,
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
safe_reads,
)
}
}
sgo_count_read_only_to_write_expr(
body, safe_reads, mod_, func_type_indices,
)
}
@lib.Loop(_, body) => {
sgo_count_structured_guarded_scalar_read(instrs, pos, safe_reads)
sgo_count_read_only_to_write_expr(
body, safe_reads, mod_, func_type_indices,
)
}
@lib.If(_, then_body, else_body) => {
sgo_count_if_arm_condition_read_only_to_write_read(
instrs, pos, safe_reads,
)
sgo_count_if_arm_condition_if_return_read(instrs, pos, safe_reads)
sgo_count_read_only_to_write_instrs(
then_body, safe_reads, mod_, func_type_indices,
)
match else_body {
Some(body) =>
sgo_count_read_only_to_write_instrs(
body, safe_reads, mod_, func_type_indices,
)
None => ()
}
}
@lib.TryTable(_, _, body) => {
sgo_count_structured_guarded_scalar_read(instrs, pos, safe_reads)
sgo_count_read_only_to_write_expr(
body, safe_reads, mod_, func_type_indices,
)
}
_ => ()
}
}
}
///|
fn sgo_if_is_read_only_to_write_set(
instr : @lib.Instruction,
idx : @lib.GlobalIdx,
) -> Bool {
match instr {
@lib.If(_, then_body, None) =>
sgo_instrs_are_single_const_global_set(then_body, idx)
_ => false
}
}
///|
fn sgo_if_arm_condition_read_idx(instr : @lib.Instruction) -> @lib.GlobalIdx? {
match instr {
@lib.If(_, then_body, Some(else_body)) => {
let then_idx = sgo_block_condition_safe_arm_read_idx(then_body)
let else_idx = sgo_block_condition_safe_arm_read_idx(else_body)
match (then_idx, else_idx) {
(Some(idx), None) if !sgo_instrs_may_reference_global(else_body) =>
Some(idx)
(None, Some(idx)) if !sgo_instrs_may_reference_global(then_body) =>
Some(idx)
_ => None
}
}
_ => None
}
}
///|
fn sgo_count_if_arm_condition_read_only_to_write_read(
instrs : Array[@lib.Instruction],
pos : Int32,
safe_reads : Array[Int32],
) -> Unit {
match sgo_if_arm_condition_read_idx(instrs[pos]) {
Some(idx) =>
for scan = pos + 1; scan < instrs.length(); scan = scan + 1 {
let instr = instrs[scan]
match instr {
@lib.If(_, _, _) => {
if sgo_if_is_read_only_to_write_set(instr, idx) {
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
}
return
}
_ =>
if !sgo_is_read_only_to_write_pure_condition_instr(instr) {
return
}
}
}
None => ()
}
}
///|
fn sgo_count_if_arm_condition_if_return_read(
instrs : Array[@lib.Instruction],
pos : Int32,
safe_reads : Array[Int32],
) -> Unit {
match sgo_if_arm_condition_read_idx(instrs[pos]) {
Some(idx) =>
for scan = pos + 1; scan < instrs.length(); scan = scan + 1 {
let instr = instrs[scan]
if sgo_is_return_guard(instr) {
if sgo_if_return_tail_matches(instrs, scan + 1, idx) {
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
}
return
}
if !sgo_is_read_only_to_write_pure_condition_instr(instr) {
return
}
}
None => ()
}
}
///|
fn sgo_read_only_to_write_read_at(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Bool {
sgo_is_exact_if_return_set(instrs, pos, idx) ||
sgo_is_pure_condition_if_return_set(instrs, pos, idx) ||
sgo_scalar_call_parent_chain_read_at(
instrs, pos, idx, mod_, func_type_indices,
) ||
sgo_select_operand_independent_call_read_at(
instrs, pos, idx, mod_, func_type_indices,
) ||
sgo_compare_operand_independent_call_read_at(
instrs, pos, idx, mod_, func_type_indices,
) ||
sgo_pure_i32_binary_operand_independent_call_read_at(
instrs, pos, idx, mod_, func_type_indices,
) ||
sgo_pure_i64_binary_operand_independent_call_read_at(
instrs, pos, idx, mod_, func_type_indices,
) ||
sgo_float_binary_operand_independent_call_read_at(
instrs, pos, idx, mod_, func_type_indices,
) ||
sgo_compare_operand_independent_memory_read_at(instrs, pos, idx) ||
sgo_compare_operand_independent_table_read_at(instrs, pos, idx) ||
sgo_compare_operand_independent_local_tee_read_at(instrs, pos, idx) ||
sgo_compare_operand_independent_global_set_read_at(instrs, pos, idx) ||
sgo_compare_operand_independent_memory_store_read_at(instrs, pos, idx) ||
sgo_select_operand_independent_memory_read_at(instrs, pos, idx) ||
sgo_select_operand_independent_table_read_at(instrs, pos, idx) ||
sgo_select_operand_independent_local_tee_read_at(instrs, pos, idx) ||
(
pos + 1 < instrs.length() &&
sgo_if_is_read_only_to_write_set(instrs[pos + 1], idx)
) ||
(
pos + 2 < instrs.length() &&
instrs[pos + 1] == @lib.Instruction::i32_eqz() &&
sgo_if_is_read_only_to_write_set(instrs[pos + 2], idx)
) ||
(
pos + 3 < instrs.length() &&
sgo_const_instr(instrs[pos + 1]) != None &&
(
instrs[pos + 2] == @lib.Instruction::i32_eq() ||
instrs[pos + 2] == @lib.Instruction::i32_ne()
) &&
sgo_if_is_read_only_to_write_set(instrs[pos + 3], idx)
) ||
(
pos > 0 &&
pos + 2 < instrs.length() &&
sgo_const_instr(instrs[pos - 1]) != None &&
(
instrs[pos + 1] == @lib.Instruction::i32_eq() ||
instrs[pos + 1] == @lib.Instruction::i32_ne()
) &&
sgo_if_is_read_only_to_write_set(instrs[pos + 2], idx)
) ||
sgo_read_only_to_write_pure_condition_read_at(instrs, pos, idx) ||
sgo_read_only_to_write_if_return_value_read_at(instrs, pos, idx)
}
///|
fn sgo_local_effect_value_is_independent(instr : @lib.Instruction) -> Bool {
match instr {
@lib.LocalGet(_) => true
_ => sgo_const_instr(instr) != None
}
}
///|
fn sgo_select_operand_independent_local_tee_read_at(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
) -> Bool {
// Narrow FlowScanner subset: an independent constant local.tee supplies one
// select operand while the guarded global supplies the other. The guarded
// value must not flow into the local write; it may only flow through select
// to the same-global guarded write or if-return tail.
match instrs[pos] {
@lib.GlobalGet(get_idx) if get_idx == idx => ()
_ => return false
}
if pos + 5 < instrs.length() {
match
(
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
) {
(value, @lib.LocalTee(_), const_instr, @lib.Select(_), if_instr) if sgo_const_instr(
value,
) !=
None &&
sgo_const_instr(const_instr) != None =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 6, idx)
) {
return true
}
_ => ()
}
}
if pos > 1 && pos + 3 < instrs.length() {
match
(
instrs[pos - 2],
instrs[pos - 1],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
) {
(value, @lib.LocalTee(_), const_instr, @lib.Select(_), if_instr) if sgo_const_instr(
value,
) !=
None &&
sgo_const_instr(const_instr) != None =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 4, idx)
) {
return true
}
_ => ()
}
}
false
}
///|
fn sgo_compare_operand_independent_local_tee_read_at(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
) -> Bool {
// Narrow FlowScanner subset: an independent constant-or-local.get local.tee
// supplies one compare operand while the guarded global supplies the other.
// The guarded value must not flow into the local write; it may only reach the
// compare that feeds the same-global guarded write or if-return tail.
match instrs[pos] {
@lib.GlobalGet(get_idx) if get_idx == idx => ()
_ => return false
}
if pos + 4 < instrs.length() {
match (instrs[pos + 1], instrs[pos + 2], instrs[pos + 3], instrs[pos + 4]) {
(value, @lib.LocalTee(_), compare, if_instr) if sgo_local_effect_value_is_independent(
value,
) &&
sgo_is_compare_instr(compare) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 5, idx)
) {
return true
}
_ => ()
}
}
if pos > 1 && pos + 2 < instrs.length() {
match (instrs[pos - 2], instrs[pos - 1], instrs[pos + 1], instrs[pos + 2]) {
(value, @lib.LocalTee(_), compare, if_instr) if sgo_local_effect_value_is_independent(
value,
) &&
sgo_is_compare_instr(compare) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 3, idx)
) {
return true
}
_ => ()
}
}
false
}
///|
fn sgo_global_set_result_block_value_is_independent(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.LocalGet(_) => true
_ => sgo_const_instr(instr) != None
}
}
///|
fn sgo_independent_global_set_result_block_matches(
instr : @lib.Instruction,
idx : @lib.GlobalIdx,
) -> Bool {
match instr {
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([value, @lib.GlobalSet(set_idx), result_const])
) =>
set_idx != idx &&
sgo_global_set_result_block_value_is_independent(value) &&
sgo_const_instr(result_const) != None
_ => false
}
}
///|
fn sgo_compare_operand_independent_global_set_read_at(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
) -> Bool {
// Narrow FlowScanner subset: an independent constant-or-local.get global.set
// to a different global may produce the other compare operand through a
// result block. The guarded value reaches only the compare and final
// same-global guarded write / if-return tail; it must not feed the
// independent set.
match instrs[pos] {
@lib.GlobalGet(get_idx) if get_idx == idx => ()
_ => return false
}
if pos + 3 < instrs.length() {
match (instrs[pos + 1], instrs[pos + 2], instrs[pos + 3]) {
(block, compare, if_instr) if sgo_is_compare_instr(compare) &&
sgo_independent_global_set_result_block_matches(block, idx) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 4, idx)
) {
return true
}
_ => ()
}
}
if pos > 0 && pos + 2 < instrs.length() {
match (instrs[pos - 1], instrs[pos + 1], instrs[pos + 2]) {
(block, compare, if_instr) if sgo_is_compare_instr(compare) &&
sgo_independent_global_set_result_block_matches(block, idx) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 3, idx)
) {
return true
}
_ => ()
}
}
false
}
///|
fn sgo_is_plain_memory_store_instr(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I32Store(_)
| @lib.I64Store(_)
| @lib.F32Store(_)
| @lib.F64Store(_)
| @lib.I32Store8(_)
| @lib.I32Store16(_)
| @lib.I64Store8(_)
| @lib.I64Store16(_)
| @lib.I64Store32(_)
| @lib.V128Store(_) => true
_ => false
}
}
///|
fn sgo_memory_store_result_block_operand_is_independent(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.LocalGet(_) => true
_ => sgo_const_instr(instr) != None
}
}
///|
fn sgo_independent_memory_store_result_block_matches(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([addr, value, store, result_const])
) =>
sgo_memory_store_result_block_operand_is_independent(addr) &&
sgo_memory_store_result_block_operand_is_independent(value) &&
sgo_is_plain_memory_store_instr(store) &&
sgo_const_instr(result_const) != None
_ => false
}
}
///|
fn sgo_push_memory_store_result_block_effect(
instr : @lib.Instruction,
out : Array[@lib.Instruction],
) -> Unit {
match instr {
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([addr, value, store, _])
) if sgo_memory_store_result_block_operand_is_independent(addr) &&
sgo_memory_store_result_block_operand_is_independent(value) &&
sgo_is_plain_memory_store_instr(store) => {
out.push(addr)
out.push(value)
out.push(store)
}
_ => ()
}
}
///|
fn sgo_compare_operand_independent_memory_store_read_at(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
) -> Bool {
// Narrow FlowScanner subset: an independent constant memory store may produce
// the other compare operand through a result block. The guarded value reaches
// only the compare and final same-global guarded write / if-return tail; it
// must not feed the store address or stored value.
match instrs[pos] {
@lib.GlobalGet(get_idx) if get_idx == idx => ()
_ => return false
}
if pos + 3 < instrs.length() {
match (instrs[pos + 1], instrs[pos + 2], instrs[pos + 3]) {
(block, compare, if_instr) if sgo_is_compare_instr(compare) &&
sgo_independent_memory_store_result_block_matches(block) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 4, idx)
) {
return true
}
_ => ()
}
}
if pos > 0 && pos + 2 < instrs.length() {
match (instrs[pos - 1], instrs[pos + 1], instrs[pos + 2]) {
(block, compare, if_instr) if sgo_is_compare_instr(compare) &&
sgo_independent_memory_store_result_block_matches(block) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 3, idx)
) {
return true
}
_ => ()
}
}
false
}
///|
fn sgo_select_operand_independent_call_read_at(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Bool {
// Narrow FlowScanner subset: from a valid empty-stack prefix, the guarded
// global is the first select operand while a following zero-param call
// provides the second operand independently. The global-derived select
// result may then only reach the final same-global guarded write.
if pos != 0 || pos + 4 >= instrs.length() {
return false
}
match instrs[pos] {
@lib.GlobalGet(get_idx) if get_idx == idx => ()
_ => return false
}
match (instrs[pos + 1], instrs[pos + 2], instrs[pos + 3], instrs[pos + 4]) {
(@lib.Call(func_idx), const_instr, @lib.Select(_), if_instr) =>
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_const_instr(const_instr) != None &&
(
sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 5, idx)
)
)
_ => false
}
}
///|
fn sgo_compare_operand_independent_call_read_at(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Bool {
// Narrow FlowScanner subset: a zero-param/result call supplies one compare
// operand independently of the guarded global, so the global-derived value
// reaches only the final same-global guarded write or if-return tail.
match instrs[pos] {
@lib.GlobalGet(get_idx) if get_idx == idx => ()
_ => return false
}
if pos + 3 < instrs.length() {
match (instrs[pos + 1], instrs[pos + 2], instrs[pos + 3]) {
(@lib.Call(func_idx), compare, if_instr) if sgo_is_compare_instr(compare) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 4, idx)
) {
return true
}
_ => ()
}
}
if pos > 0 && pos + 2 < instrs.length() {
match (instrs[pos - 1], instrs[pos + 1], instrs[pos + 2]) {
(@lib.Call(func_idx), compare, if_instr) if sgo_is_compare_instr(compare) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 3, idx)
) {
return true
}
_ => ()
}
}
false
}
///|
fn sgo_is_nontrapping_i32_binary_flow_instr(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I32Add
| @lib.I32Sub
| @lib.I32Mul
| @lib.I32And
| @lib.I32Or
| @lib.I32Xor
| @lib.I32Shl
| @lib.I32ShrS
| @lib.I32ShrU
| @lib.I32Rotl
| @lib.I32Rotr => true
_ => false
}
}
///|
fn sgo_i32_call_binary_pure_suffix_depth_after(
instr : @lib.Instruction,
depth : Int32,
) -> Int32? {
// Narrow source-backed suffixes: one nontrapping pure i32 parent above the
// nontrapping i32 binary call sibling. Broader pure suffix chains still need
// separate probes and tests before admission.
match instr {
@lib.I32Eqz | @lib.I32Clz | @lib.I32Ctz | @lib.I32Popcnt =>
if depth >= 1 {
Some(depth)
} else {
None
}
_ => None
}
}
///|
fn sgo_i32_pure_suffix_tail_matches(
instrs : Array[@lib.Instruction],
start : Int32,
idx : @lib.GlobalIdx,
) -> Bool {
// One source-backed suffix may also be a constant-fed i32 equality compare.
// This is intentionally limited to eq/ne and exactly one constant operand.
if start + 2 < instrs.length() &&
sgo_const_instr(instrs[start]) != None &&
sgo_is_compare_instr(instrs[start + 1]) {
let tail = instrs[start + 2]
return sgo_if_is_read_only_to_write_set(tail, idx) ||
(
sgo_is_return_guard(tail) &&
sgo_if_return_tail_matches(instrs, start + 3, idx)
)
}
let mut depth = 1
let mut suffix_count = 0
for scan = start; scan < instrs.length(); scan = scan + 1 {
let instr = instrs[scan]
match instr {
@lib.If(_, _, _) if depth == 1 =>
return sgo_if_is_read_only_to_write_set(instr, idx) ||
(
sgo_is_return_guard(instr) &&
sgo_if_return_tail_matches(instrs, scan + 1, idx)
)
_ => {
if suffix_count >= 1 {
return false
}
match sgo_i32_call_binary_pure_suffix_depth_after(instr, depth) {
Some(next_depth) => {
suffix_count = suffix_count + 1
depth = next_depth
}
None => return false
}
}
}
}
false
}
///|
fn sgo_i32_pure_suffix_empty_if_end(
instrs : Array[@lib.Instruction],
start : Int32,
) -> Int32? {
if start + 2 < instrs.length() &&
sgo_const_instr(instrs[start]) != None &&
sgo_is_compare_instr(instrs[start + 1]) &&
sgo_instr_is_empty_void_if(instrs[start + 2]) {
return Some(start + 3)
}
let mut depth = 1
let mut suffix_count = 0
for scan = start; scan < instrs.length(); scan = scan + 1 {
let instr = instrs[scan]
if depth == 1 && sgo_instr_is_empty_void_if(instr) {
return Some(scan + 1)
}
if suffix_count >= 1 {
return None
}
match sgo_i32_call_binary_pure_suffix_depth_after(instr, depth) {
Some(next_depth) => {
suffix_count = suffix_count + 1
depth = next_depth
}
None => return None
}
}
None
}
///|
fn sgo_i32_pure_suffix_if_return_drop_end(
instrs : Array[@lib.Instruction],
start : Int32,
) -> Int32? {
if start + 4 < instrs.length() &&
sgo_const_instr(instrs[start]) != None &&
sgo_is_compare_instr(instrs[start + 1]) &&
sgo_is_return_guard(instrs[start + 2]) &&
sgo_const_instr(instrs[start + 3]) != None &&
instrs[start + 4] == @lib.Instruction::drop() {
return Some(start + 5)
}
let mut depth = 1
let mut suffix_count = 0
for scan = start; scan < instrs.length(); scan = scan + 1 {
let instr = instrs[scan]
if depth == 1 && sgo_is_return_guard(instr) {
if scan + 2 < instrs.length() &&
sgo_const_instr(instrs[scan + 1]) != None &&
instrs[scan + 2] == @lib.Instruction::drop() {
return Some(scan + 3)
}
return None
}
if suffix_count >= 1 {
return None
}
match sgo_i32_call_binary_pure_suffix_depth_after(instr, depth) {
Some(next_depth) => {
suffix_count = suffix_count + 1
depth = next_depth
}
None => return None
}
}
None
}
///|
fn sgo_pure_i32_binary_operand_independent_call_read_at(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Bool {
// Narrow FlowScanner subset: an independent zero-param/one-result call
// supplies the other operand to a nontrapping pure i32 binary operator while
// the guarded global supplies one operand. The guarded value reaches only the
// pure operator and final same-global guarded write / if-return tail; it must
// not flow into the call itself. Trapping div/rem operators stay excluded.
match instrs[pos] {
@lib.GlobalGet(get_idx) if get_idx == idx => ()
_ => return false
}
if pos + 2 < instrs.length() {
match (instrs[pos + 1], instrs[pos + 2]) {
(@lib.Call(func_idx), op) if sgo_is_nontrapping_i32_binary_flow_instr(op) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
if sgo_i32_pure_suffix_tail_matches(instrs, pos + 3, idx) {
return true
}
_ => ()
}
}
if pos > 0 && pos + 1 < instrs.length() {
match (instrs[pos - 1], instrs[pos + 1]) {
(@lib.Call(func_idx), op) if sgo_is_nontrapping_i32_binary_flow_instr(op) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
if sgo_i32_pure_suffix_tail_matches(instrs, pos + 2, idx) {
return true
}
_ => ()
}
}
// Constant-first equality comparison around the binary result. These two
// stack orders are separate from the result-first suffix above because the
// constant is already below the guarded expression when we reach the get.
if pos > 0 &&
pos + 3 < instrs.length() &&
sgo_const_instr(instrs[pos - 1]) != None {
match (instrs[pos + 1], instrs[pos + 2], instrs[pos + 3]) {
(@lib.Call(func_idx), op, compare) if sgo_is_nontrapping_i32_binary_flow_instr(
op,
) &&
sgo_is_compare_instr(compare) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
if sgo_i32_pure_suffix_tail_matches(instrs, pos + 4, idx) {
return true
}
_ => ()
}
}
if pos > 1 &&
pos + 2 < instrs.length() &&
sgo_const_instr(instrs[pos - 2]) != None {
match (instrs[pos - 1], instrs[pos + 1], instrs[pos + 2]) {
(@lib.Call(func_idx), op, compare) if sgo_is_nontrapping_i32_binary_flow_instr(
op,
) &&
sgo_is_compare_instr(compare) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
if sgo_i32_pure_suffix_tail_matches(instrs, pos + 3, idx) {
return true
}
_ => ()
}
}
false
}
///|
fn sgo_is_nontrapping_i64_binary_flow_instr(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I64Add
| @lib.I64Sub
| @lib.I64Mul
| @lib.I64And
| @lib.I64Or
| @lib.I64Xor
| @lib.I64Shl
| @lib.I64ShrS
| @lib.I64ShrU
| @lib.I64Rotl
| @lib.I64Rotr => true
_ => false
}
}
///|
fn sgo_is_i64_equality_compare(instr : @lib.Instruction) -> Bool {
instr == @lib.Instruction::i64_eq() || instr == @lib.Instruction::i64_ne()
}
///|
fn sgo_is_i64_const(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I64Const(_) => true
_ => false
}
}
///|
fn sgo_i64_pure_suffix_tail_matches(
instrs : Array[@lib.Instruction],
start : Int32,
idx : @lib.GlobalIdx,
) -> Bool {
// Narrow source-backed i64 suffixes: one i64.eqz, or one constant-fed
// i64.eq/ne. A direct tail is accepted only for the constant-first compare
// order, where the comparison instruction was already visited by the caller.
// Broader i64 chains and relational comparisons remain excluded.
if start < instrs.length() {
let tail = instrs[start]
if sgo_if_is_read_only_to_write_set(tail, idx) ||
(
sgo_is_return_guard(tail) &&
sgo_if_return_tail_matches(instrs, start + 1, idx)
) {
return true
}
}
if start + 1 < instrs.length() && instrs[start] == @lib.Instruction::i64_eqz() {
let tail = instrs[start + 1]
return sgo_if_is_read_only_to_write_set(tail, idx) ||
(
sgo_is_return_guard(tail) &&
sgo_if_return_tail_matches(instrs, start + 2, idx)
)
}
if start + 2 < instrs.length() {
match (instrs[start], instrs[start + 1]) {
(@lib.I64Const(_), compare) if sgo_is_i64_equality_compare(compare) => {
let tail = instrs[start + 2]
return sgo_if_is_read_only_to_write_set(tail, idx) ||
(
sgo_is_return_guard(tail) &&
sgo_if_return_tail_matches(instrs, start + 3, idx)
)
}
_ => ()
}
}
false
}
///|
fn sgo_i64_pure_suffix_empty_if_end(
instrs : Array[@lib.Instruction],
start : Int32,
) -> Int32? {
if start + 1 < instrs.length() &&
instrs[start] == @lib.Instruction::i64_eqz() &&
sgo_instr_is_empty_void_if(instrs[start + 1]) {
return Some(start + 2)
}
if start + 2 < instrs.length() {
match (instrs[start], instrs[start + 1]) {
(@lib.I64Const(_), compare) if sgo_is_i64_equality_compare(compare) &&
sgo_instr_is_empty_void_if(instrs[start + 2]) => return Some(start + 3)
_ => ()
}
}
None
}
///|
fn sgo_i64_pure_suffix_if_return_drop_end(
instrs : Array[@lib.Instruction],
start : Int32,
) -> Int32? {
if start + 3 < instrs.length() &&
instrs[start] == @lib.Instruction::i64_eqz() &&
sgo_is_return_guard(instrs[start + 1]) &&
sgo_const_instr(instrs[start + 2]) != None &&
instrs[start + 3] == @lib.Instruction::drop() {
return Some(start + 4)
}
if start + 4 < instrs.length() {
match (instrs[start], instrs[start + 1]) {
(@lib.I64Const(_), compare) if sgo_is_i64_equality_compare(compare) &&
sgo_is_return_guard(instrs[start + 2]) &&
sgo_const_instr(instrs[start + 3]) != None &&
instrs[start + 4] == @lib.Instruction::drop() => return Some(start + 5)
_ => ()
}
}
None
}
///|
fn sgo_pure_i64_binary_operand_independent_call_read_at(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Bool {
// Narrow FlowScanner subset: an independent zero-param/one-result call and
// the guarded i64 global feed a nontrapping pure i64 binary parent. The
// result then reaches exactly one i64.eqz or constant-fed i64.eq/ne before
// the same-global guarded write / if-return tail.
match instrs[pos] {
@lib.GlobalGet(get_idx) if get_idx == idx => ()
_ => return false
}
if pos + 2 < instrs.length() {
match (instrs[pos + 1], instrs[pos + 2]) {
(@lib.Call(func_idx), op) if sgo_is_nontrapping_i64_binary_flow_instr(op) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
if sgo_i64_pure_suffix_tail_matches(instrs, pos + 3, idx) {
return true
}
_ => ()
}
}
if pos > 0 && pos + 1 < instrs.length() {
match (instrs[pos - 1], instrs[pos + 1]) {
(@lib.Call(func_idx), op) if sgo_is_nontrapping_i64_binary_flow_instr(op) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
if sgo_i64_pure_suffix_tail_matches(instrs, pos + 2, idx) {
return true
}
_ => ()
}
}
// Constant-first equality around the binary result, for both binary operand
// orders. The comparison constant is already below the guarded expression.
if pos > 0 && pos + 3 < instrs.length() && sgo_is_i64_const(instrs[pos - 1]) {
match (instrs[pos + 1], instrs[pos + 2], instrs[pos + 3]) {
(@lib.Call(func_idx), op, compare) if sgo_is_nontrapping_i64_binary_flow_instr(
op,
) &&
sgo_is_i64_equality_compare(compare) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
if sgo_i64_pure_suffix_tail_matches(instrs, pos + 4, idx) {
return true
}
_ => ()
}
}
if pos > 1 && pos + 2 < instrs.length() && sgo_is_i64_const(instrs[pos - 2]) {
match (instrs[pos - 1], instrs[pos + 1], instrs[pos + 2]) {
(@lib.Call(func_idx), op, compare) if sgo_is_nontrapping_i64_binary_flow_instr(
op,
) &&
sgo_is_i64_equality_compare(compare) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
if sgo_i64_pure_suffix_tail_matches(instrs, pos + 3, idx) {
return true
}
_ => ()
}
}
false
}
///|
fn sgo_is_f32_binary_flow_instr(instr : @lib.Instruction) -> Bool {
match instr {
@lib.F32Add
| @lib.F32Sub
| @lib.F32Mul
| @lib.F32Div
| @lib.F32Min
| @lib.F32Max
| @lib.F32Copysign => true
_ => false
}
}
///|
fn sgo_is_f64_binary_flow_instr(instr : @lib.Instruction) -> Bool {
match instr {
@lib.F64Add
| @lib.F64Sub
| @lib.F64Mul
| @lib.F64Div
| @lib.F64Min
| @lib.F64Max
| @lib.F64Copysign => true
_ => false
}
}
///|
fn sgo_is_f32_compare_instr(instr : @lib.Instruction) -> Bool {
match instr {
@lib.F32Eq
| @lib.F32Ne
| @lib.F32Lt
| @lib.F32Gt
| @lib.F32Le
| @lib.F32Ge => true
_ => false
}
}
///|
fn sgo_is_f64_compare_instr(instr : @lib.Instruction) -> Bool {
match instr {
@lib.F64Eq
| @lib.F64Ne
| @lib.F64Lt
| @lib.F64Gt
| @lib.F64Le
| @lib.F64Ge => true
_ => false
}
}
///|
fn sgo_is_i32_flow_compare_instr(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 => true
_ => false
}
}
///|
fn sgo_is_i64_flow_compare_instr(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I64Eq
| @lib.I64Ne
| @lib.I64LtS
| @lib.I64LtU
| @lib.I64GtS
| @lib.I64GtU
| @lib.I64LeS
| @lib.I64LeU
| @lib.I64GeS
| @lib.I64GeU => true
_ => false
}
}
///|
fn sgo_scalar_const_kind(instr : @lib.Instruction) -> SgoScalarFlowKind? {
match instr {
@lib.I32Const(_) => Some(SgoFlowI32)
@lib.I64Const(_) => Some(SgoFlowI64)
@lib.F32Const(_) => Some(SgoFlowF32)
@lib.F64Const(_) => Some(SgoFlowF64)
_ => None
}
}
///|
fn sgo_scalar_same_type_unary_result(
instr : @lib.Instruction,
kind : SgoScalarFlowKind,
) -> SgoScalarFlowKind? {
match (kind, instr) {
(SgoFlowI32, @lib.I32Clz | @lib.I32Ctz | @lib.I32Popcnt) => Some(SgoFlowI32)
(SgoFlowI64, @lib.I64Clz | @lib.I64Ctz | @lib.I64Popcnt) => Some(SgoFlowI64)
(
SgoFlowF32,
@lib.F32Abs
| @lib.F32Neg
| @lib.F32Ceil
| @lib.F32Floor
| @lib.F32Trunc
| @lib.F32Nearest
| @lib.F32Sqrt,
) => Some(SgoFlowF32)
(
SgoFlowF64,
@lib.F64Abs
| @lib.F64Neg
| @lib.F64Ceil
| @lib.F64Floor
| @lib.F64Trunc
| @lib.F64Nearest
| @lib.F64Sqrt,
) => Some(SgoFlowF64)
_ => None
}
}
///|
fn sgo_scalar_unary_result(
instr : @lib.Instruction,
kind : SgoScalarFlowKind,
) -> SgoScalarFlowKind? {
match sgo_scalar_same_type_unary_result(instr, kind) {
Some(next) => return Some(next)
None => ()
}
match (kind, instr) {
(SgoFlowI32, @lib.I32Eqz) => Some(SgoFlowI32)
(SgoFlowI64, @lib.I64Eqz) => Some(SgoFlowI32)
(SgoFlowI64, @lib.I32WrapI64) => Some(SgoFlowI32)
(SgoFlowI32, @lib.I64ExtendI32S | @lib.I64ExtendI32U) => Some(SgoFlowI64)
(
SgoFlowI32,
@lib.F32ConvertI32S
| @lib.F32ConvertI32U
| @lib.F32ReinterpretI32,
) => Some(SgoFlowF32)
(SgoFlowI32, @lib.F64ConvertI32S | @lib.F64ConvertI32U) => Some(SgoFlowF64)
(SgoFlowI64, @lib.F32ConvertI64S | @lib.F32ConvertI64U) => Some(SgoFlowF32)
(
SgoFlowI64,
@lib.F64ConvertI64S
| @lib.F64ConvertI64U
| @lib.F64ReinterpretI64,
) => Some(SgoFlowF64)
(
SgoFlowF32,
@lib.I32ReinterpretF32
| @lib.I32TruncSatF32S
| @lib.I32TruncSatF32U,
) => Some(SgoFlowI32)
(SgoFlowF32, @lib.I64TruncSatF32S | @lib.I64TruncSatF32U) =>
Some(SgoFlowI64)
(SgoFlowF32, @lib.F64PromoteF32) => Some(SgoFlowF64)
(
SgoFlowF64,
@lib.I64ReinterpretF64
| @lib.I64TruncSatF64S
| @lib.I64TruncSatF64U,
) => Some(SgoFlowI64)
(SgoFlowF64, @lib.I32TruncSatF64S | @lib.I32TruncSatF64U) =>
Some(SgoFlowI32)
(SgoFlowF64, @lib.F32DemoteF64) => Some(SgoFlowF32)
_ => None
}
}
///|
fn sgo_scalar_trapping_unary_result(
instr : @lib.Instruction,
kind : SgoScalarFlowKind,
) -> SgoScalarFlowKind? {
match (kind, instr) {
(SgoFlowF32, @lib.I32TruncF32S | @lib.I32TruncF32U) => Some(SgoFlowI32)
(SgoFlowF64, @lib.I32TruncF64S | @lib.I32TruncF64U) => Some(SgoFlowI32)
(SgoFlowF32, @lib.I64TruncF32S | @lib.I64TruncF32U) => Some(SgoFlowI64)
(SgoFlowF64, @lib.I64TruncF64S | @lib.I64TruncF64U) => Some(SgoFlowI64)
_ => None
}
}
///|
fn sgo_scalar_any_unary_result(
instr : @lib.Instruction,
kind : SgoScalarFlowKind,
) -> SgoScalarFlowKind? {
match sgo_scalar_unary_result(instr, kind) {
Some(next) => Some(next)
None => sgo_scalar_trapping_unary_result(instr, kind)
}
}
///|
fn sgo_is_any_scalar_unary(instr : @lib.Instruction) -> Bool {
sgo_scalar_any_unary_result(instr, SgoFlowI32) != None ||
sgo_scalar_any_unary_result(instr, SgoFlowI64) != None ||
sgo_scalar_any_unary_result(instr, SgoFlowF32) != None ||
sgo_scalar_any_unary_result(instr, SgoFlowF64) != None
}
///|
fn sgo_scalar_load_result_kind(instr : @lib.Instruction) -> SgoScalarFlowKind? {
match instr {
@lib.I32Load(_)
| @lib.I32Load8S(_)
| @lib.I32Load8U(_)
| @lib.I32Load16S(_)
| @lib.I32Load16U(_) => Some(SgoFlowI32)
@lib.I64Load(_)
| @lib.I64Load8S(_)
| @lib.I64Load8U(_)
| @lib.I64Load16S(_)
| @lib.I64Load16U(_)
| @lib.I64Load32S(_)
| @lib.I64Load32U(_) => Some(SgoFlowI64)
@lib.F32Load(_) => Some(SgoFlowF32)
@lib.F64Load(_) => Some(SgoFlowF64)
_ => None
}
}
///|
fn sgo_scalar_pure_expr_result_kind(
instrs : Array[@lib.Instruction],
) -> SgoScalarFlowKind? {
if instrs.length() == 0 {
return None
}
let mut kind = match sgo_scalar_const_kind(instrs[0]) {
Some(kind) => kind
None => return None
}
for pos = 1; pos < instrs.length(); pos = pos + 1 {
match sgo_scalar_unary_result(instrs[pos], kind) {
Some(next_kind) => kind = next_kind
None => return None
}
}
Some(kind)
}
///|
fn sgo_scalar_result_block_effect_prefix(
body : Array[@lib.Instruction],
) -> (SgoScalarFlowKind, Int32)? {
match body {
[value, @lib.LocalSet(_), result] if sgo_local_effect_value_is_independent(
value,
) =>
match sgo_scalar_const_kind(result) {
Some(result_kind) => Some((result_kind, 2))
None => None
}
[value, @lib.LocalTee(_), @lib.Drop, result] if sgo_local_effect_value_is_independent(
value,
) =>
match sgo_scalar_const_kind(result) {
Some(result_kind) => Some((result_kind, 3))
None => None
}
[ref_value, grow_delta, @lib.TableGrow(_), @lib.Drop, result] if sgo_table_grow_arg_is_independent(
ref_value,
) &&
sgo_table_grow_arg_is_independent(grow_delta) =>
match sgo_scalar_const_kind(result) {
Some(result_kind) => Some((result_kind, 3))
None => None
}
[delta, @lib.MemoryGrow(_), @lib.Drop, result] if sgo_memory_grow_arg_is_independent(
delta,
) =>
match sgo_scalar_const_kind(result) {
Some(result_kind) => Some((result_kind, 2))
None => None
}
[left, right, @lib.I32Add, @lib.MemoryGrow(_), @lib.Drop, result] if sgo_i32_add_grow_arg_is_independent(
left,
right,
@lib.Instruction::i32_add(),
) =>
match sgo_scalar_const_kind(result) {
Some(result_kind) => Some((result_kind, 4))
None => None
}
_ => None
}
}
///|
fn sgo_scalar_result_if_kind(instr : @lib.Instruction) -> SgoScalarFlowKind? {
match instr {
@lib.If(@lib.ValTypeBlockType(value_type), then_body, Some(else_body)) =>
match sgo_scalar_flow_kind(value_type) {
Some(result_kind) =>
if sgo_scalar_pure_expr_result_kind(then_body) == Some(result_kind) &&
sgo_scalar_pure_expr_result_kind(else_body) == Some(result_kind) {
Some(result_kind)
} else {
None
}
None => None
}
_ => None
}
}
///|
fn sgo_scalar_independent_fragment_starts_at(
instrs : Array[@lib.Instruction],
pos : Int32,
) -> Bool {
if pos < 0 || pos >= instrs.length() {
return false
}
match instrs[pos] {
@lib.Call(_) | @lib.MemorySize(_) | @lib.Block(_, _) | @lib.Loop(_, _) =>
true
@lib.LocalGet(_) | @lib.I32Const(_) if pos + 1 < instrs.length() &&
sgo_scalar_result_if_kind(instrs[pos + 1]) != None => true
@lib.I32Const(_) if pos + 1 < instrs.length() =>
sgo_scalar_load_result_kind(instrs[pos + 1]) != None
_ => false
}
}
///|
fn sgo_scalar_independent_fragment_can_end_at(
instrs : Array[@lib.Instruction],
end_pos : Int32,
) -> Bool {
if end_pos <= 0 {
return false
}
match instrs[end_pos - 1] {
@lib.Call(_)
| @lib.MemorySize(_)
| @lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _) => true
@lib.LocalTee(_) => false
instr =>
sgo_scalar_load_result_kind(instr) != None ||
sgo_is_any_scalar_unary(instr)
}
}
///|
fn sgo_scalar_independent_fragment_at(
instrs : Array[@lib.Instruction],
pos : Int32,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> SgoScalarIndependentFragment? {
if pos < 0 || pos >= instrs.length() {
return None
}
match instrs[pos] {
@lib.Call(_) | @lib.MemorySize(_) if pos + 1 < instrs.length() =>
match instrs[pos + 1] {
@lib.LocalTee(_) | @lib.LocalSet(_) | @lib.GlobalSet(_) => return None
_ => ()
}
@lib.I32Const(_) if pos + 2 < instrs.length() &&
sgo_scalar_load_result_kind(instrs[pos + 1]) != None =>
match instrs[pos + 2] {
@lib.LocalTee(_) | @lib.LocalSet(_) | @lib.GlobalSet(_) => return None
_ => ()
}
_ => ()
}
match instrs[pos] {
@lib.LocalGet(_) | @lib.I32Const(_) if pos + 1 < instrs.length() =>
match sgo_scalar_result_if_kind(instrs[pos + 1]) {
Some(result_kind) =>
Some({
result_kind,
start_pos: pos,
next_pos: pos + 2,
replay_end_pos: pos,
replay_nested_block: false,
})
None =>
match instrs[pos] {
@lib.I32Const(_) =>
match sgo_scalar_load_result_kind(instrs[pos + 1]) {
Some(result_kind) =>
Some({
result_kind,
start_pos: pos,
next_pos: pos + 2,
replay_end_pos: pos + 2,
replay_nested_block: false,
})
None => None
}
_ => None
}
}
@lib.Block(@lib.ValTypeBlockType(value_type), @lib.Expr(body)) =>
match sgo_scalar_flow_kind(value_type) {
Some(result_kind) =>
match sgo_scalar_pure_expr_result_kind(body) {
Some(body_kind) if body_kind == result_kind =>
Some({
result_kind,
start_pos: pos,
next_pos: pos + 1,
replay_end_pos: pos,
replay_nested_block: false,
})
_ =>
match
sgo_scalar_independent_fragment_at(
body, 0, mod_, func_type_indices,
) {
Some(nested) => {
let mut kind = nested.result_kind
let mut scan = nested.next_pos
while scan < body.length() {
match sgo_scalar_any_unary_result(body[scan], kind) {
Some(next_kind) => {
kind = next_kind
scan = scan + 1
}
None => break
}
}
if scan == body.length() && kind == result_kind {
Some({
result_kind,
start_pos: pos,
next_pos: pos + 1,
replay_end_pos: pos,
replay_nested_block: true,
})
} else {
None
}
}
None =>
match sgo_scalar_result_block_effect_prefix(body) {
Some((body_kind, _)) if body_kind == result_kind =>
Some({
result_kind,
start_pos: pos,
next_pos: pos + 1,
replay_end_pos: pos,
replay_nested_block: true,
})
_ if !sgo_instrs_may_reference_global(body) =>
Some({
result_kind,
start_pos: pos,
next_pos: pos + 1,
replay_end_pos: pos + 1,
replay_nested_block: false,
})
_ => None
}
}
}
None => None
}
@lib.Loop(@lib.ValTypeBlockType(value_type), @lib.Expr(body)) =>
match sgo_scalar_flow_kind(value_type) {
Some(result_kind) if !sgo_instrs_may_reference_global(body) =>
Some({
result_kind,
start_pos: pos,
next_pos: pos + 1,
replay_end_pos: pos + 1,
replay_nested_block: false,
})
_ => None
}
@lib.Call(func_idx) =>
match
sgo_call_no_params_scalar_result_kind(mod_, func_type_indices, func_idx) {
Some(SgoFlowI32) if pos + 1 < instrs.length() =>
match sgo_scalar_result_if_kind(instrs[pos + 1]) {
Some(result_kind) =>
Some({
result_kind,
start_pos: pos,
next_pos: pos + 2,
replay_end_pos: pos + 1,
replay_nested_block: false,
})
None =>
Some({
result_kind: SgoFlowI32,
start_pos: pos,
next_pos: pos + 1,
replay_end_pos: pos + 1,
replay_nested_block: false,
})
}
Some(result_kind) =>
Some({
result_kind,
start_pos: pos,
next_pos: pos + 1,
replay_end_pos: pos + 1,
replay_nested_block: false,
})
None => None
}
@lib.MemorySize(_) =>
Some({
result_kind: SgoFlowI32,
start_pos: pos,
next_pos: pos + 1,
replay_end_pos: pos,
replay_nested_block: false,
})
_ => None
}
}
///|
fn sgo_scalar_fragment_cleanup_replay(
instrs : Array[@lib.Instruction],
fragment : SgoScalarIndependentFragment,
unary_end : Int32,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Array[@lib.Instruction] {
let replay : Array[@lib.Instruction] = []
if fragment.replay_nested_block {
match instrs[fragment.start_pos] {
@lib.Block(_, @lib.Expr(body)) =>
match
sgo_scalar_independent_fragment_at(body, 0, mod_, func_type_indices) {
Some(nested) =>
replay.append(
sgo_scalar_fragment_cleanup_replay(
body,
nested,
body.length(),
mod_,
func_type_indices,
),
)
None =>
match sgo_scalar_result_block_effect_prefix(body) {
Some((_, replay_end)) =>
match body {
[value, @lib.LocalSet(local_idx), _]
| [value, @lib.LocalTee(local_idx), @lib.Drop, _] => {
replay.push(value)
replay.push(@lib.Instruction::local_tee(local_idx))
}
_ =>
for replay_pos = 0
replay_pos < replay_end
replay_pos = replay_pos + 1 {
replay.push(body[replay_pos])
}
}
None => ()
}
}
_ => ()
}
} else {
for replay_pos = fragment.start_pos
replay_pos < fragment.replay_end_pos
replay_pos = replay_pos + 1 {
replay.push(instrs[replay_pos])
}
}
let mut kind = fragment.result_kind
for scan = fragment.next_pos; scan < unary_end; scan = scan + 1 {
match sgo_scalar_unary_result(instrs[scan], kind) {
Some(next_kind) => kind = next_kind
None =>
match sgo_scalar_trapping_unary_result(instrs[scan], kind) {
Some(next_kind) => {
replay.push(instrs[scan])
kind = next_kind
}
None => ()
}
}
}
replay
}
///|
fn sgo_scalar_independent_fragment_before(
instrs : Array[@lib.Instruction],
end_pos : Int32,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> (Int32, SgoScalarIndependentFragment)? {
if end_pos > 0 {
match
sgo_scalar_independent_fragment_at(
instrs,
end_pos - 1,
mod_,
func_type_indices,
) {
Some(fragment) if fragment.next_pos == end_pos =>
return Some((end_pos - 1, fragment))
_ => ()
}
}
if end_pos > 1 {
match
sgo_scalar_independent_fragment_at(
instrs,
end_pos - 2,
mod_,
func_type_indices,
) {
Some(fragment) if fragment.next_pos == end_pos =>
return Some((end_pos - 2, fragment))
_ => ()
}
}
None
}
///|
fn sgo_scalar_binary_result(
instr : @lib.Instruction,
kind : SgoScalarFlowKind,
) -> SgoScalarFlowKind? {
match kind {
SgoFlowI32 =>
if sgo_is_nontrapping_i32_binary_flow_instr(instr) {
Some(SgoFlowI32)
} else if sgo_is_i32_flow_compare_instr(instr) {
Some(SgoFlowI32)
} else {
None
}
SgoFlowI64 =>
if sgo_is_nontrapping_i64_binary_flow_instr(instr) {
Some(SgoFlowI64)
} else if sgo_is_i64_flow_compare_instr(instr) {
Some(SgoFlowI32)
} else {
None
}
SgoFlowF32 =>
if sgo_is_f32_binary_flow_instr(instr) {
Some(SgoFlowF32)
} else if sgo_is_f32_compare_instr(instr) {
Some(SgoFlowI32)
} else {
None
}
SgoFlowF64 =>
if sgo_is_f64_binary_flow_instr(instr) {
Some(SgoFlowF64)
} else if sgo_is_f64_compare_instr(instr) {
Some(SgoFlowI32)
} else {
None
}
}
}
///|
fn sgo_float_const_matches_binary(
const_instr : @lib.Instruction,
op : @lib.Instruction,
) -> Bool {
match const_instr {
@lib.F32Const(_) => sgo_is_f32_binary_flow_instr(op)
@lib.F64Const(_) => sgo_is_f64_binary_flow_instr(op)
_ => false
}
}
///|
fn sgo_float_binary_compare_types_match(
op : @lib.Instruction,
compare_const : @lib.Instruction,
compare : @lib.Instruction,
) -> Bool {
match compare_const {
@lib.F32Const(_) =>
sgo_is_f32_binary_flow_instr(op) && sgo_is_f32_compare_instr(compare)
@lib.F64Const(_) =>
sgo_is_f64_binary_flow_instr(op) && sgo_is_f64_compare_instr(compare)
_ => false
}
}
///|
fn sgo_scalar_flow_guard_tail_matches(
instrs : Array[@lib.Instruction],
tail_pos : Int32,
idx : @lib.GlobalIdx,
kind : SgoScalarFlowKind,
) -> Bool {
if kind != SgoFlowI32 || tail_pos >= instrs.length() {
return false
}
let tail = instrs[tail_pos]
sgo_if_is_read_only_to_write_set(tail, idx) ||
(
sgo_is_return_guard(tail) &&
sgo_if_return_tail_matches(instrs, tail_pos + 1, idx)
)
}
///|
fn sgo_scalar_const_prefix_start(
instrs : Array[@lib.Instruction],
end : Int32,
) -> Int32 {
let mut start = end
while start > 0 && sgo_scalar_const_kind(instrs[start - 1]) != None {
start = start - 1
}
start
}
///|
fn sgo_scalar_pure_suffix_with_prefix_tail_matches(
instrs : Array[@lib.Instruction],
start : Int32,
idx : @lib.GlobalIdx,
initial_kind : SgoScalarFlowKind,
prefix_start : Int32,
prefix_end : Int32,
) -> Bool {
let mut scan = start
let mut kind = initial_kind
let mut prefix_scan = prefix_end
while scan < instrs.length() {
if prefix_scan == prefix_start &&
sgo_scalar_flow_guard_tail_matches(instrs, scan, idx, kind) {
return true
}
match sgo_scalar_unary_result(instrs[scan], kind) {
Some(next_kind) => {
kind = next_kind
scan = scan + 1
continue
}
None => ()
}
if scan + 1 < instrs.length() &&
sgo_scalar_const_kind(instrs[scan]) == Some(kind) {
match sgo_scalar_binary_result(instrs[scan + 1], kind) {
Some(next_kind) => {
kind = next_kind
scan = scan + 2
continue
}
None => ()
}
}
if prefix_scan > prefix_start &&
sgo_scalar_const_kind(instrs[prefix_scan - 1]) == Some(kind) {
match sgo_scalar_binary_result(instrs[scan], kind) {
Some(next_kind) => {
prefix_scan = prefix_scan - 1
kind = next_kind
scan = scan + 1
continue
}
None => ()
}
}
return false
}
false
}
///|
fn sgo_scalar_pure_suffix_cleanup_end(
instrs : Array[@lib.Instruction],
start : Int32,
initial_kind : SgoScalarFlowKind,
if_return : Bool,
prefix_start : Int32,
prefix_end : Int32,
) -> Int32? {
let mut scan = start
let mut kind = initial_kind
let mut prefix_scan = prefix_end
while scan < instrs.length() {
if prefix_scan == prefix_start && kind == SgoFlowI32 {
if !if_return && sgo_instr_is_empty_void_if(instrs[scan]) {
return Some(scan + 1)
}
if if_return &&
sgo_is_return_guard(instrs[scan]) &&
scan + 2 < instrs.length() &&
sgo_const_instr(instrs[scan + 1]) != None &&
instrs[scan + 2] == @lib.Instruction::drop() {
return Some(scan + 3)
}
}
match sgo_scalar_unary_result(instrs[scan], kind) {
Some(next_kind) => {
kind = next_kind
scan = scan + 1
continue
}
None => ()
}
if scan + 1 < instrs.length() &&
sgo_scalar_const_kind(instrs[scan]) == Some(kind) {
match sgo_scalar_binary_result(instrs[scan + 1], kind) {
Some(next_kind) => {
kind = next_kind
scan = scan + 2
continue
}
None => ()
}
}
if prefix_scan > prefix_start &&
sgo_scalar_const_kind(instrs[prefix_scan - 1]) == Some(kind) {
match sgo_scalar_binary_result(instrs[scan], kind) {
Some(next_kind) => {
prefix_scan = prefix_scan - 1
kind = next_kind
scan = scan + 1
continue
}
None => ()
}
}
return None
}
None
}
///|
fn sgo_scalar_call_parent_chain_read_at(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Bool {
match instrs[pos] {
@lib.GlobalGet(get_idx) if get_idx == idx => ()
_ => return false
}
let direct_candidate = sgo_scalar_independent_fragment_starts_at(
instrs,
pos + 1,
)
let reverse_candidate = sgo_scalar_independent_fragment_can_end_at(
instrs, pos,
)
if !direct_candidate && !reverse_candidate {
return false
}
// Bounded straight-line FlowScanner subset: an independent scalar producer
// (zero-param call, memory.size, or constant-address scalar load) and the
// guarded global meet at a nontrapping scalar binary/compare parent. Unary
// instructions on the independent producer may be removable or trap-capable;
// they cannot depend on the guarded value and are replayed by cleanup when
// needed. Instructions after the first parent remain pure because the guarded
// value flows through them.
let direct_prefix_start = sgo_scalar_const_prefix_start(instrs, pos)
match
sgo_scalar_independent_fragment_at(instrs, pos + 1, mod_, func_type_indices) {
Some(fragment) => {
let mut scan = fragment.next_pos
let mut parent_input_kind = fragment.result_kind
while scan < instrs.length() {
match sgo_scalar_any_unary_result(instrs[scan], parent_input_kind) {
Some(next_kind) => {
parent_input_kind = next_kind
scan = scan + 1
}
None => break
}
}
if scan < instrs.length() {
match sgo_scalar_binary_result(instrs[scan], parent_input_kind) {
Some(result_kind) =>
if sgo_scalar_pure_suffix_with_prefix_tail_matches(
instrs,
scan + 1,
idx,
result_kind,
direct_prefix_start,
pos,
) {
return true
}
None => ()
}
}
}
None => ()
}
let mut fragment_end = pos
while fragment_end > 0 && sgo_is_any_scalar_unary(instrs[fragment_end - 1]) {
fragment_end = fragment_end - 1
}
match
sgo_scalar_independent_fragment_before(
instrs, fragment_end, mod_, func_type_indices,
) {
Some((fragment_pos, fragment)) => {
let mut scan = fragment.next_pos
let mut parent_input_kind = fragment.result_kind
while scan < pos {
match sgo_scalar_any_unary_result(instrs[scan], parent_input_kind) {
Some(next_kind) => {
parent_input_kind = next_kind
scan = scan + 1
}
None => break
}
}
if scan == pos && pos + 1 < instrs.length() {
match sgo_scalar_binary_result(instrs[pos + 1], parent_input_kind) {
Some(result_kind) => {
let reverse_prefix_start = sgo_scalar_const_prefix_start(
instrs, fragment_pos,
)
if sgo_scalar_pure_suffix_with_prefix_tail_matches(
instrs,
pos + 2,
idx,
result_kind,
reverse_prefix_start,
fragment_pos,
) {
return true
}
}
None => ()
}
}
}
None => ()
}
false
}
///|
fn sgo_scalar_call_parent_cleanup_prefix(
instrs : Array[@lib.Instruction],
pos : Int32,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> (Array[@lib.Instruction], SgoScalarFlowKind, Int32, Int32, Int32)? {
if pos >= instrs.length() {
return None
}
let mut leading_const_end = pos
while leading_const_end < instrs.length() &&
sgo_scalar_const_kind(instrs[leading_const_end]) != None {
leading_const_end = leading_const_end + 1
}
for producer_pos = pos
producer_pos <= leading_const_end
producer_pos = producer_pos + 1 {
match
sgo_scalar_independent_fragment_at(
instrs, producer_pos, mod_, func_type_indices,
) {
Some(fragment) => {
if fragment.next_pos >= instrs.length() {
continue
}
match instrs[fragment.next_pos] {
@lib.LocalTee(_) | @lib.LocalSet(_) | @lib.GlobalSet(_) => continue
_ => ()
}
let mut parent_input_kind = fragment.result_kind
let mut scan = fragment.next_pos
while scan < instrs.length() {
match sgo_scalar_unary_result(instrs[scan], parent_input_kind) {
Some(next_kind) => {
parent_input_kind = next_kind
scan = scan + 1
continue
}
None => ()
}
match
sgo_scalar_trapping_unary_result(instrs[scan], parent_input_kind) {
Some(next_kind) => {
parent_input_kind = next_kind
scan = scan + 1
continue
}
None => break
}
}
// Producer-first order: optional prefix constants, producer fragment,
// replaced global constant, first parent.
if scan + 1 < instrs.length() &&
sgo_scalar_const_kind(instrs[scan]) == Some(parent_input_kind) {
match sgo_scalar_binary_result(instrs[scan + 1], parent_input_kind) {
Some(result_kind) => {
let replay = sgo_scalar_fragment_cleanup_replay(
instrs, fragment, scan, mod_, func_type_indices,
)
return Some((replay, result_kind, scan + 2, pos, producer_pos))
}
None => ()
}
}
// Global-first order: optional prefix constants, replaced global
// constant, producer fragment, first parent.
if producer_pos > pos &&
sgo_scalar_const_kind(instrs[producer_pos - 1]) ==
Some(parent_input_kind) &&
scan < instrs.length() {
match sgo_scalar_binary_result(instrs[scan], parent_input_kind) {
Some(result_kind) => {
let replay = sgo_scalar_fragment_cleanup_replay(
instrs, fragment, scan, mod_, func_type_indices,
)
return Some(
(replay, result_kind, scan + 1, pos, producer_pos - 1),
)
}
None => ()
}
}
}
None => ()
}
}
None
}
///|
fn sgo_float_guard_tail_matches(
instrs : Array[@lib.Instruction],
tail_pos : Int32,
idx : @lib.GlobalIdx,
) -> Bool {
if tail_pos >= instrs.length() {
return false
}
let tail = instrs[tail_pos]
sgo_if_is_read_only_to_write_set(tail, idx) ||
(
sgo_is_return_guard(tail) &&
sgo_if_return_tail_matches(instrs, tail_pos + 1, idx)
)
}
///|
fn sgo_float_result_first_compare_tail_matches(
instrs : Array[@lib.Instruction],
start : Int32,
idx : @lib.GlobalIdx,
op : @lib.Instruction,
) -> Bool {
if start + 2 >= instrs.length() {
return false
}
sgo_float_binary_compare_types_match(op, instrs[start], instrs[start + 1]) &&
sgo_float_guard_tail_matches(instrs, start + 2, idx)
}
///|
fn sgo_float_binary_operand_independent_call_read_at(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Bool {
// Narrow FlowScanner subset: an independent zero-param/one-result float call
// and the guarded same-typed float global feed one IEEE float binary parent.
// The result then reaches exactly one same-typed float comparison with a
// constant before the guarded write / if-return tail. IEEE div, min/max,
// copysign, and NaN-sensitive comparisons are nontrapping and admitted.
match instrs[pos] {
@lib.GlobalGet(get_idx) if get_idx == idx => ()
_ => return false
}
// Result-first comparison constant, with both binary operand orders.
if pos + 2 < instrs.length() {
match (instrs[pos + 1], instrs[pos + 2]) {
(@lib.Call(func_idx), op) if (
sgo_is_f32_binary_flow_instr(op) || sgo_is_f64_binary_flow_instr(op)
) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_float_result_first_compare_tail_matches(instrs, pos + 3, idx, op) =>
return true
_ => ()
}
}
if pos > 0 && pos + 1 < instrs.length() {
match (instrs[pos - 1], instrs[pos + 1]) {
(@lib.Call(func_idx), op) if (
sgo_is_f32_binary_flow_instr(op) || sgo_is_f64_binary_flow_instr(op)
) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_float_result_first_compare_tail_matches(instrs, pos + 2, idx, op) =>
return true
_ => ()
}
}
// Constant-first comparison, with both binary operand orders.
if pos > 0 && pos + 3 < instrs.length() {
match (instrs[pos - 1], instrs[pos + 1], instrs[pos + 2], instrs[pos + 3]) {
(compare_const, @lib.Call(func_idx), op, compare) if sgo_float_binary_compare_types_match(
op, compare_const, compare,
) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_float_guard_tail_matches(instrs, pos + 4, idx) => return true
_ => ()
}
}
if pos > 1 && pos + 2 < instrs.length() {
match (instrs[pos - 2], instrs[pos - 1], instrs[pos + 1], instrs[pos + 2]) {
(compare_const, @lib.Call(func_idx), op, compare) if sgo_float_binary_compare_types_match(
op, compare_const, compare,
) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_float_guard_tail_matches(instrs, pos + 3, idx) => return true
_ => ()
}
}
false
}
///|
fn sgo_float_call_binary_compare_prefix_func(
instrs : Array[@lib.Instruction],
pos : Int32,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> @lib.FuncIdx? {
if pos + 4 >= instrs.length() {
return None
}
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
) {
(value_const, @lib.Call(func_idx), op, compare_const, compare) if sgo_float_const_matches_binary(
value_const, op,
) &&
sgo_float_binary_compare_types_match(op, compare_const, compare) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
Some(func_idx)
(@lib.Call(func_idx), value_const, op, compare_const, compare) if sgo_float_const_matches_binary(
value_const, op,
) &&
sgo_float_binary_compare_types_match(op, compare_const, compare) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
Some(func_idx)
(compare_const, value_const, @lib.Call(func_idx), op, compare) if sgo_float_const_matches_binary(
value_const, op,
) &&
sgo_float_binary_compare_types_match(op, compare_const, compare) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
Some(func_idx)
(compare_const, @lib.Call(func_idx), value_const, op, compare) if sgo_float_const_matches_binary(
value_const, op,
) &&
sgo_float_binary_compare_types_match(op, compare_const, compare) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
Some(func_idx)
_ => None
}
}
///|
fn sgo_memory_grow_arg_is_independent(instr : @lib.Instruction) -> Bool {
match instr {
@lib.LocalGet(_) => true
_ => sgo_const_instr(instr) != None
}
}
///|
fn sgo_i32_add_grow_arg_is_independent(
left : @lib.Instruction,
right : @lib.Instruction,
op : @lib.Instruction,
) -> Bool {
sgo_memory_grow_arg_is_independent(left) &&
sgo_memory_grow_arg_is_independent(right) &&
op == @lib.Instruction::i32_add()
}
///|
fn sgo_compare_operand_independent_memory_read_at(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
) -> Bool {
// Narrow FlowScanner subset: an independent memory.size or constant-or-local.get
// memory.grow supplies one compare operand while the guarded global supplies
// the other. A memory.grow delta must be independent so the guarded value
// cannot affect the grow.
match instrs[pos] {
@lib.GlobalGet(get_idx) if get_idx == idx => ()
_ => return false
}
if pos + 3 < instrs.length() {
match (instrs[pos + 1], instrs[pos + 2], instrs[pos + 3]) {
(@lib.MemorySize(_), compare, if_instr) if sgo_is_compare_instr(compare) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 4, idx)
) {
return true
}
_ => ()
}
}
if pos + 4 < instrs.length() {
match (instrs[pos + 1], instrs[pos + 2], instrs[pos + 3], instrs[pos + 4]) {
(grow_delta, @lib.MemoryGrow(_), compare, if_instr) if sgo_memory_grow_arg_is_independent(
grow_delta,
) &&
sgo_is_compare_instr(compare) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 5, idx)
) {
return true
}
_ => ()
}
}
if pos + 6 < instrs.length() {
match
(
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
) {
(grow_left, grow_right, grow_op, @lib.MemoryGrow(_), compare, if_instr) if sgo_i32_add_grow_arg_is_independent(
grow_left, grow_right, grow_op,
) &&
sgo_is_compare_instr(compare) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 7, idx)
) {
return true
}
_ => ()
}
}
if pos > 0 && pos + 2 < instrs.length() {
match (instrs[pos - 1], instrs[pos + 1], instrs[pos + 2]) {
(@lib.MemorySize(_), compare, if_instr) if sgo_is_compare_instr(compare) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 3, idx)
) {
return true
}
_ => ()
}
}
if pos > 1 && pos + 2 < instrs.length() {
match (instrs[pos - 2], instrs[pos - 1], instrs[pos + 1], instrs[pos + 2]) {
(grow_delta, @lib.MemoryGrow(_), compare, if_instr) if sgo_memory_grow_arg_is_independent(
grow_delta,
) &&
sgo_is_compare_instr(compare) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 3, idx)
) {
return true
}
_ => ()
}
}
if pos > 3 && pos + 2 < instrs.length() {
match
(
instrs[pos - 4],
instrs[pos - 3],
instrs[pos - 2],
instrs[pos - 1],
instrs[pos + 1],
instrs[pos + 2],
) {
(grow_left, grow_right, grow_op, @lib.MemoryGrow(_), compare, if_instr) if sgo_i32_add_grow_arg_is_independent(
grow_left, grow_right, grow_op,
) &&
sgo_is_compare_instr(compare) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 3, idx)
) {
return true
}
_ => ()
}
}
false
}
///|
fn sgo_table_grow_arg_is_independent(instr : @lib.Instruction) -> Bool {
match instr {
@lib.LocalGet(_) => true
_ => sgo_const_instr(instr) != None
}
}
///|
fn sgo_compare_operand_independent_table_read_at(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
) -> Bool {
// Narrow FlowScanner subset: an independent table.size or constant-or-local.get
// table.grow supplies one compare operand while the guarded global supplies
// the other. A table.grow ref and delta must be independent so the guarded
// value cannot affect the grow.
match instrs[pos] {
@lib.GlobalGet(get_idx) if get_idx == idx => ()
_ => return false
}
if pos + 3 < instrs.length() {
match (instrs[pos + 1], instrs[pos + 2], instrs[pos + 3]) {
(@lib.TableSize(_), compare, if_instr) if sgo_is_compare_instr(compare) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 4, idx)
) {
return true
}
_ => ()
}
}
if pos + 5 < instrs.length() {
match
(
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
) {
(ref_value, grow_delta, @lib.TableGrow(_), compare, if_instr) if sgo_table_grow_arg_is_independent(
ref_value,
) &&
sgo_table_grow_arg_is_independent(grow_delta) &&
sgo_is_compare_instr(compare) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 6, idx)
) {
return true
}
_ => ()
}
}
if pos + 7 < instrs.length() {
match
(
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
) {
(
ref_value,
grow_left,
grow_right,
grow_op,
@lib.TableGrow(_),
compare,
if_instr,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_is_compare_instr(compare) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 8, idx)
) {
return true
}
_ => ()
}
}
if pos > 0 && pos + 2 < instrs.length() {
match (instrs[pos - 1], instrs[pos + 1], instrs[pos + 2]) {
(@lib.TableSize(_), compare, if_instr) if sgo_is_compare_instr(compare) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 3, idx)
) {
return true
}
_ => ()
}
}
if pos > 2 && pos + 2 < instrs.length() {
match
(
instrs[pos - 3],
instrs[pos - 2],
instrs[pos - 1],
instrs[pos + 1],
instrs[pos + 2],
) {
(ref_value, grow_delta, @lib.TableGrow(_), compare, if_instr) if sgo_table_grow_arg_is_independent(
ref_value,
) &&
sgo_table_grow_arg_is_independent(grow_delta) &&
sgo_is_compare_instr(compare) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 3, idx)
) {
return true
}
_ => ()
}
}
if pos > 4 && pos + 2 < instrs.length() {
match
(
instrs[pos - 5],
instrs[pos - 4],
instrs[pos - 3],
instrs[pos - 2],
instrs[pos - 1],
instrs[pos + 1],
instrs[pos + 2],
) {
(
ref_value,
grow_left,
grow_right,
grow_op,
@lib.TableGrow(_),
compare,
if_instr,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_is_compare_instr(compare) =>
if sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 3, idx)
) {
return true
}
_ => ()
}
}
false
}
///|
fn sgo_select_operand_independent_memory_read_at(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
) -> Bool {
// Narrow FlowScanner subset: the guarded global is a select operand while an
// independent memory query/grow supplies the other operand. A memory.grow is
// accepted when its delta is a constant or local.get independent of the
// guarded global value.
match instrs[pos] {
@lib.GlobalGet(get_idx) if get_idx == idx => ()
_ => return false
}
if pos + 4 < instrs.length() {
match (instrs[pos + 1], instrs[pos + 2], instrs[pos + 3], instrs[pos + 4]) {
(@lib.MemorySize(_), const_instr, @lib.Select(_), if_instr) =>
if sgo_const_instr(const_instr) != None &&
(
sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 5, idx)
)
) {
return true
}
_ => ()
}
}
if pos + 5 < instrs.length() {
match
(
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
) {
(grow_delta, @lib.MemoryGrow(_), const_instr, @lib.Select(_), if_instr) =>
if sgo_memory_grow_arg_is_independent(grow_delta) &&
sgo_const_instr(const_instr) != None &&
(
sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 6, idx)
)
) {
return true
}
_ => ()
}
}
if pos + 7 < instrs.length() {
match
(
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
) {
(
grow_left,
grow_right,
grow_op,
@lib.MemoryGrow(_),
const_instr,
@lib.Select(_),
if_instr,
) =>
if sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_const_instr(const_instr) != None &&
(
sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 8, idx)
)
) {
return true
}
_ => ()
}
}
if pos > 3 && pos + 3 < instrs.length() {
match
(
instrs[pos - 4],
instrs[pos - 3],
instrs[pos - 2],
instrs[pos - 1],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
) {
(
grow_left,
grow_right,
grow_op,
@lib.MemoryGrow(_),
const_instr,
@lib.Select(_),
if_instr,
) =>
if sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_const_instr(const_instr) != None &&
(
sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 4, idx)
)
) {
return true
}
_ => ()
}
}
false
}
///|
fn sgo_select_operand_independent_table_read_at(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
) -> Bool {
// Narrow FlowScanner subset: the guarded global is a select operand while an
// independent table.size or constant-or-local.get table.grow supplies the other
// operand. A table.grow ref and delta must be independent so the guarded value
// cannot affect the table growth.
match instrs[pos] {
@lib.GlobalGet(get_idx) if get_idx == idx => ()
_ => return false
}
if pos == 0 && pos + 4 < instrs.length() {
match (instrs[pos + 1], instrs[pos + 2], instrs[pos + 3], instrs[pos + 4]) {
(@lib.TableSize(_), const_instr, @lib.Select(_), if_instr) =>
if sgo_const_instr(const_instr) != None &&
(
sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 5, idx)
)
) {
return true
}
_ => ()
}
}
if pos == 0 && pos + 6 < instrs.length() {
match
(
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
) {
(
ref_value,
grow_delta,
@lib.TableGrow(_),
const_instr,
@lib.Select(_),
if_instr,
) =>
if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_table_grow_arg_is_independent(grow_delta) &&
sgo_const_instr(const_instr) != None &&
(
sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 7, idx)
)
) {
return true
}
_ => ()
}
}
if pos == 0 && pos + 8 < instrs.length() {
match
(
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
instrs[pos + 8],
) {
(
ref_value,
grow_left,
grow_right,
grow_op,
@lib.TableGrow(_),
const_instr,
@lib.Select(_),
if_instr,
) =>
if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_const_instr(const_instr) != None &&
(
sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 9, idx)
)
) {
return true
}
_ => ()
}
}
if pos > 0 && pos + 3 < instrs.length() {
match (instrs[pos - 1], instrs[pos + 1], instrs[pos + 2], instrs[pos + 3]) {
(@lib.TableSize(_), const_instr, @lib.Select(_), if_instr) =>
if sgo_const_instr(const_instr) != None &&
(
sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 4, idx)
)
) {
return true
}
_ => ()
}
}
if pos > 2 && pos + 3 < instrs.length() {
match
(
instrs[pos - 3],
instrs[pos - 2],
instrs[pos - 1],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
) {
(
ref_value,
grow_delta,
@lib.TableGrow(_),
const_instr,
@lib.Select(_),
if_instr,
) =>
if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_table_grow_arg_is_independent(grow_delta) &&
sgo_const_instr(const_instr) != None &&
(
sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 4, idx)
)
) {
return true
}
_ => ()
}
}
if pos > 4 && pos + 3 < instrs.length() {
match
(
instrs[pos - 5],
instrs[pos - 4],
instrs[pos - 3],
instrs[pos - 2],
instrs[pos - 1],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
) {
(
ref_value,
grow_left,
grow_right,
grow_op,
@lib.TableGrow(_),
const_instr,
@lib.Select(_),
if_instr,
) =>
if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_const_instr(const_instr) != None &&
(
sgo_if_is_read_only_to_write_set(if_instr, idx) ||
(
sgo_is_return_guard(if_instr) &&
sgo_if_return_tail_matches(instrs, pos + 4, idx)
)
) {
return true
}
_ => ()
}
}
false
}
///|
fn sgo_is_i32_pure_condition_instr(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I32Clz
| @lib.I32Ctz
| @lib.I32Popcnt
| @lib.I32Eqz
| @lib.I32Eq
| @lib.I32Ne
| @lib.I32Add
| @lib.I32Sub
| @lib.I32Mul
| @lib.I32And
| @lib.I32Or
| @lib.I32Xor
| @lib.I32Shl
| @lib.I32ShrS
| @lib.I32ShrU
| @lib.I32Rotl
| @lib.I32Rotr
| @lib.I32LtS
| @lib.I32LtU
| @lib.I32GtS
| @lib.I32GtU
| @lib.I32LeS
| @lib.I32LeU
| @lib.I32GeS
| @lib.I32GeU => true
_ => false
}
}
///|
fn sgo_is_i64_pure_condition_instr(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I64Clz
| @lib.I64Ctz
| @lib.I64Popcnt
| @lib.I64Eqz
| @lib.I64Eq
| @lib.I64Ne
| @lib.I64LtS
| @lib.I64LtU
| @lib.I64GtS
| @lib.I64GtU
| @lib.I64LeS
| @lib.I64LeU
| @lib.I64GeS
| @lib.I64GeU
| @lib.I64Add
| @lib.I64Sub
| @lib.I64Mul
| @lib.I64And
| @lib.I64Or
| @lib.I64Xor
| @lib.I64Shl
| @lib.I64ShrS
| @lib.I64ShrU
| @lib.I64Rotl
| @lib.I64Rotr => true
_ => false
}
}
///|
fn sgo_is_float_compare_condition_instr(instr : @lib.Instruction) -> Bool {
match instr {
@lib.F32Eq
| @lib.F32Ne
| @lib.F32Lt
| @lib.F32Gt
| @lib.F32Le
| @lib.F32Ge
| @lib.F64Eq
| @lib.F64Ne
| @lib.F64Lt
| @lib.F64Gt
| @lib.F64Le
| @lib.F64Ge => true
_ => false
}
}
///|
fn sgo_is_numeric_conversion_condition_instr(instr : @lib.Instruction) -> Bool {
match instr {
@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 => true
_ => false
}
}
///|
fn sgo_is_ref_condition_instr(instr : @lib.Instruction) -> Bool {
match instr {
@lib.RefIsNull
| @lib.RefEq
| @lib.RefTest(_, _, _)
| @lib.RefTestDesc(_, _) => true
_ => false
}
}
///|
fn sgo_is_float_value_condition_instr(instr : @lib.Instruction) -> Bool {
match instr {
@lib.F32Abs
| @lib.F32Neg
| @lib.F32Ceil
| @lib.F32Floor
| @lib.F32Trunc
| @lib.F32Nearest
| @lib.F32Sqrt
| @lib.F32Add
| @lib.F32Sub
| @lib.F32Mul
| @lib.F32Div
| @lib.F32Min
| @lib.F32Max
| @lib.F32Copysign
| @lib.F64Abs
| @lib.F64Neg
| @lib.F64Ceil
| @lib.F64Floor
| @lib.F64Trunc
| @lib.F64Nearest
| @lib.F64Sqrt
| @lib.F64Add
| @lib.F64Sub
| @lib.F64Mul
| @lib.F64Div
| @lib.F64Min
| @lib.F64Max
| @lib.F64Copysign => true
_ => false
}
}
///|
fn sgo_is_read_only_to_write_pure_condition_instr(
instr : @lib.Instruction,
) -> Bool {
match instr {
@lib.LocalGet(_) => true
_ =>
sgo_const_instr(instr) != None ||
sgo_is_i32_pure_condition_instr(instr) ||
sgo_is_i64_pure_condition_instr(instr) ||
sgo_is_float_compare_condition_instr(instr) ||
sgo_is_float_value_condition_instr(instr) ||
sgo_is_numeric_conversion_condition_instr(instr) ||
sgo_is_ref_condition_instr(instr)
}
}
///|
fn sgo_read_only_to_write_pure_condition_read_at(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
) -> Bool {
for scan = pos + 1; scan < instrs.length(); scan = scan + 1 {
let instr = instrs[scan]
match instr {
@lib.If(_, _, _) => return sgo_if_is_read_only_to_write_set(instr, idx)
@lib.Select(_) if scan + 1 < instrs.length() => {
for after_select = scan + 1
after_select < instrs.length()
after_select = after_select + 1 {
let next = instrs[after_select]
match next {
@lib.If(_, _, _) =>
return sgo_if_is_read_only_to_write_set(next, idx)
_ =>
if !sgo_is_read_only_to_write_pure_condition_instr(next) {
return false
}
}
}
return false
}
_ =>
if !sgo_is_read_only_to_write_pure_condition_instr(instr) {
return false
}
}
}
false
}
///|
fn sgo_read_only_to_write_if_return_value_read_at(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
) -> Bool {
for scan = pos + 1; scan < instrs.length(); scan = scan + 1 {
let instr = instrs[scan]
if sgo_is_return_guard(instr) {
return sgo_if_return_tail_matches(instrs, scan + 1, idx)
}
match instr {
@lib.Select(_) if scan + 1 < instrs.length() => {
for after_select = scan + 1
after_select < instrs.length()
after_select = after_select + 1 {
let next = instrs[after_select]
if sgo_is_return_guard(next) {
return sgo_if_return_tail_matches(instrs, after_select + 1, idx)
}
if !sgo_is_read_only_to_write_pure_condition_instr(next) {
return false
}
}
return false
}
_ =>
if !sgo_is_read_only_to_write_pure_condition_instr(instr) {
return false
}
}
}
false
}
///|
fn sgo_is_return_guard(instr : @lib.Instruction) -> Bool {
match instr {
@lib.If(_, [@lib.Return], None) => true
_ => false
}
}
///|
fn sgo_if_return_set_tail_matches(
value : @lib.Instruction,
set : @lib.Instruction,
idx : @lib.GlobalIdx,
) -> Bool {
match set {
@lib.GlobalSet(set_idx) => set_idx == idx && sgo_const_instr(value) != None
_ => false
}
}
///|
fn sgo_if_return_block_tail_matches(
instr : @lib.Instruction,
idx : @lib.GlobalIdx,
) -> Bool {
match instr {
@lib.Block(@lib.VoidBlockType, @lib.Expr(body)) =>
sgo_instrs_are_single_const_global_set(body, idx)
_ => false
}
}
///|
fn sgo_is_compare_instr(instr : @lib.Instruction) -> Bool {
instr == @lib.Instruction::i32_eq() || instr == @lib.Instruction::i32_ne()
}
///|
fn sgo_is_exact_if_return_set(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
) -> Bool {
match instrs {
[@lib.GlobalGet(get_idx), if_instr, block_tail] =>
pos == 0 &&
get_idx == idx &&
sgo_is_return_guard(if_instr) &&
sgo_if_return_block_tail_matches(block_tail, idx)
[@lib.GlobalGet(get_idx), first, second, third] if first ==
@lib.Instruction::i32_eqz() =>
pos == 0 &&
get_idx == idx &&
sgo_is_return_guard(second) &&
sgo_if_return_block_tail_matches(third, idx)
[@lib.GlobalGet(get_idx), if_instr, value, set] =>
pos == 0 &&
get_idx == idx &&
sgo_is_return_guard(if_instr) &&
sgo_if_return_set_tail_matches(value, set, idx)
[@lib.GlobalGet(get_idx), first, second, value, set] if first ==
@lib.Instruction::i32_eqz() =>
pos == 0 &&
get_idx == idx &&
sgo_is_return_guard(second) &&
sgo_if_return_set_tail_matches(value, set, idx)
[@lib.GlobalGet(get_idx), const_instr, cmp_instr, if_instr, block_tail] =>
pos == 0 &&
get_idx == idx &&
sgo_const_instr(const_instr) != None &&
sgo_is_compare_instr(cmp_instr) &&
sgo_is_return_guard(if_instr) &&
sgo_if_return_block_tail_matches(block_tail, idx)
[const_instr, @lib.GlobalGet(get_idx), cmp_instr, if_instr, block_tail] =>
pos == 1 &&
get_idx == idx &&
sgo_const_instr(const_instr) != None &&
sgo_is_compare_instr(cmp_instr) &&
sgo_is_return_guard(if_instr) &&
sgo_if_return_block_tail_matches(block_tail, idx)
[@lib.GlobalGet(get_idx), const_instr, cmp_instr, if_instr, value, set] =>
pos == 0 &&
get_idx == idx &&
sgo_const_instr(const_instr) != None &&
sgo_is_compare_instr(cmp_instr) &&
sgo_is_return_guard(if_instr) &&
sgo_if_return_set_tail_matches(value, set, idx)
[const_instr, @lib.GlobalGet(get_idx), cmp_instr, if_instr, value, set] =>
pos == 1 &&
get_idx == idx &&
sgo_const_instr(const_instr) != None &&
sgo_is_compare_instr(cmp_instr) &&
sgo_is_return_guard(if_instr) &&
sgo_if_return_set_tail_matches(value, set, idx)
_ => false
}
}
///|
fn sgo_if_return_tail_matches(
instrs : Array[@lib.Instruction],
start : Int32,
idx : @lib.GlobalIdx,
) -> Bool {
if start + 2 == instrs.length() {
return sgo_if_return_set_tail_matches(instrs[start], instrs[start + 1], idx)
}
if start + 1 == instrs.length() {
return sgo_if_return_block_tail_matches(instrs[start], idx)
}
false
}
///|
fn sgo_is_pure_condition_if_return_set(
instrs : Array[@lib.Instruction],
pos : Int32,
idx : @lib.GlobalIdx,
) -> Bool {
if pos != 0 {
return false
}
for scan = pos + 1; scan < instrs.length(); scan = scan + 1 {
let instr = instrs[scan]
if sgo_is_return_guard(instr) {
return sgo_if_return_tail_matches(instrs, scan + 1, idx)
}
if !sgo_is_read_only_to_write_pure_condition_instr(instr) {
return false
}
}
false
}
///|
fn sgo_collect_read_only_to_write_body(
instrs : Array[@lib.Instruction],
out : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Nop => ()
@lib.Block(@lib.VoidBlockType, body) => {
let @lib.Expr(body_instrs) = body
if !sgo_collect_read_only_to_write_body(body_instrs, out) {
return false
}
}
_ => out.push(instr)
}
}
true
}
///|
fn sgo_skip_noop_const_drops(instrs : Array[@lib.Instruction]) -> Int32 {
let mut start = 0
while start + 1 < instrs.length() &&
sgo_const_instr(instrs[start]) != None &&
instrs[start + 1] == @lib.Instruction::drop() {
start = start + 2
}
start
}
///|
fn sgo_single_const_global_set_idx(
instrs : Array[@lib.Instruction],
) -> @lib.GlobalIdx? {
let flat : Array[@lib.Instruction] = []
if !sgo_collect_read_only_to_write_body(instrs, flat) {
return None
}
let start = sgo_skip_noop_const_drops(flat)
if start + 2 != flat.length() {
return None
}
match (flat[start], flat[start + 1]) {
(value, @lib.GlobalSet(set_idx)) if sgo_const_instr(value) != None =>
Some(set_idx)
_ => None
}
}
///|
fn sgo_instrs_are_single_const_global_set(
instrs : Array[@lib.Instruction],
idx : @lib.GlobalIdx,
) -> Bool {
match sgo_single_const_global_set_idx(instrs) {
Some(set_idx) => set_idx == idx
None => false
}
}
///|
fn sgo_instrs_are_const_global_set_with_nested_guards(
instrs : Array[@lib.Instruction],
idx : @lib.GlobalIdx,
infos : Array[SgoGlobalInfo],
) -> Bool {
let flat : Array[@lib.Instruction] = []
if !sgo_collect_read_only_to_write_body(instrs, flat) {
return false
}
let mut seen_target_set = false
let mut seen_nested_guard = false
let mut scan = 0
while scan < flat.length() {
if scan + 1 < flat.length() &&
sgo_const_instr(flat[scan]) != None &&
flat[scan + 1] == @lib.Instruction::drop() {
scan = scan + 2
continue
}
if scan + 1 < flat.length() {
match (flat[scan], flat[scan + 1]) {
(value, @lib.GlobalSet(set_idx)) if set_idx == idx &&
sgo_const_instr(value) != None => {
if seen_target_set {
return false
}
seen_target_set = true
scan = scan + 2
continue
}
(@lib.GlobalGet(nested_idx), if_instr) if nested_idx != idx &&
sgo_global_has_one_read(infos, nested_idx) &&
(
sgo_if_is_read_only_to_write_set(if_instr, nested_idx) ||
sgo_if_is_nested_multiglobal_read_only_to_write_set(
if_instr, nested_idx, infos,
)
) => {
seen_nested_guard = true
scan = scan + 2
continue
}
_ => ()
}
}
return false
}
seen_target_set && seen_nested_guard
}
///|
fn sgo_global_has_one_read(
infos : Array[SgoGlobalInfo],
idx : @lib.GlobalIdx,
) -> Bool {
let slot = sgo_global_idx_slot(idx)
slot >= 0 && slot < infos.length() && infos[slot].read_count == 1
}
///|
fn sgo_if_is_nested_multiglobal_read_only_to_write_set(
instr : @lib.Instruction,
idx : @lib.GlobalIdx,
infos : Array[SgoGlobalInfo],
) -> Bool {
match instr {
@lib.If(_, then_body, None) =>
sgo_instrs_are_const_global_set_with_nested_guards(then_body, idx, infos)
_ => false
}
}
///|
fn sgo_count_nested_multiglobal_read_only_to_write_expr(
expr : @lib.Expr,
safe_reads : Array[Int32],
infos : Array[SgoGlobalInfo],
) -> Unit {
let @lib.Expr(instrs) = expr
sgo_count_nested_multiglobal_read_only_to_write_instrs(
instrs, safe_reads, infos,
)
}
///|
fn sgo_count_nested_multiglobal_read_only_to_write_instrs(
instrs : Array[@lib.Instruction],
safe_reads : Array[Int32],
infos : Array[SgoGlobalInfo],
) -> Unit {
for pos = 0; pos < instrs.length(); pos = pos + 1 {
match instrs[pos] {
@lib.GlobalGet(idx) if pos + 1 < instrs.length() &&
sgo_if_is_nested_multiglobal_read_only_to_write_set(
instrs[pos + 1],
idx,
infos,
) => sgo_count_read_only_to_write_safe_read(idx, safe_reads)
@lib.Block(_, body) | @lib.Loop(_, body) | @lib.TryTable(_, _, body) =>
sgo_count_nested_multiglobal_read_only_to_write_expr(
body, safe_reads, infos,
)
@lib.If(_, then_body, else_body) => {
sgo_count_nested_multiglobal_read_only_to_write_instrs(
then_body, safe_reads, infos,
)
match else_body {
Some(body) =>
sgo_count_nested_multiglobal_read_only_to_write_instrs(
body, safe_reads, infos,
)
None => ()
}
}
_ => ()
}
}
}
///|
fn sgo_collect_read_only_to_write_condition_body(
instrs : Array[@lib.Instruction],
out : Array[@lib.Instruction],
) -> Bool {
for instr in instrs {
match instr {
@lib.Nop => ()
@lib.Block(_, body) => {
let @lib.Expr(body_instrs) = body
if !sgo_collect_read_only_to_write_condition_body(body_instrs, out) {
return false
}
}
_ => out.push(instr)
}
}
true
}
///|
fn sgo_block_condition_direct_read_idx(
instrs : Array[@lib.Instruction],
) -> @lib.GlobalIdx? {
let flat : Array[@lib.Instruction] = []
if !sgo_collect_read_only_to_write_condition_body(instrs, flat) {
return None
}
let start = sgo_skip_noop_const_drops(flat)
if start + 1 == flat.length() {
match flat[start] {
@lib.GlobalGet(idx) => return Some(idx)
_ => ()
}
}
None
}
///|
fn sgo_block_condition_single_read_idx(
instrs : Array[@lib.Instruction],
) -> @lib.GlobalIdx? {
match sgo_block_condition_direct_read_idx(instrs) {
Some(idx) => return Some(idx)
None => ()
}
let flat : Array[@lib.Instruction] = []
if !sgo_collect_read_only_to_write_condition_body(instrs, flat) {
return None
}
let start = sgo_skip_noop_const_drops(flat)
if start + 2 == flat.length() &&
flat[start + 1] == @lib.Instruction::i32_eqz() {
match flat[start] {
@lib.GlobalGet(idx) => return Some(idx)
_ => ()
}
}
None
}
///|
fn sgo_block_condition_safe_arm_read_idx(
instrs : Array[@lib.Instruction],
) -> @lib.GlobalIdx? {
match sgo_block_condition_direct_read_idx(instrs) {
Some(idx) => return Some(idx)
None => ()
}
let flat : Array[@lib.Instruction] = []
if !sgo_collect_read_only_to_write_condition_body(instrs, flat) {
return None
}
let start = sgo_skip_noop_const_drops(flat)
if start >= flat.length() {
return None
}
match flat[start] {
@lib.GlobalGet(idx) => {
for scan = start + 1; scan < flat.length(); scan = scan + 1 {
if !sgo_is_read_only_to_write_pure_condition_instr(flat[scan]) {
return None
}
}
Some(idx)
}
@lib.If(_, _, Some(_)) =>
match sgo_if_arm_condition_read_idx(flat[start]) {
Some(idx) => {
for scan = start + 1; scan < flat.length(); scan = scan + 1 {
if !sgo_is_read_only_to_write_pure_condition_instr(flat[scan]) {
return None
}
}
Some(idx)
}
None => None
}
_ => {
for if_pos = start; if_pos < flat.length(); if_pos = if_pos + 1 {
match flat[if_pos] {
@lib.If(_, _, Some(_)) =>
match sgo_if_arm_condition_read_idx(flat[if_pos]) {
Some(idx) => {
for scan = start; scan < if_pos; scan = scan + 1 {
if sgo_instr_may_reference_global(flat[scan]) {
return None
}
}
for scan = if_pos + 1; scan < flat.length(); scan = scan + 1 {
if !sgo_is_read_only_to_write_pure_condition_instr(flat[scan]) {
return None
}
}
return Some(idx)
}
None => ()
}
_ => ()
}
}
None
}
}
}
///|
fn sgo_block_condition_final_read_matches(
instrs : Array[@lib.Instruction],
idx : @lib.GlobalIdx,
) -> Bool {
let flat : Array[@lib.Instruction] = []
if !sgo_collect_read_only_to_write_condition_body(instrs, flat) {
return false
}
let len = flat.length()
if len < 3 {
return false
}
let cmp = flat[len - 1]
if cmp != @lib.Instruction::i32_eq() && cmp != @lib.Instruction::i32_ne() {
return false
}
match (flat[len - 3], flat[len - 2]) {
(@lib.GlobalGet(get_idx), const_instr) =>
get_idx == idx && sgo_const_instr(const_instr) != None
(const_instr, @lib.GlobalGet(get_idx)) =>
get_idx == idx && sgo_const_instr(const_instr) != None
_ => false
}
}
///|
fn sgo_block_condition_pure_read_matches(
instrs : Array[@lib.Instruction],
idx : @lib.GlobalIdx,
) -> Bool {
let flat : Array[@lib.Instruction] = []
if !sgo_collect_read_only_to_write_condition_body(instrs, flat) {
return false
}
let start = sgo_skip_noop_const_drops(flat)
if start >= flat.length() {
return false
}
match flat[start] {
@lib.GlobalGet(get_idx) if get_idx == idx => ()
_ => return false
}
for scan = start + 1; scan < flat.length(); scan = scan + 1 {
if !sgo_is_read_only_to_write_pure_condition_instr(flat[scan]) {
return false
}
}
true
}
///|
fn sgo_flat_prefix_is_read_only_to_write_guard(
flat : Array[@lib.Instruction],
start : Int32,
end_exclusive : Int32,
idx : @lib.GlobalIdx,
) -> Bool {
if end_exclusive <= start + 1 || end_exclusive > flat.length() {
return false
}
match flat[start] {
@lib.GlobalGet(get_idx) if get_idx == idx => ()
_ => return false
}
if !sgo_if_is_read_only_to_write_set(flat[end_exclusive - 1], idx) {
return false
}
for scan = start + 1; scan < end_exclusive - 1; scan = scan + 1 {
if !sgo_is_read_only_to_write_pure_condition_instr(flat[scan]) {
return false
}
}
true
}
///|
fn sgo_block_condition_nested_guard_final_read_matches(
instrs : Array[@lib.Instruction],
idx : @lib.GlobalIdx,
) -> Bool {
let flat : Array[@lib.Instruction] = []
if !sgo_collect_read_only_to_write_condition_body(instrs, flat) {
return false
}
let start = sgo_skip_noop_const_drops(flat)
let len = flat.length()
if start + 2 >= len {
return false
}
match flat[len - 1] {
@lib.GlobalGet(get_idx) if get_idx == idx =>
sgo_flat_prefix_is_read_only_to_write_guard(flat, start, len - 1, idx)
_ => false
}
}
///|
fn sgo_external_condition_stack_after(
instr : @lib.Instruction,
depth : Int32,
) -> Int32? {
match sgo_const_instr(instr) {
Some(_) => return Some(depth + 1)
None => ()
}
match instr {
@lib.I32Clz
| @lib.I32Ctz
| @lib.I32Popcnt
| @lib.I32Eqz
| @lib.I64Clz
| @lib.I64Ctz
| @lib.I64Popcnt
| @lib.I64Eqz => if depth >= 1 { Some(depth) } else { None }
_ =>
if sgo_is_read_only_to_write_pure_condition_instr(instr) && depth >= 2 {
Some(depth - 1)
} else {
None
}
}
}
///|
fn sgo_is_short_block_condition_external_legacy(
instrs : Array[@lib.Instruction],
pos : Int32,
if_index : Int32,
) -> Bool {
let start = pos + 1
let len = if_index - start
if len == 1 {
return instrs[start] == @lib.Instruction::i32_eqz()
}
if len == 2 {
return sgo_const_instr(instrs[start]) != None &&
sgo_is_compare_instr(instrs[start + 1])
}
false
}
///|
fn sgo_is_short_block_condition_external_reverse_legacy(
instrs : Array[@lib.Instruction],
pos : Int32,
if_index : Int32,
) -> Bool {
pos > 0 &&
if_index == pos + 2 &&
sgo_const_instr(instrs[pos - 1]) != None &&
sgo_is_compare_instr(instrs[pos + 1])
}
///|
fn sgo_block_condition_external_pure_if_index_with_depth(
instrs : Array[@lib.Instruction],
pos : Int32,
initial_depth : Int32,
reverse_legacy : Bool,
) -> Int32? {
let mut depth = initial_depth
let mut pure_count = 0
for scan = pos + 1; scan < instrs.length(); scan = scan + 1 {
match instrs[scan] {
@lib.If(_, _, _) => {
let legacy = sgo_is_short_block_condition_external_legacy(
instrs, pos, scan,
) ||
(
reverse_legacy &&
sgo_is_short_block_condition_external_reverse_legacy(
instrs, pos, scan,
)
)
if pure_count > 0 && depth == 1 && !legacy {
return Some(scan)
} else {
return None
}
}
instr =>
match sgo_external_condition_stack_after(instr, depth) {
Some(next_depth) => {
depth = next_depth
pure_count = pure_count + 1
}
None => return None
}
}
}
None
}
///|
fn sgo_block_condition_external_pure_if_index(
instrs : Array[@lib.Instruction],
pos : Int32,
) -> Int32? {
sgo_block_condition_external_pure_if_index_with_depth(instrs, pos, 1, false)
}
///|
fn sgo_block_condition_reverse_external_pure_if_index(
instrs : Array[@lib.Instruction],
pos : Int32,
) -> Int32? {
if pos <= 0 || sgo_const_instr(instrs[pos - 1]) == None {
return None
}
sgo_block_condition_external_pure_if_index_with_depth(instrs, pos, 2, true)
}
///|
fn sgo_count_block_condition_external_pure_read(
instrs : Array[@lib.Instruction],
pos : Int32,
safe_reads : Array[Int32],
) -> Unit {
match instrs[pos] {
@lib.Block(_, @lib.Expr(body)) =>
match sgo_block_condition_external_pure_if_index(instrs, pos) {
Some(if_index) =>
match instrs[if_index] {
@lib.If(_, then_body, None) =>
match sgo_single_const_global_set_idx(then_body) {
Some(idx) =>
sgo_count_block_condition_safe_body_read(
body, idx, safe_reads,
)
None => ()
}
_ => ()
}
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_external_pure_if_return_read(
instrs : Array[@lib.Instruction],
pos : Int32,
safe_reads : Array[Int32],
) -> Unit {
match instrs[pos] {
@lib.Block(_, @lib.Expr(body)) =>
match sgo_block_condition_external_pure_if_index(instrs, pos) {
Some(if_index) if sgo_is_return_guard(instrs[if_index]) =>
if if_index + 3 == instrs.length() {
match instrs[if_index + 2] {
@lib.GlobalSet(idx) if sgo_const_instr(instrs[if_index + 1]) !=
None =>
sgo_count_block_condition_safe_body_read(body, idx, safe_reads)
_ => ()
}
} else if if_index + 2 == instrs.length() {
match sgo_block_set_tail_idx(instrs[if_index + 1]) {
Some(idx) =>
sgo_count_block_condition_safe_body_read(body, idx, safe_reads)
None => ()
}
}
_ => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_reverse_external_pure_read(
instrs : Array[@lib.Instruction],
pos : Int32,
safe_reads : Array[Int32],
) -> Unit {
match instrs[pos] {
@lib.Block(_, @lib.Expr(body)) =>
match sgo_block_condition_reverse_external_pure_if_index(instrs, pos) {
Some(if_index) =>
match instrs[if_index] {
@lib.If(_, then_body, None) =>
match sgo_single_const_global_set_idx(then_body) {
Some(idx) =>
sgo_count_block_condition_safe_body_read(
body, idx, safe_reads,
)
None => ()
}
_ => ()
}
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_reverse_external_pure_if_return_read(
instrs : Array[@lib.Instruction],
pos : Int32,
safe_reads : Array[Int32],
) -> Unit {
match instrs[pos] {
@lib.Block(_, @lib.Expr(body)) =>
match sgo_block_condition_reverse_external_pure_if_index(instrs, pos) {
Some(if_index) if sgo_is_return_guard(instrs[if_index]) =>
if if_index + 3 == instrs.length() {
match instrs[if_index + 2] {
@lib.GlobalSet(idx) if sgo_const_instr(instrs[if_index + 1]) !=
None =>
sgo_count_block_condition_safe_body_read(body, idx, safe_reads)
_ => ()
}
} else if if_index + 2 == instrs.length() {
match sgo_block_set_tail_idx(instrs[if_index + 1]) {
Some(idx) =>
sgo_count_block_condition_safe_body_read(body, idx, safe_reads)
None => ()
}
}
_ => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_read_only_to_write_read(
instr : @lib.Instruction,
next : @lib.Instruction,
safe_reads : Array[Int32],
) -> Unit {
match (instr, next) {
(@lib.Block(_, @lib.Expr(body)), @lib.If(_, then_body, None)) =>
match sgo_single_const_global_set_idx(then_body) {
Some(idx) if sgo_block_condition_final_read_matches(body, idx) ||
sgo_block_condition_pure_read_matches(body, idx) =>
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
Some(idx) =>
match sgo_block_condition_single_read_idx(body) {
Some(read_idx) if read_idx == idx =>
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
_ => ()
}
None => ()
}
_ => ()
}
}
///|
fn sgo_block_condition_independent_call_final_read_idx(
instrs : Array[@lib.Instruction],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> @lib.GlobalIdx? {
let flat : Array[@lib.Instruction] = []
if !sgo_collect_read_only_to_write_condition_body(instrs, flat) {
return None
}
match flat {
[@lib.Call(func_idx), @lib.Drop, @lib.GlobalGet(idx)] if sgo_call_has_no_params_one_result(
mod_, func_type_indices, func_idx,
) => Some(idx)
[@lib.Call(func_idx), @lib.GlobalGet(idx)] if sgo_call_has_no_params_no_results(
mod_, func_type_indices, func_idx,
) => Some(idx)
_ => None
}
}
///|
fn sgo_block_condition_independent_memory_final_read_idx(
instrs : Array[@lib.Instruction],
) -> @lib.GlobalIdx? {
let flat : Array[@lib.Instruction] = []
if !sgo_collect_read_only_to_write_condition_body(instrs, flat) {
return None
}
match flat {
[@lib.MemorySize(_), @lib.Drop, @lib.GlobalGet(idx)] => Some(idx)
[grow_delta, @lib.MemoryGrow(_), @lib.Drop, @lib.GlobalGet(idx)] if sgo_const_instr(
grow_delta,
) !=
None => Some(idx)
_ => None
}
}
///|
fn sgo_block_condition_independent_table_final_read_idx(
instrs : Array[@lib.Instruction],
) -> @lib.GlobalIdx? {
let flat : Array[@lib.Instruction] = []
if !sgo_collect_read_only_to_write_condition_body(instrs, flat) {
return None
}
match flat {
[@lib.TableSize(_), @lib.Drop, @lib.GlobalGet(idx)] => Some(idx)
[ref_value, grow_delta, @lib.TableGrow(_), @lib.Drop, @lib.GlobalGet(idx)] if sgo_const_instr(
ref_value,
) !=
None &&
sgo_const_instr(grow_delta) != None => Some(idx)
[dest, value, @lib.TableSet(_), @lib.GlobalGet(idx)] if sgo_const_instr(
dest,
) !=
None &&
sgo_const_instr(value) != None => Some(idx)
[dest, value, len, @lib.TableFill(_), @lib.GlobalGet(idx)] if sgo_const_instr(
dest,
) !=
None &&
sgo_const_instr(value) != None &&
sgo_const_instr(len) != None => Some(idx)
[dest, src, len, @lib.TableCopy(_, _), @lib.GlobalGet(idx)] if sgo_const_instr(
dest,
) !=
None &&
sgo_const_instr(src) != None &&
sgo_const_instr(len) != None => Some(idx)
[dest, src, len, @lib.TableInit(_, _), @lib.GlobalGet(idx)] if sgo_const_instr(
dest,
) !=
None &&
sgo_const_instr(src) != None &&
sgo_const_instr(len) != None => Some(idx)
[@lib.ElemDrop(_), @lib.GlobalGet(idx)] => Some(idx)
_ => None
}
}
///|
fn sgo_block_condition_independent_local_final_read_idx(
instrs : Array[@lib.Instruction],
) -> @lib.GlobalIdx? {
let flat : Array[@lib.Instruction] = []
if !sgo_collect_read_only_to_write_condition_body(instrs, flat) {
return None
}
match flat {
[value, @lib.LocalSet(_), @lib.GlobalGet(idx)] if sgo_const_instr(value) !=
None => Some(idx)
[value, @lib.LocalTee(_), @lib.Drop, @lib.GlobalGet(idx)] if sgo_const_instr(
value,
) !=
None => Some(idx)
_ => None
}
}
///|
fn sgo_block_condition_independent_local_compare_read_matches(
instrs : Array[@lib.Instruction],
idx : @lib.GlobalIdx,
) -> Bool {
let flat : Array[@lib.Instruction] = []
if !sgo_collect_read_only_to_write_condition_body(instrs, flat) {
return false
}
if flat.length() != 5 ||
!sgo_local_effect_value_is_independent(flat[0]) ||
!sgo_is_compare_instr(flat[4]) {
return false
}
match flat[1] {
@lib.LocalSet(local_idx) =>
match (flat[2], flat[3]) {
(@lib.GlobalGet(read_idx), @lib.LocalGet(get_idx)) =>
read_idx == idx && get_idx == local_idx
(@lib.LocalGet(get_idx), @lib.GlobalGet(read_idx)) =>
read_idx == idx && get_idx == local_idx
_ => false
}
_ => false
}
}
///|
fn sgo_block_condition_independent_global_set_final_read_idx(
instrs : Array[@lib.Instruction],
) -> @lib.GlobalIdx? {
let flat : Array[@lib.Instruction] = []
if !sgo_collect_read_only_to_write_condition_body(instrs, flat) {
return None
}
match flat {
[value, @lib.GlobalSet(set_idx), @lib.GlobalGet(idx)] if set_idx != idx &&
sgo_const_instr(value) != None => Some(idx)
_ => None
}
}
///|
fn sgo_count_block_condition_independent_memory_read_only_to_write_read(
instr : @lib.Instruction,
next : @lib.Instruction,
safe_reads : Array[Int32],
) -> Unit {
match (instr, next) {
(@lib.Block(_, @lib.Expr(body)), @lib.If(_, then_body, None)) =>
match sgo_single_const_global_set_idx(then_body) {
Some(idx) =>
match sgo_block_condition_independent_memory_final_read_idx(body) {
Some(read_idx) if read_idx == idx =>
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
_ => ()
}
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_independent_table_read_only_to_write_read(
instr : @lib.Instruction,
next : @lib.Instruction,
safe_reads : Array[Int32],
) -> Unit {
match (instr, next) {
(@lib.Block(_, @lib.Expr(body)), @lib.If(_, then_body, None)) =>
match sgo_single_const_global_set_idx(then_body) {
Some(idx) =>
match sgo_block_condition_independent_table_final_read_idx(body) {
Some(read_idx) if read_idx == idx =>
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
_ => ()
}
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_independent_local_read_only_to_write_read(
instr : @lib.Instruction,
next : @lib.Instruction,
safe_reads : Array[Int32],
) -> Unit {
match (instr, next) {
(@lib.Block(_, @lib.Expr(body)), @lib.If(_, then_body, None)) =>
match sgo_single_const_global_set_idx(then_body) {
Some(idx) => {
if sgo_block_condition_independent_local_compare_read_matches(
body, idx,
) {
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
return
}
match sgo_block_condition_independent_local_final_read_idx(body) {
Some(read_idx) if read_idx == idx =>
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
_ => ()
}
}
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_independent_global_set_read_only_to_write_read(
instr : @lib.Instruction,
next : @lib.Instruction,
safe_reads : Array[Int32],
) -> Unit {
match (instr, next) {
(@lib.Block(_, @lib.Expr(body)), @lib.If(_, then_body, None)) =>
match sgo_single_const_global_set_idx(then_body) {
Some(idx) =>
match
sgo_block_condition_independent_global_set_final_read_idx(body) {
Some(read_idx) if read_idx == idx =>
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
_ => ()
}
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_independent_call_read_only_to_write_read(
instr : @lib.Instruction,
next : @lib.Instruction,
safe_reads : Array[Int32],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Unit {
match (instr, next) {
(@lib.Block(_, @lib.Expr(body)), @lib.If(_, then_body, None)) =>
match sgo_single_const_global_set_idx(then_body) {
Some(idx) =>
match
sgo_block_condition_independent_call_final_read_idx(
body, mod_, func_type_indices,
) {
Some(read_idx) if read_idx == idx =>
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
_ => ()
}
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_independent_call_if_return_read(
instrs : Array[@lib.Instruction],
pos : Int32,
safe_reads : Array[Int32],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Unit {
if pos + 2 >= instrs.length() {
return
}
match instrs[pos] {
@lib.Block(_, @lib.Expr(body)) if sgo_is_return_guard(instrs[pos + 1]) =>
match
sgo_block_condition_independent_call_final_read_idx(
body, mod_, func_type_indices,
) {
Some(idx) =>
if sgo_if_return_tail_matches(instrs, pos + 2, idx) {
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
}
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_independent_memory_if_return_read(
instrs : Array[@lib.Instruction],
pos : Int32,
safe_reads : Array[Int32],
) -> Unit {
if pos + 2 >= instrs.length() {
return
}
match instrs[pos] {
@lib.Block(_, @lib.Expr(body)) if sgo_is_return_guard(instrs[pos + 1]) =>
match sgo_block_condition_independent_memory_final_read_idx(body) {
Some(idx) =>
if sgo_if_return_tail_matches(instrs, pos + 2, idx) {
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
}
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_independent_table_if_return_read(
instrs : Array[@lib.Instruction],
pos : Int32,
safe_reads : Array[Int32],
) -> Unit {
if pos + 2 >= instrs.length() {
return
}
match instrs[pos] {
@lib.Block(_, @lib.Expr(body)) if sgo_is_return_guard(instrs[pos + 1]) =>
match sgo_block_condition_independent_table_final_read_idx(body) {
Some(idx) =>
if sgo_if_return_tail_matches(instrs, pos + 2, idx) {
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
}
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_independent_local_if_return_read(
instrs : Array[@lib.Instruction],
pos : Int32,
safe_reads : Array[Int32],
) -> Unit {
if pos + 2 >= instrs.length() {
return
}
match instrs[pos] {
@lib.Block(_, @lib.Expr(body)) if sgo_is_return_guard(instrs[pos + 1]) =>
match sgo_block_condition_independent_local_final_read_idx(body) {
Some(idx) =>
if sgo_if_return_tail_matches(instrs, pos + 2, idx) {
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
}
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_independent_global_set_if_return_read(
instrs : Array[@lib.Instruction],
pos : Int32,
safe_reads : Array[Int32],
) -> Unit {
if pos + 2 >= instrs.length() {
return
}
match instrs[pos] {
@lib.Block(_, @lib.Expr(body)) if sgo_is_return_guard(instrs[pos + 1]) =>
match sgo_block_condition_independent_global_set_final_read_idx(body) {
Some(idx) =>
if sgo_if_return_tail_matches(instrs, pos + 2, idx) {
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
}
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_safe_body_read(
body : Array[@lib.Instruction],
idx : @lib.GlobalIdx,
safe_reads : Array[Int32],
) -> Unit {
if sgo_block_condition_final_read_matches(body, idx) ||
sgo_block_condition_pure_read_matches(body, idx) ||
sgo_block_condition_nested_guard_final_read_matches(body, idx) ||
sgo_block_condition_independent_local_compare_read_matches(body, idx) {
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
} else {
match sgo_block_condition_single_read_idx(body) {
Some(read_idx) if read_idx == idx =>
sgo_count_read_only_to_write_safe_read(idx, safe_reads)
_ => ()
}
}
}
///|
fn sgo_count_block_condition_if_return_body_read(
body : Array[@lib.Instruction],
idx : @lib.GlobalIdx,
safe_reads : Array[Int32],
) -> Unit {
sgo_count_block_condition_safe_body_read(body, idx, safe_reads)
}
///|
fn sgo_count_block_condition_eqz_read_only_to_write_read(
instr : @lib.Instruction,
eqz : @lib.Instruction,
if_instr : @lib.Instruction,
safe_reads : Array[Int32],
) -> Unit {
match (instr, if_instr) {
(@lib.Block(_, @lib.Expr(body)), @lib.If(_, then_body, None)) if eqz ==
@lib.Instruction::i32_eqz() =>
match sgo_single_const_global_set_idx(then_body) {
Some(idx) =>
sgo_count_block_condition_safe_body_read(body, idx, safe_reads)
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_compare_read_only_to_write_read(
instr : @lib.Instruction,
const_instr : @lib.Instruction,
cmp_instr : @lib.Instruction,
if_instr : @lib.Instruction,
safe_reads : Array[Int32],
) -> Unit {
match (instr, if_instr) {
(@lib.Block(_, @lib.Expr(body)), @lib.If(_, then_body, None)) if sgo_const_instr(
const_instr,
) !=
None &&
sgo_is_compare_instr(cmp_instr) =>
match sgo_single_const_global_set_idx(then_body) {
Some(idx) =>
sgo_count_block_condition_safe_body_read(body, idx, safe_reads)
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_reverse_compare_read_only_to_write_read(
instrs : Array[@lib.Instruction],
pos : Int32,
safe_reads : Array[Int32],
) -> Unit {
if pos <= 0 || pos + 2 >= instrs.length() {
return
}
let const_instr = instrs[pos - 1]
let instr = instrs[pos]
let cmp_instr = instrs[pos + 1]
let if_instr = instrs[pos + 2]
match (instr, if_instr) {
(@lib.Block(_, @lib.Expr(body)), @lib.If(_, then_body, None)) if sgo_const_instr(
const_instr,
) !=
None &&
sgo_is_compare_instr(cmp_instr) =>
match sgo_single_const_global_set_idx(then_body) {
Some(idx) =>
sgo_count_block_condition_safe_body_read(body, idx, safe_reads)
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_if_return_block_set_read(
instr : @lib.Instruction,
next : @lib.Instruction,
block_tail : @lib.Instruction,
safe_reads : Array[Int32],
) -> Unit {
match (instr, next, block_tail) {
(
@lib.Block(_, @lib.Expr(body)),
if_instr,
@lib.Block(@lib.VoidBlockType, @lib.Expr(tail)),
) if sgo_is_return_guard(if_instr) =>
match sgo_single_const_global_set_idx(tail) {
Some(idx) =>
sgo_count_block_condition_if_return_body_read(body, idx, safe_reads)
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_if_return_set_read(
instr : @lib.Instruction,
next : @lib.Instruction,
value : @lib.Instruction,
set : @lib.Instruction,
safe_reads : Array[Int32],
) -> Unit {
match (instr, next, set) {
(@lib.Block(_, @lib.Expr(body)), if_instr, @lib.GlobalSet(idx)) if sgo_is_return_guard(
if_instr,
) &&
sgo_const_instr(value) != None =>
sgo_count_block_condition_if_return_body_read(body, idx, safe_reads)
_ => ()
}
}
///|
fn sgo_count_block_condition_eqz_if_return_set_read(
instr : @lib.Instruction,
eqz : @lib.Instruction,
if_instr : @lib.Instruction,
value : @lib.Instruction,
set : @lib.Instruction,
safe_reads : Array[Int32],
) -> Unit {
match (instr, set) {
(@lib.Block(_, @lib.Expr(body)), @lib.GlobalSet(idx)) if eqz ==
@lib.Instruction::i32_eqz() &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(value) != None =>
sgo_count_block_condition_safe_body_read(body, idx, safe_reads)
_ => ()
}
}
///|
fn sgo_block_set_tail_idx(instr : @lib.Instruction) -> @lib.GlobalIdx? {
match instr {
@lib.Block(@lib.VoidBlockType, @lib.Expr(tail)) =>
sgo_single_const_global_set_idx(tail)
_ => None
}
}
///|
fn sgo_count_block_condition_eqz_if_return_block_set_read(
instr : @lib.Instruction,
eqz : @lib.Instruction,
if_instr : @lib.Instruction,
block_tail : @lib.Instruction,
safe_reads : Array[Int32],
) -> Unit {
match instr {
@lib.Block(_, @lib.Expr(body)) if eqz == @lib.Instruction::i32_eqz() &&
sgo_is_return_guard(if_instr) =>
match sgo_block_set_tail_idx(block_tail) {
Some(idx) =>
sgo_count_block_condition_safe_body_read(body, idx, safe_reads)
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_compare_if_return_set_read(
instr : @lib.Instruction,
const_instr : @lib.Instruction,
cmp_instr : @lib.Instruction,
if_instr : @lib.Instruction,
value : @lib.Instruction,
set : @lib.Instruction,
safe_reads : Array[Int32],
) -> Unit {
match (instr, set) {
(@lib.Block(_, @lib.Expr(body)), @lib.GlobalSet(idx)) if sgo_const_instr(
const_instr,
) !=
None &&
sgo_is_compare_instr(cmp_instr) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(value) != None =>
sgo_count_block_condition_safe_body_read(body, idx, safe_reads)
_ => ()
}
}
///|
fn sgo_count_block_condition_compare_if_return_block_set_read(
instr : @lib.Instruction,
const_instr : @lib.Instruction,
cmp_instr : @lib.Instruction,
if_instr : @lib.Instruction,
block_tail : @lib.Instruction,
safe_reads : Array[Int32],
) -> Unit {
match instr {
@lib.Block(_, @lib.Expr(body)) if sgo_const_instr(const_instr) != None &&
sgo_is_compare_instr(cmp_instr) &&
sgo_is_return_guard(if_instr) =>
match sgo_block_set_tail_idx(block_tail) {
Some(idx) =>
sgo_count_block_condition_safe_body_read(body, idx, safe_reads)
None => ()
}
_ => ()
}
}
///|
fn sgo_count_block_condition_reverse_compare_if_return_set_read(
instrs : Array[@lib.Instruction],
pos : Int32,
safe_reads : Array[Int32],
) -> Unit {
if pos <= 0 || pos + 4 >= instrs.length() {
return
}
let const_instr = instrs[pos - 1]
let instr = instrs[pos]
let cmp_instr = instrs[pos + 1]
let if_instr = instrs[pos + 2]
let value = instrs[pos + 3]
let set = instrs[pos + 4]
match (instr, set) {
(@lib.Block(_, @lib.Expr(body)), @lib.GlobalSet(idx)) if sgo_const_instr(
const_instr,
) !=
None &&
sgo_is_compare_instr(cmp_instr) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(value) != None =>
sgo_count_block_condition_safe_body_read(body, idx, safe_reads)
_ => ()
}
}
///|
fn sgo_count_block_condition_reverse_compare_if_return_block_set_read(
instrs : Array[@lib.Instruction],
pos : Int32,
safe_reads : Array[Int32],
) -> Unit {
if pos <= 0 || pos + 3 >= instrs.length() {
return
}
let const_instr = instrs[pos - 1]
let instr = instrs[pos]
let cmp_instr = instrs[pos + 1]
let if_instr = instrs[pos + 2]
let block_tail = instrs[pos + 3]
match instr {
@lib.Block(_, @lib.Expr(body)) if sgo_const_instr(const_instr) != None &&
sgo_is_compare_instr(cmp_instr) &&
sgo_is_return_guard(if_instr) =>
match sgo_block_set_tail_idx(block_tail) {
Some(idx) =>
sgo_count_block_condition_safe_body_read(body, idx, safe_reads)
None => ()
}
_ => ()
}
}
///|
fn sgo_plan_remove_sets(
infos : Array[SgoGlobalInfo],
same_init_writes : Array[Bool],
read_only_to_write : Array[Bool],
) -> Array[Bool] {
let remove_sets = Array::make(infos.length(), false)
for idx = 0; idx < infos.length(); idx = idx + 1 {
let info = infos[idx]
if !info.imported &&
!info.exported &&
info.write_count > 0 &&
(info.read_count == 0 || same_init_writes[idx] || read_only_to_write[idx]) {
remove_sets[idx] = true
}
}
remove_sets
}
///|
fn sgo_apply_practical_immutability(
infos : Array[SgoGlobalInfo],
remove_sets : Array[Bool],
) -> Bool {
let mut changed = false
for idx = 0; idx < infos.length(); idx = idx + 1 {
let info = infos[idx]
if info.imported || info.exported || !info.mutable_ {
continue
}
if info.write_count == 0 || remove_sets[idx] {
infos[idx].mutable_ = false
changed = true
}
}
changed
}
///|
fn sgo_rewrite_instr_with_state(
instr : @lib.Instruction,
state : SgoRewriteState,
rewrite_sets : Bool,
) -> @lib.Instruction {
match instr {
@lib.GlobalGet(idx) => {
let slot = sgo_global_idx_slot(idx)
if slot >= 0 && slot < state.consts.length() {
match state.consts[slot] {
Some(replacement) => {
state.changed = true
return replacement
}
None => ()
}
}
if slot >= 0 && slot < state.aliases.length() {
match state.aliases[slot] {
Some(target_idx) => {
state.changed = true
return @lib.Instruction::global_get(target_idx)
}
None => ()
}
}
instr
}
@lib.GlobalSet(idx) if rewrite_sets => {
let slot = sgo_global_idx_slot(idx)
if slot >= 0 &&
slot < state.remove_sets.length() &&
state.remove_sets[slot] {
state.changed = true
return @lib.Instruction::drop()
}
instr
}
@lib.Block(block_type, body) =>
@lib.Instruction::block(
block_type,
sgo_rewrite_expr_with_state(body, state, rewrite_sets),
)
@lib.Loop(block_type, body) =>
@lib.Instruction::loop_(
block_type,
sgo_rewrite_expr_with_state(body, state, rewrite_sets),
)
@lib.If(block_type, then_body, else_body) => {
let rewritten_then = sgo_rewrite_instrs_with_state(
then_body, state, rewrite_sets,
)
let rewritten_else = match else_body {
Some(body) =>
Some(sgo_rewrite_instrs_with_state(body, state, rewrite_sets))
None => None
}
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else)
}
@lib.TryTable(block_type, catches, body) =>
@lib.Instruction::try_table(
block_type,
catches,
sgo_rewrite_expr_with_state(body, state, rewrite_sets),
)
_ => instr
}
}
///|
fn sgo_rewrite_instrs_with_state(
instrs : Array[@lib.Instruction],
state : SgoRewriteState,
rewrite_sets : Bool,
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
for instr in instrs {
out.push(sgo_rewrite_instr_with_state(instr, state, rewrite_sets))
}
out
}
///|
fn sgo_rewrite_expr_with_state(
expr : @lib.Expr,
state : SgoRewriteState,
rewrite_sets : Bool,
) -> @lib.Expr {
let @lib.Expr(instrs) = expr
@lib.Expr::new(sgo_rewrite_instrs_with_state(instrs, state, rewrite_sets))
}
///|
fn sgo_rewrite_expr(
expr : @lib.Expr,
consts : Array[@lib.Instruction?],
aliases : Array[@lib.GlobalIdx?],
remove_sets : Array[Bool],
rewrite_sets : Bool,
) -> (@lib.Expr, Bool) {
let state = { consts, aliases, remove_sets, changed: false, }
let rewritten = sgo_rewrite_expr_with_state(expr, state, rewrite_sets)
(rewritten, state.changed)
}
///|
fn sgo_rewrite_global_init_instr_into(
instr : @lib.Instruction,
state : SgoGlobalInitRewriteState,
out : Array[@lib.Instruction],
) -> Unit {
match instr {
@lib.GlobalGet(idx) => {
let slot = sgo_global_idx_slot(idx)
if slot >= 0 && slot < state.inlines.length() {
match state.inlines[slot] {
Some(@lib.Expr(replacement)) => {
for item in replacement {
out.push(item)
}
state.changed = true
return
}
None => ()
}
}
if slot >= 0 && slot < state.consts.length() {
match state.consts[slot] {
Some(replacement) => {
out.push(replacement)
state.changed = true
return
}
None => ()
}
}
if slot >= 0 && slot < state.aliases.length() {
match state.aliases[slot] {
Some(target_idx) => {
out.push(@lib.Instruction::global_get(target_idx))
state.changed = true
return
}
None => ()
}
}
out.push(instr)
}
@lib.Block(block_type, body) =>
out.push(
@lib.Instruction::block(
block_type,
sgo_rewrite_global_init_expr_with_state(body, state),
),
)
@lib.Loop(block_type, body) =>
out.push(
@lib.Instruction::loop_(
block_type,
sgo_rewrite_global_init_expr_with_state(body, state),
),
)
@lib.If(block_type, then_body, else_body) => {
let rewritten_then = sgo_rewrite_global_init_instrs_with_state(
then_body, state,
)
let rewritten_else = match else_body {
Some(body) =>
Some(sgo_rewrite_global_init_instrs_with_state(body, state))
None => None
}
out.push(
@lib.Instruction::if_(block_type, rewritten_then, rewritten_else),
)
}
@lib.TryTable(block_type, catches, body) =>
out.push(
@lib.Instruction::try_table(
block_type,
catches,
sgo_rewrite_global_init_expr_with_state(body, state),
),
)
_ => out.push(instr)
}
}
///|
fn sgo_rewrite_global_init_instrs_with_state(
instrs : Array[@lib.Instruction],
state : SgoGlobalInitRewriteState,
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = []
for instr in instrs {
sgo_rewrite_global_init_instr_into(instr, state, out)
}
out
}
///|
fn sgo_rewrite_global_init_expr_with_state(
expr : @lib.Expr,
state : SgoGlobalInitRewriteState,
) -> @lib.Expr {
let @lib.Expr(instrs) = expr
@lib.Expr::new(sgo_rewrite_global_init_instrs_with_state(instrs, state))
}
///|
fn sgo_rewrite_global_init_expr(
expr : @lib.Expr,
consts : Array[@lib.Instruction?],
aliases : Array[@lib.GlobalIdx?],
inlines : Array[@lib.Expr?],
) -> (@lib.Expr, Bool) {
let state = { consts, aliases, inlines, changed: false, }
let rewritten = sgo_rewrite_global_init_expr_with_state(expr, state)
(rewritten, state.changed)
}
///|
fn sgo_build_initial_startup_consts(
infos : Array[SgoGlobalInfo],
) -> Array[@lib.Instruction?] {
let consts = sgo_empty_const_array(infos.length())
for idx = 0; idx < infos.length(); idx = idx + 1 {
let info = infos[idx]
if info.imported {
continue
}
match info.init {
Some(expr) => consts[idx] = sgo_expr_single_const(expr)
None => ()
}
}
consts
}
///|
fn sgo_build_code_consts(
infos : Array[SgoGlobalInfo],
) -> Array[@lib.Instruction?] {
let consts = sgo_empty_const_array(infos.length())
for idx = 0; idx < infos.length(); idx = idx + 1 {
let info = infos[idx]
if info.imported || info.mutable_ {
continue
}
match info.init {
Some(expr) => consts[idx] = sgo_expr_single_const(expr)
None => ()
}
}
consts
}
///|
fn sgo_record_alias_for_global(
aliases : Array[@lib.GlobalIdx?],
infos : Array[SgoGlobalInfo],
abs_idx : Int32,
init : @lib.Expr,
) -> Unit {
if abs_idx < 0 || abs_idx >= infos.length() || infos[abs_idx].mutable_ {
return
}
match init {
@lib.Expr([@lib.GlobalGet(dep)]) => {
let dep_slot = sgo_global_idx_slot(dep)
if dep_slot < 0 || dep_slot >= infos.length() || infos[dep_slot].mutable_ {
return
}
if infos[dep_slot].value_type != infos[abs_idx].value_type {
return
}
aliases[abs_idx] = match aliases[dep_slot] {
Some(target_idx) => Some(target_idx)
None => Some(dep)
}
}
_ => ()
}
}
///|
fn sgo_rewrite_global_sec(
global_sec : @lib.GlobalSec?,
infos : Array[SgoGlobalInfo],
imported_globals : Int32,
) -> (@lib.GlobalSec?, Array[@lib.Instruction?], Array[@lib.GlobalIdx?], Bool) {
let startup_consts = sgo_build_initial_startup_consts(infos)
let aliases = sgo_empty_global_idx_array(infos.length())
let init_inlines = sgo_empty_expr_array(infos.length())
let mut changed = false
match global_sec {
Some(@lib.GlobalSec(globals)) => {
let out : Array[@lib.Global] = []
for defined_idx = 0
defined_idx < globals.length()
defined_idx = defined_idx + 1 {
let abs_idx = sgo_defined_global_abs_idx(imported_globals, defined_idx)
let @lib.Global(@lib.GlobalType(_, original_mutable), init) = globals[defined_idx]
let (rewritten_init, init_changed) = sgo_rewrite_global_init_expr(
init, startup_consts, aliases, init_inlines,
)
if init_changed {
changed = true
}
let info = infos[abs_idx]
if original_mutable != info.mutable_ {
changed = true
}
infos[abs_idx].init = Some(rewritten_init)
startup_consts[abs_idx] = sgo_expr_single_const(rewritten_init)
sgo_record_alias_for_global(aliases, infos, abs_idx, rewritten_init)
let @lib.Expr(rewritten_init_instrs) = rewritten_init
if !info.imported &&
!info.mutable_ &&
!info.exported &&
info.read_count == 1 &&
rewritten_init_instrs.length() == 1 {
init_inlines[abs_idx] = Some(rewritten_init)
}
out.push(
@lib.Global::new(
@lib.GlobalType::new(info.value_type, info.mutable_),
rewritten_init,
),
)
}
(Some(@lib.GlobalSec::new(out)), startup_consts, aliases, changed)
}
None => (None, startup_consts, aliases, false)
}
}
///|
fn sgo_rewrite_table_sec(
table_sec : @lib.TableSec?,
consts : Array[@lib.Instruction?],
aliases : Array[@lib.GlobalIdx?],
remove_sets : Array[Bool],
) -> (@lib.TableSec?, Bool) {
match table_sec {
Some(@lib.TableSec(tables)) => {
let out : Array[@lib.Table] = []
let mut changed = false
for table in tables {
match table {
@lib.Table(table_type, Some(init)) => {
let (rewritten, init_changed) = sgo_rewrite_expr(
init, consts, aliases, remove_sets, false,
)
if init_changed {
changed = true
}
out.push(@lib.Table::new(table_type, Some(rewritten)))
}
@lib.Table(_, _) => out.push(table)
}
}
(Some(@lib.TableSec::new(out)), changed)
}
None => (None, false)
}
}
///|
fn sgo_rewrite_elem_mode(
mode : @lib.ElemMode,
consts : Array[@lib.Instruction?],
aliases : Array[@lib.GlobalIdx?],
remove_sets : Array[Bool],
) -> (@lib.ElemMode, Bool) {
match mode {
@lib.Active(table_idx, offset) => {
let (rewritten, changed) = sgo_rewrite_expr(
offset, consts, aliases, remove_sets, false,
)
(@lib.ElemMode::active(table_idx, rewritten), changed)
}
_ => (mode, false)
}
}
///|
fn sgo_rewrite_elem_kind(kind : @lib.ElemKind) -> (@lib.ElemKind, Bool) {
// Element item expressions are reference-typed and need exact type/refinalize
// care. Keep them unchanged in the first SGO slice; element offsets are still
// rewritten through sgo_rewrite_elem_mode.
(kind, false)
}
///|
fn sgo_rewrite_elem_sec(
elem_sec : @lib.ElemSec?,
consts : Array[@lib.Instruction?],
aliases : Array[@lib.GlobalIdx?],
remove_sets : Array[Bool],
) -> (@lib.ElemSec?, Bool) {
match elem_sec {
Some(@lib.ElemSec(elems)) => {
let out : Array[@lib.Elem] = []
let mut changed = false
for elem in elems {
let @lib.Elem(mode, kind) = elem
let (next_mode, mode_changed) = sgo_rewrite_elem_mode(
mode, consts, aliases, remove_sets,
)
let (next_kind, kind_changed) = sgo_rewrite_elem_kind(kind)
if mode_changed || kind_changed {
changed = true
}
out.push(@lib.Elem::new(next_mode, next_kind))
}
(Some(@lib.ElemSec::new(out)), changed)
}
None => (None, false)
}
}
///|
fn sgo_rewrite_data_sec(
data_sec : @lib.DataSec?,
consts : Array[@lib.Instruction?],
aliases : Array[@lib.GlobalIdx?],
remove_sets : Array[Bool],
) -> (@lib.DataSec?, Bool) {
match data_sec {
Some(@lib.DataSec(datas)) => {
let out : Array[@lib.Data] = []
let mut changed = false
for data in datas {
let @lib.Data(mode, bytes) = data
let next_mode = match mode {
@lib.Active(mem_idx, offset) => {
let (rewritten, offset_changed) = sgo_rewrite_expr(
offset, consts, aliases, remove_sets, false,
)
if offset_changed {
changed = true
}
@lib.DataMode::active(mem_idx, rewritten)
}
@lib.Passive => mode
}
out.push(@lib.Data::new(next_mode, bytes))
}
(Some(@lib.DataSec::new(out)), changed)
}
None => (None, false)
}
}
///|
fn sgo_runtime_trace_clear(
facts : Array[@lib.Instruction?],
touched : Array[Int32],
marked : Array[Bool],
) -> Unit {
for slot in touched {
facts[slot] = None
marked[slot] = false
}
touched.clear()
}
///|
fn sgo_runtime_trace_barrier(instr : @lib.Instruction) -> Bool {
match instr {
@lib.Call(_)
| @lib.CallIndirect(_, _)
| @lib.CallRef(_)
| @lib.ReturnCall(_)
| @lib.ReturnCallIndirect(_, _)
| @lib.ReturnCallRef(_)
| @lib.Block(_, _)
| @lib.Loop(_, _)
| @lib.If(_, _, _)
| @lib.TryTable(_, _, _)
| @lib.Br(_)
| @lib.BrIf(_)
| @lib.BrOnNull(_)
| @lib.BrOnNonNull(_)
| @lib.BrOnCast(_, _, _, _)
| @lib.BrOnCastFail(_, _, _, _)
| @lib.BrOnCastDescEq(_, _, _)
| @lib.BrOnCastDescEqFail(_, _, _)
| @lib.BrTable(_, _)
| @lib.Return
| @lib.Throw(_)
| @lib.ThrowRef
| @lib.Try(_, _, _, _)
| @lib.Rethrow(_)
| @lib.Resume(_, _)
| @lib.ResumeThrow(_, _, _)
| @lib.ResumeThrowRef(_, _)
| @lib.Suspend(_)
| @lib.StackSwitch(_, _) => true
_ => false
}
}
///|
fn sgo_runtime_trace_trackable(
infos : Array[SgoGlobalInfo],
slot : Int32,
) -> Bool {
slot >= 0 && slot < infos.length()
}
///|
priv struct SgoRuntimeTraceWorkspace {
facts : Array[@lib.Instruction?]
marked : Array[Bool]
touched : Array[Int32]
mutable_imports : Array[Int32]
}
///|
fn SgoRuntimeTraceWorkspace::new(
infos : Array[SgoGlobalInfo],
) -> SgoRuntimeTraceWorkspace {
let mutable_imports : Array[Int32] = []
for slot = 0; slot < infos.length(); slot = slot + 1 {
if infos[slot].imported && infos[slot].mutable_ {
mutable_imports.push(slot)
}
}
{
facts: sgo_empty_const_array(infos.length()),
marked: Array::make(infos.length(), false),
touched: [],
mutable_imports,
}
}
///|
fn sgo_rewrite_runtime_trace_expr_with_workspace(
expr : @lib.Expr,
infos : Array[SgoGlobalInfo],
workspace : SgoRuntimeTraceWorkspace,
) -> (@lib.Expr, Bool) {
sgo_runtime_trace_clear(workspace.facts, workspace.touched, workspace.marked)
let (rewritten, changed, _) = sgo_rewrite_runtime_trace_expr_with_sparse_facts(
expr,
infos,
workspace.facts,
workspace.mutable_imports,
workspace.touched,
workspace.marked,
)
sgo_runtime_trace_clear(workspace.facts, workspace.touched, workspace.marked)
(rewritten, changed)
}
///|
fn sgo_rewrite_runtime_trace_expr(
expr : @lib.Expr,
infos : Array[SgoGlobalInfo],
) -> (@lib.Expr, Bool) {
sgo_rewrite_runtime_trace_expr_with_workspace(
expr,
infos,
SgoRuntimeTraceWorkspace::new(infos),
)
}
///|
fn sgo_i32_add_no_overflow(lhs : Int32, rhs : Int32) -> Int32? {
let sum = lhs.to_int64() + rhs.to_int64()
if sum >= -2147483648L && sum <= 2147483647L {
Some(sum.to_int())
} else {
None
}
}
///|
fn sgo_instr_refs_local(
instr : @lib.Instruction,
local_idx : @lib.LocalIdx,
) -> Bool {
match instr {
@lib.LocalGet(idx) | @lib.LocalSet(idx) | @lib.LocalTee(idx) =>
idx == local_idx
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
sgo_instrs_ref_local(body, local_idx, 0, 0)
@lib.If(_, then_body, else_body) => {
if sgo_instrs_ref_local(then_body, local_idx, 0, 0) {
return true
}
match else_body {
Some(body) => sgo_instrs_ref_local(body, local_idx, 0, 0)
None => false
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if sgo_instrs_ref_local(body, local_idx, 0, 0) {
return true
}
for catch_ in catches {
let @lib.Expr(body) = match catch_ {
@lib.LegacyCatch(_, expr) | @lib.LegacyCatchAll(expr) => expr
}
if sgo_instrs_ref_local(body, local_idx, 0, 0) {
return true
}
}
false
}
_ => false
}
}
///|
fn sgo_instrs_ref_local(
instrs : Array[@lib.Instruction],
local_idx : @lib.LocalIdx,
skip_start : Int32,
skip_end : Int32,
outside_refs~ : ((@lib.LocalIdx) -> Bool)? = None,
) -> Bool {
match outside_refs {
Some(has_outside_refs) => if has_outside_refs(local_idx) { return true }
None => ()
}
for idx = 0; idx < instrs.length(); idx = idx + 1 {
if idx >= skip_start && idx < skip_end {
continue
}
if sgo_instr_refs_local(instrs[idx], local_idx) {
return true
}
}
false
}
///|
fn sgo_instr_is_empty_void_if(instr : @lib.Instruction) -> Bool {
match instr {
@lib.If(@lib.VoidBlockType, [], None) => true
_ => false
}
}
///|
fn sgo_instr_is_nonzero_i32_const(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I32Const(@lib.I32(value)) => value != 0
_ => false
}
}
///|
fn sgo_cheap_cleanup_compare_if_shell(
instrs : Array[@lib.Instruction],
pos : Int32,
out : Array[@lib.Instruction],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
outside_refs~ : ((@lib.LocalIdx) -> Bool)? = None,
) -> Int32? {
match
sgo_scalar_call_parent_cleanup_prefix(instrs, pos, mod_, func_type_indices) {
Some((replay, result_kind, suffix_start, prefix_start, prefix_end)) => {
match
sgo_scalar_pure_suffix_cleanup_end(
instrs, suffix_start, result_kind, false, prefix_start, prefix_end,
) {
Some(next_pos) => {
if replay.length() > 0 {
out.append(replay)
out.push(@lib.Instruction::drop())
}
return Some(next_pos)
}
None => ()
}
match
sgo_scalar_pure_suffix_cleanup_end(
instrs, suffix_start, result_kind, true, prefix_start, prefix_end,
) {
Some(next_pos) => {
if replay.length() > 0 {
out.append(replay)
out.push(@lib.Instruction::drop())
}
return Some(next_pos)
}
None => ()
}
}
None => ()
}
match
sgo_float_call_binary_compare_prefix_func(
instrs, pos, mod_, func_type_indices,
) {
Some(func_idx) => {
if pos + 5 < instrs.length() &&
sgo_instr_is_empty_void_if(instrs[pos + 5]) {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 6)
}
if pos + 7 < instrs.length() &&
sgo_is_return_guard(instrs[pos + 5]) &&
sgo_const_instr(instrs[pos + 6]) != None &&
instrs[pos + 7] == @lib.Instruction::drop() {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 8)
}
}
None => ()
}
if pos + 5 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
) {
(
@lib.I64Const(_),
@lib.I64Const(_),
@lib.Call(func_idx),
op,
compare,
if_instr,
)
| (
@lib.I64Const(_),
@lib.Call(func_idx),
@lib.I64Const(_),
op,
compare,
if_instr,
) if sgo_is_nontrapping_i64_binary_flow_instr(op) &&
sgo_is_i64_equality_compare(compare) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 6)
}
_ => ()
}
}
if pos + 2 < instrs.length() {
match (instrs[pos], instrs[pos + 1], instrs[pos + 2]) {
(@lib.I64Const(_), @lib.Call(func_idx), op) if sgo_is_nontrapping_i64_binary_flow_instr(
op,
) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
match sgo_i64_pure_suffix_empty_if_end(instrs, pos + 3) {
Some(next_pos) => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(next_pos)
}
None => ()
}
(@lib.Call(func_idx), @lib.I64Const(_), op) if sgo_is_nontrapping_i64_binary_flow_instr(
op,
) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
match sgo_i64_pure_suffix_empty_if_end(instrs, pos + 3) {
Some(next_pos) => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(next_pos)
}
None => ()
}
_ => ()
}
}
if pos + 5 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
) {
(
@lib.I32Const(_),
@lib.I32Const(_),
@lib.Call(func_idx),
op,
compare,
if_instr,
)
| (
@lib.I32Const(_),
@lib.Call(func_idx),
@lib.I32Const(_),
op,
compare,
if_instr,
) if sgo_is_nontrapping_i32_binary_flow_instr(op) &&
sgo_is_compare_instr(compare) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 6)
}
_ => ()
}
}
if pos + 2 < instrs.length() {
match (instrs[pos], instrs[pos + 1], instrs[pos + 2]) {
(@lib.I32Const(_), @lib.Call(func_idx), op) if sgo_is_nontrapping_i32_binary_flow_instr(
op,
) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
match sgo_i32_pure_suffix_empty_if_end(instrs, pos + 3) {
Some(next_pos) => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(next_pos)
}
None => ()
}
(@lib.Call(func_idx), @lib.I32Const(_), op) if sgo_is_nontrapping_i32_binary_flow_instr(
op,
) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
match sgo_i32_pure_suffix_empty_if_end(instrs, pos + 3) {
Some(next_pos) => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(next_pos)
}
None => ()
}
_ => ()
}
}
if pos + 3 < instrs.length() {
match (instrs[pos], instrs[pos + 1], instrs[pos + 2], instrs[pos + 3]) {
(@lib.I32Const(_), @lib.Call(func_idx), compare, if_instr) if (
sgo_is_compare_instr(compare) ||
sgo_is_nontrapping_i32_binary_flow_instr(compare)
) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 4)
}
(@lib.Call(func_idx), @lib.I32Const(_), compare, if_instr) if (
sgo_is_compare_instr(compare) ||
sgo_is_nontrapping_i32_binary_flow_instr(compare)
) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 4)
}
(@lib.I32Const(_), @lib.MemorySize(_), compare, if_instr) if sgo_is_compare_instr(
compare,
) &&
sgo_instr_is_empty_void_if(if_instr) => return Some(pos + 4)
(@lib.MemorySize(_), @lib.I32Const(_), compare, if_instr) if sgo_is_compare_instr(
compare,
) &&
sgo_instr_is_empty_void_if(if_instr) => return Some(pos + 4)
(@lib.I32Const(_), @lib.TableSize(_), compare, if_instr) if sgo_is_compare_instr(
compare,
) &&
sgo_instr_is_empty_void_if(if_instr) => return Some(pos + 4)
(@lib.TableSize(_), @lib.I32Const(_), compare, if_instr) if sgo_is_compare_instr(
compare,
) &&
sgo_instr_is_empty_void_if(if_instr) => return Some(pos + 4)
(
@lib.I32Const(_),
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([value, @lib.GlobalSet(global_idx), result_const])
),
compare,
if_instr,
) if sgo_global_set_result_block_value_is_independent(value) &&
sgo_const_instr(result_const) != None &&
sgo_is_compare_instr(compare) &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(value)
out.push(@lib.Instruction::global_set(global_idx))
return Some(pos + 4)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([value, @lib.GlobalSet(global_idx), result_const])
),
@lib.I32Const(_),
compare,
if_instr,
) if sgo_global_set_result_block_value_is_independent(value) &&
sgo_const_instr(result_const) != None &&
sgo_is_compare_instr(compare) &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(value)
out.push(@lib.Instruction::global_set(global_idx))
return Some(pos + 4)
}
(@lib.I32Const(_), block, compare, if_instr) if sgo_independent_memory_store_result_block_matches(
block,
) &&
sgo_is_compare_instr(compare) &&
sgo_instr_is_empty_void_if(if_instr) => {
sgo_push_memory_store_result_block_effect(block, out)
return Some(pos + 4)
}
(block, @lib.I32Const(_), compare, if_instr) if sgo_independent_memory_store_result_block_matches(
block,
) &&
sgo_is_compare_instr(compare) &&
sgo_instr_is_empty_void_if(if_instr) => {
sgo_push_memory_store_result_block_effect(block, out)
return Some(pos + 4)
}
_ => ()
}
}
if pos + 4 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
) {
(value, @lib.LocalSet(local_idx), @lib.LocalGet(get_idx), eqz, if_instr) if local_idx ==
get_idx &&
eqz == @lib.Instruction::i32_eqz() &&
sgo_local_effect_value_is_independent(value) &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(value)
out.push(@lib.Instruction::local_set(local_idx))
return Some(pos + 5)
}
(const_instr, value, @lib.LocalTee(local_idx), compare, if_instr) if sgo_const_instr(
const_instr,
) !=
None &&
sgo_local_effect_value_is_independent(value) &&
sgo_is_compare_instr(compare) &&
sgo_instr_is_empty_void_if(if_instr) &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 5, outside_refs~) =>
return Some(pos + 5)
(value, @lib.LocalTee(local_idx), const_instr, compare, if_instr) if sgo_const_instr(
const_instr,
) !=
None &&
sgo_local_effect_value_is_independent(value) &&
sgo_is_compare_instr(compare) &&
sgo_instr_is_empty_void_if(if_instr) &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 5, outside_refs~) =>
return Some(pos + 5)
(@lib.I32Const(_), grow_delta, @lib.MemoryGrow(mem_idx), compare, if_instr
) if sgo_memory_grow_arg_is_independent(grow_delta) &&
sgo_is_compare_instr(compare) &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(grow_delta)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 5)
}
(grow_delta, @lib.MemoryGrow(mem_idx), @lib.I32Const(_), compare, if_instr
) if sgo_memory_grow_arg_is_independent(grow_delta) &&
sgo_is_compare_instr(compare) &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(grow_delta)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 5)
}
_ => ()
}
}
if pos + 6 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
) {
(
@lib.I32Const(_),
grow_left,
grow_right,
grow_op,
@lib.MemoryGrow(mem_idx),
compare,
if_instr,
) if sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_is_compare_instr(compare) &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(grow_left)
out.push(grow_right)
out.push(grow_op)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 7)
}
(
grow_left,
grow_right,
grow_op,
@lib.MemoryGrow(mem_idx),
@lib.I32Const(_),
compare,
if_instr,
) if sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_is_compare_instr(compare) &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(grow_left)
out.push(grow_right)
out.push(grow_op)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 7)
}
_ => ()
}
}
if pos + 5 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
) {
(
@lib.I32Const(_),
ref_value,
grow_delta,
@lib.TableGrow(table_idx),
compare,
if_instr,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_table_grow_arg_is_independent(grow_delta) &&
sgo_is_compare_instr(compare) &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(ref_value)
out.push(grow_delta)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 6)
}
(
ref_value,
grow_delta,
@lib.TableGrow(table_idx),
@lib.I32Const(_),
compare,
if_instr,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_table_grow_arg_is_independent(grow_delta) &&
sgo_is_compare_instr(compare) &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(ref_value)
out.push(grow_delta)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 6)
}
_ => ()
}
}
if pos + 7 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
) {
(
@lib.I32Const(_),
ref_value,
grow_left,
grow_right,
grow_op,
@lib.TableGrow(table_idx),
compare,
if_instr,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_is_compare_instr(compare) &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(ref_value)
out.push(grow_left)
out.push(grow_right)
out.push(grow_op)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 8)
}
(
ref_value,
grow_left,
grow_right,
grow_op,
@lib.TableGrow(table_idx),
@lib.I32Const(_),
compare,
if_instr,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_is_compare_instr(compare) &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(ref_value)
out.push(grow_left)
out.push(grow_right)
out.push(grow_op)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 8)
}
_ => ()
}
}
if pos + 7 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
) {
(
@lib.I64Const(_),
@lib.I64Const(_),
@lib.Call(func_idx),
op,
compare,
if_instr,
dropped_const,
@lib.Drop,
)
| (
@lib.I64Const(_),
@lib.Call(func_idx),
@lib.I64Const(_),
op,
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_is_nontrapping_i64_binary_flow_instr(op) &&
sgo_is_i64_equality_compare(compare) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 8)
}
_ => ()
}
}
if pos + 2 < instrs.length() {
match (instrs[pos], instrs[pos + 1], instrs[pos + 2]) {
(@lib.I64Const(_), @lib.Call(func_idx), op) if sgo_is_nontrapping_i64_binary_flow_instr(
op,
) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
match sgo_i64_pure_suffix_if_return_drop_end(instrs, pos + 3) {
Some(next_pos) => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(next_pos)
}
None => ()
}
(@lib.Call(func_idx), @lib.I64Const(_), op) if sgo_is_nontrapping_i64_binary_flow_instr(
op,
) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
match sgo_i64_pure_suffix_if_return_drop_end(instrs, pos + 3) {
Some(next_pos) => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(next_pos)
}
None => ()
}
_ => ()
}
}
if pos + 7 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
) {
(
@lib.I32Const(_),
@lib.I32Const(_),
@lib.Call(func_idx),
op,
compare,
if_instr,
dropped_const,
@lib.Drop,
)
| (
@lib.I32Const(_),
@lib.Call(func_idx),
@lib.I32Const(_),
op,
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_is_nontrapping_i32_binary_flow_instr(op) &&
sgo_is_compare_instr(compare) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 8)
}
_ => ()
}
}
if pos + 2 < instrs.length() {
match (instrs[pos], instrs[pos + 1], instrs[pos + 2]) {
(@lib.I32Const(_), @lib.Call(func_idx), op) if sgo_is_nontrapping_i32_binary_flow_instr(
op,
) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
match sgo_i32_pure_suffix_if_return_drop_end(instrs, pos + 3) {
Some(next_pos) => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(next_pos)
}
None => ()
}
(@lib.Call(func_idx), @lib.I32Const(_), op) if sgo_is_nontrapping_i32_binary_flow_instr(
op,
) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) =>
match sgo_i32_pure_suffix_if_return_drop_end(instrs, pos + 3) {
Some(next_pos) => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(next_pos)
}
None => ()
}
_ => ()
}
}
if pos + 5 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
) {
(
value,
@lib.LocalSet(local_idx),
@lib.LocalGet(get_idx),
const_instr,
compare,
if_instr,
) if pos + 6 == instrs.length() &&
local_idx == get_idx &&
sgo_local_effect_value_is_independent(value) &&
sgo_const_instr(const_instr) != None &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) => {
out.push(value)
out.push(@lib.Instruction::local_set(local_idx))
return Some(pos + 6)
}
(
@lib.I32Const(_),
@lib.Call(func_idx),
compare,
if_instr,
dropped_const,
@lib.Drop,
) if (
sgo_is_compare_instr(compare) ||
sgo_is_nontrapping_i32_binary_flow_instr(compare)
) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 6)
}
(
@lib.Call(func_idx),
@lib.I32Const(_),
compare,
if_instr,
dropped_const,
@lib.Drop,
) if (
sgo_is_compare_instr(compare) ||
sgo_is_nontrapping_i32_binary_flow_instr(compare)
) &&
sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 6)
}
(
@lib.I32Const(_),
@lib.MemorySize(_),
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => return Some(pos + 6)
(
@lib.MemorySize(_),
@lib.I32Const(_),
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => return Some(pos + 6)
(
@lib.I32Const(_),
@lib.TableSize(_),
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => return Some(pos + 6)
(
@lib.TableSize(_),
@lib.I32Const(_),
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => return Some(pos + 6)
(
@lib.I32Const(_),
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([value, @lib.GlobalSet(global_idx), result_const])
),
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_global_set_result_block_value_is_independent(value) &&
sgo_const_instr(result_const) != None &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(value)
out.push(@lib.Instruction::global_set(global_idx))
return Some(pos + 6)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([value, @lib.GlobalSet(global_idx), result_const])
),
@lib.I32Const(_),
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_global_set_result_block_value_is_independent(value) &&
sgo_const_instr(result_const) != None &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(value)
out.push(@lib.Instruction::global_set(global_idx))
return Some(pos + 6)
}
(@lib.I32Const(_), block, compare, if_instr, dropped_const, @lib.Drop) if sgo_independent_memory_store_result_block_matches(
block,
) &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
sgo_push_memory_store_result_block_effect(block, out)
return Some(pos + 6)
}
(block, @lib.I32Const(_), compare, if_instr, dropped_const, @lib.Drop) if sgo_independent_memory_store_result_block_matches(
block,
) &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
sgo_push_memory_store_result_block_effect(block, out)
return Some(pos + 6)
}
_ => ()
}
}
if pos + 6 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
) {
(
const_instr,
value,
@lib.LocalTee(local_idx),
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(const_instr) != None &&
sgo_local_effect_value_is_independent(value) &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 7, outside_refs~) =>
return Some(pos + 7)
(
value,
@lib.LocalTee(local_idx),
const_instr,
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(const_instr) != None &&
sgo_local_effect_value_is_independent(value) &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 7, outside_refs~) =>
return Some(pos + 7)
(
@lib.I32Const(_),
grow_delta,
@lib.MemoryGrow(mem_idx),
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_memory_grow_arg_is_independent(grow_delta) &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(grow_delta)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 7)
}
(
grow_delta,
@lib.MemoryGrow(mem_idx),
@lib.I32Const(_),
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_memory_grow_arg_is_independent(grow_delta) &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(grow_delta)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 7)
}
_ => ()
}
}
if pos + 8 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
instrs[pos + 8],
) {
(
@lib.I32Const(_),
grow_left,
grow_right,
grow_op,
@lib.MemoryGrow(mem_idx),
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(grow_left)
out.push(grow_right)
out.push(grow_op)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 9)
}
(
grow_left,
grow_right,
grow_op,
@lib.MemoryGrow(mem_idx),
@lib.I32Const(_),
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(grow_left)
out.push(grow_right)
out.push(grow_op)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 9)
}
_ => ()
}
}
if pos + 7 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
) {
(
@lib.I32Const(_),
ref_value,
grow_delta,
@lib.TableGrow(table_idx),
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_table_grow_arg_is_independent(grow_delta) &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(ref_value)
out.push(grow_delta)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 8)
}
(
ref_value,
grow_delta,
@lib.TableGrow(table_idx),
@lib.I32Const(_),
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_table_grow_arg_is_independent(grow_delta) &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(ref_value)
out.push(grow_delta)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 8)
}
_ => ()
}
}
if pos + 9 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
instrs[pos + 8],
instrs[pos + 9],
) {
(
@lib.I32Const(_),
ref_value,
grow_left,
grow_right,
grow_op,
@lib.TableGrow(table_idx),
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(ref_value)
out.push(grow_left)
out.push(grow_right)
out.push(grow_op)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 10)
}
(
ref_value,
grow_left,
grow_right,
grow_op,
@lib.TableGrow(table_idx),
@lib.I32Const(_),
compare,
if_instr,
dropped_const,
@lib.Drop,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(ref_value)
out.push(grow_left)
out.push(grow_right)
out.push(grow_op)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 10)
}
_ => ()
}
}
None
}
///|
fn sgo_cheap_cleanup_select_if_shell(
instrs : Array[@lib.Instruction],
pos : Int32,
out : Array[@lib.Instruction],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
outside_refs~ : ((@lib.LocalIdx) -> Bool)? = None,
) -> Int32? {
if pos == 0 && pos + 4 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
) {
(
@lib.I32Const(_),
@lib.Call(func_idx),
const_instr,
@lib.Select(_),
if_instr,
) =>
if sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 5)
}
(
@lib.Call(func_idx),
@lib.I32Const(_),
const_instr,
@lib.Select(_),
if_instr,
) =>
if sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 5)
}
(
@lib.I32Const(_),
@lib.MemorySize(_),
const_instr,
@lib.Select(_),
if_instr,
) =>
if sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) {
return Some(pos + 5)
}
(
@lib.MemorySize(_),
@lib.I32Const(_),
const_instr,
@lib.Select(_),
if_instr,
) =>
if sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) {
return Some(pos + 5)
}
(
@lib.I32Const(_),
@lib.TableSize(_),
const_instr,
@lib.Select(_),
if_instr,
) =>
if sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) {
return Some(pos + 5)
}
(
@lib.TableSize(_),
@lib.I32Const(_),
const_instr,
@lib.Select(_),
if_instr,
) =>
if sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) {
return Some(pos + 5)
}
_ => ()
}
}
if pos == 0 && pos + 5 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
) {
(
@lib.I32Const(_),
value,
@lib.LocalTee(local_idx),
const_instr,
@lib.Select(_),
if_instr,
) if sgo_const_instr(value) != None &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 6, outside_refs~) =>
return Some(pos + 6)
(
value,
@lib.LocalTee(local_idx),
@lib.I32Const(_),
const_instr,
@lib.Select(_),
if_instr,
) if sgo_const_instr(value) != None &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 6, outside_refs~) =>
return Some(pos + 6)
_ => ()
}
}
if pos == 0 && pos + 5 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
) {
(
@lib.I32Const(_),
grow_delta,
@lib.MemoryGrow(mem_idx),
const_instr,
@lib.Select(_),
if_instr,
) =>
if sgo_memory_grow_arg_is_independent(grow_delta) &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) {
out.push(grow_delta)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 6)
}
(
grow_delta,
@lib.MemoryGrow(mem_idx),
@lib.I32Const(_),
const_instr,
@lib.Select(_),
if_instr,
) =>
if sgo_memory_grow_arg_is_independent(grow_delta) &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) {
out.push(grow_delta)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 6)
}
_ => ()
}
}
if pos == 0 && pos + 7 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
) {
(
@lib.I32Const(_),
grow_left,
grow_right,
grow_op,
@lib.MemoryGrow(mem_idx),
const_instr,
@lib.Select(_),
if_instr,
) =>
if sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) {
out.push(grow_left)
out.push(grow_right)
out.push(grow_op)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 8)
}
(
grow_left,
grow_right,
grow_op,
@lib.MemoryGrow(mem_idx),
@lib.I32Const(_),
const_instr,
@lib.Select(_),
if_instr,
) =>
if sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) {
out.push(grow_left)
out.push(grow_right)
out.push(grow_op)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 8)
}
_ => ()
}
}
if pos + 6 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
) {
(
@lib.I32Const(_),
@lib.Call(func_idx),
const_instr,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_const_instr(const_instr) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 7)
}
(
@lib.Call(func_idx),
@lib.I32Const(_),
const_instr,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_const_instr(const_instr) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 7)
}
(
@lib.I32Const(_),
@lib.MemorySize(_),
const_instr,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(const_instr) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => return Some(pos + 7)
(
@lib.MemorySize(_),
@lib.I32Const(_),
const_instr,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(const_instr) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => return Some(pos + 7)
(
@lib.I32Const(_),
@lib.TableSize(_),
const_instr,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(const_instr) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => return Some(pos + 7)
(
@lib.TableSize(_),
@lib.I32Const(_),
const_instr,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(const_instr) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => return Some(pos + 7)
(
@lib.I32Const(_),
ref_value,
grow_delta,
@lib.TableGrow(table_idx),
const_instr,
@lib.Select(_),
if_instr,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_table_grow_arg_is_independent(grow_delta) &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(ref_value)
out.push(grow_delta)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 7)
}
(
ref_value,
grow_delta,
@lib.TableGrow(table_idx),
@lib.I32Const(_),
const_instr,
@lib.Select(_),
if_instr,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_table_grow_arg_is_independent(grow_delta) &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(ref_value)
out.push(grow_delta)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 7)
}
(
@lib.I32Const(_),
@lib.I32Const(addr),
@lib.I32Load(memarg),
@lib.LocalTee(local_idx),
cond,
@lib.Select(_),
if_instr,
) if sgo_instr_is_nonzero_i32_const(cond) &&
sgo_instr_is_empty_void_if(if_instr) &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 7, outside_refs~) => {
out.push(@lib.Instruction::i32_const(addr))
out.push(@lib.Instruction::i32_load(memarg))
out.push(@lib.Instruction::drop())
return Some(pos + 7)
}
_ => ()
}
}
if pos == 0 && pos + 8 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
instrs[pos + 8],
) {
(
@lib.I32Const(_),
ref_value,
grow_left,
grow_right,
grow_op,
@lib.TableGrow(table_idx),
const_instr,
@lib.Select(_),
if_instr,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(ref_value)
out.push(grow_left)
out.push(grow_right)
out.push(grow_op)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 9)
}
(
ref_value,
grow_left,
grow_right,
grow_op,
@lib.TableGrow(table_idx),
@lib.I32Const(_),
const_instr,
@lib.Select(_),
if_instr,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(ref_value)
out.push(grow_left)
out.push(grow_right)
out.push(grow_op)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 9)
}
_ => ()
}
}
if pos + 7 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
) {
(
@lib.I32Const(_),
value,
@lib.LocalTee(local_idx),
const_instr,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(value) != None &&
sgo_const_instr(const_instr) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 8, outside_refs~) =>
return Some(pos + 8)
(
value,
@lib.LocalTee(local_idx),
@lib.I32Const(_),
const_instr,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(value) != None &&
sgo_const_instr(const_instr) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 8, outside_refs~) =>
return Some(pos + 8)
(
@lib.I32Const(_),
grow_delta,
@lib.MemoryGrow(mem_idx),
const_instr,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_memory_grow_arg_is_independent(grow_delta) &&
sgo_const_instr(const_instr) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(grow_delta)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 8)
}
(
grow_delta,
@lib.MemoryGrow(mem_idx),
@lib.I32Const(_),
const_instr,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_memory_grow_arg_is_independent(grow_delta) &&
sgo_const_instr(const_instr) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(grow_delta)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 8)
}
_ => ()
}
}
if pos + 9 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
instrs[pos + 8],
instrs[pos + 9],
) {
(
@lib.I32Const(_),
grow_left,
grow_right,
grow_op,
@lib.MemoryGrow(mem_idx),
const_instr,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_const_instr(const_instr) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(grow_left)
out.push(grow_right)
out.push(grow_op)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 10)
}
(
grow_left,
grow_right,
grow_op,
@lib.MemoryGrow(mem_idx),
@lib.I32Const(_),
const_instr,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_const_instr(const_instr) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(grow_left)
out.push(grow_right)
out.push(grow_op)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 10)
}
_ => ()
}
}
if pos + 8 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
instrs[pos + 8],
) {
(
@lib.I32Const(_),
ref_value,
grow_delta,
@lib.TableGrow(table_idx),
const_instr,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_table_grow_arg_is_independent(grow_delta) &&
sgo_const_instr(const_instr) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(ref_value)
out.push(grow_delta)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 9)
}
(
ref_value,
grow_delta,
@lib.TableGrow(table_idx),
@lib.I32Const(_),
const_instr,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_table_grow_arg_is_independent(grow_delta) &&
sgo_const_instr(const_instr) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(ref_value)
out.push(grow_delta)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 9)
}
_ => ()
}
}
if pos + 10 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
instrs[pos + 8],
instrs[pos + 9],
instrs[pos + 10],
) {
(
@lib.I32Const(_),
ref_value,
grow_left,
grow_right,
grow_op,
@lib.TableGrow(table_idx),
const_instr,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_const_instr(const_instr) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(ref_value)
out.push(grow_left)
out.push(grow_right)
out.push(grow_op)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 11)
}
(
ref_value,
grow_left,
grow_right,
grow_op,
@lib.TableGrow(table_idx),
@lib.I32Const(_),
const_instr,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_table_grow_arg_is_independent(ref_value) &&
sgo_i32_add_grow_arg_is_independent(grow_left, grow_right, grow_op) &&
sgo_const_instr(const_instr) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(ref_value)
out.push(grow_left)
out.push(grow_right)
out.push(grow_op)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 11)
}
_ => ()
}
}
if pos + 8 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
instrs[pos + 8],
) {
(
@lib.I32Const(addr),
@lib.I32Load(memarg),
@lib.I32Const(_),
@lib.LocalTee(local_idx),
cond,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if pos + 9 == instrs.length() &&
sgo_instr_is_nonzero_i32_const(cond) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 9, outside_refs~) => {
out.push(@lib.Instruction::i32_const(addr))
out.push(@lib.Instruction::i32_load(memarg))
out.push(@lib.Instruction::drop())
return Some(pos + 9)
}
(
@lib.I32Const(addr),
@lib.I32Load(memarg),
@lib.I32Const(_),
@lib.LocalTee(local_idx),
cond,
@lib.Select(_),
if_instr,
@lib.LocalGet(get_local),
@lib.Drop,
) if local_idx == get_local &&
sgo_instr_is_nonzero_i32_const(cond) &&
sgo_is_return_guard(if_instr) &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 9, outside_refs~) => {
out.push(@lib.Instruction::i32_const(addr))
out.push(@lib.Instruction::i32_load(memarg))
out.push(@lib.Instruction::drop())
return Some(pos + 9)
}
(
@lib.I32Const(addr),
@lib.I32Load(memarg),
@lib.I32Const(_),
@lib.LocalTee(local_idx),
cond,
@lib.Select(_),
if_instr,
@lib.LocalGet(get_local),
@lib.Drop,
) if local_idx == get_local &&
sgo_instr_is_nonzero_i32_const(cond) &&
sgo_instr_is_empty_void_if(if_instr) &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 9, outside_refs~) => {
out.push(@lib.Instruction::i32_const(addr))
out.push(@lib.Instruction::i32_load(memarg))
out.push(@lib.Instruction::drop())
return Some(pos + 9)
}
_ => ()
}
}
if pos + 7 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
) {
(
@lib.I32Const(_),
@lib.I32Const(addr),
@lib.I32Load(memarg),
@lib.LocalTee(local_idx),
cond,
@lib.Select(_),
@lib.I32Eqz,
if_instr,
) if sgo_instr_is_nonzero_i32_const(cond) &&
sgo_instr_is_empty_void_if(if_instr) &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 8, outside_refs~) => {
out.push(@lib.Instruction::i32_const(addr))
out.push(@lib.Instruction::i32_load(memarg))
out.push(@lib.Instruction::drop())
return Some(pos + 8)
}
_ => ()
}
}
if pos + 10 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
instrs[pos + 8],
instrs[pos + 9],
instrs[pos + 10],
) {
(
@lib.I32Const(addr),
@lib.I32Load(memarg),
@lib.I32Const(_),
@lib.LocalTee(local_idx),
@lib.I32Const(@lib.I32(lhs)),
@lib.I32Const(@lib.I32(rhs)),
@lib.I32Add,
@lib.Select(_),
if_instr,
dropped_const,
@lib.Drop,
) if pos + 11 == instrs.length() &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 11, outside_refs~) =>
match sgo_i32_add_no_overflow(lhs, rhs) {
Some(sum) if sum != 0 => {
out.push(@lib.Instruction::i32_const(addr))
out.push(@lib.Instruction::i32_load(memarg))
out.push(@lib.Instruction::drop())
return Some(pos + 11)
}
_ => ()
}
(
@lib.I32Const(addr),
@lib.I32Load(memarg),
@lib.I32Const(_),
@lib.LocalTee(local_idx),
@lib.I32Const(@lib.I32(lhs)),
@lib.I32Const(@lib.I32(rhs)),
@lib.I32Add,
@lib.Select(_),
if_instr,
@lib.LocalGet(get_local),
@lib.Drop,
) if pos + 11 == instrs.length() &&
local_idx == get_local &&
sgo_is_return_guard(if_instr) &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 11, outside_refs~) =>
match sgo_i32_add_no_overflow(lhs, rhs) {
Some(sum) if sum != 0 => {
out.push(@lib.Instruction::i32_const(addr))
out.push(@lib.Instruction::i32_load(memarg))
out.push(@lib.Instruction::drop())
return Some(pos + 11)
}
_ => ()
}
_ => ()
}
}
if pos + 9 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
instrs[pos + 8],
instrs[pos + 9],
) {
(
@lib.I32Const(addr),
@lib.I32Load(memarg),
@lib.I32Const(_),
@lib.LocalTee(local_idx),
cond,
@lib.Select(_),
@lib.I32Eqz,
if_instr,
@lib.LocalGet(get_local),
@lib.Drop,
) if local_idx == get_local &&
sgo_instr_is_nonzero_i32_const(cond) &&
sgo_instr_is_empty_void_if(if_instr) &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 10, outside_refs~) => {
out.push(@lib.Instruction::i32_const(addr))
out.push(@lib.Instruction::i32_load(memarg))
out.push(@lib.Instruction::drop())
return Some(pos + 10)
}
(
@lib.I32Const(addr),
@lib.I32Load(memarg),
@lib.I32Const(_),
@lib.LocalTee(local_idx),
cond,
@lib.Select(_),
@lib.I32Eqz,
if_instr,
dropped_const,
@lib.Drop,
) if pos + 10 == instrs.length() &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None &&
sgo_instr_is_nonzero_i32_const(cond) &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 10, outside_refs~) => {
out.push(@lib.Instruction::i32_const(addr))
out.push(@lib.Instruction::i32_load(memarg))
out.push(@lib.Instruction::drop())
return Some(pos + 10)
}
_ => ()
}
}
if pos + 7 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
instrs[pos + 7],
) {
(
@lib.I32Const(addr),
@lib.I32Load(memarg),
@lib.I32Const(_),
@lib.LocalTee(local_idx),
cond,
@lib.Select(_),
@lib.I32Eqz,
if_instr,
) if pos + 8 == instrs.length() &&
sgo_is_return_guard(if_instr) &&
sgo_instr_is_nonzero_i32_const(cond) &&
!sgo_instrs_ref_local(instrs, local_idx, pos, pos + 8, outside_refs~) => {
out.push(@lib.Instruction::i32_const(addr))
out.push(@lib.Instruction::i32_load(memarg))
out.push(@lib.Instruction::drop())
return Some(pos + 8)
}
_ => ()
}
}
if pos + 5 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
) {
(
@lib.I32Const(addr),
@lib.I32Load(memarg),
@lib.I32Const(_),
cond,
@lib.Select(_),
if_instr,
) if pos + 6 == instrs.length() &&
sgo_is_return_guard(if_instr) &&
sgo_instr_is_nonzero_i32_const(cond) => {
out.push(@lib.Instruction::i32_const(addr))
out.push(@lib.Instruction::i32_load(memarg))
out.push(@lib.Instruction::drop())
return Some(pos + 6)
}
_ => ()
}
}
if pos + 6 < instrs.length() {
match
(
instrs[pos],
instrs[pos + 1],
instrs[pos + 2],
instrs[pos + 3],
instrs[pos + 4],
instrs[pos + 5],
instrs[pos + 6],
) {
(
@lib.I32Const(addr),
@lib.I32Load(memarg),
@lib.I32Const(_),
cond,
@lib.Select(_),
@lib.I32Eqz,
if_instr,
) if pos + 7 == instrs.length() &&
sgo_is_return_guard(if_instr) &&
sgo_instr_is_nonzero_i32_const(cond) => {
out.push(@lib.Instruction::i32_const(addr))
out.push(@lib.Instruction::i32_load(memarg))
out.push(@lib.Instruction::drop())
return Some(pos + 7)
}
_ => ()
}
}
None
}
///|
fn sgo_cheap_cleanup_block_if_shell(
instrs : Array[@lib.Instruction],
pos : Int32,
out : Array[@lib.Instruction],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Int32? {
if pos + 1 < instrs.length() {
match (instrs[pos], instrs[pos + 1]) {
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([@lib.Call(func_idx), @lib.Drop, const_instr])
),
if_instr,
) if sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 2)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([@lib.Call(func_idx), const_instr])
),
if_instr,
) if sgo_call_has_no_params_no_results(mod_, func_type_indices, func_idx) &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(@lib.Instruction::call(func_idx))
return Some(pos + 2)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([@lib.MemorySize(_), @lib.Drop, const_instr])
),
if_instr,
) if sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => return Some(pos + 2)
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([@lib.TableSize(_), @lib.Drop, const_instr])
),
if_instr,
) if sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => return Some(pos + 2)
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([value, @lib.LocalSet(local_idx), const_instr])
),
if_instr,
) if sgo_local_effect_value_is_independent(value) &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(value)
out.push(@lib.Instruction::local_set(local_idx))
return Some(pos + 2)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr(
[
value,
@lib.LocalSet(local_idx),
const_instr,
@lib.LocalGet(get_idx),
compare,
]
)
),
if_instr,
) if local_idx == get_idx &&
sgo_local_effect_value_is_independent(value) &&
sgo_const_instr(const_instr) != None &&
sgo_is_compare_instr(compare) &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(value)
out.push(@lib.Instruction::local_set(local_idx))
return Some(pos + 2)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr(
[
value,
@lib.LocalSet(local_idx),
@lib.LocalGet(get_idx),
const_instr,
compare,
]
)
),
if_instr,
) if local_idx == get_idx &&
sgo_local_effect_value_is_independent(value) &&
sgo_const_instr(const_instr) != None &&
sgo_is_compare_instr(compare) &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(value)
out.push(@lib.Instruction::local_set(local_idx))
return Some(pos + 2)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr(
[
value,
@lib.LocalSet(local_idx),
const_instr,
@lib.LocalGet(get_idx),
compare,
]
)
),
if_instr,
) if pos + 2 == instrs.length() &&
local_idx == get_idx &&
sgo_local_effect_value_is_independent(value) &&
sgo_const_instr(const_instr) != None &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) => {
out.push(value)
out.push(@lib.Instruction::local_set(local_idx))
return Some(pos + 2)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr(
[
value,
@lib.LocalSet(local_idx),
@lib.LocalGet(get_idx),
const_instr,
compare,
]
)
),
if_instr,
) if pos + 2 == instrs.length() &&
local_idx == get_idx &&
sgo_local_effect_value_is_independent(value) &&
sgo_const_instr(const_instr) != None &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) => {
out.push(value)
out.push(@lib.Instruction::local_set(local_idx))
return Some(pos + 2)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([value, @lib.GlobalSet(global_idx), const_instr])
),
if_instr,
) if sgo_const_instr(value) != None &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(value)
out.push(@lib.Instruction::global_set(global_idx))
return Some(pos + 2)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([value, @lib.LocalTee(local_idx), @lib.Drop, const_instr])
),
if_instr,
) if sgo_local_effect_value_is_independent(value) &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(value)
out.push(@lib.Instruction::local_set(local_idx))
return Some(pos + 2)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr(
[grow_delta, @lib.MemoryGrow(mem_idx), @lib.Drop, const_instr]
)
),
if_instr,
) if sgo_const_instr(grow_delta) != None &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(grow_delta)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 2)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr(
[
ref_value,
grow_delta,
@lib.TableGrow(table_idx),
@lib.Drop,
const_instr,
]
)
),
if_instr,
) if sgo_const_instr(ref_value) != None &&
sgo_const_instr(grow_delta) != None &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(ref_value)
out.push(grow_delta)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 2)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([dest, value, @lib.TableSet(table_idx), const_instr])
),
if_instr,
) if sgo_const_instr(dest) != None &&
sgo_const_instr(value) != None &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(dest)
out.push(value)
out.push(@lib.Instruction::table_set(table_idx))
return Some(pos + 2)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([dest, value, len, @lib.TableFill(table_idx), const_instr])
),
if_instr,
) if sgo_const_instr(dest) != None &&
sgo_const_instr(value) != None &&
sgo_const_instr(len) != None &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(dest)
out.push(value)
out.push(len)
out.push(@lib.Instruction::table_fill(table_idx))
return Some(pos + 2)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr(
[dest, src, len, @lib.TableCopy(dst_idx, src_idx), const_instr]
)
),
if_instr,
) if sgo_const_instr(dest) != None &&
sgo_const_instr(src) != None &&
sgo_const_instr(len) != None &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(dest)
out.push(src)
out.push(len)
out.push(@lib.Instruction::table_copy(dst_idx, src_idx))
return Some(pos + 2)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr(
[dest, src, len, @lib.TableInit(elem_idx, table_idx), const_instr]
)
),
if_instr,
) if sgo_const_instr(dest) != None &&
sgo_const_instr(src) != None &&
sgo_const_instr(len) != None &&
sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(dest)
out.push(src)
out.push(len)
out.push(@lib.Instruction::table_init(elem_idx, table_idx))
return Some(pos + 2)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([@lib.ElemDrop(elem_idx), const_instr])
),
if_instr,
) if sgo_const_instr(const_instr) != None &&
sgo_instr_is_empty_void_if(if_instr) => {
out.push(@lib.Instruction::elem_drop(elem_idx))
return Some(pos + 2)
}
_ => ()
}
}
if pos + 3 < instrs.length() {
match (instrs[pos], instrs[pos + 1], instrs[pos + 2], instrs[pos + 3]) {
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([@lib.Call(func_idx), @lib.Drop, cond_const])
),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_call_has_no_params_one_result(mod_, func_type_indices, func_idx) &&
sgo_const_instr(cond_const) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(@lib.Instruction::call(func_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 4)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([@lib.Call(func_idx), cond_const])
),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_call_has_no_params_no_results(mod_, func_type_indices, func_idx) &&
sgo_const_instr(cond_const) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(@lib.Instruction::call(func_idx))
return Some(pos + 4)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([@lib.MemorySize(_), @lib.Drop, cond_const])
),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(cond_const) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => return Some(pos + 4)
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([@lib.TableSize(_), @lib.Drop, cond_const])
),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(cond_const) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => return Some(pos + 4)
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([value, @lib.LocalSet(local_idx), cond_const])
),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_local_effect_value_is_independent(value) &&
sgo_const_instr(cond_const) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(value)
out.push(@lib.Instruction::local_set(local_idx))
return Some(pos + 4)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr(
[
value,
@lib.LocalSet(local_idx),
cond_const,
@lib.LocalGet(get_idx),
compare,
]
)
),
if_instr,
dropped_const,
@lib.Drop,
) if local_idx == get_idx &&
sgo_local_effect_value_is_independent(value) &&
sgo_const_instr(cond_const) != None &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(value)
out.push(@lib.Instruction::local_set(local_idx))
return Some(pos + 4)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr(
[
value,
@lib.LocalSet(local_idx),
@lib.LocalGet(get_idx),
cond_const,
compare,
]
)
),
if_instr,
dropped_const,
@lib.Drop,
) if local_idx == get_idx &&
sgo_local_effect_value_is_independent(value) &&
sgo_const_instr(cond_const) != None &&
sgo_is_compare_instr(compare) &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(value)
out.push(@lib.Instruction::local_set(local_idx))
return Some(pos + 4)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([value, @lib.GlobalSet(global_idx), cond_const])
),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(value) != None &&
sgo_const_instr(cond_const) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(value)
out.push(@lib.Instruction::global_set(global_idx))
return Some(pos + 4)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([value, @lib.LocalTee(local_idx), @lib.Drop, cond_const])
),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(value) != None &&
sgo_const_instr(cond_const) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(value)
out.push(@lib.Instruction::local_set(local_idx))
return Some(pos + 4)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr(
[grow_delta, @lib.MemoryGrow(mem_idx), @lib.Drop, cond_const]
)
),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(grow_delta) != None &&
sgo_const_instr(cond_const) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(grow_delta)
out.push(@lib.Instruction::memory_grow(mem_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 4)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr(
[
ref_value,
grow_delta,
@lib.TableGrow(table_idx),
@lib.Drop,
cond_const,
]
)
),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(ref_value) != None &&
sgo_const_instr(grow_delta) != None &&
sgo_const_instr(cond_const) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(ref_value)
out.push(grow_delta)
out.push(@lib.Instruction::table_grow(table_idx))
out.push(@lib.Instruction::drop())
return Some(pos + 4)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([dest, value, @lib.TableSet(table_idx), cond_const])
),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(dest) != None &&
sgo_const_instr(value) != None &&
sgo_const_instr(cond_const) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(dest)
out.push(value)
out.push(@lib.Instruction::table_set(table_idx))
return Some(pos + 4)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([dest, value, len, @lib.TableFill(table_idx), cond_const])
),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(dest) != None &&
sgo_const_instr(value) != None &&
sgo_const_instr(len) != None &&
sgo_const_instr(cond_const) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(dest)
out.push(value)
out.push(len)
out.push(@lib.Instruction::table_fill(table_idx))
return Some(pos + 4)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr(
[dest, src, len, @lib.TableCopy(dst_idx, src_idx), cond_const]
)
),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(dest) != None &&
sgo_const_instr(src) != None &&
sgo_const_instr(len) != None &&
sgo_const_instr(cond_const) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(dest)
out.push(src)
out.push(len)
out.push(@lib.Instruction::table_copy(dst_idx, src_idx))
return Some(pos + 4)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr(
[dest, src, len, @lib.TableInit(elem_idx, table_idx), cond_const]
)
),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(dest) != None &&
sgo_const_instr(src) != None &&
sgo_const_instr(len) != None &&
sgo_const_instr(cond_const) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(dest)
out.push(src)
out.push(len)
out.push(@lib.Instruction::table_init(elem_idx, table_idx))
return Some(pos + 4)
}
(
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr([@lib.ElemDrop(elem_idx), cond_const])
),
if_instr,
dropped_const,
@lib.Drop,
) if sgo_const_instr(cond_const) != None &&
sgo_is_return_guard(if_instr) &&
sgo_const_instr(dropped_const) != None => {
out.push(@lib.Instruction::elem_drop(elem_idx))
return Some(pos + 4)
}
_ => ()
}
}
None
}
///|
fn sgo_cleanup_unreachable_drop_debris_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let normalized : Array[@lib.Instruction] = []
let mut recursive_changed = false
for instr in instrs {
match instr {
@lib.Block(block_type, body) => {
let (next_body, body_changed) = sgo_cleanup_unreachable_drop_debris_expr(
body,
)
recursive_changed = recursive_changed || body_changed
normalized.push(
if body_changed {
@lib.Instruction::block(block_type, next_body)
} else {
instr
},
)
}
@lib.Loop(block_type, body) => {
let (next_body, body_changed) = sgo_cleanup_unreachable_drop_debris_expr(
body,
)
recursive_changed = recursive_changed || body_changed
normalized.push(
if body_changed {
@lib.Instruction::loop_(block_type, next_body)
} else {
instr
},
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then_expr, then_changed) = sgo_cleanup_unreachable_drop_debris_expr(
@lib.Expr::new(then_body),
)
let @lib.Expr(next_then) = next_then_expr
let mut else_changed = false
let next_else = match else_body {
Some(body) => {
let (next_else_expr, changed) = sgo_cleanup_unreachable_drop_debris_expr(
@lib.Expr::new(body),
)
let @lib.Expr(next_else_body) = next_else_expr
else_changed = changed
Some(next_else_body)
}
None => None
}
recursive_changed = recursive_changed || then_changed || else_changed
normalized.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instr
},
)
}
@lib.TryTable(block_type, catches, body) => {
let (next_body, body_changed) = sgo_cleanup_unreachable_drop_debris_expr(
body,
)
recursive_changed = recursive_changed || body_changed
normalized.push(
if body_changed {
@lib.Instruction::try_table(block_type, catches, next_body)
} else {
instr
},
)
}
_ => normalized.push(instr)
}
}
let out : Array[@lib.Instruction] = []
let mut changed = recursive_changed
let mut pos = 0
while pos < normalized.length() {
if pos + 1 < normalized.length() &&
normalized[pos + 1] == @lib.Instruction::unreachable_() {
match normalized[pos] {
@lib.Block(
@lib.ValTypeBlockType(_)
| @lib.TypeIdxBlockType(_),
@lib.Expr(body)
) =>
match body {
[@lib.Unreachable] => {
out.push(@lib.Instruction::unreachable_())
changed = true
pos += 2
continue
}
[dead_value, @lib.Unreachable] if sgo_const_instr(dead_value) !=
None => {
out.push(dead_value)
out.push(@lib.Instruction::drop())
out.push(@lib.Instruction::unreachable_())
changed = true
pos += 2
continue
}
_ => ()
}
_ => ()
}
}
match normalized[pos] {
@lib.Unreachable => {
let mut next = pos + 1
while next < normalized.length() &&
normalized[next] == @lib.Instruction::drop() {
next += 1
}
if next > pos + 1 &&
next < normalized.length() &&
normalized[next] == @lib.Instruction::unreachable_() {
out.push(@lib.Instruction::unreachable_())
changed = true
pos = next + 1
continue
}
}
_ => ()
}
out.push(normalized[pos])
pos += 1
}
(out, changed)
}
///|
fn sgo_cleanup_unreachable_drop_debris_expr(
expr : @lib.Expr,
) -> (@lib.Expr, Bool) {
let @lib.Expr(instrs) = expr
let (cleaned, changed) = sgo_cleanup_unreachable_drop_debris_instrs(instrs)
(@lib.Expr::new(cleaned), changed)
}
///|
fn sgo_cheap_cleanup_whole_load_select_dead_if(
instrs : Array[@lib.Instruction],
outside_refs~ : ((@lib.LocalIdx) -> Bool)? = None,
) -> Array[@lib.Instruction]? {
match instrs {
[
@lib.I32Const(_),
@lib.I32Const(addr),
@lib.I32Load(memarg),
@lib.LocalTee(local_idx),
@lib.I32Const(_),
@lib.I32Const(_),
@lib.I32Add,
@lib.I32Eqz,
@lib.Select(_),
@lib.If(@lib.VoidBlockType, [dropped_const, @lib.Drop], None),
] if sgo_const_instr(dropped_const) != None &&
!sgo_instrs_ref_local(
instrs,
local_idx,
0,
instrs.length(),
outside_refs~,
) =>
Some([
@lib.Instruction::i32_const(addr),
@lib.Instruction::i32_load(memarg),
@lib.Instruction::drop(),
])
_ => None
}
}
///|
fn sgo_has_prior_local_write(
instrs : Array[@lib.Instruction],
end : Int32,
local_idx : @lib.LocalIdx,
) -> Bool {
let stop = if end < instrs.length() { end } else { instrs.length() }
for idx = 0; idx < stop; idx = idx + 1 {
match instrs[idx] {
@lib.LocalSet(written) | @lib.LocalTee(written) if written == local_idx =>
return true
_ => ()
}
}
false
}
///|
fn sgo_cheap_cleanup_instrs(
instrs : Array[@lib.Instruction],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
outside_refs~ : ((@lib.LocalIdx) -> Bool)? = None,
function_body~ : Array[@lib.Instruction]? = None,
function_root~ : Bool = true,
) -> (Array[@lib.Instruction], Bool) {
let function_body = Some(function_body.unwrap_or(instrs))
// This common post-SGO shell is already final after preserving the load's
// trap/effect. Recognize it before recursive normalization and the general
// compare/select matcher roster.
match sgo_cheap_cleanup_whole_load_select_dead_if(instrs, outside_refs~) {
Some(cleaned) => return (cleaned, true)
None => ()
}
let normalized : Array[@lib.Instruction] = []
let mut recursive_changed = false
for instr in instrs {
match instr {
@lib.Block(block_type, body) => {
let (next_body, body_changed) = sgo_cheap_cleanup_expr(
body,
mod_,
func_type_indices,
function_body~,
function_root=false,
)
recursive_changed = recursive_changed || body_changed
normalized.push(
if body_changed {
@lib.Instruction::block(block_type, next_body)
} else {
instr
},
)
}
@lib.Loop(block_type, body) => {
let (next_body, body_changed) = sgo_cheap_cleanup_expr(
body,
mod_,
func_type_indices,
function_body~,
function_root=false,
)
recursive_changed = recursive_changed || body_changed
normalized.push(
if body_changed {
@lib.Instruction::loop_(block_type, next_body)
} else {
instr
},
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then_expr, then_changed) = sgo_cheap_cleanup_expr(
@lib.Expr::new(then_body),
mod_,
func_type_indices,
function_body~,
function_root=false,
)
let @lib.Expr(next_then) = next_then_expr
let mut else_changed = false
let next_else = match else_body {
Some(body) => {
let (next_else_expr, changed) = sgo_cheap_cleanup_expr(
@lib.Expr::new(body),
mod_,
func_type_indices,
function_body~,
function_root=false,
)
let @lib.Expr(next_else_body) = next_else_expr
else_changed = changed
Some(next_else_body)
}
None => None
}
recursive_changed = recursive_changed || then_changed || else_changed
normalized.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instr
},
)
}
@lib.TryTable(block_type, catches, body) => {
let (next_body, body_changed) = sgo_cheap_cleanup_expr(
body,
mod_,
func_type_indices,
function_body~,
function_root=false,
)
recursive_changed = recursive_changed || body_changed
normalized.push(
if body_changed {
@lib.Instruction::try_table(block_type, catches, next_body)
} else {
instr
},
)
}
_ => normalized.push(instr)
}
}
let out : Array[@lib.Instruction] = []
let mut changed = recursive_changed
let mut pos = 0
while pos < normalized.length() {
let output_start = out.length()
match
sgo_cheap_cleanup_block_if_shell(
normalized, pos, out, mod_, func_type_indices,
) {
Some(next_pos) => {
if sgo_cleanup_can_erase_returns(
normalized, pos, next_pos, function_root,
) {
changed = true
pos = next_pos
continue
}
while out.length() > output_start {
ignore(out.pop())
}
}
None => ()
}
match
sgo_cheap_cleanup_compare_if_shell(
normalized,
pos,
out,
mod_,
func_type_indices,
outside_refs~,
) {
Some(next_pos) => {
if sgo_cleanup_can_erase_returns(
normalized, pos, next_pos, function_root,
) {
changed = true
pos = next_pos
continue
}
while out.length() > output_start {
ignore(out.pop())
}
}
None => ()
}
match
sgo_cheap_cleanup_select_if_shell(
normalized,
pos,
out,
mod_,
func_type_indices,
outside_refs~,
) {
Some(next_pos) => {
if sgo_cleanup_can_erase_returns(
normalized, pos, next_pos, function_root,
) {
changed = true
pos = next_pos
continue
}
while out.length() > output_start {
ignore(out.pop())
}
}
None => ()
}
match normalized[pos] {
@lib.If(
@lib.ValTypeBlockType(@lib.NumTypeValType(@lib.I32NumType)),
_,
Some(_)
) if pos + 1 < normalized.length() &&
sgo_instr_is_empty_void_if(normalized[pos + 1]) => {
// Once SGO removes the guarded write, the empty outer if still
// consumes this result-if's i32 value. Preserve all arm effects and
// traps by replacing only that outer control shell with drop.
out.push(normalized[pos])
out.push(@lib.Instruction::drop())
changed = true
pos = pos + 2
continue
}
_ => ()
}
if pos + 1 < normalized.length() {
match (normalized[pos], normalized[pos + 1]) {
(@lib.LocalGet(local_idx), @lib.Drop) if !sgo_has_prior_local_write(
normalized, pos, local_idx,
) => {
changed = true
pos = pos + 2
continue
}
_ => ()
}
}
if pos + 1 < normalized.length() &&
sgo_const_instr(normalized[pos]) != None &&
normalized[pos + 1] == @lib.Instruction::drop() {
changed = true
pos = pos + 2
continue
}
if pos + 1 < normalized.length() &&
sgo_instr_is_empty_void_if(normalized[pos + 1]) &&
sgo_const_instr(normalized[pos]) != None {
changed = true
pos = pos + 2
continue
}
if pos + 5 < normalized.length() {
match
(
normalized[pos],
normalized[pos + 1],
normalized[pos + 2],
normalized[pos + 3],
normalized[pos + 4],
normalized[pos + 5],
) {
(
@lib.I32Const(@lib.I32(value)),
@lib.LocalTee(local_idx),
@lib.Drop,
@lib.LocalGet(first_get),
@lib.LocalGet(second_get),
@lib.I32Add,
) if local_idx == first_get &&
local_idx == second_get &&
!sgo_instrs_ref_local(
normalized,
local_idx,
pos,
pos + 6,
outside_refs~,
) =>
match sgo_i32_add_no_overflow(value, value) {
Some(sum) => {
out.push(@lib.Instruction::i32_const(@lib.I32(value)))
out.push(@lib.Instruction::local_set(local_idx))
out.push(@lib.Instruction::i32_const(@lib.I32(sum)))
changed = true
pos = pos + 6
continue
}
None => ()
}
_ => ()
}
}
if pos + 3 < normalized.length() {
match
(
normalized[pos],
normalized[pos + 1],
normalized[pos + 2],
normalized[pos + 3],
) {
(value, @lib.LocalTee(local_idx), @lib.Drop, @lib.LocalGet(get_idx)) if local_idx ==
get_idx &&
sgo_local_effect_value_is_independent(value) &&
!sgo_instrs_ref_local(
normalized,
local_idx,
pos,
pos + 4,
outside_refs~,
) => {
out.push(value)
changed = true
pos = pos + 4
continue
}
_ => ()
}
}
if pos + 2 < normalized.length() {
match (normalized[pos], normalized[pos + 1], normalized[pos + 2]) {
(@lib.I32Const(@lib.I32(lhs)), @lib.I32Const(@lib.I32(rhs)), @lib.I32Add
) =>
match sgo_i32_add_no_overflow(lhs, rhs) {
Some(sum) => {
out.push(@lib.Instruction::i32_const(@lib.I32(sum)))
changed = true
pos = pos + 3
continue
}
None => ()
}
_ => ()
}
}
if pos + 1 < normalized.length() {
match (normalized[pos], normalized[pos + 1]) {
(@lib.I32Const(@lib.I32(value)), @lib.I32Eqz) => {
out.push(
@lib.Instruction::i32_const(
@lib.I32(if value == 0 { 1 } else { 0 }),
),
)
changed = true
pos = pos + 2
continue
}
_ => ()
}
}
out.push(normalized[pos])
pos = pos + 1
}
(out, changed)
}
///|
fn sgo_cheap_cleanup_expr(
expr : @lib.Expr,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
function_body~ : Array[@lib.Instruction]? = None,
function_root~ : Bool = true,
) -> (@lib.Expr, Bool) {
let @lib.Expr(original_region) = expr
let full_body = function_body.unwrap_or(original_region)
let cache : @hashmap.HashMap[@lib.LocalIdx, Bool] = @hashmap.new()
let outside_refs = Some(fn(local_idx) {
match cache.get(local_idx) {
Some(result) => result
None => {
let result = sgo_local_ref_count(full_body, local_idx) >
sgo_local_ref_count(original_region, local_idx)
cache.set(local_idx, result)
result
}
}
})
let mut current = expr
let mut any_changed = false
let mut keep_going = true
while keep_going {
let @lib.Expr(instrs) = current
let (cleaned, changed) = sgo_cheap_cleanup_instrs(
instrs,
mod_,
func_type_indices,
outside_refs~,
function_body=Some(full_body),
function_root~,
)
if changed {
current = @lib.Expr::new(cleaned)
any_changed = true
} else {
keep_going = false
}
}
(current, any_changed)
}
///|
fn sgo_expr_is_final_runtime_set_get_shape(expr : @lib.Expr) -> Bool {
match expr {
@lib.Expr(
[written, @lib.GlobalSet(_), @lib.LocalGet(_), replacement, @lib.I32Add]
) => sgo_const_instr(written) != None && written == replacement
@lib.Expr(
[written, @lib.GlobalSet(_), replacement, @lib.LocalGet(_), @lib.I32Add]
) => sgo_const_instr(written) != None && written == replacement
_ => false
}
}
///|
fn sgo_rewrite_runtime_trace_expr_with_facts(
expr : @lib.Expr,
infos : Array[SgoGlobalInfo],
facts : Array[@lib.Instruction?],
mutable_imports : Array[Int32],
) -> (@lib.Expr, Bool, Bool) {
let marked = Array::make(facts.length(), false)
let touched : Array[Int32] = []
for slot = 0; slot < facts.length(); slot = slot + 1 {
if facts[slot] is Some(_) {
marked[slot] = true
touched.push(slot)
}
}
sgo_rewrite_runtime_trace_expr_with_sparse_facts(
expr, infos, facts, mutable_imports, touched, marked,
)
}
///|
fn sgo_rewrite_runtime_trace_expr_with_sparse_facts(
expr : @lib.Expr,
infos : Array[SgoGlobalInfo],
facts : Array[@lib.Instruction?],
mutable_imports : Array[Int32],
touched : Array[Int32],
marked : Array[Bool],
) -> (@lib.Expr, Bool, Bool) {
let @lib.Expr(instrs) = expr
let out : Array[@lib.Instruction] = []
let mut changed = false
let mut has_barrier = false
for pos = 0; pos < instrs.length(); pos = pos + 1 {
let instr = instrs[pos]
match instr {
@lib.GlobalGet(idx) => {
let slot = sgo_global_idx_slot(idx)
if sgo_runtime_trace_trackable(infos, slot) {
match facts[slot] {
Some(replacement) => {
out.push(replacement)
changed = true
continue
}
None => ()
}
}
out.push(instr)
}
@lib.GlobalSet(idx) => {
out.push(instr)
let slot = sgo_global_idx_slot(idx)
if sgo_runtime_trace_trackable(infos, slot) {
// Distinct import indices can denote the same host mutable global.
// A write invalidates facts for every type-compatible import alias.
if infos[slot].imported {
for other in mutable_imports {
let may_alias = match
(infos[other].value_type, infos[slot].value_type) {
// Distinct defined-type indices may be structurally equal.
(@lib.RefTypeValType(_), @lib.RefTypeValType(_)) => true
(lhs, rhs) => lhs == rhs
}
if may_alias {
facts[other] = None
}
}
}
let replacement = if pos > 0 {
sgo_const_instr(instrs[pos - 1])
} else {
None
}
if replacement is Some(_) && !marked[slot] {
marked[slot] = true
touched.push(slot)
}
facts[slot] = replacement
}
}
@lib.Block(block_type, body) => {
let (next_body, body_changed, body_has_barrier) = sgo_rewrite_runtime_trace_expr_with_sparse_facts(
body, infos, facts, mutable_imports, touched, marked,
)
has_barrier = has_barrier || body_has_barrier
out.push(
if body_changed {
@lib.Instruction::block(block_type, next_body)
} else {
instr
},
)
changed = changed || body_changed
if body_has_barrier {
sgo_runtime_trace_clear(facts, touched, marked)
}
}
@lib.If(block_type, then_body, Some(else_body)) => {
let (next_then, then_changed, _) = sgo_rewrite_runtime_trace_expr_with_sparse_facts(
@lib.Expr::new(then_body),
infos,
facts,
mutable_imports,
touched,
marked,
)
let @lib.Expr(next_then_body) = next_then
out.push(
if then_changed {
@lib.Instruction::if_(block_type, next_then_body, Some(else_body))
} else {
instr
},
)
changed = changed || then_changed
has_barrier = true
sgo_runtime_trace_clear(facts, touched, marked)
}
@lib.If(block_type, then_body, None) => {
let (next_then, then_changed, _) = sgo_rewrite_runtime_trace_expr_with_sparse_facts(
@lib.Expr::new(then_body),
infos,
facts,
mutable_imports,
touched,
marked,
)
let @lib.Expr(next_then_body) = next_then
out.push(
if then_changed {
@lib.Instruction::if_(block_type, next_then_body, None)
} else {
instr
},
)
changed = changed || then_changed
has_barrier = true
sgo_runtime_trace_clear(facts, touched, marked)
}
_ => {
out.push(instr)
if sgo_runtime_trace_barrier(instr) {
has_barrier = true
sgo_runtime_trace_clear(facts, touched, marked)
}
}
}
}
(@lib.Expr::new(out), changed, has_barrier)
}
///|
fn sgo_rewrite_code_sec(
mod_ : @lib.Module,
code_sec : @lib.CodeSec?,
infos : Array[SgoGlobalInfo],
consts : Array[@lib.Instruction?],
aliases : Array[@lib.GlobalIdx?],
remove_sets : Array[Bool],
) -> (@lib.CodeSec?, Array[Bool], Bool) {
let func_type_indices = sgo_collect_func_type_indices(mod_)
match code_sec {
Some(@lib.CodeSec(funcs)) => {
let out : Array[@lib.Func] = []
let touched = Array::make(funcs.length(), false)
let runtime_workspace = SgoRuntimeTraceWorkspace::new(infos)
let mut changed = false
for idx = 0; idx < funcs.length(); idx = idx + 1 {
let @lib.Func(locals, body) = funcs[idx]
if !sgo_expr_may_reference_global(body) {
let @lib.Expr(original_instrs) = body
let cleanup_allowed = locals.length() <= 192 &&
original_instrs.length() <= 1000
let (cleaned, cleanup_changed) = if cleanup_allowed {
sgo_cleanup_unreachable_drop_debris_expr(body)
} else {
(body, false)
}
if cleanup_changed {
out.push(@lib.Func::new(locals, cleaned))
changed = true
} else {
out.push(funcs[idx])
}
continue
}
let (rewritten, func_changed) = sgo_rewrite_expr(
body, consts, aliases, remove_sets, true,
)
let (runtime_rewritten, runtime_changed) = sgo_rewrite_runtime_trace_expr_with_workspace(
rewritten, infos, runtime_workspace,
)
let @lib.Expr(original_instrs) = body
let cleanup_allowed = locals.length() <= 192 &&
original_instrs.length() <= 1000
let (cleaned, cleanup_changed) = if cleanup_allowed {
sgo_cheap_cleanup_expr(runtime_rewritten, mod_, func_type_indices)
} else {
(runtime_rewritten, false)
}
if func_changed || runtime_changed || cleanup_changed {
let final_body = match cleaned {
@lib.Expr([]) => @lib.Expr::new([@lib.Instruction::nop()])
_ => cleaned
}
// When the SGO-owned cheap cleanup consumed the debris it created, or
// runtime propagation already produced the same simple set/get shape
// Binaryen leaves behind, do not enqueue large batches for the
// expensive Binaryen-default nested cleanup roster just to rediscover
// that no pass has work.
touched[idx] = !(cleanup_allowed &&
funcs.length() > 256 &&
(
cleanup_changed ||
sgo_expr_is_final_runtime_set_get_shape(final_body)
))
changed = true
out.push(@lib.Func::new(locals, final_body))
} else {
out.push(funcs[idx])
}
}
(Some(@lib.CodeSec::new(out)), touched, changed)
}
None => (None, [], false)
}
}
///|
fn sgo_build_module(
mod_ : @lib.Module,
global_sec : @lib.GlobalSec?,
table_sec : @lib.TableSec?,
elem_sec : @lib.ElemSec?,
data_sec : @lib.DataSec?,
code_sec : @lib.CodeSec?,
) -> @lib.Module {
@lib.Module::new(
custom_secs=mod_.custom_secs,
type_sec=mod_.type_sec,
import_sec=mod_.import_sec,
func_annotation_sec=mod_.func_annotation_sec,
func_sec=mod_.func_sec,
table_sec~,
mem_sec=mod_.mem_sec,
tag_sec=mod_.tag_sec,
stringrefs_sec=mod_.stringrefs_sec,
global_sec~,
export_sec=mod_.export_sec,
start_sec=mod_.start_sec,
elem_sec~,
data_cnt_sec=mod_.data_cnt_sec,
code_sec~,
data_sec~,
name_sec=mod_.name_sec,
raw_name_sec_payload=mod_.raw_name_sec_payload,
)
}
///|
fn sgo_build_immutable_literal_consts(
mod_ : @lib.Module,
) -> (Int32, Array[@lib.Instruction?]) {
let mut imported_globals = 0
match mod_.import_sec {
Some(@lib.ImportSec(imports)) =>
for import_ in imports {
if import_ is @lib.Import(_, _, @lib.GlobalExternType(_)) {
imported_globals += 1
}
}
None => ()
}
let globals = match mod_.global_sec {
Some(@lib.GlobalSec(globals)) => globals
None => return (imported_globals, [])
}
let consts = sgo_empty_const_array(globals.length())
for idx = 0; idx < globals.length(); idx = idx + 1 {
match globals[idx] {
@lib.Global(@lib.GlobalType(_, false), init) =>
consts[idx] = sgo_expr_single_const(init)
_ => ()
}
}
(imported_globals, consts)
}
///|
fn sgo_rewrite_immutable_literal_instr(
instr : @lib.Instruction,
consts : Array[@lib.Instruction?],
imported_globals : Int32,
) -> @lib.Instruction? {
match instr {
@lib.GlobalGet(idx) => {
let slot = sgo_global_idx_slot(idx) - imported_globals
if slot >= 0 && slot < consts.length() {
consts[slot]
} else {
None
}
}
@lib.Block(block_type, @lib.Expr(body)) => {
let (next, changed) = sgo_rewrite_immutable_literal_instrs(
body, consts, imported_globals,
)
if changed {
Some(@lib.Instruction::block(block_type, @lib.Expr::new(next)))
} else {
None
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next, changed) = sgo_rewrite_immutable_literal_instrs(
body, consts, imported_globals,
)
if changed {
Some(@lib.Instruction::loop_(block_type, @lib.Expr::new(next)))
} else {
None
}
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = sgo_rewrite_immutable_literal_instrs(
then_body, consts, imported_globals,
)
let (next_else, else_changed) = match else_body {
Some(body) => {
let (next, changed) = sgo_rewrite_immutable_literal_instrs(
body, consts, imported_globals,
)
(Some(next), changed)
}
None => (None, false)
}
if then_changed || else_changed {
Some(@lib.Instruction::if_(block_type, next_then, next_else))
} else {
None
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next, changed) = sgo_rewrite_immutable_literal_instrs(
body, consts, imported_globals,
)
if changed {
Some(
@lib.Instruction::try_table(block_type, catches, @lib.Expr::new(next)),
)
} else {
None
}
}
_ => None
}
}
///|
fn sgo_rewrite_immutable_literal_instrs(
instrs : Array[@lib.Instruction],
consts : Array[@lib.Instruction?],
imported_globals : Int32,
) -> (Array[@lib.Instruction], Bool) {
let mut next : Array[@lib.Instruction]? = None
for idx = 0; idx < instrs.length(); idx = idx + 1 {
match
sgo_rewrite_immutable_literal_instr(instrs[idx], consts, imported_globals) {
Some(rewritten) => {
let out = match next {
Some(out) => out
None => {
let out = instrs.copy()
next = Some(out)
out
}
}
out[idx] = rewritten
}
None => ()
}
}
match next {
Some(out) => (out, true)
None => (instrs, false)
}
}
///|
fn sgo_run_immutable_literal_siblings(mod_ : @lib.Module) -> SgoPassResult {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return { mod_out: mod_, touched: [], }
}
let touched = Array::make(funcs.length(), false)
if mod_.global_sec is None {
return { mod_out: mod_, touched, }
}
let mut next_funcs : Array[@lib.Func]? = None
let mut literal_consts : Array[@lib.Instruction?]? = None
let mut imported_globals = 0
for idx = 0; idx < funcs.length(); idx = idx + 1 {
let @lib.Func(locals, @lib.Expr(body)) = funcs[idx]
// The full write planner does not cover legacy handlers yet. Keep those
// functions intact and fold only declared immutable globals in siblings.
if precompute_raw_instrs_have_legacy_try(body) ||
!precompute_raw_instrs_have_global_get(body) {
continue
}
let consts = match literal_consts {
Some(consts) => consts
None => {
let (imported, consts) = sgo_build_immutable_literal_consts(mod_)
imported_globals = imported
literal_consts = Some(consts)
consts
}
}
let (next_body, changed) = sgo_rewrite_immutable_literal_instrs(
body, consts, imported_globals,
)
if changed {
let out = match next_funcs {
Some(out) => out
None => {
let out = funcs.copy()
next_funcs = Some(out)
out
}
}
out[idx] = @lib.Func::new(locals, @lib.Expr::new(next_body))
touched[idx] = true
}
}
let mod_out = match next_funcs {
Some(funcs) => mod_.with_code_sec(@lib.CodeSec::new(funcs))
None => mod_
}
{ mod_out, touched, }
}
///|
fn sgo_run_core(
mod_ : @lib.Module,
trace : (String) -> Unit,
perf~ : HotPerfSession? = None,
legacy_try~ : Bool? = None,
) -> SgoPassResult {
if pass_module_has_offset_sensitive_metadata(mod_) {
let touched = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => Array::make(funcs.length(), false)
None => []
}
return { mod_out: mod_, touched, }
}
let has_legacy_try = match legacy_try {
Some(flag) => flag
None => pass_module_has_legacy_try(mod_)
}
if has_legacy_try {
return sgo_run_immutable_literal_siblings(mod_)
}
let collect_start_us = perf_start_timer_opt(
perf, "detail:sgo:collect-global-infos",
)
let (infos, imported_globals) = sgo_collect_global_infos(mod_)
perf_stop_timer_opt(perf, "detail:sgo:collect-global-infos", collect_start_us)
let flags_start_us = perf_start_timer_opt(perf, "detail:sgo:plan-flags")
let same_init_writes = sgo_same_init_write_flags(mod_.code_sec, infos)
let read_only_to_write = sgo_read_only_to_write_flags(mod_, infos)
let remove_sets = sgo_plan_remove_sets(
infos, same_init_writes, read_only_to_write,
)
let mut changed = sgo_apply_practical_immutability(infos, remove_sets)
perf_stop_timer_opt(perf, "detail:sgo:plan-flags", flags_start_us)
let global_start_us = perf_start_timer_opt(perf, "detail:sgo:rewrite-globals")
let (next_global_sec, startup_consts, aliases, global_changed) = sgo_rewrite_global_sec(
mod_.global_sec,
infos,
imported_globals,
)
changed = changed || global_changed
let code_consts = sgo_build_code_consts(infos)
perf_stop_timer_opt(perf, "detail:sgo:rewrite-globals", global_start_us)
let startup_start_us = perf_start_timer_opt(
perf, "detail:sgo:rewrite-startup",
)
let empty_startup_aliases = sgo_empty_global_idx_array(
startup_consts.length(),
)
let (next_table_sec, table_changed) = sgo_rewrite_table_sec(
mod_.table_sec,
startup_consts,
empty_startup_aliases,
remove_sets,
)
let (next_elem_sec, elem_changed) = sgo_rewrite_elem_sec(
mod_.elem_sec,
startup_consts,
empty_startup_aliases,
remove_sets,
)
let (next_data_sec, data_changed) = sgo_rewrite_data_sec(
mod_.data_sec,
startup_consts,
empty_startup_aliases,
remove_sets,
)
perf_stop_timer_opt(perf, "detail:sgo:rewrite-startup", startup_start_us)
let code_start_us = perf_start_timer_opt(perf, "detail:sgo:rewrite-code")
let (next_code_sec, touched, code_changed) = sgo_rewrite_code_sec(
mod_,
mod_.code_sec,
infos,
code_consts,
aliases,
remove_sets,
)
perf_stop_timer_opt(perf, "detail:sgo:rewrite-code", code_start_us)
changed = changed ||
table_changed ||
elem_changed ||
data_changed ||
code_changed
if code_changed {
trace(
"pass[simplify-globals-optimizing]:rewrite touched=\{sgo_count_touched(touched)}",
)
}
if !changed {
return { mod_out: mod_, touched, }
}
{
mod_out: sgo_build_module(
mod_, next_global_sec, next_table_sec, next_elem_sec, next_data_sec, next_code_sec,
),
touched,
}
}
///|
fn sgo_final_cleanup_dropped_root_is_pure(instr : @lib.Instruction) -> Bool {
match instr {
@lib.LocalGet(_)
| @lib.GlobalGet(_)
| @lib.I32Const(_)
| @lib.I64Const(_)
| @lib.F32Const(_)
| @lib.F64Const(_)
| @lib.V128Const(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _)
| @lib.RefNull(_)
| @lib.RefFunc(_) => true
_ => false
}
}
///|
fn sgo_final_cleanup_unary_root_is_pure(instr : @lib.Instruction) -> Bool {
match instr {
@lib.I32Clz
| @lib.I32Ctz
| @lib.I32Popcnt
| @lib.I32Eqz
| @lib.I64Clz
| @lib.I64Ctz
| @lib.I64Popcnt
| @lib.I64Eqz => true
_ => false
}
}
///|
fn sgo_final_cleanup_binary_root_is_pure(instr : @lib.Instruction) -> Bool {
(
sgo_is_i32_pure_condition_instr(instr) ||
sgo_is_i64_pure_condition_instr(instr) ||
sgo_is_float_compare_condition_instr(instr)
) &&
!sgo_final_cleanup_unary_root_is_pure(instr)
}
///|
fn sgo_final_cleanup_pure_expr_range(
instrs : Array[@lib.Instruction],
start : Int32,
end_ : Int32,
) -> Bool {
let mut stack_depth = 0
for idx = start; idx < end_; idx = idx + 1 {
let instr = instrs[idx]
if sgo_final_cleanup_dropped_root_is_pure(instr) {
stack_depth += 1
} else if sgo_final_cleanup_unary_root_is_pure(instr) {
if stack_depth < 1 {
return false
}
} else if sgo_final_cleanup_binary_root_is_pure(instr) {
if stack_depth < 2 {
return false
}
stack_depth -= 1
} else {
return false
}
}
stack_depth == 1
}
///|
fn sgo_final_cleanup_dropped_pure_result_if_length(
instrs : Array[@lib.Instruction],
start : Int32,
purity_checks~ : Array[Int32]? = None,
) -> Int32 {
let max_condition_length = 16
let condition_limit = Int32::min(
instrs.length() - 2,
start + max_condition_length,
)
for if_pos = start + 1; if_pos <= condition_limit; if_pos = if_pos + 1 {
if if_pos + 1 >= instrs.length() || !(instrs[if_pos + 1] is @lib.Drop) {
continue
}
match instrs[if_pos] {
@lib.If(@lib.ValTypeBlockType(_), then_body, Some(else_body)) => {
match purity_checks {
Some(count) => count[0] += 1
None => ()
}
if sgo_final_cleanup_pure_expr_range(instrs, start, if_pos) &&
sgo_final_cleanup_pure_expr_range(then_body, 0, then_body.length()) &&
sgo_final_cleanup_pure_expr_range(else_body, 0, else_body.length()) {
return if_pos - start + 2
}
}
_ => ()
}
}
0
}
///|
fn sgo_final_cleanup_is_dropped_trivial_result_block(
instr : @lib.Instruction,
) -> Bool {
match instr {
// An inline result type has no parameters. A type-index block may consume
// caller stack values, so removing it needs a signature proof unavailable
// to this context-free cleanup.
@lib.Block(@lib.ValTypeBlockType(_), @lib.Expr(body)) =>
match body {
[value, @lib.Br(label)] => {
let @lib.LabelIdx(raw) = label
raw == 0U && sgo_final_cleanup_dropped_root_is_pure(value)
}
_ => false
}
_ => false
}
}
///|
fn sgo_final_cleanup_instrs(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let normalized : Array[@lib.Instruction] = []
let mut changed = false
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = sgo_final_cleanup_instrs(body)
changed = changed || body_changed
normalized.push(
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instr
},
)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = sgo_final_cleanup_instrs(body)
changed = changed || body_changed
normalized.push(
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instr
},
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = sgo_final_cleanup_instrs(then_body)
let mut else_changed = false
let next_else = match else_body {
Some(body) => {
let (next_body, body_changed) = sgo_final_cleanup_instrs(body)
else_changed = body_changed
Some(next_body)
}
None => None
}
changed = changed || then_changed || else_changed
normalized.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instr
},
)
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (next_body, body_changed) = sgo_final_cleanup_instrs(body)
changed = changed || body_changed
normalized.push(
if body_changed {
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(next_body),
)
} else {
instr
},
)
}
@lib.Nop => changed = true
_ => normalized.push(instr)
}
}
let out : Array[@lib.Instruction] = []
let mut idx = 0
while idx < normalized.length() {
let dropped_pure_if_length = sgo_final_cleanup_dropped_pure_result_if_length(
normalized, idx,
)
if dropped_pure_if_length > 0 {
changed = true
idx += dropped_pure_if_length
continue
}
if idx + 1 < normalized.length() &&
normalized[idx + 1] is @lib.Drop &&
sgo_final_cleanup_is_dropped_trivial_result_block(normalized[idx]) {
changed = true
idx += 2
continue
}
if idx + 3 < normalized.length() &&
sgo_final_cleanup_dropped_root_is_pure(normalized[idx]) &&
sgo_final_cleanup_dropped_root_is_pure(normalized[idx + 1]) &&
sgo_final_cleanup_binary_root_is_pure(normalized[idx + 2]) &&
normalized[idx + 3] is @lib.Drop {
changed = true
idx += 4
continue
}
if idx + 2 < normalized.length() &&
sgo_final_cleanup_dropped_root_is_pure(normalized[idx]) &&
sgo_final_cleanup_unary_root_is_pure(normalized[idx + 1]) &&
normalized[idx + 2] is @lib.Drop {
changed = true
idx += 3
continue
}
if idx + 1 < normalized.length() &&
normalized[idx + 1] is @lib.Drop &&
sgo_final_cleanup_dropped_root_is_pure(normalized[idx]) {
changed = true
idx += 2
continue
}
out.push(normalized[idx])
idx += 1
}
(out, changed)
}
///|
fn sgo_final_cleanup_label_targets_depth(
label_idx : @lib.LabelIdx,
depth : Int32,
) -> Bool {
let @lib.LabelIdx(raw) = label_idx
raw.reinterpret_as_int() == depth
}
///|
fn sgo_final_cleanup_instr_branches_to_depth(
instr : @lib.Instruction,
depth : Int32,
) -> Bool {
match instr {
@lib.Br(label_idx)
| @lib.BrIf(label_idx)
| @lib.BrOnNull(label_idx)
| @lib.BrOnNonNull(label_idx) =>
sgo_final_cleanup_label_targets_depth(label_idx, depth)
@lib.BrOnCast(label_idx, _, _, _)
| @lib.BrOnCastFail(label_idx, _, _, _)
| @lib.BrOnCastDescEq(label_idx, _, _)
| @lib.BrOnCastDescEqFail(label_idx, _, _) =>
sgo_final_cleanup_label_targets_depth(label_idx, depth)
@lib.Resume(_, handlers)
| @lib.ResumeThrow(_, _, handlers)
| @lib.ResumeThrowRef(_, handlers) =>
handlers.any(fn(handler) {
match handler {
@lib.ResumeOnLabel(_, label) =>
sgo_final_cleanup_label_targets_depth(label, depth)
@lib.ResumeOnSwitch(_) => false
}
})
@lib.BrTable(labels, default_label) => {
if sgo_final_cleanup_label_targets_depth(default_label, depth) {
return true
}
labels.any(fn(label_idx) {
sgo_final_cleanup_label_targets_depth(label_idx, depth)
})
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
body.any(fn(child) {
sgo_final_cleanup_instr_branches_to_depth(child, depth + 1)
})
@lib.If(_, then_body, else_body) => {
if then_body.any(fn(child) {
sgo_final_cleanup_instr_branches_to_depth(child, depth + 1)
}) {
return true
}
match else_body {
Some(body) =>
body.any(fn(child) {
sgo_final_cleanup_instr_branches_to_depth(child, depth + 1)
})
None => false
}
}
// Exception handlers introduce additional control edges and carried values.
// Keep this final size sweep fail-closed until those targets are modeled.
@lib.Try(_, _, _, _) | @lib.TryTable(_, _, _) => true
_ => false
}
}
///|
fn sgo_final_cleanup_prefix_is_stack_neutral(
body : Array[@lib.Instruction],
end_ : Int32,
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> Bool {
let mut stack_depth = 0
for idx = 0; idx < end_; idx = idx + 1 {
let stack_effect = match
dae_instr_stack_effect(body[idx], mod_, func_type_indices) {
Some(effect) => Some(effect)
None => mp_instr_stack_counts(body[idx])
}
match stack_effect {
Some((inputs, outputs)) => {
if inputs < 0 || outputs < 0 || stack_depth < inputs {
return false
}
stack_depth = stack_depth - inputs + outputs
}
None => return false
}
}
stack_depth == 0
}
///|
fn sgo_final_cleanup_void_dropped_terminal_result_blocks(
instrs : Array[@lib.Instruction],
mod_ : @lib.Module,
func_type_indices : Array[@lib.TypeIdx],
) -> (Array[@lib.Instruction], Bool) {
let normalized : Array[@lib.Instruction] = []
let mut changed = false
for instr in instrs {
match instr {
@lib.Block(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = sgo_final_cleanup_void_dropped_terminal_result_blocks(
body, mod_, func_type_indices,
)
changed = changed || body_changed
normalized.push(
if body_changed {
@lib.Instruction::block(block_type, @lib.Expr::new(next_body))
} else {
instr
},
)
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (next_body, body_changed) = sgo_final_cleanup_void_dropped_terminal_result_blocks(
body, mod_, func_type_indices,
)
changed = changed || body_changed
normalized.push(
if body_changed {
@lib.Instruction::loop_(block_type, @lib.Expr::new(next_body))
} else {
instr
},
)
}
@lib.If(block_type, then_body, else_body) => {
let (next_then, then_changed) = sgo_final_cleanup_void_dropped_terminal_result_blocks(
then_body, mod_, func_type_indices,
)
let mut else_changed = false
let next_else = match else_body {
Some(body) => {
let (next_body, body_changed) = sgo_final_cleanup_void_dropped_terminal_result_blocks(
body, mod_, func_type_indices,
)
else_changed = body_changed
Some(next_body)
}
None => None
}
changed = changed || then_changed || else_changed
normalized.push(
if then_changed || else_changed {
@lib.Instruction::if_(block_type, next_then, next_else)
} else {
instr
},
)
}
_ => normalized.push(instr)
}
}
let out : Array[@lib.Instruction] = []
let mut idx = 0
while idx < normalized.length() {
if idx + 1 < normalized.length() && normalized[idx + 1] is @lib.Drop {
match normalized[idx] {
@lib.Block(@lib.ValTypeBlockType(_), @lib.Expr(body)) =>
if body.length() >= 2 {
let value_idx = body.length() - 2
let branch_idx = body.length() - 1
match body[branch_idx] {
@lib.Br(label_idx) =>
if sgo_final_cleanup_label_targets_depth(label_idx, 0) &&
sgo_final_cleanup_dropped_root_is_pure(body[value_idx]) &&
sgo_final_cleanup_prefix_is_stack_neutral(
body, value_idx, mod_, func_type_indices,
) {
let mut targets_result_block = false
for prefix_idx = 0
prefix_idx < value_idx
prefix_idx = prefix_idx + 1 {
if sgo_final_cleanup_instr_branches_to_depth(
body[prefix_idx],
0,
) {
targets_result_block = true
break
}
}
if !targets_result_block {
let prefix : Array[@lib.Instruction] = []
for prefix_idx = 0
prefix_idx < value_idx
prefix_idx = prefix_idx + 1 {
prefix.push(body[prefix_idx])
}
out.push(
@lib.Instruction::block(
@lib.BlockType::void_(),
@lib.Expr::new(prefix),
),
)
changed = true
idx += 2
continue
}
}
_ => ()
}
}
_ => ()
}
}
out.push(normalized[idx])
idx += 1
}
(out, changed)
}
///|
fn sgo_apply_void_dropped_terminal_result_blocks(
cleaned : @lib.Module,
) -> @lib.Module {
match cleaned.code_sec {
Some(@lib.CodeSec(cleaned_funcs)) => {
let out = cleaned_funcs.copy()
let func_type_indices = sgo_collect_func_type_indices(cleaned)
let mut changed = false
for idx = 0; idx < cleaned_funcs.length(); idx = idx + 1 {
let @lib.Func(locals, @lib.Expr(body)) = cleaned_funcs[idx]
let (next_body, body_changed) = sgo_final_cleanup_void_dropped_terminal_result_blocks(
body, cleaned, func_type_indices,
)
if body_changed {
out[idx] = @lib.Func::new(locals, @lib.Expr::new(next_body))
changed = true
}
}
if changed {
cleaned.with_code_sec(@lib.CodeSec::new(out))
} else {
cleaned
}
}
None => cleaned
}
}
///|
fn sgo_apply_final_cleanup(cleaned : @lib.Module) -> @lib.Module {
match cleaned.code_sec {
Some(@lib.CodeSec(cleaned_funcs)) => {
let out = cleaned_funcs.copy()
let mut changed = false
for idx = 0; idx < cleaned_funcs.length(); idx = idx + 1 {
let @lib.Func(locals, @lib.Expr(body)) = cleaned_funcs[idx]
let (next_body, body_changed) = sgo_final_cleanup_instrs(body)
if body_changed {
out[idx] = @lib.Func::new(locals, @lib.Expr::new(next_body))
changed = true
}
}
let final_cleaned = if changed {
cleaned.with_code_sec(@lib.CodeSec::new(out))
} else {
cleaned
}
sgo_apply_void_dropped_terminal_result_blocks(final_cleaned)
}
None => cleaned
}
}
///|
fn sgo_count_touched(touched : Array[Bool]) -> Int32 {
let mut count = 0
for item in touched {
if item {
count += 1
}
}
count
}
///|
fn sgo_local_ref_count(
instrs : Array[@lib.Instruction],
local_idx : @lib.LocalIdx,
) -> Int32 {
let mut count = 0
for instr in instrs {
match instr {
@lib.LocalGet(idx) | @lib.LocalSet(idx) | @lib.LocalTee(idx) =>
if idx == local_idx {
count += 1
}
@lib.Block(_, @lib.Expr(body))
| @lib.Loop(_, @lib.Expr(body))
| @lib.TryTable(_, _, @lib.Expr(body)) =>
count += sgo_local_ref_count(body, local_idx)
@lib.If(_, then_body, else_body) => {
count += sgo_local_ref_count(then_body, local_idx)
match else_body {
Some(body) => count += sgo_local_ref_count(body, local_idx)
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
count += sgo_local_ref_count(body, local_idx)
for catch_ in catches {
let @lib.Expr(body) = match catch_ {
@lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
}
count += sgo_local_ref_count(body, local_idx)
}
}
_ => ()
}
}
count
}
///|
fn sgo_instr_contains_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(sgo_instr_contains_return)
@lib.If(_, then_body, else_body) => {
if then_body.any(sgo_instr_contains_return) {
return true
}
match else_body {
Some(body) => body.any(sgo_instr_contains_return)
None => false
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if body.any(sgo_instr_contains_return) {
return true
}
for catch_ in catches {
let @lib.Expr(body) = match catch_ {
@lib.LegacyCatch(_, body) | @lib.LegacyCatchAll(body) => body
}
if body.any(sgo_instr_contains_return) {
return true
}
}
false
}
_ => false
}
}
///|
fn sgo_cleanup_can_erase_returns(
instrs : Array[@lib.Instruction],
start : Int32,
end : Int32,
function_root : Bool,
) -> Bool {
for i = start; i < end; i = i + 1 {
if !sgo_instr_contains_return(instrs[i]) {
continue
}
if !function_root || !sgo_is_return_guard(instrs[i]) {
return false
}
// Only the function's own disposable tail proves an early return redundant.
for j = i + 1; j < instrs.length(); j = j + 1 {
if sgo_const_instr(instrs[j]) != None {
continue
}
match instrs[j] {
@lib.Nop | @lib.Drop => ()
_ => return false
}
}
}
true
}