From a938f6961c53342ce9565a9fe7669cd3f4c01467 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 30 May 2023 16:49:53 +0200 Subject: [PATCH 01/59] initial poc --- gnovm/pkg/gnolang/go2gno.go | 15 +- gnovm/pkg/gnolang/nodes.go | 9 +- gnovm/stdlibs/context.go | 3 + gnovm/stdlibs/crypto/sha256/sha256.gno | 8 +- gnovm/stdlibs/crypto/sha256/sha256.go | 7 + .../stdlibs/internal/crypto/sha256/sha256.gno | 3 - gnovm/stdlibs/internal/stdgen/stdgen.go | 390 ++++++++++++++++++ gnovm/stdlibs/internal/stdgen/template.tmpl | 71 ++++ gnovm/stdlibs/math/abs.gno | 6 +- gnovm/stdlibs/math/bits.gno | 8 +- gnovm/stdlibs/math/copysign.gno | 6 +- gnovm/stdlibs/math/floor.gno | 12 +- gnovm/stdlibs/math/ldexp.gno | 8 +- gnovm/stdlibs/math/modf.gno | 8 +- gnovm/stdlibs/math/native.gno | 21 + gnovm/stdlibs/math/native.go | 8 + gnovm/stdlibs/native.go | 156 +++++++ gnovm/stdlibs/stdlibs.go | 98 +---- gnovm/stdlibs/time/time.go | 21 + gnovm/tests/file.go | 2 +- gnovm/tests/files/float5_native.gno | 4 +- gnovm/tests/files/float5_stdlibs.gno | 6 +- 22 files changed, 731 insertions(+), 139 deletions(-) create mode 100644 gnovm/stdlibs/crypto/sha256/sha256.go delete mode 100644 gnovm/stdlibs/internal/crypto/sha256/sha256.gno create mode 100644 gnovm/stdlibs/internal/stdgen/stdgen.go create mode 100644 gnovm/stdlibs/internal/stdgen/template.tmpl create mode 100644 gnovm/stdlibs/math/native.gno create mode 100644 gnovm/stdlibs/math/native.go create mode 100644 gnovm/stdlibs/native.go create mode 100644 gnovm/stdlibs/time/time.go diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index c3ab4e95b73..b387f548f48 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -112,9 +112,9 @@ func ParseFile(filename string, body string) (fn *FileNode, err error) { defer func() { if r := recover(); r != nil { if rerr, ok := r.(error); ok { - err = rerr + err = errors.Wrap(rerr, "parsing file") } else { - err = errors.New(fmt.Sprintf("%v", r)) + err = errors.New(fmt.Sprintf("%v", r)).Stacktrace() } return } @@ -431,7 +431,16 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { } name := toName(gon.Name) type_ := Go2Gno(fs, gon.Type).(*FuncTypeExpr) - body := Go2Gno(fs, gon.Body).(*BlockStmt).Body + var body []Stmt + if gon.Body != nil { + body = Go2Gno(fs, gon.Body).(*BlockStmt).Body + } else { + /*bd, err := ParseExpr(`panic("call to bodyless/external function is invalid outside of standard library")`) + if err != nil { + panic(err) + } + body = []Stmt{&ExprStmt{X: bd}}*/ + } return &FuncDecl{ IsMethod: isMethod, Recv: recv, diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index e838ba562c0..610e46de191 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1369,6 +1369,14 @@ func (x *PackageNode) DefineNative(n Name, ps, rs FieldTypeExprs, native func(*M if native == nil { panic("DefineNative expects a function, but got nil") } + + if idx, exists := x.GetLocalIndex(n); exists { + // XXX(morgan): trick to look as a new definition to Define2; + // if we try to redefine an old value it looks like precompile + // is not cooperating well with us... + x.Names[idx] = "#uncallable_" + x.Names[idx] + } + fd := FuncD(n, ps, rs, nil) fd = Preprocess(nil, x, fd).(*FuncDecl) ft := evalStaticType(nil, x, &fd.Type).(*FuncType) @@ -1648,7 +1656,6 @@ func (sb *StaticBlock) GetStaticTypeOfAt(store Store, path ValuePath) Type { path.Depth -= 1 } } - panic("should not happen") } // Implements BlockNode. diff --git a/gnovm/stdlibs/context.go b/gnovm/stdlibs/context.go index 5f140c344d4..7cc077c6213 100644 --- a/gnovm/stdlibs/context.go +++ b/gnovm/stdlibs/context.go @@ -18,3 +18,6 @@ type ExecContext struct { OrigSendSpent *std.Coins // mutable Banker Banker } + +func (e ExecContext) GetTimestamp() int64 { return e.Timestamp } +func (e ExecContext) GetTimestampNano() int64 { return e.TimestampNano } diff --git a/gnovm/stdlibs/crypto/sha256/sha256.gno b/gnovm/stdlibs/crypto/sha256/sha256.gno index efb8ebb8e37..6292b57b3ff 100644 --- a/gnovm/stdlibs/crypto/sha256/sha256.gno +++ b/gnovm/stdlibs/crypto/sha256/sha256.gno @@ -1,12 +1,6 @@ package sha256 -import ( - isha256 "internal/crypto/sha256" -) - const Size = 32 // Sum returns the SHA-256 checksum of the data. -func Sum256(data []byte) [Size]byte { - return isha256.Sum256(data) -} +func Sum256(data []byte) [Size]byte diff --git a/gnovm/stdlibs/crypto/sha256/sha256.go b/gnovm/stdlibs/crypto/sha256/sha256.go new file mode 100644 index 00000000000..42c1493864d --- /dev/null +++ b/gnovm/stdlibs/crypto/sha256/sha256.go @@ -0,0 +1,7 @@ +package sha256 + +import "crypto/sha256" + +func Sum256(data []byte) [32]byte { + return sha256.Sum256(data) +} diff --git a/gnovm/stdlibs/internal/crypto/sha256/sha256.gno b/gnovm/stdlibs/internal/crypto/sha256/sha256.gno deleted file mode 100644 index 458b2123f59..00000000000 --- a/gnovm/stdlibs/internal/crypto/sha256/sha256.gno +++ /dev/null @@ -1,3 +0,0 @@ -package sha256 - -// XXX injected via stdlibs/stdlibs.go diff --git a/gnovm/stdlibs/internal/stdgen/stdgen.go b/gnovm/stdlibs/internal/stdgen/stdgen.go new file mode 100644 index 00000000000..f7984615795 --- /dev/null +++ b/gnovm/stdlibs/internal/stdgen/stdgen.go @@ -0,0 +1,390 @@ +// Command stdgen provides static code generation for standard library native +// bindings. +package main + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/fs" + "os" + "os/exec" + "path/filepath" + "reflect" + "strconv" + "strings" + "text/template" +) + +func main() { + if err := _main("."); err != nil { + fmt.Fprintf(os.Stderr, "%+v\n", err) + os.Exit(1) + } +} + +// for now a simple call to fmt.Fprintf, but could be improved +func logWarning(format string, v ...any) { + // TODO: add these at the top of the generated file as a comment + // so that if there are exceptions to make these are made + // consciously in code review. + fmt.Fprintf(os.Stderr, "warn: "+format+"\n", v...) +} + +func _main(stdlibsPath string) error { + stdlibsPath = filepath.Clean(stdlibsPath) + if s, err := os.Stat(stdlibsPath); err != nil { + return err + } else if !s.IsDir() { + return fmt.Errorf("not a directory: %q", stdlibsPath) + } + + pkgs, err := walkStdlibs(stdlibsPath) + if err != nil { + return err + } + + // Create mappings. + mappings := linkFunctions(pkgs) + + // Create file. + f, err := os.Create("native.go") + if err != nil { + return fmt.Errorf("create native.go: %w", err) + } + defer f.Close() + + // Execute template + td := &tplData{ + Mappings: mappings, + } + td.generateLibnums() + if err := tpl.Execute(f, td); err != nil { + return fmt.Errorf("execute template: %w", err) + } + if err := f.Close(); err != nil { + return err + } + + cmd := exec.Command( + "go", "run", "-modfile", "../../misc/devdeps/go.mod", + "mvdan.cc/gofumpt", "-w", "native.go", + ) + _, err = cmd.Output() + if err != nil { + return fmt.Errorf("error executing gofumpt: %w", err) + } + + return nil +} + +type pkgData struct { + importPath string + fsDir string + gnoBodyless []*ast.FuncDecl + goExported []*ast.FuncDecl +} + +func walkStdlibs(stdlibsPath string) ([]*pkgData, error) { + pkgs := make([]*pkgData, 0, 64) + err := filepath.WalkDir(stdlibsPath, func(fpath string, d fs.DirEntry, err error) error { + // skip dirs and top-level directory. + if d.IsDir() || filepath.Dir(fpath) == stdlibsPath { + return nil + } + + // skip non-source files. + ext := filepath.Ext(fpath) + if ext != ".go" && ext != ".gno" { + return nil + } + + dir := filepath.Dir(fpath) + var pkg *pkgData + // because of bfs, we know that if we've already been in this directory + // in a previous file, it must be in the last entry of pkgs. + if len(pkgs) == 0 || pkgs[len(pkgs)-1].fsDir != dir { + pkg = &pkgData{ + importPath: strings.ReplaceAll(strings.TrimPrefix(dir, stdlibsPath+"/"), string(filepath.Separator), "/"), + fsDir: dir, + } + pkgs = append(pkgs, pkg) + } else { + pkg = pkgs[len(pkgs)-1] + } + fs := token.NewFileSet() + f, err := parser.ParseFile(fs, fpath, nil, parser.SkipObjectResolution) + if err != nil { + return err + } + if ext == ".go" { + // keep track of exported function declarations. + // warn about all exported type, const and var declarations. + exp := resolveGnoMachine(f, filterExported(f)) + if len(exp) > 0 { + pkg.goExported = append(pkg.goExported, exp...) + } + } else if bd := filterBodylessFuncDecls(f); len(bd) > 0 { + // gno file -- keep track of function declarations without body. + pkg.gnoBodyless = append(pkg.gnoBodyless, bd...) + } + return nil + }) + return pkgs, err +} + +func filterBodylessFuncDecls(f *ast.File) (bodyless []*ast.FuncDecl) { + for _, decl := range f.Decls { + fd, ok := decl.(*ast.FuncDecl) + if !ok || fd.Body != nil { + continue + } + bodyless = append(bodyless, fd) + } + return +} + +func filterExported(f *ast.File) (exported []*ast.FuncDecl) { + for _, decl := range f.Decls { + switch d := decl.(type) { + case *ast.GenDecl: + // TODO: complain if there are exported types/vars/consts + continue + case *ast.FuncDecl: + if d.Name.IsExported() { + exported = append(exported, d) + } + } + } + return +} + +func resolveGnoMachine(f *ast.File, fns []*ast.FuncDecl) []*ast.FuncDecl { + iname := gnolangImportName(f) + if iname == "" { + return fns + } + for _, fn := range fns { + if len(fn.Type.Params.List) == 0 { + continue + } + first := fn.Type.Params.List[0] + if len(first.Names) > 1 { + continue + } + ind, ok := first.Type.(*ast.StarExpr) + if !ok { + continue + } + res, ok := ind.X.(*ast.SelectorExpr) + if !ok { + continue + } + if id, ok := res.X.(*ast.Ident); ok && id.Name == iname && res.Sel.Name == "Machine" { + id.Name = "#gnomachine" + } + } + return fns +} + +func gnolangImportName(f *ast.File) string { + for _, i := range f.Imports { + ipath, err := strconv.Unquote(i.Path.Value) + if err != nil { + continue + } + if ipath == "github.com/gnolang/gno/gnovm/pkg/gnolang" { + if i.Name == nil { + return "gnolang" + } + return i.Name.Name + } + } + return "" +} + +type mapping struct { + GnoImportPath string + GnoMethod string + GnoParamTypes []string + GnoResultTypes []string + GoImportPath string + GoFunc string + MachineParam bool +} + +func linkFunctions(pkgs []*pkgData) []mapping { + var mappings []mapping + for _, pkg := range pkgs { + for _, gb := range pkg.gnoBodyless { + nameWant := gb.Name.Name + if !gb.Name.IsExported() { + nameWant = "X_" + nameWant + } + fn := findFuncByName(pkg.goExported, nameWant) + if fn == nil { + logWarning("package %q: no matching go function declaration (%q) exists for function %q", + pkg.importPath, nameWant, gb.Name.Name) + continue + } + mp := mapping{ + GnoImportPath: pkg.importPath, + GnoMethod: gb.Name.Name, + GoImportPath: "github.com/gnolang/gno/gnovm/stdlibs/" + pkg.importPath, + GoFunc: fn.Name.Name, + } + if !mp.loadSignaturesMatch(gb, fn) { + logWarning("package %q: signature of gno function %s doesn't match signature of go function %s", + pkg.importPath, gb.Name.Name, fn.Name.Name) + continue + } + mappings = append(mappings, mp) + } + } + return mappings +} + +func findFuncByName(fns []*ast.FuncDecl, name string) *ast.FuncDecl { + for _, fn := range fns { + if fn.Name.Name == name { + return fn + } + } + return nil +} + +func (m *mapping) loadSignaturesMatch(gnof, gof *ast.FuncDecl) bool { + if gnof.Type.TypeParams != nil || gof.Type.TypeParams != nil { + panic("type parameters not supported") + } + // Ideally, signatures match when they accept the same types, + // or aliases. + gnop, gnor := fieldListToTypes(gnof.Type.Params), fieldListToTypes(gnof.Type.Results) + // store gno params and results in mapping + m.GnoParamTypes = gnop + m.GnoResultTypes = gnor + gop, gor := fieldListToTypes(gof.Type.Params), fieldListToTypes(gof.Type.Results) + if len(gop) > 0 && gop[0] == "*gno.Machine" { + m.MachineParam = true + gop = gop[1:] + } + return reflect.DeepEqual(gnop, gop) && reflect.DeepEqual(gnor, gor) +} + +var builtinTypes = [...]string{ + "bool", + "string", + "int", + "int8", + "int16", + "rune", + "int32", + "int64", + "uint", + "byte", + "uint8", + "uint16", + "uint32", + "uint64", + "bigint", + "float32", + "float64", + "error", + "any", +} + +func validIdent(name string) bool { + for _, t := range builtinTypes { + if name == t { + return true + } + } + return false +} + +func exprToString(e ast.Expr) string { + switch e := e.(type) { + case *ast.Ident: + if !validIdent(e.Name) { + panic(fmt.Sprintf("ident is not builtin: %q", e.Name)) + } + return e.Name + case *ast.Ellipsis: + return "..." + exprToString(e.Elt) + case *ast.SelectorExpr: + if x, ok := e.X.(*ast.Ident); ok && x.Name == "#gnomachine" { + return "gno.Machine" + } + panic("SelectorExpr not supported") + case *ast.StarExpr: + return "*" + exprToString(e.X) + case *ast.ArrayType: + var ls string + if e.Len != nil { + switch e.Len.(type) { + case *ast.Ellipsis: + ls = "..." + } + } + return "[" + ls + "]" + exprToString(e.Elt) + case *ast.StructType: + if len(e.Fields.List) > 0 { + panic("structs with values not supported yet") + } + return "struct{}" + case *ast.FuncType: + return "func(" + strings.Join(fieldListToTypes(e.Params), ", ") + ")" + strings.Join(fieldListToTypes(e.Results), ", ") + case *ast.InterfaceType: + if len(e.Methods.List) > 0 { + panic("interfaces with methods not supported yet") + } + return "interface{}" + case *ast.MapType: + return "map[" + exprToString(e.Key) + "]" + exprToString(e.Value) + default: + panic(fmt.Sprintf("invalid expression as func param/return type: %T", e)) + } +} + +func fieldListToTypes(fl *ast.FieldList) []string { + r := make([]string, 0, len(fl.List)) + for _, f := range fl.List { + ts := exprToString(f.Type) + times := len(f.Names) + if times == 0 { + // case of unnamed params; such as return values (often) + times = 1 + } + for i := 0; i < times; i++ { + r = append(r, ts) + } + } + return r +} + +type tplData struct { + Mappings []mapping + LibNums []string +} + +var tpl = template.Must(template.ParseFiles("./internal/stdgen/template.tmpl")) + +func (t tplData) FindLibNum(s string) (int, error) { + for i, v := range t.LibNums { + if v == s { + return i, nil + } + } + return -1, fmt.Errorf("could not find lib: %q", s) +} + +func (t *tplData) generateLibnums() { + var last string + for _, m := range t.Mappings { + if m.GoImportPath != last { + t.LibNums = append(t.LibNums, m.GoImportPath) + last = m.GoImportPath + } + } +} diff --git a/gnovm/stdlibs/internal/stdgen/template.tmpl b/gnovm/stdlibs/internal/stdgen/template.tmpl new file mode 100644 index 00000000000..1261abd55c7 --- /dev/null +++ b/gnovm/stdlibs/internal/stdgen/template.tmpl @@ -0,0 +1,71 @@ +// This file is autogenerated; do not edit. +// To regenerate it, run `go generate` or `go run ./internal/stdgen` +// from @/stdlibs. + +package stdlibs + +import ( + "reflect" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" +{{- range $i, $m := .LibNums }} + lib{{ $i }} {{ printf "%q" $m }} +{{- end }} +) + +var nativeFuncs = [...]nativeFunc{ +{{- range $i, $m := .Mappings }} + { + {{ printf "%q" $m.GnoImportPath }}, + {{ printf "%q" $m.GnoMethod }}, + {{/*- TODO set nil if empty -*/}} + []gno.FieldTypeExpr{ + {{- range $i, $p := $m.GnoParamTypes }} + {Name: gno.N("p{{ $i }}"), Type: gno.X({{ printf "%q" $p }})}, + {{- end }} + }, + []gno.FieldTypeExpr{ + {{- range $i, $r := $m.GnoResultTypes }} + {Name: gno.N("r{{ $i }}"), Type: gno.X({{ printf "%q" $r }})}, + {{- end }} + }, + func(m *gno.Machine) { + {{ if $m.GnoParamTypes -}} + b := m.LastBlock() + var ( + {{- range $pn, $pv := $m.GnoParamTypes }} + p{{ $pn }} {{ $pv }} + rp{{ $pn }} = reflect.ValueOf(&p{{ $pn }}).Elem() + {{- end }} + ) + + {{ range $pn, $pv := $m.GnoParamTypes -}} + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, {{ $pn }}, "")).TV, rp{{ $pn }}) + {{ end }} + {{- end }} + + {{ range $rn, $rv := $m.GnoResultTypes -}} + {{- if gt $rn 0 -}}, {{ end -}} + r{{ $rn }} + {{- end -}} + {{- if gt (len $m.GnoResultTypes) 0 }} := {{ end -}} + lib{{ $.FindLibNum $m.GoImportPath }}.{{ $m.GoFunc }}( + {{- if $m.MachineParam }} + m, + {{ end -}} + {{- range $pn, $pv := $m.GnoParamTypes -}} + p{{ $pn }}, + {{- end -}} + ) + + {{ range $rn, $rv := $m.GnoResultTypes -}} + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(r{{ $rn }}), + )) + {{ end }} + }, + }, +{{- end }} +} diff --git a/gnovm/stdlibs/math/abs.gno b/gnovm/stdlibs/math/abs.gno index 7a55c0c5bd2..08be14548dd 100644 --- a/gnovm/stdlibs/math/abs.gno +++ b/gnovm/stdlibs/math/abs.gno @@ -4,10 +4,6 @@ package math -import ( - imath "internal/math" // XXX -) - // Abs returns the absolute value of x. // // Special cases are: @@ -15,5 +11,5 @@ import ( // Abs(±Inf) = +Inf // Abs(NaN) = NaN func Abs(x float64) float64 { - return imath.Float64frombits(imath.Float64bits(x) &^ (1 << 63)) + return Float64frombits(Float64bits(x) &^ (1 << 63)) } diff --git a/gnovm/stdlibs/math/bits.gno b/gnovm/stdlibs/math/bits.gno index 6cf646d2f5e..c5cb93b1594 100644 --- a/gnovm/stdlibs/math/bits.gno +++ b/gnovm/stdlibs/math/bits.gno @@ -4,10 +4,6 @@ package math -import ( - imath "internal/math" -) - const ( uvnan = 0x7FF8000000000001 uvinf = 0x7FF0000000000000 @@ -28,11 +24,11 @@ func Inf(sign int) float64 { } else { v = uvneginf } - return imath.Float64frombits(v) + return Float64frombits(v) } // NaN returns an IEEE 754 “not-a-number” value. -func NaN() float64 { return imath.Float64frombits(uvnan) } +func NaN() float64 { return Float64frombits(uvnan) } // IsNaN reports whether f is an IEEE 754 “not-a-number” value. func IsNaN(f float64) (is bool) { diff --git a/gnovm/stdlibs/math/copysign.gno b/gnovm/stdlibs/math/copysign.gno index a5e05427c6f..3a30afb4136 100644 --- a/gnovm/stdlibs/math/copysign.gno +++ b/gnovm/stdlibs/math/copysign.gno @@ -4,13 +4,9 @@ package math -import ( - imath "internal/math" -) - // Copysign returns a value with the magnitude of f // and the sign of sign. func Copysign(f, sign float64) float64 { const signBit = 1 << 63 - return imath.Float64frombits(imath.Float64bits(f)&^signBit | imath.Float64bits(sign)&signBit) + return Float64frombits(Float64bits(f)&^signBit | Float64bits(sign)&signBit) } diff --git a/gnovm/stdlibs/math/floor.gno b/gnovm/stdlibs/math/floor.gno index 7d6320a1983..19890df50ad 100644 --- a/gnovm/stdlibs/math/floor.gno +++ b/gnovm/stdlibs/math/floor.gno @@ -4,10 +4,6 @@ package math -import ( - imath "internal/math" -) - // Floor returns the greatest integer value less than or equal to x. // // Special cases are: @@ -100,7 +96,7 @@ func Round(x float64) float64 { // } // return t // } - bits := imath.Float64bits(x) + bits := Float64bits(x) e := uint(bits>>shift) & mask if e < bias { // Round abs(x) < 1 including denormals. @@ -118,7 +114,7 @@ func Round(x float64) float64 { bits += half >> e bits &^= fracMask >> e } - return imath.Float64frombits(bits) + return Float64frombits(bits) } // RoundToEven returns the nearest integer, rounding ties to even. @@ -139,7 +135,7 @@ func RoundToEven(x float64) float64 { // } // return t // } - bits := imath.Float64bits(x) + bits := Float64bits(x) e := uint(bits>>shift) & mask if e >= bias { // Round abs(x) >= 1. @@ -157,5 +153,5 @@ func RoundToEven(x float64) float64 { // Round abs(x) <= 0.5 including denormals. bits &= signMask // +-0 } - return imath.Float64frombits(bits) + return Float64frombits(bits) } diff --git a/gnovm/stdlibs/math/ldexp.gno b/gnovm/stdlibs/math/ldexp.gno index 5af0cff2c3b..c6018b73a3b 100644 --- a/gnovm/stdlibs/math/ldexp.gno +++ b/gnovm/stdlibs/math/ldexp.gno @@ -4,10 +4,6 @@ package math -import ( - imath "internal/math" -) - // Ldexp is the inverse of Frexp. // It returns frac × 2**exp. // @@ -35,7 +31,7 @@ func ldexp(frac float64, exp int) float64 { } frac, e := normalize(frac) exp += e - x := imath.Float64bits(frac) + x := Float64bits(frac) exp += int(x>>shift)&mask - bias if exp < -1075 { return Copysign(0, frac) // underflow @@ -53,5 +49,5 @@ func ldexp(frac float64, exp int) float64 { } x &^= mask << shift x |= uint64(exp+bias) << shift - return m * imath.Float64frombits(x) + return m * Float64frombits(x) } diff --git a/gnovm/stdlibs/math/modf.gno b/gnovm/stdlibs/math/modf.gno index d0fb27ef007..dafec69f78c 100644 --- a/gnovm/stdlibs/math/modf.gno +++ b/gnovm/stdlibs/math/modf.gno @@ -4,10 +4,6 @@ package math -import ( - imath "internal/math" -) - // Modf returns integer and fractional floating-point numbers // that sum to f. Both values have the same sign as f. // @@ -36,14 +32,14 @@ func modf(f float64) (int float64, frac float64) { return 0, f } - x := imath.Float64bits(f) + x := Float64bits(f) e := uint(x>>shift)&mask - bias // Keep the top 12+e bits, the integer part; clear the rest. if e < 64-12 { x &^= 1<<(64-12-e) - 1 } - int = imath.Float64frombits(x) + int = Float64frombits(x) frac = f - int return } diff --git a/gnovm/stdlibs/math/native.gno b/gnovm/stdlibs/math/native.gno new file mode 100644 index 00000000000..30a758558d1 --- /dev/null +++ b/gnovm/stdlibs/math/native.gno @@ -0,0 +1,21 @@ +package math + +// Float32bits returns the IEEE 754 binary representation of f, with the sign +// bit of f and the result in the same bit position. +// Float32bits(Float32frombits(x)) == x. +func Float32bits(f float32) uint32 + +// Float32frombits returns the floating-point number corresponding to the IEEE +// 754 binary representation b, with the sign bit of b and the result in the +// same bit position. Float32frombits(Float32bits(x)) == x. +func Float32frombits(b uint32) float32 + +// Float64bits returns the IEEE 754 binary representation of f, with the sign +// bit of f and the result in the same bit position. +// Float64bits(Float64frombits(x)) == x. +func Float64bits(f float64) uint64 + +// Float64frombits returns the floating-point number corresponding to the IEEE +// 754 binary representation b, with the sign bit of b and the result in the +// same bit position. Float64frombits(Float64bits(x)) == x. +func Float64frombits(b uint64) float64 diff --git a/gnovm/stdlibs/math/native.go b/gnovm/stdlibs/math/native.go new file mode 100644 index 00000000000..21021085f6d --- /dev/null +++ b/gnovm/stdlibs/math/native.go @@ -0,0 +1,8 @@ +package math + +import "math" + +func Float32bits(f float32) uint32 { return math.Float32bits(f) } +func Float32frombits(b uint32) float32 { return math.Float32frombits(b) } +func Float64bits(f float64) uint64 { return math.Float64bits(f) } +func Float64frombits(b uint64) float64 { return math.Float64frombits(b) } diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go new file mode 100644 index 00000000000..8f9244ef927 --- /dev/null +++ b/gnovm/stdlibs/native.go @@ -0,0 +1,156 @@ +// This file is autogenerated; do not edit. +// To regenerate it, run `go generate` or `go run ./internal/stdgen` +// from @/stdlibs. + +package stdlibs + +import ( + "reflect" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + lib0 "github.com/gnolang/gno/gnovm/stdlibs/crypto/sha256" + lib1 "github.com/gnolang/gno/gnovm/stdlibs/math" +) + +var nativeFuncs = [...]nativeFunc{ + { + "crypto/sha256", + "Sum256", + + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("[]byte")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("[]byte")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 []byte + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := lib0.Sum256(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(r0), + )) + }, + }, + { + "math", + "Float32bits", + + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("float32")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("uint32")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 float32 + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := lib1.Float32bits(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(r0), + )) + }, + }, + { + "math", + "Float32frombits", + + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("uint32")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("float32")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 uint32 + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := lib1.Float32frombits(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(r0), + )) + }, + }, + { + "math", + "Float64bits", + + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("float64")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("uint64")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 float64 + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := lib1.Float64bits(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(r0), + )) + }, + }, + { + "math", + "Float64frombits", + + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("uint64")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("float64")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 uint64 + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := lib1.Float64frombits(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(r0), + )) + }, + }, +} diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index 95803558d06..d5519a9c199 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -1,8 +1,8 @@ package stdlibs +//go:generate go run ./internal/stdgen + import ( - "crypto/sha256" - "math" "reflect" "strconv" "time" @@ -19,88 +19,22 @@ func InjectNativeMappings(store gno.Store) { store.AddGo2GnoMapping(reflect.TypeOf(std.Coin{}), "std", "Coin") } -func InjectPackage(store gno.Store, pn *gno.PackageNode) { - switch pn.PkgPath { - case "internal/crypto/sha256": - pn.DefineNative("Sum256", - gno.Flds( // params - "data", "[]byte", - ), - gno.Flds( // results - "bz", "[32]byte", - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - bz := []byte(nil) +type nativeFunc struct { + gnoPkg string + gnoFunc gno.Name + params []gno.FieldTypeExpr + results []gno.FieldTypeExpr + f func(m *gno.Machine) +} - if arg0.V != nil { - slice := arg0.V.(*gno.SliceValue) - array := slice.GetBase(m.Store) - bz = array.GetReadonlyBytes()[:slice.Length] - } +func InjectPackage(store gno.Store, pn *gno.PackageNode) { + for _, nf := range nativeFuncs { + if nf.gnoPkg == pn.PkgPath { + pn.DefineNative(nf.gnoFunc, nf.params, nf.results, nf.f) + } + } - hash := sha256.Sum256(bz) - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(hash), - ) - m.PushValue(res0) - }, - ) - case "internal/math": - pn.DefineNative("Float32bits", - gno.Flds( // params - "f", "float32", - ), - gno.Flds( // results - "b", "uint32", - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - res0 := typedUint32(math.Float32bits(arg0.GetFloat32())) - m.PushValue(res0) - }, - ) - pn.DefineNative("Float32frombits", - gno.Flds( // params - "b", "uint32", - ), - gno.Flds( // results - "f", "float32", - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - res0 := typedFloat32(math.Float32frombits(arg0.GetUint32())) - m.PushValue(res0) - }, - ) - pn.DefineNative("Float64bits", - gno.Flds( // params - "f", "float64", - ), - gno.Flds( // results - "b", "uint64", - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - res0 := typedUint64(math.Float64bits(arg0.GetFloat64())) - m.PushValue(res0) - }, - ) - pn.DefineNative("Float64frombits", - gno.Flds( // params - "b", "uint64", - ), - gno.Flds( // results - "f", "float64", - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - res0 := typedFloat64(math.Float64frombits(arg0.GetUint64())) - m.PushValue(res0) - }, - ) + switch pn.PkgPath { case "internal/os": pn.DefineNative("Now", gno.Flds( // params diff --git a/gnovm/stdlibs/time/time.go b/gnovm/stdlibs/time/time.go new file mode 100644 index 00000000000..f75c484afa3 --- /dev/null +++ b/gnovm/stdlibs/time/time.go @@ -0,0 +1,21 @@ +package time + +import ( + "time" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +type execContext interface { + GetTimestamp() int64 + GetTimestampNano() int64 +} + +func X_now(m *gno.Machine) (sec int64, nsec int32, mono int64) { + if m == nil || m.Context == nil { + return 0, 0, 0 + } + + ctx := m.Context.(execContext) + return ctx.GetTimestamp(), int32(ctx.GetTimestampNano()), ctx.GetTimestamp()*int64(time.Second) + ctx.GetTimestampNano() +} diff --git a/gnovm/tests/file.go b/gnovm/tests/file.go index 51917633158..5c7f6106f9b 100644 --- a/gnovm/tests/file.go +++ b/gnovm/tests/file.go @@ -284,7 +284,7 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { if tv, ok := pnc.(*gno.TypedValue); ok { panic(fmt.Sprintf("fail on %s: got unexpected error: %s", path, tv.Sprint(m))) } else { // happens on 'unknown import path ...' - panic(fmt.Sprintf("fail on %s: got unexpected error: %v", path, pnc)) + panic(fmt.Sprintf("fail on %s: got unexpected error: %#v", path, pnc)) } } // check result diff --git a/gnovm/tests/files/float5_native.gno b/gnovm/tests/files/float5_native.gno index 1050d277605..b2d9228c0bc 100644 --- a/gnovm/tests/files/float5_native.gno +++ b/gnovm/tests/files/float5_native.gno @@ -1,13 +1,13 @@ package main import ( - imath "internal/math" + "math" ) func main() { // test float64 f := float64(0.3) - x := imath.Float64bits(f) + x := math.Float64bits(f) e := uint(40) println(f, x, e, (1 << (64 - e))) diff --git a/gnovm/tests/files/float5_stdlibs.gno b/gnovm/tests/files/float5_stdlibs.gno index 8449ddff820..b3d8cd84713 100644 --- a/gnovm/tests/files/float5_stdlibs.gno +++ b/gnovm/tests/files/float5_stdlibs.gno @@ -2,13 +2,11 @@ package main import ( "math" - - imath "internal/math" ) func main() { - println(math.MaxFloat32, imath.Float32bits(math.MaxFloat32)) - println(math.MaxFloat64, imath.Float64bits(math.MaxFloat64)) + println(math.MaxFloat32, math.Float32bits(math.MaxFloat32)) + println(math.MaxFloat64, math.Float64bits(math.MaxFloat64)) } // Output: From 6153b6a0f51f4edfa85575745b7a440f3e9fe62c Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 20 Jun 2023 16:12:50 +0200 Subject: [PATCH 02/59] changes --- gnovm/pkg/gnolang/go2gno.go | 10 +++--- gnovm/pkg/gnolang/machine.go | 41 ++++++++++++++---------- gnovm/pkg/gnolang/nodes.go | 60 +++++++++++++++++++++--------------- gnovm/pkg/gnolang/op_call.go | 3 ++ gnovm/stdlibs/native.go | 33 ++++++++++++++++++++ gnovm/stdlibs/time/time.gno | 9 ++---- gnovm/tests/imports.go | 19 +++++++----- tm2/pkg/errors/errors.go | 9 +++--- tm2/pkg/sdk/vm/builtins.go | 39 +---------------------- tm2/pkg/std/memfile.go | 8 +++-- 10 files changed, 128 insertions(+), 103 deletions(-) diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index b387f548f48..e67d59cc95f 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -96,9 +96,11 @@ func MustParseExpr(expr string) Expr { return x } -// filename must not include the path. +// ParseFile uses the Go parser to parse body. It then runs [Go2Gno] on the +// resulting AST -- the resulting FileNode is returned, together with any other +// error (including panics, which are recovered) from [Go2Gno]. func ParseFile(filename string, body string) (fn *FileNode, err error) { - // Parse src but stop after processing the imports. + // Use go parser to parse the body. fs := token.NewFileSet() f, err := parser.ParseFile(fs, filename, body, parser.ParseComments|parser.DeclarationErrors) if err != nil { @@ -435,11 +437,11 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { if gon.Body != nil { body = Go2Gno(fs, gon.Body).(*BlockStmt).Body } else { - /*bd, err := ParseExpr(`panic("call to bodyless/external function is invalid outside of standard library")`) + bd, err := ParseExpr(`panic("call to bodyless/external function is invalid outside of standard library")`) if err != nil { panic(err) } - body = []Stmt{&ExprStmt{X: bd}}*/ + body = []Stmt{&ExprStmt{X: bd}} } return &FuncDecl{ IsMethod: isMethod, diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 0da2874afd3..cb0277b2106 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -34,6 +34,7 @@ type Machine struct { Exceptions []*TypedValue // if panic'd unless recovered NumResults int // number of results returned Cycles int64 // number of "cpu" cycles + Injector func(store Store, pn *PackageNode) // Configuration CheckTypes bool // not yet used @@ -45,14 +46,15 @@ type Machine struct { Context interface{} } -// machine.Release() must be called on objects -// created via this constructor -// Machine with new package of given path. -// Creates a new MemRealmer for any new realms. -// Looks in store for package of pkgPath; if not found, -// creates new instances as necessary. -// If pkgPath is zero, the machine has no active package -// and one must be set prior to usage. +// NewMachine initializes a new gno virtual machine, acting as a shorthand +// for [NewMachineWithOptions], setting the given options PkgPath and Store. +// +// The machine will run on the package at the given path, which will be +// retrieved through the given store. If it is not set, the machine has no +// active package, and one must be set prior to usage. +// +// Like for [NewMachineWithOptions], Machines initialized through this +// constructor must be finalized with [Machine.Release]. func NewMachine(pkgPath string, store Store) *Machine { return NewMachineWithOptions( MachineOptions{ @@ -61,16 +63,19 @@ func NewMachine(pkgPath string, store Store) *Machine { }) } +// MachineOptions is used to pass options to [NewMachineWithOptions]. type MachineOptions struct { + // Active package of the given machine; must be set before execution. PkgPath string CheckTypes bool // not yet used ReadOnly bool - Output io.Writer - Store Store + Output io.Writer // default os.Stdout + Store Store // default NewStore(Alloc, nil, nil) Context interface{} Alloc *Allocator // or see MaxAllocBytes. MaxAllocBytes int64 // or 0 for no limit. MaxCycles int64 // or 0 for no limit. + Injector func(store Store, pn *PackageNode) } // the machine constructor gets spammed @@ -86,6 +91,11 @@ var machinePool = sync.Pool{ }, } +// NewMachineWithOptions initializes a new gno virtual machine with the given +// options. +// +// Machines initialized through this constructor must be finalized with +// [Machine.Release]. func NewMachineWithOptions(opts MachineOptions) *Machine { checkTypes := opts.CheckTypes readOnly := opts.ReadOnly @@ -129,6 +139,7 @@ func NewMachineWithOptions(opts MachineOptions) *Machine { Output: output, Store: store, Context: context, + Injector: opts.Injector, } if pv != nil { @@ -147,13 +158,11 @@ var ( valueZeroed [ValueSize]TypedValue ) -// m should not be used after this call -// if m is nil, this will panic -// this is on purpose, to discourage misuse -// and prevent objects that were not taken from -// the pool, to call Release +// Release resets some of the values of *Machine and puts back m into the +// machine pool; for this reason, Release() should be called as a finalizer, +// and m should not be used after this call. Only Machines initialized with this +// package's constructors should be released. func (m *Machine) Release() { - // copy() // here we zero in the values for the next user m.NumOps = 0 m.NumValues = 0 diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 610e46de191..48f74c16657 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1089,14 +1089,22 @@ func PackageNameFromFileBody(name string, body string) Name { return n.PkgName } -// NOTE: panics if package name is invalid. +// ReadMemPackage initializes a new MemPackage by reading the OS directory +// at dir, and saving it with the given pkgPath (import path). +// The resulting MemPackage will contain the names and content of all *.gno files, +// and additionally README.md, LICENSE, and gno.mod. +// +// ReadMemPackage does not perform validation aside from the package's name; +// the files are not parsed but their contents are merely stored inside a MemFile. +// +// NOTE: panics if package name is invalid (characters must be alphanumeric or _, +// lowercase, and must start with a letter). func ReadMemPackage(dir string, pkgPath string) *std.MemPackage { files, err := ioutil.ReadDir(dir) if err != nil { panic(err) } memPkg := &std.MemPackage{Path: pkgPath} - var pkgName Name allowedFiles := []string{ // make case insensitive? "gno.mod", "LICENSE", @@ -1105,6 +1113,7 @@ func ReadMemPackage(dir string, pkgPath string) *std.MemPackage { allowedFileExtensions := []string{ ".gno", } + var pkgName Name for _, file := range files { if file.IsDir() || strings.HasPrefix(file.Name(), ".") || @@ -1116,6 +1125,7 @@ func ReadMemPackage(dir string, pkgPath string) *std.MemPackage { if err != nil { panic(err) } + // XXX: should check that all pkg names are the same (else package is invalid) if pkgName == "" && strings.HasSuffix(file.Name(), ".gno") { pkgName = PackageNameFromFileBody(file.Name(), string(bz)) if strings.HasSuffix(string(pkgName), "_test") { @@ -1133,33 +1143,29 @@ func ReadMemPackage(dir string, pkgPath string) *std.MemPackage { return memPkg } -func PrecompileMemPackage(memPkg *std.MemPackage) error { - return nil -} - -// Returns the code fileset minus any spurious or test files. +// ParseMemPackage executes [ParseFile] on each file of the memPkg, excluding +// test and spurious (non-gno) files. The resulting *FileSet is returned. +// +// If one of the files has a different package name than memPkg.Name, +// or [ParseFile] returns an error, ParseMemPackage panics. func ParseMemPackage(memPkg *std.MemPackage) (fset *FileSet) { fset = &FileSet{} for _, mfile := range memPkg.Files { - if !strings.HasSuffix(mfile.Name, ".gno") { - continue // skip spurious file. + if !strings.HasSuffix(mfile.Name, ".gno") || + endsWith(mfile.Name, []string{"_test.gno", "_filetest.gno"}) { + continue // skip spurious or test file. } n, err := ParseFile(mfile.Name, mfile.Body) if err != nil { panic(errors.Wrap(err, "parsing file "+mfile.Name)) } - if strings.HasSuffix(mfile.Name, "_test.gno") { - // skip test file. - } else if strings.HasSuffix(mfile.Name, "_filetest.gno") { - // skip test file. - } else if memPkg.Name == string(n.PkgName) { - // add package file. - fset.AddFiles(n) - } else { + if memPkg.Name != string(n.PkgName) { panic(fmt.Sprintf( - "expected package name [%s] or [%s_test] but got [%s]", - memPkg.Name, memPkg.Name, n.PkgName)) + "expected package name [%s] but got [%s]", + memPkg.Name, n.PkgName)) } + // add package file. + fset.AddFiles(n) } return fset } @@ -1370,11 +1376,16 @@ func (x *PackageNode) DefineNative(n Name, ps, rs FieldTypeExprs, native func(*M panic("DefineNative expects a function, but got nil") } - if idx, exists := x.GetLocalIndex(n); exists { - // XXX(morgan): trick to look as a new definition to Define2; - // if we try to redefine an old value it looks like precompile - // is not cooperating well with us... - x.Names[idx] = "#uncallable_" + x.Names[idx] + if v := x.GetValueRef(nil, n); v != nil { + // redefinition + fv, ok := v.V.(*FuncValue) + if !ok { + panic("cannot redefine non-function as native function") + } + // XXX: type-check + fv.body = nil + fv.nativeBody = native + return } fd := FuncD(n, ps, rs, nil) @@ -2023,6 +2034,7 @@ const ( ) // TODO: consider length restrictions. +// If this function is changed, ReadMemPackage's documentation should be updated accordingly. func validatePkgName(name string) { if nameOK, _ := regexp.MatchString( `^[a-z][a-z0-9_]+$`, name); !nameOK { diff --git a/gnovm/pkg/gnolang/op_call.go b/gnovm/pkg/gnolang/op_call.go index f43a593c50e..824500c227c 100644 --- a/gnovm/pkg/gnolang/op_call.go +++ b/gnovm/pkg/gnolang/op_call.go @@ -58,6 +58,9 @@ func (m *Machine) doOpCall() { clo := fr.Func.GetClosure(m.Store) b := m.Alloc.NewBlock(fr.Func.GetSource(m.Store), clo) m.PushBlock(b) + if fv.Name == "now" { + fmt.Printf("CALL NOW %#v\n", fv) + } if fv.nativeBody == nil { fbody := fv.GetBodyFromSource(m.Store) if len(ft.Results) == 0 { diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go index 8f9244ef927..9f7bcaf5139 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/native.go @@ -10,6 +10,7 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" lib0 "github.com/gnolang/gno/gnovm/stdlibs/crypto/sha256" lib1 "github.com/gnolang/gno/gnovm/stdlibs/math" + lib2 "github.com/gnolang/gno/gnovm/stdlibs/time" ) var nativeFuncs = [...]nativeFunc{ @@ -153,4 +154,36 @@ var nativeFuncs = [...]nativeFunc{ )) }, }, + { + "time", + "now", + + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("int64")}, + {Name: gno.N("r1"), Type: gno.X("int32")}, + {Name: gno.N("r2"), Type: gno.X("int64")}, + }, + func(m *gno.Machine) { + r0, r1, r2 := lib2.X_now( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(r0), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(r1), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(r2), + )) + }, + }, } diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index 7b7e45ba9b6..dfae44b07b9 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -80,8 +80,6 @@ package time import ( "errors" - - ios "internal/os" // XXX to access time. // XXX _ "unsafe" // for go:linkname ) @@ -1071,17 +1069,14 @@ func daysSinceEpoch(year int) uint64 { return d } -/* XXX replaced with ios.Now() -// Provided by package runtime. func now() (sec int64, nsec int32, mono int64) -*/ // XXX SHIM // runtimeNano returns the current value of the runtime clock in nanoseconds. // //go:linkname runtimeNano runtime.nanotime func runtimeNano() int64 { - _, _, mono := ios.Now() // XXX now() + _, _, mono := now() return mono } @@ -1095,7 +1090,7 @@ var startNano int64 = runtimeNano() - 1 // Now returns the current local time. func Now() Time { - sec, nsec, mono := ios.Now() // XXX now() + sec, nsec, mono := now() mono -= startNano sec += unixToInternal - minWall if uint64(sec)>>33 != 0 { diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 58d62f02d8d..daa5f992cda 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -99,10 +99,12 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri memPkg := gno.ReadMemPackage(stdlibPath, pkgPath) m2 := gno.NewMachineWithOptions(gno.MachineOptions{ // NOTE: see also pkgs/sdk/vm/builtins.go - // XXX: why does this fail when just pkgPath? - PkgPath: "gno.land/r/stdlibs/" + pkgPath, - Output: stdout, - Store: store, + // Needs PkgPath != its name because, seeing as we are passing ourselves + // as a store, it would recusively call this function on the same package + PkgPath: "stdlibload", + Output: stdout, + Store: store, + Injector: testPackageInjector, }) return m2.RunMemPackage(memPkg, true) } @@ -413,9 +415,10 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri if osm.DirExists(stdlibPath) { memPkg := gno.ReadMemPackage(stdlibPath, pkgPath) m2 := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: "test", - Output: stdout, - Store: store, + PkgPath: "test", + Output: stdout, + Store: store, + Injector: testPackageInjector, }) pn, pv = m2.RunMemPackage(memPkg, true) return @@ -442,7 +445,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri iavlStore := iavl.StoreConstructor(db, stypes.StoreOptions{}) store = gno.NewStore(nil, baseStore, iavlStore) store.SetPackageGetter(getPackage) - store.SetPackageInjector(testPackageInjector) + // store.SetPackageInjector(testPackageInjector) store.SetStrictGo2GnoMapping(false) // native mappings stdlibs.InjectNativeMappings(store) diff --git a/tm2/pkg/errors/errors.go b/tm2/pkg/errors/errors.go index 8485d68feb8..7ae0dcbe76b 100644 --- a/tm2/pkg/errors/errors.go +++ b/tm2/pkg/errors/errors.go @@ -158,10 +158,11 @@ func (err *cmnError) Format(s fmt.State, verb rune) { // Write stack trace. if err.stacktrace != nil { s.Write([]byte("Stack Trace:\n")) - for i, pc := range err.stacktrace { - fnc := runtime.FuncForPC(pc) - file, line := fnc.FileLine(pc) - fmt.Fprintf(s, " %4d %s:%d\n", i, file, line) + frames := runtime.CallersFrames(err.stacktrace) + i := 0 + for frame, _ := frames.Next(); frame.PC != 0; frame, _ = frames.Next() { + fmt.Fprintf(s, " %4d %s:%d\n", i, frame.File, frame.Line) + i++ } } s.Write([]byte("--= /Error =--\n")) diff --git a/tm2/pkg/sdk/vm/builtins.go b/tm2/pkg/sdk/vm/builtins.go index 3e846f32288..c40f3dadc74 100644 --- a/tm2/pkg/sdk/vm/builtins.go +++ b/tm2/pkg/sdk/vm/builtins.go @@ -35,47 +35,10 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { return m2.RunMemPackage(memPkg, true) } store.SetPackageGetter(getPackage) - store.SetPackageInjector(vm.packageInjector) + store.SetPackageInjector(stdlibs.InjectPackage) stdlibs.InjectNativeMappings(store) } -func (vm *VMKeeper) packageInjector(store gno.Store, pn *gno.PackageNode) { - // Also inject stdlibs native functions. - stdlibs.InjectPackage(store, pn) - // vm (this package) specific injections: - switch pn.PkgPath { - case "std": - /* XXX deleteme - // Also see stdlibs/InjectPackage. - pn.DefineNative("AssertOriginCall", - gno.Flds( // params - ), - gno.Flds( // results - ), - func(m *gno.Machine) { - isOrigin := len(m.Frames) == 2 - if !isOrigin { - panic("invalid non-origin call") - } - }, - ) - pn.DefineNative("IsOriginCall", - gno.Flds( // params - ), - gno.Flds( // results - "isOrigin", "bool", - ), - func(m *gno.Machine) { - isOrigin := len(m.Frames) == 2 - res0 := gno.TypedValue{T: gno.BoolType} - res0.SetBool(isOrigin) - m.PushValue(res0) - }, - ) - */ - } -} - // ---------------------------------------- // SDKBanker diff --git a/tm2/pkg/std/memfile.go b/tm2/pkg/std/memfile.go index 55c384485e7..141f3f8f5bc 100644 --- a/tm2/pkg/std/memfile.go +++ b/tm2/pkg/std/memfile.go @@ -13,11 +13,15 @@ type MemFile struct { Body string } +// MemPackage represents the information and files of a package which will be +// stored in memory. It will generally be initialized by package gnolang's +// ReadMemPackage. +// // NOTE: in the future, a MemPackage may represent // updates/additional-files for an existing package. type MemPackage struct { - Name string - Path string + Name string // package name as declared by `package` + Path string // import path Files []*MemFile } From 000281969d75d5864de7b269cb01c28b090533ce Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 20 Jun 2023 16:36:35 +0200 Subject: [PATCH 03/59] add injector call --- gnovm/pkg/gnolang/machine.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index cb0277b2106..8a70d7a8af8 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -503,6 +503,11 @@ func (m *Machine) runFiles(fns ...*FileNode) { } } + // Inject native functions + if m.Injector != nil { + m.Injector(m.Store, pn) + } + // Declarations (and variable initializations). This must happen // after all files are preprocessed, because value decl may be out of // order and depend on other files. From cd94b0c69ca242f9fba9f1f5e2a9a4ee9c649ce5 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Tue, 20 Jun 2023 16:45:54 +0200 Subject: [PATCH 04/59] log ptrs --- gnovm/pkg/gnolang/nodes.go | 1 + gnovm/pkg/gnolang/op_call.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 48f74c16657..c033162513a 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1382,6 +1382,7 @@ func (x *PackageNode) DefineNative(n Name, ps, rs FieldTypeExprs, native func(*M if !ok { panic("cannot redefine non-function as native function") } + fmt.Printf("REDEFINE %s %p\n", n, fv) // XXX: type-check fv.body = nil fv.nativeBody = native diff --git a/gnovm/pkg/gnolang/op_call.go b/gnovm/pkg/gnolang/op_call.go index 824500c227c..1e19579ea0d 100644 --- a/gnovm/pkg/gnolang/op_call.go +++ b/gnovm/pkg/gnolang/op_call.go @@ -59,7 +59,7 @@ func (m *Machine) doOpCall() { b := m.Alloc.NewBlock(fr.Func.GetSource(m.Store), clo) m.PushBlock(b) if fv.Name == "now" { - fmt.Printf("CALL NOW %#v\n", fv) + fmt.Printf("CALL NOW %p %#v\n", fv, fv) } if fv.nativeBody == nil { fbody := fv.GetBodyFromSource(m.Store) From 6248c801e740ca792fc0448be81a570583265949 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 23 Jun 2023 14:32:55 +0200 Subject: [PATCH 05/59] change approach to "NativeStore" --- gnovm/pkg/gnolang/go2gno.go | 6 - gnovm/pkg/gnolang/gonative.go | 9 + gnovm/pkg/gnolang/machine.go | 2 +- gnovm/pkg/gnolang/op_call.go | 2 +- gnovm/pkg/gnolang/preprocess.go | 31 +- gnovm/pkg/gnolang/realm.go | 19 +- gnovm/pkg/gnolang/store.go | 25 +- gnovm/stdlibs/context.go | 2 + gnovm/stdlibs/encoding/base64/base64.gno | 2 +- gnovm/stdlibs/internal/stdgen/stdgen.go | 3 + gnovm/stdlibs/internal/stdgen/template.tmpl | 2 +- gnovm/stdlibs/math/signbit.gno | 6 +- gnovm/stdlibs/native.go | 360 +++++++++++++++++++- gnovm/stdlibs/std/native.gno | 7 + gnovm/stdlibs/std/native.go | 46 +++ gnovm/stdlibs/stdlibs.go | 129 +------ gnovm/stdlibs/strconv/strconv.gno | 12 +- gnovm/stdlibs/strconv/strconv.go | 12 + gnovm/stdlibs/time/time.gno | 1 - gnovm/tests/imports.go | 3 +- 20 files changed, 503 insertions(+), 176 deletions(-) create mode 100644 gnovm/stdlibs/std/native.gno create mode 100644 gnovm/stdlibs/std/native.go create mode 100644 gnovm/stdlibs/strconv/strconv.go diff --git a/gnovm/pkg/gnolang/go2gno.go b/gnovm/pkg/gnolang/go2gno.go index e67d59cc95f..ee82cb39555 100644 --- a/gnovm/pkg/gnolang/go2gno.go +++ b/gnovm/pkg/gnolang/go2gno.go @@ -436,12 +436,6 @@ func Go2Gno(fs *token.FileSet, gon ast.Node) (n Node) { var body []Stmt if gon.Body != nil { body = Go2Gno(fs, gon.Body).(*BlockStmt).Body - } else { - bd, err := ParseExpr(`panic("call to bodyless/external function is invalid outside of standard library")`) - if err != nil { - panic(err) - } - body = []Stmt{&ExprStmt{X: bd}} } return &FuncDecl{ IsMethod: isMethod, diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index 69e468f755a..80dc1d06cd6 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -676,6 +676,8 @@ func go2GnoValueUpdate(alloc *Allocator, rlm *Realm, lvl int, tv *TypedValue, rv return } +var tError = reflect.TypeOf(new(error)).Elem() + // If recursive is false, this function is like go2GnoValue() but less lazy // (but still not recursive/eager). When recursive is false, it is for // converting Go types to Gno types upon an explicit conversion (via @@ -757,6 +759,13 @@ func go2GnoValue2(alloc *Allocator, store Store, rv reflect.Value, recursive boo // regardless. tv.V = alloc.NewNative(rv) case reflect.Interface: + if rv.Type() == tError { + tv.T = gErrorType + if !rv.IsNil() { + tv.V = alloc.NewNative(rv.Elem()) + } + return + } panic("not yet implemented") case reflect.Map: panic("not yet implemented") diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 8a70d7a8af8..1a2ab335107 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -139,7 +139,7 @@ func NewMachineWithOptions(opts MachineOptions) *Machine { Output: output, Store: store, Context: context, - Injector: opts.Injector, + // Injector: opts.Injector, } if pv != nil { diff --git a/gnovm/pkg/gnolang/op_call.go b/gnovm/pkg/gnolang/op_call.go index 1e19579ea0d..d8178043813 100644 --- a/gnovm/pkg/gnolang/op_call.go +++ b/gnovm/pkg/gnolang/op_call.go @@ -58,7 +58,7 @@ func (m *Machine) doOpCall() { clo := fr.Func.GetClosure(m.Store) b := m.Alloc.NewBlock(fr.Func.GetSource(m.Store), clo) m.PushBlock(b) - if fv.Name == "now" { + if fv.Name == "Now" { fmt.Printf("CALL NOW %p %#v\n", fv, fv) } if fv.nativeBody == nil { diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index e02a158fcf1..9e392cf8e8f 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -3122,19 +3122,28 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { } // define a FuncValue w/ above type as d.Name. // fill in later during *FuncDecl:BLOCK. + fv := &FuncValue{ + Type: ft, + IsMethod: false, + Source: d, + Name: d.Name, + Closure: nil, // set lazily. + FileName: fileNameOf(last), + PkgPath: pkg.PkgPath, + body: d.Body, + nativeBody: nil, + } + // NOTE: fv.body == nil means no body (ie. not even curly braces) + // len(fv.body) == 0 could mean also {} (ie. no statements inside) + if fv.body == nil && store != nil { + fv.nativeBody = store.GetNative(pkg.PkgPath, d.Name) + if fv.nativeBody == nil { + panic(fmt.Sprintf("function %s does not have a body but is not natively defined", d.Name)) + } + } pkg.Define(d.Name, TypedValue{ T: ft, - V: &FuncValue{ - Type: ft, - IsMethod: false, - Source: d, - Name: d.Name, - Closure: nil, // set lazily. - FileName: fileNameOf(last), - PkgPath: pkg.PkgPath, - body: d.Body, - nativeBody: nil, - }, + V: fv, }) if d.Name == "init" { // init functions can't be referenced. diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 6e1301c023f..a4ddb17835f 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -1125,18 +1125,19 @@ func copyValueWithRefs(parent Object, val Value) Value { if cv.Closure != nil { closure = toRefValue(parent, cv.Closure) } - if cv.nativeBody != nil { + /*if cv.nativeBody != nil { panic("should not happen") - } + }*/ ft := copyTypeWithRefs(cv.Type) return &FuncValue{ - Type: ft, - IsMethod: cv.IsMethod, - Source: source, - Name: cv.Name, - Closure: closure, - FileName: cv.FileName, - PkgPath: cv.PkgPath, + Type: ft, + IsMethod: cv.IsMethod, + Source: source, + Name: cv.Name, + Closure: closure, + FileName: cv.FileName, + PkgPath: cv.PkgPath, + nativeBody: cv.nativeBody, } case *BoundMethodValue: fnc := copyValueWithRefs(cv, cv.Func).(*FuncValue) diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index d3628edf216..08f10d91d72 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -19,6 +19,9 @@ type PackageGetter func(pkgPath string) (*PackageNode, *PackageValue) // inject natives into a new or loaded package (value and node) type PackageInjector func(store Store, pn *PackageNode) +// NativeStore is a function which can retrieve native bodies of native functions. +type NativeStore func(pkgName string, name Name) func(m *Machine) + type Store interface { // STABLE SetPackageGetter(PackageGetter) @@ -50,10 +53,12 @@ type Store interface { GetMemPackage(path string) *std.MemPackage GetMemFile(path string, name string) *std.MemFile IterMemPackage() <-chan *std.MemPackage - ClearObjectCache() // for each delivertx. - Fork() Store // for checktx, simulate, and queries. - SwapStores(baseStore, iavlStore store.Store) // for gas wrappers. - SetPackageInjector(PackageInjector) // for natives + ClearObjectCache() // for each delivertx. + Fork() Store // for checktx, simulate, and queries. + SwapStores(baseStore, iavlStore store.Store) // for gas wrappers. + SetPackageInjector(PackageInjector) // for natives + SetNativeStore(NativeStore) // for "new" natives XXX + GetNative(pkgPath string, name Name) func(m *Machine) // for "new" natives XXX SetLogStoreOps(enabled bool) SprintStoreOps() string LogSwitchRealm(rlmpath string) // to mark change of realm boundaries @@ -72,6 +77,7 @@ type defaultStore struct { baseStore store.Store // for objects, types, nodes iavlStore store.Store // for escaped object hashes pkgInjector PackageInjector // for injecting natives + nativeStore NativeStore // for injecting natives go2gnoMap map[string]string // go pkgpath.name -> gno pkgpath.name go2gnoStrict bool // if true, native->gno type conversion must be registered. @@ -622,6 +628,17 @@ func (ds *defaultStore) SetPackageInjector(inj PackageInjector) { ds.pkgInjector = inj } +func (ds *defaultStore) SetNativeStore(ns NativeStore) { + ds.nativeStore = ns +} + +func (ds *defaultStore) GetNative(pkgPath string, name Name) func(m *Machine) { + if ds.nativeStore != nil { + return ds.nativeStore(pkgPath, name) + } + return nil +} + func (ds *defaultStore) Flush() { // XXX } diff --git a/gnovm/stdlibs/context.go b/gnovm/stdlibs/context.go index 7cc077c6213..aa1853f22fc 100644 --- a/gnovm/stdlibs/context.go +++ b/gnovm/stdlibs/context.go @@ -21,3 +21,5 @@ type ExecContext struct { func (e ExecContext) GetTimestamp() int64 { return e.Timestamp } func (e ExecContext) GetTimestampNano() int64 { return e.TimestampNano } +func (e ExecContext) GetChainID() string { return e.ChainID } +func (e ExecContext) GetHeight() int64 { return e.Height } diff --git a/gnovm/stdlibs/encoding/base64/base64.gno b/gnovm/stdlibs/encoding/base64/base64.gno index 7889a548832..015d7a825bb 100644 --- a/gnovm/stdlibs/encoding/base64/base64.gno +++ b/gnovm/stdlibs/encoding/base64/base64.gno @@ -496,7 +496,7 @@ func (enc *Encoding) Decode(dst, src []byte) (n int, err error) { _ = enc.decodeMap si := 0 - for strconv.IntSize >= 64 && len(src)-si >= 8 && len(dst)-n >= 8 { + for len(src)-si >= 8 && len(dst)-n >= 8 { src2 := src[si : si+8] if dn, ok := assemble64( enc.decodeMap[src2[0]], diff --git a/gnovm/stdlibs/internal/stdgen/stdgen.go b/gnovm/stdlibs/internal/stdgen/stdgen.go index f7984615795..75a71d39099 100644 --- a/gnovm/stdlibs/internal/stdgen/stdgen.go +++ b/gnovm/stdlibs/internal/stdgen/stdgen.go @@ -348,6 +348,9 @@ func exprToString(e ast.Expr) string { } func fieldListToTypes(fl *ast.FieldList) []string { + if fl == nil { + return nil + } r := make([]string, 0, len(fl.List)) for _, f := range fl.List { ts := exprToString(f.Type) diff --git a/gnovm/stdlibs/internal/stdgen/template.tmpl b/gnovm/stdlibs/internal/stdgen/template.tmpl index 1261abd55c7..7fc5bb2846c 100644 --- a/gnovm/stdlibs/internal/stdgen/template.tmpl +++ b/gnovm/stdlibs/internal/stdgen/template.tmpl @@ -62,7 +62,7 @@ var nativeFuncs = [...]nativeFunc{ m.PushValue(gno.Go2GnoValue( m.Alloc, m.Store, - reflect.ValueOf(r{{ $rn }}), + reflect.ValueOf(&r{{ $rn }}).Elem(), {{- /* necessary to support interfaces (ie. error) */}} )) {{ end }} }, diff --git a/gnovm/stdlibs/math/signbit.gno b/gnovm/stdlibs/math/signbit.gno index 38c2178932e..6ce44bb1a67 100644 --- a/gnovm/stdlibs/math/signbit.gno +++ b/gnovm/stdlibs/math/signbit.gno @@ -1,10 +1,6 @@ package math -import ( - imath "internal/math" -) - // Signbit reports whether x is negative or negative zero. func Signbit(x float64) bool { - return imath.Float64bits(x)&(1<<63) != 0 + return Float64bits(x)&(1<<63) != 0 } diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go index 9f7bcaf5139..208f10a4838 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/native.go @@ -10,7 +10,9 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" lib0 "github.com/gnolang/gno/gnovm/stdlibs/crypto/sha256" lib1 "github.com/gnolang/gno/gnovm/stdlibs/math" - lib2 "github.com/gnolang/gno/gnovm/stdlibs/time" + lib2 "github.com/gnolang/gno/gnovm/stdlibs/std" + lib3 "github.com/gnolang/gno/gnovm/stdlibs/strconv" + lib4 "github.com/gnolang/gno/gnovm/stdlibs/time" ) var nativeFuncs = [...]nativeFunc{ @@ -38,7 +40,7 @@ var nativeFuncs = [...]nativeFunc{ m.PushValue(gno.Go2GnoValue( m.Alloc, m.Store, - reflect.ValueOf(r0), + reflect.ValueOf(&r0).Elem(), )) }, }, @@ -66,7 +68,7 @@ var nativeFuncs = [...]nativeFunc{ m.PushValue(gno.Go2GnoValue( m.Alloc, m.Store, - reflect.ValueOf(r0), + reflect.ValueOf(&r0).Elem(), )) }, }, @@ -94,7 +96,7 @@ var nativeFuncs = [...]nativeFunc{ m.PushValue(gno.Go2GnoValue( m.Alloc, m.Store, - reflect.ValueOf(r0), + reflect.ValueOf(&r0).Elem(), )) }, }, @@ -122,7 +124,7 @@ var nativeFuncs = [...]nativeFunc{ m.PushValue(gno.Go2GnoValue( m.Alloc, m.Store, - reflect.ValueOf(r0), + reflect.ValueOf(&r0).Elem(), )) }, }, @@ -150,7 +152,345 @@ var nativeFuncs = [...]nativeFunc{ m.PushValue(gno.Go2GnoValue( m.Alloc, m.Store, - reflect.ValueOf(r0), + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "AssertOriginCall", + + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + lib2.AssertOriginCall( + m, + ) + }, + }, + { + "std", + "IsOriginCall", + + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("bool")}, + }, + func(m *gno.Machine) { + r0 := lib2.IsOriginCall( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "CurrentRealmPath", + + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + }, + func(m *gno.Machine) { + r0 := lib2.CurrentRealmPath( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "GetChainID", + + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + }, + func(m *gno.Machine) { + r0 := lib2.GetChainID( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "GetHeight", + + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("int64")}, + }, + func(m *gno.Machine) { + r0 := lib2.GetHeight( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "strconv", + "Itoa", + + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("int")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 int + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := lib3.Itoa(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "strconv", + "AppendUint", + + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("[]byte")}, + {Name: gno.N("p1"), Type: gno.X("uint64")}, + {Name: gno.N("p2"), Type: gno.X("int")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("[]byte")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 []byte + rp0 = reflect.ValueOf(&p0).Elem() + p1 uint64 + rp1 = reflect.ValueOf(&p1).Elem() + p2 int + rp2 = reflect.ValueOf(&p2).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) + + r0 := lib3.AppendUint(p0, p1, p2) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "strconv", + "Atoi", + + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("int")}, + {Name: gno.N("r1"), Type: gno.X("error")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0, r1 := lib3.Atoi(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) + }, + }, + { + "strconv", + "CanBackquote", + + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("bool")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := lib3.CanBackquote(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "strconv", + "FormatInt", + + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("int64")}, + {Name: gno.N("p1"), Type: gno.X("int")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 int64 + rp0 = reflect.ValueOf(&p0).Elem() + p1 int + rp1 = reflect.ValueOf(&p1).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + r0 := lib3.FormatInt(p0, p1) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "strconv", + "FormatUint", + + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("uint64")}, + {Name: gno.N("p1"), Type: gno.X("int")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 uint64 + rp0 = reflect.ValueOf(&p0).Elem() + p1 int + rp1 = reflect.ValueOf(&p1).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + r0 := lib3.FormatUint(p0, p1) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "strconv", + "Quote", + + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := lib3.Quote(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "strconv", + "QuoteToASCII", + + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := lib3.QuoteToASCII(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), )) }, }, @@ -165,24 +505,24 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r2"), Type: gno.X("int64")}, }, func(m *gno.Machine) { - r0, r1, r2 := lib2.X_now( + r0, r1, r2 := lib4.X_now( m, ) m.PushValue(gno.Go2GnoValue( m.Alloc, m.Store, - reflect.ValueOf(r0), + reflect.ValueOf(&r0).Elem(), )) m.PushValue(gno.Go2GnoValue( m.Alloc, m.Store, - reflect.ValueOf(r1), + reflect.ValueOf(&r1).Elem(), )) m.PushValue(gno.Go2GnoValue( m.Alloc, m.Store, - reflect.ValueOf(r2), + reflect.ValueOf(&r2).Elem(), )) }, }, diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno new file mode 100644 index 00000000000..e2e73209142 --- /dev/null +++ b/gnovm/stdlibs/std/native.gno @@ -0,0 +1,7 @@ +package std + +func AssertOriginCall() // injected +func IsOriginCall() bool // injected +func CurrentRealmPath() string // injected +func GetChainID() string // injected +func GetHeight() int64 // injected diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go new file mode 100644 index 00000000000..149d333e4cb --- /dev/null +++ b/gnovm/stdlibs/std/native.go @@ -0,0 +1,46 @@ +package std + +import ( + "fmt" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +func AssertOriginCall(m *gno.Machine) { + fmt.Println("AssertOriginCall -- ", len(m.Frames)) + isOrigin := len(m.Frames) == 2 + if !isOrigin { + m.Panic(typedString("invalid non-origin call")) + return + } +} + +func IsOriginCall(m *gno.Machine) bool { + return len(m.Frames) == 2 +} + +func CurrentRealmPath(m *gno.Machine) string { + if m.Realm != nil { + return m.Realm.Path + } + return "" +} + +func GetChainID(m *gno.Machine) string { + return m.Context.(execContext).GetChainID() +} + +func GetHeight(m *gno.Machine) int64 { + return m.Context.(execContext).GetHeight() +} + +func typedString(s gno.StringValue) gno.TypedValue { + tv := gno.TypedValue{T: gno.StringType} + tv.SetString(s) + return tv +} + +type execContext interface { + GetHeight() int64 + GetChainID() string +} diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index e7d92f18bbb..b639be76eab 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -4,8 +4,6 @@ package stdlibs import ( "reflect" - "strconv" - "time" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/bech32" @@ -27,134 +25,19 @@ type nativeFunc struct { f func(m *gno.Machine) } -func InjectPackage(store gno.Store, pn *gno.PackageNode) { +func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { for _, nf := range nativeFuncs { - if nf.gnoPkg == pn.PkgPath { - pn.DefineNative(nf.gnoFunc, nf.params, nf.results, nf.f) + if nf.gnoPkg == pkgPath && name == nf.gnoFunc { + return nf.f } } + return nil +} +func InjectPackage(store gno.Store, pn *gno.PackageNode) { switch pn.PkgPath { - case "internal/os": - pn.DefineNative("Now", - gno.Flds( // params - ), - gno.Flds( // results - "sec", "int64", - "nsec", "int32", - "mono", "int64", - ), - func(m *gno.Machine) { - if m.Context == nil { - res0 := typedInt64(0) - res1 := typedInt32(0) - res2 := typedInt64(0) - m.PushValue(res0) - m.PushValue(res1) - m.PushValue(res2) - } else { - ctx := m.Context.(ExecContext) - res0 := typedInt64(ctx.Timestamp) - res1 := typedInt32(int32(ctx.TimestampNano)) - res2 := typedInt64(ctx.Timestamp*int64(time.Second) + ctx.TimestampNano) - m.PushValue(res0) - m.PushValue(res1) - m.PushValue(res2) - } - }, - ) - // case "internal/os_test": - // XXX defined in tests/imports.go - case "strconv": - pn.DefineGoNativeValue("Itoa", strconv.Itoa) - pn.DefineGoNativeValue("Atoi", strconv.Atoi) - pn.DefineGoNativeValue("FormatInt", strconv.FormatInt) - pn.DefineGoNativeValue("FormatUint", strconv.FormatUint) - pn.DefineGoNativeValue("Quote", strconv.Quote) - pn.DefineGoNativeValue("QuoteToASCII", strconv.QuoteToASCII) - pn.DefineGoNativeValue("CanBackquote", strconv.CanBackquote) - pn.DefineGoNativeValue("IntSize", strconv.IntSize) - pn.DefineGoNativeValue("AppendUint", strconv.AppendUint) case "std": // NOTE: some of these are overridden in tests/imports.go - // Also see stdlibs/InjectPackage. - pn.DefineNative("AssertOriginCall", - gno.Flds( // params - ), - gno.Flds( // results - ), - func(m *gno.Machine) { - isOrigin := len(m.Frames) == 2 - if !isOrigin { - m.Panic(typedString("invalid non-origin call")) - return - } - }, - ) - pn.DefineNative("IsOriginCall", - gno.Flds( // params - ), - gno.Flds( // results - "isOrigin", "bool", - ), - func(m *gno.Machine) { - isOrigin := len(m.Frames) == 2 - res0 := gno.TypedValue{T: gno.BoolType} - res0.SetBool(isOrigin) - m.PushValue(res0) - }, - ) - pn.DefineNative("CurrentRealmPath", - gno.Flds( // params - ), - gno.Flds( // results - "", "string", - ), - func(m *gno.Machine) { - realmPath := "" - if m.Realm != nil { - realmPath = m.Realm.Path - } - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(realmPath), - ) - m.PushValue(res0) - }, - ) - pn.DefineNative("GetChainID", - gno.Flds( // params - ), - gno.Flds( // results - "", "string", - ), - func(m *gno.Machine) { - ctx := m.Context.(ExecContext) - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(ctx.ChainID), - ) - m.PushValue(res0) - }, - ) - pn.DefineNative("GetHeight", - gno.Flds( // params - ), - gno.Flds( // results - "", "int64", - ), - func(m *gno.Machine) { - ctx := m.Context.(ExecContext) - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(ctx.Height), - ) - m.PushValue(res0) - }, - ) pn.DefineNative("GetOrigSend", gno.Flds( // params ), diff --git a/gnovm/stdlibs/strconv/strconv.gno b/gnovm/stdlibs/strconv/strconv.gno index dce62b890e3..600aab905a5 100644 --- a/gnovm/stdlibs/strconv/strconv.gno +++ b/gnovm/stdlibs/strconv/strconv.gno @@ -1,4 +1,12 @@ package strconv -// NOTE: currently these are implemented as native functions. -// See InjectNatives(). +// All injected + +func Itoa(n int) string +func AppendUint(dst []byte, i uint64, base int) []byte +func Atoi(s string) (int, error) +func CanBackquote(s string) bool +func FormatInt(i int64, base int) string +func FormatUint(i uint64, base int) string +func Quote(s string) string +func QuoteToASCII(s string) string diff --git a/gnovm/stdlibs/strconv/strconv.go b/gnovm/stdlibs/strconv/strconv.go new file mode 100644 index 00000000000..782a63e84b6 --- /dev/null +++ b/gnovm/stdlibs/strconv/strconv.go @@ -0,0 +1,12 @@ +package strconv + +import "strconv" + +func Itoa(n int) string { return strconv.Itoa(n) } +func AppendUint(dst []byte, i uint64, base int) []byte { return strconv.AppendUint(dst, i, base) } +func Atoi(s string) (int, error) { return strconv.Atoi(s) } +func CanBackquote(s string) bool { return strconv.CanBackquote(s) } +func FormatInt(i int64, base int) string { return strconv.FormatInt(i, base) } +func FormatUint(i uint64, base int) string { return strconv.FormatUint(i, base) } +func Quote(s string) string { return strconv.Quote(s) } +func QuoteToASCII(r string) string { return strconv.QuoteToASCII(r) } diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index dfae44b07b9..aa81f972501 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -1071,7 +1071,6 @@ func daysSinceEpoch(year int) uint64 { func now() (sec int64, nsec int32, mono int64) -// XXX SHIM // runtimeNano returns the current value of the runtime clock in nanoseconds. // //go:linkname runtimeNano runtime.nanotime diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index daa5f992cda..b9ac247bb73 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -445,7 +445,8 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri iavlStore := iavl.StoreConstructor(db, stypes.StoreOptions{}) store = gno.NewStore(nil, baseStore, iavlStore) store.SetPackageGetter(getPackage) - // store.SetPackageInjector(testPackageInjector) + store.SetNativeStore(stdlibs.NativeStore) + store.SetPackageInjector(testPackageInjector) store.SetStrictGo2GnoMapping(false) // native mappings stdlibs.InjectNativeMappings(store) From 9fc487a849c7bf6f65883a192d5a170646cfe033 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 23 Jun 2023 14:37:33 +0200 Subject: [PATCH 06/59] remove old debug call --- gnovm/pkg/gnolang/op_call.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/gnovm/pkg/gnolang/op_call.go b/gnovm/pkg/gnolang/op_call.go index d8178043813..f43a593c50e 100644 --- a/gnovm/pkg/gnolang/op_call.go +++ b/gnovm/pkg/gnolang/op_call.go @@ -58,9 +58,6 @@ func (m *Machine) doOpCall() { clo := fr.Func.GetClosure(m.Store) b := m.Alloc.NewBlock(fr.Func.GetSource(m.Store), clo) m.PushBlock(b) - if fv.Name == "Now" { - fmt.Printf("CALL NOW %p %#v\n", fv, fv) - } if fv.nativeBody == nil { fbody := fv.GetBodyFromSource(m.Store) if len(ft.Results) == 0 { From da4f9a8ff4cfad0777b64927a91ded204069c568 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 7 Jul 2023 15:55:35 +0200 Subject: [PATCH 07/59] make AssertOriginCall native gno --- gnovm/stdlibs/std/frame.gno | 6 ++++++ gnovm/stdlibs/std/native.gno | 1 - gnovm/stdlibs/std/native.go | 11 ----------- gnovm/tests/imports.go | 7 +++---- 4 files changed, 9 insertions(+), 16 deletions(-) diff --git a/gnovm/stdlibs/std/frame.gno b/gnovm/stdlibs/std/frame.gno index bc3a000f5a0..4bf9ce80720 100644 --- a/gnovm/stdlibs/std/frame.gno +++ b/gnovm/stdlibs/std/frame.gno @@ -16,3 +16,9 @@ func (r Realm) PkgPath() string { func (r Realm) IsUser() bool { return r.pkgPath == "" } + +func AssertOriginCall() { + if !IsOriginCall() { + panic("invalid non-origin call") + } +} diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index e2e73209142..32d7f24c190 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -1,6 +1,5 @@ package std -func AssertOriginCall() // injected func IsOriginCall() bool // injected func CurrentRealmPath() string // injected func GetChainID() string // injected diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index 149d333e4cb..d42b680ca79 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -1,20 +1,9 @@ package std import ( - "fmt" - gno "github.com/gnolang/gno/gnovm/pkg/gnolang" ) -func AssertOriginCall(m *gno.Machine) { - fmt.Println("AssertOriginCall -- ", len(m.Frames)) - isOrigin := len(m.Frames) == 2 - if !isOrigin { - m.Panic(typedString("invalid non-origin call")) - return - } -} - func IsOriginCall(m *gno.Machine) bool { return len(m.Frames) == 2 } diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index b9ac247bb73..1955d82398d 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -453,10 +453,9 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri return } -//---------------------------------------- -// testInjectNatives -// analogous to stdlibs.InjectNatives, but with -// native methods suitable for the testing environment. +func testNativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { + return stdlibs.NativeStore(pkgPath, name) +} func testPackageInjector(store gno.Store, pn *gno.PackageNode) { // Also inject stdlibs native functions. From 9edecac94bc509da1aeb46237766ca4f8be8a226 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 7 Jul 2023 16:07:20 +0200 Subject: [PATCH 08/59] more permanent location and name for stdgen --- gnovm/stdlibs/native.go | 17 ++--------------- gnovm/stdlibs/stdlibs.go | 2 +- .../stdgen/stdgen.go => misc/genstd/genstd.go | 15 ++++++++++++--- .../stdgen => misc/genstd}/template.tmpl | 5 ++--- 4 files changed, 17 insertions(+), 22 deletions(-) rename gnovm/stdlibs/internal/stdgen/stdgen.go => misc/genstd/genstd.go (96%) rename {gnovm/stdlibs/internal/stdgen => misc/genstd}/template.tmpl (92%) diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go index 208f10a4838..3141ffcc9b9 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/native.go @@ -1,6 +1,5 @@ -// This file is autogenerated; do not edit. -// To regenerate it, run `go generate` or `go run ./internal/stdgen` -// from @/stdlibs. +// This file is autogenerated from the genstd tool (@/misc/stdgen); do not edit. +// To regenerate it, run `go generate` from @/stdlibs. package stdlibs @@ -156,18 +155,6 @@ var nativeFuncs = [...]nativeFunc{ )) }, }, - { - "std", - "AssertOriginCall", - - []gno.FieldTypeExpr{}, - []gno.FieldTypeExpr{}, - func(m *gno.Machine) { - lib2.AssertOriginCall( - m, - ) - }, - }, { "std", "IsOriginCall", diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index 666d1be3a67..ba656a95884 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -1,6 +1,6 @@ package stdlibs -//go:generate go run ./internal/stdgen +//go:generate go run github.com/gnolang/gno/misc/genstd import ( "reflect" diff --git a/gnovm/stdlibs/internal/stdgen/stdgen.go b/misc/genstd/genstd.go similarity index 96% rename from gnovm/stdlibs/internal/stdgen/stdgen.go rename to misc/genstd/genstd.go index 75a71d39099..fb121d373e2 100644 --- a/gnovm/stdlibs/internal/stdgen/stdgen.go +++ b/misc/genstd/genstd.go @@ -1,4 +1,4 @@ -// Command stdgen provides static code generation for standard library native +// Command genstd provides static code generation for standard library native // bindings. package main @@ -15,10 +15,16 @@ import ( "strconv" "strings" "text/template" + + _ "embed" ) func main() { - if err := _main("."); err != nil { + path := "." + if len(os.Args) > 1 { + path = os.Args[1] + } + if err := _main(path); err != nil { fmt.Fprintf(os.Stderr, "%+v\n", err) os.Exit(1) } @@ -371,7 +377,10 @@ type tplData struct { LibNums []string } -var tpl = template.Must(template.ParseFiles("./internal/stdgen/template.tmpl")) +//go:embed template.tmpl +var templateText string + +var tpl = template.Must(template.New("").Parse(templateText)) func (t tplData) FindLibNum(s string) (int, error) { for i, v := range t.LibNums { diff --git a/gnovm/stdlibs/internal/stdgen/template.tmpl b/misc/genstd/template.tmpl similarity index 92% rename from gnovm/stdlibs/internal/stdgen/template.tmpl rename to misc/genstd/template.tmpl index 7fc5bb2846c..10d47ecfc54 100644 --- a/gnovm/stdlibs/internal/stdgen/template.tmpl +++ b/misc/genstd/template.tmpl @@ -1,6 +1,5 @@ -// This file is autogenerated; do not edit. -// To regenerate it, run `go generate` or `go run ./internal/stdgen` -// from @/stdlibs. +// This file is autogenerated from the genstd tool (@/misc/stdgen); do not edit. +// To regenerate it, run `go generate` from @/stdlibs. package stdlibs From c4094d22c45b4639c37cec473a748fa57eac6c48 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 10 Jul 2023 15:39:43 +0200 Subject: [PATCH 09/59] allow overriding std functions in test context --- gnovm/pkg/gnolang/machine.go | 65 +++++++++++ gnovm/pkg/gnolang/nodes.go | 29 +++-- gnovm/stdlibs/native.go | 20 ++++ gnovm/stdlibs/std/frame.gno | 6 - gnovm/stdlibs/std/native.gno | 1 + gnovm/stdlibs/std/native.go | 6 + gnovm/stdlibs/stdlibs.go | 8 -- gnovm/tests/imports.go | 200 ++++++++------------------------ gnovm/tests/stdlibs/README.md | 6 + gnovm/tests/stdlibs/native.go | 106 +++++++++++++++++ gnovm/tests/stdlibs/std/std.gno | 7 ++ gnovm/tests/stdlibs/std/std.go | 68 +++++++++++ gnovm/tests/stdlibs/stdlibs.go | 18 +++ misc/genstd/genstd.go | 82 +++++++++++-- misc/genstd/template.tmpl | 8 ++ 15 files changed, 444 insertions(+), 186 deletions(-) create mode 100644 gnovm/tests/stdlibs/README.md create mode 100644 gnovm/tests/stdlibs/native.go create mode 100644 gnovm/tests/stdlibs/std/std.gno create mode 100644 gnovm/tests/stdlibs/std/std.go create mode 100644 gnovm/tests/stdlibs/stdlibs.go diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index bfd57c52238..f08979c7420 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -189,10 +189,19 @@ func (m *Machine) SetActivePackage(pv *PackageValue) { // Upon restart, preprocess all MemPackage and save blocknodes. // This is a temporary measure until we optimize/make-lazy. +// +// NOTE: package paths not beginning with gno.land will be allowed to override, +// to support cases of stdlibs processed through [RunMemPackagesWithOverrides]. func (m *Machine) PreprocessAllFilesAndSaveBlockNodes() { ch := m.Store.IterMemPackage() for memPkg := range ch { fset := ParseMemPackage(memPkg) + // Implicitly do filterDuplicates for stdlibs. + // This is done as this function is at the time of writing only used + // in tests, ie. where we use the "override" feature for stdlibs. + if !strings.HasPrefix(memPkg.Path, "gno.land/") { + filterDuplicates(fset) + } pn := NewPackageNode(Name(memPkg.Name), memPkg.Path, fset) m.Store.SetBlockNode(pn) PredefineFileSet(m.Store, pn, fset) @@ -223,8 +232,23 @@ func (m *Machine) PreprocessAllFilesAndSaveBlockNodes() { // and corresponding package node, package value, and types to store. Save // is set to false for tests where package values may be native. func (m *Machine) RunMemPackage(memPkg *std.MemPackage, save bool) (*PackageNode, *PackageValue) { + return m.runMemPackage(memPkg, save, false) +} + +// RunMemPackageWithOverrides works as [RunMemPackage], however after parsing, +// declarations are filtered removing duplicate declarations. +// To control which declaration overrides which, use [ReadMemPackageFromList], +// putting the overrides at the top of the list. +func (m *Machine) RunMemPackageWithOverrides(memPkg *std.MemPackage, save bool) (*PackageNode, *PackageValue) { + return m.runMemPackage(memPkg, save, true) +} + +func (m *Machine) runMemPackage(memPkg *std.MemPackage, save, overrides bool) (*PackageNode, *PackageValue) { // parse files. files := ParseMemPackage(memPkg) + if overrides { + filterDuplicates(files) + } // make and set package if doesn't exist. pn := (*PackageNode)(nil) pv := (*PackageValue)(nil) @@ -251,6 +275,47 @@ func (m *Machine) RunMemPackage(memPkg *std.MemPackage, save bool) (*PackageNode return pn, pv } +func filterDuplicates(fset *FileSet) { + defined := make(map[Name]struct{}, 128) + for _, f := range fset.Files { + for i := 0; i < len(f.Decls); i++ { + d := f.Decls[i] + var name Name + switch d := d.(type) { + case *FuncDecl: + name = d.Name + if d.IsMethod { + name = Name(derefStar(d.Recv.Type).String()) + "." + name + } + case *TypeDecl: + name = d.Name + case *ValueDecl: + if len(d.NameExprs) == 1 { + name = d.NameExprs[0].Name + } else { + // TODO: support multiple names + continue + } + default: + continue + } + if _, ok := defined[name]; ok { + f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) + i-- + } else { + defined[name] = struct{}{} + } + } + } +} + +func derefStar(x Expr) Expr { + if x, ok := x.(*StarExpr); ok { + return x.X + } + return x +} + // Tests all test files in a mempackage. // Assumes that the importing of packages is handled elsewhere. // The resulting package value and node become injected with TestMethods and diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index c033162513a..43ce48e1200 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1099,12 +1099,11 @@ func PackageNameFromFileBody(name string, body string) Name { // // NOTE: panics if package name is invalid (characters must be alphanumeric or _, // lowercase, and must start with a letter). -func ReadMemPackage(dir string, pkgPath string) *std.MemPackage { +func ReadMemPackage(dir, pkgPath string) *std.MemPackage { files, err := ioutil.ReadDir(dir) if err != nil { panic(err) } - memPkg := &std.MemPackage{Path: pkgPath} allowedFiles := []string{ // make case insensitive? "gno.mod", "LICENSE", @@ -1113,28 +1112,43 @@ func ReadMemPackage(dir string, pkgPath string) *std.MemPackage { allowedFileExtensions := []string{ ".gno", } - var pkgName Name + list := make([]string, 0, len(files)) for _, file := range files { if file.IsDir() || strings.HasPrefix(file.Name(), ".") || (!endsWith(file.Name(), allowedFileExtensions) && !contains(allowedFiles, file.Name())) { continue } - fpath := filepath.Join(dir, file.Name()) + list = append(list, filepath.Join(dir, file.Name())) + } + return ReadMemPackageFromList(list, pkgPath) +} + +// ReadMemPackageFromList creates a new [std.MemPackage] with the specified pkgPath, +// containing the contents of all the files provided in the list slice. +// No parsing or validation is done on the filenames. +// +// NOTE: panics if package name is invalid (characters must be alphanumeric or _, +// lowercase, and must start with a letter). +func ReadMemPackageFromList(list []string, pkgPath string) *std.MemPackage { + memPkg := &std.MemPackage{Path: pkgPath} + var pkgName Name + for _, fpath := range list { + fname := filepath.Base(fpath) bz, err := os.ReadFile(fpath) if err != nil { panic(err) } // XXX: should check that all pkg names are the same (else package is invalid) - if pkgName == "" && strings.HasSuffix(file.Name(), ".gno") { - pkgName = PackageNameFromFileBody(file.Name(), string(bz)) + if pkgName == "" && strings.HasSuffix(fname, ".gno") { + pkgName = PackageNameFromFileBody(fname, string(bz)) if strings.HasSuffix(string(pkgName), "_test") { pkgName = pkgName[:len(pkgName)-len("_test")] } } memPkg.Files = append(memPkg.Files, &std.MemFile{ - Name: file.Name(), + Name: fname, Body: string(bz), }) } @@ -1382,7 +1396,6 @@ func (x *PackageNode) DefineNative(n Name, ps, rs FieldTypeExprs, native func(*M if !ok { panic("cannot redefine non-function as native function") } - fmt.Printf("REDEFINE %s %p\n", n, fv) // XXX: type-check fv.body = nil fv.nativeBody = native diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go index 3141ffcc9b9..af4462acebe 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/native.go @@ -14,6 +14,14 @@ import ( lib4 "github.com/gnolang/gno/gnovm/stdlibs/time" ) +type nativeFunc struct { + gnoPkg string + gnoFunc gno.Name + params []gno.FieldTypeExpr + results []gno.FieldTypeExpr + f func(m *gno.Machine) +} + var nativeFuncs = [...]nativeFunc{ { "crypto/sha256", @@ -155,6 +163,18 @@ var nativeFuncs = [...]nativeFunc{ )) }, }, + { + "std", + "AssertOriginCall", + + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + lib2.AssertOriginCall( + m, + ) + }, + }, { "std", "IsOriginCall", diff --git a/gnovm/stdlibs/std/frame.gno b/gnovm/stdlibs/std/frame.gno index 4bf9ce80720..bc3a000f5a0 100644 --- a/gnovm/stdlibs/std/frame.gno +++ b/gnovm/stdlibs/std/frame.gno @@ -16,9 +16,3 @@ func (r Realm) PkgPath() string { func (r Realm) IsUser() bool { return r.pkgPath == "" } - -func AssertOriginCall() { - if !IsOriginCall() { - panic("invalid non-origin call") - } -} diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index 32d7f24c190..e2e73209142 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -1,5 +1,6 @@ package std +func AssertOriginCall() // injected func IsOriginCall() bool // injected func CurrentRealmPath() string // injected func GetChainID() string // injected diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index d42b680ca79..6eca8b501ff 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -4,6 +4,12 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" ) +func AssertOriginCall(m *gno.Machine) { + if !IsOriginCall(m) { + m.Panic(typedString("invalid non-origin call")) + } +} + func IsOriginCall(m *gno.Machine) bool { return len(m.Frames) == 2 } diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index ba656a95884..39b6b336176 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -18,14 +18,6 @@ func InjectNativeMappings(store gno.Store) { store.AddGo2GnoMapping(reflect.TypeOf(Realm{}), "std", "Realm") } -type nativeFunc struct { - gnoPkg string - gnoFunc gno.Name - params []gno.FieldTypeExpr - results []gno.FieldTypeExpr - f func(m *gno.Machine) -} - func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { for _, nf := range nativeFuncs { if nf.gnoPkg == pkgPath && name == nf.gnoFunc { diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 1955d82398d..83e5bcf1d4e 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -27,6 +27,7 @@ import ( "math/rand" "net" "net/url" + "os" "path/filepath" "reflect" "sort" @@ -34,13 +35,13 @@ import ( "strings" "sync" "sync/atomic" - "testing" "text/template" "time" "unicode/utf8" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs" + teststdlibs "github.com/gnolang/gno/gnovm/tests/stdlibs" "github.com/gnolang/gno/tm2/pkg/crypto" dbm "github.com/gnolang/gno/tm2/pkg/db" osm "github.com/gnolang/gno/tm2/pkg/os" @@ -52,15 +53,16 @@ import ( type importMode uint64 +// Import modes to control the import behaviour of TestStore. const ( + // use stdlibs/* only (except a few exceptions). for stdlibs/* and examples/* testing. ImportModeStdlibsOnly importMode = iota + // use stdlibs/* if present, otherwise use native. used in files/tests, excluded for *_native.go ImportModeStdlibsPreferred + // do not use stdlibs/* if native registered. used in files/tests, excluded for *_stdlibs.go ImportModeNativePreferred ) -// ImportModeStdlibsOnly: use stdlibs/* only (except a few exceptions). for stdlibs/* and examples/* testing. -// ImportModeStdlibsPreferred: use stdlibs/* if present, otherwise use native. for files/tests2/*. -// ImportModeNativePreferred: do not use stdlibs/* if native registered. for files/tests/*. // NOTE: this isn't safe, should only be used for testing. func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Writer, mode importMode) (store gno.Store) { getPackage := func(pkgPath string) (pn *gno.PackageNode, pv *gno.PackageValue) { @@ -94,19 +96,9 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if stdlibs package is preferred , try to load it first. if mode == ImportModeStdlibsOnly || mode == ImportModeStdlibsPreferred { - stdlibPath := filepath.Join(rootDir, "gnovm", "stdlibs", pkgPath) - if osm.DirExists(stdlibPath) { - memPkg := gno.ReadMemPackage(stdlibPath, pkgPath) - m2 := gno.NewMachineWithOptions(gno.MachineOptions{ - // NOTE: see also pkgs/sdk/vm/builtins.go - // Needs PkgPath != its name because, seeing as we are passing ourselves - // as a store, it would recusively call this function on the same package - PkgPath: "stdlibload", - Output: stdout, - Store: store, - Injector: testPackageInjector, - }) - return m2.RunMemPackage(memPkg, true) + pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) + if pn != nil { + return } } @@ -212,17 +204,6 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkg.DefineGoNativeType(reflect.TypeOf(net.TCPAddr{})) pkg.DefineGoNativeValue("IPv4", net.IPv4) return pkg, pkg.NewPackage() - case "net/http": - // XXX UNSAFE - // There's no reason why we can't replace these with safer alternatives. - panic("just say gno") - /* - pkg := gno.NewPackageNode("http", pkgPath, nil) - pkg.DefineGoNativeType(reflect.TypeOf(http.Request{})) - pkg.DefineGoNativeValue("DefaultClient", http.DefaultClient) - pkg.DefineGoNativeType(reflect.TypeOf(http.Client{})) - return pkg, pkg.NewPackage() - */ case "net/url": pkg := gno.NewPackageNode("url", pkgPath, nil) pkg.DefineGoNativeType(reflect.TypeOf(url.Values{})) @@ -385,25 +366,6 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkg := gno.NewPackageNode("fnv", pkgPath, nil) pkg.DefineGoNativeValue("New32a", fnv.New32a) return pkg, pkg.NewPackage() - /* XXX support somehow for speed. for now, generic implemented in stdlibs. - case "internal/bytealg": - pkg := gno.NewPackageNode("bytealg", pkgPath, nil) - pkg.DefineGoNativeValue("Compare", bytealg.Compare) - pkg.DefineGoNativeValue("CountString", bytealg.CountString) - pkg.DefineGoNativeValue("Cutover", bytealg.Cutover) - pkg.DefineGoNativeValue("Equal", bytealg.Equal) - pkg.DefineGoNativeValue("HashStr", bytealg.HashStr) - pkg.DefineGoNativeValue("HashStrBytes", bytealg.HashStrBytes) - pkg.DefineGoNativeValue("HashStrRev", bytealg.HashStrRev) - pkg.DefineGoNativeValue("HashStrRevBytes", bytealg.HashStrRevBytes) - pkg.DefineGoNativeValue("Index", bytealg.Index) - pkg.DefineGoNativeValue("IndexByte", bytealg.IndexByte) - pkg.DefineGoNativeValue("IndexByteString", bytealg.IndexByteString) - pkg.DefineGoNativeValue("IndexRabinKarp", bytealg.IndexRabinKarp) - pkg.DefineGoNativeValue("IndexRabinKarpBytes", bytealg.IndexRabinKarpBytes) - pkg.DefineGoNativeValue("IndexString", bytealg.IndexString) - return pkg, pkg.NewPackage() - */ default: // continue on... } @@ -411,16 +373,8 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if native package is preferred, try to load stdlibs/* as backup. if mode == ImportModeNativePreferred { - stdlibPath := filepath.Join(rootDir, "gnovm", "stdlibs", pkgPath) - if osm.DirExists(stdlibPath) { - memPkg := gno.ReadMemPackage(stdlibPath, pkgPath) - m2 := gno.NewMachineWithOptions(gno.MachineOptions{ - PkgPath: "test", - Output: stdout, - Store: store, - Injector: testPackageInjector, - }) - pn, pv = m2.RunMemPackage(memPkg, true) + pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) + if pn != nil { return } } @@ -445,7 +399,7 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri iavlStore := iavl.StoreConstructor(db, stypes.StoreOptions{}) store = gno.NewStore(nil, baseStore, iavlStore) store.SetPackageGetter(getPackage) - store.SetNativeStore(stdlibs.NativeStore) + store.SetNativeStore(teststdlibs.NativeStore) store.SetPackageInjector(testPackageInjector) store.SetStrictGo2GnoMapping(false) // native mappings @@ -453,29 +407,46 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri return } -func testNativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { - return stdlibs.NativeStore(pkgPath, name) +func loadStdlib(rootDir, pkgPath string, store gno.Store, stdout io.Writer) (*gno.PackageNode, *gno.PackageValue) { + dirs := [...]string{ + // override path. definitions here should take precedence. + filepath.Join(rootDir, "gnovm", "tests", "stdlibs", pkgPath), + // normal stdlib path. + filepath.Join(rootDir, "gnovm", "stdlibs", pkgPath), + } + files := make([]string, 0, 32) // pre-alloc 32 as a likely high number of files + for _, path := range dirs { + if dl, err := os.ReadDir(path); err == nil { + for _, f := range dl { + // NOTE: RunMemPackage has other rules; those should be mostly useful + // for on-chain packages (ie. include README and gno.mod). + if !f.IsDir() && strings.HasSuffix(f.Name(), ".gno") { + files = append(files, filepath.Join(path, f.Name())) + } + } + } else if !os.IsNotExist(err) { + panic(fmt.Errorf("could not access dir %q: %w", path, err)) + } + } + if len(files) == 0 { + return nil, nil + } + memPkg := gno.ReadMemPackageFromList(files, pkgPath) + m2 := gno.NewMachineWithOptions(gno.MachineOptions{ + // NOTE: see also pkgs/sdk/vm/builtins.go + // Needs PkgPath != its name because TestStore.getPackage is the package + // getter for the store, which calls loadStdlib, so it would be recursively called. + PkgPath: "stdlibload", + Output: stdout, + Store: store, + Injector: testPackageInjector, + }) + return m2.RunMemPackageWithOverrides(memPkg, true) } func testPackageInjector(store gno.Store, pn *gno.PackageNode) { // Also inject stdlibs native functions. stdlibs.InjectPackage(store, pn) - isOriginCall := func(m *gno.Machine) bool { - tname := m.Frames[0].Func.Name - switch tname { - case "main": // test is a _filetest - return len(m.Frames) == 3 - case "runtest": // test is a _test - return len(m.Frames) == 7 - } - // support init() in _filetest - // XXX do we need to distinguish from 'runtest'/_test? - // XXX pretty hacky even if not. - if strings.HasPrefix(string(tname), "init.") { - return len(m.Frames) == 3 - } - panic("unable to determine if test is a _test or a _filetest") - } // Test specific injections: switch pn.PkgPath { case "strconv": @@ -486,34 +457,6 @@ func testPackageInjector(store gno.Store, pn *gno.PackageNode) { case "std": // NOTE: some of these are overrides. // Also see stdlibs/InjectPackage. - pn.DefineNativeOverride("AssertOriginCall", - /* - gno.Flds( // params - ), - gno.Flds( // results - ), - */ - func(m *gno.Machine) { - if !isOriginCall(m) { - m.Panic(typedString("invalid non-origin call")) - return - } - }, - ) - pn.DefineNativeOverride("IsOriginCall", - /* - gno.Flds( // params - ), - gno.Flds( // results - "isOrigin", "bool", - ), - */ - func(m *gno.Machine) { - res0 := gno.TypedValue{T: gno.BoolType} - res0.SetBool(isOriginCall(m)) - m.PushValue(res0) - }, - ) pn.DefineNativeOverride("GetCallerAt", /* gno.Flds( // params @@ -631,55 +574,6 @@ func testPackageInjector(store gno.Store, pn *gno.PackageNode) { } }, ) - pn.DefineNative("TestCurrentRealm", - gno.Flds( // params - ), - gno.Flds( // results - "realm", "string", - ), - func(m *gno.Machine) { - rlmpath := m.Realm.Path - m.PushValue(typedString(rlmpath)) - }, - ) - pn.DefineNative("TestSkipHeights", - gno.Flds( // params - "count", "int64", - ), - gno.Flds( // results - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - count := arg0.GetInt64() - - ctx := m.Context.(stdlibs.ExecContext) - ctx.Height += count - m.Context = ctx - }, - ) - // TODO: move elsewhere. - pn.DefineNative("ClearStoreCache", - gno.Flds( // params - ), - gno.Flds( // results - ), - func(m *gno.Machine) { - if gno.IsDebug() && testing.Verbose() { - store.Print() - fmt.Println("========================================") - fmt.Println("CLEAR CACHE (RUNTIME)") - fmt.Println("========================================") - } - m.Store.ClearCache() - m.PreprocessAllFilesAndSaveBlockNodes() - if gno.IsDebug() && testing.Verbose() { - store.Print() - fmt.Println("========================================") - fmt.Println("CLEAR CACHE DONE") - fmt.Println("========================================") - } - }, - ) } } diff --git a/gnovm/tests/stdlibs/README.md b/gnovm/tests/stdlibs/README.md new file mode 100644 index 00000000000..16d5d171342 --- /dev/null +++ b/gnovm/tests/stdlibs/README.md @@ -0,0 +1,6 @@ +# tests/stdlibs + +This directory contains test-specific standard libraries. These are only +available when testing gno code in `_test.gno` and `_filetest.gno` files. +Re-declarations of functions already existing override the definitions of the +normal stdlibs directory. diff --git a/gnovm/tests/stdlibs/native.go b/gnovm/tests/stdlibs/native.go new file mode 100644 index 00000000000..4b4ba46280a --- /dev/null +++ b/gnovm/tests/stdlibs/native.go @@ -0,0 +1,106 @@ +// This file is autogenerated from the genstd tool (@/misc/stdgen); do not edit. +// To regenerate it, run `go generate` from @/stdlibs. + +package stdlibs + +import ( + "reflect" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + lib0 "github.com/gnolang/gno/gnovm/tests/stdlibs/std" +) + +type nativeFunc struct { + gnoPkg string + gnoFunc gno.Name + params []gno.FieldTypeExpr + results []gno.FieldTypeExpr + f func(m *gno.Machine) +} + +var nativeFuncs = [...]nativeFunc{ + { + "std", + "AssertOriginCall", + + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + lib0.AssertOriginCall( + m, + ) + }, + }, + { + "std", + "IsOriginCall", + + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("bool")}, + }, + func(m *gno.Machine) { + r0 := lib0.IsOriginCall( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "TestCurrentRealm", + + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + }, + func(m *gno.Machine) { + r0 := lib0.TestCurrentRealm( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "TestSkipHeights", + + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("int64")}, + }, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 int64 + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + lib0.TestSkipHeights(p0) + }, + }, + { + "std", + "ClearStoreCache", + + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + lib0.ClearStoreCache( + m, + ) + }, + }, +} diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno new file mode 100644 index 00000000000..9546ed984c1 --- /dev/null +++ b/gnovm/tests/stdlibs/std/std.gno @@ -0,0 +1,7 @@ +package std + +func AssertOriginCall() // injected +func IsOriginCall() bool // injected +func TestCurrentRealm() string // injected +func TestSkipHeights(count int64) // injected +func ClearStoreCache() // injected diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go new file mode 100644 index 00000000000..b36affd52ce --- /dev/null +++ b/gnovm/tests/stdlibs/std/std.go @@ -0,0 +1,68 @@ +package std + +import ( + "fmt" + "strings" + "testing" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +func AssertOriginCall(m *gno.Machine) { + if !IsOriginCall(m) { + m.Panic(typedString("invalid non-origin call")) + } +} + +func typedString(s gno.StringValue) gno.TypedValue { + tv := gno.TypedValue{T: gno.StringType} + tv.SetString(s) + return tv +} + +func IsOriginCall(m *gno.Machine) bool { + tname := m.Frames[0].Func.Name + switch tname { + case "main": // test is a _filetest + return len(m.Frames) == 3 + case "runtest": // test is a _test + return len(m.Frames) == 7 + } + // support init() in _filetest + // XXX do we need to distinguish from 'runtest'/_test? + // XXX pretty hacky even if not. + if strings.HasPrefix(string(tname), "init.") { + return len(m.Frames) == 3 + } + panic("unable to determine if test is a _test or a _filetest") +} + +func TestCurrentRealm(m *gno.Machine) string { + return m.Realm.Path +} + +func TestSkipHeights(count int64) { + panic("not implemented") + /* + ctx := m.Context.(stdlibs.ExecContext) + ctx.Height += count + m.Context = ctx + */ +} + +func ClearStoreCache(m *gno.Machine) { + if gno.IsDebug() && testing.Verbose() { + m.Store.Print() + fmt.Println("========================================") + fmt.Println("CLEAR CACHE (RUNTIME)") + fmt.Println("========================================") + } + m.Store.ClearCache() + m.PreprocessAllFilesAndSaveBlockNodes() + if gno.IsDebug() && testing.Verbose() { + m.Store.Print() + fmt.Println("========================================") + fmt.Println("CLEAR CACHE DONE") + fmt.Println("========================================") + } +} diff --git a/gnovm/tests/stdlibs/stdlibs.go b/gnovm/tests/stdlibs/stdlibs.go new file mode 100644 index 00000000000..b0a1050af41 --- /dev/null +++ b/gnovm/tests/stdlibs/stdlibs.go @@ -0,0 +1,18 @@ +// Package stdlibs provides supplemental stdlibs for the testing environment. +package stdlibs + +import ( + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/stdlibs" +) + +//go:generate go run github.com/gnolang/gno/misc/genstd + +func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { + for _, nf := range nativeFuncs { + if nf.gnoPkg == pkgPath && name == nf.gnoFunc { + return nf.f + } + } + return stdlibs.NativeStore(pkgPath, name) +} diff --git a/misc/genstd/genstd.go b/misc/genstd/genstd.go index fb121d373e2..3b39f6804b6 100644 --- a/misc/genstd/genstd.go +++ b/misc/genstd/genstd.go @@ -3,6 +3,7 @@ package main import ( + "errors" "fmt" "go/ast" "go/parser" @@ -14,6 +15,7 @@ import ( "reflect" "strconv" "strings" + "sync" "text/template" _ "embed" @@ -73,16 +75,7 @@ func _main(stdlibsPath string) error { return err } - cmd := exec.Command( - "go", "run", "-modfile", "../../misc/devdeps/go.mod", - "mvdan.cc/gofumpt", "-w", "native.go", - ) - _, err = cmd.Output() - if err != nil { - return fmt.Errorf("error executing gofumpt: %w", err) - } - - return nil + return runGofumpt() } type pkgData struct { @@ -237,7 +230,7 @@ func linkFunctions(pkgs []*pkgData) []mapping { mp := mapping{ GnoImportPath: pkg.importPath, GnoMethod: gb.Name.Name, - GoImportPath: "github.com/gnolang/gno/gnovm/stdlibs/" + pkg.importPath, + GoImportPath: "github.com/gnolang/gno/" + relPath() + "/" + pkg.importPath, GoFunc: fn.Name.Name, } if !mp.loadSignaturesMatch(gb, fn) { @@ -400,3 +393,70 @@ func (t *tplData) generateLibnums() { } } } + +func runGofumpt() error { + gr := gitRoot() + + cmd := exec.Command( + "go", "run", "-modfile", filepath.Join(gr, "misc/devdeps/go.mod"), + "mvdan.cc/gofumpt", "-w", "native.go", + ) + _, err := cmd.Output() + if err != nil { + if err, ok := err.(*exec.ExitError); ok { + return fmt.Errorf("error executing gofumpt: %w; output: %v", err, string(err.Stderr)) + } + return fmt.Errorf("error executing gofumpt: %w", err) + } + return nil +} + +var ( + memoGitRoot string + memoRelPath string + + dirsOnce sync.Once +) + +func gitRoot() string { + dirsOnceDo() + return memoGitRoot +} + +func relPath() string { + dirsOnceDo() + return memoRelPath +} + +func dirsOnceDo() { + dirsOnce.Do(func() { + var err error + memoGitRoot, memoRelPath, err = findDirs() + if err != nil { + panic(fmt.Errorf("could not determine git root: %w", err)) + } + }) +} + +func findDirs() (gitRoot string, relPath string, err error) { + abs, err := filepath.Abs(".") + if err != nil { + return + } + p := abs + for { + if s, e := os.Stat(filepath.Join(p, ".git")); e == nil && s.IsDir() { + // make relPath relative to the git root + rp := strings.TrimPrefix(abs, p+string(filepath.Separator)) + // normalize separator to / + rp = strings.ReplaceAll(rp, string(filepath.Separator), "/") + return p, rp, nil + } + + if strings.HasSuffix(p, string(filepath.Separator)) { + return "", "", errors.New("root git not found") + } + + p = filepath.Dir(p) + } +} diff --git a/misc/genstd/template.tmpl b/misc/genstd/template.tmpl index 10d47ecfc54..9505196fef4 100644 --- a/misc/genstd/template.tmpl +++ b/misc/genstd/template.tmpl @@ -12,6 +12,14 @@ import ( {{- end }} ) +type nativeFunc struct { + gnoPkg string + gnoFunc gno.Name + params []gno.FieldTypeExpr + results []gno.FieldTypeExpr + f func(m *gno.Machine) +} + var nativeFuncs = [...]nativeFunc{ {{- range $i, $m := .Mappings }} { From bc2d5580618d4ea5a0a63ed5ddeafa6c9514bfda Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 10 Jul 2023 16:59:51 +0200 Subject: [PATCH 10/59] implement SkipHeights, add NativeStore elsewhere --- gnovm/stdlibs/context.go | 10 ++++++++++ gnovm/tests/stdlibs/native.go | 4 +++- gnovm/tests/stdlibs/std/std.go | 12 +++++------- tm2/pkg/sdk/vm/builtins.go | 1 + 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/gnovm/stdlibs/context.go b/gnovm/stdlibs/context.go index aa1853f22fc..6fadcb1fd4e 100644 --- a/gnovm/stdlibs/context.go +++ b/gnovm/stdlibs/context.go @@ -19,7 +19,17 @@ type ExecContext struct { Banker Banker } +// XXX: migration from InjectStdlibs to NativeStore meant that stdlibs can +// no longer directly access ExecContext, as it would create a circular +// dependency; hence the helper functions, so access can still be done through +// interface type assertions. +// These probably need to have a better place. + func (e ExecContext) GetTimestamp() int64 { return e.Timestamp } func (e ExecContext) GetTimestampNano() int64 { return e.TimestampNano } func (e ExecContext) GetChainID() string { return e.ChainID } func (e ExecContext) GetHeight() int64 { return e.Height } +func (e ExecContext) SkipHeights(i int64) any { + e.Height += i + return e +} diff --git a/gnovm/tests/stdlibs/native.go b/gnovm/tests/stdlibs/native.go index 4b4ba46280a..1f94a4dd4af 100644 --- a/gnovm/tests/stdlibs/native.go +++ b/gnovm/tests/stdlibs/native.go @@ -88,7 +88,9 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - lib0.TestSkipHeights(p0) + lib0.TestSkipHeights( + m, + p0) }, }, { diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go index b36affd52ce..8016e367fd9 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -41,13 +41,11 @@ func TestCurrentRealm(m *gno.Machine) string { return m.Realm.Path } -func TestSkipHeights(count int64) { - panic("not implemented") - /* - ctx := m.Context.(stdlibs.ExecContext) - ctx.Height += count - m.Context = ctx - */ +func TestSkipHeights(m *gno.Machine, count int64) { + ctx := m.Context.(interface { + SkipHeights(i int64) any + }) + m.Context = ctx.SkipHeights(count) } func ClearStoreCache(m *gno.Machine) { diff --git a/tm2/pkg/sdk/vm/builtins.go b/tm2/pkg/sdk/vm/builtins.go index c40f3dadc74..ce14ca42659 100644 --- a/tm2/pkg/sdk/vm/builtins.go +++ b/tm2/pkg/sdk/vm/builtins.go @@ -36,6 +36,7 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { } store.SetPackageGetter(getPackage) store.SetPackageInjector(stdlibs.InjectPackage) + store.SetNativeStore(stdlibs.NativeStore) stdlibs.InjectNativeMappings(store) } From d27a7ca4974a8a09fe86a1b8c3e549e0596b81e1 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 12 Jul 2023 15:27:14 +0200 Subject: [PATCH 11/59] fix native bindings in realms --- gnovm/pkg/gnolang/op_call.go | 4 ++++ gnovm/pkg/gnolang/op_eval.go | 2 +- gnovm/pkg/gnolang/op_expressions.go | 8 ++++++-- gnovm/pkg/gnolang/preprocess.go | 2 ++ gnovm/pkg/gnolang/realm.go | 6 ++---- gnovm/pkg/gnolang/values.go | 24 +++++++++++----------- gnovm/tests/files/zrealm0.gno | 2 ++ gnovm/tests/files/zrealm1.gno | 2 ++ gnovm/tests/files/zrealm2.gno | 4 ++++ gnovm/tests/files/zrealm3.gno | 4 ++++ gnovm/tests/files/zrealm4.gno | 4 ++++ gnovm/tests/files/zrealm5.gno | 4 ++++ gnovm/tests/files/zrealm6.gno | 4 ++++ gnovm/tests/files/zrealm7.gno | 4 ++++ gnovm/tests/files/zrealm_avl0.gno | 4 ++++ gnovm/tests/files/zrealm_avl1.gno | 4 ++++ gnovm/tests/files/zrealm_tests0.gno | 32 +++++++++++++++++++++++++++++ gnovm/tests/imports.go | 4 ++-- 18 files changed, 97 insertions(+), 21 deletions(-) diff --git a/gnovm/pkg/gnolang/op_call.go b/gnovm/pkg/gnolang/op_call.go index f43a593c50e..110d301652e 100644 --- a/gnovm/pkg/gnolang/op_call.go +++ b/gnovm/pkg/gnolang/op_call.go @@ -58,6 +58,10 @@ func (m *Machine) doOpCall() { clo := fr.Func.GetClosure(m.Store) b := m.Alloc.NewBlock(fr.Func.GetSource(m.Store), clo) m.PushBlock(b) + if fv.nativeBody == nil && fv.NativePkg != "" { + // native function, unmarshaled so doesn't have nativeBody yet + fv.nativeBody = m.Store.GetNative(fv.NativePkg, fv.NativeName) + } if fv.nativeBody == nil { fbody := fv.GetBodyFromSource(m.Store) if len(ft.Results) == 0 { diff --git a/gnovm/pkg/gnolang/op_eval.go b/gnovm/pkg/gnolang/op_eval.go index 83dd3737b6b..7df144b1deb 100644 --- a/gnovm/pkg/gnolang/op_eval.go +++ b/gnovm/pkg/gnolang/op_eval.go @@ -15,7 +15,7 @@ import ( func (m *Machine) doOpEval() { x := m.PeekExpr(1) if debug { - debug.Printf("EVAL: %v\n", x) + debug.Printf("EVAL: (%T) %v\n", x, x) // fmt.Println(m.String()) } // This case moved out of switch for performance. diff --git a/gnovm/pkg/gnolang/op_expressions.go b/gnovm/pkg/gnolang/op_expressions.go index b46e343b00e..b3bf240aea1 100644 --- a/gnovm/pkg/gnolang/op_expressions.go +++ b/gnovm/pkg/gnolang/op_expressions.go @@ -78,8 +78,12 @@ func (m *Machine) doOpIndex2() { func (m *Machine) doOpSelector() { sx := m.PopExpr().(*SelectorExpr) xv := m.PeekValue(1) - res := xv.GetPointerTo(m.Alloc, m.Store, sx.Path) - *xv = res.Deref() // reuse as result + res := xv.GetPointerTo(m.Alloc, m.Store, sx.Path).Deref() + if debug { + m.Printf("-v[S] %v\n", xv) + m.Printf("+v[S] %v\n", res) + } + *xv = res // reuse as result } func (m *Machine) doOpSlice() { diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 9e392cf8e8f..44457ad0cb0 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -3140,6 +3140,8 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { if fv.nativeBody == nil { panic(fmt.Sprintf("function %s does not have a body but is not natively defined", d.Name)) } + fv.NativePkg = pkg.PkgPath + fv.NativeName = d.Name } pkg.Define(d.Name, TypedValue{ T: ft, diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index a4ddb17835f..f14c13b774b 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -1125,9 +1125,6 @@ func copyValueWithRefs(parent Object, val Value) Value { if cv.Closure != nil { closure = toRefValue(parent, cv.Closure) } - /*if cv.nativeBody != nil { - panic("should not happen") - }*/ ft := copyTypeWithRefs(cv.Type) return &FuncValue{ Type: ft, @@ -1137,7 +1134,8 @@ func copyValueWithRefs(parent Object, val Value) Value { Closure: closure, FileName: cv.FileName, PkgPath: cv.PkgPath, - nativeBody: cv.nativeBody, + NativePkg: cv.NativePkg, + NativeName: cv.NativeName, } case *BoundMethodValue: fnc := copyValueWithRefs(cv, cv.Func).(*FuncValue) diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 495fcf74cb7..04dc88541c4 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -517,13 +517,15 @@ func (sv *StructValue) Copy(alloc *Allocator) *StructValue { // makes construction TypedValue{T:*FuncType{},V:*FuncValue{}} // faster. type FuncValue struct { - Type Type // includes unbound receiver(s) - IsMethod bool // is an (unbound) method - Source BlockNode // for block mem allocation - Name Name // name of function/method - Closure Value // *Block or RefValue to closure (may be nil for file blocks; lazy) - FileName Name // file name where declared - PkgPath string + Type Type // includes unbound receiver(s) + IsMethod bool // is an (unbound) method + Source BlockNode // for block mem allocation + Name Name // name of function/method + Closure Value // *Block or RefValue to closure (may be nil for file blocks; lazy) + FileName Name // file name where declared + PkgPath string + NativePkg string // for native bindings through NativeStore + NativeName Name // not redundant with Name; this cannot be changed in userspace body []Stmt // function body nativeBody func(*Machine) // alternative to Body @@ -539,6 +541,8 @@ func (fv *FuncValue) Copy(alloc *Allocator) *FuncValue { Closure: fv.Closure, FileName: fv.FileName, PkgPath: fv.PkgPath, + NativePkg: fv.NativePkg, + NativeName: fv.NativeName, body: fv.body, nativeBody: fv.nativeBody, } @@ -2309,12 +2313,8 @@ func (b *Block) GetPointerTo(store Store, path ValuePath) PointerValue { // the generation for uverse is 0. If path.Depth is // 0, it implies that b == uverse, and the condition // would fail as if it were 1. - i := uint8(1) -LOOP: - if i < path.Depth { + for i := uint8(1); i < path.Depth; i++ { b = b.GetParent(store) - i++ - goto LOOP } return b.GetPointerToInt(store, int(path.Index)) } diff --git a/gnovm/tests/files/zrealm0.gno b/gnovm/tests/files/zrealm0.gno index 1b8f37540b3..7578781e503 100644 --- a/gnovm/tests/files/zrealm0.gno +++ b/gnovm/tests/files/zrealm0.gno @@ -56,6 +56,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm1.gno b/gnovm/tests/files/zrealm1.gno index 1dea983a49d..d90c5e8621a 100644 --- a/gnovm/tests/files/zrealm1.gno +++ b/gnovm/tests/files/zrealm1.gno @@ -170,6 +170,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm2.gno b/gnovm/tests/files/zrealm2.gno index bf321c42d31..67ba2f5a768 100644 --- a/gnovm/tests/files/zrealm2.gno +++ b/gnovm/tests/files/zrealm2.gno @@ -173,6 +173,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.3", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -207,6 +209,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm3.gno b/gnovm/tests/files/zrealm3.gno index 4ff8dd1a531..da8a581375c 100644 --- a/gnovm/tests/files/zrealm3.gno +++ b/gnovm/tests/files/zrealm3.gno @@ -172,6 +172,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.2", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -206,6 +208,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm4.gno b/gnovm/tests/files/zrealm4.gno index 2e2fa4e8d09..dc3c48c774b 100644 --- a/gnovm/tests/files/zrealm4.gno +++ b/gnovm/tests/files/zrealm4.gno @@ -114,6 +114,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -148,6 +150,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm5.gno b/gnovm/tests/files/zrealm5.gno index 8ad3e7400b3..e65b089c18d 100644 --- a/gnovm/tests/files/zrealm5.gno +++ b/gnovm/tests/files/zrealm5.gno @@ -185,6 +185,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -219,6 +221,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm6.gno b/gnovm/tests/files/zrealm6.gno index fbe320ad962..20615fa7d39 100644 --- a/gnovm/tests/files/zrealm6.gno +++ b/gnovm/tests/files/zrealm6.gno @@ -257,6 +257,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -291,6 +293,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm7.gno b/gnovm/tests/files/zrealm7.gno index 689d55d3916..9decb0dae10 100644 --- a/gnovm/tests/files/zrealm7.gno +++ b/gnovm/tests/files/zrealm7.gno @@ -329,6 +329,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -363,6 +365,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm_avl0.gno b/gnovm/tests/files/zrealm_avl0.gno index 814e19d6d49..e91788ac8eb 100644 --- a/gnovm/tests/files/zrealm_avl0.gno +++ b/gnovm/tests/files/zrealm_avl0.gno @@ -267,6 +267,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -301,6 +303,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm_avl1.gno b/gnovm/tests/files/zrealm_avl1.gno index 410e9e93601..cdd56a5ad89 100644 --- a/gnovm/tests/files/zrealm_avl1.gno +++ b/gnovm/tests/files/zrealm_avl1.gno @@ -291,6 +291,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -325,6 +327,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/files/zrealm_tests0.gno b/gnovm/tests/files/zrealm_tests0.gno index bc07acea1b4..f121e7587f2 100644 --- a/gnovm/tests/files/zrealm_tests0.gno +++ b/gnovm/tests/files/zrealm_tests0.gno @@ -239,6 +239,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": true, // "Name": "Modify", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -295,6 +297,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": true, // "Name": "Modify", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -399,6 +403,8 @@ func main() { // "FileName": "interfaces.gno", // "IsMethod": false, // "Name": "AddStringer", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -463,6 +469,8 @@ func main() { // "FileName": "interfaces.gno", // "IsMethod": false, // "Name": "Render", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -517,6 +525,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "IncCounter", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -561,6 +571,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "Counter", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -615,6 +627,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "CurrentRealmPath", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -659,6 +673,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "AssertOriginCall", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -703,6 +719,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "IsOriginCall", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -760,6 +778,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "ModifyTestRealmObject", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -807,6 +827,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "InitTestNodes", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -841,6 +863,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "ModTestNodes", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -875,6 +899,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "PrintTestNodes", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -919,6 +945,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "GetPrevRealm", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -973,6 +1001,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "GetPSubtestsPrevRealm", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", @@ -1028,6 +1058,8 @@ func main() { // "FileName": "tests.gno", // "IsMethod": false, // "Name": "Exec", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/demo/tests", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 83e5bcf1d4e..b7dbb781c3d 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -109,12 +109,10 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkgPath == "crypto/rand" || pkgPath == "crypto/md5" || pkgPath == "crypto/sha1" || - pkgPath == "encoding/base64" || pkgPath == "encoding/binary" || pkgPath == "encoding/json" || pkgPath == "encoding/xml" || pkgPath == "internal/os_test" || - pkgPath == "math" || pkgPath == "math/big" || pkgPath == "math/rand" || mode == ImportModeStdlibsPreferred || @@ -255,6 +253,8 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri pkg.DefineGoNativeValue("Abs", math.Abs) pkg.DefineGoNativeValue("Cos", math.Cos) pkg.DefineGoNativeValue("Pi", math.Pi) + pkg.DefineGoNativeValue("Float64bits", math.Float64bits) + pkg.DefineGoNativeValue("Pi", math.Pi) pkg.DefineGoNativeValue("MaxFloat32", math.MaxFloat32) pkg.DefineGoNativeValue("MaxFloat64", math.MaxFloat64) return pkg, pkg.NewPackage() From 0f54a0c566ddd03497cb51aa7aff6d00b010b662 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 12 Jul 2023 15:38:18 +0200 Subject: [PATCH 12/59] fix low-hanging failing tests --- examples/gno.land/p/demo/avl/z_0_filetest.gno | 4 ++++ examples/gno.land/p/demo/avl/z_1_filetest.gno | 4 ++++ examples/gno.land/r/demo/boards/z_4_filetest.gno | 8 ++++---- gnovm/pkg/gnolang/gno_test.go | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/examples/gno.land/p/demo/avl/z_0_filetest.gno b/examples/gno.land/p/demo/avl/z_0_filetest.gno index 814e19d6d49..e91788ac8eb 100644 --- a/examples/gno.land/p/demo/avl/z_0_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_0_filetest.gno @@ -267,6 +267,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -301,6 +303,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/examples/gno.land/p/demo/avl/z_1_filetest.gno b/examples/gno.land/p/demo/avl/z_1_filetest.gno index 410e9e93601..cdd56a5ad89 100644 --- a/examples/gno.land/p/demo/avl/z_1_filetest.gno +++ b/examples/gno.land/p/demo/avl/z_1_filetest.gno @@ -291,6 +291,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", @@ -325,6 +327,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/test", // "Source": { // "@type": "/gno.RefNode", diff --git a/examples/gno.land/r/demo/boards/z_4_filetest.gno b/examples/gno.land/r/demo/boards/z_4_filetest.gno index 749566ea5bc..c891a352d8c 100644 --- a/examples/gno.land/r/demo/boards/z_4_filetest.gno +++ b/examples/gno.land/r/demo/boards/z_4_filetest.gno @@ -374,7 +374,7 @@ func main() { // "Escaped": true, // "ObjectID": "336074805fc853987abe6f7fe3ad97a6a6f3077a:2" // }, -// "Index": "188", +// "Index": "189", // "TV": null // } // } @@ -541,7 +541,7 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "8164abed5231309c88497013f7da72a1b5d427b0", +// "Hash": "25ffc45509708ca0ae17271cb4c3a1dfb367b965", // "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:115" // } // }, @@ -847,7 +847,7 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "5b4b593f1d4b37cb99166247ea28174f91087fdd", +// "Hash": "a8e67b9881af89ca2ec2f05778bf7528a54a5833", // "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:82" // } // }, @@ -865,7 +865,7 @@ func main() { // }, // "V": { // "@type": "/gno.RefValue", -// "Hash": "7e9fd9bb5e90a06c7751585cd80f23aedddde25b", +// "Hash": "d8ae14a4620e3c6dedabd76cd0c5d7e3c205d647", // "ObjectID": "f6dbf411da22e67d74cd7ddba6a76cd7e14a4822:83" // } // }, diff --git a/gnovm/pkg/gnolang/gno_test.go b/gnovm/pkg/gnolang/gno_test.go index 5ed5971c836..a05c23067e2 100644 --- a/gnovm/pkg/gnolang/gno_test.go +++ b/gnovm/pkg/gnolang/gno_test.go @@ -14,7 +14,7 @@ import ( // run empty main(). func TestRunEmptyMain(t *testing.T) { m := NewMachine("test", nil) - main := FuncD("main", nil, nil, nil) + main := FuncD("main", nil, nil, []Stmt{}) m.RunDeclaration(main) m.RunMain() } From 3a985fc33d563be9210a64b02778e430399754aa Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 12 Jul 2023 17:13:15 +0200 Subject: [PATCH 13/59] remove unused code, add more docs --- gnovm/pkg/gnolang/gno_test.go | 2 ++ gnovm/pkg/gnolang/gonative.go | 3 +++ gnovm/pkg/gnolang/machine.go | 8 -------- gnovm/pkg/gnolang/nodes.go | 12 ------------ gnovm/pkg/gnolang/realm.go | 6 ++++++ gnovm/tests/imports.go | 7 +++---- 6 files changed, 14 insertions(+), 24 deletions(-) diff --git a/gnovm/pkg/gnolang/gno_test.go b/gnovm/pkg/gnolang/gno_test.go index a05c23067e2..1a965a4d37e 100644 --- a/gnovm/pkg/gnolang/gno_test.go +++ b/gnovm/pkg/gnolang/gno_test.go @@ -14,6 +14,8 @@ import ( // run empty main(). func TestRunEmptyMain(t *testing.T) { m := NewMachine("test", nil) + // []Stmt{} != nil, as nil means that in the source code not even the + // brackets are present and is reserved for external (ie. native) functions. main := FuncD("main", nil, nil, []Stmt{}) m.RunDeclaration(main) m.RunMain() diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index 80dc1d06cd6..c90ba7b07a8 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -676,6 +676,7 @@ func go2GnoValueUpdate(alloc *Allocator, rlm *Realm, lvl int, tv *TypedValue, rv return } +// used for direct comparison to error types var tError = reflect.TypeOf(new(error)).Elem() // If recursive is false, this function is like go2GnoValue() but less lazy @@ -759,6 +760,8 @@ func go2GnoValue2(alloc *Allocator, store Store, rv reflect.Value, recursive boo // regardless. tv.V = alloc.NewNative(rv) case reflect.Interface: + // special case for errors, which are very often used esepcially in + // native bindings if rv.Type() == tError { tv.T = gErrorType if !rv.IsNil() { diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index f08979c7420..64b3961f42d 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -34,7 +34,6 @@ type Machine struct { Exceptions []*TypedValue // if panic'd unless recovered NumResults int // number of results returned Cycles int64 // number of "cpu" cycles - Injector func(store Store, pn *PackageNode) // Configuration CheckTypes bool // not yet used @@ -75,7 +74,6 @@ type MachineOptions struct { Alloc *Allocator // or see MaxAllocBytes. MaxAllocBytes int64 // or 0 for no limit. MaxCycles int64 // or 0 for no limit. - Injector func(store Store, pn *PackageNode) } // the machine constructor gets spammed @@ -139,7 +137,6 @@ func NewMachineWithOptions(opts MachineOptions) *Machine { Output: output, Store: store, Context: context, - // Injector: opts.Injector, } if pv != nil { @@ -568,11 +565,6 @@ func (m *Machine) runFiles(fns ...*FileNode) { } } - // Inject native functions - if m.Injector != nil { - m.Injector(m.Store, pn) - } - // Declarations (and variable initializations). This must happen // after all files are preprocessed, because value decl may be out of // order and depend on other files. diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 43ce48e1200..b4986d51498 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1390,18 +1390,6 @@ func (x *PackageNode) DefineNative(n Name, ps, rs FieldTypeExprs, native func(*M panic("DefineNative expects a function, but got nil") } - if v := x.GetValueRef(nil, n); v != nil { - // redefinition - fv, ok := v.V.(*FuncValue) - if !ok { - panic("cannot redefine non-function as native function") - } - // XXX: type-check - fv.body = nil - fv.nativeBody = native - return - } - fd := FuncD(n, ps, rs, nil) fd = Preprocess(nil, x, fd).(*FuncDecl) ft := evalStaticType(nil, x, &fd.Type).(*FuncType) diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 392c9b197f3..3aa255e30b9 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -1125,6 +1125,12 @@ func copyValueWithRefs(parent Object, val Value) Value { if cv.Closure != nil { closure = toRefValue(parent, cv.Closure) } + // nativeBody funcs which don't come from NativeStore (and thus don't + // have NativePkg/Name) can't be persisted, and should not be able + // to get here anyway. + if cv.nativeBody != nil && cv.NativePkg == "" { + panic("should not happen") + } ft := copyTypeWithRefs(cv.Type) return &FuncValue{ Type: ft, diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index b7dbb781c3d..35603e7688b 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -436,10 +436,9 @@ func loadStdlib(rootDir, pkgPath string, store gno.Store, stdout io.Writer) (*gn // NOTE: see also pkgs/sdk/vm/builtins.go // Needs PkgPath != its name because TestStore.getPackage is the package // getter for the store, which calls loadStdlib, so it would be recursively called. - PkgPath: "stdlibload", - Output: stdout, - Store: store, - Injector: testPackageInjector, + PkgPath: "stdlibload", + Output: stdout, + Store: store, }) return m2.RunMemPackageWithOverrides(memPkg, true) } From f2d8894a92d99d0ca8b66b3be0f3fb12e9bd1929 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 12 Jul 2023 19:06:26 +0200 Subject: [PATCH 14/59] more docs & text fixes --- gnovm/stdlibs/native.go | 2 +- gnovm/tests/stdlibs/native.go | 2 +- misc/genstd/genstd.go | 3 +++ misc/genstd/template.tmpl | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go index af4462acebe..e62e02f57f6 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/native.go @@ -1,5 +1,5 @@ // This file is autogenerated from the genstd tool (@/misc/stdgen); do not edit. -// To regenerate it, run `go generate` from @/stdlibs. +// To regenerate it, run `go generate` from this directory. package stdlibs diff --git a/gnovm/tests/stdlibs/native.go b/gnovm/tests/stdlibs/native.go index 1f94a4dd4af..8435eb66b0f 100644 --- a/gnovm/tests/stdlibs/native.go +++ b/gnovm/tests/stdlibs/native.go @@ -1,5 +1,5 @@ // This file is autogenerated from the genstd tool (@/misc/stdgen); do not edit. -// To regenerate it, run `go generate` from @/stdlibs. +// To regenerate it, run `go generate` from this directory. package stdlibs diff --git a/misc/genstd/genstd.go b/misc/genstd/genstd.go index 3b39f6804b6..48639ca3aba 100644 --- a/misc/genstd/genstd.go +++ b/misc/genstd/genstd.go @@ -271,6 +271,9 @@ func (m *mapping) loadSignaturesMatch(gnof, gof *ast.FuncDecl) bool { return reflect.DeepEqual(gnop, gop) && reflect.DeepEqual(gnor, gor) } +// TODO: this is created based on the uverse definitions. This should be +// centralized, or at least have a CI/make check to make sure this stays the +// same var builtinTypes = [...]string{ "bool", "string", diff --git a/misc/genstd/template.tmpl b/misc/genstd/template.tmpl index 9505196fef4..a692f0d9aa1 100644 --- a/misc/genstd/template.tmpl +++ b/misc/genstd/template.tmpl @@ -1,5 +1,5 @@ // This file is autogenerated from the genstd tool (@/misc/stdgen); do not edit. -// To regenerate it, run `go generate` from @/stdlibs. +// To regenerate it, run `go generate` from this directory. package stdlibs From 50c919d09945bfa872b6846a67ee9500280310e1 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 24 Jul 2023 20:16:37 +0200 Subject: [PATCH 15/59] ci: add workflow to ensure go generate is executed --- .github/workflows/{misc.yml => lint.yml} | 19 +++++++++++++++++++ gnovm/stdlibs/native.go | 1 + 2 files changed, 20 insertions(+) rename .github/workflows/{misc.yml => lint.yml} (78%) diff --git a/.github/workflows/misc.yml b/.github/workflows/lint.yml similarity index 78% rename from .github/workflows/misc.yml rename to .github/workflows/lint.yml index 5e72ccb7486..4cdea549a71 100644 --- a/.github/workflows/misc.yml +++ b/.github/workflows/lint.yml @@ -76,3 +76,22 @@ jobs: env -C $path go mod tidy -v || exit 1 done echo "$sums" | sha256sum -c + generated: + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version: 1.20.x + + - name: Checkout code + uses: actions/checkout@v3 + + - name: Check generated files are up to date + run: | + go generate -v ./... + if [ "$(git status -s)" != "" ]; then + echo "command 'go generate' creates file that differ from git tree, please run 'go generate' and commit:" + git status -s + exit 1 + fi diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go index e62e02f57f6..5ac15fdaeef 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/native.go @@ -1,3 +1,4 @@ +// XXX TEST GITHUB ACTION // This file is autogenerated from the genstd tool (@/misc/stdgen); do not edit. // To regenerate it, run `go generate` from this directory. From 73e411edf09e121acac50e220af4e432b033b341 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 24 Jul 2023 20:33:49 +0200 Subject: [PATCH 16/59] add generate to make --- .github/workflows/lint.yml | 4 ++-- gnovm/Makefile | 9 +++++++++ gnovm/stdlibs/native.go | 1 - 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4cdea549a71..06fa69affe6 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ -name: misc +name: lint on: push: @@ -89,7 +89,7 @@ jobs: - name: Check generated files are up to date run: | - go generate -v ./... + go generate -x ./... if [ "$(git status -s)" != "" ]; then echo "command 'go generate' creates file that differ from git tree, please run 'go generate' and commit:" git status -s diff --git a/gnovm/Makefile b/gnovm/Makefile index 5fcacf94f62..18655bb8c6b 100644 --- a/gnovm/Makefile +++ b/gnovm/Makefile @@ -58,6 +58,11 @@ _test.gnolang.stdlibs.sync:; go test $(GOTEST_FLAGS) tests/*.go -test.short -run ######################################## # Code gen +# TODO: move _dev.stringer to go:generate instructions, simplify generate +# to just go generate. +.PHONY: generate +generate: _dev.stringer _dev.generate + stringer_cmd=$(rundep) golang.org/x/tools/cmd/stringer .PHONY: _dev.stringer _dev.stringer: @@ -68,5 +73,9 @@ _dev.stringer: $(stringer_cmd) -type=VPType ./pkg/gnolang $(stringer_cmd) -type=Word ./pkg/gnolang +.PHONY: _dev.generate +_dev.generate: + go generate -x ./... + # genproto: # see top-level Makefile. diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go index 5ac15fdaeef..e62e02f57f6 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/native.go @@ -1,4 +1,3 @@ -// XXX TEST GITHUB ACTION // This file is autogenerated from the genstd tool (@/misc/stdgen); do not edit. // To regenerate it, run `go generate` from this directory. From b1d6a3d468f102380c77144b3a44b9c595af7228 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 23 Aug 2023 14:26:01 +0200 Subject: [PATCH 17/59] convert all injections to new native bindings --- gnovm/stdlibs/context.go | 35 -- gnovm/stdlibs/crypto/sha256/sha256.gno | 4 +- gnovm/stdlibs/crypto/sha256/sha256.go | 2 +- gnovm/stdlibs/internal/math/math.gno | 3 - gnovm/stdlibs/native.go | 322 +++++++++++++++--- gnovm/stdlibs/{ => std}/banker.go | 6 +- gnovm/stdlibs/std/context.go | 20 ++ gnovm/stdlibs/{ => std}/frame.go | 2 +- gnovm/stdlibs/std/native.gno | 21 +- gnovm/stdlibs/std/native.go | 150 ++++++++- gnovm/stdlibs/stdlibs.go | 380 +-------------------- gnovm/stdlibs/time/time.go | 10 +- gnovm/tests/imports.go | 124 ------- gnovm/tests/stdlibs/native.go | 138 +++++++- gnovm/tests/stdlibs/std/std.gno | 15 +- gnovm/tests/stdlibs/std/std.go | 58 +++- misc/devdeps/go.mod | 6 +- misc/devdeps/go.sum | 6 + misc/genstd/exprstring.go | 289 ++++++++++++++++ misc/genstd/genstd.go | 367 ++++---------------- misc/genstd/mapping.go | 446 +++++++++++++++++++++++++ misc/genstd/template.tmpl | 34 +- misc/genstd/util.go | 119 +++++++ tm2/pkg/sdk/vm/builtins.go | 1 - 24 files changed, 1603 insertions(+), 955 deletions(-) delete mode 100644 gnovm/stdlibs/context.go delete mode 100644 gnovm/stdlibs/internal/math/math.gno rename gnovm/stdlibs/{ => std}/banker.go (97%) create mode 100644 gnovm/stdlibs/std/context.go rename gnovm/stdlibs/{ => std}/frame.go (94%) create mode 100644 misc/genstd/exprstring.go create mode 100644 misc/genstd/mapping.go create mode 100644 misc/genstd/util.go diff --git a/gnovm/stdlibs/context.go b/gnovm/stdlibs/context.go deleted file mode 100644 index 6fadcb1fd4e..00000000000 --- a/gnovm/stdlibs/context.go +++ /dev/null @@ -1,35 +0,0 @@ -package stdlibs - -import ( - "github.com/gnolang/gno/tm2/pkg/crypto" - "github.com/gnolang/gno/tm2/pkg/sdk" - "github.com/gnolang/gno/tm2/pkg/std" -) - -type ExecContext struct { - ChainID string - Height int64 - Timestamp int64 // seconds - TimestampNano int64 // nanoseconds, only used for testing. - Msg sdk.Msg - OrigCaller crypto.Bech32Address - OrigPkgAddr crypto.Bech32Address - OrigSend std.Coins - OrigSendSpent *std.Coins // mutable - Banker Banker -} - -// XXX: migration from InjectStdlibs to NativeStore meant that stdlibs can -// no longer directly access ExecContext, as it would create a circular -// dependency; hence the helper functions, so access can still be done through -// interface type assertions. -// These probably need to have a better place. - -func (e ExecContext) GetTimestamp() int64 { return e.Timestamp } -func (e ExecContext) GetTimestampNano() int64 { return e.TimestampNano } -func (e ExecContext) GetChainID() string { return e.ChainID } -func (e ExecContext) GetHeight() int64 { return e.Height } -func (e ExecContext) SkipHeights(i int64) any { - e.Height += i - return e -} diff --git a/gnovm/stdlibs/crypto/sha256/sha256.gno b/gnovm/stdlibs/crypto/sha256/sha256.gno index 6292b57b3ff..3d0c6cf3611 100644 --- a/gnovm/stdlibs/crypto/sha256/sha256.gno +++ b/gnovm/stdlibs/crypto/sha256/sha256.gno @@ -3,4 +3,6 @@ package sha256 const Size = 32 // Sum returns the SHA-256 checksum of the data. -func Sum256(data []byte) [Size]byte +func Sum256(data []byte) [Size]byte { return sum256(data) } + +func sum256(data []byte) [32]byte diff --git a/gnovm/stdlibs/crypto/sha256/sha256.go b/gnovm/stdlibs/crypto/sha256/sha256.go index 42c1493864d..c3aac1106e2 100644 --- a/gnovm/stdlibs/crypto/sha256/sha256.go +++ b/gnovm/stdlibs/crypto/sha256/sha256.go @@ -2,6 +2,6 @@ package sha256 import "crypto/sha256" -func Sum256(data []byte) [32]byte { +func X_sum256(data []byte) [32]byte { return sha256.Sum256(data) } diff --git a/gnovm/stdlibs/internal/math/math.gno b/gnovm/stdlibs/internal/math/math.gno deleted file mode 100644 index 42245392caf..00000000000 --- a/gnovm/stdlibs/internal/math/math.gno +++ /dev/null @@ -1,3 +0,0 @@ -package math - -// XXX injected via stdlibs/stdlibs.go diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go index e62e02f57f6..028c75a9ff2 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/native.go @@ -7,11 +7,12 @@ import ( "reflect" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - lib0 "github.com/gnolang/gno/gnovm/stdlibs/crypto/sha256" - lib1 "github.com/gnolang/gno/gnovm/stdlibs/math" - lib2 "github.com/gnolang/gno/gnovm/stdlibs/std" - lib3 "github.com/gnolang/gno/gnovm/stdlibs/strconv" - lib4 "github.com/gnolang/gno/gnovm/stdlibs/time" + libs_crypto_sha256 "github.com/gnolang/gno/gnovm/stdlibs/crypto/sha256" + libs_math "github.com/gnolang/gno/gnovm/stdlibs/math" + libs_std "github.com/gnolang/gno/gnovm/stdlibs/std" + libs_strconv "github.com/gnolang/gno/gnovm/stdlibs/strconv" + libs_time "github.com/gnolang/gno/gnovm/stdlibs/time" + tm2_crypto "github.com/gnolang/gno/tm2/pkg/crypto" ) type nativeFunc struct { @@ -25,13 +26,12 @@ type nativeFunc struct { var nativeFuncs = [...]nativeFunc{ { "crypto/sha256", - "Sum256", - + "sum256", []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("[]byte")}, }, []gno.FieldTypeExpr{ - {Name: gno.N("r0"), Type: gno.X("[]byte")}, + {Name: gno.N("r0"), Type: gno.X("[32]byte")}, }, func(m *gno.Machine) { b := m.LastBlock() @@ -42,7 +42,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := lib0.Sum256(p0) + r0 := libs_crypto_sha256.X_sum256(p0) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -54,7 +54,6 @@ var nativeFuncs = [...]nativeFunc{ { "math", "Float32bits", - []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("float32")}, }, @@ -70,7 +69,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := lib1.Float32bits(p0) + r0 := libs_math.Float32bits(p0) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -82,7 +81,6 @@ var nativeFuncs = [...]nativeFunc{ { "math", "Float32frombits", - []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("uint32")}, }, @@ -98,7 +96,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := lib1.Float32frombits(p0) + r0 := libs_math.Float32frombits(p0) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -110,7 +108,6 @@ var nativeFuncs = [...]nativeFunc{ { "math", "Float64bits", - []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("float64")}, }, @@ -126,7 +123,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := lib1.Float64bits(p0) + r0 := libs_math.Float64bits(p0) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -138,7 +135,6 @@ var nativeFuncs = [...]nativeFunc{ { "math", "Float64frombits", - []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("uint64")}, }, @@ -154,7 +150,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := lib1.Float64frombits(p0) + r0 := libs_math.Float64frombits(p0) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -166,11 +162,10 @@ var nativeFuncs = [...]nativeFunc{ { "std", "AssertOriginCall", - []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{}, func(m *gno.Machine) { - lib2.AssertOriginCall( + libs_std.AssertOriginCall( m, ) }, @@ -178,13 +173,12 @@ var nativeFuncs = [...]nativeFunc{ { "std", "IsOriginCall", - []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("bool")}, }, func(m *gno.Machine) { - r0 := lib2.IsOriginCall( + r0 := libs_std.IsOriginCall( m, ) @@ -198,13 +192,12 @@ var nativeFuncs = [...]nativeFunc{ { "std", "CurrentRealmPath", - []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { - r0 := lib2.CurrentRealmPath( + r0 := libs_std.CurrentRealmPath( m, ) @@ -218,13 +211,12 @@ var nativeFuncs = [...]nativeFunc{ { "std", "GetChainID", - []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { - r0 := lib2.GetChainID( + r0 := libs_std.GetChainID( m, ) @@ -238,13 +230,107 @@ var nativeFuncs = [...]nativeFunc{ { "std", "GetHeight", - []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("int64")}, }, func(m *gno.Machine) { - r0 := lib2.GetHeight( + r0 := libs_std.GetHeight( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "GetOrigSend", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Coins")}, + }, + func(m *gno.Machine) { + r0 := libs_std.GetOrigSend( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "GetOrigCaller", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Address")}, + }, + func(m *gno.Machine) { + r0 := libs_std.GetOrigCaller( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "CurrentRealm", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Realm")}, + }, + func(m *gno.Machine) { + r0 := libs_std.CurrentRealm( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "PrevRealm", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Realm")}, + }, + func(m *gno.Machine) { + r0 := libs_std.PrevRealm( + m, + ) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "GetOrigPkgAddr", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Address")}, + }, + func(m *gno.Machine) { + r0 := libs_std.GetOrigPkgAddr( m, ) @@ -255,10 +341,164 @@ var nativeFuncs = [...]nativeFunc{ )) }, }, + { + "std", + "GetCallerAt", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("int")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Address")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 int + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := libs_std.GetCallerAt( + m, + p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "GetBanker", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("BankerType")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Banker")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 libs_std.BankerType + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := libs_std.GetBanker( + m, + p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "DerivePkgAddr", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Address")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0 := libs_std.DerivePkgAddr(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "EncodeBech32", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("string")}, + {Name: gno.N("p1"), Type: gno.X("[20]byte")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Address")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 string + rp0 = reflect.ValueOf(&p0).Elem() + p1 [20]byte + rp1 = reflect.ValueOf(&p1).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + r0 := libs_std.EncodeBech32(p0, p1) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "DecodeBech32", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("Address")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("string")}, + {Name: gno.N("r1"), Type: gno.X("[20]byte")}, + {Name: gno.N("r2"), Type: gno.X("bool")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 tm2_crypto.Bech32Address + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + r0, r1, r2 := libs_std.DecodeBech32(p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r1).Elem(), + )) + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r2).Elem(), + )) + }, + }, { "strconv", "Itoa", - []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("int")}, }, @@ -274,7 +514,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := lib3.Itoa(p0) + r0 := libs_strconv.Itoa(p0) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -286,7 +526,6 @@ var nativeFuncs = [...]nativeFunc{ { "strconv", "AppendUint", - []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("[]byte")}, {Name: gno.N("p1"), Type: gno.X("uint64")}, @@ -310,7 +549,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 2, "")).TV, rp2) - r0 := lib3.AppendUint(p0, p1, p2) + r0 := libs_strconv.AppendUint(p0, p1, p2) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -322,7 +561,6 @@ var nativeFuncs = [...]nativeFunc{ { "strconv", "Atoi", - []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("string")}, }, @@ -339,7 +577,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0, r1 := lib3.Atoi(p0) + r0, r1 := libs_strconv.Atoi(p0) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -356,7 +594,6 @@ var nativeFuncs = [...]nativeFunc{ { "strconv", "CanBackquote", - []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("string")}, }, @@ -372,7 +609,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := lib3.CanBackquote(p0) + r0 := libs_strconv.CanBackquote(p0) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -384,7 +621,6 @@ var nativeFuncs = [...]nativeFunc{ { "strconv", "FormatInt", - []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("int64")}, {Name: gno.N("p1"), Type: gno.X("int")}, @@ -404,7 +640,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) - r0 := lib3.FormatInt(p0, p1) + r0 := libs_strconv.FormatInt(p0, p1) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -416,7 +652,6 @@ var nativeFuncs = [...]nativeFunc{ { "strconv", "FormatUint", - []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("uint64")}, {Name: gno.N("p1"), Type: gno.X("int")}, @@ -436,7 +671,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) - r0 := lib3.FormatUint(p0, p1) + r0 := libs_strconv.FormatUint(p0, p1) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -448,7 +683,6 @@ var nativeFuncs = [...]nativeFunc{ { "strconv", "Quote", - []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("string")}, }, @@ -464,7 +698,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := lib3.Quote(p0) + r0 := libs_strconv.Quote(p0) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -476,7 +710,6 @@ var nativeFuncs = [...]nativeFunc{ { "strconv", "QuoteToASCII", - []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("string")}, }, @@ -492,7 +725,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - r0 := lib3.QuoteToASCII(p0) + r0 := libs_strconv.QuoteToASCII(p0) m.PushValue(gno.Go2GnoValue( m.Alloc, @@ -504,7 +737,6 @@ var nativeFuncs = [...]nativeFunc{ { "time", "now", - []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("int64")}, @@ -512,7 +744,7 @@ var nativeFuncs = [...]nativeFunc{ {Name: gno.N("r2"), Type: gno.X("int64")}, }, func(m *gno.Machine) { - r0, r1, r2 := lib4.X_now( + r0, r1, r2 := libs_time.X_now( m, ) diff --git a/gnovm/stdlibs/banker.go b/gnovm/stdlibs/std/banker.go similarity index 97% rename from gnovm/stdlibs/banker.go rename to gnovm/stdlibs/std/banker.go index 82bf1bad42a..7653f2a519f 100644 --- a/gnovm/stdlibs/banker.go +++ b/gnovm/stdlibs/std/banker.go @@ -1,4 +1,4 @@ -package stdlibs +package std import ( "fmt" @@ -21,10 +21,10 @@ type Banker interface { } // Used in std.GetBanker(options). -// Also available as Gno in stdlibs/std/banker.go +// Also available as Gno in stdlibs/std/banker.gno type BankerType uint8 -// Also available as Gno in stdlibs/std/banker.go +// Also available as Gno in stdlibs/std/banker.gno const ( // Can only read state. BankerTypeReadonly BankerType = iota diff --git a/gnovm/stdlibs/std/context.go b/gnovm/stdlibs/std/context.go new file mode 100644 index 00000000000..c50e2e5e1b9 --- /dev/null +++ b/gnovm/stdlibs/std/context.go @@ -0,0 +1,20 @@ +package std + +import ( + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/sdk" + "github.com/gnolang/gno/tm2/pkg/std" +) + +type ExecContext struct { + ChainID string + Height int64 + Timestamp int64 // seconds + TimestampNano int64 // nanoseconds, only used for testing. + Msg sdk.Msg + OrigCaller crypto.Bech32Address + OrigPkgAddr crypto.Bech32Address + OrigSend std.Coins + OrigSendSpent *std.Coins // mutable + Banker Banker +} diff --git a/gnovm/stdlibs/frame.go b/gnovm/stdlibs/std/frame.go similarity index 94% rename from gnovm/stdlibs/frame.go rename to gnovm/stdlibs/std/frame.go index e428bb1776d..2948813ad0f 100644 --- a/gnovm/stdlibs/frame.go +++ b/gnovm/stdlibs/std/frame.go @@ -1,4 +1,4 @@ -package stdlibs +package std import "github.com/gnolang/gno/tm2/pkg/crypto" diff --git a/gnovm/stdlibs/std/native.gno b/gnovm/stdlibs/std/native.gno index e2e73209142..2f7da810bcb 100644 --- a/gnovm/stdlibs/std/native.gno +++ b/gnovm/stdlibs/std/native.gno @@ -1,7 +1,18 @@ package std -func AssertOriginCall() // injected -func IsOriginCall() bool // injected -func CurrentRealmPath() string // injected -func GetChainID() string // injected -func GetHeight() int64 // injected +func AssertOriginCall() // injected +func IsOriginCall() bool // injected +func CurrentRealmPath() string // injected +func GetChainID() string // injected +func GetHeight() int64 // injected +func GetOrigSend() Coins // injected +func GetOrigCaller() Address // injected +func CurrentRealm() Realm // injected +func PrevRealm() Realm // injected +func GetOrigPkgAddr() Address // injected +func GetCallerAt(n int) Address // injected +func GetBanker(bt BankerType) Banker // injected +func DerivePkgAddr(pkgPath string) Address // injected + +func EncodeBech32(prefix string, bz [20]byte) Address // injected +func DecodeBech32(addr Address) (prefix string, bz [20]byte, ok bool) // injected diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index 6eca8b501ff..ff547e108dd 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -2,6 +2,9 @@ package std import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/tm2/pkg/bech32" + "github.com/gnolang/gno/tm2/pkg/crypto" + "github.com/gnolang/gno/tm2/pkg/std" ) func AssertOriginCall(m *gno.Machine) { @@ -22,11 +25,149 @@ func CurrentRealmPath(m *gno.Machine) string { } func GetChainID(m *gno.Machine) string { - return m.Context.(execContext).GetChainID() + return m.Context.(ExecContext).ChainID } func GetHeight(m *gno.Machine) int64 { - return m.Context.(execContext).GetHeight() + return m.Context.(ExecContext).Height +} + +func GetOrigSend(m *gno.Machine) std.Coins { + return m.Context.(ExecContext).OrigSend +} + +func GetOrigCaller(m *gno.Machine) crypto.Bech32Address { + return m.Context.(ExecContext).OrigCaller +} + +func CurrentRealm(m *gno.Machine) Realm { + var ( + ctx = m.Context.(ExecContext) + // Default lastCaller is OrigCaller, the signer of the tx + lastCaller = ctx.OrigCaller + lastPkgPath = "" + ) + + for i := m.NumFrames() - 1; i > 0; i-- { + fr := m.Frames[i] + if fr.LastPackage != nil && fr.LastPackage.IsRealm() { + lastCaller = fr.LastPackage.GetPkgAddr().Bech32() + lastPkgPath = fr.LastPackage.PkgPath + break + } + } + + return Realm{ + addr: lastCaller, + pkgPath: lastPkgPath, + } +} + +func PrevRealm(m *gno.Machine) Realm { + var ( + ctx = m.Context.(ExecContext) + // Default lastCaller is OrigCaller, the signer of the tx + lastCaller = ctx.OrigCaller + lastPkgPath = "" + ) + + for i := m.NumFrames() - 1; i > 0; i-- { + fr := m.Frames[i] + if fr.LastPackage == nil || !fr.LastPackage.IsRealm() { + // Ignore non-realm frame + continue + } + pkgPath := fr.LastPackage.PkgPath + // The first realm we encounter will be the one calling + // this function; to get the calling realm determine the first frame + // where fr.LastPackage changes. + if lastPkgPath == "" { + lastPkgPath = pkgPath + } else if lastPkgPath == pkgPath { + continue + } else { + lastCaller = fr.LastPackage.GetPkgAddr().Bech32() + lastPkgPath = pkgPath + break + } + } + + // Empty the pkgPath if we return a user + if ctx.OrigCaller == lastCaller { + lastPkgPath = "" + } + + return Realm{ + addr: lastCaller, + pkgPath: lastPkgPath, + } +} + +func GetOrigPkgAddr(m *gno.Machine) crypto.Bech32Address { + return m.Context.(ExecContext).OrigPkgAddr +} + +func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { + if n <= 0 { + m.Panic(typedString("GetCallerAt requires positive arg")) + return "" + } + if n > m.NumFrames() { + // NOTE: the last frame's LastPackage + // is set to the original non-frame + // package, so need this check. + m.Panic(typedString("frame not found")) + return "" + } + if n == m.NumFrames() { + // This makes it consistent with GetOrigCaller. + ctx := m.Context.(ExecContext) + return ctx.OrigCaller + } + return m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32() +} + +func GetBanker(m *gno.Machine, bankerType BankerType) Banker { + ctx := m.Context.(ExecContext) + banker := ctx.Banker + switch bankerType { + case BankerTypeReadonly: + banker = NewReadonlyBanker(banker) + case BankerTypeOrigSend: + banker = NewOrigSendBanker(banker, ctx.OrigPkgAddr, ctx.OrigSend, ctx.OrigSendSpent) + case BankerTypeRealmSend: + banker = NewRealmSendBanker(banker, ctx.OrigPkgAddr) + case BankerTypeRealmIssue: + banker = banker + default: + panic("should not happen") // defensive + } + m.Alloc.AllocateStruct() // defensive; native space not allocated. + m.Alloc.AllocateStructFields(10) // defensive 10; native space not allocated. + + return banker +} + +func EncodeBech32(prefix string, bytes [20]byte) crypto.Bech32Address { + b32, err := bech32.ConvertAndEncode(prefix, bytes[:]) + if err != nil { + panic(err) // should not happen + } + return crypto.Bech32Address(b32) +} + +func DerivePkgAddr(pkgPath string) crypto.Bech32Address { + return gno.DerivePkgAddr(pkgPath).Bech32() +} + +func DecodeBech32(addr crypto.Bech32Address) (prefix string, bytes [20]byte, ok bool) { + prefix, bz, err := bech32.Decode(string(addr)) + if err != nil || len(bz) != 20 { + return "", [20]byte{}, false + } + // TODO: can be simplified when we switch to go1.20 in go mod to be a simple [20]byte(bz) + copy(bytes[:], bz) + return prefix, bytes, true } func typedString(s gno.StringValue) gno.TypedValue { @@ -34,8 +175,3 @@ func typedString(s gno.StringValue) gno.TypedValue { tv.SetString(s) return tv } - -type execContext interface { - GetHeight() int64 - GetChainID() string -} diff --git a/gnovm/stdlibs/stdlibs.go b/gnovm/stdlibs/stdlibs.go index 39b6b336176..22054613c03 100644 --- a/gnovm/stdlibs/stdlibs.go +++ b/gnovm/stdlibs/stdlibs.go @@ -6,16 +6,18 @@ import ( "reflect" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - "github.com/gnolang/gno/tm2/pkg/bech32" + libsstd "github.com/gnolang/gno/gnovm/stdlibs/std" "github.com/gnolang/gno/tm2/pkg/crypto" "github.com/gnolang/gno/tm2/pkg/std" ) +type ExecContext = libsstd.ExecContext + func InjectNativeMappings(store gno.Store) { store.AddGo2GnoMapping(reflect.TypeOf(crypto.Bech32Address("")), "std", "Address") store.AddGo2GnoMapping(reflect.TypeOf(std.Coins{}), "std", "Coins") store.AddGo2GnoMapping(reflect.TypeOf(std.Coin{}), "std", "Coin") - store.AddGo2GnoMapping(reflect.TypeOf(Realm{}), "std", "Realm") + store.AddGo2GnoMapping(reflect.TypeOf(libsstd.Realm{}), "std", "Realm") } func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { @@ -26,377 +28,3 @@ func NativeStore(pkgPath string, name gno.Name) func(*gno.Machine) { } return nil } - -func InjectPackage(store gno.Store, pn *gno.PackageNode) { - switch pn.PkgPath { - case "std": - // NOTE: some of these are overridden in tests/imports.go - pn.DefineNative("GetOrigSend", - gno.Flds( // params - ), - gno.Flds( // results - "", "Coins", - ), - func(m *gno.Machine) { - ctx := m.Context.(ExecContext) - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(ctx.OrigSend), - ) - coinT := store.GetType(gno.DeclaredTypeID("std", "Coin")) - coinsT := store.GetType(gno.DeclaredTypeID("std", "Coins")) - res0.T = coinsT - av := res0.V.(*gno.SliceValue).Base.(*gno.ArrayValue) - for i := range av.List { - av.List[i].T = coinT - } - m.PushValue(res0) - }, - ) - pn.DefineNative("GetOrigCaller", - gno.Flds( // params - ), - gno.Flds( // results - "", "Address", - ), - func(m *gno.Machine) { - ctx := m.Context.(ExecContext) - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(ctx.OrigCaller), - ) - addrT := store.GetType(gno.DeclaredTypeID("std", "Address")) - res0.T = addrT - m.PushValue(res0) - }, - ) - pn.DefineNative("CurrentRealm", - gno.Flds( // params - ), - gno.Flds( // results - "", "Realm", - ), - func(m *gno.Machine) { - var ( - ctx = m.Context.(ExecContext) - // Default lastCaller is OrigCaller, the signer of the tx - lastCaller = ctx.OrigCaller - lastPkgPath = "" - ) - - for i := m.NumFrames() - 1; i > 0; i-- { - fr := m.Frames[i] - if fr.LastPackage != nil && fr.LastPackage.IsRealm() { - lastCaller = fr.LastPackage.GetPkgAddr().Bech32() - lastPkgPath = fr.LastPackage.PkgPath - break - } - } - - // Return the result - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(Realm{ - addr: lastCaller, - pkgPath: lastPkgPath, - }), - ) - - realmT := store.GetType(gno.DeclaredTypeID("std", "Realm")) - res0.T = realmT - m.PushValue(res0) - }, - ) - pn.DefineNative("PrevRealm", - gno.Flds( // params - ), - gno.Flds( // results - "", "Realm", - ), - func(m *gno.Machine) { - var ( - ctx = m.Context.(ExecContext) - // Default lastCaller is OrigCaller, the signer of the tx - lastCaller = ctx.OrigCaller - lastPkgPath = "" - ) - - for i := m.NumFrames() - 1; i > 0; i-- { - fr := m.Frames[i] - if fr.LastPackage == nil || !fr.LastPackage.IsRealm() { - // Ignore non-realm frame - continue - } - pkgPath := fr.LastPackage.PkgPath - // The first realm we encounter will be the one calling - // this function; to get the calling realm determine the first frame - // where fr.LastPackage changes. - if lastPkgPath == "" { - lastPkgPath = pkgPath - } else if lastPkgPath == pkgPath { - continue - } else { - lastCaller = fr.LastPackage.GetPkgAddr().Bech32() - lastPkgPath = pkgPath - break - } - } - - // Empty the pkgPath if we return a user - if ctx.OrigCaller == lastCaller { - lastPkgPath = "" - } - - // Return the result - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(Realm{ - addr: lastCaller, - pkgPath: lastPkgPath, - }), - ) - - realmT := store.GetType(gno.DeclaredTypeID("std", "Realm")) - res0.T = realmT - m.PushValue(res0) - }, - ) - pn.DefineNative("GetOrigPkgAddr", - gno.Flds( // params - ), - gno.Flds( // results - "", "Address", - ), - func(m *gno.Machine) { - ctx := m.Context.(ExecContext) - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(ctx.OrigPkgAddr), - ) - addrT := store.GetType(gno.DeclaredTypeID("std", "Address")) - res0.T = addrT - m.PushValue(res0) - }, - ) - pn.DefineNative("GetCallerAt", - gno.Flds( // params - "n", "int", - ), - gno.Flds( // results - "", "Address", - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - n := arg0.GetInt() - if n <= 0 { - m.Panic(typedString("GetCallerAt requires positive arg")) - return - } - if n > m.NumFrames() { - // NOTE: the last frame's LastPackage - // is set to the original non-frame - // package, so need this check. - m.Panic(typedString("frame not found")) - return - } - var pkgAddr string - if n == m.NumFrames() { - // This makes it consistent with GetOrigCaller. - ctx := m.Context.(ExecContext) - pkgAddr = string(ctx.OrigCaller) - } else { - pkgAddr = string(m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) - } - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(pkgAddr), - ) - addrT := store.GetType(gno.DeclaredTypeID("std", "Address")) - res0.T = addrT - m.PushValue(res0) - }, - ) - pn.DefineNative("GetBanker", - gno.Flds( // params - "bankerType", "BankerType", - ), - gno.Flds( // results - "", "Banker", - ), - func(m *gno.Machine) { - ctx := m.Context.(ExecContext) - arg0 := m.LastBlock().GetParams1().TV - bankerType := BankerType(arg0.GetUint8()) - banker := ctx.Banker - switch bankerType { - case BankerTypeReadonly: - banker = NewReadonlyBanker(banker) - case BankerTypeOrigSend: - banker = NewOrigSendBanker(banker, ctx.OrigPkgAddr, ctx.OrigSend, ctx.OrigSendSpent) - case BankerTypeRealmSend: - banker = NewRealmSendBanker(banker, ctx.OrigPkgAddr) - case BankerTypeRealmIssue: - banker = banker - default: - panic("should not happen") // defensive - } - rv := reflect.ValueOf(banker) - m.Alloc.AllocateStruct() // defensive; native space not allocated. - m.Alloc.AllocateStructFields(10) // defensive 10; native space not allocated. - - // make gno bankAdapter{rv} - btv := gno.Go2GnoNativeValue(m.Alloc, rv) - bsv := m.Alloc.NewStructWithFields(btv) - bankAdapterType := store.GetType(gno.DeclaredTypeID("std", "bankAdapter")) - res0 := gno.TypedValue{T: bankAdapterType, V: bsv} - m.PushValue(res0) - }, - ) - pn.DefineNative("EncodeBech32", - gno.Flds( // params - "prefix", "string", - "bytes", "[20]byte", - ), - gno.Flds( // results - "addr", "Address", - ), - func(m *gno.Machine) { - arg0, arg1 := m.LastBlock().GetParams2() - prefix := arg0.TV.GetString() - bz := arg1.TV.V.(*gno.ArrayValue).GetReadonlyBytes() - if len(bz) != crypto.AddressSize { - panic("should not happen") - } - b32, err := bech32.ConvertAndEncode(prefix, bz) - if err != nil { - panic(err) // should not happen - } - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(b32), - ) - addrT := store.GetType(gno.DeclaredTypeID("std", "Address")) - res0.T = addrT - m.PushValue(res0) - }, - ) - pn.DefineNative("DecodeBech32", - gno.Flds( // params - "addr", "Address", - ), - gno.Flds( // results - "prefix", "string", - "bytes", "[20]byte", - "ok", "bool", - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1() - addr := arg0.TV.GetString() - prefix, bz, err := bech32.Decode(addr) - if err != nil || len(bz) != 20 { - m.PushValue(typedString(m.Alloc.NewString(""))) - m.PushValue(typedByteArray(20, m.Alloc.NewDataArray(20))) - m.PushValue(typedBool(false)) - } else { - m.PushValue(typedString(m.Alloc.NewString(prefix))) - m.PushValue(typedByteArray(20, m.Alloc.NewArrayFromData(bz))) - m.PushValue(typedBool(true)) - } - }, - ) - pn.DefineNative("DerivePkgAddr", - gno.Flds( // params - "pkgPath", "string", - ), - gno.Flds( // results - "addr", "Address", - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - pkgPath := arg0.GetString() - pkgAddr := gno.DerivePkgAddr(pkgPath).Bech32() - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(pkgAddr), - ) - addrT := store.GetType(gno.DeclaredTypeID("std", "Address")) - res0.T = addrT - m.PushValue(res0) - }, - ) - } -} - -func typedInt32(i32 int32) gno.TypedValue { - tv := gno.TypedValue{T: gno.Int32Type} - tv.SetInt32(i32) - return tv -} - -func typedInt64(i64 int64) gno.TypedValue { - tv := gno.TypedValue{T: gno.Int64Type} - tv.SetInt64(i64) - return tv -} - -func typedUint32(u32 uint32) gno.TypedValue { - tv := gno.TypedValue{T: gno.Uint32Type} - tv.SetUint32(u32) - return tv -} - -func typedUint64(u64 uint64) gno.TypedValue { - tv := gno.TypedValue{T: gno.Uint64Type} - tv.SetUint64(u64) - return tv -} - -func typedFloat32(f32 float32) gno.TypedValue { - tv := gno.TypedValue{T: gno.Float32Type} - tv.SetFloat32(f32) - return tv -} - -func typedFloat64(f64 float64) gno.TypedValue { - tv := gno.TypedValue{T: gno.Float64Type} - tv.SetFloat64(f64) - return tv -} - -func typedString(s gno.StringValue) gno.TypedValue { - tv := gno.TypedValue{T: gno.StringType} - tv.SetString(s) - return tv -} - -func typedBool(b bool) gno.TypedValue { - tv := gno.TypedValue{T: gno.BoolType} - tv.SetBool(b) - return tv -} - -func typedByteArray(ln int, bz *gno.ArrayValue) gno.TypedValue { - if bz != nil && bz.GetLength() != ln { - panic("array length mismatch") - } - tv := gno.TypedValue{T: &gno.ArrayType{Len: ln, Elt: gno.Uint8Type}, V: bz} - return tv -} - -func typedByteSlice(bz *gno.SliceValue) gno.TypedValue { - tv := gno.TypedValue{T: &gno.SliceType{Elt: gno.Uint8Type}, V: bz} - return tv -} - -func typedNil(t gno.Type) gno.TypedValue { - tv := gno.TypedValue{T: t, V: nil} - return tv -} diff --git a/gnovm/stdlibs/time/time.go b/gnovm/stdlibs/time/time.go index f75c484afa3..8c1c768715e 100644 --- a/gnovm/stdlibs/time/time.go +++ b/gnovm/stdlibs/time/time.go @@ -4,18 +4,14 @@ import ( "time" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/stdlibs/std" ) -type execContext interface { - GetTimestamp() int64 - GetTimestampNano() int64 -} - func X_now(m *gno.Machine) (sec int64, nsec int32, mono int64) { if m == nil || m.Context == nil { return 0, 0, 0 } - ctx := m.Context.(execContext) - return ctx.GetTimestamp(), int32(ctx.GetTimestampNano()), ctx.GetTimestamp()*int64(time.Second) + ctx.GetTimestampNano() + ctx := m.Context.(std.ExecContext) + return ctx.Timestamp, int32(ctx.TimestampNano), ctx.Timestamp*int64(time.Second) + ctx.TimestampNano } diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 35603e7688b..0d29eeeb51b 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -42,10 +42,8 @@ import ( gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/gnovm/stdlibs" teststdlibs "github.com/gnolang/gno/gnovm/tests/stdlibs" - "github.com/gnolang/gno/tm2/pkg/crypto" dbm "github.com/gnolang/gno/tm2/pkg/db" osm "github.com/gnolang/gno/tm2/pkg/os" - "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/store/dbadapter" "github.com/gnolang/gno/tm2/pkg/store/iavl" stypes "github.com/gnolang/gno/tm2/pkg/store/types" @@ -444,8 +442,6 @@ func loadStdlib(rootDir, pkgPath string, store gno.Store, stdout io.Writer) (*gn } func testPackageInjector(store gno.Store, pn *gno.PackageNode) { - // Also inject stdlibs native functions. - stdlibs.InjectPackage(store, pn) // Test specific injections: switch pn.PkgPath { case "strconv": @@ -453,126 +449,6 @@ func testPackageInjector(store gno.Store, pn *gno.PackageNode) { // from stdlibs.InjectNatives. pn.DefineGoNativeType(reflect.TypeOf(strconv.NumError{})) pn.DefineGoNativeValue("ParseInt", strconv.ParseInt) - case "std": - // NOTE: some of these are overrides. - // Also see stdlibs/InjectPackage. - pn.DefineNativeOverride("GetCallerAt", - /* - gno.Flds( // params - "n", "int", - ), - gno.Flds( // results - "", "Address", - ), - */ - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - n := arg0.GetInt() - if n <= 0 { - m.Panic(typedString("GetCallerAt requires positive arg")) - return - } - if n > m.NumFrames()-1 { - // NOTE: the last frame's LastPackage - // is set to the original non-frame - // package, so need this check. - m.Panic(typedString("frame not found")) - return - } - var pkgAddr string - if n == m.NumFrames()-1 { - // This makes it consistent with GetOrigCaller and TestSetOrigCaller. - ctx := m.Context.(stdlibs.ExecContext) - pkgAddr = string(ctx.OrigCaller) - } else { - pkgAddr = string(m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32()) - } - res0 := gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(pkgAddr), - ) - addrT := store.GetType(gno.DeclaredTypeID("std", "Address")) - res0.T = addrT - m.PushValue(res0) - }, - ) - pn.DefineNative("TestSetOrigCaller", - gno.Flds( // params - "", "Address", - ), - gno.Flds( // results - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - addr := arg0.GetString() - // overwrite context - ctx := m.Context.(stdlibs.ExecContext) - ctx.OrigCaller = crypto.Bech32Address(addr) - m.Context = ctx - }, - ) - pn.DefineNative("TestSetOrigPkgAddr", - gno.Flds( // params - "", "Address", - ), - gno.Flds( // results - ), - func(m *gno.Machine) { - arg0 := m.LastBlock().GetParams1().TV - addr := crypto.Bech32Address(arg0.GetString()) - // overwrite context - ctx := m.Context.(stdlibs.ExecContext) - ctx.OrigPkgAddr = addr - m.Context = ctx - }, - ) - pn.DefineNative("TestSetOrigSend", - gno.Flds( // params - "sent", "Coins", - "spent", "Coins", - ), - gno.Flds( // results - ), - func(m *gno.Machine) { - arg0, arg1 := m.LastBlock().GetParams2() - var sent std.Coins - rvSent := reflect.ValueOf(&sent).Elem() - gno.Gno2GoValue(arg0.TV, rvSent) - sent = rvSent.Interface().(std.Coins) // needed? - var spent std.Coins - rvSpent := reflect.ValueOf(&spent).Elem() - gno.Gno2GoValue(arg1.TV, rvSpent) - spent = rvSpent.Interface().(std.Coins) // needed? - // overwrite context. - ctx := m.Context.(stdlibs.ExecContext) - ctx.OrigSend = sent - ctx.OrigSendSpent = &spent - m.Context = ctx - }, - ) - pn.DefineNative("TestIssueCoins", - gno.Flds( // params - "addr", "Address", - "coins", "Coins", - ), - gno.Flds( // results - ), - func(m *gno.Machine) { - arg0, arg1 := m.LastBlock().GetParams2() - addr := crypto.Bech32Address(arg0.TV.GetString()) - var coins std.Coins - rvCoins := reflect.ValueOf(&coins).Elem() - gno.Gno2GoValue(arg1.TV, rvCoins) - coins = rvCoins.Interface().(std.Coins) // needed? - // overwrite context. - ctx := m.Context.(stdlibs.ExecContext) - banker := ctx.Banker - for _, coin := range coins { - banker.IssueCoin(addr, coin.Denom, coin.Amount) - } - }, - ) } } diff --git a/gnovm/tests/stdlibs/native.go b/gnovm/tests/stdlibs/native.go index 8435eb66b0f..129508a5132 100644 --- a/gnovm/tests/stdlibs/native.go +++ b/gnovm/tests/stdlibs/native.go @@ -7,7 +7,9 @@ import ( "reflect" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" - lib0 "github.com/gnolang/gno/gnovm/tests/stdlibs/std" + testlibs_std "github.com/gnolang/gno/gnovm/tests/stdlibs/std" + tm2_crypto "github.com/gnolang/gno/tm2/pkg/crypto" + tm2_std "github.com/gnolang/gno/tm2/pkg/std" ) type nativeFunc struct { @@ -22,11 +24,10 @@ var nativeFuncs = [...]nativeFunc{ { "std", "AssertOriginCall", - []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{}, func(m *gno.Machine) { - lib0.AssertOriginCall( + testlibs_std.AssertOriginCall( m, ) }, @@ -34,13 +35,12 @@ var nativeFuncs = [...]nativeFunc{ { "std", "IsOriginCall", - []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("bool")}, }, func(m *gno.Machine) { - r0 := lib0.IsOriginCall( + r0 := testlibs_std.IsOriginCall( m, ) @@ -54,13 +54,12 @@ var nativeFuncs = [...]nativeFunc{ { "std", "TestCurrentRealm", - []gno.FieldTypeExpr{}, []gno.FieldTypeExpr{ {Name: gno.N("r0"), Type: gno.X("string")}, }, func(m *gno.Machine) { - r0 := lib0.TestCurrentRealm( + r0 := testlibs_std.TestCurrentRealm( m, ) @@ -74,7 +73,6 @@ var nativeFuncs = [...]nativeFunc{ { "std", "TestSkipHeights", - []gno.FieldTypeExpr{ {Name: gno.N("p0"), Type: gno.X("int64")}, }, @@ -88,7 +86,7 @@ var nativeFuncs = [...]nativeFunc{ gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) - lib0.TestSkipHeights( + testlibs_std.TestSkipHeights( m, p0) }, @@ -96,13 +94,133 @@ var nativeFuncs = [...]nativeFunc{ { "std", "ClearStoreCache", + []gno.FieldTypeExpr{}, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + testlibs_std.ClearStoreCache( + m, + ) + }, + }, + { + "std", + "GetCallerAt", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("int")}, + }, + []gno.FieldTypeExpr{ + {Name: gno.N("r0"), Type: gno.X("Address")}, + }, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 int + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + r0 := testlibs_std.GetCallerAt( + m, + p0) + + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r0).Elem(), + )) + }, + }, + { + "std", + "TestSetOrigCaller", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("Address")}, + }, []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 tm2_crypto.Bech32Address + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + testlibs_std.TestSetOrigCaller( + m, + p0) + }, + }, + { + "std", + "TestSetOrigPkgAddr", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("Address")}, + }, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 tm2_crypto.Bech32Address + rp0 = reflect.ValueOf(&p0).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + + testlibs_std.TestSetOrigPkgAddr( + m, + p0) + }, + }, + { + "std", + "TestSetOrigSend", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("Coins")}, + {Name: gno.N("p1"), Type: gno.X("Coins")}, + }, []gno.FieldTypeExpr{}, func(m *gno.Machine) { - lib0.ClearStoreCache( + b := m.LastBlock() + var ( + p0 tm2_std.Coins + rp0 = reflect.ValueOf(&p0).Elem() + p1 tm2_std.Coins + rp1 = reflect.ValueOf(&p1).Elem() + ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + testlibs_std.TestSetOrigSend( m, + p0, p1) + }, + }, + { + "std", + "TestIssueCoins", + []gno.FieldTypeExpr{ + {Name: gno.N("p0"), Type: gno.X("Address")}, + {Name: gno.N("p1"), Type: gno.X("Coins")}, + }, + []gno.FieldTypeExpr{}, + func(m *gno.Machine) { + b := m.LastBlock() + var ( + p0 tm2_crypto.Bech32Address + rp0 = reflect.ValueOf(&p0).Elem() + p1 tm2_std.Coins + rp1 = reflect.ValueOf(&p1).Elem() ) + + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 0, "")).TV, rp0) + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, 1, "")).TV, rp1) + + testlibs_std.TestIssueCoins( + m, + p0, p1) }, }, } diff --git a/gnovm/tests/stdlibs/std/std.gno b/gnovm/tests/stdlibs/std/std.gno index 9546ed984c1..380549be694 100644 --- a/gnovm/tests/stdlibs/std/std.gno +++ b/gnovm/tests/stdlibs/std/std.gno @@ -1,7 +1,12 @@ package std -func AssertOriginCall() // injected -func IsOriginCall() bool // injected -func TestCurrentRealm() string // injected -func TestSkipHeights(count int64) // injected -func ClearStoreCache() // injected +func AssertOriginCall() // injected +func IsOriginCall() bool // injected +func TestCurrentRealm() string // injected +func TestSkipHeights(count int64) // injected +func ClearStoreCache() // injected +func GetCallerAt(n int) Address // injected +func TestSetOrigCaller(addr Address) // injected +func TestSetOrigPkgAddr(addr Address) // injected +func TestSetOrigSend(sent, spent Coins) // injected +func TestIssueCoins(addr Address, coins Coins) // injected diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go index 8016e367fd9..ea2ff453f9e 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -6,6 +6,10 @@ import ( "testing" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/stdlibs" + "github.com/gnolang/gno/gnovm/stdlibs/std" + "github.com/gnolang/gno/tm2/pkg/crypto" + tm2std "github.com/gnolang/gno/tm2/pkg/std" ) func AssertOriginCall(m *gno.Machine) { @@ -42,10 +46,9 @@ func TestCurrentRealm(m *gno.Machine) string { } func TestSkipHeights(m *gno.Machine, count int64) { - ctx := m.Context.(interface { - SkipHeights(i int64) any - }) - m.Context = ctx.SkipHeights(count) + ctx := m.Context.(std.ExecContext) + ctx.Height += count + m.Context = ctx } func ClearStoreCache(m *gno.Machine) { @@ -64,3 +67,50 @@ func ClearStoreCache(m *gno.Machine) { fmt.Println("========================================") } } + +func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { + if n <= 0 { + m.Panic(typedString("GetCallerAt requires positive arg")) + return "" + } + if n > m.NumFrames()-1 { + // NOTE: the last frame's LastPackage + // is set to the original non-frame + // package, so need this check. + m.Panic(typedString("frame not found")) + return "" + } + if n == m.NumFrames()-1 { + // This makes it consistent with GetOrigCaller and TestSetOrigCaller. + ctx := m.Context.(stdlibs.ExecContext) + return ctx.OrigCaller + } + return m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32() +} + +func TestSetOrigCaller(m *gno.Machine, addr crypto.Bech32Address) { + ctx := m.Context.(std.ExecContext) + ctx.OrigCaller = crypto.Bech32Address(addr) + m.Context = ctx +} + +func TestSetOrigPkgAddr(m *gno.Machine, addr crypto.Bech32Address) { + ctx := m.Context.(stdlibs.ExecContext) + ctx.OrigPkgAddr = addr + m.Context = ctx +} + +func TestSetOrigSend(m *gno.Machine, sent, spent tm2std.Coins) { + ctx := m.Context.(stdlibs.ExecContext) + ctx.OrigSend = sent + ctx.OrigSendSpent = &spent + m.Context = ctx +} + +func TestIssueCoins(m *gno.Machine, addr crypto.Bech32Address, coins tm2std.Coins) { + ctx := m.Context.(stdlibs.ExecContext) + banker := ctx.Banker + for _, coin := range coins { + banker.IssueCoin(addr, coin.Denom, coin.Amount) + } +} diff --git a/misc/devdeps/go.mod b/misc/devdeps/go.mod index 3dbbad75f5c..c05e1e2bd3f 100644 --- a/misc/devdeps/go.mod +++ b/misc/devdeps/go.mod @@ -4,7 +4,7 @@ go 1.18 require ( github.com/golangci/golangci-lint v1.53.3 // sync with github action - golang.org/x/tools v0.10.1-0.20230622221742-0622ad2359a7 + golang.org/x/tools v0.12.0 golang.org/x/tools/gopls v0.12.4 google.golang.org/protobuf v1.30.0 moul.io/testman v1.5.0 @@ -176,9 +176,9 @@ require ( go.uber.org/zap v1.24.0 // indirect golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect golang.org/x/exp/typeparams v0.0.0-20230224173230-c95f2b4c22f2 // indirect - golang.org/x/mod v0.11.0 // indirect + golang.org/x/mod v0.12.0 // indirect golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.9.0 // indirect + golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.10.0 // indirect golang.org/x/vuln v0.0.0-20230110180137-6ad3e3d07815 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/misc/devdeps/go.sum b/misc/devdeps/go.sum index 8c099ac57db..368f0addbb2 100644 --- a/misc/devdeps/go.sum +++ b/misc/devdeps/go.sum @@ -642,6 +642,8 @@ golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -769,6 +771,8 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -863,6 +867,8 @@ golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.10.1-0.20230622221742-0622ad2359a7 h1:5PWemM67wMSPpO0Y3lOPlyvgO3z56YkZRxPFcdd300g= golang.org/x/tools v0.10.1-0.20230622221742-0622ad2359a7/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= +golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/tools/gopls v0.12.4 h1:nce5etAamR46d9oNGxop1aRK5rDQ0NqcY/SHIcyfEKY= golang.org/x/tools/gopls v0.12.4/go.mod h1:TVMDG6cF53o2lhDFCp4nd0XfCp9MWdaYp3d2QxfZ9JI= golang.org/x/vuln v0.0.0-20230110180137-6ad3e3d07815 h1:A9kONVi4+AnuOr1dopsibH6hLi1Huy54cbeJxnq4vmU= diff --git a/misc/genstd/exprstring.go b/misc/genstd/exprstring.go new file mode 100644 index 00000000000..a93d7afbbb1 --- /dev/null +++ b/misc/genstd/exprstring.go @@ -0,0 +1,289 @@ +// Forked from go/types (go 1.20.3) to implement support for *linkedIdent. + +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file implements printing of expressions. + +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/types" +) + +const ( + printerModeGoQualified = iota + printerModeGnoType +) + +type exprPrinter struct { + mode int +} + +// ExprString returns the (possibly shortened) string representation for x. +// Shortened representations are suitable for user interfaces but may not +// necessarily follow Go syntax. +// +// ExprString is identical to [types.ExprString] with the difference that it +// supports *linkedIdent. +func (ep *exprPrinter) ExprString(x ast.Expr) string { + var buf bytes.Buffer + ep.WriteExpr(&buf, x) + return buf.String() +} + +// WriteExpr writes the (possibly shortened) string representation for x to buf. +// Shortened representations are suitable for user interfaces but may not +// necessarily follow Go syntax. +// +// WriteExpr is identical to [types.WriteExpr] with the difference that it +// supports *linkedIdent. +func (ep *exprPrinter) WriteExpr(buf *bytes.Buffer, x ast.Expr) { + // The AST preserves source-level parentheses so there is + // no need to introduce them here to correct for different + // operator precedences. (This assumes that the AST was + // generated by a Go parser.) + + switch x := x.(type) { + default: + // fallback to go original -- for all non-recursive ast.Expr types + types.WriteExpr(buf, x) + + case *linkedIdent: + switch ep.mode { + case printerModeGoQualified: + n := pkgNameFromPath(x.lt.goPackage) + buf.WriteString(n) + buf.WriteByte('.') + buf.WriteString(x.lt.goName) + case printerModeGnoType: + buf.WriteString(x.lt.gnoName) + default: + panic(fmt.Errorf("invalid mode %d", ep.mode)) + } + + case *ast.Ellipsis: + buf.WriteString("...") + if x.Elt != nil { + ep.WriteExpr(buf, x.Elt) + } + + case *ast.FuncLit: + buf.WriteByte('(') + ep.WriteExpr(buf, x.Type) + buf.WriteString(" literal)") // shortened + + case *ast.CompositeLit: + ep.WriteExpr(buf, x.Type) + buf.WriteByte('{') + if len(x.Elts) > 0 { + buf.WriteString("…") + } + buf.WriteByte('}') + + case *ast.ParenExpr: + buf.WriteByte('(') + ep.WriteExpr(buf, x.X) + buf.WriteByte(')') + + case *ast.SelectorExpr: + ep.WriteExpr(buf, x.X) + buf.WriteByte('.') + buf.WriteString(x.Sel.Name) + + case *ast.IndexExpr, *ast.IndexListExpr: + ix := tpUnpackIndexExpr(x) + ep.WriteExpr(buf, ix.X) + buf.WriteByte('[') + ep.writeExprList(buf, ix.Indices) + buf.WriteByte(']') + + case *ast.SliceExpr: + ep.WriteExpr(buf, x.X) + buf.WriteByte('[') + if x.Low != nil { + ep.WriteExpr(buf, x.Low) + } + buf.WriteByte(':') + if x.High != nil { + ep.WriteExpr(buf, x.High) + } + if x.Slice3 { + buf.WriteByte(':') + if x.Max != nil { + ep.WriteExpr(buf, x.Max) + } + } + buf.WriteByte(']') + + case *ast.TypeAssertExpr: + ep.WriteExpr(buf, x.X) + buf.WriteString(".(") + ep.WriteExpr(buf, x.Type) + buf.WriteByte(')') + + case *ast.CallExpr: + ep.WriteExpr(buf, x.Fun) + buf.WriteByte('(') + ep.writeExprList(buf, x.Args) + if x.Ellipsis.IsValid() { + buf.WriteString("...") + } + buf.WriteByte(')') + + case *ast.StarExpr: + buf.WriteByte('*') + ep.WriteExpr(buf, x.X) + + case *ast.UnaryExpr: + buf.WriteString(x.Op.String()) + ep.WriteExpr(buf, x.X) + + case *ast.BinaryExpr: + ep.WriteExpr(buf, x.X) + buf.WriteByte(' ') + buf.WriteString(x.Op.String()) + buf.WriteByte(' ') + ep.WriteExpr(buf, x.Y) + + case *ast.ArrayType: + buf.WriteByte('[') + if x.Len != nil { + ep.WriteExpr(buf, x.Len) + } + buf.WriteByte(']') + ep.WriteExpr(buf, x.Elt) + + case *ast.StructType: + buf.WriteString("struct{") + ep.writeFieldList(buf, x.Fields.List, "; ", false) + buf.WriteByte('}') + + case *ast.FuncType: + buf.WriteString("func") + ep.writeSigExpr(buf, x) + + case *ast.InterfaceType: + buf.WriteString("interface{") + ep.writeFieldList(buf, x.Methods.List, "; ", true) + buf.WriteByte('}') + + case *ast.MapType: + buf.WriteString("map[") + ep.WriteExpr(buf, x.Key) + buf.WriteByte(']') + ep.WriteExpr(buf, x.Value) + + case *ast.ChanType: + var s string + switch x.Dir { + case ast.SEND: + s = "chan<- " + case ast.RECV: + s = "<-chan " + default: + s = "chan " + } + buf.WriteString(s) + ep.WriteExpr(buf, x.Value) + } +} + +func (ep *exprPrinter) writeSigExpr(buf *bytes.Buffer, sig *ast.FuncType) { + buf.WriteByte('(') + ep.writeFieldList(buf, sig.Params.List, ", ", false) + buf.WriteByte(')') + + res := sig.Results + n := res.NumFields() + if n == 0 { + // no result + return + } + + buf.WriteByte(' ') + if n == 1 && len(res.List[0].Names) == 0 { + // single unnamed result + ep.WriteExpr(buf, res.List[0].Type) + return + } + + // multiple or named result(s) + buf.WriteByte('(') + ep.writeFieldList(buf, res.List, ", ", false) + buf.WriteByte(')') +} + +func (ep *exprPrinter) writeFieldList(buf *bytes.Buffer, list []*ast.Field, sep string, iface bool) { + for i, f := range list { + if i > 0 { + buf.WriteString(sep) + } + + // field list names + ep.writeIdentList(buf, f.Names) + + // types of interface methods consist of signatures only + if sig, _ := f.Type.(*ast.FuncType); sig != nil && iface { + ep.writeSigExpr(buf, sig) + continue + } + + // named fields are separated with a blank from the field type + if len(f.Names) > 0 { + buf.WriteByte(' ') + } + + ep.WriteExpr(buf, f.Type) + + // ignore tag + } +} + +func (ep *exprPrinter) writeIdentList(buf *bytes.Buffer, list []*ast.Ident) { + for i, x := range list { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(x.Name) + } +} + +func (ep *exprPrinter) writeExprList(buf *bytes.Buffer, list []ast.Expr) { + for i, x := range list { + if i > 0 { + buf.WriteString(", ") + } + ep.WriteExpr(buf, x) + } +} + +// The following are copied from go/internal/typeparams. +// We cannot use the original directly as it comes from an "internal" package. + +// tpIndexExpr wraps an ast.IndexExpr or ast.IndexListExpr. +// +// Orig holds the original ast.Expr from which this IndexExpr was derived. +type tpIndexExpr struct { + Orig ast.Expr // the wrapped expr, which may be distinct from the IndexListExpr below. + *ast.IndexListExpr +} + +func tpUnpackIndexExpr(n ast.Node) *tpIndexExpr { + switch e := n.(type) { + case *ast.IndexExpr: + return &tpIndexExpr{e, &ast.IndexListExpr{ + X: e.X, + Lbrack: e.Lbrack, + Indices: []ast.Expr{e.Index}, + Rbrack: e.Rbrack, + }} + case *ast.IndexListExpr: + return &tpIndexExpr{e, e} + } + return nil +} diff --git a/misc/genstd/genstd.go b/misc/genstd/genstd.go index 48639ca3aba..ec22a89f591 100644 --- a/misc/genstd/genstd.go +++ b/misc/genstd/genstd.go @@ -3,19 +3,15 @@ package main import ( - "errors" "fmt" "go/ast" "go/parser" "go/token" "io/fs" "os" - "os/exec" "path/filepath" - "reflect" "strconv" "strings" - "sync" "text/template" _ "embed" @@ -48,26 +44,27 @@ func _main(stdlibsPath string) error { return fmt.Errorf("not a directory: %q", stdlibsPath) } + // Gather data about each package, getting functions of interest + // (gno bodyless + go exported). pkgs, err := walkStdlibs(stdlibsPath) if err != nil { return err } - // Create mappings. + // Link up each Gno function with its matching Go function. mappings := linkFunctions(pkgs) - // Create file. + // Create generated file. f, err := os.Create("native.go") if err != nil { return fmt.Errorf("create native.go: %w", err) } defer f.Close() - // Execute template + // Execute template. td := &tplData{ Mappings: mappings, } - td.generateLibnums() if err := tpl.Execute(f, td); err != nil { return fmt.Errorf("execute template: %w", err) } @@ -75,16 +72,37 @@ func _main(stdlibsPath string) error { return err } - return runGofumpt() + // gofumpt doesn't do "import fixing" like goimports: + // https://github.com/mvdan/gofumpt#frequently-asked-questions + if err := runTool("golang.org/x/tools/cmd/goimports"); err != nil { + return err + } + return runTool("mvdan.cc/gofumpt") } type pkgData struct { importPath string fsDir string - gnoBodyless []*ast.FuncDecl - goExported []*ast.FuncDecl + gnoBodyless []funcDecl + goExported []funcDecl +} + +type funcDecl struct { + *ast.FuncDecl + imports []*ast.ImportSpec +} + +func addImports(fds []*ast.FuncDecl, imports []*ast.ImportSpec) []funcDecl { + r := make([]funcDecl, len(fds)) + for i, fd := range fds { + r[i] = funcDecl{fd, imports} + } + return r } +// walkStdlibs does a BFS walk through the given directory, expected to be a +// "stdlib" directory, parsing and keeping track of Go and Gno functions of +// interest. func walkStdlibs(stdlibsPath string) ([]*pkgData, error) { pkgs := make([]*pkgData, 0, 64) err := filepath.WalkDir(stdlibsPath, func(fpath string, d fs.DirEntry, err error) error { @@ -93,9 +111,10 @@ func walkStdlibs(stdlibsPath string) ([]*pkgData, error) { return nil } - // skip non-source files. + // skip non-source and test files. ext := filepath.Ext(fpath) - if ext != ".go" && ext != ".gno" { + noExt := fpath[:len(fpath)-len(ext)] + if (ext != ".go" && ext != ".gno") || strings.HasSuffix(noExt, "_test") { return nil } @@ -120,19 +139,20 @@ func walkStdlibs(stdlibsPath string) ([]*pkgData, error) { if ext == ".go" { // keep track of exported function declarations. // warn about all exported type, const and var declarations. - exp := resolveGnoMachine(f, filterExported(f)) - if len(exp) > 0 { - pkg.goExported = append(pkg.goExported, exp...) + if exp := filterExported(f); len(exp) > 0 { + pkg.goExported = append(pkg.goExported, addImports(exp, f.Imports)...) } } else if bd := filterBodylessFuncDecls(f); len(bd) > 0 { // gno file -- keep track of function declarations without body. - pkg.gnoBodyless = append(pkg.gnoBodyless, bd...) + pkg.gnoBodyless = append(pkg.gnoBodyless, addImports(bd, f.Imports)...) } return nil }) return pkgs, err } +// filterBodylessFuncDecls returns the function declarations in the given file +// which don't contain a body. func filterBodylessFuncDecls(f *ast.File) (bodyless []*ast.FuncDecl) { for _, decl := range f.Decls { fd, ok := decl.(*ast.FuncDecl) @@ -144,6 +164,7 @@ func filterBodylessFuncDecls(f *ast.File) (bodyless []*ast.FuncDecl) { return } +// filterExported returns the exported function declarations of the given file. func filterExported(f *ast.File) (exported []*ast.FuncDecl) { for _, decl := range f.Decls { switch d := decl.(type) { @@ -159,307 +180,39 @@ func filterExported(f *ast.File) (exported []*ast.FuncDecl) { return } -func resolveGnoMachine(f *ast.File, fns []*ast.FuncDecl) []*ast.FuncDecl { - iname := gnolangImportName(f) - if iname == "" { - return fns - } - for _, fn := range fns { - if len(fn.Type.Params.List) == 0 { - continue - } - first := fn.Type.Params.List[0] - if len(first.Names) > 1 { - continue - } - ind, ok := first.Type.(*ast.StarExpr) - if !ok { - continue - } - res, ok := ind.X.(*ast.SelectorExpr) - if !ok { - continue - } - if id, ok := res.X.(*ast.Ident); ok && id.Name == iname && res.Sel.Name == "Machine" { - id.Name = "#gnomachine" - } - } - return fns -} - -func gnolangImportName(f *ast.File) string { - for _, i := range f.Imports { - ipath, err := strconv.Unquote(i.Path.Value) - if err != nil { - continue - } - if ipath == "github.com/gnolang/gno/gnovm/pkg/gnolang" { - if i.Name == nil { - return "gnolang" - } - return i.Name.Name - } - } - return "" -} - -type mapping struct { - GnoImportPath string - GnoMethod string - GnoParamTypes []string - GnoResultTypes []string - GoImportPath string - GoFunc string - MachineParam bool -} - -func linkFunctions(pkgs []*pkgData) []mapping { - var mappings []mapping - for _, pkg := range pkgs { - for _, gb := range pkg.gnoBodyless { - nameWant := gb.Name.Name - if !gb.Name.IsExported() { - nameWant = "X_" + nameWant - } - fn := findFuncByName(pkg.goExported, nameWant) - if fn == nil { - logWarning("package %q: no matching go function declaration (%q) exists for function %q", - pkg.importPath, nameWant, gb.Name.Name) - continue - } - mp := mapping{ - GnoImportPath: pkg.importPath, - GnoMethod: gb.Name.Name, - GoImportPath: "github.com/gnolang/gno/" + relPath() + "/" + pkg.importPath, - GoFunc: fn.Name.Name, - } - if !mp.loadSignaturesMatch(gb, fn) { - logWarning("package %q: signature of gno function %s doesn't match signature of go function %s", - pkg.importPath, gb.Name.Name, fn.Name.Name) - continue - } - mappings = append(mappings, mp) - } - } - return mappings -} - -func findFuncByName(fns []*ast.FuncDecl, name string) *ast.FuncDecl { - for _, fn := range fns { - if fn.Name.Name == name { - return fn - } - } - return nil -} - -func (m *mapping) loadSignaturesMatch(gnof, gof *ast.FuncDecl) bool { - if gnof.Type.TypeParams != nil || gof.Type.TypeParams != nil { - panic("type parameters not supported") - } - // Ideally, signatures match when they accept the same types, - // or aliases. - gnop, gnor := fieldListToTypes(gnof.Type.Params), fieldListToTypes(gnof.Type.Results) - // store gno params and results in mapping - m.GnoParamTypes = gnop - m.GnoResultTypes = gnor - gop, gor := fieldListToTypes(gof.Type.Params), fieldListToTypes(gof.Type.Results) - if len(gop) > 0 && gop[0] == "*gno.Machine" { - m.MachineParam = true - gop = gop[1:] - } - return reflect.DeepEqual(gnop, gop) && reflect.DeepEqual(gnor, gor) -} - -// TODO: this is created based on the uverse definitions. This should be -// centralized, or at least have a CI/make check to make sure this stays the -// same -var builtinTypes = [...]string{ - "bool", - "string", - "int", - "int8", - "int16", - "rune", - "int32", - "int64", - "uint", - "byte", - "uint8", - "uint16", - "uint32", - "uint64", - "bigint", - "float32", - "float64", - "error", - "any", -} - -func validIdent(name string) bool { - for _, t := range builtinTypes { - if name == t { - return true - } - } - return false -} - -func exprToString(e ast.Expr) string { - switch e := e.(type) { - case *ast.Ident: - if !validIdent(e.Name) { - panic(fmt.Sprintf("ident is not builtin: %q", e.Name)) - } - return e.Name - case *ast.Ellipsis: - return "..." + exprToString(e.Elt) - case *ast.SelectorExpr: - if x, ok := e.X.(*ast.Ident); ok && x.Name == "#gnomachine" { - return "gno.Machine" - } - panic("SelectorExpr not supported") - case *ast.StarExpr: - return "*" + exprToString(e.X) - case *ast.ArrayType: - var ls string - if e.Len != nil { - switch e.Len.(type) { - case *ast.Ellipsis: - ls = "..." - } - } - return "[" + ls + "]" + exprToString(e.Elt) - case *ast.StructType: - if len(e.Fields.List) > 0 { - panic("structs with values not supported yet") - } - return "struct{}" - case *ast.FuncType: - return "func(" + strings.Join(fieldListToTypes(e.Params), ", ") + ")" + strings.Join(fieldListToTypes(e.Results), ", ") - case *ast.InterfaceType: - if len(e.Methods.List) > 0 { - panic("interfaces with methods not supported yet") - } - return "interface{}" - case *ast.MapType: - return "map[" + exprToString(e.Key) + "]" + exprToString(e.Value) - default: - panic(fmt.Sprintf("invalid expression as func param/return type: %T", e)) - } -} +//go:embed template.tmpl +var templateText string -func fieldListToTypes(fl *ast.FieldList) []string { - if fl == nil { - return nil - } - r := make([]string, 0, len(fl.List)) - for _, f := range fl.List { - ts := exprToString(f.Type) - times := len(f.Names) - if times == 0 { - // case of unnamed params; such as return values (often) - times = 1 - } - for i := 0; i < times; i++ { - r = append(r, ts) - } - } - return r -} +var tpl = template.Must(template.New("").Parse(templateText)) +// tplData is the data passed to the template. type tplData struct { Mappings []mapping - LibNums []string } -//go:embed template.tmpl -var templateText string +type tplImport struct{ Name, Path string } -var tpl = template.Must(template.New("").Parse(templateText)) - -func (t tplData) FindLibNum(s string) (int, error) { - for i, v := range t.LibNums { - if v == s { - return i, nil +func (t tplData) Imports() (res []tplImport) { + add := func(path string) { + for _, v := range res { + if v.Path == path { + return + } } + res = append(res, tplImport{Name: pkgNameFromPath(path), Path: path}) } - return -1, fmt.Errorf("could not find lib: %q", s) -} - -func (t *tplData) generateLibnums() { - var last string for _, m := range t.Mappings { - if m.GoImportPath != last { - t.LibNums = append(t.LibNums, m.GoImportPath) - last = m.GoImportPath - } - } -} - -func runGofumpt() error { - gr := gitRoot() - - cmd := exec.Command( - "go", "run", "-modfile", filepath.Join(gr, "misc/devdeps/go.mod"), - "mvdan.cc/gofumpt", "-w", "native.go", - ) - _, err := cmd.Output() - if err != nil { - if err, ok := err.(*exec.ExitError); ok { - return fmt.Errorf("error executing gofumpt: %w; output: %v", err, string(err.Stderr)) + add(m.GoImportPath) + // There might be a bit more than we need - but we run goimports to fix that. + for _, v := range m.goImports { + s, err := strconv.Unquote(v.Path.Value) + if err != nil { + panic(fmt.Errorf("could not unquote go import string literal: %s", v.Path.Value)) + } + add(s) } - return fmt.Errorf("error executing gofumpt: %w", err) } - return nil -} - -var ( - memoGitRoot string - memoRelPath string - - dirsOnce sync.Once -) - -func gitRoot() string { - dirsOnceDo() - return memoGitRoot -} - -func relPath() string { - dirsOnceDo() - return memoRelPath -} - -func dirsOnceDo() { - dirsOnce.Do(func() { - var err error - memoGitRoot, memoRelPath, err = findDirs() - if err != nil { - panic(fmt.Errorf("could not determine git root: %w", err)) - } - }) + return } -func findDirs() (gitRoot string, relPath string, err error) { - abs, err := filepath.Abs(".") - if err != nil { - return - } - p := abs - for { - if s, e := os.Stat(filepath.Join(p, ".git")); e == nil && s.IsDir() { - // make relPath relative to the git root - rp := strings.TrimPrefix(abs, p+string(filepath.Separator)) - // normalize separator to / - rp = strings.ReplaceAll(rp, string(filepath.Separator), "/") - return p, rp, nil - } - - if strings.HasSuffix(p, string(filepath.Separator)) { - return "", "", errors.New("root git not found") - } - - p = filepath.Dir(p) - } -} +func (tplData) PkgName(path string) string { return pkgNameFromPath(path) } diff --git a/misc/genstd/mapping.go b/misc/genstd/mapping.go new file mode 100644 index 00000000000..03632cdef77 --- /dev/null +++ b/misc/genstd/mapping.go @@ -0,0 +1,446 @@ +package main + +import ( + "fmt" + "go/ast" + "path" + "strconv" +) + +type mapping struct { + GnoImportPath string // time + GnoFunc string // now + GoImportPath string // github.com/gnolang/gno/gnovm/stdlibs/time + GoFunc string // X_now + Params []mappingType + Results []mappingType + MachineParam bool + + gnoImports []*ast.ImportSpec + goImports []*ast.ImportSpec +} + +type mappingType struct { + // type of ast.Expr is from the normal ast.Expr types + // + *linkedIdent. + Type ast.Expr +} + +func (mt mappingType) GoQualifiedName() string { + return (&exprPrinter{ + mode: printerModeGoQualified, + }).ExprString(mt.Type) +} + +func (mt mappingType) GnoType() string { + return (&exprPrinter{ + mode: printerModeGnoType, + }).ExprString(mt.Type) +} + +type linkedIdent struct { + ast.BadExpr // Unused, but it makes *linkedIdent implement ast.Expr + + lt linkedType +} + +func linkFunctions(pkgs []*pkgData) []mapping { + var mappings []mapping + for _, pkg := range pkgs { + for _, gb := range pkg.gnoBodyless { + nameWant := gb.Name.Name + if !gb.Name.IsExported() { + nameWant = "X_" + nameWant + } + fn := findFuncByName(pkg.goExported, nameWant) + if fn.FuncDecl == nil { + logWarning("package %q: no matching go function declaration (%q) exists for function %q", + pkg.importPath, nameWant, gb.Name.Name) + continue + } + mp := mapping{ + GnoImportPath: pkg.importPath, + GnoFunc: gb.Name.Name, + GoImportPath: "github.com/gnolang/gno/" + relPath() + "/" + pkg.importPath, + GoFunc: fn.Name.Name, + + gnoImports: gb.imports, + goImports: fn.imports, + } + if !mp.signaturesMatch(gb, fn) { + logWarning("package %q: signature of gno function %s doesn't match signature of go function %s", + pkg.importPath, gb.Name.Name, fn.Name.Name) + continue + } + mp.loadParamsResults(gb, fn) + mappings = append(mappings, mp) + } + } + return mappings +} + +func findFuncByName(fns []funcDecl, name string) funcDecl { + for _, fn := range fns { + if fn.Name.Name == name { + return fn + } + } + return funcDecl{} +} + +func (m *mapping) loadParamsResults(gnof, gof funcDecl) { + // initialise with lengths + m.Params = make([]mappingType, 0, gnof.Type.Params.NumFields()) + m.Results = make([]mappingType, 0, gnof.Type.Results.NumFields()) + + gofpl := gof.Type.Params.List + if m.MachineParam { + // skip machine parameter + gofpl = gofpl[1:] + } + if gnof.Type.Params != nil { + m._loadParamsResults(&m.Params, gnof.Type.Params.List, gofpl) + } + if gnof.Type.Results != nil { + m._loadParamsResults(&m.Results, gnof.Type.Results.List, gof.Type.Results.List) + } +} + +func (m *mapping) _loadParamsResults(dst *[]mappingType, gnol, gol []*ast.Field) { + // we know at this point in code that the signatures match; + // however, we still need to guard against cases like this one: + // func(n, n1 int) and func(n int, n1 int). + // these generate two different param lists, so we need to keep track of + // where we are in each of the two lists separately. + var goIdx, goNameIdx int + goAdvance := func() { + goNameIdx++ + if goNameIdx >= len(gol[goIdx].Names) { + goIdx++ + goNameIdx = 0 + } + } + + for _, l := range gnol { + n := len(l.Names) + if n == 0 { + n = 1 + } + gnoe := l.Type + for i := 0; i < n; i++ { + goe := gol[goIdx].Type + merged := m.mergeTypes(gnoe, goe) + *dst = append(*dst, mappingType{Type: merged}) + + goAdvance() + } + } +} + +// mergeTypes merges gnoe and goe into a single ast.Expr. +// +// gnoe and goe are expected to have the same underlying structure, but they +// may differ in their type identifiers (possibly qualified, ie pkg.T). +// if they differ, mergeTypes returns nil. +// +// When two type identifiers are found, they are checked against the list of +// linkedTypes to determine if they refer to a linkedType. If they are not, +// mergeTypes returns nil. If they are, the *ast.Ident/*ast.SelectorExpr is +// replaced with a *linkedIdent. +// +// mergeTypes does not modify the given gnoe or goe; the returned ast.Expr is +// (recursively) newly allocated. +func (m *mapping) mergeTypes(gnoe, goe ast.Expr) ast.Expr { + resolveGoNamed := func(lt *linkedType) bool { + switch goe := goe.(type) { + case *ast.SelectorExpr: + // selector - resolve pkg ident to path + lt.goPackage = resolveSelectorImport(m.goImports, goe) + lt.goName = goe.Sel.Name + case *ast.Ident: + // local name -- use import path of go pkg + lt.goPackage = m.GoImportPath + lt.goName = goe.Name + default: + return false + } + return true + } + + switch gnoe := gnoe.(type) { + // We're working with a subset of all expressions: + // https://go.dev/ref/spec#Type + + case *ast.SelectorExpr: + lt := linkedType{ + gnoPackage: resolveSelectorImport(m.gnoImports, gnoe), + gnoName: gnoe.Sel.Name, + } + if !resolveGoNamed(<) || !linkedTypeExists(lt) { + return nil + } + return &linkedIdent{lt: lt} + case *ast.Ident: + // easy case - built-in identifiers + goi, ok := goe.(*ast.Ident) + if ok && isBuiltin(gnoe.Name) && gnoe.Name == goi.Name { + return &ast.Ident{Name: gnoe.Name} + } + + lt := linkedType{ + gnoPackage: m.GnoImportPath, + gnoName: gnoe.Name, + } + if !resolveGoNamed(<) || !linkedTypeExists(lt) { + return nil + } + return &linkedIdent{lt: lt} + + // easier cases -- check for equality of structure and underlying types + case *ast.Ellipsis: + goe, ok := goe.(*ast.Ellipsis) + if !ok { + return nil + } + elt := m.mergeTypes(gnoe.Elt, goe.Elt) + if elt == nil { + return nil + } + return &ast.Ellipsis{Elt: elt} + case *ast.StarExpr: + goe, ok := goe.(*ast.StarExpr) + if !ok { + return nil + } + x := m.mergeTypes(gnoe.X, goe.X) + if x == nil { + return nil + } + return &ast.StarExpr{X: x} + case *ast.ArrayType: + goe, ok := goe.(*ast.ArrayType) + if !ok || !basicLitsEqual(gnoe.Len, goe.Len) { + return nil + } + elt := m.mergeTypes(gnoe.Elt, goe.Elt) + if elt == nil { + return nil + } + return &ast.ArrayType{Len: gnoe.Len, Elt: elt} + case *ast.StructType, + *ast.FuncType, + *ast.InterfaceType, + *ast.MapType: + // TODO + panic("not implemented") + default: + panic(fmt.Errorf("invalid expression as func param/return type: %T (%v)", gnoe, gnoe)) + } +} + +// returns full import path from package ident +func resolveImport(imports []*ast.ImportSpec, ident string) string { + for _, i := range imports { + s, err := strconv.Unquote(i.Path.Value) + if err != nil { + panic(fmt.Errorf("could not unquote import path literal: %s", i.Path.Value)) + } + + // TODO: for simplicity, if i.Name is nil we assume the name to be == + // to the last part of the import path. + // ideally, use importer to resolve package directory on user's FS and + // resolve by parsing and reading package clause + var name string + if i.Name != nil { + name = i.Name.Name + } else { + name = path.Base(s) + } + + if name == ident { + return s + } + } + return "" +} + +func resolveSelectorImport(imports []*ast.ImportSpec, sx *ast.SelectorExpr) string { + pkgIdent, ok := sx.X.(*ast.Ident) + if !ok { + panic(fmt.Errorf("encountered unhandled SelectorExpr.X type: %T (%v)", sx.X, sx)) + } + impPath := resolveImport(imports, pkgIdent.Name) + if impPath == "" { + panic(fmt.Errorf( + "unknown identifier %q (for resolving type %q)", + pkgIdent.Name, pkgIdent.Name+"."+sx.Sel.Name, + )) + } + return impPath +} + +// simple equivalence between two BasicLits. +// Note that this returns true only if the expressions are exactly the same; +// ie. 16 != 0x10, only 16 == 16. +func basicLitsEqual(x1, x2 ast.Expr) bool { + if x1 == nil || x2 == nil { + return x1 == nil && x2 == nil + } + l1, ok1 := x1.(*ast.BasicLit) + l2, ok2 := x2.(*ast.BasicLit) + if !ok1 || !ok2 { + return false + } + return l1.Value == l2.Value +} + +// Signatures match when they accept the same elementary types, or a linked +// type mapping (see [linkedTypes]). +// +// Additionally, if the first parameter to the Go function is +// *[gnolang.Machine], it is ignored when matching to the Gno function. +func (m *mapping) signaturesMatch(gnof, gof funcDecl) bool { + if gnof.Type.TypeParams != nil || gof.Type.TypeParams != nil { + panic("type parameters not supported") + } + + // if first param of go function is *gno.Machine, remove it + gofp := gof.Type.Params + if len(gofp.List) > 0 && m.isGnoMachine(gofp.List[0]) { + // avoid touching original struct + n := *gofp + n.List = n.List[1:] + gofp = &n + + m.MachineParam = true + } + + return m.fieldListsMatch(gnof.Type.Params, gofp) && + m.fieldListsMatch(gnof.Type.Results, gof.Type.Results) +} + +func (m *mapping) isGnoMachine(field *ast.Field) bool { + if len(field.Names) > 1 { + return false + } + + px, ok := field.Type.(*ast.StarExpr) + if !ok { + return false + } + + sx, ok := px.X.(*ast.SelectorExpr) + if !ok { + return false + } + + imp := resolveSelectorImport(m.goImports, sx) + return imp == "github.com/gnolang/gno/gnovm/pkg/gnolang" && sx.Sel.Name == "Machine" +} + +func (m *mapping) fieldListsMatch(gnofl, gofl *ast.FieldList) bool { + if gnofl == nil || gofl == nil { + return gnofl == nil && gofl == nil + } + gnots, gots := fieldListToTypes(gnofl), fieldListToTypes(gofl) + if len(gnots) != len(gots) { + return false + } + for idx, gnot := range gnots { + if m.mergeTypes(gnot, gots[idx]) == nil { + return false + } + } + return true +} + +func fieldListToTypes(fl *ast.FieldList) []ast.Expr { + e := make([]ast.Expr, 0, len(fl.List)) + for _, f := range fl.List { + nnames := len(f.Names) + if nnames < 1 { + // case of unnamed param (ie. `func X(int) {}`) + nnames = 1 + } + for i := 0; i < nnames; i++ { + e = append(e, f.Type) + } + } + return e +} + +// TODO: this is created based on the uverse definitions. This should be +// centralized, or at least have a CI/make check to make sure this stays the +// same +var builtinTypes = [...]string{ + "bool", + "string", + "int", + "int8", + "int16", + "rune", + "int32", + "int64", + "uint", + "byte", + "uint8", + "uint16", + "uint32", + "uint64", + "bigint", + "float32", + "float64", + "error", +} + +func isBuiltin(name string) bool { + for _, x := range builtinTypes { + if x == name { + return true + } + } + return false +} + +type linkedType struct { + gnoPackage string + gnoName string + goPackage string + goName string +} + +var linkedTypes = [...]linkedType{ + { + "std", "Address", + "github.com/gnolang/gno/tm2/pkg/crypto", "Bech32Address", + }, + { + "std", "Coin", + "github.com/gnolang/gno/tm2/pkg/std", "Coin", + }, + { + "std", "Coins", + "github.com/gnolang/gno/tm2/pkg/std", "Coins", + }, + { + "std", "Realm", + "github.com/gnolang/gno/gnovm/stdlibs/std", "Realm", + }, + { + "std", "BankerType", + "github.com/gnolang/gno/gnovm/stdlibs/std", "BankerType", + }, + { + "std", "Banker", + "github.com/gnolang/gno/gnovm/stdlibs/std", "Banker", + }, +} + +func linkedTypeExists(lt linkedType) bool { + for _, ltx := range linkedTypes { + if lt == ltx { + return true + } + } + return false +} diff --git a/misc/genstd/template.tmpl b/misc/genstd/template.tmpl index a692f0d9aa1..4d164993979 100644 --- a/misc/genstd/template.tmpl +++ b/misc/genstd/template.tmpl @@ -7,8 +7,8 @@ import ( "reflect" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" -{{- range $i, $m := .LibNums }} - lib{{ $i }} {{ printf "%q" $m }} +{{- range .Imports }} + {{ .Name }} {{ printf "%q" .Path }} {{- end }} ) @@ -24,48 +24,48 @@ var nativeFuncs = [...]nativeFunc{ {{- range $i, $m := .Mappings }} { {{ printf "%q" $m.GnoImportPath }}, - {{ printf "%q" $m.GnoMethod }}, - {{/*- TODO set nil if empty -*/}} + {{ printf "%q" $m.GnoFunc }}, + {{- /* TODO: set nil if empty */}} []gno.FieldTypeExpr{ - {{- range $i, $p := $m.GnoParamTypes }} - {Name: gno.N("p{{ $i }}"), Type: gno.X({{ printf "%q" $p }})}, + {{- range $i, $p := $m.Params }} + {Name: gno.N("p{{ $i }}"), Type: gno.X({{ printf "%q" $p.GnoType }})}, {{- end }} }, []gno.FieldTypeExpr{ - {{- range $i, $r := $m.GnoResultTypes }} - {Name: gno.N("r{{ $i }}"), Type: gno.X({{ printf "%q" $r }})}, + {{- range $i, $r := $m.Results }} + {Name: gno.N("r{{ $i }}"), Type: gno.X({{ printf "%q" $r.GnoType }})}, {{- end }} }, func(m *gno.Machine) { - {{ if $m.GnoParamTypes -}} + {{ if $m.Params -}} b := m.LastBlock() var ( - {{- range $pn, $pv := $m.GnoParamTypes }} - p{{ $pn }} {{ $pv }} + {{- range $pn, $pv := $m.Params }} + p{{ $pn }} {{ $pv.GoQualifiedName }} rp{{ $pn }} = reflect.ValueOf(&p{{ $pn }}).Elem() {{- end }} ) - {{ range $pn, $pv := $m.GnoParamTypes -}} + {{ range $pn, $pv := $m.Params -}} gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, {{ $pn }}, "")).TV, rp{{ $pn }}) {{ end }} {{- end }} - {{ range $rn, $rv := $m.GnoResultTypes -}} + {{ range $rn, $rv := $m.Results -}} {{- if gt $rn 0 -}}, {{ end -}} r{{ $rn }} {{- end -}} - {{- if gt (len $m.GnoResultTypes) 0 }} := {{ end -}} - lib{{ $.FindLibNum $m.GoImportPath }}.{{ $m.GoFunc }}( + {{- if $m.Results }} := {{ end -}} + {{ $.PkgName $m.GoImportPath }}.{{ $m.GoFunc }}( {{- if $m.MachineParam }} m, {{ end -}} - {{- range $pn, $pv := $m.GnoParamTypes -}} + {{- range $pn, $pv := $m.Params -}} p{{ $pn }}, {{- end -}} ) - {{ range $rn, $rv := $m.GnoResultTypes -}} + {{ range $rn, $rv := $m.Results -}} m.PushValue(gno.Go2GnoValue( m.Alloc, m.Store, diff --git a/misc/genstd/util.go b/misc/genstd/util.go new file mode 100644 index 00000000000..850ba43d133 --- /dev/null +++ b/misc/genstd/util.go @@ -0,0 +1,119 @@ +package main + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + "sync" +) + +func runTool(importPath string) error { + shortName := path.Base(importPath) + gr := gitRoot() + + cmd := exec.Command( + "go", "run", "-modfile", filepath.Join(gr, "misc/devdeps/go.mod"), + importPath, "-w", "native.go", + ) + _, err := cmd.Output() + if err != nil { + if err, ok := err.(*exec.ExitError); ok { + return fmt.Errorf("error executing %s: %w; output: %v", shortName, err, string(err.Stderr)) + } + return fmt.Errorf("error executing %s: %w", shortName, err) + } + return nil +} + +var ( + memoGitRoot string + memoRelPath string + + dirsOnce sync.Once +) + +func gitRoot() string { + dirsOnceDo() + return memoGitRoot +} + +func relPath() string { + dirsOnceDo() + return memoRelPath +} + +func dirsOnceDo() { + dirsOnce.Do(func() { + var err error + memoGitRoot, memoRelPath, err = findDirs() + if err != nil { + panic(fmt.Errorf("could not determine git root: %w", err)) + } + }) +} + +func findDirs() (gitRoot string, relPath string, err error) { + abs, err := filepath.Abs(".") + if err != nil { + return + } + p := abs + for { + if s, e := os.Stat(filepath.Join(p, ".git")); e == nil && s.IsDir() { + // make relPath relative to the git root + rp := strings.TrimPrefix(abs, p+string(filepath.Separator)) + // normalize separator to / + rp = strings.ReplaceAll(rp, string(filepath.Separator), "/") + return p, rp, nil + } + + if strings.HasSuffix(p, string(filepath.Separator)) { + return "", "", errors.New("root git not found") + } + + p = filepath.Dir(p) + } +} + +// pkgNameFromPath derives the package name from the given path, +// unambiguously for the most part (so safe for the code generation). +// +// The path is taken and possibly shortened if it starts with a known prefix. +// For instance, github.com/gnolang/gno/stdlibs/std simply becomes "libs_std". +// "Unsafe" characters are removed (ie. invalid for go identifiers). +func pkgNameFromPath(path string) string { + const ( + repoPrefix = "github.com/gnolang/gno/" + vmPrefix = repoPrefix + "gnovm/" + tm2Prefix = repoPrefix + "tm2/pkg/" + libsPrefix = vmPrefix + "stdlibs/" + testlibsPrefix = vmPrefix + "tests/stdlibs/" + ) + + ns := "ext" + switch { + case strings.HasPrefix(path, testlibsPrefix): + ns, path = "testlibs", path[len(testlibsPrefix):] + case strings.HasPrefix(path, libsPrefix): + ns, path = "libs", path[len(libsPrefix):] + case strings.HasPrefix(path, vmPrefix): + ns, path = "vm", path[len(vmPrefix):] + case strings.HasPrefix(path, tm2Prefix): + ns, path = "tm2", path[len(tm2Prefix):] + case strings.HasPrefix(path, repoPrefix): + ns, path = "repo", path[len(repoPrefix):] + case !strings.Contains(path, "."): + ns = "go" + } + + flds := strings.FieldsFunc(path, func(r rune) bool { + return (r < 'a' || r > 'z') && + (r < 'A' || r > 'Z') && + (r < '0' || r > '9') + }) + return ns + "_" + strings.Join(flds, "_") +} diff --git a/tm2/pkg/sdk/vm/builtins.go b/tm2/pkg/sdk/vm/builtins.go index ce14ca42659..9add501cb6c 100644 --- a/tm2/pkg/sdk/vm/builtins.go +++ b/tm2/pkg/sdk/vm/builtins.go @@ -35,7 +35,6 @@ func (vm *VMKeeper) initBuiltinPackagesAndTypes(store gno.Store) { return m2.RunMemPackage(memPkg, true) } store.SetPackageGetter(getPackage) - store.SetPackageInjector(stdlibs.InjectPackage) store.SetNativeStore(stdlibs.NativeStore) stdlibs.InjectNativeMappings(store) } From 3560f428d10fb87b52ac24b5ef8e63a04d4a45b7 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 23 Aug 2023 14:29:53 +0200 Subject: [PATCH 18/59] add native.go files as generated --- .gitattributes | 2 ++ gnovm/tests/file.go | 2 +- misc/genstd/template.tmpl | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index a0c478da1fc..c22d136ec50 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,5 @@ *.gno linguist-language=Go *.pb.go linguist-generated merge=ours -diff go.sum linguist-generated text +gnovm/stdlibs/native.go linguist-generated +gnovm/tests/stdlibs/native.go linguist-generated diff --git a/gnovm/tests/file.go b/gnovm/tests/file.go index 72de824987b..451bf0677dc 100644 --- a/gnovm/tests/file.go +++ b/gnovm/tests/file.go @@ -283,7 +283,7 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { if tv, ok := pnc.(*gno.TypedValue); ok { panic(fmt.Sprintf("fail on %s: got unexpected error: %s", path, tv.Sprint(m))) } else { // happens on 'unknown import path ...' - panic(fmt.Sprintf("fail on %s: got unexpected error: %#v", path, pnc)) + panic(fmt.Sprintf("fail on %s: got unexpected error: %v", path, pnc)) } } // check result diff --git a/misc/genstd/template.tmpl b/misc/genstd/template.tmpl index 4d164993979..d037ecdd7a0 100644 --- a/misc/genstd/template.tmpl +++ b/misc/genstd/template.tmpl @@ -1,4 +1,4 @@ -// This file is autogenerated from the genstd tool (@/misc/stdgen); do not edit. +// This file is autogenerated from the genstd tool (@/misc/genstd); do not edit. // To regenerate it, run `go generate` from this directory. package stdlibs From 645230550699cd3dcf22247b27307609cce6cd89 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 23 Aug 2023 14:58:02 +0200 Subject: [PATCH 19/59] add // injected comment on all injected fns --- gnovm/stdlibs/crypto/sha256/sha256.gno | 2 +- gnovm/stdlibs/math/native.gno | 8 ++++---- gnovm/stdlibs/native.go | 2 +- gnovm/stdlibs/strconv/strconv.gno | 18 ++++++++---------- gnovm/stdlibs/time/time.gno | 2 +- gnovm/tests/stdlibs/native.go | 2 +- gnovm/tests/stdlibs/std/std.go | 2 +- 7 files changed, 17 insertions(+), 19 deletions(-) diff --git a/gnovm/stdlibs/crypto/sha256/sha256.gno b/gnovm/stdlibs/crypto/sha256/sha256.gno index 3d0c6cf3611..c36313f5482 100644 --- a/gnovm/stdlibs/crypto/sha256/sha256.gno +++ b/gnovm/stdlibs/crypto/sha256/sha256.gno @@ -5,4 +5,4 @@ const Size = 32 // Sum returns the SHA-256 checksum of the data. func Sum256(data []byte) [Size]byte { return sum256(data) } -func sum256(data []byte) [32]byte +func sum256(data []byte) [32]byte // injected diff --git a/gnovm/stdlibs/math/native.gno b/gnovm/stdlibs/math/native.gno index 30a758558d1..b1b5684f9af 100644 --- a/gnovm/stdlibs/math/native.gno +++ b/gnovm/stdlibs/math/native.gno @@ -3,19 +3,19 @@ package math // Float32bits returns the IEEE 754 binary representation of f, with the sign // bit of f and the result in the same bit position. // Float32bits(Float32frombits(x)) == x. -func Float32bits(f float32) uint32 +func Float32bits(f float32) uint32 // injected // Float32frombits returns the floating-point number corresponding to the IEEE // 754 binary representation b, with the sign bit of b and the result in the // same bit position. Float32frombits(Float32bits(x)) == x. -func Float32frombits(b uint32) float32 +func Float32frombits(b uint32) float32 // injected // Float64bits returns the IEEE 754 binary representation of f, with the sign // bit of f and the result in the same bit position. // Float64bits(Float64frombits(x)) == x. -func Float64bits(f float64) uint64 +func Float64bits(f float64) uint64 // injected // Float64frombits returns the floating-point number corresponding to the IEEE // 754 binary representation b, with the sign bit of b and the result in the // same bit position. Float64frombits(Float64bits(x)) == x. -func Float64frombits(b uint64) float64 +func Float64frombits(b uint64) float64 // injected diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go index 028c75a9ff2..069ae140226 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/native.go @@ -1,4 +1,4 @@ -// This file is autogenerated from the genstd tool (@/misc/stdgen); do not edit. +// This file is autogenerated from the genstd tool (@/misc/genstd); do not edit. // To regenerate it, run `go generate` from this directory. package stdlibs diff --git a/gnovm/stdlibs/strconv/strconv.gno b/gnovm/stdlibs/strconv/strconv.gno index 600aab905a5..bc7b5d8d1b6 100644 --- a/gnovm/stdlibs/strconv/strconv.gno +++ b/gnovm/stdlibs/strconv/strconv.gno @@ -1,12 +1,10 @@ package strconv -// All injected - -func Itoa(n int) string -func AppendUint(dst []byte, i uint64, base int) []byte -func Atoi(s string) (int, error) -func CanBackquote(s string) bool -func FormatInt(i int64, base int) string -func FormatUint(i uint64, base int) string -func Quote(s string) string -func QuoteToASCII(s string) string +func Itoa(n int) string // injected +func AppendUint(dst []byte, i uint64, base int) []byte // injected +func Atoi(s string) (int, error) // injected +func CanBackquote(s string) bool // injected +func FormatInt(i int64, base int) string // injected +func FormatUint(i uint64, base int) string // injected +func Quote(s string) string // injected +func QuoteToASCII(s string) string // injected diff --git a/gnovm/stdlibs/time/time.gno b/gnovm/stdlibs/time/time.gno index aa81f972501..ceed70452f6 100644 --- a/gnovm/stdlibs/time/time.gno +++ b/gnovm/stdlibs/time/time.gno @@ -1069,7 +1069,7 @@ func daysSinceEpoch(year int) uint64 { return d } -func now() (sec int64, nsec int32, mono int64) +func now() (sec int64, nsec int32, mono int64) // injected // runtimeNano returns the current value of the runtime clock in nanoseconds. // diff --git a/gnovm/tests/stdlibs/native.go b/gnovm/tests/stdlibs/native.go index 129508a5132..18d281bebe0 100644 --- a/gnovm/tests/stdlibs/native.go +++ b/gnovm/tests/stdlibs/native.go @@ -1,4 +1,4 @@ -// This file is autogenerated from the genstd tool (@/misc/stdgen); do not edit. +// This file is autogenerated from the genstd tool (@/misc/genstd); do not edit. // To regenerate it, run `go generate` from this directory. package stdlibs diff --git a/gnovm/tests/stdlibs/std/std.go b/gnovm/tests/stdlibs/std/std.go index ea2ff453f9e..27ad079b4a6 100644 --- a/gnovm/tests/stdlibs/std/std.go +++ b/gnovm/tests/stdlibs/std/std.go @@ -90,7 +90,7 @@ func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { func TestSetOrigCaller(m *gno.Machine, addr crypto.Bech32Address) { ctx := m.Context.(std.ExecContext) - ctx.OrigCaller = crypto.Bech32Address(addr) + ctx.OrigCaller = addr m.Context = ctx } From ed813318bb9dfd15501f89eac82e8dae202c79b4 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 23 Aug 2023 15:33:45 +0200 Subject: [PATCH 20/59] address PR comments --- misc/genstd/genstd.go | 8 -------- misc/genstd/mapping.go | 14 ++++++++------ 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/misc/genstd/genstd.go b/misc/genstd/genstd.go index ec22a89f591..318a63e5ee8 100644 --- a/misc/genstd/genstd.go +++ b/misc/genstd/genstd.go @@ -28,14 +28,6 @@ func main() { } } -// for now a simple call to fmt.Fprintf, but could be improved -func logWarning(format string, v ...any) { - // TODO: add these at the top of the generated file as a comment - // so that if there are exceptions to make these are made - // consciously in code review. - fmt.Fprintf(os.Stderr, "warn: "+format+"\n", v...) -} - func _main(stdlibsPath string) error { stdlibsPath = filepath.Clean(stdlibsPath) if s, err := os.Stat(stdlibsPath); err != nil { diff --git a/misc/genstd/mapping.go b/misc/genstd/mapping.go index 03632cdef77..0acfb55e6e3 100644 --- a/misc/genstd/mapping.go +++ b/misc/genstd/mapping.go @@ -54,9 +54,10 @@ func linkFunctions(pkgs []*pkgData) []mapping { } fn := findFuncByName(pkg.goExported, nameWant) if fn.FuncDecl == nil { - logWarning("package %q: no matching go function declaration (%q) exists for function %q", - pkg.importPath, nameWant, gb.Name.Name) - continue + panic( + fmt.Errorf("package %q: no matching go function declaration (%q) exists for function %q", + pkg.importPath, nameWant, gb.Name.Name), + ) } mp := mapping{ GnoImportPath: pkg.importPath, @@ -68,9 +69,10 @@ func linkFunctions(pkgs []*pkgData) []mapping { goImports: fn.imports, } if !mp.signaturesMatch(gb, fn) { - logWarning("package %q: signature of gno function %s doesn't match signature of go function %s", - pkg.importPath, gb.Name.Name, fn.Name.Name) - continue + panic( + fmt.Errorf("package %q: signature of gno function %s doesn't match signature of go function %s", + pkg.importPath, gb.Name.Name, fn.Name.Name), + ) } mp.loadParamsResults(gb, fn) mappings = append(mappings, mp) From b661430412e671090fb319cc6cadac5cb2a5dcab Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 23 Aug 2023 16:07:47 +0200 Subject: [PATCH 21/59] support returning gno.TypedValue, for specialcasing GetBanker --- gnovm/stdlibs/native.go | 6 +----- gnovm/stdlibs/std/native.go | 12 +++++++++-- misc/genstd/mapping.go | 43 +++++++++++++++++++++++++++++++------ misc/genstd/template.tmpl | 30 +++++++++++++++++--------- 4 files changed, 67 insertions(+), 24 deletions(-) diff --git a/gnovm/stdlibs/native.go b/gnovm/stdlibs/native.go index 069ae140226..f85780f2021 100644 --- a/gnovm/stdlibs/native.go +++ b/gnovm/stdlibs/native.go @@ -392,11 +392,7 @@ var nativeFuncs = [...]nativeFunc{ m, p0) - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r0).Elem(), - )) + m.PushValue(r0) }, }, { diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index ff547e108dd..8cdddd916ad 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -1,6 +1,8 @@ package std import ( + "reflect" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" "github.com/gnolang/gno/tm2/pkg/bech32" "github.com/gnolang/gno/tm2/pkg/crypto" @@ -127,7 +129,7 @@ func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { return m.LastCallFrame(n).LastPackage.GetPkgAddr().Bech32() } -func GetBanker(m *gno.Machine, bankerType BankerType) Banker { +func GetBanker(m *gno.Machine, bankerType BankerType) gno.TypedValue { ctx := m.Context.(ExecContext) banker := ctx.Banker switch bankerType { @@ -145,7 +147,13 @@ func GetBanker(m *gno.Machine, bankerType BankerType) Banker { m.Alloc.AllocateStruct() // defensive; native space not allocated. m.Alloc.AllocateStructFields(10) // defensive 10; native space not allocated. - return banker + // make gno bankAdapter{rv} + btv := gno.Go2GnoNativeValue(m.Alloc, reflect.ValueOf(banker)) + bsv := m.Alloc.NewStructWithFields(btv) + bankAdapterType := m.Store.GetType(gno.DeclaredTypeID("std", "bankAdapter")) + res0 := gno.TypedValue{T: bankAdapterType, V: bsv} + + return res0 } func EncodeBech32(prefix string, bytes [20]byte) crypto.Bech32Address { diff --git a/misc/genstd/mapping.go b/misc/genstd/mapping.go index 0acfb55e6e3..c67bcd5acc9 100644 --- a/misc/genstd/mapping.go +++ b/misc/genstd/mapping.go @@ -7,6 +7,8 @@ import ( "strconv" ) +const gnoPackagePath = "github.com/gnolang/gno/gnovm/pkg/gnolang" + type mapping struct { GnoImportPath string // time GnoFunc string // now @@ -24,6 +26,11 @@ type mappingType struct { // type of ast.Expr is from the normal ast.Expr types // + *linkedIdent. Type ast.Expr + + // IsTypedValue is set to true if the parameter or result in go is of type + // gno.TypedValue. This prevents the generated code from performing + // Go2Gno/Gno2Go reflection-based conversion. + IsTypedValue bool } func (mt mappingType) GoQualifiedName() string { @@ -131,8 +138,12 @@ func (m *mapping) _loadParamsResults(dst *[]mappingType, gnol, gol []*ast.Field) gnoe := l.Type for i := 0; i < n; i++ { goe := gol[goIdx].Type - merged := m.mergeTypes(gnoe, goe) - *dst = append(*dst, mappingType{Type: merged}) + if m.isTypedValue(goe) { + *dst = append(*dst, mappingType{Type: gnoe, IsTypedValue: true}) + } else { + merged := m.mergeTypes(gnoe, goe) + *dst = append(*dst, mappingType{Type: merged}) + } goAdvance() } @@ -321,23 +332,37 @@ func (m *mapping) signaturesMatch(gnof, gof funcDecl) bool { m.fieldListsMatch(gnof.Type.Results, gof.Type.Results) } +// isGnoMachine checks whether field is of type *gno.Machine, +// and it has at most 1 name. func (m *mapping) isGnoMachine(field *ast.Field) bool { if len(field.Names) > 1 { return false } - px, ok := field.Type.(*ast.StarExpr) - if !ok { - return false + return m.isGnoType(field.Type, true, "Machine") +} + +// isTypedValue checks whether e is type gno.TypedValue. +func (m *mapping) isTypedValue(e ast.Expr) bool { + return m.isGnoType(e, false, "TypedValue") +} + +func (m *mapping) isGnoType(e ast.Expr, star bool, typeName string) bool { + if star { + px, ok := e.(*ast.StarExpr) + if !ok { + return false + } + e = px.X } - sx, ok := px.X.(*ast.SelectorExpr) + sx, ok := e.(*ast.SelectorExpr) if !ok { return false } imp := resolveSelectorImport(m.goImports, sx) - return imp == "github.com/gnolang/gno/gnovm/pkg/gnolang" && sx.Sel.Name == "Machine" + return imp == gnoPackagePath && sx.Sel.Name == typeName } func (m *mapping) fieldListsMatch(gnofl, gofl *ast.FieldList) bool { @@ -349,6 +374,10 @@ func (m *mapping) fieldListsMatch(gnofl, gofl *ast.FieldList) bool { return false } for idx, gnot := range gnots { + // if the go type is gno.TypedValue, we just don't perform reflect-based conversion. + if m.isTypedValue(gots[idx]) { + continue + } if m.mergeTypes(gnot, gots[idx]) == nil { return false } diff --git a/misc/genstd/template.tmpl b/misc/genstd/template.tmpl index d037ecdd7a0..f2cad0a851b 100644 --- a/misc/genstd/template.tmpl +++ b/misc/genstd/template.tmpl @@ -40,14 +40,20 @@ var nativeFuncs = [...]nativeFunc{ {{ if $m.Params -}} b := m.LastBlock() var ( - {{- range $pn, $pv := $m.Params }} - p{{ $pn }} {{ $pv.GoQualifiedName }} - rp{{ $pn }} = reflect.ValueOf(&p{{ $pn }}).Elem() + {{- range $pn, $pv := $m.Params -}} + {{- if $pv.IsTypedValue }} + p{{ $pn }} = gno.NewValuePathBlock(1, {{ $pn }}, "")).TV + {{- else }} + p{{ $pn }} {{ $pv.GoQualifiedName }} + rp{{ $pn }} = reflect.ValueOf(&p{{ $pn }}).Elem() + {{- end }} {{- end }} ) {{ range $pn, $pv := $m.Params -}} - gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, {{ $pn }}, "")).TV, rp{{ $pn }}) + {{- if not $pv.IsTypedValue }} + gno.Gno2GoValue(b.GetPointerTo(nil, gno.NewValuePathBlock(1, {{ $pn }}, "")).TV, rp{{ $pn }}) + {{- end -}} {{ end }} {{- end }} @@ -66,12 +72,16 @@ var nativeFuncs = [...]nativeFunc{ ) {{ range $rn, $rv := $m.Results -}} - m.PushValue(gno.Go2GnoValue( - m.Alloc, - m.Store, - reflect.ValueOf(&r{{ $rn }}).Elem(), {{- /* necessary to support interfaces (ie. error) */}} - )) - {{ end }} + {{- if $rv.IsTypedValue }} + m.PushValue(r{{ $rn }}) + {{- else }} + m.PushValue(gno.Go2GnoValue( + m.Alloc, + m.Store, + reflect.ValueOf(&r{{ $rn }}).Elem(), {{- /* necessary to support interfaces (ie. error) */}} + )) + {{- end }} + {{- end }} }, }, {{- end }} From 5bda7459f8b41114fd732ba5c5895431a0bbf22a Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 23 Aug 2023 16:34:26 +0200 Subject: [PATCH 22/59] fix failing cmd tests --- gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar | 2 ++ gnovm/cmd/gno/testdata/gno_test/realm_incorrect.txtar | 2 +- gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar b/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar index f376e61d9a4..6ac909116af 100644 --- a/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/realm_correct.txtar @@ -55,6 +55,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/x", // "Source": { // "@type": "/gno.RefNode", diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_incorrect.txtar b/gnovm/cmd/gno/testdata/gno_test/realm_incorrect.txtar index 3922c34fec8..38794a3e645 100644 --- a/gnovm/cmd/gno/testdata/gno_test/realm_incorrect.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/realm_incorrect.txtar @@ -7,7 +7,7 @@ stderr '=== RUN file/x_filetest.gno' stderr 'panic: fail on x_filetest.gno: diff:' stderr '--- Expected' stderr '\+\+\+ Actual' -stderr '@@ -1 \+1,64 @@' +stderr '@@ -1 \+1,66 @@' stderr '-xxx' stderr '\+switchrealm\["gno.land/r/x"\]' diff --git a/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar b/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar index 918fb0b88c9..104d4bb1064 100644 --- a/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar +++ b/gnovm/cmd/gno/testdata/gno_test/realm_sync.txtar @@ -70,6 +70,8 @@ func main() { // "FileName": "main.gno", // "IsMethod": false, // "Name": "main", +// "NativeName": "", +// "NativePkg": "", // "PkgPath": "gno.land/r/x", // "Source": { // "@type": "/gno.RefNode", From e1bf401e1ecb1269a59be7ce4126c3610c29e8c4 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 23 Aug 2023 17:13:12 +0200 Subject: [PATCH 23/59] refactor genstd field iteration --- misc/genstd/mapping.go | 146 ++++++++++++++++++++--------------------- 1 file changed, 72 insertions(+), 74 deletions(-) diff --git a/misc/genstd/mapping.go b/misc/genstd/mapping.go index c67bcd5acc9..7e809934b7b 100644 --- a/misc/genstd/mapping.go +++ b/misc/genstd/mapping.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "go/ast" "path" @@ -116,20 +117,60 @@ func (m *mapping) loadParamsResults(gnof, gof funcDecl) { } func (m *mapping) _loadParamsResults(dst *[]mappingType, gnol, gol []*ast.Field) { - // we know at this point in code that the signatures match; - // however, we still need to guard against cases like this one: - // func(n, n1 int) and func(n int, n1 int). - // these generate two different param lists, so we need to keep track of - // where we are in each of the two lists separately. - var goIdx, goNameIdx int - goAdvance := func() { - goNameIdx++ - if goNameIdx >= len(gol[goIdx].Names) { - goIdx++ - goNameIdx = 0 + iterFields(gnol, gol, func(gnoe, goe ast.Expr) error { + if m.isTypedValue(goe) { + *dst = append(*dst, mappingType{Type: gnoe, IsTypedValue: true}) + } else { + merged := m.mergeTypes(gnoe, goe) + *dst = append(*dst, mappingType{Type: merged}) + } + return nil + }) +} + +// isGnoMachine checks whether field is of type *gno.Machine, +// and it has at most 1 name. +func (m *mapping) isGnoMachine(field *ast.Field) bool { + if len(field.Names) > 1 { + return false + } + + return m.isGnoType(field.Type, true, "Machine") +} + +// isTypedValue checks whether e is type gno.TypedValue. +func (m *mapping) isTypedValue(e ast.Expr) bool { + return m.isGnoType(e, false, "TypedValue") +} + +func (m *mapping) isGnoType(e ast.Expr, star bool, typeName string) bool { + if star { + px, ok := e.(*ast.StarExpr) + if !ok { + return false } + e = px.X + } + + sx, ok := e.(*ast.SelectorExpr) + if !ok { + return false } + imp := resolveSelectorImport(m.goImports, sx) + return imp == gnoPackagePath && sx.Sel.Name == typeName +} + +// iterFields iterates over gnol and gol, calling callback for each matching +// paramter. iterFields assumes the caller already checked for the "true" number +// of parameters in the two arrays to be equal (can be checked using +// (*ast.FieldList).NumFields()). +// +// If callback returns an error, iterFields returns that error immediately. +// No errors are otherwise generated. +func iterFields(gnol, gol []*ast.Field, callback func(gnoType, goType ast.Expr) error) error { + var goIdx, goNameIdx int + for _, l := range gnol { n := len(l.Names) if n == 0 { @@ -138,16 +179,19 @@ func (m *mapping) _loadParamsResults(dst *[]mappingType, gnol, gol []*ast.Field) gnoe := l.Type for i := 0; i < n; i++ { goe := gol[goIdx].Type - if m.isTypedValue(goe) { - *dst = append(*dst, mappingType{Type: gnoe, IsTypedValue: true}) - } else { - merged := m.mergeTypes(gnoe, goe) - *dst = append(*dst, mappingType{Type: merged}) + + if err := callback(gnoe, goe); err != nil { + return err } - goAdvance() + goNameIdx++ + if goNameIdx >= len(gol[goIdx].Names) { + goIdx++ + goNameIdx = 0 + } } } + return nil } // mergeTypes merges gnoe and goe into a single ast.Expr. @@ -319,7 +363,7 @@ func (m *mapping) signaturesMatch(gnof, gof funcDecl) bool { // if first param of go function is *gno.Machine, remove it gofp := gof.Type.Params - if len(gofp.List) > 0 && m.isGnoMachine(gofp.List[0]) { + if gofp != nil && len(gofp.List) > 0 && m.isGnoMachine(gofp.List[0]) { // avoid touching original struct n := *gofp n.List = n.List[1:] @@ -332,72 +376,26 @@ func (m *mapping) signaturesMatch(gnof, gof funcDecl) bool { m.fieldListsMatch(gnof.Type.Results, gof.Type.Results) } -// isGnoMachine checks whether field is of type *gno.Machine, -// and it has at most 1 name. -func (m *mapping) isGnoMachine(field *ast.Field) bool { - if len(field.Names) > 1 { - return false - } - - return m.isGnoType(field.Type, true, "Machine") -} - -// isTypedValue checks whether e is type gno.TypedValue. -func (m *mapping) isTypedValue(e ast.Expr) bool { - return m.isGnoType(e, false, "TypedValue") -} - -func (m *mapping) isGnoType(e ast.Expr, star bool, typeName string) bool { - if star { - px, ok := e.(*ast.StarExpr) - if !ok { - return false - } - e = px.X - } - - sx, ok := e.(*ast.SelectorExpr) - if !ok { - return false - } - - imp := resolveSelectorImport(m.goImports, sx) - return imp == gnoPackagePath && sx.Sel.Name == typeName -} +var errNoMatch = errors.New("no match") func (m *mapping) fieldListsMatch(gnofl, gofl *ast.FieldList) bool { if gnofl == nil || gofl == nil { return gnofl == nil && gofl == nil } - gnots, gots := fieldListToTypes(gnofl), fieldListToTypes(gofl) - if len(gnots) != len(gots) { + if gnofl.NumFields() != gofl.NumFields() { return false } - for idx, gnot := range gnots { + err := iterFields(gnofl.List, gofl.List, func(gnoe, goe ast.Expr) error { // if the go type is gno.TypedValue, we just don't perform reflect-based conversion. - if m.isTypedValue(gots[idx]) { - continue - } - if m.mergeTypes(gnot, gots[idx]) == nil { - return false - } - } - return true -} - -func fieldListToTypes(fl *ast.FieldList) []ast.Expr { - e := make([]ast.Expr, 0, len(fl.List)) - for _, f := range fl.List { - nnames := len(f.Names) - if nnames < 1 { - // case of unnamed param (ie. `func X(int) {}`) - nnames = 1 + if m.isTypedValue(goe) { + return nil } - for i := 0; i < nnames; i++ { - e = append(e, f.Type) + if m.mergeTypes(gnoe, goe) == nil { + return errNoMatch } - } - return e + return nil + }) + return err == nil } // TODO: this is created based on the uverse definitions. This should be From 60525e3b1503c6668e9b3d891a27fe9e1169b06b Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 23 Aug 2023 17:14:56 +0200 Subject: [PATCH 24/59] typo --- misc/genstd/mapping.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/genstd/mapping.go b/misc/genstd/mapping.go index 7e809934b7b..e4fd28346d4 100644 --- a/misc/genstd/mapping.go +++ b/misc/genstd/mapping.go @@ -162,7 +162,7 @@ func (m *mapping) isGnoType(e ast.Expr, star bool, typeName string) bool { } // iterFields iterates over gnol and gol, calling callback for each matching -// paramter. iterFields assumes the caller already checked for the "true" number +// parameter. iterFields assumes the caller already checked for the "true" number // of parameters in the two arrays to be equal (can be checked using // (*ast.FieldList).NumFields()). // From b6a6c5d6e45eb7e4525d087bbce028f9f474dbdc Mon Sep 17 00:00:00 2001 From: Morgan Date: Wed, 30 Aug 2023 13:01:45 +0200 Subject: [PATCH 25/59] Update gnovm/pkg/gnolang/gonative.go Co-authored-by: Thomas Bruyelle --- gnovm/pkg/gnolang/gonative.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/gonative.go b/gnovm/pkg/gnolang/gonative.go index c90ba7b07a8..f3739dad0f3 100644 --- a/gnovm/pkg/gnolang/gonative.go +++ b/gnovm/pkg/gnolang/gonative.go @@ -760,7 +760,7 @@ func go2GnoValue2(alloc *Allocator, store Store, rv reflect.Value, recursive boo // regardless. tv.V = alloc.NewNative(rv) case reflect.Interface: - // special case for errors, which are very often used esepcially in + // special case for errors, which are very often used especially in // native bindings if rv.Type() == tError { tv.T = gErrorType From 62afd2294119e75ec49cfaf4c7bea1225882939f Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Thu, 31 Aug 2023 14:05:30 +0200 Subject: [PATCH 26/59] add tests --- gnovm/tests/files/zrealm_natbind0.gno | 197 ++++++++++++ misc/genstd/mapping.go | 20 +- misc/genstd/mapping_test.go | 294 ++++++++++++++++++ .../genstd/testdata/linkFunctions/std/std.gno | 23 ++ misc/genstd/testdata/linkFunctions/std/std.go | 47 +++ .../linkFunctions_TypedValue/std/std.gno | 11 + .../linkFunctions_TypedValue/std/std.go | 16 + .../linkFunctions_noMatch/std/std.gno | 3 + .../testdata/linkFunctions_noMatch/std/std.go | 3 + .../linkFunctions_noMatchSig/std/std.gno | 3 + .../linkFunctions_noMatchSig/std/std.go | 8 + .../testdata/linkFunctions_unexp/std/std.gno | 4 + .../testdata/linkFunctions_unexp/std/std.go | 13 + misc/genstd/util.go | 6 +- misc/genstd/util_test.go | 31 ++ 15 files changed, 664 insertions(+), 15 deletions(-) create mode 100644 gnovm/tests/files/zrealm_natbind0.gno create mode 100644 misc/genstd/mapping_test.go create mode 100644 misc/genstd/testdata/linkFunctions/std/std.gno create mode 100644 misc/genstd/testdata/linkFunctions/std/std.go create mode 100644 misc/genstd/testdata/linkFunctions_TypedValue/std/std.gno create mode 100644 misc/genstd/testdata/linkFunctions_TypedValue/std/std.go create mode 100644 misc/genstd/testdata/linkFunctions_noMatch/std/std.gno create mode 100644 misc/genstd/testdata/linkFunctions_noMatch/std/std.go create mode 100644 misc/genstd/testdata/linkFunctions_noMatchSig/std/std.gno create mode 100644 misc/genstd/testdata/linkFunctions_noMatchSig/std/std.go create mode 100644 misc/genstd/testdata/linkFunctions_unexp/std/std.gno create mode 100644 misc/genstd/testdata/linkFunctions_unexp/std/std.go create mode 100644 misc/genstd/util_test.go diff --git a/gnovm/tests/files/zrealm_natbind0.gno b/gnovm/tests/files/zrealm_natbind0.gno new file mode 100644 index 00000000000..60e0d448202 --- /dev/null +++ b/gnovm/tests/files/zrealm_natbind0.gno @@ -0,0 +1,197 @@ +// PKGPATH: gno.land/r/test +package test + +import ( + "std" +) + +var node interface{} + +func init() { + node = std.GetOrigCaller +} + +func main() { + f := node.(func() std.Address) + println(f()) + node = std.DerivePkgAddr + g := node.(func(path string) std.Address) + println(g("x")) +} + +// Output: +// g1wymu47drhr0kuq2098m792lytgtj2nyx77yrsm +// g19kt9e22k34ny5jf5plrjdltmws0jc0qqd2cwky + +// Realm: +// switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ +// "Blank": {}, +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "IsEscaped": true, +// "ModTime": "3", +// "RefCount": "2" +// }, +// "Parent": null, +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "", +// "Line": "0", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Values": [ +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "init.0", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "main.gno", +// "Line": "10", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "main", +// "NativeName": "", +// "NativePkg": "", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "main.gno", +// "Line": "14", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [ +// { +// "Embedded": false, +// "Name": "pkgPath", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// } +// } +// ], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "std.Address" +// } +// } +// ] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a7f5397443359ea76c50be82c77f1f893a060925:5" +// }, +// "FileName": "native.gno", +// "IsMethod": false, +// "Name": "DerivePkgAddr", +// "NativeName": "DerivePkgAddr", +// "NativePkg": "std", +// "PkgPath": "std", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "native.gno", +// "Line": "15", +// "Nonce": "0", +// "PkgPath": "std" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [ +// { +// "Embedded": false, +// "Name": "pkgPath", +// "Tag": "", +// "Type": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// } +// } +// ], +// "Results": [ +// { +// "Embedded": false, +// "Name": "", +// "Tag": "", +// "Type": { +// "@type": "/gno.RefType", +// "ID": "std.Address" +// } +// } +// ] +// } +// } +// } +// ] +// } diff --git a/misc/genstd/mapping.go b/misc/genstd/mapping.go index e4fd28346d4..0e4034a1ab9 100644 --- a/misc/genstd/mapping.go +++ b/misc/genstd/mapping.go @@ -254,16 +254,6 @@ func (m *mapping) mergeTypes(gnoe, goe ast.Expr) ast.Expr { return &linkedIdent{lt: lt} // easier cases -- check for equality of structure and underlying types - case *ast.Ellipsis: - goe, ok := goe.(*ast.Ellipsis) - if !ok { - return nil - } - elt := m.mergeTypes(gnoe.Elt, goe.Elt) - if elt == nil { - return nil - } - return &ast.Ellipsis{Elt: elt} case *ast.StarExpr: goe, ok := goe.(*ast.StarExpr) if !ok { @@ -283,11 +273,17 @@ func (m *mapping) mergeTypes(gnoe, goe ast.Expr) ast.Expr { if elt == nil { return nil } - return &ast.ArrayType{Len: gnoe.Len, Elt: elt} + var l ast.Expr + if gnoe.Len != nil { + l = &ast.BasicLit{Value: gnoe.Len.(*ast.BasicLit).Value} + } + return &ast.ArrayType{Len: l, Elt: elt} + case *ast.StructType, *ast.FuncType, *ast.InterfaceType, - *ast.MapType: + *ast.MapType, + *ast.Ellipsis: // TODO panic("not implemented") default: diff --git a/misc/genstd/mapping_test.go b/misc/genstd/mapping_test.go new file mode 100644 index 00000000000..b0cfa1bd4a7 --- /dev/null +++ b/misc/genstd/mapping_test.go @@ -0,0 +1,294 @@ +package main + +import ( + "fmt" + "go/ast" + "go/parser" + "os" + "path/filepath" + "sync" + "testing" + + "github.com/jaekwon/testify/assert" + "github.com/jaekwon/testify/require" +) + +const testdataDir = "github.com/gnolang/gno/misc/genstd/testdata/" + +var initWD = func() string { + d, err := os.Getwd() + if err != nil { + panic(err) + } + return d +}() + +func chdir(t *testing.T, s string) { + t.Helper() + + os.Chdir(filepath.Join(initWD, s)) + t.Cleanup(func() { + os.Chdir(initWD) + dirsOnce = sync.Once{} + memoGitRoot, memoRelPath = "", "" + }) +} + +func Test_linkFunctions(t *testing.T) { + chdir(t, "testdata/linkFunctions") + + pkgs, err := walkStdlibs(".") + require.NoError(t, err) + + mappings := linkFunctions(pkgs) + require.Len(t, mappings, 8) + + const ( + ret = 1 << iota + param + machine + ) + str := func(i int) string { + s := "Fn" + if i&machine != 0 { + s += "Machine" + } + if i¶m != 0 { + s += "Param" + } + if i&ret != 0 { + s += "Ret" + } + return s + } + + for i, v := range mappings { + exp := str(i) + assert.Equal(t, v.GnoFunc, exp) + assert.Equal(t, v.GoFunc, exp) + assert.Equal(t, v.GnoImportPath, "std") + assert.Equal(t, v.GoImportPath, testdataDir+"linkFunctions/std") + + assert.Equal(t, v.MachineParam, i&machine != 0, "MachineParam should match expected value") + if i¶m != 0 { + // require, otherwise the following would panic + require.Len(t, v.Params, 1) + p := v.Params[0] + assert.Equal(t, p.GnoType(), "int") + assert.Equal(t, p.GoQualifiedName(), "int") + assert.False(t, p.IsTypedValue) + } else { + assert.Len(t, v.Params, 0) + } + if i&ret != 0 { + // require, otherwise the following would panic + require.Len(t, v.Results, 1) + p := v.Results[0] + assert.Equal(t, p.GnoType(), "int") + assert.Equal(t, p.GoQualifiedName(), "int") + assert.False(t, p.IsTypedValue) + } else { + assert.Len(t, v.Results, 0) + } + } +} + +func Test_linkFunctions_unexp(t *testing.T) { + chdir(t, "testdata/linkFunctions_unexp") + + pkgs, err := walkStdlibs(".") + require.NoError(t, err) + + mappings := linkFunctions(pkgs) + require.Len(t, mappings, 2) + + assert.Equal(t, mappings[0].MachineParam, false) + assert.Equal(t, mappings[0].GnoFunc, "t1") + assert.Equal(t, mappings[0].GoFunc, "X_t1") + + assert.Equal(t, mappings[1].MachineParam, true) + assert.Equal(t, mappings[1].GnoFunc, "t2") + assert.Equal(t, mappings[1].GoFunc, "X_t2") +} + +func Test_linkFunctions_TypedValue(t *testing.T) { + chdir(t, "testdata/linkFunctions_TypedValue") + + pkgs, err := walkStdlibs(".") + require.NoError(t, err) + + mappings := linkFunctions(pkgs) + require.Len(t, mappings, 3) + + assert.Equal(t, mappings[0].MachineParam, false) + assert.Equal(t, mappings[0].GnoFunc, "TVParam") + assert.Equal(t, mappings[0].GoFunc, "TVParam") + assert.Len(t, mappings[0].Results, 0) + _ = assert.Len(t, mappings[0].Params, 1) && + assert.Equal(t, mappings[0].Params[0].IsTypedValue, true) && + assert.Equal(t, mappings[0].Params[0].GnoType(), "struct{m1 map[string]interface{}}") + + assert.Equal(t, mappings[1].MachineParam, false) + assert.Equal(t, mappings[1].GnoFunc, "TVResult") + assert.Equal(t, mappings[1].GoFunc, "TVResult") + assert.Len(t, mappings[1].Params, 0) + _ = assert.Len(t, mappings[1].Results, 1) && + assert.Equal(t, mappings[1].Results[0].IsTypedValue, true) && + assert.Equal(t, mappings[1].Results[0].GnoType(), "interface{S() map[int]Banker}") + + assert.Equal(t, mappings[2].MachineParam, true) + assert.Equal(t, mappings[2].GnoFunc, "TVFull") + assert.Equal(t, mappings[2].GoFunc, "TVFull") + assert.Len(t, mappings[2].Params, 1) + assert.Len(t, mappings[2].Results, 1) +} + +func Test_linkFunctions_noMatch(t *testing.T) { + chdir(t, "testdata/linkFunctions_noMatch") + + pkgs, err := walkStdlibs(".") + require.NoError(t, err) + + defer func() { + r := recover() + assert.NotNil(t, r) + assert.Contains(t, fmt.Sprint(r), "no matching go function declaration") + }() + + linkFunctions(pkgs) +} + +func Test_linkFunctions_noMatchSig(t *testing.T) { + chdir(t, "testdata/linkFunctions_noMatchSig") + + pkgs, err := walkStdlibs(".") + require.NoError(t, err) + + defer func() { + r := recover() + assert.NotNil(t, r) + assert.Contains(t, fmt.Sprint(r), "doesn't match signature of go function") + }() + + linkFunctions(pkgs) +} + +// mergeTypes - separate tests. + +var mergeTypesMapping = &mapping{ + GnoImportPath: "std", + GnoFunc: "Fn", + GoImportPath: "github.com/gnolang/gno/gnovm/stdlibs/std", + GoFunc: "Fn", + goImports: []*ast.ImportSpec{ + { + Name: &ast.Ident{Name: "gno"}, + Path: &ast.BasicLit{Value: `"github.com/gnolang/gno/gnovm/pkg/gnolang"`}, + }, + { + Path: &ast.BasicLit{Value: `"github.com/gnolang/gno/tm2/pkg/crypto"`}, + }, + }, + gnoImports: []*ast.ImportSpec{ + { + // cheating a bit -- but we currently only have linked types in `std`. + Path: &ast.BasicLit{Value: `"std"`}, + }, + { + Path: &ast.BasicLit{Value: `"math"`}, + }, + }, +} + +func Test_mergeTypes(t *testing.T) { + tt := []struct { + gnoe, goe string + result ast.Expr + }{ + {"int", "int", &ast.Ident{Name: "int"}}, + {"*[11][]rune", "*[11][]rune", &ast.StarExpr{ + X: &ast.ArrayType{Len: &ast.BasicLit{Value: "11"}, Elt: &ast.ArrayType{ + Elt: &ast.Ident{Name: "rune"}, + }}, + }}, + + {"Address", "crypto.Bech32Address", &linkedIdent{lt: linkedType{ + gnoPackage: "std", + gnoName: "Address", + goPackage: "github.com/gnolang/gno/tm2/pkg/crypto", + goName: "Bech32Address", + }}}, + {"std.Realm", "Realm", &linkedIdent{lt: linkedType{ + gnoPackage: "std", + gnoName: "Realm", + goPackage: "github.com/gnolang/gno/gnovm/stdlibs/std", + goName: "Realm", + }}}, + } + + for _, tv := range tt { + t.Run(tv.gnoe, func(t *testing.T) { + gnoe, err := parser.ParseExpr(tv.gnoe) + require.NoError(t, err) + goe, err := parser.ParseExpr(tv.goe) + require.NoError(t, err) + + result := mergeTypesMapping.mergeTypes(gnoe, goe) + assert.Equal(t, result, tv.result) + }) + } +} + +func Test_mergeTypes_invalid(t *testing.T) { + tt := []struct { + gnoe, goe string + panic string + }{ + {"int", "string", ""}, + + {"*int", "int", ""}, + {"string", "*string", ""}, + {"*string", "*int", ""}, + + {"[]int", "[1]int", ""}, + {"[1]int", "[]int", ""}, + {"[2]int", "[2]string", ""}, + // valid, but unsupported (only BasicLits) + {"[(11)]int", "[(11)]string", ""}, + + {"Address", "string", ""}, + {"math.X", "X", ""}, + + {"map[string]string", "map[string]string", "not implemented"}, + {"func(s string)", "func(s string)", "not implemented"}, + {"interface{}", "interface{}", "not implemented"}, + {"struct{}", "struct{}", "not implemented"}, + + {"1 + 2", "1 + 2", "invalid expression"}, + + // even though semantically equal, for simplicity we don't implement + // "true" basic lit equivalence + {"[8]int", "[0x8]int", ""}, + } + + for _, tv := range tt { + t.Run(tv.gnoe, func(t *testing.T) { + gnoe, err := parser.ParseExpr(tv.gnoe) + require.NoError(t, err) + goe, err := parser.ParseExpr(tv.goe) + require.NoError(t, err) + + defer func() { + r := recover() + if tv.panic == "" { + assert.Nil(t, r) + } else { + assert.Contains(t, fmt.Sprint(r), tv.panic) + } + }() + + result := mergeTypesMapping.mergeTypes(gnoe, goe) + assert.Nil(t, result) + }) + } +} diff --git a/misc/genstd/testdata/linkFunctions/std/std.gno b/misc/genstd/testdata/linkFunctions/std/std.gno new file mode 100644 index 00000000000..ab04b4084ba --- /dev/null +++ b/misc/genstd/testdata/linkFunctions/std/std.gno @@ -0,0 +1,23 @@ +package std + +func Fn() + +func FnRet() int + +func FnParam(n int) + +func FnParamRet(n int) int + +func FnMachine() + +func FnMachineRet() int + +func FnMachineParam(n int) + +func FnMachineParamRet(n int) int + +func Ignored() int { + // Ignored even if it has a matching go definition - + // as this one has a body. + return 1 +} diff --git a/misc/genstd/testdata/linkFunctions/std/std.go b/misc/genstd/testdata/linkFunctions/std/std.go new file mode 100644 index 00000000000..1b7a791c6cc --- /dev/null +++ b/misc/genstd/testdata/linkFunctions/std/std.go @@ -0,0 +1,47 @@ +package std + +import ( + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +func Fn() { + println("call Fn") +} + +func FnRet() int { + println("call FnRet") + return 1 +} + +func FnParam(n int) { + println("call FnParam", n) +} + +func FnParamRet(n int) int { + println("call FnParamRet", n) + return 1 +} + +func FnMachine(m *gno.Machine) { + println("call FnMachine") +} + +func FnMachineRet(m *gno.Machine) int { + println("call FnMachineRet") + return 1 +} + +func FnMachineParam(m *gno.Machine, n int) { + println("call FnMachineParam", n) +} + +func FnMachineParamRet(m *gno.Machine, n int) int { + println("call FnMachineParamRet", n) + return 1 +} + +func Ignored() int { + // Ignored even if it has a matching go definition - + // as gno's has a body. + return 1 +} diff --git a/misc/genstd/testdata/linkFunctions_TypedValue/std/std.gno b/misc/genstd/testdata/linkFunctions_TypedValue/std/std.gno new file mode 100644 index 00000000000..3bba36774e3 --- /dev/null +++ b/misc/genstd/testdata/linkFunctions_TypedValue/std/std.gno @@ -0,0 +1,11 @@ +package std + +type Banker interface { + B() +} + +func TVParam(m struct{ m1 map[string]interface{} }) + +func TVResult() interface{ S() map[int]Banker } + +func TVFull(map[Banker]map[string]interface{}) (n [86]map[string]bool) diff --git a/misc/genstd/testdata/linkFunctions_TypedValue/std/std.go b/misc/genstd/testdata/linkFunctions_TypedValue/std/std.go new file mode 100644 index 00000000000..03d95721438 --- /dev/null +++ b/misc/genstd/testdata/linkFunctions_TypedValue/std/std.go @@ -0,0 +1,16 @@ +package std + +import ( + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +func TVParam(p gno.TypedValue) { +} + +func TVResult() gno.TypedValue { + return gno.TypedValue{} +} + +func TVFull(m *gno.Machine, v gno.TypedValue) gno.TypedValue { + return gno.TypedValue{} +} diff --git a/misc/genstd/testdata/linkFunctions_noMatch/std/std.gno b/misc/genstd/testdata/linkFunctions_noMatch/std/std.gno new file mode 100644 index 00000000000..2ef4be8abc6 --- /dev/null +++ b/misc/genstd/testdata/linkFunctions_noMatch/std/std.gno @@ -0,0 +1,3 @@ +package std + +func X() int diff --git a/misc/genstd/testdata/linkFunctions_noMatch/std/std.go b/misc/genstd/testdata/linkFunctions_noMatch/std/std.go new file mode 100644 index 00000000000..97399743533 --- /dev/null +++ b/misc/genstd/testdata/linkFunctions_noMatch/std/std.go @@ -0,0 +1,3 @@ +package std + +func Y() {} diff --git a/misc/genstd/testdata/linkFunctions_noMatchSig/std/std.gno b/misc/genstd/testdata/linkFunctions_noMatchSig/std/std.gno new file mode 100644 index 00000000000..75e8e10e2e3 --- /dev/null +++ b/misc/genstd/testdata/linkFunctions_noMatchSig/std/std.gno @@ -0,0 +1,3 @@ +package std + +func X(n int) int diff --git a/misc/genstd/testdata/linkFunctions_noMatchSig/std/std.go b/misc/genstd/testdata/linkFunctions_noMatchSig/std/std.go new file mode 100644 index 00000000000..7a5a0e5893b --- /dev/null +++ b/misc/genstd/testdata/linkFunctions_noMatchSig/std/std.go @@ -0,0 +1,8 @@ +package std + +import ( + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +func X(m *gno.Machine, n string) { +} diff --git a/misc/genstd/testdata/linkFunctions_unexp/std/std.gno b/misc/genstd/testdata/linkFunctions_unexp/std/std.gno new file mode 100644 index 00000000000..c4811e5e837 --- /dev/null +++ b/misc/genstd/testdata/linkFunctions_unexp/std/std.gno @@ -0,0 +1,4 @@ +package std + +func t1() int +func t2() int diff --git a/misc/genstd/testdata/linkFunctions_unexp/std/std.go b/misc/genstd/testdata/linkFunctions_unexp/std/std.go new file mode 100644 index 00000000000..023b424e87c --- /dev/null +++ b/misc/genstd/testdata/linkFunctions_unexp/std/std.go @@ -0,0 +1,13 @@ +package std + +import ( + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +func X_t1() int { + return 1 +} + +func X_t2(m *gno.Machine) int { + return m.NumOps +} diff --git a/misc/genstd/util.go b/misc/genstd/util.go index 850ba43d133..061a9604c67 100644 --- a/misc/genstd/util.go +++ b/misc/genstd/util.go @@ -57,15 +57,15 @@ func dirsOnceDo() { } func findDirs() (gitRoot string, relPath string, err error) { - abs, err := filepath.Abs(".") + wd, err := os.Getwd() if err != nil { return } - p := abs + p := wd for { if s, e := os.Stat(filepath.Join(p, ".git")); e == nil && s.IsDir() { // make relPath relative to the git root - rp := strings.TrimPrefix(abs, p+string(filepath.Separator)) + rp := strings.TrimPrefix(wd, p+string(filepath.Separator)) // normalize separator to / rp = strings.ReplaceAll(rp, string(filepath.Separator), "/") return p, rp, nil diff --git a/misc/genstd/util_test.go b/misc/genstd/util_test.go new file mode 100644 index 00000000000..f6e804d545f --- /dev/null +++ b/misc/genstd/util_test.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "testing" + + "github.com/jaekwon/testify/assert" +) + +func Test_pkgNameFromPath(t *testing.T) { + tt := []struct { + input, result string + }{ + {"math", "go_math"}, + {"crypto/sha256", "go_crypto_sha256"}, + {"github.com/import/path", "ext_github_com_import_path"}, + // consecutive unsupported characters => _ + {"kebab----------case", "go_kebab_case"}, + + {"github.com/gnolang/gno/misc/test", "repo_misc_test"}, + {"github.com/gnolang/gno/tm2/pkg/crypto", "tm2_crypto"}, + {"github.com/gnolang/gno/gnovm/test", "vm_test"}, + {"github.com/gnolang/gno/gnovm/stdlibs/std", "libs_std"}, + {"github.com/gnolang/gno/gnovm/tests/stdlibs/std", "testlibs_std"}, + } + for i, tv := range tt { + t.Run(fmt.Sprintf("n%d", i+1), func(t *testing.T) { + assert.Equal(t, pkgNameFromPath(tv.input), tv.result) + }) + } +} From 0e787fa3997ae00ac5faf5bd89b60ed26c9ae86f Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Thu, 31 Aug 2023 14:16:38 +0200 Subject: [PATCH 27/59] update makefiles & workflows --- .github/workflows/misc.yml | 66 ++++++++++++++++++++++++++++++++++++++ Makefile | 1 + misc/Makefile | 28 ++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 .github/workflows/misc.yml create mode 100644 misc/Makefile diff --git a/.github/workflows/misc.yml b/.github/workflows/misc.yml new file mode 100644 index 00000000000..c2562f584de --- /dev/null +++ b/.github/workflows/misc.yml @@ -0,0 +1,66 @@ +# tests the "misc" directory & tools +# (not meant for miscellaneous workflows) +name: misc + +on: + pull_request: + paths: + - "misc/genstd/**.go" + - "misc/Makefile" + - ".github/workflows/misc.yml" + push: + branches: [ "master" ] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + build: + strategy: + fail-fast: false + matrix: + go-version: # two latest versions + - "1.19.x" + - "1.20.x" + program: + - "genstd" + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go-version }} + - uses: actions/checkout@v3 + - name: go install + working-directory: misc + run: go install ./${{ matrix.program }} + + test: + strategy: + fail-fast: false + matrix: + go-version: [ "1.19.x", "1.20.x" ] + args: + - _test.genstd + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go-version }} + - uses: actions/checkout@v3 + - name: test + working-directory: misc + run: | + export GOPATH=$HOME/go + export GOTEST_FLAGS="-v -p 1 -timeout=30m -coverprofile=coverage.out -covermode=atomic" + make ${{ matrix.args }} + - if: runner.os == 'Linux' + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + name: misc + flags: misc,misc-${{matrix.args}},go-${{ matrix.go-version }} + files: ./misc/coverage.out + fail_ci_if_error: false # temporarily diff --git a/Makefile b/Makefile index 358094e5c4a..80c9bef2ed7 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ test.components: $(MAKE) --no-print-directory -C gnovm test $(MAKE) --no-print-directory -C gno.land test $(MAKE) --no-print-directory -C examples test + $(MAKE) --no-print-directory -C misc test .PHONY: test.docker test.docker: diff --git a/misc/Makefile b/misc/Makefile new file mode 100644 index 00000000000..84acc40e387 --- /dev/null +++ b/misc/Makefile @@ -0,0 +1,28 @@ +.PHONY: help +help: + @echo "Available make commands:" + @cat Makefile | grep '^[a-z][^:]*:' | cut -d: -f1 | sort | sed 's/^/ /' + +rundep=go run -modfile ./devdeps/go.mod + +######################################## +# Dev tools +.PHONY: lint +lint: + $(rundep) github.com/golangci/golangci-lint/cmd/golangci-lint run --config ../.github/golangci.yml ./... + +.PHONY: fmt +GOFMT_FLAGS ?= -w +fmt: + $(rundep) mvdan.cc/gofumpt $(GOFMT_FLAGS) . + +######################################## +# Test suite +.PHONY: test +test: _test.genstd + +GOTEST_FLAGS ?= -v -p 1 -timeout=30m + +.PHONY: _test.genstd +_test.genstd: + go test ./genstd/... $(GOTEST_FLAGS) From 9f3f261b682128643dba3ec61e587879e89d4565 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Thu, 31 Aug 2023 14:35:40 +0200 Subject: [PATCH 28/59] add misc to codecov --- .github/codecov.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/codecov.yml b/.github/codecov.yml index 0feaea151ea..e8ec2532cb3 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -28,3 +28,7 @@ flags: paths: - gno.land after_n_builds: 3 + misc: + paths: + - misc + after_n_builds: 1 From ec9f5da00ba042f99e8e37569558bafd57cc1dba Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 6 Sep 2023 11:32:40 +0200 Subject: [PATCH 29/59] address TODO in filterDuplicates --- gnovm/pkg/gnolang/machine.go | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 64fe93aa526..f64a7f3def8 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -276,6 +276,14 @@ func (m *Machine) runMemPackage(memPkg *std.MemPackage, save, overrides bool) (* func filterDuplicates(fset *FileSet) { defined := make(map[Name]struct{}, 128) for _, f := range fset.Files { + // returns true if name already defined + addName := func(n Name) bool { + if _, ok := defined[n]; ok { + return true // i-- + } + defined[n] = struct{}{} + return false + } for i := 0; i < len(f.Decls); i++ { d := f.Decls[i] var name Name @@ -288,20 +296,34 @@ func filterDuplicates(fset *FileSet) { case *TypeDecl: name = d.Name case *ValueDecl: + // simpler, most common case if len(d.NameExprs) == 1 { name = d.NameExprs[0].Name - } else { - // TODO: support multiple names + break + } + + // more complex: filter out the NameExprs which are not + // redeclarations, and set them in d.NameExprs + newNames := make([]NameExpr, 0, len(d.NameExprs)) + for _, nx := range d.NameExprs { + if !addName(nx.Name) { + newNames = append(newNames, nx) + } + } + // all new names are re-declarations, remove + if len(newNames) == 0 { + f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) + i-- continue } + d.NameExprs = newNames + continue default: continue } - if _, ok := defined[name]; ok { + if addName(name) { f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) i-- - } else { - defined[name] = struct{}{} } } } From a3b25e5f63ed0b78db77015dd1218d1eedd98966 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 6 Sep 2023 13:38:31 +0200 Subject: [PATCH 30/59] add stdlibs documentation file; --- docs/stdlibs.md | 187 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 docs/stdlibs.md diff --git a/docs/stdlibs.md b/docs/stdlibs.md new file mode 100644 index 00000000000..b46327153cd --- /dev/null +++ b/docs/stdlibs.md @@ -0,0 +1,187 @@ +# Standard Libraries + +Gno comes with a set of standard libraries which are included whenever you +execute Gno code. These are distinguishable from imports of packages from the +chain by not referring to a "domain" as the first part of their import path. For +instance, `import "encoding/binary"` refers to a standard library, while +`import "gno.land/p/demo/avl"` refers to an on-chain package. + +Standard libraries packages follow the same semantics as on-chain packages (ie. +they don't persist state like realms do) and come as a part of the Gno +programming language rather than with the Gno.land chain. + +Many standard libaries are near-identical copies of the equivalent Go standard +libraries; in fact, you can check the current status of implementation of each +Go standard libarary on [Go\<\>Gno compatibility](go-gno-compatibility.md). + +## Gathering documentation + +At the time being, there is no "list" of the available standard libraries +available from Gno tooling or documentation, but you can obtain a list of all +the available packages with the following commands: + +```console +$ cd gnovm/stdlibs # go to correct directory +$ find -type d +./testing +./math +./crypto +./crypto/chacha20 +./crypto/chacha20/chacha +./crypto/chacha20/rand +./crypto/sha256 +./crypto/cipher +... +``` + +All of the packages have automatic, generated documentation through the use of +`gno doc`, which has similar functionality and features to `go doc`: + +```console +$ gno doc encoding/binary +package binary // import "encoding/binary" + +Package binary implements simple translation between numbers and byte sequences +and encoding and decoding of varints. + +[...] + +var BigEndian bigEndian +var LittleEndian littleEndian +type AppendByteOrder interface{ ... } +type ByteOrder interface{ ... } +$ gno doc -u -src encoding/binary littleEndian.AppendUint16 +package binary // import "encoding/binary" + +func (littleEndian) AppendUint16(b []byte, v uint16) []byte { + return append(b, + byte(v), + byte(v>>8), + ) +} +``` + +`gno doc` will work automatically when used within the Gno repository or any +repository which has a `go.mod` dependency on `github.com/gnolang/gno`, which +can be a simple way to set up your Gno repositories to automatically support +`gno` commands (aside from `doc`, also `test`, `run`, etc.). + +Another alternative is setting your enviornment variable `GNOROOT` to point to +where you cloned the Gno repository. You can set this in your `~/.profile` file +to be automatically set up in your console: + +```sh +export GNOROOT=$HOME/gno +``` + +## Test standard libraries + +There are some additional standard library functions and packages which are +currently available only in `_test.gno` and `_filetest.gno` files. At the time +of writing, these are only some additions in the `std` package to support +changing some values in test functions. + +`gno doc` currently doesn't support reading from the test standard libraries, +though support is planned to be added. For now, you can inspect the directory +`gnovm/tests/stdlibs`. + +## Adding new standard libraries + +New standard libraries may be added by simply creating a new directory (whose +path relative to the `stdlibs` directory will be the import path used in Gno +programs). Following that, the suggested approach for adding a Go standard +libary is to copy the original files from the Go source tree, and renaming their +extensions from `.go` to `.gno`. + +> As a small aid, this bash one-liner can be useful to convert all the file +> extensions: +> +> ```sh +> for i in *.go; do mv $i "$(echo $i | sed 's/\.go$/.gno/')"; done +> ``` + +Following that, the suggested approach is to iteratively try running `gno test .`, +while fixing any errors that may come out of trying to test the package. + +Some things to keep in mind: + +- Gno doesn't support assembly functions and build tags. Some Go packages may + contain assembly versions for different architecture and a `generic.go` file + containing the architecture-independent version. The general approach is that + of removing everything architecture/os-specific except for the `generic.go` file. +- Gno doesn't support reflection at the time of writing, which means that for + now many packages which rely heavily on reflection have to be delayed or + reduced while we figure out the details on how to implement reflection. + Aside from the `reflect` package itself, this also translates to very common + packages still not available in Gno, such as `fmt` or `encoding/json`. + +If you intend to create a PR to add a new standard library, remember to update +[Go\<\>Gno compatibility](go-gno-compatibility.md) accordingly. + +## Native bindings + +Gno has support for "natively-defined functions" exclusively within the standard +libaries. These are functions which are _declared_ in Gno code, but only _defined_ +in Go. There are generally three reasons why a function should be natively +defined: + +1. It relies on inspecting the Gno Virtual Machine itself.\ + For example: `std.AssertOriginCall`, `std.GetBanker`. +2. It relies on `unsafe`, or other features which are not planned to be + available in the GnoVM.\ + For example: `math.Float64frombits`. +3. Its native Go performance significantly outperforms the Gno counterpart by + several orders of magnitude, and it is used in crucial code or hot paths in + many programs.\ + For example: `sha256.Sum256`. + +The takeaway here is that native bindings are a special feature which can be +useful to overcome pure Gno limitations, but it is not a substitute for writing +standard libaries in Gno. + +There are three components to a natively bound function in Gno: + +1. The Gno function definition, which must be a top-level function with no body + (and no brackets).\ + For example: `crypto/sha256/sha256.gno`. +2. The Go function declaration, which must be a top-level function with the same + name and signature.\ + For example: `crypto/sha256/sha256.go`. +3. When the two above are present and valid, the native binding can be created + by executing the code generator: either execute `go generate` from the + `stdlibs` directory, or run `make generate` from the `gnovm` directory.\ + This generates the `native.go` file available in the `stdlibs` directory, + which provides the binding itself to then be used by the GnoVM. + +The code generator in question is available in the `misc/genstd` directory. +There are some quirks and features that must be kept in mind when writing native +bindings, which are the following: + +- Unexported functions (for instance, `func sum256(b []byte)`) must have their + Go counterpart prefixed with `X_` in order to make the functions exported (ie. + `func X_sum256(b []byte)`). +- The Go function declaration may specify as the first argument + `m *gno.Machine`, where `gno` is an import for + `github.com/gnolang/gno/gnovm/pkg/gnolang`. This gives the function access to + the Virtual Machine state, and is used by functions like `std.AssertOriginCall()`. +- The Go function may change the type of any parameter or result to + `gno.TypedValue` (where `gno` is an import for the above import path). This + means that the `native.go` generated code will not attempt to automatically + convert the Gno value into the Go value, and can be useful for unsupported + conversions like interface values. +- A small set of named types are "linked" between their Gno version and Go + counterpart. For instance, `std.Address` in Gno is + `(".../tm2/pkg/crypto").Bech32Address` in Go. A list of these can be found in + `misc/genstd/mapping.go`. +- Not all type literals are currently supported when converting from their Gno + version to their Go counterpart. Notable omissions at the time of writing + include struct and map literals. If you intend to use these, modify the code + generator to support them. +- The code generator does not inspect any imported packages from the Go native code; + as a consequence, either the import contains an identifier (ie. + `import gno "github.com/gnolang/gno/gnovm/pkg/gnolang"`), or the last element + in the import path is assumed to be the identifier used in the file. Note that + this is not always the case (the default identifier is the same as the + declared package name in the `package` clause of each Go file), so when the + last element and the package name differ, remember to specify an identifier in + the import. From a68cfe45874af54f312b205bd12ed3ef74686abf Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 6 Sep 2023 13:47:11 +0200 Subject: [PATCH 31/59] fix ci --- gnovm/tests/imports.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index ca894ed585f..75a9c975c6f 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -372,10 +372,9 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri // if native package is preferred, try to load stdlibs/* as backup. if mode == ImportModeNativePreferred { pn, pv = loadStdlib(rootDir, pkgPath, store, stdout) - if pn == nil { - panic(fmt.Sprintf("found an empty package %q", pkgPath)) + if pn != nil { + return } - return } // if examples package... From a89302faa99c46b6e99a2910b4a23360a592227d Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 6 Sep 2023 18:55:45 +0200 Subject: [PATCH 32/59] remove 1.20 from versions for genstd --- .github/workflows/misc.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/misc.yml b/.github/workflows/misc.yml index 629bf8045b6..4ef98587d0e 100644 --- a/.github/workflows/misc.yml +++ b/.github/workflows/misc.yml @@ -21,7 +21,6 @@ jobs: fail-fast: false matrix: go-version: # two latest versions - - "1.20.x" - "1.21.x" program: - "genstd" @@ -41,7 +40,6 @@ jobs: fail-fast: false matrix: go-version: - - "1.20.x" - "1.21.x" args: - _test.genstd From c6a4be35d416839eb3d6ed224d4192adbe3891a2 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Wed, 20 Sep 2023 18:33:08 +0200 Subject: [PATCH 33/59] fix: copy native store on Fork() --- gnovm/pkg/gnolang/op_call.go | 3 +++ gnovm/pkg/gnolang/store.go | 1 + 2 files changed, 4 insertions(+) diff --git a/gnovm/pkg/gnolang/op_call.go b/gnovm/pkg/gnolang/op_call.go index 110d301652e..b01169bf466 100644 --- a/gnovm/pkg/gnolang/op_call.go +++ b/gnovm/pkg/gnolang/op_call.go @@ -61,6 +61,9 @@ func (m *Machine) doOpCall() { if fv.nativeBody == nil && fv.NativePkg != "" { // native function, unmarshaled so doesn't have nativeBody yet fv.nativeBody = m.Store.GetNative(fv.NativePkg, fv.NativeName) + if fv.nativeBody == nil { + panic(fmt.Sprintf("natively defined function (%q).%s could not be resolved", fv.NativePkg, fv.NativeName)) + } } if fv.nativeBody == nil { fbody := fv.GetBodyFromSource(m.Store) diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 08f10d91d72..c43d5a57e62 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -609,6 +609,7 @@ func (ds *defaultStore) Fork() Store { baseStore: ds.baseStore, iavlStore: ds.iavlStore, pkgInjector: ds.pkgInjector, + nativeStore: ds.nativeStore, go2gnoMap: ds.go2gnoMap, go2gnoStrict: ds.go2gnoStrict, opslog: nil, // new ops log. From 42a11cccc617c5fc88b0f71190861d75b23c4ed2 Mon Sep 17 00:00:00 2001 From: Morgan Date: Fri, 20 Oct 2023 20:44:27 +0200 Subject: [PATCH 34/59] Update docs/stdlibs.md --- docs/stdlibs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/stdlibs.md b/docs/stdlibs.md index b46327153cd..42ed1b0f472 100644 --- a/docs/stdlibs.md +++ b/docs/stdlibs.md @@ -141,10 +141,10 @@ standard libaries in Gno. There are three components to a natively bound function in Gno: -1. The Gno function definition, which must be a top-level function with no body +1. The Gno function declaration, which must be a top-level function with no body (and no brackets).\ For example: `crypto/sha256/sha256.gno`. -2. The Go function declaration, which must be a top-level function with the same +2. The Go function definition, which must be a top-level function with the same name and signature.\ For example: `crypto/sha256/sha256.go`. 3. When the two above are present and valid, the native binding can be created From c64aaac6756a04de62e8df862b835f0a9eefbe53 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sat, 21 Oct 2023 08:37:49 -0400 Subject: [PATCH 35/59] allow duplicate definitions in preprocessor/vm; switch to checkDuplicates h/t @jaekwon --- gnovm/pkg/gnolang/machine.go | 52 ++++----------- gnovm/pkg/gnolang/nodes.go | 7 +- gnovm/pkg/gnolang/preprocess.go | 110 +++++++++++++++++++------------- gnovm/tests/imports.go | 4 +- 4 files changed, 81 insertions(+), 92 deletions(-) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 12b6a8c714a..2bc5e8a91c4 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -189,12 +189,6 @@ func (m *Machine) PreprocessAllFilesAndSaveBlockNodes() { ch := m.Store.IterMemPackage() for memPkg := range ch { fset := ParseMemPackage(memPkg) - // Implicitly do filterDuplicates for stdlibs. - // This is done as this function is at the time of writing only used - // in tests, ie. where we use the "override" feature for stdlibs. - if !strings.HasPrefix(memPkg.Path, "gno.land/") { - filterDuplicates(fset) - } pn := NewPackageNode(Name(memPkg.Name), memPkg.Path, fset) m.Store.SetBlockNode(pn) PredefineFileSet(m.Store, pn, fset) @@ -239,8 +233,8 @@ func (m *Machine) RunMemPackageWithOverrides(memPkg *std.MemPackage, save bool) func (m *Machine) runMemPackage(memPkg *std.MemPackage, save, overrides bool) (*PackageNode, *PackageValue) { // parse files. files := ParseMemPackage(memPkg) - if overrides { - filterDuplicates(files) + if !overrides && checkDuplicates(files) { + panic(fmt.Errorf("running package %q: duplicate declarations not allowed", memPkg.Path)) } // make and set package if doesn't exist. pn := (*PackageNode)(nil) @@ -268,19 +262,11 @@ func (m *Machine) runMemPackage(memPkg *std.MemPackage, save, overrides bool) (* return pn, pv } -func filterDuplicates(fset *FileSet) { +// checkDuplicates returns true if there duplicate declarations in the fset. +func checkDuplicates(fset *FileSet) bool { defined := make(map[Name]struct{}, 128) for _, f := range fset.Files { - // returns true if name already defined - addName := func(n Name) bool { - if _, ok := defined[n]; ok { - return true // i-- - } - defined[n] = struct{}{} - return false - } - for i := 0; i < len(f.Decls); i++ { - d := f.Decls[i] + for _, d := range f.Decls { var name Name switch d := d.(type) { case *FuncDecl: @@ -291,37 +277,23 @@ func filterDuplicates(fset *FileSet) { case *TypeDecl: name = d.Name case *ValueDecl: - // simpler, most common case - if len(d.NameExprs) == 1 { - name = d.NameExprs[0].Name - break - } - - // more complex: filter out the NameExprs which are not - // redeclarations, and set them in d.NameExprs - newNames := make([]NameExpr, 0, len(d.NameExprs)) for _, nx := range d.NameExprs { - if !addName(nx.Name) { - newNames = append(newNames, nx) + if _, ok := defined[nx.Name]; ok { + return true } + defined[nx.Name] = struct{}{} } - // all new names are re-declarations, remove - if len(newNames) == 0 { - f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) - i-- - continue - } - d.NameExprs = newNames continue default: continue } - if addName(name) { - f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) - i-- + if _, ok := defined[name]; ok { + return true } + defined[name] = struct{}{} } } + return false } func derefStar(x Expr) Expr { diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 803a8698a22..06f3d9cb537 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1780,11 +1780,8 @@ func (sb *StaticBlock) Define2(isConst bool, n Name, st Type, tv TypedValue) { "StaticBlock.Define2(%s) cannot change .T; was %v, new %v", n, old.T, tv.T)) } - if tv.V != old.V { - panic(fmt.Sprintf( - "StaticBlock.Define2(%s) cannot change .V", - n)) - } + // Allow re-definitions if they have the same type. + // (In normal scenarios, duplicate declarations are "caught" by RunMemPackage. } sb.Block.Values[idx] = tv sb.Types[idx] = st diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 44457ad0cb0..caf156066c3 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -3,7 +3,10 @@ package gnolang import ( "fmt" "math/big" + "path/filepath" "reflect" + "runtime" + "strings" "github.com/gnolang/gno/tm2/pkg/errors" ) @@ -1817,8 +1820,13 @@ func Preprocess(store Store, ctx BlockNode, n Node) Node { for i, vx := range n.Values { if cx, ok := vx.(*ConstExpr); ok && !cx.TypedValue.IsUndefined() { - // if value is non-nil const expr: - tvs[i] = cx.TypedValue + if n.Const { + // const _ = : static block should contain value + tvs[i] = cx.TypedValue + } else { + // var _ = : static block should NOT contain value + tvs[i] = anyValue(cx.TypedValue.T) + } } else { // for var decls of non-const expr. st := sts[i] @@ -2887,48 +2895,7 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De } switch cd := d.(type) { case *FuncDecl: - // *FuncValue/*FuncType is mostly empty still; here - // we just fill the func type (and recv if method). - // NOTE: unlike the *ValueDecl case, this case doesn't - // preprocess d itself (only d.Type). - if cd.IsMethod { - if cd.Recv.Name == "" || cd.Recv.Name == "_" { - // create a hidden var with leading dot. - // NOTE: document somewhere. - cd.Recv.Name = ".recv" - } - cd.Recv = *Preprocess(store, last, &cd.Recv).(*FieldTypeExpr) - cd.Type = *Preprocess(store, last, &cd.Type).(*FuncTypeExpr) - rft := evalStaticType(store, last, &cd.Recv).(FieldType) - rt := rft.Type - ft := evalStaticType(store, last, &cd.Type).(*FuncType) - ft = ft.UnboundType(rft) - dt := (*DeclaredType)(nil) - if pt, ok := rt.(*PointerType); ok { - dt = pt.Elem().(*DeclaredType) - } else { - dt = rt.(*DeclaredType) - } - dt.DefineMethod(&FuncValue{ - Type: ft, - IsMethod: true, - Source: cd, - Name: cd.Name, - Closure: nil, // set lazily. - FileName: fileNameOf(last), - PkgPath: pkg.PkgPath, - body: cd.Body, - nativeBody: nil, - }) - } else { - ftv := pkg.GetValueRef(store, cd.Name) - ft := ftv.T.(*FuncType) - cd.Type = *Preprocess(store, last, &cd.Type).(*FuncTypeExpr) - ft2 := evalStaticType(store, last, &cd.Type).(*FuncType) - *ft = *ft2 - // XXX replace attr w/ ft? - // return Preprocess(store, last, cd).(Decl), true - } + // Full type declaration/preprocessing already done in tryPredefine return d, false case *ValueDecl: return Preprocess(store, last, cd).(Decl), true @@ -2939,6 +2906,25 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De } } +func smallStacktrace() { + pc := make([]uintptr, 10) // adjust max number of lines to print + pc = pc[:runtime.Callers(2, pc)] + frames := runtime.CallersFrames(pc) + for { + f, more := frames.Next() + + if idx := strings.LastIndexByte(f.Function, '/'); idx >= 0 { + f.Function = f.Function[idx+1:] + } + + fmt.Printf("%-25s %s\n", fmt.Sprintf("%s:%d", filepath.Base(f.File), f.Line), f.Function) + + if !more { + return + } + } +} + // If a dependent name is not yet defined, that name is // returned; this return value is used by the caller to // enforce declaration order. If a dependent type is not yet @@ -3108,9 +3094,43 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { if un != "" { return } + + pkg := packageOf(last) + + // d.Recv's type is defined; add method to DeclaredType. + if d.Recv.Name == "" || d.Recv.Name == "_" { + // create a hidden var with leading dot. + // TODO: document somewhere. + d.Recv.Name = ".recv" + } + + d.Recv = *Preprocess(store, last, &d.Recv).(*FieldTypeExpr) + d.Type = *Preprocess(store, last, &d.Type).(*FuncTypeExpr) + rft := evalStaticType(store, last, &d.Recv).(FieldType) + rt := rft.Type + ft := evalStaticType(store, last, &d.Type).(*FuncType) + ft = ft.UnboundType(rft) + var dt *DeclaredType + if pt, ok := rt.(*PointerType); ok { + dt = pt.Elem().(*DeclaredType) + } else { + dt = rt.(*DeclaredType) + } + dt.DefineMethod(&FuncValue{ + Type: ft, + IsMethod: true, + Source: d, + Name: d.Name, + Closure: nil, // set lazily. + FileName: fileNameOf(last), + PkgPath: pkg.PkgPath, + body: d.Body, + nativeBody: nil, + }) } else { // define package-level function. - ft := &FuncType{} + d.Type = *Preprocess(store, last, &d.Type).(*FuncTypeExpr) + ft := evalStaticType(store, last, &d.Type).(*FuncType) pkg := skipFile(last).(*PackageNode) // special case: if d.Name == "init", assign unique suffix. if d.Name == "init" { diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 75a9c975c6f..4c65345a985 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -411,10 +411,10 @@ func TestStore(rootDir, filesPath string, stdin io.Reader, stdout, stderr io.Wri func loadStdlib(rootDir, pkgPath string, store gno.Store, stdout io.Writer) (*gno.PackageNode, *gno.PackageValue) { dirs := [...]string{ - // override path. definitions here should take precedence. - filepath.Join(rootDir, "gnovm", "tests", "stdlibs", pkgPath), // normal stdlib path. filepath.Join(rootDir, "gnovm", "stdlibs", pkgPath), + // override path. definitions here override the previous if duplicate. + filepath.Join(rootDir, "gnovm", "tests", "stdlibs", pkgPath), } files := make([]string, 0, 32) // pre-alloc 32 as a likely high number of files for _, path := range dirs { From f87411942baf4021ef543e123d330fb4e31e7917 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sat, 21 Oct 2023 09:12:23 -0400 Subject: [PATCH 36/59] add comment on exprstring; --- misc/genstd/exprstring.go | 1 + 1 file changed, 1 insertion(+) diff --git a/misc/genstd/exprstring.go b/misc/genstd/exprstring.go index a93d7afbbb1..c95c05c584e 100644 --- a/misc/genstd/exprstring.go +++ b/misc/genstd/exprstring.go @@ -1,4 +1,5 @@ // Forked from go/types (go 1.20.3) to implement support for *linkedIdent. +// It cannot be easily split from the original as WriteExpr is highly recursive. // Copyright 2013 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style From 237bce95701a06597a83b9999c52ac9fb37567ac Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sat, 21 Oct 2023 09:16:23 -0400 Subject: [PATCH 37/59] add godoc to FuncD --- gnovm/pkg/gnolang/helpers.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gnovm/pkg/gnolang/helpers.go b/gnovm/pkg/gnolang/helpers.go index c16ea795ea3..bf88a9d441a 100644 --- a/gnovm/pkg/gnolang/helpers.go +++ b/gnovm/pkg/gnolang/helpers.go @@ -105,6 +105,12 @@ func MaybeNativeT(tx interface{}) *MaybeNativeTypeExpr { } } +// FuncD creates a new function declaration. +// +// There is a difference between passing nil to body or passing []Stmt{}: +// nil means that the curly brackets are missing in the source code, indicating +// a declaration for an externally-defined function, while []Stmt{} is simply a +// functions with no statements (func() {}). func FuncD(name interface{}, params, results FieldTypeExprs, body []Stmt) *FuncDecl { return &FuncDecl{ NameExpr: *Nx(name), From bef0473f8afe79d5a5bf35f82e48274761d75be0 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sat, 21 Oct 2023 09:55:11 -0400 Subject: [PATCH 38/59] doc changes --- docs/stdlibs.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/stdlibs.md b/docs/stdlibs.md index 42ed1b0f472..7d38c3380d5 100644 --- a/docs/stdlibs.md +++ b/docs/stdlibs.md @@ -114,6 +114,10 @@ Some things to keep in mind: reduced while we figure out the details on how to implement reflection. Aside from the `reflect` package itself, this also translates to very common packages still not available in Gno, such as `fmt` or `encoding/json`. +- In the package documentation, specify the Go version from which the library + was taken. +- All changes from the Go standard libaries must be explicitly marked, possibly + with `// XXX` comments as needed. If you intend to create a PR to add a new standard library, remember to update [Go\<\>Gno compatibility](go-gno-compatibility.md) accordingly. @@ -126,7 +130,7 @@ in Go. There are generally three reasons why a function should be natively defined: 1. It relies on inspecting the Gno Virtual Machine itself.\ - For example: `std.AssertOriginCall`, `std.GetBanker`. + For example: `std.AssertOriginCall`, `std.CurrentRealmPath`. 2. It relies on `unsafe`, or other features which are not planned to be available in the GnoVM.\ For example: `math.Float64frombits`. @@ -177,11 +181,9 @@ bindings, which are the following: version to their Go counterpart. Notable omissions at the time of writing include struct and map literals. If you intend to use these, modify the code generator to support them. -- The code generator does not inspect any imported packages from the Go native code; - as a consequence, either the import contains an identifier (ie. - `import gno "github.com/gnolang/gno/gnovm/pkg/gnolang"`), or the last element - in the import path is assumed to be the identifier used in the file. Note that - this is not always the case (the default identifier is the same as the - declared package name in the `package` clause of each Go file), so when the - last element and the package name differ, remember to specify an identifier in - the import. +- The code generator does not inspect any imported packages from the Go native code + to determine the default package identifier (ie. the `package` clause). + Ie. if a package is in `foo/bar`, but declares `package xyz`, when importing + foo/bar the generator will assume the name to be `bar` instead of `xyz`. + You can add an identifier to the import to fix this and use the identifier + you want/need, ie.: `import gno "github.com/gnolang/gno/gnovm/pkg/gnolang"`. From 361667a2569a7387901ffc4f88c7929f22a11800 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sat, 21 Oct 2023 09:57:19 -0400 Subject: [PATCH 39/59] base64 change doc --- gnovm/stdlibs/encoding/base64/base64.gno | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gnovm/stdlibs/encoding/base64/base64.gno b/gnovm/stdlibs/encoding/base64/base64.gno index 015d7a825bb..ea3b0a55c2a 100644 --- a/gnovm/stdlibs/encoding/base64/base64.gno +++ b/gnovm/stdlibs/encoding/base64/base64.gno @@ -496,6 +496,8 @@ func (enc *Encoding) Decode(dst, src []byte) (n int, err error) { _ = enc.decodeMap si := 0 + // XXX: Go source checks for strconv.IntSize >= 64 as well in this loop. + // In the gnovm, int size is always guaranteed to be 64 bits. for len(src)-si >= 8 && len(dst)-n >= 8 { src2 := src[si : si+8] if dn, ok := assemble64( From 5592265c28f14e0b2fe3e6f6a18cddbf49dfc424 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sat, 21 Oct 2023 11:50:05 -0400 Subject: [PATCH 40/59] fixes on new method --- gnovm/pkg/gnolang/machine.go | 3 +++ gnovm/pkg/gnolang/nodes.go | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 2bc5e8a91c4..b196c7cc5a0 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -270,6 +270,9 @@ func checkDuplicates(fset *FileSet) bool { var name Name switch d := d.(type) { case *FuncDecl: + if d.Name == "init" { + continue + } name = d.Name if d.IsMethod { name = Name(derefStar(d.Recv.Type).String()) + "." + name diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 06f3d9cb537..1f45b572521 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1257,7 +1257,11 @@ func (fs *FileSet) GetDeclFor(n Name) (*FileNode, *Decl) { func (fs *FileSet) GetDeclForSafe(n Name) (*FileNode, *Decl, bool) { // XXX index to bound to linear time. - for _, fn := range fs.Files { + + // Iteration happens reversing fs.Files; this is because the LAST declaration + // of n is what we are looking for. + for i := len(fs.Files) - 1; i >= 0; i-- { + fn := fs.Files[i] for i, dn := range fn.Decls { if _, isImport := dn.(*ImportDecl); isImport { // imports in other files don't count. From b5b46d6dc42627917f312f47ab1dd64b9d1d3781 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sat, 21 Oct 2023 11:57:13 -0400 Subject: [PATCH 41/59] lint fix --- gnovm/pkg/gnolang/machine.go | 2 +- gnovm/pkg/gnolang/nodes.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index b196c7cc5a0..5b0d891c0ce 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -270,7 +270,7 @@ func checkDuplicates(fset *FileSet) bool { var name Name switch d := d.(type) { case *FuncDecl: - if d.Name == "init" { + if d.Name == "init" { //nolint:goconst continue } name = d.Name diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 1f45b572521..48855f81c02 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1785,7 +1785,7 @@ func (sb *StaticBlock) Define2(isConst bool, n Name, st Type, tv TypedValue) { n, old.T, tv.T)) } // Allow re-definitions if they have the same type. - // (In normal scenarios, duplicate declarations are "caught" by RunMemPackage. + // (In normal scenarios, duplicate declarations are "caught" by RunMemPackage.) } sb.Block.Values[idx] = tv sb.Types[idx] = st From c7f0c1ae24763ef0927bfde3bbe41f785a611392 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Sat, 21 Oct 2023 10:36:23 -0700 Subject: [PATCH 42/59] jae trying new approach --- gnovm/pkg/gnolang/nodes.go | 38 +++++++--- gnovm/pkg/gnolang/preprocess.go | 125 ++++++++++++++++++++++++-------- gnovm/pkg/gnolang/types.go | 6 ++ gnovm/stdlibs/std/native.go | 2 + 4 files changed, 127 insertions(+), 44 deletions(-) diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 1f45b572521..6893fe39efb 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1731,12 +1731,11 @@ func (sb *StaticBlock) GetValueRef(store Store, n Name) *TypedValue { // values, which are pre-computeed in the preprocessor. // Once a typed value is defined, it cannot be changed. // -// NOTE: Currently tv.V is only set when the value -// represents a Type(Value). The purpose of tv is to describe -// the invariant of a named value, at the minimum its type, -// but also sometimes the typeval value; but we could go -// further and store preprocessed constant results here -// too. See "anyValue()" and "asValue()" for usage. +// NOTE: Currently tv.V is only set when the value represents a Type(Value) or +// a FuncValue. The purpose of tv is to describe the invariant of a named +// value, at the minimum its type, but also sometimes the typeval value; but we +// could go further and store preprocessed constant results here too. See +// "anyValue()" and "asValue()" for usage. func (sb *StaticBlock) Define(n Name, tv TypedValue) { sb.Define2(false, n, tv.T, tv) } @@ -1779,13 +1778,28 @@ func (sb *StaticBlock) Define2(isConst bool, n Name, st Type, tv TypedValue) { } old := sb.Block.Values[idx] if !old.IsUndefined() { - if tv.T.TypeID() != old.T.TypeID() { - panic(fmt.Sprintf( - "StaticBlock.Define2(%s) cannot change .T; was %v, new %v", - n, old.T, tv.T)) + if tv.T.Kind() == FuncKind && tv.T.(*FuncType).IsZero() { + // special case, + // allow re-predefining for func upgrades. + // keep the old type so we can check it at preprocessor. + tv.T = old.T + st = old.T + } else { + if tv.T.TypeID() != old.T.TypeID() { + panic(fmt.Sprintf( + "StaticBlock.Define2(%s) cannot change .T; was %v, new %v", + n, old.T, tv.T)) + } + if tv.V != old.V { + panic(fmt.Sprintf( + "StaticBlock.Define2(%s) cannot change .V", + n)) + } } - // Allow re-definitions if they have the same type. - // (In normal scenarios, duplicate declarations are "caught" by RunMemPackage. + /* + // Allow re-definitions if they have the same type. + // (In normal scenarios, duplicate declarations are "caught" by RunMemPackage. + */ } sb.Block.Values[idx] = tv sb.Types[idx] = st diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index caf156066c3..43905cb3265 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2895,6 +2895,59 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De } switch cd := d.(type) { case *FuncDecl: + // *FuncValue/*FuncType is mostly empty still; here + // we just fill the func type (and recv if method). + // NOTE: unlike the *ValueDecl case, this case doesn't + // preprocess d itself (only d.Type). + if cd.IsMethod { + if cd.Recv.Name == "" || cd.Recv.Name == "_" { + // create a hidden var with leading dot. + // NOTE: document somewhere. + cd.Recv.Name = ".recv" + } + cd.Recv = *Preprocess(store, last, &cd.Recv).(*FieldTypeExpr) + cd.Type = *Preprocess(store, last, &cd.Type).(*FuncTypeExpr) + rft := evalStaticType(store, last, &cd.Recv).(FieldType) + rt := rft.Type + ft := evalStaticType(store, last, &cd.Type).(*FuncType) + ft = ft.UnboundType(rft) + dt := (*DeclaredType)(nil) + if pt, ok := rt.(*PointerType); ok { + dt = pt.Elem().(*DeclaredType) + } else { + dt = rt.(*DeclaredType) + } + dt.DefineMethod(&FuncValue{ + Type: ft, + IsMethod: true, + Source: cd, + Name: cd.Name, + Closure: nil, // set lazily. + FileName: fileNameOf(last), + PkgPath: pkg.PkgPath, + body: cd.Body, + nativeBody: nil, + }) + } else { + ftv := pkg.GetValueRef(store, cd.Name) + ft := ftv.T.(*FuncType) + cd.Type = *Preprocess(store, last, &cd.Type).(*FuncTypeExpr) + ft2 := evalStaticType(store, last, &cd.Type).(*FuncType) + if !ft.IsZero() { + // redefining function. + // make sure the type is the sae. + if ft.TypeID() != ft2.TypeID() { + panic(fmt.Sprintf( + "Redefinition (%s) cannot change .T; was %v, new %v", + cd, ft, ft2)) + } + // keep the orig type. + } else { + *ft = *ft2 + } + // XXX replace attr w/ ft? + // return Preprocess(store, last, cd).(Decl), true + } // Full type declaration/preprocessing already done in tryPredefine return d, false case *ValueDecl: @@ -3095,42 +3148,47 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { return } - pkg := packageOf(last) + /* + pkg := packageOf(last) - // d.Recv's type is defined; add method to DeclaredType. - if d.Recv.Name == "" || d.Recv.Name == "_" { - // create a hidden var with leading dot. - // TODO: document somewhere. - d.Recv.Name = ".recv" - } + // d.Recv's type is defined; add method to DeclaredType. + if d.Recv.Name == "" || d.Recv.Name == "_" { + // create a hidden var with leading dot. + // TODO: document somewhere. + d.Recv.Name = ".recv" + } - d.Recv = *Preprocess(store, last, &d.Recv).(*FieldTypeExpr) - d.Type = *Preprocess(store, last, &d.Type).(*FuncTypeExpr) - rft := evalStaticType(store, last, &d.Recv).(FieldType) - rt := rft.Type - ft := evalStaticType(store, last, &d.Type).(*FuncType) - ft = ft.UnboundType(rft) - var dt *DeclaredType - if pt, ok := rt.(*PointerType); ok { - dt = pt.Elem().(*DeclaredType) - } else { - dt = rt.(*DeclaredType) - } - dt.DefineMethod(&FuncValue{ - Type: ft, - IsMethod: true, - Source: d, - Name: d.Name, - Closure: nil, // set lazily. - FileName: fileNameOf(last), - PkgPath: pkg.PkgPath, - body: d.Body, - nativeBody: nil, - }) + d.Recv = *Preprocess(store, last, &d.Recv).(*FieldTypeExpr) + d.Type = *Preprocess(store, last, &d.Type).(*FuncTypeExpr) + rft := evalStaticType(store, last, &d.Recv).(FieldType) + rt := rft.Type + ft := evalStaticType(store, last, &d.Type).(*FuncType) + ft = ft.UnboundType(rft) + var dt *DeclaredType + if pt, ok := rt.(*PointerType); ok { + dt = pt.Elem().(*DeclaredType) + } else { + dt = rt.(*DeclaredType) + } + dt.DefineMethod(&FuncValue{ + Type: ft, + IsMethod: true, + Source: d, + Name: d.Name, + Closure: nil, // set lazily. + FileName: fileNameOf(last), + PkgPath: pkg.PkgPath, + body: d.Body, + nativeBody: nil, + }) + */ } else { // define package-level function. - d.Type = *Preprocess(store, last, &d.Type).(*FuncTypeExpr) - ft := evalStaticType(store, last, &d.Type).(*FuncType) + ft := &FuncType{} + /* + d.Type = *Preprocess(store, last, &d.Type).(*FuncTypeExpr) + ft := evalStaticType(store, last, &d.Type).(*FuncType) + */ pkg := skipFile(last).(*PackageNode) // special case: if d.Name == "init", assign unique suffix. if d.Name == "init" { @@ -3142,6 +3200,9 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { } // define a FuncValue w/ above type as d.Name. // fill in later during *FuncDecl:BLOCK. + // XXX how can this work upon restart? + // XXX that is, are upgrades saved/loaded? how? + // XXX doesn't matter for tests... fv := &FuncValue{ Type: ft, IsMethod: false, diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index c3e439e9427..61065d5d6cb 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -1085,6 +1085,12 @@ type FuncType struct { bound *FuncType } +// true for predefined func types that are not filled in yet. +func (ft *FuncType) IsZero() bool { + // XXX be explicit. + return ft.Params == nil && ft.Results == nil && ft.typeid.IsZero() && ft.bound == nil +} + // if ft is a method, returns whether method takes a pointer receiver. func (ft *FuncType) HasPointerReceiver() bool { if debug { diff --git a/gnovm/stdlibs/std/native.go b/gnovm/stdlibs/std/native.go index 8cdddd916ad..5f6cc8a0061 100644 --- a/gnovm/stdlibs/std/native.go +++ b/gnovm/stdlibs/std/native.go @@ -1,6 +1,7 @@ package std import ( + "fmt" "reflect" gno "github.com/gnolang/gno/gnovm/pkg/gnolang" @@ -111,6 +112,7 @@ func GetOrigPkgAddr(m *gno.Machine) crypto.Bech32Address { func GetCallerAt(m *gno.Machine, n int) crypto.Bech32Address { if n <= 0 { + fmt.Println("QWEQWEQWEQWE", n) m.Panic(typedString("GetCallerAt requires positive arg")) return "" } From 1475790851f85f699a4ac8c16a0a92a34444f7bb Mon Sep 17 00:00:00 2001 From: jaekwon Date: Sat, 21 Oct 2023 10:47:02 -0700 Subject: [PATCH 43/59] also update the FuncValue.Type --- gnovm/pkg/gnolang/nodes.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 6893fe39efb..a64c6f40bbd 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1782,7 +1782,11 @@ func (sb *StaticBlock) Define2(isConst bool, n Name, st Type, tv TypedValue) { // special case, // allow re-predefining for func upgrades. // keep the old type so we can check it at preprocessor. + //fmt.Println("QWEQWEQWE>>>", old.String()) + //fmt.Println("QWEQWEQWE>>>", tv.String()) tv.T = old.T + fv := tv.V.(*FuncValue) + fv.Type = old.T st = old.T } else { if tv.T.TypeID() != old.T.TypeID() { From bb613a73bc1362e203d575bfc29991300e498f52 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sat, 21 Oct 2023 14:01:46 -0400 Subject: [PATCH 44/59] help for debugging --- gno.land/pkg/gnoland/app.go | 2 +- gnovm/pkg/gnolang/machine.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/gno.land/pkg/gnoland/app.go b/gno.land/pkg/gnoland/app.go index 3585f99d7de..59cf3dc42b1 100644 --- a/gno.land/pkg/gnoland/app.go +++ b/gno.land/pkg/gnoland/app.go @@ -159,7 +159,7 @@ func InitChainer(baseApp *sdk.BaseApp, acctKpr auth.AccountKeeperI, bankKpr bank // NOTE: comment out to ignore. if !skipFailingGenesisTxs { - panic(res.Error) + panic(res.Log) } } else { ctx.Logger().Info("SUCCESS:", string(amino.MustMarshalJSON(tx))) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 5b0d891c0ce..e8f942cf40c 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -281,6 +281,9 @@ func checkDuplicates(fset *FileSet) bool { name = d.Name case *ValueDecl: for _, nx := range d.NameExprs { + if nx.Name == "_" { + continue + } if _, ok := defined[nx.Name]; ok { return true } @@ -290,6 +293,9 @@ func checkDuplicates(fset *FileSet) bool { default: continue } + if name == "_" { + continue + } if _, ok := defined[name]; ok { return true } From ce0185eeb51775330cbd6461a613627abf7f5d6d Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sat, 21 Oct 2023 14:10:22 -0400 Subject: [PATCH 45/59] gofmt --- gnovm/pkg/gnolang/nodes.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 50966902d34..203feb61542 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1782,8 +1782,8 @@ func (sb *StaticBlock) Define2(isConst bool, n Name, st Type, tv TypedValue) { // special case, // allow re-predefining for func upgrades. // keep the old type so we can check it at preprocessor. - //fmt.Println("QWEQWEQWE>>>", old.String()) - //fmt.Println("QWEQWEQWE>>>", tv.String()) + // fmt.Println("QWEQWEQWE>>>", old.String()) + // fmt.Println("QWEQWEQWE>>>", tv.String()) tv.T = old.T fv := tv.V.(*FuncValue) fv.Type = old.T From 1f647e0982304fe904792180212535b94fd9df7a Mon Sep 17 00:00:00 2001 From: jaekwon Date: Sat, 21 Oct 2023 12:55:41 -0700 Subject: [PATCH 46/59] redeclaration tests; better redeclaration handling; still WIP --- .../tests/invalid/redeclaration1/README.md | 4 +++ .../invalid/redeclaration1/redeclaration.gno | 3 ++ .../invalid/redeclaration1/redeclaration2.gno | 4 +++ .../tests/invalid/redeclaration2/README.md | 4 +++ .../invalid/redeclaration2/redeclaration.gno | 3 ++ .../invalid/redeclaration2/redeclaration2.gno | 4 +++ .../tests/invalid/redeclaration3/README.md | 4 +++ .../invalid/redeclaration3/redeclaration.gno | 5 ++++ .../invalid/redeclaration3/redeclaration2.gno | 4 +++ gnovm/pkg/gnolang/nodes.go | 18 +++++++++++ gnovm/pkg/gnolang/preprocess.go | 5 +++- gnovm/pkg/gnolang/types.go | 30 +++++++++++++++++++ gnovm/pkg/gnolang/values.go | 16 ++++++++++ gnovm/tests/files/redeclaration10.gno | 12 ++++++++ gnovm/tests/files/redeclaration6.gno | 12 ++++++++ gnovm/tests/files/redeclaration7.gno | 13 ++++++++ gnovm/tests/files/redeclaration8.gno | 12 ++++++++ gnovm/tests/files/redeclaration9.gno | 14 +++++++++ 18 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 examples/gno.land/p/demo/tests/invalid/redeclaration1/README.md create mode 100644 examples/gno.land/p/demo/tests/invalid/redeclaration1/redeclaration.gno create mode 100644 examples/gno.land/p/demo/tests/invalid/redeclaration1/redeclaration2.gno create mode 100644 examples/gno.land/p/demo/tests/invalid/redeclaration2/README.md create mode 100644 examples/gno.land/p/demo/tests/invalid/redeclaration2/redeclaration.gno create mode 100644 examples/gno.land/p/demo/tests/invalid/redeclaration2/redeclaration2.gno create mode 100644 examples/gno.land/p/demo/tests/invalid/redeclaration3/README.md create mode 100644 examples/gno.land/p/demo/tests/invalid/redeclaration3/redeclaration.gno create mode 100644 examples/gno.land/p/demo/tests/invalid/redeclaration3/redeclaration2.gno create mode 100644 gnovm/tests/files/redeclaration10.gno create mode 100644 gnovm/tests/files/redeclaration6.gno create mode 100644 gnovm/tests/files/redeclaration7.gno create mode 100644 gnovm/tests/files/redeclaration8.gno create mode 100644 gnovm/tests/files/redeclaration9.gno diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration1/README.md b/examples/gno.land/p/demo/tests/invalid/redeclaration1/README.md new file mode 100644 index 00000000000..6c8514c3ca1 --- /dev/null +++ b/examples/gno.land/p/demo/tests/invalid/redeclaration1/README.md @@ -0,0 +1,4 @@ +This package is invalid because 'a' is defined twice. +NOTE: the Go parser itself returns an error for redefinitions in the same file, +but testing for redeclarations across files requires our own custom logic. +(arguably we should check ourself either way). diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration1/redeclaration.gno b/examples/gno.land/p/demo/tests/invalid/redeclaration1/redeclaration.gno new file mode 100644 index 00000000000..b9b17a50e80 --- /dev/null +++ b/examples/gno.land/p/demo/tests/invalid/redeclaration1/redeclaration.gno @@ -0,0 +1,3 @@ +package redeclaration + +var a = 1 diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration1/redeclaration2.gno b/examples/gno.land/p/demo/tests/invalid/redeclaration1/redeclaration2.gno new file mode 100644 index 00000000000..11f2f095cb4 --- /dev/null +++ b/examples/gno.land/p/demo/tests/invalid/redeclaration1/redeclaration2.gno @@ -0,0 +1,4 @@ +package redeclaration + +// redeclared +var a = 2 diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration2/README.md b/examples/gno.land/p/demo/tests/invalid/redeclaration2/README.md new file mode 100644 index 00000000000..6c8514c3ca1 --- /dev/null +++ b/examples/gno.land/p/demo/tests/invalid/redeclaration2/README.md @@ -0,0 +1,4 @@ +This package is invalid because 'a' is defined twice. +NOTE: the Go parser itself returns an error for redefinitions in the same file, +but testing for redeclarations across files requires our own custom logic. +(arguably we should check ourself either way). diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration2/redeclaration.gno b/examples/gno.land/p/demo/tests/invalid/redeclaration2/redeclaration.gno new file mode 100644 index 00000000000..ef6cac198db --- /dev/null +++ b/examples/gno.land/p/demo/tests/invalid/redeclaration2/redeclaration.gno @@ -0,0 +1,3 @@ +package redeclaration + +func a() int { return 1 } diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration2/redeclaration2.gno b/examples/gno.land/p/demo/tests/invalid/redeclaration2/redeclaration2.gno new file mode 100644 index 00000000000..3f58a963502 --- /dev/null +++ b/examples/gno.land/p/demo/tests/invalid/redeclaration2/redeclaration2.gno @@ -0,0 +1,4 @@ +package redeclaration + +// redeclared (with same signature) +func a() int { return 2 } diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration3/README.md b/examples/gno.land/p/demo/tests/invalid/redeclaration3/README.md new file mode 100644 index 00000000000..6c8514c3ca1 --- /dev/null +++ b/examples/gno.land/p/demo/tests/invalid/redeclaration3/README.md @@ -0,0 +1,4 @@ +This package is invalid because 'a' is defined twice. +NOTE: the Go parser itself returns an error for redefinitions in the same file, +but testing for redeclarations across files requires our own custom logic. +(arguably we should check ourself either way). diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration3/redeclaration.gno b/examples/gno.land/p/demo/tests/invalid/redeclaration3/redeclaration.gno new file mode 100644 index 00000000000..380a1a4288f --- /dev/null +++ b/examples/gno.land/p/demo/tests/invalid/redeclaration3/redeclaration.gno @@ -0,0 +1,5 @@ +package redeclaration + +type a struct{} + +func (_ a) method() int { return 1 } diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration3/redeclaration2.gno b/examples/gno.land/p/demo/tests/invalid/redeclaration3/redeclaration2.gno new file mode 100644 index 00000000000..c2256bdf7cb --- /dev/null +++ b/examples/gno.land/p/demo/tests/invalid/redeclaration3/redeclaration2.gno @@ -0,0 +1,4 @@ +package redeclaration + +// redeclared (with same signature) +func (_ a) method() int { return 2 } diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 50966902d34..f72e3560193 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -1483,6 +1483,22 @@ type StaticBlock struct { Consts []Name // TODO consider merging with Names. Externs []Name Loc Location + + // temporary storage for rolling back redefinitions. + oldValues []oldValue +} + +type oldValue struct { + idx uint16 + value Value +} + +// revert values upon failure of redefinitions. +func (sb *StaticBlock) revertToOld() { + for _, ov := range sb.oldValues { + sb.Block.Values[ov.idx].V = ov.value + } + sb.oldValues = nil } // Implements BlockNode @@ -1788,6 +1804,8 @@ func (sb *StaticBlock) Define2(isConst bool, n Name, st Type, tv TypedValue) { fv := tv.V.(*FuncValue) fv.Type = old.T st = old.T + sb.oldValues = append(sb.oldValues, + oldValue{idx, old.V}) } else { if tv.T.TypeID() != old.T.TypeID() { panic(fmt.Sprintf( diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 43905cb3265..d6e92d1395e 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -2935,8 +2935,11 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De ft2 := evalStaticType(store, last, &cd.Type).(*FuncType) if !ft.IsZero() { // redefining function. - // make sure the type is the sae. + // make sure the type is the same. if ft.TypeID() != ft2.TypeID() { + // revert all(????) new values. + pkg.StaticBlock.revertToOld() + // XXX what about new names?! panic(fmt.Sprintf( "Redefinition (%s) cannot change .T; was %v, new %v", cd, ft, ft2)) diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index 61065d5d6cb..7a1da3bace1 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -1452,6 +1452,36 @@ func (dt *DeclaredType) GetPkgPath() string { } func (dt *DeclaredType) DefineMethod(fv *FuncValue) { + name := fv.Name + if fv.FileName == "addr_set.gno" && fv.Name == "Size" { + panic("QWE") + } + // error for redeclarations + for i, tv := range dt.Methods { + ofv := tv.V.(*FuncValue) + if ofv.Name == name { + // as an exception, allow defining a native body. + if fv.Type.TypeID() == ofv.Type.TypeID() && + !ofv.IsNative() && fv.IsNative() { + dt.Methods[i] = TypedValue{ + T: fv.Type, // XXX shouldn't matter + V: fv, + } + return + } else { + fmt.Println("FV.Type>>>>", fv.Type.TypeID()) + fmt.Println("OFV.Type>>>>", ofv.Type.TypeID()) + fmt.Println(">>>>", fv.Type.TypeID() == ofv.Type.TypeID()) + fmt.Println(">>>>", fv.IsNative()) + fmt.Println(">>>>", ofv.IsNative()) + fmt.Printf("FV>>>> %#v\n", fv) + fmt.Printf("OFV>>>> %#v\n", ofv) + panic(fmt.Sprintf("redeclaration of method %s.%s", + dt.Name, name)) + } + } + } + // XXX maybe need to update in place for redefinitions? dt.Methods = append(dt.Methods, TypedValue{ T: fv.Type, V: fv, diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index f0b363b7bac..859794c8c01 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -532,6 +532,22 @@ type FuncValue struct { nativeBody func(*Machine) // alternative to Body } +func (fv *FuncValue) IsNative() bool { + if fv.NativePkg == "" { + if fv.NativeName == "" { + return false + } else { + panic("should not happen") + } + } else { + if fv.NativeName == "" { + panic("should not happen") + } else { + return true + } + } +} + func (fv *FuncValue) Copy(alloc *Allocator) *FuncValue { alloc.AllocateFunc() return &FuncValue{ diff --git a/gnovm/tests/files/redeclaration10.gno b/gnovm/tests/files/redeclaration10.gno new file mode 100644 index 00000000000..5c7819397c1 --- /dev/null +++ b/gnovm/tests/files/redeclaration10.gno @@ -0,0 +1,12 @@ +package main + +import _ "gno.land/p/demo/tests/invalid/redeclaration3" + +func main() { + println("should not happen") +} + +// XXX show what was redeclared. + +// Error: +// running package "gno.land/p/demo/tests/invalid/redeclaration3": duplicate declarations not allowed diff --git a/gnovm/tests/files/redeclaration6.gno b/gnovm/tests/files/redeclaration6.gno new file mode 100644 index 00000000000..6d7e4b3f0f4 --- /dev/null +++ b/gnovm/tests/files/redeclaration6.gno @@ -0,0 +1,12 @@ +package main + +import _ "gno.land/p/demo/tests/invalid/redeclaration1" + +func main() { + println("should not happen") +} + +// XXX show what was redeclared. + +// Error: +// running package "gno.land/p/demo/tests/invalid/redeclaration1": duplicate declarations not allowed diff --git a/gnovm/tests/files/redeclaration7.gno b/gnovm/tests/files/redeclaration7.gno new file mode 100644 index 00000000000..d987a58d7eb --- /dev/null +++ b/gnovm/tests/files/redeclaration7.gno @@ -0,0 +1,13 @@ +package main + +func f1() int { return 1 } + +func f1() int { return 2 } + +func main() { + println("hello", f1()) +} + +// Error: +// files/redeclaration7.gno:5:6: f1 redeclared in this block +// previous declaration at files/redeclaration7.gno:3:6 diff --git a/gnovm/tests/files/redeclaration8.gno b/gnovm/tests/files/redeclaration8.gno new file mode 100644 index 00000000000..9fafac22c21 --- /dev/null +++ b/gnovm/tests/files/redeclaration8.gno @@ -0,0 +1,12 @@ +package main + +import _ "gno.land/p/demo/tests/invalid/redeclaration2" + +func main() { + println("should not happen") +} + +// XXX show what was redeclared. + +// Error: +// running package "gno.land/p/demo/tests/invalid/redeclaration2": duplicate declarations not allowed diff --git a/gnovm/tests/files/redeclaration9.gno b/gnovm/tests/files/redeclaration9.gno new file mode 100644 index 00000000000..89a63683c13 --- /dev/null +++ b/gnovm/tests/files/redeclaration9.gno @@ -0,0 +1,14 @@ +package main + +type a struct{} + +func (_ a) method() int { return 1 } + +func (_ a) method() int { return 2 } + +func main() { + println("hello") +} + +// Error: +// main/files/redeclaration9.gno:7: redeclaration of method a.method From f6c83ae6dbc78035d2158a95ce726953e53d1439 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Sat, 21 Oct 2023 16:25:03 -0700 Subject: [PATCH 47/59] do not redeclare methods upon restart // hack --- gnovm/pkg/gnolang/nodes.go | 4 ++ gnovm/pkg/gnolang/types.go | 11 +++++- gnovm/tests/file.go | 1 + gnovm/tests/files/zrealm_tests0.gno | 58 ----------------------------- 4 files changed, 15 insertions(+), 59 deletions(-) diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index f72e3560193..6f77956c649 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -146,6 +146,10 @@ func (loc Location) IsZero() bool { loc.Nonce == 0 } +func (loc Location) Equal(other Location) bool { + return loc == other +} + // ---------------------------------------- // Attributes // All nodes have attributes for general analysis purposes. diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index 7a1da3bace1..00f0567194b 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -1454,12 +1454,20 @@ func (dt *DeclaredType) GetPkgPath() string { func (dt *DeclaredType) DefineMethod(fv *FuncValue) { name := fv.Name if fv.FileName == "addr_set.gno" && fv.Name == "Size" { - panic("QWE") + //panic("QWE") } // error for redeclarations for i, tv := range dt.Methods { ofv := tv.V.(*FuncValue) if ofv.Name == name { + // if the type and location are the same, + // this is due to PreprocessAllFilesAndSaveBlockNodes. + // XXX this is silly, don't even do this. + if fv.Type.TypeID() == ofv.Type.TypeID() && + fv.Source.GetLocation().Equal(ofv.Source.GetLocation()) { + // keep the old value + return + } // as an exception, allow defining a native body. if fv.Type.TypeID() == ofv.Type.TypeID() && !ofv.IsNative() && fv.IsNative() { @@ -1469,6 +1477,7 @@ func (dt *DeclaredType) DefineMethod(fv *FuncValue) { } return } else { + // XXX because the type was stored. fmt.Println("FV.Type>>>>", fv.Type.TypeID()) fmt.Println("OFV.Type>>>>", ofv.Type.TypeID()) fmt.Println(">>>>", fv.Type.TypeID() == ofv.Type.TypeID()) diff --git a/gnovm/tests/file.go b/gnovm/tests/file.go index 451bf0677dc..a021628a385 100644 --- a/gnovm/tests/file.go +++ b/gnovm/tests/file.go @@ -183,6 +183,7 @@ func RunFileTest(rootDir string, path string, opts ...RunFileTestOption) error { }, }, } + // run decls and init functions. m.RunMemPackage(memPkg, true) // reconstruct machine and clear store cache. // whether package is realm or not, since non-realm diff --git a/gnovm/tests/files/zrealm_tests0.gno b/gnovm/tests/files/zrealm_tests0.gno index ca4fc544e87..73d07f726eb 100644 --- a/gnovm/tests/files/zrealm_tests0.gno +++ b/gnovm/tests/files/zrealm_tests0.gno @@ -271,64 +271,6 @@ func main() { // "Results": [] // } // } -// }, -// { -// "T": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "t", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// } -// } -// } -// ], -// "Results": [] -// }, -// "V": { -// "@type": "/gno.FuncValue", -// "Closure": null, -// "FileName": "tests.gno", -// "IsMethod": true, -// "Name": "Modify", -// "NativeName": "", -// "NativePkg": "", -// "PkgPath": "gno.land/r/demo/tests", -// "Source": { -// "@type": "/gno.RefNode", -// "BlockNode": null, -// "Location": { -// "File": "tests.gno", -// "Line": "42", -// "Nonce": "0", -// "PkgPath": "gno.land/r/demo/tests" -// } -// }, -// "Type": { -// "@type": "/gno.FuncType", -// "Params": [ -// { -// "Embedded": false, -// "Name": "t", -// "Tag": "", -// "Type": { -// "@type": "/gno.PointerType", -// "Elt": { -// "@type": "/gno.RefType", -// "ID": "gno.land/r/demo/tests.TestRealmObject" -// } -// } -// } -// ], -// "Results": [] -// } -// } // } // ], // "Name": "TestRealmObject", From 7874fb788077a5ea4e112ae73114250f1f33e43e Mon Sep 17 00:00:00 2001 From: jaekwon Date: Mon, 23 Oct 2023 15:24:37 -0700 Subject: [PATCH 48/59] cleanup --- gnovm/pkg/gnolang/types.go | 45 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index 00f0567194b..41f41d761e1 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -1453,44 +1453,45 @@ func (dt *DeclaredType) GetPkgPath() string { func (dt *DeclaredType) DefineMethod(fv *FuncValue) { name := fv.Name - if fv.FileName == "addr_set.gno" && fv.Name == "Size" { - //panic("QWE") - } - // error for redeclarations + + // Handle redeclarations. for i, tv := range dt.Methods { ofv := tv.V.(*FuncValue) if ofv.Name == name { - // if the type and location are the same, - // this is due to PreprocessAllFilesAndSaveBlockNodes. - // XXX this is silly, don't even do this. + // Do not allow redeclaring (override) a method. + // In the future we may allow this, just like we + // allow package-level function overrides. + + // Special case: if the type and location are the same, + // ignore and do not redefine. + // This is due to PreprocessAllFilesAndSaveBlocknodes, + // and because the preprocessor fills some of the + // method's FuncValue. Since the method was already + // filled in prior to PreprocessAllFilesAndSaveBlocks, + // there is no need to re-set it. + // Keep this or move this check outside. if fv.Type.TypeID() == ofv.Type.TypeID() && fv.Source.GetLocation().Equal(ofv.Source.GetLocation()) { - // keep the old value return } - // as an exception, allow defining a native body. + + // Special case: allow defining a native body. if fv.Type.TypeID() == ofv.Type.TypeID() && !ofv.IsNative() && fv.IsNative() { dt.Methods[i] = TypedValue{ - T: fv.Type, // XXX shouldn't matter + T: fv.Type, // keep old type. V: fv, } return - } else { - // XXX because the type was stored. - fmt.Println("FV.Type>>>>", fv.Type.TypeID()) - fmt.Println("OFV.Type>>>>", ofv.Type.TypeID()) - fmt.Println(">>>>", fv.Type.TypeID() == ofv.Type.TypeID()) - fmt.Println(">>>>", fv.IsNative()) - fmt.Println(">>>>", ofv.IsNative()) - fmt.Printf("FV>>>> %#v\n", fv) - fmt.Printf("OFV>>>> %#v\n", ofv) - panic(fmt.Sprintf("redeclaration of method %s.%s", - dt.Name, name)) } + + // Otherwise panic. + panic(fmt.Sprintf("redeclaration of method %s.%s", + dt.Name, name)) } } - // XXX maybe need to update in place for redefinitions? + + // If not redeclaring, just append. dt.Methods = append(dt.Methods, TypedValue{ T: fv.Type, V: fv, From 5930f1a15eddf408b942090e9f2de5b5b7b2ec77 Mon Sep 17 00:00:00 2001 From: jaekwon Date: Tue, 24 Oct 2023 08:49:54 -0700 Subject: [PATCH 49/59] recover and revertToOld upon panic in preprocessor --- gnovm/pkg/gnolang/preprocess.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index d6e92d1395e..4ee08b6e73e 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -121,6 +121,19 @@ var preprocessing int // - Assigns BlockValuePath to NameExprs. // - TODO document what it does. func Preprocess(store Store, ctx BlockNode, n Node) Node { + + // When panic, revert any package updates. + defer func() { + // Revert all new values. + // this is needed to revert top level + // function redeclarations. + if r := recover(); r != nil { + pkg := packageOf(ctx) + pkg.StaticBlock.revertToOld() + panic(r) + } + }() + // Increment preprocessing counter while preprocessing. { preprocessing += 1 @@ -2937,9 +2950,6 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De // redefining function. // make sure the type is the same. if ft.TypeID() != ft2.TypeID() { - // revert all(????) new values. - pkg.StaticBlock.revertToOld() - // XXX what about new names?! panic(fmt.Sprintf( "Redefinition (%s) cannot change .T; was %v, new %v", cd, ft, ft2)) From d8d93c87b9ad9d17415254336b3b59e761d60f5f Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 27 Oct 2023 14:51:26 -0400 Subject: [PATCH 50/59] workflow changes --- .github/workflows/codegen.yml | 36 +++++++++++++++++++++++++++++++++++ .github/workflows/lint.yml | 19 ------------------ 2 files changed, 36 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/codegen.yml diff --git a/.github/workflows/codegen.yml b/.github/workflows/codegen.yml new file mode 100644 index 00000000000..f941dd69855 --- /dev/null +++ b/.github/workflows/codegen.yml @@ -0,0 +1,36 @@ +name: code generation + +on: + push: + branches: [ "master" ] + pull_request: + paths: + - 'gnovm/stdlibs/**' + - 'gnovm/tests/stdlibs/**' + - 'misc/genstd' + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + generated: + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + + - name: Checkout code + uses: actions/checkout@v4 + + - name: Check generated files are up to date + run: | + go generate -x ./... + if [ "$(git status -s)" != "" ]; then + echo "command 'go generate' creates file that differ from git tree, please run 'go generate' and commit:" + git status -s + exit 1 + fi + diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 1284cb78ddb..b4bccbff5df 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -76,22 +76,3 @@ jobs: env -C $path go mod tidy -v || exit 1 done echo "$sums" | sha256sum -c - generated: - runs-on: ubuntu-latest - steps: - - name: Install Go - uses: actions/setup-go@v4 - with: - go-version: 1.21.x - - - name: Checkout code - uses: actions/checkout@v4 - - - name: Check generated files are up to date - run: | - go generate -x ./... - if [ "$(git status -s)" != "" ]; then - echo "command 'go generate' creates file that differ from git tree, please run 'go generate' and commit:" - git status -s - exit 1 - fi From c9deb06f8fb0f95e62fadcaafb6b172a4be21cbf Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 27 Oct 2023 15:06:16 -0400 Subject: [PATCH 51/59] fmt --- gnovm/pkg/gnolang/preprocess.go | 1 - 1 file changed, 1 deletion(-) diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 4ee08b6e73e..e947e336b94 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -121,7 +121,6 @@ var preprocessing int // - Assigns BlockValuePath to NameExprs. // - TODO document what it does. func Preprocess(store Store, ctx BlockNode, n Node) Node { - // When panic, revert any package updates. defer func() { // Revert all new values. From 9439ac3924fc44b90b856edac0e257d38386ceb0 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 27 Oct 2023 15:22:35 -0400 Subject: [PATCH 52/59] make error understandable --- gnovm/pkg/gnolang/values.go | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/gnovm/pkg/gnolang/values.go b/gnovm/pkg/gnolang/values.go index 859794c8c01..49d9f6100fe 100644 --- a/gnovm/pkg/gnolang/values.go +++ b/gnovm/pkg/gnolang/values.go @@ -533,19 +533,15 @@ type FuncValue struct { } func (fv *FuncValue) IsNative() bool { - if fv.NativePkg == "" { - if fv.NativeName == "" { - return false - } else { - panic("should not happen") - } - } else { - if fv.NativeName == "" { - panic("should not happen") - } else { - return true - } + if fv.NativePkg == "" && fv.NativeName == "" { + return false + } + if fv.NativePkg == "" || fv.NativeName == "" { + panic(fmt.Sprintf("function (%q).%s has invalid native pkg/name ((%q).%s)", + fv.Source.GetLocation().PkgPath, fv.Name, + fv.NativePkg, fv.NativeName)) } + return true } func (fv *FuncValue) Copy(alloc *Allocator) *FuncValue { From b89bf911863ad549c04eeafbe5427da88181389c Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 27 Oct 2023 15:51:47 -0400 Subject: [PATCH 53/59] move invalid packages to gnovm/tests/files/extern --- .../tests/files/extern}/redeclaration1/README.md | 0 .../tests/files/extern}/redeclaration1/redeclaration.gno | 0 .../tests/files/extern}/redeclaration1/redeclaration2.gno | 0 .../tests/files/extern}/redeclaration2/README.md | 0 .../tests/files/extern}/redeclaration2/redeclaration.gno | 0 .../tests/files/extern}/redeclaration2/redeclaration2.gno | 0 .../tests/files/extern}/redeclaration3/README.md | 0 .../tests/files/extern}/redeclaration3/redeclaration.gno | 0 .../tests/files/extern}/redeclaration3/redeclaration2.gno | 0 gnovm/tests/files/redeclaration10.gno | 4 ++-- gnovm/tests/files/redeclaration6.gno | 4 ++-- gnovm/tests/files/redeclaration8.gno | 4 ++-- 12 files changed, 6 insertions(+), 6 deletions(-) rename {examples/gno.land/p/demo/tests/invalid => gnovm/tests/files/extern}/redeclaration1/README.md (100%) rename {examples/gno.land/p/demo/tests/invalid => gnovm/tests/files/extern}/redeclaration1/redeclaration.gno (100%) rename {examples/gno.land/p/demo/tests/invalid => gnovm/tests/files/extern}/redeclaration1/redeclaration2.gno (100%) rename {examples/gno.land/p/demo/tests/invalid => gnovm/tests/files/extern}/redeclaration2/README.md (100%) rename {examples/gno.land/p/demo/tests/invalid => gnovm/tests/files/extern}/redeclaration2/redeclaration.gno (100%) rename {examples/gno.land/p/demo/tests/invalid => gnovm/tests/files/extern}/redeclaration2/redeclaration2.gno (100%) rename {examples/gno.land/p/demo/tests/invalid => gnovm/tests/files/extern}/redeclaration3/README.md (100%) rename {examples/gno.land/p/demo/tests/invalid => gnovm/tests/files/extern}/redeclaration3/redeclaration.gno (100%) rename {examples/gno.land/p/demo/tests/invalid => gnovm/tests/files/extern}/redeclaration3/redeclaration2.gno (100%) diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration1/README.md b/gnovm/tests/files/extern/redeclaration1/README.md similarity index 100% rename from examples/gno.land/p/demo/tests/invalid/redeclaration1/README.md rename to gnovm/tests/files/extern/redeclaration1/README.md diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration1/redeclaration.gno b/gnovm/tests/files/extern/redeclaration1/redeclaration.gno similarity index 100% rename from examples/gno.land/p/demo/tests/invalid/redeclaration1/redeclaration.gno rename to gnovm/tests/files/extern/redeclaration1/redeclaration.gno diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration1/redeclaration2.gno b/gnovm/tests/files/extern/redeclaration1/redeclaration2.gno similarity index 100% rename from examples/gno.land/p/demo/tests/invalid/redeclaration1/redeclaration2.gno rename to gnovm/tests/files/extern/redeclaration1/redeclaration2.gno diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration2/README.md b/gnovm/tests/files/extern/redeclaration2/README.md similarity index 100% rename from examples/gno.land/p/demo/tests/invalid/redeclaration2/README.md rename to gnovm/tests/files/extern/redeclaration2/README.md diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration2/redeclaration.gno b/gnovm/tests/files/extern/redeclaration2/redeclaration.gno similarity index 100% rename from examples/gno.land/p/demo/tests/invalid/redeclaration2/redeclaration.gno rename to gnovm/tests/files/extern/redeclaration2/redeclaration.gno diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration2/redeclaration2.gno b/gnovm/tests/files/extern/redeclaration2/redeclaration2.gno similarity index 100% rename from examples/gno.land/p/demo/tests/invalid/redeclaration2/redeclaration2.gno rename to gnovm/tests/files/extern/redeclaration2/redeclaration2.gno diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration3/README.md b/gnovm/tests/files/extern/redeclaration3/README.md similarity index 100% rename from examples/gno.land/p/demo/tests/invalid/redeclaration3/README.md rename to gnovm/tests/files/extern/redeclaration3/README.md diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration3/redeclaration.gno b/gnovm/tests/files/extern/redeclaration3/redeclaration.gno similarity index 100% rename from examples/gno.land/p/demo/tests/invalid/redeclaration3/redeclaration.gno rename to gnovm/tests/files/extern/redeclaration3/redeclaration.gno diff --git a/examples/gno.land/p/demo/tests/invalid/redeclaration3/redeclaration2.gno b/gnovm/tests/files/extern/redeclaration3/redeclaration2.gno similarity index 100% rename from examples/gno.land/p/demo/tests/invalid/redeclaration3/redeclaration2.gno rename to gnovm/tests/files/extern/redeclaration3/redeclaration2.gno diff --git a/gnovm/tests/files/redeclaration10.gno b/gnovm/tests/files/redeclaration10.gno index 5c7819397c1..01584b1755c 100644 --- a/gnovm/tests/files/redeclaration10.gno +++ b/gnovm/tests/files/redeclaration10.gno @@ -1,6 +1,6 @@ package main -import _ "gno.land/p/demo/tests/invalid/redeclaration3" +import _ "github.com/gnolang/gno/_test/redeclaration3" func main() { println("should not happen") @@ -9,4 +9,4 @@ func main() { // XXX show what was redeclared. // Error: -// running package "gno.land/p/demo/tests/invalid/redeclaration3": duplicate declarations not allowed +// running package "github.com/gnolang/gno/_test/redeclaration3": duplicate declarations not allowed diff --git a/gnovm/tests/files/redeclaration6.gno b/gnovm/tests/files/redeclaration6.gno index 6d7e4b3f0f4..25e36fa61aa 100644 --- a/gnovm/tests/files/redeclaration6.gno +++ b/gnovm/tests/files/redeclaration6.gno @@ -1,6 +1,6 @@ package main -import _ "gno.land/p/demo/tests/invalid/redeclaration1" +import _ "github.com/gnolang/gno/_test/redeclaration1" func main() { println("should not happen") @@ -9,4 +9,4 @@ func main() { // XXX show what was redeclared. // Error: -// running package "gno.land/p/demo/tests/invalid/redeclaration1": duplicate declarations not allowed +// running package "github.com/gnolang/gno/_test/redeclaration1": duplicate declarations not allowed diff --git a/gnovm/tests/files/redeclaration8.gno b/gnovm/tests/files/redeclaration8.gno index 9fafac22c21..d0e5b958030 100644 --- a/gnovm/tests/files/redeclaration8.gno +++ b/gnovm/tests/files/redeclaration8.gno @@ -1,6 +1,6 @@ package main -import _ "gno.land/p/demo/tests/invalid/redeclaration2" +import _ "github.com/gnolang/gno/_test/redeclaration2" func main() { println("should not happen") @@ -9,4 +9,4 @@ func main() { // XXX show what was redeclared. // Error: -// running package "gno.land/p/demo/tests/invalid/redeclaration2": duplicate declarations not allowed +// running package "github.com/gnolang/gno/_test/redeclaration2": duplicate declarations not allowed From 7ca01671209b3348e36ca3b5cf13a24a285933a3 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Fri, 27 Oct 2023 18:06:48 -0400 Subject: [PATCH 54/59] make gno test . work for math, and other native bindings stdlibs --- gnovm/cmd/gno/test.go | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/gnovm/cmd/gno/test.go b/gnovm/cmd/gno/test.go index 85fe3f7ee7d..0bdc7abe844 100644 --- a/gnovm/cmd/gno/test.go +++ b/gnovm/cmd/gno/test.go @@ -321,8 +321,11 @@ func gnoTestPkg( if err == nil { gnoPkgPath = modfile.Module.Mod.Path } else { - // unable to read pkgPath from gno.mod, generate a random realm path - gnoPkgPath = gno.GnoRealmPkgsPrefixBefore + random.RandStr(8) + gnoPkgPath = pkgPathFromRootDir(pkgPath, rootDir) + if gnoPkgPath == "" { + // unable to read pkgPath from gno.mod, generate a random realm path + gnoPkgPath = gno.GnoRealmPkgsPrefixBefore + random.RandStr(8) + } } memPkg := gno.ReadMemPackage(pkgPath, gnoPkgPath) @@ -408,6 +411,35 @@ func gnoTestPkg( return errs } +// attempts to determine the full gno pkg path by analyzing the directory. +func pkgPathFromRootDir(pkgPath, rootDir string) string { + abPkgPath, err := filepath.Abs(pkgPath) + if err != nil { + log.Printf("could not determine abs path: %v", err) + return "" + } + abRootDir, err := filepath.Abs(rootDir) + if err != nil { + log.Printf("could not determine abs path: %v", err) + return "" + } + abRootDir += string(filepath.Separator) + if !strings.HasPrefix(abPkgPath, abRootDir) { + return "" + } + impPath := strings.ReplaceAll(abPkgPath[len(abRootDir):], string(filepath.Separator), "/") + for _, prefix := range [...]string{ + "examples/", + "gnovm/stdlibs/", + "gnovm/tests/stdlibs/", + } { + if strings.HasPrefix(impPath, prefix) { + return impPath[len(prefix):] + } + } + return "" +} + func runTestFiles( m *gno.Machine, files *gno.FileSet, From 2ff3f638d29b0aa532c0e234aa09fae06e10b9d0 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Sat, 28 Oct 2023 18:17:12 +0200 Subject: [PATCH 55/59] add test to check for revertToOld --- gnovm/pkg/gnolang/machine_test.go | 50 ++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/gnovm/pkg/gnolang/machine_test.go b/gnovm/pkg/gnolang/machine_test.go index e37fc508cb6..4268e3f3332 100644 --- a/gnovm/pkg/gnolang/machine_test.go +++ b/gnovm/pkg/gnolang/machine_test.go @@ -1,6 +1,16 @@ package gnolang -import "testing" +import ( + "fmt" + "testing" + + dbm "github.com/gnolang/gno/tm2/pkg/db" + "github.com/gnolang/gno/tm2/pkg/std" + "github.com/gnolang/gno/tm2/pkg/store/dbadapter" + "github.com/gnolang/gno/tm2/pkg/store/iavl" + stypes "github.com/gnolang/gno/tm2/pkg/store/types" + "github.com/jaekwon/testify/assert" +) func BenchmarkCreateNewMachine(b *testing.B) { for i := 0; i < b.N; i++ { @@ -8,3 +18,41 @@ func BenchmarkCreateNewMachine(b *testing.B) { m.Release() } } + +func TestRunMemPackageWithOverrides_revertToOld(t *testing.T) { + // A test to check revertToOld is correctly putting back an old value, + // after preprocessing fails. + db := dbm.NewMemDB() + baseStore := dbadapter.StoreConstructor(db, stypes.StoreOptions{}) + iavlStore := iavl.StoreConstructor(db, stypes.StoreOptions{}) + store := NewStore(nil, baseStore, iavlStore) + m := NewMachine("std", store) + m.RunMemPackageWithOverrides(&std.MemPackage{ + Name: "std", + Path: "std", + Files: []*std.MemFile{ + {Name: "a.gno", Body: `package std; func Redecl(x int) string { return "1" }`}, + }, + }, true) + result := func() (p string) { + defer func() { + p = fmt.Sprint(recover()) + }() + m.RunMemPackageWithOverrides(&std.MemPackage{ + Name: "std", + Path: "std", + Files: []*std.MemFile{ + {Name: "b.gno", Body: `package std; func Redecl(x int) string { var y string; _, _ = y; return "2" }`}, + }, + }, true) + return + }() + t.Log("panic trying to redeclare invalid func", result) + m.RunStatement(S(Call(X("Redecl"), 11))) + + // Check last value, assuming it is the result of Redecl. + v := m.Values[0] + assert.NotNil(t, v) + assert.Equal(t, v.T.Kind(), StringKind) + assert.Equal(t, v.V, StringValue("1")) +} From 6f6a621e63cb3ee3fa9106aa6c64ea4dea2d7c23 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 18 Dec 2023 22:24:42 +0100 Subject: [PATCH 56/59] prefer comparing locations directly --- gnovm/pkg/gnolang/nodes.go | 4 ---- gnovm/pkg/gnolang/types.go | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/gnovm/pkg/gnolang/nodes.go b/gnovm/pkg/gnolang/nodes.go index 747f418376e..de8f350f02f 100644 --- a/gnovm/pkg/gnolang/nodes.go +++ b/gnovm/pkg/gnolang/nodes.go @@ -146,10 +146,6 @@ func (loc Location) IsZero() bool { loc.Nonce == 0 } -func (loc Location) Equal(other Location) bool { - return loc == other -} - // ---------------------------------------- // Attributes // All nodes have attributes for general analysis purposes. diff --git a/gnovm/pkg/gnolang/types.go b/gnovm/pkg/gnolang/types.go index 41f41d761e1..60d6611ae1e 100644 --- a/gnovm/pkg/gnolang/types.go +++ b/gnovm/pkg/gnolang/types.go @@ -1471,7 +1471,7 @@ func (dt *DeclaredType) DefineMethod(fv *FuncValue) { // there is no need to re-set it. // Keep this or move this check outside. if fv.Type.TypeID() == ofv.Type.TypeID() && - fv.Source.GetLocation().Equal(ofv.Source.GetLocation()) { + fv.Source.GetLocation() == ofv.Source.GetLocation() { return } From ac66975181255eb98a4dce5b3b0fdc4ffebe80ff Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 18 Dec 2023 22:36:48 +0100 Subject: [PATCH 57/59] fix lint --- docs/reference/standard-library.md | 226 ++++++++++++++++++++++------- gnovm/pkg/gnolang/preprocess.go | 22 --- gnovm/tests/imports.go | 7 - 3 files changed, 176 insertions(+), 79 deletions(-) diff --git a/docs/reference/standard-library.md b/docs/reference/standard-library.md index f6ab5b307ce..8776c62faf8 100644 --- a/docs/reference/standard-library.md +++ b/docs/reference/standard-library.md @@ -2,67 +2,193 @@ id: standard-library --- -# Gno Standard Library - -When developing a realm in Gnolang, developers may utilize libraries in [stdlibs](https://github.com/gnolang/gno/tree/master/gnovm/stdlibs). These are the core standard packages provided for Gnolang [Realms ](../explanation/realms.md)& [Packages](../explanation/packages.md). - -Libraries can be imported in a manner similar to how libraries are imported in Golang. - -An example of importing a `std` library in Gnolang is demonstrated in the following command: - -```go -import "std" +# Standard Libraries + +Gno comes with a set of standard libraries which are included whenever you +execute Gno code. These are distinguishable from imports of packages from the +chain by not referring to a "domain" as the first part of their import path. For +instance, `import "encoding/binary"` refers to a standard library, while +`import "gno.land/p/demo/avl"` refers to an on-chain package. + +Standard libraries packages follow the same semantics as on-chain packages (ie. +they don't persist state like realms do) and come as a part of the Gno +programming language rather than with the Gno.land chain. + +Many standard libaries are near-identical copies of the equivalent Go standard +libraries; in fact, you can check the current status of implementation of each +Go standard libarary on [Go\<\>Gno compatibility](go-gno-compatibility.md). + +## Gathering documentation + +At the time being, there is no "list" of the available standard libraries +available from Gno tooling or documentation, but you can obtain a list of all +the available packages with the following commands: + +```console +$ cd gnovm/stdlibs # go to correct directory +$ find -type d +./testing +./math +./crypto +./crypto/chacha20 +./crypto/chacha20/chacha +./crypto/chacha20/rand +./crypto/sha256 +./crypto/cipher +... ``` -Let's explore some of the most commonly used modules in the library. - -## `stdshim` - -### `banker.gno` +All of the packages have automatic, generated documentation through the use of +`gno doc`, which has similar functionality and features to `go doc`: -A library for manipulating `Coins`. Interfaces that must be implemented when using this library are as follows: +```console +$ gno doc encoding/binary +package binary // import "encoding/binary" -[embedmd]:# (../assets/reference/standard-library/std-1.gno go) -```go -// returns the list of coins owned by the address -GetCoins(addr Address) (dst Coins) +Package binary implements simple translation between numbers and byte sequences +and encoding and decoding of varints. -// sends coins from one address to another -SendCoins(from, to Address, amt Coins) +[...] -// returns the total supply of the coin -TotalCoin(denom string) int64 +var BigEndian bigEndian +var LittleEndian littleEndian +type AppendByteOrder interface{ ... } +type ByteOrder interface{ ... } +$ gno doc -u -src encoding/binary littleEndian.AppendUint16 +package binary // import "encoding/binary" -// issues coins to the address -IssueCoin(addr Address, denom string, amount int64) - -// burns coins from the address -RemoveCoin(addr Address, denom string, amount int64) +func (littleEndian) AppendUint16(b []byte, v uint16) []byte { + return append(b, + byte(v), + byte(v>>8), + ) +} ``` -### `coins.gno` +`gno doc` will work automatically when used within the Gno repository or any +repository which has a `go.mod` dependency on `github.com/gnolang/gno`, which +can be a simple way to set up your Gno repositories to automatically support +`gno` commands (aside from `doc`, also `test`, `run`, etc.). -A library that declares structs for expressing `Coins`. The struct looks like the following: +Another alternative is setting your enviornment variable `GNOROOT` to point to +where you cloned the Gno repository. You can set this in your `~/.profile` file +to be automatically set up in your console: -[embedmd]:# (../assets/reference/standard-library/std-2.gno go) -```go -type Coin struct { - Denom string `json:"denom"` // the symbol of the coin - Amount int64 `json:"amount"` // the quantity of the coin -} +```sh +export GNOROOT=$HOME/gno ``` -### `testing` - -A library that declares `*testing`, which is a tool used for the creation and execution of test cases during the development and testing phase of realms utilizing the `gno` CLI tool with the `test` option. - -There are 3 types of testing in `gno`. +## Test standard libraries + +There are some additional standard library functions and packages which are +currently available only in `_test.gno` and `_filetest.gno` files. At the time +of writing, these are only some additions in the `std` package to support +changing some values in test functions. + +`gno doc` currently doesn't support reading from the test standard libraries, +though support is planned to be added. For now, you can inspect the directory +`gnovm/tests/stdlibs`. + +## Adding new standard libraries + +New standard libraries may be added by simply creating a new directory (whose +path relative to the `stdlibs` directory will be the import path used in Gno +programs). Following that, the suggested approach for adding a Go standard +libary is to copy the original files from the Go source tree, and renaming their +extensions from `.go` to `.gno`. + +> As a small aid, this bash one-liner can be useful to convert all the file +> extensions: +> +> ```sh +> for i in *.go; do mv $i "$(echo $i | sed 's/\.go$/.gno/')"; done +> ``` + +Following that, the suggested approach is to iteratively try running `gno test .`, +while fixing any errors that may come out of trying to test the package. + +Some things to keep in mind: + +- Gno doesn't support assembly functions and build tags. Some Go packages may + contain assembly versions for different architecture and a `generic.go` file + containing the architecture-independent version. The general approach is that + of removing everything architecture/os-specific except for the `generic.go` file. +- Gno doesn't support reflection at the time of writing, which means that for + now many packages which rely heavily on reflection have to be delayed or + reduced while we figure out the details on how to implement reflection. + Aside from the `reflect` package itself, this also translates to very common + packages still not available in Gno, such as `fmt` or `encoding/json`. +- In the package documentation, specify the Go version from which the library + was taken. +- All changes from the Go standard libaries must be explicitly marked, possibly + with `// XXX` comments as needed. + +If you intend to create a PR to add a new standard library, remember to update +[Go\<\>Gno compatibility](go-gno-compatibility.md) accordingly. + +## Native bindings + +Gno has support for "natively-defined functions" exclusively within the standard +libaries. These are functions which are _declared_ in Gno code, but only _defined_ +in Go. There are generally three reasons why a function should be natively +defined: + +1. It relies on inspecting the Gno Virtual Machine itself.\ + For example: `std.AssertOriginCall`, `std.CurrentRealmPath`. +2. It relies on `unsafe`, or other features which are not planned to be + available in the GnoVM.\ + For example: `math.Float64frombits`. +3. Its native Go performance significantly outperforms the Gno counterpart by + several orders of magnitude, and it is used in crucial code or hot paths in + many programs.\ + For example: `sha256.Sum256`. + +The takeaway here is that native bindings are a special feature which can be +useful to overcome pure Gno limitations, but it is not a substitute for writing +standard libaries in Gno. + +There are three components to a natively bound function in Gno: + +1. The Gno function declaration, which must be a top-level function with no body + (and no brackets).\ + For example: `crypto/sha256/sha256.gno`. +2. The Go function definition, which must be a top-level function with the same + name and signature.\ + For example: `crypto/sha256/sha256.go`. +3. When the two above are present and valid, the native binding can be created + by executing the code generator: either execute `go generate` from the + `stdlibs` directory, or run `make generate` from the `gnovm` directory.\ + This generates the `native.go` file available in the `stdlibs` directory, + which provides the binding itself to then be used by the GnoVM. + +The code generator in question is available in the `misc/genstd` directory. +There are some quirks and features that must be kept in mind when writing native +bindings, which are the following: + +- Unexported functions (for instance, `func sum256(b []byte)`) must have their + Go counterpart prefixed with `X_` in order to make the functions exported (ie. + `func X_sum256(b []byte)`). +- The Go function declaration may specify as the first argument + `m *gno.Machine`, where `gno` is an import for + `github.com/gnolang/gno/gnovm/pkg/gnolang`. This gives the function access to + the Virtual Machine state, and is used by functions like `std.AssertOriginCall()`. +- The Go function may change the type of any parameter or result to + `gno.TypedValue` (where `gno` is an import for the above import path). This + means that the `native.go` generated code will not attempt to automatically + convert the Gno value into the Go value, and can be useful for unsupported + conversions like interface values. +- A small set of named types are "linked" between their Gno version and Go + counterpart. For instance, `std.Address` in Gno is + `(".../tm2/pkg/crypto").Bech32Address` in Go. A list of these can be found in + `misc/genstd/mapping.go`. +- Not all type literals are currently supported when converting from their Gno + version to their Go counterpart. Notable omissions at the time of writing + include struct and map literals. If you intend to use these, modify the code + generator to support them. +- The code generator does not inspect any imported packages from the Go native code + to determine the default package identifier (ie. the `package` clause). + Ie. if a package is in `foo/bar`, but declares `package xyz`, when importing + foo/bar the generator will assume the name to be `bar` instead of `xyz`. + You can add an identifier to the import to fix this and use the identifier + you want/need, ie.: `import gno "github.com/gnolang/gno/gnovm/pkg/gnolang"`. -* Type `T` - * Type passed to Test functions to manage test state and support formatted test logs. -* Type `B` - * Type passed to Benchmark functions. - * Manage benchmark timing. - * Specify the number of iterations to run. -* Type `PB` - * Used by `RunParallel` for running parallel benchmarks. diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index 1145f04c899..b07fcd88f49 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -3,10 +3,7 @@ package gnolang import ( "fmt" "math/big" - "path/filepath" "reflect" - "runtime" - "strings" "github.com/gnolang/gno/tm2/pkg/errors" ) @@ -2978,25 +2975,6 @@ func predefineNow2(store Store, last BlockNode, d Decl, m map[Name]struct{}) (De } } -func smallStacktrace() { - pc := make([]uintptr, 10) // adjust max number of lines to print - pc = pc[:runtime.Callers(2, pc)] - frames := runtime.CallersFrames(pc) - for { - f, more := frames.Next() - - if idx := strings.LastIndexByte(f.Function, '/'); idx >= 0 { - f.Function = f.Function[idx+1:] - } - - fmt.Printf("%-25s %s\n", fmt.Sprintf("%s:%d", filepath.Base(f.File), f.Line), f.Function) - - if !more { - return - } - } -} - // If a dependent name is not yet defined, that name is // returned; this return value is used by the caller to // enforce declaration order. If a dependent type is not yet diff --git a/gnovm/tests/imports.go b/gnovm/tests/imports.go index 6e86ae12999..0db5651fbcc 100644 --- a/gnovm/tests/imports.go +++ b/gnovm/tests/imports.go @@ -471,13 +471,6 @@ func (*dummyReader) Read(b []byte) (n int, err error) { //---------------------------------------- -// NOTE: does not allocate; used for panics. -func typedString(s string) gno.TypedValue { - tv := gno.TypedValue{T: gno.StringType} - tv.V = gno.StringValue(s) - return tv -} - type TestReport struct { Name string Verbose bool From 108acc1887c4d0dc52bc85f30880bbf498a83761 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 18 Dec 2023 23:08:37 +0100 Subject: [PATCH 58/59] cleanup --- gnovm/pkg/gnolang/machine.go | 4 ++-- gnovm/pkg/gnolang/preprocess.go | 42 --------------------------------- 2 files changed, 2 insertions(+), 44 deletions(-) diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index e8f942cf40c..a22d335ad3b 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -275,7 +275,7 @@ func checkDuplicates(fset *FileSet) bool { } name = d.Name if d.IsMethod { - name = Name(derefStar(d.Recv.Type).String()) + "." + name + name = Name(destar(d.Recv.Type).String()) + "." + name } case *TypeDecl: name = d.Name @@ -305,7 +305,7 @@ func checkDuplicates(fset *FileSet) bool { return false } -func derefStar(x Expr) Expr { +func destar(x Expr) Expr { if x, ok := x.(*StarExpr); ok { return x.X } diff --git a/gnovm/pkg/gnolang/preprocess.go b/gnovm/pkg/gnolang/preprocess.go index b07fcd88f49..ee943226c85 100644 --- a/gnovm/pkg/gnolang/preprocess.go +++ b/gnovm/pkg/gnolang/preprocess.go @@ -3144,48 +3144,9 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { if un != "" { return } - - /* - pkg := packageOf(last) - - // d.Recv's type is defined; add method to DeclaredType. - if d.Recv.Name == "" || d.Recv.Name == "_" { - // create a hidden var with leading dot. - // TODO: document somewhere. - d.Recv.Name = ".recv" - } - - d.Recv = *Preprocess(store, last, &d.Recv).(*FieldTypeExpr) - d.Type = *Preprocess(store, last, &d.Type).(*FuncTypeExpr) - rft := evalStaticType(store, last, &d.Recv).(FieldType) - rt := rft.Type - ft := evalStaticType(store, last, &d.Type).(*FuncType) - ft = ft.UnboundType(rft) - var dt *DeclaredType - if pt, ok := rt.(*PointerType); ok { - dt = pt.Elem().(*DeclaredType) - } else { - dt = rt.(*DeclaredType) - } - dt.DefineMethod(&FuncValue{ - Type: ft, - IsMethod: true, - Source: d, - Name: d.Name, - Closure: nil, // set lazily. - FileName: fileNameOf(last), - PkgPath: pkg.PkgPath, - body: d.Body, - nativeBody: nil, - }) - */ } else { // define package-level function. ft := &FuncType{} - /* - d.Type = *Preprocess(store, last, &d.Type).(*FuncTypeExpr) - ft := evalStaticType(store, last, &d.Type).(*FuncType) - */ pkg := skipFile(last).(*PackageNode) // special case: if d.Name == "init", assign unique suffix. if d.Name == "init" { @@ -3197,9 +3158,6 @@ func tryPredefine(store Store, last BlockNode, d Decl) (un Name) { } // define a FuncValue w/ above type as d.Name. // fill in later during *FuncDecl:BLOCK. - // XXX how can this work upon restart? - // XXX that is, are upgrades saved/loaded? how? - // XXX doesn't matter for tests... fv := &FuncValue{ Type: ft, IsMethod: false, From 9d13896124bfc35c5c66092486f092518f2022f3 Mon Sep 17 00:00:00 2001 From: Morgan Bazalgette Date: Mon, 18 Dec 2023 23:22:51 +0100 Subject: [PATCH 59/59] remove old stdlib doc --- docs/stdlibs.md | 189 ------------------------------------------------ 1 file changed, 189 deletions(-) delete mode 100644 docs/stdlibs.md diff --git a/docs/stdlibs.md b/docs/stdlibs.md deleted file mode 100644 index 7d38c3380d5..00000000000 --- a/docs/stdlibs.md +++ /dev/null @@ -1,189 +0,0 @@ -# Standard Libraries - -Gno comes with a set of standard libraries which are included whenever you -execute Gno code. These are distinguishable from imports of packages from the -chain by not referring to a "domain" as the first part of their import path. For -instance, `import "encoding/binary"` refers to a standard library, while -`import "gno.land/p/demo/avl"` refers to an on-chain package. - -Standard libraries packages follow the same semantics as on-chain packages (ie. -they don't persist state like realms do) and come as a part of the Gno -programming language rather than with the Gno.land chain. - -Many standard libaries are near-identical copies of the equivalent Go standard -libraries; in fact, you can check the current status of implementation of each -Go standard libarary on [Go\<\>Gno compatibility](go-gno-compatibility.md). - -## Gathering documentation - -At the time being, there is no "list" of the available standard libraries -available from Gno tooling or documentation, but you can obtain a list of all -the available packages with the following commands: - -```console -$ cd gnovm/stdlibs # go to correct directory -$ find -type d -./testing -./math -./crypto -./crypto/chacha20 -./crypto/chacha20/chacha -./crypto/chacha20/rand -./crypto/sha256 -./crypto/cipher -... -``` - -All of the packages have automatic, generated documentation through the use of -`gno doc`, which has similar functionality and features to `go doc`: - -```console -$ gno doc encoding/binary -package binary // import "encoding/binary" - -Package binary implements simple translation between numbers and byte sequences -and encoding and decoding of varints. - -[...] - -var BigEndian bigEndian -var LittleEndian littleEndian -type AppendByteOrder interface{ ... } -type ByteOrder interface{ ... } -$ gno doc -u -src encoding/binary littleEndian.AppendUint16 -package binary // import "encoding/binary" - -func (littleEndian) AppendUint16(b []byte, v uint16) []byte { - return append(b, - byte(v), - byte(v>>8), - ) -} -``` - -`gno doc` will work automatically when used within the Gno repository or any -repository which has a `go.mod` dependency on `github.com/gnolang/gno`, which -can be a simple way to set up your Gno repositories to automatically support -`gno` commands (aside from `doc`, also `test`, `run`, etc.). - -Another alternative is setting your enviornment variable `GNOROOT` to point to -where you cloned the Gno repository. You can set this in your `~/.profile` file -to be automatically set up in your console: - -```sh -export GNOROOT=$HOME/gno -``` - -## Test standard libraries - -There are some additional standard library functions and packages which are -currently available only in `_test.gno` and `_filetest.gno` files. At the time -of writing, these are only some additions in the `std` package to support -changing some values in test functions. - -`gno doc` currently doesn't support reading from the test standard libraries, -though support is planned to be added. For now, you can inspect the directory -`gnovm/tests/stdlibs`. - -## Adding new standard libraries - -New standard libraries may be added by simply creating a new directory (whose -path relative to the `stdlibs` directory will be the import path used in Gno -programs). Following that, the suggested approach for adding a Go standard -libary is to copy the original files from the Go source tree, and renaming their -extensions from `.go` to `.gno`. - -> As a small aid, this bash one-liner can be useful to convert all the file -> extensions: -> -> ```sh -> for i in *.go; do mv $i "$(echo $i | sed 's/\.go$/.gno/')"; done -> ``` - -Following that, the suggested approach is to iteratively try running `gno test .`, -while fixing any errors that may come out of trying to test the package. - -Some things to keep in mind: - -- Gno doesn't support assembly functions and build tags. Some Go packages may - contain assembly versions for different architecture and a `generic.go` file - containing the architecture-independent version. The general approach is that - of removing everything architecture/os-specific except for the `generic.go` file. -- Gno doesn't support reflection at the time of writing, which means that for - now many packages which rely heavily on reflection have to be delayed or - reduced while we figure out the details on how to implement reflection. - Aside from the `reflect` package itself, this also translates to very common - packages still not available in Gno, such as `fmt` or `encoding/json`. -- In the package documentation, specify the Go version from which the library - was taken. -- All changes from the Go standard libaries must be explicitly marked, possibly - with `// XXX` comments as needed. - -If you intend to create a PR to add a new standard library, remember to update -[Go\<\>Gno compatibility](go-gno-compatibility.md) accordingly. - -## Native bindings - -Gno has support for "natively-defined functions" exclusively within the standard -libaries. These are functions which are _declared_ in Gno code, but only _defined_ -in Go. There are generally three reasons why a function should be natively -defined: - -1. It relies on inspecting the Gno Virtual Machine itself.\ - For example: `std.AssertOriginCall`, `std.CurrentRealmPath`. -2. It relies on `unsafe`, or other features which are not planned to be - available in the GnoVM.\ - For example: `math.Float64frombits`. -3. Its native Go performance significantly outperforms the Gno counterpart by - several orders of magnitude, and it is used in crucial code or hot paths in - many programs.\ - For example: `sha256.Sum256`. - -The takeaway here is that native bindings are a special feature which can be -useful to overcome pure Gno limitations, but it is not a substitute for writing -standard libaries in Gno. - -There are three components to a natively bound function in Gno: - -1. The Gno function declaration, which must be a top-level function with no body - (and no brackets).\ - For example: `crypto/sha256/sha256.gno`. -2. The Go function definition, which must be a top-level function with the same - name and signature.\ - For example: `crypto/sha256/sha256.go`. -3. When the two above are present and valid, the native binding can be created - by executing the code generator: either execute `go generate` from the - `stdlibs` directory, or run `make generate` from the `gnovm` directory.\ - This generates the `native.go` file available in the `stdlibs` directory, - which provides the binding itself to then be used by the GnoVM. - -The code generator in question is available in the `misc/genstd` directory. -There are some quirks and features that must be kept in mind when writing native -bindings, which are the following: - -- Unexported functions (for instance, `func sum256(b []byte)`) must have their - Go counterpart prefixed with `X_` in order to make the functions exported (ie. - `func X_sum256(b []byte)`). -- The Go function declaration may specify as the first argument - `m *gno.Machine`, where `gno` is an import for - `github.com/gnolang/gno/gnovm/pkg/gnolang`. This gives the function access to - the Virtual Machine state, and is used by functions like `std.AssertOriginCall()`. -- The Go function may change the type of any parameter or result to - `gno.TypedValue` (where `gno` is an import for the above import path). This - means that the `native.go` generated code will not attempt to automatically - convert the Gno value into the Go value, and can be useful for unsupported - conversions like interface values. -- A small set of named types are "linked" between their Gno version and Go - counterpart. For instance, `std.Address` in Gno is - `(".../tm2/pkg/crypto").Bech32Address` in Go. A list of these can be found in - `misc/genstd/mapping.go`. -- Not all type literals are currently supported when converting from their Gno - version to their Go counterpart. Notable omissions at the time of writing - include struct and map literals. If you intend to use these, modify the code - generator to support them. -- The code generator does not inspect any imported packages from the Go native code - to determine the default package identifier (ie. the `package` clause). - Ie. if a package is in `foo/bar`, but declares `package xyz`, when importing - foo/bar the generator will assume the name to be `bar` instead of `xyz`. - You can add an identifier to the import to fix this and use the identifier - you want/need, ie.: `import gno "github.com/gnolang/gno/gnovm/pkg/gnolang"`.