DISABLE FDs (REMOVE ME).
[jackhill/mal.git] / wasm / run.js
CommitLineData
89149437
JM
1#!/usr/bin/env node
2
3// Copyright Joel Martin
4// License MIT
5
6const { promisify } = require('util')
7const fs = require('fs')
8const readFile = promisify(fs.readFile)
9const assert = require('assert')
10const { TextDecoder, TextEncoder } = require('text-encoding')
11const node_readline = require('./node_readline.js')
12
13assert('WebAssembly' in global, 'WebAssembly not detected')
14
15//
16// Memory interaction utilities
17//
18
19// Convert node Buffer to Uint8Array
20function toUint8Array(buf) {
21 let u = new Uint8Array(buf.length)
22 for (let i = 0; i < buf.length; ++i) {
23 u[i] = buf[i]
24 }
25 return u
26}
27
28// Read null terminated string out of webassembly memory
29function get_string(memory, addr) {
30 //console.warn("get_string:", addr)
31 let u8 = new Uint8Array(memory.buffer, addr)
32 let length = u8.findIndex(e => e == 0)
33 let bytes = new Uint8Array(memory.buffer, addr, length)
34 let str = new TextDecoder('utf8').decode(bytes)
35 return str
36}
37
38// Write null terminated string into webassembly memory
39function put_string(memory, addr, str, max_length) {
40 let buf8 = new Uint8Array(memory.buffer, addr)
41
42 let bytes = new TextEncoder('utf8').encode(str)
43 if (max_length && bytes.length > max_length) {
44 bytes = bytes.slice(0, max_length)
45 }
46
47 buf8.set(bytes, 0)
48 buf8[bytes.length] = 0 // null terminator
49 return bytes.length+1
50}
51
52// Put argv structure at beginning of memory
53function marshal_argv(memory, offset, args) {
54 let view = new DataView(memory.buffer, offset)
55 let buf8 = new Uint8Array(memory.buffer, offset)
56
57 let stringStart = (args.length + 1) * 4
58 for (let i = 0; i < args.length; i++) {
59 let len = put_string(memory, stringStart, args[i])
60 view.setUint32(i*4, stringStart, true)
61 stringStart = stringStart + len
62 }
63 view.setUint32(args.length*4, 0, true)
64 return offset + stringStart // start of free memory
65}
66
67// Based on:
68// https://gist.github.com/kripken/59c67556dc03bb6d57052fedef1e61ab
69
70// Loads a WebAssembly dynamic library, returns a promise.
71async function loadWebAssembly(filename, args) {
72 // Fetch the file and compile it
73 const wasm_str = await readFile(filename)
74 const wasm_bin = toUint8Array(wasm_str)
75 const module = await WebAssembly.compile(wasm_bin)
76 let memory = new WebAssembly.Memory({ initial: 256 })
77 // Core imports
76adfab9 78 function printline(addr, stream) {
89149437
JM
79 console.log(get_string(memory, addr).replace(/\n$/, ''))
80 }
81
82 // Returns addr on success and -1 on failure
83 // Truncates to max_length
84 function readline(prompt, addr, max_length) {
85 let line = node_readline.readline(get_string(memory, prompt))
86 if (line === null) { return 0 }
87 put_string(memory, addr, line, max_length)
88 return 1
89 }
90
91 function read_file(path_addr, buf) {
92 let path = get_string(memory, path_addr)
93 let contents = fs.readFileSync(path, 'utf8')
94 return put_string(memory, buf, contents)
95 }
96
6a51946b 97 function get_time_ms() {
266391c5
JM
98 // subtract 30 years to make sure it fits into i32 without
99 // wrapping or becoming negative
100 return (new Date()).getTime() - 0x38640900
76adfab9
JM
101 }
102
89149437
JM
103 // Marshal arguments
104 const memoryStart = 0
105 let memoryBase = marshal_argv(memory, memoryStart, args)
106 memoryBase = memoryBase + (8 - (memoryBase % 8))
107
108 // Create the imports for the module, including the
109 // standard dynamic library imports
110 imports = {}
111 imports.env = {}
112 imports.env.exit = process.exit
76adfab9 113 imports.env.printline = printline
89149437 114 imports.env.readline = readline
89149437 115 imports.env.read_file = read_file
6a51946b
JM
116 imports.env.get_time_ms = get_time_ms
117
6a51946b
JM
118 imports.env.stdout = 0
119 imports.env.fputs = printline
89149437
JM
120
121 imports.env.memory = memory
122 imports.env.memoryBase = memoryBase
123 imports.env.table = new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
124 imports.env.tableBase = imports.env.tableBase || 0
125 // Create the instance.
126 return [new WebAssembly.Instance(module, imports), args.length, 0]
127}
128
129async function main() {
130 assert(process.argv.length >= 3,
131 'Usage: ./run.js prog.wasm [ARGS...]')
132
133 const wasm = process.argv[2]
134 const args = process.argv.slice(2)
135 const [instance, argc, argv] = await loadWebAssembly(wasm, args)
136
137 let exports = instance.exports
138 assert(exports, 'no exports found')
139 assert('_main' in exports, '_main not found in wasm module exports')
140 if ('__post_instantiate' in exports) {
141 //console.warn('calling exports.__post_instantiate()')
142 exports['__post_instantiate']()
143 }
144 //console.warn(`calling exports._main(${argc}, ${argv})`)
145 let start = new Date()
146 let res = exports['_main'](argc, argv)
147 let end = new Date()
148 //console.warn('runtime: ' + (end-start) + 'ms')
149 process.exit(res)
150}
151
152if (module.parent) {
153 module.exports.loadWebAssembly = loadWebAssembly
154} else {
155 main()
156}