From 076df1148fe5fe3faf709af4f58366e0849ed72a Mon Sep 17 00:00:00 2001 From: Lukas Taegert-Atkinson Date: Fri, 2 Jul 2021 05:58:02 +0200 Subject: [PATCH] Deoptimize return values of async functions (#4163) --- src/ast/nodes/ArrowFunctionExpression.ts | 14 +++++++++++++- src/ast/nodes/AwaitExpression.ts | 1 + src/ast/nodes/shared/FunctionNode.ts | 14 +++++++++++++- .../_config.js | 10 ++++++++++ .../main.js | 19 +++++++++++++++++++ .../_config.js | 10 ++++++++++ .../track-async-function-return-value/main.js | 18 ++++++++++++++++++ .../samples/track-awaited-object/main.js | 1 - 8 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 test/function/samples/track-async-arrow-function-return-value/_config.js create mode 100644 test/function/samples/track-async-arrow-function-return-value/main.js create mode 100644 test/function/samples/track-async-function-return-value/_config.js create mode 100644 test/function/samples/track-async-function-return-value/main.js diff --git a/src/ast/nodes/ArrowFunctionExpression.ts b/src/ast/nodes/ArrowFunctionExpression.ts index c9f9aae3ba6..fd8ca028256 100644 --- a/src/ast/nodes/ArrowFunctionExpression.ts +++ b/src/ast/nodes/ArrowFunctionExpression.ts @@ -20,6 +20,7 @@ export default class ArrowFunctionExpression extends NodeBase { preventChildBlockScope!: true; scope!: ReturnValueScope; type!: NodeType.tArrowFunctionExpression; + private deoptimizedReturn = false; createScope(parentScope: Scope): void { this.scope = new ReturnValueScope(parentScope, this.context); @@ -37,7 +38,18 @@ export default class ArrowFunctionExpression extends NodeBase { deoptimizeThisOnEventAtPath(): void {} getReturnExpressionWhenCalledAtPath(path: ObjectPath): ExpressionEntity { - return !this.async && path.length === 0 ? this.scope.getReturnExpression() : UNKNOWN_EXPRESSION; + if (path.length !== 0) { + return UNKNOWN_EXPRESSION; + } + if (this.async) { + if (!this.deoptimizedReturn) { + this.deoptimizedReturn = true; + this.scope.getReturnExpression().deoptimizePath(UNKNOWN_PATH); + this.context.requestTreeshakingPass(); + } + return UNKNOWN_EXPRESSION; + } + return this.scope.getReturnExpression(); } hasEffects(): boolean { diff --git a/src/ast/nodes/AwaitExpression.ts b/src/ast/nodes/AwaitExpression.ts index 8a9f5c3569e..22ad50cdafe 100644 --- a/src/ast/nodes/AwaitExpression.ts +++ b/src/ast/nodes/AwaitExpression.ts @@ -34,5 +34,6 @@ export default class AwaitExpression extends NodeBase { protected applyDeoptimizations(): void { this.deoptimized = true; this.argument.deoptimizePath(UNKNOWN_PATH); + this.context.requestTreeshakingPass(); } } diff --git a/src/ast/nodes/shared/FunctionNode.ts b/src/ast/nodes/shared/FunctionNode.ts index 8003a023e85..2d3de584c28 100644 --- a/src/ast/nodes/shared/FunctionNode.ts +++ b/src/ast/nodes/shared/FunctionNode.ts @@ -21,6 +21,7 @@ export default class FunctionNode extends NodeBase { params!: PatternNode[]; preventChildBlockScope!: true; scope!: FunctionScope; + private deoptimizedReturn = false; private isPrototypeDeoptimized = false; createScope(parentScope: FunctionScope): void { @@ -57,7 +58,18 @@ export default class FunctionNode extends NodeBase { } getReturnExpressionWhenCalledAtPath(path: ObjectPath): ExpressionEntity { - return !this.async && path.length === 0 ? this.scope.getReturnExpression() : UNKNOWN_EXPRESSION; + if (path.length !== 0) { + return UNKNOWN_EXPRESSION; + } + if (this.async) { + if (!this.deoptimizedReturn) { + this.deoptimizedReturn = true; + this.scope.getReturnExpression().deoptimizePath(UNKNOWN_PATH); + this.context.requestTreeshakingPass(); + } + return UNKNOWN_EXPRESSION; + } + return this.scope.getReturnExpression(); } hasEffects(): boolean { diff --git a/test/function/samples/track-async-arrow-function-return-value/_config.js b/test/function/samples/track-async-arrow-function-return-value/_config.js new file mode 100644 index 00000000000..3857c8b429d --- /dev/null +++ b/test/function/samples/track-async-arrow-function-return-value/_config.js @@ -0,0 +1,10 @@ +const assert = require('assert'); + +module.exports = { + description: 'tracks object mutations of async arrow function return values', + async exports(exports) { + assert.strictEqual(exports.toggled, false); + await exports.test(); + assert.strictEqual(exports.toggled, true); + } +}; diff --git a/test/function/samples/track-async-arrow-function-return-value/main.js b/test/function/samples/track-async-arrow-function-return-value/main.js new file mode 100644 index 00000000000..6f0d2aeb01b --- /dev/null +++ b/test/function/samples/track-async-arrow-function-return-value/main.js @@ -0,0 +1,19 @@ +const fn = async () => { + const obj = { + test() { + if (typeof obj.testfn === 'function') { + obj.testfn(); + } + } + }; + return obj; +}; + +export let toggled = false; + +export const test = async function () { + const obj1 = await fn(); + const obj2 = await fn(); + obj1.testfn = () => (toggled = true); + obj1.test(); +}; diff --git a/test/function/samples/track-async-function-return-value/_config.js b/test/function/samples/track-async-function-return-value/_config.js new file mode 100644 index 00000000000..1bcafba7017 --- /dev/null +++ b/test/function/samples/track-async-function-return-value/_config.js @@ -0,0 +1,10 @@ +const assert = require('assert'); + +module.exports = { + description: 'tracks object mutations of async function return values', + async exports(exports) { + assert.strictEqual(exports.toggled, false); + await exports.test(); + assert.strictEqual(exports.toggled, true); + } +}; diff --git a/test/function/samples/track-async-function-return-value/main.js b/test/function/samples/track-async-function-return-value/main.js new file mode 100644 index 00000000000..ef9c1bb05d8 --- /dev/null +++ b/test/function/samples/track-async-function-return-value/main.js @@ -0,0 +1,18 @@ +async function fn() { + const obj = { + test() { + if (typeof obj.testfn === 'function') { + obj.testfn(); + } + } + }; + return obj; +} + +export let toggled = false; + +export const test = async function () { + const obj = await fn(); + obj.testfn = () => (toggled = true); + obj.test(); +}; diff --git a/test/function/samples/track-awaited-object/main.js b/test/function/samples/track-awaited-object/main.js index abeaa8a8482..6a8791e31a7 100644 --- a/test/function/samples/track-awaited-object/main.js +++ b/test/function/samples/track-awaited-object/main.js @@ -6,7 +6,6 @@ function fn() { } } }; - return obj; }