git ssb

0+

wanderer🌟 / js-primea-wasm-container



Tree: 172b9d65cd4d2fbc210b0cee09b451dfdccabf34

Files: 172b9d65cd4d2fbc210b0cee09b451dfdccabf34 / index.js

9187 bytesRaw
1const {Message, FunctionRef, ModuleRef, getType} = require('primea-objects')
2const {wasm2json, json2wasm} = require('wasm-json-toolkit')
3const annotations = require('primea-annotations')
4const wasmMetering = require('wasm-metering')
5const ReferanceMap = require('reference-map')
6const typeCheckWrapper = require('./typeCheckWrapper.js')
7
8const nativeTypes = new Set(['i32', 'i64', 'f32', 'f64'])
9const FUNC_INDEX_OFFSET = 1
10
11function fromMetaJSON (json, id) {
12 const exports = {}
13 for (const ex in json.exports) {
14 const type = json.types[json.indexes[json.exports[ex].toString()]].params
15 exports[ex] = type
16 }
17 return new ModuleRef(exports, id)
18}
19
20function generateWrapper (funcRef, container) {
21 // check if the wrapper has been generated
22 if (funcRef.wrapper) {
23 return funcRef.wrapper
24 }
25 let wrapper = typeCheckWrapper(funcRef.params)
26 const wasm = json2wasm(wrapper)
27 const mod = WebAssembly.Module(wasm)
28 const self = funcRef
29 wrapper = WebAssembly.Instance(mod, {
30 'env': {
31 'checkTypes': function () {
32 const args = [...arguments]
33 const checkedArgs = []
34 while (args.length) {
35 const type = annotations.LANGUAGE_TYPES_BIN[args.shift()]
36 let arg = args.shift()
37 if (!nativeTypes.has(type)) {
38 arg = container.refs.get(arg, type)
39 checkedArgs.push(arg)
40 } else if (type === 'i64') {
41 checkedArgs.push(arg)
42 checkedArgs.push(args.shift())
43 } else {
44 checkedArgs.push(arg)
45 }
46 }
47 const message = new Message({
48 funcRef: self,
49 funcArguments: checkedArgs
50 })
51 container.actor.send(message)
52 }
53 }
54 })
55 // cache the wrapper
56 funcRef.wrapper = wrapper
57 wrapper.exports.check.object = funcRef
58 return wrapper
59}
60
61module.exports = class WasmContainer {
62 constructor (actor) {
63 this.actor = actor
64 this.refs = new ReferanceMap()
65 this._opsQueue = Promise.resolve()
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 // recompile the wasm
80 wasm = json2wasm(moduleJSON)
81 const modRef = fromMetaJSON(json, id)
82 return {
83 wasm,
84 json,
85 modRef
86 }
87 }
88
89 static onCreation (unverifiedWasm, id, tree) {
90 const {modRef} = this.createModule(unverifiedWasm, id)
91 return modRef
92 }
93
94 getInterface (funcRef) {
95 const self = this
96 return {
97 func: {
98 externalize: index => {
99 const func = self.instance.exports.table.get(index)
100 const object = func.object
101 if (object) {
102 // externalize a pervously internalized function
103 return self.refs.add(object)
104 } else {
105 const params = self.json.types[self.json.indexes[func.name - FUNC_INDEX_OFFSET]].params
106 const ref = new FunctionRef({
107 identifier: [true, func.tableIndex],
108 params,
109 actorID: self.actor.id
110 })
111 return self.refs.add(ref, 'func')
112 }
113 },
114 internalize: (index, ref) => {
115 const funcRef = self.refs.get(ref, 'func')
116 const wrapper = generateWrapper(funcRef, self)
117 self.instance.exports.table.set(index, wrapper.exports.check)
118 },
119 get_gas_budget: funcRef => {
120 const func = self.refs.get(funcRef, 'func')
121 return func.gas
122 },
123 set_gas_budget: (funcRef, amount) => {
124 const func = self.refs.get(funcRef, 'func').copy()
125 func.gas = amount
126 return self.refs.add(func, 'func')
127 }
128 },
129 link: {
130 wrap: ref => {
131 const obj = self.refs.get(ref)
132 const link = {'/': obj}
133 return self.refs.add(link, 'link')
134 },
135 unwrap: async (ref, cb) => {
136 const link = self.refs.get(ref, 'link')
137 const promise = self.actor.tree.graph.tree(link)
138 await self.pushOpsQueue(promise)
139 const obj = link['/']
140 const linkRef = self.refs.add(obj, getType(obj))
141 self.instance.exports.table.get(cb)(linkRef)
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, getType(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 storage: {
200 get: () => {
201 return this.refs.add(this.actor.storage, getType(this.actor.storage))
202 },
203 set: ref => {
204 const object = this.refs.get(ref)
205 this.actor.storage = object
206 }
207 },
208 metering: {
209 usegas: amount => {
210 self.actor.incrementTicks(amount)
211 funcRef.gas -= amount
212 if (funcRef.gas < 0) {
213 throw new Error('out of gas! :(')
214 }
215 }
216 }
217 }
218 }
219
220 async onMessage (message) {
221 const funcRef = message.funcRef
222 const intef = this.getInterface(funcRef)
223 this.instance = WebAssembly.Instance(this.mod, intef)
224 // map table indexes
225 const table = this.instance.exports.table
226 if (table) {
227 let length = table.length
228 while (length--) {
229 const func = table.get(length)
230 if (func) {
231 func.tableIndex = length
232 }
233 }
234 }
235 // import references
236 let index = 0
237 const args = []
238 message.funcRef.params.forEach(type => {
239 const arg = message.funcArguments[index]
240 if (nativeTypes.has(type)) {
241 args.push(arg)
242 if (type === 'i64') {
243 args.push(message.funcArguments[++index])
244 }
245 } else {
246 args.push(this.refs.add(arg, type))
247 }
248 index++
249 })
250
251 // call entrypoint function
252 let wasmFunc
253 if (funcRef.identifier[0]) {
254 wasmFunc = this.instance.exports.table.get(funcRef.identifier[1])
255 } else {
256 wasmFunc = this.instance.exports[funcRef.identifier[1]]
257 }
258
259 const wrapper = generateWrapper(funcRef)
260 wrapper.exports.table.set(0, wasmFunc)
261 wrapper.exports.invoke(...args)
262 await this.onDone()
263 this.refs.clear()
264 }
265
266 /**
267 * returns a promise that resolves when the wasm instance is done running
268 * @returns {Promise}
269 */
270 async onDone () {
271 let prevOps
272 while (prevOps !== this._opsQueue) {
273 prevOps = this._opsQueue
274 await prevOps
275 }
276 }
277
278 /**
279 * Pushed an async operation to the a promise queue that
280 * @returns {Promise} the returned promise resolves in the order the intail
281 * operation was pushed to the queue
282 */
283 pushOpsQueue (promise) {
284 this._opsQueue = Promise.all([this._opsQueue, promise])
285 return this._opsQueue
286 }
287
288 async onStartup () {
289 const code = this.actor.code
290 const {json, wasm, modRef} = WasmContainer.createModule(code, this.actor.id)
291 this.mod = WebAssembly.Module(wasm)
292 this.json = json
293 this.modSelf = modRef
294 }
295
296 get8Memory (offset, length) {
297 return new Uint8Array(this.instance.exports.memory.buffer, offset, length)
298 }
299
300 get32Memory (offset, length) {
301 return new Uint32Array(this.instance.exports.memory.buffer, offset, length)
302 }
303
304 static get typeId () {
305 return 9
306 }
307}
308

Built with git-ssb-web