[builtins] Make ToIndex() uintptr index friendly
The new ToIndex() must eventually replace ToSmiIndex(). The CL fixes the following abstract operations: GetViewValue(view, requestIndex, isLittleEndian, type) SetViewValue(view, requestIndex, isLittleEndian, type, value) and the following builtins: DataView.prototype.getXXX DataView.prototype.setXXX where XXX are all typed elements. Bug: v8:4153 Change-Id: Ic2f33e91b59426deb0efa28bb4c15253e80a299c Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1874345 Commit-Queue: Igor Sheludko <ishell@chromium.org> Reviewed-by: Jakob Kummerow <jkummerow@chromium.org> Cr-Commit-Position: refs/heads/master@{#64506}
This commit is contained in:
parent
1e256fc3f6
commit
dec3de8a70
@ -1407,6 +1407,7 @@ const kArrayBufferMaxByteLength:
|
||||
const kMaxTypedArrayInHeap:
|
||||
constexpr int31 generates 'JSTypedArray::kMaxSizeInHeap';
|
||||
const kMaxSafeInteger: constexpr float64 generates 'kMaxSafeInteger';
|
||||
const kMaxUInt32Double: constexpr float64 generates 'kMaxUInt32Double';
|
||||
const kSmiMaxValue: constexpr uintptr generates 'kSmiMaxValue';
|
||||
const kSmiMax: uintptr = kSmiMaxValue;
|
||||
// TODO(v8:8996): Use uintptr version instead and drop this one.
|
||||
@ -1854,6 +1855,7 @@ extern transitioning macro ToInteger_Inline(
|
||||
Context, JSAny, constexpr ToIntegerTruncationMode): Number;
|
||||
extern transitioning macro ToLength_Inline(Context, JSAny): Number;
|
||||
extern transitioning macro ToNumber_Inline(Context, JSAny): Number;
|
||||
// TODO(v8:4153): Use ToIndex() instead.
|
||||
extern transitioning macro ToSmiIndex(implicit context: Context)(JSAny):
|
||||
PositiveSmi labels IfRangeError;
|
||||
extern transitioning macro ToSmiLength(implicit context: Context)(JSAny):
|
||||
@ -3615,18 +3617,46 @@ macro SameValue(a: JSAny, b: JSAny): bool {
|
||||
BranchIfSameValue(a, b) otherwise return true, return false;
|
||||
}
|
||||
|
||||
transitioning macro ToIndex(input: JSAny, context: Context): Number
|
||||
labels RangeError {
|
||||
if (input == Undefined) {
|
||||
return 0;
|
||||
// https://tc39.github.io/ecma262/#sec-toindex
|
||||
@export
|
||||
transitioning macro ToIndex(implicit context: Context)(value: JSAny):
|
||||
uintptr labels IfRangeError {
|
||||
if (value == Undefined) return 0;
|
||||
const indexNumber = ToInteger_Inline(context, value, kTruncateMinusZero);
|
||||
return ToIndex(indexNumber) otherwise IfRangeError;
|
||||
}
|
||||
|
||||
const value: Number = ToInteger_Inline(context, input, kTruncateMinusZero);
|
||||
if (value < 0 || value > kMaxSafeInteger) {
|
||||
goto RangeError;
|
||||
// https://tc39.github.io/ecma262/#sec-toindex
|
||||
// Same as the version above but for Number arguments.
|
||||
macro ToIndex(indexNumber: Number): uintptr labels IfRangeError {
|
||||
typeswitch (indexNumber) {
|
||||
case (indexSmi: Smi): {
|
||||
if (indexSmi < 0) goto IfRangeError;
|
||||
const index: uintptr = Unsigned(Convert<intptr>(indexSmi));
|
||||
// Positive Smi values definitely fit into both [0, kMaxSafeInteger] and
|
||||
// [0, kMaxUintPtr] ranges.
|
||||
return index;
|
||||
}
|
||||
case (indexHeapNumber: HeapNumber): {
|
||||
assert(IsNumberNormalized(indexHeapNumber));
|
||||
const indexDouble: float64 = Convert<float64>(indexHeapNumber);
|
||||
// NaNs must already be handled by ToIndex() version above accepting
|
||||
// JSAny indices.
|
||||
assert(!Float64IsNaN(indexDouble));
|
||||
if (indexDouble < 0) goto IfRangeError;
|
||||
|
||||
return value;
|
||||
if constexpr (Is64()) {
|
||||
if (indexDouble > kMaxSafeInteger) goto IfRangeError;
|
||||
} else {
|
||||
// On 32-bit architectures not all safe integers fit into uintptr but
|
||||
// callers handle this case the same way as the safe integer range
|
||||
// overflow case so we don't need special handling for the values in
|
||||
// (kMaxUInt32, kMaxSafeInteger] range.
|
||||
if (indexDouble > kMaxUInt32Double) goto IfRangeError;
|
||||
}
|
||||
return ChangeFloat64ToUintPtr(indexDouble);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transitioning macro GetLengthProperty(implicit context: Context)(o: JSAny):
|
||||
@ -3694,7 +3724,7 @@ macro ConvertToRelativeIndex(indexNumber: Number, length: uintptr): uintptr {
|
||||
const indexDouble: float64 = Convert<float64>(indexHeapNumber);
|
||||
// NaNs must already be handled by ConvertToRelativeIndex() version
|
||||
// above accepting JSAny indices.
|
||||
assert(indexDouble == indexDouble);
|
||||
assert(!Float64IsNaN(indexDouble));
|
||||
const lengthDouble: float64 = Convert<float64>(length);
|
||||
assert(lengthDouble <= kMaxSafeInteger);
|
||||
if (indexDouble < 0) {
|
||||
|
@ -351,44 +351,63 @@ namespace data_view {
|
||||
return MakeBigInt(lowWord, highWord, signed);
|
||||
}
|
||||
|
||||
extern macro ToSmiIndex(JSAny, Context): Smi
|
||||
labels RangeError;
|
||||
extern macro DataViewBuiltinsAssembler::DataViewElementSize(
|
||||
constexpr ElementsKind): constexpr int31;
|
||||
|
||||
// GetViewValue ( view, requestIndex, isLittleEndian, type )
|
||||
// https://tc39.es/ecma262/#sec-getviewvalue
|
||||
transitioning macro DataViewGet(
|
||||
context: Context, receiver: JSAny, offset: JSAny,
|
||||
context: Context, receiver: JSAny, requestIndex: JSAny,
|
||||
requestedLittleEndian: JSAny, kind: constexpr ElementsKind): Numeric {
|
||||
// 1. Perform ? RequireInternalSlot(view, [[DataView]]).
|
||||
// 2. Assert: view has a [[ViewedArrayBuffer]] internal slot.
|
||||
const dataView: JSDataView =
|
||||
ValidateDataView(context, receiver, MakeDataViewGetterNameString(kind));
|
||||
|
||||
let getIndex: Number;
|
||||
try {
|
||||
getIndex = ToIndex(offset, context) otherwise RangeError;
|
||||
}
|
||||
label RangeError {
|
||||
ThrowRangeError(kInvalidDataViewAccessorOffset);
|
||||
}
|
||||
// 3. Let getIndex be ? ToIndex(requestIndex).
|
||||
const getIndex: uintptr = ToIndex(requestIndex) otherwise RangeError;
|
||||
|
||||
// 4. Set isLittleEndian to ! ToBoolean(isLittleEndian).
|
||||
const littleEndian: bool = ToBoolean(requestedLittleEndian);
|
||||
|
||||
// 5. Let buffer be view.[[ViewedArrayBuffer]].
|
||||
const buffer: JSArrayBuffer = dataView.buffer;
|
||||
|
||||
// 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
|
||||
if (IsDetachedBuffer(buffer)) {
|
||||
ThrowTypeError(kDetachedOperation, MakeDataViewGetterNameString(kind));
|
||||
}
|
||||
|
||||
const getIndexFloat: float64 = Convert<float64>(getIndex);
|
||||
const getIndexWord: uintptr = Convert<uintptr>(getIndexFloat);
|
||||
// 7. Let viewOffset be view.[[ByteOffset]].
|
||||
const viewOffset: uintptr = dataView.byte_offset;
|
||||
|
||||
const viewOffsetWord: uintptr = dataView.byte_offset;
|
||||
const viewSizeFloat: float64 = Convert<float64>(dataView.byte_length);
|
||||
const elementSizeFloat: float64 = DataViewElementSize(kind);
|
||||
// 8. Let viewSize be view.[[ByteLength]].
|
||||
const viewSize: uintptr = dataView.byte_length;
|
||||
|
||||
if (getIndexFloat + elementSizeFloat > viewSizeFloat) {
|
||||
ThrowRangeError(kInvalidDataViewAccessorOffset);
|
||||
// 9. Let elementSize be the Element Size value specified in Table 62
|
||||
// for Element Type type.
|
||||
const elementSize: uintptr = DataViewElementSize(kind);
|
||||
|
||||
// 10. If getIndex + elementSize > viewSize, throw a RangeError exception.
|
||||
if constexpr (Is64()) {
|
||||
// Given that
|
||||
// a) getIndex is in [0, kMaxSafeInteger] range
|
||||
// b) elementSize is in [1, 8] range
|
||||
// the addition can't overflow.
|
||||
if (getIndex + elementSize > viewSize) goto RangeError;
|
||||
} else {
|
||||
// In order to avoid operating on float64s we deal with uintptr values
|
||||
// and do two comparisons to handle potential uintptr overflow on
|
||||
// 32-bit architectures.
|
||||
const lastPossibleElementOffset: uintptr = viewSize - elementSize;
|
||||
// Check if lastPossibleElementOffset underflowed.
|
||||
if (lastPossibleElementOffset > viewSize) goto RangeError;
|
||||
if (getIndex > lastPossibleElementOffset) goto RangeError;
|
||||
}
|
||||
|
||||
const bufferIndex: uintptr = getIndexWord + viewOffsetWord;
|
||||
// 11. Let bufferIndex be getIndex + viewOffset.
|
||||
const bufferIndex: uintptr = getIndex + viewOffset;
|
||||
|
||||
if constexpr (kind == UINT8_ELEMENTS) {
|
||||
return LoadDataView8(buffer, bufferIndex, false);
|
||||
@ -414,6 +433,10 @@ namespace data_view {
|
||||
unreachable;
|
||||
}
|
||||
}
|
||||
label RangeError {
|
||||
ThrowRangeError(kInvalidDataViewAccessorOffset);
|
||||
}
|
||||
}
|
||||
|
||||
transitioning javascript builtin DataViewPrototypeGetUint8(
|
||||
js-implicit context: Context, receiver: JSAny)(...arguments): JSAny {
|
||||
@ -631,65 +654,75 @@ namespace data_view {
|
||||
StoreDataView64(buffer, offset, lowWord, highWord, requestedLittleEndian);
|
||||
}
|
||||
|
||||
// SetViewValue ( view, requestIndex, isLittleEndian, type, value )
|
||||
// https://tc39.es/ecma262/#sec-setviewvalue
|
||||
transitioning macro DataViewSet(
|
||||
context: Context, receiver: JSAny, offset: JSAny, value: JSAny,
|
||||
context: Context, receiver: JSAny, requestIndex: JSAny, value: JSAny,
|
||||
requestedLittleEndian: JSAny, kind: constexpr ElementsKind): JSAny {
|
||||
// 1. Perform ? RequireInternalSlot(view, [[DataView]]).
|
||||
// 2. Assert: view has a [[ViewedArrayBuffer]] internal slot.
|
||||
const dataView: JSDataView =
|
||||
ValidateDataView(context, receiver, MakeDataViewSetterNameString(kind));
|
||||
|
||||
let getIndex: Number;
|
||||
try {
|
||||
getIndex = ToIndex(offset, context) otherwise RangeError;
|
||||
}
|
||||
label RangeError {
|
||||
ThrowRangeError(kInvalidDataViewAccessorOffset);
|
||||
}
|
||||
// 3. Let getIndex be ? ToIndex(requestIndex).
|
||||
const getIndex: uintptr = ToIndex(requestIndex) otherwise RangeError;
|
||||
|
||||
const littleEndian: bool = ToBoolean(requestedLittleEndian);
|
||||
const buffer: JSArrayBuffer = dataView.buffer;
|
||||
|
||||
// According to ES6 section 24.2.1.2 SetViewValue, we must perform
|
||||
// the conversion before doing the bounds check.
|
||||
let numberValue: Numeric;
|
||||
if constexpr (kind == BIGUINT64_ELEMENTS || kind == BIGINT64_ELEMENTS) {
|
||||
const bigIntValue: BigInt = ToBigInt(context, value);
|
||||
// 4. If ! IsBigIntElementType(type) is true, let numberValue be
|
||||
// ? ToBigInt(value).
|
||||
numberValue = ToBigInt(context, value);
|
||||
} else {
|
||||
// 5. Otherwise, let numberValue be ? ToNumber(value).
|
||||
numberValue = ToNumber(context, value);
|
||||
}
|
||||
|
||||
// 6. If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
|
||||
if (IsDetachedBuffer(buffer)) {
|
||||
ThrowTypeError(kDetachedOperation, MakeDataViewSetterNameString(kind));
|
||||
}
|
||||
|
||||
const getIndexFloat: float64 = Convert<float64>(getIndex);
|
||||
const getIndexWord: uintptr = Convert<uintptr>(getIndexFloat);
|
||||
// 9. Let viewOffset be view.[[ByteOffset]].
|
||||
const viewOffset: uintptr = dataView.byte_offset;
|
||||
|
||||
const viewOffsetWord: uintptr = dataView.byte_offset;
|
||||
const viewSizeFloat: float64 = Convert<float64>(dataView.byte_length);
|
||||
const elementSizeFloat: float64 = DataViewElementSize(kind);
|
||||
// 10. Let viewSize be view.[[ByteLength]].
|
||||
const viewSize: uintptr = dataView.byte_length;
|
||||
|
||||
if (getIndexFloat + elementSizeFloat > viewSizeFloat) {
|
||||
ThrowRangeError(kInvalidDataViewAccessorOffset);
|
||||
// 11. Let elementSize be the Element Size value specified in Table 62
|
||||
// for Element Type type.
|
||||
const elementSize: uintptr = DataViewElementSize(kind);
|
||||
|
||||
// 12. If getIndex + elementSize > viewSize, throw a RangeError exception.
|
||||
if constexpr (Is64()) {
|
||||
// Given that
|
||||
// a) getIndex is in [0, kMaxSafeInteger] range
|
||||
// b) elementSize is in [1, 8] range
|
||||
// the addition can't overflow.
|
||||
if (getIndex + elementSize > viewSize) goto RangeError;
|
||||
} else {
|
||||
// In order to avoid operating on float64s we deal with uintptr values
|
||||
// and do two comparisons to handle potential uintptr overflow on
|
||||
// 32-bit architectures.
|
||||
const lastPossibleElementOffset: uintptr = viewSize - elementSize;
|
||||
// Check if lastPossibleElementOffset underflowed.
|
||||
if (lastPossibleElementOffset > viewSize) goto RangeError;
|
||||
if (getIndex > lastPossibleElementOffset) goto RangeError;
|
||||
}
|
||||
|
||||
const bufferIndex: uintptr = getIndexWord + viewOffsetWord;
|
||||
// 13. Let bufferIndex be getIndex + viewOffset.
|
||||
const bufferIndex: uintptr = getIndex + viewOffset;
|
||||
|
||||
if constexpr (kind == BIGUINT64_ELEMENTS || kind == BIGINT64_ELEMENTS) {
|
||||
// For these elements kinds numberValue is BigInt.
|
||||
const bigIntValue: BigInt = %RawDownCast<BigInt>(numberValue);
|
||||
StoreDataViewBigInt(buffer, bufferIndex, bigIntValue, littleEndian);
|
||||
} else {
|
||||
const numValue: Number = ToNumber(context, value);
|
||||
|
||||
if (IsDetachedBuffer(buffer)) {
|
||||
ThrowTypeError(kDetachedOperation, MakeDataViewSetterNameString(kind));
|
||||
}
|
||||
|
||||
const getIndexFloat: float64 = Convert<float64>(getIndex);
|
||||
const getIndexWord: uintptr = Convert<uintptr>(getIndexFloat);
|
||||
|
||||
const viewOffsetWord: uintptr = dataView.byte_offset;
|
||||
const viewSizeFloat: float64 = Convert<float64>(dataView.byte_length);
|
||||
const elementSizeFloat: float64 = DataViewElementSize(kind);
|
||||
|
||||
if (getIndexFloat + elementSizeFloat > viewSizeFloat) {
|
||||
ThrowRangeError(kInvalidDataViewAccessorOffset);
|
||||
}
|
||||
|
||||
const bufferIndex: uintptr = getIndexWord + viewOffsetWord;
|
||||
|
||||
// For these elements kinds numberValue is Number.
|
||||
const numValue: Number = %RawDownCast<Number>(numberValue);
|
||||
const doubleValue: float64 = ChangeNumberToFloat64(numValue);
|
||||
|
||||
if constexpr (kind == UINT8_ELEMENTS || kind == INT8_ELEMENTS) {
|
||||
@ -716,6 +749,10 @@ namespace data_view {
|
||||
}
|
||||
return Undefined;
|
||||
}
|
||||
label RangeError {
|
||||
ThrowRangeError(kInvalidDataViewAccessorOffset);
|
||||
}
|
||||
}
|
||||
|
||||
transitioning javascript builtin DataViewPrototypeSetUint8(
|
||||
js-implicit context: Context, receiver: JSAny)(...arguments): JSAny {
|
||||
|
@ -212,18 +212,13 @@ namespace typed_array {
|
||||
map: Map, buffer: JSArrayBuffer, byteOffset: JSAny, length: JSAny,
|
||||
elementsInfo: typed_array::TypedArrayElementsInfo): JSTypedArray {
|
||||
try {
|
||||
let offset: uintptr = 0;
|
||||
if (byteOffset != Undefined) {
|
||||
// 6. Let offset be ? ToIndex(byteOffset).
|
||||
offset = TryNumberToUintPtr(
|
||||
ToInteger_Inline(context, byteOffset, kTruncateMinusZero))
|
||||
otherwise goto IfInvalidOffset;
|
||||
const offset: uintptr = ToIndex(byteOffset) otherwise IfInvalidOffset;
|
||||
|
||||
// 7. If offset modulo elementSize ≠ 0, throw a RangeError exception.
|
||||
if (elementsInfo.IsUnaligned(offset)) {
|
||||
goto IfInvalidAlignment('start offset');
|
||||
}
|
||||
}
|
||||
|
||||
let newLength: PositiveSmi = 0;
|
||||
let newByteLength: uintptr;
|
||||
|
@ -2664,6 +2664,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
|
||||
};
|
||||
|
||||
// ES6 7.1.17 ToIndex, but jumps to range_error if the result is not a Smi.
|
||||
// TODO(v8:4153): Use ToIndex() instead.
|
||||
TNode<Smi> ToSmiIndex(TNode<Context> context, TNode<Object> input,
|
||||
Label* range_error);
|
||||
|
||||
|
@ -1043,6 +1043,8 @@ constexpr uint64_t kHoleNanInt64 =
|
||||
// ES6 section 20.1.2.6 Number.MAX_SAFE_INTEGER
|
||||
constexpr double kMaxSafeInteger = 9007199254740991.0; // 2^53-1
|
||||
|
||||
constexpr double kMaxUInt32Double = double{kMaxUInt32};
|
||||
|
||||
// The order of this enum has to be kept in sync with the predicates below.
|
||||
enum class VariableMode : uint8_t {
|
||||
// User declared variables:
|
||||
|
Loading…
Reference in New Issue
Block a user