Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
99 changes: 92 additions & 7 deletions compiler/backend/emit.ts

Large diffs are not rendered by default.

10 changes: 8 additions & 2 deletions compiler/backend/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,13 @@ export interface Entry { from: string; into: Dword; add: Num; scale?: number }
* `tasks`: where a run's tasks have their turn (a map of our own): the walk goes there after the program's turn, and when
* the program's wake does not hold; it ends at `END`.
*/
export interface Graph { entries: Entry[]; triggers: Trigger[]; arrays?: { id: string; stride: number }[]; cells?: Region[]; templates?: { id: string; bytes: number[]; at?: number; data?: boolean }[]; rows?: string[][]; tasks?: string }
/**
* `pass`: where the map's events pass starts (a map of our own), which the walk goes through before the run's `@wake`;
* it ends at `PASS_END`.
*/
export interface Graph { entries: Entry[]; triggers: Trigger[]; arrays?: { id: string; stride: number }[]; cells?: Region[]; templates?: { id: string; bytes: number[]; at?: number; data?: boolean }[]; rows?: string[][]; tasks?: string; pass?: string }
/** Where the events pass goes when it is done: its run's `@wake`. */
export const PASS_END = "@passed";
export interface Region { id: string; cells: number; values?: number[] }

/**
Expand Down Expand Up @@ -231,7 +237,7 @@ export const actValue = (label: string, k: number): Dword => ({ at: label, field
export function checkGraph(g: Graph): string[] {
const labels = new Set(g.triggers.map((t) => t.label));
const out: string[] = [];
const known = (l: string) => l === END || labels.has(l);
const known = (l: string) => l === END || l === PASS_END || labels.has(l);
const dword = (d: Dword, where: string) => {
if ("at" in d && !labels.has(d.at)) out.push(`${where}: no trigger ${d.at}`);
};
Expand Down
2 changes: 1 addition & 1 deletion compiler/backend/heap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -840,7 +840,7 @@ export function growingArrays(program: Program, cells = HEAP_CELLS): Program {
/** The statements whose bodies run again and again. */
const LOOPS = new Set(["while", "do", "for", "unitLoop", "textLoop", "unrolled"]);
/** The kinds of statement (a `call` and a `pop` with a label: an expression of either has none). */
const STMT_KINDS = new Set(["action", "assign", "assignBool", "assignText", "assignUnit", "block", "break", "buildText", "call", "cancel", "centerLocation", "continue", "declare", "declareArray", "do", "for", "graph", "if", "parallel", "pop", "print", "push", "releaseText", "remark", "return", "select", "setLength", "sleep", "start", "store", "storeText", "switch", "syncSend", "tableWrite", "textLoop", "throw", "try", "uncaught", "unitDo", "unitLoop", "unitWrite", "unrolled", "while"]);
const STMT_KINDS = new Set(["action", "assign", "assignBool", "assignText", "assignUnit", "block", "break", "buildText", "call", "cancel", "centerLocation", "continue", "declare", "declareArray", "do", "for", "graph", "if", "parallel", "pop", "print", "push", "releaseText", "remark", "return", "select", "setLength", "sleep", "start", "store", "storeText", "switch", "syncSend", "tableWrite", "textLoop", "throw", "try", "uncaught", "unitDo", "unitLoop", "unitWrite", "unrolled", "watch", "while"]);

/**
* The variables the closures count (`VarDecl.held`: a reference to an environment, a row of the front end's arrays — the
Expand Down
10 changes: 6 additions & 4 deletions compiler/backend/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
* went on — a trigger of a few conditions and actions in a little over 200 bytes, not 2,408. A row a doubling trigger
* walks (`Graph.rows`) is stacked as one, its triggers 2,408 bytes apart.
*/
import { END, NOW, TRIGGER_BYTES, TRIGGER_OFF, TRIGGER_ON, type Act, type Cond, type Dword, type Graph, type Value } from "./graph";
import { END, NOW, PASS_END, TRIGGER_BYTES, TRIGGER_OFF, TRIGGER_ON, type Act, type Cond, type Dword, type Graph, type Value } from "./graph";
import { TRIGGER_LISTS, WAKE_AT, WAKE_NEXT, WAKE_TICK } from "./emit";
import type { ScriptString } from "../runtime";

