Skip to content

Scopes: Encoding pseudo-tail-calls #255

Description

@rakudrama

It is not clear how to conditionally hide frames depending on what more recent frames are on the stack.

The Dart method foo might require parametric covariant type checks and / or argument defaulting

class X<T> {
  void foo(T a,               // 8
           [T? b = null]) {   // 9
    throw 'ouch';             // 10
  }  
}

main() {
  X<Object> x = X<String>();
  x.foo('abc');              // 21
  x.foo(123);                // 22
  X<int>().foo(1,2);         // 23
}

To support the checks, one strategy (used by dart2js) is to compile the method into multiple entry points.
Each entry point handles some combination of checks and argument defaulting without doing unnecessary checks.
Some entry points call others, to eventually reach the main unchecked body.
If JavaScript had tail calls (and the syntax for tail calls was compact), tail calls could be used.

X.prototype = {
  foo$body(a, b) { throwExpression("ouch"); }
  foo$1$unchecked(a) { return this.foo$body(a, null); }
  foo$2(a, b) { return this.foo$body(checkType(a, this.T), checkTypeNullable(b, this.T)); }
  foo$1(a) { return this.foo$1$unchecked(checkType(a, this.T)); }
}

(The minifier can pick almost any name for these entry points which erases the naming system, e.g., k7, q, A_, f.)

Without tail calls, multiple stack frames can represent one call. Depending which calls are commented out, one might see different JavaScript stacks for the 'same' call in Dart:

Error: "ouch"
throwExpression
foo$body
foo$1$unchecked
foo$1
main
TypeError: 123: Type 'int' is not a subtype of 'String'
checkType
foo$1
main
Error: "ouch"
throwExpression
foo$body            // body is called directly because arguments don't need to be checked since T=int
main

All three stacks should be translated to a stack trace that has one frame for foo.

Error: "ouch"
foo     file.dart:10
main    file.dart:21
TypeError: 123: Type 'int' is not a subtype of 'String'
foo     file.dart:8        // position of 'foo' or. better, position of 'T a' or 'a'
main    file.dart:22
Error: "ouch"
foo     file.dart:10
main    file.dart:23

The frames that might have been tail calls should be hidden, but only if the target of the 'tail' call frame is not above it.
Complications include:

  • foo could be recursive, calling any of the entry points from the body
  • The body, if small, might be inlined into any of the entry points

Does the scopes proposal contain enough mechanism to implement the policy of translating the JavaScript stack to one 'best' frame for Dart?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions