updated vendor

This commit is contained in:
2026-06-16 08:02:19 +02:00
parent 2f7f99d3f0
commit 77299d0c64
1283 changed files with 67302 additions and 208958 deletions
+215 -6
View File
@@ -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
}
@@ -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)
}
@@ -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
}
@@ -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")
}