{"id":20722480,"url":"https://github.com/toomanybees/wasm-demo","last_synced_at":"2026-04-18T21:05:03.070Z","repository":{"id":74388726,"uuid":"143666587","full_name":"TooManyBees/wasm-demo","owner":"TooManyBees","description":null,"archived":false,"fork":false,"pushed_at":"2018-11-04T05:35:52.000Z","size":51,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-17T23:15:28.043Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TooManyBees.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-08-06T02:30:45.000Z","updated_at":"2018-11-04T05:35:53.000Z","dependencies_parsed_at":null,"dependency_job_id":"f8516b83-12dd-495c-bc03-3d4363cf411e","html_url":"https://github.com/TooManyBees/wasm-demo","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/TooManyBees%2Fwasm-demo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TooManyBees%2Fwasm-demo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TooManyBees%2Fwasm-demo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TooManyBees%2Fwasm-demo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TooManyBees","download_url":"https://codeload.github.com/TooManyBees/wasm-demo/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242996847,"owners_count":20219028,"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":[],"created_at":"2024-11-17T03:36:01.435Z","updated_at":"2026-04-18T21:04:58.045Z","avatar_url":"https://github.com/TooManyBees.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# hi\n\nthis is about WebAssembly\n\nlots of guides exist that either focus on the \"hello world\" wasm\nprogram (`x_plus_one.wasm`) or the construction of a fully featured\nmodule with an automagically-written javascript side.\n\nthis is specifically about the middle ground between those two poles:\nthe interface between JS land and Wasm land\n\n# what is wasm\n\n`wasm` is a binary format:\n\n```text\n0061 736d 0100 0000 0107 0160 027f 7f01\n7f02 0b01 026a 7303 6d65 6d02 0001 0302\n0100 070e 010a 6163 6375 6d75 6c61 7465\n0000 0a32 0130 0102 7f20 0020 0141 046c\n6a21 0202 4003 4020 0020 0246 0d01 2003\n2000 2802 006a 2103 2000 4104 6a21 000c\n000b 0b20 030b\n```\n\n# wat\n\nThe text representation is called `wat` (to my eternal delight).\n\n```wasm\n(module\n  (memory (import \"js\" \"mem\") 1)\n  (func (export \"accumulate\") (param $ptr i32) (param $len i32) (result i32)\n    (local $end i32)\n    (local $sum i32)\n    (set_local $end (i32.add (get_local $ptr) (i32.mul (get_local $len) (i32.const 4))))\n    (block $break (loop $top\n      (br_if $break (i32.eq (get_local $ptr) (get_local $end)))\n      (set_local $sum (i32.add (get_local $sum)\n                               (i32.load (get_local $ptr))))\n        (set_local $ptr (i32.add (get_local $ptr) (i32.const 4)))\n        (br $top)\n    ))\n    (get_local $sum)\n  )\n)\n```\n\nIt's a lisp. It's readable, to a limit. This function sums a list\nof 32 bit integers. Scaling beyond this by hand is a pain. You even\nneed to account for i32s being 4 bytes as you increment the pointer\nwhile looping.\n\nOther examples are written in Rust. This is not a talk about Rust.\nToo many people already think I work for Mozilla. This is also not\nan endorsement of Rust, beyond saying that it has the best wasm\ntool chain. The alternative is emscripten which takes 45 minutes\nand about 10GB to build. Installing Rust and the wasm32 target is\na fifteen minute job.\n\n# quick summary\n\nIt's byte code, it runs in a virtual stack machine, it exposes\nan API that the browser and Node can access.\n\nSome use cases of wasm modules:\n* expensive computation (think media codecs)\n* you just want to write in not-js for a specific thing\n* pixel crunching?\n    * (unfortunately, `\u003ccanvas\u003e` jealously guards its bytes like a dragon)\n* like, who cares\n    * we're past demanding a use case justification for javascript\n    * so let's skip that whole argument for wasm's adoption while we're at it\n\n# what can't wasm do?\n\n* handle exceptions\n    * errors stop execution of a fn and raise an exception in JS land\n* return anything except numbers to JS land\n    * though you can return pointers to something that can be represented as byte arrays\n    * which doesn't include JS objects\n* reach outside of its address space\n\nJavascript glue code needed for:\n* allocation\n* re-referencing memory buffer after it grew/moved\n* passing strings, arrays\n\n# loading\n\nHere's how you load and instantiate a wasm program, but it won't\nwork for you the first time.\n\n```javascript\nWebAssembly.instantiateStreaming(fetch(\"url.wasm\"), {})\n.then(wasm =\u003e ...)\n```\n\n## Your first hiccup after skimming a \"hello world\" tutorial\n\nYour browser demands the `Content-Type: application/webassembly` header.\n`file://` protocol won't add a content type, and no server currently\nmaps the `wasm` extension to that without manual config.\n\nEither add the right mime type detection to your webserver or do this:\n\n```javascript\nfetch(\"url.wasm\")\n.then(response =\u003e response.arrayBuffer())\n.then(bytes =\u003e WebAssembly.instantiate(bytes, {}))\n.then(wasm =\u003e ...)\n```\n\nThis blocks validation and instantiation until the whole module is\ndownloaded, unlike `instantiateStreaming` which begins with the 1st byte.\n\n# \"hello wasm!\"\n\nThe `[hello world program](hello_wasm/hello_wasm.wat)` of WebAssembly\nis adding 1 to a number.\n\n```javascript\nWebAssembly.instantiateStreaming(fetch(\"hello_wasm.wasm\"), {})\n.then(wasm =\u003e { window.hello_wasm = wasm.instance.exports; });\n```\n\n```javascript\nwindow.hello_wasm.add_one(5)\n// =\u003e 6\n```\n\nWasm can only represent number types `i32`, `i64`, `f32`, `f64`, so\nits exported functions can only accept or return the javascript\n`Number` type. Non-numbers will get coerced to zero.\n\n```javascript\nwindow.hello_wasm.add_one(\"Hi there!\")\n// =\u003e 1\nwindow.hello_wasm.add_one(NaN)\n// =\u003e 1\n```\n\n# importing functions\n\nIn `[imported_func.wat](imported_func/imported_func.wat)`, the first\ntwo lines are\n\n```wasm\n(func $logTime (import \"import\" \"logTime\") (param f64))\n(func $getTimestamp (import \"import\" \"getTimestamp\") (result f64))\n;;                                    ^^^^^^^^^^^^ imported function\n;;                           ^^^^^^ module name\n```\n\nwhich declares what imports are required for the Wasm module to be\nimported. The string `\"import\"` directly correlates to the key name\n`\"import\"` in the import object below. *This name is totally arbitrary.*\nSome examples use `\"import\"` or `\"js\"`, and LLVM picks `\"env\"` for you.\n*You can have multiple top level module names.*\n\n```javascript\nimportObj = {\n    import: {\n        getTimestamp: Date.now.bind(date), // `bind` works as expected\n        logTime: (value) =\u003e console.log(`Elapsed time: ${value} ms`),\n    }\n};\nWebAssembly.instantiateStreaming(\n    fetch(\"imported_func.wasm\"),\n    importObj,\n).then(wasm =\u003e { window.imported_func = wasm.instance.exports; });\n```\n\nThis wasm module exports one function `doWork` that takes a number, and\nincrements a value that many times, just to spend a noticable amount of\ntime. It uses the imported functions `getTimestamp` to compute the\namount of time it spent incrementing that variable, and then invokes\n`logTime` to `console.log` the number of ms.\n\n```javascript\nimported_func.doWork(10);\n// Elapsed time: 0ms\nimported_func.doWork(10000000);\n// Elapsed time: 5ms\n```\n\n# memory\n\nWebAssembly has a heap in the form of `WebAssembly.Memory`.\nA Wasm program can accept a `Memory` imported from its environment,\nor it can create its own and export it for the environment to access.\nOr it can create its own memory and keep it to itself!\n\n## importing/exporting\n\nThe program `[imported_memory](imported_memory/imported_memory.wat)`\naccepts an imported `Memory` object. With is exported function\n`double(ptr, len)`, it doubles the `len`-element sub-array of `Uint32`s\nstarting at address `ptr`.\n\n```javascript\nmem = new WebAssembly.Memory({ initial: 1 });\narray = new Uint32Array(mem.buffer);\nimportObj = {\n    import: {\n        memory: mem\n    }\n};\nWebAssembly.instantiateStreaming(\n    fetch(\"imported_memory.wasm\"),\n    importObj,\n).then(wasm =\u003e { window.imported_memory = wasm.instance.exports; });\n```\n\n```javascript\nconst ptr = 5;\nconst len = 10;\nfor (n in array.slice(ptr, ptr + len)) {\n    array[ptr + parseInt(n)] = parseInt(n);\n}\n// Uint32Array(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\nimported_memory.double(ptr, len);\n// Uint32Array(10) [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]\n```\n\nThe program could just have easily defined its memory internally,\nthe exported it for the Javascript code to work with.\n\n## growing\n\n`[grow_memory](grow_memory/grow_memory.wat)`\n\n```javascript\nWebAssembly.instantiateStreaming(fetch(\"grow_memory.wasm\"), {})\n.then(wasm =\u003e { window.grow_memory = wasm.instance.exports; });\n```\n\nWebAssembly memory can grow in pages of 65536 bytes. This program\nstarts with one page of memory exported. It exports one function\nto store a `f64` at address 0, and one function to grow the memory\nby one page at a time.\n\n```javascript\n// What size is the memory initially? 65536 bytes\ngrow_memory.memory.buffer.byteLength;\n\n// Store a number at address 0 and confirm it's there\ngrow_memory.store(0.75);\narray = new Float64Array(grow_memory.memory.buffer);\n\n// Grow memory with the exported grow function;\ngrow_memory.grow();\n\n// What size is the memory now? 131072 bytes\ngrow_memory.memory.buffer.byteLength;\n\n// But when memory is resized, its buffer is invalidated and a new\n// one is created. So `array` points to nothing now.\narray.buffer.byteLength === 0;\n\n// We need to recreate it after every grow\narray = new Float64Array(grow_memory.memory.buffer);\n```\n\n```javascript\n// Since the program exports its memory, we can use the Memory object's\n// Javascript API to grow it.\ngrow_memory.memory.grow(1);\n\n// The program specified a maximum of 3 pages. It's now at its max size.\ngrow_memory.memory.grow(1);\n// Uncaught RangeError: WebAssembly.Memory.grow(): maximum memory size exceeded\n\n// Or from the WebAssembly side, failure returns `-1`\ngrow_memory.grow();\n```\n\n# using an allocator\n\n`[allocating_memory](allocating_memory/allocating_memory.wat)`\n\nnote that you can pass random numbers as the pointer\nincrement (pointer+1), then check the value of (pointer)\nnote that the address of state is larger than the original memory size\n    then note that memory size grew\n    by 64k, the size of 1 webassembly page\n    you can grow memory with memory.prototype.grow; wasm code can grow itself too\n    growing beyond its capacity raises an exception in JS land\nlook at the size of that pointer!\n    It's kind of low for a pointer\n    But it's also not at zero\n    There's state for the allocator taking up space\n    Also, LLVM creates the shadow stack\n        since wasm can only represent i32/i64/f32/f64, structs\n        can't go on the heap. They're reserved their own space\n        on the heap\n\nlet's return some strings\n    c-style with null bytes: return the address it starts at\n        you'd use a function or generator to consume the arraybuffer until a null byte\n    pass in the starting addr to the wasm function, return the length of the string\n        you'd read the arraybuffer from offset to offset+length\n    (new TextDecoder(\"encoding-scheme\")).decode(str) // not in Edge though\n\nlet's export some memory\n    you can pass IN memory\n    you can pass IN tables\n    multiple modules can SHARE memory/tables\n    dynamic linking between modules, in JS\n\nwe obviously need glue code to deal with shared memory, tables, and strings\n\n## hangman\n\nhey look it's a hangman game\n\nhttps://webassembly.studio\nhttps://blog.scottlogic.com/2018/04/26/webassembly-by-hand.html\nhttps://developer.mozilla.org/en-US/docs/WebAssembly/Concepts\nhttps://webassembly.org/docs/semantics/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoomanybees%2Fwasm-demo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftoomanybees%2Fwasm-demo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftoomanybees%2Fwasm-demo/lists"}