git ssb

0+

wanderer🌟 / js-primea-wasm-container



Tree: 07737b314f70efef4e3fb3e6a879859ae12d1799

Files: 07737b314f70efef4e3fb3e6a879859ae12d1799 / index.js

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

Built with git-ssb-web