Skip to content

Control flow graph #222

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 54 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
c1d41af
remove operation cache from the scope
JeanJPNM Jan 5, 2024
5b0ec10
start experimenting with cfg based ir
JeanJPNM Jan 11, 2024
1dffe9e
ignore blocks that can't be connected
JeanJPNM Jan 12, 2024
26dae45
track backward edges
JeanJPNM Jan 12, 2024
50f4a33
add missing source locations to do while handler
JeanJPNM Jan 12, 2024
2475e60
always add an end instruction
JeanJPNM Jan 12, 2024
383fffd
prevent deop on do while loop jumps
JeanJPNM Jan 12, 2024
9b02c80
negate the condition value when the source isn't found
JeanJPNM Jan 13, 2024
6304a3b
refactor parent and edge fields
JeanJPNM Jan 14, 2024
f65f8ee
add handleWrite method
JeanJPNM Jan 20, 2024
234a422
dump changes
JeanJPNM Jan 29, 2024
baa9170
remove the AssignmentInstruction
JeanJPNM Feb 3, 2024
ac02501
update outdated scope definitions
JeanJPNM Feb 3, 2024
13080db
allow macro functions to run during node handling
JeanJPNM Feb 3, 2024
b9ff7fe
update basic value get methods
JeanJPNM Feb 3, 2024
d959354
convert math functions into comptime macro functions
JeanJPNM Feb 3, 2024
15dabc0
fix mlog math not being initialized properly
JeanJPNM Feb 4, 2024
9bc02ed
add constant operation folding
JeanJPNM Feb 4, 2024
85a2924
remove break-ifs that have constant conditions
JeanJPNM Feb 4, 2024
c629f4e
small refactor on BreakIfInstruction
JeanJPNM Feb 4, 2024
ba2be13
canonicalize binary operations
JeanJPNM Feb 4, 2024
6a2c064
apply transformations to binary operations
JeanJPNM Feb 4, 2024
5a4a10d
remove unused operations
JeanJPNM Feb 4, 2024
ad1963f
update break-if canonicalization
JeanJPNM Feb 4, 2024
d9ff33e
add missing code from graph.ts
JeanJPNM Feb 4, 2024
609065d
add missing code from other flow/ files
JeanJPNM Feb 4, 2024
8dedaf4
update block merging and skipping
JeanJPNM Feb 5, 2024
21de6dc
only traverse each block once
JeanJPNM Feb 5, 2024
aaad7d8
add missing import
JeanJPNM Feb 5, 2024
111a5ad
don't invert break-ifs when both targets are empty
JeanJPNM Feb 5, 2024
4a96582
prevent index out of bounds when removing unused instructions
JeanJPNM Feb 5, 2024
30cb0de
flip break-ifs that check if a value is falsy
JeanJPNM Feb 8, 2024
eefbef9
canonicalize range comparison operations
JeanJPNM Feb 8, 2024
e0b23e4
refactor logic for computing readers and writers of values
JeanJPNM Feb 19, 2024
0ed19f9
only accept immutable ids in the writer map
JeanJPNM Feb 19, 2024
f751439
optimize load instructions that are immediately used
JeanJPNM Feb 19, 2024
da23012
big changes
JeanJPNM Apr 17, 2024
97b8f5b
incomplete macro updates
JeanJPNM Apr 17, 2024
fcff1bd
migrate most built-in functions to work with the new IR
JeanJPNM Apr 18, 2024
3d09038
fix spawnWave argument count checks
JeanJPNM Apr 18, 2024
27c2b71
fix evaluation of computed keys in assignment expressions
JeanJPNM Jul 31, 2024
62a5025
fix wrong instruction list length when using endScript()
JeanJPNM Jan 9, 2025
8269ca0
improve generated instruction layout for most cases
JeanJPNM Jan 9, 2025
82f02a9
add the end-if instruction
JeanJPNM Jan 9, 2025
20590bf
remove legacy variable declaration handlers
JeanJPNM Jan 9, 2025
ba6f5da
remove the JumpOutValue class
JeanJPNM Jan 10, 2025
da616c4
fix implementation of print
JeanJPNM Jan 13, 2025
edc9f19
make DOT ir generator dependency free
JeanJPNM Jan 20, 2025
b732866
figured out why the back break check existed
JeanJPNM Jan 20, 2025
6c3fad8
use numbers to differentiate ids instead of object identities
JeanJPNM Jan 20, 2025
c30b864
fix return type of isTemplateObjectArray
JeanJPNM Jan 20, 2025
c60f732
generic changes
JeanJPNM Jan 20, 2025
1e752a2
fix named parameters of built-in functions
JeanJPNM Jan 21, 2025
81f0609
improve appearance of visualized nodes
JeanJPNM Jan 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions compiler/src/BlockCursor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { CompilerError } from "./CompilerError";
import {
Block,
BreakInstruction,
InstructionNode,
TBlockEndInstruction,
TBlockInstruction,
} from "./flow";
import { Location, es } from "./types";

