git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 19b980f7bfa315ae01afacb96f1bb76873270b53

Files: 19b980f7bfa315ae01afacb96f1bb76873270b53 / wasmContainer.js

9979 bytesRaw
1const {wasm2json, json2wasm} = require('wasm-json-toolkit')
2const wasmMetering = require('wasm-metering')
3const ReferanceMap = require('reference-map')
4const Message = require('./message.js')
5const customTypes = require('./customTypes.js')
6const injectGlobals = require('./injectGlobals.js')
7const typeCheckWrapper = require('./typeCheckWrapper.js')
8const {FunctionRef, ModuleRef, DEFAULTS} = require('./systemObjects.js')
9
10const nativeTypes = new Set(['i32', 'i64', 'f32', 'f64'])
11const LANGUAGE_TYPES = {
12 0x0: 'actor',
13 0x1: 'buf',
14 0x02: 'elem',
15 0x03: 'link',
16 0x04: 'id',
17 0x7f: 'i32',
18 0x7e: 'i64',
19 0x7d: 'f32',
20 0x7c: 'f64',
21 0x70: 'anyFunc',
22 0x60: 'func',
23 0x40: 'block_type'
24}
25const FUNC_INDEX_OFFSET = 1
26
27function generateWrapper (funcRef, container) {
28 let wrapper = typeCheckWrapper(funcRef.params)
29 const wasm = json2wasm(wrapper)
30 const mod = WebAssembly.Module(wasm)
31 const self = funcRef
32 wrapper = WebAssembly.Instance(mod, {
33 'env': {
34 'checkTypes': function () {
35 const args = [...arguments]
36 const checkedArgs = []
37 while (args.length) {
38 const type = LANGUAGE_TYPES[args.shift()]
39 let arg = args.shift()
40 if (!nativeTypes.has(type)) {
41 arg = container.refs.get(arg, type)
42 }
43 checkedArgs.push(arg)
44 }
45 const message = new Message({
46 funcRef: self,
47 funcArguments: checkedArgs
48 }).on('execution:error', e => {
49 console.log(e)
50 })
51 container.actor.send(message)
52 }
53 }
54 })
55 wrapper.exports.check.object = funcRef
56 return wrapper
57}
58
59module.exports = class WasmContainer {
60 constructor (actor) {
61 this.actor = actor
62 this.refs = new ReferanceMap()
63 }
64
65 static createModule (wasm, id) {
66 if (!WebAssembly.validate(wasm)) {
67 throw new Error('invalid wasm binary')
68 }
69
70 let moduleJSON = wasm2json(wasm)
71 const json = customTypes.mergeTypeSections(moduleJSON)
72 moduleJSON = wasmMetering.meterJSON(moduleJSON, {
73 meterType: 'i32'
74 })
75
76 // initialize the globals
77 let numOfGlobals = json.globals.length
78 if (numOfGlobals) {
79 moduleJSON = injectGlobals(moduleJSON, json.globals)
80 }
81 // recompile the wasm
82 wasm = json2wasm(moduleJSON)
83 const modRef = ModuleRef.fromMetaJSON(json, id)
84 return {
85 wasm,
86 json,
87 modRef
88 }
89 }
90
91 static async onCreation (unverifiedWasm, id, tree) {
92 const cachedb = tree.dag._dag
93 let {json, wasm, modRef} = this.createModule(unverifiedWasm, id)
94 await Promise.all([
95 new Promise((resolve, reject) => {
96 cachedb.put(id.id.toString() + 'meta', JSON.stringify(json), resolve)
97 }),
98 new Promise((resolve, reject) => {
99 cachedb.put(id.id.toString() + 'code', wasm.toString('hex'), resolve)
100 })
101 ])
102 return modRef
103 }
104
105 getInterface (funcRef) {
106 const self = this
107 return {
108 func: {
109 externalize: index => {
110 const func = this.instance.exports.table.get(index)
111 const object = func.object
112 if (object) {
113 // externalize a pervously internalized function
114 return self.refs.add(object)
115 } else {
116 const params = self.json.types[self.json.indexes[func.name - FUNC_INDEX_OFFSET]].params
117 const ref = new FunctionRef(true, func.tableIndex, params, self.actor.id)
118 return self.refs.add(ref, 'func')
119 }
120 },
121 internalize: (index, ref) => {
122 const funcRef = self.refs.get(ref, 'func')
123 const wrapper = generateWrapper(funcRef, self)
124 this.instance.exports.table.set(index, wrapper.exports.check)
125 },
126 catch: (ref, catchRef) => {
127 const {funcRef} = self.refs.get(ref, FunctionRef)
128 const {funcRef: catchFunc} = self.refs.get(ref, FunctionRef)
129 funcRef.catch = catchFunc
130 },
131 get_gas_budget: (funcRef) => {
132 const func = self.refs.get(funcRef, 'func')
133 return func.gas
134 },
135 set_gas_budget: (funcRef, amount) => {
136 const func = self.refs.get(funcRef, 'func')
137 func.gas = amount
138 }
139 },
140 link: {
141 wrap: ref => {
142 const obj = this.refs.get(ref)
143 const link = {'/': obj}
144 return this.refs.add(link, 'link')
145 },
146 unwrap: async (ref, cb) => {
147 const obj = this.refs.get(ref, 'link')
148 const promise = this.actor.tree.dataStore.get(obj)
149 await this._opsQueue.push(promise)
150 // todo
151 }
152 },
153 module: {
154 new: dataRef => {
155
156 },
157 export: (modRef, bufRef) => {
158 const mod = this.refs.get(modRef, 'mod')
159 let name = this.refs.get(bufRef, 'buf')
160 name = Buffer.from(name).toString()
161 const funcRef = mod.getFuncRef(name)
162 return this.refs.add(funcRef, 'func')
163 },
164 self: () => {
165 return this.refs.add(this.modSelf, 'mod')
166 }
167 },
168 memory: {
169 externalize: (index, length) => {
170 const buf = Buffer.from(this.get8Memory(index, length))
171 return this.refs.add(buf, 'buf')
172 },
173 internalize: (dataRef, srcOffset, sinkOffset, length) => {
174 let buf = this.refs.get(dataRef, 'buf')
175 buf = buf.subarray(srcOffset, length)
176 const mem = this.get8Memory(sinkOffset, buf.length)
177 mem.set(buf)
178 },
179 length (dataRef) {
180 let buf = this.refs.get(dataRef, 'buf')
181 return buf.length
182 }
183 },
184 table: {
185 externalize: (index, length) => {
186 const mem = Buffer.from(this.get8Memory(index, length * 4))
187 const objects = []
188 while (length--) {
189 const ref = mem.readUInt32LE(length * 4)
190 const obj = this.refs.get(ref)
191 objects.unshift(obj)
192 }
193 return this.refs.add(objects, 'elem')
194 },
195 internalize: (elemRef, srcOffset, sinkOffset, length) => {
196 let table = this.refs.get(elemRef, 'elem')
197 const buf = table.slice(srcOffset, srcOffset + length).map(obj => this.refs.add(obj))
198 const mem = this.get32Memory(sinkOffset, length)
199 mem.set(buf)
200 },
201 length (elemRef) {
202 let elem = this.refs.get(elemRef, 'elem')
203 return elem.length
204 }
205 },
206 metering: {
207 usegas: amount => {
208 this.actor.incrementTicks(amount)
209 // funcRef.gas -= amount
210 if (funcRef.gas < 0) {
211 throw new Error('out of gas! :(')
212 }
213 }
214 }
215 }
216 }
217
218 async onMessage (message) {
219 const funcRef = message.funcRef
220 const intef = this.getInterface(funcRef)
221 this.instance = WebAssembly.Instance(this.mod, intef)
222 // map table indexes
223 const table = this.instance.exports.table
224 if (table) {
225 let length = table.length
226 while (length--) {
227 const func = table.get(length)
228 if (func) {
229 func.tableIndex = length
230 }
231 }
232 }
233 // import references
234 const args = message.funcArguments.map((arg, index) => {
235 const type = funcRef.params[index]
236 if (nativeTypes.has(type)) {
237 return arg
238 } else {
239 return this.refs.add(arg, type)
240 }
241 })
242
243 // setup globals
244 let numOfGlobals = this.json.globals.length
245 if (numOfGlobals) {
246 const refs = []
247 while (numOfGlobals--) {
248 const obj = this.actor.storage[numOfGlobals] || DEFAULTS[this.json.globals[numOfGlobals].type]
249 refs.push(this.refs.add(obj, this.json.globals[numOfGlobals].type))
250 }
251 this.instance.exports.setter_globals(...refs)
252 }
253
254 // call entrypoint function
255 if (funcRef.private) {
256 this.instance.exports.table.get(funcRef.identifier)(...args)
257 } else {
258 this.instance.exports[funcRef.identifier](...args)
259 }
260 await this.onDone()
261
262 // store globals
263 numOfGlobals = this.json.globals.length
264 if (numOfGlobals) {
265 this.actor.storage = []
266 this.instance.exports.getter_globals()
267 const mem = this.get32Memory(0, numOfGlobals)
268 while (numOfGlobals--) {
269 const ref = mem[numOfGlobals]
270 this.actor.storage.push(this.refs.get(ref, this.json.globals[numOfGlobals].type))
271 }
272 }
273
274 this.refs.clear()
275 }
276
277 /**
278 * returns a promise that resolves when the wasm instance is done running
279 * @returns {Promise}
280 */
281 async onDone () {
282 let prevOps
283 while (prevOps !== this._opsQueue) {
284 prevOps = this._opsQueue
285 await prevOps
286 }
287 }
288
289 /**
290 * Pushed an async operation to the a promise queue that
291 * @returns {Promise} the returned promise resolves in the order the intail
292 * operation was pushed to the queue
293 */
294 pushOpsQueue (promise) {
295 this._opsQueue = Promise.all([this._opsQueue, promise])
296 return this._opsQueue
297 }
298
299 async onStartup () {
300 let [json, wasm] = await Promise.all([
301 new Promise((resolve, reject) => {
302 this.actor.cachedb.get(this.actor.id.id.toString() + 'meta', (err, json) => {
303 if (err) {
304 reject(err)
305 } else {
306 resolve(json)
307 }
308 })
309 }),
310 new Promise((resolve, reject) => {
311 this.actor.cachedb.get(this.actor.id.id.toString() + 'code', (err, wasm) => {
312 if (err) {
313 reject(err)
314 } else {
315 resolve(wasm)
316 }
317 })
318 })
319 ])
320 wasm = Buffer.from(wasm, 'hex')
321 json = JSON.parse(json)
322 this.mod = WebAssembly.Module(wasm)
323 this.json = json
324 this.modSelf = ModuleRef.fromMetaJSON(json, this.actor.id)
325 }
326
327 get8Memory (offset, length) {
328 return new Uint8Array(this.instance.exports.memory.buffer, offset, length)
329 }
330
331 get32Memory (offset, length) {
332 return new Uint32Array(this.instance.exports.memory.buffer, offset, length)
333 }
334
335 static get typeId () {
336 return 9
337 }
338}
339

Built with git-ssb-web