Expand Down Expand Up @@ -281,11 +281,13 @@ export function layout(input: LayoutInput): OwnMap {
// past the program once woken.
const wake = (i: number) => (i ? `@wake#${i}` : "@wake"), init = (i: number) => (i ? `@init#${i}` : "@init");
const stop = "@stop", run = "@run", entry = (p: number) => `@entry${p}`, gate = (p: number) => `@gate${p}`;
const after = (i: number) => (i + 1 < runs.length ? wake(i + 1) : stop);
// (A run whose graph holds the map's events pass goes through it first.)
const first = (i: number) => runs[i].graph.pass ?? wake(i);
const after = (i: number) => (i + 1 < runs.length ? first(i + 1) : stop);
const list = (p: number) => TRIGGER_LISTS + 12 * p;
const listEnd = (p: number) => u(~(list(p) + 4));
// (Where each run's graph ends: the next run.)
const ends = new Map<string, string>(runs.map((_, i) => [i ? `${END}#${i}` : END, after(i)]));
const ends = new Map<string, string>(runs.flatMap((_, i): [string, string][] => [[i ? `${END}#${i}` : END, after(i)], [i ? `${PASS_END}#${i}` : PASS_END, wake(i)]]));
const endOf = (l: string) => ends.get(l) ?? l;
type T = { label: string; conds: Cond[]; acts: Act[]; next?: string | number; prev?: number; off?: boolean };
const triggers: T[] = runs.flatMap(({ graph, owner }, i) => [
Expand Down Expand Up @@ -323,7 +325,7 @@ export function layout(input: LayoutInput): OwnMap {
next: listEnd(p),
})),
// Every entry and gate back to ending at its list's end, the count of frames one more, then the first run.
{ label: run, conds: [], acts: [...players.flatMap((p): Act[] => [{ dword: { at: entry(p), field: "next" }, modifier: "set", value: listEnd(p) }, { dword: { at: gate(p), field: "next" }, modifier: "set", value: listEnd(p) }]), { dword: { var: NOW }, modifier: "add", value: 1 }], next: wake(0) },
{ label: run, conds: [], acts: [...players.flatMap((p): Act[] => [{ dword: { at: entry(p), field: "next" }, modifier: "set", value: listEnd(p) }, { dword: { at: gate(p), field: "next" }, modifier: "set", value: listEnd(p) }]), { dword: { var: NOW }, modifier: "add", value: 1 }], next: first(0) },
);
for (const t of runs.flatMap((r) => r.graph.triggers)) if (t.copy || t.island || t.call) throw new Error(`${t.label}: a copy, an island or a call has no place in a map of our own`);
// The triggers stacked, a row as one.
Expand Down
15 changes: 10 additions & 5 deletions compiler/backend/own.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { ANY_PLAYERS, emitBody, groupSlots, type MapPlayers } from "./emit";
import { NOW, type Act, type Cond, type Dword, type Graph, type Num, type Trigger, type Value } from "./graph";
import { TextPool, knownTexts } from "./texts";
import { HEAP_GLOBALS, growingArrays, heldVariables, isHeapGlobal, loopScoped } from "./heap";
import { UNIT_GLOBALS, unitValues } from "./units";
import { EVENT_GLOBALS, UNIT_GLOBALS, eventSources, isEventGlobal, unitValues } from "./units";
import { ownErrors } from "./errors";