export interface IBlockCursor {
currentBlock: Block;
position: InstructionNode | undefined;

/**
* Adds an instruction after the instruction currently pointed at by the
* cursor
*/
addInstruction(instruction: TBlockInstruction): void;
/** Removes the instruction currently pointed at by the cursor */
removeInstruction(): void;

/** Attempts to connect a block to the current block pointed at by this cursor. */
connectBlock(block: Block, node: es.Node): void;

setEndInstruction(instruction: TBlockEndInstruction): void;

discardFollowing(): void;
}

export type CursorMode = "create" | "edit";

export class BlockCursor implements IBlockCursor {
private _currentBlock: Block;
position: InstructionNode | undefined;

constructor(
public mode: CursorMode,
currentBlock: Block,
) {
this._currentBlock = currentBlock;
}

get currentBlock(): Block {
return this._currentBlock;
}

set currentBlock(block: Block) {
this._currentBlock = block;
this.position = undefined;
}

addInstruction(instruction: TBlockInstruction) {
if (this.mode === "create" && this.currentBlock.endInstruction) return;

if (this.position === undefined) {
this.currentBlock.instructions.add(instruction);
return;
}

this.currentBlock.instructions.insertAfter(this.position, instruction);
this.position = this.position.next;
}

removeInstruction() {
if (this.position === undefined) {
this.currentBlock.instructions.removeLast();
return;
}

const { previous } = this.position;
this.currentBlock.instructions.remove(this.position);
this.position = previous;
}

connectBlock(block: Block, loc: Location) {
if (block === this.currentBlock) return;
this.setEndInstruction(new BreakInstruction(block, loc));
this.currentBlock = block;
}

setEndInstruction(instruction: TBlockEndInstruction) {
if (this.mode === "create" && this.currentBlock.endInstruction) return;
this.currentBlock.endInstruction = instruction;
}

discardFollowing(): void {
if (!this.position)
throw new CompilerError(
"Attempted to discard instructions without a position",
);

this.currentBlock.instructions.removeAllAfter(this.position);
}
}
160 changes: 33 additions & 127 deletions compiler/src/Compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,13 @@ import { parse } from "@babel/parser";
import { CompilerError } from "./CompilerError";
import * as handlers from "./handlers";
import { createGlobalScope } from "./modules";
import { AddressResolver, EndInstruction } from "./instructions";
import {
es,
IInstruction,
IScope,
IValue,
TEOutput,
THandler,
TValueInstructions,
} from "./types";
import { appendSourceLocations, hideRedundantJumps, pipeInsts } from "./utils";

type THandlerMap = { [k in es.Node["type"]]?: THandler<IValue | null> };
import { es, IInstruction, THandler } from "./types";
import { hideRedundantJumps } from "./utils";
import { CompilerContext } from "./CompilerContext";
import { BlockCursor } from "./BlockCursor";
import { Block, EndInstruction, Graph } from "./flow";

type THandlerMap = { [k in es.Node["type"]]?: THandler };

