diff --git a/examples/gno.land/r/x/benchmark/storage/boards.gno b/examples/gno.land/r/x/benchmark/storage/boards.gno new file mode 100644 index 00000000000..adb3d2d709c --- /dev/null +++ b/examples/gno.land/r/x/benchmark/storage/boards.gno @@ -0,0 +1,97 @@ +package storage + +import ( + "strconv" + + "gno.land/p/demo/avl" +) + +var boards avl.Tree + +type Board interface { + AddPost(title, content string) + GetPost(id int) (Post, bool) + Size() int +} + +// posts are persisted in an avl tree +type TreeBoard struct { + id int + posts *avl.Tree +} + +func (b *TreeBoard) AddPost(title, content string) { + n := b.posts.Size() + p := Post{n, title, content} + b.posts.Set(strconv.Itoa(n), p) +} + +func (b *TreeBoard) GetPost(id int) (Post, bool) { + p, ok := b.posts.Get(strconv.Itoa(id)) + if ok { + return p.(Post), ok + } else { + return Post{}, ok + } +} + +func (b *TreeBoard) Size() int { + return b.posts.Size() +} + +// posts are persisted in a map +type MapBoard struct { + id int + posts map[int]Post +} + +func (b *MapBoard) AddPost(title, content string) { + n := len(b.posts) + p := Post{n, title, content} + b.posts[n] = p +} + +func (b *MapBoard) GetPost(id int) (Post, bool) { + p, ok := b.posts[id] + if ok { + return p, ok + } else { + return Post{}, ok + } +} + +func (b *MapBoard) Size() int { + return len(b.posts) +} + +// posts are persisted in a slice +type SliceBoard struct { + id int + posts []Post +} + +func (b *SliceBoard) AddPost(title, content string) { + n := len(b.posts) + p := Post{n, title, content} + b.posts = append(b.posts, p) +} + +func (b *SliceBoard) GetPost(id int) (Post, bool) { + if id < len(b.posts) { + p := b.posts[id] + + return p, true + } else { + return Post{}, false + } +} + +func (b *SliceBoard) Size() int { + return len(b.posts) +} + +type Post struct { + id int + title string + content string +} diff --git a/examples/gno.land/r/x/benchmark/storage/forum.gno b/examples/gno.land/r/x/benchmark/storage/forum.gno new file mode 100644 index 00000000000..8f1b3734de6 --- /dev/null +++ b/examples/gno.land/r/x/benchmark/storage/forum.gno @@ -0,0 +1,64 @@ +package storage + +import ( + "strconv" + + "gno.land/p/demo/avl" +) + +func init() { + // we write to three common data structure for persistence + // avl.Tree, map and slice. + posts0 := avl.NewTree() + b0 := &TreeBoard{0, posts0} + boards.Set(strconv.Itoa(0), b0) + + posts1 := make(map[int]Post) + b1 := &MapBoard{1, posts1} + boards.Set(strconv.Itoa(1), b1) + + posts2 := []Post{} + b2 := &SliceBoard{2, posts2} + boards.Set(strconv.Itoa(2), b2) +} + +// post to all boards. +func AddPost(title, content string) { + for i := 0; i < boards.Size(); i++ { + boardId := strconv.Itoa(i) + b, ok := boards.Get(boardId) + if ok { + b.(Board).AddPost(title, content) + } + } +} + +func GetPost(boardId, postId int) string { + b, ok := boards.Get(strconv.Itoa(boardId)) + var res string + + if ok { + p, ok := b.(Board).GetPost(postId) + if ok { + res = p.title + "," + p.content + } + } + return res +} + +func GetPostSize(boardId int) int { + b, ok := boards.Get(strconv.Itoa(boardId)) + var res int + + if ok { + res = b.(Board).Size() + } else { + res = -1 + } + + return res +} + +func GetBoardSize() int { + return boards.Size() +} diff --git a/examples/gno.land/r/x/benchmark/storage/gno.mod b/examples/gno.land/r/x/benchmark/storage/gno.mod new file mode 100644 index 00000000000..ae9f2a3d864 --- /dev/null +++ b/examples/gno.land/r/x/benchmark/storage/gno.mod @@ -0,0 +1,3 @@ +module gno.land/r/x/benchmark/storage + +require gno.land/p/demo/avl v0.0.0-latest diff --git a/gnovm/pkg/benchops/Makefile b/gnovm/pkg/benchops/Makefile new file mode 100644 index 00000000000..59bb9efd119 --- /dev/null +++ b/gnovm/pkg/benchops/Makefile @@ -0,0 +1,28 @@ +.PHONY: opcode storage build_opcode build_storage + +# Build target +build_opcode: + go build -tags "benchmarkingops" -o build/gnobench ./cmd + +# Build target +build_storage: + go build -tags "benchmarkingstorage" -o build/gnobench ./cmd + + +# Run target +opcode: build_opcode + ./build/gnobench + +# Run target +storage: build_storage + ./build/gnobench + +# command to run dependency utilities, like goimports. +rundep=go run -modfile ../misc/devdeps/go.mod + +# flags for `make fmt`. -w will write the result to the destination files. +GOFMT_FLAGS ?= -w + +.PHONY: fmt +fmt: + $(rundep) mvdan.cc/gofumpt $(GOFMT_FLAGS) . diff --git a/gnovm/pkg/benchops/README.md b/gnovm/pkg/benchops/README.md new file mode 100644 index 00000000000..d2eda4ce99d --- /dev/null +++ b/gnovm/pkg/benchops/README.md @@ -0,0 +1,90 @@ +# `gnobench` the time consumed for GnoVM OpCode execution and store access + +`gnobench` benchmarks the time consumed for each VM CPU OpCode and persistent access to the store, including marshalling and unmarshalling of realm objects. + +## Usage + +### Simple mode + +The benchmark only involves the GnoVM and the persistent store. It benchmarks the bare minimum components, and the results are isolated from other components. We use standardize gno contract to perform the benchmarking. + +This mode is the best for benchmarking each major release and/or changes in GnoVM. + + make opcode + make storage + +### Production mode + +It benchmarks the node in the production environment with minimum overhead. +We can not only benchmark with standardize the contract but also capture the live usage in production environment. +It gives us a complete picture of the node perform. + + + 1. Build the production node with benchmarking flags: + + `go build -tags "benchmarkingstorage benchmarkingops" gno.land/cmd/gnoland` + + 2. Run the node in the production environment. It will dump benchmark data to a benchmarks.bin file. + + 3. call the realm contracts at `gno.land/r/x/benchmark/opcodes` and `gno.land/r/x/benchmark/storage` + + 4. Stop the server after the benchmarking session is complete. + + 5. Run the following command to convert the binary dump: + + `gnobench -bin path_to_benchmarks.bin` + + it converts the binary dump to results.csv and results_stats.csv. + + +## Results + +The benchmarking results are stored in two files: + 1. The raw results are saved in results.csv. + + | Operation | Elapsed Time | Disk IO Bytes | + |-----------------|--------------|---------------| + | OpEval | 40333 | 0 | + | OpPopBlock | 208 | 0 | + | OpHalt | 167 | 0 | + | OpEval | 500 | 0 | + | OpInterfaceType | 458 | 0 | + | OpPopBlock | 166 | 0 | + | OpHalt | 125 | 0 | + | OpInterfaceType | 21125 | 0 | + | OpEval | 541 | 0 | + | OpEval | 209 | 0 | + | OpInterfaceType | 334 | 0 | + + + + 2. The averages and standard deviations are summarized in results_stats.csv. + + | Operation | Avg Time | Avg Size | Time Std Dev | Count | +|----------------|----------|----------|--------------|-------| +| OpAdd | 101 | 0 | 45 | 300 | +| OpAddAssign | 309 | 0 | 1620 | 100 | +| OpArrayLit | 242 | 0 | 170 | 700 | +| OpArrayType | 144 | 0 | 100 | 714 | +| OpAssign | 136 | 0 | 95 | 2900 | +| OpBand | 92 | 0 | 30 | 100 | +| OpBandAssign | 127 | 0 | 62 | 100 | +| OpBandn | 97 | 0 | 54 | 100 | +| OpBandnAssign | 125 | 0 | 113 | 100 | +| OpBinary1 | 128 | 0 | 767 | 502 | +| OpBody | 127 | 0 | 145 | 13700 | + +## Design consideration + +### Minimum Overhead and Footprint + +- Constant build flags enable benchmarking. +- Encode operations and measurements in binary. +- Dump to a local file in binary. +- No logging, printout, or network access involved. + +### Accurate + +- Pause the timer for storage access while performing VM opcode benchmarking. +- Measure each OpCode execution in nanoseconds. +- Store access includes the duration for Amino marshalling and unmarshalling. diff --git a/gnovm/pkg/benchops/bench.go b/gnovm/pkg/benchops/bench.go new file mode 100644 index 00000000000..d802ba1f864 --- /dev/null +++ b/gnovm/pkg/benchops/bench.go @@ -0,0 +1,117 @@ +package benchmarking + +import ( + "time" +) + +const ( + invalidCode = byte(0x00) +) + +var measure bench + +type bench struct { + opCounts [256]int64 + opAccumDur [256]time.Duration + opStartTime [256]time.Time + isOpCodeStarted bool + curOpCode byte + timeZero time.Time + + storeCounts [256]int64 + storeAccumDur [256]time.Duration + storeAccumSize [256]int64 + storeStartTime [256]time.Time + curStoreCode byte +} + +func InitMeasure() { + measure = bench{ + // this will be called to reset each benchmarking + isOpCodeStarted: false, + curOpCode: invalidCode, + curStoreCode: invalidCode, + } +} + +func StartOpCode(code byte) { + if code == invalidCode { + panic("the OpCode is invalid") + } + if measure.opStartTime[code] != measure.timeZero { + panic("Can not start a non-stopped timer") + } + measure.opStartTime[code] = time.Now() + measure.opCounts[code]++ + + measure.isOpCodeStarted = true + measure.curOpCode = code +} + +// StopMeasurement ends the current measurement and resumes the previous one +// if one exists. It accepts the number of bytes that were read/written to/from +// the store. This value is zero if the operation is not a read or write. +func StopOpCode() { + code := measure.curOpCode + if measure.opStartTime[code] == measure.timeZero { + panic("Can not stop a stopped timer") + } + measure.opAccumDur[code] += time.Since(measure.opStartTime[code]) + measure.opStartTime[code] = measure.timeZero // stop the timer +} + +// Pause current opcode measurement +func PauseOpCode() { + if measure.isOpCodeStarted == false { + return + } + if measure.curOpCode == invalidCode { + panic("Can not Pause timer of an invalid OpCode") + } + code := measure.curOpCode + if measure.opStartTime[code] == measure.timeZero { + panic("Should not pause a stopped timer") + } + measure.opAccumDur[code] += time.Since(measure.opStartTime[code]) + measure.opStartTime[code] = measure.timeZero +} + +// Resume resumes current measurement +func ResumeOpCode() { + if measure.isOpCodeStarted == false { + return + } + if measure.curOpCode == invalidCode { + panic("Can not resume timer of an invalid OpCode") + } + + code := measure.curOpCode + + if measure.opStartTime[code] != measure.timeZero { + panic("Should not resume a running timer") + } + measure.opStartTime[code] = time.Now() +} + +func StartStore(code byte) { + if measure.storeStartTime[code] != measure.timeZero { + panic("Can not start a non-stopped timer") + } + measure.storeStartTime[code] = time.Now() + measure.storeCounts[code]++ + measure.curStoreCode = code +} + +// assume there is no recursive call for store. +func StopStore(size int) { + code := measure.curStoreCode + + if measure.storeStartTime[code] == measure.timeZero { + panic("Can not stop a stopped timer") + } + + measure.storeAccumDur[code] += time.Since(measure.storeStartTime[code]) + measure.storeStartTime[code] = measure.timeZero // stop the timer + measure.storeAccumSize[code] += int64(size) + measure.curStoreCode = invalidCode +} diff --git a/gnovm/pkg/benchops/cmd/main.go b/gnovm/pkg/benchops/cmd/main.go new file mode 100644 index 00000000000..2d2cdc0262a --- /dev/null +++ b/gnovm/pkg/benchops/cmd/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "flag" + "log" + "os" + "path/filepath" + + bm "github.com/gnolang/gno/gnovm/pkg/benchops" +) + +var ( + outFlag = flag.String("out", "results.csv", "the out put file") + benchFlag = flag.String("bench", "./gno", "the path to the benchmark contract") + binFlag = flag.String("bin", "", "interpret the existing benchmarking file.") +) + +// We dump the benchmark in bytes for speed and minimal overhead. +const tmpFile = "benchmark.bin" + +func main() { + flag.Parse() + if *binFlag != "" { + binFile, err := filepath.Abs(*binFlag) + if err != nil { + log.Fatal("unable to get absolute path for the file", err) + } + stats(binFile) + return + } + bm.Init(tmpFile) + bstore := benchmarkDiskStore() + + dir, err := filepath.Abs(*benchFlag) + if err != nil { + log.Fatal("unable to get absolute path for storage directory.", err) + } + + // load stdlibs + loadStdlibs(bstore) + + if bm.OpsEnabled { + benchmarkOpCodes(bstore, dir) + } + if bm.StorageEnabled { + benchmarkStorage(bstore, dir) + } + bm.Finish() + stats(tmpFile) + err = os.Remove(tmpFile) + if err != nil { + log.Printf("Error removing tmp file: %v", err) + } +} diff --git a/gnovm/pkg/benchops/cmd/opcode_test.go b/gnovm/pkg/benchops/cmd/opcode_test.go new file mode 100644 index 00000000000..526542b857f --- /dev/null +++ b/gnovm/pkg/benchops/cmd/opcode_test.go @@ -0,0 +1,67 @@ +package main + +import ( + "testing" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestLoadOpcodesPackage(t *testing.T) { + dir := "../gno/opcodes" + bstore := benchmarkDiskStore() + pv := addPackage(bstore, dir, opcodesPkgPath) + pb := pv.GetBlock(bstore) + + assert := assert.New(t) + require := require.New(t) + + declTypes := []string{ + "foo", + "dog", + "foofighter", + } + for i := 0; i < len(declTypes); i++ { + tv := pb.Values[i] + v, ok := tv.V.(gno.TypeValue) + require.True(ok, "it should be a TypeValue") + dtv, ok2 := v.Type.(*gno.DeclaredType) + tn := declTypes[i] + + require.True(ok2, "it should be a DeclaredType") + assert.Equal(tn, string(dtv.Name), "the declared type name should be "+tn) + } + + // These are the functions used to benchmark the OpCode in the benchmarking contract. + // We call each to benchmark a group of OpCodes. + funcValues := []string{ + "ExprOps", + "OpDecl", + "OpEvalInt", + "OpEvalFloat", + "StmtOps", + "ControlOps", + "OpDefer", + "OpUnary", + "OpBinary", + "OpLor", + "OpLand", + "OpPanic", + "OpTypeSwitch", + "OpCallDeferNativeBody", + "OpRange", + "OpForLoop", + "OpTypes", + "OpOpValues", + } + + for i := 3; i < 3+len(funcValues); i++ { + j := i - 3 + tv := pb.Values[i] + fv, ok := tv.V.(*gno.FuncValue) + require.True(ok, "it should be a FuncValue") + fn := funcValues[j] + assert.Equal(fn, string(fv.Name), "the declared type name should be "+fn) + } +} diff --git a/gnovm/pkg/benchops/cmd/run.go b/gnovm/pkg/benchops/cmd/run.go new file mode 100644 index 00000000000..55e1c04eef9 --- /dev/null +++ b/gnovm/pkg/benchops/cmd/run.go @@ -0,0 +1,138 @@ +package main + +import ( + "os" + "path/filepath" + "strings" + + "github.com/gnolang/gno/gnovm/pkg/gnoenv" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/gnovm/stdlibs" + osm "github.com/gnolang/gno/tm2/pkg/os" +) + +const ( + opcodesPkgPath = "gno.land/r/x/benchmark/opcodes" + rounds = 1000 +) + +func benchmarkOpCodes(bstore gno.Store, dir string) { + opcodesPkgDir := filepath.Join(dir, "opcodes") + + pv := addPackage(bstore, opcodesPkgDir, opcodesPkgPath) + for i := 0; i < rounds; i++ { + callOpsBench(bstore, pv) + } +} + +func callOpsBench(bstore gno.Store, pv *gno.PackageValue) { + // start + pb := pv.GetBlock(bstore) + for _, tv := range pb.Values { + if fv, ok := tv.V.(*gno.FuncValue); ok { + cx := gno.Call(fv.Name) + callFunc(bstore, pv, cx) + } + } +} + +const storagePkgPath = "gno.land/r/x/benchmark/storage" + +func benchmarkStorage(bstore gno.Store, dir string) { + avlPkgDir := filepath.Join(dir, "avl") + addPackage(bstore, avlPkgDir, "gno.land/p/demo/avl") + + storagePkgDir := filepath.Join(dir, "storage") + pv := addPackage(bstore, storagePkgDir, storagePkgPath) + benchStoreSet(bstore, pv) + benchStoreGet(bstore, pv) +} + +func benchStoreSet(bstore gno.Store, pv *gno.PackageValue) { + title := "1KB content" + content := strings.Repeat("a", 1024) + + // in forum.gno: func AddPost(title, content string) + // one AddPost will be added to three different boards in the forum.gno contract + + for i := 0; i < rounds; i++ { + cx := gno.Call("AddPost", gno.Str(title), gno.Str(content)) + callFunc(bstore, pv, cx) + } +} + +func benchStoreGet(bstore gno.Store, pv *gno.PackageValue) { + // in forum.gno: func GetPost(boardId, postId int) string in forum.gno + // there are three different boards on the benchmarking forum contract + for i := 0; i < 3; i++ { + for j := 0; j < rounds; j++ { + cx := gno.Call("GetPost", gno.X(i), gno.X(j)) + callFunc(bstore, pv, cx) + } + } +} + +func callFunc(bstore gno.Store, pv *gno.PackageValue, cx gno.Expr) []gno.TypedValue { + m := gno.NewMachineWithOptions( + gno.MachineOptions{ + PkgPath: pv.PkgPath, + Output: os.Stdout, // XXX + Store: bstore, + }) + + defer m.Release() + + m.SetActivePackage(pv) + return m.Eval(cx) +} + +// addPacakge + +func addPackage(bstore gno.Store, dir string, pkgPath string) *gno.PackageValue { + // load benchmark contract + m := gno.NewMachineWithOptions( + gno.MachineOptions{ + PkgPath: "", + Output: os.Stdout, + Store: bstore, + }) + defer m.Release() + + memPkg := gno.ReadMemPackage(dir, pkgPath) + + // pare the file, create pn, pv and save the values in m.store + _, pv := m.RunMemPackage(memPkg, true) + + return pv +} + +// load stdlibs +func loadStdlibs(bstore gno.Store) { + // copied from vm/builtin.go + getPackage := func(pkgPath string, newStore gno.Store) (pn *gno.PackageNode, pv *gno.PackageValue) { + stdlibDir := filepath.Join(gnoenv.RootDir(), "gnovm", "stdlibs") + stdlibPath := filepath.Join(stdlibDir, pkgPath) + if !osm.DirExists(stdlibPath) { + // does not exist. + return nil, nil + } + + memPkg := gno.ReadMemPackage(stdlibPath, pkgPath) + if memPkg.IsEmpty() { + // no gno files are present, skip this package + return nil, nil + } + + m2 := gno.NewMachineWithOptions(gno.MachineOptions{ + PkgPath: "gno.land/r/stdlibs/" + pkgPath, + // PkgPath: pkgPath, + Output: os.Stdout, + Store: newStore, + }) + defer m2.Release() + return m2.RunMemPackage(memPkg, true) + } + + bstore.SetPackageGetter(getPackage) + bstore.SetNativeStore(stdlibs.NativeStore) +} diff --git a/gnovm/pkg/benchops/cmd/stats.go b/gnovm/pkg/benchops/cmd/stats.go new file mode 100644 index 00000000000..071cabbc25a --- /dev/null +++ b/gnovm/pkg/benchops/cmd/stats.go @@ -0,0 +1,188 @@ +package main + +import ( + "encoding/binary" + "fmt" + "math" + "os" + "sort" + "strings" + "sync" + + bm "github.com/gnolang/gno/gnovm/pkg/benchops" + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" +) + +type codeStats struct { + codeName string + avgTime int64 + avgSize int64 + timeStdDev int64 + count int +} + +type codeRecord struct { + codeName string + elapsed uint32 + size uint32 +} + +// It reads binary record, calcuate and output the statistics of operations +func stats(binFile string) { + in, err := os.Open(binFile) + if err != nil { + panic("could not create benchmark file: " + err.Error()) + } + defer in.Close() + + inputCh := make(chan []byte, 10000) + outputCh := make(chan codeRecord, 10000) + wg := sync.WaitGroup{} + numWorkers := 2 + wg.Add(numWorkers) + doneCh := make(chan struct{}) + for i := 0; i < numWorkers; i++ { + go func() { + for { + record, ok := <-inputCh + if !ok { + break + } + opName := gno.Op(record[0]).String() + if record[1] != 0 { + opName = bm.StoreCodeString(record[1]) + } + + elapsedTime := binary.LittleEndian.Uint32(record[2:]) + size := binary.LittleEndian.Uint32(record[6:]) + outputCh <- codeRecord{opName, elapsedTime, size} + } + wg.Done() + }() + } + + crs := []codeRecord{} + // out put + go func() { + out, err := os.Create(*outFlag) + if err != nil { + panic("could not create readable output file: " + err.Error()) + } + defer out.Close() + fmt.Fprintln(out, "op,elapsedTime,diskIOBytes") + + for { + output, ok := <-outputCh + if !ok { + break + } + csv := output.codeName + "," + fmt.Sprint(output.elapsed) + "," + fmt.Sprint(output.size) + fmt.Fprintln(out, csv) + crs = append(crs, output) + } + + out.Close() + doneCh <- struct{}{} + }() + + recordSize := bm.RecordSize + bufSize := recordSize * 100000 + buf := make([]byte, bufSize) + + for { + nbytes, err := in.Read(buf) + + if err != nil && nbytes == 0 { + break + } + n := nbytes / recordSize + + for j := 0; j < n; j++ { + inputCh <- buf[j*recordSize : (j+1)*recordSize] + } + } + + close(inputCh) + wg.Wait() + close(outputCh) + <-doneCh + close(doneCh) + + calculateStats(crs) + fmt.Println("done") +} + +func calculateStats(crs []codeRecord) { + filename := *outFlag + out, err := os.Create(addSuffix(filename)) + if err != nil { + panic("could not create readable output file: " + err.Error()) + } + defer out.Close() + fmt.Fprintln(out, "op,avg_time,avg_size,time_stddev,count") + + m := make(map[string][]codeRecord) + for _, v := range crs { + crs, ok := m[v.codeName] + if ok { + crs = append(crs, v) + m[v.codeName] = crs + } else { + m[v.codeName] = []codeRecord{v} + } + } + + keys := make([]string, 0, 100) + + for k := range m { + keys = append(keys, k) + } + sort.Slice(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, k := range keys { + cs := calculate(k, m[k]) + csv := cs.codeName + "," + fmt.Sprint(cs.avgTime) + "," + fmt.Sprint(cs.avgSize) + "," + fmt.Sprint(cs.timeStdDev) + "," + fmt.Sprint(cs.count) + fmt.Fprintln(out, csv) + } + + fmt.Println("## Benchmark results saved in : ", filename) + fmt.Println("## Benchmark result stats saved in : ", out.Name()) +} + +func addSuffix(filename string) string { + // Find the position of the last dot + dotPos := strings.LastIndex(filename, ".") + if dotPos == -1 { + // No dot found, return the original filename with '_status' appended + return filename + "_stats" + } + // Insert '_status' before the last suffix + return filename[:dotPos] + "_stats" + filename[dotPos:] +} + +// calcuate the average and standard deviation in time of a code name + +func calculate(codeName string, crs []codeRecord) codeStats { + // Calculate average + var sumTime int64 + var sumSize int64 + for _, cr := range crs { + t := cr.elapsed + s := cr.size + sumTime += int64(t) + sumSize += int64(s) + } + avgTime := float64(sumTime) / float64(len(crs)) + avgSize := float64(sumSize) / float64(len(crs)) + + // Calculate standard deviation of duration in time + var varianceSum float64 + for _, cr := range crs { + varianceSum += math.Pow(float64(cr.elapsed)-avgTime, 2) + } + variance := varianceSum / float64(len(crs)) + stdDev := math.Sqrt(variance) + return codeStats{codeName, int64(avgTime), int64(avgSize), int64(stdDev), len(crs)} +} diff --git a/gnovm/pkg/benchops/cmd/storage_test.go b/gnovm/pkg/benchops/cmd/storage_test.go new file mode 100644 index 00000000000..8341cdd353d --- /dev/null +++ b/gnovm/pkg/benchops/cmd/storage_test.go @@ -0,0 +1,37 @@ +package main + +import ( + "path/filepath" + "strings" + "testing" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/stretchr/testify/assert" +) + +func TestBenchStoreSet(t *testing.T) { + assert := assert.New(t) + + dir := "../gno" + bstore := benchmarkDiskStore() + + // load stdlibs + loadStdlibs(bstore) + avlPkgDir := filepath.Join(dir, "avl") + addPackage(bstore, avlPkgDir, "gno.land/p/demo/avl") + + storagePkgDir := filepath.Join(dir, "storage") + pv := addPackage(bstore, storagePkgDir, storagePkgPath) + benchStoreSet(bstore, pv) + // verify the post content from all three boards + for i := 0; i < 3; i++ { + for j := 0; j < rounds; j++ { + cx := gno.Call("GetPost", gno.X(0), gno.X(0)) + res := callFunc(bstore, pv, cx) + parts := strings.Split(res[0].V.String(), ",") + p := strings.Trim(parts[1], `\"`) + expected := strings.Repeat("a", 1024) + assert.Equal(p, expected, "it should be 1 KB of character a") + } + } +} diff --git a/gnovm/pkg/benchops/cmd/store.go b/gnovm/pkg/benchops/cmd/store.go new file mode 100644 index 00000000000..09c8f9844ec --- /dev/null +++ b/gnovm/pkg/benchops/cmd/store.go @@ -0,0 +1,56 @@ +package main + +import ( + "log" + "os" + "path/filepath" + + gno "github.com/gnolang/gno/gnovm/pkg/gnolang" + "github.com/gnolang/gno/tm2/pkg/bft/config" + dbm "github.com/gnolang/gno/tm2/pkg/db" + _ "github.com/gnolang/gno/tm2/pkg/db/goleveldb" + "github.com/gnolang/gno/tm2/pkg/store" + "github.com/gnolang/gno/tm2/pkg/store/dbadapter" + "github.com/gnolang/gno/tm2/pkg/store/iavl" +) + +const maxAllocTx = 500 * 1000 * 1000 + +func benchmarkDiskStore() gno.Store { + storeDir, err := filepath.Abs("bench_store") + if err != nil { + log.Fatal("unable to get absolute path for storage directory.", err) + } + err = os.RemoveAll(storeDir) + if err != nil { + if os.IsNotExist(err) { + log.Fatal("Directory does not exist.", err) + } else { + log.Fatal("can not clean up storage directory", err) + } + } + + db, err := dbm.NewDB("gnolang", dbm.GoLevelDBBackend, filepath.Join(storeDir, config.DefaultDBDir)) + if err != nil { + log.Fatalf("error initializing database %q using path %q: %s\n", dbm.GoLevelDBBackend, storeDir, err) + } + + return newStore(db) +} + +func newStore(db dbm.DB) gno.Store { + baseKey := store.NewStoreKey("baseKey") + iavlKey := store.NewStoreKey("iavlKey") + + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(baseKey, dbadapter.StoreConstructor, db) + ms.MountStoreWithDB(iavlKey, iavl.StoreConstructor, db) + ms.LoadLatestVersion() + + alloc := gno.NewAllocator(maxAllocTx) + baseSDKStore := ms.GetStore(baseKey) + iavlSDKStore := ms.GetStore(iavlKey) + bstore := gno.NewStore(alloc, baseSDKStore, iavlSDKStore) + + return bstore +} diff --git a/gnovm/pkg/benchops/exporter.go b/gnovm/pkg/benchops/exporter.go new file mode 100644 index 00000000000..55cfcf32d88 --- /dev/null +++ b/gnovm/pkg/benchops/exporter.go @@ -0,0 +1,109 @@ +package benchmarking + +import ( + "encoding/binary" + "log" + "math" + "os" + "time" +) + +// the byte size of a exported record +const RecordSize int = 10 + +var fileWriter *exporter + +func initExporter(fileName string) { + file, err := os.Create(fileName) + if err != nil { + panic("could not create benchmark file: " + err.Error()) + } + + fileWriter = &exporter{ + file: file, + } +} + +type exporter struct { + file *os.File +} + +// export code, duration, size in a 10 bytes record +// byte 1: OpCode +// byte 2: StoreCode +// byte 3-6: Duration +// byte 7-10: Size +func (e *exporter) export(code Code, elapsedTime time.Duration, size int) { + // the MaxUint32 is 4294967295. It represents 4.29 seconds in duration or 4G bytes. + // It panics not only for overflow protection, but also for abnormal measurements. + if elapsedTime > math.MaxUint32 { + log.Fatalf("elapsedTime %d out of uint32 range", elapsedTime) + } + if size > math.MaxUint32 { + log.Fatalf("size %d out of uint32 range", size) + } + + buf := []byte{code[0], code[1], 0, 0, 0, 0, 0, 0, 0, 0} + binary.LittleEndian.PutUint32(buf[2:], uint32(elapsedTime)) + binary.LittleEndian.PutUint32(buf[6:], uint32(size)) + _, err := e.file.Write(buf) + if err != nil { + panic("could not write to benchmark file: " + err.Error()) + } +} + +func (e *exporter) close() { + e.file.Sync() + e.file.Close() +} + +func FinishStore() { + for i := 0; i < 256; i++ { + count := measure.storeCounts[i] + + if count == 0 { + continue + } + // check unstopped timer + if measure.storeStartTime[i] != measure.timeZero { + panic("timer should have stopped before FinishRun") + } + + code := [2]byte{0x00, byte(i)} + + fileWriter.export( + code, + measure.storeAccumDur[i]/time.Duration(count), + int(measure.storeAccumSize[i]/count), + ) + } +} + +func FinishRun() { + for i := 0; i < 256; i++ { + if measure.opCounts[i] == 0 { + continue + } + // check unstopped timer + if measure.opStartTime[i] != measure.timeZero { + panic("timer should have stopped before FinishRun") + } + + code := [2]byte{byte(i), 0x00} + fileWriter.export(code, measure.opAccumDur[i]/time.Duration(measure.opCounts[i]), 0) + } + ResetRun() +} + +// It reset each machine Runs +func ResetRun() { + measure.opCounts = [256]int64{} + measure.opAccumDur = [256]time.Duration{} + measure.opStartTime = [256]time.Time{} + measure.curOpCode = invalidCode + measure.isOpCodeStarted = false +} + +func Finish() { + fileWriter.close() +} diff --git a/gnovm/pkg/benchops/gno.mod b/gnovm/pkg/benchops/gno.mod new file mode 100644 index 00000000000..033544d051f --- /dev/null +++ b/gnovm/pkg/benchops/gno.mod @@ -0,0 +1 @@ +module gno.land/r/x/benchmark diff --git a/gnovm/pkg/benchops/gno/avl/gno.mod b/gnovm/pkg/benchops/gno/avl/gno.mod new file mode 100644 index 00000000000..a6a2a1362e3 --- /dev/null +++ b/gnovm/pkg/benchops/gno/avl/gno.mod @@ -0,0 +1 @@ +module gno.land/p/demo/avl diff --git a/gnovm/pkg/benchops/gno/avl/node.gno b/gnovm/pkg/benchops/gno/avl/node.gno new file mode 100644 index 00000000000..7308e163768 --- /dev/null +++ b/gnovm/pkg/benchops/gno/avl/node.gno @@ -0,0 +1,487 @@ +package avl + +//---------------------------------------- +// Node + +// Node represents a node in an AVL tree. +type Node struct { + key string // key is the unique identifier for the node. + value interface{} // value is the data stored in the node. + height int8 // height is the height of the node in the tree. + size int // size is the number of nodes in the subtree rooted at this node. + leftNode *Node // leftNode is the left child of the node. + rightNode *Node // rightNode is the right child of the node. +} + +// NewNode creates a new node with the given key and value. +func NewNode(key string, value interface{}) *Node { + return &Node{ + key: key, + value: value, + height: 0, + size: 1, + } +} + +// Size returns the size of the subtree rooted at the node. +func (node *Node) Size() int { + if node == nil { + return 0 + } + return node.size +} + +// IsLeaf checks if the node is a leaf node (has no children). +func (node *Node) IsLeaf() bool { + return node.height == 0 +} + +// Key returns the key of the node. +func (node *Node) Key() string { + return node.key +} + +// Value returns the value of the node. +func (node *Node) Value() interface{} { + return node.value +} + +// _copy creates a copy of the node (excluding value). +func (node *Node) _copy() *Node { + if node.height == 0 { + panic("Why are you copying a value node?") + } + return &Node{ + key: node.key, + height: node.height, + size: node.size, + leftNode: node.leftNode, + rightNode: node.rightNode, + } +} + +// Has checks if a node with the given key exists in the subtree rooted at the node. +func (node *Node) Has(key string) (has bool) { + if node == nil { + return false + } + if node.key == key { + return true + } + if node.height == 0 { + return false + } + if key < node.key { + return node.getLeftNode().Has(key) + } + return node.getRightNode().Has(key) +} + +// Get searches for a node with the given key in the subtree rooted at the node +// and returns its index, value, and whether it exists. +func (node *Node) Get(key string) (index int, value interface{}, exists bool) { + if node == nil { + return 0, nil, false + } + + if node.height == 0 { + if node.key == key { + return 0, node.value, true + } + if node.key < key { + return 1, nil, false + } + return 0, nil, false + } + + if key < node.key { + return node.getLeftNode().Get(key) + } + + rightNode := node.getRightNode() + index, value, exists = rightNode.Get(key) + index += node.size - rightNode.size + return index, value, exists +} + +// GetByIndex retrieves the key-value pair of the node at the given index +// in the subtree rooted at the node. +func (node *Node) GetByIndex(index int) (key string, value interface{}) { + if node.height == 0 { + if index == 0 { + return node.key, node.value + } + panic("GetByIndex asked for invalid index") + } + // TODO: could improve this by storing the sizes + leftNode := node.getLeftNode() + if index < leftNode.size { + return leftNode.GetByIndex(index) + } + return node.getRightNode().GetByIndex(index - leftNode.size) +} + +// Set inserts a new node with the given key-value pair into the subtree rooted at the node, +// and returns the new root of the subtree and whether an existing node was updated. +// +// XXX consider a better way to do this... perhaps split Node from Node. +func (node *Node) Set(key string, value interface{}) (newSelf *Node, updated bool) { + if node == nil { + return NewNode(key, value), false + } + + if node.height == 0 { + return node.setLeaf(key, value) + } + + node = node._copy() + if key < node.key { + node.leftNode, updated = node.getLeftNode().Set(key, value) + } else { + node.rightNode, updated = node.getRightNode().Set(key, value) + } + + if updated { + return node, updated + } + + node.calcHeightAndSize() + return node.balance(), updated +} + +// setLeaf inserts a new leaf node with the given key-value pair into the subtree rooted at the node, +// and returns the new root of the subtree and whether an existing node was updated. +func (node *Node) setLeaf(key string, value interface{}) (newSelf *Node, updated bool) { + if key == node.key { + return NewNode(key, value), true + } + + if key < node.key { + return &Node{ + key: node.key, + height: 1, + size: 2, + leftNode: NewNode(key, value), + rightNode: node, + }, false + } + + return &Node{ + key: key, + height: 1, + size: 2, + leftNode: node, + rightNode: NewNode(key, value), + }, false +} + +// Remove deletes the node with the given key from the subtree rooted at the node. +// returns the new root of the subtree, the new leftmost leaf key (if changed), +// the removed value and the removal was successful. +func (node *Node) Remove(key string) ( + newNode *Node, newKey string, value interface{}, removed bool, +) { + if node == nil { + return nil, "", nil, false + } + if node.height == 0 { + if key == node.key { + return nil, "", node.value, true + } + return node, "", nil, false + } + if key < node.key { + var newLeftNode *Node + newLeftNode, newKey, value, removed = node.getLeftNode().Remove(key) + if !removed { + return node, "", value, false + } + if newLeftNode == nil { // left node held value, was removed + return node.rightNode, node.key, value, true + } + node = node._copy() + node.leftNode = newLeftNode + node.calcHeightAndSize() + node = node.balance() + return node, newKey, value, true + } + + var newRightNode *Node + newRightNode, newKey, value, removed = node.getRightNode().Remove(key) + if !removed { + return node, "", value, false + } + if newRightNode == nil { // right node held value, was removed + return node.leftNode, "", value, true + } + node = node._copy() + node.rightNode = newRightNode + if newKey != "" { + node.key = newKey + } + node.calcHeightAndSize() + node = node.balance() + return node, "", value, true +} + +// getLeftNode returns the left child of the node. +func (node *Node) getLeftNode() *Node { + return node.leftNode +} + +// getRightNode returns the right child of the node. +func (node *Node) getRightNode() *Node { + return node.rightNode +} + +// rotateRight performs a right rotation on the node and returns the new root. +// NOTE: overwrites node +// TODO: optimize balance & rotate +func (node *Node) rotateRight() *Node { + node = node._copy() + l := node.getLeftNode() + _l := l._copy() + + _lrCached := _l.rightNode + _l.rightNode = node + node.leftNode = _lrCached + + node.calcHeightAndSize() + _l.calcHeightAndSize() + + return _l +} + +// rotateLeft performs a left rotation on the node and returns the new root. +// NOTE: overwrites node +// TODO: optimize balance & rotate +func (node *Node) rotateLeft() *Node { + node = node._copy() + r := node.getRightNode() + _r := r._copy() + + _rlCached := _r.leftNode + _r.leftNode = node + node.rightNode = _rlCached + + node.calcHeightAndSize() + _r.calcHeightAndSize() + + return _r +} + +// calcHeightAndSize updates the height and size of the node based on its children. +// NOTE: mutates height and size +func (node *Node) calcHeightAndSize() { + node.height = maxInt8(node.getLeftNode().height, node.getRightNode().height) + 1 + node.size = node.getLeftNode().size + node.getRightNode().size +} + +// calcBalance calculates the balance factor of the node. +func (node *Node) calcBalance() int { + return int(node.getLeftNode().height) - int(node.getRightNode().height) +} + +// balance balances the subtree rooted at the node and returns the new root. +// NOTE: assumes that node can be modified +// TODO: optimize balance & rotate +func (node *Node) balance() (newSelf *Node) { + balance := node.calcBalance() + if balance >= -1 { + return node + } + if balance > 1 { + if node.getLeftNode().calcBalance() >= 0 { + // Left Left Case + return node.rotateRight() + } + // Left Right Case + left := node.getLeftNode() + node.leftNode = left.rotateLeft() + return node.rotateRight() + } + + if node.getRightNode().calcBalance() <= 0 { + // Right Right Case + return node.rotateLeft() + } + + // Right Left Case + right := node.getRightNode() + node.rightNode = right.rotateRight() + return node.rotateLeft() +} + +// Shortcut for TraverseInRange. +func (node *Node) Iterate(start, end string, cb func(*Node) bool) bool { + return node.TraverseInRange(start, end, true, true, cb) +} + +// Shortcut for TraverseInRange. +func (node *Node) ReverseIterate(start, end string, cb func(*Node) bool) bool { + return node.TraverseInRange(start, end, false, true, cb) +} + +// TraverseInRange traverses all nodes, including inner nodes. +// Start is inclusive and end is exclusive when ascending, +// Start and end are inclusive when descending. +// Empty start and empty end denote no start and no end. +// If leavesOnly is true, only visit leaf nodes. +// NOTE: To simulate an exclusive reverse traversal, +// just append 0x00 to start. +func (node *Node) TraverseInRange(start, end string, ascending bool, leavesOnly bool, cb func(*Node) bool) bool { + if node == nil { + return false + } + afterStart := (start == "" || start < node.key) + startOrAfter := (start == "" || start <= node.key) + beforeEnd := false + if ascending { + beforeEnd = (end == "" || node.key < end) + } else { + beforeEnd = (end == "" || node.key <= end) + } + + // Run callback per inner/leaf node. + stop := false + if (!node.IsLeaf() && !leavesOnly) || + (node.IsLeaf() && startOrAfter && beforeEnd) { + stop = cb(node) + if stop { + return stop + } + } + if node.IsLeaf() { + return stop + } + + if ascending { + // check lower nodes, then higher + if afterStart { + stop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + if stop { + return stop + } + if beforeEnd { + stop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + } else { + // check the higher nodes first + if beforeEnd { + stop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + if stop { + return stop + } + if afterStart { + stop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + } + + return stop +} + +// TraverseByOffset traverses all nodes, including inner nodes. +// A limit of math.MaxInt means no limit. +func (node *Node) TraverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { + if node == nil { + return false + } + + // fast paths. these happen only if TraverseByOffset is called directly on a leaf. + if limit <= 0 || offset >= node.size { + return false + } + if node.IsLeaf() { + if offset > 0 { + return false + } + return cb(node) + } + + // go to the actual recursive function. + return node.traverseByOffset(offset, limit, descending, leavesOnly, cb) +} + +// TraverseByOffset traverses the subtree rooted at the node by offset and limit, +// in either ascending or descending order, and applies the callback function to each traversed node. +// If leavesOnly is true, only leaf nodes are visited. +func (node *Node) traverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { + // caller guarantees: offset < node.size; limit > 0. + if !leavesOnly { + if cb(node) { + return true + } + } + first, second := node.getLeftNode(), node.getRightNode() + if descending { + first, second = second, first + } + if first.IsLeaf() { + // either run or skip, based on offset + if offset > 0 { + offset-- + } else { + cb(first) + limit-- + if limit <= 0 { + return false + } + } + } else { + // possible cases: + // 1 the offset given skips the first node entirely + // 2 the offset skips none or part of the first node, but the limit requires some of the second node. + // 3 the offset skips none or part of the first node, and the limit stops our search on the first node. + if offset >= first.size { + offset -= first.size // 1 + } else { + if first.traverseByOffset(offset, limit, descending, leavesOnly, cb) { + return true + } + // number of leaves which could actually be called from inside + delta := first.size - offset + offset = 0 + if delta >= limit { + return true // 3 + } + limit -= delta // 2 + } + } + + // because of the caller guarantees and the way we handle the first node, + // at this point we know that limit > 0 and there must be some values in + // this second node that we include. + + // => if the second node is a leaf, it has to be included. + if second.IsLeaf() { + return cb(second) + } + // => if it is not a leaf, it will still be enough to recursively call this + // function with the updated offset and limit + return second.traverseByOffset(offset, limit, descending, leavesOnly, cb) +} + +// Only used in testing... +func (node *Node) lmd() *Node { + if node.height == 0 { + return node + } + return node.getLeftNode().lmd() +} + +// Only used in testing... +func (node *Node) rmd() *Node { + if node.height == 0 { + return node + } + return node.getRightNode().rmd() +} + +func maxInt8(a, b int8) int8 { + if a > b { + return a + } + return b +} diff --git a/gnovm/pkg/benchops/gno/avl/tree.gno b/gnovm/pkg/benchops/gno/avl/tree.gno new file mode 100644 index 00000000000..e7aa55eb7e4 --- /dev/null +++ b/gnovm/pkg/benchops/gno/avl/tree.gno @@ -0,0 +1,103 @@ +package avl + +type IterCbFn func(key string, value interface{}) bool + +//---------------------------------------- +// Tree + +// The zero struct can be used as an empty tree. +type Tree struct { + node *Node +} + +// NewTree creates a new empty AVL tree. +func NewTree() *Tree { + return &Tree{ + node: nil, + } +} + +// Size returns the number of key-value pair in the tree. +func (tree *Tree) Size() int { + return tree.node.Size() +} + +// Has checks whether a key exists in the tree. +// It returns true if the key exists, otherwise false. +func (tree *Tree) Has(key string) (has bool) { + return tree.node.Has(key) +} + +// Get retrieves the value associated with the given key. +// It returns the value and a boolean indicating whether the key exists. +func (tree *Tree) Get(key string) (value interface{}, exists bool) { + _, value, exists = tree.node.Get(key) + return +} + +// GetByIndex retrieves the key-value pair at the specified index in the tree. +// It returns the key and value at the given index. +func (tree *Tree) GetByIndex(index int) (key string, value interface{}) { + return tree.node.GetByIndex(index) +} + +// Set inserts a key-value pair into the tree. +// If the key already exists, the value will be updated. +// It returns a boolean indicating whether the key was newly inserted or updated. +func (tree *Tree) Set(key string, value interface{}) (updated bool) { + newnode, updated := tree.node.Set(key, value) + tree.node = newnode + return updated +} + +// Remove removes a key-value pair from the tree. +// It returns the removed value and a boolean indicating whether the key was found and removed. +func (tree *Tree) Remove(key string) (value interface{}, removed bool) { + newnode, _, value, removed := tree.node.Remove(key) + tree.node = newnode + return value, removed +} + +// Iterate performs an in-order traversal of the tree within the specified key range. +// It calls the provided callback function for each key-value pair encountered. +// If the callback returns true, the iteration is stopped. +func (tree *Tree) Iterate(start, end string, cb IterCbFn) bool { + return tree.node.TraverseInRange(start, end, true, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// ReverseIterate performs a reverse in-order traversal of the tree within the specified key range. +// It calls the provided callback function for each key-value pair encountered. +// If the callback returns true, the iteration is stopped. +func (tree *Tree) ReverseIterate(start, end string, cb IterCbFn) bool { + return tree.node.TraverseInRange(start, end, false, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// IterateByOffset performs an in-order traversal of the tree starting from the specified offset. +// It calls the provided callback function for each key-value pair encountered, up to the specified count. +// If the callback returns true, the iteration is stopped. +func (tree *Tree) IterateByOffset(offset int, count int, cb IterCbFn) bool { + return tree.node.TraverseByOffset(offset, count, true, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// ReverseIterateByOffset performs a reverse in-order traversal of the tree starting from the specified offset. +// It calls the provided callback function for each key-value pair encountered, up to the specified count. +// If the callback returns true, the iteration is stopped. +func (tree *Tree) ReverseIterateByOffset(offset int, count int, cb IterCbFn) bool { + return tree.node.TraverseByOffset(offset, count, false, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} diff --git a/gnovm/pkg/benchops/gno/opcodes/gno.mod b/gnovm/pkg/benchops/gno/opcodes/gno.mod new file mode 100644 index 00000000000..326364184cd --- /dev/null +++ b/gnovm/pkg/benchops/gno/opcodes/gno.mod @@ -0,0 +1 @@ +module gno.land/r/x/benchmark/opcodes diff --git a/gnovm/pkg/benchops/gno/opcodes/opcode.gno b/gnovm/pkg/benchops/gno/opcodes/opcode.gno new file mode 100644 index 00000000000..05a5f88b48d --- /dev/null +++ b/gnovm/pkg/benchops/gno/opcodes/opcode.gno @@ -0,0 +1,1103 @@ +package opcodes + +type foo struct { + i int +} + +func (f foo) bark() { +} + +type dog interface { + bark() +} + +type foofighter struct { + f foo +} + +/* func ExprOps() +OpEval, [(const (2 int))](const-type int){(const (0 int)), (const (1 int))} +OpEval, [(const (2 int))](const-type int) +OpEval, (const-type int) +OpEval, (const (2 int)) +OpArrayType, [(const (2 int))](const-type int) +OpCompositeLit, [(const (2 int))](const-type int){(const (0 int)), (const (1 int))} +OpEval, (const (0 int)) +OpEval, (const (1 int)) +OpArrayLit, [(const (2 int))](const-type int){(const (0 int)), (const (1 int))} +OpDefine, a := [(const (2 int))](const-type int){(const (0 int)), (const (1 int))} +OpExec, bodyStmt[0/0/1]=a2 := [(const (2 int))](const-type int){(const (0 int)), (const (1 int))} +OpEval, [(const (2 int))](const-type int){(const (0 int)), (const (1 int))} +OpEval, [(const (2 int))](const-type int) +OpEval, (const-type int) +OpEval, (const (2 int)) +OpArrayType, [(const (2 int))](const-type int) +OpCompositeLit, [(const (2 int))](const-type int){(const (0 int)), (const (1 int))} +OpEval, (const (0 int)) +OpEval, (const (1 int)) +OpArrayLit, [(const (2 int))](const-type int){(const (0 int)), (const (1 int))} +OpDefine, a2 := [(const (2 int))](const-type int){(const (0 int)), (const (1 int))} +OpExec, bodyStmt[0/0/2]=m := (const (make func(t type{},z ...interface{})( map[int]int)))(map[(const-type int)] (const-type int)) +OpEval, (const (make func(t type{},z ...interface{})( map[int]int)))(map[(const-type int)] (const-type int)) +OpEval, (const (make func(t type{},z ...interface{})( map[int]int))) +OpEval, map[(const-type int)] (const-type int) +OpEval, (const-type int) +OpEval, (const-type int) +OpMapType, (typeval{int} type{}) +OpPreCall, (const (make func(t type{},z ...interface{})( map[int]int)))(map[(const-type int)] (const-type int)) +OpCall, make +OpCallNativeBody, make +OpReturn, [FRAME FUNC:make RECV:(undefined) (1 args) 4/1/0/2/2 LASTPKG:gno.land/r/x/benchmark LASTRLM:Realm{Path:"gno.land/r/x/benchmark",Time:3}#707D4A13D8A59C3A9220761016E2B0AF5FFCBC5A] +OpDefine, m := (const (make func(t type{},z ...interface{})( map[int]int)))(map[(const-type int)] (const-type int)) +OpExec, bodyStmt[0/0/3]=s := [](const-type int){(const (0 int)), (const (1 int)), (const (2 int)), (const (3 int)), (const (4 int)), (const (5 int)), (const (6 int)), (const (7 int)), (const (8 int)), (const (9 int))} +OpEval, [](const-type int){(const (0 int)), (const (1 int)), (const (2 int)), (const (3 int)), (const (4 int)), (const (5 int)), (const (6 int)), (const (7 int)), (const (8 int)), (const (9 int))} +OpEval, [](const-type int) +OpEval, (const-type int) +OpSliceType, [](const-type int) +OpCompositeLit, [](const-type int){(const (0 int)), (const (1 int)), (const (2 int)), (const (3 int)), (const (4 int)), (const (5 int)), (const (6 int)), (const (7 int)), (const (8 int)), (const (9 int))} +OpEval, (const (0 int)) +OpEval, (const (1 int)) +OpEval, (const (2 int)) +OpEval, (const (3 int)) +OpEval, (const (4 int)) +OpEval, (const (5 int)) +OpEval, (const (6 int)) +OpEval, (const (7 int)) +OpEval, (const (8 int)) +OpEval, (const (9 int)) +OpSliceLit, [](const-type int){(const (0 int)), (const (1 int)), (const (2 int)), (const (3 int)), (const (4 int)), (const (5 int)), (const (6 int)), (const (7 int)), (const (8 int)), (const (9 int))} +OpDefine, s := [](const-type int){(const (0 int)), (const (1 int)), (const (2 int)), (const (3 int)), (const (4 int)), (const (5 int)), (const (6 int)), (const (7 int)), (const (8 int)), (const (9 int))} +OpExec, bodyStmt[0/0/4]=s2 := [](const-type int){(const (9 int)): (const (90 int))} +OpEval, [](const-type int){(const (9 int)): (const (90 int))} +OpEval, [](const-type int) +OpEval, (const-type int) +OpSliceType, [](const-type int) +OpCompositeLit, [](const-type int){(const (9 int)): (const (90 int))} +OpEval, (const (9 int)) +OpEval, (const (90 int)) +OpSliceLit2, [](const-type int){(const (9 int)): (const (90 int))} +OpDefine, s2 := [](const-type int){(const (9 int)): (const (90 int))} +OpExec, bodyStmt[0/0/5]=f := foo{i: (const (1 int))} +OpEval, foo{i: (const (1 int))} +OpEval, foo +OpCompositeLit, foo{i: (const (1 int))} +OpEval, (const (1 int)) +OpStructLit, foo{i: (const (1 int))} +OpDefine, f := foo{i: (const (1 int))} +OpExec, bodyStmt[0/0/6]=ff := foofighter{f: f} +OpEval, foofighter{f: f} +OpEval, foofighter +OpCompositeLit, foofighter{f: f} +OpEval, f +OpStructLit, foofighter{f: f} +OpDefine, ff := foofighter{f: f} +OpExec, bodyStmt[0/0/7]=b := a[(const (0 int))] +OpEval, a[(const (0 int))] +OpEval, a +OpEval, (const (0 int)) +OpIndex1, (array[(0 int),(1 int)] [2]int) +OpDefine, b := a[(const (0 int))] +OpExec, bodyStmt[0/0/8]=b, _ = m[(const (0 int))] +OpEval, m[(const (0 int))] +OpEval, m +OpEval, (const (0 int)) +OpIndex2, (map{} map[int]int) +OpAssgin, b, _ = m[(const (0 int))] +OpExec, bodyStmt[0/0/9]=b = f.i +OpEval, f.i +OpEval, f +OpSelector, f.i +OpAssgin, b = f.i +OpExec, bodyStmt[0/0/10]=subs := s[(const (1 int)):(const (5 int)):(const (10 int))] +OpEval, s[(const (1 int)):(const (5 int)):(const (10 int))] +OpEval, s +OpEval, (const (1 int)) +OpEval, (const (5 int)) +OpEval, (const (10 int)) +OpSlice, s[(const (1 int)):(const (5 int)):(const (10 int))] +OpDefine, subs := s[(const (1 int)):(const (5 int)):(const (10 int))] +OpExec, bodyStmt[0/0/11]=ptr := &(a2[(const (0 int))]) +OpEval, &(a2[(const (0 int))]) +OpEval, a2 +OpEval, (const (0 int)) +OpRef, &(a2[(const (0 int))]) +OpDefine, ptr := &(a2[(const (0 int))]) +OpExec, bodyStmt[0/0/12]=b = *(ptr) +OpEval, *(ptr) +OpEval, ptr +OpStar, (&0x1400dfd25f0.(*int) *int) +OpAssgin, b = *(ptr) +OpExec, bodyStmt[0/0/13]=var d dog +OpExec, var d dog +OpEval, dog +OpValueDecl, var d dog +OpExec, bodyStmt[0/0/14]=d = f +OpEval, f +OpAssgin, d = f +OpExec, bodyStmt[0/0/15]=f = d.(foo) +OpEval, d.(foo) +OpEval, d +OpEval, foo +OpTypeAssert1, concrete type (struct{(1 int)} gno.land/r/x/benchmark.foo) +OpAssgin, f = d.(foo) +OpExec, bodyStmt[0/0/16]=f, ok := d.(foo) +OpEval, d.(foo) +OpEval, d +OpEval, foo +OpTypeAssert2, concrete type (struct{(1 int)} gno.land/r/x/benchmark.foo) +OpDefine, f, ok := d.(foo) +OpExec, bodyStmt[0/0/17]=(end) +OpExec, return +OpReturnFromBlock, [FRAME FUNC:ExprOps RECV:(undefined) (0 args) 1/0/0/0/1 LASTPKG:main LASTRLM:Realm(nil)] +OpHalt +*/ + +func ExprOps() { + a := [2]int{0, 1} // OpArrayLit + + a2 := [...]int{0, 1} // same as [2]int + m := make(map[int]int) // OpMapLit + s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // OpSliceLit + + s2 := []int{9: 90} // OpSliceLit2 + f := foo{i: 1} // OpStructLit + ff := foofighter{f: f} // OpCompositeLit + + b := a[0] // OpIndex1 + b, _ = m[0] // OpIndex2 + b = f.i // OpSelector + + subs := s[1:5:10] // OpSlice + + ptr := &a2[0] // OpRef + b = *ptr // OpStar + + var d dog + d = f + f = d.(foo) // OpTypeAssert1 concrete type + // d = f.(d) // OpTypeAssert1 interface + f, ok := d.(foo) // OpTypeAssert2 +} + +/* +func OpDecl() + +OpExec, type T (const-type gno.land/r/x/benchmark.T) +OpEval, (const-type gno.land/r/x/benchmark.T) +OpTypeDecl, type T (const-type gno.land/r/x/benchmark.T) +OpExec, bodyStmt[0/0/1]=var i (const-type int) +OpExec, var i (const-type int) +OpEval, (const-type int) +OpValueDecl, var i (const-type int) +OpExec, bodyStmt[0/0/2]=const c (const-type int) = (const (1 int)) +OpExec, const c (const-type int) = (const (1 int)) +OpEval, (const (1 int)) +OpEval, (const-type int) +OpValueDecl, const c (const-type int) = (const (1 int)) +OpExec, bodyStmt[0/0/3]=(end) +OpExec, return +OpReturnFromBlock, func()() +OpHalt +*/ +func OpDecl() { + type T int + var i int + const c int = 1 +} + +/* +func OpEvalInt() + +OpEval, (const (1234567891 int)) +OpDefine, i := (const (1234567891 int)) +OpExec, bodyStmt[0/0/1]=i2 := (const (1234567892 int)) +OpEval, (const (1234567892 int)) +OpDefine, i2 := (const (1234567892 int)) +OpExec, bodyStmt[0/0/2]=(end) +OpExec, return +OpReturnFromBlock, [FRAME FUNC:OpEvalInt RECV:(undefined) (0 args) 1/0/0/0/1 LASTPKG:main LASTRLM:Realm(nil)] +OpHalt +*/ +func OpEvalInt() { + i := 1234567891 + i2 := 123_456_7892 +} + +/* +func OpEvalFloat() + +OpEval, (const (1.23456789 float64)) +OpDefine, f := (const (1.23456789 float64)) +OpExec, bodyStmt[0/0/1]=f2 := (const (1.23456789 float64)) +OpEval, (const (1.23456789 float64)) +OpDefine, f2 := (const (1.23456789 float64)) +OpExec, bodyStmt[0/0/2]=f3 := (const (123.456789 float64)) +OpEval, (const (123.456789 float64)) +OpDefine, f3 := (const (123.456789 float64)) +OpExec, bodyStmt[0/0/3]=f4 := (const (0.0123456789 float64)) +OpEval, (const (0.0123456789 float64)) +OpDefine, f4 := (const (0.0123456789 float64)) +OpExec, bodyStmt[0/0/4]=f5 := (const (2048 float64)) +OpEval, (const (2048 float64)) +OpDefine, f5 := (const (2048 float64)) +OpExec, bodyStmt[0/0/5]=f6 := (const (15.5 float64)) +OpEval, (const (15.5 float64)) +OpDefine, f6 := (const (15.5 float64)) +OpExec, bodyStmt[0/0/6]=(end) +OpExec, return +OpReturnFromBlock, [FRAME FUNC:OpEvalFloat RECV:(undefined) (0 args) 1/0/0/0/1 LASTPKG:main LASTRLM:Realm(nil)] +OpHalt +*/ +func OpEvalFloat() { + // decimal + f := 1.23456789 + f2 := 1.234_56789 + // exp + f3 := 1.23456789e2 + f4 := 1.23456789e-2 + // hex + f5 := 0x2.p10 // == 2048.0 + f6 := 0x1.Fp+0 // == 1.9375 +} + +/* +func StmtOps() + +OpEval, (const (1000000 int)) +OpDefine, i := (const (1000000 int)) +OpExec, bodyStmt[0/0/1]=i++ +OpInc, i++ +OpExec, bodyStmt[0/0/2]=i-- +OpDec, i-- +OpExec, bodyStmt[0/0/3]=i += (const (1000000 int)) +OpEval, (const (1000000 int)) +OpAddAssgin, i += (const (1000000 int)) +OpExec, bodyStmt[0/0/4]=i -= (const (1000000 int)) +OpEval, (const (1000000 int)) +OpSubAssgin, i -= (const (1000000 int)) +OpExec, bodyStmt[0/0/5]=i *= (const (1 int)) +OpEval, (const (1 int)) +OpMulAssgin, i *= (const (1 int)) +OpExec, bodyStmt[0/0/6]=i /= (const (1000000 int)) +OpEval, (const (1000000 int)) +OpQuoAssgin, i /= (const (1000000 int)) +OpExec, bodyStmt[0/0/7]=i %= (const (3 int)) +OpEval, (const (3 int)) +OpRemAssgin, i %= (const (3 int)) +OpExec, bodyStmt[0/0/8]=i &= (const (2 int)) +OpEval, (const (2 int)) +OpAddAssgin, i &= (const (2 int)) +OpExec, bodyStmt[0/0/9]=i |= (const (12 int)) +OpEval, (const (12 int)) +OpBorAssgin, i |= (const (12 int)) +OpExec, bodyStmt[0/0/10]=i &^= (const (6 int)) +OpEval, (const (6 int)) +OpBandnAssgin, i &^= (const (6 int)) +OpExec, bodyStmt[0/0/11]=i ^= (const (14 int)) +OpEval, (const (14 int)) +OpXorAssgin, i ^= (const (14 int)) +OpExec, bodyStmt[0/0/12]=i <<= (const (2 uint)) +OpEval, (const (2 uint)) +OpShlAssgin, i <<= (const (2 uint)) +OpExec, bodyStmt[0/0/13]=i >>= (const (2 uint)) +OpEval, (const (2 uint)) +OpShrAssgin, i >>= (const (2 uint)) +OpExec, bodyStmt[0/0/14]=(end) +OpExec, return +OpReturnFromBlock, [FRAME FUNC:StmtOps RECV:(undefined) (0 args) 1/0/0/0/1 LASTPKG:main LASTRLM:Realm(nil)] +OpHalt +*/ +func StmtOps() { + i := 1_000_000 + i++ // 1,000,001 + i-- // 1,000,00 + i += 1_000_000 // 2_000_000 + i -= 1_000_000 // 1_000_000 + i *= 1 // 1,000,000 + i /= 1_000_000 // 1 + i %= 3 // 1 + i &= 2 // 01 & 10 = 0 + i |= 12 // 12( 1100 ) + i &^= 6 // 12&^6=8 (1100 &^ 0110 = 1000) + i ^= 14 // 8^14 = 6 (1000 ^ 1110 = 0110) + i <<= 2 // 24 + i >>= 2 // 6 +} + +/* +func ControlOps() + +OpExec, var a (const-type int) +OpEval, (const-type int) +OpValueDecl, var a (const-type int) +OpExec, bodyStmt[0/0/1]=var b (const-type int) +OpExec, var b (const-type int) +OpEval, (const-type int) +OpValueDecl, var b (const-type int) +OpExec, bodyStmt[0/0/2]=if (const-type bool)(a > (const (0 int))) { b++ } else { b-- } +OpEval, (const-type bool)(a > (const (0 int))) +OpEval, (const-type bool) +OpEval, a > (const (0 int)) +OpEval, a +OpEval, (const (0 int)) +OpGtr, (0 int) | (0 int) | false +OpPreCall, (const-type bool)(a > (const (0 int))) +OpConvert, Value: (false bool) | Type: bool +OpIfCond, if (const-type bool)(a > (const (0 int))) { b++ } else { b-- } +OpExec, bodyStmt[0/0/-2]=(init) +OpDec, b-- +OpExec, bodyStmt[0/0/1]=(end) +OpPopBlock, Block(ID:0000000000000000000000000000000000000000:0,Addr:0x1400ad725a0,Source:if (const-type bool)(a { case (const (0 int)): a++; case (const (1 int)): a--; default: a = (const (2 int)) } +OpEval, b +OpSwitchClause, switch b { case (const (0 int)): a++; case (const (1 int)): a--; default: a = (const (2 int)) } +OpEval, (const (0 int)) +OpSwitchClauseCase, (0 int) | (-1 int) +OpSwitchClause, switch b { case (const (0 int)): a++; case (const (1 int)): a--; default: a = (const (2 int)) } +OpEval, (const (1 int)) +OpSwitchClauseCase, (1 int) | (-1 int) +OpSwitchClause, switch b { case (const (0 int)): a++; case (const (1 int)): a--; default: a = (const (2 int)) } +OpExec, bodyStmt[0/0/-2]=(init) +OpEval, (const (2 int)) +OpAssgin, a = (const (2 int)) +OpExec, bodyStmt[0/0/1]=(end) +OpPopBlock, Block(ID:0000000000000000000000000000000000000000:0,Addr:0x1400ad72780,Source:switch b { case (c...,Parent:0x1400ad723c0) +OpPopFrameAndReset, [FRAME LABEL: 3/1/0/2/2] +OpExec, bodyStmt[0/0/4]=(end) +OpExec, return +OpReturnFromBlock, [FRAME FUNC:ControlOps RECV:(undefined) (0 args) 1/0/0/0/1 LASTPKG:main LASTRLM:Realm(nil)] +OpHalt +*/ +func ControlOps() { + var a int + var b int + if a > 0 { + b++ + } else { + b-- + } + switch b { + + case 0: + a++ + case 1: + a-- + default: + a = 2 + } +} + +/* +func OpDefer() + +OpEval, (const (1 int)) +OpArrayType, [(const (1 int))](const-type int) +OpCompositeLit, [(const (1 int))](const-type int){(const (0 int))} +OpEval, (const (0 int)) +OpArrayLit, [(const (1 int))](const-type int){(const (0 int))} +OpDefine, a := [(const (1 int))](const-type int){(const (0 int))} +OpExec, bodyStmt[0/0/1]=defer func func(){ a[(const (0 int))]++ }() +OpBody, defer func func(){ a[(const (0 int))]++ }() +OpEval, func func(){ a[(const (0 int))]++ } +OpEval, func() +OpFuncType, func() +OpFuncLit, func func(){ a[(const (0 int))]++ } +OpDefer, defer func func(){ a[(const (0 int))]++ }() +OpExec, bodyStmt[0/0/2]=defer (const (len func(x interface{})( int)))(a) +OpBody, defer (const (len func(x interface{})( int)))(a) +OpEval, (const (len func(x interface{})( int))) +OpEval, a +OpDefer, defer (const (len func(x interface{})( int)))(a) +OpExec, bodyStmt[0/0/3]=return a[(const (0 int))] +OpBody, return a[(const (0 int))] +OpEval, a[(const (0 int))] +OpEval, a +OpEval, (const (0 int)) +OpIndex1, (array[(0 int)] [1]int) +OpReturnToBlock, [FRAME FUNC:OpDefer RECV:(undefined) (0 args) 1/0/0/0/1 LASTPKG:main LASTRLM:Realm(nil)] +OpReturnCallDefers +OpCallDeferNativeBody, len +OpReturnCallDefers +OpExec, bodyStmt[0/0/-2]=(init) +OpBody, a[(const (0 int))]++ +OpEval, a +OpEval, (const (0 int)) +OpInc, a[(const (0 int))]++ +OpExec, bodyStmt[0/0/1]=(end) +OpReturnCallDefers +OpReturnFromBlock, [FRAME FUNC:OpDefer RECV:(undefined) (0 args) 1/0/0/0/1 LASTPKG:main LASTRLM:Realm(nil)] +OpHalt +*/ +func OpDefer() int { + a := [1]int{0} + + defer func() { + // a[0]++ + }() + + defer len(a) + + return a[0] +} + +/* +func OpUnary() +*/ +func OpUnary() { + a := 1 + b := -a + b = +a + b = ^a + + c := true + d := !c +} + +/* +func OpBinary() + +OpEval, (const (1000000 int)) +OpDefine, a := (const (1000000 int)) +OpExec, bodyStmt[0/0/1]=b := (const (1000001 int)) +OpEval, (const (1000001 int)) +OpDefine, b := (const (1000001 int)) +OpExec, bodyStmt[0/0/2]=var c (const-type bool) +OpExec, var c (const-type bool) +OpEval, (const-type bool) +OpValueDecl, var c (const-type bool) +OpExec, bodyStmt[0/0/3]=var d (const-type int) +OpExec, var d (const-type int) +OpEval, (const-type int) +OpValueDecl, var d (const-type int) +OpExec, bodyStmt[0/0/4]=c = (const (true bool)) +OpEval, (const (true bool)) +OpAssgin, c = (const (true bool)) +OpExec, bodyStmt[0/0/5]=c = (const (false bool)) +OpEval, (const (false bool)) +OpAssgin, c = (const (false bool)) +OpExec, bodyStmt[0/0/6]=c = (const-type bool)(a == b) +OpEval, (const-type bool)(a == b) +OpEval, (const-type bool) +OpEval, a == b +OpEval, a +OpEval, b +OpEql, (1000000 int) == (1000001 int) is false +OpPreCall, (const-type bool)(a == b) +OpConvert, Value: (false bool) | Type: bool +OpAssgin, c = (const-type bool)(a == b) +OpExec, bodyStmt[0/0/7]=c = (const-type bool)(a != b) +OpEval, (const-type bool)(a != b) +OpEval, (const-type bool) +OpEval, a != b +OpEval, a +OpEval, b +OpNeq, (1000000 int) != (1000001 int) is true +OpPreCall, (const-type bool)(a != b) +OpConvert, Value: (true bool) | Type: bool +OpAssgin, c = (const-type bool)(a != b) +OpExec, bodyStmt[0/0/8]=c = (const-type bool)(a < b) +OpEval, (const-type bool)(a < b) +OpEval, (const-type bool) +OpEval, a < b +OpEval, a +OpEval, b +OpLss, (1000000 int) < (1000001 int) is true +OpPreCall, (const-type bool)(a < b) +OpConvert, Value: (true bool) | Type: bool +OpAssgin, c = (const-type bool)(a < b) +OpExec, bodyStmt[0/0/9]=c = (const-type bool)(a <= b) +OpEval, (const-type bool)(a <= b) +OpEval, (const-type bool) +OpEval, a <= b +OpEval, a +OpEval, b +OpLeq, (1000000 int) <= (1000001 int) is true +OpPreCall, (const-type bool)(a <= b) +OpConvert, Value: (true bool) | Type: bool +OpAssgin, c = (const-type bool)(a <= b) +OpExec, bodyStmt[0/0/10]=c = (const-type bool)(a > b) +OpEval, (const-type bool)(a > b) +OpEval, (const-type bool) +OpEval, a > b +OpEval, a +OpEval, b +OpGtr, (1000000 int) > (1000001 int) is false +OpPreCall, (const-type bool)(a > b) +OpConvert, Value: (false bool) | Type: bool +OpAssgin, c = (const-type bool)(a > b) +OpExec, bodyStmt[0/0/11]=c = (const-type bool)(a >= b) +OpEval, (const-type bool)(a >= b) +OpEval, (const-type bool) +OpEval, a >= b +OpEval, a +OpEval, b +OpGeq, (1000000 int) >= (1000001 int) is false +OpPreCall, (const-type bool)(a >= b) +OpConvert, Value: (false bool) | Type: bool +OpAssgin, c = (const-type bool)(a >= b) +OpExec, bodyStmt[0/0/12]=d = a + b +OpEval, a + b +OpEval, a +OpEval, b +OpAdd, (1000000 int) + (1000001 int) +OpAssgin, d = a + b +OpExec, bodyStmt[0/0/13]=d = a - b +OpEval, a - b +OpEval, a +OpEval, b +OpSub, (1000000 int) - (1000001 int) +OpAssgin, d = a - b +OpExec, bodyStmt[0/0/14]=d = a | b +OpEval, a | b +OpEval, a +OpEval, b +OpBor, (1000000 int) | (1000001 int) +OpAssgin, d = a | b +OpExec, bodyStmt[0/0/15]=d = a ^ b +OpEval, a ^ b +OpEval, a +OpEval, b +OpXor, (1000000 int) ^ (1000001 int) +OpAssgin, d = a ^ b +OpExec, bodyStmt[0/0/16]=d = a * b +OpEval, a * b +OpEval, a +OpEval, b +OpMul, (1000000 int) * (1000001 int) +OpAssgin, d = a * b +OpExec, bodyStmt[0/0/17]=d = a / b +OpEval, a / b +OpEval, a +OpEval, b +OpQuo, (1000000 int) / (1000001 int) +OpAssgin, d = a / b +OpExec, bodyStmt[0/0/18]=d = a % b +OpEval, a % b +OpEval, a +OpEval, b +OpRem, (1000000 int) % (1000001 int) +OpAssgin, d = a % b +OpExec, bodyStmt[0/0/19]=d = (const (63 int)) << (const-type uint)(a) +OpEval, (const (63 int)) << (const-type uint)(a) +OpEval, (const (63 int)) +OpEval, (const-type uint)(a) +OpEval, (const-type uint) +OpEval, a +OpPreCall, (const-type uint)(a) +OpConvert, Value: (1000000 int) | Type: uint +OpShl, (63 int) << (1000000 uint) +OpAssgin, d = (const (63 int)) << (const-type uint)(a) +OpExec, bodyStmt[0/0/20]=d = a >> (const (63 uint)) +OpEval, a >> (const (63 uint)) +OpEval, a +OpEval, (const (63 uint)) +OpShr, (1000000 int) >> (63 uint) +OpAssgin, d = a >> (const (63 uint)) +OpExec, bodyStmt[0/0/21]=d = a & b +OpEval, a & b +OpEval, a +OpEval, b +OpBand, (1000000 int) & (1000001 int) +OpAssgin, d = a & b +OpExec, bodyStmt[0/0/22]=d = a &^ b +OpEval, a &^ b +OpEval, a +OpEval, b +OpBandn, (1000000 int) &^ (1000001 int) +OpAssgin, d = a &^ b +OpExec, bodyStmt[0/0/23]=(end) +OpExec, return +OpReturnFromBlock, [FRAME FUNC:OpBinary RECV:(undefined) (0 args) 1/0/0/0/1 LASTPKG:main LASTRLM:Realm(nil)] +OpHalt +*/ +func OpBinary() { + a := 1_000_000 + b := 1_000_001 + + var c bool + var d int + // boolean + c = true || false + c = true && false + c = a == b + c = a != b + c = a < b + c = a <= b + c = a > b + c = a >= b + c = true && a < b + c = true || a < b + // + d = a + b // 1 + d = a - b // 0 + d = a | b // 1 + d = a ^ b // 1 + d = a * b // 1,000,001,000,000 + d = a / b + d = a % b + d = 63 << a + d = a >> 63 + d = a & b + d = a &^ b +} + +/* +OpEval, (const (true bool)) +OpDefine, a := (const (true bool)) +OpExec, bodyStmt[0/0/1]=b := (const (false bool)) +OpEval, (const (false bool)) +OpDefine, b := (const (false bool)) +OpExec, bodyStmt[0/0/2]=b || a +OpEval, b || a +OpEval, b +OpEval, a +OpLor, (false bool), (true bool) +OpExec, bodyStmt[0/0/3]=a || b +OpEval, a || b +OpEval, a +OpExec, bodyStmt[0/0/4]=(end) +OpExec, return +OpReturnFromBlock, [FRAME FUNC:OpLor RECV:(undefined) (0 args) 1/0/0/0/1 LASTPKG:main LASTRLM:Realm(nil)] +OpHalt +*/ +func OpLor() { + a := true + b := false + b || a + a || b +} + +/* +OpEval, (const (true bool)) +OpDefine, a := (const (true bool)) +OpExec, bodyStmt[0/0/1]=b := (const (false bool)) +OpEval, (const (false bool)) +OpDefine, b := (const (false bool)) +OpExec, bodyStmt[0/0/2]=a && b +OpEval, a && b +OpEval, a +OpEval, b +OpLand, (true bool), (false bool) +OpExec, bodyStmt[0/0/3]=(end) +OpExec, return +OpReturnFromBlock, [FRAME FUNC:OpLand RECV:(undefined) (0 args) 1/0/0/0/1 LASTPKG:main LASTRLM:Realm(nil)] +OpHalt +*/ +func OpLand() { + a := true + b := false + a && b +} + +/* +OpEval, (const (recover func()(exception interface{}))) +OpDefer, defer (const (recover func()(exception interface{})))() +OpExec, bodyStmt[0/0/1]=panic((const ("panic" string))) +OpEval, (const ("panic" string)) +OpPanic1 +OpReturnCallDefers +OpReturnCallDefers +OpPanic2 +OpReturnCallDefers +OpReturnFromBlock, [FRAME FUNC:OpPanic RECV:(undefined) (0 args) 1/0/0/0/1 LASTPKG:main LASTRLM:Realm(nil)] +OpHalt +*/ +func OpPanic() { + defer func() { + recover() + }() + panic("panic") +} + +/* +OpEval, []interface { }{(const (1 int)), (const ("hello" string)), (const (1 float64)), interface { }} +OpEval, []interface { } +OpEval, interface { } +OpInterfaceType, interface { } +OpSliceType, []interface { } +OpCompositeLit, []interface { }{(const (1 int)), (const ("hello" string)), (const (1 float64)), interface { }} +OpEval, (const (1 int)) +OpEval, (const ("hello" string)) +OpEval, (const (1 float64)) +OpEval, interface { } +OpInterfaceType, interface { } +OpSliceLit, []interface { }{(const (1 int)), (const ("hello" string)), (const (1 float64)), interface { }} +OpDefine, values := []interface { }{(const (1 int)), (const ("hello" string)), (const (1 float64)), interface { }} +OpBody, for _, v := range values { switch v { case (const-type int): ; case (const-type string): ; case (const-type float64): ; default: } } +OpEval, values +OpRangeIter, switch v { case (const-type int): ; case (const-type string): ; case (const-type float64): ; default: } +OpEval, v +OpTypeSwitch, switch v { case (const-type int): ; case (const-type string): ; case (const-type float64): ; default: }, (1 int) +OpPopBlock, Block(ID:0000000000000000000000000000000000000000:0,Addr:0x1400bbbc780,Source:switch v { case (c...,Parent:0x1400bbbc5a0) +OpPopFrameAndReset, [FRAME LABEL: 4/2/0/3/3] +OpRangeIter, bodyStmt[4/1/-1]=(init) +OpRangeIter, switch v { case (const-type int): ; case (const-type string): ; case (const-type float64): ; default: } +OpEval, v +OpTypeSwitch, switch v { case (const-type int): ; case (const-type string): ; case (const-type float64): ; default: }, ("hello" string) +OpPopBlock, Block(ID:0000000000000000000000000000000000000000:0,Addr:0x1400bbbc960,Source:switch v { case (c...,Parent:0x1400bbbc5a0) +OpPopFrameAndReset, [FRAME LABEL: 4/2/0/3/3] +OpRangeIter, bodyStmt[4/2/-1]=(init) +OpRangeIter, switch v { case (const-type int): ; case (const-type string): ; case (const-type float64): ; default: } +OpEval, v +OpTypeSwitch, switch v { case (const-type int): ; case (const-type string): ; case (const-type float64): ; default: }, (1 float64) +OpPopBlock, Block(ID:0000000000000000000000000000000000000000:0,Addr:0x1400bbbcb40,Source:switch v { case (c...,Parent:0x1400bbbc5a0) +OpPopFrameAndReset, [FRAME LABEL: 4/2/0/3/3] +OpRangeIter, bodyStmt[4/3/-1]=(init) +OpRangeIter, switch v { case (const-type int): ; case (const-type string): ; case (const-type float64): ; default: } +OpEval, v +OpTypeSwitch, switch v { case (const-type int): ; case (const-type string): ; case (const-type float64): ; default: }, (typeval{interface{} (0x1400bba9130)} type{}) +OpPopBlock, Block(ID:0000000000000000000000000000000000000000:0,Addr:0x1400bbbcd20,Source:switch v { case (c...,Parent:0x1400bbbc5a0) +OpPopFrameAndReset, [FRAME LABEL: 4/2/0/3/3] +OpPopFrameAndReset, [FRAME LABEL: 3/1/0/2/2] +OpBody, bodyStmt[0/0/2]=(end) +OpExec, return, OpExec +OpReturnFromBlock, [FRAME FUNC:OpTypeSwitch RECV:(undefined) (0 args) 1/0/0/0/1 LASTPKG:main LASTRLM:Realm(nil)] +OpHalt +*/ +func OpTypeSwitch() { + values := []interface{}{1, "hello", 1.0, interface{}} + for _, v := range values { + switch v.(type) { + case int: + // ... + case string: + // ... + case float64: + // ... + default: + // ... + } + } +} + +func OpCallDeferNativeBody() { + a := [1]int{0} + defer func() { + len(a) + }() +} + +/* +OpEval, [(const (1 int))](const-type int){(const (1 int))} +OpEval, [(const (1 int))](const-type int) +OpEval, (const-type int) +OpEval, (const (1 int)) +OpArrayType, [(const (1 int))](const-type int) +OpCompositeLit, [(const (1 int))](const-type int){(const (1 int))} +OpEval, (const (1 int)) +OpArrayLit, [(const (1 int))](const-type int){(const (1 int))} +OpDefine, a := [(const (1 int))](const-type int){(const (1 int))} +OpExec, bodyStmt[0/0/1]=s := (const ("h" string)) +OpBody, s := (const ("h" string)) +OpEval, (const ("h" string)) +OpDefine, s := (const ("h" string)) +OpExec, bodyStmt[0/0/2]=m := map[(const-type int)] (const-type string){(const (1 int)): (const ("one" string))} +OpBody, m := map[(const-type int)] (const-type string){(const (1 int)): (const ("one" string))} +OpEval, map[(const-type int)] (const-type string){(const (1 int)): (const ("one" string))} +OpEval, map[(const-type int)] (const-type string) +OpEval, (const-type int) +OpEval, (const-type string) +OpMapType, (typeval{string} type{}) +OpCompositeLit, map[(const-type int)] (const-type string){(const (1 int)): (const ("one" string))} +OpEval, (const (1 int)) +OpEval, (const ("one" string)) +OpMapLit, map[(const-type int)] (const-type string){(const (1 int)): (const ("one" string))} +OpDefine, m := map[(const-type int)] (const-type string){(const (1 int)): (const ("one" string))} +OpExec, bodyStmt[0/0/3]=one := (const (1 int)) +OpBody, one := (const (1 int)) +OpEval, (const (1 int)) +OpDefine, one := (const (1 int)) +OpExec, bodyStmt[0/0/4]=p := &([(const (1 int))](const-type int){one}) +OpBody, p := &([(const (1 int))](const-type int){one}) +OpEval, &([(const (1 int))](const-type int){one}) +OpEval, [(const (1 int))](const-type int){one} +OpEval, [(const (1 int))](const-type int) +OpEval, (const-type int) +OpEval, (const (1 int)) +OpArrayType, [(const (1 int))](const-type int) +OpCompositeLit, [(const (1 int))](const-type int){one} +OpEval, one +OpArrayLit, [(const (1 int))](const-type int){one} +OpRef, &([(const (1 int))](const-type int){one}) +OpDefine, p := &([(const (1 int))](const-type int){one}) +OpExec, bodyStmt[0/0/5]=for i := range a { (const (println func(xs ...interface{})()))(a[i]) } +OpBody, for i := range a { (const (println func(xs ...interface{})()))(a[i]) } +OpEval, a +OpExec, bodyStmt[0/0/-2]=(init) +OpRangeIter, (const (println func(xs ...interface{})()))(a[i]) +OpEval, (const (println func(xs ...interface{})()))(a[i]) +OpEval, (const (println func(xs ...interface{})())) +OpEval, a[i] +OpEval, a +OpEval, i +OpIndex1, (array[(1 int)] [1]int) +OpPreCall, (const (println func(xs ...interface{})()))(a[i]) +OpCall, println +OpCallNativeBody, println +OpReturn, [FRAME FUNC:println RECV:(undefined) (1 args) 5/2/0/3/3 LASTPKG:gno.land/r/x/benchmark LASTRLM:Realm{Path:"gno.land/r/x/benchmark",Time:3}#707D4A13D8A59C3A9220761016E2B0AF5FFCBC5A] +OpPopResults +OpExec, bodyStmt[1/0/1]=(end) +OpPopFrameAndReset, [FRAME LABEL: 3/1/0/2/2] +OpExec, bodyStmt[0/0/6]=for i := range s { (const (println func(xs ...interface{})()))(s[i]) } +OpBody, for i := range s { (const (println func(xs ...interface{})()))(s[i]) } +OpEval, s +OpExec, bodyStmt[0/0/-2]=(init) +OpRangeIterString, (const (println func(xs ...interface{})()))(s[i]) +OpEval, (const (println func(xs ...interface{})()))(s[i]) +OpEval, (const (println func(xs ...interface{})())) +OpEval, s[i] +OpEval, s +OpEval, i +OpIndex1, ("h" string) +OpPreCall, (const (println func(xs ...interface{})()))(s[i]) +OpCall, println +OpCallNativeBody, println +OpReturn, [FRAME FUNC:println RECV:(undefined) (1 args) 5/2/0/3/3 LASTPKG:gno.land/r/x/benchmark LASTRLM:Realm{Path:"gno.land/r/x/benchmark",Time:3}#707D4A13D8A59C3A9220761016E2B0AF5FFCBC5A] +OpPopResults +OpExec, bodyStmt[0/0/1]=(end) +OpPopFrameAndReset, [FRAME LABEL: 3/1/0/2/2] +OpExec, bodyStmt[0/0/7]=for i := range m { (const (println func(xs ...interface{})()))(m[i]) } +OpBody, for i := range m { (const (println func(xs ...interface{})()))(m[i]) } +OpEval, m +OpExec, bodyStmt[0/0/-2]=(init) +OpRangeIterMap, (const (println func(xs ...interface{})()))(m[i]) +OpEval, (const (println func(xs ...interface{})()))(m[i]) +OpEval, (const (println func(xs ...interface{})())) +OpEval, m[i] +OpEval, m +OpEval, i +OpIndex1, (map{(1 int):("one" string)} map[int]string) +OpPreCall, (const (println func(xs ...interface{})()))(m[i]) +OpCall, println +OpCallNativeBody, println +OpReturn, [FRAME FUNC:println RECV:(undefined) (1 args) 5/2/0/3/3 LASTPKG:gno.land/r/x/benchmark LASTRLM:Realm{Path:"gno.land/r/x/benchmark",Time:3}#707D4A13D8A59C3A9220761016E2B0AF5FFCBC5A] +OpPopResults +OpExec, bodyStmt[0/0/1]=(end) +OpPopFrameAndReset, [FRAME LABEL: 3/1/0/2/2] +OpExec, bodyStmt[0/0/8]=for i := range p { (const (println func(xs ...interface{})()))(*(p)[i]) } +OpBody, for i := range p { (const (println func(xs ...interface{})()))(*(p)[i]) } +OpEval, p +OpExec, bodyStmt[0/0/-2]=(init) +OpRangeIterArrayPtr, (const (println func(xs ...interface{})()))(*(p)[i]) +OpEval, (const (println func(xs ...interface{})()))(*(p)[i]) +OpEval, (const (println func(xs ...interface{})())) +OpEval, *(p)[i] +OpEval, *(p) +OpEval, p +OpStar, (&0x1400a7b1590.(*[1]int) *[1]int) +OpEval, i +OpIndex1, (array[(1 int)] [1]int) +OpPreCall, (const (println func(xs ...interface{})()))(*(p)[i]) +OpCall, println +OpCallNativeBody, println +OpReturn, [FRAME FUNC:println RECV:(undefined) (1 args) 5/2/0/3/3 LASTPKG:gno.land/r/x/benchmark LASTRLM:Realm{Path:"gno.land/r/x/benchmark",Time:3}#707D4A13D8A59C3A9220761016E2B0AF5FFCBC5A] +OpPopResults +OpExec, bodyStmt[1/0/1]=(end) +OpPopFrameAndReset, [FRAME LABEL: 3/1/0/2/2] +OpExec, bodyStmt[0/0/9]=(end) +OpExec, return +OpReturnFromBlock, [FRAME FUNC:OpRange RECV:(undefined) (0 args) 1/0/0/0/1 LASTPKG:main LASTRLM:Realm(nil)] +OpHalt +*/ + +func OpRange() { + a := [1]int{1} + s := "h" + m := map[int]string{ + 1: "one", + } + one := 1 + p := &[1]int{one} + + for i := range a { + println(a[i]) + } + + for i := range s { + println(s[i]) + } + + for i := range m { + println(m[i]) + } + + for i := range p { + println(p[i]) + } +} + +/* +OpEval, [(const (1 int))](const-type int){(const (0 int))} +OpEval, [(const (1 int))](const-type int) +OpEval, (const-type int) +OpEval, (const (1 int)) +OpArrayType, [(const (1 int))](const-type int) +OpCompositeLit, [(const (1 int))](const-type int){(const (0 int))} +OpEval, (const (0 int)) +OpArrayLit, [(const (1 int))](const-type int){(const (0 int))} +OpDefine, a := [(const (1 int))](const-type int){(const (0 int))} +OpExec, bodyStmt[0/0/1]=for i := (const (0 int)); (const-type bool)(i < (const (1 int))); i++ { (const (len func(x interface{})( int)))(a) +OpBody, for i := (const (0 int)); (const-type bool)(i < (const (1 int))); i++ { (const (len func(x interface{})( int)))(a) } +OpExec, i := (const (0 int)) +OpEval, (const (0 int)) +OpDefine, i := (const (0 int)) +OpEval, (const-type bool)(i < (const (1 int))) +OpEval, (const-type bool) +OpEval, i < (const (1 int)) +OpEval, i +OpEval, (const (1 int)) +OpLss, (0 int) < (1 int) is true +OpPreCall, (const-type bool)(i < (const (1 int))) +OpConvert, Value: (true bool) | Type: bool +OpExec, bodyStmt[0/0/-2]=(init) +OpForLoop, (const (len func(x interface{})( int)))(a) +OpEval, (const (len func(x interface{})( int)))(a) +OpEval, (const (len func(x interface{})( int))) +OpEval, a +OpPreCall, (const (len func(x interface{})( int)))(a) +OpCall, len +OpCallNativeBody, len +OpReturn, [FRAME FUNC:len RECV:(undefined) (1 args) 5/1/0/3/3 LASTPKG:gno.land/r/x/benchmark LASTRLM:Realm{Path:"gno.land/r/x/benchmark",Time:3}#707D4A13D8A59C3A9220761016E2B0AF5FFCBC5A] +OpPopResults +OpExec, bodyStmt[0/0/1]=(end) +OpForLoop, Post i++ +OpInc, i++ +OpEval, (const-type bool)(i < (const (1 int))) +OpEval, (const-type bool) +OpEval, i < (const (1 int)) +OpEval, i +OpEval, (const (1 int)) +OpLss, (1 int) < (1 int) is false +OpPreCall, (const-type bool)(i < (const (1 int))) +OpConvert, Value: (false bool) | Type: bool +OpExec, bodyStmt[0/0/-1]=(init) +OpPopFrameAndReset, [FRAME LABEL: 3/1/0/2/2] +OpExec, bodyStmt[0/0/2]=(end) +OpExec, return +OpReturnFromBlock, [FRAME FUNC:OpLoop RECV:(undefined) (0 args) 1/0/0/0/1 LASTPKG:main LASTRLM:Realm(nil)] +OpHalt +*/ +func OpForLoop() { + a := [1]int{0} + for i := 0; i < 1; i++ { + len(a) + } +} + +/* +OpEval, struct { a [](const-type string), b map[(const-type string)] (const-type string), c <-chan (const-type string), d func(), e interface { } }{} +OpEval, struct { a [](const-type string), b map[(const-type string)] (const-type string), c <-chan (const-type string), d func(), e interface { } } +OpEval, a [](const-type string) +OpEval, [](const-type string) +OpEval, (const-type string) +OpSliceType, [](const-type string) +OpFieldType, a [](const-type string) +OpEval, b map[(const-type string)] (const-type string) +OpEval, map[(const-type string)] (const-type string) +OpEval, (const-type string) +OpEval, (const-type string) +OpMapType, (typeval{string} type{}) +OpFieldType, b map[(const-type string)] (const-type string) +OpEval, c <-chan (const-type string) +OpEval, <-chan (const-type string) +OpEval, (const-type string) +OpChanType, <-chan (const-type string) +OpFieldType, c <-chan (const-type string) +OpEval, d func() +OpEval, func() +OpFuncType, func() +OpFieldType, d func() +OpEval, e interface { } +OpEval, interface { } +OpInterfaceType, interface { } +OpFieldType, e interface { } +OpStructType, struct { a [](const-type string), b map[(const-type string)] (const-type string), c <-chan (const-type string), d func(), e interface { } } +OpCompositeLit, struct { a [](const-type string), b map[(const-type string)] (const-type string), c <-chan (const-type string), d func(), e interface { } }{} +OpStructLit, struct { a [](const-type string), b map[(const-type string)] (const-type string), c <-chan (const-type string), d func(), e interface { } }{} +OpDefine, t := struct { a [](const-type string), b map[(const-type string)] (const-type string), c <-chan (const-type string), d func(), e interface +OpExec, bodyStmt[0/0/1]=(end) +OpExec, return +OpReturnFromBlock, [FRAME FUNC:OpTypes RECV:(undefined) (0 args) 1/0/0/0/1 LASTPKG:main LASTRLM:Realm(nil)] +OpHalt +*/ +func OpTypes() { + t := struct { + a []string + b map[string]string + c chan string + d func() + e interface{} + }{} +} + +/* +OpEval, (const (1 int)) +OpDefine, x := (const (1 int)) +OpExec, bodyStmt[0/0/1]=for i := (const (0 int)); (const-type bool)(i < (const (1 int))); i++ { x + +OpBody, for i := (const (0 int)); (const-type bool)(i < (const (1 int))); i++ { x + (const (1 int)) +OpExec, i := (const (0 int)), OpExec +OpEval, (const (0 int)) +OpDefine, i := (const (0 int)) +OpEval, (const-type bool)(i < (const (1 int))) +OpEval, (const-type bool) +OpEval, i < (const (1 int)) +OpEval, i +OpEval, (const (1 int)) +OpLss, (0 int) < (1 int) is true +OpPreCall, (const-type bool)(i < (const (1 int))) +OpConvert, Value: (true bool) | Type: bool +OpExec, bodyStmt[0/0/-2]=(init), OpForLoop +OpForLoop, x + (const (1 int)) +OpEval, x + (const (1 int)) +OpEval, x +OpEval, (const (1 int)) +OpAdd, (1 int) + (1 int) +OpExec, bodyStmt[0/0/1]=(end), OpForLoop +OpForLoop, Post i++ +OpInc, i++ +OpEval, (const-type bool)(i < (const (1 int))) +OpEval, (const-type bool) +OpEval, i < (const (1 int)) +OpEval, i +OpEval, (const (1 int)) +OpLss, (1 int) < (1 int) is false +OpPreCall, (const-type bool)(i < (const (1 int))) +OpConvert, Value: (false bool) | Type: bool +OpExec, bodyStmt[0/0/-1]=(init), OpForLoop +OpPopFrameAndReset, [FRAME LABEL: 3/1/0/2/2] +OpExec, bodyStmt[0/0/2]=if (const-type bool)(x == (const (1 int))) { x + (const (1 int)) }, OpBody +OpBody, if (const-type bool)(x == (const (1 int))) { x + (const (1 int)) } +OpEval, (const-type bool)(x == (const (1 int))) +OpEval, (const-type bool) +OpEval, x == (const (1 int)) +OpEval, x +OpEval, (const (1 int)) +OpEql, (1 int) == (1 int) is true +OpPreCall, (const-type bool)(x == (const (1 int))) +OpConvert, Value: (true bool) | Type: bool +OpIfCond, if (const-type bool)(x == (const (1 int))) { x + (const (1 int)) } +OpExec, bodyStmt[0/0/-2]=(init), OpBody +OpBody, x + (const (1 int)) +OpEval, x + (const (1 int)) +OpEval, x +OpEval, (const (1 int)) +OpAdd, (1 int) + (1 int) +OpExec, bodyStmt[0/0/1]=(end), OpBody +OpBody, bodyStmt[0/0/1]=(end) +OpPopBlock, Block(ID:0000000000000000000000000000000000000000:0,Addr:0x140063d03c0,Source:if (const-type bool)(x= len(storeCodeNames) { + return invalidStoreCode + } + return storeCodeNames[storeCode] +} diff --git a/gnovm/pkg/benchops/ops_disabled.go b/gnovm/pkg/benchops/ops_disabled.go new file mode 100644 index 00000000000..84482067d83 --- /dev/null +++ b/gnovm/pkg/benchops/ops_disabled.go @@ -0,0 +1,5 @@ +//go:build !benchmarkingops + +package benchmarking + +const OpsEnabled = false diff --git a/gnovm/pkg/benchops/ops_enabled.go b/gnovm/pkg/benchops/ops_enabled.go new file mode 100644 index 00000000000..836cfd57a23 --- /dev/null +++ b/gnovm/pkg/benchops/ops_enabled.go @@ -0,0 +1,5 @@ +//go:build benchmarkingops + +package benchmarking + +const OpsEnabled = true diff --git a/gnovm/pkg/benchops/storage_disabled.go b/gnovm/pkg/benchops/storage_disabled.go new file mode 100644 index 00000000000..a42029f47a9 --- /dev/null +++ b/gnovm/pkg/benchops/storage_disabled.go @@ -0,0 +1,5 @@ +//go:build !benchmarkingstorage + +package benchmarking + +const StorageEnabled = false diff --git a/gnovm/pkg/benchops/storage_enabled.go b/gnovm/pkg/benchops/storage_enabled.go new file mode 100644 index 00000000000..2cb7cf976d2 --- /dev/null +++ b/gnovm/pkg/benchops/storage_enabled.go @@ -0,0 +1,5 @@ +//go:build benchmarkingstorage + +package benchmarking + +const StorageEnabled = true diff --git a/gnovm/pkg/gnolang/machine.go b/gnovm/pkg/gnolang/machine.go index 24f94abc10b..c899ace82ff 100644 --- a/gnovm/pkg/gnolang/machine.go +++ b/gnovm/pkg/gnolang/machine.go @@ -12,6 +12,7 @@ import ( "sync" "testing" + bm "github.com/gnolang/gno/gnovm/pkg/benchops" "github.com/gnolang/gno/tm2/pkg/errors" "github.com/gnolang/gno/tm2/pkg/std" "github.com/gnolang/gno/tm2/pkg/store" @@ -263,6 +264,12 @@ 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) { + if bm.OpsEnabled || bm.StorageEnabled { + bm.InitMeasure() + } + if bm.StorageEnabled { + defer bm.FinishStore() + } return m.runMemPackage(memPkg, save, false) } @@ -824,6 +831,13 @@ func (m *Machine) Eval(x Expr) []TypedValue { if debug { m.Printf("Machine.Eval(%v)\n", x) } + if bm.OpsEnabled || bm.StorageEnabled { + // reset the benchmark stack + bm.InitMeasure() + } + if bm.StorageEnabled { + defer bm.FinishStore() + } // X must not have been preprocessed. if x.GetAttribute(ATTR_PREPROCESSED) != nil { panic(fmt.Sprintf( @@ -1104,6 +1118,7 @@ const ( OpRangeIterMap Op = 0xD5 OpRangeIterArrayPtr Op = 0xD6 OpReturnCallDefers Op = 0xD7 // TODO rename? + OpVoid Op = 0xFF // For profiling simple operation ) const GasFactorCPU int64 = 1 @@ -1251,16 +1266,34 @@ const ( // main run loop. func (m *Machine) Run() { + if bm.OpsEnabled { + defer func() { + // output each machine run results to file + bm.FinishRun() + }() + } for { if m.Debugger.enabled { m.Debug() } op := m.PopOp() + if bm.OpsEnabled { + // benchmark the operation. + bm.StartOpCode(byte(OpVoid)) + bm.StopOpCode() + // we do not benchmark static evaluation. + if op != OpStaticTypeOf { + bm.StartOpCode(byte(op)) + } + } // TODO: this can be optimized manually, even into tiers. switch op { /* Control operators */ case OpHalt: m.incrCPU(OpCPUHalt) + if bm.OpsEnabled { + bm.StopOpCode() + } return case OpNoop: m.incrCPU(OpCPUNoop) @@ -1579,6 +1612,11 @@ func (m *Machine) Run() { default: panic(fmt.Sprintf("unexpected opcode %s", op.String())) } + if bm.OpsEnabled { + if op != OpStaticTypeOf { + bm.StopOpCode() + } + } } } diff --git a/gnovm/pkg/gnolang/op_string.go b/gnovm/pkg/gnolang/op_string.go index db52b4ff67b..b13bb8f278e 100644 --- a/gnovm/pkg/gnolang/op_string.go +++ b/gnovm/pkg/gnolang/op_string.go @@ -114,6 +114,7 @@ func _() { _ = x[OpRangeIterMap-213] _ = x[OpRangeIterArrayPtr-214] _ = x[OpReturnCallDefers-215] + _ = x[OpVoid-255] } const ( @@ -126,6 +127,7 @@ const ( _Op_name_6 = "OpAssignOpAddAssignOpSubAssignOpMulAssignOpQuoAssignOpRemAssignOpBandAssignOpBandnAssignOpBorAssignOpXorAssignOpShlAssignOpShrAssignOpDefineOpIncOpDec" _Op_name_7 = "OpValueDeclOpTypeDecl" _Op_name_8 = "OpStickyOpBodyOpForLoopOpRangeIterOpRangeIterStringOpRangeIterMapOpRangeIterArrayPtrOpReturnCallDefers" + _Op_name_9 = "OpVoid" ) var ( @@ -168,6 +170,8 @@ func (i Op) String() string { case 208 <= i && i <= 215: i -= 208 return _Op_name_8[_Op_index_8[i]:_Op_index_8[i+1]] + case i == 255: + return _Op_name_9 default: return "Op(" + strconv.FormatInt(int64(i), 10) + ")" } diff --git a/gnovm/pkg/gnolang/realm.go b/gnovm/pkg/gnolang/realm.go index 3710524130a..a27b483a173 100644 --- a/gnovm/pkg/gnolang/realm.go +++ b/gnovm/pkg/gnolang/realm.go @@ -6,6 +6,8 @@ import ( "fmt" "reflect" "strings" + + bm "github.com/gnolang/gno/gnovm/pkg/benchops" ) /* @@ -134,6 +136,10 @@ func (rlm *Realm) String() string { // xo or co is nil if the element value is undefined or has no // associated object. func (rlm *Realm) DidUpdate(po, xo, co Object) { + if bm.OpsEnabled { + bm.PauseOpCode() + defer bm.ResumeOpCode() + } if rlm == nil { return } @@ -293,6 +299,10 @@ func (rlm *Realm) MarkNewEscaped(oo Object) { // OpReturn calls this when exiting a realm transaction. func (rlm *Realm) FinalizeRealmTransaction(readonly bool, store Store) { + if bm.OpsEnabled { + bm.PauseOpCode() + defer bm.ResumeOpCode() + } if readonly { if true || len(rlm.newCreated) > 0 || diff --git a/gnovm/pkg/gnolang/store.go b/gnovm/pkg/gnolang/store.go index 038f4ba894b..3e16fe5b93b 100644 --- a/gnovm/pkg/gnolang/store.go +++ b/gnovm/pkg/gnolang/store.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + bm "github.com/gnolang/gno/gnovm/pkg/benchops" "github.com/gnolang/gno/tm2/pkg/amino" "github.com/gnolang/gno/tm2/pkg/colors" "github.com/gnolang/gno/tm2/pkg/std" @@ -240,6 +241,13 @@ func (ds *defaultStore) SetCachePackage(pv *PackageValue) { // Some atomic operation. func (ds *defaultStore) GetPackageRealm(pkgPath string) (rlm *Realm) { + var size int + if bm.StorageEnabled { + bm.StartStore(bm.StoreGetPackageRealm) + defer func() { + bm.StopStore(size) + }() + } oid := ObjectIDFromPkgPath(pkgPath) key := backendRealmKey(oid) bz := ds.baseStore.Get([]byte(key)) @@ -247,6 +255,7 @@ func (ds *defaultStore) GetPackageRealm(pkgPath string) (rlm *Realm) { return nil } amino.MustUnmarshal(bz, &rlm) + size = len(bz) if debug { if rlm.ID != oid.PkgID { panic(fmt.Sprintf("unexpected realm id: expected %v but got %v", @@ -258,10 +267,23 @@ func (ds *defaultStore) GetPackageRealm(pkgPath string) (rlm *Realm) { // An atomic operation to set the package realm info (id counter etc). func (ds *defaultStore) SetPackageRealm(rlm *Realm) { + if bm.OpsEnabled { + bm.PauseOpCode() + defer bm.ResumeOpCode() + } + + var size int + if bm.StorageEnabled { + bm.StartStore(bm.StoreSetPackageRealm) + defer func() { + bm.StopStore(size) + }() + } oid := ObjectIDFromPkgPath(rlm.Path) key := backendRealmKey(oid) bz := amino.MustMarshal(rlm) ds.baseStore.Set([]byte(key), bz) + size = len(bz) } // NOTE: does not consult the packageGetter, so instead @@ -270,6 +292,10 @@ func (ds *defaultStore) SetPackageRealm(rlm *Realm) { // all []TypedValue types and TypeValue{} types to be // loaded (non-ref) types. func (ds *defaultStore) GetObject(oid ObjectID) Object { + if bm.OpsEnabled { + bm.PauseOpCode() + defer bm.ResumeOpCode() + } oo := ds.GetObjectSafe(oid) if oo == nil { panic(fmt.Sprintf("unexpected object with id %s", oid.String())) @@ -299,9 +325,23 @@ func (ds *defaultStore) GetObjectSafe(oid ObjectID) Object { // loads and caches an object. // CONTRACT: object isn't already in the cache. func (ds *defaultStore) loadObjectSafe(oid ObjectID) Object { + if bm.OpsEnabled { + bm.PauseOpCode() + defer bm.ResumeOpCode() + } + + var size int + + if bm.StorageEnabled { + bm.StartStore(bm.StoreGetObject) + defer func() { + bm.StopStore(size) + }() + } key := backendObjectKey(oid) hashbz := ds.baseStore.Get([]byte(key)) if hashbz != nil { + size = len(hashbz) hash := hashbz[:HashSize] bz := hashbz[HashSize:] var oo Object @@ -324,6 +364,17 @@ func (ds *defaultStore) loadObjectSafe(oid ObjectID) Object { // NOTE: unlike GetObject(), SetObject() is also used to persist updated // package values. func (ds *defaultStore) SetObject(oo Object) { + if bm.OpsEnabled { + bm.PauseOpCode() + defer bm.ResumeOpCode() + } + var size int + if bm.StorageEnabled { + bm.StartStore(bm.StoreSetObject) + defer func() { + bm.StopStore(size) + }() + } oid := oo.GetObjectID() // replace children/fields with Ref. o2 := copyValueWithRefs(oo) @@ -342,6 +393,7 @@ func (ds *defaultStore) SetObject(oo Object) { copy(hashbz, hash.Bytes()) copy(hashbz[HashSize:], bz) ds.baseStore.Set([]byte(key), hashbz) + size = len(hashbz) } // save object to cache. if debug { @@ -378,6 +430,17 @@ func (ds *defaultStore) SetObject(oo Object) { } func (ds *defaultStore) DelObject(oo Object) { + if bm.OpsEnabled { + bm.PauseOpCode() + defer bm.ResumeOpCode() + } + if bm.StorageEnabled { + bm.StartStore(bm.StoreDeleteObject) + defer func() { + // delete is a signle operation, not a func of size of bytes + bm.StopStore(0) + }() + } oid := oo.GetObjectID() // delete from cache. delete(ds.cacheObjects, oid) @@ -406,6 +469,19 @@ func (ds *defaultStore) GetType(tid TypeID) Type { } func (ds *defaultStore) GetTypeSafe(tid TypeID) Type { + if bm.OpsEnabled { + bm.PauseOpCode() + defer bm.ResumeOpCode() + } + + var size int + + if bm.StorageEnabled { + bm.StartStore(bm.StoreGetType) + defer func() { + bm.StopStore(size) + }() + } // check cache. if tt, exists := ds.cacheTypes[tid]; exists { return tt @@ -417,6 +493,7 @@ func (ds *defaultStore) GetTypeSafe(tid TypeID) Type { if bz != nil { var tt Type amino.MustUnmarshal(bz, &tt) + size = len(bz) if debug { if tt.TypeID() != tid { panic(fmt.Sprintf("unexpected type id: expected %v but got %v", @@ -448,6 +525,18 @@ func (ds *defaultStore) SetCacheType(tt Type) { } func (ds *defaultStore) SetType(tt Type) { + if bm.OpsEnabled { + bm.PauseOpCode() + defer bm.ResumeOpCode() + } + var size int + + if bm.StorageEnabled { + bm.StartStore(bm.StoreSetType) + defer func() { + bm.StopStore(size) + }() + } tid := tt.TypeID() // return if tid already known. if tt2, exists := ds.cacheTypes[tid]; exists { @@ -463,6 +552,7 @@ func (ds *defaultStore) SetType(tt Type) { tcopy := copyTypeWithRefs(tt) bz := amino.MustMarshalAny(tcopy) ds.baseStore.Set([]byte(key), bz) + size = len(bz) } // save type to cache. ds.cacheTypes[tid] = tt @@ -477,6 +567,19 @@ func (ds *defaultStore) GetBlockNode(loc Location) BlockNode { } func (ds *defaultStore) GetBlockNodeSafe(loc Location) BlockNode { + if bm.OpsEnabled { + bm.PauseOpCode() + defer bm.ResumeOpCode() + } + + var size int + + if bm.StorageEnabled { + bm.StartStore(bm.StoreGetBlockNode) + defer func() { + bm.StopStore(size) + }() + } // check cache. if bn, exists := ds.cacheNodes[loc]; exists { return bn @@ -488,6 +591,7 @@ func (ds *defaultStore) GetBlockNodeSafe(loc Location) BlockNode { if bz != nil { var bn BlockNode amino.MustUnmarshal(bz, &bn) + size = len(bz) if debug { if bn.GetLocation() != loc { panic(fmt.Sprintf("unexpected node location: expected %v but got %v", @@ -551,6 +655,18 @@ func (ds *defaultStore) incGetPackageIndexCounter() uint64 { } func (ds *defaultStore) AddMemPackage(memPkg *std.MemPackage) { + if bm.OpsEnabled { + bm.PauseOpCode() + defer bm.ResumeOpCode() + } + var size int + + if bm.StorageEnabled { + bm.StartStore(bm.StoreAddMemPackage) + defer func() { + bm.StopStore(size) + }() + } memPkg.Validate() // NOTE: duplicate validation. ctr := ds.incGetPackageIndexCounter() idxkey := []byte(backendPackageIndexKey(ctr)) @@ -558,6 +674,7 @@ func (ds *defaultStore) AddMemPackage(memPkg *std.MemPackage) { ds.baseStore.Set(idxkey, []byte(memPkg.Path)) pathkey := []byte(backendPackagePathKey(memPkg.Path)) ds.iavlStore.Set(pathkey, bz) + size = len(bz) } // GetMemPackage retrieves the MemPackage at the given path. @@ -567,6 +684,19 @@ func (ds *defaultStore) GetMemPackage(path string) *std.MemPackage { } func (ds *defaultStore) getMemPackage(path string, isRetry bool) *std.MemPackage { + if bm.OpsEnabled { + bm.PauseOpCode() + defer bm.ResumeOpCode() + } + + var size int + + if bm.StorageEnabled { + bm.StartStore(bm.StoreGetMemPackage) + defer func() { + bm.StopStore(size) + }() + } pathkey := []byte(backendPackagePathKey(path)) bz := ds.iavlStore.Get(pathkey) if bz == nil { @@ -584,6 +714,7 @@ func (ds *defaultStore) getMemPackage(path string, isRetry bool) *std.MemPackage } var memPkg *std.MemPackage amino.MustUnmarshal(bz, &memPkg) + size = len(bz) return memPkg }