[hir] Split value tracking into a separate pass

This commit is contained in:
Sathya Gunasekaran
2022-12-13 09:17:19 +00:00
parent 5aa4143e24
commit 7f8ad49165
2 changed files with 150 additions and 118 deletions
+6 -118
View File
@@ -3,97 +3,14 @@ import DisjointSet from "./DisjointSet";
import { HIRFunction, Identifier, Instruction, LValue, Place } from "./HIR";
import { printInstructionValue } from "./PrintHIR";
type AbstractValue = AbstractObject | AbstractPrimitive;
type AbstractObject = {
kind: "Object";
values: Map<string, AbstractValue>;
};
type AbstractPrimitive = {
kind: "Primitive";
value: number | boolean | string | null | undefined;
};
export type AliasSet = Set<Identifier>;
class AbstractState {
class AliasAnalyser {
aliases = new DisjointSet<Identifier>();
// NOTE(gsn): Should this be a part of AbstractObject? No, because this has
// nothing to do with values in the object.
objectAliases = new Map<Identifier, Map<string, AliasSet>>();
#values = new Map<Identifier, AbstractValue>();
read(alias: Place): AbstractValue {
// Simple alias:
// read(alias);
if (alias.memberPath === null) {
let value = this.#values.get(alias.identifier);
// Don't know what this is, let's default to an Object conservatively.
if (value === undefined) {
value = { kind: "Object", values: new Map() };
}
this.#values.set(alias.identifier, value);
return value;
}
if (alias.memberPath.length > 1) {
// TODO(gsn): Correctly handle nested member paths when reading values
return { kind: "Object", values: new Map() };
}
// Complex alias:
// read(alias.memberPath);
let object = this.#values.get(alias.identifier);
// Don't know what this is, let's default to an Object conservatively.
if (object === undefined) {
object = { kind: "Object", values: new Map() };
this.#values.set(alias.identifier, object);
}
// We're doing a member lookup on a non object.
//
// alias = 1;
// read(alias.memberPath);
if (object.kind !== "Object") {
// Update alias to be an object
object = { kind: "Object", values: new Map() };
this.#values.set(alias.identifier, object);
// Conservatively type the value as object.
//
// NOTE(gsn): Should this be an AbstractUnknown rather than an
// AbstractObject?
let value: AbstractObject = { kind: "Object", values: new Map() };
object.values.set(alias.memberPath[0], value);
return value;
}
let value = object.values.get(alias.memberPath[0]);
// We don't have a value for this member path.
//
// alias = {};
// read(alias.memberPath);
if (value === undefined) {
// Conservatively type the value as object.
value = {
kind: "Object",
values: new Map(),
};
object.values.set(alias.memberPath[0], value);
return value;
}
// We have a value for this memberPath!
//
// alias.memberPath = value;
// read(alias.memberPath);
return value;
}
alias(lvalue: LValue, alias: Place) {
// Complex lvalue:
// lvalue.memberPath = alias;
@@ -141,66 +58,37 @@ class AbstractState {
// lvalue = alias.memberPath;
this.aliases.union([lvalue.place.identifier, alias.identifier]);
}
store(lvalue: LValue, value: AbstractValue) {
// TODO(gsn): Handle stores for complex lvalue
if (lvalue.place.memberPath !== null) {
return;
}
// Simple lvalue:
// lvalue = alias;
// lvalue = alias.memberPath;
this.#values.set(lvalue.place.identifier, value);
}
}
export function inferAliases(func: HIRFunction): DisjointSet<Identifier> {
const state = new AbstractState();
const analyser = new AliasAnalyser();
for (const [_, block] of func.body.blocks) {
for (const instr of block.instructions) {
inferInstr(instr, state);
inferInstr(instr, analyser);
}
}
return state.aliases;
return analyser.aliases;
}
function inferInstr(instr: Instruction, state: AbstractState) {
function inferInstr(instr: Instruction, state: AliasAnalyser) {
const { lvalue, value: instrValue } = instr;
let alias: Place | null = null;
let value: AbstractValue | null = null;
switch (instrValue.kind) {
case "Primitive": {
value = {
kind: "Primitive",
value: instrValue.value,
};
break;
}
case "Identifier": {
alias = instrValue;
value = state.read(alias);
break;
}
default:
return;
}
invariant(
value !== null,
`expected ${printInstructionValue(instrValue)} to be an alias or value`
);
// TODO(gsn): handle this.
if (lvalue === null) {
return;
}
// No need to alias Primitives.
if (alias !== null && value.kind !== "Primitive") {
if (alias !== null) {
state.alias(lvalue, alias);
}
state.store(lvalue, value);
}
+144
View File
@@ -0,0 +1,144 @@
import invariant from "invariant";
import DisjointSet from "./DisjointSet";
import { HIRFunction, Identifier, Instruction, LValue, Place } from "./HIR";
import { printInstructionValue } from "./PrintHIR";
type AbstractValue = AbstractObject | AbstractPrimitive;
type AbstractObject = {
kind: "Object";
values: Map<string, AbstractValue>;
};
type AbstractPrimitive = {
kind: "Primitive";
value: number | boolean | string | null | undefined;
};
class AbstractState {
#values = new Map<Identifier, AbstractValue>();
read(alias: Place): AbstractValue {
// Simple alias:
// read(alias);
if (alias.memberPath === null) {
let value = this.#values.get(alias.identifier);
// Don't know what this is, let's default to an Object conservatively.
if (value === undefined) {
value = { kind: "Object", values: new Map() };
}
this.#values.set(alias.identifier, value);
return value;
}
if (alias.memberPath.length > 1) {
// TODO(gsn): Correctly handle nested member paths when reading values
return { kind: "Object", values: new Map() };
}
// Complex alias:
// read(alias.memberPath);
let object = this.#values.get(alias.identifier);
// Don't know what this is, let's default to an Object conservatively.
if (object === undefined) {
object = { kind: "Object", values: new Map() };
this.#values.set(alias.identifier, object);
}
// We're doing a member lookup on a non object.
//
// alias = 1;
// read(alias.memberPath);
if (object.kind !== "Object") {
// Update alias to be an object
object = { kind: "Object", values: new Map() };
this.#values.set(alias.identifier, object);
// Conservatively type the value as object.
//
// NOTE(gsn): Should this be an AbstractUnknown rather than an
// AbstractObject?
let value: AbstractObject = { kind: "Object", values: new Map() };
object.values.set(alias.memberPath[0], value);
return value;
}
let value = object.values.get(alias.memberPath[0]);
// We don't have a value for this member path.
//
// alias = {};
// read(alias.memberPath);
if (value === undefined) {
// Conservatively type the value as object.
value = {
kind: "Object",
values: new Map(),
};
object.values.set(alias.memberPath[0], value);
return value;
}
// We have a value for this memberPath!
//
// alias.memberPath = value;
// read(alias.memberPath);
return value;
}
store(lvalue: LValue, value: AbstractValue) {
// TODO(gsn): Handle stores for complex lvalue
if (lvalue.place.memberPath !== null) {
return;
}
// Simple lvalue:
// lvalue = alias;
// lvalue = alias.memberPath;
this.#values.set(lvalue.place.identifier, value);
}
}
export function inferTypes(func: HIRFunction) {
const state = new AbstractState();
for (const [_, block] of func.body.blocks) {
for (const instr of block.instructions) {
inferInstr(instr, state);
}
}
}
function inferInstr(instr: Instruction, state: AbstractState) {
const { lvalue, value: instrValue } = instr;
let alias: Place | null = null;
let value: AbstractValue | null = null;
switch (instrValue.kind) {
case "Primitive": {
value = {
kind: "Primitive",
value: instrValue.value,
};
break;
}
case "Identifier": {
alias = instrValue;
value = state.read(alias);
break;
}
default:
return;
}
invariant(
value !== null,
`expected ${printInstructionValue(instrValue)} to be an alias or value`
);
// TODO(gsn): handle this.
if (lvalue === null) {
return;
}
state.store(lvalue, value);
}