@cavelang/solver-z3 0.35.0 → 0.36.1
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/BENCHMARK.md +56 -3
- package/README.md +267 -9
- package/dist/scripts/benchmark.js +35 -13
- package/dist/scripts/benchmark.js.map +1 -1
- package/dist/scripts/memory-stress.d.ts +2 -0
- package/dist/scripts/memory-stress.d.ts.map +1 -0
- package/dist/scripts/memory-stress.js +74 -0
- package/dist/scripts/memory-stress.js.map +1 -0
- package/dist/src/cleanup.d.ts +8 -0
- package/dist/src/cleanup.d.ts.map +1 -0
- package/dist/src/cleanup.js +65 -0
- package/dist/src/cleanup.js.map +1 -0
- package/dist/src/close-workers.d.ts +12 -0
- package/dist/src/close-workers.d.ts.map +1 -0
- package/dist/src/close-workers.js +27 -0
- package/dist/src/close-workers.js.map +1 -0
- package/dist/src/deadline.d.ts +7 -0
- package/dist/src/deadline.d.ts.map +1 -0
- package/dist/src/deadline.js +46 -0
- package/dist/src/deadline.js.map +1 -0
- package/dist/src/error-message.d.ts +3 -0
- package/dist/src/error-message.d.ts.map +1 -0
- package/dist/src/error-message.js +10 -0
- package/dist/src/error-message.js.map +1 -0
- package/dist/src/runtime.d.ts.map +1 -1
- package/dist/src/runtime.js +137 -72
- package/dist/src/runtime.js.map +1 -1
- package/dist/src/with-release.d.ts +3 -0
- package/dist/src/with-release.d.ts.map +1 -0
- package/dist/src/with-release.js +25 -0
- package/dist/src/with-release.js.map +1 -0
- package/dist/src/workflow-fixture.d.ts.map +1 -1
- package/dist/src/workflow-fixture.js +27 -10
- package/dist/src/workflow-fixture.js.map +1 -1
- package/package.json +4 -3
- package/scripts/benchmark.ts +35 -12
- package/scripts/memory-stress.ts +72 -0
- package/src/cleanup.ts +55 -0
- package/src/close-workers.ts +28 -0
- package/src/deadline.ts +40 -0
- package/src/error-message.ts +5 -0
- package/src/runtime.ts +132 -70
- package/src/with-release.ts +21 -0
- package/src/workflow-fixture.ts +30 -11
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { errorMessage } from "./error-message.js";
|
|
2
|
+
/**
|
|
3
|
+
* The binding's finalizers call this instance's low-level API directly, outside
|
|
4
|
+
* its async mutex. Native AST deletion must not race a pthread solver check.
|
|
5
|
+
* Guard the two async native operations CAVE uses; normal synchronous releases
|
|
6
|
+
* remain immediate, and queued releases drain before a check returns to CAVE.
|
|
7
|
+
*/
|
|
8
|
+
export const guardCleanup = (api) => {
|
|
9
|
+
const functions = api;
|
|
10
|
+
const pending = [];
|
|
11
|
+
let checking = 0;
|
|
12
|
+
for (const [name, value] of Object.entries(functions)) {
|
|
13
|
+
if (typeof value !== 'function')
|
|
14
|
+
continue;
|
|
15
|
+
if (name === 'dec_ref' || name.endsWith('_dec_ref') || name === 'del_context') {
|
|
16
|
+
functions[name] = (...args) => {
|
|
17
|
+
const release = () => { value.apply(api, args); };
|
|
18
|
+
if (checking > 0)
|
|
19
|
+
pending.push(release);
|
|
20
|
+
else
|
|
21
|
+
release();
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const name of ['solver_check_assumptions', 'optimize_check']) {
|
|
26
|
+
const check = functions[name];
|
|
27
|
+
if (typeof check !== 'function')
|
|
28
|
+
throw new TypeError(`Z3 is missing ${name}`);
|
|
29
|
+
functions[name] = async (...args) => {
|
|
30
|
+
checking++;
|
|
31
|
+
let checkFailed = false;
|
|
32
|
+
let checkError;
|
|
33
|
+
try {
|
|
34
|
+
return await check.apply(api, args);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
checkFailed = true;
|
|
38
|
+
checkError = error;
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
checking--;
|
|
43
|
+
if (checking === 0) {
|
|
44
|
+
const releases = pending.splice(0);
|
|
45
|
+
const failures = [];
|
|
46
|
+
for (const release of releases) {
|
|
47
|
+
try {
|
|
48
|
+
release();
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
failures.push(error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (failures.length > 0) {
|
|
55
|
+
if (!checkFailed && failures.length === 1)
|
|
56
|
+
throw failures[0];
|
|
57
|
+
const errors = checkFailed ? [checkError, ...failures] : failures;
|
|
58
|
+
throw new AggregateError(errors, `Z3 native cleanup failed: ${errors.map(errorMessage).join('; ')}`, checkFailed ? { cause: checkError } : undefined);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=cleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/cleanup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type Worker = {
|
|
2
|
+
terminate: () => Promise<unknown>;
|
|
3
|
+
};
|
|
4
|
+
type Pool = {
|
|
5
|
+
readonly runningWorkers: readonly Worker[];
|
|
6
|
+
readonly unusedWorkers: readonly Worker[];
|
|
7
|
+
terminateAllThreads: () => void;
|
|
8
|
+
};
|
|
9
|
+
/** Keep Emscripten's message handlers until Node confirms each worker has exited. */
|
|
10
|
+
export declare const closeWorkers: (pool: Pool | undefined) => Promise<void>;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=close-workers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"close-workers.d.ts","sourceRoot":"","sources":["../../src/close-workers.ts"],"names":[],"mappings":"AACA,KAAK,MAAM,GAAG;IAAE,SAAS,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAA;AACnD,KAAK,IAAI,GAAG;IACV,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1C,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,mBAAmB,EAAE,MAAM,IAAI,CAAA;CAChC,CAAA;AAED,qFAAqF;AACrF,eAAO,MAAM,YAAY,GAAU,MAAM,IAAI,GAAG,SAAS,KAAG,OAAO,CAAC,IAAI,CAkBvE,CAAA"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { errorMessage } from "./error-message.js";
|
|
2
|
+
/** Keep Emscripten's message handlers until Node confirms each worker has exited. */
|
|
3
|
+
export const closeWorkers = async (pool) => {
|
|
4
|
+
if (pool === undefined)
|
|
5
|
+
return;
|
|
6
|
+
const stopped = new Set();
|
|
7
|
+
while (true) {
|
|
8
|
+
const workers = [...new Set([...pool.runningWorkers, ...pool.unusedWorkers])]
|
|
9
|
+
.filter(worker => !stopped.has(worker));
|
|
10
|
+
if (workers.length === 0)
|
|
11
|
+
break;
|
|
12
|
+
for (const worker of workers)
|
|
13
|
+
stopped.add(worker);
|
|
14
|
+
// A throwing termination must not prevent attempts on the remaining workers.
|
|
15
|
+
// Keep handlers alive until every attempted termination has settled.
|
|
16
|
+
const results = await Promise.allSettled(workers.map(async (worker) => worker.terminate()));
|
|
17
|
+
const failures = results.flatMap(result => result.status === 'rejected' ? [result.reason] : []);
|
|
18
|
+
if (failures.length === 1)
|
|
19
|
+
throw failures[0];
|
|
20
|
+
if (failures.length > 1)
|
|
21
|
+
throw new AggregateError(failures, `Z3 worker termination failed: ${failures.map(errorMessage).join('; ')}`);
|
|
22
|
+
}
|
|
23
|
+
// The binding replaces handlers with warnings when it clears the pool.
|
|
24
|
+
// Worker exit acknowledgments ensure pending cleanup messages came first.
|
|
25
|
+
pool.terminateAllThreads();
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=close-workers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"close-workers.js","sourceRoot":"","sources":["../../src/close-workers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAQjD,qFAAqF;AACrF,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAE,IAAsB,EAAiB,EAAE;IAC1E,IAAI,IAAI,KAAK,SAAS;QAAE,OAAM;IAC9B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAA;IACjC,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,cAAc,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;aAC1E,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAA;QACzC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,MAAK;QAC/B,KAAK,MAAM,MAAM,IAAI,OAAO;YAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACjD,6EAA6E;QAC7E,qEAAqE;QACrE,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAC,MAAM,EAAC,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAA;QACzF,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;QAC/F,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAA;QAC5C,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,IAAI,cAAc,CAAC,QAAQ,EAAE,iCAAiC,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACvI,CAAC;IACD,uEAAuE;IACvE,0EAA0E;IAC1E,IAAI,CAAC,mBAAmB,EAAE,CAAA;AAC5B,CAAC,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const withDeadline: (context: {
|
|
2
|
+
interrupt: () => void;
|
|
3
|
+
}, timeoutMs: number, check: () => Promise<"sat" | "unsat" | "unknown">) => Promise<{
|
|
4
|
+
readonly status: "sat" | "unsat" | "unknown";
|
|
5
|
+
readonly interrupted: boolean;
|
|
6
|
+
}>;
|
|
7
|
+
//# sourceMappingURL=deadline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deadline.d.ts","sourceRoot":"","sources":["../../src/deadline.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,GACvB,SAAS;IAAE,SAAS,EAAE,MAAM,IAAI,CAAA;CAAE,EAClC,WAAW,MAAM,EACjB,OAAO,MAAM,OAAO,CAAC,KAAK,GAAG,OAAO,GAAG,SAAS,CAAC,KAChD,OAAO,CAAC;IAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,GAAG,OAAO,GAAG,SAAS,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAkCzF,CAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const withDeadline = async (context, timeoutMs, check) => {
|
|
2
|
+
let interrupted = false;
|
|
3
|
+
let interruptFailed = false;
|
|
4
|
+
let interruptError;
|
|
5
|
+
// Dispatch and native cancellation are asynchronous. Keep requesting cancellation
|
|
6
|
+
// until the check settles instead of assuming one interrupt was acknowledged.
|
|
7
|
+
const interrupt = () => {
|
|
8
|
+
interrupted = true;
|
|
9
|
+
try {
|
|
10
|
+
context.interrupt();
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
// Timer failures belong to this check, not the host's uncaught-error
|
|
14
|
+
// handler. Retain one error while continuing cooperative cancellation.
|
|
15
|
+
if (!interruptFailed) {
|
|
16
|
+
interruptFailed = true;
|
|
17
|
+
interruptError = error;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
timer = setTimeout(interrupt, 50);
|
|
21
|
+
timer.unref();
|
|
22
|
+
};
|
|
23
|
+
let timer = setTimeout(interrupt, timeoutMs);
|
|
24
|
+
timer.unref();
|
|
25
|
+
try {
|
|
26
|
+
let status;
|
|
27
|
+
try {
|
|
28
|
+
status = await check();
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (interruptFailed) {
|
|
32
|
+
throw new AggregateError([error, interruptError], `Z3 check failed: ${errorMessage(error)}; deadline interruption also failed: ${errorMessage(interruptError)}`, { cause: error });
|
|
33
|
+
}
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
// Wait for native work before the caller releases its solver/optimizer.
|
|
37
|
+
if (interruptFailed)
|
|
38
|
+
throw interruptError;
|
|
39
|
+
return { status, interrupted };
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
import { errorMessage } from "./error-message.js";
|
|
46
|
+
//# sourceMappingURL=deadline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deadline.js","sourceRoot":"","sources":["../../src/deadline.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAC/B,OAAkC,EAClC,SAAiB,EACjB,KAAiD,EACyC,EAAE;IAC5F,IAAI,WAAW,GAAG,KAAK,CAAA;IACvB,IAAI,eAAe,GAAG,KAAK,CAAA;IAC3B,IAAI,cAAuB,CAAA;IAC3B,kFAAkF;IAClF,8EAA8E;IAC9E,MAAM,SAAS,GAAG,GAAS,EAAE;QAC3B,WAAW,GAAG,IAAI,CAAA;QAClB,IAAI,CAAC;YAAC,OAAO,CAAC,SAAS,EAAE,CAAA;QAAC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACzC,qEAAqE;YACrE,uEAAuE;YACvE,IAAI,CAAC,eAAe,EAAE,CAAC;gBAAC,eAAe,GAAG,IAAI,CAAC;gBAAC,cAAc,GAAG,KAAK,CAAA;YAAC,CAAC;QAC1E,CAAC;QACD,KAAK,GAAG,UAAU,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;QACjC,KAAK,CAAC,KAAK,EAAE,CAAA;IACf,CAAC,CAAA;IACD,IAAI,KAAK,GAAG,UAAU,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;IAC5C,KAAK,CAAC,KAAK,EAAE,CAAA;IACb,IAAI,CAAC;QACH,IAAI,MAAmC,CAAA;QACvC,IAAI,CAAC;YAAC,MAAM,GAAG,MAAM,KAAK,EAAE,CAAA;QAAC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAC5C,IAAI,eAAe,EAAE,CAAC;gBACpB,MAAM,IAAI,cAAc,CAAC,CAAC,KAAK,EAAE,cAAc,CAAC,EAC9C,oBAAoB,YAAY,CAAC,KAAK,CAAC,wCAAwC,YAAY,CAAC,cAAc,CAAC,EAAE,EAC7G,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAA;YACrB,CAAC;YACD,MAAM,KAAK,CAAA;QACb,CAAC;QACD,wEAAwE;QACxE,IAAI,eAAe;YAAE,MAAM,cAAc,CAAA;QACzC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,CAAA;IAChC,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAA;IACrB,CAAC;AACH,CAAC,CAAA;AACD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-message.d.ts","sourceRoot":"","sources":["../../src/error-message.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,eAAO,MAAM,YAAY,GAAI,OAAO,OAAO,KAAG,MAG7C,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Formatting a failure must not prevent runtime cleanup or replace its diagnostic. */
|
|
2
|
+
export const errorMessage = (error) => {
|
|
3
|
+
try {
|
|
4
|
+
return error instanceof Error ? String(error.message) : String(error);
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return '[unprintable thrown value]';
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=error-message.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-message.js","sourceRoot":"","sources":["../../src/error-message.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAc,EAAU,EAAE;IACrD,IAAI,CAAC;QAAC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAAC,CAAC;IAC7E,MAAM,CAAC;QAAC,OAAO,4BAA4B,CAAA;IAAC,CAAC;AAC/C,CAAC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAgB,MAAM,kBAAkB,CAAA;AAiCxD,MAAM,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,GAAG;IAChC,8DAA8D;IAC9D,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,gFAAgF;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CACpC,CAAA;AA8eD,+DAA+D;AAC/D,eAAO,MAAM,MAAM,QAAO,OAAO,CAAC,OAAO,CAGvC,CAAA"}
|
package/dist/src/runtime.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
import { withDeadline } from "./deadline.js";
|
|
2
|
+
import { withRelease } from "./with-release.js";
|
|
3
|
+
import { errorMessage } from "./error-message.js";
|
|
1
4
|
import { performance } from 'node:perf_hooks';
|
|
2
5
|
import { Adapter, Exact, Model } from '@cavelang/solver';
|
|
6
|
+
import { guardCleanup } from "./cleanup.js";
|
|
7
|
+
import { closeWorkers } from "./close-workers.js";
|
|
3
8
|
const backendError = (backend, started, error) => ({
|
|
4
9
|
status: 'unknown',
|
|
5
10
|
reason: {
|
|
6
11
|
kind: 'backend-error',
|
|
7
|
-
message:
|
|
12
|
+
message: errorMessage(error)
|
|
8
13
|
},
|
|
9
14
|
backend,
|
|
10
15
|
diagnostics: [],
|
|
@@ -29,6 +34,7 @@ const compiler = (context, model) => {
|
|
|
29
34
|
domain.id,
|
|
30
35
|
[...domain.values].sort(compareText)
|
|
31
36
|
]));
|
|
37
|
+
const enumCodes = new Map([...enums].map(([domain, values]) => [domain, new Map(values.map((value, index) => [value, index]))]));
|
|
32
38
|
const variables = new Map();
|
|
33
39
|
const bounds = [];
|
|
34
40
|
for (const variable of model.variables) {
|
|
@@ -62,47 +68,110 @@ const compiler = (context, model) => {
|
|
|
62
68
|
}
|
|
63
69
|
}
|
|
64
70
|
}
|
|
65
|
-
const
|
|
71
|
+
const expressions = new WeakMap();
|
|
72
|
+
const compiledChild = (expression) => expressions.get(expression);
|
|
73
|
+
const compileBoolean = (kind, operands) => {
|
|
74
|
+
const combine = (values) => kind === 'and' ? context.And(...values) : context.Or(...values);
|
|
75
|
+
let values = operands.map(value => asBool(compiledChild(value)));
|
|
76
|
+
// The binding also spreads arguments internally, including its AstVector
|
|
77
|
+
// overload. Bound every call and retain all operands through associativity.
|
|
78
|
+
const width = 1024;
|
|
79
|
+
while (values.length > width) {
|
|
80
|
+
const groups = [];
|
|
81
|
+
for (let index = 0; index < values.length; index += width) {
|
|
82
|
+
groups.push(combine(values.slice(index, index + width)));
|
|
83
|
+
}
|
|
84
|
+
values = groups;
|
|
85
|
+
}
|
|
86
|
+
return combine(values);
|
|
87
|
+
};
|
|
88
|
+
const compileNode = (expression) => {
|
|
66
89
|
switch (expression.kind) {
|
|
67
90
|
case 'literal':
|
|
68
91
|
switch (expression.sort) {
|
|
69
92
|
case 'bool': return context.Bool.val(expression.value);
|
|
70
93
|
case 'int': return context.Int.val(Exact.integer(expression.value));
|
|
71
94
|
case 'real': return context.Real.val(exact(expression.value));
|
|
72
|
-
case 'enum': return context.Int.val(
|
|
95
|
+
case 'enum': return context.Int.val(enumCodes.get(expression.domain).get(expression.value));
|
|
73
96
|
}
|
|
74
97
|
case 'variable': return variables.get(expression.id);
|
|
75
|
-
case 'not': return asBool(
|
|
76
|
-
case 'and':
|
|
77
|
-
case 'or': return
|
|
78
|
-
case 'implies': return context.Implies(asBool(
|
|
79
|
-
case 'eq': return
|
|
80
|
-
case 'neq': return
|
|
81
|
-
case 'lt': return asArith(
|
|
82
|
-
case 'lte': return asArith(
|
|
83
|
-
case 'gt': return asArith(
|
|
84
|
-
case 'gte': return asArith(
|
|
98
|
+
case 'not': return asBool(compiledChild(expression.value)).not();
|
|
99
|
+
case 'and':
|
|
100
|
+
case 'or': return compileBoolean(expression.kind, expression.operands);
|
|
101
|
+
case 'implies': return context.Implies(asBool(compiledChild(expression.left)), asBool(compiledChild(expression.right)));
|
|
102
|
+
case 'eq': return compiledChild(expression.left).eq(compiledChild(expression.right));
|
|
103
|
+
case 'neq': return compiledChild(expression.left).neq(compiledChild(expression.right));
|
|
104
|
+
case 'lt': return asArith(compiledChild(expression.left)).lt(asArith(compiledChild(expression.right)));
|
|
105
|
+
case 'lte': return asArith(compiledChild(expression.left)).le(asArith(compiledChild(expression.right)));
|
|
106
|
+
case 'gt': return asArith(compiledChild(expression.left)).gt(asArith(compiledChild(expression.right)));
|
|
107
|
+
case 'gte': return asArith(compiledChild(expression.left)).ge(asArith(compiledChild(expression.right)));
|
|
85
108
|
case 'add': {
|
|
86
|
-
const [first, ...rest] = expression.operands.map(value => asArith(
|
|
109
|
+
const [first, ...rest] = expression.operands.map(value => asArith(compiledChild(value)));
|
|
87
110
|
return rest.reduce((left, right) => left.add(right), first);
|
|
88
111
|
}
|
|
89
112
|
case 'multiply': {
|
|
90
|
-
const [first, ...rest] = expression.operands.map(value => asArith(
|
|
113
|
+
const [first, ...rest] = expression.operands.map(value => asArith(compiledChild(value)));
|
|
91
114
|
return rest.reduce((left, right) => left.mul(right), first);
|
|
92
115
|
}
|
|
93
|
-
case 'subtract': return asArith(
|
|
116
|
+
case 'subtract': return asArith(compiledChild(expression.left)).sub(asArith(compiledChild(expression.right)));
|
|
94
117
|
case 'divide': {
|
|
95
|
-
const left = asArith(
|
|
96
|
-
const right = asArith(
|
|
118
|
+
const left = asArith(compiledChild(expression.left));
|
|
119
|
+
const right = asArith(compiledChild(expression.right));
|
|
97
120
|
// Portable division is exact-real division even when both operands are
|
|
98
121
|
// integers; Z3's native Int / Int operation would truncate instead.
|
|
99
122
|
const real = (value) => context.isInt(value) ? context.ToReal(value) : value;
|
|
100
123
|
return real(left).div(real(right));
|
|
101
124
|
}
|
|
102
|
-
case 'negate': return asArith(
|
|
103
|
-
case 'if': return context.If(asBool(
|
|
125
|
+
case 'negate': return asArith(compiledChild(expression.value)).neg();
|
|
126
|
+
case 'if': return context.If(asBool(compiledChild(expression.condition)), compiledChild(expression.then), compiledChild(expression.else));
|
|
104
127
|
}
|
|
105
128
|
};
|
|
129
|
+
function* children(expression) {
|
|
130
|
+
switch (expression.kind) {
|
|
131
|
+
case 'literal':
|
|
132
|
+
case 'variable': return;
|
|
133
|
+
case 'not':
|
|
134
|
+
case 'negate':
|
|
135
|
+
yield expression.value;
|
|
136
|
+
return;
|
|
137
|
+
case 'and':
|
|
138
|
+
case 'or':
|
|
139
|
+
case 'add':
|
|
140
|
+
case 'multiply':
|
|
141
|
+
yield* expression.operands;
|
|
142
|
+
return;
|
|
143
|
+
case 'if':
|
|
144
|
+
yield expression.condition;
|
|
145
|
+
yield expression.then;
|
|
146
|
+
yield expression.else;
|
|
147
|
+
return;
|
|
148
|
+
default:
|
|
149
|
+
yield expression.left;
|
|
150
|
+
yield expression.right;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const compile = (expression) => {
|
|
154
|
+
if (expressions.has(expression))
|
|
155
|
+
return expressions.get(expression);
|
|
156
|
+
const stack = [{ expression, children: children(expression) }];
|
|
157
|
+
const active = new WeakSet([expression]);
|
|
158
|
+
while (stack.length > 0) {
|
|
159
|
+
const frame = stack[stack.length - 1];
|
|
160
|
+
const child = frame.children.next();
|
|
161
|
+
if (child.done) {
|
|
162
|
+
expressions.set(frame.expression, compileNode(frame.expression));
|
|
163
|
+
active.delete(frame.expression);
|
|
164
|
+
stack.pop();
|
|
165
|
+
}
|
|
166
|
+
else if (!expressions.has(child.value)) {
|
|
167
|
+
if (active.has(child.value))
|
|
168
|
+
throw new TypeError('cyclic solver expression');
|
|
169
|
+
active.add(child.value);
|
|
170
|
+
stack.push({ expression: child.value, children: children(child.value) });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return expressions.get(expression);
|
|
174
|
+
};
|
|
106
175
|
return {
|
|
107
176
|
variables,
|
|
108
177
|
enums,
|
|
@@ -164,20 +233,6 @@ const numericValue = (context, value) => {
|
|
|
164
233
|
};
|
|
165
234
|
throw new TypeError(`Z3 returned a non-rational objective value: ${value.sexpr()}`);
|
|
166
235
|
};
|
|
167
|
-
const withDeadline = async (context, timeoutMs, check) => {
|
|
168
|
-
let interrupted = false;
|
|
169
|
-
const timer = setTimeout(() => {
|
|
170
|
-
interrupted = true;
|
|
171
|
-
context.interrupt();
|
|
172
|
-
}, timeoutMs);
|
|
173
|
-
timer.unref();
|
|
174
|
-
try {
|
|
175
|
-
return { status: await check(), interrupted };
|
|
176
|
-
}
|
|
177
|
-
finally {
|
|
178
|
-
clearTimeout(timer);
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
236
|
const unknown = (backend, started, reason, interrupted) => {
|
|
182
237
|
const timeout = interrupted || /timeout|canceled|cancelled/i.test(reason);
|
|
183
238
|
const memory = /memory|memout/i.test(reason);
|
|
@@ -194,12 +249,13 @@ const unknown = (backend, started, reason, interrupted) => {
|
|
|
194
249
|
};
|
|
195
250
|
};
|
|
196
251
|
const addHard = (solver, compiled, track) => {
|
|
197
|
-
|
|
252
|
+
for (let index = 0; index < compiled.bounds.length; index += 1024) {
|
|
253
|
+
solver.add(...compiled.bounds.slice(index, index + 1024));
|
|
254
|
+
}
|
|
198
255
|
const trackers = new Map();
|
|
199
256
|
for (const [id, expression] of compiled.constraints) {
|
|
200
257
|
if (track) {
|
|
201
|
-
const
|
|
202
|
-
const tracker = solver.ctx.Bool.const(symbol);
|
|
258
|
+
const tracker = solver.ctx.Bool.fresh('cave.constraint');
|
|
203
259
|
solver.addAndTrack(expression, tracker);
|
|
204
260
|
trackers.set(tracker.id(), id);
|
|
205
261
|
}
|
|
@@ -211,7 +267,7 @@ const addHard = (solver, compiled, track) => {
|
|
|
211
267
|
};
|
|
212
268
|
const coreFor = async (context, compiled, limits) => {
|
|
213
269
|
const solver = new context.Solver();
|
|
214
|
-
|
|
270
|
+
return withRelease(async () => {
|
|
215
271
|
solver.set('timeout', limits.timeoutMs);
|
|
216
272
|
const trackers = addHard(solver, compiled, true);
|
|
217
273
|
if ((await withDeadline(context, limits.timeoutMs, () => solver.check())).status !== 'unsat')
|
|
@@ -220,10 +276,7 @@ const coreFor = async (context, compiled, limits) => {
|
|
|
220
276
|
.map(value => trackers.get(value.id()))
|
|
221
277
|
.filter((value) => value !== undefined)
|
|
222
278
|
.sort();
|
|
223
|
-
}
|
|
224
|
-
finally {
|
|
225
|
-
solver.release();
|
|
226
|
-
}
|
|
279
|
+
}, () => solver.release());
|
|
227
280
|
};
|
|
228
281
|
const limitOutput = (result, limits) => Buffer.byteLength(JSON.stringify(result)) <= limits.maxOutputBytes
|
|
229
282
|
? result
|
|
@@ -240,13 +293,16 @@ const limitOutput = (result, limits) => Buffer.byteLength(JSON.stringify(result)
|
|
|
240
293
|
};
|
|
241
294
|
const solveModel = async (api, context, backend, model, request) => {
|
|
242
295
|
const started = performance.now();
|
|
296
|
+
if (!Number.isSafeInteger(request.limits.timeoutMs) || request.limits.timeoutMs < 1 || request.limits.timeoutMs > 2147483647) {
|
|
297
|
+
return backendError(backend, started, new TypeError('Z3 timeoutMs must be in 1..2147483647'));
|
|
298
|
+
}
|
|
243
299
|
try {
|
|
244
300
|
api.setParam('memory_max_size', Math.max(1, Math.floor(request.limits.maxMemoryBytes / 1024 / 1024)));
|
|
245
301
|
const compiled = compiler(context, model);
|
|
246
302
|
const optimize = compiled.objectives.length > 0 || compiled.softConstraints.length > 0;
|
|
247
303
|
if (!optimize) {
|
|
248
304
|
const solver = new context.Solver();
|
|
249
|
-
|
|
305
|
+
return await withRelease(async () => {
|
|
250
306
|
solver.set('timeout', request.limits.timeoutMs);
|
|
251
307
|
const trackers = addHard(solver, compiled, request.unsatCore);
|
|
252
308
|
const checked = await withDeadline(context, request.limits.timeoutMs, () => solver.check());
|
|
@@ -276,22 +332,16 @@ const solveModel = async (api, context, backend, model, request) => {
|
|
|
276
332
|
diagnostics: [],
|
|
277
333
|
elapsedMs: Math.round(performance.now() - started)
|
|
278
334
|
}, request.limits);
|
|
279
|
-
}
|
|
280
|
-
finally {
|
|
281
|
-
solver.release();
|
|
282
|
-
}
|
|
335
|
+
}, () => solver.release());
|
|
283
336
|
}
|
|
284
337
|
const optimizer = new context.Optimize();
|
|
285
|
-
|
|
338
|
+
return await withRelease(async () => {
|
|
286
339
|
optimizer.set('timeout', request.limits.timeoutMs);
|
|
287
340
|
optimizer.set('priority', 'lex');
|
|
288
341
|
addHard(optimizer, compiled, false);
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
else
|
|
293
|
-
optimizer.maximize(objective.expression);
|
|
294
|
-
}
|
|
342
|
+
const handles = compiled.objectives.map(objective => objective.direction === 'minimize'
|
|
343
|
+
? optimizer.minimize(objective.expression)
|
|
344
|
+
: optimizer.maximize(objective.expression));
|
|
295
345
|
// Soft preferences are deliberately the lowest-priority objective. Their
|
|
296
346
|
// weights are explicit model data and never derived from CAVE confidence.
|
|
297
347
|
for (const constraint of compiled.softConstraints) {
|
|
@@ -313,6 +363,25 @@ const solveModel = async (api, context, backend, model, request) => {
|
|
|
313
363
|
}, request.limits);
|
|
314
364
|
}
|
|
315
365
|
const z3Model = optimizer.model();
|
|
366
|
+
const rational = (value) => {
|
|
367
|
+
if (context.isIntVal(value))
|
|
368
|
+
return value.value().toString();
|
|
369
|
+
if (context.isRealVal(value))
|
|
370
|
+
return {
|
|
371
|
+
numerator: value.value().numerator.toString(),
|
|
372
|
+
denominator: value.value().denominator.toString()
|
|
373
|
+
};
|
|
374
|
+
return undefined;
|
|
375
|
+
};
|
|
376
|
+
for (const [index, objective] of compiled.objectives.entries()) {
|
|
377
|
+
const lower = rational(optimizer.getLower(handles[index]));
|
|
378
|
+
const upper = rational(optimizer.getUpper(handles[index]));
|
|
379
|
+
const attained = rational(z3Model.eval(objective.expression, true));
|
|
380
|
+
if (lower === undefined || upper === undefined || attained === undefined ||
|
|
381
|
+
Exact.compare(lower, upper) !== 0 || Exact.compare(lower, attained) !== 0) {
|
|
382
|
+
return unknown(backend, started, `objective ${JSON.stringify(objective.id)} has no proved finite attained optimum`, false);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
316
385
|
return limitOutput({
|
|
317
386
|
status: 'optimal',
|
|
318
387
|
assignment: assignment(context, compiled, model, z3Model),
|
|
@@ -325,10 +394,7 @@ const solveModel = async (api, context, backend, model, request) => {
|
|
|
325
394
|
diagnostics: [],
|
|
326
395
|
elapsedMs: Math.round(performance.now() - started)
|
|
327
396
|
}, request.limits);
|
|
328
|
-
}
|
|
329
|
-
finally {
|
|
330
|
-
optimizer.release();
|
|
331
|
-
}
|
|
397
|
+
}, () => optimizer.release());
|
|
332
398
|
}
|
|
333
399
|
catch (error) {
|
|
334
400
|
if (api.Z3.get_estimated_alloc_size() > BigInt(request.limits.maxMemoryBytes)) {
|
|
@@ -348,16 +414,18 @@ const solveModel = async (api, context, backend, model, request) => {
|
|
|
348
414
|
}
|
|
349
415
|
};
|
|
350
416
|
let singleton;
|
|
417
|
+
let shutdown;
|
|
351
418
|
const initialize = async () => {
|
|
352
419
|
const started = performance.now();
|
|
353
420
|
// Keep this as the only runtime import: importing @cavelang/solver-z3 itself
|
|
354
421
|
// must not load the 34 MB Wasm artifact or start worker threads.
|
|
355
422
|
const api = await import('z3-solver').then(module => module.init());
|
|
423
|
+
guardCleanup(api.Z3);
|
|
356
424
|
const context = api.Context('cave');
|
|
357
425
|
const backend = Object.freeze({ name: 'z3-wasm', version: api.Z3.get_full_version() });
|
|
358
426
|
let tail = Promise.resolve();
|
|
359
427
|
let closing = false;
|
|
360
|
-
let
|
|
428
|
+
let closePromise;
|
|
361
429
|
const enqueue = (work) => {
|
|
362
430
|
const result = tail.then(work, work);
|
|
363
431
|
tail = result.then(() => undefined, () => undefined);
|
|
@@ -372,27 +440,24 @@ const initialize = async () => {
|
|
|
372
440
|
return Promise.resolve(backendError(backend, performance.now(), new Error('Z3 runtime is closed')));
|
|
373
441
|
return enqueue(() => solveModel(api, context, backend, model, request));
|
|
374
442
|
},
|
|
375
|
-
close:
|
|
376
|
-
if (
|
|
377
|
-
return;
|
|
443
|
+
close: () => {
|
|
444
|
+
if (closePromise)
|
|
445
|
+
return closePromise;
|
|
378
446
|
closing = true;
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
// Let the completed pthread post its cleanup message before forcibly
|
|
383
|
-
// terminating the pool. Without this turn, Emscripten can report a
|
|
384
|
-
// late `cleanupThread` command from a worker we just terminated.
|
|
385
|
-
await new Promise(resolve => setTimeout(resolve, 0));
|
|
386
|
-
api.em?.PThread?.terminateAllThreads?.();
|
|
387
|
-
closed = true;
|
|
447
|
+
closePromise = enqueue(async () => {
|
|
448
|
+
// Await worker exits before Emscripten discards their message handlers.
|
|
449
|
+
await closeWorkers(api.em?.PThread);
|
|
388
450
|
singleton = undefined;
|
|
451
|
+
shutdown = undefined;
|
|
389
452
|
});
|
|
453
|
+
shutdown = closePromise;
|
|
454
|
+
return closePromise;
|
|
390
455
|
}
|
|
391
456
|
};
|
|
392
457
|
return runtime;
|
|
393
458
|
};
|
|
394
459
|
/** Lazily initialize and reuse one process-wide Z3 runtime. */
|
|
395
|
-
export const create = () => singleton ??= initialize().catch(error => {
|
|
460
|
+
export const create = () => shutdown ? shutdown.then(create) : singleton ??= initialize().catch(error => {
|
|
396
461
|
singleton = undefined;
|
|
397
462
|
throw error;
|
|
398
463
|
});
|