Skip to content

Commit 312e037

Browse files
authored
wasm build: disable error handler (#5996)
* wasm: set error handler to no-op * wasm: better wrapper for use in html
1 parent 39f57fb commit 312e037

8 files changed

Lines changed: 83 additions & 18 deletions

File tree

src/api/js/PUBLISHED_README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Z3 itself is distributed as a wasm artifact as part of this package. You can fin
99

1010
This 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.
1111

12-
The Emscripten worker model will spawn multiple instances of `z3-built.js` for long-running operations. If you are using a bundler like Webpack, Emscripten can no longer reference `z3-built.js` - that file will be merged with the rest of your codebase. To fix this, you need to host the unmodified file separately, and set `Module['mainScriptUrlOrBlob']` to the URL of this file. If you don't do this, your bundle `main.js` will be used in all workers, which will undoubtedly fail and cause weird issues.
12+
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. That script defines a global variable named `initZ3`. Your main script, which can be bundled, should do `let { init } = require('z3-solver/build/wrapper.js'); let { Z3 } = await init(initZ3);`.
1313

1414
Other than the differences below, the bindings can be used exactly as you'd use the C library. Because this is a wrapper around a C library, most of the values you'll use are just numbers representing pointers. For this reason you are strongly encouraged to make use of the TypeScript types to differentiate among the different kinds of value.
1515

src/api/js/example-raw.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { init } from './build/wrapper';
1+
import { init, Z3_error_code } from './build/node-wrapper';
22

33
// demonstrates use of the raw API
44

@@ -47,6 +47,16 @@ import { init } from './build/wrapper';
4747
console.log(Z3.query_constructor(ctx, nil_con, 0));
4848
console.log(Z3.query_constructor(ctx, cons_con, 2));
4949

50+
if (Z3.get_error_code(ctx) !== Z3_error_code.Z3_OK) {
51+
throw new Error('something failed: ' + Z3.get_error_msg(ctx, Z3.get_error_code(ctx)));
52+
}
53+
await Z3.eval_smtlib2_string(ctx, '(simplify)');
54+
if (Z3.get_error_code(ctx) === Z3_error_code.Z3_OK) {
55+
throw new Error('expected call to eval_smtlib2_string with invalid argument to fail');
56+
}
57+
console.log('confirming error messages work:', Z3.get_error_msg(ctx, Z3.get_error_code(ctx)));
58+
59+
5060
Z3.dec_ref(ctx, strAst);
5161
Z3.del_context(ctx);
5262

src/api/js/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
"engines": {
77
"node": ">=16"
88
},
9-
"main": "build/wrapper.js",
10-
"types": "build/wrapper.d.ts",
9+
"main": "build/node-wrapper.js",
10+
"types": "build/node-wrapper.d.ts",
1111
"files": [
1212
"build/*.{js,d.ts,wasm}"
1313
],
1414
"scripts": {
15-
"build-ts": "mkdir -p build && node scripts/make-ts-wrapper.js > build/wrapper.ts && tsc",
15+
"build-ts": "mkdir -p build && rm -rf build/*.ts && cp src/node-wrapper.ts build && node scripts/make-ts-wrapper.js > build/wrapper.ts && tsc",
1616
"build-wasm": "mkdir -p build && node scripts/make-cc-wrapper.js > build/async-fns.cc && ./build-wasm.sh",
1717
"format": "prettier --write --single-quote --arrow-parens avoid --print-width 120 --trailing-comma all '{,src/,scripts/}*.{js,ts}'",
1818
"test": "node test-ts-api.js"

src/api/js/scripts/list-exports.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
let { functions } = require('./parse-api.js');
66
let asyncFns = require('./async-fns.js');
77

8-
let extras = asyncFns.map(f => '_async_' + f);
8+
let extras = ['_set_throwy_error_handler', '_set_noop_error_handler', ...asyncFns.map(f => '_async_' + f)];
99
let fns = functions.filter(f => !asyncFns.includes(f.name));
1010

1111
console.log(JSON.stringify([...extras, ...functions.map(f => '_' + f.name)]));

src/api/js/scripts/make-cc-wrapper.js

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,14 @@ void wrapper(Args&&... args) {
5151
MAIN_THREAD_ASYNC_EM_ASM({
5252
resolve_async($0);
5353
}, result);
54+
} catch (std::exception& e) {
55+
MAIN_THREAD_ASYNC_EM_ASM({
56+
reject_async(new Error(UTF8ToString($0)));
57+
}, e.what());
5458
} catch (...) {
5559
MAIN_THREAD_ASYNC_EM_ASM({
5660
reject_async('failed with unknown exception');
5761
});
58-
throw;
5962
}
6063
});
6164
t.detach();
@@ -69,14 +72,44 @@ void wrapper_str(Args&&... args) {
6972
MAIN_THREAD_ASYNC_EM_ASM({
7073
resolve_async(UTF8ToString($0));
7174
}, result);
75+
} catch (std::exception& e) {
76+
MAIN_THREAD_ASYNC_EM_ASM({
77+
reject_async(new Error(UTF8ToString($0)));
78+
}, e.what());
7279
} catch (...) {
7380
MAIN_THREAD_ASYNC_EM_ASM({
74-
reject_async('failed with unknown exception');
81+
reject_async(new Error('failed with unknown exception'));
7582
});
76-
throw;
7783
}
7884
});
7985
t.detach();
8086
}
8187
88+
89+
90+
class Z3Exception : public std::exception {
91+
public:
92+
const std::string m_msg;
93+
Z3Exception(const std::string& msg) : m_msg(msg) {}
94+
virtual const char* what() const throw () {
95+
return m_msg.c_str();
96+
}
97+
};
98+
99+
void throwy_error_handler(Z3_context ctx, Z3_error_code c) {
100+
throw Z3Exception(Z3_get_error_msg(ctx, c));
101+
}
102+
103+
void noop_error_handler(Z3_context ctx, Z3_error_code c) {
104+
// pass
105+
}
106+
107+
extern "C" void set_throwy_error_handler(Z3_context ctx) {
108+
Z3_set_error_handler(ctx, throwy_error_handler);
109+
}
110+
111+
extern "C" void set_noop_error_handler(Z3_context ctx) {
112+
Z3_set_error_handler(ctx, noop_error_handler);
113+
}
114+
82115
${wrappers.join('\n\n')}`);

src/api/js/scripts/make-ts-wrapper.js

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ let makePointerType = t =>
1919
// or up to 3 out int64s
2020
const BYTES_TO_ALLOCATE_FOR_OUT_PARAMS = 24;
2121

22+
const CUSTOM_IMPLEMENTATIONS = ['Z3_mk_context', 'Z3_mk_context_rc'];
23+
2224
function toEmType(type) {
2325
if (type in primitiveTypes) {
2426
type = primitiveTypes[type];
@@ -70,6 +72,10 @@ function toEm(p) {
7072

7173
let isInParam = p => ['in', 'in_array'].includes(p.kind);
7274
function wrapFunction(fn) {
75+
if (CUSTOM_IMPLEMENTATIONS.includes(fn.name)) {
76+
return null;
77+
}
78+
7379
let inParams = fn.params.filter(isInParam);
7480
let outParams = fn.params.map((p, idx) => ({ ...p, idx })).filter(p => !isInParam(p));
7581

@@ -318,9 +324,6 @@ function wrapFunction(fn) {
318324
`.trim();
319325
}
320326

