Removes ModuleInstance.TypeIDsIndex to reduce allocations (#897)
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
@@ -18,7 +18,6 @@ import (
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
"github.com/tetratelabs/wazero/internal/wasmruntime"
|
||||
)
|
||||
|
||||
// MustInstantiate calls Instantiate or panics on error.
|
||||
@@ -148,7 +147,7 @@ var invokeI = &wasm.HostFunc{
|
||||
}
|
||||
|
||||
func invokeIFn(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
ret, err := callDynamic(ctx, mod.(*wasm.CallContext), "v_i32", wasm.Index(stack[0]), nil)
|
||||
ret, err := callDynamic(ctx, mod.(*wasm.CallContext), wasm.PreAllocatedTypeID_v_i32, wasm.Index(stack[0]), nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -168,7 +167,7 @@ var invokeIi = &wasm.HostFunc{
|
||||
}
|
||||
|
||||
func invokeIiFn(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
ret, err := callDynamic(ctx, mod.(*wasm.CallContext), "i32_i32", wasm.Index(stack[0]), stack[1:])
|
||||
ret, err := callDynamic(ctx, mod.(*wasm.CallContext), wasm.PreAllocatedTypeID_i32_i32, wasm.Index(stack[0]), stack[1:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -188,7 +187,7 @@ var invokeIii = &wasm.HostFunc{
|
||||
}
|
||||
|
||||
func invokeIiiFn(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
ret, err := callDynamic(ctx, mod.(*wasm.CallContext), "i32i32_i32", wasm.Index(stack[0]), stack[1:])
|
||||
ret, err := callDynamic(ctx, mod.(*wasm.CallContext), wasm.PreAllocatedTypeID_i32i32_i32, wasm.Index(stack[0]), stack[1:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -208,7 +207,7 @@ var invokeIiii = &wasm.HostFunc{
|
||||
}
|
||||
|
||||
func invokeIiiiFn(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
ret, err := callDynamic(ctx, mod.(*wasm.CallContext), "i32i32i32_i32", wasm.Index(stack[0]), stack[1:])
|
||||
ret, err := callDynamic(ctx, mod.(*wasm.CallContext), wasm.PreAllocatedTypeID_i32i32i32_i32, wasm.Index(stack[0]), stack[1:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -228,7 +227,7 @@ var invokeIiiii = &wasm.HostFunc{
|
||||
}
|
||||
|
||||
func invokeIiiiiFn(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
ret, err := callDynamic(ctx, mod.(*wasm.CallContext), "i32i32i32i32_i32", wasm.Index(stack[0]), stack[1:])
|
||||
ret, err := callDynamic(ctx, mod.(*wasm.CallContext), wasm.PreAllocatedTypeID_i32i32i32i32_i32, wasm.Index(stack[0]), stack[1:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -248,7 +247,7 @@ var invokeV = &wasm.HostFunc{
|
||||
}
|
||||
|
||||
func invokeVFn(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_, err := callDynamic(ctx, mod.(*wasm.CallContext), "v_v", wasm.Index(stack[0]), nil)
|
||||
_, err := callDynamic(ctx, mod.(*wasm.CallContext), wasm.PreAllocatedTypeID_v_v, wasm.Index(stack[0]), nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -267,7 +266,7 @@ var invokeVi = &wasm.HostFunc{
|
||||
}
|
||||
|
||||
func invokeViFn(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_, err := callDynamic(ctx, mod.(*wasm.CallContext), "i32_v", wasm.Index(stack[0]), stack[1:])
|
||||
_, err := callDynamic(ctx, mod.(*wasm.CallContext), wasm.PreAllocatedTypeID_i32_v, wasm.Index(stack[0]), stack[1:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -286,7 +285,7 @@ var invokeVii = &wasm.HostFunc{
|
||||
}
|
||||
|
||||
func invokeViiFn(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_, err := callDynamic(ctx, mod.(*wasm.CallContext), "i32i32_v", wasm.Index(stack[0]), stack[1:])
|
||||
_, err := callDynamic(ctx, mod.(*wasm.CallContext), wasm.PreAllocatedTypeID_i32i32_v, wasm.Index(stack[0]), stack[1:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -305,7 +304,7 @@ var invokeViii = &wasm.HostFunc{
|
||||
}
|
||||
|
||||
func invokeViiiFn(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_, err := callDynamic(ctx, mod.(*wasm.CallContext), "i32i32i32_v", wasm.Index(stack[0]), stack[1:])
|
||||
_, err := callDynamic(ctx, mod.(*wasm.CallContext), wasm.PreAllocatedTypeID_i32i32i32_v, wasm.Index(stack[0]), stack[1:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -324,7 +323,7 @@ var invokeViiii = &wasm.HostFunc{
|
||||
}
|
||||
|
||||
func invokeViiiiFn(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
_, err := callDynamic(ctx, mod.(*wasm.CallContext), "i32i32i32i32_v", wasm.Index(stack[0]), stack[1:])
|
||||
_, err := callDynamic(ctx, mod.(*wasm.CallContext), wasm.PreAllocatedTypeID_i32i32i32i32_v, wasm.Index(stack[0]), stack[1:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -337,18 +336,14 @@ func invokeViiiiFn(ctx context.Context, mod api.Module, stack []uint64) {
|
||||
//
|
||||
// - ctx: the propagated go context.
|
||||
// - callCtx: the incoming context of the `invoke_` function.
|
||||
// - typeName: used to look up the function type. ex "i32i32_i32" or "v_i32"
|
||||
// - typeID: used to type check on indirect calls.
|
||||
// - tableOffset: position in the module's only table
|
||||
// - params: parameters to the funcref
|
||||
func callDynamic(ctx context.Context, callCtx *wasm.CallContext, typeName string, tableOffset wasm.Index, params []uint64) (results []uint64, err error) {
|
||||
func callDynamic(ctx context.Context, callCtx *wasm.CallContext, typeID wasm.FunctionTypeID, tableOffset wasm.Index, params []uint64) (results []uint64, err error) {
|
||||
m := callCtx.Module()
|
||||
typeId, ok := m.TypeIDIndex[typeName]
|
||||
if !ok {
|
||||
return nil, wasmruntime.ErrRuntimeIndirectCallTypeMismatch
|
||||
}
|
||||
|
||||
t := m.Tables[0] // Emscripten doesn't use multiple tables
|
||||
idx, err := m.Engine.LookupFunction(t, typeId, tableOffset)
|
||||
idx, err := m.Engine.LookupFunction(t, typeID, tableOffset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -358,8 +358,8 @@ const (
|
||||
moduleInstanceTablesOffset = 80
|
||||
moduleInstanceEngineOffset = 112
|
||||
moduleInstanceTypeIDsOffset = 128
|
||||
moduleInstanceDataInstancesOffset = 160
|
||||
moduleInstanceElementInstancesOffset = 184
|
||||
moduleInstanceDataInstancesOffset = 152
|
||||
moduleInstanceElementInstancesOffset = 176
|
||||
|
||||
// Offsets for wasm.TableInstance.
|
||||
tableInstanceTableOffset = 0
|
||||
|
||||
@@ -69,8 +69,7 @@ type (
|
||||
|
||||
// TypeIDs is index-correlated with types and holds typeIDs which is uniquely assigned to a type by store.
|
||||
// This is necessary to achieve fast runtime type checking for indirect function calls at runtime.
|
||||
TypeIDs []FunctionTypeID
|
||||
TypeIDIndex map[string]FunctionTypeID
|
||||
TypeIDs []FunctionTypeID
|
||||
|
||||
// DataInstances holds data segments bytes of the module.
|
||||
// This is only used by bulk memory operations.
|
||||
@@ -157,13 +156,7 @@ type (
|
||||
const maximumFunctionTypes = 1 << 27
|
||||
|
||||
// addSections adds section elements to the ModuleInstance
|
||||
func (m *ModuleInstance) addSections(module *Module, importedGlobals, globals []*GlobalInstance, tables []*TableInstance, memory, importedMemory *MemoryInstance,
|
||||
types []*FunctionType,
|
||||
) {
|
||||
m.TypeIDIndex = make(map[string]FunctionTypeID, len(types))
|
||||
for i, t := range types {
|
||||
m.TypeIDIndex[t.string] = m.TypeIDs[i]
|
||||
}
|
||||
func (m *ModuleInstance) addSections(module *Module, importedGlobals, globals []*GlobalInstance, tables []*TableInstance, memory, importedMemory *MemoryInstance) {
|
||||
m.Globals = append(importedGlobals, globals...)
|
||||
m.Tables = tables
|
||||
|
||||
@@ -272,11 +265,16 @@ func (m *ModuleInstance) getExport(name string, et ExternType) (ExportInstance,
|
||||
|
||||
func NewStore(enabledFeatures api.CoreFeatures, engine Engine) (*Store, *Namespace) {
|
||||
ns := newNamespace()
|
||||
|
||||
typeIDs := make(map[string]FunctionTypeID, len(preAllocatedTypeIDs))
|
||||
for k, v := range preAllocatedTypeIDs {
|
||||
typeIDs[k] = v
|
||||
}
|
||||
return &Store{
|
||||
EnabledFeatures: enabledFeatures,
|
||||
Engine: engine,
|
||||
namespaces: []*Namespace{ns},
|
||||
typeIDs: map[string]FunctionTypeID{},
|
||||
typeIDs: typeIDs,
|
||||
functionMaxTypes: maximumFunctionTypes,
|
||||
}, ns
|
||||
}
|
||||
@@ -371,7 +369,7 @@ func (s *Store) instantiate(
|
||||
globals, memory := module.buildGlobals(importedGlobals, m.Engine.FunctionInstanceReference), module.buildMemory()
|
||||
|
||||
// Now we have all instances from imports and local ones, so ready to create a new ModuleInstance.
|
||||
m.addSections(module, importedGlobals, globals, tables, importedMemory, memory, module.TypeSection)
|
||||
m.addSections(module, importedGlobals, globals, tables, importedMemory, memory)
|
||||
|
||||
// As of reference types proposal, data segment validation must happen after instantiation,
|
||||
// and the side effect must persist even if there's out of bounds error after instantiation.
|
||||
@@ -590,6 +588,45 @@ func (s *Store) getFunctionTypeIDs(ts []*FunctionType) ([]FunctionTypeID, error)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// preAllocatedTypeIDs maps several "well-known" FunctionType strings to the pre allocated FunctionID.
|
||||
// This is used by emscripten integration, but it is harmless to have this all the time as it's only
|
||||
// used during Store creation.
|
||||
var preAllocatedTypeIDs = map[string]FunctionTypeID{
|
||||
"i32i32i32i32_v": PreAllocatedTypeID_i32i32i32i32_v,
|
||||
"i32i32i32_v": PreAllocatedTypeID_i32i32i32_v,
|
||||
"i32i32_v": PreAllocatedTypeID_i32i32_v,
|
||||
"i32_v": PreAllocatedTypeID_i32_v,
|
||||
"v_v": PreAllocatedTypeID_v_v,
|
||||
"i32i32i32i32_i32": PreAllocatedTypeID_i32i32i32i32_i32,
|
||||
"i32i32i32_i32": PreAllocatedTypeID_i32i32i32_i32,
|
||||
"i32i32_i32": PreAllocatedTypeID_i32i32_i32,
|
||||
"i32_i32": PreAllocatedTypeID_i32_i32,
|
||||
"v_i32": PreAllocatedTypeID_v_i32,
|
||||
}
|
||||
|
||||
const (
|
||||
// PreAllocatedTypeID_i32i32i32i32_v is FunctionTypeID for i32i32i32i32_v.
|
||||
PreAllocatedTypeID_i32i32i32i32_v FunctionTypeID = iota
|
||||
// PreAllocatedTypeID_i32i32i32_v is FunctionTypeID for i32i32i32_v
|
||||
PreAllocatedTypeID_i32i32i32_v
|
||||
// PreAllocatedTypeID_i32i32_v is FunctionTypeID for i32i32_v
|
||||
PreAllocatedTypeID_i32i32_v
|
||||
// PreAllocatedTypeID_i32_v is FunctionTypeID for i32_v
|
||||
PreAllocatedTypeID_i32_v
|
||||
// PreAllocatedTypeID_v_v is FunctionTypeID for v_v
|
||||
PreAllocatedTypeID_v_v
|
||||
// PreAllocatedTypeID_i32i32i32i32_i32 is FunctionTypeID for i32i32i32i32_i32
|
||||
PreAllocatedTypeID_i32i32i32i32_i32
|
||||
// PreAllocatedTypeID_i32i32i32_i32 is FunctionTypeID for i32i32i32_i32
|
||||
PreAllocatedTypeID_i32i32i32_i32
|
||||
// PreAllocatedTypeID_i32i32_i32 is FunctionTypeID for i32i32_i32
|
||||
PreAllocatedTypeID_i32i32_i32
|
||||
// PreAllocatedTypeID_i32_i32 is FunctionTypeID for i32_i32
|
||||
PreAllocatedTypeID_i32_i32
|
||||
// PreAllocatedTypeID_v_i32 is FunctionTypeID for v_i32
|
||||
PreAllocatedTypeID_v_i32
|
||||
)
|
||||
|
||||
func (s *Store) getFunctionTypeID(t *FunctionType) (FunctionTypeID, error) {
|
||||
key := t.key()
|
||||
s.mux.RLock()
|
||||
|
||||
@@ -90,6 +90,16 @@ func TestModuleInstance_Memory(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewStore(t *testing.T) {
|
||||
s, _ := NewStore(api.CoreFeaturesV1, &mockEngine{shouldCompileFail: false, callFailIndex: -1})
|
||||
// Ensures that a newly created store has the pre allocated type IDs.
|
||||
for k, v := range preAllocatedTypeIDs {
|
||||
actual, ok := s.typeIDs[k]
|
||||
require.True(t, ok)
|
||||
require.Equal(t, v, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore_Instantiate(t *testing.T) {
|
||||
s, ns := newStore()
|
||||
m, err := NewHostModule("", map[string]interface{}{"fn": func() {}}, map[string]*HostFuncNames{"fn": {}}, api.CoreFeaturesV1)
|
||||
@@ -910,3 +920,13 @@ func TestModuleInstance_applyTableInits(t *testing.T) {
|
||||
tables[0].References)
|
||||
})
|
||||
}
|
||||
|
||||
// TestPreAllocatedTypeIDs ensures that PreAllocatedTypeIDs has no duplication on the values (FunctionTypeID).
|
||||
func TestPreAllocatedTypeIDs(t *testing.T) {
|
||||
exists := make(map[FunctionTypeID]struct{}, len(preAllocatedTypeIDs))
|
||||
for _, v := range preAllocatedTypeIDs {
|
||||
_, ok := exists[v]
|
||||
require.False(t, ok)
|
||||
exists[v] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user