Skip to content

Commit 98d43fe

Browse files
committed
Fuse switch selection with branch entry matching
Amp-Thread-ID: https://ampcode.com/threads/T-01a15ba8-2c2e-737b-ad72-3fd2746cba8e
1 parent d6efeb9 commit 98d43fe

28 files changed

Lines changed: 1411 additions & 629 deletions

‎crates/compiler/src/bytecode/mod.rs‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ pub(crate) use instructions::{
4646
Call, CallOwnership, CalleeContract, Match, MatchCounts, MatchInstr, MatchPredicate, Opcode,
4747
PREDICATE_SLOTS, Return, SuccessorAddr, Switch, SwitchEntry,
4848
};
49-
pub(crate) use module::{DecodedCall, DecodedInstr, DecodedMatch, DecodedPredicate, Instruction};
49+
pub(crate) use module::{
50+
DecodedAction, DecodedCall, DecodedCandidate, DecodedInstr, DecodedMatch, DecodedPredicate,
51+
Instruction,
52+
};
5053
pub(crate) use node_kind_constraint::NodeKindConstraint;
5154
pub(crate) use plotnik_runtime::{Nav, deserialize_dfa};
5255
pub(crate) use predicate_op::PredicateOp;

‎crates/compiler/src/bytecode/module/decoded.rs‎

Lines changed: 127 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
use std::collections::{HashMap, HashSet};
2+
13
use crate::bytecode::{
2-
BYTECODE_WORD_SIZE, CallOwnership, CodeAddr, Effect, Nav, NodeKindConstraint, PredicateOp,
3-
SuccessorAddr, SwitchEntry,
4+
BYTECODE_WORD_SIZE, CallOwnership, CodeAddr, Effect, Nav, PredicateOp, SuccessorAddr,
5+
SwitchEntry,
46
};
5-
use crate::core::NodeFieldId;
7+
use crate::core::{NodeFieldId, NodeKindId};
68
use plotnik_runtime::PortId;
79

810
use super::super::instructions::header_byte;
9-
use super::{Instruction, Module};
11+
use super::{Instruction, Module, ModuleError};
1012

