wazevo: adds support for context cancelation (#1709)
Signed-off-by: Takeshi Yoneda <t.y.mathetake@gmail.com>
This commit is contained in:
@@ -3100,7 +3100,7 @@ L9 (SSA Block: blk6):
|
|||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
ssab := ssa.NewBuilder()
|
ssab := ssa.NewBuilder()
|
||||||
offset := wazevoapi.NewModuleContextOffsetData(tc.m)
|
offset := wazevoapi.NewModuleContextOffsetData(tc.m)
|
||||||
fc := frontend.NewFrontendCompiler(tc.m, ssab, &offset)
|
fc := frontend.NewFrontendCompiler(tc.m, ssab, &offset, false)
|
||||||
machine := newMachine()
|
machine := newMachine()
|
||||||
machine.DisableStackCheck()
|
machine.DisableStackCheck()
|
||||||
be := backend.NewCompiler(context.Background(), machine, ssab)
|
be := backend.NewCompiler(context.Background(), machine, ssab)
|
||||||
|
|||||||
@@ -376,7 +376,7 @@ func (m *machine) insertStackBoundsCheck(requiredStackSize int64, cur *instructi
|
|||||||
ldrAddress.asULoad(operandNR(tmpRegVReg), addressMode{
|
ldrAddress.asULoad(operandNR(tmpRegVReg), addressMode{
|
||||||
kind: addressModeKindRegUnsignedImm12,
|
kind: addressModeKindRegUnsignedImm12,
|
||||||
rn: x0VReg, // execution context is always the first argument
|
rn: x0VReg, // execution context is always the first argument
|
||||||
imm: wazevoapi.ExecutionContextOffsets.StackGrowCallSequenceAddress.I64(),
|
imm: wazevoapi.ExecutionContextOffsets.StackGrowCallTrampolineAddress.I64(),
|
||||||
}, 64)
|
}, 64)
|
||||||
cur = linkInstr(cur, ldrAddress)
|
cur = linkInstr(cur, ldrAddress)
|
||||||
|
|
||||||
|
|||||||
@@ -63,11 +63,12 @@ type (
|
|||||||
stackGrowRequiredSize uintptr
|
stackGrowRequiredSize uintptr
|
||||||
// memoryGrowTrampolineAddress holds the address of memory grow trampoline function.
|
// memoryGrowTrampolineAddress holds the address of memory grow trampoline function.
|
||||||
memoryGrowTrampolineAddress *byte
|
memoryGrowTrampolineAddress *byte
|
||||||
// stackGrowCallSequenceAddress holds the address of stack grow call sequence function.
|
// stackGrowCallTrampolineAddress holds the address of stack grow trampoline function.
|
||||||
stackGrowCallSequenceAddress *byte
|
stackGrowCallTrampolineAddress *byte
|
||||||
|
// checkModuleExitCodeTrampolineAddress holds the address of check-module-exit-code function.
|
||||||
|
checkModuleExitCodeTrampolineAddress *byte
|
||||||
// savedRegisters is the opaque spaces for save/restore registers.
|
// savedRegisters is the opaque spaces for save/restore registers.
|
||||||
// We want to align 16 bytes for each register, so we use [64][2]uint64.
|
// We want to align 16 bytes for each register, so we use [64][2]uint64.
|
||||||
_ uint64
|
|
||||||
savedRegisters [64][2]uint64
|
savedRegisters [64][2]uint64
|
||||||
// goFunctionCallCalleeModuleContextOpaque is the pointer to the target Go function's moduleContextOpaque.
|
// goFunctionCallCalleeModuleContextOpaque is the pointer to the target Go function's moduleContextOpaque.
|
||||||
goFunctionCallCalleeModuleContextOpaque uintptr
|
goFunctionCallCalleeModuleContextOpaque uintptr
|
||||||
@@ -138,6 +139,19 @@ func (c *callEngine) CallWithStack(ctx context.Context, paramResultStack []uint6
|
|||||||
|
|
||||||
// CallWithStack implements api.Function.
|
// CallWithStack implements api.Function.
|
||||||
func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint64) (err error) {
|
func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint64) (err error) {
|
||||||
|
p := c.parent
|
||||||
|
ensureTermination := p.parent.ensureTermination
|
||||||
|
m := p.module
|
||||||
|
if ensureTermination {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
// If the provided context is already done, close the module and return the error.
|
||||||
|
m.CloseWithCtxErr(ctx)
|
||||||
|
return m.FailIfClosed()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var paramResultPtr *uint64
|
var paramResultPtr *uint64
|
||||||
if len(paramResultStack) > 0 {
|
if len(paramResultStack) > 0 {
|
||||||
paramResultPtr = ¶mResultStack[0]
|
paramResultPtr = ¶mResultStack[0]
|
||||||
@@ -165,6 +179,11 @@ func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint6
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
if ensureTermination {
|
||||||
|
done := m.CloseModuleOnCanceledOrTimeout(ctx)
|
||||||
|
defer done()
|
||||||
|
}
|
||||||
|
|
||||||
entrypoint(c.preambleExecutable, c.executable, c.execCtxPtr, c.parent.opaquePtr, paramResultPtr, c.stackTop)
|
entrypoint(c.preambleExecutable, c.executable, c.execCtxPtr, c.parent.opaquePtr, paramResultPtr, c.stackTop)
|
||||||
for {
|
for {
|
||||||
switch ec := c.execCtx.exitCode; ec & wazevoapi.ExitCodeMask {
|
switch ec := c.execCtx.exitCode; ec & wazevoapi.ExitCodeMask {
|
||||||
@@ -210,6 +229,15 @@ func (c *callEngine) callWithStack(ctx context.Context, paramResultStack []uint6
|
|||||||
f.Call(ctx, mod, c.execCtx.goFunctionCallStack[:])
|
f.Call(ctx, mod, c.execCtx.goFunctionCallStack[:])
|
||||||
c.execCtx.exitCode = wazevoapi.ExitCodeOK
|
c.execCtx.exitCode = wazevoapi.ExitCodeOK
|
||||||
afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, c.execCtx.stackPointerBeforeGoCall)
|
afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, c.execCtx.stackPointerBeforeGoCall)
|
||||||
|
case wazevoapi.ExitCodeCheckModuleExitCode:
|
||||||
|
// Note: this operation must be done in Go, not native code. The reason is that
|
||||||
|
// native code cannot be preempted and that means it can block forever if there are not
|
||||||
|
// enough OS threads (which we don't have control over).
|
||||||
|
if err := m.FailIfClosed(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
c.execCtx.exitCode = wazevoapi.ExitCodeOK
|
||||||
|
afterGoFunctionCallEntrypoint(c.execCtx.goCallReturnAddress, c.execCtxPtr, c.execCtx.stackPointerBeforeGoCall)
|
||||||
case wazevoapi.ExitCodeUnreachable:
|
case wazevoapi.ExitCodeUnreachable:
|
||||||
panic(wasmruntime.ErrRuntimeUnreachable)
|
panic(wasmruntime.ErrRuntimeUnreachable)
|
||||||
case wazevoapi.ExitCodeMemoryOutOfBounds:
|
case wazevoapi.ExitCodeMemoryOutOfBounds:
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ type (
|
|||||||
sharedFunctions struct {
|
sharedFunctions struct {
|
||||||
// memoryGrowExecutable is a compiled executable for memory.grow builtin function.
|
// memoryGrowExecutable is a compiled executable for memory.grow builtin function.
|
||||||
memoryGrowExecutable []byte
|
memoryGrowExecutable []byte
|
||||||
|
// checkModuleExitCode is a compiled executable for checking module instance exit code. This
|
||||||
|
// is used when ensureTermination is true.
|
||||||
|
checkModuleExitCode []byte
|
||||||
// stackGrowExecutable is a compiled executable for growing stack builtin function.
|
// stackGrowExecutable is a compiled executable for growing stack builtin function.
|
||||||
stackGrowExecutable []byte
|
stackGrowExecutable []byte
|
||||||
entryPreambles map[*wasm.FunctionType][]byte
|
entryPreambles map[*wasm.FunctionType][]byte
|
||||||
@@ -54,10 +57,11 @@ type (
|
|||||||
compiledModule struct {
|
compiledModule struct {
|
||||||
executable []byte
|
executable []byte
|
||||||
// functionOffsets maps a local function index to the offset in the executable.
|
// functionOffsets maps a local function index to the offset in the executable.
|
||||||
functionOffsets []int
|
functionOffsets []int
|
||||||
parent *engine
|
parent *engine
|
||||||
module *wasm.Module
|
module *wasm.Module
|
||||||
entryPreambles []*byte // indexed-correlated with the type index.
|
entryPreambles []*byte // indexed-correlated with the type index.
|
||||||
|
ensureTermination bool
|
||||||
|
|
||||||
// The followings are only available for non host modules.
|
// The followings are only available for non host modules.
|
||||||
|
|
||||||
@@ -78,7 +82,7 @@ func NewEngine(ctx context.Context, _ api.CoreFeatures, _ filecache.Cache) wasm.
|
|||||||
machine: machine,
|
machine: machine,
|
||||||
be: be,
|
be: be,
|
||||||
}
|
}
|
||||||
e.compileBuiltinFunctions()
|
e.compileSharedFunctions()
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +112,7 @@ func (e *engine) compileModule(ctx context.Context, module *wasm.Module, listene
|
|||||||
e.rels = e.rels[:0]
|
e.rels = e.rels[:0]
|
||||||
cm := &compiledModule{
|
cm := &compiledModule{
|
||||||
offsets: wazevoapi.NewModuleContextOffsetData(module), parent: e, module: module,
|
offsets: wazevoapi.NewModuleContextOffsetData(module), parent: e, module: module,
|
||||||
|
ensureTermination: ensureTermination,
|
||||||
}
|
}
|
||||||
|
|
||||||
if module.IsHostModule {
|
if module.IsHostModule {
|
||||||
@@ -131,7 +136,7 @@ func (e *engine) compileModule(ctx context.Context, module *wasm.Module, listene
|
|||||||
|
|
||||||
// Creates new compiler instances which are reused for each function.
|
// Creates new compiler instances which are reused for each function.
|
||||||
ssaBuilder := ssa.NewBuilder()
|
ssaBuilder := ssa.NewBuilder()
|
||||||
fe := frontend.NewFrontendCompiler(module, ssaBuilder, &cm.offsets)
|
fe := frontend.NewFrontendCompiler(module, ssaBuilder, &cm.offsets, ensureTermination)
|
||||||
machine := newMachine()
|
machine := newMachine()
|
||||||
be := backend.NewCompiler(ctx, machine, ssaBuilder)
|
be := backend.NewCompiler(ctx, machine, ssaBuilder)
|
||||||
|
|
||||||
@@ -154,7 +159,7 @@ func (e *engine) compileModule(ctx context.Context, module *wasm.Module, listene
|
|||||||
ctx = wazevoapi.SetCurrentFunctionName(ctx, fmt.Sprintf("[%d/%d] \"%s\"", i, len(module.CodeSection)-1, name))
|
ctx = wazevoapi.SetCurrentFunctionName(ctx, fmt.Sprintf("[%d/%d] \"%s\"", i, len(module.CodeSection)-1, name))
|
||||||
}
|
}
|
||||||
|
|
||||||
body, rels, err := e.compileLocalWasmFunction(ctx, module, wasm.Index(i), fe, ssaBuilder, be, listeners, ensureTermination)
|
body, rels, err := e.compileLocalWasmFunction(ctx, module, wasm.Index(i), fe, ssaBuilder, be, listeners)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("compile function %d/%d: %v", i, len(module.CodeSection)-1, err)
|
return nil, fmt.Errorf("compile function %d/%d: %v", i, len(module.CodeSection)-1, err)
|
||||||
}
|
}
|
||||||
@@ -214,7 +219,7 @@ func (e *engine) compileLocalWasmFunction(
|
|||||||
fe *frontend.Compiler,
|
fe *frontend.Compiler,
|
||||||
ssaBuilder ssa.Builder,
|
ssaBuilder ssa.Builder,
|
||||||
be backend.Compiler,
|
be backend.Compiler,
|
||||||
_ []experimental.FunctionListener, _ bool,
|
_ []experimental.FunctionListener,
|
||||||
) (body []byte, rels []backend.RelocationInfo, err error) {
|
) (body []byte, rels []backend.RelocationInfo, err error) {
|
||||||
typ := &module.TypeSection[module.FunctionSection[localFunctionIndex]]
|
typ := &module.TypeSection[module.FunctionSection[localFunctionIndex]]
|
||||||
codeSeg := &module.CodeSection[localFunctionIndex]
|
codeSeg := &module.CodeSection[localFunctionIndex]
|
||||||
@@ -468,7 +473,7 @@ func (e *engine) NewModuleEngine(m *wasm.Module, mi *wasm.ModuleInstance) (wasm.
|
|||||||
return me, nil
|
return me, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *engine) compileBuiltinFunctions() {
|
func (e *engine) compileSharedFunctions() {
|
||||||
e.sharedFunctions = &sharedFunctions{entryPreambles: make(map[*wasm.FunctionType][]byte)}
|
e.sharedFunctions = &sharedFunctions{entryPreambles: make(map[*wasm.FunctionType][]byte)}
|
||||||
|
|
||||||
e.be.Init()
|
e.be.Init()
|
||||||
@@ -480,6 +485,15 @@ func (e *engine) compileBuiltinFunctions() {
|
|||||||
e.sharedFunctions.memoryGrowExecutable = mmapExecutable(src)
|
e.sharedFunctions.memoryGrowExecutable = mmapExecutable(src)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
e.be.Init()
|
||||||
|
{
|
||||||
|
src := e.machine.CompileGoFunctionTrampoline(wazevoapi.ExitCodeCheckModuleExitCode, &ssa.Signature{
|
||||||
|
Params: []ssa.Type{ssa.TypeI32 /* exec context */},
|
||||||
|
Results: []ssa.Type{ssa.TypeI32},
|
||||||
|
}, false)
|
||||||
|
e.sharedFunctions.checkModuleExitCode = mmapExecutable(src)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: table grow, etc.
|
// TODO: table grow, etc.
|
||||||
|
|
||||||
e.be.Init()
|
e.be.Init()
|
||||||
@@ -491,21 +505,26 @@ func (e *engine) compileBuiltinFunctions() {
|
|||||||
e.setFinalizer(e.sharedFunctions, sharedFunctionsFinalizer)
|
e.setFinalizer(e.sharedFunctions, sharedFunctionsFinalizer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func sharedFunctionsFinalizer(bf *sharedFunctions) {
|
func sharedFunctionsFinalizer(sf *sharedFunctions) {
|
||||||
if err := platform.MunmapCodeSegment(bf.memoryGrowExecutable); err != nil {
|
if err := platform.MunmapCodeSegment(sf.memoryGrowExecutable); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if err := platform.MunmapCodeSegment(bf.stackGrowExecutable); err != nil {
|
if err := platform.MunmapCodeSegment(sf.checkModuleExitCode); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
for _, f := range bf.entryPreambles {
|
if err := platform.MunmapCodeSegment(sf.stackGrowExecutable); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for _, f := range sf.entryPreambles {
|
||||||
if err := platform.MunmapCodeSegment(f); err != nil {
|
if err := platform.MunmapCodeSegment(f); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bf.memoryGrowExecutable = nil
|
sf.memoryGrowExecutable = nil
|
||||||
bf.stackGrowExecutable = nil
|
sf.checkModuleExitCode = nil
|
||||||
|
sf.stackGrowExecutable = nil
|
||||||
|
sf.entryPreambles = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func compiledModuleFinalizer(cm *compiledModule) {
|
func compiledModuleFinalizer(cm *compiledModule) {
|
||||||
|
|||||||
@@ -13,19 +13,37 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Test_sharedFunctionsFinalizer(t *testing.T) {
|
func Test_sharedFunctionsFinalizer(t *testing.T) {
|
||||||
bf := &sharedFunctions{}
|
sf := &sharedFunctions{}
|
||||||
|
|
||||||
b1, err := platform.MmapCodeSegment(100)
|
b1, err := platform.MmapCodeSegment(100)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
b2, err := platform.MmapCodeSegment(100)
|
b2, err := platform.MmapCodeSegment(100)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
bf.memoryGrowExecutable = b1
|
|
||||||
bf.stackGrowExecutable = b2
|
|
||||||
|
|
||||||
sharedFunctionsFinalizer(bf)
|
b3, err := platform.MmapCodeSegment(100)
|
||||||
require.Nil(t, bf.memoryGrowExecutable)
|
require.NoError(t, err)
|
||||||
require.Nil(t, bf.stackGrowExecutable)
|
|
||||||
|
b4, err := platform.MmapCodeSegment(100)
|
||||||
|
require.NoError(t, err)
|
||||||
|
b5, err := platform.MmapCodeSegment(100)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
preabmles := map[*wasm.FunctionType][]byte{
|
||||||
|
{Params: []wasm.ValueType{}}: b4,
|
||||||
|
{Params: []wasm.ValueType{wasm.ValueTypeI32}}: b5,
|
||||||
|
}
|
||||||
|
|
||||||
|
sf.memoryGrowExecutable = b1
|
||||||
|
sf.stackGrowExecutable = b2
|
||||||
|
sf.checkModuleExitCode = b3
|
||||||
|
sf.entryPreambles = preabmles
|
||||||
|
|
||||||
|
sharedFunctionsFinalizer(sf)
|
||||||
|
require.Nil(t, sf.memoryGrowExecutable)
|
||||||
|
require.Nil(t, sf.stackGrowExecutable)
|
||||||
|
require.Nil(t, sf.checkModuleExitCode)
|
||||||
|
require.Nil(t, sf.entryPreambles)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_compiledModuleFinalizer(t *testing.T) {
|
func Test_compiledModuleFinalizer(t *testing.T) {
|
||||||
|
|||||||
@@ -17,9 +17,12 @@ type Compiler struct {
|
|||||||
m *wasm.Module
|
m *wasm.Module
|
||||||
offset *wazevoapi.ModuleContextOffsetData
|
offset *wazevoapi.ModuleContextOffsetData
|
||||||
// ssaBuilder is a ssa.Builder used by this frontend.
|
// ssaBuilder is a ssa.Builder used by this frontend.
|
||||||
ssaBuilder ssa.Builder
|
ssaBuilder ssa.Builder
|
||||||
signatures map[*wasm.FunctionType]*ssa.Signature
|
signatures map[*wasm.FunctionType]*ssa.Signature
|
||||||
memoryGrowSig ssa.Signature
|
memoryGrowSig ssa.Signature
|
||||||
|
checkModuleExitCodeSig ssa.Signature
|
||||||
|
checkModuleExitCodeArg [1]ssa.Value
|
||||||
|
ensureTermination bool
|
||||||
|
|
||||||
// Followings are reset by per function.
|
// Followings are reset by per function.
|
||||||
|
|
||||||
@@ -43,13 +46,14 @@ type Compiler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewFrontendCompiler returns a frontend Compiler.
|
// NewFrontendCompiler returns a frontend Compiler.
|
||||||
func NewFrontendCompiler(m *wasm.Module, ssaBuilder ssa.Builder, offset *wazevoapi.ModuleContextOffsetData) *Compiler {
|
func NewFrontendCompiler(m *wasm.Module, ssaBuilder ssa.Builder, offset *wazevoapi.ModuleContextOffsetData, ensureTermination bool) *Compiler {
|
||||||
c := &Compiler{
|
c := &Compiler{
|
||||||
m: m,
|
m: m,
|
||||||
ssaBuilder: ssaBuilder,
|
ssaBuilder: ssaBuilder,
|
||||||
br: bytes.NewReader(nil),
|
br: bytes.NewReader(nil),
|
||||||
wasmLocalToVariable: make(map[wasm.Index]ssa.Variable),
|
wasmLocalToVariable: make(map[wasm.Index]ssa.Variable),
|
||||||
offset: offset,
|
offset: offset,
|
||||||
|
ensureTermination: ensureTermination,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.signatures = make(map[*wasm.FunctionType]*ssa.Signature, len(m.TypeSection)+1)
|
c.signatures = make(map[*wasm.FunctionType]*ssa.Signature, len(m.TypeSection)+1)
|
||||||
@@ -70,6 +74,12 @@ func NewFrontendCompiler(m *wasm.Module, ssaBuilder ssa.Builder, offset *wazevoa
|
|||||||
}
|
}
|
||||||
c.ssaBuilder.DeclareSignature(&c.memoryGrowSig)
|
c.ssaBuilder.DeclareSignature(&c.memoryGrowSig)
|
||||||
|
|
||||||
|
c.checkModuleExitCodeSig = ssa.Signature{
|
||||||
|
ID: c.memoryGrowSig.ID + 1,
|
||||||
|
// Only takes execution context.
|
||||||
|
Params: []ssa.Type{ssa.TypeI64},
|
||||||
|
}
|
||||||
|
c.ssaBuilder.DeclareSignature(&c.checkModuleExitCodeSig)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ func TestCompiler_LowerToSSA(t *testing.T) {
|
|||||||
// what output should look like, you can run:
|
// what output should look like, you can run:
|
||||||
// `~/wasmtime/target/debug/clif-util wasm --target aarch64-apple-darwin testcase.wat -p -t`
|
// `~/wasmtime/target/debug/clif-util wasm --target aarch64-apple-darwin testcase.wat -p -t`
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
|
ensureTermination bool
|
||||||
// m is the *wasm.Module to be compiled in this test.
|
// m is the *wasm.Module to be compiled in this test.
|
||||||
m *wasm.Module
|
m *wasm.Module
|
||||||
// targetIndex is the index of a local function to be compiled in this test.
|
// targetIndex is the index of a local function to be compiled in this test.
|
||||||
@@ -219,6 +220,36 @@ blk0: (exec_ctx:i64, module_ctx:i64)
|
|||||||
|
|
||||||
blk1: () <-- (blk0,blk1)
|
blk1: () <-- (blk0,blk1)
|
||||||
Jump blk1
|
Jump blk1
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "loop - br / ensure termination", m: testcases.LoopBr.Module,
|
||||||
|
ensureTermination: true,
|
||||||
|
exp: `
|
||||||
|
signatures:
|
||||||
|
sig2: i64_v
|
||||||
|
|
||||||
|
blk0: (exec_ctx:i64, module_ctx:i64)
|
||||||
|
Jump blk1
|
||||||
|
|
||||||
|
blk1: () <-- (blk0,blk1)
|
||||||
|
v2:i64 = Load exec_ctx, 0x58
|
||||||
|
CallIndirect v2:sig2, exec_ctx
|
||||||
|
Jump blk1
|
||||||
|
|
||||||
|
blk2: ()
|
||||||
|
`,
|
||||||
|
expAfterOpt: `
|
||||||
|
signatures:
|
||||||
|
sig2: i64_v
|
||||||
|
|
||||||
|
blk0: (exec_ctx:i64, module_ctx:i64)
|
||||||
|
Jump blk1
|
||||||
|
|
||||||
|
blk1: () <-- (blk0,blk1)
|
||||||
|
v2:i64 = Load exec_ctx, 0x58
|
||||||
|
CallIndirect v2:sig2, exec_ctx
|
||||||
|
Jump blk1
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -1736,7 +1767,7 @@ blk4: () <-- (blk2,blk3)
|
|||||||
b := ssa.NewBuilder()
|
b := ssa.NewBuilder()
|
||||||
|
|
||||||
offset := wazevoapi.NewModuleContextOffsetData(tc.m)
|
offset := wazevoapi.NewModuleContextOffsetData(tc.m)
|
||||||
fc := NewFrontendCompiler(tc.m, b, &offset)
|
fc := NewFrontendCompiler(tc.m, b, &offset, tc.ensureTermination)
|
||||||
typeIndex := tc.m.FunctionSection[tc.targetIndex]
|
typeIndex := tc.m.FunctionSection[tc.targetIndex]
|
||||||
code := &tc.m.CodeSection[tc.targetIndex]
|
code := &tc.m.CodeSection[tc.targetIndex]
|
||||||
fc.Init(tc.targetIndex, &tc.m.TypeSection[typeIndex], code.LocalTypes, code.Body)
|
fc.Init(tc.targetIndex, &tc.m.TypeSection[typeIndex], code.LocalTypes, code.Body)
|
||||||
|
|||||||
@@ -1012,6 +1012,19 @@ func (c *Compiler) lowerCurrentOpcode() {
|
|||||||
|
|
||||||
c.switchTo(originalLen, loopHeader)
|
c.switchTo(originalLen, loopHeader)
|
||||||
|
|
||||||
|
if c.ensureTermination {
|
||||||
|
checkModuleExitCodePtr := builder.AllocateInstruction().
|
||||||
|
AsLoad(c.execCtxPtrValue,
|
||||||
|
wazevoapi.ExecutionContextOffsets.CheckModuleExitCodeTrampolineAddress.U32(),
|
||||||
|
ssa.TypeI64,
|
||||||
|
).Insert(builder).Return()
|
||||||
|
|
||||||
|
c.checkModuleExitCodeArg[0] = c.execCtxPtrValue
|
||||||
|
|
||||||
|
builder.AllocateInstruction().
|
||||||
|
AsCallIndirect(checkModuleExitCodePtr, &c.checkModuleExitCodeSig, c.checkModuleExitCodeArg[:]).
|
||||||
|
Insert(builder)
|
||||||
|
}
|
||||||
case wasm.OpcodeIf:
|
case wasm.OpcodeIf:
|
||||||
bt := c.readBlockType()
|
bt := c.readBlockType()
|
||||||
|
|
||||||
|
|||||||
@@ -142,7 +142,8 @@ func (m *moduleEngine) NewFunction(index wasm.Index) api.Function {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ce.execCtx.memoryGrowTrampolineAddress = &m.parent.sharedFunctions.memoryGrowExecutable[0]
|
ce.execCtx.memoryGrowTrampolineAddress = &m.parent.sharedFunctions.memoryGrowExecutable[0]
|
||||||
ce.execCtx.stackGrowCallSequenceAddress = &m.parent.sharedFunctions.stackGrowExecutable[0]
|
ce.execCtx.stackGrowCallTrampolineAddress = &m.parent.sharedFunctions.stackGrowExecutable[0]
|
||||||
|
ce.execCtx.checkModuleExitCodeTrampolineAddress = &m.parent.sharedFunctions.checkModuleExitCode[0]
|
||||||
ce.init()
|
ce.init()
|
||||||
return ce
|
return ce
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,8 @@ func Test_ExecutionContextOffsets(t *testing.T) {
|
|||||||
require.Equal(t, wazevoapi.Offset(unsafe.Offsetof(execCtx.stackPointerBeforeGoCall)), offsets.StackPointerBeforeGoCall)
|
require.Equal(t, wazevoapi.Offset(unsafe.Offsetof(execCtx.stackPointerBeforeGoCall)), offsets.StackPointerBeforeGoCall)
|
||||||
require.Equal(t, wazevoapi.Offset(unsafe.Offsetof(execCtx.stackGrowRequiredSize)), offsets.StackGrowRequiredSize)
|
require.Equal(t, wazevoapi.Offset(unsafe.Offsetof(execCtx.stackGrowRequiredSize)), offsets.StackGrowRequiredSize)
|
||||||
require.Equal(t, wazevoapi.Offset(unsafe.Offsetof(execCtx.memoryGrowTrampolineAddress)), offsets.MemoryGrowTrampolineAddress)
|
require.Equal(t, wazevoapi.Offset(unsafe.Offsetof(execCtx.memoryGrowTrampolineAddress)), offsets.MemoryGrowTrampolineAddress)
|
||||||
require.Equal(t, wazevoapi.Offset(unsafe.Offsetof(execCtx.stackGrowCallSequenceAddress)), offsets.StackGrowCallSequenceAddress)
|
require.Equal(t, wazevoapi.Offset(unsafe.Offsetof(execCtx.stackGrowCallTrampolineAddress)), offsets.StackGrowCallTrampolineAddress)
|
||||||
|
require.Equal(t, wazevoapi.Offset(unsafe.Offsetof(execCtx.checkModuleExitCodeTrampolineAddress)), offsets.CheckModuleExitCodeTrampolineAddress)
|
||||||
require.Equal(t, wazevoapi.Offset(unsafe.Offsetof(execCtx.savedRegisters))%16, wazevoapi.Offset(0),
|
require.Equal(t, wazevoapi.Offset(unsafe.Offsetof(execCtx.savedRegisters))%16, wazevoapi.Offset(0),
|
||||||
"SavedRegistersBegin must be aligned to 16 bytes")
|
"SavedRegistersBegin must be aligned to 16 bytes")
|
||||||
require.Equal(t, wazevoapi.Offset(unsafe.Offsetof(execCtx.savedRegisters)), offsets.SavedRegistersBegin)
|
require.Equal(t, wazevoapi.Offset(unsafe.Offsetof(execCtx.savedRegisters)), offsets.SavedRegistersBegin)
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const (
|
|||||||
ExitCodeIntegerDivisionByZero
|
ExitCodeIntegerDivisionByZero
|
||||||
ExitCodeIntegerOverflow
|
ExitCodeIntegerOverflow
|
||||||
ExitCodeInvalidConversionToInteger
|
ExitCodeInvalidConversionToInteger
|
||||||
|
ExitCodeCheckModuleExitCode
|
||||||
exitCodeMax
|
exitCodeMax
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -51,6 +52,8 @@ func (e ExitCode) String() string {
|
|||||||
return "integer_overflow"
|
return "integer_overflow"
|
||||||
case ExitCodeInvalidConversionToInteger:
|
case ExitCodeInvalidConversionToInteger:
|
||||||
return "invalid_conversion_to_integer"
|
return "invalid_conversion_to_integer"
|
||||||
|
case ExitCodeCheckModuleExitCode:
|
||||||
|
return "check_module_exit_code"
|
||||||
}
|
}
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ var ExecutionContextOffsets = ExecutionContextOffsetData{
|
|||||||
StackPointerBeforeGoCall: 56,
|
StackPointerBeforeGoCall: 56,
|
||||||
StackGrowRequiredSize: 64,
|
StackGrowRequiredSize: 64,
|
||||||
MemoryGrowTrampolineAddress: 72,
|
MemoryGrowTrampolineAddress: 72,
|
||||||
StackGrowCallSequenceAddress: 80,
|
StackGrowCallTrampolineAddress: 80,
|
||||||
|
CheckModuleExitCodeTrampolineAddress: 88,
|
||||||
SavedRegistersBegin: 96,
|
SavedRegistersBegin: 96,
|
||||||
GoFunctionCallCalleeModuleContextOpaque: 1120,
|
GoFunctionCallCalleeModuleContextOpaque: 1120,
|
||||||
GoFunctionCallStackBegin: 1128,
|
GoFunctionCallStackBegin: 1128,
|
||||||
@@ -53,8 +54,10 @@ type ExecutionContextOffsetData struct {
|
|||||||
StackGrowRequiredSize Offset
|
StackGrowRequiredSize Offset
|
||||||
// MemoryGrowTrampolineAddress is an offset of `memoryGrowTrampolineAddress` field in wazevo.executionContext
|
// MemoryGrowTrampolineAddress is an offset of `memoryGrowTrampolineAddress` field in wazevo.executionContext
|
||||||
MemoryGrowTrampolineAddress Offset
|
MemoryGrowTrampolineAddress Offset
|
||||||
// StackGrowCallSequenceAddress is an offset of `stackGrowCallSequenceAddress` field in wazevo.executionContext
|
// stackGrowCallTrampolineAddress is an offset of `stackGrowCallTrampolineAddress` field in wazevo.executionContext.
|
||||||
StackGrowCallSequenceAddress Offset
|
StackGrowCallTrampolineAddress Offset
|
||||||
|
// CheckModuleExitCodeTrampolineAddress is an offset of `checkModuleExitCodeTrampolineAddress` field in wazevo.executionContext.
|
||||||
|
CheckModuleExitCodeTrampolineAddress Offset
|
||||||
// GoCallReturnAddress is an offset of the first element of `savedRegisters` field in wazevo.executionContext
|
// GoCallReturnAddress is an offset of the first element of `savedRegisters` field in wazevo.executionContext
|
||||||
SavedRegistersBegin Offset
|
SavedRegistersBegin Offset
|
||||||
// GoFunctionCallCalleeModuleContextOpaque is an offset of `goFunctionCallCalleeModuleContextOpaque` field in wazevo.executionContext
|
// GoFunctionCallCalleeModuleContextOpaque is an offset of `goFunctionCallCalleeModuleContextOpaque` field in wazevo.executionContext
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ var tests = map[string]testCase{
|
|||||||
"overflow integer addition": {f: testOverflow},
|
"overflow integer addition": {f: testOverflow},
|
||||||
"un-signed extend global": {f: testGlobalExtend},
|
"un-signed extend global": {f: testGlobalExtend},
|
||||||
"user-defined primitive in host func": {f: testUserDefinedPrimitiveHostFunc},
|
"user-defined primitive in host func": {f: testUserDefinedPrimitiveHostFunc},
|
||||||
"ensures invocations terminate on module close": {f: testEnsureTerminationOnClose, wazevoSkip: true},
|
"ensures invocations terminate on module close": {f: testEnsureTerminationOnClose},
|
||||||
"call host function indirectly": {f: callHostFunctionIndirect, wazevoSkip: true},
|
"call host function indirectly": {f: callHostFunctionIndirect, wazevoSkip: true},
|
||||||
"lookup function": {f: testLookupFunction},
|
"lookup function": {f: testLookupFunction},
|
||||||
"memory grow in recursive call": {f: testMemoryGrowInRecursiveCall},
|
"memory grow in recursive call": {f: testMemoryGrowInRecursiveCall},
|
||||||
|
|||||||
Reference in New Issue
Block a user