Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 8c0df46

Browse files
aamCommit Queue
authored andcommitted
[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 dart-lang/sdk#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>
1 parent 9f32d19 commit 8c0df46

29 files changed

+620
-16
lines changed

runtime/lib/double.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
#include "lib/integers.h"
6+
57
#include "vm/bootstrap_natives.h"
68

79
#include <math.h> // NOLINT
@@ -76,7 +78,7 @@ DEFINE_NATIVE_ENTRY(Double_hashCode, 0, 1) {
7678
(val <= kMaxInt64RepresentableAsDouble)) {
7779
int64_t ival = static_cast<int64_t>(val);
7880
if (static_cast<double>(ival) == val) {
79-
return Integer::New(ival);
81+
return Integer::New(Multiply64Hash(ival));
8082
}
8183
}
8284

runtime/lib/integers.cc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
#include "lib/integers.h"
56
#include "vm/bootstrap_natives.h"
67

78
#include "include/dart_api.h"
@@ -271,6 +272,37 @@ DEFINE_NATIVE_ENTRY(Smi_bitLength, 0, 1) {
271272
return Smi::New(result);
272273
}
273274

275+
// Should be kept in sync with
276+
// - il_(x64/arm64/...).cc HashIntegerOpInstr,
277+
// - asm_intrinsifier(...).cc Multiply64Hash
278+
uint32_t Multiply64Hash(int64_t ivalue) {
279+
const uint64_t magic_constant = /*0x1b873593cc9e*/ 0x2d51;
280+
uint64_t value = static_cast<uint64_t>(ivalue);
281+
#if defined(ARCH_IS_64_BIT)
282+
#ifdef _MSC_VER
283+
__int64 hi = __umulh(value, magic_constant);
284+
uint64_t lo = value * magic_constant;
285+
uint64_t hash = lo ^ hi;
286+
#else
287+
const __int128 res = static_cast<__int128>(value) * magic_constant;
288+
uint64_t hash = res ^ static_cast<int64_t>(res >> 64);
289+
#endif
290+
hash = hash ^ (hash >> 32);
291+
#else
292+
uint64_t prod_lo64 = value * magic_constant;
293+
294+
uint64_t value_lo32 = value & 0xffffffff;
295+
uint64_t value_hi32 = value >> 32;
296+
uint64_t carry = (((value_hi32 * magic_constant) & 0xffffffff) +
297+
((value_lo32 * magic_constant) >> 32)) >>
298+
32;
299+
uint64_t prod_hi64 = ((value_hi32 * magic_constant) >> 32) + carry;
300+
uint64_t hash = prod_hi64 ^ prod_lo64;
301+
hash = hash ^ (hash >> 32);
302+
#endif
303+
return hash & 0x3fffffff;
304+
}
305+
274306
// Mint natives.
275307

276308
DEFINE_NATIVE_ENTRY(Mint_bitNegate, 0, 1) {

runtime/lib/integers.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
#ifndef RUNTIME_LIB_INTEGERS_H_
6+
#define RUNTIME_LIB_INTEGERS_H_
7+
8+
#include "platform/globals.h"
9+
10+
namespace dart {
11+
12+
uint32_t Multiply64Hash(int64_t value);
13+
14+
} // namespace dart
15+
16+
#endif // RUNTIME_LIB_INTEGERS_H_

runtime/tests/vm/dart/generic_field_invocation_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@ class C {
1616
main() {
1717
var c = C();
1818
c.field = <T>(T x) => x.hashCode;
19-
Expect.equals(3, c.field<dynamic>(3));
19+
Expect.equals(3.hashCode, c.field<dynamic>(3));
2020
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// VMOptions=--intrinsify
6+
// VMOptions=--no_intrinsify
7+
8+
import 'package:expect/expect.dart';
9+
10+
main() {
11+
// We do not guarantee any particular hash function for integers,
12+
// but our implementation relies on the hash function being the same
13+
// across architectures.
14+
<int, int>{
15+
0: 0x0,
16+
-0: 0x0,
17+
1: 0x2d51,
18+
-1: 0x0,
19+
0xffff: 0x2d50d2af,
20+
-0xffff: 0x2d50fffe,
21+
0xffffffff: 0x3fffffff,
22+
-0xffffffff: 0x3fffd2ae,
23+
0x111111111111: 0x25630507,
24+
-0x111111111111: 0x25632856,
25+
0xffffffffffff: 0x12af2d50,
26+
-0xffffffffffff: 0x12af0001,
27+
9007199254840856: 0x2f2da59d,
28+
144115188075954880: 0x26761e9a,
29+
936748722493162112: 0x196ac8cd,
30+
}.forEach((value, expected) {
31+
Expect.equals(expected, value.hashCode, "${value}.hashCode");
32+
});
33+
}

runtime/tests/vm/dart_2/generic_field_invocation_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ class C {
1818
main() {
1919
var c = C();
2020
c.field = <T>(T x) => x.hashCode;
21-
Expect.equals(3, c.field<dynamic>(3));
21+
Expect.equals(3.hashCode, c.field<dynamic>(3));
2222
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// VMOptions=--intrinsify
6+
// VMOptions=--no_intrinsify
7+
8+
// @dart = 2.9
9+
10+
import 'package:expect/expect.dart';
11+
12+
main() {
13+
// We do not guarantee any particular hash function for integers,
14+
// but our implementation relies on the hash function being the same
15+
// across architectures.
16+
<int, int>{
17+
0: 0x0,
18+
-0: 0x0,
19+
1: 0x2d51,
20+
-1: 0x0,
21+
0xffff: 0x2d50d2af,
22+
-0xffff: 0x2d50fffe,
23+
0xffffffff: 0x3fffffff,
24+
-0xffffffff: 0x3fffd2ae,
25+
0x111111111111: 0x25630507,
26+
-0x111111111111: 0x25632856,
27+
0xffffffffffff: 0x12af2d50,
28+
-0xffffffffffff: 0x12af0001,
29+
9007199254840856: 0x2f2da59d,
30+
144115188075954880: 0x26761e9a,
31+
936748722493162112: 0x196ac8cd,
32+
}.forEach((value, expected) {
33+
Expect.equals(expected, value.hashCode, "${value}.hashCode");
34+
});
35+
}

runtime/vm/compiler/asm_intrinsifier_arm.cc

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,24 @@ void AsmIntrinsifier::Double_getIsNegative(Assembler* assembler,
945945
__ b(&is_false);
946946
}
947947

948+
// Input: tagged integer in R0
949+
// Output: tagged hash code value in R0
950+
// Should be kept in sync with
951+
// - il_(x64/arm64/...).cc HashIntegerOpInstr,
952+
// - asm_intrinsifier(...).cc Multiply64Hash
953+
// - integers.cc Multiply64Hash
954+
static void Multiply64Hash(Assembler* assembler) {
955+
__ SmiUntag(R0);
956+
__ SignFill(R1, R0); // sign extend R0 to R1
957+
__ LoadImmediate(TMP, compiler::Immediate(0x2d51));
958+
__ umull(TMP, R0, R0, TMP); // (R0:TMP) = R0 * 0x2d51
959+
// (0:0:R0:TMP) is 128-bit product
960+
__ eor(R0, TMP, compiler::Operand(R0));
961+
__ eor(R0, R1, compiler::Operand(R0));
962+
__ AndImmediate(R0, R0, 0x3fffffff);
963+
__ SmiTag(R0);
964+
}
965+
948966
void AsmIntrinsifier::Double_hashCode(Assembler* assembler,
949967
Label* normal_ir_body) {
950968
// TODO(dartbug.com/31174): Convert this to a graph intrinsic.
@@ -975,7 +993,9 @@ void AsmIntrinsifier::Double_hashCode(Assembler* assembler,
975993
__ vcvtdi(D1, S2);
976994
__ vcmpd(D0, D1);
977995
__ vmstat();
978-
READS_RETURN_ADDRESS_FROM_LR(__ bx(LR, EQ));
996+
__ b(&double_hash, NE);
997+
Multiply64Hash(assembler);
998+
__ Ret();
979999
// Convert the double bits to a hash code that fits in a Smi.
9801000
__ Bind(&double_hash);
9811001
__ ldr(R0, FieldAddress(R1, target::Double::value_offset()));

runtime/vm/compiler/asm_intrinsifier_arm64.cc

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1100,6 +1100,23 @@ void AsmIntrinsifier::Double_getIsNegative(Assembler* assembler,
11001100
__ ret();
11011101
}
11021102

1103+
// Input: tagged integer in R0
1104+
// Output: tagged hash code value in R0
1105+
// Should be kept in sync with
1106+
// - il_(x64/arm64/...).cc HashIntegerOpInstr,
1107+
// - asm_intrinsifier(...).cc Multiply64Hash
1108+
// - integers.cc Multiply64Hash
1109+
static void Multiply64Hash(Assembler* assembler) {
1110+
__ SmiUntag(R0);
1111+
__ LoadImmediate(TMP, compiler::Immediate(0x2d51));
1112+
__ mul(R1, TMP, R0);
1113+
__ umulh(TMP, TMP, R0);
1114+
__ eor(R0, R1, compiler::Operand(TMP));
1115+
__ eor(R0, R0, compiler::Operand(R0, LSR, 32));
1116+
__ AndImmediate(R0, R0, 0x3fffffff);
1117+
__ SmiTag(R0);
1118+
}
1119+
11031120
void AsmIntrinsifier::Double_hashCode(Assembler* assembler,
11041121
Label* normal_ir_body) {
11051122
// TODO(dartbug.com/31174): Convert this to a graph intrinsic.
@@ -1136,6 +1153,8 @@ void AsmIntrinsifier::Double_hashCode(Assembler* assembler,
11361153
// Smi tagged result immediately as the hash code.
11371154
__ fcmpd(V0, V1);
11381155
__ b(&double_hash, NE);
1156+
1157+
Multiply64Hash(assembler);
11391158
__ ret();
11401159

11411160
// Convert the double bits to a hash code that fits in a Smi.

runtime/vm/compiler/asm_intrinsifier_ia32.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,30 @@ void AsmIntrinsifier::Double_getIsNegative(Assembler* assembler,
10731073
__ jmp(&is_false, Assembler::kNearJump);
10741074
}
10751075

1076+
// Input: tagged integer in EAX
1077+
// Output: tagged hash code value in EAX
1078+
// - il_(x64/arm64/...).cc HashIntegerOpInstr,
1079+
// - asm_intrinsifier(...).cc Multiply64Hash
1080+
// - integers.cc Multiply64Hash
1081+
static void Multiply64Hash(Assembler* assembler) {
1082+
__ SmiUntag(EAX);
1083+
__ cdq(); // // sign-extend EAX to EDX
1084+
__ movl(ECX, EDX); // save "value_hi" in ECX
1085+
__ movl(EDX, compiler::Immediate(0x2d51));
1086+
__ mull(EDX); // (EDX:EAX) = value_lo * 0x2d51
1087+
__ movl(EBX, EAX); // save lo32 in EBX
1088+
__ movl(EAX, ECX); // get saved value_hi
1089+
__ movl(ECX, EDX); // save hi32 in ECX
1090+
__ movl(EDX, compiler::Immediate(0x2d51));
1091+
__ mull(EDX); // (EDX:EAX) = value_hi * 0x2d51
1092+
__ addl(EAX, ECX); // EAX has prod_hi32, EDX has prod_hi64_lo32
1093+
1094+
__ xorl(EAX, EDX); // EAX = prod_hi32 ^ prod_hi64_lo32
1095+
__ xorl(EAX, EBX); // result = prod_hi32 ^ prod_hi64_lo32 ^ prod_lo32
1096+
__ andl(EAX, compiler::Immediate(0x3fffffff));
1097+
__ SmiTag(EAX);
1098+
}
1099+
10761100
void AsmIntrinsifier::Double_hashCode(Assembler* assembler,
10771101
Label* normal_ir_body) {
10781102
// TODO(dartbug.com/31174): Convert this to a graph intrinsic.
@@ -1096,6 +1120,8 @@ void AsmIntrinsifier::Double_hashCode(Assembler* assembler,
10961120
Label double_hash;
10971121
__ comisd(XMM0, XMM1);
10981122
__ j(NOT_EQUAL, &double_hash, Assembler::kNearJump);
1123+
1124+
Multiply64Hash(assembler);
10991125
__ ret();
11001126

11011127
// Convert the double bits to a hash code that fits in a Smi.

0 commit comments

Comments
 (0)