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
This commit is contained in:
André Bargull 2020-08-11 06:41:46 +00:00
Родитель e8c9163853
Коммит f47ccee416
7 изменённых файлов: 251 добавлений и 0 удалений

Просмотреть файл

@ -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<TypedArrayObject>()) {
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<TypedArrayObject>();
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;
}

Просмотреть файл

@ -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);

Просмотреть файл

@ -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 <typename Fn, Fn fn>
void CacheIRCompiler::callVM(MacroAssembler& masm) {
VMFunctionId id = VMFunctionToId<Fn, fn>::id;

Просмотреть файл

@ -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

Просмотреть файл

@ -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<ComparisonKind::LessThan>(JSContext* cx,
template bool StringBigIntCompare<ComparisonKind::GreaterThanOrEqual>(
JSContext* cx, HandleString x, HandleBigInt y, bool* res);
template <typename T>
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<T*> addr = typedArray->dataPointerEither().cast<T*>();
return jit::AtomicOperations::compareExchangeSeqCst(addr + index, T(expected),
T(replacement));
}
AtomicsCompareExchangeFn AtomicsCompareExchange(Scalar::Type elementType) {
switch (elementType) {
case Scalar::Int8:
return AtomicsCompareExchange<int8_t>;
case Scalar::Uint8:
return AtomicsCompareExchange<uint8_t>;
case Scalar::Int16:
return AtomicsCompareExchange<int16_t>;
case Scalar::Uint16:
return AtomicsCompareExchange<uint16_t>;
case Scalar::Int32:
return AtomicsCompareExchange<int32_t>;
default:
MOZ_CRASH("Unexpected TypedArray type");
}
}
} // namespace jit
} // namespace js

Просмотреть файл

@ -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 <ComparisonKind Kind>
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;

Просмотреть файл

@ -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.