Skip to content
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

AVM: Modify StackType to provide additional information #5130

Merged
merged 63 commits into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
e54d4e7
initial addition of new StackType
barnjamin Feb 10, 2023
2f836a1
add more specific types to opspec proto
barnjamin Feb 10, 2023
d6513c2
more specific stack type for Fields
barnjamin Feb 10, 2023
ee56f94
make opdoc handle the new StackType and provide array of types in Arg…
barnjamin Feb 10, 2023
53659cb
AVM: modify immediate arguments to get their encoding from the type i…
barnjamin Feb 10, 2023
2aa92cb
use StackType to check assignability
barnjamin Feb 10, 2023
b966f21
adding type refinement to bytes and bzero
barnjamin Feb 10, 2023
e725b73
Merge branch 'master' into avm-stack-type-definition
barnjamin Mar 25, 2023
8890a5a
Merge branch 'avm-stack-type-definition' into immediate-argument-details
barnjamin Mar 25, 2023
bdfcbee
Merge branch 'avm-stack-type-definition' into narrow-stack-types
barnjamin Mar 25, 2023
79dcf29
partial cr
barnjamin Apr 3, 2023
5e665af
fix assignable overlap check
barnjamin Apr 3, 2023
0b2e713
modify union fn to skip assignable check and demote type to Any where…
barnjamin Apr 3, 2023
0035d15
merge master
barnjamin Apr 10, 2023
b1dc4d8
Make union a method on StackType receiver, fix bounds on bytemath
barnjamin Apr 10, 2023
a85c88b
single bounds array for StackType, regen docs
barnjamin Apr 10, 2023
b49fb93
union stack type into all scratch slots
barnjamin Apr 10, 2023
919dd26
Merge branch 'master' into avm-stack-type-definition
barnjamin Apr 13, 2023
5545169
merge master back in
barnjamin Apr 13, 2023
aabaa2d
merge master
barnjamin Apr 13, 2023
26779b2
dont modify the scratch space until we're sure there is no error
barnjamin Apr 13, 2023
b2170c0
add fn to refine int pseudo op
barnjamin Apr 13, 2023
f9b38aa
remove err from union method sig, cleanup
barnjamin Apr 13, 2023
ee60eea
Merge branch 'avm-stack-type-definition' into refine-int-pseudo-op
barnjamin Apr 13, 2023
51a1213
amend union to allow either to be None and return the other
barnjamin Apr 13, 2023
8117273
initialize scratch to zeros
barnjamin Apr 13, 2023
df4a7df
Merge branch 'avm-stack-type-definition' into refine-int-pseudo-op
barnjamin Apr 13, 2023
5f9b73a
adding a test to check that scratch slot bounds get set appropriately
barnjamin Apr 13, 2023
f4b2775
specify BoxKey for box ops
barnjamin Apr 13, 2023
1e938b2
fix tests
barnjamin Apr 13, 2023
acb52fd
regen langspec
barnjamin Apr 14, 2023
ec25ba5
tmp
barnjamin Apr 14, 2023
93a356c
add more bounds, regen docs
barnjamin Apr 14, 2023
1f225d5
rename to assignable to overlaps
barnjamin Apr 14, 2023
4ec1e6c
notrack on more tests that fail at assembly time
barnjamin Apr 14, 2023
a1b05ad
adding more refinement to loads if we know its a const
barnjamin Apr 14, 2023
12d7ade
improve naming, fix more tests
barnjamin Apr 14, 2023
b1bab65
add check for nones in docs
barnjamin Apr 14, 2023
734d9f4
adding named types to readme
barnjamin Apr 14, 2023
265d889
Merge branch 'master' of github.com:algorand/go-algorand into avm-sta…
barnjamin Apr 14, 2023
70469fc
merge main
barnjamin Apr 14, 2023
056b943
Merge branch 'master' of github.com:algorand/go-algorand into avm-sta…
barnjamin Apr 14, 2023
3070d16
maybe sorted
barnjamin Apr 15, 2023
6c10ba7
Merge branch 'master' into avm-stack-type-definition
barnjamin May 1, 2023
a7cdad3
merge up
barnjamin May 1, 2023
88a3cb7
Update cmd/opdoc/opdoc.go
barnjamin May 8, 2023
3d2d4f3
Apply suggestions from code review
barnjamin May 8, 2023
7af6ed8
merge master
barnjamin May 8, 2023
abb22e5
Merge branch 'avm-stack-type-definition' of github.com:barnjamin/go-a…
barnjamin May 8, 2023
99ea31b
regen files
barnjamin May 8, 2023
aff63f5
cleanup bounds in docs
barnjamin May 8, 2023
9f1c07a
Apply suggestions from code review
barnjamin May 8, 2023
7d5e89c
fix test expecting short name, tweak var names in opdoc
barnjamin May 8, 2023
7e0a097
Merge branch 'avm-stack-type-definition' of github.com:barnjamin/go-a…
barnjamin May 8, 2023
3ece0ef
merge up
barnjamin May 8, 2023
133c916
use names directly, omit 'FieldGroup'
barnjamin May 8, 2023
80c2fb4
Merge branch 'avm-stack-type-definition' of github.com:barnjamin/go-a…
barnjamin May 8, 2023
662927e
Merge branch 'avm-stack-type-definition' into immediate-argument-details
barnjamin May 8, 2023
aa9a277
fix bools for asset/app/acct params that were lost during merge
barnjamin May 8, 2023
76d3f0e
merging in immediate arg details
barnjamin May 8, 2023
00f611c
fix spacing in immediate notes
barnjamin May 8, 2023
682557e
adding descriptive comment for bounds in StackType
barnjamin May 8, 2023
d9ca502
changed names to be more descriptive
barnjamin May 9, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 144 additions & 42 deletions cmd/opdoc/opdoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"io"
"os"
"sort"
"strings"

