From f985d6cdba078fc49b787c900f14dae8d4e379ee Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Wed, 8 Mar 2023 12:04:27 -0800 Subject: [PATCH] PropertyDelete/ComputedDelete instructions --- compiler/forget/src/HIR/BuildHIR.ts | 41 +++++++++++-- compiler/forget/src/HIR/HIR.ts | 14 +++++ compiler/forget/src/HIR/PrintHIR.ts | 12 ++++ compiler/forget/src/HIR/visitors.ts | 18 ++++++ .../src/Inference/InferReferenceEffects.ts | 13 ++++ .../src/Optimization/DeadCodeElimination.ts | 2 + .../ReactiveScopes/CodegenReactiveFunction.ts | 21 +++++++ .../InferReactiveScopeVariables.ts | 2 + .../hir/delete-computed-property.expect.md | 35 +++++++++++ .../fixtures/hir/delete-computed-property.js | 6 ++ .../fixtures/hir/delete-property.expect.md | 34 +++++++++++ .../__tests__/fixtures/hir/delete-property.js | 5 ++ .../fixtures/hir/unary-expr.expect.md | 60 +++++++++++-------- 13 files changed, 233 insertions(+), 30 deletions(-) create mode 100644 compiler/forget/src/__tests__/fixtures/hir/delete-computed-property.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/hir/delete-computed-property.js create mode 100644 compiler/forget/src/__tests__/fixtures/hir/delete-property.expect.md create mode 100644 compiler/forget/src/__tests__/fixtures/hir/delete-property.js diff --git a/compiler/forget/src/HIR/BuildHIR.ts b/compiler/forget/src/HIR/BuildHIR.ts index 00d3ae87e1..3b341daf94 100644 --- a/compiler/forget/src/HIR/BuildHIR.ts +++ b/compiler/forget/src/HIR/BuildHIR.ts @@ -1560,12 +1560,41 @@ function lowerExpression( } case "UnaryExpression": { let expr = exprPath as NodePath; - return { - kind: "UnaryExpression", - operator: expr.node.operator, - value: lowerExpressionToTemporary(builder, expr.get("argument")), - loc: exprLoc, - }; + if (expr.node.operator === "delete") { + const argument = expr.get("argument"); + if (argument.isMemberExpression()) { + const { object, property } = lowerMemberExpression(builder, argument); + if (typeof property === "string") { + return { + kind: "PropertyDelete", + object, + property, + loc: exprLoc, + }; + } else { + return { + kind: "ComputedDelete", + object, + property, + loc: exprLoc, + }; + } + } else { + builder.errors.push({ + reason: `(BuildHIR::lowerExpression) delete on a non-member expression has no semantic meaning`, + severity: ErrorSeverity.InvalidInput, + nodePath: expr, + }); + return { kind: "UnsupportedNode", node: expr.node, loc: exprLoc }; + } + } else { + return { + kind: "UnaryExpression", + operator: expr.node.operator, + value: lowerExpressionToTemporary(builder, expr.get("argument")), + loc: exprLoc, + }; + } } case "TypeCastExpression": { let expr = exprPath as NodePath; diff --git a/compiler/forget/src/HIR/HIR.ts b/compiler/forget/src/HIR/HIR.ts index af34e9e531..763695da43 100644 --- a/compiler/forget/src/HIR/HIR.ts +++ b/compiler/forget/src/HIR/HIR.ts @@ -544,6 +544,13 @@ export type InstructionValue = optional: boolean; loc: SourceLocation; } + // `delete object.property` + | { + kind: "PropertyDelete"; + object: Place; + property: string; + loc: SourceLocation; + } // store `object[index] = value` - like PropertyStore but with a dynamic property | { @@ -560,6 +567,13 @@ export type InstructionValue = property: Place; loc: SourceLocation; } + // `delete object[property]` + | { + kind: "ComputedDelete"; + object: Place; + property: Place; + loc: SourceLocation; + } | { kind: "LoadGlobal"; name: string; loc: SourceLocation } | FunctionExpression | { diff --git a/compiler/forget/src/HIR/PrintHIR.ts b/compiler/forget/src/HIR/PrintHIR.ts index 690828ffcf..ab3afe2d26 100644 --- a/compiler/forget/src/HIR/PrintHIR.ts +++ b/compiler/forget/src/HIR/PrintHIR.ts @@ -351,6 +351,12 @@ export function printInstructionValue(instrValue: ReactiveValue): string { } = ${printPlace(instrValue.value)}`; break; } + case "PropertyDelete": { + value = `PropertyDelete ${printPlace(instrValue.object)}.${ + instrValue.property + }`; + break; + } case "ComputedLoad": { value = `ComputedLoad ${printPlace(instrValue.object)}[${printPlace( instrValue.property @@ -363,6 +369,12 @@ export function printInstructionValue(instrValue: ReactiveValue): string { )}] = ${printPlace(instrValue.value)}`; break; } + case "ComputedDelete": { + value = `ComputedDelete ${printPlace(instrValue.object)}[${printPlace( + instrValue.property + )}]`; + break; + } case "FunctionExpression": { const fn = printFunction(instrValue.loweredFunc) .split("\n") diff --git a/compiler/forget/src/HIR/visitors.ts b/compiler/forget/src/HIR/visitors.ts index 2ee5644fc3..359895c981 100644 --- a/compiler/forget/src/HIR/visitors.ts +++ b/compiler/forget/src/HIR/visitors.ts @@ -80,6 +80,10 @@ export function* eachInstructionValueOperand( yield instrValue.object; break; } + case "PropertyDelete": { + yield instrValue.object; + break; + } case "PropertyStore": { yield instrValue.object; yield instrValue.value; @@ -90,6 +94,11 @@ export function* eachInstructionValueOperand( yield instrValue.property; break; } + case "ComputedDelete": { + yield instrValue.object; + yield instrValue.property; + break; + } case "ComputedStore": { yield instrValue.object; yield instrValue.property; @@ -254,6 +263,10 @@ export function mapInstructionOperands( instrValue.object = fn(instrValue.object); break; } + case "PropertyDelete": { + instrValue.object = fn(instrValue.object); + break; + } case "PropertyStore": { instrValue.object = fn(instrValue.object); instrValue.value = fn(instrValue.value); @@ -264,6 +277,11 @@ export function mapInstructionOperands( instrValue.property = fn(instrValue.property); break; } + case "ComputedDelete": { + instrValue.object = fn(instrValue.object); + instrValue.property = fn(instrValue.property); + break; + } case "ComputedStore": { instrValue.object = fn(instrValue.object); instrValue.property = fn(instrValue.property); diff --git a/compiler/forget/src/Inference/InferReferenceEffects.ts b/compiler/forget/src/Inference/InferReferenceEffects.ts index bb8f397a97..ef71ee4999 100644 --- a/compiler/forget/src/Inference/InferReferenceEffects.ts +++ b/compiler/forget/src/Inference/InferReferenceEffects.ts @@ -716,6 +716,12 @@ function inferBlock( lvalue.effect = Effect.Store; continue; } + case "PropertyDelete": { + // `delete` returns a boolean (immutable) and modifies the object + valueKind = ValueKind.Immutable; + effectKind = Effect.Mutate; + break; + } case "PropertyLoad": { if (!state.isDefined(instrValue.object)) { // TODO @josephsavona: improve handling of globals @@ -749,6 +755,13 @@ function inferBlock( lvalue.effect = Effect.Store; continue; } + case "ComputedDelete": { + state.reference(instrValue.object, Effect.Mutate); + state.reference(instrValue.property, Effect.Read); + state.initialize(instrValue, ValueKind.Immutable); + state.reference(instr.lvalue, Effect.Mutate); + continue; + } case "ComputedLoad": { if (!state.isDefined(instrValue.object)) { // TODO @josephsavona: improve handling of globals diff --git a/compiler/forget/src/Optimization/DeadCodeElimination.ts b/compiler/forget/src/Optimization/DeadCodeElimination.ts index 74a29c51c3..eefbe504bc 100644 --- a/compiler/forget/src/Optimization/DeadCodeElimination.ts +++ b/compiler/forget/src/Optimization/DeadCodeElimination.ts @@ -175,8 +175,10 @@ function pruneableValue( return true; } case "CallExpression": + case "ComputedDelete": case "ComputedCall": case "ComputedStore": + case "PropertyDelete": case "PropertyCall": case "PropertyStore": { // Mutating instructions are not safe to prune. diff --git a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts index 058d45fbc7..c4cde0a172 100644 --- a/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts +++ b/compiler/forget/src/ReactiveScopes/CodegenReactiveFunction.ts @@ -711,6 +711,16 @@ function codegenInstructionValue( } break; } + case "PropertyDelete": { + value = t.unaryExpression( + "delete", + t.memberExpression( + codegenPlace(cx, instrValue.object), + t.identifier(instrValue.property) + ) + ); + break; + } case "ComputedStore": { value = t.assignmentExpression( "=", @@ -731,6 +741,17 @@ function codegenInstructionValue( ); break; } + case "ComputedDelete": { + value = t.unaryExpression( + "delete", + t.memberExpression( + codegenPlace(cx, instrValue.object), + codegenPlace(cx, instrValue.property), + true + ) + ); + break; + } case "LoadLocal": { value = codegenPlace(cx, instrValue.place); break; diff --git a/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts b/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts index daa48d5ee1..581c88aedd 100644 --- a/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts +++ b/compiler/forget/src/ReactiveScopes/InferReactiveScopeVariables.ts @@ -205,7 +205,9 @@ function mayAllocate(value: InstructionValue): boolean { case "BinaryExpression": case "LoadLocal": case "PropertyLoad": + case "PropertyDelete": case "ComputedLoad": + case "ComputedDelete": case "JSXText": case "UnaryExpression": case "TemplateLiteral": diff --git a/compiler/forget/src/__tests__/fixtures/hir/delete-computed-property.expect.md b/compiler/forget/src/__tests__/fixtures/hir/delete-computed-property.expect.md new file mode 100644 index 0000000000..19624226aa --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/delete-computed-property.expect.md @@ -0,0 +1,35 @@ + +## Input + +```javascript +function Component(props) { + const x = { a: props.a, b: props.b }; + const key = "b"; + delete x[key]; + return x; +} + +``` + +## Code + +```javascript +function Component(props) { + const $ = React.unstable_useMemoCache(3); + const c_0 = $[0] !== props.a; + const c_1 = $[1] !== props.b; + let x; + if (c_0 || c_1) { + x = { a: props.a, b: props.b }; + delete x["b"]; + $[0] = props.a; + $[1] = props.b; + $[2] = x; + } else { + x = $[2]; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/delete-computed-property.js b/compiler/forget/src/__tests__/fixtures/hir/delete-computed-property.js new file mode 100644 index 0000000000..79bfdfd237 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/delete-computed-property.js @@ -0,0 +1,6 @@ +function Component(props) { + const x = { a: props.a, b: props.b }; + const key = "b"; + delete x[key]; + return x; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/delete-property.expect.md b/compiler/forget/src/__tests__/fixtures/hir/delete-property.expect.md new file mode 100644 index 0000000000..9a02c751fe --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/delete-property.expect.md @@ -0,0 +1,34 @@ + +## Input + +```javascript +function Component(props) { + const x = { a: props.a, b: props.b }; + delete x.b; + return x; +} + +``` + +## Code + +```javascript +function Component(props) { + const $ = React.unstable_useMemoCache(3); + const c_0 = $[0] !== props.a; + const c_1 = $[1] !== props.b; + let x; + if (c_0 || c_1) { + x = { a: props.a, b: props.b }; + delete x.b; + $[0] = props.a; + $[1] = props.b; + $[2] = x; + } else { + x = $[2]; + } + return x; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/delete-property.js b/compiler/forget/src/__tests__/fixtures/hir/delete-property.js new file mode 100644 index 0000000000..84d0de961d --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/delete-property.js @@ -0,0 +1,5 @@ +function Component(props) { + const x = { a: props.a, b: props.b }; + delete x.b; + return x; +} diff --git a/compiler/forget/src/__tests__/fixtures/hir/unary-expr.expect.md b/compiler/forget/src/__tests__/fixtures/hir/unary-expr.expect.md index 6eb72a1f79..868956d994 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/unary-expr.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/unary-expr.expect.md @@ -20,44 +20,56 @@ function component(a) { ```javascript function component(a) { - const $ = React.unstable_useMemoCache(10); + const $ = React.unstable_useMemoCache(14); const c_0 = $[0] !== a; let t0; + let t; + let z; + let p; + let q; if (c_0) { - t0 = { t: a }; + t = { t: a }; + z = +t.t; + q = -t.t; + p = void t.t; + t0 = delete t.t; $[0] = a; $[1] = t0; + $[2] = t; + $[3] = z; + $[4] = p; + $[5] = q; } else { t0 = $[1]; + t = $[2]; + z = $[3]; + p = $[4]; + q = $[5]; } - const t = t0; - const z = +t.t; - const q = -t.t; - const p = void t.t; - const n = delete t.t; + const n = t0; const m = !t.t; const e = ~t.t; const f = typeof t.t; - const c_2 = $[2] !== z; - const c_3 = $[3] !== p; - const c_4 = $[4] !== q; - const c_5 = $[5] !== n; - const c_6 = $[6] !== m; - const c_7 = $[7] !== e; - const c_8 = $[8] !== f; + const c_6 = $[6] !== z; + const c_7 = $[7] !== p; + const c_8 = $[8] !== q; + const c_9 = $[9] !== n; + const c_10 = $[10] !== m; + const c_11 = $[11] !== e; + const c_12 = $[12] !== f; let t1; - if (c_2 || c_3 || c_4 || c_5 || c_6 || c_7 || c_8) { + if (c_6 || c_7 || c_8 || c_9 || c_10 || c_11 || c_12) { t1 = { z, p, q, n, m, e, f }; - $[2] = z; - $[3] = p; - $[4] = q; - $[5] = n; - $[6] = m; - $[7] = e; - $[8] = f; - $[9] = t1; + $[6] = z; + $[7] = p; + $[8] = q; + $[9] = n; + $[10] = m; + $[11] = e; + $[12] = f; + $[13] = t1; } else { - t1 = $[9]; + t1 = $[13]; } return t1; }