Implement float IR operations: 25 words compiled to native WASM f64

Convert 25 float words from host functions to IR primitives:
- Stack: FDROP FDUP FSWAP FOVER FNIP FTUCK
- Arithmetic: F+ F- F* F/ FNEGATE FABS FSQRT FMIN FMAX FLOOR FROUND
- Comparisons: F0= F0< F= F<
- Memory: F@ F!
- Conversions: S>F F>S

24 new IrOp variants compiled to native WASM f64 instructions.
EmitCtx struct threads f64 scratch locals through all emit functions.
Float constant folding: 1.5E0 2.5E0 F+ folds to PushF64(4.0).
Float peephole: PushF64+FDrop, FDup+FDrop, FSwap+FSwap eliminated.
Float literals now compile as PushF64 IR ops instead of anonymous host calls.

~420 lines of Rust closure code removed from outer.rs.
All 14 optimizations now implemented. 430 tests passing.
This commit is contained in:
2026-04-02 13:47:28 +02:00
parent f7a8bf1d24
commit bf7581ad9e
4 changed files with 893 additions and 423 deletions
+133
View File
@@ -194,6 +194,26 @@ fn peephole_one_pass(ops: Vec<IrOp>) -> Vec<IrOp> {
out.pop();
continue;
}
// PushF64, FDrop => remove both
(IrOp::PushF64(_), IrOp::FDrop) => {
out.pop();
continue;
}
// FDup, FDrop => remove both
(IrOp::FDup, IrOp::FDrop) => {
out.pop();
continue;
}
// FSwap, FSwap => remove both
(IrOp::FSwap, IrOp::FSwap) => {
out.pop();
continue;
}
// FNegate, FNegate => remove both
(IrOp::FNegate, IrOp::FNegate) => {
out.pop();
continue;
}
// Over, Over => TwoDup
(IrOp::Over, IrOp::Over) => {
out.pop();
@@ -236,6 +256,17 @@ fn constant_fold(ops: Vec<IrOp>) -> Vec<IrOp> {
continue;
}
// Try float binary fold: last two outputs are PushF64
if out.len() >= 2
&& let Some(result) =
try_float_binary_fold(&out[out.len() - 2], &out[out.len() - 1], &op)
{
out.pop();
out.pop();
out.push(IrOp::PushF64(result));
continue;
}
// Try unary fold: last output is PushI32, current op is foldable
if !out.is_empty()
&& let Some(result) = try_unary_fold(&out[out.len() - 1], &op)
@@ -245,6 +276,15 @@ fn constant_fold(ops: Vec<IrOp>) -> Vec<IrOp> {
continue;
}
// Try float unary fold: last output is PushF64
if !out.is_empty()
&& let Some(result) = try_float_unary_fold(&out[out.len() - 1], &op)
{
out.pop();
out.push(IrOp::PushF64(result));
continue;
}
out.push(op);
}
out
@@ -317,6 +357,53 @@ fn try_unary_fold(n_op: &IrOp, op: &IrOp) -> Option<i32> {
}
}
/// Try to fold a binary float operation on two constants.
fn try_float_binary_fold(a_op: &IrOp, b_op: &IrOp, op: &IrOp) -> Option<f64> {
let (a, b) = match (a_op, b_op) {
(IrOp::PushF64(a), IrOp::PushF64(b)) => (*a, *b),
_ => return None,
};
match op {
IrOp::FAdd => Some(a + b),
IrOp::FSub => Some(a - b),
IrOp::FMul => Some(a * b),
IrOp::FDiv => {
if b != 0.0 {
Some(a / b)
} else {
None
}
}
IrOp::FMin => Some(a.min(b)),
IrOp::FMax => Some(a.max(b)),
_ => None,
}
}
/// Try to fold a unary float operation on a constant.
fn try_float_unary_fold(n_op: &IrOp, op: &IrOp) -> Option<f64> {
let n = match n_op {
IrOp::PushF64(n) => *n,
_ => return None,
};
match op {
IrOp::FNegate => Some(-n),
IrOp::FAbs => Some(n.abs()),
IrOp::FSqrt => {
if n >= 0.0 {
Some(n.sqrt())
} else {
None
}
}
IrOp::FFloor => Some(n.floor()),
IrOp::FRound => Some(n.round_ties_even()),
_ => None,
}
}
// ---------------------------------------------------------------------------
// Pass 3: Strength reduction
// ---------------------------------------------------------------------------
@@ -779,6 +866,52 @@ mod tests {
));
}
// Float peephole tests
#[test]
fn float_push_fdrop_removed() {
assert_eq!(opt(vec![IrOp::PushF64(1.0), IrOp::FDrop]), vec![]);
}
#[test]
fn float_fdup_fdrop_removed() {
assert_eq!(opt(vec![IrOp::FDup, IrOp::FDrop]), vec![]);
}
#[test]
fn float_fswap_fswap_removed() {
assert_eq!(opt(vec![IrOp::FSwap, IrOp::FSwap]), vec![]);
}
#[test]
fn float_fnegate_fnegate_removed() {
assert_eq!(opt(vec![IrOp::FNegate, IrOp::FNegate]), vec![]);
}
// Float constant folding tests
#[test]
fn float_constant_fold_add() {
assert_eq!(
opt(vec![IrOp::PushF64(1.5), IrOp::PushF64(2.5), IrOp::FAdd]),
vec![IrOp::PushF64(4.0)]
);
}
#[test]
fn float_constant_fold_negate() {
assert_eq!(
opt(vec![IrOp::PushF64(3.0), IrOp::FNegate]),
vec![IrOp::PushF64(-3.0)]
);
}
#[test]
fn float_constant_fold_sqrt() {
assert_eq!(
opt(vec![IrOp::PushF64(9.0), IrOp::FSqrt]),
vec![IrOp::PushF64(3.0)]
);
}
#[test]
fn no_inline_large() {
let mut bodies = HashMap::new();