Skip to content

Commit

Permalink
AVM: Assembly simplification & flexible costs (#3857)
Browse files Browse the repository at this point in the history
Unify most assembly routines to minimize difference and ease doc gen
Flexible opcode costs and fewer assembly routines

Co-authored-by: michaeldiamant <[email protected]>
  • Loading branch information
jannotti and michaeldiamant authored Apr 6, 2022
1 parent 70a5532 commit 95c5b0e
Show file tree
Hide file tree
Showing 20 changed files with 763 additions and 1,040 deletions.
120 changes: 36 additions & 84 deletions cmd/opdoc/opdoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ func opGroupMarkdownTable(names []string, out io.Writer) {
| - | -- |
`)
opSpecs := logic.OpsByName[logic.LogicVersion]
// TODO: sort by logic.OpSpecs[].Opcode
for _, opname := range names {
spec, ok := opSpecs[opname]
if !ok {
Expand All @@ -49,15 +48,6 @@ func markdownTableEscape(x string) string {
return strings.ReplaceAll(x, "|", "\\|")
}

func typeEnumTableMarkdown(out io.Writer) {
fmt.Fprintf(out, "| Index | \"Type\" string | Description |\n")
fmt.Fprintf(out, "| --- | --- | --- |\n")
for i, name := range logic.TxnTypeNames {
fmt.Fprintf(out, "| %d | %s | %s |\n", i, markdownTableEscape(name), logic.TypeNameDescriptions[name])
}
out.Write([]byte("\n"))
}

func integerConstantsTableMarkdown(out io.Writer) {
fmt.Fprintf(out, "#### OnComplete\n\n")
fmt.Fprintf(out, "%s\n\n", logic.OnCompletionPreamble)
Expand All @@ -77,20 +67,22 @@ func integerConstantsTableMarkdown(out io.Writer) {
out.Write([]byte("\n"))
}

func fieldGroupMarkdown(out io.Writer, group logic.FieldGroup) {
fieldSpecsMarkdown(out, group.Names, group.Specs)
}

func fieldSpecsMarkdown(out io.Writer, names []string, specs logic.FieldSpecMap) {
func fieldGroupMarkdown(out io.Writer, group *logic.FieldGroup) {
showTypes := false
showVers := false
spec0 := specs.SpecByName(names[0])
opVer := spec0.OpVersion()
for _, name := range names {
if specs.SpecByName(name).Type() != logic.StackNone {
opVer := uint64(0)
for _, name := range group.Names {
spec, ok := group.SpecByName(name)
// reminder: group.Names can be "sparse" See: logic.TxnaFields
if !ok {
continue
}
if spec.Type().Typed() {
showTypes = true
}
if specs.SpecByName(name).Version() != opVer {
if opVer == uint64(0) {
opVer = spec.Version()
} else if opVer != spec.Version() {
showVers = true
}
}
Expand All @@ -107,8 +99,11 @@ func fieldSpecsMarkdown(out io.Writer, names []string, specs logic.FieldSpecMap)
headers += " Notes |\n"
widths += " --------- |\n"
fmt.Fprint(out, headers, widths)
for i, name := range names {
spec := specs.SpecByName(name)
for i, name := range group.Names {
spec, ok := group.SpecByName(name)
if !ok {
continue
}
str := fmt.Sprintf("| %d | %s", i, markdownTableEscape(name))
if showTypes {
str = fmt.Sprintf("%s | %s", str, markdownTableEscape(spec.Type().String()))
Expand All @@ -125,41 +120,6 @@ func fieldSpecsMarkdown(out io.Writer, names []string, specs logic.FieldSpecMap)
fmt.Fprint(out, "\n")
}

func transactionFieldsMarkdown(out io.Writer) {
fmt.Fprintf(out, "\n`txn` Fields (see [transaction reference](https://developer.algorand.org/docs/reference/transactions/)):\n\n")
fieldGroupMarkdown(out, logic.TxnFields)
}

func globalFieldsMarkdown(out io.Writer) {
fmt.Fprintf(out, "\n`global` Fields:\n\n")
fieldGroupMarkdown(out, logic.GlobalFields)
}

func assetHoldingFieldsMarkdown(out io.Writer) {
fmt.Fprintf(out, "\n`asset_holding_get` Fields:\n\n")
fieldGroupMarkdown(out, logic.AssetHoldingFields)
}

func assetParamsFieldsMarkdown(out io.Writer) {
fmt.Fprintf(out, "\n`asset_params_get` Fields:\n\n")
fieldGroupMarkdown(out, logic.AssetParamsFields)
}

func appParamsFieldsMarkdown(out io.Writer) {
fmt.Fprintf(out, "\n`app_params_get` Fields:\n\n")
fieldGroupMarkdown(out, logic.AppParamsFields)
}

func acctParamsFieldsMarkdown(out io.Writer) {
fmt.Fprintf(out, "\n`acct_params_get` Fields:\n\n")
fieldGroupMarkdown(out, logic.AcctParamsFields)
}

func ecDsaCurvesMarkdown(out io.Writer) {
fmt.Fprintf(out, "\n`ECDSA` Curves:\n\n")
fieldGroupMarkdown(out, logic.EcdsaCurves)
}

func immediateMarkdown(op *logic.OpSpec) string {
markdown := ""
for _, imm := range op.Details.Immediates {
Expand Down Expand Up @@ -198,7 +158,7 @@ func stackMarkdown(op *logic.OpSpec) string {
return out + "\n"
}

func opToMarkdown(out io.Writer, op *logic.OpSpec) (err error) {
func opToMarkdown(out io.Writer, op *logic.OpSpec, groupDocWritten map[string]bool) (err error) {
ws := ""
opextra := logic.OpImmediateNote(op.Name)
if opextra != "" {
Expand Down Expand Up @@ -233,25 +193,16 @@ func opToMarkdown(out io.Writer, op *logic.OpSpec) (err error) {
fmt.Fprintf(out, "- Availability: v%d\n", op.Version)
}
if !op.Modes.Any() {
fmt.Fprintf(out, "- Mode: %s\n", op.Modes.String())
fmt.Fprintf(out, "- Mode: %s\n", op.Modes)
}
switch op.Name {
case "global":
globalFieldsMarkdown(out)
case "txn":
transactionFieldsMarkdown(out)
fmt.Fprintf(out, "\nTypeEnum mapping:\n\n")
typeEnumTableMarkdown(out)
case "asset_holding_get":
assetHoldingFieldsMarkdown(out)
case "asset_params_get":
assetParamsFieldsMarkdown(out)
case "app_params_get":
appParamsFieldsMarkdown(out)
case "acct_params_get":
acctParamsFieldsMarkdown(out)
case "ecdsa_verify":
ecDsaCurvesMarkdown(out)

for i := range op.Details.Immediates {
group := op.Details.Immediates[i].Group
if group != nil && group.Doc != "" && !groupDocWritten[group.Name] {
fmt.Fprintf(out, "\n`%s` %s:\n\n", group.Name, group.Doc)
fieldGroupMarkdown(out, group)
groupDocWritten[group.Name] = true
}
}
ode := logic.OpDocExtra(op.Name)
if ode != "" {
Expand All @@ -263,8 +214,9 @@ func opToMarkdown(out io.Writer, op *logic.OpSpec) (err error) {
func opsToMarkdown(out io.Writer) (err error) {
out.Write([]byte("# Opcodes\n\nOps have a 'cost' of 1 unless otherwise specified.\n\n"))
opSpecs := logic.OpcodesByVersion(logic.LogicVersion)
written := make(map[string]bool)
for _, spec := range opSpecs {
err = opToMarkdown(out, &spec)
err = opToMarkdown(out, &spec, written)
if err != nil {
return
}
Expand Down Expand Up @@ -323,22 +275,22 @@ func fieldsAndTypes(group logic.FieldGroup) ([]string, string) {
fields := make([]string, 0, len(group.Names))
types := make([]logic.StackType, 0, len(group.Names))
for _, name := range group.Names {
if name != "" {
if spec, ok := group.SpecByName(name); ok {
fields = append(fields, name)
types = append(types, group.Specs.SpecByName(name).Type())
types = append(types, spec.Type())
}
}
return fields, typeString(types)
}

func argEnums(name string) (names []string, types string) {
func argEnums(name string) ([]string, string) {
switch name {
case "txn", "gtxn", "gtxns", "itxn", "gitxn", "itxn_field":
return fieldsAndTypes(logic.TxnFields)
case "global":
return
return fieldsAndTypes(logic.GlobalFields)
case "txna", "gtxna", "gtxnsa", "txnas", "gtxnas", "gtxnsas", "itxna", "gitxna":
return fieldsAndTypes(logic.TxnaFields)
return fieldsAndTypes(logic.TxnArrayFields)
case "asset_holding_get":
return fieldsAndTypes(logic.AssetHoldingFields)
case "asset_params_get":
Expand Down Expand Up @@ -405,8 +357,8 @@ func main() {
for _, spec := range opSpecs {
for _, imm := range spec.Details.Immediates {
if imm.Group != nil && !written[imm.Group.Name] {
out := create(imm.Group.Name + "_fields.md")
fieldSpecsMarkdown(out, imm.Group.Names, imm.Group.Specs)
out := create(strings.ToLower(imm.Group.Name) + "_fields.md")
fieldGroupMarkdown(out, imm.Group)
out.Close()
written[imm.Group.Name] = true
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/opdoc/tmLanguage.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func buildSyntaxHighlight() *tmLanguage {
for _, spec := range opSpecs {
for _, imm := range spec.Details.Immediates {
if imm.Group != nil && !accumulated[imm.Group.Name] {
allNamedFields = append(allNamedFields, imm.Group.Names[:]...)
allNamedFields = append(allNamedFields, imm.Group.Names...)
accumulated[imm.Group.Name] = true
}
}
Expand Down
4 changes: 2 additions & 2 deletions data/transactions/logic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,8 @@ return stack matches the name of the input value.
| `!=` | A is not equal to B => {0 or 1} |
| `!` | A == 0 yields 1; else 0 |
| `len` | yields length of byte value A |
| `itob` | converts uint64 A to big endian bytes |
| `btoi` | converts bytes A as big endian to uint64 |
| `itob` | converts uint64 A to big-endian byte array, always of length 8 |
| `btoi` | converts big-endian byte array A to uint64. Fails if len(A) > 8. Padded by leading 0s if len(A) < 8. |
| `%` | A modulo B. Fail if B == 0. |
| `\|` | A bitwise-or B |
| `&` | A bitwise-and B |
Expand Down
52 changes: 28 additions & 24 deletions data/transactions/logic/TEAL_opcodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,13 @@ Overflow is an error condition which halts execution and fails the transaction.

- Opcode: 0x16
- Stack: ..., A: uint64 &rarr; ..., []byte
- converts uint64 A to big endian bytes
- converts uint64 A to big-endian byte array, always of length 8

## btoi

- Opcode: 0x17
- Stack: ..., A: []byte &rarr; ..., uint64
- converts bytes A as big endian to uint64
- converts big-endian byte array A to uint64. Fails if len(A) > 8. Padded by leading 0s if len(A) < 8.

`btoi` fails if the input is longer than 8 bytes.

Expand Down Expand Up @@ -427,19 +427,6 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u
| 63 | StateProofPK | []byte | v6 | 64 byte state proof public key commitment |


TypeEnum mapping:

| Index | "Type" string | Description |
| --- | --- | --- |
| 0 | unknown | Unknown type. Invalid transaction |
| 1 | pay | Payment |
| 2 | keyreg | KeyRegistration |
| 3 | acfg | AssetConfig |
| 4 | axfer | AssetTransfer |
| 5 | afrz | AssetFreeze |
| 6 | appl | ApplicationCall |


FirstValidTime causes the program to fail. The field is reserved for future use.

## global f
Expand Down Expand Up @@ -575,7 +562,7 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g

## bnz target

- Opcode: 0x40 {int16 branch offset, big endian}
- Opcode: 0x40 {int16 branch offset, big-endian}
- Stack: ..., A: uint64 &rarr; ...
- branch to TARGET if value A is not zero

Expand All @@ -585,7 +572,7 @@ At v2 it became allowed to branch to the end of the program exactly after the la

## bz target

- Opcode: 0x41 {int16 branch offset, big endian}
- Opcode: 0x41 {int16 branch offset, big-endian}
- Stack: ..., A: uint64 &rarr; ...
- branch to TARGET if value A is zero
- Availability: v2
Expand All @@ -594,7 +581,7 @@ See `bnz` for details on how branches work. `bz` inverts the behavior of `bnz`.

## b target

- Opcode: 0x42 {int16 branch offset, big endian}
- Opcode: 0x42 {int16 branch offset, big-endian}
- Stack: ... &rarr; ...
- branch unconditionally to TARGET
- Availability: v2
Expand Down Expand Up @@ -764,9 +751,17 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on
- Opcode: 0x5c {uint8 encoding index}
- Stack: ..., A: []byte &rarr; ..., []byte
- decode A which was base64-encoded using _encoding_ E. Fail if A is not base64 encoded with encoding E
- **Cost**: 25
- **Cost**: 1 + 1 per 16 bytes
- Availability: v7

`base64` Encodings:

| Index | Name | Notes |
| - | ------ | --------- |
| 0 | URLEncoding | |
| 1 | StdEncoding | |


Decodes A using the base64 encoding E. Specify the encoding with an immediate arg either as URL and Filename Safe (`URLEncoding`) or Standard (`StdEncoding`). See <a href="https://rfc-editor.org/rfc/rfc4648.html#section-4">RFC 4648</a> (sections 4 and 5). It is assumed that the encoding ends with the exact number of `=` padding characters as required by the RFC. When padding occurs, any unused pad bits in the encoding must be set to zero or the decoding will fail. The special cases of `\n` and `\r` are allowed but completely ignored. An error will result when attempting to decode a string with a character that is not in the encoding alphabet or not one of `=`, `\r`, or `\n`.

## json_ref r
Expand All @@ -776,6 +771,15 @@ Decodes A using the base64 encoding E. Specify the encoding with an immediate ar
- return key B's value from a [valid](jsonspec.md) utf-8 encoded json object A
- Availability: v7

`json_ref` Types:

| Index | Name | Type | Notes |
| - | ------ | -- | --------- |
| 0 | JSONString | []byte | |
| 1 | JSONUint64 | uint64 | |
| 2 | JSONObject | []byte | |


specify the return type with an immediate arg either as JSONUint64 or JSONString or JSONObject.

## balance
Expand Down Expand Up @@ -888,7 +892,7 @@ Deleting a key which is already absent has no effect on the application global s
- Availability: v2
- Mode: Application

`asset_holding_get` Fields:
`asset_holding` Fields:

| Index | Name | Type | Notes |
| - | ------ | -- | --------- |
Expand All @@ -906,7 +910,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ address), asset id (or
- Availability: v2
- Mode: Application

`asset_params_get` Fields:
`asset_params` Fields:

| Index | Name | Type | In | Notes |
| - | ------ | -- | - | --------- |
Expand Down Expand Up @@ -934,7 +938,7 @@ params: Txn.ForeignAssets offset (or, since v4, an _available_ asset id. Return:
- Availability: v5
- Mode: Application

`app_params_get` Fields:
`app_params` Fields:

| Index | Name | Type | Notes |
| - | ------ | -- | --------- |
Expand All @@ -959,7 +963,7 @@ params: Txn.ForeignApps offset or an _available_ app id. Return: did_exist flag
- Availability: v6
- Mode: Application

`acct_params_get` Fields:
`acct_params` Fields:

| Index | Name | Type | Notes |
| - | ------ | -- | --------- |
Expand Down Expand Up @@ -1006,7 +1010,7 @@ pushint args are not added to the intcblock during assembly processes

## callsub target

- Opcode: 0x88 {int16 branch offset, big endian}
- Opcode: 0x88 {int16 branch offset, big-endian}
- Stack: ... &rarr; ...
- branch unconditionally to TARGET, saving the next instruction on the call stack
- Availability: v4
Expand Down
Loading

0 comments on commit 95c5b0e

Please sign in to comment.