Lines
83.16 %
Functions
30 %
Branches
100 %
//! Codegen for the runtime path of `DO` / `DO*`.
//!
//! `compile_do_runtime_loop` emits the actual wasm `block / loop / br_if`
//! prelude + body + step sequence; the three wrapper functions
//! (`compile_do_runtime` / `_for_effect` / `_for_stack`) differ only
//! in how they consume the result-forms after the loop exits — value
//! position, effect-only, or push-to-stack respectively.
//! Both `DO` and `DO*` parallel/sequential semantics are encoded as
//! `DoLoop.sequential`. The step assignment block here is the only
//! place that distinction surfaces in the runtime codegen.
use crate::ast::{Expr, WasmType};
use crate::compiler::context::CompileContext;
use crate::compiler::emit::FunctionEmitter;
use crate::compiler::expr::{
compile_body, compile_for_effect, compile_for_stack, compile_nil, emit_nil_default, eval_value,
};
use crate::error::Result;
use crate::runtime::{Symbol, SymbolKind, SymbolTable};
use super::common::{DoLoop, collect_setf_targets, infer_wasm_type, promote_to_wasm_local};
fn compile_do_runtime_loop(
ctx: &mut CompileContext,
emit: &mut FunctionEmitter,
local: &mut SymbolTable,
dl: &DoLoop<'_>,
) -> Result<()> {
// Declare the DO-vars before promoting body setf targets so the
// setf-target type inference (which peeks at CONS step expressions)
// can resolve references to the loop variables. Otherwise an
// accumulator like `(setf result (cons i result))` would be allocated
// with a default element type before `i`'s real type is known.
let mut wasm_vars: Vec<(String, u32, WasmType, Option<Expr>)> = Vec::new();
for v in dl.vars {
let init_expr = v.init.clone().unwrap_or(Expr::Nil);
// Probe on a CLONE: the value is only used to size the local and detect a
// nil init; the live emission below (compile_for_effect / compile_for_stack)
// is the single application of any seed side effects.
let init_val = eval_value(&mut local.clone(), &init_expr)?;
let ty = infer_wasm_type(&init_val, v.step.as_ref(), local);
let idx = ctx.alloc_local(ty)?;
if matches!(init_val, Expr::Nil) {
// A non-literal init that *resolves* to nil (e.g. `(progn (side-effect) nil)`)
// must still emit its effects before the typed nil seed.
if !matches!(init_expr, Expr::Nil) {
compile_for_effect(ctx, emit, local, &init_expr)?;
}
emit_nil_default(ctx, emit, ty)?;
} else {
compile_for_stack(ctx, emit, local, &init_expr)?;
emit.local_set(idx);
local.define(
Symbol::new(&v.name, SymbolKind::Variable).with_value(Expr::WasmLocal(idx, ty)),
);
wasm_vars.push((v.name.clone(), idx, ty, v.step.clone()));
let do_var_names: Vec<_> = dl.vars.iter().map(|v| v.name.clone()).collect();
for (target, rhs) in collect_setf_targets(dl.body) {
if !do_var_names.contains(&target) {
promote_to_wasm_local(ctx, emit, local, &target, rhs.as_ref())?;
// block $exit
emit.block_start();
// loop $continue
emit.loop_start();
compile_for_stack(ctx, emit, local, dl.end_test)?;
emit.br_if(1);
for expr in dl.body {
compile_for_effect(ctx, emit, local, expr)?;
if dl.sequential {
for (_, idx, _, step) in &wasm_vars {
if let Some(step_expr) = step {
compile_for_stack(ctx, emit, local, step_expr)?;
emit.local_set(*idx);
let step_vars: Vec<_> = wasm_vars
.iter()
.filter(|(_, _, _, step)| step.is_some())
.collect();
for (_, _, _, step) in &step_vars {
compile_for_stack(ctx, emit, local, step.as_ref().unwrap())?;
for (_, idx, _, _) in step_vars.iter().rev() {
emit.br(0);
emit.block_end(); // end loop
emit.block_end(); // end block
Ok(())
pub(super) fn compile_do_runtime(
symbols: &mut SymbolTable,
let mut local = symbols.clone();
compile_do_runtime_loop(ctx, emit, &mut local, dl)?;
if dl.result_forms.is_empty() {
compile_nil(ctx, emit);
compile_body(ctx, emit, &mut local, dl.result_forms)?;
pub(super) fn compile_do_runtime_for_effect(
for expr in dl.result_forms {
compile_for_effect(ctx, emit, &mut local, expr)?;
pub(super) fn compile_do_runtime_for_stack(
) -> Result<WasmType> {
// No result forms ≡ nil — falsy i31, typed `Bool` so it serializes as
// Nil and agrees with the eval mirror's `Expr::Nil`.
emit.i32_const(0);
Ok(WasmType::Bool)
let last = dl.result_forms.last().unwrap();
for expr in &dl.result_forms[..dl.result_forms.len() - 1] {
compile_for_stack(ctx, emit, &mut local, last)