Skip to content

Commit 7874462

Browse files
sstricklcommit-bot@chromium.org
authored andcommitted
[vm/compiler] Move argument shape (not type) checks out of closures.
This CL performs the following checks in the invoke field dispatcher for dynamic closure calls when lazy dispatchers are enabled: * The provided function type arguments vector (if any) has the correct length. * No function type arguments should be provided if the closure has delayed type arguments. * All required arguments (positional in all modes, named in appropriate null safety modes) have been provided by the caller. * If there are optional positional arguments, an appropriate number has been provided. * If there are optional named arguments, their names are valid. Since the runtime already handles checking the argument shapes when lazy dispatchers are disabled, these checks are now completely removed from closure bodies in all cases. Thus, the only remaining checks in closure bodies are the type checks performed by AssertSubtype and AssertAssignable when lazy dispatchers are enabled. Changes in the Flutter Gallery: * ARM7, release: -3.61% instructions, -2.19% total * ARM7, sizeopt: -3.62% instructions, -2.55% total * ARM8, release: -3.66% instructions, -1.98% total * ARM8, sizeopt: -3.65% instructions, -2.37% total Most of these changes are already exercised by existing tests such as (but not limited to): * corelib{,_2}/dynamic_nosuchmethod_test * language{,_2}/call/call_test * language{,_2}/closure/tearoff_dynamic_test * language{,_2}/generic/function_bounds_test * language{,_2}/parameter/named_with_conversions_test * language{,_2}/vm/no_such_args_error_message_vm_test I've added one test to specifically check the interaction between dynamic calls and required named parameters. There is some coverage in other NNBD tests, but those are not directly focused on testing this specifically. Other changes: * Adds initial cached ranges for certain BinarySmiOp and ShiftIntegerOp instructions when the RHS is a constant, to avoid false negatives for deoptimization and throw checks prior to range analysis. * Adds new slots for various Function fields. * Adds the ability to define unboxed native slots, which are always unboxed after retrieval even in unoptimized code. In the first iteration, the backend only handles loads from Uint32 unboxed native slots. Part of #42793. * Removed the special handling for loading from non-nullable int fields in AOT compilation. Instead, their treatment is unified with the treatment of the new unboxed native fields, since the source field is always unboxed and the result of the load is also always unboxed, as code involving them is always optimized. Bug: #40813 Change-Id: Ia02aa3e872c1fefd906fd67b55021ea1797556e4 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/155604 Commit-Queue: Tess Strickland <[email protected]> Reviewed-by: Alexander Markov <[email protected]>
1 parent 07a4409 commit 7874462

40 files changed

+1987
-637
lines changed

pkg/vm/lib/bytecode/dbc.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ enum Opcode {
106106
// Prologue and stack management.
107107
kEntry,
108108
kEntry_Wide,
109+
// TODO(alexmarkov): cleanup now unused EntryFixed instruction.
109110
kEntryFixed,
110111
kEntryFixed_Wide,
111112
kEntryOptional,

pkg/vm/lib/bytecode/gen_bytecode.dart

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1988,8 +1988,6 @@ class BytecodeGenerator extends RecursiveVisitor<Null> {
19881988
}
19891989

19901990
asm.emitFrame(locals.frameSize - locals.numParameters);
1991-
} else if (isClosure) {
1992-
asm.emitEntryFixed(locals.numParameters, locals.frameSize);
19931991
} else {
19941992
asm.emitEntry(locals.frameSize);
19951993
}
@@ -2015,14 +2013,6 @@ class BytecodeGenerator extends RecursiveVisitor<Null> {
20152013
_handleDefaultTypeArguments(function, done);
20162014

20172015
asm.bind(done);
2018-
} else if (isClosure &&
2019-
!(parentFunction != null &&
2020-
parentFunction.dartAsyncMarker != AsyncMarker.Sync)) {
2021-
// Closures can be called dynamically with arbitrary arguments,
2022-
// so they should check number of type arguments, even if
2023-
// closure is not generic.
2024-
// Synthetic async_op closures don't need this check.
2025-
asm.emitCheckFunctionTypeArgs(0, locals.scratchVarIndexInFrame);
20262016
}
20272017

20282018
// Open initial scope before the first CheckStack, as VM might
@@ -2218,8 +2208,9 @@ class BytecodeGenerator extends RecursiveVisitor<Null> {
22182208
initializedPosition);
22192209
}
22202210

2221-
bool get canSkipTypeChecksForNonCovariantArguments =>
2222-
!isClosure && enclosingMember.name.name != 'call';
2211+
// TODO(dartbug.com/40813): Remove the closure case when we move the
2212+
// type checks out of closure bodies.
2213+
bool get canSkipTypeChecksForNonCovariantArguments => !isClosure;
22232214

22242215
bool get skipTypeChecksForGenericCovariantImplArguments =>
22252216
procedureAttributesMetadata != null &&

pkg/vm/testcases/bytecode/async.dart.expect

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,10 @@ ConstantPool {
6969
}
7070
Closure #lib::asyncInFieldInitializer (field)::'<anonymous closure>' async (dart:async::Future < dart:core::int* >* x) -> dart:async::Future < dart:core::Null? >*
7171
ClosureCode {
72-
EntryFixed 2, 4
72+
Entry 4
7373
Push FP[-6]
7474
LoadFieldTOS CP#1
7575
PopLocal r0
76-
CheckFunctionTypeArgs 0, r1
7776
CheckStack 0
7877
AllocateContext 0, 9
7978
PopLocal r0
@@ -1551,11 +1550,10 @@ ConstantPool {
15511550
}
15521551
Closure #lib::closure::'nested' async () -> dart:async::Future < dart:core::int* >*
15531552
ClosureCode {
1554-
EntryFixed 1, 4
1553+
Entry 4
15551554
Push FP[-5]
15561555
LoadFieldTOS CP#1
15571556
PopLocal r0
1558-
CheckFunctionTypeArgs 0, r1
15591557
CheckStack 0
15601558
AllocateContext 1, 9
15611559
StoreLocal r1

pkg/vm/testcases/bytecode/closures.dart.expect

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,10 @@ ConstantPool {
6565
}
6666
Closure #lib::simpleClosure::'<anonymous closure>' (dart:core::int* y) -> dart:core::Null?
6767
ClosureCode {
68-
EntryFixed 2, 3
68+
Entry 3
6969
Push FP[-6]
7070
LoadFieldTOS CP#1
7171
PopLocal r0
72-
CheckFunctionTypeArgs 0, r1
7372
CheckStack 0
7473
JumpIfUnchecked L1
7574
Push FP[-5]
@@ -312,7 +311,7 @@ ConstantPool {
312311
}
313312
Closure #lib::testPartialInstantiation::'foo' <dart:core::Object* T> (#lib::testPartialInstantiation::Closure/0::TypeParam/0* t) -> void
314313
ClosureCode {
315-
EntryFixed 2, 3
314+
Entry 3
316315
Push FP[-6]
317316
LoadFieldTOS CP#1
318317
PopLocal r1
@@ -975,7 +974,7 @@ ConstantPool {
975974
}
976975
Closure #lib::A::foo::'nested1' <dart:core::Object* T5, dart:core::Object* T6> () -> void
977976
ClosureCode {
978-
EntryFixed 1, 5
977+
Entry 5
979978
Push FP[-5]
980979
LoadFieldTOS CP#1
981980
PopLocal r1
@@ -1035,7 +1034,7 @@ L2:
10351034

10361035
Closure #lib::A::foo::Closure/0::'nested2' <dart:core::Object* T7, dart:core::Object* T8> () -> void
10371036
ClosureCode {
1038-
EntryFixed 1, 5
1037+
Entry 5
10391038
Push FP[-5]
10401039
LoadFieldTOS CP#1
10411040
PopLocal r1
@@ -1085,11 +1084,10 @@ L2:
10851084

10861085
Closure #lib::A::foo::Closure/1::'<anonymous closure>' () -> dart:core::Null?
10871086
ClosureCode {
1088-
EntryFixed 1, 4
1087+
Entry 4
10891088
Push FP[-5]
10901089
LoadFieldTOS CP#1
10911090
PopLocal r1
1092-
CheckFunctionTypeArgs 0, r2
10931091
CheckStack 0
10941092
Push FP[-5]
10951093
LoadFieldTOS CP#6
@@ -1345,11 +1343,10 @@ ConstantPool {
13451343
}
13461344
Closure #lib::B::topLevel::'<anonymous closure>' (dart:core::int* y) -> dart:core::Null?
13471345
ClosureCode {
1348-
EntryFixed 2, 4
1346+
Entry 4
13491347
Push FP[-6]
13501348
LoadFieldTOS CP#1
13511349
PopLocal r0
1352-
CheckFunctionTypeArgs 0, r1
13531350
CheckStack 0
13541351
AllocateContext 1, 2
13551352
StoreLocal r1
@@ -1416,11 +1413,10 @@ L2:
14161413

14171414
Closure #lib::B::topLevel::Closure/0::'closure2' () -> void
14181415
ClosureCode {
1419-
EntryFixed 1, 3
1416+
Entry 3
14201417
Push FP[-5]
14211418
LoadFieldTOS CP#1
14221419
PopLocal r0
1423-
CheckFunctionTypeArgs 0, r1
14241420
CheckStack 0
14251421
Push r0
14261422
LoadContextParent
@@ -1445,11 +1441,10 @@ ClosureCode {
14451441

14461442
Closure #lib::B::topLevel::'<anonymous closure>' () -> dart:core::Null?
14471443
ClosureCode {
1448-
EntryFixed 1, 3
1444+
Entry 3
14491445
Push FP[-5]
14501446
LoadFieldTOS CP#1
14511447
PopLocal r0
1452-
CheckFunctionTypeArgs 0, r1
14531448
CheckStack 0
14541449
Push r0
14551450
LoadContextVar 0, 0
@@ -1643,11 +1638,10 @@ ConstantPool {
16431638
}
16441639
Closure #lib::C::testForLoop::'<anonymous closure>' () -> dart:core::int*
16451640
ClosureCode {
1646-
EntryFixed 1, 2
1641+
Entry 2
16471642
Push FP[-5]
16481643
LoadFieldTOS CP#5
16491644
PopLocal r0
1650-
CheckFunctionTypeArgs 0, r1
16511645
CheckStack 0
16521646
Push r0
16531647
LoadContextVar 1, 0
@@ -1660,11 +1654,10 @@ ClosureCode {
16601654

16611655
Closure #lib::C::testForLoop::'<anonymous closure>' (dart:core::int* ii) -> dart:core::Null?
16621656
ClosureCode {
1663-
EntryFixed 2, 3
1657+
Entry 3
16641658
Push FP[-6]
16651659
LoadFieldTOS CP#5
16661660
PopLocal r0
1667-
CheckFunctionTypeArgs 0, r1
16681661
CheckStack 0
16691662
JumpIfUnchecked L1
16701663
Push FP[-5]
@@ -1764,11 +1757,10 @@ ConstantPool {
17641757
}
17651758
Closure #lib::C::testForInLoop::'<anonymous closure>' () -> dart:core::Null?
17661759
ClosureCode {
1767-
EntryFixed 1, 3
1760+
Entry 3
17681761
Push FP[-5]
17691762
LoadFieldTOS CP#7
17701763
PopLocal r0
1771-
CheckFunctionTypeArgs 0, r1
17721764
CheckStack 0
17731765
Push r0
17741766
Push r0
@@ -1902,11 +1894,10 @@ ConstantPool {
19021894
}
19031895
Closure #lib::D::foo::'<anonymous closure>' () -> #lib::D::TypeParam/0*
19041896
ClosureCode {
1905-
EntryFixed 1, 2
1897+
Entry 2
19061898
Push FP[-5]
19071899
LoadFieldTOS CP#5
19081900
PopLocal r0
1909-
CheckFunctionTypeArgs 0, r1
19101901
CheckStack 0
19111902
Push r0
19121903
LoadContextVar 0, 0
@@ -1961,11 +1952,10 @@ ConstantPool {
19611952
}
19621953
Closure #lib::D::bar::'<anonymous closure>' () -> dart:core::Null?
19631954
ClosureCode {
1964-
EntryFixed 1, 4
1955+
Entry 4
19651956
Push FP[-5]
19661957
LoadFieldTOS CP#1
19671958
PopLocal r0
1968-
CheckFunctionTypeArgs 0, r1
19691959
CheckStack 0
19701960
AllocateClosure CP#3
19711961
StoreLocal r3
@@ -1995,11 +1985,10 @@ ClosureCode {
19951985

19961986
Closure #lib::D::bar::Closure/0::'inner' () -> dart:core::Null?
19971987
ClosureCode {
1998-
EntryFixed 1, 2
1988+
Entry 2
19991989
Push FP[-5]
20001990
LoadFieldTOS CP#1
20011991
PopLocal r0
2002-
CheckFunctionTypeArgs 0, r1
20031992
CheckStack 0
20041993
PushNull
20051994
ReturnTOS

pkg/vm/testcases/bytecode/try_blocks.dart.expect

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -337,11 +337,10 @@ ConstantPool {
337337
}
338338
Closure #lib::testTryCatch3::'foo' () -> void
339339
ClosureCode {
340-
EntryFixed 1, 6
340+
Entry 6
341341
Push FP[-5]
342342
LoadFieldTOS CP#1
343343
PopLocal r0
344-
CheckFunctionTypeArgs 0, r1
345344
CheckStack 0
346345
Push r0
347346
PopLocal r2
@@ -374,11 +373,10 @@ L1:
374373

375374
Closure #lib::testTryCatch3::'bar' () -> void
376375
ClosureCode {
377-
EntryFixed 1, 6
376+
Entry 6
378377
Push FP[-5]
379378
LoadFieldTOS CP#1
380379
PopLocal r0
381-
CheckFunctionTypeArgs 0, r1
382380
CheckStack 0
383381
Push r0
384382
PopLocal r2
@@ -721,11 +719,10 @@ ConstantPool {
721719
}
722720
Closure #lib::testTryFinally2::'foo' () -> void
723721
ClosureCode {
724-
EntryFixed 1, 2
722+
Entry 2
725723
Push FP[-5]
726724
LoadFieldTOS CP#7
727725
PopLocal r0
728-
CheckFunctionTypeArgs 0, r1
729726
CheckStack 0
730727
Push r0
731728
LoadContextVar 0, 0
@@ -830,11 +827,10 @@ ConstantPool {
830827
}
831828
Closure #lib::testTryFinally3::'<anonymous closure>' () -> dart:core::int*
832829
ClosureCode {
833-
EntryFixed 1, 6
830+
Entry 6
834831
Push FP[-5]
835832
LoadFieldTOS CP#1
836833
PopLocal r0
837-
CheckFunctionTypeArgs 0, r1
838834
CheckStack 0
839835
Push r0
840836
LoadContextVar 0, 0

runtime/tests/vm/dart/causal_stacks/utils.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,10 @@ Future<void> doTestAwaitThen(Future f(), List<String> expectedStack,
253253
[String? debugInfoFilename]) async {
254254
// Caller catches but a then is set.
255255
try {
256-
await f().then((e) {
257-
// Ignore.
258-
});
256+
// Passing (e) {} to then() can cause the closure instructions to be
257+
// dedupped, changing the stack trace to the dedupped owner, so we
258+
// duplicate the Expect.fail() call in the closure.
259+
await f().then((e) => Expect.fail('No exception thrown!'));
259260
Expect.fail('No exception thrown!');
260261
} on String catch (e, s) {
261262
assertStack(expectedStack, s, debugInfoFilename);

runtime/tests/vm/dart_2/causal_stacks/utils.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,10 @@ Future<void> doTestAwaitThen(Future f(), List<String> expectedStack,
253253
[String debugInfoFilename]) async {
254254
// Caller catches but a then is set.
255255
try {
256-
await f().then((e) {
257-
// Ignore.
258-
});
256+
// Passing (e) {} to then() can cause the closure instructions to be
257+
// dedupped, changing the stack trace to the dedupped owner, so we
258+
// duplicate the Expect.fail() call in the closure.
259+
await f().then((e) => Expect.fail('No exception thrown!'));
259260
Expect.fail('No exception thrown!');
260261
} on String catch (e, s) {
261262
assertStack(expectedStack, s, debugInfoFilename);

runtime/vm/compiler/backend/flow_graph_compiler.cc

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2470,16 +2470,24 @@ void FlowGraphCompiler::FrameStatePush(Definition* defn) {
24702470
Representation rep = defn->representation();
24712471
if ((rep == kUnboxedDouble) || (rep == kUnboxedFloat64x2) ||
24722472
(rep == kUnboxedFloat32x4)) {
2473-
// LoadField instruction lies about its representation in the unoptimized
2474-
// code because Definition::representation() can't depend on the type of
2475-
// compilation but MakeLocationSummary and EmitNativeCode can.
2476-
ASSERT(defn->IsLoadField() && defn->AsLoadField()->IsUnboxedLoad());
2473+
// The LoadField instruction may lie about its representation in unoptimized
2474+
// code for Dart fields because Definition::representation() can't depend on
2475+
// the type of compilation but MakeLocationSummary and EmitNativeCode can.
2476+
ASSERT(defn->IsLoadField() &&
2477+
defn->AsLoadField()->IsUnboxedDartFieldLoad());
24772478
ASSERT(defn->locs()->out(0).IsRegister());
24782479
rep = kTagged;
24792480
}
24802481
ASSERT(!is_optimizing());
2481-
ASSERT((rep == kTagged) || (rep == kUntagged));
2482+
ASSERT((rep == kTagged) || (rep == kUntagged) || (rep == kUnboxedUint32));
24822483
ASSERT(rep != kUntagged || flow_graph_.IsIrregexpFunction());
2484+
const auto& function = flow_graph_.parsed_function().function();
2485+
// Currently, we only allow unboxed uint32 on the stack in unoptimized code
2486+
// when building a dynamic closure call dispatcher, where any unboxed values
2487+
// on the stack are consumed before possible FrameStateIsSafeToCall() checks.
2488+
// See FlowGraphBuilder::BuildDynamicCallVarsInit().
2489+
ASSERT(rep != kUnboxedUint32 ||
2490+
function.IsDynamicClosureCallDispatcher(thread()));
24832491
frame_state_.Add(rep);
24842492
}
24852493

0 commit comments

Comments
 (0)