1113
#[derive(Clone, Copy, Debug)]
1214
pub(crate) enum DecodedInstr {
@@ -26,16 +28,22 @@ pub(crate) struct DecodedSwitch {
2628
#[derive(Clone, Copy, Debug)]
2729
pub(crate) struct DecodedMatch {
2830
pub(crate) nav: Nav,
29-
pub(crate) node_kind: NodeKindConstraint,
31+
pub(crate) kind: Option<NodeKindId>,
32+
pub(crate) candidate: DecodedCandidate,
33+
effects_base: u32,
34+
succ_base: u32,
35+
effects_len: u8,
36+
succ_len: u8,
37+
}
38+
39+
#[derive(Clone, Copy, Debug)]
40+
pub(crate) struct DecodedCandidate {
41+
pub(crate) named: Option<bool>,
3042
pub(crate) node_field: Option<NodeFieldId>,
3143
pub(crate) missing: bool,
3244
pub(crate) predicate: Option<DecodedPredicate>,
33-
effects_base: u32,
3445
neg_base: u32,
35-
succ_base: u32,
36-
effects_len: u8,
3746
neg_len: u8,
38-
succ_len: u8,
3947
}
4048

4149
impl DecodedMatch {
@@ -67,18 +75,50 @@ impl DecodedCall {
6775
}
6876
}
6977

78+
#[derive(Clone, Copy, Debug)]
79+
pub(crate) struct DecodedSwitchEntry {
80+
pub(crate) kind: NodeKindId,
81+
action: u16,
82+
}
83+
84+
#[derive(Debug)]
85+
pub(crate) struct DecodedAction {
86+
pub(crate) entry: CodeAddr,
87+
pub(crate) matched_at: CodeAddr,
88+
pub(crate) matched: DecodedMatch,
89+
pub(crate) prefix: Vec<DecodedPrefix>,
90+
}
91+
92+
#[derive(Debug)]
93+
pub(crate) struct DecodedPrefix {
94+
pub(crate) ip: CodeAddr,
95+
effects_base: u32,
96+
effects_len: u8,
97+
}
98+
7099
#[derive(Debug, Default)]
71100
pub(crate) struct DecodedProgram {
72101
words: Vec<DecodedInstr>,
73102
effects: Vec<Effect>,
74103
neg_fields: Vec<NodeFieldId>,
75104
successors: Vec<SuccessorAddr>,
76-
switch_entries: Vec<SwitchEntry>,
105+
switch_entries: Vec<DecodedSwitchEntry>,
106+
actions: Vec<DecodedAction>,
77107
}
78108

79109
impl DecodedProgram {
80-
pub(crate) fn switch_entries(&self, switch: &DecodedSwitch) -> &[SwitchEntry] {
81-
&self.switch_entries[switch.base as usize..][..switch.len as usize]
110+
#[inline]
111+
pub(crate) fn switch_entries(&self, switch: &DecodedSwitch) -> &[DecodedSwitchEntry] {
112+
&self.switch_entries[switch.base as usize..][..usize::from(switch.len)]
113+
}
114+
115+
#[inline]
116+
pub(crate) fn switch_action(&self, entry: &DecodedSwitchEntry) -> &DecodedAction {
117+
&self.actions[usize::from(entry.action)]
118+
}
119+
120+
pub(crate) fn prefix_effects(&self, prefix: &DecodedPrefix) -> &[Effect] {
121+
&self.effects[prefix.effects_base as usize..][..prefix.effects_len as usize]
82122
}
83123

84124
#[inline]
@@ -98,8 +138,8 @@ impl DecodedProgram {
98138
}
99139

100140
#[inline]
101-
pub(crate) fn neg_fields(&self, m: &DecodedMatch) -> &[NodeFieldId] {
102-
&self.neg_fields[m.neg_base as usize..][..m.neg_len as usize]
141+
pub(crate) fn neg_fields(&self, candidate: &DecodedCandidate) -> &[NodeFieldId] {
142+
&self.neg_fields[candidate.neg_base as usize..][..candidate.neg_len as usize]
103143
}
104144

105145
#[inline]
@@ -118,13 +158,10 @@ impl DecodedProgram {
118158
}
119159
}
120160

121-
pub(crate) fn build(module: &Module) -> DecodedProgram {
161+
pub(crate) fn build(module: &Module) -> Result<DecodedProgram, ModuleError> {
122162
let instructions = module.instructions_slice();
123163
let word_count = instructions.len() / BYTECODE_WORD_SIZE;
124-
let mut program = DecodedProgram {
125-
switch_entries: module.switch_tables().iter().collect(),
126-
..Default::default()
127-
};
164+
let mut program = DecodedProgram::default();
128165
program.words.reserve(word_count);
129166

130167
let mut word_addr = 0usize;
@@ -154,15 +191,18 @@ pub(crate) fn build(module: &Module) -> DecodedProgram {
154191

155192
program.words.push(DecodedInstr::Match(DecodedMatch {
156193
nav: m.nav,
157-
node_kind: m.node_kind,
158-
node_field: m.node_field,
159-
missing: m.missing(),
160-
predicate,
194+
kind: m.node_kind.kind_id(),
195+
candidate: DecodedCandidate {
196+
named: (!m.node_kind.is_any()).then_some(m.node_kind.is_named()),
197+
node_field: m.node_field,
198+
missing: m.missing(),
199+
predicate,
200+
neg_base,
201+
neg_len: (program.neg_fields.len() - neg_base as usize) as u8,
202+
},
161203
effects_base,
162-
neg_base,
163204
succ_base,
164205
effects_len: (program.effects.len() - effects_base as usize) as u8,
165-
neg_len: (program.neg_fields.len() - neg_base as usize) as u8,
166206
succ_len: (program.successors.len() - succ_base as usize) as u8,
167207
}));
168208
for _ in 1..opcode.word_count() {
@@ -192,7 +232,68 @@ pub(crate) fn build(module: &Module) -> DecodedProgram {
192232
}
193233
}
194234
}
195-
program
235+
let mut actions = HashMap::new();
236+
for entry in module.switch_tables().iter() {
237+
let target = CodeAddr::from(u16::from(entry.target));
238+
let action = if let Some(&index) = actions.get(&(target, entry.kind)) {
239+
index
240+
} else {
241+
let action = program.selected_entry(target, entry.kind)?;
242+
let index = u16::try_from(program.actions.len())
243+
.expect("selected entries are bounded by instruction count");
244+
program.actions.push(action);
245+
actions.insert((target, entry.kind), index);
246+
index
247+
};
248+
program.switch_entries.push(DecodedSwitchEntry {
249+
kind: entry.kind,
250+
action,
251+
});
252+
}
253+
Ok(program)
254+
}
255+
256+
impl DecodedProgram {
257+
fn selected_entry(
258+
&self,
259+
entry: CodeAddr,
260+
kind: NodeKindId,
261+
) -> Result<DecodedAction, ModuleError> {
262+
let mut ip = entry;
263+
let mut visited = HashSet::new();
264+
let mut prefix = Vec::new();
265+
loop {
266+
if !visited.insert(ip) {
267+
return Err(ModuleError::MalformedSwitchTable);
268+
}
269+
270+
let DecodedInstr::Match(matched) = self.instruction_at(ip) else {
271+
return Err(ModuleError::MalformedSwitchTable);
272+
};
273+
if !matched.is_epsilon() {
274+
if matched.nav != Nav::StayExact || matched.kind != Some(kind) {
275+
return Err(ModuleError::MalformedSwitchTable);
276+
}
277+
278+
return Ok(DecodedAction {
279+
entry,
280+
matched_at: ip,
281+
matched,
282+
prefix,
283+
});
284+
}
285+
286+
let [next] = self.successors(&matched) else {
287+
return Err(ModuleError::MalformedSwitchTable);
288+
};
289+
prefix.push(DecodedPrefix {
290+
ip,
291+
effects_base: matched.effects_base,
292+
effects_len: matched.effects_len,
293+
});
294+
ip = CodeAddr::from(u16::from(*next));
295+
}
296+
}
196297
}
197298

198299
fn pool_base(len: usize) -> u32 {

‎crates/compiler/src/bytecode/module/load.rs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ impl Module {
140140
}
141141
#[cfg(not(debug_assertions))]
142142
let _ = is_start;
143-
module.decoded = decoded::build(&module);
143+
module.decoded = decoded::build(&module)?;
144144
Ok(module)
145145
}
146146

‎crates/compiler/src/bytecode/module/mod.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ mod matcher_verify;
2121
mod verify;
2222

2323
pub(crate) use decoded::{
24-
DecodedCall, DecodedInstr, DecodedMatch, DecodedPredicate, DecodedProgram,
24+
DecodedAction, DecodedCall, DecodedCandidate, DecodedInstr, DecodedMatch, DecodedPredicate,
25+
DecodedProgram,
2526
};
2627
pub(crate) use load::ModuleError;
2728

‎crates/compiler/src/bytecode/module/validate_tests.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1770,6 +1770,10 @@ fn malformed_switch_tables_are_rejected() {
17701770
(vec![(44, (size - 1).to_le_bytes().to_vec())], true),
17711771
(vec![(table, second_entry), (table + 4, first_entry)], true),
17721772
(vec![(table + 4, first_kind)], true),
1773+
(
1774+
vec![(table + 2, valid[table + 6..table + 8].to_vec())],
1775+
true,
1776+
),
17731777
(vec![(instruction + 4, size.to_le_bytes().to_vec())], false),
17741778
(
17751779
vec![

0 commit comments

Comments
 (0)