[vm] Better implementation of hashCode function.

With this cl hashCode function for integers which used to be identity function becomes `uint128_t hash = value * constant; hash ^= hash >> 64; hash ^= hash >> 32; hash &= 0x3fffffff`. Note that the hashCode has to stay the same across platforms(64-bit/32-bit).

This dramatically improves performance of integer HashSet/HashMap lookups when integers differ in higher bits only(see the issue referenced below).

AOT ARM64 benchmarks:
===
WordSolverIdentity -3.630% (-0.9 noise)
BigInt.parse.0064.bits 15.43% (0.9 noise)
BigInt.parse.4096.bits 40.80% (1.6 noise)
BigInt.parse.0256.bits 42.01% (2.3 noise)
BigInt.parse.1024.bits 50.91% (2.6 noise)
IntegerSetLookup.DefaultHashSet 549916% (14727.6 noise)
IntegerSetLookup.DefaultHashSet 597150% (55520.2 noise)
IntegerSetLookup.HashSet 846924% (78126.7 noise)
IntegerSetLookup.HashSet 791864% (107221.1 noise)
===

AOT x64:
===
Havlak -14.25% (-1.7 noise)
DartMicroBench.Int64Div -7.091% (-1.2 noise)
ObjectHash.manual.5 -9.541% (-0.8 noise)
AsyncLiveVars.LiveInt1 4.726% (0.8 noise)
IsolateJson.SendAndExit_Decode1MBx1 9.067% (0.8 noise)
SplayHarderLatency 4.629% (0.9 noise)
TypedDataDuplicate.Float64List.32.loop 35.01% (1.8 noise)
IntegerSetLookup.DefaultHashSet 627996% (124823.6 noise)
IntegerSetLookup.HashSet 1245362% (244705.3 noise)
===

JIT ARM64:
===
IntegerSetLookup.DefaultHashSet_Random 73.80% (1.2 noise)
IntegerSetLookup.DefaultHashSet 344999% (6202.9 noise)
IntegerSetLookup.HashSet 483731% (7845.7 noise)
===

JIT x64:
===
CollectionSieves-Set-removeLoop -6.294% (-0.9 noise)
Utf8Encode.ru.10M 59.11% (0.8 noise)
Utf8Encode.ru.10k 71.62% (0.9 noise)
Utf8Encode.zh.10M 53.93% (0.9 noise)
Utf8Encode.ne.10k 71.34% (0.9 noise)
Utf8Encode.zh.10k 72.52% (0.9 noise)
Utf8Encode.ne.10M 53.17% (0.9 noise)
IntegerSetLookup.HashSet_Random 27.80% (1.1 noise)
String.replaceAll.String.Zero 8.659% (1.2 noise)
IntegerSetLookup.DefaultHashSet_Random 96.20% (1.3 noise)
IntegerSetLookup.HashSet 481037% (18028.8 noise)
IntegerSetLookup.DefaultHashSet 454450% (31501.3 noise)
==

Fixes https://github.com/dart-lang/sdk/issues/48641
TEST=ci

Change-Id: Id982e4aa30cd1d6a63f93c73917a8b921ad464a3
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/258600
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
This commit is contained in:
Alexander Aprelev 2022-10-20 20:31:20 +00:00 committed by Commit Queue
parent 9f32d19a42
commit 8c0df46887
29 changed files with 620 additions and 16 deletions

View file

@ -2,6 +2,8 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "lib/integers.h"
#include "vm/bootstrap_natives.h"
#include <math.h> // NOLINT
@ -76,7 +78,7 @@ DEFINE_NATIVE_ENTRY(Double_hashCode, 0, 1) {
(val <= kMaxInt64RepresentableAsDouble)) {
int64_t ival = static_cast<int64_t>(val);
if (static_cast<double>(ival) == val) {
return Integer::New(ival);
return Integer::New(Multiply64Hash(ival));
}
}

View file

