use crate::op::*;
use crate::program::*;
use indexmap::IndexMap;
use lex_ast as a;
#[derive(Default)]
struct ConstPool {
pool: Vec<Const>,
fields: IndexMap<String, u32>,
variants: IndexMap<String, u32>,
node_ids: IndexMap<String, u32>,
ints: IndexMap<i64, u32>,
bools: IndexMap<u8, u32>,
strs: IndexMap<String, u32>,
record_shapes: Vec<Vec<u32>>,
record_shape_dedup: IndexMap<Vec<u32>, u32>,
}
impl ConstPool {
fn field(&mut self, name: &str) -> u32 {
if let Some(i) = self.fields.get(name) { return *i; }
let i = self.pool.len() as u32;
self.pool.push(Const::FieldName(name.into()));
self.fields.insert(name.into(), i);
i
}
fn variant(&mut self, name: &str) -> u32 {
if let Some(i) = self.variants.get(name) { return *i; }
let i = self.pool.len() as u32;
self.pool.push(Const::VariantName(name.into()));
self.variants.insert(name.into(), i);
i
}
fn node_id(&mut self, name: &str) -> u32 {
if let Some(i) = self.node_ids.get(name) { return *i; }
let i = self.pool.len() as u32;
self.pool.push(Const::NodeId(name.into()));
self.node_ids.insert(name.into(), i);
i
}
fn int(&mut self, n: i64) -> u32 {
if let Some(i) = self.ints.get(&n) { return *i; }
let i = self.pool.len() as u32;
self.pool.push(Const::Int(n));
self.ints.insert(n, i);
i
}
fn bool(&mut self, b: bool) -> u32 {
let key = b as u8;
if let Some(i) = self.bools.get(&key) { return *i; }
let i = self.pool.len() as u32;
self.pool.push(Const::Bool(b));
self.bools.insert(key, i);
i
}
fn str(&mut self, s: &str) -> u32 {
if let Some(i) = self.strs.get(s) { return *i; }
let i = self.pool.len() as u32;
self.pool.push(Const::Str(s.into()));
self.strs.insert(s.into(), i);
i
}
fn float(&mut self, f: f64) -> u32 {
let i = self.pool.len() as u32;
self.pool.push(Const::Float(f));
i
}
fn unit(&mut self) -> u32 {
let i = self.pool.len() as u32;
self.pool.push(Const::Unit);
i
}
fn record_shape(&mut self, idxs: Vec<u32>) -> u32 {
if let Some(i) = self.record_shape_dedup.get(&idxs) {
return *i;
}
let i = self.record_shapes.len() as u32;
self.record_shape_dedup.insert(idxs.clone(), i);
self.record_shapes.push(idxs);
i
}
}
pub fn compile_program(stages: &[a::Stage]) -> Program {
let mut p = Program {
constants: Vec::new(),
functions: Vec::new(),
function_names: IndexMap::new(),
module_aliases: IndexMap::new(),
entry: None,
record_shapes: Vec::new(),
};
for s in stages {
if let a::Stage::Import(i) = s {
let module = i.reference.strip_prefix("std.").unwrap_or(&i.reference).to_string();
p.module_aliases.insert(i.alias.clone(), module);
}
}
for s in stages {
if let a::Stage::FnDecl(fd) = s {
let idx = p.functions.len() as u32;
p.function_names.insert(fd.name.clone(), idx);
p.functions.push(Function {
name: fd.name.clone(),
arity: fd.params.len() as u16,
locals_count: 0,
code: Vec::new(),
effects: fd.effects.iter().map(|e| DeclaredEffect {
kind: e.name.clone(),
arg: e.arg.as_ref().map(|a| match a {
a::EffectArg::Str { value } => EffectArg::Str(value.clone()),
a::EffectArg::Int { value } => EffectArg::Int(*value),
a::EffectArg::Ident { value } => EffectArg::Ident(value.clone()),
}),
}).collect(),
body_hash: crate::program::ZERO_BODY_HASH,
refinements: fd.params.iter().map(|p| match &p.ty {
a::TypeExpr::Refined { binding, predicate, .. } =>
Some(crate::program::Refinement {
binding: binding.clone(),
predicate: (**predicate).clone(),
}),
_ => None,
}).collect(),
});
}
}
let mut pool = ConstPool::default();
let function_names = p.function_names.clone();
let module_aliases = p.module_aliases.clone();
let mut pending_lambdas: Vec<PendingLambda> = Vec::new();
for s in stages {
if let a::Stage::FnDecl(_) = s {
let id_map = lex_ast::expr_ids(s);
let fd = match s { a::Stage::FnDecl(fd) => fd, _ => unreachable!() };
let mut fc = FnCompiler {
code: Vec::new(),
locals: IndexMap::new(),
next_local: 0,
peak_local: 0,
pool: &mut pool,
function_names: &function_names,
module_aliases: &module_aliases,
id_map: &id_map,
pending_lambdas: &mut pending_lambdas,
next_fn_id: &mut p.functions,
};
for param in &fd.params {
let i = fc.next_local;
fc.locals.insert(param.name.clone(), i);
fc.next_local += 1;
fc.peak_local = fc.next_local;
}
fc.compile_expr(&fd.body, true);
fc.code.push(Op::Return);
let code = std::mem::take(&mut fc.code);
let peak = fc.peak_local;
drop(fc);
let idx = function_names[&fd.name];
p.functions[idx as usize].code = code;
p.functions[idx as usize].locals_count = peak;
}
}
while let Some(pl) = pending_lambdas.pop() {
let id_map = std::collections::HashMap::new();
let mut fc = FnCompiler {
code: Vec::new(),
locals: IndexMap::new(),
next_local: 0,
peak_local: 0,
pool: &mut pool,
function_names: &function_names,
module_aliases: &module_aliases,
id_map: &id_map,
pending_lambdas: &mut pending_lambdas,
next_fn_id: &mut p.functions,
};
for name in &pl.capture_names {
let i = fc.next_local;
fc.locals.insert(name.clone(), i);
fc.next_local += 1;
fc.peak_local = fc.next_local;
}
for p in &pl.params {
let i = fc.next_local;
fc.locals.insert(p.name.clone(), i);
fc.next_local += 1;
fc.peak_local = fc.next_local;
}
fc.compile_expr(&pl.body, true);
fc.code.push(Op::Return);
let code = std::mem::take(&mut fc.code);
let peak = fc.peak_local;
drop(fc);
p.functions[pl.fn_id as usize].code = code;
p.functions[pl.fn_id as usize].locals_count = peak;
}
for f in p.functions.iter_mut() {
apply_peephole(&mut f.code, &pool.pool);
}
for f in p.functions.iter_mut() {
if f.body_hash == crate::program::ZERO_BODY_HASH {
f.body_hash = crate::program::compute_body_hash(
f.arity, f.locals_count, &f.code, &pool.record_shapes);
}
}
p.constants = pool.pool;
p.record_shapes = pool.record_shapes;
p
}
fn apply_peephole(code: &mut [Op], constants: &[Const]) {
if code.len() < 3 { return; }
let mut jump_targets = std::collections::HashSet::new();
for (pc, op) in code.iter().enumerate() {
let off = match op {
Op::Jump(off) | Op::JumpIf(off) | Op::JumpIfNot(off) => Some(*off),
_ => None,
};
if let Some(off) = off {
let target = (pc as i32 + 1 + off) as usize;
jump_targets.insert(target);
}
}
let n = code.len();
let mut k = 0;
while k + 2 < n {
if let (Op::LoadLocal(local_idx), Op::PushConst(imm_const_idx), Op::IntAdd)
= (code[k], code[k + 1], code[k + 2])
{
let imm_is_int = matches!(
constants.get(imm_const_idx as usize),
Some(Const::Int(_))
);
let safe = imm_is_int
&& !jump_targets.contains(&(k + 1))
&& !jump_targets.contains(&(k + 2));
if safe {
code[k] = Op::LoadLocalAddIntConst { local_idx, imm_const_idx };
k += 3;
continue;
}
}
k += 1;
}
}
#[derive(Debug, Clone)]
struct PendingLambda {
fn_id: u32,
capture_names: Vec<String>,
params: Vec<a::Param>,
body: a::CExpr,
}
struct FnCompiler<'a> {
code: Vec<Op>,
locals: IndexMap<String, u16>,
next_local: u16,
peak_local: u16,
pool: &'a mut ConstPool,
function_names: &'a IndexMap<String, u32>,
module_aliases: &'a IndexMap<String, String>,
id_map: &'a std::collections::HashMap<*const a::CExpr, lex_ast::NodeId>,
pending_lambdas: &'a mut Vec<PendingLambda>,
next_fn_id: &'a mut Vec<Function>,
}
impl<'a> FnCompiler<'a> {
fn alloc_local(&mut self, name: &str) -> u16 {
let i = self.next_local;
self.locals.insert(name.into(), i);
self.next_local += 1;
if self.next_local > self.peak_local { self.peak_local = self.next_local; }
i
}
fn emit(&mut self, op: Op) { self.code.push(op); }
fn compile_expr(&mut self, e: &a::CExpr, tail: bool) {
match e {
a::CExpr::Literal { value } => self.compile_lit(value),
a::CExpr::Var { name } => {
if let Some(slot) = self.locals.get(name) {
self.emit(Op::LoadLocal(*slot));
} else if let Some(&fn_id) = self.function_names.get(name) {
self.emit(Op::MakeClosure { fn_id, capture_count: 0 });
} else {
panic!("unknown var in compiler: {name}");
}
}
a::CExpr::Let { name, ty: _, value, body } => {
self.compile_expr(value, false);
let slot = self.alloc_local(name);
self.emit(Op::StoreLocal(slot));
self.compile_expr(body, tail);
}
a::CExpr::Block { statements, result } => {
for s in statements {
self.compile_expr(s, false);
self.emit(Op::Pop);
}
self.compile_expr(result, tail);
}
a::CExpr::Call { callee, args } => self.compile_call(e, callee, args, tail),
a::CExpr::Constructor { name, args } => {
for a in args { self.compile_expr(a, false); }
let name_idx = self.pool.variant(name);
self.emit(Op::MakeVariant { name_idx, arity: args.len() as u16 });
}
a::CExpr::Match { scrutinee, arms } => self.compile_match(scrutinee, arms, tail),
a::CExpr::RecordLit { fields } => {
let mut idxs = Vec::with_capacity(fields.len());
for f in fields {
self.compile_expr(&f.value, false);
idxs.push(self.pool.field(&f.name));
}
let field_count = idxs.len() as u16;
let shape_idx = self.pool.record_shape(idxs);
self.emit(Op::MakeRecord { shape_idx, field_count });
}
a::CExpr::TupleLit { items } => {
for it in items { self.compile_expr(it, false); }
self.emit(Op::MakeTuple(items.len() as u16));
}
a::CExpr::ListLit { items } => {
for it in items { self.compile_expr(it, false); }
self.emit(Op::MakeList(items.len() as u32));
}
a::CExpr::FieldAccess { value, field } => {
self.compile_expr(value, false);
let idx = self.pool.field(field);
self.emit(Op::GetField(idx));
}
a::CExpr::BinOp { op, lhs, rhs } => self.compile_binop(op, lhs, rhs),
a::CExpr::UnaryOp { op, expr } => {
self.compile_expr(expr, false);
match op.as_str() {
"-" => self.emit(Op::NumNeg),
"not" => self.emit(Op::BoolNot),
other => panic!("unknown unary: {other}"),
}
}
a::CExpr::Lambda { params, body, .. } => self.compile_lambda(params, body),
a::CExpr::Return { value } => {
self.compile_expr(value, true);
self.emit(Op::Return);
}
}
}
fn compile_lit(&mut self, l: &a::CLit) {
let i = match l {
a::CLit::Int { value } => self.pool.int(*value),
a::CLit::Bool { value } => self.pool.bool(*value),
a::CLit::Float { value } => {
let f: f64 = value.parse().unwrap_or(0.0);
self.pool.float(f)
}
a::CLit::Str { value } => self.pool.str(value),
a::CLit::Bytes { value: _ } => {
let i = self.pool.pool.len() as u32;
self.pool.pool.push(Const::Bytes(Vec::new()));
i
}
a::CLit::Unit => self.pool.unit(),
};
self.emit(Op::PushConst(i));
}
fn compile_call(&mut self, call_expr: &a::CExpr, callee: &a::CExpr, args: &[a::CExpr], tail: bool) {
let node_id = self
.id_map
.get(&(call_expr as *const a::CExpr))
.map(|n| n.as_str().to_string())
.unwrap_or_else(|| "n_?".into());
let node_id_idx = self.pool.node_id(&node_id);
if let a::CExpr::FieldAccess { value, field } = callee {
if let a::CExpr::Var { name } = value.as_ref() {
if let Some(module) = self.module_aliases.get(name) {
if self.try_emit_higher_order(module, field, args, node_id_idx) {
let _ = tail;
return;
}
for a in args { self.compile_expr(a, false); }
let kind_idx = self.pool.str(module);
let op_idx = self.pool.str(field);
self.emit(Op::EffectCall {
kind_idx,
op_idx,
arity: args.len() as u16,
node_id_idx,
});
let _ = tail;
return;
}
}
}
match callee {
a::CExpr::Var { name } if self.function_names.contains_key(name) => {
for a in args { self.compile_expr(a, false); }
let fn_id = self.function_names[name];
if tail {
self.emit(Op::TailCall { fn_id, arity: args.len() as u16, node_id_idx });
} else {
self.emit(Op::Call { fn_id, arity: args.len() as u16, node_id_idx });
}
}
a::CExpr::Var { name } if self.locals.contains_key(name) => {
let slot = self.locals[name];
self.emit(Op::LoadLocal(slot));
for a in args { self.compile_expr(a, false); }
self.emit(Op::CallClosure { arity: args.len() as u16, node_id_idx });
}
other => {
self.compile_expr(other, false);
for a in args { self.compile_expr(a, false); }
self.emit(Op::CallClosure { arity: args.len() as u16, node_id_idx });
}
}
}
fn compile_binop(&mut self, op: &str, lhs: &a::CExpr, rhs: &a::CExpr) {
self.compile_expr(lhs, false);
self.compile_expr(rhs, false);
match op {
"+" => self.emit(Op::NumAdd),
"-" => self.emit(Op::NumSub),
"*" => self.emit(Op::NumMul),
"/" => self.emit(Op::NumDiv),
"%" => self.emit(Op::NumMod),
"==" => self.emit(Op::NumEq),
"!=" => { self.emit(Op::NumEq); self.emit(Op::BoolNot); }
"<" => self.emit(Op::NumLt),
"<=" => self.emit(Op::NumLe),
">" => { self.emit_swap_top2(); self.emit(Op::NumLt); }
">=" => { self.emit_swap_top2(); self.emit(Op::NumLe); }
"and" => self.emit(Op::BoolAnd),
"or" => self.emit(Op::BoolOr),
other => panic!("unknown binop: {other:?}"),
}
}
fn emit_swap_top2(&mut self) {
let a = self.alloc_local("__swap_a");
let b = self.alloc_local("__swap_b");
self.emit(Op::StoreLocal(b));
self.emit(Op::StoreLocal(a));
self.emit(Op::LoadLocal(b));
self.emit(Op::LoadLocal(a));
}
fn compile_match(&mut self, scrutinee: &a::CExpr, arms: &[a::Arm], tail: bool) {
self.compile_expr(scrutinee, false);
let scrut_slot = self.alloc_local("__scrut");
self.emit(Op::StoreLocal(scrut_slot));
let mut end_jumps: Vec<usize> = Vec::new();
for arm in arms {
let arm_start_locals = self.next_local;
let arm_start_locals_map = self.locals.clone();
self.emit(Op::LoadLocal(scrut_slot));
let mut bindings: Vec<(String, u16)> = Vec::new();
let fail_jumps: Vec<usize> = self.compile_pattern_test(&arm.pattern, &mut bindings);
self.compile_expr(&arm.body, tail);
let j_end = self.code.len();
self.emit(Op::Jump(0));
end_jumps.push(j_end);
let fail_target = self.code.len() as i32;
for j in fail_jumps {
match &mut self.code[j] {
Op::JumpIfNot(off) => *off = fail_target - (j as i32 + 1),
Op::Jump(off) => *off = fail_target - (j as i32 + 1),
_ => {}
}
}
self.next_local = arm_start_locals;
self.locals = arm_start_locals_map;
}
let panic_msg_idx = self.pool.str("non-exhaustive match");
self.emit(Op::Panic(panic_msg_idx));
let end_target = self.code.len() as i32;
for j in end_jumps {
if let Op::Jump(off) = &mut self.code[j] {
*off = end_target - (j as i32 + 1);
}
}
}
fn compile_pattern_test(&mut self, p: &a::Pattern, bindings: &mut Vec<(String, u16)>) -> Vec<usize> {
let mut fails = Vec::new();
match p {
a::Pattern::PWild => { self.emit(Op::Pop); }
a::Pattern::PVar { name } => {
let slot = self.alloc_local(name);
self.emit(Op::StoreLocal(slot));
bindings.push((name.clone(), slot));
}
a::Pattern::PLiteral { value } => {
self.compile_lit(value);
match value {
a::CLit::Str { .. } => self.emit(Op::StrEq),
a::CLit::Bytes { .. } => self.emit(Op::BytesEq),
_ => self.emit(Op::NumEq),
}
let j = self.code.len();
self.emit(Op::JumpIfNot(0));
fails.push(j);
}
a::Pattern::PConstructor { name, args } => {
let name_idx = self.pool.variant(name);
self.emit(Op::Dup); self.emit(Op::TestVariant(name_idx)); let j_success = self.code.len();
self.emit(Op::JumpIf(0)); self.emit(Op::Pop); let j_fail = self.code.len();
self.emit(Op::Jump(0)); fails.push(j_fail);
let success_target = self.code.len() as i32;
if let Op::JumpIf(off) = &mut self.code[j_success] {
*off = success_target - (j_success as i32 + 1);
}
if args.is_empty() {
self.emit(Op::Pop);
} else if args.len() == 1 {
self.emit(Op::GetVariantArg(0));
let sub_fails = self.compile_pattern_test(&args[0], bindings);
fails.extend(sub_fails);
} else {
let slot = self.alloc_local("__variant");
self.emit(Op::StoreLocal(slot));
for (i, arg) in args.iter().enumerate() {
self.emit(Op::LoadLocal(slot));
self.emit(Op::GetVariantArg(i as u16));
let sub_fails = self.compile_pattern_test(arg, bindings);
fails.extend(sub_fails);
}
}
}
a::Pattern::PRecord { fields } => {
let slot = self.alloc_local("__record");
self.emit(Op::StoreLocal(slot));
for f in fields {
self.emit(Op::LoadLocal(slot));
let fi = self.pool.field(&f.name);
self.emit(Op::GetField(fi));
let sub_fails = self.compile_pattern_test(&f.pattern, bindings);
fails.extend(sub_fails);
}
}
a::Pattern::PTuple { items } => {
let slot = self.alloc_local("__tuple");
self.emit(Op::StoreLocal(slot));
for (i, item) in items.iter().enumerate() {
self.emit(Op::LoadLocal(slot));
self.emit(Op::GetElem(i as u16));
let sub_fails = self.compile_pattern_test(item, bindings);
fails.extend(sub_fails);
}
}
}
fails
}
fn compile_lambda(&mut self, params: &[a::Param], body: &a::CExpr) {
let mut bound: std::collections::HashSet<String> = params.iter().map(|p| p.name.clone()).collect();
let mut frees: Vec<String> = Vec::new();
free_vars(body, &mut bound, &mut frees);
let captures: Vec<String> = frees.into_iter()
.filter(|n| self.locals.contains_key(n))
.collect();
let fn_id = self.next_fn_id.len() as u32;
self.next_fn_id.push(Function {
name: format!("__lambda_{fn_id}"),
arity: (captures.len() + params.len()) as u16,
locals_count: 0,
code: Vec::new(),
effects: Vec::new(),
body_hash: crate::program::ZERO_BODY_HASH,
refinements: Vec::new(),
});
for c in &captures {
let slot = *self.locals.get(c).expect("free var must be in scope");
self.emit(Op::LoadLocal(slot));
}
self.emit(Op::MakeClosure { fn_id, capture_count: captures.len() as u16 });
self.pending_lambdas.push(PendingLambda {
fn_id,
capture_names: captures,
params: params.to_vec(),
body: body.clone(),
});
}
fn try_emit_higher_order(
&mut self,
module: &str,
op: &str,
args: &[a::CExpr],
node_id_idx: u32,
) -> bool {
match (module, op) {
("result", "map") => self.emit_variant_map(args, "Ok", true),
("result", "and_then") => self.emit_variant_map(args, "Ok", false),
("result", "map_err") => self.emit_variant_map(args, "Err", true),
("result", "or_else") => self.emit_variant_or_else(args, "Err", 1),
("option", "map") => self.emit_variant_map(args, "Some", true),
("option", "and_then") => self.emit_variant_map(args, "Some", false),
("option", "or_else") => self.emit_variant_or_else(args, "None", 0),
("option", "unwrap_or_else") => self.emit_option_unwrap_or_else(args),
("list", "map") => self.emit_list_map(args),
("list", "par_map") => self.emit_list_par_map(args),
("list", "sort_by") => self.emit_list_sort_by(args),
("list", "filter") => self.emit_list_filter(args),
("list", "fold") => self.emit_list_fold(args),
("iter", "from_list") => self.emit_iter_from_list(args),
("iter", "unfold") => self.emit_iter_unfold(args),
("iter", "next") => self.emit_iter_next(args),
("iter", "is_empty") => self.emit_iter_is_empty(args),
("iter", "count") => self.emit_iter_count(args),
("iter", "take") => self.emit_iter_take(args),
("iter", "skip") => self.emit_iter_skip(args),
("iter", "to_list") => self.emit_iter_to_list(args),
("iter", "map") => self.emit_iter_map(args),
("iter", "filter") => self.emit_iter_filter(args),
("iter", "fold") => self.emit_iter_fold(args),
("map", "fold") => self.emit_map_fold(args, node_id_idx),
("flow", "sequential") => self.emit_flow_sequential(args),
("flow", "branch") => self.emit_flow_branch(args),
("flow", "retry") => self.emit_flow_retry(args),
("flow", "retry_with_backoff") => self.emit_flow_retry_with_backoff(args),
("flow", "parallel") => self.emit_flow_parallel(args),
("flow", "parallel_list") => self.emit_flow_parallel_list(args),
_ => return false,
}
true
}
fn emit_list_map(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
let xs = self.alloc_local("__lm_xs");
self.emit(Op::StoreLocal(xs));
self.compile_expr(&args[1], false);
let f = self.alloc_local("__lm_f");
self.emit(Op::StoreLocal(f));
self.emit(Op::MakeList(0));
let out = self.alloc_local("__lm_out");
self.emit(Op::StoreLocal(out));
let zero = self.pool.int(0);
self.emit(Op::PushConst(zero));
let i = self.alloc_local("__lm_i");
self.emit(Op::StoreLocal(i));
let loop_top = self.code.len();
self.emit(Op::LoadLocal(i));
self.emit(Op::LoadLocal(xs));
self.emit(Op::GetListLen);
self.emit(Op::NumLt);
let j_exit = self.code.len();
self.emit(Op::JumpIfNot(0));
let nid = self.pool.node_id("n_list_map");
self.emit(Op::LoadLocal(out));
self.emit(Op::LoadLocal(f));
self.emit(Op::LoadLocal(xs));
self.emit(Op::LoadLocal(i));
self.emit(Op::GetListElemDyn);
self.emit(Op::CallClosure { arity: 1, node_id_idx: nid });
self.emit(Op::ListAppend);
self.emit(Op::StoreLocal(out));
self.emit(Op::LoadLocal(i));
let one = self.pool.int(1);
self.emit(Op::PushConst(one));
self.emit(Op::NumAdd);
self.emit(Op::StoreLocal(i));
let jump_back = self.code.len();
let back = (loop_top as i32) - (jump_back as i32 + 1);
self.emit(Op::Jump(back));
let exit_target = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_exit] {
*off = exit_target - (j_exit as i32 + 1);
}
self.emit(Op::LoadLocal(out));
}
fn emit_list_par_map(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
self.compile_expr(&args[1], false);
let nid = self.pool.node_id("n_list_par_map");
self.emit(Op::ParallelMap { node_id_idx: nid });
}
fn emit_list_sort_by(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
self.compile_expr(&args[1], false);
let nid = self.pool.node_id("n_list_sort_by");
self.emit(Op::SortByKey { node_id_idx: nid });
}
fn emit_list_filter(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
let xs = self.alloc_local("__lf_xs");
self.emit(Op::StoreLocal(xs));
self.compile_expr(&args[1], false);
let f = self.alloc_local("__lf_f");
self.emit(Op::StoreLocal(f));
self.emit(Op::MakeList(0));
let out = self.alloc_local("__lf_out");
self.emit(Op::StoreLocal(out));
let zero = self.pool.int(0);
self.emit(Op::PushConst(zero));
let i = self.alloc_local("__lf_i");
self.emit(Op::StoreLocal(i));
let loop_top = self.code.len();
self.emit(Op::LoadLocal(i));
self.emit(Op::LoadLocal(xs));
self.emit(Op::GetListLen);
self.emit(Op::NumLt);
let j_exit = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::LoadLocal(xs));
self.emit(Op::LoadLocal(i));
self.emit(Op::GetListElemDyn);
let x = self.alloc_local("__lf_x");
self.emit(Op::StoreLocal(x));
let nid = self.pool.node_id("n_list_filter");
self.emit(Op::LoadLocal(f));
self.emit(Op::LoadLocal(x));
self.emit(Op::CallClosure { arity: 1, node_id_idx: nid });
let j_skip = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::LoadLocal(out));
self.emit(Op::LoadLocal(x));
self.emit(Op::ListAppend);
self.emit(Op::StoreLocal(out));
let skip_target = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_skip] {
*off = skip_target - (j_skip as i32 + 1);
}
self.emit(Op::LoadLocal(i));
let one = self.pool.int(1);
self.emit(Op::PushConst(one));
self.emit(Op::NumAdd);
self.emit(Op::StoreLocal(i));
let jump_back = self.code.len();
let back = (loop_top as i32) - (jump_back as i32 + 1);
self.emit(Op::Jump(back));
let exit_target = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_exit] {
*off = exit_target - (j_exit as i32 + 1);
}
self.emit(Op::LoadLocal(out));
}
fn emit_list_fold(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
let xs = self.alloc_local("__lo_xs");
self.emit(Op::StoreLocal(xs));
self.compile_expr(&args[1], false);
let acc = self.alloc_local("__lo_acc");
self.emit(Op::StoreLocal(acc));
self.compile_expr(&args[2], false);
let f = self.alloc_local("__lo_f");
self.emit(Op::StoreLocal(f));
let zero = self.pool.int(0);
self.emit(Op::PushConst(zero));
let i = self.alloc_local("__lo_i");
self.emit(Op::StoreLocal(i));
let loop_top = self.code.len();
self.emit(Op::LoadLocal(i));
self.emit(Op::LoadLocal(xs));
self.emit(Op::GetListLen);
self.emit(Op::NumLt);
let j_exit = self.code.len();
self.emit(Op::JumpIfNot(0));
let nid = self.pool.node_id("n_list_fold");
self.emit(Op::LoadLocal(f));
self.emit(Op::LoadLocal(acc));
self.emit(Op::LoadLocal(xs));
self.emit(Op::LoadLocal(i));
self.emit(Op::GetListElemDyn);
self.emit(Op::CallClosure { arity: 2, node_id_idx: nid });
self.emit(Op::StoreLocal(acc));
self.emit(Op::LoadLocal(i));
let one = self.pool.int(1);
self.emit(Op::PushConst(one));
self.emit(Op::NumAdd);
self.emit(Op::StoreLocal(i));
let jump_back = self.code.len();
let back = (loop_top as i32) - (jump_back as i32 + 1);
self.emit(Op::Jump(back));
let exit_target = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_exit] {
*off = exit_target - (j_exit as i32 + 1);
}
self.emit(Op::LoadLocal(acc));
}
fn emit_iter_from_list(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
let zero = self.pool.int(0);
self.emit(Op::PushConst(zero));
let v = self.pool.variant("__IterEager");
self.emit(Op::MakeVariant { name_idx: v, arity: 2 });
}
fn emit_iter_next(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
let it = self.alloc_local("__in_it");
self.emit(Op::StoreLocal(it));
self.emit(Op::LoadLocal(it));
self.emit(Op::Dup);
let lazy_name = self.pool.variant("__IterLazy");
self.emit(Op::TestVariant(lazy_name));
let j_to_check_cursor = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::LoadLocal(it));
self.emit(Op::GetVariantArg(0)); let seed = self.alloc_local("__in_seed");
self.emit(Op::StoreLocal(seed));
self.emit(Op::LoadLocal(it));
self.emit(Op::GetVariantArg(1)); let step = self.alloc_local("__in_step");
self.emit(Op::StoreLocal(step));
let nid_lazy = self.pool.node_id("n_iter_next_lazy");
self.emit(Op::LoadLocal(step));
self.emit(Op::LoadLocal(seed));
self.emit(Op::CallClosure { arity: 1, node_id_idx: nid_lazy });
let opt = self.alloc_local("__in_opt");
self.emit(Op::StoreLocal(opt));
self.emit(Op::LoadLocal(opt));
let some_name = self.pool.variant("Some");
self.emit(Op::TestVariant(some_name));
let j_lazy_none = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::LoadLocal(opt));
self.emit(Op::GetVariantArg(0)); let pair = self.alloc_local("__in_pair");
self.emit(Op::StoreLocal(pair));
self.emit(Op::LoadLocal(pair));
self.emit(Op::GetElem(0)); self.emit(Op::LoadLocal(pair));
self.emit(Op::GetElem(1)); self.emit(Op::LoadLocal(step)); let lazy_v = self.pool.variant("__IterLazy");
self.emit(Op::MakeVariant { name_idx: lazy_v, arity: 2 }); self.emit(Op::MakeTuple(2)); let some_v = self.pool.variant("Some");
self.emit(Op::MakeVariant { name_idx: some_v, arity: 1 });
let j_after_lazy = self.code.len();
self.emit(Op::Jump(0));
let none_t = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_lazy_none] {
*off = none_t - (j_lazy_none as i32 + 1);
}
let none_v = self.pool.variant("None");
self.emit(Op::MakeVariant { name_idx: none_v, arity: 0 });
let j_after_lazy_none = self.code.len();
self.emit(Op::Jump(0));
let cursor_check_t = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_to_check_cursor] {
*off = cursor_check_t - (j_to_check_cursor as i32 + 1);
}
self.emit(Op::LoadLocal(it));
self.emit(Op::Dup);
let cursor_name = self.pool.variant("__IterCursor");
self.emit(Op::TestVariant(cursor_name));
let j_to_eager = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::LoadLocal(it));
self.emit(Op::GetVariantArg(0)); let handle = self.alloc_local("__in_handle");
self.emit(Op::StoreLocal(handle));
let kind_idx = self.pool.str("sql");
let op_idx = self.pool.str("cursor_next");
let nid_cursor = self.pool.node_id("n_iter_next_cursor");
self.emit(Op::LoadLocal(handle));
self.emit(Op::EffectCall {
kind_idx,
op_idx,
arity: 1,
node_id_idx: nid_cursor,
});
let cur_opt = self.alloc_local("__in_cur_opt");
self.emit(Op::StoreLocal(cur_opt));
self.emit(Op::LoadLocal(cur_opt));
let some_c = self.pool.variant("Some");
self.emit(Op::TestVariant(some_c));
let j_cursor_none = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::LoadLocal(cur_opt));
self.emit(Op::GetVariantArg(0)); self.emit(Op::LoadLocal(handle));
let cursor_v = self.pool.variant("__IterCursor");
self.emit(Op::MakeVariant { name_idx: cursor_v, arity: 1 });
self.emit(Op::MakeTuple(2)); let some_c2 = self.pool.variant("Some");
self.emit(Op::MakeVariant { name_idx: some_c2, arity: 1 });
let j_after_cursor = self.code.len();
self.emit(Op::Jump(0));
let cursor_none_t = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_cursor_none] {
*off = cursor_none_t - (j_cursor_none as i32 + 1);
}
let none_c = self.pool.variant("None");
self.emit(Op::MakeVariant { name_idx: none_c, arity: 0 });
let j_after_cursor_none = self.code.len();
self.emit(Op::Jump(0));
let eager_t = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_to_eager] {
*off = eager_t - (j_to_eager as i32 + 1);
}
self.emit(Op::LoadLocal(it));
self.emit(Op::GetVariantArg(0));
let list = self.alloc_local("__in_list");
self.emit(Op::StoreLocal(list));
self.emit(Op::LoadLocal(it));
self.emit(Op::GetVariantArg(1));
let idx = self.alloc_local("__in_idx");
self.emit(Op::StoreLocal(idx));
self.emit(Op::LoadLocal(idx));
self.emit(Op::LoadLocal(list));
self.emit(Op::GetListLen);
self.emit(Op::NumLt);
let j_eager_else = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::LoadLocal(list));
self.emit(Op::LoadLocal(idx));
self.emit(Op::GetListElemDyn);
self.emit(Op::LoadLocal(list));
self.emit(Op::LoadLocal(idx));
let one = self.pool.int(1);
self.emit(Op::PushConst(one));
self.emit(Op::NumAdd);
let eager_v = self.pool.variant("__IterEager");
self.emit(Op::MakeVariant { name_idx: eager_v, arity: 2 });
self.emit(Op::MakeTuple(2));
let some_e = self.pool.variant("Some");
self.emit(Op::MakeVariant { name_idx: some_e, arity: 1 });
let j_after_eager = self.code.len();
self.emit(Op::Jump(0));
let eager_none_t = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_eager_else] {
*off = eager_none_t - (j_eager_else as i32 + 1);
}
let none_e = self.pool.variant("None");
self.emit(Op::MakeVariant { name_idx: none_e, arity: 0 });
let end = self.code.len() as i32;
if let Op::Jump(off) = &mut self.code[j_after_lazy] {
*off = end - (j_after_lazy as i32 + 1);
}
if let Op::Jump(off) = &mut self.code[j_after_lazy_none] {
*off = end - (j_after_lazy_none as i32 + 1);
}
if let Op::Jump(off) = &mut self.code[j_after_cursor] {
*off = end - (j_after_cursor as i32 + 1);
}
if let Op::Jump(off) = &mut self.code[j_after_cursor_none] {
*off = end - (j_after_cursor_none as i32 + 1);
}
if let Op::Jump(off) = &mut self.code[j_after_eager] {
*off = end - (j_after_eager as i32 + 1);
}
}
fn emit_iter_unfold(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false); self.compile_expr(&args[1], false); let lazy = self.pool.variant("__IterLazy");
self.emit(Op::MakeVariant { name_idx: lazy, arity: 2 });
}
fn emit_iter_is_empty(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
let it = self.alloc_local("__ie_it");
self.emit(Op::StoreLocal(it));
self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(1)); self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(0)); self.emit(Op::GetListLen); self.emit(Op::NumLt); self.emit(Op::BoolNot); }
fn emit_iter_count(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
let it = self.alloc_local("__ic_it");
self.emit(Op::StoreLocal(it));
self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(0));
self.emit(Op::GetListLen); self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(1)); self.emit(Op::NumSub); }
fn emit_iter_take(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
let it = self.alloc_local("__itk_it");
self.emit(Op::StoreLocal(it));
self.compile_expr(&args[1], false);
let n = self.alloc_local("__itk_n");
self.emit(Op::StoreLocal(n));
self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(0));
let list = self.alloc_local("__itk_list");
self.emit(Op::StoreLocal(list));
self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(1));
let i = self.alloc_local("__itk_i");
self.emit(Op::StoreLocal(i));
self.emit(Op::MakeList(0));
let out = self.alloc_local("__itk_out");
self.emit(Op::StoreLocal(out));
let zero = self.pool.int(0);
self.emit(Op::PushConst(zero));
let cnt = self.alloc_local("__itk_cnt");
self.emit(Op::StoreLocal(cnt));
let loop_top = self.code.len();
self.emit(Op::LoadLocal(cnt));
self.emit(Op::LoadLocal(n));
self.emit(Op::NumLt);
let j_exit_n = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::LoadLocal(i));
self.emit(Op::LoadLocal(list)); self.emit(Op::GetListLen);
self.emit(Op::NumLt);
let j_exit_l = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::LoadLocal(out));
self.emit(Op::LoadLocal(list));
self.emit(Op::LoadLocal(i));
self.emit(Op::GetListElemDyn);
self.emit(Op::ListAppend);
self.emit(Op::StoreLocal(out));
let one = self.pool.int(1);
self.emit(Op::LoadLocal(i));
self.emit(Op::PushConst(one));
self.emit(Op::NumAdd);
self.emit(Op::StoreLocal(i));
self.emit(Op::LoadLocal(cnt));
self.emit(Op::PushConst(one));
self.emit(Op::NumAdd);
self.emit(Op::StoreLocal(cnt));
let jback = self.code.len();
self.emit(Op::Jump((loop_top as i32) - (jback as i32 + 1)));
let exit_t = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_exit_n] { *off = exit_t - (j_exit_n as i32 + 1); }
if let Op::JumpIfNot(off) = &mut self.code[j_exit_l] { *off = exit_t - (j_exit_l as i32 + 1); }
self.emit(Op::LoadLocal(out));
self.emit(Op::PushConst(zero));
let eager_v = self.pool.variant("__IterEager");
self.emit(Op::MakeVariant { name_idx: eager_v, arity: 2 });
}
fn emit_iter_skip(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
let it = self.alloc_local("__isk_it");
self.emit(Op::StoreLocal(it));
self.compile_expr(&args[1], false);
let n = self.alloc_local("__isk_n");
self.emit(Op::StoreLocal(n));
self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(0));
let list = self.alloc_local("__isk_list");
self.emit(Op::StoreLocal(list));
self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(1));
let idx = self.alloc_local("__isk_idx");
self.emit(Op::StoreLocal(idx));
self.emit(Op::LoadLocal(idx));
self.emit(Op::LoadLocal(n));
self.emit(Op::NumAdd);
let raw = self.alloc_local("__isk_raw");
self.emit(Op::StoreLocal(raw));
self.emit(Op::LoadLocal(raw));
self.emit(Op::LoadLocal(list)); self.emit(Op::GetListLen);
self.emit(Op::NumLt);
let j_use_raw = self.code.len();
self.emit(Op::JumpIf(0));
self.emit(Op::LoadLocal(list)); self.emit(Op::GetListLen);
let j_end = self.code.len();
self.emit(Op::Jump(0));
let raw_t = self.code.len() as i32;
if let Op::JumpIf(off) = &mut self.code[j_use_raw] { *off = raw_t - (j_use_raw as i32 + 1); }
self.emit(Op::LoadLocal(raw));
let end_t = self.code.len() as i32;
if let Op::Jump(off) = &mut self.code[j_end] { *off = end_t - (j_end as i32 + 1); }
let new_idx = self.alloc_local("__isk_ni");
self.emit(Op::StoreLocal(new_idx));
self.emit(Op::LoadLocal(list));
self.emit(Op::LoadLocal(new_idx));
let eager_v = self.pool.variant("__IterEager");
self.emit(Op::MakeVariant { name_idx: eager_v, arity: 2 });
}
fn emit_iter_to_list(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
let it = self.alloc_local("__itl_it");
self.emit(Op::StoreLocal(it));
self.emit(Op::MakeList(0));
let out = self.alloc_local("__itl_out");
self.emit(Op::StoreLocal(out));
self.emit(Op::LoadLocal(it));
let lazy_name = self.pool.variant("__IterLazy");
self.emit(Op::TestVariant(lazy_name));
let j_to_eager = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(0));
let seed = self.alloc_local("__itl_seed");
self.emit(Op::StoreLocal(seed));
self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(1));
let step = self.alloc_local("__itl_step");
self.emit(Op::StoreLocal(step));
let lazy_loop = self.code.len();
let nid_lazy = self.pool.node_id("n_iter_to_list_lazy");
self.emit(Op::LoadLocal(step));
self.emit(Op::LoadLocal(seed));
self.emit(Op::CallClosure { arity: 1, node_id_idx: nid_lazy });
let opt = self.alloc_local("__itl_opt");
self.emit(Op::StoreLocal(opt));
self.emit(Op::LoadLocal(opt));
let some_name = self.pool.variant("Some");
self.emit(Op::TestVariant(some_name));
let j_lazy_exit = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::LoadLocal(opt));
self.emit(Op::GetVariantArg(0));
let pair = self.alloc_local("__itl_pair");
self.emit(Op::StoreLocal(pair));
self.emit(Op::LoadLocal(out));
self.emit(Op::LoadLocal(pair)); self.emit(Op::GetElem(0));
self.emit(Op::ListAppend);
self.emit(Op::StoreLocal(out));
self.emit(Op::LoadLocal(pair)); self.emit(Op::GetElem(1));
self.emit(Op::StoreLocal(seed));
let jback_lazy = self.code.len();
self.emit(Op::Jump((lazy_loop as i32) - (jback_lazy as i32 + 1)));
let lazy_exit_t = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_lazy_exit] {
*off = lazy_exit_t - (j_lazy_exit as i32 + 1);
}
let j_after_lazy = self.code.len();
self.emit(Op::Jump(0));
let eager_t = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_to_eager] {
*off = eager_t - (j_to_eager as i32 + 1);
}
self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(0));
let list = self.alloc_local("__itl_list");
self.emit(Op::StoreLocal(list));
self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(1));
let i = self.alloc_local("__itl_i");
self.emit(Op::StoreLocal(i));
let loop_top = self.code.len();
self.emit(Op::LoadLocal(i));
self.emit(Op::LoadLocal(list)); self.emit(Op::GetListLen);
self.emit(Op::NumLt);
let j_exit = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::LoadLocal(out));
self.emit(Op::LoadLocal(list));
self.emit(Op::LoadLocal(i));
self.emit(Op::GetListElemDyn);
self.emit(Op::ListAppend);
self.emit(Op::StoreLocal(out));
self.emit(Op::LoadLocal(i));
let one = self.pool.int(1);
self.emit(Op::PushConst(one));
self.emit(Op::NumAdd);
self.emit(Op::StoreLocal(i));
let jback = self.code.len();
self.emit(Op::Jump((loop_top as i32) - (jback as i32 + 1)));
let exit_t = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_exit] {
*off = exit_t - (j_exit as i32 + 1);
}
let converge = self.code.len() as i32;
if let Op::Jump(off) = &mut self.code[j_after_lazy] {
*off = converge - (j_after_lazy as i32 + 1);
}
self.emit(Op::LoadLocal(out));
}
fn emit_iter_map(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
let it = self.alloc_local("__im_it");
self.emit(Op::StoreLocal(it));
self.compile_expr(&args[1], false);
let f = self.alloc_local("__im_f");
self.emit(Op::StoreLocal(f));
self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(0));
let list = self.alloc_local("__im_list");
self.emit(Op::StoreLocal(list));
self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(1));
let i = self.alloc_local("__im_i");
self.emit(Op::StoreLocal(i));
self.emit(Op::MakeList(0));
let out = self.alloc_local("__im_out");
self.emit(Op::StoreLocal(out));
let loop_top = self.code.len();
self.emit(Op::LoadLocal(i));
self.emit(Op::LoadLocal(list)); self.emit(Op::GetListLen);
self.emit(Op::NumLt);
let j_exit = self.code.len();
self.emit(Op::JumpIfNot(0));
let nid = self.pool.node_id("n_iter_map");
self.emit(Op::LoadLocal(out));
self.emit(Op::LoadLocal(f));
self.emit(Op::LoadLocal(list));
self.emit(Op::LoadLocal(i));
self.emit(Op::GetListElemDyn);
self.emit(Op::CallClosure { arity: 1, node_id_idx: nid });
self.emit(Op::ListAppend);
self.emit(Op::StoreLocal(out));
self.emit(Op::LoadLocal(i));
let one = self.pool.int(1);
self.emit(Op::PushConst(one));
self.emit(Op::NumAdd);
self.emit(Op::StoreLocal(i));
let jback = self.code.len();
self.emit(Op::Jump((loop_top as i32) - (jback as i32 + 1)));
let exit_t = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_exit] { *off = exit_t - (j_exit as i32 + 1); }
let zero = self.pool.int(0);
self.emit(Op::LoadLocal(out));
self.emit(Op::PushConst(zero));
let eager_v = self.pool.variant("__IterEager");
self.emit(Op::MakeVariant { name_idx: eager_v, arity: 2 });
}
fn emit_iter_filter(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
let it = self.alloc_local("__if_it");
self.emit(Op::StoreLocal(it));
self.compile_expr(&args[1], false);
let f = self.alloc_local("__if_f");
self.emit(Op::StoreLocal(f));
self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(0));
let list = self.alloc_local("__if_list");
self.emit(Op::StoreLocal(list));
self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(1));
let i = self.alloc_local("__if_i");
self.emit(Op::StoreLocal(i));
self.emit(Op::MakeList(0));
let out = self.alloc_local("__if_out");
self.emit(Op::StoreLocal(out));
let loop_top = self.code.len();
self.emit(Op::LoadLocal(i));
self.emit(Op::LoadLocal(list)); self.emit(Op::GetListLen);
self.emit(Op::NumLt);
let j_exit = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::LoadLocal(list));
self.emit(Op::LoadLocal(i));
self.emit(Op::GetListElemDyn);
let x = self.alloc_local("__if_x");
self.emit(Op::StoreLocal(x));
let nid = self.pool.node_id("n_iter_filter");
self.emit(Op::LoadLocal(f));
self.emit(Op::LoadLocal(x));
self.emit(Op::CallClosure { arity: 1, node_id_idx: nid });
let j_skip = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::LoadLocal(out));
self.emit(Op::LoadLocal(x));
self.emit(Op::ListAppend);
self.emit(Op::StoreLocal(out));
let skip_t = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_skip] { *off = skip_t - (j_skip as i32 + 1); }
self.emit(Op::LoadLocal(i));
let one = self.pool.int(1);
self.emit(Op::PushConst(one));
self.emit(Op::NumAdd);
self.emit(Op::StoreLocal(i));
let jback = self.code.len();
self.emit(Op::Jump((loop_top as i32) - (jback as i32 + 1)));
let exit_t = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_exit] { *off = exit_t - (j_exit as i32 + 1); }
let zero = self.pool.int(0);
self.emit(Op::LoadLocal(out));
self.emit(Op::PushConst(zero));
let eager_v = self.pool.variant("__IterEager");
self.emit(Op::MakeVariant { name_idx: eager_v, arity: 2 });
}
fn emit_iter_fold(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
let it = self.alloc_local("__ifo_it");
self.emit(Op::StoreLocal(it));
self.compile_expr(&args[1], false);
let acc = self.alloc_local("__ifo_acc");
self.emit(Op::StoreLocal(acc));
self.compile_expr(&args[2], false);
let f = self.alloc_local("__ifo_f");
self.emit(Op::StoreLocal(f));
self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(0));
let list = self.alloc_local("__ifo_list");
self.emit(Op::StoreLocal(list));
self.emit(Op::LoadLocal(it)); self.emit(Op::GetVariantArg(1));
let i = self.alloc_local("__ifo_i");
self.emit(Op::StoreLocal(i));
let loop_top = self.code.len();
self.emit(Op::LoadLocal(i));
self.emit(Op::LoadLocal(list)); self.emit(Op::GetListLen);
self.emit(Op::NumLt);
let j_exit = self.code.len();
self.emit(Op::JumpIfNot(0));
let nid = self.pool.node_id("n_iter_fold");
self.emit(Op::LoadLocal(f));
self.emit(Op::LoadLocal(acc));
self.emit(Op::LoadLocal(list));
self.emit(Op::LoadLocal(i));
self.emit(Op::GetListElemDyn);
self.emit(Op::CallClosure { arity: 2, node_id_idx: nid });
self.emit(Op::StoreLocal(acc));
self.emit(Op::LoadLocal(i));
let one = self.pool.int(1);
self.emit(Op::PushConst(one));
self.emit(Op::NumAdd);
self.emit(Op::StoreLocal(i));
let jback = self.code.len();
self.emit(Op::Jump((loop_top as i32) - (jback as i32 + 1)));
let exit_t = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_exit] { *off = exit_t - (j_exit as i32 + 1); }
self.emit(Op::LoadLocal(acc));
}
fn emit_map_fold(&mut self, args: &[a::CExpr], node_id_idx: u32) {
self.compile_expr(&args[0], false);
let map_kind = self.pool.str("map");
let entries_op = self.pool.str("entries");
self.emit(Op::EffectCall {
kind_idx: map_kind,
op_idx: entries_op,
arity: 1,
node_id_idx,
});
let xs = self.alloc_local("__mf_xs");
self.emit(Op::StoreLocal(xs));
self.compile_expr(&args[1], false);
let acc = self.alloc_local("__mf_acc");
self.emit(Op::StoreLocal(acc));
self.compile_expr(&args[2], false);
let f = self.alloc_local("__mf_f");
self.emit(Op::StoreLocal(f));
let zero = self.pool.int(0);
self.emit(Op::PushConst(zero));
let i = self.alloc_local("__mf_i");
self.emit(Op::StoreLocal(i));
let loop_top = self.code.len();
self.emit(Op::LoadLocal(i));
self.emit(Op::LoadLocal(xs));
self.emit(Op::GetListLen);
self.emit(Op::NumLt);
let j_exit = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::LoadLocal(xs));
self.emit(Op::LoadLocal(i));
self.emit(Op::GetListElemDyn);
let pair = self.alloc_local("__mf_pair");
self.emit(Op::StoreLocal(pair));
let nid = self.pool.node_id("n_map_fold");
self.emit(Op::LoadLocal(f));
self.emit(Op::LoadLocal(acc));
self.emit(Op::LoadLocal(pair));
self.emit(Op::GetElem(0));
self.emit(Op::LoadLocal(pair));
self.emit(Op::GetElem(1));
self.emit(Op::CallClosure { arity: 3, node_id_idx: nid });
self.emit(Op::StoreLocal(acc));
self.emit(Op::LoadLocal(i));
let one = self.pool.int(1);
self.emit(Op::PushConst(one));
self.emit(Op::NumAdd);
self.emit(Op::StoreLocal(i));
let jump_back = self.code.len();
let back = (loop_top as i32) - (jump_back as i32 + 1);
self.emit(Op::Jump(back));
let exit_target = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_exit] {
*off = exit_target - (j_exit as i32 + 1);
}
self.emit(Op::LoadLocal(acc));
}
fn emit_variant_map(
&mut self,
args: &[a::CExpr],
wrap_with: &str,
wrap_result: bool,
) {
let wrap_idx = self.pool.variant(wrap_with);
self.compile_expr(&args[0], false);
let val_slot = self.alloc_local("__hov");
self.emit(Op::StoreLocal(val_slot));
self.compile_expr(&args[1], false);
let f_slot = self.alloc_local("__hof");
self.emit(Op::StoreLocal(f_slot));
self.emit(Op::LoadLocal(val_slot));
self.emit(Op::Dup);
self.emit(Op::TestVariant(wrap_idx));
let j_skip = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::GetVariantArg(0));
let arg_slot = self.alloc_local("__hov_arg");
self.emit(Op::StoreLocal(arg_slot));
self.emit(Op::LoadLocal(f_slot));
self.emit(Op::LoadLocal(arg_slot));
let nid = self.pool.node_id("n_hov");
self.emit(Op::CallClosure { arity: 1, node_id_idx: nid });
if wrap_result {
self.emit(Op::MakeVariant { name_idx: wrap_idx, arity: 1 });
}
let j_end = self.code.len();
self.emit(Op::Jump(0));
let skip_target = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_skip] {
*off = skip_target - (j_skip as i32 + 1);
}
let end_target = self.code.len() as i32;
if let Op::Jump(off) = &mut self.code[j_end] {
*off = end_target - (j_end as i32 + 1);
}
}
fn emit_variant_or_else(
&mut self,
args: &[a::CExpr],
match_on: &str,
closure_arity: u16,
) {
let match_idx = self.pool.variant(match_on);
self.compile_expr(&args[0], false);
let val_slot = self.alloc_local("__hoe");
self.emit(Op::StoreLocal(val_slot));
self.compile_expr(&args[1], false);
let f_slot = self.alloc_local("__hoe_f");
self.emit(Op::StoreLocal(f_slot));
self.emit(Op::LoadLocal(val_slot));
self.emit(Op::Dup);
self.emit(Op::TestVariant(match_idx));
let j_skip = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::Pop);
self.emit(Op::LoadLocal(f_slot));
if closure_arity == 1 {
self.emit(Op::LoadLocal(val_slot));
self.emit(Op::GetVariantArg(0));
}
let nid = self.pool.node_id("n_hoe");
self.emit(Op::CallClosure { arity: closure_arity, node_id_idx: nid });
let j_end = self.code.len();
self.emit(Op::Jump(0));
let skip_target = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_skip] {
*off = skip_target - (j_skip as i32 + 1);
}
let end_target = self.code.len() as i32;
if let Op::Jump(off) = &mut self.code[j_end] {
*off = end_target - (j_end as i32 + 1);
}
}
fn emit_option_unwrap_or_else(&mut self, args: &[a::CExpr]) {
let some_idx = self.pool.variant("Some");
self.compile_expr(&args[0], false);
let val_slot = self.alloc_local("__uoe_val");
self.emit(Op::StoreLocal(val_slot));
self.compile_expr(&args[1], false);
let f_slot = self.alloc_local("__uoe_f");
self.emit(Op::StoreLocal(f_slot));
self.emit(Op::LoadLocal(val_slot));
self.emit(Op::Dup);
self.emit(Op::TestVariant(some_idx));
let j_none = self.code.len();
self.emit(Op::JumpIfNot(0));
self.emit(Op::GetVariantArg(0));
let j_end = self.code.len();
self.emit(Op::Jump(0));
let none_target = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_none] {
*off = none_target - (j_none as i32 + 1);
}
self.emit(Op::Pop);
self.emit(Op::LoadLocal(f_slot));
let nid = self.pool.node_id("n_uoe");
self.emit(Op::CallClosure { arity: 0, node_id_idx: nid });
let end_target = self.code.len() as i32;
if let Op::Jump(off) = &mut self.code[j_end] {
*off = end_target - (j_end as i32 + 1);
}
}
fn install_trampoline(&mut self, name: &str, arity: u16, locals_count: u16, code: Vec<Op>) -> u32 {
let fn_id = self.next_fn_id.len() as u32;
let body_hash = crate::program::compute_body_hash(
arity, locals_count, &code, &self.pool.record_shapes);
self.next_fn_id.push(Function {
name: name.into(),
arity,
locals_count,
code,
effects: Vec::new(),
body_hash,
refinements: Vec::new(),
});
fn_id
}
fn emit_flow_sequential(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
self.compile_expr(&args[1], false);
let nid = self.pool.node_id("n_flow_sequential");
let code = vec![
Op::LoadLocal(0), Op::LoadLocal(2), Op::CallClosure { arity: 1, node_id_idx: nid }, Op::StoreLocal(3), Op::LoadLocal(1), Op::LoadLocal(3), Op::CallClosure { arity: 1, node_id_idx: nid }, Op::Return,
];
let fn_id = self.install_trampoline("__flow_sequential", 3, 4, code);
self.emit(Op::MakeClosure { fn_id, capture_count: 2 });
}
fn emit_flow_parallel(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
self.compile_expr(&args[1], false);
let nid = self.pool.node_id("n_flow_parallel");
let code = vec![
Op::LoadLocal(0), Op::CallClosure { arity: 0, node_id_idx: nid }, Op::LoadLocal(1), Op::CallClosure { arity: 0, node_id_idx: nid }, Op::MakeTuple(2), Op::Return,
];
let fn_id = self.install_trampoline("__flow_parallel", 2, 2, code);
self.emit(Op::MakeClosure { fn_id, capture_count: 2 });
}
fn emit_flow_parallel_list(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
let xs = self.alloc_local("__fpl_xs");
self.emit(Op::StoreLocal(xs));
self.emit(Op::MakeList(0));
let out = self.alloc_local("__fpl_out");
self.emit(Op::StoreLocal(out));
let zero = self.pool.int(0);
self.emit(Op::PushConst(zero));
let i = self.alloc_local("__fpl_i");
self.emit(Op::StoreLocal(i));
let loop_top = self.code.len();
self.emit(Op::LoadLocal(i));
self.emit(Op::LoadLocal(xs));
self.emit(Op::GetListLen);
self.emit(Op::NumLt);
let j_exit = self.code.len();
self.emit(Op::JumpIfNot(0));
let nid = self.pool.node_id("n_flow_parallel_list");
self.emit(Op::LoadLocal(out));
self.emit(Op::LoadLocal(xs));
self.emit(Op::LoadLocal(i));
self.emit(Op::GetListElemDyn);
self.emit(Op::CallClosure { arity: 0, node_id_idx: nid });
self.emit(Op::ListAppend);
self.emit(Op::StoreLocal(out));
self.emit(Op::LoadLocal(i));
let one = self.pool.int(1);
self.emit(Op::PushConst(one));
self.emit(Op::NumAdd);
self.emit(Op::StoreLocal(i));
let jump_back = self.code.len();
let back = (loop_top as i32) - (jump_back as i32 + 1);
self.emit(Op::Jump(back));
let exit_target = self.code.len() as i32;
if let Op::JumpIfNot(off) = &mut self.code[j_exit] {
*off = exit_target - (j_exit as i32 + 1);
}
self.emit(Op::LoadLocal(out));
}
fn emit_flow_branch(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
self.compile_expr(&args[1], false);
self.compile_expr(&args[2], false);
let nid = self.pool.node_id("n_flow_branch");
let mut code = vec![
Op::LoadLocal(0), Op::LoadLocal(3), Op::CallClosure { arity: 1, node_id_idx: nid }, ];
let j_false = code.len();
code.push(Op::JumpIfNot(0)); code.push(Op::LoadLocal(1));
code.push(Op::LoadLocal(3));
code.push(Op::CallClosure { arity: 1, node_id_idx: nid });
code.push(Op::Return);
let false_target = code.len() as i32;
if let Op::JumpIfNot(off) = &mut code[j_false] {
*off = false_target - (j_false as i32 + 1);
}
code.push(Op::LoadLocal(2));
code.push(Op::LoadLocal(3));
code.push(Op::CallClosure { arity: 1, node_id_idx: nid });
code.push(Op::Return);
let fn_id = self.install_trampoline("__flow_branch", 4, 4, code);
self.emit(Op::MakeClosure { fn_id, capture_count: 3 });
}
fn emit_flow_retry(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
self.compile_expr(&args[1], false);
let call_nid = self.pool.node_id("n_flow_retry");
let ok_idx = self.pool.variant("Ok");
let zero_const = self.pool.int(0);
let one_const = self.pool.int(1);
let mut code = vec![
Op::PushConst(zero_const),
Op::StoreLocal(3),
];
let loop_top = code.len() as i32;
code.push(Op::LoadLocal(3));
code.push(Op::LoadLocal(1));
code.push(Op::NumLt);
let j_done = code.len();
code.push(Op::JumpIfNot(0));
code.push(Op::LoadLocal(0));
code.push(Op::LoadLocal(2));
code.push(Op::CallClosure { arity: 1, node_id_idx: call_nid });
code.push(Op::StoreLocal(4));
code.push(Op::LoadLocal(4));
code.push(Op::TestVariant(ok_idx));
let j_was_err = code.len();
code.push(Op::JumpIfNot(0)); code.push(Op::LoadLocal(4));
code.push(Op::Return);
let was_err_target = code.len() as i32;
if let Op::JumpIfNot(off) = &mut code[j_was_err] {
*off = was_err_target - (j_was_err as i32 + 1);
}
code.push(Op::LoadLocal(3));
code.push(Op::PushConst(one_const));
code.push(Op::NumAdd);
code.push(Op::StoreLocal(3));
let pc_after_jump = code.len() as i32 + 1;
code.push(Op::Jump(loop_top - pc_after_jump));
let done_target = code.len() as i32;
if let Op::JumpIfNot(off) = &mut code[j_done] {
*off = done_target - (j_done as i32 + 1);
}
code.push(Op::LoadLocal(4));
code.push(Op::Return);
let fn_id = self.install_trampoline("__flow_retry", 3, 5, code);
self.emit(Op::MakeClosure { fn_id, capture_count: 2 });
}
fn emit_flow_retry_with_backoff(&mut self, args: &[a::CExpr]) {
self.compile_expr(&args[0], false);
self.compile_expr(&args[1], false);
self.compile_expr(&args[2], false);
let call_nid = self.pool.node_id("n_flow_retry_backoff");
let sleep_nid = self.pool.node_id("n_flow_retry_backoff_sleep");
let kind_idx = self.pool.str("time");
let op_idx = self.pool.str("sleep_ms");
let ok_idx = self.pool.variant("Ok");
let zero_const = self.pool.int(0);
let one_const = self.pool.int(1);
let two_const = self.pool.int(2);
let mut code = vec![
Op::LoadLocal(2),
Op::StoreLocal(6),
Op::PushConst(zero_const),
Op::StoreLocal(4),
];
let loop_top = code.len() as i32;
code.push(Op::LoadLocal(4));
code.push(Op::LoadLocal(1));
code.push(Op::NumLt);
let j_done = code.len();
code.push(Op::JumpIfNot(0));
code.push(Op::PushConst(zero_const));
code.push(Op::LoadLocal(4));
code.push(Op::NumLt); let j_no_sleep = code.len();
code.push(Op::JumpIfNot(0)); code.push(Op::LoadLocal(6)); code.push(Op::EffectCall {
kind_idx, op_idx, arity: 1, node_id_idx: sleep_nid,
});
code.push(Op::Pop); code.push(Op::LoadLocal(6));
code.push(Op::PushConst(two_const));
code.push(Op::NumMul);
code.push(Op::StoreLocal(6));
let after_sleep = code.len() as i32;
if let Op::JumpIfNot(off) = &mut code[j_no_sleep] {
*off = after_sleep - (j_no_sleep as i32 + 1);
}
code.push(Op::LoadLocal(0));
code.push(Op::LoadLocal(3));
code.push(Op::CallClosure { arity: 1, node_id_idx: call_nid });
code.push(Op::StoreLocal(5));
code.push(Op::LoadLocal(5));
code.push(Op::TestVariant(ok_idx));
let j_was_err = code.len();
code.push(Op::JumpIfNot(0)); code.push(Op::LoadLocal(5));
code.push(Op::Return);
let was_err_target = code.len() as i32;
if let Op::JumpIfNot(off) = &mut code[j_was_err] {
*off = was_err_target - (j_was_err as i32 + 1);
}
code.push(Op::LoadLocal(4));
code.push(Op::PushConst(one_const));
code.push(Op::NumAdd);
code.push(Op::StoreLocal(4));
let pc_after_jump = code.len() as i32 + 1;
code.push(Op::Jump(loop_top - pc_after_jump));
let done_target = code.len() as i32;
if let Op::JumpIfNot(off) = &mut code[j_done] {
*off = done_target - (j_done as i32 + 1);
}
code.push(Op::LoadLocal(5));
code.push(Op::Return);
let fn_id = self.install_trampoline("__flow_retry_backoff", 4, 7, code);
self.emit(Op::MakeClosure { fn_id, capture_count: 3 });
}
}
fn free_vars(e: &a::CExpr, bound: &mut std::collections::HashSet<String>, out: &mut Vec<String>) {
match e {
a::CExpr::Literal { .. } => {}
a::CExpr::Var { name } => {
if !bound.contains(name) && !out.contains(name) {
out.push(name.clone());
}
}
a::CExpr::Call { callee, args } => {
free_vars(callee, bound, out);
for a in args { free_vars(a, bound, out); }
}
a::CExpr::Let { name, value, body, .. } => {
free_vars(value, bound, out);
let was_bound = bound.contains(name);
bound.insert(name.clone());
free_vars(body, bound, out);
if !was_bound { bound.remove(name); }
}
a::CExpr::Match { scrutinee, arms } => {
free_vars(scrutinee, bound, out);
for arm in arms {
let mut local_bound = bound.clone();
pattern_binders(&arm.pattern, &mut local_bound);
free_vars(&arm.body, &mut local_bound, out);
}
}
a::CExpr::Block { statements, result } => {
let mut local_bound = bound.clone();
for s in statements { free_vars(s, &mut local_bound, out); }
free_vars(result, &mut local_bound, out);
}
a::CExpr::Constructor { args, .. } => {
for a in args { free_vars(a, bound, out); }
}
a::CExpr::RecordLit { fields } => {
for f in fields { free_vars(&f.value, bound, out); }
}
a::CExpr::TupleLit { items } | a::CExpr::ListLit { items } => {
for it in items { free_vars(it, bound, out); }
}
a::CExpr::FieldAccess { value, .. } => free_vars(value, bound, out),
a::CExpr::Lambda { params, body, .. } => {
let mut inner = bound.clone();
for p in params { inner.insert(p.name.clone()); }
free_vars(body, &mut inner, out);
}
a::CExpr::BinOp { lhs, rhs, .. } => {
free_vars(lhs, bound, out);
free_vars(rhs, bound, out);
}
a::CExpr::UnaryOp { expr, .. } => free_vars(expr, bound, out),
a::CExpr::Return { value } => free_vars(value, bound, out),
}
}
fn pattern_binders(p: &a::Pattern, bound: &mut std::collections::HashSet<String>) {
match p {
a::Pattern::PWild | a::Pattern::PLiteral { .. } => {}
a::Pattern::PVar { name } => { bound.insert(name.clone()); }
a::Pattern::PConstructor { args, .. } => {
for a in args { pattern_binders(a, bound); }
}
a::Pattern::PRecord { fields } => {
for f in fields { pattern_binders(&f.pattern, bound); }
}
a::Pattern::PTuple { items } => {
for it in items { pattern_binders(it, bound); }
}
}
}