@@ -184,6 +184,34 @@ function getOpcodeLengthU16 (ip: MintOpcodePtr, opcode: MintOpcode) {
184184 }
185185}
186186
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+
187215// Perform a quick scan through the opcodes potentially in this trace to build a table of
188216// backwards branch targets, compatible with the layout of the old one that was generated in C.
189217// We do this here to match the exact way that the jiterp calculates branch targets, since
@@ -205,47 +233,60 @@ export function generateBackwardBranchTable (
205233 const opcode = < MintOpcode > getU16 ( ip ) ;
206234 const opLengthU16 = getOpcodeLengthU16 ( ip , opcode ) ;
207235
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+ }
222255
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` ) ;
232260 break ;
233261 }
234262
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+ }
240274
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+ }
249290 }
250291
251292 ip += < any > ( opLengthU16 * 2 ) ;
@@ -399,7 +440,7 @@ export function generateWasmBody (
399440
400441 switch ( opcode ) {
401442 case MintOpcode . MINT_SWITCH : {
402- if ( ! emit_switch ( builder , ip ) )
443+ if ( ! emit_switch ( builder , ip , exitOpcodeCounter ) )
403444 ip = abort ;
404445 break ;
405446 }
@@ -4036,7 +4077,39 @@ function emit_atomics (
40364077 return false ;
40374078}
40384079
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 ) ;
40414114 return true ;
40424115}
0 commit comments