[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>
Javad Amiri <javad.amiri@anu.edu.au>
Jay Freeman <saurik@saurik.com>
Jérôme Vouillon <jerome.vouillon@gmail.com>
Jesper van den Ende <jespertheend@gmail.com>
Ji Qiu <qiuji@iscas.ac.cn>
Jiawen Geng <technicalcute@gmail.com>

View File

@ -213,6 +213,19 @@ FieldAccess AccessBuilder::ForJSFunctionSharedFunctionInfo() {
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
FieldAccess AccessBuilder::ForJSFunctionFeedbackCell() {
FieldAccess access = {kTaggedBase, JSFunction::kFeedbackCellOffset,

View File

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

View File

@ -161,6 +161,12 @@ PropertyAccessInfo PropertyAccessInfo::StringLength(Zone* 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
PropertyAccessInfo PropertyAccessInfo::DictionaryProtoDataConstant(
Zone* zone, MapRef receiver_map, JSObjectRef holder,
@ -343,7 +349,8 @@ bool PropertyAccessInfo::Merge(PropertyAccessInfo const* that,
}
case kNotFound:
case kStringLength: {
case kStringLength:
case kFunctionLength: {
DCHECK(unrecorded_dependencies_.empty());
DCHECK(that->unrecorded_dependencies_.empty());
AppendVector(&lookup_start_object_maps_, that->lookup_start_object_maps_);
@ -1042,6 +1049,14 @@ PropertyAccessInfo AccessInfoFactory::LookupSpecialFieldAccessor(
}
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.
FieldIndex field_index;
if (Accessors::IsJSObjectFieldAccessor(isolate(), map.object(), name.object(),

View File

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

View File

@ -180,6 +180,7 @@ class EffectControlLinearizer {
Node* LowerStringEqual(Node* node);
Node* LowerStringLessThan(Node* node);
Node* LowerStringLessThanOrEqual(Node* node);
Node* LowerFunctionLength(Node* node);
Node* LowerBigIntAdd(Node* node, Node* frame_state);
Node* LowerBigIntSubtract(Node* node, Node* frame_state);
Node* LowerBigIntMultiply(Node* node, Node* frame_state);
@ -1275,6 +1276,9 @@ bool EffectControlLinearizer::TryWireInStateEffect(Node* node,
case IrOpcode::kStringLessThanOrEqual:
result = LowerStringLessThanOrEqual(node);
break;
case IrOpcode::kFunctionLength:
result = LowerFunctionLength(node);
break;
case IrOpcode::kBigIntAdd:
result = LowerBigIntAdd(node, frame_state);
break;
@ -4577,6 +4581,14 @@ Node* EffectControlLinearizer::LowerStringLessThanOrEqual(Node* 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* lhs = node->InputAt(0);
Node* rhs = node->InputAt(1);

View File

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

View File

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

View File

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

View File

@ -808,6 +808,7 @@ bool operator==(CheckMinusZeroParameters const& lhs,
V(StringLength, Operator::kNoProperties, 1, 0) \
V(StringToLowerCaseIntl, Operator::kNoProperties, 1, 0) \
V(StringToUpperCaseIntl, Operator::kNoProperties, 1, 0) \
V(FunctionLength, Operator::kNoProperties, 1, 0) \
V(TypeOf, Operator::kNoProperties, 1, 1) \
V(PlainPrimitiveToNumber, 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* StringSubstring();
const Operator* FunctionLength();
const Operator* FindOrderedHashMapEntryForInt32Key();
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::TypeFunctionLength(Node* node) {
return Type::Range(0, InstructionStream::kMaxArguments, zone());
}
Type Typer::Visitor::TypeCheckBounds(Node* node) {
return typer_->operation_typer_.CheckBounds(Operand(node, 0),
Operand(node, 1));

View File

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

View File

@ -2453,6 +2453,22 @@ void StringLength::GenerateCode(MaglevAssembler* masm,
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() {
UseRegister(value());
DefineAsRegister(this);

View File

@ -1960,6 +1960,13 @@ bool MaglevGraphBuilder::TryBuildPropertyLoad(
current_interpreter_frame_.accumulator(),
access_info);
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(StringAt) \
V(StringLength) \
V(FunctionLength) \
V(ToBoolean) \
V(ToBooleanLogicalNot) \
V(TaggedEqual) \
@ -4639,6 +4640,25 @@ class StringLength : public FixedInputValueNodeT<1, StringLength> {
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
: public 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));
}
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() {
UseRegister(left_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);