Repository: tetratelabs/wazero
Pull request #1960 (2024-01-25 18:40:06)

wazevo(amd64): lowering for div/rem
Not finished yet, I must more tests to verify all the code paths.

mathetake: maybe you might want to do rebase as I merged some bugs
evacchi: rebased, did a quick+dirty change, will review, add more tests. Promising!

Patch:
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr.go b/internal/engine/wazevo/backend/isa/amd64/instr.go
index 51129904..b34e8ffb 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr.go
@@ -94,7 +94,17 @@ func (i *instruction) String() string {
 		}
 		return fmt.Sprintf("%s %s", op, i.op1.format(i.b1))
 	case div:
-		panic("TODO")
+		var prefix string
+		var op string
+		if i.b1 {
+			op = "divq"
+		} else {
+			op = "divl"
+		}
+		if i.u1 != 0 {
+			prefix = "i"
+		}
+		return fmt.Sprintf("%s%s %s", prefix, op, i.op1.format(i.b1))
 	case mulHi:
 		signed, _64 := i.u1 != 0, i.b1
 		var op string
@@ -112,7 +122,13 @@ func (i *instruction) String() string {
 	case checkedDivOrRemSeq:
 		panic("TODO")
 	case signExtendData:
-		panic("TODO")
+		var op string
+		if i.b1 {
+			op = "cqo"
+		} else {
+			op = "cdq"
+		}
+		return op
 	case movzxRmR:
 		return fmt.Sprintf("movzx.%s %s, %s", extMode(i.u1), i.op1.format(true), i.op2.format(true))
 	case mov64MR:
@@ -236,6 +252,11 @@ func (i *instruction) Defs(regs *[]regalloc.VReg) []regalloc.VReg {
 		*regs = append(*regs, i.op2.r)
 	case defKindCall:
 		*regs = append(*regs, i.abi.RetRealRegs...)
+	case defKindRdx:
+		*regs = append(*regs, rdxVReg)
+	case defKindRaxRdx:
+		*regs = append(*regs, raxVReg, rdxVReg)
+
 	default:
 		panic(fmt.Sprintf("BUG: invalid defKind \"%s\" for %s", dk, i))
 	}
@@ -291,6 +312,21 @@ func (i *instruction) Uses(regs *[]regalloc.VReg) []regalloc.VReg {
 		*regs = append(*regs, i.abi.ArgRealRegs...)
 	case useKindCall:
 		*regs = append(*regs, i.abi.ArgRealRegs...)
+	case useKindRax:
+		*regs = append(*regs, raxVReg)
+	case useKindOp1Rax:
+		op := i.op1
+		switch op.kind {
+		case operandKindReg:
+			*regs = append(*regs, op.r)
+		case operandKindMem:
+			op.amode.uses(regs)
+		case operandKindImm32, operandKindLabel:
+		default:
+			panic(fmt.Sprintf("BUG: invalid operand: %s", i))
+		}
+		*regs = append(*regs, raxVReg)
+
 	default:
 		panic(fmt.Sprintf("BUG: invalid useKind %s for %s", uk, i))
 	}
@@ -374,6 +410,32 @@ func (i *instruction) AssignUse(index int, v regalloc.VReg) {
 		default:
 			panic(fmt.Sprintf("BUG: invalid operand: %s", i))
 		}
+	case useKindOp1Rax:
+		if index == 0 {
+			op := &i.op1
+			switch op.kind {
+			case operandKindReg:
+				if index != 0 {
+					panic("BUG")
+				}
+				if op.r.IsRealReg() {
+					panic("BUG already assigned: " + i.String())
+				}
+				op.r = v
+			case operandKindMem:
+				op.amode.assignUses(index, v)
+			default:
+				panic(fmt.Sprintf("BUG: invalid operand: %s", i))
+			}
+		} else if index == 1 {
+			// Do nothing.
+		} else {
+			panic("BUG")
+		}
+	case useKindRax:
+		if index != 0 {
+			panic("BUG")
+		}
 	default:
 		panic(fmt.Sprintf("BUG: invalid useKind %s for %s", uk, i))
 	}
@@ -843,11 +905,27 @@ func (i *instruction) asMovzxRmR(ext extMode, src operand, rd regalloc.VReg) *in
 	return i
 }
 
+func (i *instruction) asSignExtendData(_64 bool) *instruction {
+	i.kind = signExtendData
+	i.b1 = _64
+	return i
+}
+
 func (i *instruction) asUD2() *instruction {
 	i.kind = ud2
 	return i
 }
 
+func (i *instruction) asDiv(rn operand, signed bool, _64 bool) *instruction {
+	i.kind = div
+	i.op1 = rn
+	i.b1 = _64
+	if signed {
+		i.u1 = 1
+	}
+	return i
+}
+
 func (i *instruction) asMov64MR(rm operand, rd regalloc.VReg) *instruction {
 	if rm.kind != operandKindMem {
 		panic("BUG")
@@ -1572,10 +1650,14 @@ const (
 	defKindNone defKind = iota + 1
 	defKindOp2
 	defKindCall
+	defKindRdx
+	defKindRaxRdx
 )
 
 var defKinds = [instrMax]defKind{
 	nop0:            defKindNone,
+	div:             defKindRaxRdx,
+	signExtendData:  defKindRdx,
 	ret:             defKindNone,
 	movRR:           defKindOp2,
 	movRM:           defKindNone,
@@ -1612,6 +1694,10 @@ func (d defKind) String() string {
 		return "op2"
 	case defKindCall:
 		return "call"
+	case defKindRdx:
+		return "rdx"
+	case defKindRaxRdx:
+		return "raxrdx"
 	default:
 		return "invalid"
 	}
@@ -1626,12 +1712,18 @@ const (
 	useKindOp1Op2Reg
 	// useKindOp1RegOp2 is Op1 must be a register, Op2 can be any operand.
 	useKindOp1RegOp2
+	// useKindRax is %rax is used (for instance in signExtendData).
+	useKindRax
+	// useKindOp1Rax is Op1 must be a reg, mem operand; the other operand is implicitly %rax.
+	useKindOp1Rax
 	useKindCall
 	useKindCallInd
 )
 
 var useKinds = [instrMax]useKind{
 	nop0:            useKindNone,
+	div:             useKindOp1Rax,
+	signExtendData:  useKindRax,
 	ret:             useKindNone,
 	movRR:           useKindOp1,
 	movRM:           useKindOp1RegOp2,
@@ -1671,6 +1763,12 @@ func (u useKind) String() string {
 		return "op1RegOp2"
 	case useKindCall:
 		return "call"
+	case useKindCallInd:
+		return "callInd"
+	case useKindOp1Rax:
+		return "op1rax"
+	case useKindRax:
+		return "rax"
 	default:
 		return "invalid"
 	}
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
index 7fa54545..54c5f9f6 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_encoding.go
@@ -522,7 +522,29 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 		encodeEncEnc(c, prefix, 0xf7, 1, subopcode, uint8(src), rex)
 
 	case div:
-		panic("TODO")
+		rex := rexInfo(0)
+		if i.b1 { // 64 bit.
+			rex = rexInfo(0).setW()
+		} else {
+			rex = rexInfo(0).clearW()
+		}
+		var subopcode uint8
+		if i.u1 != 0 { // Signed.
+			subopcode = 7
+		} else {
+			subopcode = 6
+		}
+
+		divisor := i.op1
+		if divisor.kind == operandKindReg {
+			src := regEncodings[divisor.r.RealReg()]
+			encodeEncEnc(c, legacyPrefixesNone, 0xf7, 1, subopcode, uint8(src), rex)
+		} else if divisor.kind == operandKindMem {
+			m := divisor.amode
+			encodeEncMem(c, legacyPrefixesNone, 0xf7, 1, subopcode, m, rex)
+		} else {
+			panic("BUG: invalid operand kind")
+		}
 
 	case mulHi:
 		var prefix legacyPrefixes
@@ -558,7 +580,12 @@ func (i *instruction) encode(c backend.Compiler) (needsLabelResolution bool) {
 	case checkedDivOrRemSeq:
 		panic("TODO")
 	case signExtendData:
-		panic("TODO")
+		if i.b1 { // 64 bit.
+			c.EmitByte(0x48)
+			c.EmitByte(0x99)
+		} else {
+			c.EmitByte(0x99)
+		}
 	case movzxRmR, movsxRmR:
 		signed := i.kind == movsxRmR
 
diff --git a/internal/engine/wazevo/backend/isa/amd64/machine.go b/internal/engine/wazevo/backend/isa/amd64/machine.go
index 133eb8a1..0077592a 100644
--- a/internal/engine/wazevo/backend/isa/amd64/machine.go
+++ b/internal/engine/wazevo/backend/isa/amd64/machine.go
@@ -258,6 +258,10 @@ func (m *machine) LowerInstr(instr *ssa.Instruction) {
 		m.lowerAluRmiROp(instr, aluRmiROpcodeSub)
 	case ssa.OpcodeImul:
 		m.lowerAluRmiROp(instr, aluRmiROpcodeMul)
+	case ssa.OpcodeSdiv, ssa.OpcodeUdiv, ssa.OpcodeSrem, ssa.OpcodeUrem:
+		isDiv := op == ssa.OpcodeSdiv || op == ssa.OpcodeUdiv
+		isSigned := op == ssa.OpcodeSdiv || op == ssa.OpcodeSrem
+		m.lowerIDivRem(instr, isDiv, isSigned)
 	case ssa.OpcodeBand:
 		m.lowerAluRmiROp(instr, aluRmiROpcodeAnd)
 	case ssa.OpcodeBor:
@@ -1176,3 +1180,130 @@ func (m *machine) frameSize() int64 {
 func (m *machine) clobberedRegSlotSize() int64 {
 	return int64(len(m.clobberedRegs) * 16)
 }
+
+func (m *machine) lowerIDivRem(si *ssa.Instruction, isDiv bool, signed bool) {
+	x, y, execCtx := si.Arg3()
+	if !x.Type().IsInt() {
+		panic("BUG?")
+	}
+	_64 := x.Type().Bits() == 64
+
+	xDef, yDef := m.c.ValueDefinition(x), m.c.ValueDefinition(y)
+	xr := m.getOperand_Reg(xDef)
+	yr := m.getOperand_Reg(yDef)
+	ctxVReg := m.c.VRegOf(execCtx)
+	rd := m.c.VRegOf(si.Return())
+
+	// Ensure yr is not zero.
+	test := m.allocateInstr()
+	test.asCmpRmiR(false, yr, yr.r, _64)
+	m.insert(test)
+
+	// We need to copy the execution context to a temp register *BEFORE BRANCHING*, because if it's spilled,
+	// it might end up being reloaded inside the exiting branch.
+	execCtxTmp := m.copyToTmp(ctxVReg)
+
+	jnz := m.allocateInstr()
+	m.insert(jnz)
+
+	nz := m.lowerExitWithCode(execCtxTmp, wazevoapi.ExitCodeIntegerDivisionByZero)
+
+	// If not zero, we can proceed with the division.
+	jnz.asJmpIf(condNZ, newOperandLabel(nz))
+
+	m.copyTo(xr.r, raxVReg)
+
+	var ifRemNeg1 *instruction
+	if signed {
+		var neg1 uint64
+		if _64 {
+			neg1 = 0xffffffffffffffff
+		} else {
+			neg1 = 0xffffffff
+		}
+		tmp1 := m.c.AllocateVReg(si.Return().Type())
+		m.lowerIconst(tmp1, neg1, _64)
+
+		if isDiv {
+			// For signed division, we have to have branches for "math.MinInt{32,64} / -1"
+			// case which results in the floating point exception via division error as
+			// the resulting value exceeds the maximum of signed int.
+
+			// First, we check if the divisor is -1.
+			cmp := m.allocateInstr()
+			cmp.asCmpRmiR(true, newOperandReg(tmp1), yr.r, _64)
+			m.insert(cmp)
+
+			// Again, we need to copy the execution context to a temp register *BEFORE BRANCHING*, because if it's spilled,
+			// it might end up being reloaded inside the exiting branch.
+			execCtxTmp2 := m.copyToTmp(execCtxTmp)
+			ifNotNeg1 := m.allocateInstr()
+			m.insert(ifNotNeg1)
+
+			var minInt uint64
+			if _64 {
+				minInt = 0x8000000000000000
+			} else {
+				minInt = 0x80000000
+			}
+			tmp := m.c.AllocateVReg(si.Return().Type())
+			m.lowerIconst(tmp, minInt, _64)
+
+			// Next we check if the quotient is the most negative value for the signed integer, i.e.
+			// if we are trying to do (math.MinInt32 / -1) or (math.MinInt64 / -1) respectively.
+			cmp2 := m.allocateInstr()
+			cmp2.asCmpRmiR(true, newOperandReg(tmp), xr.r, _64)
+			m.insert(cmp2)
+
+			ifNotMinInt := m.allocateInstr()
+			m.insert(ifNotMinInt)
+
+			// Trap if we are trying to do (math.MinInt32 / -1) or (math.MinInt64 / -1),
+			// as that is the overflow in division as the result becomes 2^31 which is larger than
+			// the maximum of signed 32-bit int (2^31-1).
+			end := m.lowerExitWithCode(execCtxTmp2, wazevoapi.ExitCodeIntegerOverflow)
+			ifNotNeg1.asJmpIf(condNZ, newOperandLabel(end))
+			ifNotMinInt.asJmpIf(condNZ, newOperandLabel(end))
+		} else {
+			// If it is remainder, zeros DX register and compare the divisor to -1.
+			xor := m.allocateInstr()
+			xor.asAluRmiR(aluRmiROpcodeXor, newOperandReg(rdxVReg), rdxVReg, _64)
+			m.insert(xor)
+
+			// We check if the divisor is -1.
+			cmp := m.allocateInstr()
+			cmp.asCmpRmiR(true, newOperandReg(tmp1), yr.r, _64)
+			m.insert(cmp)
+
+			ifRemNeg1 = m.allocateInstr()
+			m.insert(ifRemNeg1)
+		}
+
+		// Sign-extend DX register to have 2*x.Type().Bits() dividend over DX and AX registers.
+		sed := m.allocateInstr()
+		sed.asSignExtendData(_64)
+		m.insert(sed)
+	} else {
+		// Zeros DX register to have 2*x.Type().Bits() dividend over DX and AX registers.
+		xor := m.allocateInstr()
+		xor.asAluRmiR(aluRmiROpcodeXor, newOperandReg(rdxVReg), rdxVReg, _64)
+		m.insert(xor)
+	}
+
+	div := m.allocateInstr()
+	div.asDiv(yr, signed, _64)
+	m.insert(div)
+
+	nop, end := m.allocateBrTarget()
+	m.insert(nop)
+	// If we are compiling a Rem instruction, when the divisor is -1 we land at the end of the function.
+	if ifRemNeg1 != nil {
+		ifRemNeg1.asJmpIf(condZ, newOperandLabel(end))
+	}
+
+	if isDiv {
+		m.copyTo(raxVReg, rd)
+	} else {
+		m.copyTo(rdxVReg, rd)
+	}
+}


Test patch:
diff --git a/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go b/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
index f6c1d042..c7aefd4c 100644
--- a/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
+++ b/internal/engine/wazevo/backend/isa/amd64/instr_encoding_test.go
@@ -70,6 +70,56 @@ func TestInstruction_format_encode(t *testing.T) {
 			want:       "48c7c182ffffff",
 			wantFormat: "movabsq $-126, %rcx",
 		},
+		{
+			setup:      func(i *instruction) { i.asSignExtendData(false) },
+			want:       "99",
+			wantFormat: "cdq",
+		},
+		{
+			setup:      func(i *instruction) { i.asSignExtendData(true) },
+			want:       "4899",
+			wantFormat: "cqo",
+		},
+		{
+			setup:      func(i *instruction) { i.asDiv(newOperandReg(raxVReg), true, true) },
+			want:       "48f7f8",
+			wantFormat: "idivq %rax",
+		},
+		{
+			setup:      func(i *instruction) { i.asDiv(newOperandReg(raxVReg), false, true) },
+			want:       "48f7f0",
+			wantFormat: "divq %rax",
+		},
+		{
+			setup:      func(i *instruction) { i.asDiv(newOperandReg(raxVReg), true, false) },
+			want:       "f7f8",
+			wantFormat: "idivl %eax",
+		},
+		{
+			setup:      func(i *instruction) { i.asDiv(newOperandReg(raxVReg), false, false) },
+			want:       "f7f0",
+			wantFormat: "divl %eax",
+		},
+		{
+			setup:      func(i *instruction) { i.asDiv(newOperandMem(newAmodeImmReg(123, raxVReg)), true, true) },
+			want:       "48f7787b",
+			wantFormat: "idivq 123(%rax)",
+		},
+		{
+			setup:      func(i *instruction) { i.asDiv(newOperandMem(newAmodeImmReg(123, raxVReg)), false, true) },
+			want:       "48f7707b",
+			wantFormat: "divq 123(%rax)",
+		},
+		{
+			setup:      func(i *instruction) { i.asDiv(newOperandMem(newAmodeImmReg(123, raxVReg)), true, false) },
+			want:       "f7787b",
+			wantFormat: "idivl 123(%rax)",
+		},
+		{
+			setup:      func(i *instruction) { i.asDiv(newOperandMem(newAmodeImmReg(123, raxVReg)), false, false) },
+			want:       "f7707b",
+			wantFormat: "divl 123(%rax)",
+		},
 		{
 			setup:      func(i *instruction) { i.asMovRR(raxVReg, rdiVReg, false) },
 			want:       "89c7",
diff --git a/internal/engine/wazevo/e2e_test.go b/internal/engine/wazevo/e2e_test.go
index fd5de3ad..29f786a2 100644
--- a/internal/engine/wazevo/e2e_test.go
+++ b/internal/engine/wazevo/e2e_test.go
@@ -122,6 +122,128 @@ func TestE2E(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "divrem_unsigned_return", m: testcases.DivUReturn32.Module,
+			calls: []callCase{
+				{
+					params:     []uint64{21, 10, 21, 10},
+					expResults: []uint64{21 / 10, 21 % 10},
+				},
+				{
+					params: []uint64{21, 0, 1, 1},
+					expErr: "wasm error: integer divide by zero",
+				},
+				{
+					params: []uint64{1, 1, 21, 0},
+					expErr: "wasm error: integer divide by zero",
+				},
+				{
+					params: []uint64{
+						0x80000000, 0xffffffff, 1, 1,
+					},
+					expResults: []uint64{0, 0},
+				},
+				{
+					params: []uint64{
+						1, 1, 0x80000000, 0xffffffff,
+					},
+					expResults: []uint64{1, 0x80000000},
+				},
+			},
+		},
+		{
+			name: "divrem_unsigned_return", m: testcases.DivUReturn64.Module,
+			calls: []callCase{
+				{
+					params:     []uint64{21, 10, 21, 10},
+					expResults: []uint64{21 / 10, 21 % 10},
+				},
+				{
+					params: []uint64{21, 0, 1, 1},
+					expErr: "wasm error: integer divide by zero",
+				},
+				{
+					params: []uint64{1, 1, 21, 0},
+					expErr: "wasm error: integer divide by zero",
+				},
+				{
+					params:     []uint64{0x80000000, 0xffffffff, 1, 1},
+					expResults: []uint64{0, 0},
+				},
+				{
+					params:     []uint64{1, 1, 0x80000000, 0xffffffff},
+					expResults: []uint64{1, 0x80000000},
+				},
+			},
+		},
+		{
+			name: "divrem_signed_return32", m: testcases.DivSReturn32.Module,
+			calls: []callCase{
+				{
+					params:     []uint64{21, 10, 21, 10},
+					expResults: []uint64{21 / 10, 21 % 10},
+				},
+				{
+					params: []uint64{21, 0, 1, 1},
+					expErr: "wasm error: integer divide by zero",
+				},
+				{
+					params: []uint64{1, 1, 21, 0},
+					expErr: "wasm error: integer divide by zero",
+				},
+				{
+					params: []uint64{0x80000000, 0xffffffff, 1, 1},
+					expErr: "wasm error: integer overflow",
+				},
+				{
+					params:     []uint64{1, 1, 0x80000000, 0xffffffff},
+					expResults: []uint64{1, 0},
+				},
+			},
+		},
+
+		{
+			name: "divrem_signed_return32 inverted rem div order (different crash)", m: testcases.DivSReturn32_weird.Module,
+			calls: []callCase{
+				{
+					params: []uint64{21, 0, 1, 1},
+					expErr: "wasm error: integer divide by zero",
+				},
+				{
+					params: []uint64{1, 1, 21, 0},
+					expErr: "wasm error: integer divide by zero",
+				},
+				{
+					params: []uint64{0x80000000, 0xffffffff, 1, 1},
+					expErr: "wasm error: integer overflow",
+				},
+				{
+					params:     []uint64{1, 1, 0x80000000, 0xffffffff},
+					expResults: []uint64{0, 1},
+				},
+			},
+		},
+		{
+			name: "divrem_signed_return64", m: testcases.DivSReturn64.Module,
+			calls: []callCase{
+				{
+					params:     []uint64{21, 10, 21, 10},
+					expResults: []uint64{21 / 10, 21 % 10},
+				},
+				{
+					params: []uint64{21, 0, 1, 1},
+					expErr: "wasm error: integer divide by zero",
+				},
+				{
+					params: []uint64{1, 1, 21, 0},
+					expErr: "wasm error: integer divide by zero",
+				},
+				{
+					params: []uint64{0x8000000000000000, 0xffffffffffffffff, 1, 1},
+					expErr: "wasm error: integer overflow",
+				},
+			},
+		},
 		{
 			name: "integer bit counts", m: testcases.IntegerBitCounts.Module,
 			calls: []callCase{{
diff --git a/internal/engine/wazevo/testcases/testcases.go b/internal/engine/wazevo/testcases/testcases.go
index 0a8d3040..809cf07a 100644
--- a/internal/engine/wazevo/testcases/testcases.go
+++ b/internal/engine/wazevo/testcases/testcases.go
@@ -163,6 +163,102 @@ var (
 				wasm.OpcodeEnd,
 			}, nil),
 	}
+	DivUReturn32 = TestCase{
+		Name: "div return unsigned 32",
+		Module: SingleFunctionModule(
+			wasm.FunctionType{
+				Params:  []wasm.ValueType{i32, i32, i32, i32},
+				Results: []wasm.ValueType{i32, i32},
+			},
+			[]byte{
+				wasm.OpcodeLocalGet, 0,
+				wasm.OpcodeLocalGet, 1,
+				wasm.OpcodeI32DivU,
+
+				wasm.OpcodeLocalGet, 2,
+				wasm.OpcodeLocalGet, 3,
+				wasm.OpcodeI32RemU,
+
+				wasm.OpcodeEnd,
+			}, nil),
+	}
+	DivUReturn64 = TestCase{
+		Name: "div return unsigned 64",
+		Module: SingleFunctionModule(
+			wasm.FunctionType{
+				Params:  []wasm.ValueType{i64, i64, i64, i64},
+				Results: []wasm.ValueType{i64, i64},
+			},
+			[]byte{
+				wasm.OpcodeLocalGet, 0,
+				wasm.OpcodeLocalGet, 1,
+				wasm.OpcodeI64DivU,
+
+				wasm.OpcodeLocalGet, 2,
+				wasm.OpcodeLocalGet, 3,
+				wasm.OpcodeI64RemU,
+
+				wasm.OpcodeEnd,
+			}, nil),
+	}
+	DivSReturn32 = TestCase{
+		Name: "div return signed 32",
+		Module: SingleFunctionModule(
+			wasm.FunctionType{
+				Params:  []wasm.ValueType{i32, i32, i32, i32},
+				Results: []wasm.ValueType{i32, i32},
+			},
+			[]byte{
+				wasm.OpcodeLocalGet, 0,
+				wasm.OpcodeLocalGet, 1,
+				wasm.OpcodeI32DivS,
+
+				wasm.OpcodeLocalGet, 2,
+				wasm.OpcodeLocalGet, 3,
+				wasm.OpcodeI32RemS,
+
+				wasm.OpcodeEnd,
+			}, nil),
+	}
+	DivSReturn32_weird = TestCase{
+		Name: "div return signed 32",
+		Module: SingleFunctionModule(
+			wasm.FunctionType{
+				Params:  []wasm.ValueType{i32, i32, i32, i32},
+				Results: []wasm.ValueType{i32, i32},
+			},
+			[]byte{
+				wasm.OpcodeLocalGet, 2,
+				wasm.OpcodeLocalGet, 3,
+				wasm.OpcodeI32RemS,
+
+				wasm.OpcodeLocalGet, 0,
+				wasm.OpcodeLocalGet, 1,
+				wasm.OpcodeI32DivS,
+
+				wasm.OpcodeEnd,
+			}, nil),
+	}
+
+	DivSReturn64 = TestCase{
+		Name: "div return signed 64",
+		Module: SingleFunctionModule(
+			wasm.FunctionType{
+				Params:  []wasm.ValueType{i64, i64, i64, i64},
+				Results: []wasm.ValueType{i64, i64},
+			},
+			[]byte{
+				wasm.OpcodeLocalGet, 0,
+				wasm.OpcodeLocalGet, 1,
+				wasm.OpcodeI64DivS,
+
+				wasm.OpcodeLocalGet, 2,
+				wasm.OpcodeLocalGet, 3,
+				wasm.OpcodeI64RemS,
+
+				wasm.OpcodeEnd,
+			}, nil),
+	}
 	Locals       = TestCase{Name: "locals", Module: SingleFunctionModule(vv, []byte{wasm.OpcodeEnd}, []wasm.ValueType{i32, i64, f32, f64})}
 	LocalsParams = TestCase{
 		Name: "locals_params",