// static
+FieldAccess AccessBuilder::ForFixedArrayLength() {
+ return {kTaggedBase, FixedArray::kLengthOffset, MaybeHandle<Name>(),
+ Type::TaggedSigned(), kMachAnyTagged};
+}
+
+
+// static
FieldAccess AccessBuilder::ForExternalArrayPointer() {
return {kTaggedBase, ExternalArray::kExternalPointerOffset,
MaybeHandle<Name>(), Type::UntaggedPointer(), kMachPtr};
// Provides access to JSArrayBuffer::backing_store() field.
static FieldAccess ForJSArrayBufferBackingStore();
+ // Provides access to FixedArray::length() field.
+ static FieldAccess ForFixedArrayLength();
+
// Provides access to ExternalArray::external_pointer() field.
static FieldAccess ForExternalArrayPointer();
}
+// A helper class to construct inline allocations on the simplified operator
+// level. This keeps track of the effect chain for initial stores on a newly
+// allocated object and also provides helpers for commonly allocated objects.
+class AllocationBuilder final {
+ public:
+ AllocationBuilder(JSGraph* jsgraph, SimplifiedOperatorBuilder* simplified,
+ Node* effect, Node* control)
+ : jsgraph_(jsgraph),
+ simplified_(simplified),
+ allocation_(nullptr),
+ effect_(effect),
+ control_(control) {}
+
+ // Primitive allocation of static size.
+ void Allocate(int size) {
+ allocation_ = graph()->NewNode(
+ simplified()->Allocate(), jsgraph()->Constant(size), effect_, control_);
+ effect_ = allocation_;
+ }
+
+ // Primitive store into a field.
+ void Store(const FieldAccess& access, Node* value) {
+ effect_ = graph()->NewNode(simplified()->StoreField(access), allocation_,
+ value, effect_, control_);
+ }
+
+ // Compound allocation of a FixedArray.
+ void AllocateArray(int length, Handle<Map> map) {
+ Allocate(FixedArray::SizeFor(length));
+ Store(AccessBuilder::ForMap(), map);
+ Store(AccessBuilder::ForFixedArrayLength(), jsgraph()->Constant(length));
+ }
+
+ // Compound store of a constant into a field.
+ void Store(const FieldAccess& access, Handle<Object> value) {
+ Store(access, jsgraph()->Constant(value));
+ }
+
+ Node* allocation() const { return allocation_; }
+ Node* effect() const { return effect_; }
+
+ protected:
+ JSGraph* jsgraph() { return jsgraph_; }
+ Graph* graph() { return jsgraph_->graph(); }
+ SimplifiedOperatorBuilder* simplified() { return simplified_; }
+
+ private:
+ JSGraph* const jsgraph_;
+ SimplifiedOperatorBuilder* simplified_;
+ Node* allocation_;
+ Node* effect_;
+ Node* control_;
+};
+
+
// A helper class to simplify the process of reducing a single binop node with a
// JSOperator. This class manages the rewriting of context, control, and effect
// dependencies during lowering of a binop and contains numerous helper
}
+Reduction JSTypedLowering::ReduceJSCreateWithContext(Node* node) {
+ DCHECK_EQ(IrOpcode::kJSCreateWithContext, node->opcode());
+ Node* const input = NodeProperties::GetValueInput(node, 0);
+ Type* input_type = NodeProperties::GetBounds(input).upper;
+ if (FLAG_turbo_allocate && input_type->Is(Type::Receiver())) {
+ // JSCreateWithContext(o:receiver, f)
+ Node* const effect = NodeProperties::GetEffectInput(node);
+ Node* const control = NodeProperties::GetControlInput(node);
+ Node* const closure = NodeProperties::GetValueInput(node, 1);
+ Node* const context = NodeProperties::GetContextInput(node);
+ Node* const load = graph()->NewNode(
+ simplified()->LoadField(
+ AccessBuilder::ForContextSlot(Context::GLOBAL_OBJECT_INDEX)),
+ context, effect, control);
+ AllocationBuilder a(jsgraph(), simplified(), effect, control);
+ STATIC_ASSERT(Context::MIN_CONTEXT_SLOTS == 4); // Ensure fully covered.
+ a.AllocateArray(Context::MIN_CONTEXT_SLOTS, factory()->with_context_map());
+ a.Store(AccessBuilder::ForContextSlot(Context::CLOSURE_INDEX), closure);
+ a.Store(AccessBuilder::ForContextSlot(Context::PREVIOUS_INDEX), context);
+ a.Store(AccessBuilder::ForContextSlot(Context::EXTENSION_INDEX), input);
+ a.Store(AccessBuilder::ForContextSlot(Context::GLOBAL_OBJECT_INDEX), load);
+ // TODO(mstarzinger): We could mutate {node} into the allocation instead.
+ NodeProperties::SetBounds(a.allocation(), NodeProperties::GetBounds(node));
+ NodeProperties::ReplaceWithValue(node, node, a.effect());
+ node->ReplaceInput(0, a.allocation());
+ node->ReplaceInput(1, a.effect());
+ node->set_op(common()->Finish(1));
+ node->TrimInputCount(2);
+ return Changed(node);
+ }
+ return NoChange();
+}
+
+
+Reduction JSTypedLowering::ReduceJSCreateBlockContext(Node* node) {
+ DCHECK_EQ(IrOpcode::kJSCreateBlockContext, node->opcode());
+ Node* const input = NodeProperties::GetValueInput(node, 0);
+ HeapObjectMatcher<ScopeInfo> minput(input);
+ DCHECK(minput.HasValue()); // TODO(mstarzinger): Make ScopeInfo static.
+ int context_length = minput.Value().handle()->ContextLength();
+ if (FLAG_turbo_allocate && context_length < kBlockContextAllocationLimit) {
+ // JSCreateBlockContext(s:scope[length < limit], f)
+ Node* const effect = NodeProperties::GetEffectInput(node);
+ Node* const control = NodeProperties::GetControlInput(node);
+ Node* const closure = NodeProperties::GetValueInput(node, 1);
+ Node* const context = NodeProperties::GetContextInput(node);
+ Node* const load = graph()->NewNode(
+ simplified()->LoadField(
+ AccessBuilder::ForContextSlot(Context::GLOBAL_OBJECT_INDEX)),
+ context, effect, control);
+ AllocationBuilder a(jsgraph(), simplified(), effect, control);
+ STATIC_ASSERT(Context::MIN_CONTEXT_SLOTS == 4); // Ensure fully covered.
+ a.AllocateArray(context_length, factory()->block_context_map());
+ a.Store(AccessBuilder::ForContextSlot(Context::CLOSURE_INDEX), closure);
+ a.Store(AccessBuilder::ForContextSlot(Context::PREVIOUS_INDEX), context);
+ a.Store(AccessBuilder::ForContextSlot(Context::EXTENSION_INDEX), input);
+ a.Store(AccessBuilder::ForContextSlot(Context::GLOBAL_OBJECT_INDEX), load);
+ for (int i = Context::MIN_CONTEXT_SLOTS; i < context_length; ++i) {
+ a.Store(AccessBuilder::ForContextSlot(i), jsgraph()->TheHoleConstant());
+ }
+ // TODO(mstarzinger): We could mutate {node} into the allocation instead.
+ NodeProperties::SetBounds(a.allocation(), NodeProperties::GetBounds(node));
+ NodeProperties::ReplaceWithValue(node, node, a.effect());
+ node->ReplaceInput(0, a.allocation());
+ node->ReplaceInput(1, a.effect());
+ node->set_op(common()->Finish(1));
+ node->TrimInputCount(2);
+ return Changed(node);
+ }
+ return NoChange();
+}
+
+
Reduction JSTypedLowering::Reduce(Node* node) {
// Check if the output type is a singleton. In that case we already know the
// result value and can simply replace the node if it's eliminable.
return ReduceJSCreateLiteralArray(node);
case IrOpcode::kJSCreateLiteralObject:
return ReduceJSCreateLiteralObject(node);
+ case IrOpcode::kJSCreateWithContext:
+ return ReduceJSCreateWithContext(node);
+ case IrOpcode::kJSCreateBlockContext:
+ return ReduceJSCreateBlockContext(node);
default:
break;
}
Reduction ReduceJSCreateClosure(Node* node);
Reduction ReduceJSCreateLiteralArray(Node* node);
Reduction ReduceJSCreateLiteralObject(Node* node);
+ Reduction ReduceJSCreateWithContext(Node* node);
+ Reduction ReduceJSCreateBlockContext(Node* node);
Reduction ReduceNumberBinop(Node* node, const Operator* numberOp);
Reduction ReduceInt32Binop(Node* node, const Operator* intOp);
Reduction ReduceUI32Shift(Node* node, Signedness left_signedness,
SimplifiedOperatorBuilder* simplified() { return &simplified_; }
MachineOperatorBuilder* machine() const;
+ // Limits up to which context allocations are inlined.
+ static const int kBlockContextAllocationLimit = 16;
+
JSGraph* jsgraph_;
SimplifiedOperatorBuilder simplified_;
ZoneVector<Node*> conversions_; // Cache inserted JSToXXX() conversions.
// not to call into arbitrary JavaScript, not to throw, and not to deoptimize
// are blacklisted here and can be called without a FrameState.
switch (function) {
+ case Runtime::kAllocateInTargetSpace:
case Runtime::kDefineClassMethod: // TODO(jarin): Is it safe?
case Runtime::kDefineGetterPropertyUnchecked: // TODO(jarin): Is it safe?
case Runtime::kDefineSetterPropertyUnchecked: // TODO(jarin): Is it safe?
V(ChangeFloat64ToTagged) \
V(ChangeBoolToBit) \
V(ChangeBitToBool) \
+ V(Allocate) \
V(LoadField) \
V(LoadBuffer) \
V(LoadElement) \
if (lower()) lowering->DoStringAdd(node);
break;
}
+ case IrOpcode::kAllocate: {
+ ProcessInput(node, 0, kMachAnyTagged);
+ ProcessRemainingInputs(node, 1);
+ SetOutput(node, kMachAnyTagged);
+ if (lower()) lowering->DoAllocate(node);
+ break;
+ }
case IrOpcode::kLoadField: {
FieldAccess access = FieldAccessOf(node->op());
ProcessInput(node, 0, changer_->TypeForBasePointer(access));
} // namespace
+void SimplifiedLowering::DoAllocate(Node* node) {
+ PretenureFlag pretenure = OpParameter<PretenureFlag>(node->op());
+ AllocationSpace space = pretenure == TENURED ? OLD_SPACE : NEW_SPACE;
+ Runtime::FunctionId f = Runtime::kAllocateInTargetSpace;
+ Operator::Properties props = node->op()->properties();
+ CallDescriptor* desc = Linkage::GetRuntimeCallDescriptor(zone(), f, 2, props);
+ node->set_op(common()->Call(desc));
+ ExternalReference ref(f, jsgraph()->isolate());
+ int32_t flags = AllocateTargetSpace::encode(space);
+ node->InsertInput(graph()->zone(), 0, jsgraph()->CEntryStubConstant(1));
+ node->InsertInput(graph()->zone(), 2, jsgraph()->SmiConstant(flags));
+ node->InsertInput(graph()->zone(), 3, jsgraph()->ExternalConstant(ref));
+ node->InsertInput(graph()->zone(), 4, jsgraph()->Int32Constant(2));
+ node->InsertInput(graph()->zone(), 5, jsgraph()->NoContextConstant());
+}
+
+
void SimplifiedLowering::DoLoadField(Node* node) {
const FieldAccess& access = FieldAccessOf(node->op());
node->set_op(machine()->Load(access.machine_type));
Node* SimplifiedLowering::StringComparison(Node* node, bool requires_ordering) {
- CEntryStub stub(jsgraph()->isolate(), 1);
Runtime::FunctionId f =
requires_ordering ? Runtime::kStringCompareRT : Runtime::kStringEquals;
ExternalReference ref(f, jsgraph()->isolate());
// interface descriptor is available for it.
CallDescriptor* desc = Linkage::GetRuntimeCallDescriptor(zone(), f, 2, props);
return graph()->NewNode(common()->Call(desc),
- jsgraph()->HeapConstant(stub.GetCode()),
+ jsgraph()->CEntryStubConstant(1),
NodeProperties::GetValueInput(node, 0),
NodeProperties::GetValueInput(node, 1),
jsgraph()->ExternalConstant(ref),
jsgraph()->Int32Constant(2),
- jsgraph()->UndefinedConstant());
+ jsgraph()->NoContextConstant());
}
void LowerAllNodes();
// TODO(titzer): These are exposed for direct testing. Use a friend class.
+ void DoAllocate(Node* node);
void DoLoadField(Node* node);
void DoStoreField(Node* node);
// TODO(turbofan): The output_type can be removed once the result of the
}
+const Operator* SimplifiedOperatorBuilder::Allocate(PretenureFlag pretenure) {
+ return new (zone())
+ Operator1<PretenureFlag>(IrOpcode::kAllocate, Operator::kNoThrow,
+ "Allocate", 1, 1, 1, 1, 1, 0, pretenure);
+}
+
+
const Operator* SimplifiedOperatorBuilder::LoadBuffer(BufferAccess access) {
switch (access.external_array_type()) {
#define LOAD_BUFFER(Type, type, TYPE, ctype, size) \
const Operator* ObjectIsSmi();
const Operator* ObjectIsNonNegativeSmi();
+ const Operator* Allocate(PretenureFlag pretenure = NOT_TENURED);
+
const Operator* LoadField(FieldAccess const&);
const Operator* StoreField(FieldAccess const&);
}
+Bounds Typer::Visitor::TypeAllocate(Node* node) {
+ return Bounds(Type::TaggedPointer());
+}
+
+
Bounds Typer::Visitor::TypeLoadField(Node* node) {
return Bounds(FieldAccessOf(node->op()).type);
}
CheckValueInputIs(node, 0, Type::Any());
CheckUpperIs(node, Type::Boolean());
break;
+ case IrOpcode::kAllocate:
+ CheckValueInputIs(node, 0, Type::PlainNumber());
+ CheckUpperIs(node, Type::TaggedPointer());
+ break;
case IrOpcode::kChangeTaggedToInt32: {
// Signed32 /\ Tagged -> Signed32 /\ UntaggedInt32
DEFINE_BOOL(turbo_splitting, true, "split nodes during scheduling in TurboFan")
DEFINE_BOOL(turbo_types, true, "use typed lowering in TurboFan")
DEFINE_BOOL(turbo_type_feedback, false, "use type feedback in TurboFan")
+DEFINE_BOOL(turbo_allocate, false, "enable inline allocations in TurboFan")
DEFINE_BOOL(turbo_source_positions, false,
"track source code positions when building TurboFan IR")
DEFINE_IMPLICATION(trace_turbo, turbo_source_positions)
#include "src/scopes.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/codegen-tester.h"
+#include "test/cctest/compiler/function-tester.h"
#include "test/cctest/compiler/graph-builder-tester.h"
#include "test/cctest/compiler/value-helper.h"
CHECK(factory()->NewNumber(expected)->SameValue(result));
}
+ template <typename T>
+ T* CallWithPotentialGC() {
+ // TODO(titzer): we wrap the code in a JSFunction here to reuse the
+ // JSEntryStub; that could be done with a special prologue or other stub.
+ Handle<JSFunction> fun = FunctionTester::ForMachineGraph(this->graph());
+ Handle<Object>* args = NULL;
+ MaybeHandle<Object> result = Execution::Call(
+ this->isolate(), fun, factory()->undefined_value(), 0, args, false);
+ return T::cast(*result.ToHandleChecked());
+ }
+
Factory* factory() { return this->isolate()->factory(); }
Heap* heap() { return this->isolate()->heap(); }
};
}
+TEST(RunAllocate) {
+ PretenureFlag flag[] = {NOT_TENURED, TENURED};
+
+ for (size_t i = 0; i < arraysize(flag); i++) {
+ SimplifiedLoweringTester<HeapObject*> t;
+ FieldAccess access = AccessBuilder::ForMap();
+ Node* size = t.jsgraph.Constant(HeapNumber::kSize);
+ Node* alloc = t.NewNode(t.simplified()->Allocate(flag[i]), size);
+ Node* map = t.jsgraph.Constant(t.factory()->heap_number_map());
+ t.StoreField(access, alloc, map);
+ t.Return(alloc);
+
+ t.LowerAllNodes();
+ t.GenerateCode();
+
+ if (Pipeline::SupportedTarget()) {
+ HeapObject* result = t.CallWithPotentialGC<HeapObject>();
+ CHECK(t.heap()->new_space()->Contains(result) || flag[i] == TENURED);
+ CHECK(t.heap()->old_space()->Contains(result) || flag[i] == NOT_TENURED);
+ CHECK(result->IsHeapNumber());
+ }
+ }
+}
+
+
// Fills in most of the nodes of the graph in order to make tests shorter.
class TestingGraph : public HandleAndZoneScope, public GraphAndBuilders {
public:
}
#endif
+
+// -----------------------------------------------------------------------------
+// JSCreateWithContext
+
+
+TEST_F(JSTypedLoweringTest, JSCreateWithContext) {
+ FLAG_turbo_allocate = true;
+ Node* const object = Parameter(Type::Receiver());
+ Node* const closure = Parameter(Type::Any());
+ Node* const context = Parameter(Type::Any());
+ Node* const frame_state = EmptyFrameState();
+ Node* const effect = graph()->start();
+ Node* const control = graph()->start();
+ Reduction r =
+ Reduce(graph()->NewNode(javascript()->CreateWithContext(), object,
+ closure, context, frame_state, effect, control));
+ ASSERT_TRUE(r.Changed());
+ EXPECT_THAT(r.replacement(),
+ IsFinish(IsAllocate(IsNumberConstant(Context::SizeFor(
+ Context::MIN_CONTEXT_SLOTS)),
+ effect, control),
+ _));
+}
+
} // namespace compiler
} // namespace internal
} // namespace v8
};
+class IsAllocateMatcher final : public NodeMatcher {
+ public:
+ IsAllocateMatcher(const Matcher<Node*>& size_matcher,
+ const Matcher<Node*>& effect_matcher,
+ const Matcher<Node*>& control_matcher)
+ : NodeMatcher(IrOpcode::kAllocate),
+ size_matcher_(size_matcher),
+ effect_matcher_(effect_matcher),
+ control_matcher_(control_matcher) {}
+
+ bool MatchAndExplain(Node* node, MatchResultListener* listener) const final {
+ return (NodeMatcher::MatchAndExplain(node, listener) &&
+ PrintMatchAndExplain(NodeProperties::GetValueInput(node, 0), "size",
+ size_matcher_, listener) &&
+ PrintMatchAndExplain(NodeProperties::GetEffectInput(node), "effect",
+ effect_matcher_, listener) &&
+ PrintMatchAndExplain(NodeProperties::GetControlInput(node),
+ "control", control_matcher_, listener));
+ }
+
+ private:
+ const Matcher<Node*> size_matcher_;
+ const Matcher<Node*> effect_matcher_;
+ const Matcher<Node*> control_matcher_;
+};
+
+
class IsLoadFieldMatcher final : public NodeMatcher {
public:
IsLoadFieldMatcher(const Matcher<FieldAccess>& access_matcher,
}
+Matcher<Node*> IsAllocate(const Matcher<Node*>& size_matcher,
+ const Matcher<Node*>& effect_matcher,
+ const Matcher<Node*>& control_matcher) {
+ return MakeMatcher(
+ new IsAllocateMatcher(size_matcher, effect_matcher, control_matcher));
+}
+
+
Matcher<Node*> IsLoadField(const Matcher<FieldAccess>& access_matcher,
const Matcher<Node*>& base_matcher,
const Matcher<Node*>& effect_matcher,
const Matcher<Node*>& rhs_matcher);
Matcher<Node*> IsNumberMultiply(const Matcher<Node*>& lhs_matcher,
const Matcher<Node*>& rhs_matcher);
+Matcher<Node*> IsAllocate(const Matcher<Node*>& size_matcher,
+ const Matcher<Node*>& effect_matcher,
+ const Matcher<Node*>& control_matcher);
Matcher<Node*> IsLoadField(const Matcher<FieldAccess>& access_matcher,
const Matcher<Node*>& base_matcher,
const Matcher<Node*>& effect_matcher,