mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-26 06:34:20 +08:00
Decode memargs the same way in validation and translation
Validation, translation and instruction skipping now read memarg flags through one function, readMemargFlags in module.ts. Flags with bit 6 set are followed by an explicit memory index, which may be 0; flags of 128 or more are malformed. The translator refuses a module (WasmTranslateError) when a memarg, memory.size, memory.grow, memory.init, memory.copy or memory.fill names a memory other than 0, and when the operand of local.set, local.tee, global.set, table.set, a load or a store does not have the type of its destination. Locals are stored with their declared type.
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
* The decoder never evaluates anything and keeps no module contents beyond
|
||||
* the counts and the feature set it reports. It is plain TypeScript that
|
||||
* the native compiler also compiles. */
|
||||
import { readMemargFlags } from "./module.js";
|
||||
|
||||
/** Proposals the summary can name, in report order. The first group is the
|
||||
* translatable set; the rest are reported as unsupported. */
|
||||
@@ -1244,19 +1245,9 @@ class FunctionValidator {
|
||||
/** A memarg. A memory other than 0 stops typing (multi-memory). */
|
||||
memarg(naturalAlign: number): void {
|
||||
const r = this.r;
|
||||
let align = r.u32();
|
||||
let multi = false;
|
||||
if (align >= 64 && align < 128) {
|
||||
align -= 64;
|
||||
multi = true;
|
||||
} else if (align >= 64) {
|
||||
r.malformed("malformed memop flags");
|
||||
}
|
||||
let memory = 0;
|
||||
if (multi) {
|
||||
memory = r.u32();
|
||||
if (memory !== 0) this.otherMemory(memory);
|
||||
}
|
||||
const { align, memory } = readMemargFlags(r);
|
||||
if (align < 0) r.malformed("malformed memop flags");
|
||||
if (memory !== 0) this.otherMemory(memory);
|
||||
if (this.m.memory64) r.u64();
|
||||
else r.u32();
|
||||
if (!this.typed) return;
|
||||
@@ -1840,9 +1831,9 @@ class FunctionValidator {
|
||||
|
||||
scanMemarg(): void {
|
||||
const r = this.r;
|
||||
const align = r.u32();
|
||||
if (align >= 128) r.malformed("malformed memop flags");
|
||||
if (align >= 64 && r.u32() !== 0) this.m.feature("multi-memory");
|
||||
const { align, memory } = readMemargFlags(r);
|
||||
if (align < 0) r.malformed("malformed memop flags");
|
||||
if (memory !== 0) this.m.feature("multi-memory");
|
||||
if (this.m.memory64) r.u64();
|
||||
else r.u32();
|
||||
}
|
||||
|
||||
@@ -283,6 +283,20 @@ function decodeUtf8(bytes: Uint8Array, start: number, end: number): string {
|
||||
return out;
|
||||
}
|
||||
|
||||
/** The flags of a memarg (the immediates of loads, stores and atomics):
|
||||
* the alignment exponent and the memory index. Flags with bit 6 set
|
||||
* (multi-memory) are followed by an explicit memory index, which may be
|
||||
* 0; flags of 128 or more are malformed and answer align -1 without
|
||||
* reading further. The offset follows. Validation (binary.ts), translation
|
||||
* and skipImmediates all read memargs through this function, so they agree
|
||||
* on where each instruction ends. */
|
||||
export function readMemargFlags(r: { u32(): number }): { align: number; memory: number } {
|
||||
const flags = r.u32();
|
||||
if (flags >= 128) return { align: -1, memory: 0 };
|
||||
if (flags >= 64) return { align: flags - 64, memory: r.u32() };
|
||||
return { align: flags, memory: 0 };
|
||||
}
|
||||
|
||||
/** Reads the immediates of opcode `op` (already consumed). Covers the
|
||||
* translatable feature level; anything else was refused before
|
||||
* translation. */
|
||||
@@ -372,7 +386,9 @@ export function skipImmediates(c: WasmCursor, op: number): void {
|
||||
}
|
||||
default:
|
||||
if (op >= 0x28 && op <= 0x3e) {
|
||||
c.u32();
|
||||
const flags = readMemargFlags(c);
|
||||
if (flags.align < 0 || flags.memory !== 0)
|
||||
throw new Error(`wasm: memarg of memory ${flags.memory} is not supported`);
|
||||
c.u32();
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
VAL_I64,
|
||||
WasmCursor,
|
||||
parseWasmModule,
|
||||
readMemargFlags,
|
||||
skipImmediates,
|
||||
type WasmFuncType,
|
||||
type WasmModule,
|
||||
@@ -830,6 +831,39 @@ class FunctionTranslator {
|
||||
return v;
|
||||
}
|
||||
|
||||
/** Pops a value that must have type `t`: the type a local, global,
|
||||
* table or memory store writes. Validation guarantees it; a mismatch
|
||||
* means the translator and the validator disagree about the code, so
|
||||
* the module is not translated. */
|
||||
popType(t: number, what: string): StackValue {
|
||||
const v = this.pop();
|
||||
if (v.t !== t) throw new WasmTranslateError(`${what} operand type does not match`);
|
||||
return v;
|
||||
}
|
||||
|
||||
/** The declared type of local `i` (parameters first). */
|
||||
localType(i: number): number {
|
||||
const t = this.localTypes[i];
|
||||
if (t === undefined) throw new WasmTranslateError(`unknown local ${i}`);
|
||||
return t;
|
||||
}
|
||||
|
||||
/** The offset of a memarg over memory 0 (the only memory a translatable
|
||||
* module has). */
|
||||
memargOffset(c: WasmCursor): number {
|
||||
const flags = readMemargFlags(c);
|
||||
if (flags.align < 0 || flags.memory !== 0)
|
||||
throw new WasmTranslateError(`memarg of memory ${flags.memory} is not supported`);
|
||||
return c.u32();
|
||||
}
|
||||
|
||||
/** A memory index immediate (memory.size, memory.grow and the bulk
|
||||
* memory instructions), which must be 0. */
|
||||
memoryIndex(c: WasmCursor): void {
|
||||
const index = c.u32();
|
||||
if (index !== 0) throw new WasmTranslateError(`memory ${index} is not supported`);
|
||||
}
|
||||
|
||||
popN(n: number): StackValue[] {
|
||||
const out = this.stack.splice(this.stack.length - n, n);
|
||||
if (out.length !== n) throw new Error("wasm translator: operand stack underflow");
|
||||
@@ -1540,20 +1574,23 @@ class FunctionTranslator {
|
||||
}
|
||||
case 0x20: {
|
||||
const i = c.u32();
|
||||
const t = this.localTypes[i]!;
|
||||
const t = this.localType(i);
|
||||
this.push(this.emit(`load ${llType(t)}, ptr %l${i}`), t);
|
||||
return;
|
||||
}
|
||||
case 0x21: {
|
||||
const i = c.u32();
|
||||
const v = this.pop();
|
||||
this.add(` store ${llType(v.t)} ${v.v}, ptr %l${i}`);
|
||||
const t = this.localType(i);
|
||||
const v = this.popType(t, "local.set");
|
||||
this.add(` store ${llType(t)} ${v.v}, ptr %l${i}`);
|
||||
return;
|
||||
}
|
||||
case 0x22: {
|
||||
const i = c.u32();
|
||||
const v = this.stack[this.stack.length - 1]!;
|
||||
this.add(` store ${llType(v.t)} ${v.v}, ptr %l${i}`);
|
||||
const t = this.localType(i);
|
||||
const v = this.popType(t, "local.tee");
|
||||
this.stack.push(v);
|
||||
this.add(` store ${llType(t)} ${v.v}, ptr %l${i}`);
|
||||
return;
|
||||
}
|
||||
case 0x23: {
|
||||
@@ -1565,7 +1602,9 @@ class FunctionTranslator {
|
||||
}
|
||||
case 0x24: {
|
||||
const g = c.u32();
|
||||
const v = this.pop();
|
||||
const global = this.m.globals[g];
|
||||
if (global === undefined) throw new WasmTranslateError(`unknown global ${g}`);
|
||||
const v = this.popType(global.type, "global.set");
|
||||
const cell = this.loadPtrSlot(this.mt.globalSlot(g));
|
||||
if (v.t === VAL_EXTERNREF)
|
||||
this.add(` call void @scr_wasm_global_set_ref(ptr ${cell}, ptr ${v.v})`);
|
||||
@@ -1583,13 +1622,15 @@ class FunctionTranslator {
|
||||
}
|
||||
case 0x26: {
|
||||
const t = c.u32();
|
||||
const v = this.pop();
|
||||
const i = this.pop();
|
||||
const table = this.m.tables[t];
|
||||
if (table === undefined) throw new WasmTranslateError(`unknown table ${t}`);
|
||||
const v = this.popType(table.elemType, "table.set");
|
||||
const i = this.popType(VAL_I32, "table.set");
|
||||
this.add(` call void @scr_wasm_table_set(ptr %inst, i32 ${t}, i32 ${i.v}, ptr ${v.v})`);
|
||||
return;
|
||||
}
|
||||
case 0x3f: {
|
||||
c.u32();
|
||||
this.memoryIndex(c);
|
||||
const mem = this.loadPtrSlot(OFF_MEMORY);
|
||||
const sizePtr = this.emit(`getelementptr inbounds i8, ptr ${mem}, i64 8`);
|
||||
const size = this.emit(`load i64, ptr ${sizePtr}, align 8`);
|
||||
@@ -1598,7 +1639,7 @@ class FunctionTranslator {
|
||||
return;
|
||||
}
|
||||
case 0x40: {
|
||||
c.u32();
|
||||
this.memoryIndex(c);
|
||||
const delta = this.pop();
|
||||
this.push(this.emit(`call i32 @scr_wasm_memory_grow(ptr %inst, i32 ${delta.v})`), VAL_I32);
|
||||
this.refreshMemory();
|
||||
@@ -1641,10 +1682,9 @@ class FunctionTranslator {
|
||||
break;
|
||||
}
|
||||
if (op >= 0x28 && op <= 0x35) {
|
||||
c.u32();
|
||||
const offset = c.u32();
|
||||
const offset = this.memargOffset(c);
|
||||
const [vt, mt, bytes, ext] = LOAD_INFO[op]!;
|
||||
const addr = this.pop();
|
||||
const addr = this.popType(VAL_I32, "load address");
|
||||
const p = this.address(addr.v, offset, bytes);
|
||||
const line = this.lines.lines.length;
|
||||
const loaded = this.emit(`load ${mt}, ptr ${p}, align 1`);
|
||||
@@ -1656,11 +1696,10 @@ class FunctionTranslator {
|
||||
return;
|
||||
}
|
||||
if (op >= 0x36 && op <= 0x3e) {
|
||||
c.u32();
|
||||
const offset = c.u32();
|
||||
const [, mt, bytes] = STORE_INFO[op]!;
|
||||
const v = this.pop();
|
||||
const addr = this.pop();
|
||||
const offset = this.memargOffset(c);
|
||||
const [vt, mt, bytes] = STORE_INFO[op]!;
|
||||
const v = this.popType(vt, "store");
|
||||
const addr = this.popType(VAL_I32, "store address");
|
||||
const p = this.address(addr.v, offset, bytes);
|
||||
let value = v.v;
|
||||
if (mt === "i8" || mt === "i16" || (mt === "i32" && v.t === VAL_I64)) {
|
||||
@@ -1960,7 +1999,7 @@ class FunctionTranslator {
|
||||
case 8: {
|
||||
// memory.init
|
||||
const seg = c.u32();
|
||||
c.u32();
|
||||
this.memoryIndex(c);
|
||||
const n = this.pop();
|
||||
const s = this.pop();
|
||||
const d = this.pop();
|
||||
@@ -1973,8 +2012,8 @@ class FunctionTranslator {
|
||||
this.add(` call void @scr_wasm_data_drop(ptr %inst, i32 ${c.u32()})`);
|
||||
return;
|
||||
case 10: {
|
||||
c.u32();
|
||||
c.u32();
|
||||
this.memoryIndex(c);
|
||||
this.memoryIndex(c);
|
||||
const n = this.pop();
|
||||
const s = this.pop();
|
||||
const d = this.pop();
|
||||
@@ -1984,7 +2023,7 @@ class FunctionTranslator {
|
||||
return;
|
||||
}
|
||||
case 11: {
|
||||
c.u32();
|
||||
this.memoryIndex(c);
|
||||
const n = this.pop();
|
||||
const v = this.pop();
|
||||
const d = this.pop();
|
||||
|
||||
Reference in New Issue
Block a user