@@ -479,7 +479,8 @@ def compute(
479479
480480 - ``strict_miniexpr`` (bool): controls whether miniexpr compilation/execution
481481 failures are raised instead of silently falling back to regular chunked eval
482- for non-DSL expressions.
482+ for non-DSL expressions. Setting it ``True`` also opts a DSL kernel out of the
483+ WebAssembly prefer-js default, keeping it on miniexpr.
483484
484485 - ``jit`` (bool | None): enable (``True``) or disable (``False``) JIT compilation
485486 of the expression via miniexpr. When ``None`` (default), JIT is only used
@@ -488,9 +489,21 @@ def compute(
488489 kernels.
489490
490491 - ``jit_backend`` (str | None): select the JIT compiler backend. Valid
491- values are ``"tcc"`` (bundled Tiny C Compiler) and ``"cc"`` (system C
492- compiler, e.g. gcc or clang). ``None`` (default) defers to the miniexpr
493- default (``"tcc"``).
492+ values are ``"tcc"`` (bundled Tiny C Compiler), ``"cc"`` (system C
493+ compiler, e.g. gcc or clang), and ``"js"`` (transpile the DSL kernel to
494+ JavaScript; browser/Pyodide only — see below). ``None`` (default) defers
495+ to the miniexpr default (``"tcc"``), except under WebAssembly where — unless
496+ ``jit=False`` — it *prefers* ``"js"`` for transpilable float DSL kernels and
497+ falls back to miniexpr otherwise. Since ``"js"`` is itself JIT-compiled by
498+ the JS engine, ``jit=True`` prefers it too; force miniexpr with
499+ ``jit_backend="tcc"``/``"cc"``.
500+
501+ - ``"js"`` backend (WebAssembly/Pyodide only): transpiles a
502+ :func:`blosc2.dsl_kernel` to JavaScript so it runs at the browser engine's
503+ optimized native speed. It tends to beat the WASM miniexpr JIT (~2x) for
504+ float kernels dominated by arithmetic and control flow, and is roughly a
505+ wash for transcendental-heavy or trivial kernels. Outside WebAssembly,
506+ ``jit_backend="js"`` raises. Forcing ``"tcc"``/``"cc"`` always uses miniexpr.
494507
495508 - ``BLOSC_ME_JIT`` environment variable: when set to ``"1"``, ``"true"``,
496509 ``"on"``, ``"tcc"``, or ``"cc"``, it forces ``jit=True`` and overrides
@@ -1407,8 +1420,11 @@ def fill_chunk_operands(
14071420def _apply_jit_backend_pragma (expression : str , inputs : dict , jit_backend : str | None ) -> str :
14081421 if jit_backend is None :
14091422 return expression
1423+ if jit_backend == "js" :
1424+ # "js" is handled earlier (DSL kernels -> JS bridge); it never carries a C pragma.
1425+ return expression
14101426 if jit_backend not in ("tcc" , "cc" ):
1411- raise ValueError ("jit_backend must be one of: None, 'tcc', 'cc'" )
1427+ raise ValueError ("jit_backend must be one of: None, 'tcc', 'cc', 'js' " )
14121428
14131429 pragma = f"# me:compiler={ jit_backend } \n "
14141430 stripped = expression .lstrip ()
@@ -1444,14 +1460,52 @@ def _as_js_udf(expression):
14441460 return js_kernel (expression )
14451461
14461462
1447- def _maybe_js_backend (expression , jit , jit_backend , reduce_args ):
1448- """For jit_backend="js", swap the DSL kernel for its JS bridge (a plain per-block
1449- callable) and disable miniexpr/backend-pragma. Otherwise a no-op passthrough."""
1450- if jit_backend != "js" :
1463+ def _js_dtypes_ok (operands , kwargs ) -> bool :
1464+ """True only if the JS bridge (which computes in float64) is safe for these operands:
1465+ floating-point NDArray inputs and a floating/inferred output dtype. Integer/complex go
1466+ to miniexpr instead (float64 can't represent int64 exactly)."""
1467+ dt = kwargs .get ("dtype" )
1468+ if dt is not None and not np .issubdtype (np .dtype (dt ), np .floating ):
1469+ return False
1470+ return all (
1471+ np .issubdtype (op .dtype , np .floating ) for op in operands .values () if isinstance (op , blosc2 .NDArray )
1472+ )
1473+
1474+
1475+ def _maybe_js_backend (expression , jit , jit_backend , reduce_args , operands , kwargs ):
1476+ """Resolve the JS backend for a DSL kernel.
1477+
1478+ - ``jit_backend="js"`` (explicit): transpile to the JS bridge, or raise if it can't.
1479+ - ``jit_backend=None`` under WebAssembly, unless ``jit=False``: *prefer* JS (it is a
1480+ JIT too, and the fastest one here) for transpilable float DSL kernels, silently
1481+ falling back to miniexpr for anything it can't do (non-float dtypes, reductions, or
1482+ unsupported DSL constructs). ``jit=True`` and ``jit=None`` both prefer JS; only
1483+ ``jit=False`` (interpreter), ``strict_miniexpr=True``, or an explicit ``jit_backend``
1484+ opts out.
1485+
1486+ Returns ``(expression, jit, jit_backend)`` — expression becomes a plain per-block
1487+ callable when JS is chosen, else everything passes through unchanged.
1488+ """
1489+ if jit_backend == "js" :
1490+ if reduce_args :
1491+ raise ValueError ('jit_backend="js" does not support reductions' )
1492+ return _as_js_udf (expression ), None , None
1493+ prefer_js = (
1494+ jit is not False # jit=True/None prefer the best JIT (js); only jit=False forces interpreter
1495+ and jit_backend is None
1496+ and not kwargs .get ("strict_miniexpr" ) # explicit strict_miniexpr=True keeps miniexpr
1497+ and blosc2 .IS_WASM
1498+ and _is_dsl_kernel_expression (expression )
1499+ and not reduce_args
1500+ and _js_dtypes_ok (operands , kwargs )
1501+ )
1502+ if not prefer_js :
14511503 return expression , jit , jit_backend
1452- if reduce_args :
1453- raise ValueError ('jit_backend="js" does not support reductions' )
1454- return _as_js_udf (expression ), None , None
1504+ try :
1505+ bridge = _as_js_udf (expression ) # transpiles; raises on any unsupported construct
1506+ except Exception :
1507+ return expression , jit , jit_backend # fall back to miniexpr, no regression
1508+ return bridge , None , None
14551509
14561510
14571511def _format_dsl_parse_error_hint (expr_text : str , backend_msg : str ):
@@ -2983,8 +3037,11 @@ def chunked_eval(
29833037 operands = {** operands , ** where }
29843038
29853039 reduce_args = kwargs .pop ("_reduce_args" , {})
2986- # jit_backend="js": swap the DSL kernel for its JS bridge (a plain per-block callable).
2987- expression , jit , jit_backend = _maybe_js_backend (expression , jit , jit_backend , reduce_args )
3040+ # Resolve the JS backend: explicit jit_backend="js", or prefer-js-with-fallback under
3041+ # WebAssembly when the user left jit_backend unset (see _maybe_js_backend).
3042+ expression , jit , jit_backend = _maybe_js_backend (
3043+ expression , jit , jit_backend , reduce_args , operands , kwargs
3044+ )
29883045
29893046 fast_path = _validate_chunked_eval_inputs (operands , out , shape , reduce_args )
29903047
@@ -4839,9 +4896,13 @@ def myudf(inputs_tuple, output, offset):
48394896 jit: bool or None, optional
48404897 JIT policy for miniexpr-backed execution:
48414898 ``None`` uses default behavior (currently, JIT is tried out), ``True`` prefers JIT, ``False`` disables JIT.
4842- jit_backend: {"tcc", "cc"} or None, optional
4843- JIT backend selection for miniexpr-backed execution:
4844- ``None`` uses backend defaults (currently "tcc"), ``"tcc"`` forces libtcc, ``"cc"`` forces C compiler backend.
4899+ jit_backend: {"tcc", "cc", "js"} or None, optional
4900+ JIT backend selection. ``None`` uses backend defaults (miniexpr "tcc"), except under
4901+ WebAssembly where — unless ``jit=False`` — it *prefers* ``"js"`` for transpilable
4902+ float DSL kernels and falls back to miniexpr otherwise (``jit=True`` prefers ``"js"``
4903+ too, since it is JIT-compiled by the JS engine). ``"tcc"`` forces libtcc, ``"cc"``
4904+ forces the C compiler backend, and ``"js"`` transpiles a :func:`blosc2.dsl_kernel`
4905+ to JavaScript (browser/Pyodide only; raises elsewhere).
48454906 kwargs: Any, optional
48464907 Keyword arguments that are supported by the :func:`empty` constructor.
48474908 These arguments will be used by the :meth:`LazyArray.__getitem__` and
0 commit comments