Avoides allocation with constExpr execution (#1257)
Previously executConstExpr is called various places, and therefore it was necessary to return interface{}
and that resulted in allocation per call.
This commit avoids the allocation by adding executConstExprI32 which is used by data segment manipulation,
and repurposes executConstExpr solely to global instance initialization.
As a result, this completely removes the allocation around const expr execution, and hence the perf improvement
in the instantiation phrase. The improvement diff is proportionate to the number of data segments and globals.
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
@@ -591,26 +591,7 @@ func (m *Module) buildGlobals(importedGlobals []*GlobalInstance, funcRefResolver
|
||||
for i := range m.GlobalSection {
|
||||
gs := &m.GlobalSection[i]
|
||||
g := &GlobalInstance{Type: gs.Type}
|
||||
switch v := executeConstExpression(importedGlobals, &gs.Init).(type) {
|
||||
case uint32:
|
||||
if gs.Type.ValType == ValueTypeFuncref {
|
||||
g.Val = uint64(funcRefResolver(v))
|
||||
} else {
|
||||
g.Val = uint64(v)
|
||||
}
|
||||
case int32:
|
||||
g.Val = uint64(uint32(v))
|
||||
case int64:
|
||||
g.Val = uint64(v)
|
||||
case float32:
|
||||
g.Val = api.EncodeF32(v)
|
||||
case float64:
|
||||
g.Val = api.EncodeF64(v)
|
||||
case [2]uint64:
|
||||
g.Val, g.ValHi = v[0], v[1]
|
||||
default:
|
||||
panic(fmt.Errorf("BUG: invalid conversion %d", v))
|
||||
}
|
||||
g.initialize(importedGlobals, &gs.Init, funcRefResolver)
|
||||
globals[i] = g
|
||||
}
|
||||
return
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/internal/ieee754"
|
||||
"github.com/tetratelabs/wazero/internal/leb128"
|
||||
internalsys "github.com/tetratelabs/wazero/internal/sys"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
@@ -220,7 +219,7 @@ func (m *ModuleInstance) validateData(data []DataSegment) (err error) {
|
||||
for i := range data {
|
||||
d := &data[i]
|
||||
if !d.IsPassive() {
|
||||
offset := int(executeConstExpression(m.Globals, &d.OffsetExpression).(int32))
|
||||
offset := int(executeConstExpressionI32(m.Globals, &d.OffsetExpression))
|
||||
ceil := offset + len(d.Init)
|
||||
if offset < 0 || ceil > len(m.Memory.Buffer) {
|
||||
return fmt.Errorf("%s[%d]: out of bounds memory access", SectionIDName(SectionIDData), i)
|
||||
@@ -239,7 +238,7 @@ func (m *ModuleInstance) applyData(data []DataSegment) error {
|
||||
d := &data[i]
|
||||
m.DataInstances[i] = d.Init
|
||||
if !d.IsPassive() {
|
||||
offset := executeConstExpression(m.Globals, &d.OffsetExpression).(int32)
|
||||
offset := executeConstExpressionI32(m.Globals, &d.OffsetExpression)
|
||||
if offset < 0 || int(offset)+len(d.Init) > len(m.Memory.Buffer) {
|
||||
return fmt.Errorf("%s[%d]: out of bounds memory access", SectionIDName(SectionIDData), i)
|
||||
}
|
||||
@@ -516,51 +515,68 @@ func errorInvalidImport(i *Import, idx int, err error) error {
|
||||
return fmt.Errorf("import[%d] %s[%s.%s]: %w", idx, ExternTypeName(i.Type), i.Module, i.Name, err)
|
||||
}
|
||||
|
||||
// Global initialization constant expression can only reference the imported globals.
|
||||
// See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0
|
||||
func executeConstExpression(importedGlobals []*GlobalInstance, expr *ConstantExpression) (v interface{}) {
|
||||
// executeConstExpressionI32 executes the ConstantExpression which returns ValueTypeI32.
|
||||
// The validity of the expression is ensured when calling this function as this is only called
|
||||
// during instantiation phrase, and the validation happens in compilation (validateConstExpression).
|
||||
func executeConstExpressionI32(importedGlobals []*GlobalInstance, expr *ConstantExpression) (ret int32) {
|
||||
switch expr.Opcode {
|
||||
case OpcodeI32Const:
|
||||
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
||||
v, _, _ = leb128.LoadInt32(expr.Data)
|
||||
case OpcodeI64Const:
|
||||
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
||||
v, _, _ = leb128.LoadInt64(expr.Data)
|
||||
case OpcodeF32Const:
|
||||
v, _ = ieee754.DecodeFloat32(expr.Data)
|
||||
case OpcodeF64Const:
|
||||
v, _ = ieee754.DecodeFloat64(expr.Data)
|
||||
ret, _, _ = leb128.LoadInt32(expr.Data)
|
||||
case OpcodeGlobalGet:
|
||||
id, _, _ := leb128.LoadUint32(expr.Data)
|
||||
g := importedGlobals[id]
|
||||
switch g.Type.ValType {
|
||||
ret = int32(g.Val)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// initialize initializes the value of this global instance given the const expr and imported globals.
|
||||
// funcRefResolver is called to get the actual funcref (engine specific) from the OpcodeRefFunc const expr.
|
||||
//
|
||||
// Global initialization constant expression can only reference the imported globals.
|
||||
// See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0
|
||||
func (g *GlobalInstance) initialize(importedGlobals []*GlobalInstance, expr *ConstantExpression, funcRefResolver func(funcIndex Index) Reference) {
|
||||
switch expr.Opcode {
|
||||
case OpcodeI32Const:
|
||||
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
||||
v, _, _ := leb128.LoadInt32(expr.Data)
|
||||
g.Val = uint64(uint32(v))
|
||||
case OpcodeI64Const:
|
||||
// Treat constants as signed as their interpretation is not yet known per /RATIONALE.md
|
||||
v, _, _ := leb128.LoadInt64(expr.Data)
|
||||
g.Val = uint64(v)
|
||||
case OpcodeF32Const:
|
||||
g.Val = uint64(binary.LittleEndian.Uint32(expr.Data))
|
||||
case OpcodeF64Const:
|
||||
g.Val = binary.LittleEndian.Uint64(expr.Data)
|
||||
case OpcodeGlobalGet:
|
||||
id, _, _ := leb128.LoadUint32(expr.Data)
|
||||
importedG := importedGlobals[id]
|
||||
switch importedG.Type.ValType {
|
||||
case ValueTypeI32:
|
||||
v = int32(g.Val)
|
||||
g.Val = uint64(uint32(importedG.Val))
|
||||
case ValueTypeI64:
|
||||
v = int64(g.Val)
|
||||
g.Val = importedG.Val
|
||||
case ValueTypeF32:
|
||||
v = api.DecodeF32(g.Val)
|
||||
g.Val = importedG.Val
|
||||
case ValueTypeF64:
|
||||
v = api.DecodeF64(g.Val)
|
||||
g.Val = importedG.Val
|
||||
case ValueTypeV128:
|
||||
v = [2]uint64{g.Val, g.ValHi}
|
||||
g.Val, g.ValHi = importedG.Val, importedG.ValHi
|
||||
case ValueTypeFuncref, ValueTypeExternref:
|
||||
v = int64(g.Val)
|
||||
g.Val = importedG.Val
|
||||
}
|
||||
case OpcodeRefNull:
|
||||
switch expr.Data[0] {
|
||||
case ValueTypeExternref, ValueTypeFuncref:
|
||||
v = int64(0) // Reference types are opaque 64bit pointer at runtime.
|
||||
g.Val = 0 // Reference types are opaque 64bit pointer at runtime.
|
||||
}
|
||||
case OpcodeRefFunc:
|
||||
// For ref.func const expression, we temporarily store the index as value,
|
||||
// and if this is the const expr for global, the value will be further downed to
|
||||
// opaque pointer of the engine-specific compiled function.
|
||||
v, _, _ = leb128.LoadUint32(expr.Data)
|
||||
v, _, _ := leb128.LoadUint32(expr.Data)
|
||||
g.Val = uint64(funcRefResolver(v))
|
||||
case OpcodeVecV128Const:
|
||||
v = [2]uint64{binary.LittleEndian.Uint64(expr.Data[0:8]), binary.LittleEndian.Uint64(expr.Data[8:16])}
|
||||
g.Val, g.ValHi = binary.LittleEndian.Uint64(expr.Data[0:8]), binary.LittleEndian.Uint64(expr.Data[8:16])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Store) GetFunctionTypeIDs(ts []FunctionType) ([]FunctionTypeID, error) {
|
||||
|
||||
@@ -501,10 +501,11 @@ func TestStore_getFunctionTypeID(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestExecuteConstExpression(t *testing.T) {
|
||||
func TestGlobalInstance_initialize(t *testing.T) {
|
||||
t.Run("basic type const expr", func(t *testing.T) {
|
||||
for _, vt := range []ValueType{ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64} {
|
||||
t.Run(ValueTypeName(vt), func(t *testing.T) {
|
||||
g := &GlobalInstance{Type: GlobalType{ValType: vt}}
|
||||
expr := &ConstantExpression{}
|
||||
switch vt {
|
||||
case ValueTypeI32:
|
||||
@@ -521,35 +522,25 @@ func TestExecuteConstExpression(t *testing.T) {
|
||||
expr.Opcode = OpcodeF64Const
|
||||
}
|
||||
|
||||
raw := executeConstExpression(nil, expr)
|
||||
require.NotNil(t, raw)
|
||||
g.initialize(nil, expr, nil)
|
||||
|
||||
switch vt {
|
||||
case ValueTypeI32:
|
||||
actual, ok := raw.(int32)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, int32(1), actual)
|
||||
require.Equal(t, int32(1), int32(g.Val))
|
||||
case ValueTypeI64:
|
||||
actual, ok := raw.(int64)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, int64(2), actual)
|
||||
require.Equal(t, int64(2), int64(g.Val))
|
||||
case ValueTypeF32:
|
||||
actual, ok := raw.(float32)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, float32(math.MaxFloat32), actual)
|
||||
require.Equal(t, float32(math.MaxFloat32), math.Float32frombits(uint32(g.Val)))
|
||||
case ValueTypeF64:
|
||||
actual, ok := raw.(float64)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, float64(math.MaxFloat64), actual)
|
||||
require.Equal(t, math.MaxFloat64, math.Float64frombits(g.Val))
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("reference types", func(t *testing.T) {
|
||||
t.Run("ref.null", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
expr *ConstantExpression
|
||||
exp interface{}
|
||||
}{
|
||||
{
|
||||
name: "ref.null (externref)",
|
||||
@@ -557,7 +548,6 @@ func TestExecuteConstExpression(t *testing.T) {
|
||||
Opcode: OpcodeRefNull,
|
||||
Data: []byte{RefTypeExternref},
|
||||
},
|
||||
exp: int64(0),
|
||||
},
|
||||
{
|
||||
name: "ref.null (funcref)",
|
||||
@@ -565,34 +555,30 @@ func TestExecuteConstExpression(t *testing.T) {
|
||||
Opcode: OpcodeRefNull,
|
||||
Data: []byte{RefTypeFuncref},
|
||||
},
|
||||
exp: int64(0),
|
||||
},
|
||||
{
|
||||
name: "ref.func",
|
||||
expr: &ConstantExpression{
|
||||
Opcode: OpcodeRefFunc,
|
||||
Data: []byte{1},
|
||||
},
|
||||
exp: uint32(1),
|
||||
},
|
||||
{
|
||||
name: "ref.func",
|
||||
expr: &ConstantExpression{
|
||||
Opcode: OpcodeRefFunc,
|
||||
Data: []byte{0x5d},
|
||||
},
|
||||
exp: uint32(93),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
val := executeConstExpression(nil, tc.expr)
|
||||
require.Equal(t, tc.exp, val)
|
||||
g := GlobalInstance{}
|
||||
g.Type.ValType = tc.expr.Data[0]
|
||||
g.initialize(nil, tc.expr, nil)
|
||||
require.Equal(t, uint64(0), g.Val)
|
||||
})
|
||||
}
|
||||
})
|
||||
t.Run("ref.func", func(t *testing.T) {
|
||||
g := GlobalInstance{Type: GlobalType{ValType: RefTypeFuncref}}
|
||||
g.initialize(nil,
|
||||
&ConstantExpression{Opcode: OpcodeRefFunc, Data: []byte{1}},
|
||||
func(funcIndex Index) Reference {
|
||||
require.Equal(t, Index(1), funcIndex)
|
||||
return 0xdeadbeaf
|
||||
},
|
||||
)
|
||||
require.Equal(t, uint64(0xdeadbeaf), g.Val)
|
||||
})
|
||||
t.Run("global expr", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
valueType ValueType
|
||||
@@ -614,35 +600,23 @@ func TestExecuteConstExpression(t *testing.T) {
|
||||
expr := &ConstantExpression{Data: []byte{0}, Opcode: OpcodeGlobalGet}
|
||||
globals := []*GlobalInstance{{Val: tc.val, ValHi: tc.valHi, Type: GlobalType{ValType: tc.valueType}}}
|
||||
|
||||
val := executeConstExpression(globals, expr)
|
||||
require.NotNil(t, val)
|
||||
g := &GlobalInstance{Type: GlobalType{ValType: tc.valueType}}
|
||||
g.initialize(globals, expr, nil)
|
||||
|
||||
switch tc.valueType {
|
||||
case ValueTypeI32:
|
||||
actual, ok := val.(int32)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, int32(tc.val), actual)
|
||||
require.Equal(t, int32(tc.val), int32(g.Val))
|
||||
case ValueTypeI64:
|
||||
actual, ok := val.(int64)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, int64(tc.val), actual)
|
||||
require.Equal(t, int64(tc.val), int64(g.Val))
|
||||
case ValueTypeF32:
|
||||
actual, ok := val.(float32)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, api.DecodeF32(tc.val), actual)
|
||||
require.Equal(t, tc.val, g.Val)
|
||||
case ValueTypeF64:
|
||||
actual, ok := val.(float64)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, api.DecodeF64(tc.val), actual)
|
||||
require.Equal(t, tc.val, g.Val)
|
||||
case ValueTypeV128:
|
||||
vector, ok := val.([2]uint64)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, uint64(0x1), vector[0])
|
||||
require.Equal(t, uint64(0x2), vector[1])
|
||||
require.Equal(t, uint64(0x1), g.Val)
|
||||
require.Equal(t, uint64(0x2), g.ValHi)
|
||||
case ValueTypeFuncref, ValueTypeExternref:
|
||||
actual, ok := val.(int64)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, int64(tc.val), actual)
|
||||
require.Equal(t, tc.val, g.Val)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -653,12 +627,10 @@ func TestExecuteConstExpression(t *testing.T) {
|
||||
1, 0, 0, 0, 0, 0, 0, 0,
|
||||
2, 0, 0, 0, 0, 0, 0, 0,
|
||||
}, Opcode: OpcodeVecV128Const}
|
||||
val := executeConstExpression(nil, expr)
|
||||
require.NotNil(t, val)
|
||||
vector, ok := val.([2]uint64)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, uint64(0x1), vector[0])
|
||||
require.Equal(t, uint64(0x2), vector[1])
|
||||
g := GlobalInstance{Type: GlobalType{ValType: ValueTypeV128}}
|
||||
g.initialize(nil, expr, nil)
|
||||
require.Equal(t, uint64(0x1), g.Val)
|
||||
require.Equal(t, uint64(0x2), g.ValHi)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user