{"id":16906553,"url":"https://github.com/rsms/wasm-util","last_synced_at":"2025-04-09T20:11:23.406Z","repository":{"id":37602093,"uuid":"78409378","full_name":"rsms/wasm-util","owner":"rsms","description":"WebAssembly utilities","archived":false,"fork":false,"pushed_at":"2019-02-06T00:17:34.000Z","size":84,"stargazers_count":361,"open_issues_count":3,"forks_count":23,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-04-09T20:11:18.996Z","etag":null,"topics":["typescript","wasm","wasm-bytecode"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rsms.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-01-09T08:33:39.000Z","updated_at":"2025-03-15T14:24:50.000Z","dependencies_parsed_at":"2022-08-08T21:00:55.680Z","dependency_job_id":null,"html_url":"https://github.com/rsms/wasm-util","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsms%2Fwasm-util","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsms%2Fwasm-util/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsms%2Fwasm-util/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsms%2Fwasm-util/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rsms","download_url":"https://codeload.github.com/rsms/wasm-util/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248103872,"owners_count":21048245,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["typescript","wasm","wasm-bytecode"],"created_at":"2024-10-13T18:43:14.437Z","updated_at":"2025-04-09T20:11:23.363Z","avatar_url":"https://github.com/rsms.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# wasm-util\n\nUtilities for working with WebAssembly (aka WASM), to be used with TypeScript and JavaScript.\nThis code currently supports version MVP-13 (candidate for v1) of WebAssembly.\n\n- Want to learn more about WebAssembly? Check out [\"Introduction to WebAssembly\"](https://rsms.me/wasm-intro)\n- You can also skip the reading and [jump to \"Building and testing\"](#building-and-testing)\n\n**Overview:**\n\n- [`ast`](src/ast.ts) provides a full TypeScript type system for [the complete WebAssembly specification](https://github.com/WebAssembly/design).\n- `ast.c` provides constructors for all parts of a WebAssembly module.\n- `ast.t` is a table of AST node types and their respective internal symbols.\n- `ast.sect_id` is a table of section names and their respective identifiers as `varunit7` objects.\n- `ast.get` provides helper functions for convenient access and traversal of an AST.\n- [`emit`](src/emit.ts) provides helpers for emitting WASM byte code from an AST.\n- [`repr`](src/repr.ts) generates human-readable text representations of an AST or ArrayBuffer.\n- [`lbtext`](src/lbtext.ts) generates [Linear Bytecode text](https://github.com/WebAssembly/design/blob/master/TextFormat.md) from AST instructions\n\nI found myself relying on a very complicated tool chain (source build of llvm, binaryen, etc) while all I was looking for was to get close to WebAssembly. The prime component of wasm-util is `ast` which provides a convenient way of building complete WASM modules, with full static type-checking if you're using TypeScript.\n\nFollowing is an example of building a module that provides the `factorial` function.\nLet's start by describing the function we're making in a C-like syntax:\n\n```cc\nint64 factorial(int64 n) {\n  return (n == 0) ?\n    1\n  :\n    n * factorial(n-1);\n}\n```\n\nThe equivalent WebAssembly code looks like this:\n\n```wasm\nget_local 0    // push parameter #0 on stack.\ni64.const 0    // push constant int64 \"0\" on stack.\ni64.eq         // execute \"eq\" which pops two operands from stack\n               //  and pushes int32 \"1\" or \"0\" on stack.\nif i64         // pops one int32 from stack; if its not \"0\":\n  i64.const 1  //   push constant int64 \"0\" on stack.\nelse           // else (if operand was \"0\"):\n  get_local 0  //   push parameter #0 on stack. $1\n  get_local 0  //   push parameter #0 on stack.\n  i64.const 1  //   push constant int64 \"0\" on stack.\n  i64.sub      //   execute \"sub[tract]\" which pops two operands\n               //    from stack (parameter #0 and constant int64 \"1\")\n               //    and finally pushes the result int64 on stack.\n  call 0       //   call function #0 (\"factorial\") which pops one\n               //    int64 from the stack and when it returns an\n               //    int64 has been pushed on stack\n  i64.mul      //   execute \"sub[tract]\" which pops two operands\n               //    from stack ($1 and result from function call)\n               //    and finally pushes the resulting int64 on stack\nend            // ends function, returning one int64 result (on stack.)\n               // Stack now contains one int64 value that's the result from one of\n               // the two branches above.\n```\n\nThe above code was printed by [lbtext](src/lbtext.ts), for which we provided an AST built with the [ast](src/ast.ts) module:\n\n```js\nimport ... 'wasm-util/ast'\nconst mod = c.module([\n\n  type_section([\n    func_type([i64], i64), // type index = 0\n  ]),\n  \n  function_section([\n    varuint32(0), // function index = 0, using type index 0\n  ]),\n  \n  export_section([\n    // exports \"factorial\" as function at index 0\n    export_entry(str_ascii(\"factorial\"), external_kind.function, varuint32(0)),\n  ]),\n\n  code_section([\n    // body of function at index 0:\n    function_body([ /* additional local variables here */ ], [\n      if_(i64, // i64 = result type of `if` expression\n        i64.eq(get_local(i64, 0), i64.const(0)), // condition\n        [ // then\n          i64.const(1)\n        ], [ // else\n          i64.mul(\n            get_local(i64, 0),\n            call(i64, varuint32(0), [ // 0 = function index\n              i64.sub(get_local(i64, 0), i64.const(1))\n            ]))])])]\n  )]\n)\n```\n\nWe can now generate WASM bytecode through the `Emittable` interface:\n\n```ts\nconst emitbuf = new BufferedEmitter(new ArrayBuffer(mod.z))\nmod.emit(emitbuf)\n// the array buffer (emitbuf.buffer) now contains the complete module code\n```\n\nOr print a human-readable representation of the AST:\n\n```ts\nimport { strRepr } from 'wasm-util/repr'\nconsole.log(strRepr(mod))\n```\n\nWhich yields the following in the console:\n\n```lisp\n(module 13\n  (section type 6 1\n    (func_type (i64) i64))\n  (section function 2 1 0)\n  (section export 13 1\n    (export_entry \"factorial\" external_kind.function 0))\n  (section code 25 1\n    (function_body 23 0\n      (if [i64]\n        (i64.eq\n          (get_local [0])\n          (i64.const [0])\n        )\n        (then\n          (i64.const [1]))\n        (else\n          (i64.mul\n            (get_local [0])\n            (call [0]\n              (i64.sub\n                (get_local [0])\n                (i64.const [1])\n              )))) end) end)))\n```\n\nA complete version of this \"factorial\" demo can be found at [test/build_factorial_test.ts](test/build_factorial_test.ts).\n\n## ast\n\n`ast` provides a full TypeScript type system for [the complete WebAssembly specification](https://github.com/WebAssembly/design) including AST constructors and access functions.\n\nNoteworthy properties of the AST:\n\n- Nodes are immutable and contains no parents, meaning that any subtree can be used in multiple locations without the need to copy any data (e.g. macros can be trivially \"implemented\" by building a structure once and using it multiple times.)\n- Nodes' underlying type structures are uniform to allow efficient JavaScript VM optimizations.\n- Nodes' TypeScript types are rich in expression but with almost zero effect on runtime code — i.e. the `type_section` constructor returns the same kind of underlying structure as `import_section`, but the two functions when operated in TypeScript returns two exclusive, incompatible types (`TypeSection` and `ImportSection`, respectively.)\n- Each node has the ability to emit WASM bytecode that represents itself in a very efficient and side-effect-free manner.\n\nThe AST is built in a way that makes it as portable and light-weight as possible, with two basic types: atoms and cells. An atom is a single value and represents some amount of bytes corresponding to actual WASM bytecode. A cell is a compount structure that contains other atoms and cells, possibly also represents WASM bytecode.\n\n```ts\n// N is the common type of all AST nodes\ninterface N extends Emittable {\n  readonly t :TypeTag  // type\n  readonly z :uint32   // size in bytes (includes size of any children)\n  readonly v :any      // value\n}\ninterface Atom\u003cT\u003e extends N {\n  readonly v :T\n}\ninterface Cell\u003cT extends N\u003e extends N {\n  readonly v :T[]\n}\ninterface Module ...\n```\n\nFor the full type system, see [`ast.ts`](src/ast.ts)\n\n\n### Static type checking with TypeScript\n\nWhen used in TypeScript, the operand types, immediate types and result types of opcode and compound instructions are checked at compile time. For instance, say that we're converting a certain code path from i32 to i64 and forget something:\n\n```ts\ni64.eq(i64.const(1), i32.const(3))\n```\n\nThen the TypeScript compiler will complain:\n\n\u003e error TS2345: Argument of type 'Op\u003cI64\u003e' is not assignable to parameter of type 'Op\u003cI32\u003e'.\n\u003e   Type 'I64' is not assignable to type 'I32'.\n\u003e     Property '_I32' is missing in type 'I64'.\n\nWe can correct the error by replacing `i32.const(3)` with `i64.const(3)` in this case since the `i64.eq` function has the type signature `(i64, i64) i32`.\n\n\n## emit\n\nAST nodes has the ability to efficiently produce its corresponding\nWASM bytecode through the `Emittable` interface:\n\n```ts\ninterface Emittable {\n  emit(e :Emitter) :Emitter\n}\n```\n\nWhich takes an Emitter as its parameter and returns a potentially different Emitter which reflects the state after emitting code for the callee node. The Emitter interface looks like this:\n\n```ts\ninterface Emitter {\n  writeU8(v :uint8) :Emitter\n  writeU16(v :uint16) :Emitter\n  writeU32(v :uint32) :Emitter\n  writeF32(v :float32) :Emitter\n  writeF64(v :float64) :Emitter\n  writeBytes(v :ArrayLike\u003cuint8\u003e) :Emitter\n}\n```\n\nEach modifying operation returns a potentially different Emitter which is the result of\nthe receiver + modifications, thus modifying operations should be called like this:\n\n```js\ne = e.writeU32(1)\ne = e.writeU32(2)\n```\n\nHowever **NOT** like this:\n\n```js\ne.writeU32(1)\ne.writeU32(2)\n// e represents same state as before `e.writeU32(1)`\n```\n\nThis interface makes it possible to implement emitters with immutable persistent data structures.\n\nA concrete implementation of an Emitter is provided by `emit` which writes to an `ArrayBuffer`:\n\n```ts\nclass BufferedEmitter implements Emitter {\n  readonly buffer :ArrayBuffer\n  readonly view   :DataView\n           length :uint32\n  constructor(buffer :ArrayBuffer)\n}\n```\n\n## repr\n\n[repr](src/repr.ts) has the ability to generate human-readable text representations of AST nodes.\n\nThere's an example of using `repr` to visualize an AST earlier in this document.\n\nThe `repr` function generates a S-expression representation of the AST in a form that is similar to how the AST would have been built using `ast`.\n\nThe `reprBuffer` function generates rows and columns of bytes values representing\nan `ArrayBuffer` with optional terminal-color higlighting of a range of bytes.\nUseful for visualizing what an `Emitter` produces, or for pointing out bytes in a module\nthat causes an error with the spec interpreter.\n\n```ts\nfunction repr(n :N, w :Writer, options? :Options)\n\nfunction reprBuffer(\n  buffer          :ArrayBuffer,\n  w               :Writer,\n  limit?          :number,\n  highlightRange? :number[],\n  options?        :Options)\n\ntype Writer = (s :string)=\u003evoid\ninterface Options {\n  readonly colors         :boolean  // explicitly enable or disable terminal colors\n  readonly immSeparator   :string   // defaults to `:`\n  readonly detailedTypes? :boolean, // `vi32(9)` or just `9`\n}\n\n// Convenience function that returns a string\nfunction strRepr(n :N, options? :Options) :string\n\n// Convenience function that returns a string\nfunction strReprBuffer(\n  buffer          :ArrayBuffer,\n  limit?          :number,\n  highlightRange? :number[],\n  options?        :Options) :string\n```\n\n\n## eval\n\n`eval` is rather specialized module for executing the WebAssembly spec interpreter. It only works with Nodejs as it needs access to both the file system and process spawning.\n\nThe `specEval` function evaluates a WASM module and resolves a promise with any stdout output from the spec interpreter.\n\n```ts\nfunction specEval(buf :ArrayBuffer, options? :SpecOptions) :Promise\u003cstring\u003e\n\ninterface SpecOptions {\n  eval?      :string  // S-expression to evaluate after loading the module\n  timeout?   :number  // 0 = no timeout. Defaults to 30000ms.\n  logErrors? :boolean // when true, logs errors to stderr\n  trace?     :boolean // trace execution, printing to stdout\n}\n```\n\nHave a look at [test/build_test.js](test/build_test.js) for an example where `specEval` is used to test the functionality of a module built with `ast`.\n\n\n## lbtext\n\n`lbtext` can be used to generate [Linear Bytecode text](https://github.com/WebAssembly/design/blob/master/TextFormat.md) from AST code. E.g.\n\n```wasm\nget_local 0\ni64.const 2\ni64.div_s\nend\n```\n\nThe `printCode` function takes a list of operations to print.\n\n```ts\nfunction printCode(instructions :N[], writer :Writer)\ntype Writer = (s :string)=\u003evoid\n```\n\n## Building and testing\n\nFirst-time setup:\n\n```bash\ngit clone https://github.com/WebAssembly/spec.git wasm-spec\ncd wasm-spec/interpreter\n# install ocaml in some way, perhaps with homebrew or aptitude, then\nmake test \u0026\u0026 make opt\ncd ../..\nyarn || npm\n```\n\nBuilding JavaScript from TypeScript source:\n\n```\n$ node_modules/.bin/tsc  // puts things in \"build\" directory\n```\n\nRunning tests:\n\n```\n$ test/test.js\n```\n\nUpgrading the spec interpreter:\n\n```bash\ngit -C wasm-spec pull origin\ncd wasm-spec/interpreter\nmake clean \u0026\u0026 make test \u0026\u0026 make opt\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frsms%2Fwasm-util","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frsms%2Fwasm-util","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frsms%2Fwasm-util/lists"}