Skip to content
36 changes: 30 additions & 6 deletions src/benchmark/Sieve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
App,
TimeValue,
Origin,
Log
Log,
GraphDebugLogger
} from "../core/internal";

Log.global.level = Log.levels.INFO;
Expand All @@ -21,7 +22,7 @@ class Ramp extends Reactor {
value = new OutPort<number>(this);

constructor(parent: Reactor, until = 100000, period: TimeValue) {
super(parent);
super(parent, "Ramp");
this.until = new Parameter(until);
this.next = new Action<number>(this, Origin.logical, period);
this.addReaction(
Expand Down Expand Up @@ -56,7 +57,7 @@ class Filter extends Reactor {
hasChild: State<boolean>;

constructor(parent: Reactor, startPrime: number, numberOfPrimes: number) {
super(parent);
super(parent, `FilterFor${startPrime}`);
// console.log("Created filter with prime: " + prime)
this.startPrime = new Parameter(startPrime);
this.localPrimes = new State(new Array<number>());
Expand Down Expand Up @@ -87,17 +88,24 @@ class Filter extends Reactor {
if (size < numberOfPrimes) {
seen.push(p);
console.log(`Found new prime number ${p}`);
if (!primes.has(p)) {
;
} else {
primes.delete(p);
}
} else {
// Potential prime found.
if (!hasChild.get()) {
const n = new Filter(this.getReactor(), p, numberOfPrimes);
const n = this.getReactor()._uncheckedAddSibling(Filter, p, numberOfPrimes);
// this.start(n)
// console.log("CREATING...")
// let x = this.create(Filter, [this.getReactor(), p])
// console.log("CREATED: " + x._getFullyQualifiedName())
// FIXME: weird hack. Maybe just accept writable ports as well?
const port = (out as unknown as WritablePort<number>).getPort();
console.log("connecting......");
this.connect(port, n.inp);
printSieveGraph();
// FIXME: this updates the dependency graph, but it doesn't redo the topological sort
// For a pipeline like this one, it is not necessary, but in general it is.
// Can we avoid redoing the entire sort?
Expand Down Expand Up @@ -126,12 +134,28 @@ class Sieve extends App {
success?: () => void,
fail?: () => void
) {
super(timeout, keepAlive, fast, success, fail);
super(timeout, keepAlive, fast, success, fail, name);
this.source = new Ramp(this, 100000, TimeValue.nsec(1));
this.filter = new Filter(this, 2, 1000);
this._connect(this.source.value, this.filter.inp);
}
}

const sieve = new Sieve("Sieve");

const sieve = new Sieve("Sieve", undefined, undefined, undefined, ()=>{globalThis.graphDebugLogger?.write("debug0.json")});

const printSieveGraph = (): void => {
const graph = sieve["_getPrecedenceGraph"]();
const hierarchy = sieve._getNodeHierarchyLevels();
const str = graph.toMermaidString(undefined, hierarchy);
const time = sieve["util"].getElapsedLogicalTime();
console.log(str);
console.log(time);
}

globalThis.graphDebugLogger = new GraphDebugLogger(sieve);
globalThis.recording = false;

const primes = new Set([2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 9972, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997]);

sieve._start();
11 changes: 5 additions & 6 deletions src/core/component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {Runtime} from "./internal";
import {Reactor, App, MultiPort, IOPort, Bank} from "./internal";
import {v4 as uuidv4} from "uuid";

/**
* Base class for named objects embedded in a hierarchy of reactors. Each
Expand All @@ -17,7 +18,7 @@ export abstract class Component {
* A symbol that identifies this component, and it also used to selectively
* grant access to its privileged functions.
*/
protected _key = Symbol("Unique component identifier");
protected _key = Symbol(uuidv4());

/**
* The container of this component. Each component is contained by a
Expand Down Expand Up @@ -186,11 +187,9 @@ export abstract class Component {
public _getName(): string {
let name;

if (this instanceof App) {
if (this instanceof Reactor) {
name = this._name;
} else {
name = Component.keyOfMatchingEntry(this, this._container);
}
}

if (name === "" && this instanceof IOPort) {
name = Component.keyOfMatchingMultiport(this, this._container);
Expand All @@ -200,7 +199,7 @@ export abstract class Component {
name = Component.keyOfMatchingBank(this, this._container);
}

if (name !== "") {
if (name != null && name !== "") {
return name;
} else {
return this.constructor.name;
Expand Down
82 changes: 73 additions & 9 deletions src/core/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,56 @@
* @author Marten Lohstroh <[email protected]>
*/

import { DebugLogger } from "util";
import {Reaction} from "./reaction";
import type {Sortable, Variable} from "./types";
import {Log} from "./util";
import {GraphDebugLogger, Log} from "./util";

// TODO: find a way to to this with decorators.
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type, @typescript-eslint/no-explicit-any
/* const debugLoggerDecorator = (target: any, context: ClassMethodDecoratorContext) => {
if (context.kind === "method") {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return function (this: any, ...args: unknown[]): any {
console.log(`${context.name.toString()} is called.`);
console.log(`Tracestack: ${(new Error()).stack}`);
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
return target.call(this, ...args);
}
}
} */

declare global {
// eslint-disable-next-line no-var
var graphDebugLogger: GraphDebugLogger | undefined;
// eslint-disable-next-line no-var
var recording: boolean;
}

const debugHelper = (stacktrace: Error): void => {
if (globalThis.recording) { return; }
// If recording now, do not record any subsequent operations,
// as recursion hell might involve when calling `capture`,
// causing infinite loop.
globalThis.recording = true;
if (globalThis.graphDebugLogger == null) {
return
}

const debuglogger = globalThis.graphDebugLogger;
debuglogger.capture(stacktrace);
globalThis.recording = false;
};

/**
* A generic precedence graph.
*/

export interface HierarchyGraphLevel<T> {
name: string,
nodes: T[],
childrenLevels: Array<HierarchyGraphLevel<T>>,
}
export class PrecedenceGraph<T> {
/**
* A map from nodes to the set of their upstream neighbors.
Expand Down Expand Up @@ -47,6 +90,7 @@ export class PrecedenceGraph<T> {
* @param node
*/
addNode(node: T): void {
debugHelper(new Error("addNode"));
if (!this.adjacencyMap.has(node)) {
this.adjacencyMap.set(node, new Set());
}
Expand Down Expand Up @@ -137,6 +181,7 @@ export class PrecedenceGraph<T> {
* @param downstream The node at which the directed edge ends.
*/
addEdge(upstream: T, downstream: T): void {
debugHelper(new Error("addEdge"));
const deps = this.adjacencyMap.get(downstream);
if (deps == null) {
this.adjacencyMap.set(downstream, new Set([upstream]));
Expand Down Expand Up @@ -189,10 +234,10 @@ export class PrecedenceGraph<T> {
* @param edgesWithIssue An array containing arrays with [origin, effect].
* Denotes edges in the graph that causes issues to the execution, will be visualized as `--x` in mermaid.
*/
toMermaidString(edgesWithIssue?: Array<[T, T]>): string {
toMermaidString(edgesWithIssue?: Array<[T, T]>, hierarchy?: HierarchyGraphLevel<T>, uniqueNames?: boolean): string {
if (edgesWithIssue == null) edgesWithIssue = [];
let result = "graph";
const nodeToNumber = new Map<T, number>();
let result = "graph\n";
const nodeToSymbolString = new Map<T, string>();
const getNodeString = (node: T, def: string): string => {
if (node == null || node?.toString === Object.prototype.toString) {
console.error(
Expand All @@ -205,27 +250,46 @@ export class PrecedenceGraph<T> {
return node.toString();
};

// Build a block here since we only need `counter` temporarily here
if (hierarchy != null) {
let counter = 0;
let subgraphCounter = 0;
const recurse = (h: HierarchyGraphLevel<T>, level: number): void => {
const indent = " ".repeat(level);
result += level === 0 ? "" : `${indent}subgraph "${(uniqueNames ?? false) ? h.name : `sg${subgraphCounter++}`}"\n`;
for (const v of h.nodes) {
result += `${indent} ${counter}["${getNodeString(v, String(counter))}"]\n`
nodeToSymbolString.set(v, `${counter++}`);
}
for (const c of h.childrenLevels) {
recurse(c, level + 1);
}
result += level === 0 ? "" : `${indent}end\n`;
}

recurse(hierarchy, 0);
}

// Build a block here since we only need `counter` temporarily here
// We use numbers instead of names of reactors directly as node names
// in mermaid.js because mermaid has strict restrictions regarding
// what could be used as names of the node.
{
let counter = 0;
for (const v of this.getNodes()) {
result += `\n${counter}["${getNodeString(v, String(counter))}"]`;
nodeToNumber.set(v, counter++);
if (nodeToSymbolString.has(v)) { continue; }
result += `\nmissing${counter}["${getNodeString(v, String(counter))}"]`;
nodeToSymbolString.set(v, `missing${counter++}`);
}
}
// This is the effect
for (const s of this.getNodes()) {
// This is the origin
for (const t of this.getUpstreamNeighbors(s)) {
result += `\n${nodeToNumber.get(t)}`;
result += `\n${nodeToSymbolString.get(t)}`;
result += edgesWithIssue.some((v) => v[0] === t && v[1] === s)
? " --x "
: " --> ";
result += `${nodeToNumber.get(s)}`;
result += `${nodeToSymbolString.get(s)}`;
}
}
return result;
Expand Down
4 changes: 4 additions & 0 deletions src/core/port.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ export abstract class IOPort<T> extends Port<T> {
}
}

public checkKey(key: symbol | undefined): boolean {
return this._key === key;
}

/**
* Only the holder of the key may obtain a writable port.
* @param key
Expand Down
20 changes: 11 additions & 9 deletions src/core/reaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ export class Reaction<T extends Variable[]>
private deadline?: TimeValue,
private readonly late: (...args: ArgList<T>) => void = () => {
Log.global.warn("Deadline violation occurred!");
}
},
readonly name?: string
) {}

/**
Expand Down Expand Up @@ -181,9 +182,9 @@ export class Reaction<T extends Variable[]>
* Return string representation of the reaction.
*/
public toString(): string {
return `${this.reactor._getFullyQualifiedName()}[R${this.reactor._getReactionIndex(
this as unknown as Reaction<Variable[]>
)}]`;
return `${this.reactor._getFullyQualifiedName()}` +
((this.name != null) ? `${this.name} aka` : "") +
`[R${this.reactor._getReactionIndex(this as unknown as Reaction<Variable[]>)}]`;
}
}

Expand All @@ -199,18 +200,19 @@ export class Mutation<T extends Variable[]> extends Reaction<T> {
args: [...ArgList<T>],
react: (...args: ArgList<T>) => void,
deadline?: TimeValue,
late?: (...args: ArgList<T>) => void
late?: (...args: ArgList<T>) => void,
name?: string
) {
super(__parent__, sandbox, trigs, args, react, deadline, late);
super(__parent__, sandbox, trigs, args, react, deadline, late, name);
this.parent = __parent__;
}

/**
* @override
*/
public toString(): string {
return `${this.parent._getFullyQualifiedName()}[M${this.parent._getReactionIndex(
this as unknown as Reaction<Variable[]>
)}]`;
return `${this.parent._getFullyQualifiedName()}` +
((this.name != null) ? `${this.name} aka` : "") +
`[M${this.parent._getReactionIndex(this as unknown as Reaction<Variable[]>)}]`;
}
}
Loading