tla-ts 0.0.1 → 0.0.3-dev.763cc71
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 +81 -42
- 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 +2 -2
- 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 +78 -30
- package/src/parallel.ts +116 -62
- 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,19 +1,21 @@
|
|
|
1
1
|
import { createCanonicalizer } from "./canonical";
|
|
2
2
|
import { createSearch } from "./check";
|
|
3
3
|
import { createEnvironment } from "./environment";
|
|
4
|
-
import { createFingerprintTable, getFingerprintOwner,
|
|
4
|
+
import { createFingerprintTable, getFingerprintOwner, writeStateFingerprint } from "./fingerprints";
|
|
5
5
|
import type { LevelMessage, Port, StartMessage, WorkerMessage } from "./parallel";
|
|
6
|
-
import { parseModule } from "./parser";
|
|
7
6
|
import { getSuccessors } from "./successors";
|
|
7
|
+
import { getTlcStringOrder, getTlcVariableOrder } from "./tlc-order";
|
|
8
|
+
import { OUT_DEGREE_BUCKETS } from "./tlc-summary";
|
|
8
9
|
import { createStateDecoder, createStateEncoder, type EncodedStates } from "./transfer";
|
|
9
|
-
import type
|
|
10
|
+
import { getRankedStringCount, setStringOrder, type Value } from "./values";
|
|
10
11
|
|
|
11
12
|
const WORKER_THREADS_MODULE = "node:worker_threads";
|
|
12
13
|
|
|
13
14
|
interface Pending {
|
|
14
15
|
states: Value[][];
|
|
15
|
-
|
|
16
|
+
origins: number[];
|
|
16
17
|
fingerprints: number[];
|
|
18
|
+
fingerprintStates: Value[][];
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
interface PeerMessage {
|
|
@@ -33,12 +35,12 @@ interface Transfer {
|
|
|
33
35
|
count: number;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
const getBalancePlan = (sizes: number[])
|
|
38
|
+
const getBalancePlan = (sizes: number[]) => {
|
|
37
39
|
const total = sizes.reduce((sum, size) => sum + size, 0);
|
|
38
40
|
const average = total / sizes.length;
|
|
39
41
|
if (Math.max(...sizes) - Math.min(...sizes) <= Math.max(64, average / 4)) return [];
|
|
40
42
|
const targets = sizes.map(
|
|
41
|
-
(_, index) => Math.floor(
|
|
43
|
+
(_, index) => Math.floor(average) + (index < total % sizes.length ? 1 : 0),
|
|
42
44
|
);
|
|
43
45
|
const donors = sizes.flatMap((size, index) =>
|
|
44
46
|
size > targets[index] ? [{ index, amount: size - targets[index] }] : [],
|
|
@@ -59,7 +61,7 @@ const getBalancePlan = (sizes: number[]): Transfer[] => {
|
|
|
59
61
|
return transfers;
|
|
60
62
|
};
|
|
61
63
|
|
|
62
|
-
const getParentPort = async ()
|
|
64
|
+
const getParentPort = async () => {
|
|
63
65
|
if (typeof (globalThis as { postMessage?: unknown }).postMessage === "function") {
|
|
64
66
|
return globalThis as unknown as Port;
|
|
65
67
|
}
|
|
@@ -98,21 +100,33 @@ const createInbox = () => {
|
|
|
98
100
|
|
|
99
101
|
const run = (start: StartMessage, parent: Port) => {
|
|
100
102
|
const modules = new Map(start.modules);
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
+
const loadModule = (name: string) => modules.get(name);
|
|
104
|
+
const classes = new Map(start.classes);
|
|
105
|
+
setStringOrder(getTlcStringOrder(start.source, start.config, loadModule));
|
|
106
|
+
const startupStrings = getRankedStringCount();
|
|
107
|
+
const specification = createEnvironment(start.config, {
|
|
108
|
+
source: start.source,
|
|
109
|
+
loadModule,
|
|
110
|
+
loadClass: (name) => classes.get(name),
|
|
103
111
|
print: () => {},
|
|
104
112
|
files: { readFile: undefined, writeFile: undefined },
|
|
105
113
|
});
|
|
106
114
|
const environment = specification.environment;
|
|
107
|
-
const search = createSearch(start.config, environment)
|
|
108
|
-
|
|
109
|
-
|
|
115
|
+
const search = createSearch(start.config, environment, () =>
|
|
116
|
+
getTlcVariableOrder(start.source, loadModule),
|
|
117
|
+
);
|
|
118
|
+
const nextAction = search?.spec.next;
|
|
119
|
+
if (!search || nextAction === undefined)
|
|
120
|
+
return parent.postMessage({ type: "abort" } satisfies WorkerMessage);
|
|
110
121
|
const count = start.count;
|
|
111
122
|
const self = start.index;
|
|
112
123
|
const canonicalize = createCanonicalizer();
|
|
113
|
-
const decode = createStateDecoder(
|
|
124
|
+
const decode = createStateDecoder(environment.variables.length);
|
|
114
125
|
const table = createFingerprintTable();
|
|
115
126
|
const known = createFingerprintTable();
|
|
127
|
+
// Under symmetry, a successor the worker generated before has the representative it had then,
|
|
128
|
+
// so the worker skips the search for one.
|
|
129
|
+
const generatedStates = search.isSymmetric ? createFingerprintTable() : undefined;
|
|
116
130
|
const fingerprint = new Int32Array(2);
|
|
117
131
|
const inbox = createInbox();
|
|
118
132
|
const peers = start.ports.flatMap((port, index) => (port ? [{ port, index }] : []));
|
|
@@ -127,37 +141,51 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
127
141
|
const send = (index: number, message: PeerMessage, transfer: unknown[]) =>
|
|
128
142
|
start.ports[index]?.postMessage(message, transfer);
|
|
129
143
|
|
|
130
|
-
const expand = (states: Value[][], next: Value[][]) => {
|
|
144
|
+
const expand = (states: Value[][], next: Value[][], nextFingerprintStates: Value[][]) => {
|
|
131
145
|
const pending: Pending[] = Array.from({ length: count }, () => ({
|
|
132
146
|
states: [],
|
|
133
|
-
|
|
147
|
+
origins: [],
|
|
134
148
|
fingerprints: [],
|
|
149
|
+
fingerprintStates: [],
|
|
135
150
|
}));
|
|
136
151
|
const statistics = { generated: 0 };
|
|
137
|
-
|
|
138
|
-
|
|
152
|
+
const newStates = new Int32Array(states.length);
|
|
153
|
+
for (let origin = 0; origin < states.length; origin++) {
|
|
154
|
+
const state = states[origin];
|
|
155
|
+
const successors = getSuccessors(nextAction, state, environment, search.spec.scope);
|
|
139
156
|
statistics.generated += successors.length;
|
|
140
157
|
if (successors.length === 0 && start.config.checkDeadlock) return undefined;
|
|
141
158
|
for (const successor of successors) {
|
|
142
159
|
if (search.findViolatedActionProperty(state, successor.state)) return undefined;
|
|
143
160
|
if (!search.satisfiesActionConstraints(state, successor.state)) continue;
|
|
161
|
+
if (generatedStates) {
|
|
162
|
+
writeStateFingerprint(successor.state, fingerprint);
|
|
163
|
+
if (!generatedStates.insert(fingerprint[0], fingerprint[1])) continue;
|
|
164
|
+
}
|
|
144
165
|
canonicalize(successor.state);
|
|
145
|
-
|
|
166
|
+
const fingerprintState = search.getFingerprintState(successor.state);
|
|
167
|
+
writeStateFingerprint(fingerprintState, fingerprint);
|
|
146
168
|
const owner = getFingerprintOwner(fingerprint[1], count);
|
|
147
169
|
if (owner === self) {
|
|
148
170
|
if (!table.insert(fingerprint[0], fingerprint[1])) continue;
|
|
149
171
|
if (search.findViolatedInvariant(successor.state, state)) return undefined;
|
|
150
|
-
if (search.satisfiesConstraints(successor.state, state))
|
|
151
|
-
|
|
172
|
+
if (!search.satisfiesConstraints(successor.state, state)) {
|
|
173
|
+
table.remove(fingerprint[0], fingerprint[1]);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
next.push(successor.state);
|
|
177
|
+
nextFingerprintStates.push(fingerprintState);
|
|
178
|
+
newStates[origin]++;
|
|
152
179
|
continue;
|
|
153
180
|
}
|
|
154
181
|
if (!known.insert(fingerprint[0], fingerprint[1])) continue;
|
|
155
182
|
pending[owner].states.push(successor.state);
|
|
156
|
-
pending[owner].
|
|
183
|
+
pending[owner].origins.push(origin);
|
|
157
184
|
pending[owner].fingerprints.push(fingerprint[0], fingerprint[1]);
|
|
185
|
+
pending[owner].fingerprintStates.push(fingerprintState);
|
|
158
186
|
}
|
|
159
187
|
}
|
|
160
|
-
return { pending, generated: statistics.generated };
|
|
188
|
+
return { pending, generated: statistics.generated, newStates };
|
|
161
189
|
};
|
|
162
190
|
|
|
163
191
|
const answer = (fingerprints: Int32Array | number[]) => {
|
|
@@ -168,15 +196,23 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
168
196
|
return bits;
|
|
169
197
|
};
|
|
170
198
|
|
|
171
|
-
const commit = (
|
|
199
|
+
const commit = (
|
|
200
|
+
pending: Pending,
|
|
201
|
+
bits: Uint8Array,
|
|
202
|
+
next: Value[][],
|
|
203
|
+
nextFingerprintStates: Value[][],
|
|
204
|
+
newStates: Int32Array,
|
|
205
|
+
) => {
|
|
172
206
|
const unmarks: number[] = [];
|
|
173
207
|
for (let index = 0; index < bits.length; index++) {
|
|
174
208
|
if (bits[index] === 0) continue;
|
|
175
209
|
const state = pending.states[index];
|
|
176
|
-
const parentState = pending.
|
|
210
|
+
const parentState = frontier.states[pending.origins[index]];
|
|
177
211
|
if (search.findViolatedInvariant(state, parentState)) return undefined;
|
|
178
212
|
if (search.satisfiesConstraints(state, parentState)) {
|
|
179
213
|
next.push(state);
|
|
214
|
+
nextFingerprintStates.push(pending.fingerprintStates[index]);
|
|
215
|
+
newStates[pending.origins[index]]++;
|
|
180
216
|
continue;
|
|
181
217
|
}
|
|
182
218
|
known.remove(pending.fingerprints[index * 2], pending.fingerprints[index * 2 + 1]);
|
|
@@ -213,8 +249,9 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
213
249
|
|
|
214
250
|
const runLevel = async (level: number): Promise<LevelMessage | undefined> => {
|
|
215
251
|
const next: Value[][] = [];
|
|
216
|
-
const
|
|
217
|
-
|
|
252
|
+
const nextFingerprintStates: Value[][] = [];
|
|
253
|
+
const expanded = expand(frontier.states, next, nextFingerprintStates);
|
|
254
|
+
if (!expanded || getRankedStringCount() !== startupStrings) return undefined;
|
|
218
255
|
peers.forEach((peer) => {
|
|
219
256
|
const fingerprints = Int32Array.from(expanded.pending[peer.index].fingerprints);
|
|
220
257
|
send(peer.index, { phase: "query", level, from: self, fingerprints }, [fingerprints.buffer]);
|
|
@@ -226,11 +263,20 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
226
263
|
});
|
|
227
264
|
const answers = await inbox.receive("answer", level, peers.length);
|
|
228
265
|
for (const reply of answers) {
|
|
229
|
-
const unmarks = commit(
|
|
266
|
+
const unmarks = commit(
|
|
267
|
+
expanded.pending[reply.from],
|
|
268
|
+
reply.bits ?? new Uint8Array(),
|
|
269
|
+
next,
|
|
270
|
+
nextFingerprintStates,
|
|
271
|
+
expanded.newStates,
|
|
272
|
+
);
|
|
230
273
|
if (!unmarks) return undefined;
|
|
231
|
-
send(reply.from, { phase: "sizes", level, from: self, unmarks
|
|
274
|
+
send(reply.from, { phase: "sizes", level, from: self, unmarks }, [unmarks.buffer]);
|
|
232
275
|
}
|
|
233
276
|
const added = next.length;
|
|
277
|
+
const outDegrees = Array.from({ length: OUT_DEGREE_BUCKETS }, () => 0);
|
|
278
|
+
expanded.newStates.forEach((degree) => outDegrees[Math.min(degree, OUT_DEGREE_BUCKETS - 1)]++);
|
|
279
|
+
const fingerprints = search.getStoredFingerprints(next, nextFingerprintStates);
|
|
234
280
|
const sizes = Array.from({ length: count }, () => 0);
|
|
235
281
|
sizes[self] = added;
|
|
236
282
|
peers.forEach((peer) =>
|
|
@@ -241,12 +287,14 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
241
287
|
if (message.unmarks) removeAll(message.unmarks);
|
|
242
288
|
else sizes[message.from] = message.size ?? 0;
|
|
243
289
|
});
|
|
290
|
+
if (getRankedStringCount() !== startupStrings) return undefined;
|
|
244
291
|
if (!(await balance(level, next, sizes))) return undefined;
|
|
245
292
|
frontier.states = next;
|
|
246
|
-
return { type: "level", level, generated: expanded.generated, added };
|
|
293
|
+
return { type: "level", level, generated: expanded.generated, added, outDegrees, fingerprints };
|
|
247
294
|
};
|
|
248
295
|
|
|
249
|
-
const report = (message: WorkerMessage) =>
|
|
296
|
+
const report = (message: WorkerMessage) =>
|
|
297
|
+
parent.postMessage(message, message.type === "level" ? [message.fingerprints.buffer] : []);
|
|
250
298
|
const step = (level: number) => {
|
|
251
299
|
runLevel(level).then(
|
|
252
300
|
(message) => report(message ?? { type: "abort" }),
|
package/src/parallel.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Expression } from "./ast";
|
|
2
2
|
import { createCanonicalizer } from "./canonical";
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
checkSequentially,
|
|
5
5
|
createSearch,
|
|
6
6
|
findAssumptionViolation,
|
|
7
7
|
type CheckOptions,
|
|
@@ -9,11 +9,14 @@ import {
|
|
|
9
9
|
} from "./check";
|
|
10
10
|
import { parseConfig, type ModelConfig } from "./config";
|
|
11
11
|
import { createEnvironment } from "./environment";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
12
|
+
import { TlaEvaluationError } from "./errors";
|
|
13
|
+
import { isImpure } from "./evaluate";
|
|
14
|
+
import { createFingerprintTable, getFingerprintOwner, writeStateFingerprint } from "./fingerprints";
|
|
15
15
|
import { getInitialStates } from "./successors";
|
|
16
|
+
import { getTlcStringOrder, getTlcVariableOrder } from "./tlc-order";
|
|
17
|
+
import { getFingerprintDistance, OUT_DEGREE_BUCKETS } from "./tlc-summary";
|
|
16
18
|
import { createStateEncoder, type EncodedStates } from "./transfer";
|
|
19
|
+
import { getRankedStringCount, setStringOrder, type Value } from "./values";
|
|
17
20
|
|
|
18
21
|
const WORKER_THREADS_MODULE = "node:worker_threads";
|
|
19
22
|
|
|
@@ -29,6 +32,7 @@ export interface StartMessage {
|
|
|
29
32
|
source: string;
|
|
30
33
|
config: ModelConfig;
|
|
31
34
|
modules: [string, string | undefined][];
|
|
35
|
+
classes: [string, Uint8Array | undefined][];
|
|
32
36
|
index: number;
|
|
33
37
|
count: number;
|
|
34
38
|
ports: (Port | undefined)[];
|
|
@@ -41,14 +45,12 @@ export interface LevelMessage {
|
|
|
41
45
|
level: number;
|
|
42
46
|
generated: number;
|
|
43
47
|
added: number;
|
|
48
|
+
outDegrees: number[];
|
|
49
|
+
fingerprints: BigInt64Array;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
export type WorkerMessage = { type: "ready" } | { type: "abort" } | LevelMessage;
|
|
47
53
|
|
|
48
|
-
export interface ParallelCheckOptions extends CheckOptions {
|
|
49
|
-
workers: number;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
54
|
interface WorkerHandle {
|
|
53
55
|
postMessage: (message: unknown, transfer?: unknown[]) => void;
|
|
54
56
|
terminate: () => void;
|
|
@@ -73,7 +75,7 @@ const spawnWorker = async (onMessage: (message: WorkerMessage) => void): Promise
|
|
|
73
75
|
/* @vite-ignore */ /* webpackIgnore: true */ WORKER_THREADS_MODULE
|
|
74
76
|
);
|
|
75
77
|
const worker = new threads.Worker(new URL("./parallel-worker.js", import.meta.url));
|
|
76
|
-
worker.on("message",
|
|
78
|
+
worker.on("message", onMessage);
|
|
77
79
|
worker.on("error", () => onMessage({ type: "abort" }));
|
|
78
80
|
return {
|
|
79
81
|
postMessage: (message, transfer) =>
|
|
@@ -82,47 +84,52 @@ const spawnWorker = async (onMessage: (message: WorkerMessage) => void): Promise
|
|
|
82
84
|
};
|
|
83
85
|
};
|
|
84
86
|
|
|
85
|
-
const
|
|
86
|
-
((expression.kind === "Apply" || expression.kind === "OperatorReference") &&
|
|
87
|
-
isImpureBuiltin(expression.operator, environment)) ||
|
|
88
|
-
(expression.kind === "Identifier" && isImpureBuiltin(expression.name, environment)) ||
|
|
89
|
-
getChildren(expression).some((child) => usesImpureBuiltin(child, environment));
|
|
90
|
-
|
|
91
|
-
const isImpure = (environment: Environment) =>
|
|
92
|
-
[...environment.definitions.values()].some(
|
|
93
|
-
(definition) =>
|
|
94
|
-
usesImpureBuiltin(definition.body, environment) ||
|
|
95
|
-
(definition.kind === "Function" &&
|
|
96
|
-
definition.bounds.some((bound) => usesImpureBuiltin(bound.set, environment))),
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
export const checkInParallel = async (options: ParallelCheckOptions): Promise<CheckResult> => {
|
|
87
|
+
export const check = async (options: CheckOptions): Promise<CheckResult> => {
|
|
100
88
|
const config = typeof options.config === "string" ? parseConfig(options.config) : options.config;
|
|
101
|
-
const
|
|
89
|
+
const fallBack = () => checkSequentially({ ...options, config });
|
|
90
|
+
const count = options.workers ?? 1;
|
|
102
91
|
const isSequential =
|
|
103
|
-
|
|
92
|
+
count < 2 ||
|
|
104
93
|
config.view !== undefined ||
|
|
105
94
|
config.postconditions.length > 0 ||
|
|
106
95
|
config.possible.length > 0;
|
|
107
|
-
if (isSequential) return
|
|
96
|
+
if (isSequential) return fallBack();
|
|
108
97
|
const modules = new Map<string, string | undefined>();
|
|
109
|
-
const
|
|
110
|
-
loadModule
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
98
|
+
const loadModule = (name: string) => {
|
|
99
|
+
const source = options.loadModule?.(name);
|
|
100
|
+
modules.set(name, source);
|
|
101
|
+
return source;
|
|
102
|
+
};
|
|
103
|
+
const classes = new Map<string, Uint8Array | undefined>();
|
|
104
|
+
const loadClass = (name: string) => {
|
|
105
|
+
const bytes = options.loadClass?.(name);
|
|
106
|
+
classes.set(name, bytes);
|
|
107
|
+
return bytes;
|
|
108
|
+
};
|
|
109
|
+
setStringOrder(getTlcStringOrder(options.source, config, loadModule));
|
|
110
|
+
const startupStrings = getRankedStringCount();
|
|
111
|
+
const specification = createEnvironment(config, {
|
|
112
|
+
source: options.source,
|
|
113
|
+
loadModule,
|
|
114
|
+
loadClass,
|
|
115
115
|
print: options.onPrint ?? console.log,
|
|
116
116
|
files: { readFile: options.readFile, writeFile: options.writeFile },
|
|
117
117
|
});
|
|
118
118
|
const environment = specification.environment;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
119
|
+
const search = createSearch(config, environment, () =>
|
|
120
|
+
getTlcVariableOrder(options.source, loadModule),
|
|
121
|
+
);
|
|
122
|
+
if (findAssumptionViolation(specification.assumptions, environment)) return fallBack();
|
|
123
|
+
if (
|
|
124
|
+
!search ||
|
|
125
|
+
search.spec.init === undefined ||
|
|
126
|
+
search.spec.next === undefined ||
|
|
127
|
+
search.properties.liveness.length > 0 ||
|
|
128
|
+
isImpure(environment)
|
|
129
|
+
) {
|
|
130
|
+
return fallBack();
|
|
123
131
|
}
|
|
124
132
|
|
|
125
|
-
const count = options.workers;
|
|
126
133
|
const canonicalize = createCanonicalizer();
|
|
127
134
|
const table = createFingerprintTable();
|
|
128
135
|
const fingerprint = new Int32Array(2);
|
|
@@ -130,30 +137,70 @@ export const checkInParallel = async (options: ParallelCheckOptions): Promise<Ch
|
|
|
130
137
|
encoder: createStateEncoder(),
|
|
131
138
|
fingerprints: [] as number[],
|
|
132
139
|
}));
|
|
133
|
-
const statistics = {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
const statistics = {
|
|
141
|
+
generatedStates: 0,
|
|
142
|
+
distinctStates: 0,
|
|
143
|
+
queuedStates: 0,
|
|
144
|
+
depth: 0,
|
|
145
|
+
outDegrees: Array.from({ length: OUT_DEGREE_BUCKETS }, () => 0),
|
|
146
|
+
};
|
|
147
|
+
const tlcFingerprints: BigInt64Array[] = [];
|
|
148
|
+
const createSuccess = (): CheckResult => ({
|
|
149
|
+
status: "success",
|
|
150
|
+
violation: undefined,
|
|
151
|
+
failure: undefined,
|
|
152
|
+
...statistics,
|
|
153
|
+
fingerprintDistance: getFingerprintDistance(
|
|
154
|
+
statistics.distinctStates,
|
|
155
|
+
statistics.generatedStates,
|
|
156
|
+
() => {
|
|
157
|
+
const fingerprints = new BigInt64Array(statistics.distinctStates);
|
|
158
|
+
tlcFingerprints.reduce((offset, level) => {
|
|
159
|
+
fingerprints.set(level, offset);
|
|
160
|
+
return offset + level.length;
|
|
161
|
+
}, 0);
|
|
162
|
+
return fingerprints;
|
|
163
|
+
},
|
|
164
|
+
),
|
|
165
|
+
});
|
|
166
|
+
const seedInitialStates = (init: Expression) => {
|
|
167
|
+
const seeded: Value[][] = [];
|
|
168
|
+
for (const state of getInitialStates(init, environment, search.spec.scope)) {
|
|
169
|
+
canonicalize(state);
|
|
170
|
+
statistics.generatedStates++;
|
|
171
|
+
writeStateFingerprint(search.getFingerprintState(state), fingerprint);
|
|
172
|
+
if (!table.insert(fingerprint[0], fingerprint[1])) continue;
|
|
173
|
+
if (search.findViolatedInvariant(state) || search.findFailedInitialPredicate(state)) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
if (!search.satisfiesConstraints(state)) {
|
|
177
|
+
table.remove(fingerprint[0], fingerprint[1]);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const seed = seeds[getFingerprintOwner(fingerprint[1], count)];
|
|
181
|
+
if (!seed.encoder.encode(state)) return false;
|
|
182
|
+
seed.fingerprints.push(fingerprint[0], fingerprint[1]);
|
|
183
|
+
seeded.push(state);
|
|
184
|
+
statistics.distinctStates++;
|
|
145
185
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
186
|
+
tlcFingerprints.push(
|
|
187
|
+
BigInt64Array.from(seeded, (state) => environment.runtime.getFingerprint(state)),
|
|
188
|
+
);
|
|
189
|
+
return true;
|
|
190
|
+
};
|
|
191
|
+
// The sequential search reports a violation or a failed evaluation in the initial states.
|
|
192
|
+
try {
|
|
193
|
+
if (!seedInitialStates(search.spec.init)) return fallBack();
|
|
194
|
+
} catch (error) {
|
|
195
|
+
if (!(error instanceof TlaEvaluationError)) throw error;
|
|
196
|
+
return fallBack();
|
|
150
197
|
}
|
|
151
|
-
if (
|
|
152
|
-
|
|
153
|
-
statistics.
|
|
198
|
+
if (getRankedStringCount() !== startupStrings) return fallBack();
|
|
199
|
+
statistics.depth = statistics.generatedStates === 0 ? 0 : 1;
|
|
200
|
+
if (statistics.distinctStates === 0) return createSuccess();
|
|
154
201
|
|
|
155
202
|
const reports: WorkerMessage[] = [];
|
|
156
|
-
const pending = { resolve: (
|
|
203
|
+
const pending = { resolve: (_reports: WorkerMessage[]) => {} };
|
|
157
204
|
const onMessage = (message: WorkerMessage) => {
|
|
158
205
|
reports.push(message);
|
|
159
206
|
if (message.type === "abort" || reports.length === count) {
|
|
@@ -168,7 +215,7 @@ export const checkInParallel = async (options: ParallelCheckOptions): Promise<Ch
|
|
|
168
215
|
const workers = await Promise.all(Array.from({ length: count }, () => spawnWorker(onMessage)));
|
|
169
216
|
const terminate = () => workers.forEach((worker) => worker.terminate());
|
|
170
217
|
const channels = new Map<string, MessageChannel>();
|
|
171
|
-
const getPort = (from: number, to: number)
|
|
218
|
+
const getPort = (from: number, to: number) => {
|
|
172
219
|
const key = from < to ? `${from}:${to}` : `${to}:${from}`;
|
|
173
220
|
const channel = channels.get(key) ?? new MessageChannel();
|
|
174
221
|
channels.set(key, channel);
|
|
@@ -185,6 +232,7 @@ export const checkInParallel = async (options: ParallelCheckOptions): Promise<Ch
|
|
|
185
232
|
source: options.source,
|
|
186
233
|
config,
|
|
187
234
|
modules: [...modules],
|
|
235
|
+
classes: [...classes],
|
|
188
236
|
index,
|
|
189
237
|
count,
|
|
190
238
|
ports,
|
|
@@ -201,7 +249,7 @@ export const checkInParallel = async (options: ParallelCheckOptions): Promise<Ch
|
|
|
201
249
|
messages.some((message) => message.type === "abort");
|
|
202
250
|
if (isAborted(await ready)) {
|
|
203
251
|
terminate();
|
|
204
|
-
return
|
|
252
|
+
return fallBack();
|
|
205
253
|
}
|
|
206
254
|
for (let level = 1; ; level++) {
|
|
207
255
|
const reported = waitForReports();
|
|
@@ -209,15 +257,21 @@ export const checkInParallel = async (options: ParallelCheckOptions): Promise<Ch
|
|
|
209
257
|
const messages = await reported;
|
|
210
258
|
if (isAborted(messages)) {
|
|
211
259
|
terminate();
|
|
212
|
-
return
|
|
260
|
+
return fallBack();
|
|
213
261
|
}
|
|
214
262
|
const levels = messages.flatMap((message) => (message.type === "level" ? [message] : []));
|
|
215
263
|
const added = levels.reduce((sum, message) => sum + message.added, 0);
|
|
216
264
|
statistics.generatedStates += levels.reduce((sum, message) => sum + message.generated, 0);
|
|
217
265
|
statistics.distinctStates += added;
|
|
266
|
+
levels.forEach((message) => {
|
|
267
|
+
message.outDegrees.forEach(
|
|
268
|
+
(degreeCount, degree) => (statistics.outDegrees[degree] += degreeCount),
|
|
269
|
+
);
|
|
270
|
+
tlcFingerprints.push(message.fingerprints);
|
|
271
|
+
});
|
|
218
272
|
if (added === 0) {
|
|
219
273
|
terminate();
|
|
220
|
-
return
|
|
274
|
+
return createSuccess();
|
|
221
275
|
}
|
|
222
276
|
statistics.depth++;
|
|
223
277
|
}
|