Optimization of computing short-integer string's hash code

issue : https://gitee.com/openharmony/arkcompiler_ets_runtime/issues/I83SNR

Signed-off-by: DaiHN <daihuina1@huawei.com>
Change-Id: I81a050d7f43fb60ce185685433ca9eceafd967d2
This commit is contained in:
DaiHN 2023-11-01 17:43:38 +08:00
parent d29a1082fb
commit c8aa4db942
23 changed files with 561 additions and 98 deletions

View File

@ -484,6 +484,61 @@ int64_t NumberHelper::DoubleToInt64(double d)
return static_cast<int64_t>(d);
}
bool NumberHelper::IsDigitalString(const uint8_t *start, const uint8_t *end)
{
int len = end - start;
for (int i = 0; i < len; i++) {
if (*(start + i) < '0' || *(start + i) > '9') {
return false;
}
}
return true;
}
int NumberHelper::StringToInt(const uint8_t *start, const uint8_t *end)
{
int num = *start - '0';
for (int i = 1; i < (end - start); i++) {
num = 10 * num + (*(start + i) - '0');
}
return num;
}
// only for string is ordinary string and using UTF8 encoding
// Fast path for short integer and some special value
std::pair<bool, JSTaggedNumber> NumberHelper::FastStringToNumber(const uint8_t *start,
const uint8_t *end, JSTaggedValue string)
{
ASSERT(start < end);
EcmaStringAccessor strAccessor(string);
bool minus = (start[0] == '-');
int pos = (minus ? 1 : 0);
if (pos == (end - start)) {
return {true, JSTaggedNumber(NAN_VALUE)};
} else if (*(start + pos) > '9') {
// valid number's codes not longer than '9', except 'I' and non-breaking space.
if (*(start + pos) != 'I' && *(start + pos) != 0xA0) {
return {true, JSTaggedNumber(NAN_VALUE)};
}
} else if ((end - (start + pos)) <= MAX_ELEMENT_INDEX_LEN && IsDigitalString((start + pos), end)) {
int num = StringToInt((start + pos), end);
if (minus) {
if (num == 0) {
return {true, JSTaggedNumber(SignedZero(Sign::NEG))};
}
num = -num;
} else {
if (num != 0 || (num == 0 && (end - start == 1))) {
strAccessor.TryToSetIntegerHash(num);
}
}
return {true, JSTaggedNumber(num)};
}
return {false, JSTaggedNumber(NAN_VALUE)};
}
double NumberHelper::StringToDouble(const uint8_t *start, const uint8_t *end, uint8_t radix, uint32_t flags)
{
auto p = const_cast<uint8_t *>(start);

View File

@ -108,6 +108,10 @@ public:
static JSHandle<EcmaString> NumberToString(const JSThread *thread, JSTaggedValue number);
static double TruncateDouble(double d);
static int64_t DoubleToInt64(double d);
static bool IsDigitalString(const uint8_t *start, const uint8_t *end);
static int StringToInt(const uint8_t *start, const uint8_t *end);
static std::pair<bool, JSTaggedNumber> FastStringToNumber(const uint8_t *start,
const uint8_t *end, JSTaggedValue string);
static double StringToDouble(const uint8_t *start, const uint8_t *end, uint8_t radix, uint32_t flags = NO_FLAGS);
static int32_t DoubleToInt(double d, size_t bits);
static int32_t DoubleInRangeInt32(double d);

View File

@ -44,21 +44,26 @@ JSTaggedValue BuiltinsNumber::NumberConstructor(EcmaRuntimeCallInfo *argv)
JSThread *thread = argv->GetThread();
[[maybe_unused]] EcmaHandleScope handleScope(thread);
JSHandle<JSTaggedValue> newTarget = GetNewTarget(argv);
// 1. If value is present, then a , b , c.
// 2. Else Let n be +0𝔽.
JSTaggedNumber numberValue(0);
if (argv->GetArgsNumber() > 0) {
JSHandle<JSTaggedValue> value = GetCallArg(argv, 0);
// a. Let prim be ? ToNumeric(value).
JSHandle<JSTaggedValue> numericVal = JSTaggedValue::ToNumeric(thread, value);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// b. If Type(prim) is BigInt, let n be 𝔽((prim)).
if (numericVal->IsBigInt()) {
JSHandle<BigInt> bigNumericVal(numericVal);
numberValue = BigInt::BigIntToNumber(bigNumericVal);
if (!value->IsNumber()) {
JSHandle<JSTaggedValue> numericVal = JSTaggedValue::ToNumeric(thread, value);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
// b. If Type(prim) is BigInt, let n be 𝔽((prim)).
if (numericVal->IsBigInt()) {
JSHandle<BigInt> bigNumericVal(numericVal);
numberValue = BigInt::BigIntToNumber(bigNumericVal);
} else {
// c. Otherwise, let n be prim.
numberValue = JSTaggedNumber(numericVal.GetTaggedValue());
}
} else {
// c. Otherwise, let n be prim.
numberValue = JSTaggedNumber(numericVal.GetTaggedValue());
numberValue = JSTaggedNumber(value.GetTaggedValue());
}
}
// 3. If NewTarget is undefined, return n.

View File

@ -2735,7 +2735,7 @@ HWTEST_F_L0(BuiltinsMathTest, Max_3)
[[maybe_unused]] auto prev = TestHelper::SetupFrame(thread_, ecmaRuntimeCallInfo);
JSTaggedValue result = BuiltinsMath::Max(ecmaRuntimeCallInfo);
TestHelper::TearDownFrame(thread_, prev);
JSTaggedValue expect = BuiltinsBase::GetTaggedDouble(100.0);
JSTaggedValue expect = BuiltinsBase::GetTaggedInt(100);
ASSERT_EQ(result.GetRawData(), expect.GetRawData());
}

View File

@ -576,6 +576,9 @@ public:
GateRef GetLengthFromString(GateRef value);
GateRef GetHashcodeFromString(GateRef glue, GateRef value);
GateRef TryGetHashcodeFromString(GateRef string);
GateRef IsIntegerString(GateRef string);
GateRef GetRawHashFromString(GateRef value);
void SetRawHashcode(GateRef glue, GateRef str, GateRef rawHashcode, GateRef isInteger);
// for in
GateRef GetEnumCacheKind(GateRef glue, GateRef enumCache);

View File

@ -14,6 +14,9 @@
*/
#include "ecmascript/compiler/mcr_circuit_builder.h"
#include "ecmascript/message_string.h"
#include "ecmascript/stubs/runtime_stubs-inl.h"
#include "ecmascript/stubs/runtime_stubs.h"
#include "ecmascript/compiler/circuit_builder-inl.h"
#include "ecmascript/global_env.h"
@ -958,6 +961,47 @@ GateRef CircuitBuilder::InsertLoadArrayLength(GateRef array, bool isTypedArray)
return Circuit::NullGate();
}
GateRef CircuitBuilder::IsIntegerString(GateRef string)
{
// compressedStringsEnabled fixed to true constant
GateRef hash = Load(VariableType::INT32(), string, IntPtr(EcmaString::MIX_HASHCODE_OFFSET));
return Int32Equal(
Int32And(hash, Int32(EcmaString::IS_INTEGER_MASK)),
Int32(EcmaString::IS_INTEGER_MASK));
}
GateRef CircuitBuilder::GetRawHashFromString(GateRef value)
{
GateRef hash = Load(VariableType::INT32(), value, IntPtr(EcmaString::MIX_HASHCODE_OFFSET));
return Int32And(hash, Int32(~EcmaString::IS_INTEGER_MASK));
}
void CircuitBuilder::SetRawHashcode(GateRef glue, GateRef str, GateRef rawHashcode, GateRef isInteger)
{
Label subentry(env_);
SubCfgEntry(&subentry);
Label integer(env_);
Label notInteger(env_);
Label exit(env_);
DEFVAlUE(hash, env_, VariableType::INT32(), Int32(0));
Branch(isInteger, &integer, &notInteger);
Bind(&integer);
{
hash = Int32Or(rawHashcode, Int32(EcmaString::IS_INTEGER_MASK));
Jump(&exit);
}
Bind(&notInteger);
{
hash = Int32And(rawHashcode, Int32(~EcmaString::IS_INTEGER_MASK));
Jump(&exit);
}
Bind(&exit);
Store(VariableType::INT32(), glue, str, IntPtr(EcmaString::MIX_HASHCODE_OFFSET), *hash);
SubCfgExit();
return;
}
GateRef CircuitBuilder::GetLengthFromString(GateRef value)
{
GateRef len = Load(VariableType::INT32(), value, IntPtr(EcmaString::MIX_LENGTH_OFFSET));
@ -972,13 +1016,13 @@ GateRef CircuitBuilder::GetHashcodeFromString(GateRef glue, GateRef value)
Label noRawHashcode(env_);
Label exit(env_);
DEFVAlUE(hashcode, env_, VariableType::INT32(), Int32(0));
hashcode = Load(VariableType::INT32(), value, IntPtr(EcmaString::HASHCODE_OFFSET));
hashcode = Load(VariableType::INT32(), value, IntPtr(EcmaString::MIX_HASHCODE_OFFSET));
Branch(Int32Equal(*hashcode, Int32(0)), &noRawHashcode, &exit);
Bind(&noRawHashcode);
{
hashcode =
CallNGCRuntime(glue, RTSTUB_ID(ComputeHashcode), Gate::InvalidGateRef, { value }, Circuit::NullGate());
Store(VariableType::INT32(), glue, value, IntPtr(EcmaString::HASHCODE_OFFSET), *hashcode);
Store(VariableType::INT32(), glue, value, IntPtr(EcmaString::MIX_HASHCODE_OFFSET), *hashcode);
Jump(&exit);
}
Bind(&exit);
@ -995,7 +1039,7 @@ GateRef CircuitBuilder::TryGetHashcodeFromString(GateRef string)
Label storeHash(env_);
Label exit(env_);
DEFVAlUE(result, env_, VariableType::INT64(), Int64(-1));
GateRef hashCode = ZExtInt32ToInt64(Load(VariableType::INT32(), string, IntPtr(EcmaString::HASHCODE_OFFSET)));
GateRef hashCode = ZExtInt32ToInt64(Load(VariableType::INT32(), string, IntPtr(EcmaString::MIX_HASHCODE_OFFSET)));
Branch(Int64Equal(hashCode, Int64(0)), &noRawHashcode, &storeHash);
Bind(&noRawHashcode);
{

View File

@ -527,7 +527,7 @@ void NewObjectStubBuilder::AllocLineStringObject(Variable *result, Label *exit,
ConstantIndex::LINE_STRING_CLASS_INDEX);
StoreHClass(glue_, result->ReadVariable(), stringClass);
SetLength(glue_, result->ReadVariable(), length, compressed);
SetRawHashcode(glue_, result->ReadVariable(), Int32(0));
SetRawHashcode(glue_, result->ReadVariable(), Int32(0), False());
Jump(exit);
}
@ -547,7 +547,7 @@ void NewObjectStubBuilder::AllocSlicedStringObject(Variable *result, Label *exit
GateRef mixLength = Load(VariableType::INT32(), flatString->GetFlatString(), IntPtr(EcmaString::MIX_LENGTH_OFFSET));
GateRef isCompressed = Int32And(Int32(EcmaString::STRING_COMPRESSED_BIT), mixLength);
SetLength(glue_, result->ReadVariable(), length, isCompressed);
SetRawHashcode(glue_, result->ReadVariable(), Int32(0));
SetRawHashcode(glue_, result->ReadVariable(), Int32(0), False());
BuiltinsStringStubBuilder builtinsStringStubBuilder(this);
builtinsStringStubBuilder.StoreParent(glue_, result->ReadVariable(), flatString->GetFlatString());
builtinsStringStubBuilder.StoreStartIndex(glue_, result->ReadVariable(),
@ -569,7 +569,7 @@ void NewObjectStubBuilder::AllocTreeStringObject(Variable *result, Label *exit,
ConstantIndex::TREE_STRING_CLASS_INDEX);
StoreHClass(glue_, result->ReadVariable(), stringClass);
SetLength(glue_, result->ReadVariable(), length, compressed);
SetRawHashcode(glue_, result->ReadVariable(), Int32(0));
SetRawHashcode(glue_, result->ReadVariable(), Int32(0), False());
Store(VariableType::JS_POINTER(), glue_, result->ReadVariable(), IntPtr(TreeEcmaString::FIRST_OFFSET), first);
Store(VariableType::JS_POINTER(), glue_, result->ReadVariable(), IntPtr(TreeEcmaString::SECOND_OFFSET), second);
Jump(exit);

View File

@ -2622,9 +2622,19 @@ inline void StubBuilder::SetLength(GateRef glue, GateRef str, GateRef length, Ga
Store(VariableType::INT32(), glue, str, IntPtr(EcmaString::MIX_LENGTH_OFFSET), mixLength);
}
inline void StubBuilder::SetRawHashcode(GateRef glue, GateRef str, GateRef rawHashcode)
inline GateRef StubBuilder::IsIntegerString(GateRef string)
{
Store(VariableType::INT32(), glue, str, IntPtr(EcmaString::HASHCODE_OFFSET), rawHashcode);
return env_->GetBuilder()->IsIntegerString(string);
}
inline GateRef StubBuilder::GetRawHashFromString(GateRef value)
{
return env_->GetBuilder()->GetRawHashFromString(value);
}
inline void StubBuilder::SetRawHashcode(GateRef glue, GateRef str, GateRef rawHashcode, GateRef isInteger)
{
env_->GetBuilder()->SetRawHashcode(glue, str, rawHashcode, isInteger);
}
inline GateRef StubBuilder::TryGetHashcodeFromString(GateRef string)

View File

@ -1530,6 +1530,27 @@ GateRef StubBuilder::IsDigit(GateRef ch)
Int32GreaterThanOrEqual(ch, Int32('0')));
}
void StubBuilder::TryToGetInteger(GateRef string, Variable *num, Label *success, Label *failed)
{
auto env = GetEnvironment();
Label exit(env);
Label inRange(env);
Label isInteger(env);
GateRef len = GetLengthFromString(string);
Branch(Int32LessThan(len, Int32(MAX_ELEMENT_INDEX_LEN)), &inRange, failed);
Bind(&inRange);
{
Branch(IsIntegerString(string), &isInteger, failed);
Bind(&isInteger);
{
GateRef integerNum = ZExtInt32ToInt64(GetRawHashFromString(string));
num->WriteVariable(integerNum);
Jump(success);
}
}
}
GateRef StubBuilder::StringToElementIndex(GateRef glue, GateRef string)
{
auto env = GetEnvironment();
@ -1551,6 +1572,9 @@ GateRef StubBuilder::StringToElementIndex(GateRef glue, GateRef string)
Branch(isUtf16String, &exit, &isUtf8);
Bind(&isUtf8);
{
Label getFailed(env);
TryToGetInteger(string, &result, &exit, &getFailed);
Bind(&getFailed);
DEFVARIABLE(c, VariableType::INT32(), Int32(0));
FlatStringStubBuilder thisFlat(this);
thisFlat.FlattenString(glue, string, &flattenFastPath);
@ -5957,24 +5981,7 @@ void StubBuilder::ReturnExceptionIfAbruptCompletion(GateRef glue)
GateRef StubBuilder::GetHashcodeFromString(GateRef glue, GateRef value)
{
auto env = GetEnvironment();
Label subentry(env);
env->SubCfgEntry(&subentry);
Label noRawHashcode(env);
Label exit(env);
DEFVARIABLE(hashcode, VariableType::INT32(), Int32(0));
hashcode = Load(VariableType::INT32(), value, IntPtr(EcmaString::HASHCODE_OFFSET));
Branch(Int32Equal(*hashcode, Int32(0)), &noRawHashcode, &exit);
Bind(&noRawHashcode);
{
hashcode = CallNGCRuntime(glue, RTSTUB_ID(ComputeHashcode), { value });
Store(VariableType::INT32(), glue, value, IntPtr(EcmaString::HASHCODE_OFFSET), *hashcode);
Jump(&exit);
}
Bind(&exit);
auto ret = *hashcode;
env->SubCfgExit();
return ret;
return env_->GetBuilder()->GetHashcodeFromString(glue, value);
}
GateRef StubBuilder::ConstructorCheck(GateRef glue, GateRef ctor, GateRef outPut, GateRef thisObj)

View File

@ -406,6 +406,9 @@ public:
GateRef GetBitFieldFromHClass(GateRef hClass);
GateRef GetLengthFromString(GateRef value);
GateRef GetHashcodeFromString(GateRef glue, GateRef value);
inline GateRef IsIntegerString(GateRef string);
inline void SetRawHashcode(GateRef glue, GateRef str, GateRef rawHashcode, GateRef isInteger);
inline GateRef GetRawHashFromString(GateRef value);
GateRef TryGetHashcodeFromString(GateRef string);
GateRef GetFirstFromTreeString(GateRef string);
GateRef GetSecondFromTreeString(GateRef string);
@ -478,6 +481,7 @@ public:
GateRef IsUtf8String(GateRef string);
GateRef IsInternalString(GateRef string);
GateRef IsDigit(GateRef ch);
void TryToGetInteger(GateRef string, Variable *num, Label *success, Label *failed);
GateRef StringToElementIndex(GateRef glue, GateRef string);
GateRef ComputeNonInlinedFastPropsCapacity(GateRef glue, GateRef oldLength,
GateRef maxNonInlinedFastPropsCapacity);
@ -701,7 +705,6 @@ public:
std::initializer_list<GateRef> args, JSCallMode mode);
inline void SetLength(GateRef glue, GateRef str, GateRef length, bool compressed);
inline void SetLength(GateRef glue, GateRef str, GateRef length, GateRef isCompressed);
inline void SetRawHashcode(GateRef glue, GateRef str, GateRef rawHashcode);
void Assert(int messageId, int line, GateRef glue, GateRef condition, Label *nextLabel);
GateRef GetNormalStringData(const StringInfoGateRef &stringInfoGate);

View File

@ -281,6 +281,14 @@ inline uint16_t EcmaString::At(int32_t index) const
}
}
inline Span<const uint8_t> EcmaString::FastToUtf8Span() const
{
uint32_t strLen = GetLength();
ASSERT(IsUtf8());
const uint8_t *data = GetDataUtf8();
return Span<const uint8_t>(data, strLen);
}
inline void EcmaString::WriteData(uint32_t index, uint16_t src)
{
ASSERT(index < GetLength());
@ -406,5 +414,10 @@ inline void EcmaStringAccessor::ReadData(EcmaString *dst, EcmaString *src,
{
dst->WriteData(src, start, destSize, length);
}
inline Span<const uint8_t> EcmaStringAccessor::FastToUtf8Span()
{
return string_->FastToUtf8Span();
}
} // namespace panda::ecmascript
#endif

View File

@ -675,43 +675,112 @@ bool EcmaString::MemCopyChars(Span<T> &dst, size_t dstMax, Span<const T> &src, s
return true;
}
uint32_t EcmaString::ComputeHashcode(uint32_t hashSeed) const
bool EcmaString::HashIntegerString(uint32_t length, uint32_t *hash, const uint32_t hashSeed) const
{
uint32_t hash;
ASSERT(length >= 0);
Span<const uint8_t> str = FastToUtf8Span();
return HashIntegerString(str.data(), length, hash, hashSeed);
}
uint32_t EcmaString::ComputeHashcode() const
{
auto [hash, isInteger] = ComputeRawHashcode();
return MixHashcode(hash, isInteger);
}
// hashSeed only be used when computing two separate strings merged hashcode.
std::pair<uint32_t, bool> EcmaString::ComputeRawHashcode() const
{
uint32_t hash = 0;
uint32_t length = GetLength();
if (length == 0) {
return {hash, false};
}
if (IsUtf8()) {
// String using UTF8 encoding, and length smaller than 10, try to compute integer hash.
if (length < MAX_ELEMENT_INDEX_LEN && this->HashIntegerString(length, &hash, 0)) {
return {hash, true};
}
CVector<uint8_t> buf;
const uint8_t *data = EcmaString::GetUtf8DataFlat(this, buf);
hash = ComputeHashForData(data, length, hashSeed);
// String can not convert to integer number, using normal hashcode computing algorithm.
hash = this->ComputeHashForData(data, length, 0);
return {hash, false};
} else {
CVector<uint16_t> buf;
const uint16_t *data = EcmaString::GetUtf16DataFlat(this, buf);
hash = ComputeHashForData(data, length, hashSeed);
// If rawSeed has certain value, and second string uses UTF16 encoding,
// then merged string can not be small integer number.
hash = this->ComputeHashForData(data, length, 0);
return {hash, false};
}
}
// hashSeed only be used when computing two separate strings merged hashcode.
uint32_t EcmaString::ComputeHashcode(uint32_t rawHashSeed, bool isInteger) const
{
uint32_t hash;
uint32_t length = GetLength();
if (length == 0) {
return MixHashcode(rawHashSeed, isInteger);
}
if (IsUtf8()) {
// String using UTF8 encoding, and length smaller than 10, try to compute integer hash.
if ((rawHashSeed == 0 || isInteger) &&
length < MAX_ELEMENT_INDEX_LEN && this->HashIntegerString(length, &hash, rawHashSeed)) {
return hash;
}
CVector<uint8_t> buf;
const uint8_t *data = EcmaString::GetUtf8DataFlat(this, buf);
// String can not convert to integer number, using normal hashcode computing algorithm.
hash = this->ComputeHashForData(data, length, rawHashSeed);
return MixHashcode(hash, NOT_INTEGER);
} else {
CVector<uint16_t> buf;
const uint16_t *data = EcmaString::GetUtf16DataFlat(this, buf);
// If rawSeed has certain value, and second string uses UTF16 encoding,
// then merged string can not be small integer number.
hash = this->ComputeHashForData(data, length, rawHashSeed);
return MixHashcode(hash, NOT_INTEGER);
}
return hash;
}
/* static */
uint32_t EcmaString::ComputeHashcodeUtf8(const uint8_t *utf8Data, size_t utf8Len, bool canBeCompress)
{
uint32_t hash = 0;
uint32_t mixHash = 0;
if (canBeCompress) {
hash = ComputeHashForData(utf8Data, utf8Len, 0);
// String using UTF8 encoding, and length smaller than 10, try to compute integer hash.
if (utf8Len < MAX_ELEMENT_INDEX_LEN && HashIntegerString(utf8Data, utf8Len, &mixHash, 0)) {
return mixHash;
}
uint32_t hash = ComputeHashForData(utf8Data, utf8Len, 0);
return MixHashcode(hash, NOT_INTEGER);
} else {
auto utf16Len = base::utf_helper::Utf8ToUtf16Size(utf8Data, utf8Len);
CVector<uint16_t> tmpBuffer(utf16Len);
[[maybe_unused]] auto len = base::utf_helper::ConvertRegionUtf8ToUtf16(utf8Data, tmpBuffer.data(), utf8Len,
utf16Len, 0);
ASSERT(len == utf16Len);
hash = ComputeHashForData(tmpBuffer.data(), utf16Len, 0);
uint32_t hash = ComputeHashForData(tmpBuffer.data(), utf16Len, 0);
return MixHashcode(hash, NOT_INTEGER);
}
return hash;
LOG_ECMA(FATAL) << "this branch is unreachable";
UNREACHABLE();
}
/* static */
uint32_t EcmaString::ComputeHashcodeUtf16(const uint16_t *utf16Data, uint32_t length)
{
return ComputeHashForData(utf16Data, length, 0);
uint32_t mixHash = 0;
// String length smaller than 10, try to compute integer hash.
if (length < MAX_ELEMENT_INDEX_LEN && HashIntegerString(utf16Data, length, &mixHash, 0)) {
return mixHash;
}
uint32_t hash = ComputeHashForData(utf16Data, length, 0);
return MixHashcode(hash, NOT_INTEGER);
}
/* static */
@ -751,6 +820,11 @@ bool EcmaString::ToElementIndex(uint32_t *index)
return false;
}
// fast path: get integer from string's hash value
if (TryToGetInteger(index)) {
return true;
}
CVector<uint8_t> buf;
const uint8_t *data = EcmaString::GetUtf8DataFlat(this, buf);
uint32_t c = data[0];

