Commit 51c79bc4 authored by Ivica Bogosavljevic's avatar Ivica Bogosavljevic Committed by Commit Bot

MIPS[64]: Rewrite overflow instructions

Rewrite overflow instructions in order to allow branch
poisoning.

Change-Id: I5f97808c7eac08927243780a0ae0c48f6de0c45b
Reviewed-on: https://chromium-review.googlesource.com/964321
Commit-Queue: Ivica Bogosavljevic <ivica.bogosavljevic@mips.com>
Reviewed-by: 's avatarSreten Kovacevic <sreten.kovacevic@mips.com>
Cr-Commit-Position: refs/heads/master@{#51984}
parent 9625520f
......@@ -997,19 +997,22 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
__ Addu(i.OutputRegister(), i.InputRegister(0), i.InputOperand(1));
break;
case kMipsAddOvf:
// Pseudo-instruction used for overflow/branch. No opcode emitted here.
__ AddOverflow(i.OutputRegister(), i.InputRegister(0), i.InputOperand(1),
kScratchReg);
break;
case kMipsSub:
__ Subu(i.OutputRegister(), i.InputRegister(0), i.InputOperand(1));
break;
case kMipsSubOvf:
// Pseudo-instruction used for overflow/branch. No opcode emitted here.
__ SubOverflow(i.OutputRegister(), i.InputRegister(0), i.InputOperand(1),
kScratchReg);
break;
case kMipsMul:
__ Mul(i.OutputRegister(), i.InputRegister(0), i.InputOperand(1));
break;
case kMipsMulOvf:
// Pseudo-instruction used for overflow/branch. No opcode emitted here.
__ MulOverflow(i.OutputRegister(), i.InputRegister(0), i.InputOperand(1),
kScratchReg);
break;
case kMipsMulHigh:
__ Mulh(i.OutputRegister(), i.InputRegister(0), i.InputOperand(1));
......@@ -2859,43 +2862,28 @@ void AssembleBranchToLabels(CodeGenerator* gen, TurboAssembler* tasm,
cc = FlagsConditionToConditionTst(condition);
__ And(at, i.InputRegister(0), i.InputOperand(1));
__ Branch(tlabel, cc, at, Operand(zero_reg));
} else if (instr->arch_opcode() == kMipsAddOvf) {
switch (condition) {
case kOverflow:
__ AddBranchOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), tlabel, flabel);
break;
case kNotOverflow:
__ AddBranchOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), flabel, tlabel);
break;
default:
UNSUPPORTED_COND(kMipsAddOvf, condition);
break;
}
} else if (instr->arch_opcode() == kMipsSubOvf) {
} else if (instr->arch_opcode() == kMipsAddOvf ||
instr->arch_opcode() == kMipsSubOvf) {
// Overflow occurs if overflow register is negative
switch (condition) {
case kOverflow:
__ SubBranchOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), tlabel, flabel);
__ Branch(tlabel, lt, kScratchReg, Operand(zero_reg));
break;
case kNotOverflow:
__ SubBranchOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), flabel, tlabel);
__ Branch(tlabel, ge, kScratchReg, Operand(zero_reg));
break;
default:
UNSUPPORTED_COND(kMipsAddOvf, condition);
UNSUPPORTED_COND(instr->arch_opcode(), condition);
break;
}
} else if (instr->arch_opcode() == kMipsMulOvf) {
// Overflow occurs if overflow register is not zero
switch (condition) {
case kOverflow:
__ MulBranchOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), tlabel, flabel);
__ Branch(tlabel, ne, kScratchReg, Operand(zero_reg));
break;
case kNotOverflow:
__ MulBranchOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), flabel, tlabel);
__ Branch(tlabel, eq, kScratchReg, Operand(zero_reg));
break;
default:
UNSUPPORTED_COND(kMipsMulOvf, condition);
......@@ -3061,32 +3049,12 @@ void CodeGenerator::AssembleArchBoolean(Instruction* instr,
}
return;
} else if (instr->arch_opcode() == kMipsAddOvf ||
instr->arch_opcode() == kMipsSubOvf ||
instr->arch_opcode() == kMipsMulOvf) {
Label flabel, tlabel;
switch (instr->arch_opcode()) {
case kMipsAddOvf:
__ AddBranchNoOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), &flabel);
break;
case kMipsSubOvf:
__ SubBranchNoOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), &flabel);
break;
case kMipsMulOvf:
__ MulBranchNoOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), &flabel);
break;
default:
UNREACHABLE();
break;
}
__ li(result, 1);
__ Branch(&tlabel);
__ bind(&flabel);
__ li(result, 0);
__ bind(&tlabel);
instr->arch_opcode() == kMipsSubOvf) {
// Overflow occurs if overflow register is negative
__ slt(result, kScratchReg, zero_reg);
} else if (instr->arch_opcode() == kMipsMulOvf) {
// Overflow occurs if overflow register is not zero
__ Sgtu(result, kScratchReg, zero_reg);
} else if (instr->arch_opcode() == kMipsCmp) {
cc = FlagsConditionToConditionCmp(condition);
switch (cc) {
......
......@@ -1024,7 +1024,8 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
__ Daddu(i.OutputRegister(), i.InputRegister(0), i.InputOperand(1));
break;
case kMips64DaddOvf:
// Pseudo-instruction used for overflow/branch. No opcode emitted here.
__ DaddOverflow(i.OutputRegister(), i.InputRegister(0), i.InputOperand(1),
kScratchReg);
break;
case kMips64Sub:
__ Subu(i.OutputRegister(), i.InputRegister(0), i.InputOperand(1));
......@@ -1033,13 +1034,15 @@ CodeGenerator::CodeGenResult CodeGenerator::AssembleArchInstruction(
__ Dsubu(i.OutputRegister(), i.InputRegister(0), i.InputOperand(1));
break;
case kMips64DsubOvf:
// Pseudo-instruction used for overflow/branch. No opcode emitted here.
__ DsubOverflow(i.OutputRegister(), i.InputRegister(0), i.InputOperand(1),
kScratchReg);
break;
case kMips64Mul:
__ Mul(i.OutputRegister(), i.InputRegister(0), i.InputOperand(1));
break;
case kMips64MulOvf:
// Pseudo-instruction used for overflow/branch. No opcode emitted here.
__ MulOverflow(i.OutputRegister(), i.InputRegister(0), i.InputOperand(1),
kScratchReg);
break;
case kMips64MulHigh:
__ Mulh(i.OutputRegister(), i.InputRegister(0), i.InputOperand(1));
......@@ -3078,46 +3081,31 @@ void AssembleBranchToLabels(CodeGenerator* gen, TurboAssembler* tasm,
__ dsra32(kScratchReg, i.OutputRegister(), 0);
__ sra(at, i.OutputRegister(), 31);
__ Branch(tlabel, cc, at, Operand(kScratchReg));
} else if (instr->arch_opcode() == kMips64DaddOvf) {
} else if (instr->arch_opcode() == kMips64DaddOvf ||
instr->arch_opcode() == kMips64DsubOvf) {
switch (condition) {
// Overflow occurs if overflow register is negative
case kOverflow:
__ DaddBranchOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), tlabel, flabel);
__ Branch(tlabel, lt, kScratchReg, Operand(zero_reg));
break;
case kNotOverflow:
__ DaddBranchOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), flabel, tlabel);
__ Branch(tlabel, ge, kScratchReg, Operand(zero_reg));
break;
default:
UNSUPPORTED_COND(kMips64DaddOvf, condition);
UNSUPPORTED_COND(instr->arch_opcode(), condition);
break;
}
} else if (instr->arch_opcode() == kMips64DsubOvf) {
} else if (instr->arch_opcode() == kMips64MulOvf) {
// Overflow occurs if overflow register is not zero
switch (condition) {
case kOverflow:
__ DsubBranchOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), tlabel, flabel);
__ Branch(tlabel, ne, kScratchReg, Operand(zero_reg));
break;
case kNotOverflow:
__ DsubBranchOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), flabel, tlabel);
__ Branch(tlabel, eq, kScratchReg, Operand(zero_reg));
break;
default:
UNSUPPORTED_COND(kMips64DsubOvf, condition);
break;
}
} else if (instr->arch_opcode() == kMips64MulOvf) {
switch (condition) {
case kOverflow: {
__ MulBranchOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), tlabel, flabel, kScratchReg);
} break;
case kNotOverflow: {
__ MulBranchOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), flabel, tlabel, kScratchReg);
} break;
default:
UNSUPPORTED_COND(kMips64MulOvf, condition);
UNSUPPORTED_COND(kMipsMulOvf, condition);
break;
}
} else if (instr->arch_opcode() == kMips64Cmp) {
......@@ -3289,32 +3277,12 @@ void CodeGenerator::AssembleArchBoolean(Instruction* instr,
__ xori(result, result, 1);
return;
} else if (instr->arch_opcode() == kMips64DaddOvf ||
instr->arch_opcode() == kMips64DsubOvf ||
instr->arch_opcode() == kMips64MulOvf) {
Label flabel, tlabel;
switch (instr->arch_opcode()) {
case kMips64DaddOvf:
__ DaddBranchNoOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), &flabel);
break;
case kMips64DsubOvf:
__ DsubBranchNoOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), &flabel);
break;
case kMips64MulOvf:
__ MulBranchNoOvf(i.OutputRegister(), i.InputRegister(0),
i.InputOperand(1), &flabel, kScratchReg);
break;
default:
UNREACHABLE();
break;
}
__ li(result, 1);
__ Branch(&tlabel);
__ bind(&flabel);
__ li(result, 0);
__ bind(&tlabel);
instr->arch_opcode() == kMips64DsubOvf) {
// Overflow occurs if overflow register is negative
__ slt(result, kScratchReg, zero_reg);
} else if (instr->arch_opcode() == kMips64MulOvf) {
// Overflow occurs if overflow register is not zero
__ Sgtu(result, kScratchReg, zero_reg);
} else if (instr->arch_opcode() == kMips64Cmp) {
cc = FlagsConditionToConditionCmp(condition);
switch (cc) {
......
......@@ -821,6 +821,32 @@ void TurboAssembler::Sltu(Register rd, Register rs, const Operand& rt) {
}
}
void TurboAssembler::Sgt(Register rd, Register rs, const Operand& rt) {
if (rt.is_reg()) {
slt(rd, rt.rm(), rs);
} else {
// li handles the relocation.
UseScratchRegisterScope temps(this);
Register scratch = temps.hasAvailable() ? temps.Acquire() : t8;
DCHECK(rs != scratch);
li(scratch, rt);
slt(rd, scratch, rs);
}
}
void TurboAssembler::Sgtu(Register rd, Register rs, const Operand& rt) {
if (rt.is_reg()) {
sltu(rd, rt.rm(), rs);
} else {
// li handles the relocation.
UseScratchRegisterScope temps(this);
Register scratch = temps.hasAvailable() ? temps.Acquire() : t8;
DCHECK(rs != scratch);
li(scratch, rt);
sltu(rd, scratch, rs);
}
}
void TurboAssembler::Ror(Register rd, Register rs, const Operand& rt) {
if (IsMipsArchVariant(kMips32r2) || IsMipsArchVariant(kMips32r6)) {
if (rt.is_reg()) {
......@@ -4242,254 +4268,90 @@ bool TurboAssembler::AllowThisStubCall(CodeStub* stub) {
return has_frame() || !stub->SometimesSetsUpAFrame();
}
static inline void BranchOvfHelper(TurboAssembler* tasm, Register overflow_dst,
Label* overflow_label,
Label* no_overflow_label) {
DCHECK(overflow_label || no_overflow_label);
if (!overflow_label) {
DCHECK(no_overflow_label);
tasm->Branch(no_overflow_label, ge, overflow_dst, Operand(zero_reg));
void TurboAssembler::AddOverflow(Register dst, Register left,
const Operand& right, Register overflow) {
Register right_reg = no_reg;
Register scratch = t8;
if (!right.is_reg()) {
li(at, Operand(right));
right_reg = at;
} else {
tasm->Branch(overflow_label, lt, overflow_dst, Operand(zero_reg));
if (no_overflow_label) tasm->Branch(no_overflow_label);
right_reg = right.rm();
}
}
void TurboAssembler::AddBranchOvf(Register dst, Register left,
const Operand& right, Label* overflow_label,
Label* no_overflow_label, Register scratch) {
if (right.is_reg()) {
AddBranchOvf(dst, left, right.rm(), overflow_label, no_overflow_label,
scratch);
DCHECK(left != scratch && right_reg != scratch && dst != scratch &&
overflow != scratch);
DCHECK(overflow != left && overflow != right_reg);
if (dst == left || dst == right_reg) {
addu(scratch, left, right_reg);
xor_(overflow, scratch, left);
xor_(at, scratch, right_reg);
and_(overflow, overflow, at);
mov(dst, scratch);
} else {
if (IsMipsArchVariant(kMips32r6)) {
Register right_reg = t9;
DCHECK(left != right_reg);
li(right_reg, Operand(right));
AddBranchOvf(dst, left, right_reg, overflow_label, no_overflow_label);
} else {
Register overflow_dst = t9;
DCHECK(dst != scratch);
DCHECK(dst != overflow_dst);
DCHECK(scratch != overflow_dst);
DCHECK(left != overflow_dst);
if (dst == left) {
mov(scratch, left); // Preserve left.
Addu(dst, left, right.immediate()); // Left is overwritten.
xor_(scratch, dst, scratch); // Original left.
// Load right since xori takes uint16 as immediate.
Addu(overflow_dst, zero_reg, right);
xor_(overflow_dst, dst, overflow_dst);
and_(overflow_dst, overflow_dst, scratch);
} else {
Addu(dst, left, right.immediate());
xor_(overflow_dst, dst, left);
// Load right since xori takes uint16 as immediate.
Addu(scratch, zero_reg, right);
xor_(scratch, dst, scratch);
and_(overflow_dst, scratch, overflow_dst);
}
BranchOvfHelper(this, overflow_dst, overflow_label, no_overflow_label);
}
addu(dst, left, right_reg);
xor_(overflow, dst, left);
xor_(at, dst, right_reg);
and_(overflow, overflow, at);
}
}
void TurboAssembler::AddBranchOvf(Register dst, Register left, Register right,
Label* overflow_label,
Label* no_overflow_label, Register scratch) {
if (IsMipsArchVariant(kMips32r6)) {
if (!overflow_label) {
DCHECK(no_overflow_label);
DCHECK(dst != scratch);
Register left_reg = left == dst ? scratch : left;
Register right_reg = right == dst ? t9 : right;
DCHECK(dst != left_reg);
DCHECK(dst != right_reg);
Move(left_reg, left);
Move(right_reg, right);
addu(dst, left, right);
Bnvc(left_reg, right_reg, no_overflow_label);
} else {
Bovc(left, right, overflow_label);
addu(dst, left, right);
if (no_overflow_label) bc(no_overflow_label);
}
void TurboAssembler::SubOverflow(Register dst, Register left,
const Operand& right, Register overflow) {
Register right_reg = no_reg;
Register scratch = t8;
if (!right.is_reg()) {
li(at, Operand(right));
right_reg = at;
} else {
Register overflow_dst = t9;
DCHECK(dst != scratch);
DCHECK(dst != overflow_dst);
DCHECK(scratch != overflow_dst);
DCHECK(left != overflow_dst);
DCHECK(right != overflow_dst);
DCHECK(left != scratch);
DCHECK(right != scratch);
if (left == right && dst == left) {
mov(overflow_dst, right);
right = overflow_dst;
}
if (dst == left) {
mov(scratch, left); // Preserve left.
addu(dst, left, right); // Left is overwritten.
xor_(scratch, dst, scratch); // Original left.
xor_(overflow_dst, dst, right);
and_(overflow_dst, overflow_dst, scratch);
} else if (dst == right) {
mov(scratch, right); // Preserve right.
addu(dst, left, right); // Right is overwritten.
xor_(scratch, dst, scratch); // Original right.
xor_(overflow_dst, dst, left);
and_(overflow_dst, overflow_dst, scratch);
} else {
addu(dst, left, right);
xor_(overflow_dst, dst, left);
xor_(scratch, dst, right);
and_(overflow_dst, scratch, overflow_dst);
}
BranchOvfHelper(this, overflow_dst, overflow_label, no_overflow_label);
right_reg = right.rm();
}
}
void TurboAssembler::SubBranchOvf(Register dst, Register left,
const Operand& right, Label* overflow_label,
Label* no_overflow_label, Register scratch) {
DCHECK(overflow_label || no_overflow_label);
if (right.is_reg()) {
SubBranchOvf(dst, left, right.rm(), overflow_label, no_overflow_label,
scratch);
} else {
Register overflow_dst = t9;
DCHECK(dst != scratch);
DCHECK(dst != overflow_dst);
DCHECK(scratch != overflow_dst);
DCHECK(left != overflow_dst);
DCHECK(left != scratch);
if (dst == left) {
mov(scratch, left); // Preserve left.
Subu(dst, left, right.immediate()); // Left is overwritten.
// Load right since xori takes uint16 as immediate.
Addu(overflow_dst, zero_reg, right);
xor_(overflow_dst, scratch, overflow_dst); // scratch is original left.
xor_(scratch, dst, scratch); // scratch is original left.
and_(overflow_dst, scratch, overflow_dst);
} else {
Subu(dst, left, right);
xor_(overflow_dst, dst, left);
// Load right since xori takes uint16 as immediate.
Addu(scratch, zero_reg, right);
xor_(scratch, left, scratch);
and_(overflow_dst, scratch, overflow_dst);
}
BranchOvfHelper(this, overflow_dst, overflow_label, no_overflow_label);
}
}
void TurboAssembler::SubBranchOvf(Register dst, Register left, Register right,
Label* overflow_label,
Label* no_overflow_label, Register scratch) {
DCHECK(overflow_label || no_overflow_label);
Register overflow_dst = t9;
DCHECK(dst != scratch);
DCHECK(dst != overflow_dst);
DCHECK(scratch != overflow_dst);
DCHECK(overflow_dst != left);
DCHECK(overflow_dst != right);
DCHECK(scratch != left);
DCHECK(scratch != right);
// This happens with some crankshaft code. Since Subu works fine if
// left == right, let's not make that restriction here.
if (left == right) {
mov(dst, zero_reg);
if (no_overflow_label) {
Branch(no_overflow_label);
}
}
if (dst == left) {
mov(scratch, left); // Preserve left.
subu(dst, left, right); // Left is overwritten.
xor_(overflow_dst, dst, scratch); // scratch is original left.
xor_(scratch, scratch, right); // scratch is original left.
and_(overflow_dst, scratch, overflow_dst);
} else if (dst == right) {
mov(scratch, right); // Preserve right.
subu(dst, left, right); // Right is overwritten.
xor_(overflow_dst, dst, left);
xor_(scratch, left, scratch); // Original right.
and_(overflow_dst, scratch, overflow_dst);
} else {
subu(dst, left, right);
xor_(overflow_dst, dst, left);
xor_(scratch, left, right);
and_(overflow_dst, scratch, overflow_dst);
}
BranchOvfHelper(this, overflow_dst, overflow_label, no_overflow_label);
}
static inline void BranchOvfHelperMult(TurboAssembler* tasm,
Register overflow_dst,
Label* overflow_label,
Label* no_overflow_label) {
DCHECK(overflow_label || no_overflow_label);
if (!overflow_label) {
DCHECK(no_overflow_label);
tasm->Branch(no_overflow_label, eq, overflow_dst, Operand(zero_reg));
DCHECK(left != scratch && right_reg != scratch && dst != scratch &&
overflow != scratch);
DCHECK(overflow != left && overflow != right_reg);
if (dst == left || dst == right_reg) {
subu(scratch, left, right_reg);
xor_(overflow, left, scratch);
xor_(at, left, right_reg);
and_(overflow, overflow, at);
mov(dst, scratch);
} else {
tasm->Branch(overflow_label, ne, overflow_dst, Operand(zero_reg));
if (no_overflow_label) tasm->Branch(no_overflow_label);
subu(dst, left, right_reg);
xor_(overflow, left, dst);
xor_(at, left, right_reg);
and_(overflow, overflow, at);
}
}
void TurboAssembler::MulBranchOvf(Register dst, Register left,
const Operand& right, Label* overflow_label,
Label* no_overflow_label, Register scratch) {
DCHECK(overflow_label || no_overflow_label);
if (right.is_reg()) {
MulBranchOvf(dst, left, right.rm(), overflow_label, no_overflow_label,
scratch);
void TurboAssembler::MulOverflow(Register dst, Register left,
const Operand& right, Register overflow) {
Register right_reg = no_reg;
Register scratch = t8;
Register scratch2 = t9;
if (!right.is_reg()) {
li(at, Operand(right));
right_reg = at;
} else {
Register overflow_dst = t9;
DCHECK(dst != scratch);
DCHECK(dst != overflow_dst);
DCHECK(scratch != overflow_dst);
DCHECK(left != overflow_dst);
DCHECK(left != scratch);
Mul(overflow_dst, dst, left, right.immediate());
sra(scratch, dst, 31);
xor_(overflow_dst, overflow_dst, scratch);
BranchOvfHelperMult(this, overflow_dst, overflow_label, no_overflow_label);
right_reg = right.rm();
}
}
void TurboAssembler::MulBranchOvf(Register dst, Register left, Register right,
Label* overflow_label,
Label* no_overflow_label, Register scratch) {
DCHECK(overflow_label || no_overflow_label);
Register overflow_dst = t9;
DCHECK(dst != scratch);
DCHECK(dst != overflow_dst);
DCHECK(scratch != overflow_dst);
DCHECK(overflow_dst != left);
DCHECK(overflow_dst != right);
DCHECK(scratch != left);
DCHECK(scratch != right);
DCHECK(left != scratch && right_reg != scratch && dst != scratch &&
overflow != scratch);
DCHECK(overflow != left && overflow != right_reg);
if (IsMipsArchVariant(kMips32r6) && dst == right) {
mov(scratch, right);
Mul(overflow_dst, dst, left, scratch);
sra(scratch, dst, 31);
xor_(overflow_dst, overflow_dst, scratch);
if (dst == left || dst == right_reg) {
Mul(overflow, scratch2, left, right_reg);
sra(scratch, scratch2, 31);
xor_(overflow, overflow, scratch);
mov(dst, scratch2);
} else {
Mul(overflow_dst, dst, left, right);
Mul(overflow, dst, left, right_reg);
sra(scratch, dst, 31);
xor_(overflow_dst, overflow_dst, scratch);
xor_(overflow, overflow, scratch);
}
BranchOvfHelperMult(this, overflow_dst, overflow_label, no_overflow_label);
}
void TurboAssembler::CallRuntimeDelayed(Zone* zone, Runtime::FunctionId fid,
......
......@@ -461,6 +461,8 @@ class TurboAssembler : public Assembler {
DEFINE_INSTRUCTION(Slt);
DEFINE_INSTRUCTION(Sltu);
DEFINE_INSTRUCTION(Sgt);
DEFINE_INSTRUCTION(Sgtu);
// MIPS32 R2 instruction macro.
DEFINE_INSTRUCTION(Ror);
......@@ -742,63 +744,19 @@ class TurboAssembler : public Assembler {
void Move(FPURegister dst, uint64_t src);
// -------------------------------------------------------------------------
// Overflow handling functions.
// Usage: first call the appropriate arithmetic function, then call one of the
// jump functions with the overflow_dst register as the second parameter.
inline void AddBranchOvf(Register dst, Register left, const Operand& right,
Label* overflow_label, Register scratch = at) {
AddBranchOvf(dst, left, right, overflow_label, nullptr, scratch);
}
inline void AddBranchNoOvf(Register dst, Register left, const Operand& right,
Label* no_overflow_label, Register scratch = at) {
AddBranchOvf(dst, left, right, nullptr, no_overflow_label, scratch);
}
void AddBranchOvf(Register dst, Register left, const Operand& right,
Label* overflow_label, Label* no_overflow_label,
Register scratch = at);
void AddBranchOvf(Register dst, Register left, Register right,
Label* overflow_label, Label* no_overflow_label,
Register scratch = at);
inline void SubBranchOvf(Register dst, Register left, const Operand& right,
Label* overflow_label, Register scratch = at) {
SubBranchOvf(dst, left, right, overflow_label, nullptr, scratch);
}
inline void SubBranchNoOvf(Register dst, Register left, const Operand& right,
Label* no_overflow_label, Register scratch = at) {
SubBranchOvf(dst, left, right, nullptr, no_overflow_label, scratch);
}
void SubBranchOvf(Register dst, Register left, const Operand& right,
Label* overflow_label, Label* no_overflow_label,
Register scratch = at);
void SubBranchOvf(Register dst, Register left, Register right,
Label* overflow_label, Label* no_overflow_label,
Register scratch = at);
inline void MulBranchOvf(Register dst, Register left, const Operand& right,
Label* overflow_label, Register scratch = at) {
MulBranchOvf(dst, left, right, overflow_label, nullptr, scratch);
}
inline void MulBranchNoOvf(Register dst, Register left, const Operand& right,
Label* no_overflow_label, Register scratch = at) {
MulBranchOvf(dst, left, right, nullptr, no_overflow_label, scratch);
}
void MulBranchOvf(Register dst, Register left, const Operand& right,
Label* overflow_label, Label* no_overflow_label,
Register scratch = at);
void MulBranchOvf(Register dst, Register left, Register right,
Label* overflow_label, Label* no_overflow_label,
Register scratch = at);
// Overflow operations.
// AddOverflow sets overflow register to a negative value if
// overflow occured, otherwise it is zero or positive
void AddOverflow(Register dst, Register left, const Operand& right,
Register overflow);
// SubOverflow sets overflow register to a negative value if
// overflow occured, otherwise it is zero or positive
void SubOverflow(Register dst, Register left, const Operand& right,
Register overflow);
// MulOverflow sets overflow register to zero if no overflow occured
void MulOverflow(Register dst, Register left, const Operand& right,
Register overflow);
// Number of instructions needed for calculation of switch table entry address
#ifdef _MIPS_ARCH_MIPS32R6
......
......@@ -959,6 +959,32 @@ void TurboAssembler::Sltu(Register rd, Register rs, const Operand& rt) {
}
}
void TurboAssembler::Sgt(Register rd, Register rs, const Operand& rt) {
if (rt.is_reg()) {
slt(rd, rt.rm(), rs);
} else {
// li handles the relocation.
UseScratchRegisterScope temps(this);
Register scratch = temps.hasAvailable() ? temps.Acquire() : t8;
DCHECK(rs != scratch);
li(scratch, rt);
slt(rd, scratch, rs);
}
}
void TurboAssembler::Sgtu(Register rd, Register rs, const Operand& rt) {
if (rt.is_reg()) {
sltu(rd, rt.rm(), rs);
} else {
// li handles the relocation.
UseScratchRegisterScope temps(this);
Register scratch = temps.hasAvailable() ? temps.Acquire() : t8;
DCHECK(rs != scratch);
li(scratch, rt);
sltu(rd, scratch, rs);
}
}
void TurboAssembler::Ror(Register rd, Register rs, const Operand& rt) {
if (rt.is_reg()) {
rotrv(rd, rs, rt.rm());
......@@ -4564,231 +4590,90 @@ bool TurboAssembler::AllowThisStubCall(CodeStub* stub) {
return has_frame() || !stub->SometimesSetsUpAFrame();
}
static inline void BranchOvfHelper(TurboAssembler* tasm, Register overflow_dst,
Label* overflow_label,
Label* no_overflow_label) {
DCHECK(overflow_label || no_overflow_label);
if (!overflow_label) {
DCHECK(no_overflow_label);
tasm->Branch(no_overflow_label, ge, overflow_dst, Operand(zero_reg));
void TurboAssembler::DaddOverflow(Register dst, Register left,
const Operand& right, Register overflow) {
Register right_reg = no_reg;
Register scratch = t8;
if (!right.is_reg()) {
li(at, Operand(right));
right_reg = at;
} else {
tasm->Branch(overflow_label, lt, overflow_dst, Operand(zero_reg));
if (no_overflow_label) tasm->Branch(no_overflow_label);
right_reg = right.rm();
}
}
void TurboAssembler::DaddBranchOvf(Register dst, Register left,
const Operand& right, Label* overflow_label,
Label* no_overflow_label, Register scratch) {
if (right.is_reg()) {
DaddBranchOvf(dst, left, right.rm(), overflow_label, no_overflow_label,
scratch);
} else {
Register overflow_dst = t9;
DCHECK(dst != scratch);
DCHECK(dst != overflow_dst);
DCHECK(scratch != overflow_dst);
DCHECK(left != overflow_dst);
li(overflow_dst, right); // Load right.
if (dst == left) {
mov(scratch, left); // Preserve left.
Daddu(dst, left, overflow_dst); // Left is overwritten.
xor_(scratch, dst, scratch); // Original left.
xor_(overflow_dst, dst, overflow_dst);
and_(overflow_dst, overflow_dst, scratch);
} else {
Daddu(dst, left, overflow_dst);
xor_(scratch, dst, overflow_dst);
xor_(overflow_dst, dst, left);
and_(overflow_dst, scratch, overflow_dst);
}
BranchOvfHelper(this, overflow_dst, overflow_label, no_overflow_label);
}
}
void TurboAssembler::DaddBranchOvf(Register dst, Register left, Register right,
Label* overflow_label,
Label* no_overflow_label, Register scratch) {
Register overflow_dst = t9;
DCHECK(dst != scratch);
DCHECK(dst != overflow_dst);
DCHECK(scratch != overflow_dst);
DCHECK(left != overflow_dst);
DCHECK(right != overflow_dst);
DCHECK(left != scratch);
DCHECK(right != scratch);
if (left == right && dst == left) {
mov(overflow_dst, right);
right = overflow_dst;
}
if (dst == left) {
mov(scratch, left); // Preserve left.
daddu(dst, left, right); // Left is overwritten.
xor_(scratch, dst, scratch); // Original left.
xor_(overflow_dst, dst, right);
and_(overflow_dst, overflow_dst, scratch);
} else if (dst == right) {
mov(scratch, right); // Preserve right.
daddu(dst, left, right); // Right is overwritten.
xor_(scratch, dst, scratch); // Original right.
xor_(overflow_dst, dst, left);
and_(overflow_dst, overflow_dst, scratch);
DCHECK(left != scratch && right_reg != scratch && dst != scratch &&
overflow != scratch);
DCHECK(overflow != left && overflow != right_reg);
if (dst == left || dst == right_reg) {
daddu(scratch, left, right_reg);
xor_(overflow, scratch, left);
xor_(at, scratch, right_reg);
and_(overflow, overflow, at);
mov(dst, scratch);
} else {
daddu(dst, left, right);
xor_(overflow_dst, dst, left);
xor_(scratch, dst, right);
and_(overflow_dst, scratch, overflow_dst);
daddu(dst, left, right_reg);
xor_(overflow, dst, left);
xor_(at, dst, right_reg);
and_(overflow, overflow, at);
}
BranchOvfHelper(this, overflow_dst, overflow_label, no_overflow_label);
}
void TurboAssembler::DsubBranchOvf(Register dst, Register left,
const Operand& right, Label* overflow_label,
Label* no_overflow_label, Register scratch) {
DCHECK(overflow_label || no_overflow_label);
if (right.is_reg()) {
DsubBranchOvf(dst, left, right.rm(), overflow_label, no_overflow_label,
scratch);
void TurboAssembler::DsubOverflow(Register dst, Register left,
const Operand& right, Register overflow) {
Register right_reg = no_reg;
Register scratch = t8;
if (!right.is_reg()) {
li(at, Operand(right));
right_reg = at;
} else {
Register overflow_dst = t9;
DCHECK(dst != scratch);
DCHECK(dst != overflow_dst);
DCHECK(scratch != overflow_dst);
DCHECK(left != overflow_dst);
DCHECK(left != scratch);
li(overflow_dst, right); // Load right.
if (dst == left) {
mov(scratch, left); // Preserve left.
Dsubu(dst, left, overflow_dst); // Left is overwritten.
xor_(overflow_dst, scratch, overflow_dst); // scratch is original left.
xor_(scratch, dst, scratch); // scratch is original left.
and_(overflow_dst, scratch, overflow_dst);
} else {
Dsubu(dst, left, overflow_dst);
xor_(scratch, left, overflow_dst);
xor_(overflow_dst, dst, left);
and_(overflow_dst, scratch, overflow_dst);
}
BranchOvfHelper(this, overflow_dst, overflow_label, no_overflow_label);
}
}
void TurboAssembler::DsubBranchOvf(Register dst, Register left, Register right,
Label* overflow_label,
Label* no_overflow_label, Register scratch) {
DCHECK(overflow_label || no_overflow_label);
Register overflow_dst = t9;
DCHECK(dst != scratch);
DCHECK(dst != overflow_dst);
DCHECK(scratch != overflow_dst);
DCHECK(overflow_dst != left);
DCHECK(overflow_dst != right);
DCHECK(scratch != left);
DCHECK(scratch != right);
// This happens with some crankshaft code. Since Subu works fine if
// left == right, let's not make that restriction here.
if (left == right) {
mov(dst, zero_reg);
if (no_overflow_label) {
Branch(no_overflow_label);
}
right_reg = right.rm();
}
if (dst == left) {
mov(scratch, left); // Preserve left.
dsubu(dst, left, right); // Left is overwritten.
xor_(overflow_dst, dst, scratch); // scratch is original left.
xor_(scratch, scratch, right); // scratch is original left.
and_(overflow_dst, scratch, overflow_dst);
} else if (dst == right) {
mov(scratch, right); // Preserve right.
dsubu(dst, left, right); // Right is overwritten.
xor_(overflow_dst, dst, left);
xor_(scratch, left, scratch); // Original right.
and_(overflow_dst, scratch, overflow_dst);
} else {
dsubu(dst, left, right);
xor_(overflow_dst, dst, left);
xor_(scratch, left, right);
and_(overflow_dst, scratch, overflow_dst);
}
BranchOvfHelper(this, overflow_dst, overflow_label, no_overflow_label);
}
static inline void BranchOvfHelperMult(TurboAssembler* tasm,
Register overflow_dst,
Label* overflow_label,
Label* no_overflow_label) {
DCHECK(overflow_label || no_overflow_label);
if (!overflow_label) {
DCHECK(no_overflow_label);
tasm->Branch(no_overflow_label, eq, overflow_dst, Operand(zero_reg));
DCHECK(left != scratch && right_reg != scratch && dst != scratch &&
overflow != scratch);
DCHECK(overflow != left && overflow != right_reg);
if (dst == left || dst == right_reg) {
dsubu(scratch, left, right_reg);
xor_(overflow, left, scratch);
xor_(at, left, right_reg);
and_(overflow, overflow, at);
mov(dst, scratch);
} else {
tasm->Branch(overflow_label, ne, overflow_dst, Operand(zero_reg));
if (no_overflow_label) tasm->Branch(no_overflow_label);
dsubu(dst, left, right_reg);
xor_(overflow, left, dst);
xor_(at, left, right_reg);
and_(overflow, overflow, at);
}
}
void TurboAssembler::MulBranchOvf(Register dst, Register left,
const Operand& right, Label* overflow_label,
Label* no_overflow_label, Register scratch) {
DCHECK(overflow_label || no_overflow_label);
if (right.is_reg()) {
MulBranchOvf(dst, left, right.rm(), overflow_label, no_overflow_label,
scratch);
void TurboAssembler::MulOverflow(Register dst, Register left,
const Operand& right, Register overflow) {
Register right_reg = no_reg;
Register scratch = t8;
if (!right.is_reg()) {
li(at, Operand(right));
right_reg = at;
} else {
Register overflow_dst = t9;
DCHECK(dst != scratch);
DCHECK(dst != overflow_dst);
DCHECK(scratch != overflow_dst);
DCHECK(left != overflow_dst);
DCHECK(left != scratch);
if (dst == left) {
Mul(scratch, left, static_cast<int32_t>(right.immediate()));
Mulh(overflow_dst, left, static_cast<int32_t>(right.immediate()));
mov(dst, scratch);
} else {
Mul(dst, left, static_cast<int32_t>(right.immediate()));
Mulh(overflow_dst, left, static_cast<int32_t>(right.immediate()));
}
dsra32(scratch, dst, 0);
xor_(overflow_dst, overflow_dst, scratch);
BranchOvfHelperMult(this, overflow_dst, overflow_label, no_overflow_label);
right_reg = right.rm();
}
}
void TurboAssembler::MulBranchOvf(Register dst, Register left, Register right,
Label* overflow_label,
Label* no_overflow_label, Register scratch) {
DCHECK(overflow_label || no_overflow_label);
Register overflow_dst = t9;
DCHECK(dst != scratch);
DCHECK(dst != overflow_dst);
DCHECK(scratch != overflow_dst);
DCHECK(overflow_dst != left);
DCHECK(overflow_dst != right);
DCHECK(scratch != left);
DCHECK(scratch != right);
DCHECK(left != scratch && right_reg != scratch && dst != scratch &&
overflow != scratch);
DCHECK(overflow != left && overflow != right_reg);
if (dst == left || dst == right) {
Mul(scratch, left, right);
Mulh(overflow_dst, left, right);
if (dst == left || dst == right_reg) {
Mul(scratch, left, right_reg);
Mulh(overflow, left, right_reg);
mov(dst, scratch);
} else {
Mul(dst, left, right);
Mulh(overflow_dst, left, right);
Mul(dst, left, right_reg);
Mulh(overflow, left, right_reg);
}
dsra32(scratch, dst, 0);
xor_(overflow_dst, overflow_dst, scratch);
BranchOvfHelperMult(this, overflow_dst, overflow_label, no_overflow_label);
xor_(overflow, overflow, scratch);
}
void TurboAssembler::CallRuntimeDelayed(Zone* zone, Runtime::FunctionId fid,
......
......@@ -495,6 +495,8 @@ class TurboAssembler : public Assembler {
DEFINE_INSTRUCTION(Slt);
DEFINE_INSTRUCTION(Sltu);
DEFINE_INSTRUCTION(Sgt);
DEFINE_INSTRUCTION(Sgtu);
// MIPS32 R2 instruction macro.
DEFINE_INSTRUCTION(Ror);
......@@ -756,59 +758,17 @@ class TurboAssembler : public Assembler {
void Move(FPURegister dst, uint32_t src);
void Move(FPURegister dst, uint64_t src);
inline void MulBranchOvf(Register dst, Register left, const Operand& right,
Label* overflow_label, Register scratch = at) {
MulBranchOvf(dst, left, right, overflow_label, nullptr, scratch);
}
inline void MulBranchNoOvf(Register dst, Register left, const Operand& right,
Label* no_overflow_label, Register scratch = at) {
MulBranchOvf(dst, left, right, nullptr, no_overflow_label, scratch);
}
void MulBranchOvf(Register dst, Register left, const Operand& right,
Label* overflow_label, Label* no_overflow_label,
Register scratch = at);
void MulBranchOvf(Register dst, Register left, Register right,
Label* overflow_label, Label* no_overflow_label,
Register scratch = at);
inline void DaddBranchOvf(Register dst, Register left, const Operand& right,
Label* overflow_label, Register scratch = at) {
DaddBranchOvf(dst, left, right, overflow_label, nullptr, scratch);
}
inline void DaddBranchNoOvf(Register dst, Register left, const Operand& right,
Label* no_overflow_label, Register scratch = at) {
DaddBranchOvf(dst, left, right, nullptr, no_overflow_label, scratch);
}
void DaddBranchOvf(Register dst, Register left, const Operand& right,
Label* overflow_label, Label* no_overflow_label,
Register scratch = at);
void DaddBranchOvf(Register dst, Register left, Register right,
Label* overflow_label, Label* no_overflow_label,
Register scratch = at);
inline void DsubBranchOvf(Register dst, Register left, const Operand& right,
Label* overflow_label, Register scratch = at) {
DsubBranchOvf(dst, left, right, overflow_label, nullptr, scratch);
}
inline void DsubBranchNoOvf(Register dst, Register left, const Operand& right,
Label* no_overflow_label, Register scratch = at) {
DsubBranchOvf(dst, left, right, nullptr, no_overflow_label, scratch);
}
void DsubBranchOvf(Register dst, Register left, const Operand& right,
Label* overflow_label, Label* no_overflow_label,
Register scratch = at);
void DsubBranchOvf(Register dst, Register left, Register right,
Label* overflow_label, Label* no_overflow_label,
Register scratch = at);
// DaddOverflow sets overflow register to a negative value if
// overflow occured, otherwise it is zero or positive
void DaddOverflow(Register dst, Register left, const Operand& right,
Register overflow);
// DsubOverflow sets overflow register to a negative value if
// overflow occured, otherwise it is zero or positive
void DsubOverflow(Register dst, Register left, const Operand& right,
Register overflow);
// MulOverflow sets overflow register to zero if no overflow occured
void MulOverflow(Register dst, Register left, const Operand& right,
Register overflow);
// Number of instructions needed for calculation of switch table entry address
#ifdef _MIPS_ARCH_MIPS64R6
......
......@@ -588,403 +588,108 @@ static const std::vector<int32_t> overflow_int32_test_values() {
return std::vector<int32_t>(&kValues[0], &kValues[arraysize(kValues)]);
}
enum OverflowBranchType {
kAddBranchOverflow,
kSubBranchOverflow,
};
struct OverflowRegisterCombination {
Register dst;
Register left;
Register right;
Register scratch;
};
static const std::vector<enum OverflowBranchType> overflow_branch_type() {
static const enum OverflowBranchType kValues[] = {kAddBranchOverflow,
kSubBranchOverflow};
return std::vector<enum OverflowBranchType>(&kValues[0],
&kValues[arraysize(kValues)]);
}
static const std::vector<struct OverflowRegisterCombination>
overflow_register_combination() {
static const struct OverflowRegisterCombination kValues[] = {
{t0, t1, t2, t3}, {t0, t0, t2, t3}, {t0, t1, t0, t3}, {t0, t1, t1, t3}};
return std::vector<struct OverflowRegisterCombination>(
&kValues[0], &kValues[arraysize(kValues)]);
}
template <typename T>
static bool IsAddOverflow(T x, T y) {
DCHECK(std::numeric_limits<T>::is_integer);
T max = std::numeric_limits<T>::max();
T min = std::numeric_limits<T>::min();
return (x > 0 && y > (max - x)) || (x < 0 && y < (min - x));
}
template <typename T>
static bool IsSubOverflow(T x, T y) {
DCHECK(std::numeric_limits<T>::is_integer);
T max = std::numeric_limits<T>::max();
T min = std::numeric_limits<T>::min();
return (y > 0 && x < (min + y)) || (y < 0 && x > (max + y));
}
template <typename IN_TYPE, typename Func>
static bool runOverflow(IN_TYPE valLeft, IN_TYPE valRight,
Func GenerateOverflowInstructions) {
typedef int32_t(F_CVT)(char* x0, int x1, int x2, int x3, int x4);
TEST(OverflowInstructions) {
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
HandleScope scope(isolate);
MacroAssembler assm(isolate, nullptr, 0,
v8::internal::CodeObjectRequired::kYes);
MacroAssembler* masm = &assm;
GenerateOverflowInstructions(masm, valLeft, valRight);
__ jr(ra);
__ nop();
CodeDesc desc;
assm.GetCode(isolate, &desc);
Handle<Code> code =
isolate->factory()->NewCode(desc, Code::STUB, Handle<Code>());
auto f = GeneratedCode<F_CVT>::FromCode(*code);
int32_t r = reinterpret_cast<int32_t>(f.Call(0, 0, 0, 0, 0));
DCHECK(r == 0 || r == 1);
return r;
}
TEST(BranchOverflowInt32BothLabelsTrampoline) {
if (!IsMipsArchVariant(kMips32r6)) return;
static const int kMaxBranchOffset = (1 << (18 - 1)) - 1;
FOR_INT32_INPUTS(i, overflow_int32_test_values) {
FOR_INT32_INPUTS(j, overflow_int32_test_values) {
FOR_ENUM_INPUTS(br, OverflowBranchType, overflow_branch_type) {
FOR_STRUCT_INPUTS(regComb, OverflowRegisterCombination,
overflow_register_combination) {
int32_t ii = *i;
int32_t jj = *j;
enum OverflowBranchType branchType = *br;
struct OverflowRegisterCombination rc = *regComb;
// If left and right register are same then left and right
// test values must also be same, otherwise we skip the test
if (rc.left.code() == rc.right.code()) {
if (ii != jj) {
continue;
}
}
bool res1 = runOverflow<int32_t>(
ii, jj, [branchType, rc](MacroAssembler* masm, int32_t valLeft,
int32_t valRight) {
Label overflow, no_overflow, end;
__ li(rc.left, valLeft);
__ li(rc.right, valRight);
switch (branchType) {
case kAddBranchOverflow:
__ AddBranchOvf(rc.dst, rc.left, rc.right, &overflow,
&no_overflow, rc.scratch);
break;
case kSubBranchOverflow:
__ SubBranchOvf(rc.dst, rc.left, rc.right, &overflow,
&no_overflow, rc.scratch);
break;
}
Label done;
size_t nr_calls =
kMaxBranchOffset / (2 * Instruction::kInstrSize) + 2;
for (size_t i = 0; i < nr_calls; ++i) {
__ BranchShort(&done, eq, a0, Operand(a1));
}
__ bind(&done);
__ li(v0, 2);
__ Branch(&end);
__ bind(&overflow);
__ li(v0, 1);
__ Branch(&end);
__ bind(&no_overflow);
__ li(v0, 0);
__ bind(&end);
});
switch (branchType) {
case kAddBranchOverflow:
CHECK_EQ(IsAddOverflow<int32_t>(ii, jj), res1);
break;
case kSubBranchOverflow:
CHECK_EQ(IsSubOverflow<int32_t>(ii, jj), res1);
break;
default:
UNREACHABLE();
}
}
}
}
}
}
HandleScope handles(isolate);
TEST(BranchOverflowInt32BothLabels) {
FOR_INT32_INPUTS(i, overflow_int32_test_values) {
FOR_INT32_INPUTS(j, overflow_int32_test_values) {
FOR_ENUM_INPUTS(br, OverflowBranchType, overflow_branch_type) {
FOR_STRUCT_INPUTS(regComb, OverflowRegisterCombination,
overflow_register_combination) {
int32_t ii = *i;
int32_t jj = *j;
enum OverflowBranchType branchType = *br;
struct OverflowRegisterCombination rc = *regComb;
// If left and right register are same then left and right
// test values must also be same, otherwise we skip the test
if (rc.left.code() == rc.right.code()) {
if (ii != jj) {
continue;
}
}
bool res1 = runOverflow<int32_t>(
ii, jj, [branchType, rc](MacroAssembler* masm, int32_t valLeft,
int32_t valRight) {
Label overflow, no_overflow, end;
__ li(rc.left, valLeft);
__ li(rc.right, valRight);
switch (branchType) {
case kAddBranchOverflow:
__ AddBranchOvf(rc.dst, rc.left, rc.right, &overflow,
&no_overflow, rc.scratch);
break;
case kSubBranchOverflow:
__ SubBranchOvf(rc.dst, rc.left, rc.right, &overflow,
&no_overflow, rc.scratch);
break;
}
__ li(v0, 2);
__ Branch(&end);
__ bind(&overflow);
__ li(v0, 1);
__ Branch(&end);
__ bind(&no_overflow);
__ li(v0, 0);
__ bind(&end);
});
bool res2 = runOverflow<int32_t>(
ii, jj, [branchType, rc](MacroAssembler* masm, int32_t valLeft,
int32_t valRight) {
Label overflow, no_overflow, end;
__ li(rc.left, valLeft);
switch (branchType) {
case kAddBranchOverflow:
__ AddBranchOvf(rc.dst, rc.left, Operand(valRight),
&overflow, &no_overflow, rc.scratch);
break;
case kSubBranchOverflow:
__ SubBranchOvf(rc.dst, rc.left, Operand(valRight),
&overflow, &no_overflow, rc.scratch);
break;
}
__ li(v0, 2);
__ Branch(&end);
__ bind(&overflow);
__ li(v0, 1);
__ Branch(&end);
__ bind(&no_overflow);
__ li(v0, 0);
__ bind(&end);
});
switch (branchType) {
case kAddBranchOverflow:
CHECK_EQ(IsAddOverflow<int32_t>(ii, jj), res1);
CHECK_EQ(IsAddOverflow<int32_t>(ii, jj), res2);
break;
case kSubBranchOverflow:
CHECK_EQ(IsSubOverflow<int32_t>(ii, jj), res1);
CHECK_EQ(IsSubOverflow<int32_t>(ii, jj), res2);
break;
default:
UNREACHABLE();
}
}
}
}
}
}
struct T {
int32_t lhs;
int32_t rhs;
int32_t output_add;
int32_t output_add2;
int32_t output_sub;
int32_t output_sub2;
int32_t output_mul;
int32_t output_mul2;
int32_t overflow_add;
int32_t overflow_add2;
int32_t overflow_sub;
int32_t overflow_sub2;
int32_t overflow_mul;
int32_t overflow_mul2;
};
T t;
TEST(BranchOverflowInt32LeftLabel) {
FOR_INT32_INPUTS(i, overflow_int32_test_values) {
FOR_INT32_INPUTS(j, overflow_int32_test_values) {
FOR_ENUM_INPUTS(br, OverflowBranchType, overflow_branch_type) {
FOR_STRUCT_INPUTS(regComb, OverflowRegisterCombination,
overflow_register_combination) {
int32_t ii = *i;
int32_t jj = *j;
enum OverflowBranchType branchType = *br;
struct OverflowRegisterCombination rc = *regComb;
// If left and right register are same then left and right
// test values must also be same, otherwise we skip the test
if (rc.left.code() == rc.right.code()) {
if (ii != jj) {
continue;
}
}
bool res1 = runOverflow<int32_t>(
ii, jj, [branchType, rc](MacroAssembler* masm, int32_t valLeft,
int32_t valRight) {
Label overflow, end;
__ li(rc.left, valLeft);
__ li(rc.right, valRight);
switch (branchType) {
case kAddBranchOverflow:
__ AddBranchOvf(rc.dst, rc.left, rc.right, &overflow,
nullptr, rc.scratch);
break;
case kSubBranchOverflow:
__ SubBranchOvf(rc.dst, rc.left, rc.right, &overflow,
nullptr, rc.scratch);
break;
}
__ li(v0, 0);
__ Branch(&end);
__ bind(&overflow);
__ li(v0, 1);
__ bind(&end);
});
bool res2 = runOverflow<int32_t>(
ii, jj, [branchType, rc](MacroAssembler* masm, int32_t valLeft,
int32_t valRight) {
Label overflow, end;
__ li(rc.left, valLeft);
switch (branchType) {
case kAddBranchOverflow:
__ AddBranchOvf(rc.dst, rc.left, Operand(valRight),
&overflow, nullptr, rc.scratch);
break;
case kSubBranchOverflow:
__ SubBranchOvf(rc.dst, rc.left, Operand(valRight),
&overflow, nullptr, rc.scratch);
break;
}
__ li(v0, 0);
__ Branch(&end);
__ bind(&overflow);
__ li(v0, 1);
__ bind(&end);
});
switch (branchType) {
case kAddBranchOverflow:
CHECK_EQ(IsAddOverflow<int32_t>(ii, jj), res1);
CHECK_EQ(IsAddOverflow<int32_t>(ii, jj), res2);
break;
case kSubBranchOverflow:
CHECK_EQ(IsSubOverflow<int32_t>(ii, jj), res1);
CHECK_EQ(IsSubOverflow<int32_t>(ii, jj), res2);
break;
default:
UNREACHABLE();
}
}
int32_t ii = *i;
int32_t jj = *j;
int32_t expected_add, expected_sub, expected_mul;
bool expected_add_ovf, expected_sub_ovf, expected_mul_ovf;
MacroAssembler assembler(isolate, nullptr, 0,
v8::internal::CodeObjectRequired::kYes);
MacroAssembler* masm = &assembler;
__ lw(t0, MemOperand(a0, offsetof(T, lhs)));
__ lw(t1, MemOperand(a0, offsetof(T, rhs)));
__ AddOverflow(t2, t0, Operand(t1), t3);
__ sw(t2, MemOperand(a0, offsetof(T, output_add)));
__ sw(t3, MemOperand(a0, offsetof(T, overflow_add)));
__ mov(t3, zero_reg);
__ AddOverflow(t0, t0, Operand(t1), t3);
__ sw(t0, MemOperand(a0, offsetof(T, output_add2)));
__ sw(t3, MemOperand(a0, offsetof(T, overflow_add2)));
__ lw(t0, MemOperand(a0, offsetof(T, lhs)));
__ lw(t1, MemOperand(a0, offsetof(T, rhs)));
__ SubOverflow(t2, t0, Operand(t1), t3);
__ sw(t2, MemOperand(a0, offsetof(T, output_sub)));
__ sw(t3, MemOperand(a0, offsetof(T, overflow_sub)));
__ mov(t3, zero_reg);
__ SubOverflow(t0, t0, Operand(t1), t3);
__ sw(t0, MemOperand(a0, offsetof(T, output_sub2)));
__ sw(t3, MemOperand(a0, offsetof(T, overflow_sub2)));
__ lw(t0, MemOperand(a0, offsetof(T, lhs)));
__ lw(t1, MemOperand(a0, offsetof(T, rhs)));
__ MulOverflow(t2, t0, Operand(t1), t3);
__ sw(t2, MemOperand(a0, offsetof(T, output_mul)));
__ sw(t3, MemOperand(a0, offsetof(T, overflow_mul)));
__ mov(t3, zero_reg);
__ MulOverflow(t0, t0, Operand(t1), t3);
__ sw(t0, MemOperand(a0, offsetof(T, output_mul2)));
__ sw(t3, MemOperand(a0, offsetof(T, overflow_mul2)));
__ jr(ra);
__ nop();
CodeDesc desc;
masm->GetCode(isolate, &desc);
Handle<Code> code =
isolate->factory()->NewCode(desc, Code::STUB, Handle<Code>());
auto f = GeneratedCode<F3>::FromCode(*code);
t.lhs = ii;
t.rhs = jj;
f.Call(&t, 0, 0, 0, 0);
expected_add_ovf = base::bits::SignedAddOverflow32(ii, jj, &expected_add);
expected_sub_ovf = base::bits::SignedSubOverflow32(ii, jj, &expected_sub);
expected_mul_ovf = base::bits::SignedMulOverflow32(ii, jj, &expected_mul);
CHECK_EQ(expected_add_ovf, t.overflow_add < 0);
CHECK_EQ(expected_sub_ovf, t.overflow_sub < 0);
CHECK_EQ(expected_mul_ovf, t.overflow_mul != 0);
CHECK_EQ(t.overflow_add, t.overflow_add2);
CHECK_EQ(t.overflow_sub, t.overflow_sub2);
CHECK_EQ(t.overflow_mul, t.overflow_mul2);
CHECK_EQ(expected_add, t.output_add);
CHECK_EQ(expected_add, t.output_add2);
CHECK_EQ(expected_sub, t.output_sub);
CHECK_EQ(expected_sub, t.output_sub2);
if (!expected_mul_ovf) {
CHECK_EQ(expected_mul, t.output_mul);
CHECK_EQ(expected_mul, t.output_mul2);
}
}
}
}
TEST(BranchOverflowInt32RightLabel) {
FOR_INT32_INPUTS(i, overflow_int32_test_values) {
FOR_INT32_INPUTS(j, overflow_int32_test_values) {
FOR_ENUM_INPUTS(br, OverflowBranchType, overflow_branch_type) {
FOR_STRUCT_INPUTS(regComb, OverflowRegisterCombination,
overflow_register_combination) {
int32_t ii = *i;
int32_t jj = *j;
enum OverflowBranchType branchType = *br;
struct OverflowRegisterCombination rc = *regComb;
// If left and right register are same then left and right
// test values must also be same, otherwise we skip the test
if (rc.left.code() == rc.right.code()) {
if (ii != jj) {
continue;
}
}
bool res1 = runOverflow<int32_t>(
ii, jj, [branchType, rc](MacroAssembler* masm, int32_t valLeft,
int32_t valRight) {
Label no_overflow, end;
__ li(rc.left, valLeft);
__ li(rc.right, valRight);
switch (branchType) {
case kAddBranchOverflow:
__ AddBranchOvf(rc.dst, rc.left, rc.right, nullptr,
&no_overflow, rc.scratch);
break;
case kSubBranchOverflow:
__ SubBranchOvf(rc.dst, rc.left, rc.right, nullptr,
&no_overflow, rc.scratch);
break;
}
__ li(v0, 1);
__ Branch(&end);
__ bind(&no_overflow);
__ li(v0, 0);
__ bind(&end);
});
bool res2 = runOverflow<int32_t>(
ii, jj, [branchType, rc](MacroAssembler* masm, int32_t valLeft,
int32_t valRight) {
Label no_overflow, end;
__ li(rc.left, valLeft);
switch (branchType) {
case kAddBranchOverflow:
__ AddBranchOvf(rc.dst, rc.left, Operand(valRight), nullptr,
&no_overflow, rc.scratch);
break;
case kSubBranchOverflow:
__ SubBranchOvf(rc.dst, rc.left, Operand(valRight), nullptr,
&no_overflow, rc.scratch);
break;
}
__ li(v0, 1);
__ Branch(&end);
__ bind(&no_overflow);
__ li(v0, 0);
__ bind(&end);
});
switch (branchType) {
case kAddBranchOverflow:
CHECK_EQ(IsAddOverflow<int32_t>(ii, jj), res1);
CHECK_EQ(IsAddOverflow<int32_t>(ii, jj), res2);
break;
case kSubBranchOverflow:
CHECK_EQ(IsSubOverflow<int32_t>(ii, jj), res1);
CHECK_EQ(IsSubOverflow<int32_t>(ii, jj), res2);
break;
default:
UNREACHABLE();
}
}
}
}
}
}
TEST(min_max_nan) {
CcTest::InitializeVM();
......
......@@ -785,326 +785,109 @@ static const std::vector<int64_t> overflow_int64_test_values() {
return std::vector<int64_t>(&kValues[0], &kValues[arraysize(kValues)]);
}
enum OverflowBranchType {
kAddBranchOverflow,
kSubBranchOverflow,
};
struct OverflowRegisterCombination {
Register dst;
Register left;
Register right;
Register scratch;
};
static const std::vector<enum OverflowBranchType> overflow_branch_type() {
static const enum OverflowBranchType kValues[] = {kAddBranchOverflow,
kSubBranchOverflow};
return std::vector<enum OverflowBranchType>(&kValues[0],
&kValues[arraysize(kValues)]);
}
static const std::vector<struct OverflowRegisterCombination>
overflow_register_combination() {
static const struct OverflowRegisterCombination kValues[] = {
{t0, t1, t2, t3}, {t0, t0, t2, t3}, {t0, t1, t0, t3}, {t0, t1, t1, t3}};
return std::vector<struct OverflowRegisterCombination>(
&kValues[0], &kValues[arraysize(kValues)]);
}
template <typename T>
static bool IsAddOverflow(T x, T y) {
DCHECK(std::numeric_limits<T>::is_integer);
T max = std::numeric_limits<T>::max();
T min = std::numeric_limits<T>::min();
return (x > 0 && y > (max - x)) || (x < 0 && y < (min - x));
}
template <typename T>
static bool IsSubOverflow(T x, T y) {
DCHECK(std::numeric_limits<T>::is_integer);
T max = std::numeric_limits<T>::max();
T min = std::numeric_limits<T>::min();
return (y > 0 && x < (min + y)) || (y < 0 && x > (max + y));
}
template <typename IN_TYPE, typename Func>
static bool runOverflow(IN_TYPE valLeft, IN_TYPE valRight,
Func GenerateOverflowInstructions) {
typedef int64_t(F_CVT)(char* x0, int x1, int x2, int x3, int x4);
TEST(OverflowInstructions) {
CcTest::InitializeVM();
Isolate* isolate = CcTest::i_isolate();
HandleScope scope(isolate);
MacroAssembler assm(isolate, nullptr, 0,
v8::internal::CodeObjectRequired::kYes);
MacroAssembler* masm = &assm;
GenerateOverflowInstructions(masm, valLeft, valRight);
__ jr(ra);
__ nop();
CodeDesc desc;
assm.GetCode(isolate, &desc);
Handle<Code> code =
isolate->factory()->NewCode(desc, Code::STUB, Handle<Code>());
auto f = GeneratedCode<F_CVT>::FromCode(*code);
int64_t r = reinterpret_cast<int64_t>(f.Call(0, 0, 0, 0, 0));
DCHECK(r == 0 || r == 1);
return r;
}
TEST(BranchOverflowInt64BothLabels) {
FOR_INT64_INPUTS(i, overflow_int64_test_values) {
FOR_INT64_INPUTS(j, overflow_int64_test_values) {
FOR_ENUM_INPUTS(br, OverflowBranchType, overflow_branch_type) {
FOR_STRUCT_INPUTS(regComb, OverflowRegisterCombination,
overflow_register_combination) {
int64_t ii = *i;
int64_t jj = *j;
enum OverflowBranchType branchType = *br;
struct OverflowRegisterCombination rc = *regComb;
// If left and right register are same then left and right
// test values must also be same, otherwise we skip the test
if (rc.left.code() == rc.right.code()) {
if (ii != jj) {
continue;
}
}
bool res1 = runOverflow<int64_t>(
ii, jj, [branchType, rc](MacroAssembler* masm, int64_t valLeft,
int64_t valRight) {
Label overflow, no_overflow, end;
__ li(rc.left, valLeft);
__ li(rc.right, valRight);
switch (branchType) {
case kAddBranchOverflow:
__ DaddBranchOvf(rc.dst, rc.left, rc.right, &overflow,
&no_overflow, rc.scratch);
break;
case kSubBranchOverflow:
__ DsubBranchOvf(rc.dst, rc.left, rc.right, &overflow,
&no_overflow, rc.scratch);
break;
}
__ li(v0, 2);
__ Branch(&end);
__ bind(&overflow);
__ li(v0, 1);
__ Branch(&end);
__ bind(&no_overflow);
__ li(v0, 0);
__ bind(&end);
});
bool res2 = runOverflow<int64_t>(
ii, jj, [branchType, rc](MacroAssembler* masm, int64_t valLeft,
int64_t valRight) {
Label overflow, no_overflow, end;
__ li(rc.left, valLeft);
switch (branchType) {
case kAddBranchOverflow:
__ DaddBranchOvf(rc.dst, rc.left, Operand(valRight),
&overflow, &no_overflow, rc.scratch);
break;
case kSubBranchOverflow:
__ DsubBranchOvf(rc.dst, rc.left, Operand(valRight),
&overflow, &no_overflow, rc.scratch);
break;
}
__ li(v0, 2);
__ Branch(&end);
__ bind(&overflow);
__ li(v0, 1);
__ Branch(&end);
__ bind(&no_overflow);
__ li(v0, 0);
__ bind(&end);
});
switch (branchType) {
case kAddBranchOverflow:
CHECK_EQ(IsAddOverflow<int64_t>(ii, jj), res1);
CHECK_EQ(IsAddOverflow<int64_t>(ii, jj), res2);
break;
case kSubBranchOverflow:
CHECK_EQ(IsSubOverflow<int64_t>(ii, jj), res1);
CHECK_EQ(IsSubOverflow<int64_t>(ii, jj), res2);
break;
default:
UNREACHABLE();
}
}
}
}
}
}
HandleScope handles(isolate);
TEST(BranchOverflowInt64LeftLabel) {
FOR_INT64_INPUTS(i, overflow_int64_test_values) {
FOR_INT64_INPUTS(j, overflow_int64_test_values) {
FOR_ENUM_INPUTS(br, OverflowBranchType, overflow_branch_type) {
FOR_STRUCT_INPUTS(regComb, OverflowRegisterCombination,
overflow_register_combination) {
int64_t ii = *i;
int64_t jj = *j;
enum OverflowBranchType branchType = *br;
struct OverflowRegisterCombination rc = *regComb;
// If left and right register are same then left and right
// test values must also be same, otherwise we skip the test
if (rc.left.code() == rc.right.code()) {
if (ii != jj) {
continue;
}
}
bool res1 = runOverflow<int64_t>(
ii, jj, [branchType, rc](MacroAssembler* masm, int64_t valLeft,
int64_t valRight) {
Label overflow, end;
__ li(rc.left, valLeft);
__ li(rc.right, valRight);
switch (branchType) {
case kAddBranchOverflow:
__ DaddBranchOvf(rc.dst, rc.left, rc.right, &overflow,
nullptr, rc.scratch);
break;
case kSubBranchOverflow:
__ DsubBranchOvf(rc.dst, rc.left, rc.right, &overflow,
nullptr, rc.scratch);
break;
}
__ li(v0, 0);
__ Branch(&end);
__ bind(&overflow);
__ li(v0, 1);
__ bind(&end);
});
bool res2 = runOverflow<int64_t>(
ii, jj, [branchType, rc](MacroAssembler* masm, int64_t valLeft,
int64_t valRight) {
Label overflow, end;
__ li(rc.left, valLeft);
switch (branchType) {
case kAddBranchOverflow:
__ DaddBranchOvf(rc.dst, rc.left, Operand(valRight),
&overflow, nullptr, rc.scratch);
break;
case kSubBranchOverflow:
__ DsubBranchOvf(rc.dst, rc.left, Operand(valRight),
&overflow, nullptr, rc.scratch);
break;
}
__ li(v0, 0);
__ Branch(&end);
__ bind(&overflow);
__ li(v0, 1);
__ bind(&end);
});
switch (branchType) {
case kAddBranchOverflow:
CHECK_EQ(IsAddOverflow<int64_t>(ii, jj), res1);
CHECK_EQ(IsAddOverflow<int64_t>(ii, jj), res2);
break;
case kSubBranchOverflow:
CHECK_EQ(IsSubOverflow<int64_t>(ii, jj), res1);
CHECK_EQ(IsSubOverflow<int64_t>(ii, jj), res2);
break;
default:
UNREACHABLE();
}
}
}
}
}
}
struct T {
int64_t lhs;
int64_t rhs;
int64_t output_add;
int64_t output_add2;
int64_t output_sub;
int64_t output_sub2;
int64_t output_mul;
int64_t output_mul2;
int64_t overflow_add;
int64_t overflow_add2;
int64_t overflow_sub;
int64_t overflow_sub2;
int64_t overflow_mul;
int64_t overflow_mul2;
};
T t;
TEST(BranchOverflowInt64RightLabel) {
FOR_INT64_INPUTS(i, overflow_int64_test_values) {
FOR_INT64_INPUTS(j, overflow_int64_test_values) {
FOR_ENUM_INPUTS(br, OverflowBranchType, overflow_branch_type) {
FOR_STRUCT_INPUTS(regComb, OverflowRegisterCombination,
overflow_register_combination) {
int64_t ii = *i;
int64_t jj = *j;
enum OverflowBranchType branchType = *br;
struct OverflowRegisterCombination rc = *regComb;
// If left and right register are same then left and right
// test values must also be same, otherwise we skip the test
if (rc.left.code() == rc.right.code()) {
if (ii != jj) {
continue;
}
}
bool res1 = runOverflow<int64_t>(
ii, jj, [branchType, rc](MacroAssembler* masm, int64_t valLeft,
int64_t valRight) {
Label no_overflow, end;
__ li(rc.left, valLeft);
__ li(rc.right, valRight);
switch (branchType) {
case kAddBranchOverflow:
__ DaddBranchOvf(rc.dst, rc.left, rc.right, nullptr,
&no_overflow, rc.scratch);
break;
case kSubBranchOverflow:
__ DsubBranchOvf(rc.dst, rc.left, rc.right, nullptr,
&no_overflow, rc.scratch);
break;
}
__ li(v0, 1);
__ Branch(&end);
__ bind(&no_overflow);
__ li(v0, 0);
__ bind(&end);
});
bool res2 = runOverflow<int64_t>(
ii, jj, [branchType, rc](MacroAssembler* masm, int64_t valLeft,
int64_t valRight) {
Label no_overflow, end;
__ li(rc.left, valLeft);
switch (branchType) {
case kAddBranchOverflow:
__ DaddBranchOvf(rc.dst, rc.left, Operand(valRight),
nullptr, &no_overflow, rc.scratch);
break;
case kSubBranchOverflow:
__ DsubBranchOvf(rc.dst, rc.left, Operand(valRight),
nullptr, &no_overflow, rc.scratch);
break;
}
__ li(v0, 1);
__ Branch(&end);
__ bind(&no_overflow);
__ li(v0, 0);
__ bind(&end);
});
switch (branchType) {
case kAddBranchOverflow:
CHECK_EQ(IsAddOverflow<int64_t>(ii, jj), res1);
CHECK_EQ(IsAddOverflow<int64_t>(ii, jj), res2);
break;
case kSubBranchOverflow:
CHECK_EQ(IsSubOverflow<int64_t>(ii, jj), res1);
CHECK_EQ(IsSubOverflow<int64_t>(ii, jj), res2);
break;
default:
UNREACHABLE();
}
}
int64_t ii = *i;
int64_t jj = *j;
int64_t expected_add, expected_sub;
int32_t ii32 = static_cast<int32_t>(ii);
int32_t jj32 = static_cast<int32_t>(jj);
int32_t expected_mul;
int64_t expected_add_ovf, expected_sub_ovf, expected_mul_ovf;
MacroAssembler assembler(isolate, nullptr, 0,
v8::internal::CodeObjectRequired::kYes);
MacroAssembler* masm = &assembler;
__ ld(t0, MemOperand(a0, offsetof(T, lhs)));
__ ld(t1, MemOperand(a0, offsetof(T, rhs)));
__ DaddOverflow(t2, t0, Operand(t1), t3);
__ sd(t2, MemOperand(a0, offsetof(T, output_add)));
__ sd(t3, MemOperand(a0, offsetof(T, overflow_add)));
__ mov(t3, zero_reg);
__ DaddOverflow(t0, t0, Operand(t1), t3);
__ sd(t0, MemOperand(a0, offsetof(T, output_add2)));
__ sd(t3, MemOperand(a0, offsetof(T, overflow_add2)));
__ ld(t0, MemOperand(a0, offsetof(T, lhs)));
__ ld(t1, MemOperand(a0, offsetof(T, rhs)));
__ DsubOverflow(t2, t0, Operand(t1), t3);
__ sd(t2, MemOperand(a0, offsetof(T, output_sub)));
__ sd(t3, MemOperand(a0, offsetof(T, overflow_sub)));
__ mov(t3, zero_reg);
__ DsubOverflow(t0, t0, Operand(t1), t3);
__ sd(t0, MemOperand(a0, offsetof(T, output_sub2)));
__ sd(t3, MemOperand(a0, offsetof(T, overflow_sub2)));
__ ld(t0, MemOperand(a0, offsetof(T, lhs)));
__ ld(t1, MemOperand(a0, offsetof(T, rhs)));
__ sll(t0, t0, 0);
__ sll(t1, t1, 0);
__ MulOverflow(t2, t0, Operand(t1), t3);
__ sd(t2, MemOperand(a0, offsetof(T, output_mul)));
__ sd(t3, MemOperand(a0, offsetof(T, overflow_mul)));
__ mov(t3, zero_reg);
__ MulOverflow(t0, t0, Operand(t1), t3);
__ sd(t0, MemOperand(a0, offsetof(T, output_mul2)));
__ sd(t3, MemOperand(a0, offsetof(T, overflow_mul2)));
__ jr(ra);
__ nop();
CodeDesc desc;
masm->GetCode(isolate, &desc);
Handle<Code> code =
isolate->factory()->NewCode(desc, Code::STUB, Handle<Code>());
auto f = GeneratedCode<F3>::FromCode(*code);
t.lhs = ii;
t.rhs = jj;
f.Call(&t, 0, 0, 0, 0);
expected_add_ovf = base::bits::SignedAddOverflow64(ii, jj, &expected_add);
expected_sub_ovf = base::bits::SignedSubOverflow64(ii, jj, &expected_sub);
expected_mul_ovf =
base::bits::SignedMulOverflow32(ii32, jj32, &expected_mul);
CHECK_EQ(expected_add_ovf, t.overflow_add < 0);
CHECK_EQ(expected_sub_ovf, t.overflow_sub < 0);
CHECK_EQ(expected_mul_ovf, t.overflow_mul != 0);
CHECK_EQ(t.overflow_add, t.overflow_add2);
CHECK_EQ(t.overflow_sub, t.overflow_sub2);
CHECK_EQ(t.overflow_mul, t.overflow_mul2);
CHECK_EQ(expected_add, t.output_add);
CHECK_EQ(expected_add, t.output_add2);
CHECK_EQ(expected_sub, t.output_sub);
CHECK_EQ(expected_sub, t.output_sub2);
if (!expected_mul_ovf) {
CHECK_EQ(expected_mul, t.output_mul);
CHECK_EQ(expected_mul, t.output_mul2);
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment