Commit c9ef0405 authored by peterwmwong's avatar peterwmwong Committed by Commit Bot

[builtins]: Optimize CreateTypedArray to use element size log 2 for calculations.

TypedArrayElementsInfo now represents an element's size as a log 2 and typed as
uintptr.  This simplifies and speeds up (avoids possible HeapNumber allocations) a
number of calculations:

  - Number of Elements (length) -> Byte Length - is now a WordShl
  - Byte Length -> Number of Elements (length) - is now a WordShr
  - Testing alignment (byte offset or length)  - is now a WordAnd

These element/byte length related calculations are encapsulated in
TypedArrayElementsInfo as struct methods.

This reduces the size of CreateTypedArray by 2.125 KB (24%) on Mac x64.release:
  - Before: 9,088
  - After:  6,896

This improves the performance of the following microbencmarks
  - TypedArrays-ConstructWithBuffer: ~87%
  - TypedArrays-SubarrayNoSpecies:   ~28%

Bug: v8:7161
Change-Id: I2239fd0e0af9d3ad55cd52318088d3c7c913ae44
Reviewed-on: https://chromium-review.googlesource.com/c/1456299
Commit-Queue: Peter Wong <peter.wm.wong@gmail.com>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarSimon Zünd <szuend@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59531}
parent ad5b6d7d
......@@ -340,6 +340,7 @@ const kTypedArrayMaxByteLength:
const V8_TYPED_ARRAY_MAX_SIZE_IN_HEAP:
constexpr int31 generates 'V8_TYPED_ARRAY_MAX_SIZE_IN_HEAP';
const kMaxSafeInteger: constexpr float64 generates 'kMaxSafeInteger';
const kSmiMaxValue: constexpr uintptr generates 'kSmiMaxValue';
const kStringMaxLength: constexpr int31 generates 'String::kMaxLength';
const kFixedArrayMaxLength:
constexpr int31 generates 'FixedArray::kMaxLength';
......@@ -594,10 +595,6 @@ extern operator '>=' macro BranchIfNumberGreaterThanOrEqual(
Number, Number): never
labels Taken, NotTaken;
extern builtin Divide(implicit context: Context)(Object, Object): Numeric;
extern builtin Modulus(implicit context: Context)(Object, Object): Numeric;
extern builtin Subtract(implicit context: Context)(Object, Object): Numeric;
// The type of all tagged values that can safely be compared with WordEqual.
type TaggedWithIdentity =
JSReceiver | FixedArrayBase | Oddball | Map | EmptyString;
......@@ -637,6 +634,7 @@ extern operator '|' macro WordOr(intptr, intptr): intptr;
extern operator '+' macro UintPtrAdd(uintptr, uintptr): uintptr;
extern operator '-' macro UintPtrSub(uintptr, uintptr): uintptr;
extern operator '<<' macro WordShl(uintptr, uintptr): uintptr;
extern operator '>>>' macro WordShr(uintptr, uintptr): uintptr;
extern operator '&' macro WordAnd(uintptr, uintptr): uintptr;
extern operator '|' macro WordOr(uintptr, uintptr): uintptr;
......@@ -988,6 +986,11 @@ extern macro LoadNativeContext(Context): NativeContext;
extern macro LoadJSArrayElementsMap(constexpr ElementsKind, Context): Map;
extern macro LoadJSArrayElementsMap(ElementsKind, Context): Map;
extern macro ChangeNonnegativeNumberToUintPtr(Number): uintptr;
extern macro TryNumberToUintPtr(Number): uintptr labels IfNegative;
macro TryUintPtrToPositiveSmi(ui: uintptr): PositiveSmi labels IfOverflow {
if (ui > kSmiMaxValue) goto IfOverflow;
return %RawDownCast<PositiveSmi>(SmiTag(Signed(ui)));
}
extern macro NumberConstant(constexpr float64): Number;
extern macro NumberConstant(constexpr int32): Number;
......@@ -1138,6 +1141,9 @@ Convert<uint32, uintptr>(ui: uintptr): uint32 {
Convert<intptr, Smi>(s: Smi): intptr {
return SmiUntag(s);
}
Convert<uintptr, PositiveSmi>(ps: PositiveSmi): uintptr {
return Unsigned(SmiUntag(ps));
}
Convert<intptr, uintptr>(ui: uintptr): intptr {
const i = Signed(ui);
assert(i >= 0);
......
......@@ -44,6 +44,15 @@ TNode<Map> TypedArrayBuiltinsAssembler::LoadMapForType(
return var_typed_map.value();
}
TNode<BoolT> TypedArrayBuiltinsAssembler::IsMockArrayBufferAllocatorFlag() {
TNode<Word32T> flag_value = UncheckedCast<Word32T>(Load(
MachineType::Uint8(),
ExternalConstant(
ExternalReference::address_of_mock_arraybuffer_allocator_flag())));
return Word32NotEqual(Word32And(flag_value, Int32Constant(0xFF)),
Int32Constant(0));
}
// The byte_offset can be higher than Smi range, in which case to perform the
// pointer arithmetic necessary to calculate external_pointer, converting
// byte_offset to an intptr is more difficult. The max byte_offset is 8 * MaxSmi
......@@ -52,9 +61,22 @@ TNode<Map> TypedArrayBuiltinsAssembler::LoadMapForType(
// bit platforms could theoretically have an offset up to 2^35 - 1, so we may
// need to convert the float heap number to an intptr.
TNode<UintPtrT> TypedArrayBuiltinsAssembler::CalculateExternalPointer(
TNode<UintPtrT> backing_store, TNode<Number> byte_offset) {
return Unsigned(
IntPtrAdd(backing_store, ChangeNonnegativeNumberToUintPtr(byte_offset)));
TNode<UintPtrT> backing_store, TNode<UintPtrT> byte_offset) {
TNode<UintPtrT> external_pointer = UintPtrAdd(backing_store, byte_offset);
#ifdef DEBUG
// Assert no overflow has occurred. Only assert if the mock array buffer
// allocator is NOT used. When the mock array buffer is used, impossibly
// large allocations are allowed that would erroneously cause an overflow and
// this assertion to fail.
Label next(this);
GotoIf(IsMockArrayBufferAllocatorFlag(), &next);
CSA_ASSERT(this, UintPtrGreaterThanOrEqual(external_pointer, backing_store));
Goto(&next);
BIND(&next);
#endif // DEBUG
return external_pointer;
}
// Setup the TypedArray which is under construction.
......@@ -85,7 +107,7 @@ void TypedArrayBuiltinsAssembler::AttachBuffer(TNode<JSTypedArray> holder,
TNode<JSArrayBuffer> buffer,
TNode<Map> map,
TNode<Smi> length,
TNode<Number> byte_offset) {
TNode<UintPtrT> byte_offset) {
CSA_ASSERT(this, TaggedIsPositiveSmi(length));
StoreObjectField(holder, JSArrayBufferView::kBufferOffset, buffer);
......@@ -195,29 +217,6 @@ Node* TypedArrayBuiltinsAssembler::LoadDataPtr(Node* typed_array) {
return LoadFixedTypedArrayBackingStore(CAST(elements));
}
TNode<BoolT> TypedArrayBuiltinsAssembler::ByteLengthIsValid(
TNode<Number> byte_length) {
Label smi(this), done(this);
TVARIABLE(BoolT, is_valid);
GotoIf(TaggedIsSmi(byte_length), &smi);
TNode<Float64T> float_value = LoadHeapNumberValue(CAST(byte_length));
TNode<Float64T> max_byte_length_double =
Float64Constant(FixedTypedArrayBase::kMaxByteLength);
is_valid = Float64LessThanOrEqual(float_value, max_byte_length_double);
Goto(&done);
BIND(&smi);
TNode<IntPtrT> max_byte_length =
IntPtrConstant(FixedTypedArrayBase::kMaxByteLength);
is_valid =
UintPtrLessThanOrEqual(SmiUntag(CAST(byte_length)), max_byte_length);
Goto(&done);
BIND(&done);
return is_valid.value();
}
TF_BUILTIN(TypedArrayBaseConstructor, TypedArrayBuiltinsAssembler) {
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
ThrowTypeError(context, MessageTemplate::kConstructAbstractClass,
......@@ -337,7 +336,7 @@ TypedArrayBuiltinsFromDSLAssembler::TypedArrayElementsInfo
TypedArrayBuiltinsAssembler::GetTypedArrayElementsInfo(
TNode<JSTypedArray> typed_array) {
TNode<Int32T> elements_kind = LoadElementsKind(typed_array);
TVARIABLE(Smi, var_element_size);
TVARIABLE(UintPtrT, var_size_log2);
TVARIABLE(Map, var_map);
ReadOnlyRoots roots(isolate());
......@@ -345,14 +344,14 @@ TypedArrayBuiltinsAssembler::GetTypedArrayElementsInfo(
elements_kind,
[&](ElementsKind kind, int size, int typed_array_fun_index) {
DCHECK_GT(size, 0);
var_element_size = SmiConstant(size);
var_size_log2 = UintPtrConstant(ElementsKindToShiftSize(kind));
Handle<Map> map(roots.MapForFixedTypedArray(kind), isolate());
var_map = HeapConstant(map);
});
return TypedArrayBuiltinsFromDSLAssembler::TypedArrayElementsInfo{
var_element_size.value(), var_map.value(), elements_kind};
var_size_log2.value(), var_map.value(), elements_kind};
}
TNode<JSFunction> TypedArrayBuiltinsAssembler::GetDefaultConstructor(
......
......@@ -38,7 +38,7 @@ class TypedArrayBuiltinsAssembler : public CodeStubAssembler {
TNode<UintPtrT> byte_length);
void AttachBuffer(TNode<JSTypedArray> holder, TNode<JSArrayBuffer> buffer,
TNode<Map> map, TNode<Smi> length,
TNode<Number> byte_offset);
TNode<UintPtrT> byte_offset);
TNode<JSArrayBuffer> AllocateEmptyOnHeapBuffer(TNode<Context> context,
TNode<JSTypedArray> holder,
......@@ -49,10 +49,10 @@ class TypedArrayBuiltinsAssembler : public CodeStubAssembler {
TNode<Number> length);
TNode<Map> LoadMapForType(TNode<JSTypedArray> array);
TNode<BoolT> IsMockArrayBufferAllocatorFlag();
TNode<UintPtrT> CalculateExternalPointer(TNode<UintPtrT> backing_store,
TNode<Number> byte_offset);
TNode<UintPtrT> byte_offset);
Node* LoadDataPtr(Node* typed_array);
TNode<BoolT> ByteLengthIsValid(TNode<Number> byte_length);
// Returns true if kind is either UINT8_ELEMENTS or UINT8_CLAMPED_ELEMENTS.
TNode<Word32T> IsUint8ElementsKind(TNode<Word32T> kind);
......
......@@ -14,15 +14,12 @@ namespace typed_array_createtypedarray {
implicit context: Context)(JSTypedArray, uintptr): JSArrayBuffer;
extern macro TypedArrayBuiltinsAssembler::AllocateOnHeapElements(
Map, intptr, Number): FixedTypedArrayBase;
extern macro TypedArrayBuiltinsAssembler::ByteLengthIsValid(Number): bool;
extern macro TypedArrayBuiltinsAssembler::GetTypedArrayElementsInfo(
ElementsKind): typed_array::TypedArrayElementsInfo;
extern macro TypedArrayBuiltinsAssembler::IsSharedArrayBuffer(JSArrayBuffer):
bool;
extern macro TypedArrayBuiltinsAssembler::SetupTypedArray(
JSTypedArray, Smi, uintptr, uintptr): void;
extern macro TypedArrayBuiltinsAssembler::AttachBuffer(
JSTypedArray, JSArrayBuffer, Map, Smi, Number): void;
JSTypedArray, JSArrayBuffer, Map, Smi, uintptr): void;
extern runtime ThrowInvalidTypedArrayAlignment(implicit context: Context)(
Map, String): never;
......@@ -39,10 +36,8 @@ namespace typed_array_createtypedarray {
initialize: constexpr bool, typedArray: JSTypedArray, length: PositiveSmi,
elementsInfo: typed_array::TypedArrayElementsInfo,
bufferConstructor: JSReceiver): Object {
assert(Is<PositiveSmi>(length));
const byteLengthNum = SmiMul(length, elementsInfo.size);
const byteLength = Convert<uintptr>(byteLengthNum);
const byteLength = elementsInfo.CalculateByteLength(length);
const byteLengthNum = Convert<Number>(byteLength);
const defaultConstructor = GetArrayBufferFunction();
try {
......@@ -76,7 +71,7 @@ namespace typed_array_createtypedarray {
}
label AttachBuffer(bufferObj: Object) {
const buffer = Cast<JSArrayBuffer>(bufferObj) otherwise unreachable;
const byteOffset: Smi = 0;
const byteOffset: uintptr = 0;
AttachBuffer(typedArray, buffer, elementsInfo.map, length, byteOffset);
}
......@@ -86,18 +81,6 @@ namespace typed_array_createtypedarray {
return Undefined;
}
macro TypedArrayInitializeWithBuffer(
typedArray: JSTypedArray, length: PositiveSmi, buffer: JSArrayBuffer,
elementsInfo: typed_array::TypedArrayElementsInfo, byteOffset: Number) {
const byteLength: Number = SmiMul(length, elementsInfo.size);
SetupTypedArray(
typedArray, length, Convert<uintptr>(byteOffset),
Convert<uintptr>(byteLength));
AttachBuffer(typedArray, buffer, elementsInfo.map, length, byteOffset);
}
// 22.2.4.2 TypedArray ( length )
// ES #sec-typedarray-length
transitioning macro ConstructByLength(implicit context: Context)(
......@@ -141,10 +124,9 @@ namespace typed_array_createtypedarray {
goto IfSlow;
} else if (length > 0) {
const byteLength: Number = SmiMul(length, elementsInfo.size);
assert(ByteLengthIsValid(byteLength));
typed_array::CallCMemcpy(
typedArray.data_ptr, src.data_ptr, Convert<uintptr>(byteLength));
const byteLength = elementsInfo.CalculateByteLength(length);
assert(byteLength <= kTypedArrayMaxByteLength);
typed_array::CallCMemcpy(typedArray.data_ptr, src.data_ptr, byteLength);
}
}
label IfSlow deferred {
......@@ -187,36 +169,27 @@ namespace typed_array_createtypedarray {
goto IfConstructByArrayLike(srcTypedArray, length, bufferConstructor);
}
// Determines if `bytes` (byte offset or length) cannot be evenly divded by
// element size.
macro IsUnaligned(implicit context: Context)(bytes: Number, elementSize: Smi):
bool {
const kZero: Smi = 0;
if (bytes == kZero) return false;
const remainder: Number =
Cast<Number>(Modulus(bytes, elementSize)) otherwise unreachable;
return remainder != kZero;
}
// 22.2.4.5 TypedArray ( buffer, byteOffset, length )
// ES #sec-typedarray-buffer-byteoffset-length
macro ConstructByArrayBuffer(implicit context: Context)(
typedArray: JSTypedArray, buffer: JSArrayBuffer, byteOffset: Object,
length: Object, elementsInfo: typed_array::TypedArrayElementsInfo): void {
try {
let offset: Number = FromConstexpr<Smi>(0);
let offset: uintptr = 0;
if (byteOffset != Undefined) {
// 6. Let offset be ? ToIndex(byteOffset).
offset = ToInteger_Inline(context, byteOffset, kTruncateMinusZero);
if (offset < 0) goto IfInvalidOffset;
offset = TryNumberToUintPtr(
ToInteger_Inline(context, byteOffset, kTruncateMinusZero))
otherwise goto IfInvalidOffset;
// 7. If offset modulo elementSize ≠ 0, throw a RangeError exception.
if (IsUnaligned(offset, elementsInfo.size)) {
if (elementsInfo.IsUnaligned(offset)) {
goto IfInvalidAlignment('start offset');
}
}
let newLength: PositiveSmi = 0;
let newByteLength: uintptr;
// 8. If length is present and length is not undefined, then
if (length != Undefined) {
// a. Let newLength be ? ToIndex(length).
......@@ -229,13 +202,13 @@ namespace typed_array_createtypedarray {
}
// 10. Let bufferByteLength be buffer.[[ArrayBufferByteLength]].
const bufferByteLength: Number = Convert<Number>(buffer.byte_length);
const bufferByteLength: uintptr = buffer.byte_length;
// 11. If length is either not present or undefined, then
if (length == Undefined) {
// a. If bufferByteLength modulo elementSize ≠ 0, throw a RangeError
// exception.
if (IsUnaligned(bufferByteLength, elementsInfo.size)) {
if (elementsInfo.IsUnaligned(bufferByteLength)) {
goto IfInvalidAlignment('byte length');
}
......@@ -245,25 +218,24 @@ namespace typed_array_createtypedarray {
// Spec step 16 length calculated here to avoid recalculating the length
// in the step 12 branch.
newLength = ToSmiIndex(
Divide((Subtract(bufferByteLength, offset)), elementsInfo.size))
otherwise IfInvalidLength;
newByteLength = bufferByteLength - offset;
newLength = elementsInfo.CalculateLength(newByteLength)
otherwise IfInvalidOffset;
// 12. Else,
} else {
// a. Let newByteLength be newLength × elementSize.
const newByteLength: Number = SmiMul(newLength, elementsInfo.size);
newByteLength = elementsInfo.CalculateByteLength(newLength);
// b. If offset + newByteLength > bufferByteLength, throw a RangeError
// exception.
const difference: Number =
Cast<Number>(Subtract(bufferByteLength, newByteLength))
otherwise unreachable;
if (difference < offset) goto IfInvalidLength;
if ((bufferByteLength < newByteLength) ||
(offset > bufferByteLength - newByteLength))
goto IfInvalidLength;
}
TypedArrayInitializeWithBuffer(
typedArray, newLength, buffer, elementsInfo, offset);
SetupTypedArray(typedArray, newLength, offset, newByteLength);
AttachBuffer(typedArray, buffer, elementsInfo.map, newLength, offset);
}
label IfInvalidAlignment(problemString: String) deferred {
ThrowInvalidTypedArrayAlignment(typedArray.map, problemString);
......
......@@ -6,11 +6,38 @@
namespace typed_array {
struct TypedArrayElementsInfo {
size: PositiveSmi;
// Calculates the number of bytes required for specified number of elements.
CalculateByteLength(length: uintptr): uintptr {
const byteLength = length << this.sizeLog2;
// Verify no overflow has ocurred.
assert(byteLength >>> this.sizeLog2 == length);
return byteLength;
}
CalculateByteLength(length: PositiveSmi): uintptr {
return this.CalculateByteLength(Convert<uintptr>(length));
}
// Calculates the maximum number of elements supported by a specified number
// of bytes.
CalculateLength(byteLength: uintptr): PositiveSmi labels IfInvalid {
return TryUintPtrToPositiveSmi(byteLength >>> this.sizeLog2)
otherwise IfInvalid;
}
// Determines if `bytes` (byte offset or length) cannot be evenly divided by
// element size.
IsUnaligned(bytes: uintptr): bool {
// Exploits the fact the element size is a power of 2. Determining whether
// there is remainder (not aligned) can be achieved efficiently with bit
// masking. Shift is safe as sizeLog2 can be 3 at most (see
// ElementsKindToShiftSize).
return (bytes & ((1 << this.sizeLog2) - 1)) != 0;
}
sizeLog2: uintptr;
map: Map;
kind: ElementsKind;
}
extern runtime TypedArraySortFast(Context, Object): JSTypedArray;
extern macro TypedArrayBuiltinsAssembler::ValidateTypedArray(
Context, Object, constexpr string): JSTypedArray;
......
......@@ -5701,20 +5701,28 @@ TNode<Float64T> CodeStubAssembler::ChangeNumberToFloat64(
return result.value();
}
TNode<UintPtrT> CodeStubAssembler::ChangeNonnegativeNumberToUintPtr(
TNode<Number> value) {
TNode<UintPtrT> CodeStubAssembler::TryNumberToUintPtr(TNode<Number> value,
Label* if_negative) {
TVARIABLE(UintPtrT, result);
Label done(this, &result);
Branch(TaggedIsSmi(value),
[&] {
TNode<Smi> value_smi = CAST(value);
CSA_SLOW_ASSERT(this, SmiLessThan(SmiConstant(-1), value_smi));
if (if_negative == nullptr) {
CSA_SLOW_ASSERT(this, SmiLessThan(SmiConstant(-1), value_smi));
} else {
GotoIfNot(TaggedIsPositiveSmi(value), if_negative);
}
result = UncheckedCast<UintPtrT>(SmiToIntPtr(value_smi));
Goto(&done);
},
[&] {
TNode<HeapNumber> value_hn = CAST(value);
result = ChangeFloat64ToUintPtr(LoadHeapNumberValue(value_hn));
TNode<Float64T> value = LoadHeapNumberValue(value_hn);
if (if_negative != nullptr) {
GotoIf(Float64LessThan(value, Float64Constant(0.0)), if_negative);
}
result = ChangeFloat64ToUintPtr(value);
Goto(&done);
});
......
......@@ -1938,7 +1938,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
TNode<Number> ChangeUintPtrToTagged(TNode<UintPtrT> value);
TNode<Uint32T> ChangeNumberToUint32(TNode<Number> value);
TNode<Float64T> ChangeNumberToFloat64(SloppyTNode<Number> value);
TNode<UintPtrT> ChangeNonnegativeNumberToUintPtr(TNode<Number> value);
TNode<UintPtrT> TryNumberToUintPtr(TNode<Number> value, Label* if_negative);
TNode<UintPtrT> ChangeNonnegativeNumberToUintPtr(TNode<Number> value) {
return TryNumberToUintPtr(value, nullptr);
}
void TaggedToNumeric(Node* context, Node* value, Label* done,
Variable* var_numeric);
......
......@@ -417,6 +417,11 @@ ExternalReference ExternalReference::address_of_min_int() {
return ExternalReference(reinterpret_cast<Address>(&double_min_int_constant));
}
ExternalReference
ExternalReference::address_of_mock_arraybuffer_allocator_flag() {
return ExternalReference(&FLAG_mock_arraybuffer_allocator);
}
ExternalReference ExternalReference::address_of_runtime_stats_flag() {
return ExternalReference(&FLAG_runtime_stats);
}
......
......@@ -92,6 +92,8 @@ class StatsCounter;
V(address_of_harmony_await_optimization_flag, \
"FLAG_harmony_await_optimization") \
V(address_of_min_int, "LDoubleConstant::min_int") \
V(address_of_mock_arraybuffer_allocator_flag, \
"FLAG_mock_arraybuffer_allocator") \
V(address_of_one_half, "LDoubleConstant::one_half") \
V(address_of_runtime_stats_flag, "FLAG_runtime_stats") \
V(address_of_the_hole_nan, "the_hole_nan") \
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment