experimental: split program counter and source offset resolution (#1448)

Signed-off-by: Thomas Pelletier <thomas@pelletier.codes>
This commit is contained in:
Thomas Pelletier
2023-05-09 16:34:01 -04:00
committed by GitHub
parent 509827a23f
commit ef3d671195
7 changed files with 146 additions and 60 deletions
+19
View File
@@ -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
View File
@@ -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 {
+39 -13
View File
@@ -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
+26 -13
View File
@@ -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)
+1 -1
View File
@@ -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)
}
+25 -11
View File
@@ -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
+4 -2
View File
@@ -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