git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: 69542e9b41cc18d3434269b364a714712a5a5631

Files: 69542e9b41cc18d3434269b364a714712a5a5631 / wasmContainer.js

10398 bytesRaw
1const {wasm2json, json2wasm} = require('wasm-json-toolkit')
2const Message = require('./message.js')
3const wasmMetering = require('wasm-metering')
4const customTypes = require('./customTypes.js')
5const typeCheckWrapper = require('./typeCheckWrapper.js')
6const ReferanceMap = require('reference-map')
7const leb128 = require('leb128')
8
9const nativeTypes = new Set(['i32', 'i64', 'f32', 'f64'])
10const LANGUAGE_TYPES = {
11 'actor': 0x0,
12 'buf': 0x1,
13 'elem': 0x2,
14 'i32': 0x7f,
15 'i64': 0x7e,
16 'f32': 0x7d,
17 'f64': 0x7c,
18 'anyFunc': 0x70,
19 'func': 0x60,
20 'block_type': 0x40,
21
22 0x0: 'actor',
23 0x1: 'buf',
24 0x7f: 'i32',
25 0x7e: 'i64',
26 0x7d: 'f32',
27 0x7c: 'f64',
28 0x70: 'anyFunc',
29 0x60: 'func',
30 0x40: 'block_type'
31}
32
33
34class ElementBuffer {
35 constructor (size) {
36 this._array = new Array(size)
37 }
38
39 serialize () {
40 const serialized = this._array.map(ref => ref.serailize())
41 return Buffer.concat(Buffer.from([LANGUAGE_TYPES['elem']]), leb128.encode(serialized.length), serialized)
42 }
43
44 static deserialize (serialized) {}
45}
46
47class DataBuffer {
48 constructor (memory, offset, length) {
49 this._data = new Uint8Array(this.instance.exports.memory.buffer, offset, length)
50 }
51 serialize () {
52 return Buffer.concat(Buffer.from([LANGUAGE_TYPES['elem']]), leb128.encode(this._data.length), this._data)
53 }
54 static deserialize (serialized) {}
55}
56
57class LinkRef {
58 serialize () {
59 return Buffer.concat(Buffer.from([LANGUAGE_TYPES['link'], this]))
60 }
61 static deserialize (serialized) {}
62}
63
64class FunctionRef {
65 static get type () {
66 return 'funcRef'
67 }
68
69 constructor (name, json, id) {
70 this.name = name
71 this.destId = id
72 this.args = []
73 const typeIndex = json.typeMap[name]
74 const type = json.type[typeIndex]
75 const wrapper = typeCheckWrapper(type)
76 const wasm = json2wasm(wrapper)
77 this.mod = WebAssembly.Module(wasm)
78 const self = this
79 const instance = WebAssembly.Instance(this.mod, {
80 'env': {
81 'checkTypes': function () {
82 const args = [...arguments]
83 const checkedArgs = []
84 while (args.length) {
85 const type = LANGUAGE_TYPES[args.shift()]
86 let arg = args.shift()
87 if (!nativeTypes.has(type)) {
88 arg = self._container.refs.get(arg, type)
89 }
90 checkedArgs.push(arg)
91 }
92 const message = new Message({
93 funcRef: self,
94 funcArguments: checkedArgs
95 })
96 self._container.actor.send(message)
97 }
98 }
99 })
100 this.wrapper = instance
101 }
102 set container (container) {
103 this._container = container
104 }
105}
106
107class ModuleRef {
108 constructor (json, id) {
109 this._json = json
110 this.id = id
111 }
112
113 getFuncRef (name) {
114 return new FunctionRef(name, this._json, this.id)
115 }
116
117 serialize () {
118 return this._json
119 }
120
121 static deserialize (serialized) {}
122}
123
124module.exports = class WasmContainer {
125 constructor (actor) {
126 this.actor = actor
127 this.refs = new ReferanceMap()
128 }
129
130 static async onCreation (wasm, id, cachedb) {
131 if (!WebAssembly.validate(wasm)) {
132 throw new Error('invalid wasm binary')
133 }
134 let moduleJSON = wasm2json(wasm)
135 const json = mergeTypeSections(moduleJSON)
136 moduleJSON = wasmMetering.meterJSON(moduleJSON, {
137 meterType: 'i32'
138 })
139 wasm = json2wasm(moduleJSON)
140 await Promise.all([
141 new Promise((resolve, reject) => {
142 cachedb.put(id.toString() + 'meta', JSON.stringify(json), resolve)
143 }),
144 new Promise((resolve, reject) => {
145 cachedb.put(id.toString() + 'code', wasm.toString('hex'), resolve)
146 })
147 ])
148 return new ModuleRef(json, id)
149 }
150
151 getInterface (funcRef) {
152 const self = this
153 return {
154 func: {
155 externalize: () => {},
156 internalize: (ref, index) => {
157 const funcRef = self.refs.get(ref, 'funcRef')
158 funcRef.container = self
159 this.instance.exports.table.set(index, funcRef.wrapper.exports.check)
160 },
161 catch: (ref, catchRef) => {
162 const {funcRef} = self.refs.get(ref, FunctionRef)
163 const {funcRef: catchFunc} = self.refs.get(ref, FunctionRef)
164 funcRef.catch = catchFunc
165 },
166 getGasAmount: (funcRef) => {},
167 setGasAmount: (funcRef) => {}
168 },
169 link: {
170 wrap: (ref) => {
171 const obj = this.refs.get(ref)
172 const link = new LinkRef(obj.serialize())
173 return this.refs.add(link, 'link')
174 },
175 unwrap: async (ref, cb) => {
176 const obj = this.refs.get(ref, 'link')
177 const promise = this.actor.tree.dataStore.get(obj)
178 await this._opsQueue.push(promise)
179 // todo
180 }
181 },
182 module: {
183 new: code => {},
184 exports: (modRef, offset, length) => {
185 const mod = this.refs.get(modRef, 'mod')
186 let name = this.getMemory(offset, length)
187 name = Buffer.from(name).toString()
188 const funcRef = new FunctionRef(name, mod, this.actor.id)
189 return this.refs.add(funcRef)
190 },
191 self: () => {
192 return this.refs.add(this.json, 'mod')
193 }
194 },
195 memory: {
196 externalize: (index, length) => {
197 const buf = this.getMemory(index, length)
198 return this.refs.add(buf, 'buf')
199 },
200 internalize: (dataRef, writeOffset, readOffset, length) => {
201 let buf = this.refs.get(dataRef, 'buf')
202 buf = buf.subarray(readOffset, length)
203 const mem = this.getMemory(writeOffset, buf.length)
204 mem.set(buf)
205 }
206 },
207 table: {
208 externalize: (index, length) => {
209 const mem = this.getMemory(index, length * 4)
210 const objects = []
211 while (length--) {
212 const ref = mem[index + length]
213 if (this.refs.has(ref)) {
214 objects.push(ref)
215 } else {
216 throw new Error('invalid ref')
217 }
218 }
219 const eleBuf = new ElementBuffer(objects)
220 return this.refs.add(eleBuf, 'ele')
221 },
222 internalize: (dataRef, writeOffset, readOffset, length) => {
223 let buf = this.refs.get(dataRef, 'ele')
224 buf = buf.subarray(readOffset, length)
225 const mem = this.getMemory(writeOffset, buf.length)
226 mem.set(buf)
227 }
228 },
229 metering: {
230 usegas: (amount) => {
231 funcRef.gas -= amount
232 if (funcRef.gas < 0) {
233 throw new Error('out of gas! :(')
234 }
235 }
236 }
237 }
238 }
239
240 async onMessage (message) {
241 const funcRef = message.funcRef
242 const intef = this.getInterface(funcRef)
243 this.instance = WebAssembly.Instance(this.mod, intef)
244 if (this.instance.exports.table) {
245 this._orginalTable = this.instance.exports.table
246 }
247 const args = message.funcArguments.map(arg => {
248 if (typeof arg === 'number') {
249 return arg
250 } else {
251 return this.refs.add(arg, arg.constructor.type)
252 }
253 })
254 this.instance.exports[funcRef.name](...args)
255 await this.onDone()
256 this.refs.clear()
257 }
258
259 /**
260 * returns a promise that resolves when the wasm instance is done running
261 * @returns {Promise}
262 */
263 async onDone () {
264 let prevOps
265 while (prevOps !== this._opsQueue) {
266 prevOps = this._opsQueue
267 await prevOps
268 }
269 }
270
271 /**
272 * Pushed an async operation to the a promise queue that
273 * @returns {Promise} the returned promise resolves in the order the intail
274 * operation was pushed to the queue
275 */
276 pushOpsQueue (promise) {
277 this._opsQueue = Promise.all([this._opsQueue, promise])
278 return this._opsQueue
279 }
280
281 getFuncRef (name, send) {
282 const funcRef = new FunctionRef(this.json, name, send)
283 return funcRef
284 }
285
286 async onStartup () {
287 let [json, wasm] = await Promise.all([
288 new Promise((resolve, reject) => {
289 this.actor.cachedb.get(this.actor.id.toString() + 'meta', (err, json) => {
290 if (err) {
291 reject(err)
292 } else {
293 resolve(json)
294 }
295 })
296 }),
297 new Promise((resolve, reject) => {
298 this.actor.cachedb.get(this.actor.id.toString() + 'code', (err, wasm) => {
299 if (err) {
300 reject(err)
301 } else {
302 resolve(wasm)
303 }
304 })
305 })
306 ])
307 wasm = Buffer.from(wasm, 'hex')
308 json = JSON.parse(json)
309 this.mod = WebAssembly.Module(wasm)
310 this.json = json
311 }
312
313 getMemory (offset, length) {
314 return new Uint8Array(this.instance.exports.memory.buffer, offset, length)
315 }
316
317 static get typeId () {
318 return 9
319 }
320}
321
322function mergeTypeSections (json) {
323 const typeInfo = {
324 typeMap: [],
325 type: []
326 }
327 let typeSection = {
328 'entries': []
329 }
330 let importSection = {
331 'entries': []
332 }
333 let functionSection = {
334 'entries': []
335 }
336 let exportSection = {
337 'entries': []
338 }
339 json.forEach(section => {
340 switch (section.name) {
341 case 'type':
342 typeSection = section
343 break
344 case 'export':
345 exportSection = section
346 break
347 case 'import':
348 importSection = section
349 break
350 case 'function':
351 functionSection = section
352 break
353 case 'custom':
354 switch (section.sectionName) {
355 case 'type':
356 typeInfo.type = customTypes.decodeType(section.payload)
357 break
358 case 'typeMap':
359 typeInfo.typeMap = customTypes.decodeTypeMap(section.payload)
360 break
361 }
362 break
363 }
364 })
365
366 const foundTypes = new Map()
367 const mappedFuncs = new Map()
368 const newTypeMap = {}
369 typeInfo.typeMap.forEach(map => mappedFuncs.set(map.func, map.type))
370 for (let exprt of exportSection.entries) {
371 if (exprt.kind === 'function') {
372 if (!mappedFuncs.has(exprt.index)) {
373 const typeIndex = functionSection.entries[exprt.index - importSection.entries.length]
374 if (!foundTypes.has(typeIndex)) {
375 const customIndex = typeInfo.type.push(typeSection.entries[typeIndex]) - 1
376 foundTypes.set(typeIndex, customIndex)
377 }
378 const customIndex = foundTypes.get(typeIndex)
379 newTypeMap[exprt.field_str] = customIndex
380 } else {
381 newTypeMap[exprt.field_str] = mappedFuncs.get(exprt.index)
382 }
383 }
384 }
385
386 typeInfo.typeMap = newTypeMap
387 return typeInfo
388}
389

Built with git-ssb-web