Skip to content

Commit a11aeeb

Browse files
committed
[dart2js] Annotation for exception stacks with shorter prefix
Dart's `throw` expression does not correspond exactly to JavaScript's `throw` statement. To implement Dart semantics, and to reduce code size, dart2js uses the functions `wrapException` and `throwExpression` from the dart2js runtime. As a result, these functions appear in the stack trace (one or both). A consequence of this is that a fixed prefix of an error stack is less useful, as potentially interesting frames are forced out of the prefix by these 'noise' frames. This change ensures only one of the helpers on the stack trace. It is also possible to get rid of the helper frame by an annotation. The annotation `@pragma('dart2js:stack-starts-at-throw')` causes the stack to be captured in the current JavaScript function rather than in a helper. The cost is more code at the call site, about 12 bytes per `throw` expression in minified code. The annotation can be placed on a method, class or library, and applies to all `throw` expression in the scope of the annotated element. This change uses the annotation to remove noise frames from type errors and some other errors in the runtime. Change-Id: If15184a5963fb054199177bb4526b32f25e53fe9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/406684 Reviewed-by: Mayank Patke <[email protected]> Reviewed-by: Nate Biggs <[email protected]>
1 parent e038d8a commit a11aeeb

File tree

14 files changed

+139
-54
lines changed

14 files changed

+139
-54
lines changed

pkg/compiler/doc/pragmas.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
| `dart2js:noElision` | Disables an optimization whereby unused fields or unused parameters are removed |
1313
| `dart2js:load-priority` | [Affects deferred library loading](#load-priority) |
1414
| `dart2js:resource-identifier` | [Collects data references to resources](resource_identifiers.md) |
15+
| `dart2js:stack-starts-at-throw` | [Affects stack trace from `throw` expressions](#stack-statrs-at-throw) |
1516
| `weak-tearoff-reference` | [Declaring a static weak reference intrinsic method.](#declaring-a-static-weak-reference-intrinsic-method) |
1617

1718
## Unsafe pragmas for general use
@@ -214,6 +215,21 @@ In the future this annotation might be extended to apply to `late` local
214215
variables, static variables, and top-level variables.
215216

216217

218+
#### Stack starts at throw
219+
220+
In order to generate smaller code, `throw` expressions are usually implemented
221+
by calling a helper method to capture the stack trace and perform the actual
222+
throw. This adds one (or occasionally two) frames to the stack trace showing the
223+
helper functions. It is possible for the stack trace to be captured directly in
224+
the method containing the `throw` expression. This takes a little extra code, so
225+
is opt-in via the following annotation.
226+
227+
```dart
228+
@pragma('dart2js:stack-starts-at-throw')
229+
```
230+
231+
This annotation can be placed on a method, class or library.
232+
217233
### Annotations related to deferred library loading
218234

219235
#### Load priority

pkg/compiler/lib/src/common/elements.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,9 @@ abstract class CommonElements {
898898

899899
FunctionEntity get stringInterpolationHelper => _findHelperFunction('S');
900900

901+
FunctionEntity get initializeExceptionWrapper =>
902+
_findHelperFunction('initializeExceptionWrapper');
903+
901904
FunctionEntity get wrapExceptionHelper =>
902905
_findHelperFunction('wrapException');
903906

pkg/compiler/lib/src/js_backend/annotations.dart

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ enum PragmaAnnotation {
9191
lateCheck('late:check'),
9292

9393
loadLibraryPriority('load-priority', hasOption: true),
94-
resourceIdentifier('resource-identifier');
94+
resourceIdentifier('resource-identifier'),
95+
96+
throwWithoutHelperFrame('stack-starts-at-throw');
9597

9698
final String name;
9799
final bool forFunctionsOnly;
@@ -357,6 +359,11 @@ abstract class AnnotationsData {
357359

358360
/// Determines whether [member] is annotated as a resource identifier.
359361
bool methodIsResourceIdentifier(FunctionEntity member);
362+
363+
/// Is this node in a context requesting that the captured stack in a `throw`
364+
/// expression generates extra code to avoid having a runtime helper on the
365+
/// stack?
366+
bool throwWithoutHelperFrame(ir.TreeNode node);
360367
}
361368

362369
class AnnotationsDataImpl implements AnnotationsData {
@@ -645,6 +652,22 @@ class AnnotationsDataImpl implements AnnotationsData {
645652
}
646653
return false;
647654
}
655+
656+
@override
657+
bool throwWithoutHelperFrame(ir.TreeNode node) {
658+
return _throwWithoutHelperFrame(_findContext(node));
659+
}
660+
661+
bool _throwWithoutHelperFrame(DirectivesContext? context) {
662+
while (context != null) {
663+
EnumSet<PragmaAnnotation>? annotations = context.annotations;
664+
if (annotations.contains(PragmaAnnotation.throwWithoutHelperFrame)) {
665+
return true;
666+
}
667+
context = context.parent;
668+
}
669+
return false;
670+
}
648671
}
649672

650673
class AnnotationsDataBuilder {

pkg/compiler/lib/src/ssa/builder.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2529,7 +2529,14 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault<void>
25292529
final sourceInformation = _sourceInformationBuilder.buildThrow(
25302530
node.expression,
25312531
);
2532-
_closeAndGotoExit(HThrow(pop(), sourceInformation));
2532+
_closeAndGotoExit(
2533+
HThrow(
2534+
pop(),
2535+
sourceInformation,
2536+
withoutHelperFrame: closedWorld.annotationsData
2537+
.throwWithoutHelperFrame(node),
2538+
),
2539+
);
25332540
} else {
25342541
expression.accept(this);
25352542
pop();
@@ -7759,6 +7766,8 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault<void>
77597766
pop(),
77607767
_abstractValueDomain.emptyType,
77617768
sourceInformation,
7769+
withoutHelperFrame: closedWorld.annotationsData
7770+
.throwWithoutHelperFrame(node),
77627771
),
77637772
);
77647773
_isReachable = false;

pkg/compiler/lib/src/ssa/codegen.dart

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2994,13 +2994,24 @@ class SsaCodeGenerator implements HVisitor<void>, HBlockInformationVisitor {
29942994
pushStatement(js.Throw(pop()).withSourceInformation(sourceInformation));
29952995
} else {
29962996
use(node.inputs[0]);
2997-
_pushCallStatic(_commonElements.wrapExceptionHelper, [
2998-
pop(),
2999-
], sourceInformation);
2997+
if (node.withoutHelperFrame) {
2998+
_pushCallStatic(_commonElements.initializeExceptionWrapper, [
2999+
pop(),
3000+
_newErrorObject(sourceInformation),
3001+
], sourceInformation);
3002+
} else {
3003+
_pushCallStatic(_commonElements.wrapExceptionHelper, [
3004+
pop(),
3005+
], sourceInformation);
3006+
}
30003007
pushStatement(js.Throw(pop()).withSourceInformation(sourceInformation));
30013008
}
30023009
}
30033010

3011+
js.Expression _newErrorObject(SourceInformation? sourceInformation) {
3012+
return js.js('new Error()').withSourceInformation(sourceInformation);
3013+
}
3014+
30043015
@override
30053016
void visitAwait(HAwait node) {
30063017
use(node.inputs[0]);
@@ -3185,6 +3196,7 @@ class SsaCodeGenerator implements HVisitor<void>, HBlockInformationVisitor {
31853196
use(node.inputs[0]);
31863197
_pushCallStatic(_commonElements.throwExpressionHelper, [
31873198
pop(),
3199+
if (node.withoutHelperFrame) _newErrorObject(node.sourceInformation),
31883200
], node.sourceInformation);
31893201
}
31903202

pkg/compiler/lib/src/ssa/nodes.dart

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3417,11 +3417,13 @@ class HReturn extends HControlFlow {
34173417
}
34183418

34193419
class HThrowExpression extends HInstruction {
3420+
final bool withoutHelperFrame;
34203421
HThrowExpression(
34213422
super.value,
34223423
super.type,
3423-
SourceInformation? sourceInformation,
3424-
) : super._oneInput() {
3424+
SourceInformation? sourceInformation, {
3425+
this.withoutHelperFrame = false,
3426+
}) : super._oneInput() {
34253427
this.sourceInformation = sourceInformation;
34263428
}
34273429
@override
@@ -3466,10 +3468,12 @@ class HYield extends HInstruction {
34663468

34673469
class HThrow extends HControlFlow {
34683470
final bool isRethrow;
3471+
final bool withoutHelperFrame;
34693472
HThrow(
34703473
HInstruction value,
34713474
SourceInformation? sourceInformation, {
34723475
this.isRethrow = false,
3476+
this.withoutHelperFrame = false,
34733477
}) {
34743478
inputs.add(value);
34753479
this.sourceInformation = sourceInformation;

pkg/compiler/test/impact/data/expressions.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,8 +358,11 @@ testAsGenericRaw(dynamic o) => o as GenericClass;
358358
testAsGenericDynamic(dynamic o) => o as GenericClass<dynamic, dynamic>;
359359

360360
/*member: testThrow:
361-
static=[throwExpression(1),wrapException(1)],
362-
type=[inst:JSString]*/
361+
static=[
362+
throwExpression(2),
363+
wrapException(1)],
364+
type=[inst:JSString]
365+
*/
363366
testThrow() => throw '';
364367

365368
/*member: testIfNotNull:

pkg/compiler/test/impact/data/statements.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ testTryFinally() {
424424

425425
/*member: testSwitchWithoutFallthrough:
426426
static=[
427-
throwExpression(1),
427+
throwExpression(2),
428428
wrapException(1)],
429429
type=[
430430
inst:JSInt,

pkg/compiler/test/sourcemaps/stacktrace_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ Future runTest(
124124
const List<LineException> beforeExceptions = const [
125125
const LineException('wrapException', 'js_helper.dart'),
126126
const LineException('throwExpression', 'js_helper.dart'),
127+
const LineException('throw_', 'js_helper.dart'),
127128
];
128129

129130
/// Lines allowed after the intended stack trace. Typically from the event

sdk/lib/_internal/js_runtime/lib/async_patch.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import 'dart:_js_helper'
1212
getTraceFromException,
1313
Primitives,
1414
requiresPreamble,
15-
wrapException,
1615
unwrapException;
1716

1817
import 'dart:_foreign_helper'

0 commit comments

Comments
 (0)