Bug 1341265 - Part 11: Optimise Set.prototype.has for objects in CacheIR. r=iain

Inline `Set.prototype.has` in CacheIR when called with objects.

Implementing `MacroAssembler::hashObject()` on 32-bit platforms is difficult,
because it uses 64-bit operations, so we either have to allocate twice as much
registers for `Register64` or alternatively spill the values on the stack. For
now just punt and only support this optimisation on 64-bit platforms.

Differential Revision: https://phabricator.services.mozilla.com/D118977
This commit is contained in:
André Bargull 2021-08-02 16:38:04 +00:00
parent b3cba780a0
commit 58f4af8485
8 changed files with 214 additions and 1 deletions

View File

@ -612,6 +612,15 @@ class OrderedHashTable {
static constexpr size_t offsetOfDataChain() { return offsetof(Data, chain); }
static constexpr size_t sizeofData() { return sizeof(Data); }
static constexpr size_t offsetOfHcsK0() {
return offsetof(OrderedHashTable, hcs) +
mozilla::HashCodeScrambler::offsetOfMK0();
}
static constexpr size_t offsetOfHcsK1() {
return offsetof(OrderedHashTable, hcs) +
mozilla::HashCodeScrambler::offsetOfMK1();
}
private:
/* Logarithm base 2 of the number of buckets in the hash table initially. */
static uint32_t initialBucketsLog2() { return 1; }
@ -922,6 +931,9 @@ class OrderedHashSet {
}
static constexpr size_t sizeofImplData() { return Impl::sizeofData(); }
static constexpr size_t offsetOfImplHcsK0() { return Impl::offsetOfHcsK0(); }
static constexpr size_t offsetOfImplHcsK1() { return Impl::offsetOfHcsK1(); }
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
return impl.sizeOfExcludingThis(mallocSizeOf);
}

View File

@ -0,0 +1,30 @@
// Return a new set, possibly filling some dummy entries to enforce creating
// multiple hash buckets.
function createSet(values, n) {
var xs = [...values];
for (var i = 0; i < n; ++i) {
xs.push({});
}
return new Set(xs);
}
function runTest(fn) {
fn(0);
fn(100);
}
function test(n) {
var xs = [{}, {}];
var ys = [{}, {}];
var zs = [...xs, ...ys];
var set = createSet(xs, n);
var N = 100;
var c = 0;
for (var i = 0; i < N; ++i) {
var z = zs[i & 3];
if (set.has(z)) c++;
}
assertEq(c, N / 2);
}
runTest(test);

View File

@ -7821,9 +7821,16 @@ AttachDecision CallIRGenerator::tryAttachSetHas(HandleFunction callee) {
writer.setHasBigIntResult(objId, bigIntId);
break;
}
case ValueType::Object:
case ValueType::Object: {
// Currently only supported on 64-bit platforms.
# ifdef JS_PUNBOX64
ObjOperandId valId = writer.guardToObject(argId);
writer.setHasObjectResult(objId, valId);
# else
writer.setHasResult(objId, argId);
# endif
break;
}
case ValueType::Magic:
case ValueType::PrivateGCThing:

View File

@ -8236,6 +8236,30 @@ bool CacheIRCompiler::emitSetHasBigIntResult(ObjOperandId setId,
return true;
}
bool CacheIRCompiler::emitSetHasObjectResult(ObjOperandId setId,
ObjOperandId objId) {
JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);
AutoOutputRegister output(*this);
Register set = allocator.useRegister(masm, setId);
Register obj = allocator.useRegister(masm, objId);
AutoScratchRegister scratch1(allocator, masm);
AutoScratchRegister scratch2(allocator, masm);
AutoScratchRegister scratch3(allocator, masm);
AutoScratchRegister scratch4(allocator, masm);
AutoScratchRegister scratch5(allocator, masm);
masm.tagValue(JSVAL_TYPE_OBJECT, obj, output.valueReg());
masm.prepareHashObject(set, output.valueReg(), scratch1, scratch2, scratch3,
scratch4, scratch5);
masm.setObjectHasNonBigInt(set, output.valueReg(), scratch1, scratch2,
scratch3, scratch4);
masm.tagValue(JSVAL_TYPE_BOOLEAN, scratch2, output.valueReg());
return true;
}
bool CacheIRCompiler::emitBailout() {
JitSpew(JitSpew_Codegen, "%s", __FUNCTION__);

View File

@ -2653,6 +2653,14 @@
set: ObjId
bigInt: BigIntId
- name: SetHasObjectResult
shared: true
transpile: false
cost_estimate: 3
args:
set: ObjId
obj: ObjId
- name: CallPrintString
shared: true
transpile: false

View File

@ -4819,6 +4819,127 @@ void MacroAssembler::prepareHashBigInt(Register bigInt, Register result,
scrambleHashCode(result);
}
void MacroAssembler::prepareHashObject(Register setObj, ValueOperand value,
Register result, Register temp1,
Register temp2, Register temp3,
Register temp4) {
#ifdef JS_PUNBOX64
// Inline implementation of |OrderedHashTable::prepareHash()| and
// |HashCodeScrambler::scramble(v.asRawBits())|.
// Load the |ValueSet|.
loadPrivate(Address(setObj, SetObject::getDataSlotOffset()), temp1);
// Load |HashCodeScrambler::mK0| and |HashCodeScrambler::mK0|.
auto k0 = Register64(temp1);
auto k1 = Register64(temp2);
load64(Address(temp1, ValueSet::offsetOfImplHcsK1()), k1);
load64(Address(temp1, ValueSet::offsetOfImplHcsK0()), k0);
// Hash numbers are 32-bit values, so only hash the lower double-word.
static_assert(sizeof(mozilla::HashNumber) == 4);
move64To32(value.toRegister64(), result);
// Inline implementation of |SipHasher::sipHash()|.
auto m = Register64(result);
auto v0 = Register64(temp3);
auto v1 = Register64(temp4);
auto v2 = k0;
auto v3 = k1;
auto sipRound = [&]() {
// mV0 = WrappingAdd(mV0, mV1);
add64(v1, v0);
// mV1 = RotateLeft(mV1, 13);
rotateLeft64(Imm32(13), v1, v1, InvalidReg);
// mV1 ^= mV0;
xor64(v0, v1);
// mV0 = RotateLeft(mV0, 32);
rotateLeft64(Imm32(32), v0, v0, InvalidReg);
// mV2 = WrappingAdd(mV2, mV3);
add64(v3, v2);
// mV3 = RotateLeft(mV3, 16);
rotateLeft64(Imm32(16), v3, v3, InvalidReg);
// mV3 ^= mV2;
xor64(v2, v3);
// mV0 = WrappingAdd(mV0, mV3);
add64(v3, v0);
// mV3 = RotateLeft(mV3, 21);
rotateLeft64(Imm32(21), v3, v3, InvalidReg);
// mV3 ^= mV0;
xor64(v0, v3);
// mV2 = WrappingAdd(mV2, mV1);
add64(v1, v2);
// mV1 = RotateLeft(mV1, 17);
rotateLeft64(Imm32(17), v1, v1, InvalidReg);
// mV1 ^= mV2;
xor64(v2, v1);
// mV2 = RotateLeft(mV2, 32);
rotateLeft64(Imm32(32), v2, v2, InvalidReg);
};
// 1. Initialization.
// mV0 = aK0 ^ UINT64_C(0x736f6d6570736575);
move64(Imm64(0x736f6d6570736575), v0);
xor64(k0, v0);
// mV1 = aK1 ^ UINT64_C(0x646f72616e646f6d);
move64(Imm64(0x646f72616e646f6d), v1);
xor64(k1, v1);
// mV2 = aK0 ^ UINT64_C(0x6c7967656e657261);
MOZ_ASSERT(v2 == k0);
xor64(Imm64(0x6c7967656e657261), v2);
// mV3 = aK1 ^ UINT64_C(0x7465646279746573);
MOZ_ASSERT(v3 == k1);
xor64(Imm64(0x7465646279746573), v3);
// 2. Compression.
// mV3 ^= aM;
xor64(m, v3);
// sipRound();
sipRound();
// mV0 ^= aM;
xor64(m, v0);
// 3. Finalization.
// mV2 ^= 0xff;
xor64(Imm64(0xff), v2);
// for (int i = 0; i < 3; i++) sipRound();
for (int i = 0; i < 3; i++) {
sipRound();
}
// return mV0 ^ mV1 ^ mV2 ^ mV3;
xor64(v1, v0);
xor64(v2, v3);
xor64(v3, v0);
move64To32(v0, result);
scrambleHashCode(result);
#else
MOZ_CRASH("Not implemented");
#endif
}
template <typename OrderedHashTable>
void MacroAssembler::orderedHashTableLookup(Register setOrMapObj,
ValueOperand value, Register hash,

View File

@ -4684,6 +4684,9 @@ class MacroAssembler : public MacroAssemblerSpecific {
void prepareHashSymbol(Register sym, Register result);
void prepareHashBigInt(Register bigInt, Register result, Register temp1,
Register temp2, Register temp3);
void prepareHashObject(Register setObj, ValueOperand value, Register result,
Register temp1, Register temp2, Register temp3,
Register temp4);
private:
enum class IsBigInt { No, Yes, Maybe };

View File

@ -361,6 +361,14 @@ class HashCodeScrambler {
return HashNumber(hasher.sipHash(aHashCode));
}
static constexpr size_t offsetOfMK0() {
return offsetof(HashCodeScrambler, mK0);
}
static constexpr size_t offsetOfMK1() {
return offsetof(HashCodeScrambler, mK1);
}
private:
struct SipHasher {
SipHasher(uint64_t aK0, uint64_t aK1) {