///|
pub enum CmdError {
CliParse(CliParseError)
UnknownPassFlag(String)
InvalidConfig(String)
ConfigNotFound(String)
ConfigReadFailed(String)
InputReadFailed(String)
SpecFailed(String)
TextLoweringFailed(String)
DecodeFailed(String, DecodeError)
OptimizeFailed(String)
EncodeFailed(String, CmdEncodeError)
OutputWriteFailed(String)
AmbiguousOutputFile(String)
NoInputFiles
} derive(Eq, Debug)
///|
pub impl Show for CmdError with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub suberror CmdEncodeError {
Encode(EncodeError)
Adapter(String)
} derive(Eq, Debug)
///|
pub impl Show for CmdEncodeError with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub fn CmdEncodeError::encode(err : EncodeError) -> CmdEncodeError {
Encode(err)
}
///|
pub fn CmdEncodeError::adapter(msg : String) -> CmdEncodeError {
Adapter(msg)
}
///|
pub fn CmdError::unknown_pass_flag(flag : String) -> CmdError {
UnknownPassFlag(flag)
}
///|
pub fn CmdError::ambiguous_output_file(path : String) -> CmdError {
AmbiguousOutputFile(path)
}
///|
pub struct CmdIO {
get_env : (String) -> String?
file_exists : (String) -> Bool
read_file : (String) -> Result[Bytes, String]
encode_module : (Module) -> Result[Bytes, CmdEncodeError]
print_text_module : (Module) -> Result[Bytes, String]
write_file : (String, Bytes) -> Result[Unit, String]
write_stdout : (Bytes) -> Result[Unit, String]
write_stderr : (Bytes) -> Result[Unit, String]
list_candidates : () -> Array[String]
lower_text_module : (String, CliInputFormat, Bytes) -> Result[Bytes, String]
}
///|
pub fn CmdIO::new(
get_env~ : (String) -> String? = fn(_) { None },
file_exists~ : (String) -> Bool = fn(_) { false },
read_file~ : (String) -> Result[Bytes, String] = fn(_) {
Err("read_file not configured")
},
encode_module~ : (Module) -> Result[Bytes, CmdEncodeError] = fn(mod) {
match encode_module(mod) {
Ok(bytes) => Ok(bytes)
Err(err) => Err(CmdEncodeError::encode(err))
}
},
print_text_module~ : (Module) -> Result[Bytes, String] = fn(_) {
Err("text module printing not configured")
},
write_file~ : (String, Bytes) -> Result[Unit, String] = fn(_, _) {
Err("write_file not configured")
},
write_stdout~ : (Bytes) -> Result[Unit, String] = fn(_) { Ok(()) },
write_stderr~ : (Bytes) -> Result[Unit, String] = fn(_) { Ok(()) },
list_candidates~ : () -> Array[String] = fn() { [] },
lower_text_module~ : (String, CliInputFormat, Bytes) -> Result[Bytes, String] = fn(
path,
format,
_,
) {
Err("text lowering not configured for \{path} (\{format})")
},
) -> CmdIO {
{
get_env,
file_exists,
read_file,
encode_module,
print_text_module,
write_file,
write_stdout,
write_stderr,
list_candidates,
lower_text_module,
}
}
///|
pub struct CmdRunSummary {
input_files : Array[String]
output_files : Array[String]
resolved_passes : Array[String]
optimize_level : Int32
shrink_level : Int32
traps_never_happen : Bool
ignore_implicit_traps : Bool
monomorphize_min_benefit : Int32
closed_world : Bool
low_memory_unused : Bool
low_memory_bound : UInt64
} derive(Eq, Debug)
///|
pub impl Show for CmdRunSummary with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub fn CmdRunSummary::new(
input_files~ : Array[String] = [],
output_files~ : Array[String] = [],
resolved_passes~ : Array[String] = [],
optimize_level~ : Int32 = 0,
shrink_level~ : Int32 = 0,
traps_never_happen~ : Bool = false,
ignore_implicit_traps~ : Bool = false,
monomorphize_min_benefit~ : Int32 = 5,
closed_world~ : Bool = false,
low_memory_unused~ : Bool = false,
low_memory_bound~ : UInt64 = 1024UL,
) -> CmdRunSummary {
{
input_files,
output_files,
resolved_passes,
optimize_level,
shrink_level,
traps_never_happen,
ignore_implicit_traps,
monomorphize_min_benefit,
closed_world,
low_memory_unused,
low_memory_bound,
}
}
///|
pub enum OptimizeTracingLevel {
Pass
Phase
Helper
} derive(Eq, Debug)
///|
pub impl Show for OptimizeTracingLevel with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub fn OptimizeTracingLevel::pass() -> OptimizeTracingLevel {
Pass
}
///|
pub fn OptimizeTracingLevel::phase() -> OptimizeTracingLevel {
Phase
}
///|
pub fn OptimizeTracingLevel::helper() -> OptimizeTracingLevel {
Helper
}
///|
fn cmd_phase_trace_pass_name(line : String) -> String? {
if !line.has_prefix("pass[") {
return None
}
let out = StringBuilder::new()
let mut idx = 5
while idx < line.length() {
let ch = line[idx]
if ch == ']' {
return Some(out.to_string())
}
out.write_char(ch.unsafe_to_char())
idx += 1
}
None
}
///|
fn cmd_phase_trace_callback(trace : (String) -> Unit) -> (String) -> Unit {
let mut active_pass = ""
fn(line) {
if line.has_prefix("pipeline:") || line.has_prefix("perf:timer name=pass:") {
trace(line)
return
}
match cmd_phase_trace_pass_name(line) {
Some(pass_name) if pass_name != active_pass => {
active_pass = pass_name
trace("phase pass=\{pass_name}")
}
_ => ()
}
}
}
///|
enum CmdPipelineDumpFormat {
Wasm
Wat
}
///|
enum CmdPipelinePrintSelector {
Index(UInt32)
Name(String)
}
///|
enum CmdPipelineStep {
PipelinePass(String)
ExtractFunctions(Array[UInt32])
Dump(String, CmdPipelineDumpFormat)
Print(String, CmdPipelinePrintSelector)
Validate
}
///|
pub enum OptimizeValidationPolicy {
FinalModuleOnly
AfterSegment
} derive(Eq, Debug)
///|
pub impl Show for OptimizeValidationPolicy with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub fn OptimizeValidationPolicy::final_module_only() -> OptimizeValidationPolicy {
FinalModuleOnly
}
///|
pub fn OptimizeValidationPolicy::after_segment() -> OptimizeValidationPolicy {
AfterSegment
}
///|
pub struct InliningOptions {
always_inline_max_size : Int32
one_caller_inline_max_size : Int32
flexible_inline_max_size : Int32
max_combined_binary_size : Int32
allow_functions_with_loops : Bool
partial_inlining_ifs : Int32
} derive(Eq, Debug)
///|
pub impl Show for InliningOptions with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub fn InliningOptions::new(
always_inline_max_size~ : Int32 = 2,
one_caller_inline_max_size~ : Int32 = -1,
flexible_inline_max_size~ : Int32 = 20,
max_combined_binary_size~ : Int32 = 400 * 1024,
allow_functions_with_loops~ : Bool = false,
partial_inlining_ifs~ : Int32 = 0,
) -> InliningOptions {
{
always_inline_max_size,
one_caller_inline_max_size,
flexible_inline_max_size,
max_combined_binary_size,
allow_functions_with_loops,
partial_inlining_ifs,
}
}
///|
pub struct OptimizeOptions {
optimize_level : Int32
shrink_level : Int32
inlining : InliningOptions
monomorphize_min_benefit : Int32
closed_world : Bool
zero_filled_memory : Bool
fast_math : Bool
low_memory_unused : Bool
low_memory_bound : UInt64
traps_never_happen : Bool
ignore_implicit_traps : Bool
compiler_fact_policy : @passes.CompilerFactUsePolicy
validation_policy : OptimizeValidationPolicy
stack_function_passes : Bool
} derive(Eq, Debug)
///|
pub impl Show for OptimizeOptions with fn output(self, logger) {
logger.write_string(@debug.to_string(self))
}
///|
pub fn OptimizeOptions::new(
optimize_level~ : Int32 = 0,
shrink_level~ : Int32 = 0,
inlining~ : InliningOptions = InliningOptions::new(),
monomorphize_min_benefit~ : Int32 = 5,
closed_world~ : Bool = false,
zero_filled_memory~ : Bool = false,
fast_math~ : Bool = false,
low_memory_unused~ : Bool = false,
low_memory_bound~ : UInt64 = 1024UL,
traps_never_happen~ : Bool = false,
ignore_implicit_traps~ : Bool = false,
compiler_fact_policy~ : @passes.CompilerFactUsePolicy = @passes.CompilerFactUsePolicy::ignore(),
validation_policy~ : OptimizeValidationPolicy = OptimizeValidationPolicy::final_module_only(),
stack_function_passes~ : Bool = true,
) -> OptimizeOptions {
{
optimize_level,
shrink_level,
inlining,
monomorphize_min_benefit,
closed_world,
zero_filled_memory,
fast_math,
low_memory_unused,
low_memory_bound,
traps_never_happen,
ignore_implicit_traps,
compiler_fact_policy,
validation_policy,
stack_function_passes,
}
}
///|
fn ascii_lower_u16(c : UInt16) -> UInt16 {
if c >= 'A'.to_int().to_uint16() && c <= 'Z'.to_int().to_uint16() {
(c.to_int() + 32).to_uint16()
} else {
c
}
}
///|
fn is_ascii_space(c : UInt16) -> Bool {
c == ' '.to_int().to_uint16() ||
c == '\t'.to_int().to_uint16() ||
c == '\n'.to_int().to_uint16() ||
c == '\r'.to_int().to_uint16()
}
///|
fn trimmed_bounds(input : String) -> (Int32, Int32) {
let mut start = 0
let mut end = input.length()
while start < end && is_ascii_space(input[start]) {
start += 1
}
while end > start && is_ascii_space(input[end - 1]) {
end -= 1
}
(start, end)
}
///|
fn trimmed_string(input : String) -> String {
let (start, end) = trimmed_bounds(input)
input.sub(start~, end~).to_owned()
}
///|
fn ascii_lower_eq_at_bounds(
input : String,
start : Int32,
end : Int32,
expected : String,
) -> Bool {
if end - start != expected.length() {
return false
}
let mut i = 0
while i < expected.length() {
if ascii_lower_u16(input[start + i]) != expected[i] {
return false
}
i += 1
}
true
}
///|
fn trimmed_ascii_lower_string(input : String) -> String {
let (start, end) = trimmed_bounds(input)
let out = StringBuilder::new()
let mut i = start
while i < end {
out.write_char(ascii_lower_u16(input[i]).unsafe_to_char())
i += 1
}
out.to_string()
}
///|
fn parse_bool_string(raw : String) -> Bool? {
let (start, end) = trimmed_bounds(raw)
if start == end {
return None
}
let len = end - start
if len == 1 {
let c = ascii_lower_u16(raw[start])
if c == '1' {
return Some(true)
}
if c == '0' {
return Some(false)
}
}
if ascii_lower_eq_at_bounds(raw, start, end, "true") ||
ascii_lower_eq_at_bounds(raw, start, end, "yes") ||
ascii_lower_eq_at_bounds(raw, start, end, "on") {
return Some(true)
}
if ascii_lower_eq_at_bounds(raw, start, end, "false") ||
ascii_lower_eq_at_bounds(raw, start, end, "no") ||
ascii_lower_eq_at_bounds(raw, start, end, "off") {
return Some(false)
}
None
}
///|
fn parse_input_format_name(raw : String) -> CliInputFormat? {
let (start, end) = trimmed_bounds(raw)
if ascii_lower_eq_at_bounds(raw, start, end, "wasm") {
Some(CliInputFormat::wasm())
} else if ascii_lower_eq_at_bounds(raw, start, end, "wat") {
Some(CliInputFormat::wat())
} else if ascii_lower_eq_at_bounds(raw, start, end, "wast") {
Some(CliInputFormat::wast())
} else {
None
}
}
///|
fn parse_trap_mode_name(raw : String) -> TrapMode? {
let (start, end) = trimmed_bounds(raw)
if ascii_lower_eq_at_bounds(raw, start, end, "allow") ||
ascii_lower_eq_at_bounds(raw, start, end, "traps-may-happen") {
Some(TrapMode::allow())
} else if ascii_lower_eq_at_bounds(raw, start, end, "never") ||
ascii_lower_eq_at_bounds(raw, start, end, "traps-never-happen") {
Some(TrapMode::never())
} else {
None
}
}
///|
fn parse_tracing_level_name(raw : String) -> CliTracingLevel? {
let (start, end) = trimmed_bounds(raw)
if ascii_lower_eq_at_bounds(raw, start, end, "pass") {
Some(CliTracingLevel::pass())
} else if ascii_lower_eq_at_bounds(raw, start, end, "phase") {
Some(CliTracingLevel::phase())
} else if ascii_lower_eq_at_bounds(raw, start, end, "helper") {
Some(CliTracingLevel::helper())
} else {
None
}
}
///|
fn append_unique_string(out : Array[String], value : String) -> Unit {
if !out.contains(value) {
out.push(value)
}
}
///|
fn push_unique_output_target_local(
out : Array[CliOutputTarget],
target : CliOutputTarget,
) -> Unit {
for existing in out {
if existing == target {
return
}
}
out.push(target)
}
///|
fn has_glob_wildcard(pattern : String) -> Bool {
pattern.contains("*") || pattern.contains("?")
}
///|
fn normalize_output_target_path(target : CliOutputTarget) -> CliOutputTarget {
match target {
StdoutOutput => target
FileOutput(path) => CliOutputTarget::file(normalize_cli_path(path))
DirOutput(dir) => CliOutputTarget::dir(normalize_cli_path(dir))
}
}
///|
fn normalize_parse_result_paths(parsed : CliParseResult) -> CliParseResult {
let output_targets : Array[CliOutputTarget] = []
for target in parsed.output_targets {
push_unique_output_target_local(
output_targets,
normalize_output_target_path(target),
)
}
let config_path = match parsed.config_path {
Some(path) => Some(normalize_cli_path(path))
None => None
}
CliParseResult::new(
config_path~,
input_globs=parsed.input_globs.copy(),
glob_enabled=parsed.glob_enabled,
help_requested=parsed.help_requested,
version_requested=parsed.version_requested,
debug_serial_passes=parsed.debug_serial_passes,
read_stdin=parsed.read_stdin,
input_format=parsed.input_format,
output_targets~,
pass_flags=parsed.pass_flags.copy(),
optimize_flags=parsed.optimize_flags.copy(),
trap_mode=parsed.trap_mode,
ignore_implicit_traps=parsed.ignore_implicit_traps,
monomorphize_min_benefit=parsed.monomorphize_min_benefit,
inlining_always_inline_max_size=parsed.inlining_always_inline_max_size,
inlining_one_caller_inline_max_size=parsed.inlining_one_caller_inline_max_size,
inlining_flexible_inline_max_size=parsed.inlining_flexible_inline_max_size,
inlining_max_combined_binary_size=parsed.inlining_max_combined_binary_size,
inlining_allow_functions_with_loops=parsed.inlining_allow_functions_with_loops,
inlining_partial_inlining_ifs=parsed.inlining_partial_inlining_ifs,
low_memory_unused=parsed.low_memory_unused,
low_memory_bound=parsed.low_memory_bound,
closed_world=parsed.closed_world,
compiler_facts_policy=parsed.compiler_facts_policy,
tracing=parsed.tracing,
)
}
///|
fn basename_of_normalized_path(path : String) -> String {
let mut last_sep = -1
let mut i = 0
while i < path.length() {
if path[i] == '/' {
last_sep = i
}
i += 1
}
if last_sep < 0 {
return path
}
path.sub(start=last_sep + 1).to_owned()
}
///|
fn path_with_wasm_extension_normalized(path : String) -> String {
let mut last_dot = -1
let mut i = 0
while i < path.length() {
if path[i] == '/' {
last_dot = -1
} else if path[i] == '.' {
last_dot = i
}
i += 1
}
if last_dot >= 0 {
let stem = path.sub(end=last_dot).to_owned()
"\{stem}.wasm"
} else {
"\{path}.wasm"
}
}
///|
fn output_basename_for_normalized_input(
path : String,
format : CliInputFormat,
) -> String {
let base = basename_of_normalized_path(path)
match format {
Wasm => base
Wat | Wast => path_with_wasm_extension_normalized(base)
}
}
///|
fn join_normalized_cli_path(dir : String, file : String) -> String {
if dir.length() == 0 || dir == "." {
file
} else if dir[dir.length() - 1] == '/' {
"\{dir}\{file}"
} else {
"\{dir}/\{file}"
}
}
///|
fn bytes_to_string_lossy(bytes : Bytes) -> String {
let out = StringBuilder::new()
for b in bytes {
let i = b.to_int()
if i >= 0 && i <= 127 {
out.write_char(i.unsafe_to_char())
} else {
out.write_char('?')
}
}
out.to_string()
}
///|
fn string_to_bytes_lossy(text : String) -> Bytes {
let out = @buffer.new(size_hint=text.length())
for c in text {
let code = c.to_int()
if code >= 0 && code <= 255 {
out.write_byte(code.to_byte())
} else {
out.write_byte('?'.to_int().to_byte())
}
}
out.to_bytes()
}
///|
fn cmd_cli_parse_error_message(err : CliParseError) -> String {
match err {
MissingFlagValue(flag) => "missing value for flag \{flag}"
InvalidInputFormat(format) => "invalid input format: \{format}"
StdinNeedsFormat => "stdin requires --input-format "
UnknownShortFlag(flag) => "unknown short flag: -\{flag}"
InvalidLongFlag(flag) => "invalid long flag: --\{flag}"
UnexpectedFlagValue(flag) => "unexpected value for flag \{flag}"
InvalidDumpPath(path) => "dump path must end with .wasm or .wat: \{path}"
InvalidFunctionIndexList(raw) => "invalid --extract-functions list: \{raw}"
InvalidOptimizationFlag(flag) => "invalid optimization flag: \{flag}"
InvalidTrapMode(mode) => "invalid trap mode: \{mode}"
InvalidTracingLevel(level) => "invalid tracing level: \{level}"
}
}
///|
fn cmd_encode_error_message(err : CmdEncodeError) -> String {
match err {
Encode(inner) => inner.to_string()
Adapter(msg) => msg
}
}
///|
fn cmd_error_message(err : CmdError) -> String {
match err {
CliParse(e) => cmd_cli_parse_error_message(e)
UnknownPassFlag(flag) => "unknown pass flag: \{flag}"
InvalidConfig(msg) => "invalid config: \{msg}"
ConfigNotFound(path) => "config not found: \{path}"
ConfigReadFailed(msg) => "config read failed: \{msg}"
InputReadFailed(msg) => "input read failed: \{msg}"
SpecFailed(msg) => "spec failed: \{msg}"
TextLoweringFailed(msg) => "text lowering failed: \{msg}"
DecodeFailed(path, err) => "decode failed for \{path}: \{err}"
OptimizeFailed(msg) => msg
EncodeFailed(path, err) =>
"encode failed for \{path}: \{cmd_encode_error_message(err)}"
OutputWriteFailed(msg) => "output write failed: \{msg}"
AmbiguousOutputFile(path) =>
"ambiguous output target for multiple inputs: \{path}"
NoInputFiles => "no input files"
}
}
///|
fn cmd_error_stderr_bytes(err : CmdError) -> Bytes {
string_to_bytes_lossy("error: \{cmd_error_message(err)}\n")
}
///|
test "cmd_error_stderr_bytes preserves literal newlines for optimize errors" {
let text = bytes_to_string_lossy(
cmd_error_stderr_bytes(OptimizeFailed("line one\nline two")),
)
assert_true(text.contains("line one\nline two"), msg=text)
assert_false(text.contains("\\n"), msg=text)
}
///|
test "cmd_error_stderr_bytes preserves literal newlines for adapter encode errors" {
let text = bytes_to_string_lossy(
cmd_error_stderr_bytes(
EncodeFailed("a.wasm", CmdEncodeError::adapter("line one\nline two")),
),
)
assert_true(text.contains("line one\nline two"), msg=text)
assert_false(text.contains("\\n"), msg=text)
}
///|
test "cmd_path_has_ascii_suffix_ignore_case is case-insensitive for ascii extensions" {
assert_true(cmd_path_has_ascii_suffix_ignore_case("module.WASM", ".wasm"))
assert_true(cmd_path_has_ascii_suffix_ignore_case("module.wAt", ".wAt"))
assert_false(cmd_path_has_ascii_suffix_ignore_case("module.wasmx", ".wasm"))
assert_false(cmd_path_has_ascii_suffix_ignore_case("module", ".wasm"))
}
///|
test "cmd dump and extract target parsers use exact textual dispatch" {
@debug.assert_eq(cmd_dump_pass_target("dump=out.wasm"), Some("out.wasm"))
@debug.assert_eq(cmd_dump_pass_target("x-dump=out.wasm"), None)
@debug.assert_eq(
cmd_extract_functions_pass_target("extract-functions=1,2,3"),
Some("1,2,3"),
)
@debug.assert_eq(cmd_extract_functions_pass_target("extract-functions"), None)
}
///|
fn parse_olevel_text(raw : String) -> CliOptimizationFlag? {
let (start, end) = trimmed_bounds(raw)
if start == end {
return None
}
let mut i = start
if raw[i] == '-' {
i += 1
}
if i >= end || raw[i] != 'O' {
return None
}
i += 1
let mut saw_digit = false
let mut level = 2
while i < end {
let c = raw[i]
if c < '0' || c > '9' {
break
}
if !saw_digit {
saw_digit = true
level = 0
}
level = level * 10 + (c.to_int() - '0'.to_int())
i += 1
}
let mut size_bias = false
if i < end {
if raw[i] == 's' && !saw_digit {
i += 1
if i != end {
return None
}
return Some(CliOptimizationFlag::osize())
}
if raw[i] != 'z' {
return None
}
size_bias = true
i += 1
}
if i != end {
return None
}
Some(CliOptimizationFlag::olevel(level, size_bias))
}
///|
test "parse_olevel_text parses direct raw O-level forms" {
@debug.assert_eq(
parse_olevel_text("O"),
Some(CliOptimizationFlag::olevel(2, false)),
)
@debug.assert_eq(
parse_olevel_text("-O"),
Some(CliOptimizationFlag::olevel(2, false)),
)
@debug.assert_eq(
parse_olevel_text(" O3z "),
Some(CliOptimizationFlag::olevel(3, true)),
)
@debug.assert_eq(parse_olevel_text("-Os"), Some(CliOptimizationFlag::osize()))
@debug.assert_eq(
parse_olevel_text("-Oz"),
Some(CliOptimizationFlag::olevel(2, true)),
)
@debug.assert_eq(
parse_olevel_text("-O10"),
Some(CliOptimizationFlag::olevel(10, false)),
)
}
///|
test "parse_olevel_text rejects invalid raw O-level forms" {
@debug.assert_eq(parse_olevel_text(""), None)
@debug.assert_eq(parse_olevel_text("optimize"), None)
@debug.assert_eq(parse_olevel_text("o3"), None)
@debug.assert_eq(parse_olevel_text("-O3zz"), None)
@debug.assert_eq(parse_olevel_text("-O3Z"), None)
}
///|
test "normalize_parse_result_paths caches normalized cli config and output paths" {
let parsed = CliParseResult::new(
config_path=Some("./configs/../starshine.json"),
input_globs=["src/**/*.wasm"],
output_targets=[
CliOutputTarget::file("./dist/../out/main.wasm"),
CliOutputTarget::dir("./build//subdir/.."),
CliOutputTarget::file("out/main.wasm"),
CliOutputTarget::dir("build"),
],
)
let normalized = normalize_parse_result_paths(parsed)
@debug.assert_eq(normalized.config_path, Some("starshine.json"))
@debug.assert_eq(normalized.input_globs, ["src/**/*.wasm"])
@debug.assert_eq(normalized.output_targets, [
CliOutputTarget::file("out/main.wasm"),
CliOutputTarget::dir("build"),
])
}
///|
test "normalized output path helpers reuse normalized startup paths" {
@debug.assert_eq(
output_path_for_normalized_default_target(
"src/main/module.wasm",
CliInputFormat::wasm(),
),
"src/main/module.wasm",
)
@debug.assert_eq(
output_path_for_normalized_default_target(
"src/main/module.wat",
CliInputFormat::wat(),
),
"src/main/module.wasm",
)
@debug.assert_eq(
output_basename_for_normalized_input(
"src/main/module.wat",
CliInputFormat::wat(),
),
"module.wasm",
)
@debug.assert_eq(
join_normalized_cli_path("dist/out", "module.wasm"),
"dist/out/module.wasm",
)
@debug.assert_eq(join_normalized_cli_path(".", "module.wasm"), "module.wasm")
}
///|
fn parse_pass_list(raw : String) -> Array[String] {
let out = Array::new()
for part in raw.split(",") {
let lowered = trimmed_ascii_lower_string(part.to_owned())
if lowered.length() > 0 {
out.push(lowered)
}
}
out
}
///|
fn parse_non_negative_decimal(raw : String) -> Int32? {
let (start, end) = trimmed_bounds(raw)
if start == end {
return None
}
let mut value = 0
let mut i = start
while i < end {
let c = raw[i]
if c < '0' || c > '9' {
return None
}
value = value * 10 + (c.to_int() - '0'.to_int())
i += 1
}
Some(value)
}
///|
fn parse_json_non_negative_int(value : Json) -> Int32? {
match value {
Number(n, ..) => {
let int_value = n.to_int()
if int_value < 0 || int_value.to_double() != n {
None
} else {
Some(int_value)
}
}
String(raw) => parse_non_negative_decimal(raw)
_ => None
}
}
///|
fn parse_json_non_negative_or_minus_one(value : Json) -> Int32? {
match value {
Number(n, ..) if n == -1.0 => Some(-1)
String(raw) if trimmed_string(raw) == "-1" => Some(-1)
_ => parse_json_non_negative_int(value)
}
}
///|
fn parse_json_bool(value : Json) -> Bool? {
match value {
True => Some(true)
False => Some(false)
String(raw) => parse_bool_string(raw)
_ => None
}
}
///|
/// Parse configuration JSON from a file or stdin string.
/// This parser is permissive by design: unknown keys are ignored, alias keys are
/// supported, and bad leaf values are dropped so that partial config can still be
/// combined with CLI/environment layers instead of aborting entirely.
fn parse_config_json(text : String) -> Result[CliParseResult, CmdError] {
let root = parse(text) catch {
e => return Err(CmdError::InvalidConfig("invalid config json: \{e}"))
}
let input_globs : Array[String] = []
let mut read_stdin = false
let mut input_format : CliInputFormat? = None
let output_targets : Array[CliOutputTarget] = []
let pass_flags : Array[String] = []
let optimize_flags : Array[CliOptimizationFlag] = []
let mut trap_mode : TrapMode? = None
let mut ignore_implicit_traps = false
let mut monomorphize_min_benefit : Int32? = None
let mut inlining_always_inline_max_size : Int32? = None
let mut inlining_one_caller_inline_max_size : Int32? = None
let mut inlining_flexible_inline_max_size : Int32? = None
let mut inlining_max_combined_binary_size : Int32? = None
let mut inlining_allow_functions_with_loops : Bool? = None
let mut inlining_partial_inlining_ifs : Int32? = None
let mut low_memory_unused : Bool? = None
let mut low_memory_bound : UInt64? = None
let mut closed_world : Bool? = None
let mut tracing : CliTracingLevel? = None
let mut debug_serial_passes = false
let root_obj = match root {
Object(obj) => obj
_ => return Err(CmdError::InvalidConfig("config root must be an object"))
}
match root_obj.get("inputs") {
Some(Object(inputs)) => {
match inputs.get("globs") {
Some(Array(globs)) =>
for glob in globs {
match glob {
String(raw) => {
let normalized = normalize_cli_path(trimmed_string(raw))
if normalized.length() > 0 {
append_unique_string(input_globs, normalized)
}
}
_ => ()
}
}
_ => ()
}
match inputs.get("stdin") {
Some(True) => read_stdin = true
Some(False) => read_stdin = false
_ => ()
}
match inputs.get("format") {
Some(String(raw)) =>
match parse_input_format_name(raw) {
Some(format) => input_format = Some(format)
None => ()
}
_ => ()
}
}
_ => ()
}
match root_obj.get("outputs") {
Some(Object(outputs)) => {
match outputs.get("stdout") {
Some(True) =>
push_unique_output_target_local(
output_targets,
CliOutputTarget::stdout(),
)
_ => ()
}
match outputs.get("out") {
Some(String(raw)) => {
let trimmed = trimmed_string(raw)
if trimmed.length() > 0 {
push_unique_output_target_local(
output_targets,
CliOutputTarget::file(normalize_cli_path(trimmed)),
)
}
}
_ => ()
}
match outputs.get("outDir") {
Some(String(raw)) => {
let trimmed = trimmed_string(raw)
if trimmed.length() > 0 {
push_unique_output_target_local(
output_targets,
CliOutputTarget::dir(normalize_cli_path(trimmed)),
)
}
}
_ => ()
}
match outputs.get("out-dir") {
Some(String(raw)) => {
let trimmed = trimmed_string(raw)
if trimmed.length() > 0 {
push_unique_output_target_local(
output_targets,
CliOutputTarget::dir(normalize_cli_path(trimmed)),
)
}
}
_ => ()
}
}
_ => ()
}
match root_obj.get("passes") {
Some(Array(passes)) =>
for pass in passes {
match pass {
String(raw) => {
let lowered = trimmed_ascii_lower_string(raw)
if lowered.length() > 0 {
pass_flags.push(lowered)
match lowered {
"optimize" =>
optimize_flags.push(CliOptimizationFlag::optimize())
"shrink" => optimize_flags.push(CliOptimizationFlag::shrink())
_ => ()
}
}
}
_ => ()
}
}
_ => ()
}
match root_obj.get("tracing") {
Some(String(raw)) =>
match parse_tracing_level_name(raw) {
Some(level) => tracing = Some(level)
None => ()
}
_ => ()
}
match root_obj.get("debugSerialPasses") {
Some(value) =>
match parse_json_bool(value) {
Some(parsed_value) => debug_serial_passes = parsed_value
None => ()
}
None => ()
}
match root_obj.get("optimize") {
Some(Object(optimize)) => {
match optimize.get("preset") {
Some(String(raw)) =>
match trimmed_ascii_lower_string(raw) {
"optimize" => {
pass_flags.push("optimize")
optimize_flags.push(CliOptimizationFlag::optimize())
}
"shrink" => {
pass_flags.push("shrink")
optimize_flags.push(CliOptimizationFlag::shrink())
}
_ => ()
}
_ => ()
}
let olevel_value = match optimize.get("olevel") {
Some(v) => Some(v)
None => optimize.get("optimizeLevel")
}
match olevel_value {
Some(String(raw)) =>
match parse_olevel_text(raw) {
Some(flag) => optimize_flags.push(flag)
None => ()
}
Some(value) =>
match parse_json_non_negative_int(value) {
Some(level) =>
optimize_flags.push(CliOptimizationFlag::olevel(level, false))
None => ()
}
_ => ()
}
let shrink_level_value = match optimize.get("shrinkLevel") {
Some(v) => Some(v)
None => optimize.get("shrink-level")
}
match shrink_level_value {
Some(value) =>
match parse_json_non_negative_int(value) {
Some(level) =>
optimize_flags.push(CliOptimizationFlag::olevel(level, true))
None => ()
}
None => ()
}
let trap_value = match optimize.get("trapMode") {
Some(v) => Some(v)
None => optimize.get("trap-mode")
}
match trap_value {
Some(String(raw)) =>
match parse_trap_mode_name(raw) {
Some(mode) => trap_mode = Some(mode)
None => ()
}
_ => ()
}
}
_ => ()
}
match root_obj.get("options") {
Some(Object(options)) => {
let traps_value = match options.get("trapsNeverHappen") {
Some(v) => Some(v)
None => options.get("traps-never-happen")
}
match traps_value {
Some(value) =>
match parse_json_bool(value) {
Some(true) => trap_mode = Some(TrapMode::never())
Some(false) => trap_mode = Some(TrapMode::allow())
None => ()
}
None => ()
}
let ignore_implicit_value = match options.get("ignoreImplicitTraps") {
Some(v) => Some(v)
None => options.get("ignore-implicit-traps")
}
match ignore_implicit_value {
Some(value) =>
match parse_json_bool(value) {
Some(parsed_value) => ignore_implicit_traps = parsed_value
None => ()
}
None => ()
}
let optimize_level_value = match options.get("optimizeLevel") {
Some(v) => Some(v)
None => options.get("optimize-level")
}
match optimize_level_value {
Some(value) =>
match parse_json_non_negative_int(value) {
Some(level) =>
optimize_flags.push(CliOptimizationFlag::olevel(level, false))
None => ()
}
None => ()
}
let shrink_level_value = match options.get("shrinkLevel") {
Some(v) => Some(v)
None => options.get("shrink-level")
}
match shrink_level_value {
Some(value) =>
match parse_json_non_negative_int(value) {
Some(level) =>
optimize_flags.push(CliOptimizationFlag::olevel(level, true))
None => ()
}
None => ()
}
let monomorphize_value = match options.get("monomorphizeMinBenefit") {
Some(v) => Some(v)
None => options.get("monomorphize-min-benefit")
}
match monomorphize_value {
Some(value) =>
match parse_json_non_negative_int(value) {
Some(parsed_value) => monomorphize_min_benefit = Some(parsed_value)
None => ()
}
None => ()
}
let always_inline_value = match
options.get("alwaysInlineMaxFunctionSize") {
Some(v) => Some(v)
None => options.get("always-inline-max-function-size")
}
match always_inline_value {
Some(value) =>
match parse_json_non_negative_int(value) {
Some(parsed_value) =>
inlining_always_inline_max_size = Some(parsed_value)
None => ()
}
None => ()
}
let one_caller_inline_value = match
options.get("oneCallerInlineMaxFunctionSize") {
Some(v) => Some(v)
None => options.get("one-caller-inline-max-function-size")
}
match one_caller_inline_value {
Some(value) =>
match parse_json_non_negative_or_minus_one(value) {
Some(parsed_value) =>
inlining_one_caller_inline_max_size = Some(parsed_value)
None => ()
}
None => ()
}
let flexible_inline_value = match
options.get("flexibleInlineMaxFunctionSize") {
Some(v) => Some(v)
None => options.get("flexible-inline-max-function-size")
}
match flexible_inline_value {
Some(value) =>
match parse_json_non_negative_int(value) {
Some(parsed_value) =>
inlining_flexible_inline_max_size = Some(parsed_value)
None => ()
}
None => ()
}
let combined_inline_value = match
options.get("inlineMaxCombinedBinarySize") {
Some(v) => Some(v)
None => options.get("inline-max-combined-binary-size")
}
match combined_inline_value {
Some(value) =>
match parse_json_non_negative_int(value) {
Some(parsed_value) =>
inlining_max_combined_binary_size = Some(parsed_value)
None => ()
}
None => ()
}
let inline_loops_value = match options.get("inlineFunctionsWithLoops") {
Some(v) => Some(v)
None => options.get("inline-functions-with-loops")
}
match inline_loops_value {
Some(value) =>
match parse_json_bool(value) {
Some(parsed_value) =>
inlining_allow_functions_with_loops = Some(parsed_value)
None => ()
}
None => ()
}
let partial_inline_value = match options.get("partialInliningIfs") {
Some(v) => Some(v)
None => options.get("partial-inlining-ifs")
}
match partial_inline_value {
Some(value) =>
match parse_json_non_negative_int(value) {
Some(parsed_value) =>
inlining_partial_inlining_ifs = Some(parsed_value)
None => ()
}
None => ()
}
let low_memory_unused_value = match options.get("lowMemoryUnused") {
Some(v) => Some(v)
None => options.get("low-memory-unused")
}
match low_memory_unused_value {
Some(value) =>
match parse_json_bool(value) {
Some(parsed_value) => low_memory_unused = Some(parsed_value)
None => ()
}
None => ()
}
let low_memory_bound_value = match options.get("lowMemoryBound") {
Some(v) => Some(v)
None => options.get("low-memory-bound")
}
match low_memory_bound_value {
Some(value) =>
match parse_json_non_negative_int(value) {
Some(parsed_value) =>
low_memory_bound = Some(parsed_value.to_uint64())
None => ()
}
None => ()
}
let closed_world_value = match options.get("closedWorld") {
Some(v) => Some(v)
None => options.get("closed-world")
}
match closed_world_value {
Some(value) =>
match parse_json_bool(value) {
Some(parsed_value) => closed_world = Some(parsed_value)
None => ()
}
None => ()
}
}
_ => ()
}
Ok(
CliParseResult::new(
input_globs~,
read_stdin~,
input_format~,
output_targets~,
pass_flags~,
optimize_flags~,
trap_mode~,
ignore_implicit_traps~,
monomorphize_min_benefit~,
inlining_always_inline_max_size~,
inlining_one_caller_inline_max_size~,
inlining_flexible_inline_max_size~,
inlining_max_combined_binary_size~,
inlining_allow_functions_with_loops~,
inlining_partial_inlining_ifs~,
low_memory_unused~,
low_memory_bound~,
closed_world~,
tracing~,
debug_serial_passes~,
),
)
}
///|
/// Parse environment overlay variables into a parse result shape.
/// Environment values follow the same normalization rules as `parse_config_json`
/// (trimmed values, alias keys, permissive leaf handling), and they are merged
/// later with config+CLI using explicit precedence. Only variables that can still
/// affect the active execution mode are probed.
fn parse_env_overlay(
io : CmdIO,
config : CliParseResult,
cli : CliParseResult,
) -> CliParseResult {
let mut input_format : CliInputFormat? = None
let output_targets : Array[CliOutputTarget] = []
let pass_flags : Array[String] = []
let optimize_flags : Array[CliOptimizationFlag] = []
let mut trap_mode : TrapMode? = None
let mut ignore_implicit_traps = false
let mut monomorphize_min_benefit : Int32? = None
let mut low_memory_unused : Bool? = None
let mut low_memory_bound : UInt64? = None
let mut closed_world : Bool? = None
let mut tracing : CliTracingLevel? = None
let mut debug_serial_passes = false
let env_pass_flags_relevant = cli.pass_flags.length() == 0 &&
config.pass_flags.length() == 0
let env_optimize_flags_relevant = cli.optimize_flags.length() == 0
let env_output_targets_relevant = cli.output_targets.length() == 0
if cli.input_format is None {
match (io.get_env)("STARSHINE_FORMAT") {
Some(raw) =>
match parse_input_format_name(raw) {
Some(format) => input_format = Some(format)
None => ()
}
None => ()
}
}
if env_output_targets_relevant {
match (io.get_env)("STARSHINE_OUT") {
Some(raw) => {
let trimmed = trimmed_string(raw)
if trimmed.length() > 0 {
push_unique_output_target_local(
output_targets,
CliOutputTarget::file(normalize_cli_path(trimmed)),
)
}
}
None => ()
}
match (io.get_env)("STARSHINE_OUT_DIR") {
Some(raw) => {
let trimmed = trimmed_string(raw)
if trimmed.length() > 0 {
push_unique_output_target_local(
output_targets,
CliOutputTarget::dir(normalize_cli_path(trimmed)),
)
}
}
None => ()
}
match (io.get_env)("STARSHINE_STDOUT") {
Some(raw) =>
match parse_bool_string(raw) {
Some(true) =>
push_unique_output_target_local(
output_targets,
CliOutputTarget::stdout(),
)
_ => ()
}
None => ()
}
}
if env_pass_flags_relevant {
match (io.get_env)("STARSHINE_PASSES") {
Some(raw) =>
for pass in parse_pass_list(raw) {
pass_flags.push(pass)
}
None => ()
}
}
if cli.trap_mode is None {
match (io.get_env)("STARSHINE_TRAP_MODE") {
Some(raw) =>
match parse_trap_mode_name(raw) {
Some(mode) => trap_mode = Some(mode)
None => ()
}
None => ()
}
}
let cli_traps_never_fixed = match cli.trap_mode {
Some(mode) => mode == TrapMode::never()
None => false
}
if !cli_traps_never_fixed &&
!cli.ignore_implicit_traps &&
!config.ignore_implicit_traps {
match (io.get_env)("STARSHINE_IGNORE_IMPLICIT_TRAPS") {
Some(raw) =>
match parse_bool_string(raw) {
Some(parsed_value) => ignore_implicit_traps = parsed_value
None => ()
}
None => ()
}
}
if env_pass_flags_relevant || env_optimize_flags_relevant {
match (io.get_env)("STARSHINE_OPTIMIZE") {
Some(raw) =>
for part in parse_pass_list(raw) {
match part {
"optimize" => {
pass_flags.push("optimize")
optimize_flags.push(CliOptimizationFlag::optimize())
}
"shrink" => {
pass_flags.push("shrink")
optimize_flags.push(CliOptimizationFlag::shrink())
}
_ => ()
}
}
None => ()
}
match (io.get_env)("STARSHINE_PRESET") {
Some(raw) =>
for part in parse_pass_list(raw) {
match part {
"optimize" => {
pass_flags.push("optimize")
optimize_flags.push(CliOptimizationFlag::optimize())
}
"shrink" => {
pass_flags.push("shrink")
optimize_flags.push(CliOptimizationFlag::shrink())
}
_ => ()
}
}
None => ()
}
}
if env_optimize_flags_relevant {
match (io.get_env)("STARSHINE_OLEVEL") {
Some(raw) =>
match parse_olevel_text(raw) {
Some(flag) => optimize_flags.push(flag)
None => ()
}
None => ()
}
}
if cli.monomorphize_min_benefit is None {
match (io.get_env)("STARSHINE_MONOMORPHIZE_MIN_BENEFIT") {
Some(raw) =>
match parse_non_negative_decimal(raw) {
Some(parsed_value) => monomorphize_min_benefit = Some(parsed_value)
None => ()
}
None => ()
}
}
if cli.low_memory_unused is None {
match (io.get_env)("STARSHINE_LOW_MEMORY_UNUSED") {
Some(raw) =>
match parse_bool_string(raw) {
Some(parsed_value) => low_memory_unused = Some(parsed_value)
None => ()
}
None => ()
}
}
if cli.low_memory_bound is None {
match (io.get_env)("STARSHINE_LOW_MEMORY_BOUND") {
Some(raw) =>
match parse_non_negative_decimal(raw) {
Some(parsed_value) =>
low_memory_bound = Some(parsed_value.to_uint64())
None => ()
}
None => ()
}
}
if cli.closed_world is None {
match (io.get_env)("STARSHINE_CLOSED_WORLD") {
Some(raw) =>
match parse_bool_string(raw) {
Some(parsed_value) => closed_world = Some(parsed_value)
None => ()
}
None => ()
}
}
if cli.tracing is None {
match (io.get_env)("STARSHINE_TRACING") {
Some(raw) =>
match parse_tracing_level_name(raw) {
Some(level) => tracing = Some(level)
None => ()
}
None => ()
}
}
if !(cli.debug_serial_passes || config.debug_serial_passes) {
match (io.get_env)("STARSHINE_DEBUG_SERIAL_PASSES") {
Some(raw) =>
match parse_bool_string(raw) {
Some(parsed_value) => debug_serial_passes = parsed_value
None => ()
}
None => ()
}
}
CliParseResult::new(
input_format~,
output_targets~,
pass_flags~,
optimize_flags~,
trap_mode~,
ignore_implicit_traps~,
monomorphize_min_benefit~,
low_memory_unused~,
low_memory_bound~,
closed_world~,
tracing~,
debug_serial_passes~,
)
}
///|
/// Merge config + env + cli parse layers with explicit precedence:
/// CLI overrides env, env overrides config for scalar fields and selected lists.
/// Input globs are concatenated with dedupe to preserve all sources while avoiding
/// duplicate entries from overlapping layers.
fn merge_parse_results(
config : CliParseResult,
env : CliParseResult,
cli : CliParseResult,
) -> CliParseResult {
let input_globs : Array[String] = []
for glob in config.input_globs {
append_unique_string(input_globs, glob)
}
for glob in env.input_globs {
append_unique_string(input_globs, glob)
}
for glob in cli.input_globs {
append_unique_string(input_globs, glob)
}
let glob_enabled = cli.glob_enabled
let read_stdin = config.read_stdin || env.read_stdin || cli.read_stdin
let input_format = match cli.input_format {
Some(format) => Some(format)
None =>
match env.input_format {
Some(format) => Some(format)
None => config.input_format
}
}
let output_targets : Array[CliOutputTarget] = []
let chosen_outputs = if cli.output_targets.length() > 0 {
cli.output_targets
} else if env.output_targets.length() > 0 {
env.output_targets
} else {
config.output_targets
}
for target in chosen_outputs {
output_targets.push(target)
}
let pass_flags : Array[String] = []
let selected_passes = if cli.pass_flags.length() > 0 {
cli.pass_flags
} else if config.pass_flags.length() > 0 {
config.pass_flags
} else {
env.pass_flags
}
for pass in selected_passes {
pass_flags.push(pass)
}
let optimize_flags : Array[CliOptimizationFlag] = []
let selected_optimize_flags = if cli.optimize_flags.length() > 0 {
cli.optimize_flags
} else if env.optimize_flags.length() > 0 {
env.optimize_flags
} else {
config.optimize_flags
}
for flag in selected_optimize_flags {
optimize_flags.push(flag)
}
let trap_mode = match cli.trap_mode {
Some(mode) => Some(mode)
None =>
match env.trap_mode {
Some(mode) => Some(mode)
None => config.trap_mode
}
}
let ignore_implicit_traps = cli.ignore_implicit_traps ||
env.ignore_implicit_traps ||
config.ignore_implicit_traps
let config_path = match cli.config_path {
Some(path) => Some(path)
None =>
match env.config_path {
Some(path) => Some(path)
None => config.config_path
}
}
let monomorphize_min_benefit = match cli.monomorphize_min_benefit {
Some(v) => Some(v)
None =>
match env.monomorphize_min_benefit {
Some(v) => Some(v)
None => config.monomorphize_min_benefit
}
}
let inlining_always_inline_max_size = match
cli.inlining_always_inline_max_size {
Some(v) => Some(v)
None =>
match env.inlining_always_inline_max_size {
Some(v) => Some(v)
None => config.inlining_always_inline_max_size
}
}
let inlining_one_caller_inline_max_size = match
cli.inlining_one_caller_inline_max_size {
Some(v) => Some(v)
None =>
match env.inlining_one_caller_inline_max_size {
Some(v) => Some(v)
None => config.inlining_one_caller_inline_max_size
}
}
let inlining_flexible_inline_max_size = match
cli.inlining_flexible_inline_max_size {
Some(v) => Some(v)
None =>
match env.inlining_flexible_inline_max_size {
Some(v) => Some(v)
None => config.inlining_flexible_inline_max_size
}
}
let inlining_max_combined_binary_size = match
cli.inlining_max_combined_binary_size {
Some(v) => Some(v)
None =>
match env.inlining_max_combined_binary_size {
Some(v) => Some(v)
None => config.inlining_max_combined_binary_size
}
}
let inlining_allow_functions_with_loops = match
cli.inlining_allow_functions_with_loops {
Some(v) => Some(v)
None =>
match env.inlining_allow_functions_with_loops {
Some(v) => Some(v)
None => config.inlining_allow_functions_with_loops
}
}
let inlining_partial_inlining_ifs = match cli.inlining_partial_inlining_ifs {
Some(v) => Some(v)
None =>
match env.inlining_partial_inlining_ifs {
Some(v) => Some(v)
None => config.inlining_partial_inlining_ifs
}
}
let low_memory_unused = match cli.low_memory_unused {
Some(v) => Some(v)
None =>
match env.low_memory_unused {
Some(v) => Some(v)
None => config.low_memory_unused
}
}
let low_memory_bound = match cli.low_memory_bound {
Some(v) => Some(v)
None =>
match env.low_memory_bound {
Some(v) => Some(v)
None => config.low_memory_bound
}
}
let closed_world = match cli.closed_world {
Some(v) => Some(v)
None =>
match env.closed_world {
Some(v) => Some(v)
None => config.closed_world
}
}
let compiler_facts_policy = match cli.compiler_facts_policy {
Some(value) => Some(value)
None =>
match env.compiler_facts_policy {
Some(value) => Some(value)
None => config.compiler_facts_policy
}
}
let tracing = match cli.tracing {
Some(level) => Some(level)
None =>
match env.tracing {
Some(level) => Some(level)
None => config.tracing
}
}
let debug_serial_passes = cli.debug_serial_passes ||
env.debug_serial_passes ||
config.debug_serial_passes
CliParseResult::new(
config_path~,
input_globs~,
glob_enabled~,
debug_serial_passes~,
read_stdin~,
input_format~,
output_targets~,
pass_flags~,
optimize_flags~,
trap_mode~,
ignore_implicit_traps~,
monomorphize_min_benefit~,
inlining_always_inline_max_size~,
inlining_one_caller_inline_max_size~,
inlining_flexible_inline_max_size~,
inlining_max_combined_binary_size~,
inlining_allow_functions_with_loops~,
inlining_partial_inlining_ifs~,
low_memory_unused~,
low_memory_bound~,
closed_world~,
compiler_facts_policy~,
tracing~,
)
}
///|
/// Config path resolution order: explicit CLI > STARSHINE_CONFIG env var > default
/// package config file. Returns whether a real config was selected versus default.
fn resolve_config_path(cli : CliParseResult, io : CmdIO) -> (String?, Bool) {
match cli.config_path {
Some(path) => return (Some(path), true)
None => ()
}
match (io.get_env)("STARSHINE_CONFIG") {
Some(raw) => {
let trimmed = trimmed_string(raw)
if trimmed.length() > 0 {
return (Some(normalize_cli_path(trimmed)), true)
}
}
None => ()
}
if (io.file_exists)(@cli.DEFAULT_CONFIG_PATH) {
return (Some(@cli.DEFAULT_CONFIG_PATH), false)
}
(None, false)
}
///|
/// Split globs into literal paths vs wildcard patterns, dedupe early, expand only
/// patterns with the adapter when enabled, and preserve deterministic ordering.
fn resolve_input_files_with_glob(
globs : Array[String],
io : CmdIO,
glob_enabled : Bool,
) -> Array[String] {
let literal = Array::new()
let patterns = Array::new()
for normalized in globs {
if normalized.length() == 0 {
continue
}
if glob_enabled && has_glob_wildcard(normalized) {
append_unique_string(patterns, normalized)
} else {
append_unique_string(literal, normalized)
}
}
let out = Array::new()
for value in literal {
append_unique_string(out, value)
}
if patterns.length() > 0 {
for value in expand_globs_with_adapter(patterns, io.list_candidates) {
append_unique_string(out, value)
}
}
out
}
///|
fn max_int(a : Int32, b : Int32) -> Int32 {
if a > b {
a
} else {
b
}
}
///|
/// Compute effective optimize/shrink levels as maxima across explicit `-O` flags and
/// preset shortcuts, matching precedence rules used later by explicit pipeline flags.
fn resolve_optimize_levels(
flags : Array[CliOptimizationFlag],
) -> (Int32, Int32) {
let mut optimize_level = 0
let mut shrink_level = 0
for flag in flags {
match flag {
OLevel(level, size_bias) => {
optimize_level = max_int(optimize_level, level)
if size_bias {
shrink_level = max_int(shrink_level, level)
}
}
OptimizePreset => optimize_level = max_int(optimize_level, 2)
OptimizeSizePreset => {
optimize_level = max_int(optimize_level, 2)
shrink_level = max_int(shrink_level, 1)
}
ShrinkPreset => {
optimize_level = max_int(optimize_level, 2)
shrink_level = max_int(shrink_level, 2)
}
}
}
(optimize_level, shrink_level)
}
///|
test "Os preset resolves to Binaryen Os levels" {
@debug.assert_eq(
resolve_optimize_levels([CliOptimizationFlag::osize()]),
(2, 1),
)
}
///|
test "shrink preset resolves to Binaryen Oz levels" {
@debug.assert_eq(
resolve_optimize_levels([CliOptimizationFlag::shrink()]),
(2, 2),
)
}
///|
/// If explicit CLI pass flags are absent, synthesize exactly one default pass name
/// (`shrink` preferred over `optimize`) from the highest requested levels.
fn resolve_effective_pass_flags(
resolved_passes : Array[String],
optimize_level : Int32,
shrink_level : Int32,
) -> Array[String] {
if resolved_passes.length() > 0 {
return resolved_passes
}
let out : Array[String] = []
if shrink_level > 0 {
out.push("shrink")
} else if optimize_level > 0 {
out.push("optimize")
}
out
}
///|
fn default_inlining_options(
always_inline_max_size~ : Int32 = 2,
one_caller_inline_max_size~ : Int32 = -1,
flexible_inline_max_size~ : Int32 = 20,
max_combined_binary_size~ : Int32 = 400 * 1024,
allow_functions_with_loops~ : Bool = false,
partial_inlining_ifs~ : Int32 = 0,
) -> InliningOptions {
InliningOptions::new(
always_inline_max_size~,
one_caller_inline_max_size~,
flexible_inline_max_size~,
max_combined_binary_size~,
allow_functions_with_loops~,
partial_inlining_ifs~,
)
}
///|
fn resolve_compiler_fact_policy(
value : String?,
) -> @passes.CompilerFactUsePolicy {
match value {
Some("hints") => @passes.CompilerFactUsePolicy::hints_only()
Some("trust") => @passes.CompilerFactUsePolicy::trust_assertions()
_ => @passes.CompilerFactUsePolicy::ignore()
}
}
///|
fn make_optimize_options(
optimize_level : Int32,
shrink_level : Int32,
traps_never_happen : Bool,
ignore_implicit_traps : Bool,
monomorphize_min_benefit : Int32,
inlining_always_inline_max_size : Int32,
inlining_one_caller_inline_max_size : Int32,
inlining_flexible_inline_max_size : Int32,
inlining_max_combined_binary_size : Int32,
inlining_allow_functions_with_loops : Bool,
inlining_partial_inlining_ifs : Int32,
closed_world : Bool,
zero_filled_memory : Bool,
fast_math : Bool,
low_memory_unused : Bool,
low_memory_bound : UInt64,
debug_serial_passes : Bool,
compiler_fact_policy : @passes.CompilerFactUsePolicy,
) -> OptimizeOptions {
OptimizeOptions::new(
optimize_level~,
shrink_level~,
inlining=default_inlining_options(
always_inline_max_size=inlining_always_inline_max_size,
one_caller_inline_max_size=inlining_one_caller_inline_max_size,
flexible_inline_max_size=inlining_flexible_inline_max_size,
max_combined_binary_size=inlining_max_combined_binary_size,
allow_functions_with_loops=inlining_allow_functions_with_loops,
partial_inlining_ifs=inlining_partial_inlining_ifs,
),
monomorphize_min_benefit~,
closed_world~,
zero_filled_memory~,
fast_math~,
low_memory_unused~,
low_memory_bound~,
traps_never_happen~,
ignore_implicit_traps~,
compiler_fact_policy~,
validation_policy=if debug_serial_passes {
OptimizeValidationPolicy::after_segment()
} else {
OptimizeValidationPolicy::final_module_only()
},
stack_function_passes=!debug_serial_passes,
)
}
///|
fn optimize_options_to_hot_pipeline_options(
options : OptimizeOptions,
validate_final_module~ : Bool = true,
trace~ : ((String) -> Unit)? = None,
) -> @passes.HotPipelineOptions {
@passes.HotPipelineOptions::new(
verify_policy=match options.validation_policy {
AfterSegment => @passes.HotPipelineVerifyPolicy::after_each_pass()
FinalModuleOnly => @passes.HotPipelineVerifyPolicy::final_module_only()
},
validate_final_module~,
closed_world=options.closed_world,
traps_never_happen=options.traps_never_happen,
ignore_implicit_traps=options.ignore_implicit_traps,
zero_filled_memory=options.zero_filled_memory,
fast_math=options.fast_math,
compiler_fact_policy=options.compiler_fact_policy,
optimize_level=options.optimize_level,
shrink_level=options.shrink_level,
inlining_always_inline_max_size=options.inlining.always_inline_max_size,
inlining_one_caller_inline_max_size=options.inlining.one_caller_inline_max_size,
inlining_flexible_inline_max_size=options.inlining.flexible_inline_max_size,
inlining_max_combined_binary_size=options.inlining.max_combined_binary_size,
inlining_allow_functions_with_loops=options.inlining.allow_functions_with_loops,
inlining_partial_inlining_ifs=options.inlining.partial_inlining_ifs,
stack_function_passes=options.stack_function_passes,
trace~,
perf=match trace {
Some(cb) =>
Some(
@passes.HotPerfSession::new(
trace=cb,
trace_checkpoints=false,
trace_counters=false,
),
)
None => None
},
)
}
///|
fn optimize_module_with_pass_flags(
mod : Module,
pass_flags : Array[String],
options : OptimizeOptions,
validate_final_module~ : Bool = true,
) -> Result[Module, String] {
@passes.run_hot_pipeline(
mod,
optimize_options_to_hot_pipeline_options(options, validate_final_module~),
pass_flags,
)
}
///|
fn optimize_module_with_pass_flags_trace(
mod : Module,
pass_flags : Array[String],
options : OptimizeOptions,
trace : (String) -> Unit,
validate_final_module~ : Bool = true,
trace_level~ : OptimizeTracingLevel = OptimizeTracingLevel::pass(),
) -> Result[Module, String] {
let effective_trace = match trace_level {
Phase => cmd_phase_trace_callback(trace)
Pass | Helper => trace
}
@passes.run_hot_pipeline(
mod,
optimize_options_to_hot_pipeline_options(
options,
validate_final_module~,
trace=Some(effective_trace),
),
pass_flags,
)
}
///|
test "performance candidates retain active command folds boundary rewrites and helper removal" {
let scalar = @passes.pass_test_module_from_wat(
"(module (func (export \"value\") (result i32) i32.const 1 i32.const 2 i32.add))",
)
let scalar_bytes = @binary.encode_module(scalar).unwrap()
for name in ["precompute", "precompute-propagate", "optimize-instructions"] {
let output = optimize_module_with_pass_flags(
scalar,
[name],
OptimizeOptions::new(),
).unwrap()
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_eq(body, [@lib.Instruction::i32_const(@lib.I32(3))])
@validate.validate_module(output).unwrap()
}
assert_eq(@binary.encode_module(scalar).unwrap(), scalar_bytes)
let calls = @passes.pass_test_module_from_wat(
"(module (func $helper (param i32) (result i32) i32.const 7) (func (export \"run\") (result i32) i32.const 9 call $helper))",
)
for name in ["dae", "dae-optimizing", "dae2", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
calls,
[name],
OptimizeOptions::new(),
).unwrap()
let env = @validate.Env::new().with_module(output)
assert_eq(
env.get_functype_by_funcidx(@lib.FuncIdx::new(0U)),
Some(@lib.FuncType::new([], [@lib.ValType::i32()])),
)
@validate.validate_module(output).unwrap()
}
for name in ["inlining", "inlining-optimizing"] {
let output = optimize_module_with_pass_flags(
calls,
[name],
OptimizeOptions::new(),
).unwrap()
assert_eq(output.code_sec.unwrap().0.length(), 1)
@validate.validate_module(output).unwrap()
}
let copies = @passes.pass_test_module_from_wat(
"(module (func (export \"copy\") (param i32) (result i32) (local i32 i32) local.get 0 local.set 1 local.get 1 local.set 2 local.get 2))",
)
for name in ["coalesce-locals", "simplify-locals"] {
let output = optimize_module_with_pass_flags(
copies,
[name],
OptimizeOptions::new(),
).unwrap()
assert_true(
@binary.encode_module(output).unwrap().length() <
@binary.encode_module(copies).unwrap().length(),
)
@validate.validate_module(output).unwrap()
}
}
///|
fn cmd_path_has_ascii_suffix_ignore_case(
path : String,
suffix : String,
) -> Bool {
if path.length() < suffix.length() {
return false
}
let start = path.length() - suffix.length()
let mut i = 0
while i < suffix.length() {
if ascii_lower_u16(path[start + i]) != ascii_lower_u16(suffix[i]) {
return false
}
i += 1
}
true
}
///|
fn cmd_dump_output_format(path : String) -> CmdPipelineDumpFormat? {
if cmd_path_has_ascii_suffix_ignore_case(path, ".wasm") {
Some(CmdPipelineDumpFormat::Wasm)
} else if cmd_path_has_ascii_suffix_ignore_case(path, ".wat") {
Some(CmdPipelineDumpFormat::Wat)
} else {
None
}
}
///|
fn cmd_dump_pass_target(flag : String) -> String? {
if flag.length() <= 5 {
return None
}
if flag[0] != 'd' ||
flag[1] != 'u' ||
flag[2] != 'm' ||
flag[3] != 'p' ||
flag[4] != '=' {
return None
}
Some(flag.sub(start=5).to_owned())
}
///|
fn cmd_extract_functions_pass_target(flag : String) -> String? {
if flag.length() <= 18 {
return None
}
if flag[0] != 'e' ||
flag[1] != 'x' ||
flag[2] != 't' ||
flag[3] != 'r' ||
flag[4] != 'a' ||
flag[5] != 'c' ||
flag[6] != 't' ||
flag[7] != '-' ||
flag[8] != 'f' ||
flag[9] != 'u' ||
flag[10] != 'n' ||
flag[11] != 'c' ||
flag[12] != 't' ||
flag[13] != 'i' ||
flag[14] != 'o' ||
flag[15] != 'n' ||
flag[16] != 's' ||
flag[17] != '=' {
return None
}
Some(flag.sub(start=18).to_owned())
}
///|
fn cmd_parse_function_index_list(raw : String) -> Array[UInt32]? {
let out : Array[UInt32] = []
for part in raw.split(",") {
match parse_non_negative_decimal(part.to_owned()) {
Some(idx) => out.push(idx.reinterpret_as_uint())
None => return None
}
}
if out.is_empty() {
None
} else {
Some(out)
}
}
///|
fn cmd_print_pass_target(flag : String) -> (String, String)? {
if !flag.has_prefix("print-") {
return None
}
let mut i = 6
while i < flag.length() {
if flag[i] == '=' {
let kind = flag.sub(start=6, end=i).to_owned()
let selector = flag.sub(start=i + 1).to_owned()
if kind.length() == 0 || selector.length() == 0 {
return None
}
return Some((kind, selector))
}
i += 1
}
None
}
///|
fn cmd_is_supported_print_kind(kind : String) -> Bool {
match kind {
"type"
| "func"
| "import"
| "table"
| "memory"
| "global"
| "export"
| "tag"
| "elem"
| "data" => true
_ => false
}
}
///|
fn cmd_parse_print_selector(raw : String) -> CmdPipelinePrintSelector? {
let trimmed = trimmed_string(raw)
if trimmed.length() == 0 {
return None
}
if trimmed.length() >= 2 &&
trimmed[0] == '(' &&
trimmed[trimmed.length() - 1] == ')' {
let inner = trimmed.sub(start=1, end=trimmed.length() - 1).to_owned()
match parse_non_negative_decimal(inner) {
Some(idx) =>
return Some(CmdPipelinePrintSelector::Index(idx.reinterpret_as_uint()))
None => ()
}
}
match parse_non_negative_decimal(trimmed) {
Some(idx) =>
Some(CmdPipelinePrintSelector::Index(idx.reinterpret_as_uint()))
None => Some(CmdPipelinePrintSelector::Name(trimmed))
}
}
///|
fn cmd_pipeline_step_optimize_count(steps : Array[CmdPipelineStep]) -> Int32 {
let mut count = 0
for step in steps {
match step {
PipelinePass(_) => count += 1
ExtractFunctions(_) => ()
_ => ()
}
}
count
}
///|
fn cmd_resolve_pipeline_steps(
pass_flags : Array[String],
) -> Result[Array[CmdPipelineStep], CmdError] {
let out : Array[CmdPipelineStep] = []
for flag in pass_flags {
if flag == "no-inline" ||
flag == "no-full-inline" ||
flag == "no-partial-inline" {
return Err(CmdError::unknown_pass_flag(flag))
}
if flag == "validate" {
out.push(CmdPipelineStep::Validate)
continue
}
match cmd_dump_pass_target(flag) {
Some(path) =>
match cmd_dump_output_format(path) {
Some(format) => {
out.push(CmdPipelineStep::Dump(path, format))
continue
}
None =>
return Err(
CmdError::CliParse(CliParseError::invalid_dump_path(path)),
)
}
None => ()
}
match cmd_extract_functions_pass_target(flag) {
Some(raw_indices) =>
match cmd_parse_function_index_list(raw_indices) {
Some(indices) => {
out.push(CmdPipelineStep::ExtractFunctions(indices))
continue
}
None =>
return Err(
CmdError::CliParse(
CliParseError::invalid_function_index_list(raw_indices),
),
)
}
None => ()
}
match cmd_print_pass_target(flag) {
Some((kind, raw_selector)) =>
if !cmd_is_supported_print_kind(kind) {
return Err(CmdError::unknown_pass_flag("print-\{kind}"))
} else {
match cmd_parse_print_selector(raw_selector) {
Some(selector) => {
out.push(CmdPipelineStep::Print(kind, selector))
continue
}
None => return Err(CmdError::unknown_pass_flag(flag))
}
}
None => ()
}
let registry_flag = match @passes.no_inline_pass_pattern(flag) {
Some((base, _)) => base
None => flag
}
match @passes.pass_registry_category(registry_flag) {
Some(@passes.HotPassRegistryCategory::HotPass)
| Some(@passes.HotPassRegistryCategory::ModulePass)
| Some(@passes.HotPassRegistryCategory::Preset)
| Some(@passes.HotPassRegistryCategory::BoundaryOnly) =>
out.push(CmdPipelineStep::PipelinePass(flag))
Some(@passes.HotPassRegistryCategory::Removed) | None =>
return Err(CmdError::unknown_pass_flag(flag))
}
}
Ok(out)
}
///|
fn string_to_bytes(text : String) -> Bytes {
let buf = @buffer.new(size_hint=text.length())
for c in text {
buf.write_byte(c.to_int().to_byte())
}
buf.to_bytes()
}
///|
fn cmd_optimize_trace_level_to_string(level : CliTracingLevel) -> String {
match level {
Pass => "pass"
Phase => "phase"
Helper => "helper"
}
}
///|
fn cmd_to_optimize_trace_level(level : CliTracingLevel) -> OptimizeTracingLevel {
match level {
Pass => OptimizeTracingLevel::pass()
Phase => OptimizeTracingLevel::phase()
Helper => OptimizeTracingLevel::helper()
}
}
///|
fn cmd_trace_optimize_max_passes(io : CmdIO) -> Int32? {
match (io.get_env)("STARSHINE_OPTIMIZE_MAX_PASSES") {
Some(raw) => parse_non_negative_decimal(raw)
None => None
}
}
///|
fn cmd_resolve_optimize_trace_level(
level : CliTracingLevel?,
) -> OptimizeTracingLevel? {
match level {
Some(level) => Some(cmd_to_optimize_trace_level(level))
None => None
}
}
///|
fn limit_pass_flags_by_max_passes(
pass_flags : Array[String],
max_passes : Int32?,
) -> (Array[String], Bool) {
match max_passes {
Some(limit) =>
if limit < pass_flags.length() {
let out : Array[String] = []
let mut i = 0
while i < limit {
out.push(pass_flags[i])
i += 1
}
(out, true)
} else {
(pass_flags, false)
}
None => (pass_flags, false)
}
}
///|
fn cmd_trace_join_pass_flags(flags : Array[String]) -> String {
let out = StringBuilder::new()
let mut i = 0
while i < flags.length() {
if i > 0 {
out.write_string(",")
}
out.write_string(flags[i])
i += 1
}
out.to_string()
}
///|
fn format_pipeline_validation_error(
stage : String,
mod : Module,
err : ValidationError,
) -> String {
let out = StringBuilder::new()
out.write_string("\{stage}: \{validation_error_message(err)}")
match validation_error_func_idx(err) {
Some(func_idx) => {
out.write_string("\n\nOffending function idx=\{func_idx}\n")
out.write_string(mod.pretty_print_func(func_idx))
}
None => ()
}
out.to_string()
}
///|
fn validate_module_for_pipeline_step(
mod : Module,
stage : String,
) -> Result[Unit, CmdError] {
match validate_module(mod) {
Ok(_) => Ok(())
Err(err) => {
cmd_debug_dump_invalid_final_module(mod)
match validation_error_func_idx(err) {
Some(func_idx) =>
cmd_debug_dump_invalid_final_func_text(
mod.pretty_print_func(func_idx),
)
None => ()
}
Err(
CmdError::OptimizeFailed(
format_pipeline_validation_error(stage, mod, err),
),
)
}
}
}
///|
fn write_pipeline_dump(
io : CmdIO,
mod : Module,
path : String,
format : CmdPipelineDumpFormat,
) -> Result[Unit, CmdError] {
let bytes = match format {
Wasm =>
match (io.encode_module)(mod) {
Ok(bytes) => bytes
Err(err) => return Err(CmdError::EncodeFailed(path, err))
}
Wat =>
match (io.print_text_module)(mod) {
Ok(text) => text
Err(err) => return Err(CmdError::OptimizeFailed("dump \{path}: \{err}"))
}
}
match (io.write_file)(path, bytes) {
Ok(_) => Ok(())
Err(err) => Err(CmdError::OutputWriteFailed("\{path}: \{err}"))
}
}
///|
fn run_extract_functions_step(
mod : Module,
indices : Array[UInt32],
) -> Result[Module, CmdError] {
let roots : Array[@lib.FuncIdx] = []
for idx in indices {
roots.push(@lib.FuncIdx::new(idx))
}
match @passes.rume_extract_functions(mod, roots) {
Ok(out) => Ok(out)
Err(err) => Err(CmdError::OptimizeFailed("extract-functions: \{err}"))
}
}
///|
fn cmd_name_to_string(name : @lib.Name) -> String {
let @lib.Name(sv) = name
sv.to_owned()
}
///|
fn cmd_lookup_name_map_index(names : @lib.NameMap?, target : String) -> UInt32? {
match names {
Some(@lib.NameMap(entries)) =>
for entry in entries {
let @lib.NameAssoc(idx, name) = entry
if cmd_name_to_string(name) == target {
return Some(idx)
}
}
None => ()
}
None
}
///|
fn cmd_lookup_name_map_name(
names : @lib.NameMap?,
target_idx : UInt32,
) -> String? {
match names {
Some(@lib.NameMap(entries)) =>
for entry in entries {
let @lib.NameAssoc(idx, name) = entry
if idx == target_idx {
return Some(cmd_name_to_string(name))
}
}
None => ()
}
None
}
///|
fn cmd_flatten_type_sec(type_sec : @lib.TypeSec) -> Array[@lib.SubType] {
let @lib.TypeSec(rec_types) = type_sec
let subtypes : Array[@lib.SubType] = []
for rec_type in rec_types {
match rec_type {
@lib.SingleRecType(sub_type) => subtypes.push(sub_type)
@lib.GroupRecType(group) =>
for sub_type in group {
subtypes.push(sub_type)
}
}
}
subtypes
}
///|
fn cmd_print_val_type_list(types : Array[@lib.ValType]) -> String {
let out = StringBuilder::new()
out.write_char('[')
let mut i = 0
while i < types.length() {
if i > 0 {
out.write_char(' ')
}
out.write_string(types[i].pretty_print(PrettyPrintContext::new()))
i += 1
}
out.write_char(']')
out.to_string()
}
///|
fn cmd_print_type_idx_list(type_idxs : Array[@lib.TypeIdx]) -> String {
let out = StringBuilder::new()
let mut i = 0
while i < type_idxs.length() {
if i > 0 {
out.write_char(' ')
}
out.write_string(type_idxs[i].pretty_print(PrettyPrintContext::new()))
i += 1
}
out.to_string()
}
///|
fn cmd_print_comp_type(comp_type : @lib.CompType) -> String {
match comp_type {
@lib.FuncCompType(params, results) =>
"(Func \{cmd_print_val_type_list(params)} \{cmd_print_val_type_list(results)})"
_ => comp_type.pretty_print(PrettyPrintContext::new())
}
}
///|
fn cmd_print_sub_type(sub_type : @lib.SubType) -> String {
match sub_type {
@lib.CompTypeSubType(_, comp_type) =>
"(Sub Final \{cmd_print_comp_type(comp_type)})"
@lib.SubType(is_final, super_types, _, comp_type) => {
if super_types.is_empty() && !is_final {
return cmd_print_comp_type(comp_type)
}
let prefix = if is_final { "(Sub Final " } else { "(Sub " }
if super_types.is_empty() {
prefix + cmd_print_comp_type(comp_type) + ")"
} else {
prefix +
cmd_print_type_idx_list(super_types) +
" " +
cmd_print_comp_type(comp_type) +
")"
}
}
}
}
///|
fn cmd_print_selector_text(selector : CmdPipelinePrintSelector) -> String {
match selector {
Index(idx) => "(\{idx})"
Name(name) => "\"\{name}\""
}
}
///|
fn cmd_print_lookup_failed(
kind : String,
selector : CmdPipelinePrintSelector,
) -> CmdError {
CmdError::OptimizeFailed(
"print \{kind}: no item matched selector \{cmd_print_selector_text(selector)}",
)
}
///|
fn cmd_print_kind_label(kind : String) -> String {
match kind {
"type" => "Type"
"func" => "Func"
"import" => "Import"
"table" => "Table"
"memory" => "Memory"
"global" => "Global"
"export" => "Export"
"tag" => "Tag"
"elem" => "Elem"
"data" => "Data"
_ => kind
}
}
///|
fn cmd_module_item_names(mod : Module, kind : String) -> @lib.NameMap? {
match mod.name_sec {
Some(name_sec) =>
match kind {
"type" => name_sec.type_names
"func" => name_sec.func_names
"table" => name_sec.table_names
"memory" => name_sec.memory_names
"global" => name_sec.global_names
"tag" => name_sec.tag_names
"elem" => name_sec.elem_names
"data" => name_sec.data_names
_ => None
}
None => None
}
}
///|
fn cmd_print_label(
kind : String,
idx : UInt32,
name~ : String? = None,
) -> String {
match name {
Some(name) if name.length() > 0 =>
"\{cmd_print_kind_label(kind)}[\{idx} \{name}]"
_ => "\{cmd_print_kind_label(kind)}[\{idx}]"
}
}
///|
fn cmd_resolve_named_section_index(
mod : Module,
kind : String,
selector : CmdPipelinePrintSelector,
count : Int32,
) -> Result[(UInt32, String?), CmdError] {
let names = cmd_module_item_names(mod, kind)
match selector {
Index(idx) => {
if idx.reinterpret_as_int() >= count {
return Err(cmd_print_lookup_failed(kind, selector))
}
Ok((idx, cmd_lookup_name_map_name(names, idx)))
}
Name(target) =>
match cmd_lookup_name_map_index(names, target) {
Some(idx) if idx.reinterpret_as_int() < count => Ok((idx, Some(target)))
_ => Err(cmd_print_lookup_failed(kind, selector))
}
}
}
///|
fn cmd_lookup_import_index(
imports : Array[@lib.Import],
target : String,
) -> UInt32? {
let mut field_match : UInt32? = None
let mut i = 0
while i < imports.length() {
let @lib.Import(module_name, field_name, _) = imports[i]
let module_text = cmd_name_to_string(module_name)
let field_text = cmd_name_to_string(field_name)
if target == "\{module_text}.\{field_text}" {
return Some(i.reinterpret_as_uint())
}
if field_match is None && target == field_text {
field_match = Some(i.reinterpret_as_uint())
}
i += 1
}
field_match
}
///|
fn cmd_lookup_export_index(
exports : Array[@lib.Export],
target : String,
) -> UInt32? {
let mut i = 0
while i < exports.length() {
let @lib.Export(name, _) = exports[i]
if cmd_name_to_string(name) == target {
return Some(i.reinterpret_as_uint())
}
i += 1
}
None
}
///|
fn cmd_resolve_import_index(
selector : CmdPipelinePrintSelector,
imports : Array[@lib.Import],
) -> Result[(UInt32, String?), CmdError] {
match selector {
Index(idx) => {
if idx.reinterpret_as_int() >= imports.length() {
return Err(cmd_print_lookup_failed("import", selector))
}
let @lib.Import(module_name, field_name, _) = imports[idx.reinterpret_as_int()]
Ok(
(
idx,
Some(
"\{cmd_name_to_string(module_name)}.\{cmd_name_to_string(field_name)}",
),
),
)
}
Name(target) =>
match cmd_lookup_import_index(imports, target) {
Some(idx) => {
let @lib.Import(module_name, field_name, _) = imports[idx.reinterpret_as_int()]
Ok(
(
idx,
Some(
"\{cmd_name_to_string(module_name)}.\{cmd_name_to_string(field_name)}",
),
),
)
}
None => Err(cmd_print_lookup_failed("import", selector))
}
}
}
///|
fn cmd_resolve_export_index(
selector : CmdPipelinePrintSelector,
exports : Array[@lib.Export],
) -> Result[(UInt32, String?), CmdError] {
match selector {
Index(idx) => {
if idx.reinterpret_as_int() >= exports.length() {
return Err(cmd_print_lookup_failed("export", selector))
}
let @lib.Export(name, _) = exports[idx.reinterpret_as_int()]
Ok((idx, Some(cmd_name_to_string(name))))
}
Name(target) =>
match cmd_lookup_export_index(exports, target) {
Some(idx) => Ok((idx, Some(target)))
None => Err(cmd_print_lookup_failed("export", selector))
}
}
}
///|
fn cmd_indent_print_body(text : String) -> String {
if text.length() == 0 {
return " \n"
}
let out = StringBuilder::new()
let mut at_line_start = true
for c in text {
if at_line_start {
out.write_string(" ")
at_line_start = false
}
out.write_char(c)
if c == '\n' {
at_line_start = true
}
}
if !at_line_start {
out.write_char('\n')
}
out.to_string()
}
///|
fn cmd_write_pipeline_print_log(
io : CmdIO,
input_path : String,
entry_idx : Int32,
label : String,
body : String,
write_header : Bool,
) -> Result[Unit, CmdError] {
let out = StringBuilder::new()
if write_header {
out.write_string("Log: \{input_path}\n")
}
out.write_string("\{entry_idx}: \{label}\n")
out.write_string(cmd_indent_print_body(body))
match (io.write_stderr)(string_to_bytes(out.to_string())) {
Ok(_) => Ok(())
Err(err) =>
Err(
CmdError::OutputWriteFailed(
"stderr print log for \{input_path}: \{err}",
),
)
}
}
///|
fn cmd_resolve_pipeline_print_entry(
mod : Module,
kind : String,
selector : CmdPipelinePrintSelector,
) -> Result[(String, String), CmdError] {
match kind {
"type" =>
match mod.type_sec {
Some(type_sec) => {
let subtypes = cmd_flatten_type_sec(type_sec)
let (idx, name) = match
cmd_resolve_named_section_index(
mod,
kind,
selector,
subtypes.length(),
) {
Ok(found) => found
Err(err) => return Err(err)
}
let body = match subtypes.get(idx.reinterpret_as_int()) {
Some(sub_type) => cmd_print_sub_type(sub_type)
None => return Err(cmd_print_lookup_failed(kind, selector))
}
Ok((cmd_print_label(kind, idx, name~), body))
}
None => Err(cmd_print_lookup_failed(kind, selector))
}
"func" => {
let imported_funcs = match mod.import_sec {
Some(@lib.ImportSec(imports)) => {
let mut count = 0
for import_entry in imports {
match import_entry {
@lib.Import(_, _, @lib.FuncExternType(_)) => count += 1
_ => ()
}
}
count
}
None => 0
}
let defined_funcs = match mod.func_sec {
Some(@lib.FuncSec(type_idxs)) => type_idxs.length()
None => 0
}
let total_funcs = imported_funcs + defined_funcs
let (idx, name) = match
cmd_resolve_named_section_index(mod, kind, selector, total_funcs) {
Ok(found) => found
Err(err) => return Err(err)
}
Ok(
(
cmd_print_label(kind, idx, name~),
mod.pretty_print_func(@lib.FuncIdx::new(idx)),
),
)
}
"import" =>
match mod.import_sec {
Some(@lib.ImportSec(imports)) => {
let (idx, name) = match cmd_resolve_import_index(selector, imports) {
Ok(found) => found
Err(err) => return Err(err)
}
let body = match imports.get(idx.reinterpret_as_int()) {
Some(import_entry) => import_entry.to_string()
None => return Err(cmd_print_lookup_failed(kind, selector))
}
Ok((cmd_print_label(kind, idx, name~), body))
}
None => Err(cmd_print_lookup_failed(kind, selector))
}
"table" =>
match mod.table_sec {
Some(@lib.TableSec(tables)) => {
let (idx, name) = match
cmd_resolve_named_section_index(
mod,
kind,
selector,
tables.length(),
) {
Ok(found) => found
Err(err) => return Err(err)
}
let body = match tables.get(idx.reinterpret_as_int()) {
Some(table) => table.to_string()
None => return Err(cmd_print_lookup_failed(kind, selector))
}
Ok((cmd_print_label(kind, idx, name~), body))
}
None => Err(cmd_print_lookup_failed(kind, selector))
}
"memory" =>
match mod.mem_sec {
Some(@lib.MemSec(mems)) => {
let (idx, name) = match
cmd_resolve_named_section_index(mod, kind, selector, mems.length()) {
Ok(found) => found
Err(err) => return Err(err)
}
let body = match mems.get(idx.reinterpret_as_int()) {
Some(mem) => mem.to_string()
None => return Err(cmd_print_lookup_failed(kind, selector))
}
Ok((cmd_print_label(kind, idx, name~), body))
}
None => Err(cmd_print_lookup_failed(kind, selector))
}
"global" =>
match mod.global_sec {
Some(@lib.GlobalSec(globals)) => {
let (idx, name) = match
cmd_resolve_named_section_index(
mod,
kind,
selector,
globals.length(),
) {
Ok(found) => found
Err(err) => return Err(err)
}
let body = match globals.get(idx.reinterpret_as_int()) {
Some(global) => global.to_string()
None => return Err(cmd_print_lookup_failed(kind, selector))
}
Ok((cmd_print_label(kind, idx, name~), body))
}
None => Err(cmd_print_lookup_failed(kind, selector))
}
"export" =>
match mod.export_sec {
Some(@lib.ExportSec(exports)) => {
let (idx, name) = match cmd_resolve_export_index(selector, exports) {
Ok(found) => found
Err(err) => return Err(err)
}
let body = match exports.get(idx.reinterpret_as_int()) {
Some(export_entry) => export_entry.to_string()
None => return Err(cmd_print_lookup_failed(kind, selector))
}
Ok((cmd_print_label(kind, idx, name~), body))
}
None => Err(cmd_print_lookup_failed(kind, selector))
}
"tag" =>
match mod.tag_sec {
Some(@lib.TagSec(tags)) => {
let (idx, name) = match
cmd_resolve_named_section_index(mod, kind, selector, tags.length()) {
Ok(found) => found
Err(err) => return Err(err)
}
let body = match tags.get(idx.reinterpret_as_int()) {
Some(tag) => tag.to_string()
None => return Err(cmd_print_lookup_failed(kind, selector))
}
Ok((cmd_print_label(kind, idx, name~), body))
}
None => Err(cmd_print_lookup_failed(kind, selector))
}
"elem" =>
match mod.elem_sec {
Some(@lib.ElemSec(elems)) => {
let (idx, name) = match
cmd_resolve_named_section_index(mod, kind, selector, elems.length()) {
Ok(found) => found
Err(err) => return Err(err)
}
let body = match elems.get(idx.reinterpret_as_int()) {
Some(elem) => elem.to_string()
None => return Err(cmd_print_lookup_failed(kind, selector))
}
Ok((cmd_print_label(kind, idx, name~), body))
}
None => Err(cmd_print_lookup_failed(kind, selector))
}
"data" =>
match mod.data_sec {
Some(@lib.DataSec(datas)) => {
let (idx, name) = match
cmd_resolve_named_section_index(mod, kind, selector, datas.length()) {
Ok(found) => found
Err(err) => return Err(err)
}
let body = match datas.get(idx.reinterpret_as_int()) {
Some(data) => data.to_string()
None => return Err(cmd_print_lookup_failed(kind, selector))
}
Ok((cmd_print_label(kind, idx, name~), body))
}
None => Err(cmd_print_lookup_failed(kind, selector))
}
_ => Err(CmdError::unknown_pass_flag("print-\{kind}"))
}
}
///|
#valtype
priv struct CmdPassSegmentResult {
module_ : Module
changed : Bool
}
///|
fn run_pipeline_pass_segment(
io : CmdIO,
input_path : String,
mod : Module,
pass_flags : Array[String],
options : OptimizeOptions,
trace_optimize : Bool,
trace_level : OptimizeTracingLevel?,
) -> Result[CmdPassSegmentResult, CmdError] {
if pass_flags.length() == 0 {
return Ok({ module_: mod, changed: false, })
}
if trace_optimize {
let resolved_trace_level = match trace_level {
Some(level) => level
None => OptimizeTracingLevel::pass()
}
match
optimize_module_with_pass_flags_trace(
mod,
pass_flags,
options,
fn(msg) { cmd_trace_emit(io, true, "input \{input_path}:opt \{msg}") },
validate_final_module=false,
trace_level=resolved_trace_level,
) {
Ok(out) => Ok({ module_: out, changed: !physical_equal(out, mod), })
Err(e) => Err(CmdError::OptimizeFailed("\{input_path}: \{e}"))
}
} else {
match
optimize_module_with_pass_flags(
mod,
pass_flags,
options,
validate_final_module=false,
) {
Ok(out) => Ok({ module_: out, changed: !physical_equal(out, mod), })
Err(e) => Err(CmdError::OptimizeFailed("\{input_path}: \{e}"))
}
}
}
///|
fn cmd_post_encode_repro_note(
io : CmdIO,
mod : Module,
pass_flags : Array[String],
options : OptimizeOptions,
optimize_level : Int32,
shrink_level : Int32,
traps_never_happen : Bool,
ignore_implicit_traps : Bool,
monomorphize_min_benefit : Int32,
closed_world : Bool,
low_memory_unused : Bool,
low_memory_bound : UInt64,
) -> String {
let _ = io
let _ = mod
let args = StringBuilder::new()
let mut needs_space = false
let write_arg = fn(text : String) {
if needs_space {
args.write_char(' ')
}
args.write_string(text)
needs_space = true
}
for flag in pass_flags {
write_arg("--\{flag}")
}
if optimize_level > 0 {
write_arg("--optimize-level \{optimize_level}")
}
if shrink_level > 0 {
write_arg("--shrink-level \{shrink_level}")
}
if traps_never_happen {
write_arg("--traps-never-happen")
}
if ignore_implicit_traps {
write_arg("--ignore-implicit-traps")
}
if options.zero_filled_memory {
write_arg("--zero-filled-memory")
}
if options.inlining.always_inline_max_size != 2 {
write_arg(
"--always-inline-max-function-size \{options.inlining.always_inline_max_size}",
)
}
if options.inlining.flexible_inline_max_size != 20 {
write_arg(
"--flexible-inline-max-function-size \{options.inlining.flexible_inline_max_size}",
)
}
if options.inlining.one_caller_inline_max_size != -1 {
write_arg(
"--one-caller-inline-max-function-size \{options.inlining.one_caller_inline_max_size}",
)
}
if options.inlining.max_combined_binary_size != 400 * 1024 {
write_arg(
"--inline-max-combined-binary-size \{options.inlining.max_combined_binary_size}",
)
}
if options.inlining.allow_functions_with_loops {
write_arg("--inline-functions-with-loops")
}
if options.inlining.partial_inlining_ifs != 0 {
write_arg("--partial-inlining-ifs \{options.inlining.partial_inlining_ifs}")
}
if monomorphize_min_benefit != 5 {
write_arg("--monomorphize-min-benefit \{monomorphize_min_benefit}")
}
if closed_world {
write_arg("--closed-world")
}
if low_memory_unused {
write_arg("--low-memory-unused")
}
if low_memory_bound != 1024UL {
write_arg("--low-memory-bound \{low_memory_bound}")
}
let args_text = args.to_string()
let suffix = if args_text.length() > 0 { " \{args_text}" } else { "" }
"\n\nRepro hint: rerun the active hot pipeline with `starshine --debug-serial-passes --tracing pass\{suffix} `."
}
///|
test "cmd_post_encode_repro_note reflects active hot pipeline flags" {
let note = cmd_post_encode_repro_note(
CmdIO::new(),
Module::new(),
["vacuum"],
OptimizeOptions::new(),
0,
0,
false,
false,
5,
false,
false,
1024UL,
)
assert_true(note.contains("vacuum"))
assert_false(note.contains("currently disabled"))
assert_true(note.contains("--debug-serial-passes"))
}
///|
test "vacuum and dae2 optimizing retain effects before a nested unreachable" {
let input = @wast.wast_to_binary_module(
"(module (import \"host\" \"tick\" (func $tick)) (global (export \"state\") (mut i32) (i32.const 0)) (func (export \"run\") (param i32) i32.const 7 global.set 0 call $tick block i32.const 1 drop block unreachable end end i32.const 9 global.set 0))",
).unwrap()
let saved = @binary.encode_module(input).unwrap()
for pass in ["vacuum", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[pass],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_eq(body[0:4].to_owned(), [
@lib.Instruction::i32_const(@lib.I32(7)),
@lib.Instruction::global_set(@lib.GlobalIdx::new(0U)),
@lib.Instruction::call(@lib.FuncIdx::new(0U)),
@lib.Instruction::unreachable_(),
])
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "explicit vacuum dispatches parity cleanup through the cmd pipeline" {
let wat =
#|(module
#| (import "env" "value" (func $value (result i32)))
#| (global $sink (mut i32) (i32.const 0))
#| (func
#| (drop (i32.div_s (i32.const 84) (i32.const 3)))
#| (global.set $sink (i32.const 1)))
#| (func (local i32)
#| (drop (local.tee 0 (call $value)))
#| (global.set $sink (local.get 0)))
#| (func (local i32)
#| (local.set 0 (i32.const 7))))
#|
let input = match
@wast.wast_to_binary_module(wat, filename="vacuum-cmd-dispatch.wat") {
Ok(mod_) => mod_
Err(err) => fail("expected cmd vacuum fixture parse success, got \{err}")
}
let optimized = match
optimize_module_with_pass_flags(input, ["vacuum"], OptimizeOptions::new()) {
Ok(mod_) => mod_
Err(err) => fail("expected explicit cmd vacuum success, got \{err}")
}
match optimized.code_sec {
Some(
@lib.CodeSec(
[
@lib.Func(
_,
@lib.Expr(
[@lib.I32Const(@lib.I32(1)), @lib.GlobalSet(@lib.GlobalIdx(0U))]
)
),
@lib.Func(
_,
@lib.Expr(
[
@lib.Call(@lib.FuncIdx(0U)),
@lib.LocalSet(@lib.LocalIdx(0U)),
@lib.LocalGet(@lib.LocalIdx(0U)),
@lib.GlobalSet(@lib.GlobalIdx(0U)),
]
)
),
@lib.Func(_, @lib.Expr([@lib.Nop])),
]
)
) => ()
other =>
fail(
"expected cmd vacuum dispatch to expose all three cleanup families, got \{@debug.to_string(other)}",
)
}
}
///|
test "explicit vacuum dispatches residual parity cleanup through cmd" {
let wat =
#|(module
#| (type $s (struct (field (mut i32)) (field eqref)))
#| (func
#| (if (i32.eqz (i32.const 0)) (then)))
#| (func (local i32 i32)
#| (loop
#| (local.set 0 (i32.const 44))
#| (local.set 1 (local.get 0))))
#| (func (local anyref)
#| (local.set 0 (struct.new_default $s)))
#| (func
#| (drop (struct.get $s 0 (struct.new_default $s)))))
#|
let input = match
@wast.wast_to_binary_module(wat, filename="vacuum-residual-cmd.wat") {
Ok(mod_) => mod_
Err(err) =>
fail("expected residual cmd vacuum fixture parse success, got \{err}")
}
let optimized = match
optimize_module_with_pass_flags(input, ["vacuum"], OptimizeOptions::new()) {
Ok(mod_) => mod_
Err(err) =>
fail("expected residual explicit cmd vacuum success, got \{err}")
}
match optimized.code_sec {
Some(
@lib.CodeSec(
[
@lib.Func(_, @lib.Expr([@lib.Nop])),
@lib.Func(_, @lib.Expr([@lib.Nop])),
@lib.Func(_, @lib.Expr([@lib.Nop])),
@lib.Func(_, @lib.Expr([@lib.Nop])),
]
)
) => ()
other =>
fail(
"expected cmd vacuum dispatch to remove the residual cleanup families, got \{@debug.to_string(other)}",
)
}
}
///|
test "explicit vacuum prunes a GC type made unused by body cleanup" {
let wat =
#|(module
#| (type (func))
#| (type (struct (field (mut i32)) (field eqref)))
#| (func (export "main") (type 0)
#| (local i32 (ref null 0) anyref)
#| struct.new_default 1
#| local.set 2
#| local.get 2
#| drop
#| return
#| local.get 2
#| drop)
#| (start 0))
#|
let input = match
@wast.wast_to_binary_module(wat, filename="vacuum-dead-gc-type-cmd.wat") {
Ok(mod_) => mod_
Err(err) => fail("expected dead GC type fixture parse success, got \{err}")
}
let optimized = match
optimize_module_with_pass_flags(input, ["vacuum"], OptimizeOptions::new()) {
Ok(mod_) => mod_
Err(err) => fail("expected explicit Vacuum success, got \{err}")
}
match @validate.validate_module(optimized) {
Ok(_) => ()
Err(err) => fail("expected dead GC type output to validate: \{err}")
}
match optimized.type_sec {
Some(@lib.TypeSec([@lib.SingleRecType(subtype)])) =>
assert_true(subtype.get_comptype() is @lib.FuncCompType(_, _))
other =>
fail(
"expected command Vacuum to keep only the function type, got \{@debug.to_string(other)}",
)
}
match optimized.func_sec {
Some(@lib.FuncSec([type_idx])) =>
@debug.assert_eq(type_idx, @lib.TypeIdx::new(0U))
other =>
fail("expected one remapped function, got \{@debug.to_string(other)}")
}
}
///|
test "explicit vacuum dispatch removes null throw_ref try_table wrappers" {
let wat =
#|(module
#| (func
#| block (result exnref)
#| try_table (result exnref) (catch_all_ref 0)
#| ref.null exn
#| throw_ref
#| end
#| end
#| drop)
#| (func
#| block
#| try_table (catch_all 0)
#| ref.null exn
#| throw_ref
#| end
#| end))
#|
let input = match
@wast.wast_to_binary_module(wat, filename="vacuum-null-throw-ref-cmd.wat") {
Ok(mod_) => mod_
Err(err) =>
fail("expected cmd null throw_ref fixture parse success, got \{err}")
}
let optimized = match
optimize_module_with_pass_flags(input, ["vacuum"], OptimizeOptions::new()) {
Ok(mod_) => mod_
Err(err) =>
fail("expected explicit cmd null throw_ref vacuum success, got \{err}")
}
match @validate.validate_module(optimized) {
Ok(_) => ()
Err(err) => fail("expected cmd null throw_ref output to validate: \{err}")
}
match optimized.code_sec {
Some(
@lib.CodeSec(
[
@lib.Func(_, @lib.Expr([@lib.RefNull(_), @lib.ThrowRef])),
@lib.Func(_, @lib.Expr([@lib.RefNull(_), @lib.ThrowRef])),
]
)
) => ()
other =>
fail(
"expected cmd vacuum to expose both bare null throw_ref traps, got \{@debug.to_string(other)}",
)
}
let encoded = match @binary.encode_module(optimized) {
Ok(bytes) => bytes
Err(err) => fail("expected cmd null throw_ref output to encode: \{err}")
}
assert_eq(encoded.length(), 34)
}
///|
test "explicit remove-unused-brs dispatch folds caught throw through empty block" {
let wat =
#|(module
#| (type $empty (func))
#| (type $unused (func (param i32)))
#| (tag $e (type $empty))
#| (tag $unused_e (type $unused))
#| (func
#| (block $outer
#| (block
#| (try_table (catch $e $outer)
#| (throw $e))
#| (unreachable)))))
#|
let input = match
@wast.wast_to_binary_module(wat, filename="rub-caught-throw-exit-cmd.wat") {
Ok(mod_) => mod_
Err(err) =>
fail("expected cmd caught throw exit fixture parse success, got \{err}")
}
let optimized = match
optimize_module_with_pass_flags(
input,
["remove-unused-brs"],
OptimizeOptions::new(),
) {
Ok(mod_) => mod_
Err(err) =>
fail("expected explicit cmd caught throw exit success, got \{err}")
}
match @validate.validate_module(optimized) {
Ok(_) => ()
Err(err) =>
fail("expected cmd caught throw exit output to validate: \{err}")
}
match optimized.code_sec {
Some(
@lib.CodeSec(
[
@lib.Func(
_,
@lib.Expr(
[
@lib.Block(
_,
@lib.Expr(
[
@lib.Block(
_,
@lib.Expr(
[
@lib.TryTable(
_,
[@lib.Catch(_, @lib.LabelIdx(1U))],
@lib.Expr([])
),
]
)
),
]
)
),
]
)
),
]
)
) => ()
other =>
fail(
"expected cmd caught try_table to fall through nested void blocks, got \{@debug.to_string(other)}",
)
}
}
///|
test "Binaryen 132 dae2 dispatch removes a result forwarded to an unused parameter" {
let source =
#|(module
#| (func $producer (param i32) (result i32) (local.get 0))
#| (func $sink (param i32))
#| (func (export "run") (call $sink (call $producer (i32.const 8)))))
let original = match @wast.wast_to_binary_module(source) {
Ok(value) => value
Err(err) => fail("invalid DAE2 command fixture: \{err}")
}
for pass in ["dae2", "dae2-optimizing"] {
let optimized = match
optimize_module_with_pass_flags(original, [pass], OptimizeOptions::new()) {
Ok(value) => value
Err(err) => fail("DAE2 command dispatch: \{err}")
}
let context = @validate.Env::new().with_module(optimized)
for index in [0U, 1U] {
@debug.assert_eq(
context.get_functype_by_funcidx(@lib.FuncIdx::new(index)),
Some(@lib.FuncType::new([], [])),
)
}
}
}
///|
test "command precompute-propagate keeps a folded if self-branch target live" {
let source =
#|(module
#| (func (export "main") (result i32)
#| block
#| i32.const 1
#| if
#| br 0
#| end
#| end
#| i32.const 0))
for pass in ["precompute", "precompute-propagate"] {
let input = match @wast.wast_to_binary_module(source) {
Ok(mod_) => mod_
Err(err) => fail("expected valid command branch fixture: \{err}")
}
let optimized = match
optimize_module_with_pass_flags(input, [pass], OptimizeOptions::new()) {
Ok(mod_) => mod_
Err(err) => fail("expected branch-safe command \{pass}: \{err}")
}
match @validate.validate_module(optimized) {
Ok(_) => ()
Err(err) =>
fail("expected valid command branch target after \{pass}: \{err}")
}
}
}
///|
test "Binaryen 132 explicit precompute variants fold relaxed immutable struct reads through cmd" {
let source =
#|(module
#| (type (struct (field i32)))
#| (func (result i32)
#| (struct.atomic.get relaxed 0 0 (struct.new 0 (i32.const 42)))))
let original = match @wast.wast_to_binary_module(source) {
Ok(value) => value
Err(err) => fail("expected relaxed atomic command fixture: \{err}")
}
for pass in ["precompute", "precompute-propagate"] {
let optimized = match
optimize_module_with_pass_flags(original, [pass], OptimizeOptions::new()) {
Ok(value) => value
Err(err) => fail("expected relaxed atomic command optimization: \{err}")
}
match optimized.code_sec {
Some(@lib.CodeSec([@lib.Func(_, @lib.Expr(body))])) =>
@debug.assert_eq(body, [@lib.Instruction::i32_const(@lib.I32(42))])
_ => fail("expected one optimized function")
}
}
}
///|
test "explicit vacuum removes unshared atomic gets through cmd" {
let input = match
@binary.decode_module(
Bytes::from_array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0d, 0x03, 0x5f, 0x01,
0x78, 0x01, 0x5e, 0x7f, 0x01, 0x60, 0x01, 0x64, 0x00, 0x00, 0x03, 0x02, 0x01,
0x02, 0x0a, 0x19, 0x01, 0x17, 0x00, 0x20, 0x00, 0xfe, 0x5d, 0x00, 0x00, 0x00,
0x1a, 0x41, 0x07, 0x41, 0x01, 0xfb, 0x06, 0x01, 0x41, 0x00, 0xfb, 0x0b, 0x01,
0x1a, 0x0b,
]),
) {
Ok(mod_) => mod_
Err(err) => fail("expected cmd atomic-get fixture to decode, got \{err}")
}
let optimized = match
optimize_module_with_pass_flags(input, ["vacuum"], OptimizeOptions::new()) {
Ok(mod_) => mod_
Err(err) =>
fail("expected explicit cmd atomic-get vacuum success, got \{err}")
}
match optimized.code_sec {
Some(@lib.CodeSec([@lib.Func(_, @lib.Expr(instrs))])) => {
assert_false(
instrs.any(fn(instr) {
match instr {
@lib.StructAtomicGet(_, _, _)
| @lib.StructAtomicGetS(_, _, _)
| @lib.StructAtomicGetU(_, _, _) => true
_ => false
}
}),
)
assert_true(instrs.any(fn(instr) { instr is @lib.ArrayGet(_) }))
}
other => fail("expected one cmd atomic-get function, got \{other}")
}
}
///|
test "explicit vacuum removes loop-local dropped reads through cmd" {
let wat =
#|(module
#| (func (param i32) (result i32)
#| (local i32 i32)
#| (local.set 1 (i32.const 37))
#| (local.set 2 (local.get 1))
#| (loop $again
#| (drop (local.get 1))
#| (br_if $again (local.get 0)))
#| (local.get 2)))
#|
let input = match
@wast.wast_to_binary_module(wat, filename="vacuum-loop-read-cmd.wat") {
Ok(mod_) => mod_
Err(err) => fail("expected cmd loop-read fixture parse success, got \{err}")
}
let optimized = match
optimize_module_with_pass_flags(input, ["vacuum"], OptimizeOptions::new()) {
Ok(mod_) => mod_
Err(err) =>
fail("expected explicit cmd loop-read vacuum success, got \{err}")
}
match optimized.code_sec {
Some(@lib.CodeSec([@lib.Func(_, @lib.Expr(instrs))])) => {
let mut saw_loop = false
let mut saw_backedge = false
for instr in instrs {
match instr {
@lib.Loop(_, @lib.Expr(body)) => {
saw_loop = true
for nested in body {
match nested {
@lib.Drop =>
fail("expected cmd loop-local dropped read removal")
@lib.BrIf(@lib.LabelIdx(0U)) => saw_backedge = true
_ => ()
}
}
}
_ => ()
}
}
assert_true(saw_loop)
assert_true(saw_backedge)
}
other => fail("expected one cmd loop-read function, got \{other}")
}
}
///|
fn cmd_append_error_note(err : CmdError, note : String) -> CmdError {
match err {
EncodeFailed(path, inner) =>
CmdError::EncodeFailed(
path,
CmdEncodeError::adapter(cmd_encode_error_message(inner) + note),
)
OptimizeFailed(msg) => CmdError::OptimizeFailed(msg + note)
_ => err
}
}
///|
fn cmd_trace_emit(io : CmdIO, enabled : Bool, msg : String) -> Unit {
if !enabled {
return
}
ignore((io.write_stderr)(string_to_bytes("[trace] \{msg}\n")))
}
///|
fn cmd_trace_timer_start(enabled : Bool) -> UInt64 {
if enabled {
@lib.trace_now_us()
} else {
0UL
}
}
///|
fn cmd_trace_timer_stop(
io : CmdIO,
enabled : Bool,
name : String,
start_us : UInt64,
) -> Unit {
if !enabled {
return
}
let elapsed_us = @lib.trace_elapsed_us_since(start_us)
cmd_trace_emit(
io,
true,
"perf:timer name=cmd:\{name} elapsed_us=\{elapsed_us} total_us=\{elapsed_us}",
)
}
///|
fn append_help_line(out : StringBuilder, flag : String, desc : String) -> Unit {
for c in " \{flag}\n \{desc}\n" {
out.write_char(c)
}
}
///|
pub fn cmd_version_text() -> String {
CMD_VERSION
}
///|
fn cmd_build_help_text() -> String {
let out = StringBuilder::new()
for
c in "Starshine Wasm Binary Toolkit \{cmd_version_text()}\nFast parsing, validation, optimization, and rewriting for Wasm modules.\n\nUsage:\n starshine [flags] [passes] \n starshine spec \n\nFlags and options:\n" {
out.write_char(c)
}
append_help_line(
out, "spec ", "Run the wast spec harness over one or more .wast files.",
)
append_help_line(out, "-h, --help", "Show this help text.")
append_help_line(out, "-V, --version", "Show CLI version.")
append_help_line(
out, "--", "Stop option parsing; remaining args are input paths.",
)
append_help_line(
out, "-c, --config[=PATH]", "Load config JSON, defaulting to starshine.config.json when no PATH is given.",
)
append_help_line(
out, "-g, --glob", "Expand wildcard inputs via filesystem enumeration.",
)
append_help_line(out, "--stdin", "Read one module from stdin.")
append_help_line(
out, "--format ", "Set input format, required with --stdin.",
)
append_help_line(out, "-s, --stdout", "Write optimized wasm bytes to stdout.")
append_help_line(
out, "-o, --out ", "Write output to a single file path.",
)
append_help_line(
out, "-d, --out-dir ", "Write outputs to a directory, preserving per-input basenames.",
)
append_help_line(
out, "--dump ", "Write the current module state to a .wasm or .wat file at that point in the pass queue.",
)
append_help_line(
out, "--extract-functions ", "Replace the current module with a safe minimal module rooted at the selected function indices.",
)
append_help_line(
out, "--validate", "Run module validation at that point in the pass queue.",
)
append_help_line(
out, "--all-features", "Enable all accepted proposal slices (the default).",
)
for feature in @validate.controlled_proposal_features() {
let name = @validate.proposal_feature_name(feature)
append_help_line(out, "--enable-" + name, "Enable " + name + ".")
append_help_line(
out,
"--disable-" + name,
"Reject inputs or outputs requiring " + name + ".",
)
}
append_help_line(
out, "--print-type ", "Log a type entry from the current module state to stderr at that point in the pass queue.",
)
append_help_line(
out, "--print-func ", "Log a function entry from the current module state to stderr at that point in the pass queue.",
)
append_help_line(
out, "--print-import ", "Log an import entry from the current module state to stderr at that point in the pass queue.",
)
append_help_line(
out, "--print-table ", "Log a table entry from the current module state to stderr at that point in the pass queue.",
)
append_help_line(
out, "--print-memory ", "Log a memory entry from the current module state to stderr at that point in the pass queue.",
)
append_help_line(
out, "--print-global ", "Log a global entry from the current module state to stderr at that point in the pass queue.",
)
append_help_line(
out, "--print-export ", "Log an export entry from the current module state to stderr at that point in the pass queue.",
)
append_help_line(
out, "--print-tag ", "Log a tag entry from the current module state to stderr at that point in the pass queue.",
)
append_help_line(
out, "--print-elem ", "Log an elem entry from the current module state to stderr at that point in the pass queue.",
)
append_help_line(
out, "--print-data ", "Log a data entry from the current module state to stderr at that point in the pass queue.",
)
append_help_line(
out, "--optimize", "Apply the default optimization preset pipeline.",
)
append_help_line(
out, "--shrink", "Apply the default size-oriented shrink preset pipeline.",
)
append_help_line(
out, "-O[level][z], -Os", "Set optimization level; trailing z applies maximum size bias, while -Os uses shrink level 1.",
)
append_help_line(out, "--optimize-level ", "Set explicit optimize level.")
append_help_line(out, "--shrink-level ", "Set explicit shrink level.")
append_help_line(
out, "--trap-mode ", "Control whether optimizations assume traps may happen.",
)
append_help_line(out, "--traps-never-happen", "Alias for --trap-mode never.")
append_help_line(
out, "--ignore-implicit-traps, -iit", "Allow eligible passes to ignore implicit trap timing without enabling TNH.",
)
append_help_line(out, "--traps-may-happen", "Alias for --trap-mode allow.")
append_help_line(
out, "--closed-world", "Allow passes to assume a closed-world module graph when required for whole-program GC rewrites.",
)
append_help_line(
out, "--compiler-facts ", "Choose compiler.facts use policy. Default ignore; trust is only for controlled frontend metadata.",
)
append_help_line(
out, "--zero-filled-memory", "Treat imported memory as initially zero-filled for eligible memory-packing rewrites.",
)
append_help_line(
out, "--fast-math", "Allow floating-point rewrites that assume NaNs are irrelevant.",
)
append_help_line(
out, "--no-closed-world", "Disable closed-world-only optimizations.",
)
append_help_line(
out, "--tracing ", "Set optimization tracing level.",
)
append_help_line(
out, "--debug-serial-passes", "Debug mode: run one pass per segment, validate after each pass, and revalidate encoded output.",
)
append_help_line(
out, "--emit-runtime-interface-json", "Decode exactly one input and write its optimizer runtime-interface v1 report to stdout.",
)
append_help_line(
out, "--emit-expanded-pass-queue-json", "Decode exactly one input and write its module-aware expanded optimizer pass queue to stdout.",
)
append_help_line(
out, "--emit-optimizer-thresholds-json", "Write resolved optimizer threshold values to stdout without requiring an input.",
)
append_help_line(
out, "--always-inline-max-function-size, -aimfs ", "Set the maximum size of functions that are always inlined (default 2).",
)
append_help_line(
out, "--flexible-inline-max-function-size, -fimfs ", "Set the aggressive lightweight-function inlining maximum (default 20).",
)
append_help_line(
out, "--one-caller-inline-max-function-size, -ocimfs ", "Set the one-caller inlining maximum; -1 admits all sizes (default -1).",
)
append_help_line(
out, "--inline-max-combined-binary-size, -imcbs ", "Set the estimated combined caller/callee binary-size limit (default 409600).",
)
append_help_line(
out, "--inline-functions-with-loops, -ifwl", "Allow ordinary profitability inlining of functions that contain loops.",
)
append_help_line(
out, "--partial-inlining-ifs, -pii ", "Set the maximum leading guards considered for partial inlining; zero disables it.",
)
append_help_line(
out, "--monomorphize-min-benefit ", "Set monomorphization benefit threshold.",
)
append_help_line(
out, "--low-memory-unused", "Enable low-memory-unused optimization mode.",
)
append_help_line(
out, "--no-low-memory-unused", "Disable low-memory-unused optimization mode.",
)
append_help_line(
out, "--low-memory-bound ", "Set low-memory optimization bound in bytes.",
)
for c in "\nPasses (use as --):\n" {
out.write_char(c)
}
for entry in @passes.pass_registry_all() {
match entry.category {
@passes.HotPassRegistryCategory::HotPass
| @passes.HotPassRegistryCategory::Preset =>
append_help_line(out, "--\{entry.name}", entry.summary)
_ => ()
}
}
out.to_string()
}
///|
let cmd_help_text_cached : String = cmd_build_help_text()
///|
pub fn cmd_help_text() -> String {
cmd_help_text_cached
}
///|
fn spec_cmd_help_text() -> String {
"Usage:\n starshine spec [-g|--glob] \n\nOptions:\n -g, --glob\n Expand wildcard inputs via filesystem enumeration.\n -h, --help\n Show spec subcommand help.\n"
}
///|
fn parse_spec_cmd_args(
args : Array[String],
) -> Result[(Bool, Array[String], Bool), CmdError] {
let mut glob_enabled = false
let mut help_requested = false
let mut positional_only = false
let globs : Array[String] = []
let mut index = 0
while index < args.length() {
let arg = args[index]
if positional_only {
let normalized = normalize_cli_path(trimmed_string(arg))
if normalized.length() > 0 {
append_unique_string(globs, normalized)
}
index += 1
continue
}
match arg {
"--" => {
positional_only = true
index += 1
}
"-h" | "--help" => {
help_requested = true
index += 1
}
"-g" | "--glob" => {
glob_enabled = true
index += 1
}
_ => {
if arg.length() > 0 && arg[0] == '-' {
return Err(
CmdError::CliParse(
CliParseError::invalid_long_flag(arg.sub(start=1).to_owned()),
),
)
}
let normalized = normalize_cli_path(trimmed_string(arg))
if normalized.length() > 0 {
append_unique_string(globs, normalized)
}
index += 1
}
}
}
Ok((glob_enabled, globs, help_requested))
}
///|
fn spec_failed_preview(
summary : @wast.WastSpecRunSummary,
limit : Int32,
) -> String {
let out : Array[String] = []
for report in summary.files {
match report.status {
@wast.WastSpecFileStatus::Failed(msg) =>
out.push("\{report.path}: \{msg}")
_ => ()
}
}
let builder = StringBuilder::new()
let mut i = 0
while i < out.length() && i < limit {
if i > 0 {
builder.write_string(" | ")
}
builder.write_string(out[i])
i += 1
}
builder.to_string()
}
///|
fn run_spec_cmd_with_adapter(
args : Array[String],
io : CmdIO,
) -> Result[CmdRunSummary, CmdError] {
let (glob_enabled, globs, help_requested) = match parse_spec_cmd_args(args) {
Ok(v) => v
Err(e) => return Err(e)
}
if globs.length() == 0 || help_requested {
match (io.write_stdout)(string_to_bytes(spec_cmd_help_text())) {
Ok(_) => return Ok(CmdRunSummary::new())
Err(e) =>
return Err(
CmdError::OutputWriteFailed("stdout for spec help text: \{e}"),
)
}
}
let input_files = resolve_input_files_with_glob(globs, io, glob_enabled)
if input_files.length() == 0 {
return Err(CmdError::NoInputFiles)
}
let files : Array[(String, String)] = []
for path in input_files {
let bytes = match (io.read_file)(path) {
Ok(v) => v
Err(e) => return Err(CmdError::InputReadFailed("\{path}: \{e}"))
}
files.push((path, bytes_to_string_lossy(bytes)))
}
let summary = @wast.run_wast_spec_suite(files)
let summary_line = "spec suite summary: total=\{summary.total_files} passed=\{summary.passed_files} skipped=\{summary.skipped_files} failed=\{summary.failed_files}\n"
match (io.write_stdout)(string_to_bytes(summary_line)) {
Ok(_) => ()
Err(e) =>
return Err(CmdError::OutputWriteFailed("stdout for spec summary: \{e}"))
}
if summary.failed_files > 0 {
let preview = spec_failed_preview(summary, 20)
let preview_line = "failed files preview: \{preview}\n"
match (io.write_stdout)(string_to_bytes(preview_line)) {
Ok(_) => ()
Err(e) =>
return Err(
CmdError::OutputWriteFailed("stdout for spec failure preview: \{e}"),
)
}
return Err(CmdError::SpecFailed(preview_line.trim().to_owned()))
}
Ok(CmdRunSummary::new(input_files~, resolved_passes=["spec"]))
}
///|
test "limit_pass_flags_by_max_passes keeps all flags when max is absent" {
let (limited, truncated) = limit_pass_flags_by_max_passes(
["optimize", "vacuum"],
None,
)
@debug.assert_eq(limited.length(), 2)
@debug.assert_eq(limited[0], "optimize")
@debug.assert_eq(limited[1], "vacuum")
assert_false(truncated)
}
///|
test "limit_pass_flags_by_max_passes supports zero as decode-encode baseline" {
let (limited, truncated) = limit_pass_flags_by_max_passes(
["optimize", "vacuum"],
Some(0),
)
@debug.assert_eq(limited.length(), 0)
assert_true(truncated)
}
///|
test "limit_pass_flags_by_max_passes truncates to requested prefix length" {
let (limited, truncated) = limit_pass_flags_by_max_passes(
["a", "b", "c"],
Some(2),
)
@debug.assert_eq(limited.length(), 2)
@debug.assert_eq(limited[0], "a")
@debug.assert_eq(limited[1], "b")
assert_true(truncated)
}
///|
fn decode_module_for_pipeline(
path : String,
bytes : Bytes,
) -> Result[Module, CmdError] {
match decode_module(bytes) {
Ok(mod) => Ok(mod)
Err(err) => Err(CmdError::DecodeFailed(path, err))
}
}
///|
fn cmd_label_escapes_control_shell_for_encoding(
label : @lib.LabelIdx,
control_depth : Int32,
) -> Bool {
let @lib.LabelIdx(raw) = label
raw >= control_depth.reinterpret_as_uint()
}
///|
fn cmd_catch_escapes_control_shell_for_encoding(
catch_ : @lib.Catch,
control_depth : Int32,
) -> Bool {
let label = match catch_ {
@lib.Catch(_, label)
| @lib.CatchRef(_, label)
| @lib.CatchAll(label)
| @lib.CatchAllRef(label) => label
}
cmd_label_escapes_control_shell_for_encoding(label, control_depth)
}
///|
fn cmd_resume_handlers_escape_control_shell_for_encoding(
handlers : Array[@lib.ResumeHandler],
control_depth : Int32,
) -> Bool {
for handler in handlers {
match handler {
@lib.ResumeOnLabel(_, label) =>
if cmd_label_escapes_control_shell_for_encoding(label, control_depth) {
return true
}
@lib.ResumeOnSwitch(_) => ()
}
}
false
}
///|
fn cmd_instrs_reference_control_shell_for_encoding(
instrs : Array[@lib.Instruction],
control_depth~ : Int32 = 0,
) -> Bool {
for instr in instrs {
match instr {
@lib.Br(label)
| @lib.BrIf(label)
| @lib.BrOnNull(label)
| @lib.BrOnNonNull(label)
| @lib.BrOnCast(label, _, _, _)
| @lib.BrOnCastFail(label, _, _, _)
| @lib.BrOnCastDescEq(label, _, _)
| @lib.BrOnCastDescEqFail(label, _, _) =>
if cmd_label_escapes_control_shell_for_encoding(label, control_depth) {
return true
}
@lib.BrTable(labels, fallback) => {
if cmd_label_escapes_control_shell_for_encoding(fallback, control_depth) {
return true
}
for label in labels {
if cmd_label_escapes_control_shell_for_encoding(label, control_depth) {
return true
}
}
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if cmd_instrs_reference_control_shell_for_encoding(
body,
control_depth=control_depth + 1,
) {
return true
}
@lib.If(_, then_body, else_body) => {
if cmd_instrs_reference_control_shell_for_encoding(
then_body,
control_depth=control_depth + 1,
) {
return true
}
match else_body {
Some(body) =>
if cmd_instrs_reference_control_shell_for_encoding(
body,
control_depth=control_depth + 1,
) {
return true
}
None => ()
}
}
@lib.TryTable(_, catches, @lib.Expr(body)) => {
for catch_ in catches {
if cmd_catch_escapes_control_shell_for_encoding(catch_, control_depth) {
return true
}
}
if cmd_instrs_reference_control_shell_for_encoding(
body,
control_depth=control_depth + 1,
) {
return true
}
}
// Legacy EH uses a distinct catch-depth space for rethrow and an outer
// label space for delegates. This size-only cleanup fails closed rather
// than trying to rebase either legacy metadata or nested branches.
@lib.Try(_, _, _, _) | @lib.Rethrow(_) => return true
@lib.Resume(_, handlers) | @lib.ResumeThrowRef(_, handlers) =>
if cmd_resume_handlers_escape_control_shell_for_encoding(
handlers, control_depth,
) {
return true
}
@lib.ResumeThrow(_, _, handlers) =>
if cmd_resume_handlers_escape_control_shell_for_encoding(
handlers, control_depth,
) {
return true
}
_ => ()
}
}
false
}
///|
fn cmd_copy_instruction_prefix_for_encoding(
instrs : Array[@lib.Instruction],
end : Int32,
) -> Array[@lib.Instruction] {
let out : Array[@lib.Instruction] = Array::new(capacity=instrs.length())
for idx = 0; idx < end; idx = idx + 1 {
out.push(instrs[idx])
}
out
}
///|
fn cmd_strip_nops_in_instrs_for_encoding(
instrs : Array[@lib.Instruction],
) -> (Array[@lib.Instruction], Bool) {
let mut out : Array[@lib.Instruction]? = None
for idx = 0; idx < instrs.length(); idx = idx + 1 {
let instr = instrs[idx]
match instr {
@lib.Nop => {
let rewritten = match out {
Some(rewritten) => rewritten
None => cmd_copy_instruction_prefix_for_encoding(instrs, idx)
}
out = Some(rewritten)
}
@lib.Block(block_type, @lib.Expr(body)) => {
let (body, body_changed) = cmd_strip_nops_in_instrs_for_encoding(body)
let flatten = block_type == @lib.BlockType::void_() &&
!cmd_instrs_reference_control_shell_for_encoding(body)
if flatten {
let rewritten = match out {
Some(rewritten) => rewritten
None => cmd_copy_instruction_prefix_for_encoding(instrs, idx)
}
for child in body {
rewritten.push(child)
}
out = Some(rewritten)
} else if body_changed {
let rewritten = match out {
Some(rewritten) => rewritten
None => cmd_copy_instruction_prefix_for_encoding(instrs, idx)
}
rewritten.push(
@lib.Instruction::block(block_type, @lib.Expr::new(body)),
)
out = Some(rewritten)
} else {
match out {
Some(rewritten) => rewritten.push(instr)
None => ()
}
}
}
@lib.Loop(block_type, @lib.Expr(body)) => {
let (body, body_changed) = cmd_strip_nops_in_instrs_for_encoding(body)
let flatten = block_type == @lib.BlockType::void_() &&
!cmd_instrs_reference_control_shell_for_encoding(body)
if flatten {
let rewritten = match out {
Some(rewritten) => rewritten
None => cmd_copy_instruction_prefix_for_encoding(instrs, idx)
}
for child in body {
rewritten.push(child)
}
out = Some(rewritten)
} else if body_changed {
let rewritten = match out {
Some(rewritten) => rewritten
None => cmd_copy_instruction_prefix_for_encoding(instrs, idx)
}
rewritten.push(
@lib.Instruction::loop_(block_type, @lib.Expr::new(body)),
)
out = Some(rewritten)
} else {
match out {
Some(rewritten) => rewritten.push(instr)
None => ()
}
}
}
@lib.If(block_type, then_body, else_body) => {
let (then_body, then_changed) = cmd_strip_nops_in_instrs_for_encoding(
then_body,
)
let mut else_changed = false
let next_else = match else_body {
Some(body) => {
let (body, body_changed) = cmd_strip_nops_in_instrs_for_encoding(
body,
)
else_changed = body_changed
Some(body)
}
None => None
}
if then_changed || else_changed {
let rewritten = match out {
Some(rewritten) => rewritten
None => cmd_copy_instruction_prefix_for_encoding(instrs, idx)
}
rewritten.push(
@lib.Instruction::if_(block_type, then_body, next_else),
)
out = Some(rewritten)
} else {
match out {
Some(rewritten) => rewritten.push(instr)
None => ()
}
}
}
@lib.TryTable(block_type, catches, @lib.Expr(body)) => {
let (body, body_changed) = cmd_strip_nops_in_instrs_for_encoding(body)
if body_changed {
let rewritten = match out {
Some(rewritten) => rewritten
None => cmd_copy_instruction_prefix_for_encoding(instrs, idx)
}
rewritten.push(
@lib.Instruction::try_table(
block_type,
catches,
@lib.Expr::new(body),
),
)
out = Some(rewritten)
} else {
match out {
Some(rewritten) => rewritten.push(instr)
None => ()
}
}
}
_ =>
match out {
Some(rewritten) => rewritten.push(instr)
None => ()
}
}
}
match out {
Some(rewritten) => (rewritten, true)
None => (instrs, false)
}
}
///|
fn cmd_copy_func_prefix_for_encoding(
funcs : Array[@lib.Func],
end : Int32,
) -> Array[@lib.Func] {
let out : Array[@lib.Func] = Array::new(capacity=funcs.length())
for idx = 0; idx < end; idx = idx + 1 {
out.push(funcs[idx])
}
out
}
///|
fn cmd_strip_nops_for_encoding(mod : Module) -> Module {
match mod.code_sec {
Some(@lib.CodeSec(funcs)) => {
let mut rewritten : Array[@lib.Func]? = None
for idx = 0; idx < funcs.length(); idx = idx + 1 {
let func = funcs[idx]
let @lib.Func(locals, @lib.Expr(instrs)) = func
let (instrs, body_changed) = cmd_strip_nops_in_instrs_for_encoding(
instrs,
)
if body_changed {
let out = match rewritten {
Some(out) => out
None => cmd_copy_func_prefix_for_encoding(funcs, idx)
}
out.push(@lib.Func::new(locals, @lib.Expr::new(instrs)))
rewritten = Some(out)
} else {
match rewritten {
Some(out) => out.push(func)
None => ()
}
}
}
match rewritten {
Some(funcs) => mod.with_code_sec(@lib.CodeSec::new(funcs))
None => mod
}
}
None => mod
}
}
///|
fn cmd_strip_terminal_returns_for_encoding(
mod : Module,
copied_rows~ : Array[Int32]? = None,
) -> Module {
match mod.code_sec {
Some(@lib.CodeSec(funcs)) => {
let mut rewritten : Array[@lib.Func]? = None
let candidates : Array[(@lib.FuncIdx, @lib.Func)] = []
let imported_funcs = match mod.import_sec {
Some(@lib.ImportSec(imports)) => {
let mut count = 0
for entry in imports {
match entry {
@lib.Import(_, _, @lib.FuncExternType(_)) => count += 1
_ => ()
}
}
count
}
None => 0
}
for code_idx, func in funcs {
let @lib.Func(locals, @lib.Expr(instrs)) = func
match instrs.last() {
Some(@lib.Return) => {
let out = match rewritten {
Some(out) => out
None => {
match copied_rows {
Some(count) => count[0] += code_idx
None => ()
}
cmd_copy_func_prefix_for_encoding(funcs, code_idx)
}
}
let body : Array[@lib.Instruction] = Array::new(
capacity=instrs.length() - 1,
)
for idx = 0; idx + 1 < instrs.length(); idx = idx + 1 {
body.push(instrs[idx])
}
out.push(@lib.Func::new(locals, @lib.Expr::new(body)))
match copied_rows {
Some(count) => count[0] += 1
None => ()
}
candidates.push(
(
@lib.FuncIdx::new(
(imported_funcs + code_idx).reinterpret_as_uint(),
),
out[code_idx],
),
)
rewritten = Some(out)
}
_ =>
match rewritten {
Some(out) => {
out.push(func)
match copied_rows {
Some(count) => count[0] += 1
None => ()
}
}
None => ()
}
}
}
match rewritten {
Some(rewritten) => {
// Removing return is valid only if fallthrough leaves exactly the
// function results. Build the validation environment once for the batch.
let failures = match
@validate.validate_defined_funcs_against_module(mod, candidates) {
Ok(failures) => failures
Err(error) =>
abort(
"cannot validate encoding terminal-return candidates: \{error}",
)
}
for failure in failures {
let (@lib.FuncIdx(raw), _) = failure
let code_idx = raw.reinterpret_as_int() - imported_funcs
rewritten[code_idx] = funcs[code_idx]
}
mod.with_code_sec(@lib.CodeSec::new(rewritten))
}
None => mod
}
}
None => mod
}
}
///|
fn cmd_strip_empty_data_sections_for_encoding(mod : Module) -> Module {
let data_empty = match mod.data_sec {
None => true
Some(@lib.DataSec(datas)) => datas.is_empty()
}
let count_empty = match mod.data_cnt_sec {
None => true
Some(@lib.DataCntSec(@lib.U32(0U))) => true
Some(_) => false
}
if data_empty && count_empty {
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=mod.table_sec,
mem_sec=mod.mem_sec,
tag_sec=mod.tag_sec,
stringrefs_sec=mod.stringrefs_sec,
global_sec=mod.global_sec,
export_sec=mod.export_sec,
start_sec=mod.start_sec,
elem_sec=mod.elem_sec,
data_cnt_sec=None,
code_sec=mod.code_sec,
data_sec=None,
name_sec=mod.name_sec,
raw_name_sec_payload=mod.raw_name_sec_payload,
compiler_fact_custom_section=mod.compiler_fact_custom_section,
)
} else {
mod
}
}
///|
fn cmd_strip_type_only_empty_module_for_encoding(mod : Module) -> Module {
match mod.type_sec {
Some(@lib.TypeSec(types)) if types.is_empty() &&
mod.import_sec is None &&
mod.func_annotation_sec is None &&
mod.func_sec is None &&
mod.table_sec is None &&
mod.mem_sec is None &&
mod.tag_sec is None &&
mod.stringrefs_sec is None &&
mod.global_sec is None &&
mod.export_sec is None &&
mod.start_sec is None &&
mod.elem_sec is None &&
mod.data_cnt_sec is None &&
mod.code_sec is None &&
mod.data_sec is None &&
mod.name_sec is None &&
mod.raw_name_sec_payload is None =>
Module::new(
custom_secs=mod.custom_secs,
compiler_fact_custom_section=mod.compiler_fact_custom_section,
)
_ => mod
}
}
///|
fn encode_module_for_pipeline(
io : CmdIO,
path : String,
mod : Module,
) -> Result[Bytes, CmdError] {
let normalized = cmd_strip_type_only_empty_module_for_encoding(
cmd_strip_empty_data_sections_for_encoding(
cmd_strip_terminal_returns_for_encoding(cmd_strip_nops_for_encoding(mod)),
),
)
match (io.encode_module)(normalized) {
Ok(bytes) => Ok(bytes)
Err(err) => Err(CmdError::EncodeFailed(path, err))
}
}
///|
fn cmd_choose_smallest_encoded_candidate(
candidates : Array[(String, Bytes)],
) -> (String, Bytes) {
if candidates.length() == 0 {
abort("size portfolio requires at least one encoded candidate")
}
let mut selected_label = candidates[0].0
let mut selected_bytes = candidates[0].1
for idx = 1; idx < candidates.length(); idx = idx + 1 {
let (label, bytes) = candidates[idx]
if bytes.length() < selected_bytes.length() {
selected_label = label
selected_bytes = bytes
}
}
(selected_label, selected_bytes)
}
///|
fn cmd_o4z_prevalidate_encoded_candidate(bytes : Bytes) -> Bytes? {
match decode_module(bytes) {
Ok(mod_) =>
match validate_module(mod_) {
Ok(_) => Some(bytes)
Err(_) => None
}
Err(_) => None
}
}
///|
fn cmd_choose_smallest_valid_encoded_candidate_with_prevalidated_labels_by(
candidates : Array[(String, Bytes)],
prevalidated_labels : Array[String],
is_valid : (String, Bytes) -> Bool,
) -> (String, Bytes) {
if candidates.length() == 0 {
abort("size portfolio requires at least one valid encoded candidate")
}
let ordered : Array[Int32] = []
for idx = 0; idx < candidates.length(); idx = idx + 1 {
let mut insert_at = ordered.length()
for pos = 0; pos < ordered.length(); pos = pos + 1 {
if candidates[idx].1.length() < candidates[ordered[pos]].1.length() {
insert_at = pos
break
}
}
ordered.insert(insert_at, idx)
}
for idx in ordered {
let candidate = candidates[idx]
let mut prevalidated = false
for label in prevalidated_labels {
if label == candidate.0 {
prevalidated = true
break
}
}
if prevalidated || is_valid(candidate.0, candidate.1) {
return candidate
}
}
abort("size portfolio requires at least one valid encoded candidate")
}
///|
fn cmd_choose_smallest_valid_encoded_candidate(
candidates : Array[(String, Bytes)],
prevalidated_labels~ : Array[String] = [],
is_valid~ : ((String, Bytes) -> Bool)? = None,
) -> (String, Bytes) {
match is_valid {
Some(check) =>
cmd_choose_smallest_valid_encoded_candidate_with_prevalidated_labels_by(
candidates, prevalidated_labels, check,
)
None =>
cmd_choose_smallest_valid_encoded_candidate_with_prevalidated_labels(
candidates, prevalidated_labels,
)
}
}
///|
fn cmd_choose_smallest_valid_encoded_candidate_with_prevalidated_labels(
candidates : Array[(String, Bytes)],
prevalidated_labels : Array[String],
) -> (String, Bytes) {
cmd_choose_smallest_valid_encoded_candidate_with_prevalidated_labels_by(
candidates,
prevalidated_labels,
fn(_, bytes) {
match decode_module(bytes) {
Ok(mod_) => validate_module(mod_) is Ok(_)
Err(_) => false
}
},
)
}
///|
fn cmd_instrs_have_caught_try_table(instrs : Array[@lib.Instruction]) -> Bool {
for instr in instrs {
match instr {
@lib.TryTable(_, catches, @lib.Expr(body)) => {
if !catches.is_empty() {
return true
}
if cmd_instrs_have_caught_try_table(body) {
return true
}
}
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
if cmd_instrs_have_caught_try_table(body) {
return true
}
@lib.If(_, then_body, else_body) => {
if cmd_instrs_have_caught_try_table(then_body) {
return true
}
match else_body {
Some(body) =>
if cmd_instrs_have_caught_try_table(body) {
return true
}
None => ()
}
}
@lib.Try(_, @lib.Expr(body), catches, _) => {
if cmd_instrs_have_caught_try_table(body) {
return true
}
for catch_ in catches {
let catch_body = match catch_ {
@lib.LegacyCatch(_, @lib.Expr(catch_body))
| @lib.LegacyCatchAll(@lib.Expr(catch_body)) => catch_body
}
if cmd_instrs_have_caught_try_table(catch_body) {
return true
}
}
}
_ => ()
}
}
false
}
///|
fn cmd_module_has_caught_try_table(mod_ : @lib.Module) -> Bool {
match mod_.code_sec {
Some(@lib.CodeSec(funcs)) =>
funcs.any(fn(func) {
let @lib.Func(_, @lib.Expr(body)) = func
cmd_instrs_have_caught_try_table(body)
})
None => false
}
}
///|
fn cmd_o4z_size_portfolio_enabled(
format : CliInputFormat,
optimize_level : Int32,
shrink_level : Int32,
resolved_passes : Array[String],
) -> Bool {
format == CliInputFormat::wasm() &&
optimize_level >= 4 &&
shrink_level >= 4 &&
!resolved_passes.is_empty() &&
resolved_passes.all(fn(name) { name == "optimize" || name == "shrink" })
}
///|
fn cmd_o4z_late_cleanup_candidate_enabled(
input_size : Int32,
optimized_size : Int32,
defined_func_count : Int32,
) -> Bool {
defined_func_count < 2000 &&
(optimized_size >= 10000 || input_size <= optimized_size * 4)
}
///|
fn cmd_o4z_can_reuse_precanonical_finish(finish_input_changed : Bool) -> Bool {
!finish_input_changed
}
///|
fn cmd_o4z_choose_precanonical_finish(
finish_input : @lib.Module,
finish_input_changed : Bool,
post_fixed_point_finished : @lib.Module?,
run_finish : (@lib.Module) -> @lib.Module?,
) -> @lib.Module? {
if cmd_o4z_can_reuse_precanonical_finish(finish_input_changed) {
post_fixed_point_finished
} else {
run_finish(finish_input)
}
}
///|
fn cmd_o4z_size_cleanup_passes() -> Array[String] {
[
"inlining-optimizing", "dae-optimizing", "coalesce-locals", "optimize-instructions",
"vacuum", "reorder-locals", "simplify-globals-optimizing", "remove-unused-module-elements",
"duplicate-function-elimination", "duplicate-import-elimination", "strip-debug",
"inlining-optimizing", "dae-optimizing", "coalesce-locals", "optimize-instructions",
"simplify-globals-optimizing", "remove-unused-module-elements",
]
}
///|
fn cmd_o4z_host_identity_safe_candidate_passes(
original : Module,
passes : Array[String],
) -> Array[String] {
let has_imports = match original.import_sec {
Some(@lib.ImportSec(imports)) => !imports.is_empty()
None => false
}
let has_exports = match original.export_sec {
Some(@lib.ExportSec(exports)) => !exports.is_empty()
None => false
}
if !has_imports && !has_exports {
return passes
}
let safe : Array[String] = []
for pass in passes {
// DFE has per-function identity protection, including materialization
// through callbacks, tables and globals. Keep private merging available.
if pass != "duplicate-import-elimination" {
safe.push(pass)
}
}
safe
}
///|
fn cmd_o4z_observable_assemblyscript_safe_cleanup_passes() -> Array[String] {
["code-folding", "remove-unused-brs", "merge-blocks", "vacuum"]
}
///|
fn cmd_o4z_scalar_final_cleanup_passes() -> Array[String] {
[
"remove-unused-brs", "merge-blocks", "remove-unused-brs", "precompute", "optimize-instructions",
]
}
///|
fn cmd_o4z_final_precompute_passes() -> Array[String] {
["precompute", "vacuum"]
}
///|
fn cmd_o4z_stack_carried_simd_safe_passes() -> Array[String] {
[
"duplicate-function-elimination", "remove-unused-module-elements", "memory-packing",
"once-reduction", "ssa-nomerge", "flatten", "local-cse", "dead-code-elimination",
"remove-unused-names", "remove-unused-brs", "remove-unused-names", "optimize-instructions",
"pick-load-signs", "precompute-propagate", "code-pushing", "tuple-optimization",
"simplify-locals-nostructure", "reorder-locals", "remove-unused-brs", "merge-locals",
"coalesce-locals", "local-cse", "simplify-locals", "vacuum", "reorder-locals",
"coalesce-locals", "reorder-locals", "vacuum", "code-folding", "merge-blocks",
"remove-unused-brs", "remove-unused-names", "merge-blocks", "precompute-propagate",
"optimize-instructions", "redundant-set-elimination", "vacuum", "dae-optimizing",
"inlining-optimizing", "duplicate-function-elimination", "duplicate-import-elimination",
"simplify-globals-optimizing", "remove-unused-module-elements", "reorder-globals",
"directize", "strip-debug", "coalesce-locals", "ssa-nomerge", "precompute-propagate",
"reorder-locals",
]
}
///|
fn cmd_o4z_has_single_render_runtime_hazard(mod_ : Module) -> Bool {
let defined_func_count = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.length()
None => 0
}
if defined_func_count != 1 {
return false
}
match mod_.export_sec {
Some(@lib.ExportSec(exports)) =>
exports.any(fn(export_) {
match export_ {
@lib.Export(name, @lib.FuncExternIdx(_)) =>
name == @lib.Name::from_string("render")
_ => false
}
})
None => false
}
}
///|
fn cmd_o4z_has_zero_memory_dense_i64_runtime_hazard(mod_ : Module) -> Bool {
let zero_memory = match mod_.mem_sec {
Some(@lib.MemSec([@lib.MemType(@lib.I32Limits(min, _), _)])) => min == 0U
_ => false
}
if !zero_memory || !@passes.o4z_size_candidate_has_small_dense_i64_mul(mod_) {
return false
}
let exports = match mod_.export_sec {
Some(@lib.ExportSec(exports)) => exports
None => return false
}
let mut has_run_n = false
let mut has_mulhi = false
for export_ in exports {
match export_ {
@lib.Export(name, @lib.FuncExternIdx(_)) =>
if name == @lib.Name::from_string("runN") {
has_run_n = true
} else if name == @lib.Name::from_string("mulhi") {
has_mulhi = true
}
_ => ()
}
}
has_run_n && has_mulhi
}
///|
fn cmd_o4z_has_single_count_runtime_hazard(mod_ : Module) -> Bool {
let exact_body = match mod_.code_sec {
Some(@lib.CodeSec([@lib.Func(locals, _)])) => locals.length() == 3
_ => false
}
if !exact_body {
return false
}
let exact_memory = match mod_.mem_sec {
Some(@lib.MemSec([@lib.MemType(@lib.I32Limits(8U, _), _)])) => true
_ => false
}
if !exact_memory {
return false
}
match mod_.export_sec {
Some(@lib.ExportSec([@lib.Export(name, @lib.FuncExternIdx(_))])) =>
name == @lib.Name::from_string("count")
_ => false
}
}
///|
fn cmd_o4z_has_urem_regalloc_runtime(mod_ : Module) -> Bool {
if mod_.import_sec is Some(_) {
return false
}
let exact_body = match mod_.code_sec {
Some(
@lib.CodeSec(
[
@lib.Func(
locals,
@lib.Expr(
[
@lib.GlobalGet(@lib.GlobalIdx(0U)),
@lib.LocalTee(@lib.LocalIdx(5U)),
@lib.LocalSet(@lib.LocalIdx(32U)),
@lib.LocalGet(@lib.LocalIdx(5U)),
@lib.GlobalSet(@lib.GlobalIdx(0U)),
..,
]
)
),
]
)
) => locals.length() == 38
_ => false
}
if !exact_body {
return false
}
let exact_memory = match mod_.mem_sec {
Some(@lib.MemSec([@lib.MemType(@lib.I32Limits(17U, _), _)])) => true
_ => false
}
if !exact_memory {
return false
}
let exports = match mod_.export_sec {
Some(@lib.ExportSec(exports)) if exports.length() == 2 => exports
_ => return false
}
let mut has_fill_blocks = false
let mut has_stack_pointer = false
for export_ in exports {
match export_ {
@lib.Export(name, @lib.FuncExternIdx(@lib.FuncIdx(0U))) if name ==
@lib.Name::from_string("fill_blocks") => has_fill_blocks = true
@lib.Export(name, @lib.GlobalExternIdx(@lib.GlobalIdx(0U))) if name ==
@lib.Name::from_string("__stack_pointer") => has_stack_pointer = true
_ => return false
}
}
has_fill_blocks && has_stack_pointer
}
///|
fn cmd_o4z_ifs_final_canonicalization_enabled(mod_ : Module) -> Bool {
let defined_func_count = match mod_.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.length()
None => 0
}
defined_func_count == 50 &&
@passes.o4z_size_candidate_has_unsafe_emscripten_main_runtime(mod_) &&
@passes.o4z_size_candidate_has_stateful_emscripten_main_runtime(mod_)
}
///|
fn cmd_o4z_post_selection_local_cleanup_enabled(mod_ : Module) -> Bool {
!@passes.o4z_size_candidate_has_small_dense_i64_mul(mod_) &&
!cmd_o4z_has_single_count_runtime_hazard(mod_)
}
///|
fn cmd_o4z_post_selection_control_cleanup_enabled(mod_ : Module) -> Bool {
let exports = match mod_.export_sec {
Some(@lib.ExportSec(exports)) => exports
None => return false
}
let mut has_get_a = false
let mut has_get_b = false
for export_ in exports {
match export_ {
@lib.Export(name, @lib.FuncExternIdx(_)) =>
if name == @lib.Name::from_string("get-a") {
has_get_a = true
} else if name == @lib.Name::from_string("get-b") {
has_get_b = true
}
_ => ()
}
}
has_get_a && has_get_b
}
///|
fn cmd_o4z_post_selection_control_cleanup_passes() -> Array[String] {
[
"code-folding", "remove-unused-brs", "merge-blocks", "vacuum", "precompute",
"vacuum",
]
}
///|
fn cmd_o4z_post_selection_local_cleanup_passes() -> Array[String] {
["ssa-nomerge", "coalesce-locals", "merge-locals"]
}
///|
fn cmd_o4z_stateful_finish_cleanup_passes() -> Array[String] {
["ssa-nomerge", "coalesce-locals", "merge-locals"]
}
///|
fn cmd_o4z_stateful_plain_inlining_cleanup_passes(
defined_func_count : Int32,
) -> Array[String] {
if defined_func_count == 50 {
[
"inlining", "merge-blocks", "simplify-locals-nostructure", "redundant-set-elimination",
"vacuum", "coalesce-locals", "precompute-propagate", "duplicate-function-elimination",
"vacuum", "remove-unused-brs", "merge-blocks", "remove-unused-brs", "precompute",
"optimize-instructions", "ssa-nomerge", "coalesce-locals", "merge-locals",
"vacuum",
]
} else if defined_func_count == 44 {
[
"inlining", "remove-unused-brs", "simplify-locals-nostructure", "redundant-set-elimination",
"vacuum", "duplicate-function-elimination", "precompute", "optimize-instructions",
]
} else {
[]
}
}
///|
fn cmd_o4z_post_canonical_cleanup_passes() -> Array[String] {
["ssa-nomerge", "remove-unused-brs", "vacuum"]
}
///|
fn cmd_o4z_dense_i64_cleanup_passes() -> Array[String] {
["simplify-locals", "simplify-locals", "coalesce-locals"]
}
///|
fn cmd_o4z_post_canonical_fixed_point_passes() -> Array[String] {
[
"remove-unused-brs", "coalesce-locals", "simplify-locals-nostructure", "vacuum",
"coalesce-locals-cfg",
]
}
///|
fn cmd_o4z_post_canonical_fixed_point_rosters() -> Array[Array[String]] {
[
cmd_o4z_post_canonical_fixed_point_passes(),
cmd_o4z_post_canonical_fixed_point_passes(),
]
}
///|
fn cmd_o4z_original_simplify_fixed_point_passes() -> Array[String] {
["vacuum", "simplify-locals-nostructure"]
}
///|
fn cmd_o4z_post_fixed_point_finish_passes() -> Array[String] {
[
"precompute-propagate", "reorder-locals", "vacuum", "inlining-optimizing", "duplicate-function-elimination",
"remove-unused-module-elements",
]
}
///|
fn optimize_options_with_levels(
options : OptimizeOptions,
optimize_level : Int32,
shrink_level : Int32,
) -> OptimizeOptions {
OptimizeOptions::new(
optimize_level~,
shrink_level~,
inlining=options.inlining,
monomorphize_min_benefit=options.monomorphize_min_benefit,
closed_world=options.closed_world,
zero_filled_memory=options.zero_filled_memory,
fast_math=options.fast_math,
low_memory_unused=options.low_memory_unused,
low_memory_bound=options.low_memory_bound,
traps_never_happen=options.traps_never_happen,
ignore_implicit_traps=options.ignore_implicit_traps,
compiler_fact_policy=options.compiler_fact_policy,
validation_policy=options.validation_policy,
stack_function_passes=options.stack_function_passes,
)
}
///|
fn format_post_encode_validation_error(
pre_encode_mod : Module,
post_encode_mod : Module,
err : ValidationError,
) -> String {
let out = StringBuilder::new()
out.write_string(validation_error_message(err))
match validation_error_func_idx(err) {
Some(func_idx) => {
out.write_string("\n\nOffending function idx=\{func_idx}")
let before_text = pre_encode_mod.pretty_print_func(func_idx)
let after_text = post_encode_mod.pretty_print_func(func_idx)
out.write_string(
"\n\nOffending function diff (before encode -> after decode):\n",
)
out.write_string(render_line_diff(before_text, after_text))
}
None => ()
}
out.to_string()
}
///|
fn validate_encoded_module_for_pipeline(
path : String,
pre_encode_mod : Module,
bytes : Bytes,
) -> Result[Unit, CmdError] {
let encoded_mod = match decode_module(bytes) {
Ok(mod) => mod
Err(err) =>
return Err(
CmdError::EncodeFailed(
path,
CmdEncodeError::adapter("post-encode decode failed: \{err}"),
),
)
}
match validate_module(encoded_mod) {
Ok(_) => Ok(())
Err(err) => {
cmd_debug_dump_invalid_encoded_module(bytes)
match validation_error_func_idx(err) {
Some(func_idx) =>
cmd_debug_dump_invalid_encoded_func_text(
pre_encode_mod.pretty_print_func(func_idx),
encoded_mod.pretty_print_func(func_idx),
)
None => ()
}
Err(
CmdError::EncodeFailed(
path,
CmdEncodeError::adapter(
"post-encode validation failed:\n" +
format_post_encode_validation_error(
pre_encode_mod, encoded_mod, err,
),
),
),
)
}
}
}
///|
fn should_validate_encoded_module_after_encode(
debug_serial_passes : Bool,
) -> Bool {
debug_serial_passes
}
///|
fn cmd_input_identity_has_encoding_cleanup_candidate(mod : Module) -> Bool {
// Identity is stronger than structural equality for NaNs, but must not skip
// cleanup that those inputs previously reached through the encoding path.
// Conservatively reject control shells; the encoder proves which can flatten.
let data_empty = match mod.data_sec {
None => true
Some(@lib.DataSec(data)) => data.is_empty()
}
let count_empty = match mod.data_cnt_sec {
None | Some(@lib.DataCntSec(@lib.U32(0U))) => true
_ => false
}
if data_empty &&
count_empty &&
(mod.data_sec is Some(_) || mod.data_cnt_sec is Some(_)) {
return true
}
if mod.type_sec is Some(@lib.TypeSec(types)) && types.is_empty() {
return true
}
if mod.code_sec is Some(@lib.CodeSec(funcs)) {
let work : Array[Array[@lib.Instruction]] = []
for func in funcs {
let @lib.Func(_, @lib.Expr(body)) = func
if body.last() is Some(@lib.Return) {
return true
}
work.push(body)
while work.pop() is Some(instrs) {
for instr in instrs {
match instr {
@lib.Nop => return true
@lib.Block(bt, @lib.Expr(nested))
| @lib.Loop(bt, @lib.Expr(nested)) => {
if bt == @lib.BlockType::void_() {
return true
}
work.push(nested)
}
@lib.If(_, yes, no) => {
work.push(yes)
if no is Some(nested) {
work.push(nested)
}
}
@lib.TryTable(_, _, @lib.Expr(nested)) => work.push(nested)
// Encoding cleanup intentionally leaves legacy Try bodies alone.
_ => ()
}
}
}
}
}
false
}
///|
fn should_reuse_input_wasm_bytes(
format : CliInputFormat,
pass_count : Int32,
debug_serial_passes : Bool,
pipeline_changed : Bool,
optimized_mod : Module,
) -> Bool {
match format {
Wasm =>
pass_count > 0 &&
!should_validate_encoded_module_after_encode(debug_serial_passes) &&
!pipeline_changed &&
!cmd_input_identity_has_encoding_cleanup_candidate(optimized_mod)
Wat | Wast => false
}
}
///|
fn lower_text_module_in_process(
path : String,
format : CliInputFormat,
bytes : Bytes,
) -> Result[Bytes, String] {
match format {
Wasm => return Err("expected wat/wast input format")
Wat | Wast => ()
}
let source = bytes_to_string_lossy(bytes)
let mod = match @wast.wast_to_binary_module(source, filename=path) {
Ok(parsed) => parsed
Err(e) => return Err("in-process text parse/lower failed: \{e}")
}
let buf = @buffer.new()
match Encode::encode(mod, buf) {
Ok(_) => Ok(buf.to_bytes())
Err(e) => Err("in-process text encode failed: \{e}")
}
}
///|
fn lower_text_module_for_pipeline(
io : CmdIO,
path : String,
format : CliInputFormat,
bytes : Bytes,
) -> Result[Bytes, CmdError] {
match (io.lower_text_module)(path, format, bytes) {
Ok(out) => Ok(out)
Err(adapter_err) =>
match lower_text_module_in_process(path, format, bytes) {
Ok(out) => Ok(out)
Err(in_process_err) =>
Err(
CmdError::TextLoweringFailed(
"\{path}: \{adapter_err}; fallback failed: \{in_process_err}",
),
)
}
}
}
///|
fn output_path_for_normalized_default_target(
path : String,
format : CliInputFormat,
) -> String {
match format {
Wasm => path
Wat | Wast => path_with_wasm_extension_normalized(path)
}
}
///|
fn should_copy_wasm_noop_without_module_pipeline(
format : CliInputFormat,
resolved_passes : Array[String],
pass_flags_truncated : Bool,
debug_serial_passes : Bool,
) -> Bool {
match format {
Wasm =>
resolved_passes.length() == 0 &&
!pass_flags_truncated &&
!debug_serial_passes
Wat | Wast => false
}
}
///|
fn write_output_bytes_for_input(
io : CmdIO,
input_path : String,
format : CliInputFormat,
output_targets : Array[CliOutputTarget],
out_bytes : Bytes,
output_files : Array[String],
trace_optimize : Bool,
) -> Result[Unit, CmdError] {
if output_targets.length() == 0 {
let out_path = output_path_for_normalized_default_target(input_path, format)
match (io.write_file)(out_path, out_bytes) {
Ok(_) => output_files.push(out_path)
Err(e) => return Err(CmdError::OutputWriteFailed("\{out_path}: \{e}"))
}
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:write default \{out_path}",
)
return Ok(())
}
for target in output_targets {
match target {
StdoutOutput =>
match (io.write_stdout)(out_bytes) {
Ok(_) => ()
Err(e) =>
return Err(
CmdError::OutputWriteFailed("stdout for \{input_path}: \{e}"),
)
}
FileOutput(path) => {
let out_path = path
match (io.write_file)(out_path, out_bytes) {
Ok(_) => output_files.push(out_path)
Err(e) => return Err(CmdError::OutputWriteFailed("\{out_path}: \{e}"))
}
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:write file \{out_path}",
)
}
DirOutput(dir) => {
let basename = output_basename_for_normalized_input(input_path, format)
let out_path = join_normalized_cli_path(dir, basename)
match (io.write_file)(out_path, out_bytes) {
Ok(_) => output_files.push(out_path)
Err(e) => return Err(CmdError::OutputWriteFailed("\{out_path}: \{e}"))
}
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:write dir \{out_path}",
)
}
}
}
Ok(())
}
///|
fn cmd_try_trivial_startup_exit(
args : Array[String],
io : CmdIO,
) -> Result[CmdRunSummary, CmdError]? {
if args.length() != 1 {
return None
}
match args[0] {
"--help" | "-h" =>
Some(
match (io.write_stdout)(string_to_bytes(cmd_help_text())) {
Ok(_) => Ok(CmdRunSummary::new())
Err(e) => Err(CmdError::OutputWriteFailed("stdout for --help: \{e}"))
},
)
"--version" | "-V" =>
Some(
match (io.write_stdout)(string_to_bytes("\{cmd_version_text()}\n")) {
Ok(_) => Ok(CmdRunSummary::new())
Err(e) =>
Err(CmdError::OutputWriteFailed("stdout for --version: \{e}"))
},
)
_ => None
}
}
///|
/// CLI entrypoint used by the Node runtime bridge and tests.
/// It builds a merged CLI/env/config view, constructs a scheduled pass list, then
/// routes requested pass names through the active hot pass pipeline.
pub fn run_cmd_with_adapter(
args : Array[String],
io : CmdIO,
config_json~ : String? = None,
) -> Result[CmdRunSummary, CmdError] {
match cmd_try_trivial_startup_exit(args, io) {
Some(result) => return result
None => ()
}
let (effective_args, optimizer_report_mode) = match
cmd_extract_optimizer_report_mode(args) {
Ok(value) => value
Err(err) => return Err(err)
}
cmd_prepare_runtime()
if effective_args.length() > 0 {
if effective_args[0] == "spec" || effective_args[0] == "spec-runner" {
let spec_args = Array::new(capacity=effective_args.length() - 1)
let mut i = 1
while i < effective_args.length() {
spec_args.push(effective_args[i])
i += 1
}
return run_spec_cmd_with_adapter(spec_args, io)
}
}
let cli_base = match parse_cli_args(effective_args) {
Ok(parsed) => normalize_parse_result_paths(parsed)
Err(e) => return Err(CmdError::CliParse(e))
}
if cli_base.help_requested {
let help = string_to_bytes(cmd_help_text())
match (io.write_stdout)(help) {
Ok(_) => return Ok(CmdRunSummary::new())
Err(e) =>
return Err(CmdError::OutputWriteFailed("stdout for --help: \{e}"))
}
}
if cli_base.version_requested {
let version = string_to_bytes("\{cmd_version_text()}\n")
match (io.write_stdout)(version) {
Ok(_) => return Ok(CmdRunSummary::new())
Err(e) =>
return Err(CmdError::OutputWriteFailed("stdout for --version: \{e}"))
}
}
let cli = match (io.get_env)("STARSHINE_INPUT") {
Some(raw) => {
let input_globs = cli_base.input_globs.copy()
for glob in @cli.parse_starshine_input_env(Some(raw)) {
input_globs.push(glob)
}
CliParseResult::new(
config_path=cli_base.config_path,
input_globs~,
glob_enabled=cli_base.glob_enabled,
help_requested=cli_base.help_requested,
version_requested=cli_base.version_requested,
debug_serial_passes=cli_base.debug_serial_passes,
read_stdin=cli_base.read_stdin,
input_format=cli_base.input_format,
output_targets=cli_base.output_targets.copy(),
pass_flags=cli_base.pass_flags.copy(),
optimize_flags=cli_base.optimize_flags.copy(),
trap_mode=cli_base.trap_mode,
monomorphize_min_benefit=cli_base.monomorphize_min_benefit,
inlining_always_inline_max_size=cli_base.inlining_always_inline_max_size,
inlining_one_caller_inline_max_size=cli_base.inlining_one_caller_inline_max_size,
inlining_flexible_inline_max_size=cli_base.inlining_flexible_inline_max_size,
inlining_max_combined_binary_size=cli_base.inlining_max_combined_binary_size,
inlining_allow_functions_with_loops=cli_base.inlining_allow_functions_with_loops,
inlining_partial_inlining_ifs=cli_base.inlining_partial_inlining_ifs,
low_memory_unused=cli_base.low_memory_unused,
low_memory_bound=cli_base.low_memory_bound,
closed_world=cli_base.closed_world,
compiler_facts_policy=cli_base.compiler_facts_policy,
tracing=cli_base.tracing,
)
}
None => cli_base
}
let config = match config_json {
Some(config_text) =>
match parse_config_json(config_text) {
Ok(parsed) => parsed
Err(e) => return Err(e)
}
None => {
let (maybe_config_path, config_required) = resolve_config_path(cli, io)
match maybe_config_path {
Some(config_path) =>
if !(io.file_exists)(config_path) {
if config_required {
return Err(CmdError::ConfigNotFound(config_path))
} else {
CliParseResult::new()
}
} else {
let config_bytes = match (io.read_file)(config_path) {
Ok(bytes) => bytes
Err(e) =>
return Err(CmdError::ConfigReadFailed("\{config_path}: \{e}"))
}
let config_text = bytes_to_string_lossy(config_bytes)
match parse_config_json(config_text) {
Ok(parsed) => parsed
Err(e) => return Err(e)
}
}
None => CliParseResult::new()
}
}
}
let env = parse_env_overlay(io, config, cli)
let merged = merge_parse_results(config, env, cli)
let trace_level = cmd_resolve_optimize_trace_level(merged.tracing)
let trace_optimize = trace_level is Some(_)
let input_files = resolve_input_files_with_glob(
merged.input_globs,
io,
merged.glob_enabled,
)
if input_files.length() == 0 &&
optimizer_report_mode != Some(CmdOptimizerReportMode::Thresholds) {
return Err(CmdError::NoInputFiles)
}
let traps_never_happen = resolve_traps_never_happen(merged)
let ignore_implicit_traps = resolve_ignore_implicit_traps(merged)
let closed_world = resolve_closed_world(merged)
let compiler_fact_policy = resolve_compiler_fact_policy(
merged.compiler_facts_policy,
)
let (optimize_level, shrink_level) = resolve_optimize_levels(
merged.optimize_flags,
)
let (requested_pass_flags, disabled_features) = cmd_extract_proposal_feature_flags(
resolve_pass_flags(merged),
)
let pass_flags : Array[String] = []
let mut zero_filled_memory = false
let mut fast_math = false
for flag in requested_pass_flags {
if flag == "zero-filled-memory" {
zero_filled_memory = true
} else if flag == "fast-math" {
fast_math = true
} else {
pass_flags.push(flag)
}
}
let scheduled_pass_flags = resolve_effective_pass_flags(
pass_flags, optimize_level, shrink_level,
)
let max_passes = cmd_trace_optimize_max_passes(io)
let (resolved_passes, pass_flags_truncated) = limit_pass_flags_by_max_passes(
scheduled_pass_flags, max_passes,
)
cmd_trace_emit(
io,
trace_optimize,
"run start inputs=\{input_files.length()} explicit_flags=\{merged.pass_flags.length()} optimize_flags=\{merged.optimize_flags.length()} scheduled_flags=\{scheduled_pass_flags.length()} effective_scheduled_flags=\{resolved_passes.length()}",
)
match max_passes {
Some(limit) =>
cmd_trace_emit(
io,
trace_optimize,
"pass_limit max_passes=\{limit} scheduled_flag_truncated=\{pass_flags_truncated}",
)
None => ()
}
let monomorphize_min_benefit = match merged.monomorphize_min_benefit {
Some(v) => v
None => 5
}
let inlining_always_inline_max_size = match
merged.inlining_always_inline_max_size {
Some(v) => v
None => 2
}
let inlining_one_caller_inline_max_size = match
merged.inlining_one_caller_inline_max_size {
Some(v) => v
None => -1
}
let inlining_flexible_inline_max_size = match
merged.inlining_flexible_inline_max_size {
Some(v) => v
None => 20
}
let inlining_max_combined_binary_size = match
merged.inlining_max_combined_binary_size {
Some(v) => v
None => 400 * 1024
}
let inlining_allow_functions_with_loops = match
merged.inlining_allow_functions_with_loops {
Some(v) => v
None => false
}
let inlining_partial_inlining_ifs = match
merged.inlining_partial_inlining_ifs {
Some(v) => v
None => 0
}
let low_memory_unused = match merged.low_memory_unused {
Some(v) => v
None => false
}
let low_memory_bound = match merged.low_memory_bound {
Some(v) => v
None => 1024UL
}
let options = make_optimize_options(
optimize_level,
shrink_level,
traps_never_happen,
ignore_implicit_traps,
monomorphize_min_benefit,
inlining_always_inline_max_size,
inlining_one_caller_inline_max_size,
inlining_flexible_inline_max_size,
inlining_max_combined_binary_size,
inlining_allow_functions_with_loops,
inlining_partial_inlining_ifs,
closed_world,
zero_filled_memory,
fast_math,
low_memory_unused,
low_memory_bound,
merged.debug_serial_passes,
compiler_fact_policy,
)
match optimizer_report_mode {
Some(CmdOptimizerReportMode::Thresholds) =>
match
cmd_write_optimizer_report_stdout(
io,
cmd_optimizer_threshold_report_json(options),
) {
Ok(_) =>
return Ok(
CmdRunSummary::new(
resolved_passes=resolved_passes.copy(),
optimize_level~,
shrink_level~,
traps_never_happen~,
ignore_implicit_traps~,
monomorphize_min_benefit~,
closed_world~,
low_memory_unused~,
low_memory_bound~,
),
)
Err(err) => return Err(err)
}
Some(CmdOptimizerReportMode::ExpandedPassQueue)
| Some(CmdOptimizerReportMode::RuntimeInterface) =>
if input_files.length() != 1 {
return Err(
CmdError::OptimizeFailed(
"module optimizer JSON reports require exactly one input",
),
)
}
None => ()
}
cmd_trace_emit(
io,
trace_optimize,
"options o=\{optimize_level} z=\{shrink_level} traps_never=\{traps_never_happen} ignore_implicit_traps=\{ignore_implicit_traps} closed_world=\{closed_world} zero_filled_memory=\{zero_filled_memory}",
)
match merged.tracing {
Some(level) =>
cmd_trace_emit(
io,
true,
"trace_mode=\{cmd_optimize_trace_level_to_string(level)}",
)
None => ()
}
if trace_optimize {
cmd_trace_emit(
io,
true,
"effective_pass_flags=\{cmd_trace_join_pass_flags(resolved_passes)}",
)
}
if input_files.length() > 1 {
for target in merged.output_targets {
match target {
FileOutput(path) => return Err(CmdError::ambiguous_output_file(path))
_ => ()
}
}
}
let output_files : Array[String] = []
for input_path in input_files {
let input_total_start_us = cmd_trace_timer_start(trace_optimize)
cmd_trace_emit(io, trace_optimize, "input \{input_path}:start")
let format = match merged.input_format {
Some(format) => format
None =>
match infer_input_format(input_path) {
Some(inferred) => inferred
None => CliInputFormat::wasm()
}
}
let read_start_us = cmd_trace_timer_start(trace_optimize)
let raw_bytes = match (io.read_file)(input_path) {
Ok(bytes) => bytes
Err(e) => return Err(CmdError::InputReadFailed("\{input_path}: \{e}"))
}
cmd_trace_timer_stop(io, trace_optimize, "read-input", read_start_us)
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:read bytes=\{raw_bytes.length()}",
)
let text_lowering_start_us = cmd_trace_timer_start(trace_optimize)
let wasm_bytes = match format {
Wasm => raw_bytes
Wat | Wast =>
match
lower_text_module_for_pipeline(io, input_path, format, raw_bytes) {
Ok(bytes) => bytes
Err(e) => return Err(e)
}
}
cmd_trace_timer_stop(
io, trace_optimize, "text-lowering", text_lowering_start_us,
)
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:lowered bytes=\{wasm_bytes.length()}",
)
if disabled_features.is_empty() &&
optimizer_report_mode is None &&
should_copy_wasm_noop_without_module_pipeline(
format,
resolved_passes,
pass_flags_truncated,
merged.debug_serial_passes,
) {
cmd_trace_emit(io, trace_optimize, "input \{input_path}:copy-noop:start")
match
write_output_bytes_for_input(
io,
input_path,
format,
merged.output_targets,
wasm_bytes,
output_files,
trace_optimize,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
cmd_trace_emit(io, trace_optimize, "input \{input_path}:copy-noop:done")
cmd_trace_timer_stop(
io, trace_optimize, "input-total", input_total_start_us,
)
continue
}
let decode_start_us = cmd_trace_timer_start(trace_optimize)
let mod = match decode_module_for_pipeline(input_path, wasm_bytes) {
Ok(mod) => mod
Err(e) => return Err(e)
}
match cmd_validate_proposal_features(mod, disabled_features) {
Ok(_) => ()
Err(err) => return Err(err)
}
cmd_trace_timer_stop(io, trace_optimize, "decode", decode_start_us)
cmd_trace_emit(io, trace_optimize, "input \{input_path}:decode done")
match optimizer_report_mode {
Some(CmdOptimizerReportMode::ExpandedPassQueue) => {
let report = match
cmd_optimizer_pass_queue_report_json(mod, options, resolved_passes) {
Ok(text) => text
Err(err) => return Err(CmdError::OptimizeFailed(err))
}
match cmd_write_optimizer_report_stdout(io, report) {
Ok(_) =>
return Ok(
CmdRunSummary::new(
input_files=[input_path],
resolved_passes=resolved_passes.copy(),
optimize_level~,
shrink_level~,
traps_never_happen~,
ignore_implicit_traps~,
monomorphize_min_benefit~,
closed_world~,
low_memory_unused~,
low_memory_bound~,
),
)
Err(err) => return Err(err)
}
}
Some(CmdOptimizerReportMode::RuntimeInterface) =>
match
cmd_write_optimizer_report_stdout(
io,
cmd_optimizer_runtime_interface_report_json(mod),
) {
Ok(_) =>
return Ok(
CmdRunSummary::new(
input_files=[input_path],
resolved_passes=resolved_passes.copy(),
optimize_level~,
shrink_level~,
traps_never_happen~,
ignore_implicit_traps~,
monomorphize_min_benefit~,
closed_world~,
low_memory_unused~,
low_memory_bound~,
),
)
Err(err) => return Err(err)
}
_ => ()
}
let pipeline_setup_start_us = cmd_trace_timer_start(trace_optimize)
let pipeline_steps = match cmd_resolve_pipeline_steps(resolved_passes) {
Ok(steps) => steps
Err(err) => return Err(err)
}
let effective_pass_count = cmd_pipeline_step_optimize_count(pipeline_steps)
cmd_trace_timer_stop(
io, trace_optimize, "pipeline-setup", pipeline_setup_start_us,
)
match max_passes {
Some(limit) =>
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:pass_limit max_passes=\{limit} effective_pass_count=\{effective_pass_count}",
)
None => ()
}
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:pass_count=\{effective_pass_count} optimize:start",
)
let main_pipeline_start_us = cmd_trace_timer_start(trace_optimize)
let pending_passes : Array[String] = []
let mut optimized = mod
let mut pipeline_changed = false
let mut print_log_entries = 0
for step in pipeline_steps {
match step {
PipelinePass(flag) => pending_passes.push(flag)
ExtractFunctions(indices) => {
let segment = match
run_pipeline_pass_segment(
io, input_path, optimized, pending_passes, options, trace_optimize,
trace_level,
) {
Ok(out) => out
Err(err) => return Err(err)
}
optimized = segment.module_
pipeline_changed = pipeline_changed || segment.changed
pending_passes.clear()
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:extract-functions:start roots=\{indices.length()}",
)
let before_extract = optimized
optimized = match run_extract_functions_step(optimized, indices) {
Ok(out) => out
Err(err) => return Err(err)
}
pipeline_changed = pipeline_changed ||
!physical_equal(optimized, before_extract)
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:extract-functions:done roots=\{indices.length()}",
)
}
Dump(path, format) => {
let segment = match
run_pipeline_pass_segment(
io, input_path, optimized, pending_passes, options, trace_optimize,
trace_level,
) {
Ok(out) => out
Err(err) => return Err(err)
}
optimized = segment.module_
pipeline_changed = pipeline_changed || segment.changed
pending_passes.clear()
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:dump:start path=\{path}",
)
match write_pipeline_dump(io, optimized, path, format) {
Ok(_) => ()
Err(err) => return Err(err)
}
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:dump:done path=\{path}",
)
}
Print(kind, selector) => {
let segment = match
run_pipeline_pass_segment(
io, input_path, optimized, pending_passes, options, trace_optimize,
trace_level,
) {
Ok(out) => out
Err(err) => return Err(err)
}
optimized = segment.module_
pipeline_changed = pipeline_changed || segment.changed
pending_passes.clear()
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:print:start kind=\{kind} selector=\{cmd_print_selector_text(selector)}",
)
let (label, body) = match
cmd_resolve_pipeline_print_entry(optimized, kind, selector) {
Ok(entry) => entry
Err(err) => return Err(err)
}
match
cmd_write_pipeline_print_log(
io,
input_path,
print_log_entries,
label,
body,
print_log_entries == 0,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
print_log_entries += 1
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:print:done kind=\{kind} selector=\{cmd_print_selector_text(selector)}",
)
}
Validate => {
let segment = match
run_pipeline_pass_segment(
io, input_path, optimized, pending_passes, options, trace_optimize,
trace_level,
) {
Ok(out) => out
Err(err) => return Err(err)
}
optimized = segment.module_
pipeline_changed = pipeline_changed || segment.changed
pending_passes.clear()
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:validate:start",
)
match validate_module_for_pipeline_step(optimized, "validate pass") {
Ok(_) => ()
Err(err) => return Err(err)
}
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:validate:done",
)
}
}
}
let final_segment = match
run_pipeline_pass_segment(
io, input_path, optimized, pending_passes, options, trace_optimize, trace_level,
) {
Ok(out) => out
Err(err) => return Err(err)
}
optimized = final_segment.module_
pipeline_changed = pipeline_changed || final_segment.changed
cmd_trace_timer_stop(
io, trace_optimize, "main-pipeline", main_pipeline_start_us,
)
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:final-validate:start",
)
let final_validate_start_us = cmd_trace_timer_start(trace_optimize)
match
validate_module_for_pipeline_step(optimized, "final module validate") {
Ok(_) => ()
Err(err) => return Err(err)
}
cmd_trace_timer_stop(
io, trace_optimize, "validate:final-module", final_validate_start_us,
)
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:final-validate:done",
)
cmd_trace_emit(io, trace_optimize, "input \{input_path}:optimize done")
let reuse_check_start_us = cmd_trace_timer_start(trace_optimize)
let reuse_input_wasm_bytes = should_reuse_input_wasm_bytes(
format,
effective_pass_count,
merged.debug_serial_passes,
pipeline_changed,
optimized,
)
cmd_trace_timer_stop(
io, trace_optimize, "reuse-input-check", reuse_check_start_us,
)
let normal_out_bytes = if reuse_input_wasm_bytes {
wasm_bytes
} else {
let encode_start_us = cmd_trace_timer_start(trace_optimize)
let encoded = match
encode_module_for_pipeline(io, input_path, optimized) {
Ok(bytes) => bytes
Err(e) => return Err(e)
}
cmd_trace_timer_stop(io, trace_optimize, "encode", encode_start_us)
encoded
}
let size_portfolio_start_us = cmd_trace_timer_start(trace_optimize)
let size_portfolio_enabled = cmd_o4z_size_portfolio_enabled(
format, optimize_level, shrink_level, resolved_passes,
)
let original_for_portfolio = if size_portfolio_enabled {
match decode_module_for_pipeline(input_path, wasm_bytes) {
Ok(mod_) => Some(mod_)
Err(_) => None
}
} else {
None
}
let caught_try_table_boundary = match original_for_portfolio {
Some(original) => cmd_module_has_caught_try_table(original)
None => false
}
let unsafe_emscripten_main_runtime = match original_for_portfolio {
Some(original) =>
@passes.o4z_size_candidate_has_unsafe_emscripten_main_runtime(original)
None => false
}
let stateful_emscripten_main_runtime = match original_for_portfolio {
Some(original) =>
@passes.o4z_size_candidate_has_stateful_emscripten_main_runtime(
original,
)
None => false
}
let ifs_final_canonicalization_enabled = match original_for_portfolio {
Some(original) => cmd_o4z_ifs_final_canonicalization_enabled(original)
None => false
}
let stack_carried_simd_trunc_global_write = match original_for_portfolio {
Some(original) =>
@passes.o4z_size_candidate_has_stack_carried_simd_trunc_global_write(
original,
)
None => false
}
let zero_memory_dense_i64_runtime_hazard = match original_for_portfolio {
Some(original) =>
cmd_o4z_has_zero_memory_dense_i64_runtime_hazard(original)
None => false
}
let single_render_runtime_hazard = match original_for_portfolio {
Some(original) => cmd_o4z_has_single_render_runtime_hazard(original)
None => false
}
let root_default_local_copy = @passes.o4z_size_candidate_has_root_default_local_copy(
optimized,
)
let optimized_defined_func_count = match optimized.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.length()
None => 0
}
let portfolio_candidates : Array[(String, Bytes)] = []
let prevalidated_portfolio_labels : Array[String] = []
if !size_portfolio_enabled ||
(
!stack_carried_simd_trunc_global_write &&
!zero_memory_dense_i64_runtime_hazard &&
!single_render_runtime_hazard &&
!@passes.o4z_size_candidate_has_observable_assemblyscript_run(optimized)
) {
portfolio_candidates.push(("optimized", normal_out_bytes))
}
if size_portfolio_enabled && !caught_try_table_boundary {
match original_for_portfolio {
Some(original) => {
match validate_module(original) {
Ok(_) => portfolio_candidates.push(("input", wasm_bytes))
Err(_) => ()
}
if stack_carried_simd_trunc_global_write {
match
optimize_module_with_pass_flags(
original,
cmd_o4z_host_identity_safe_candidate_passes(
mod,
cmd_o4z_stack_carried_simd_safe_passes(),
),
options,
) {
Ok(cleaned) => {
let cleaned = @passes.o4z_canonicalize_final_size_candidate(
cleaned,
)
match validate_module(cleaned) {
Ok(_) =>
match encode_module_for_pipeline(io, input_path, cleaned) {
Ok(bytes) =>
portfolio_candidates.push(
("stack-carried-simd-safe-cleanup", bytes),
)
Err(_) => ()
}
Err(_) => ()
}
}
Err(_) => ()
}
}
}
None => ()
}
if @passes.o4z_size_candidate_has_observable_assemblyscript_memory(
optimized,
) &&
!@passes.o4z_size_candidate_has_observable_assemblyscript_run(optimized) {
match
optimize_module_with_pass_flags(
optimized,
cmd_o4z_observable_assemblyscript_safe_cleanup_passes(),
optimize_options_with_levels(options, 0, 0),
) {
Ok(cleaned) =>
match validate_module(cleaned) {
Ok(_) =>
match encode_module_for_pipeline(io, input_path, cleaned) {
Ok(bytes) =>
portfolio_candidates.push(
("observable-assemblyscript-safe-cleanup", bytes),
)
Err(_) => ()
}
Err(_) => ()
}
Err(_) => ()
}
}
if !unsafe_emscripten_main_runtime &&
!stack_carried_simd_trunc_global_write &&
!zero_memory_dense_i64_runtime_hazard &&
!single_render_runtime_hazard &&
!root_default_local_copy &&
!@passes.o4z_size_candidate_has_observable_assemblyscript_memory(
optimized,
) {
let final_cleanup_options = optimize_options_with_levels(options, 0, 0)
let independent_final_candidates = cmd_run_independent_encoded_candidate_jobs([
(
"optimized-final-precompute",
fn() {
match
optimize_module_with_pass_flags(
optimized,
cmd_o4z_final_precompute_passes(),
final_cleanup_options,
validate_final_module=false,
) {
Ok(precomputed) => {
let precomputed = @passes.o4z_canonicalize_final_size_candidate(
precomputed,
)
if precomputed == optimized {
None
} else {
match validate_module(precomputed) {
Ok(_) =>
match
encode_module_for_pipeline(
io, input_path, precomputed,
) {
Ok(bytes) =>
cmd_o4z_prevalidate_encoded_candidate(bytes)
Err(_) => None
}
Err(_) => None
}
}
}
Err(_) => None
}
},
),
(
"optimized-global-refining-cleanup",
fn() {
match
optimize_module_with_pass_flags(
optimized,
cmd_o4z_host_identity_safe_candidate_passes(mod, [
"global-refining", "optimize-instructions", "precompute", "duplicate-function-elimination",
]),
final_cleanup_options,
validate_final_module=false,
) {
Ok(cleaned) =>
if cleaned == optimized {
None
} else {
match validate_module(cleaned) {
Ok(_) =>
match
encode_module_for_pipeline(io, input_path, cleaned) {
Ok(bytes) =>
cmd_o4z_prevalidate_encoded_candidate(bytes)
Err(_) => None
}
Err(_) => None
}
}
Err(_) => None
}
},
),
(
"optimized-scalar-final-cleanup",
fn() {
match
optimize_module_with_pass_flags(
optimized,
cmd_o4z_scalar_final_cleanup_passes(),
final_cleanup_options,
validate_final_module=false,
) {
Ok(cleaned) =>
if cleaned == optimized {
None
} else {
match validate_module(cleaned) {
Ok(_) =>
match
encode_module_for_pipeline(io, input_path, cleaned) {
Ok(bytes) =>
cmd_o4z_prevalidate_encoded_candidate(bytes)
Err(_) => None
}
Err(_) => None
}
}
Err(_) => None
}
},
),
],
)
for candidate in independent_final_candidates {
portfolio_candidates.push(candidate)
prevalidated_portfolio_labels.push(candidate.0)
}
if @passes.o4z_size_candidate_has_unobservable_atomic_fence(optimized) {
let final_canonical = @passes.o4z_canonicalize_final_size_candidate(
optimized,
)
if final_canonical != optimized {
match validate_module(final_canonical) {
Ok(_) =>
match
encode_module_for_pipeline(io, input_path, final_canonical) {
Ok(bytes) =>
portfolio_candidates.push(
("optimized-final-canonicalization", bytes),
)
Err(_) => ()
}
Err(_) => ()
}
}
}
let outlined = @passes.o4z_outline_similar_size_functions(optimized)
if outlined != optimized {
match validate_module(outlined) {
Ok(_) =>
match encode_module_for_pipeline(io, input_path, outlined) {
Ok(bytes) =>
portfolio_candidates.push(
("optimized-similar-functions", bytes),
)
Err(_) => ()
}
Err(_) => ()
}
}
}
if cmd_o4z_late_cleanup_candidate_enabled(
wasm_bytes.length(),
normal_out_bytes.length(),
optimized_defined_func_count,
) &&
!stack_carried_simd_trunc_global_write &&
!zero_memory_dense_i64_runtime_hazard &&
!single_render_runtime_hazard &&
!root_default_local_copy &&
!@passes.o4z_size_candidate_has_observable_assemblyscript_memory(
optimized,
) {
let cleanup_options = optimize_options_with_levels(options, 0, 0)
match
optimize_module_with_pass_flags(
optimized,
cmd_o4z_host_identity_safe_candidate_passes(
mod,
cmd_o4z_size_cleanup_passes(),
),
cleanup_options,
) {
Ok(cleaned) => {
match encode_module_for_pipeline(io, input_path, cleaned) {
Ok(bytes) => portfolio_candidates.push(("late-cleanup", bytes))
Err(_) => ()
}
let late_root_default_local_copy = @passes.o4z_size_candidate_has_root_default_local_copy(
cleaned,
)
if unsafe_emscripten_main_runtime {
match
optimize_module_with_pass_flags(
cleaned,
["ssa-nomerge"],
cleanup_options,
validate_final_module=false,
) {
Ok(ssa_cleaned) =>
match validate_module(ssa_cleaned) {
Ok(_) => {
match
encode_module_for_pipeline(io, input_path, ssa_cleaned) {
Ok(bytes) =>
portfolio_candidates.push(("late-cleanup-ssa", bytes))
Err(_) => ()
}
let defined_func_count = match ssa_cleaned.code_sec {
Some(@lib.CodeSec(funcs)) => funcs.length()
None => 0
}
let stateful_cleanup_passes = cmd_o4z_stateful_plain_inlining_cleanup_passes(
defined_func_count,
)
if !stateful_cleanup_passes.is_empty() {
match
optimize_module_with_pass_flags(
ssa_cleaned,
cmd_o4z_host_identity_safe_candidate_passes(
mod, stateful_cleanup_passes,
),
cleanup_options,
validate_final_module=false,
) {
Ok(stateful_cleaned) => {
let stateful_candidate = if ifs_final_canonicalization_enabled {
@passes.o4z_canonicalize_final_size_ifs_stateful_candidate(
stateful_cleaned,
)
} else {
stateful_cleaned
}
match validate_module(stateful_candidate) {
Ok(_) => {
match
encode_module_for_pipeline(
io, input_path, stateful_candidate,
) {
Ok(bytes) =>
portfolio_candidates.push(
(
if ifs_final_canonicalization_enabled {
"late-cleanup-stateful-ifs-final-canonicalization"
} else {
"late-cleanup-stateful-plain-inlining"
},
bytes,
),
)
Err(_) => ()
}
if @passes.o4z_size_candidate_has_optimized_primes_variadic_pack_runtime(
stateful_cleaned,
) {
let canonical = @passes.o4z_restore_optimized_primes_variadic_pack_funcs(
stateful_cleaned,
@passes.o4z_canonicalize_final_size_candidate(
stateful_cleaned,
),
)
match validate_module(canonical) {
Ok(_) => {
match
encode_module_for_pipeline(
io, input_path, canonical,
) {
Ok(bytes) =>
portfolio_candidates.push(
(
"late-cleanup-stateful-primes-canonical",
bytes,
),
)
Err(_) => ()
}
match
optimize_module_with_pass_flags(
canonical,
cmd_o4z_post_canonical_fixed_point_passes(),
cleanup_options,
validate_final_module=false,
) {
Ok(fixed_point) => {
let fixed_point = @passes.o4z_restore_optimized_primes_variadic_pack_funcs(
stateful_cleaned, fixed_point,
)
match validate_module(fixed_point) {
Ok(_) => {
match
encode_module_for_pipeline(
io, input_path, fixed_point,
) {
Ok(bytes) =>
portfolio_candidates.push(
(
"late-cleanup-stateful-primes-fixed-point",
bytes,
),
)
Err(_) => ()
}
match
optimize_module_with_pass_flags(
fixed_point,
cmd_o4z_host_identity_safe_candidate_passes(
mod,
cmd_o4z_post_fixed_point_finish_passes(),
),
cleanup_options,
validate_final_module=false,
) {
Ok(finished) => {
let finished = @passes.o4z_canonicalize_final_size_store_carries(
@passes.o4z_restore_optimized_primes_variadic_pack_funcs(
stateful_cleaned,
@passes.o4z_canonicalize_final_size_candidate(
finished,
),
),
)
match
validate_module(finished) {
Ok(_) =>
match
encode_module_for_pipeline(
io, input_path, finished,
) {
Ok(bytes) =>
portfolio_candidates.push(
(
"late-cleanup-stateful-primes-fixed-point-finish",
bytes,
),
)
Err(_) => ()
}
Err(_) => ()
}
}
Err(_) => ()
}
}
Err(_) => ()
}
}
Err(_) => ()
}
}
Err(_) => ()
}
}
}
Err(_) => ()
}
}
Err(_) => ()
}
}
}
Err(_) => ()
}
Err(_) => ()
}
}
if !unsafe_emscripten_main_runtime && !late_root_default_local_copy {
let block_canonical = @passes.o4z_canonicalize_final_size_candidate(
cleaned,
)
match validate_module(block_canonical) {
Ok(_) => {
match
encode_module_for_pipeline(io, input_path, block_canonical) {
Ok(bytes) =>
portfolio_candidates.push(
("late-block-canonicalization", bytes),
)
Err(_) => ()
}
let block_root_default_local_copy = @passes.o4z_size_candidate_has_root_default_local_copy(
block_canonical,
)
if !block_root_default_local_copy {
let dense_i64 = @passes.o4z_size_candidate_has_small_dense_i64_mul(
block_canonical,
)
let post_canonical_passes = if dense_i64 {
cmd_o4z_dense_i64_cleanup_passes()
} else {
cmd_o4z_post_canonical_cleanup_passes()
}
match
optimize_module_with_pass_flags(
block_canonical,
post_canonical_passes,
cleanup_options,
validate_final_module=false,
) {
Ok(post_canonical) =>
match validate_module(post_canonical) {
Ok(_) => {
match
encode_module_for_pipeline(
io, input_path, post_canonical,
) {
Ok(bytes) =>
portfolio_candidates.push(
("post-canonical-cleanup", bytes),
)
Err(_) => ()
}
let post_canonical_root_default_local_copy = @passes.o4z_size_candidate_has_root_default_local_copy(
post_canonical,
)
if !dense_i64 &&
!post_canonical_root_default_local_copy {
let mut fixed_point_candidate = post_canonical
let mut wave = 0
for
roster in cmd_o4z_post_canonical_fixed_point_rosters() {
match
optimize_module_with_pass_flags(
fixed_point_candidate,
roster,
cleanup_options,
validate_final_module=false,
) {
Ok(fixed_point) =>
match validate_module(fixed_point) {
Ok(_) => {
wave = wave + 1
fixed_point_candidate = fixed_point
match
encode_module_for_pipeline(
io, input_path, fixed_point,
) {
Ok(bytes) =>
portfolio_candidates.push(
(
"post-canonical-fixed-point-wave-\{wave}",
bytes,
),
)
Err(_) => ()
}
}
Err(_) => break
}
Err(_) => break
}
}
let mut post_fixed_point_finished : @lib.Module? = None
match
optimize_module_with_pass_flags(
fixed_point_candidate,
cmd_o4z_host_identity_safe_candidate_passes(
mod,
cmd_o4z_post_fixed_point_finish_passes(),
),
cleanup_options,
validate_final_module=false,
) {
Ok(finished) => {
let finished = @passes.o4z_canonicalize_final_size_candidate(
finished,
)
match validate_module(finished) {
Ok(_) => {
post_fixed_point_finished = Some(finished)
match
encode_module_for_pipeline(
io, input_path, finished,
) {
Ok(bytes) =>
portfolio_candidates.push(
("post-fixed-point-finish", bytes),
)
Err(_) => ()
}
}
Err(_) => ()
}
}
Err(_) => ()
}
if !unsafe_emscripten_main_runtime {
let (finish_input, finish_input_changed) = @passes.o4z_canonicalize_final_size_candidate_with_changed(
fixed_point_candidate,
)
let precanonical_finished = cmd_o4z_choose_precanonical_finish(
finish_input,
finish_input_changed,
post_fixed_point_finished,
fn(finish_input) {
match
optimize_module_with_pass_flags(
finish_input,
cmd_o4z_host_identity_safe_candidate_passes(
mod,
cmd_o4z_post_fixed_point_finish_passes(),
),
cleanup_options,
validate_final_module=false,
) {
Ok(finished) =>
Some(
@passes.o4z_canonicalize_final_size_candidate(
finished,
),
)
Err(_) => None
}
},
)
match precanonical_finished {
Some(finished) =>
match validate_module(finished) {
Ok(_) => {
match
encode_module_for_pipeline(
io, input_path, finished,
) {
Ok(bytes) =>
portfolio_candidates.push(
("precanonical-finish", bytes),
)
Err(_) => ()
}
if stateful_emscripten_main_runtime {
match
optimize_module_with_pass_flags(
finished,
cmd_o4z_stateful_finish_cleanup_passes(),
cleanup_options,
validate_final_module=false,
) {
Ok(stateful_finished) =>
match
validate_module(
stateful_finished,
) {
Ok(_) =>
match
encode_module_for_pipeline(
io, input_path, stateful_finished,
) {
Ok(bytes) =>
portfolio_candidates.push(
(
"precanonical-finish-stateful-cleanup",
bytes,
),
)
Err(_) => ()
}
Err(_) => ()
}
Err(_) => ()
}
}
}
Err(_) => ()
}
None => ()
}
}
}
}
Err(_) => ()
}
Err(_) => ()
}
}
}
Err(_) => ()
}
}
}
Err(_) => ()
}
}
if cmd_o4z_late_cleanup_candidate_enabled(
wasm_bytes.length(),
normal_out_bytes.length(),
optimized_defined_func_count,
) &&
!stack_carried_simd_trunc_global_write &&
!zero_memory_dense_i64_runtime_hazard &&
!single_render_runtime_hazard {
match original_for_portfolio {
Some(original) =>
match
optimize_module_with_pass_flags(
original,
["simplify-locals-nostructure"],
options,
) {
Ok(simplified) => {
match encode_module_for_pipeline(io, input_path, simplified) {
Ok(bytes) =>
portfolio_candidates.push(
("original-simplify-locals", bytes),
)
Err(_) => ()
}
if @passes.o4z_size_candidate_has_observable_assemblyscript_memory(
simplified,
) {
match
optimize_module_with_pass_flags(
simplified,
cmd_o4z_observable_assemblyscript_safe_cleanup_passes(),
optimize_options_with_levels(options, 0, 0),
validate_final_module=false,
) {
Ok(cleaned) =>
match validate_module(cleaned) {
Ok(_) =>
match
encode_module_for_pipeline(io, input_path, cleaned) {
Ok(bytes) =>
portfolio_candidates.push(
(
"original-simplify-locals-assemblyscript-safe-cleanup",
bytes,
),
)
Err(_) => ()
}
Err(_) => ()
}
Err(_) => ()
}
}
if !unsafe_emscripten_main_runtime &&
!@passes.o4z_size_candidate_has_observable_assemblyscript_memory(
simplified,
) {
match
optimize_module_with_pass_flags(
simplified,
cmd_o4z_original_simplify_fixed_point_passes(),
options,
validate_final_module=false,
) {
Ok(fixed_point) =>
match validate_module(fixed_point) {
Ok(_) => {
match
encode_module_for_pipeline(
io, input_path, fixed_point,
) {
Ok(bytes) =>
portfolio_candidates.push(
("original-simplify-locals-fixed-point", bytes),
)
Err(_) => ()
}
if !stateful_emscripten_main_runtime &&
!stack_carried_simd_trunc_global_write {
match
optimize_module_with_pass_flags(
fixed_point,
cmd_o4z_scalar_final_cleanup_passes(),
optimize_options_with_levels(options, 0, 0),
validate_final_module=false,
) {
Ok(cleaned) => {
let cleaned = if @passes.o4z_size_candidate_has_unreferenced_defined_global(
cleaned,
) {
@passes.o4z_prune_final_size_unreferenced_globals(
cleaned,
)
} else {
cleaned
}
match validate_module(cleaned) {
Ok(_) =>
match
encode_module_for_pipeline(
io, input_path, cleaned,
) {
Ok(bytes) =>
portfolio_candidates.push(
(
"original-simplify-locals-scalar-final-cleanup",
bytes,
),
)
Err(_) => ()
}
Err(_) => ()
}
}
Err(_) => ()
}
}
}
Err(_) => ()
}
Err(_) => ()
}
}
}
Err(_) => ()
}
None => ()
}
}
}
if portfolio_candidates.length() == 0 {
portfolio_candidates.push(("optimized", normal_out_bytes))
}
if size_portfolio_enabled &&
!caught_try_table_boundary &&
!unsafe_emscripten_main_runtime &&
!@passes.o4z_size_candidate_has_observable_assemblyscript_memory(
optimized,
) &&
!stack_carried_simd_trunc_global_write &&
!zero_memory_dense_i64_runtime_hazard &&
!single_render_runtime_hazard &&
!root_default_local_copy &&
optimized_defined_func_count < 2000 &&
cmd_o4z_post_selection_local_cleanup_enabled(optimized) {
let (_, provisional_bytes) = cmd_choose_smallest_valid_encoded_candidate(
portfolio_candidates,
prevalidated_labels=prevalidated_portfolio_labels,
)
match decode_module_for_pipeline(input_path, provisional_bytes) {
Ok(provisional) =>
match
optimize_module_with_pass_flags(
provisional,
cmd_o4z_post_selection_local_cleanup_passes(),
optimize_options_with_levels(options, 0, 0),
validate_final_module=false,
) {
Ok(cleaned) =>
match validate_module(cleaned) {
Ok(_) =>
match encode_module_for_pipeline(io, input_path, cleaned) {
Ok(bytes) =>
if bytes.length() < provisional_bytes.length() {
portfolio_candidates.push(
("post-selection-local-cleanup", bytes),
)
}
Err(_) => ()
}
Err(_) => ()
}
Err(_) => ()
}
Err(_) => ()
}
if cmd_o4z_post_selection_control_cleanup_enabled(optimized) {
let (_, control_input_bytes) = cmd_choose_smallest_valid_encoded_candidate(
portfolio_candidates,
prevalidated_labels=prevalidated_portfolio_labels,
)
match decode_module_for_pipeline(input_path, control_input_bytes) {
Ok(control_input) =>
match
optimize_module_with_pass_flags(
control_input,
cmd_o4z_post_selection_control_cleanup_passes(),
optimize_options_with_levels(options, 0, 0),
validate_final_module=false,
) {
Ok(cleaned) =>
match validate_module(cleaned) {
Ok(_) =>
match encode_module_for_pipeline(io, input_path, cleaned) {
Ok(bytes) =>
if bytes.length() < control_input_bytes.length() {
portfolio_candidates.push(
("post-selection-control-cleanup", bytes),
)
}
Err(_) => ()
}
Err(_) => ()
}
Err(_) => ()
}
Err(_) => ()
}
}
let (_, urem_input_bytes) = cmd_choose_smallest_valid_encoded_candidate(
portfolio_candidates,
prevalidated_labels=prevalidated_portfolio_labels,
)
match decode_module_for_pipeline(input_path, urem_input_bytes) {
Ok(urem_input) if cmd_o4z_has_urem_regalloc_runtime(urem_input) => {
let canonical = @passes.o4z_canonicalize_final_size_candidate(
urem_input,
)
match validate_module(canonical) {
Ok(_) =>
match encode_module_for_pipeline(io, input_path, canonical) {
Ok(bytes) if bytes.length() < urem_input_bytes.length() =>
portfolio_candidates.push(
("post-selection-urem-canonical", bytes),
)
_ => ()
}
Err(_) => ()
}
}
_ => ()
}
}
cmd_trace_timer_stop(
io, trace_optimize, "size-portfolio", size_portfolio_start_us,
)
let candidate_selection_start_us = cmd_trace_timer_start(trace_optimize)
for candidate in portfolio_candidates {
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:candidate label=\{candidate.0} bytes=\{candidate.1.length()}",
)
}
let (selected_candidate, out_bytes) = if caught_try_table_boundary {
if compiler_fact_policy ==
@passes.CompilerFactUsePolicy::trust_assertions() &&
!physical_equal(mod, optimized) {
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:candidate force=prepared-input reason=caught-try-table",
)
("prepared-input", normal_out_bytes)
} else {
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:candidate force=input reason=caught-try-table",
)
("input", wasm_bytes)
}
} else if size_portfolio_enabled {
cmd_choose_smallest_valid_encoded_candidate_with_prevalidated_labels(
portfolio_candidates, prevalidated_portfolio_labels,
)
} else {
cmd_choose_smallest_encoded_candidate(portfolio_candidates)
}
cmd_trace_timer_stop(
io, trace_optimize, "candidate-selection", candidate_selection_start_us,
)
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:encode bytes=\{out_bytes.length()} candidate=\{selected_candidate}",
)
let post_encode_validate_start_us = cmd_trace_timer_start(trace_optimize)
if should_validate_encoded_module_after_encode(merged.debug_serial_passes) {
let selected_pre_encode_mod = match decode_module(out_bytes) {
Ok(mod_) => mod_
Err(_) => optimized
}
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:post-encode-validate:start",
)
match
validate_encoded_module_for_pipeline(
input_path, selected_pre_encode_mod, out_bytes,
) {
Ok(_) => ()
Err(e) =>
return Err(
cmd_append_error_note(
e,
cmd_post_encode_repro_note(
io, mod, resolved_passes, options, optimize_level, shrink_level,
traps_never_happen, ignore_implicit_traps, monomorphize_min_benefit,
closed_world, low_memory_unused, low_memory_bound,
),
),
)
}
cmd_trace_emit(
io,
trace_optimize,
"input \{input_path}:post-encode-validate:done",
)
}
cmd_trace_timer_stop(
io, trace_optimize, "post-encode-validate", post_encode_validate_start_us,
)
if !disabled_features.is_empty() {
let encoded = match decode_module_for_pipeline(input_path, out_bytes) {
Ok(encoded) => encoded
Err(err) => return Err(err)
}
match cmd_validate_proposal_features(encoded, disabled_features) {
Ok(_) => ()
Err(err) => return Err(err)
}
}
let write_start_us = cmd_trace_timer_start(trace_optimize)
match
write_output_bytes_for_input(
io,
input_path,
format,
merged.output_targets,
out_bytes,
output_files,
trace_optimize,
) {
Ok(_) => ()
Err(err) => return Err(err)
}
cmd_trace_timer_stop(io, trace_optimize, "write-output", write_start_us)
cmd_trace_timer_stop(
io, trace_optimize, "input-total", input_total_start_us,
)
}
Ok(
CmdRunSummary::new(
input_files~,
output_files~,
resolved_passes~,
optimize_level~,
shrink_level~,
traps_never_happen~,
ignore_implicit_traps~,
monomorphize_min_benefit~,
closed_world~,
low_memory_unused~,
low_memory_bound~,
),
)
}
///|
#cfg(target="native")
fn cmd_native_c_string(text : String) -> Bytes {
let out = @buffer.new(size_hint=text.length() + 1)
for c in text {
let code = c.to_int()
if code >= 0 && code <= 255 {
out.write_byte(code.to_byte())
} else {
out.write_byte('?'.to_int().to_byte())
}
}
out.write_byte(0)
out.to_bytes()
}
///|
#cfg(target="native")
#borrow(path, mode)
extern "c" fn cmd_native_fopen(path : Bytes, mode : Bytes) -> UInt64 = "fopen"
///|
#cfg(target="native")
extern "c" fn cmd_native_fclose(stream : UInt64) -> Int32 = "fclose"
///|
#cfg(target="native")
extern "c" fn cmd_native_fseek(
stream : UInt64,
offset : Int32,
whence : Int32,
) -> Int32 = "fseek"
///|
#cfg(target="native")
extern "c" fn cmd_native_ftell(stream : UInt64) -> Int64 = "ftell"
///|
#cfg(target="native")
#borrow(ptr)
extern "c" fn cmd_native_fread(
ptr : Bytes,
size : UInt64,
count : UInt64,
stream : UInt64,
) -> UInt64 = "fread"
///|
#cfg(target="native")
#borrow(path)
extern "c" fn cmd_native_access(path : Bytes, mode : Int32) -> Int32 = "access"
///|
#cfg(target="native")
#borrow(command, mode)
extern "c" fn cmd_native_popen(command : Bytes, mode : Bytes) -> UInt64 = "popen"
///|
#cfg(target="native")
extern "c" fn cmd_native_pclose(pipe : UInt64) -> Int32 = "pclose"
///|
#cfg(target="native")
fn cmd_native_system(command : Bytes) -> Int32 {
// Avoid binding directly to libc `system`: MoonBit emits an incompatible C
// prototype for borrowed `Bytes`, which conflicts with `` on native
// release builds. `popen`/`pclose` still execute through the shell and return
// the command's wait status for the small native helper probes used here.
let pipe = cmd_native_popen(command, cmd_native_c_string("r"))
if pipe == 0UL {
-1
} else {
cmd_native_pclose(pipe)
}
}
///|
#cfg(target="native")
extern "c" fn cmd_native_getpid() -> Int32 = "getpid"
///|
#cfg(target="native")
extern "c" fn cmd_native_fork() -> Int32 = "fork"
///|
#cfg(target="native")
#borrow(status)
extern "c" fn cmd_native_waitpid(
pid : Int32,
status : Bytes,
options : Int32,
) -> Int32 = "waitpid"
///|
#cfg(target="native")
#borrow(ptr)
extern "c" fn cmd_native_write(
fd : Int32,
ptr : Bytes,
count : UInt64,
) -> Int32 = "write"
///|
#cfg(target="native")
#borrow(buf)
extern "c" fn cmd_native_getrlimit(resource : Int32, buf : Bytes) -> Int32 = "getrlimit"
///|
#cfg(target="native")
#borrow(buf)
extern "c" fn cmd_native_setrlimit(resource : Int32, buf : Bytes) -> Int32 = "setrlimit"
///|
#cfg(target="native")
fn cmd_native_file_exists(path : String) -> Bool {
let c_path = cmd_native_c_string(path)
cmd_native_access(c_path, 0) == 0
}
///|
#cfg(target="native")
fn cmd_native_read_file(path : String) -> Result[Bytes, String] {
let c_path = cmd_native_c_string(path)
let c_mode = cmd_native_c_string("rb")
let file = cmd_native_fopen(c_path, c_mode)
if file == 0UL {
return Err("open failed")
}
if cmd_native_fseek(file, 0, 2) != 0 {
ignore(cmd_native_fclose(file))
return Err("seek end failed")
}
let size_i64 = cmd_native_ftell(file)
if size_i64 < 0L {
ignore(cmd_native_fclose(file))
return Err("tell failed")
}
let size_u = size_i64.reinterpret_as_uint64()
if size_u > 2147483647UL {
ignore(cmd_native_fclose(file))
return Err("file too large")
}
let size = size_i64.to_int()
if cmd_native_fseek(file, 0, 0) != 0 {
ignore(cmd_native_fclose(file))
return Err("seek start failed")
}
let out = Bytes::new(size)
if size > 0 {
let read = cmd_native_fread(out, 1UL, size_u, file)
if read != size_u {
ignore(cmd_native_fclose(file))
return Err("read failed")
}
}
if cmd_native_fclose(file) != 0 {
return Err("close failed")
}
Ok(out)
}
///|
#cfg(target="native")
fn cmd_native_write_file(path : String, bytes : Bytes) -> Result[Unit, String] {
@sfs.write_file_bytes(path, bytes)
}
///|
#cfg(target="native")
fn cmd_debug_dump_invalid_encoded_module(bytes : Bytes) -> Unit {
ignore(cmd_native_write_file("/tmp/starshine-invalid-encoded.wasm", bytes))
}
///|
#cfg(target="native")
fn cmd_debug_dump_invalid_final_module(mod : Module) -> Unit {
match encode_module(mod) {
Ok(bytes) =>
ignore(cmd_native_write_file("/tmp/starshine-invalid-final.wasm", bytes))
Err(_) => ()
}
}
///|
#cfg(target="native")
fn cmd_debug_dump_invalid_final_func_text(text : String) -> Unit {
ignore(
@sfs.write_file_bytes(
"/tmp/starshine-invalid-final-func.txt",
@utf8.encode(text),
),
)
}
///|
#cfg(target="native")
fn cmd_debug_dump_invalid_encoded_func_text(
before_text : String,
after_text : String,
) -> Unit {
ignore(
@sfs.write_file_bytes(
"/tmp/starshine-invalid-before.txt",
@utf8.encode(before_text),
),
)
ignore(
@sfs.write_file_bytes(
"/tmp/starshine-invalid-after.txt",
@utf8.encode(after_text),
),
)
}
///|
#cfg(target="js")
fn cmd_debug_dump_invalid_encoded_module(_bytes : Bytes) -> Unit {
}
///|
#cfg(target="js")
fn cmd_debug_dump_invalid_final_module(_mod : Module) -> Unit {
}
///|
#cfg(target="js")
fn cmd_debug_dump_invalid_final_func_text(_text : String) -> Unit {
}
///|
#cfg(target="js")
fn cmd_debug_dump_invalid_encoded_func_text(
_before_text : String,
_after_text : String,
) -> Unit {
}
///|
#cfg(target="llvm")
fn cmd_debug_dump_invalid_encoded_module(_bytes : Bytes) -> Unit {
}
///|
#cfg(target="llvm")
fn cmd_debug_dump_invalid_final_module(_mod : Module) -> Unit {
}
///|
#cfg(target="llvm")
fn cmd_debug_dump_invalid_final_func_text(_text : String) -> Unit {
}
///|
#cfg(target="llvm")
fn cmd_debug_dump_invalid_encoded_func_text(
_before_text : String,
_after_text : String,
) -> Unit {
}
///|
#cfg(target="wasm-gc")
fn cmd_debug_dump_invalid_encoded_module(_bytes : Bytes) -> Unit {
}
///|
#cfg(target="wasm-gc")
fn cmd_debug_dump_invalid_final_module(_mod : Module) -> Unit {
}
///|
#cfg(target="wasm-gc")
fn cmd_debug_dump_invalid_final_func_text(_text : String) -> Unit {
}
///|
#cfg(target="wasm-gc")
fn cmd_debug_dump_invalid_encoded_func_text(
_before_text : String,
_after_text : String,
) -> Unit {
}
///|
#cfg(target="wasm")
fn cmd_debug_dump_invalid_encoded_module(_bytes : Bytes) -> Unit {
}
///|
#cfg(target="wasm")
fn cmd_debug_dump_invalid_final_module(_mod : Module) -> Unit {
}
///|
#cfg(target="wasm")
fn cmd_debug_dump_invalid_final_func_text(_text : String) -> Unit {
}
///|
#cfg(target="wasm")
fn cmd_debug_dump_invalid_encoded_func_text(
_before_text : String,
_after_text : String,
) -> Unit {
}
///|
#cfg(target="native")
fn cmd_native_remove_file(path : String) -> Unit {
ignore(@sfs.remove_file(path))
}
///|
#cfg(target="native")
fn cmd_native_write_stdout(bytes : Bytes) -> Result[Unit, String] {
if bytes.length() == 0 {
return Ok(())
}
let wrote = cmd_native_write(1, bytes, bytes.length().to_uint64())
if wrote < 0 {
return Err("write failed")
}
if wrote != bytes.length() {
return Err("short write: \{wrote}/\{bytes.length()}")
}
Ok(())
}
///|
#cfg(target="native")
fn cmd_native_write_stderr(bytes : Bytes) -> Result[Unit, String] {
if bytes.length() == 0 {
return Ok(())
}
let wrote = cmd_native_write(2, bytes, bytes.length().to_uint64())
if wrote < 0 {
return Err("write failed")
}
if wrote != bytes.length() {
return Err("short write: \{wrote}/\{bytes.length()}")
}
Ok(())
}
///|
#cfg(target="native")
fn cmd_native_read_u64_le(bytes : Bytes, start : Int32) -> UInt64 {
let mut out : UInt64 = 0UL
let mut i = 0
while i < 8 {
out = out | (bytes[start + i].to_uint64() << (i * 8))
i += 1
}
out
}
///|
#cfg(target="native")
fn cmd_native_raise_stack_soft_limit() -> Unit {
// Linux/macOS RLIMIT_STACK constant.
let rlimit_stack = 3
// struct rlimit { rlim_t cur; rlim_t max; } with 64-bit rlim_t.
let buf = Bytes::new(16)
if cmd_native_getrlimit(rlimit_stack, buf) != 0 {
return
}
let cur = cmd_native_read_u64_le(buf, 0)
let max = cmd_native_read_u64_le(buf, 8)
if cur >= max {
return
}
let set_buf = @buffer.new(size_hint=16)
let mut i = 0
while i < 8 {
set_buf.write_byte(((max >> (i * 8)) & 0xFFUL).to_byte())
i += 1
}
i = 0
while i < 8 {
set_buf.write_byte(((max >> (i * 8)) & 0xFFUL).to_byte())
i += 1
}
ignore(cmd_native_setrlimit(rlimit_stack, set_buf.to_bytes()))
}
///|
#cfg(target="native")
fn cmd_native_get_env(name : String) -> String? {
@env.get_env_var(name)
}
///|
#cfg(target="native")
fn cmd_native_shell_quote(text : String) -> String {
let escaped = text.replace(old="'", new="'\"'\"'")
"'\{escaped}'"
}
///|
#cfg(target="native")
#borrow(tv)
extern "c" fn cmd_native_gettimeofday(tv : Bytes, tz : UInt64) -> Int32 = "gettimeofday"
///|
#cfg(target="native")
fn cmd_native_read_u64_timeval(bytes : Bytes, start : Int32) -> UInt64 {
let mut out = 0UL
let mut i = 0
while i < 8 {
out = out | (bytes[start + i].to_uint64() << (i * 8))
i += 1
}
out
}
///|
#cfg(target="native")
fn cmd_native_now_ms() -> UInt64 {
let tv = Bytes::new(16)
if cmd_native_gettimeofday(tv, 0UL) != 0 {
return @env.now()
}
let sec = cmd_native_read_u64_timeval(tv, 0)
let usec = cmd_native_read_u64_timeval(tv, 8)
sec * 1000UL + usec / 1000UL
}
///|
#cfg(target="native")
fn cmd_native_tmp_path(prefix : String, suffix : String) -> String {
"/tmp/starshine-cmd-\{prefix}-\{cmd_native_getpid()}-\{cmd_native_now_ms()}\{suffix}"
}
///|
fn cmd_run_independent_encoded_candidate_jobs_serial(
jobs : Array[(String, () -> Bytes?)],
) -> Array[(String, Bytes)] {
let out : Array[(String, Bytes)] = []
for job in jobs {
match (job.1)() {
Some(bytes) => out.push((job.0, bytes))
None => ()
}
}
out
}
///|
#cfg(target="native")
fn cmd_native_wait_exit_code(pid : Int32) -> Int32? {
let status = Bytes::new(4)
if cmd_native_waitpid(pid, status, 0) != pid {
return None
}
let mut raw = 0
let mut shift = 0
for byte in status {
raw = raw | (byte.to_int() << shift)
shift += 8
}
let signal = raw & 0x7F
if signal == 0 {
Some((raw >> 8) & 0xFF)
} else {
None
}
}
///|
#cfg(target="native")
fn cmd_run_independent_encoded_candidate_jobs(
jobs : Array[(String, () -> Bytes?)],
) -> Array[(String, Bytes)] {
if jobs.length() < 2 {
return cmd_run_independent_encoded_candidate_jobs_serial(jobs)
}
let base = cmd_native_tmp_path("o4z-portfolio", "")
let pids : Array[Int32] = []
let paths : Array[String] = []
for idx = 0; idx < jobs.length(); idx = idx + 1 {
let path = "\{base}-\{idx}.wasm"
cmd_native_remove_file(path)
paths.push(path)
let pid = cmd_native_fork()
if pid == 0 {
let exit_code = match (jobs[idx].1)() {
Some(bytes) =>
match cmd_native_write_file(path, bytes) {
Ok(_) => 0
Err(_) => 2
}
None => 3
}
cmd_native_exit(exit_code)
abort("native candidate worker exit returned")
}
pids.push(pid)
}
let out : Array[(String, Bytes)] = []
for idx = 0; idx < jobs.length(); idx = idx + 1 {
let pid = pids[idx]
let path = paths[idx]
let worker_code = if pid > 0 {
cmd_native_wait_exit_code(pid)
} else {
None
}
match worker_code {
Some(0) =>
match cmd_native_read_file(path) {
Ok(bytes) => out.push((jobs[idx].0, bytes))
Err(_) =>
match (jobs[idx].1)() {
Some(bytes) => out.push((jobs[idx].0, bytes))
None => ()
}
}
Some(3) => ()
_ =>
match (jobs[idx].1)() {
Some(bytes) => out.push((jobs[idx].0, bytes))
None => ()
}
}
cmd_native_remove_file(path)
}
out
}
///|
#cfg(target="wasm")
fn cmd_run_independent_encoded_candidate_jobs(
jobs : Array[(String, () -> Bytes?)],
) -> Array[(String, Bytes)] {
cmd_run_independent_encoded_candidate_jobs_serial(jobs)
}
///|
#cfg(target="wasm-gc")
fn cmd_run_independent_encoded_candidate_jobs(
jobs : Array[(String, () -> Bytes?)],
) -> Array[(String, Bytes)] {
cmd_run_independent_encoded_candidate_jobs_serial(jobs)
}
///|
#cfg(target="js")
fn cmd_run_independent_encoded_candidate_jobs(
jobs : Array[(String, () -> Bytes?)],
) -> Array[(String, Bytes)] {
cmd_run_independent_encoded_candidate_jobs_serial(jobs)
}
///|
#cfg(target="llvm")
fn cmd_run_independent_encoded_candidate_jobs(
jobs : Array[(String, () -> Bytes?)],
) -> Array[(String, Bytes)] {
cmd_run_independent_encoded_candidate_jobs_serial(jobs)
}
///|
fn cmd_default_list_candidates() -> Array[String] {
match @sfs.list_files_recursive(".") {
Ok(files) => files
Err(_) => []
}
}
///|
#cfg(target="native")
fn cmd_native_list_candidates() -> Array[String] {
cmd_default_list_candidates()
}
///|
#cfg(target="native")
fn cmd_native_lower_text_module(
path : String,
format : CliInputFormat,
bytes : Bytes,
) -> Result[Bytes, String] {
match format {
Wasm => return Err("expected wat/wast input format")
Wat | Wast => ()
}
let in_suffix = match format {
Wat => ".wat"
Wast => ".wast"
Wasm => ".wat"
}
let in_path = cmd_native_tmp_path("text-in", in_suffix)
let out_path = cmd_native_tmp_path("text-out", ".wasm")
if cmd_native_write_file(in_path, bytes) is Err(e) {
cmd_native_remove_file(in_path)
cmd_native_remove_file(out_path)
return Err("failed to stage text input for \{path}: \{e}")
}
let in_q = cmd_native_shell_quote(in_path)
let out_q = cmd_native_shell_quote(out_path)
let commands = [
"wasm-tools parse \{in_q} -o \{out_q}",
"wat2wasm \{in_q} -o \{out_q}",
]
let mut lowered = false
for command in commands {
let c_command = cmd_native_c_string(command)
let code = cmd_native_system(c_command)
if code == 0 && cmd_native_file_exists(out_path) {
lowered = true
break
}
}
let result = if lowered {
match cmd_native_read_file(out_path) {
Ok(out) => Ok(out)
Err(e) => Err("failed to read lowered wasm for \{path}: \{e}")
}
} else {
Err(
"failed to lower \{path}: expected native `wat2wasm` or `wasm-tools parse` to be available",
)
}
cmd_native_remove_file(in_path)
cmd_native_remove_file(out_path)
result
}
///|
#cfg(target="native")
fn cmd_native_print_text_module(mod : Module) -> Result[Bytes, String] {
let wasm_bytes = match encode_module(mod) {
Ok(bytes) => bytes
Err(err) => return Err("failed to encode module for wat dump: \{err}")
}
let in_path = cmd_native_tmp_path("print-in", ".wasm")
let out_path = cmd_native_tmp_path("print-out", ".wat")
if cmd_native_write_file(in_path, wasm_bytes) is Err(e) {
cmd_native_remove_file(in_path)
cmd_native_remove_file(out_path)
return Err("failed to stage wat dump input: \{e}")
}
let in_q = cmd_native_shell_quote(in_path)
let out_q = cmd_native_shell_quote(out_path)
let commands = [
"wasm-tools print \{in_q} -o \{out_q}",
"wasm-tools print \{in_q} > \{out_q}",
"wasm-dis \{in_q} -o \{out_q}",
]
let mut printed = false
for command in commands {
let c_command = cmd_native_c_string(command)
let code = cmd_native_system(c_command)
if code == 0 && cmd_native_file_exists(out_path) {
printed = true
break
}
}
let result = if printed {
match cmd_native_read_file(out_path) {
Ok(out) => Ok(out)
Err(e) => Err("failed to read printed wat dump: \{e}")
}
} else {
Err(
"failed to print wat dump: expected native `wasm-tools print` or `wasm-dis` to be available",
)
}
cmd_native_remove_file(in_path)
cmd_native_remove_file(out_path)
result
}
///|
#cfg(target="native")
fn default_cmd_io() -> CmdIO {
CmdIO::new(
get_env=cmd_native_get_env,
file_exists=cmd_native_file_exists,
read_file=cmd_native_read_file,
print_text_module=cmd_native_print_text_module,
write_file=cmd_native_write_file,
write_stdout=cmd_native_write_stdout,
write_stderr=cmd_native_write_stderr,
list_candidates=cmd_native_list_candidates,
lower_text_module=cmd_native_lower_text_module,
)
}
///|
#cfg(target="js")
fn default_cmd_io() -> CmdIO {
let _ = @sfs.remove_file
let _ = cmd_default_list_candidates
CmdIO::new()
}
///|
#cfg(target="llvm")
fn default_cmd_io() -> CmdIO {
let _ = @sfs.remove_file
let _ = cmd_default_list_candidates
CmdIO::new()
}
///|
pub fn run_cmd(args : Array[String]) -> Result[CmdRunSummary, CmdError] {
run_cmd_with_adapter(args, default_cmd_io())
}
///|
pub fn run_cmd_exit_code_with_adapter(
args : Array[String],
io : CmdIO,
config_json~ : String? = None,
) -> Int32 {
match run_cmd_with_adapter(args, io, config_json~) {
Ok(_) => 0
Err(err) => {
ignore((io.write_stderr)(cmd_error_stderr_bytes(err)))
1
}
}
}
///|
pub fn run_cmd_exit_code(args : Array[String]) -> Int32 {
run_cmd_exit_code_with_adapter(args, default_cmd_io())
}
///|
#warnings("-unused_value")
fn trim_program_name(argv : Array[String]) -> Array[String] {
if argv.length() == 0 {
return []
}
let out = Array::new(capacity=argv.length() - 1)
let mut i = 1
while i < argv.length() {
out.push(argv[i])
i += 1
}
out
}
///|
#cfg(target="wasm")
fn cmd_wasm_proc_exit(code : UInt32) -> Unit = "wasi_snapshot_preview1" "proc_exit"
///|
#cfg(target="wasm")
fn main {
let exit_code = run_cmd_exit_code(trim_program_name(@env.args()))
if exit_code != 0 {
cmd_wasm_proc_exit(exit_code.reinterpret_as_uint())
}
}
///|
#cfg(target="wasm-gc")
fn main {
ignore(run_cmd_exit_code(trim_program_name(@env.args())))
}
///|
#cfg(target="native")
extern "c" fn cmd_native_exit(code : Int32) -> Unit = "quick_exit"
///|
#cfg(target="native")
fn main {
let args = trim_program_name(@env.args())
let exit_code = run_cmd_exit_code(args)
if exit_code != 0 {
cmd_native_exit(exit_code)
}
}
///|
#cfg(target="js")
fn main {
()
}
///|
#cfg(target="native")
fn cmd_prepare_runtime() -> Unit {
cmd_native_raise_stack_soft_limit()
}
///|
#cfg(target="js")
fn cmd_prepare_runtime() -> Unit {
}
///|
#cfg(target="llvm")
fn cmd_prepare_runtime() -> Unit {
}
///|
#cfg(target="wasm-gc")
fn cmd_prepare_runtime() -> Unit {
}
///|
#cfg(target="wasm")
fn cmd_prepare_runtime() -> Unit {
}
///|
///|
#cfg(target="wasm")
#external
priv type CmdExternString
///|
#cfg(target="wasm")
#external
priv type CmdStringReadHandle
///|
#cfg(target="wasm")
fn cmd_wasm_begin_read_string(s : CmdExternString) -> CmdStringReadHandle = "__moonbit_fs_unstable" "begin_read_string"
///|
#cfg(target="wasm")
fn cmd_wasm_string_read_char(handle : CmdStringReadHandle) -> Int32 = "__moonbit_fs_unstable" "string_read_char"
///|
#cfg(target="wasm")
fn cmd_wasm_finish_read_string(handle : CmdStringReadHandle) = "__moonbit_fs_unstable" "finish_read_string"
///|
#cfg(target="wasm")
fn cmd_wasm_string_from_extern(extern_str : CmdExternString) -> String {
let out = StringBuilder::new()
let handle = cmd_wasm_begin_read_string(extern_str)
while true {
let ch = cmd_wasm_string_read_char(handle)
if ch == -1 {
break
}
out.write_char(ch.unsafe_to_char())
}
cmd_wasm_finish_read_string(handle)
out.to_string()
}
///|
#cfg(any(target="wasm", target="wasm-gc"))
fn cmd_wasm_list_candidates() -> Array[String] {
cmd_default_list_candidates()
}
///|
#cfg(any(target="wasm", target="wasm-gc"))
fn cmd_wasm_file_exists(path : String) -> Bool {
match @sfs.list_files_recursive(path) {
Ok(files) => files.length() > 0
Err(_) => false
}
}
///|
#cfg(any(target="wasm", target="wasm-gc"))
fn cmd_wasm_read_file(path : String) -> Result[Bytes, String] {
@sfs.read_file_bytes(path)
}
///|
#cfg(any(target="wasm", target="wasm-gc"))
fn cmd_wasm_write_file(path : String, bytes : Bytes) -> Result[Unit, String] {
@sfs.write_file_bytes(path, bytes)
}
///|
#cfg(any(target="wasm", target="wasm-gc"))
fn cmd_wasm_write_stdout(bytes : Bytes) -> Result[Unit, String] {
if bytes.length() == 0 {
return Ok(())
}
@sfs.write_file_bytes("/dev/stdout", bytes)
}
///|
#cfg(any(target="wasm", target="wasm-gc"))
fn cmd_wasm_write_stderr(bytes : Bytes) -> Result[Unit, String] {
if bytes.length() == 0 {
return Ok(())
}
@sfs.write_file_bytes("/dev/stderr", bytes)
}
///|
#cfg(any(target="wasm", target="wasm-gc"))
fn default_cmd_io() -> CmdIO {
let _ = @sfs.remove_file
CmdIO::new(
file_exists=cmd_wasm_file_exists,
read_file=cmd_wasm_read_file,
write_file=cmd_wasm_write_file,
write_stdout=cmd_wasm_write_stdout,
write_stderr=cmd_wasm_write_stderr,
list_candidates=cmd_wasm_list_candidates,
)
}
///|
test "Binaryen 132 instruction command folds a constant tee zero comparison" {
let input = @wast.wast_to_binary_module(
"(module (global (mut i32) (i32.const 0)) (func (export \"run\") (param i64) (result i32) (if (result i32) (i64.eq (local.tee 0 (i64.const 0)) (i64.const 0)) (then (global.set 0 (i32.const 1)) (i64.eqz (local.get 0))) (else (i32.const 0)))))",
).unwrap()
let output = optimize_module_with_pass_flags(
input,
["optimize-instructions"],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_false(body.contains(@lib.Instruction::i64_eq()))
}
///|
test "Binaryen 132 instruction command converges saved constraint joins case 907 in one run" {
let source =
#|(module
#| (type (func (param i32 i32) (result i32)))
#| (export "run" (func 0))
#| (func (type 0) (param i32 i32) (result i32)
#| (local i32)
#| local.get 0
#| i32.eqz
#| if
#| i32.const 3
#| local.set 2
#| else
#| i32.const 5
#| local.set 2
#| end
#| local.get 2
#| i32.const 0
#| i32.ne))
let input = @wast.wast_to_binary_module(source).unwrap()
let once = optimize_module_with_pass_flags(
input,
["optimize-instructions"],
OptimizeOptions::new(),
).unwrap()
let twice = optimize_module_with_pass_flags(
once,
["optimize-instructions"],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(once).unwrap()
@validate.validate_module(twice).unwrap()
@debug.assert_eq(once, twice)
@debug.assert_eq(
cmd_deep_audit_count(cmd_deep_audit_body(once), fn(instr) {
instr is @lib.I32Eqz
}),
0,
)
}
///|
test "Binaryen 132 instruction command orders saved constraint logical case 767" {
let source =
#|(module
#| (type (func (param i32 i32) (result i32)))
#| (export "run" (func 0))
#| (func (type 0) (param i32 i32) (result i32)
#| (local i32)
#| local.get 0
#| i32.const 10
#| i32.ge_u
#| local.get 0
#| i32.const 20
#| i32.le_u
#| i32.or
#| i32.eqz
#| i32.eqz
#| if (result i32)
#| local.get 0
#| i32.const 0
#| i32.ne
#| else
#| local.get 0
#| i32.const 30
#| i32.lt_u
#| end))
let input = @wast.wast_to_binary_module(source).unwrap()
let output = optimize_module_with_pass_flags(
input,
["optimize-instructions"],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let body = cmd_deep_audit_body(output)
@debug.assert_eq(body[0], @lib.Instruction::local_get(@lib.LocalIdx::new(0U)))
@debug.assert_eq(body[1], @lib.Instruction::i32_const(@lib.I32(20)))
@debug.assert_eq(body[2], @lib.Instruction::i32_le_u())
@debug.assert_eq(body[3], @lib.Instruction::local_get(@lib.LocalIdx::new(0U)))
@debug.assert_eq(body[4], @lib.Instruction::i32_const(@lib.I32(10)))
@debug.assert_eq(body[5], @lib.Instruction::i32_ge_u())
@debug.assert_eq(body[6], @lib.Instruction::i32_or())
}
///|
test "Binaryen 132 constraint analysis command dispatch folds an implied predicate" {
let source =
#|(module (func (export "run") (param i32) (result i32)
#| (if (result i32) (i32.eq (local.get 0) (i32.const 10))
#| (then (i32.ne (local.get 0) (i32.const 0))) (else (i32.const 2)))))
let original = match @wast.wast_to_binary_module(source) {
Ok(value) => value
Err(err) => fail(err)
}
let optimized = match
optimize_module_with_pass_flags(
original,
["constraint-analysis"],
OptimizeOptions::new(),
) {
Ok(value) => value
Err(err) => fail(err)
}
fn count(
instrs : Array[@lib.Instruction],
wanted : @lib.Instruction,
) -> Int32 {
let mut found = 0
for instr in instrs {
if instr == wanted {
found += 1
}
match instr {
@lib.Instruction::Block(_, @lib.Expr(body))
| @lib.Instruction::Loop(_, @lib.Expr(body)) =>
found += count(body, wanted)
@lib.Instruction::If(_, yes, no) => {
found += count(yes, wanted)
match no {
Some(body) => found += count(body, wanted)
None => ()
}
}
_ => ()
}
}
found
}
match optimized.code_sec {
Some(@lib.CodeSec([@lib.Func(_, @lib.Expr(body))])) => {
@debug.assert_eq(count(body, @lib.Instruction::i32_ne()), 0)
@debug.assert_eq(count(body, @lib.Instruction::i32_eq()), 1)
}
_ => fail("expected one constraint function")
}
match @validate.validate_module(optimized) {
Ok(_) => ()
Err(err) => fail("\{err}")
}
}
///|
test "Binaryen 132 dae2 optimizing command removes unused continuation allocation but retains binds" {
let profile = @validate.gen_valid_profile_by_name("dae2-continuations").unwrap()
for seed in [0UL, 1UL] {
let input = @validate.gen_valid_module_from_seed(
seed,
@validate.GenValidConfig::for_profile(profile),
).unwrap().wasm_module
let output = optimize_module_with_pass_flags(
input,
["dae2-optimizing"],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let funcs = output.code_sec.unwrap().0
let @lib.Func(_, @lib.Expr(body)) = funcs[funcs.length() - 1]
if seed == 0UL {
assert_false(body.any(fn(instr) { instr is @lib.ContNew(_) }))
} else {
assert_true(body.any(fn(instr) { instr is @lib.ContBind(_, _) }))
}
}
}
///|
test "Binaryen 132 nested descriptor branches retain observable writes through command dispatch" {
let profile = @validate.gen_valid_profile_by_name(
"binaryen132-descriptor-branches",
).unwrap()
for seed in [64UL, 65UL] {
let original = @validate.gen_valid_module_from_seed(
seed,
@validate.GenValidConfig::for_profile(profile),
).unwrap().wasm_module
for
pass in [
"ssa", "ssa-nomerge", "vacuum", "local-subtyping", "optimize-casts", "optimize-instructions",
"constraint-analysis", "dae2",
] {
let output = match
optimize_module_with_pass_flags(
original,
[pass],
OptimizeOptions::new(),
) {
Ok(output) => output
Err(err) => fail("descriptor command \{pass}: \{err}")
}
let decoded = @binary.decode_module(encode_module(output).unwrap()).unwrap()
assert_true(@validate.validate_module(decoded) is Ok(_))
let @lib.Func(_, @lib.Expr(body)) = decoded.code_sec.unwrap().0[0]
let work = [body]
let mut writes = 0
while !work.is_empty() {
for instr in work.pop().unwrap() {
match instr {
@lib.GlobalSet(_) => writes += 1
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
work.push(body)
@lib.If(_, yes, no) => {
work.push(yes)
if no is Some(body) {
work.push(body)
}
}
_ => ()
}
}
}
assert_eq(writes, 2, msg=pass)
}
}
}
///|
test "Binaryen 132 array memory arguments survive active pass dispatch" {
let source =
#|(module (type $A (array (mut i32)))
#| (func (export "run") (param (ref null $A) i32 i32) (result i32)
#| local.get 0 local.get 1 local.get 2 i32.store (type $A) offset=17 align=1
#| local.get 0 local.get 1 i32.load (type $A) offset=17 align=1))
let original = @wast.wast_to_binary_module(source).unwrap()
for
pass in [
"precompute", "precompute-propagate", "optimize-instructions", "local-subtyping",
"vacuum", "local-cse", "constraint-analysis", "dae2",
] {
let output = match
optimize_module_with_pass_flags(original, [pass], OptimizeOptions::new()) {
Ok(output) => output
Err(err) => fail("array command \{pass}: \{err}")
}
let bytes = encode_module(output).unwrap()
let decoded = @binary.decode_module(bytes).unwrap()
match @validate.validate_module(decoded) {
Ok(_) => ()
Err(err) => fail("array pass validation \{pass}: \{err}")
}
let @lib.CodeSec(bodies) = decoded.code_sec.unwrap()
let @lib.Func(_, @lib.Expr(body)) = bodies[0]
let mut accesses = 0
fn inspect_array_ops(body : Array[@lib.Instruction]) -> Unit raise {
for instr in body {
match instr {
@lib.ArrayLoad(_, _, ma) | @lib.ArrayStore(_, _, ma) => {
@debug.assert_eq(ma, @lib.MemArg::new(0U, None, 17UL))
accesses += 1
}
@lib.Block(_, @lib.Expr(nested)) => inspect_array_ops(nested)
_ => ()
}
}
}
inspect_array_ops(body)
@debug.assert_eq(accesses, 2, msg=pass)
}
}
///|
test "Binaryen 132 waitqueue operations survive active pass dispatch" {
let source =
#|(module (type $S (shared (struct (field (mut i32)))))
#| (func (export "run") (param (ref null $S)) (result i32)
#| (drop (waitqueue.notify (waitqueue.new) (i32.const 1)))
#| (struct.wait $S 0 (local.get 0) (waitqueue.new) (i32.const 0) (i64.const 0))))
let original = @wast.wast_to_binary_module(source).unwrap()
for
pass in [
"precompute", "precompute-propagate", "optimize-instructions", "local-subtyping",
"vacuum", "local-cse", "heap-store-optimization", "constraint-analysis", "dae2",
] {
let output = match
optimize_module_with_pass_flags(original, [pass], OptimizeOptions::new()) {
Ok(output) => output
Err(err) => fail("waitqueue command \{pass}: \{err}")
}
let decoded = @binary.decode_module(encode_module(output).unwrap()).unwrap()
match @validate.validate_module(decoded) {
Ok(_) => ()
Err(err) => fail("\{err}")
}
let mut allocations = 0
let mut synchronization = 0
fn inspect_ops(ops : Array[@lib.Instruction]) -> Unit {
for op in ops {
match op {
@lib.WaitqueueNew => allocations += 1
@lib.WaitqueueNotify | @lib.StructWait(_, _) => synchronization += 1
@lib.Block(_, @lib.Expr(body)) | @lib.Loop(_, @lib.Expr(body)) =>
inspect_ops(body)
_ => ()
}
}
}
let @lib.Func(_, @lib.Expr(body)) = decoded.code_sec.unwrap().0[0]
inspect_ops(body)
@debug.assert_eq((allocations, synchronization), (2, 2), msg=pass)
}
}
///|
test "Binaryen 132 command DAE2 prunes declaration-only signatures" {
let source = "(module (func $target (param i32) (result i32) (local.get 0)) (elem declare funcref (ref.func $target)) (func (export \"run\") (result i32) (i32.const 42)))"
let input = @wast.wast_to_binary_module(source).unwrap()
let output = optimize_module_with_pass_flags(
input,
["dae2"],
OptimizeOptions::new(),
).unwrap()
let index = match output.func_sec.unwrap().0[0] {
@lib.TypeIdx(index) => index
_ => fail("expected module type index")
}
let @lib.TypeSec(groups) = output.type_sec.unwrap()
let types : Array[@lib.SubType] = []
for group in groups {
match group {
@lib.SingleRecType(type_) => types.push(type_)
@lib.GroupRecType(group) => types.append(group)
}
}
@debug.assert_eq(
types[index.to_int()].get_comptype(),
@lib.CompType::func([], []),
)
assert_true(@validate.validate_module(output) is Ok(_))
}
///|
test "command DAE2 preserves signatures in exported abstract tables" {
let input = @passes.pass_test_module_from_wat(
(
#|(module
#| (type $sig (func (param i32) (result i32)))
#| (table (export "callbacks") 1 funcref)
#| (elem (i32.const 0) $target)
#| (func $target (type $sig) (param i32) (result i32) (local.get 0))
#| (func $direct (type $sig) (param i32) (result i32) (local.get 0))
#| (func (export "run") (result i32)
#| (drop (call_indirect (type $sig) (i32.const 42) (i32.const 0)))
#| (drop (call_ref (type $sig) (i32.const 43) (ref.func $target)))
#| (call $direct (i32.const 9))))
),
)
let output = match
optimize_module_with_pass_flags(
input,
["dae2"],
OptimizeOptions::new(closed_world=true),
) {
Ok(output) => output
Err(err) => fail("expected exported-table command success: \{err}")
}
assert_true(@validate.validate_module(output) is Ok(_))
@debug.assert_eq(
@validate.Env::new()
.with_module(output)
.get_functype_by_funcidx(@lib.FuncIdx::new(0U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
assert_true(
output.elem_sec
is Some(
@lib.ElemSec(
[
@lib.Elem(
@lib.Active(@lib.TableIdx(0U), _),
@lib.FuncsElemKind([@lib.FuncIdx(0U)])
),
]
)
),
)
}
///|
test "Binaryen 132 optimizing command keeps a later tee after conditional calls" {
let source =
#|(module
#| (import "env" "observe" (func (param i32 i32)))
#| (global (export "observed") (mut i32) (i32.const 0))
#| (func (export "run") (param i32 i32) (result i32) (local i32)
#| local.get 1
#| if
#| local.get 0 local.get 1 call 0
#| local.get 1 local.set 2
#| end
#| i32.const 3 i32.const 4 call 0
#| local.get 2 if local.get 2 global.set 0 end
#| i32.const 2 local.tee 0 local.get 0 i32.add))
let input = @wast.wast_to_binary_module(source).unwrap()
let output = optimize_module_with_pass_flags(
input,
["dae2-optimizing"],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
// Before the repair, lowering emitted the final constant/tee at entry.
@debug.assert_eq(body[0], @lib.Instruction::local_get(@lib.LocalIdx::new(1U)))
}
///|
test "Binaryen 132 optimizing command keeps a derived pointer after allocation" {
let source =
#|(module
#| (import "env" "alloc" (func (param i32) (result i32)))
#| (memory (export "memory") 1)
#| (func (export "run") (param i32) (result i32) (local i32 i32)
#| i32.const 8 local.get 0 i32.add call 0 local.set 1
#| local.get 1 i32.const 8 i32.add local.set 2
#| local.get 1 i32.const 4 i32.store align=1
#| local.get 2))
let input = @wast.wast_to_binary_module(source).unwrap()
let output = optimize_module_with_pass_flags(
input,
["dae2-optimizing"],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
let mut called = false
for instruction in body {
match instruction {
@lib.Call(_) => called = true
@lib.LocalGet(@lib.LocalIdx(1U)) => assert_true(called)
_ => ()
}
}
assert_true(called)
}
///|
test "Binaryen 132 simplify-locals command removes unused lowering scratch declarations" {
let source =
#|(module (memory 1)
#| (func (export "run") (param i32) (result i32) (local i32 i32)
#| memory.size local.tee 1
#| i32.const 0 i32.load local.get 1 i32.add i32.add))
let input = @wast.wast_to_binary_module(source).unwrap()
let output = optimize_module_with_pass_flags(
input,
["simplify-locals"],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let @lib.Func(locals, _) = output.code_sec.unwrap().0[0]
@debug.assert_eq(locals.length(), 2)
}
///|
test "Binaryen 132 DAE2 command reuses an existing pruned signature" {
let source =
#|(module
#| (func (export "identity") (param i32) (result i32) local.get 0)
#| (func (param i32 i32) (result i32) local.get 0)
#| (func (export "run") (param i32) (result i32)
#| local.get 0 i32.const 7 call 1))
let input = @wast.wast_to_binary_module(source).unwrap()
let output = optimize_module_with_pass_flags(
input,
["dae2"],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let @lib.FuncSec(indices) = output.func_sec.unwrap()
@debug.assert_eq(indices[1], indices[0])
}
///|
test "Binaryen 132 simplify-locals command folds an adjacent allocation capture" {
let source =
#|(module
#| (import "env" "alloc" (func (param i32) (result i32)))
#| (memory 1)
#| (func (export "run") (param i32) (result i32) (local i32 i32)
#| i32.const 8 local.get 0 i32.add call 0 local.set 1
#| local.get 1 i32.const 8 i32.add local.set 2
#| local.get 1 i32.const 4 i32.store align=1 local.get 2))
let input = @wast.wast_to_binary_module(source).unwrap()
let output = optimize_module_with_pass_flags(
input,
["simplify-locals"],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
@debug.assert_eq(body[3], @lib.Instruction::call(@lib.FuncIdx::new(0U)))
@debug.assert_eq(body[4], @lib.Instruction::local_tee(@lib.LocalIdx::new(1U)))
}
///|
test "ssa-nomerge command reads the write that exits an enclosing block" {
let source =
#|(module
#| (func (export "main") (result i32) (local i32 i32)
#| i32.const 0 local.set 0
#| i32.const 0 local.set 1
#| block
#| loop
#| block
#| local.get 1 i32.const 3 i32.lt_s
#| if
#| local.get 1 i32.const 1 i32.add local.set 1
#| br 2
#| br 1
#| end
#| local.get 1 i32.const 3 i32.eq
#| if
#| local.get 1 local.set 0
#| br 3
#| br 1
#| end
#| unreachable
#| nop
#| end
#| unreachable
#| end
#| unreachable
#| end
#| local.get 0))
let input = @wast.wast_to_binary_module(source).unwrap()
let output = optimize_module_with_pass_flags(
input,
["ssa-nomerge"],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
// Local 2 holds the initial zero; local 3 holds the exiting write of three.
@debug.assert_eq(
body[body.length() - 1],
@lib.Instruction::local_get(@lib.LocalIdx::new(3U)),
)
}
///|
test "dae2 command dispatch preserves implicit function-label payloads" {
for results in [[], [@lib.ValType::i32()]] {
let input = @lib.Module::new()
.with_type_sec(
@lib.TypeSec::new([
@lib.RecType::new(
@lib.SubType::comp_type(@lib.CompType::func([], results)),
),
]),
)
.with_func_sec(@lib.FuncSec::new([@lib.TypeIdx::new(0U)]))
.with_export_sec(
@lib.ExportSec::new([
@lib.Export::new(
@lib.Name::new("run"),
@lib.ExternIdx::func(@lib.FuncIdx::new(0U)),
),
]),
)
let body = if results.is_empty() {
[@lib.Instruction::br(@lib.LabelIdx::new(0U))]
} else {
[
@lib.Instruction::i32_const(@lib.I32(42)),
@lib.Instruction::br(@lib.LabelIdx::new(0U)),
]
}
let input = input.with_code_sec(
@lib.CodeSec::new([
@lib.Func::new(@lib.Locals::empty(), @lib.Expr::new(body)),
]),
)
for pass in ["dae2", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[pass],
OptimizeOptions::new(),
).unwrap()
assert_true(@validate.validate_module(output) is Ok(_))
@debug.assert_eq(
@validate.Env::new()
.with_module(output)
.get_functype_by_funcidx(@lib.FuncIdx::new(0U)),
Some(@lib.FuncType::new([], results)),
)
}
}
}
///|
test "deep audit command string gathering nonnullable result existing False" {
let nonnull = @lib.ValType::ref_type(
@lib.RefType::new(false, @lib.HeapType::abs(@lib.AbsHeapType::string_())),
)
let input = @passes.pass_test_module_from_wat(
"(module (func (result stringref) (string.const \"hello\")))",
)
let input = input.with_type_sec(
@lib.TypeSec::new([
@lib.RecType::new(
@lib.SubType::comp_type(@lib.CompType::func([], [nonnull])),
),
]),
)
let output = cmd_deep_audit_run_module(input, "string-gathering")
assert_true(@validate.validate_module(output) is Ok(_))
}
///|
test "deep audit command simplify globals resolves signatures after multi member recursive groups" {
let output = cmd_deep_audit_run(
(
#|(module (rec (type (func)) (type (func (param i64))))
#| (type $host (func (param i32) (result i32 i32)))
#| (type (func (result i32)))
#| (import "env" "host" (func $host (type $host)))
#| (global $g (mut i32) (i32.const 0))
#| (func (export "run") global.get $g call $host i32.const 0 select
#| if i32.const 1 global.set $g end))
),
"simplify-globals-optimizing",
)
@debug.assert_eq(
cmd_deep_audit_count(cmd_deep_audit_body(output), fn(i) {
i is @lib.GlobalSet(_)
}),
1,
)
}
///|
test "deep audit command coalesce-locals keeps default local value on nullable branch before sole tee" {
let pass = "coalesce-locals"
let output = cmd_deep_audit_run(
(
#|(module (func (param $p i32) (param $r externref) (result i32) (local $t i32)
#| (block local.get $r br_on_null 0 drop
#| i32.const 7 local.tee $t i32.const 1 i32.add drop)
#| local.get $t))
),
pass,
)
let @lib.Func(locals, _) = output.code_sec.unwrap().0[0]
// The null edge reads the default zero, which cannot alias arbitrary $p.
assert_true(locals.length() > 0)
}
///|
test "deep audit command once reduction preserves call skipped by br_on_cast_desc_eq" {
let output = cmd_deep_audit_run(
(
#|(module (rec (type $s (sub (descriptor $d) (struct)))
#| (type $d (sub (describes $s) (struct))))
#| (global $desc (ref (exact $d)) (struct.new $d))
#| (global $obj (ref $s) (struct.new_default_desc $s (global.get $desc)))
#| (global $flag (mut i32) (i32.const 0))
#| (global $hits (mut i32) (i32.const 0))
#| (func $once global.get $flag if return end i32.const 1 global.set $flag
#| (global.set $hits (i32.add (global.get $hits) (i32.const 1))))
#| (func (export "run") (result i32)
#| (drop (block $exit (result (ref null $s))
#| (br_on_cast_desc_eq $exit (ref null $s) (ref $s) (global.get $obj) (global.get $desc))
#| (call $once))) (call $once) (global.get $hits)))
),
"once-reduction",
)
@debug.assert_eq(
cmd_deep_audit_count(cmd_deep_audit_body(output, index=1), fn(i) {
i is @lib.Call(_)
}),
2,
)
}
///|
test "deep audit command precompute-propagate retains struct receiver global.set" {
let output = cmd_deep_audit_run(
"(module (type $s (struct (field i32))) (type $a (array i32)) (import \"env\" \"effect\" (func $effect)) (global (export \"g\") (mut i32) (i32.const 0)) (func (result i32) (struct.get $s 0 (block (result (ref $s)) (global.set 0 (i32.const 1)) (struct.new $s (i32.const 7))))))",
"precompute-propagate",
)
@debug.assert_eq(
cmd_deep_audit_count(cmd_deep_audit_body(output), fn(i) {
i is @lib.GlobalSet(_)
}),
1,
)
}
///|
test "deep audit command precompute rounds i64 s exp 54 neighbor 1 negative False" {
let output = cmd_deep_audit_run(
"(module (func (result i32) i64.const 18014399583223809 f32.convert_i64_s i32.reinterpret_f32))",
"precompute",
)
assert_true(
cmd_deep_audit_body(output).contains(
@lib.Instruction::i32_const(@lib.I32(1518338049)),
),
)
}
///|
test "deep audit command precompute preserves exact ref.null global type" {
let output = cmd_deep_audit_run(
(
#|(module
#| (type $box (struct))
#| (global $value (ref null (exact $box)) (ref.null (exact $box)))
#| (func (export "read") (result (ref null (exact $box)))
#| (global.get $value)))
#|
),
"precompute",
)
assert_true(@validate.validate_module(output) is Ok(_))
match cmd_deep_audit_body(output) {
[@lib.RefNull(ref_type)] => assert_true(ref_type.is_exact())
body => fail("expected one folded exact ref.null, got \{body}")
}
}
///|
test "deep audit command precompute allocation resource boundary" {
let array_type = @lib.TypeIdx::new(0U)
let func_type = @lib.TypeIdx::new(1U)
let input = @lib.Module::new()
.with_type_sec(
@lib.TypeSec::new([
@lib.RecType::new(
@lib.SubType::comp_type(
@lib.CompType::array(
@lib.FieldType::new(
@lib.StorageType::val_type(@lib.ValType::i32()),
@lib.Mut::const_(),
),
),
),
),
@lib.RecType::new(
@lib.SubType::comp_type(
@lib.CompType::func([], [@lib.ValType::i32()]),
),
),
]),
)
.with_func_sec(@lib.FuncSec::new([func_type, func_type]))
.with_code_sec(
@lib.CodeSec::new([
@lib.Func::new(
@lib.Locals::empty(),
@lib.Expr::new([
@lib.Instruction::i32_const(@lib.I32(16)),
@lib.Instruction::array_new_default(array_type),
@lib.Instruction::ref_is_null(),
]),
),
@lib.Func::new(
@lib.Locals::empty(),
@lib.Expr::new([
@lib.Instruction::i32_const(@lib.I32(-1)),
@lib.Instruction::array_new_default(array_type),
@lib.Instruction::ref_is_null(),
]),
),
]),
)
for pass in ["precompute", "precompute-propagate"] {
let output = cmd_deep_audit_run_module(input, pass)
match @validate.validate_module(output) {
Ok(_) => ()
Err(err) => fail("invalid allocation resource boundary output: \{err}")
}
let finite = cmd_deep_audit_body(output)
let oversized = cmd_deep_audit_body(output, index=1)
@debug.assert_eq(
cmd_deep_audit_count(finite, fn(i) {
i is @lib.ArrayNewDefault(_) || i is @lib.RefIsNull
}),
0,
)
@debug.assert_eq(
cmd_deep_audit_count(finite, fn(i) { i is @lib.I32Const(@lib.I32(0)) }),
1,
)
@debug.assert_eq(
cmd_deep_audit_count(oversized, fn(i) { i is @lib.ArrayNewDefault(_) }),
1,
)
@debug.assert_eq(
cmd_deep_audit_count(oversized, fn(i) { i is @lib.RefIsNull }),
1,
)
}
}
///|
test "deep audit command memory packing shared passive split" {
let wat =
#| (module
#| (memory 1 1 shared)
#| (data "A\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00B")
#| (func (export "run")
#| i32.const 0 i32.const 0 i32.const 34 memory.init 0))
#|
let output = cmd_deep_audit_run(wat, "memory-packing")
match @validate.validate_module(output) {
Ok(_) => ()
Err(err) => fail("invalid shared passive split output: \{err}")
}
let @lib.DataSec(datas) = output.data_sec.unwrap()
assert_true(datas.length() > 1)
assert_true(
cmd_deep_audit_count(cmd_deep_audit_body(output), fn(i) {
i is @lib.MemoryFill(_)
}) >
0,
)
}
///|
test "deep audit command memory packing preserves atomic bounds failure for imported active segment" {
let input = @passes.pass_test_module_from_wat(
(
#|(module (import "env" "memory" (memory 1 1))
#| (data (i32.const 65520) "A\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00"))
),
)
let output = optimize_module_with_pass_flags(
input,
["memory-packing"],
OptimizeOptions::new(zero_filled_memory=true),
).unwrap()
// The entire segment is bounds-checked before any byte is written.
@debug.assert_eq(output.data_sec, input.data_sec)
}
///|
test "deep audit command optimize instructions retains copies using distinct shared i32.atomic.load" {
let load = "i32.atomic.load"
let input = @passes.pass_test_module_from_wat(
"(module (memory 1 1 shared) (func (param i32 i32) local.get 0 " +
load +
" local.get 0 " +
load +
" local.get 1 memory.copy))",
)
let output = optimize_module_with_pass_flags(
input,
["optimize-instructions"],
OptimizeOptions::new(traps_never_happen=true),
).unwrap()
// Concurrent stores can change the address between the two reads.
@debug.assert_eq(
cmd_deep_audit_count(cmd_deep_audit_body(output), fn(i) {
i is @lib.MemoryCopy(_, _)
}),
1,
)
}
///|
test "deep audit command optimize instructions preserves shared identity atomic RMW order" {
for order in ["acq_rel", "relaxed"] {
let output = cmd_deep_audit_run(
"(module (type $S (shared (struct (field (mut i32))))) (func (param (ref null $S)) (result i32) (struct.atomic.rmw.add \{order} \{order} $S 0 (local.get 0) (i32.const 0))))",
"optimize-instructions",
)
let expected = if order == "acq_rel" {
@lib.AtomicOrder::acq_rel()
} else {
@lib.AtomicOrder::relaxed()
}
@debug.assert_eq(
cmd_deep_audit_count(cmd_deep_audit_body(output), fn(instr) {
match instr {
@lib.StructAtomicRmw(actual, @lib.AggregateAtomicRmwAdd, _, _) =>
actual == expected
_ => false
}
}),
1,
)
}
}
///|
test "deep audit command optimize instructions preserves shared same value cmpxchg order" {
for order in ["acq_rel", "relaxed"] {
let output = cmd_deep_audit_run(
"(module (type $S (shared (struct (field (mut i32))))) (func (param (ref null $S)) (result i32) (struct.atomic.rmw.cmpxchg \{order} \{order} $S 0 (local.get 0) (i32.const 7) (i32.const 7))))",
"optimize-instructions",
)
let expected = if order == "acq_rel" {
@lib.AtomicOrder::acq_rel()
} else {
@lib.AtomicOrder::relaxed()
}
@debug.assert_eq(
cmd_deep_audit_count(cmd_deep_audit_body(output), fn(instr) {
match instr {
@lib.StructAtomicCmpxchg(actual, _, _) => actual == expected
_ => false
}
}),
1,
)
}
}
///|
test "deep audit command HSO preserves fresh shared struct publication stores" {
let shared_struct = @lib.RecType::new(
@lib.SubType::comp_type(
@lib.CompType::struct_([
@lib.FieldType::new(
@lib.StorageType::val_type(@lib.ValType::i32()),
@lib.Mut::var_(),
),
]),
metadata=@lib.TypeMetadata::new(shared=true),
),
)
let shared_ref = @lib.ValType::ref_type(
@lib.RefType::new(true, @lib.HeapType::new(@lib.TypeIdx::new(0U))),
)
let publish_func = @lib.RecType::new(
@lib.SubType::comp_type(@lib.CompType::func([], [shared_ref])),
)
for
order in [
@lib.AtomicOrder::acq_rel(),
@lib.AtomicOrder::seq_cst(),
@lib.AtomicOrder::relaxed(),
] {
let input = @lib.Module::new()
.with_type_sec(@lib.TypeSec::new([shared_struct, publish_func]))
.with_func_sec(@lib.FuncSec::new([@lib.TypeIdx::new(1U)]))
.with_export_sec(
@lib.ExportSec::new([
@lib.Export::new(
@lib.Name::new("publish"),
@lib.ExternIdx::func(@lib.FuncIdx::new(0U)),
),
]),
)
.with_code_sec(
@lib.CodeSec::new([
@lib.Func::new(
@lib.Locals::from_types([shared_ref]),
@lib.Expr::new([
@lib.Instruction::i32_const(@lib.I32(0)),
@lib.Instruction::struct_new(@lib.TypeIdx::new(0U)),
@lib.Instruction::local_set(@lib.LocalIdx::new(0U)),
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::i32_const(@lib.I32(42)),
@lib.Instruction::struct_atomic_set(
order,
@lib.TypeIdx::new(0U),
@lib.U32(0U),
),
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::struct_atomic_get(
@lib.AtomicOrder::relaxed(),
@lib.TypeIdx::new(0U),
@lib.U32(0U),
),
@lib.Instruction::drop(),
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
]),
),
]),
)
let output = cmd_deep_audit_run_module(input, "heap-store-optimization")
@debug.assert_eq(
cmd_deep_audit_count(cmd_deep_audit_body(output), fn(instr) {
match instr {
@lib.StructAtomicSet(actual, _, _) => actual == order
_ => false
}
}),
if order == @lib.AtomicOrder::relaxed() {
0
} else {
1
},
)
assert_true(@validate.validate_module(output) is Ok(_))
}
}
///|
test "deep audit command merge-similar-functions honors disabled tail-call metadata" {
let input = @passes.pass_test_module_from_wat(
(
#|(module
#| (global (mut i32) (i32.const 0))
#| (func (result i32)
#| global.get 0 i32.const 1 i32.add global.set 0
#| global.get 0 i32.const 1 i32.add global.set 0
#| global.get 0 i32.const 1 i32.add global.set 0
#| global.get 0 i32.const 1 i32.add global.set 0
#| i32.const 42)
#| (func (result i32)
#| global.get 0 i32.const 1 i32.add global.set 0
#| global.get 0 i32.const 1 i32.add global.set 0
#| global.get 0 i32.const 1 i32.add global.set 0
#| global.get 0 i32.const 1 i32.add global.set 0
#| i32.const 43))
#|
),
).with_custom_secs([
@lib.CustomSec::new(
@lib.Name::new("target_features"),
Bytes::from_array([
1, 0x2d, 9, 0x74, 0x61, 0x69, 0x6c, 0x2d, 0x63, 0x61, 0x6c, 0x6c,
]),
),
])
assert_true(@validate.validate_module(input) is Ok(_))
let output = match
optimize_module_with_pass_flags(
input,
["merge-similar-functions"],
OptimizeOptions::new(),
) {
Ok(mod_) => mod_
Err(err) => fail("expected metadata-compatible MSF dispatch: \{err}")
}
@debug.assert_eq(output.custom_secs, input.custom_secs)
assert_true(@validate.validate_module(output) is Ok(_))
match output.code_sec {
Some(@lib.CodeSec([first, second, _])) =>
for func in [first, second] {
let @lib.Func(_, @lib.Expr(body)) = func
assert_true(body.last() is Some(@lib.Call(_)), msg=body.to_string())
assert_false(
body.any(fn(instr) { instr is @lib.ReturnCall(_) }),
msg=body.to_string(),
)
}
other => fail("expected dispatched MSF helper, got \{other}")
}
}
///|
test "deep audit command optimize casts folds canonical nullable-null descriptor cast" {
let output = cmd_deep_audit_run(
(
#|(module
#| (rec
#| (type $s (descriptor $d) (struct))
#| (type $d (describes $s) (struct)))
#| (import "env" "source" (func $source))
#| (import "env" "descriptor" (func $descriptor (result (ref $d))))
#| (func (result (ref null $s))
#| (block (result (ref null $s))
#| call $source
#| ref.null $s)
#| call $descriptor
#| ref.cast_desc_eq_null $s))
),
"optimize-casts",
)
@debug.assert_eq(
cmd_deep_audit_count(cmd_deep_audit_body(output), fn(i) {
i is @lib.RefCastDescEq(_, _, _)
}),
0,
)
@debug.assert_eq(
cmd_deep_audit_count(cmd_deep_audit_body(output), fn(i) {
i == @lib.Instruction::call(@lib.FuncIdx::new(0U))
}),
1,
)
@debug.assert_eq(
cmd_deep_audit_count(cmd_deep_audit_body(output), fn(i) {
i == @lib.Instruction::call(@lib.FuncIdx::new(1U))
}),
1,
)
}
///|
test "deep audit command flatten retains continuation handler target block" {
let input = @passes.pass_test_module_from_wat(
"(module (type $sig (func (param i32) (result i32))) (type $cont (func (param i64))) (type $tag (func (result i32))) (tag (type $tag)) (func (export \"run\") (param (ref null $cont)) (result i32) unreachable))",
)
let types = input.type_sec.unwrap().0.copy()
types[1] = @lib.RecType::new(
@lib.SubType::comp_type(@lib.CompType::cont(@lib.TypeIdx::new(0U))),
)
let funcs = input.code_sec.unwrap().0.copy()
funcs[0] = @lib.Func::new(
@lib.Locals::empty(),
@lib.Expr::new([
@lib.Instruction::block(
@lib.BlockType::val_type(
@lib.ValType::ref_null(@lib.HeapType::new(@lib.TypeIdx::new(1U))),
),
@lib.Expr::new([
@lib.Instruction::i32_const(@lib.I32(0)),
@lib.Instruction::if_(
@lib.BlockType::void_(),
[
@lib.Instruction::ref_null(
@lib.HeapType::new(@lib.TypeIdx::new(1U)),
),
@lib.Instruction::br(@lib.LabelIdx::new(1U)),
],
None,
),
@lib.Instruction::i32_const(@lib.I32(7)),
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::resume_(@lib.TypeIdx::new(1U), [
@lib.ResumeHandler::on_label(
@lib.TagIdx::new(0U),
@lib.LabelIdx::new(0U),
),
]),
@lib.Instruction::drop(),
@lib.Instruction::ref_null(@lib.HeapType::new(@lib.TypeIdx::new(1U))),
]),
),
@lib.Instruction::drop(),
@lib.Instruction::unreachable_(),
]),
)
let input = input
.with_type_sec(@lib.TypeSec::new(types))
.with_code_sec(@lib.CodeSec::new(funcs))
assert_true(@validate.validate_module(input) is Ok(_))
let output = optimize_module_with_pass_flags(
input,
["flatten"],
OptimizeOptions::new(),
).unwrap()
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_true(body.any(fn(instr) { instr is @lib.Block(_, _) }))
@debug.assert_eq(
cmd_deep_audit_count(body, fn(instr) { instr is @lib.Resume(_, _) }),
1,
)
assert_true(@validate.validate_module(output) is Ok(_))
}
///|
test "deep audit command remove-unused-module-elements retains active global reference closed false" {
let input = cmd_deep_audit_rume_input("active")
assert_true(@validate.validate_module(input) is Ok(_))
let output = match
optimize_module_with_pass_flags(
input,
["remove-unused-module-elements"],
OptimizeOptions::new(closed_world=false),
) {
Ok(output) => output
Err(err) => fail("expected valid RUME output: \{err}")
}
assert_true(@validate.validate_module(output) is Ok(_))
assert_true(output.global_sec is Some(@lib.GlobalSec([_, ..])))
assert_true(output.code_sec is Some(@lib.CodeSec([_, _, ..])))
}
///|
test "deep audit command global struct inference keeps imported nominal origins" {
let wat =
#| (module
#| (type $box (sub (struct (field i32))))
#| (import "env" "external" (global $external (ref $box)))
#| (global $internal (ref $box) (struct.new $box (i32.const 7)))
#| (func $read (param (ref $box)) (result i32)
#| local.get 0
#| struct.get $box 0)
#| (func (export "run") (result i32)
#| global.get $external
#| call $read))
#|
let input = @passes.pass_test_module_from_wat(wat)
let output = optimize_module_with_pass_flags(
input,
["global-struct-inference"],
OptimizeOptions::new(closed_world=true),
).unwrap()
let read = cmd_deep_audit_body(output)
@debug.assert_eq(
cmd_deep_audit_count(read, fn(i) { i is @lib.LocalGet(@lib.LocalIdx(0U)) }),
1,
)
@debug.assert_eq(
cmd_deep_audit_count(read, fn(i) { i is @lib.GlobalGet(_) }),
0,
)
}
///|
test "deep audit command global struct inference keeps exported nominal params" {
let wat =
#| (module
#| (type $box (sub (struct (field i32))))
#| (global $internal (ref $box) (struct.new $box (i32.const 7)))
#| (func (export "read") (param (ref $box)) (result i32)
#| local.get 0
#| struct.get $box 0))
#|
let input = @passes.pass_test_module_from_wat(wat)
let output = optimize_module_with_pass_flags(
input,
["global-struct-inference"],
OptimizeOptions::new(closed_world=true),
).unwrap()
let read = cmd_deep_audit_body(output)
@debug.assert_eq(
cmd_deep_audit_count(read, fn(i) { i is @lib.LocalGet(@lib.LocalIdx(0U)) }),
1,
)
@debug.assert_eq(
cmd_deep_audit_count(read, fn(i) { i is @lib.GlobalGet(_) }),
0,
)
}
///|
test "deep audit command optimize casts follows validator abstract heap lattice" {
let source_ref = @lib.ValType::ref_type(
@lib.RefType::new(true, @lib.HeapType::abs(@lib.AbsHeapType::none())),
)
let func_type = @lib.RecType::new(
@lib.SubType::comp_type(
@lib.CompType::func([source_ref], [@lib.ValType::i32()]),
),
)
let input = @lib.Module::new()
.with_type_sec(@lib.TypeSec::new([func_type]))
.with_func_sec(@lib.FuncSec::new([@lib.TypeIdx::new(0U)]))
.with_code_sec(
@lib.CodeSec::new([
@lib.Func::new(
@lib.Locals::empty(),
@lib.Expr::new([
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::ref_test(
true,
@lib.HeapType::abs(@lib.AbsHeapType::i31()),
),
]),
),
]),
)
let output = cmd_deep_audit_run_module(input, "optimize-casts")
let body = cmd_deep_audit_body(output)
@debug.assert_eq(
cmd_deep_audit_count(body, fn(i) { i is @lib.RefTest(_, _, _) }),
0,
)
@debug.assert_eq(
cmd_deep_audit_count(body, fn(i) { i is @lib.I32Const(@lib.I32(1)) }),
1,
)
}
///|
test "deep audit command global struct inference keeps exported table callable params" {
let wat =
#| (module
#| (type $box (sub (struct (field i32))))
#| (global $internal (ref $box) (struct.new $box (i32.const 7)))
#| (table (export "readers") 1 funcref)
#| (elem (i32.const 0) func $read)
#| (func $read (param (ref $box)) (result i32)
#| local.get 0
#| struct.get $box 0))
#|
let input = @passes.pass_test_module_from_wat(wat)
let output = optimize_module_with_pass_flags(
input,
["global-struct-inference"],
OptimizeOptions::new(closed_world=true),
).unwrap()
let read = cmd_deep_audit_body(output)
@debug.assert_eq(
cmd_deep_audit_count(read, fn(i) { i is @lib.LocalGet(@lib.LocalIdx(0U)) }),
1,
)
@debug.assert_eq(
cmd_deep_audit_count(read, fn(i) { i is @lib.GlobalGet(_) }),
0,
)
}
///|
test "deep audit command heap2local intentionally keeps missing subtype field trap" {
let base = @lib.TypeIdx::new(0U)
let sub = @lib.TypeIdx::new(1U)
let owner = @lib.LocalIdx::new(1U)
let input = @passes.pass_test_module_from_wat(
"(module (type $base (sub (struct (field i32)))) (type $sub (sub $base (struct (field i32) (field i32)))) (func (param i32) (result i32) unreachable))",
).with_code_sec(
@lib.CodeSec::new([
@lib.Func::new(
@lib.Locals::from_types([
@lib.ValType::ref_type(
@lib.RefType::new(true, @lib.HeapType::new(base)),
),
]),
@lib.Expr::new([
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::if_(
@lib.BlockType::val_type(
@lib.ValType::ref_type(
@lib.RefType::new(false, @lib.HeapType::new(base)),
),
),
[
@lib.Instruction::i32_const(@lib.I32(11)),
@lib.Instruction::i32_const(@lib.I32(22)),
@lib.Instruction::struct_new(sub),
],
Some([
@lib.Instruction::i32_const(@lib.I32(33)),
@lib.Instruction::struct_new(base),
]),
),
@lib.Instruction::local_set(owner),
@lib.Instruction::local_get(owner),
@lib.Instruction::ref_cast(false, @lib.HeapType::new(sub)),
@lib.Instruction::struct_get(sub, @lib.U32(1U)),
]),
),
]),
)
let output = optimize_module_with_pass_flags(
input,
["heap2local"],
OptimizeOptions::new(),
).unwrap()
assert_true(@validate.validate_module(output) is Ok(_))
let body = cmd_deep_audit_body(output)
@debug.assert_eq(
cmd_deep_audit_count(body, fn(i) { i is @lib.StructNew(_) }),
2,
)
@debug.assert_eq(
cmd_deep_audit_count(body, fn(i) { i is @lib.RefCast(_, _, _) }),
1,
)
@debug.assert_eq(
cmd_deep_audit_count(body, fn(i) { i is @lib.StructGet(_, _) }),
1,
)
}
///|
test "deep audit command local cse materializes repeated nominal cast result" {
let base = @lib.TypeIdx::new(0U)
let subtype = @lib.TypeIdx::new(1U)
let function_type = @lib.TypeIdx::new(2U)
let field = @lib.FieldType::new(
@lib.StorageType::val_type(@lib.ValType::i32()),
@lib.Mut::var_(),
)
let nullable_base = @lib.ValType::ref_type(
@lib.RefType::new(true, @lib.HeapType::new(base)),
)
let nonnull_subtype = @lib.ValType::ref_type(
@lib.RefType::new(false, @lib.HeapType::new(subtype)),
)
let input = @lib.Module::new()
.with_type_sec(
@lib.TypeSec::new([
@lib.RecType::new(
@lib.SubType::new(false, [], @lib.CompType::struct_([field])),
),
@lib.RecType::new(
@lib.SubType::new(
false,
[base],
@lib.CompType::struct_([field, field]),
),
),
@lib.RecType::new(
@lib.SubType::comp_type(
@lib.CompType::func([], [@lib.ValType::i32()]),
),
),
]),
)
.with_func_sec(@lib.FuncSec::new([function_type]))
.with_code_sec(
@lib.CodeSec::new([
@lib.Func::new(
@lib.Locals::from_types([nullable_base]),
@lib.Expr::new([
@lib.Instruction::i32_const(@lib.I32(23955)),
@lib.Instruction::i32_const(@lib.I32(12715)),
@lib.Instruction::struct_new(subtype),
@lib.Instruction::local_set(@lib.LocalIdx::new(0U)),
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::ref_cast(false, @lib.HeapType::new(subtype)),
@lib.Instruction::i32_const(@lib.I32(22289)),
@lib.Instruction::struct_set(subtype, @lib.U32(1U)),
@lib.Instruction::local_get(@lib.LocalIdx::new(0U)),
@lib.Instruction::ref_cast(false, @lib.HeapType::new(subtype)),
@lib.Instruction::struct_get(subtype, @lib.U32(1U)),
]),
),
]),
)
let output = cmd_deep_audit_run_module(input, "local-cse")
let @lib.Func(locals, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
@debug.assert_eq(
locals,
@lib.Locals::from_types([nullable_base, nonnull_subtype]),
)
@debug.assert_eq(
cmd_deep_audit_count(body, fn(instr) {
match instr {
@lib.RefCast(false, false, target) =>
target == @lib.HeapType::new(subtype)
_ => false
}
}),
1,
)
@debug.assert_eq(
cmd_deep_audit_count(body, fn(instr) {
instr is @lib.LocalTee(@lib.LocalIdx(1U))
}),
1,
)
@debug.assert_eq(
cmd_deep_audit_count(body, fn(instr) {
instr is @lib.LocalGet(@lib.LocalIdx(1U))
}),
1,
)
}
///|
fn cmd_deep_audit_run(wat : String, pass : String) -> @lib.Module raise {
let input = match @wast.wast_to_binary_module(wat, filename="audit.wat") {
Ok(value) => value
Err(err) => fail("invalid audit syntax: \{err}")
}
match @validate.validate_module(input) {
Ok(_) => ()
Err(err) => fail("invalid audit input: \{err}")
}
match optimize_module_with_pass_flags(input, [pass], OptimizeOptions::new()) {
Ok(value) => value
Err(err) => fail("audit pipeline: \{err}")
}
}
///|
fn cmd_deep_audit_body(
mod_ : @lib.Module,
index~ : Int32 = 0,
) -> Array[@lib.Instruction] {
let @lib.Func(_, @lib.Expr(body)) = mod_.code_sec.unwrap().0[index]
body
}
///|
fn cmd_deep_audit_count(
body : Array[@lib.Instruction],
predicate : (@lib.Instruction) -> Bool,
) -> Int32 {
let mut count = 0
for instr in body {
if predicate(instr) {
count += 1
}
match instr {
@lib.Block(_, @lib.Expr(nested))
| @lib.Loop(_, @lib.Expr(nested))
| @lib.TryTable(_, _, @lib.Expr(nested)) =>
count += cmd_deep_audit_count(nested, predicate)
@lib.If(_, yes, no) => {
count += cmd_deep_audit_count(yes, predicate)
count += cmd_deep_audit_count(no.unwrap_or([]), predicate)
}
@lib.Try(_, @lib.Expr(nested), catches, _) => {
count += cmd_deep_audit_count(nested, predicate)
for catch_ in catches {
let body = match catch_ {
@lib.LegacyCatch(_, @lib.Expr(body))
| @lib.LegacyCatchAll(@lib.Expr(body)) => body
}
count += cmd_deep_audit_count(body, predicate)
}
}
_ => ()
}
}
count
}
///|
fn cmd_deep_audit_run_module(
input : @lib.Module,
pass : String,
) -> @lib.Module raise {
match @validate.validate_module(input) {
Ok(_) => ()
Err(err) => fail("invalid audit IR: \{err}")
}
match optimize_module_with_pass_flags(input, [pass], OptimizeOptions::new()) {
Ok(value) => value
Err(err) => fail("audit IR pipeline: \{err}")
}
}
///|
fn cmd_deep_audit_rume_input(kind : String) -> @lib.Module {
if kind == "table" {
let base = @passes.pass_test_module_from_wat(
"(module (type $f (func (result i32))) (import \"env\" \"target\" (global funcref)) (table 1 funcref) (func (export \"run\") (result i32) i32.const 0 call_indirect (type $f)))",
)
return base.with_table_sec(
@lib.TableSec::new([
@lib.Table::new(
@lib.TableType::new(
@lib.RefType::abs(@lib.AbsHeapType::func()),
@lib.Limits::i32(1U, None),
),
Some(
@lib.Expr::new([
@lib.Instruction::global_get(@lib.GlobalIdx::new(0U)),
]),
),
),
]),
)
}
let base = @passes.pass_test_module_from_wat(
"(module (type $f (func (result i32))) (type $a (array (mut funcref))) (table 1 funcref) (func $target (type $f) i32.const 42) (func (export \"run\") (result i32) i32.const 0 call_indirect (type $f)))",
)
let get = @lib.Expr::new([
@lib.Instruction::global_get(@lib.GlobalIdx::new(0U)),
])
let func_ref = @lib.Expr::new([
@lib.Instruction::ref_func(@lib.FuncIdx::new(0U)),
])
let base = base.with_global_sec(
@lib.GlobalSec::new([
@lib.Global::new(
@lib.GlobalType::new(@lib.ValType::funcref(), false),
func_ref,
),
]),
)
let declarations = @lib.Elem::new(
@lib.ElemMode::declarative(),
@lib.ElemKind::funcs([@lib.FuncIdx::new(0U)]),
)
let mode = if kind == "active" {
@lib.ElemMode::active(
@lib.TableIdx::new(0U),
@lib.Expr::new([@lib.Instruction::i32_const(@lib.I32(0))]),
)
} else {
@lib.ElemMode::passive()
}
let base = base.with_elem_sec(
@lib.ElemSec::new([
@lib.Elem::new(
mode,
@lib.ElemKind::typed_exprs(
@lib.RefType::abs(@lib.AbsHeapType::func()),
[get],
),
),
declarations,
]),
)
if kind == "passive" {
let funcs = base.code_sec.unwrap().0.copy()
funcs[1] = @lib.Func::new(
@lib.Locals::empty(),
@lib.Expr::new([
@lib.Instruction::i32_const(@lib.I32(0)),
@lib.Instruction::i32_const(@lib.I32(1)),
@lib.Instruction::array_new_elem(
@lib.TypeIdx::new(1U),
@lib.ElemIdx::new(0U),
),
@lib.Instruction::array_len(),
]),
)
base.with_code_sec(@lib.CodeSec::new(funcs))
} else {
base
}
}
///|
test "full O4z command presets merge private helpers across host boundaries" {
let input = @passes.pass_test_module_from_wat(
(
#|(module
#| (import "host" "tick" (func $tick (param i32) (result i32)))
#| (func $first (param i32) (result i32) local.get 0 call $tick local.get 0 i32.mul local.get 0 i32.add)
#| (func $second (param i32) (result i32) local.get 0 call $tick local.get 0 i32.mul local.get 0 i32.add)
#| (func (export "run") (param i32) (result i32)
#| local.get 0 call $first local.get 0 call $second i32.add))
),
)
let options = OptimizeOptions::new(
optimize_level=4,
shrink_level=4,
inlining=default_inlining_options(
always_inline_max_size=0,
one_caller_inline_max_size=0,
flexible_inline_max_size=0,
),
)
for preset in ["optimize", "shrink"] {
let output = optimize_module_with_pass_flags(input, [preset], options).unwrap()
assert_true(@validate.validate_module(output).is_ok())
let funcs = match output.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => fail("expected private helper and exported caller")
}
@debug.assert_eq(funcs.length(), 2)
assert_true(
@binary.encode_module(output).unwrap().length() <
@binary.encode_module(input).unwrap().length(),
)
}
}
///|
test "direct DFE preserves distinct host-exported function identities" {
let input = @passes.pass_test_module_from_wat(
"(module (func (export \"a\") (result i32) i32.const 7) (func (export \"b\") (result i32) i32.const 7))",
)
let optimized = match
optimize_module_with_pass_flags(
input,
["duplicate-function-elimination"],
OptimizeOptions::new(),
) {
Ok(mod_) => mod_
Err(err) => fail("direct DFE dispatch failed: \{err}")
}
match optimized.export_sec {
Some(
@lib.ExportSec(
[
@lib.Export(_, @lib.FuncExternIdx(@lib.FuncIdx(first))),
@lib.Export(_, @lib.FuncExternIdx(@lib.FuncIdx(second))),
]
)
) => assert_true(first != second)
_ => fail("expected two distinct exported functions")
}
assert_true(@validate.validate_module(optimized) is Ok(_))
}
///|
test "direct DFE preserves address-taken internal function identities" {
let wat =
#|(module
#| (func $first (result i32) (i32.const 7))
#| (func $second (result i32) (i32.const 7))
#| (table (export "refs") 2 funcref)
#| (elem (i32.const 0) func $first $second)
#| (global (export "first_ref") funcref (ref.func $first))
#| (global (export "second_ref") funcref (ref.func $second)))
let input = @passes.pass_test_module_from_wat(wat)
let optimized = match
optimize_module_with_pass_flags(
input,
["duplicate-function-elimination"],
OptimizeOptions::new(),
) {
Ok(mod_) => mod_
Err(err) => fail("direct DFE dispatch failed: \{err}")
}
assert_true(
optimized.elem_sec
is Some(
@lib.ElemSec(
[
@lib.Elem(
@lib.Active(@lib.TableIdx(0U), _),
@lib.FuncsElemKind([@lib.FuncIdx(0U), @lib.FuncIdx(1U)])
),
]
)
),
)
assert_true(@validate.validate_module(optimized) is Ok(_))
}
///|
test "direct performance consumers preserve references initialized before branches" {
let input = @passes.pass_test_module_from_wat(
(
#|(module (type $f (func))
#| (func (export "choose") (param (ref $f) i32) (result (ref $f))
#| (local (ref $f) (ref $f))
#| local.get 0 local.set 2
#| local.get 1 if local.get 0 local.set 2 else local.get 0 local.set 2 end
#| local.get 2))
),
)
let original = @binary.encode_module(input)
for
pass in [
"precompute", "precompute-propagate", "coalesce-locals", "dae2", "optimize-instructions",
"simplify-locals", "inlining-optimizing", "simplify-locals-nonesting", "simplify-locals-notee",
"simplify-locals-nostructure", "simplify-locals-notee-nostructure",
] {
let output = optimize_module_with_pass_flags(
input,
[pass],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
assert_eq(output.export_sec, input.export_sec)
let @lib.Func(locals, _) = output.code_sec.unwrap().0[0]
assert_true(locals.length() <= 2)
assert_eq(@binary.encode_module(input), original)
}
}
///|
test "unchanged NaN input reuses its exact encoded bytes after an optimizer pass" {
let mod_ = @passes.pass_test_module_from_wat(
"(module (func (result f32) f32.const nan:0x400001))",
)
let bytes = @binary.encode_module(mod_).unwrap()
assert_true(
should_reuse_input_wasm_bytes(CliInputFormat::wasm(), 1, false, false, mod_),
)
assert_false(
should_reuse_input_wasm_bytes(CliInputFormat::wasm(), 1, true, false, mod_),
)
assert_false(
should_reuse_input_wasm_bytes(CliInputFormat::wat(), 1, false, false, mod_),
)
assert_false(
should_reuse_input_wasm_bytes(CliInputFormat::wasm(), 0, false, false, mod_),
)
let changed = @passes.pass_test_module_from_wat(
"(module (func (result f32) f32.const nan:0x400002))",
)
assert_false(
should_reuse_input_wasm_bytes(
CliInputFormat::wasm(),
1,
false,
true,
changed,
),
)
let rebuilt = @binary.decode_module(bytes).unwrap()
assert_false(
should_reuse_input_wasm_bytes(
CliInputFormat::wasm(),
1,
false,
true,
rebuilt,
),
)
@validate.validate_module(mod_).unwrap()
assert_eq(@binary.encode_module(mod_).unwrap(), bytes)
}
///|
test "Binaryen 133 command GTO removes an unread private struct field" {
let input = @passes.pass_test_module_from_wat(
"(module (type $t (struct (field (mut i32)))) (func (export \"run\") (result i32) (drop (struct.new $t (i32.const 1))) (i32.const 7)))",
)
let output = optimize_module_with_pass_flags(
input,
["global-type-optimization"],
OptimizeOptions::new(closed_world=true),
).unwrap()
assert_true(@validate.validate_module(output).is_ok())
let @lib.TypeSec(types) = output.type_sec.unwrap()
let fields : Array[@lib.FieldType] = []
let found = [false]
for rec in types {
let group = match rec {
@lib.SingleRecType(t) => [t]
@lib.GroupRecType(g) => g
}
for subtype in group {
match subtype.get_comptype() {
@lib.StructCompType(fs) => {
fields.append(fs)
found[0] = true
}
_ => ()
}
}
}
assert_true(found[0])
assert_eq(fields.length(), 0)
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_true(body.any(fn(instr) { instr is @lib.StructNewDefault(_) }))
}
///|
test "Binaryen 133 command shared objects lowers indirect calls" {
let input = @passes.pass_test_module_from_wat(
"(module (type $f (func (result i32))) (table 1 funcref) (elem (i32.const 0) func $target) (func $target (type $f) (i32.const 7)) (func (export \"run\") (result i32) (call_indirect (type $f) (i32.const 0))))",
)
let output = optimize_module_with_pass_flags(
input,
["make-shared-objects"],
OptimizeOptions::new(),
).unwrap()
assert_true(@validate.validate_module(output).is_ok())
assert_eq(output.table_sec.unwrap().0.length(), 2)
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[1]
assert_true(body.any(fn(i) { i is @lib.CallIndirect(_, @lib.TableIdx(1U)) }))
}
///|
test "Binaryen 133 command GTO remaps inherited fields" {
let input = @passes.pass_test_module_from_wat(
"(module (type $a (sub (struct (field (mut i32))))) (type $b (sub final $a (struct (field (mut i32)) (field (mut i32))))) (func (export \"run\") (result i32) (struct.get $b 1 (struct.new $b (i32.const 7) (i32.const 9)))))",
)
let output = optimize_module_with_pass_flags(
input,
["global-type-optimization"],
OptimizeOptions::new(closed_world=true),
).unwrap()
assert_true(@validate.validate_module(output).is_ok())
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_true(body.any(fn(i) { i is @lib.StructGet(_, @lib.U32(0U)) }))
assert_eq(body.filter(fn(i) { i is @lib.I32Const(_) }).length(), 1)
}
///|
test "Binaryen 133 command tail-call lowers an indirect table64 exit" {
let input = @binary.decode_module(
Bytes::from_array([
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x04, 0x01, 0x60, 0x00,
0x00, 0x03, 0x03, 0x02, 0x00, 0x00, 0x04, 0x05, 0x01, 0x70, 0x05, 0x02, 0x02,
0x09, 0x0b, 0x01, 0x06, 0x00, 0x42, 0x01, 0x0b, 0x70, 0x01, 0xd2, 0x00, 0x0b,
0x0a, 0x0d, 0x02, 0x03, 0x00, 0x01, 0x0b, 0x07, 0x00, 0x42, 0x01, 0x11, 0x00,
0x00, 0x0b,
]),
).unwrap()
assert_true(@validate.validate_module(input).is_ok())
let output = optimize_module_with_pass_flags(
input,
["tail-call"],
OptimizeOptions::new(),
).unwrap()
assert_true(@validate.validate_module(output).is_ok())
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[1]
assert_true(body.any(fn(i) { i is @lib.ReturnCallIndirect(_, _) }))
}
///|
test "Binaryen 133 command GTO preserves canonical packed array identities" {
let input = @passes.pass_test_module_from_wat(
"(module (type $s (struct (field i32))) (type $a (array (mut i8))) (type $b (array (mut i16))) (func (export \"run\") (local (ref null $s) (ref null $a) (ref null $b))))",
)
let output = optimize_module_with_pass_flags(
input,
["global-type-optimization"],
OptimizeOptions::new(closed_world=true),
).unwrap()
assert_true(@validate.validate_module(output).is_ok())
let groups = output.type_sec.unwrap().0
assert_eq(groups.length(), 4)
assert_true(groups.all(fn(g) { g is @lib.SingleRecType(_) }))
}
///|
test "command DAE2 modes retain mutable loop sources and imported effects" {
let input = @passes.pass_test_module_from_wat(
"(module (import \"host\" \"f\" (func (param i32) (result i32))) (global (export \"state\") (mut i32) (i32.const 666)) (func $target (param i32 i32) (result i32) (local i32) local.get 0 local.set 2 block loop local.get 2 i32.eqz br_if 1 local.get 2 call 0 drop local.get 2 i32.const 1 i32.sub local.set 2 local.get 2 global.set 0 br 0 end end local.get 2) (func (export \"run\") (param i32) (result i32) local.get 0 i32.const 3 i32.and i32.const 9 call $target))",
)
let bytes = @binary.encode_module(input).unwrap()
for pass in ["dae2", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[pass],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
assert_eq(output.import_sec, input.import_sec)
assert_eq(@binary.encode_module(input).unwrap(), bytes)
let env = @validate.Env::new().with_module(output)
assert_eq(
env.get_functype_by_funcidx(@lib.FuncIdx::new(1U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
}
}
///|
test "command Coalesce retains calls and trap after nested terminal control" {
let input = @passes.pass_test_module_from_wat(
"(module (import \"host\" \"f\" (func (result i32))) (func (export \"run\") (param i32) (result i32) (local i32 i32) local.get 0 if block call 0 local.tee 1 drop i32.const 7 return end else call 0 local.tee 2 drop unreachable end i32.const 99))",
)
let bytes = @binary.encode_module(input).unwrap()
let output = optimize_module_with_pass_flags(
input,
["coalesce-locals"],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
let @lib.Func(_, @lib.Expr(before)) = input.code_sec.unwrap().0[0]
assert_eq(@binary.encode_module(input).unwrap(), bytes)
assert_eq(output.import_sec, input.import_sec)
assert_true(body.length() <= before.length())
match body[1] {
@lib.If(_, yes, Some(no)) => {
assert_true(no.any(fn(i) { i is @lib.Call(@lib.FuncIdx(0U)) }))
assert_true(no.any(fn(i) { i is @lib.Unreachable }))
assert_true(
yes.any(fn(i) {
i is @lib.Block(_, _) || i is @lib.Call(@lib.FuncIdx(0U))
}),
)
}
_ => fail("expected retained conditional effects")
}
}
///|
test "command OI local grouping preserves imported call and untouched body" {
let input = @passes.pass_test_module_from_wat(
"(module (import \"host\" \"f\" (func (param i32) (result i32))) (func (export \"run\") (result i32) (local i32 f64 i32) i32.const 41 local.set 2 local.get 2 call 0) (func (result i32) i32.const 7))",
)
let output = optimize_module_with_pass_flags(
input,
["optimize-instructions"],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let @lib.Func(locals, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_eq(locals.runs().length(), 2)
assert_eq(body, [
@lib.Instruction::i32_const(41),
@lib.Instruction::local_set(@lib.LocalIdx::new(1U)),
@lib.Instruction::local_get(@lib.LocalIdx::new(1U)),
@lib.Instruction::call(@lib.FuncIdx::new(0U)),
])
assert_eq(output.code_sec.unwrap().0[1], input.code_sec.unwrap().0[1])
}
///|
test "Binaryen 133 command shared objects refines nested null branch chains" {
let input = @passes.pass_test_module_from_wat(
"(module (func (drop (block (result funcref) (block ref.null func i32.const 1 br_if 1 br 1 unreachable) unreachable))))",
)
let output = optimize_module_with_pass_flags(
input,
["make-shared-objects"],
OptimizeOptions::new(),
).unwrap()
assert_true(@validate.validate_module(output).is_ok())
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
let result = match body[0] {
@lib.Block(@lib.ValTypeBlockType(vt), _) => vt
_ => fail("expected reference block")
}
assert_eq(
result,
@lib.ValType::ref_type(
@lib.RefType::new(
true,
@lib.HeapType::shared_abs(@lib.AbsHeapType::none()),
),
),
)
}
///|
test "command simplify locals and DAE2 optimizing retain leaf selects and ordered global writes" {
let input = @passes.pass_test_module_from_wat(
"(module (global (export \"state\") (mut i32) (i32.const 0)) (func (export \"run\") (param i32) (result i32) local.get 0 global.set 0 local.get 0 if (result i32) i32.const 7 else i32.const 9 end))",
)
let saved = @binary.encode_module(input).unwrap()
for pass in ["simplify-locals", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[pass],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_true(body.any(fn(instr) { instr is @lib.Select }))
assert_eq(body[0], @lib.Instruction::local_get(@lib.LocalIdx::new(0U)))
assert_eq(body[1], @lib.Instruction::global_set(@lib.GlobalIdx::new(0U)))
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command simplify locals and DAE2 optimizing keep active additive recurrence reductions" {
let step = "local.get 0 local.get 2 i32.add local.tee 1 local.get 3 i32.add local.tee 2 local.get 1 i32.add local.set 3"
let input = @passes.pass_test_module_from_wat(
"(module (global (export \"state\") (mut i32) (i32.const 0)) (func (export \"run\") (param i32) (result i32) (local i32 i32 i32) i32.const 17 global.set 0 \{step} \{step} \{step} local.get 3 global.set 0 local.get 3))",
)
let saved = @binary.encode_module(input).unwrap()
for pass in ["simplify-locals", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[pass],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_eq(body.filter(fn(instr) { instr is @lib.GlobalSet(_) }).length(), 2)
assert_true(
@binary.encode_module(output).unwrap().length() < saved.length(),
)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command simplify locals and DAE2 optimizing preserve wide continuation state writes" {
let locals = Array::make(128, "i32").join(" ")
let reads = Array::makei(64, fn(index) { "local.get \{index} drop" }).join(
" ",
)
let input = @passes.pass_test_module_from_wat(
"(module (global (export \"state\") (mut i32) (i32.const 0)) (func (export \"run\") (param i32) (result i32) (local \{locals}) i32.const 17 global.set 0 local.get 0 i32.const 1 i32.and if loop local.get 65 global.set 0 end else local.get 0 local.set 65 local.get 65 global.set 0 end \{reads} local.get 64 drop local.get 128 drop global.get 0))",
)
let saved = @binary.encode_module(input).unwrap()
for pass in ["simplify-locals", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[pass],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_eq(body[0], @lib.Instruction::i32_const(@lib.I32(17)))
assert_eq(body[1], @lib.Instruction::global_set(@lib.GlobalIdx::new(0U)))
assert_eq(
body[body.length() - 1],
@lib.Instruction::global_get(@lib.GlobalIdx::new(0U)),
)
assert_eq(output.global_sec, input.global_sec)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command simplify locals and DAE2 optimizing retain nested suffix cleanup" {
for producer in ["i32.const 7", "call $produce"] {
let input = @passes.pass_test_module_from_wat(
"(module (import \"env\" \"produce\" (func $produce (result i32))) (import \"env\" \"consume\" (func $consume (param i32))) (import \"env\" \"tick\" (func $tick)) (func (export \"run\") (param i32) (local i32) block call $tick end block loop local.get 0 if \{producer} local.set 1 nop local.get 1 call $consume else \{producer} local.set 1 nop local.get 1 call $consume end end end block call $tick end))",
)
let saved = @binary.encode_module(input).unwrap()
for pass in ["simplify-locals", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[pass],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
assert_eq(output.import_sec, input.import_sec)
assert_true(
@binary.encode_module(output).unwrap().length() < saved.length(),
)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
}
///|
test "command simplify locals and DAE2 optimizing preserve repeated copy uses and source writes" {
let input = @passes.pass_test_module_from_wat(
"(module (import \"host\" \"consume\" (func $consume (param i32))) (global (export \"state\") (mut i32) (i32.const 0)) (func (export \"run\") (param i32) (result i32) (local i32 i32 i32) i32.const 17 global.set 0 local.get 0 local.set 1 i32.const 7 local.set 2 i32.const 9 local.set 3 local.get 0 i32.const 1 i32.add local.set 0 local.get 1 call $consume local.get 2 call $consume local.get 3 call $consume local.get 1 call $consume local.get 0 global.set 0 global.get 0))",
)
let saved = @binary.encode_module(input).unwrap()
for pass in ["simplify-locals", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[pass],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
assert_eq(output.import_sec, input.import_sec)
assert_eq(output.global_sec, input.global_sec)
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_eq(body[0], @lib.Instruction::i32_const(@lib.I32(17)))
assert_eq(body[1], @lib.Instruction::global_set(@lib.GlobalIdx::new(0U)))
assert_eq(
body[body.length() - 1],
@lib.Instruction::global_get(@lib.GlobalIdx::new(0U)),
)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command coalesce and DAE2 optimizing preserve lowered nop tee effects and GC reads" {
let input = @passes.pass_test_module_from_wat(
"(module (type $s (struct (field i32))) (import \"host\" \"tick\" (func $tick)) (global (export \"state\") (mut i32) (i32.const 0)) (func (export \"run\") (param i32) (local i32 i64 (ref null $s)) i32.const 17 global.set 0 call $tick block i32.const 7 local.tee 1 drop nop i64.const 9 local.tee 2 drop end i32.const 7 struct.new $s local.tee 3 drop nop local.get 3 struct.get $s 0 global.set 0 call $tick))",
)
let saved = @binary.encode_module(input).unwrap()
for pass in ["coalesce-locals", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[pass],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
assert_eq(output.import_sec, input.import_sec)
assert_eq(output.global_sec, input.global_sec)
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_eq(body[0], @lib.Instruction::i32_const(@lib.I32(17)))
assert_eq(body[1], @lib.Instruction::global_set(@lib.GlobalIdx::new(0U)))
assert_eq(body.filter(fn(i) { i is @lib.Call(_) }).length(), 2)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command Coalesce preserves weighted loop carriers and later weightless values" {
let input = @passes.pass_test_module_from_wat(
"(module (import \"host\" \"tick\" (func $tick)) (global (export \"state\") (mut i32) (i32.const 0)) (func (export \"run\") (param i32) (result i32) (local i32 i32 i32) i32.const 17 global.set 0 call $tick local.get 0 i32.const 3 i32.and local.set 1 block loop local.get 1 i32.eqz br_if 1 local.get 1 local.set 2 local.get 2 global.set 0 call $tick local.get 1 i32.const 1 i32.sub local.set 1 br 0 end end i32.const 7 local.set 3 local.get 3 global.get 0 i32.add))",
)
let saved = @binary.encode_module(input).unwrap()
let output = optimize_module_with_pass_flags(
input,
["coalesce-locals"],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
assert_eq(output.import_sec, input.import_sec)
assert_eq(output.global_sec, input.global_sec)
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_eq(body[0], @lib.Instruction::i32_const(@lib.I32(17)))
assert_eq(body[1], @lib.Instruction::global_set(@lib.GlobalIdx::new(0U)))
assert_eq(body[2], @lib.Instruction::call(@lib.FuncIdx::new(0U)))
assert_true(@binary.encode_module(output).unwrap() != saved)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
///|
test "command Coalesce preserves simultaneous loop lifetimes and ordered effects" {
let input = @passes.pass_test_module_from_wat(
"(module (import \"host\" \"tick\" (func $tick)) (global (export \"state\") (mut i32) (i32.const 0)) (func (export \"run\") (param i32) (result i32) (local i32 i32 i32) i32.const 17 global.set 0 call $tick local.get 0 i32.const 3 i32.and local.set 1 i32.const 7 local.set 2 i32.const 9 local.set 3 block loop local.get 1 i32.eqz br_if 1 local.get 2 local.get 3 i32.add global.set 0 call $tick local.get 1 i32.const 1 i32.sub local.set 1 br 0 end end local.get 2 local.get 3 i32.add))",
)
let saved = @binary.encode_module(input).unwrap()
let output = optimize_module_with_pass_flags(
input,
["coalesce-locals"],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
assert_eq(output.import_sec, input.import_sec)
assert_eq(output.global_sec, input.global_sec)
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_eq(body[0], @lib.Instruction::i32_const(@lib.I32(17)))
assert_eq(body[1], @lib.Instruction::global_set(@lib.GlobalIdx::new(0U)))
assert_eq(body[2], @lib.Instruction::call(@lib.FuncIdx::new(0U)))
assert_true(@binary.encode_module(output).unwrap() != saved)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
///|
test "command Coalesce and DAE2 modes preserve binary spill effects and nested traps" {
let input = @passes.pass_test_module_from_wat(
"(module (import \"host\" \"tick\" (func $tick)) (global (export \"state\") (mut i32) (i32.const 0)) (func (export \"run\") (param i32) (result i32) (local i32 i32) i32.const 17 global.set 0 call $tick block (result i32) local.get 0 i32.const 7 i32.div_s local.set 1 local.get 1 end local.set 2 local.get 2))",
)
let saved = @binary.encode_module(input).unwrap()
for pass in ["coalesce-locals", "dae2", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[pass],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
assert_eq(output.import_sec, input.import_sec)
assert_eq(output.global_sec, input.global_sec)
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_eq(body[0], @lib.Instruction::i32_const(@lib.I32(17)))
assert_eq(body[1], @lib.Instruction::global_set(@lib.GlobalIdx::new(0U)))
assert_eq(body[2], @lib.Instruction::call(@lib.FuncIdx::new(0U)))
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command DAE2 modes and Coalesce preserve pending local values across writes" {
let input = @passes.pass_test_module_from_wat(
"(module (import \"host\" \"tick\" (func $tick)) (global (export \"state\") (mut i32) (i32.const 0)) (func (export \"run\") (param i32) (result i32) (local i32 i32) i32.const 17 global.set 0 call $tick local.get 0 i32.const 7 i32.add i32.const 9 local.set 0 local.get 0 i32.add))",
)
let saved = @binary.encode_module(input).unwrap()
for pass in ["dae2", "dae2-optimizing", "coalesce-locals"] {
let output = optimize_module_with_pass_flags(
input,
[pass],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
assert_eq(output.import_sec, input.import_sec)
assert_eq(output.global_sec, input.global_sec)
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_eq(body[0], @lib.Instruction::i32_const(@lib.I32(17)))
assert_eq(body[1], @lib.Instruction::global_set(@lib.GlobalIdx::new(0U)))
assert_eq(body[2], @lib.Instruction::call(@lib.FuncIdx::new(0U)))
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command DAE2 modes and Coalesce retain nested results with compact lowering counts" {
let input = @passes.pass_test_module_from_wat(
"(module (func $helper (param i32 i32) (result i32) local.get 0 i32.const 7 i32.add) (func (export \"run\") (param i32) (result i32) (local i32) local.get 0 i32.const 99 call $helper local.set 1 block (result i32) local.get 0 if (result i32) local.get 1 else i32.const 0 end end))",
)
let saved = @binary.encode_module(input).unwrap()
for pass in ["dae2", "dae2-optimizing", "coalesce-locals"] {
let output = optimize_module_with_pass_flags(
input,
[pass],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let env = @validate.empty_env().with_module(output)
guard env.get_functype_by_funcidx(@lib.FuncIdx::new(0U))
is Some(@lib.FuncType(params, results)) else {
fail("helper signature must remain present")
}
assert_eq(results, [@lib.ValType::i32()])
if pass != "coalesce-locals" {
assert_eq(params, [@lib.ValType::i32()])
}
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command large-module pass routes preserve mixed tuple results while eliminating dead arguments" {
let input = @passes.pass_test_module_from_wat(
"(module (func $helper (param i32 i32) (result i32 i64) local.get 0 i64.const 9) (func (export \"run\") (param i32) (result i32) (local i64 i32) local.get 0 i32.const 99 call $helper local.set 1 local.set 2 local.get 2 local.get 1 i32.wrap_i64 i32.add))",
)
let saved = @binary.encode_module(input).unwrap()
for
pass in [
"dae2", "dae2-optimizing", "coalesce-locals", "optimize-instructions",
] {
let output = optimize_module_with_pass_flags(
input,
[pass],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let env = @validate.empty_env().with_module(output)
guard env.get_functype_by_funcidx(@lib.FuncIdx::new(0U))
is Some(@lib.FuncType(params, results)) else {
fail("tuple helper signature must remain present")
}
assert_eq(results, [@lib.ValType::i32(), @lib.ValType::i64()])
if pass == "dae2" || pass == "dae2-optimizing" {
assert_eq(params, [@lib.ValType::i32()])
}
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command Coalesce block liveness retains loop-carried results and removes unused locals" {
let input = @passes.pass_test_module_from_wat(
"(module (func (export \"run\") (param i32) (result i32) (local i32 i32) local.get 0 local.set 1 block loop local.get 1 i32.eqz br_if 1 local.get 1 i32.const 1 i32.sub local.set 1 br 0 end end local.get 1))",
)
let saved = @binary.encode_module(input).unwrap()
let output = optimize_module_with_pass_flags(
input,
["coalesce-locals"],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
guard output.code_sec is Some(@lib.CodeSec(funcs)) else {
fail("loop function must remain present")
}
let @lib.Func(locals, _) = funcs[0]
assert_true(locals.length() < 2)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
///|
test "command lazy lower ordering preserves call snapshots across large pass routes" {
let input = @passes.pass_test_module_from_wat(
"(module (func $callee (param i32 i32) (result i32) local.get 0) (func (export \"run\") (param i32) (result i32) (local i32) local.get 0 local.set 1 local.get 1 local.get 0 i32.const 1 i32.add local.set 1 i32.const 7 call $callee))",
)
let saved = @binary.encode_module(input).unwrap()
for
flag in [
"dae2", "dae2-optimizing", "coalesce-locals", "optimize-instructions",
] {
let output = optimize_module_with_pass_flags(
input,
[flag],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
if flag == "dae2" || flag == "dae2-optimizing" {
let context = @validate.Env::new().with_module(output)
assert_eq(
context.get_functype_by_funcidx(@lib.FuncIdx::new(0U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
}
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command checked payload queries preserve numeric GC and memory pass routes" {
let input = @passes.pass_test_module_from_wat(
"(module (type $s (struct (field i32))) (memory (export \"memory\") 1) (func $callee (param i32 i32) (result i32) local.get 0 i32.const 7 i32.xor) (func (export \"run\") (param i32) (result i32) i32.const 0 i32.const 17 i32.store local.get 0 i32.load local.get 0 struct.new $s struct.get $s 0 i32.xor i64.extend_i32_u i32.wrap_i64 i32.const 99 call $callee))",
)
let saved = @binary.encode_module(input).unwrap()
for
flag in [
"dae2", "dae2-optimizing", "coalesce-locals", "optimize-instructions",
] {
let output = optimize_module_with_pass_flags(
input,
[flag],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
if flag == "dae2" || flag == "dae2-optimizing" {
let context = @validate.Env::new().with_module(output)
assert_eq(
context.get_functype_by_funcidx(@lib.FuncIdx::new(0U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
}
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command local-band masks retain call snapshots across large pass routes" {
let input = @passes.pass_test_module_from_wat(
"(module (import \"host\" \"f\" (func $f (param i32) (result i32))) (func $callee (param i32 i32) (result i32) local.get 0) (func (export \"run\") (param i32) (result i32) (local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 ) local.get 0 call $f i32.const 1 i32.add i32.const 1 i32.add i32.const 1 i32.add i32.const 1 i32.add i32.const 1 i32.add i32.const 1 i32.add i32.const 1 i32.add i32.const 1 i32.add i32.const 7 local.set 64 local.get 0 i32.const 1 i32.add local.set 0 local.get 0 call $f drop i32.const 99 call $callee))",
)
let saved = @binary.encode_module(input).unwrap()
for
flag in [
"dae2", "dae2-optimizing", "coalesce-locals", "optimize-instructions",
] {
let output = optimize_module_with_pass_flags(
input,
[flag],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
if flag == "dae2" || flag == "dae2-optimizing" {
assert_eq(
@validate.Env::new()
.with_module(output)
.get_functype_by_funcidx(@lib.FuncIdx::new(1U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
}
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command Coalesce copy barrier preserves active branch and call route" {
let input = @passes.pass_test_module_from_wat(
"(module (import \"host\" \"f\" (func $f (param i32) (result i32))) (func (export \"run\") (param i32) (result i32) (local i32 i32 i32) local.get 0 local.set 1 local.get 1 local.set 2 block local.get 0 br_if 0 local.get 2 call $f local.set 3 end local.get 2))",
)
let saved = @binary.encode_module(input).unwrap()
let output = optimize_module_with_pass_flags(
input,
["coalesce-locals"],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let funcs = match output.code_sec {
Some(@lib.CodeSec(funcs)) => funcs
None => abort("missing code")
}
let @lib.Func(locals, _) = funcs[0]
assert_true(locals.length() < 3)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
///|
test "command value CFG edges retain active GC and exception routes" {
let input = @passes.pass_test_module_from_wat(
"(module (type $s (struct (field i32))) (import \"host\" \"tick\" (func $tick)) (tag $t (param i32)) (func $callee (param i32 i32) (result i32) local.get 0) (func (export \"run\") (param i32) (result i32) (local i32 (ref null $s)) block (result i32) try_table (catch $t 0) local.get 0 i32.const 0 i32.lt_s if i32.const 9 throw $t end local.get 0 i32.const 3 i32.and local.set 1 local.get 0 struct.new $s local.set 2 block loop local.get 1 i32.eqz br_if 1 call $tick local.get 1 i32.const 1 i32.sub local.set 1 br 0 end end local.get 2 struct.get $s 0 i32.const 99 call $callee return end i32.const 0 end))",
)
let saved = @binary.encode_module(input).unwrap()
for
flag in [
"dae2", "dae2-optimizing", "coalesce-locals", "optimize-instructions",
] {
let output = optimize_module_with_pass_flags(
input,
[flag],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
if flag == "dae2" || flag == "dae2-optimizing" {
assert_eq(
@validate.Env::new()
.with_module(output)
.get_functype_by_funcidx(@lib.FuncIdx::new(1U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
}
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command typed pop boundaries preserve GC table and SIMD routes" {
let input = @passes.pass_test_module_from_wat(
"(module (type $s (struct (field i32))) (import \"host\" \"f\" (func $f (param i32) (result i32))) (table 1 anyref) (func $callee (param i32 i32) (result i32) local.get 0) (func (export \"run\") (param i32) (result i32) i32.const 0 local.get 0 struct.new $s table.set 0 local.get 0 call $f i64.extend_i32_s i64.const 7 i64.add i32.wrap_i64 i32.const 0 table.get 0 ref.is_null local.get 0 i32.add i32.add local.get 0 i32x4.splat v128.const i32x4 1 1 1 1 v128.xor i32x4.extract_lane 0 i32.add i32.const 99 call $callee))",
)
let saved = @binary.encode_module(input).unwrap()
for
flag in [
"dae2", "dae2-optimizing", "coalesce-locals", "optimize-instructions",
] {
let output = optimize_module_with_pass_flags(
input,
[flag],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
if flag == "dae2" || flag == "dae2-optimizing" {
assert_eq(
@validate.Env::new()
.with_module(output)
.get_functype_by_funcidx(@lib.FuncIdx::new(1U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
}
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command declaration validation retains reference calls across routes" {
let input = @passes.pass_test_module_from_wat(
"(module (type $sig (func (result i32))) (func $target (type $sig) i32.const 7) (func $callee (param i32 i32) (result i32) local.get 0) (elem declare func $target) (func (export \"run\") (param i32) (result i32) local.get 0 ref.func $target call_ref $sig i32.add i32.const 99 call $callee))",
)
let saved = @binary.encode_module(input).unwrap()
for
flag in [
"dae2", "dae2-optimizing", "coalesce-locals", "optimize-instructions",
] {
let output = optimize_module_with_pass_flags(
input,
[flag],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
if flag == "dae2" || flag == "dae2-optimizing" {
assert_eq(
@validate.Env::new()
.with_module(output)
.get_functype_by_funcidx(@lib.FuncIdx::new(1U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
}
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command lift shapes retain memory immediates and indirect signatures" {
let input = @passes.pass_test_module_from_wat(
"(module (type $sig (func (param i32) (result i32))) (table 1 funcref) (memory (export \"memory\") 1) (func $target (type $sig) local.get 0 i32.const 1 i32.add) (elem (i32.const 0) $target) (func $callee (param i32 i32) (result i32) local.get 0) (func (export \"run\") (param i32) (result i32) (local i32 i32) i32.const 0 local.get 0 i32.store i32.const 0 i32.load local.set 1 local.get 1 local.set 2 local.get 2 i32.const 0 call_indirect (type $sig) i32.const 99 call $callee i32.const 0 i32.add))",
)
let saved = @binary.encode_module(input).unwrap()
for
flag in [
"dae2", "dae2-optimizing", "coalesce-locals", "optimize-instructions",
] {
let output = optimize_module_with_pass_flags(
input,
[flag],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
if flag == "dae2" || flag == "dae2-optimizing" {
assert_eq(
@validate.Env::new()
.with_module(output)
.get_functype_by_funcidx(@lib.FuncIdx::new(1U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
}
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command DAE routes preserve parameter values carried through branches" {
let input = @passes.pass_test_module_from_wat(
"(module (global $g (mut i32) (i32.const 1)) (func (export \"run\") (result i32) global.get $g call $callee) (func $callee (param i32) (result i32) block (result i32) local.get 0 br 0 end i32.const 5 local.set 0))",
)
let saved = @binary.encode_module(input).unwrap()
for flag in ["dae", "dae-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[flag],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
assert_eq(
@validate.Env::new()
.with_module(output)
.get_functype_by_funcidx(@lib.FuncIdx::new(1U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command lift data results preserve mixed call result lanes" {
let input = @passes.pass_test_module_from_wat(
"(module (func $helper (param i32 i32) (result i32 i64) local.get 0 i64.const 9) (func (export \"run\") (param i32) (result i32) (local i64 i32) local.get 0 i32.const 99 call $helper local.set 1 local.set 2 local.get 2 local.get 1 i32.wrap_i64 i32.add))",
)
let saved = @binary.encode_module(input).unwrap()
for flag in ["dae2", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[flag],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
assert_eq(
@validate.Env::new()
.with_module(output)
.get_functype_by_funcidx(@lib.FuncIdx::new(0U)),
Some(
@lib.FuncType::new([@lib.ValType::i32()], [
@lib.ValType::i32(),
@lib.ValType::i64(),
]),
),
)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command scalar scratch retains nested GC tuple types" {
let input = @passes.pass_test_module_from_wat(
"(module (type $s (struct (field i32))) (func $pair (param i32 i32) (result (ref $s) i64) local.get 0 struct.new $s i64.const 9) (func (export \"run\") (param i32) (result i32) (local i64 (ref null $s)) local.get 0 if (result (ref $s) i64) local.get 0 i32.const 99 call $pair else i32.const 7 i32.const 99 call $pair end local.set 1 local.set 2 i32.const 0 drop local.get 2 struct.get $s 0 local.get 1 i32.wrap_i64 i32.add))",
)
let saved = @binary.encode_module(input).unwrap()
for flag in ["dae2", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[flag],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let @lib.FuncType(params, results) = @validate.Env::new()
.with_module(output)
.get_functype_by_funcidx(@lib.FuncIdx::new(0U))
.unwrap()
assert_eq(params, [@lib.ValType::i32()])
assert_eq(results.length(), 2)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "large-pass dispatch preserves structured mutable-local lift state" {
let source =
#|(module
#| (func $helper (param i32 i32) (result i32) (local i32)
#| local.get 0 i32.const 3 i32.and local.set 2
#| block loop
#| local.get 2 i32.eqz br_if 1
#| local.get 2 i32.const 1 i32.sub local.set 2 br 0
#| end end local.get 2)
#| (func (export "run") (param i32) (result i32)
#| local.get 0 i32.const 99 call $helper))
let input = @wast.wast_to_binary_module(source).unwrap()
for
pass in [
"dae2", "dae2-optimizing", "coalesce-locals", "optimize-instructions",
] {
let output = optimize_module_with_pass_flags(
input,
[pass],
OptimizeOptions::new(),
).unwrap()
assert_true(@validate.validate_module(output) is Ok(_))
assert_eq(output.export_sec, input.export_sec)
if pass == "dae2" || pass == "dae2-optimizing" {
let env = @validate.Env::new().with_module(output)
assert_eq(
env.get_functype_by_funcidx(@lib.FuncIdx::new(0U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
}
}
}
///|
test "dae2 optimizing preserves a local read after an enclosing branch exit" {
let input = @wast.wast_to_binary_module(
(
#|(module
#| (import "host" "f" (func (param i32) (result i32)))
#| (func $callee (param i32 i32) (result i32) (local i32)
#| local.get 0 call 0 local.set 2
#| block loop local.get 0 br_if 1 br 1 end end
#| local.get 2 call 0)
#| (func (export "run") (param i32) (result i32)
#| local.get 0 i32.const 99 call $callee))
),
).unwrap()
let output = optimize_module_with_pass_flags(
input,
["dae2-optimizing"],
OptimizeOptions::new(),
).unwrap()
assert_true(@validate.validate_module(output) is Ok(_))
let env = @validate.Env::new().with_module(output)
assert_eq(
env.get_functype_by_funcidx(@lib.FuncIdx::new(1U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
// Both ordered imported calls remain in the callee after cleanup.
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_eq(
body.filter(fn(instr) { instr is @lib.Call(@lib.FuncIdx(0U)) }).length(),
2,
)
}
///|
test "dae2 optimizing retains effectful typed-block operands during suffix probing" {
let input = @wast.wast_to_binary_module(
(
#|(module
#| (import "host" "f" (func (param i32) (result i32)))
#| (func $callee (param i32 i32) (result i32) (local i32)
#| local.get 0 call 0
#| block (param i32) (result i32) i32.const 7 i32.add end
#| local.set 2 local.get 2 call 0)
#| (func (export "run") (param i32) (result i32)
#| local.get 0 i32.const 99 call $callee))
),
).unwrap()
let output = optimize_module_with_pass_flags(
input,
["dae2-optimizing"],
OptimizeOptions::new(),
).unwrap()
assert_true(@validate.validate_module(output) is Ok(_))
let env = @validate.Env::new().with_module(output)
assert_eq(
env.get_functype_by_funcidx(@lib.FuncIdx::new(1U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_eq(
body.filter(fn(instr) { instr is @lib.Call(@lib.FuncIdx(0U)) }).length(),
2,
)
}
///|
test "dae2 optimizing retains ordered calls across scalar and nop runs" {
let input = @wast.wast_to_binary_module(
(
#|(module
#| (import "host" "f" (func (param i32) (result i32)))
#| (import "host" "g" (func (param i32) (result i32)))
#| (func $callee (param i32 i32) (result i32)
#| nop nop nop local.get 0 nop nop call 0
#| i32.const 7 nop i32.const 9 i32.add i32.add call 1)
#| (func (export "run") (param i32) (result i32)
#| local.get 0 i32.const 99 call $callee))
),
).unwrap()
let output = optimize_module_with_pass_flags(
input,
["dae2-optimizing"],
OptimizeOptions::new(),
).unwrap()
assert_true(@validate.validate_module(output) is Ok(_))
let env = @validate.Env::new().with_module(output)
assert_eq(
env.get_functype_by_funcidx(@lib.FuncIdx::new(2U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_eq(body.filter(fn(instr) { instr is @lib.Call(_) }), [
@lib.Instruction::call(@lib.FuncIdx::new(0U)),
@lib.Instruction::call(@lib.FuncIdx::new(1U)),
])
}
///|
fn cmd_test_collect_simd_effect_order(
body : Array[@lib.Instruction],
events : Array[Int32],
) -> Unit {
for instr in body {
match instr {
@lib.Call(_) => events.push(1)
@lib.V128Store(_) => events.push(2)
@lib.I32Load(_) => events.push(3)
@lib.Block(_, @lib.Expr(nested)) =>
cmd_test_collect_simd_effect_order(nested, events)
@lib.If(_, then_body, else_body) => {
cmd_test_collect_simd_effect_order(then_body, events)
cmd_test_collect_simd_effect_order(else_body.unwrap_or([]), events)
}
_ => ()
}
}
}
///|
test "direct pass dispatcher retains SIMD and string effects" {
let load_input = @wast.wast_to_binary_module(
"(module (memory 1) (func (local v128) i32.const 65536 v128.load local.set 0))",
).unwrap()
let load_output = optimize_module_with_pass_flags(
load_input,
["simplify-locals"],
OptimizeOptions::new(),
).unwrap()
let @lib.Func(_, @lib.Expr(load_body)) = load_output.code_sec.unwrap().0[0]
assert_true(load_body.any(fn(instr) { instr is @lib.V128Load(_) }))
let string_input = @wast.wast_to_binary_module(
"(module (type $u8 (array (mut i8))) (func (result i32) ref.null noextern ref.null $u8 i32.const 0 string.encode_utf8_array i32.const 20 i32.shr_u i32.const 20 i32.shr_u))",
).unwrap()
let string_output = optimize_module_with_pass_flags(
string_input,
["optimize-instructions"],
OptimizeOptions::new(),
).unwrap()
let @lib.Func(_, @lib.Expr(string_body)) = string_output.code_sec.unwrap().0[0]
assert_true(
string_body.any(fn(instr) { instr is @lib.StringEncodeUtf8Array }),
)
}
///|
test "direct pass dispatcher keeps SIMD stores after earlier memory reads" {
let merge_input = @wast.wast_to_binary_module(
"(module (memory 1) (func $reader (result i32) (i32.load (i32.const 0))) (func (result i32) (i32.add (call $reader) (block (result i32) (v128.store (i32.const 0) (v128.const i32x4 1 2 3 4)) (i32.const 1)))))",
).unwrap()
let merge_output = optimize_module_with_pass_flags(
merge_input,
["merge-blocks"],
OptimizeOptions::new(),
).unwrap()
let @lib.Func(_, @lib.Expr(merge_body)) = merge_output.code_sec.unwrap().0[1]
let merge_events : Array[Int32] = []
cmd_test_collect_simd_effect_order(merge_body, merge_events)
assert_eq(merge_events, [1, 2])
let push_input = @wast.wast_to_binary_module(
"(module (memory 1) (func (param i32) (result i32) (local i32) (local.set 1 (i32.load (i32.const 0))) (v128.store (i32.const 0) (v128.const i32x4 1 2 3 4)) (if (local.get 0) (then (nop))) (local.get 1)))",
).unwrap()
let push_output = optimize_module_with_pass_flags(
push_input,
["code-pushing"],
OptimizeOptions::new(ignore_implicit_traps=true),
).unwrap()
let @lib.Func(_, @lib.Expr(push_body)) = push_output.code_sec.unwrap().0[0]
let push_events : Array[Int32] = []
cmd_test_collect_simd_effect_order(push_body, push_events)
assert_eq(push_events, [3, 2])
}
///|
test "dae2 optimizing retains nested read dependencies while removing dead arguments" {
let input = @wast.wast_to_binary_module(
(
#|(module
#| (import "host" "f" (func (param i32) (result i32)))
#| (func $callee (param i32 i32) (result i32) (local i32)
#| block local.get 0 call 0 local.set 2 local.get 2 drop end
#| block block nop end i32.const 9 local.set 2 local.get 2 call 0 drop end
#| local.get 0 if block local.get 2 call 0 drop end
#| else block nop end i32.const 7 local.set 2 local.get 2 call 0 drop end
#| local.get 2)
#| (func (export "run") (param i32) (result i32)
#| local.get 0 i32.const 99 call $callee))
),
).unwrap()
let saved = @binary.encode_module(input).unwrap()
let output = optimize_module_with_pass_flags(
input,
["dae2-optimizing"],
OptimizeOptions::new(),
).unwrap()
assert_true(@validate.validate_module(output) is Ok(_))
let env = @validate.Env::new().with_module(output)
assert_eq(
env.get_functype_by_funcidx(@lib.FuncIdx::new(1U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
///|
test "dae2 variants preserve recursive type prefixes while removing dead arguments" {
let input = @wast.wast_to_binary_module(
(
#|(module
#| (type $base (sub (struct)))
#| (rec
#| (type $node (struct (field (ref null $node))))
#| (type $box (struct (field (ref null $node)))))
#| (type $derived (sub $base (struct (field i32))))
#| (func $callee (param i32 i64) (result i32)
#| local.get 0 struct.new $derived struct.get $derived 0)
#| (func (export "run") (param i32) (result i32)
#| local.get 0 i64.const 99 call $callee))
),
).unwrap()
@validate.validate_module(input).unwrap()
let saved = @binary.encode_module(input).unwrap()
for pass in ["dae2", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[pass],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let env = @validate.Env::new().with_module(output)
assert_eq(
env.get_functype_by_funcidx(@lib.FuncIdx::new(0U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command DAE2 modes preserve nonfallthrough scalar result typing" {
let input = @passes.pass_test_module_from_wat(
"(module (func $h (param i32 i32) (result i32) block (result i32) local.get 0 return end) (func (export \"run\") (param i32) (result i32) local.get 0 i32.const 99 call $h))",
)
@validate.validate_module(input).unwrap()
let saved = @binary.encode_module(input).unwrap()
for pass in ["dae2", "dae2-optimizing"] {
let output = match
optimize_module_with_pass_flags(input, [pass], OptimizeOptions::new()) {
Ok(output) => output
Err(error) => fail("nonfallthrough scalar command failed: \{error}")
}
@validate.validate_module(output).unwrap()
assert_eq(
@validate.Env::new()
.with_module(output)
.get_functype_by_funcidx(@lib.FuncIdx::new(0U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "command package consumer preserves exact host-root function indices" {
let input = @passes.pass_test_module_from_wat(
(
#| (module
#| (func (result i32) i32.const 9)
#| (func (export "run") (result i32) i32.const 42)
#| (func (export "debug") (result i32) i32.const 99))
),
)
let output = @passes.rume_run_host_roots_module_pass(input, ["run"]).unwrap()
@debug.assert_eq(output.code_sec.unwrap().0.length(), 2)
@debug.assert_eq(output.export_sec.unwrap().0, [
@lib.Export::new(
@lib.Name::from_string("run"),
@lib.ExternIdx::func(@lib.FuncIdx::new(1U)),
),
])
@debug.assert_eq(@validate.validate_module(output), Ok(()))
}
///|
test "DAE command fact reuse preserves result cascades and overwritten parameters" {
let input = @passes.pass_test_module_from_wat(
(
#|(module
#| (import "host" "effect" (func $effect))
#| (func $leaf (param i32 i32) (result i32)
#| call $effect i32.const 9 local.set 0 local.get 0 local.get 1 i32.add)
#| (func $middle (result i32) i32.const 7 i32.const 11 call $leaf)
#| (func $outer (result i32) call $middle)
#| (func (export "run") call $outer drop))
),
)
let saved = @binary.encode_module(input).unwrap()
for pass_name in ["dae", "dae-optimizing", "dae2", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[pass_name],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_eq(body.filter(fn(instr) { instr is @lib.Call(_) }), [
@lib.Instruction::call(@lib.FuncIdx::new(0U)),
])
assert_eq(output.export_sec, input.export_sec)
assert_eq(
@validate.Env::new()
.with_module(output)
.get_functype_by_funcidx(@lib.FuncIdx::new(1U)),
Some(@lib.FuncType::new([], [])),
)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "DAE2 command reuses wide signatures while pruning one private callee" {
let params = Array::make(32, "i32").join(" ")
let args = Array::make(32, "i32.const 7").join(" ")
let input = @passes.pass_test_module_from_wat(
"(module (func $wide (export \"wide\") (param \{params}) (result i32) local.get 0) (func $leaf (param i32 i32) (result i32) local.get 1) (func (export \"run\") (result i32) \{args} call $wide drop \{args} call $wide drop i32.const 8 i32.const 9 call $leaf))",
)
let saved = @binary.encode_module(input).unwrap()
for pass_name in ["dae2", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[pass_name],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
assert_eq(output.export_sec, input.export_sec)
assert_eq(
@validate.Env::new()
.with_module(output)
.get_functype_by_funcidx(@lib.FuncIdx::new(1U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
assert_eq(output.code_sec.unwrap().0[0], input.code_sec.unwrap().0[0])
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[2]
assert_eq(body.filter(fn(instr) { instr is @lib.Call(_) }).length(), 3)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "DAE2 closed command preserves contravariant family parameter types" {
let input = @passes.pass_test_module_from_wat(
(
#|(module
#| (type $root (sub (func (param eqref i32) (result i32))))
#| (type $child (sub $root (func (param anyref i32) (result i32))))
#| (elem declare func $target)
#| (func $target (type $child) (param anyref i32) (result i32)
#| local.get 0 ref.is_null)
#| (func (export "run") (param anyref) (result i32)
#| local.get 0 i32.const 7 ref.func $target call_ref $child))
),
)
@validate.validate_module(input).unwrap()
let saved = @binary.encode_module(input).unwrap()
for pass_name in ["dae2", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[pass_name],
OptimizeOptions::new(closed_world=true),
).unwrap()
@validate.validate_module(output).unwrap()
assert_eq(output.export_sec, input.export_sec)
assert_eq(
@validate.Env::new()
.with_module(output)
.get_functype_by_funcidx(@lib.FuncIdx::new(0U)),
Some(@lib.FuncType::new([@lib.ValType::anyref()], [@lib.ValType::i32()])),
)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}
///|
test "DAE command body exposure keeps unseen signatures and private candidate effects" {
let input = @passes.pass_test_module_from_wat(
(
#|(module
#| (import "host" "effect" (func $effect (result i32)))
#| (global funcref (ref.func $module))
#| (elem declare func $escaped $module)
#| (func $target (param i32 i32) (result i32)
#| call $effect drop local.get 0)
#| (func $escaped (param i32 i32) (result i32) local.get 0)
#| (func $module (param i32 i32) (result i32) local.get 0)
#| (func (export "run") (param i32) (result i32)
#| ref.func $escaped drop
#| local.get 0 i32.const 20 call $target)
#| (func (export "inactive") unreachable ref.func $escaped drop))
),
)
let saved = @binary.encode_module(input).unwrap()
for pass_name in ["dae", "dae-optimizing", "dae2", "dae2-optimizing"] {
let output = optimize_module_with_pass_flags(
input,
[pass_name],
OptimizeOptions::new(),
).unwrap()
@validate.validate_module(output).unwrap()
let env = @validate.Env::new().with_module(output)
assert_eq(
env.get_functype_by_funcidx(@lib.FuncIdx::new(1U)),
Some(@lib.FuncType::new([@lib.ValType::i32()], [@lib.ValType::i32()])),
)
for unseen in [2U, 3U] {
assert_eq(
env.get_functype_by_funcidx(@lib.FuncIdx::new(unseen)),
Some(
@lib.FuncType::new([@lib.ValType::i32(), @lib.ValType::i32()], [
@lib.ValType::i32(),
]),
),
)
}
let @lib.Func(_, @lib.Expr(body)) = output.code_sec.unwrap().0[0]
assert_eq(body.filter(fn(instr) { instr is @lib.Call(_) }), [
@lib.Instruction::call(@lib.FuncIdx::new(0U)),
])
assert_eq(output.global_sec, input.global_sec)
assert_eq(output.elem_sec, input.elem_sec)
assert_eq(output.export_sec, input.export_sec)
assert_eq(@binary.encode_module(input).unwrap(), saved)
}
}