Skip to main content

tidepool_codegen/emit/
join.rs

1use crate::emit::expr::ensure_heap_ptr;
2use crate::emit::*;
3use cranelift_codegen::ir::{types, BlockArg, InstBuilder, Value};
4use cranelift_frontend::FunctionBuilder;
5use tidepool_repr::*;
6
7/// Emits a Join expression.
8/// Join { label, params, rhs, body } creates a join point (a parameterized block)
9/// that can be jumped to from within the body.
10#[allow(clippy::too_many_arguments)]
11pub fn emit_join(
12    ctx: &mut EmitContext,
13    sess: &mut EmitSession,
14    builder: &mut FunctionBuilder,
15    label: &JoinId,
16    params: &[VarId],
17    rhs_idx: usize,
18    body_idx: usize,
19) -> Result<SsaVal, EmitError> {
20    // 1. Create a new block for the join point
21    let join_block = builder.create_block();
22
23    // 2. Add block params — one I64 param per join parameter
24    for _ in params {
25        builder.append_block_param(join_block, types::I64);
26    }
27
28    // 3. Create a continuation/merge block for the result
29    let merge_block = builder.create_block();
30    builder.append_block_param(merge_block, types::I64); // result
31
32    // 4. Register the join point in ctx
33    // We use a dummy Value(0) for param_types since Jump just needs to know they are heap pointers.
34    let dummy_val = Value::from_u32(0);
35    ctx.join_blocks.register(
36        *label,
37        JoinInfo {
38            block: join_block,
39            param_types: params.iter().map(|_| SsaVal::HeapPtr(dummy_val)).collect(),
40        },
41    );
42
43    // 5. Emit body (the continuation that may contain Jumps)
44    let body_result = ctx.emit_node(sess, builder, body_idx, TailCtx::NonTail)?;
45    let body_val = ensure_heap_ptr(builder, sess.vmctx, sess.gc_sig, sess.oom_func, body_result);
46    builder
47        .ins()
48        .jump(merge_block, &[BlockArg::Value(body_val)]);
49
50    // 6. Switch to join block, emit rhs
51    builder.switch_to_block(join_block);
52    ctx.declare_env(builder);
53
54    // Bind params to block params
55    let block_params = builder.block_params(join_block).to_vec();
56    let mut scope = EnvScope::new();
57    // NOTE: EnvGuard cannot be used here because it would borrow ctx.env mutably,
58    // preventing the use of ctx in emit_node.
59    for (i, param_var) in params.iter().enumerate() {
60        let val = block_params[i];
61        builder.declare_value_needs_stack_map(val); // CRITICAL
62        ctx.env
63            .insert_scoped(&mut scope, *param_var, SsaVal::HeapPtr(val));
64    }
65
66    let rhs_result = ctx.emit_node(sess, builder, rhs_idx, TailCtx::NonTail)?;
67    let rhs_val = ensure_heap_ptr(builder, sess.vmctx, sess.gc_sig, sess.oom_func, rhs_result);
68    builder.ins().jump(merge_block, &[BlockArg::Value(rhs_val)]);
69
70    // 7. Seal blocks
71    // Body is emitted, so all Jumps to join_block are known.
72    builder.seal_block(join_block);
73    // Both body and rhs paths to merge_block are known.
74    builder.seal_block(merge_block);
75
76    // 8. Switch to merge block, get result
77    builder.switch_to_block(merge_block);
78    let result = builder.block_params(merge_block)[0];
79    builder.declare_value_needs_stack_map(result); // CRITICAL
80    ctx.declare_env(builder);
81
82    // 9. Clean up
83    ctx.join_blocks.remove(label);
84    ctx.env.restore_scope(scope);
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.
92#[allow(clippy::too_many_arguments)]
93pub fn emit_jump(
94    ctx: &mut EmitContext,
95    sess: &mut EmitSession,
96    builder: &mut FunctionBuilder,
97    label: &JoinId,
98    arg_indices: &[usize],
99) -> Result<SsaVal, EmitError> {
100    // 1. Look up label in ctx.join_blocks
101    let join_block = ctx.join_blocks.get(label)?.block;
102
103    // 2. Emit each arg
104    let mut arg_values: Vec<BlockArg> = Vec::new();
105    for &arg_idx in arg_indices {
106        // Jump arguments are always evaluated before we emit the jump terminator,
107        // so they are not in tail position. Do NOT propagate any surrounding tail
108        // context into these expressions: they must always be emitted as NonTail.
109        let val = ctx.emit_node(sess, builder, arg_idx, TailCtx::NonTail)?;
110        // 3. Ensure all args are HeapPtr
111        arg_values.push(BlockArg::Value(ensure_heap_ptr(
112            builder,
113            sess.vmctx,
114            sess.gc_sig,
115            sess.oom_func,
116            val,
117        )));
118    }
119
120    // 4. Jump
121    builder.ins().jump(join_block, &arg_values);
122
123    // 5. After a jump, the current block is terminated.
124    // Create a new unreachable block so Cranelift doesn't complain about instructions after a terminator.
125    let unreachable_block = builder.create_block();
126    builder.switch_to_block(unreachable_block);
127    builder.seal_block(unreachable_block);
128
129    // 6. Return a dummy SsaVal (dead code)
130    Ok(SsaVal::Raw(
131        builder.ins().iconst(types::I64, 0),
132        LIT_TAG_INT,
133    ))
134}