@@ -184,6 +184,34 @@ function getOpcodeLengthU16 (ip: MintOpcodePtr, opcode: MintOpcode) {
184
184
}
185
185
}
186
186
187
+ function decodeSwitch ( ip : MintOpcodePtr ) : MintOpcodePtr [ ] {
188
+ mono_assert ( getU16 ( ip ) === MintOpcode . MINT_SWITCH , "decodeSwitch called on a non-switch" ) ;
189
+ const n = getArgU32 ( ip , 2 ) ;
190
+ const result = [ ] ;
191
+ /*
192
+ guint32 val = LOCAL_VAR (ip [1], guint32);
193
+ guint32 n = READ32 (ip + 2);
194
+ ip += 4;
195
+ if (val < n) {
196
+ ip += 2 * val;
197
+ int offset = READ32 (ip);
198
+ ip += offset;
199
+ } else {
200
+ ip += 2 * n;
201
+ }
202
+ */
203
+ // mono_log_info(`switch[${n}] @${ip}`);
204
+ for ( let i = 0 ; i < n ; i ++ ) {
205
+ const base = < any > ip + 8 + ( 4 * i ) ,
206
+ offset = getU32_unaligned ( base ) ,
207
+ target = base + ( offset * 2 ) ;
208
+ // mono_log_info(` ${i} -> ${target}`);
209
+ result . push ( target ) ;
210
+ }
211
+
212
+ return result ;
213
+ }
214
+
187
215
// Perform a quick scan through the opcodes potentially in this trace to build a table of
188
216
// backwards branch targets, compatible with the layout of the old one that was generated in C.
189
217
// We do this here to match the exact way that the jiterp calculates branch targets, since
@@ -205,47 +233,60 @@ export function generateBackwardBranchTable (
205
233
const opcode = < MintOpcode > getU16 ( ip ) ;
206
234
const opLengthU16 = getOpcodeLengthU16 ( ip , opcode ) ;
207
235
208
- // Any opcode with a branch argtype will have a decoded displacement, even if we don't
209
- // implement the opcode. Everything else will return undefined here and be skipped
210
- const displacement = getBranchDisplacement ( ip , opcode ) ;
211
- if ( typeof ( displacement ) !== "number" ) {
212
- ip += < any > ( opLengthU16 * 2 ) ;
213
- continue ;
214
- }
215
-
216
- // These checks shouldn't fail unless memory is corrupted or something is wrong with the decoder.
217
- // We don't want to cause decoder bugs to make the application exit, though - graceful degradation.
218
- if ( displacement === 0 ) {
219
- mono_log_info ( `opcode @${ ip } branch target is self. aborting backbranch table generation` ) ;
220
- break ;
221
- }
236
+ if ( opcode === MintOpcode . MINT_SWITCH ) {
237
+ // FIXME: Once the cfg supports back-branches in jump tables, uncomment this to
238
+ // insert the back-branch targets into the table so they'll actually work
239
+ /*
240
+ const switchTable = decodeSwitch(ip);
241
+ for (const target of switchTable) {
242
+ const rtarget16 = (<any>target - <any>startOfBody) / 2;
243
+ if (target < ip)
244
+ table.push(rtarget16);
245
+ }
246
+ */
247
+ } else {
248
+ // Any opcode with a branch argtype will have a decoded displacement, even if we don't
249
+ // implement the opcode. Everything else will return undefined here and be skipped
250
+ const displacement = getBranchDisplacement ( ip , opcode ) ;
251
+ if ( typeof ( displacement ) !== "number" ) {
252
+ ip += < any > ( opLengthU16 * 2 ) ;
253
+ continue ;
254
+ }
222
255
223
- // Only record *backward* branches
224
- // We will filter this down further in the Cfg because it takes note of which branches it sees,
225
- // but it is also beneficial to have a null table (further down) due to seeing no potential
226
- // back branch targets at all, as it allows the Cfg to skip additional code generation entirely
227
- // if it knows there will never be any backwards branches in a given trace
228
- if ( displacement < 0 ) {
229
- const rtarget16 = rip16 + ( displacement ) ;
230
- if ( rtarget16 < 0 ) {
231
- mono_log_info ( `opcode @${ ip } 's displacement of ${ displacement } goes before body: ${ rtarget16 } . aborting backbranch table generation` ) ;
256
+ // These checks shouldn't fail unless memory is corrupted or something is wrong with the decoder.
257
+ // We don't want to cause decoder bugs to make the application exit, though - graceful degradation.
258
+ if ( displacement === 0 ) {
259
+ mono_log_info ( `opcode @${ ip } branch target is self. aborting backbranch table generation` ) ;
232
260
break ;
233
261
}
234
262
235
- // If the relative target is before the start of the trace, don't record it.
236
- // The trace will be unable to successfully branch to it so it would just make the table bigger.
237
- if ( rtarget16 >= rbase16 )
238
- table . push ( rtarget16 ) ;
239
- }
263
+ // Only record *backward* branches
264
+ // We will filter this down further in the Cfg because it takes note of which branches it sees,
265
+ // but it is also beneficial to have a null table (further down) due to seeing no potential
266
+ // back branch targets at all, as it allows the Cfg to skip additional code generation entirely
267
+ // if it knows there will never be any backwards branches in a given trace
268
+ if ( displacement < 0 ) {
269
+ const rtarget16 = rip16 + ( displacement ) ;
270
+ if ( rtarget16 < 0 ) {
271
+ mono_log_info ( `opcode @${ ip } 's displacement of ${ displacement } goes before body: ${ rtarget16 } . aborting backbranch table generation` ) ;
272
+ break ;
273
+ }
240
274
241
- switch ( opcode ) {
242
- case MintOpcode . MINT_CALL_HANDLER :
243
- case MintOpcode . MINT_CALL_HANDLER_S :
244
- // While this formally isn't a backward branch target, we want to record
245
- // the offset of its following instruction so that the jiterpreter knows
246
- // to generate the necessary dispatch code to enable branching back to it.
247
- table . push ( rip16 + opLengthU16 ) ;
248
- break ;
275
+ // If the relative target is before the start of the trace, don't record it.
276
+ // The trace will be unable to successfully branch to it so it would just make the table bigger.
277
+ if ( rtarget16 >= rbase16 )
278
+ table . push ( rtarget16 ) ;
279
+ }
280
+
281
+ switch ( opcode ) {
282
+ case MintOpcode . MINT_CALL_HANDLER :
283
+ case MintOpcode . MINT_CALL_HANDLER_S :
284
+ // While this formally isn't a backward branch target, we want to record
285
+ // the offset of its following instruction so that the jiterpreter knows
286
+ // to generate the necessary dispatch code to enable branching back to it.
287
+ table . push ( rip16 + opLengthU16 ) ;
288
+ break ;
289
+ }
249
290
}
250
291
251
292
ip += < any > ( opLengthU16 * 2 ) ;
@@ -399,7 +440,7 @@ export function generateWasmBody (
399
440
400
441
switch ( opcode ) {
401
442
case MintOpcode . MINT_SWITCH : {
402
- if ( ! emit_switch ( builder , ip ) )
443
+ if ( ! emit_switch ( builder , ip , exitOpcodeCounter ) )
403
444
ip = abort ;
404
445
break ;
405
446
}
@@ -4036,7 +4077,39 @@ function emit_atomics (
4036
4077
return false ;
4037
4078
}
4038
4079
4039
- function emit_switch ( builder : WasmBuilder , ip : MintOpcodePtr ) : boolean {
4040
- append_bailout ( builder , ip , BailoutReason . Switch ) ;
4080
+ function emit_switch ( builder : WasmBuilder , ip : MintOpcodePtr , exitOpcodeCounter : number ) : boolean {
4081
+ const lengthU16 = getOpcodeLengthU16 ( ip , MintOpcode . MINT_SWITCH ) ,
4082
+ table = decodeSwitch ( ip ) ;
4083
+ let failed = false ;
4084
+
4085
+ if ( table . length > builder . options . maxSwitchSize ) {
4086
+ failed = true ;
4087
+ } else {
4088
+ // Record all the switch's forward branch targets.
4089
+ // If it contains any back branches they will bailout at runtime.
4090
+ for ( const target of table ) {
4091
+ if ( target > ip )
4092
+ builder . branchTargets . add ( target ) ;
4093
+ }
4094
+ }
4095
+
4096
+ if ( failed ) {
4097
+ modifyCounter ( JiterpCounter . SwitchTargetsFailed , table . length ) ;
4098
+ append_bailout ( builder , ip , BailoutReason . SwitchSize ) ;
4099
+ return true ;
4100
+ }
4101
+
4102
+ const fallthrough = < any > ip + ( lengthU16 * 2 ) ;
4103
+ builder . branchTargets . add ( fallthrough ) ;
4104
+
4105
+ // Jump table needs a block so it can `br 0` for missing targets
4106
+ builder . block ( ) ;
4107
+ // Load selector
4108
+ append_ldloc ( builder , getArgU16 ( ip , 1 ) , WasmOpcode . i32_load ) ;
4109
+ // Dispatch
4110
+ builder . cfg . jumpTable ( table , fallthrough ) ;
4111
+ // Missing target
4112
+ builder . endBlock ( ) ;
4113
+ append_exit ( builder , ip , exitOpcodeCounter , BailoutReason . SwitchTarget ) ;
4041
4114
return true ;
4042
4115
}
0 commit comments