Adds LookupFunction in experimental/table package (#1637)

This commit is contained in:
Takeshi Yoneda
2023-08-20 08:10:10 +09:00
committed by GitHub
parent 78b4581b51
commit 4709f04be3
4 changed files with 135 additions and 7 deletions

View 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)
}

View 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())
})
}

View File

@@ -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 {

View File

@@ -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) {