mirror of
https://github.com/wazero/wazero
synced 2026-06-21 14:12:37 +00:00
experimental: split program counter and source offset resolution (#1448)
Signed-off-by: Thomas Pelletier <thomas@pelletier.codes>
This commit is contained in:
@@ -20,3 +20,22 @@ type InternalModule interface {
|
||||
// The methods panics if i is out of bounds.
|
||||
Global(i int) api.Global
|
||||
}
|
||||
|
||||
// ProgramCounter is an opaque value representing a specific execution point in
|
||||
// a module. It is meant to be used with Function.SourceOffsetForPC and
|
||||
// StackIterator.
|
||||
type ProgramCounter uint64
|
||||
|
||||
// InternalFunction exposes some information about a function instance.
|
||||
type InternalFunction interface {
|
||||
// Definition provides introspection into the function's names and
|
||||
// signature.
|
||||
Definition() api.FunctionDefinition
|
||||
|
||||
// SourceOffsetForPC resolves a program counter into its corresponding
|
||||
// offset in the Code section of the module this function belongs to.
|
||||
// The source offset is meant to help map the function calls to their
|
||||
// location in the original source files. Returns 0 if the offset cannot
|
||||
// be calculated.
|
||||
SourceOffsetForPC(pc ProgramCounter) uint64
|
||||
}
|
||||
|
||||
+32
-20
@@ -16,16 +16,11 @@ type StackIterator interface {
|
||||
// Next moves the iterator to the next function in the stack. Returns
|
||||
// false if it reached the bottom of the stack.
|
||||
Next() bool
|
||||
// FunctionDefinition returns the function type of the current function.
|
||||
FunctionDefinition() api.FunctionDefinition
|
||||
// SourceOffset computes the offset in the module Code section where the
|
||||
// call occured (translated for native functions), or the beginning of
|
||||
// function for the top of the stack. Returns 0 if the source offset
|
||||
// cannot be calculated.
|
||||
//
|
||||
// The source offset is meant to help map the function calls to their
|
||||
// location in the original source files.
|
||||
SourceOffset() uint64
|
||||
// Function describes the function called by the current frame.
|
||||
Function() InternalFunction
|
||||
// ProgramCounter returns the program counter associated with the
|
||||
// function call.
|
||||
ProgramCounter() ProgramCounter
|
||||
// Parameters returns api.ValueType-encoded parameters of the current
|
||||
// function. Do not modify the content of the slice, and copy out any
|
||||
// value you need.
|
||||
@@ -198,7 +193,7 @@ type stackIterator struct {
|
||||
base StackIterator
|
||||
index int
|
||||
pcs []uint64
|
||||
fns []api.FunctionDefinition
|
||||
fns []InternalFunction
|
||||
params parameters
|
||||
}
|
||||
|
||||
@@ -209,8 +204,8 @@ func (si *stackIterator) Next() bool {
|
||||
si.params.clear()
|
||||
|
||||
for si.base.Next() {
|
||||
si.pcs = append(si.pcs, si.base.SourceOffset())
|
||||
si.fns = append(si.fns, si.base.FunctionDefinition())
|
||||
si.pcs = append(si.pcs, uint64(si.base.ProgramCounter()))
|
||||
si.fns = append(si.fns, si.base.Function())
|
||||
si.params.append(si.base.Parameters())
|
||||
}
|
||||
|
||||
@@ -220,11 +215,11 @@ func (si *stackIterator) Next() bool {
|
||||
return si.index < len(si.pcs)
|
||||
}
|
||||
|
||||
func (si *stackIterator) SourceOffset() uint64 {
|
||||
return si.pcs[si.index]
|
||||
func (si *stackIterator) ProgramCounter() ProgramCounter {
|
||||
return ProgramCounter(si.pcs[si.index])
|
||||
}
|
||||
|
||||
func (si *stackIterator) FunctionDefinition() api.FunctionDefinition {
|
||||
func (si *stackIterator) Function() InternalFunction {
|
||||
return si.fns[si.index]
|
||||
}
|
||||
|
||||
@@ -237,9 +232,23 @@ type StackFrame struct {
|
||||
Function api.Function
|
||||
Params []uint64
|
||||
Results []uint64
|
||||
PC uint64
|
||||
SourceOffset uint64
|
||||
}
|
||||
|
||||
type internalFunction struct {
|
||||
definition api.FunctionDefinition
|
||||
sourceOffset uint64
|
||||
}
|
||||
|
||||
func (f internalFunction) Definition() api.FunctionDefinition {
|
||||
return f.definition
|
||||
}
|
||||
|
||||
func (f internalFunction) SourceOffsetForPC(pc ProgramCounter) uint64 {
|
||||
return f.sourceOffset
|
||||
}
|
||||
|
||||
// stackFrameIterator is an implementation of the experimental.stackFrameIterator
|
||||
// interface.
|
||||
type stackFrameIterator struct {
|
||||
@@ -253,12 +262,15 @@ func (si *stackFrameIterator) Next() bool {
|
||||
return si.index < len(si.stack)
|
||||
}
|
||||
|
||||
func (si *stackFrameIterator) FunctionDefinition() api.FunctionDefinition {
|
||||
return si.fndef[si.index]
|
||||
func (si *stackFrameIterator) Function() InternalFunction {
|
||||
return internalFunction{
|
||||
definition: si.fndef[si.index],
|
||||
sourceOffset: si.stack[si.index].SourceOffset,
|
||||
}
|
||||
}
|
||||
|
||||
func (si *stackFrameIterator) SourceOffset() uint64 {
|
||||
return si.stack[si.index].SourceOffset
|
||||
func (si *stackFrameIterator) ProgramCounter() ProgramCounter {
|
||||
return ProgramCounter(si.stack[si.index].PC)
|
||||
}
|
||||
|
||||
func (si *stackFrameIterator) Parameters() []uint64 {
|
||||
|
||||
@@ -89,28 +89,35 @@ func Example_stackIterator() {
|
||||
it := &fakeStackIterator{}
|
||||
|
||||
for it.Next() {
|
||||
fmt.Println("function:", it.FunctionDefinition().DebugName())
|
||||
fn := it.Function()
|
||||
pc := it.ProgramCounter()
|
||||
fmt.Println("function:", fn.Definition().DebugName())
|
||||
fmt.Println("\tparameters:", it.Parameters())
|
||||
fmt.Println("\tsource offset:", it.SourceOffset())
|
||||
fmt.Println("\tprogram counter:", pc)
|
||||
fmt.Println("\tsource offset:", fn.SourceOffsetForPC(pc))
|
||||
}
|
||||
|
||||
// Output:
|
||||
// function: fn0
|
||||
// parameters: [1 2 3]
|
||||
// program counter: 5890831
|
||||
// source offset: 1234
|
||||
// function: fn1
|
||||
// parameters: []
|
||||
// program counter: 5899822
|
||||
// source offset: 7286
|
||||
// function: fn2
|
||||
// parameters: [4]
|
||||
// program counter: 6820312
|
||||
// source offset: 935891
|
||||
}
|
||||
|
||||
type fakeStackIterator struct {
|
||||
iteration int
|
||||
def api.FunctionDefinition
|
||||
args []uint64
|
||||
offset uint64
|
||||
iteration int
|
||||
def api.FunctionDefinition
|
||||
args []uint64
|
||||
pc uint64
|
||||
sourceOffset uint64
|
||||
}
|
||||
|
||||
func (s *fakeStackIterator) Next() bool {
|
||||
@@ -118,15 +125,18 @@ func (s *fakeStackIterator) Next() bool {
|
||||
case 0:
|
||||
s.def = &mockFunctionDefinition{debugName: "fn0"}
|
||||
s.args = []uint64{1, 2, 3}
|
||||
s.offset = 1234
|
||||
s.pc = 5890831
|
||||
s.sourceOffset = 1234
|
||||
case 1:
|
||||
s.def = &mockFunctionDefinition{debugName: "fn1"}
|
||||
s.args = []uint64{}
|
||||
s.offset = 7286
|
||||
s.pc = 5899822
|
||||
s.sourceOffset = 7286
|
||||
case 2:
|
||||
s.def = &mockFunctionDefinition{debugName: "fn2"}
|
||||
s.args = []uint64{4}
|
||||
s.offset = 935891
|
||||
s.pc = 6820312
|
||||
s.sourceOffset = 935891
|
||||
case 3:
|
||||
return false
|
||||
}
|
||||
@@ -134,20 +144,36 @@ func (s *fakeStackIterator) Next() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *fakeStackIterator) FunctionDefinition() api.FunctionDefinition {
|
||||
return s.def
|
||||
func (s *fakeStackIterator) Function() experimental.InternalFunction {
|
||||
return internalFunction{
|
||||
definition: s.def,
|
||||
sourceOffset: s.sourceOffset,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *fakeStackIterator) Parameters() []uint64 {
|
||||
return s.args
|
||||
}
|
||||
|
||||
func (s *fakeStackIterator) SourceOffset() uint64 {
|
||||
return s.offset
|
||||
func (s *fakeStackIterator) ProgramCounter() experimental.ProgramCounter {
|
||||
return experimental.ProgramCounter(s.pc)
|
||||
}
|
||||
|
||||
var _ experimental.StackIterator = &fakeStackIterator{}
|
||||
|
||||
type internalFunction struct {
|
||||
definition api.FunctionDefinition
|
||||
sourceOffset uint64
|
||||
}
|
||||
|
||||
func (f internalFunction) Definition() api.FunctionDefinition {
|
||||
return f.definition
|
||||
}
|
||||
|
||||
func (f internalFunction) SourceOffsetForPC(pc experimental.ProgramCounter) uint64 {
|
||||
return f.sourceOffset
|
||||
}
|
||||
|
||||
type mockFunctionDefinition struct {
|
||||
debugName string
|
||||
*wasm.FunctionDefinition
|
||||
|
||||
@@ -1116,7 +1116,7 @@ func (si *stackIterator) clear() {
|
||||
si.started = false
|
||||
}
|
||||
|
||||
// Next implements the same method as documtend on experimental.StackIterator.
|
||||
// Next implements the same method as documented on experimental.StackIterator.
|
||||
func (si *stackIterator) Next() bool {
|
||||
if !si.started {
|
||||
si.started = true
|
||||
@@ -1136,22 +1136,16 @@ func (si *stackIterator) Next() bool {
|
||||
return si.fn != nil
|
||||
}
|
||||
|
||||
// SourceOffset implements the same method as documented on
|
||||
// ProgramCounter implements the same method as documented on
|
||||
// experimental.StackIterator.
|
||||
func (si *stackIterator) SourceOffset() uint64 {
|
||||
p := si.fn.parent
|
||||
|
||||
if len(p.sourceOffsetMap.irOperationSourceOffsetsInWasmBinary) == 0 {
|
||||
return 0 // source not available
|
||||
}
|
||||
|
||||
return si.fn.getSourceOffsetInWasmBinary(si.pc)
|
||||
func (si *stackIterator) ProgramCounter() experimental.ProgramCounter {
|
||||
return experimental.ProgramCounter(si.pc)
|
||||
}
|
||||
|
||||
// FunctionDefinition implements the same method as documented on
|
||||
// Function implements the same method as documented on
|
||||
// experimental.StackIterator.
|
||||
func (si *stackIterator) FunctionDefinition() api.FunctionDefinition {
|
||||
return si.fn.definition()
|
||||
func (si *stackIterator) Function() experimental.InternalFunction {
|
||||
return internalFunction{si.fn}
|
||||
}
|
||||
|
||||
// Parameters implements the same method as documented on
|
||||
@@ -1160,6 +1154,25 @@ func (si *stackIterator) Parameters() []uint64 {
|
||||
return si.stack[si.base : si.base+si.fn.funcType.ParamNumInUint64]
|
||||
}
|
||||
|
||||
// internalFunction implements experimental.InternalFunction.
|
||||
type internalFunction struct{ *function }
|
||||
|
||||
// Definition implements the same method as documented on
|
||||
// experimental.InternalFunction.
|
||||
func (f internalFunction) Definition() api.FunctionDefinition {
|
||||
return f.definition()
|
||||
}
|
||||
|
||||
// SourceOffsetForPC implements the same method as documented on
|
||||
// experimental.InternalFunction.
|
||||
func (f internalFunction) SourceOffsetForPC(pc experimental.ProgramCounter) uint64 {
|
||||
p := f.parent
|
||||
if len(p.sourceOffsetMap.irOperationSourceOffsetsInWasmBinary) == 0 {
|
||||
return 0 // source not available
|
||||
}
|
||||
return f.getSourceOffsetInWasmBinary(uint64(pc))
|
||||
}
|
||||
|
||||
func (ce *callEngine) builtinFunctionFunctionListenerBefore(ctx context.Context, mod api.Module, fn *function) {
|
||||
base := int(ce.stackBasePointerInBytes >> 3)
|
||||
pc := uint64(ce.returnAddress)
|
||||
|
||||
@@ -577,7 +577,7 @@ type stackEntry struct {
|
||||
func assertStackIterator(t *testing.T, it experimental.StackIterator, expected []stackEntry) {
|
||||
var actual []stackEntry
|
||||
for it.Next() {
|
||||
actual = append(actual, stackEntry{def: it.FunctionDefinition(), args: it.Parameters()})
|
||||
actual = append(actual, stackEntry{def: it.Function().Definition(), args: it.Parameters()})
|
||||
}
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
@@ -259,21 +259,16 @@ func (si *stackIterator) Next() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// FunctionDefinition implements the same method as documented on
|
||||
// Function implements the same method as documented on
|
||||
// experimental.StackIterator.
|
||||
func (si *stackIterator) FunctionDefinition() api.FunctionDefinition {
|
||||
return si.fn.definition()
|
||||
func (si *stackIterator) Function() experimental.InternalFunction {
|
||||
return internalFunction{si.fn}
|
||||
}
|
||||
|
||||
// SourceOffset implements the same method as documented on
|
||||
// ProgramCounter implements the same method as documented on
|
||||
// experimental.StackIterator.
|
||||
func (si *stackIterator) SourceOffset() uint64 {
|
||||
offsetsMap := si.fn.parent.offsetsInWasmBinary
|
||||
pc := si.pc
|
||||
if pc < uint64(len(offsetsMap)) {
|
||||
return offsetsMap[pc]
|
||||
}
|
||||
return 0
|
||||
func (si *stackIterator) ProgramCounter() experimental.ProgramCounter {
|
||||
return experimental.ProgramCounter(si.pc)
|
||||
}
|
||||
|
||||
// Parameters implements the same method as documented on
|
||||
@@ -284,6 +279,25 @@ func (si *stackIterator) Parameters() []uint64 {
|
||||
return si.stack[top-paramsCount:]
|
||||
}
|
||||
|
||||
// internalFunction implements experimental.InternalFunction.
|
||||
type internalFunction struct{ *function }
|
||||
|
||||
// Definition implements the same method as documented on
|
||||
// experimental.InternalFunction.
|
||||
func (f internalFunction) Definition() api.FunctionDefinition {
|
||||
return f.definition()
|
||||
}
|
||||
|
||||
// SourceOffsetForPC implements the same method as documented on
|
||||
// experimental.InternalFunction.
|
||||
func (f internalFunction) SourceOffsetForPC(pc experimental.ProgramCounter) uint64 {
|
||||
offsetsMap := f.parent.offsetsInWasmBinary
|
||||
if uint64(pc) < uint64(len(offsetsMap)) {
|
||||
return offsetsMap[pc]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// interpreter mode doesn't maintain call frames in the stack, so pass the zero size to the IR.
|
||||
const callFrameStackSize = 0
|
||||
|
||||
|
||||
@@ -561,7 +561,7 @@ func RunTestModuleEngineBeforeListenerStackIterator(t *testing.T, et EngineTeste
|
||||
expectedCallstack := expectedCallstacks[0]
|
||||
for si.Next() {
|
||||
require.True(t, len(expectedCallstack) > 0)
|
||||
require.Equal(t, expectedCallstack[0].debugName, si.FunctionDefinition().DebugName())
|
||||
require.Equal(t, expectedCallstack[0].debugName, si.Function().Definition().DebugName())
|
||||
require.Equal(t, expectedCallstack[0].args, si.Parameters())
|
||||
expectedCallstack = expectedCallstack[1:]
|
||||
}
|
||||
@@ -838,7 +838,9 @@ func RunTestModuleEngineStackIteratorOffset(t *testing.T, et EngineTester) {
|
||||
beforeFn: func(ctx context.Context, mod api.Module, def api.FunctionDefinition, paramValues []uint64, si experimental.StackIterator) context.Context {
|
||||
var stack []frame
|
||||
for si.Next() {
|
||||
stack = append(stack, frame{si.FunctionDefinition(), si.SourceOffset()})
|
||||
fn := si.Function()
|
||||
pc := si.ProgramCounter()
|
||||
stack = append(stack, frame{fn.Definition(), fn.SourceOffsetForPC(pc)})
|
||||
}
|
||||
tape = append(tape, stack)
|
||||
return ctx
|
||||
|
||||
Reference in New Issue
Block a user