Skip to content

Commit e969fd9

Browse files
sstricklcommit-bot@chromium.org
authored andcommitted
[vm/compiler] Optimize prologue IL for invocation dispatchers.
Arguments descriptors for invocation dispatchers are loaded as constants. However, we only optimize type argument length field loads from constant argument descriptors, so the prologue code is less efficient than it could be in these cases. Add the appropriate cases for pulling out the count, positional count, and size as constants from constant argument descriptors in LoadFieldInstr::TryEvaluateLoad, which is enough to let the optimizing compiler tune this code appropriately. Also remove the code for handling dynamic closure call dispatchers specially within the prologue builder and instead explicitly generate any checks that use the runtime closure value in the dynamic closure call fragment builders after the prologue. Bug: #40813 Change-Id: Ie9bd4c01c3b604f282e9b2a72327b7fc81dc4eaf Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/159905 Commit-Queue: Tess Strickland <[email protected]> Reviewed-by: Martin Kustermann <[email protected]> Reviewed-by: Alexander Markov <[email protected]>
1 parent 7b26efb commit e969fd9

13 files changed

+178
-97
lines changed

runtime/vm/compiler/backend/il.cc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2687,6 +2687,7 @@ bool LoadFieldInstr::IsImmutableLengthLoad() const {
26872687
case Slot::Kind::kArray_length:
26882688
case Slot::Kind::kTypedDataBase_length:
26892689
case Slot::Kind::kString_length:
2690+
case Slot::Kind::kTypeArguments_length:
26902691
return true;
26912692
case Slot::Kind::kGrowableObjectArray_length:
26922693
return false;
@@ -2793,6 +2794,42 @@ bool LoadFieldInstr::TryEvaluateLoad(const Object& instance,
27932794
}
27942795
return false;
27952796

2797+
case Slot::Kind::kArgumentsDescriptor_count:
2798+
if (instance.IsArray() && Array::Cast(instance).IsImmutable()) {
2799+
ArgumentsDescriptor desc(Array::Cast(instance));
2800+
*result = Smi::New(desc.Count());
2801+
return true;
2802+
}
2803+
return false;
2804+
2805+
case Slot::Kind::kArgumentsDescriptor_positional_count:
2806+
if (instance.IsArray() && Array::Cast(instance).IsImmutable()) {
2807+
ArgumentsDescriptor desc(Array::Cast(instance));
2808+
*result = Smi::New(desc.PositionalCount());
2809+
return true;
2810+
}
2811+
return false;
2812+
2813+
case Slot::Kind::kArgumentsDescriptor_size:
2814+
// If a constant arguments descriptor appears, then either it is from
2815+
// a invocation dispatcher (which always has tagged arguments and so
2816+
// [host]Size() == [target]Size() == Count()) or the constant should
2817+
// have the correct Size() in terms of the target architecture if any
2818+
// spill slots are involved.
2819+
if (instance.IsArray() && Array::Cast(instance).IsImmutable()) {
2820+
ArgumentsDescriptor desc(Array::Cast(instance));
2821+
*result = Smi::New(desc.Size());
2822+
return true;
2823+
}
2824+
return false;
2825+
2826+
case Slot::Kind::kTypeArguments_length:
2827+
if (instance.IsTypeArguments()) {
2828+
*result = Smi::New(TypeArguments::Cast(instance).Length());
2829+
return true;
2830+
}
2831+
return false;
2832+
27962833
default:
27972834
break;
27982835
}

runtime/vm/compiler/backend/range_analysis.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2689,6 +2689,7 @@ void LoadFieldInstr::InferRange(RangeAnalysis* analysis, Range* range) {
26892689
RangeBoundary::FromConstant(compiler::target::Array::kMaxElements));
26902690
break;
26912691

2692+
case Slot::Kind::kTypeArguments_length:
26922693
case Slot::Kind::kTypedDataBase_length:
26932694
case Slot::Kind::kTypedDataView_offset_in_bytes:
26942695
*range = Range(RangeBoundary::FromConstant(0), RangeBoundary::MaxSmi());

