Skip to content

Commit

Permalink
AVM: match, pushints, and pushbytess opcodes (#4645)
Browse files Browse the repository at this point in the history
add match opcode along with assembler and eval unit tests
implementation of match, pushints, and pushbytess
add docs for pushints and pushbytess
  • Loading branch information
algoidurovic authored Oct 26, 2022
1 parent 4e36847 commit ab87a8a
Show file tree
Hide file tree
Showing 11 changed files with 578 additions and 106 deletions.
3 changes: 3 additions & 0 deletions data/transactions/logic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -383,13 +383,15 @@ Some of these have immediate data in the byte or bytes after the opcode.
| `intc_2` | constant 2 from intcblock |
| `intc_3` | constant 3 from intcblock |
| `pushint uint` | immediate UINT |
| `pushints uint ...` | push sequence of immediate uints to stack in the order they appear (first uint being deepest) |
| `bytecblock bytes ...` | prepare block of byte-array constants for use by bytec |
| `bytec i` | Ith constant from bytecblock |
| `bytec_0` | constant 0 from bytecblock |
| `bytec_1` | constant 1 from bytecblock |
| `bytec_2` | constant 2 from bytecblock |
| `bytec_3` | constant 3 from bytecblock |
| `pushbytes bytes` | immediate BYTES |
| `pushbytess bytes ...` | push sequences of immediate byte arrays to stack (first byte array being deepest) |
| `bzero` | zero filled byte-array of length A |
| `arg n` | Nth LogicSig argument |
| `arg_0` | LogicSig argument 0 |
Expand Down Expand Up @@ -601,6 +603,7 @@ Account fields used in the `acct_params_get` opcode.
| `proto a r` | Prepare top call frame for a retsub that will assume A args and R return values. |
| `retsub` | pop the top instruction from the call stack and branch to it |
| `switch target ...` | branch to the Ath label. Continue at following instruction if index A exceeds the number of labels. |
| `match target ...` | given match cases from A[1] to A[N], branch to the Ith label where A[I] = B. Continue to the following instruction if no matches are found. |

### State Access

Expand Down
31 changes: 29 additions & 2 deletions data/transactions/logic/TEAL_opcodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u

## intcblock uint ...

- Opcode: 0x20 {varuint length} [{varuint value}, ...]
- Opcode: 0x20 {varuint count} [{varuint value}, ...]
- Stack: ... → ...
- prepare block of uint64 constants for use by intc

Expand Down Expand Up @@ -280,7 +280,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u

## bytecblock bytes ...

- Opcode: 0x26 {varuint length} [({varuint value length} bytes), ...]
- Opcode: 0x26 {varuint count} [({varuint value length} bytes), ...]
- Stack: ... → ...
- prepare block of byte-array constants for use by bytec

Expand Down Expand Up @@ -1048,6 +1048,24 @@ pushbytes args are not added to the bytecblock during assembly processes

pushint args are not added to the intcblock during assembly processes

## pushbytess bytes ...

- Opcode: 0x82 {varuint count} [({varuint value length} bytes), ...]
- Stack: ... → ..., [N items]
- 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

## pushints uint ...

- Opcode: 0x83 {varuint count} [{varuint value}, ...]
- Stack: ... → ..., [N items]
- push sequence of immediate uints to stack in the order they appear (first uint being deepest)
- Availability: v8

pushints args are not added to the intcblock during assembly processes

## ed25519verify_bare

- Opcode: 0x84
Expand Down Expand Up @@ -1104,6 +1122,15 @@ Fails unless the last instruction executed was a `callsub`.
- branch to the Ath label. Continue at following instruction if index A exceeds the number of labels.
- Availability: v8

## match target ...

- Opcode: 0x8e {uint8 branch count} [{int16 branch offset, big-endian}, ...]
- Stack: ..., [A1, A2, ..., AN], B → ...
- given match cases from A[1] to A[N], branch to the Ith label where A[I] = B. 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 B. The following N values represent an ordered list of match cases/constants (A), where the first value (A[0]) is the deepest in the stack. The immediate arguments are an ordered list of N labels (T). `match` will branch to target T[I], where A[I] = B. If there are no matches then execution continues on to the next instruction.

## shl

- Opcode: 0x90
Expand Down
155 changes: 78 additions & 77 deletions data/transactions/logic/assembler.go
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,13 @@ func asmPushInt(ops *OpStream, spec *OpSpec, args []string) error {
ops.pending.Write(scratch[:vlen])
return nil
}

func asmPushInts(ops *OpStream, spec *OpSpec, args []string) error {
ops.pending.WriteByte(spec.Opcode)
_, err := asmIntImmArgs(ops, args)
return err
}

func asmPushBytes(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) == 0 {
return ops.errorf("%s operation needs byte literal argument", spec.Name)
Expand All @@ -602,6 +609,12 @@ func asmPushBytes(ops *OpStream, spec *OpSpec, args []string) error {
return nil
}

func asmPushBytess(ops *OpStream, spec *OpSpec, args []string) error {
ops.pending.WriteByte(spec.Opcode)
_, err := asmByteImmArgs(ops, args)
return err
}

func base32DecodeAnyPadding(x string) (val []byte, err error) {
val, err = base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(x)
if err != nil {
Expand Down Expand Up @@ -812,8 +825,7 @@ func asmMethod(ops *OpStream, spec *OpSpec, args []string) error {
return ops.error("Unable to parse method signature")
}

func asmIntCBlock(ops *OpStream, spec *OpSpec, args []string) error {
ops.pending.WriteByte(spec.Opcode)
func asmIntImmArgs(ops *OpStream, args []string) ([]uint64, error) {
ivals := make([]uint64, len(args))
var scratch [binary.MaxVarintLen64]byte
l := binary.PutUvarint(scratch[:], uint64(len(args)))
Expand All @@ -825,9 +837,17 @@ func asmIntCBlock(ops *OpStream, spec *OpSpec, args []string) error {
}
l = binary.PutUvarint(scratch[:], cu)
ops.pending.Write(scratch[:l])
if !ops.known.deadcode {
ivals[i] = cu
}
ivals[i] = cu
}

return ivals, nil
}

func asmIntCBlock(ops *OpStream, spec *OpSpec, args []string) error {
ops.pending.WriteByte(spec.Opcode)
ivals, err := asmIntImmArgs(ops, args)
if err != nil {
return err
}
if !ops.known.deadcode {
// If we previously processed an `int`, we thought we could insert our
Expand All @@ -843,8 +863,7 @@ func asmIntCBlock(ops *OpStream, spec *OpSpec, args []string) error {
return nil
}

func asmByteCBlock(ops *OpStream, spec *OpSpec, args []string) error {
ops.pending.WriteByte(spec.Opcode)
func asmByteImmArgs(ops *OpStream, args []string) ([][]byte, error) {
bvals := make([][]byte, 0, len(args))
rest := args
for len(rest) > 0 {
Expand All @@ -854,7 +873,7 @@ func asmByteCBlock(ops *OpStream, spec *OpSpec, args []string) error {
// intcblock, but parseBinaryArgs would have
// to return a useful consumed value even in
// the face of errors. Hard.
return ops.error(err)
return nil, ops.error(err)
}
bvals = append(bvals, val)
rest = rest[consumed:]
Expand All @@ -867,6 +886,17 @@ func asmByteCBlock(ops *OpStream, spec *OpSpec, args []string) error {
ops.pending.Write(scratch[:l])
ops.pending.Write(bv)
}

return bvals, nil
}

func asmByteCBlock(ops *OpStream, spec *OpSpec, args []string) error {
ops.pending.WriteByte(spec.Opcode)
bvals, err := asmByteImmArgs(ops, args)
if err != nil {
return err
}

if !ops.known.deadcode {
// If we previously processed a pseudo `byte`, we thought we could
// insert our own bytecblock, but now we see a manual one.
Expand Down Expand Up @@ -1454,6 +1484,24 @@ func typeDupN(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, err
return nil, copies, nil
}

func typePushBytess(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) {
types := make(StackTypes, len(args))
for i := range types {
types[i] = StackBytes
}

return nil, types, nil
}

func typePushInts(pgm *ProgramKnowledge, args []string) (StackTypes, StackTypes, error) {
types := make(StackTypes, len(args))
for i := range types {
types[i] = StackUint64
}

return nil, types, nil
}

func joinIntsOnOr(singularTerminator string, list ...int) string {
if len(list) == 1 {
switch list[0] {
Expand Down Expand Up @@ -2519,7 +2567,7 @@ func disassemble(dis *disassembleState, spec *OpSpec) (string, error) {
out += fmt.Sprintf("0x%s // %s", hex.EncodeToString(constant), guessByteFormat(constant))
pc = int(end)
case immInts:
intc, nextpc, err := parseIntcblock(dis.program, pc)
intc, nextpc, err := parseIntImmArgs(dis.program, pc)
if err != nil {
return "", err
}
Expand All @@ -2533,7 +2581,7 @@ func disassemble(dis *disassembleState, spec *OpSpec) (string, error) {
}
pc = nextpc
case immBytess:
bytec, nextpc, err := parseBytecBlock(dis.program, pc)
bytec, nextpc, err := parseByteImmArgs(dis.program, pc)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -2590,13 +2638,13 @@ func disassemble(dis *disassembleState, spec *OpSpec) (string, error) {
return out, nil
}

var errShortIntcblock = errors.New("intcblock ran past end of program")
var errTooManyIntc = errors.New("intcblock with too many items")
var errShortIntImmArgs = errors.New("const int list ran past end of program")
var errTooManyIntc = errors.New("const int list with too many items")

func parseIntcblock(program []byte, pos int) (intc []uint64, nextpc int, err error) {
func parseIntImmArgs(program []byte, pos int) (intc []uint64, nextpc int, err error) {
numInts, bytesUsed := binary.Uvarint(program[pos:])
if bytesUsed <= 0 {
err = fmt.Errorf("could not decode intcblock size at pc=%d", pos)
err = fmt.Errorf("could not decode length of int list at pc=%d", pos)
return
}
pos += bytesUsed
Expand All @@ -2607,7 +2655,7 @@ func parseIntcblock(program []byte, pos int) (intc []uint64, nextpc int, err err
intc = make([]uint64, numInts)
for i := uint64(0); i < numInts; i++ {
if pos >= len(program) {
err = errShortIntcblock
err = errShortIntImmArgs
return
}
intc[i], bytesUsed = binary.Uvarint(program[pos:])
Expand All @@ -2621,38 +2669,19 @@ func parseIntcblock(program []byte, pos int) (intc []uint64, nextpc int, err err
return
}

func checkIntConstBlock(cx *EvalContext) error {
pos := cx.pc + 1
numInts, bytesUsed := binary.Uvarint(cx.program[pos:])
if bytesUsed <= 0 {
return fmt.Errorf("could not decode intcblock size at pc=%d", pos)
}
pos += bytesUsed
if numInts > uint64(len(cx.program)) {
return errTooManyIntc
}
//intc = make([]uint64, numInts)
for i := uint64(0); i < numInts; i++ {
if pos >= len(cx.program) {
return errShortIntcblock
}
_, bytesUsed = binary.Uvarint(cx.program[pos:])
if bytesUsed <= 0 {
return fmt.Errorf("could not decode int const[%d] at pc=%d", i, pos)
}
pos += bytesUsed
}
cx.nextpc = pos
return nil
func checkIntImmArgs(cx *EvalContext) error {
var err error
_, cx.nextpc, err = parseIntImmArgs(cx.program, cx.pc+1)
return err
}

var errShortBytecblock = errors.New("bytecblock ran past end of program")
var errTooManyItems = errors.New("bytecblock with too many items")
var errShortByteImmArgs = errors.New("const bytes list ran past end of program")
var errTooManyItems = errors.New("const bytes list with too many items")

func parseBytecBlock(program []byte, pos int) (bytec [][]byte, nextpc int, err error) {
func parseByteImmArgs(program []byte, pos int) (bytec [][]byte, nextpc int, err error) {
numItems, bytesUsed := binary.Uvarint(program[pos:])
if bytesUsed <= 0 {
err = fmt.Errorf("could not decode bytecblock size at pc=%d", pos)
err = fmt.Errorf("could not decode length of bytes list at pc=%d", pos)
return
}
pos += bytesUsed
Expand All @@ -2663,7 +2692,7 @@ func parseBytecBlock(program []byte, pos int) (bytec [][]byte, nextpc int, err e
bytec = make([][]byte, numItems)
for i := uint64(0); i < numItems; i++ {
if pos >= len(program) {
err = errShortBytecblock
err = errShortByteImmArgs
return
}
itemLen, bytesUsed := binary.Uvarint(program[pos:])
Expand All @@ -2673,12 +2702,12 @@ func parseBytecBlock(program []byte, pos int) (bytec [][]byte, nextpc int, err e
}
pos += bytesUsed
if pos >= len(program) {
err = errShortBytecblock
err = errShortByteImmArgs
return
}
end := uint64(pos) + itemLen
if end > uint64(len(program)) || end < uint64(pos) {
err = errShortBytecblock
err = errShortByteImmArgs
return
}
bytec[i] = program[pos : pos+int(itemLen)]
Expand All @@ -2688,38 +2717,10 @@ func parseBytecBlock(program []byte, pos int) (bytec [][]byte, nextpc int, err e
return
}

func checkByteConstBlock(cx *EvalContext) error {
pos := cx.pc + 1
numItems, bytesUsed := binary.Uvarint(cx.program[pos:])
if bytesUsed <= 0 {
return fmt.Errorf("could not decode bytecblock size at pc=%d", pos)
}
pos += bytesUsed
if numItems > uint64(len(cx.program)) {
return errTooManyItems
}
//bytec = make([][]byte, numItems)
for i := uint64(0); i < numItems; i++ {
if pos >= len(cx.program) {
return errShortBytecblock
}
itemLen, bytesUsed := binary.Uvarint(cx.program[pos:])
if bytesUsed <= 0 {
return fmt.Errorf("could not decode []byte const[%d] at pc=%d", i, pos)
}
pos += bytesUsed
if pos >= len(cx.program) {
return errShortBytecblock
}
end := uint64(pos) + itemLen
if end > uint64(len(cx.program)) || end < uint64(pos) {
return errShortBytecblock
}
//bytec[i] = program[pos : pos+int(itemLen)]
pos += int(itemLen)
}
cx.nextpc = pos
return nil
func checkByteImmArgs(cx *EvalContext) error {
var err error
_, cx.nextpc, err = parseByteImmArgs(cx.program, cx.pc+1)
return err
}

func parseSwitch(program []byte, pos int) (targets []int, nextpc int, err error) {
Expand Down
Loading

0 comments on commit ab87a8a

Please sign in to comment.