failures: 0,
bytesGenerated: 0,
nullChecksEliminated: 0,
+ nullChecksFused: 0,
backBranchesEmitted: 0,
backBranchesNotEmitted: 0,
simdFallback: simdFallbackCounters,
return bytes;
}
+export function isZeroPageReserved(): boolean {
+ // FIXME: This check will always return true on worker threads.
+ // Right now the jiterpreter is disabled when threading is active, so that's not an issue.
+ if (!cwraps.mono_wasm_is_zero_page_reserved())
+ return false;
+
+ // Determine whether emscripten's stack checker or some other troublemaker has
+ // written junk at the start of memory. The previous cwraps call will have
+ // checked whether the stack starts at zero or not (on the main thread).
+ // We can't do this in the C helper because emcc/asan might be checking pointers.
+ return (Module.HEAPU32[0] === 0) &&
+ (Module.HEAPU32[1] === 0) &&
+ (Module.HEAPU32[2] === 0) &&
+ (Module.HEAPU32[3] === 0);
+}
+
export type JiterpreterOptions = {
enableAll?: boolean;
enableTraces: boolean;
enableCallResume: boolean;
enableWasmEh: boolean;
enableSimd: boolean;
+ zeroPageOptimization: boolean;
// For locations where the jiterpreter heuristic says we will be unable to generate
// a trace, insert an entry point opcode anyway. This enables collecting accurate
// stats for options like estimateHeat, but raises overhead.
"enableCallResume": "jiterpreter-call-resume-enabled",
"enableWasmEh": "jiterpreter-wasm-eh-enabled",
"enableSimd": "jiterpreter-simd-enabled",
+ "zeroPageOptimization": "jiterpreter-zero-page-optimization",
"enableStats": "jiterpreter-stats-enabled",
"disableHeuristic": "jiterpreter-disable-heuristic",
"estimateHeat": "jiterpreter-estimate-heat",
append_memmove_dest_src, try_append_memset_fast,
try_append_memmove_fast, counters, getOpcodeTableValue,
getMemberOffset, JiterpMember, BailoutReason,
+ isZeroPageReserved
} from "./jiterpreter-support";
import { compileSimdFeatureDetect } from "./jiterpreter-feature-detect";
import {
append_ldloc(builder, getArgU16(ip, 3), WasmOpcode.i32_load);
// stash it, we'll be using it multiple times
builder.local("math_lhs32", WasmOpcode.tee_local);
+
+ /*
+ const constantIndex = get_known_constant_value(getArgU16(ip, 3));
+ if (typeof (constantIndex) === "number")
+ console.log(`getchr in ${builder.functions[0].name} with constant index ${constantIndex}`);
+ */
+
// str
- append_ldloc_cknull(builder, getArgU16(ip, 2), ip, true);
+ const ptrLocal = builder.options.zeroPageOptimization ? "math_rhs32" : "cknull_ptr";
+ if (builder.options.zeroPageOptimization && isZeroPageReserved()) {
+ // load string ptr and stash it
+ // if the string ptr is null, the length check will fail and we will bail out,
+ // so the null check is not necessary
+ counters.nullChecksFused++;
+ append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load);
+ builder.local(ptrLocal, WasmOpcode.tee_local);
+ } else
+ append_ldloc_cknull(builder, getArgU16(ip, 2), ip, true);
+
// get string length
builder.appendU8(WasmOpcode.i32_load);
builder.appendMemarg(getMemberOffset(JiterpMember.StringLength), 2);
builder.local("math_lhs32");
builder.i32_const(2);
builder.appendU8(WasmOpcode.i32_mul);
- builder.local("cknull_ptr");
+ builder.local(ptrLocal);
builder.appendU8(WasmOpcode.i32_add);
// Load char
builder.appendU8(WasmOpcode.i32_load16_u);
append_ldloca(builder, localOffset, sizeBytes);
// src
builder.local("cknull_ptr");
- builder.i32_const(fieldOffset);
- builder.appendU8(WasmOpcode.i32_add);
+ if (fieldOffset !== 0) {
+ builder.i32_const(fieldOffset);
+ builder.appendU8(WasmOpcode.i32_add);
+ }
append_memmove_dest_src(builder, sizeBytes);
return true;
}
const klass = get_imethod_data(frame, getArgU16(ip, 4));
// dest = (char*)o + ip [3]
builder.local("cknull_ptr");
- builder.i32_const(fieldOffset);
- builder.appendU8(WasmOpcode.i32_add);
+ if (fieldOffset !== 0) {
+ builder.i32_const(fieldOffset);
+ builder.appendU8(WasmOpcode.i32_add);
+ }
// src = locals + ip [2]
append_ldloca(builder, localOffset, 0);
builder.ptr_const(klass);
const sizeBytes = getArgU16(ip, 4);
// dest
builder.local("cknull_ptr");
- builder.i32_const(fieldOffset);
- builder.appendU8(WasmOpcode.i32_add);
+ if (fieldOffset !== 0) {
+ builder.i32_const(fieldOffset);
+ builder.appendU8(WasmOpcode.i32_add);
+ }
// src
append_ldloca(builder, localOffset, 0);
append_memmove_dest_src(builder, sizeBytes);
builder.local("pLocals");
// cknull_ptr isn't always initialized here
append_ldloc(builder, objectOffset, WasmOpcode.i32_load);
- builder.i32_const(fieldOffset);
- builder.appendU8(WasmOpcode.i32_add);
+ if (fieldOffset !== 0) {
+ builder.i32_const(fieldOffset);
+ builder.appendU8(WasmOpcode.i32_add);
+ }
append_stloc_tail(builder, localOffset, setter);
return true;
) {
builder.block();
+ /*
+ const constantIndex = get_known_constant_value(indexOffset);
+ if (typeof (constantIndex) === "number")
+ console.log(`getelema1 in ${builder.functions[0].name} with constant index ${constantIndex}`);
+ */
+
// load index for check
append_ldloc(builder, indexOffset, WasmOpcode.i32_load);
// stash it since we need it twice
builder.local("math_lhs32", WasmOpcode.tee_local);
- // array null check
- append_ldloc_cknull(builder, objectOffset, ip, true);
+
+ const ptrLocal = builder.options.zeroPageOptimization ? "math_rhs32" : "cknull_ptr";
+ if (builder.options.zeroPageOptimization && isZeroPageReserved()) {
+ // load array ptr and stash it
+ // if the array ptr is null, the length check will fail and we will bail out
+ counters.nullChecksFused++;
+ append_ldloc(builder, objectOffset, WasmOpcode.i32_load);
+ builder.local(ptrLocal, WasmOpcode.tee_local);
+ } else
+ // array null check
+ append_ldloc_cknull(builder, objectOffset, ip, true);
+
// load array length
builder.appendU8(WasmOpcode.i32_load);
builder.appendMemarg(getMemberOffset(JiterpMember.ArrayLength), 2);
// check index < array.length, unsigned. if index is negative it will be interpreted as
// a massive value which is naturally going to be bigger than array.length. interp.c
// exploits this property so we can too
+ // for a null array pointer array.length will also be zero thanks to the zero page optimization
builder.appendU8(WasmOpcode.i32_lt_u);
// bailout unless (index < array.length)
builder.appendU8(WasmOpcode.br_if);
builder.endBlock();
// We did a null check and bounds check so we can now compute the actual address
- builder.local("cknull_ptr");
+ builder.local(ptrLocal);
builder.i32_const(getMemberOffset(JiterpMember.ArrayData));
builder.appendU8(WasmOpcode.i32_add);
case MintOpcode.MINT_LDLEN: {
builder.local("pLocals");
// array null check
+ // note: zero page optimization is not valid here since we want to throw on null
append_ldloc_cknull(builder, objectOffset, ip, true);
// load array length
builder.appendU8(WasmOpcode.i32_load);
import cwraps from "./cwraps";
import {
MintOpcodePtr, WasmValtype, WasmBuilder, addWasmFunctionPointer,
- _now, elapsedTimes,
+ _now, elapsedTimes, isZeroPageReserved,
counters, getRawCwrap, importDef,
JiterpreterOptions, getOptions, recordFailure,
JiterpMember, getMemberOffset,
if (!mostRecentOptions.enableStats && (b !== undefined))
return;
- mono_log_info(`// jitted ${counters.bytesGenerated} bytes; ${counters.tracesCompiled} traces (${counters.traceCandidates} candidates, ${(counters.tracesCompiled / counters.traceCandidates * 100).toFixed(1)}%); ${counters.jitCallsCompiled} jit_calls (${(counters.directJitCallsCompiled / counters.jitCallsCompiled * 100).toFixed(1)}% direct); ${counters.entryWrappersCompiled} interp_entries`);
- const backBranchHitRate = (counters.backBranchesEmitted / (counters.backBranchesEmitted + counters.backBranchesNotEmitted)) * 100;
- const tracesRejected = cwraps.mono_jiterp_get_rejected_trace_count();
- mono_log_info(`// time: ${elapsedTimes.generation | 0}ms generating, ${elapsedTimes.compilation | 0}ms compiling wasm. ${counters.nullChecksEliminated} cknulls removed. ${counters.backBranchesEmitted} back-branches (${counters.backBranchesNotEmitted} failed, ${backBranchHitRate.toFixed(1)}%), ${tracesRejected} traces rejected`);
+ const backBranchHitRate = (counters.backBranchesEmitted / (counters.backBranchesEmitted + counters.backBranchesNotEmitted)) * 100,
+ tracesRejected = cwraps.mono_jiterp_get_rejected_trace_count(),
+ nullChecksEliminatedText = mostRecentOptions.eliminateNullChecks ? counters.nullChecksEliminated.toString() : "off",
+ nullChecksFusedText = (mostRecentOptions.zeroPageOptimization ? counters.nullChecksFused.toString() + (isZeroPageReserved() ? "" : " (disabled)") : "off"),
+ backBranchesEmittedText = mostRecentOptions.enableBackwardBranches ? `emitted: ${counters.backBranchesEmitted}, failed: ${counters.backBranchesNotEmitted} (${backBranchHitRate.toFixed(1)}%)` : ": off",
+ directJitCallsText = counters.jitCallsCompiled ? (
+ mostRecentOptions.directJitCalls ? `direct jit calls: ${counters.directJitCallsCompiled} (${(counters.directJitCallsCompiled / counters.jitCallsCompiled * 100).toFixed(1)}%)` : "direct jit calls: off"
+ ) : "";
+
+ mono_log_info(`// jitted ${counters.bytesGenerated} bytes; ${counters.tracesCompiled} traces (${(counters.tracesCompiled / counters.traceCandidates * 100).toFixed(1)}%) (${tracesRejected} rejected); ${counters.jitCallsCompiled} jit_calls; ${counters.entryWrappersCompiled} interp_entries`);
+ mono_log_info(`// cknulls eliminated: ${nullChecksEliminatedText}, fused: ${nullChecksFusedText}; back-branches ${backBranchesEmittedText}; ${directJitCallsText}`);
+ mono_log_info(`// time: ${elapsedTimes.generation | 0}ms generating, ${elapsedTimes.compilation | 0}ms compiling wasm.`);
if (concise)
return;