git ssb

0+

wanderer🌟 / js-primea-annotations



Tree: b3206558d7fec55487d0c3d4adef1303fe29c547

Files: b3206558d7fec55487d0c3d4adef1303fe29c547 / index.js

9294 bytesRaw
1const Stream = require('buffer-pipe')
2const Buffer = require('safe-buffer').Buffer
3const leb = require('leb128')
4
5const nativeTypes = new Set(['i32', 'i64', 'f32', 'f64'])
6const FUNC_TYPE = 0x60
7const LANGUAGE_TYPES_STRG = {
8 'i32': 0x7f,
9 'i64': 0x7e,
10 'f32': 0x7d,
11 'f64': 0x7c,
12 'anyref': 0x70,
13 'module': 0x6f,
14 'func': 0x6e,
15 'data': 0x6d,
16 'elem': 0x6c,
17 'link': 0x6b,
18 'id': 0x6a
19}
20
21const LANGUAGE_TYPES_BIN = {
22 0x7f: 'i32',
23 0x7e: 'i64',
24 0x7d: 'f32',
25 0x7c: 'f64',
26 0x70: 'anyref',
27 0x6f: 'module',
28 0x6e: 'func',
29 0x6d: 'data',
30 0x6c: 'elem',
31 0x6b: 'link',
32 0x6a: 'id'
33}
34
35const EXTERNAL_KIND_BIN = {
36 0x0: 'func',
37 0x1: 'table',
38 0x2: 'memory',
39 0x3: 'global'
40}
41
42const EXTERNAL_KIND_STRG = {
43 'func': 0x0,
44 'table': 0x1,
45 'memory': 0x2,
46 'global': 0x3
47}
48
49/**
50 * encodes the type annotations
51 * @param {Object} annotations
52 * @return {Buffer}
53 */
54function encode (annotations) {
55 const stream = new Stream()
56 encodeCustomSection('types', annotations, stream, encodeType)
57 encodeCustomSection('typeMap', annotations, stream, encodeTypeMap)
58 encodeCustomSection('persist', annotations, stream, encodePersist)
59
60 return stream.buffer
61}
62
63function encodeCustomSection (name, json, stream, encodingFunc) {
64 let payload = new Stream()
65 json = json[name]
66
67 if (json) {
68 stream.write([0])
69 // encode type
70 leb.unsigned.write(name.length, payload)
71 payload.write(name)
72 encodingFunc(json, payload)
73 // write the size of the payload
74 leb.unsigned.write(payload.bytesWrote, stream)
75 stream.write(payload.buffer)
76 }
77 return stream
78}
79
80/**
81 * encodes the type annoations for persist
82 * @param {Object} annoations
83 * @param {buffer-pipe} [stream]
84 * @return {Buffer}
85 */
86function encodePersist (annotations, stream = new Stream()) {
87 leb.unsigned.write(annotations.length, stream)
88 for (const entry of annotations) {
89 const form = EXTERNAL_KIND_STRG[entry.form]
90 leb.unsigned.write(form, stream)
91 leb.unsigned.write(entry.index, stream)
92 leb.unsigned.write(LANGUAGE_TYPES_STRG[entry.type], stream)
93 }
94 return stream.buffer
95}
96
97/**
98 * decodes the persist annotations
99 * @param {Buffer} buf
100 * @param {Object}
101 */
102function decodePersist (buf) {
103 const stream = new Stream(Buffer.from(buf))
104 let numOfEntries = leb.unsigned.read(stream)
105 const json = []
106 while (numOfEntries--) {
107 const form = EXTERNAL_KIND_BIN[leb.unsigned.readBn(stream).toNumber()]
108 if (!form) {
109 throw new Error('invalid form')
110 }
111 const index = leb.unsigned.readBn(stream).toNumber()
112 const type = LANGUAGE_TYPES_BIN[leb.unsigned.readBn(stream).toNumber()]
113 if (!type) {
114 throw new Error('invalid param')
115 }
116 json.push({
117 form,
118 index,
119 type
120 })
121 }
122
123 if (stream.buffer.length) {
124 throw new Error('invalid buffer length')
125 }
126
127 return json
128}
129
130/**
131 * encodes a typeMap definition
132 * @param {Object} definition
133 * @param {buffer-pipe} [stream]
134 * @return {Buffer}
135 */
136function encodeTypeMap (definition, stream = new Stream()) {
137 leb.unsigned.write(definition.length, stream)
138 for (let entry of definition) {
139 leb.unsigned.write(entry.func, stream)
140 leb.unsigned.write(entry.type, stream)
141 }
142 return stream.buffer
143}
144
145/**
146 * decodes the TypeMap section
147 * @param {Buffer} buf
148 * @param {Object}
149 */
150function decodeTypeMap (buf) {
151 const stream = new Stream(Buffer.from(buf))
152 let numOfEntries = leb.unsigned.read(stream)
153 const json = []
154 while (numOfEntries--) {
155 json.push({
156 func: leb.unsigned.readBn(stream).toNumber(),
157 type: leb.unsigned.readBn(stream).toNumber()
158 })
159 }
160 if (stream.buffer.length) {
161 throw new Error('invalid buffer length')
162 }
163 return json
164}
165
166/**
167 * encodes the type annotations
168 * @param {Object} definition
169 * @param {buffer-pipe} [stream]
170 * @return {Buffer}
171 */
172function encodeType (annotations, stream = new Stream()) {
173 let binEntries = new Stream()
174
175 leb.unsigned.write(annotations.length, binEntries)
176 for (let entry of annotations) {
177 // a single type entry binary encoded
178 binEntries.write([FUNC_TYPE])
179
180 const len = entry.params.length // number of parameters
181 leb.unsigned.write(len, binEntries)
182 binEntries.write(entry.params.map(type => LANGUAGE_TYPES_STRG[type])) // the paramter types
183 binEntries.write([0])
184 // binEntries.write([entry.return_type ? 1 : 0]) // number of return types
185 // if (entry.return_type) {
186 // binEntries.write([LANGUAGE_TYPES[entry.return_type]])
187 // throw new Error('return type are not allowed')
188 // }
189 }
190
191 stream.write(binEntries.buffer)
192 return stream.buffer
193}
194
195/**
196 * decodes the Type section
197 * @param {Buffer} buf
198 * @param {Object}
199 */
200function decodeType (buf) {
201 const stream = new Stream(Buffer.from(buf))
202 const numberOfEntries = leb.unsigned.readBn(stream).toNumber()
203 const json = []
204 for (let i = 0; i < numberOfEntries; i++) {
205 let type = stream.read(1)[0]
206 if (type !== FUNC_TYPE) {
207 throw new Error('invalid form')
208 }
209 const entry = {
210 form: 'func',
211 params: []
212 }
213
214 let paramCount = leb.unsigned.readBn(stream).toNumber()
215
216 // parse the entries
217 while (paramCount--) {
218 const type = stream.read(1)[0]
219 const param = LANGUAGE_TYPES_BIN[type]
220 if (!param) {
221 throw new Error('invalid param')
222 }
223 entry.params.push(param)
224 }
225 // remove the last byte
226 leb.unsigned.readBn(stream)
227 // const numOfReturns = leb.unsigned.readBn(stream).toNumber()
228 // if (numOfReturns) {
229 // type = stream.read(1)[0]
230 // entry.return_type = LANGUAGE_TYPES[type]
231 // }
232
233 json.push(entry)
234 }
235
236 if (stream.buffer.length) {
237 throw new Error('invalid buffer length')
238 }
239 return json
240}
241
242/**
243 * injects custom sections into a wasm binary
244 * @param {Buffer} custom - the custom section(s)
245 * @param {Buffer} wasm - the wasm binary
246 * @return {Buffer}
247 */
248function injectCustomSection (custom, wasm) {
249 const preramble = wasm.subarray(0, 8)
250 const body = wasm.subarray(8)
251 return Buffer.concat([
252 Buffer.from(preramble),
253 Buffer.from(custom),
254 Buffer.from(body)
255 ])
256}
257
258/**
259 * encodes a json definition and injects it into a wasm binary
260 * @param {Object} annotation - the type definition
261 * @param {Buffer} wasm - the wasm binary to inject
262 */
263function encodeAndInject (annotation, wasm) {
264 const buf = encode(annotation)
265 return injectCustomSection(buf, wasm)
266}
267
268function mergeTypeSections (json) {
269 const result = {
270 types: [],
271 indexes: {},
272 exports: {},
273 persist: []
274 }
275
276 const mappedFuncs = new Map()
277 const mappedTypes = new Map()
278 let type = {
279 entries: []
280 }
281 let functions = {
282 entries: []
283 }
284 let imports = {
285 entries: []
286 }
287
288 for (const section of json) {
289 const name = section.name
290 if (name === 'custom') {
291 const sectionName = section.sectionName
292 if (sectionName === 'types') {
293 const type = decodeType(section.payload)
294 result.types = type
295 } else if (sectionName === 'typeMap') {
296 decodeTypeMap(section.payload).forEach(map => mappedFuncs.set(map.func, map.type))
297 } else if (sectionName === 'persist') {
298 result.persist = decodePersist(section.payload)
299 }
300 } else if (name === 'type') {
301 type = section
302 } else if (name === 'import') {
303 imports = section
304 } else if (name === 'function') {
305 functions = section
306 section.entries.forEach((typeIndex, funcIndex) => {
307 const newType = type.entries[typeIndex]
308 if (!newType.return_type) {
309 let customIndex = mappedFuncs.get(funcIndex)
310 if (customIndex === undefined) {
311 customIndex = mappedTypes.get(typeIndex)
312 } else {
313 const customType = result.types[customIndex]
314 if (customType.params.length !== newType.params.length) {
315 throw new Error('invalid param length')
316 }
317
318 newType.params.forEach((param, index) => {
319 if (!nativeTypes.has(customType.params[index]) && param !== 'i32') {
320 throw new Error('invalid base param type')
321 }
322 })
323 }
324
325 if (customIndex === undefined) {
326 customIndex = result.types.push(newType) - 1
327 mappedTypes.set(typeIndex, customIndex)
328 }
329 result.indexes[funcIndex + imports.entries.length] = customIndex
330 }
331 })
332 } else if (name === 'export') {
333 section.entries.forEach(entry => {
334 if (entry.kind === 'function') {
335 // validate that no function signature have no return types
336 // get the type index. entry.index is the global function index (imported function + internal function)
337 const typeIndex = functions.entries[entry.index - imports.entries.length]
338 const exportType = type.entries[typeIndex]
339 if (exportType.return_type) {
340 throw new Error('no return types allowed')
341 }
342 result.exports[entry.field_str] = entry.index
343 }
344 })
345 }
346 }
347
348 return result
349}
350
351module.exports = {
352 injectCustomSection,
353 encodeAndInject,
354 decodeType,
355 decodeTypeMap,
356 decodePersist,
357 encodeType,
358 encodeTypeMap,
359 encodePersist,
360 encode,
361 mergeTypeSections,
362 LANGUAGE_TYPES_BIN,
363 LANGUAGE_TYPES_STRG
364}
365

Built with git-ssb-web