in src/mono/browser/runtime/jiterpreter-trace-generator.ts [239:1746]
export function generateWasmBody (
frame: NativePointer, traceName: string, ip: MintOpcodePtr,
startOfBody: MintOpcodePtr, endOfBody: MintOpcodePtr,
builder: WasmBuilder, instrumentedTraceId: number,
backwardBranchTable: Uint16Array | null
): number {
const abort = <MintOpcodePtr><any>0;
let isFirstInstruction = true, isConditionallyExecuted = false,
containsSimd = false,
pruneOpcodes = false, hasEmittedUnreachable = false;
let result = 0,
prologueOpcodeCounter = 0,
conditionalOpcodeCounter = 0;
eraseInferredState();
// If a trace is instrumented, also activate back branch tracing
builder.backBranchTraceLevel = instrumentedTraceId
? 2
: defaultTraceBackBranches;
// Record the address of our prepare_jiterpreter opcode as the entry point, not the opcode after it.
// Some back-branches will target prepare_jiterpreter directly, and we need them to work.
let rip = builder.cfg.entry(ip);
while (ip) {
// This means some code went 'ip = abort; continue'
if (!ip)
break;
builder.cfg.ip = ip;
if (ip >= endOfBody) {
record_abort(builder.traceIndex, ip, traceName, "end-of-body");
if (instrumentedTraceId)
mono_log_info(`instrumented trace ${traceName} exited at end of body @${(<any>ip).toString(16)}`);
break;
}
// HACK: Browsers set a limit of 4KB, we lower it slightly since a single opcode
// might generate a ton of code and we generate a bit of an epilogue after
// we finish
const maxBytesGenerated = 3840,
spaceLeft = maxBytesGenerated - builder.bytesGeneratedSoFar - builder.cfg.overheadBytes;
if (builder.size >= spaceLeft) {
// mono_log_info(`trace too big, estimated size is ${builder.size + builder.bytesGeneratedSoFar}`);
record_abort(builder.traceIndex, ip, traceName, "trace-too-big");
if (instrumentedTraceId)
mono_log_info(`instrumented trace ${traceName} exited because of size limit at @${(<any>ip).toString(16)} (spaceLeft=${spaceLeft}b)`);
break;
}
if (instrumentedTraceId && traceEip) {
builder.i32_const(instrumentedTraceId);
builder.ip_const(ip);
builder.callImport("trace_eip");
}
let opcode = getU16(ip);
const numSregs = cwraps.mono_jiterp_get_opcode_info(opcode, OpcodeInfoType.Sregs),
numDregs = cwraps.mono_jiterp_get_opcode_info(opcode, OpcodeInfoType.Dregs),
opLengthU16 = cwraps.mono_jiterp_get_opcode_info(opcode, OpcodeInfoType.Length);
const isSimdIntrins = (opcode >= MintOpcode.MINT_SIMD_INTRINS_P_P) &&
(opcode <= MintOpcode.MINT_SIMD_INTRINS_P_PPP);
const simdIntrinsArgCount = isSimdIntrins
? opcode - MintOpcode.MINT_SIMD_INTRINS_P_P + 2
: 0;
const simdIntrinsIndex = isSimdIntrins
? getArgU16(ip, 1 + simdIntrinsArgCount)
: 0;
mono_assert((opcode >= 0) && (opcode < MintOpcode.MINT_LASTOP), () => `invalid opcode ${opcode}`);
const opname = isSimdIntrins
? SimdInfo[simdIntrinsArgCount][simdIntrinsIndex]
: getOpcodeName(opcode);
const _ip = ip;
const isBackBranchTarget = builder.options.noExitBackwardBranches &&
is_backward_branch_target(ip, startOfBody, backwardBranchTable),
isForwardBranchTarget = builder.branchTargets.has(ip),
startBranchBlock = isBackBranchTarget || isForwardBranchTarget ||
// If a method contains backward branches, we also need to check eip at the first insn
// because a backward branch might target a point in the middle of the trace
(isFirstInstruction && backwardBranchTable),
// We want to approximate the number of unconditionally executed instructions along with
// the ones that were probably conditionally executed by the time we reached the exit point
// We don't know the exact path that would have taken us to a given point, but it's a reasonable
// guess that methods dense with branches are more likely to take a complex path to reach
// a given exit
exitOpcodeCounter = conditionalOpcodeCounter + prologueOpcodeCounter +
builder.branchTargets.size;
let skipDregInvalidation = false,
opcodeValue = getOpcodeTableValue(opcode);
// We record the offset of each backward branch we encounter, so that later branch
// opcodes know that it's available by branching to the top of the dispatch loop
if (isBackBranchTarget) {
if (builder.backBranchTraceLevel > 1)
mono_log_info(`${traceName} recording back branch target 0x${(<any>ip).toString(16)}`);
builder.backBranchOffsets.push(ip);
}
if (startBranchBlock) {
// We've reached a branch target so we need to stop pruning opcodes, since
// we are no longer in a dead zone that execution can't reach
pruneOpcodes = false;
hasEmittedUnreachable = false;
// If execution runs past the end of the current branch block, ensure
// that the instruction pointer is updated appropriately. This will
// also guarantee that the branch target block's comparison will
// succeed so that execution continues.
// We make sure above that this isn't done for the start of the trace,
// otherwise loops will run forever and never terminate since after
// branching to the top of the loop we would blow away eip
append_branch_target_block(builder, ip, isBackBranchTarget);
isConditionallyExecuted = true;
eraseInferredState();
// Monitoring wants an opcode count that is a measurement of how many opcodes
// we definitely executed, so we want to ignore any opcodes that might
// have been skipped due to forward branching. This gives us an approximation
// of that by only counting how far we are from the most recent branch target
conditionalOpcodeCounter = 0;
}
// Handle the _OUTSIDE_BRANCH_BLOCK table entries
if ((opcodeValue < -1) && isConditionallyExecuted)
opcodeValue = (opcodeValue === -2) ? 2 : 0;
isFirstInstruction = false;
if (opcode === MintOpcode.MINT_SWITCH) {
// HACK: This opcode breaks all our table-based parsing and will cause the trace compiler to hang
// if it encounters a switch inside of a pruning region, so we need to let the normal code path
// run even if pruning is on
} else if (disabledOpcodes.indexOf(opcode) >= 0) {
append_bailout(builder, ip, BailoutReason.Debugging);
opcode = MintOpcode.MINT_NOP;
// Intentionally leave the correct info in place so we skip the right number of bytes
} else if (pruneOpcodes) {
opcode = MintOpcode.MINT_NOP;
}
switch (opcode) {
case MintOpcode.MINT_NOP: {
// This typically means the current opcode was disabled or pruned
if (pruneOpcodes) {
// We emit an unreachable opcode so that if execution somehow reaches a pruned opcode, we will abort
// This should be impossible anyway but it's also useful to have pruning visible in the wasm
// FIXME: Ideally we would stop generating opcodes after the first unreachable, but that causes v8 to hang
if (!hasEmittedUnreachable)
builder.appendU8(WasmOpcode.unreachable);
// Each unreachable opcode could generate a bunch of native code in a bad wasm jit so generate nops after it
hasEmittedUnreachable = true;
}
break;
}
case MintOpcode.MINT_INITLOCAL:
case MintOpcode.MINT_INITLOCALS: {
// FIXME: We should move the first entry point after initlocals if it exists
const startOffsetInBytes = getArgU16(ip, 1),
sizeInBytes = getArgU16(ip, 2);
append_memset_local(builder, startOffsetInBytes, 0, sizeInBytes);
break;
}
case MintOpcode.MINT_LOCALLOC: {
// dest
append_ldloca(builder, getArgU16(ip, 1));
// len
append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load);
// frame
builder.local("frame");
builder.callImport("localloc");
break;
}
case MintOpcode.MINT_ZEROBLK: {
// dest
append_ldloc(builder, getArgU16(ip, 1), WasmOpcode.i32_load);
// value
builder.i32_const(0);
// count
append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load);
// memset
builder.appendU8(WasmOpcode.PREFIX_sat);
builder.appendU8(11);
builder.appendU8(0);
break;
}
case MintOpcode.MINT_ZEROBLK_IMM: {
append_ldloc(builder, getArgU16(ip, 1), WasmOpcode.i32_load);
append_memset_dest(builder, 0, getArgU16(ip, 2));
break;
}
case MintOpcode.MINT_CPBLK: {
const sizeOffset = getArgU16(ip, 3),
srcOffset = getArgU16(ip, 2),
destOffset = getArgU16(ip, 1),
constantSize = get_known_constant_value(builder, sizeOffset);
if (constantSize !== 0) {
if (typeof (constantSize) !== "number") {
// size (FIXME: uint32 not int32)
append_ldloc(builder, sizeOffset, WasmOpcode.i32_load);
builder.local("count", WasmOpcode.tee_local);
// if size is 0 then don't do anything
builder.block(WasmValtype.void, WasmOpcode.if_); // if size
} else {
// Store the count into the local in case the unroll fails
builder.i32_const(constantSize);
builder.local("count", WasmOpcode.set_local);
}
// stash dest then check for null
append_ldloc(builder, destOffset, WasmOpcode.i32_load);
builder.local("dest_ptr", WasmOpcode.tee_local);
builder.appendU8(WasmOpcode.i32_eqz);
// stash src then check for null
append_ldloc(builder, srcOffset, WasmOpcode.i32_load);
builder.local("src_ptr", WasmOpcode.tee_local);
builder.appendU8(WasmOpcode.i32_eqz);
// now we memmove if both dest and src are valid. The stack currently has
// the eqz result for each pointer so we can stash a bailout inside of an if
builder.appendU8(WasmOpcode.i32_or);
builder.block(WasmValtype.void, WasmOpcode.if_); // if null
append_bailout(builder, ip, BailoutReason.NullCheck);
builder.endBlock(); // if null
if (
(typeof (constantSize) !== "number") ||
!try_append_memmove_fast(builder, 0, 0, constantSize, false, "dest_ptr", "src_ptr")
) {
// We passed the null check so now prepare the stack
builder.local("dest_ptr");
builder.local("src_ptr");
builder.local("count");
// wasm memmove with stack layout dest, src, count
builder.appendU8(WasmOpcode.PREFIX_sat);
builder.appendU8(10);
builder.appendU8(0);
builder.appendU8(0);
}
if (typeof (constantSize) !== "number")
builder.endBlock(); // if size
}
break;
}
case MintOpcode.MINT_INITBLK: {
const sizeOffset = getArgU16(ip, 3),
valueOffset = getArgU16(ip, 2),
destOffset = getArgU16(ip, 1);
// TODO: Handle constant size initblks. Not sure if they matter though
// FIXME: This will cause an erroneous bailout if dest and size are both 0
// but that really shouldn't ever happen, and it will only cause a slowdown
// dest
append_ldloc_cknull(builder, destOffset, ip, true);
// value
append_ldloc(builder, valueOffset, WasmOpcode.i32_load);
// size (FIXME: uint32 not int32)
append_ldloc(builder, sizeOffset, WasmOpcode.i32_load);
// spec: pop n, pop val, pop d, fill from d[0] to d[n] with value val
builder.appendU8(WasmOpcode.PREFIX_sat);
builder.appendU8(11);
builder.appendU8(0);
break;
}
// Other conditional branch types are handled by the relop table.
case MintOpcode.MINT_BRFALSE_I4_S:
case MintOpcode.MINT_BRTRUE_I4_S:
case MintOpcode.MINT_BRFALSE_I4_SP:
case MintOpcode.MINT_BRTRUE_I4_SP:
case MintOpcode.MINT_BRFALSE_I8_S:
case MintOpcode.MINT_BRTRUE_I8_S:
if (!emit_branch(builder, ip, frame, opcode))
ip = abort;
else
isConditionallyExecuted = true;
break;
case MintOpcode.MINT_BR_S:
case MintOpcode.MINT_CALL_HANDLER:
case MintOpcode.MINT_CALL_HANDLER_S:
if (!emit_branch(builder, ip, frame, opcode))
ip = abort;
else {
// Technically incorrect, but the instructions following this one may not be executed
// since we might have skipped over them.
// FIXME: Identify when we should actually set the conditionally executed flag, perhaps
// by doing a simple static flow analysis based on the displacements. Update heuristic too!
isConditionallyExecuted = true;
}
break;
case MintOpcode.MINT_CKNULL: {
// if (locals[ip[2]]) locals[ip[1]] = locals[ip[2]] else throw
const src = getArgU16(ip, 2),
dest = getArgU16(ip, 1);
// locals[n] = cknull(locals[n]) is a common pattern, and we don't
// need to do the write for it since it can't change the value
if (src !== dest) {
builder.local("pLocals");
append_ldloc_cknull(builder, src, ip, true);
append_stloc_tail(builder, dest, WasmOpcode.i32_store);
} else {
append_ldloc_cknull(builder, src, ip, false);
}
// We will have bailed out if the object was null
if (builder.allowNullCheckOptimization) {
if (traceNullCheckOptimizations)
mono_log_info(`(0x${(<any>ip).toString(16)}) locals[${dest}] passed cknull`);
notNullSince.set(dest, <any>ip);
}
skipDregInvalidation = true;
break;
}
case MintOpcode.MINT_TIER_ENTER_METHOD:
case MintOpcode.MINT_TIER_PATCHPOINT: {
// We need to make sure to notify the interpreter about tiering opcodes
// so that tiering up will still happen
const iMethod = getU32_unaligned(<any>frame + getMemberOffset(JiterpMember.Imethod));
builder.ptr_const(iMethod);
// increase_entry_count will return 1 if we can continue, otherwise
// we need to bail out into the interpreter so it can perform tiering
builder.callImport("entry");
builder.block(WasmValtype.void, WasmOpcode.if_);
append_bailout(builder, ip, BailoutReason.InterpreterTiering);
builder.endBlock();
break;
}
case MintOpcode.MINT_TIER_ENTER_JITERPRETER:
opcodeValue = 0;
break;
case MintOpcode.MINT_SAFEPOINT:
append_safepoint(builder, ip);
break;
case MintOpcode.MINT_LDLOCA_S: {
// Pre-load locals for the store op
builder.local("pLocals");
// locals[ip[1]] = &locals[ip[2]]
const offset = getArgU16(ip, 2),
flag = isAddressTaken(builder, offset),
destOffset = getArgU16(ip, 1);
if (!flag)
mono_log_error(`${traceName}: Expected local ${offset} to have address taken flag`);
append_ldloca(builder, offset);
append_stloc_tail(builder, destOffset, WasmOpcode.i32_store);
// Record this ldloca as a known constant so that later uses of it turn into a lea,
// and the wasm runtime can constant fold them with other constants. It's not uncommon
// to have code that does '&x + c', which (if this optimization works) should
// turn into '&locals + offsetof(x) + c' and get constant folded to have the same cost
// as a regular ldloc
knownConstants.set(destOffset, { type: "ldloca", offset: offset });
// dreg invalidation would blow the known constant away, so disable it
skipDregInvalidation = true;
break;
}
case MintOpcode.MINT_LDSTR:
case MintOpcode.MINT_LDFTN:
case MintOpcode.MINT_LDFTN_ADDR:
case MintOpcode.MINT_LDPTR: {
// Pre-load locals for the store op
builder.local("pLocals");
// frame->imethod->data_items [ip [2]]
let data = get_imethod_data(frame, getArgU16(ip, 2));
if (opcode === MintOpcode.MINT_LDFTN)
data = <any>cwraps.mono_jiterp_imethod_to_ftnptr(<any>data);
builder.ptr_const(data);
append_stloc_tail(builder, getArgU16(ip, 1), WasmOpcode.i32_store);
break;
}
case MintOpcode.MINT_CPOBJ_VT: {
const klass = get_imethod_data(frame, getArgU16(ip, 3));
append_ldloc(builder, getArgU16(ip, 1), WasmOpcode.i32_load);
append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load);
builder.ptr_const(klass);
builder.callImport("value_copy");
break;
}
case MintOpcode.MINT_CPOBJ_VT_NOREF: {
const sizeBytes = getArgU16(ip, 3);
append_ldloc(builder, getArgU16(ip, 1), WasmOpcode.i32_load);
append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load);
append_memmove_dest_src(builder, sizeBytes);
break;
}
case MintOpcode.MINT_LDOBJ_VT: {
const size = getArgU16(ip, 3);
append_ldloca(builder, getArgU16(ip, 1), size);
append_ldloc_cknull(builder, getArgU16(ip, 2), ip, true);
append_memmove_dest_src(builder, size);
break;
}
case MintOpcode.MINT_STOBJ_VT: {
const klass = get_imethod_data(frame, getArgU16(ip, 3));
append_ldloc(builder, getArgU16(ip, 1), WasmOpcode.i32_load);
append_ldloca(builder, getArgU16(ip, 2), 0);
builder.ptr_const(klass);
builder.callImport("value_copy");
break;
}
case MintOpcode.MINT_STOBJ_VT_NOREF: {
const sizeBytes = getArgU16(ip, 3);
append_ldloc(builder, getArgU16(ip, 1), WasmOpcode.i32_load);
append_ldloca(builder, getArgU16(ip, 2), 0);
append_memmove_dest_src(builder, sizeBytes);
break;
}
case MintOpcode.MINT_STRLEN: {
builder.local("pLocals");
append_ldloc_cknull(builder, getArgU16(ip, 2), ip, true);
builder.appendU8(WasmOpcode.i32_load);
builder.appendMemarg(getMemberOffset(JiterpMember.StringLength), 2);
append_stloc_tail(builder, getArgU16(ip, 1), WasmOpcode.i32_store);
break;
}
case MintOpcode.MINT_GETCHR: {
builder.block();
// index
append_ldloc(builder, getArgU16(ip, 3), WasmOpcode.i32_load);
// stash it, we'll be using it multiple times
builder.local("index", 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
let ptrLocal = "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
modifyCounter(JiterpCounter.NullChecksFused, 1);
append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load);
ptrLocal = "src_ptr";
builder.local(ptrLocal, WasmOpcode.tee_local);
} else
append_ldloc_cknull(builder, getArgU16(ip, 2), ip, true);
// current stack layout is [index, ptr]
// get string length
builder.appendU8(WasmOpcode.i32_load);
builder.appendMemarg(getMemberOffset(JiterpMember.StringLength), 2);
// current stack layout is [index, length]
// index < length
builder.appendU8(WasmOpcode.i32_lt_s);
// index >= 0
builder.local("index");
builder.i32_const(0);
builder.appendU8(WasmOpcode.i32_ge_s);
// (index >= 0) && (index < length)
builder.appendU8(WasmOpcode.i32_and);
// If either of the index checks failed we will fall through to the bailout
builder.appendU8(WasmOpcode.br_if);
builder.appendULeb(0);
append_bailout(builder, ip, BailoutReason.StringOperationFailed);
builder.endBlock();
// The null check and range check both passed so we can load the character now
// Pre-load destination for the stloc at the end (we can't do this inside the block above)
builder.local("pLocals");
// (index * 2) + offsetof(MonoString, chars) + pString
builder.local("index");
builder.i32_const(2);
builder.appendU8(WasmOpcode.i32_mul);
builder.local(ptrLocal);
builder.appendU8(WasmOpcode.i32_add);
// Load char
builder.appendU8(WasmOpcode.i32_load16_u);
builder.appendMemarg(getMemberOffset(JiterpMember.StringData), 1);
// Store into result
append_stloc_tail(builder, getArgU16(ip, 1), WasmOpcode.i32_store);
break;
}
case MintOpcode.MINT_GETITEM_SPAN:
case MintOpcode.MINT_GETITEM_LOCALSPAN: {
const elementSize = getArgI16(ip, 4);
builder.block();
// Load index and stash it
append_ldloc(builder, getArgU16(ip, 3), WasmOpcode.i32_load);
builder.local("index", WasmOpcode.tee_local);
// Load address of the span structure
let ptrLocal = "cknull_ptr";
if (opcode === MintOpcode.MINT_GETITEM_SPAN) {
// span = *(MonoSpanOfVoid *)locals[2]
append_ldloc_cknull(builder, getArgU16(ip, 2), ip, true);
} else {
// span = (MonoSpanOfVoid)locals[2]
append_ldloca(builder, getArgU16(ip, 2), 0);
ptrLocal = "src_ptr";
builder.local(ptrLocal, WasmOpcode.tee_local);
}
// length = span->length
builder.appendU8(WasmOpcode.i32_load);
builder.appendMemarg(getMemberOffset(JiterpMember.SpanLength), 2);
// index < length
builder.appendU8(WasmOpcode.i32_lt_u);
// index >= 0
// FIXME: It would be nice to optimize this down to a single (index < length) comparison
// but interp.c doesn't do it - presumably because a span could be bigger than 2gb?
builder.local("index");
builder.i32_const(0);
builder.appendU8(WasmOpcode.i32_ge_s);
// (index >= 0) && (index < length)
builder.appendU8(WasmOpcode.i32_and);
builder.appendU8(WasmOpcode.br_if);
builder.appendULeb(0);
append_bailout(builder, ip, BailoutReason.SpanOperationFailed);
builder.endBlock();
// We successfully null checked and bounds checked. Now compute
// the address and store it to the destination
builder.local("pLocals");
// src = span->_reference + (index * element_size);
builder.local(ptrLocal);
builder.appendU8(WasmOpcode.i32_load);
builder.appendMemarg(getMemberOffset(JiterpMember.SpanData), 2);
builder.local("index");
builder.i32_const(elementSize);
builder.appendU8(WasmOpcode.i32_mul);
builder.appendU8(WasmOpcode.i32_add);
append_stloc_tail(builder, getArgU16(ip, 1), WasmOpcode.i32_store);
break;
}
case MintOpcode.MINT_INTRINS_SPAN_CTOR: {
// if (len < 0) bailout
builder.block();
// int len = LOCAL_VAR (ip [3], gint32);
append_ldloc(builder, getArgU16(ip, 3), WasmOpcode.i32_load);
builder.local("count", WasmOpcode.tee_local);
builder.i32_const(0);
builder.appendU8(WasmOpcode.i32_ge_s);
builder.appendU8(WasmOpcode.br_if);
builder.appendULeb(0);
append_bailout(builder, ip, BailoutReason.SpanOperationFailed);
builder.endBlock();
// gpointer span = locals + ip [1];
append_ldloca(builder, getArgU16(ip, 1), 16);
builder.local("dest_ptr", WasmOpcode.tee_local);
// *(gpointer*)span = ptr;
append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load);
builder.appendU8(WasmOpcode.i32_store);
builder.appendMemarg(0, 0);
// *(gint32*)((gpointer*)span + 1) = len;
builder.local("dest_ptr");
builder.local("count");
builder.appendU8(WasmOpcode.i32_store);
builder.appendMemarg(4, 0);
break;
}
case MintOpcode.MINT_LD_DELEGATE_METHOD_PTR: {
// FIXME: ldloca invalidation size
append_ldloca(builder, getArgU16(ip, 1), 8);
append_ldloca(builder, getArgU16(ip, 2), 8);
builder.callImport("ld_del_ptr");
break;
}
case MintOpcode.MINT_LDTSFLDA: {
append_ldloca(builder, getArgU16(ip, 1), 4);
// This value is unsigned but I32 is probably right
builder.ptr_const(getArgI32(ip, 2));
builder.callImport("ldtsflda");
break;
}
case MintOpcode.MINT_INTRINS_GET_TYPE:
builder.block();
// dest, src
append_ldloca(builder, getArgU16(ip, 1), 4);
append_ldloca(builder, getArgU16(ip, 2), 0);
builder.callImport("gettype");
// bailout if gettype failed
builder.appendU8(WasmOpcode.br_if);
builder.appendULeb(0);
append_bailout(builder, ip, BailoutReason.NullCheck);
builder.endBlock();
break;
case MintOpcode.MINT_INTRINS_ENUM_HASFLAG: {
const klass = get_imethod_data(frame, getArgU16(ip, 4));
builder.ptr_const(klass);
append_ldloca(builder, getArgU16(ip, 1), 4);
append_ldloca(builder, getArgU16(ip, 2), 0);
append_ldloca(builder, getArgU16(ip, 3), 0);
builder.callImport("hasflag");
break;
}
case MintOpcode.MINT_INTRINS_MEMORYMARSHAL_GETARRAYDATAREF: {
const offset = getMemberOffset(JiterpMember.ArrayData);
builder.local("pLocals");
append_ldloc_cknull(builder, getArgU16(ip, 2), ip, true);
builder.i32_const(offset);
builder.appendU8(WasmOpcode.i32_add);
append_stloc_tail(builder, getArgU16(ip, 1), WasmOpcode.i32_store);
break;
}
case MintOpcode.MINT_INTRINS_GET_HASHCODE:
builder.local("pLocals");
append_ldloca(builder, getArgU16(ip, 2), 0);
builder.callImport("hashcode");
append_stloc_tail(builder, getArgU16(ip, 1), WasmOpcode.i32_store);
break;
case MintOpcode.MINT_INTRINS_TRY_GET_HASHCODE:
builder.local("pLocals");
append_ldloca(builder, getArgU16(ip, 2), 0);
builder.callImport("try_hash");
append_stloc_tail(builder, getArgU16(ip, 1), WasmOpcode.i32_store);
break;
case MintOpcode.MINT_INTRINS_RUNTIMEHELPERS_OBJECT_HAS_COMPONENT_SIZE:
builder.local("pLocals");
append_ldloca(builder, getArgU16(ip, 2), 0);
builder.callImport("hascsize");
append_stloc_tail(builder, getArgU16(ip, 1), WasmOpcode.i32_store);
break;
case MintOpcode.MINT_INTRINS_ORDINAL_IGNORE_CASE_ASCII: {
builder.local("pLocals");
// valueA (cache in lhs32, we need it again later)
append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load);
builder.local("math_lhs32", WasmOpcode.tee_local);
// valueB
append_ldloc(builder, getArgU16(ip, 3), WasmOpcode.i32_load);
// compute differentBits = (valueA ^ valueB) << 2
builder.appendU8(WasmOpcode.i32_xor);
builder.i32_const(2);
builder.appendU8(WasmOpcode.i32_shl);
builder.local("math_rhs32", WasmOpcode.set_local);
// compute indicator
builder.local("math_lhs32");
builder.i32_const(0x00050005);
builder.appendU8(WasmOpcode.i32_add);
builder.i32_const(0x00A000A0);
builder.appendU8(WasmOpcode.i32_or);
builder.i32_const(0x001A001A);
builder.appendU8(WasmOpcode.i32_add);
builder.i32_const(-8388737); // 0xFF7FFF7F == 4286578559U == -8388737
builder.appendU8(WasmOpcode.i32_or);
// result = (differentBits & indicator) == 0
builder.local("math_rhs32");
builder.appendU8(WasmOpcode.i32_and);
builder.appendU8(WasmOpcode.i32_eqz);
append_stloc_tail(builder, getArgU16(ip, 1), WasmOpcode.i32_store);
break;
}
case MintOpcode.MINT_ARRAY_RANK:
case MintOpcode.MINT_ARRAY_ELEMENT_SIZE: {
builder.block();
// dest, src
append_ldloca(builder, getArgU16(ip, 1), 4);
append_ldloca(builder, getArgU16(ip, 2), 0);
builder.callImport(opcode === MintOpcode.MINT_ARRAY_RANK ? "array_rank" : "a_elesize");
// If the array was null we will bail out, otherwise continue
builder.appendU8(WasmOpcode.br_if);
builder.appendULeb(0);
append_bailout(builder, ip, BailoutReason.NullCheck);
builder.endBlock();
break;
}
case MintOpcode.MINT_CASTCLASS_INTERFACE:
case MintOpcode.MINT_ISINST_INTERFACE: {
const klass = get_imethod_data(frame, getArgU16(ip, 3)),
isSpecialInterface = cwraps.mono_jiterp_is_special_interface(klass),
bailoutOnFailure = (opcode === MintOpcode.MINT_CASTCLASS_INTERFACE),
destOffset = getArgU16(ip, 1);
if (!klass) {
record_abort(builder.traceIndex, ip, traceName, "null-klass");
ip = abort;
continue;
}
builder.block(); // depth x -> 0 (opcode block)
if (builder.options.zeroPageOptimization && isZeroPageReserved()) {
// Null check fusion is possible, so (obj->vtable) will be 0 for !obj
append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load);
builder.local("dest_ptr", WasmOpcode.tee_local);
modifyCounter(JiterpCounter.NullChecksFused, 1);
} else {
builder.block(); // depth 0 -> 1 (null check block)
// src
append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load);
builder.local("dest_ptr", WasmOpcode.tee_local);
// Null ptr check: If the ptr is non-null, skip this block
builder.appendU8(WasmOpcode.br_if);
builder.appendULeb(0);
builder.local("pLocals");
builder.i32_const(0);
append_stloc_tail(builder, destOffset, WasmOpcode.i32_store);
// at the end of this block (depth 0) we skip to the end of the opcode block (depth 1)
// because we successfully zeroed the destination register
builder.appendU8(WasmOpcode.br);
builder.appendULeb(1);
builder.endBlock(); // depth 1 -> 0 (end null check block)
// Put ptr back on the stack
builder.local("dest_ptr");
}
// the special interface version signature is (obj, vtable, klass), but
// the fast signature is (vtable, klass)
if (isSpecialInterface) {
// load a second copy of obj to build the helper arglist (obj, vtable, klass)
builder.local("dest_ptr");
}
builder.appendU8(WasmOpcode.i32_load); // obj->vtable
builder.appendMemarg(getMemberOffset(JiterpMember.VTable), 0); // fixme: alignment
builder.ptr_const(klass);
builder.callImport(isSpecialInterface ? "imp_iface_s" : "imp_iface");
if (bailoutOnFailure) {
// generate a 1 for null ptrs so we don't bail out and instead write the 0
// to the destination
builder.local("dest_ptr");
builder.appendU8(WasmOpcode.i32_eqz);
builder.appendU8(WasmOpcode.i32_or);
}
builder.block(WasmValtype.void, WasmOpcode.if_); // if cast succeeded
builder.local("pLocals");
builder.local("dest_ptr");
append_stloc_tail(builder, destOffset, WasmOpcode.i32_store);
builder.appendU8(WasmOpcode.else_); // else cast failed
if (bailoutOnFailure) {
// so bailout
append_bailout(builder, ip, BailoutReason.CastFailed);
} else {
// this is isinst, so write 0 to destination instead
builder.local("pLocals");
builder.i32_const(0);
append_stloc_tail(builder, destOffset, WasmOpcode.i32_store);
}
builder.endBlock(); // endif
builder.endBlock(); // depth 0 -> x (end opcode block)
break;
}
case MintOpcode.MINT_CASTCLASS_COMMON:
case MintOpcode.MINT_ISINST_COMMON:
case MintOpcode.MINT_CASTCLASS:
case MintOpcode.MINT_ISINST: {
const klass = get_imethod_data(frame, getArgU16(ip, 3)),
canDoFastCheck = (opcode === MintOpcode.MINT_CASTCLASS_COMMON) ||
(opcode === MintOpcode.MINT_ISINST_COMMON),
bailoutOnFailure = (opcode === MintOpcode.MINT_CASTCLASS) ||
(opcode === MintOpcode.MINT_CASTCLASS_COMMON),
destOffset = getArgU16(ip, 1);
if (!klass) {
record_abort(builder.traceIndex, ip, traceName, "null-klass");
ip = abort;
continue;
}
builder.block(); // depth x -> 0 (opcode block)
if (builder.options.zeroPageOptimization && isZeroPageReserved()) {
// Null check fusion is possible, so (obj->vtable)->klass will be 0 for !obj
append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load);
builder.local("dest_ptr", WasmOpcode.tee_local);
modifyCounter(JiterpCounter.NullChecksFused, 1);
} else {
builder.block(); // depth 0 -> 1 (null check block)
// src
append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load);
builder.local("dest_ptr", WasmOpcode.tee_local);
// Null ptr check: If the ptr is non-null, skip this block
builder.appendU8(WasmOpcode.br_if);
builder.appendULeb(0);
builder.local("pLocals");
builder.i32_const(0);
append_stloc_tail(builder, destOffset, WasmOpcode.i32_store);
// at the end of this block (depth 0) we skip to the end of the opcode block (depth 1)
// because we successfully zeroed the destination register
builder.appendU8(WasmOpcode.br);
builder.appendULeb(1);
builder.endBlock(); // depth 1 -> 0 (end null check block)
// Put ptr back on the stack
builder.local("dest_ptr");
}
// If we're here the null check passed and we now need to type-check
builder.appendU8(WasmOpcode.i32_load); // obj->vtable
builder.appendMemarg(getMemberOffset(JiterpMember.VTable), 0); // fixme: alignment
builder.appendU8(WasmOpcode.i32_load); // (obj->vtable)->klass
builder.appendMemarg(getMemberOffset(JiterpMember.VTableKlass), 0); // fixme: alignment
// Stash obj->vtable->klass so we can do a fast has_parent check later
if (canDoFastCheck)
builder.local("src_ptr", WasmOpcode.tee_local);
builder.i32_const(klass);
builder.appendU8(WasmOpcode.i32_eq);
builder.block(WasmValtype.void, WasmOpcode.if_); // if A
// Fast type-check passed (exact match), so store the ptr and continue
builder.local("pLocals");
builder.local("dest_ptr");
append_stloc_tail(builder, destOffset, WasmOpcode.i32_store);
// Fast type-check failed, so call the helper function
builder.appendU8(WasmOpcode.else_); // else A
if (canDoFastCheck) {
// Fast path for ISINST_COMMON/CASTCLASS_COMMON. We know klass is a simple type
// so all we need to do is a parentage check.
builder.local("src_ptr"); // obj->vtable->klass
builder.ptr_const(klass);
builder.callImport("hasparent");
if (bailoutOnFailure) {
// generate a 1 for null ptrs so we don't bail out and instead write the 0
// to the destination
builder.local("dest_ptr");
builder.appendU8(WasmOpcode.i32_eqz);
builder.appendU8(WasmOpcode.i32_or);
}
builder.block(WasmValtype.void, WasmOpcode.if_); // if B
// mono_class_has_parent_fast returned 1 so *destination = obj
builder.local("pLocals");
builder.local("dest_ptr");
append_stloc_tail(builder, destOffset, WasmOpcode.i32_store);
builder.appendU8(WasmOpcode.else_); // else B
// mono_class_has_parent_fast returned 0
if (bailoutOnFailure) {
// so bailout
append_bailout(builder, ip, BailoutReason.CastFailed);
} else {
// this is isinst, so write 0 to destination instead
builder.local("pLocals");
builder.i32_const(0);
append_stloc_tail(builder, destOffset, WasmOpcode.i32_store);
}
builder.endBlock(); // endif B
} else {
// Slow path for ISINST/CASTCLASS, handles things like generics and nullable.
// &dest
append_ldloca(builder, getArgU16(ip, 1), 4);
// src
builder.local("dest_ptr");
// klass
builder.ptr_const(klass);
// opcode
builder.i32_const(opcode);
builder.callImport("castv2");
// We don't need to do an explicit null check because mono_jiterp_cast_v2 does it
// Check whether the cast operation failed
builder.appendU8(WasmOpcode.i32_eqz);
builder.block(WasmValtype.void, WasmOpcode.if_); // if B
// Cast failed so bail out
append_bailout(builder, ip, BailoutReason.CastFailed);
builder.endBlock(); // endif B
}
builder.endBlock(); // endif A
builder.endBlock(); // depth 0 -> x (end opcode block)
break;
}
case MintOpcode.MINT_BOX:
case MintOpcode.MINT_BOX_VT: {
// MonoVTable *vtable = (MonoVTable*)frame->imethod->data_items [ip [3]];
builder.ptr_const(get_imethod_data(frame, getArgU16(ip, 3)));
// dest, src
append_ldloca(builder, getArgU16(ip, 1), 4);
append_ldloca(builder, getArgU16(ip, 2), 0);
builder.i32_const(opcode === MintOpcode.MINT_BOX_VT ? 1 : 0);
builder.callImport("box");
break;
}
case MintOpcode.MINT_UNBOX: {
const klass = get_imethod_data(frame, getArgU16(ip, 3)),
// The type check needs to examine the boxed value's rank and element class
elementClassOffset = getMemberOffset(JiterpMember.ClassElementClass),
destOffset = getArgU16(ip, 1),
// Get the class's element class, which is what we will actually type-check against
elementClass = getU32_unaligned(klass + elementClassOffset);
if (!klass || !elementClass) {
record_abort(builder.traceIndex, ip, traceName, "null-klass");
ip = abort;
continue;
}
if (builder.options.zeroPageOptimization && isZeroPageReserved()) {
// Null check fusion is possible, so (obj->vtable)->klass will be 0 for !obj
append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load);
builder.local("dest_ptr", WasmOpcode.tee_local);
modifyCounter(JiterpCounter.NullChecksFused, 1);
} else {
append_ldloc_cknull(builder, getArgU16(ip, 2), ip, true);
builder.local("dest_ptr", WasmOpcode.tee_local);
}
// Fetch the object's klass so we can perform a type check
builder.appendU8(WasmOpcode.i32_load); // obj->vtable
builder.appendMemarg(getMemberOffset(JiterpMember.VTable), 0); // fixme: alignment
builder.appendU8(WasmOpcode.i32_load); // (obj->vtable)->klass
builder.appendMemarg(getMemberOffset(JiterpMember.VTableKlass), 0); // fixme: alignment
// Stash obj->vtable->klass, then check klass->element_class == expected
builder.local("src_ptr", WasmOpcode.tee_local);
builder.appendU8(WasmOpcode.i32_load);
builder.appendMemarg(elementClassOffset, 0);
builder.i32_const(elementClass);
builder.appendU8(WasmOpcode.i32_eq);
// Check klass->rank == 0
builder.local("src_ptr");
builder.appendU8(WasmOpcode.i32_load8_u); // rank is a uint8
builder.appendMemarg(getMemberOffset(JiterpMember.ClassRank), 0);
builder.appendU8(WasmOpcode.i32_eqz);
// (element_class == expected) && (rank == 0)
builder.appendU8(WasmOpcode.i32_and);
builder.block(WasmValtype.void, WasmOpcode.if_); // if type check passed
// Type-check passed, so now compute the address of the object's data
// and store the address
builder.local("pLocals");
builder.local("dest_ptr");
builder.i32_const(getMemberOffset(JiterpMember.BoxedValueData));
builder.appendU8(WasmOpcode.i32_add);
append_stloc_tail(builder, destOffset, WasmOpcode.i32_store);
builder.appendU8(WasmOpcode.else_); // else type check failed
//
append_bailout(builder, ip, BailoutReason.UnboxFailed);
builder.endBlock(); // endif A
break;
}
case MintOpcode.MINT_NEWSTR: {
builder.block();
append_ldloca(builder, getArgU16(ip, 1), 4);
append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load);
builder.callImport("newstr");
// If the newstr operation succeeded, continue, otherwise bailout
// Note that this assumes the newstr operation will fail again when the interpreter does it
// (the only reason for a newstr to fail I can think of is an out-of-memory condition)
builder.appendU8(WasmOpcode.br_if);
builder.appendULeb(0);
append_bailout(builder, ip, BailoutReason.AllocFailed);
builder.endBlock();
break;
}
case MintOpcode.MINT_NEWOBJ_INLINED: {
builder.block();
// MonoObject *o = mono_gc_alloc_obj (vtable, m_class_get_instance_size (vtable->klass));
append_ldloca(builder, getArgU16(ip, 1), 4);
builder.ptr_const(get_imethod_data(frame, getArgU16(ip, 2)));
// LOCAL_VAR (ip [1], MonoObject*) = o;
builder.callImport("newobj_i");
// If the newobj operation succeeded, continue, otherwise bailout
builder.appendU8(WasmOpcode.br_if);
builder.appendULeb(0);
append_bailout(builder, ip, BailoutReason.AllocFailed);
builder.endBlock();
break;
}
case MintOpcode.MINT_NEWOBJ:
case MintOpcode.MINT_NEWOBJ_VT:
case MintOpcode.MINT_CALLVIRT_FAST:
case MintOpcode.MINT_CALL: {
if (countCallTargets) {
const targetImethod = get_imethod_data(frame, getArgU16(ip, 3));
const targetMethod = <MonoMethod><any>getU32_unaligned(targetImethod);
const count = callTargetCounts[<any>targetMethod];
if (typeof (count) === "number")
callTargetCounts[<any>targetMethod] = count + 1;
else
callTargetCounts[<any>targetMethod] = 1;
}
if (isConditionallyExecuted) {
// We generate a bailout instead of aborting, because we don't want calls
// to abort the entire trace if we have branch support enabled - the call
// might be infrequently hit and as a result it's worth it to keep going.
append_exit(builder, ip, exitOpcodeCounter, BailoutReason.Call);
pruneOpcodes = true;
opcodeValue = 0;
} else {
// We're in a block that executes unconditionally, and no branches have been
// executed before now so the trace will always need to bail out into the
// interpreter here. No point in compiling more.
ip = abort;
}
break;
}
// TODO: Verify that this isn't worse. I think these may only show up in wrappers?
// case MintOpcode.MINT_JIT_CALL:
case MintOpcode.MINT_CALLI:
case MintOpcode.MINT_CALLI_NAT:
case MintOpcode.MINT_CALLI_NAT_DYNAMIC:
case MintOpcode.MINT_CALLI_NAT_FAST:
case MintOpcode.MINT_CALL_DELEGATE:
// See comments for MINT_CALL
if (isConditionallyExecuted) {
append_exit(builder, ip, exitOpcodeCounter,
opcode == MintOpcode.MINT_CALL_DELEGATE
? BailoutReason.CallDelegate
: BailoutReason.Call
);
pruneOpcodes = true;
} else {
ip = abort;
}
break;
// Unlike regular rethrow which will only appear in catch blocks,
// MONO_RETHROW appears to show up in other places, so it's worth conditional bailout
case MintOpcode.MINT_MONO_RETHROW:
case MintOpcode.MINT_THROW:
// Not an exit, because throws are by definition unlikely
// We shouldn't make optimization decisions based on them.
append_bailout(builder, ip, BailoutReason.Throw);
pruneOpcodes = true;
break;
// These are generated in place of regular LEAVEs inside of the body of a catch clause.
// We can safely assume that during normal execution, catch clauses won't be running.
case MintOpcode.MINT_LEAVE_CHECK:
case MintOpcode.MINT_LEAVE_S_CHECK:
append_bailout(builder, ip, BailoutReason.LeaveCheck);
pruneOpcodes = true;
break;
case MintOpcode.MINT_ENDFINALLY: {
if (
(builder.callHandlerReturnAddresses.length > 0) &&
(builder.callHandlerReturnAddresses.length <= maxCallHandlerReturnAddresses)
) {
// mono_log_info(`endfinally @0x${(<any>ip).toString(16)}. return addresses:`, builder.callHandlerReturnAddresses.map(ra => (<any>ra).toString(16)));
// FIXME: Clean this codegen up
// Load ret_ip
const clauseIndex = getArgU16(ip, 1),
clauseDataOffset = get_imethod_clause_data_offset(frame, clauseIndex);
builder.local("pLocals");
builder.appendU8(WasmOpcode.i32_load);
builder.appendMemarg(clauseDataOffset, 0);
// Stash it in a variable because we're going to need to use it multiple times
builder.local("index", WasmOpcode.set_local);
// Do a bunch of trivial comparisons to see if ret_ip is one of our expected return addresses,
// and if it is, generate a branch back to the dispatcher at the top
for (let r = 0; r < builder.callHandlerReturnAddresses.length; r++) {
const ra = builder.callHandlerReturnAddresses[r];
builder.local("index");
builder.ptr_const(ra);
builder.appendU8(WasmOpcode.i32_eq);
builder.cfg.branch(ra, ra < ip, CfgBranchType.Conditional);
}
// If none of the comparisons succeeded we won't have branched anywhere, so bail out
// This shouldn't happen during non-exception-handling execution unless the trace doesn't
// contain the CALL_HANDLER that led here
append_bailout(builder, ip, BailoutReason.UnexpectedRetIp);
// FIXME: prune opcodes?
} else {
ip = abort;
}
break;
}
case MintOpcode.MINT_RETHROW:
case MintOpcode.MINT_PROF_EXIT:
case MintOpcode.MINT_PROF_EXIT_VOID:
ip = abort;
break;
// Generating code for these is kind of complex due to the intersection of JS and int64,
// and it would bloat the implementation so we handle them all in C instead and match
// the interp implementation. Most of these are rare in runtime tests or browser bench
case MintOpcode.MINT_CONV_OVF_I4_I8:
case MintOpcode.MINT_CONV_OVF_U4_I8:
case MintOpcode.MINT_CONV_OVF_I4_U8:
case MintOpcode.MINT_CONV_OVF_I4_R8:
case MintOpcode.MINT_CONV_OVF_I8_R8:
case MintOpcode.MINT_CONV_OVF_I4_R4:
case MintOpcode.MINT_CONV_OVF_I8_R4:
case MintOpcode.MINT_CONV_OVF_U4_I4:
builder.block();
// dest, src
append_ldloca(builder, getArgU16(ip, 1), 8);
append_ldloca(builder, getArgU16(ip, 2), 0);
builder.i32_const(opcode);
builder.callImport("conv");
// If the conversion succeeded, continue, otherwise bailout
builder.appendU8(WasmOpcode.br_if);
builder.appendULeb(0);
append_bailout(builder, ip, BailoutReason.Overflow); // could be underflow but awkward to tell
builder.endBlock();
break;
/*
* The native conversion opcodes for these are not specified for nan/inf, and v8
* chooses to throw, so we have to do some tricks to identify non-finite values
* and substitute INTnn_MIN, like clang would.
* This attempts to reproduce what clang does in -O3 with no special flags set:
*
* f64 -> i64
*
* block
* local.get 0
* f64.abs
* f64.const 0x1p63
* f64.lt
* i32.eqz
* br_if 0 # 0: down to label0
* local.get 0
* i64.trunc_f64_s
* return
* end_block # label0:
* i64.const -9223372036854775808
*
* f32 -> i32
*
* block
* local.get 0
* f32.abs
* f32.const 0x1p31
* f32.lt
* i32.eqz
* br_if 0 # 0: down to label3
* local.get 0
* i32.trunc_f32_s
* return
* end_block # label3:
* i32.const -2147483648
*/
case MintOpcode.MINT_CONV_I4_R4:
case MintOpcode.MINT_CONV_I4_R8:
case MintOpcode.MINT_CONV_I8_R4:
case MintOpcode.MINT_CONV_I8_R8: {
const isF32 = (opcode === MintOpcode.MINT_CONV_I4_R4) ||
(opcode === MintOpcode.MINT_CONV_I8_R4),
isI64 = (opcode === MintOpcode.MINT_CONV_I8_R4) ||
(opcode === MintOpcode.MINT_CONV_I8_R8),
limit = isI64
? 9223372036854775807 // this will round up to 0x1p63
: 2147483648, // this is 0x1p31 exactly
tempLocal = isF32 ? "temp_f32" : "temp_f64";
// Pre-load locals for the result store at the end
builder.local("pLocals");
// Load src
append_ldloc(builder, getArgU16(ip, 2), isF32 ? WasmOpcode.f32_load : WasmOpcode.f64_load);
builder.local(tempLocal, WasmOpcode.tee_local);
// Detect whether the value is within the representable range for the target type
builder.appendU8(isF32 ? WasmOpcode.f32_abs : WasmOpcode.f64_abs);
builder.appendU8(isF32 ? WasmOpcode.f32_const : WasmOpcode.f64_const);
if (isF32)
builder.appendF32(limit);
else
builder.appendF64(limit);
builder.appendU8(isF32 ? WasmOpcode.f32_lt : WasmOpcode.f64_lt);
// Select value via an if block that returns the result
builder.block(isI64 ? WasmValtype.i64 : WasmValtype.i32, WasmOpcode.if_);
// Value in range so truncate it to the appropriate type
builder.local(tempLocal);
builder.appendU8(floatToIntTable[opcode]);
builder.appendU8(WasmOpcode.else_);
// Value out of range so load the appropriate boundary value
builder.appendU8(isI64 ? WasmOpcode.i64_const : WasmOpcode.i32_const);
builder.appendBoundaryValue(isI64 ? 64 : 32, -1);
builder.endBlock();
append_stloc_tail(builder, getArgU16(ip, 1), isI64 ? WasmOpcode.i64_store : WasmOpcode.i32_store);
break;
}
case MintOpcode.MINT_ADD_MUL_I4_IMM:
case MintOpcode.MINT_ADD_MUL_I8_IMM: {
const isI32 = opcode === MintOpcode.MINT_ADD_MUL_I4_IMM;
builder.local("pLocals");
append_ldloc(builder, getArgU16(ip, 2), isI32 ? WasmOpcode.i32_load : WasmOpcode.i64_load);
const rhs = getArgI16(ip, 3),
multiplier = getArgI16(ip, 4);
if (isI32)
builder.i32_const(rhs);
else
builder.i52_const(rhs);
builder.appendU8(isI32 ? WasmOpcode.i32_add : WasmOpcode.i64_add);
if (isI32)
builder.i32_const(multiplier);
else
builder.i52_const(multiplier);
builder.appendU8(isI32 ? WasmOpcode.i32_mul : WasmOpcode.i64_mul);
append_stloc_tail(builder, getArgU16(ip, 1), isI32 ? WasmOpcode.i32_store : WasmOpcode.i64_store);
break;
}
case MintOpcode.MINT_MONO_CMPXCHG_I4:
builder.local("pLocals");
append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load); // dest
append_ldloc(builder, getArgU16(ip, 3), WasmOpcode.i32_load); // newVal
append_ldloc(builder, getArgU16(ip, 4), WasmOpcode.i32_load); // expected
builder.callImport("cmpxchg_i32");
append_stloc_tail(builder, getArgU16(ip, 1), WasmOpcode.i32_store);
break;
case MintOpcode.MINT_MONO_CMPXCHG_I8:
// because i64 values can't pass through JS cleanly (c.f getRawCwrap and
// EMSCRIPTEN_KEEPALIVE), we pass addresses of newVal, expected and the return value
// to the helper function. The "dest" for the compare-exchange is already a
// pointer, so load it normally
append_ldloc(builder, getArgU16(ip, 2), WasmOpcode.i32_load); // dest
append_ldloca(builder, getArgU16(ip, 3), 0); // newVal
append_ldloca(builder, getArgU16(ip, 4), 0); // expected
append_ldloca(builder, getArgU16(ip, 1), 8); // oldVal
builder.callImport("cmpxchg_i64");
break;
case MintOpcode.MINT_LOG2_I4:
case MintOpcode.MINT_LOG2_I8: {
const isI64 = (opcode === MintOpcode.MINT_LOG2_I8);
builder.local("pLocals");
append_ldloc(builder, getArgU16(ip, 2), isI64 ? WasmOpcode.i64_load : WasmOpcode.i32_load);
if (isI64)
builder.i52_const(1);
else
builder.i32_const(1);
builder.appendU8(isI64 ? WasmOpcode.i64_or : WasmOpcode.i32_or);
builder.appendU8(isI64 ? WasmOpcode.i64_clz : WasmOpcode.i32_clz);
if (isI64)
builder.appendU8(WasmOpcode.i32_wrap_i64);
builder.i32_const(isI64 ? 63 : 31);
builder.appendU8(WasmOpcode.i32_xor);
append_stloc_tail(builder, getArgU16(ip, 1), WasmOpcode.i32_store);
break;
}
case MintOpcode.MINT_SHL_AND_I4:
case MintOpcode.MINT_SHL_AND_I8: {
const isI32 = (opcode === MintOpcode.MINT_SHL_AND_I4),
loadOp = isI32 ? WasmOpcode.i32_load : WasmOpcode.i64_load,
storeOp = isI32 ? WasmOpcode.i32_store : WasmOpcode.i64_store;
builder.local("pLocals");
append_ldloc(builder, getArgU16(ip, 2), loadOp);
append_ldloc(builder, getArgU16(ip, 3), loadOp);
if (isI32)
builder.i32_const(31);
else
builder.i52_const(63);
builder.appendU8(isI32 ? WasmOpcode.i32_and : WasmOpcode.i64_and);
builder.appendU8(isI32 ? WasmOpcode.i32_shl : WasmOpcode.i64_shl);
append_stloc_tail(builder, getArgU16(ip, 1), storeOp);
break;
}
case MintOpcode.MINT_FMA:
case MintOpcode.MINT_FMAF: {
const isF32 = (opcode === MintOpcode.MINT_FMAF),
loadOp = isF32 ? WasmOpcode.f32_load : WasmOpcode.f64_load,
storeOp = isF32 ? WasmOpcode.f32_store : WasmOpcode.f64_store;
builder.local("pLocals");
// LOCAL_VAR (ip [1], double) = fma (LOCAL_VAR (ip [2], double), LOCAL_VAR (ip [3], double), LOCAL_VAR (ip [4], double));
append_ldloc(builder, getArgU16(ip, 2), loadOp);
append_ldloc(builder, getArgU16(ip, 3), loadOp);
append_ldloc(builder, getArgU16(ip, 4), loadOp);
builder.callImport(isF32 ? "fmaf" : "fma");
append_stloc_tail(builder, getArgU16(ip, 1), storeOp);
break;
}
default:
if (
(
(opcode >= MintOpcode.MINT_RET) &&
(opcode <= MintOpcode.MINT_RET_U2)
) ||
(
(opcode >= MintOpcode.MINT_RET_I4_IMM) &&
(opcode <= MintOpcode.MINT_RET_I8_IMM)
)
) {
if (isConditionallyExecuted || builder.options.countBailouts) {
// Not an exit, because returns are normal and we don't want to make them more expensive.
// FIXME: Or do we want to record them? Early conditional returns might reduce the value of a trace,
// but the main problem is more likely to be calls early in traces. Worth testing later.
append_bailout(builder, ip, BailoutReason.Return);
pruneOpcodes = true;
} else
ip = abort;
} else if (
(opcode >= MintOpcode.MINT_LDC_I4_0) &&
(opcode <= MintOpcode.MINT_LDC_R8)
) {
if (!emit_ldc(builder, ip, opcode))
ip = abort;
else
skipDregInvalidation = true;
} else if (
(opcode >= MintOpcode.MINT_MOV_I4_I1) &&
(opcode <= MintOpcode.MINT_MOV_8_4)
) {
if (!emit_mov(builder, ip, opcode))
ip = abort;
} else if (
// binops
(opcode >= MintOpcode.MINT_ADD_I4) &&
(opcode <= MintOpcode.MINT_CLT_UN_R8)
) {
if (!emit_binop(builder, ip, opcode))
ip = abort;
} else if (unopTable[opcode]) {
if (!emit_unop(builder, ip, opcode))
ip = abort;
} else if (relopbranchTable[opcode]) {
if (!emit_relop_branch(builder, ip, frame, opcode))
ip = abort;
else
isConditionallyExecuted = true;
} else if (
// instance ldfld/stfld
(opcode >= MintOpcode.MINT_LDFLD_I1) &&
(opcode <= MintOpcode.MINT_STFLD_R8_UNALIGNED)
) {
if (!emit_fieldop(builder, frame, ip, opcode))
ip = abort;
} else if (
// static ldfld/stfld
(opcode >= MintOpcode.MINT_LDSFLD_I1) &&
(opcode <= MintOpcode.MINT_LDTSFLDA)
) {
if (!emit_sfieldop(builder, frame, ip, opcode))
ip = abort;
} else if (
// indirect load/store
(opcode >= MintOpcode.MINT_LDIND_I1) &&
(opcode <= MintOpcode.MINT_STIND_OFFSET_IMM_I8)
) {
if (!emit_indirectop(builder, ip, opcode))
ip = abort;
} else if (
// math intrinsics
(opcode >= MintOpcode.MINT_ASIN) &&
(opcode <= MintOpcode.MINT_MAXF)
) {
if (!emit_math_intrinsic(builder, ip, opcode))
ip = abort;
} else if (
(opcode >= MintOpcode.MINT_LDELEM_I1) &&
(opcode <= MintOpcode.MINT_LDLEN)
) {
if (!emit_arrayop(builder, frame, ip, opcode))
ip = abort;
} else if (
(opcode >= MintOpcode.MINT_BRFALSE_I4_SP) &&
(opcode <= MintOpcode.MINT_BLT_UN_I8_IMM_SP)
) {
// NOTE: This elseif comes last so that specific safepoint branch
// types can be handled by emit_branch or emit_relop_branch,
// to only perform a conditional bailout
// complex safepoint branches, just generate a bailout
if (builder.branchTargets.size > 0) {
// FIXME: Try to reduce the number of these
append_exit(builder, ip, exitOpcodeCounter, BailoutReason.ComplexBranch);
pruneOpcodes = true;
} else
ip = abort;
} else if (
(opcode >= MintOpcode.MINT_SIMD_V128_LDC) &&
(opcode <= MintOpcode.MINT_SIMD_INTRINS_P_PPP)
) {
if (!emit_simd(builder, ip, opcode, opname, simdIntrinsArgCount, simdIntrinsIndex))
ip = abort;
else {
containsSimd = true;
// We need to do dreg invalidation differently for simd, especially to handle ldc
skipDregInvalidation = true;
}
} else if (opcodeValue === 0) {
// This means it was explicitly marked as no-value in the opcode value table
// so we can just skip over it. This is done for things like nops.
} else {
/*
if (opcodeValue > 0)
mono_log_info(`JITERP: aborting trace for opcode ${opname} with value ${opcodeValue}`);
*/
ip = abort;
}
break;
}
if (ip) {
if (!skipDregInvalidation) {
// Invalidate cached values for all the instruction's destination registers.
// This should have already happened, but it's possible there are opcodes where
// our invalidation is incorrect so it's best to do this for safety reasons
const firstDreg = <any>ip + 2;
for (let r = 0; r < numDregs; r++) {
const dreg = getU16(firstDreg + (r * 2));
invalidate_local(dreg);
}
}
if ((trace > 1) || traceOnError || mostRecentOptions!.dumpTraces || instrumentedTraceId) {
let stmtText = `${(<any>ip).toString(16)} ${opname} `;
const firstDreg = <any>ip + 2;
const firstSreg = firstDreg + (numDregs * 2);
// print sregs
for (let r = 0; r < numSregs; r++) {
if (r !== 0)
stmtText += ", ";
stmtText += getU16(firstSreg + (r * 2));
}
// print dregs
if (numDregs > 0)
stmtText += " -> ";
for (let r = 0; r < numDregs; r++) {
if (r !== 0)
stmtText += ", ";
stmtText += getU16(firstDreg + (r * 2));
}
builder.traceBuf.push(stmtText);
}
if (opcodeValue > 0) {
if (isConditionallyExecuted)
conditionalOpcodeCounter++;
else
prologueOpcodeCounter++;
result += opcodeValue;
} else if (opcodeValue < 0) {
// mono_log_info(`JITERP: opcode ${opname} did not abort but had value ${opcodeValue}`);
}
ip += <any>(opLengthU16 * 2);
if (<any>ip <= (<any>endOfBody))
rip = ip;
// For debugging
if (emitPadding)
builder.appendU8(WasmOpcode.nop);
} else {
if (instrumentedTraceId)
mono_log_info(`instrumented trace ${traceName} aborted for opcode ${opname} @${(<any>_ip).toString(16)}`);
record_abort(builder.traceIndex, _ip, traceName, opcode);
}
}
if (emitPadding)
builder.appendU8(WasmOpcode.nop);
// We need to close any open blocks before generating our closing ret,
// because wasm would allow branching past the ret otherwise
while (builder.activeBlocks > 0)
builder.endBlock();
builder.cfg.exitIp = rip;
// mono_log_info(`estimated size: ${builder.size + builder.cfg.overheadBytes + builder.bytesGeneratedSoFar}`);
// HACK: Traces containing simd will be *much* shorter than non-simd traces,
// which will cause both the heuristic and our length requirement outside
// to reject them. For now, just add a big constant to the length
if (containsSimd)
result += 10240;
return result;
}