[turbofan] Optimize access to the length property of functions

When compiling to JavaScript a language that supports curryfication, it
is convenient to be able to efficiently get the arity of a function to
check for partial application.

Change-Id: I6611b523b2c3795f1f8fb123f63f5b6d604d793d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4111447
Reviewed-by: Jakob Linke <jgruber@chromium.org>
Commit-Queue: Jakob Linke <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/main@{#85409}
This commit is contained in:
Jérôme Vouillon 2023-01-19 16:58:59 +01:00 committed by V8 LUCI CQ
parent 48e79783ee
commit 7eb8937bca
18 changed files with 158 additions and 2 deletions

View File

@ -147,6 +147,7 @@ Jan de Mooij <jandemooij@gmail.com>
Janusz Majnert <jmajnert@gmail.com> Janusz Majnert <jmajnert@gmail.com>
Javad Amiri <javad.amiri@anu.edu.au> Javad Amiri <javad.amiri@anu.edu.au>
Jay Freeman <saurik@saurik.com> Jay Freeman <saurik@saurik.com>
Jérôme Vouillon <jerome.vouillon@gmail.com>
Jesper van den Ende <jespertheend@gmail.com> Jesper van den Ende <jespertheend@gmail.com>
Ji Qiu <qiuji@iscas.ac.cn> Ji Qiu <qiuji@iscas.ac.cn>
Jiawen Geng <technicalcute@gmail.com> Jiawen Geng <technicalcute@gmail.com>

View File

@ -213,6 +213,19 @@ FieldAccess AccessBuilder::ForJSFunctionSharedFunctionInfo() {
return access; return access;
} }
// static
FieldAccess AccessBuilder::ForSharedFunctionInfoLength() {
FieldAccess access = {kTaggedBase,
SharedFunctionInfo::kLengthOffset,
Handle<Name>(),
MaybeHandle<Map>(),
TypeCache::Get()->kArgumentsLengthType,
MachineType::Uint16(),
kNoWriteBarrier,
"SharedFunctionInfoLength"};
return access;
}
// static // static
FieldAccess AccessBuilder::ForJSFunctionFeedbackCell() { FieldAccess AccessBuilder::ForJSFunctionFeedbackCell() {
FieldAccess access = {kTaggedBase, JSFunction::kFeedbackCellOffset, FieldAccess access = {kTaggedBase, JSFunction::kFeedbackCellOffset,

View File

@ -92,6 +92,9 @@ class V8_EXPORT_PRIVATE AccessBuilder final
// Provides access to JSFunction::shared() field. // Provides access to JSFunction::shared() field.
static FieldAccess ForJSFunctionSharedFunctionInfo(); static FieldAccess ForJSFunctionSharedFunctionInfo();
// Provides access to SharedFunctionInfo::length() field.
static FieldAccess ForSharedFunctionInfoLength();
// Provides access to JSFunction::feedback_cell() field. // Provides access to JSFunction::feedback_cell() field.
static FieldAccess ForJSFunctionFeedbackCell(); static FieldAccess ForJSFunctionFeedbackCell();

View File

@ -161,6 +161,12 @@ PropertyAccessInfo PropertyAccessInfo::StringLength(Zone* zone,
return PropertyAccessInfo(zone, kStringLength, {}, {{receiver_map}, zone}); return PropertyAccessInfo(zone, kStringLength, {}, {{receiver_map}, zone});
} }
// static
PropertyAccessInfo PropertyAccessInfo::FunctionLength(Zone* zone,
MapRef receiver_map) {
return PropertyAccessInfo(zone, kFunctionLength, {}, {{receiver_map}, zone});
}
// static // static
PropertyAccessInfo PropertyAccessInfo::DictionaryProtoDataConstant( PropertyAccessInfo PropertyAccessInfo::DictionaryProtoDataConstant(
Zone* zone, MapRef receiver_map, JSObjectRef holder, Zone* zone, MapRef receiver_map, JSObjectRef holder,
@ -343,7 +349,8 @@ bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
} }
case kNotFound: case kNotFound:
case kStringLength: { case kStringLength:
case kFunctionLength: {
DCHECK(unrecorded_dependencies_.empty()); DCHECK(unrecorded_dependencies_.empty());
DCHECK(that->unrecorded_dependencies_.empty()); DCHECK(that->unrecorded_dependencies_.empty());
AppendVector(&lookup_start_object_maps_, that->lookup_start_object_maps_); AppendVector(&lookup_start_object_maps_, that->lookup_start_object_maps_);
@ -1042,6 +1049,14 @@ PropertyAccessInfo AccessInfoFactory::LookupSpecialFieldAccessor(
} }
return Invalid(); return Invalid();
} }
// Check for JSFunction::length field accessor.
if (map.object()->IsJSFunctionMap()) {
if (Name::Equals(isolate(), name.object(),
isolate()->factory()->length_string())) {
return PropertyAccessInfo::FunctionLength(zone(), map);
}
return Invalid();
}
// Check for special JSObject field accessors. // Check for special JSObject field accessors.
FieldIndex field_index; FieldIndex field_index;
if (Accessors::IsJSObjectFieldAccessor(isolate(), map.object(), name.object(), if (Accessors::IsJSObjectFieldAccessor(isolate(), map.object(), name.object(),

View File

@ -65,7 +65,8 @@ class PropertyAccessInfo final {
kFastAccessorConstant, kFastAccessorConstant,
kDictionaryProtoAccessorConstant, kDictionaryProtoAccessorConstant,
kModuleExport, kModuleExport,
kStringLength kStringLength,
kFunctionLength
}; };
static PropertyAccessInfo NotFound(Zone* zone, MapRef receiver_map, static PropertyAccessInfo NotFound(Zone* zone, MapRef receiver_map,
@ -91,6 +92,7 @@ class PropertyAccessInfo final {
static PropertyAccessInfo ModuleExport(Zone* zone, MapRef receiver_map, static PropertyAccessInfo ModuleExport(Zone* zone, MapRef receiver_map,
CellRef cell); CellRef cell);
static PropertyAccessInfo StringLength(Zone* zone, MapRef receiver_map); static PropertyAccessInfo StringLength(Zone* zone, MapRef receiver_map);
static PropertyAccessInfo FunctionLength(Zone* zone, MapRef receiver_map);
static PropertyAccessInfo Invalid(Zone* zone); static PropertyAccessInfo Invalid(Zone* zone);
static PropertyAccessInfo DictionaryProtoDataConstant( static PropertyAccessInfo DictionaryProtoDataConstant(
Zone* zone, MapRef receiver_map, JSObjectRef holder, Zone* zone, MapRef receiver_map, JSObjectRef holder,
@ -113,6 +115,7 @@ class PropertyAccessInfo final {
} }
bool IsModuleExport() const { return kind() == kModuleExport; } bool IsModuleExport() const { return kind() == kModuleExport; }
bool IsStringLength() const { return kind() == kStringLength; } bool IsStringLength() const { return kind() == kStringLength; }
bool IsFunctionLength() const { return kind() == kFunctionLength; }
bool IsDictionaryProtoDataConstant() const { bool IsDictionaryProtoDataConstant() const {
return kind() == kDictionaryProtoDataConstant; return kind() == kDictionaryProtoDataConstant;
} }

View File

@ -180,6 +180,7 @@ class EffectControlLinearizer {
Node* LowerStringEqual(Node* node); Node* LowerStringEqual(Node* node);
Node* LowerStringLessThan(Node* node); Node* LowerStringLessThan(Node* node);
Node* LowerStringLessThanOrEqual(Node* node); Node* LowerStringLessThanOrEqual(Node* node);
Node* LowerFunctionLength(Node* node);
Node* LowerBigIntAdd(Node* node, Node* frame_state); Node* LowerBigIntAdd(Node* node, Node* frame_state);
Node* LowerBigIntSubtract(Node* node, Node* frame_state); Node* LowerBigIntSubtract(Node* node, Node* frame_state);
Node* LowerBigIntMultiply(Node* node, Node* frame_state); Node* LowerBigIntMultiply(Node* node, Node* frame_state);
@ -1275,6 +1276,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
case IrOpcode::kStringLessThanOrEqual: case IrOpcode::kStringLessThanOrEqual:
result = LowerStringLessThanOrEqual(node); result = LowerStringLessThanOrEqual(node);
break; break;
case IrOpcode::kFunctionLength:
result = LowerFunctionLength(node);
break;
case IrOpcode::kBigIntAdd: case IrOpcode::kBigIntAdd:
result = LowerBigIntAdd(node, frame_state); result = LowerBigIntAdd(node, frame_state);
break; break;
@ -4577,6 +4581,14 @@ Node* EffectControlLinearizer::LowerStringLessThanOrEqual(Node* node) {
Builtins::CallableFor(isolate(), Builtin::kStringLessThanOrEqual), node); Builtins::CallableFor(isolate(), Builtin::kStringLessThanOrEqual), node);
} }
Node* EffectControlLinearizer::LowerFunctionLength(Node* node) {
Node* subject = node->InputAt(0);
auto shared =
__ LoadField(AccessBuilder::ForJSFunctionSharedFunctionInfo(), subject);
return __ LoadField(AccessBuilder::ForSharedFunctionInfoLength(), shared);
}
Node* EffectControlLinearizer::LowerBigIntAdd(Node* node, Node* frame_state) { Node* EffectControlLinearizer::LowerBigIntAdd(Node* node, Node* frame_state) {
Node* lhs = node->InputAt(0); Node* lhs = node->InputAt(0);
Node* rhs = node->InputAt(1); Node* rhs = node->InputAt(1);

View File

@ -2798,6 +2798,9 @@ JSNativeContextSpecialization::BuildPropertyLoad(
} else if (access_info.IsStringLength()) { } else if (access_info.IsStringLength()) {
DCHECK_EQ(receiver, lookup_start_object); DCHECK_EQ(receiver, lookup_start_object);
value = graph()->NewNode(simplified()->StringLength(), receiver); value = graph()->NewNode(simplified()->StringLength(), receiver);
} else if (access_info.IsFunctionLength()) {
DCHECK_EQ(receiver, lookup_start_object);
value = graph()->NewNode(simplified()->FunctionLength(), receiver);
} else { } else {
DCHECK(access_info.IsDataField() || access_info.IsFastDataConstant() || DCHECK(access_info.IsDataField() || access_info.IsFastDataConstant() ||
access_info.IsDictionaryProtoDataConstant()); access_info.IsDictionaryProtoDataConstant());

View File

@ -523,6 +523,7 @@
V(StringToLowerCaseIntl) \ V(StringToLowerCaseIntl) \
V(StringToNumber) \ V(StringToNumber) \
V(StringToUpperCaseIntl) \ V(StringToUpperCaseIntl) \
V(FunctionLength) \
V(ToBoolean) \ V(ToBoolean) \
V(TransitionAndStoreElement) \ V(TransitionAndStoreElement) \
V(TransitionAndStoreNonNumberElement) \ V(TransitionAndStoreNonNumberElement) \

View File

@ -3595,6 +3595,11 @@ class RepresentationSelector {
MachineRepresentation::kTaggedPointer); MachineRepresentation::kTaggedPointer);
return; return;
} }
case IrOpcode::kFunctionLength: {
VisitUnop<T>(node, UseInfo::AnyTagged(),
MachineRepresentation::kWord32);
return;
}
case IrOpcode::kCheckBounds: case IrOpcode::kCheckBounds:
return VisitCheckBounds<T>(node, lowering); return VisitCheckBounds<T>(node, lowering);
case IrOpcode::kCheckHeapObject: { case IrOpcode::kCheckHeapObject: {

View File

@ -808,6 +808,7 @@ bool operator==(CheckMinusZeroParameters const& lhs,
V(StringLength, Operator::kNoProperties, 1, 0) \ V(StringLength, Operator::kNoProperties, 1, 0) \
V(StringToLowerCaseIntl, Operator::kNoProperties, 1, 0) \ V(StringToLowerCaseIntl, Operator::kNoProperties, 1, 0) \
V(StringToUpperCaseIntl, Operator::kNoProperties, 1, 0) \ V(StringToUpperCaseIntl, Operator::kNoProperties, 1, 0) \
V(FunctionLength, Operator::kNoProperties, 1, 0) \
V(TypeOf, Operator::kNoProperties, 1, 1) \ V(TypeOf, Operator::kNoProperties, 1, 1) \
V(PlainPrimitiveToNumber, Operator::kNoProperties, 1, 0) \ V(PlainPrimitiveToNumber, Operator::kNoProperties, 1, 0) \
V(PlainPrimitiveToWord32, Operator::kNoProperties, 1, 0) \ V(PlainPrimitiveToWord32, Operator::kNoProperties, 1, 0) \

View File

@ -911,6 +911,8 @@ class V8_EXPORT_PRIVATE SimplifiedOperatorBuilder final
const Operator* StringToUpperCaseIntl(); const Operator* StringToUpperCaseIntl();
const Operator* StringSubstring(); const Operator* StringSubstring();
const Operator* FunctionLength();
const Operator* FindOrderedHashMapEntryForInt32Key(); const Operator* FindOrderedHashMapEntryForInt32Key();
const Operator* FindOrderedCollectionEntry(CollectionKind collection_kind); const Operator* FindOrderedCollectionEntry(CollectionKind collection_kind);

View File

@ -2287,6 +2287,10 @@ Type Typer::Visitor::TypeStringLength(Node* node) {
Type Typer::Visitor::TypeStringSubstring(Node* node) { return Type::String(); } Type Typer::Visitor::TypeStringSubstring(Node* node) { return Type::String(); }
Type Typer::Visitor::TypeFunctionLength(Node* node) {
return Type::Range(0, InstructionStream::kMaxArguments, zone());
}
Type Typer::Visitor::TypeCheckBounds(Node* node) { Type Typer::Visitor::TypeCheckBounds(Node* node) {
return typer_->operation_typer_.CheckBounds(Operand(node, 0), return typer_->operation_typer_.CheckBounds(Operand(node, 0),
Operand(node, 1)); Operand(node, 1));

View File

@ -1227,6 +1227,10 @@ void Verifier::Visitor::Check(Node* node, const AllNodes& all) {
CheckValueInputIs(node, 2, Type::SignedSmall()); CheckValueInputIs(node, 2, Type::SignedSmall());
CheckTypeIs(node, Type::String()); CheckTypeIs(node, Type::String());
break; break;
case IrOpcode::kFunctionLength:
CheckValueInputIs(node, 0, Type::Any());
CheckTypeIs(node, TypeCache::Get()->kArgumentsLengthType);
break;
case IrOpcode::kReferenceEqual: case IrOpcode::kReferenceEqual:
CheckTypeIs(node, Type::Boolean()); CheckTypeIs(node, Type::Boolean());
break; break;

View File

@ -2453,6 +2453,22 @@ void StringLength::GenerateCode(MaglevAssembler* masm,
FieldMemOperand(object, String::kLengthOffset)); FieldMemOperand(object, String::kLengthOffset));
} }
void FunctionLength::SetValueLocationConstraints() {
UseRegister(object_input());
DefineAsRegister(this);
}
void FunctionLength::GenerateCode(MaglevAssembler* masm,
const ProcessingState& state) {
Register object = ToRegister(object_input());
__ AssertFunction(object);
UseScratchRegisterScope temps(masm);
Register shared = temps.AcquireX();
__ LoadTaggedPointerField(
shared, FieldMemOperand(object, JSFunction::kSharedFunctionInfoOffset));
__ Ldr(ToRegister(result()).W(),
FieldMemOperand(shared, SharedFunctionInfo::kLengthOffset));
}
void TestUndetectable::SetValueLocationConstraints() { void TestUndetectable::SetValueLocationConstraints() {
UseRegister(value()); UseRegister(value());
DefineAsRegister(this); DefineAsRegister(this);

View File

@ -1960,6 +1960,13 @@ bool MaglevGraphBuilder::TryBuildPropertyLoad(
current_interpreter_frame_.accumulator(), current_interpreter_frame_.accumulator(),
access_info); access_info);
return true; return true;
case compiler::PropertyAccessInfo::kFunctionLength:
DCHECK_EQ(receiver, lookup_start_object);
SetAccumulator(AddNewNode<FunctionLength>({receiver}));
RecordKnownProperty(lookup_start_object, name,
current_interpreter_frame_.accumulator(),
access_info);
return true;
} }
} }

View File

@ -210,6 +210,7 @@ class CompactInterpreterFrameState;
V(SetPendingMessage) \ V(SetPendingMessage) \
V(StringAt) \ V(StringAt) \
V(StringLength) \ V(StringLength) \
V(FunctionLength) \
V(ToBoolean) \ V(ToBoolean) \
V(ToBooleanLogicalNot) \ V(ToBooleanLogicalNot) \
V(TaggedEqual) \ V(TaggedEqual) \
@ -4639,6 +4640,25 @@ class StringLength : public FixedInputValueNodeT<1, StringLength> {
void PrintParams(std::ostream&, MaglevGraphLabeller*) const {} void PrintParams(std::ostream&, MaglevGraphLabeller*) const {}
}; };
class FunctionLength : public FixedInputValueNodeT<1, FunctionLength> {
using Base = FixedInputValueNodeT<1, FunctionLength>;
public:
explicit FunctionLength(uint64_t bitfield) : Base(bitfield) {}
static constexpr OpProperties kProperties =
OpProperties::Reading() | OpProperties::Int32();
static constexpr
typename Base::InputTypes kInputTypes{ValueRepresentation::kTagged};
static constexpr int kObjectIndex = 0;
Input& object_input() { return input(kObjectIndex); }
void SetValueLocationConstraints();
void GenerateCode(MaglevAssembler*, const ProcessingState&);
void PrintParams(std::ostream&, MaglevGraphLabeller*) const {}
};
class DefineNamedOwnGeneric class DefineNamedOwnGeneric
: public FixedInputValueNodeT<3, DefineNamedOwnGeneric> { : public FixedInputValueNodeT<3, DefineNamedOwnGeneric> {
using Base = FixedInputValueNodeT<3, DefineNamedOwnGeneric>; using Base = FixedInputValueNodeT<3, DefineNamedOwnGeneric>;

View File

@ -1331,6 +1331,21 @@ void StringLength::GenerateCode(MaglevAssembler* masm,
__ movl(ToRegister(result()), FieldOperand(object, String::kLengthOffset)); __ movl(ToRegister(result()), FieldOperand(object, String::kLengthOffset));
} }
void FunctionLength::SetValueLocationConstraints() {
UseRegister(object_input());
DefineAsRegister(this);
}
void FunctionLength::GenerateCode(MaglevAssembler* masm,
const ProcessingState& state) {
Register object = ToRegister(object_input());
__ AssertFunction(object);
__ LoadTaggedPointerField(
kScratchRegister,
FieldOperand(object, JSFunction::kSharedFunctionInfoOffset));
__ movl(ToRegister(result()),
FieldOperand(kScratchRegister, SharedFunctionInfo::kLengthOffset));
}
void Int32AddWithOverflow::SetValueLocationConstraints() { void Int32AddWithOverflow::SetValueLocationConstraints() {
UseRegister(left_input()); UseRegister(left_input());
UseRegister(right_input()); UseRegister(right_input());

View File

@ -0,0 +1,31 @@
// Copyright 2023 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --turbofan --no-always-turbofan
function f(g) {
return g.length;
}
function g(x, y) {}
function h(x, y, z) {}
function OptimizeAndTest(fn) {
%PrepareFunctionForOptimization(fn);
assertEquals(1, fn(f));
assertEquals(2, fn(g));
assertEquals(3, fn(h));
%OptimizeFunctionOnNextCall(fn);
fn(g);
assertOptimized(fn);
assertEquals(1, fn(f));
assertEquals(2, fn(g));
assertEquals(3, fn(h));
assertOptimized(fn);
assertEquals(3, fn('abc'));
assertUnoptimized(fn);
}
OptimizeAndTest(f);