git ssb

0+

wanderer🌟 / js-primea-hypervisor



Tree: b9423af2cd45a86722df9ab973b4db265a8fcaa3

Files: b9423af2cd45a86722df9ab973b4db265a8fcaa3 / wasmContainer.js

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

Built with git-ssb-web