#include "src/compiler/js-native-context-specialization.h"
#include <optional>
#include "src/base/logging.h"
#include "src/builtins/accessors.h"
#include "src/codegen/code-factory.h"
#include "src/common/globals.h"
#include "src/compiler/access-builder.h"
#include "src/compiler/access-info.h"
#include "src/compiler/allocation-builder-inl.h"
#include "src/compiler/allocation-builder.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/common-utils.h"
#include "src/compiler/compilation-dependencies.h"
#include "src/compiler/fast-api-calls.h"
#include "src/compiler/frame-states.h"
#include "src/compiler/graph-assembler.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/js-heap-broker.h"
#include "src/compiler/js-operator.h"
#include "src/compiler/linkage.h"
#include "src/compiler/map-inference.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/node-properties.h"
#include "src/compiler/property-access-builder.h"
#include "src/compiler/simplified-operator.h"
#include "src/compiler/type-cache.h"
#include "src/flags/flags.h"
#include "src/handles/handles.h"
#include "src/heap/factory.h"
#include "src/heap/heap-layout-inl.h"
#include "src/heap/heap-write-barrier-inl.h"
#include "src/objects/elements-kind.h"
#include "src/objects/feedback-vector.h"
#include "src/objects/heap-number.h"
#include "src/objects/property-details.h"
#include "src/objects/string.h"
namespace v8 {
namespace internal {
namespace compiler {
namespace {
bool HasNumberMaps(JSHeapBroker* broker, ZoneVector<MapRef> const& maps) {
for (MapRef map : maps) {
if (map.IsHeapNumberMap()) return true;
}
return false;
}
bool HasOnlyJSArrayMaps(JSHeapBroker* broker, ZoneVector<MapRef> const& maps) {
for (MapRef map : maps) {
if (!map.IsJSArrayMap()) return false;
}
return true;
}
}
JSNativeContextSpecialization::JSNativeContextSpecialization(
Editor* editor, JSGraph* jsgraph, JSHeapBroker* broker, Flags flags,
Zone* zone, Zone* shared_zone)
: AdvancedReducer(editor),
jsgraph_(jsgraph),
broker_(broker),
flags_(flags),
global_object_(
broker->target_native_context().global_object(broker).object()),
global_proxy_(
broker->target_native_context().global_proxy_object(broker).object()),
zone_(zone),
shared_zone_(shared_zone),
type_cache_(TypeCache::Get()),
created_strings_(zone) {}
Reduction JSNativeContextSpecialization::Reduce(Node* node) {
switch (node->opcode()) {
case IrOpcode::kJSAdd:
return ReduceJSAdd(node);
case IrOpcode::kJSAsyncFunctionEnter:
return ReduceJSAsyncFunctionEnter(node);
case IrOpcode::kJSAsyncFunctionReject:
return ReduceJSAsyncFunctionReject(node);
case IrOpcode::kJSAsyncFunctionResolve:
return ReduceJSAsyncFunctionResolve(node);
case IrOpcode::kJSGetSuperConstructor:
return ReduceJSGetSuperConstructor(node);
case IrOpcode::kJSFindNonDefaultConstructorOrConstruct:
return ReduceJSFindNonDefaultConstructorOrConstruct(node);
case IrOpcode::kJSInstanceOf:
return ReduceJSInstanceOf(node);
case IrOpcode::kJSHasInPrototypeChain:
return ReduceJSHasInPrototypeChain(node);
case IrOpcode::kJSOrdinaryHasInstance:
return ReduceJSOrdinaryHasInstance(node);
case IrOpcode::kJSPromiseResolve:
return ReduceJSPromiseResolve(node);
case IrOpcode::kJSResolvePromise:
return ReduceJSResolvePromise(node);
case IrOpcode::kJSLoadGlobal:
return ReduceJSLoadGlobal(node);
case IrOpcode::kJSStoreGlobal:
return ReduceJSStoreGlobal(node);
case IrOpcode::kJSLoadNamed:
return ReduceJSLoadNamed(node);
case IrOpcode::kJSLoadNamedFromSuper:
return ReduceJSLoadNamedFromSuper(node);
case IrOpcode::kJSSetNamedProperty:
return ReduceJSSetNamedProperty(node);
case IrOpcode::kJSHasProperty:
return ReduceJSHasProperty(node);
case IrOpcode::kJSLoadProperty:
return ReduceJSLoadProperty(node);
case IrOpcode::kJSSetKeyedProperty:
return ReduceJSSetKeyedProperty(node);
case IrOpcode::kJSDefineKeyedOwnProperty:
return ReduceJSDefineKeyedOwnProperty(node);
case IrOpcode::kJSDefineNamedOwnProperty:
return ReduceJSDefineNamedOwnProperty(node);
case IrOpcode::kJSDefineKeyedOwnPropertyInLiteral:
return ReduceJSDefineKeyedOwnPropertyInLiteral(node);
case IrOpcode::kJSStoreInArrayLiteral:
return ReduceJSStoreInArrayLiteral(node);
case IrOpcode::kJSToObject:
return ReduceJSToObject(node);
case IrOpcode::kJSToString:
return ReduceJSToString(node);
case IrOpcode::kJSGetIterator:
return ReduceJSGetIterator(node);
default:
break;
}
return NoChange();
}
std::optional<size_t> JSNativeContextSpecialization::GetMaxStringLength(
JSHeapBroker* broker, Node* node) {
HeapObjectMatcher matcher(node);
if (matcher.HasResolvedValue() && matcher.Ref(broker).IsString()) {
StringRef input = matcher.Ref(broker).AsString();
return input.length();
}
NumberMatcher number_matcher(node);
if (number_matcher.HasResolvedValue()) {
return kMaxDoubleStringLength;
}
return std::nullopt;
}
Reduction JSNativeContextSpecialization::ReduceJSToString(Node* node) {
DCHECK_EQ(IrOpcode::kJSToString, node->opcode());
Node* const input = node->InputAt(0);
HeapObjectMatcher matcher(input);
if (matcher.HasResolvedValue() && matcher.Ref(broker()).IsString()) {
Reduction reduction = Changed(input);
ReplaceWithValue(node, reduction.replacement());
return reduction;
}
NumberMatcher number_matcher(input);
if (number_matcher.HasResolvedValue()) {
Handle<String> num_str =
broker()->local_isolate_or_isolate()->factory()->DoubleToString(
number_matcher.ResolvedValue());
Node* reduced = graph()->NewNode(
common()->HeapConstant(broker()->CanonicalPersistentHandle(num_str)));
ReplaceWithValue(node, reduced);
return Replace(reduced);
}
return NoChange();
}
Handle<String> JSNativeContextSpecialization::CreateStringConstant(Node* node) {
DCHECK(IrOpcode::IsConstantOpcode(node->opcode()));
NumberMatcher number_matcher(node);
if (number_matcher.HasResolvedValue()) {
return broker()->local_isolate_or_isolate()->factory()->DoubleToString(
number_matcher.ResolvedValue());
} else {
HeapObjectMatcher matcher(node);
if (matcher.HasResolvedValue() && matcher.Ref(broker()).IsString()) {
return matcher.Ref(broker()).AsString().object();
} else {
UNREACHABLE();
}
}
}
namespace {
bool IsStringConstant(JSHeapBroker* broker, Node* node) {
HeapObjectMatcher matcher(node);
return matcher.HasResolvedValue() && matcher.Ref(broker).IsString();
}
bool IsStringWithNonAccessibleContent(JSHeapBroker* broker, Node* node) {
HeapObjectMatcher matcher(node);
if (matcher.HasResolvedValue() && matcher.Ref(broker).IsString()) {
StringRef input = matcher.Ref(broker).AsString();
return !input.IsContentAccessible();
}
return false;
}
}
Reduction JSNativeContextSpecialization::ReduceJSAsyncFunctionEnter(
Node* node) {
DCHECK_EQ(IrOpcode::kJSAsyncFunctionEnter, node->opcode());
Node* closure = NodeProperties::GetValueInput(node, 0);
Node* receiver = NodeProperties::GetValueInput(node, 1);
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
if (!dependencies()->DependOnPromiseHookProtector()) return NoChange();
Node* promise = effect =
graph()->NewNode(javascript()->CreatePromise(), context, effect);
FrameStateInfo state_info = FrameStateInfoOf(frame_state->op());
Handle<BytecodeArray> bytecode_array =
state_info.bytecode_array().ToHandleChecked();
DCHECK_EQ(state_info.shared_info()
.ToHandleChecked()
->internal_formal_parameter_count_without_receiver(),
bytecode_array->parameter_count_without_receiver());
int register_count = bytecode_array->parameter_count_without_receiver() +
bytecode_array->register_count();
MapRef fixed_array_map = broker()->fixed_array_map();
AllocationBuilder ab(jsgraph(), broker(), effect, control);
if (!ab.CanAllocateArray(register_count, fixed_array_map)) {
return NoChange();
}
Node* value = effect =
graph()->NewNode(javascript()->CreateAsyncFunctionObject(register_count),
closure, receiver, promise, context, effect, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
Reduction JSNativeContextSpecialization::ReduceJSAsyncFunctionReject(
Node* node) {
DCHECK_EQ(IrOpcode::kJSAsyncFunctionReject, node->opcode());
Node* async_function_object = NodeProperties::GetValueInput(node, 0);
Node* reason = NodeProperties::GetValueInput(node, 1);
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
if (!dependencies()->DependOnPromiseHookProtector()) return NoChange();
Node* promise = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSAsyncFunctionObjectPromise()),
async_function_object, effect, control);
Node* parameters[] = {promise};
frame_state = CreateStubBuiltinContinuationFrameState(
jsgraph(), Builtin::kAsyncFunctionLazyDeoptContinuation, context,
parameters, arraysize(parameters), frame_state,
ContinuationFrameStateMode::LAZY);
Node* debug_event = jsgraph()->FalseConstant();
effect = graph()->NewNode(javascript()->RejectPromise(), promise, reason,
debug_event, context, frame_state, effect, control);
ReplaceWithValue(node, promise, effect, control);
return Replace(promise);
}
Reduction JSNativeContextSpecialization::ReduceJSAsyncFunctionResolve(
Node* node) {
DCHECK_EQ(IrOpcode::kJSAsyncFunctionResolve, node->opcode());
Node* async_function_object = NodeProperties::GetValueInput(node, 0);
Node* value = NodeProperties::GetValueInput(node, 1);
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
if (!dependencies()->DependOnPromiseHookProtector()) return NoChange();
Node* promise = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSAsyncFunctionObjectPromise()),
async_function_object, effect, control);
Node* parameters[] = {promise};
frame_state = CreateStubBuiltinContinuationFrameState(
jsgraph(), Builtin::kAsyncFunctionLazyDeoptContinuation, context,
parameters, arraysize(parameters), frame_state,
ContinuationFrameStateMode::LAZY);
effect = graph()->NewNode(javascript()->ResolvePromise(), promise, value,
context, frame_state, effect, control);
ReplaceWithValue(node, promise, effect, control);
return Replace(promise);
}
bool JSNativeContextSpecialization::StringCanSafelyBeRead(Node* const node,
Handle<String> str) {
DCHECK(node->opcode() == IrOpcode::kHeapConstant ||
node->opcode() == IrOpcode::kNumberConstant);
if (broker()->IsMainThread()) {
return true;
}
if (node->opcode() == IrOpcode::kNumberConstant) {
return true;
}
return !IsStringWithNonAccessibleContent(broker(), node) ||
created_strings_.find(str) != created_strings_.end();
}
Reduction JSNativeContextSpecialization::ReduceJSAdd(Node* node) {
DCHECK_EQ(IrOpcode::kJSAdd, node->opcode());
Node* const lhs = node->InputAt(0);
Node* const rhs = node->InputAt(1);
std::optional<size_t> lhs_len = GetMaxStringLength(broker(), lhs);
std::optional<size_t> rhs_len = GetMaxStringLength(broker(), rhs);
if (!lhs_len || !rhs_len) return NoChange();
if (*lhs_len + *rhs_len <= String::kMaxLength &&
(IsStringConstant(broker(), lhs) || IsStringConstant(broker(), rhs))) {
Handle<String> left =
broker()->CanonicalPersistentHandle(CreateStringConstant(lhs));
Handle<String> right =
broker()->CanonicalPersistentHandle(CreateStringConstant(rhs));
if (!(StringCanSafelyBeRead(lhs, left) &&
StringCanSafelyBeRead(rhs, right))) {
if (left->length() + right->length() > ConsString::kMinLength) {
Handle<String> concatenated =
broker()
->local_isolate_or_isolate()
->factory()
->NewConsString(left, right, AllocationType::kOld)
.ToHandleChecked();
Node* reduced = graph()->NewNode(common()->HeapConstant(
broker()->CanonicalPersistentHandle(concatenated)));
ReplaceWithValue(node, reduced);
return Replace(reduced);
} else {
return NoChange();
}
}
Handle<String> concatenated =
utils::ConcatenateStrings(left, right, broker()).ToHandleChecked();
created_strings_.insert(concatenated);
Node* reduced = graph()->NewNode(common()->HeapConstant(
broker()->CanonicalPersistentHandle(concatenated)));
ReplaceWithValue(node, reduced);
return Replace(reduced);
}
return NoChange();
}
Reduction JSNativeContextSpecialization::ReduceJSGetSuperConstructor(
Node* node) {
DCHECK_EQ(IrOpcode::kJSGetSuperConstructor, node->opcode());
Node* constructor = NodeProperties::GetValueInput(node, 0);
HeapObjectMatcher m(constructor);
if (!m.HasResolvedValue() || !m.Ref(broker()).IsJSFunction()) {
return NoChange();
}
JSFunctionRef function = m.Ref(broker()).AsJSFunction();
MapRef function_map = function.map(broker());
HeapObjectRef function_prototype = function_map.prototype(broker());
if (function_map.is_stable()) {
dependencies()->DependOnStableMap(function_map);
Node* value = jsgraph()->ConstantNoHole(function_prototype, broker());
ReplaceWithValue(node, value);
return Replace(value);
}
return NoChange();
}
Reduction
JSNativeContextSpecialization::ReduceJSFindNonDefaultConstructorOrConstruct(
Node* node) {
JSFindNonDefaultConstructorOrConstructNode n(node);
Node* this_function = n.this_function();
Node* new_target = n.new_target();
Node* effect = n.effect();
Control control = n.control();
if (NodeProperties::IsExceptionalCall(node)) {
return NoChange();
}
HeapObjectMatcher m(this_function);
if (!m.HasResolvedValue() || !m.Ref(broker()).IsJSFunction()) {
return NoChange();
}
JSFunctionRef this_function_ref = m.Ref(broker()).AsJSFunction();
MapRef function_map = this_function_ref.map(broker());
HeapObjectRef current = function_map.prototype(broker());
OptionalJSObjectRef last_function;
Node* return_value;
Node* ctor_or_instance;
while (true) {
if (!current.IsJSFunction()) {
return NoChange();
}
JSFunctionRef current_function = current.AsJSFunction();
if (current_function.shared(broker())
.requires_instance_members_initializer()) {
return NoChange();
}
if (current_function.context(broker())
.scope_info(broker())
.ClassScopeHasPrivateBrand()) {
return NoChange();
}
FunctionKind kind = current_function.shared(broker()).kind();
if (kind != FunctionKind::kDefaultDerivedConstructor) {
if (!dependencies()->DependOnArrayIteratorProtector()) {
return NoChange();
}
last_function = current_function;
if (kind == FunctionKind::kDefaultBaseConstructor) {
return_value = jsgraph()->BooleanConstant(true);
Node* constructor =
jsgraph()->ConstantNoHole(current_function, broker());
FrameState old_frame_state = n.frame_state();
auto old_poke_offset = old_frame_state.frame_state_info()
.state_combine()
.GetOffsetToPokeAt();
FrameState new_frame_state = CloneFrameState(
jsgraph(), old_frame_state,
OutputFrameStateCombine::PokeAt(old_poke_offset - 1));
effect = ctor_or_instance = graph()->NewNode(
jsgraph()->javascript()->Create(), constructor, new_target,
n.context(), new_frame_state, effect, control);
} else {
return_value = jsgraph()->BooleanConstant(false);
ctor_or_instance =
jsgraph()->ConstantNoHole(current_function, broker());
}
break;
}
current = current_function.map(broker()).prototype(broker());
}
dependencies()->DependOnStablePrototypeChain(
function_map, WhereToStart::kStartAtReceiver, last_function);
for (Edge edge : node->use_edges()) {
Node* const user = edge.from();
if (NodeProperties::IsEffectEdge(edge)) {
edge.UpdateTo(effect);
} else if (NodeProperties::IsControlEdge(edge)) {
edge.UpdateTo(control);
} else {
DCHECK(NodeProperties::IsValueEdge(edge));
switch (ProjectionIndexOf(user->op())) {
case 0:
Replace(user, return_value);
break;
case 1:
Replace(user, ctor_or_instance);
break;
default:
UNREACHABLE();
}
}
}
node->Kill();
return Replace(return_value);
}
Reduction JSNativeContextSpecialization::ReduceJSInstanceOf(Node* node) {
JSInstanceOfNode n(node);
FeedbackParameter const& p = n.Parameters();
Node* object = n.left();
Node* constructor = n.right();
TNode<Object> context = n.context();
FrameState frame_state = n.frame_state();
Effect effect = n.effect();
Control control = n.control();
OptionalJSObjectRef receiver;
HeapObjectMatcher m(constructor);
if (m.HasResolvedValue() && m.Ref(broker()).IsJSObject()) {
receiver = m.Ref(broker()).AsJSObject();
} else if (p.feedback().IsValid()) {
ProcessedFeedback const& feedback =
broker()->GetFeedbackForInstanceOf(FeedbackSource(p.feedback()));
if (feedback.IsInsufficient()) return NoChange();
receiver = feedback.AsInstanceOf().value();
} else {
return NoChange();
}
if (!receiver.has_value()) return NoChange();
MapRef receiver_map = receiver->map(broker());
NameRef name = broker()->has_instance_symbol();
PropertyAccessInfo access_info =
broker()->GetPropertyAccessInfo(receiver_map, name, AccessMode::kLoad);
if (access_info.IsInvalid() || access_info.HasDictionaryHolder()) {
return NoChange();
}
access_info.RecordDependencies(dependencies());
PropertyAccessBuilder access_builder(jsgraph(), broker());
if (access_info.IsNotFound()) {
if (!receiver_map.is_callable()) return NoChange();
dependencies()->DependOnStablePrototypeChains(
access_info.lookup_start_object_maps(), kStartAtPrototype);
access_builder.BuildCheckMaps(constructor, &effect, control,
access_info.lookup_start_object_maps());
NodeProperties::ReplaceValueInput(node, constructor, 0);
NodeProperties::ReplaceValueInput(node, object, 1);
NodeProperties::ReplaceEffectInput(node, effect);
static_assert(n.FeedbackVectorIndex() == 2);
node->RemoveInput(n.FeedbackVectorIndex());
NodeProperties::ChangeOp(node, javascript()->OrdinaryHasInstance());
return Changed(node).FollowedBy(ReduceJSOrdinaryHasInstance(node));
}
if (access_info.IsFastDataConstant()) {
OptionalJSObjectRef holder = access_info.holder();
bool found_on_proto = holder.has_value();
JSObjectRef holder_ref = found_on_proto ? holder.value() : receiver.value();
if (access_info.field_representation().IsDouble()) return NoChange();
OptionalObjectRef constant = holder_ref.GetOwnFastConstantDataProperty(
broker(), access_info.field_representation(), access_info.field_index(),
dependencies());
if (!constant.has_value() || !constant->IsHeapObject() ||
!constant->AsHeapObject().map(broker()).is_callable()) {
return NoChange();
}
if (found_on_proto) {
dependencies()->DependOnStablePrototypeChains(
access_info.lookup_start_object_maps(), kStartAtPrototype,
holder.value());
}
constructor = access_builder.BuildCheckValue(constructor, &effect, control,
*receiver);
access_builder.BuildCheckMaps(constructor, &effect, control,
access_info.lookup_start_object_maps());
Node* continuation_frame_state = CreateStubBuiltinContinuationFrameState(
jsgraph(), Builtin::kToBooleanLazyDeoptContinuation, context, nullptr,
0, frame_state, ContinuationFrameStateMode::LAZY);
Node* target = jsgraph()->ConstantNoHole(*constant, broker());
Node* feedback = jsgraph()->UndefinedConstant();
static_assert(JSCallNode::ArityForArgc(1) + 4 == 8);
node->EnsureInputCount(graph()->zone(), 8);
node->ReplaceInput(JSCallNode::TargetIndex(), target);
node->ReplaceInput(JSCallNode::ReceiverIndex(), constructor);
node->ReplaceInput(JSCallNode::ArgumentIndex(0), object);
node->ReplaceInput(3, feedback);
node->ReplaceInput(4, context);
node->ReplaceInput(5, continuation_frame_state);
node->ReplaceInput(6, effect);
node->ReplaceInput(7, control);
NodeProperties::ChangeOp(
node, javascript()->Call(JSCallNode::ArityForArgc(1), CallFrequency(),
FeedbackSource(),
ConvertReceiverMode::kNotNullOrUndefined));
Node* value = graph()->NewNode(simplified()->ToBoolean(), node);
for (Edge edge : node->use_edges()) {
if (NodeProperties::IsValueEdge(edge) && edge.from() != value) {
edge.UpdateTo(value);
Revisit(edge.from());
}
}
return Changed(node);
}
return NoChange();
}
JSNativeContextSpecialization::InferHasInPrototypeChainResult
JSNativeContextSpecialization::InferHasInPrototypeChain(
Node* receiver, Effect effect, HeapObjectRef prototype) {
ZoneRefSet<Map> receiver_maps;
NodeProperties::InferMapsResult result = NodeProperties::InferMapsUnsafe(
broker(), receiver, effect, &receiver_maps);
if (result == NodeProperties::kNoMaps) return kMayBeInPrototypeChain;
ZoneVector<MapRef> receiver_map_refs(zone());
bool all = true;
bool none = true;
for (MapRef map : receiver_maps) {
receiver_map_refs.push_back(map);
if (result == NodeProperties::kUnreliableMaps && !map.is_stable()) {
return kMayBeInPrototypeChain;
}
while (true) {
if (IsSpecialReceiverInstanceType(map.instance_type())) {
return kMayBeInPrototypeChain;
}
if (!map.IsJSObjectMap()) {
all = false;
break;
}
HeapObjectRef map_prototype = map.prototype(broker());
if (map_prototype.equals(prototype)) {
none = false;
break;
}
map = map_prototype.map(broker());
if (!map.is_stable() || map.is_dictionary_map()) {
return kMayBeInPrototypeChain;
}
if (map.oddball_type(broker()) == OddballType::kNull) {
all = false;
break;
}
}
}
DCHECK_IMPLIES(all, !none);
if (!all && !none) return kMayBeInPrototypeChain;
{
OptionalJSObjectRef last_prototype;
if (all) {
if (!prototype.IsJSObject() || !prototype.map(broker()).is_stable()) {
return kMayBeInPrototypeChain;
}
last_prototype = prototype.AsJSObject();
}
WhereToStart start = result == NodeProperties::kUnreliableMaps
? kStartAtReceiver
: kStartAtPrototype;
dependencies()->DependOnStablePrototypeChains(receiver_map_refs, start,
last_prototype);
}
DCHECK_EQ(all, !none);
return all ? kIsInPrototypeChain : kIsNotInPrototypeChain;
}
Reduction JSNativeContextSpecialization::ReduceJSHasInPrototypeChain(
Node* node) {
DCHECK_EQ(IrOpcode::kJSHasInPrototypeChain, node->opcode());
Node* value = NodeProperties::GetValueInput(node, 0);
Node* prototype = NodeProperties::GetValueInput(node, 1);
Effect effect{NodeProperties::GetEffectInput(node)};
HeapObjectMatcher m(prototype);
if (m.HasResolvedValue()) {
InferHasInPrototypeChainResult result =
InferHasInPrototypeChain(value, effect, m.Ref(broker()));
if (result != kMayBeInPrototypeChain) {
Node* result_in_chain =
jsgraph()->BooleanConstant(result == kIsInPrototypeChain);
ReplaceWithValue(node, result_in_chain);
return Replace(result_in_chain);
}
}
return NoChange();
}
Reduction JSNativeContextSpecialization::ReduceJSOrdinaryHasInstance(
Node* node) {
DCHECK_EQ(IrOpcode::kJSOrdinaryHasInstance, node->opcode());
Node* constructor = NodeProperties::GetValueInput(node, 0);
Node* object = NodeProperties::GetValueInput(node, 1);
HeapObjectMatcher m(constructor);
if (!m.HasResolvedValue()) return NoChange();
if (m.Ref(broker()).IsJSBoundFunction()) {
JSBoundFunctionRef function = m.Ref(broker()).AsJSBoundFunction();
Node* feedback = jsgraph()->UndefinedConstant();
NodeProperties::ReplaceValueInput(node, object,
JSInstanceOfNode::LeftIndex());
NodeProperties::ReplaceValueInput(
node,
jsgraph()->ConstantNoHole(function.bound_target_function(broker()),
broker()),
JSInstanceOfNode::RightIndex());
node->InsertInput(zone(), JSInstanceOfNode::FeedbackVectorIndex(),
feedback);
NodeProperties::ChangeOp(node, javascript()->InstanceOf(FeedbackSource()));
return Changed(node).FollowedBy(ReduceJSInstanceOf(node));
}
if (m.Ref(broker()).IsJSFunction()) {
JSFunctionRef function = m.Ref(broker()).AsJSFunction();
if (!function.map(broker()).has_prototype_slot() ||
!function.has_instance_prototype(broker()) ||
function.PrototypeRequiresRuntimeLookup(broker())) {
return NoChange();
}
HeapObjectRef prototype =
dependencies()->DependOnPrototypeProperty(function);
Node* prototype_constant = jsgraph()->ConstantNoHole(prototype, broker());
NodeProperties::ReplaceValueInput(node, object, 0);
NodeProperties::ReplaceValueInput(node, prototype_constant, 1);
NodeProperties::ChangeOp(node, javascript()->HasInPrototypeChain());
return Changed(node).FollowedBy(ReduceJSHasInPrototypeChain(node));
}
return NoChange();
}
Reduction JSNativeContextSpecialization::ReduceJSPromiseResolve(Node* node) {
DCHECK_EQ(IrOpcode::kJSPromiseResolve, node->opcode());
Node* constructor = NodeProperties::GetValueInput(node, 0);
Node* value = NodeProperties::GetValueInput(node, 1);
Node* context = NodeProperties::GetContextInput(node);
FrameState frame_state{NodeProperties::GetFrameStateInput(node)};
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
HeapObjectMatcher m(constructor);
if (!m.HasResolvedValue() ||
!m.Ref(broker()).equals(native_context().promise_function(broker()))) {
return NoChange();
}
MapInference inference(broker(), value, effect);
if (!inference.HaveMaps() ||
inference.AnyOfInstanceTypesAre(JS_PROMISE_TYPE)) {
return NoChange();
}
if (!dependencies()->DependOnPromiseHookProtector()) return NoChange();
Node* promise = effect =
graph()->NewNode(javascript()->CreatePromise(), context, effect);
Node* parameters[] = {promise};
frame_state = CreateStubBuiltinContinuationFrameState(
jsgraph(), Builtin::kAsyncFunctionLazyDeoptContinuation, context,
parameters, arraysize(parameters), frame_state,
ContinuationFrameStateMode::LAZY);
effect = graph()->NewNode(javascript()->ResolvePromise(), promise, value,
context, frame_state, effect, control);
ReplaceWithValue(node, promise, effect, control);
return Replace(promise);
}
Reduction JSNativeContextSpecialization::ReduceJSResolvePromise(Node* node) {
DCHECK_EQ(IrOpcode::kJSResolvePromise, node->opcode());
Node* promise = NodeProperties::GetValueInput(node, 0);
Node* resolution = NodeProperties::GetValueInput(node, 1);
Node* context = NodeProperties::GetContextInput(node);
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
MapInference inference(broker(), resolution, effect);
if (!inference.HaveMaps()) return NoChange();
ZoneRefSet<Map> const& resolution_maps = inference.GetMaps();
ZoneVector<PropertyAccessInfo> access_infos(graph()->zone());
AccessInfoFactory access_info_factory(broker(), graph()->zone());
for (MapRef map : resolution_maps) {
access_infos.push_back(broker()->GetPropertyAccessInfo(
map, broker()->then_string(), AccessMode::kLoad));
}
PropertyAccessInfo access_info =
access_info_factory.FinalizePropertyAccessInfosAsOne(access_infos,
AccessMode::kLoad);
if (access_info.IsInvalid() || access_info.HasDictionaryHolder()) {
return inference.NoChange();
}
if (!access_info.IsNotFound()) return inference.NoChange();
if (!inference.RelyOnMapsViaStability(dependencies())) {
return inference.NoChange();
}
dependencies()->DependOnStablePrototypeChains(
access_info.lookup_start_object_maps(), kStartAtPrototype);
Node* value = effect =
graph()->NewNode(javascript()->FulfillPromise(), promise, resolution,
context, effect, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
namespace {
FieldAccess ForPropertyCellValue(MachineRepresentation representation,
Type type, OptionalMapRef map, NameRef name) {
WriteBarrierKind kind = kFullWriteBarrier;
if (representation == MachineRepresentation::kTaggedSigned) {
kind = kNoWriteBarrier;
} else if (representation == MachineRepresentation::kTaggedPointer) {
kind = kPointerWriteBarrier;
}
MachineType r = MachineType::TypeForRepresentation(representation);
FieldAccess access = {
kTaggedBase, PropertyCell::kValueOffset, name.object(), map, type, r,
kind, "PropertyCellValue"};
return access;
}
}
Reduction JSNativeContextSpecialization::ReduceGlobalAccess(
Node* node, Node* lookup_start_object, Node* receiver, Node* value,
NameRef name, AccessMode access_mode, Node* key,
PropertyCellRef property_cell, Node* effect) {
if (!property_cell.Cache(broker())) {
TRACE_BROKER_MISSING(broker(), "usable data for " << property_cell);
return NoChange();
}
ObjectRef property_cell_value = property_cell.value(broker());
if (property_cell_value.IsPropertyCellHole()) {
return NoChange();
}
PropertyDetails property_details = property_cell.property_details();
PropertyCellType property_cell_type = property_details.cell_type();
DCHECK_EQ(PropertyKind::kData, property_details.kind());
Node* control = NodeProperties::GetControlInput(node);
if (effect == nullptr) {
effect = NodeProperties::GetEffectInput(node);
}
if (access_mode == AccessMode::kStore) {
DCHECK_EQ(receiver, lookup_start_object);
if (property_details.IsReadOnly()) {
return NoChange();
} else if (property_cell_type == PropertyCellType::kUndefined) {
return NoChange();
} else if (property_cell_type == PropertyCellType::kConstantType) {
if (property_cell_value.IsHeapObject() &&
!property_cell_value.AsHeapObject().map(broker()).is_stable()) {
return NoChange();
}
}
} else if (access_mode == AccessMode::kHas) {
DCHECK_EQ(receiver, lookup_start_object);
if ((property_details.IsConfigurable() || !property_details.IsReadOnly()) &&
property_details.cell_type() != PropertyCellType::kConstant &&
property_details.cell_type() != PropertyCellType::kUndefined)
return NoChange();
}
if (key != nullptr) {
effect = BuildCheckEqualsName(name, key, effect, control);
}
if (lookup_start_object != nullptr) {
effect = graph()->NewNode(
simplified()->CheckMaps(
CheckMapsFlag::kNone,
ZoneRefSet<Map>(
native_context().global_proxy_object(broker()).map(broker()))),
lookup_start_object, effect, control);
}
if (access_mode == AccessMode::kLoad || access_mode == AccessMode::kHas) {
if (!property_details.IsConfigurable() && property_details.IsReadOnly()) {
value = access_mode == AccessMode::kHas
? jsgraph()->TrueConstant()
: jsgraph()->ConstantNoHole(property_cell_value, broker());
} else {
if (property_details.cell_type() != PropertyCellType::kMutable ||
property_details.IsConfigurable()) {
dependencies()->DependOnGlobalProperty(property_cell);
}
if (property_details.cell_type() == PropertyCellType::kConstant ||
property_details.cell_type() == PropertyCellType::kUndefined) {
value = access_mode == AccessMode::kHas
? jsgraph()->TrueConstant()
: jsgraph()->ConstantNoHole(property_cell_value, broker());
DCHECK(!property_cell_value.IsHeapObject() ||
!property_cell_value.IsPropertyCellHole());
} else {
DCHECK_NE(AccessMode::kHas, access_mode);
OptionalMapRef map;
Type property_cell_value_type = Type::NonInternal();
MachineRepresentation representation = MachineRepresentation::kTagged;
if (property_details.cell_type() == PropertyCellType::kConstantType) {
if (property_cell_value.IsSmi()) {
property_cell_value_type = Type::SignedSmall();
representation = MachineRepresentation::kTaggedSigned;
} else if (property_cell_value.IsHeapNumber()) {
property_cell_value_type = Type::Number();
representation = MachineRepresentation::kTaggedPointer;
} else {
MapRef property_cell_value_map =
property_cell_value.AsHeapObject().map(broker());
property_cell_value_type =
Type::For(property_cell_value_map, broker());
representation = MachineRepresentation::kTaggedPointer;
if (property_cell_value_map.is_stable()) {
dependencies()->DependOnStableMap(property_cell_value_map);
map = property_cell_value_map;
}
}
}
value = effect = graph()->NewNode(
simplified()->LoadField(ForPropertyCellValue(
representation, property_cell_value_type, map, name)),
jsgraph()->ConstantNoHole(property_cell, broker()), effect,
control);
}
}
} else if (access_mode == AccessMode::kStore) {
DCHECK_EQ(receiver, lookup_start_object);
DCHECK(!property_details.IsReadOnly());
switch (property_details.cell_type()) {
case PropertyCellType::kConstant: {
dependencies()->DependOnGlobalProperty(property_cell);
Node* check = graph()->NewNode(
simplified()->ReferenceEqual(), value,
jsgraph()->ConstantNoHole(property_cell_value, broker()));
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kValueMismatch), check,
effect, control);
break;
}
case PropertyCellType::kConstantType: {
dependencies()->DependOnGlobalProperty(property_cell);
Type property_cell_value_type;
MachineRepresentation representation = MachineRepresentation::kTagged;
if (property_cell_value.IsHeapObject()) {
MapRef property_cell_value_map =
property_cell_value.AsHeapObject().map(broker());
dependencies()->DependOnStableMap(property_cell_value_map);
value = effect = graph()->NewNode(simplified()->CheckHeapObject(),
value, effect, control);
effect = graph()->NewNode(
simplified()->CheckMaps(CheckMapsFlag::kNone,
ZoneRefSet<Map>(property_cell_value_map)),
value, effect, control);
property_cell_value_type = Type::OtherInternal();
representation = MachineRepresentation::kTaggedPointer;
} else {
value = effect = graph()->NewNode(
simplified()->CheckSmi(FeedbackSource()), value, effect, control);
property_cell_value_type = Type::SignedSmall();
representation = MachineRepresentation::kTaggedSigned;
}
effect =
graph()->NewNode(simplified()->StoreField(ForPropertyCellValue(
representation, property_cell_value_type,
OptionalMapRef(), name)),
jsgraph()->ConstantNoHole(property_cell, broker()),
value, effect, control);
break;
}
case PropertyCellType::kMutable: {
dependencies()->DependOnGlobalProperty(property_cell);
effect =
graph()->NewNode(simplified()->StoreField(ForPropertyCellValue(
MachineRepresentation::kTagged,
Type::NonInternal(), OptionalMapRef(), name)),
jsgraph()->ConstantNoHole(property_cell, broker()),
value, effect, control);
break;
}
case PropertyCellType::kUndefined:
case PropertyCellType::kInTransition:
UNREACHABLE();
}
} else {
return NoChange();
}
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
Reduction JSNativeContextSpecialization::ReduceJSLoadGlobal(Node* node) {
JSLoadGlobalNode n(node);
LoadGlobalParameters const& p = n.Parameters();
if (!p.feedback().IsValid()) return NoChange();
ProcessedFeedback const& processed =
broker()->GetFeedbackForGlobalAccess(FeedbackSource(p.feedback()));
if (processed.IsInsufficient()) return NoChange();
GlobalAccessFeedback const& feedback = processed.AsGlobalAccess();
if (feedback.IsScriptContextSlot()) {
Effect effect = n.effect();
Control control = n.control();
Node* script_context =
jsgraph()->ConstantNoHole(feedback.script_context(), broker());
Node* value;
if (v8_flags.script_context_cells && !feedback.immutable()) {
value = effect = control =
graph()->NewNode(javascript()->LoadContext(0, feedback.slot_index()),
script_context, effect, control);
} else {
value = effect =
graph()->NewNode(javascript()->LoadContextNoCell(
0, feedback.slot_index(), feedback.immutable()),
script_context, effect);
}
ReplaceWithValue(node, value, effect, control);
return Replace(value);
} else if (feedback.IsPropertyCell()) {
return ReduceGlobalAccess(node, nullptr, nullptr, nullptr, p.name(),
AccessMode::kLoad, nullptr,
feedback.property_cell());
} else {
DCHECK(feedback.IsMegamorphic());
return NoChange();
}
}
Reduction JSNativeContextSpecialization::ReduceJSStoreGlobal(Node* node) {
JSStoreGlobalNode n(node);
StoreGlobalParameters const& p = n.Parameters();
Node* value = n.value();
if (!p.feedback().IsValid()) return NoChange();
ProcessedFeedback const& processed =
broker()->GetFeedbackForGlobalAccess(FeedbackSource(p.feedback()));
if (processed.IsInsufficient()) return NoChange();
GlobalAccessFeedback const& feedback = processed.AsGlobalAccess();
if (feedback.IsScriptContextSlot()) {
if (feedback.immutable()) return NoChange();
Node* effect = n.effect();
Node* control = n.control();
Node* frame_state = n.frame_state();
Node* script_context =
jsgraph()->ConstantNoHole(feedback.script_context(), broker());
if (v8_flags.script_context_cells) {
effect = control =
graph()->NewNode(javascript()->StoreContext(0, feedback.slot_index()),
value, script_context, frame_state, effect, control);
} else {
effect = graph()->NewNode(
javascript()->StoreContextNoCell(0, feedback.slot_index()), value,
script_context, effect, control);
}
ReplaceWithValue(node, value, effect, control);
return Replace(value);
} else if (feedback.IsPropertyCell()) {
return ReduceGlobalAccess(node, nullptr, nullptr, value, p.name(),
AccessMode::kStore, nullptr,
feedback.property_cell());
} else {
DCHECK(feedback.IsMegamorphic());
return NoChange();
}
}
Reduction JSNativeContextSpecialization::ReduceMegaDOMPropertyAccess(
Node* node, Node* value, MegaDOMPropertyAccessFeedback const& feedback,
FeedbackSource const& source) {
DCHECK(node->opcode() == IrOpcode::kJSLoadNamed ||
node->opcode() == IrOpcode::kJSLoadProperty);
static_assert(JSLoadNamedNode::ObjectIndex() == 0 &&
JSLoadPropertyNode::ObjectIndex() == 0,
"Assumptions about ObjectIndex have changed, please update "
"this function.");
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* lookup_start_object = NodeProperties::GetValueInput(node, 0);
if (!dependencies()->DependOnMegaDOMProtector()) {
return NoChange();
}
FunctionTemplateInfoRef function_template_info = feedback.info();
int16_t range_start =
function_template_info.allowed_receiver_instance_type_range_start();
int16_t range_end =
function_template_info.allowed_receiver_instance_type_range_end();
DCHECK_IMPLIES(range_start == 0, range_end == 0);
DCHECK_LE(range_start, range_end);
Node* receiver_map = effect =
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
lookup_start_object, effect, control);
Node* receiver_instance_type = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForMapInstanceType()),
receiver_map, effect, control);
if (v8_flags.experimental_embedder_instance_types && range_start != 0) {
Node* diff_to_start =
graph()->NewNode(simplified()->NumberSubtract(), receiver_instance_type,
jsgraph()->ConstantNoHole(range_start));
Node* range_length = jsgraph()->ConstantNoHole(range_end - range_start);
Node* check = graph()->NewNode(simplified()->NumberLessThanOrEqual(),
diff_to_start, range_length);
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kWrongInstanceType), check,
effect, control);
} else if (function_template_info.is_signature_undefined(broker())) {
Node* check =
graph()->NewNode(simplified()->NumberEqual(), receiver_instance_type,
jsgraph()->ConstantNoHole(JS_API_OBJECT_TYPE));
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kWrongInstanceType), check,
effect, control);
} else {
Callable callable = Builtins::CallableFor(
isolate(), Builtin::kCallFunctionTemplate_CheckCompatibleReceiver);
int stack_arg_count = callable.descriptor().GetStackParameterCount() +
1 ;
CallDescriptor* call_descriptor = Linkage::GetStubCallDescriptor(
graph()->zone(), callable.descriptor(), stack_arg_count,
CallDescriptor::kNeedsFrameState, Operator::kNoProperties);
Node* inputs[8] = {
jsgraph()->HeapConstantNoHole(callable.code()),
jsgraph()->ConstantNoHole(function_template_info, broker()),
jsgraph()->Int32Constant(stack_arg_count),
lookup_start_object,
jsgraph()->ConstantNoHole(native_context(), broker()),
frame_state,
effect,
control};
value = effect = control =
graph()->NewNode(common()->Call(call_descriptor), 8, inputs);
return Replace(value);
}
value = InlineApiCall(lookup_start_object, frame_state, nullptr ,
&effect, &control, function_template_info, source);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
Reduction JSNativeContextSpecialization::ReduceNamedAccess(
Node* node, Node* value, NamedAccessFeedback const& feedback,
AccessMode access_mode, Node* key) {
DCHECK(node->opcode() == IrOpcode::kJSLoadNamed ||
node->opcode() == IrOpcode::kJSSetNamedProperty ||
node->opcode() == IrOpcode::kJSLoadProperty ||
node->opcode() == IrOpcode::kJSSetKeyedProperty ||
node->opcode() == IrOpcode::kJSDefineNamedOwnProperty ||
node->opcode() == IrOpcode::kJSDefineKeyedOwnPropertyInLiteral ||
node->opcode() == IrOpcode::kJSHasProperty ||
node->opcode() == IrOpcode::kJSLoadNamedFromSuper ||
node->opcode() == IrOpcode::kJSDefineKeyedOwnProperty);
static_assert(JSLoadNamedNode::ObjectIndex() == 0 &&
JSSetNamedPropertyNode::ObjectIndex() == 0 &&
JSLoadPropertyNode::ObjectIndex() == 0 &&
JSSetKeyedPropertyNode::ObjectIndex() == 0 &&
JSDefineNamedOwnPropertyNode::ObjectIndex() == 0 &&
JSSetNamedPropertyNode::ObjectIndex() == 0 &&
JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 &&
JSHasPropertyNode::ObjectIndex() == 0 &&
JSDefineKeyedOwnPropertyNode::ObjectIndex() == 0);
static_assert(JSLoadNamedFromSuperNode::ReceiverIndex() == 0);
Node* context = NodeProperties::GetContextInput(node);
FrameState frame_state{NodeProperties::GetFrameStateInput(node)};
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
Node* receiver = NodeProperties::GetValueInput(node, 0);
Node* lookup_start_object;
if (node->opcode() == IrOpcode::kJSLoadNamedFromSuper) {
DCHECK(v8_flags.super_ic);
JSLoadNamedFromSuperNode n(node);
lookup_start_object = effect =
BuildLoadPrototypeFromObject(n.home_object(), effect, control);
} else {
lookup_start_object = receiver;
}
ZoneVector<MapRef> inferred_maps(zone());
if (!InferMaps(lookup_start_object, effect, &inferred_maps)) {
for (MapRef map : feedback.maps()) {
inferred_maps.push_back(map);
}
}
RemoveImpossibleMaps(lookup_start_object, &inferred_maps);
if (inferred_maps.size() == 1) {
MapRef lookup_start_object_map = inferred_maps[0];
if (lookup_start_object_map.equals(
native_context().global_proxy_object(broker()).map(broker()))) {
if (!native_context().GlobalIsDetached(broker())) {
OptionalPropertyCellRef cell =
native_context().global_object(broker()).GetPropertyCell(
broker(), feedback.name());
if (!cell.has_value()) return NoChange();
return ReduceGlobalAccess(node, lookup_start_object, receiver, value,
feedback.name(), access_mode, key, *cell,
effect);
}
}
}
ZoneVector<PropertyAccessInfo> access_infos(zone());
{
ZoneVector<PropertyAccessInfo> access_infos_for_feedback(zone());
for (MapRef map : inferred_maps) {
if (map.is_deprecated()) continue;
if (InstanceTypeChecker::IsAlwaysSharedSpaceJSObject(
map.instance_type()) &&
access_mode == AccessMode::kStore) {
return NoChange();
}
PropertyAccessInfo access_info =
broker()->GetPropertyAccessInfo(map, feedback.name(), access_mode);
access_infos_for_feedback.push_back(access_info);
}
AccessInfoFactory access_info_factory(broker(), graph()->zone());
if (!access_info_factory.FinalizePropertyAccessInfos(
access_infos_for_feedback, access_mode, &access_infos)) {
return NoChange();
}
}
if (key != nullptr) {
effect = BuildCheckEqualsName(feedback.original_name_maybe_thin(), key,
effect, control);
}
ZoneVector<Node*> if_exception_nodes(zone());
ZoneVector<Node*>* if_exceptions = nullptr;
Node* if_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &if_exception)) {
if_exceptions = &if_exception_nodes;
}
PropertyAccessBuilder access_builder(jsgraph(), broker());
if (access_infos.size() == 1) {
const PropertyAccessInfo& access_info = access_infos.front();
if (receiver != lookup_start_object) {
access_builder.BuildCheckMaps(
lookup_start_object, &effect, control,
access_info.lookup_start_object_maps(),
feedback.has_deprecated_map_without_migration_target());
if (HasOnlyStringWrapperMaps(broker(),
access_info.lookup_start_object_maps())) {
lookup_start_object = effect =
graph()->NewNode(common()->TypeGuard(Type::StringWrapper()),
lookup_start_object, effect, control);
} else if (HasOnlyNonResizableTypedArrayMaps(
broker(), access_info.lookup_start_object_maps())) {
lookup_start_object = effect =
graph()->NewNode(common()->TypeGuard(Type::TypedArray()),
lookup_start_object, effect, control);
}
} else if (!access_builder.TryBuildStringCheck(
broker(), access_info.lookup_start_object_maps(), &receiver,
&effect, control) &&
!access_builder.TryBuildNumberCheck(
broker(), access_info.lookup_start_object_maps(), &receiver,
&effect, control)) {
DCHECK_EQ(receiver, lookup_start_object);
if (HasNumberMaps(broker(), access_info.lookup_start_object_maps())) {
Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), receiver);
Node* branch = graph()->NewNode(common()->Branch(), check, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Control if_false{graph()->NewNode(common()->IfFalse(), branch)};
Effect efalse = effect;
access_builder.BuildCheckMaps(
receiver, &efalse, if_false, access_info.lookup_start_object_maps(),
feedback.has_deprecated_map_without_migration_target());
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
effect =
graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
} else {
access_builder.BuildCheckMaps(
receiver, &effect, control, access_info.lookup_start_object_maps(),
feedback.has_deprecated_map_without_migration_target());
}
if (HasOnlyStringWrapperMaps(broker(),
access_info.lookup_start_object_maps())) {
lookup_start_object = receiver = effect =
graph()->NewNode(common()->TypeGuard(Type::StringWrapper()),
lookup_start_object, effect, control);
} else if (HasOnlyNonResizableTypedArrayMaps(
broker(), access_info.lookup_start_object_maps())) {
lookup_start_object = receiver = effect =
graph()->NewNode(common()->TypeGuard(Type::TypedArray()),
lookup_start_object, effect, control);
}
} else {
lookup_start_object = receiver;
}
std::optional<ValueEffectControl> continuation = BuildPropertyAccess(
lookup_start_object, receiver, value, context, frame_state, effect,
control, feedback.name(), if_exceptions, access_info, access_mode);
if (!continuation) {
return NoChange();
}
value = continuation->value();
effect = continuation->effect();
control = continuation->control();
} else {
ZoneVector<Node*> values(zone());
ZoneVector<Node*> effects(zone());
ZoneVector<Node*> controls(zone());
Node* receiverissmi_control = nullptr;
Node* receiverissmi_effect = effect;
if (receiver == lookup_start_object) {
bool receiverissmi_possible = false;
for (PropertyAccessInfo const& access_info : access_infos) {
if (HasNumberMaps(broker(), access_info.lookup_start_object_maps())) {
receiverissmi_possible = true;
break;
}
}
if (receiverissmi_possible) {
Node* check = graph()->NewNode(simplified()->ObjectIsSmi(), receiver);
Node* branch = graph()->NewNode(common()->Branch(), check, control);
control = graph()->NewNode(common()->IfFalse(), branch);
receiverissmi_control = graph()->NewNode(common()->IfTrue(), branch);
receiverissmi_effect = effect;
}
}
Node* fallthrough_control = control;
for (size_t j = 0; j < access_infos.size(); ++j) {
PropertyAccessInfo const& access_info = access_infos[j];
Node* this_value = value;
Node* this_lookup_start_object = lookup_start_object;
Node* this_receiver = receiver;
Effect this_effect = effect;
Control this_control{fallthrough_control};
ZoneVector<MapRef> const& lookup_start_object_maps =
access_info.lookup_start_object_maps();
{
bool insert_map_guard = true;
if (j == access_infos.size() - 1) {
access_builder.BuildCheckMaps(
lookup_start_object, &this_effect, this_control,
lookup_start_object_maps,
feedback.has_deprecated_map_without_migration_target());
fallthrough_control = nullptr;
insert_map_guard = false;
} else {
ZoneRefSet<Map> maps(lookup_start_object_maps.begin(),
lookup_start_object_maps.end(), graph()->zone());
Node* check = this_effect =
graph()->NewNode(simplified()->CompareMaps(maps),
lookup_start_object, this_effect, this_control);
Node* branch =
graph()->NewNode(common()->Branch(), check, this_control);
fallthrough_control = graph()->NewNode(common()->IfFalse(), branch);
this_control = graph()->NewNode(common()->IfTrue(), branch);
}
if (HasNumberMaps(broker(), lookup_start_object_maps)) {
DCHECK_EQ(receiver, lookup_start_object);
DCHECK_NOT_NULL(receiverissmi_effect);
DCHECK_NOT_NULL(receiverissmi_control);
this_control = graph()->NewNode(common()->Merge(2), this_control,
receiverissmi_control);
this_effect = graph()->NewNode(common()->EffectPhi(2), this_effect,
receiverissmi_effect, this_control);
receiverissmi_effect = receiverissmi_control = nullptr;
insert_map_guard = false;
}
if (insert_map_guard) {
ZoneRefSet<Map> maps(lookup_start_object_maps.begin(),
lookup_start_object_maps.end(), graph()->zone());
this_effect =
graph()->NewNode(simplified()->MapGuard(maps),
lookup_start_object, this_effect, this_control);
}
if (HasOnlyStringMaps(broker(), lookup_start_object_maps)) {
DCHECK_EQ(receiver, lookup_start_object);
this_lookup_start_object = this_receiver = this_effect =
graph()->NewNode(common()->TypeGuard(Type::String()),
lookup_start_object, this_effect, this_control);
} else if (HasOnlyStringWrapperMaps(broker(),
lookup_start_object_maps)) {
bool receiver_is_lookup_start =
this_lookup_start_object == this_receiver;
DCHECK_IMPLIES(access_mode != AccessMode::kLoad,
receiver_is_lookup_start);
this_lookup_start_object = this_effect =
graph()->NewNode(common()->TypeGuard(Type::StringWrapper()),
lookup_start_object, this_effect, this_control);
if (receiver_is_lookup_start) {
this_receiver = this_lookup_start_object;
}
} else if (HasOnlyNonResizableTypedArrayMaps(
broker(), lookup_start_object_maps)) {
bool receiver_is_lookup_start =
this_lookup_start_object == this_receiver;
DCHECK_IMPLIES(access_mode != AccessMode::kLoad,
receiver_is_lookup_start);
this_lookup_start_object = this_effect =
graph()->NewNode(common()->TypeGuard(Type::TypedArray()),
lookup_start_object, this_effect, this_control);
if (receiver_is_lookup_start) {
this_receiver = this_lookup_start_object;
}
}
}
std::optional<ValueEffectControl> continuation = BuildPropertyAccess(
this_lookup_start_object, this_receiver, this_value, context,
frame_state, this_effect, this_control, feedback.name(),
if_exceptions, access_info, access_mode);
if (!continuation) {
return NoChange();
}
values.push_back(continuation->value());
effects.push_back(continuation->effect());
controls.push_back(continuation->control());
}
DCHECK_NULL(fallthrough_control);
int const control_count = static_cast<int>(controls.size());
if (control_count == 0) {
value = effect = control = jsgraph()->Dead();
} else if (control_count == 1) {
value = values.front();
effect = effects.front();
control = controls.front();
} else {
control = graph()->NewNode(common()->Merge(control_count), control_count,
&controls.front());
values.push_back(control);
value = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, control_count),
control_count + 1, &values.front());
effects.push_back(control);
effect = graph()->NewNode(common()->EffectPhi(control_count),
control_count + 1, &effects.front());
}
}
if (!if_exception_nodes.empty()) {
DCHECK_NOT_NULL(if_exception);
DCHECK_EQ(if_exceptions, &if_exception_nodes);
int const if_exception_count = static_cast<int>(if_exceptions->size());
Node* merge = graph()->NewNode(common()->Merge(if_exception_count),
if_exception_count, &if_exceptions->front());
if_exceptions->push_back(merge);
Node* ephi =
graph()->NewNode(common()->EffectPhi(if_exception_count),
if_exception_count + 1, &if_exceptions->front());
Node* phi = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, if_exception_count),
if_exception_count + 1, &if_exceptions->front());
ReplaceWithValue(if_exception, phi, ephi, merge);
}
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
Reduction JSNativeContextSpecialization::ReduceJSLoadNamed(Node* node) {
JSLoadNamedNode n(node);
NamedAccess const& p = n.Parameters();
Node* const receiver = n.object();
NameRef name = p.name();
HeapObjectMatcher m(receiver);
if (m.HasResolvedValue()) {
ObjectRef object = m.Ref(broker());
if (object.IsJSFunction() && name.equals(broker()->prototype_string())) {
JSFunctionRef function = object.AsJSFunction();
if (!function.map(broker()).has_prototype_slot() ||
!function.has_instance_prototype(broker()) ||
function.PrototypeRequiresRuntimeLookup(broker())) {
return NoChange();
}
HeapObjectRef prototype =
dependencies()->DependOnPrototypeProperty(function);
Node* value = jsgraph()->ConstantNoHole(prototype, broker());
ReplaceWithValue(node, value);
return Replace(value);
} else if (object.IsString() && name.equals(broker()->length_string())) {
Node* value = jsgraph()->ConstantNoHole(object.AsString().length());
ReplaceWithValue(node, value);
return Replace(value);
}
}
if (!p.feedback().IsValid()) return NoChange();
return ReducePropertyAccess(node, nullptr, name, jsgraph()->Dead(),
FeedbackSource(p.feedback()), AccessMode::kLoad);
}
Reduction JSNativeContextSpecialization::ReduceJSLoadNamedFromSuper(
Node* node) {
JSLoadNamedFromSuperNode n(node);
NamedAccess const& p = n.Parameters();
NameRef name = p.name();
if (!p.feedback().IsValid()) return NoChange();
return ReducePropertyAccess(node, nullptr, name, jsgraph()->Dead(),
FeedbackSource(p.feedback()), AccessMode::kLoad);
}
Reduction JSNativeContextSpecialization::ReduceJSGetIterator(Node* node) {
JSGetIteratorNode n(node);
GetIteratorParameters const& p = n.Parameters();
TNode<Object> receiver = n.receiver();
TNode<Object> context = n.context();
FrameState frame_state = n.frame_state();
Effect effect = n.effect();
Control control = n.control();
Node* iterator_exception_node = nullptr;
Node* if_exception_merge = nullptr;
Node* if_exception_effect_phi = nullptr;
Node* if_exception_phi = nullptr;
bool has_exception_node =
NodeProperties::IsExceptionalCall(node, &iterator_exception_node);
int exception_node_index = 0;
if (has_exception_node) {
DCHECK_NOT_NULL(iterator_exception_node);
Node* dead_node = jsgraph()->Dead();
if_exception_merge =
graph()->NewNode(common()->Merge(5), dead_node, dead_node, dead_node,
dead_node, dead_node);
if_exception_effect_phi =
graph()->NewNode(common()->EffectPhi(5), dead_node, dead_node,
dead_node, dead_node, dead_node, if_exception_merge);
if_exception_phi = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, 5), dead_node, dead_node,
dead_node, dead_node, dead_node, if_exception_merge);
ReplaceWithValue(iterator_exception_node, if_exception_phi,
if_exception_effect_phi, if_exception_merge);
if_exception_merge->ReplaceInput(exception_node_index,
iterator_exception_node);
if_exception_effect_phi->ReplaceInput(exception_node_index,
iterator_exception_node);
if_exception_phi->ReplaceInput(exception_node_index,
iterator_exception_node);
exception_node_index++;
}
NameRef iterator_symbol = broker()->iterator_symbol();
const Operator* load_op =
javascript()->LoadNamed(iterator_symbol, p.loadFeedback());
Node* call_slot = jsgraph()->SmiConstant(p.callFeedback().slot.ToInt());
Node* call_feedback = jsgraph()->HeapConstantNoHole(p.callFeedback().vector);
Node* lazy_deopt_parameters[] = {receiver, call_slot, call_feedback};
Node* lazy_deopt_frame_state = CreateStubBuiltinContinuationFrameState(
jsgraph(), Builtin::kGetIteratorWithFeedbackLazyDeoptContinuation,
context, lazy_deopt_parameters, arraysize(lazy_deopt_parameters),
frame_state, ContinuationFrameStateMode::LAZY);
Node* load_property =
graph()->NewNode(load_op, receiver, n.feedback_vector(), context,
lazy_deopt_frame_state, effect, control);
effect = load_property;
control = load_property;
if (has_exception_node) {
Node* if_exception =
graph()->NewNode(common()->IfException(), effect, control);
if_exception_merge->ReplaceInput(exception_node_index, if_exception);
if_exception_phi->ReplaceInput(exception_node_index, if_exception);
if_exception_effect_phi->ReplaceInput(exception_node_index, if_exception);
exception_node_index++;
control = graph()->NewNode(common()->IfSuccess(), control);
}
Node* check = graph()->NewNode(simplified()->ReferenceEqual(), load_property,
jsgraph()->UndefinedConstant());
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control);
{
Node* if_not_iterator = graph()->NewNode(common()->IfTrue(), branch);
Node* effect_not_iterator = effect;
Node* control_not_iterator = if_not_iterator;
Node* call_runtime = effect_not_iterator = control_not_iterator =
graph()->NewNode(
javascript()->CallRuntime(Runtime::kThrowIteratorError, 1),
receiver, context, frame_state, effect_not_iterator,
control_not_iterator);
if (has_exception_node) {
Node* if_exception = graph()->NewNode(
common()->IfException(), effect_not_iterator, control_not_iterator);
if_exception_merge->ReplaceInput(exception_node_index, if_exception);
if_exception_phi->ReplaceInput(exception_node_index, if_exception);
if_exception_effect_phi->ReplaceInput(exception_node_index, if_exception);
exception_node_index++;
control_not_iterator =
graph()->NewNode(common()->IfSuccess(), control_not_iterator);
}
Node* throw_node =
graph()->NewNode(common()->Throw(), call_runtime, control_not_iterator);
MergeControlToEnd(graph(), common(), throw_node);
}
control = graph()->NewNode(common()->IfFalse(), branch);
Node* parameters[] = {receiver, load_property, call_slot, call_feedback};
Node* eager_deopt_frame_state = CreateStubBuiltinContinuationFrameState(
jsgraph(), Builtin::kCallIteratorWithFeedback, context, parameters,
arraysize(parameters), frame_state, ContinuationFrameStateMode::EAGER);
Node* deopt_checkpoint = graph()->NewNode(
common()->Checkpoint(), eager_deopt_frame_state, effect, control);
effect = deopt_checkpoint;
ProcessedFeedback const& feedback =
broker()->GetFeedbackForCall(p.callFeedback());
SpeculationMode mode = feedback.IsInsufficient()
? SpeculationMode::kDisallowSpeculation
: feedback.AsCall().speculation_mode();
const Operator* call_op = javascript()->Call(
JSCallNode::ArityForArgc(0), CallFrequency(), p.callFeedback(),
ConvertReceiverMode::kNotNullOrUndefined, mode,
CallFeedbackRelation::kTarget);
Node* call_lazy_deopt_frame_state = CreateStubBuiltinContinuationFrameState(
jsgraph(), Builtin::kCallIteratorWithFeedbackLazyDeoptContinuation,
context, nullptr, 0, frame_state, ContinuationFrameStateMode::LAZY);
Node* call_property = effect = control =
graph()->NewNode(call_op, load_property, receiver, n.feedback_vector(),
context, call_lazy_deopt_frame_state, effect, control);
if (has_exception_node) {
Node* if_exception =
graph()->NewNode(common()->IfException(), effect, control);
if_exception_merge->ReplaceInput(exception_node_index, if_exception);
if_exception_phi->ReplaceInput(exception_node_index, if_exception);
if_exception_effect_phi->ReplaceInput(exception_node_index, if_exception);
exception_node_index++;
control = graph()->NewNode(common()->IfSuccess(), control);
}
Node* is_receiver =
graph()->NewNode(simplified()->ObjectIsReceiver(), call_property);
Node* branch_node = graph()->NewNode(common()->Branch(BranchHint::kTrue),
is_receiver, control);
{
Node* if_not_receiver = graph()->NewNode(common()->IfFalse(), branch_node);
Node* effect_not_receiver = effect;
Node* control_not_receiver = if_not_receiver;
Node* call_runtime = effect_not_receiver = control_not_receiver =
graph()->NewNode(
javascript()->CallRuntime(Runtime::kThrowSymbolIteratorInvalid, 0),
context, frame_state, effect_not_receiver, control_not_receiver);
if (has_exception_node) {
Node* if_exception = graph()->NewNode(
common()->IfException(), effect_not_receiver, control_not_receiver);
if_exception_merge->ReplaceInput(exception_node_index, if_exception);
if_exception_phi->ReplaceInput(exception_node_index, if_exception);
if_exception_effect_phi->ReplaceInput(exception_node_index, if_exception);
exception_node_index++;
control_not_receiver =
graph()->NewNode(common()->IfSuccess(), control_not_receiver);
}
Node* throw_node =
graph()->NewNode(common()->Throw(), call_runtime, control_not_receiver);
MergeControlToEnd(graph(), common(), throw_node);
}
Node* if_receiver = graph()->NewNode(common()->IfTrue(), branch_node);
ReplaceWithValue(node, call_property, effect, if_receiver);
if (has_exception_node) {
DCHECK_EQ(exception_node_index, if_exception_merge->InputCount());
DCHECK_EQ(exception_node_index, if_exception_effect_phi->InputCount() - 1);
DCHECK_EQ(exception_node_index, if_exception_phi->InputCount() - 1);
#ifdef DEBUG
for (Node* input : if_exception_merge->inputs()) {
DCHECK(!input->IsDead());
}
for (Node* input : if_exception_effect_phi->inputs()) {
DCHECK(!input->IsDead());
}
for (Node* input : if_exception_phi->inputs()) {
DCHECK(!input->IsDead());
}
#endif
}
return Replace(if_receiver);
}
Reduction JSNativeContextSpecialization::ReduceJSSetNamedProperty(Node* node) {
JSSetNamedPropertyNode n(node);
NamedAccess const& p = n.Parameters();
if (!p.feedback().IsValid()) return NoChange();
return ReducePropertyAccess(node, nullptr, p.name(), n.value(),
FeedbackSource(p.feedback()), AccessMode::kStore);
}
Reduction JSNativeContextSpecialization::ReduceJSDefineNamedOwnProperty(
Node* node) {
JSDefineNamedOwnPropertyNode n(node);
DefineNamedOwnPropertyParameters const& p = n.Parameters();
if (!p.feedback().IsValid()) return NoChange();
return ReducePropertyAccess(node, nullptr, p.name(), n.value(),
FeedbackSource(p.feedback()),
AccessMode::kStoreInLiteral);
}
Reduction JSNativeContextSpecialization::ReduceElementAccessOnString(
Node* node, Node* index, Node* value, KeyedAccessMode const& keyed_mode) {
Node* receiver = NodeProperties::GetValueInput(node, 0);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
if (keyed_mode.access_mode() == AccessMode::kStore) return NoChange();
if (keyed_mode.access_mode() == AccessMode::kHas) return NoChange();
receiver = effect = graph()->NewNode(
simplified()->CheckString(FeedbackSource()), receiver, effect, control);
Node* length = graph()->NewNode(simplified()->StringLength(), receiver);
value = BuildIndexedStringLoad(receiver, index, length, &effect, &control,
keyed_mode.load_mode());
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
namespace {
OptionalJSTypedArrayRef GetTypedArrayConstant(JSHeapBroker* broker,
Node* receiver) {
HeapObjectMatcher m(receiver);
if (!m.HasResolvedValue()) return std::nullopt;
ObjectRef object = m.Ref(broker);
if (!object.IsJSTypedArray()) return std::nullopt;
JSTypedArrayRef typed_array = object.AsJSTypedArray();
if (typed_array.is_on_heap()) return std::nullopt;
return typed_array;
}
}
void JSNativeContextSpecialization::RemoveImpossibleMaps(
Node* object, ZoneVector<MapRef>* maps) const {
OptionalMapRef root_map = InferRootMap(object);
if (root_map.has_value() && !root_map->is_abandoned_prototype_map()) {
maps->erase(
std::remove_if(maps->begin(), maps->end(),
[root_map, this](MapRef map) {
return map.is_abandoned_prototype_map() ||
!map.FindRootMap(broker()).equals(*root_map);
}),
maps->end());
}
}
ElementAccessFeedback const&
JSNativeContextSpecialization::TryRefineElementAccessFeedback(
ElementAccessFeedback const& feedback, Node* receiver,
Effect effect) const {
AccessMode access_mode = feedback.keyed_mode().access_mode();
bool use_inference =
access_mode == AccessMode::kLoad || access_mode == AccessMode::kHas;
if (!use_inference) return feedback;
ZoneVector<MapRef> inferred_maps(zone());
if (!InferMaps(receiver, effect, &inferred_maps)) return feedback;
RemoveImpossibleMaps(receiver, &inferred_maps);
return feedback.Refine(broker(), inferred_maps);
}
Reduction JSNativeContextSpecialization::ReduceElementAccess(
Node* node, Node* index, Node* value,
ElementAccessFeedback const& feedback) {
DCHECK(node->opcode() == IrOpcode::kJSLoadProperty ||
node->opcode() == IrOpcode::kJSSetKeyedProperty ||
node->opcode() == IrOpcode::kJSStoreInArrayLiteral ||
node->opcode() == IrOpcode::kJSDefineKeyedOwnPropertyInLiteral ||
node->opcode() == IrOpcode::kJSHasProperty ||
node->opcode() == IrOpcode::kJSDefineKeyedOwnProperty);
static_assert(JSLoadPropertyNode::ObjectIndex() == 0 &&
JSSetKeyedPropertyNode::ObjectIndex() == 0 &&
JSStoreInArrayLiteralNode::ArrayIndex() == 0 &&
JSDefineKeyedOwnPropertyInLiteralNode::ObjectIndex() == 0 &&
JSHasPropertyNode::ObjectIndex() == 0);
Node* receiver = NodeProperties::GetValueInput(node, 0);
Effect effect{NodeProperties::GetEffectInput(node)};
Control control{NodeProperties::GetControlInput(node)};
Node* context = NodeProperties::GetContextInput(node);
FrameState frame_state{NodeProperties::GetFrameStateInput(node)};
if (feedback.transition_groups().empty()) return NoChange();
ElementAccessFeedback const& refined_feedback =
TryRefineElementAccessFeedback(feedback, receiver, effect);
LanguageMode language_mode = LanguageMode::kSloppy;
if (node->opcode() == IrOpcode::kJSSetKeyedProperty) {
language_mode = JSSetKeyedPropertyNode(node).Parameters().language_mode();
}
AccessMode access_mode = refined_feedback.keyed_mode().access_mode();
if ((access_mode == AccessMode::kLoad || access_mode == AccessMode::kHas) &&
receiver->opcode() == IrOpcode::kHeapConstant) {
Reduction reduction = ReduceElementLoadFromHeapConstant(
node, index, access_mode, refined_feedback.keyed_mode().load_mode());
if (reduction.Changed()) return reduction;
}
if (!refined_feedback.transition_groups().empty() &&
refined_feedback.HasOnlyStringMaps(broker())) {
return ReduceElementAccessOnString(node, index, value,
refined_feedback.keyed_mode());
}
AccessInfoFactory access_info_factory(broker(), graph()->zone());
ZoneVector<ElementAccessInfo> access_infos(zone());
if (!access_info_factory.ComputeElementAccessInfos(refined_feedback,
&access_infos) ||
access_infos.empty()) {
return NoChange();
}
if (access_mode == AccessMode::kDefine) {
for (const ElementAccessInfo& access_info : access_infos) {
if (IsTypedArrayOrRabGsabTypedArrayElementsKind(
access_info.elements_kind())) {
return NoChange();
}
}
}
if (access_mode == AccessMode::kStore) {
ZoneVector<MapRef> prototype_maps(zone());
for (ElementAccessInfo const& access_info : access_infos) {
for (MapRef receiver_map : access_info.lookup_start_object_maps()) {
if ((IsHoleyOrDictionaryElementsKind(receiver_map.elements_kind()) ||
StoreModeCanGrow(feedback.keyed_mode().store_mode())) &&
#if V8_ENABLE_WEBASSEMBLY
!(receiver_map.IsWasmObjectMap() &&
access_info.is_proxy_on_prototype()) &&
#endif
!receiver_map.PrototypesElementsDoNotHaveAccessorsOrThrow(
broker(), &prototype_maps)) {
return NoChange();
}
if (InstanceTypeChecker::IsAlwaysSharedSpaceJSObject(
receiver_map.instance_type())) {
return NoChange();
}
}
}
for (MapRef prototype_map : prototype_maps) {
dependencies()->DependOnStableMap(prototype_map);
}
} else if (access_mode == AccessMode::kHas) {
for (ElementAccessInfo const& access_info : access_infos) {
if (IsFastElementsKind(access_info.elements_kind())) {
if (!dependencies()->DependOnNoElementsProtector()) return NoChange();
break;
}
}
}
ZoneVector<Node*> if_exception_nodes(zone());
ZoneVector<Node*>* if_exceptions = nullptr;
Node* if_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &if_exception)) {
if_exceptions = &if_exception_nodes;
}
PropertyAccessBuilder access_builder(jsgraph(), broker());
if (access_infos.size() == 1) {
const ElementAccessInfo& access_info = access_infos.front();
if (!access_info.transition_sources().empty()) {
DCHECK_EQ(access_info.lookup_start_object_maps().size(), 1);
MapRef transition_target = access_info.lookup_start_object_maps().front();
ZoneRefSet<Map> sources(access_info.transition_sources().begin(),
access_info.transition_sources().end(),
graph()->zone());
effect = graph()->NewNode(simplified()->TransitionElementsKindOrCheckMap(
ElementsTransitionWithMultipleSources(
sources, transition_target)),
receiver, effect, control);
} else {
access_builder.BuildCheckMaps(receiver, &effect, control,
access_info.lookup_start_object_maps());
}
ValueEffectControl continuation = BuildElementAccess(
receiver, index, value, effect, control, context, access_info,
language_mode, feedback.keyed_mode(), if_exceptions, frame_state);
value = continuation.value();
effect = continuation.effect();
control = continuation.control();
} else {
ZoneVector<Node*> values(zone());
ZoneVector<Node*> effects(zone());
ZoneVector<Node*> controls(zone());
Node* fallthrough_control = control;
for (size_t j = 0; j < access_infos.size(); ++j) {
ElementAccessInfo const& access_info = access_infos[j];
Node* this_receiver = receiver;
Node* this_value = value;
Node* this_index = index;
Effect this_effect = effect;
Control this_control{fallthrough_control};
MapRef transition_target = access_info.lookup_start_object_maps().front();
for (MapRef transition_source : access_info.transition_sources()) {
DCHECK_EQ(access_info.lookup_start_object_maps().size(), 1);
this_effect = graph()->NewNode(
simplified()->TransitionElementsKind(ElementsTransition(
IsSimpleMapChangeTransition(transition_source.elements_kind(),
transition_target.elements_kind())
? ElementsTransition::kFastTransition
: ElementsTransition::kSlowTransition,
transition_source, transition_target)),
receiver, this_effect, this_control);
}
ZoneVector<MapRef> const& receiver_maps =
access_info.lookup_start_object_maps();
if (j == access_infos.size() - 1) {
access_builder.BuildCheckMaps(receiver, &this_effect, this_control,
receiver_maps);
fallthrough_control = nullptr;
} else {
ZoneRefSet<Map> maps(receiver_maps.begin(), receiver_maps.end(),
graph()->zone());
Node* check = this_effect =
graph()->NewNode(simplified()->CompareMaps(maps), receiver,
this_effect, fallthrough_control);
Node* branch =
graph()->NewNode(common()->Branch(), check, fallthrough_control);
fallthrough_control = graph()->NewNode(common()->IfFalse(), branch);
this_control = graph()->NewNode(common()->IfTrue(), branch);
this_effect = graph()->NewNode(simplified()->MapGuard(maps), receiver,
this_effect, this_control);
}
ValueEffectControl continuation =
BuildElementAccess(this_receiver, this_index, this_value, this_effect,
this_control, context, access_info, language_mode,
feedback.keyed_mode(), if_exceptions, frame_state);
values.push_back(continuation.value());
effects.push_back(continuation.effect());
controls.push_back(continuation.control());
}
DCHECK_NULL(fallthrough_control);
int const control_count = static_cast<int>(controls.size());
if (control_count == 0) {
value = effect = control = jsgraph()->Dead();
} else if (control_count == 1) {
value = values.front();
effect = effects.front();
control = controls.front();
} else {
control = graph()->NewNode(common()->Merge(control_count), control_count,
&controls.front());
values.push_back(control);
value = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, control_count),
control_count + 1, &values.front());
effects.push_back(control);
effect = graph()->NewNode(common()->EffectPhi(control_count),
control_count + 1, &effects.front());
}
}
if (!if_exception_nodes.empty()) {
DCHECK_NOT_NULL(if_exception);
DCHECK_EQ(if_exceptions, &if_exception_nodes);
int const if_exception_count = static_cast<int>(if_exceptions->size());
Node* merge = graph()->NewNode(common()->Merge(if_exception_count),
if_exception_count, &if_exceptions->front());
if_exceptions->push_back(merge);
Node* ephi =
graph()->NewNode(common()->EffectPhi(if_exception_count),
if_exception_count + 1, &if_exceptions->front());
Node* phi = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, if_exception_count),
if_exception_count + 1, &if_exceptions->front());
ReplaceWithValue(if_exception, phi, ephi, merge);
}
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
Reduction JSNativeContextSpecialization::ReduceElementLoadFromHeapConstant(
Node* node, Node* key, AccessMode access_mode,
KeyedAccessLoadMode load_mode) {
DCHECK(node->opcode() == IrOpcode::kJSLoadProperty ||
node->opcode() == IrOpcode::kJSHasProperty);
Node* receiver = NodeProperties::GetValueInput(node, 0);
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
HeapObjectMatcher mreceiver(receiver);
HeapObjectRef receiver_ref = mreceiver.Ref(broker());
if (receiver_ref.IsNull() || receiver_ref.IsUndefined() ||
(receiver_ref.IsString() && access_mode == AccessMode::kHas)) {
return NoChange();
}
NumberMatcher mkey(key);
if (mkey.IsInteger() &&
mkey.IsInRange(0.0, static_cast<double>(JSObject::kMaxElementIndex))) {
static_assert(JSObject::kMaxElementIndex <= kMaxUInt32);
const uint32_t index = static_cast<uint32_t>(mkey.ResolvedValue());
OptionalObjectRef element;
if (receiver_ref.IsJSObject()) {
JSObjectRef jsobject_ref = receiver_ref.AsJSObject();
OptionalFixedArrayBaseRef elements =
jsobject_ref.elements(broker(), kRelaxedLoad);
if (elements.has_value()) {
element = jsobject_ref.GetOwnConstantElement(broker(), *elements, index,
dependencies());
if (!element.has_value() && receiver_ref.IsJSArray()) {
element = receiver_ref.AsJSArray().GetOwnCowElement(broker(),
*elements, index);
if (element.has_value()) {
Node* actual_elements = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()),
receiver, effect, control);
Node* check = graph()->NewNode(
simplified()->ReferenceEqual(), actual_elements,
jsgraph()->ConstantNoHole(*elements, broker()));
effect = graph()->NewNode(
simplified()->CheckIf(
DeoptimizeReason::kCowArrayElementsChanged),
check, effect, control);
}
}
}
} else if (receiver_ref.IsString()) {
element =
receiver_ref.AsString().GetCharAsStringOrUndefined(broker(), index);
}
if (element.has_value()) {
Node* value = access_mode == AccessMode::kHas
? jsgraph()->TrueConstant()
: jsgraph()->ConstantNoHole(*element, broker());
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
}
if (receiver_ref.IsString()) {
DCHECK_NE(access_mode, AccessMode::kHas);
Node* length = jsgraph()->ConstantNoHole(receiver_ref.AsString().length());
Node* value = BuildIndexedStringLoad(receiver, key, length, &effect,
&control, load_mode);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
return NoChange();
}
Reduction JSNativeContextSpecialization::ReducePropertyAccess(
Node* node, Node* key, OptionalNameRef static_name, Node* value,
FeedbackSource const& source, AccessMode access_mode) {
DCHECK_EQ(key == nullptr, static_name.has_value());
DCHECK(node->opcode() == IrOpcode::kJSLoadProperty ||
node->opcode() == IrOpcode::kJSSetKeyedProperty ||
node->opcode() == IrOpcode::kJSStoreInArrayLiteral ||
node->opcode() == IrOpcode::kJSDefineKeyedOwnPropertyInLiteral ||
node->opcode() == IrOpcode::kJSHasProperty ||
node->opcode() == IrOpcode::kJSLoadNamed ||
node->opcode() == IrOpcode::kJSSetNamedProperty ||
node->opcode() == IrOpcode::kJSDefineNamedOwnProperty ||
node->opcode() == IrOpcode::kJSLoadNamedFromSuper ||
node->opcode() == IrOpcode::kJSDefineKeyedOwnProperty);
DCHECK_GE(node->op()->ControlOutputCount(), 1);
ProcessedFeedback const* feedback =
&broker()->GetFeedbackForPropertyAccess(source, access_mode, static_name);
if (feedback->kind() == ProcessedFeedback::kElementAccess &&
feedback->AsElementAccess().transition_groups().empty()) {
HeapObjectMatcher m_key(key);
if (m_key.HasResolvedValue() && m_key.Ref(broker()).IsName()) {
NameRef name_key = m_key.Ref(broker()).AsName();
if (name_key.IsUniqueName() && !name_key.object()->IsArrayIndex()) {
feedback = &feedback->AsElementAccess().Refine(
broker(), m_key.Ref(broker()).AsName());
}
}
}
switch (feedback->kind()) {
case ProcessedFeedback::kInsufficient:
return ReduceEagerDeoptimize(
node,
DeoptimizeReason::kInsufficientTypeFeedbackForGenericNamedAccess);
case ProcessedFeedback::kNamedAccess:
return ReduceNamedAccess(node, value, feedback->AsNamedAccess(),
access_mode, key);
case ProcessedFeedback::kMegaDOMPropertyAccess:
DCHECK_EQ(access_mode, AccessMode::kLoad);
DCHECK_NULL(key);
return ReduceMegaDOMPropertyAccess(
node, value, feedback->AsMegaDOMPropertyAccess(), source);
case ProcessedFeedback::kElementAccess:
DCHECK_EQ(feedback->AsElementAccess().keyed_mode().access_mode(),
access_mode);
DCHECK_NE(node->opcode(), IrOpcode::kJSLoadNamedFromSuper);
return ReduceElementAccess(node, key, value, feedback->AsElementAccess());
default:
UNREACHABLE();
}
}
Reduction JSNativeContextSpecialization::ReduceEagerDeoptimize(
Node* node, DeoptimizeReason reason) {
if (!(flags() & kBailoutOnUninitialized)) return NoChange();
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
Node* frame_state =
NodeProperties::FindFrameStateBefore(node, jsgraph()->Dead());
Node* deoptimize =
graph()->NewNode(common()->Deoptimize(reason, FeedbackSource()),
frame_state, effect, control);
MergeControlToEnd(graph(), common(), deoptimize);
node->TrimInputCount(0);
NodeProperties::ChangeOp(node, common()->Dead());
return Changed(node);
}
Reduction JSNativeContextSpecialization::ReduceJSHasProperty(Node* node) {
JSHasPropertyNode n(node);
PropertyAccess const& p = n.Parameters();
Node* value = jsgraph()->Dead();
if (!p.feedback().IsValid()) return NoChange();
return ReducePropertyAccess(node, n.key(), std::nullopt, value,
FeedbackSource(p.feedback()), AccessMode::kHas);
}
Reduction JSNativeContextSpecialization::ReduceJSLoadPropertyWithEnumeratedKey(
Node* node) {
DCHECK_EQ(IrOpcode::kJSLoadProperty, node->opcode());
Node* receiver = NodeProperties::GetValueInput(node, 0);
JSForInNextNode name(NodeProperties::GetValueInput(node, 1));
Node* effect = NodeProperties::GetEffectInput(node);
Node* control = NodeProperties::GetControlInput(node);
if (name.Parameters().mode() != ForInMode::kUseEnumCacheKeysAndIndices) {
return NoChange();
}
Node* object = name.receiver();
Node* cache_type = name.cache_type();
Node* index = name.index();
if (object->opcode() == IrOpcode::kJSToObject) {
object = NodeProperties::GetValueInput(object, 0);
}
bool speculating_object_is_receiver = false;
if (object != receiver) {
JSLoadPropertyNode n(node);
PropertyAccess const& p = n.Parameters();
ProcessedFeedback const& feedback = broker()->GetFeedbackForPropertyAccess(
FeedbackSource(p.feedback()), AccessMode::kLoad, std::nullopt);
if (feedback.kind() != ProcessedFeedback::kInsufficient) {
return NoChange();
}
receiver = effect = graph()->NewNode(simplified()->CheckHeapObject(),
receiver, effect, control);
speculating_object_is_receiver = true;
}
if (!NodeProperties::NoObservableSideEffectBetween(effect, name) ||
speculating_object_is_receiver) {
Node* receiver_map = effect =
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()),
receiver, effect, control);
Node* check = graph()->NewNode(simplified()->ReferenceEqual(), receiver_map,
cache_type);
effect =
graph()->NewNode(simplified()->CheckIf(DeoptimizeReason::kWrongMap),
check, effect, control);
}
Node* descriptor_array = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForMapDescriptors()), cache_type,
effect, control);
Node* enum_cache = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForDescriptorArrayEnumCache()),
descriptor_array, effect, control);
Node* enum_indices = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForEnumCacheIndices()), enum_cache,
effect, control);
Node* check = graph()->NewNode(
simplified()->BooleanNot(),
graph()->NewNode(simplified()->ReferenceEqual(), enum_indices,
jsgraph()->EmptyFixedArrayConstant()));
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kWrongEnumIndices), check, effect,
control);
Node* key = effect = graph()->NewNode(
simplified()->LoadElement(
AccessBuilder::ForFixedArrayElement(PACKED_SMI_ELEMENTS)),
enum_indices, index, effect, control);
Node* value = effect = graph()->NewNode(simplified()->LoadFieldByIndex(),
receiver, key, effect, control);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
Reduction JSNativeContextSpecialization::ReduceJSLoadProperty(Node* node) {
JSLoadPropertyNode n(node);
PropertyAccess const& p = n.Parameters();
Node* name = n.key();
if (name->opcode() == IrOpcode::kJSForInNext) {
Reduction reduction = ReduceJSLoadPropertyWithEnumeratedKey(node);
if (reduction.Changed()) return reduction;
}
if (!p.feedback().IsValid()) return NoChange();
Node* value = jsgraph()->Dead();
return ReducePropertyAccess(node, name, std::nullopt, value,
FeedbackSource(p.feedback()), AccessMode::kLoad);
}
Reduction JSNativeContextSpecialization::ReduceJSSetKeyedProperty(Node* node) {
JSSetKeyedPropertyNode n(node);
PropertyAccess const& p = n.Parameters();
if (!p.feedback().IsValid()) return NoChange();
return ReducePropertyAccess(node, n.key(), std::nullopt, n.value(),
FeedbackSource(p.feedback()), AccessMode::kStore);
}
Reduction JSNativeContextSpecialization::ReduceJSDefineKeyedOwnProperty(
Node* node) {
JSDefineKeyedOwnPropertyNode n(node);
PropertyAccess const& p = n.Parameters();
if (!p.feedback().IsValid()) return NoChange();
return ReducePropertyAccess(node, n.key(), std::nullopt, n.value(),
FeedbackSource(p.feedback()),
AccessMode::kDefine);
}
Node* JSNativeContextSpecialization::InlinePropertyGetterCall(
Node* receiver, ConvertReceiverMode receiver_mode,
Node* lookup_start_object, Node* context, Node* frame_state, Node** effect,
Node** control, ZoneVector<Node*>* if_exceptions,
PropertyAccessInfo const& access_info) {
ObjectRef constant = access_info.constant().value();
if (access_info.IsDictionaryProtoAccessorConstant()) {
for (const MapRef map : access_info.lookup_start_object_maps()) {
dependencies()->DependOnConstantInDictionaryPrototypeChain(
map, access_info.name(), constant, PropertyKind::kAccessor);
}
}
Node* target = jsgraph()->ConstantNoHole(constant, broker());
Node* value;
if (constant.IsJSFunction()) {
Node* feedback = jsgraph()->UndefinedConstant();
value = *effect = *control = graph()->NewNode(
jsgraph()->javascript()->Call(JSCallNode::ArityForArgc(0),
CallFrequency(), FeedbackSource(),
receiver_mode),
target, receiver, feedback, context, frame_state, *effect, *control);
} else {
if (receiver != lookup_start_object) {
return nullptr;
}
value = InlineApiCall(receiver, frame_state, nullptr, effect, control,
constant.AsFunctionTemplateInfo(), FeedbackSource());
}
if (if_exceptions != nullptr) {
Node* const if_exception =
graph()->NewNode(common()->IfException(), *control, *effect);
Node* const if_success = graph()->NewNode(common()->IfSuccess(), *control);
if_exceptions->push_back(if_exception);
*control = if_success;
}
return value;
}
void JSNativeContextSpecialization::InlinePropertySetterCall(
Node* receiver, Node* value, Node* context, Node* frame_state,
Node** effect, Node** control, ZoneVector<Node*>* if_exceptions,
PropertyAccessInfo const& access_info) {
ObjectRef constant = access_info.constant().value();
Node* target = jsgraph()->ConstantNoHole(constant, broker());
if (constant.IsJSFunction()) {
Node* feedback = jsgraph()->UndefinedConstant();
*effect = *control = graph()->NewNode(
jsgraph()->javascript()->Call(JSCallNode::ArityForArgc(1),
CallFrequency(), FeedbackSource(),
ConvertReceiverMode::kNotNullOrUndefined),
target, receiver, value, feedback, context, frame_state, *effect,
*control);
} else {
InlineApiCall(receiver, frame_state, value, effect, control,
constant.AsFunctionTemplateInfo(), FeedbackSource());
}
if (if_exceptions != nullptr) {
Node* const if_exception =
graph()->NewNode(common()->IfException(), *control, *effect);
Node* const if_success = graph()->NewNode(common()->IfSuccess(), *control);
if_exceptions->push_back(if_exception);
*control = if_success;
}
}
namespace {
CallDescriptor* PushRegularApiCallInputs(
JSGraph* jsgraph, JSHeapBroker* broker, Node* receiver, Node* frame_state,
Node* value, Node** effect, Node** control,
FunctionTemplateInfoRef function_template_info, Node** inputs,
int& cursor) {
int const argc = value == nullptr ? 0 : 1;
bool no_profiling = broker->dependencies()->DependOnNoProfilingProtector();
Callable call_api_callback = Builtins::CallableFor(
jsgraph->isolate(), no_profiling
? Builtin::kCallApiCallbackOptimizedNoProfiling
: Builtin::kCallApiCallbackOptimized);
const ZoneVector<CFunctionInfoWithDetails> overloads =
function_template_info.c_functions_with_signatures(broker);
const uint32_t overloads_count = static_cast<uint32_t>(overloads.size());
ZoneVector<Address> c_functions(overloads_count, jsgraph->zone());
ZoneVector<const CFunctionInfo*> c_signatures(overloads_count,
jsgraph->zone());
for (uint32_t i = 0; i < overloads_count; ++i) {
c_functions[i] = overloads[i].address;
c_signatures[i] = overloads[i].signature;
}
Node* func_templ =
jsgraph->HeapConstantNoHole(function_template_info.object());
ApiFunction function(function_template_info.callback(broker));
Node* function_reference = jsgraph->graph()->NewNode(
jsgraph->common()->ExternalConstant(ExternalReference::Create(
jsgraph->isolate(), &function, ExternalReference::DIRECT_API_CALL,
c_functions.data(), c_signatures.data(), overloads_count)));
Node* code = jsgraph->HeapConstantNoHole(call_api_callback.code());
Node* context =
jsgraph->ConstantNoHole(broker->target_native_context(), broker);
inputs[cursor++] = code;
inputs[cursor++] = function_reference;
inputs[cursor++] = jsgraph->ConstantNoHole(argc);
inputs[cursor++] = func_templ;
inputs[cursor++] = receiver;
if (value) {
inputs[cursor++] = value;
}
inputs[cursor++] = context;
inputs[cursor++] = frame_state;
inputs[cursor++] = *effect;
inputs[cursor++] = *control;
CallInterfaceDescriptor call_interface_descriptor =
call_api_callback.descriptor();
return Linkage::GetStubCallDescriptor(
jsgraph->zone(), call_interface_descriptor,
call_interface_descriptor.GetStackParameterCount() + argc +
1 ,
CallDescriptor::kNeedsFrameState);
}
}
Node* JSNativeContextSpecialization::InlineApiCall(
Node* receiver, Node* frame_state, Node* value, Node** effect,
Node** control, FunctionTemplateInfoRef function_template_info,
const FeedbackSource& feedback) {
compiler::OptionalObjectRef maybe_callback_data =
function_template_info.callback_data(broker());
if (!maybe_callback_data.has_value()) {
TRACE_BROKER_MISSING(broker(), "call code for function template info "
<< function_template_info);
return nullptr;
}
int const argc = value == nullptr ? 0 : 1;
FastApiCallFunction c_function = fast_api_call::GetFastApiCallTarget(
broker(), function_template_info, argc);
if (c_function.address) {
const int input_count = 14;
Node* inputs[input_count];
int cursor = 0;
inputs[cursor++] = receiver;
if (value) {
inputs[cursor++] = value;
}
inputs[cursor++] =
jsgraph()->ConstantNoHole(maybe_callback_data.value(), broker());
auto call_descriptor = PushRegularApiCallInputs(
jsgraph(), broker(), receiver, frame_state, value, effect, control,
function_template_info, inputs, cursor);
DCHECK_LE(cursor, input_count);
return *effect = *control = graph()->NewNode(
simplified()->FastApiCall(c_function, feedback, call_descriptor),
cursor, inputs);
}
Node* inputs[11];
int cursor = 0;
CallDescriptor* call_descriptor = PushRegularApiCallInputs(
jsgraph(), broker(), receiver, frame_state, value, effect, control,
function_template_info, inputs, cursor);
return *effect = *control =
graph()->NewNode(common()->Call(call_descriptor), cursor, inputs);
}
std::optional<JSNativeContextSpecialization::ValueEffectControl>
JSNativeContextSpecialization::BuildPropertyLoad(
Node* lookup_start_object, Node* receiver, Node* context, Node* frame_state,
Node* effect, Node* control, NameRef name, ZoneVector<Node*>* if_exceptions,
PropertyAccessInfo const& access_info) {
OptionalJSObjectRef holder = access_info.holder();
if (holder.has_value() && !access_info.HasDictionaryHolder()) {
dependencies()->DependOnStablePrototypeChains(
access_info.lookup_start_object_maps(), kStartAtPrototype,
holder.value());
}
Node* value;
if (access_info.IsNotFound()) {
value = jsgraph()->UndefinedConstant();
} else if (access_info.IsFastAccessorConstant() ||
access_info.IsDictionaryProtoAccessorConstant()) {
ConvertReceiverMode receiver_mode =
receiver == lookup_start_object
? ConvertReceiverMode::kNotNullOrUndefined
: ConvertReceiverMode::kAny;
value = InlinePropertyGetterCall(
receiver, receiver_mode, lookup_start_object, context, frame_state,
&effect, &control, if_exceptions, access_info);
} else if (access_info.IsModuleExport()) {
Node* cell = jsgraph()->ConstantNoHole(
access_info.constant().value().AsCell(), broker());
value = effect =
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForCellValue()),
cell, effect, control);
} else if (access_info.IsStringLength()) {
DCHECK_EQ(receiver, lookup_start_object);
value = graph()->NewNode(simplified()->StringLength(), receiver);
} else if (access_info.IsStringWrapperLength()) {
value = graph()->NewNode(simplified()->StringWrapperLength(),
lookup_start_object);
} else if (access_info.IsTypedArrayLength()) {
if (receiver != lookup_start_object) {
value = effect = control = graph()->NewNode(
javascript()->CallRuntime(Runtime::kThrowTypeError, 3),
jsgraph()->ConstantNoHole(
static_cast<int>(MessageTemplate::kIncompatibleMethodReceiver)),
jsgraph()->HeapConstantNoHole(factory()->TypedArrayLength_string()),
receiver, context, frame_state, effect, control);
if (if_exceptions != nullptr) {
Node* const if_exception =
graph()->NewNode(common()->IfException(), control, effect);
Node* const if_success =
graph()->NewNode(common()->IfSuccess(), control);
if_exceptions->push_back(if_exception);
control = if_success;
}
} else {
const ZoneVector<MapRef>& maps = access_info.lookup_start_object_maps();
DCHECK_EQ(maps.size(), 1);
value = graph()->NewNode(
simplified()->TypedArrayLength(maps[0].elements_kind()),
lookup_start_object);
}
} else {
DCHECK(access_info.IsDataField() || access_info.IsFastDataConstant() ||
access_info.IsDictionaryProtoDataConstant());
PropertyAccessBuilder access_builder(jsgraph(), broker());
if (access_info.IsDictionaryProtoDataConstant()) {
auto maybe_value =
access_builder.FoldLoadDictPrototypeConstant(access_info);
if (!maybe_value) return {};
value = maybe_value.value();
} else {
value = access_builder.BuildLoadDataField(
name, access_info, lookup_start_object, &effect, &control);
}
}
if (value != nullptr) {
return ValueEffectControl(value, effect, control);
}
return std::optional<ValueEffectControl>();
}
JSNativeContextSpecialization::ValueEffectControl
JSNativeContextSpecialization::BuildPropertyTest(
Node* effect, Node* control, PropertyAccessInfo const& access_info) {
DCHECK(!access_info.HasDictionaryHolder());
OptionalJSObjectRef holder = access_info.holder();
if (holder.has_value()) {
dependencies()->DependOnStablePrototypeChains(
access_info.lookup_start_object_maps(), kStartAtPrototype,
holder.value());
}
return ValueEffectControl(
jsgraph()->BooleanConstant(!access_info.IsNotFound()), effect, control);
}
std::optional<JSNativeContextSpecialization::ValueEffectControl>
JSNativeContextSpecialization::BuildPropertyAccess(
Node* lookup_start_object, Node* receiver, Node* value, Node* context,
Node* frame_state, Node* effect, Node* control, NameRef name,
ZoneVector<Node*>* if_exceptions, PropertyAccessInfo const& access_info,
AccessMode access_mode) {
switch (access_mode) {
case AccessMode::kLoad:
return BuildPropertyLoad(lookup_start_object, receiver, context,
frame_state, effect, control, name,
if_exceptions, access_info);
case AccessMode::kStore:
case AccessMode::kStoreInLiteral:
case AccessMode::kDefine:
DCHECK_EQ(receiver, lookup_start_object);
return BuildPropertyStore(receiver, value, context, frame_state, effect,
control, name, if_exceptions, access_info,
access_mode);
case AccessMode::kHas:
DCHECK_EQ(receiver, lookup_start_object);
return BuildPropertyTest(effect, control, access_info);
}
UNREACHABLE();
}
JSNativeContextSpecialization::ValueEffectControl
JSNativeContextSpecialization::BuildPropertyStore(
Node* receiver, Node* value, Node* context, Node* frame_state, Node* effect,
Node* control, NameRef name, ZoneVector<Node*>* if_exceptions,
PropertyAccessInfo const& access_info, AccessMode access_mode) {
PropertyAccessBuilder access_builder(jsgraph(), broker());
OptionalJSObjectRef holder = access_info.holder();
if (holder.has_value()) {
DCHECK_NE(AccessMode::kStoreInLiteral, access_mode);
DCHECK_NE(AccessMode::kDefine, access_mode);
dependencies()->DependOnStablePrototypeChains(
access_info.lookup_start_object_maps(), kStartAtPrototype,
holder.value());
}
DCHECK(!access_info.IsNotFound());
if (access_info.IsFastAccessorConstant()) {
InlinePropertySetterCall(receiver, value, context, frame_state, &effect,
&control, if_exceptions, access_info);
} else {
DCHECK(access_info.IsDataField() || access_info.IsFastDataConstant());
DCHECK(access_mode == AccessMode::kStore ||
access_mode == AccessMode::kStoreInLiteral ||
access_mode == AccessMode::kDefine);
FieldIndex const field_index = access_info.field_index();
Type const field_type = access_info.field_type();
MachineRepresentation const field_representation =
PropertyAccessBuilder::ConvertRepresentation(
access_info.field_representation());
Node* storage = receiver;
if (!field_index.is_inobject()) {
storage = effect = graph()->NewNode(
simplified()->LoadField(
AccessBuilder::ForJSObjectPropertiesOrHashKnownPointer()),
storage, effect, control);
}
if (access_info.IsFastDataConstant() && access_mode == AccessMode::kStore &&
!access_info.HasTransitionMap()) {
Node* deoptimize = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kStoreToConstant),
jsgraph()->FalseConstant(), effect, control);
return ValueEffectControl(jsgraph()->UndefinedConstant(), deoptimize,
control);
}
FieldAccess field_access = {
kTaggedBase,
field_index.offset(),
name.object(),
OptionalMapRef(),
field_type,
MachineType::TypeForRepresentation(field_representation),
kFullWriteBarrier,
"BuildPropertyStore",
access_info.GetConstFieldInfo(),
access_mode == AccessMode::kStoreInLiteral};
switch (field_representation) {
case MachineRepresentation::kFloat64: {
value = effect =
graph()->NewNode(simplified()->CheckNumber(FeedbackSource()), value,
effect, control);
if (access_info.HasTransitionMap()) {
AllocationBuilder a(jsgraph(), broker(), effect, control);
a.Allocate(sizeof(HeapNumber), AllocationType::kYoung,
Type::OtherInternal());
a.Store(AccessBuilder::ForMap(), broker()->heap_number_map());
FieldAccess value_field_access = AccessBuilder::ForHeapNumberValue();
value_field_access.const_field_info = field_access.const_field_info;
a.Store(value_field_access, value);
value = effect = a.Finish();
field_access.type = Type::Any();
field_access.machine_type = MachineType::TaggedPointer();
field_access.write_barrier_kind = kPointerWriteBarrier;
} else {
FieldAccess const storage_access = {
kTaggedBase,
field_index.offset(),
name.object(),
OptionalMapRef(),
Type::OtherInternal(),
MachineType::TaggedPointer(),
kPointerWriteBarrier,
"BuildPropertyStore",
access_info.GetConstFieldInfo(),
access_mode == AccessMode::kStoreInLiteral};
storage = effect =
graph()->NewNode(simplified()->LoadField(storage_access), storage,
effect, control);
FieldAccess value_field_access = AccessBuilder::ForHeapNumberValue();
value_field_access.const_field_info = field_access.const_field_info;
value_field_access.is_store_in_literal =
field_access.is_store_in_literal;
field_access = value_field_access;
}
break;
}
case MachineRepresentation::kTaggedSigned:
case MachineRepresentation::kTaggedPointer:
case MachineRepresentation::kTagged:
if (field_representation == MachineRepresentation::kTaggedSigned) {
value = effect = graph()->NewNode(
simplified()->CheckSmi(FeedbackSource()), value, effect, control);
field_access.write_barrier_kind = kNoWriteBarrier;
} else if (field_representation ==
MachineRepresentation::kTaggedPointer) {
OptionalMapRef field_map = access_info.field_map();
if (field_map.has_value()) {
effect = graph()->NewNode(
simplified()->CheckMaps(CheckMapsFlag::kNone,
ZoneRefSet<Map>(*field_map)),
value, effect, control);
} else {
value = effect = graph()->NewNode(simplified()->CheckHeapObject(),
value, effect, control);
}
field_access.write_barrier_kind = kPointerWriteBarrier;
} else {
DCHECK(field_representation == MachineRepresentation::kTagged);
}
break;
case MachineRepresentation::kNone:
case MachineRepresentation::kBit:
case MachineRepresentation::kCompressedPointer:
case MachineRepresentation::kCompressed:
case MachineRepresentation::kProtectedPointer:
case MachineRepresentation::kIndirectPointer:
case MachineRepresentation::kSandboxedPointer:
case MachineRepresentation::kWord8:
case MachineRepresentation::kWord16:
case MachineRepresentation::kWord32:
case MachineRepresentation::kWord64:
case MachineRepresentation::kFloat16:
case MachineRepresentation::kFloat32:
case MachineRepresentation::kSimd128:
case MachineRepresentation::kSimd256:
case MachineRepresentation::kMapWord:
case MachineRepresentation::kFloat16RawBits:
UNREACHABLE();
}
OptionalMapRef transition_map = access_info.transition_map();
if (transition_map.has_value()) {
MapRef transition_map_ref = transition_map.value();
MapRef original_map = transition_map_ref.GetBackPointer(broker()).AsMap();
if (!field_index.is_inobject()) {
dependencies()->DependOnNoSlackTrackingChange(original_map);
}
if (original_map.UnusedPropertyFields() == 0) {
DCHECK(!field_index.is_inobject());
storage = effect = BuildExtendPropertiesBackingStore(
original_map, storage, effect, control);
effect = graph()->NewNode(simplified()->StoreField(field_access),
storage, value, effect, control);
field_access = AccessBuilder::ForJSObjectPropertiesOrHashKnownPointer();
value = storage;
storage = receiver;
}
effect = graph()->NewNode(
common()->BeginRegion(RegionObservability::kObservable), effect);
effect = graph()->NewNode(simplified()->StoreField(field_access), storage,
value, effect, control);
effect = graph()->NewNode(
simplified()->StoreField(AccessBuilder::ForMap()), receiver,
jsgraph()->ConstantNoHole(transition_map_ref, broker()), effect,
control);
effect = graph()->NewNode(common()->FinishRegion(),
jsgraph()->UndefinedConstant(), effect);
} else {
effect = graph()->NewNode(simplified()->StoreField(field_access), storage,
value, effect, control);
}
}
return ValueEffectControl(value, effect, control);
}
Reduction
JSNativeContextSpecialization::ReduceJSDefineKeyedOwnPropertyInLiteral(
Node* node) {
JSDefineKeyedOwnPropertyInLiteralNode n(node);
FeedbackParameter const& p = n.Parameters();
if (!p.feedback().IsValid()) return NoChange();
NumberMatcher mflags(n.flags());
CHECK(mflags.HasResolvedValue());
DefineKeyedOwnPropertyInLiteralFlags cflags(mflags.ResolvedValue());
if (cflags & DefineKeyedOwnPropertyInLiteralFlag::kSetFunctionName)
return NoChange();
return ReducePropertyAccess(node, n.name(), std::nullopt, n.value(),
FeedbackSource(p.feedback()),
AccessMode::kStoreInLiteral);
}
Reduction JSNativeContextSpecialization::ReduceJSStoreInArrayLiteral(
Node* node) {
JSStoreInArrayLiteralNode n(node);
FeedbackParameter const& p = n.Parameters();
if (!p.feedback().IsValid()) return NoChange();
return ReducePropertyAccess(node, n.index(), std::nullopt, n.value(),
FeedbackSource(p.feedback()),
AccessMode::kStoreInLiteral);
}
Reduction JSNativeContextSpecialization::ReduceJSToObject(Node* node) {
DCHECK_EQ(IrOpcode::kJSToObject, node->opcode());
Node* receiver = NodeProperties::GetValueInput(node, 0);
Effect effect{NodeProperties::GetEffectInput(node)};
MapInference inference(broker(), receiver, effect);
if (!inference.HaveMaps() || !inference.AllOfInstanceTypesAreJSReceiver()) {
return NoChange();
}
ReplaceWithValue(node, receiver, effect);
return Replace(receiver);
}
#if V8_ENABLE_WEBASSEMBLY
JSNativeContextSpecialization::ValueEffectControl
JSNativeContextSpecialization::BuildPrototypeProxyElementAccess(
Node* receiver, Node* index, Node* value, Node* effect, Node* control,
Node* context, ElementAccessInfo const& access_info,
LanguageMode language_mode, KeyedAccessMode const& keyed_mode,
ZoneVector<Node*>* if_exceptions, Node* frame_state) {
Node* call_target =
jsgraph()->ConstantNoHole(access_info.accessor().value(), broker());
Node* proxy_target =
jsgraph()->ConstantNoHole(access_info.target().value(), broker());
Node* feedback = jsgraph()->UndefinedConstant();
DCHECK(!TrustedCast<WasmExportedFunctionData>(
Cast<JSFunction>(access_info.accessor().value().object())
->shared()
->GetTrustedData(isolate()))
->receiver_is_first_param());
Node* call_receiver = jsgraph()->UndefinedConstant();
ConvertReceiverMode receiver_mode = ConvertReceiverMode::kNullOrUndefined;
if (access_info.string_keys()) {
index = effect = graph()->NewNode(
simplified()->CheckString(FeedbackSource()), index, effect, control);
}
Node* call = nullptr;
if (keyed_mode.access_mode() == AccessMode::kLoad) {
value = effect = control = graph()->NewNode(
jsgraph()->javascript()->Call(JSCallNode::ArityForArgc(3),
CallFrequency(), FeedbackSource(),
receiver_mode),
call_target, call_receiver,
proxy_target, index, receiver,
feedback, context, frame_state, effect, control);
} else {
DCHECK_EQ(keyed_mode.access_mode(), AccessMode::kStore);
call = effect = control = graph()->NewNode(
jsgraph()->javascript()->Call(JSCallNode::ArityForArgc(4),
CallFrequency(), FeedbackSource(),
receiver_mode),
call_target, call_receiver,
proxy_target, index, value, receiver,
feedback, context, frame_state, effect, control);
}
if (if_exceptions != nullptr) {
if_exceptions->push_back(
graph()->NewNode(common()->IfException(), effect, control));
control = graph()->NewNode(common()->IfSuccess(), control);
}
if (keyed_mode.access_mode() == AccessMode::kStore &&
language_mode == LanguageMode::kStrict) {
DCHECK_NOT_NULL(call);
Node* to_bool = graph()->NewNode(simplified()->ToBoolean(), call);
Node* check = graph()->NewNode(simplified()->ReferenceEqual(), to_bool,
jsgraph()->FalseConstant());
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kFalse), check, control);
{
control = graph()->NewNode(common()->IfTrue(), branch);
Node* call_runtime = graph()->NewNode(
javascript()->CallRuntime(Runtime::kThrowTypeError, 3),
jsgraph()->ConstantNoHole(
static_cast<int>(MessageTemplate::kProxyTrapReturnedFalsishFor)),
jsgraph()->HeapConstantNoHole(factory()->set_string()), index,
context, frame_state, effect, control);
if (if_exceptions != nullptr) {
if_exceptions->push_back(graph()->NewNode(common()->IfException(),
call_runtime, call_runtime));
control = graph()->NewNode(common()->IfSuccess(), call_runtime);
}
Node* throw_node =
graph()->NewNode(common()->Throw(), call_runtime, control);
MergeControlToEnd(graph(), common(), throw_node);
}
control = graph()->NewNode(common()->IfFalse(), branch);
}
return {value, effect, control};
}
#endif
JSNativeContextSpecialization::ValueEffectControl
JSNativeContextSpecialization::BuildElementAccess(
Node* receiver, Node* index, Node* value, Node* effect, Node* control,
Node* context, ElementAccessInfo const& access_info,
LanguageMode language_mode, KeyedAccessMode const& keyed_mode,
ZoneVector<Node*>* if_exceptions, Node* frame_state) {
ElementsKind elements_kind = access_info.elements_kind();
ZoneVector<MapRef> const& receiver_maps =
access_info.lookup_start_object_maps();
if (IsTypedArrayElementsKind(elements_kind) ||
IsRabGsabTypedArrayElementsKind(elements_kind)) {
return BuildElementAccessForTypedArrayOrRabGsabTypedArray(
receiver, index, value, effect, control, context, elements_kind,
keyed_mode);
}
#if V8_ENABLE_WEBASSEMBLY
if (receiver_maps.size() == 1 && receiver_maps.front().IsWasmObjectMap() &&
access_info.is_proxy_on_prototype()) {
return BuildPrototypeProxyElementAccess(
receiver, index, value, effect, control, context, access_info,
language_mode, keyed_mode, if_exceptions, frame_state);
}
#endif
Node* elements = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSObjectElements()), receiver,
effect, control);
if (IsAnyStore(keyed_mode.access_mode()) &&
IsSmiOrObjectElementsKind(elements_kind) &&
!StoreModeHandlesCOW(keyed_mode.store_mode())) {
effect = graph()->NewNode(
simplified()->CheckMaps(CheckMapsFlag::kNone,
ZoneRefSet<Map>(broker()->fixed_array_map())),
elements, effect, control);
}
bool receiver_is_jsarray = HasOnlyJSArrayMaps(broker(), receiver_maps);
Node* length = effect =
receiver_is_jsarray
? graph()->NewNode(
simplified()->LoadField(
AccessBuilder::ForJSArrayLength(elements_kind)),
receiver, effect, control)
: graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForFixedArrayLength()),
elements, effect, control);
if (keyed_mode.IsStore() && StoreModeCanGrow(keyed_mode.store_mode())) {
} else if (keyed_mode.IsLoad() &&
LoadModeHandlesOOB(keyed_mode.load_mode()) &&
CanTreatHoleAsUndefined(receiver_maps)) {
index = effect = graph()->NewNode(
simplified()->CheckBounds(FeedbackSource(),
CheckBoundsFlag::kConvertStringAndMinusZero),
index, jsgraph()->ConstantNoHole(Smi::kMaxValue), effect, control);
} else {
index = effect = graph()->NewNode(
simplified()->CheckBounds(FeedbackSource(),
CheckBoundsFlag::kConvertStringAndMinusZero),
index, length, effect, control);
}
Type element_type = Type::NonInternal();
MachineType element_machine_type = MachineType::AnyTagged();
if (IsDoubleElementsKind(elements_kind)) {
element_type = Type::Number();
#ifdef V8_ENABLE_UNDEFINED_DOUBLE
if (elements_kind == HOLEY_DOUBLE_ELEMENTS) {
element_type =
Type::Union(element_type, Type::Undefined(), graph()->zone());
}
#endif
element_machine_type = MachineType::Float64();
} else if (IsSmiElementsKind(elements_kind)) {
element_type = Type::SignedSmall();
element_machine_type = MachineType::TaggedSigned();
}
ElementAccess element_access = {kTaggedBase, OFFSET_OF_DATA_START(FixedArray),
element_type, element_machine_type,
kFullWriteBarrier};
if (keyed_mode.access_mode() == AccessMode::kLoad) {
if (IsHoleyElementsKind(elements_kind)) {
element_access.type =
Type::Union(element_type, Type::Hole(), graph()->zone());
}
if (elements_kind == HOLEY_ELEMENTS ||
elements_kind == HOLEY_SMI_ELEMENTS) {
element_access.machine_type = MachineType::AnyTagged();
}
if (LoadModeHandlesOOB(keyed_mode.load_mode()) &&
CanTreatHoleAsUndefined(receiver_maps)) {
Node* check =
graph()->NewNode(simplified()->NumberLessThan(), index, length);
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Node* vtrue;
{
if (v8_flags.turbo_typer_hardening) {
index = etrue =
graph()->NewNode(simplified()->CheckBounds(
FeedbackSource(),
CheckBoundsFlag::kConvertStringAndMinusZero |
CheckBoundsFlag::kAbortOnOutOfBounds),
index, length, etrue, if_true);
}
vtrue = etrue =
graph()->NewNode(simplified()->LoadElement(element_access),
elements, index, etrue, if_true);
if (elements_kind == HOLEY_ELEMENTS ||
elements_kind == HOLEY_SMI_ELEMENTS) {
vtrue = graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(),
vtrue);
} else if (elements_kind == HOLEY_DOUBLE_ELEMENTS) {
if (LoadModeHandlesHoles(keyed_mode.load_mode())) {
#ifdef V8_ENABLE_UNDEFINED_DOUBLE
vtrue = graph()->NewNode(
simplified()->ChangeFloat64OrUndefinedOrHoleToTagged(), vtrue);
#else
vtrue = graph()->NewNode(simplified()->ChangeFloat64HoleToTagged(),
vtrue);
#endif
} else {
vtrue = etrue = graph()->NewNode(
simplified()->CheckFloat64Hole(
CheckFloat64HoleMode::kAllowReturnHole, FeedbackSource()),
vtrue, etrue, if_true);
}
}
}
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
Node* vfalse;
{
vfalse = jsgraph()->UndefinedConstant();
}
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
vtrue, vfalse, control);
} else {
value = effect =
graph()->NewNode(simplified()->LoadElement(element_access), elements,
index, effect, control);
if (elements_kind == HOLEY_ELEMENTS ||
elements_kind == HOLEY_SMI_ELEMENTS) {
if (CanTreatHoleAsUndefined(receiver_maps)) {
value = graph()->NewNode(simplified()->ConvertTaggedHoleToUndefined(),
value);
} else {
value = effect = graph()->NewNode(simplified()->CheckNotTaggedHole(),
value, effect, control);
}
} else if (elements_kind == HOLEY_DOUBLE_ELEMENTS) {
if (CanTreatHoleAsUndefined(receiver_maps)) {
if (LoadModeHandlesHoles(keyed_mode.load_mode())) {
#ifdef V8_ENABLE_UNDEFINED_DOUBLE
value = graph()->NewNode(
simplified()->ChangeFloat64OrUndefinedOrHoleToTagged(), value);
#else
value = graph()->NewNode(simplified()->ChangeFloat64HoleToTagged(),
value);
#endif
} else {
value = effect = graph()->NewNode(
simplified()->CheckFloat64Hole(
CheckFloat64HoleMode::kAllowReturnHole, FeedbackSource()),
value, effect, control);
}
} else {
value = effect = graph()->NewNode(
simplified()->CheckFloat64Hole(
CheckFloat64HoleMode::kNeverReturnHole, FeedbackSource()),
value, effect, control);
}
}
}
} else if (keyed_mode.access_mode() == AccessMode::kHas) {
value = effect = graph()->NewNode(simplified()->SpeculativeNumberLessThan(
NumberOperationHint::kSignedSmall),
index, length, effect, control);
if (IsHoleyElementsKind(elements_kind)) {
Node* branch = graph()->NewNode(common()->Branch(), value, control);
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
Node* vfalse = jsgraph()->FalseConstant();
element_access.type =
Type::Union(element_type, Type::Hole(), graph()->zone());
if (elements_kind == HOLEY_ELEMENTS ||
elements_kind == HOLEY_SMI_ELEMENTS) {
element_access.machine_type = MachineType::AnyTagged();
}
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Node* checked = etrue = graph()->NewNode(
simplified()->CheckBounds(
FeedbackSource(), CheckBoundsFlag::kConvertStringAndMinusZero),
index, length, etrue, if_true);
Node* element = etrue =
graph()->NewNode(simplified()->LoadElement(element_access), elements,
checked, etrue, if_true);
Node* vtrue;
if (CanTreatHoleAsUndefined(receiver_maps)) {
if (elements_kind == HOLEY_ELEMENTS ||
elements_kind == HOLEY_SMI_ELEMENTS) {
vtrue = graph()->NewNode(simplified()->ReferenceEqual(), element,
jsgraph()->TheHoleConstant());
} else {
vtrue =
graph()->NewNode(simplified()->NumberIsFloat64Hole(), element);
}
vtrue = graph()->NewNode(simplified()->BooleanNot(), vtrue);
} else {
if (elements_kind == HOLEY_ELEMENTS ||
elements_kind == HOLEY_SMI_ELEMENTS) {
etrue = graph()->NewNode(simplified()->CheckNotTaggedHole(), element,
etrue, if_true);
} else {
etrue = graph()->NewNode(
simplified()->CheckFloat64Hole(
CheckFloat64HoleMode::kNeverReturnHole, FeedbackSource()),
element, etrue, if_true);
}
vtrue = jsgraph()->TrueConstant();
}
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
effect = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
value = graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
vtrue, vfalse, control);
}
} else {
DCHECK(keyed_mode.access_mode() == AccessMode::kStore ||
keyed_mode.access_mode() == AccessMode::kStoreInLiteral ||
keyed_mode.access_mode() == AccessMode::kDefine);
if (IsSmiElementsKind(elements_kind)) {
value = effect = graph()->NewNode(
simplified()->CheckSmi(FeedbackSource()), value, effect, control);
} else if (IsDoubleElementsKind(elements_kind)) {
#ifdef V8_ENABLE_UNDEFINED_DOUBLE
if (elements_kind == HOLEY_DOUBLE_ELEMENTS) {
value = effect = graph()->NewNode(
simplified()->CheckNumberOrUndefined(FeedbackSource()), value,
effect, control);
value = graph()->NewNode(
simplified()->NumberSilenceNaN(SilenceNanMode::kPreserveUndefined),
value);
} else {
#endif
value = effect =
graph()->NewNode(simplified()->CheckNumber(FeedbackSource()), value,
effect, control);
value = graph()->NewNode(simplified()->NumberSilenceNaN(), value);
#ifdef V8_ENABLE_UNDEFINED_DOUBLE
}
#endif
}
if (IsSmiOrObjectElementsKind(elements_kind) &&
keyed_mode.store_mode() == KeyedAccessStoreMode::kHandleCOW) {
elements = effect =
graph()->NewNode(simplified()->EnsureWritableFastElements(), receiver,
elements, effect, control);
} else if (StoreModeCanGrow(keyed_mode.store_mode())) {
Node* elements_length = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForFixedArrayLength()),
elements, effect, control);
Node* limit =
IsHoleyElementsKind(elements_kind)
? graph()->NewNode(simplified()->NumberAdd(), elements_length,
jsgraph()->ConstantNoHole(JSObject::kMaxGap))
: receiver_is_jsarray
? graph()->NewNode(simplified()->NumberAdd(), length,
jsgraph()->OneConstant())
: elements_length;
index = effect = graph()->NewNode(
simplified()->CheckBounds(
FeedbackSource(), CheckBoundsFlag::kConvertStringAndMinusZero),
index, limit, effect, control);
GrowFastElementsMode mode =
IsDoubleElementsKind(elements_kind)
? GrowFastElementsMode::kDoubleElements
: GrowFastElementsMode::kSmiOrObjectElements;
elements = effect = graph()->NewNode(
simplified()->MaybeGrowFastElements(mode, FeedbackSource()), receiver,
elements, index, elements_length, effect, control);
if (IsSmiOrObjectElementsKind(elements_kind) &&
keyed_mode.store_mode() == KeyedAccessStoreMode::kGrowAndHandleCOW) {
elements = effect =
graph()->NewNode(simplified()->EnsureWritableFastElements(),
receiver, elements, effect, control);
}
if (receiver_is_jsarray) {
Node* check =
graph()->NewNode(simplified()->NumberLessThan(), index, length);
Node* branch = graph()->NewNode(common()->Branch(), check, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
{
}
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
{
Node* new_length = graph()->NewNode(simplified()->NumberAdd(), index,
jsgraph()->OneConstant());
efalse = graph()->NewNode(
simplified()->StoreField(
AccessBuilder::ForJSArrayLength(elements_kind)),
receiver, new_length, efalse, if_false);
}
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
effect =
graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
}
}
effect = graph()->NewNode(simplified()->StoreElement(element_access),
elements, index, value, effect, control);
}
return ValueEffectControl(value, effect, control);
}
JSNativeContextSpecialization::ValueEffectControl
JSNativeContextSpecialization::
BuildElementAccessForTypedArrayOrRabGsabTypedArray(
Node* receiver, Node* index, Node* value, Node* effect, Node* control,
Node* context, ElementsKind elements_kind,
KeyedAccessMode const& keyed_mode) {
DCHECK(IsTypedArrayElementsKind(elements_kind) ||
IsRabGsabTypedArrayElementsKind(elements_kind));
DCHECK(keyed_mode.access_mode() != AccessMode::kDefine);
Node* buffer_or_receiver = receiver;
Node* length;
Node* base_pointer;
Node* external_pointer;
OptionalJSTypedArrayRef typed_array =
GetTypedArrayConstant(broker(), receiver);
if (typed_array.has_value() &&
!IsRabGsabTypedArrayElementsKind(elements_kind)) {
if (typed_array->map(broker()).elements_kind() != elements_kind) {
JSGraphAssembler assembler(broker(), jsgraph_, zone(),
BranchSemantics::kJS,
[this](Node* n) { this->Revisit(n); });
assembler.InitializeEffectControl(effect, control);
assembler.Unreachable();
ReleaseEffectAndControlFromAssembler(&assembler);
Node* dead = jsgraph_->Dead();
return ValueEffectControl{dead, dead, dead};
} else {
length = jsgraph()->ConstantNoHole(
typed_array->byte_length() >> ElementsKindToShiftSize(elements_kind));
DCHECK(!typed_array->is_on_heap());
base_pointer = jsgraph()->ZeroConstant();
external_pointer = jsgraph()->PointerConstant(typed_array->data_ptr());
}
} else {
JSGraphAssembler assembler(broker(), jsgraph_, zone(), BranchSemantics::kJS,
[this](Node* n) { this->Revisit(n); });
assembler.InitializeEffectControl(effect, control);
length = assembler.TypedArrayLength(
TNode<JSTypedArray>::UncheckedCast(receiver), {elements_kind},
TNode<Context>::UncheckedCast(context));
std::tie(effect, control) =
ReleaseEffectAndControlFromAssembler(&assembler);
if (JSTypedArray::kMaxSizeInHeap == 0) {
base_pointer = jsgraph()->ZeroConstant();
} else {
base_pointer = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSTypedArrayBasePointer()),
receiver, effect, control);
}
external_pointer = effect =
graph()->NewNode(simplified()->LoadField(
AccessBuilder::ForJSTypedArrayExternalPointer()),
receiver, effect, control);
}
if (!dependencies()->DependOnArrayBufferDetachingProtector()) {
Node* buffer =
typed_array.has_value()
? jsgraph()->ConstantNoHole(typed_array->buffer(broker()), broker())
: (effect = graph()->NewNode(
simplified()->LoadField(
AccessBuilder::ForJSArrayBufferViewBuffer()),
receiver, effect, control));
Node* buffer_bit_field = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForJSArrayBufferBitField()),
buffer, effect, control);
Node* check = graph()->NewNode(
simplified()->NumberEqual(),
graph()->NewNode(
simplified()->NumberBitwiseAnd(), buffer_bit_field,
jsgraph()->ConstantNoHole(JSArrayBuffer::WasDetachedBit::kMask)),
jsgraph()->ZeroConstant());
effect = graph()->NewNode(
simplified()->CheckIf(DeoptimizeReason::kArrayBufferWasDetached), check,
effect, control);
buffer_or_receiver = buffer;
}
enum Situation { kBoundsCheckDone, kHandleOOB_SmiAndRangeCheckComputed };
Situation situation;
TNode<BoolT> check;
if ((keyed_mode.IsLoad() && LoadModeHandlesOOB(keyed_mode.load_mode())) ||
(keyed_mode.IsStore() &&
StoreModeIgnoresTypeArrayOOB(keyed_mode.store_mode()))) {
index = effect = graph()->NewNode(simplified()->CheckSmi(FeedbackSource()),
index, effect, control);
TNode<Boolean> compare_length = TNode<Boolean>::UncheckedCast(
graph()->NewNode(simplified()->NumberLessThan(), index, length));
JSGraphAssembler assembler(broker(), jsgraph_, zone(), BranchSemantics::kJS,
[this](Node* n) { this->Revisit(n); });
assembler.InitializeEffectControl(effect, control);
TNode<BoolT> check_less_than_length =
assembler.EnterMachineGraph<BoolT>(compare_length, UseInfo::Bool());
TNode<Int32T> index_int32 = assembler.EnterMachineGraph<Int32T>(
TNode<Smi>::UncheckedCast(index), UseInfo::TruncatingWord32());
TNode<BoolT> check_non_negative =
assembler.Int32LessThanOrEqual(assembler.Int32Constant(0), index_int32);
check = TNode<BoolT>::UncheckedCast(
assembler.Word32And(check_less_than_length, check_non_negative));
std::tie(effect, control) =
ReleaseEffectAndControlFromAssembler(&assembler);
situation = kHandleOOB_SmiAndRangeCheckComputed;
} else {
index = effect = graph()->NewNode(
simplified()->CheckBounds(FeedbackSource(),
CheckBoundsFlag::kConvertStringAndMinusZero |
CheckBoundsFlag::kAllow64BitBounds),
index, length, effect, control);
situation = kBoundsCheckDone;
}
ExternalArrayType external_array_type =
GetArrayTypeFromElementsKind(elements_kind);
switch (keyed_mode.access_mode()) {
case AccessMode::kLoad: {
if (situation == kHandleOOB_SmiAndRangeCheckComputed) {
DCHECK_NE(check, nullptr);
Node* branch = graph()->NewNode(
common()->Branch(BranchHint::kTrue, BranchSemantics::kMachine),
check, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
Node* vtrue;
{
if (v8_flags.turbo_typer_hardening) {
index = etrue = graph()->NewNode(
simplified()->CheckBounds(
FeedbackSource(),
CheckBoundsFlag::kConvertStringAndMinusZero |
CheckBoundsFlag::kAbortOnOutOfBounds |
CheckBoundsFlag::kAllow64BitBounds),
index, length, etrue, if_true);
}
vtrue = etrue = graph()->NewNode(
simplified()->LoadTypedElement(external_array_type),
buffer_or_receiver, base_pointer, external_pointer, index, etrue,
if_true);
}
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
Node* vfalse;
{
vfalse = jsgraph()->UndefinedConstant();
}
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
effect =
graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
vtrue, vfalse, control);
} else {
DCHECK_EQ(kBoundsCheckDone, situation);
value = effect = graph()->NewNode(
simplified()->LoadTypedElement(external_array_type),
buffer_or_receiver, base_pointer, external_pointer, index, effect,
control);
}
break;
}
case AccessMode::kStoreInLiteral:
case AccessMode::kDefine:
UNREACHABLE();
case AccessMode::kStore: {
if (external_array_type == kExternalBigInt64Array ||
external_array_type == kExternalBigUint64Array) {
value = effect = graph()->NewNode(
simplified()->SpeculativeToBigInt(BigIntOperationHint::kBigInt,
FeedbackSource()),
value, effect, control);
} else {
value = effect = graph()->NewNode(
simplified()->SpeculativeToNumber(
NumberOperationHint::kNumberOrOddball, FeedbackSource()),
value, effect, control);
}
if (external_array_type == kExternalUint8ClampedArray) {
value = graph()->NewNode(simplified()->NumberToUint8Clamped(), value);
}
if (situation == kHandleOOB_SmiAndRangeCheckComputed) {
DCHECK_NE(check, nullptr);
Node* branch = graph()->NewNode(
common()->Branch(BranchHint::kTrue, BranchSemantics::kMachine),
check, control);
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = effect;
{
if (v8_flags.turbo_typer_hardening) {
index = etrue = graph()->NewNode(
simplified()->CheckBounds(
FeedbackSource(),
CheckBoundsFlag::kConvertStringAndMinusZero |
CheckBoundsFlag::kAbortOnOutOfBounds |
CheckBoundsFlag::kAllow64BitBounds),
index, length, etrue, if_true);
}
etrue = graph()->NewNode(
simplified()->StoreTypedElement(external_array_type),
buffer_or_receiver, base_pointer, external_pointer, index, value,
etrue, if_true);
}
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* efalse = effect;
{
}
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
effect =
graph()->NewNode(common()->EffectPhi(2), etrue, efalse, control);
} else {
DCHECK_EQ(kBoundsCheckDone, situation);
effect = graph()->NewNode(
simplified()->StoreTypedElement(external_array_type),
buffer_or_receiver, base_pointer, external_pointer, index, value,
effect, control);
}
break;
}
case AccessMode::kHas:
if (situation == kHandleOOB_SmiAndRangeCheckComputed) {
DCHECK_NE(check, nullptr);
JSGraphAssembler assembler(broker(), jsgraph_, zone(),
BranchSemantics::kJS,
[this](Node* n) { this->Revisit(n); });
assembler.InitializeEffectControl(effect, control);
value = assembler.MachineSelectIf<Boolean>(check)
.Then([&]() { return assembler.TrueConstant(); })
.Else([&]() { return assembler.FalseConstant(); })
.ExpectTrue()
.Value();
std::tie(effect, control) =
ReleaseEffectAndControlFromAssembler(&assembler);
} else {
DCHECK_EQ(kBoundsCheckDone, situation);
value = jsgraph()->TrueConstant();
}
break;
}
return ValueEffectControl(value, effect, control);
}
Node* JSNativeContextSpecialization::BuildIndexedStringLoad(
Node* receiver, Node* index, Node* length, Node** effect, Node** control,
KeyedAccessLoadMode load_mode) {
if (LoadModeHandlesOOB(load_mode) &&
dependencies()->DependOnNoElementsProtector()) {
index = *effect = graph()->NewNode(
simplified()->CheckBounds(FeedbackSource(),
CheckBoundsFlag::kConvertStringAndMinusZero),
index, jsgraph()->ConstantNoHole(String::kMaxLength), *effect,
*control);
Node* check =
graph()->NewNode(simplified()->NumberLessThan(), index, length);
Node* branch =
graph()->NewNode(common()->Branch(BranchHint::kTrue), check, *control);
Node* if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* etrue = *effect;
if (v8_flags.turbo_typer_hardening) {
etrue = index = graph()->NewNode(
simplified()->CheckBounds(
FeedbackSource(), CheckBoundsFlag::kConvertStringAndMinusZero |
CheckBoundsFlag::kAbortOnOutOfBounds),
index, length, etrue, if_true);
}
Node* vtrue = etrue = graph()->NewNode(simplified()->StringCharCodeAt(),
receiver, index, etrue, if_true);
vtrue = graph()->NewNode(simplified()->StringFromSingleCharCode(), vtrue);
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
Node* vfalse = jsgraph()->UndefinedConstant();
*control = graph()->NewNode(common()->Merge(2), if_true, if_false);
*effect =
graph()->NewNode(common()->EffectPhi(2), etrue, *effect, *control);
return graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
vtrue, vfalse, *control);
} else {
index = *effect = graph()->NewNode(
simplified()->CheckBounds(FeedbackSource(),
CheckBoundsFlag::kConvertStringAndMinusZero),
index, length, *effect, *control);
Node* value = *effect = graph()->NewNode(
simplified()->StringCharCodeAt(), receiver, index, *effect, *control);
value = graph()->NewNode(simplified()->StringFromSingleCharCode(), value);
return value;
}
}
Node* JSNativeContextSpecialization::BuildExtendPropertiesBackingStore(
MapRef map, Node* properties, Node* effect, Node* control) {
DCHECK_EQ(map.UnusedPropertyFields(), 0);
int in_object_length = map.GetInObjectProperties();
int length = map.NextFreePropertyIndex() - in_object_length;
SBXCHECK_GE(length, 0);
int new_length = length + JSObject::kFieldsAdded;
DescriptorArrayRef descs = map.instance_descriptors(broker());
InternalIndex first_out_of_object_descriptor(in_object_length);
InternalIndex number_of_descriptors(descs.object()->number_of_descriptors());
for (InternalIndex i(in_object_length); i < number_of_descriptors; ++i) {
PropertyDetails details = descs.GetPropertyDetails(i);
if (details.location() != PropertyLocation::kField) {
continue;
}
if (details.field_index() < in_object_length) {
continue;
}
first_out_of_object_descriptor = i;
break;
}
ZoneVector<std::pair<Node*, Representation>> values(zone());
values.reserve(new_length);
InternalIndex descriptor = first_out_of_object_descriptor;
for (int i = 0; i < length; ++i, ++descriptor) {
PropertyDetails details = descs.GetPropertyDetails(descriptor);
while (details.location() != PropertyLocation::kField) {
++descriptor;
details = descs.GetPropertyDetails(descriptor);
}
DCHECK_EQ(i, details.field_index() - in_object_length);
Node* value = effect = graph()->NewNode(
simplified()->LoadField(
AccessBuilder::ForPropertyArraySlot(i, details.representation())),
properties, effect, control);
values.push_back({value, details.representation()});
}
for (int i = 0; i < JSObject::kFieldsAdded; ++i) {
values.push_back(
{jsgraph()->UndefinedConstant(), Representation::Tagged()});
}
Node* hash;
if (length == 0) {
hash = graph()->NewNode(
common()->Select(MachineRepresentation::kTaggedSigned),
graph()->NewNode(simplified()->ObjectIsSmi(), properties), properties,
jsgraph()->SmiConstant(PropertyArray::kNoHashSentinel));
hash = effect = graph()->NewNode(common()->TypeGuard(Type::SignedSmall()),
hash, effect, control);
hash = graph()->NewNode(
simplified()->NumberShiftLeft(), hash,
jsgraph()->ConstantNoHole(PropertyArray::HashField::kShift));
} else {
hash = effect = graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForPropertyArrayLengthAndHash()),
properties, effect, control);
hash = graph()->NewNode(
simplified()->NumberBitwiseAnd(), hash,
jsgraph()->ConstantNoHole(PropertyArray::HashField::kMask));
}
Node* new_length_and_hash =
graph()->NewNode(simplified()->NumberBitwiseOr(),
jsgraph()->ConstantNoHole(new_length), hash);
new_length_and_hash = effect =
graph()->NewNode(common()->TypeGuard(Type::SignedSmall()),
new_length_and_hash, effect, control);
AllocationBuilder a(jsgraph(), broker(), effect, control);
a.Allocate(PropertyArray::SizeFor(new_length), AllocationType::kYoung,
Type::OtherInternal());
a.Store(AccessBuilder::ForMap(), jsgraph()->PropertyArrayMapConstant());
a.Store(AccessBuilder::ForPropertyArrayLengthAndHash(), new_length_and_hash);
for (int i = 0; i < new_length; ++i) {
a.Store(AccessBuilder::ForPropertyArraySlot(i, values[i].second),
values[i].first);
}
return a.Finish();
}
Node* JSNativeContextSpecialization::BuildCheckEqualsName(NameRef name,
Node* value,
Node* effect,
Node* control) {
DCHECK(name.IsUniqueName());
Operator const* const op =
name.IsSymbol() ? simplified()->CheckEqualsSymbol()
: simplified()->CheckEqualsInternalizedString();
return graph()->NewNode(op, jsgraph()->ConstantNoHole(name, broker()), value,
effect, control);
}
bool JSNativeContextSpecialization::CanTreatHoleAsUndefined(
ZoneVector<MapRef> const& receiver_maps) {
for (MapRef receiver_map : receiver_maps) {
ObjectRef receiver_prototype = receiver_map.prototype(broker());
if (!receiver_prototype.IsJSObject() ||
!broker()->IsArrayOrObjectPrototype(receiver_prototype.AsJSObject())) {
return false;
}
}
return dependencies()->DependOnNoElementsProtector();
}
bool JSNativeContextSpecialization::InferMaps(Node* object, Effect effect,
ZoneVector<MapRef>* maps) const {
ZoneRefSet<Map> map_set;
NodeProperties::InferMapsResult result =
NodeProperties::InferMapsUnsafe(broker(), object, effect, &map_set);
if (result == NodeProperties::kReliableMaps) {
for (MapRef map : map_set) {
maps->push_back(map);
}
return true;
} else if (result == NodeProperties::kUnreliableMaps) {
for (MapRef map : map_set) {
if (!map.is_stable()) return false;
}
for (MapRef map : map_set) {
maps->push_back(map);
}
return true;
}
return false;
}
OptionalMapRef JSNativeContextSpecialization::InferRootMap(Node* object) const {
HeapObjectMatcher m(object);
if (m.HasResolvedValue()) {
MapRef map = m.Ref(broker()).map(broker());
return map.FindRootMap(broker());
} else if (m.IsJSCreate()) {
OptionalMapRef initial_map =
NodeProperties::GetJSCreateMap(broker(), object);
if (initial_map.has_value()) {
DCHECK(initial_map->equals(initial_map->FindRootMap(broker())));
return *initial_map;
}
}
return std::nullopt;
}
Node* JSNativeContextSpecialization::BuildLoadPrototypeFromObject(
Node* object, Node* effect, Node* control) {
Node* map = effect =
graph()->NewNode(simplified()->LoadField(AccessBuilder::ForMap()), object,
effect, control);
return graph()->NewNode(
simplified()->LoadField(AccessBuilder::ForMapPrototype()), map, effect,
control);
}
std::pair<Node*, Node*>
JSNativeContextSpecialization::ReleaseEffectAndControlFromAssembler(
JSGraphAssembler* gasm) {
auto catch_scope = gasm->catch_scope();
DCHECK(catch_scope->is_outermost());
if (catch_scope->has_handler() &&
catch_scope->has_exceptional_control_flow()) {
TNode<Object> handler_exception;
Effect handler_effect{nullptr};
Control handler_control{nullptr};
gasm->catch_scope()->MergeExceptionalPaths(
&handler_exception, &handler_effect, &handler_control);
ReplaceWithValue(gasm->outermost_handler(), handler_exception,
handler_effect, handler_control);
}
return {gasm->effect(), gasm->control()};
}
TFGraph* JSNativeContextSpecialization::graph() const {
return jsgraph()->graph();
}
Isolate* JSNativeContextSpecialization::isolate() const {
return jsgraph()->isolate();
}
Factory* JSNativeContextSpecialization::factory() const {
return isolate()->factory();
}
CommonOperatorBuilder* JSNativeContextSpecialization::common() const {
return jsgraph()->common();
}
JSOperatorBuilder* JSNativeContextSpecialization::javascript() const {
return jsgraph()->javascript();
}
SimplifiedOperatorBuilder* JSNativeContextSpecialization::simplified() const {
return jsgraph()->simplified();
}
}
}
}