diff --git a/src/builtins/base.tq b/src/builtins/base.tq index 05db520dfe..931b104627 100644 --- a/src/builtins/base.tq +++ b/src/builtins/base.tq @@ -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(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(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(indexHeapNumber); // NaNs must already be handled by ConvertToRelativeIndex() version // above accepting JSAny indices. - assert(indexDouble == indexDouble); + assert(!Float64IsNaN(indexDouble)); const lengthDouble: float64 = Convert(length); assert(lengthDouble <= kMaxSafeInteger); if (indexDouble < 0) { diff --git a/src/builtins/data-view.tq b/src/builtins/data-view.tq index 2cf0609561..f249154068 100644 --- a/src/builtins/data-view.tq +++ b/src/builtins/data-view.tq @@ -351,68 +351,91 @@ 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; + // 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)); + } + + // 7. Let viewOffset be view.[[ByteOffset]]. + const viewOffset: uintptr = dataView.byte_offset; + + // 8. Let viewSize be view.[[ByteLength]]. + const viewSize: uintptr = dataView.byte_length; + + // 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; + } + + // 11. Let bufferIndex be getIndex + viewOffset. + const bufferIndex: uintptr = getIndex + viewOffset; + + if constexpr (kind == UINT8_ELEMENTS) { + return LoadDataView8(buffer, bufferIndex, false); + } else if constexpr (kind == INT8_ELEMENTS) { + return LoadDataView8(buffer, bufferIndex, true); + } else if constexpr (kind == UINT16_ELEMENTS) { + return LoadDataView16(buffer, bufferIndex, littleEndian, false); + } else if constexpr (kind == INT16_ELEMENTS) { + return LoadDataView16(buffer, bufferIndex, littleEndian, true); + } else if constexpr (kind == UINT32_ELEMENTS) { + return LoadDataView32(buffer, bufferIndex, littleEndian, kind); + } else if constexpr (kind == INT32_ELEMENTS) { + return LoadDataView32(buffer, bufferIndex, littleEndian, kind); + } else if constexpr (kind == FLOAT32_ELEMENTS) { + return LoadDataView32(buffer, bufferIndex, littleEndian, kind); + } else if constexpr (kind == FLOAT64_ELEMENTS) { + return LoadDataViewFloat64(buffer, bufferIndex, littleEndian); + } else if constexpr (kind == BIGUINT64_ELEMENTS) { + return LoadDataViewBigInt(buffer, bufferIndex, littleEndian, false); + } else if constexpr (kind == BIGINT64_ELEMENTS) { + return LoadDataViewBigInt(buffer, bufferIndex, littleEndian, true); + } else { + unreachable; + } } label RangeError { ThrowRangeError(kInvalidDataViewAccessorOffset); } - - const littleEndian: bool = ToBoolean(requestedLittleEndian); - const buffer: JSArrayBuffer = dataView.buffer; - - if (IsDetachedBuffer(buffer)) { - ThrowTypeError(kDetachedOperation, MakeDataViewGetterNameString(kind)); - } - - const getIndexFloat: float64 = Convert(getIndex); - const getIndexWord: uintptr = Convert(getIndexFloat); - - const viewOffsetWord: uintptr = dataView.byte_offset; - const viewSizeFloat: float64 = Convert(dataView.byte_length); - const elementSizeFloat: float64 = DataViewElementSize(kind); - - if (getIndexFloat + elementSizeFloat > viewSizeFloat) { - ThrowRangeError(kInvalidDataViewAccessorOffset); - } - - const bufferIndex: uintptr = getIndexWord + viewOffsetWord; - - if constexpr (kind == UINT8_ELEMENTS) { - return LoadDataView8(buffer, bufferIndex, false); - } else if constexpr (kind == INT8_ELEMENTS) { - return LoadDataView8(buffer, bufferIndex, true); - } else if constexpr (kind == UINT16_ELEMENTS) { - return LoadDataView16(buffer, bufferIndex, littleEndian, false); - } else if constexpr (kind == INT16_ELEMENTS) { - return LoadDataView16(buffer, bufferIndex, littleEndian, true); - } else if constexpr (kind == UINT32_ELEMENTS) { - return LoadDataView32(buffer, bufferIndex, littleEndian, kind); - } else if constexpr (kind == INT32_ELEMENTS) { - return LoadDataView32(buffer, bufferIndex, littleEndian, kind); - } else if constexpr (kind == FLOAT32_ELEMENTS) { - return LoadDataView32(buffer, bufferIndex, littleEndian, kind); - } else if constexpr (kind == FLOAT64_ELEMENTS) { - return LoadDataViewFloat64(buffer, bufferIndex, littleEndian); - } else if constexpr (kind == BIGUINT64_ELEMENTS) { - return LoadDataViewBigInt(buffer, bufferIndex, littleEndian, false); - } else if constexpr (kind == BIGINT64_ELEMENTS) { - return LoadDataViewBigInt(buffer, bufferIndex, littleEndian, true); - } else { - unreachable; - } } transitioning javascript builtin DataViewPrototypeGetUint8( @@ -631,90 +654,104 @@ 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; + // 3. Let getIndex be ? ToIndex(requestIndex). + const getIndex: uintptr = ToIndex(requestIndex) otherwise RangeError; + + const littleEndian: bool = ToBoolean(requestedLittleEndian); + const buffer: JSArrayBuffer = dataView.buffer; + + let numberValue: Numeric; + if constexpr (kind == BIGUINT64_ELEMENTS || kind == BIGINT64_ELEMENTS) { + // 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)); + } + + // 9. Let viewOffset be view.[[ByteOffset]]. + const viewOffset: uintptr = dataView.byte_offset; + + // 10. Let viewSize be view.[[ByteLength]]. + const viewSize: uintptr = dataView.byte_length; + + // 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; + } + + // 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(numberValue); + StoreDataViewBigInt(buffer, bufferIndex, bigIntValue, littleEndian); + } else { + // For these elements kinds numberValue is Number. + const numValue: Number = %RawDownCast(numberValue); + const doubleValue: float64 = ChangeNumberToFloat64(numValue); + + if constexpr (kind == UINT8_ELEMENTS || kind == INT8_ELEMENTS) { + StoreDataView8( + buffer, bufferIndex, TruncateFloat64ToWord32(doubleValue)); + } else if constexpr (kind == UINT16_ELEMENTS || kind == INT16_ELEMENTS) { + StoreDataView16( + buffer, bufferIndex, TruncateFloat64ToWord32(doubleValue), + littleEndian); + } else if constexpr (kind == UINT32_ELEMENTS || kind == INT32_ELEMENTS) { + StoreDataView32( + buffer, bufferIndex, TruncateFloat64ToWord32(doubleValue), + littleEndian); + } else if constexpr (kind == FLOAT32_ELEMENTS) { + const floatValue: float32 = TruncateFloat64ToFloat32(doubleValue); + StoreDataView32( + buffer, bufferIndex, BitcastFloat32ToInt32(floatValue), + littleEndian); + } else if constexpr (kind == FLOAT64_ELEMENTS) { + const lowWord: uint32 = Float64ExtractLowWord32(doubleValue); + const highWord: uint32 = Float64ExtractHighWord32(doubleValue); + StoreDataView64(buffer, bufferIndex, lowWord, highWord, littleEndian); + } + } + return Undefined; } label RangeError { ThrowRangeError(kInvalidDataViewAccessorOffset); } - - 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. - if constexpr (kind == BIGUINT64_ELEMENTS || kind == BIGINT64_ELEMENTS) { - const bigIntValue: BigInt = ToBigInt(context, value); - - if (IsDetachedBuffer(buffer)) { - ThrowTypeError(kDetachedOperation, MakeDataViewSetterNameString(kind)); - } - - const getIndexFloat: float64 = Convert(getIndex); - const getIndexWord: uintptr = Convert(getIndexFloat); - - const viewOffsetWord: uintptr = dataView.byte_offset; - const viewSizeFloat: float64 = Convert(dataView.byte_length); - const elementSizeFloat: float64 = DataViewElementSize(kind); - - if (getIndexFloat + elementSizeFloat > viewSizeFloat) { - ThrowRangeError(kInvalidDataViewAccessorOffset); - } - - const bufferIndex: uintptr = getIndexWord + viewOffsetWord; - StoreDataViewBigInt(buffer, bufferIndex, bigIntValue, littleEndian); - } else { - const numValue: Number = ToNumber(context, value); - - if (IsDetachedBuffer(buffer)) { - ThrowTypeError(kDetachedOperation, MakeDataViewSetterNameString(kind)); - } - - const getIndexFloat: float64 = Convert(getIndex); - const getIndexWord: uintptr = Convert(getIndexFloat); - - const viewOffsetWord: uintptr = dataView.byte_offset; - const viewSizeFloat: float64 = Convert(dataView.byte_length); - const elementSizeFloat: float64 = DataViewElementSize(kind); - - if (getIndexFloat + elementSizeFloat > viewSizeFloat) { - ThrowRangeError(kInvalidDataViewAccessorOffset); - } - - const bufferIndex: uintptr = getIndexWord + viewOffsetWord; - - const doubleValue: float64 = ChangeNumberToFloat64(numValue); - - if constexpr (kind == UINT8_ELEMENTS || kind == INT8_ELEMENTS) { - StoreDataView8( - buffer, bufferIndex, TruncateFloat64ToWord32(doubleValue)); - } else if constexpr (kind == UINT16_ELEMENTS || kind == INT16_ELEMENTS) { - StoreDataView16( - buffer, bufferIndex, TruncateFloat64ToWord32(doubleValue), - littleEndian); - } else if constexpr (kind == UINT32_ELEMENTS || kind == INT32_ELEMENTS) { - StoreDataView32( - buffer, bufferIndex, TruncateFloat64ToWord32(doubleValue), - littleEndian); - } else if constexpr (kind == FLOAT32_ELEMENTS) { - const floatValue: float32 = TruncateFloat64ToFloat32(doubleValue); - StoreDataView32( - buffer, bufferIndex, BitcastFloat32ToInt32(floatValue), - littleEndian); - } else if constexpr (kind == FLOAT64_ELEMENTS) { - const lowWord: uint32 = Float64ExtractLowWord32(doubleValue); - const highWord: uint32 = Float64ExtractHighWord32(doubleValue); - StoreDataView64(buffer, bufferIndex, lowWord, highWord, littleEndian); - } - } - return Undefined; } transitioning javascript builtin DataViewPrototypeSetUint8( diff --git a/src/builtins/typed-array-createtypedarray.tq b/src/builtins/typed-array-createtypedarray.tq index b752cb1b36..57f6b10160 100644 --- a/src/builtins/typed-array-createtypedarray.tq +++ b/src/builtins/typed-array-createtypedarray.tq @@ -212,17 +212,12 @@ 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; + // 6. Let offset be ? ToIndex(byteOffset). + 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'); - } + // 7. If offset modulo elementSize ≠ 0, throw a RangeError exception. + if (elementsInfo.IsUnaligned(offset)) { + goto IfInvalidAlignment('start offset'); } let newLength: PositiveSmi = 0; diff --git a/src/codegen/code-stub-assembler.h b/src/codegen/code-stub-assembler.h index 1abf396d52..59f7e00858 100644 --- a/src/codegen/code-stub-assembler.h +++ b/src/codegen/code-stub-assembler.h @@ -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 ToSmiIndex(TNode context, TNode input, Label* range_error); diff --git a/src/common/globals.h b/src/common/globals.h index 291c8fc859..740fec2bdd 100644 --- a/src/common/globals.h +++ b/src/common/globals.h @@ -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: