5
5
import '../constants/values.dart' ;
6
6
import '../elements/entities.dart' ;
7
7
import '../inferrer/abstract_value_domain.dart' ;
8
+ import '../js_backend/interceptor_data.dart' ;
8
9
import '../options.dart' ;
9
10
import '../universe/selector.dart' show Selector;
10
11
import '../world.dart' show JClosedWorld;
@@ -26,14 +27,21 @@ bool canUseAliasedSuperMember(MemberEntity member, Selector selector) {
26
27
///
27
28
/// - Remove NullChecks where the next instruction would fail on the operand.
28
29
///
30
+ /// - Dummy receiver optimization.
31
+ ///
32
+ /// - One-shot interceptor optimization.
33
+ ///
29
34
/// - Combine read/modify/write sequences into HReadModifyWrite instructions to
30
35
/// simplify codegen of expressions like `a.x += y` .
36
+
31
37
class SsaInstructionSelection extends HBaseVisitor with CodegenPhase {
32
38
final JClosedWorld _closedWorld;
39
+ final InterceptorData _interceptorData;
33
40
final CompilerOptions _options;
34
41
HGraph graph;
35
42
36
- SsaInstructionSelection (this ._options, this ._closedWorld);
43
+ SsaInstructionSelection (
44
+ this ._options, this ._closedWorld, this ._interceptorData);
37
45
38
46
AbstractValueDomain get _abstractValueDomain =>
39
47
_closedWorld.abstractValueDomain;
@@ -220,6 +228,128 @@ class SsaInstructionSelection extends HBaseVisitor with CodegenPhase {
220
228
.isInterceptor (_abstractValueDomain.excludeNull (type))
221
229
.isPotentiallyTrue;
222
230
231
+ @override
232
+ HInstruction visitInvokeDynamic (HInvokeDynamic node) {
233
+ if (! node.isInterceptedCall) return node;
234
+
235
+ tryReplaceExplicitReceiverWithDummy (
236
+ node, node.selector, node.element, node.receiverType);
237
+
238
+ // Try to replace
239
+ //
240
+ // getInterceptor(o).method(o, ...)
241
+ //
242
+ // with a 'one shot interceptor' which is a call to a synthesized static
243
+ // helper function that combines the two operations.
244
+ //
245
+ // oneShotMethod(o, 1, 2)
246
+ //
247
+ // This saves code size and makes the receiver of an intercepted call a
248
+ // candidate for being generated at use site.
249
+ //
250
+ // Avoid combining a hoisted interceptor back into a loop, and the faster
251
+ // almost-constant kind of interceptor.
252
+
253
+ HInstruction interceptor = node.inputs[0 ];
254
+ if (interceptor is HInterceptor &&
255
+ interceptor.usedBy.length == 1 &&
256
+ ! interceptor.isConditionalConstantInterceptor &&
257
+ interceptor.hasSameLoopHeaderAs (node)) {
258
+ // Copy inputs and replace interceptor with `null`.
259
+ List <HInstruction > inputs = List .of (node.inputs);
260
+ inputs[0 ] = graph.addConstantNull (_closedWorld);
261
+
262
+ HOneShotInterceptor oneShot = HOneShotInterceptor (
263
+ node.selector,
264
+ node.receiverType,
265
+ inputs,
266
+ node.instructionType,
267
+ node.typeArguments,
268
+ interceptor.interceptedClasses);
269
+ oneShot.sourceInformation = node.sourceInformation;
270
+ oneShot.sourceElement = node.sourceElement;
271
+ oneShot.sideEffects.setTo (node.sideEffects);
272
+
273
+ HBasicBlock block = node.block;
274
+ block.addAfter (node, oneShot);
275
+ block.rewrite (node, oneShot);
276
+ block.remove (node);
277
+ interceptor.block.remove (interceptor);
278
+ return null ;
279
+ }
280
+
281
+ return node;
282
+ }
283
+
284
+ @override
285
+ HInstruction visitInvokeSuper (HInvokeSuper node) {
286
+ tryReplaceExplicitReceiverWithDummy (
287
+ node, node.selector, node.element, null );
288
+ return node;
289
+ }
290
+
291
+ @override
292
+ HInstruction visitOneShotInterceptor (HOneShotInterceptor node) {
293
+ throw StateError ('Should not see HOneShotInterceptor: $node ' );
294
+ }
295
+
296
+ void tryReplaceExplicitReceiverWithDummy (HInvoke node, Selector selector,
297
+ MemberEntity target, AbstractValue mask) {
298
+ // Calls of the form
299
+ //
300
+ // a.foo$1(a, x)
301
+ //
302
+ // where the interceptor calling convention is used come from recognizing
303
+ // that 'a' is a 'self-interceptor'. If the selector matches only methods
304
+ // that ignore the explicit receiver parameter, replace occurrences of the
305
+ // receiver argument with a dummy receiver '0':
306
+ //
307
+ // a.foo$1(a, x) ---> a.foo$1(0, x)
308
+ //
309
+ // This often reduces the number of references to 'a' to one, allowing 'a'
310
+ // to be generated at use to avoid a temporary, e.g.
311
+ //
312
+ // t1 = b.get$thing();
313
+ // t1.foo$1(t1, x)
314
+ // --->
315
+ // b.get$thing().foo$1(0, x)
316
+ //
317
+ assert (target != null || mask != null );
318
+
319
+ if (! node.isInterceptedCall) return ;
320
+
321
+ // TODO(15933): Make automatically generated property extraction closures
322
+ // work with the dummy receiver optimization.
323
+ if (selector.isGetter) return ;
324
+
325
+ // This assignment of inputs is uniform for HInvokeDynamic and HInvokeSuper.
326
+ HInstruction interceptor = node.inputs[0 ];
327
+ HInstruction receiverArgument = node.inputs[1 ];
328
+
329
+ // A 'self-interceptor'?
330
+ if (interceptor.nonCheck () != receiverArgument.nonCheck ()) return ;
331
+
332
+ // TODO(sra): Should this be an assert?
333
+ if (! _interceptorData.isInterceptedSelector (selector)) return ;
334
+
335
+ if (target != null ) {
336
+ // A call that resolves to a single instance method (element) requires the
337
+ // calling convention consistent with the method.
338
+ ClassEntity cls = target.enclosingClass;
339
+ assert (_interceptorData.isInterceptedMethod (target));
340
+ if (_interceptorData.isInterceptedClass (cls)) return ;
341
+ } else if (_interceptorData.isInterceptedMixinSelector (
342
+ selector, mask, _closedWorld)) {
343
+ return ;
344
+ }
345
+
346
+ ConstantValue constant = DummyInterceptorConstantValue ();
347
+ HConstant dummy = graph.addConstant (constant, _closedWorld);
348
+ receiverArgument.usedBy.remove (node);
349
+ node.inputs[1 ] = dummy;
350
+ dummy.usedBy.add (node);
351
+ }
352
+
223
353
@override
224
354
HInstruction visitFieldSet (HFieldSet setter) {
225
355
// Pattern match
0 commit comments