Skip to content

Commit 9f89c67

Browse files
authored
Fix branch table logic (#776)
The main changes are: - Do not create a branch table entry for unreachable code. This saves space and permits a uniform treatment of branches. - Use the correct stack length for the Loop branch target (we're taking the branch before being in the label, so the current stack needs to be added). - Take the Else source and target branches at the correct time (i.e. when the stack is the expected one such that the stack length is correctly computed).
1 parent 9124966 commit 9f89c67

File tree

2 files changed

+56
-75
lines changed

2 files changed

+56
-75
lines changed

crates/interpreter/src/side_table.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ pub fn serialize(side_table: &[MetadataEntry]) -> Result<Vec<u8>, Error> {
106106
#[repr(transparent)]
107107
pub struct BranchTableEntry([u8; 6]);
108108

109+
#[derive(Debug)]
109110
pub struct BranchTableEntryView {
110111
/// The amount to adjust the instruction pointer by if the branch is taken.
111112
pub delta_ip: i32,

crates/interpreter/src/valid.rs

Lines changed: 55 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,9 @@ impl<'m> BranchTableApi<'m> for &mut Vec<BranchTableEntry> {
113113
) -> CheckResult {
114114
let delta_ip = delta(source, target, |x| x.parser.as_ptr() as isize)?;
115115
let delta_stp = delta(source, target, |x| x.branch_table as isize)?;
116-
let val_cnt = u32::try_from(target.result).map_err(|_| {
117-
#[cfg(feature = "debug")]
118-
eprintln!("side-table val_cnt overflow {0}", target.result);
119-
unsupported(if_debug!(Unsupported::SideTable))
120-
})?;
121-
let pop_cnt = pop_cnt(source, target)?;
116+
let val_cnt = u32::try_from(target.values).map_err(|_| side_table_unsupported())?;
117+
let pop_cnt = source.stack.checked_sub(target.stack).ok_or_else(invalid)?;
118+
let pop_cnt = u32::try_from(pop_cnt).map_err(|_| side_table_unsupported())?;
122119
debug_assert!(self[source.branch_table].is_invalid());
123120
self[source.branch_table] =
124121
BranchTableEntry::new(BranchTableEntryView { delta_ip, delta_stp, val_cnt, pop_cnt })?;
@@ -181,37 +178,25 @@ impl ValidMode for Verify {
181178

182179
impl<'m> BranchesApi<'m> for Option<SideTableBranch<'m>> {
183180
fn push_branch(&mut self, branch: SideTableBranch<'m>) -> CheckResult {
184-
// TODO(dev/fast-interp): This should be `is_none_or(|x| x == branch)` once pop_cnt is
185-
// figured out.
186-
check(self.replace(branch).is_none_or(|x| {
187-
x.parser == branch.parser
188-
&& x.branch_table == branch.branch_table
189-
&& x.result == branch.result
190-
}))
181+
check(self.replace(branch).is_none_or(|x| x == branch))
191182
}
192183
}
193184

194185
impl<'m> BranchTableApi<'m> for MetadataView<'m> {
195186
fn stitch_branch(
196187
&mut self, source: SideTableBranch<'m>, target: SideTableBranch<'m>,
197188
) -> CheckResult {
198-
// TODO(dev/fast-interp): This should be `source == target)` once pop_cnt is figured out.
199-
check(
200-
source.parser == target.parser
201-
&& source.result == target.result
202-
&& source.branch_table == target.branch_table
203-
&& source.stack <= target.stack,
204-
)
189+
check(source == target)
205190
}
206191

207192
fn patch_branch(&self, mut source: SideTableBranch<'m>) -> Result<SideTableBranch<'m>, Error> {
208193
let entry = self.metadata.branch_table()[source.branch_table].view();
194+
// TODO(dev/fast-interp): We want a safe version of offset_front.
209195
source.parser = offset_front(source.parser, entry.delta_ip as isize);
210-
// TODO(dev/fast-interp): This should wrapping_add_signed for performance and
211-
// strict_add_signed for debugging/safety.
212-
source.branch_table = source.branch_table.saturating_add_signed(entry.delta_stp as isize);
213-
source.result = entry.val_cnt as usize;
196+
source.branch_table =
197+
source.branch_table.checked_add_signed(entry.delta_stp as isize).ok_or_else(invalid)?;
214198
source.stack -= entry.pop_cnt as usize;
199+
source.values = entry.val_cnt as usize;
215200
Ok(source)
216201
}
217202

@@ -619,8 +604,10 @@ struct Expr<'a, 'm, M: ValidMode> {
619604
struct SideTableBranch<'m> {
620605
parser: &'m [u8],
621606
branch_table: usize,
607+
/// Function stack length (including branch values).
622608
stack: usize,
623-
result: usize, // unused (zero) for source branches
609+
/// Branch values (only for target branches, zero for source branches).
610+
values: usize,
624611
}
625612

626613
#[derive(Debug, Default)]
@@ -632,7 +619,7 @@ struct Label<'m, M: ValidMode> {
632619
polymorphic: bool,
633620
stack: Vec<OpdType>,
634621
branches: M::Branches<'m>,
635-
/// Total stack length of the labels in this function up to this label.
622+
/// Function stack length up to this label (excluding branch values).
636623
prev_stack: usize,
637624
}
638625

@@ -641,7 +628,7 @@ enum LabelKind<'m> {
641628
#[default]
642629
Block,
643630
Loop(SideTableBranch<'m>),
644-
If(SideTableBranch<'m>),
631+
If(Option<SideTableBranch<'m>>),
645632
}
646633

647634
impl<'a, 'm, M: ValidMode> Expr<'a, 'm, M> {
@@ -722,6 +709,8 @@ impl<'a, 'm, M: ValidMode> Expr<'a, 'm, M> {
722709
let type_ = self.blocktype(&b)?;
723710
let mut target = self.branch_target(type_.params.len());
724711
target.parser = saved;
712+
target.stack +=
713+
self.stack().len().checked_sub(target.values).ok_or_else(invalid)?;
725714
self.push_label(type_, LabelKind::Loop(target))?
726715
}
727716
If(b) => {
@@ -730,25 +719,24 @@ impl<'a, 'm, M: ValidMode> Expr<'a, 'm, M> {
730719
self.push_label(self.blocktype(&b)?, LabelKind::If(branch))?;
731720
}
732721
Else => {
722+
self.br_label(0)?;
723+
let FuncType { params, results } = self.label().type_;
724+
self.pops(results)?;
725+
check(self.stack().is_empty())?;
726+
self.label().polymorphic = false;
727+
self.pushs(params);
733728
match core::mem::replace(&mut self.label().kind, LabelKind::Block) {
734-
LabelKind::If(source) => {
729+
LabelKind::If(None) => (),
730+
LabelKind::If(Some(source)) => {
735731
let source = M::BranchTable::patch_branch(
736732
self.branch_table.as_ref().unwrap(),
737733
source,
738734
)?;
739-
let result = self.label().type_.results.len();
740-
let mut target = self.branch_target(result);
741-
target.branch_table += 1;
735+
let target = self.branch_target(params.len());
742736
self.branch_table.as_mut().unwrap().stitch_branch(source, target)?;
743737
}
744738
_ => Err(invalid())?,
745739
}
746-
let FuncType { params, results } = self.label().type_;
747-
self.pops(results)?;
748-
check(self.stack().is_empty())?;
749-
self.label().polymorphic = false;
750-
self.pushs(params);
751-
self.br_label(0)?;
752740
}
753741
End => unreachable!(),
754742
Br(l) => {
@@ -1011,10 +999,12 @@ impl<'a, 'm, M: ValidMode> Expr<'a, 'm, M> {
1011999
let label = self.label();
10121000
if let LabelKind::If(source) = label.kind {
10131001
check(label.type_.params == label.type_.results)?;
1014-
let source = self.branch_table.as_ref().unwrap().patch_branch(source)?;
1015-
// SAFETY: This function is only called after parsing an End instruction.
1016-
target.parser = offset_front(target.parser, -1);
1017-
self.branch_table.as_mut().unwrap().stitch_branch(source, target)?;
1002+
if let Some(source) = source {
1003+
let source = self.branch_table.as_ref().unwrap().patch_branch(source)?;
1004+
// SAFETY: This function is only called after parsing an End instruction.
1005+
target.parser = offset_front(target.parser, -1);
1006+
self.branch_table.as_mut().unwrap().stitch_branch(source, target)?;
1007+
}
10181008
}
10191009
}
10201010
let results = self.label().type_.results;
@@ -1030,35 +1020,42 @@ impl<'a, 'm, M: ValidMode> Expr<'a, 'm, M> {
10301020
let l = l as usize;
10311021
let n = self.labels.len();
10321022
check(l < n)?;
1033-
let source = self.branch_source();
1034-
let source = self.branch_table.as_ref().unwrap().patch_branch(source)?;
1023+
let source = match self.branch_source() {
1024+
None => None,
1025+
Some(x) => Some(self.branch_table.as_ref().unwrap().patch_branch(x)?),
1026+
};
10351027
let label = &mut self.labels[n - l - 1];
10361028
Ok(match label.kind {
10371029
LabelKind::Block | LabelKind::If(_) => {
1038-
label.branches.push_branch(source)?;
1030+
if let Some(source) = source {
1031+
label.branches.push_branch(source)?;
1032+
}
10391033
label.type_.results
10401034
}
10411035
LabelKind::Loop(target) => {
1042-
self.branch_table.as_mut().unwrap().stitch_branch(source, target)?;
1036+
if let Some(source) = source {
1037+
self.branch_table.as_mut().unwrap().stitch_branch(source, target)?;
1038+
}
10431039
label.type_.params
10441040
}
10451041
})
10461042
}
10471043

1048-
fn branch_source(&mut self) -> SideTableBranch<'m> {
1044+
fn branch_source(&mut self) -> Option<SideTableBranch<'m>> {
1045+
if self.label().polymorphic {
1046+
// We don't need a branch table entry for unreachable code.
1047+
return None;
1048+
}
10491049
let mut branch = self.branch();
10501050
branch.stack += self.stack().len();
10511051
self.branch_table.as_mut().unwrap().allocate_branch();
1052-
branch
1052+
Some(branch)
10531053
}
10541054

1055-
fn branch_target(&self, result: usize) -> SideTableBranch<'m> {
1055+
fn branch_target(&self, values: usize) -> SideTableBranch<'m> {
10561056
let mut branch = self.branch();
1057-
branch.result = result;
1058-
// TODO(dev/fast-interp): Figure out if there are not cases (like Loop or Else) where we
1059-
// want to add `self.stack().len()`. Also adding result here should purely be a convention
1060-
// with pop_cnt, and we should be able to do the opposite choice (to be considered).
1061-
branch.stack += result;
1057+
branch.stack += values;
1058+
branch.values = values;
10621059
branch
10631060
}
10641061

@@ -1067,7 +1064,7 @@ impl<'a, 'm, M: ValidMode> Expr<'a, 'm, M> {
10671064
parser: self.parser.save(),
10681065
branch_table: self.branch_table.as_ref().unwrap().next_index(),
10691066
stack: self.immutable_label().prev_stack,
1070-
result: 0,
1067+
values: 0,
10711068
}
10721069
}
10731070

@@ -1137,27 +1134,10 @@ impl<'a, 'm, M: ValidMode> Expr<'a, 'm, M> {
11371134
fn delta(
11381135
source: SideTableBranch, target: SideTableBranch, field: fn(SideTableBranch) -> isize,
11391136
) -> MResult<i32, Check> {
1140-
let source = field(source);
1141-
let target = field(target);
1142-
let Some(delta) = target.checked_sub(source) else {
1143-
#[cfg(feature = "debug")]
1144-
eprintln!("side-table subtraction overflow {target} - {source}");
1145-
return Err(unsupported(if_debug!(Unsupported::SideTable)));
1146-
};
1147-
i32::try_from(delta).map_err(|_| {
1148-
#[cfg(feature = "debug")]
1149-
eprintln!("side-table conversion overflow {delta}");
1150-
unsupported(if_debug!(Unsupported::SideTable))
1151-
})
1137+
let delta = field(target).checked_sub(field(source)).ok_or_else(side_table_unsupported)?;
1138+
i32::try_from(delta).map_err(|_| side_table_unsupported())
11521139
}
11531140

1154-
fn pop_cnt(source: SideTableBranch, target: SideTableBranch) -> MResult<u32, Check> {
1155-
// TODO(dev/fast-interp): Figure out why we can't simply source.stack - target.stack and
1156-
// document it. We're losing information by saturating.
1157-
let res = source.stack.saturating_sub(target.stack);
1158-
u32::try_from(res).map_err(|_| {
1159-
#[cfg(feature = "debug")]
1160-
eprintln!("side-table pop_cnt overflow {res}");
1161-
unsupported(if_debug!(Unsupported::SideTable))
1162-
})
1141+
fn side_table_unsupported() -> Error {
1142+
unsupported(if_debug!(Unsupported::SideTable))
11631143
}

0 commit comments

Comments
 (0)