mirror of
https://github.com/dart-lang/sdk
synced 2024-10-06 15:50:01 +00:00
[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:
parent
9f32d19a42
commit
8c0df46887
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
16
runtime/lib/integers.h
Normal 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_
|
|
@ -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));
|
||||
}
|
||||
|
|
33
runtime/tests/vm/dart/hashcode_int_values_test.dart
Normal file
33
runtime/tests/vm/dart/hashcode_int_values_test.dart
Normal 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");
|
||||
});
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
|
35
runtime/tests/vm/dart_2/hashcode_int_values_test.dart
Normal file
35
runtime/tests/vm/dart_2/hashcode_int_values_test.dart
Normal 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");
|
||||
});
|
||||
}
|
|
@ -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()));
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -575,6 +575,8 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder {
|
|||
return instructions;
|
||||
}
|
||||
|
||||
Fragment BuildHashCode(bool smi);
|
||||
|
||||
TranslationHelper translation_helper_;
|
||||
Thread* thread_;
|
||||
Zone* zone_;
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ~();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue