|
| 1 | +//! Lowering pass: transforms unconstrained IR into bytecode-compatible form. |
| 2 | +//! |
| 3 | +//! This pass handles bytecode encoding constraints by splitting oversized |
| 4 | +//! instructions into cascading epsilon chains: |
| 5 | +//! - pre_effects > 7 → epsilon chain before match |
| 6 | +//! - post_effects > 7 → epsilon chain after match |
| 7 | +//! - neg_fields > 7 → epsilon chain for overflow checks |
| 8 | +//! - successors > 28 → cascading epsilon branches |
| 9 | +
|
| 10 | +use plotnik_bytecode::{MAX_MATCH_PAYLOAD_SLOTS, MAX_PRE_EFFECTS}; |
| 11 | + |
| 12 | +use crate::bytecode::{EffectIR, InstructionIR, Label, MatchIR}; |
| 13 | +use crate::compile::CompileResult; |
| 14 | + |
| 15 | +const MAX_POST_EFFECTS: usize = 7; |
| 16 | +const MAX_NEG_FIELDS: usize = 7; |
| 17 | + |
| 18 | +enum PostChain { |
| 19 | + NegFields(Vec<u16>), |
| 20 | + PostEffects(Vec<EffectIR>), |
| 21 | + Successors(Vec<Label>), |
| 22 | +} |
| 23 | + |
| 24 | +/// Lower IR to bytecode-compatible form. |
| 25 | +/// |
| 26 | +/// Transforms instructions that exceed bytecode encoding limits into |
| 27 | +/// equivalent cascades of smaller instructions. |
| 28 | +pub fn lower(result: &mut CompileResult) { |
| 29 | + let mut next_label = result |
| 30 | + .instructions |
| 31 | + .iter() |
| 32 | + .map(|i| i.label().0) |
| 33 | + .max() |
| 34 | + .unwrap_or(0) |
| 35 | + + 1; |
| 36 | + |
| 37 | + let mut new_instructions = Vec::with_capacity(result.instructions.len()); |
| 38 | + |
| 39 | + for instr in result.instructions.drain(..) { |
| 40 | + let InstructionIR::Match(m) = instr else { |
| 41 | + new_instructions.push(instr); |
| 42 | + continue; |
| 43 | + }; |
| 44 | + lower_match(m, &mut new_instructions, &mut next_label); |
| 45 | + } |
| 46 | + |
| 47 | + result.instructions = new_instructions; |
| 48 | +} |
| 49 | + |
| 50 | +fn fresh_label(next: &mut u32) -> Label { |
| 51 | + let l = Label(*next); |
| 52 | + *next += 1; |
| 53 | + l |
| 54 | +} |
| 55 | + |
| 56 | +fn lower_match(mut m: MatchIR, out: &mut Vec<InstructionIR>, next_label: &mut u32) { |
| 57 | + // Step 1: Handle pre_effects overflow → epsilon chain before the match |
| 58 | + if m.pre_effects.len() > MAX_PRE_EFFECTS { |
| 59 | + let all_pre = std::mem::take(&mut m.pre_effects); |
| 60 | + let entry = m.label; |
| 61 | + m.label = fresh_label(next_label); |
| 62 | + emit_effects_chain(entry, m.label, all_pre, out, next_label); |
| 63 | + } |
| 64 | + |
| 65 | + // Collect overflow chains to emit after the match |
| 66 | + let mut post_chains: Vec<PostChain> = Vec::new(); |
| 67 | + |
| 68 | + if m.neg_fields.len() > MAX_NEG_FIELDS { |
| 69 | + let overflow: Vec<_> = m.neg_fields.drain(MAX_NEG_FIELDS..).collect(); |
| 70 | + post_chains.push(PostChain::NegFields(overflow)); |
| 71 | + } |
| 72 | + |
| 73 | + if m.post_effects.len() > MAX_POST_EFFECTS { |
| 74 | + let overflow: Vec<_> = m.post_effects.drain(MAX_POST_EFFECTS..).collect(); |
| 75 | + post_chains.push(PostChain::PostEffects(overflow)); |
| 76 | + } |
| 77 | + |
| 78 | + if m.successors.len() > MAX_MATCH_PAYLOAD_SLOTS { |
| 79 | + let overflow: Vec<_> = m.successors.drain(MAX_MATCH_PAYLOAD_SLOTS - 1..).collect(); |
| 80 | + post_chains.push(PostChain::Successors(overflow)); |
| 81 | + } |
| 82 | + |
| 83 | + if post_chains.is_empty() { |
| 84 | + out.push(m.into()); |
| 85 | + return; |
| 86 | + } |
| 87 | + |
| 88 | + // Build chain: match → chain1 → chain2 → ... → final_succs |
| 89 | + let mut current_succs = std::mem::take(&mut m.successors); |
| 90 | + |
| 91 | + for chain in post_chains.into_iter().rev() { |
| 92 | + let chain_entry = fresh_label(next_label); |
| 93 | + match chain { |
| 94 | + PostChain::NegFields(neg_fields) => { |
| 95 | + emit_neg_fields_chain(chain_entry, current_succs, neg_fields, out, next_label); |
| 96 | + } |
| 97 | + PostChain::PostEffects(effects) => { |
| 98 | + emit_effects_chain_to_succs(chain_entry, current_succs, effects, out, next_label); |
| 99 | + } |
| 100 | + PostChain::Successors(succs) => { |
| 101 | + emit_successors_cascade(chain_entry, succs, out, next_label); |
| 102 | + current_succs = vec![chain_entry]; |
| 103 | + continue; |
| 104 | + } |
| 105 | + } |
| 106 | + current_succs = vec![chain_entry]; |
| 107 | + } |
| 108 | + |
| 109 | + m.successors = current_succs; |
| 110 | + out.push(m.into()); |
| 111 | +} |
| 112 | + |
| 113 | +fn emit_effects_chain( |
| 114 | + entry: Label, |
| 115 | + exit: Label, |
| 116 | + mut effects: Vec<EffectIR>, |
| 117 | + out: &mut Vec<InstructionIR>, |
| 118 | + next_label: &mut u32, |
| 119 | +) { |
| 120 | + if effects.is_empty() { |
| 121 | + out.push(MatchIR::epsilon(entry, exit).into()); |
| 122 | + return; |
| 123 | + } |
| 124 | + |
| 125 | + if effects.len() <= MAX_PRE_EFFECTS { |
| 126 | + out.push(MatchIR::epsilon(entry, exit).pre_effects(effects).into()); |
| 127 | + return; |
| 128 | + } |
| 129 | + |
| 130 | + let first_batch: Vec<_> = effects.drain(..MAX_PRE_EFFECTS).collect(); |
| 131 | + let intermediate = fresh_label(next_label); |
| 132 | + out.push( |
| 133 | + MatchIR::epsilon(entry, intermediate) |
| 134 | + .pre_effects(first_batch) |
| 135 | + .into(), |
| 136 | + ); |
| 137 | + emit_effects_chain(intermediate, exit, effects, out, next_label); |
| 138 | +} |
| 139 | + |
| 140 | +fn emit_neg_fields_chain( |
| 141 | + entry: Label, |
| 142 | + final_succs: Vec<Label>, |
| 143 | + mut neg_fields: Vec<u16>, |
| 144 | + out: &mut Vec<InstructionIR>, |
| 145 | + next_label: &mut u32, |
| 146 | +) { |
| 147 | + if neg_fields.len() <= MAX_NEG_FIELDS { |
| 148 | + let mut m = MatchIR::at(entry).neg_fields(neg_fields); |
| 149 | + m.successors = final_succs; |
| 150 | + out.push(m.into()); |
| 151 | + return; |
| 152 | + } |
| 153 | + |
| 154 | + let first_batch: Vec<_> = neg_fields.drain(..MAX_NEG_FIELDS).collect(); |
| 155 | + let intermediate = fresh_label(next_label); |
| 156 | + out.push( |
| 157 | + MatchIR::at(entry) |
| 158 | + .neg_fields(first_batch) |
| 159 | + .next(intermediate) |
| 160 | + .into(), |
| 161 | + ); |
| 162 | + emit_neg_fields_chain(intermediate, final_succs, neg_fields, out, next_label); |
| 163 | +} |
| 164 | + |
| 165 | +fn emit_effects_chain_to_succs( |
| 166 | + entry: Label, |
| 167 | + final_succs: Vec<Label>, |
| 168 | + mut effects: Vec<EffectIR>, |
| 169 | + out: &mut Vec<InstructionIR>, |
| 170 | + next_label: &mut u32, |
| 171 | +) { |
| 172 | + if effects.len() <= MAX_POST_EFFECTS { |
| 173 | + let mut m = MatchIR::at(entry).post_effects(effects); |
| 174 | + m.successors = final_succs; |
| 175 | + out.push(m.into()); |
| 176 | + return; |
| 177 | + } |
| 178 | + |
| 179 | + let first_batch: Vec<_> = effects.drain(..MAX_POST_EFFECTS).collect(); |
| 180 | + let intermediate = fresh_label(next_label); |
| 181 | + out.push( |
| 182 | + MatchIR::at(entry) |
| 183 | + .post_effects(first_batch) |
| 184 | + .next(intermediate) |
| 185 | + .into(), |
| 186 | + ); |
| 187 | + emit_effects_chain_to_succs(intermediate, final_succs, effects, out, next_label); |
| 188 | +} |
| 189 | + |
| 190 | +fn emit_successors_cascade( |
| 191 | + entry: Label, |
| 192 | + mut succs: Vec<Label>, |
| 193 | + out: &mut Vec<InstructionIR>, |
| 194 | + next_label: &mut u32, |
| 195 | +) { |
| 196 | + if succs.len() <= MAX_MATCH_PAYLOAD_SLOTS { |
| 197 | + out.push(MatchIR::at(entry).next_many(succs).into()); |
| 198 | + return; |
| 199 | + } |
| 200 | + |
| 201 | + let overflow: Vec<_> = succs.drain(MAX_MATCH_PAYLOAD_SLOTS - 1..).collect(); |
| 202 | + let intermediate = fresh_label(next_label); |
| 203 | + succs.push(intermediate); |
| 204 | + out.push(MatchIR::at(entry).next_many(succs).into()); |
| 205 | + emit_successors_cascade(intermediate, overflow, out, next_label); |
| 206 | +} |
0 commit comments