Skip to content

Conversation

@AyAggarwal
Copy link
Contributor

This PR adds two opcodes.
cover: "remove top of stack, and place it down the stack such that N elements are above it",
uncover: "remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack"

@codecov-commenter
Copy link

codecov-commenter commented Jul 14, 2021

Codecov Report

Merging #2541 (90e3521) into master (58fd804) will increase coverage by 0.04%.
The diff coverage is 84.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2541      +/-   ##
==========================================
+ Coverage   46.97%   47.01%   +0.04%     
==========================================
  Files         348      348              
  Lines       55716    55766      +50     
==========================================
+ Hits        26172    26220      +48     
+ Misses      26600    26598       -2     
- Partials     2944     2948       +4     
Impacted Files Coverage Δ
data/transactions/logic/doc.go 82.75% <ø> (ø)
data/transactions/logic/opcodes.go 96.00% <ø> (ø)
data/transactions/logic/assembler.go 81.97% <75.00%> (-0.17%) ⬇️
data/transactions/logic/eval.go 90.70% <100.00%> (+0.09%) ⬆️
ledger/blockqueue.go 82.18% <0.00%> (-2.88%) ⬇️
catchup/service.go 70.31% <0.00%> (ø)
network/wsPeer.go 75.20% <0.00%> (+3.06%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 58fd804...90e3521. Read the comment docs.

@AyAggarwal
Copy link
Contributor Author

Hello All, the PR build is failing due to "scripts/travis/codegen_verification.sh". Will look into this tomorrow morning (PST)

Copy link
Contributor

@jannotti jannotti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the shifts can be done clearer and faster with copy()

func opCover(cx *evalContext) {
depth := int(uint(cx.program[cx.pc+1]))
idx := len(cx.stack) - 1 - depth
// Need to check stack size explicitly here because checkArgs() doesn't understand dig
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cover, not dig

return
}
topIndex := len(cx.stack) - 1
Idxsv := cx.stack[idx]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use lower case for locals

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@AyAggarwal
Copy link
Contributor Author

updated to use copy

@AyAggarwal AyAggarwal linked an issue Jul 14, 2021 that may be closed by this pull request
2 tasks
@jannotti
Copy link
Contributor

jannotti commented Jul 14, 2021

I'm realizing there is a tricky issue with these opcodes - normally our assembler and the Check() call try to keep track of the types on the stack. If you push an int, then a bytes, then use +, you will get an error at assembly time. (In the presence of branches, we downgrade this error to a warning).

This can only happen because each opcode (so far) only manipulates the top of the stack. The OpSpec describes that manipulation in terms of types. But these opcodes are different. They manipulate the stack deeply, and potentially change the types of many elements (the element doesn't change, but the type at a particular location does, as they all shift).

We need to figure out how that can be made to work. It might just be that they need their own check function (like the branches have) or it might be that they can explicitly manipulate the type stack during assembly. They might need both, or even more.

First step, let's get a unit test in that shows the problem. Something like:

int 4; byte "john"; int 5; cover 2; pop; +

I think this will not type check, but it is correct (if I haven't messed up).

Copy link
Contributor

@jdtzmn jdtzmn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly good, nice work on this! I left a few comments.

testPanics(t, "int 3; int 2; int 1; dig 11; int 2; ==; return", 3)
}

func TestCover(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to see test cases here that check that other elements are shifted correctly

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will add these

Copy link
Contributor

@algochoi algochoi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work! Just very minor comments.

Based on the Travis error, maybe try running make sanity on the root folder or go fmt again?

cx.err = fmt.Errorf("cover %d with stack size = %d", depth, len(cx.stack))
return
}
topidx := len(cx.stack) - 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: topIdx

cx.err = fmt.Errorf("uncover %d with stack size = %d", depth, len(cx.stack))
return
}
topidx := len(cx.stack) - 1
Copy link
Contributor

@algochoi algochoi Jul 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: topIdx

I think Go convention is to camelCase local variables

return
}
topidx := len(cx.stack) - 1
idxsv := cx.stack[idx]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think this should be sv (based on the dig opcode)

return
}
topidx := len(cx.stack) - 1
topsv := cx.stack[topidx]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe sv (based on the dig opcode)?

@jannotti
Copy link
Contributor

It looks to me like all we need to do to address the type checking is to manipulate the type stack in the assemble function for cover/uncover. Currently they use asmDefault. So they'll need custom assemblers, but it should pretty much just call the default assembler, and manipulate the stack.

@jannotti
Copy link
Contributor

jannotti commented Jul 19, 2021 via email

@AyAggarwal AyAggarwal requested a review from jdtzmn July 19, 2021 20:06
}
returns[len(returns)-1] = sv
}
fmt.Println(returns)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: fmt.Println

returns[i] = StackAny
}
idx := len(ops.typeStack) - depth
if idx >= 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the idx here ever be less than 0? Won't that be caught in the opCover function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this check occurs first, as jj said below opCover should now be sure that it has the correct size stack

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually because the arrays will create out of bounds error if you don't check here. It doesn't panic though