"github.com/algorand/go-algorand/config"
Expand All @@ -30,6 +31,38 @@ import (

var docVersion = 9

// OpImmediateNote returns a short string about immediate data which follows the op byte
func opImmediateNoteSyntaxMarkdown(name string, oids []logic.OpImmediateDetails) string {
if len(oids) == 0 {
return ""
}

argNames := make([]string, len(oids))
argDocs := make([]string, len(oids))
for idx, oid := range oids {
argNote := oid.Comment
if oid.Reference != "" {
argNote = fmt.Sprintf("[%s](#field-group-%s)", oid.Reference, strings.ToLower(oid.Reference))
}
argNames[idx] = oid.Name
argDocs[idx] = fmt.Sprintf("%s: %s", oid.Name, argNote)
}

return fmt.Sprintf("`%s %s` ∋ %s", name, strings.Join(argNames, " "), strings.Join(argDocs, ", "))
}

func opImmediateNoteEncoding(opcode byte, oids []logic.OpImmediateDetails) string {
if len(oids) == 0 {
return fmt.Sprintf("0x%02x", opcode)
}

notes := make([]string, len(oids))
for idx, oid := range oids {
notes[idx] = oid.Encoding
}
return fmt.Sprintf("0x%02x {%s}", opcode, strings.Join(notes, "}, {"))
}

func opGroupMarkdownTable(names []string, out io.Writer) {
fmt.Fprint(out, `| Opcode | Description |
| - | -- |
Expand All @@ -50,6 +83,17 @@ func markdownTableEscape(x string) string {
return strings.ReplaceAll(x, "|", "\\|")
}

func namedStackTypesMarkdown(out io.Writer, stackTypes []namedType) {
fmt.Fprintf(out, "#### Definitions\n\n")
fmt.Fprintf(out, "| Name | Bound | AVM Type |\n")
fmt.Fprintf(out, "| ---- | ---- | -------- |\n")

for _, st := range stackTypes {
fmt.Fprintf(out, "| %s | %s | %s |\n", st.Name, st.boundString(), st.AVMType)
}
out.Write([]byte("\n"))
}

func integerConstantsTableMarkdown(out io.Writer) {
fmt.Fprintf(out, "#### OnComplete\n\n")
fmt.Fprintf(out, "%s\n\n", logic.OnCompletionPreamble)
Expand Down Expand Up @@ -169,14 +213,22 @@ func stackMarkdown(op *logic.OpSpec) string {
}

func opToMarkdown(out io.Writer, op *logic.OpSpec, groupDocWritten map[string]bool) (err error) {
ws := ""
opextra := logic.OpImmediateNote(op.Name)
if opextra != "" {
ws = " "

deets := logic.OpImmediateDetailsFromSpec(*op)

// Only need syntax line if there are immediates
// so it carries its own newline
syntax := ""
if opSyntax := opImmediateNoteSyntaxMarkdown(op.Name, deets); opSyntax != "" {
syntax = fmt.Sprintf("- Syntax: %s\n", opSyntax)
}

encoding := fmt.Sprintf("- Bytecode: %s", opImmediateNoteEncoding(op.Opcode, deets))

stackEffects := stackMarkdown(op)
fmt.Fprintf(out, "\n## %s%s\n\n- Opcode: 0x%02x%s%s\n%s",
op.Name, immediateMarkdown(op), op.Opcode, ws, opextra, stackEffects)

fmt.Fprintf(out, "\n## %s\n\n%s%s\n%s", op.Name, syntax, encoding, stackEffects)

fmt.Fprintf(out, "- %s\n", logic.OpDoc(op.Name))
// if cost changed with versions print all of them
costs := logic.OpAllCosts(op.Name)
Expand Down Expand Up @@ -209,7 +261,7 @@ func opToMarkdown(out io.Writer, op *logic.OpSpec, groupDocWritten map[string]bo
for i := range op.OpDetails.Immediates {
group := op.OpDetails.Immediates[i].Group
if group != nil && group.Doc != "" && !groupDocWritten[group.Name] {
fmt.Fprintf(out, "\n`%s` %s:\n\n", group.Name, group.Doc)
fmt.Fprintf(out, "\n### %s\n\n%s\n\n", group.Name, group.Doc)
fieldGroupMarkdown(out, group)
groupDocWritten[group.Name] = true
}
Expand Down Expand Up @@ -238,56 +290,86 @@ func opsToMarkdown(out io.Writer) (err error) {
type OpRecord struct {
Opcode byte
Name string
Args string `json:",omitempty"`
Returns string `json:",omitempty"`
Args []string `json:",omitempty"`
Returns []string `json:",omitempty"`
Size int

ArgEnum []string `json:",omitempty"`
ArgEnumTypes string `json:",omitempty"`
ArgEnumTypes []string `json:",omitempty"`

Doc string
DocExtra string `json:",omitempty"`
ImmediateNote string `json:",omitempty"`
DocExtra string `json:",omitempty"`
ImmediateNote []logic.OpImmediateDetails `json:",omitempty"`
IntroducedVersion uint64
Groups []string
}

type namedType struct {
Name string
Abbreviation string
Bound []uint64
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Bound []uint64
Bound [2]uint64

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 think I left this as a slice so itd jsonify to nothing if its empty rather than [0,0]

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, is this only used locally? (must be, lowercase) So yeah, I guess that's fine.

Wait, won't it only be two 0's for the constant 0? Almost seems better to show it for that rare case, since it's so meaningful.

AVMType string
}

func (nt namedType) boundString() string {
if nt.Bound[0] == 0 && nt.Bound[1] == 0 {
return ""
}

val := "x"
// if its bytes, the length is bounded
if nt.AVMType == "[]byte" {
val = "len(x)"
}

// If they're equal, the val should match exactly
if nt.Bound[0] > 0 && nt.Bound[0] == nt.Bound[1] {
return fmt.Sprintf("%s == %d", val, nt.Bound[0])
}

// otherwise, provide min/max bounds as lte expression
minBound, maxBound := "", ""
if nt.Bound[0] > 0 {
minBound = fmt.Sprintf("%d <= ", nt.Bound[0])
}
if nt.Bound[1] > 0 {
maxBound = fmt.Sprintf(" <= %d", nt.Bound[1])
}

return fmt.Sprintf("%s%s%s", minBound, val, maxBound)

}

// LanguageSpec records the ops of the language at some version
type LanguageSpec struct {
EvalMaxVersion int
LogicSigVersion uint64
NamedTypes []namedType
Ops []OpRecord
}

func typeString(types []logic.StackType) string {
out := make([]byte, len(types))
for i, t := range types {
switch t {
case logic.StackUint64:
out[i] = 'U'
case logic.StackBytes:
out[i] = 'B'
case logic.StackAny:
out[i] = '.'
case logic.StackNone:
out[i] = '_'
default:
panic("unexpected type in opdoc typeString")
func typeStrings(types logic.StackTypes) []string {
out := make([]string, len(types))
allNones := true
for idx, t := range types {
out[idx] = t.String()
if out[idx] != "none" {
allNones = false
}
}

// Cant return None and !None from same op
if strings.Contains(string(out), "_") {
if strings.ContainsAny(string(out), "UB.") {
panic("unexpected StackNone in opdoc typeString")
}
return ""
// If all the types are none, we just return
// an empty array, otherwise leave the nones
// in so we don't break the indices by omitting
// a valid none in a fields array
if allNones {
return nil
}

return string(out)
return out
}

func fieldsAndTypes(group logic.FieldGroup) ([]string, string) {
func fieldsAndTypes(group logic.FieldGroup) ([]string, []string) {
// reminder: group.Names can be "sparse" See: logic.TxnaFields
fields := make([]string, 0, len(group.Names))
types := make([]logic.StackType, 0, len(group.Names))
Expand All @@ -297,10 +379,10 @@ func fieldsAndTypes(group logic.FieldGroup) ([]string, string) {
types = append(types, spec.Type())
}
}
return fields, typeString(types)
return fields, typeStrings(types)
}

func argEnums(name string) ([]string, string) {
func argEnums(name string) ([]string, []string) {
// reminder: this needs to be manually updated every time
// a new opcode is added with an associated FieldGroup
// it'd be nice to have this auto-update
Expand Down Expand Up @@ -334,29 +416,31 @@ func argEnums(name string) ([]string, string) {
case "ecdsa_pk_recover", "ecdsa_verify", "ecdsa_pk_decompress":
return fieldsAndTypes(logic.EcdsaCurves)
default:
return nil, ""
return nil, nil
}
}

func buildLanguageSpec(opGroups map[string][]string) *LanguageSpec {
func buildLanguageSpec(opGroups map[string][]string, namedTypes []namedType) *LanguageSpec {
opSpecs := logic.OpcodesByVersion(uint64(docVersion))
records := make([]OpRecord, len(opSpecs))
for i, spec := range opSpecs {
records[i].Opcode = spec.Opcode
records[i].Name = spec.Name
records[i].Args = typeString(spec.Arg.Types)
records[i].Returns = typeString(spec.Return.Types)
records[i].Args = typeStrings(spec.Arg.Types)
records[i].Returns = typeStrings(spec.Return.Types)
records[i].Size = spec.OpDetails.Size
records[i].ArgEnum, records[i].ArgEnumTypes = argEnums(spec.Name)
records[i].Doc = strings.ReplaceAll(logic.OpDoc(spec.Name), "<br />", "\n")
records[i].DocExtra = logic.OpDocExtra(spec.Name)
records[i].ImmediateNote = logic.OpImmediateNote(spec.Name)
records[i].ImmediateNote = logic.OpImmediateDetailsFromSpec(spec)
records[i].Groups = opGroups[spec.Name]
records[i].IntroducedVersion = spec.Version
}

return &LanguageSpec{
EvalMaxVersion: docVersion,
LogicSigVersion: config.Consensus[protocol.ConsensusCurrentVersion].LogicSigVersion,
NamedTypes: namedTypes,
Ops: records,
}
}
Expand Down Expand Up @@ -389,6 +473,21 @@ func main() {
integerConstantsTableMarkdown(constants)
constants.Close()

named := make([]namedType, 0, len(logic.AllStackTypes))
for abbr, t := range logic.AllStackTypes {
named = append(named, namedType{
Name: t.String(),
Bound: []uint64{t.Bound[0], t.Bound[1]},
Abbreviation: string(abbr),
AVMType: t.AVMType.String(),
})
}
sort.Slice(named, func(i, j int) bool { return strings.Compare(named[i].Name, named[j].Name) > 0 })

namedStackTypes := create("named_stack_types.md")
namedStackTypesMarkdown(namedStackTypes, named)
namedStackTypes.Close()

written := make(map[string]bool)
opSpecs := logic.OpcodesByVersion(uint64(docVersion))
for _, spec := range opSpecs {
Expand All @@ -405,7 +504,10 @@ func main() {
langspecjs := create("langspec.json")
enc := json.NewEncoder(langspecjs)
enc.SetIndent("", " ")
enc.Encode(buildLanguageSpec(opGroups))
err := enc.Encode(buildLanguageSpec(opGroups, named))
if err != nil {
panic(err.Error())
}
langspecjs.Close()

tealtm := create("teal.tmLanguage.json")
Expand Down
Loading