git ssb

0+

wanderer🌟 / js-primea-wasm-container



Tree: 0f31d31650ad897c852214495d044937472b54b9

Files: 0f31d31650ad897c852214495d044937472b54b9 / index.js

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

Built with git-ssb-web