-
Notifications
You must be signed in to change notification settings - Fork 524
AVM: match, pushints, and pushbytess opcodes #4645
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| - construct a list of match cases from B_1 to B_N and branch to the Ith label where B_I = A. Continue to the following instruction if no matches are found. | ||
| - Availability: v8 | ||
|
|
||
| `match` consumes N+1 values from the stack. Let the top stack value be A. The following N values represent an ordered list of match cases/constants (B), where the first value (B[0]) is the deepest in the stack. The immediate arguments are an ordered list of N labels (L). `match` will branch to the L[I], where B[I] = A. If there are no matches then execution continues on to the next instruction. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We call it B_1 to B_N above, then B[0] in description.
data/transactions/logic/assembler.go
Outdated
| l = binary.PutUvarint(scratch[:], cu) | ||
| ops.pending.Write(scratch[:l]) | ||
| if !ops.known.deadcode { | ||
| if !ops.known.deadcode { // this conditional doesn't seem necessary |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It allows intcblock in deadcode to exist without mucking up the assemblers belief about what is in the cblock.
data/transactions/logic/eval_test.go
Outdated
| int 0 | ||
| int 1 | ||
| int 0 | ||
| match zero one |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's do these test with big weird numbers like 37 and 62 or some such, so it's obvious at a glance that we're not talking about indices into the labels.
data/transactions/logic/eval.go
Outdated
| matchVal := cx.stack[top] | ||
| cx.stack = cx.stack[:top] | ||
|
|
||
| top = len(cx.stack) - n |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weird to call this top. It's the base of the match values, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I was thinking of top as representing the new top of the stack after the match cases are removed from the stack but I can see how that's imprecise/confusing.
Codecov Report
@@ Coverage Diff @@
## master #4645 +/- ##
==========================================
+ Coverage 54.33% 54.37% +0.04%
==========================================
Files 402 402
Lines 51828 51859 +31
==========================================
+ Hits 28159 28198 +39
+ Misses 21297 21292 -5
+ Partials 2372 2369 -3
📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
|
|
||
| ## pushbytess bytes ... | ||
|
|
||
| - Opcode: 0x82 {varuint length} [({varuint value length} bytes), ...] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about we change the first length to count?
For consistency, we would do this for the cblock opcodes and pushints too.
|
|
||
| - Opcode: 0x8e {uint8 branch count} [{int16 branch offset, big-endian}, ...] | ||
| - Stack: ..., [N items] → ... | ||
| - construct a list of match cases from B[0] to B[N] and branch to the Ith label where B[I] = A. Continue to the following instruction if no matches are found. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
B[0] to B[N] implies N+1 targets.
I'd avoid talk of "construct a list"
The input stack doesn't seem right. It should have N items and a value, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, seems I've been sloppy with the docs.
data/transactions/logic/eval.go
Outdated
| finalLen := len(cx.stack) + len(intc) | ||
| if cap(cx.stack) < finalLen { | ||
| // Let's grow all at once, plus a little slack. | ||
| newStack := make([]stackValue, len(cx.stack), finalLen+4) | ||
| copy(newStack, cx.stack) | ||
| cx.stack = newStack | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we're doing this in three places now. Maybe we should have a function, grow or ensure?
data/transactions/logic/eval.go
Outdated
| // first we must verify that the matchList has the expected length and all types match the type of matchVal | ||
| if len(matchList) != n { | ||
| return fmt.Errorf("list of match constants has length %d when length %d was expected", len(matchList), n) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is impossible, right? You've confirmed the stack is big enough, then sliced it up properly.
(Certainly the comment is out of date.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, the check and comment are both out of date.
data/transactions/logic/opcodes.go
Outdated
| {0x8c, "frame_bury", opFrameBury, proto("a:"), fpVersion, immKinded(immInt8, "i").typed(typeFrameBury)}, | ||
| {0x8d, "switch", opSwitch, proto("i:"), 8, detSwitch()}, | ||
| // 0x8e will likely be a switch on pairs of values/targets, called `match` | ||
| {0x8e, "match", opMatch, proto(":", "[N items]", ""), 8, detSwitch().trust()}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's where you need something like, "[N items], A".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, how about using:
"[T1 T2 T3 ... TN], B"
That way you have names for the targets (which I renamed T for target instead of B for branch to prevent confusion because we normally named the arguments A and B with A deeper in the stack).
That should make your documentation of the behavior easier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oops, they are not targets, they are match values. So I'd be ok with "[A1 A2 A3 ... An], B"
data/transactions/logic/README.md
Outdated
| | `bytec_2` | constant 2 from bytecblock | | ||
| | `bytec_3` | constant 3 from bytecblock | | ||
| | `pushbytes bytes` | immediate BYTES | | ||
| | `pushbytess bytes ...` | push sequences of immediate bytes to stack | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess this should also say "(first byte array being deepest)"
PS, we try to consistently say "byte array" rather than "bytes".
| ## pushbytes bytes | ||
|
|
||
| - Opcode: 0x80 {varuint length} {bytes} | ||
| - Opcode: 0x80 {varuint count} {bytes} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this one should be changed. It's the length of the byte array, not the count of how many byte arrays there are.
| - Stack: ..., [A1, A2, ..., AN], B → ... | ||
| - given match cases from A[0] to A[N-1], branch to the Ith label where A[I] = B. Continue to the following instruction if no matches are found. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still some disagreement on whether A is 0 based or 1 based. I vote for 1 based, only so the "Stack:" line can avoid using "N-1" as a "subscript".
data/transactions/logic/eval.go
Outdated
| continue | ||
| } | ||
|
|
||
| if matchVal.argType() == StackBytes && string(matchVal.Bytes) == string(stackArg.Bytes) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if matchVal.argType() == StackBytes && string(matchVal.Bytes) == string(stackArg.Bytes) { | |
| if matchVal.argType() == StackBytes && bytes.Equal(matchVal.Bytes, stackArg.Bytes) { |
| - push sequences of immediate byte arrays to stack (first byte array being deepest) | ||
| - Availability: v8 | ||
|
|
||
| pushbytess args are not added to the bytecblock during assembly processes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think we need these caveats? I'd rather remove them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't mind removing it. I thought that it'd be useful to communicate in some way that these opcodes aren't necessarily more space efficient than a series of byte/int instructions. pushbytes/pushint have the same caveat in the docs.
| matchedIdx := n | ||
| for i, stackArg := range matchList { | ||
| if stackArg.argType() != matchVal.argType() { | ||
| continue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we not have any tests that mix bytes and ints?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added more to the eval tests.
Summary
Adding the match opcode, which is an extension of the switch opcode, where the later is a dense switch and the former a sparse switch. The match opcode is also able to match on bytes values, where switch only supports ints. The pushints and pushbytess were added to reduce the size of encoding match cases/constants as stack arguments. The alternative is to push the match cases onto the stack individually, taking up more space to store the additional opcodes. It's possible that this approach is better in some cases if the constants are stored in constant blocks so HLL compilers will need to be aware of this.
Test Plan
Assembly and evaluation tests.