libcascade

Emscripten settings and flags

The recommended settings baseline for a custom build, flag-by-flag rationale, and the trade-offs behind SIMD, exceptions, and EVAL_CTORS.

Once the bindings list is right, the second knob is how the binary is linked. Three fields cover it:

FieldHolds
settingsEmscripten -s settings, typed against the image's own emsdk.
compilerFlagsThe closed set of non--s flags worth typing (-O3, -msimd128, -fwasm-exceptions, -flto, --no-entry).
rawFlagsEverything else, passed through verbatim after the typed flags.
libcascade.config.ts
settings: {
  MODULARIZE: true,                    // init() returns Promise<Module>
  EXPORT_ES6: true,                    // ESM output
  ALLOW_MEMORY_GROWTH: true,           // the 16 MB initial heap is not enough
  INITIAL_MEMORY: '100MB',
  MAXIMUM_MEMORY: '4GB',               // the wasm32 ceiling
  STACK_SIZE: 8_388_608,
  WASM_BIGINT: true,                   // no i64 legalisation shim
  EXPORTED_RUNTIME_METHODS: [
    'FS',
    'getExceptionMessage',
    'incrementExceptionRefcount',
    'decrementExceptionRefcount',
  ],
  ENVIRONMENT: ['web', 'worker', 'node'],
  ERROR_ON_UNDEFINED_SYMBOLS: false,   // OSD_MemInfo references mallinfo
  EVAL_CTORS: 2,                       // static-init evaluation at build time
},
compilerFlags: { optimize: 'O3', simd: true, exceptions: 'wasm', noEntry: true },

Setting-by-setting rationale

SettingWhy
optimize: 'O3'Production LLVM optimisation. 'Os' is a size-tuned alternative — benchmark it against your workload. 'O0' is for debugging only.
exceptions: 'wasm'Native Wasm exception instructions instead of JavaScript invoke_* trampolines.
simd: trueBaseline WebAssembly SIMD, supported across the package's browser matrix.
WASM_BIGINT: trueRemoves the i64↔i32-pair legalisation shim.
EVAL_CTORS: 2Runs static initialisers at build time. Smaller payload, faster startup. Requires O2 or better.
MODULARIZE + EXPORT_ES6Required for the ESM glue the generated entries import.
ENVIRONMENTStrips dead environment detection. Without it the runtime probes for process / window / importScripts.
ALLOW_MEMORY_GROWTHRequired for any non-trivial geometry.
MAXIMUM_MEMORY: '4GB'The wasm32 hard ceiling (2³² bytes).
ERROR_ON_UNDEFINED_SYMBOLS: falseOCCT's OSD_MemInfo references mallinfo, which Emscripten does not provide.

Every setting name carries its upstream emsdk documentation as JSDoc, so hovering it in your editor shows what emcc says about it. There are 312 of them; the ones above are the ones a custom build normally touches.

The exception helpers are not optional

emsdk 6.0.5 removed -sEXPORT_EXCEPTION_HANDLING_HELPERS. With exceptions: 'wasm' the link pipeline hard-fails unless these three are in EXPORTED_RUNTIME_METHODS:

EXPORTED_RUNTIME_METHODS: [
  'getExceptionMessage',
  'incrementExceptionRefcount',
  'decrementExceptionRefcount',
],

Add whatever else you need ('FS', 'wasmMemory') to the same array.

When to reach for rawFlags

settings has no index signature — an unknown -s name is a compile error, not a silently-ignored flag. Anything the typed surface cannot express goes here, verbatim, after every typed flag:

rawFlags: ['-Wl,--allow-undefined', '--emit-symbol-map'],
variants: [
  { name: 'debug', rawFlags: ['-gsource-map'] },
],

Variant rawFlags are appended after the base ones.

Reach for compilerFlags first. -pthread, -msimd128, -O3, -fwasm-exceptions, -flto, and --no-entry are all typed there, on the base config or per variant — a flag that lands in rawFlags today and turns out to be common is a candidate for compilerFlags tomorrow, not a permanent resident.

Trade-offs

Wasm exceptions vs JavaScript exceptions

-fwasm-exceptions requires that all object files and the linker use the flag consistently. Mixed builds surface __cpp_exception as an unresolved import at link time. The published images compile everything with wasm exceptions; only override if you target a wasm engine without try_table support.

SIMD: baseline vs relaxed

Baseline simd: true (-msimd128) is universal across the supported matrix. Relaxed SIMD is not: Safari 26.x refuses to parse the relaxed opcodes and the module fails to instantiate. If you ship it, ship it as an extra variant alongside a baseline one:

variants: [
  { name: 'single' },
  { name: 'relaxed', rawFlags: ['-mrelaxed-simd'] },
],

and select between them yourself — the generated capability probes cover threads, not SIMD flavours.

EVAL_CTORS levels

LevelBehaviour
0Off — every static initialiser runs at startup
1Evaluates constructors with safe side effects
2Recommended — full constructor evaluation, requires O2+

Drop it in a threads variant. Constructor evaluation order is non-deterministic under pthread workers, which is exactly what settings: { EVAL_CTORS: null } on the variant is for — null removes an inherited base setting.

The wasm bitwidth (wasm32 vs wasm64), the C++ standard-library version, the OCCT commit pin, and the libclang version are all baked into the published image during the bindgen pipeline. Those live on the other side of the two-channel split; changing them means forking the image build.

On this page