Skip to main content

tidepool_codegen/emit/
join.rs

1use crate::pipeline::CodegenPipeline;
2use crate::emit::*;
3use crate::emit::expr::ensure_heap_ptr;
4use tidepool_repr::*;
5use cranelift_codegen::ir::{self, types, InstBuilder, Value};
6use cranelift_frontend::FunctionBuilder;
7
8/// Emits a Join expression.
9/// Join { label, params, rhs, body } creates a join point (a parameterized block)
10/// that can be jumped to from within the body.
11pub fn emit_join(
12    ctx: &mut EmitContext,
13    pipeline: &mut CodegenPipeline,
14    builder: &mut FunctionBuilder,
15    vmctx: Value,
16    gc_sig: ir::SigRef,
17    tree: &CoreExpr,
18    label: &JoinId,
19    params: &[VarId],
20    rhs_idx: usize,
21    body_idx: usize,
22) -> Result<SsaVal, EmitError> {
23    // 1. Create a new block for the join point
24    let join_block = builder.create_block();
25
26    // 2. Add block params — one I64 param per join parameter
27    for _ in params {
28        builder.append_block_param(join_block, types::I64);
29    }
30
31    // 3. Create a continuation/merge block for the result
32    let merge_block = builder.create_block();
33    builder.append_block_param(merge_block, types::I64); // result
34
35    // 4. Register the join point in ctx
36    // We use a dummy Value(0) for param_types since Jump just needs to know they are heap pointers.
37    let dummy_val = Value::from_u32(0);
38    ctx.join_blocks.insert(*label, JoinInfo {
39        block: join_block,
40        param_types: params.iter().map(|_| SsaVal::HeapPtr(dummy_val)).collect(),
41    });
42
43    // 5. Emit body (the continuation that may contain Jumps)
44    let body_result = ctx.emit_node(pipeline, builder, vmctx, gc_sig, tree, body_idx)?;
45    let body_val = ensure_heap_ptr(builder, vmctx, gc_sig, body_result);
46    builder.ins().jump(merge_block, &[body_val]);
47
48    // 6. Switch to join block, emit rhs
49    builder.switch_to_block(join_block);
50    
51    // Bind params to block params
52    let block_params = builder.block_params(join_block).to_vec();
53    let mut old_env_vals = Vec::new();
54    for (i, param_var) in params.iter().enumerate() {
55        let val = block_params[i];
56        builder.declare_value_needs_stack_map(val);  // CRITICAL
57        let old_val = ctx.env.insert(*param_var, SsaVal::HeapPtr(val));
58        old_env_vals.push((*param_var, old_val));
59    }
60
61    let rhs_result = ctx.emit_node(pipeline, builder, vmctx, gc_sig, tree, rhs_idx)?;
62    let rhs_val = ensure_heap_ptr(builder, vmctx, gc_sig, rhs_result);
63    builder.ins().jump(merge_block, &[rhs_val]);
64
65    // 7. Seal blocks
66    // Body is emitted, so all Jumps to join_block are known.
67    builder.seal_block(join_block);
68    // Both body and rhs paths to merge_block are known.
69    builder.seal_block(merge_block);
70
71    // 8. Switch to merge block, get result
72    builder.switch_to_block(merge_block);
73    let result = builder.block_params(merge_block)[0];
74    builder.declare_value_needs_stack_map(result);  // CRITICAL
75
76    // 9. Clean up
77    ctx.join_blocks.remove(label);
78    for (param_var, old_val) in old_env_vals.into_iter().rev() {
79        if let Some(v) = old_val {
80            ctx.env.insert(param_var, v);
81        } else {
82            ctx.env.remove(&param_var);
83        }
84    }
85
86    // 10. Return result
87    Ok(SsaVal::HeapPtr(result))
88}
89
90/// Emits a Jump expression.
91/// Jump { label, args } transfers control to the join point block.
92pub fn emit_jump(
93    ctx: &mut EmitContext,
94    pipeline: &mut CodegenPipeline,
95    builder: &mut FunctionBuilder,
96    vmctx: Value,
97    gc_sig: ir::SigRef,
98    tree: &CoreExpr,
99    label: &JoinId,
100    arg_indices: &[usize],
101) -> Result<SsaVal, EmitError> {
102    // 1. Look up label in ctx.join_blocks
103    // Note: JoinInfo must be cloned or copied out because we'll be using the builder.
104    // However, JoinInfo doesn't implement Clone. But Block and Value are Copy.
105    // Actually, JoinInfo is not needed, just the block.
106    let join_block = ctx.join_blocks.get(label)
107        .ok_or_else(|| EmitError::NotYetImplemented(format!("Jump to unknown label {:?}", label)))?.block;
108
109    // 2. Emit each arg
110    let mut arg_values = Vec::new();
111    for &arg_idx in arg_indices {
112        let val = ctx.emit_node(pipeline, builder, vmctx, gc_sig, tree, arg_idx)?;
113        // 3. Ensure all args are HeapPtr
114        arg_values.push(ensure_heap_ptr(builder, vmctx, gc_sig, val));
115    }
116
117    // 4. Jump
118    builder.ins().jump(join_block, &arg_values);
119
120    // 5. After a jump, the current block is terminated.
121    // Create a new unreachable block so Cranelift doesn't complain about instructions after a terminator.
122    let unreachable_block = builder.create_block();
123    builder.switch_to_block(unreachable_block);
124    builder.seal_block(unreachable_block);
125
126    // 6. Return a dummy SsaVal (dead code)
127    Ok(SsaVal::Raw(builder.ins().iconst(types::I64, 0), LIT_TAG_INT))
128}