tla-ts 0.0.1 → 0.0.3-dev.6979965
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -40
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +215 -30
- package/dist/index.d.ts +215 -30
- package/dist/index.js +1 -1
- package/dist/parallel-worker.cjs +2 -2
- package/dist/parallel-worker.js +2 -2
- package/dist/parallel.cjs +1 -1
- package/dist/parallel.js +2 -2
- package/dist/run-cli.cjs +20 -1
- package/dist/run-cli.js +20 -1
- package/dist/transfer.cjs +388 -39
- package/dist/transfer.js +388 -39
- package/package.json +6 -2
- package/src/ast.ts +122 -5
- package/src/bags.ts +161 -71
- package/src/builtins.ts +203 -23
- package/src/bundled-modules.ts +63 -32
- package/src/check.ts +945 -238
- package/src/cli-options.ts +58 -0
- package/src/codegen.ts +52 -18
- package/src/config.ts +39 -11
- package/src/environment.ts +485 -140
- package/src/errors.ts +59 -1
- package/src/evaluate.ts +489 -137
- package/src/exit-codes.ts +75 -0
- package/src/fingerprints.ts +48 -13
- package/src/index.ts +9 -4
- package/src/instances.ts +221 -66
- package/src/java-overrides.ts +241 -0
- package/src/json.ts +153 -72
- package/src/levels.ts +850 -0
- package/src/lexer.ts +103 -28
- package/src/liveness.ts +47 -91
- package/src/obligation-action.ts +384 -0
- package/src/obligation-anon.ts +575 -0
- package/src/obligation-deref.ts +637 -0
- package/src/obligation-expression.ts +1060 -0
- package/src/obligation-format.ts +406 -0
- package/src/obligation-generate.ts +1074 -0
- package/src/obligation-levels.ts +329 -0
- package/src/obligation-module.ts +1042 -0
- package/src/obligation-printer.ts +889 -0
- package/src/obligation-proof.ts +76 -0
- package/src/obligation-source.ts +1084 -0
- package/src/obligation-tuples.ts +184 -0
- package/src/obligations.ts +316 -0
- package/src/parallel-worker.ts +121 -123
- package/src/parallel.ts +167 -83
- package/src/parser.ts +958 -205
- package/src/pretty-print.ts +259 -0
- package/src/randomization.ts +24 -113
- package/src/resolve.ts +472 -41
- package/src/run-cli.ts +427 -61
- package/src/runtime.ts +63 -28
- package/src/spec-description.ts +509 -0
- package/src/standard-levels.ts +105 -0
- package/src/standard-modules.ts +223 -92
- package/src/subexpressions.ts +21 -35
- package/src/substitution-levels.ts +184 -0
- package/src/successors.ts +261 -46
- package/src/symmetry.ts +65 -21
- package/src/tableau.ts +43 -37
- package/src/tlc-ext.ts +120 -33
- package/src/tlc-module.ts +71 -47
- package/src/tlc-modules.ts +1022 -0
- package/src/tlc-order.ts +497 -0
- package/src/tlc-random.ts +411 -0
- package/src/tlc-summary.ts +109 -0
- package/src/transfer.ts +26 -5
- package/src/values.ts +1231 -176
package/src/parallel-worker.ts
CHANGED
|
@@ -1,30 +1,42 @@
|
|
|
1
1
|
import { createCanonicalizer } from "./canonical";
|
|
2
2
|
import { createSearch } from "./check";
|
|
3
3
|
import { createEnvironment } from "./environment";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import {
|
|
5
|
+
createFingerprintTable,
|
|
6
|
+
insertSharedFingerprint,
|
|
7
|
+
isSharedFingerprintsFull,
|
|
8
|
+
writeStateFingerprint,
|
|
9
|
+
} from "./fingerprints";
|
|
10
|
+
import type {
|
|
11
|
+
CoordinatorMessage,
|
|
12
|
+
LevelMessage,
|
|
13
|
+
Port,
|
|
14
|
+
StartMessage,
|
|
15
|
+
WorkerMessage,
|
|
16
|
+
} from "./parallel";
|
|
17
|
+
import { getSuccessors, type Successor } from "./successors";
|
|
18
|
+
import { getTlcStringOrder, getTlcVariableOrder } from "./tlc-order";
|
|
19
|
+
import { OUT_DEGREE_BUCKETS } from "./tlc-summary";
|
|
8
20
|
import { createStateDecoder, createStateEncoder, type EncodedStates } from "./transfer";
|
|
9
|
-
import type
|
|
21
|
+
import { getRankedStringCount, setStringOrder, type Value } from "./values";
|
|
10
22
|
|
|
11
23
|
const WORKER_THREADS_MODULE = "node:worker_threads";
|
|
12
24
|
|
|
13
|
-
interface Pending {
|
|
14
|
-
states: Value[][];
|
|
15
|
-
parents: Value[][];
|
|
16
|
-
fingerprints: number[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
25
|
interface PeerMessage {
|
|
20
|
-
phase: "
|
|
26
|
+
phase: "balance";
|
|
21
27
|
level: number;
|
|
22
28
|
from: number;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
states: EncodedStates;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface Expansion {
|
|
33
|
+
origin: number;
|
|
34
|
+
successors: Successor[] | undefined;
|
|
35
|
+
index: number;
|
|
36
|
+
generated: number;
|
|
37
|
+
newStates: Int32Array;
|
|
38
|
+
next: Value[][];
|
|
39
|
+
fingerprintStates: Value[][];
|
|
28
40
|
}
|
|
29
41
|
|
|
30
42
|
interface Transfer {
|
|
@@ -33,12 +45,12 @@ interface Transfer {
|
|
|
33
45
|
count: number;
|
|
34
46
|
}
|
|
35
47
|
|
|
36
|
-
const getBalancePlan = (sizes: number[])
|
|
48
|
+
const getBalancePlan = (sizes: number[]) => {
|
|
37
49
|
const total = sizes.reduce((sum, size) => sum + size, 0);
|
|
38
50
|
const average = total / sizes.length;
|
|
39
51
|
if (Math.max(...sizes) - Math.min(...sizes) <= Math.max(64, average / 4)) return [];
|
|
40
52
|
const targets = sizes.map(
|
|
41
|
-
(_, index) => Math.floor(
|
|
53
|
+
(_, index) => Math.floor(average) + (index < total % sizes.length ? 1 : 0),
|
|
42
54
|
);
|
|
43
55
|
const donors = sizes.flatMap((size, index) =>
|
|
44
56
|
size > targets[index] ? [{ index, amount: size - targets[index] }] : [],
|
|
@@ -59,7 +71,7 @@ const getBalancePlan = (sizes: number[]): Transfer[] => {
|
|
|
59
71
|
return transfers;
|
|
60
72
|
};
|
|
61
73
|
|
|
62
|
-
const getParentPort = async ()
|
|
74
|
+
const getParentPort = async () => {
|
|
63
75
|
if (typeof (globalThis as { postMessage?: unknown }).postMessage === "function") {
|
|
64
76
|
return globalThis as unknown as Port;
|
|
65
77
|
}
|
|
@@ -98,21 +110,31 @@ const createInbox = () => {
|
|
|
98
110
|
|
|
99
111
|
const run = (start: StartMessage, parent: Port) => {
|
|
100
112
|
const modules = new Map(start.modules);
|
|
101
|
-
const
|
|
102
|
-
|
|
113
|
+
const loadModule = (name: string) => modules.get(name);
|
|
114
|
+
const classes = new Map(start.classes);
|
|
115
|
+
setStringOrder(getTlcStringOrder(start.source, start.config, loadModule));
|
|
116
|
+
const startupStrings = getRankedStringCount();
|
|
117
|
+
const specification = createEnvironment(start.config, {
|
|
118
|
+
source: start.source,
|
|
119
|
+
loadModule,
|
|
120
|
+
loadClass: (name) => classes.get(name),
|
|
103
121
|
print: () => {},
|
|
104
122
|
files: { readFile: undefined, writeFile: undefined },
|
|
105
123
|
});
|
|
106
124
|
const environment = specification.environment;
|
|
107
|
-
const search = createSearch(start.config, environment)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
125
|
+
const search = createSearch(start.config, environment, () =>
|
|
126
|
+
getTlcVariableOrder(start.source, loadModule),
|
|
127
|
+
);
|
|
128
|
+
const nextAction = search?.spec.next;
|
|
129
|
+
if (!search || nextAction === undefined)
|
|
130
|
+
return parent.postMessage({ type: "abort" } satisfies WorkerMessage);
|
|
111
131
|
const self = start.index;
|
|
112
132
|
const canonicalize = createCanonicalizer();
|
|
113
|
-
const decode = createStateDecoder(
|
|
114
|
-
const
|
|
115
|
-
|
|
133
|
+
const decode = createStateDecoder(environment.variables.length);
|
|
134
|
+
const shared = { fingerprints: start.fingerprints };
|
|
135
|
+
// Under symmetry, a successor the worker generated before has the representative it had then,
|
|
136
|
+
// so the worker skips the search for one.
|
|
137
|
+
const generatedStates = search.isSymmetric ? createFingerprintTable() : undefined;
|
|
116
138
|
const fingerprint = new Int32Array(2);
|
|
117
139
|
const inbox = createInbox();
|
|
118
140
|
const peers = start.ports.flatMap((port, index) => (port ? [{ port, index }] : []));
|
|
@@ -120,75 +142,44 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
120
142
|
peer.port.addEventListener("message", (event) => inbox.deliver(event.data as PeerMessage));
|
|
121
143
|
peer.port.start?.();
|
|
122
144
|
});
|
|
123
|
-
for (let index = 0; index < start.seedFingerprints.length; index += 2) {
|
|
124
|
-
table.insert(start.seedFingerprints[index], start.seedFingerprints[index + 1]);
|
|
125
|
-
}
|
|
126
145
|
const frontier = { states: decode(start.seed).map(canonicalize) };
|
|
127
146
|
const send = (index: number, message: PeerMessage, transfer: unknown[]) =>
|
|
128
147
|
start.ports[index]?.postMessage(message, transfer);
|
|
129
148
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
for (
|
|
149
|
+
// The worker pauses before a successor when the shared table needs to grow, and resumes there.
|
|
150
|
+
const expand = (expansion: Expansion) => {
|
|
151
|
+
const states = frontier.states;
|
|
152
|
+
for (; expansion.origin < states.length; expansion.origin++) {
|
|
153
|
+
const state = states[expansion.origin];
|
|
154
|
+
if (!expansion.successors) {
|
|
155
|
+
expansion.successors = getSuccessors(nextAction, state, environment, search.spec.scope);
|
|
156
|
+
expansion.generated += expansion.successors.length;
|
|
157
|
+
if (expansion.successors.length === 0 && start.config.checkDeadlock) return undefined;
|
|
158
|
+
}
|
|
159
|
+
const successors = expansion.successors;
|
|
160
|
+
for (; expansion.index < successors.length; expansion.index++) {
|
|
161
|
+
if (isSharedFingerprintsFull(shared.fingerprints)) return "paused";
|
|
162
|
+
const successor = successors[expansion.index];
|
|
142
163
|
if (search.findViolatedActionProperty(state, successor.state)) return undefined;
|
|
143
164
|
if (!search.satisfiesActionConstraints(state, successor.state)) continue;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (owner === self) {
|
|
148
|
-
if (!table.insert(fingerprint[0], fingerprint[1])) continue;
|
|
149
|
-
if (search.findViolatedInvariant(successor.state, state)) return undefined;
|
|
150
|
-
if (search.satisfiesConstraints(successor.state, state)) next.push(successor.state);
|
|
151
|
-
else table.remove(fingerprint[0], fingerprint[1]);
|
|
152
|
-
continue;
|
|
165
|
+
if (generatedStates) {
|
|
166
|
+
writeStateFingerprint(successor.state, fingerprint);
|
|
167
|
+
if (!generatedStates.insert(fingerprint[0], fingerprint[1])) continue;
|
|
153
168
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const answer = (fingerprints: Int32Array | number[]) => {
|
|
164
|
-
const bits = new Uint8Array(fingerprints.length / 2);
|
|
165
|
-
for (let index = 0; index < bits.length; index++) {
|
|
166
|
-
bits[index] = table.insert(fingerprints[index * 2], fingerprints[index * 2 + 1]) ? 1 : 0;
|
|
167
|
-
}
|
|
168
|
-
return bits;
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
const commit = (pending: Pending, bits: Uint8Array, next: Value[][]) => {
|
|
172
|
-
const unmarks: number[] = [];
|
|
173
|
-
for (let index = 0; index < bits.length; index++) {
|
|
174
|
-
if (bits[index] === 0) continue;
|
|
175
|
-
const state = pending.states[index];
|
|
176
|
-
const parentState = pending.parents[index];
|
|
177
|
-
if (search.findViolatedInvariant(state, parentState)) return undefined;
|
|
178
|
-
if (search.satisfiesConstraints(state, parentState)) {
|
|
179
|
-
next.push(state);
|
|
180
|
-
continue;
|
|
169
|
+
canonicalize(successor.state);
|
|
170
|
+
const fingerprintState = search.getFingerprintState(successor.state);
|
|
171
|
+
writeStateFingerprint(fingerprintState, fingerprint);
|
|
172
|
+
if (!insertSharedFingerprint(shared.fingerprints, fingerprint[0], fingerprint[1])) continue;
|
|
173
|
+
if (search.findViolatedInvariant(successor.state, state)) return undefined;
|
|
174
|
+
if (!search.satisfiesConstraints(successor.state, state)) continue;
|
|
175
|
+
expansion.next.push(successor.state);
|
|
176
|
+
expansion.fingerprintStates.push(fingerprintState);
|
|
177
|
+
expansion.newStates[expansion.origin]++;
|
|
181
178
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
return Int32Array.from(unmarks);
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const removeAll = (fingerprints: Int32Array) => {
|
|
189
|
-
for (let index = 0; index < fingerprints.length; index += 2) {
|
|
190
|
-
table.remove(fingerprints[index], fingerprints[index + 1]);
|
|
179
|
+
expansion.successors = undefined;
|
|
180
|
+
expansion.index = 0;
|
|
191
181
|
}
|
|
182
|
+
return "done";
|
|
192
183
|
};
|
|
193
184
|
|
|
194
185
|
const balance = async (level: number, next: Value[][], sizes: number[]) => {
|
|
@@ -205,48 +196,50 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
205
196
|
send(index, { phase: "balance", level, from: self, states }, [states.words.buffer]);
|
|
206
197
|
});
|
|
207
198
|
const incoming = await inbox.receive("balance", level, peers.length);
|
|
208
|
-
incoming.forEach((message) =>
|
|
209
|
-
if (message.states) next.push(...decode(message.states).map(canonicalize));
|
|
210
|
-
});
|
|
199
|
+
incoming.forEach((message) => next.push(...decode(message.states).map(canonicalize)));
|
|
211
200
|
return true;
|
|
212
201
|
};
|
|
213
202
|
|
|
203
|
+
const waiters = { resume: () => {}, balance: (_sizes: number[]) => {} };
|
|
204
|
+
const report = (message: WorkerMessage) =>
|
|
205
|
+
parent.postMessage(message, message.type === "level" ? [message.fingerprints.buffer] : []);
|
|
206
|
+
|
|
214
207
|
const runLevel = async (level: number): Promise<LevelMessage | undefined> => {
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const unmarks = commit(expanded.pending[reply.from], reply.bits ?? new Uint8Array(), next);
|
|
230
|
-
if (!unmarks) return undefined;
|
|
231
|
-
send(reply.from, { phase: "sizes", level, from: self, unmarks, size: 0 }, [unmarks.buffer]);
|
|
208
|
+
const expansion: Expansion = {
|
|
209
|
+
origin: 0,
|
|
210
|
+
successors: undefined,
|
|
211
|
+
index: 0,
|
|
212
|
+
generated: 0,
|
|
213
|
+
newStates: new Int32Array(frontier.states.length),
|
|
214
|
+
next: [],
|
|
215
|
+
fingerprintStates: [],
|
|
216
|
+
};
|
|
217
|
+
for (let status = expand(expansion); status !== "done"; status = expand(expansion)) {
|
|
218
|
+
if (status === undefined) return undefined;
|
|
219
|
+
const resumed = new Promise<void>((resolve) => (waiters.resume = resolve));
|
|
220
|
+
report({ type: "paused" });
|
|
221
|
+
await resumed;
|
|
232
222
|
}
|
|
223
|
+
if (getRankedStringCount() !== startupStrings) return undefined;
|
|
224
|
+
const next = expansion.next;
|
|
233
225
|
const added = next.length;
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
received.forEach((message) => {
|
|
241
|
-
if (message.unmarks) removeAll(message.unmarks);
|
|
242
|
-
else sizes[message.from] = message.size ?? 0;
|
|
243
|
-
});
|
|
244
|
-
if (!(await balance(level, next, sizes))) return undefined;
|
|
226
|
+
const outDegrees = Array.from({ length: OUT_DEGREE_BUCKETS }, () => 0);
|
|
227
|
+
expansion.newStates.forEach((degree) => outDegrees[Math.min(degree, OUT_DEGREE_BUCKETS - 1)]++);
|
|
228
|
+
const fingerprints = search.getStoredFingerprints(next, expansion.fingerprintStates);
|
|
229
|
+
const sizes = new Promise<number[]>((resolve) => (waiters.balance = resolve));
|
|
230
|
+
report({ type: "expanded", from: self, added });
|
|
231
|
+
if (!(await balance(level, next, await sizes))) return undefined;
|
|
245
232
|
frontier.states = next;
|
|
246
|
-
return {
|
|
233
|
+
return {
|
|
234
|
+
type: "level",
|
|
235
|
+
level,
|
|
236
|
+
generated: expansion.generated,
|
|
237
|
+
added,
|
|
238
|
+
outDegrees,
|
|
239
|
+
fingerprints,
|
|
240
|
+
};
|
|
247
241
|
};
|
|
248
242
|
|
|
249
|
-
const report = (message: WorkerMessage) => parent.postMessage(message);
|
|
250
243
|
const step = (level: number) => {
|
|
251
244
|
runLevel(level).then(
|
|
252
245
|
(message) => report(message ?? { type: "abort" }),
|
|
@@ -254,8 +247,13 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
254
247
|
);
|
|
255
248
|
};
|
|
256
249
|
parent.addEventListener("message", (event) => {
|
|
257
|
-
const message = event.data as
|
|
250
|
+
const message = event.data as CoordinatorMessage;
|
|
258
251
|
if (message.type === "level") step(message.level);
|
|
252
|
+
if (message.type === "resize") {
|
|
253
|
+
shared.fingerprints = message.fingerprints;
|
|
254
|
+
waiters.resume();
|
|
255
|
+
}
|
|
256
|
+
if (message.type === "balance") waiters.balance(message.sizes);
|
|
259
257
|
});
|
|
260
258
|
report({ type: "ready" });
|
|
261
259
|
};
|