From f47ccee416b4b388c5438be192a61976ce422db8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bargull?= Date: Tue, 11 Aug 2020 06:41:46 +0000 Subject: [PATCH] Bug 1657820 - Part 2: Optimise Atomics.compareExchange in CacheIR and Warp. r=jandem Uint32 isn't yet supported, because it may return a Double, which is difficult to represent in CacheIR. Bug 1077305 proposes to unconditionally use Double for Uint32 Atomics, which will make this easier to implement in CacheIR. Differential Revision: https://phabricator.services.mozilla.com/D86299 --- js/src/jit/CacheIR.cpp | 117 +++++++++++++++++++++++++++ js/src/jit/CacheIR.h | 1 + js/src/jit/CacheIRCompiler.cpp | 59 ++++++++++++++ js/src/jit/CacheIROps.yaml | 11 +++ js/src/jit/VMFunctions.cpp | 32 ++++++++ js/src/jit/VMFunctions.h | 6 ++ js/src/jit/WarpCacheIRTranspiler.cpp | 25 ++++++ 7 files changed, 251 insertions(+) diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp index 381165da0e76..5f3ab52e25e6 100644 --- a/js/src/jit/CacheIR.cpp +++ b/js/src/jit/CacheIR.cpp @@ -17,6 +17,7 @@ #include "jit/CacheIRSpewer.h" #include "jit/InlinableNatives.h" #include "jit/Ion.h" // IsIonEnabled +#include "jit/JitContext.h" #include "js/friend/WindowProxy.h" // js::IsWindow, js::IsWindowProxy, js::ToWindowIfWindowProxy #include "js/ScalarType.h" // js::Scalar::Type #include "js/Wrapper.h" @@ -6929,6 +6930,118 @@ AttachDecision CallIRGenerator::tryAttachReflectGetPrototypeOf( return AttachDecision::Attach; } +static bool AtomicsMeetsPreconditions(TypedArrayObject* typedArray, + double index) { + switch (typedArray->type()) { + case Scalar::Int8: + case Scalar::Uint8: + case Scalar::Int16: + case Scalar::Uint16: + case Scalar::Int32: + case Scalar::Uint32: + break; + + case Scalar::BigInt64: + case Scalar::BigUint64: + // Bug 1638295: Not yet implemented. + return false; + + case Scalar::Float32: + case Scalar::Float64: + case Scalar::Uint8Clamped: + // Exclude floating types and Uint8Clamped. + return false; + + case Scalar::MaxTypedArrayViewType: + case Scalar::Int64: + case Scalar::Simd128: + MOZ_CRASH("Unsupported TypedArray type"); + } + + // Bounds check the index argument. + int32_t indexInt32; + if (!mozilla::NumberEqualsInt32(index, &indexInt32)) { + return false; + } + if (indexInt32 < 0 || uint32_t(indexInt32) >= typedArray->length()) { + return false; + } + + return true; +} + +AttachDecision CallIRGenerator::tryAttachAtomicsCompareExchange( + HandleFunction callee) { + if (!JitSupportsAtomics()) { + return AttachDecision::NoAction; + } + + // Need four arguments. + if (argc_ != 4) { + return AttachDecision::NoAction; + } + + // Arguments: typedArray, index (number), expected, replacement. + if (!args_[0].isObject() || !args_[0].toObject().is()) { + return AttachDecision::NoAction; + } + if (!args_[1].isNumber()) { + return AttachDecision::NoAction; + } + if (!args_[2].isNumber()) { + return AttachDecision::NoAction; + } + if (!args_[3].isNumber()) { + return AttachDecision::NoAction; + } + + auto* typedArray = &args_[0].toObject().as(); + if (!AtomicsMeetsPreconditions(typedArray, args_[1].toNumber())) { + return AttachDecision::NoAction; + } + + // TODO: Uint32 isn't yet supported (bug 1077305). + if (typedArray->type() == Scalar::Uint32) { + return AttachDecision::NoAction; + } + + // Initialize the input operand. + Int32OperandId argcId(writer.setInputOperandId(0)); + + // Guard callee is the `compareExchange` native function. + emitNativeCalleeGuard(callee); + + ValOperandId arg0Id = writer.loadArgumentFixedSlot(ArgumentKind::Arg0, argc_); + ObjOperandId objId = writer.guardToObject(arg0Id); + writer.guardShapeForClass(objId, typedArray->shape()); + + // Convert index to int32. + ValOperandId indexId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg1, argc_); + Int32OperandId int32IndexId = writer.guardToInt32Index(indexId); + + // Convert expected value to int32. + ValOperandId expectedId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg2, argc_); + Int32OperandId int32ExpectedId = writer.guardToInt32ModUint32(expectedId); + + // Convert replacement value to int32. + ValOperandId replacementId = + writer.loadArgumentFixedSlot(ArgumentKind::Arg3, argc_); + Int32OperandId int32ReplacementId = + writer.guardToInt32ModUint32(replacementId); + + writer.atomicsCompareExchangeResult(objId, int32IndexId, int32ExpectedId, + int32ReplacementId, typedArray->type()); + + // This stub doesn't need to be monitored, because it always returns an int32. + writer.returnFromIC(); + cacheIRStubKind_ = BaselineCacheIRStubKind::Regular; + + trackAttached("AtomicsCompareExchange"); + return AttachDecision::Attach; +} + AttachDecision CallIRGenerator::tryAttachFunCall(HandleFunction callee) { MOZ_ASSERT(callee->isNativeWithoutJitEntry()); if (callee->native() != fun_call) { @@ -8009,6 +8122,10 @@ AttachDecision CallIRGenerator::tryAttachInlinableNative( case InlinableNative::ReflectGetPrototypeOf: return tryAttachReflectGetPrototypeOf(callee); + // Atomics intrinsics: + case InlinableNative::AtomicsCompareExchange: + return tryAttachAtomicsCompareExchange(callee); + default: return AttachDecision::NoAction; } diff --git a/js/src/jit/CacheIR.h b/js/src/jit/CacheIR.h index ddac28b5ae80..fa6605b73f4e 100644 --- a/js/src/jit/CacheIR.h +++ b/js/src/jit/CacheIR.h @@ -1701,6 +1701,7 @@ class MOZ_RAII CallIRGenerator : public IRGenerator { AttachDecision tryAttachArrayConstructor(HandleFunction callee); AttachDecision tryAttachTypedArrayConstructor(HandleFunction callee); AttachDecision tryAttachReflectGetPrototypeOf(HandleFunction callee); + AttachDecision tryAttachAtomicsCompareExchange(HandleFunction callee); AttachDecision tryAttachFunCall(HandleFunction calleeFunc); AttachDecision tryAttachFunApply(HandleFunction calleeFunc); diff --git a/js/src/jit/CacheIRCompiler.cpp b/js/src/jit/CacheIRCompiler.cpp index c8b3192c6a79..8722fcccbbd1 100644 --- a/js/src/jit/CacheIRCompiler.cpp +++ b/js/src/jit/CacheIRCompiler.cpp @@ -7491,6 +7491,65 @@ bool CacheIRCompiler::emitGetFirstDollarIndexResult(StringOperandId strId) { return true; } +bool CacheIRCompiler::emitAtomicsCompareExchangeResult( + ObjOperandId objId, Int32OperandId indexId, Int32OperandId expectedId, + Int32OperandId replacementId, Scalar::Type elementType) { + JitSpew(JitSpew_Codegen, "%s", __FUNCTION__); + + AutoOutputRegister output(*this); +#ifdef JS_CODEGEN_X86 + // Use a scratch register to avoid running out of registers. + Register obj = output.valueReg().typeReg(); + allocator.copyToScratchRegister(masm, objId, obj); +#else + Register obj = allocator.useRegister(masm, objId); +#endif + Register index = allocator.useRegister(masm, indexId); + Register expected = allocator.useRegister(masm, expectedId); + Register replacement = allocator.useRegister(masm, replacementId); + + Register scratch = output.valueReg().scratchReg(); + MOZ_ASSERT(scratch != obj, "scratchReg must not be typeReg"); + + // Not enough registers on X86. + Register spectreTemp = Register::Invalid(); + + FailurePath* failure; + if (!addFailurePath(&failure)) { + return false; + } + + // Bounds check. + LoadTypedThingLength(masm, TypedThingLayout::TypedArray, obj, scratch); + masm.spectreBoundsCheck32(index, scratch, spectreTemp, failure->label()); + + // Atomic operations are highly platform-dependent, for example x86/x64 has + // specific requirements on which registers are used; MIPS needs multiple + // additional temporaries. Therefore we're using an ABI call here instead of + // handling each platform separately. + { + LiveRegisterSet volatileRegs(GeneralRegisterSet::Volatile(), + liveVolatileFloatRegs()); + volatileRegs.takeUnchecked(output.valueReg()); + volatileRegs.takeUnchecked(scratch); + masm.PushRegsInMask(volatileRegs); + + masm.setupUnalignedABICall(scratch); + masm.passABIArg(obj); + masm.passABIArg(index); + masm.passABIArg(expected); + masm.passABIArg(replacement); + masm.callWithABI( + JS_FUNC_TO_DATA_PTR(void*, AtomicsCompareExchange(elementType))); + masm.storeCallInt32Result(scratch); + + masm.PopRegsInMask(volatileRegs); + masm.tagValue(JSVAL_TYPE_INT32, scratch, output.valueReg()); + } + + return true; +} + template void CacheIRCompiler::callVM(MacroAssembler& masm) { VMFunctionId id = VMFunctionToId::id; diff --git a/js/src/jit/CacheIROps.yaml b/js/src/jit/CacheIROps.yaml index 49547f476b30..4f5bd165c37c 100644 --- a/js/src/jit/CacheIROps.yaml +++ b/js/src/jit/CacheIROps.yaml @@ -1292,6 +1292,17 @@ index: Int32Id rhs: RawId +- name: AtomicsCompareExchangeResult + shared: true + transpile: true + cost_estimate: 4 + args: + obj: ObjId + index: Int32Id + expected: Int32Id + replacement: Int32Id + elementType: ScalarTypeImm + - name: CallNativeSetter shared: false transpile: false diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 6c040e4b08d1..369aa53f4080 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -12,6 +12,7 @@ #include "builtin/TypedObject.h" #include "frontend/BytecodeCompiler.h" #include "jit/arm/Simulator-arm.h" +#include "jit/AtomicOperations.h" #include "jit/BaselineIC.h" #include "jit/JitFrames.h" #include "jit/JitRealm.h" @@ -2163,5 +2164,36 @@ template bool StringBigIntCompare(JSContext* cx, template bool StringBigIntCompare( JSContext* cx, HandleString x, HandleBigInt y, bool* res); +template +static int32_t AtomicsCompareExchange(TypedArrayObject* typedArray, + int32_t index, int32_t expected, + int32_t replacement) { + AutoUnsafeCallWithABI unsafe; + + MOZ_ASSERT(!typedArray->hasDetachedBuffer()); + MOZ_ASSERT(index >= 0 && uint32_t(index) < typedArray->length()); + + SharedMem addr = typedArray->dataPointerEither().cast(); + return jit::AtomicOperations::compareExchangeSeqCst(addr + index, T(expected), + T(replacement)); +} + +AtomicsCompareExchangeFn AtomicsCompareExchange(Scalar::Type elementType) { + switch (elementType) { + case Scalar::Int8: + return AtomicsCompareExchange; + case Scalar::Uint8: + return AtomicsCompareExchange; + case Scalar::Int16: + return AtomicsCompareExchange; + case Scalar::Uint16: + return AtomicsCompareExchange; + case Scalar::Int32: + return AtomicsCompareExchange; + default: + MOZ_CRASH("Unexpected TypedArray type"); + } +} + } // namespace jit } // namespace js diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index b80faa189a53..020b9464eb7c 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -15,6 +15,7 @@ #include "jit/CompileInfo.h" #include "jit/IonScript.h" #include "jit/JitFrames.h" +#include "js/ScalarType.h" #include "vm/Interpreter.h" namespace js { @@ -1161,6 +1162,11 @@ template bool StringBigIntCompare(JSContext* cx, HandleString x, HandleBigInt y, bool* res); +using AtomicsCompareExchangeFn = int32_t (*)(TypedArrayObject*, int32_t, + int32_t, int32_t); + +AtomicsCompareExchangeFn AtomicsCompareExchange(Scalar::Type elementType); + enum class TailCallVMFunctionId; enum class VMFunctionId; diff --git a/js/src/jit/WarpCacheIRTranspiler.cpp b/js/src/jit/WarpCacheIRTranspiler.cpp index 6b140eb571ff..9afec32ade3b 100644 --- a/js/src/jit/WarpCacheIRTranspiler.cpp +++ b/js/src/jit/WarpCacheIRTranspiler.cpp @@ -2327,6 +2327,31 @@ bool WarpCacheIRTranspiler::emitNewTypedArrayFromArrayResult( return resumeAfter(obj); } +bool WarpCacheIRTranspiler::emitAtomicsCompareExchangeResult( + ObjOperandId objId, Int32OperandId indexId, Int32OperandId expectedId, + Int32OperandId replacementId, Scalar::Type elementType) { + MDefinition* obj = getOperand(objId); + MDefinition* index = getOperand(indexId); + MDefinition* expected = getOperand(expectedId); + MDefinition* replacement = getOperand(replacementId); + + auto* length = MArrayBufferViewLength::New(alloc(), obj); + add(length); + + index = addBoundsCheck(index, length); + + auto* elements = MArrayBufferViewElements::New(alloc(), obj); + add(elements); + + auto* cas = MCompareExchangeTypedArrayElement::New( + alloc(), elements, index, elementType, expected, replacement); + cas->setResultType(MIRType::Int32); + addEffectful(cas); + + pushResult(cas); + return resumeAfter(cas); +} + bool WarpCacheIRTranspiler::emitLoadArgumentSlot(ValOperandId resultId, uint32_t slotIndex) { // Reverse of GetIndexOfArgument specialized to !hasArgumentArray.