Skip to content

Commit 507acb6

Browse files
authored
[wasm] Improve jiterpreter trace entry point selection heuristic (#82604)
This PR adjusts the jiterpreter's heuristic that decides where it's best to put entry points: * Adds a requirement that entry points be at least a certain distance apart, since in some cases we can end up with trace entry points right next to each other, which isn't very useful and adds overhead. (Backwards branch targets are exempted from this so loops will still JIT properly). * If we fail to create a trace exactly located at a backwards branch target, continue trying at blocks afterward. This should help in the rare case where the body of a loop begins with an unsupported instruction. * When considering how long a trace actually is, we treat conditional aborts (like calls and throws) separately from ignored and supported instructions, so they don't count towards the overall size of the trace. These instructions aren't actually doing any useful work and if executed the trace will exit, so it's better not to consider them when deciding whether a trace is worth compiling. This PR also manually inlines trace entry logic.
1 parent f6d564e commit 507acb6

File tree

4 files changed

+52
-44
lines changed

4 files changed

+52
-44
lines changed

src/mono/mono/mini/interp/interp.c

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3798,31 +3798,6 @@ max_d (double lhs, double rhs)
37983798
return fmax (lhs, rhs);
37993799
}
38003800

3801-
#ifdef HOST_BROWSER
3802-
MONO_ALWAYS_INLINE static ptrdiff_t
3803-
mono_interp_tier_enter_jiterpreter (
3804-
JiterpreterThunk thunk, InterpFrame *frame, unsigned char *locals, ThreadContext *context,
3805-
const guint16 *ip
3806-
)
3807-
{
3808-
// g_assert(thunk);
3809-
ptrdiff_t offset = thunk(frame, locals);
3810-
/*
3811-
* Verify that the offset returned by the thunk is not total garbage
3812-
* FIXME: These constants might actually be too small since a method
3813-
* could have massive amounts of IL - maybe we should disable the jiterpreter
3814-
* for methods that big
3815-
*/
3816-
// g_assertf((offset >= -0xFFFFF) && (offset <= 0xFFFFF), "thunk returned an obviously invalid offset: %i", offset);
3817-
#ifdef ENABLE_EXPERIMENT_TIERED
3818-
if (offset < 0) {
3819-
mini_tiered_inc (frame->imethod->method, &frame->imethod->tiered_counter, 0);
3820-
}
3821-
#endif
3822-
return offset;
3823-
}
3824-
#endif // HOST_BROWSER
3825-
38263801
/*
38273802
* If CLAUSE_ARGS is non-null, start executing from it.
38283803
* The ERROR argument is used to avoid declaring an error object for every interp frame, its not used
@@ -7780,9 +7755,7 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK;
77807755
// now execute the trace
77817756
// this isn't important for performance, but it makes it easier to use the
77827757
// jiterpreter early in automated tests where code only runs once
7783-
offset = mono_interp_tier_enter_jiterpreter (
7784-
prepare_result, frame, locals, context, ip
7785-
);
7758+
offset = prepare_result(frame, locals);
77867759
ip = (guint16*) (((guint8*)ip) + offset);
77877760
break;
77887761
}
@@ -7795,9 +7768,7 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK;
77957768

77967769
MINT_IN_CASE(MINT_TIER_ENTER_JITERPRETER) {
77977770
JiterpreterThunk thunk = (void*)READ32(ip + 1);
7798-
ptrdiff_t offset = mono_interp_tier_enter_jiterpreter (
7799-
thunk, frame, locals, context, ip
7800-
);
7771+
ptrdiff_t offset = thunk(frame, locals);
78017772
ip = (guint16*) (((guint8*)ip) + offset);
78027773
MINT_IN_BREAK;
78037774
}

src/mono/mono/mini/interp/jiterpreter.c

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,7 @@ mono_jiterp_cas_i64 (volatile int64_t *addr, int64_t *newVal, int64_t *expected,
613613
#define TRACE_IGNORE -1
614614
#define TRACE_CONTINUE 0
615615
#define TRACE_ABORT 1
616+
#define TRACE_CONDITIONAL_ABORT 2
616617

617618
/*
618619
* This function provides an approximate answer for "will this instruction cause the jiterpreter
@@ -703,7 +704,7 @@ jiterp_should_abort_trace (InterpInst *ins, gboolean *inside_branch_block)
703704
// Detect backwards branches
704705
if (ins->info.target_bb->il_offset <= ins->il_offset) {
705706
if (*inside_branch_block)
706-
return TRACE_CONTINUE;
707+
return TRACE_CONDITIONAL_ABORT;
707708
else
708709
return mono_opt_jiterpreter_backward_branches_enabled ? TRACE_CONTINUE : TRACE_ABORT;
709710
}
@@ -714,7 +715,7 @@ jiterp_should_abort_trace (InterpInst *ins, gboolean *inside_branch_block)
714715
case MINT_MONO_RETHROW:
715716
case MINT_THROW:
716717
if (*inside_branch_block)
717-
return TRACE_CONTINUE;
718+
return TRACE_CONDITIONAL_ABORT;
718719

719720
return TRACE_ABORT;
720721

@@ -755,13 +756,13 @@ jiterp_should_abort_trace (InterpInst *ins, gboolean *inside_branch_block)
755756
(opcode <= MINT_CALLI_NAT_FAST)
756757
// (opcode <= MINT_JIT_CALL2)
757758
)
758-
return *inside_branch_block ? TRACE_CONTINUE : TRACE_ABORT;
759+
return *inside_branch_block ? TRACE_CONDITIONAL_ABORT : TRACE_ABORT;
759760
else if (
760761
// returns
761762
(opcode >= MINT_RET) &&
762763
(opcode <= MINT_RET_U2)
763764
)
764-
return *inside_branch_block ? TRACE_CONTINUE : TRACE_ABORT;
765+
return *inside_branch_block ? TRACE_CONDITIONAL_ABORT : TRACE_ABORT;
765766
else if (
766767
(opcode >= MINT_LDC_I4_M1) &&
767768
(opcode <= MINT_LDC_R8)
@@ -834,6 +835,10 @@ should_generate_trace_here (InterpBasicBlock *bb) {
834835
case TRACE_ABORT:
835836
jiterpreter_abort_counts[ins->opcode]++;
836837
return current_trace_length >= mono_opt_jiterpreter_minimum_trace_length;
838+
case TRACE_CONDITIONAL_ABORT:
839+
// FIXME: Stop traces that contain these early on, as long as we are relatively certain
840+
// that these instructions will be hit (i.e. they are not unlikely branches)
841+
break;
837842
case TRACE_IGNORE:
838843
break;
839844
default:
@@ -925,6 +930,9 @@ jiterp_insert_entry_points (void *_imethod, void *_td)
925930
if (!mono_opt_jiterpreter_traces_enabled)
926931
return;
927932

933+
// Start with a high instruction counter so the distance check will pass
934+
int instruction_count = mono_opt_jiterpreter_minimum_distance_between_traces;
935+
928936
for (InterpBasicBlock *bb = td->entry_bb; bb != NULL; bb = bb->next_bb) {
929937
// Enter trace at top of functions
930938
gboolean is_backwards_branch = FALSE,
@@ -941,7 +949,16 @@ jiterp_insert_entry_points (void *_imethod, void *_td)
941949
// multiple times and waste some work. At present this is unavoidable because
942950
// control flow means we can end up with two traces covering different subsets
943951
// of the same method in order to handle loops and resuming
944-
gboolean should_generate = enabled && should_generate_trace_here(bb);
952+
gboolean should_generate = enabled &&
953+
// Only insert a trace if the heuristic says this location will likely produce a long
954+
// enough one to be worth it
955+
should_generate_trace_here(bb) &&
956+
// And don't insert another trace if we inserted one too recently, unless this
957+
// is a backwards branch target
958+
(
959+
(instruction_count >= mono_opt_jiterpreter_minimum_distance_between_traces) ||
960+
is_backwards_branch
961+
);
945962

946963
if (mono_opt_jiterpreter_call_resume_enabled && bb->contains_call_instruction)
947964
enter_at_next = TRUE;
@@ -957,12 +974,25 @@ jiterp_insert_entry_points (void *_imethod, void *_td)
957974
InterpInst *ins = mono_jiterp_insert_ins (td, NULL, MINT_TIER_PREPARE_JITERPRETER);
958975
memcpy(ins->data, &trace_index, sizeof (trace_index));
959976

977+
// Clear the instruction counter
978+
instruction_count = 0;
979+
960980
// Note that we only clear enter_at_next here, after generating a trace.
961981
// This means that the flag will stay set intentionally if we keep failing
962982
// to generate traces, perhaps due to a string of small basic blocks
963983
// or multiple call instructions.
964984
enter_at_next = bb->contains_call_instruction;
985+
} else if (is_backwards_branch && enabled && !should_generate) {
986+
// We failed to start a trace at a backwards branch target, but that might just mean
987+
// that the loop body starts with one or two unsupported opcodes, so it may be
988+
// worthwhile to try again later
989+
enter_at_next = TRUE;
965990
}
991+
992+
// Increase the instruction counter. If we inserted an entry point at the top of this bb,
993+
// the new instruction counter will be the number of instructions in the block, so if
994+
// it's big enough we'll be able to insert another entry point right away.
995+
instruction_count += bb->in_count;
966996
}
967997
}
968998

src/mono/mono/utils/options-def.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ DEFINE_BOOL(jiterpreter_backward_branches_enabled, "jiterpreter-backward-branche
115115
DEFINE_BOOL(jiterpreter_direct_jit_call, "jiterpreter-direct-jit-calls", TRUE, "Bypass gsharedvt wrappers when compiling JIT call wrappers")
116116
// any trace that doesn't have at least this many meaningful (non-nop) opcodes in it will be rejected
117117
DEFINE_INT(jiterpreter_minimum_trace_length, "jiterpreter-minimum-trace-length", 10, "Reject traces shorter than this number of meaningful opcodes")
118+
// ensure that we don't create trace entry points too close together
119+
DEFINE_INT(jiterpreter_minimum_distance_between_traces, "jiterpreter-minimum-distance-between-traces", 6, "Don't insert entry points closer together than this")
118120
// once a trace entry point is inserted, we only actually JIT code for it once it's been hit this many times
119121
DEFINE_INT(jiterpreter_minimum_trace_hit_count, "jiterpreter-minimum-trace-hit-count", 5000, "JIT trace entry points once they are hit this many times")
120122
// After a do_jit_call call site is hit this many times, we will queue it to be jitted

src/mono/wasm/runtime/jiterpreter-trace-generator.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ export function generate_wasm_body (
203203
// because a backward branch might target a point in the middle of the trace
204204
(isFirstInstruction && backwardBranchTable),
205205
needsFallthroughEipUpdate = needsEipCheck && !isFirstInstruction;
206-
let isDeadOpcode = false,
206+
let isLowValueOpcode = false,
207207
skipDregInvalidation = false;
208208

209209
// We record the offset of each backward branch we encounter, so that later branch
@@ -317,7 +317,7 @@ export function generate_wasm_body (
317317
}
318318

319319
case MintOpcode.MINT_TIER_ENTER_JITERPRETER:
320-
isDeadOpcode = true;
320+
isLowValueOpcode = true;
321321
// If we hit an enter opcode and we're not currently in a branch block
322322
// or the enter opcode is the first opcode in a branch block, this likely
323323
// indicates that we've reached a loop body that was already jitted before
@@ -363,7 +363,7 @@ export function generate_wasm_body (
363363
case MintOpcode.MINT_SDB_BREAKPOINT:
364364
case MintOpcode.MINT_SDB_INTR_LOC:
365365
case MintOpcode.MINT_SDB_SEQ_POINT:
366-
isDeadOpcode = true;
366+
isLowValueOpcode = true;
367367
break;
368368

369369
case MintOpcode.MINT_SAFEPOINT:
@@ -792,6 +792,7 @@ export function generate_wasm_body (
792792
// to abort the entire trace if we have branch support enabled - the call
793793
// might be infrequently hit and as a result it's worth it to keep going.
794794
append_bailout(builder, ip, BailoutReason.Call);
795+
isLowValueOpcode = true;
795796
} else {
796797
// We're in a block that executes unconditionally, and no branches have been
797798
// executed before now so the trace will always need to bail out into the
@@ -815,6 +816,7 @@ export function generate_wasm_body (
815816
? BailoutReason.CallDelegate
816817
: BailoutReason.Call
817818
);
819+
isLowValueOpcode = true;
818820
} else {
819821
ip = abort;
820822
}
@@ -828,6 +830,7 @@ export function generate_wasm_body (
828830
// Otherwise, it may be in a branch that is unlikely to execute
829831
if (builder.branchTargets.size > 0) {
830832
append_bailout(builder, ip, BailoutReason.Throw);
833+
isLowValueOpcode = true;
831834
} else {
832835
ip = abort;
833836
}
@@ -1031,9 +1034,10 @@ export function generate_wasm_body (
10311034
(opcode <= MintOpcode.MINT_RET_I8_IMM)
10321035
)
10331036
) {
1034-
if ((builder.branchTargets.size > 0) || trapTraceErrors || builder.options.countBailouts)
1037+
if ((builder.branchTargets.size > 0) || trapTraceErrors || builder.options.countBailouts) {
10351038
append_bailout(builder, ip, BailoutReason.Return);
1036-
else
1039+
isLowValueOpcode = true;
1040+
} else
10371041
ip = abort;
10381042
} else if (
10391043
(opcode >= MintOpcode.MINT_LDC_I4_M1) &&
@@ -1102,9 +1106,10 @@ export function generate_wasm_body (
11021106
// types can be handled by emit_branch or emit_relop_branch,
11031107
// to only perform a conditional bailout
11041108
// complex safepoint branches, just generate a bailout
1105-
if (builder.branchTargets.size > 0)
1109+
if (builder.branchTargets.size > 0) {
11061110
append_bailout(builder, ip, BailoutReason.ComplexBranch);
1107-
else
1111+
isLowValueOpcode = true;
1112+
} else
11081113
ip = abort;
11091114
} else {
11101115
ip = abort;
@@ -1147,7 +1152,7 @@ export function generate_wasm_body (
11471152
builder.traceBuf.push(stmtText);
11481153
}
11491154

1150-
if (!isDeadOpcode)
1155+
if (!isLowValueOpcode)
11511156
result++;
11521157

11531158
ip += <any>(info[1] * 2);

0 commit comments

Comments
 (0)