321-
if (isAsync) {
322-
}
323-
324327
// prettier-ignore
325328
let invocation = `Mod.ccall('${isAsync ? 'async_' : ''}${fn.name}', '${cReturnType}', ${JSON.stringify(ctypes)}, [${args.map(toEm).join(', ')}])`;
326329

@@ -358,8 +361,6 @@ let out = `
358361
// THIS FILE IS AUTOMATICALLY GENERATED BY ${path.basename(__filename)}
359362
// DO NOT EDIT IT BY HAND
360363
361-
// @ts-ignore no-implicit-any
362-
import initModule = require('./z3-built.js');
363364
interface Pointer<T extends string> extends Number {
364365
readonly __typeName: T;
365366
}
@@ -381,7 +382,7 @@ ${Object.entries(enums)
381382
.map(e => wrapEnum(e[0], e[1]))
382383
.join('\n\n')}
383384
384-
export async function init() {
385+
export async function init(initModule: any) {
385386
let Mod = await initModule();
386387
387388
// this works for both signed and unsigned, because JS will wrap for you when constructing the Uint32Array
@@ -410,10 +411,21 @@ export async function init() {
410411
return {
411412
em: Mod,
412413
Z3: {
413-
${functions
414+
mk_context: function(c: Z3_config): Z3_context {
415+
let ctx = Mod._Z3_mk_context(c);
416+
Mod._set_noop_error_handler(ctx);
417+
return ctx;
418+
},
419+
mk_context_rc: function(c: Z3_config): Z3_context {
420+
let ctx = Mod._Z3_mk_context_rc(c);
421+
Mod._set_noop_error_handler(ctx);
422+
return ctx;
423+
},
424+
${functions
414425
.map(wrapFunction)
415426
.filter(f => f != null)
416427
.join(',\n')}
428+
417429
}
418430
};
419431
}

src/api/js/src/node-wrapper.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// @ts-ignore no-implicit-any
2+
import initModule = require('./z3-built.js');
3+
4+
// @ts-ignore no-implicit-any
5+
import { init as initWrapper } from './wrapper';
6+
7+
export * from './wrapper';
8+
export function init() {
9+
return initWrapper(initModule);
10+
}

src/api/js/test-ts-api.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ import type {
1616
Z3_func_decl,
1717
Z3_func_interp,
1818
Z3_func_entry,
19-
} from './build/wrapper';
20-
import { init, Z3_lbool, Z3_ast_kind, Z3_sort_kind, Z3_symbol_kind } from './build/wrapper';
19+
} from './build/node-wrapper';
20+
import { init, Z3_lbool, Z3_ast_kind, Z3_sort_kind, Z3_symbol_kind } from './build/node-wrapper';
2121

2222
// @ts-ignore we're not going to bother with types for this
2323
import { sprintf } from 'sprintf-js';

0 commit comments

Comments
 (0)