| // Copyright 2019 the V8 project authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include 'src/objects/js-objects.h' |
| #include 'src/objects/intl-objects.h' |
| |
| extern macro IntlAsciiCollationWeightsL1(): RawPtr<uint8>; |
| extern macro IntlAsciiCollationWeightsL3(): RawPtr<uint8>; |
| const kIntlAsciiCollationWeightsLength: |
| constexpr int31 generates 'Intl::kAsciiCollationWeightsLength'; |
| |
| macro IntlAsciiCollationWeightL1(c: char8): uint8 labels _Bailout { |
| static_assert(kIntlAsciiCollationWeightsLength == 256); |
| return IntlAsciiCollationWeightsL1()[Convert<intptr>(c)]; |
| } |
| macro IntlAsciiCollationWeightL1(c: char16): uint8 labels Bailout { |
| if (Convert<uint32>(c) >= kIntlAsciiCollationWeightsLength) goto Bailout; |
| return IntlAsciiCollationWeightsL1()[Convert<intptr>(c)]; |
| } |
| |
| macro IntlAsciiCollationWeightL3(c: char8): uint8 labels _Bailout { |
| static_assert(kIntlAsciiCollationWeightsLength == 256); |
| return IntlAsciiCollationWeightsL3()[Convert<intptr>(c)]; |
| } |
| macro IntlAsciiCollationWeightL3(c: char16): uint8 labels Bailout { |
| if (Convert<uint32>(c) >= kIntlAsciiCollationWeightsLength) goto Bailout; |
| return IntlAsciiCollationWeightsL3()[Convert<intptr>(c)]; |
| } |
| |
| |
| macro CheckNextIs1Byte( |
| _slice: ConstSlice<char8>, _index: intptr): void labels _Bailout { |
| // char8 is always within 0xFF. |
| } |
| macro CheckNextIs1Byte( |
| slice: ConstSlice<char16>, index: intptr): void labels Bailout { |
| if (index + 1 < slice.length) { |
| const nextChar = *slice.UncheckedAtIndex(index + 1); |
| if (Convert<uint32>(nextChar) > 0xFF) goto Bailout; |
| } |
| } |
| |
| // This fast path works for ASCII-only strings and is based on the assumption |
| // that most strings are either bytewise equal or differ on L1 (i.e., not just |
| // in capitalization). So we first compare the strings on L1 and only afterwards |
| // consider L3. This makes use of the 256-entry L1 and L3 tables defined in |
| // src/objects/intl-objects.cc. |
| macro LocaleCompareFastPath<T1: type, T2: type>( |
| left: ConstSlice<T1>, right: ConstSlice<T2>): Number labels Bailout { |
| let skip: intptr = 0; |
| const minLengthIntptr = |
| left.length < right.length ? left.length : right.length; |
| while (skip < minLengthIntptr) { |
| const lChar = *left.UncheckedAtIndex(skip); |
| const rChar = *right.UncheckedAtIndex(skip); |
| |
| if (lChar != rChar) break; |
| |
| // Restrict fast prefix skipping to standard ASCII characters. We cannot |
| // skip non-ASCII characters because they could form a contraction with the |
| // following differing character. |
| if (Convert<uint32>(lChar) >= 128) break; |
| |
| skip++; |
| } |
| |
| if (skip == left.length && skip == right.length) return 0; |
| |
| let i: intptr = skip; |
| while (i < minLengthIntptr) { |
| const lChar = *left.UncheckedAtIndex(i); |
| const rChar = *right.UncheckedAtIndex(i); |
| |
| const leftWeight = IntlAsciiCollationWeightL1(lChar) otherwise Bailout; |
| if (leftWeight == 0) goto Bailout; |
| |
| const rightWeight = IntlAsciiCollationWeightL1(rChar) otherwise Bailout; |
| if (rightWeight == 0) goto Bailout; |
| |
| if (leftWeight != rightWeight) { |
| // The result is only valid if the last processed character is not |
| // followed by a unicode combining character (we are overly strict and |
| // restrict to code points up to 0xFF). |
| CheckNextIs1Byte(left, i) otherwise Bailout; |
| CheckNextIs1Byte(right, i) otherwise Bailout; |
| if (leftWeight < rightWeight) return -1; |
| return 1; |
| } |
| i++; |
| } |
| |
| if (i < left.length) { |
| // If right is exhausted, we must check if the next char of the left string |
| // is ignorable or a combining character. |
| const lChar = *left.UncheckedAtIndex(i); |
| const leftWeight = IntlAsciiCollationWeightL1(lChar) otherwise Bailout; |
| if (leftWeight == 0) goto Bailout; |
| return 1; |
| } else if (i < right.length) { |
| // If left is exhausted, we must check if the next char of the right string |
| // is ignorable or a combining character. |
| const rChar = *right.UncheckedAtIndex(i); |
| const rightWeight = IntlAsciiCollationWeightL1(rChar) otherwise Bailout; |
| if (rightWeight == 0) goto Bailout; |
| return -1; |
| } |
| |
| i = skip; |
| while (i < minLengthIntptr) { |
| const lChar = *left.UncheckedAtIndex(i); |
| const rChar = *right.UncheckedAtIndex(i); |
| const leftWeight = IntlAsciiCollationWeightL3(lChar) otherwise unreachable; |
| const rightWeight = IntlAsciiCollationWeightL3(rChar) otherwise unreachable; |
| dcheck(leftWeight != 0 && rightWeight != 0); |
| if (leftWeight != rightWeight) { |
| if (leftWeight < rightWeight) return -1; |
| return 1; |
| } |
| i++; |
| } |
| return 0; |
| } |
| |
| transitioning builtin StringFastLocaleCompare( |
| implicit context: Context)(localeCompareFn: JSFunction, left: JSAny, |
| right: JSAny, locales: JSAny): JSAny { |
| try { |
| const leftStr = Cast<String>(left) otherwise Bailout; |
| const rightStr = Cast<String>(right) otherwise Bailout; |
| if (TaggedEqual(leftStr, rightStr)) goto Done(SmiConstant(0)); |
| |
| // TODO(mrcvtl): Investigate why StringToSlice slows down SeqOneByteString |
| // comparisons. |
| typeswitch (leftStr) { |
| case (lSeq: SeqOneByteString): { |
| typeswitch (rightStr) { |
| case (rSeq: SeqOneByteString): { |
| const leftLength = Convert<intptr>(lSeq.length); |
| const rightLength = Convert<intptr>(rSeq.length); |
| const leftSlice = |
| Subslice(&lSeq.chars, 0, leftLength) otherwise unreachable; |
| const rightSlice = |
| Subslice(&rSeq.chars, 0, rightLength) otherwise unreachable; |
| const result = LocaleCompareFastPath(leftSlice, rightSlice) |
| otherwise Bailout; |
| goto Done(result); |
| } |
| case (String): { |
| } |
| } |
| } |
| case (String): { |
| } |
| } |
| |
| StringToSlice(leftStr) otherwise LeftOneByte, LeftTwoByte; |
| } label LeftOneByte(leftSlice: ConstSlice<char8>) { |
| try { |
| const rightStr = Cast<String>(right) otherwise Bailout; |
| StringToSlice(rightStr) otherwise RightOneByte, RightTwoByte; |
| } label RightOneByte(rightSlice: ConstSlice<char8>) { |
| const result = |
| LocaleCompareFastPath(leftSlice, rightSlice) otherwise Bailout; |
| goto Done(result); |
| } label RightTwoByte(rightSlice: ConstSlice<char16>) { |
| const result = |
| LocaleCompareFastPath(leftSlice, rightSlice) otherwise Bailout; |
| goto Done(result); |
| } |
| } label LeftTwoByte(leftSlice: ConstSlice<char16>) { |
| try { |
| const rightStr = Cast<String>(right) otherwise Bailout; |
| StringToSlice(rightStr) otherwise RightOneByte, RightTwoByte; |
| } label RightOneByte(rightSlice: ConstSlice<char8>) { |
| const result = |
| LocaleCompareFastPath(leftSlice, rightSlice) otherwise Bailout; |
| goto Done(result); |
| } label RightTwoByte(rightSlice: ConstSlice<char16>) { |
| const result = |
| LocaleCompareFastPath(leftSlice, rightSlice) otherwise Bailout; |
| goto Done(result); |
| } |
| } label Bailout deferred { |
| const result = Call(context, localeCompareFn, left, right, locales); |
| goto Done(result); |
| } label Done(result: JSAny) { |
| // The Turboshaft StringLocaleCompareIntlOp narrows the result to |
| // V<Smi> from {-1, 0, 1}; keep the invariant explicit. |
| dcheck(TaggedIsSmi(result)); |
| dcheck( |
| UnsafeCast<Smi>(result) == -1 || UnsafeCast<Smi>(result) == 0 || |
| UnsafeCast<Smi>(result) == 1); |
| return result; |
| } |
| } |