export interface OwnRun {
Expand Down Expand Up @@ -66,7 +66,10 @@ export function emitOwn(r: Pick<CompileResult, "ir" | "triggers" | "strings">, p
const texts = pool.finish();
// Units as two numbers each (`units.ts`), then arrays that grow on the heap (`heap.ts`), once the texts' numbers are
// in: those passes copy what they rewrite.
const programs = known.map((p) => loopScoped(growingArrays(heldVariables(unitValues(p)))));
// (The map's events pass is one, laid out by the first program that has events, for what all of them loop over.)
const sources = eventSources(known);
const passer = known.findIndex((p) => eventSources(p).size);
const programs = known.map((p, k) => loopScoped(growingArrays(heldVariables(unitValues(p, { sources, pass: k === passer })))));
for (const [k, program] of programs.entries()) {
const slots = ownerSlots(program, players);
if (typeof slots === "string") return slots;
Expand All @@ -81,19 +84,20 @@ export function emitOwn(r: Pick<CompileResult, "ir" | "triggers" | "strings">, p
};
collect(program);
// (The heap's own variables, which no program declares, and the count of frames: one cell each for the map.)
for (const d of [...HEAP_GLOBALS, ...UNIT_GLOBALS, NOW_DECL]) decls.set(d.id, d);
for (const d of [...HEAP_GLOBALS, ...UNIT_GLOBALS, ...EVENT_GLOBALS, NOW_DECL]) decls.set(d.id, d);
const functions = new Map<string, FuncDecl>((program.functions ?? []).map((f) => [f.id, f]));
const arrays = new Map<string, ArrayDecl>((program.arrays ?? []).map((a) => [a.id, a]));
// The variables and arrays every run of the program has in common: one for every player of a per-player program.
const shared = new Set<string>(program.perPlayer ? [...[...decls.values()].filter((d) => d.shared).map((d) => d.id), ...[...arrays.values()].filter((a) => a.shared).map((a) => a.id)] : []);
// A program for one player runs as the first of its owners (the Python side's `slots[0]`), one for each player
// as each of them, in the order of the slots.
const firstRun = runs.length;
for (const player of program.perPlayer ? [...slots].sort((a, b) => a - b) : slots.slice(0, 1)) {
const n = runs.length;
// (The first run keeps the emitter's names; each other one's are its own, but for what its program shares.)
const graph = emitBody(program.body, decls, n ? `own${n}` : "own", functions, arrays, undefined, player, players, program.errors);
const graph = emitBody(program.body, decls, n ? `own${n}` : "own", functions, arrays, undefined, player, players, program.errors, runs.length === firstRun ? program.pass : undefined);
if (typeof graph === "string") return graph;
const name = (id: string) => (!n || isHeapGlobal(id) || id === NOW || UNIT_GLOBALS.some((d) => d.id === id) ? id : shared.has(id) ? (k ? `${id}#p${k}` : id) : `${id}#${n}`);
const name = (id: string) => (!n || isHeapGlobal(id) || id === NOW || isEventGlobal(id) || UNIT_GLOBALS.some((d) => d.id === id) ? id : shared.has(id) ? (k ? `${id}#p${k}` : id) : `${id}#${n}`);
const g = n ? renamed(graph, `#${n}`, name) : graph;
// An action of the game's names its text and its sound file by the script's strings: given here, for the layout.
const of = (i: number) => (i ? r.strings[i - 1] : undefined);
Expand Down Expand Up @@ -143,6 +147,7 @@ function renamed(g: Graph, suffix: string, name: (id: string) => string): Graph
...(g.templates ? { templates: g.templates.map((t) => ({ ...t, id: t.id + suffix })) } : {}),
...(g.rows ? { rows: g.rows.map((r) => r.map(label)) } : {}),
...(g.tasks ? { tasks: label(g.tasks) } : {}),
...(g.pass ? { pass: label(g.pass) } : {}),
};
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/backend/texts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1470,7 +1470,7 @@ const TEXT_BYTES = 1023;
const DISPLAY_TEXT = 9;
// (`call` and `pop` are numbers too: a list of numbers that starts with one is walked as statements, which does to
// each what walking it as a number would.)
const STMT_KINDS = new Set(["action", "assign", "assignBool", "assignText", "assignUnit", "block", "break", "buildText", "call", "cancel", "centerLocation", "continue", "declare", "declareArray", "do", "for", "graph", "if", "parallel", "pop", "print", "push", "releaseText", "remark", "return", "select", "setLength", "sleep", "start", "store", "storeText", "switch", "syncSend", "tableWrite", "textLoop", "throw", "try", "uncaught", "unitDo", "unitLoop", "unitWrite", "unrolled", "while"]);
const STMT_KINDS = new Set(["action", "assign", "assignBool", "assignText", "assignUnit", "block", "break", "buildText", "call", "cancel", "centerLocation", "continue", "declare", "declareArray", "do", "for", "graph", "if", "parallel", "pop", "print", "push", "releaseText", "remark", "return", "select", "setLength", "sleep", "start", "store", "storeText", "switch", "syncSend", "tableWrite", "textLoop", "throw", "try", "uncaught", "unitDo", "unitLoop", "unitWrite", "unrolled", "watch", "while"]);
function isStmt(o: unknown): boolean {
return !!o && typeof o === "object" && STMT_KINDS.has((o as { kind?: string }).kind ?? "");
}
Loading
Loading