export interface CompilerOptions {
/** Wether the compiler should preserve or compact variable and function names */
Expand Down Expand Up @@ -44,23 +38,34 @@ export class Compiler {
let sourcemaps: es.SourceLocation[] | undefined;

try {
const c = new CompilerContext(this);
const program = this.parse(script);
const globalScope = createGlobalScope();
const globalScope = createGlobalScope(c);
// this allows the user to shadow global variables inside
// the script, since it is treated as a module
const scope = globalScope.createScope();

const valueInst = this.handle(scope, program);
if (needsEndInstruction(valueInst[1], scope)) {
valueInst[1].push(new EndInstruction());
}
valueInst[1].push(...scope.inst);
const entryBlock = new Block();
const exitBlock = new Block(new EndInstruction());
const cursor = new BlockCursor("create", entryBlock);

c.handle(scope, cursor, program);

const rootGraph = Graph.from(entryBlock, exitBlock);

hideRedundantJumps(valueInst[1]);
this.resolve(valueInst);
output = this.serialize(valueInst) + "\n";
const inst = rootGraph.toMlog(c);

// const valueInst = this.handle(scope, program);
// if (needsEndInstruction(valueInst[1], scope)) {
// valueInst[1].push(new EndInstruction());
// }
// valueInst[1].push(...scope.inst);

hideRedundantJumps(inst);
this.resolve(inst);
output = this.serialize(inst) + "\n";
if (this.sourcemap) {
sourcemaps = this.mapSources(valueInst);
sourcemaps = this.mapSources(inst);
}
} catch (error) {
const err =
Expand All @@ -70,16 +75,15 @@ export class Compiler {
return [output, null, sourcemaps];
}

protected resolve(valueInst: TValueInstructions<IValue | null>) {
protected resolve(instructions: IInstruction[]) {
let i = 0;
for (const inst of valueInst[1]) {
for (const inst of instructions) {
inst.resolve(i);
if (!inst.hidden) i++;
}
}

protected serialize(resLines: TValueInstructions<IValue | null>) {
const [, inst] = resLines;
protected serialize(inst: IInstruction[]) {
return inst.filter(l => !l.hidden).join("\n");
}

Expand All @@ -91,111 +95,13 @@ export class Compiler {
});
}

protected mapSources(
valueInst: TValueInstructions<IValue | null>,
): es.SourceLocation[] {
protected mapSources(insts: IInstruction[]): es.SourceLocation[] {
const result: es.SourceLocation[] = [];

for (const inst of valueInst[1]) {
for (const inst of insts) {
if (!inst.hidden) result.push(inst.source as es.SourceLocation);
}

return result;
}

handle(
scope: IScope,
node: es.Node,
handler = this.handlers[node.type],
out?: TEOutput,
arg?: unknown,
): TValueInstructions<IValue | null> {
try {
if (!handler) throw new CompilerError("Missing handler for " + node.type);
const result = handler(this, scope, node, out, arg);
if (this.sourcemap) return appendSourceLocations(result, node);
return result;
} catch (error) {
if (error instanceof CompilerError) {
error.loc ??= node.loc as es.SourceLocation;
}
throw error;
}
}

/** Handles the node and asserts that it resolves to a value. */
handleValue(
scope: IScope,
node: es.Node,
handler = this.handlers[node.type],
out?: TEOutput,
arg?: unknown,
): TValueInstructions {
const result = this.handle(scope, node, handler, out, arg);
if (!result[0])
throw new CompilerError(
`This node (${node.type}) did not return a value`,
node,
);
return result as TValueInstructions;
}

handleEval(
scope: IScope,
node: es.Node,
out?: TEOutput,
arg?: unknown,
): TValueInstructions {
const [res, inst] = this.handleValue(scope, node, undefined, out, arg);
const [evaluated, evaluatedInst] = res.eval(scope, out);
const result: TValueInstructions = [evaluated, [...inst, ...evaluatedInst]];

if (this.sourcemap) return appendSourceLocations(result, node);
return result;
}

/**
* Handles many nodes in order.
*
* The usage of this method over a regular loop over an array of nodes is only
* required if the code inside the loop generates instructions that are not
* tracked by the compiler handler methods ({@link handle}, {@link handleEval}
* and {@link handleMany})
*/
handleMany<T extends es.Node>(
scope: IScope,
nodes: T[],
handler?: (node: T) => TValueInstructions<IValue | null>,
): TValueInstructions<null> {
const lines: IInstruction[] = [];
for (const node of hoistedFunctionNodes(nodes)) {
pipeInsts(
this.handle(scope, node, handler && (() => handler(node))),
lines,
);
}
return [null, lines];
}
}

function* hoistedFunctionNodes<T extends es.Node>(nodes: T[]) {
// sorting is O(n long n) while this is just O(n)
// besides, it's better not to modify the node array
for (const node of nodes) {
if (node.type === "FunctionDeclaration") {
yield node;
}
}

for (const node of nodes) {
if (node.type !== "FunctionDeclaration") {
yield node;
}
}
}

function needsEndInstruction(inst: IInstruction[], scope: IScope) {
const last = inst[inst.length - 1];
if (last instanceof AddressResolver) return true;
return scope.inst.length > 0;
}
Loading
Loading