package wax
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
On This Page
A compiler toolchain for Wax, a Rust-like syntax for WebAssembly
Install
dune-project
Dependency
Authors
Maintainers
Sources
wax-v0.3.0.tbz
sha256=cc19989d0db930d9300d7356c8f127309424a82767dd3c91ffd19a0bfd20e3a6
sha512=14ff9219dc6c4e8855c3cb895bd1af6fd81d1c521afefe1e7f69208a2e37abc7ec0946f188198a26780aa405fd43a526b938158334a019f7a5d991d8efc437c3
doc/CHANGES.html
Changelog
This is the changelog for the wax / wax-lib opam packages. dune-release reads the top entry's version and notes when cutting an opam release (see RELEASING.md). The VS Code extension keeps its own changelog, editors/vscode/CHANGELOG.md; the tree-sitter grammar keeps none, and both are versioned independently of the toolchain.
0.3.0
Language
- Value-type aliases:
type word = i64;in Wax,(@type $word i64)in WAT, names a value type that can then be used wherever a value type is expected ((@type $word)in WAT). Defined differently in the branches of an#[if]or(@if …), an alias stands for a different type in each configuration, so code that only passes such values around no longer has to be written once per configuration. A value read from a declaration written with an alias has the alias's type, so a local whose type is inferred from it is declared with the alias, and editors show the alias. Theunused-fieldwarning reports an alias nothing reachable uses. Aliases convert between Wax and WAT, conditional ones included. Compiling to the binary format, or--desugar, replaces them by the types they stand for. - A type may no longer be named after a packed storage type or a conversion target (
i8,i16,i32_s, …), as it already could not be named after a value type; the conversion from WAT renames such a type. - A conditional-compilation variable must now be named by a valid Wax identifier, in WAT (
(@if $name …)) and with-D. A name that is not one, such as$portable-int, used to be accepted but could not be converted to Wax. A keyword is a valid name, and Wax now accepts it in a condition (#[if(loop)]).
Command line
- New
--try-tableflag: rewrite the legacy exception-handling instructions (trywithcatch/catch_all) into the standardtry_table, for engines and tools that only accept the current exception-handling proposal. It works on any input, including WAT and Wasm files using legacy exceptions; on a Wax-to-Wax conversion, eachtry_legacybecomes a structuredtry.
Diagnostics
- A syntax error that expects one of two things no longer puts a comma before "or": it now reads "expecting ';' or '}'".
Editors
- Completion offers a name defined in several branches of a conditional annotation once, with each definition's detail: a function defined with an
i64result in one branch and ani32one in the other showsfn() -> i64 | fn() -> i32, where only the first was shown.
Fixes
--faithfulno longer rejects a module that converts a non-null value withextern.convert_anyorany.convert_externand uses the result where a non-null reference is expected (a(ref extern)function result, for example): the conversion's result was typed nullable.- A legacy
trytaking block parameters is no longer rejected when it has acatch_allhandler: the handler was wrongly expected to receive the parameters, which the body consumes. - A legacy
trytaking block parameters in expression position is now reported as an error, as a block, loop orifthere is, instead of crashing the type checker. - A structured
trywithout a result type now takes it from its context, astry_legacydoes: a value it yields as a function's result, for example, is no longer typedi32when the function returnsf32, which made a literal in dead code after athrowa type error. - Converting a Wax module without resolving its conditional annotations no longer reads the wrong field when a field access, outside a conditional, is on a value whose struct type differs between the branches and has the field at a different position (a subtype that renames an inherited field). The access is now rejected, as it has no single WebAssembly form;
-Dor an access in each branch converts. - Converting WAT or Wasm to Wax no longer changes which field a
struct.getorstruct.setreads or writes when the value's type is a subtype that names the field differently (or names another field the same): the receiver is ascribed the instruction's type,(x : &?s1).f, so the field is looked up there. - Converting WAT or Wasm to Wax no longer fails with "Decompiler width invariant violated" when code outside a conditional annotation reads a value whose type differs between its branches: a call to a function, or through a function type, declared with an
i64result in one branch and ani32one in the other, a global or a struct field declared likewise. - Converting between WAT and Wax no longer fails with "This reference resolves to nothing" when a struct type is declared in both branches of an
(@if …)(or#[if]) with different fields. Each branch now uses its own field names. - Converting WAT or Wasm to Wax no longer fails with "The label '…' is not bound" when a block holds a type-test chain (
br_on_cast_fail,br_on_castorbr_on_null) whose code branches to the block's label again, for example twobr_on_cast_failto the same block. Such blocks now stay as blocks instead of being turned into amatch. The same conversion also no longer fails with "The variable '…' is not bound", or silently produces code that computes a different result, when a local set by such a type test is used again outside it (by a second test, or on the next iteration of a loop). - Converting WAT or Wasm to Wax no longer fails with "The label '…' is not bound" on a loop whose label is the target of a
try_tablecatch clause or of a branch that skips the loop's final update, nor with "This expression occurs before a hole" on atry_tablewhose caught value feeds a compound assignment such asx += …. - The npm package, the VS Code extension and the playground no longer crash with "Maximum call stack size exceeded" on a Wax module with tens of thousands of top-level items, such as one decompiled from a large Wasm binary.
Performance
- Type-checking a large module is much faster when many functions use the same type or helper, as in code generated by compilers such as wasm_of_ocaml. Converting a 2.8 MB Wasm binary to Wax, or back, went from about 2.5 minutes to about 12 seconds.
Library
Wax.output_binarytakes a?label_namesargument; passingfalseleaves label names out of thenamesection. Binaryen'swasm-optdoes not know this subsection and warns about it.Wax.wat_to_binaryandWax.wax_to_binarytake?try_table, the embedding counterpart of--try-table.
0.2.0
Language
- The final
;of a block is now optional. Writing it still does not discard the statement's value, and the formatter keeps emitting the canonical trailing;. - New parenthesized type ascription
(e : t). It asserts a type instead of converting to it: the operand's type must already be a subtype oft, the expression compiles to nothing, and an ascription that does not hold is a compile-time error. Use it to pin a literal's width, or to widen a reference without a runtime cast. - The compilation-hints proposal is supported and preserved through every conversion, with no feature flag:
#[freq = n],#[never_opt],#[always_opt]and#[targets(f: 0.73, …)]prefixing an expression, and#[priority = n],#[optimization = n]and#[run_once]on a function. - A hole
_is now rejected, with an explanation, as amatchscrutinee, adispatchindex or awhilecondition. Those positions desugar to a nested block, where a hole draws from the block's own stack and could never pick up the intended value. custom-descriptorsfollows two upstream rule changes: a type and its descriptor must agree on finality, and the "L" subtyping rule is gone. Exact allocation types are now gated on the feature as well.compact-import-sectiontreats the text form as authoritative for import layout. A Waximport "m" { … }block (or a WAT(import "m" (item …) …)group) lowers to a compact entry, a one-item block flattens to a plain import, and imports written separately are never merged. A binary input, which has no authorial layout, still gets its runs of same-module imports coalesced. The shared-type text form cannot bind identifiers, so a group whose items carry names takes the one-type-per-item form and the binary encoder restores the shared-type encoding.- A module name annotation is rejected inside a
#[if]/(@if)conditional, since the name applies in every configuration. - A branch-hint annotation is accepted on a folded operand in WAT.
Command line
- New
--faithful: decompile without the stream-reshaping recoveries, so a WAT or Wasm module converted to Wax recompiles with the same reachable instruction structure. Wax output only. The new Round-Tripping page states the contract and the few inert divergences that remain. --desugarnow synthesizes the declarative element segment that Wax's lenient reader lets a module omit, so the output passes strict spec validation, including after resolving conditionals with-D. Themetadata.code.*hint annotations are real WebAssembly text and are kept.--error-format humanappends the warning's-Wname to the header (Warning [unused-local]:). Withjson, a diagnostic carrying a machine-applicable fix gained aneditobject, and syntax errors now emithint,relatedandeditlike every other diagnostic.--source-mapis rejected as a usage error when the input is a Wasm binary, which carries no source to map.- New developer
--debugcategorieswidth-checkandwidth-record, self checks on the decompiler's numeric-width recording.
Warnings
unused-fieldandunused-importnow ask reachability from the module's roots rather than the mere presence of a reference, so a dead cycle of mutually recursive functions or types is reported. They span every named index space: functions, globals, memories, tables, tags, types, and passive data and element segments.- New
unnecessary-mut(shown by default): a module-defined, non-exported global declared mutable that no assignment ever targets, so it could be aconst. - New
confusable-unicode(shown by default): a "Trojan Source" bidirectional control character in a string the module carries (an export or import name, a string literal, a data segment), which can make the source read differently than it runs. - New
suggestiongroup, hidden by default and reported at a distinct Suggestion severity, each entry carrying a machine-applicable rewrite:compound-assignment(x = x + etox += e),field-punning({x: x}to{x}) andredundant-annotation(a type the inference already pins). dead-codealso reports a#[if]/(@if)branch that no configuration can select.- Quick-fix edits are attached to
unused-local,unused-label,precedenceand redundant casts, so editors can offer them. cast-always-failsreports only the innermost cast of a chain, where the fix belongs.- Fewer false positives: float arithmetic identities,
x - xon floats,any/externconversions, a leading sign on a constant operand, and SIMD vector methods now classified as effect-free. Constant expressions are linted too, not just function bodies.
Diagnostics
- Parser error messages are regenerated by
stele, a message generator vendored with the repository, and are golden-tested. They name the construct under repair, underline the right token, and fall back to hand-written wording for the states heuristics cannot serve. - Syntax errors carry a structured payload, and a parse that recovery repairs by inserting a token (a missing
;or operand) offers that insertion as a fix. - Better locations throughout:
br_tabletargets, block parameters and results, a missing block argument, the arms of anifand of a structuredtry, the intrinsic name in an arity error, a non-function callee at the call site, a missing;at the end of the previous lexeme. - A duplicate binding, export or dispatch arm now reports where the previous one was.
- Error cascades are cut: a failed cast, a failed arithmetic operand, a local whose declared type is unresolved, and a chained or nested type error each report once instead of at every level.
Editors
- Machine-applicable suggestions are served as
textDocument/codeActionquick fixes over LSP, mirroring the VS Code code-action provider. - Contextual type completion: value and heap types in Wax, and types in WAT buffers.
Fixes
- Silent miscompilations in both directions of the Wax/Wasm conversion, most of them numeric-width drift: a decompiled expression could recompile at a different width than the original opcode stated. The decompiler now records the type each source opcode states and the type checker pins any value that would otherwise come back at the wrong width.
- A shadowing
let's initializer is kept in the outer scope. - A call through a struct's function-pointer field is no longer rejected when the field's name is also a built-in method's (
copy,length,switch, a SIMD lane operation). The type checker chose the intrinsic by name and argument count, while the lowering had always chosen by the receiver's type. Field names come from the name section, so a decompiled module could carry one and produce Wax that did not compile back. - Conditional modules: entities referenced only inside an
(@if)body are converted, imports are hoisted, and every configuration is checked as a whole rather than through a separate specializer. - A memory's minimum is derived from its custom page size.
- A bare
(type N)blocktype naming an implicit type resolves. - Generated labels no longer clash with
dispatchandmatcharm labels. - Crash on
becomeof a binary intrinsic method, plus several crashes and a hang in typer error recovery. - Writing a large Wasm binary no longer overflows the stack, and lowering nested calls no longer blows up exponentially.
- Inputs that were wrongly accepted are now rejected: a
br_on_castflags byte outside0..3, overlong tag bytes, degenerate import groups, a non-finite or over-long hint payload, abrvalue that violates the block's result type, and a table with a non-nullable element type written with an inline element list, whose active segment fills the table at instantiation but leaves the table itself without a default. An out-of-f32-range float literal takes typef64. - A decompiled block (
do,if,loop,try) whose value is left on the stack for a later statement keeps its result type. Two annotations that were each redundant on their own — the block's result, and the cast pinning the same type — were both dropped, and the Wax that came out no longer compiled. - Decompiled dead code no longer gains a
ref.cast, or a strayextern.convert_any, on the way back. The decompiler pins the type of an operand it cannot see, and those pins were landing on a neighbouring value instead of on the hole they were meant for, where they turned into real instructions. Affected aref.castinto the extern hierarchy, bothextern/anyconversions, acall_refcallee, a field or element access receiver,array.lenandi31.get_s. - A conditionally-compiled module no longer defeats the decompiler's type pins. Where a pin sat over a value whose type the decompiler could not read, behind an
#[if]it could take a value one of the branches had pushed — producing Wax that either did not compile back or gained a strayextern.convert_any. - Output fixes: an empty blocktype uses the
0x40shorthand, funcidx element segments encode(ref func)rather thanfuncref, an empty name-section entry names nothing, and data strings are split at word boundaries. - Formatting fixes: an empty block body beside a following clause,
recgroups and catch arms always broken across lines,(do &t { x; }[0] = 1), and more faithful preservation of comments and of annotations the tokenizer does not interpret.
Performance
- The pretty-printer streams straight to the output channel instead of building a document tree, and an in-house
PrinterreplacesFormatthroughout. - An O(n²) in the binary-to-text path is gone, trivia tables are keyed on byte offsets, and several hot traversals are allocation-free.
Library
wax-libdepends oncmdliner, and now needs menhir 20260122 or later: the parser and its error messages are built with options that release introduced.Driver.to_binaryreports a failed lowering (a conditional that survived specialization, an unresolved reference) as a located diagnostic instead of letting an exception escape to the embedder.- The embedding API can turn the unused-declaration warnings off.
ParsingandParser_error_runtimemoved tolib-utils,Namingtolib-conversion.
Documentation
- A browser playground: write Wax, see the WAT and the diagnostics live, with no install.
- A
waxagent skill underskills/wax/, for coding assistants that read theSKILL.mdformat (npx skills add ocsigen/wax). opam install waxis documented now that both packages are in opam-repository.
0.1.0
- Initial release of the Wax toolchain: bidirectional conversion between Wax, WebAssembly text (WAT), and WebAssembly binary, with formatting, type checking, a language server, and the supporting libraries (
wax-lib).
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
On This Page