Skip to content

Commit 1cfda5e

Browse files
authored
[wasm] Add a monitoring phase to jiterpreter traces and discard unproductive ones (#83432)
* Add a monitoring phase to jiterpreter traces, where we determine the average distance (in bytes) they travel. Then reject traces that have a low average distance after the monitoring period
1 parent 367a360 commit 1cfda5e

File tree

7 files changed

+93
-6
lines changed

7 files changed

+93
-6
lines changed

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7787,11 +7787,7 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK;
77877787
* (note that right now threading doesn't work, but it's worth being correct
77887788
* here so that implementing thread support will be easier later.)
77897789
*/
7790-
*mutable_ip = MINT_TIER_NOP_JITERPRETER;
7791-
mono_memory_barrier ();
7792-
*(volatile JiterpreterThunk*)(ip + 1) = prepare_result;
7793-
mono_memory_barrier ();
7794-
*mutable_ip = MINT_TIER_ENTER_JITERPRETER;
7790+
*mutable_ip = MINT_TIER_MONITOR_JITERPRETER;
77957791
// now execute the trace
77967792
// this isn't important for performance, but it makes it easier to use the
77977793
// jiterpreter early in automated tests where code only runs once
@@ -7806,6 +7802,15 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK;
78067802
MINT_IN_BREAK;
78077803
}
78087804

