diff --git a/disasm/disasm.go b/disasm/disasm.go index 66cda640..d9f6fc16 100644 --- a/disasm/disasm.go +++ b/disasm/disasm.go @@ -52,10 +52,16 @@ type StackInfo struct { type BlockInfo struct { Start bool // If true, this instruction starts a block. Else this instruction ends it. Signature wasm.BlockType // The block signature - // The index to the accompanying control operator. - // For 'if', this is an index to the 'else' operator - // For else/loop/block, the index is to the 'end' operator - PairIndex int + + // Indices to the accompanying control operator. + // For 'if', this is the index to the 'else' operator. + IfElseIndex int + // For 'else', this is the index to the 'if' operator. + ElseIfIndex int + // The index to the `end' operator for if/else/loop/block. + EndIndex int + // For end, it is the index to the operator that starts the block. + BlockStartIndex int } // Disassembly is the result of disassembling a WebAssembly function. @@ -133,7 +139,7 @@ func Disassemble(fn wasm.Function, module *wasm.Module) (*Disassembly, error) { instr.Unreachable = !isInstrReachable(blockPolymorphicOps) } - logger.Printf("op: %s, ureachable: %v", opStr.Name, instr.Unreachable) + logger.Printf("op: %s, unreachable: %v", opStr.Name, instr.Unreachable) if !opStr.Polymorphic && !instr.Unreachable { top := int(stackDepths.Top()) top -= len(opStr.Args) @@ -157,7 +163,7 @@ func Disassemble(fn wasm.Function, module *wasm.Module) (*Disassembly, error) { } case ops.Select: if !instr.Unreachable { - stackDepths.SetTop(stackDepths.Top() - 3) + stackDepths.SetTop(stackDepths.Top() - 2) } case ops.Return: if !instr.Unreachable { @@ -173,10 +179,14 @@ func Disassemble(fn wasm.Function, module *wasm.Module) (*Disassembly, error) { instr.Block = &BlockInfo{ Start: false, Signature: blockSig, - PairIndex: int(blockStartIndex), } - - disas.Code[blockStartIndex].Block.PairIndex = curIndex + if op == ops.End { + instr.Block.BlockStartIndex = int(blockStartIndex) + disas.Code[blockStartIndex].Block.EndIndex = curIndex + } else { // ops.Else + instr.Block.ElseIfIndex = int(blockStartIndex) + disas.Code[blockStartIndex].Block.IfElseIndex = int(blockStartIndex) + } // The max depth reached while execing the last block // If the signature of the current block is not empty, @@ -201,10 +211,12 @@ func Disassemble(fn wasm.Function, module *wasm.Module) (*Disassembly, error) { StackTopDiff: int64(elemsDiscard), PreserveTop: blockSig != wasm.BlockTypeEmpty, } + logger.Printf("discard %d elements, preserve top: %v", elemsDiscard, instr.NewStack.PreserveTop) } else { instr.NewStack = &StackInfo{} } + logger.Printf("setting new stack for %s block (%d)", disas.Code[blockStartIndex].Op.Name, blockStartIndex) disas.Code[blockStartIndex].NewStack = instr.NewStack if !instr.Unreachable { blockPolymorphicOps = blockPolymorphicOps[:len(blockPolymorphicOps)-1] @@ -351,10 +363,19 @@ func Disassemble(fn wasm.Function, module *wasm.Module) (*Disassembly, error) { instr.Immediates = append(instr.Immediates, reserved) } if !instr.Unreachable { - fn := module.GetFunction(int(index)) + var sig *wasm.FunctionSig top := int(stackDepths.Top()) - top -= len(fn.Sig.ParamTypes) - top += len(fn.Sig.ReturnTypes) + if op == ops.CallIndirect { + if module.Types == nil { + return nil, errors.New("missing types section") + } + sig = &module.Types.Entries[index] + top-- + } else { + sig = module.GetFunction(int(index)).Sig + } + top -= len(sig.ParamTypes) + top += len(sig.ReturnTypes) stackDepths.SetTop(uint64(top)) disas.checkMaxDepth(top) } @@ -438,5 +459,11 @@ func Disassemble(fn wasm.Function, module *wasm.Module) (*Disassembly, error) { curIndex++ } + if logging { + for _, instr := range disas.Code { + logger.Printf("%v %v", instr.Op.Name, instr.NewStack) + } + } + return disas, nil } diff --git a/disasm/log.go b/disasm/log.go index 36478390..4b6912cc 100644 --- a/disasm/log.go +++ b/disasm/log.go @@ -10,17 +10,24 @@ import ( "os" ) -var PrintDebugInfo = false - -var logger *log.Logger +var ( + logger *log.Logger + logging bool +) -func init() { +func SetDebugMode(l bool) { w := ioutil.Discard + logging = l - if PrintDebugInfo { + if l { w = os.Stderr } logger = log.New(w, "", log.Lshortfile) logger.SetFlags(log.Lshortfile) + +} + +func init() { + SetDebugMode(false) } diff --git a/exec/call.go b/exec/call.go index 65d735f7..757e835a 100644 --- a/exec/call.go +++ b/exec/call.go @@ -7,7 +7,7 @@ package exec import "errors" func (vm *VM) doCall(compiled compiledFunction, index int64) { - newStack := make([]uint64, compiled.maxDepth) + newStack := make([]uint64, 0, compiled.maxDepth) locals := make([]uint64, compiled.totalLocalVars) for i := compiled.args - 1; i >= 0; i-- { @@ -81,5 +81,5 @@ func (vm *VM) callIndirect() { } } - vm.doCall(vm.compiledFuncs[elemIndex], int64(index)) + vm.doCall(vm.compiledFuncs[elemIndex], int64(elemIndex)) } diff --git a/exec/internal/compile/compile.go b/exec/internal/compile/compile.go index 840957aa..3441cbe3 100644 --- a/exec/internal/compile/compile.go +++ b/exec/internal/compile/compile.go @@ -181,14 +181,15 @@ func Compile(disassembly []disasm.Instr) ([]byte, []*BranchTable) { } continue case ops.Else: - // add code for jumping out of a taken if branch - if instr.NewStack != nil && instr.NewStack.StackTopDiff != 0 { - if instr.NewStack.PreserveTop { + ifInstr := disassembly[instr.Block.ElseIfIndex] // the corresponding `if` instruction for this else + if ifInstr.NewStack != nil && ifInstr.NewStack.StackTopDiff != 0 { + // add code for jumping out of a taken if branch + if ifInstr.NewStack.PreserveTop { buffer.WriteByte(OpDiscardPreserveTop) } else { buffer.WriteByte(OpDiscard) } - binary.Write(buffer, binary.LittleEndian, instr.NewStack.StackTopDiff) + binary.Write(buffer, binary.LittleEndian, ifInstr.NewStack.StackTopDiff) } buffer.WriteByte(OpJmp) ifBlockEndOffset := int64(buffer.Len()) diff --git a/exec/testdata/bug-49.wasm b/exec/testdata/bug-49.wasm new file mode 100644 index 00000000..fb6eb487 Binary files /dev/null and b/exec/testdata/bug-49.wasm differ diff --git a/exec/testdata/modules.json b/exec/testdata/modules.json index 6d91db5d..e2c6f90e 100644 --- a/exec/testdata/modules.json +++ b/exec/testdata/modules.json @@ -1106,5 +1106,15 @@ "return": "i32:0" } ] + }, + { + "file": "bug-49.wasm", + "tests": [ + { + "function": "sample", + "args": [], + "trap": "i32:1" + } + ] } ] diff --git a/exec/vm.go b/exec/vm.go index cb1ba360..3a12c0aa 100644 --- a/exec/vm.go +++ b/exec/vm.go @@ -246,9 +246,8 @@ func (vm *VM) ExecCode(fnIndex int64, args ...uint64) (interface{}, error) { return nil, ErrInvalidArgumentCount } compiled := vm.compiledFuncs[fnIndex] - if len(vm.ctx.stack) < compiled.maxDepth { - vm.ctx.stack = make([]uint64, 0, compiled.maxDepth) - } + + vm.ctx.stack = make([]uint64, 0, compiled.maxDepth) vm.ctx.locals = make([]uint64, compiled.totalLocalVars) vm.ctx.pc = 0 vm.ctx.code = compiled.code diff --git a/wasm/operators/memory.go b/wasm/operators/memory.go index 24c92dbe..9aafa00a 100644 --- a/wasm/operators/memory.go +++ b/wasm/operators/memory.go @@ -9,30 +9,30 @@ import ( ) var ( - I32Load = newOp(0x28, "i32.load", nil, wasm.ValueTypeI32) - I64Load = newOp(0x29, "i64.load", nil, wasm.ValueTypeI64) - F32Load = newOp(0x2a, "f32.load", nil, wasm.ValueTypeF32) - F64Load = newOp(0x2b, "f64.load", nil, wasm.ValueTypeF64) - I32Load8s = newOp(0x2c, "i32.load8_s", nil, wasm.ValueTypeI32) - I32Load8u = newOp(0x2d, "i32.load8_u", nil, wasm.ValueTypeI32) - I32Load16s = newOp(0x2e, "i32.load16_s", nil, wasm.ValueTypeI32) - I32Load16u = newOp(0x2f, "i32.load16_u", nil, wasm.ValueTypeI32) - I64Load8s = newOp(0x30, "i64.load8_s", nil, wasm.ValueTypeI64) - I64Load8u = newOp(0x31, "i64.load8_u", nil, wasm.ValueTypeI64) - I64Load16s = newOp(0x32, "i64.load16_s", nil, wasm.ValueTypeI64) - I64Load16u = newOp(0x33, "i64.load16_u", nil, wasm.ValueTypeI64) - I64Load32s = newOp(0x34, "i64.load32_s", nil, wasm.ValueTypeI64) - I64Load32u = newOp(0x35, "i64.load32_u", nil, wasm.ValueTypeI64) + I32Load = newOp(0x28, "i32.load", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32) + I64Load = newOp(0x29, "i64.load", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64) + F32Load = newOp(0x2a, "f32.load", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeF32) + F64Load = newOp(0x2b, "f64.load", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeF64) + I32Load8s = newOp(0x2c, "i32.load8_s", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32) + I32Load8u = newOp(0x2d, "i32.load8_u", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32) + I32Load16s = newOp(0x2e, "i32.load16_s", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32) + I32Load16u = newOp(0x2f, "i32.load16_u", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32) + I64Load8s = newOp(0x30, "i64.load8_s", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64) + I64Load8u = newOp(0x31, "i64.load8_u", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64) + I64Load16s = newOp(0x32, "i64.load16_s", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64) + I64Load16u = newOp(0x33, "i64.load16_u", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64) + I64Load32s = newOp(0x34, "i64.load32_s", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64) + I64Load32u = newOp(0x35, "i64.load32_u", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI64) - I32Store = newOp(0x36, "i32.store", nil, wasm.ValueTypeI32) - I64Store = newOp(0x37, "i64.store", nil, wasm.ValueTypeI64) - F32Store = newOp(0x38, "f32.store", nil, wasm.ValueTypeF32) - F64Store = newOp(0x39, "f64.store", nil, wasm.ValueTypeF64) - I32Store8 = newOp(0x3a, "i32.store8", nil, wasm.ValueTypeI32) - I32Store16 = newOp(0x3b, "i32.store16", nil, wasm.ValueTypeI32) - I64Store8 = newOp(0x3c, "i64.store8", nil, wasm.ValueTypeI64) - I64Store16 = newOp(0x3d, "i64.store16", nil, wasm.ValueTypeI64) - I64Store32 = newOp(0x3e, "i64.store32", nil, wasm.ValueTypeI32) + I32Store = newOp(0x36, "i32.store", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, noReturn) + I64Store = newOp(0x37, "i64.store", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32}, noReturn) + F32Store = newOp(0x38, "f32.store", []wasm.ValueType{wasm.ValueTypeF32, wasm.ValueTypeI32}, noReturn) + F64Store = newOp(0x39, "f64.store", []wasm.ValueType{wasm.ValueTypeF64, wasm.ValueTypeI32}, noReturn) + I32Store8 = newOp(0x3a, "i32.store8", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, noReturn) + I32Store16 = newOp(0x3b, "i32.store16", []wasm.ValueType{wasm.ValueTypeI32, wasm.ValueTypeI32}, noReturn) + I64Store8 = newOp(0x3c, "i64.store8", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32}, noReturn) + I64Store16 = newOp(0x3d, "i64.store16", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32}, noReturn) + I64Store32 = newOp(0x3e, "i64.store32", []wasm.ValueType{wasm.ValueTypeI64, wasm.ValueTypeI32}, noReturn) CurrentMemory = newOp(0x3f, "current_memory", nil, wasm.ValueTypeI32) GrowMemory = newOp(0x40, "grow_memory", []wasm.ValueType{wasm.ValueTypeI32}, wasm.ValueTypeI32)