@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "lib/integers.h"
#include "vm/bootstrap_natives.h"
#include "include/dart_api.h"
@ -271,6 +272,37 @@ DEFINE_NATIVE_ENTRY(Smi_bitLength, 0, 1) {
return Smi::New(result);
}
// Should be kept in sync with
// - il_(x64/arm64/...).cc HashIntegerOpInstr,
// - asm_intrinsifier(...).cc Multiply64Hash
uint32_t Multiply64Hash(int64_t ivalue) {
const uint64_t magic_constant = /*0x1b873593cc9e*/ 0x2d51;
uint64_t value = static_cast<uint64_t>(ivalue);
#if defined(ARCH_IS_64_BIT)
#ifdef _MSC_VER
__int64 hi = __umulh(value, magic_constant);
uint64_t lo = value * magic_constant;
uint64_t hash = lo ^ hi;
#else
const __int128 res = static_cast<__int128>(value) * magic_constant;
uint64_t hash = res ^ static_cast<int64_t>(res >> 64);
#endif
hash = hash ^ (hash >> 32);
#else
uint64_t prod_lo64 = value * magic_constant;
uint64_t value_lo32 = value & 0xffffffff;
uint64_t value_hi32 = value >> 32;
uint64_t carry = (((value_hi32 * magic_constant) & 0xffffffff) +
((value_lo32 * magic_constant) >> 32)) >>
32;
uint64_t prod_hi64 = ((value_hi32 * magic_constant) >> 32) + carry;
uint64_t hash = prod_hi64 ^ prod_lo64;
hash = hash ^ (hash >> 32);
#endif
return hash & 0x3fffffff;
}
// Mint natives.
DEFINE_NATIVE_ENTRY(Mint_bitNegate, 0, 1) {

16
runtime/lib/integers.h Normal file
View file

@ -0,0 +1,16 @@
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#ifndef RUNTIME_LIB_INTEGERS_H_
#define RUNTIME_LIB_INTEGERS_H_
#include "platform/globals.h"
namespace dart {
uint32_t Multiply64Hash(int64_t value);
} // namespace dart
#endif // RUNTIME_LIB_INTEGERS_H_

View file

@ -16,5 +16,5 @@ class C {
main() {
var c = C();
c.field = <T>(T x) => x.hashCode;
Expect.equals(3, c.field<dynamic>(3));
Expect.equals(3.hashCode, c.field<dynamic>(3));
}

View file

@ -0,0 +1,33 @@
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// VMOptions=--intrinsify
// VMOptions=--no_intrinsify
import 'package:expect/expect.dart';
main() {
// We do not guarantee any particular hash function for integers,
// but our implementation relies on the hash function being the same
// across architectures.
<int, int>{
0: 0x0,
-0: 0x0,
1: 0x2d51,
-1: 0x0,
0xffff: 0x2d50d2af,
-0xffff: 0x2d50fffe,
0xffffffff: 0x3fffffff,
-0xffffffff: 0x3fffd2ae,
0x111111111111: 0x25630507,
-0x111111111111: 0x25632856,
0xffffffffffff: 0x12af2d50,
-0xffffffffffff: 0x12af0001,
9007199254840856: 0x2f2da59d,
144115188075954880: 0x26761e9a,
936748722493162112: 0x196ac8cd,
}.forEach((value, expected) {
Expect.equals(expected, value.hashCode, "${value}.hashCode");
});
}

View file

@ -18,5 +18,5 @@ class C {
main() {
var c = C();
c.field = <T>(T x) => x.hashCode;
Expect.equals(3, c.field<dynamic>(3));
Expect.equals(3.hashCode, c.field<dynamic>(3));
}

View file

@ -0,0 +1,35 @@
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// VMOptions=--intrinsify
// VMOptions=--no_intrinsify
// @dart = 2.9
import 'package:expect/expect.dart';
main() {
// We do not guarantee any particular hash function for integers,
// but our implementation relies on the hash function being the same
// across architectures.
<int, int>{
0: 0x0,
-0: 0x0,
1: 0x2d51,
-1: 0x0,
0xffff: 0x2d50d2af,
-0xffff: 0x2d50fffe,
0xffffffff: 0x3fffffff,
-0xffffffff: 0x3fffd2ae,
0x111111111111: 0x25630507,
-0x111111111111: 0x25632856,
0xffffffffffff: 0x12af2d50,
-0xffffffffffff: 0x12af0001,
9007199254840856: 0x2f2da59d,
144115188075954880: 0x26761e9a,
936748722493162112: 0x196ac8cd,
}.forEach((value, expected) {
Expect.equals(expected, value.hashCode, "${value}.hashCode");
});
}

View file

@ -945,6 +945,24 @@ void AsmIntrinsifier::Double_getIsNegative(Assembler* assembler,
__ b(&is_false);
}
// Input: tagged integer in R0
// Output: tagged hash code value in R0
// Should be kept in sync with
// - il_(x64/arm64/...).cc HashIntegerOpInstr,
// - asm_intrinsifier(...).cc Multiply64Hash
// - integers.cc Multiply64Hash
static void Multiply64Hash(Assembler* assembler) {
__ SmiUntag(R0);
__ SignFill(R1, R0); // sign extend R0 to R1
__ LoadImmediate(TMP, compiler::Immediate(0x2d51));
__ umull(TMP, R0, R0, TMP); // (R0:TMP) = R0 * 0x2d51
// (0:0:R0:TMP) is 128-bit product
__ eor(R0, TMP, compiler::Operand(R0));
__ eor(R0, R1, compiler::Operand(R0));
__ AndImmediate(R0, R0, 0x3fffffff);
__ SmiTag(R0);
}
void AsmIntrinsifier::Double_hashCode(Assembler* assembler,
Label* normal_ir_body) {
// TODO(dartbug.com/31174): Convert this to a graph intrinsic.
@ -975,7 +993,9 @@ void AsmIntrinsifier::Double_hashCode(Assembler* assembler,
__ vcvtdi(D1, S2);
__ vcmpd(D0, D1);
__ vmstat();
READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, EQ));
__ b(&double_hash, NE);
Multiply64Hash(assembler);
__ Ret();
// Convert the double bits to a hash code that fits in a Smi.
__ Bind(&double_hash);
__ ldr(R0, FieldAddress(R1, target::Double::value_offset()));

View file

@ -1100,6 +1100,23 @@ void AsmIntrinsifier::Double_getIsNegative(Assembler* assembler,
__ ret();
}
// Input: tagged integer in R0
// Output: tagged hash code value in R0
// Should be kept in sync with
// - il_(x64/arm64/...).cc HashIntegerOpInstr,
// - asm_intrinsifier(...).cc Multiply64Hash
// - integers.cc Multiply64Hash
static void Multiply64Hash(Assembler* assembler) {
__ SmiUntag(R0);
__ LoadImmediate(TMP, compiler::Immediate(0x2d51));
__ mul(R1, TMP, R0);
__ umulh(TMP, TMP, R0);
__ eor(R0, R1, compiler::Operand(TMP));
__ eor(R0, R0, compiler::Operand(R0, LSR, 32));
__ AndImmediate(R0, R0, 0x3fffffff);
__ SmiTag(R0);
}
void AsmIntrinsifier::Double_hashCode(Assembler* assembler,
Label* normal_ir_body) {
// TODO(dartbug.com/31174): Convert this to a graph intrinsic.
@ -1136,6 +1153,8 @@ void AsmIntrinsifier::Double_hashCode(Assembler* assembler,
// Smi tagged result immediately as the hash code.
__ fcmpd(V0, V1);
__ b(&double_hash, NE);
Multiply64Hash(assembler);
__ ret();
// Convert the double bits to a hash code that fits in a Smi.

View file

@ -1073,6 +1073,30 @@ void AsmIntrinsifier::Double_getIsNegative(Assembler* assembler,
__ jmp(&is_false, Assembler::kNearJump);
}
// Input: tagged integer in EAX
// Output: tagged hash code value in EAX
// - il_(x64/arm64/...).cc HashIntegerOpInstr,
// - asm_intrinsifier(...).cc Multiply64Hash
// - integers.cc Multiply64Hash
static void Multiply64Hash(Assembler* assembler) {
__ SmiUntag(EAX);
__ cdq(); // // sign-extend EAX to EDX
__ movl(ECX, EDX); // save "value_hi" in ECX
__ movl(EDX, compiler::Immediate(0x2d51));
__ mull(EDX); // (EDX:EAX) = value_lo * 0x2d51
__ movl(EBX, EAX); // save lo32 in EBX
__ movl(EAX, ECX); // get saved value_hi
__ movl(ECX, EDX); // save hi32 in ECX
__ movl(EDX, compiler::Immediate(0x2d51));
__ mull(EDX); // (EDX:EAX) = value_hi * 0x2d51
__ addl(EAX, ECX); // EAX has prod_hi32, EDX has prod_hi64_lo32
__ xorl(EAX, EDX); // EAX = prod_hi32 ^ prod_hi64_lo32
__ xorl(EAX, EBX); // result = prod_hi32 ^ prod_hi64_lo32 ^ prod_lo32
__ andl(EAX, compiler::Immediate(0x3fffffff));
__ SmiTag(EAX);
}
void AsmIntrinsifier::Double_hashCode(Assembler* assembler,
Label* normal_ir_body) {
// TODO(dartbug.com/31174): Convert this to a graph intrinsic.
@ -1096,6 +1120,8 @@ void AsmIntrinsifier::Double_hashCode(Assembler* assembler,
Label double_hash;
__ comisd(XMM0, XMM1);
__ j(NOT_EQUAL, &double_hash, Assembler::kNearJump);
Multiply64Hash(assembler);
__ ret();
// Convert the double bits to a hash code that fits in a Smi.

View file

@ -1128,6 +1128,38 @@ void AsmIntrinsifier::Double_getIsNegative(Assembler* assembler,
kFClassNegSubnormal | kFClassNegZero);
}
// Input: untagged integer in A1
// Output: tagged hash code value in A0
// Should be kept in sync with
// - il_(x64/arm64/...).cc HashIntegerOpInstr,
// - asm_intrinsifier(...).cc Multiply64Hash
// - integers.cc Multiply64Hash
static void Multiply64Hash(Assembler* assembler) {
#if XLEN == 32
__ srai(A0, A1, 31); // sign extend A1 to A0
__ LoadImmediate(TMP, 0x2d51);
__ mulhu(A2, A1, TMP);
__ mul(A1, A1, TMP); // (A2:A1) = lo32 * 0x2d51
__ mulhu(TMP2, A0, TMP);
__ mul(A0, A0, TMP); // (TMP2:A0) = hi32 * 0x2d51
__ add(A0, A0, A2); // (0: TMP2: A0: A1)
__ xor_(TMP2, TMP2, A1);
__ xor_(A0, A0, TMP2);
#else
__ LoadImmediate(TMP, 0x2d51);
__ mul(A0, TMP, A1);
__ mulhu(TMP, TMP, A1);
__ xor_(A0, A0, TMP);
__ srai(A1, A0, 32);
__ xor_(A0, A0, A1);
#endif
__ AndImmediate(A0, A0, 0x3fffffff);
__ SmiTag(A0);
}
void AsmIntrinsifier::Double_hashCode(Assembler* assembler,
Label* normal_ir_body) {
Label double_hash;
@ -1138,16 +1170,28 @@ void AsmIntrinsifier::Double_hashCode(Assembler* assembler,
#if XLEN == 32
__ fcvtwd(A1, FA0);
__ fcvtdw(FA1, A1);
// Ensure value in Smi range
__ SmiTag(TMP, A1);
__ SmiUntag(TMP2, TMP);
__ bne(TMP2, A1, normal_ir_body, Assembler::kNearJump);
__ feqd(TMP, FA0, FA1);
__ beqz(TMP, &double_hash, Assembler::kNearJump); // Not integer.
#else
__ fcvtld(A1, FA0);
__ fcvtdl(FA1, A1);
#endif
__ feqd(TMP, FA0, FA1);
__ beqz(TMP, &double_hash, Assembler::kNearJump); // Not integer.
__ SmiTag(A0, A1);
__ SmiUntag(TMP, A0);
__ bne(TMP, A1, normal_ir_body, Assembler::kNearJump); // Not Smi.
// Ensure value in Smi range
__ SmiTag(TMP, A1);
__ SmiUntag(TMP2, TMP);
__ bne(TMP2, A1, normal_ir_body, Assembler::kNearJump);
#endif
Multiply64Hash(assembler);
__ ret();
__ Bind(&double_hash);
@ -1159,6 +1203,7 @@ void AsmIntrinsifier::Double_hashCode(Assembler* assembler,
__ fmvxd(A0, FA0);
__ srli(A1, A0, 32);
#endif
__ xor_(A0, A0, A1);
__ AndImmediate(A0, A0, target::kSmiMax);
__ SmiTag(A0);

View file

@ -972,6 +972,24 @@ void AsmIntrinsifier::Double_getIsNegative(Assembler* assembler,
__ jmp(&is_false, Assembler::kNearJump);
}
// Input: tagged integer in RAX
// Output: tagged hash code value in RAX
// Should be kept in sync with
// - il_(x64/arm64/...).cc HashIntegerOpInstr,
// - asm_intrinsifier(...).cc Multiply64Hash
// - integers.cc Multiply64Hash
static void Multiply64Hash(Assembler* assembler) {
__ SmiUntagAndSignExtend(RAX);
__ movq(RDX, Immediate(0x2d51));
__ mulq(RDX);
__ xorq(RAX, RDX);
__ movq(RDX, RAX);
__ shrq(RDX, Immediate(32));
__ xorq(RAX, RDX);
__ andq(RAX, Immediate(0x3fffffff));
__ SmiTag(RAX);
}
void AsmIntrinsifier::Double_hashCode(Assembler* assembler,
Label* normal_ir_body) {
// TODO(dartbug.com/31174): Convert this to a graph intrinsic.
@ -995,6 +1013,8 @@ void AsmIntrinsifier::Double_hashCode(Assembler* assembler,
Label double_hash;
__ comisd(XMM0, XMM1);
__ j(NOT_EQUAL, &double_hash, Assembler::kNearJump);
Multiply64Hash(assembler);
__ ret();
// Convert the double bits to a hash code that fits in a Smi.

View file

@ -1204,6 +1204,17 @@ void ConstantPropagator::VisitUnboxInt64(UnboxInt64Instr* instr) {
VisitUnbox(instr);
}
void ConstantPropagator::VisitHashIntegerOp(HashIntegerOpInstr* instr) {
const Object& value = instr->value()->definition()->constant_value();
if (IsUnknown(value)) {
return;
}
if (value.IsInteger()) {
// TODO(aam): Add constant hash evaluation
}
SetValue(instr, non_constant_);
}
void ConstantPropagator::VisitUnaryIntegerOp(UnaryIntegerOpInstr* unary_op) {
const Object& value = unary_op->value()->definition()->constant_value();
if (IsUnknown(value)) {

View file

@ -469,6 +469,7 @@ struct InstrAttrs {
M(CloneContext, _) \
M(BinarySmiOp, kNoGC) \
M(BinaryInt32Op, kNoGC) \
M(HashIntegerOp, kNoGC) \
M(UnarySmiOp, kNoGC) \
M(UnaryDoubleOp, kNoGC) \
M(CheckStackOverflow, _) \
@ -8368,6 +8369,53 @@ class DoubleTestOpInstr : public TemplateComparison<1, NoThrow, Pure> {
DISALLOW_COPY_AND_ASSIGN(DoubleTestOpInstr);
};
class HashIntegerOpInstr : public TemplateDefinition<1, NoThrow, Pure> {
public:
HashIntegerOpInstr(Value* value, bool smi, intptr_t deopt_id)
: TemplateDefinition(deopt_id), smi_(smi) {
SetInputAt(0, value);
}
static HashIntegerOpInstr* Create(Value* value, bool smi, intptr_t deopt_id) {
return new HashIntegerOpInstr(value, smi, deopt_id);
}
Value* value() const { return inputs_[0]; }
virtual intptr_t DeoptimizationTarget() const {
// Direct access since this instruction cannot deoptimize, and the deopt-id
// was inherited from another instruction that could deoptimize.
return GetDeoptId();
}
virtual Representation representation() const { return kTagged; }
virtual Representation RequiredInputRepresentation(intptr_t idx) const {
ASSERT(idx == 0);
return kTagged;
}
DECLARE_INSTRUCTION(HashIntegerOp)
virtual bool ComputeCanDeoptimize() const { return false; }
virtual CompileType ComputeType() const { return CompileType::Smi(); }
virtual bool AttributesEqual(const Instruction& other) const { return true; }
#define FIELD_LIST(F) F(const bool, smi_)
DECLARE_INSTRUCTION_SERIALIZABLE_FIELDS(HashIntegerOpInstr,
TemplateDefinition,
FIELD_LIST)
#undef FIELD_LIST
PRINT_OPERANDS_TO_SUPPORT
private:
DISALLOW_COPY_AND_ASSIGN(HashIntegerOpInstr);
};
class UnaryIntegerOpInstr : public TemplateDefinition<1, NoThrow, Pure> {
public:
UnaryIntegerOpInstr(Token::Kind op_kind, Value* value, intptr_t deopt_id)

View file

@ -5877,6 +5877,50 @@ void TruncDivModInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ Bind(&done);
}
LocationSummary* HashIntegerOpInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 1;
const intptr_t kNumTemps = 1;
LocationSummary* summary = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
summary->set_in(0, Location::WritableRegister());
summary->set_out(0, Location::RequiresRegister());
summary->set_temp(0, Location::RequiresRegister());
return summary;
}
// Should be kept in sync with
// - asm_intrinsifier_x64.cc Multiply64Hash
// - integers.cc Multiply64Hash
// - integers.dart computeHashCode
void HashIntegerOpInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
Register value = locs()->in(0).reg();
Register result = locs()->out(0).reg();
Register temp = locs()->temp(0).reg();
if (smi_) {
__ SmiUntag(value);
__ SignFill(temp, value);
} else {
__ LoadFieldFromOffset(temp, value,
Mint::value_offset() + compiler::target::kWordSize);
__ LoadFieldFromOffset(value, value, Mint::value_offset());
}
Register value_lo = value;
Register value_hi = temp;
__ LoadImmediate(TMP, compiler::Immediate(0x2d51));
__ umull(result, value_lo, value_lo, TMP); // (lo:result) = lo32 * 0x2d51
__ umull(TMP, value_hi, value_hi, TMP); // (hi:TMP) = hi32 * 0x2d51
__ add(TMP, TMP, compiler::Operand(value_lo));
// (0:hi:TMP:result) is 128-bit product
__ eor(result, value_hi, compiler::Operand(result));
__ eor(result, TMP, compiler::Operand(result));
__ AndImmediate(result, result, 0x3fffffff);
__ SmiTag(result);
}
LocationSummary* BranchInstr::MakeLocationSummary(Zone* zone, bool opt) const {
comparison()->InitializeLocationSummary(zone, opt);
// Branches don't produce a result.

View file

@ -4999,6 +4999,39 @@ void TruncDivModInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ Bind(&done);
}
LocationSummary* HashIntegerOpInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 1;
const intptr_t kNumTemps = 0;
LocationSummary* summary = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
summary->set_in(0, Location::RequiresRegister());
summary->set_out(0, Location::RequiresRegister());
return summary;
}
// Should be kept in sync with
// - asm_intrinsifier_x64.cc Multiply64Hash
// - integers.cc Multiply64Hash
// - integers.dart computeHashCode
void HashIntegerOpInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
Register value = locs()->in(0).reg();
Register result = locs()->out(0).reg();
if (smi_) {
__ SmiUntag(TMP2, value);
} else {
__ LoadFieldFromOffset(TMP2, value, Mint::value_offset());
}
__ LoadImmediate(TMP, compiler::Immediate(0x2d51));
__ mul(result, TMP, TMP2);
__ umulh(TMP, TMP, TMP2);
__ eor(result, result, compiler::Operand(TMP));
__ eor(result, result, compiler::Operand(result, LSR, 32));
__ ubfm(result, result, 63, 29); // SmiTag(result & 0x3fffffff)
}
LocationSummary* BranchInstr::MakeLocationSummary(Zone* zone, bool opt) const {
comparison()->InitializeLocationSummary(zone, opt);
// Branches don't produce a result.

View file

@ -5066,6 +5066,65 @@ void TruncDivModInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
__ SmiTag(EDX);
}
LocationSummary* HashIntegerOpInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 1;
const intptr_t kNumTemps = 3;
LocationSummary* summary = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
summary->set_in(0, Location::RegisterLocation(EAX));
summary->set_out(0, Location::SameAsFirstInput());
summary->set_temp(0, Location::RequiresRegister());
summary->set_temp(1, Location::RequiresRegister());
summary->set_temp(2, Location::RegisterLocation(EDX));
return summary;
}
void HashIntegerOpInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
Register value = locs()->in(0).reg();
Register result = locs()->out(0).reg();
Register temp = locs()->temp(0).reg();
Register temp1 = locs()->temp(1).reg();
ASSERT(value == EAX);
ASSERT(result == EAX);
if (smi_) {
__ SmiUntag(EAX);
__ cdq(); // sign-extend EAX to EDX
__ movl(temp, EDX);
} else {
__ LoadFieldFromOffset(temp, EAX,
Mint::value_offset() + compiler::target::kWordSize);
__ LoadFieldFromOffset(EAX, EAX, Mint::value_offset());
}
// value = value_hi << 32 + value_lo
//
// value * 0x2d51 = (value_hi * 0x2d51) << 32 + value_lo * 0x2d51
// prod_lo32 = value_lo * 0x2d51
// prod_hi32 = carry(value_lo * 0x2d51) + value_hi * 0x2d51
// prod_lo64 = prod_hi32 << 32 + prod_lo32
// prod_hi64_lo32 = carry(value_hi * 0x2d51)
// result = prod_lo32 ^ prod_hi32 ^ prod_hi64_lo32
// return result & 0x3fffffff
// EAX has value_lo
__ movl(EDX, compiler::Immediate(0x2d51));
__ mull(
EDX); // EAX = lo32(value_lo * 0x2d51), EDX = carry(value_lo * 0x2d51)
__ movl(temp1, EAX); // save prod_lo32
__ movl(EAX, temp); // get saved value_hi
__ movl(temp, EDX); // save carry
__ movl(EDX, compiler::Immediate(0x2d51));
__ mull(EDX); // EAX = lo32(value_hi * 0x2d51, EDX = carry(value_hi * 0x2d51)
__ addl(EAX, temp); // EAX has prod_hi32, EDX has prod_hi64_lo32
__ xorl(EAX, EDX); // EAX = prod_hi32 ^ prod_hi64_lo32
__ xorl(EAX, temp1); // result = prod_hi32 ^ prod_hi64_lo32 ^ prod_lo32
__ andl(EAX, compiler::Immediate(0x3fffffff));
__ SmiTag(EAX);
}
LocationSummary* BranchInstr::MakeLocationSummary(Zone* zone, bool opt) const {
comparison()->InitializeLocationSummary(zone, opt);
// Branches don't produce a result.

View file

@ -1048,6 +1048,14 @@ void CheckClassIdInstr::PrintOperandsTo(BaseTextBuffer* f) const {
}
}
void HashIntegerOpInstr::PrintOperandsTo(BaseTextBuffer* f) const {
if (smi_) {
f->AddString("smi ");
}
value()->PrintTo(f);
}
void CheckClassInstr::PrintOperandsTo(BaseTextBuffer* f) const {
value()->PrintTo(f);
PrintCidsHelper(f, cids_, FlowGraphPrinter::kPrintAll);

View file

@ -5145,6 +5145,78 @@ void TruncDivModInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
}
}
LocationSummary* HashIntegerOpInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 1;
#if XLEN == 32
const intptr_t kNumTemps = 1;
LocationSummary* summary = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
summary->set_temp(0, Location::RequiresRegister());
#else
const intptr_t kNumTemps = 0;
LocationSummary* summary = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
#endif
summary->set_in(0, Location::WritableRegister());
summary->set_out(0, Location::RequiresRegister());
return summary;
}
// Should be kept in sync with
// - asm_intrinsifier_x64.cc Multiply64Hash
// - integers.cc Multiply64Hash
// - integers.dart computeHashCode
void HashIntegerOpInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
Register result = locs()->out(0).reg();
Register value = locs()->in(0).reg();
#if XLEN == 32
Register value_hi = locs()->temp(0).reg();
if (smi_) {
__ SmiUntag(value);
__ srai(value_hi, value, XLEN - 1); // SignFill
} else {
__ LoadFieldFromOffset(value_hi, value,
Mint::value_offset() + compiler::target::kWordSize);
__ LoadFieldFromOffset(value, value, Mint::value_offset());
}
Register value_lo = value;
__ LoadImmediate(TMP, 0x2d51);
// (value_hi:value_lo) * (0:TMP) =
// value_lo * TMP + (value_hi * TMP) * 2^32 =
// lo32(value_lo * TMP) +
// (hi32(value_lo * TMP) + lo32(value_hi * TMP) * 2^32 +
// hi32(value_hi * TMP) * 2^64
__ mulhu(TMP2, value_lo, TMP);
__ mul(result, value_lo, TMP); // (TMP2:result) = lo32 * 0x2d51
__ mulhu(value_lo, value_hi, TMP);
__ mul(TMP, value_hi, TMP); // (value_lo:TMP) = hi32 * 0x2d51
__ add(TMP, TMP, TMP2);
// (0:value_lo:TMP:result) is 128-bit product
__ xor_(result, value_lo, result);
__ xor_(result, TMP, result);
#else
if (smi_) {
__ SmiUntag(value);
} else {
__ LoadFieldFromOffset(value, value, Mint::value_offset());
}
__ LoadImmediate(TMP, 0x2d51);
__ mul(result, TMP, value);
__ mulhu(TMP, TMP, value);
__ xor_(result, result, TMP);
__ srai(TMP, result, 32);
__ xor_(result, result, TMP);
#endif
__ AndImmediate(result, result, 0x3fffffff);
__ SmiTag(result);
}
LocationSummary* BranchInstr::MakeLocationSummary(Zone* zone, bool opt) const {
comparison()->InitializeLocationSummary(zone, opt);
// Branches don't produce a result.

View file

@ -5318,6 +5318,46 @@ void TruncDivModInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
// in-range arguments, cannot create out-of-range result.
}
LocationSummary* HashIntegerOpInstr::MakeLocationSummary(Zone* zone,
bool opt) const {
const intptr_t kNumInputs = 1;
const intptr_t kNumTemps = 1;
LocationSummary* summary = new (zone)
LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall);
summary->set_in(0, Location::RegisterLocation(RAX));
summary->set_out(0, Location::SameAsFirstInput());
summary->set_temp(0, Location::RegisterLocation(RDX));
return summary;
}
// Should be kept in sync with
// - asm_intrinsifier_x64.cc Multiply64Hash
// - integers.cc Multiply64Hash
// - integers.dart computeHashCode
void HashIntegerOpInstr::EmitNativeCode(FlowGraphCompiler* compiler) {
Register value = locs()->in(0).reg();
Register result = locs()->out(0).reg();
Register temp = locs()->temp(0).reg();
ASSERT(value == RAX);
ASSERT(result == RAX);
ASSERT(temp == RDX);
if (smi_) {
__ SmiUntagAndSignExtend(RAX);
} else {
__ LoadFieldFromOffset(RAX, RAX, Mint::value_offset());
}
__ movq(RDX, compiler::Immediate(0x2d51)); // 1b873593cc9e2d51));
__ mulq(RDX);
__ xorq(RAX, RDX); // RAX = xor(hi64, lo64)
__ movq(RDX, RAX);
__ shrq(RDX, compiler::Immediate(32));
__ xorq(RAX, RDX);
__ andq(RAX, compiler::Immediate(0x3fffffff));
__ SmiTag(RAX);
}
LocationSummary* BranchInstr::MakeLocationSummary(Zone* zone, bool opt) const {
comparison()->InitializeLocationSummary(zone, opt);
// Branches don't produce a result.

View file

@ -1021,6 +1021,8 @@ bool FlowGraphBuilder::IsRecognizedMethodForFlowGraph(
case MethodRecognizer::kUtf8DecoderScan:
case MethodRecognizer::kHas63BitSmis:
case MethodRecognizer::kExtensionStreamHasListener:
case MethodRecognizer::kSmi_hashCode:
case MethodRecognizer::kMint_hashCode:
#define CASE(method, slot) case MethodRecognizer::k##method:
LOAD_NATIVE_FIELD(CASE)
STORE_NATIVE_FIELD(CASE)
@ -1488,6 +1490,17 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfRecognizedMethod(
body += IntToBool();
#endif // PRODUCT
} break;
case MethodRecognizer::kSmi_hashCode: {
ASSERT_EQUAL(function.NumParameters(), 1);
body += LoadLocal(parsed_function_->RawParameterVariable(0));
body += BuildHashCode(/*smi=*/true);
} break;
case MethodRecognizer::kMint_hashCode: {
ASSERT_EQUAL(function.NumParameters(), 1);
body += LoadLocal(parsed_function_->RawParameterVariable(0));
body += BuildHashCode(/*smi=*/false);
} break;
// case MethodRecognizer::kDouble_hashCode:
case MethodRecognizer::kFfiAsExternalTypedDataInt8:
case MethodRecognizer::kFfiAsExternalTypedDataInt16:
case MethodRecognizer::kFfiAsExternalTypedDataInt32:
@ -5085,6 +5098,16 @@ const Function& FlowGraphBuilder::PrependTypeArgumentsFunction() {
return prepend_type_arguments_;
}
Fragment FlowGraphBuilder::BuildHashCode(bool smi) {
Fragment body;
Value* unboxed_value = Pop();
HashIntegerOpInstr* hash =
new HashIntegerOpInstr(unboxed_value, smi, DeoptId::kNone);
Push(hash);
body <<= hash;
return body;
}
int64_t SwitchHelper::ExpressionRange() const {
const int64_t min = expression_min().AsInt64Value();
const int64_t max = expression_max().AsInt64Value();

View file

@ -575,6 +575,8 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder {
return instructions;
}
Fragment BuildHashCode(bool smi);
TranslationHelper translation_helper_;
Thread* thread_;
Zone* zone_;

View file

@ -320,6 +320,8 @@ namespace dart {
V(_FutureListener, handleValue, FutureListenerHandleValue, 0x25b39832) \
V(::, has63BitSmis, Has63BitSmis, 0xf61b56f1) \
V(::, get:extensionStreamHasListener, ExtensionStreamHasListener, 0xfab46343)\
V(_Smi, get:hashCode, Smi_hashCode, 0x75e0ccd2) \
V(_Mint, get:hashCode, Mint_hashCode, 0x75e0ccd2) \
// List of intrinsics:
// (class-name, function-name, intrinsification method, fingerprint).

View file

@ -8,6 +8,7 @@
#include "compiler/method_recognizer.h"
#include "include/dart_api.h"
#include "lib/integers.h"
#include "lib/stacktrace.h"
#include "platform/assert.h"
#include "platform/text_buffer.h"
@ -22755,6 +22756,10 @@ int Integer::CompareWith(const Integer& other) const {
return 0;
}
uint32_t Integer::CanonicalizeHash() const {
return Multiply64Hash(AsInt64Value());
}
IntegerPtr Integer::AsValidInteger() const {
if (IsSmi()) return ptr();
if (IsMint()) {

View file

@ -9222,7 +9222,7 @@ class Integer : public Number {
virtual bool CanonicalizeEquals(const Instance& other) const {
return Equals(other);
}
virtual uint32_t CanonicalizeHash() const { return AsTruncatedUint32Value(); }
virtual uint32_t CanonicalizeHash() const;
virtual bool Equals(const Instance& other) const;
virtual ObjectPtr HashCode() const { return ptr(); }

View file

@ -6534,7 +6534,11 @@ TEST_CASE(HashCode_Double) {
// cannot be used as keys in constant sets and maps. However, doubles
// _can_ be used for lookups in which case they are equal to their integer
// value.
const uint32_t kInt1HashCode = 1;
uint32_t kInt1HashCode = 0;
{
TransitionNativeToVM transition(thread);
kInt1HashCode = Integer::Handle(Integer::New(1)).CanonicalizeHash();
}
EXPECT(HashCodeEqualsCanonicalizeHash(kScript, kInt1HashCode));
}

View file

@ -563,8 +563,11 @@ class _Smi extends _IntegerImplementation {
throw "Unreachable";
}
int get hashCode => this;
int get _identityHashCode => this;
@pragma("vm:recognized", "other")
external int get hashCode;
int get _identityHashCode => hashCode;
@pragma("vm:recognized", "graph-intrinsic")
@pragma("vm:exact-result-type", "dart:core#_Smi")
@pragma("vm:disable-unboxed-parameters")
@ -774,8 +777,10 @@ class _Mint extends _IntegerImplementation {
throw "Unreachable";
}
int get hashCode => this;
int get _identityHashCode => this;
@pragma("vm:recognized", "other")
external int get hashCode;
int get _identityHashCode => hashCode;
@pragma("vm:non-nullable-result-type")
@pragma("vm:external-name", "Mint_bitNegate")
external int operator ~();

View file

@ -6,5 +6,5 @@ import "package:expect/expect.dart";
void main() {
dynamic x = 3;
Expect.equals(3, x.hashCode);
Expect.equals(3.0.hashCode, x.hashCode);
}

View file

@ -8,5 +8,5 @@ import "package:expect/expect.dart";
void main() {
dynamic x = 3;
Expect.equals(3, x.hashCode);
Expect.equals(3.0.hashCode, x.hashCode);
}