git ssb

0+

wanderer🌟 / js-primea-wasm-container



Tree: 8fc026b2a62edac0dd238380f77dfa98fb0e5b33

Files: 8fc026b2a62edac0dd238380f77dfa98fb0e5b33 / index.js

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

Built with git-ssb-web