Adds LookupFunction in experimental/table package (#1637)
This commit is contained in:
33
experimental/table/lookup.go
Normal file
33
experimental/table/lookup.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
// LookupFunction tries to get an api.Function from the table instance specified by `tableIndex` and `tableOffset` in the
|
||||
// given api.Module. The user of this function must be well aware of the structure of the given api.Module,
|
||||
// and the offset and table index must be valid. If this fails to find it, e.g. table is not found,
|
||||
// table offset is out of range, violates the expected types, this panics according to the same semantics as
|
||||
// call_indirect instruction: https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/instructions.html#xref-syntax-instructions-syntax-instr-control-mathsf-call-indirect-x-y
|
||||
//
|
||||
// - `module` is a module instance to look up the function.
|
||||
// - `tableIndex` is the index of the table instance in the module.
|
||||
// - `tableOffset` is the offset of the lookup target in the table.
|
||||
// - `expectedParamTypes` and `expectedResultTypes` are used to check the type of the function found in the table.
|
||||
//
|
||||
// Note: the returned api.Function is always valid, i.e. not nil, if this returns without panic.
|
||||
func LookupFunction(
|
||||
module api.Module, tableIndex uint32, tableOffset uint32,
|
||||
expectedParamTypes, expectedResultTypes []api.ValueType,
|
||||
) api.Function {
|
||||
m := module.(*wasm.ModuleInstance)
|
||||
typ := &wasm.FunctionType{Params: expectedParamTypes, Results: expectedResultTypes}
|
||||
typ.CacheNumInUint64()
|
||||
typeID := m.GetFunctionTypeID(typ)
|
||||
if int(tableIndex) >= len(m.Tables) {
|
||||
panic("table index out of range")
|
||||
}
|
||||
table := m.Tables[tableIndex]
|
||||
return m.LookupFunction(table, typeID, tableOffset)
|
||||
}
|
||||
92
experimental/table/lookup_test.go
Normal file
92
experimental/table/lookup_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package table_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental/table"
|
||||
"github.com/tetratelabs/wazero/internal/testing/binaryencoding"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
)
|
||||
|
||||
func TestLookupFunction(t *testing.T) {
|
||||
const i32 = wasm.ValueTypeI32
|
||||
bytes := binaryencoding.EncodeModule(&wasm.Module{
|
||||
TypeSection: []wasm.FunctionType{
|
||||
{Results: []wasm.ValueType{i32}},
|
||||
{Params: []wasm.ValueType{i32, i32}, Results: []wasm.ValueType{i32, i32}},
|
||||
},
|
||||
FunctionSection: []wasm.Index{0, 1},
|
||||
CodeSection: []wasm.Code{
|
||||
{Body: []byte{
|
||||
wasm.OpcodeI32Const, 1,
|
||||
wasm.OpcodeEnd,
|
||||
}},
|
||||
{Body: []byte{
|
||||
// Swap the two i32s params.
|
||||
wasm.OpcodeLocalGet, 1,
|
||||
wasm.OpcodeLocalGet, 0,
|
||||
wasm.OpcodeEnd,
|
||||
}},
|
||||
},
|
||||
ElementSection: []wasm.ElementSegment{
|
||||
{
|
||||
OffsetExpr: wasm.ConstantExpression{Opcode: wasm.OpcodeI32Const, Data: []byte{0}},
|
||||
Init: []wasm.Index{0, 1},
|
||||
Type: wasm.RefTypeFuncref,
|
||||
},
|
||||
},
|
||||
TableSection: []wasm.Table{{Type: wasm.RefTypeFuncref, Min: 100}},
|
||||
})
|
||||
|
||||
r := wazero.NewRuntime(context.Background())
|
||||
m, err := r.Instantiate(context.Background(), bytes)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, m)
|
||||
|
||||
t.Run("v_i32", func(t *testing.T) {
|
||||
f := table.LookupFunction(m, 0, 0, nil, []api.ValueType{i32})
|
||||
var result [1]uint64
|
||||
err = f.CallWithStack(context.Background(), result[:])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), result[0])
|
||||
})
|
||||
t.Run("i32i32_i32i32", func(t *testing.T) {
|
||||
f := table.LookupFunction(m, 0, 1, []api.ValueType{i32, i32}, []api.ValueType{i32, i32})
|
||||
stack := [2]uint64{100, 200}
|
||||
err = f.CallWithStack(context.Background(), stack[:])
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(200), stack[0])
|
||||
require.Equal(t, uint64(100), stack[1])
|
||||
})
|
||||
|
||||
t.Run("panics", func(t *testing.T) {
|
||||
err := require.CapturePanic(func() {
|
||||
table.LookupFunction(m, 0, 2000, nil, []api.ValueType{i32})
|
||||
})
|
||||
require.Equal(t, "invalid table access", err.Error())
|
||||
err = require.CapturePanic(func() {
|
||||
table.LookupFunction(m, 1000, 0, nil, []api.ValueType{i32})
|
||||
})
|
||||
require.Equal(t, "table index out of range", err.Error())
|
||||
err = require.CapturePanic(func() {
|
||||
table.LookupFunction(m, 0, 0, nil, []api.ValueType{api.ValueTypeF32})
|
||||
})
|
||||
require.Equal(t, "indirect call type mismatch", err.Error())
|
||||
err = require.CapturePanic(func() {
|
||||
table.LookupFunction(m, 0, 0, []api.ValueType{i32}, nil)
|
||||
})
|
||||
require.Equal(t, "indirect call type mismatch", err.Error())
|
||||
err = require.CapturePanic(func() {
|
||||
table.LookupFunction(m, 0, 1, []api.ValueType{i32, i32}, nil)
|
||||
})
|
||||
require.Equal(t, "indirect call type mismatch", err.Error())
|
||||
err = require.CapturePanic(func() {
|
||||
table.LookupFunction(m, 0, 1, []api.ValueType{i32, i32}, []api.ValueType{i32, api.ValueTypeF32})
|
||||
})
|
||||
require.Equal(t, "indirect call type mismatch", err.Error())
|
||||
})
|
||||
}
|
||||
@@ -93,10 +93,7 @@ func (v *InvokeFunc) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
m := mod.(*wasm.ModuleInstance)
|
||||
|
||||
// Lookup the type of the function we are calling indirectly.
|
||||
typeID, err := m.GetFunctionTypeID(v.FunctionType)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
typeID := m.GetFunctionTypeID(v.FunctionType)
|
||||
|
||||
// This needs copy (not reslice) because the stack is reused for results.
|
||||
// Consider invoke_i (zero arguments, one result): index zero (tableOffset)
|
||||
@@ -129,7 +126,7 @@ func (v *InvokeFunc) Call(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
var savedStack [2]uint64
|
||||
callOrPanic(ctx, mod, "stackSave", savedStack[:])
|
||||
|
||||
err = f.CallWithStack(ctx, stack)
|
||||
err := f.CallWithStack(ctx, stack)
|
||||
if err != nil {
|
||||
// Module closed: any calls will just fail with the same error.
|
||||
if _, ok := err.(*sys.ExitError); ok {
|
||||
|
||||
@@ -153,8 +153,14 @@ type (
|
||||
const maximumFunctionTypes = 1 << 27
|
||||
|
||||
// GetFunctionTypeID is used by emscripten.
|
||||
func (m *ModuleInstance) GetFunctionTypeID(t *FunctionType) (FunctionTypeID, error) {
|
||||
return m.s.GetFunctionTypeID(t)
|
||||
func (m *ModuleInstance) GetFunctionTypeID(t *FunctionType) FunctionTypeID {
|
||||
id, err := m.s.GetFunctionTypeID(t)
|
||||
if err != nil {
|
||||
// This is not recoverable in practice since the only error GetFunctionTypeID returns is
|
||||
// when there's too many function types in the store.
|
||||
panic(err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func (m *ModuleInstance) buildElementInstances(elements []ElementSegment) {
|
||||
|
||||
Reference in New Issue
Block a user