git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 49051b3e16dc9927b3bbcfe2f7a8af88ccc8a232

Files: 49051b3e16dc9927b3bbcfe2f7a8af88ccc8a232 / wasmContainer.js

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

Built with git-ssb-web