tla-ts 0.0.1 → 0.0.3-dev.a50fc89
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 +57 -28
- 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,18 +1,19 @@
|
|
|
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[];
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -33,12 +34,12 @@ interface Transfer {
|
|
|
33
34
|
count: number;
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
const getBalancePlan = (sizes: number[])
|
|
37
|
+
const getBalancePlan = (sizes: number[]) => {
|
|
37
38
|
const total = sizes.reduce((sum, size) => sum + size, 0);
|
|
38
39
|
const average = total / sizes.length;
|
|
39
40
|
if (Math.max(...sizes) - Math.min(...sizes) <= Math.max(64, average / 4)) return [];
|
|
40
41
|
const targets = sizes.map(
|
|
41
|
-
(_, index) => Math.floor(
|
|
42
|
+
(_, index) => Math.floor(average) + (index < total % sizes.length ? 1 : 0),
|
|
42
43
|
);
|
|
43
44
|
const donors = sizes.flatMap((size, index) =>
|
|
44
45
|
size > targets[index] ? [{ index, amount: size - targets[index] }] : [],
|
|
@@ -59,7 +60,7 @@ const getBalancePlan = (sizes: number[]): Transfer[] => {
|
|
|
59
60
|
return transfers;
|
|
60
61
|
};
|
|
61
62
|
|
|
62
|
-
const getParentPort = async ()
|
|
63
|
+
const getParentPort = async () => {
|
|
63
64
|
if (typeof (globalThis as { postMessage?: unknown }).postMessage === "function") {
|
|
64
65
|
return globalThis as unknown as Port;
|
|
65
66
|
}
|
|
@@ -98,19 +99,28 @@ const createInbox = () => {
|
|
|
98
99
|
|
|
99
100
|
const run = (start: StartMessage, parent: Port) => {
|
|
100
101
|
const modules = new Map(start.modules);
|
|
101
|
-
const
|
|
102
|
-
|
|
102
|
+
const loadModule = (name: string) => modules.get(name);
|
|
103
|
+
const classes = new Map(start.classes);
|
|
104
|
+
setStringOrder(getTlcStringOrder(start.source, start.config, loadModule));
|
|
105
|
+
const startupStrings = getRankedStringCount();
|
|
106
|
+
const specification = createEnvironment(start.config, {
|
|
107
|
+
source: start.source,
|
|
108
|
+
loadModule,
|
|
109
|
+
loadClass: (name) => classes.get(name),
|
|
103
110
|
print: () => {},
|
|
104
111
|
files: { readFile: undefined, writeFile: undefined },
|
|
105
112
|
});
|
|
106
113
|
const environment = specification.environment;
|
|
107
|
-
const search = createSearch(start.config, environment)
|
|
108
|
-
|
|
109
|
-
|
|
114
|
+
const search = createSearch(start.config, environment, () =>
|
|
115
|
+
getTlcVariableOrder(start.source, loadModule),
|
|
116
|
+
);
|
|
117
|
+
const nextAction = search?.spec.next;
|
|
118
|
+
if (!search || nextAction === undefined)
|
|
119
|
+
return parent.postMessage({ type: "abort" } satisfies WorkerMessage);
|
|
110
120
|
const count = start.count;
|
|
111
121
|
const self = start.index;
|
|
112
122
|
const canonicalize = createCanonicalizer();
|
|
113
|
-
const decode = createStateDecoder(
|
|
123
|
+
const decode = createStateDecoder(environment.variables.length);
|
|
114
124
|
const table = createFingerprintTable();
|
|
115
125
|
const known = createFingerprintTable();
|
|
116
126
|
const fingerprint = new Int32Array(2);
|
|
@@ -130,34 +140,40 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
130
140
|
const expand = (states: Value[][], next: Value[][]) => {
|
|
131
141
|
const pending: Pending[] = Array.from({ length: count }, () => ({
|
|
132
142
|
states: [],
|
|
133
|
-
|
|
143
|
+
origins: [],
|
|
134
144
|
fingerprints: [],
|
|
135
145
|
}));
|
|
136
146
|
const statistics = { generated: 0 };
|
|
137
|
-
|
|
138
|
-
|
|
147
|
+
const newStates = new Int32Array(states.length);
|
|
148
|
+
for (let origin = 0; origin < states.length; origin++) {
|
|
149
|
+
const state = states[origin];
|
|
150
|
+
const successors = getSuccessors(nextAction, state, environment, search.spec.scope);
|
|
139
151
|
statistics.generated += successors.length;
|
|
140
152
|
if (successors.length === 0 && start.config.checkDeadlock) return undefined;
|
|
141
153
|
for (const successor of successors) {
|
|
142
154
|
if (search.findViolatedActionProperty(state, successor.state)) return undefined;
|
|
143
155
|
if (!search.satisfiesActionConstraints(state, successor.state)) continue;
|
|
144
156
|
canonicalize(successor.state);
|
|
145
|
-
|
|
157
|
+
writeStateFingerprint(search.getFingerprintState(successor.state), fingerprint);
|
|
146
158
|
const owner = getFingerprintOwner(fingerprint[1], count);
|
|
147
159
|
if (owner === self) {
|
|
148
160
|
if (!table.insert(fingerprint[0], fingerprint[1])) continue;
|
|
149
161
|
if (search.findViolatedInvariant(successor.state, state)) return undefined;
|
|
150
|
-
if (search.satisfiesConstraints(successor.state, state))
|
|
151
|
-
|
|
162
|
+
if (!search.satisfiesConstraints(successor.state, state)) {
|
|
163
|
+
table.remove(fingerprint[0], fingerprint[1]);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
next.push(successor.state);
|
|
167
|
+
newStates[origin]++;
|
|
152
168
|
continue;
|
|
153
169
|
}
|
|
154
170
|
if (!known.insert(fingerprint[0], fingerprint[1])) continue;
|
|
155
171
|
pending[owner].states.push(successor.state);
|
|
156
|
-
pending[owner].
|
|
172
|
+
pending[owner].origins.push(origin);
|
|
157
173
|
pending[owner].fingerprints.push(fingerprint[0], fingerprint[1]);
|
|
158
174
|
}
|
|
159
175
|
}
|
|
160
|
-
return { pending, generated: statistics.generated };
|
|
176
|
+
return { pending, generated: statistics.generated, newStates };
|
|
161
177
|
};
|
|
162
178
|
|
|
163
179
|
const answer = (fingerprints: Int32Array | number[]) => {
|
|
@@ -168,15 +184,16 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
168
184
|
return bits;
|
|
169
185
|
};
|
|
170
186
|
|
|
171
|
-
const commit = (pending: Pending, bits: Uint8Array, next: Value[][]) => {
|
|
187
|
+
const commit = (pending: Pending, bits: Uint8Array, next: Value[][], newStates: Int32Array) => {
|
|
172
188
|
const unmarks: number[] = [];
|
|
173
189
|
for (let index = 0; index < bits.length; index++) {
|
|
174
190
|
if (bits[index] === 0) continue;
|
|
175
191
|
const state = pending.states[index];
|
|
176
|
-
const parentState = pending.
|
|
192
|
+
const parentState = frontier.states[pending.origins[index]];
|
|
177
193
|
if (search.findViolatedInvariant(state, parentState)) return undefined;
|
|
178
194
|
if (search.satisfiesConstraints(state, parentState)) {
|
|
179
195
|
next.push(state);
|
|
196
|
+
newStates[pending.origins[index]]++;
|
|
180
197
|
continue;
|
|
181
198
|
}
|
|
182
199
|
known.remove(pending.fingerprints[index * 2], pending.fingerprints[index * 2 + 1]);
|
|
@@ -214,7 +231,7 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
214
231
|
const runLevel = async (level: number): Promise<LevelMessage | undefined> => {
|
|
215
232
|
const next: Value[][] = [];
|
|
216
233
|
const expanded = expand(frontier.states, next);
|
|
217
|
-
if (!expanded) return undefined;
|
|
234
|
+
if (!expanded || getRankedStringCount() !== startupStrings) return undefined;
|
|
218
235
|
peers.forEach((peer) => {
|
|
219
236
|
const fingerprints = Int32Array.from(expanded.pending[peer.index].fingerprints);
|
|
220
237
|
send(peer.index, { phase: "query", level, from: self, fingerprints }, [fingerprints.buffer]);
|
|
@@ -226,11 +243,21 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
226
243
|
});
|
|
227
244
|
const answers = await inbox.receive("answer", level, peers.length);
|
|
228
245
|
for (const reply of answers) {
|
|
229
|
-
const unmarks = commit(
|
|
246
|
+
const unmarks = commit(
|
|
247
|
+
expanded.pending[reply.from],
|
|
248
|
+
reply.bits ?? new Uint8Array(),
|
|
249
|
+
next,
|
|
250
|
+
expanded.newStates,
|
|
251
|
+
);
|
|
230
252
|
if (!unmarks) return undefined;
|
|
231
|
-
send(reply.from, { phase: "sizes", level, from: self, unmarks
|
|
253
|
+
send(reply.from, { phase: "sizes", level, from: self, unmarks }, [unmarks.buffer]);
|
|
232
254
|
}
|
|
233
255
|
const added = next.length;
|
|
256
|
+
const outDegrees = Array.from({ length: OUT_DEGREE_BUCKETS }, () => 0);
|
|
257
|
+
expanded.newStates.forEach((degree) => outDegrees[Math.min(degree, OUT_DEGREE_BUCKETS - 1)]++);
|
|
258
|
+
const fingerprints = BigInt64Array.from(next, (state) =>
|
|
259
|
+
environment.runtime.getFingerprint(state),
|
|
260
|
+
);
|
|
234
261
|
const sizes = Array.from({ length: count }, () => 0);
|
|
235
262
|
sizes[self] = added;
|
|
236
263
|
peers.forEach((peer) =>
|
|
@@ -241,12 +268,14 @@ const run = (start: StartMessage, parent: Port) => {
|
|
|
241
268
|
if (message.unmarks) removeAll(message.unmarks);
|
|
242
269
|
else sizes[message.from] = message.size ?? 0;
|
|
243
270
|
});
|
|
271
|
+
if (getRankedStringCount() !== startupStrings) return undefined;
|
|
244
272
|
if (!(await balance(level, next, sizes))) return undefined;
|
|
245
273
|
frontier.states = next;
|
|
246
|
-
return { type: "level", level, generated: expanded.generated, added };
|
|
274
|
+
return { type: "level", level, generated: expanded.generated, added, outDegrees, fingerprints };
|
|
247
275
|
};
|
|
248
276
|
|
|
249
|
-
const report = (message: WorkerMessage) =>
|
|
277
|
+
const report = (message: WorkerMessage) =>
|
|
278
|
+
parent.postMessage(message, message.type === "level" ? [message.fingerprints.buffer] : []);
|
|
250
279
|
const step = (level: number) => {
|
|
251
280
|
runLevel(level).then(
|
|
252
281
|
(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
|
}
|