Compute delta_ip and delta_stp in side table#626
Conversation
ia0
left a comment
There was a problem hiding this comment.
There are a few issues:
side_table_last_entryis wrong, you need to supported nested blocks. You might need to put an index to the side table inLabelorLabelKind.- I don't think instruction index is the correct abstraction, I think we want instruction address. So you don't need
instr_idxat all and can useparser.save().as_ptr() as i32.
Then in terms of design, I think we should keep Context constant in Expr and make Expr::check_body return the side-table. We can then set it from Context::check_module where check_body is called.
Thanks! The issues with
|
ia0
left a comment
There was a problem hiding this comment.
I'm not sure it's worth iterating too much. It's simpler to just design correctly from the start, which means understanding the goal. Essentially, we want all occurrences of skip_to_{else,end}() in exec.rs to be resolved from the side-table (to which we can add Loop to handle everything uniformly). This means If, Else, Br{,If} should each have an entry in the side-table and BrTable as much as its arguments. This defines when an entry is added. Then we need to fill their content, which is done on Loop, Else, and End (with Loop done before the entry is added). This means that for Loop we can store the parser position in the LabelKind (like done in exec.rs). For Else and End, we need the side-table entries to register themselves in their labels. This means that Label should contain an entries: Vec<(&'m [u8], usize)> recording for each side-table entry the parser position and the side-table length when it was added. Then in end_label() we can simply go over all entries compute the difference between the current parser position and the saved one to compute the delta IP, as well as the difference between the current side-table length and the index for the delta STP. We can then update the side-table entry.
Those are not issues because we only care about deltas between |
Thanks! I implemented this design except |
| if label.kind == LabelKind::If { | ||
| check(label.type_.params == label.type_.results)?; |
There was a problem hiding this comment.
@ia0 Could you explain the purpose of this if block? Thanks.
There was a problem hiding this comment.
This is because the binary format allows an if without else (see the note of https://webassembly.github.io/spec/core/binary/instructions.html#control-instructions) and defines it as having an empty sequence for the else. So here we are validating the empty sequence according to https://webassembly.github.io/spec/core/valid/instructions.html#empty-instruction-sequence-epsilon which essentially says that the params and results must be the same.
ia0
left a comment
There was a problem hiding this comment.
Thanks! It starts to get in shape but there is still some confusion between "branches" and "entries". I introduced those new names to help make the distinction. An entry is an element in the side table. A branch is a marker left by an entry in the label it wishes to know the End (or Loop for Loop). There is thus a one-to-one mapping between branches and entries. This is ensured by used Option<SideTableEntryView> and making sure we write Some only on None and we only have Some at the end. We have a stitch function that stitches an entry based on its branch (with assumption that the parser is at the End of the label in which the branch is, or Else for If).
| if label.kind == LabelKind::If { | ||
| check(label.type_.params == label.type_.results)?; |
There was a problem hiding this comment.
This is because the binary format allows an if without else (see the note of https://webassembly.github.io/spec/core/binary/instructions.html#control-instructions) and defines it as having an empty sequence for the else. So here we are validating the empty sequence according to https://webassembly.github.io/spec/core/valid/instructions.html#empty-instruction-sequence-epsilon which essentially says that the params and results must be the same.
ia0
left a comment
There was a problem hiding this comment.
Thanks! There are still some fundamental issues but the overall shape is getting there.
ia0
left a comment
There was a problem hiding this comment.
Thanks, it's almost there. The main question is how to deal with pathological examples in the test suite. I think the strategy of using a sentinel value in the side table entries and returning an error at runtime is a good option. If this results in performance problems (because we need to check the sentinel at runtime), we can gate this behavior behind a feature (like toctou). The feature would move returning an error from exec.rs to valid.rs ensuring that there are no sentinel values at runtime.
|
I've merge |
6176de2 to
8ef39d1
Compare
|
Could you clarify the hw-host error? I guess it results from the side table bit mask size issue. |
|
I posted zhouwfang#2 to add |
ia0
left a comment
There was a problem hiding this comment.
Thanks! I think this should work modulo off-by-one errors. While updating exec.rs we might want to change the exact location of the branches or how to interpret the side table.
Yes, this is indeed the side table masks being too short. So we'll probably have to use a --- a/crates/interpreter/src/bit_field.rs
+++ b/crates/interpreter/src/bit_field.rs
@@ -29,6 +29,8 @@ fn offset(mask: u32) -> i32 {
pub fn into_field(mask: u32, value: u32) -> Result<u32, Error> {
let field = (value << mask.trailing_zeros()) & mask;
if from_field(mask, field) != value {
+ #[cfg(feature = "debug")]
+ eprintln!("Bit field value {value:08x} doesn't fit in mask {mask:08x}.");
return Err(unsupported(if_debug!(Unsupported::SideTable)));
}
Ok(field)This is similar to what we do in the |
ia0
left a comment
There was a problem hiding this comment.
Thanks! I've refactored the side table out to help the borrow checker understand it's alright. I also split branch() into branch_source() and branch_target() to be more explicit why we're taking a branch and avoid accidents of forgetting to allocate a side-table entry when taking a source branch.
#46