├── .gitignore ├── LICENSE ├── README.md ├── etc └── memory-and-addressing-notes.md ├── package.json ├── src ├── ast.ts ├── basic-types.ts ├── emit.ts ├── eval.ts ├── info.ts ├── lbtext.ts ├── repr.ts └── utf8.ts ├── test ├── .gitignore ├── build_factorial_test.ts ├── build_test.js ├── mem_align_checks_test.js └── test.js ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.min.js 3 | *.g.js 4 | *.map 5 | *.lock 6 | *.tmp 7 | build 8 | wasm-spec 9 | node_modules 10 | _local 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Rasmus Andersson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wasm-util 2 | 3 | Utilities for working with WebAssembly (aka WASM), to be used with TypeScript and JavaScript. 4 | This code currently supports version MVP-13 (candidate for v1) of WebAssembly. 5 | 6 | - Want to learn more about WebAssembly? Check out ["Introduction to WebAssembly"](https://rsms.me/wasm-intro) 7 | - You can also skip the reading and [jump to "Building and testing"](#building-and-testing) 8 | 9 | **Overview:** 10 | 11 | - [`ast`](src/ast.ts) provides a full TypeScript type system for [the complete WebAssembly specification](https://github.com/WebAssembly/design). 12 | - `ast.c` provides constructors for all parts of a WebAssembly module. 13 | - `ast.t` is a table of AST node types and their respective internal symbols. 14 | - `ast.sect_id` is a table of section names and their respective identifiers as `varunit7` objects. 15 | - `ast.get` provides helper functions for convenient access and traversal of an AST. 16 | - [`emit`](src/emit.ts) provides helpers for emitting WASM byte code from an AST. 17 | - [`repr`](src/repr.ts) generates human-readable text representations of an AST or ArrayBuffer. 18 | - [`lbtext`](src/lbtext.ts) generates [Linear Bytecode text](https://github.com/WebAssembly/design/blob/master/TextFormat.md) from AST instructions 19 | 20 | I 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. 21 | 22 | Following is an example of building a module that provides the `factorial` function. 23 | Let's start by describing the function we're making in a C-like syntax: 24 | 25 | ```cc 26 | int64 factorial(int64 n) { 27 | return (n == 0) ? 28 | 1 29 | : 30 | n * factorial(n-1); 31 | } 32 | ``` 33 | 34 | The equivalent WebAssembly code looks like this: 35 | 36 | ```wasm 37 | get_local 0 // push parameter #0 on stack. 38 | i64.const 0 // push constant int64 "0" on stack. 39 | i64.eq // execute "eq" which pops two operands from stack 40 | // and pushes int32 "1" or "0" on stack. 41 | if i64 // pops one int32 from stack; if its not "0": 42 | i64.const 1 // push constant int64 "0" on stack. 43 | else // else (if operand was "0"): 44 | get_local 0 // push parameter #0 on stack. $1 45 | get_local 0 // push parameter #0 on stack. 46 | i64.const 1 // push constant int64 "0" on stack. 47 | i64.sub // execute "sub[tract]" which pops two operands 48 | // from stack (parameter #0 and constant int64 "1") 49 | // and finally pushes the result int64 on stack. 50 | call 0 // call function #0 ("factorial") which pops one 51 | // int64 from the stack and when it returns an 52 | // int64 has been pushed on stack 53 | i64.mul // execute "sub[tract]" which pops two operands 54 | // from stack ($1 and result from function call) 55 | // and finally pushes the resulting int64 on stack 56 | end // ends function, returning one int64 result (on stack.) 57 | // Stack now contains one int64 value that's the result from one of 58 | // the two branches above. 59 | ``` 60 | 61 | The above code was printed by [lbtext](src/lbtext.ts), for which we provided an AST built with the [ast](src/ast.ts) module: 62 | 63 | ```js 64 | import ... 'wasm-util/ast' 65 | const mod = c.module([ 66 | 67 | type_section([ 68 | func_type([i64], i64), // type index = 0 69 | ]), 70 | 71 | function_section([ 72 | varuint32(0), // function index = 0, using type index 0 73 | ]), 74 | 75 | export_section([ 76 | // exports "factorial" as function at index 0 77 | export_entry(str_ascii("factorial"), external_kind.function, varuint32(0)), 78 | ]), 79 | 80 | code_section([ 81 | // body of function at index 0: 82 | function_body([ /* additional local variables here */ ], [ 83 | if_(i64, // i64 = result type of `if` expression 84 | i64.eq(get_local(i64, 0), i64.const(0)), // condition 85 | [ // then 86 | i64.const(1) 87 | ], [ // else 88 | i64.mul( 89 | get_local(i64, 0), 90 | call(i64, varuint32(0), [ // 0 = function index 91 | i64.sub(get_local(i64, 0), i64.const(1)) 92 | ]))])])] 93 | )] 94 | ) 95 | ``` 96 | 97 | We can now generate WASM bytecode through the `Emittable` interface: 98 | 99 | ```ts 100 | const emitbuf = new BufferedEmitter(new ArrayBuffer(mod.z)) 101 | mod.emit(emitbuf) 102 | // the array buffer (emitbuf.buffer) now contains the complete module code 103 | ``` 104 | 105 | Or print a human-readable representation of the AST: 106 | 107 | ```ts 108 | import { strRepr } from 'wasm-util/repr' 109 | console.log(strRepr(mod)) 110 | ``` 111 | 112 | Which yields the following in the console: 113 | 114 | ```lisp 115 | (module 13 116 | (section type 6 1 117 | (func_type (i64) i64)) 118 | (section function 2 1 0) 119 | (section export 13 1 120 | (export_entry "factorial" external_kind.function 0)) 121 | (section code 25 1 122 | (function_body 23 0 123 | (if [i64] 124 | (i64.eq 125 | (get_local [0]) 126 | (i64.const [0]) 127 | ) 128 | (then 129 | (i64.const [1])) 130 | (else 131 | (i64.mul 132 | (get_local [0]) 133 | (call [0] 134 | (i64.sub 135 | (get_local [0]) 136 | (i64.const [1]) 137 | )))) end) end))) 138 | ``` 139 | 140 | A complete version of this "factorial" demo can be found at [test/build_factorial_test.ts](test/build_factorial_test.ts). 141 | 142 | ## ast 143 | 144 | `ast` provides a full TypeScript type system for [the complete WebAssembly specification](https://github.com/WebAssembly/design) including AST constructors and access functions. 145 | 146 | Noteworthy properties of the AST: 147 | 148 | - 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.) 149 | - Nodes' underlying type structures are uniform to allow efficient JavaScript VM optimizations. 150 | - 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.) 151 | - Each node has the ability to emit WASM bytecode that represents itself in a very efficient and side-effect-free manner. 152 | 153 | The 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. 154 | 155 | ```ts 156 | // N is the common type of all AST nodes 157 | interface N extends Emittable { 158 | readonly t :TypeTag // type 159 | readonly z :uint32 // size in bytes (includes size of any children) 160 | readonly v :any // value 161 | } 162 | interface Atom extends N { 163 | readonly v :T 164 | } 165 | interface Cell extends N { 166 | readonly v :T[] 167 | } 168 | interface Module ... 169 | ``` 170 | 171 | For the full type system, see [`ast.ts`](src/ast.ts) 172 | 173 | 174 | ### Static type checking with TypeScript 175 | 176 | When 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: 177 | 178 | ```ts 179 | i64.eq(i64.const(1), i32.const(3)) 180 | ``` 181 | 182 | Then the TypeScript compiler will complain: 183 | 184 | > error TS2345: Argument of type 'Op' is not assignable to parameter of type 'Op'. 185 | > Type 'I64' is not assignable to type 'I32'. 186 | > Property '_I32' is missing in type 'I64'. 187 | 188 | We 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`. 189 | 190 | 191 | ## emit 192 | 193 | AST nodes has the ability to efficiently produce its corresponding 194 | WASM bytecode through the `Emittable` interface: 195 | 196 | ```ts 197 | interface Emittable { 198 | emit(e :Emitter) :Emitter 199 | } 200 | ``` 201 | 202 | Which 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: 203 | 204 | ```ts 205 | interface Emitter { 206 | writeU8(v :uint8) :Emitter 207 | writeU16(v :uint16) :Emitter 208 | writeU32(v :uint32) :Emitter 209 | writeF32(v :float32) :Emitter 210 | writeF64(v :float64) :Emitter 211 | writeBytes(v :ArrayLike) :Emitter 212 | } 213 | ``` 214 | 215 | Each modifying operation returns a potentially different Emitter which is the result of 216 | the receiver + modifications, thus modifying operations should be called like this: 217 | 218 | ```js 219 | e = e.writeU32(1) 220 | e = e.writeU32(2) 221 | ``` 222 | 223 | However **NOT** like this: 224 | 225 | ```js 226 | e.writeU32(1) 227 | e.writeU32(2) 228 | // e represents same state as before `e.writeU32(1)` 229 | ``` 230 | 231 | This interface makes it possible to implement emitters with immutable persistent data structures. 232 | 233 | A concrete implementation of an Emitter is provided by `emit` which writes to an `ArrayBuffer`: 234 | 235 | ```ts 236 | class BufferedEmitter implements Emitter { 237 | readonly buffer :ArrayBuffer 238 | readonly view :DataView 239 | length :uint32 240 | constructor(buffer :ArrayBuffer) 241 | } 242 | ``` 243 | 244 | ## repr 245 | 246 | [repr](src/repr.ts) has the ability to generate human-readable text representations of AST nodes. 247 | 248 | There's an example of using `repr` to visualize an AST earlier in this document. 249 | 250 | The `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`. 251 | 252 | The `reprBuffer` function generates rows and columns of bytes values representing 253 | an `ArrayBuffer` with optional terminal-color higlighting of a range of bytes. 254 | Useful for visualizing what an `Emitter` produces, or for pointing out bytes in a module 255 | that causes an error with the spec interpreter. 256 | 257 | ```ts 258 | function repr(n :N, w :Writer, options? :Options) 259 | 260 | function reprBuffer( 261 | buffer :ArrayBuffer, 262 | w :Writer, 263 | limit? :number, 264 | highlightRange? :number[], 265 | options? :Options) 266 | 267 | type Writer = (s :string)=>void 268 | interface Options { 269 | readonly colors :boolean // explicitly enable or disable terminal colors 270 | readonly immSeparator :string // defaults to `:` 271 | readonly detailedTypes? :boolean, // `vi32(9)` or just `9` 272 | } 273 | 274 | // Convenience function that returns a string 275 | function strRepr(n :N, options? :Options) :string 276 | 277 | // Convenience function that returns a string 278 | function strReprBuffer( 279 | buffer :ArrayBuffer, 280 | limit? :number, 281 | highlightRange? :number[], 282 | options? :Options) :string 283 | ``` 284 | 285 | 286 | ## eval 287 | 288 | `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. 289 | 290 | The `specEval` function evaluates a WASM module and resolves a promise with any stdout output from the spec interpreter. 291 | 292 | ```ts 293 | function specEval(buf :ArrayBuffer, options? :SpecOptions) :Promise 294 | 295 | interface SpecOptions { 296 | eval? :string // S-expression to evaluate after loading the module 297 | timeout? :number // 0 = no timeout. Defaults to 30000ms. 298 | logErrors? :boolean // when true, logs errors to stderr 299 | trace? :boolean // trace execution, printing to stdout 300 | } 301 | ``` 302 | 303 | Have 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`. 304 | 305 | 306 | ## lbtext 307 | 308 | `lbtext` can be used to generate [Linear Bytecode text](https://github.com/WebAssembly/design/blob/master/TextFormat.md) from AST code. E.g. 309 | 310 | ```wasm 311 | get_local 0 312 | i64.const 2 313 | i64.div_s 314 | end 315 | ``` 316 | 317 | The `printCode` function takes a list of operations to print. 318 | 319 | ```ts 320 | function printCode(instructions :N[], writer :Writer) 321 | type Writer = (s :string)=>void 322 | ``` 323 | 324 | ## Building and testing 325 | 326 | First-time setup: 327 | 328 | ```bash 329 | git clone https://github.com/WebAssembly/spec.git wasm-spec 330 | cd wasm-spec/interpreter 331 | # install ocaml in some way, perhaps with homebrew or aptitude, then 332 | make test && make opt 333 | cd ../.. 334 | yarn || npm 335 | ``` 336 | 337 | Building JavaScript from TypeScript source: 338 | 339 | ``` 340 | $ node_modules/.bin/tsc // puts things in "build" directory 341 | ``` 342 | 343 | Running tests: 344 | 345 | ``` 346 | $ test/test.js 347 | ``` 348 | 349 | Upgrading the spec interpreter: 350 | 351 | ```bash 352 | git -C wasm-spec pull origin 353 | cd wasm-spec/interpreter 354 | make clean && make test && make opt 355 | ``` 356 | -------------------------------------------------------------------------------- /etc/memory-and-addressing-notes.md: -------------------------------------------------------------------------------- 1 | irc.w3.org#webassembly Jan 11, 2017 2 | 3 | TL;DR: mem imm's offset is simply a way for the assembler/compiler to add a constant offset in addition to the dynamic address provided by the addr operand. 4 | 5 | ``` 6 | 11:58 rsms: Question: memory address semantics (https://github.com/WebAssembly/design/blob/master/Semantics.md#addressing) doesn't make it clear if the `address` operand signifies memory index, memory-page index or virtual address. I'm _guessing_ it signified memory-page index, but I'm _hoping_ it's virtual address (byte offset into total default memory segment.) 7 | 13:04 jfb: rsms it's the address within the WebAssembly.Memory's buffer. Address 0 is the start of that buffer. It's *not* virtual address from the process' POV, but you can think of it as the virtual address from the WebAssembly VM's perspective (certainly, from C++ code's POV when executing in a wasm VM). 8 | 13:09 rsms: jfb: Thanks for your reply. So `(i32.const 8) (i32.const 132) (i32.store (alignment 2) (offset 0))` would cause the value "123" to be stored at bytes [8..11] in the first memory page? 9 | 13:10 rsms: And `(i32.const 72) (i32.const 132) (i32.store (alignment 2) (offset 0))` would cause the value "123" to be stored at bytes [72..75] in the second memory page, e.g. at effective address 72? 10 | 13:42 dschuff: rsms: not in the second memory page 11 | 13:42 dschuff: but at effective address 72 yes 12 | 13:43 dschuff: page size in wasm is defined as 64k but it's only meaningful for specifying the size of the linear memory 13 | 13:50 rsms: dschuff: Sorry, I meant 72000 th byte in the second example, i.e. `(i32.const 72000) (i32.const 132) (i32.store (alignment 2) (offset 0))` writes i32 "123" at bytes [72000..75000] within the second memory page, assuming the second page starts at byte 64000 14 | 13:50 rsms: 72000..72003 * 15 | 13:51 rsms: If this is correct, then when would I ever use the "offset" property of the memory immediate? 16 | 14:03 dschuff: rsms: that's correct. and yes, `(i32.const 8)(i32.const 132)(i32.store (offset 0))` is equivalent to `(i32.const 0)(i32.const 132)(i32.store (offset 8))` 17 | 14:04 dschuff: but if you imagine that instead of (i32.const 8) you have some non-constant expression, then it might still be convenient for your compiler to additionaly have the constant offset 18 | ``` 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "source-map-support": "^0.4.8", 4 | "typescript": "^2.1.5" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/ast.ts: -------------------------------------------------------------------------------- 1 | import {utf8} from './utf8' 2 | import {Emittable, Emitter} from './emit' 3 | 4 | // For some reason we can't import basic-types into this module. 5 | // import {uint1,uint7,uint8,uint16,uint32,int7,int32,int64,float32,float64} from './basic-types' 6 | export type uint1 = number 7 | export type uint7 = number 8 | export type uint8 = number 9 | export type uint16 = number 10 | export type uint32 = number 11 | export type int7 = number 12 | export type int32 = number 13 | export type int64 = number 14 | export type float32 = number 15 | export type float64 = number 16 | 17 | const DEBUG = false 18 | const assert = DEBUG ? function(cond :any, msg? :any) { 19 | if (!cond) { throw new Error('assertion failure'); } 20 | } : function(){} 21 | 22 | //—————————————————————————————————————————————————————————————————————————————— 23 | // Basic node types 24 | 25 | export type TypeTag = symbol 26 | 27 | export interface N extends Emittable { 28 | readonly t :TypeTag // type 29 | readonly z :uint32 // size in bytes (includes size of any children) 30 | readonly v :any // value 31 | } 32 | 33 | export interface Atom extends N { 34 | readonly v :T 35 | } 36 | 37 | export interface Cell extends N { 38 | readonly v :T[] 39 | } 40 | 41 | //————————————————————————————————————— 42 | // Formal types 43 | // We use a trick here to get the most out of TypeScripts type checker, 44 | // namely we specify interfaces that have "type tag" properties. 45 | // However, concrete types doesn't actually have these properties, so any 46 | // attempts to access these properties will always yield `undefined`. 47 | 48 | export interface Module extends Cell
{ readonly _Module: undefined 49 | readonly version :uint32 50 | } 51 | 52 | export type Section = CustomSection 53 | | TypeSection // Function signature declarations 54 | | ImportSection // Import declarations 55 | | FunctionSection // Function declarations 56 | | TableSection // Indirect function table and other tables 57 | | MemorySection // Memory attributes 58 | | GlobalSection // Global declarations 59 | | ExportSection // Exports 60 | | StartSection // Start function declaration 61 | | ElementSection // Elements section 62 | | CodeSection // Function bodies (code) 63 | | DataSection // Data segments 64 | 65 | export interface CustomSection extends Cell { readonly _CustomSection: undefined } 66 | export interface TypeSection extends Cell { readonly _TypeSection: undefined } 67 | export interface ImportSection extends Cell { readonly _ImportSection: undefined } 68 | export interface FunctionSection extends Cell { readonly _FunctionSection: undefined } 69 | export interface TableSection extends Cell { readonly _TableSection: undefined } 70 | export interface MemorySection extends Cell{readonly _MemorySection:undefined} 71 | export interface GlobalSection extends Cell {readonly _GlobalSection:undefined} 72 | export interface ExportSection extends Cell { readonly _ExportSection: undefined } 73 | export interface StartSection extends Cell { readonly _StartSection: undefined } 74 | export interface ElementSection extends Cell { readonly _ElementSection: undefined} 75 | export interface CodeSection extends Cell { readonly _CodeSection: undefined } 76 | export interface DataSection extends Cell { readonly _DataSection: undefined } 77 | 78 | export interface ImportEntry extends Cell { readonly _ImportEntry: undefined } 79 | export interface ExportEntry extends Cell { readonly _ExportEntry: undefined } 80 | 81 | export interface FuncType extends Cell { readonly _FuncType: undefined } 82 | export interface TableType extends Cell { readonly _TableType: undefined } 83 | export interface GlobalType extends Cell { readonly _GlobalType: undefined } 84 | 85 | export interface ResizableLimits extends Cell { readonly _ResizableLimits: undefined } 86 | export interface GlobalVariable extends Cell { readonly _GlobalVariable: undefined } 87 | export interface ElemSegment extends Cell { readonly _ElemSegment: undefined } 88 | export interface DataSegment extends Cell { readonly _DataSegment: undefined } 89 | 90 | export interface InitExpr extends Cell { readonly _InitExpr: undefined } 91 | export interface FunctionBody extends Cell { readonly _FunctionBody: undefined } 92 | export interface LocalEntry extends Cell { readonly _LocalEntry: undefined } 93 | 94 | export interface Str extends Atom> { readonly _Str: undefined 95 | readonly len :VarUint32 96 | } 97 | 98 | export interface Data extends Atom> { readonly _Data: undefined } 99 | 100 | export interface Uint8 extends Atom {} 101 | export interface Uint16 extends Atom {} 102 | export interface Uint32 extends Atom {} 103 | export interface VarUint32 extends Atom {} 104 | export interface VarUint7 extends Atom {} 105 | export interface VarUint1 extends Atom {} 106 | export interface VarInt7 extends Atom {} 107 | export interface VarInt32 extends Atom {} 108 | export interface VarInt64 extends Atom {} 109 | export interface Float32 extends Atom {} 110 | export interface Float64 extends Atom {} 111 | 112 | export interface I32 extends VarInt32 { readonly _I32: undefined } 113 | export interface I64 extends VarInt64 { readonly _I64: undefined } 114 | export interface F32 extends Float32 { readonly _F32: undefined } 115 | export interface F64 extends Float64 { readonly _F64: undefined } 116 | export interface Void extends VarInt7 { readonly _Void :undefined } 117 | 118 | export type Int = I32 | I64 // wasm32 | wasm64 119 | export type Result = I32 | I64 | F32 | F64 120 | export type AnyResult = Result | Void 121 | export type AnyOp = Op 122 | 123 | export interface ValueType extends Atom {} 124 | type AnyFunc = VarInt7 125 | type Func = VarInt7 126 | type EmptyBlock = VarInt7 127 | type ElemType = AnyFunc 128 | type ExternalKind = Uint8 129 | type BlockType = ValueType | EmptyBlock 130 | 131 | // Memory immediate. 132 | // In wasm32, address operands and offset attributes have type i32 133 | export type MemImm = [ 134 | // flags - a bitfield which currently contains the alignment in the least 135 | // significant bits, encoded as log2(alignment) 136 | VarUint32, 137 | 138 | // offset - added to the address to form effective address. 139 | // Useful when the address is dynamic and the compiler wants to add some 140 | // constant amount of offset to the dynamically-produced address. 141 | // I.e. effective_address = address + offset 142 | Int 143 | ] 144 | 145 | // Instruction opcodes 146 | export type OpCode = uint8 147 | 148 | export interface Op extends N { 149 | readonly _Op :R 150 | readonly r :AnyResult 151 | readonly v :OpCode 152 | readonly pre? :N[] | N // instrs. pushing values onto the stack, used by "pre" types 153 | readonly imm? :N[] | N // immediates, used by "imm" types 154 | readonly post? :N[] // used by "post" types 155 | } 156 | 157 | // Operations on all number types 158 | export interface NumOps { 159 | const(v :number) :Op 160 | load(mi :MemImm, addr :Op) :Op 161 | store(mi :MemImm, addr :Op, v :Op) :Op 162 | addrIsAligned(mi :MemImm, addr :number) :boolean 163 | eq(a :Op, b :Op) :Op 164 | ne(a :Op, b :Op) :Op 165 | add(a :Op, b :Op) :Op 166 | sub(a :Op, b :Op) :Op 167 | mul(a :Op, b :Op) :Op 168 | } 169 | 170 | // Operations on all integer number types 171 | export interface IntOps extends NumOps { 172 | // Memory 173 | load8_s(mi :MemImm, addr :Op) :Op 174 | load8_u(mi :MemImm, addr :Op) :Op 175 | load16_s(mi :MemImm, addr :Op) :Op 176 | load16_u(mi :MemImm, addr :Op) :Op 177 | store8(mi :MemImm, addr :Op, v :Op) :Op 178 | store16(mi :MemImm, addr :Op, v :Op) :Op 179 | 180 | // Comparison 181 | eqz (a :Op) :Op 182 | lt_s(a :Op, b :Op) :Op 183 | lt_u(a :Op, b :Op) :Op 184 | gt_s(a :Op, b :Op) :Op 185 | gt_u(a :Op, b :Op) :Op 186 | le_s(a :Op, b :Op) :Op 187 | le_u(a :Op, b :Op) :Op 188 | ge_s(a :Op, b :Op) :Op 189 | ge_u(a :Op, b :Op) :Op 190 | 191 | // Numeric 192 | clz (a :Op) :Op 193 | ctz (a :Op) :Op 194 | popcnt(a :Op) :Op 195 | add (a :Op, b :Op) :Op 196 | sub (a :Op, b :Op) :Op 197 | mul (a :Op, b :Op) :Op 198 | div_s (a :Op, b :Op) :Op 199 | div_u (a :Op, b :Op) :Op 200 | rem_s (a :Op, b :Op) :Op 201 | rem_u (a :Op, b :Op) :Op 202 | and (a :Op, b :Op) :Op 203 | or (a :Op, b :Op) :Op 204 | xor (a :Op, b :Op) :Op 205 | shl (a :Op, b :Op) :Op 206 | shr_s (a :Op, b :Op) :Op 207 | shr_u (a :Op, b :Op) :Op 208 | rotl (a :Op, b :Op) :Op 209 | rotr (a :Op, b :Op) :Op 210 | 211 | // Conversion 212 | trunc_s_f32(a :Op) :Op 213 | trunc_u_f32(a :Op) :Op 214 | trunc_s_f64(a :Op) :Op 215 | trunc_u_f64(a :Op) :Op 216 | } 217 | 218 | export interface FloatOps extends NumOps { 219 | // Comparison 220 | eq(a :Op, b :Op) :Op 221 | ne(a :Op, b :Op) :Op 222 | lt(a :Op, b :Op) :Op 223 | gt(a :Op, b :Op) :Op 224 | le(a :Op, b :Op) :Op 225 | ge(a :Op, b :Op) :Op 226 | 227 | // Numeric 228 | add (a :Op, b :Op) :Op 229 | sub (a :Op, b :Op) :Op 230 | mul (a :Op, b :Op) :Op 231 | abs (a :Op) :Op 232 | neg (a :Op) :Op 233 | ceil (a :Op) :Op 234 | floor (a :Op) :Op 235 | trunc (a :Op) :Op 236 | nearest (a :Op) :Op 237 | sqrt (a :Op) :Op 238 | div (a :Op, b :Op) :Op 239 | min (a :Op, b :Op) :Op 240 | max (a :Op, b :Op) :Op 241 | copysign(a :Op, b :Op) :Op 242 | 243 | // Conversion 244 | convert_s_i32(a :Op) :Op 245 | convert_u_i32(a :Op) :Op 246 | convert_s_i64(a :Op) :Op 247 | convert_u_i64(a :Op) :Op 248 | } 249 | 250 | export interface I32ops extends I32, IntOps { 251 | constv(v :VarInt32) :Op 252 | const(v :int32) :Op 253 | wrap_i64(a :Op) :Op 254 | reinterpret_f32(a :Op) :Op 255 | } 256 | 257 | export interface I64ops extends I64, IntOps { 258 | constv(v :VarInt64) :Op 259 | const(v :int64) :Op 260 | load32_s(mi :MemImm, addr :Op) :Op 261 | load32_u(mi :MemImm, addr :Op) :Op 262 | store32(mi :MemImm, addr :Op, v :Op) :Op 263 | extend_s_i32(a :Op) :Op 264 | extend_u_i32(a :Op) :Op 265 | reinterpret_f64(a :Op) :Op 266 | } 267 | 268 | export interface F32ops extends F32, FloatOps { 269 | constv(v :Float32) :Op 270 | const(v :float32) :Op 271 | demote_f64(a :Op) :Op 272 | reinterpret_i32(a :Op) :Op 273 | } 274 | 275 | export interface F64ops extends F64, FloatOps { 276 | constv(v :Float64) :Op 277 | const(v :float64) :Op 278 | promote_f32(a :Op) :Op 279 | reinterpret_i64(a :Op) :Op 280 | } 281 | 282 | //—————————————————————————————————————————————————————————————————————————————— 283 | // Type tags 284 | 285 | const T = { 286 | // Atoms 287 | uint8: Symbol('u8'), 288 | uint16: Symbol('u16'), 289 | uint32: Symbol('u32'), 290 | varuint1: Symbol('vu1'), 291 | varuint7: Symbol('vu7'), 292 | varuint32: Symbol('vu32'), 293 | varint7: Symbol('vs7'), 294 | varint32: Symbol('vs32'), 295 | varint64: Symbol('vs64'), 296 | float32: Symbol('f32'), // non-standard 297 | float64: Symbol('f64'), // non-standard 298 | data: Symbol('data'), // non-standard 299 | type: Symbol('type'), // non-standard, signifies a varint7 type constant 300 | external_kind: Symbol('type'), 301 | 302 | // Instructions 303 | instr: Symbol('instr'), // non-standard 304 | instr_pre: Symbol('instr_pre'), // non-standard 305 | instr_pre1: Symbol('instr_pre1'), // non-standard 306 | instr_imm1: Symbol('instr_imm1'), // non-standard 307 | instr_imm1_post: Symbol('instr_imm1_post'), // non-standard 308 | instr_pre_imm: Symbol('instr_pre_imm'), // non-standard 309 | instr_pre_imm_post: Symbol('instr_pre_imm_post'), // non-standard 310 | 311 | // Cells 312 | module: Symbol('module'), 313 | section: Symbol('section'), 314 | import_entry: Symbol('import_entry'), 315 | export_entry: Symbol('export_entry'), 316 | local_entry: Symbol('local_entry'), 317 | func_type: Symbol('func_type'), 318 | table_type: Symbol('table_type'), 319 | memory_type: Symbol('memory_type'), 320 | global_type: Symbol('global_type'), 321 | resizable_limits: Symbol('resizable_limits'), 322 | global_variable: Symbol('global_variable'), 323 | init_expr: Symbol('init_expr'), 324 | elem_segment: Symbol('elem_segment'), 325 | data_segment: Symbol('data_segment'), 326 | function_body: Symbol('function_body'), 327 | str: Symbol('str'), // non-standard 328 | } 329 | 330 | //—————————————————————————————————————————————————————————————————————————————— 331 | // node structs 332 | 333 | const writev = (e :Emitter, objs :Emittable[]) :Emitter => objs.reduce((e, n) => n.emit(e), e) 334 | 335 | const sumz = function(n :N[]) { 336 | let sum = 0 337 | for (let i = 0, L = n.length; i != L; ++i) { 338 | sum += n[i].z 339 | } 340 | return sum 341 | } 342 | 343 | const readVarInt7 = (byte :uint8) :int7 => 344 | byte < 64 ? byte : -(128 - byte) 345 | 346 | class bytes_atom implements Atom> { 347 | readonly t :TypeTag 348 | readonly z :uint32 349 | readonly v :ArrayLike 350 | 351 | constructor(t :TypeTag, v :ArrayLike) { 352 | this.t = t 353 | this.z = v.length 354 | this.v = v 355 | } 356 | 357 | emit(e :Emitter) { return e.writeBytes(this.v) } 358 | } 359 | 360 | class val_atom implements Atom { 361 | readonly t :TypeTag 362 | readonly z :uint32 363 | readonly v :T 364 | 365 | constructor(t :TypeTag, z :uint32, v :T) { this.t = t; this.z = z; this.v = v } 366 | emit(e :Emitter) { return e } // override in subclasses 367 | } 368 | 369 | class bytesval_atom extends val_atom { 370 | readonly bytes :ArrayLike 371 | constructor(t :TypeTag, v :T, bytes :ArrayLike) { 372 | super(t, bytes.length, v) 373 | this.bytes = bytes 374 | } 375 | emit(e :Emitter) { return e.writeBytes(this.bytes) } 376 | } 377 | 378 | class u32_atom extends val_atom { 379 | constructor(v :uint32) { super(T.uint32, 4, v) } 380 | emit(e :Emitter) { return e.writeU32(this.v) } 381 | } 382 | 383 | class f32_atom extends val_atom { 384 | constructor(v :number) { super(T.float32, 4, v) } 385 | emit(e :Emitter) { return e.writeF32(this.v) } 386 | } 387 | 388 | class f64_atom extends val_atom { 389 | constructor(v :number) { super(T.float64, 8, v) } 390 | emit(e :Emitter) { return e.writeF64(this.v) } 391 | } 392 | 393 | class u8_atom extends val_atom { 394 | constructor(t :TypeTag, v :T) { super(t, 1, v) } 395 | emit(e :Emitter) { return e.writeU8(this.v) } 396 | } 397 | 398 | class type_atom extends u8_atom { 399 | readonly b :uint8 400 | constructor(v :int7, b :uint8) { super(T.type, v); this.b = b } 401 | emit(e :Emitter) { return e.writeU8(this.b) } 402 | } 403 | 404 | class str_atom implements Atom> { 405 | readonly t :TypeTag 406 | readonly z :uint32 407 | readonly v :ArrayLike 408 | readonly len :VarUint32 409 | 410 | constructor(len: VarUint32, v :ArrayLike) { 411 | assert(len.v == v.length) 412 | this.t = T.str 413 | this.z = len.z + v.length 414 | this.v = v 415 | this.len = len 416 | } 417 | emit(e :Emitter) { return this.len.emit(e).writeBytes(this.v) } 418 | } 419 | 420 | class cell implements Cell { 421 | readonly t :TypeTag 422 | readonly z :uint32 423 | readonly v :T[] 424 | 425 | constructor(t :TypeTag, v :T[]) { 426 | this.t = t 427 | this.z = sumz(v) 428 | this.v = v 429 | } 430 | 431 | emit(e :Emitter) { return writev(e, this.v) } 432 | } 433 | 434 | //————————————————————————————————————————————— 435 | 436 | // Instructions 437 | 438 | class instr_atom extends u8_atom { 439 | readonly r :AnyResult 440 | constructor(v :uint8, r :AnyResult) { super(T.instr, v); this.r = r } 441 | } 442 | 443 | class instr_cell implements N { 444 | readonly t :TypeTag 445 | readonly z :uint32 446 | readonly v :OpCode 447 | readonly r :AnyResult 448 | 449 | constructor(t :TypeTag, op :uint8, r :AnyResult, z :uint32) { 450 | this.t = t 451 | this.z = z 452 | this.v = op 453 | this.r = r 454 | } 455 | emit(e :Emitter) { return e } 456 | } 457 | 458 | class instr_pre1 extends instr_cell { 459 | readonly pre :N 460 | constructor(op :uint8, r :AnyResult, pre :N) { 461 | super(T.instr_pre1, op, r, 1 + pre.z) 462 | this.pre = pre 463 | } 464 | emit(e :Emitter) { return this.pre.emit(e).writeU8(this.v) } 465 | } 466 | 467 | class instr_imm1 extends instr_cell { 468 | readonly imm :N 469 | constructor(op :uint8, r :AnyResult, imm :N) { 470 | super(T.instr_imm1, op, r, 1 + imm.z) 471 | this.imm = imm 472 | } 473 | emit(e :Emitter) { return this.imm.emit(e.writeU8(this.v)) } 474 | } 475 | 476 | class instr_pre extends instr_cell { 477 | readonly pre :N[] 478 | constructor(op :uint8, r :AnyResult, pre :N[]) { 479 | super(T.instr_pre, op, r, 1 + sumz(pre)) 480 | this.pre = pre 481 | } 482 | emit(e :Emitter) { return writev(e, this.pre).writeU8(this.v) } 483 | } 484 | 485 | class instr_imm1_post extends instr_cell { 486 | readonly imm :N 487 | readonly post :N[] 488 | constructor(op :uint8, r :AnyResult, imm :N, post :N[]) { 489 | super(T.instr_imm1_post, op, r, 1 + imm.z + sumz(post)) 490 | this.imm = imm 491 | this.post = post 492 | } 493 | emit(e :Emitter) { return writev(this.imm.emit(e.writeU8(this.v)), this.post) } 494 | } 495 | 496 | class instr_pre_imm extends instr_cell { 497 | readonly pre :N[] 498 | readonly imm :N[] 499 | constructor(op :uint8, r :AnyResult, pre :N[], imm :N[]) { 500 | super(T.instr_pre_imm, op, r, 1 + sumz(pre) + sumz(imm)) 501 | this.pre = pre 502 | this.imm = imm 503 | } 504 | emit(e :Emitter) { return writev(writev(e, this.pre).writeU8(this.v), this.imm) } 505 | } 506 | 507 | class instr_pre_imm_post extends instr_cell { 508 | readonly pre :N[] 509 | readonly imm :N[] 510 | readonly post :N[] 511 | constructor(op :uint8, r :AnyResult, pre :N[], imm :N[], post :N[]) { 512 | super(T.instr_pre_imm_post, op, r, 1 + sumz(pre) + sumz(imm) + sumz(post)) 513 | this.pre = pre 514 | this.imm = imm 515 | this.post = post 516 | } 517 | emit(e :Emitter) { 518 | return writev(writev(writev(e, this.pre).writeU8(this.v), this.imm), this.post) 519 | } 520 | } 521 | 522 | function maprange(start:number, stop:number, fn:(v:number)=>R|undefined) :Array { 523 | let a :R[] = [] 524 | while (start < stop) { 525 | let v :R = fn(start) 526 | if (v !== undefined) { 527 | a.push(v) 528 | } 529 | start += 1 530 | } 531 | return a 532 | } 533 | 534 | //—————————————————————————————————————————————————————————————————————————————— 535 | // constructors 536 | 537 | const uint8Cache :Uint8[] = maprange(0,16, v => 538 | new u8_atom(T.uint8, v as uint8)) 539 | const varUint7Cache :VarUint7[] = maprange(0,16, v => 540 | new u8_atom(T.varuint7, v as uint8)) 541 | const varUint32Cache :VarUint7[] = maprange(0,16, v => 542 | new u8_atom(T.varuint32, v as uint8)) 543 | const varuint1_0 = new u8_atom(T.varuint1, 0) as Atom 544 | const varuint1_1 = new u8_atom(T.varuint1, 1) as Atom 545 | 546 | function uint8(v :uint8) { 547 | return uint8Cache[v] || new u8_atom(T.uint8, v) as Uint8 548 | } 549 | function uint32(v :uint32) { return new u32_atom(v) as Uint32 } 550 | function float32(v :float32) { return new f32_atom(v) as Float32 } 551 | function float64(v :float64) { return new f64_atom(v) as Float64 } 552 | 553 | // LEB128-encoded variable-length integers: (N = bits) 554 | // unsigned range: [0, 2^N-1] 555 | // signed range: [-2^(N-1), +2^(N-1)-1] 556 | 557 | function varuint1(v :any) { 558 | return v ? varuint1_1 : varuint1_0 559 | } 560 | 561 | function varuint7(v :uint7) { 562 | assert(v >= 0 && v <= 128) 563 | return varUint7Cache[v] || new u8_atom(T.varuint7, v) as VarUint7 564 | } 565 | 566 | function varuint32(value :uint32) { 567 | const c = varUint32Cache[value] 568 | if (c) { return c } 569 | assert(value >= 0 && value <= 0xffffffff) 570 | 571 | let v = value 572 | const bytes :uint8[] = [] 573 | while (v >= 0x80) { 574 | bytes.push((v & 0x7f) | 0x80) 575 | v >>>= 7 576 | } 577 | bytes.push(v) 578 | 579 | return new bytesval_atom(T.varuint32, value, bytes) as VarUint32 580 | } 581 | 582 | function varint7(value :int7) { 583 | assert(value >= -64 && value <= 63); 584 | return new u8_atom(T.varint7, value < 0 ? (128 + value) : value) as VarInt7 585 | } 586 | 587 | function encVarIntN(v :int64) :uint8[] { 588 | // FIXME: broken for values larger than uint32 589 | const bytes :uint8[] = [] 590 | while (true) { 591 | let b = v & 0x7f 592 | if (-64 <= v && v < 64) { 593 | bytes.push(b) 594 | break 595 | } 596 | v >>= 7 // Note: sign-propagating right shift 597 | bytes.push(b | 0x80) 598 | } 599 | return bytes 600 | } 601 | 602 | function varint32(value :int32) :VarInt32 { 603 | assert(value >= -0x80000000 && value <= 0x7fffffff) 604 | return new bytesval_atom(T.varint32, value, encVarIntN(value)) as VarInt32 605 | } 606 | 607 | function varint64(value :int64) :VarInt64 { 608 | // Here be dragons! Not all negative 64bit numbers can be represented with 609 | // JavaScript numbers. The ECMAScript double type has 53 bits of integer 610 | // precision. We thus assert this range 611 | assert(value >= Number.MIN_SAFE_INTEGER && value <= Number.MAX_SAFE_INTEGER) 612 | return new bytesval_atom(T.varint64, value, encVarIntN(value)) as VarInt64 613 | } 614 | 615 | 616 | // Language types 617 | const AnyFunc = new type_atom(-0x10, 0x70) as any as AnyFunc 618 | const Func = new type_atom(-0x20, 0x60) as any as Func 619 | const EmptyBlock = new type_atom(-0x40, 0x40) as any as EmptyBlock 620 | const Void = EmptyBlock as any as Void 621 | 622 | const external_kind_function = new u8_atom(T.external_kind, 0) as any as ExternalKind 623 | const external_kind_table = new u8_atom(T.external_kind, 1) as any as ExternalKind 624 | const external_kind_memory = new u8_atom(T.external_kind, 2) as any as ExternalKind 625 | const external_kind_global = new u8_atom(T.external_kind, 3) as any as ExternalKind 626 | 627 | const str = (data: ArrayLike) => 628 | new str_atom(varuint32(data.length), data) as any as Str 629 | 630 | const sect_id_custom = varuint7(0) 631 | const sect_id_type = varuint7(1) 632 | const sect_id_import = varuint7(2) 633 | const sect_id_function = varuint7(3) 634 | const sect_id_table = varuint7(4) 635 | const sect_id_memory = varuint7(5) 636 | const sect_id_global = varuint7(6) 637 | const sect_id_export = varuint7(7) 638 | const sect_id_start = varuint7(8) 639 | const sect_id_element = varuint7(9) 640 | const sect_id_code = varuint7(10) 641 | const sect_id_data = varuint7(11) 642 | 643 | export const sect_id = { 644 | custom: sect_id_custom, 645 | type: sect_id_type, 646 | import: sect_id_import, 647 | function: sect_id_function, 648 | table: sect_id_table, 649 | memory: sect_id_memory, 650 | global: sect_id_global, 651 | export: sect_id_export, 652 | start: sect_id_start, 653 | element: sect_id_element, 654 | code: sect_id_code, 655 | data: sect_id_data, 656 | } 657 | 658 | function section(id :VarUint7, imm :N, payload :N[]) { 659 | return new cell(T.section, 660 | [id, varuint32(imm.z + sumz(payload)), imm, ...payload] 661 | ) 662 | } 663 | 664 | 665 | const memload = (op :OpCode, r :R, mi :MemImm, addr :Op) => 666 | new instr_pre_imm(op, r, [addr], mi) as any as Op 667 | 668 | const memstore = (op :OpCode, mi :MemImm, addr :Op, v :Op) => 669 | new instr_pre_imm(op, Void, [addr, v], mi) as any as Op 670 | 671 | // memAddrIsAligned returns true if the memory operation will actually be aligned. 672 | // Note: natAl and al should be encoded as log2(bits), i.e. 32bit = 2 673 | const addrIsAligned = (natAl :uint32, al :uint32, offs :number, addr :number) => 674 | al <= natAl && 675 | ((addr + offs) % [1, 2, 4, 8][al]) == 0 676 | 677 | 678 | class i32ops extends type_atom implements I32ops { readonly _I32: undefined; 679 | // Constants 680 | constv(v :VarInt32) { return new instr_imm1(0x41, this, v) as any as Op } 681 | const(v :int32) :Op { return this.constv(varint32(v)) } 682 | 683 | // Memory 684 | load(mi :MemImm, addr :Op) { return memload(0x28, this, mi, addr) } 685 | load8_s(mi :MemImm, addr :Op) { return memload(0x2c, this, mi, addr) } 686 | load8_u(mi :MemImm, addr :Op) { return memload(0x2d, this, mi, addr) } 687 | load16_s(mi :MemImm, addr :Op) { return memload(0x2e, this, mi, addr) } 688 | load16_u(mi :MemImm, addr :Op) { return memload(0x2f, this, mi, addr) } 689 | store(mi :MemImm, addr :Op, v :Op) { return memstore(0x36, mi, addr, v) } 690 | store8(mi :MemImm, addr :Op, v :Op) { return memstore(0x3a, mi, addr, v) } 691 | store16(mi :MemImm, addr :Op, v :Op) { return memstore(0x3b, mi, addr, v) } 692 | addrIsAligned(mi :MemImm, addr :number) { return addrIsAligned(2, mi[0].v, mi[1].v, addr) } 693 | 694 | // Comparison 695 | eqz (a :Op) { return new instr_pre1(0x45,this,a) as any as Op } 696 | eq (a :Op, b :Op) { return new instr_pre(0x46,this,[a,b]) as any as Op } 697 | ne (a :Op, b :Op) { return new instr_pre(0x47,this,[a,b]) as any as Op } 698 | lt_s(a :Op, b :Op) { return new instr_pre(0x48,this,[a,b]) as any as Op } 699 | lt_u(a :Op, b :Op) { return new instr_pre(0x49,this,[a,b]) as any as Op } 700 | gt_s(a :Op, b :Op) { return new instr_pre(0x4a,this,[a,b]) as any as Op } 701 | gt_u(a :Op, b :Op) { return new instr_pre(0x4b,this,[a,b]) as any as Op } 702 | le_s(a :Op, b :Op) { return new instr_pre(0x4c,this,[a,b]) as any as Op } 703 | le_u(a :Op, b :Op) { return new instr_pre(0x4d,this,[a,b]) as any as Op } 704 | ge_s(a :Op, b :Op) { return new instr_pre(0x4e,this,[a,b]) as any as Op } 705 | ge_u(a :Op, b :Op) { return new instr_pre(0x4f,this,[a,b]) as any as Op } 706 | 707 | // Numeric 708 | clz (a :Op) { return new instr_pre1(0x67,this,a) as any as Op } 709 | ctz (a :Op) { return new instr_pre1(0x68,this,a) as any as Op } 710 | popcnt(a :Op) { return new instr_pre1(0x69,this,a) as any as Op } 711 | add (a :Op, b :Op) { return new instr_pre(0x6a,this,[a,b]) as any as Op } 712 | sub (a :Op, b :Op) { return new instr_pre(0x6b,this,[a,b]) as any as Op } 713 | mul (a :Op, b :Op) { return new instr_pre(0x6c,this,[a,b]) as any as Op } 714 | div_s (a :Op, b :Op) { return new instr_pre(0x6d,this,[a,b]) as any as Op } 715 | div_u (a :Op, b :Op) { return new instr_pre(0x6e,this,[a,b]) as any as Op } 716 | rem_s (a :Op, b :Op) { return new instr_pre(0x6f,this,[a,b]) as any as Op } 717 | rem_u (a :Op, b :Op) { return new instr_pre(0x70,this,[a,b]) as any as Op } 718 | and (a :Op, b :Op) { return new instr_pre(0x71,this,[a,b]) as any as Op } 719 | or (a :Op, b :Op) { return new instr_pre(0x72,this,[a,b]) as any as Op } 720 | xor (a :Op, b :Op) { return new instr_pre(0x73,this,[a,b]) as any as Op } 721 | shl (a :Op, b :Op) { return new instr_pre(0x74,this,[a,b]) as any as Op } 722 | shr_s (a :Op, b :Op) { return new instr_pre(0x75,this,[a,b]) as any as Op } 723 | shr_u (a :Op, b :Op) { return new instr_pre(0x76,this,[a,b]) as any as Op } 724 | rotl (a :Op, b :Op) { return new instr_pre(0x77,this,[a,b]) as any as Op } 725 | rotr (a :Op, b :Op) { return new instr_pre(0x78,this,[a,b]) as any as Op } 726 | 727 | // Conversion 728 | wrap_i64 (a :Op) { return new instr_pre1(0xa7,this,a) as any as Op } 729 | trunc_s_f32 (a :Op) { return new instr_pre1(0xa8,this,a) as any as Op } 730 | trunc_u_f32 (a :Op) { return new instr_pre1(0xa9,this,a) as any as Op } 731 | trunc_s_f64 (a :Op) { return new instr_pre1(0xaa,this,a) as any as Op } 732 | trunc_u_f64 (a :Op) { return new instr_pre1(0xab,this,a) as any as Op } 733 | reinterpret_f32 (a :Op) { return new instr_pre1(0xbc,this,a) as any as Op } 734 | } 735 | 736 | class i64ops extends type_atom implements I64ops { readonly _I64: undefined 737 | // Constants 738 | constv(v :VarInt64) { return new instr_imm1(0x42, this, v) as any as Op } 739 | const(v :int64) :Op { return this.constv(varint64(v)) } 740 | 741 | // Memory 742 | load(mi :MemImm, addr :Op) { return memload(0x29, this, mi, addr) } 743 | load8_s(mi :MemImm, addr :Op) { return memload(0x30, this, mi, addr) } 744 | load8_u(mi :MemImm, addr :Op) { return memload(0x31, this, mi, addr) } 745 | load16_s(mi :MemImm, addr :Op) { return memload(0x32, this, mi, addr) } 746 | load16_u(mi :MemImm, addr :Op) { return memload(0x33, this, mi, addr) } 747 | load32_s(mi :MemImm, addr :Op) { return memload(0x34, this, mi, addr) } 748 | load32_u(mi :MemImm, addr :Op) { return memload(0x35, this, mi, addr) } 749 | store(mi :MemImm, addr :Op, v :Op) { return memstore(0x37, mi, addr, v) } 750 | store8 (mi :MemImm, addr :Op, v :Op) { return memstore(0x3c, mi, addr, v) } 751 | store16(mi :MemImm, addr :Op, v :Op) { return memstore(0x3d, mi, addr, v) } 752 | store32(mi :MemImm, addr :Op, v :Op) { return memstore(0x3e, mi, addr, v) } 753 | addrIsAligned(mi :MemImm, addr :number) { return addrIsAligned(3, mi[0].v, mi[1].v, addr) } 754 | 755 | // Comparison 756 | eqz (a :Op) { return new instr_pre1(0x50,this,a) as any as Op } 757 | eq (a :Op, b :Op) { return new instr_pre(0x51,this,[a,b]) as any as Op } 758 | ne (a :Op, b :Op) { return new instr_pre(0x52,this,[a,b]) as any as Op } 759 | lt_s(a :Op, b :Op) { return new instr_pre(0x53,this,[a,b]) as any as Op } 760 | lt_u(a :Op, b :Op) { return new instr_pre(0x54,this,[a,b]) as any as Op } 761 | gt_s(a :Op, b :Op) { return new instr_pre(0x55,this,[a,b]) as any as Op } 762 | gt_u(a :Op, b :Op) { return new instr_pre(0x56,this,[a,b]) as any as Op } 763 | le_s(a :Op, b :Op) { return new instr_pre(0x57,this,[a,b]) as any as Op } 764 | le_u(a :Op, b :Op) { return new instr_pre(0x58,this,[a,b]) as any as Op } 765 | ge_s(a :Op, b :Op) { return new instr_pre(0x59,this,[a,b]) as any as Op } 766 | ge_u(a :Op, b :Op) { return new instr_pre(0x5a,this,[a,b]) as any as Op } 767 | 768 | // Numeric 769 | clz (a :Op) { return new instr_pre1(0x79,this,a) as any as Op } 770 | ctz (a :Op) { return new instr_pre1(0x7a,this,a) as any as Op } 771 | popcnt(a :Op) { return new instr_pre1(0x7b,this,a) as any as Op } 772 | add (a :Op, b :Op) { return new instr_pre(0x7c,this,[a,b]) as any as Op } 773 | sub (a :Op, b :Op) { return new instr_pre(0x7d,this,[a,b]) as any as Op } 774 | mul (a :Op, b :Op) { return new instr_pre(0x7e,this,[a,b]) as any as Op } 775 | div_s (a :Op, b :Op) { return new instr_pre(0x7f,this,[a,b]) as any as Op } 776 | div_u (a :Op, b :Op) { return new instr_pre(0x80,this,[a,b]) as any as Op } 777 | rem_s (a :Op, b :Op) { return new instr_pre(0x81,this,[a,b]) as any as Op } 778 | rem_u (a :Op, b :Op) { return new instr_pre(0x82,this,[a,b]) as any as Op } 779 | and (a :Op, b :Op) { return new instr_pre(0x83,this,[a,b]) as any as Op } 780 | or (a :Op, b :Op) { return new instr_pre(0x84,this,[a,b]) as any as Op } 781 | xor (a :Op, b :Op) { return new instr_pre(0x85,this,[a,b]) as any as Op } 782 | shl (a :Op, b :Op) { return new instr_pre(0x86,this,[a,b]) as any as Op } 783 | shr_s (a :Op, b :Op) { return new instr_pre(0x87,this,[a,b]) as any as Op } 784 | shr_u (a :Op, b :Op) { return new instr_pre(0x88,this,[a,b]) as any as Op } 785 | rotl (a :Op, b :Op) { return new instr_pre(0x89,this,[a,b]) as any as Op } 786 | rotr (a :Op, b :Op) { return new instr_pre(0x8a,this,[a,b]) as any as Op } 787 | 788 | // Conversions 789 | extend_s_i32 (a :Op) { return new instr_pre1(0xac,this,a) as any as Op } 790 | extend_u_i32 (a :Op) { return new instr_pre1(0xad,this,a) as any as Op } 791 | trunc_s_f32 (a :Op) { return new instr_pre1(0xae,this,a) as any as Op } 792 | trunc_u_f32 (a :Op) { return new instr_pre1(0xaf,this,a) as any as Op } 793 | trunc_s_f64 (a :Op) { return new instr_pre1(0xb0,this,a) as any as Op } 794 | trunc_u_f64 (a :Op) { return new instr_pre1(0xb1,this,a) as any as Op } 795 | reinterpret_f64 (a :Op) { return new instr_pre1(0xbd,this,a) as any as Op } 796 | } 797 | 798 | 799 | class f32ops extends type_atom implements F32ops { readonly _F32: undefined; 800 | // Constants 801 | constv(v :Float32) { return new instr_imm1(0x43, this, v) as any as Op } 802 | const(v :float32) :Op { return this.constv(float32(v)) } 803 | 804 | // Memory 805 | load(mi :MemImm, addr :Op) { return memload(0x2a, this, mi, addr) } 806 | store(mi :MemImm, addr :Op, v :Op) { return memstore(0x38, mi, addr, v) } 807 | addrIsAligned(mi :MemImm, addr :number) { return addrIsAligned(2, mi[0].v, mi[1].v, addr) } 808 | 809 | // Comparison 810 | eq(a :Op, b :Op) { return new instr_pre(0x5b,this,[a,b]) as any as Op } 811 | ne(a :Op, b :Op) { return new instr_pre(0x5c,this,[a,b]) as any as Op } 812 | lt(a :Op, b :Op) { return new instr_pre(0x5d,this,[a,b]) as any as Op } 813 | gt(a :Op, b :Op) { return new instr_pre(0x5e,this,[a,b]) as any as Op } 814 | le(a :Op, b :Op) { return new instr_pre(0x5f,this,[a,b]) as any as Op } 815 | ge(a :Op, b :Op) { return new instr_pre(0x60,this,[a,b]) as any as Op } 816 | 817 | // Numeric 818 | abs (a :Op) { return new instr_pre1(0x8b,this,a) as any as Op } 819 | neg (a :Op) { return new instr_pre1(0x8c,this,a) as any as Op } 820 | ceil (a :Op) { return new instr_pre1(0x8d,this,a) as any as Op } 821 | floor (a :Op) { return new instr_pre1(0x8e,this,a) as any as Op } 822 | trunc (a :Op) { return new instr_pre1(0x8f,this,a) as any as Op } 823 | nearest (a :Op) { return new instr_pre1(0x90,this,a) as any as Op } 824 | sqrt (a :Op) { return new instr_pre1(0x91,this,a) as any as Op } 825 | add (a :Op, b :Op) { return new instr_pre(0x92,this,[a,b]) as any as Op } 826 | sub (a :Op, b :Op) { return new instr_pre(0x93,this,[a,b]) as any as Op } 827 | mul (a :Op, b :Op) { return new instr_pre(0x94,this,[a,b]) as any as Op } 828 | div (a :Op, b :Op) { return new instr_pre(0x95,this,[a,b]) as any as Op } 829 | min (a :Op, b :Op) { return new instr_pre(0x96,this,[a,b]) as any as Op } 830 | max (a :Op, b :Op) { return new instr_pre(0x97,this,[a,b]) as any as Op } 831 | copysign(a :Op, b :Op) { return new instr_pre(0x98,this,[a,b]) as any as Op } 832 | 833 | // Conversion 834 | convert_s_i32 (a :Op) { return new instr_pre1(0xb2,this,a) as any as Op } 835 | convert_u_i32 (a :Op) { return new instr_pre1(0xb3,this,a) as any as Op } 836 | convert_s_i64 (a :Op) { return new instr_pre1(0xb4,this,a) as any as Op } 837 | convert_u_i64 (a :Op) { return new instr_pre1(0xb5,this,a) as any as Op } 838 | demote_f64 (a :Op) { return new instr_pre1(0xb6,this,a) as any as Op } 839 | reinterpret_i32(a :Op) { return new instr_pre1(0xbe,this,a) as any as Op } 840 | } 841 | 842 | class f64ops extends type_atom implements F64ops { readonly _F64: undefined; 843 | // Constants 844 | constv(v :Float64) { return new instr_imm1(0x44, this, v) as any as Op } 845 | const(v :float64) :Op { return this.constv(float64(v)) } 846 | 847 | // Memory 848 | load(mi :MemImm, addr :Op) { return memload(0x2b, this, mi, addr) } 849 | store(mi :MemImm, addr :Op, v :Op) { return memstore(0x39, mi, addr, v) } 850 | addrIsAligned(mi :MemImm, addr :number) { return addrIsAligned(3, mi[0].v, mi[1].v, addr) } 851 | 852 | // Comparison 853 | eq(a :Op, b :Op) { return new instr_pre(0x61,this,[a,b]) as any as Op } 854 | ne(a :Op, b :Op) { return new instr_pre(0x62,this,[a,b]) as any as Op } 855 | lt(a :Op, b :Op) { return new instr_pre(0x63,this,[a,b]) as any as Op } 856 | gt(a :Op, b :Op) { return new instr_pre(0x64,this,[a,b]) as any as Op } 857 | le(a :Op, b :Op) { return new instr_pre(0x65,this,[a,b]) as any as Op } 858 | ge(a :Op, b :Op) { return new instr_pre(0x66,this,[a,b]) as any as Op } 859 | 860 | // Numeric 861 | abs (a :Op) { return new instr_pre1(0x99,this,a) as any as Op } 862 | neg (a :Op) { return new instr_pre1(0x9a,this,a) as any as Op } 863 | ceil (a :Op) { return new instr_pre1(0x9b,this,a) as any as Op } 864 | floor (a :Op) { return new instr_pre1(0x9c,this,a) as any as Op } 865 | trunc (a :Op) { return new instr_pre1(0x9d,this,a) as any as Op } 866 | nearest (a :Op) { return new instr_pre1(0x9e,this,a) as any as Op } 867 | sqrt (a :Op) { return new instr_pre1(0x9f,this,a) as any as Op } 868 | add (a :Op, b :Op) { return new instr_pre(0xa0,this,[a,b]) as any as Op } 869 | sub (a :Op, b :Op) { return new instr_pre(0xa1,this,[a,b]) as any as Op } 870 | mul (a :Op, b :Op) { return new instr_pre(0xa2,this,[a,b]) as any as Op } 871 | div (a :Op, b :Op) { return new instr_pre(0xa3,this,[a,b]) as any as Op } 872 | min (a :Op, b :Op) { return new instr_pre(0xa4,this,[a,b]) as any as Op } 873 | max (a :Op, b :Op) { return new instr_pre(0xa5,this,[a,b]) as any as Op } 874 | copysign(a :Op, b :Op) { return new instr_pre(0xa6,this,[a,b]) as any as Op } 875 | 876 | // Conversions 877 | convert_s_i32 (a :Op) { return new instr_pre1(0xb7,this,a) as any as Op } 878 | convert_u_i32 (a :Op) { return new instr_pre1(0xb8,this,a) as any as Op } 879 | convert_s_i64 (a :Op) { return new instr_pre1(0xb9,this,a) as any as Op } 880 | convert_u_i64 (a :Op) { return new instr_pre1(0xba,this,a) as any as Op } 881 | promote_f32 (a :Op) { return new instr_pre1(0xbb,this,a) as any as Op } 882 | reinterpret_i64(a :Op) { return new instr_pre1(0xbf,this,a) as any as Op } 883 | } 884 | 885 | const magic = uint32(0x6d736100) 886 | const latestVersion = uint32(0x1) 887 | const end = new instr_atom(0x0b, Void) as any as Op 888 | const elseOp = new instr_atom(0x05, Void) as any as Op 889 | 890 | function if_(r :R, cond :Op, then_ :AnyOp[], else_? :AnyOp[]) { 891 | assert(r === then_[then_.length-1].r) 892 | assert(!else_ || else_.length == 0 || r === else_[else_.length-1].r) 893 | return new instr_pre_imm_post(0x04, r, 894 | [cond], // pre 895 | [r], // imm 896 | // post: 897 | else_ ? [...then_, elseOp, ...else_, end] : 898 | [...then_, end] 899 | ) as any as Op 900 | } 901 | 902 | const return_ = (value :Op) => 903 | new instr_pre1(0x0f, value.r, value) as any as Op 904 | 905 | export const t = T 906 | 907 | export const c = { 908 | uint8, 909 | uint32, 910 | float32, 911 | float64, 912 | varuint1, 913 | varuint7, 914 | varuint32, 915 | varint7, 916 | varint32, 917 | varint64, 918 | 919 | any_func: AnyFunc, 920 | func: Func, 921 | empty_block: EmptyBlock, 922 | void: Void, void_: Void, 923 | 924 | external_kind: { 925 | function: external_kind_function, // Function import or definition 926 | table: external_kind_table, // Table import or definition 927 | memory: external_kind_memory, // Memory import or definition 928 | global: external_kind_global, // Global import or definition 929 | }, 930 | 931 | data(buf: ArrayLike) { 932 | return new bytes_atom(T.data, buf) as any as Data 933 | }, 934 | 935 | str, 936 | 937 | str_ascii(text: string) { 938 | const bytes :uint8[] = [] 939 | for (let i = 0, L = text.length; i != L; ++i) { 940 | bytes[i] = 0xff & text.charCodeAt(i); 941 | } 942 | return str(bytes) 943 | }, 944 | 945 | str_utf8: (text: string) => 946 | str(utf8.encode(text)), 947 | 948 | // If you are targeting a pre-MVP version, provide the desired version number (e.g. `0xd`). 949 | // If not provided or falsy, the latest stable version is used. 950 | module(sections :Section[], version? :uint32) { 951 | const v = version ? uint32(version) : latestVersion 952 | return new cell
(T.module, 953 | [magic, v, ...sections] as Section[]) as any as Module 954 | }, 955 | 956 | custom_section: (name :Str, payload :N[]) => 957 | section(sect_id_custom, name, payload) as any as CustomSection, 958 | 959 | type_section: (types: FuncType[]) => 960 | section(sect_id_type, varuint32(types.length), types) as any as TypeSection, 961 | 962 | import_section: (entries: ImportEntry[]) => 963 | section(sect_id_import, varuint32(entries.length), entries) as any as ImportSection, 964 | 965 | function_section: (types: VarUint32[]) => 966 | section(sect_id_function, varuint32(types.length), types) as any as FunctionSection, 967 | 968 | table_section: (types: TableType[]) => 969 | section(sect_id_table, varuint32(types.length), types) as any as TableSection, 970 | 971 | memory_section: (limits: ResizableLimits[]) => 972 | section(sect_id_memory, varuint32(limits.length), limits) as any as MemorySection, 973 | 974 | global_section: (globals: GlobalVariable[]) => 975 | section(sect_id_global, varuint32(globals.length), globals) as any as GlobalSection, 976 | 977 | export_section: (exports: ExportEntry[]) => 978 | section(sect_id_export, varuint32(exports.length), exports) as any as ExportSection, 979 | 980 | start_section: (funcIndex: VarUint32) => 981 | section(sect_id_start, funcIndex, []) as any as StartSection, 982 | 983 | element_section: (entries: ElemSegment[]) => 984 | section(sect_id_element, varuint32(entries.length), entries) as any as ElementSection, 985 | 986 | code_section: (bodies: FunctionBody[]) => 987 | section(sect_id_code, varuint32(bodies.length), bodies) as any as CodeSection, 988 | 989 | data_section: (entries: DataSegment[]) => 990 | section(sect_id_data, varuint32(entries.length), entries) as any as DataSection, 991 | 992 | 993 | function_import_entry: (module :Str, field :Str, typeIndex: VarUint32) => 994 | new cell(T.import_entry, [ 995 | module, field, external_kind_function, typeIndex 996 | ]) as any as ImportEntry, 997 | 998 | table_import_entry: (module :Str, field :Str, type: TableType) => 999 | new cell(T.import_entry, 1000 | [module, field, external_kind_table, type]) as any as ImportEntry, 1001 | 1002 | memory_import_entry: (module :Str, field :Str, limits: ResizableLimits) => 1003 | new cell(T.import_entry, 1004 | [module, field, external_kind_memory, limits]) as any as ImportEntry, 1005 | 1006 | global_import_entry: (module :Str, field :Str, type: GlobalType) => 1007 | new cell(T.import_entry, 1008 | [module, field, external_kind_global, type]) as any as ImportEntry, 1009 | 1010 | 1011 | export_entry: (field :Str, kind :ExternalKind, index :VarUint32) => 1012 | new cell(T.export_entry, [field, kind, index]) as any as ExportEntry, 1013 | 1014 | 1015 | elem_segment: (index :VarUint32, offset :InitExpr, funcIndex :VarUint32[]) => 1016 | new cell(T.elem_segment, [ 1017 | index, offset, varuint32(funcIndex.length), ...funcIndex 1018 | ]) as any as ElemSegment, 1019 | 1020 | data_segment: (index :VarUint32, offset :InitExpr, data :Data) => 1021 | new cell(T.data_segment, [index, offset, varuint32(data.z), data]) as any as DataSegment, 1022 | 1023 | 1024 | func_type(paramTypes :ValueType[], returnType? :ValueType|null) { 1025 | const paramLen = varuint32(paramTypes.length) 1026 | return new cell(T.func_type, 1027 | returnType ? [Func, paramLen, ...paramTypes, varuint1_1, returnType] 1028 | : [Func, paramLen, ...paramTypes, varuint1_0]) as any as FuncType 1029 | }, 1030 | 1031 | table_type(type :ElemType, limits :ResizableLimits) { 1032 | assert(type.v == AnyFunc.v) // WASM MVP limitation 1033 | return new cell(T.table_type, [type, limits]) as any as TableType 1034 | }, 1035 | 1036 | global_type: (contentType :ValueType, mutable? :boolean) => 1037 | new cell(T.global_type, [ 1038 | contentType, mutable ? varuint1_1 : varuint1_0 1039 | ]) as any as GlobalType, 1040 | 1041 | 1042 | // expressed in number of memory pages (not bytes; pagesize=64KiB) 1043 | resizable_limits: (initial :VarUint32, maximum? :VarUint32) => 1044 | new cell(T.resizable_limits, maximum ? 1045 | [varuint1_1, initial, maximum] : [varuint1_0, initial] 1046 | ) as any as ResizableLimits, 1047 | 1048 | global_variable: (type :GlobalType, init :InitExpr) => 1049 | new cell(T.global_variable, [type, init]) as any as GlobalVariable, 1050 | 1051 | init_expr: (expr :N[]) => 1052 | new cell(T.init_expr, [...expr, end]) as any as InitExpr, 1053 | 1054 | function_body(locals :LocalEntry[], code :N[]) { 1055 | const localCount = varuint32(locals.length) 1056 | return new cell(T.function_body, [ 1057 | varuint32(localCount.z + sumz(locals) + sumz(code) + 1), // body_size 1058 | localCount, ...locals, ...code, end 1059 | ]) as any as FunctionBody 1060 | }, 1061 | 1062 | local_entry: (count :VarUint32, type :ValueType) => 1063 | new cell(T.local_entry, [count, type]) as any as LocalEntry, 1064 | 1065 | // Semantics of the WebAssembly stack machine: 1066 | // - Control instructions pop their argument value(s) off the stack, may change 1067 | // the program counter, and push result value(s) onto the stack. 1068 | // - Simple instructions pop their argument value(s) from the stack, apply an 1069 | // operator to the values, and then push the result value(s) onto the stack, 1070 | // followed by an implicit advancement of the program counter. 1071 | 1072 | unreachable: new instr_atom(0x00, Void) as any as Op, 1073 | nop: new instr_atom(0x01, Void) as any as Op, 1074 | 1075 | // begin a block which can also form CF loops 1076 | block(r :R, body :AnyOp[]) { 1077 | assert(r === body[body.length-1].r) 1078 | return new instr_imm1_post(0x02, r, r as N, [...body, end]) as any as Op 1079 | }, 1080 | 1081 | void_block(body :AnyOp[]) { 1082 | assert(body.length == 0 || Void === body[body.length-1].r) 1083 | return new instr_imm1_post(0x02, Void, EmptyBlock, [...body, end]) as any as Op 1084 | }, 1085 | 1086 | 1087 | // begin a block which can also form CF loops 1088 | loop(r :R, body :AnyOp[]) { 1089 | assert(r === body[body.length-1].r) 1090 | return new instr_imm1_post(0x03, r, r as N, [...body, end]) as any as Op 1091 | }, 1092 | 1093 | void_loop(body :AnyOp[]) { 1094 | assert(body.length == 0 || Void === body[body.length-1].r) 1095 | return new instr_imm1_post(0x03, Void, EmptyBlock, [...body, end]) as any as Op 1096 | }, 1097 | 1098 | if: if_, if_, 1099 | end: end, 1100 | 1101 | // Branch to a given label (relative depth) in an enclosing construct. 1102 | // Note: 1103 | // - "branching" to a block correspond to a "break" statement 1104 | // - "branching" to a loop correspond to a "continue" statement 1105 | br: (relDepth :uint32) => 1106 | new instr_imm1(0x0c, Void, varuint32(relDepth)) as any as Op, 1107 | 1108 | // Conditionall branch to a given label in an enclosing construct. 1109 | // When condition is false, this is equivalent to a "Nop" operation. 1110 | // When condition is true, this is equivalent to a "Br" operation. 1111 | br_if: (relDepth :uint32, cond :Op) => 1112 | new instr_pre_imm(0x0d, Void, [cond], [varuint32(relDepth)]) as any as Op, 1113 | 1114 | // Jump table which jumps to a label in an enclosing construct. 1115 | // A br_table consists of a zero-based array of labels, a default label, 1116 | // and an index operand. A br_table jumps to the label indexed in the 1117 | // array or the default label if the index is out of bounds. 1118 | br_table: (targetLabels :VarUint32[], defaultLabel :VarUint32, index :AnyOp) => 1119 | new instr_pre_imm(0x0e, Void, 1120 | [index], 1121 | [varuint32(targetLabels.length), ...targetLabels, defaultLabel] 1122 | ) as any as Op, 1123 | 1124 | // return zero or one value from this function 1125 | return: return_, return_, 1126 | return_void: new instr_atom(0x0f, Void) as Op, 1127 | 1128 | // Calling 1129 | call(r :R, funcIndex: VarUint32, args :AnyOp[]) { 1130 | return new instr_pre_imm(0x10, r, args, [funcIndex]) as any as Op 1131 | }, 1132 | 1133 | call_indirect(r :R, funcIndex: VarUint32, args :AnyOp[]) { 1134 | return new instr_pre_imm(0x11, r, args, [funcIndex, varuint1_0]) as any as Op 1135 | }, 1136 | 1137 | // drop discards the value of its operand 1138 | // R should be the value on the stack "under" the operand. E.g. with a stack: 1139 | // I32 top 1140 | // F64 ... 1141 | // F32 bottom 1142 | // drop 1143 | // F64 top 1144 | // F32 bottom 1145 | // i.e. R=F64 1146 | drop(r :R, n :Op) { 1147 | return new instr_pre1(0x1a, r, n) as any as Op 1148 | }, 1149 | 1150 | // select one of two values based on condition 1151 | select(cond :Op, trueRes :Op, falseRes :Op) { 1152 | assert(trueRes.r === falseRes.r) 1153 | return new instr_pre(0x1b, trueRes.r, [trueRes, falseRes, cond]) as any as Op 1154 | }, 1155 | 1156 | // Variable access 1157 | get_local(r :R, localIndex :uint32) { 1158 | return new instr_imm1(0x20, r, varuint32(localIndex)) as any as Op 1159 | }, 1160 | 1161 | set_local(localIndex :uint32, expr :Op) { 1162 | return new instr_pre_imm(0x21, Void, [expr], [varuint32(localIndex)]) as any as Op 1163 | }, 1164 | 1165 | tee_local(localIndex :uint32, expr :Op) { 1166 | return new instr_pre_imm(0x22, expr.r, [expr], [varuint32(localIndex)]) as any as Op 1167 | }, 1168 | 1169 | get_global(r :R, globalIndex :uint32) { 1170 | return new instr_imm1(0x23, r, varuint32(globalIndex)) as any as Op 1171 | }, 1172 | 1173 | set_global(globalIndex :uint32, expr :Op) { 1174 | return new instr_pre_imm(0x24, Void, [expr], [varuint32(globalIndex)]) as any as Op 1175 | }, 1176 | 1177 | // Memory 1178 | current_memory() { // query the size of memory (number of pages) 1179 | return new instr_imm1(0x3f, c.i32, varuint1_0) as any as Op 1180 | }, 1181 | 1182 | // Grow the size of memory by `delta` memory pages. 1183 | // Returns the previous memory size in units of pages or -1 on failure. 1184 | grow_memory(delta :Op) { 1185 | assert(delta.v >= 0) 1186 | return new instr_pre_imm(0x40, c.i32, [delta], [varuint1_0]) as any as Op 1187 | }, 1188 | 1189 | // MemImm: Alignment Offset 1190 | align8: [ varUint32Cache[0], varUint32Cache[0] ] as [VarUint32,Int], 1191 | align16: [ varUint32Cache[1], varUint32Cache[0] ] as [VarUint32,Int], 1192 | align32: [ varUint32Cache[2], varUint32Cache[0] ] as [VarUint32,Int], 1193 | align64: [ varUint32Cache[3], varUint32Cache[0] ] as [VarUint32,Int], 1194 | 1195 | i32: new i32ops(-0x01, 0x7f) as I32ops, 1196 | i64: new i64ops(-0x02, 0x7e) as I64ops, 1197 | f32: new f32ops(-0x03, 0x7d) as F32ops, 1198 | f64: new f64ops(-0x04, 0x7c) as F64ops, 1199 | } 1200 | 1201 | 1202 | export interface FunctionBodyInfo { 1203 | index :number // module function index 1204 | locals :N[] 1205 | code :AnyOp[] 1206 | } 1207 | 1208 | // access helpers 1209 | export const get = { 1210 | sections(m :Module) :Section[] { 1211 | return m.v.slice(2) // 0=magic, 1=version, 2...=Section[] 1212 | }, 1213 | 1214 | section(m :Module, id :VarUint7|uint7) :Section { 1215 | let ido = (typeof id != 'object') ? varuint7(id as uint7) : (id as VarUint7) 1216 | for (let i = 2; i < m.v.length; ++i) { 1217 | let section = m.v[i] 1218 | if (section.v[0] === ido) { 1219 | return section 1220 | } 1221 | } 1222 | }, 1223 | 1224 | function_bodies(s :CodeSection) :Iterable { 1225 | return { 1226 | [Symbol.iterator](startIndex? :number) { 1227 | let index = 3 + (startIndex || 0) 1228 | return { 1229 | next() { 1230 | const funcBody = s.v[index] 1231 | if (!funcBody) { 1232 | return {done: true, value: null} 1233 | } 1234 | let localCount = funcBody.v[1] 1235 | return { 1236 | done: false, 1237 | value: { 1238 | index: index++, 1239 | locals: funcBody.v.slice(2, localCount.v + 2), 1240 | code: funcBody.v.slice(2 + localCount.v, funcBody.v.length - 1) as AnyOp[] 1241 | // -1 to skip terminating `end` 1242 | } 1243 | } 1244 | } 1245 | } 1246 | } 1247 | } 1248 | }, 1249 | 1250 | } -------------------------------------------------------------------------------- /src/basic-types.ts: -------------------------------------------------------------------------------- 1 | export type uint1 = number 2 | export type uint7 = number 3 | export type uint8 = number 4 | export type uint16 = number 5 | export type uint32 = number 6 | 7 | export type int7 = number 8 | export type int32 = number 9 | export type int64 = number 10 | 11 | export type float32 = number 12 | export type float64 = number 13 | -------------------------------------------------------------------------------- /src/emit.ts: -------------------------------------------------------------------------------- 1 | import {uint8, uint16, uint32, float32, float64} from './basic-types' 2 | 3 | export interface Emitter { 4 | // Emits code 5 | // 6 | // Each modifying operation returns a potentially new Emitter which is the result of 7 | // the receiver + modifications, thus modifying operations should be called like this: 8 | // e = e.writeU32(1) 9 | // e = e.writeU32(2) 10 | // but NOT like this: 11 | // e.writeU32(1) 12 | // e.writeU32(2) 13 | // // e represents same state as before `e.writeU32(1)` 14 | // 15 | // Think of Emitter as being persistent immutable state 16 | // 17 | writeU8(v :uint8) :Emitter 18 | writeU16(v :uint16) :Emitter 19 | writeU32(v :uint32) :Emitter 20 | writeF32(v :float32) :Emitter 21 | writeF64(v :float64) :Emitter 22 | writeBytes(v :ArrayLike) :Emitter 23 | } 24 | 25 | export interface Emittable { 26 | emit(ctx :Emitter) :Emitter 27 | } 28 | 29 | // Emitter that writes to an ArrayBuffer 30 | export class BufferedEmitter implements Emitter { 31 | readonly buffer :ArrayBuffer 32 | readonly view :DataView 33 | length :uint32 34 | 35 | constructor(buffer :ArrayBuffer) { 36 | this.buffer = buffer 37 | this.view = new DataView(this.buffer) 38 | this.length = 0 39 | } 40 | 41 | writeU8(v :uint8) :Emitter { 42 | this.view.setUint8(this.length++, v) 43 | return this 44 | } 45 | 46 | writeU16(v :uint16) :Emitter { 47 | this.view.setUint16(this.length, v, true) 48 | this.length += 2 49 | return this 50 | } 51 | 52 | writeU32(v :uint32) :Emitter { 53 | this.view.setUint32(this.length, v, true) 54 | this.length += 4 55 | return this 56 | } 57 | 58 | writeF32(v :float32) :Emitter { 59 | this.view.setFloat32(this.length, v, true) 60 | this.length += 4 61 | return this 62 | } 63 | 64 | writeF64(v :float64) :Emitter { 65 | this.view.setFloat64(this.length, v, true) 66 | this.length += 8 67 | return this 68 | } 69 | 70 | writeBytes(bytes :ArrayLike) { 71 | for (let i = 0, L = bytes.length; i != L; ++i) { 72 | this.view.setUint8(this.length++, bytes[i]) 73 | } 74 | return this 75 | } 76 | } 77 | 78 | // Note: you can use repr.reprBuffer(ArrayBuffer, Writer) 79 | // to print an ASCII representation of a buffer. 80 | -------------------------------------------------------------------------------- /src/eval.ts: -------------------------------------------------------------------------------- 1 | import {reprBuffer} from './repr' 2 | 3 | declare function require(ref:string):any; 4 | 5 | declare interface Buffer { 6 | buffer :ArrayBuffer 7 | byteLength :number 8 | from(a :ArrayLike) :Buffer 9 | from(a :ArrayBuffer, byteOffset? :number, byteLength? :number) :Buffer 10 | from(s :string, encoding? :string) :Buffer 11 | slice(begin :number, end? :number) :ArrayBuffer 12 | toString(encoding? :string) :string 13 | [Symbol.toStringTag]: "ArrayBuffer" 14 | } 15 | declare var Buffer :Buffer 16 | declare var __dirname :string 17 | declare var process :{ 18 | on(ev:string, f:(v? :any)=>any), 19 | removeListener(ev:string, f:(v? :any)=>any), 20 | env :{[k:string]:any}, 21 | } 22 | 23 | 24 | export interface SpecOptions { 25 | eval? :string // S-expression to evaluate after loading the module 26 | timeout? :number // 0 = no timeout. Defaults to 30000ms. 27 | logErrors? :boolean // when true, logs errors to stderr 28 | trace? :boolean // trace execution, printing to stdout 29 | } 30 | 31 | // Evaluate a WASM module using the WebAssembly spec reference interpreter. 32 | export function specEval(buf :ArrayBuffer, options? :SpecOptions) :Promise { 33 | if (typeof require === 'undefined' || !require('child_process')) { 34 | throw new Error('eval not supported by current platform') 35 | } 36 | 37 | if (!options) { options = {} } 38 | 39 | return new Promise(function (resolve, reject) { 40 | let tmpdir = process.env['TMPDIR'] || '' 41 | if (!tmpdir) { 42 | tmpdir = '.' 43 | } else if (tmpdir[tmpdir.length-1] != '/') { 44 | tmpdir += '/' 45 | } 46 | const randname = Math.random().toString() + Date.now().toString(36) 47 | const tmpname = tmpdir + 'tmp-' + randname + '.wasm'; 48 | 49 | // create temporary file with WASM code 50 | let rmtmpExecuted = false 51 | const rmtmp = function() { 52 | if (!rmtmpExecuted) { 53 | require('fs').unlinkSync(tmpname) 54 | rmtmpExecuted = true 55 | process.removeListener('exit', rmtmp) 56 | } 57 | } 58 | process.on('exit', rmtmp) 59 | require('fs').writeFileSync(tmpname, Buffer.from(buf)) 60 | 61 | let args = [] 62 | 63 | if (options.trace) { 64 | args.push('-t') 65 | } 66 | 67 | args.push(tmpname) 68 | 69 | if (options.eval) { 70 | args = args.concat(['-e', options.eval]) 71 | } 72 | 73 | require('child_process').execFile( 74 | __dirname + '/../wasm-spec/interpreter/wasm.opt', 75 | args, 76 | { 77 | stdio: 'inherit', 78 | timeout: options.timeout !== undefined ? options.timeout : 30000, 79 | }, 80 | (err :Error|null, stdout :string, stderr :string) => { 81 | rmtmp() 82 | handleResult(resolve, reject, buf, err, stdout, stderr, options) 83 | } 84 | ) 85 | }) 86 | } 87 | 88 | 89 | function handleResult( 90 | resolve :(value? :{} | PromiseLike<{}>)=>void, 91 | reject :(e:Error)=>void, 92 | buf :ArrayBuffer, 93 | err :Error|null, 94 | stdout :string, 95 | stderr :string, 96 | options :SpecOptions) 97 | { 98 | if (err) { 99 | if ((err as any).errno === 'ENOENT') { 100 | // wasm-spec/interpreter/wasm.opt not found 101 | return reject(new Error( 102 | `missing "wasm-spec/interpreter/wasm.opt" ${String(err)}` 103 | )) 104 | } 105 | 106 | const m = /^[^:]+\.wasm:0x([0-9A-Fa-f]+)(?:-0x([0-9A-Fa-f]+)|):\s*(.+)/.exec(stderr) 107 | 108 | if (!m || !m[1]) { 109 | reject(err) 110 | } else { 111 | 112 | const e :any = new Error(m[3] || String(err)) 113 | const byteRange = [ 114 | parseInt(m[1], 16), 115 | m[2] ? parseInt(m[2], 16) : parseInt(m[1], 16) + 1 116 | ] 117 | e.byteRange = byteRange 118 | 119 | if (options.logErrors) { 120 | console.error(e.message) 121 | const limit = byteRange[1] ? byteRange[1] + 2 : 500 122 | reprBuffer(buf, s => { (process as any).stderr.write(s) }, limit, byteRange) 123 | } 124 | 125 | reject(e as Error) 126 | } 127 | 128 | } else { 129 | resolve(stdout.replace(/[\r\n\t\s ]+$/, '')) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/info.ts: -------------------------------------------------------------------------------- 1 | export const opcodes = new Map([ 2 | [0x0,"unreachable"], 3 | [0x1,"nop"], 4 | [0x2,"block"], 5 | [0x3,"loop"], 6 | [0x4,"if"], 7 | [0x5,"else"], 8 | [0xb,"end"], 9 | [0xc,"br"], 10 | [0xd,"br_if"], 11 | [0xe,"br_table"], 12 | [0xf,"return"], 13 | [0x10,"call"], 14 | [0x11,"call_indirect"], 15 | [0x1a,"drop"], 16 | [0x1b,"select"], 17 | [0x20,"get_local"], 18 | [0x21,"set_local"], 19 | [0x22,"tee_local"], 20 | [0x23,"get_global"], 21 | [0x24,"set_global"], 22 | [0x28,"i32.load"], 23 | [0x29,"i64.load"], 24 | [0x2a,"f32.load"], 25 | [0x2b,"f64.load"], 26 | [0x2c,"i32.load8_s"], 27 | [0x2d,"i32.load8_u"], 28 | [0x2e,"i32.load16_s"], 29 | [0x2f,"i32.load16_u"], 30 | [0x30,"i64.load8_s"], 31 | [0x31,"i64.load8_u"], 32 | [0x32,"i64.load16_s"], 33 | [0x33,"i64.load16_u"], 34 | [0x34,"i64.load32_s"], 35 | [0x35,"i64.load32_u"], 36 | [0x36,"i32.store"], 37 | [0x37,"i64.store"], 38 | [0x38,"f32.store"], 39 | [0x39,"f64.store"], 40 | [0x3a,"i32.store8"], 41 | [0x3b,"i32.store16"], 42 | [0x3c,"i64.store8"], 43 | [0x3d,"i64.store16"], 44 | [0x3e,"i64.store32"], 45 | [0x3f,"current_memory"], 46 | [0x40,"grow_memory"], 47 | [0x41,"i32.const"], 48 | [0x42,"i64.const"], 49 | [0x43,"f32.const"], 50 | [0x44,"f64.const"], 51 | [0x45,"i32.eqz"], 52 | [0x46,"i32.eq"], 53 | [0x47,"i32.ne"], 54 | [0x48,"i32.lt_s"], 55 | [0x49,"i32.lt_u"], 56 | [0x4a,"i32.gt_s"], 57 | [0x4b,"i32.gt_u"], 58 | [0x4c,"i32.le_s"], 59 | [0x4d,"i32.le_u"], 60 | [0x4e,"i32.ge_s"], 61 | [0x4f,"i32.ge_u"], 62 | [0x50,"i64.eqz"], 63 | [0x51,"i64.eq"], 64 | [0x52,"i64.ne"], 65 | [0x53,"i64.lt_s"], 66 | [0x54,"i64.lt_u"], 67 | [0x55,"i64.gt_s"], 68 | [0x56,"i64.gt_u"], 69 | [0x57,"i64.le_s"], 70 | [0x58,"i64.le_u"], 71 | [0x59,"i64.ge_s"], 72 | [0x5a,"i64.ge_u"], 73 | [0x5b,"f32.eq"], 74 | [0x5c,"f32.ne"], 75 | [0x5d,"f32.lt"], 76 | [0x5e,"f32.gt"], 77 | [0x5f,"f32.le"], 78 | [0x60,"f32.ge"], 79 | [0x61,"f64.eq"], 80 | [0x62,"f64.ne"], 81 | [0x63,"f64.lt"], 82 | [0x64,"f64.gt"], 83 | [0x65,"f64.le"], 84 | [0x66,"f64.ge"], 85 | [0x67,"i32.clz"], 86 | [0x68,"i32.ctz"], 87 | [0x69,"i32.popcnt"], 88 | [0x6a,"i32.add"], 89 | [0x6b,"i32.sub"], 90 | [0x6c,"i32.mul"], 91 | [0x6d,"i32.div_s"], 92 | [0x6e,"i32.div_u"], 93 | [0x6f,"i32.rem_s"], 94 | [0x70,"i32.rem_u"], 95 | [0x71,"i32.and"], 96 | [0x72,"i32.or"], 97 | [0x73,"i32.xor"], 98 | [0x74,"i32.shl"], 99 | [0x75,"i32.shr_s"], 100 | [0x76,"i32.shr_u"], 101 | [0x77,"i32.rotl"], 102 | [0x78,"i32.rotr"], 103 | [0x79,"i64.clz"], 104 | [0x7a,"i64.ctz"], 105 | [0x7b,"i64.popcnt"], 106 | [0x7c,"i64.add"], 107 | [0x7d,"i64.sub"], 108 | [0x7e,"i64.mul"], 109 | [0x7f,"i64.div_s"], 110 | [0x80,"i64.div_u"], 111 | [0x81,"i64.rem_s"], 112 | [0x82,"i64.rem_u"], 113 | [0x83,"i64.and"], 114 | [0x84,"i64.or"], 115 | [0x85,"i64.xor"], 116 | [0x86,"i64.shl"], 117 | [0x87,"i64.shr_s"], 118 | [0x88,"i64.shr_u"], 119 | [0x89,"i64.rotl"], 120 | [0x8a,"i64.rotr"], 121 | [0x8b,"f32.abs"], 122 | [0x8c,"f32.neg"], 123 | [0x8d,"f32.ceil"], 124 | [0x8e,"f32.floor"], 125 | [0x8f,"f32.trunc"], 126 | [0x90,"f32.nearest"], 127 | [0x91,"f32.sqrt"], 128 | [0x92,"f32.add"], 129 | [0x93,"f32.sub"], 130 | [0x94,"f32.mul"], 131 | [0x95,"f32.div"], 132 | [0x96,"f32.min"], 133 | [0x97,"f32.max"], 134 | [0x98,"f32.copysign"], 135 | [0x99,"f64.abs"], 136 | [0x9a,"f64.neg"], 137 | [0x9b,"f64.ceil"], 138 | [0x9c,"f64.floor"], 139 | [0x9d,"f64.trunc"], 140 | [0x9e,"f64.nearest"], 141 | [0x9f,"f64.sqrt"], 142 | [0xa0,"f64.add"], 143 | [0xa1,"f64.sub"], 144 | [0xa2,"f64.mul"], 145 | [0xa3,"f64.div"], 146 | [0xa4,"f64.min"], 147 | [0xa5,"f64.max"], 148 | [0xa6,"f64.copysign"], 149 | [0xa7,"i32.wrap_i64"], 150 | [0xa8,"i32.trunc_s_f32"], 151 | [0xa9,"i32.trunc_u_f32"], 152 | [0xaa,"i32.trunc_s_f64"], 153 | [0xab,"i32.trunc_u_f64"], 154 | [0xac,"i64.extend_s_i32"], 155 | [0xad,"i64.extend_u_i32"], 156 | [0xae,"i64.trunc_s_f32"], 157 | [0xaf,"i64.trunc_u_f32"], 158 | [0xb0,"i64.trunc_s_f64"], 159 | [0xb1,"i64.trunc_u_f64"], 160 | [0xb2,"f32.convert_s_i32"], 161 | [0xb3,"f32.convert_u_i32"], 162 | [0xb4,"f32.convert_s_i64"], 163 | [0xb5,"f32.convert_u_i64"], 164 | [0xb6,"f32.demote_f64"], 165 | [0xb7,"f64.convert_s_i32"], 166 | [0xb8,"f64.convert_u_i32"], 167 | [0xb9,"f64.convert_s_i64"], 168 | [0xba,"f64.convert_u_i64"], 169 | [0xbb,"f64.promote_f32"], 170 | [0xbc,"i32.reinterpret_f32"], 171 | [0xbd,"i64.reinterpret_f64"], 172 | [0xbe,"f32.reinterpret_i32"], 173 | [0xbf,"f64.reinterpret_i64"], 174 | ]) 175 | -------------------------------------------------------------------------------- /src/lbtext.ts: -------------------------------------------------------------------------------- 1 | // Linear bytecode textual representation. 2 | // https://github.com/WebAssembly/design/blob/master/TextFormat.md 3 | // 4 | import { t, N, AnyOp, VarUint7 } from './ast' 5 | import { utf8 } from './utf8' 6 | import { uint8, uint16, uint32, float32, float64 } from './basic-types'; 7 | import { opcodes } from './info' 8 | 9 | // Maps opname to opcode 10 | const opnames = new Map() 11 | for (let e of opcodes.entries()) { opnames.set(e[1], e[0]) } 12 | 13 | 14 | export type Writer = (s :string)=>void 15 | 16 | interface Ctx { 17 | writeln(depth :number, s :string) 18 | } 19 | 20 | function readVarInt7(byte :uint8) { 21 | return byte < 64 ? byte : -(128 - byte) 22 | } 23 | 24 | function fmtimm(n :N) { 25 | switch (n.t) { 26 | case t.uint8: 27 | case t.uint16: 28 | case t.uint32: 29 | case t.varuint1: 30 | case t.varuint7: 31 | case t.varuint32: 32 | case t.varint32: 33 | case t.varint64: 34 | case t.float32: 35 | case t.float64: { 36 | return n.v.toString(10) 37 | } 38 | case t.varint7: { 39 | return readVarInt7(n.v).toString(10) 40 | } 41 | case t.type: { 42 | switch (n.v) { 43 | case -1: return 'i32' 44 | case -2: return 'i64' 45 | case -3: return 'f32' 46 | case -4: return 'f64' 47 | case -0x10: return 'anyfunc' 48 | case -0x20: return 'func' 49 | case -0x40: return 'void' // aka empty_block 50 | default: 51 | throw new Error('unexpected type ' + n.t.toString()) 52 | } 53 | } 54 | default: 55 | throw new Error('unexpected imm ' + n.t.toString()) 56 | } 57 | } 58 | 59 | function fmtimmv(n :N[]) { // " A B" if n=[A,B]; "" if n=[] (leading space) 60 | return n.map(n => ' ' + fmtimm(n)).join('') 61 | } 62 | 63 | function visitOps(nodes :N[], c :Ctx, depth :number) { 64 | for (let n of nodes) { 65 | visitOp(n, c, depth) 66 | } 67 | } 68 | 69 | function visitOp(n :N, c :Ctx, depth :number) { 70 | // const tname = style(symname(n.t), '92') 71 | switch (n.t) { 72 | case t.instr: { 73 | if (n.v == 0x0b/*end*/ || n.v == 0x05/*else*/) { 74 | depth-- 75 | } 76 | return c.writeln(depth, opcodes.get(n.v)) 77 | } 78 | 79 | case t.instr_imm1: { 80 | return c.writeln(depth, opcodes.get(n.v) + ' ' + fmtimm((n as AnyOp).imm as N)) 81 | } 82 | 83 | case t.instr_pre: { 84 | visitOps((n as AnyOp).pre as N[], c, depth) 85 | return c.writeln(depth, opcodes.get(n.v)) 86 | } 87 | 88 | case t.instr_pre1: { 89 | visitOp((n as AnyOp).pre as N, c, depth) 90 | return c.writeln(depth, opcodes.get(n.v)) 91 | } 92 | 93 | case t.instr_imm1_post: { 94 | c.writeln(depth, opcodes.get(n.v) + ' ' + fmtimm((n as AnyOp).imm as N)) 95 | return visitOps((n as AnyOp).post as N[], c, depth + 1) 96 | } 97 | 98 | case t.instr_pre_imm: { 99 | visitOps((n as AnyOp).pre as N[], c, depth) 100 | return c.writeln(depth, opcodes.get(n.v) + fmtimmv((n as AnyOp).imm as N[])) 101 | } 102 | 103 | case t.instr_pre_imm_post: { 104 | visitOps((n as AnyOp).pre as N[], c, depth) 105 | c.writeln(depth, opcodes.get(n.v) + fmtimmv((n as AnyOp).imm as N[])) 106 | visitOps((n as AnyOp).post as N[], c, depth + 1) 107 | break 108 | } 109 | 110 | default: 111 | throw new Error('unexpected op ' + n.t.toString()) 112 | } 113 | } 114 | 115 | export function printCode(instructions :N[], writer :Writer) { 116 | const SP = ' ' 117 | const ctx = { 118 | writeln(depth :number, chunk :string) { 119 | writer(SP.substr(0, depth * 2) + chunk + '\n') 120 | }, 121 | } 122 | 123 | visitOps(instructions, ctx, 0) 124 | } 125 | -------------------------------------------------------------------------------- /src/repr.ts: -------------------------------------------------------------------------------- 1 | // repr(n :N, w :Writer, options? :Options) :void 2 | // Represents a tree visually as plain text (with optional terminal colors.) 3 | // 4 | import { t, N, AnyOp, VarUint7 } from './ast' 5 | import { utf8 } from './utf8' 6 | import { uint8, uint16, uint32, float32, float64 } from './basic-types'; 7 | import { opcodes } from './info' 8 | 9 | export type Writer = (s :string)=>void 10 | 11 | export interface Options { 12 | readonly colors :boolean // explicitly enable or disable terminal colors 13 | readonly immSeparator :string // defaults to `:` 14 | readonly detailedTypes? :boolean, // `vi32(9)` or just `9` 15 | } 16 | 17 | interface Ctx { 18 | readonly write :(depth :number, s :string)=>void 19 | readonly writeln :(depth :number, s :string)=>void 20 | readonly writeinline :(s :string)=>void 21 | readonly options :Options 22 | } 23 | 24 | function symname(y :Symbol) { 25 | const s = y.toString() 26 | return s.length > 8 ? s.substr(7,s.length-8) : 'Symbol(?)'; 27 | } 28 | 29 | // const indln = (indent? :string) => (indent ? '\n' + indent : '') 30 | const style = (str :string, style :string) => '\x1B[' + style + 'm' + str + '\x1B[0m' 31 | const reprop = (op :uint8) => style(opcodes.get(op), '96') 32 | const styleType = (s :string) => style(s, '33') 33 | 34 | function readVarInt7(byte :uint8) { 35 | return byte < 64 ? byte : -(128 - byte) 36 | } 37 | 38 | function visitAll(nodes :N[], c :Ctx, depth :number) { 39 | for (let n of nodes) { 40 | visit(n, c, depth) 41 | } 42 | } 43 | 44 | function visitCell(n :N, tname :string, c :Ctx, depth :number) { 45 | c.write(depth, '(' + tname) 46 | visitAll(n.v, c, depth + 1) 47 | c.write(depth, ')') 48 | } 49 | 50 | function visitValue(v :any, tname :string, c :Ctx, depth :number) { 51 | const s = String(v) 52 | return c.write(depth, c.options.detailedTypes ? `${tname}(${s})` : s) 53 | } 54 | 55 | function visit(n :N, c :Ctx, depth :number) { 56 | const tname = style(symname(n.t), '92') 57 | switch (n.t) { 58 | // Atoms 59 | case t.uint8: 60 | case t.uint16: 61 | case t.uint32: 62 | case t.varuint1: 63 | case t.varuint7: 64 | case t.varuint32: 65 | case t.varint32: 66 | case t.varint64: 67 | case t.float32: 68 | case t.float64: { 69 | return visitValue(n.v, tname, c, depth) 70 | } 71 | case t.varint7: { 72 | return visitValue(readVarInt7(n.v), tname, c, depth) 73 | } 74 | 75 | case t.external_kind: { 76 | switch (n.v) { 77 | case 0: return c.write(depth, styleType('external_kind.function')) 78 | case 1: return c.write(depth, styleType('external_kind.table')) 79 | case 2: return c.write(depth, styleType('external_kind.memory')) 80 | case 3: return c.write(depth, styleType('external_kind.global')) 81 | default: return visitValue(readVarInt7(n.v), tname, c, depth) 82 | } 83 | } 84 | 85 | // Instructions 86 | case t.instr: { 87 | return c.write(depth, reprop(n.v)) 88 | } 89 | 90 | case t.instr_imm1: { 91 | c.write(depth, '(' + reprop(n.v) + ' [') 92 | visit((n as AnyOp).imm as N, c, depth + 1) 93 | return c.write(depth, '])') 94 | } 95 | 96 | case t.instr_pre: { 97 | c.write(depth, '(' + reprop(n.v)) 98 | visitAll((n as AnyOp).pre as N[], c, depth + 1) 99 | return c.writeln(depth , ')') 100 | } 101 | case t.instr_pre1: { 102 | c.write(depth, '(' + reprop(n.v)) 103 | visit((n as AnyOp).pre as N, c, depth + 1) 104 | return c.writeln(depth, ')') 105 | } 106 | 107 | case t.instr_imm1_post: { 108 | c.write(depth, '(' + reprop(n.v) + ' [') 109 | visit((n as AnyOp).imm as N, c, depth + 1) 110 | c.write(depth, ']') 111 | visitAll((n as AnyOp).post as N[], c, depth + 1) 112 | return c.write(depth, ')') 113 | } 114 | 115 | case t.instr_pre_imm: { 116 | c.write(depth, '(' + reprop(n.v) + ' [') 117 | visitAll((n as AnyOp).imm as N[], c, depth + 1) 118 | c.write(depth, ']') 119 | visitAll((n as AnyOp).pre as N[], c, depth + 1) 120 | return c.write(depth, ')') 121 | } 122 | 123 | case t.instr_pre_imm_post: { 124 | c.write(depth, '(' + reprop(n.v) + ' [') 125 | visitAll((n as AnyOp).imm as N[], c, depth + 1) 126 | c.write(depth, ']') 127 | visitAll((n as AnyOp).pre as N[], c, depth + 1) 128 | visitAll((n as AnyOp).post as N[], c, depth + 1) 129 | return c.write(depth, ')') 130 | } 131 | 132 | // TODO: special case of "if" 133 | // { 134 | // let s = ind(indent) + '(' + reprop(this.v) 135 | // 136 | // if (this.v == 0x04/*if*/) { 137 | // let cind = (indent || '') + ' ' 138 | // 139 | // s += ' ' + this.imm[0].repr() + // type 140 | // this.pre[0].repr(cind) + // cond 141 | // ind(cind) + '(then'; 142 | // 143 | // let i = 1, ci = (cind || '') + ' ', E = this.imm.length-1 // skip `end` 144 | // for (; i < E; ++i) { 145 | // let n = this.imm[i] 146 | // if (n.v == 0x05/*else*/) { 147 | // s += ')' // end of "then" 148 | // break 149 | // } 150 | // s += ' ' + n.repr(ci) 151 | // } 152 | // 153 | // if (i < E) { 154 | // s += ind(cind) + '(else'; 155 | // ++i 156 | // for (; i < E; ++i) { 157 | // let n = this.imm[i] 158 | // s += ' ' + n.repr(ci) 159 | // } 160 | // } 161 | // 162 | // return s + ') end)' // end of "then" or "else" 163 | // } 164 | // 165 | // return s + `${reprv(indent, this.imm)}${reprv(indent, this.pre)})` 166 | // } 167 | 168 | case t.data: { 169 | let v :string[] = [] 170 | for (let i = 0, L = n.v.length; i != L; ++i) { 171 | if (v.length == 8) { v.push('...') ;break } 172 | v.push((n.v[i] as number).toString(16)) 173 | } 174 | return c.write(depth, '(' + tname + ' ' + v.join(' ') + ')') 175 | } 176 | 177 | case t.type: { 178 | switch (n.v) { 179 | case -1: return c.write(depth, styleType('i32')) 180 | case -2: return c.write(depth, styleType('i64')) 181 | case -3: return c.write(depth, styleType('f32')) 182 | case -4: return c.write(depth, styleType('f64')) 183 | case -0x10: return c.write(depth, styleType('anyfunc')) 184 | case -0x20: return c.write(depth, styleType('func')) 185 | case -0x40: return c.write(depth, styleType('void')) // aka empty_block 186 | default: return c.write(depth, styleType('?')) 187 | } 188 | } 189 | 190 | // ————————————————————————————————————————————————————————————————— 191 | // Cells 192 | 193 | case t.module: { 194 | c.write(depth, '(' + tname + ' ' + n.v[1].v) 195 | visitAll(n.v.slice(2), c, depth + 1) 196 | return c.write(depth, ')') 197 | } 198 | 199 | case t.section: { 200 | const id = (n.v[0] as VarUint7).v 201 | const name = [ 202 | 'custom', // 0 Custom named section 203 | 'type', // 1 Function signature declarations 204 | 'import', // 2 Import declarations 205 | 'function', // 3 Function declarations 206 | 'table', // 4 Indirect function table and other tables 207 | 'memory', // 5 Memory attributes 208 | 'global', // 6 Global declarations 209 | 'export', // 7 Exports 210 | 'start', // 8 Start function declaration 211 | 'element', // 9 Elements section 212 | 'code', // 10 Function bodies (code) 213 | 'data', // 11 Data segments 214 | ][id] || ('0x' + id.toString(16)) 215 | 216 | c.write(depth, '(' + tname + ' ' + name) 217 | visitAll(n.v.slice(1), c, depth + 1) 218 | return c.write(depth, ')') 219 | } 220 | 221 | case t.import_entry: 222 | case t.export_entry: 223 | case t.local_entry: 224 | case t.table_type: 225 | case t.memory_type: { 226 | return visitCell(n, tname, c, depth) 227 | } 228 | 229 | case t.func_type: { 230 | // func, paramCount, paramT*, retCount, retT* -> "(" paramT* ")" retT 231 | c.write(depth, '(' + tname + ' (') 232 | const paramCount = n.v[1].v as number 233 | const resCount = n.v[1 + paramCount + 1].v as number 234 | visitAll(n.v.slice(2, 2+paramCount), c, depth) 235 | c.writeinline(')') 236 | if (resCount > 0) { 237 | visitAll(n.v.slice(1 + paramCount + 2), c, depth) 238 | } 239 | return c.write(depth, ')') 240 | } 241 | 242 | case t.global_type: { 243 | c.write(depth, '(' + tname) 244 | visitAll(n.v.slice(0, n.v.length-1), c, depth + 1) 245 | c.write(depth + 1, (n.v[1].v ? ' mutable' : 'immutable')) 246 | return c.write(depth, ')') 247 | } 248 | 249 | case t.resizable_limits: { 250 | return c.write(depth, 251 | '(' + tname + ' ' + n.v[1].v + '..' + (n.v[0].v ? n.v[2].v : '') + ')' 252 | ) 253 | } 254 | 255 | case t.global_variable: 256 | case t.init_expr: 257 | case t.elem_segment: 258 | case t.data_segment: 259 | case t.function_body: { 260 | return visitCell(n, tname, c, depth) 261 | } 262 | 263 | case t.str: { 264 | return c.write(depth, '"' + utf8.decode(n.v) + '"') 265 | } 266 | 267 | default: 268 | throw new Error('unexpected type ' + n.t.toString()) 269 | } 270 | } 271 | 272 | 273 | export function reprBuffer( 274 | buffer :ArrayBuffer, 275 | w :Writer, 276 | limit? :number, 277 | highlightRange? :number[], 278 | options? :Options) 279 | { 280 | limit = Math.min(buffer.byteLength, limit || Infinity) 281 | 282 | if (highlightRange && !highlightRange[1]) { 283 | highlightRange[1] = highlightRange[0]+1 284 | } 285 | 286 | let s = [], i = 0, view = new DataView(buffer) 287 | 288 | for (; i < limit; ++i) { 289 | let b = view.getUint8(i) 290 | const inHLRange = (highlightRange && i < highlightRange[1] && i >= highlightRange[0]) 291 | if (b == 0 && !inHLRange) { 292 | s.push('\x1B[2m00\x1B[0m') 293 | } else { 294 | let str = b.toString(16) 295 | s.push( 296 | inHLRange ? 297 | ('\x1B[45;97m' + (str.length == 1 ? '0' : '') + str + '\x1B[0m' ) : 298 | str.length == 1 ? '\x1B[2m0\x1B[0m' + str : 299 | str 300 | ) 301 | } 302 | if (s.length % 20 == 19) { 303 | w(s.join(' ') + '\n'); 304 | s = []; 305 | } else if (s.length % 5 == 4) { 306 | s.push('') 307 | } 308 | } 309 | const tail = (highlightRange && highlightRange[0] >= i) ? '\x1B[45;97m..\x1B[0m' : '' 310 | if (s.length) { 311 | w(s.join(' ') + (tail ? ' ' + tail : '') + '\n'); 312 | } else if (tail) { 313 | w(tail + '\n') 314 | } 315 | } 316 | 317 | 318 | export function repr(n :N, w :Writer, options? :Options) { 319 | const maxLineLength = 40 320 | const SP = ' ' 321 | 322 | let currLineLen = 0 323 | let lastCh = '' 324 | let writer = w 325 | 326 | if (!options || options.colors !== false) { 327 | // writer that colors immediate brackets 328 | let immOpen = style('[', '2'), 329 | immClose = style(']', '2') 330 | writer = s => { 331 | w(s.replace(/([^\x1B]|^)[\[\]]/g, m => 332 | m.length == 2 ? 333 | m[0] + (m[1] == '[' ? immOpen : immClose) : 334 | (m[0] == '[' ? immOpen : immClose) 335 | )) 336 | } 337 | } 338 | 339 | const ctx = { 340 | 341 | options: Object.assign({ 342 | // detailedTypes: true, 343 | immSeparator: ':', 344 | }, options || {}) as Options, 345 | 346 | writeln(depth :number, chunk :string) { 347 | const line = SP.substr(0, depth * 2) + chunk 348 | currLineLen = line.length 349 | lastCh = chunk[chunk.length-1] 350 | writer('\n' + line) 351 | }, 352 | 353 | writeinline(chunk :string) { 354 | currLineLen += chunk.length 355 | lastCh = chunk[chunk.length-1] 356 | writer(chunk) 357 | }, 358 | 359 | write(depth :number, chunk :string) { 360 | if (currLineLen > 0 && depth > 0) { 361 | const ch0 = chunk[0] 362 | if (ch0 == '(') { 363 | return this.writeln(depth, chunk) 364 | } 365 | if (ch0 != ')' && ch0 != ']' && 366 | lastCh != '[' && lastCh != '(') 367 | { 368 | currLineLen += 1 369 | chunk = ' ' + chunk 370 | } 371 | } 372 | this.writeinline(chunk) 373 | }, 374 | } 375 | 376 | visit(n, ctx, 0) 377 | } 378 | 379 | // Simple Writer that buffers everything as a string that 380 | export function BufferedWriter() { 381 | const buf = [] 382 | const w = s => { buf.push(s) } 383 | w.toString = () => buf.join('') 384 | return w 385 | } 386 | 387 | // Convenience functions that returns strings. 388 | // Will be slower and use more memory than `repr` but convenient for 389 | // visualizing smaller structures. 390 | export function strRepr(n :N, options? :Options) { 391 | const w = BufferedWriter() 392 | repr(n, w, options) 393 | return w.toString() 394 | } 395 | 396 | export function strReprBuffer( 397 | buffer :ArrayBuffer, 398 | limit? :number, 399 | highlightRange? :number[], 400 | options? :Options) 401 | { 402 | const w = BufferedWriter() 403 | reprBuffer(buffer, w, limit, highlightRange, options) 404 | return w.toString() 405 | } 406 | -------------------------------------------------------------------------------- /src/utf8.ts: -------------------------------------------------------------------------------- 1 | // UTF-8 encoding/decoding 2 | export interface UTF8 { 3 | encode(text :string) :Uint8Array 4 | decode(buf: ArrayBufferView | ArrayLike) :string 5 | } 6 | 7 | declare interface TextEncoder { 8 | new(encoding :string) 9 | encode(v :string, options? :{stream :boolean}) :Uint8Array 10 | } 11 | declare var TextEncoder: TextEncoder 12 | 13 | declare interface TextDecoder { 14 | new(encoding :string) 15 | decode(v :ArrayBufferView, options? :{stream :boolean}) :string 16 | } 17 | declare var TextDecoder: TextDecoder 18 | 19 | declare interface Buffer { 20 | buffer :ArrayBuffer 21 | byteLength :number 22 | from(a :ArrayLike) :Buffer 23 | from(a :ArrayBuffer, byteOffset? :number, byteLength? :number) :Buffer 24 | from(s :string, encoding? :string) :Buffer 25 | slice(begin :number, end? :number) :ArrayBuffer 26 | toString(encoding? :string) :string 27 | [Symbol.toStringTag]: "ArrayBuffer" 28 | } 29 | declare var Buffer: Buffer 30 | 31 | // —————————————————————————————————————————————————————————————————————————— 32 | 33 | export const utf8 = typeof TextEncoder != 'undefined' ? (function(){ 34 | // Modern browsers 35 | const enc = new TextEncoder('utf-8') 36 | const dec = new TextDecoder('utf-8') 37 | return { 38 | 39 | encode(text :string) :Uint8Array { 40 | return enc.encode(text) 41 | }, 42 | 43 | decode(b: ArrayBufferView|ArrayLike) :string { 44 | return dec.decode( 45 | (b as ArrayBufferView).buffer != undefined ? b as ArrayBufferView : 46 | new Uint8Array(b as ArrayLike) 47 | ) 48 | }, 49 | 50 | }})() : typeof Buffer != 'undefined' ? { 51 | // Nodejs 52 | encode(text :string) :Uint8Array { 53 | return new Uint8Array(Buffer.from(text, 'utf-8')) 54 | }, 55 | 56 | decode(b: ArrayBufferView|ArrayLike) :string { 57 | return ( 58 | (b as ArrayBufferView).buffer != undefined ? 59 | Buffer.from( 60 | (b as ArrayBufferView).buffer, 61 | (b as ArrayBufferView).byteOffset, 62 | (b as ArrayBufferView).byteLength) : 63 | Buffer.from(b as ArrayLike) 64 | ).toString('utf8') 65 | } 66 | 67 | } : { 68 | // Some other pesky JS environment 69 | encode(text :string) :Uint8Array { 70 | let asciiBytes = [] 71 | for (let i = 0, L = text.length; i != L; ++i) { 72 | asciiBytes[i] = 0xff & text.charCodeAt(i); 73 | } 74 | return new Uint8Array(asciiBytes) 75 | }, 76 | decode(buf: ArrayBufferView) :string { 77 | return '' 78 | } 79 | } as UTF8 80 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *.wasm 3 | *wast 4 | -------------------------------------------------------------------------------- /test/build_factorial_test.ts: -------------------------------------------------------------------------------- 1 | import { c, get, sect_id, CodeSection } from '../build/ast' 2 | import { BufferedEmitter } from '../build/emit' 3 | import { specEval } from '../build/eval' 4 | import { printCode } from '../build/lbtext' 5 | import { strRepr } from '../build/repr' 6 | 7 | 8 | declare function require(ref:string):any; 9 | const assert :(cond:any, msg?:any)=>void = require('assert') 10 | const isUnitTest = this.isUnitTest 11 | 12 | const { 13 | type_section, function_section, code_section, export_section, 14 | func_type, varuint32, function_body, str_ascii, external_kind, export_entry, 15 | i64, if_, get_local, call 16 | } = c 17 | 18 | const mod = c.module([ 19 | 20 | type_section([ 21 | func_type([i64], i64), // type index = 0 22 | ]), 23 | 24 | function_section([ 25 | varuint32(0), // function index = 0, using type index 0 26 | ]), 27 | 28 | export_section([ 29 | // exports "factorial" as function at index 0 30 | export_entry(str_ascii("factorial"), external_kind.function, varuint32(0)), 31 | ]), 32 | 33 | code_section([ 34 | // body of function at index 0: 35 | function_body([ /* additional local variables here */ ], [ 36 | if_(i64, // i64 = result type of `if` expression 37 | i64.eq(get_local(i64, 0), i64.const(0)), // condition 38 | [ // then 39 | i64.const(1) 40 | ], [ // else 41 | i64.mul( 42 | get_local(i64, 0), 43 | call(i64, varuint32(0), [ // 0 = function index 44 | i64.sub(get_local(i64, 0), i64.const(1)) 45 | ]) 46 | ) 47 | ] 48 | ) 49 | ]) 50 | ]) 51 | ]) 52 | 53 | function factorial(n :number) { 54 | return (n == 0) ? 55 | 1 56 | : 57 | n * factorial(n - 1) 58 | } 59 | 60 | function Test() { 61 | // lbtext print code of each function body 62 | const codeSection = get.section(mod, sect_id.code) as CodeSection 63 | for (let funcBody of get.function_bodies(codeSection)) { 64 | printCode(funcBody.code, s => { console.log(s.replace(/[\r\n]+$/, '')) }) 65 | } 66 | 67 | // codegen 68 | const emitbuf = new BufferedEmitter(new ArrayBuffer(mod.z)) 69 | mod.emit(emitbuf) 70 | 71 | if (!isUnitTest) { 72 | console.log(strRepr(mod)) 73 | } 74 | 75 | // eval with spec interpreter 76 | return specEval(emitbuf.buffer, { 77 | eval: '(invoke "factorial" (i64.const 8))', 78 | logErrors: !isUnitTest, 79 | trace: !isUnitTest, 80 | }).then(stdout => { 81 | if (!isUnitTest) { 82 | console.log(stdout) 83 | } 84 | const expected = '40320 : i64' 85 | assert(stdout.substr(stdout.length - expected.length) == expected) 86 | }).catch(err => { 87 | console.error(err.stack || String(err)) 88 | throw err 89 | }) 90 | } 91 | 92 | if (!isUnitTest) { 93 | Test() 94 | } -------------------------------------------------------------------------------- /test/build_test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | require('source-map-support').install() 3 | 4 | const assert = require('assert') 5 | const ast = require('../build/ast.js') 6 | const { 7 | uint8, uint32, float32, float64, varuint1, varuint7, varuint32, varint7, varint32, varint64, 8 | i32, i64, f32, f64, any_func, func, empty_block, void_, 9 | external_kind, 10 | data, str, str_ascii, str_utf8, 11 | custom_section, type_section, import_section, function_section, table_section, memory_section, 12 | global_section, export_section, start_section, element_section, code_section, data_section, 13 | function_import_entry, table_import_entry, memory_import_entry, global_import_entry, local_entry, 14 | export_entry, elem_segment, data_segment, 15 | func_type, table_type, global_type, 16 | resizable_limits, global_variable, init_expr, function_body, 17 | 18 | unreachable, nop, end, 19 | block, void_block, loop, void_loop, 20 | if_, br, br_if, br_table, return_, 21 | call, call_indirect, 22 | drop, select, get_local, set_local, tee_local, get_global, set_global, 23 | current_memory, grow_memory, align8, align16, align32, align64 24 | } = ast.c; 25 | 26 | // const s_crypto = str_ascii("crypto") 27 | 28 | const mod = ast.c.module([ 29 | type_section([ 30 | func_type([]), // 0 31 | func_type([i32], i32), // 1 32 | func_type([f32], f32), // 2 33 | func_type([i32]), // 3 34 | ]), 35 | 36 | import_section([ 37 | // Note: MVP only allows one table and one memory 38 | function_import_entry(str_ascii("spectest"), str_ascii("print"), varuint32(3)), 39 | // global_import_entry(s_crypto, str_ascii("version"), global_type(i32)), 40 | // memory_import_entry(s_crypto, str_ascii("data"), 41 | // resizable_limits(varuint32(0), varuint32(8)) 42 | // ), 43 | // table_import_entry(s_crypto, str_ascii("ciphers"), 44 | // table_type(AnyFunc, resizable_limits(varuint32(0), varuint32(8))) 45 | // ), 46 | ]), 47 | 48 | // Function offsets are: imported... + local... 49 | function_section([ 50 | varuint32(0), // 1 51 | varuint32(1), // 2 52 | varuint32(2), // 3 53 | ]), 54 | 55 | // Note: MVP only allows a total of 1 table; either a table_section OR a table_import_entry 56 | table_section([ 57 | // must have enought size for element_section's element segments 58 | table_type(any_func, resizable_limits(varuint32(1))), 59 | ]), 60 | 61 | // Note: MVP only allows a total of 1 memory; either a MemorySection OR a MemoryImportEntry 62 | memory_section([ 63 | resizable_limits(varuint32(1)), 64 | ]), 65 | 66 | global_section([ 67 | global_variable(global_type(i32), init_expr([ 68 | i32.const(0) 69 | ])), 70 | ]), 71 | 72 | export_section([ 73 | export_entry(str_ascii("foo"), external_kind.function, varuint32(2)), 74 | export_entry(str_ascii("bar"), external_kind.function, varuint32(3)), 75 | ]), 76 | 77 | start_section(varuint32(1)), 78 | 79 | element_section([ 80 | elem_segment(varuint32(0), init_expr([ i32.const(0) ]), [varuint32(0)]), 81 | ]), 82 | 83 | code_section([ 84 | 85 | function_body([], []), // () 86 | 87 | // func (local0 i32) i32 { 88 | function_body( 89 | [local_entry(varuint32(1), i32)], // var local1 i32 = 0 90 | [ 91 | set_local(1, i32.mul( 92 | get_local(i32,0), 93 | i32.const(4) 94 | )), 95 | 96 | // loop(void_, [ 97 | // Drop(f32.const(123.4567)), 98 | // nop, 99 | // nop, 100 | // ]), 101 | 102 | // if (local1 <= 0) { set local1 = 1 } // avoid infinite loop below 103 | if_(void_, i32.le_s(get_local(i32,1), i32.const(0)), [ 104 | set_local(1, i32.const(1)) 105 | ]), 106 | 107 | // do { 108 | // set local1 = (local1 * 2) 109 | // } while (local1 < 9000) 110 | // loop(void_, [ 111 | // set_local(1, 112 | // i32.mul(get_local(i32,1), i32.const(2)) ), // set local1 = local1 * 2 113 | // br_if(0, // continue if 114 | // i32.lt_u(get_local(i32,1), i32.const(9000)) ), // local1 < 9000 115 | // ]), 116 | 117 | // while ( (set local1 = (local1 * 2)) < 9000 ) { 118 | // } 119 | loop(void_, [ 120 | br_if(0, // (continue-if (lt (set local1 (* (get local1) 2)) 9000)) 121 | i32.lt_u( 122 | tee_local(1, i32.mul(get_local(i32,1), i32.const(2))), 123 | i32.const(9000) 124 | ) 125 | ), 126 | ]), 127 | 128 | void_block([ 129 | nop, 130 | ]), 131 | 132 | // if_(void_, 133 | // i32.const(1), // cond 134 | // [nop, drop(i32.const(10000)) ], // then 135 | // [nop, drop(i32.const(0)) ], // else 136 | // ), 137 | 138 | // Calls spectest.print (i32) void 139 | call(void_, varuint32(0), [ 140 | i32.const(123) 141 | ]), 142 | 143 | set_local(1, 144 | if_(i32, 145 | i32.lt_s(i32.const(1), i32.const(3)), // condition 146 | [ // then 147 | nop, 148 | i32.wrap_i64( 149 | i64.mul( 150 | i64.extend_s_i32( 151 | get_local(i32,1) 152 | ), 153 | i64.const(-0xffffffff) 154 | ) 155 | ) 156 | ],[ // else 157 | nop, 158 | i32.const(0) 159 | ] 160 | ) 161 | ), 162 | 163 | set_local(1, 164 | select( 165 | get_local(i32,1), 166 | i32.mul( 167 | get_local(i32,1), 168 | i32.trunc_s_f32( 169 | call(f32, varuint32(3), [ 170 | f32.const(2) 171 | ]) 172 | ) 173 | ), 174 | i32.const(333) 175 | ) 176 | ), 177 | 178 | i32.store(align32, i32.const(0), 179 | get_local(i32,1) 180 | ), 181 | drop(void_, grow_memory(i32.const(2))), 182 | set_local(1, i32.const(0)), 183 | // Note: no need for `return` if the stack contains N values where N == return_count 184 | i32.load(align32, i32.const(0)) // load previosuly stored result 185 | // i32.load(align32, i32.const(4)) // load from data section 186 | // get_local(i32,1) 187 | // i32.const(1) 188 | // current_memory // query number of allocated memory pages 189 | ] 190 | ), 191 | 192 | // func (local0 f32) f32 { 193 | // return local0 + 123.4567 194 | // } 195 | function_body( 196 | [], // no locals 197 | [ 198 | f32.add(get_local(f32,0), f32.const(123.4567)) 199 | ] 200 | ), 201 | 202 | ]), // end code_section 203 | 204 | data_section([ 205 | data_segment( 206 | varuint32(0), // linear memory index (==0 in MVP) 207 | init_expr([i32.const(0)]), // offset at which to place the data 208 | data([0,0,0,0, 44,0,0,0]) 209 | ), 210 | ]), 211 | 212 | custom_section(str_ascii("names"), []), 213 | custom_section(str_utf8("Äntligen"), [ data([1,2,3]) ]), 214 | ]) 215 | 216 | if (!process.isUnitTest) { 217 | // repr 218 | const {repr} = require('../build/repr.js') 219 | repr(mod, s => { process.stdout.write(s) }) 220 | process.stdout.write('\n') 221 | 222 | // lbtext print code of each function body 223 | const lbtext = require('../build/lbtext.js') 224 | const codeSection = ast.get.section(mod, ast.sect_id.code) 225 | for (let funcBody of ast.get.function_bodies(codeSection)) { 226 | console.log('———— function ————') 227 | lbtext.printCode(funcBody.code, s => { process.stdout.write(s) }) 228 | } 229 | } 230 | 231 | // emit WASM code 232 | const {BufferedEmitter} = require('../build/emit.js') 233 | const emitter = new BufferedEmitter(new ArrayBuffer(mod.z)) 234 | mod.emit(emitter) 235 | 236 | if (!process.isUnitTest) { 237 | const {reprBuffer} = require('../build/repr.js') 238 | reprBuffer(emitter.buffer, s => { process.stdout.write(s) }) 239 | // 240 | // Copy the output from emitter.repr and paste it into the multiline string 241 | // of this chunk, then paste & run in a JS env with WebAssembly: 242 | // 243 | // WebAssembly.compile(new Uint8Array( 244 | // `00 61 73 6d 0d 00 00 00 01 09 02 60 00 00 60 01 245 | // 7f 01 7f 03 03 02 00 01 05 04 01 00 80 01 07 07 246 | // 01 03 66 6f 6f 00 01 08 01 00 0a 3a 02 02 00 0b 247 | // 35 01 01 7f 20 00 41 04 6c 21 01 03 40 01 01 01 248 | // 0b 03 7f 41 01 0b 20 01 41 e4 00 6c 41 cd 02 20 249 | // 01 1b 21 01 41 00 20 01 36 02 00 41 00 21 01 41 250 | // 00 28 02 00 0f 0b 0b 0e 01 00 41 00 0b 08 00 00 251 | // 00 00 2c 00 00 00`.split(/[\s\r\n]+/g).map(v => parseInt(v, 16)) 252 | // )).then(mod => { 253 | // let m = new WebAssembly.Instance(mod) 254 | // console.log('m.exports.foo() =>', m.exports.foo(1)) 255 | // }) 256 | } 257 | 258 | function Test() { 259 | // Run generated WASM module binary through the spec interpreter and 260 | // finally print the text format generated. 261 | const { specEval } = require('../build/eval.js') 262 | return specEval(emitter.buffer, { 263 | eval: '(invoke "foo" (i32.const 3))', 264 | logErrors: !process.isUnitTest, 265 | trace: !process.isUnitTest, 266 | }).then(stdout => { 267 | if (!process.isUnitTest) { 268 | console.log(stdout) 269 | } 270 | const expected = '1536000 : i32' 271 | assert.equal(stdout.substr(stdout.length - expected.length), expected) 272 | }).catch(err => { 273 | console.error(err.stack || String(err)) 274 | if (!process.isUnitTest) { 275 | process.exit(1) 276 | } else { 277 | throw err 278 | } 279 | }) 280 | } 281 | 282 | if (!process.isUnitTest) { 283 | Test() 284 | } 285 | -------------------------------------------------------------------------------- /test/mem_align_checks_test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | require('source-map-support').install() 3 | 4 | const assert = require('assert') 5 | const ast = require('../build/ast.js') 6 | const {i32,i64,f32,f64,align64,align32,align16,align8} = ast.c 7 | 8 | assert( ! i32.addrIsAligned(align64, 0), '[i32] align 64, offs 0, addr 0') 9 | assert( i32.addrIsAligned(align32, 0), '[i32] align 32, offs 0, addr 0') 10 | assert( i32.addrIsAligned(align16, 0), '[i32] align 16, offs 0, addr 0') 11 | assert( i32.addrIsAligned(align8, 0), '[i32] align 8, offs 0, addr 0') 12 | assert( ! i32.addrIsAligned(align32, 1), '[i32] align 32, offs 0, addr 1') 13 | assert( ! i32.addrIsAligned(align16, 1), '[i32] align 16, offs 0, addr 1') 14 | assert( i32.addrIsAligned(align8, 1), '[i32] align 8, offs 0, addr 1') 15 | assert( ! i32.addrIsAligned(align32, 2), '[i32] align 32, offs 0, addr 2') 16 | assert( i32.addrIsAligned(align16, 2), '[i32] align 16, offs 0, addr 2') 17 | assert( i32.addrIsAligned(align8, 2), '[i32] align 8, offs 0, addr 2') 18 | assert( i32.addrIsAligned(align32, 4), '[i32] align 32, offs 0, addr 4') 19 | assert( i32.addrIsAligned(align16, 4), '[i32] align 16, offs 0, addr 4') 20 | assert( i32.addrIsAligned(align8, 4), '[i32] align 8, offs 0, addr 4') 21 | assert( i32.addrIsAligned(align32, 8), '[i32] align 32, offs 0, addr 8') 22 | assert( i32.addrIsAligned(align16, 8), '[i32] align 16, offs 0, addr 8') 23 | assert( i32.addrIsAligned(align8, 8), '[i32] align 8, offs 0, addr 8') 24 | 25 | assert( i64.addrIsAligned(align64, 0), '[i64] align 64, offs 0, addr 0') 26 | assert( i64.addrIsAligned(align32, 0), '[i64] align 32, offs 0, addr 0') 27 | assert( i64.addrIsAligned(align16, 0), '[i64] align 16, offs 0, addr 0') 28 | assert( i64.addrIsAligned(align8, 0), '[i64] align 8, offs 0, addr 0') 29 | assert( ! i64.addrIsAligned(align64, 1), '[i64] align 64, offs 0, addr 1') 30 | assert( ! i64.addrIsAligned(align32, 1), '[i64] align 32, offs 0, addr 1') 31 | assert( ! i64.addrIsAligned(align16, 1), '[i64] align 16, offs 0, addr 1') 32 | assert( i64.addrIsAligned(align8, 1), '[i64] align 8, offs 0, addr 1') 33 | assert( ! i64.addrIsAligned(align64, 2), '[i64] align 64, offs 0, addr 2') 34 | assert( ! i64.addrIsAligned(align32, 2), '[i64] align 32, offs 0, addr 2') 35 | assert( i64.addrIsAligned(align16, 2), '[i64] align 16, offs 0, addr 2') 36 | assert( i64.addrIsAligned(align8, 2), '[i64] align 8, offs 0, addr 2') 37 | assert( ! i64.addrIsAligned(align64, 4), '[i64] align 64, offs 0, addr 4') 38 | assert( i64.addrIsAligned(align32, 4), '[i64] align 32, offs 0, addr 4') 39 | assert( i64.addrIsAligned(align16, 4), '[i64] align 16, offs 0, addr 4') 40 | assert( i64.addrIsAligned(align8, 4), '[i64] align 8, offs 0, addr 4') 41 | assert( i64.addrIsAligned(align64, 8), '[i64] align 64, offs 0, addr 8') 42 | assert( i64.addrIsAligned(align32, 8), '[i64] align 32, offs 0, addr 8') 43 | assert( i64.addrIsAligned(align16, 8), '[i64] align 16, offs 0, addr 8') 44 | assert( i64.addrIsAligned(align8, 8), '[i64] align 8, offs 0, addr 8') 45 | 46 | assert( ! f32.addrIsAligned(align64, 0), '[f32] align 64, offs 0, addr 0') 47 | assert( f32.addrIsAligned(align32, 0), '[f32] align 32, offs 0, addr 0') 48 | assert( f32.addrIsAligned(align16, 0), '[f32] align 16, offs 0, addr 0') 49 | assert( f32.addrIsAligned(align8, 0), '[f32] align 8, offs 0, addr 0') 50 | assert( ! f32.addrIsAligned(align32, 1), '[f32] align 32, offs 0, addr 1') 51 | assert( ! f32.addrIsAligned(align16, 1), '[f32] align 16, offs 0, addr 1') 52 | assert( f32.addrIsAligned(align8, 1), '[f32] align 8, offs 0, addr 1') 53 | assert( ! f32.addrIsAligned(align32, 2), '[f32] align 32, offs 0, addr 2') 54 | assert( f32.addrIsAligned(align16, 2), '[f32] align 16, offs 0, addr 2') 55 | assert( f32.addrIsAligned(align8, 2), '[f32] align 8, offs 0, addr 2') 56 | assert( f32.addrIsAligned(align32, 4), '[f32] align 32, offs 0, addr 4') 57 | assert( f32.addrIsAligned(align16, 4), '[f32] align 16, offs 0, addr 4') 58 | assert( f32.addrIsAligned(align8, 4), '[f32] align 8, offs 0, addr 4') 59 | assert( f32.addrIsAligned(align32, 8), '[f32] align 32, offs 0, addr 8') 60 | assert( f32.addrIsAligned(align16, 8), '[f32] align 16, offs 0, addr 8') 61 | assert( f32.addrIsAligned(align8, 8), '[f32] align 8, offs 0, addr 8') 62 | 63 | assert( f64.addrIsAligned(align64, 0), '[f64] align 64, offs 0, addr 0') 64 | assert( f64.addrIsAligned(align32, 0), '[f64] align 32, offs 0, addr 0') 65 | assert( f64.addrIsAligned(align16, 0), '[f64] align 16, offs 0, addr 0') 66 | assert( f64.addrIsAligned(align8, 0), '[f64] align 8, offs 0, addr 0') 67 | assert( ! f64.addrIsAligned(align64, 1), '[f64] align 64, offs 0, addr 1') 68 | assert( ! f64.addrIsAligned(align32, 1), '[f64] align 32, offs 0, addr 1') 69 | assert( ! f64.addrIsAligned(align16, 1), '[f64] align 16, offs 0, addr 1') 70 | assert( f64.addrIsAligned(align8, 1), '[f64] align 8, offs 0, addr 1') 71 | assert( ! f64.addrIsAligned(align64, 2), '[f64] align 64, offs 0, addr 2') 72 | assert( ! f64.addrIsAligned(align32, 2), '[f64] align 32, offs 0, addr 2') 73 | assert( f64.addrIsAligned(align16, 2), '[f64] align 16, offs 0, addr 2') 74 | assert( f64.addrIsAligned(align8, 2), '[f64] align 8, offs 0, addr 2') 75 | assert( ! f64.addrIsAligned(align64, 4), '[f64] align 64, offs 0, addr 4') 76 | assert( f64.addrIsAligned(align32, 4), '[f64] align 32, offs 0, addr 4') 77 | assert( f64.addrIsAligned(align16, 4), '[f64] align 16, offs 0, addr 4') 78 | assert( f64.addrIsAligned(align8, 4), '[f64] align 8, offs 0, addr 4') 79 | assert( f64.addrIsAligned(align64, 8), '[f64] align 64, offs 0, addr 8') 80 | assert( f64.addrIsAligned(align32, 8), '[f64] align 32, offs 0, addr 8') 81 | assert( f64.addrIsAligned(align16, 8), '[f64] align 16, offs 0, addr 8') 82 | assert( f64.addrIsAligned(align8, 8), '[f64] align 8, offs 0, addr 8') 83 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | const assert = require('assert') 4 | const vm = require('vm') 5 | const fs = require('fs') 6 | const { basename, dirname, extname } = require('path') 7 | 8 | let logDebug = function(){}; 9 | let debugMode = false; 10 | let isInteractive = null; // [stdoutistty:bool, stderristty:bool] 11 | 12 | function style(streamno, style, what) { 13 | if (!isInteractive[streamno]) { 14 | return what; 15 | } 16 | const styles = { 17 | 'bold' : ['1', '22'], 18 | 'italic' : ['3', '23'], 19 | 'underline' : ['4', '24'], 20 | 'inverse' : ['7', '27'], 21 | 22 | 'white' : ['37', '39'], 23 | 'grey' : ['90', '39'], 24 | 'black' : ['30', '39'], 25 | 'blue' : ['34', '39'], 26 | 'cyan' : ['36', '39'], 27 | 'green' : ['32', '39'], 28 | 'magenta' : ['35', '39'], 29 | 'red' : ['31', '39'], 30 | 'yellow' : ['33', '39'], 31 | 32 | 'boldWhite' : ['1;37', '0;39'], 33 | 'boldGrey' : ['1;90', '0;39'], 34 | 'boldBlack' : ['1;30', '0;39'], 35 | 'boldBlue' : ['1;34', '0;39'], 36 | 'boldCyan' : ['1;36', '0;39'], 37 | 'boldGreen' : ['1;32', '0;39'], 38 | 'boldMagenta' : ['1;35', '0;39'], 39 | 'boldRed' : ['1;31', '0;39'], 40 | 'boldYellow' : ['1;33', '0;39'], 41 | 42 | 'italicWhite' : ['3;37', '0;39'], 43 | 'italicGrey' : ['3;90', '0;39'], 44 | 'italicBlack' : ['3;30', '0;39'], 45 | 'italicBlue' : ['3;34', '0;39'], 46 | 'italicCyan' : ['3;36', '0;39'], 47 | 'italicGreen' : ['3;32', '0;39'], 48 | 'italicMagenta' : ['3;35', '0;39'], 49 | 'italicRed' : ['3;31', '0;39'], 50 | 'italicYellow' : ['3;33', '0;39'], 51 | }; 52 | let v = styles[style]; 53 | return `\x1b[${v[0]}m${what}\x1b[${v[1]}m`; 54 | } 55 | 56 | 57 | function readfile(filename) { 58 | return new Promise((resolve, reject) => { 59 | fs.readFile(filename, {encoding:'utf8'}, (e, s) => { 60 | if (e) { reject(e) } else { resolve(s) } 61 | }) 62 | }) 63 | } 64 | 65 | function writefile(filename, data) { 66 | return new Promise((resolve, reject) => { 67 | let triedmkdir = false 68 | function tryw() { 69 | fs.writeFile(filename, data, e => { 70 | if (e) { 71 | if (!triedmkdir && e.code == 'ENOENT') { 72 | triedmkdir = true 73 | fs.mkdir(dirname(filename), e2 => { 74 | if (e2) { reject(e) } else { tryw() } 75 | }) 76 | } else { 77 | reject(e) 78 | } 79 | } else { 80 | resolve() 81 | } 82 | }) 83 | } 84 | tryw() 85 | }) 86 | } 87 | 88 | 89 | function runJSTestInSandbox(src, filename, name, sandbox) { // :Promise 90 | logDebug('run', name); 91 | return new Promise((resolve, reject) => { 92 | let expectError = null; 93 | let m = src.match(/\/\/\!error\s+(.+)\s*(?:\n|$)/); 94 | if (m) { 95 | expectError = m[1]; 96 | if (expectError[0] != '/') { 97 | throw new Error('"//!error" value must be a regex (/.../)'); 98 | } 99 | expectError = vm.runInNewContext(expectError); 100 | } 101 | 102 | let script = new vm.Script(src, { 103 | filename, 104 | displayErrors: true, 105 | timeout: 5000, 106 | }); 107 | 108 | let ctx = { 109 | require, 110 | console, 111 | assert, 112 | setTimeout, 113 | clearTimeout, 114 | Buffer, 115 | isUnitTest: true, 116 | __dirname: dirname(filename), 117 | process: { 118 | stdout: process.stdout, 119 | stderr: process.stderr, 120 | isUnitTest: true, 121 | nextTick: process.nextTick, 122 | exit(code){ throw new Error('EXIT ' + code) }, 123 | } 124 | } 125 | if (sandbox) { 126 | Object.extend(ctx, sandbox) 127 | } 128 | 129 | if (expectError) { 130 | assert.throws(() => { 131 | script.runInNewContext(ctx); 132 | }, expectError); 133 | } else { 134 | logDebug(`script.runInNewContext("${filename}") ENTER`) 135 | script.runInNewContext(ctx); 136 | logDebug(`script.runInNewContext("${filename}") EXIT`) 137 | 138 | if (ctx.Test) { 139 | let p = ctx.Test(); 140 | if (typeof p == 'object' && typeof p.then == 'function') { 141 | // Test function returned a promise 142 | p.then(resolve).catch(reject); 143 | return; 144 | } 145 | } 146 | } 147 | 148 | resolve(); 149 | }); 150 | } 151 | 152 | function tsc(source, filename) { // :Promise 153 | return new Promise((resolve, reject) => { 154 | const ts = require(__dirname + '/../node_modules/typescript/lib/typescript.js') 155 | const compilerOptions = { 156 | module: ts.ModuleKind.CommonJS, 157 | target: ts.ScriptTarget.Latest, 158 | noImplicitAny: false, 159 | inlineSourceMap: true, 160 | alwaysStrict: true, 161 | forceConsistentCasingInFileNames: true, 162 | noEmitOnError: true, 163 | lib: [ 164 | "dom", 165 | "es6", 166 | ], 167 | pretty: true 168 | } 169 | 170 | let r = ts.transpileModule(source, { 171 | compilerOptions, 172 | fileName: filename, 173 | reportDiagnostics: true, 174 | moduleName: basename(filename, extname(filename)), 175 | }) 176 | 177 | if (r.diagnostics && r.diagnostics.length) { 178 | const log = { 179 | [ts.DiagnosticCategory.Warning]: console.warn.bind(console, '[tsc warning]'), 180 | [ts.DiagnosticCategory.Error]: console.error.bind(console, '[tsc error]'), 181 | [ts.DiagnosticCategory.Message]: console.log.bind(console, '[tsc]'), 182 | } 183 | let firstError = null 184 | for (let d of r.diagnostics) { 185 | let prefix = '' 186 | if (d.file) { 187 | const pos = d.file.getLineAndCharacterOfPosition(d.start) 188 | prefix += (d.file.fileName.endsWith(filename) ? 189 | basename(filename) : d.file.fileName) + 190 | ':' + pos.line + ':' + pos.character + ': ' 191 | } 192 | prefix += 'TS'+d.code+':' 193 | log[d.category](prefix, d.messageText) 194 | if (!firstError && d.category == ts.DiagnosticCategory.Error) { 195 | firstError = d.messageText 196 | } 197 | } 198 | if (firstError) { 199 | return reject(new Error(firstError)) 200 | } 201 | } 202 | 203 | resolve(r.outputText) 204 | }) 205 | } 206 | 207 | function getCompiledTypeScript(filename) { // :Promise 208 | assert.equal(extname(filename), '.ts') 209 | 210 | const outfilename = __dirname + '/build/' + basename(filename, extname(filename)) + '.js' 211 | 212 | // has input been modified since output was modified? 213 | const instat = fs.statSync(filename) 214 | try { 215 | const outstat = fs.statSync(outfilename) 216 | if (instat.mtime.getTime() <= outstat.mtime.getTime()) { 217 | // infile hasn't changed since outfile did 218 | return readfile(outfilename) 219 | } 220 | } catch (_) {} 221 | 222 | logDebug('tsc', filename, '->', outfilename) 223 | 224 | return readfile(filename) 225 | .then(source => tsc(source, filename)) 226 | .then(source => writefile(outfilename, source) 227 | .then(() => source)) 228 | } 229 | 230 | function runJSTest(filename, name) { 231 | return ( 232 | extname(filename) == '.ts' 233 | ? getCompiledTypeScript(filename) 234 | : readfile(filename) 235 | ).catch(e => { 236 | console.error(e.stack || String(e)) 237 | throw e 238 | }).then(source => 239 | runJSTestInSandbox(source, filename, name).then(() => { 240 | process.stdout.write( 241 | `${style(0,'boldGreen','pass')} ${name}\n` 242 | ) 243 | }).catch(err => { 244 | process.stderr.write( 245 | `${style(1,'boldRed','FAIL')} ${name}:\n${err.stack || String(err)}\n` 246 | ) 247 | throw err; 248 | }) 249 | ) 250 | } 251 | 252 | const suffix = '_test'; 253 | const fileexts = ['.js', '.ts'] 254 | 255 | function runAllTests() { 256 | return Promise.all( 257 | fs.readdirSync(__dirname).filter(fn => { 258 | const ext = extname(fn) 259 | return ext.includes(ext) && basename(fn, ext).substr(-suffix.length) == suffix 260 | }).map(fn => 261 | runJSTest(__dirname + '/' + fn, fn.substr(0, fn.length - extname(fn).length - suffix.length)) 262 | ) 263 | ) 264 | } 265 | 266 | function isFileSync(filename) { 267 | try { 268 | const st = fs.statSync(filename) 269 | return st.isFile() 270 | } catch (_) {} 271 | return false 272 | } 273 | 274 | function testNameToFilename(test) { 275 | const mkname = s => { 276 | const b = basename(s, ext) 277 | return b.substr(0, b.length - suffix.length) 278 | } 279 | 280 | let ext = extname(test) 281 | if (test[0] == '/' || isFileSync(test)) { 282 | return { path: test, name: mkname(test) } 283 | } 284 | 285 | for (let ext of fileexts) { 286 | let name = test + suffix + ext 287 | let path = __dirname + '/' + name 288 | if (isFileSync(path)) { 289 | return { path, name: test } 290 | } 291 | } 292 | 293 | let path = __dirname + '/' + test; 294 | if (isFileSync(path)) { 295 | return {path, name: mkname(test)} 296 | } 297 | 298 | throw new Error(`test not found: "${test}"`); 299 | } 300 | 301 | function runSomeTests(tests) { 302 | return Promise.all( 303 | tests.map(testNameToFilename).map(f => 304 | runJSTest(f.path, f.name)) 305 | ) 306 | } 307 | 308 | let args = process.argv.slice(2); 309 | let timeout = 30000; 310 | let timeoutTimer; 311 | 312 | let onerr = err => { 313 | clearTimeout(timeoutTimer); 314 | console.error(style(1,'boldRed', 'aborted by error')); 315 | process.exit(1); 316 | }; 317 | 318 | let onallpass = () => { 319 | console.log(style(0,'boldGreen','all pass')); 320 | clearTimeout(timeoutTimer); 321 | } 322 | 323 | timeoutTimer = setTimeout(function(){ 324 | console.error(style(1,'boldRed', `timeout after ${(timeout/1000).toFixed(2)}s`)); 325 | onerr(); 326 | }, timeout); 327 | 328 | function checkflag(flag) { 329 | let i = args.indexOf('-' + flag) 330 | if (i == -1) { i = args.indexOf('--' + flag) } 331 | if (i != -1) { 332 | args.splice(i,1) 333 | return true 334 | } 335 | return false 336 | } 337 | 338 | if (args.indexOf('-h') != -1 || args.indexOf('--help') != -1) { 339 | console.error([ 340 | 'Usage: test [options] [ ...]', 341 | 'options:', 342 | ' --debug Enable debug mode; prints detailed information.', 343 | ' --[non-]interactive Instead of checking if stdout is interactive, be explicit', 344 | ' about whether colors and other TTY-related things are output.', 345 | ].join('\n')); 346 | process.exit(1); 347 | } 348 | 349 | if (checkflag('non-interactive')) { 350 | isInteractive = [false,false]; 351 | } 352 | if (checkflag('interactive')) { 353 | isInteractive = [true,true]; 354 | } 355 | if (isInteractive === null) { 356 | // auto-detect 357 | isInteractive = [process.stdout.isTTY,process.stderr.isTTY]; 358 | } 359 | 360 | if (checkflag('debug')) { 361 | debugMode = true; 362 | logDebug = function() { 363 | let args = Array.prototype.slice.call(arguments); 364 | args.unshift(style(0,'boldMagenta','D')); 365 | console.log.apply(console, args); 366 | }; 367 | } 368 | 369 | 370 | (args.length ? runSomeTests(args) : runAllTests()).catch(onerr).then(onallpass); 371 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ESNEXT", 5 | "noImplicitAny": false, 6 | "sourceMap": true, 7 | "alwaysStrict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "lib": [ 10 | "dom", 11 | "es6" 12 | ], 13 | "noEmitOnError": true, 14 | "outDir": "build", 15 | "declaration": true, 16 | "pretty": true, 17 | "rootDir": "src" 18 | }, 19 | "include": [ 20 | "src/**/*.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | source-map-support@^0.4.8: 6 | version "0.4.8" 7 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.8.tgz#4871918d8a3af07289182e974e32844327b2e98b" 8 | dependencies: 9 | source-map "^0.5.3" 10 | 11 | source-map@^0.5.3: 12 | version "0.5.6" 13 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" 14 | 15 | typescript@^2.1.5: 16 | version "2.1.5" 17 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.1.5.tgz#6fe9479e00e01855247cea216e7561bafcdbcd4a" 18 | --------------------------------------------------------------------------------