as-labs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +183 -0
- package/branch-hinting/index.ts +9 -0
- package/branch-hinting/package.json +5 -0
- package/branch-hinting/transform/index.ts +584 -0
- package/dist/branch-hinting/transform/index.d.ts +15 -0
- package/dist/branch-hinting/transform/index.js +446 -0
- package/dist/branch-hinting/transform/index.js.map +1 -0
- package/dist/transform/index.d.ts +10 -0
- package/dist/transform/index.js +25 -0
- package/dist/transform/index.js.map +1 -0
- package/index.ts +1 -0
- package/package.json +55 -0
- package/transform/index.ts +37 -0
- package/transform/types.d.ts +3 -0
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
import { decode } from "@webassemblyjs/wasm-parser";
|
|
2
|
+
import {
|
|
3
|
+
CallExpression,
|
|
4
|
+
FunctionDeclaration,
|
|
5
|
+
IdentifierExpression,
|
|
6
|
+
MethodDeclaration,
|
|
7
|
+
Node,
|
|
8
|
+
NodeKind,
|
|
9
|
+
ParenthesizedExpression,
|
|
10
|
+
SourceKind,
|
|
11
|
+
getFunctionName,
|
|
12
|
+
} from "assemblyscript/dist/assemblyscript.js";
|
|
13
|
+
import type {
|
|
14
|
+
Expression,
|
|
15
|
+
Program,
|
|
16
|
+
Source,
|
|
17
|
+
} from "assemblyscript/dist/assemblyscript.js";
|
|
18
|
+
import type binaryen from "assemblyscript/lib/binaryen.js";
|
|
19
|
+
import { Transform } from "assemblyscript/dist/transform.js";
|
|
20
|
+
|
|
21
|
+
const CUSTOM_SECTION_NAME = "metadata.code.branch_hint";
|
|
22
|
+
|
|
23
|
+
type HintValue = 0 | 1;
|
|
24
|
+
|
|
25
|
+
type HintRecord = {
|
|
26
|
+
value: HintValue;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
type ResolvedHint = {
|
|
30
|
+
offset: number;
|
|
31
|
+
value: HintValue;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type FunctionHintGroup = {
|
|
35
|
+
functionIndex: number;
|
|
36
|
+
hints: ResolvedHint[];
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type SectionInfo = {
|
|
40
|
+
id: number;
|
|
41
|
+
start: number;
|
|
42
|
+
payloadOffset: number;
|
|
43
|
+
end: number;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type U32 = {
|
|
47
|
+
value: number;
|
|
48
|
+
nextOffset: number;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
type BranchHintCall = {
|
|
52
|
+
value: HintValue;
|
|
53
|
+
expression: Expression;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type BinaryWithSourceMap = {
|
|
57
|
+
binary: Uint8Array;
|
|
58
|
+
sourceMap?: Uint8Array | string | null;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
type BinaryEmitter = (...args: unknown[]) => BinaryWithSourceMap;
|
|
62
|
+
|
|
63
|
+
type ConditionalStatement = {
|
|
64
|
+
condition: Expression;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default class BranchHintTransform extends Transform {
|
|
68
|
+
private functionHintsByDeclaration = new Map<
|
|
69
|
+
FunctionDeclaration | MethodDeclaration,
|
|
70
|
+
HintRecord[]
|
|
71
|
+
>();
|
|
72
|
+
private functionHintsByInternalName = new Map<string, HintRecord[]>();
|
|
73
|
+
|
|
74
|
+
afterParse(parser: { sources: Source[] }): void {
|
|
75
|
+
for (const source of parser.sources) {
|
|
76
|
+
if (source.sourceKind === SourceKind.Library) continue;
|
|
77
|
+
if (source.internalPath.startsWith("~lib/")) continue;
|
|
78
|
+
for (const statement of source.statements) {
|
|
79
|
+
this.visitNode(statement, null);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
afterInitialize(program: Program): void {
|
|
85
|
+
for (const [declaration, hints] of this.functionHintsByDeclaration) {
|
|
86
|
+
const element = program.elementsByDeclaration.get(declaration);
|
|
87
|
+
if (!element || !element.internalName || hints.length === 0) continue;
|
|
88
|
+
this.functionHintsByInternalName.set(element.internalName, hints);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
afterCompile(module: binaryen.Module): void {
|
|
93
|
+
if (this.functionHintsByInternalName.size === 0) return;
|
|
94
|
+
|
|
95
|
+
const originalEmitBinary = module.emitBinary.bind(module) as BinaryEmitter;
|
|
96
|
+
module.emitBinary = ((...args: unknown[]) => {
|
|
97
|
+
const result = originalEmitBinary(...args);
|
|
98
|
+
result.binary = injectBranchHints(
|
|
99
|
+
result.binary,
|
|
100
|
+
module,
|
|
101
|
+
this.functionHintsByInternalName,
|
|
102
|
+
);
|
|
103
|
+
return result;
|
|
104
|
+
}) as typeof module.emitBinary;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private visitNode(
|
|
108
|
+
node: Node | Node[] | null | undefined,
|
|
109
|
+
currentFunction: FunctionDeclaration | MethodDeclaration | null,
|
|
110
|
+
): void {
|
|
111
|
+
if (!node) return;
|
|
112
|
+
if (Array.isArray(node)) {
|
|
113
|
+
for (const item of node) this.visitNode(item, currentFunction);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
switch (node.kind) {
|
|
118
|
+
case NodeKind.FunctionDeclaration:
|
|
119
|
+
this.visitFunctionLike(node as FunctionDeclaration, node as FunctionDeclaration);
|
|
120
|
+
return;
|
|
121
|
+
case NodeKind.MethodDeclaration:
|
|
122
|
+
this.visitFunctionLike(node as MethodDeclaration, node as MethodDeclaration);
|
|
123
|
+
return;
|
|
124
|
+
case NodeKind.If:
|
|
125
|
+
this.visitConditionalStatement(
|
|
126
|
+
node as unknown as ConditionalStatement,
|
|
127
|
+
currentFunction,
|
|
128
|
+
[(node as any).ifTrue, (node as any).ifFalse],
|
|
129
|
+
);
|
|
130
|
+
return;
|
|
131
|
+
case NodeKind.While:
|
|
132
|
+
this.visitConditionalStatement(
|
|
133
|
+
node as unknown as ConditionalStatement,
|
|
134
|
+
currentFunction,
|
|
135
|
+
[(node as any).body],
|
|
136
|
+
);
|
|
137
|
+
return;
|
|
138
|
+
case NodeKind.Do:
|
|
139
|
+
this.visitConditionalStatement(
|
|
140
|
+
node as unknown as ConditionalStatement,
|
|
141
|
+
currentFunction,
|
|
142
|
+
[(node as any).body],
|
|
143
|
+
);
|
|
144
|
+
return;
|
|
145
|
+
case NodeKind.For:
|
|
146
|
+
this.visitConditionalStatement(
|
|
147
|
+
node as unknown as ConditionalStatement,
|
|
148
|
+
currentFunction,
|
|
149
|
+
[(node as any).initializer, (node as any).incrementor, (node as any).body],
|
|
150
|
+
);
|
|
151
|
+
return;
|
|
152
|
+
case NodeKind.Block:
|
|
153
|
+
this.visitNode((node as any).statements, currentFunction);
|
|
154
|
+
return;
|
|
155
|
+
case NodeKind.Switch:
|
|
156
|
+
this.visitNode((node as any).condition, currentFunction);
|
|
157
|
+
this.visitNode((node as any).cases, currentFunction);
|
|
158
|
+
return;
|
|
159
|
+
case NodeKind.SwitchCase:
|
|
160
|
+
this.visitNode((node as any).label, currentFunction);
|
|
161
|
+
this.visitNode((node as any).statements, currentFunction);
|
|
162
|
+
return;
|
|
163
|
+
case NodeKind.Expression:
|
|
164
|
+
this.visitNode((node as any).expression, currentFunction);
|
|
165
|
+
return;
|
|
166
|
+
case NodeKind.Return:
|
|
167
|
+
this.visitNode((node as any).value, currentFunction);
|
|
168
|
+
return;
|
|
169
|
+
case NodeKind.Variable:
|
|
170
|
+
this.visitNode((node as any).declarations, currentFunction);
|
|
171
|
+
return;
|
|
172
|
+
case NodeKind.VariableDeclaration:
|
|
173
|
+
this.visitNode((node as any).initializer, currentFunction);
|
|
174
|
+
return;
|
|
175
|
+
case NodeKind.Call:
|
|
176
|
+
this.visitNode((node as any).expression, currentFunction);
|
|
177
|
+
this.visitNode((node as any).args, currentFunction);
|
|
178
|
+
return;
|
|
179
|
+
case NodeKind.Binary:
|
|
180
|
+
this.visitNode((node as any).left, currentFunction);
|
|
181
|
+
this.visitNode((node as any).right, currentFunction);
|
|
182
|
+
return;
|
|
183
|
+
case NodeKind.UnaryPrefix:
|
|
184
|
+
case NodeKind.UnaryPostfix:
|
|
185
|
+
case NodeKind.Parenthesized:
|
|
186
|
+
case NodeKind.Assertion:
|
|
187
|
+
this.visitNode((node as any).operand ?? (node as any).expression, currentFunction);
|
|
188
|
+
return;
|
|
189
|
+
case NodeKind.Ternary:
|
|
190
|
+
this.visitNode((node as any).condition, currentFunction);
|
|
191
|
+
this.visitNode((node as any).ifThen, currentFunction);
|
|
192
|
+
this.visitNode((node as any).ifElse, currentFunction);
|
|
193
|
+
return;
|
|
194
|
+
case NodeKind.ForOf:
|
|
195
|
+
this.visitNode((node as any).variable, currentFunction);
|
|
196
|
+
this.visitNode((node as any).iterable, currentFunction);
|
|
197
|
+
this.visitNode((node as any).body, currentFunction);
|
|
198
|
+
return;
|
|
199
|
+
case NodeKind.Try:
|
|
200
|
+
this.visitNode((node as any).bodyStatements, currentFunction);
|
|
201
|
+
this.visitNode((node as any).catchStatements, currentFunction);
|
|
202
|
+
this.visitNode((node as any).finallyStatements, currentFunction);
|
|
203
|
+
return;
|
|
204
|
+
case NodeKind.Throw:
|
|
205
|
+
this.visitNode((node as any).value, currentFunction);
|
|
206
|
+
return;
|
|
207
|
+
case NodeKind.Void:
|
|
208
|
+
this.visitNode((node as any).expression, currentFunction);
|
|
209
|
+
return;
|
|
210
|
+
default:
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private visitFunctionLike(
|
|
216
|
+
node: FunctionDeclaration | MethodDeclaration,
|
|
217
|
+
declaration: FunctionDeclaration | MethodDeclaration,
|
|
218
|
+
): void {
|
|
219
|
+
if (!node.body) return;
|
|
220
|
+
this.visitNode(node.body, declaration);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private visitConditionalStatement(
|
|
224
|
+
node: ConditionalStatement,
|
|
225
|
+
currentFunction: FunctionDeclaration | MethodDeclaration | null,
|
|
226
|
+
extraChildren: Array<Node | Node[] | null | undefined>,
|
|
227
|
+
): void {
|
|
228
|
+
if (!currentFunction) return;
|
|
229
|
+
|
|
230
|
+
const hint = unwrapHint(node.condition);
|
|
231
|
+
if (hint) {
|
|
232
|
+
node.condition = hint.expression;
|
|
233
|
+
let hints = this.functionHintsByDeclaration.get(currentFunction);
|
|
234
|
+
if (!hints) {
|
|
235
|
+
hints = [];
|
|
236
|
+
this.functionHintsByDeclaration.set(currentFunction, hints);
|
|
237
|
+
}
|
|
238
|
+
hints.push({ value: hint.value });
|
|
239
|
+
} else {
|
|
240
|
+
this.visitNode(node.condition, currentFunction);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
for (const child of extraChildren) {
|
|
244
|
+
this.visitNode(child, currentFunction);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function unwrapHint(expression: Expression): BranchHintCall | null {
|
|
250
|
+
let current: Expression = expression;
|
|
251
|
+
while (current instanceof ParenthesizedExpression) {
|
|
252
|
+
current = current.expression;
|
|
253
|
+
}
|
|
254
|
+
if (!(current instanceof CallExpression)) return null;
|
|
255
|
+
|
|
256
|
+
const callee = current.expression;
|
|
257
|
+
if (!(callee instanceof IdentifierExpression)) return null;
|
|
258
|
+
if (current.args.length !== 1) return null;
|
|
259
|
+
|
|
260
|
+
if (callee.text === "likely") {
|
|
261
|
+
return { value: 1, expression: current.args[0] };
|
|
262
|
+
}
|
|
263
|
+
if (callee.text === "unlikely") {
|
|
264
|
+
return { value: 0, expression: current.args[0] };
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function injectBranchHints(
|
|
270
|
+
binary: Uint8Array,
|
|
271
|
+
module: binaryen.Module,
|
|
272
|
+
functionHintsByInternalName: Map<string, HintRecord[]>,
|
|
273
|
+
): Uint8Array {
|
|
274
|
+
const importedFunctionCount = countImportedFunctions(binary);
|
|
275
|
+
const functionOrder: string[] = [];
|
|
276
|
+
for (let i = 0, n = module.getNumFunctions(); i < n; i++) {
|
|
277
|
+
const name = getFunctionName(module.getFunctionByIndex(i));
|
|
278
|
+
functionOrder.push(name ?? "");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const conditionalOffsets = collectConditionalOffsets(binary);
|
|
282
|
+
const functionHints: FunctionHintGroup[] = [];
|
|
283
|
+
|
|
284
|
+
for (let definedIndex = 0; definedIndex < conditionalOffsets.length; definedIndex++) {
|
|
285
|
+
const internalName = functionOrder[importedFunctionCount + definedIndex];
|
|
286
|
+
const wanted = functionHintsByInternalName.get(internalName);
|
|
287
|
+
if (!wanted || wanted.length === 0) continue;
|
|
288
|
+
|
|
289
|
+
const available = conditionalOffsets[definedIndex] || [];
|
|
290
|
+
const matched: ResolvedHint[] = [];
|
|
291
|
+
const count = Math.min(wanted.length, available.length);
|
|
292
|
+
for (let i = 0; i < count; i++) {
|
|
293
|
+
matched.push({
|
|
294
|
+
offset: available[i],
|
|
295
|
+
value: wanted[i].value,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
if (matched.length === 0) continue;
|
|
299
|
+
|
|
300
|
+
functionHints.push({
|
|
301
|
+
functionIndex: importedFunctionCount + definedIndex,
|
|
302
|
+
hints: matched,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (functionHints.length === 0) return binary;
|
|
307
|
+
|
|
308
|
+
const payload = encodeBranchHintPayload(functionHints);
|
|
309
|
+
const customSection = encodeCustomSection(CUSTOM_SECTION_NAME, payload);
|
|
310
|
+
return insertCustomSectionBeforeCode(binary, customSection, CUSTOM_SECTION_NAME);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function countImportedFunctions(binary: Uint8Array): number {
|
|
314
|
+
const sections = parseSections(binary);
|
|
315
|
+
const importSection = sections.find((section) => section.id === 2);
|
|
316
|
+
if (!importSection) return 0;
|
|
317
|
+
|
|
318
|
+
let offset = importSection.payloadOffset;
|
|
319
|
+
const end = importSection.end;
|
|
320
|
+
const countInfo = readU32(binary, offset);
|
|
321
|
+
offset = countInfo.nextOffset;
|
|
322
|
+
let functionImports = 0;
|
|
323
|
+
|
|
324
|
+
for (let i = 0; i < countInfo.value && offset < end; i++) {
|
|
325
|
+
const moduleName = readName(binary, offset);
|
|
326
|
+
offset = moduleName.nextOffset;
|
|
327
|
+
const fieldName = readName(binary, offset);
|
|
328
|
+
offset = fieldName.nextOffset;
|
|
329
|
+
const kind = binary[offset++] as number;
|
|
330
|
+
|
|
331
|
+
switch (kind) {
|
|
332
|
+
case 0:
|
|
333
|
+
functionImports++;
|
|
334
|
+
offset = readU32(binary, offset).nextOffset;
|
|
335
|
+
break;
|
|
336
|
+
case 1:
|
|
337
|
+
offset = skipTableType(binary, offset);
|
|
338
|
+
break;
|
|
339
|
+
case 2:
|
|
340
|
+
offset = skipMemoryType(binary, offset);
|
|
341
|
+
break;
|
|
342
|
+
case 3:
|
|
343
|
+
offset = skipGlobalType(binary, offset);
|
|
344
|
+
break;
|
|
345
|
+
case 4:
|
|
346
|
+
offset = skipTagType(binary, offset);
|
|
347
|
+
break;
|
|
348
|
+
default:
|
|
349
|
+
throw new Error(`Unsupported import kind ${kind}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return functionImports;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function collectConditionalOffsets(binary: Uint8Array): number[][] {
|
|
357
|
+
const ast = decode(binary, {
|
|
358
|
+
dump: false,
|
|
359
|
+
ignoreCodeSection: false,
|
|
360
|
+
ignoreDataSection: true,
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const functions: any[] = [];
|
|
364
|
+
walk(ast, (node: any) => {
|
|
365
|
+
if (node?.type === "Func") {
|
|
366
|
+
functions.push(node);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
return functions.map((func) => {
|
|
371
|
+
const bodySize = readU32(binary, func.loc.start.column as number);
|
|
372
|
+
const localsOffset = bodySize.nextOffset;
|
|
373
|
+
const offsets: number[] = [];
|
|
374
|
+
|
|
375
|
+
walk(func.body, (node: any) => {
|
|
376
|
+
if (!node || !node.loc) return;
|
|
377
|
+
if (node.type === "IfInstruction") {
|
|
378
|
+
offsets.push((node.loc.start.column as number) - localsOffset - 1);
|
|
379
|
+
} else if (node.type === "Instr" && node.id === "br_if") {
|
|
380
|
+
offsets.push((node.loc.start.column as number) - localsOffset);
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
return offsets.filter((offset) => offset >= 0);
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function walk(node: unknown, visit: (node: unknown) => void): void {
|
|
389
|
+
if (!node || typeof node !== "object") return;
|
|
390
|
+
if (Array.isArray(node)) {
|
|
391
|
+
for (const item of node) walk(item, visit);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
visit(node);
|
|
396
|
+
for (const [key, value] of Object.entries(node)) {
|
|
397
|
+
if (key === "loc") continue;
|
|
398
|
+
walk(value, visit);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function encodeBranchHintPayload(functionHints: FunctionHintGroup[]): Uint8Array {
|
|
403
|
+
const bytes: number[] = [];
|
|
404
|
+
writeU32(bytes, functionHints.length);
|
|
405
|
+
|
|
406
|
+
for (const { functionIndex, hints } of functionHints) {
|
|
407
|
+
writeU32(bytes, functionIndex);
|
|
408
|
+
writeU32(bytes, hints.length);
|
|
409
|
+
for (const hint of hints) {
|
|
410
|
+
writeU32(bytes, hint.offset);
|
|
411
|
+
writeU32(bytes, 1);
|
|
412
|
+
writeU32(bytes, hint.value);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return Uint8Array.from(bytes);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function encodeCustomSection(name: string, payload: Uint8Array): Uint8Array {
|
|
420
|
+
const nameBytes = new TextEncoder().encode(name);
|
|
421
|
+
const contents: number[] = [];
|
|
422
|
+
writeU32(contents, nameBytes.length);
|
|
423
|
+
contents.push(...nameBytes);
|
|
424
|
+
contents.push(...payload);
|
|
425
|
+
|
|
426
|
+
const section: number[] = [];
|
|
427
|
+
section.push(0);
|
|
428
|
+
writeU32(section, contents.length);
|
|
429
|
+
section.push(...contents);
|
|
430
|
+
return Uint8Array.from(section);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function insertCustomSectionBeforeCode(
|
|
434
|
+
binary: Uint8Array,
|
|
435
|
+
customSection: Uint8Array,
|
|
436
|
+
sectionName: string,
|
|
437
|
+
): Uint8Array {
|
|
438
|
+
const sections = parseSections(binary);
|
|
439
|
+
const filtered = sections.filter((section) => {
|
|
440
|
+
if (section.id !== 0) return true;
|
|
441
|
+
return readCustomSectionName(binary, section) !== sectionName;
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
const codeSection = filtered.find((section) => section.id === 10);
|
|
445
|
+
if (!codeSection) {
|
|
446
|
+
throw new Error("WebAssembly module has no code section");
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const output: number[] = [];
|
|
450
|
+
output.push(...binary.slice(0, 8));
|
|
451
|
+
|
|
452
|
+
for (const section of filtered) {
|
|
453
|
+
if (section.start === codeSection.start) {
|
|
454
|
+
output.push(...customSection);
|
|
455
|
+
}
|
|
456
|
+
output.push(...binary.slice(section.start, section.end));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return Uint8Array.from(output);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function parseSections(binary: Uint8Array): SectionInfo[] {
|
|
463
|
+
const sections: SectionInfo[] = [];
|
|
464
|
+
let offset = 8;
|
|
465
|
+
|
|
466
|
+
while (offset < binary.length) {
|
|
467
|
+
const start = offset;
|
|
468
|
+
const id = binary[offset++] as number;
|
|
469
|
+
const size = readU32(binary, offset);
|
|
470
|
+
offset = size.nextOffset;
|
|
471
|
+
const payloadOffset = offset;
|
|
472
|
+
const end = payloadOffset + size.value;
|
|
473
|
+
sections.push({
|
|
474
|
+
id,
|
|
475
|
+
start,
|
|
476
|
+
payloadOffset,
|
|
477
|
+
end,
|
|
478
|
+
});
|
|
479
|
+
offset = end;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
return sections;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function readCustomSectionName(binary: Uint8Array, section: SectionInfo): string {
|
|
486
|
+
const name = readName(binary, section.payloadOffset);
|
|
487
|
+
return name.value;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function readName(binary: Uint8Array, offset: number): { value: string; nextOffset: number } {
|
|
491
|
+
const length = readU32(binary, offset);
|
|
492
|
+
const start = length.nextOffset;
|
|
493
|
+
const end = start + length.value;
|
|
494
|
+
return {
|
|
495
|
+
value: new TextDecoder().decode(binary.slice(start, end)),
|
|
496
|
+
nextOffset: end,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function readU32(binary: Uint8Array, offset: number): U32 {
|
|
501
|
+
let value = 0;
|
|
502
|
+
let shift = 0;
|
|
503
|
+
let current = offset;
|
|
504
|
+
|
|
505
|
+
while (true) {
|
|
506
|
+
const byte = binary[current++] as number;
|
|
507
|
+
value |= (byte & 0x7f) << shift;
|
|
508
|
+
if ((byte & 0x80) === 0) {
|
|
509
|
+
return { value: value >>> 0, nextOffset: current };
|
|
510
|
+
}
|
|
511
|
+
shift += 7;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function skipTableType(binary: Uint8Array, offset: number): number {
|
|
516
|
+
offset = skipReferenceType(binary, offset);
|
|
517
|
+
return skipLimits(binary, offset);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function skipMemoryType(binary: Uint8Array, offset: number): number {
|
|
521
|
+
return skipLimits(binary, offset);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function skipGlobalType(binary: Uint8Array, offset: number): number {
|
|
525
|
+
offset = skipValueType(binary, offset);
|
|
526
|
+
return offset + 1;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function skipTagType(binary: Uint8Array, offset: number): number {
|
|
530
|
+
offset += 1;
|
|
531
|
+
return readU32(binary, offset).nextOffset;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function skipLimits(binary: Uint8Array, offset: number): number {
|
|
535
|
+
const flags = binary[offset++] as number;
|
|
536
|
+
offset = readU32(binary, offset).nextOffset;
|
|
537
|
+
if (flags & 0x01) {
|
|
538
|
+
offset = readU32(binary, offset).nextOffset;
|
|
539
|
+
}
|
|
540
|
+
if (flags & 0x04) {
|
|
541
|
+
offset = readU32(binary, offset).nextOffset;
|
|
542
|
+
}
|
|
543
|
+
return offset;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function skipReferenceType(binary: Uint8Array, offset: number): number {
|
|
547
|
+
const type = binary[offset] as number;
|
|
548
|
+
if (type === 0x63 || type === 0x64) {
|
|
549
|
+
return offset + 1;
|
|
550
|
+
}
|
|
551
|
+
return skipHeapType(binary, offset + 1);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function skipValueType(binary: Uint8Array, offset: number): number {
|
|
555
|
+
const type = binary[offset] as number;
|
|
556
|
+
if (
|
|
557
|
+
type === 0x7f ||
|
|
558
|
+
type === 0x7e ||
|
|
559
|
+
type === 0x7d ||
|
|
560
|
+
type === 0x7c ||
|
|
561
|
+
type === 0x7b ||
|
|
562
|
+
type === 0x70 ||
|
|
563
|
+
type === 0x6f
|
|
564
|
+
) {
|
|
565
|
+
return offset + 1;
|
|
566
|
+
}
|
|
567
|
+
return skipReferenceType(binary, offset);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function skipHeapType(binary: Uint8Array, offset: number): number {
|
|
571
|
+
const type = binary[offset] as number;
|
|
572
|
+
if (type <= 0x7f) return offset + 1;
|
|
573
|
+
return readU32(binary, offset).nextOffset;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function writeU32(target: number[], value: number): void {
|
|
577
|
+
let current = value >>> 0;
|
|
578
|
+
do {
|
|
579
|
+
let byte = current & 0x7f;
|
|
580
|
+
current >>>= 7;
|
|
581
|
+
if (current !== 0) byte |= 0x80;
|
|
582
|
+
target.push(byte);
|
|
583
|
+
} while (current !== 0);
|
|
584
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Program, Source } from "assemblyscript/dist/assemblyscript.js";
|
|
2
|
+
import type binaryen from "assemblyscript/lib/binaryen.js";
|
|
3
|
+
import { Transform } from "assemblyscript/dist/transform.js";
|
|
4
|
+
export default class BranchHintTransform extends Transform {
|
|
5
|
+
private functionHintsByDeclaration;
|
|
6
|
+
private functionHintsByInternalName;
|
|
7
|
+
afterParse(parser: {
|
|
8
|
+
sources: Source[];
|
|
9
|
+
}): void;
|
|
10
|
+
afterInitialize(program: Program): void;
|
|
11
|
+
afterCompile(module: binaryen.Module): void;
|
|
12
|
+
private visitNode;
|
|
13
|
+
private visitFunctionLike;
|
|
14
|
+
private visitConditionalStatement;
|
|
15
|
+
}
|