idx := len(ops.typeStack) - depth
if idx >= 0 {
sv := ops.typeStack[len(ops.typeStack)-1]
for i := idx; i < len(ops.typeStack)-1; i++ {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be shifted more cleanly using copy()?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could, but I'm to blame for setting the precedent with dig. We need cleaner helper functions for building these arrays in the type* functions. Then we can also do select, dup, dup.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Does it make sense to include those changes in this PR or should it be done in a separate one?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should do those separately. I'm still mulling over the right interface, but I think I'm imagining two things, one I'm fairly sure about, and one less so.

Fairly sure: the typeCheck functions should check the appropriate depth first, and return early if the stack is short (with an appropriately sized slice to cause checkStack to complain about length). This means the rest of the typeCheck function can assume the stack is deep enough, and it'll be easier to write the code (using copy!) that returns slices that match parts of the existing stack.

Less sure: the interface should be reworked a little so that immediates are parsed early in one place, and supplied in a structured way to the typeCheck and assemble functions. That way it only happens once, and assemble functions are no longer expected to do that parsing or error checking.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the first one for sure, the second point is good as well - would that still mean if a program failed in typeCheck it fails during assembly?

cx.err = fmt.Errorf("cover %d with stack size = %d", depth, len(cx.stack))
return
}
topIdx := len(cx.stack) - 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may read better if you move topIdx above idx like so:

topIdx := len(cx.stack - 1)
idx := topIdx - depth

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point

jannotti
jannotti previously approved these changes Jul 21, 2021
Comment on lines +1705 to +1706
// Need to check stack size explicitly here because checkArgs() doesn't understand cover
// so we can't expect our stack to be prechecked.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may not be true any more with the changes to checkStack. I think that now opCover is sure to have a big enough stack to work with.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, i'll re visit this

testAccepts(t, "int 4; int 3; int 2; int 1; uncover 3; pop; int 1; ==; return", 5)
testAccepts(t, "int 4; int 3; int 2; int 1; uncover 3; pop; pop; int 2; ==; return", 5)
testAccepts(t, "int 1; int 3; int 2; int 1; uncover 3; pop; pop; int 2; ==; return", 5)
testPanics(t, obfuscate("int 4; int 3; int 2; int 1; uncover 11; int 3; ==; return"), 5)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add one that panics, but just barely? In other words, confirm theres no off-by-one error by uncovering with an argument that's just one too high.

jdtzmn
jdtzmn previously approved these changes Jul 23, 2021
@AyAggarwal AyAggarwal requested a review from jannotti July 26, 2021 20:09
Copy link
Contributor

@algorandskiy algorandskiy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, one more test request and a minor code style fix

"Byteslice Logic": {"b|", "b&", "b^", "b~"},
"Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "txn", "gtxn", "txna", "gtxna", "gtxns", "gtxnsa", "global", "load", "store", "gload", "gloads", "gaid", "gaids"},
"Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "dup", "dup2", "dig", "swap", "select", "assert", "callsub", "retsub"},
"Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "dup", "dup2", "dig", "cover", "uncover", "swap", "select", "assert", "callsub", "retsub"},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just wondering how did "pop", "dup", "dup2", "dig", "cover", "uncover" end up to be flow control

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's because it used to only be pop and dup so we didn't create a category for stack manipulation.


func TestUncover(t *testing.T) {
t.Parallel()
testAccepts(t, "int 4; int 3; int 2; int 1; uncover 2; int 3; ==; return", 5)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add unvcover 0 test as an edge case test

Comment on lines 1718 to 1726
depth := int(cx.program[cx.pc+1])
idx := len(cx.stack) - 1 - depth
// Need to check stack size explicitly here because checkArgs() doesn't understand uncover
// so we can't expect our stack to be prechecked.
if idx < 0 {
cx.err = fmt.Errorf("uncover %d with stack size = %d", depth, len(cx.stack))
return
}
topIdx := len(cx.stack) - 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make it the same as prev function

Suggested change
depth := int(cx.program[cx.pc+1])
idx := len(cx.stack) - 1 - depth
// Need to check stack size explicitly here because checkArgs() doesn't understand uncover
// so we can't expect our stack to be prechecked.
if idx < 0 {
cx.err = fmt.Errorf("uncover %d with stack size = %d", depth, len(cx.stack))
return
}
topIdx := len(cx.stack) - 1
depth := int(cx.program[cx.pc+1])
topIdx := len(cx.stack) - 1
idx := topIdx - depth
// Need to check stack size explicitly here because checkArgs() doesn't understand uncover
// so we can't expect our stack to be prechecked.
if idx < 0 {
cx.err = fmt.Errorf("uncover %d with stack size = %d", depth, len(cx.stack))
return
}

Copy link
Contributor

@jannotti jannotti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ABI - AVM Support - selector and dig/bury

8 participants