Skip to content

Commit 85679d2

Browse files
authored
[wasm] New jiterpreter backbranch scan pass (#98530)
Remove the C code responsible for generating the backward_branch_offsets table stored in InterpMethod that was used by the jiterpreter. Add a new pre-pass to the jiterpreter that does a quick opcode scan to find back-branch targets on the fly when a trace is actually being compiled. Reimplement interpreter branch decoding in a central place in the jiterpreter, and generalize it based on opcode info tables instead of a bunch of hand-written decode logic. Remove some dead options and dead code from the jiterpreter typescript. Fix an enum that got out of sync. Minor debug logging improvements.
1 parent 0f6df43 commit 85679d2

8 files changed

+140
-102
lines changed

src/mono/browser/runtime/jiterpreter-opcodes.ts

+22-22
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
// Keep this file in sync with mintops.def. The order and values need to match exactly.
5-
64
import cwraps from "./cwraps";
75
import { utf8ToString } from "./strings";
86
import { OpcodeInfoType } from "./jiterpreter-enums";
@@ -28,27 +26,29 @@ export type SimdInfoTable = {
2826
[argument_count: number]: SimdInfoSubtable
2927
}
3028

29+
// Keep in sync with mintops.h
3130
export const enum MintOpArgType {
32-
MintOpNoArgs = 0,
33-
MintOpShortInt,
34-
MintOpUShortInt,
35-
MintOpInt,
36-
MintOpLongInt,
37-
MintOpFloat,
38-
MintOpDouble,
39-
MintOpBranch,
40-
MintOpShortBranch,
41-
MintOpSwitch,
42-
MintOpMethodToken,
43-
MintOpFieldToken,
44-
MintOpClassToken,
45-
MintOpTwoShorts,
46-
MintOpTwoInts,
47-
MintOpShortAndInt,
48-
MintOpShortAndShortBranch,
49-
MintOpPair2,
50-
MintOpPair3,
51-
MintOpPair4
31+
MintOpNoArgs = 0,
32+
MintOpShortInt,
33+
MintOpUShortInt,
34+
MintOpInt,
35+
MintOpLongInt,
36+
MintOpFloat,
37+
MintOpDouble,
38+
MintOpBranch,
39+
MintOpShortBranch,
40+
MintOpSwitch,
41+
MintOpMethodToken,
42+
MintOpFieldToken,
43+
MintOpClassToken,
44+
MintOpVTableToken,
45+
MintOpTwoShorts,
46+
MintOpTwoInts,
47+
MintOpShortAndInt,
48+
MintOpShortAndShortBranch,
49+
MintOpPair2,
50+
MintOpPair3,
51+
MintOpPair4
5252
}
5353

5454
// keep in sync with jiterpreter.c, see mono_jiterp_relop_fp

src/mono/browser/runtime/jiterpreter-support.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1321,6 +1321,9 @@ class Cfg {
13211321
this.builder.appendU8(WasmOpcode.br_if);
13221322
this.builder.appendULeb(this.blockStack.indexOf(this.backDispatchOffsets[0]));
13231323
} else {
1324+
if (this.trace > 0)
1325+
mono_log_info(`${this.backDispatchOffsets.length} back branch offsets after filtering.`);
1326+
13241327
// the loop needs to start with a br_table that performs dispatch based on the current value
13251328
// of the dispatch index local
13261329
// br_table has to be surrounded by a block in order for a depth of 0 to be fallthrough

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

+106-23
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
} from "./memory";
1010
import {
1111
WasmOpcode, WasmSimdOpcode, WasmValtype,
12-
getOpcodeName,
12+
getOpcodeName, MintOpArgType
1313
} from "./jiterpreter-opcodes";
1414
import {
1515
MintOpcode, SimdInfo,
@@ -33,7 +33,7 @@ import {
3333

3434
disabledOpcodes, countCallTargets,
3535
callTargetCounts,
36-
trace, traceOnError, traceOnRuntimeError,
36+
trace, traceOnError,
3737
emitPadding, traceBranchDisplacements,
3838
traceEip, nullCheckValidation,
3939
traceNullCheckOptimizations,
@@ -109,6 +109,7 @@ function is_backward_branch_target(
109109
if (!backwardBranchTable)
110110
return false;
111111

112+
// TODO: sort the table and exploit that for faster scan. Not important yet
112113
for (let i = 0; i < backwardBranchTable.length; i++) {
113114
const actualOffset = (backwardBranchTable[i] * 2) + <any>startOfBody;
114115
if (actualOffset === ip)
@@ -128,6 +129,74 @@ function get_known_constant_value(builder: WasmBuilder, localOffset: number): Kn
128129
return knownConstantValues.get(localOffset);
129130
}
130131

132+
// Perform a quick scan through the opcodes potentially in this trace to build a table of
133+
// backwards branch targets, compatible with the layout of the old one that was generated in C.
134+
// We do this here to match the exact way that the jiterp calculates branch targets, since
135+
// there were previously corner cases where jiterp and interp disagreed.
136+
export function generateBackwardBranchTable(
137+
ip: MintOpcodePtr, startOfBody: MintOpcodePtr, sizeOfBody: MintOpcodePtr,
138+
): Uint16Array | null {
139+
const endOfBody = <any>startOfBody + <any>sizeOfBody;
140+
// TODO: Cache this table object instance and reuse it to reduce gc pressure?
141+
const table : number[] = [];
142+
// IP of the start of the trace in U16s, relative to startOfBody.
143+
const rbase16 = (<any>ip - <any>startOfBody) / 2;
144+
145+
while (ip < endOfBody) {
146+
// IP of the current opcode in U16s, relative to startOfBody. This is what the back branch table uses
147+
const rip16 = (<any>ip - <any>startOfBody) / 2;
148+
const opcode = <MintOpcode>getU16(ip);
149+
// HACK
150+
if (opcode === MintOpcode.MINT_SWITCH)
151+
break;
152+
153+
const opLengthU16 = cwraps.mono_jiterp_get_opcode_info(opcode, OpcodeInfoType.Length);
154+
// Any opcode with a branch argtype will have a decoded displacement, even if we don't
155+
// implement the opcode. Everything else will return undefined here and be skipped
156+
const displacement = getBranchDisplacement(ip, opcode);
157+
if (typeof (displacement) !== "number") {
158+
ip += <any>(opLengthU16 * 2);
159+
continue;
160+
}
161+
162+
// These checks shouldn't fail unless memory is corrupted or something is wrong with the decoder.
163+
// We don't want to cause decoder bugs to make the application exit, though - graceful degradation.
164+
if (displacement === 0) {
165+
mono_log_info(`opcode @${ip} branch target is self. aborting backbranch table generation`);
166+
break;
167+
}
168+
169+
const rtarget16 = rip16 + (displacement);
170+
if (rtarget16 < 0) {
171+
mono_log_info(`opcode @${ip}'s displacement of ${displacement} goes before body: ${rtarget16}. aborting backbranch table generation`);
172+
break;
173+
}
174+
175+
// If the relative target is before the start of the trace, don't record it.
176+
// The trace will be unable to successfully branch to it so it would just make the table bigger.
177+
if (rtarget16 >= rbase16)
178+
table.push(rtarget16);
179+
180+
switch (opcode) {
181+
case MintOpcode.MINT_CALL_HANDLER:
182+
case MintOpcode.MINT_CALL_HANDLER_S:
183+
// While this formally isn't a backward branch target, we want to record
184+
// the offset of its following instruction so that the jiterpreter knows
185+
// to generate the necessary dispatch code to enable branching back to it.
186+
table.push(rip16 + opLengthU16);
187+
break;
188+
}
189+
190+
ip += <any>(opLengthU16 * 2);
191+
}
192+
193+
if (table.length <= 0)
194+
return null;
195+
// Not important yet, so not doing it
196+
// table.sort((a, b) => a - b);
197+
return new Uint16Array(table);
198+
}
199+
131200
export function generateWasmBody(
132201
frame: NativePointer, traceName: string, ip: MintOpcodePtr,
133202
startOfBody: MintOpcodePtr, endOfBody: MintOpcodePtr,
@@ -1546,7 +1615,7 @@ export function generateWasmBody(
15461615
}
15471616
}
15481617

1549-
if ((trace > 1) || traceOnError || traceOnRuntimeError || mostRecentOptions!.dumpTraces || instrumentedTraceId) {
1618+
if ((trace > 1) || traceOnError || mostRecentOptions!.dumpTraces || instrumentedTraceId) {
15501619
let stmtText = `${(<any>ip).toString(16)} ${opname} `;
15511620
const firstDreg = <any>ip + 2;
15521621
const firstSreg = firstDreg + (numDregs * 2);
@@ -2572,13 +2641,45 @@ function append_call_handler_store_ret_ip(
25722641
builder.callHandlerReturnAddresses.push(retIp);
25732642
}
25742643

2644+
function getBranchDisplacement(
2645+
ip: MintOpcodePtr, opcode: MintOpcode
2646+
) : number | undefined {
2647+
const opArgType = cwraps.mono_jiterp_get_opcode_info(opcode, OpcodeInfoType.OpArgType),
2648+
payloadOffset = cwraps.mono_jiterp_get_opcode_info(opcode, OpcodeInfoType.Sregs),
2649+
payloadAddress = <any>ip + 2 + (payloadOffset * 2);
2650+
2651+
let result : number;
2652+
switch (opArgType) {
2653+
case MintOpArgType.MintOpBranch:
2654+
result = getI32_unaligned(payloadAddress);
2655+
break;
2656+
case MintOpArgType.MintOpShortBranch:
2657+
result = getI16(payloadAddress);
2658+
break;
2659+
case MintOpArgType.MintOpShortAndShortBranch:
2660+
result = getI16(payloadAddress + 2);
2661+
break;
2662+
default:
2663+
return undefined;
2664+
}
2665+
2666+
if (traceBranchDisplacements)
2667+
mono_log_info(`${getOpcodeName(opcode)} @${ip} displacement=${result}`);
2668+
2669+
return result;
2670+
}
2671+
25752672
function emit_branch(
25762673
builder: WasmBuilder, ip: MintOpcodePtr,
2577-
frame: NativePointer, opcode: MintOpcode, displacement?: number
2674+
frame: NativePointer, opcode: MintOpcode
25782675
): boolean {
25792676
const isSafepoint = (opcode >= MintOpcode.MINT_BRFALSE_I4_SP) &&
25802677
(opcode <= MintOpcode.MINT_BLT_UN_I8_IMM_SP);
25812678

2679+
const displacement = getBranchDisplacement(ip, opcode);
2680+
if (typeof (displacement) !== "number")
2681+
return false;
2682+
25822683
// If the branch is taken we bail out to allow the interpreter to do it.
25832684
// So for brtrue, we want to do 'cond == 0' to produce a bailout only
25842685
// when the branch will be taken (by skipping the bailout in this block)
@@ -2592,15 +2693,7 @@ function emit_branch(
25922693
case MintOpcode.MINT_BR_S: {
25932694
const isCallHandler = (opcode === MintOpcode.MINT_CALL_HANDLER) ||
25942695
(opcode === MintOpcode.MINT_CALL_HANDLER_S);
2595-
displacement = (
2596-
(opcode === MintOpcode.MINT_BR) ||
2597-
(opcode === MintOpcode.MINT_CALL_HANDLER)
2598-
)
2599-
? getArgI32(ip, 1)
2600-
: getArgI16(ip, 1);
26012696

2602-
if (traceBranchDisplacements)
2603-
mono_log_info(`br.s @${ip} displacement=${displacement}`);
26042697
const destination = <any>ip + (displacement * 2);
26052698

26062699
if (displacement <= 0) {
@@ -2653,7 +2746,6 @@ function emit_branch(
26532746

26542747
// Load the condition
26552748

2656-
displacement = getArgI16(ip, 2);
26572749
append_ldloc(builder, getArgU16(ip, 1), is64 ? WasmOpcode.i64_load : WasmOpcode.i32_load);
26582750
if (
26592751
(opcode === MintOpcode.MINT_BRFALSE_I4_S) ||
@@ -2684,11 +2776,6 @@ function emit_branch(
26842776
}
26852777
}
26862778

2687-
if (!displacement)
2688-
throw new Error("Branch had no displacement");
2689-
else if (traceBranchDisplacements)
2690-
mono_log_info(`${getOpcodeName(opcode)} @${ip} displacement=${displacement}`);
2691-
26922779
const destination = <any>ip + (displacement * 2);
26932780

26942781
if (displacement < 0) {
@@ -2741,10 +2828,6 @@ function emit_relop_branch(
27412828
if (!relopInfo && !intrinsicFpBinop)
27422829
return false;
27432830

2744-
const displacement = getArgI16(ip, 3);
2745-
if (traceBranchDisplacements)
2746-
mono_log_info(`relop @${ip} displacement=${displacement}`);
2747-
27482831
const operandLoadOp = relopInfo
27492832
? relopInfo[1]
27502833
: (
@@ -2779,7 +2862,7 @@ function emit_relop_branch(
27792862
builder.callImport("relop_fp");
27802863
}
27812864

2782-
return emit_branch(builder, ip, frame, opcode, displacement);
2865+
return emit_branch(builder, ip, frame, opcode);
27832866
}
27842867

27852868
function emit_math_intrinsic(builder: WasmBuilder, ip: MintOpcodePtr, opcode: MintOpcode): boolean {

src/mono/browser/runtime/jiterpreter.ts

+6-18
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,23 @@
44
import { MonoMethod } from "./types/internal";
55
import { NativePointer } from "./types/emscripten";
66
import { Module, mono_assert, runtimeHelpers } from "./globals";
7-
import { getU16, getU32_unaligned, localHeapViewU8 } from "./memory";
7+
import { getU16 } from "./memory";
88
import { WasmValtype, WasmOpcode, getOpcodeName } from "./jiterpreter-opcodes";
99
import { MintOpcode } from "./mintops";
1010
import cwraps from "./cwraps";
1111
import {
1212
MintOpcodePtr, WasmBuilder, addWasmFunctionPointer,
1313
_now, isZeroPageReserved,
1414
getRawCwrap, importDef, JiterpreterOptions, getOptions, recordFailure,
15-
getMemberOffset, getCounter, modifyCounter,
15+
getCounter, modifyCounter,
1616
simdFallbackCounters, getWasmFunctionTable
1717
} from "./jiterpreter-support";
1818
import {
19-
JiterpMember, BailoutReasonNames, BailoutReason,
19+
BailoutReasonNames, BailoutReason,
2020
JiterpreterTable, JiterpCounter,
2121
} from "./jiterpreter-enums";
2222
import {
23-
generateWasmBody
23+
generateWasmBody, generateBackwardBranchTable
2424
} from "./jiterpreter-trace-generator";
2525
import { mono_jiterp_free_method_data_interp_entry } from "./jiterpreter-interp-entry";
2626
import { mono_jiterp_free_method_data_jit_call } from "./jiterpreter-jit-call";
@@ -34,10 +34,6 @@ export const
3434
// Record a trace of all managed interpreter opcodes then dump it to console
3535
// if an error occurs while compiling the output wasm
3636
traceOnError = false,
37-
// Record trace but dump it when the trace has a runtime error instead
38-
// requires trapTraceErrors to work and will slow trace compilation +
39-
// increase memory usage
40-
traceOnRuntimeError = false,
4137
// Trace the method name, location and reason for each abort
4238
traceAbortLocations = false,
4339
// Count the number of times a given method is seen as a call target, then
@@ -61,11 +57,6 @@ export const
6157
// Print diagnostic information when generating backward branches
6258
// 1 = failures only, 2 = full detail
6359
traceBackBranches = 0,
64-
// If we encounter an enter opcode that looks like a loop body and it was already
65-
// jitted, we should abort the current trace since it's not worth continuing
66-
// Unproductive if we have backward branches enabled because it can stop us from jitting
67-
// nested loops
68-
abortAtJittedLoopBodies = true,
6960
// Enable generating conditional backward branches for ENDFINALLY opcodes if we saw some CALL_HANDLER
7061
// opcodes previously, up to this many potential return addresses. If a trace contains more potential
7162
// return addresses than this we will not emit code for the ENDFINALLY opcode
@@ -1030,11 +1021,8 @@ export function mono_interp_tier_prepare_jiterpreter(
10301021
const methodName = utf8ToString(cwraps.mono_wasm_method_get_name(method));
10311022
info.name = methodFullName || methodName;
10321023

1033-
const imethod = getU32_unaligned(getMemberOffset(JiterpMember.Imethod) + <any>frame);
1034-
const backBranchCount = getU32_unaligned(getMemberOffset(JiterpMember.BackwardBranchOffsetsCount) + imethod);
1035-
const pBackBranches = getU32_unaligned(getMemberOffset(JiterpMember.BackwardBranchOffsets) + imethod);
1036-
let backwardBranchTable = backBranchCount
1037-
? new Uint16Array(localHeapViewU8().buffer, pBackBranches, backBranchCount)
1024+
let backwardBranchTable = mostRecentOptions.noExitBackwardBranches
1025+
? generateBackwardBranchTable(ip, startOfBody, sizeOfBody)
10381026
: null;
10391027

10401028
// If we're compiling a trace that doesn't start at the beginning of a method,

src/mono/mono/mini/interp/interp-internals.h

-2
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,6 @@ struct InterpMethod {
181181
unsigned int is_verbose : 1;
182182
#if HOST_BROWSER
183183
unsigned int contains_traces : 1;
184-
guint16 *backward_branch_offsets;
185-
unsigned int backward_branch_offsets_count;
186184
MonoBitSet *address_taken_bits;
187185
#endif
188186
#if PROFILE_INTERP

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

+2-4
Original file line numberDiff line numberDiff line change
@@ -1165,7 +1165,9 @@ enum {
11651165
JITERP_MEMBER_SPAN_LENGTH,
11661166
JITERP_MEMBER_SPAN_DATA,
11671167
JITERP_MEMBER_ARRAY_LENGTH,
1168+
// Kept as-is but no longer implemented
11681169
JITERP_MEMBER_BACKWARD_BRANCH_OFFSETS,
1170+
// Ditto
11691171
JITERP_MEMBER_BACKWARD_BRANCH_OFFSETS_COUNT,
11701172
JITERP_MEMBER_CLAUSE_DATA_OFFSETS,
11711173
JITERP_MEMBER_PARAMS_COUNT,
@@ -1195,10 +1197,6 @@ mono_jiterp_get_member_offset (int member) {
11951197
return offsetof (InterpFrame, imethod);
11961198
case JITERP_MEMBER_DATA_ITEMS:
11971199
return offsetof (InterpMethod, data_items);
1198-
case JITERP_MEMBER_BACKWARD_BRANCH_OFFSETS:
1199-
return offsetof (InterpMethod, backward_branch_offsets);
1200-
case JITERP_MEMBER_BACKWARD_BRANCH_OFFSETS_COUNT:
1201-
return offsetof (InterpMethod, backward_branch_offsets_count);
12021200
case JITERP_MEMBER_CLAUSE_DATA_OFFSETS:
12031201
return offsetof (InterpMethod, clause_data_offsets);
12041202
case JITERP_MEMBER_RMETHOD:

src/mono/mono/mini/interp/mintops.h

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <config.h>
99
#include <glib.h>
1010

11+
// If you change this, update jiterpreter-opcodes.ts.
1112
typedef enum
1213
{
1314
MintOpNoArgs,

0 commit comments

Comments
 (0)