mirror of
https://github.com/Z3Prover/z3
synced 2026-07-14 19:15:41 +00:00
Merge 1faba5c769 into 038b367d68
This commit is contained in:
commit
7d1bdf041e
11 changed files with 445 additions and 22 deletions
7
.github/workflows/wasm-release.yml
vendored
7
.github/workflows/wasm-release.yml
vendored
|
|
@ -48,7 +48,7 @@ jobs:
|
|||
- name: Build TypeScript
|
||||
run: npm run build:ts
|
||||
|
||||
- name: Build wasm
|
||||
- name: Build wasm (threaded)
|
||||
run: |
|
||||
emsdk install ${EM_VERSION}
|
||||
emsdk activate ${EM_VERSION}
|
||||
|
|
@ -57,6 +57,11 @@ jobs:
|
|||
which clang++
|
||||
npm run build:wasm
|
||||
|
||||
- name: Build wasm (single-threaded)
|
||||
run: |
|
||||
source $(dirname $(which emsdk))/emsdk_env.sh
|
||||
npm run build:wasm:singlethread
|
||||
|
||||
- name: Test
|
||||
run: npm test
|
||||
|
||||
|
|
|
|||
36
.github/workflows/wasm.yml
vendored
36
.github/workflows/wasm.yml
vendored
|
|
@ -17,7 +17,7 @@ permissions:
|
|||
|
||||
jobs:
|
||||
check:
|
||||
name: Check
|
||||
name: Check (threaded)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
|
@ -52,3 +52,37 @@ jobs:
|
|||
|
||||
- name: Test
|
||||
run: npm test
|
||||
|
||||
check-singlethread:
|
||||
name: Check (single-threaded)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7.0.0
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "lts/*"
|
||||
|
||||
- name: Setup emscripten
|
||||
uses: mymindstorm/setup-emsdk@v16
|
||||
with:
|
||||
no-install: true
|
||||
version: ${{env.EM_VERSION}}
|
||||
actions-cache-folder: "emsdk-cache"
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build TypeScript
|
||||
run: npm run build:ts
|
||||
|
||||
- name: Build single-threaded wasm
|
||||
run: |
|
||||
emsdk install ${EM_VERSION}
|
||||
emsdk activate ${EM_VERSION}
|
||||
source $(dirname $(which emsdk))/emsdk_env.sh
|
||||
which node
|
||||
which clang++
|
||||
npm run build:wasm:singlethread
|
||||
|
|
|
|||
|
|
@ -29,10 +29,66 @@ const api = await init({
|
|||
|
||||
### Limitations
|
||||
|
||||
The package requires threads, which means you'll need to be running in an environment which supports `SharedArrayBuffer`. In browsers, in addition to ensuring the browser has implemented `SharedArrayBuffer`, you'll need to serve your page with [special headers](https://web.dev/coop-coep/). There's a [neat trick](https://github.com/gzuidhof/coi-serviceworker) for doing that client-side on e.g. Github Pages, though you shouldn't use that trick in more complex applications.
|
||||
The default package requires threads, which means you'll need to be running in an environment which supports `SharedArrayBuffer`. In browsers, in addition to ensuring the browser has implemented `SharedArrayBuffer`, you'll need to serve your page with [special headers](https://web.dev/coop-coep/). There's a [neat trick](https://github.com/gzuidhof/coi-serviceworker) for doing that client-side on e.g. Github Pages, though you shouldn't use that trick in more complex applications.
|
||||
|
||||
The Emscripten worker model will spawn multiple instances of `z3-built.js` for long-running operations. When building for the web, you should include that file as its own script on the page - using a bundler like webpack will prevent it from loading correctly.
|
||||
|
||||
#### Single-threaded build (no SharedArrayBuffer required)
|
||||
|
||||
If you cannot set the COOP/COEP headers required by the default threaded build (for example because your application embeds cross-origin iframes such as YouTube), you can use the single-threaded variant instead:
|
||||
|
||||
```javascript
|
||||
// Node.js / bundler (no SharedArrayBuffer needed)
|
||||
const { init } = require('z3-solver/singlethread');
|
||||
```
|
||||
|
||||
```typescript
|
||||
// TypeScript
|
||||
import { init } from 'z3-solver/singlethread';
|
||||
```
|
||||
|
||||
The API is identical to the default export. The key difference is that all Z3 operations run **synchronously in the calling thread** rather than in a background thread. To avoid blocking the UI, run your Z3 code inside a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers):
|
||||
|
||||
```javascript
|
||||
// my-z3-worker.js (loaded as a classic Web Worker)
|
||||
|
||||
// 1. Load the single-threaded WASM artifact (exposes initZ3SingleThread globally).
|
||||
importScripts('z3-built-singlethread.js');
|
||||
|
||||
// 2. Load the single-threaded entry point as a classic script bundle.
|
||||
// Most bundlers can produce a CJS/UMD build that can be loaded with importScripts.
|
||||
// Alternatively, switch to a module worker (new Worker(..., { type: 'module' }))
|
||||
// and use ES module import syntax instead.
|
||||
importScripts('z3-solver-singlethread-bundle.js'); // exposes Z3SolverSingleThread
|
||||
|
||||
const { init } = Z3SolverSingleThread;
|
||||
const { Context } = await init();
|
||||
const { Solver, Int } = new Context('main');
|
||||
|
||||
// 3. Use Z3 normally – calls block this worker thread, not the main thread.
|
||||
const x = Int.const('x');
|
||||
const solver = new Solver();
|
||||
solver.add(x.ge(0));
|
||||
self.postMessage(await solver.check()); // 'sat'
|
||||
```
|
||||
|
||||
Or with a **module worker** (supported in modern browsers):
|
||||
|
||||
```javascript
|
||||
// my-z3-worker.mjs (loaded as new Worker('...', { type: 'module' }))
|
||||
import { init } from 'z3-solver/singlethread';
|
||||
|
||||
const { Context } = await init();
|
||||
const { Solver, Int } = new Context('main');
|
||||
|
||||
const x = Int.const('x');
|
||||
const solver = new Solver();
|
||||
solver.add(x.ge(0));
|
||||
self.postMessage(await solver.check()); // 'sat'
|
||||
```
|
||||
|
||||
The single-threaded WASM file (`z3-built-singlethread.js` / `z3-built-singlethread.wasm`) is distributed alongside the default build in the npm package.
|
||||
|
||||
## High-level
|
||||
|
||||
You can find the documentation for the high-level Z3 API [here](https://z3prover.github.io/api/html/js/index.html). There are some usage examples in `src/high-level/high-level.test.ts`
|
||||
|
|
|
|||
|
|
@ -18,6 +18,18 @@
|
|||
"browser": "build/browser.js",
|
||||
"main": "build/node.js",
|
||||
"types": "build/node.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./build/node.d.ts",
|
||||
"browser": "./build/browser.js",
|
||||
"default": "./build/node.js"
|
||||
},
|
||||
"./singlethread": {
|
||||
"types": "./build/node-singlethread.d.ts",
|
||||
"browser": "./build/browser-singlethread.js",
|
||||
"default": "./build/node-singlethread.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"build/**/*.{js,d.ts,wasm}"
|
||||
],
|
||||
|
|
@ -26,6 +38,7 @@
|
|||
"build:ts:tsc": "tsc --pretty --project tsconfig.build.json ",
|
||||
"build:ts:generate": "ts-node --transpileOnly scripts/make-ts-wrapper.ts src/low-level/wrapper.__GENERATED__.ts src/low-level/types.__GENERATED__.ts",
|
||||
"build:wasm": "ts-node --transpileOnly ./scripts/build-wasm.ts",
|
||||
"build:wasm:singlethread": "ts-node --transpileOnly ./scripts/build-wasm-singlethread.ts",
|
||||
"clean": "rimraf build 'src/**/*.__GENERATED__.*'",
|
||||
"lint": "prettier -c '{./,src/,scripts/,examples/}**/*.{js,ts}'",
|
||||
"format": "prettier --write '{./,src/,scripts/}**/*.{js,ts}'",
|
||||
|
|
|
|||
86
src/api/js/scripts/build-wasm-singlethread.ts
Normal file
86
src/api/js/scripts/build-wasm-singlethread.ts
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
import assert from 'assert';
|
||||
import { SpawnOptions, spawnSync as originalSpawnSync } from 'child_process';
|
||||
import fs, { existsSync } from 'fs';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import process from 'process';
|
||||
import { asyncFuncs } from './async-fns';
|
||||
import { makeCCSinglethreadWrapper } from './make-cc-wrapper';
|
||||
import { functions } from './parse-api';
|
||||
|
||||
console.log('--- Building single-threaded WASM (no SharedArrayBuffer required)');
|
||||
|
||||
// Single-threaded build: no -pthread / USE_PTHREADS.
|
||||
// POLLING_TIMER gives timeout support via wall-clock polling rather than threads.
|
||||
const SWAP_OPTS: SpawnOptions = {
|
||||
shell: true,
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
...process.env,
|
||||
CXXFLAGS: '-DSINGLE_THREAD -DPOLLING_TIMER -fexceptions -s DISABLE_EXCEPTION_CATCHING=0',
|
||||
LDFLAGS: '-s WASM_BIGINT',
|
||||
FPMATH_ENABLED: 'False', // Until Safari supports WASM SSE, we have to disable fast FP support
|
||||
},
|
||||
};
|
||||
|
||||
function spawnSync(command: string, opts: SpawnOptions = {}) {
|
||||
console.log(`- ${command}`);
|
||||
// TODO(ritave): Create a splitter that keeps track of quoted strings
|
||||
const [cmd, ...args] = command.split(' ');
|
||||
const { error, ...rest } = originalSpawnSync(cmd, args, { ...SWAP_OPTS, ...opts });
|
||||
if (error !== undefined || rest.status !== 0) {
|
||||
if (error !== undefined) {
|
||||
console.error(error.message);
|
||||
} else {
|
||||
console.error(`Process exited with status ${rest.status}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
return rest;
|
||||
}
|
||||
|
||||
function exportedFuncs(): string[] {
|
||||
const extras = [
|
||||
'_malloc',
|
||||
'_free',
|
||||
'_set_throwy_error_handler',
|
||||
'_set_noop_error_handler',
|
||||
...asyncFuncs.map(f => '_async_' + f),
|
||||
];
|
||||
|
||||
return [...extras, ...(functions as any[]).map(f => '_' + f.name)];
|
||||
}
|
||||
|
||||
assert(fs.existsSync('./package.json'), 'Not in the root directory of js api');
|
||||
const z3RootDir = path.join(process.cwd(), '../../../');
|
||||
|
||||
// Single-threaded libz3.a is built in a separate directory to avoid
|
||||
// conflicting with the threaded build in 'build/'.
|
||||
const singlethreadBuildDir = 'build-singlethread';
|
||||
const singlethreadBuildPath = path.join(z3RootDir, singlethreadBuildDir);
|
||||
|
||||
if (!existsSync(path.join(singlethreadBuildPath, 'Makefile'))) {
|
||||
spawnSync(
|
||||
`emconfigure python scripts/mk_make.py --staticlib --single-threaded --arm64=false --build=${singlethreadBuildDir}`,
|
||||
{ cwd: z3RootDir },
|
||||
);
|
||||
}
|
||||
|
||||
spawnSync(`emmake make -j${os.cpus().length} libz3.a`, { cwd: singlethreadBuildPath });
|
||||
|
||||
const ccWrapperPath = 'build/async-fns-singlethread.cc';
|
||||
console.log(`- Building ${ccWrapperPath}`);
|
||||
fs.mkdirSync(path.dirname(ccWrapperPath), { recursive: true });
|
||||
fs.writeFileSync(ccWrapperPath, makeCCSinglethreadWrapper());
|
||||
|
||||
const fns = JSON.stringify(exportedFuncs());
|
||||
// No PThread in exported methods for the single-threaded build.
|
||||
const methods = '["ccall","FS","UTF8ToString","intArrayFromString","addFunction","removeFunction"]';
|
||||
const libz3a = path.normalize(`../../../${singlethreadBuildDir}/libz3.a`);
|
||||
spawnSync(
|
||||
`emcc build/async-fns-singlethread.cc ${libz3a} --std=c++20 --pre-js src/low-level/async-wrapper.js -fexceptions -s WASM_BIGINT -s MODULARIZE=1 -s 'EXPORT_NAME="initZ3SingleThread"' -s EXPORTED_RUNTIME_METHODS=${methods} -s EXPORTED_FUNCTIONS=${fns} -s DISABLE_EXCEPTION_CATCHING=0 -s SAFE_HEAP=0 -s ALLOW_MEMORY_GROWTH=1 -s INITIAL_MEMORY=64MB -s TOTAL_STACK=20MB -s ALLOW_TABLE_GROWTH=1 -I z3/src/api/ -o build/z3-built-singlethread.js`,
|
||||
);
|
||||
|
||||
fs.rmSync(ccWrapperPath);
|
||||
|
||||
console.log('--- Single-threaded WASM build finished');
|
||||
|
|
@ -4,6 +4,34 @@ import path from 'path';
|
|||
import { asyncFuncs } from './async-fns';
|
||||
import { functions } from './parse-api';
|
||||
|
||||
// Shared error handler code used by both threaded and single-threaded wrappers
|
||||
const SHARED_ERROR_HANDLERS = `
|
||||
class Z3Exception : public std::exception {
|
||||
public:
|
||||
const std::string m_msg;
|
||||
Z3Exception(const std::string& msg) : m_msg(msg) {}
|
||||
virtual const char* what() const throw () {
|
||||
return m_msg.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
void throwy_error_handler(Z3_context ctx, Z3_error_code c) {
|
||||
throw Z3Exception(Z3_get_error_msg(ctx, c));
|
||||
}
|
||||
|
||||
void noop_error_handler(Z3_context ctx, Z3_error_code c) {
|
||||
// pass
|
||||
}
|
||||
|
||||
extern "C" void set_throwy_error_handler(Z3_context ctx) {
|
||||
Z3_set_error_handler(ctx, throwy_error_handler);
|
||||
}
|
||||
|
||||
extern "C" void set_noop_error_handler(Z3_context ctx) {
|
||||
Z3_set_error_handler(ctx, noop_error_handler);
|
||||
}
|
||||
`.trim();
|
||||
|
||||
export function makeCCWrapper() {
|
||||
let wrappers = [];
|
||||
|
||||
|
|
@ -213,30 +241,77 @@ void wrapper_str(Args&&... args) {
|
|||
|
||||
|
||||
|
||||
class Z3Exception : public std::exception {
|
||||
public:
|
||||
const std::string m_msg;
|
||||
Z3Exception(const std::string& msg) : m_msg(msg) {}
|
||||
virtual const char* what() const throw () {
|
||||
return m_msg.c_str();
|
||||
${SHARED_ERROR_HANDLERS}
|
||||
|
||||
${wrappers.join('\n\n')}
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a C++ wrapper file for async Z3 functions that runs each function
|
||||
* synchronously in the calling thread. This version does NOT use pthreads and
|
||||
* therefore does NOT require SharedArrayBuffer / COOP-COEP headers.
|
||||
*
|
||||
* Intended to be paired with the Emscripten build flags:
|
||||
* -DSINGLE_THREAD -DPOLLING_TIMER (no -pthread / USE_PTHREADS)
|
||||
*/
|
||||
export function makeCCSinglethreadWrapper() {
|
||||
const wrappers: string[] = [];
|
||||
|
||||
for (const fnName of asyncFuncs) {
|
||||
const fn = functions.find(f => f.name === fnName);
|
||||
if (fn == null) {
|
||||
throw new Error(`could not find definition for ${fnName}`);
|
||||
}
|
||||
|
||||
const isString = fn.cRet === 'Z3_string';
|
||||
const returnType = isString ? 'auto' : fn.cRet;
|
||||
|
||||
const paramList = fn.params
|
||||
.map(p => `${p.isConst ? 'const ' : ''}${p.cType}${p.isPtr ? '*' : ''} ${p.name}${p.isArray ? '[]' : ''}`)
|
||||
.join(', ');
|
||||
|
||||
const callArgs = fn.params.map(p => p.name).join(', ');
|
||||
|
||||
const resolveExpr = isString
|
||||
? `EM_ASM({
|
||||
resolve_async(UTF8ToString($0));
|
||||
}, result);`
|
||||
: `EM_ASM({
|
||||
resolve_async($0);
|
||||
}, result);`;
|
||||
|
||||
wrappers.push(
|
||||
`
|
||||
extern "C" void async_${fn.name}(${paramList}) {
|
||||
try {
|
||||
${returnType} result = ${fn.name}(${callArgs});
|
||||
${resolveExpr}
|
||||
} catch (std::exception& e) {
|
||||
EM_ASM({
|
||||
reject_async(new Error(UTF8ToString($0)));
|
||||
}, e.what());
|
||||
} catch (...) {
|
||||
EM_ASM({
|
||||
reject_async(new Error('failed with unknown exception'));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
void throwy_error_handler(Z3_context ctx, Z3_error_code c) {
|
||||
throw Z3Exception(Z3_get_error_msg(ctx, c));
|
||||
}
|
||||
`.trim(),
|
||||
);
|
||||
}
|
||||
|
||||
void noop_error_handler(Z3_context ctx, Z3_error_code c) {
|
||||
// pass
|
||||
}
|
||||
return `// THIS FILE IS AUTOMATICALLY GENERATED BY ${path.basename(__filename)}
|
||||
// DO NOT EDIT IT BY HAND
|
||||
|
||||
extern "C" void set_throwy_error_handler(Z3_context ctx) {
|
||||
Z3_set_error_handler(ctx, throwy_error_handler);
|
||||
}
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
extern "C" void set_noop_error_handler(Z3_context ctx) {
|
||||
Z3_set_error_handler(ctx, noop_error_handler);
|
||||
}
|
||||
#include <emscripten.h>
|
||||
|
||||
#include "../../z3.h"
|
||||
|
||||
${SHARED_ERROR_HANDLERS}
|
||||
|
||||
${wrappers.join('\n\n')}
|
||||
`;
|
||||
|
|
|
|||
41
src/api/js/src/browser-singlethread.test.ts
Normal file
41
src/api/js/src/browser-singlethread.test.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
const mockInitWrapper = jest.fn();
|
||||
const mockCreateApi = jest.fn();
|
||||
|
||||
jest.mock('./low-level', () => ({
|
||||
init: mockInitWrapper,
|
||||
Z3Core: undefined,
|
||||
Z3LowLevel: undefined,
|
||||
}));
|
||||
jest.mock('./high-level', () => ({
|
||||
createApi: mockCreateApi,
|
||||
}));
|
||||
|
||||
import { init } from './browser-singlethread';
|
||||
|
||||
describe('browser-singlethread init', () => {
|
||||
beforeEach(() => {
|
||||
delete (global as any).initZ3SingleThread;
|
||||
mockInitWrapper.mockReset();
|
||||
mockCreateApi.mockReset();
|
||||
});
|
||||
|
||||
it('passes module overrides to the browser-singlethread initializer', async () => {
|
||||
const initZ3SingleThread = jest.fn();
|
||||
const locateFile = jest.fn((file: string) => `https://example.test/${file}`);
|
||||
const lowLevel = { Z3: { low: true }, em: { module: true } };
|
||||
const highLevel = { Context: jest.fn() };
|
||||
(global as any).initZ3SingleThread = initZ3SingleThread;
|
||||
mockInitWrapper.mockResolvedValue(lowLevel);
|
||||
mockCreateApi.mockReturnValue(highLevel);
|
||||
|
||||
const api = await init({ locateFile });
|
||||
|
||||
expect(mockInitWrapper).toHaveBeenCalledWith(initZ3SingleThread, { locateFile });
|
||||
expect(mockCreateApi).toHaveBeenCalledWith(lowLevel.Z3, lowLevel.em);
|
||||
expect(api).toEqual({ ...lowLevel, ...highLevel });
|
||||
});
|
||||
|
||||
it('throws when initZ3SingleThread is unavailable', async () => {
|
||||
await expect(init()).rejects.toThrow('initZ3SingleThread was not found.');
|
||||
});
|
||||
});
|
||||
35
src/api/js/src/browser-singlethread.ts
Normal file
35
src/api/js/src/browser-singlethread.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { createApi, Z3HighLevel } from './high-level';
|
||||
import { init as initWrapper, Z3LowLevel, Z3ModuleOverrides } from './low-level';
|
||||
export * from './high-level/types';
|
||||
export { Z3Core, Z3LowLevel } from './low-level';
|
||||
export * from './low-level/types.__GENERATED__';
|
||||
|
||||
// Name of the global exposed by z3-built-singlethread.js when loaded as a script.
|
||||
const INIT_GLOBAL = 'initZ3SingleThread';
|
||||
|
||||
/**
|
||||
* Browser entry point for the single-threaded Z3 build.
|
||||
*
|
||||
* This variant does NOT require SharedArrayBuffer or the COOP/COEP HTTP headers
|
||||
* that the default (threaded) build needs. All Z3 operations run synchronously
|
||||
* in the calling thread, so this build should be used inside a Web Worker to
|
||||
* avoid blocking the main browser thread.
|
||||
*
|
||||
* Before calling `init()`, make sure the `z3-built-singlethread.js` script has
|
||||
* been loaded (e.g. via `importScripts('z3-built-singlethread.js')` inside the
|
||||
* worker), so that `initZ3SingleThread` is available on the global object.
|
||||
* @category Global */
|
||||
export async function init(moduleOverrides: Z3ModuleOverrides = {}): Promise<Z3LowLevel & Z3HighLevel> {
|
||||
const initZ3 = (global as any)[INIT_GLOBAL];
|
||||
if (initZ3 === undefined) {
|
||||
throw new Error(
|
||||
`${INIT_GLOBAL} was not found. ` +
|
||||
'Load z3-built-singlethread.js before calling init() ' +
|
||||
'(e.g. importScripts("z3-built-singlethread.js") in a Web Worker).',
|
||||
);
|
||||
}
|
||||
|
||||
const lowLevel = await initWrapper(initZ3, moduleOverrides);
|
||||
const highLevel = createApi(lowLevel.Z3, lowLevel.em);
|
||||
return { ...lowLevel, ...highLevel };
|
||||
}
|
||||
37
src/api/js/src/node-singlethread.test.ts
Normal file
37
src/api/js/src/node-singlethread.test.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
const mockInitModule = jest.fn();
|
||||
const mockInitWrapper = jest.fn();
|
||||
const mockCreateApi = jest.fn();
|
||||
|
||||
jest.mock('./z3-built-singlethread', () => mockInitModule, { virtual: true });
|
||||
jest.mock('./low-level', () => ({
|
||||
init: mockInitWrapper,
|
||||
Z3Core: undefined,
|
||||
Z3LowLevel: undefined,
|
||||
}));
|
||||
jest.mock('./high-level', () => ({
|
||||
createApi: mockCreateApi,
|
||||
}));
|
||||
|
||||
import { init } from './node-singlethread';
|
||||
|
||||
describe('node-singlethread init', () => {
|
||||
beforeEach(() => {
|
||||
mockInitModule.mockReset();
|
||||
mockInitWrapper.mockReset();
|
||||
mockCreateApi.mockReset();
|
||||
});
|
||||
|
||||
it('passes module overrides to the low-level initializer', async () => {
|
||||
const locateFile = jest.fn((file: string) => `npm:z3-solver/build/${file}`);
|
||||
const lowLevel = { Z3: { low: true }, em: { module: true } };
|
||||
const highLevel = { Context: jest.fn() };
|
||||
mockInitWrapper.mockResolvedValue(lowLevel);
|
||||
mockCreateApi.mockReturnValue(highLevel);
|
||||
|
||||
const api = await init({ locateFile });
|
||||
|
||||
expect(mockInitWrapper).toHaveBeenCalledWith(mockInitModule, { locateFile });
|
||||
expect(mockCreateApi).toHaveBeenCalledWith(lowLevel.Z3, lowLevel.em);
|
||||
expect(api).toEqual({ ...lowLevel, ...highLevel });
|
||||
});
|
||||
});
|
||||
34
src/api/js/src/node-singlethread.ts
Normal file
34
src/api/js/src/node-singlethread.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// @ts-ignore no-implicit-any
|
||||
import initModule = require('./z3-built-singlethread');
|
||||
|
||||
import { createApi, Z3HighLevel } from './high-level';
|
||||
import { init as initWrapper, Z3LowLevel, Z3ModuleOverrides } from './low-level';
|
||||
export * from './high-level/types';
|
||||
export { Z3Core, Z3LowLevel } from './low-level';
|
||||
export * from './low-level/types.__GENERATED__';
|
||||
|
||||
/**
|
||||
* The main entry point to the single-threaded Z3 API.
|
||||
*
|
||||
* This variant does NOT require SharedArrayBuffer or the COOP/COEP HTTP headers
|
||||
* that the default (threaded) build needs. All Z3 operations run synchronously
|
||||
* in the calling thread, so this build should be used inside a Web Worker to
|
||||
* avoid blocking the main browser thread.
|
||||
*
|
||||
* ```typescript
|
||||
* import { init } from 'z3-solver/singlethread';
|
||||
*
|
||||
* const { Context } = await init();
|
||||
* const { Solver, Int } = new Context('main');
|
||||
*
|
||||
* const x = Int.const('x');
|
||||
* const solver = new Solver();
|
||||
* solver.add(x.ge(0));
|
||||
* console.log(await solver.check()); // 'sat'
|
||||
* ```
|
||||
* @category Global */
|
||||
export async function init(moduleOverrides: Z3ModuleOverrides = {}): Promise<Z3HighLevel & Z3LowLevel> {
|
||||
const lowLevel = await initWrapper(initModule, moduleOverrides);
|
||||
const highLevel = createApi(lowLevel.Z3, lowLevel.em);
|
||||
return { ...lowLevel, ...highLevel };
|
||||
}
|
||||
|
|
@ -87,6 +87,13 @@ scoped_timer::scoped_timer(unsigned ms, event_handler * eh) {
|
|||
r->set_auto_cancel();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// In single-threaded Emscripten builds (without pthreads), std::thread is not
|
||||
// usable. Timeout support is provided via POLLING_TIMER for the primary use
|
||||
// case (cancel_eh<reslimit>) above. Any other event-handler type simply gets
|
||||
// no timeout rather than a crash.
|
||||
#if defined(SINGLE_THREAD) && !defined(__EMSCRIPTEN_PTHREADS__)
|
||||
return;
|
||||
#endif
|
||||
workers.lock();
|
||||
if (available_workers.empty()) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue