Skip to content

Commit c8df55f

Browse files
authored
Teal4 math (#2139)
Add a lot of math oriented opcodes that have been requested by teal users. sqrt, exp, expw, divmodw byteslice oriented math - a set of opcodes that will treat byteslices as arbitrary precision uints.
1 parent 15bfd9a commit c8df55f

File tree

13 files changed

+1209
-710
lines changed

13 files changed

+1209
-710
lines changed

cmd/opdoc/opdoc.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ import (
2828
"github.com/algorand/go-algorand/protocol"
2929
)
3030

31-
func opGroupMarkdownTable(og *logic.OpGroup, out io.Writer) {
31+
func opGroupMarkdownTable(names []string, out io.Writer) {
3232
fmt.Fprint(out, `| Op | Description |
3333
| --- | --- |
3434
`)
3535
opSpecs := logic.OpsByName[logic.LogicVersion]
3636
// TODO: sort by logic.OpSpecs[].Opcode
37-
for _, opname := range og.Ops {
37+
for _, opname := range names {
3838
spec := opSpecs[opname]
3939
fmt.Fprintf(out, "| `%s%s` | %s |\n",
4040
markdownTableEscape(spec.Name), immediateMarkdown(&spec),
@@ -314,14 +314,14 @@ func main() {
314314
opsToMarkdown(opcodesMd)
315315
opcodesMd.Close()
316316
opGroups := make(map[string][]string, len(logic.OpSpecs))
317-
for _, og := range logic.OpGroupList {
318-
fname := fmt.Sprintf("%s.md", og.GroupName)
317+
for grp, names := range logic.OpGroups {
318+
fname := fmt.Sprintf("%s.md", grp)
319319
fname = strings.ReplaceAll(fname, " ", "_")
320320
fout, _ := os.Create(fname)
321-
opGroupMarkdownTable(&og, fout)
321+
opGroupMarkdownTable(names, fout)
322322
fout.Close()
323-
for _, opname := range og.Ops {
324-
opGroups[opname] = append(opGroups[opname], og.GroupName)
323+
for _, opname := range names {
324+
opGroups[opname] = append(opGroups[opname], grp)
325325
}
326326
}
327327
constants, _ := os.Create("named_integer_constants.md")

cmd/opdoc/tmLanguage.go

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -152,26 +152,30 @@ func buildSyntaxHighlight() *tmLanguage {
152152
},
153153
},
154154
}
155-
for _, opgroup := range logic.OpGroupList {
156-
switch opgroup.GroupName {
155+
var allArithmetics []string
156+
for grp, names := range logic.OpGroups {
157+
switch grp {
157158
case "Flow Control":
158159
keywords.Patterns = append(keywords.Patterns, pattern{
159160
Name: "keyword.control.teal",
160-
Match: fmt.Sprintf("^(%s)\\b", strings.Join(opgroup.Ops, "|")),
161+
Match: fmt.Sprintf("^(%s)\\b", strings.Join(names, "|")),
161162
})
162163
case "Loading Values":
163164
loading := []string{"int", "byte", "addr"}
164-
loading = append(loading, opgroup.Ops...)
165+
loading = append(loading, names...)
165166
keywords.Patterns = append(keywords.Patterns, pattern{
166167
Name: "keyword.other.teal",
167168
Match: fmt.Sprintf("^(%s)\\b", strings.Join(loading, "|")),
168169
})
169170
case "State Access":
170171
keywords.Patterns = append(keywords.Patterns, pattern{
171172
Name: "keyword.other.unit.teal",
172-
Match: fmt.Sprintf("^(%s)\\b", strings.Join(opgroup.Ops, "|")),
173+
Match: fmt.Sprintf("^(%s)\\b", strings.Join(names, "|")),
173174
})
174-
case "Arithmetic":
175+
// For these three, accumulate into allArithmetics,
176+
// and only add to keyword.Patterns later, when all
177+
// have been collected.
178+
case "Arithmetic", "Byteslice Arithmetic", "Byteslice Logic":
175179
escape := map[rune]bool{
176180
'*': true,
177181
'+': true,
@@ -186,30 +190,24 @@ func buildSyntaxHighlight() *tmLanguage {
186190
'<': true,
187191
'>': true,
188192
}
189-
var allArithmetics []string
190-
for _, op := range opgroup.Ops {
191-
if len(op) < 3 {
192-
// all symbol-based opcodes are under 3 chars, and no trigraphs so far
193-
escaped := make([]byte, 0, len(op)*2)
194-
for _, ch := range op {
195-
if _, ok := escape[ch]; ok {
196-
escaped = append(escaped, '\\')
197-
}
198-
escaped = append(escaped, byte(ch))
193+
for _, op := range names {
194+
escaped := make([]byte, 0, len(op)*2)
195+
for _, ch := range op {
196+
if _, ok := escape[ch]; ok {
197+
escaped = append(escaped, '\\')
199198
}
200-
allArithmetics = append(allArithmetics, string(escaped))
201-
} else {
202-
allArithmetics = append(allArithmetics, op)
199+
escaped = append(escaped, byte(ch))
203200
}
201+
allArithmetics = append(allArithmetics, string(escaped))
204202
}
205-
keywords.Patterns = append(keywords.Patterns, pattern{
206-
Name: "keyword.operator.teal",
207-
Match: fmt.Sprintf("^(%s)\\b", strings.Join(allArithmetics, "|")),
208-
})
209203
default:
210-
panic(fmt.Sprintf("Unknown ops group: %s", opgroup.GroupName))
204+
panic(fmt.Sprintf("Unknown ops group: %s", grp))
211205
}
212206
}
207+
keywords.Patterns = append(keywords.Patterns, pattern{
208+
Name: "keyword.operator.teal",
209+
Match: fmt.Sprintf("^(%s)\\b", strings.Join(allArithmetics, "|")),
210+
})
213211
tm.Repository["keywords"] = keywords
214212

215213
return &tm

data/transactions/logic/Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
all: TEAL_opcodes.md README.md fields_string.go
1+
all: TEAL_opcodes.md teal.tmLanguage.json README.md fields_string.go
22

33
# Location of algorandfoundation/specs repo. (Optional)
44
SPECS := ../../../../specs
55
# Location of algorand/docs repo. (Optional)
66
DOCS := ../../../../docs
77

8-
TEAL_opcodes.md: fields_string.go ../../../cmd/opdoc/opdoc.go eval.go assembler.go doc.go opcodes.go
8+
TEAL_opcodes.md teal.tmLanguage.json: fields_string.go ../../../cmd/opdoc/opdoc.go ../../../cmd/opdoc/tmLanguage.go eval.go assembler.go doc.go opcodes.go
99
go run ../../../cmd/opdoc/opdoc.go ../../../cmd/opdoc/tmLanguage.go
1010
@if [ -e $(SPECS)/dev/TEAL_opcodes.md ]; then \
1111
sed '/^$$/q' $(SPECS)/dev/TEAL_opcodes.md | cat - TEAL_opcodes.md > opcodes.spec; \

data/transactions/logic/README.md

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ A program can either authorize some delegated action on a normal private key sig
2727
* If the account has signed the program (an ed25519 signature on "Program" concatenated with the program bytes) then if the program returns true the transaction is authorized as if the account had signed it. This allows an account to hand out a signed program so that other users can carry out delegated actions which are approved by the program.
2828
* If the SHA512_256 hash of the program (prefixed by "Program") is equal to the transaction Sender address then this is a contract account wholly controlled by the program. No other signature is necessary or possible. The only way to execute a transaction against the contract account is for the program to approve it.
2929

30-
The TEAL bytecode plus the length of any Args must add up to less than 1000 bytes (consensus parameter LogicSigMaxSize). Each TEAL op has an associated cost and the program cost must total less than 20000 (consensus parameter LogicSigMaxCost). Most ops have a cost of 1, but a few slow crypto ops are much higher. Prior to v4, program costs was estimated as the static sum of all opcode costs in a program (ignoring conditionals that might skip some code). Beginning with v4, a program's cost is tracked dynamically, while being evaluated. If the program exceeds its budget, it fails.
30+
The TEAL bytecode plus the length of any Args must add up to less than 1000 bytes (consensus parameter LogicSigMaxSize). Each TEAL op has an associated cost and the program cost must total less than 20000 (consensus parameter LogicSigMaxCost). Most ops have a cost of 1, but a few slow crypto ops are much higher. Prior to v4, program costs was estimated as the static sum of all opcode costs in a program (ignoring conditionals that might skip some code). Beginning with v4, a program's cost is tracked dynamically, while being evaluated. If the program exceeds its budget, it fails.
3131

3232
## Execution modes
3333

@@ -111,6 +111,11 @@ For two-argument ops, `A` is the previous element on the stack and `B` is the la
111111
| `>=` | A greater than or equal to B => {0 or 1} |
112112
| `&&` | A is not zero and B is not zero => {0 or 1} |
113113
| `\|\|` | A is not zero or B is not zero => {0 or 1} |
114+
| `shl` | A times 2^B, modulo 2^64 |
115+
| `shr` | A divided by 2^B |
116+
| `sqrt` | The largest integer X such that X^2 <= A |
117+
| `bitlen` | The index of the highest bit in A. If A is a byte-array, it is interpreted as a big-endian unsigned integer |
118+
| `exp` | A raised to the Bth power. Panic if A == B == 0 and on overflow |
114119
| `==` | A is equal to B => {0 or 1} |
115120
| `!=` | A is not equal to B => {0 or 1} |
116121
| `!` | X == 0 yields 1; else 0 |
@@ -124,7 +129,8 @@ For two-argument ops, `A` is the previous element on the stack and `B` is the la
124129
| `~` | bitwise invert value X |
125130
| `mulw` | A times B out to 128-bit long result as low (top) and high uint64 values on the stack |
126131
| `addw` | A plus B out to 128-bit long result as sum (top) and carry-bit uint64 values on the stack |
127-
| `divw` | Pop four uint64 values. The deepest two are interpreted as a uint128 dividend (deepest value is high word), the top two are interpreted as a uint128 divisor. Four uint64 values are pushed to the stack. The deepest two are the quotient (deeper value is the high uint64). The top two are the remainder, low bits on top. |
132+
| `divmodw` | Pop four uint64 values. The deepest two are interpreted as a uint128 dividend (deepest value is high word), the top two are interpreted as a uint128 divisor. Four uint64 values are pushed to the stack. The deepest two are the quotient (deeper value is the high uint64). The top two are the remainder, low bits on top. |
133+
| `expw` | A raised to the Bth power as a 128-bit long result as low (top) and high uint64 values on the stack. Panic if A == B == 0 or if the results exceeds 2^128-1 |
128134
| `getbit` | pop a target A (integer or byte-array), and index B. Push the Bth bit of A. |
129135
| `setbit` | pop a target A, index B, and bit C. Set the Bth bit of A to C, and push the result |
130136
| `getbyte` | pop a byte-array A and integer B. Extract the Bth byte of A and push it as an integer |
@@ -133,6 +139,42 @@ For two-argument ops, `A` is the previous element on the stack and `B` is the la
133139
| `substring s e` | pop a byte-array A. For immediate values in 0..255 S and E: extract a range of bytes from A starting at S up to but not including E, push the substring result. If E < S, or either is larger than the array length, the program fails |
134140
| `substring3` | pop a byte-array A and two integers B and C. Extract a range of bytes from A starting at B up to but not including C, push the substring result. If C < B, or either is larger than the array length, the program fails |
135141

142+
These opcodes take and return byte-array values that are interpreted
143+
as big-endian unsigned integers. Returned values are the shortest
144+
byte-array that can represent the returned value. For example, the
145+
zero value is the empty byte-array.
146+
147+
Input lengths are limited to maximum length 64, which represents a 512
148+
bit unsigned integer.
149+
150+
| Op | Description |
151+
| --- | --- |
152+
| `b+` | A plus B, where A and B are byte-arrays interpreted as big-endian unsigned integers |
153+
| `b-` | A minus B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Panic on underflow. |
154+
| `b/` | A divided by B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Panic if B is zero. |
155+
| `b*` | A times B, where A and B are byte-arrays interpreted as big-endian unsigned integers. |
156+
| `b<` | A is less than B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1} |
157+
| `b>` | A is greater than B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1} |
158+
| `b<=` | A is less than or equal to B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1} |
159+
| `b>=` | A is greater than or equal to B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1} |
160+
| `b==` | A is equals to B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1} |
161+
| `b!=` | A is not equal to B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1} |
162+
| `b%` | A modulo B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Panic if B is zero. |
163+
164+
These opcodes operate on the bits of byte-array values. The shorter
165+
array is interpeted as though left padded with zeros until it is the
166+
same length as the other input. The returned values are the same
167+
length as the longest input. Therefore, unlike array arithmetic,
168+
these results may contain leading zero bytes.
169+
170+
| Op | Description |
171+
| --- | --- |
172+
| `b\|` | A bitwise-or B, where A and B are byte-arrays, zero-left extended to the greater of their lengths |
173+
| `b&` | A bitwise-and B, where A and B are byte-arrays, zero-left extended to the greater of their lengths |
174+
| `b^` | A bitwise-xor B, where A and B are byte-arrays, zero-left extended to the greater of their lengths |
175+
| `b~` | A with all bits inverted |
176+
177+
136178
### Loading Values
137179

138180
Opcodes for getting data onto the stack.
@@ -155,6 +197,7 @@ Some of these have immediate data in the byte or bytes after the opcode.
155197
| `bytec_2` | push constant 2 from bytecblock to stack |
156198
| `bytec_3` | push constant 3 from bytecblock to stack |
157199
| `pushbytes bytes` | push the following program bytes to the stack |
200+
| `bzero` | push a byte-array of length A, containing all zero bytes |
158201
| `arg n` | push Nth LogicSig argument to stack |
159202
| `arg_0` | push LogicSig argument 0 to stack |
160203
| `arg_1` | push LogicSig argument 1 to stack |

data/transactions/logic/README_in.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,25 @@ For two-argument ops, `A` is the previous element on the stack and `B` is the la
7474

7575
@@ Arithmetic.md @@
7676

77+
These opcodes take and return byte-array values that are interpreted
78+
as big-endian unsigned integers. Returned values are the shortest
79+
byte-array that can represent the returned value. For example, the
80+
zero value is the empty byte-array.
81+
82+
Input lengths are limited to maximum length 64, which represents a 512
83+
bit unsigned integer.
84+
85+
@@ Byteslice_Arithmetic.md @@
86+
87+
These opcodes operate on the bits of byte-array values. The shorter
88+
array is interpeted as though left padded with zeros until it is the
89+
same length as the other input. The returned values are the same
90+
length as the longest input. Therefore, unlike array arithmetic,
91+
these results may contain leading zero bytes.
92+
93+
@@ Byteslice_Logic.md @@
94+
95+
7796
### Loading Values
7897

7998
Opcodes for getting data onto the stack.

0 commit comments

Comments
 (0)