wazeroir: reuses allocated slices for a module (#1342)

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
This commit is contained in:
Takeshi Yoneda
2023-04-05 04:26:44 -07:00
committed by GitHub
parent 0dc152d672
commit cc28399052
29 changed files with 631 additions and 725 deletions

View File

@@ -172,11 +172,12 @@ type callFrame struct {
}
type code struct {
source *wasm.Module
body []*wazeroir.UnionOperation
listener experimental.FunctionListener
hostFn interface{}
ensureTermination bool
source *wasm.Module
body []wazeroir.UnionOperation
listener experimental.FunctionListener
offsetsInWasmBinary []uint64
hostFn interface{}
ensureTermination bool
}
type function struct {
@@ -204,17 +205,17 @@ func functionFromUintptr(ptr uintptr) *function {
const callFrameStackSize = 0
// CompileModule implements the same method as documented on wasm.Engine.
func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, listeners []experimental.FunctionListener, ensureTermination bool) error {
func (e *engine) CompileModule(_ context.Context, module *wasm.Module, listeners []experimental.FunctionListener, ensureTermination bool) error {
if _, ok := e.getCodes(module); ok { // cache hit!
return nil
}
funcs := make([]*code, len(module.FunctionSection))
irs, err := wazeroir.CompileFunctions(e.enabledFeatures, callFrameStackSize, module, ensureTermination)
irCompiler, err := wazeroir.NewCompiler(e.enabledFeatures, callFrameStackSize, module, ensureTermination)
if err != nil {
return err
}
for i, ir := range irs {
for i := range module.CodeSection {
var lsn experimental.FunctionListener
if i < len(listeners) {
lsn = listeners[i]
@@ -224,9 +225,13 @@ func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, listene
// host function in interpreter is its Go function itself as opposed to Wasm functions,
// which need to be compiled down to wazeroir.
var compiled *code
if ir.GoFunc != nil {
compiled = &code{hostFn: ir.GoFunc, listener: lsn}
if codeSeg := &module.CodeSection[i]; codeSeg.GoFunc != nil {
compiled = &code{hostFn: codeSeg.GoFunc, listener: lsn}
} else {
ir, err := irCompiler.Next()
if err != nil {
return err
}
compiled, err = e.lowerIR(ir)
if err != nil {
def := module.FunctionDefinitionSection[uint32(i)+module.ImportFunctionCount]
@@ -235,7 +240,7 @@ func (e *engine) CompileModule(ctx context.Context, module *wasm.Module, listene
compiled.listener = lsn
}
compiled.source = module
compiled.ensureTermination = ir.EnsureTermination
compiled.ensureTermination = ensureTermination
funcs[i] = compiled
}
e.addCodes(module, funcs)
@@ -271,21 +276,24 @@ func (e *engine) NewModuleEngine(module *wasm.Module, instance *wasm.ModuleInsta
// lowerIR lowers the wazeroir operations to engine friendly struct.
func (e *engine) lowerIR(ir *wazeroir.CompilationResult) (*code, error) {
hasSourcePCs := len(ir.IROperationSourceOffsetsInWasmBinary) > 0
ops := ir.Operations
ret := &code{}
// Copy the body from the result.
ret := &code{body: make([]wazeroir.UnionOperation, len(ir.Operations))}
copy(ret.body, ir.Operations)
// Also copy the offsets if necessary.
if offsets := ir.IROperationSourceOffsetsInWasmBinary; len(offsets) > 0 {
ret.offsetsInWasmBinary = make([]uint64, len(offsets))
copy(ret.offsetsInWasmBinary, offsets)
}
labelAddress := map[wazeroir.Label]uint64{}
onLabelAddressResolved := map[wazeroir.Label][]func(addr uint64){}
for i := range ops {
op := &ops[i]
if hasSourcePCs {
op.SourcePC = ir.IROperationSourceOffsetsInWasmBinary[i]
}
for i := range ret.body {
op := &ret.body[i]
// Nullary operations don't need any further processing.
switch op.Kind {
case wazeroir.OperationKindLabel:
label := wazeroir.Label(op.U1)
address := uint64(len(ret.body))
address := uint64(i)
labelAddress[label] = address
for _, cb := range onLabelAddressResolved[label] {
cb(address)
@@ -293,8 +301,6 @@ func (e *engine) lowerIR(ir *wazeroir.CompilationResult) (*code, error) {
delete(onLabelAddressResolved, label)
// We just ignore the label operation
// as we translate branch operations to the direct address jmp.
continue
case wazeroir.OperationKindBr:
label := wazeroir.Label(op.U1)
if label.IsReturnTarget() {
@@ -316,25 +322,41 @@ func (e *engine) lowerIR(ir *wazeroir.CompilationResult) (*code, error) {
}
case wazeroir.OperationKindBrIf:
for i := 0; i < 2; i++ {
label := wazeroir.Label(op.Us[i])
if label.IsReturnTarget() {
// Jmp to the end of the possible binary.
op.Us[i] = math.MaxUint64
label := wazeroir.Label(op.U1)
if label.IsReturnTarget() {
// Jmp to the end of the possible binary.
op.U1 = math.MaxUint64
} else {
addr, ok := labelAddress[label]
if !ok {
// If this is the forward jump (e.g. to the continuation of if, etc.),
// the target is not emitted yet, so resolve the address later.
onLabelAddressResolved[label] = append(onLabelAddressResolved[label],
func(addr uint64) {
op.U1 = addr
},
)
} else {
addr, ok := labelAddress[label]
if !ok {
i := i
// If this is the forward jump (e.g. to the continuation of if, etc.),
// the target is not emitted yet, so resolve the address later.
onLabelAddressResolved[label] = append(onLabelAddressResolved[label],
func(addr uint64) {
op.Us[i] = addr
},
)
} else {
op.Us[i] = addr
}
op.U1 = addr
}
}
label = wazeroir.Label(op.U2)
if label.IsReturnTarget() {
// Jmp to the end of the possible binary.
op.U2 = math.MaxUint64
} else {
addr, ok := labelAddress[label]
if !ok {
// If this is the forward jump (e.g. to the continuation of if, etc.),
// the target is not emitted yet, so resolve the address later.
onLabelAddressResolved[label] = append(onLabelAddressResolved[label],
func(addr uint64) {
op.U2 = addr
},
)
} else {
op.U2 = addr
}
}
@@ -360,19 +382,7 @@ func (e *engine) lowerIR(ir *wazeroir.CompilationResult) (*code, error) {
}
}
}
case wazeroir.OperationKindV128ITruncSatFromF:
case wazeroir.OperationKindI32ReinterpretFromF32,
wazeroir.OperationKindI64ReinterpretFromF64,
wazeroir.OperationKindF32ReinterpretFromI32,
wazeroir.OperationKindF64ReinterpretFromI64:
// Reinterpret ops are essentially nop for engine mode
// because we treat all values as uint64, and Reinterpret* is only used at module
// validation phase where we check type soundness of all the operations.
// So just eliminate the ops.
continue
}
ret.body = append(ret.body, op)
}
if len(onLabelAddressResolved) > 0 {
@@ -497,8 +507,8 @@ func (ce *callEngine) recoverOnCall(v interface{}) (err error) {
f := frame.f
def := f.def
var sources []string
if body := frame.f.parent.body; body != nil {
sources = frame.f.parent.source.DWARFLines.Line(body[frame.pc].SourcePC)
if parent := frame.f.parent; parent.body != nil && len(parent.offsetsInWasmBinary) > 0 {
sources = parent.source.DWARFLines.Line(parent.offsetsInWasmBinary[frame.pc])
}
builder.AddFrame(def.DebugName(), def.ParamTypes(), def.ResultTypes(), sources)
}
@@ -559,7 +569,7 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
body := frame.f.parent.body
bodyLen := uint64(len(body))
for frame.pc < bodyLen {
op := body[frame.pc]
op := &body[frame.pc]
// TODO: add description of each operation/case
// on, for example, how many args are used,
// how the stack is modified, etc.
@@ -576,10 +586,10 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
case wazeroir.OperationKindBrIf:
if ce.popValue() > 0 {
ce.drop(op.Rs[0])
frame.pc = op.Us[0]
frame.pc = op.U1
} else {
ce.drop(op.Rs[1])
frame.pc = op.Us[1]
frame.pc = op.U2
}
case wazeroir.OperationKindBrTable:
if v := uint64(ce.popValue()); v < uint64(len(op.Us)-1) {
@@ -3815,6 +3825,8 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
ce.pushValue(retLo)
ce.pushValue(retHi)
frame.pc++
default:
frame.pc++
}
}
ce.popFrame()

View File

@@ -325,20 +325,20 @@ func TestInterpreter_NonTrappingFloatToIntConversion(t *testing.T) {
for i := 0; i < casenum; i++ {
i := i
t.Run(strconv.Itoa(i), func(t *testing.T) {
var body []*wazeroir.UnionOperation
var body []wazeroir.UnionOperation
if in32bit {
body = append(body, &wazeroir.UnionOperation{
body = append(body, wazeroir.UnionOperation{
Kind: wazeroir.OperationKindConstF32,
U1: uint64(math.Float32bits(tc.input32bit[i])),
})
} else {
body = append(body, &wazeroir.UnionOperation{
body = append(body, wazeroir.UnionOperation{
Kind: wazeroir.OperationKindConstF64,
U1: uint64(math.Float64bits(tc.input64bit[i])),
})
}
body = append(body, &wazeroir.UnionOperation{
body = append(body, wazeroir.UnionOperation{
Kind: wazeroir.OperationKindITruncFromF,
B1: byte(tc.inputType),
B2: byte(tc.outputType),
@@ -347,7 +347,7 @@ func TestInterpreter_NonTrappingFloatToIntConversion(t *testing.T) {
// Return from function.
body = append(body,
&wazeroir.UnionOperation{Kind: wazeroir.OperationKindBr, U1: uint64(math.MaxUint64)},
wazeroir.UnionOperation{Kind: wazeroir.OperationKindBr, U1: uint64(math.MaxUint64)},
)
ce := &callEngine{}
@@ -416,7 +416,7 @@ func TestInterpreter_CallEngine_callNativeFunc_signExtend(t *testing.T) {
ce := &callEngine{}
f := &function{
moduleInstance: &wasm.ModuleInstance{Engine: &moduleEngine{}},
parent: &code{body: []*wazeroir.UnionOperation{
parent: &code{body: []wazeroir.UnionOperation{
{Kind: wazeroir.OperationKindConstI32, U1: uint64(uint32(tc.in))},
{Kind: translateToIROperationKind(tc.opcode)},
{Kind: wazeroir.OperationKindBr, U1: uint64(math.MaxUint64)},
@@ -470,7 +470,7 @@ func TestInterpreter_CallEngine_callNativeFunc_signExtend(t *testing.T) {
ce := &callEngine{}
f := &function{
moduleInstance: &wasm.ModuleInstance{Engine: &moduleEngine{}},
parent: &code{body: []*wazeroir.UnionOperation{
parent: &code{body: []wazeroir.UnionOperation{
{Kind: wazeroir.OperationKindConstI64, U1: uint64(tc.in)},
{Kind: translateToIROperationKind(tc.opcode)},
{Kind: wazeroir.OperationKindBr, U1: uint64(math.MaxUint64)},
@@ -508,7 +508,7 @@ func TestInterpreter_Compile(t *testing.T) {
errModule.BuildFunctionDefinitions()
err := e.CompileModule(testCtx, errModule, nil, false)
require.EqualError(t, err, "failed to lower func[.$2] to wazeroir: handling instruction: apply stack failed for call: reading immediates: EOF")
require.EqualError(t, err, "handling instruction: apply stack failed for call: reading immediates: EOF")
// On the compilation failure, all the compiled functions including succeeded ones must be released.
_, ok := e.codes[errModule.ID]
@@ -543,8 +543,8 @@ func TestInterpreter_Compile(t *testing.T) {
func TestEngine_CachedcodesPerModule(t *testing.T) {
e := et.NewEngine(api.CoreFeaturesV1).(*engine)
exp := []*code{
{body: []*wazeroir.UnionOperation{}},
{body: []*wazeroir.UnionOperation{}},
{body: []wazeroir.UnionOperation{}},
{body: []wazeroir.UnionOperation{}},
}
m := &wasm.Module{}