[turbofan] Introduce initial support for TypedArrays.
This adds support for lowering keyed access to JSTypedArray objects to element loads and stores instead of IC calls. There's still a lot of room for improvement, but the improvements can be done incrementally later. We add a dedicated UnsafePointerAdd operator, which sits in the effect chain, and does the (GC invisible) computation of addresses that are potentially inside HeapObjects. Also there's now a dedicated Retain operator, which ensures that we retain a certain tagged value, which is necessary to ensure that we keep a JSArrayBuffer alive as long as we might still potentially access elements in its backing store. R=epertoso@chromium.org Review-Url: https://codereview.chromium.org/2203693002 Cr-Commit-Position: refs/heads/master@{#38235}
This commit is contained in:
parent
8135caef32
commit
66f2d3bd66
@ -327,6 +327,27 @@ FieldAccess AccessBuilder::ForFixedArrayLength() {
|
||||
return access;
|
||||
}
|
||||
|
||||
// static
|
||||
FieldAccess AccessBuilder::ForFixedTypedArrayBaseBasePointer() {
|
||||
FieldAccess access = {kTaggedBase,
|
||||
FixedTypedArrayBase::kBasePointerOffset,
|
||||
MaybeHandle<Name>(),
|
||||
Type::Tagged(),
|
||||
MachineType::AnyTagged(),
|
||||
kPointerWriteBarrier};
|
||||
return access;
|
||||
}
|
||||
|
||||
// static
|
||||
FieldAccess AccessBuilder::ForFixedTypedArrayBaseExternalPointer() {
|
||||
FieldAccess access = {kTaggedBase,
|
||||
FixedTypedArrayBase::kExternalPointerOffset,
|
||||
MaybeHandle<Name>(),
|
||||
Type::UntaggedPointer(),
|
||||
MachineType::Pointer(),
|
||||
kNoWriteBarrier};
|
||||
return access;
|
||||
}
|
||||
|
||||
// static
|
||||
FieldAccess AccessBuilder::ForDescriptorArrayEnumCache() {
|
||||
|
@ -106,6 +106,12 @@ class AccessBuilder final : public AllStatic {
|
||||
// Provides access to FixedArray::length() field.
|
||||
static FieldAccess ForFixedArrayLength();
|
||||
|
||||
// Provides access to FixedTypedArrayBase::base_pointer() field.
|
||||
static FieldAccess ForFixedTypedArrayBaseBasePointer();
|
||||
|
||||
// Provides access to FixedTypedArrayBase::external_pointer() field.
|
||||
static FieldAccess ForFixedTypedArrayBaseExternalPointer();
|
||||
|
||||
// Provides access to DescriptorArray::enum_cache() field.
|
||||
static FieldAccess ForDescriptorArrayEnumCache();
|
||||
|
||||
|
@ -25,6 +25,8 @@ bool CanInlineElementAccess(Handle<Map> map) {
|
||||
ElementsKind const elements_kind = map->elements_kind();
|
||||
if (IsFastElementsKind(elements_kind)) return true;
|
||||
// TODO(bmeurer): Add support for other elements kind.
|
||||
if (elements_kind == UINT8_CLAMPED_ELEMENTS) return false;
|
||||
if (IsFixedTypedArrayElementsKind(elements_kind)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -235,21 +235,22 @@ std::ostream& operator<<(std::ostream& os,
|
||||
return os;
|
||||
}
|
||||
|
||||
#define CACHED_OP_LIST(V) \
|
||||
V(Dead, Operator::kFoldable, 0, 0, 0, 1, 1, 1) \
|
||||
V(IfTrue, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
|
||||
V(IfFalse, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
|
||||
V(IfSuccess, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
|
||||
V(IfDefault, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
|
||||
V(Throw, Operator::kKontrol, 1, 1, 1, 0, 0, 1) \
|
||||
V(Terminate, Operator::kKontrol, 0, 1, 1, 0, 0, 1) \
|
||||
V(OsrNormalEntry, Operator::kFoldable, 0, 1, 1, 0, 1, 1) \
|
||||
V(OsrLoopEntry, Operator::kFoldable, 0, 1, 1, 0, 1, 1) \
|
||||
V(LoopExit, Operator::kKontrol, 0, 0, 2, 0, 0, 1) \
|
||||
V(LoopExitValue, Operator::kPure, 1, 0, 1, 1, 0, 0) \
|
||||
V(LoopExitEffect, Operator::kNoThrow, 0, 1, 1, 0, 1, 0) \
|
||||
V(Checkpoint, Operator::kKontrol, 0, 1, 1, 0, 1, 0) \
|
||||
V(FinishRegion, Operator::kKontrol, 1, 1, 0, 1, 1, 0)
|
||||
#define CACHED_OP_LIST(V) \
|
||||
V(Dead, Operator::kFoldable, 0, 0, 0, 1, 1, 1) \
|
||||
V(IfTrue, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
|
||||
V(IfFalse, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
|
||||
V(IfSuccess, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
|
||||
V(IfDefault, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
|
||||
V(Throw, Operator::kKontrol, 1, 1, 1, 0, 0, 1) \
|
||||
V(Terminate, Operator::kKontrol, 0, 1, 1, 0, 0, 1) \
|
||||
V(OsrNormalEntry, Operator::kFoldable, 0, 1, 1, 0, 1, 1) \
|
||||
V(OsrLoopEntry, Operator::kFoldable, 0, 1, 1, 0, 1, 1) \
|
||||
V(LoopExit, Operator::kKontrol, 0, 0, 2, 0, 0, 1) \
|
||||
V(LoopExitValue, Operator::kPure, 1, 0, 1, 1, 0, 0) \
|
||||
V(LoopExitEffect, Operator::kNoThrow, 0, 1, 1, 0, 1, 0) \
|
||||
V(Checkpoint, Operator::kKontrol, 0, 1, 1, 0, 1, 0) \
|
||||
V(FinishRegion, Operator::kKontrol, 1, 1, 0, 1, 1, 0) \
|
||||
V(Retain, Operator::kKontrol, 1, 1, 0, 0, 1, 0)
|
||||
|
||||
#define CACHED_RETURN_LIST(V) \
|
||||
V(1) \
|
||||
|
@ -248,6 +248,7 @@ class CommonOperatorBuilder final : public ZoneObject {
|
||||
const Operator* Call(const CallDescriptor* descriptor);
|
||||
const Operator* TailCall(const CallDescriptor* descriptor);
|
||||
const Operator* Projection(size_t index);
|
||||
const Operator* Retain();
|
||||
|
||||
// Constructs a new merge or phi operator with the same opcode as {op}, but
|
||||
// with {size} inputs.
|
||||
|
@ -722,6 +722,12 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
|
||||
case IrOpcode::kTransitionElementsKind:
|
||||
state = LowerTransitionElementsKind(node, *effect, *control);
|
||||
break;
|
||||
case IrOpcode::kLoadTypedElement:
|
||||
state = LowerLoadTypedElement(node, *effect, *control);
|
||||
break;
|
||||
case IrOpcode::kStoreTypedElement:
|
||||
state = LowerStoreTypedElement(node, *effect, *control);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -2619,6 +2625,59 @@ EffectControlLinearizer::LowerTransitionElementsKind(Node* node, Node* effect,
|
||||
return ValueEffectControl(nullptr, effect, control);
|
||||
}
|
||||
|
||||
EffectControlLinearizer::ValueEffectControl
|
||||
EffectControlLinearizer::LowerLoadTypedElement(Node* node, Node* effect,
|
||||
Node* control) {
|
||||
ExternalArrayType array_type = ExternalArrayTypeOf(node->op());
|
||||
Node* buffer = node->InputAt(0);
|
||||
Node* base = node->InputAt(1);
|
||||
Node* external = node->InputAt(2);
|
||||
Node* index = node->InputAt(3);
|
||||
|
||||
// We need to keep the {buffer} alive so that the GC will not release the
|
||||
// ArrayBuffer (if there's any) as long as we are still operating on it.
|
||||
effect = graph()->NewNode(common()->Retain(), buffer, effect);
|
||||
|
||||
// Compute the effective storage pointer.
|
||||
Node* storage = effect = graph()->NewNode(machine()->UnsafePointerAdd(), base,
|
||||
external, effect, control);
|
||||
|
||||
// Perform the actual typed element access.
|
||||
Node* value = effect = graph()->NewNode(
|
||||
simplified()->LoadElement(
|
||||
AccessBuilder::ForTypedArrayElement(array_type, true)),
|
||||
storage, index, effect, control);
|
||||
|
||||
return ValueEffectControl(value, effect, control);
|
||||
}
|
||||
|
||||
EffectControlLinearizer::ValueEffectControl
|
||||
EffectControlLinearizer::LowerStoreTypedElement(Node* node, Node* effect,
|
||||
Node* control) {
|
||||
ExternalArrayType array_type = ExternalArrayTypeOf(node->op());
|
||||
Node* buffer = node->InputAt(0);
|
||||
Node* base = node->InputAt(1);
|
||||
Node* external = node->InputAt(2);
|
||||
Node* index = node->InputAt(3);
|
||||
Node* value = node->InputAt(4);
|
||||
|
||||
// We need to keep the {buffer} alive so that the GC will not release the
|
||||
// ArrayBuffer (if there's any) as long as we are still operating on it.
|
||||
effect = graph()->NewNode(common()->Retain(), buffer, effect);
|
||||
|
||||
// Compute the effective storage pointer.
|
||||
Node* storage = effect = graph()->NewNode(machine()->UnsafePointerAdd(), base,
|
||||
external, effect, control);
|
||||
|
||||
// Perform the actual typed element access.
|
||||
effect = graph()->NewNode(
|
||||
simplified()->StoreElement(
|
||||
AccessBuilder::ForTypedArrayElement(array_type, true)),
|
||||
storage, index, value, effect, control);
|
||||
|
||||
return ValueEffectControl(nullptr, effect, control);
|
||||
}
|
||||
|
||||
Factory* EffectControlLinearizer::factory() const {
|
||||
return isolate()->factory();
|
||||
}
|
||||
|
@ -134,6 +134,10 @@ class EffectControlLinearizer {
|
||||
Node* control);
|
||||
ValueEffectControl LowerTransitionElementsKind(Node* node, Node* effect,
|
||||
Node* control);
|
||||
ValueEffectControl LowerLoadTypedElement(Node* node, Node* effect,
|
||||
Node* control);
|
||||
ValueEffectControl LowerStoreTypedElement(Node* node, Node* effect,
|
||||
Node* control);
|
||||
|
||||
ValueEffectControl AllocateHeapNumberWithValue(Node* node, Node* effect,
|
||||
Node* control);
|
||||
|
@ -299,6 +299,9 @@ void InstructionSelector::MarkAsDefined(Node* node) {
|
||||
|
||||
bool InstructionSelector::IsUsed(Node* node) const {
|
||||
DCHECK_NOT_NULL(node);
|
||||
// TODO(bmeurer): This is a terrible monster hack, but we have to make sure
|
||||
// that the Retain is actually emitted, otherwise the GC will mess up.
|
||||
if (node->opcode() == IrOpcode::kRetain) return true;
|
||||
if (!node->op()->HasProperty(Operator::kEliminatable)) return true;
|
||||
size_t const id = node->id();
|
||||
DCHECK_LT(id, used_.size());
|
||||
@ -929,6 +932,9 @@ void InstructionSelector::VisitNode(Node* node) {
|
||||
case IrOpcode::kComment:
|
||||
VisitComment(node);
|
||||
return;
|
||||
case IrOpcode::kRetain:
|
||||
VisitRetain(node);
|
||||
return;
|
||||
case IrOpcode::kLoad: {
|
||||
LoadRepresentation type = LoadRepresentationOf(node->op());
|
||||
MarkAsRepresentation(type.representation(), node);
|
||||
@ -1297,6 +1303,9 @@ void InstructionSelector::VisitNode(Node* node) {
|
||||
}
|
||||
case IrOpcode::kAtomicStore:
|
||||
return VisitAtomicStore(node);
|
||||
case IrOpcode::kUnsafePointerAdd:
|
||||
MarkAsRepresentation(MachineType::PointerRepresentation(), node);
|
||||
return VisitUnsafePointerAdd(node);
|
||||
default:
|
||||
V8_Fatal(__FILE__, __LINE__, "Unexpected operator #%d:%s @ node #%d",
|
||||
node->opcode(), node->op()->mnemonic(), node->id());
|
||||
@ -2016,6 +2025,19 @@ void InstructionSelector::VisitComment(Node* node) {
|
||||
Emit(kArchComment, 0, nullptr, 1, &operand);
|
||||
}
|
||||
|
||||
void InstructionSelector::VisitUnsafePointerAdd(Node* node) {
|
||||
#if V8_TARGET_ARCH_64_BIT
|
||||
VisitInt64Add(node);
|
||||
#else // V8_TARGET_ARCH_64_BIT
|
||||
VisitInt32Add(node);
|
||||
#endif // V8_TARGET_ARCH_64_BIT
|
||||
}
|
||||
|
||||
void InstructionSelector::VisitRetain(Node* node) {
|
||||
OperandGenerator g(this);
|
||||
Emit(kArchNop, g.NoOutput(), g.UseAny(node->InputAt(0)));
|
||||
}
|
||||
|
||||
bool InstructionSelector::CanProduceSignalingNaN(Node* node) {
|
||||
// TODO(jarin) Improve the heuristic here.
|
||||
if (node->opcode() == IrOpcode::kFloat64Add ||
|
||||
|
@ -296,6 +296,7 @@ class InstructionSelector final {
|
||||
Node* value);
|
||||
void VisitReturn(Node* ret);
|
||||
void VisitThrow(Node* value);
|
||||
void VisitRetain(Node* node);
|
||||
|
||||
void EmitPrepareArguments(ZoneVector<compiler::PushParameter>* arguments,
|
||||
const CallDescriptor* descriptor, Node* node);
|
||||
|
@ -732,9 +732,9 @@ Reduction JSBuiltinReducer::ReduceArrayBufferViewAccessor(
|
||||
jsgraph()->ZeroConstant());
|
||||
|
||||
// Default to zero if the {receiver}s buffer was neutered.
|
||||
Node* value =
|
||||
graph()->NewNode(common()->Select(MachineRepresentation::kTagged),
|
||||
check, receiver_length, jsgraph()->ZeroConstant());
|
||||
Node* value = graph()->NewNode(
|
||||
common()->Select(MachineRepresentation::kTagged, BranchHint::kTrue),
|
||||
check, receiver_length, jsgraph()->ZeroConstant());
|
||||
|
||||
ReplaceWithValue(node, value, effect, control);
|
||||
return Replace(value);
|
||||
|
@ -930,6 +930,24 @@ JSNativeContextSpecialization::BuildPropertyAccess(
|
||||
return ValueEffectControl(value, effect, control);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
ExternalArrayType GetArrayTypeFromElementsKind(ElementsKind kind) {
|
||||
switch (kind) {
|
||||
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) \
|
||||
case TYPE##_ELEMENTS: \
|
||||
return kExternal##Type##Array;
|
||||
TYPED_ARRAYS(TYPED_ARRAY_CASE)
|
||||
#undef TYPED_ARRAY_CASE
|
||||
default:
|
||||
break;
|
||||
}
|
||||
UNREACHABLE();
|
||||
return kExternalInt8Array;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
JSNativeContextSpecialization::ValueEffectControl
|
||||
JSNativeContextSpecialization::BuildElementAccess(
|
||||
Node* receiver, Node* index, Node* value, Node* effect, Node* control,
|
||||
@ -963,105 +981,167 @@ JSNativeContextSpecialization::BuildElementAccess(
|
||||
effect = graph()->NewNode(simplified()->CheckIf(), check, effect, control);
|
||||
}
|
||||
|
||||
// Load the length of the {receiver}.
|
||||
Node* length = effect =
|
||||
HasOnlyJSArrayMaps(receiver_maps)
|
||||
? graph()->NewNode(
|
||||
simplified()->LoadField(
|
||||
AccessBuilder::ForJSArrayLength(elements_kind)),
|
||||
receiver, effect, control)
|
||||
: graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForFixedArrayLength()),
|
||||
elements, effect, control);
|
||||
if (IsFixedTypedArrayElementsKind(elements_kind)) {
|
||||
// Load the {receiver}s length.
|
||||
Node* length = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForJSTypedArrayLength()),
|
||||
receiver, effect, control);
|
||||
|
||||
// Check that the {index} is in the valid range for the {receiver}.
|
||||
index = effect = graph()->NewNode(simplified()->CheckBounds(), index, length,
|
||||
effect, control);
|
||||
// Check if the {receiver}s buffer was neutered.
|
||||
Node* buffer = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForJSArrayBufferViewBuffer()),
|
||||
receiver, effect, control);
|
||||
Node* buffer_bitfield = effect = graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForJSArrayBufferBitField()),
|
||||
buffer, effect, control);
|
||||
Node* check = graph()->NewNode(
|
||||
simplified()->NumberEqual(),
|
||||
graph()->NewNode(
|
||||
simplified()->NumberBitwiseAnd(), buffer_bitfield,
|
||||
jsgraph()->Constant(JSArrayBuffer::WasNeutered::kMask)),
|
||||
jsgraph()->ZeroConstant());
|
||||
|
||||
// Compute the element access.
|
||||
Type* element_type = Type::Any();
|
||||
MachineType element_machine_type = MachineType::AnyTagged();
|
||||
if (IsFastDoubleElementsKind(elements_kind)) {
|
||||
element_type = Type::Number();
|
||||
element_machine_type = MachineType::Float64();
|
||||
} else if (IsFastSmiElementsKind(elements_kind)) {
|
||||
element_type = type_cache_.kSmi;
|
||||
}
|
||||
ElementAccess element_access = {kTaggedBase, FixedArray::kHeaderSize,
|
||||
element_type, element_machine_type,
|
||||
kFullWriteBarrier};
|
||||
// Default to zero if the {receiver}s buffer was neutered.
|
||||
length = graph()->NewNode(
|
||||
common()->Select(MachineRepresentation::kTagged, BranchHint::kTrue),
|
||||
check, length, jsgraph()->ZeroConstant());
|
||||
|
||||
// Access the actual element.
|
||||
// TODO(bmeurer): Refactor this into separate methods or even a separate
|
||||
// class that deals with the elements access.
|
||||
if (access_mode == AccessMode::kLoad) {
|
||||
// Compute the real element access type, which includes the hole in case
|
||||
// of holey backing stores.
|
||||
if (elements_kind == FAST_HOLEY_ELEMENTS ||
|
||||
elements_kind == FAST_HOLEY_SMI_ELEMENTS) {
|
||||
element_access.type = Type::Union(
|
||||
element_type,
|
||||
Type::Constant(factory()->the_hole_value(), graph()->zone()),
|
||||
graph()->zone());
|
||||
}
|
||||
// Perform the actual backing store access.
|
||||
value = effect = graph()->NewNode(simplified()->LoadElement(element_access),
|
||||
elements, index, effect, control);
|
||||
// Handle loading from holey backing stores correctly, by either mapping
|
||||
// the hole to undefined if possible, or deoptimizing otherwise.
|
||||
if (elements_kind == FAST_HOLEY_ELEMENTS ||
|
||||
elements_kind == FAST_HOLEY_SMI_ELEMENTS) {
|
||||
// Perform the hole check on the result.
|
||||
CheckTaggedHoleMode mode = CheckTaggedHoleMode::kNeverReturnHole;
|
||||
// Check if we are allowed to turn the hole into undefined.
|
||||
// TODO(bmeurer): We might check the JSArray map from a different
|
||||
// context here; may need reinvestigation.
|
||||
if (receiver_maps.size() == 1 &&
|
||||
receiver_maps[0].is_identical_to(
|
||||
handle(isolate()->get_initial_js_array_map(elements_kind))) &&
|
||||
isolate()->IsFastArrayConstructorPrototypeChainIntact()) {
|
||||
// Add a code dependency on the array protector cell.
|
||||
dependencies()->AssumePrototypeMapsStable(
|
||||
receiver_maps[0], isolate()->initial_object_prototype());
|
||||
dependencies()->AssumePropertyCell(factory()->array_protector());
|
||||
// Turn the hole into undefined.
|
||||
mode = CheckTaggedHoleMode::kConvertHoleToUndefined;
|
||||
// Check that the {index} is in the valid range for the {receiver}.
|
||||
index = effect = graph()->NewNode(simplified()->CheckBounds(), index,
|
||||
length, effect, control);
|
||||
|
||||
// Load the base and external pointer for the {receiver}.
|
||||
Node* base_pointer = effect = graph()->NewNode(
|
||||
simplified()->LoadField(
|
||||
AccessBuilder::ForFixedTypedArrayBaseBasePointer()),
|
||||
elements, effect, control);
|
||||
Node* external_pointer = effect = graph()->NewNode(
|
||||
simplified()->LoadField(
|
||||
AccessBuilder::ForFixedTypedArrayBaseExternalPointer()),
|
||||
elements, effect, control);
|
||||
|
||||
// Access the actual element.
|
||||
ExternalArrayType external_array_type =
|
||||
GetArrayTypeFromElementsKind(elements_kind);
|
||||
switch (access_mode) {
|
||||
case AccessMode::kLoad: {
|
||||
value = effect = graph()->NewNode(
|
||||
simplified()->LoadTypedElement(external_array_type), buffer,
|
||||
base_pointer, external_pointer, index, effect, control);
|
||||
break;
|
||||
}
|
||||
value = effect = graph()->NewNode(simplified()->CheckTaggedHole(mode),
|
||||
value, effect, control);
|
||||
} else if (elements_kind == FAST_HOLEY_DOUBLE_ELEMENTS) {
|
||||
// Perform the hole check on the result.
|
||||
CheckFloat64HoleMode mode = CheckFloat64HoleMode::kNeverReturnHole;
|
||||
// Check if we are allowed to return the hole directly.
|
||||
// TODO(bmeurer): We might check the JSArray map from a different
|
||||
// context here; may need reinvestigation.
|
||||
if (receiver_maps.size() == 1 &&
|
||||
receiver_maps[0].is_identical_to(
|
||||
handle(isolate()->get_initial_js_array_map(elements_kind))) &&
|
||||
isolate()->IsFastArrayConstructorPrototypeChainIntact()) {
|
||||
// Add a code dependency on the array protector cell.
|
||||
dependencies()->AssumePrototypeMapsStable(
|
||||
receiver_maps[0], isolate()->initial_object_prototype());
|
||||
dependencies()->AssumePropertyCell(factory()->array_protector());
|
||||
// Return the signaling NaN hole directly if all uses are truncating.
|
||||
mode = CheckFloat64HoleMode::kAllowReturnHole;
|
||||
case AccessMode::kStore: {
|
||||
// Ensure that the {value} is actually a Number.
|
||||
value = effect = graph()->NewNode(simplified()->CheckNumber(), value,
|
||||
effect, control);
|
||||
effect = graph()->NewNode(
|
||||
simplified()->StoreTypedElement(external_array_type), buffer,
|
||||
base_pointer, external_pointer, index, value, effect, control);
|
||||
break;
|
||||
}
|
||||
value = effect = graph()->NewNode(simplified()->CheckFloat64Hole(mode),
|
||||
value, effect, control);
|
||||
}
|
||||
} else {
|
||||
DCHECK_EQ(AccessMode::kStore, access_mode);
|
||||
if (IsFastSmiElementsKind(elements_kind)) {
|
||||
value = effect = graph()->NewNode(simplified()->CheckTaggedSigned(),
|
||||
value, effect, control);
|
||||
} else if (IsFastDoubleElementsKind(elements_kind)) {
|
||||
value = effect =
|
||||
graph()->NewNode(simplified()->CheckNumber(), value, effect, control);
|
||||
// Make sure we do not store signalling NaNs into double arrays.
|
||||
value = graph()->NewNode(simplified()->NumberSilenceNaN(), value);
|
||||
// Load the length of the {receiver}.
|
||||
Node* length = effect =
|
||||
HasOnlyJSArrayMaps(receiver_maps)
|
||||
? graph()->NewNode(
|
||||
simplified()->LoadField(
|
||||
AccessBuilder::ForJSArrayLength(elements_kind)),
|
||||
receiver, effect, control)
|
||||
: graph()->NewNode(
|
||||
simplified()->LoadField(AccessBuilder::ForFixedArrayLength()),
|
||||
elements, effect, control);
|
||||
|
||||
// Check that the {index} is in the valid range for the {receiver}.
|
||||
index = effect = graph()->NewNode(simplified()->CheckBounds(), index,
|
||||
length, effect, control);
|
||||
|
||||
// Compute the element access.
|
||||
Type* element_type = Type::Any();
|
||||
MachineType element_machine_type = MachineType::AnyTagged();
|
||||
if (IsFastDoubleElementsKind(elements_kind)) {
|
||||
element_type = Type::Number();
|
||||
element_machine_type = MachineType::Float64();
|
||||
} else if (IsFastSmiElementsKind(elements_kind)) {
|
||||
element_type = type_cache_.kSmi;
|
||||
}
|
||||
ElementAccess element_access = {kTaggedBase, FixedArray::kHeaderSize,
|
||||
element_type, element_machine_type,
|
||||
kFullWriteBarrier};
|
||||
|
||||
// Access the actual element.
|
||||
// TODO(bmeurer): Refactor this into separate methods or even a separate
|
||||
// class that deals with the elements access.
|
||||
if (access_mode == AccessMode::kLoad) {
|
||||
// Compute the real element access type, which includes the hole in case
|
||||
// of holey backing stores.
|
||||
if (elements_kind == FAST_HOLEY_ELEMENTS ||
|
||||
elements_kind == FAST_HOLEY_SMI_ELEMENTS) {
|
||||
element_access.type = Type::Union(
|
||||
element_type,
|
||||
Type::Constant(factory()->the_hole_value(), graph()->zone()),
|
||||
graph()->zone());
|
||||
}
|
||||
// Perform the actual backing store access.
|
||||
value = effect =
|
||||
graph()->NewNode(simplified()->LoadElement(element_access), elements,
|
||||
index, effect, control);
|
||||
// Handle loading from holey backing stores correctly, by either mapping
|
||||
// the hole to undefined if possible, or deoptimizing otherwise.
|
||||
if (elements_kind == FAST_HOLEY_ELEMENTS ||
|
||||
elements_kind == FAST_HOLEY_SMI_ELEMENTS) {
|
||||
// Perform the hole check on the result.
|
||||
CheckTaggedHoleMode mode = CheckTaggedHoleMode::kNeverReturnHole;
|
||||
// Check if we are allowed to turn the hole into undefined.
|
||||
// TODO(bmeurer): We might check the JSArray map from a different
|
||||
// context here; may need reinvestigation.
|
||||
if (receiver_maps.size() == 1 &&
|
||||
receiver_maps[0].is_identical_to(
|
||||
handle(isolate()->get_initial_js_array_map(elements_kind))) &&
|
||||
isolate()->IsFastArrayConstructorPrototypeChainIntact()) {
|
||||
// Add a code dependency on the array protector cell.
|
||||
dependencies()->AssumePrototypeMapsStable(
|
||||
receiver_maps[0], isolate()->initial_object_prototype());
|
||||
dependencies()->AssumePropertyCell(factory()->array_protector());
|
||||
// Turn the hole into undefined.
|
||||
mode = CheckTaggedHoleMode::kConvertHoleToUndefined;
|
||||
}
|
||||
value = effect = graph()->NewNode(simplified()->CheckTaggedHole(mode),
|
||||
value, effect, control);
|
||||
} else if (elements_kind == FAST_HOLEY_DOUBLE_ELEMENTS) {
|
||||
// Perform the hole check on the result.
|
||||
CheckFloat64HoleMode mode = CheckFloat64HoleMode::kNeverReturnHole;
|
||||
// Check if we are allowed to return the hole directly.
|
||||
// TODO(bmeurer): We might check the JSArray map from a different
|
||||
// context here; may need reinvestigation.
|
||||
if (receiver_maps.size() == 1 &&
|
||||
receiver_maps[0].is_identical_to(
|
||||
handle(isolate()->get_initial_js_array_map(elements_kind))) &&
|
||||
isolate()->IsFastArrayConstructorPrototypeChainIntact()) {
|
||||
// Add a code dependency on the array protector cell.
|
||||
dependencies()->AssumePrototypeMapsStable(
|
||||
receiver_maps[0], isolate()->initial_object_prototype());
|
||||
dependencies()->AssumePropertyCell(factory()->array_protector());
|
||||
// Return the signaling NaN hole directly if all uses are truncating.
|
||||
mode = CheckFloat64HoleMode::kAllowReturnHole;
|
||||
}
|
||||
value = effect = graph()->NewNode(simplified()->CheckFloat64Hole(mode),
|
||||
value, effect, control);
|
||||
}
|
||||
} else {
|
||||
DCHECK_EQ(AccessMode::kStore, access_mode);
|
||||
if (IsFastSmiElementsKind(elements_kind)) {
|
||||
value = effect = graph()->NewNode(simplified()->CheckTaggedSigned(),
|
||||
value, effect, control);
|
||||
} else if (IsFastDoubleElementsKind(elements_kind)) {
|
||||
value = effect = graph()->NewNode(simplified()->CheckNumber(), value,
|
||||
effect, control);
|
||||
// Make sure we do not store signalling NaNs into double arrays.
|
||||
value = graph()->NewNode(simplified()->NumberSilenceNaN(), value);
|
||||
}
|
||||
effect = graph()->NewNode(simplified()->StoreElement(element_access),
|
||||
elements, index, value, effect, control);
|
||||
}
|
||||
effect = graph()->NewNode(simplified()->StoreElement(element_access),
|
||||
elements, index, value, effect, control);
|
||||
}
|
||||
|
||||
return ValueEffectControl(value, effect, control);
|
||||
|
@ -63,6 +63,8 @@ Reduction LoadElimination::Reduce(Node* node) {
|
||||
return ReduceLoadElement(node);
|
||||
case IrOpcode::kStoreElement:
|
||||
return ReduceStoreElement(node);
|
||||
case IrOpcode::kStoreTypedElement:
|
||||
return ReduceStoreTypedElement(node);
|
||||
case IrOpcode::kEffectPhi:
|
||||
return ReduceEffectPhi(node);
|
||||
case IrOpcode::kDead:
|
||||
@ -455,6 +457,13 @@ Reduction LoadElimination::ReduceStoreElement(Node* node) {
|
||||
return UpdateState(node, state);
|
||||
}
|
||||
|
||||
Reduction LoadElimination::ReduceStoreTypedElement(Node* node) {
|
||||
Node* const effect = NodeProperties::GetEffectInput(node);
|
||||
AbstractState const* state = node_states_.Get(effect);
|
||||
if (state == nullptr) return NoChange();
|
||||
return UpdateState(node, state);
|
||||
}
|
||||
|
||||
Reduction LoadElimination::ReduceEffectPhi(Node* node) {
|
||||
Node* const effect0 = NodeProperties::GetEffectInput(node, 0);
|
||||
Node* const control = NodeProperties::GetControlInput(node);
|
||||
@ -571,15 +580,20 @@ LoadElimination::AbstractState const* LoadElimination::ComputeLoopState(
|
||||
|
||||
// static
|
||||
int LoadElimination::FieldIndexOf(FieldAccess const& access) {
|
||||
switch (access.machine_type.representation()) {
|
||||
MachineRepresentation rep = access.machine_type.representation();
|
||||
switch (rep) {
|
||||
case MachineRepresentation::kNone:
|
||||
case MachineRepresentation::kBit:
|
||||
UNREACHABLE();
|
||||
break;
|
||||
case MachineRepresentation::kWord8:
|
||||
case MachineRepresentation::kWord16:
|
||||
case MachineRepresentation::kWord32:
|
||||
case MachineRepresentation::kWord64:
|
||||
if (rep != MachineType::PointerRepresentation()) {
|
||||
return -1; // We currently only track pointer size fields.
|
||||
}
|
||||
break;
|
||||
case MachineRepresentation::kWord8:
|
||||
case MachineRepresentation::kWord16:
|
||||
case MachineRepresentation::kFloat32:
|
||||
return -1; // Currently untracked.
|
||||
case MachineRepresentation::kFloat64:
|
||||
|
@ -155,6 +155,7 @@ class LoadElimination final : public AdvancedReducer {
|
||||
Reduction ReduceStoreField(Node* node);
|
||||
Reduction ReduceLoadElement(Node* node);
|
||||
Reduction ReduceStoreElement(Node* node);
|
||||
Reduction ReduceStoreTypedElement(Node* node);
|
||||
Reduction ReduceEffectPhi(Node* node);
|
||||
Reduction ReduceStart(Node* node);
|
||||
Reduction ReduceOtherNode(Node* node);
|
||||
|
@ -611,6 +611,13 @@ struct MachineOperatorGlobalCache {
|
||||
0, 0, 0, 0, 0) {}
|
||||
};
|
||||
DebugBreakOperator kDebugBreak;
|
||||
|
||||
struct UnsafePointerAddOperator final : public Operator {
|
||||
UnsafePointerAddOperator()
|
||||
: Operator(IrOpcode::kUnsafePointerAdd, Operator::kKontrol,
|
||||
"UnsafePointerAdd", 2, 1, 1, 1, 1, 0) {}
|
||||
};
|
||||
UnsafePointerAddOperator kUnsafePointerAdd;
|
||||
};
|
||||
|
||||
struct CommentOperator : public Operator1<const char*> {
|
||||
@ -728,6 +735,10 @@ const Operator* MachineOperatorBuilder::Store(StoreRepresentation store_rep) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const Operator* MachineOperatorBuilder::UnsafePointerAdd() {
|
||||
return &cache_.kUnsafePointerAdd;
|
||||
}
|
||||
|
||||
const Operator* MachineOperatorBuilder::DebugBreak() {
|
||||
return &cache_.kDebugBreak;
|
||||
}
|
||||
|
@ -214,6 +214,7 @@ class MachineOperatorBuilder final : public ZoneObject {
|
||||
|
||||
const Operator* Comment(const char* msg);
|
||||
const Operator* DebugBreak();
|
||||
const Operator* UnsafePointerAdd();
|
||||
|
||||
const Operator* Word32And();
|
||||
const Operator* Word32Or();
|
||||
|
@ -92,6 +92,8 @@ void MemoryOptimizer::VisitNode(Node* node, AllocationState const* state) {
|
||||
case IrOpcode::kIfException:
|
||||
case IrOpcode::kLoad:
|
||||
case IrOpcode::kStore:
|
||||
case IrOpcode::kRetain:
|
||||
case IrOpcode::kUnsafePointerAdd:
|
||||
return VisitOtherEffect(node, state);
|
||||
default:
|
||||
break;
|
||||
|
@ -61,7 +61,8 @@
|
||||
V(LoopExit) \
|
||||
V(LoopExitValue) \
|
||||
V(LoopExitEffect) \
|
||||
V(Projection)
|
||||
V(Projection) \
|
||||
V(Retain)
|
||||
|
||||
#define COMMON_OP_LIST(V) \
|
||||
CONSTANT_OP_LIST(V) \
|
||||
@ -282,9 +283,11 @@
|
||||
V(LoadField) \
|
||||
V(LoadBuffer) \
|
||||
V(LoadElement) \
|
||||
V(LoadTypedElement) \
|
||||
V(StoreField) \
|
||||
V(StoreBuffer) \
|
||||
V(StoreElement) \
|
||||
V(StoreTypedElement) \
|
||||
V(ObjectIsCallable) \
|
||||
V(ObjectIsNumber) \
|
||||
V(ObjectIsReceiver) \
|
||||
@ -473,7 +476,8 @@
|
||||
V(Word32PairShr) \
|
||||
V(Word32PairSar) \
|
||||
V(AtomicLoad) \
|
||||
V(AtomicStore)
|
||||
V(AtomicStore) \
|
||||
V(UnsafePointerAdd)
|
||||
|
||||
#define MACHINE_SIMD_RETURN_SIMD_OP_LIST(V) \
|
||||
V(CreateFloat32x4) \
|
||||
|
@ -65,6 +65,27 @@ enum Phase {
|
||||
|
||||
namespace {
|
||||
|
||||
MachineRepresentation MachineRepresentationFromArrayType(
|
||||
ExternalArrayType array_type) {
|
||||
switch (array_type) {
|
||||
case kExternalUint8Array:
|
||||
case kExternalUint8ClampedArray:
|
||||
case kExternalInt8Array:
|
||||
return MachineRepresentation::kWord8;
|
||||
case kExternalUint16Array:
|
||||
case kExternalInt16Array:
|
||||
return MachineRepresentation::kWord16;
|
||||
case kExternalUint32Array:
|
||||
case kExternalInt32Array:
|
||||
return MachineRepresentation::kWord32;
|
||||
case kExternalFloat32Array:
|
||||
return MachineRepresentation::kFloat32;
|
||||
case kExternalFloat64Array:
|
||||
return MachineRepresentation::kFloat64;
|
||||
}
|
||||
UNREACHABLE();
|
||||
return MachineRepresentation::kNone;
|
||||
}
|
||||
|
||||
UseInfo TruncatingUseInfoFromRepresentation(MachineRepresentation rep) {
|
||||
switch (rep) {
|
||||
@ -2209,6 +2230,30 @@ class RepresentationSelector {
|
||||
}
|
||||
return;
|
||||
}
|
||||
case IrOpcode::kLoadTypedElement: {
|
||||
MachineRepresentation const rep =
|
||||
MachineRepresentationFromArrayType(ExternalArrayTypeOf(node->op()));
|
||||
ProcessInput(node, 0, UseInfo::AnyTagged()); // buffer
|
||||
ProcessInput(node, 1, UseInfo::AnyTagged()); // base pointer
|
||||
ProcessInput(node, 2, UseInfo::PointerInt()); // external pointer
|
||||
ProcessInput(node, 3, UseInfo::TruncatingWord32()); // index
|
||||
ProcessRemainingInputs(node, 4);
|
||||
SetOutput(node, rep);
|
||||
return;
|
||||
}
|
||||
case IrOpcode::kStoreTypedElement: {
|
||||
MachineRepresentation const rep =
|
||||
MachineRepresentationFromArrayType(ExternalArrayTypeOf(node->op()));
|
||||
ProcessInput(node, 0, UseInfo::AnyTagged()); // buffer
|
||||
ProcessInput(node, 1, UseInfo::AnyTagged()); // base pointer
|
||||
ProcessInput(node, 2, UseInfo::PointerInt()); // external pointer
|
||||
ProcessInput(node, 3, UseInfo::TruncatingWord32()); // index
|
||||
ProcessInput(node, 4,
|
||||
TruncatingUseInfoFromRepresentation(rep)); // value
|
||||
ProcessRemainingInputs(node, 5);
|
||||
SetOutput(node, MachineRepresentation::kNone);
|
||||
return;
|
||||
}
|
||||
case IrOpcode::kPlainPrimitiveToNumber: {
|
||||
if (InputIs(node, Type::Boolean())) {
|
||||
VisitUnop(node, UseInfo::Bool(), MachineRepresentation::kWord32);
|
||||
|
@ -181,6 +181,12 @@ const ElementAccess& ElementAccessOf(const Operator* op) {
|
||||
return OpParameter<ElementAccess>(op);
|
||||
}
|
||||
|
||||
ExternalArrayType ExternalArrayTypeOf(const Operator* op) {
|
||||
DCHECK(op->opcode() == IrOpcode::kLoadTypedElement ||
|
||||
op->opcode() == IrOpcode::kStoreTypedElement);
|
||||
return OpParameter<ExternalArrayType>(op);
|
||||
}
|
||||
|
||||
size_t hash_value(CheckFloat64HoleMode mode) {
|
||||
return static_cast<size_t>(mode);
|
||||
}
|
||||
@ -634,11 +640,13 @@ const Operator* SimplifiedOperatorBuilder::SpeculativeNumberLessThanOrEqual(
|
||||
"SpeculativeNumberLessThanOrEqual", 2, 1, 1, 1, 1, 0, hint);
|
||||
}
|
||||
|
||||
#define ACCESS_OP_LIST(V) \
|
||||
V(LoadField, FieldAccess, Operator::kNoWrite, 1, 1, 1) \
|
||||
V(StoreField, FieldAccess, Operator::kNoRead, 2, 1, 0) \
|
||||
V(LoadElement, ElementAccess, Operator::kNoWrite, 2, 1, 1) \
|
||||
V(StoreElement, ElementAccess, Operator::kNoRead, 3, 1, 0)
|
||||
#define ACCESS_OP_LIST(V) \
|
||||
V(LoadField, FieldAccess, Operator::kNoWrite, 1, 1, 1) \
|
||||
V(StoreField, FieldAccess, Operator::kNoRead, 2, 1, 0) \
|
||||
V(LoadElement, ElementAccess, Operator::kNoWrite, 2, 1, 1) \
|
||||
V(StoreElement, ElementAccess, Operator::kNoRead, 3, 1, 0) \
|
||||
V(LoadTypedElement, ExternalArrayType, Operator::kNoWrite, 4, 1, 1) \
|
||||
V(StoreTypedElement, ExternalArrayType, Operator::kNoRead, 5, 1, 0)
|
||||
|
||||
#define ACCESS(Name, Type, properties, value_input_count, control_input_count, \
|
||||
output_count) \
|
||||
|
@ -107,6 +107,8 @@ std::ostream& operator<<(std::ostream&, ElementAccess const&);
|
||||
|
||||
ElementAccess const& ElementAccessOf(const Operator* op) WARN_UNUSED_RESULT;
|
||||
|
||||
ExternalArrayType ExternalArrayTypeOf(const Operator* op) WARN_UNUSED_RESULT;
|
||||
|
||||
enum class CheckFloat64HoleMode : uint8_t {
|
||||
kNeverReturnHole, // Never return the hole (deoptimize instead).
|
||||
kAllowReturnHole // Allow to return the hole (signaling NaN).
|
||||
@ -319,12 +321,18 @@ class SimplifiedOperatorBuilder final : public ZoneObject {
|
||||
// store-buffer buffer, offset, length, value
|
||||
const Operator* StoreBuffer(BufferAccess);
|
||||
|
||||
// load-element [base + index], length
|
||||
// load-element [base + index]
|
||||
const Operator* LoadElement(ElementAccess const&);
|
||||
|
||||
// store-element [base + index], length, value
|
||||
// store-element [base + index], value
|
||||
const Operator* StoreElement(ElementAccess const&);
|
||||
|
||||
// load-typed-element buffer, [base + external + index]
|
||||
const Operator* LoadTypedElement(ExternalArrayType const&);
|
||||
|
||||
// store-typed-element buffer, [base + external + index], value
|
||||
const Operator* StoreTypedElement(ExternalArrayType const&);
|
||||
|
||||
private:
|
||||
Zone* zone() const { return zone_; }
|
||||
|
||||
|
@ -1975,6 +1975,17 @@ Type* Typer::Visitor::TypeLoadElement(Node* node) {
|
||||
return ElementAccessOf(node->op()).type;
|
||||
}
|
||||
|
||||
Type* Typer::Visitor::TypeLoadTypedElement(Node* node) {
|
||||
switch (ExternalArrayTypeOf(node->op())) {
|
||||
#define TYPED_ARRAY_CASE(ElemType, type, TYPE, ctype, size) \
|
||||
case kExternal##ElemType##Array: \
|
||||
return typer_->cache_.k##ElemType;
|
||||
TYPED_ARRAYS(TYPED_ARRAY_CASE)
|
||||
#undef TYPED_ARRAY_CASE
|
||||
}
|
||||
UNREACHABLE();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Type* Typer::Visitor::TypeStoreField(Node* node) {
|
||||
UNREACHABLE();
|
||||
@ -1993,6 +2004,11 @@ Type* Typer::Visitor::TypeStoreElement(Node* node) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Type* Typer::Visitor::TypeStoreTypedElement(Node* node) {
|
||||
UNREACHABLE();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Type* Typer::Visitor::TypeObjectIsCallable(Node* node) {
|
||||
return TypeUnaryOp(node, ObjectIsCallable);
|
||||
}
|
||||
@ -2026,6 +2042,13 @@ Type* Typer::Visitor::TypeDebugBreak(Node* node) { return Type::None(); }
|
||||
|
||||
Type* Typer::Visitor::TypeComment(Node* node) { return Type::None(); }
|
||||
|
||||
Type* Typer::Visitor::TypeRetain(Node* node) {
|
||||
UNREACHABLE();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Type* Typer::Visitor::TypeUnsafePointerAdd(Node* node) { return Type::None(); }
|
||||
|
||||
Type* Typer::Visitor::TypeLoad(Node* node) { return Type::Any(); }
|
||||
|
||||
Type* Typer::Visitor::TypeStackSlot(Node* node) { return Type::Any(); }
|
||||
|
@ -650,11 +650,10 @@ void Verifier::Visitor::Check(Node* node) {
|
||||
CheckNotTyped(node);
|
||||
break;
|
||||
|
||||
case IrOpcode::kDebugBreak:
|
||||
CheckNotTyped(node);
|
||||
break;
|
||||
|
||||
case IrOpcode::kComment:
|
||||
case IrOpcode::kDebugBreak:
|
||||
case IrOpcode::kRetain:
|
||||
case IrOpcode::kUnsafePointerAdd:
|
||||
CheckNotTyped(node);
|
||||
break;
|
||||
|
||||
@ -1042,6 +1041,8 @@ void Verifier::Visitor::Check(Node* node) {
|
||||
// CheckValueInputIs(node, 0, Type::Object());
|
||||
// CheckUpperIs(node, ElementAccessOf(node->op()).type));
|
||||
break;
|
||||
case IrOpcode::kLoadTypedElement:
|
||||
break;
|
||||
case IrOpcode::kStoreField:
|
||||
// (Object, fieldtype) -> _|_
|
||||
// TODO(rossberg): activate once machine ops are typed.
|
||||
@ -1058,6 +1059,9 @@ void Verifier::Visitor::Check(Node* node) {
|
||||
// CheckValueInputIs(node, 1, ElementAccessOf(node->op()).type));
|
||||
CheckNotTyped(node);
|
||||
break;
|
||||
case IrOpcode::kStoreTypedElement:
|
||||
CheckNotTyped(node);
|
||||
break;
|
||||
case IrOpcode::kNumberSilenceNaN:
|
||||
CheckValueInputIs(node, 0, Type::Number());
|
||||
CheckUpperIs(node, Type::Number());
|
||||
|
Loading…
Reference in New Issue
Block a user