From 250439a011cb25d2920774412f822a4765169111 Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Mon, 9 Feb 2026 13:35:36 +0000 Subject: [PATCH] feat(t8n): add --opcode.count flag for opcode frequency counting --- cmd/evm/internal/t8ntool/flags.go | 5 + cmd/evm/internal/t8ntool/opcode_counter.go | 125 +++++++++++++++++++++ cmd/evm/internal/t8ntool/transition.go | 17 +++ cmd/evm/main.go | 1 + 4 files changed, 148 insertions(+) create mode 100644 cmd/evm/internal/t8ntool/opcode_counter.go diff --git a/cmd/evm/internal/t8ntool/flags.go b/cmd/evm/internal/t8ntool/flags.go index a6ec33eacf..078d304927 100644 --- a/cmd/evm/internal/t8ntool/flags.go +++ b/cmd/evm/internal/t8ntool/flags.go @@ -162,6 +162,11 @@ var ( strings.Join(vm.ActivateableEips(), ", ")), Value: "GrayGlacier", } + OpcodeCountFlag = &cli.StringFlag{ + Name: "opcode.count", + Usage: "If set, opcode execution counts will be written to this file (relative to output.basedir).", + Value: "", + } VerbosityFlag = &cli.IntFlag{ Name: "verbosity", Usage: "sets the verbosity level", diff --git a/cmd/evm/internal/t8ntool/opcode_counter.go b/cmd/evm/internal/t8ntool/opcode_counter.go new file mode 100644 index 0000000000..6f0bd261bf --- /dev/null +++ b/cmd/evm/internal/t8ntool/opcode_counter.go @@ -0,0 +1,125 @@ +// Copyright 2025 The go-ethereum Authors +// This file is part of go-ethereum. +// +// go-ethereum is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// go-ethereum is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with go-ethereum. If not, see . + +package t8ntool + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" +) + +// opcodeCounter is a simple tracer that counts how many times each opcode is executed. +type opcodeCounter struct { + counts map[vm.OpCode]uint64 +} + +func newOpcodeCounter() *opcodeCounter { + return &opcodeCounter{ + counts: make(map[vm.OpCode]uint64), + } +} + +func (c *opcodeCounter) hooks() *tracing.Hooks { + return &tracing.Hooks{ + OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + c.counts[vm.OpCode(op)]++ + }, + } +} + +// results returns the opcode counts keyed by opcode name. +func (c *opcodeCounter) results() map[string]uint64 { + out := make(map[string]uint64, len(c.counts)) + for op, count := range c.counts { + out[op.String()] = count + } + return out +} + +// composeHooks merges two sets of hooks into one. Both sets of hooks are called +// for each event. +func composeHooks(a, b *tracing.Hooks) *tracing.Hooks { + return &tracing.Hooks{ + OnTxStart: func(vm *tracing.VMContext, tx *types.Transaction, from common.Address) { + if a.OnTxStart != nil { + a.OnTxStart(vm, tx, from) + } + if b.OnTxStart != nil { + b.OnTxStart(vm, tx, from) + } + }, + OnTxEnd: func(receipt *types.Receipt, err error) { + if a.OnTxEnd != nil { + a.OnTxEnd(receipt, err) + } + if b.OnTxEnd != nil { + b.OnTxEnd(receipt, err) + } + }, + OnEnter: func(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { + if a.OnEnter != nil { + a.OnEnter(depth, typ, from, to, input, gas, value) + } + if b.OnEnter != nil { + b.OnEnter(depth, typ, from, to, input, gas, value) + } + }, + OnExit: func(depth int, output []byte, gasUsed uint64, err error, reverted bool) { + if a.OnExit != nil { + a.OnExit(depth, output, gasUsed, err, reverted) + } + if b.OnExit != nil { + b.OnExit(depth, output, gasUsed, err, reverted) + } + }, + OnOpcode: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, rData []byte, depth int, err error) { + if a.OnOpcode != nil { + a.OnOpcode(pc, op, gas, cost, scope, rData, depth, err) + } + if b.OnOpcode != nil { + b.OnOpcode(pc, op, gas, cost, scope, rData, depth, err) + } + }, + OnFault: func(pc uint64, op byte, gas, cost uint64, scope tracing.OpContext, depth int, err error) { + if a.OnFault != nil { + a.OnFault(pc, op, gas, cost, scope, depth, err) + } + if b.OnFault != nil { + b.OnFault(pc, op, gas, cost, scope, depth, err) + } + }, + OnSystemCallStart: func() { + if a.OnSystemCallStart != nil { + a.OnSystemCallStart() + } + if b.OnSystemCallStart != nil { + b.OnSystemCallStart() + } + }, + OnSystemCallEnd: func() { + if a.OnSystemCallEnd != nil { + a.OnSystemCallEnd() + } + if b.OnSystemCallEnd != nil { + b.OnSystemCallEnd() + } + }, + } +} diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index af60333cbd..13fd258ab8 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -191,11 +191,28 @@ func Transition(ctx *cli.Context) error { }) } } + // Configure opcode counter + var counter *opcodeCounter + if ctx.IsSet(OpcodeCountFlag.Name) && ctx.String(OpcodeCountFlag.Name) != "" { + counter = newOpcodeCounter() + if vmConfig.Tracer != nil { + vmConfig.Tracer = composeHooks(vmConfig.Tracer, counter.hooks()) + } else { + vmConfig.Tracer = counter.hooks() + } + } // Run the test and aggregate the result s, result, body, err := prestate.Apply(vmConfig, chainConfig, txIt, ctx.Int64(RewardFlag.Name)) if err != nil { return err } + // Write opcode counts if enabled + if counter != nil { + fname := ctx.String(OpcodeCountFlag.Name) + if err := saveFile(baseDir, fname, counter.results()); err != nil { + return err + } + } // Dump the execution result var ( collector = make(Alloc) diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 57741b5f9c..77a06bec26 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -161,6 +161,7 @@ var ( t8ntool.ForknameFlag, t8ntool.ChainIDFlag, t8ntool.RewardFlag, + t8ntool.OpcodeCountFlag, }, }