mirror of
https://github.com/fluencelabs/assemblyscript
synced 2025-06-13 06:51:34 +00:00
Progress and a tiny WASM binary parser
This commit is contained in:
95
lib/parse/README.md
Normal file
95
lib/parse/README.md
Normal file
@ -0,0 +1,95 @@
|
||||
#  parse
|
||||
|
||||
A WebAssembly binary parser in WebAssembly.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
* **parse**(binary: `Uint8Array`, options?: `ParseOptions`): `void`<br />
|
||||
Parses the contents of a WebAssembly binary according to the specified options.
|
||||
|
||||
* **ParseOptions**<br />
|
||||
Options specified to the parser. The `onSection` callback determines the sections being evaluated in detail.
|
||||
|
||||
* **onSection**?(id: `SectionId`, payloadOff: `number`, payloadLen: `number`, nameOff: `number`, nameLen: `number`): `boolean`<br />
|
||||
Called with each section in the binary. Returning `true` evaluates the section.
|
||||
|
||||
* **onType**?(index: `number`, form: `number`): `void`<br />
|
||||
Called with each function type if the type section is evaluated.
|
||||
|
||||
* **onTypeParam**?(index: `number`, paramIndex: `number`, paramType: `Type`): `void`<br />
|
||||
Called with each function parameter if the type section is evaluated.
|
||||
|
||||
* **onTypeReturn**?(index: `number`, returnIndex: `number`, returnType: `Type`): `void`<br />
|
||||
Called with each function return type if the type section is evaluated.
|
||||
|
||||
* **onImport**?(index: `number`, kind: `ExternalKind`, moduleOff: `number`, moduleLen: `number`, fieldOff: `number`, fieldLen: `number`): `void`<br />
|
||||
Called with each import if the import section is evaluated.
|
||||
|
||||
* **onFunctionImport**?(index: `number`, type: `number`): `void`<br />
|
||||
Called with each function import if the import section is evaluated.
|
||||
|
||||
* **onTableImport**?(index: `number`, type: `Type`, initial: `number`, maximum: `number`, flags: `number`): `void`<br />
|
||||
Called with each table import if the import section is evaluated.
|
||||
|
||||
* **onMemoryImport**?(index: `number`, initial: `number`, maximum: `number`, flags: `number`): `void`<br />
|
||||
Called with each memory import if the import section is evaluated.
|
||||
|
||||
* **onGlobalImport**?(index: `number`, type: `Type`, mutability: `number`): `void`<br />
|
||||
Called with each global import if the import section is evaluated.
|
||||
|
||||
* **onMemory**?(index: `number`, initial: `number`, maximum: `number`, flags: `number`): `void`<br />
|
||||
Called with each memory if the memory section is evaluated.
|
||||
|
||||
* **onFunction**?(index: `number`, typeIndex: `number`): `void`<br />
|
||||
Called with each function if the function section is evaluated.
|
||||
|
||||
* **onGlobal**?(index: `number`, type: `Type`, mutability: `number`): `void`<br />
|
||||
Called with each global if the global section is evaluated.
|
||||
|
||||
* **onStart**?(index: `number`): `void`<br />
|
||||
Called with the start function index if the start section is evaluated.
|
||||
|
||||
* **onExport**?(index: `number`, kind: `ExternalKind`, kindIndex: `number`, nameOff: `number`, nameLen: `number`): `void`<br />
|
||||
Called with each export if the export section is evaluated.
|
||||
|
||||
* **Type**<br />
|
||||
A value or element type, depending on context.
|
||||
|
||||
| Name | Value
|
||||
|---------|-------
|
||||
| i32 | 0x7f
|
||||
| i64 | 0x7e
|
||||
| f32 | 0x7d
|
||||
| f64 | 0x7c
|
||||
| anyfunc | 0x70
|
||||
| func | 0x60
|
||||
| none | 0x40
|
||||
|
||||
* **SectionId**<br />
|
||||
Numerical id of the current section.
|
||||
|
||||
| Name | Value
|
||||
|----------|-------
|
||||
| Custom | 0
|
||||
| Type | 1
|
||||
| Import | 2
|
||||
| Function | 3
|
||||
| Table | 4
|
||||
| Memory | 5
|
||||
| Global | 6
|
||||
| Export | 7
|
||||
| Start | 8
|
||||
| Element | 9
|
||||
| Code | 10
|
||||
| Data | 11
|
||||
|
||||
* **ExternalKind**<br />
|
||||
Kind of an export or import.
|
||||
|
||||
| Name | Value
|
||||
|----------|-------
|
||||
| Function | 0
|
||||
| Table | 1
|
||||
| Memory | 2
|
||||
| Global | 3
|
285
lib/parse/assembly/index.ts
Normal file
285
lib/parse/assembly/index.ts
Normal file
@ -0,0 +1,285 @@
|
||||
/** A WebAssembly module that parses WebAssembly modules. */
|
||||
|
||||
// Common constants shared between AssemblyScript and TypeScript
|
||||
import {
|
||||
Type,
|
||||
SectionId,
|
||||
ExternalKind,
|
||||
MAX_PAGES,
|
||||
MAX_TABLES,
|
||||
Opcode
|
||||
} from "../src/common";
|
||||
|
||||
/** Current offset in memory. */
|
||||
var off: usize = 0;
|
||||
|
||||
/** Reads an unsigned integer from memory. */
|
||||
function readUint<T>(): u32 {
|
||||
var pos = off;
|
||||
var val = <u32>load<T>(pos);
|
||||
off = pos + sizeof<T>();
|
||||
return val;
|
||||
}
|
||||
|
||||
/** Reads an unsigned 64-bit integer from memory. */
|
||||
function readUint64(): u64 {
|
||||
var pos = off;
|
||||
var val = load<u64>(pos);
|
||||
off = pos + 8;
|
||||
return val;
|
||||
}
|
||||
|
||||
/** Reads a LEB128-encoded unsigned integer from memory. */
|
||||
function readVaruint(size: u32): u32 {
|
||||
var val: u32 = 0;
|
||||
var shl: u32 = 0;
|
||||
var byt: u32;
|
||||
var pos = off;
|
||||
do {
|
||||
byt = load<u8>(pos++);
|
||||
val |= (byt & 0x7F) << shl;
|
||||
if (!(byt & 0x80)) break;
|
||||
shl += 7;
|
||||
} while (true);
|
||||
off = pos;
|
||||
return val;
|
||||
}
|
||||
|
||||
/** Reads a LEB128-encoded signed integer from memory. */
|
||||
function readVarint(size: u32): i32 {
|
||||
var val: u32 = 0;
|
||||
var shl: u32 = 0;
|
||||
var byt: u32;
|
||||
var pos = off;
|
||||
do {
|
||||
byt = load<u8>(pos++);
|
||||
val |= (byt & 0x7F) << shl;
|
||||
shl += 7;
|
||||
} while (byt & 0x80);
|
||||
off = pos;
|
||||
return select<u32>(val | (~0 << shl), val, shl < size && (byt & 0x40) != 0);
|
||||
}
|
||||
|
||||
/** Reads a LEB128-encoded signed 64-bit integer from memory. */
|
||||
function readVarint64(): i64 {
|
||||
var val: u64 = 0;
|
||||
var shl: u64 = 0;
|
||||
var byt: u64;
|
||||
var pos = off;
|
||||
do {
|
||||
byt = load<u8>(pos++);
|
||||
val |= (byt & 0x7F) << shl;
|
||||
shl += 7;
|
||||
} while (byt & 0x80);
|
||||
off = pos;
|
||||
return select<u64>(val | (~0 << shl), val, shl < 64 && (byt & 0x40) != 0);
|
||||
}
|
||||
|
||||
// Imported callbacks
|
||||
declare function onSection(id: u32, offset: u32, length: u32, nameOffset: u32, nameLength: u32): bool;
|
||||
declare function onType(index: u32, form: u32): void;
|
||||
declare function onTypeParam(index: u32, paramIndex: u32, paramType: u32): void;
|
||||
declare function onTypeReturn(index: u32, returnIndex: u32, returnType: u32): void;
|
||||
declare function onImport(index: u32, kind: u32, moduleOff: u32, moduleLen: u32, fieldOff: u32, fieldLen: u32): void;
|
||||
declare function onFunctionImport(index: u32, type: u32): void;
|
||||
declare function onTableImport(index: u32, type: u32, initial: u32, maximum: u32, flags: u32): void;
|
||||
declare function onMemoryImport(index: u32, initial: u32, maximum: u32, flags: u32): void;
|
||||
declare function onGlobalImport(index: u32, type: u32, mutability: u32): void;
|
||||
declare function onMemory(index: u32, initial: u32, maximum: u32, flags: u32): void;
|
||||
declare function onFunction(index: u32, typeIndex: u32): void;
|
||||
declare function onGlobal(index: u32, type: u32, mutability: u32): void;
|
||||
declare function onExport(index: u32, kind: u32, kindIndex: u32, nameOffset: u32, nameLength: u32): void;
|
||||
declare function onStart(index: u32): void;
|
||||
|
||||
/** Starts parsing the module that has been placed in memory. */
|
||||
function parse(): void {
|
||||
var magic = readUint<u32>();
|
||||
if (magic != 0x6D736100) unreachable();
|
||||
var version = readUint<u32>();
|
||||
if (version != 1) unreachable();
|
||||
var end: usize = current_memory() << 16;
|
||||
while (off < end) {
|
||||
let section_off = off;
|
||||
let id = readVaruint(7);
|
||||
let payload_len = readVaruint(32);
|
||||
let name_off = 0;
|
||||
let name_len = 0;
|
||||
if (!id) {
|
||||
name_len = readVaruint(32);
|
||||
if (!name_len) {
|
||||
off = section_off;
|
||||
break;
|
||||
}
|
||||
name_off = off;
|
||||
off += name_len;
|
||||
} else if (id > <u32>SectionId.Data) unreachable();
|
||||
let payload_off = off;
|
||||
if (onSection(
|
||||
id,
|
||||
payload_off,
|
||||
payload_len,
|
||||
name_off,
|
||||
name_len
|
||||
)) {
|
||||
switch (id) {
|
||||
case SectionId.Type: {
|
||||
let count = readVaruint(32);
|
||||
for (let index: u32 = 0; index < count; ++index) {
|
||||
let form = readVarint(7) & 0x7f;
|
||||
onType(index, form);
|
||||
let paramCount = readVaruint(32);
|
||||
for (let paramIndex: u32 = 0; paramIndex < paramCount; ++paramIndex) {
|
||||
let paramType = readVarint(7) & 0x7f;
|
||||
onTypeParam(index, paramIndex, paramType);
|
||||
}
|
||||
let returnCount = readVaruint(1); // MVP
|
||||
for (let returnIndex: u32 = 0; returnIndex < returnCount; ++returnIndex) {
|
||||
let returnType = readVarint(7) & 0x7f;
|
||||
onTypeReturn(index, returnIndex, returnType);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SectionId.Import: {
|
||||
let count = readVaruint(32);
|
||||
for (let index: u32 = 0; index < count; ++index) {
|
||||
let module_len = readVaruint(32);
|
||||
let module_off = off;
|
||||
off += module_off;
|
||||
let field_len = readVaruint(32);
|
||||
let field_off = off;
|
||||
off += field_off;
|
||||
let kind = readUint<u8>();
|
||||
onImport(
|
||||
index,
|
||||
kind,
|
||||
module_off,
|
||||
module_len,
|
||||
field_off,
|
||||
field_len
|
||||
);
|
||||
switch (kind) {
|
||||
case ExternalKind.Function: {
|
||||
let type = readVaruint(32);
|
||||
onFunctionImport(index, type);
|
||||
break;
|
||||
}
|
||||
case ExternalKind.Table: {
|
||||
let type = readVarint(7) & 0x7f;
|
||||
let flags = readVaruint(1);
|
||||
let initial = readVaruint(32);
|
||||
let maximum: u32 = flags & 1 ? readVaruint(32) : MAX_TABLES;
|
||||
onTableImport(index, type, initial, maximum, flags);
|
||||
break;
|
||||
}
|
||||
case ExternalKind.Memory: {
|
||||
let flags = readVaruint(1);
|
||||
let initial = readVaruint(32);
|
||||
let maximum: u32 = flags & 1 ? readVaruint(32) : MAX_PAGES;
|
||||
onMemoryImport(index, initial, maximum, flags);
|
||||
break;
|
||||
}
|
||||
case ExternalKind.Global: {
|
||||
let type = readVarint(7) & 0x7f;
|
||||
let mutability = readVaruint(1);
|
||||
onGlobalImport(index, type, mutability);
|
||||
break;
|
||||
}
|
||||
default: unreachable();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SectionId.Function: {
|
||||
let count = readVaruint(32);
|
||||
for (let index: u32 = 0; index < count; ++index) {
|
||||
let typeIndex = readVaruint(32);
|
||||
onFunction(index, typeIndex);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SectionId.Memory: {
|
||||
let count = readVaruint(32);
|
||||
for (let index: u32 = 0; index < count; ++index) {
|
||||
let flags = readVaruint(1);
|
||||
let initial = readVaruint(32);
|
||||
let maximum: u32 = flags ? readVaruint(32) : MAX_PAGES;
|
||||
onMemory(index, initial, maximum, flags);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SectionId.Global: {
|
||||
let count = readVaruint(32);
|
||||
for (let index: u32 = 0; index < count; ++index) {
|
||||
let type = readVarint(7) & 0x7f;
|
||||
let mutability = readVaruint(1);
|
||||
let op = readUint<u8>();
|
||||
switch (op) {
|
||||
case Opcode.i32_const: {
|
||||
readVarint(32);
|
||||
break;
|
||||
}
|
||||
case Opcode.i64_const: {
|
||||
readVarint64();
|
||||
break;
|
||||
}
|
||||
case Opcode.f32_const: {
|
||||
readUint<u32>();
|
||||
break;
|
||||
}
|
||||
case Opcode.f64_const: {
|
||||
readUint64();
|
||||
break;
|
||||
}
|
||||
case Opcode.get_global: {
|
||||
readVaruint(32);
|
||||
break;
|
||||
}
|
||||
default: unreachable();
|
||||
}
|
||||
op = readUint<u8>();
|
||||
if (op != Opcode.end) unreachable();
|
||||
onGlobal(index, type, mutability);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SectionId.Export: {
|
||||
let count = readVaruint(32);
|
||||
for (let index: u32 = 0; index < count; ++index) {
|
||||
let field_len = readVaruint(32);
|
||||
let field_off = off;
|
||||
off += field_len;
|
||||
let kind = readUint<u8>();
|
||||
let kind_index = readVaruint(32);
|
||||
onExport(
|
||||
index,
|
||||
kind,
|
||||
kind_index,
|
||||
field_off,
|
||||
field_len
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SectionId.Start: {
|
||||
let index = readVaruint(32);
|
||||
onStart(
|
||||
index
|
||||
);
|
||||
break;
|
||||
}
|
||||
case SectionId.Custom:
|
||||
case SectionId.Code: { // TODO
|
||||
off += payload_len;
|
||||
break;
|
||||
}
|
||||
default: unreachable();
|
||||
}
|
||||
} else { // skip
|
||||
off += payload_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start parsing immediately
|
||||
parse();
|
6
lib/parse/assembly/tsconfig.json
Normal file
6
lib/parse/assembly/tsconfig.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../../../std/assembly.json",
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
]
|
||||
}
|
2
lib/parse/build/.gitignore
vendored
Normal file
2
lib/parse/build/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.wasm
|
||||
*.wasm.map
|
1
lib/parse/index.d.ts
vendored
Normal file
1
lib/parse/index.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export * from "./src";
|
2
lib/parse/index.js
Normal file
2
lib/parse/index.js
Normal file
File diff suppressed because one or more lines are too long
1
lib/parse/index.js.map
Normal file
1
lib/parse/index.js.map
Normal file
File diff suppressed because one or more lines are too long
6884
lib/parse/package-lock.json
generated
Normal file
6884
lib/parse/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
lib/parse/package.json
Normal file
30
lib/parse/package.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "@assemblyscript/parse",
|
||||
"version": "1.0.0",
|
||||
"license": "Apache-2.0",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"scripts": {
|
||||
"build:as": "asc assembly/index.ts -O3 -b build/index.wasm --noMemory --importMemory --sourceMap --noDebug --validate",
|
||||
"build": "npm run build:as && webpack --mode production --display-modules",
|
||||
"test": "ts-node tests/"
|
||||
},
|
||||
"files": [
|
||||
"package.json",
|
||||
"index.d.ts",
|
||||
"index.js",
|
||||
"index.js.map",
|
||||
"src/",
|
||||
"README.md"
|
||||
],
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@types/webassembly-js-api": "0.0.1",
|
||||
"assemblyscript": "AssemblyScript/assemblyscript",
|
||||
"ts-loader": "^4.1.0",
|
||||
"ts-node": "^5.0.1",
|
||||
"typescript": "^2.8.1",
|
||||
"webpack": "^4.4.1",
|
||||
"webpack-cli": "^2.0.13"
|
||||
}
|
||||
}
|
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"
|
||||
]
|
||||
}
|
87
lib/parse/tests/index.ts
Normal file
87
lib/parse/tests/index.ts
Normal file
@ -0,0 +1,87 @@
|
||||
import * as fs from "fs";
|
||||
import {
|
||||
Type,
|
||||
SectionId,
|
||||
ExternalKind,
|
||||
parse
|
||||
} from "..";
|
||||
|
||||
const testBinary = fs.readFileSync(__dirname + "/libm.wasm");
|
||||
|
||||
function onSection(id: SectionId, offset: number, length: number, nameOffset: number, nameLength: number): boolean {
|
||||
var name = id == 0 ? parse.readString(nameOffset, nameLength) : SectionId[id];
|
||||
console.log(name + " section at " + offset + ".." + (offset + length));
|
||||
return true;
|
||||
}
|
||||
|
||||
function onType(index: number, form: Type): void {
|
||||
console.log("- FunctionType[" + index + "] is " + Type[form]);
|
||||
}
|
||||
|
||||
function onTypeParam(index: number, paramIndex: number, paramType: Type): void {
|
||||
console.log(" > param[" + paramIndex + "] = " + Type[paramType]);
|
||||
}
|
||||
|
||||
function onTypeReturn(index: number, returnIndex: number, returnType: Type): void {
|
||||
console.log(" > return[" + returnIndex + "] = " + Type[returnType]);
|
||||
}
|
||||
|
||||
function onImport(index: number, kind: ExternalKind, moduleOff: number, moduleLen: number, fieldOff: number, fieldLen: number): void {
|
||||
var moduleName = parse.readString(moduleOff, moduleLen);
|
||||
var fieldName = parse.readString(fieldOff, fieldLen);
|
||||
console.log("- Import[" + index + "] is '" + moduleName + "." + fieldName + "'");
|
||||
}
|
||||
|
||||
function onFunctionImport(index: number, type: number): void {
|
||||
console.log(" > FunctionType[" + type + "]");
|
||||
}
|
||||
|
||||
function onTableImport(index: number, type: Type, initial: number, maximum: number, flags: number): void {
|
||||
console.log(" > " + Type[type] + ", initial=" + initial + ", maximum=" + maximum);
|
||||
}
|
||||
|
||||
function onMemoryImport(index: number, initial: number, maximum: number, flags: number): void {
|
||||
console.log(" > initial=" + initial + ", maximum=" + maximum);
|
||||
}
|
||||
|
||||
function onGlobalImport(index: number, type: Type, mutability: number): void {
|
||||
console.log(" > " + (mutability & 1 ? "mutable " : "const ") + Type[type]);
|
||||
}
|
||||
|
||||
function onMemory(index: number, initial: number, maximum: number, flags: number): void {
|
||||
console.log("- Memory[" + index + "]: initial=" + initial + ", maximum=" + maximum);
|
||||
}
|
||||
|
||||
function onFunction(index: number, typeIndex: number): void {
|
||||
console.log("- Function[" + index + "]: FunctionType[" + typeIndex + "]");
|
||||
}
|
||||
|
||||
function onGlobal(index: number, type: Type, mutability: number): void {
|
||||
console.log("- Global[" + index + "]: " + (mutability & 1 ? "mutable " : "const ") + Type[type]);
|
||||
}
|
||||
|
||||
function onStart(index: number): void {
|
||||
console.log("- Start: Function[" + index + "]");
|
||||
}
|
||||
|
||||
function onExport(index: number, kind: ExternalKind, kindIndex: number, fieldOffset: number, fieldLength: number): void {
|
||||
var field = parse.readString(fieldOffset, fieldLength);
|
||||
console.log("- Export[" + index + "]: '" + field + "' -> " + ExternalKind[kind] + "[" + kindIndex + "]");
|
||||
}
|
||||
|
||||
const result = parse(testBinary, {
|
||||
onSection,
|
||||
onType,
|
||||
onTypeParam,
|
||||
onTypeReturn,
|
||||
onImport,
|
||||
onFunctionImport,
|
||||
onTableImport,
|
||||
onMemoryImport,
|
||||
onGlobalImport,
|
||||
onMemory,
|
||||
onFunction,
|
||||
onGlobal,
|
||||
onStart,
|
||||
onExport
|
||||
});
|
BIN
lib/parse/tests/libm.wasm
Normal file
BIN
lib/parse/tests/libm.wasm
Normal file
Binary file not shown.
37
lib/parse/webpack.config.js
Normal file
37
lib/parse/webpack.config.js
Normal file
@ -0,0 +1,37 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
|
||||
const wasmData = fs.readFileSync(__dirname + "/build/index.wasm");
|
||||
|
||||
module.exports = {
|
||||
entry: [ "./src/index.ts" ],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: "ts-loader",
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: [ ".ts", ".js" ]
|
||||
},
|
||||
node: {
|
||||
fs: false
|
||||
},
|
||||
output: {
|
||||
filename: "index.js",
|
||||
path: __dirname,
|
||||
library: "asparse",
|
||||
libraryTarget: "umd",
|
||||
globalObject: "typeof self !== 'undefined' ? self : this"
|
||||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
WASM_DATA: JSON.stringify(wasmData.toString("base64"))
|
||||
})
|
||||
],
|
||||
devtool: "source-map"
|
||||
};
|
Reference in New Issue
Block a user