mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-06-13 23:11:41 +00:00
Progress and a tiny WASM binary parser
This commit is contained in:
218
lib/parse/src/common.ts
Normal file
218
lib/parse/src/common.ts
Normal file
@ -0,0 +1,218 @@
|
||||
/** Common constants shared between AssemblyScript and TypeScript. */
|
||||
|
||||
/** WebAssembly types. */
|
||||
export enum Type {
|
||||
i32 = 0x7f,
|
||||
i64 = 0x7e,
|
||||
f32 = 0x7d,
|
||||
f64 = 0x7c,
|
||||
anyfunc = 0x70,
|
||||
func = 0x60,
|
||||
none = 0x40
|
||||
}
|
||||
|
||||
/** WebAssembly section ids. */
|
||||
export enum SectionId {
|
||||
Custom = 0,
|
||||
Type = 1,
|
||||
Import = 2,
|
||||
Function = 3,
|
||||
Table = 4,
|
||||
Memory = 5,
|
||||
Global = 6,
|
||||
Export = 7,
|
||||
Start = 8,
|
||||
Element = 9,
|
||||
Code = 10,
|
||||
Data = 11
|
||||
}
|
||||
|
||||
/** WebAssembly external kinds. */
|
||||
export enum ExternalKind {
|
||||
Function = 0,
|
||||
Table = 1,
|
||||
Memory = 2,
|
||||
Global = 3
|
||||
}
|
||||
|
||||
/** Maximum number of pages. */
|
||||
export const MAX_PAGES = 0xffff;
|
||||
|
||||
/** Maximum number of tables. */
|
||||
export const MAX_TABLES = 1; // MVP
|
||||
|
||||
/** WebAssembly opcodes. */
|
||||
export enum Opcode { // just a few of these are actually used
|
||||
// unreachable = 0x00,
|
||||
// nop = 0x01,
|
||||
// block = 0x02,
|
||||
// loop = 0x03,
|
||||
// if_ = 0x04,
|
||||
// else_ = 0x05,
|
||||
end = 0x0b,
|
||||
// br = 0x0c,
|
||||
// br_if = 0x0d,
|
||||
// br_table = 0x0e,
|
||||
// return_ = 0x0f,
|
||||
// call = 0x10,
|
||||
// call_indirect = 0x11,
|
||||
// drop = 0x1a,
|
||||
// select = 0x1b,
|
||||
// get_local = 0x20,
|
||||
// set_local = 0x21,
|
||||
// tee_local = 0x22,
|
||||
get_global = 0x23,
|
||||
// set_global = 0x24,
|
||||
// i32_load = 0x28,
|
||||
// i64_load = 0x29,
|
||||
// f32_load = 0x2a,
|
||||
// f64_load = 0x2b,
|
||||
// i32_load8_s = 0x2c,
|
||||
// i32_load8_u = 0x2d,
|
||||
// i32_load16_s = 0x2e,
|
||||
// i32_load16_u = 0x2f,
|
||||
// i64_load8_s = 0x30,
|
||||
// i64_load8_u = 0x31,
|
||||
// i64_load16_s = 0x32,
|
||||
// i64_load16_u = 0x33,
|
||||
// i64_load32_s = 0x34,
|
||||
// i64_load32_u = 0x35,
|
||||
// i32_store = 0x36,
|
||||
// i64_store = 0x37,
|
||||
// f32_store = 0x38,
|
||||
// f64_store = 0x39,
|
||||
// i32_store8 = 0x3a,
|
||||
// i32_store16 = 0x3b,
|
||||
// i64_store8 = 0x3c,
|
||||
// i64_store16 = 0x3d,
|
||||
// i64_store32 = 0x3e,
|
||||
// current_memory = 0x3f,
|
||||
// grow_memory = 0x40,
|
||||
i32_const = 0x41,
|
||||
i64_const = 0x42,
|
||||
f32_const = 0x43,
|
||||
f64_const = 0x44
|
||||
// i32_eqz = 0x45,
|
||||
// i32_eq = 0x46,
|
||||
// i32_ne = 0x47,
|
||||
// i32_lt_s = 0x48,
|
||||
// i32_lt_u = 0x49,
|
||||
// i32_gt_s = 0x4a,
|
||||
// i32_gt_u = 0x4b,
|
||||
// i32_le_s = 0x4c,
|
||||
// i32_le_u = 0x4d,
|
||||
// i32_ge_s = 0x4e,
|
||||
// i32_ge_u = 0x4f,
|
||||
// i64_eqz = 0x50,
|
||||
// i64_eq = 0x51,
|
||||
// i64_ne = 0x52,
|
||||
// i64_lt_s = 0x53,
|
||||
// i64_lt_u = 0x54,
|
||||
// i64_gt_s = 0x55,
|
||||
// i64_gt_u = 0x56,
|
||||
// i64_le_s = 0x57,
|
||||
// i64_le_u = 0x58,
|
||||
// i64_ge_s = 0x59,
|
||||
// i64_ge_u = 0x5a,
|
||||
// f32_eq = 0x5b,
|
||||
// f32_ne = 0x5c,
|
||||
// f32_lt = 0x5d,
|
||||
// f32_gt = 0x5e,
|
||||
// f32_le = 0x5f,
|
||||
// f32_ge = 0x60,
|
||||
// f64_eq = 0x61,
|
||||
// f64_ne = 0x62,
|
||||
// f64_lt = 0x63,
|
||||
// f64_gt = 0x64,
|
||||
// f64_le = 0x65,
|
||||
// f64_ge = 0x66,
|
||||
// i32_clz = 0x67,
|
||||
// i32_ctz = 0x68,
|
||||
// i32_popcnt = 0x69,
|
||||
// i32_add = 0x6a,
|
||||
// i32_sub = 0x6b,
|
||||
// i32_mul = 0x6c,
|
||||
// i32_div_s = 0x6d,
|
||||
// i32_div_u = 0x6e,
|
||||
// i32_rem_s = 0x6f,
|
||||
// i32_rem_u = 0x70,
|
||||
// i32_and = 0x71,
|
||||
// i32_or = 0x72,
|
||||
// i32_xor = 0x73,
|
||||
// i32_shl = 0x74,
|
||||
// i32_shr_s = 0x75,
|
||||
// i32_shr_u = 0x76,
|
||||
// i32_rotl = 0x77,
|
||||
// i32_rotr = 0x78,
|
||||
// i64_clz = 0x79,
|
||||
// i64_ctz = 0x7a,
|
||||
// i64_popcnt = 0x7b,
|
||||
// i64_add = 0x7c,
|
||||
// i64_sub = 0x7d,
|
||||
// i64_mul = 0x7e,
|
||||
// i64_div_s = 0x7f,
|
||||
// i64_div_u = 0x80,
|
||||
// i64_rem_s = 0x81,
|
||||
// i64_rem_u = 0x82,
|
||||
// i64_and = 0x83,
|
||||
// i64_or = 0x84,
|
||||
// i64_xor = 0x85,
|
||||
// i64_shl = 0x86,
|
||||
// i64_shr_s = 0x87,
|
||||
// i64_shr_u = 0x88,
|
||||
// i64_rotl = 0x89,
|
||||
// i64_rotr = 0x8a,
|
||||
// f32_abs = 0x8b,
|
||||
// f32_neg = 0x8c,
|
||||
// f32_ceil = 0x8d,
|
||||
// f32_floor = 0x8e,
|
||||
// f32_trunc = 0x8f,
|
||||
// f32_nearest = 0x90,
|
||||
// f32_sqrt = 0x91,
|
||||
// f32_add = 0x92,
|
||||
// f32_sub = 0x93,
|
||||
// f32_mul = 0x94,
|
||||
// f32_div = 0x95,
|
||||
// f32_min = 0x96,
|
||||
// f32_max = 0x97,
|
||||
// f32_copysign = 0x98,
|
||||
// f64_abs = 0x99,
|
||||
// f64_neg = 0x9a,
|
||||
// f64_ceil = 0x9b,
|
||||
// f64_floor = 0x9c,
|
||||
// f64_trunc = 0x9d,
|
||||
// f64_nearest = 0x9e,
|
||||
// f64_sqrt = 0x9f,
|
||||
// f64_add = 0xa0,
|
||||
// f64_sub = 0xa1,
|
||||
// f64_mul = 0xa2,
|
||||
// f64_div = 0xa3,
|
||||
// f64_min = 0xa4,
|
||||
// f64_max = 0xa5,
|
||||
// f64_copysign = 0xa6,
|
||||
// i32_wrap_i64 = 0xa7,
|
||||
// i32_trunc_s_f32 = 0xa8,
|
||||
// i32_trunc_u_f32 = 0xa9,
|
||||
// i32_trunc_s_f64 = 0xaa,
|
||||
// i32_trunc_u_f64 = 0xab,
|
||||
// i64_extend_s_i32 = 0xac,
|
||||
// i64_extend_u_i32 = 0xad,
|
||||
// i64_trunc_s_f32 = 0xae,
|
||||
// i64_trunc_u_f32 = 0xaf,
|
||||
// i64_trunc_s_f64 = 0xb0,
|
||||
// i64_trunc_u_f64 = 0xb1,
|
||||
// f32_convert_s_i32 = 0xb2,
|
||||
// f32_convert_u_i32 = 0xb3,
|
||||
// f32_convert_s_i64 = 0xb4,
|
||||
// f32_convert_u_i64 = 0xb5,
|
||||
// f32_demote_f64 = 0xb6,
|
||||
// f64_convert_s_i32 = 0xb7,
|
||||
// f64_convert_u_i32 = 0xb8,
|
||||
// f64_convert_s_i64 = 0xb9,
|
||||
// f64_convert_u_i64 = 0xba,
|
||||
// f64_promote_f32 = 0xbb,
|
||||
// i32_reinterpret_f32 = 0xbc,
|
||||
// i64_reinterpret_f64 = 0xbd,
|
||||
// f32_reinterpret_i32 = 0xbe,
|
||||
// f64_reinterpret_i64 = 0xbf
|
||||
}
|
151
lib/parse/src/index.ts
Normal file
151
lib/parse/src/index.ts
Normal file
@ -0,0 +1,151 @@
|
||||
import { Type, SectionId, ExternalKind } from "./common";
|
||||
export { Type, SectionId, ExternalKind };
|
||||
|
||||
/** Cached compiled parser. */
|
||||
var compiled: WebAssembly.Module | null = null;
|
||||
|
||||
declare var WASM_DATA: string; // injected by webpack
|
||||
if (typeof WASM_DATA !== "string") WASM_DATA = require("fs").readFileSync(__dirname + "/../build/index.wasm", "base64");
|
||||
|
||||
/** Options specified to the parser. The `onSection` callback determines the sections being evaluated in detail. */
|
||||
export interface ParseOptions {
|
||||
/** Called with each section in the binary. Returning `true` evaluates the section. */
|
||||
onSection?(id: SectionId, payloadOff: number, payloadLen: number, nameOff: number, nameLen: number): boolean;
|
||||
/** Called with each function type if the type section is evaluated. */
|
||||
onType?(index: number, form: number): void;
|
||||
/** Called with each function parameter if the type section is evaluated. */
|
||||
onTypeParam?(index: number, paramIndex: number, paramType: Type): void;
|
||||
/** Called with each function return type if the type section is evaluated. */
|
||||
onTypeReturn?(index: number, returnIndex: number, returnType: Type): void;
|
||||
/** Called with each import if the import section is evaluated. */
|
||||
onImport?(index: number, kind: ExternalKind, moduleOff: number, moduleLen: number, fieldOff: number, fieldLen: number): void;
|
||||
/** Called with each function import if the import section is evaluated. */
|
||||
onFunctionImport?(index: number, type: number): void;
|
||||
/** Called with each table import if the import section is evaluated. */
|
||||
onTableImport?(index: number, type: Type, initial: number, maximum: number, flags: number): void;
|
||||
/** Called with each memory import if the import section is evaluated. */
|
||||
onMemoryImport?(index: number, initial: number, maximum: number, flags: number): void;
|
||||
/** Called with each global import if the import section is evaluated. */
|
||||
onGlobalImport?(index: number, type: Type, mutability: number): void;
|
||||
/** Called with each memory if the memory section is evaluated.*/
|
||||
onMemory?(index: number, initial: number, maximum: number, flags: number): void;
|
||||
/** Called with each function if the function section is evaluated. */
|
||||
onFunction?(index: number, typeIndex: number): void;
|
||||
/** Called with each global if the global section is evaluated. */
|
||||
onGlobal?(index: number, type: Type, mutability: number): void;
|
||||
/** Called with the start function index if the start section is evaluated. */
|
||||
onStart?(index: number): void;
|
||||
/** Called with each export if the export section is evaluated. */
|
||||
onExport?(index: number, kind: ExternalKind, kindIndex: number, nameOff: number, nameLen: number);
|
||||
}
|
||||
|
||||
/** Parses the contents of a WebAssembly binary according to the specified options. */
|
||||
export function parse(binary: Uint8Array, options?: ParseOptions): void {
|
||||
if (!options) options = {};
|
||||
|
||||
// compile the parser if not yet compiled
|
||||
if (!compiled) compiled = new WebAssembly.Module(base64_decode(WASM_DATA));
|
||||
|
||||
// use the binary as the parser's memory
|
||||
var bytes = binary.length + 5; // leave space for u8 + u32 zero terminator
|
||||
var pages = ((bytes + 0xffff) & ~0xffff) >> 16;
|
||||
var memory = new WebAssembly.Memory({ initial: pages });
|
||||
var buffer = new Uint8Array(memory.buffer);
|
||||
buffer.set(binary);
|
||||
|
||||
// provide a way to read strings from memory
|
||||
parse.readString = (offset: number, length: number): string => utf8_read(buffer, offset, offset + length);
|
||||
|
||||
// instantiate the parser and return its exports
|
||||
function nop(): void {}
|
||||
var imports = {
|
||||
env: {
|
||||
memory: memory
|
||||
}
|
||||
};
|
||||
[
|
||||
"onSection",
|
||||
"onType",
|
||||
"onTypeParam",
|
||||
"onTypeReturn",
|
||||
"onImport",
|
||||
"onFunctionImport",
|
||||
"onTableImport",
|
||||
"onMemoryImport",
|
||||
"onGlobalImport",
|
||||
"onMemory",
|
||||
"onFunction",
|
||||
"onGlobal",
|
||||
"onExport",
|
||||
"onStart"
|
||||
].forEach((name: string): void => imports.env[name] = options[name] || nop);
|
||||
var instance = new WebAssembly.Instance(compiled, imports);
|
||||
return instance.exports;
|
||||
}
|
||||
|
||||
export declare namespace parse {
|
||||
/** Utility function for reading an UTF8 encoded string from memory while parsing. */
|
||||
function readString(offset: number, length: number): string;
|
||||
}
|
||||
|
||||
// see: https://github.com/dcodeIO/protobuf.js/tree/master/lib/utf8
|
||||
function utf8_read(buffer: Uint8Array, start: number, end: number): string {
|
||||
var len = end - start;
|
||||
if (len < 1) return "";
|
||||
var parts: string[] | null = null,
|
||||
chunk: number[] = [],
|
||||
i = 0, // char offset
|
||||
t = 0; // temporary
|
||||
while (start < end) {
|
||||
t = buffer[start++];
|
||||
if (t < 128) {
|
||||
chunk[i++] = t;
|
||||
} else if (t > 191 && t < 224) {
|
||||
chunk[i++] = (t & 31) << 6 | buffer[start++] & 63;
|
||||
} else if (t > 239 && t < 365) {
|
||||
t = ((t & 7) << 18 | (buffer[start++] & 63) << 12 | (buffer[start++] & 63) << 6 | buffer[start++] & 63) - 0x10000;
|
||||
chunk[i++] = 0xD800 + (t >> 10);
|
||||
chunk[i++] = 0xDC00 + (t & 1023);
|
||||
} else {
|
||||
chunk[i++] = (t & 15) << 12 | (buffer[start++] & 63) << 6 | buffer[start++] & 63;
|
||||
}
|
||||
if (i > 8191) {
|
||||
(parts || (parts = [])).push(String.fromCharCode.apply(String, chunk));
|
||||
i = 0;
|
||||
}
|
||||
}
|
||||
if (parts) {
|
||||
if (i) parts.push(String.fromCharCode.apply(String, chunk.slice(0, i)));
|
||||
return parts.join("");
|
||||
}
|
||||
return String.fromCharCode.apply(String, chunk.slice(0, i));
|
||||
}
|
||||
|
||||
// see: https://github.com/dcodeIO/protobuf.js/tree/master/lib/base64
|
||||
function base64_decode(string: string): Uint8Array {
|
||||
var length = string.length;
|
||||
if (length) {
|
||||
let n = 0,
|
||||
p = length;
|
||||
while (--p % 4 > 1 && string.charCodeAt(p) === 61) ++n;
|
||||
length = Math.ceil(length * 3) / 4 - n;
|
||||
}
|
||||
var buffer = new Uint8Array(length);
|
||||
var j = 0, o = 0, t = 0;
|
||||
for (let i = 0, k = string.length; i < k;) {
|
||||
let c = string.charCodeAt(i++);
|
||||
if (c === 61 && j > 1) break;
|
||||
if ((c = s64[c]) === undefined) throw Error();
|
||||
switch (j) {
|
||||
case 0: { t = c; j = 1; break; }
|
||||
case 1: { buffer[o++] = t << 2 | (c & 48) >> 4; t = c; j = 2; break; }
|
||||
case 2: { buffer[o++] = (t & 15) << 4 | (c & 60) >> 2; t = c; j = 3; break; }
|
||||
case 3: { buffer[o++] = (t & 3) << 6 | c; j = 0; break; }
|
||||
}
|
||||
}
|
||||
if (j === 1) throw Error();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
var s64 = new Array(123);
|
||||
for (let i = 0; i < 64;) s64[i < 26 ? i + 65 : i < 52 ? i + 71 : i < 62 ? i - 4 : i - 59 | 43] = i++;
|
8
lib/parse/src/tsconfig.json
Normal file
8
lib/parse/src/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs"
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
]
|
||||
}
|
Reference in New Issue
Block a user