git ssb

0+

wanderer🌟 / js-primea-wasm-container



Tree: d48b7065a388045f96fc7be1588e12150b8de8bb

Files: d48b7065a388045f96fc7be1588e12150b8de8bb / index.js

9756 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 bin = self.refs.get(dataRef, 'data')
147 const {module} = self.actor.createActor(WasmContainer.typeId, bin)
148 return self.refs.add(module, 'mod')
149 },
150 export: (modRef, dataRef) => {
151 const mod = self.refs.get(modRef, 'mod')
152 let name = self.refs.get(dataRef, 'data')
153 name = Buffer.from(name).toString()
154 const funcRef = mod.getFuncRef(name)
155 return self.refs.add(funcRef, 'func')
156 },
157 self: () => {
158 return self.refs.add(this.modSelf, 'mod')
159 }
160 },
161 memory: {
162 externalize: (index, length) => {
163 const data = Buffer.from(this.get8Memory(index, length))
164 return self.refs.add(data, 'data')
165 },
166 internalize: (dataRef, srcOffset, sinkOffset, length) => {
167 let data = self.refs.get(dataRef, 'data')
168 data = data.subarray(srcOffset, length)
169 const mem = self.get8Memory(sinkOffset, data.length)
170 mem.set(data)
171 },
172 length (dataRef) {
173 let data = self.refs.get(dataRef, 'data')
174 return data.length
175 }
176 },
177 elem: {
178 externalize: (index, length) => {
179 const mem = Buffer.from(this.get8Memory(index, length * 4))
180 const objects = []
181 while (length--) {
182 const ref = mem.readUInt32LE(length * 4)
183 const obj = self.refs.get(ref)
184 objects.unshift(obj)
185 }
186 return this.refs.add(objects, 'elem')
187 },
188 internalize: (elemRef, srcOffset, sinkOffset, length) => {
189 let table = self.refs.get(elemRef, 'elem')
190 const buf = table.slice(srcOffset, srcOffset + length).map(obj => self.refs.add(obj))
191 const mem = self.get32Memory(sinkOffset, length)
192 mem.set(buf)
193 },
194 length (elemRef) {
195 let elem = self.refs.get(elemRef, 'elem')
196 return elem.length
197 }
198 },
199 metering: {
200 usegas: amount => {
201 self.actor.incrementTicks(amount)
202 funcRef.gas -= amount
203 // if (funcRef.gas < 0) {
204 // throw new Error('out of gas! :(')
205 // }
206 }
207 }
208 }
209 }
210
211 async onMessage (message) {
212 const funcRef = message.funcRef
213 const intef = this.getInterface(funcRef)
214 this.instance = WebAssembly.Instance(this.mod, intef)
215 // map table indexes
216 const table = this.instance.exports.table
217 if (table) {
218 let length = table.length
219 while (length--) {
220 const func = table.get(length)
221 if (func) {
222 func.tableIndex = length
223 }
224 }
225 }
226 // import references
227 let index = 0
228 const args = []
229 message.funcRef.params.forEach(type => {
230 const arg = message.funcArguments[index]
231 if (nativeTypes.has(type)) {
232 args.push(arg)
233 if (type === 'i64') {
234 args.push(message.funcArguments[++index])
235 }
236 } else {
237 args.push(this.refs.add(arg, type))
238 }
239 index++
240 })
241
242 // setup globals
243 let numOfGlobals = this.json.persist.length
244 if (numOfGlobals) {
245 const refs = []
246 while (numOfGlobals--) {
247 const obj = this.actor.storage[numOfGlobals] || DEFAULTS[this.json.persist[numOfGlobals].type]
248 refs.push(this.refs.add(obj, this.json.persist[numOfGlobals].type))
249 }
250 this.instance.exports.setter_globals(...refs)
251 }
252
253 try {
254 // call entrypoint function
255 let wasmFunc
256 if (funcRef.identifier[0]) {
257 wasmFunc = this.instance.exports.table.get(funcRef.identifier[1])
258 } else {
259 wasmFunc = this.instance.exports[funcRef.identifier[1]]
260 }
261
262 const wrapper = generateWrapper(funcRef)
263 wrapper.exports.table.set(0, wasmFunc)
264 wrapper.exports.invoke(...args)
265 await this.onDone()
266 } catch (e) {
267 console.log(e)
268 }
269
270 // store globals
271 numOfGlobals = this.json.persist.length
272 if (numOfGlobals) {
273 const storage = []
274 this.instance.exports.getter_globals()
275 const mem = this.get32Memory(0, numOfGlobals)
276 while (numOfGlobals--) {
277 const ref = mem[numOfGlobals]
278 storage.push(this.refs.get(ref, this.json.persist[numOfGlobals].type))
279 }
280 this.actor.storage = storage
281 }
282
283 this.refs.clear()
284 }
285
286 /**
287 * returns a promise that resolves when the wasm instance is done running
288 * @returns {Promise}
289 */
290 async onDone () {
291 let prevOps
292 while (prevOps !== this._opsQueue) {
293 prevOps = this._opsQueue
294 await prevOps
295 }
296 }
297
298 /**
299 * Pushed an async operation to the a promise queue that
300 * @returns {Promise} the returned promise resolves in the order the intail
301 * operation was pushed to the queue
302 */
303 pushOpsQueue (promise) {
304 this._opsQueue = Promise.all([this._opsQueue, promise])
305 return this._opsQueue
306 }
307
308 async onStartup () {
309 const code = this.actor.code
310 const {json, wasm, modRef} = WasmContainer.createModule(code, this.actor.id)
311 this.mod = WebAssembly.Module(wasm)
312 this.json = json
313 this.modSelf = modRef
314 }
315
316 get8Memory (offset, length) {
317 return new Uint8Array(this.instance.exports.memory.buffer, offset, length)
318 }
319
320 get32Memory (offset, length) {
321 return new Uint32Array(this.instance.exports.memory.buffer, offset, length)
322 }
323
324 static get typeId () {
325 return 9
326 }
327}
328

Built with git-ssb-web