Skip to content

Commit

Permalink
Track mutations of elements pushed into arrays (#5352)
Browse files Browse the repository at this point in the history
* Add test for dependency deoptimization

* Deoptimize elements pushed into an array

* Copy rollup types in tests to avoid temporary type issues
  • Loading branch information
lukastaegert committed Jan 21, 2024
1 parent c4ba8eb commit f2ed658
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 7 deletions.
2 changes: 1 addition & 1 deletion rollup.config.ts
Expand Up @@ -89,7 +89,7 @@ export default async function (
addCliEntry(),
esmDynamicImport(),
!command.configTest && collectLicenses(),
!command.configTest && copyNodeTypes()
copyNodeTypes()
],
strictDeprecations: true,
treeshake
Expand Down
17 changes: 14 additions & 3 deletions src/ast/nodes/shared/ArrayPrototype.ts
Expand Up @@ -19,6 +19,7 @@ const NEW_ARRAY_PROPERTIES: ObjectProperty[] = [
const METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_BOOLEAN: [ExpressionEntity] = [
new Method({
callsArgs: [0],
mutatesArgs: false,
mutatesSelfAsArray: 'deopt-only',
returns: null,
returnsPrimitive: UNKNOWN_LITERAL_BOOLEAN
Expand All @@ -28,6 +29,7 @@ const METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_BOOLEAN: [ExpressionEntity] = [
const METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_NUMBER: [ExpressionEntity] = [
new Method({
callsArgs: [0],
mutatesArgs: false,
mutatesSelfAsArray: 'deopt-only',
returns: null,
returnsPrimitive: UNKNOWN_LITERAL_NUMBER
Expand All @@ -37,6 +39,7 @@ const METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_NUMBER: [ExpressionEntity] = [
const METHOD_MUTATES_SELF_RETURNS_NEW_ARRAY: [ExpressionEntity] = [
new Method({
callsArgs: null,
mutatesArgs: false,
mutatesSelfAsArray: true,
returns: () => new ObjectEntity(NEW_ARRAY_PROPERTIES, ARRAY_PROTOTYPE),
returnsPrimitive: null
Expand All @@ -46,6 +49,7 @@ const METHOD_MUTATES_SELF_RETURNS_NEW_ARRAY: [ExpressionEntity] = [
const METHOD_DEOPTS_SELF_RETURNS_NEW_ARRAY: [ExpressionEntity] = [
new Method({
callsArgs: null,
mutatesArgs: false,
mutatesSelfAsArray: 'deopt-only',
returns: () => new ObjectEntity(NEW_ARRAY_PROPERTIES, ARRAY_PROTOTYPE),
returnsPrimitive: null
Expand All @@ -55,15 +59,17 @@ const METHOD_DEOPTS_SELF_RETURNS_NEW_ARRAY: [ExpressionEntity] = [
const METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_NEW_ARRAY: [ExpressionEntity] = [
new Method({
callsArgs: [0],
mutatesArgs: false,
mutatesSelfAsArray: 'deopt-only',
returns: () => new ObjectEntity(NEW_ARRAY_PROPERTIES, ARRAY_PROTOTYPE),
returnsPrimitive: null
})
];

const METHOD_MUTATES_SELF_RETURNS_NUMBER: [ExpressionEntity] = [
const METHOD_MUTATES_SELF_AND_ARGS_RETURNS_NUMBER: [ExpressionEntity] = [
new Method({
callsArgs: null,
mutatesArgs: true,
mutatesSelfAsArray: true,
returns: null,
returnsPrimitive: UNKNOWN_LITERAL_NUMBER
Expand All @@ -73,6 +79,7 @@ const METHOD_MUTATES_SELF_RETURNS_NUMBER: [ExpressionEntity] = [
const METHOD_MUTATES_SELF_RETURNS_UNKNOWN: [ExpressionEntity] = [
new Method({
callsArgs: null,
mutatesArgs: false,
mutatesSelfAsArray: true,
returns: null,
returnsPrimitive: UNKNOWN_EXPRESSION
Expand All @@ -82,6 +89,7 @@ const METHOD_MUTATES_SELF_RETURNS_UNKNOWN: [ExpressionEntity] = [
const METHOD_DEOPTS_SELF_RETURNS_UNKNOWN: [ExpressionEntity] = [
new Method({
callsArgs: null,
mutatesArgs: false,
mutatesSelfAsArray: 'deopt-only',
returns: null,
returnsPrimitive: UNKNOWN_EXPRESSION
Expand All @@ -91,6 +99,7 @@ const METHOD_DEOPTS_SELF_RETURNS_UNKNOWN: [ExpressionEntity] = [
const METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_UNKNOWN: [ExpressionEntity] = [
new Method({
callsArgs: [0],
mutatesArgs: false,
mutatesSelfAsArray: 'deopt-only',
returns: null,
returnsPrimitive: UNKNOWN_EXPRESSION
Expand All @@ -100,6 +109,7 @@ const METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_UNKNOWN: [ExpressionEntity] = [
const METHOD_MUTATES_SELF_RETURNS_SELF: [ExpressionEntity] = [
new Method({
callsArgs: null,
mutatesArgs: false,
mutatesSelfAsArray: true,
returns: 'self',
returnsPrimitive: null
Expand All @@ -109,6 +119,7 @@ const METHOD_MUTATES_SELF_RETURNS_SELF: [ExpressionEntity] = [
const METHOD_CALLS_ARG_MUTATES_SELF_RETURNS_SELF: [ExpressionEntity] = [
new Method({
callsArgs: [0],
mutatesArgs: false,
mutatesSelfAsArray: true,
returns: 'self',
returnsPrimitive: null
Expand Down Expand Up @@ -140,7 +151,7 @@ export const ARRAY_PROTOTYPE = new ObjectEntity(
lastIndexOf: METHOD_RETURNS_NUMBER,
map: METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_NEW_ARRAY,
pop: METHOD_MUTATES_SELF_RETURNS_UNKNOWN,
push: METHOD_MUTATES_SELF_RETURNS_NUMBER,
push: METHOD_MUTATES_SELF_AND_ARGS_RETURNS_NUMBER,
reduce: METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_UNKNOWN,
reduceRight: METHOD_CALLS_ARG_DEOPTS_SELF_RETURNS_UNKNOWN,
reverse: METHOD_MUTATES_SELF_RETURNS_SELF,
Expand All @@ -151,7 +162,7 @@ export const ARRAY_PROTOTYPE = new ObjectEntity(
splice: METHOD_MUTATES_SELF_RETURNS_NEW_ARRAY,
toLocaleString: METHOD_RETURNS_STRING,
toString: METHOD_RETURNS_STRING,
unshift: METHOD_MUTATES_SELF_RETURNS_NUMBER,
unshift: METHOD_MUTATES_SELF_AND_ARGS_RETURNS_NUMBER,
values: METHOD_DEOPTS_SELF_RETURNS_UNKNOWN
} as unknown as PropertyMap,
OBJECT_PROTOTYPE,
Expand Down
23 changes: 20 additions & 3 deletions src/ast/nodes/shared/MethodTypes.ts
Expand Up @@ -6,7 +6,12 @@ import {
NODE_INTERACTION_UNKNOWN_ASSIGNMENT,
NODE_INTERACTION_UNKNOWN_CALL
} from '../../NodeInteractions';
import { EMPTY_PATH, type ObjectPath, UNKNOWN_INTEGER_PATH } from '../../utils/PathTracker';
import {
EMPTY_PATH,
type ObjectPath,
UNKNOWN_INTEGER_PATH,
UNKNOWN_PATH
} from '../../utils/PathTracker';
import {
UNKNOWN_LITERAL_BOOLEAN,
UNKNOWN_LITERAL_NUMBER,
Expand All @@ -17,6 +22,7 @@ import { ExpressionEntity, UNKNOWN_EXPRESSION, UNKNOWN_RETURN_EXPRESSION } from
type MethodDescription = {
callsArgs: number[] | null;
mutatesSelfAsArray: boolean | 'deopt-only';
mutatesArgs: boolean;
} & (
| {
returns: 'self' | (() => ExpressionEntity);
Expand All @@ -34,8 +40,15 @@ export class Method extends ExpressionEntity {
}

deoptimizeArgumentsOnInteractionAtPath({ args, type }: NodeInteraction, path: ObjectPath): void {
if (type === INTERACTION_CALLED && path.length === 0 && this.description.mutatesSelfAsArray) {
args[0]?.deoptimizePath(UNKNOWN_INTEGER_PATH);
if (type === INTERACTION_CALLED && path.length === 0) {
if (this.description.mutatesSelfAsArray) {
args[0]?.deoptimizePath(UNKNOWN_INTEGER_PATH);
}
if (this.description.mutatesArgs) {
for (let index = 1; index < args.length; index++) {
args[index]!.deoptimizePath(UNKNOWN_PATH);
}
}
}
}

Expand Down Expand Up @@ -97,6 +110,7 @@ export class Method extends ExpressionEntity {
export const METHOD_RETURNS_BOOLEAN = [
new Method({
callsArgs: null,
mutatesArgs: false,
mutatesSelfAsArray: false,
returns: null,
returnsPrimitive: UNKNOWN_LITERAL_BOOLEAN
Expand All @@ -106,6 +120,7 @@ export const METHOD_RETURNS_BOOLEAN = [
export const METHOD_RETURNS_STRING = [
new Method({
callsArgs: null,
mutatesArgs: false,
mutatesSelfAsArray: false,
returns: null,
returnsPrimitive: UNKNOWN_LITERAL_STRING
Expand All @@ -115,6 +130,7 @@ export const METHOD_RETURNS_STRING = [
export const METHOD_RETURNS_NUMBER = [
new Method({
callsArgs: null,
mutatesArgs: false,
mutatesSelfAsArray: false,
returns: null,
returnsPrimitive: UNKNOWN_LITERAL_NUMBER
Expand All @@ -124,6 +140,7 @@ export const METHOD_RETURNS_NUMBER = [
export const METHOD_RETURNS_UNKNOWN = [
new Method({
callsArgs: null,
mutatesArgs: false,
mutatesSelfAsArray: false,
returns: null,
returnsPrimitive: UNKNOWN_EXPRESSION
Expand Down
@@ -0,0 +1,3 @@
module.exports = defineTest({
description: 'deoptimizes elements pushed into an array'
});
21 changes: 21 additions & 0 deletions test/function/samples/deoptimize-pushed-array-element/main.js
@@ -0,0 +1,21 @@
let mutated1 = false;
const state1 = {
foo: undefined
};

let mutated2 = false;
const state2 = {
foo: undefined
};

const stack = [];
stack.push(state1, state2);
stack[0].foo = () => (mutated1 = true);
stack.at(-1).foo = () => (mutated2 = true);

state1.foo?.();
assert.ok(mutated1, '1');

state2.foo?.();
assert.ok(mutated2, '2');

0 comments on commit f2ed658

Please sign in to comment.