View File

@ -52,24 +52,36 @@ class FlatStringInfo;
}
class EcmaString : public TaggedObject {
/* Mix Hash Code: -- { 0 | [31 bits raw hash code] } computed through string
\ { 1 | [31 bits integer numbers] } fastpath for string to number
*/
public:
CAST_CHECK(EcmaString, IsString);
static constexpr uint32_t IS_INTEGER_MASK = 1U << 31;
static constexpr uint32_t STRING_COMPRESSED_BIT = 0x1;
static constexpr uint32_t STRING_INTERN_BIT = 0x2;
static constexpr size_t MAX_STRING_LENGTH = 0x40000000U; // 30 bits for string length, 2 bits for special meaning
static constexpr uint32_t STRING_LENGTH_SHIFT_COUNT = 2U;
static constexpr uint32_t MAX_INTEGER_HASH_NUMBER = 0x3B9AC9FF;
static constexpr uint32_t MAX_CACHED_INTEGER_SIZE = 9;
static constexpr size_t MIX_LENGTH_OFFSET = TaggedObjectSize();
// In last bit of mix_length we store if this string is compressed or not.
ACCESSORS_PRIMITIVE_FIELD(MixLength, uint32_t, MIX_LENGTH_OFFSET, HASHCODE_OFFSET)
ACCESSORS_PRIMITIVE_FIELD(RawHashcode, uint32_t, HASHCODE_OFFSET, SIZE)
ACCESSORS_PRIMITIVE_FIELD(MixLength, uint32_t, MIX_LENGTH_OFFSET, MIX_HASHCODE_OFFSET)
// In last bit of mix_hash we store if this string is small-integer number or not.
ACCESSORS_PRIMITIVE_FIELD(MixHashcode, uint32_t, MIX_HASHCODE_OFFSET, SIZE)
enum CompressedStatus {
STRING_COMPRESSED,
STRING_UNCOMPRESSED,
};
enum IsIntegerStatus {
NOT_INTEGER = 0,
IS_INTEGER,
};
enum TrimMode : uint8_t {
TRIM,
TRIM_START,
@ -121,16 +133,21 @@ private:
static inline EcmaString *FastSubUtf16String(const EcmaVM *vm,
const JSHandle<EcmaString> &src, uint32_t start, uint32_t length);
bool IsUtf8() const
inline bool IsUtf8() const
{
return (GetMixLength() & STRING_COMPRESSED_BIT) == STRING_COMPRESSED;
}
bool IsUtf16() const
inline bool IsUtf16() const
{
return (GetMixLength() & STRING_COMPRESSED_BIT) == STRING_UNCOMPRESSED;
}
inline bool IsInteger() const
{
return (GetMixHashcode() & IS_INTEGER_MASK) == IS_INTEGER_MASK;
}
// require is LineString
inline uint16_t *GetData() const;
inline const uint8_t *GetDataUtf8() const;
@ -140,38 +157,54 @@ private:
inline uint8_t *GetDataUtf8Writable();
inline uint16_t *GetDataUtf16Writable();
uint32_t GetLength() const
inline uint32_t GetLength() const
{
return GetMixLength() >> STRING_LENGTH_SHIFT_COUNT;
}
void SetLength(uint32_t length, bool compressed = false)
inline void SetLength(uint32_t length, bool compressed = false)
{
ASSERT(length < MAX_STRING_LENGTH);
// Use 0u for compressed/utf8 expression
SetMixLength((length << STRING_LENGTH_SHIFT_COUNT) | (compressed ? STRING_COMPRESSED : STRING_UNCOMPRESSED));
}
inline uint32_t GetRawHashcode() const
{
return GetMixHashcode() & (~IS_INTEGER_MASK);
}
static inline uint32_t MixHashcode(uint32_t hashcode, bool isInteger)
{
return isInteger ? (hashcode | IS_INTEGER_MASK) : (hashcode & (~IS_INTEGER_MASK));
}
inline void SetRawHashcode(uint32_t hashcode, bool isInteger = false)
{
// Use 0u for not integer string's expression
SetMixHashcode(MixHashcode(hashcode, isInteger));
}
inline size_t GetUtf8Length(bool modify = true) const;
void SetIsInternString()
inline void SetIsInternString()
{
SetMixLength(GetMixLength() | STRING_INTERN_BIT);
}
bool IsInternString() const
inline bool IsInternString() const
{
return (GetMixLength() & STRING_INTERN_BIT) != 0;
}
void ClearInternStringFlag()
inline void ClearInternStringFlag()
{
SetMixLength(GetMixLength() & ~STRING_INTERN_BIT);
}
bool TryGetHashCode(uint32_t *hash)
inline bool TryGetHashCode(uint32_t *hash)
{
uint32_t hashcode = GetRawHashcode();
uint32_t hashcode = GetMixHashcode();
if (hashcode == 0 && GetLength() != 0) {
return false;
}
@ -179,22 +212,90 @@ private:
return true;
}
inline uint32_t GetIntegerCode()
{
ASSERT(GetMixHashcode() & IS_INTEGER_MASK);
return GetRawHashcode();
}
// not change this data structure.
// if string is not flat, this func has low efficiency.
uint32_t PUBLIC_API GetHashcode()
{
uint32_t hashcode = GetRawHashcode();
uint32_t hashcode = GetMixHashcode();
// GetLength() == 0 means it's an empty array.No need to computeHashCode again when hashseed is 0.
if (hashcode == 0 && GetLength() != 0) {
hashcode = ComputeHashcode(0);
hashcode = ComputeHashcode();
SetRawHashcode(hashcode);
}
return hashcode;
}
template<typename T>
inline static bool IsDecimalDigitChar(const T c)
{
return (c >= '0' && c <= '9');
}
static uint32_t ComputeIntegerHash(uint32_t *num, uint8_t c)
{
if (!IsDecimalDigitChar(c)) {
return false;
}
int charDate = c - '0';
*num = (*num) * 10 + charDate; // 10: decimal factor
return true;
}
bool HashIntegerString(uint32_t length, uint32_t *hash, uint32_t hashSeed) const;
template<typename T>
static bool HashIntegerString(const T *data, size_t size, uint32_t *hash, uint32_t hashSeed)
{
ASSERT(size >= 0);
if (hashSeed == 0) {
if (IsDecimalDigitChar(data[0]) && data[0] != '0') {
uint32_t num = data[0] - '0';
uint32_t i = 1;
do {
if (i == size) {
// compute mix hash
if (num <= MAX_INTEGER_HASH_NUMBER) {
*hash = MixHashcode(num, IS_INTEGER);
return true;
}
return false;
}
} while (ComputeIntegerHash(&num, data[i++]));
}
if (size == 1 && (data[0] == '0')) {
*hash = MixHashcode(0, IS_INTEGER);
return true;
}
} else {
if (IsDecimalDigitChar(data[0])) {
uint32_t num = hashSeed * 10 + (data[0] - '0'); // 10: decimal factor
uint32_t i = 1;
do {
if (i == size) {
// compute mix hash
if (num <= MAX_INTEGER_HASH_NUMBER) {
*hash = MixHashcode(num, IS_INTEGER);
return true;
}
return false;
}
} while (ComputeIntegerHash(&num, data[i++]));
}
}
return false;
}
// not change this data structure.
// if string is not flat, this func has low efficiency.
uint32_t PUBLIC_API ComputeHashcode(uint32_t hashSeed) const;
uint32_t PUBLIC_API ComputeHashcode() const;
std::pair<uint32_t, bool> PUBLIC_API ComputeRawHashcode() const;
uint32_t PUBLIC_API ComputeHashcode(uint32_t rawHashSeed, bool isInteger) const;
static uint32_t ComputeHashcodeUtf8(const uint8_t *utf8Data, size_t utf8Len, bool canBeCompress);
static uint32_t ComputeHashcodeUtf16(const uint16_t *utf16Data, uint32_t length);
@ -460,6 +561,29 @@ private:
return str;
}
inline Span<const uint8_t> FastToUtf8Span() const;
bool TryToGetInteger(uint32_t *result)
{
if (!IsInteger()) {
return false;
}
ASSERT(GetLength() <= MAX_CACHED_INTEGER_SIZE);
*result = GetIntegerCode();
return true;
}
// using integer number set into hash
inline bool TryToSetIntegerHash(int32_t num)
{
uint32_t hashcode = GetMixHashcode();
if (hashcode == 0 && GetLength() != 0) {
SetRawHashcode(static_cast<uint32_t>(num), IS_INTEGER);
return true;
}
return false;
}
void WriteData(EcmaString *src, uint32_t start, uint32_t destSize, uint32_t length);
static bool CanBeCompressed(const uint8_t *utf8Data, uint32_t utf8Len);
@ -1019,6 +1143,20 @@ public:
return string_->ToUtf8Span(buf);
}
// only for string is flat and using UTF8 encoding
inline Span<const uint8_t> FastToUtf8Span();
// Using string's hash to figure out whether the string can be converted to integer
inline bool TryToGetInteger(uint32_t *result)
{
return string_->TryToGetInteger(result);
}
inline bool TryToSetIntegerHash(int32_t num)
{
return string_->TryToSetIntegerHash(num);
}
// not change string data structure.
// if string is not flat, this func has low efficiency.
std::string ToStdString(StringConvertedUsage usage = StringConvertedUsage::PRINT);
@ -1077,11 +1215,26 @@ public:
return string_->GetHashcode();
}
uint32_t GetRawHashcode()
{
return string_->GetRawHashcode();
}
// not change src data structure.
// if src is not flat, this func has low efficiency.
uint32_t ComputeHashcode(uint32_t hashSeed)
std::pair<uint32_t, bool> ComputeRawHashcode()
{
return string_->ComputeHashcode(hashSeed);
return string_->ComputeRawHashcode();
}
uint32_t ComputeHashcode()
{
return string_->ComputeHashcode();
}
uint32_t ComputeHashcode(uint32_t rawHashSeed, bool isInteger)
{
return string_->ComputeHashcode(rawHashSeed, isInteger);
}
static uint32_t ComputeHashcodeUtf8(const uint8_t *utf8Data, size_t utf8Len, bool canBeCompress)