7805+
MINT_IN_CASE(MINT_TIER_MONITOR_JITERPRETER) {
7806+
// The trace is in monitoring mode, where we track how far it actually goes
7807+
// each time it is executed for a while. After N more hits, we either
7808+
// turn it into an ENTER or a NOP depending on how well it is working
7809+
ptrdiff_t offset = mono_jiterp_monitor_trace (ip, frame, locals);
7810+
ip = (guint16*) (((guint8*)ip) + offset);
7811+
MINT_IN_BREAK;
7812+
}
7813+
78097814
MINT_IN_CASE(MINT_TIER_ENTER_JITERPRETER) {
78107815
JiterpreterThunk thunk = (void*)READ32(ip + 1);
78117816
ptrdiff_t offset = thunk(frame, locals);

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,13 +863,24 @@ typedef struct {
863863
// 64-bits because it can get very high if estimate heat is turned on
864864
gint64 hit_count;
865865
JiterpreterThunk thunk;
866+
int size_of_trace;
867+
gint32 total_distance;
866868
} TraceInfo;
867869

870+
// If a trace exits with an exterior backward branch, treat its distance as this value
871+
#define TRACE_NEGATIVE_DISTANCE 64
872+
// Don't allow a trace to increase the total distance by more than this amount, since
873+
// it would skew the average too much
874+
#define TRACE_DISTANCE_LIMIT 512
875+
876+
// The maximum number of trace segments used to store TraceInfo. This limits
877+
// the maximum total number of traces to MAX_TRACE_SEGMENTS * TRACE_SEGMENT_SIZE
868878
#define MAX_TRACE_SEGMENTS 256
869879
#define TRACE_SEGMENT_SIZE 1024
870880

871881
static volatile gint32 trace_count = 0;
872882
static TraceInfo *trace_segments[MAX_TRACE_SEGMENTS] = { NULL };
883+
static gint32 traces_rejected = 0;
873884

874885
static TraceInfo *
875886
trace_info_allocate_segment (gint32 index) {
@@ -1030,6 +1041,9 @@ mono_interp_tier_prepare_jiterpreter_fast (
10301041
frame, method, ip, (gint32)trace_index,
10311042
start_of_body, size_of_body
10321043
);
1044+
// Record the maximum size of the trace (we don't know how long it actually is here)
1045+
// which might be smaller than the function body if this trace is in the middle
1046+
trace_info->size_of_trace = size_of_body - (ip - start_of_body);
10331047
trace_info->thunk = result;
10341048
return result;
10351049
} else {
@@ -1309,6 +1323,63 @@ mono_jiterp_write_number_unaligned (void *dest, double value, int mode) {
13091323
}
13101324
}
13111325

1326+
ptrdiff_t
1327+
mono_jiterp_monitor_trace (const guint16 *ip, void *frame, void *locals)
1328+
{
1329+
gint32 index = READ32(ip + 1);
1330+
TraceInfo *info = trace_info_get(index);
1331+
g_assert(info);
1332+
1333+
JiterpreterThunk thunk = info->thunk;
1334+
// FIXME: This shouldn't be possible
1335+
if (((guint32)(void *)thunk) <= JITERPRETER_NOT_JITTED)
1336+
return 6;
1337+
ptrdiff_t result = thunk(frame, locals);
1338+
// Maintain an approximate sum of how far trace execution has advanced over
1339+
// the monitoring period, so we can evaluate its average later and decide
1340+
// whether to keep the trace
1341+
// Note that a result of 0 means that a loop back-branched to itself.
1342+
info->total_distance += result <= 0
1343+
? TRACE_NEGATIVE_DISTANCE
1344+
: (result > TRACE_DISTANCE_LIMIT
1345+
? TRACE_DISTANCE_LIMIT
1346+
: result
1347+
);
1348+
1349+
gint64 hit_count = info->hit_count++ - mono_opt_jiterpreter_minimum_trace_hit_count;
1350+
if (hit_count == mono_opt_jiterpreter_trace_monitoring_period) {
1351+
// Prepare to enable the trace
1352+
volatile guint16 *mutable_ip = (volatile guint16*)ip;
1353+
*mutable_ip = MINT_TIER_NOP_JITERPRETER;
1354+
1355+
mono_memory_barrier ();
1356+
gint64 average_distance = info->total_distance / hit_count;
1357+
gint64 threshold = mono_opt_jiterpreter_trace_average_distance_threshold,
1358+
low_threshold = info->size_of_trace / 2;
1359+
// Don't reject short traces as long as they run mostly to the end, we already
1360+
// decided previously that they are worth keeping for some reason
1361+
if (low_threshold < threshold)
1362+
threshold = low_threshold;
1363+
1364+
if (average_distance >= threshold) {
1365+
*(volatile JiterpreterThunk*)(ip + 1) = thunk;
1366+
mono_memory_barrier ();
1367+
*mutable_ip = MINT_TIER_ENTER_JITERPRETER;
1368+
} else {
1369+
traces_rejected++;
1370+
// g_print("trace #%d @%d rejected; average_distance==%d\n", index, ip, average_distance);
1371+
}
1372+
}
1373+
1374+
return result;
1375+
}
1376+
1377+
EMSCRIPTEN_KEEPALIVE gint32
1378+
mono_jiterp_get_rejected_trace_count ()
1379+
{
1380+
return traces_rejected;
1381+
}
1382+
13121383
// HACK: fix C4206
13131384
EMSCRIPTEN_KEEPALIVE
13141385
#endif // HOST_BROWSER

src/mono/mono/mini/interp/jiterpreter.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ mono_jiterp_imethod_to_ftnptr (InterpMethod *imethod);
139139
void
140140
mono_jiterp_enum_hasflag (MonoClass *klass, gint32 *dest, stackval *sp1, stackval *sp2);
141141

142+
ptrdiff_t
143+
mono_jiterp_monitor_trace (const guint16 *ip, void *frame, void *locals);
144+
142145
#endif // __MONO_MINI_INTERPRETER_INTERNALS_H__
143146

144147
extern WasmDoJitCall jiterpreter_do_jit_call;

src/mono/mono/mini/interp/mintops.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,7 @@ OPDEF(MINT_METADATA_UPDATE_LDFLDA, "metadata_update.ldflda", 5, 1, 1, MintOpTwoS
841841
OPDEF(MINT_TIER_PREPARE_JITERPRETER, "tier_prepare_jiterpreter", 3, 0, 0, MintOpInt)
842842
OPDEF(MINT_TIER_NOP_JITERPRETER, "tier_nop_jiterpreter", 3, 0, 0, MintOpInt)
843843
OPDEF(MINT_TIER_ENTER_JITERPRETER, "tier_enter_jiterpreter", 3, 0, 0, MintOpInt)
844+
OPDEF(MINT_TIER_MONITOR_JITERPRETER, "tier_monitor_jiterpreter", 3, 0, 0, MintOpInt)
844845
#endif // HOST_BROWSER
845846

846847
IROPDEF(MINT_NOP, "nop", 1, 0, 0, MintOpNoArgs)

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ DEFINE_INT(jiterpreter_minimum_trace_length, "jiterpreter-minimum-trace-length",
119119
DEFINE_INT(jiterpreter_minimum_distance_between_traces, "jiterpreter-minimum-distance-between-traces", 4, "Don't insert entry points closer together than this")
120120
// once a trace entry point is inserted, we only actually JIT code for it once it's been hit this many times
121121
DEFINE_INT(jiterpreter_minimum_trace_hit_count, "jiterpreter-minimum-trace-hit-count", 5000, "JIT trace entry points once they are hit this many times")
122+
// trace prepares turn into a monitor opcode and stay one this long before being converted to enter or nop
123+
DEFINE_INT(jiterpreter_trace_monitoring_period, "jiterpreter-trace-monitoring-period", 3000, "Monitor jitted traces for this many calls to determine whether to keep them")
124+
// traces that only offset ip by less than this on average will be rejected
125+
DEFINE_INT(jiterpreter_trace_average_distance_threshold, "jiterpreter-trace-average-distance-threshold", 52, "Traces with an average distance less than this will be discarded")
122126
// After a do_jit_call call site is hit this many times, we will queue it to be jitted
123127
DEFINE_INT(jiterpreter_jit_call_trampoline_hit_count, "jiterpreter-jit-call-hit-count", 1000, "Queue specialized do_jit_call trampoline for JIT after this many hits")
124128
// After a do_jit_call call site is hit this many times without being jitted, we will flush the JIT queue

src/mono/wasm/runtime/cwraps.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ const fn_signatures: SigLine[] = [
120120
[true, "mono_jiterp_debug_count", "number", []],
121121
[true, "mono_jiterp_get_trace_hit_count", "number", ["number"]],
122122
[true, "mono_jiterp_get_polling_required_address", "number", []],
123+
[true, "mono_jiterp_get_rejected_trace_count", "number", []],
123124
...legacy_interop_cwraps
124125
];
125126

@@ -236,6 +237,7 @@ export interface t_Cwraps {
236237
mono_jiterp_get_trace_hit_count(traceIndex: number): number;
237238
mono_jiterp_get_polling_required_address(): Int32Ptr;
238239
mono_jiterp_write_number_unaligned(destination: VoidPtr, value: number, mode: number): void;
240+
mono_jiterp_get_rejected_trace_count(): number;
239241
}
240242

241243
const wrapped_c_functions: t_Cwraps = <any>{};

src/mono/wasm/runtime/jiterpreter.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -955,7 +955,8 @@ export function jiterpreter_dump_stats (b?: boolean, concise?: boolean) {
955955

956956
console.log(`// 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`);
957957
const backBranchHitRate = (counters.backBranchesEmitted / (counters.backBranchesEmitted + counters.backBranchesNotEmitted)) * 100;
958-
console.log(`// time: ${elapsedTimes.generation | 0}ms generating, ${elapsedTimes.compilation | 0}ms compiling wasm. ${counters.nullChecksEliminated} null checks eliminated. ${counters.backBranchesEmitted} back-branches emitted (${counters.backBranchesNotEmitted} failed, ${backBranchHitRate.toFixed(1)}%)`);
958+
const tracesRejected = cwraps.mono_jiterp_get_rejected_trace_count();
959+
console.log(`// 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`);
959960
if (concise)
960961
return;
961962

0 commit comments

Comments
 (0)