git ssb

0+

wanderer🌟 / js-primea-wasm-container



Tree: 251a0f837a762cf942d5254502a65129e1a09e0b

Files: 251a0f837a762cf942d5254502a65129e1a09e0b / index.js

3787 bytesRaw
1const ReferanceMap = require('reference-map')
2const AbstractContainer = require('primea-abstract-container')
3const ContainerTable = require('primea-container-table')
4const RadixTree = require('dfinity-radix-tree')
5
6const CODEKEY = new RadixTree.ArrayConstructor([0, 1])
7
8module.exports = class WasmContainer extends AbstractContainer {
9 /**
10 * The wasm container runs wasm code and provides a basic API for wasm
11 * interfaces for interacting with the actor
12 * @param {object} actor - the actor instance
13 * @param {object} interfaces - a map of interfaces to expose to the wasm binary
14 */
15 constructor (actor, interfaces) {
16 super(actor)
17 this.referanceMap = new ReferanceMap()
18
19 // hold the interfaces `initailize` functions, if any
20 this.initializeFuncs = []
21 // Builds a import map with an array of given interfaces
22 this.importMap = {}
23 for (const name in interfaces) {
24 this.importMap[name] = {}
25 const Interface = interfaces[name]
26 if (Interface.initialize) {
27 this.initializeFuncs.push(Interface.initialize)
28 }
29 const newInterface = new Interface(this)
30 const props = Object.getOwnPropertyNames(Interface.prototype)
31
32 // bind the methods to the correct 'this'
33 for (const prop of props) {
34 if (prop !== 'constructor') {
35 this.importMap[name][prop] = newInterface[prop].bind(newInterface)
36 }
37 }
38 }
39 }
40
41 async onCreation (message) {
42 let code = message.data
43 if (!WebAssembly.validate(code)) {
44 throw new Error('invalid wasm binary')
45 } else {
46 await Promise.all(this.initializeFuncs.map(initFunc => initFunc(code)))
47 this.actor.state.set(CODEKEY, code)
48 }
49 return this._run(message, 'onCreation')
50 }
51
52 /**
53 * Runs the wasm VM given a message
54 * @param {object} message
55 * @returns {Promise} a promise that resolves once the compuation is finished
56 */
57 onMessage (message, method = 'onMessage') {
58 return this._run(message, method)
59 }
60
61 async _run (message, method) {
62 const code = await this.actor.state.get(CODEKEY)
63 const result = await WebAssembly.instantiate(code, this.importMap)
64 this.instance = result.instance
65 if (this.instance.exports[method]) {
66 // add the message and ports to the refereance map
67 const messageRef = this.referanceMap.add(message)
68
69 // runs the wasm code
70 this.instance.exports[method](messageRef)
71 await this.onDone()
72 this.referanceMap.clear()
73 }
74 }
75
76 /**
77 * returns a promise that resolves when the wasm instance is done running
78 * @returns {Promise}
79 */
80 async onDone () {
81 let prevOps
82 while (prevOps !== this._opsQueue) {
83 prevOps = this._opsQueue
84 await prevOps
85 }
86 }
87
88 /**
89 * Pushed an async operation to the a promise queue that
90 * @returns {Promise} the returned promise resolves in the order the intail
91 * operation was pushed to the queue
92 */
93 pushOpsQueue (promise) {
94 this._opsQueue = Promise.all([this._opsQueue, promise])
95 return this._opsQueue
96 }
97
98 /**
99 * executes a callback given an index in the exported callback container
100 * @param {integer} cb
101 * @param {*} val - a value to return to the callback function
102 */
103 execute (cb) {
104 const args = [...arguments]
105 args.shift()
106 this.instance.exports.callbacks.get(cb)(...args)
107 }
108
109 /**
110 * returns a section of memory from the wasm instance
111 * @param {integer} offset
112 * @param {integer} length
113 * @returns {Uint8Array}
114 */
115 getMemory (offset, length) {
116 return new Uint8Array(this.instance.exports.memory.buffer, offset, length)
117 }
118
119 setMemory (offset, val) {
120 const mem = this.getMemory(offset, val.length)
121 mem.set(val)
122 }
123
124 static get typeId () {
125 return ContainerTable.WebAssembly
126 }
127}
128

Built with git-ssb-web