runtime/vm/compiler/backend/slot.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ class ParsedFunction;
9595
V(ArgumentsDescriptor, ArrayLayout, count, Smi, FINAL) \
9696
V(ArgumentsDescriptor, ArrayLayout, size, Smi, FINAL) \
9797
V(PointerBase, PointerBaseLayout, data_field, Dynamic, FINAL) \
98+
V(TypeArguments, TypeArgumentsLayout, length, Smi, FINAL) \
9899
V(UnhandledException, UnhandledExceptionLayout, exception, Dynamic, FINAL) \
99100
V(UnhandledException, UnhandledExceptionLayout, stacktrace, Dynamic, FINAL)
100101

runtime/vm/compiler/frontend/base_flow_graph_builder.cc

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,11 +279,31 @@ void BaseFlowGraphBuilder::InlineBailout(const char* reason) {
279279
}
280280
}
281281

282+
Fragment BaseFlowGraphBuilder::LoadArgDescriptor() {
283+
if (has_saved_args_desc_array()) {
284+
const ArgumentsDescriptor descriptor(saved_args_desc_array());
285+
// Double-check that compile-time Size() matches runtime size on target.
286+
ASSERT_EQUAL(descriptor.Size(),
287+
FlowGraph::ParameterOffsetAt(function_, descriptor.Count(),
288+
/*last_slot=*/false));
289+
return Constant(saved_args_desc_array());
290+
}
291+
ASSERT(parsed_function_->has_arg_desc_var());
292+
return LoadLocal(parsed_function_->arg_desc_var());
293+
}
294+
282295
Fragment BaseFlowGraphBuilder::TestTypeArgsLen(Fragment eq_branch,
283296
Fragment neq_branch,
284297
intptr_t num_type_args) {
285298
Fragment test;
286299

300+
// Compile-time arguments descriptor case.
301+
if (has_saved_args_desc_array()) {
302+
const ArgumentsDescriptor descriptor(saved_args_desc_array_);
303+
return descriptor.TypeArgsLen() == num_type_args ? eq_branch : neq_branch;
304+
}
305+
306+
// Runtime arguments descriptor case.
287307
TargetEntryInstr* eq_entry;
288308
TargetEntryInstr* neq_entry;
289309

runtime/vm/compiler/frontend/base_flow_graph_builder.h

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -316,14 +316,7 @@ class BaseFlowGraphBuilder {
316316

317317
void InlineBailout(const char* reason);
318318

319-
Fragment LoadArgDescriptor() {
320-
if (has_saved_args_desc_array()) {
321-
return Constant(saved_args_desc_array());
322-
}
323-
ASSERT(parsed_function_->has_arg_desc_var());
324-
return LoadLocal(parsed_function_->arg_desc_var());
325-
}
326-
319+
Fragment LoadArgDescriptor();
327320
Fragment TestTypeArgsLen(Fragment eq_branch,
328321
Fragment neq_branch,
329322
intptr_t num_type_args);

runtime/vm/compiler/frontend/kernel_to_il.cc

Lines changed: 82 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1752,14 +1752,13 @@ void FlowGraphBuilder::BuildArgumentTypeChecks(
17521752
}
17531753

17541754
BlockEntryInstr* FlowGraphBuilder::BuildPrologue(BlockEntryInstr* normal_entry,
1755-
PrologueInfo* prologue_info,
1756-
JoinEntryInstr* nsm) {
1755+
PrologueInfo* prologue_info) {
17571756
const bool compiling_for_osr = IsCompiledForOsr();
17581757

17591758
kernel::PrologueBuilder prologue_builder(
17601759
parsed_function_, last_used_block_id_, compiling_for_osr, IsInlining());
17611760
BlockEntryInstr* instruction_cursor =
1762-
prologue_builder.BuildPrologue(normal_entry, prologue_info, nsm);
1761+
prologue_builder.BuildPrologue(normal_entry, prologue_info);
17631762

17641763
last_used_block_id_ = prologue_builder.last_used_block_id();
17651764

@@ -1835,6 +1834,7 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfNoSuchMethodDispatcher(
18351834
const Function& function) {
18361835
// This function is specialized for a receiver class, a method name, and
18371836
// the arguments descriptor at a call site.
1837+
const ArgumentsDescriptor descriptor(saved_args_desc_array());
18381838

18391839
graph_entry_ =
18401840
new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId);
@@ -1846,18 +1846,6 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfNoSuchMethodDispatcher(
18461846
BlockEntryInstr* instruction_cursor =
18471847
BuildPrologue(normal_entry, &prologue_info);
18481848

1849-
// The backend will expect an array of default values for all the named
1850-
// parameters, even if they are all known to be passed at the call site
1851-
// because the call site matches the arguments descriptor. Use null for
1852-
// the default values.
1853-
ArgumentsDescriptor descriptor(saved_args_desc_array());
1854-
ZoneGrowableArray<const Instance*>* default_values =
1855-
new ZoneGrowableArray<const Instance*>(Z, descriptor.NamedCount());
1856-
for (intptr_t i = 0; i < descriptor.NamedCount(); ++i) {
1857-
default_values->Add(&Object::null_instance());
1858-
}
1859-
parsed_function_->set_default_parameter_values(default_values);
1860-
18611849
Fragment body(instruction_cursor);
18621850
body += CheckStackOverflowInPrologue(function.token_pos());
18631851

@@ -2153,10 +2141,38 @@ Fragment FlowGraphBuilder::BuildClosureCallArgumentsValidCheck(
21532141
ASSERT(has_saved_args_desc_array());
21542142
const ArgumentsDescriptor descriptor(saved_args_desc_array());
21552143

2156-
// Type argument length checking, including checking for delayed type
2157-
// arguments, is already done in the prologue builder.
2144+
LocalVariable* temp = parsed_function_->expression_temp_var();
21582145

21592146
Fragment check_entry;
2147+
// We only need to check the length of any explicitly provided type arguments.
2148+
if (descriptor.TypeArgsLen() > 0) {
2149+
Fragment check_type_args_length;
2150+
check_type_args_length += LoadLocal(closure);
2151+
check_type_args_length += LoadNativeField(Slot::Closure_function());
2152+
check_type_args_length += LoadNativeField(Slot::Function_type_parameters());
2153+
check_type_args_length += StoreLocal(TokenPosition::kNoSource, temp);
2154+
TargetEntryInstr *null, *not_null;
2155+
check_type_args_length += BranchIfNull(&null, &not_null);
2156+
check_type_args_length.current = not_null; // Continue in non-error case.
2157+
2158+
// The function is not generic.
2159+
Fragment(null) + Goto(nsm);
2160+
2161+
check_type_args_length += LoadLocal(temp);
2162+
check_type_args_length += LoadNativeField(Slot::TypeArguments_length());
2163+
check_type_args_length += IntConstant(descriptor.TypeArgsLen());
2164+
TargetEntryInstr *equal, *not_equal;
2165+
check_type_args_length += BranchIfEqual(&equal, &not_equal);
2166+
check_type_args_length.current = equal; // Continue in non-error case.
2167+
2168+
Fragment(not_equal) + Goto(nsm);
2169+
2170+
// Type arguments should not be provided if there are delayed type
2171+
// arguments, as then the closure itself is not generic.
2172+
check_entry += TestDelayedTypeArgs(closure, /*present=*/Goto(nsm),
2173+
/*absent=*/check_type_args_length);
2174+
}
2175+
21602176
check_entry += LoadLocal(vars->has_named_params);
21612177
TargetEntryInstr *has_named, *has_positional;
21622178
check_entry += BranchIfTrue(&has_named, &has_positional);
@@ -2280,6 +2296,7 @@ Fragment FlowGraphBuilder::BuildClosureCallNamedArgumentCheck(
22802296

22812297
FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher(
22822298
const Function& function) {
2299+
const ArgumentsDescriptor descriptor(saved_args_desc_array());
22832300
// Find the name of the field we should dispatch to.
22842301
const Class& owner = Class::Handle(Z, function.Owner());
22852302
ASSERT(!owner.IsNull());
@@ -2302,36 +2319,6 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher(
23022319
const bool is_closure_call = (owner.raw() == closure_class.raw()) &&
23032320
field_name.Equals(Symbols::Call());
23042321

2305-
JoinEntryInstr* nsm = nullptr;
2306-
if (is_dynamic_call && is_closure_call) {
2307-
// Create a NSM block that can be shared with the prologue builder.
2308-
nsm = BuildThrowNoSuchMethod();
2309-
// The whole reason for making this invoke field dispatcher is that
2310-
// this closure call needs checking, so we shouldn't inline a call to an
2311-
// unchecked entry that can't tail call NSM.
2312-
InlineBailout(
2313-
"kernel::FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher");
2314-
}
2315-
2316-
// Set default parameters & construct argument names array.
2317-
//
2318-
// The backend will expect an array of default values for all the named
2319-
// parameters, even if they are all known to be passed at the call site
2320-
// because the call site matches the arguments descriptor. Use null for
2321-
// the default values.
2322-
const ArgumentsDescriptor descriptor(saved_args_desc_array());
2323-
const Array& argument_names =
2324-
Array::ZoneHandle(Z, Array::New(descriptor.NamedCount(), Heap::kOld));
2325-
ZoneGrowableArray<const Instance*>* default_values =
2326-
new ZoneGrowableArray<const Instance*>(Z, descriptor.NamedCount());
2327-
String& string_handle = String::Handle(Z);
2328-
for (intptr_t i = 0; i < descriptor.NamedCount(); ++i) {
2329-
default_values->Add(&Object::null_instance());
2330-
string_handle = descriptor.NameAt(i);
2331-
argument_names.SetAt(i, string_handle);
2332-
}
2333-
parsed_function_->set_default_parameter_values(default_values);
2334-
23352322
graph_entry_ =
23362323
new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId);
23372324

@@ -2340,29 +2327,27 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher(
23402327

23412328
PrologueInfo prologue_info(-1, -1);
23422329
BlockEntryInstr* instruction_cursor =
2343-
BuildPrologue(normal_entry, &prologue_info, nsm);
2330+
BuildPrologue(normal_entry, &prologue_info);
23442331

23452332
Fragment body(instruction_cursor);
23462333
body += CheckStackOverflowInPrologue(function.token_pos());
23472334

2348-
if (descriptor.TypeArgsLen() > 0) {
2349-
LocalVariable* type_args = parsed_function_->function_type_arguments();
2350-
ASSERT(type_args != NULL);
2351-
body += LoadLocal(type_args);
2352-
}
2353-
2335+
// Build any dynamic closure call checks before pushing arguments to the
2336+
// final call on the stack to make debugging easier.
23542337
LocalVariable* closure = NULL;
23552338
if (is_closure_call) {
23562339
closure = parsed_function_->ParameterVariable(0);
2357-
2358-
// The closure itself is the first argument.
2359-
body += LoadLocal(closure);
2360-
23612340
if (is_dynamic_call) {
2362-
// We should have a throw NSM block from the prologue.
2363-
ASSERT(nsm != nullptr);
2341+
// The whole reason for making this invoke field dispatcher is that
2342+
// this closure call needs checking, so we shouldn't inline a call to an
2343+
// unchecked entry that can't tail call NSM.
2344+
InlineBailout(
2345+
"kernel::FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher");
2346+
23642347
// Init the variables we'll be using for dynamic call checking.
23652348
body += BuildDynamicCallVarsInit(closure);
2349+
2350+
JoinEntryInstr* nsm = BuildThrowNoSuchMethod();
23662351
// Check that the shape of the arguments generally matches what the
23672352
// closure function expects. The only remaining non-type check after this
23682353
// is that the names for optional arguments are valid.
@@ -2372,7 +2357,29 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher(
23722357
// in the closure body to here, using the dynamic versions of
23732358
// AssertSubtype to typecheck the type arguments using the runtime types
23742359
// available in the closure object.
2360+
2361+
// TODO(dartbug.com/40813): Move checks that are currently compiled
2362+
// in the closure body to here, using the dynamic versions of
2363+
// AssertAssignable to typecheck the parameters using the runtime types
2364+
// available in the closure object.
2365+
//
2366+
// For now, we check that any named arguments have valid names.
2367+
for (intptr_t pos = descriptor.PositionalCount();
2368+
pos < descriptor.Count(); pos++) {
2369+
body += BuildClosureCallNamedArgumentCheck(closure, pos, nsm);
2370+
}
23752371
}
2372+
}
2373+
2374+
if (descriptor.TypeArgsLen() > 0) {
2375+
LocalVariable* type_args = parsed_function_->function_type_arguments();
2376+
ASSERT(type_args != nullptr);
2377+
body += LoadLocal(type_args);
2378+
}
2379+
2380+
if (is_closure_call) {
2381+
// The closure itself is the first argument.
2382+
body += LoadLocal(closure);
23762383
} else {
23772384
// Invoke the getter to get the field value.
23782385
body += LoadLocal(parsed_function_->ParameterVariable(0));
@@ -2383,18 +2390,21 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher(
23832390
}
23842391

23852392
// Push all arguments onto the stack.
2386-
intptr_t pos = 1;
2387-
for (; pos < descriptor.Count(); pos++) {
2393+
for (intptr_t pos = 1; pos < descriptor.Count(); pos++) {
23882394
body += LoadLocal(parsed_function_->ParameterVariable(pos));
2389-
if (is_closure_call && is_dynamic_call) {
2390-
// TODO(dartbug.com/40813): Move checks that are currently compiled
2391-
// in the closure body to here, using the dynamic versions of
2392-
// AssertAssignable to typecheck the parameters using the runtime types
2393-
// available in the closure object.
2394-
//
2395-
// For now, we check that any named arguments have valid names.
2396-
body += BuildClosureCallNamedArgumentCheck(closure, pos, nsm);
2395+
}
2396+
2397+
// Construct argument names array if necessary.
2398+
const Array* argument_names = &Object::null_array();
2399+
if (descriptor.NamedCount() > 0) {
2400+
const auto& array_handle =
2401+
Array::ZoneHandle(Z, Array::New(descriptor.NamedCount(), Heap::kNew));
2402+
String& string_handle = String::Handle(Z);
2403+
for (intptr_t i = 0; i < descriptor.NamedCount(); ++i) {
2404+
string_handle = descriptor.NameAt(i);
2405+
array_handle.SetAt(i, string_handle);
23972406
}
2407+
argument_names = &array_handle;
23982408
}
23992409

24002410
if (is_closure_call) {
@@ -2403,14 +2413,14 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfInvokeFieldDispatcher(
24032413
body += LoadNativeField(Slot::Closure_function());
24042414

24052415
body += ClosureCall(TokenPosition::kNoSource, descriptor.TypeArgsLen(),
2406-
descriptor.Count(), argument_names);
2416+
descriptor.Count(), *argument_names);
24072417
} else {
24082418
const intptr_t kNumArgsChecked = 1;
24092419
body +=
24102420
InstanceCall(TokenPosition::kMinSource,
24112421
is_dynamic_call ? Symbols::DynamicCall() : Symbols::Call(),
24122422
Token::kILLEGAL, descriptor.TypeArgsLen(),
2413-
descriptor.Count(), argument_names, kNumArgsChecked);
2423+
descriptor.Count(), *argument_names, kNumArgsChecked);
24142424
}
24152425

24162426
body += Return(TokenPosition::kNoSource);

runtime/vm/compiler/frontend/kernel_to_il.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder {
6767

6868
private:
6969
BlockEntryInstr* BuildPrologue(BlockEntryInstr* normal_entry,
70-
PrologueInfo* prologue_info,
71-
JoinEntryInstr* nsm = nullptr);
70+
PrologueInfo* prologue_info);
7271

7372
// Return names of optional named parameters of [function].
7473
ArrayPtr GetOptionalParameterNames(const Function& function);

0 commit comments

Comments
 (0)