// Licensing scope: see ../../LICENSE and this directory's LICENSE and NOTICE.
// Copyright 2023 WebAssembly Community Group participants.
// Retained historical upstream notice; current derivation remains unconfirmed.
// Conservative attribution from pinned Binaryen version_130 counterpart:
// Copyright 2017 WebAssembly Community Group participants
// Counterpart: src/passes/I64ToI32Lowering.cpp (5d704ad5); origin remains unconfirmed.
// Potentially incorporated Binaryen expression retains Apache-2.0 terms.
// Original Starshine contributions use the scoped MIT grant in ../../LICENSE.
// Modified for Starshine through 2026: implementation and validation use
// Starshine's representations and APIs; this is not an unchanged upstream file.
// Conservative notice retention does not establish copying or independence.
///|
fn i64_lower_sat_float_const(is_f32 : Bool, value : Double) -> @lib.Instruction {
if is_f32 {
@lib.Instruction::f32_const(@lib.F32(Float::from_double(value)))
} else {
@lib.Instruction::f64_const(@lib.F64(value))
}
}
///|
fn i64_lower_sat_local_get(local_idx : @lib.LocalIdx) -> @lib.Instruction {
@lib.Instruction::local_get(local_idx)
}
///|
fn i64_lower_sat_sequence(
is_f32 : Bool,
signed : Bool,
local_idx : @lib.LocalIdx,
) -> Array[@lib.Instruction] {
let upper = if signed {
9223372036854775808.0
} else {
18446744073709551616.0
}
let lower = if signed { -9223372036854775808.0 } else { 0.0 }
let upper_result = if signed {
@lib.I64(9223372036854775807L)
} else {
@lib.I64(-1L)
}
let lower_result = if signed {
@lib.I64(-9223372036854775808L)
} else {
@lib.I64(0L)
}
let ne = if is_f32 {
@lib.Instruction::f32_ne()
} else {
@lib.Instruction::f64_ne()
}
let ge = if is_f32 {
@lib.Instruction::f32_ge()
} else {
@lib.Instruction::f64_ge()
}
let le = if is_f32 {
@lib.Instruction::f32_le()
} else {
@lib.Instruction::f64_le()
}
let convert = match (is_f32, signed) {
(true, true) => @lib.Instruction::i64_trunc_f32s()
(true, false) => @lib.Instruction::i64_trunc_f32u()
(false, true) => @lib.Instruction::i64_trunc_f64s()
(false, false) => @lib.Instruction::i64_trunc_f64u()
}
let result_type = @lib.BlockType::val_type(@lib.ValType::i64())
let lower_check : Array[@lib.Instruction] = [
i64_lower_sat_local_get(local_idx),
i64_lower_sat_float_const(is_f32, lower),
le,
@lib.Instruction::if_(
result_type,
[@lib.Instruction::i64_const(lower_result)],
Some([i64_lower_sat_local_get(local_idx), convert]),
),
]
let upper_check : Array[@lib.Instruction] = [
i64_lower_sat_local_get(local_idx),
i64_lower_sat_float_const(is_f32, upper),
ge,
@lib.Instruction::if_(
result_type,
[@lib.Instruction::i64_const(upper_result)],
Some(lower_check),
),
]
[
@lib.Instruction::local_set(local_idx),
i64_lower_sat_local_get(local_idx),
i64_lower_sat_local_get(local_idx),
ne,
@lib.Instruction::if_(
result_type,
[@lib.Instruction::i64_const(@lib.I64(0L))],
Some(upper_check),
),
]
}
///|
fn i64_lower_sat_rewrite_expr(
expr : @lib.Expr,
f32_temp : @lib.LocalIdx,
f64_temp : @lib.LocalIdx,
changed : Array[Bool],
) -> @lib.Expr {
let @lib.Expr(instrs) = expr
let out : Array[@lib.Instruction] = []
for instr in instrs {
match instr {
@lib.I64TruncSatF32S => {
out.append(i64_lower_sat_sequence(true, true, f32_temp))
changed[0] = true
}
@lib.I64TruncSatF32U => {
out.append(i64_lower_sat_sequence(true, false, f32_temp))
changed[0] = true
}
@lib.I64TruncSatF64S => {
out.append(i64_lower_sat_sequence(false, true, f64_temp))
changed[0] = true
}
@lib.I64TruncSatF64U => {
out.append(i64_lower_sat_sequence(false, false, f64_temp))
changed[0] = true
}
@lib.Block(block_type, nested) =>
out.push(
@lib.Instruction::block(
block_type,
i64_lower_sat_rewrite_expr(nested, f32_temp, f64_temp, changed),
),
)
@lib.Loop(block_type, nested) =>
out.push(
@lib.Instruction::loop_(
block_type,
i64_lower_sat_rewrite_expr(nested, f32_temp, f64_temp, changed),
),
)
@lib.If(block_type, then_body, else_body) => {
let @lib.Expr(next_then) = i64_lower_sat_rewrite_expr(
@lib.Expr::new(then_body),
f32_temp,
f64_temp,
changed,
)
let next_else = match else_body {
Some(body) => {
let @lib.Expr(next_body) = i64_lower_sat_rewrite_expr(
@lib.Expr::new(body),
f32_temp,
f64_temp,
changed,
)
Some(next_body)
}
None => None
}
out.push(@lib.Instruction::if_(block_type, next_then, next_else))
}
@lib.TryTable(block_type, catches, body) =>
out.push(
@lib.Instruction::try_table(
block_type,
catches,
i64_lower_sat_rewrite_expr(body, f32_temp, f64_temp, changed),
),
)
@lib.Try(block_type, body, catches, delegate) =>
out.push(
@lib.Instruction::try_(
block_type,
i64_lower_sat_rewrite_expr(body, f32_temp, f64_temp, changed),
catches.map(fn(catch_) {
match catch_ {
@lib.LegacyCatch(tag, catch_body) =>
@lib.LegacyCatch::tagged(
tag,
i64_lower_sat_rewrite_expr(
catch_body, f32_temp, f64_temp, changed,
),
)
@lib.LegacyCatchAll(catch_body) =>
@lib.LegacyCatch::all(
i64_lower_sat_rewrite_expr(
catch_body, f32_temp, f64_temp, changed,
),
)
}
}),
delegate~,
),
)
_ => out.push(instr)
}
}
@lib.Expr::new(out)
}
///|
fn i64_lower_sat_expr_candidate_types(expr : @lib.Expr) -> (Bool, Bool) {
let @lib.Expr(instrs) = expr
let mut has_f32 = false
let mut has_f64 = false
for instr in instrs {
match instr {
@lib.I64TruncSatF32S | @lib.I64TruncSatF32U => has_f32 = true
@lib.I64TruncSatF64S | @lib.I64TruncSatF64U => has_f64 = true
@lib.Block(_, nested) | @lib.Loop(_, nested) => {
let (nested_f32, nested_f64) = i64_lower_sat_expr_candidate_types(
nested,
)
has_f32 = has_f32 || nested_f32
has_f64 = has_f64 || nested_f64
}
@lib.If(_, then_body, else_body) => {
let (then_f32, then_f64) = i64_lower_sat_expr_candidate_types(
@lib.Expr::new(then_body),
)
has_f32 = has_f32 || then_f32
has_f64 = has_f64 || then_f64
match else_body {
Some(body) => {
let (else_f32, else_f64) = i64_lower_sat_expr_candidate_types(
@lib.Expr::new(body),
)
has_f32 = has_f32 || else_f32
has_f64 = has_f64 || else_f64
}
None => ()
}
}
@lib.TryTable(_, _, body) => {
let (body_f32, body_f64) = i64_lower_sat_expr_candidate_types(body)
has_f32 = has_f32 || body_f32
has_f64 = has_f64 || body_f64
}
@lib.Try(_, body, catches, _) => {
let (body_f32, body_f64) = i64_lower_sat_expr_candidate_types(body)
has_f32 = has_f32 || body_f32
has_f64 = has_f64 || body_f64
for catch_ in catches {
let catch_body = match catch_ {
@lib.LegacyCatch(_, catch_body) | @lib.LegacyCatchAll(catch_body) =>
catch_body
}
let (catch_f32, catch_f64) = i64_lower_sat_expr_candidate_types(
catch_body,
)
has_f32 = has_f32 || catch_f32
has_f64 = has_f64 || catch_f64
}
}
_ => ()
}
if has_f32 && has_f64 {
return (true, true)
}
}
(has_f32, has_f64)
}
///|
fn i64_lower_sat_expr_has_candidate(expr : @lib.Expr) -> Bool {
let types = i64_lower_sat_expr_candidate_types(expr)
types.0 || types.1
}
///|
fn i64_trunc_sat_float_lowering_run_module_pass(
mod_ : @lib.Module,
) -> @lib.Module {
let funcs = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => return mod_
}
let type_indices = match mod_.func_sec {
Some(@lib.FuncSec(type_indices)) => type_indices
None => return mod_
}
if type_indices.length() != funcs.length() {
return mod_
}
let candidate_types : Array[(Bool, Bool)] = []
let mut has_any_candidate = false
for func in funcs {
let @lib.Func(_, body) = func
let types = i64_lower_sat_expr_candidate_types(body)
candidate_types.push(types)
has_any_candidate = has_any_candidate || types.0 || types.1
}
if !has_any_candidate {
return mod_
}
let signatures = msf_build_type_signature_cache(mod_)
let changed = [false]
let rewritten : Array[@lib.Func] = []
for index, func in funcs {
let @lib.Func(locals, body) = func
let (has_f32, has_f64) = candidate_types[index]
if !has_f32 && !has_f64 {
rewritten.push(func)
continue
}
let params = match msf_cached_signature(signatures, type_indices[index]) {
Some(@lib.FuncType(params, _)) => params
None => return mod_
}
let first_temp_index = params.length() + locals.length()
let f32_index = first_temp_index
let f64_index = first_temp_index + (if has_f32 { 1 } else { 0 })
let f32_temp = @lib.LocalIdx::new(f32_index.to_uint())
let f64_temp = @lib.LocalIdx::new(f64_index.to_uint())
let func_changed = [false]
let next_body = i64_lower_sat_rewrite_expr(
body, f32_temp, f64_temp, func_changed,
)
if func_changed[0] {
changed[0] = true
let next_locals = locals.copy()
if has_f32 {
next_locals.push(@lib.ValType::f32())
}
if has_f64 {
next_locals.push(@lib.ValType::f64())
}
rewritten.push(@lib.Func::new(next_locals, next_body))
} else {
rewritten.push(func)
}
}
if !changed[0] {
return mod_
}
let candidate = mod_.with_code_sec(@lib.CodeSec::new(rewritten))
if @validate.validate_module(candidate).is_ok() {
candidate
} else {
mod_
}
}