Skip to content

Scopes: What is the correct position of generated ranges for call sites. #260

Description

@rakudrama

What are rules for placing ranges for generated call sites and generated code for inlined methods so that they are correct for all major JavaScript engines?

Can two different stacks be encoded at one generated position?

Expanding on #40 (comment):

     1	class Foo {
     2	  int value = 10;
     3	  
     4	  Foo check(Foo? x) {
     5	    return x!;
     6	  }
     7	
     8	  @pragma('dart2js:never-inline')
     9	  void run(Foo? x, Foo? y) {
    10	    work(check(x), y);
    11	  }
    12	
    13	  void work(Foo x, Foo? y) {
    14	    x.speak(y);
    15	  }
    16	
    17	  @pragma('dart2js:never-inline')
    18	  void speak(Foo? z) {
    19	    print('Foo(${z!.value})');
    20	  }
    21	
    22	  void demo1() {
    23	    run(null, this);
    24	  }
    25	
    26	  void demo2() {
    27	    final a = Foo();
    28	    a.value += 90;
    29	    run(a, null);
    30	  }
    31	}
    32	
    33	@pragma('dart2js:disable-inlining')
    34	main() {
    35	  print('---- 1');
    36	  try {
    37	    Foo().demo1();
    38	  } catch (e, s) {
    39	    print('$e\n$s');
    40	  }
    41	  print('---- 2');
    42	  try {
    43	    Foo().demo2();
    44	  } catch (e, s) {
    45	    print('$e\n$s');
    46	  }
    47	}

The Dart VM prints the following stacks.
This is the desired translation for the JavaScript stacks.

---- 1
Null check operator used on a null value
#0      Foo.check (file:///tmp/sm.dart:5:13)
#1      Foo.run (file:///tmp/sm.dart:10:10)
#2      Foo.demo1 (file:///tmp/sm.dart:23:5)
#3      main (file:///tmp/sm.dart:37:11)

---- 2
Null check operator used on a null value
#0      Foo.speak (file:///tmp/sm.dart:19:19)
#1      Foo.work (file:///tmp/sm.dart:14:7)
#2      Foo.run (file:///tmp/sm.dart:10:5)
#3      Foo.demo2 (file:///tmp/sm.dart:29:5)
#4      main (file:///tmp/sm.dart:43:11)

The code compiled by dart2js and run on V8 prints the following:

---- 1
Null check operator used on a null value
TypeError: Cannot read properties of null (reading 'speak$1')
    at Foo.run$2 (sm.js:2809:9)
    at Foo.demo1$0 (sm.js:2815:12)
    at main (sm.js:2401:18)
---- 2
Null check operator used on a null value
TypeError: Cannot read properties of null (reading 'value')
    at Foo.speak$1 (sm.js:2812:26)
    at Foo.run$2 (sm.js:2809:9)
    at Foo.demo2$0 (sm.js:2820:12)
    at main (sm.js:2409:18)

Foo.run$2 (sm.js:2809:9) appears in both V8 stacks.
Under ---- 1 this stack frame should be translated to an inlined call to check:

#0      Foo.check (file:///tmp/sm.dart:5:13)
#1      Foo.run (file:///tmp/sm.dart:10:10)

Under ---- 2, the identical stack frame line should be translated to an inlined call to work:

#1      Foo.work (file:///tmp/sm.dart:14:7)
#2      Foo.run (file:///tmp/sm.dart:10:5)

What is happening is that dart2js sometimes combines a null check and an subsequent property read, since in JavaScript a property read from null or undefined fails, so can be considered to be and implicit null check. Inlining check and work into run brings the null check adjacent to the method call, allowing them to be combined:

  2807	  A.Foo.prototype = {
  2808	    run$2(x, y) {
  2809	      x.speak$1(y);
    //	12345678901234567890123456789
                ^
  2810	    },
  2811	    speak$1(z) {
  2812	      A.print("Foo(" + z.value + ")");
  2813	    },
  2814	    demo1$0() {
  2815	      this.run$2(null, this);
  2816	    },
  2817	    demo2$0() {
  2818	      var a = new A.Foo();
  2819	      a.value = 100;
  2820	      this.run$2(a, null);
  2821	    }
  2822	  };

The other JavaScript engines pick different locations:

JSC uses the positions 8 and 16 of the operators . and (.

---- 1
Null check operator used on a null value
run$2@sm.js:2809:8
demo1$0@sm.js:2815:17
main@sm.js:2401:25
---- 2
Null check operator used on a null value
speak$1@sm.js:2812:25
run$2@sm.js:2809:16
demo2$0@sm.js:2820:17
main@sm.js:2409:25

SM uses the position 7 and 9 of the identifiers x and speak$1.

---- 1
Null check operator used on a null value
run$2@sm.js:2809:7
demo1$0@sm.js:2815:12
main@sm.js:2401:18
---- 2
Null check operator used on a null value
speak$1@sm.js:2812:24
run$2@sm.js:2809:9
demo2$0@sm.js:2820:12
main@sm.js:2409:18

Two ranges, c for inlined check and w for inlined work would give a sensible result for JSC and SM

    x.speak$1(y);
    ccwwwwwwww--

For V8, the error in check shows as a wrong location inside work. This is confusing, since the binding for (work)x would show null for a non-nullable original source type.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions