[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:
Igor Sheludko 2019-10-23 12:06:40 +02:00 committed by Commit Bot
parent 1e256fc3f6
commit dec3de8a70
5 changed files with 209 additions and 144 deletions

View File

@ -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) {

View File

@ -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<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;
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<BigInt>(numberValue);
StoreDataViewBigInt(buffer, bufferIndex, bigIntValue, littleEndian);
} else {
// 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) {
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<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;
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;
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(

View File

@ -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;

View File

@ -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);

View File

@ -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: