updated vendor
This commit is contained in:
+215
-6
@@ -21,6 +21,7 @@ const (
|
||||
controlFrameKindLoop
|
||||
controlFrameKindIfWithElse
|
||||
controlFrameKindIfWithoutElse
|
||||
controlFrameKindTryTable
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -57,7 +58,8 @@ func (c *controlFrame) asLabel() label {
|
||||
case controlFrameKindFunction:
|
||||
return newLabel(labelKindReturn, 0)
|
||||
case controlFrameKindIfWithElse,
|
||||
controlFrameKindIfWithoutElse:
|
||||
controlFrameKindIfWithoutElse,
|
||||
controlFrameKindTryTable:
|
||||
return newLabel(labelKindContinuation, c.frameID)
|
||||
}
|
||||
panic(fmt.Sprintf("unreachable: a bug in interpreterir implementation: %v", c.kind))
|
||||
@@ -187,6 +189,8 @@ type compiler struct {
|
||||
funcs []uint32
|
||||
// globals holds the global types for all declared globals in the module where the target function exists.
|
||||
globals []wasm.GlobalType
|
||||
// tags holds the type indexes for all declared tags in the module where the target function exists.
|
||||
tags []uint32
|
||||
|
||||
// needSourceOffset is true if this module requires DWARF based stack trace.
|
||||
needSourceOffset bool
|
||||
@@ -254,6 +258,9 @@ type compilationResult struct {
|
||||
LabelCallers map[label]uint32
|
||||
// UsesMemory is true if this function might use memory.
|
||||
UsesMemory bool
|
||||
// PendingExceptionTable holds unresolved exception table entries, built during
|
||||
// compilation. Labels are resolved to final PCs in lowerIR.
|
||||
PendingExceptionTable []pendingExceptionTableEntry
|
||||
|
||||
// The following fields are per-module values, not per-function.
|
||||
|
||||
@@ -276,7 +283,7 @@ type compilationResult struct {
|
||||
// newCompiler returns the new *compiler for the given parameters.
|
||||
// Use compiler.Next function to get compilation result per function.
|
||||
func newCompiler(enabledFeatures api.CoreFeatures, callFrameStackSizeInUint64 int, module *wasm.Module, ensureTermination bool) (*compiler, error) {
|
||||
functions, globals, mem, tables, err := module.AllDeclarations()
|
||||
functions, globals, mem, tables, tags, err := module.AllDeclarations()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -313,12 +320,14 @@ func newCompiler(enabledFeatures api.CoreFeatures, callFrameStackSizeInUint64 in
|
||||
},
|
||||
globals: globals,
|
||||
funcs: functions,
|
||||
tags: tags,
|
||||
types: types,
|
||||
ensureTermination: ensureTermination,
|
||||
br: bytes.NewReader(nil),
|
||||
funcTypeToSigs: funcTypeToIRSignatures{
|
||||
indirectCalls: make([]*signature, len(types)),
|
||||
directCalls: make([]*signature, len(types)),
|
||||
callRefCalls: make([]*signature, len(types)),
|
||||
wasmTypes: types,
|
||||
},
|
||||
needSourceOffset: module.DWARFLines != nil,
|
||||
@@ -336,6 +345,7 @@ func (c *compiler) Next() (*compilationResult, error) {
|
||||
c.result.Operations = c.result.Operations[:0]
|
||||
c.result.IROperationSourceOffsetsInWasmBinary = c.result.IROperationSourceOffsetsInWasmBinary[:0]
|
||||
c.result.UsesMemory = false
|
||||
c.result.PendingExceptionTable = c.result.PendingExceptionTable[:0]
|
||||
// Clears the existing entries in LabelCallers.
|
||||
for frameID := uint32(0); frameID <= c.currentFrameID; frameID++ {
|
||||
for k := labelKind(0); k < labelKindNum; k++ {
|
||||
@@ -667,7 +677,8 @@ operatorSwitch:
|
||||
// Initiate the continuation.
|
||||
c.emit(newOperationLabel(continuationLabel))
|
||||
case controlFrameKindBlockWithContinuationLabel,
|
||||
controlFrameKindIfWithElse:
|
||||
controlFrameKindIfWithElse,
|
||||
controlFrameKindTryTable:
|
||||
continuationLabel := newLabel(labelKindContinuation, frame.frameID)
|
||||
c.result.LabelCallers[continuationLabel]++
|
||||
c.emit(dropOp)
|
||||
@@ -800,6 +811,95 @@ operatorSwitch:
|
||||
// That means subsequent instructions in the current control frame are "unreachable"
|
||||
// and can be safely removed.
|
||||
c.markUnreachable()
|
||||
case wasm.OpcodeThrow:
|
||||
if c.unreachableState.on {
|
||||
break operatorSwitch
|
||||
}
|
||||
// Pop the tag's param values from the stack.
|
||||
if index < uint32(len(c.tags)) {
|
||||
tagType := &c.types[c.tags[index]]
|
||||
for i := len(tagType.Params) - 1; i >= 0; i-- {
|
||||
c.stackPop()
|
||||
}
|
||||
}
|
||||
c.emit(newOperationThrow(index))
|
||||
c.markUnreachable()
|
||||
case wasm.OpcodeThrowRef:
|
||||
if c.unreachableState.on {
|
||||
break operatorSwitch
|
||||
}
|
||||
// Pop the exnref from the stack.
|
||||
c.stackPop()
|
||||
c.emit(newOperationThrowRef())
|
||||
c.markUnreachable()
|
||||
case wasm.OpcodeTryTable:
|
||||
c.br.Reset(c.body[c.pc+1:])
|
||||
bt, num, err := wasm.DecodeBlockType(c.types, c.br, c.enabledFeatures)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading block type for try_table instruction: %w", err)
|
||||
}
|
||||
c.pc += num
|
||||
|
||||
if c.unreachableState.on {
|
||||
c.unreachableState.depth++
|
||||
// Still need to skip the catch clause bytes.
|
||||
c.pc++
|
||||
catchCount, catchNum, err := leb128.LoadUint32(c.body[c.pc:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading catch count for try_table: %w", err)
|
||||
}
|
||||
c.pc += catchNum - 1
|
||||
for i := uint32(0); i < catchCount; i++ {
|
||||
if _, _, _, err := c.parseCatchClause(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
break operatorSwitch
|
||||
}
|
||||
|
||||
// Read catch clause count.
|
||||
c.pc++
|
||||
catchCount, catchNum, err := leb128.LoadUint32(c.body[c.pc:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading catch count for try_table: %w", err)
|
||||
}
|
||||
c.pc += catchNum - 1
|
||||
|
||||
// Parse catch clauses.
|
||||
var pendingClauses []pendingCatchClause
|
||||
for i := uint32(0); i < catchCount; i++ {
|
||||
kind, tagIdx, labelIdx, err := c.parseCatchClause()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Resolve the label from the control frame stack.
|
||||
targetFrame := c.controlFrames.get(int(labelIdx))
|
||||
targetFrame.ensureContinuation()
|
||||
targetLabel := targetFrame.asLabel()
|
||||
c.result.LabelCallers[targetLabel]++
|
||||
pendingClauses = append(pendingClauses, pendingCatchClause{
|
||||
kind: kind,
|
||||
tagIndex: tagIdx,
|
||||
targetLabel: targetLabel,
|
||||
targetStackDepth: targetFrame.originalStackLenWithoutParamUint64,
|
||||
})
|
||||
}
|
||||
|
||||
// Create a control frame for the try_table block.
|
||||
frameID := c.nextFrameID()
|
||||
c.result.PendingExceptionTable = append(c.result.PendingExceptionTable, pendingExceptionTableEntry{
|
||||
startOpIndex: len(c.result.Operations),
|
||||
continuationFrameID: frameID,
|
||||
clauses: pendingClauses,
|
||||
})
|
||||
frame := controlFrame{
|
||||
frameID: frameID,
|
||||
originalStackLenWithoutParam: len(c.stack) - len(bt.Params),
|
||||
originalStackLenWithoutParamUint64: c.stackLenInUint64 - bt.ParamNumInUint64,
|
||||
kind: controlFrameKindTryTable,
|
||||
blockType: bt,
|
||||
}
|
||||
c.controlFrames.push(frame)
|
||||
case wasm.OpcodeCall:
|
||||
c.emit(
|
||||
newOperationCall(index),
|
||||
@@ -1616,7 +1716,18 @@ operatorSwitch:
|
||||
newOperationRefFunc(index),
|
||||
)
|
||||
case wasm.OpcodeRefNull:
|
||||
c.pc++ // Skip the type of reftype as every ref value is opaque pointer.
|
||||
c.pc++
|
||||
switch reftype := c.body[c.pc]; wasm.ValueType(reftype) {
|
||||
case wasm.ValueTypeFuncref, wasm.ValueTypeExternref, wasm.ValueTypeExnref:
|
||||
// Abstract ref types are a single byte; already skipped.
|
||||
default:
|
||||
// Concrete type index encoded as LEB128; skip it.
|
||||
_, num, err := leb128.LoadUint32(c.body[c.pc:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read type index for ref.null: %v", err)
|
||||
}
|
||||
c.pc += num - 1
|
||||
}
|
||||
c.emit(
|
||||
newOperationConstI64(0),
|
||||
)
|
||||
@@ -3463,6 +3574,64 @@ operatorSwitch:
|
||||
// and can be safely removed.
|
||||
c.markUnreachable()
|
||||
|
||||
case wasm.OpcodeCallRef:
|
||||
c.emit(newOperationCallRef(index))
|
||||
|
||||
case wasm.OpcodeReturnCallRef:
|
||||
functionFrame := c.controlFrames.functionFrame()
|
||||
dropRange := c.getFrameDropRange(functionFrame, false)
|
||||
c.emit(newOperationReturnCallRef(index, dropRange, functionFrame.asLabel()))
|
||||
c.markUnreachable()
|
||||
|
||||
case wasm.OpcodeRefAsNonNull:
|
||||
c.emit(newOperationRefAsNonNull())
|
||||
|
||||
case wasm.OpcodeBrOnNull:
|
||||
targetIndex, n, err := leb128.LoadUint32(c.body[c.pc+1:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("read the target for br_on_null: %w", err)
|
||||
}
|
||||
c.pc += n
|
||||
|
||||
if c.unreachableState.on {
|
||||
break operatorSwitch
|
||||
}
|
||||
|
||||
targetFrame := c.controlFrames.get(int(targetIndex))
|
||||
targetFrame.ensureContinuation()
|
||||
drop := c.getFrameDropRange(targetFrame, false)
|
||||
target := targetFrame.asLabel()
|
||||
c.result.LabelCallers[target]++
|
||||
|
||||
continuationLabel := newLabel(labelKindHeader, c.nextFrameID())
|
||||
c.result.LabelCallers[continuationLabel]++
|
||||
c.emit(newOperationBrOnNull(target, continuationLabel, drop))
|
||||
c.emit(newOperationLabel(continuationLabel))
|
||||
// On fall-through (non-null), the ref is pushed back at runtime.
|
||||
c.stackPush(unsignedTypeI64)
|
||||
|
||||
case wasm.OpcodeBrOnNonNull:
|
||||
targetIndex, n, err := leb128.LoadUint32(c.body[c.pc+1:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("read the target for br_on_non_null: %w", err)
|
||||
}
|
||||
c.pc += n
|
||||
|
||||
if c.unreachableState.on {
|
||||
break operatorSwitch
|
||||
}
|
||||
|
||||
targetFrame := c.controlFrames.get(int(targetIndex))
|
||||
targetFrame.ensureContinuation()
|
||||
drop := c.getFrameDropRange(targetFrame, false)
|
||||
target := targetFrame.asLabel()
|
||||
c.result.LabelCallers[target]++
|
||||
|
||||
continuationLabel := newLabel(labelKindHeader, c.nextFrameID())
|
||||
c.result.LabelCallers[continuationLabel]++
|
||||
c.emit(newOperationBrOnNonNull(target, continuationLabel, drop))
|
||||
c.emit(newOperationLabel(continuationLabel))
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported instruction in interpreterir: 0x%x", op)
|
||||
}
|
||||
@@ -3492,7 +3661,12 @@ func (c *compiler) applyToStack(opcode wasm.Opcode) (index uint32, err error) {
|
||||
wasm.OpcodeGlobalSet,
|
||||
// tail-call proposal
|
||||
wasm.OpcodeTailCallReturnCall,
|
||||
wasm.OpcodeTailCallReturnCallIndirect:
|
||||
wasm.OpcodeTailCallReturnCallIndirect,
|
||||
// exception handling - throw reads tag index
|
||||
wasm.OpcodeThrow,
|
||||
// typed function references
|
||||
wasm.OpcodeCallRef,
|
||||
wasm.OpcodeReturnCallRef:
|
||||
// Assumes that we are at the opcode now so skip it before read immediates.
|
||||
v, num, err := leb128.LoadUint32(c.body[c.pc+1:])
|
||||
if err != nil {
|
||||
@@ -3605,7 +3779,7 @@ func (c *compiler) emitDefaultValue(t wasm.ValueType) {
|
||||
case wasm.ValueTypeI32:
|
||||
c.stackPush(unsignedTypeI32)
|
||||
c.emit(newOperationConstI32(0))
|
||||
case wasm.ValueTypeI64, wasm.ValueTypeExternref, wasm.ValueTypeFuncref:
|
||||
case wasm.ValueTypeI64, wasm.ValueTypeExternref, wasm.ValueTypeFuncref, wasm.ValueTypeExnref:
|
||||
c.stackPush(unsignedTypeI64)
|
||||
c.emit(newOperationConstI64(0))
|
||||
case wasm.ValueTypeF32:
|
||||
@@ -3617,6 +3791,14 @@ func (c *compiler) emitDefaultValue(t wasm.ValueType) {
|
||||
case wasm.ValueTypeV128:
|
||||
c.stackPush(unsignedTypeV128)
|
||||
c.emit(newOperationV128Const(0, 0))
|
||||
default:
|
||||
// Concrete ref types (ref $t) have variable bit patterns.
|
||||
if t.IsRef() {
|
||||
c.stackPush(unsignedTypeI64)
|
||||
c.emit(newOperationConstI64(0))
|
||||
} else {
|
||||
panic(fmt.Sprintf("bug: unsupported value type for default value: 0x%x", t))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3673,3 +3855,30 @@ func (c *compiler) readMemoryArg(tag string) (memoryArg, error) {
|
||||
c.pc += num
|
||||
return memoryArg{Offset: offset, Alignment: alignment}, nil
|
||||
}
|
||||
|
||||
// parseCatchClause parses a single catch clause from the bytecode at c.pc,
|
||||
// advancing c.pc past the clause. Returns the kind, tag index (0 for catch_all
|
||||
// variants), and label index.
|
||||
func (c *compiler) parseCatchClause() (kind byte, tagIdx, labelIdx uint32, err error) {
|
||||
var n uint64
|
||||
c.pc++
|
||||
kind = c.body[c.pc]
|
||||
switch kind {
|
||||
case wasm.CatchKindCatch, wasm.CatchKindCatchRef:
|
||||
c.pc++
|
||||
tagIdx, n, err = leb128.LoadUint32(c.body[c.pc:])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("reading catch tag index: %w", err)
|
||||
return
|
||||
}
|
||||
c.pc += n - 1
|
||||
}
|
||||
c.pc++
|
||||
labelIdx, n, err = leb128.LoadUint32(c.body[c.pc:])
|
||||
if err != nil {
|
||||
err = fmt.Errorf("reading catch label index: %w", err)
|
||||
return
|
||||
}
|
||||
c.pc += n - 1
|
||||
return
|
||||
}
|
||||
|
||||
+323
-29
@@ -130,6 +130,39 @@ func (e *moduleEngine) OwnsGlobals() bool { return false }
|
||||
// MemoryGrown implements wasm.ModuleEngine.
|
||||
func (e *moduleEngine) MemoryGrown() {}
|
||||
|
||||
// restorable is implemented by panic values that can restore callEngine state.
|
||||
// Both *snapshot (snapshotter API) and *thrownException (exception handling)
|
||||
// implement this interface.
|
||||
type restorable interface {
|
||||
// canRestore unwinds ce.frames to callerFrameCount and checks whether a
|
||||
// handler exists at that depth. If no handler is found, the caller
|
||||
// re-panics and the next outer callWithUnwind unwinds further.
|
||||
canRestore(ce *callEngine, callerFrameCount int) bool
|
||||
// doRestore restores the callEngine state to the given stack frame depth.
|
||||
doRestore(ce *callEngine, callerFrameCount int)
|
||||
}
|
||||
|
||||
// thrownException is the panic value for wasm exception propagation.
|
||||
type thrownException struct {
|
||||
exception *wasm.Exception
|
||||
// Fields populated by canRestore for doRestore.
|
||||
clause *exceptionTableCatchClause
|
||||
values []uint64
|
||||
}
|
||||
|
||||
func (t *thrownException) canRestore(ce *callEngine, callerFrameCount int) bool {
|
||||
ce.frames = ce.frames[:callerFrameCount]
|
||||
frame := ce.frames[callerFrameCount-1]
|
||||
t.clause, t.values = searchExceptionTable(t.exception, frame)
|
||||
return t.clause != nil
|
||||
}
|
||||
|
||||
func (t *thrownException) doRestore(ce *callEngine, callerFrameCount int) {
|
||||
frame := ce.frames[callerFrameCount-1]
|
||||
ce.applyExceptionHandler(frame, t.clause, t.values)
|
||||
t.clause, t.values = nil, nil
|
||||
}
|
||||
|
||||
// callEngine holds context per moduleEngine.Call, and shared across all the
|
||||
// function calls originating from the same moduleEngine.Call execution.
|
||||
//
|
||||
@@ -151,6 +184,94 @@ type callEngine struct {
|
||||
stackIterator stackIterator
|
||||
}
|
||||
|
||||
// matchCatchClause checks whether a single catch clause matches the given exception.
|
||||
// Returns whether it matched and the values to push onto the stack.
|
||||
func matchCatchClause(kind byte, clauseTag *wasm.TagInstance, exn *wasm.Exception) (matched bool, values []uint64) {
|
||||
switch kind {
|
||||
case wasm.CatchKindCatch:
|
||||
if exn.Tag == clauseTag {
|
||||
return true, slices.Clone(exn.Params)
|
||||
}
|
||||
case wasm.CatchKindCatchRef:
|
||||
if exn.Tag == clauseTag {
|
||||
values = slices.Clone(exn.Params)
|
||||
values = append(values, uint64(uintptr(unsafe.Pointer(exn))))
|
||||
return true, values
|
||||
}
|
||||
case wasm.CatchKindCatchAll:
|
||||
return true, nil
|
||||
case wasm.CatchKindCatchAllRef:
|
||||
return true, []uint64{uint64(uintptr(unsafe.Pointer(exn)))}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// searchExceptionTable searches the compiled function's static exception table
|
||||
// for a handler matching the given exception at the current PC. Returns the
|
||||
// matched clause and catch values, or nil if no handler matches. Searches
|
||||
// backwards so inner try_tables (which have higher indices) are checked first.
|
||||
// This function is pure — it does not modify callEngine state.
|
||||
func searchExceptionTable(exn *wasm.Exception, frame *callFrame) (*exceptionTableCatchClause, []uint64) {
|
||||
table := frame.f.parent.exceptionTable
|
||||
pc := frame.pc
|
||||
for i := len(table) - 1; i >= 0; i-- {
|
||||
entry := &table[i]
|
||||
if pc < entry.startPC || pc >= entry.endPC {
|
||||
continue
|
||||
}
|
||||
for j := range entry.clauses {
|
||||
clause := &entry.clauses[j]
|
||||
var clauseTag *wasm.TagInstance
|
||||
if clause.kind == wasm.CatchKindCatch || clause.kind == wasm.CatchKindCatchRef {
|
||||
clauseTag = frame.f.moduleInstance.Tags[clause.tagIndex]
|
||||
}
|
||||
matched, values := matchCatchClause(clause.kind, clauseTag, exn)
|
||||
if matched {
|
||||
return clause, values
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// applyExceptionHandler applies a matched exception table clause to the callEngine state.
|
||||
func (ce *callEngine) applyExceptionHandler(frame *callFrame, clause *exceptionTableCatchClause, values []uint64) {
|
||||
ce.stack = ce.stack[:frame.base-frame.f.funcType.ParamNumInUint64+clause.targetStackDepth]
|
||||
ce.stack = append(ce.stack, values...)
|
||||
frame.pc = clause.targetPC
|
||||
}
|
||||
|
||||
// callWithUnwind calls the target function with support for stack unwinding
|
||||
// (exception handling and snapshot restores). Returns true if the frame was
|
||||
// unwound (caller should refresh frame/body/bodyLen and continue the loop).
|
||||
// Returns false on normal return (caller should do frame.pc++).
|
||||
func (ce *callEngine) callWithUnwind(ctx context.Context, m *wasm.ModuleInstance, tf *function) bool {
|
||||
// Short-circuit: skip defer/recover overhead when neither exception
|
||||
// handlers nor the snapshotter are active for the calling frame.
|
||||
frame := ce.frames[len(ce.frames)-1]
|
||||
if len(frame.f.parent.exceptionTable) == 0 && ctx.Value(expctxkeys.EnableSnapshotterKey{}) == nil {
|
||||
ce.callFunction(ctx, m, tf)
|
||||
return false
|
||||
}
|
||||
|
||||
callerFrameCount := len(ce.frames)
|
||||
caught := false
|
||||
func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if v, ok := r.(restorable); ok && v.canRestore(ce, callerFrameCount) {
|
||||
v.doRestore(ce, callerFrameCount)
|
||||
caught = true
|
||||
return
|
||||
}
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
ce.callFunction(ctx, m, tf)
|
||||
}()
|
||||
return caught
|
||||
}
|
||||
|
||||
func (e *moduleEngine) newCallEngine(compiled *function) *callEngine {
|
||||
return &callEngine{f: compiled}
|
||||
}
|
||||
@@ -233,6 +354,7 @@ type callFrame struct {
|
||||
type compiledFunction struct {
|
||||
source *wasm.Module
|
||||
body []unionOperation
|
||||
exceptionTable []exceptionTableEntry
|
||||
listener experimental.FunctionListener
|
||||
offsetsInWasmBinary []uint64
|
||||
hostFn interface{}
|
||||
@@ -284,7 +406,11 @@ func (s *snapshot) Restore(ret []uint64) {
|
||||
panic(s)
|
||||
}
|
||||
|
||||
func (s *snapshot) doRestore() {
|
||||
func (s *snapshot) canRestore(ce *callEngine, _ int) bool {
|
||||
return s.ce == ce
|
||||
}
|
||||
|
||||
func (s *snapshot) doRestore(_ *callEngine, _ int) {
|
||||
ce := s.ce
|
||||
|
||||
ce.stack = s.stack
|
||||
@@ -493,8 +619,45 @@ func (e *engine) lowerIR(ir *compilationResult, ret *compiledFunction) error {
|
||||
}
|
||||
case operationKindTailCallReturnCallIndirect:
|
||||
e.setLabelAddress(&op.Us[1], label(op.Us[1]), labelAddressResolutions)
|
||||
case operationKindBrOnNull:
|
||||
e.setLabelAddress(&op.U1, label(op.U1), labelAddressResolutions)
|
||||
e.setLabelAddress(&op.U2, label(op.U2), labelAddressResolutions)
|
||||
case operationKindBrOnNonNull:
|
||||
e.setLabelAddress(&op.U1, label(op.U1), labelAddressResolutions)
|
||||
e.setLabelAddress(&op.U2, label(op.U2), labelAddressResolutions)
|
||||
case operationKindReturnCallRef:
|
||||
e.setLabelAddress(&op.Us[1], label(op.Us[1]), labelAddressResolutions)
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve exception table entries (translate labels to PC).
|
||||
if len(ir.PendingExceptionTable) > 0 {
|
||||
ret.exceptionTable = make([]exceptionTableEntry, len(ir.PendingExceptionTable))
|
||||
for i, pe := range ir.PendingExceptionTable {
|
||||
contLabel := newLabel(labelKindContinuation, pe.continuationFrameID)
|
||||
var endPC uint64
|
||||
e.setLabelAddress(&endPC, contLabel, labelAddressResolutions)
|
||||
|
||||
clauses := make([]exceptionTableCatchClause, len(pe.clauses))
|
||||
for j, clause := range pe.clauses {
|
||||
var targetPC uint64
|
||||
e.setLabelAddress(&targetPC, clause.targetLabel, labelAddressResolutions)
|
||||
clauses[j] = exceptionTableCatchClause{
|
||||
kind: clause.kind,
|
||||
tagIndex: clause.tagIndex,
|
||||
targetPC: targetPC,
|
||||
targetStackDepth: clause.targetStackDepth,
|
||||
}
|
||||
}
|
||||
|
||||
ret.exceptionTable[i] = exceptionTableEntry{
|
||||
startPC: uint64(pe.startOpIndex),
|
||||
endPC: endPC,
|
||||
clauses: clauses,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -644,6 +807,11 @@ func (ce *callEngine) recoverOnCall(ctx context.Context, m *wasm.ModuleInstance,
|
||||
panic(s)
|
||||
}
|
||||
|
||||
// If an exception reached the top level without being caught, convert it to an uncaught exception error.
|
||||
if _, ok := v.(*thrownException); ok {
|
||||
v = wasmruntime.ErrRuntimeUncaughtException
|
||||
}
|
||||
|
||||
builder := wasmdebug.NewErrorBuilder()
|
||||
frameCount := len(ce.frames)
|
||||
functionListeners := make([]functionListenerInvocation, 0, 16)
|
||||
@@ -761,30 +929,26 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
|
||||
ce.drop(op.Us[v+1])
|
||||
frame.pc = op.Us[v]
|
||||
case operationKindCall:
|
||||
func() {
|
||||
if ctx.Value(expctxkeys.EnableSnapshotterKey{}) != nil {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if s, ok := r.(*snapshot); ok && s.ce == ce {
|
||||
s.doRestore()
|
||||
frame = ce.frames[len(ce.frames)-1]
|
||||
body = frame.f.parent.body
|
||||
bodyLen = uint64(len(body))
|
||||
} else {
|
||||
panic(r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
ce.callFunction(ctx, f.moduleInstance, &functions[op.U1])
|
||||
}()
|
||||
frameUnwound := ce.callWithUnwind(ctx, f.moduleInstance, &functions[op.U1])
|
||||
if frameUnwound {
|
||||
frame = ce.frames[len(ce.frames)-1]
|
||||
body = frame.f.parent.body
|
||||
bodyLen = uint64(len(body))
|
||||
continue
|
||||
}
|
||||
frame.pc++
|
||||
case operationKindCallIndirect:
|
||||
offset := ce.popValue()
|
||||
table := tables[op.U2]
|
||||
tf := ce.functionForOffset(table, offset, typeIDs[op.U1])
|
||||
|
||||
ce.callFunction(ctx, f.moduleInstance, tf)
|
||||
frameUnwound := ce.callWithUnwind(ctx, f.moduleInstance, tf)
|
||||
if frameUnwound {
|
||||
frame = ce.frames[len(ce.frames)-1]
|
||||
body = frame.f.parent.body
|
||||
bodyLen = uint64(len(body))
|
||||
continue
|
||||
}
|
||||
frame.pc++
|
||||
case operationKindDrop:
|
||||
ce.drop(op.U1)
|
||||
@@ -973,7 +1137,10 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
|
||||
case operationKindNe:
|
||||
var b bool
|
||||
switch unsignedType(op.B1) {
|
||||
case unsignedTypeI32, unsignedTypeI64:
|
||||
case unsignedTypeI32:
|
||||
v2, v1 := ce.popValue(), ce.popValue()
|
||||
b = uint32(v1) != uint32(v2)
|
||||
case unsignedTypeI64:
|
||||
v2, v1 := ce.popValue(), ce.popValue()
|
||||
b = v1 != v2
|
||||
case unsignedTypeF32:
|
||||
@@ -990,7 +1157,11 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
|
||||
}
|
||||
frame.pc++
|
||||
case operationKindEqz:
|
||||
if ce.popValue() == 0 {
|
||||
v := ce.popValue()
|
||||
if unsignedInt(op.B1) == unsignedInt32 {
|
||||
v = uint64(uint32(v))
|
||||
}
|
||||
if v == 0 {
|
||||
ce.pushValue(1)
|
||||
} else {
|
||||
ce.pushValue(0)
|
||||
@@ -1005,7 +1176,9 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
|
||||
b = int32(v1) < int32(v2)
|
||||
case signedTypeInt64:
|
||||
b = int64(v1) < int64(v2)
|
||||
case signedTypeUint32, signedTypeUint64:
|
||||
case signedTypeUint32:
|
||||
b = uint32(v1) < uint32(v2)
|
||||
case signedTypeUint64:
|
||||
b = v1 < v2
|
||||
case signedTypeFloat32:
|
||||
b = math.Float32frombits(uint32(v1)) < math.Float32frombits(uint32(v2))
|
||||
@@ -1027,7 +1200,9 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
|
||||
b = int32(v1) > int32(v2)
|
||||
case signedTypeInt64:
|
||||
b = int64(v1) > int64(v2)
|
||||
case signedTypeUint32, signedTypeUint64:
|
||||
case signedTypeUint32:
|
||||
b = uint32(v1) > uint32(v2)
|
||||
case signedTypeUint64:
|
||||
b = v1 > v2
|
||||
case signedTypeFloat32:
|
||||
b = math.Float32frombits(uint32(v1)) > math.Float32frombits(uint32(v2))
|
||||
@@ -1049,7 +1224,9 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
|
||||
b = int32(v1) <= int32(v2)
|
||||
case signedTypeInt64:
|
||||
b = int64(v1) <= int64(v2)
|
||||
case signedTypeUint32, signedTypeUint64:
|
||||
case signedTypeUint32:
|
||||
b = uint32(v1) <= uint32(v2)
|
||||
case signedTypeUint64:
|
||||
b = v1 <= v2
|
||||
case signedTypeFloat32:
|
||||
b = math.Float32frombits(uint32(v1)) <= math.Float32frombits(uint32(v2))
|
||||
@@ -1071,7 +1248,9 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
|
||||
b = int32(v1) >= int32(v2)
|
||||
case signedTypeInt64:
|
||||
b = int64(v1) >= int64(v2)
|
||||
case signedTypeUint32, signedTypeUint64:
|
||||
case signedTypeUint32:
|
||||
b = uint32(v1) >= uint32(v2)
|
||||
case signedTypeUint64:
|
||||
b = v1 >= v2
|
||||
case signedTypeFloat32:
|
||||
b = math.Float32frombits(uint32(v1)) >= math.Float32frombits(uint32(v2))
|
||||
@@ -1166,6 +1345,10 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
|
||||
v2, v1 := ce.popValue(), ce.popValue()
|
||||
switch t {
|
||||
case signedTypeFloat32, signedTypeFloat64: // not integers
|
||||
case signedTypeInt32, signedTypeUint32:
|
||||
if uint32(v2) == 0 {
|
||||
panic(wasmruntime.ErrRuntimeIntegerDivideByZero)
|
||||
}
|
||||
default:
|
||||
if v2 == 0 {
|
||||
panic(wasmruntime.ErrRuntimeIntegerDivideByZero)
|
||||
@@ -1203,8 +1386,15 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
|
||||
frame.pc++
|
||||
case operationKindRem:
|
||||
v2, v1 := ce.popValue(), ce.popValue()
|
||||
if v2 == 0 {
|
||||
panic(wasmruntime.ErrRuntimeIntegerDivideByZero)
|
||||
switch signedInt(op.B1) {
|
||||
case signedInt32, signedUint32:
|
||||
if uint32(v2) == 0 {
|
||||
panic(wasmruntime.ErrRuntimeIntegerDivideByZero)
|
||||
}
|
||||
default:
|
||||
if v2 == 0 {
|
||||
panic(wasmruntime.ErrRuntimeIntegerDivideByZero)
|
||||
}
|
||||
}
|
||||
switch signedInt(op.B1) {
|
||||
case signedInt32:
|
||||
@@ -4346,6 +4536,35 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
|
||||
memoryInst.Mux.Unlock()
|
||||
ce.pushValue(uint64(old))
|
||||
frame.pc++
|
||||
case operationKindThrow:
|
||||
tagIndex := uint32(op.U1)
|
||||
tag := moduleInst.Tags[tagIndex]
|
||||
paramCount := len(tag.Type.Params)
|
||||
params := make([]uint64, paramCount)
|
||||
for i := paramCount - 1; i >= 0; i-- {
|
||||
params[i] = ce.popValue()
|
||||
}
|
||||
exn := &wasm.Exception{Tag: tag, Params: params}
|
||||
if clause, values := searchExceptionTable(exn, frame); clause != nil {
|
||||
ce.applyExceptionHandler(frame, clause, values)
|
||||
continue
|
||||
}
|
||||
panic(&thrownException{exception: exn})
|
||||
|
||||
case operationKindThrowRef:
|
||||
v := ce.popValue()
|
||||
if v == 0 {
|
||||
panic(wasmruntime.ErrRuntimeNullReference) // throw_ref on null exnref traps
|
||||
}
|
||||
// Read the Exception pointer directly from the uint64 value to avoid
|
||||
// conversion from uintptr into unsafe.Pointer, which triggers checkptr.
|
||||
exn := *(**wasm.Exception)(unsafe.Pointer(&v))
|
||||
if clause, values := searchExceptionTable(exn, frame); clause != nil {
|
||||
ce.applyExceptionHandler(frame, clause, values)
|
||||
continue
|
||||
}
|
||||
panic(&thrownException{exception: exn})
|
||||
|
||||
case operationKindTailCallReturnCall:
|
||||
f := &functions[op.U1]
|
||||
ce.dropForTailCall(frame, f)
|
||||
@@ -4361,7 +4580,13 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
|
||||
// For details, see internal/engine/RATIONALE.md
|
||||
if tf.moduleInstance != f.moduleInstance {
|
||||
// Revert to a normal call.
|
||||
ce.callFunction(ctx, f.moduleInstance, tf)
|
||||
frameUnwound := ce.callWithUnwind(ctx, f.moduleInstance, tf)
|
||||
if frameUnwound {
|
||||
frame = ce.frames[len(ce.frames)-1]
|
||||
body = frame.f.parent.body
|
||||
bodyLen = uint64(len(body))
|
||||
continue
|
||||
}
|
||||
// Return
|
||||
ce.drop(op.Us[0])
|
||||
// Jump to the function frame (return)
|
||||
@@ -4372,6 +4597,73 @@ func (ce *callEngine) callNativeFunc(ctx context.Context, m *wasm.ModuleInstance
|
||||
ce.dropForTailCall(frame, tf)
|
||||
body, bodyLen = ce.resetPc(frame, tf)
|
||||
|
||||
case operationKindCallRef:
|
||||
ref := ce.popValue()
|
||||
if ref == 0 {
|
||||
panic(wasmruntime.ErrRuntimeNullReference)
|
||||
}
|
||||
tf := functionFromUintptr(uintptr(ref))
|
||||
|
||||
frameUnwound := ce.callWithUnwind(ctx, f.moduleInstance, tf)
|
||||
if frameUnwound {
|
||||
frame = ce.frames[len(ce.frames)-1]
|
||||
body = frame.f.parent.body
|
||||
bodyLen = uint64(len(body))
|
||||
continue
|
||||
}
|
||||
frame.pc++
|
||||
|
||||
case operationKindReturnCallRef:
|
||||
ref := ce.popValue()
|
||||
if ref == 0 {
|
||||
panic(wasmruntime.ErrRuntimeNullReference)
|
||||
}
|
||||
tf := functionFromUintptr(uintptr(ref))
|
||||
|
||||
if tf.moduleInstance != f.moduleInstance {
|
||||
frameUnwound := ce.callWithUnwind(ctx, f.moduleInstance, tf)
|
||||
if frameUnwound {
|
||||
frame = ce.frames[len(ce.frames)-1]
|
||||
body = frame.f.parent.body
|
||||
bodyLen = uint64(len(body))
|
||||
continue
|
||||
}
|
||||
ce.drop(op.Us[0])
|
||||
frame.pc = op.Us[1]
|
||||
continue
|
||||
}
|
||||
|
||||
ce.dropForTailCall(frame, tf)
|
||||
body, bodyLen = ce.resetPc(frame, tf)
|
||||
|
||||
case operationKindRefAsNonNull:
|
||||
ref := ce.popValue()
|
||||
if ref == 0 {
|
||||
panic(wasmruntime.ErrRuntimeNullReference)
|
||||
}
|
||||
ce.pushValue(ref)
|
||||
frame.pc++
|
||||
|
||||
case operationKindBrOnNull:
|
||||
ref := ce.popValue()
|
||||
if ref == 0 {
|
||||
ce.drop(op.U3)
|
||||
frame.pc = op.U1
|
||||
} else {
|
||||
ce.pushValue(ref)
|
||||
frame.pc = op.U2
|
||||
}
|
||||
|
||||
case operationKindBrOnNonNull:
|
||||
ref := ce.popValue()
|
||||
if ref != 0 {
|
||||
ce.drop(op.U3)
|
||||
ce.pushValue(ref)
|
||||
frame.pc = op.U1
|
||||
} else {
|
||||
frame.pc = op.U2
|
||||
}
|
||||
|
||||
default:
|
||||
frame.pc++
|
||||
}
|
||||
@@ -4623,7 +4915,9 @@ func (ce *callEngine) callNativeFuncWithListener(ctx context.Context, m *wasm.Mo
|
||||
// popMemoryOffset takes a memory offset off the stack for use in load and store instructions.
|
||||
// As the top of stack value is 64-bit, this ensures it is in range before returning it.
|
||||
func (ce *callEngine) popMemoryOffset(op *unionOperation) uint32 {
|
||||
offset := op.U2 + ce.popValue()
|
||||
// Memory addresses are i32; mask to 32 bits to ignore any
|
||||
// garbage in the upper bits of the uint64 stack slot.
|
||||
offset := op.U2 + uint64(uint32(ce.popValue()))
|
||||
if offset > math.MaxUint32 {
|
||||
panic(wasmruntime.ErrRuntimeOutOfBoundsMemoryAccess)
|
||||
}
|
||||
|
||||
+133
@@ -449,6 +449,20 @@ func (o operationKind) String() (ret string) {
|
||||
ret = "operationKindTailCallReturnCall"
|
||||
case operationKindTailCallReturnCallIndirect:
|
||||
ret = "operationKindTailCallReturnCallIndirect"
|
||||
case operationKindThrow:
|
||||
ret = "operationKindThrow"
|
||||
case operationKindThrowRef:
|
||||
ret = "operationKindThrowRef"
|
||||
case operationKindCallRef:
|
||||
ret = "operationKindCallRef"
|
||||
case operationKindReturnCallRef:
|
||||
ret = "operationKindReturnCallRef"
|
||||
case operationKindRefAsNonNull:
|
||||
ret = "operationKindRefAsNonNull"
|
||||
case operationKindBrOnNull:
|
||||
ret = "operationKindBrOnNull"
|
||||
case operationKindBrOnNonNull:
|
||||
ret = "operationKindBrOnNonNull"
|
||||
default:
|
||||
panic(fmt.Errorf("unknown operation %d", o))
|
||||
}
|
||||
@@ -777,6 +791,22 @@ const (
|
||||
// operationKindTailCallReturnCallIndirect is the Kind for newOperationKindTailCallReturnCallIndirect.
|
||||
operationKindTailCallReturnCallIndirect
|
||||
|
||||
// operationKindThrow is the Kind for throw instruction.
|
||||
operationKindThrow
|
||||
// operationKindThrowRef is the Kind for throw_ref instruction.
|
||||
operationKindThrowRef
|
||||
|
||||
// operationKindCallRef is the Kind for call_ref instruction.
|
||||
operationKindCallRef
|
||||
// operationKindReturnCallRef is the Kind for return_call_ref instruction.
|
||||
operationKindReturnCallRef
|
||||
// operationKindRefAsNonNull is the Kind for ref.as_non_null instruction.
|
||||
operationKindRefAsNonNull
|
||||
// operationKindBrOnNull is the Kind for br_on_null instruction.
|
||||
operationKindBrOnNull
|
||||
// operationKindBrOnNonNull is the Kind for br_on_non_null instruction.
|
||||
operationKindBrOnNonNull
|
||||
|
||||
// operationKindEnd is always placed at the bottom of this iota definition to be used in the test.
|
||||
operationKindEnd
|
||||
)
|
||||
@@ -1112,6 +1142,27 @@ func (o unionOperation) String() string {
|
||||
case operationKindTailCallReturnCallIndirect:
|
||||
return fmt.Sprintf("%s %d %d", o.Kind, o.U1, o.U2)
|
||||
|
||||
case operationKindThrow:
|
||||
return fmt.Sprintf("%s %d", o.Kind, o.U1)
|
||||
|
||||
case operationKindThrowRef:
|
||||
return o.Kind.String()
|
||||
|
||||
case operationKindCallRef:
|
||||
return fmt.Sprintf("%s %d", o.Kind, o.U1)
|
||||
|
||||
case operationKindReturnCallRef:
|
||||
return fmt.Sprintf("%s %d", o.Kind, o.U1)
|
||||
|
||||
case operationKindRefAsNonNull:
|
||||
return o.Kind.String()
|
||||
|
||||
case operationKindBrOnNull:
|
||||
return fmt.Sprintf("%s %s %s", o.Kind, label(o.U1).String(), label(o.U2).String())
|
||||
|
||||
case operationKindBrOnNonNull:
|
||||
return fmt.Sprintf("%s %s %s", o.Kind, label(o.U1).String(), label(o.U2).String())
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("TODO: %v", o.Kind))
|
||||
}
|
||||
@@ -2843,3 +2894,85 @@ func newOperationTailCallReturnCall(functionIndex uint32) unionOperation {
|
||||
func newOperationTailCallReturnCallIndirect(typeIndex, tableIndex uint32, dropDepth inclusiveRange, l label) unionOperation {
|
||||
return unionOperation{Kind: operationKindTailCallReturnCallIndirect, U1: uint64(typeIndex), U2: uint64(tableIndex), Us: []uint64{dropDepth.AsU64(), uint64(l)}}
|
||||
}
|
||||
|
||||
// newOperationThrow is a constructor for unionOperation with operationKindThrow.
|
||||
// U1 stores the tag index.
|
||||
func newOperationThrow(tagIndex uint32) unionOperation {
|
||||
return unionOperation{Kind: operationKindThrow, U1: uint64(tagIndex)}
|
||||
}
|
||||
|
||||
// newOperationThrowRef is a constructor for unionOperation with operationKindThrowRef.
|
||||
func newOperationThrowRef() unionOperation {
|
||||
return unionOperation{Kind: operationKindThrowRef}
|
||||
}
|
||||
|
||||
// newOperationCallRef is a constructor for operationKindCallRef.
|
||||
// U1 = type index.
|
||||
func newOperationCallRef(typeIndex uint32) unionOperation {
|
||||
return unionOperation{Kind: operationKindCallRef, U1: uint64(typeIndex)}
|
||||
}
|
||||
|
||||
// newOperationReturnCallRef is a constructor for operationKindReturnCallRef.
|
||||
// U1 = type index, U2 = table index (unused), Us = [dropDepth, label].
|
||||
func newOperationReturnCallRef(typeIndex uint32, dropDepth inclusiveRange, l label) unionOperation {
|
||||
return unionOperation{Kind: operationKindReturnCallRef, U1: uint64(typeIndex), Us: []uint64{dropDepth.AsU64(), uint64(l)}}
|
||||
}
|
||||
|
||||
// newOperationRefAsNonNull is a constructor for operationKindRefAsNonNull.
|
||||
func newOperationRefAsNonNull() unionOperation {
|
||||
return unionOperation{Kind: operationKindRefAsNonNull}
|
||||
}
|
||||
|
||||
// newOperationBrOnNull is a constructor for operationKindBrOnNull.
|
||||
// If ref is null, branch to U1 (thenTarget) with drop U3; otherwise continue at U2 (elseTarget).
|
||||
func newOperationBrOnNull(thenTarget, elseTarget label, thenDrop inclusiveRange) unionOperation {
|
||||
return unionOperation{
|
||||
Kind: operationKindBrOnNull,
|
||||
U1: uint64(thenTarget),
|
||||
U2: uint64(elseTarget),
|
||||
U3: thenDrop.AsU64(),
|
||||
}
|
||||
}
|
||||
|
||||
// newOperationBrOnNonNull is a constructor for operationKindBrOnNonNull.
|
||||
// If ref is non-null, branch to U1 (thenTarget) with drop U3; otherwise continue at U2 (elseTarget).
|
||||
func newOperationBrOnNonNull(thenTarget, elseTarget label, thenDrop inclusiveRange) unionOperation {
|
||||
return unionOperation{
|
||||
Kind: operationKindBrOnNonNull,
|
||||
U1: uint64(thenTarget),
|
||||
U2: uint64(elseTarget),
|
||||
U3: thenDrop.AsU64(),
|
||||
}
|
||||
}
|
||||
|
||||
// exceptionTableEntry represents one try_table's exception handling scope.
|
||||
// Built at compile time and stored per compiledFunction.
|
||||
type exceptionTableEntry struct {
|
||||
startPC uint64 // first PC inside the try_table body
|
||||
endPC uint64 // PC of continuation label (exclusive)
|
||||
clauses []exceptionTableCatchClause
|
||||
}
|
||||
|
||||
// exceptionTableCatchClause is a single catch clause within an exception table entry.
|
||||
type exceptionTableCatchClause struct {
|
||||
kind byte // CatchKindCatch, CatchKindCatchRef, CatchKindCatchAll, CatchKindCatchAllRef
|
||||
tagIndex uint32 // tag index for catch/catch_ref
|
||||
targetPC uint64 // resolved PC to jump to on match
|
||||
targetStackDepth int // = targetFrame.originalStackLenWithoutParamUint64
|
||||
}
|
||||
|
||||
// pendingExceptionTableEntry is an unresolved exception table entry built during compilation.
|
||||
// Labels are resolved to final PCs in lowerIR.
|
||||
type pendingExceptionTableEntry struct {
|
||||
startOpIndex int // index in Operations[] of the first instruction inside the try_table body
|
||||
continuationFrameID uint32
|
||||
clauses []pendingCatchClause
|
||||
}
|
||||
|
||||
// pendingCatchClause is an unresolved catch clause within a pending exception table entry.
|
||||
type pendingCatchClause struct {
|
||||
kind byte
|
||||
tagIndex uint32
|
||||
targetLabel label // unresolved label, resolved in lowerIR
|
||||
targetStackDepth int // = targetFrame.originalStackLenWithoutParamUint64
|
||||
}
|
||||
|
||||
+62
-4
@@ -268,6 +268,9 @@ func (c *compiler) wasmOpcodeSignature(op wasm.Opcode, index uint32) (*signature
|
||||
return signature_I32_None, nil
|
||||
case wasm.OpcodeElse, wasm.OpcodeEnd, wasm.OpcodeBr:
|
||||
return signature_None_None, nil
|
||||
case wasm.OpcodeThrow, wasm.OpcodeThrowRef, wasm.OpcodeTryTable:
|
||||
// Stack manipulation handled dynamically by the compiler.
|
||||
return signature_None_None, nil
|
||||
case wasm.OpcodeBrIf, wasm.OpcodeBrTable:
|
||||
return signature_I32_None, nil
|
||||
case wasm.OpcodeReturn:
|
||||
@@ -276,6 +279,17 @@ func (c *compiler) wasmOpcodeSignature(op wasm.Opcode, index uint32) (*signature
|
||||
return c.funcTypeToSigs.get(c.funcs[index], false /* direct */), nil
|
||||
case wasm.OpcodeCallIndirect, wasm.OpcodeTailCallReturnCallIndirect:
|
||||
return c.funcTypeToSigs.get(index, true /* call_indirect */), nil
|
||||
case wasm.OpcodeCallRef, wasm.OpcodeReturnCallRef:
|
||||
return c.funcTypeToSigs.getCallRef(index), nil
|
||||
case wasm.OpcodeRefAsNonNull:
|
||||
// Pop a ref (i64), push it back (i64). Traps if null at runtime.
|
||||
return signature_I64_I64, nil
|
||||
case wasm.OpcodeBrOnNull:
|
||||
// Pop a ref (i64). If null, branch; if non-null, push ref back.
|
||||
return signature_I64_None, nil
|
||||
case wasm.OpcodeBrOnNonNull:
|
||||
// Pop a ref (i64). If non-null, push and branch; if null, fall through.
|
||||
return signature_I64_None, nil
|
||||
case wasm.OpcodeDrop:
|
||||
return signature_Unknown_None, nil
|
||||
case wasm.OpcodeSelect, wasm.OpcodeTypedSelect:
|
||||
@@ -650,6 +664,7 @@ func (c *compiler) wasmOpcodeSignature(op wasm.Opcode, index uint32) (*signature
|
||||
type funcTypeToIRSignatures struct {
|
||||
directCalls []*signature
|
||||
indirectCalls []*signature
|
||||
callRefCalls []*signature
|
||||
wasmTypes []wasm.FunctionType
|
||||
}
|
||||
|
||||
@@ -694,13 +709,36 @@ func (f *funcTypeToIRSignatures) get(typeIndex wasm.Index, indirect bool) *signa
|
||||
return sig
|
||||
}
|
||||
|
||||
// getCallRef returns the *signature for call_ref, which is like a direct call
|
||||
// but with an extra i64 input for the funcref operand.
|
||||
func (f *funcTypeToIRSignatures) getCallRef(typeIndex wasm.Index) *signature {
|
||||
if sig := f.callRefCalls[typeIndex]; sig != nil {
|
||||
return sig
|
||||
}
|
||||
tp := &f.wasmTypes[typeIndex]
|
||||
sig := &signature{
|
||||
in: make([]unsignedType, 0, len(tp.Params)+1),
|
||||
out: make([]unsignedType, 0, len(tp.Results)),
|
||||
}
|
||||
for _, vt := range tp.Params {
|
||||
sig.in = append(sig.in, wasmValueTypeTounsignedType(vt))
|
||||
}
|
||||
sig.in = append(sig.in, unsignedTypeI64) // funcref operand
|
||||
for _, vt := range tp.Results {
|
||||
sig.out = append(sig.out, wasmValueTypeTounsignedType(vt))
|
||||
}
|
||||
f.callRefCalls[typeIndex] = sig
|
||||
return sig
|
||||
}
|
||||
|
||||
func wasmValueTypeTounsignedType(vt wasm.ValueType) unsignedType {
|
||||
switch vt {
|
||||
case wasm.ValueTypeI32:
|
||||
return unsignedTypeI32
|
||||
case wasm.ValueTypeI64,
|
||||
// From interpreterir layer, ref type values are opaque 64-bit pointers.
|
||||
wasm.ValueTypeExternref, wasm.ValueTypeFuncref:
|
||||
wasm.ValueTypeExternref, wasm.ValueTypeFuncref,
|
||||
wasm.ValueTypeExnref:
|
||||
return unsignedTypeI64
|
||||
case wasm.ValueTypeF32:
|
||||
return unsignedTypeF32
|
||||
@@ -708,6 +746,11 @@ func wasmValueTypeTounsignedType(vt wasm.ValueType) unsignedType {
|
||||
return unsignedTypeF64
|
||||
case wasm.ValueTypeV128:
|
||||
return unsignedTypeV128
|
||||
default:
|
||||
// Concrete ref types (ref $t) have variable bit patterns.
|
||||
if vt.IsRef() {
|
||||
return unsignedTypeI64
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
@@ -718,7 +761,8 @@ func wasmValueTypeToUnsignedOutSignature(vt wasm.ValueType) *signature {
|
||||
return signature_None_I32
|
||||
case wasm.ValueTypeI64,
|
||||
// From interpreterir layer, ref type values are opaque 64-bit pointers.
|
||||
wasm.ValueTypeExternref, wasm.ValueTypeFuncref:
|
||||
wasm.ValueTypeExternref, wasm.ValueTypeFuncref,
|
||||
wasm.ValueTypeExnref:
|
||||
return signature_None_I64
|
||||
case wasm.ValueTypeF32:
|
||||
return signature_None_F32
|
||||
@@ -726,6 +770,10 @@ func wasmValueTypeToUnsignedOutSignature(vt wasm.ValueType) *signature {
|
||||
return signature_None_F64
|
||||
case wasm.ValueTypeV128:
|
||||
return signature_None_V128
|
||||
default:
|
||||
if vt.IsRef() {
|
||||
return signature_None_I64
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
@@ -736,7 +784,8 @@ func wasmValueTypeToUnsignedInSignature(vt wasm.ValueType) *signature {
|
||||
return signature_I32_None
|
||||
case wasm.ValueTypeI64,
|
||||
// From interpreterir layer, ref type values are opaque 64-bit pointers.
|
||||
wasm.ValueTypeExternref, wasm.ValueTypeFuncref:
|
||||
wasm.ValueTypeExternref, wasm.ValueTypeFuncref,
|
||||
wasm.ValueTypeExnref:
|
||||
return signature_I64_None
|
||||
case wasm.ValueTypeF32:
|
||||
return signature_F32_None
|
||||
@@ -744,6 +793,10 @@ func wasmValueTypeToUnsignedInSignature(vt wasm.ValueType) *signature {
|
||||
return signature_F64_None
|
||||
case wasm.ValueTypeV128:
|
||||
return signature_V128_None
|
||||
default:
|
||||
if vt.IsRef() {
|
||||
return signature_I64_None
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
@@ -754,7 +807,8 @@ func wasmValueTypeToUnsignedInOutSignature(vt wasm.ValueType) *signature {
|
||||
return signature_I32_I32
|
||||
case wasm.ValueTypeI64,
|
||||
// At interpreterir layer, ref type values are opaque 64-bit pointers.
|
||||
wasm.ValueTypeExternref, wasm.ValueTypeFuncref:
|
||||
wasm.ValueTypeExternref, wasm.ValueTypeFuncref,
|
||||
wasm.ValueTypeExnref:
|
||||
return signature_I64_I64
|
||||
case wasm.ValueTypeF32:
|
||||
return signature_F32_F32
|
||||
@@ -762,6 +816,10 @@ func wasmValueTypeToUnsignedInOutSignature(vt wasm.ValueType) *signature {
|
||||
return signature_F64_F64
|
||||
case wasm.ValueTypeV128:
|
||||
return signature_V128_V128
|
||||
default:
|
||||
if vt.IsRef() {
|
||||
return signature_I64_I64
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user