View File

@ -30,8 +30,9 @@ EcmaString *EcmaStringTable::GetString(const JSHandle<EcmaString> &firstString,
{
ASSERT(EcmaStringAccessor(firstString).NotTreeString());
ASSERT(EcmaStringAccessor(secondString).NotTreeString());
uint32_t hashCode = EcmaStringAccessor(firstString).GetHashcode();
hashCode = EcmaStringAccessor(secondString).ComputeHashcode(hashCode);
auto [hashCode, isInteger] = EcmaStringAccessor(firstString).ComputeRawHashcode();
hashCode = EcmaStringAccessor(secondString).ComputeHashcode(hashCode, isInteger);
auto range = table_.equal_range(hashCode);
for (auto item = range.first; item != range.second; ++item) {
auto foundString = item->second;

View File

@ -82,13 +82,14 @@ inline bool JSTaggedValue::ToBoolean() const
UNREACHABLE();
}
inline JSTaggedNumber JSTaggedValue::ToNumber(JSThread *thread, const JSHandle<JSTaggedValue> &tagged)
inline JSTaggedNumber JSTaggedValue::ToNumber(JSThread *thread, JSTaggedValue tagged)
{
if (tagged->IsInt() || tagged->IsDouble()) {
return JSTaggedNumber(tagged.GetTaggedValue());
DISALLOW_GARBAGE_COLLECTION;
if (tagged.IsInt() || tagged.IsDouble()) {
return JSTaggedNumber(tagged);
}
switch (tagged->GetRawData()) {
switch (tagged.GetRawData()) {
case JSTaggedValue::VALUE_UNDEFINED:
case JSTaggedValue::VALUE_HOLE: {
return JSTaggedNumber(base::NAN_VALUE);
@ -105,23 +106,29 @@ inline JSTaggedNumber JSTaggedValue::ToNumber(JSThread *thread, const JSHandle<J
}
}
if (tagged->IsString()) {
return StringToDouble(tagged.GetTaggedValue());
if (tagged.IsString()) {
return StringToNumber(tagged);
}
if (tagged->IsECMAObject()) {
JSHandle<JSTaggedValue> primValue(thread, ToPrimitive(thread, tagged, PREFER_NUMBER));
if (tagged.IsECMAObject()) {
JSHandle<JSTaggedValue>taggedHandle(thread, tagged);
JSTaggedValue primValue = ToPrimitive(thread, taggedHandle, PREFER_NUMBER);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedNumber::Exception());
return ToNumber(thread, primValue);
}
if (tagged->IsSymbol()) {
if (tagged.IsSymbol()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "Cannot convert a Symbol value to a number", JSTaggedNumber::Exception());
}
if (tagged->IsBigInt()) {
if (tagged.IsBigInt()) {
THROW_TYPE_ERROR_AND_RETURN(thread, "Cannot convert a BigInt value to a number", JSTaggedNumber::Exception());
}
THROW_TYPE_ERROR_AND_RETURN(thread, "Cannot convert a Unknown value to a number", JSTaggedNumber::Exception());
}
inline JSTaggedNumber JSTaggedValue::ToNumber(JSThread *thread, const JSHandle<JSTaggedValue> &tagged)
{
return ToNumber(thread, tagged.GetTaggedValue());
}
inline JSTaggedValue JSTaggedValue::ToBigInt(JSThread *thread, const JSHandle<JSTaggedValue> &tagged)
{
JSHandle<JSTaggedValue> primValue(thread, ToPrimitive(thread, tagged));

View File

@ -16,6 +16,7 @@
#include "ecmascript/js_tagged_value.h"
#include "ecmascript/ecma_macros.h"
#include "ecmascript/ecma_string-inl.h"
#include "ecmascript/ecma_vm.h"
#include "ecmascript/global_env.h"
#include "ecmascript/interpreter/interpreter.h"
@ -1247,14 +1248,43 @@ bool JSTaggedValue::GetContainerProperty(JSThread *thread, const JSHandle<JSTagg
return false;
}
JSTaggedNumber JSTaggedValue::StringToNumber(JSTaggedValue tagged)
{
EcmaStringAccessor strAccessor(tagged);
size_t strLen = strAccessor.GetLength();
if (strLen == 0) {
return JSTaggedNumber(0);
}
if (strLen < MAX_ELEMENT_INDEX_LEN && strAccessor.IsUtf8()) {
uint32_t index;
// fast path: get integer from string's hash value
if (strAccessor.TryToGetInteger(&index)) {
return JSTaggedNumber(index);
}
Span<const uint8_t> str = strAccessor.FastToUtf8Span();
if (strAccessor.GetLength() == 0) {
return JSTaggedNumber(0);
}
auto [isSuccess, result] = base::NumberHelper::FastStringToNumber(str.begin(), str.end(), tagged);
if (isSuccess) {
return result;
}
}
CVector<uint8_t> buf;
Span<const uint8_t> str = strAccessor.ToUtf8Span(buf);
double d = base::NumberHelper::StringToDouble(str.begin(), str.end(), 0,
base::ALLOW_BINARY + base::ALLOW_OCTAL + base::ALLOW_HEX);
return JSTaggedNumber(d);
}
JSHandle<JSTaggedValue> JSTaggedValue::ToNumeric(JSThread *thread, JSHandle<JSTaggedValue> tagged)
{
// 1. Let primValue be ? ToPrimitive(value, number)
JSHandle<JSTaggedValue> primValue(thread, ToPrimitive(thread, tagged, PREFER_NUMBER));
JSTaggedValue primValue = ToPrimitive(thread, tagged, PREFER_NUMBER);
RETURN_HANDLE_IF_ABRUPT_COMPLETION(JSTaggedValue, thread);
// 2. If Type(primValue) is BigInt, return primValue.
if (primValue->IsBigInt()) {
return primValue;
if (primValue.IsBigInt()) {
return JSHandle<JSTaggedValue>(thread, primValue);
}
// 3. Return ? ToNumber(primValue).
JSTaggedNumber number = ToNumber(thread, primValue);

View File

@ -414,6 +414,7 @@ public:
static JSTaggedValue ToPrimitive(JSThread *thread, const JSHandle<JSTaggedValue> &tagged,
PreferredPrimitiveType type = NO_PREFERENCE);
bool ToBoolean() const;
static JSTaggedNumber ToNumber(JSThread *thread, JSTaggedValue tagged);
static JSTaggedNumber ToNumber(JSThread *thread, const JSHandle<JSTaggedValue> &tagged);
static JSTaggedValue ToBigInt(JSThread *thread, const JSHandle<JSTaggedValue> &tagged);
static JSTaggedValue ToBigInt64(JSThread *thread, const JSHandle<JSTaggedValue> &tagged);
@ -435,6 +436,7 @@ public:
static JSTaggedValue CanonicalNumericIndexString(JSThread *thread, const JSHandle<JSTaggedValue> &tagged);
static JSTaggedNumber ToIndex(JSThread *thread, const JSHandle<JSTaggedValue> &tagged);
static JSTaggedNumber StringToDouble(JSTaggedValue tagged);
static JSTaggedNumber StringToNumber(JSTaggedValue tagged);
static bool ToArrayLength(JSThread *thread, const JSHandle<JSTaggedValue> &tagged, uint32_t *output);
static bool ToElementIndex(JSTaggedValue key, uint32_t *output);

View File

@ -184,7 +184,7 @@ CString ModulePathHelper::ParsePrefixBundle(JSThread *thread, const JSPandaFile
if (bundleName != vm->GetBundleName()) {
entryPoint = PREVIEW_OF_ACROSS_HAP_FLAG;
if (vm->EnableReportModuleResolvingFailure()) {
CString msg = "[ArkRuntime Log] Cannot preview this HSP module as" \
CString msg = "[ArkRuntime Log] Cannot preview this HSP module as " \
"it is imported from outside the current application.";
LOG_NO_TAG(ERROR) << msg;
}

View File

@ -2718,8 +2718,7 @@ void RuntimeStubs::EndCallTimer(uintptr_t argGlue, JSTaggedType func)
uint32_t RuntimeStubs::ComputeHashcode(JSTaggedType ecmaString)
{
auto string = reinterpret_cast<EcmaString *>(ecmaString);
uint32_t result = EcmaStringAccessor(string).ComputeHashcode(0);
return result;
return EcmaStringAccessor(string).ComputeHashcode();
}
int32_t RuntimeStubs::StringGetStart(bool isUtf8, EcmaString *srcString, int32_t length)

View File

@ -38,9 +38,12 @@ int NameDictionary::Hash(const JSTaggedValue &key)
UNREACHABLE();
}
// for ohmurl path to compute hash code
int NameDictionary::Hash(const uint8_t* str, int strSize)
{
return EcmaString::ComputeHashForData(str, strSize, 0);
uint32_t hash = EcmaString::ComputeHashForData(str, strSize, 0);
// url path can not be small int.
return EcmaString::MixHashcode(hash, EcmaString::NOT_INTEGER);
}
bool NameDictionary::IsMatch(const JSTaggedValue &key, const JSTaggedValue &other)

View File

@ -48,18 +48,6 @@ public:
JSThread *thread {nullptr};
};
static bool CheckHole(JSHandle<TaggedHashArray> &hashArray)
{
uint32_t arrayLength = hashArray->GetLength();
for (uint32_t i = 0; i < arrayLength; i++) {
JSTaggedValue indexValue = hashArray->Get(i);
if (indexValue.IsHole()) {
return false;
}
}
return true;
}
/**
* @tc.name: CreateTaggedHashArray
* @tc.desc: Call "TaggedHashArray::Create" function Create TaggedHashArray object, check whether the object
@ -151,8 +139,6 @@ HWTEST_F_L0(TaggedHashArrayTest, SetValAndGetLinkNode)
keyHash = TaggedNode::Hash(listKey.GetTaggedValue());
TaggedHashArray::SetVal(thread, taggedHashArray, keyHash, listKey, listValue);
}
bool result = CheckHole(taggedHashArray);
EXPECT_TRUE(result);
keyHash = TaggedNode::Hash(myKey4.GetTaggedValue());
// change value and add new key
TaggedHashArray::SetVal(thread, taggedHashArray, keyHash, myKey4, myKey4Value);
@ -200,8 +186,6 @@ HWTEST_F_L0(TaggedHashArrayTest, SetValAndGetTreeNode)
uint32_t hashArrayIndex = static_cast<uint32_t>(numOfElement - 1) & keyHash;
taggedHashArray->Set(thread, hashArrayIndex, rootTreeWithValueNode.GetTaggedValue());
}
bool result = CheckHole(taggedHashArray);
EXPECT_TRUE(result);
keyHash = TaggedNode::Hash(myKey5.GetTaggedValue());
// change value and add new key
TaggedHashArray::SetVal(thread, taggedHashArray, keyHash, myKey5, myKey5Value);

View File

@ -54,7 +54,41 @@
let key7 = "no-schema:" + "/src/instantiated-1af0bf5b.js";
let key8 = head3 + tail3;
let keyArray = [key1, key3, key5, key7];
// non-intern + non-intern
let numhead1 = "12".concat("022");
let numtail1 = "4".concat("21");
let numkey1 = numhead1 + numtail1;
let numkey2 = numhead1 + numtail1;
// intern + intern
let numkey3 = "123420002";
let numkey4 = "12342" + "0002";
// non-intern + intern
let numhead2 = "90".concat("88");
let numtail2 = "90";
let numkey5 = "90" + "8890";
let numkey6 = numhead2 + numtail2;
// intern + non-intern
let numhead3 = "90880";
let numtail3 = "0".concat("0");
let numkey7 = "90880" + "00";
let numkey8 = numhead3 + numtail3;
// intern + intern
let numkey9 = "999999999";
let numkey10 = "9999".concat("99999");
// intern + intern
let numkey11 = "1000000000";
let numkey12 = "10".concat("00000000");
// intern + intern
let numkey13 = "0100000";
let numkey14 = "010".concat("0000");
let keyArray = [key1, key3, key5, key7, numkey1, numkey3, numkey5, numkey7, numkey9, numkey11, numkey13];
let system = new System();
for (let i = 0; i < keyArray.length; i++) {
getOrCreateLoad(system, keyArray[i]);
@ -68,6 +102,19 @@
getOrCreateLoad(system, key4);
getOrCreateLoad(system, key6);
getOrCreateLoad(system, key8);
print("numkey1 === numkey2: ", numkey1 === numkey2);
print("numkey3 === numkey4: ", numkey3 === numkey4);
print("numkey5 === numkey6: ", numkey5 === numkey6);
print("numkey7 === numkey8: ", numkey7 === numkey8);
print("numkey9 === numkey10: ", numkey9 === numkey10);
print("numkey11 === numkey12: ", numkey11 === numkey12);
getOrCreateLoad(system, numkey2);
getOrCreateLoad(system, numkey4);
getOrCreateLoad(system, numkey6);
getOrCreateLoad(system, numkey8);
getOrCreateLoad(system, numkey10);
getOrCreateLoad(system, numkey12);
}
{

View File

@ -15,6 +15,13 @@ id: no-schema:/src/xxx-js/instantiated-1af0bf5b.js - load: undefined
id: no-schema:/src/xxx-js/instantiation.js - load: undefined
id: no-schema:/src/xxx-js/cc.js - load: undefined
id: no-schema:/src/instantiated-1af0bf5b.js - load: undefined
id: 12022421 - load: undefined
id: 123420002 - load: undefined
id: 908890 - load: undefined
id: 9088000 - load: undefined
id: 999999999 - load: undefined
id: 1000000000 - load: undefined
id: 0100000 - load: undefined
key1 === key2: true
key3 === key4: true
key5 === key6: true
@ -23,6 +30,18 @@ id: no-schema:/src/xxx-js/instantiated-1af0bf5b.js - load: [object Object]
id: no-schema:/src/xxx-js/instantiation.js - load: [object Object]
id: no-schema:/src/xxx-js/cc.js - load: [object Object]
id: no-schema:/src/instantiated-1af0bf5b.js - load: [object Object]
numkey1 === numkey2: true
numkey3 === numkey4: true
numkey5 === numkey6: true
numkey7 === numkey8: true
numkey9 === numkey10: true
numkey11 === numkey12: true
id: 12022421 - load: [object Object]
id: 123420002 - load: [object Object]
id: 908890 - load: [object Object]
id: 9088000 - load: [object Object]
id: 999999999 - load: [object Object]
id: 1000000000 - load: [object Object]
res: true
res: true
res: true