diff --git a/compiler/forget/src/HIR/BuildDefUseGraph.ts b/compiler/forget/src/HIR/BuildDefUseGraph.ts index e31c4835ca..04bef729f8 100644 --- a/compiler/forget/src/HIR/BuildDefUseGraph.ts +++ b/compiler/forget/src/HIR/BuildDefUseGraph.ts @@ -22,8 +22,8 @@ import { mapTerminalSuccessors } from "./HIRBuilder"; import { printMixedHIR, printPlace } from "./PrintHIR"; const HOOKS: Map = new Map([ - ["useState", { kind: "State", capability: Capability.Frozen }], - ["useRef", { kind: "Ref", capability: Capability.Frozen }], + ["useState", { kind: "State", capability: Capability.Freeze }], + ["useRef", { kind: "Ref", capability: Capability.Freeze }], ]); type HookKind = { kind: "State" } | { kind: "Ref" } | { kind: "Custom" }; @@ -86,7 +86,7 @@ export default function buildDefUseGraph(fn: HIRFunction): Array { memberPath: null, value: param, path: null as any, // TODO - capability: Capability.Frozen, + capability: Capability.Freeze, }; preambleBuilder.init(place, null, true); } @@ -131,7 +131,7 @@ export default function buildDefUseGraph(fn: HIRFunction): Array { // Vertices derived from a frozen value are also frozen if ( vertex.place !== null && - vertex.place.capability === Capability.Frozen + vertex.place.capability === Capability.Freeze ) { flowFrozennessForwards(vertex, 0); } @@ -157,7 +157,7 @@ function flowFrozennessForwards(vertex: Vertex, epoch: number) { } vertex.epoch = epoch; if (vertex.place !== null) { - vertex.place.capability = Capability.Frozen; + vertex.place.capability = Capability.Freeze; } for (const outgoing of vertex.outgoing) { flowFrozennessForwards(outgoing, epoch); @@ -260,7 +260,7 @@ function buildInputsOutputsForBlock( let valueCapability = Capability.Readonly; switch (instrValue.kind) { case "BinaryExpression": { - valueCapability = Capability.Frozen; + valueCapability = Capability.Freeze; builder.reference(instrValue.left, instrValue, Capability.Readonly); builder.reference(instrValue.right, instrValue, Capability.Readonly); break; @@ -301,7 +301,7 @@ function buildInputsOutputsForBlock( break; } case "UnaryExpression": { - valueCapability = Capability.Frozen; // TODO check that value must be a primitive, or make conditional based on the operator + valueCapability = Capability.Freeze; // TODO check that value must be a primitive, or make conditional based on the operator builder.reference(instrValue.value, instrValue, Capability.Readonly); break; } @@ -310,13 +310,13 @@ function buildInputsOutputsForBlock( break; } case "JsxExpression": { - builder.reference(instrValue.tag, instrValue, Capability.Frozen); + builder.reference(instrValue.tag, instrValue, Capability.Freeze); for (const [_prop, value] of Object.entries(instrValue.props)) { - builder.reference(value, instrValue, Capability.Frozen); + builder.reference(value, instrValue, Capability.Freeze); } if (instrValue.children !== null) { for (const child of instrValue.children) { - builder.reference(child, instrValue, Capability.Frozen); + builder.reference(child, instrValue, Capability.Freeze); } } break; @@ -354,7 +354,7 @@ function buildInputsOutputsForBlock( builder.reference( block.terminal.value, block.terminal, - Capability.Frozen + Capability.Freeze ); break; } @@ -363,7 +363,7 @@ function buildInputsOutputsForBlock( builder.reference( block.terminal.value, block.terminal, - Capability.Frozen + Capability.Freeze ); } break; @@ -416,7 +416,7 @@ function parseHookCall(place: Place): Hook | null { if (hook != null) { return hook; } - return { kind: "Custom", capability: Capability.Frozen }; + return { kind: "Custom", capability: Capability.Freeze }; } type BlockResult = { diff --git a/compiler/forget/src/HIR/HIR.ts b/compiler/forget/src/HIR/HIR.ts index d2432ab19d..5bdfb38ac6 100644 --- a/compiler/forget/src/HIR/HIR.ts +++ b/compiler/forget/src/HIR/HIR.ts @@ -221,12 +221,11 @@ export type Identifier = { export enum Capability { // Default value: not allowed after lifetime inference Unknown = "", - // The value is known to be deeply, permanently mutable at this point. - Frozen = "frozen", - // The value is not modified at this point or thereafter, but is not guaranteed to be - // frozen. It would be safe to freeze the value at this point. + // The value is made frozen at this point. + Freeze = "freeze", + // The value is not modified at this point. Readonly = "readonly", - // The value is mutated at this point or at some later point. + // The value is modified at this point. Mutable = "mutable", } diff --git a/compiler/forget/src/HIR/InferMutability.ts b/compiler/forget/src/HIR/InferMutability.ts deleted file mode 100644 index dff3bd6dcc..0000000000 --- a/compiler/forget/src/HIR/InferMutability.ts +++ /dev/null @@ -1,676 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import invariant from "invariant"; -import { assertExhaustive } from "../Common/utils"; -import { - BasicBlock, - BlockId, - Capability, - HIRFunction, - IdentifierId, - Instruction, - InstructionValue, - Place, - Terminal, -} from "./HIR"; -import { printMixedHIR, printPlace } from "./PrintHIR"; - -const HOOKS: Map = new Map([ - ["useState", { kind: "State", capability: Capability.Frozen }], - ["useRef", { kind: "Ref", capability: Capability.Frozen }], -]); - -type HookKind = { kind: "State" } | { kind: "Ref" } | { kind: "Custom" }; -type Hook = HookKind & { capability: Capability }; - -/** - * For every usage of a value, infers whether the value is accessed as frozen, mutable, - * or readonly: - * - Frozen: the value is known to be deeply immutable. This usage cannot alter the value, - * and the value cannot change after this usage. The usage can therefore safely memoize - * based on the value. - * - Mutable: the value may be modified by this or a subsequent usage. - * - Readonly: the value is not modified by this or any subsequent usage, but was previously - * mutable. Thus the first usage of a value as readonly (after a previous mutable usage) - * is a point at which the value could safely frozen. - * - * In general usages default to be inferred as readonly (for operations that read) or mutable - * (for operations that may write). Usages can only be inferred as frozen when: - * - The value is derived from a reactive input: props, a hook argument. Derived here means - * that the value is itself a reactive input (`props`) or statically known to be extracted - * purely from a reactive input (`props.a`, `props.a[1]` etc). - * - The value is "moved" into a reactive ouput, ie is captured into a component/hook return - * value. - * - The value is known to be a primitive (boolean, number, string, null, or undefined). This - * is only the case when the value is a constant primitive *or* is the result of an - * operation that, under JS semantics, must produce a primitive. Examples are binary - * expressions. - * - * ## Algorithm - * Fixpoint iteration of the CFG to construct use-use chains (graphs) in which nodes - * are usages of a particular value and the required capability (frozen/read/write), - * and each "usage" node points to predecessor node(s). Iteration stops once no new - * edges/nodes need to be added to the graph. - * - * After construction, walk the graph to propagate frozenness forward and mutability - * backward: - * - If a usage of a value is frozen, then any subsequent usage of that value must be - * frozen as well. Flow the frozenness forward through the graph, following only - * use chains (outoing edges, but not captures). - * - If a usage of a vale is mutable, then any prior readonly references of that value - * must be marked as mutable as well (since there is a subsequent mutation). Again, - * usages only remain readonly if there is no subsequent mutation. - * - Finally, any value that is *initialized* as readonly and never modified (ie, that is - * still readonly in the graph) can be marked frozen (and all of its usages as frozen), - * since it is never ever modified. - */ -export default function inferLifetimes(fn: HIRFunction): Map { - const graph = UseGraph.empty(); - for (const param of fn.params) { - const place: Place = { - kind: "Identifier", - memberPath: null, - value: param, - path: null as any, // TODO - capability: Capability.Frozen, - }; - graph.init(place, null); - } - - const queue: Array = [{ block: fn.body.entry, graph }]; - const blockMemory: Map = new Map(); - while (queue.length !== 0) { - const { block: blockId, graph: inputGraph } = queue.shift()!; - const prevGraph = blockMemory.get(blockId); - let nextGraph = null; - if (prevGraph == null) { - blockMemory.set(blockId, inputGraph); - nextGraph = inputGraph.snapshot(); - } else { - const merged = prevGraph.snapshot(); - const hasChanges = merged.merge(inputGraph); - if (!hasChanges) { - continue; - } - blockMemory.set(blockId, merged); - nextGraph = prevGraph.snapshot(); - } - - const block = fn.body.blocks.get(blockId)!; - inferBlock(nextGraph, block); - - const terminal = block.terminal; - switch (terminal.kind) { - case "throw": { - nextGraph.reference(terminal.value, terminal, Capability.Frozen); - break; - } - case "return": { - if (terminal.value !== null) { - nextGraph.reference(terminal.value, terminal, Capability.Frozen); - } - break; - } - case "goto": { - queue.push({ block: terminal.block, graph: nextGraph }); - break; - } - case "if": { - nextGraph.reference(terminal.test, terminal, Capability.Readonly); - queue.push({ block: terminal.consequent, graph: nextGraph }); - queue.push({ block: terminal.alternate, graph: nextGraph }); - break; - } - case "switch": { - nextGraph.reference(terminal.test, terminal, Capability.Readonly); - for (const case_ of terminal.cases) { - if (case_.test !== null) { - nextGraph.reference(case_.test, terminal, Capability.Readonly); - } - queue.push({ block: case_.block, graph: nextGraph.snapshot() }); - } - break; - } - default: { - assertExhaustive(terminal, "Unexpected terminal kind"); - } - } - } - - // perform inference over the constructed graph and return it - return analyzeGraph(graph); -} - -type QueueEntry = { - block: BlockId; - graph: UseGraph; -}; - -function analyzeGraph(graph: UseGraph): Map { - const vertices = graph.build(); - let epoch = 0; - - // First flow frozen values forward through the graph - for (const vertex of vertices.values()) { - if (vertex.place.capability === Capability.Frozen) { - flowFrozennessForwards(vertex, epoch++); - } - } - // Then flow mutability backward through the graph - for (const vertex of vertices.values()) { - if (vertex.place.capability === Capability.Mutable) { - flowMutabilityBackwards(vertex, epoch++); - } - } - // Finally, any *entry* nodes that are readonly are now known to never be mutated, - // so convert them to frozen (and flow that frozenness forward) - for (const vertex of vertices.values()) { - invariant( - vertex.place.capability !== Capability.Unknown, - "Expected capability to have been inferred as frozen, readonly, or mutable" - ); - if ( - vertex.place.capability === Capability.Readonly && - vertex.incoming.size === 0 - ) { - flowFrozennessForwards(vertex, epoch); - } - } - return vertices; -} - -/** - * Once a value is known to be frozen, all usages forward of that point must be frozen too. - */ -function flowFrozennessForwards(vertex: Vertex, epoch: number) { - if (vertex.epoch === epoch) { - return; - } - vertex.epoch = epoch; - vertex.place.capability = Capability.Frozen; - for (const outgoing of vertex.outgoing) { - flowFrozennessForwards(outgoing, epoch); - } -} - -/** - * If a value is known to be mutable, any previous readonly references must be mutable too. - */ -function flowMutabilityBackwards(vertex: Vertex, epoch: number) { - if (vertex.epoch === epoch) { - return; - } - vertex.epoch = epoch; - if (vertex.place.capability === Capability.Frozen) { - return; - } - vertex.place.capability = Capability.Mutable; - for (const incoming of vertex.incoming) { - flowMutabilityBackwards(incoming, epoch); - } - for (const [id, capture] of vertex.captures) { - flowMutabilityBackwards(capture, epoch); - } -} - -function inferBlock(graph: UseGraph, block: BasicBlock) { - for (const instr of block.instructions) { - if (instr.lvalue !== null) { - if (instr.lvalue.place.memberPath == null) { - graph.init(instr.lvalue.place, instr); - } else { - graph.reference(instr.lvalue.place, instr, Capability.Mutable); - } - } - const instrValue = instr.value; - let valueCapability = Capability.Readonly; - switch (instrValue.kind) { - case "BinaryExpression": { - valueCapability = Capability.Frozen; - graph.reference(instrValue.left, instrValue, Capability.Readonly); - graph.reference(instrValue.right, instrValue, Capability.Readonly); - break; - } - case "ArrayExpression": { - for (const element of instrValue.elements) { - graph.reference(element, instrValue, Capability.Readonly); - if (instr.lvalue !== null) { - graph.capture(instr.lvalue.place, instr, element); - } - } - break; - } - case "NewExpression": { - graph.reference(instrValue.callee, instrValue, Capability.Mutable); - let prevArg: Place | null = null; - for (const arg of instrValue.args) { - graph.reference(arg, instrValue, Capability.Mutable); - if (instr.lvalue !== null) { - graph.capture(instr.lvalue.place, instr, arg); - } - if (prevArg !== null) { - graph.capture(prevArg, instr, arg); - } - prevArg = arg; - } - break; - } - case "CallExpression": { - let capability = Capability.Mutable; - const hook = parseHookCall(instrValue.callee); - if (hook !== null) { - capability = hook.capability; - valueCapability = hook.capability; - } - graph.reference(instrValue.callee, instrValue, capability); - let prevArg: Place | null = null; - for (const arg of instrValue.args) { - graph.reference(arg, instrValue, capability); - if (instr.lvalue !== null) { - graph.capture(instr.lvalue.place, instr, arg); - } - if (prevArg !== null) { - graph.capture(prevArg, instr, arg); - } - prevArg = arg; - } - break; - } - case "ObjectExpression": { - // Object construction captures but does not modify the key/property values - if (instrValue.properties !== null) { - for (const [_key, value] of Object.entries(instrValue.properties)) { - graph.reference(value, instrValue, Capability.Readonly); - if (instr.lvalue !== null) { - graph.capture(instr.lvalue.place, instr, value); - } - } - } - break; - } - case "UnaryExpression": { - valueCapability = Capability.Frozen; // TODO check that value must be a primitive, or make conditional based on the operator - graph.reference(instrValue.value, instrValue, Capability.Readonly); - break; - } - case "OtherStatement": { - // TODO: handle other statement kinds - break; - } - case "JsxExpression": { - graph.reference(instrValue.tag, instrValue, Capability.Readonly); - for (const [_prop, value] of Object.entries(instrValue.props)) { - graph.reference(value, instrValue, Capability.Readonly); - if (instr.lvalue !== null) { - graph.capture(instr.lvalue.place, instr, value); - } - } - if (instrValue.children !== null) { - for (const child of instrValue.children) { - graph.reference(child, instrValue, Capability.Readonly); - if (instr.lvalue !== null) { - graph.capture(instr.lvalue.place, instr, child); - } - } - } - break; - } - case "JSXText": - case "Primitive": { - valueCapability = Capability.Frozen; - break; - } - case "Identifier": { - graph.reference(instrValue, instrValue, Capability.Readonly); - if (instr.lvalue !== null) { - graph.assign(instr.lvalue.place, instr, instrValue); - } - valueCapability = instrValue.capability; - break; - } - default: { - assertExhaustive(instrValue, "Unexpected instruction kind"); - } - } - if (instr.lvalue !== null) { - instr.lvalue.place.capability = valueCapability; - } - } -} - -function parseHookCall(place: Place): Hook | null { - if (place.memberPath !== null) { - // Hook calls must be statically resolved - return null; - } - const name = place.value.name; - if (name === null || !name.match(/^_?use/)) { - return null; - } - const hook = HOOKS.get(name); - if (hook != null) { - return hook; - } - return { kind: "Custom", capability: Capability.Frozen }; -} - -/** - * A graph of usages of references within a program. - */ -class UseGraph { - /** - * Represents the last usage for each top-level identifier (by IdentifierId) - * in the program. So after `let x = []`, there will be an entry mapping - * `x` to a vertex. A subsequent `x.y` reference will update the mapping - * for `x` to point to a new vertex, with the previous `let x = []` as - * an incoming edge. Note again that vertices are always created based - * on the top-level identifier id and ignore member paths. - */ - #nodes: Map; - - /** - * A mapping of places to vertices, allowing repeated operations against - * the same place (eg referencing, assigning, etc) to update the single - * vertex for that place. - * - * NOTE: this algorithm and data structure relies on `Place` instances - * being unique, hence BuildHIR is careful to clone Place instances rather - * than use structural sharing. - */ - #vertices: Map; - - static empty(): UseGraph { - return new UseGraph(new Map(), new Map()); - } - - constructor(nodes: Map, vertices: Map) { - this.#nodes = nodes; - this.#vertices = vertices; - } - - /** - * Lookup the current vertex for a given identifier. - */ - lookup(id: IdentifierId): Vertex | null { - return this.#nodes.get(id) ?? null; - } - - /** - * Represents assignment of a value to a Place. Unlike with `reference()`, - * this does *not* establish an edge between this usage of the place and - * previous usages, because the value is not the same. - */ - init(place: Place, instr: Instruction | null) { - let vertex = this.#vertices.get(place); - if (vertex == null) { - vertex = new Vertex(place, instr); - this.#vertices.set(place, vertex); - } - this.#nodes.set(place.value.id, vertex); - } - - /** - * Represents assigning a (new) value to @param target via the given @param instr, - * with @param value as the value being assigned. - * - * This breaks the use chain, such that this and subsequent usages of @param target - * are not associated to previous usages. This replicates SSA semantics, conceptually - * @param target is a new place now. - */ - assign(target: Place, instr: Instruction, value: Place) { - this.init(target, instr); - if (value.capability === Capability.Frozen) { - target.capability = Capability.Frozen; - } - this.capture(target, instr, value); - } - - /** - * Represents data flow in which two places capture references to each other, - * such that a mutation of one place may affect data accessible via the other - * place or vice versa. This includes assignment (`x = y`) but also things like - * array or object construction, where `x = [y]` means that modifying either - * `x` or `y` _could_ be visible through either refernece (eg `x[0].foo = ...`) - * would modify `y`. - */ - capture(target: Place, instr: Instruction, value: Place) { - const targetVertex = this.#vertices.get(target)!; - const valueVertex = this.#vertices.get(value)!; - - if ( - targetVertex.place.capability === Capability.Frozen || - valueVertex.place.capability === Capability.Frozen - ) { - return; - } - - targetVertex.captures.set(value.value.id, valueVertex); - valueVertex.captures.set(target.value.id, targetVertex); - } - - /** - * Represents a reference (usage of) a Place. This establishes an edge - * with this usage and the previous usage - */ - reference( - place: Place, - instr: Instruction | InstructionValue | Terminal, - capability: Capability - ) { - place.capability = capability; - let vertex = this.#vertices.get(place); - if (vertex == null) { - vertex = new Vertex(place, instr); - this.#vertices.set(place, vertex); - } - - let prev = this.#nodes.get(place.value.id); - if (prev != null) { - vertex.incoming.add(prev); - prev.outgoing.add(vertex); - - for (const [id, _vertex] of prev.captures) { - const capture = this.#nodes.get(id)!; - if (capture.captures.has(place.value.id)) { - vertex.captures.set(id, capture); - } - } - - if (prev.place.capability === Capability.Frozen) { - vertex.place.capability = Capability.Frozen; - } - } - this.#nodes.set(place.value.id, vertex); - } - - /** - * Returns a snapshot of the graph that can be used to represent different - * control flows paths, and which later may merge together (see `merge()`). - * - * The snapshot contains a distinct mapping of the most recent vertex per - * identifier, but *shares* the mapping of places to vertices (vertices are - * stable across different control flow paths). - * - * The intent is that fixpoint iteration relies on updating the `#nodes` - * mapping of identifiers to most recent vertex, but the underlying graph - * of vertices is intended to be shared across control flow paths. - */ - snapshot(): UseGraph { - const nodes = new Map(this.#nodes); - return new UseGraph(nodes, this.#vertices); - } - - /** - * Giver some @param other use graph that shares an ancestor with @param this, - * merges the graphs together and returns whether there were any changes to - * the underlying edges (if any new edges were added). - * - * Note that @param this and @param other must share an ancestor, ie one must - * be derived from a `snapshot()` of the other, or they must both be derived - * from a `snapshot()` of the same instance. - */ - merge(other: UseGraph): boolean { - let hasChange = false; - for (const [id, newIncoming] of other.#nodes) { - const prevIncoming = this.#nodes.get(id); - if (prevIncoming == null || newIncoming === prevIncoming) { - continue; - } - for (const prevOutgoing of prevIncoming.outgoing) { - hasChange = - hasChange || - !prevOutgoing.incoming.has(newIncoming) || - !newIncoming.outgoing.has(prevOutgoing); - prevOutgoing.incoming.add(newIncoming); - newIncoming.outgoing.add(prevOutgoing); - } - } - return hasChange; - } - - /** - * Returns the mapping of places to vertices. - */ - build(): Map { - return this.#vertices; - } -} - -/** - * Represents a distinct usage of a `Place` within a program. Note that - * vertices are only created for top-level identifiers, not for points - * within an object. So both `x` and `x.y` create vertices for `x`, - * though this is enforced by the _construction_ of vertices in UseGraph, - * not the implementation of Vertex itself. - */ -class Vertex { - /** - * A set of vertices whose values are captured into this vertex, keyed - * by the identifier of the captured value. This represents any form - * of capturing, including: - * - direct assignment: `x = y`, `x.y = y`, `x[0] = y` all captures - * x into y and vice-versa. - * - composition of objects into arrays, object, jsx: `{x}{y}`, - * `{k1: x, k2: y}`, and `[x, y]` all capture x and y into the outer - * value (jsx, object, array, respectively). - * - Mutation that could cause values to take references into each other: - * `foo(x, y)` could modify x and/or y in a way that they are assigned - * into each other (eg it could internally do `x.y = y`, `y.x = x` etc). - * - * Note that the representation only stores *one* vertex for each identifier, - * so eg `y.x = x` the `y` vertex can only store a capture of *one* vertex - * for `x`. If there are multiple potentially prior usages of x when this - * occurs, we have to take care that they are both represnted as captured: - * - * ```javascript - * let x; - * if (cond) { - * x = [1]; - * } else { - * x = [2] - * } - * y = x; - * ``` - * The final line must reflect that y captures *either* of the two possible - * values of `x`. This is achieved by *first* using `x`, *then* capturing. - * The usage will create a single new `x` vertex in the final scope, to which - * there are two incoming edges. Then the capture of `x` into `y` references - * that single x vertex with two incoming edges. Thus the data model can capture - * this case, but the order of construction is important. - * - * The result is a graph such as: - * - * [x @ let x] -> [ x @ x = [1] ] -> [ x @ y = x] - * └--> [ x @ x = [2] ] ---┘ ↑ - * | capture - * ↓ - * [ y @ y - x] - * - * Note that it's possible to walk from `y` to both of the possible x values. - */ - captures: Map = new Map(); - - /** - * the set of vertices where *this* place was previously referenced - */ - incoming: Set = new Set(); - - /** - * the set of vertices where *this* place is subseqently referenced - */ - outgoing: Set = new Set(); - - /** - * The place that is using the value. - */ - place: Place; - - /** - * The instruction where the reference occurs (for debugging) - */ - instr: Instruction | InstructionValue | Terminal | null; - - /** - * The last epoch in which this vertex was visited. This is used during - * data-flow analysis post construction of the graph to ensure termination - * by avoiding revisiting the same nodes in a given pass. - */ - epoch: number | null = null; - - constructor( - place: Place, - instr: Instruction | InstructionValue | Terminal | null - ) { - this.place = place; - this.instr = instr; - } -} - -/** - * Prints the graph into GraphViz DOT format. - * https://graphviz.org/doc/info/lang.html - */ -export function printGraph(vertices: Map): string { - const output = []; - - // Maps vertices to short string names. Note that multiple vertex instances - // can share the same Place, so we can't use the place (identifier/path etc). - // Instead we create an auto-incrementing index. - const indices: Map = new Map(); - function identify(vertex: Vertex): string { - let id = indices.get(vertex); - if (id == null) { - id = `v${indices.size}`; - indices.set(vertex, id); - } - return id; - } - - for (const [place, vertex] of vertices) { - const vertexId = identify(vertex); - output.push( - `${vertexId} [ label="${vertexId} (${printPlace(vertex.place)} @ ${ - vertex.instr - ? printMixedHIR(vertex.instr).replaceAll('"', '\\"') - : "" - })", shape="box" ]` - ); - for (const outgoing of vertex.outgoing) { - const outgoingId = identify(outgoing); - output.push(`${vertexId} -> ${outgoingId}`); - } - for (const [id, capture] of vertex.captures) { - const captureId = identify(capture); - output.push(`${captureId} -> ${vertexId} [ label="capture" ]`); - } - } - const lines = output.map((line) => " " + line); - lines.unshift("digraph InferMutability {"); - lines.push("}"); - return lines.join("\n"); -} diff --git a/compiler/forget/src/HIR/InferReferenceCapability.ts b/compiler/forget/src/HIR/InferReferenceCapability.ts new file mode 100644 index 0000000000..c086761b16 --- /dev/null +++ b/compiler/forget/src/HIR/InferReferenceCapability.ts @@ -0,0 +1,705 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { assertExhaustive } from "../Common/utils"; +import { invariant } from "../CompilerError"; +import { + BasicBlock, + BlockId, + Capability, + HIRFunction, + IdentifierId, + Instruction, + InstructionValue, + Place, + Terminal, +} from "./HIR"; +import { mapTerminalSuccessors } from "./HIRBuilder"; +import { printMixedHIR } from "./PrintHIR"; + +/** + * For every usage of a value in the given function, infers the capability or action + * taken at that reference. Each reference is inferred as exactly one of: + * - freeze: this usage freezes the value, ie converts it to frozen. This is only inferred + * when the value *may* not already be frozen. + * - frozen: the value is known to already be "owned" by React and is therefore already + * frozen (permanently and transitively immutable). + * - immutable: the value is not owned by React, but is known to be an immutable value + * that therefore cannot ever change. + * - readonly: the value is not frozen or immutable, but this usage of the value does + * not modify it. the value may be mutated by a subsequent reference. Examples include + * referencing the operands of a binary expression, or referencing the items/properties + * of an array or object literal. + * - mutable: the value is not frozen or immutable, and this usage *may* modify it. + * Examples include passing a value to as a function argument or assigning into an object. + * + * Note that the inference follows variable assignment, so assigning a frozen value + * to a different value will infer usages of the other variable as frozen as well. + * + * The inference assumes that the code follows the rules of React: + * - React function arguments are frozen (component props, hook arguments). + * - Hook arguments are frozen at the point the hook is invoked. + * - React function return values are frozen at the point of being returned, + * thus the return value of a hook call is frozen. + * - JSX represents invocation of a React function (the component) and + * therefore all values passed to JSX become frozen at the point the JSX + * is created. + * + * Internally, the inference tracks the approximate type of value held by each variable, + * and iterates over the control flow graph. The inferred capability of reach reference is + * a combination of the operation performed (ie, assignment into an object mutably uses the + * object; an if condition reads the condition) and the type of the value. The types of values + * are: + * - frozen: can be any type so long as the value is known to be owned by React, permanently + * and transitively immutable + * - maybe-frozen: the value may or may not be frozen, conditionally depending on control flow. + * - immutable: a type with value semantics: primitives, records/tuples when standardized. + * - mutable: a type with reference semantics eg array, object, class instance, etc. + * + * When control flow paths converge the types of values are merged together, with the value + * types forming a lattice to ensure convergence. + */ +export default function inferReferenceCapability(fn: HIRFunction) { + // Initial environment contains function params + // TODO: include module declarations here as well + const initialEnvironment = Environment.empty(); + const id: Place = { + kind: "Identifier", + memberPath: null, + value: fn.id as any, + path: null as any, // TODO + capability: Capability.Freeze, + }; + const value: InstructionValue = { + kind: "Primitive", + path: null as any, // TODO + value: undefined, + }; + initialEnvironment.initialize(value, ValueKind.Frozen); + initialEnvironment.define(id, value); + + for (const param of fn.params) { + const place: Place = { + kind: "Identifier", + memberPath: null, + value: param, + path: null as any, // TODO + capability: Capability.Freeze, + }; + const value: InstructionValue = { + kind: "Primitive", + path: null as any, // TODO + value: undefined, + }; + initialEnvironment.initialize(value, ValueKind.Frozen); + initialEnvironment.define(place, value); + } + + // Queue of blocks to visit, with block and the incoming Environment value + const queue: Array = [ + { blockId: fn.body.entry, environment: initialEnvironment }, + ]; + // Map of blocks to the last incoming environment that was processed + const environmentsByBlock: Map = new Map(); + + while (queue.length !== 0) { + const { blockId, environment: incomingEnvironment } = queue.shift()!; + + let previousEnvironment = environmentsByBlock.get(blockId); + let nextEnvironment: Environment | null = null; + if (previousEnvironment === undefined) { + // If no previous environment, save the incoming environment and + // infer the block with this environment + environmentsByBlock.set(blockId, incomingEnvironment); + nextEnvironment = incomingEnvironment.clone(); + } else { + // If there's a previous environment, merge the previous/new incoming + // environments. If there are no changes, then the block can be skipped. + // otherwise save the merged environment and revisit the block with it. + const mergedEnvironment = previousEnvironment.merge(incomingEnvironment); + if (mergedEnvironment !== null) { + environmentsByBlock.set(blockId, mergedEnvironment); + nextEnvironment = mergedEnvironment.clone(); + } else { + continue; + } + } + + const environment = nextEnvironment; // rebind to preserve the non-null refinement + const block = fn.body.blocks.get(blockId)!; + inferBlock(environment, block); + + // TODO: add a `forEachTerminalSuccessor` helper, we don't actually want the result + // here + const _ = mapTerminalSuccessors( + block.terminal, + (nextBlockId, isFallthrough) => { + if (!isFallthrough) { + queue.push({ blockId: nextBlockId, environment }); + } + return nextBlockId; + } + ); + } +} + +type QueueEntry = { + blockId: BlockId; + environment: Environment; +}; + +/** + * Maintains a mapping of top-level variables to the kind of value they hold + */ +class Environment { + // The kind of reach value, based on its allocation site + #values: Map; + // The set of values pointed to by each identifier. This is a set + // to accomodate phi points (where a variable may have different + // values from different control flow paths). + #variables: Map>; + + constructor( + values: Map, + variables: Map> + ) { + this.#values = values; + this.#variables = variables; + } + + static empty(): Environment { + return new Environment(new Map(), new Map()); + } + + /** + * (Re)initializes a @param value with its default @param kind. + */ + initialize( + value: InstructionValue, + kind: ValueKind, + instr: Instruction | null = null + ) { + invariant( + value.kind !== "Identifier" || value.memberPath !== null, + "Expected all top-level identifiers to be defined as variables, not values" + ); + this.#values.set(value, kind); + } + + /** + * Lookup the kind of the given @param value. + */ + kind(place: Place): ValueKind { + const values = this.#variables.get(place.value.id); + invariant( + values != null, + `Expected value kind to be initialized at '${String(place.path)}'` + ); + let mergedKind: ValueKind | null = null; + for (const value of values) { + const kind = this.#values.get(value)!; + mergedKind = mergedKind !== null ? mergeValues(mergedKind, kind) : kind; + } + invariant(mergedKind !== null, "Expected at least value"); + return mergedKind; + } + + /** + * Updates the value at @param place to point to the same value as @param value. + */ + alias(place: Place, value: Place) { + const values = this.#variables.get(value.value.id); + invariant( + values != null, + `Expected value to be populated at '${String(value.path)}' in '${String( + value.path.parentPath + )}'` + ); + this.#variables.set(place.value.id, new Set(values)); + } + + /** + * Defines (initializing or updating) a variable with a specific kind of value. + */ + define(place: Place, value: InstructionValue) { + invariant( + place.memberPath === null, + "Expected a top-level identifier, not a member path" + ); + invariant( + this.#values.has(value), + `Expected value to be initialized at '${String(value.path)}' in '${String( + value.path?.parentPath + )}'` + ); + this.#variables.set(place.value.id, new Set([value])); + } + + /** + * Records that a given Place was accessed with the given kind and: + * - Updates the capability of @param place based on the kind of value + * and the kind of reference (@param effectKind). + * - Updates the value kind to reflect the effect of the reference. + * + * Notably, a mutable reference is downgraded to readonly if the + * value unless the value is known to be mutable. + * + * Similarly, a freeze reference is converted to readonly if the + * value is already frozen or is immutable. + */ + reference(place: Place, effectKind: EffectKind) { + const values = this.#variables.get(place.value.id); + if (values === undefined) { + place.capability = + effectKind === EffectKind.Write + ? Capability.Mutable + : Capability.Readonly; + return; + } + let capability: Capability | null = null; + switch (effectKind) { + case EffectKind.Freeze: { + values.forEach((value) => { + const valueKind = this.#values.get(value)!; + if ( + valueKind === ValueKind.Mutable || + valueKind === ValueKind.MaybeFrozen + ) { + this.#values.set(value, ValueKind.Frozen); + capability = Capability.Freeze; + } + }); + capability = capability ?? Capability.Readonly; + break; + } + case EffectKind.Write: { + let maybeFrozen = false; + let maybeMutable = false; + values.forEach((value) => { + const valueKind = this.#values.get(value)!; + if ( + valueKind === ValueKind.Frozen || + valueKind === ValueKind.MaybeFrozen + ) { + maybeFrozen = true; + } else if (valueKind === ValueKind.Mutable) { + maybeMutable = true; + } + }); + capability = maybeFrozen + ? Capability.Readonly + : maybeMutable + ? Capability.Mutable + : Capability.Readonly; + break; + } + case EffectKind.Read: { + capability = Capability.Readonly; + break; + } + default: { + assertExhaustive( + effectKind, + `Unexpected reference kind '${effectKind as any as string}'` + ); + } + } + invariant(capability !== null, "Expected capability to be set"); + place.capability = capability; + } + + /** + * Combine the contents of @param this and @param other, returning a new + * instance with the combined changes _if_ there are any changes, or + * returning null if no changes would occur. Changes include: + * - new entries in @param other that did not exist in @param this + * - entries whose values differ in @param this and @param other, + * and where joining the values produces a different value than + * what was in @param this. + * + * Note that values are joined using a lattice operation to ensure + * termination. + */ + merge(other: Environment): Environment | null { + let nextValues: Map | null = null; + let nextVariables: Map> | null = null; + + for (const [id, thisValue] of this.#values) { + const otherValue = other.#values.get(id); + if (otherValue !== undefined) { + const mergedValue = mergeValues(thisValue, otherValue); + if (mergedValue !== thisValue) { + nextValues = nextValues ?? new Map(this.#values); + nextValues.set(id, mergedValue); + } + } + } + for (const [id, otherValue] of other.#values) { + if (this.#values.has(id)) { + // merged above + continue; + } + nextValues = nextValues ?? new Map(this.#values); + nextValues.set(id, otherValue); + } + + for (const [id, thisValues] of this.#variables) { + const otherValues = other.#variables.get(id); + if (otherValues !== undefined) { + let mergedValues: Set | null = null; + for (const otherValue of otherValues) { + if (!thisValues.has(otherValue)) { + mergedValues = mergedValues ?? new Set(thisValues); + mergedValues.add(otherValue); + } + } + if (mergedValues !== null) { + nextVariables = nextVariables ?? new Map(this.#variables); + nextVariables.set(id, mergedValues); + } + } + } + for (const [id, otherValues] of other.#variables) { + if (this.#variables.has(id)) { + continue; + } + nextVariables = nextVariables ?? new Map(this.#variables); + nextVariables.set(id, new Set(otherValues)); + } + + if (nextVariables === null && nextValues === null) { + return null; + } else { + return new Environment( + nextValues ?? new Map(this.#values), + nextVariables ?? new Map(this.#variables) + ); + } + } + + /** + * Returns a copy of this environment. + * TODO: consider using persistent data structures to make + * clone cheaper. + */ + clone(): Environment { + return new Environment(new Map(this.#values), new Map(this.#variables)); + } + + /** + * For debugging purposes, dumps the environment to a plain + * object so that it can printed as JSON. + */ + debug(): any { + const result: any = { values: {}, variables: {} }; + const objects: Map = new Map(); + function identify(value: InstructionValue): number { + let id = objects.get(value); + if (id == null) { + id = objects.size; + objects.set(value, id); + } + return id; + } + for (const [value, kind] of this.#values) { + const id = identify(value); + result.values[id] = { kind, value: printMixedHIR(value) }; + } + for (const [variable, values] of this.#variables) { + result.variables[variable] = [...values].map(identify); + } + return result; + } +} + +/** + * Joins two values using the following rules: + * == Effect Transitions == + * + * Freezing an immutable value has not effect: + * ┌───────────────┐ + * │ │ + * ▼ │ Freeze + * ┌──────────────────────────┐ │ + * │ Immutable │──┘ + * └──────────────────────────┘ + * + * Freezing a mutable or maybe-frozen value makes it frozen. Freezing a frozen + * value has no effect: + * ┌───────────────┐ + * ┌─────────────────────────┐ Freeze │ │ + * │ MaybeFrozen │────┐ ▼ │ Freeze + * └─────────────────────────┘ │ ┌──────────────────────────┐ │ + * ├────▶│ Frozen │──┘ + * │ └──────────────────────────┘ + * ┌─────────────────────────┐ │ + * │ Mutable │────┘ + * └─────────────────────────┘ + * + * == Join Lattice == + * - immutable | frozen => frozen + * - frozen | mutable => maybe-frozen + * - | maybe-frozen => maybe-frozen + * + * ┌──────────────────────────┐ + * │ Immutable │───┐ + * └──────────────────────────┘ │ + * │ ┌─────────────────────────┐ + * ├───▶│ Frozen │──┐ + * ┌──────────────────────────┐ │ └─────────────────────────┘ │ + * │ Frozen │───┤ │ ┌─────────────────────────┐ + * └──────────────────────────┘ │ ├─▶│ MaybeFrozen │ + * │ ┌─────────────────────────┐ │ └─────────────────────────┘ + * ├───▶│ MaybeFrozen │──┘ + * ┌──────────────────────────┐ │ └─────────────────────────┘ + * │ Mutable │───┘ + * └──────────────────────────┘ + */ +function mergeValues(a: ValueKind, b: ValueKind): ValueKind { + if (a === b) { + return a; + } else if (a === ValueKind.MaybeFrozen || b === ValueKind.MaybeFrozen) { + return ValueKind.MaybeFrozen; + // after this a and b differ and neither are MaybeFrozen + } else if (a === ValueKind.Mutable || b === ValueKind.Mutable) { + if (a === ValueKind.Frozen || b === ValueKind.Frozen) { + // frozen | mutable + return ValueKind.MaybeFrozen; + } else { + // mutable | immutable + return ValueKind.Mutable; + } + } else { + // frozen | immutable + return ValueKind.Frozen; + } +} + +/** + * Distinguish between different kinds of values relevant to inference purposes: + * see the main docblock for the module for details. + */ +enum ValueKind { + MaybeFrozen = "MaybeFrozen", + Frozen = "Frozen", + Immutable = "Immutable", + Mutable = "Mutable", +} + +/** + * Distinguish between different kinds of references. + */ +enum EffectKind { + Write = "Write", + Read = "Read", + Freeze = "Freeze", +} + +/** + * Iterates over the given @param block, defining variables and + * recording references on the @param env according to JS semantics. + */ +function inferBlock(env: Environment, block: BasicBlock) { + for (const instr of block.instructions) { + const instrValue = instr.value; + let valueKind: ValueKind; + switch (instrValue.kind) { + case "BinaryExpression": { + valueKind = ValueKind.Immutable; + env.reference(instrValue.left, EffectKind.Read); + env.reference(instrValue.right, EffectKind.Read); + break; + } + case "ArrayExpression": { + valueKind = ValueKind.Mutable; + for (const element of instrValue.elements) { + env.reference(element, EffectKind.Read); + } + break; + } + case "NewExpression": { + valueKind = ValueKind.Mutable; + env.reference(instrValue.callee, EffectKind.Write); + for (const arg of instrValue.args) { + env.reference(arg, EffectKind.Write); + } + break; + } + case "CallExpression": { + let effectKind = EffectKind.Write; + valueKind = ValueKind.Mutable; + const hook = parseHookCall(instrValue.callee); + if (hook !== null) { + effectKind = hook.effectKind; + valueKind = hook.valueKind; + } + env.reference(instrValue.callee, effectKind); + for (const arg of instrValue.args) { + env.reference(arg, effectKind); + } + break; + } + case "ObjectExpression": { + valueKind = ValueKind.Mutable; + // Object construction captures but does not modify the key/property values + if (instrValue.properties !== null) { + for (const [_key, value] of Object.entries(instrValue.properties)) { + env.reference(value, EffectKind.Read); + } + } + break; + } + case "UnaryExpression": { + // TODO check that value must be a primitive, or make conditional based on the operator + valueKind = ValueKind.Immutable; + env.reference(instrValue.value, EffectKind.Read); + break; + } + case "OtherStatement": { + // TODO: handle other statement kinds + valueKind = ValueKind.Mutable; + break; + } + case "JsxExpression": { + valueKind = ValueKind.Frozen; + env.reference(instrValue.tag, EffectKind.Freeze); + for (const [_prop, value] of Object.entries(instrValue.props)) { + env.reference(value, EffectKind.Freeze); + } + if (instrValue.children !== null) { + for (const child of instrValue.children) { + env.reference(child, EffectKind.Freeze); + } + } + break; + } + case "JSXText": + case "Primitive": { + valueKind = ValueKind.Immutable; + break; + } + case "Identifier": { + env.reference(instrValue, EffectKind.Read); + const lvalue = instr.lvalue; + if (lvalue !== null) { + lvalue.place.capability = Capability.Mutable; + if ( + lvalue.place.memberPath === null && + instrValue.memberPath === null + ) { + // direct aliasing: `a = b`; + env.alias(lvalue.place, instrValue); + } else if (lvalue.place.memberPath === null) { + // redefine lvalue: `a = b.c.d` + env.initialize(instrValue, env.kind(instrValue)); + env.define(lvalue.place, instrValue); + } else if (instrValue.memberPath === null) { + // no-op: `a.b.c = d` + env.reference(lvalue.place, EffectKind.Write); + } else { + // no-op: `a.b.c = d.e.f` + env.reference(lvalue.place, EffectKind.Write); + } + } + continue; + } + default: { + assertExhaustive(instrValue, "Unexpected instruction kind"); + } + } + env.initialize(instrValue, valueKind, instr); + if (instr.lvalue !== null) { + if (instr.lvalue.place.memberPath === null) { + env.define(instr.lvalue.place, instrValue); + } else { + env.reference(instr.lvalue.place, EffectKind.Write); + } + instr.lvalue.place.capability = Capability.Mutable; + } + } + switch (block.terminal.kind) { + case "throw": { + env.reference(block.terminal.value, EffectKind.Freeze); + break; + } + case "return": { + if (block.terminal.value !== null) { + env.reference(block.terminal.value, EffectKind.Freeze); + } + break; + } + case "if": { + env.reference(block.terminal.test, EffectKind.Read); + break; + } + case "switch": { + for (const case_ of block.terminal.cases) { + if (case_.test !== null) { + env.reference(case_.test, EffectKind.Read); + } + } + break; + } + case "goto": { + break; + } + default: { + assertExhaustive( + block.terminal, + `Unexpected terminal kind '${(block.terminal as any as Terminal).kind}'` + ); + } + } +} + +const GLOBALS: Map = new Map([ + ["Map", ValueKind.Mutable], + ["Set", ValueKind.Mutable], + ["Math.max", ValueKind.Immutable], +]); + +const HOOKS: Map = new Map([ + [ + "useState", + { + kind: "State", + effectKind: EffectKind.Freeze, + valueKind: ValueKind.Frozen, + }, + ], + [ + "useRef", + { + kind: "Ref", + effectKind: EffectKind.Read, + valueKind: ValueKind.Mutable, + }, + ], +]); + +type HookKind = { kind: "State" } | { kind: "Ref" } | { kind: "Custom" }; +type Hook = HookKind & { effectKind: EffectKind; valueKind: ValueKind }; + +function parseHookCall(place: Place): Hook | null { + if (place.memberPath !== null) { + // Hook calls must be statically resolved + return null; + } + const name = place.value.name; + if (name === null || !name.match(/^_?use/)) { + return null; + } + const hook = HOOKS.get(name); + if (hook != null) { + return hook; + } + return { + kind: "Custom", + effectKind: EffectKind.Freeze, + valueKind: ValueKind.Frozen, + }; +} diff --git a/compiler/forget/src/HIR/ScopeAnalysis.ts b/compiler/forget/src/HIR/ScopeAnalysis.ts index 07c4493203..29ec69fe9b 100644 --- a/compiler/forget/src/HIR/ScopeAnalysis.ts +++ b/compiler/forget/src/HIR/ScopeAnalysis.ts @@ -66,7 +66,7 @@ export default function analyzeScopes(fn: HIRFunction): ReactFunction { kind: "Identifier", value: param, memberPath: null, - capability: Capability.Frozen, + capability: Capability.Freeze, path: null as any, })) ), diff --git a/compiler/forget/src/__tests__/fixtures/hir-svg/component.svg b/compiler/forget/src/__tests__/fixtures/hir-svg/component.svg index 14371d0713..7e95d97dcc 100644 --- a/compiler/forget/src/__tests__/fixtures/hir-svg/component.svg +++ b/compiler/forget/src/__tests__/fixtures/hir-svg/component.svg @@ -4,721 +4,721 @@ - + BuildDefUseGraph - + v0 - -v0 (frozen props$1.items @ frozen props$1.items) <update> + +v0 (freeze props$1.items @ freeze props$1.items) <update> v1 - -v1 (frozen items$2 @ Const frozen items$2 = frozen props$1.items) <assign> + +v1 (freeze items$2 @ Const freeze items$2 = freeze props$1.items) <assign> v0->v1 - - + + v2 - -v2 (frozen props$1.maxItems @ frozen props$1.maxItems) <update> + +v2 (freeze props$1.maxItems @ freeze props$1.maxItems) <update> v0->v2 - - + + v12 - -v12 (frozen items$2 @ If (frozen items$2) then:bb3 else:bb2) <update> + +v12 (freeze items$2 @ If (freeze items$2) then:bb3 else:bb2) <update> v1->v12 - - + + v3 - -v3 (frozen maxItems$3 @ Const frozen maxItems$3 = frozen props$1.maxItems) <assign> + +v3 (freeze maxItems$3 @ Const freeze maxItems$3 = freeze props$1.maxItems) <assign> v2->v3 - - + + v10 - -v10 (frozen maxItems$3 @ Call mutable Math$8.max(mutable $9, frozen maxItems$3)) <update> + +v10 (freeze maxItems$3 @ Call mutable Math$8.max(mutable $9, freeze maxItems$3)) <update> v3->v10 - - + + v4 - -v4 (readonly renderedItems$4 @ Const readonly renderedItems$4 = Array []) <assign> + +v4 (readonly renderedItems$4 @ Const readonly renderedItems$4 = Array []) <assign> v18 - -v18 (readonly renderedItems$4.length @ readonly renderedItems$4.length) <update> + +v18 (readonly renderedItems$4.length @ readonly renderedItems$4.length) <update> v4->v18 - - + + v49 - -v49 (mutable renderedItems$4.push @ Call mutable renderedItems$4.push(mutable $15)) <update> + +v49 (mutable renderedItems$4.push @ Call mutable renderedItems$4.push(mutable $15)) <update> v4->v49 - - + + v62 - -v62 (<join> @ <no-instr>) <update> + +v62 (<join> @ <no-instr>) <update> v4->v62 - - + + v71 - -v71 (<join> @ <no-instr>) <update> + +v71 (<join> @ <no-instr>) <update> v4->v71 - - + + v19 - -v19 (readonly count$17 @ Const readonly count$17 = readonly renderedItems$4.length) <assign> + +v19 (readonly count$17 @ Const readonly count$17 = readonly renderedItems$4.length) <assign> v18->v19 - - + + v34 - -v34 (frozen renderedItems$4 @ JSX <frozen $18>{frozen $19}{frozen $22}{frozen $23}{frozen renderedItems$4}{frozen $24}</frozen $18>) <update> + +v34 (freeze renderedItems$4 @ JSX <freeze $18>{freeze $19}{freeze $22}{freeze $23}{freeze renderedItems$4}{freeze $24}</freeze $18>) <update> v18->v34 - - + + v51 - -v51 (readonly renderedItems$4.length @ Binary readonly renderedItems$4.length >= readonly max$7) <update> + +v51 (readonly renderedItems$4.length @ Binary readonly renderedItems$4.length >= readonly max$7) <update> v49->v51 - - + + v62->v18 - - + + v71->v49 - - + + v5 - -v5 (mutable Set$6 @ New mutable Set$6()) <update> + +v5 (mutable Set$6 @ New mutable Set$6()) <update> v6 - -v6 (readonly seen$5 @ Const readonly seen$5 = New mutable Set$6()) <assign> + +v6 (readonly seen$5 @ Const readonly seen$5 = New mutable Set$6()) <assign> v39 - -v39 (mutable seen$5.has @ Call mutable seen$5.has(frozen item$10)) <update> + +v39 (mutable seen$5.has @ Call mutable seen$5.has(freeze item$10)) <update> v6->v39 - - + + v56 - -v56 (<join> @ <no-instr>) <update> + +v56 (<join> @ <no-instr>) <update> v6->v56 - - + + v59 - -v59 (<join> @ <no-instr>) <update> + +v59 (<join> @ <no-instr>) <update> v6->v59 - - + + v39->v56 - - + + v39->v59 - - + + v43 - -v43 (mutable seen$5.add @ Call mutable seen$5.add(frozen item$10)) <update> + +v43 (mutable seen$5.add @ Call mutable seen$5.add(freeze item$10)) <update> v56->v43 - - + + v59->v39 - - + + v7 - -v7 (readonly $9 @ Const readonly $9 = 0) <assign> + +v7 (readonly $9 @ Const readonly $9 = 0) <assign> v9 - -v9 (mutable $9 @ Call mutable Math$8.max(mutable $9, frozen maxItems$3)) <update> + +v9 (mutable $9 @ Call mutable Math$8.max(mutable $9, freeze maxItems$3)) <update> v7->v9 - - + + v8 - -v8 (mutable Math$8.max @ Call mutable Math$8.max(mutable $9, frozen maxItems$3)) <update> + +v8 (mutable Math$8.max @ Call mutable Math$8.max(mutable $9, freeze maxItems$3)) <update> v11 - -v11 (readonly max$7 @ Const readonly max$7 = Call mutable Math$8.max(mutable $9, frozen maxItems$3)) <assign> + +v11 (readonly max$7 @ Const readonly max$7 = Call mutable Math$8.max(mutable $9, freeze maxItems$3)) <assign> v52 - -v52 (readonly max$7 @ Binary readonly renderedItems$4.length >= readonly max$7) <update> + +v52 (readonly max$7 @ Binary readonly renderedItems$4.length >= readonly max$7) <update> v11->v52 - - + + v72 - -v72 (<join> @ <no-instr>) <update> + +v72 (<join> @ <no-instr>) <update> v11->v72 - - + + v52->v72 - - + + v72->v52 - - + + v13 - -v13 (readonly $11 @ Const readonly $11 = null) <assign> + +v13 (readonly $11 @ Const readonly $11 = null) <assign> v15 - -v15 (readonly $11 @ Binary frozen item$10 == readonly $11) <update> + +v15 (readonly $11 @ Binary freeze item$10 == readonly $11) <update> v13->v15 - - + + v14 - -v14 (frozen item$10 @ Binary frozen item$10 == readonly $11) <update> + +v14 (freeze item$10 @ Binary freeze item$10 == readonly $11) <update> v40 - -v40 (frozen item$10 @ Call mutable seen$5.has(frozen item$10)) <update> + +v40 (freeze item$10 @ Call mutable seen$5.has(freeze item$10)) <update> v14->v40 - - + + v57 - -v57 (<join> @ <no-instr>) <update> + +v57 (<join> @ <no-instr>) <update> v14->v57 - - + + v40->v57 - - + + v57->v14 - - + + v44 - -v44 (frozen item$10 @ Call mutable seen$5.add(frozen item$10)) <update> + +v44 (freeze item$10 @ Call mutable seen$5.add(freeze item$10)) <update> v57->v44 - - + + v16 - -v16 (frozen $12 @ Const frozen $12 = Binary frozen item$10 == readonly $11) <assign> + +v16 (freeze $12 @ Const freeze $12 = Binary freeze item$10 == readonly $11) <assign> v17 - -v17 (frozen $12 @ If (frozen $12) then:bb8 else:bb9) <update> + +v17 (freeze $12 @ If (freeze $12) then:bb8 else:bb9) <update> v16->v17 - - + + v38 - -v38 (frozen $13 @ Const frozen $13 = frozen $12) <assign> + +v38 (freeze $13 @ Const freeze $13 = freeze $12) <assign> v17->v38 - - + + v42 - -v42 (frozen $13 @ If (frozen $13) then:bb1 else:bb4) <update> + +v42 (freeze $13 @ If (freeze $13) then:bb1 else:bb4) <update> v38->v42 - - + + v25 - -v25 (frozen count$17 @ JSX <frozen $20>{frozen count$17}{frozen $21}</frozen $20>) <update> + +v25 (freeze count$17 @ JSX <freeze $20>{freeze count$17}{freeze $21}</freeze $20>) <update> v19->v25 - - + + v20 - -v20 (readonly $18 @ Const readonly $18 = "div") <assign> + +v20 (readonly $18 @ Const readonly $18 = "div") <assign> v30 - -v30 (frozen $18 @ JSX <frozen $18>{frozen $19}{frozen $22}{frozen $23}{frozen renderedItems$4}{frozen $24}</frozen $18>) <update> + +v30 (freeze $18 @ JSX <freeze $18>{freeze $19}{freeze $22}{freeze $23}{freeze renderedItems$4}{freeze $24}</freeze $18>) <update> v20->v30 - - + + v21 - -v21 (readonly $19 @ Const readonly $19 = " -      ") <assign> + +v21 (readonly $19 @ Const readonly $19 = " +      ") <assign> v31 - -v31 (frozen $19 @ JSX <frozen $18>{frozen $19}{frozen $22}{frozen $23}{frozen renderedItems$4}{frozen $24}</frozen $18>) <update> + +v31 (freeze $19 @ JSX <freeze $18>{freeze $19}{freeze $22}{freeze $23}{freeze renderedItems$4}{freeze $24}</freeze $18>) <update> v21->v31 - - + + v22 - -v22 (readonly $20 @ Const readonly $20 = "h1") <assign> + +v22 (readonly $20 @ Const readonly $20 = "h1") <assign> v24 - -v24 (frozen $20 @ JSX <frozen $20>{frozen count$17}{frozen $21}</frozen $20>) <update> + +v24 (freeze $20 @ JSX <freeze $20>{freeze count$17}{freeze $21}</freeze $20>) <update> v22->v24 - - + + v23 - -v23 (readonly $21 @ Const readonly $21 = " Items") <assign> + +v23 (readonly $21 @ Const readonly $21 = " Items") <assign> v26 - -v26 (frozen $21 @ JSX <frozen $20>{frozen count$17}{frozen $21}</frozen $20>) <update> + +v26 (freeze $21 @ JSX <freeze $20>{freeze count$17}{freeze $21}</freeze $20>) <update> v23->v26 - - + + v27 - -v27 (readonly $22 @ Const readonly $22 = JSX <frozen $20>{frozen count$17}{frozen $21}</frozen $20>) <assign> + +v27 (readonly $22 @ Const readonly $22 = JSX <freeze $20>{freeze count$17}{freeze $21}</freeze $20>) <assign> v32 - -v32 (frozen $22 @ JSX <frozen $18>{frozen $19}{frozen $22}{frozen $23}{frozen renderedItems$4}{frozen $24}</frozen $18>) <update> + +v32 (freeze $22 @ JSX <freeze $18>{freeze $19}{freeze $22}{freeze $23}{freeze renderedItems$4}{freeze $24}</freeze $18>) <update> v27->v32 - - + + v28 - -v28 (readonly $23 @ Const readonly $23 = " -      ") <assign> + +v28 (readonly $23 @ Const readonly $23 = " +      ") <assign> v33 - -v33 (frozen $23 @ JSX <frozen $18>{frozen $19}{frozen $22}{frozen $23}{frozen renderedItems$4}{frozen $24}</frozen $18>) <update> + +v33 (freeze $23 @ JSX <freeze $18>{freeze $19}{freeze $22}{freeze $23}{freeze renderedItems$4}{freeze $24}</freeze $18>) <update> v28->v33 - - + + v29 - -v29 (readonly $24 @ Const readonly $24 = " -    ") <assign> + +v29 (readonly $24 @ Const readonly $24 = " +    ") <assign> v35 - -v35 (frozen $24 @ JSX <frozen $18>{frozen $19}{frozen $22}{frozen $23}{frozen renderedItems$4}{frozen $24}</frozen $18>) <update> + +v35 (freeze $24 @ JSX <freeze $18>{freeze $19}{freeze $22}{freeze $23}{freeze renderedItems$4}{freeze $24}</freeze $18>) <update> v29->v35 - - + + v36 - -v36 (readonly $25 @ Const readonly $25 = JSX <frozen $18>{frozen $19}{frozen $22}{frozen $23}{frozen renderedItems$4}{frozen $24}</frozen $18>) <assign> + +v36 (readonly $25 @ Const readonly $25 = JSX <freeze $18>{freeze $19}{freeze $22}{freeze $23}{freeze renderedItems$4}{freeze $24}</freeze $18>) <assign> v37 - -v37 (frozen $25 @ Return frozen $25) <update> + +v37 (freeze $25 @ Return freeze $25) <update> v36->v37 - - + + v41 - -v41 (readonly $13 @ Const readonly $13 = Call mutable seen$5.has(frozen item$10)) <assign> + +v41 (readonly $13 @ Const readonly $13 = Call mutable seen$5.has(freeze item$10)) <assign> v41->v42 - - + + v43->v56 - - + + v43->v59 - - + + v47 - -v47 (frozen item$10 @ JSX <frozen $14>{frozen item$10}</frozen $14>) <update> + +v47 (freeze item$10 @ JSX <freeze $14>{freeze item$10}</freeze $14>) <update> v44->v47 - - + + v47->v57 - - + + v45 - -v45 (readonly $14 @ Const readonly $14 = "div") <assign> + +v45 (readonly $14 @ Const readonly $14 = "div") <assign> v46 - -v46 (frozen $14 @ JSX <frozen $14>{frozen item$10}</frozen $14>) <update> + +v46 (freeze $14 @ JSX <freeze $14>{freeze item$10}</freeze $14>) <update> v45->v46 - - + + v48 - -v48 (readonly $15 @ Const readonly $15 = JSX <frozen $14>{frozen item$10}</frozen $14>) <assign> + +v48 (readonly $15 @ Const readonly $15 = JSX <freeze $14>{freeze item$10}</freeze $14>) <assign> v50 - -v50 (mutable $15 @ Call mutable renderedItems$4.push(mutable $15)) <update> + +v50 (mutable $15 @ Call mutable renderedItems$4.push(mutable $15)) <update> v48->v50 - - + + v51->v18 - - + + v51->v62 - - + + v51->v71 - - + + v53 - -v53 (frozen $16 @ Const frozen $16 = Binary readonly renderedItems$4.length >= readonly max$7) <assign> + +v53 (freeze $16 @ Const freeze $16 = Binary readonly renderedItems$4.length >= readonly max$7) <assign> v54 - -v54 (frozen $16 @ If (frozen $16) then:bb2 else:bb1) <update> + +v54 (freeze $16 @ If (freeze $16) then:bb2 else:bb1) <update> v53->v54 - - + + v55 - -v55 (frozen props$1 @ <no-instr>) <assign> + +v55 (freeze props$1 @ <no-instr>) <assign> v55->v0 - - + + diff --git a/compiler/forget/src/__tests__/fixtures/hir-svg/switch.svg b/compiler/forget/src/__tests__/fixtures/hir-svg/switch.svg index 9c213d390c..6022b483e5 100644 --- a/compiler/forget/src/__tests__/fixtures/hir-svg/switch.svg +++ b/compiler/forget/src/__tests__/fixtures/hir-svg/switch.svg @@ -4,70 +4,70 @@ - + BuildDefUseGraph - + v0 - -v0 (readonly x$2 @ Let readonly x$2 = Array []) <assign> + +v0 (readonly x$2 @ Let readonly x$2 = Array []) <assign> v7 - -v7 (mutable x$2.push @ Call mutable x$2.push(frozen props$1.p2)) <update> + +v7 (mutable x$2.push @ Call mutable x$2.push(freeze props$1.p2)) <update> v0->v7 - - + + v12 - -v12 (readonly x$2 @ readonly x$2) <update> + +v12 (readonly x$2 @ readonly x$2) <update> v0->v12 - - + + v15 - -v15 (frozen x$2 @ JSX <frozen Component$0 data={frozen x$2} ></frozen Component$0>) <update> + +v15 (freeze x$2 @ JSX <freeze Component$0 data={freeze x$2} ></freeze Component$0>) <update> v0->v15 - - + + v9 - -v9 (mutable x$2.push @ Call mutable x$2.push(frozen props$1.p3)) <update> + +v9 (mutable x$2.push @ Call mutable x$2.push(freeze props$1.p3)) <update> v7->v9 - - + + v12->v15 - - + + @@ -78,224 +78,224 @@ v12->v13 - - + + v1 - -v1 (readonly y$3 @ Let readonly y$3 = undefined) <assign> + +v1 (readonly y$3 @ Let readonly y$3 = undefined) <assign> v17 - -v17 (mutable y$3.push @ Call mutable y$3.push(frozen props$1.p4)) <update> + +v17 (mutable y$3.push @ Call mutable y$3.push(freeze props$1.p4)) <update> v1->v17 - - + + v20 - -v20 (frozen y$3 @ JSX <frozen Component$0 data={frozen y$3} >{frozen child$6}</frozen Component$0>) <update> + +v20 (freeze y$3 @ JSX <freeze Component$0 data={freeze y$3} >{freeze child$6}</freeze Component$0>) <update> v17->v20 - - + + v2 - -v2 (readonly $4 @ Const readonly $4 = false) <assign> + +v2 (readonly $4 @ Const readonly $4 = false) <assign> v6 - -v6 (readonly $4 @ Switch (frozen props$1.p0);   Case readonly $5: bb4;   Case readonly $4: bb2;   Default: bb1) <update> + +v6 (readonly $4 @ Switch (freeze props$1.p0);   Case readonly $5: bb4;   Case readonly $4: bb2;   Default: bb1) <update> v2->v6 - - + + v3 - -v3 (readonly $5 @ Const readonly $5 = true) <assign> + +v3 (readonly $5 @ Const readonly $5 = true) <assign> v5 - -v5 (readonly $5 @ Switch (frozen props$1.p0);   Case readonly $5: bb4;   Case readonly $4: bb2;   Default: bb1) <update> + +v5 (readonly $5 @ Switch (freeze props$1.p0);   Case readonly $5: bb4;   Case readonly $4: bb2;   Default: bb1) <update> v3->v5 - - + + v4 - -v4 (frozen props$1.p0 @ Switch (frozen props$1.p0);   Case readonly $5: bb4;   Case readonly $4: bb2;   Default: bb1) <update> + +v4 (freeze props$1.p0 @ Switch (freeze props$1.p0);   Case readonly $5: bb4;   Case readonly $4: bb2;   Default: bb1) <update> v8 - -v8 (frozen props$1.p2 @ Call mutable x$2.push(frozen props$1.p2)) <update> + +v8 (freeze props$1.p2 @ Call mutable x$2.push(freeze props$1.p2)) <update> v4->v8 - - + + v18 - -v18 (frozen props$1.p4 @ Call mutable y$3.push(frozen props$1.p4)) <update> + +v18 (freeze props$1.p4 @ Call mutable y$3.push(freeze props$1.p4)) <update> v4->v18 - - + + v25 - -v25 (<join> @ <no-instr>) <update> + +v25 (<join> @ <no-instr>) <update> v4->v25 - - + + v10 - -v10 (frozen props$1.p3 @ Call mutable x$2.push(frozen props$1.p3)) <update> + +v10 (freeze props$1.p3 @ Call mutable x$2.push(freeze props$1.p3)) <update> v8->v10 - - + + v25->v18 - - + + v9->v12 - - + + v10->v25 - - + + v11 - -v11 (readonly y$3 @ Reassign readonly y$3 = Array []) <assign> + +v11 (readonly y$3 @ Reassign readonly y$3 = Array []) <assign> v13->v17 - - + + v14 - -v14 (frozen Component$0 @ JSX <frozen Component$0 data={frozen x$2} ></frozen Component$0>) <update> + +v14 (freeze Component$0 @ JSX <freeze Component$0 data={freeze x$2} ></freeze Component$0>) <update> v19 - -v19 (frozen Component$0 @ JSX <frozen Component$0 data={frozen y$3} >{frozen child$6}</frozen Component$0>) <update> + +v19 (freeze Component$0 @ JSX <freeze Component$0 data={freeze y$3} >{freeze child$6}</freeze Component$0>) <update> v14->v19 - - + + v16 - -v16 (readonly child$6 @ Const readonly child$6 = JSX <frozen Component$0 data={frozen x$2} ></frozen Component$0>) <assign> + +v16 (readonly child$6 @ Const readonly child$6 = JSX <freeze Component$0 data={freeze x$2} ></freeze Component$0>) <assign> v21 - -v21 (frozen child$6 @ JSX <frozen Component$0 data={frozen y$3} >{frozen child$6}</frozen Component$0>) <update> + +v21 (freeze child$6 @ JSX <freeze Component$0 data={freeze y$3} >{freeze child$6}</freeze Component$0>) <update> v16->v21 - - + + v22 - -v22 (readonly $7 @ Const readonly $7 = JSX <frozen Component$0 data={frozen y$3} >{frozen child$6}</frozen Component$0>) <assign> + +v22 (readonly $7 @ Const readonly $7 = JSX <freeze Component$0 data={freeze y$3} >{freeze child$6}</freeze Component$0>) <assign> v23 - -v23 (frozen $7 @ Return frozen $7) <update> + +v23 (freeze $7 @ Return freeze $7) <update> v22->v23 - - + + v24 - -v24 (frozen props$1 @ <no-instr>) <assign> + +v24 (freeze props$1 @ <no-instr>) <assign> v24->v4 - - + + diff --git a/compiler/forget/src/__tests__/fixtures/hir/call.expect.md b/compiler/forget/src/__tests__/fixtures/hir/call.expect.md index 05808f35db..8f53259353 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/call.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/call.expect.md @@ -34,15 +34,15 @@ function foo$0() { ``` bb0: - Const readonly a$2 = Array [] - Const readonly b$3 = Object { } + Const mutable a$2 = Array [] + Const mutable b$3 = Object { } Call mutable foo$4(mutable a$2, mutable b$3) - Const readonly $6 = "div" - Let readonly _$5 = JSX + Const mutable $6 = "div" + Let mutable _$5 = JSX Call mutable foo$4(mutable b$3) - Const readonly $7 = "div" - Const readonly $8 = JSX - Return frozen $8 + Const mutable $7 = "div" + Const mutable $8 = JSX + Return readonly $8 ``` ## Code diff --git a/compiler/forget/src/__tests__/fixtures/hir/component.expect.md b/compiler/forget/src/__tests__/fixtures/hir/component.expect.md index 279301828d..95a1623d6b 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/component.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/component.expect.md @@ -35,45 +35,45 @@ function Component(props) { ``` bb0: - Const frozen items$2 = frozen props$1.items - Const frozen maxItems$3 = frozen props$1.maxItems - Const readonly renderedItems$4 = Array [] - Const readonly seen$5 = New mutable Set$6() - Const readonly $9 = 0 - Const readonly max$7 = Call mutable Math$8.max(mutable $9, frozen maxItems$3) + Const mutable items$2 = readonly props$1.items + Const mutable maxItems$3 = readonly props$1.maxItems + Const mutable renderedItems$4 = Array [] + Const mutable seen$5 = New mutable Set$6() + Const mutable $9 = 0 + Const mutable max$7 = Call mutable Math$8.max(readonly $9, readonly maxItems$3) Goto bb1 bb1: - If (frozen items$2) then:bb3 else:bb2 + If (readonly items$2) then:bb3 else:bb2 bb3: - Const readonly $11 = null - Const frozen $12 = Binary frozen item$10 == readonly $11 - If (frozen $12) then:bb8 else:bb9 + Const mutable $11 = null + Const mutable $12 = Binary readonly item$10 == readonly $11 + If (readonly $12) then:bb8 else:bb9 bb2: - Const readonly count$17 = readonly renderedItems$4.length - Const readonly $18 = "div" - Const readonly $19 = "\n " - Const readonly $20 = "h1" - Const readonly $21 = " Items" - Const readonly $22 = JSX {frozen count$17}{frozen $21} - Const readonly $23 = "\n " - Const readonly $24 = "\n " - Const readonly $25 = JSX {frozen $19}{frozen $22}{frozen $23}{frozen renderedItems$4}{frozen $24} - Return frozen $25 + Const mutable count$17 = readonly renderedItems$4.length + Const mutable $18 = "div" + Const mutable $19 = "\n " + Const mutable $20 = "h1" + Const mutable $21 = " Items" + Const mutable $22 = JSX {freeze count$17}{readonly $21} + Const mutable $23 = "\n " + Const mutable $24 = "\n " + Const mutable $25 = JSX {readonly $19}{readonly $22}{readonly $23}{freeze renderedItems$4}{readonly $24} + Return readonly $25 bb8: - Const frozen $13 = frozen $12 + Const mutable $13 = readonly $12 Goto bb7 bb9: - Const readonly $13 = Call mutable seen$5.has(frozen item$10) + Const mutable $13 = Call mutable seen$5.has(mutable item$10) Goto bb7 bb7: - If (frozen $13) then:bb1 else:bb4 + If (readonly $13) then:bb1 else:bb4 bb4: - Call mutable seen$5.add(frozen item$10) - Const readonly $14 = "div" - Const readonly $15 = JSX {frozen item$10} - Call mutable renderedItems$4.push(mutable $15) - Const frozen $16 = Binary readonly renderedItems$4.length >= readonly max$7 - If (frozen $16) then:bb2 else:bb1 + Call mutable seen$5.add(mutable item$10) + Const mutable $14 = "div" + Const mutable $15 = JSX {readonly item$10} + Call mutable renderedItems$4.push(readonly $15) + Const mutable $16 = Binary readonly renderedItems$4.length >= readonly max$7 + If (readonly $16) then:bb2 else:bb1 ``` ## Code diff --git a/compiler/forget/src/__tests__/fixtures/hir/conditional-break.expect.md b/compiler/forget/src/__tests__/fixtures/hir/conditional-break.expect.md index 703b59f405..f656d1c9af 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/conditional-break.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/conditional-break.expect.md @@ -78,15 +78,15 @@ function Component(props) { ``` bb0: - Const readonly a_DEBUG$2 = Array [] - Call mutable a_DEBUG$2.push(frozen props$1.a) - If (frozen props$1.b) then:bb2 else:bb1 + Const mutable a_DEBUG$2 = Array [] + Call mutable a_DEBUG$2.push(readonly props$1.a) + If (readonly props$1.b) then:bb2 else:bb1 bb2: - Const readonly $3 = null - Return frozen $3 + Const mutable $3 = null + Return readonly $3 bb1: - Call mutable a_DEBUG$2.push(frozen props$1.d) - Return frozen a_DEBUG$2 + Call mutable a_DEBUG$2.push(readonly props$1.d) + Return freeze a_DEBUG$2 ``` ## Code @@ -107,15 +107,15 @@ function Component$0(props$1) { ``` bb0: - Const readonly a$2 = Array [] - Call mutable a$2.push(frozen props$1.a) - If (frozen props$1.b) then:bb2 else:bb1 + Const mutable a$2 = Array [] + Call mutable a$2.push(readonly props$1.a) + If (readonly props$1.b) then:bb2 else:bb1 bb2: - Call mutable a$2.push(frozen props$1.c) + Call mutable a$2.push(readonly props$1.c) Goto bb1 bb1: - Call mutable a$2.push(frozen props$1.d) - Return frozen a$2 + Call mutable a$2.push(readonly props$1.d) + Return freeze a$2 ``` ## Code @@ -137,16 +137,16 @@ function Component$0(props$1) { ``` bb0: - Const readonly a$2 = Array [] - Call mutable a$2.push(frozen props$1.a) - If (frozen props$1.b) then:bb2 else:bb1 + Const mutable a$2 = Array [] + Call mutable a$2.push(readonly props$1.a) + If (readonly props$1.b) then:bb2 else:bb1 bb2: - Call mutable a$2.push(frozen props$1.c) - Const readonly $3 = null - Return frozen $3 + Call mutable a$2.push(readonly props$1.c) + Const mutable $3 = null + Return readonly $3 bb1: - Call mutable a$2.push(frozen props$1.d) - Return frozen a$2 + Call mutable a$2.push(readonly props$1.d) + Return freeze a$2 ``` ## Code @@ -168,15 +168,15 @@ function Component$0(props$1) { ``` bb0: - Const readonly a$2 = Array [] - Call mutable a$2.push(frozen props$1.a) - If (frozen props$1.b) then:bb2 else:bb1 + Const mutable a$2 = Array [] + Call mutable a$2.push(readonly props$1.a) + If (readonly props$1.b) then:bb2 else:bb1 bb2: - Call mutable a$2.push(frozen props$1.c) - Return frozen a$2 + Call mutable a$2.push(readonly props$1.c) + Return freeze a$2 bb1: - Call mutable a$2.push(frozen props$1.d) - Return frozen a$2 + Call mutable a$2.push(readonly props$1.d) + Return freeze a$2 ``` ## Code @@ -198,14 +198,14 @@ function Component$0(props$1) { ``` bb0: - Const readonly a$2 = Array [] - Call mutable a$2.push(frozen props$1.a) - If (frozen props$1.b) then:bb1 else:bb2 + Const mutable a$2 = Array [] + Call mutable a$2.push(readonly props$1.a) + If (readonly props$1.b) then:bb1 else:bb2 bb1: - Call mutable a$2.push(frozen props$1.d) - Return frozen a$2 + Call mutable a$2.push(readonly props$1.d) + Return freeze a$2 bb2: - Call mutable a$2.push(frozen props$1.c) + Call mutable a$2.push(readonly props$1.c) Goto bb1 ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/conditional-on-mutable.expect.md b/compiler/forget/src/__tests__/fixtures/hir/conditional-on-mutable.expect.md index 2cc637df05..c9cdd582d0 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/conditional-on-mutable.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/conditional-on-mutable.expect.md @@ -67,20 +67,20 @@ function mayMutate() {} ``` bb0: - Const readonly a$2 = Array [] - Const readonly b$3 = Array [] + Const mutable a$2 = Array [] + Const mutable b$3 = Array [] If (readonly b$3) then:bb2 else:bb1 bb2: - Call mutable a$2.push(frozen props$1.p0) + Call mutable a$2.push(readonly props$1.p0) Goto bb1 bb1: - If (frozen props$1.p1) then:bb4 else:bb3 + If (readonly props$1.p1) then:bb4 else:bb3 bb4: - Call mutable b$3.push(frozen props$1.p2) + Call mutable b$3.push(readonly props$1.p2) Goto bb3 bb3: - Const readonly $5 = JSX - Return frozen $5 + Const mutable $5 = JSX + Return readonly $5 ``` ## Code @@ -105,21 +105,21 @@ function Component$0(props$1) { ``` bb0: - Const readonly a$2 = Array [] - Const readonly b$3 = Array [] - Const readonly $5 = Call mutable mayMutate$4(mutable b$3) + Const mutable a$2 = Array [] + Const mutable b$3 = Array [] + Const mutable $5 = Call mutable mayMutate$4(mutable b$3) If (readonly $5) then:bb2 else:bb1 bb2: - Call mutable a$2.push(frozen props$1.p0) + Call mutable a$2.push(readonly props$1.p0) Goto bb1 bb1: - If (frozen props$1.p1) then:bb4 else:bb3 + If (readonly props$1.p1) then:bb4 else:bb3 bb4: - Call mutable b$3.push(frozen props$1.p2) + Call mutable b$3.push(readonly props$1.p2) Goto bb3 bb3: - Const readonly $7 = JSX - Return frozen $7 + Const mutable $7 = JSX + Return readonly $7 ``` ## Code diff --git a/compiler/forget/src/__tests__/fixtures/hir/constructor.expect.md b/compiler/forget/src/__tests__/fixtures/hir/constructor.expect.md index 045650a9ef..82330df8a8 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/constructor.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/constructor.expect.md @@ -34,15 +34,15 @@ function Foo$0() { ``` bb0: - Const readonly a$2 = Array [] - Const readonly b$3 = Object { } + Const mutable a$2 = Array [] + Const mutable b$3 = Object { } New mutable Foo$4(mutable a$2, mutable b$3) - Const readonly $6 = "div" - Let readonly _$5 = JSX + Const mutable $6 = "div" + Let mutable _$5 = JSX New mutable Foo$4(mutable b$3) - Const readonly $7 = "div" - Const readonly $8 = JSX - Return frozen $8 + Const mutable $7 = "div" + Const mutable $8 = JSX + Return readonly $8 ``` ## Code diff --git a/compiler/forget/src/__tests__/fixtures/hir/frozen-after-alias.expect.md b/compiler/forget/src/__tests__/fixtures/hir/frozen-after-alias.expect.md new file mode 100644 index 0000000000..ae649abbae --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/frozen-after-alias.expect.md @@ -0,0 +1,70 @@ + +## Input + +```javascript +function Component() { + const a = []; + const b = a; + useFreeze(a); + foo(b); // should be readonly, value is guaranteed frozen via alias +} + +function useFreeze() {} +function foo(x) {} + +``` + +## HIR + +``` +bb0: + Const mutable a$1 = Array [] + Const mutable b$2 = readonly a$1 + Call readonly useFreeze$3(freeze a$1) + Call mutable foo$4(readonly b$2) + Return +``` + +## Code + +```javascript +function Component$0() { + const a$1 = []; + const b$2 = a$1; + useFreeze$3(a$1); + foo$4(b$2); + return; +} + +``` +## HIR + +``` +bb0: + Return +``` + +## Code + +```javascript +function useFreeze$0() { + return; +} + +``` +## HIR + +``` +bb0: + Return +``` + +## Code + +```javascript +function foo$0(x$1) { + return; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/frozen-after-alias.js b/compiler/forget/src/__tests__/fixtures/hir/frozen-after-alias.js new file mode 100644 index 0000000000..0f240a4205 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/frozen-after-alias.js @@ -0,0 +1,9 @@ +function Component() { + const a = []; + const b = a; + useFreeze(a); + foo(b); // should be readonly, value is guaranteed frozen via alias +} + +function useFreeze() {} +function foo(x) {} diff --git a/compiler/forget/src/__tests__/fixtures/hir/hook-call.expect.md b/compiler/forget/src/__tests__/fixtures/hir/hook-call.expect.md index 7ac2ad574c..d0a1dfe012 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/hook-call.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/hook-call.expect.md @@ -53,14 +53,14 @@ function foo$0() { ``` bb0: - Const readonly x$2 = Array [] - Const frozen y$3 = Call frozen useFreeze$4(frozen x$2) - Call mutable foo$5(frozen y$3, frozen x$2) - Const readonly $6 = "\n " - Const readonly $7 = "\n " - Const readonly $8 = "\n " - Const readonly $9 = JSX {frozen $6}{frozen x$2}{frozen $7}{frozen y$3}{frozen $8} - Return frozen $9 + Const mutable x$2 = Array [] + Const mutable y$3 = Call readonly useFreeze$4(freeze x$2) + Call mutable foo$5(readonly y$3, readonly x$2) + Const mutable $6 = "\n " + Const mutable $7 = "\n " + Const mutable $8 = "\n " + Const mutable $9 = JSX {readonly $6}{readonly x$2}{readonly $7}{readonly y$3}{readonly $8} + Return readonly $9 ``` ## Code diff --git a/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-arguments.expect.md b/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-arguments.expect.md new file mode 100644 index 0000000000..8d2954776b --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-arguments.expect.md @@ -0,0 +1,71 @@ + +## Input + +```javascript +function Component() { + const a = []; + useFreeze(a); // should freeze + useFreeze(a); // should be readonly + call(a); // should be readonly + return a; +} + +function useFreeze(x) {} +function call(x) {} + +``` + +## HIR + +``` +bb0: + Const mutable a$1 = Array [] + Call readonly useFreeze$2(freeze a$1) + Call readonly useFreeze$2(readonly a$1) + Call mutable call$3(readonly a$1) + Return readonly a$1 +``` + +## Code + +```javascript +function Component$0() { + const a$1 = []; + useFreeze$2(a$1); + useFreeze$2(a$1); + call$3(a$1); + return a$1; +} + +``` +## HIR + +``` +bb0: + Return +``` + +## Code + +```javascript +function useFreeze$0(x$1) { + return; +} + +``` +## HIR + +``` +bb0: + Return +``` + +## Code + +```javascript +function call$0(x$1) { + return; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-arguments.js b/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-arguments.js new file mode 100644 index 0000000000..cdb1fce7e1 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-arguments.js @@ -0,0 +1,10 @@ +function Component() { + const a = []; + useFreeze(a); // should freeze + useFreeze(a); // should be readonly + call(a); // should be readonly + return a; +} + +function useFreeze(x) {} +function call(x) {} diff --git a/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.expect.md b/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.expect.md new file mode 100644 index 0000000000..4d8ee08023 --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.expect.md @@ -0,0 +1,97 @@ + +## Input + +```javascript +function Component(props) { + const cond = props.cond; + const x = props.x; + let a; + if (cond) { + a = x; + } else { + a = []; + } + useFreeze(a); // should freeze, value *may* be mutable + useFreeze(a); // should be readonly + call(a); // should be readonly + return a; +} + +function useFreeze(x) {} +function call(x) {} + +``` + +## HIR + +``` +bb0: + Const mutable cond$2 = readonly props$1.cond + Const mutable x$3 = readonly props$1.x + Let mutable a$4 = undefined + If (readonly cond$2) then:bb2 else:bb3 +bb2: + Reassign mutable a$4 = readonly x$3 + Goto bb1 +bb3: + Reassign mutable a$4 = Array [] + Goto bb1 +bb1: + Call readonly useFreeze$5(freeze a$4) + Call readonly useFreeze$5(readonly a$4) + Call mutable call$6(readonly a$4) + Return readonly a$4 +``` + +## Code + +```javascript +function Component$0(props$1) { + const cond$2 = props$1.cond; + const x$3 = props$1.x; + let a$4 = undefined; + if (cond$2) { + a$4 = x$3; + ("<>"); + } else { + a$4 = []; + ("<>"); + } + useFreeze$5(a$4); + useFreeze$5(a$4); + call$6(a$4); + return a$4; +} + +``` +## HIR + +``` +bb0: + Return +``` + +## Code + +```javascript +function useFreeze$0(x$1) { + return; +} + +``` +## HIR + +``` +bb0: + Return +``` + +## Code + +```javascript +function call$0(x$1) { + return; +} + +``` + \ No newline at end of file diff --git a/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.js b/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.js new file mode 100644 index 0000000000..bcf51ccc4e --- /dev/null +++ b/compiler/forget/src/__tests__/fixtures/hir/hooks-freeze-possibly-mutable-arguments.js @@ -0,0 +1,17 @@ +function Component(props) { + const cond = props.cond; + const x = props.x; + let a; + if (cond) { + a = x; + } else { + a = []; + } + useFreeze(a); // should freeze, value *may* be mutable + useFreeze(a); // should be readonly + call(a); // should be readonly + return a; +} + +function useFreeze(x) {} +function call(x) {} diff --git a/compiler/forget/src/__tests__/fixtures/hir/independent-across-if.expect.md b/compiler/forget/src/__tests__/fixtures/hir/independent-across-if.expect.md index ffb2213e6c..0c54e9f6a4 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/independent-across-if.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/independent-across-if.expect.md @@ -97,16 +97,16 @@ function Foo$0() { ``` bb0: - Const readonly a$2 = Call mutable compute$3(frozen props$1.a) - Const readonly b$4 = Call mutable compute$3(frozen props$1.b) - If (frozen props$1.c) then:bb2 else:bb1 + Const mutable a$2 = Call mutable compute$3(readonly props$1.a) + Const mutable b$4 = Call mutable compute$3(readonly props$1.b) + If (readonly props$1.c) then:bb2 else:bb1 bb2: Call mutable mutate$5(mutable a$2) Call mutable mutate$5(mutable b$4) Goto bb1 bb1: - Const readonly $7 = JSX - Return frozen $7 + Const mutable $7 = JSX + Return readonly $7 ``` ## Code diff --git a/compiler/forget/src/__tests__/fixtures/hir/independent.expect.md b/compiler/forget/src/__tests__/fixtures/hir/independent.expect.md index 19ee176fee..6fc689dfc8 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/independent.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/independent.expect.md @@ -28,10 +28,10 @@ function Foo() {} ``` bb0: - Const readonly a$2 = Call mutable compute$3(frozen props$1.a) - Const readonly b$4 = Call mutable compute$3(frozen props$1.b) - Const readonly $6 = JSX - Return frozen $6 + Const mutable a$2 = Call mutable compute$3(readonly props$1.a) + Const mutable b$4 = Call mutable compute$3(readonly props$1.b) + Const mutable $6 = JSX + Return readonly $6 ``` ## Code diff --git a/compiler/forget/src/__tests__/fixtures/hir/interdependent-across-if.expect.md b/compiler/forget/src/__tests__/fixtures/hir/interdependent-across-if.expect.md index b5d8dc4883..bab5171d68 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/interdependent-across-if.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/interdependent-across-if.expect.md @@ -76,15 +76,15 @@ function Foo$0() { ``` bb0: - Const readonly a$2 = Call mutable compute$3(frozen props$1.a) - Const readonly b$4 = Call mutable compute$3(frozen props$1.b) - If (frozen props$1.c) then:bb2 else:bb1 + Const mutable a$2 = Call mutable compute$3(readonly props$1.a) + Const mutable b$4 = Call mutable compute$3(readonly props$1.b) + If (readonly props$1.c) then:bb2 else:bb1 bb2: Call mutable foo$5(mutable a$2, mutable b$4) Goto bb1 bb1: - Const readonly $7 = JSX - Return frozen $7 + Const mutable $7 = JSX + Return readonly $7 ``` ## Code diff --git a/compiler/forget/src/__tests__/fixtures/hir/interdependent.expect.md b/compiler/forget/src/__tests__/fixtures/hir/interdependent.expect.md index 43ac1bc3d8..307a2735ce 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/interdependent.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/interdependent.expect.md @@ -28,11 +28,11 @@ function Foo() {} ``` bb0: - Const readonly a$2 = Call mutable compute$3(frozen props$1.a) - Const readonly b$4 = Call mutable compute$3(frozen props$1.b) + Const mutable a$2 = Call mutable compute$3(readonly props$1.a) + Const mutable b$4 = Call mutable compute$3(readonly props$1.b) Call mutable foo$5(mutable a$2, mutable b$4) - Const readonly $7 = JSX - Return frozen $7 + Const mutable $7 = JSX + Return readonly $7 ``` ## Code diff --git a/compiler/forget/src/__tests__/fixtures/hir/property-assignment.expect.md b/compiler/forget/src/__tests__/fixtures/hir/property-assignment.expect.md index a7cb076b9e..0e9198fa54 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/property-assignment.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/property-assignment.expect.md @@ -17,13 +17,13 @@ function Component(props) { ``` bb0: - Const readonly x$2 = Object { } - Const readonly y$3 = Array [] - Reassign readonly x$2.y = readonly y$3 - Const readonly child$4 = JSX - Call mutable x$2.y.push(frozen props$1.p0) - Const readonly $5 = JSX {frozen child$4} - Return frozen $5 + Const mutable x$2 = Object { } + Const mutable y$3 = Array [] + Reassign mutable x$2.y = readonly y$3 + Const mutable child$4 = JSX + Call mutable x$2.y.push(readonly props$1.p0) + Const mutable $5 = JSX {readonly child$4} + Return readonly $5 ``` ## Code diff --git a/compiler/forget/src/__tests__/fixtures/hir/reassignment-conditional.expect.md b/compiler/forget/src/__tests__/fixtures/hir/reassignment-conditional.expect.md index 57cd445f04..82726cf705 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/reassignment-conditional.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/reassignment-conditional.expect.md @@ -23,18 +23,18 @@ function Component(props) { ``` bb0: - Let readonly x$2 = Array [] - Call mutable x$2.push(frozen props$1.p0) - Let readonly y$3 = readonly x$2 - If (frozen props$1.p1) then:bb2 else:bb1 + Let mutable x$2 = Array [] + Call mutable x$2.push(readonly props$1.p0) + Let mutable y$3 = readonly x$2 + If (readonly props$1.p1) then:bb2 else:bb1 bb2: - Reassign readonly x$2 = Array [] + Reassign mutable x$2 = Array [] Goto bb1 bb1: - Let readonly _$4 = JSX - Call mutable y$3.push(frozen props$1.p2) - Const readonly $5 = JSX - Return frozen $5 + Let mutable _$4 = JSX + Call readonly y$3.push(readonly props$1.p2) + Const mutable $5 = JSX + Return readonly $5 ``` ## Code diff --git a/compiler/forget/src/__tests__/fixtures/hir/reassignment.expect.md b/compiler/forget/src/__tests__/fixtures/hir/reassignment.expect.md index dcdd0301dd..16c8f07747 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/reassignment.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/reassignment.expect.md @@ -21,14 +21,14 @@ function Component(props) { ``` bb0: - Let readonly x$2 = Array [] - Call mutable x$2.push(frozen props$1.p0) - Let readonly y$3 = readonly x$2 - Reassign readonly x$2 = Array [] - Let readonly _$4 = JSX - Call mutable y$3.push(frozen props$1.p1) - Const readonly $5 = JSX - Return frozen $5 + Let mutable x$2 = Array [] + Call mutable x$2.push(readonly props$1.p0) + Let mutable y$3 = readonly x$2 + Reassign mutable x$2 = Array [] + Let mutable _$4 = JSX + Call mutable y$3.push(readonly props$1.p1) + Const mutable $5 = JSX + Return readonly $5 ``` ## Code diff --git a/compiler/forget/src/__tests__/fixtures/hir/simple.expect.md b/compiler/forget/src/__tests__/fixtures/hir/simple.expect.md index e1fb624c18..9c61008af3 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/simple.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/simple.expect.md @@ -15,16 +15,16 @@ function foo(x, y) { ``` bb0: - If (frozen x$1) then:bb2 else:bb1 + If (readonly x$1) then:bb2 else:bb1 bb2: - Const readonly $3 = false - Const readonly $4 = Call mutable foo$0(mutable $3, frozen y$2) - Return frozen $4 + Const mutable $3 = false + Const mutable $4 = Call readonly foo$0(readonly $3, readonly y$2) + Return freeze $4 bb1: - Const readonly $5 = 10 - Const frozen $6 = Binary frozen y$2 * readonly $5 - Const readonly $7 = Array [frozen $6] - Return frozen $7 + Const mutable $5 = 10 + Const mutable $6 = Binary readonly y$2 * readonly $5 + Const mutable $7 = Array [readonly $6] + Return freeze $7 ``` ## Code diff --git a/compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md b/compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md index d02e9e8166..9f783f2078 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/switch-non-final-default.expect.md @@ -32,27 +32,27 @@ function Component(props) { ``` bb0: - Let readonly x$2 = Array [] - Let readonly y$3 = undefined - Const readonly $4 = false - Const readonly $5 = true - Const readonly $6 = 1 - Switch (frozen props$1.p0) + Let mutable x$2 = Array [] + Let mutable y$3 = undefined + Const mutable $4 = false + Const mutable $5 = true + Const mutable $6 = 1 + Switch ( props$1.p0) Case readonly $6: bb1 Case readonly $5: bb6 Default: bb1 Case readonly $4: bb2 bb1: - Const readonly child$7 = JSX - Call mutable y$3.push(frozen props$1.p4) - Const readonly $8 = JSX {frozen child$7} - Return frozen $8 + Const mutable child$7 = JSX + Call readonly y$3.push(readonly props$1.p4) + Const mutable $8 = JSX {readonly child$7} + Return readonly $8 bb6: - Call mutable x$2.push(frozen props$1.p2) - Reassign readonly y$3 = Array [] + Call mutable x$2.push(readonly props$1.p2) + Reassign mutable y$3 = Array [] Goto bb1 bb2: - Reassign readonly y$3 = readonly x$2 + Reassign mutable y$3 = readonly x$2 Goto bb1 ``` diff --git a/compiler/forget/src/__tests__/fixtures/hir/switch.expect.md b/compiler/forget/src/__tests__/fixtures/hir/switch.expect.md index f267daf383..49527e1174 100644 --- a/compiler/forget/src/__tests__/fixtures/hir/switch.expect.md +++ b/compiler/forget/src/__tests__/fixtures/hir/switch.expect.md @@ -28,27 +28,27 @@ function Component(props) { ``` bb0: - Let readonly x$2 = Array [] - Let readonly y$3 = undefined - Const readonly $4 = false - Const readonly $5 = true - Switch (frozen props$1.p0) + Let mutable x$2 = Array [] + Let mutable y$3 = undefined + Const mutable $4 = false + Const mutable $5 = true + Switch ( props$1.p0) Case readonly $5: bb4 Case readonly $4: bb2 Default: bb1 bb4: - Call mutable x$2.push(frozen props$1.p2) - Call mutable x$2.push(frozen props$1.p3) - Reassign readonly y$3 = Array [] + Call mutable x$2.push(readonly props$1.p2) + Call mutable x$2.push(readonly props$1.p3) + Reassign mutable y$3 = Array [] Goto bb2 bb2: - Reassign readonly y$3 = readonly x$2 + Reassign mutable y$3 = readonly x$2 Goto bb1 bb1: - Const readonly child$6 = JSX - Call mutable y$3.push(frozen props$1.p4) - Const readonly $7 = JSX {frozen child$6} - Return frozen $7 + Const mutable child$6 = JSX + Call readonly y$3.push(readonly props$1.p4) + Const mutable $7 = JSX {readonly child$6} + Return readonly $7 ``` ## Code diff --git a/compiler/forget/src/__tests__/hir-test.ts b/compiler/forget/src/__tests__/hir-test.ts index e36437ced1..9a8bd19d41 100644 --- a/compiler/forget/src/__tests__/hir-test.ts +++ b/compiler/forget/src/__tests__/hir-test.ts @@ -10,15 +10,14 @@ import generate from "@babel/generator"; import * as parser from "@babel/parser"; import traverse from "@babel/traverse"; -import { graphviz, wasmFolder } from "@hpcc-js/wasm"; -import { existsSync, unlinkSync, writeFileSync } from "fs"; +import { wasmFolder } from "@hpcc-js/wasm"; import invariant from "invariant"; import path from "path"; import prettier from "prettier"; -import buildDefUseGraph, { printGraph } from "../HIR/BuildDefUseGraph"; import { lower } from "../HIR/BuildHIR"; import codegen from "../HIR/Codegen"; import { HIRFunction } from "../HIR/HIR"; +import inferReferenceCapability from "../HIR/InferReferenceCapability"; import printHIR from "../HIR/PrintHIR"; import generateTestsFromFixtures from "./test-utils/generateTestsFromFixtures"; @@ -45,22 +44,23 @@ describe("React Forget (HIR version)", () => { FunctionDeclaration: { enter(nodePath) { const ir: HIRFunction = lower(nodePath); - const lifetimeGraph = buildDefUseGraph(ir); + inferReferenceCapability(ir); + // const lifetimeGraph = buildDefUseGraph(ir); const textHIR = printHIR(ir.body); - const textLifetimeGraph = printGraph(lifetimeGraph); - const graphvizFile = path.join( - __dirname, - "fixtures", - "hir-svg", - file + ".svg" - ); - if (input.indexOf("@Out DefUseGraph") !== -1) { - graphviz.layout(textLifetimeGraph, "svg", "dot").then((svg) => { - writeFileSync(graphvizFile, svg); - }); - } else if (existsSync(graphvizFile)) { - unlinkSync(graphvizFile); - } + // const textLifetimeGraph = printGraph(lifetimeGraph); + // const graphvizFile = path.join( + // __dirname, + // "fixtures", + // "hir-svg", + // file + ".svg" + // ); + // if (input.indexOf("@Out DefUseGraph") !== -1) { + // graphviz.layout(textLifetimeGraph, "svg", "dot").then((svg) => { + // writeFileSync(graphvizFile, svg); + // }); + // } else if (existsSync(graphvizFile)) { + // unlinkSync(graphvizFile); + // } const ast = codegen(ir); const text = prettier.format( diff --git a/compiler/forget/yarn.lock b/compiler/forget/yarn.lock index 5d45b60fcd..297d6129f5 100644 --- a/compiler/forget/yarn.lock +++ b/compiler/forget/yarn.lock @@ -63,7 +63,7 @@ json5 "^2.2.1" semver "^6.3.0" -"@babel/generator@7.2.0", "@babel/generator@^7.1.6", "@babel/generator@^7.19.3", "@babel/generator@^7.2.0", "@babel/generator@^7.7.2": +"@babel/generator@7.2.0", "@babel/generator@^7.1.6", "@babel/generator@^7.19.4", "@babel/generator@^7.2.0", "@babel/generator@^7.7.2": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.2.0.tgz#eaf3821fa0301d9d4aef88e63d4bcc19b73ba16c" integrity sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg== @@ -75,11 +75,11 @@ trim-right "^1.0.1" "@babel/generator@^7.19.0": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.3.tgz#d7f4d1300485b4547cb6f94b27d10d237b42bf59" - integrity sha512-fqVZnmp1ncvZU757UzDheKZpfPgatqY59XtW2/j/18H7u76akb8xqvjw82f+i2UKd/ksYsSick/BCLQUUtJ/qQ== + version "7.19.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.5.tgz#da3f4b301c8086717eee9cab14da91b1fa5dcca7" + integrity sha512-DxbNz9Lz4aMZ99qPpO1raTbcrI1ZeYh+9NR9qhfkQIbFtVEqotHojEBxHzmxhVONkGt6VyrqVQcgpefMy9pqcg== dependencies: - "@babel/types" "^7.19.3" + "@babel/types" "^7.19.4" "@jridgewell/gen-mapping" "^0.3.2" jsesc "^2.5.1" @@ -203,6 +203,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== +"@babel/helper-string-parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63" + integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw== + "@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1": version "7.19.1" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" @@ -241,11 +246,16 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.1.tgz#6f6d6c2e621aad19a92544cc217ed13f1aac5b4c" integrity sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A== -"@babel/parser@^7.18.6", "@babel/parser@^7.19.3": +"@babel/parser@^7.18.6": version "7.19.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.3.tgz#8dd36d17c53ff347f9e55c328710321b49479a9a" integrity sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ== +"@babel/parser@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.4.tgz#03c4339d2b8971eb3beca5252bafd9b9f79db3dc" + integrity sha512-qpVT7gtuOLjWeDTKLkJ6sryqLliBaFpAtGeqw5cs5giLldvh+Ch0plqnUMKoVAUS6ZEueQQiZV+p5pxtPitEsA== + "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" @@ -465,18 +475,18 @@ lodash "^4.17.10" "@babel/traverse@^7.19.1": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.3.tgz#3a3c5348d4988ba60884e8494b0592b2f15a04b4" - integrity sha512-qh5yf6149zhq2sgIXmwjnsvmnNQC2iw70UFjp4olxucKrWd/dvlUsBI88VSLUsnMNF7/vnOiA+nk1+yLoCqROQ== + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.4.tgz#f117820e18b1e59448a6c1fa9d0ff08f7ac459a8" + integrity sha512-w3K1i+V5u2aJUOXBFFC5pveFLmtq1s3qcdDNC2qRI6WPBQIDaKFqXxDEqDO/h1dQ3HjsZoZMyIy6jGLq0xtw+g== dependencies: "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.3" + "@babel/generator" "^7.19.4" "@babel/helper-environment-visitor" "^7.18.9" "@babel/helper-function-name" "^7.19.0" "@babel/helper-hoist-variables" "^7.18.6" "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.19.3" - "@babel/types" "^7.19.3" + "@babel/parser" "^7.19.4" + "@babel/types" "^7.19.4" debug "^4.1.0" globals "^11.1.0" @@ -506,12 +516,12 @@ "@babel/helper-validator-identifier" "^7.18.6" to-fast-properties "^2.0.0" -"@babel/types@^7.19.3": - version "7.19.3" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.3.tgz#fc420e6bbe54880bce6779ffaf315f5e43ec9624" - integrity sha512-hGCaQzIY22DJlDh9CH7NOxgKkFjBk0Cw9xDO1Xmh2151ti7wiGfQ3LauXzL4HP1fmFlTX6XjpRETTpUcv7wQLw== +"@babel/types@^7.19.4": + version "7.19.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.4.tgz#0dd5c91c573a202d600490a35b33246fed8a41c7" + integrity sha512-M5LK7nAeS6+9j7hAq+b3fQs+pNfUtTGq+yFFfHnauFA8zQtLRfmuipmsKDKKLuyG+wC8ABW43A153YNawNTEtw== dependencies: - "@babel/helper-string-parser" "^7.18.10" + "@babel/helper-string-parser" "^7.19.4" "@babel/helper-validator-identifier" "^7.19.1" to-fast-properties "^2.0.0"