components work

This commit is contained in:
sfja 2026-06-11 03:55:35 +02:00
parent cbc416da46
commit e8c8609217
6 changed files with 146 additions and 46 deletions

View File

@ -29,10 +29,10 @@ export class Board {
private wireCachedState = new Map<Wire, ir.State>(); private wireCachedState = new Map<Wire, ir.State>();
constructor() {} constructor(private repo: ComponentRepo) {}
static withExample(repo: ComponentRepo): Board { static withExample(repo: ComponentRepo): Board {
const board = new Board(); const board = new Board(repo);
board.placeComponent(repo.get("input"), v2(100, 100)); board.placeComponent(repo.get("input"), v2(100, 100));
board.placeComponent(repo.get("input"), v2(100, 200)); board.placeComponent(repo.get("input"), v2(100, 200));
board.placeComponent(repo.get("and"), v2(300, 150)); board.placeComponent(repo.get("and"), v2(300, 150));
@ -52,13 +52,10 @@ export class Board {
return board; return board;
} }
static fromSerialized( static fromSerialized(data: ser.Board, repo: ComponentRepo): Board {
data: ser.Board, const board = new Board(repo);
kindMap: Map<string, ComponentKind>,
): Board {
const board = new Board();
board.components = data.components.map((c) => board.components = data.components.map((c) =>
Component.fromSerialized(c, kindMap), Component.fromSerialized(c, repo.defs),
); );
board.joints = data.joints.map((j) => Joint.fromSerialized(j)); board.joints = data.joints.map((j) => Joint.fromSerialized(j));
board.wires = data.wires.map((w) => board.wires = data.wires.map((w) =>
@ -318,12 +315,20 @@ export class Board {
simulate(inputStates: Map<Component, boolean>) { simulate(inputStates: Map<Component, boolean>) {
console.log("Lowering to IR"); console.log("Lowering to IR");
const comp = this.toIr(); const comps = new Map<string, ir.Component>();
const comp = this.toIr("<main>", comps);
console.log("Before optimizing"); console.log("Before optimizing");
for (const [_label, comp] of comps) {
console.log(...new ir.ComponentPrinter().stringifyToConsole(comp));
}
console.log(...new ir.ComponentPrinter().stringifyToConsole(comp)); console.log(...new ir.ComponentPrinter().stringifyToConsole(comp));
for (const [_label, comp] of comps) {
new ir.ComponentOptimizer(comp, []).optimizeComponent();
}
const replacedStates: [ir.State, ir.State][] = []; const replacedStates: [ir.State, ir.State][] = [];
new ir.ComponentOptimizer(comp, replacedStates).optimize(); new ir.ComponentOptimizer(comp, replacedStates).optimizeMain();
for (const [oldState, newState] of replacedStates) { for (const [oldState, newState] of replacedStates) {
this.stateWireMap this.stateWireMap
@ -333,6 +338,9 @@ export class Board {
} }
console.log("After optimizing"); console.log("After optimizing");
for (const [_label, comp] of comps) {
console.log(...new ir.ComponentPrinter().stringifyToConsole(comp));
}
console.log(...new ir.ComponentPrinter().stringifyToConsole(comp)); console.log(...new ir.ComponentPrinter().stringifyToConsole(comp));
const inputs = this.inputArray(inputStates); const inputs = this.inputArray(inputStates);
@ -357,7 +365,7 @@ export class Board {
} }
} }
toIr(): ir.Component { toIr(label: string, comps: Map<string, ir.Component>): ir.Component {
for (const comp of this.components) { for (const comp of this.components) {
comp.markedWiresConnected = []; comp.markedWiresConnected = [];
} }
@ -384,7 +392,7 @@ export class Board {
outputIdcs.set(output, i); outputIdcs.set(output, i);
} }
const b = new ir.ComponentBuilder(inputs.length, outputs.length, "main"); const b = new ir.ComponentBuilder(inputs.length, outputs.length, label);
this.stateWireMap.clear(); this.stateWireMap.clear();
const wireStates = new Map<Wire, ir.State>(); const wireStates = new Map<Wire, ir.State>();
@ -429,14 +437,32 @@ export class Board {
return b.makeBinary("And", inputStmt(0), inputStmt(1)); return b.makeBinary("And", inputStmt(0), inputStmt(1));
case "or": case "or":
return b.makeBinary("Or", inputStmt(0), inputStmt(1)); return b.makeBinary("Or", inputStmt(0), inputStmt(1));
default: default: {
throw new Error("not implemented"); const savedBoard = this.repo.getSavedBoard(comp.kind.label);
if (!savedBoard) {
throw new Error(`no component '${comp.kind.label}'`);
}
const label = comp.kind.label;
const board = Board.fromSerialized(savedBoard, this.repo);
const ir = comps.get(label) ?? board.toIr(label, comps);
comps.set(label, ir);
return b.makeCall(
ir,
comp.kind.inputs.map((_, i) => inputStmt(i)),
);
}
} }
})(); })();
for (const [wire, connection] of comp.markedWiresConnected) { for (const [wire, connection] of comp.markedWiresConnected) {
if (connection.tag === "OutputPin") { if (connection.tag === "OutputPin") {
b.makeSetState(wireStates.get(wire)!, stmt); b.makeSetState(
wireStates.get(wire)!,
stmt.kind.tag === "Call" ? b.makeElem(stmt, connection.i) : stmt,
);
} }
} }
}, },
@ -491,6 +517,7 @@ export interface BoardVisitor {
export class ComponentRepo { export class ComponentRepo {
public defs = new Map<string, ComponentKind>(); public defs = new Map<string, ComponentKind>();
private savedBoards = new Map<string, ser.Board>();
static withDefaults(): ComponentRepo { static withDefaults(): ComponentRepo {
const repo = new ComponentRepo(); const repo = new ComponentRepo();
@ -507,12 +534,14 @@ export class ComponentRepo {
repo.defs = new Map( repo.defs = new Map(
data.defs.map((e) => [e[0], ComponentKind.fromSerialized(e[1])]), data.defs.map((e) => [e[0], ComponentKind.fromSerialized(e[1])]),
); );
repo.savedBoards = new Map(data.savedBoards);
return repo; return repo;
} }
serialize(): ser.ComponentRepo { serialize(): ser.ComponentRepo {
return { return {
defs: [...this.defs.entries()].map((e) => [e[0], e[1].serialize()]), defs: [...this.defs.entries()].map((e) => [e[0], e[1].serialize()]),
savedBoards: [...this.savedBoards],
}; };
} }
@ -531,6 +560,14 @@ export class ComponentRepo {
} }
return kind; return kind;
} }
addSavedBoard(ident: string, savedBoard: ser.Board) {
this.savedBoards.set(ident, savedBoard);
}
getSavedBoard(ident: string): ser.Board | null {
return this.savedBoards.get(ident) ?? null;
}
} }
export class Component { export class Component {

View File

@ -74,6 +74,8 @@ export class Editor {
} }
case "SelectTab": { case "SelectTab": {
this.switchTab(ev.idx); this.switchTab(ev.idx);
this.events.send({ tag: "SimulateRequest" });
break; break;
} }
case "CloseComponent": { case "CloseComponent": {
@ -89,7 +91,7 @@ export class Editor {
break; break;
} }
case "SimulateRequest": { case "SimulateRequest": {
// this.runSimulation(); this.runSimulation();
break; break;
} }
case "SaveRequest": { case "SaveRequest": {
@ -146,7 +148,6 @@ export class Editor {
runSimulation() { runSimulation() {
this.board.simulate(this.inputStates); this.board.simulate(this.inputStates);
this.events.send({ tag: "RenderRequest" });
} }
private onSelectTool(tool: string) { private onSelectTool(tool: string) {

View File

@ -10,7 +10,6 @@ export class Project {
private events: EventBus, private events: EventBus,
private boardEditors: BoardEditor[], private boardEditors: BoardEditor[],
public componentRepo: ComponentRepo, public componentRepo: ComponentRepo,
private savedBoards: Map<string, ser.Board>,
) { ) {
this.current = boardEditors[this.selectedIdx]; this.current = boardEditors[this.selectedIdx];
} }
@ -34,7 +33,6 @@ export class Project {
}, },
], ],
repo, repo,
new Map(),
); );
} }
@ -46,13 +44,10 @@ export class Project {
} }
save() { save() {
console.log("Saving"); // console.log("Saving");
const data = this.serialize(); const data = this.serialize();
globalThis.localStorage.setItem( globalThis.localStorage.setItem("nandsim", JSON.stringify(data));
"nandsim", // console.log(data);
JSON.stringify(this.serialize()),
);
console.log(data);
} }
private static fromSerialized(data: ser.Project, events: EventBus): Project { private static fromSerialized(data: ser.Project, events: EventBus): Project {
@ -62,11 +57,10 @@ export class Project {
data.boardEditors.map( data.boardEditors.map(
(data): BoardEditor => ({ (data): BoardEditor => ({
name: data.name, name: data.name,
board: Board.fromSerialized(data.board, repo.defs), board: Board.fromSerialized(data.board, repo),
}), }),
), ),
repo, repo,
new Map(data.savedBoards),
); );
return project; return project;
} }
@ -82,7 +76,6 @@ export class Project {
), ),
currentBoardEditorIdx: this.selectedIdx, currentBoardEditorIdx: this.selectedIdx,
componentRepo, componentRepo,
savedBoards: [...this.savedBoards],
}; };
} }
@ -103,7 +96,7 @@ export class Project {
newTab(): number { newTab(): number {
this.boardEditors.push({ this.boardEditors.push({
name: `(Unnamed ${this.boardEditors.length})`, name: `(Unnamed ${this.boardEditors.length})`,
board: new Board(), board: new Board(this.componentRepo),
}); });
this.events.send({ tag: "ShowSelectedTab", idx: this.selectedIdx }); this.events.send({ tag: "ShowSelectedTab", idx: this.selectedIdx });
return this.boardEditors.length - 1; return this.boardEditors.length - 1;
@ -114,11 +107,11 @@ export class Project {
this.current = this.boardEditors[this.selectedIdx]; this.current = this.boardEditors[this.selectedIdx];
this.events.send({ tag: "ShowSelectedTab", idx: this.selectedIdx }); this.events.send({ tag: "ShowSelectedTab", idx: this.selectedIdx });
this.events.send({ tag: "ShowSelectedTool", tool: this.current.name }); this.events.send({ tag: "ShowSelectedTool", tool: this.current.name });
this.events.send({ tag: "SaveRequest" });
} }
closeTab(): number { closeTab(): number {
const [removed] = this.boardEditors.splice(this.selectedIdx, 1); const [_removed] = this.boardEditors.splice(this.selectedIdx, 1);
this.savedBoards.set(removed.name, removed.board.serialize());
this.events.send({ tag: "SaveRequest" }); this.events.send({ tag: "SaveRequest" });
if (this.boardEditors.length === 0) { if (this.boardEditors.length === 0) {
@ -138,7 +131,13 @@ export class Project {
this.current.name, this.current.name,
this.current.board.toComponentKind(this.current.name), this.current.board.toComponentKind(this.current.name),
); );
this.componentRepo.addSavedBoard(
this.current.name,
this.current.board.serialize(),
);
this.events.send({ tag: "ShowSelectedTool", tool: this.current.name }); this.events.send({ tag: "ShowSelectedTool", tool: this.current.name });
this.events.send({ tag: "SaveRequest" });
} }
tabWithTool(name: string): number { tabWithTool(name: string): number {
@ -148,12 +147,12 @@ export class Project {
return foundIdx; return foundIdx;
} }
const saved = this.savedBoards.get(name); const saved = this.componentRepo.getSavedBoard(name);
if (!saved) throw new Error(`cannot open '${name}'`); if (!saved) throw new Error(`cannot open '${name}'`);
this.boardEditors.push({ this.boardEditors.push({
name: name, name: name,
board: Board.fromSerialized(saved, this.componentRepo.defs), board: Board.fromSerialized(saved, this.componentRepo),
}); });
this.events.send({ tag: "ShowSelectedTab", idx: this.selectedIdx }); this.events.send({ tag: "ShowSelectedTab", idx: this.selectedIdx });
return this.boardEditors.length - 1; return this.boardEditors.length - 1;

View File

@ -29,8 +29,10 @@ export class Stmt {
case "And": case "And":
case "Or": case "Or":
return [k.lhs, k.rhs]; return [k.lhs, k.rhs];
case "Component": case "Call":
return [...k.inputs]; return [...k.inputs];
case "Elem":
return [k.src];
} }
} }
@ -53,10 +55,11 @@ export class Stmt {
if (k.lhs === oldStmt) k.lhs = newStmt; if (k.lhs === oldStmt) k.lhs = newStmt;
if (k.rhs === oldStmt) k.rhs = newStmt; if (k.rhs === oldStmt) k.rhs = newStmt;
break; break;
case "Component": case "Call":
k.inputs = k.inputs.map((stmt) => k.inputs = k.inputs.map((stmt) => (stmt === oldStmt ? newStmt : stmt));
stmt === oldStmt ? newStmt : oldStmt, break;
); case "Elem":
if (k.src === oldStmt) k.src = newStmt;
break; break;
} }
} }
@ -82,7 +85,8 @@ export type StmtKind =
| { tag: "SetState"; state: State; src: Stmt } | { tag: "SetState"; state: State; src: Stmt }
| { tag: "Not"; op: Stmt } | { tag: "Not"; op: Stmt }
| { tag: "And" | "Or"; lhs: Stmt; rhs: Stmt } | { tag: "And" | "Or"; lhs: Stmt; rhs: Stmt }
| { tag: "Component"; comp: Component; inputs: Stmt[]; outputs: Stmt[] }; | { tag: "Call"; comp: Component; inputs: Stmt[] }
| { tag: "Elem"; src: Stmt; i: number };
export class State {} export class State {}
@ -123,6 +127,12 @@ export class ComponentBuilder {
makeBinary(tag: "And" | "Or", lhs: Stmt, rhs: Stmt): Stmt { makeBinary(tag: "And" | "Or", lhs: Stmt, rhs: Stmt): Stmt {
return this.makeStmt({ tag, lhs, rhs }); return this.makeStmt({ tag, lhs, rhs });
} }
makeCall(comp: Component, inputs: Stmt[]): Stmt {
return this.makeStmt({ tag: "Call", comp, inputs });
}
makeElem(src: Stmt, i: number): Stmt {
return this.makeStmt({ tag: "Elem", src, i });
}
private makeStmt(kind: StmtKind): Stmt { private makeStmt(kind: StmtKind): Stmt {
const stmt = new Stmt(kind); const stmt = new Stmt(kind);
@ -186,7 +196,15 @@ export class ComponentOptimizer {
private replacedStates: [State, State][], private replacedStates: [State, State][],
) {} ) {}
optimize() { optimizeMain() {
this.optimizeWithOptions(false);
}
optimizeComponent() {
this.optimizeWithOptions(true);
}
private optimizeWithOptions(removeUnusedStates: boolean) {
const score = () => this.comp.stmts.length * 100 + this.comp.states.length; const score = () => this.comp.stmts.length * 100 + this.comp.states.length;
let scoreBefore: number; let scoreBefore: number;
@ -199,6 +217,9 @@ export class ComponentOptimizer {
this.collapseStates(); this.collapseStates();
this.eliminateUnusedStates(); this.eliminateUnusedStates();
this.eliminateRedundantSetState(); this.eliminateRedundantSetState();
if (removeUnusedStates) {
this.eliminateIndependentSetState();
}
} while (score() !== scoreBefore); } while (score() !== scoreBefore);
} }
@ -309,6 +330,28 @@ export class ComponentOptimizer {
} }
} }
eliminateIndependentSetState() {
const mut = new StmtsMutater(this.comp, this.replacedStates);
const usedStates = new Set<State>();
for (const stmt of mut) {
const k = stmt.kind;
switch (k.tag) {
case "GetState":
usedStates.add(k.state);
break;
default:
break;
}
}
for (const stmt of [...mut]) {
if (stmt.kind.tag === "SetState" && !usedStates.has(stmt.kind.state)) {
mut.removeStmt(stmt);
}
}
}
private indexMap(): Map<Stmt, number> { private indexMap(): Map<Stmt, number> {
return new Map(this.comp.stmts.map((stmt, i) => [stmt, i])); return new Map(this.comp.stmts.map((stmt, i) => [stmt, i]));
} }
@ -355,7 +398,7 @@ export class ComponentPrinter {
"\\c(color: #d44949; font-weight: bold)$&\\c", "\\c(color: #d44949; font-weight: bold)$&\\c",
) )
.replaceAll( .replaceAll(
/(?:Null)|(?:Input)|(?:Output)|(?:GetState)|(?:SetState)|(?:Not)|(?:And)|(?:Or)|(?:Component)/g, /(?:Null)|(?:Input)|(?:Output)|(?:GetState)|(?:SetState)|(?:Not)|(?:And)|(?:Or)|(?:Call)|(?:Elem)/g,
"\\c(color: orange)$&\\c", "\\c(color: orange)$&\\c",
); );
@ -407,8 +450,10 @@ export class ComponentPrinter {
case "And": case "And":
case "Or": case "Or":
return `${stmtId(stmt)} = ${k.tag} ${stmtId(k.lhs)}, ${stmtId(k.rhs)}`; return `${stmtId(stmt)} = ${k.tag} ${stmtId(k.lhs)}, ${stmtId(k.rhs)}`;
case "Component": case "Call":
return `Component <...>`; return `${stmtId(stmt)} = Call ${k.comp.label} (${k.inputs.map((s) => stmtId(s)).join(", ")})`;
case "Elem":
return `${stmtId(stmt)} = Elem ${stmtId(k.src)}, ${k.i}`;
} }
} }
} }

View File

@ -2,7 +2,6 @@ export type Project = {
boardEditors: BoardEditor[]; boardEditors: BoardEditor[];
currentBoardEditorIdx: number; currentBoardEditorIdx: number;
componentRepo: ComponentRepo; componentRepo: ComponentRepo;
savedBoards: [string, Board][];
}; };
export type BoardEditor = { export type BoardEditor = {
@ -37,6 +36,7 @@ export type WireConnection =
export type ComponentRepo = { export type ComponentRepo = {
defs: [string, ComponentKind][]; defs: [string, ComponentKind][];
savedBoards: [string, Board][];
}; };
export type ComponentKind = { export type ComponentKind = {

View File

@ -16,6 +16,7 @@ export class Sim {
const regs = new Array<boolean>(comp.stmts.length).fill(false); const regs = new Array<boolean>(comp.stmts.length).fill(false);
const stateDependents = new Map<ir.State, number>(); const stateDependents = new Map<ir.State, number>();
const callOutput = new Map<ir.Stmt, boolean[]>();
const operation = <Ops extends ir.Stmt[]>( const operation = <Ops extends ir.Stmt[]>(
action: (...ops: boolean[]) => boolean, action: (...ops: boolean[]) => boolean,
@ -65,8 +66,25 @@ export class Sim {
case "Or": case "Or":
regs[i] = operation((a, b) => a || b, k.lhs, k.rhs); regs[i] = operation((a, b) => a || b, k.lhs, k.rhs);
break; break;
case "Component": case "Call": {
throw new Error("not implemented"); const outputs = new Array<boolean>(k.comp.outputs).fill(false);
new Sim(
k.comp,
k.inputs.map((stmt) => regs[stmtIdcs.get(stmt)!]),
outputs,
this.state,
).simulate();
callOutput.set(stmt, outputs);
break;
}
case "Elem": {
const outputs = callOutput.get(k.src);
if (!outputs) {
throw new Error();
}
regs[i] = outputs[k.i];
break;
}
} }
// console.log("Sim:", i, stmt.kind.tag, inputs, outputs, this.state); // console.log("Sim:", i, stmt.kind.tag, inputs, outputs, this.state);