Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions src/engine/BinParser.v3
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ class BinParser(extensions: Extension.set, limits: Limits, err: ErrorGen, filena
}
Start => readStartSection();
DataCount => readDataCountSection();
Environment => {
if (!extensions.JIT_INTERFACE) return err.at(d).InvalidSectionCode(kind.code);
var count = readCountAndReserve("environments", module.environments, limits.max_num_environments);
readLoop("environments", count, readEnvironDecl, if(outline != null, outline.environments.set(startAddr, size)));
}
_ => return err.at(d).InvalidSectionCode(kind.code);
}
if (Trace.binparse) {
Expand Down Expand Up @@ -692,6 +697,47 @@ class BinParser(extensions: Extension.set, limits: Limits, err: ErrorGen, filena
var d = DataDecl.new(mode, data);
module.data.put(d);
}
def readEnvironDecl(index: int) {
var pt = decoder.pos;
var flags = parser.readByte("flags", BpConstants.renderNone);
if (flags != 0) err.rel(decoder, pt + 1).ExpectedEnvironFlagsZeroByte(flags);
var environ = EnvironDecl.new(module, extensions, limits);
environ.module = Module.new(Strings.format2("%s.env[%d]", module.filename, index));
module.addDecl(environ);
var count = readCountAndReserve("imports", environ.mapping, limits.max_num_imports);
readLoop("imports", count, readEnvironImportDecl(environ, _), null);
}
def readEnvironImportDecl(environ: EnvironDecl, index: int) {
var pt = decoder.pos;
var kind = parser.readByte("import kind", BpConstants.renderImportKind);
match (kind) {
BpImportExportKind.Function.code => {
var func = parser.readFuncRef();
if (func != null) environ.refOuterFunc(func);
}
BpImportExportKind.Table.code => {
var table = parser.readTableRef();
if (table != null) environ.refOuterTable(table);
}
BpImportExportKind.Memory.code => {
var mem = parser.readMemoryRef(extensions.MULTI_MEMORY);
if (mem != null) environ.refOuterMemory(mem);
}
BpImportExportKind.Global.code => {
var global = parser.readGlobalRef();
if (global != null) environ.refOuterGlobal(global);
}
BpImportExportKind.Tag.code => {
var tag = parser.readTagRef();
if (tag != null) environ.refOuterTag(tag);
}
BpImportExportKind.HeapType.code => {
var ty = parser.readHeapTypeRef();
if (ty != null) environ.module.addDecl(ty);
}
_ => err.rel(decoder, pt).InvalidImportKind(kind);
}
}
def checkElemsType(pt: int, table: TableDecl, vt: ValueType) {
if (table == null) return;
if (ValueTypes.isAssignable(vt, table.elemtype)) return;
Expand Down
1 change: 1 addition & 0 deletions src/engine/BpConstants.v3
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ enum BpSection(code: byte) {
Start(8),
Element(9),
DataCount(12),
Environment(14),
Code(10),
Data(11),
}
Expand Down
2 changes: 2 additions & 0 deletions src/engine/BytecodeIterator.v3
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ class BytecodeIterator {
def read_ARRAYT = read_uleb;
def read_HEAPT = read_ileb;
def read_SIG = read_uleb;
def read_ENV = read_uleb;
def read_FUNC = read_uleb;
def read_FIELD = read_uleb;
def read_TAG = read_uleb;
Expand Down Expand Up @@ -430,6 +431,7 @@ class BytecodeIterator {
ARRAY_INIT_ELEM => v.visit_ARRAY_INIT_ELEM(read_ARRAYT(), read_ELEM());
ANY_CONVERT_EXTERN => v.visit_ANY_CONVERT_EXTERN();
EXTERN_CONVERT_ANY => v.visit_EXTERN_CONVERT_ANY();
FUNC_NEW => { read_ZEROB(); v.visit_FUNC_NEW(read_MEMORY(), read_SIG(), read_ENV()); }
I32_TRUNC_SAT_F32_S => v.visit_I32_TRUNC_SAT_F32_S();
I32_TRUNC_SAT_F32_U => v.visit_I32_TRUNC_SAT_F32_U();
I32_TRUNC_SAT_F64_S => v.visit_I32_TRUNC_SAT_F64_S();
Expand Down
3 changes: 2 additions & 1 deletion src/engine/CodePtr.v3
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ class CodePtr extends DataReader {
U32,
I32,
I64,
CONT_INDEX => skip_leb();
CONT_INDEX,
ENV_INDEX => skip_leb();
BLOCK_TYPE => skip_block_type();
VALUE_TYPES => skip_value_types();
LABELS => {
Expand Down
16 changes: 16 additions & 0 deletions src/engine/CodeValidator.v3
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,22 @@ class CodeValidator(extensions: Extension.set, limits: Limits, module: Module, e
var et = if(nullable, ValueTypes.EXTERNREF, ValueTypes.EXTERNREF_NONNULL);
push(et);
}
FUNC_NEW => {
if (!checkExtension(Extension.JIT_INTERFACE, opcode)) return;
var flags_pos = codeptr.pos;
var flags = codeptr.read1();
if (flags != 0) err_atpos(flags_pos).ExpectedFuncNewZeroByte(flags);
var mem = parser.readMemoryRef(extensions.MULTI_MEMORY);
if (mem == null) return;
var sig = parser.readSigRef();
if (sig == null) return;
var env = parser.readEnvRef();
if (env == null) return;
var it = mem.size.indexType();
popE(it);
popE(it);
push(ValueType.Ref(false, HeapType.Func(sig)));
}
I32_TRUNC_SAT_F32_S => checkSignature(Opcode.I32_TRUNC_SAT_F32_S.sig);
I32_TRUNC_SAT_F32_U => checkSignature(Opcode.I32_TRUNC_SAT_F32_U.sig);
I32_TRUNC_SAT_F64_S => checkSignature(Opcode.I32_TRUNC_SAT_F64_S.sig);
Expand Down
2 changes: 2 additions & 0 deletions src/engine/Extension.v3
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ enum Extension(short_name: string, help: string) {
EXTENDED_CONST("extended-const", "Extended constant expressions"),
RELAXED_SIMD("relaxed-simd", "Relaxed SIMD"),
WASM_3_0("wasm-3.0", "All Wasm 3.0 features"),
JIT_INTERFACE("jit-interface", "Runtime code generation with func.new"),
WIZENG("wizeng", "Wizard-specific engine capabilities"),
}
component Extensions {
Expand Down Expand Up @@ -50,6 +51,7 @@ component Extensions {
set |= Extension.EXCEPTION_HANDLING;
set |= Extension.EXTENDED_CONST;
}
if (set.JIT_INTERFACE) set |= Extension.FUNCTION_REFERENCES; // func.new produces a (ref $ft)
if (set.GC) set |= Extension.FUNCTION_REFERENCES;
if (set.FUNCTION_REFERENCES) set |= Extension.TAIL_CALL;
if (set.EXCEPTION_HANDLING) set |= Extension.TAIL_CALL;
Expand Down
1 change: 1 addition & 0 deletions src/engine/Instance.v3
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class Instance(module: Module, imports: Array<Exportable>) {
def dropped_elems = Array<bool>.new(module.elems.length);
def elems = Array<Array<Value>>.new(module.elems.length);
def dropped_data = Array<bool>.new(module.data.length);
def environments = Array<Instance>.new(module.environments.length);

def getFunctionAsVal(func_index: int) -> Value.Ref {
return Value.Ref(functions[func_index]);
Expand Down
16 changes: 16 additions & 0 deletions src/engine/Instantiator.v3
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,22 @@ class Instantiator(extensions: Extension.set, module: Module, var imports: Array
Continuation(index) => {
// TODO-SS: implement
}
Environment(index) => {
// Map the environment's inner module's imports to the outer declarations.
var env = module.environments[index];
var inner = Instance.new(env.module, Array<Exportable>.new(env.module.imports.length));
instance.environments[index] = inner;
for (i < env.mapping.length) {
var t = env.mapping[i], d = t.0, nd = t.1;
match (nd) {
x: FuncDecl => inner.functions[x.func_index] = instance.functions[FuncDecl.!(d).func_index];
x: TableDecl => inner.tables[x.table_index] = instance.tables[TableDecl.!(d).table_index];
x: MemoryDecl => inner.memories[x.memory_index] = instance.memories[MemoryDecl.!(d).memory_index];
x: GlobalDecl => inner.globals[x.global_index] = instance.globals[GlobalDecl.!(d).global_index];
x: TagDecl => inner.tags[x.tag_index] = instance.tags[TagDecl.!(d).tag_index];
}
}
}
}
}
if (error.error()) return null;
Expand Down
1 change: 1 addition & 0 deletions src/engine/Limits.v3
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Limits {
var max_grow_size = 1000000;
var max_supertypes = 1u;
var max_log2_page_size = 16u;
var max_num_environments = 100000u;

var ok_memory_flags = BpMemoryFlag.HasMax.mask | BpMemoryFlag.Shared.mask;
var ok_table_flags = BpTableFlag.HasMax.mask;
Expand Down
26 changes: 26 additions & 0 deletions src/engine/Module.v3
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Module(filename: string) {
def elems = Vector<ElemDecl>.new();
def data = Vector<DataDecl>.new();
def custom_sections = Vector<CustomSection>.new();
def environments = Vector<EnvironDecl>.new();
var probes: Array<Array<Probe>>;
var dyn_probes: Vector<(int, int, Probe)>;
var outline: ModuleOutline;
Expand Down Expand Up @@ -86,6 +87,11 @@ class Module(filename: string) {
decls.put(Decl2.Continuation(index));
heaptypes.put(x);
}
x: EnvironDecl => {
var index = environments.length;
decls.put(Decl2.Environment(x.environ_index = index));
environments.put(x);
}
}
}
// Add a new import declaration to this module. Adds this declaration to {imports} as well.
Expand Down Expand Up @@ -293,6 +299,24 @@ type SegmentMode {
class DataDecl(mode: SegmentMode, data: Array<byte>) {
}

// An environment is a nested submodule that refers to members in the outer module.
class EnvironDecl(outer: Module, extensions: Extension.set, limits: Limits) extends Decl {
def mapping = Vector<(Decl, Decl)>.new(); // maps outer decl to inner decl
var environ_index = -1;
var module: Module; // inner module
var validator: CodeValidator; // runtime cache

def refOuterFunc(x: FuncDecl) { var nf = FuncDecl.new(x.sig_index); refOuter(x, nf); nf.sig = x.sig; }
def refOuterTable(x: TableDecl) { refOuter(x, TableDecl.new(x.elemtype, x.size)); }
def refOuterMemory(x: MemoryDecl) { refOuter(x, MemoryDecl.new(x.size, x.shared, x.log2_pageSize)); }
def refOuterGlobal(x: GlobalDecl) { refOuter(x, GlobalDecl.new(x.valtype, x.mutable, x.init)); }
def refOuterTag(x: TagDecl) { var nt = TagDecl.new(x.sig_index); refOuter(x, nt); nt.fields = x.fields; }
private def refOuter(d: Decl, nd: Decl) {
mapping.put(d, nd);
module.addImport(outer.name, null, nd);
}
}

// An uninterpreted custom section.
class CustomSection(name: string, payload: Array<byte>) {
}
Expand Down Expand Up @@ -366,6 +390,7 @@ type Decl2 {
case Global(index: int);
case Tag(index: int);
case Continuation(index: int);
case Environment(index: int);
}

// Stores handler information for efficient runtime lookup.
Expand All @@ -391,6 +416,7 @@ class ModuleOutline {
def elements = SectionOutline.new();
def data = SectionOutline.new();
def custom_sections = Vector<(u64, u64)>.new();
def environments = SectionOutline.new();
}
// For sections that have repeated entries, this helper simplifies getting a range of
// a specific entry.
Expand Down
8 changes: 6 additions & 2 deletions src/engine/Opcodes.v3
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ enum Opcode(prefix: u8, code: u16, mnemonic: string, imms: Array<ImmKind>, sig:
REF_I31 (0xFB, 0x1C, "ref.i31", imm.NONE, null), // XXX: could have sig
I31_GET_S (0xFB, 0x1D, "i31.get_s", imm.NONE, null), // XXX: could have sig
I31_GET_U (0xFB, 0x1E, "i31.get_u", imm.NONE, null), // XXX: could have sig
FUNC_NEW (0xFB, 0x27, "func.new", imm.ZERO_MEMORY_SIG_ENV, null),
// 0xFC prefix: saturating truncations.
I32_TRUNC_SAT_F32_S (0xFC, 0x00, "i32.trunc_sat_f32_s", imm.NONE, sig.f_i),
I32_TRUNC_SAT_F32_U (0xFC, 0x01, "i32.trunc_sat_f32_u", imm.NONE, sig.f_i),
Expand Down Expand Up @@ -642,8 +643,9 @@ enum ImmKind {
BR_CAST, // BR_CAST
CATCHES, // CATCH
CONT_INDEX, // CONT
EX_HANDLERS // EX_HANDLERS
SUS_HANDLERS // SUS_HANDLERS
EX_HANDLERS, // EX_HANDLERS
SUS_HANDLERS, // SUS_HANDLERS
ENV_INDEX
}
// Cached immediate signatures
component ImmSigs {
Expand Down Expand Up @@ -695,6 +697,7 @@ component ImmSigs {
def CONT_HANDLE = [ImmKind.CONT_INDEX, ImmKind.SUS_HANDLERS];
def CONT_TAG_HANDLE = [ImmKind.CONT_INDEX, ImmKind.TAG_INDEX, ImmKind.SUS_HANDLERS];
def CONT_TAG = [ImmKind.CONT_INDEX, ImmKind.TAG_INDEX];
def ZERO_MEMORY_SIG_ENV = [ImmKind.ZERO_BYTE, ImmKind.MEMORY_INDEX, ImmKind.SIG_INDEX, ImmKind.ENV_INDEX];
}

// Internal opcodes used by the interpreter.
Expand Down Expand Up @@ -1212,6 +1215,7 @@ class InstrTracer {
TAG_INDEX => out.put1("tag=%d", codeptr.read_uleb32());
DATA_INDEX => out.put1("data=%d", codeptr.read_uleb32());
LANE_INDEX => out.put1("lane=%d", codeptr.read1());
ENV_INDEX => out.put1("env=%d", codeptr.read_uleb32());
U32 => out.put1("%d", codeptr.read_uleb32());
I32 => out.put1("%d", codeptr.read_sleb32());
I64 => out.put1("%d", codeptr.read_sleb64());
Expand Down
36 changes: 36 additions & 0 deletions src/engine/Runtime.v3
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,42 @@ component Runtime {
if (!r) return stack.trap(TrapReason.ARRAY_OOB);
return null;
}
def FUNC_NEW(stack: ExecStack, outer: Instance, mem_index: u31, sig_index: u31, env_index: u31) -> Throwable {
// Get new code from outer module's memory.
var memory = outer.memories[mem_index], sc = memory.decl.size;
var size = stack.popa(sc);
var src = stack.popa(sc);
var r = memory.range_ol_64(src, size).then(Ranges.dup<byte>); // copy the code to avoid data races if multiple threads
if (r.reason != TrapReason.NONE) return stack.trap(r.reason); // trap if out-of-bounds
var instance = outer.environments[env_index], module = instance.module;
// Create a new function.
var func = FuncDecl.new(sig_index);
func.setOrigCode(r.result);
func.sig = SigDecl.!(outer.heaptypes[sig_index]);
func.func_index = module.functions.length + module.new_funcs++;
// Get the environment and create or use cached CodeValidator.
var env = outer.module.environments[env_index], validator = env.validator;
if (validator == null) {
env.validator = validator = CodeValidator.new(env.extensions, env.limits, module,
ErrorGen.new(module.filename));
}
var err = validator.err.reset();
// Inform the tiering configuration of new code, prior to validation.
Execute.tiering.onFuncBody(module, u32.!(func.func_index), func.orig_bytecode, err);
// Run validation and handle errors.
match (validator.validateFunc(func, 0)) {
Ok => ;
Error(error_code, error_pos, error_msg) => {
// TODO Trace.OUT.put3("error[%s]: %s @ +%d", error_code.name, error_msg, error_pos).ln();
return stack.trap(TrapReason.FUNC_INVALID); // trap if validation failed
}
}
// Create a new function instance wrapping the innner instance.
var wf = WasmFunction.new(instance, func);
Execute.tiering.onNewFunction(wf, err); // inform tiering a new function is validated and finished
stack.push(Value.Ref(wf)); // return funcref result
return null;
}
// --- Table operations -----------------------------------------------------------------------------------------
def TABLE_INIT(stack: ExecStack, instance: Instance, elem_index: u31, table_index: u31) -> Throwable {
var evals = instance.getElems(elem_index);
Expand Down
6 changes: 6 additions & 0 deletions src/engine/WasmParser.v3
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ class WasmParser(extensions: Extension.set, limits: Limits, module: Module,
_ => return HeapType.ANY;
}
}
def readHeapTypeRef() -> HeapTypeDecl {
return readIndex("heap type", module.heaptypes);
}
def readStructType() -> StructDecl {
var pt = decoder.pos;
var ht = readIndex("struct", module.heaptypes);
Expand All @@ -239,6 +242,9 @@ class WasmParser(extensions: Extension.set, limits: Limits, module: Module,
err.rel(decoder, pt).ExpectedArrayType(ht);
return null;
}
def readEnvRef() -> EnvironDecl {
return readIndex("environment", module.environments);
}
def readU32_i(quantity: string, max: u32) -> int {
return int.!(readU32(quantity, max));
}
Expand Down
7 changes: 7 additions & 0 deletions src/engine/v3/V3Interpreter.v3
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,13 @@ class V3Interpreter extends WasmStack {
ANY_CONVERT_EXTERN, EXTERN_CONVERT_ANY => {
// nop
}
FUNC_NEW => {
codeptr.read1(); // skip reserved flags
var mem_index = codeptr.read_uleb31();
var sig_index = codeptr.read_uleb31();
var env_index = codeptr.read_uleb31();
Runtime.FUNC_NEW(this, frame.func.instance, mem_index, sig_index, env_index);
}
I32_TRUNC_SAT_F32_S => pushi(i32.truncf(popf()));
I32_TRUNC_SAT_F32_U => pushu(u32.truncf(popf()));
I32_TRUNC_SAT_F64_S => pushi(i32.truncd(popd()));
Expand Down
16 changes: 16 additions & 0 deletions src/engine/x86-64/X86_64Interpreter.v3
Original file line number Diff line number Diff line change
Expand Up @@ -3005,6 +3005,22 @@ class X86_64InterpreterGen(ic: X86_64InterpreterCode, w: DataWriter) {
restoreCallerIVars();
endHandler();
}

// ext:jit-interface: {func.new} has a reserved zero byte and 3 LEBs, and can trap.
// TODO: r_tmp3 is clobbered by reading an LEB
bindHandler(Opcode.FUNC_NEW); {
computePcFromCurIp();
asm.q.inc_r(r_ip); // skip reserved flags byte
genReadUleb32(r_tmp0); // memory index
genReadUleb32(r_tmp1); // signature index
genReadUleb32(r_tmp4); // environment index
masm.emit_get_curstack(xenv.tmp3);
saveCallerIVars();
callRuntime(refRuntimeCall(RT.FUNC_NEW),
[r_tmp3, r_instance, r_tmp0, r_tmp1, r_tmp4], true);
restoreCallerIVars();
endHandler();
}
}

// helper method to move values from vsp to registers
Expand Down
6 changes: 6 additions & 0 deletions src/engine/x86-64/X86_64Target.v3
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,12 @@ class X86_64SpcStrategy extends X86_64ExecutionStrategy {
if (!applyJitFilter(wf.instance.module, wf.decl, how)) return SpcResultForStub(wf, X86_64Spc.setInterpreterFallback(wf.decl), null);

var module = wf.instance.module;
// A module that was never prepared for this target has no code region yet. This
// happens for the inner module of an environment, whose functions are created at
// runtime by {func.new}; allocate a region for it on demand.
if (module.target_module.spc_code == null) {
allocateCodeForModule(module, MINIMUM_CODE_SIZE + X86_64Spc.estimateCodeSizeFor(wf.decl));
}
var code = module.target_module.spc_code;
var compiler = newCompiler(module.filename); // XXX: cache per-thread
var masm = X86_64MacroAssembler.!(compiler.masm), w = masm.asm.w;
Expand Down
Loading
Loading