зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1438727: [Part 1] Implement a subset of JSOP_ADD in CacheIR r=jandem
This patch adds both Ion and Baseline support for ADD when the arguments are doubles or int32. This is implmented as a strangler via the SharedIC, this falls back to the shared IC if there's no attachment in CacheIR. This should allow preservation of performance throughout. To provide clobber safety to the float registers, this patch uses fixed temporaries on LBinaryCache. --HG-- extra : rebase_source : 35f32e5d0f99bb93845c41aadaf94693644192bd
This commit is contained in:
Родитель
a7ecb4b922
Коммит
480171c17d
|
@ -2113,6 +2113,7 @@ BaselineCacheIRCompiler::init(CacheKind kind)
|
||||||
case CacheKind::In:
|
case CacheKind::In:
|
||||||
case CacheKind::HasOwn:
|
case CacheKind::HasOwn:
|
||||||
case CacheKind::InstanceOf:
|
case CacheKind::InstanceOf:
|
||||||
|
case CacheKind::BinaryArith:
|
||||||
MOZ_ASSERT(numInputs == 2);
|
MOZ_ASSERT(numInputs == 2);
|
||||||
allocator.initInputLocation(0, R0);
|
allocator.initInputLocation(0, R0);
|
||||||
allocator.initInputLocation(1, R1);
|
allocator.initInputLocation(1, R1);
|
||||||
|
|
|
@ -4933,5 +4933,61 @@ ICUnaryArith_Fallback::Compiler::generateStubCode(MacroAssembler& masm)
|
||||||
return tailCallVM(DoUnaryArithFallbackInfo, masm);
|
return tailCallVM(DoUnaryArithFallbackInfo, masm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// BinaryArith_Fallback
|
||||||
|
//
|
||||||
|
// This currently is fused with the SharedIC DoBinaryArithFallback, however,
|
||||||
|
// as we deprecate the SharedIC, this will be able to eventually take over.
|
||||||
|
//
|
||||||
|
// At that point the stub argument will go away, and instead be generated here.
|
||||||
|
bool
|
||||||
|
DoCacheIRBinaryArithFallback(JSContext* cx, BaselineFrame* frame, ICBinaryArith_Fallback* stub_,
|
||||||
|
HandleValue lhs, HandleValue rhs, MutableHandleValue ret,
|
||||||
|
DebugModeOSRVolatileStub<ICBinaryArith_Fallback*>& stub)
|
||||||
|
{
|
||||||
|
RootedScript script(cx, frame->script());
|
||||||
|
jsbytecode* pc = stub->icEntry()->pc(script);
|
||||||
|
JSOp op = JSOp(*pc);
|
||||||
|
|
||||||
|
// Ensure we're only generating for an enabled opcode.
|
||||||
|
switch(op) {
|
||||||
|
case JSOP_ADD:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false; // Fallback to shared IC.
|
||||||
|
}
|
||||||
|
|
||||||
|
FallbackICSpew(cx, stub, "CacheIRBinaryArith(%s,%d,%d)", CodeName[op],
|
||||||
|
int(lhs.isDouble() ? JSVAL_TYPE_DOUBLE : lhs.extractNonDoubleType()),
|
||||||
|
int(rhs.isDouble() ? JSVAL_TYPE_DOUBLE : rhs.extractNonDoubleType()));
|
||||||
|
|
||||||
|
// Check if debug mode toggling made the stub invalid.
|
||||||
|
if (stub.invalid())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (ret.isDouble())
|
||||||
|
stub->setSawDoubleResult();
|
||||||
|
|
||||||
|
if (stub->state().maybeTransition())
|
||||||
|
stub->discardStubs(cx);
|
||||||
|
|
||||||
|
if (stub->state().canAttachStub()) {
|
||||||
|
BinaryArithIRGenerator gen(cx, script, pc, stub->state().mode(),
|
||||||
|
op, lhs, rhs, ret);
|
||||||
|
if (gen.tryAttachStub()) {
|
||||||
|
bool attached = false;
|
||||||
|
ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
|
||||||
|
BaselineCacheIRStubKind::Regular,
|
||||||
|
ICStubEngine::Baseline, script, stub, &attached);
|
||||||
|
if (newStub)
|
||||||
|
JitSpew(JitSpew_BaselineIC, " Attached BinaryArith CacheIR stub for %s", CodeName[op]);
|
||||||
|
} else {
|
||||||
|
return false; // Fallback to shared IC.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace jit
|
} // namespace jit
|
||||||
} // namespace js
|
} // namespace js
|
||||||
|
|
|
@ -5078,3 +5078,95 @@ UnaryArithIRGenerator::tryAttachNumber()
|
||||||
writer.returnFromIC();
|
writer.returnFromIC();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BinaryArithIRGenerator::BinaryArithIRGenerator(JSContext* cx, HandleScript script, jsbytecode* pc, ICState::Mode mode,
|
||||||
|
JSOp op, HandleValue lhs, HandleValue rhs, HandleValue res)
|
||||||
|
: IRGenerator(cx, script, pc, CacheKind::BinaryArith, mode),
|
||||||
|
op_(op),
|
||||||
|
lhs_(lhs),
|
||||||
|
rhs_(rhs),
|
||||||
|
res_(res)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
void
|
||||||
|
BinaryArithIRGenerator::trackAttached(const char* name)
|
||||||
|
{
|
||||||
|
#ifdef JS_CACHEIR_SPEW
|
||||||
|
if (const CacheIRSpewer::Guard& sp = CacheIRSpewer::Guard(*this, name)) {
|
||||||
|
sp.opcodeProperty("op", op_);
|
||||||
|
sp.valueProperty("rhs", rhs_);
|
||||||
|
sp.valueProperty("lhs", lhs_);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
BinaryArithIRGenerator::tryAttachStub()
|
||||||
|
{
|
||||||
|
|
||||||
|
if (tryAttachInt32())
|
||||||
|
return true;
|
||||||
|
if (tryAttachDouble())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
trackAttached(IRGenerator::NotAttached);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
BinaryArithIRGenerator::tryAttachDouble()
|
||||||
|
{
|
||||||
|
if (op_ != JSOP_ADD)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!lhs_.isDouble() || !rhs_.isDouble() || !res_.isDouble())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!cx_->runtime()->jitSupportsFloatingPoint)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ValOperandId lhsId(writer.setInputOperandId(0));
|
||||||
|
ValOperandId rhsId(writer.setInputOperandId(1));
|
||||||
|
|
||||||
|
writer.guardIsNumber(lhsId);
|
||||||
|
writer.guardIsNumber(rhsId);
|
||||||
|
|
||||||
|
switch (op_) {
|
||||||
|
case JSOP_ADD:
|
||||||
|
writer.doubleAddResult(lhsId, rhsId);
|
||||||
|
trackAttached("BinaryArith.Double.Add");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
MOZ_CRASH("Unhandled Op");
|
||||||
|
}
|
||||||
|
writer.returnFromIC();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
BinaryArithIRGenerator::tryAttachInt32()
|
||||||
|
{
|
||||||
|
if (op_ != JSOP_ADD)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!lhs_.isInt32() || !rhs_.isInt32())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ValOperandId lhsId(writer.setInputOperandId(0));
|
||||||
|
ValOperandId rhsId(writer.setInputOperandId(1));
|
||||||
|
|
||||||
|
Int32OperandId lhsIntId = writer.guardIsInt32(lhsId);
|
||||||
|
Int32OperandId rhsIntId = writer.guardIsInt32(rhsId);
|
||||||
|
|
||||||
|
switch (op_) {
|
||||||
|
case JSOP_ADD:
|
||||||
|
writer.int32AddResult(lhsIntId, rhsIntId);
|
||||||
|
trackAttached("BinaryArith.Int32.Add");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
MOZ_CRASH("Unhandled op in tryAttachInt32");
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.returnFromIC();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
|
@ -166,7 +166,8 @@ class TypedOperandId : public OperandId
|
||||||
_(Compare) \
|
_(Compare) \
|
||||||
_(ToBool) \
|
_(ToBool) \
|
||||||
_(Call) \
|
_(Call) \
|
||||||
_(UnaryArith)
|
_(UnaryArith) \
|
||||||
|
_(BinaryArith)
|
||||||
|
|
||||||
enum class CacheKind : uint8_t
|
enum class CacheKind : uint8_t
|
||||||
{
|
{
|
||||||
|
@ -290,6 +291,8 @@ extern const char* CacheKindNames[];
|
||||||
_(LoadStringResult) \
|
_(LoadStringResult) \
|
||||||
_(LoadInstanceOfObjectResult) \
|
_(LoadInstanceOfObjectResult) \
|
||||||
_(LoadTypeOfObjectResult) \
|
_(LoadTypeOfObjectResult) \
|
||||||
|
_(DoubleAddResult) \
|
||||||
|
_(Int32AddResult) \
|
||||||
_(Int32NotResult) \
|
_(Int32NotResult) \
|
||||||
_(Int32NegationResult) \
|
_(Int32NegationResult) \
|
||||||
_(DoubleNegationResult) \
|
_(DoubleNegationResult) \
|
||||||
|
@ -989,6 +992,14 @@ class MOZ_RAII CacheIRWriter : public JS::CustomAutoRooter
|
||||||
buffer_.writeByte(uint32_t(hasOwn));
|
buffer_.writeByte(uint32_t(hasOwn));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void doubleAddResult(ValOperandId lhsId, ValOperandId rhsId) {
|
||||||
|
writeOpWithOperandId(CacheOp::DoubleAddResult, lhsId);
|
||||||
|
writeOperandId(rhsId);
|
||||||
|
}
|
||||||
|
void int32AddResult(Int32OperandId lhs, Int32OperandId rhs) {
|
||||||
|
writeOpWithOperandId(CacheOp::Int32AddResult, lhs);
|
||||||
|
writeOperandId(rhs);
|
||||||
|
}
|
||||||
void int32NotResult(Int32OperandId id) {
|
void int32NotResult(Int32OperandId id) {
|
||||||
writeOpWithOperandId(CacheOp::Int32NotResult, id);
|
writeOpWithOperandId(CacheOp::Int32NotResult, id);
|
||||||
}
|
}
|
||||||
|
@ -1777,6 +1788,26 @@ class MOZ_RAII UnaryArithIRGenerator : public IRGenerator
|
||||||
bool tryAttachStub();
|
bool tryAttachStub();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MOZ_RAII BinaryArithIRGenerator : public IRGenerator
|
||||||
|
{
|
||||||
|
JSOp op_;
|
||||||
|
HandleValue lhs_;
|
||||||
|
HandleValue rhs_;
|
||||||
|
HandleValue res_;
|
||||||
|
|
||||||
|
void trackAttached(const char* name);
|
||||||
|
|
||||||
|
bool tryAttachInt32();
|
||||||
|
bool tryAttachDouble();
|
||||||
|
|
||||||
|
public:
|
||||||
|
BinaryArithIRGenerator(JSContext* cx, HandleScript, jsbytecode* pc, ICState::Mode,
|
||||||
|
JSOp op, HandleValue lhs, HandleValue rhs, HandleValue res);
|
||||||
|
|
||||||
|
bool tryAttachStub();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace jit
|
} // namespace jit
|
||||||
} // namespace js
|
} // namespace js
|
||||||
|
|
||||||
|
|
|
@ -88,6 +88,51 @@ CacheRegisterAllocator::useValueRegister(MacroAssembler& masm, ValOperandId op)
|
||||||
MOZ_CRASH();
|
MOZ_CRASH();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load a value operand directly into a float register. Caller must have
|
||||||
|
// guarded isNumber on the provided val.
|
||||||
|
void
|
||||||
|
CacheRegisterAllocator::loadDouble(MacroAssembler& masm, ValOperandId op, FloatRegister dest)
|
||||||
|
{
|
||||||
|
OperandLocation& loc = operandLocations_[op.id()];
|
||||||
|
|
||||||
|
Label failure, done;
|
||||||
|
switch (loc.kind()) {
|
||||||
|
case OperandLocation::ValueReg: {
|
||||||
|
masm.ensureDouble(loc.valueReg(), dest, &failure);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OperandLocation::ValueStack: {
|
||||||
|
masm.ensureDouble(valueAddress(masm, &loc), dest, &failure);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OperandLocation::BaselineFrame: {
|
||||||
|
Address addr = addressOf(masm, loc.baselineFrameSlot());
|
||||||
|
masm.ensureDouble(addr, dest, &failure);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OperandLocation::DoubleReg: {
|
||||||
|
masm.moveDouble(loc.doubleReg(), dest);
|
||||||
|
loc.setDoubleReg(dest);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OperandLocation::Constant:
|
||||||
|
case OperandLocation::PayloadStack:
|
||||||
|
case OperandLocation::PayloadReg:
|
||||||
|
case OperandLocation::Uninitialized:
|
||||||
|
MOZ_CRASH("Unhandled operand type in loadDouble");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
masm.jump(&done);
|
||||||
|
masm.bind(&failure);
|
||||||
|
masm.assumeUnreachable("Missing guard allowed non-number to hit loadDouble");
|
||||||
|
masm.bind(&done);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ValueOperand
|
ValueOperand
|
||||||
CacheRegisterAllocator::useFixedValueRegister(MacroAssembler& masm, ValOperandId valId,
|
CacheRegisterAllocator::useFixedValueRegister(MacroAssembler& masm, ValOperandId valId,
|
||||||
ValueOperand reg)
|
ValueOperand reg)
|
||||||
|
@ -668,6 +713,13 @@ CacheRegisterAllocator::popPayload(MacroAssembler& masm, OperandLocation* loc, R
|
||||||
loc->setPayloadReg(dest, loc->payloadType());
|
loc->setPayloadReg(dest, loc->payloadType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Address
|
||||||
|
CacheRegisterAllocator::valueAddress(MacroAssembler& masm, OperandLocation* loc)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(loc >= operandLocations_.begin() && loc < operandLocations_.end());
|
||||||
|
return Address(masm.getStackPointer(), stackPushed_ - loc->valueStack());
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
CacheRegisterAllocator::popValue(MacroAssembler& masm, OperandLocation* loc, ValueOperand dest)
|
CacheRegisterAllocator::popValue(MacroAssembler& masm, OperandLocation* loc, ValueOperand dest)
|
||||||
{
|
{
|
||||||
|
@ -777,9 +829,11 @@ CacheRegisterAllocator::restoreInputState(MacroAssembler& masm, bool shouldDisca
|
||||||
case OperandLocation::ValueStack:
|
case OperandLocation::ValueStack:
|
||||||
popValue(masm, &cur, dest.valueReg());
|
popValue(masm, &cur, dest.valueReg());
|
||||||
continue;
|
continue;
|
||||||
|
case OperandLocation::DoubleReg:
|
||||||
|
masm.boxDouble(cur.doubleReg(), dest.valueReg(), cur.doubleReg());
|
||||||
|
continue;
|
||||||
case OperandLocation::Constant:
|
case OperandLocation::Constant:
|
||||||
case OperandLocation::BaselineFrame:
|
case OperandLocation::BaselineFrame:
|
||||||
case OperandLocation::DoubleReg:
|
|
||||||
case OperandLocation::Uninitialized:
|
case OperandLocation::Uninitialized:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1867,6 +1921,39 @@ CacheIRCompiler::emitLoadInt32ArrayLengthResult()
|
||||||
EmitStoreResult(masm, scratch, JSVAL_TYPE_INT32, output);
|
EmitStoreResult(masm, scratch, JSVAL_TYPE_INT32, output);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
bool
|
||||||
|
CacheIRCompiler::emitDoubleAddResult()
|
||||||
|
{
|
||||||
|
AutoOutputRegister output(*this);
|
||||||
|
|
||||||
|
// Float register must be preserved. The BinaryArith ICs use
|
||||||
|
// the fact that baseline has them available, as well as fixed temps on
|
||||||
|
// LBinaryCache.
|
||||||
|
allocator.loadDouble(masm, reader.valOperandId(), FloatReg0);
|
||||||
|
allocator.loadDouble(masm, reader.valOperandId(), FloatReg1);
|
||||||
|
|
||||||
|
masm.addDouble(FloatReg1, FloatReg0);
|
||||||
|
masm.boxDouble(FloatReg0, output.valueReg(), FloatReg0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
CacheIRCompiler::emitInt32AddResult()
|
||||||
|
{
|
||||||
|
AutoOutputRegister output(*this);
|
||||||
|
Register lhs = allocator.useRegister(masm, reader.int32OperandId());
|
||||||
|
Register rhs = allocator.useRegister(masm, reader.int32OperandId());
|
||||||
|
|
||||||
|
FailurePath* failure;
|
||||||
|
if (!addFailurePath(&failure))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
masm.branchAdd32(Assembler::Overflow, lhs, rhs, failure->label());
|
||||||
|
EmitStoreResult(masm, rhs, JSVAL_TYPE_INT32, output);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
CacheIRCompiler::emitInt32NegationResult()
|
CacheIRCompiler::emitInt32NegationResult()
|
||||||
|
|
|
@ -51,6 +51,8 @@ namespace jit {
|
||||||
_(LoadUndefinedResult) \
|
_(LoadUndefinedResult) \
|
||||||
_(LoadBooleanResult) \
|
_(LoadBooleanResult) \
|
||||||
_(LoadInt32ArrayLengthResult) \
|
_(LoadInt32ArrayLengthResult) \
|
||||||
|
_(Int32AddResult) \
|
||||||
|
_(DoubleAddResult) \
|
||||||
_(Int32NegationResult) \
|
_(Int32NegationResult) \
|
||||||
_(Int32NotResult) \
|
_(Int32NotResult) \
|
||||||
_(DoubleNegationResult) \
|
_(DoubleNegationResult) \
|
||||||
|
@ -329,6 +331,7 @@ class MOZ_RAII CacheRegisterAllocator
|
||||||
|
|
||||||
void popPayload(MacroAssembler& masm, OperandLocation* loc, Register dest);
|
void popPayload(MacroAssembler& masm, OperandLocation* loc, Register dest);
|
||||||
void popValue(MacroAssembler& masm, OperandLocation* loc, ValueOperand dest);
|
void popValue(MacroAssembler& masm, OperandLocation* loc, ValueOperand dest);
|
||||||
|
Address valueAddress(MacroAssembler& masm, OperandLocation* loc);
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
void assertValidState() const;
|
void assertValidState() const;
|
||||||
|
@ -469,6 +472,9 @@ class MOZ_RAII CacheRegisterAllocator
|
||||||
Register defineRegister(MacroAssembler& masm, TypedOperandId typedId);
|
Register defineRegister(MacroAssembler& masm, TypedOperandId typedId);
|
||||||
ValueOperand defineValueRegister(MacroAssembler& masm, ValOperandId val);
|
ValueOperand defineValueRegister(MacroAssembler& masm, ValOperandId val);
|
||||||
|
|
||||||
|
// Loads (and unboxes) a value into a float register (caller guarded)
|
||||||
|
void loadDouble(MacroAssembler&, ValOperandId, FloatRegister);
|
||||||
|
|
||||||
// Returns |val|'s JSValueType or JSVAL_TYPE_UNKNOWN.
|
// Returns |val|'s JSValueType or JSVAL_TYPE_UNKNOWN.
|
||||||
JSValueType knownType(ValOperandId val) const;
|
JSValueType knownType(ValOperandId val) const;
|
||||||
|
|
||||||
|
|
|
@ -156,6 +156,17 @@ CacheIRSpewer::valueProperty(const char* name, const Value& v)
|
||||||
j.endObject();
|
j.endObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
CacheIRSpewer::opcodeProperty(const char* name, const JSOp op)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(enabled());
|
||||||
|
JSONPrinter& j = json.ref();
|
||||||
|
|
||||||
|
j.beginStringProperty(name);
|
||||||
|
output.put(CodeName[op]);
|
||||||
|
j.endStringProperty();
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
CacheIRSpewer::attached(const char* name)
|
CacheIRSpewer::attached(const char* name)
|
||||||
{
|
{
|
||||||
|
|
|
@ -37,6 +37,7 @@ class CacheIRSpewer
|
||||||
|
|
||||||
void beginCache(const IRGenerator& generator);
|
void beginCache(const IRGenerator& generator);
|
||||||
void valueProperty(const char* name, const Value& v);
|
void valueProperty(const char* name, const Value& v);
|
||||||
|
void opcodeProperty(const char* name, const JSOp op);
|
||||||
void attached(const char* name);
|
void attached(const char* name);
|
||||||
void endCache();
|
void endCache();
|
||||||
|
|
||||||
|
@ -74,6 +75,10 @@ class CacheIRSpewer
|
||||||
sp_.valueProperty(name, v);
|
sp_.valueProperty(name, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void opcodeProperty(const char* name, const JSOp op) const {
|
||||||
|
sp_.opcodeProperty(name, op);
|
||||||
|
}
|
||||||
|
|
||||||
explicit operator bool() const {
|
explicit operator bool() const {
|
||||||
return sp_.enabled();
|
return sp_.enabled();
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,6 +188,11 @@ typedef bool (*IonUnaryArithICFn)(JSContext* cx, HandleScript outerScript, IonUn
|
||||||
static const VMFunction IonUnaryArithICInfo =
|
static const VMFunction IonUnaryArithICInfo =
|
||||||
FunctionInfo<IonUnaryArithICFn>(IonUnaryArithIC::update, "IonUnaryArithIC::update");
|
FunctionInfo<IonUnaryArithICFn>(IonUnaryArithIC::update, "IonUnaryArithIC::update");
|
||||||
|
|
||||||
|
typedef bool (*IonBinaryArithICFn)(JSContext* cx, HandleScript outerScript, IonBinaryArithIC* stub,
|
||||||
|
HandleValue lhs, HandleValue rhs, MutableHandleValue res);
|
||||||
|
static const VMFunction IonBinaryArithICInfo =
|
||||||
|
FunctionInfo<IonBinaryArithICFn>(IonBinaryArithIC::update, "IonBinaryArithIC::update");
|
||||||
|
|
||||||
void
|
void
|
||||||
CodeGenerator::visitOutOfLineICFallback(OutOfLineICFallback* ool)
|
CodeGenerator::visitOutOfLineICFallback(OutOfLineICFallback* ool)
|
||||||
{
|
{
|
||||||
|
@ -380,6 +385,23 @@ CodeGenerator::visitOutOfLineICFallback(OutOfLineICFallback* ool)
|
||||||
masm.jump(ool->rejoin());
|
masm.jump(ool->rejoin());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
case CacheKind::BinaryArith: {
|
||||||
|
IonBinaryArithIC* binaryArithIC = ic->asBinaryArithIC();
|
||||||
|
|
||||||
|
saveLive(lir);
|
||||||
|
|
||||||
|
pushArg(binaryArithIC->rhs());
|
||||||
|
pushArg(binaryArithIC->lhs());
|
||||||
|
icInfo_[cacheInfoIndex].icOffsetForPush = pushArgWithPatch(ImmWord(-1));
|
||||||
|
pushArg(ImmGCPtr(gen->info().script()));
|
||||||
|
callVM(IonBinaryArithICInfo, lir);
|
||||||
|
|
||||||
|
StoreValueTo(binaryArithIC->output()).generate(this);
|
||||||
|
restoreLiveIgnore(lir, StoreValueTo(binaryArithIC->output()).clobbered());
|
||||||
|
|
||||||
|
masm.jump(ool->rejoin());
|
||||||
|
return;
|
||||||
|
}
|
||||||
case CacheKind::Call:
|
case CacheKind::Call:
|
||||||
case CacheKind::Compare:
|
case CacheKind::Compare:
|
||||||
case CacheKind::TypeOf:
|
case CacheKind::TypeOf:
|
||||||
|
@ -2760,6 +2782,18 @@ CodeGenerator::emitSharedStub(ICStub::Kind kind, LInstruction* lir)
|
||||||
markSafepointAt(callOffset, lir);
|
markSafepointAt(callOffset, lir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
CodeGenerator::visitBinaryCache(LBinaryCache* lir)
|
||||||
|
{
|
||||||
|
LiveRegisterSet liveRegs = lir->safepoint()->liveRegs();
|
||||||
|
TypedOrValueRegister lhs = TypedOrValueRegister(ToValue(lir, LBinaryCache::LhsInput));
|
||||||
|
TypedOrValueRegister rhs = TypedOrValueRegister(ToValue(lir, LBinaryCache::RhsInput));
|
||||||
|
ValueOperand output = ToOutValue(lir);
|
||||||
|
|
||||||
|
IonBinaryArithIC ic(liveRegs, lhs, rhs, output);
|
||||||
|
addIC(lir, allocateIC(ic));
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
CodeGenerator::visitBinarySharedStub(LBinarySharedStub* lir)
|
CodeGenerator::visitBinarySharedStub(LBinarySharedStub* lir)
|
||||||
{
|
{
|
||||||
|
|
|
@ -3570,6 +3570,12 @@ IonBuilder::arithTrySharedStub(bool* emitted, JSOp op,
|
||||||
stub = MUnaryCache::New(alloc(), right);
|
stub = MUnaryCache::New(alloc(), right);
|
||||||
break;
|
break;
|
||||||
case JSOP_ADD:
|
case JSOP_ADD:
|
||||||
|
// If not disabled, prefer the cache IR stub.
|
||||||
|
if (!JitOptions.disableCacheIRBinaryArith) {
|
||||||
|
stub = MBinaryCache::New(alloc(), left, right);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
MOZ_FALLTHROUGH;
|
||||||
case JSOP_SUB:
|
case JSOP_SUB:
|
||||||
case JSOP_MUL:
|
case JSOP_MUL:
|
||||||
case JSOP_DIV:
|
case JSOP_DIV:
|
||||||
|
|
|
@ -523,6 +523,20 @@ IonCacheIRCompiler::init()
|
||||||
allocator.initInputLocation(0, ic->input());
|
allocator.initInputLocation(0, ic->input());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case CacheKind::BinaryArith: {
|
||||||
|
IonBinaryArithIC* ic = ic_->asBinaryArithIC();
|
||||||
|
ValueOperand output = ic->output();
|
||||||
|
|
||||||
|
available.add(output);
|
||||||
|
|
||||||
|
liveRegs_.emplace(ic->liveRegs());
|
||||||
|
outputUnchecked_.emplace(TypedOrValueRegister(output));
|
||||||
|
|
||||||
|
MOZ_ASSERT(numInputs == 2);
|
||||||
|
allocator.initInputLocation(0, ic->lhs());
|
||||||
|
allocator.initInputLocation(1, ic->rhs());
|
||||||
|
break;
|
||||||
|
}
|
||||||
case CacheKind::Call:
|
case CacheKind::Call:
|
||||||
case CacheKind::Compare:
|
case CacheKind::Compare:
|
||||||
case CacheKind::TypeOf:
|
case CacheKind::TypeOf:
|
||||||
|
|
|
@ -58,6 +58,8 @@ IonIC::scratchRegisterForEntryJump()
|
||||||
return asInstanceOfIC()->output();
|
return asInstanceOfIC()->output();
|
||||||
case CacheKind::UnaryArith:
|
case CacheKind::UnaryArith:
|
||||||
return asUnaryArithIC()->output().scratchReg();
|
return asUnaryArithIC()->output().scratchReg();
|
||||||
|
case CacheKind::BinaryArith:
|
||||||
|
return asBinaryArithIC()->output().scratchReg();
|
||||||
case CacheKind::Call:
|
case CacheKind::Call:
|
||||||
case CacheKind::Compare:
|
case CacheKind::Compare:
|
||||||
case CacheKind::TypeOf:
|
case CacheKind::TypeOf:
|
||||||
|
@ -548,6 +550,48 @@ IonUnaryArithIC::update(JSContext* cx, HandleScript outerScript, IonUnaryArithIC
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* static */ bool
|
||||||
|
IonBinaryArithIC::update(JSContext* cx, HandleScript outerScript, IonBinaryArithIC* ic,
|
||||||
|
HandleValue lhs, HandleValue rhs, MutableHandleValue ret)
|
||||||
|
{
|
||||||
|
IonScript* ionScript = outerScript->ionScript();
|
||||||
|
RootedScript script(cx, ic->script());
|
||||||
|
jsbytecode* pc = ic->pc();
|
||||||
|
JSOp op = JSOp(*pc);
|
||||||
|
|
||||||
|
// Don't pass lhs/rhs directly, we need the original values when
|
||||||
|
// generating stubs.
|
||||||
|
RootedValue lhsCopy(cx, lhs);
|
||||||
|
RootedValue rhsCopy(cx, rhs);
|
||||||
|
|
||||||
|
// Perform the compare operation.
|
||||||
|
switch(op) {
|
||||||
|
case JSOP_ADD:
|
||||||
|
// Do an add.
|
||||||
|
if (!AddValues(cx, &lhsCopy, &rhsCopy, ret))
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
MOZ_CRASH("Unhandled binary arith op");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ic->state().maybeTransition())
|
||||||
|
ic->discardStubs(cx->zone());
|
||||||
|
|
||||||
|
if (ic->state().canAttachStub()) {
|
||||||
|
bool attached = false;
|
||||||
|
BinaryArithIRGenerator gen(cx, script, pc, ic->state().mode(),
|
||||||
|
op, lhs, rhs, ret);
|
||||||
|
if (gen.tryAttachStub()) {
|
||||||
|
ic->attachCacheIRStub(cx, gen.writerRef(), gen.cacheKind(), ionScript, &attached);
|
||||||
|
|
||||||
|
if (!attached)
|
||||||
|
ic->state().trackNotAttached();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t*
|
uint8_t*
|
||||||
IonICStub::stubDataStart()
|
IonICStub::stubDataStart()
|
||||||
{
|
{
|
||||||
|
|
|
@ -66,6 +66,7 @@ class IonHasOwnIC;
|
||||||
class IonInIC;
|
class IonInIC;
|
||||||
class IonInstanceOfIC;
|
class IonInstanceOfIC;
|
||||||
class IonUnaryArithIC;
|
class IonUnaryArithIC;
|
||||||
|
class IonBinaryArithIC;
|
||||||
|
|
||||||
class IonIC
|
class IonIC
|
||||||
{
|
{
|
||||||
|
@ -177,6 +178,10 @@ class IonIC
|
||||||
MOZ_ASSERT(kind_ == CacheKind::UnaryArith);
|
MOZ_ASSERT(kind_ == CacheKind::UnaryArith);
|
||||||
return (IonUnaryArithIC*)this;
|
return (IonUnaryArithIC*)this;
|
||||||
}
|
}
|
||||||
|
IonBinaryArithIC* asBinaryArithIC() {
|
||||||
|
MOZ_ASSERT(kind_ == CacheKind::BinaryArith);
|
||||||
|
return (IonBinaryArithIC*)this;
|
||||||
|
}
|
||||||
|
|
||||||
void updateBaseAddress(JitCode* code);
|
void updateBaseAddress(JitCode* code);
|
||||||
|
|
||||||
|
@ -504,6 +509,34 @@ class IonUnaryArithIC : public IonIC
|
||||||
HandleValue val, MutableHandleValue res);
|
HandleValue val, MutableHandleValue res);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class IonBinaryArithIC : public IonIC
|
||||||
|
{
|
||||||
|
LiveRegisterSet liveRegs_;
|
||||||
|
|
||||||
|
TypedOrValueRegister lhs_;
|
||||||
|
TypedOrValueRegister rhs_;
|
||||||
|
ValueOperand output_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
IonBinaryArithIC(LiveRegisterSet liveRegs, TypedOrValueRegister lhs, TypedOrValueRegister rhs, ValueOperand output)
|
||||||
|
: IonIC(CacheKind::BinaryArith),
|
||||||
|
liveRegs_(liveRegs),
|
||||||
|
lhs_(lhs),
|
||||||
|
rhs_(rhs),
|
||||||
|
output_(output)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
LiveRegisterSet liveRegs() const { return liveRegs_; }
|
||||||
|
TypedOrValueRegister lhs() const { return lhs_; }
|
||||||
|
TypedOrValueRegister rhs() const { return rhs_; }
|
||||||
|
ValueOperand output() const { return output_; }
|
||||||
|
|
||||||
|
static MOZ_MUST_USE bool update(JSContext* cx, HandleScript outerScript, IonBinaryArithIC* stub,
|
||||||
|
HandleValue lhs, HandleValue rhs, MutableHandleValue res);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
} // namespace jit
|
} // namespace jit
|
||||||
} // namespace js
|
} // namespace js
|
||||||
|
|
||||||
|
|
|
@ -125,6 +125,9 @@ DefaultJitOptions::DefaultJitOptions()
|
||||||
// Toggles whether CacheIR stubs are used.
|
// Toggles whether CacheIR stubs are used.
|
||||||
SET_DEFAULT(disableCacheIR, false);
|
SET_DEFAULT(disableCacheIR, false);
|
||||||
|
|
||||||
|
// Toggles whether CacheIR stubs for binary arith operations are used
|
||||||
|
SET_DEFAULT(disableCacheIRBinaryArith, false);
|
||||||
|
|
||||||
// Toggles whether shared stubs are used in Ionmonkey.
|
// Toggles whether shared stubs are used in Ionmonkey.
|
||||||
SET_DEFAULT(disableSharedStubs, false);
|
SET_DEFAULT(disableSharedStubs, false);
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,7 @@ struct DefaultJitOptions
|
||||||
bool disableRecoverIns;
|
bool disableRecoverIns;
|
||||||
bool disableScalarReplacement;
|
bool disableScalarReplacement;
|
||||||
bool disableCacheIR;
|
bool disableCacheIR;
|
||||||
|
bool disableCacheIRBinaryArith;
|
||||||
bool disableSharedStubs;
|
bool disableSharedStubs;
|
||||||
bool disableSincos;
|
bool disableSincos;
|
||||||
bool disableSink;
|
bool disableSink;
|
||||||
|
|
|
@ -2512,6 +2512,24 @@ LIRGenerator::visitStringReplace(MStringReplace* ins)
|
||||||
assignSafepoint(lir, ins);
|
assignSafepoint(lir, ins);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
LIRGenerator::visitBinaryCache(MBinaryCache* ins)
|
||||||
|
{
|
||||||
|
MDefinition* lhs = ins->getOperand(0);
|
||||||
|
MDefinition* rhs = ins->getOperand(1);
|
||||||
|
|
||||||
|
MOZ_ASSERT(ins->type() == MIRType::Value);
|
||||||
|
MOZ_ASSERT(ins->type() == MIRType::Value);
|
||||||
|
|
||||||
|
LBinaryCache* lir = new(alloc()) LBinaryCache(useBox(lhs),
|
||||||
|
useBox(rhs),
|
||||||
|
tempFixed(FloatReg0),
|
||||||
|
tempFixed(FloatReg1));
|
||||||
|
defineBox(lir, ins);
|
||||||
|
assignSafepoint(lir, ins);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void
|
void
|
||||||
LIRGenerator::visitBinarySharedStub(MBinarySharedStub* ins)
|
LIRGenerator::visitBinarySharedStub(MBinarySharedStub* ins)
|
||||||
{
|
{
|
||||||
|
|
|
@ -8338,6 +8338,22 @@ class MBinarySharedStub
|
||||||
TRIVIAL_NEW_WRAPPERS
|
TRIVIAL_NEW_WRAPPERS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class MBinaryCache
|
||||||
|
: public MBinaryInstruction,
|
||||||
|
public MixPolicy<BoxPolicy<0>, BoxPolicy<1> >::Data
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
explicit MBinaryCache(MDefinition* left, MDefinition* right)
|
||||||
|
: MBinaryInstruction(classOpcode, left, right)
|
||||||
|
{
|
||||||
|
setResultType(MIRType::Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
INSTRUCTION_HEADER(BinaryCache)
|
||||||
|
TRIVIAL_NEW_WRAPPERS
|
||||||
|
};
|
||||||
|
|
||||||
class MUnaryCache
|
class MUnaryCache
|
||||||
: public MUnaryInstruction,
|
: public MUnaryInstruction,
|
||||||
public BoxPolicy<0>::Data
|
public BoxPolicy<0>::Data
|
||||||
|
|
|
@ -673,6 +673,12 @@ SharedStubInfo::outerScript(JSContext* cx)
|
||||||
//
|
//
|
||||||
// BinaryArith_Fallback
|
// BinaryArith_Fallback
|
||||||
//
|
//
|
||||||
|
// This will be phased out in favour of the CacheIR system.
|
||||||
|
|
||||||
|
extern bool
|
||||||
|
DoCacheIRBinaryArithFallback(JSContext* cx, BaselineFrame* frame, ICBinaryArith_Fallback* stub_,
|
||||||
|
HandleValue lhs, HandleValue rhs, MutableHandleValue ret,
|
||||||
|
DebugModeOSRVolatileStub<ICBinaryArith_Fallback*>& stub);
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
DoBinaryArithFallback(JSContext* cx, void* payload, ICBinaryArith_Fallback* stub_,
|
DoBinaryArithFallback(JSContext* cx, void* payload, ICBinaryArith_Fallback* stub_,
|
||||||
|
@ -690,6 +696,7 @@ DoBinaryArithFallback(JSContext* cx, void* payload, ICBinaryArith_Fallback* stub
|
||||||
int(lhs.isDouble() ? JSVAL_TYPE_DOUBLE : lhs.extractNonDoubleType()),
|
int(lhs.isDouble() ? JSVAL_TYPE_DOUBLE : lhs.extractNonDoubleType()),
|
||||||
int(rhs.isDouble() ? JSVAL_TYPE_DOUBLE : rhs.extractNonDoubleType()));
|
int(rhs.isDouble() ? JSVAL_TYPE_DOUBLE : rhs.extractNonDoubleType()));
|
||||||
|
|
||||||
|
|
||||||
// Don't pass lhs/rhs directly, we need the original values when
|
// Don't pass lhs/rhs directly, we need the original values when
|
||||||
// generating stubs.
|
// generating stubs.
|
||||||
RootedValue lhsCopy(cx, lhs);
|
RootedValue lhsCopy(cx, lhs);
|
||||||
|
@ -770,6 +777,13 @@ DoBinaryArithFallback(JSContext* cx, void* payload, ICBinaryArith_Fallback* stub
|
||||||
if (stub.invalid())
|
if (stub.invalid())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
// Try to use a CacheIR stub first. If that succeeds, then we're done. Otherwise, we
|
||||||
|
// need to try to attach a shared stub.
|
||||||
|
if (engine == ICStubCompiler::Engine::Baseline && !JitOptions.disableCacheIRBinaryArith) {
|
||||||
|
if (DoCacheIRBinaryArithFallback(cx, (BaselineFrame*)payload, stub_, lhs, rhs, ret, stub))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (ret.isDouble())
|
if (ret.isDouble())
|
||||||
stub->setSawDoubleResult();
|
stub->setSawDoubleResult();
|
||||||
|
|
||||||
|
|
|
@ -5556,6 +5556,32 @@ class LBinarySharedStub : public LCallInstructionHelper<BOX_PIECES, 2 * BOX_PIEC
|
||||||
static const size_t RhsInput = BOX_PIECES;
|
static const size_t RhsInput = BOX_PIECES;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class LBinaryCache : public LInstructionHelper<BOX_PIECES, 2 * BOX_PIECES, 2>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LIR_HEADER(BinaryCache)
|
||||||
|
|
||||||
|
// Takes two temps: these are intendend to be FloatReg0 and FloatReg1
|
||||||
|
// To allow the actual cache code to safely clobber those values without
|
||||||
|
// save and restore.
|
||||||
|
LBinaryCache(const LBoxAllocation& lhs, const LBoxAllocation& rhs,
|
||||||
|
const LDefinition& temp0, const LDefinition& temp1)
|
||||||
|
: LInstructionHelper(classOpcode)
|
||||||
|
{
|
||||||
|
setBoxOperand(LhsInput, lhs);
|
||||||
|
setBoxOperand(RhsInput, rhs);
|
||||||
|
setTemp(0, temp0);
|
||||||
|
setTemp(1, temp1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MBinaryCache* mir() const {
|
||||||
|
return mir_->toBinaryCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
static const size_t LhsInput = 0;
|
||||||
|
static const size_t RhsInput = BOX_PIECES;
|
||||||
|
};
|
||||||
|
|
||||||
class LUnaryCache : public LInstructionHelper<BOX_PIECES, BOX_PIECES, 0>
|
class LUnaryCache : public LInstructionHelper<BOX_PIECES, BOX_PIECES, 0>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -687,6 +687,14 @@ LIRGeneratorShared::tempFixed(Register reg)
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LDefinition
|
||||||
|
LIRGeneratorShared::tempFixed(FloatRegister reg)
|
||||||
|
{
|
||||||
|
LDefinition t = temp(LDefinition::DOUBLE);
|
||||||
|
t.setOutput(LFloatReg(reg));
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
LDefinition
|
LDefinition
|
||||||
LIRGeneratorShared::tempFloat32()
|
LIRGeneratorShared::tempFloat32()
|
||||||
{
|
{
|
||||||
|
|
|
@ -150,8 +150,11 @@ class LIRGeneratorShared
|
||||||
inline LDefinition tempDouble();
|
inline LDefinition tempDouble();
|
||||||
inline LDefinition tempCopy(MDefinition* input, uint32_t reusedInput);
|
inline LDefinition tempCopy(MDefinition* input, uint32_t reusedInput);
|
||||||
|
|
||||||
// Note that the fixed register has a GENERAL type.
|
// Note that the fixed register has a GENERAL type,
|
||||||
|
// unless the arg is of FloatRegister type
|
||||||
inline LDefinition tempFixed(Register reg);
|
inline LDefinition tempFixed(Register reg);
|
||||||
|
inline LDefinition tempFixed(FloatRegister reg);
|
||||||
|
|
||||||
|
|
||||||
template <size_t Ops, size_t Temps>
|
template <size_t Ops, size_t Temps>
|
||||||
inline void defineFixed(LInstructionHelper<1, Ops, Temps>* lir, MDefinition* mir,
|
inline void defineFixed(LInstructionHelper<1, Ops, Temps>* lir, MDefinition* mir,
|
||||||
|
|
|
@ -8645,6 +8645,8 @@ SetContextOptions(JSContext* cx, const OptionParser& op)
|
||||||
jit::JitOptions.disableCacheIR = false;
|
jit::JitOptions.disableCacheIR = false;
|
||||||
else if (strcmp(str, "off") == 0)
|
else if (strcmp(str, "off") == 0)
|
||||||
jit::JitOptions.disableCacheIR = true;
|
jit::JitOptions.disableCacheIR = true;
|
||||||
|
else if (strcmp(str, "nobinary") == 0)
|
||||||
|
jit::JitOptions.disableCacheIRBinaryArith = true;
|
||||||
else
|
else
|
||||||
return OptionFailure("cache-ir-stubs", str);
|
return OptionFailure("cache-ir-stubs", str);
|
||||||
}
|
}
|
||||||
|
@ -9199,8 +9201,9 @@ main(int argc, char** argv, char** envp)
|
||||||
#endif
|
#endif
|
||||||
|| !op.addStringOption('\0', "spectre-mitigations", "on/off",
|
|| !op.addStringOption('\0', "spectre-mitigations", "on/off",
|
||||||
"Whether Spectre mitigations are enabled (default: off, on to enable)")
|
"Whether Spectre mitigations are enabled (default: off, on to enable)")
|
||||||
|| !op.addStringOption('\0', "cache-ir-stubs", "on/off",
|
|| !op.addStringOption('\0', "cache-ir-stubs", "on/off/nobinary",
|
||||||
"Use CacheIR stubs (default: on, off to disable)")
|
"Use CacheIR stubs (default: on, off to disable, nobinary to"
|
||||||
|
"just disable binary arith)")
|
||||||
|| !op.addStringOption('\0', "ion-shared-stubs", "on/off",
|
|| !op.addStringOption('\0', "ion-shared-stubs", "on/off",
|
||||||
"Use shared stubs (default: on, off to disable)")
|
"Use shared stubs (default: on, off to disable)")
|
||||||
|| !op.addStringOption('\0', "ion-scalar-replacement", "on/off",
|
|| !op.addStringOption('\0', "ion-scalar-replacement", "on/off",
|
||||||
|
|
Загрузка…
Ссылка в новой задаче