use anyhow::{Context, Result};
use std::sync::atomic::{AtomicU32, Ordering};
use winliner::{Optimizer, ProfileBuilder};
fn assert_optimize(
optimizer: &Optimizer,
profile: &[&[(u32, u32)]],
input: &str,
expected_wat: &str,
) -> Result<()> {
let _ = env_logger::try_init();
let mut builder = ProfileBuilder::new();
for (call_site, calls) in profile.iter().enumerate() {
let call_site = u32::try_from(call_site).unwrap();
for (callee, count) in *calls {
for _ in 0..*count {
builder.add_indirect_call(*callee, call_site);
}
}
}
let profile = builder.build();
let input = wat::parse_str(input).context("failed to parse test input as WAT")?;
let actual_wasm = optimizer
.optimize(&profile, &input)
.context("failed to optimize input with given profile")?;
if log::log_enabled!(log::Level::Debug) {
static TEST_CASE: AtomicU32 = AtomicU32::new(0);
let n = TEST_CASE.fetch_add(1, Ordering::AcqRel);
log::debug!("Writing instrumented Wasm to `optimized{n}.wasm`");
std::fs::write(format!("optimized{n}.wasm"), &actual_wasm).unwrap();
}
let actual_wat =
wasmprinter::print_bytes(&actual_wasm).context("failed to print optimized Wasm as WAT")?;
super::assert_no_diff(expected_wat.trim(), actual_wat.trim());
wasmparser::Validator::new_with_features(wasmparser::WasmFeatures {
function_references: true,
..Default::default()
})
.validate_all(&actual_wasm)
.context("the optimized wasm failed to validate")?;
Ok(())
}
#[test]
fn basic() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(1),
&[&[(42, 1)]],
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 0) (result i32)
i32.const 36
)
(func (;1;) (type 1) (param i32) (result i32)
local.get 0
call_indirect (type 0)
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 42) funcref (ref.func 0))
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 0) (result i32)
(local i32)
i32.const 36
)
(func (;1;) (type 1) (param i32) (result i32)
(local i32)
local.get 0
local.tee 1
i32.const 42
i32.eq
if (type 0) (result i32) ;; label = @1
i32.const 36
else
local.get 1
call_indirect (type 0)
end
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 42) funcref (ref.func 0))
)
"#,
)
}
#[test]
fn not_enough_total_calls() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(2),
&[&[(42, 1)]],
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 0) (result i32)
i32.const 36
)
(func (;1;) (type 1) (param i32) (result i32)
local.get 0
call_indirect (type 0)
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 42) funcref (ref.func 0))
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 0) (result i32)
(local i32)
i32.const 36
)
(func (;1;) (type 1) (param i32) (result i32)
(local i32)
local.get 0
call_indirect (type 0)
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 42) funcref (ref.func 0))
)
"#,
)
}
#[test]
fn multiple_call_sites() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(2),
&[&[(0, 2)], &[(1, 1)], &[(2, 2)], &[(3, 1)]],
r#"
(module
(type (func (result i32)))
(func (type 0)
i32.const 11
)
(func (type 0)
i32.const 22
)
(func (type 0)
i32.const 33
)
(func (type 0)
i32.const 44
)
(func (param i32 i32 i32 i32)
local.get 0
call_indirect (type 0)
drop
local.get 1
call_indirect (type 0)
drop
local.get 2
call_indirect (type 0)
drop
local.get 3
call_indirect (type 0)
drop
)
(table 100 100 funcref)
(elem (i32.const 0) funcref (ref.func 0) (ref.func 1) (ref.func 2) (ref.func 3))
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32 i32 i32 i32)))
(func (;0;) (type 0) (result i32)
(local i32)
i32.const 11
)
(func (;1;) (type 0) (result i32)
(local i32)
i32.const 22
)
(func (;2;) (type 0) (result i32)
(local i32)
i32.const 33
)
(func (;3;) (type 0) (result i32)
(local i32)
i32.const 44
)
(func (;4;) (type 1) (param i32 i32 i32 i32)
(local i32)
local.get 0
local.tee 4
i32.const 0
i32.eq
if (type 0) (result i32) ;; label = @1
i32.const 11
else
local.get 4
call_indirect (type 0)
end
drop
local.get 1
call_indirect (type 0)
drop
local.get 2
local.tee 4
i32.const 2
i32.eq
if (type 0) (result i32) ;; label = @1
i32.const 33
else
local.get 4
call_indirect (type 0)
end
drop
local.get 3
call_indirect (type 0)
drop
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 0) funcref (ref.func 0) (ref.func 1) (ref.func 2) (ref.func 3))
)
"#,
)
}
#[test]
fn no_indirect_calls() -> Result<()> {
assert_optimize(
&Optimizer::new(),
&[],
r#"
(module
(type (func (result i32)))
(func (type 0)
i32.const 11
)
(func (type 0)
i32.const 22
)
(func (type 0)
i32.const 33
)
(func (type 0)
i32.const 44
)
(func
call 0
drop
call 1
drop
call 2
drop
call 3
drop
)
(table 100 100 funcref)
(elem (i32.const 0) funcref (ref.func 0) (ref.func 1) (ref.func 2) (ref.func 3))
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func))
(func (;0;) (type 0) (result i32)
(local i32)
i32.const 11
)
(func (;1;) (type 0) (result i32)
(local i32)
i32.const 22
)
(func (;2;) (type 0) (result i32)
(local i32)
i32.const 33
)
(func (;3;) (type 0) (result i32)
(local i32)
i32.const 44
)
(func (;4;) (type 1)
(local i32)
call 0
drop
call 1
drop
call 2
drop
call 3
drop
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 0) funcref (ref.func 0) (ref.func 1) (ref.func 2) (ref.func 3))
)
"#,
)
}
#[test]
fn multiple_params_and_results() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(1),
&[&[(42, 1)]],
r#"
(module
(type (func (param i32 i64) (result i32 i64)))
(func (type 0)
local.get 0
i32.const 1
i32.add
local.get 1
i64.const 1
i64.add
)
(func (param i32) (result i32 i64)
i32.const 11
i64.const 22
local.get 0
call_indirect (type 0)
)
(table 100 100 funcref)
(elem (i32.const 42) funcref (ref.func 0))
)
"#,
r#"
(module
(type (;0;) (func (param i32 i64) (result i32 i64)))
(type (;1;) (func (param i32) (result i32 i64)))
(func (;0;) (type 0) (param i32 i64) (result i32 i64)
(local i32)
local.get 0
i32.const 1
i32.add
local.get 1
i64.const 1
i64.add
)
(func (;1;) (type 1) (param i32) (result i32 i64)
(local i32 i32 i64)
i32.const 11
i64.const 22
local.get 0
local.tee 1
i32.const 42
i32.eq
if (type 0) (param i32 i64) (result i32 i64) ;; label = @1
local.set 3
local.set 2
local.get 2
i32.const 1
i32.add
local.get 3
i64.const 1
i64.add
else
local.get 1
call_indirect (type 0)
end
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 42) funcref (ref.func 0))
)
"#,
)
}
#[test]
fn multiple_callees_that_satisfy_inlining_conditions() -> Result<()> {
assert_optimize(
Optimizer::new().min_ratio(0.1)?.min_total_calls(1),
&[&[(10, 10), (20, 5)]],
r#"
(module
(type (func (result i32)))
(func (type 0)
i32.const 11
)
(func (type 0)
i32.const 22
)
(func (param i32) (result i32)
local.get 0
call_indirect (type 0)
)
(table 100 100 funcref)
(elem (i32.const 10) funcref (ref.func 0))
(elem (i32.const 20) funcref (ref.func 1))
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 0) (result i32)
(local i32)
i32.const 11
)
(func (;1;) (type 0) (result i32)
(local i32)
i32.const 22
)
(func (;2;) (type 1) (param i32) (result i32)
(local i32)
local.get 0
local.tee 1
i32.const 10
i32.eq
if (type 0) (result i32) ;; label = @1
i32.const 11
else
local.get 1
call_indirect (type 0)
end
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 10) funcref (ref.func 0))
(elem (;1;) (i32.const 20) funcref (ref.func 1))
)
"#,
)
}
#[test]
fn multiple_callees_that_satisfy_inlining_conditions_with_tie_breaking() -> Result<()> {
assert_optimize(
Optimizer::new().min_ratio(0.1)?.min_total_calls(1),
&[&[(10, 10), (20, 10)]],
r#"
(module
(type (func (result i32)))
(func (type 0)
i32.const 11
)
(func (type 0)
i32.const 22
)
(func (param i32) (result i32)
local.get 0
call_indirect (type 0)
)
(table 100 100 funcref)
(elem (i32.const 10) funcref (ref.func 0))
(elem (i32.const 20) funcref (ref.func 1))
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 0) (result i32)
(local i32)
i32.const 11
)
(func (;1;) (type 0) (result i32)
(local i32)
i32.const 22
)
(func (;2;) (type 1) (param i32) (result i32)
(local i32)
local.get 0
local.tee 1
i32.const 10
i32.eq
if (type 0) (result i32) ;; label = @1
i32.const 11
else
local.get 1
call_indirect (type 0)
end
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 10) funcref (ref.func 0))
(elem (;1;) (i32.const 20) funcref (ref.func 1))
)
"#,
)
}
#[test]
fn not_high_enough_ratio() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(1).min_ratio(1.0)?,
&[&[(0, 1), (1, 9_999_999)]],
r#"
(module
(type (func (result i32)))
(func (type 0)
i32.const 11
)
(func (type 0)
i32.const 22
)
(func (param i32) (result i32)
local.get 0
call_indirect (type 0)
)
(table 100 100 funcref)
(elem (i32.const 0) funcref (ref.func 0) (ref.func 1))
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 0) (result i32)
(local i32)
i32.const 11
)
(func (;1;) (type 0) (result i32)
(local i32)
i32.const 22
)
(func (;2;) (type 1) (param i32) (result i32)
(local i32)
local.get 0
call_indirect (type 0)
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 0) funcref (ref.func 0) (ref.func 1))
)
"#,
)
}
#[test]
fn dont_inline_direct_recursion() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(1),
&[&[(0, 1)]],
r#"
(module
(type (func (param i32) (result i32)))
(func (type 0)
local.get 0
local.get 0
call_indirect (type 0)
)
(table 100 100 funcref)
(elem (i32.const 0) funcref (ref.func 0))
)
"#,
r#"
(module
(type (;0;) (func (param i32) (result i32)))
(func (;0;) (type 0) (param i32) (result i32)
(local i32)
local.get 0
local.get 0
call_indirect (type 0)
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 0) funcref (ref.func 0))
)
"#,
)
}
#[test]
fn dont_inline_direct_recursion_with_imports() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(100),
&[&[(1, 100)]],
r#"
(module
(type (func (param i32) (result i32)))
(import "foo" "bar" (func (type 0)))
(func (type 0)
local.get 0
local.get 0
call_indirect (type 0)
)
(table 100 100 funcref)
(elem (i32.const 0) funcref (ref.func 1))
)
"#,
r#"
(module
(type (;0;) (func (param i32) (result i32)))
(import "foo" "bar" (func (;0;) (type 0)))
(func (;1;) (type 0) (param i32) (result i32)
(local i32)
local.get 0
local.get 0
call_indirect (type 0)
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 0) funcref (ref.func 1))
)
"#,
)
}
#[test]
fn multiple_funcref_tables() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(1),
&[&[(0, 1)], &[(0, 1)]],
r#"
(module
(type (func (result i32)))
(func (type 0)
i32.const 11
)
(func (type 0)
i32.const 22
)
(func (param i32 i32) (result i32 i32)
local.get 0
call_indirect 0 (type 0)
local.get 1
call_indirect 1 (type 0)
)
(table 100 100 funcref)
(elem (table 0) (i32.const 0) funcref (ref.func 0))
(table 100 100 funcref)
(elem (table 1) (i32.const 0) funcref (ref.func 1))
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32 i32) (result i32 i32)))
(func (;0;) (type 0) (result i32)
(local i32)
i32.const 11
)
(func (;1;) (type 0) (result i32)
(local i32)
i32.const 22
)
(func (;2;) (type 1) (param i32 i32) (result i32 i32)
(local i32)
local.get 0
local.tee 2
i32.const 0
i32.eq
if (type 0) (result i32) ;; label = @1
i32.const 11
else
local.get 2
call_indirect (type 0)
end
local.get 1
local.tee 2
i32.const 0
i32.eq
if (type 0) (result i32) ;; label = @1
i32.const 22
else
local.get 2
call_indirect 1 (type 0)
end
)
(table (;0;) 100 100 funcref)
(table (;1;) 100 100 funcref)
(elem (;0;) (i32.const 0) funcref (ref.func 0))
(elem (;1;) (table 1) (i32.const 0) funcref (ref.func 1))
)
"#,
)
}
#[test]
fn inlining_into_inlined_function() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(1_000).max_inline_depth(2),
&[&[(0, 1_000)], &[(1, 1_000)]],
r#"
(module
(type (func (result i32)))
(type (func (param i32) (result i32)))
(func (type 0)
i32.const 11
)
(func (type 1)
local.get 0
call_indirect (type 0)
)
(func (param i32 i32) (result i32)
local.get 0
local.get 1
call_indirect (type 1)
)
(table 100 100 funcref)
(elem (i32.const 0) funcref (ref.func 0) (ref.func 1))
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(type (;2;) (func (param i32 i32) (result i32)))
(func (;0;) (type 0) (result i32)
(local i32)
i32.const 11
)
(func (;1;) (type 1) (param i32) (result i32)
(local i32)
local.get 0
local.tee 1
i32.const 0
i32.eq
if (type 0) (result i32) ;; label = @1
i32.const 11
else
local.get 1
call_indirect (type 0)
end
)
(func (;2;) (type 2) (param i32 i32) (result i32)
(local i32 i32)
local.get 0
local.get 1
local.tee 2
i32.const 1
i32.eq
if (type 1) (param i32) (result i32) ;; label = @1
local.set 3
local.get 3
local.tee 2
i32.const 0
i32.eq
if (type 0) (result i32) ;; label = @2
i32.const 11
else
local.get 2
call_indirect (type 0)
end
else
local.get 2
call_indirect (type 1)
end
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 0) funcref (ref.func 0) (ref.func 1))
)
"#,
)
}
#[test]
fn reach_inline_depth_limit() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(1_000).max_inline_depth(1),
&[&[(0, 1_000)], &[(1, 1_000)]],
r#"
(module
(type (func (result i32)))
(type (func (param i32) (result i32)))
(func (type 0)
i32.const 11
)
(func (type 1)
local.get 0
call_indirect (type 0)
)
(func (param i32 i32) (result i32)
local.get 0
local.get 1
call_indirect (type 1)
)
(table 100 100 funcref)
(elem (i32.const 0) funcref (ref.func 0) (ref.func 1))
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(type (;2;) (func (param i32 i32) (result i32)))
(func (;0;) (type 0) (result i32)
(local i32)
i32.const 11
)
(func (;1;) (type 1) (param i32) (result i32)
(local i32)
local.get 0
local.tee 1
i32.const 0
i32.eq
if (type 0) (result i32) ;; label = @1
i32.const 11
else
local.get 1
call_indirect (type 0)
end
)
(func (;2;) (type 2) (param i32 i32) (result i32)
(local i32 i32)
local.get 0
local.get 1
local.tee 2
i32.const 1
i32.eq
if (type 1) (param i32) (result i32) ;; label = @1
local.set 3
local.get 3
call_indirect (type 0)
else
local.get 2
call_indirect (type 1)
end
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 0) funcref (ref.func 0) (ref.func 1))
)
"#,
)
}
#[test]
fn mutual_recursion() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(100).max_inline_depth(100),
&[&[(1, 100)], &[(0, 100)]],
r#"
(module
(type (func (param i32 i32) (result i32)))
(func (type 0)
local.get 0
local.get 1
local.get 0
call_indirect (type 0)
)
(func (type 0)
local.get 0
local.get 1
local.get 1
call_indirect (type 0)
)
(table 100 100 funcref)
(elem (i32.const 0) funcref (ref.func 0) (ref.func 1))
)
"#,
r#"
(module
(type (;0;) (func (param i32 i32) (result i32)))
(func (;0;) (type 0) (param i32 i32) (result i32)
(local i32 i32 i32)
local.get 0
local.get 1
local.get 0
local.tee 2
i32.const 1
i32.eq
if (type 0) (param i32 i32) (result i32) ;; label = @1
local.set 4
local.set 3
local.get 3
local.get 4
local.get 4
call_indirect (type 0)
else
local.get 2
call_indirect (type 0)
end
)
(func (;1;) (type 0) (param i32 i32) (result i32)
(local i32 i32 i32)
local.get 0
local.get 1
local.get 1
local.tee 2
i32.const 0
i32.eq
if (type 0) (param i32 i32) (result i32) ;; label = @1
local.set 4
local.set 3
local.get 3
local.get 4
local.get 3
call_indirect (type 0)
else
local.get 2
call_indirect (type 0)
end
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 0) funcref (ref.func 0) (ref.func 1))
)
"#,
)
}
#[test]
fn inline_a_function_with_many_locals() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(100),
&[&[(0, 100)]],
r#"
(module
(type (func (result i32)))
(func (type 0) (local i32 i64 f32 f64 v128 externref funcref)
local.get 0
)
(func (param i32) (result i32)
(local funcref externref v128 f64 f32 i64 i32)
local.get 0
call_indirect (type 0)
)
(table 100 100 funcref)
(elem (i32.const 0) funcref (ref.func 0) (ref.func 1))
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 0) (result i32)
(local i32 i64 f32 f64 v128 externref funcref i32)
local.get 0
)
(func (;1;) (type 1) (param i32) (result i32)
(local funcref externref v128 f64 f32 i64 i32 i32 i32 i64 f32 f64 v128 externref funcref)
local.get 0
local.tee 8
i32.const 0
i32.eq
if (type 0) (result i32) ;; label = @1
local.get 9
else
local.get 8
call_indirect (type 0)
end
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 0) funcref (ref.func 0) (ref.func 1))
)
"#,
)
}
#[test]
fn counters() -> Result<()> {
assert_optimize(
Optimizer::new()
.min_total_calls(1)
.emit_feedback_counters(true),
&[&[(42, 1)]],
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 0) (result i32)
i32.const 36
)
(func (;1;) (type 1) (param i32) (result i32)
local.get 0
call_indirect (type 0)
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 42) funcref (ref.func 0))
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 0) (result i32)
(local i32)
i32.const 36
)
(func (;1;) (type 1) (param i32) (result i32)
(local i32)
local.get 0
local.tee 1
i32.const 42
i32.eq
if (type 0) (result i32) ;; label = @1
global.get 0
i64.const 1
i64.add
global.set 0
i32.const 36
else
global.get 1
i64.const 1
i64.add
global.set 1
local.get 1
call_indirect (type 0)
end
)
(table (;0;) 100 100 funcref)
(global (;0;) (mut i64) i64.const 0)
(global (;1;) (mut i64) i64.const 0)
(export "__winliner_counter_0_correct" (global 0))
(export "__winliner_counter_0_incorrect" (global 1))
(elem (;0;) (i32.const 42) funcref (ref.func 0))
)
"#,
)
}
#[test]
fn return_from_inlined_function_no_blocks() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(1),
&[&[(42, 1)]],
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 0) (result i32)
i32.const 36
return
)
(func (;1;) (type 1) (param i32) (result i32)
local.get 0
call_indirect (type 0)
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 42) funcref (ref.func 0))
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 0) (result i32)
(local i32)
i32.const 36
return
)
(func (;1;) (type 1) (param i32) (result i32)
(local i32)
local.get 0
local.tee 1
i32.const 42
i32.eq
if (type 0) (result i32) ;; label = @1
i32.const 36
br 0 (;@1;)
else
local.get 1
call_indirect (type 0)
end
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 42) funcref (ref.func 0))
)
"#,
)
}
#[test]
fn return_from_inlined_function_nested_blocks() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(1),
&[&[(42, 1)]],
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 0) (result i32)
block
loop
i32.const 1
if
i32.const 36
return
end
end
end
unreachable
)
(func (;1;) (type 1) (param i32) (result i32)
block
loop
i32.const 1
if
local.get 0
call_indirect (type 0)
return
end
end
end
unreachable
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 42) funcref (ref.func 0))
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 0) (result i32)
(local i32)
block ;; label = @1
loop ;; label = @2
i32.const 1
if ;; label = @3
i32.const 36
return
end
end
end
unreachable
)
(func (;1;) (type 1) (param i32) (result i32)
(local i32)
block ;; label = @1
loop ;; label = @2
i32.const 1
if ;; label = @3
local.get 0
local.tee 1
i32.const 42
i32.eq
if (type 0) (result i32) ;; label = @4
block ;; label = @5
loop ;; label = @6
i32.const 1
if ;; label = @7
i32.const 36
br 3 (;@4;)
end
end
end
unreachable
else
local.get 1
call_indirect (type 0)
end
return
end
end
end
unreachable
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 42) funcref (ref.func 0))
)
"#,
)
}
#[test]
fn fuel() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(1).fuel(Some(1)),
&[&[(42, 1)], &[(42, 1)]],
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 0) (result i32)
i32.const 42
)
(func (;1;) (type 1) (param i32) (result i32)
local.get 0
call_indirect (type 0)
drop
local.get 0
call_indirect (type 0)
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 42) funcref (ref.func 0))
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 0) (result i32)
(local i32)
i32.const 42
)
(func (;1;) (type 1) (param i32) (result i32)
(local i32)
local.get 0
local.tee 1
i32.const 42
i32.eq
if (type 0) (result i32) ;; label = @1
i32.const 42
else
local.get 1
call_indirect (type 0)
end
drop
local.get 0
call_indirect (type 0)
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 42) funcref (ref.func 0))
)
"#,
)
}
mod bogus_profile {
use super::*;
#[test]
fn call_to_func_of_wrong_type() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(1),
&[&[(0, 1)]],
r#"
(module
(type (func (result i32)))
(func (param i32) (result i32)
local.get 0
)
(func (param i32) (result i32)
local.get 0
call_indirect (type 0)
)
(table 100 100 funcref)
(elem (i32.const 0) funcref (ref.func 0))
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 1) (param i32) (result i32)
(local i32)
local.get 0
)
(func (;1;) (type 1) (param i32) (result i32)
(local i32)
local.get 0
call_indirect (type 0)
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 0) funcref (ref.func 0))
)
"#,
)
}
#[test]
fn call_to_out_of_bounds_function_index() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(1),
&[&[(999, 1)]],
r#"
(module
(type (func (result i32)))
(func (param i32) (result i32)
local.get 0
call_indirect (type 0)
)
(table 100 100 funcref)
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 1) (param i32) (result i32)
(local i32)
local.get 0
call_indirect (type 0)
)
(table (;0;) 100 100 funcref)
)
"#,
)
}
#[test]
fn more_call_site_data_than_call_sites() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(1),
&[&[(0, 999)]],
r#"
(module
(func (param i32) (result i32)
local.get 0
)
(table 100 100 funcref)
(elem (i32.const 0) funcref (ref.func 0))
)
"#,
r#"
(module
(type (;0;) (func (param i32) (result i32)))
(func (;0;) (type 0) (param i32) (result i32)
(local i32)
local.get 0
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 0) funcref (ref.func 0))
)
"#,
)
}
#[test]
fn call_to_unknown_table_element() -> Result<()> {
assert_optimize(
Optimizer::new().min_total_calls(1),
&[&[(0, 1)]],
r#"
(module
(type (func (result i32)))
(func (param i32) (result i32)
local.get 0
call_indirect (type 0)
)
(table 100 100 funcref)
(elem (i32.const 1) funcref (ref.func 0))
)
"#,
r#"
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(func (;0;) (type 1) (param i32) (result i32)
(local i32)
local.get 0
call_indirect (type 0)
)
(table (;0;) 100 100 funcref)
(elem (;0;) (i32.const 1) funcref (ref.func 0))
)
"#,
)
}
}