@@ -3247,10 +3247,25 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
32473247
32483248 // Take `count` instructions, advancing backwards, but returning
32493249 // instructions in their original order (and decoded to `Inst`s).
3250- let mut try_rev_take = |count| {
3250+ let mut try_rev_take = |count : isize | {
3251+ // HACK(eddyb) this is extremely silly but it's easier to do
3252+ // this than to rely on `Iterator::peekable` or anything else,
3253+ // lower down this file, without messing up the state here.
3254+ let is_peek = count < 0 ;
3255+ let count = count. unsigned_abs ( ) ;
3256+
3257+ let mut non_debug_insts_for_peek = is_peek. then ( || non_debug_insts. clone ( ) ) ;
3258+ let non_debug_insts = non_debug_insts_for_peek
3259+ . as_mut ( )
3260+ . unwrap_or ( & mut non_debug_insts) ;
3261+
3262+ // FIXME(eddyb) there might be an easier way to do this,
3263+ // e.g. maybe `map_while` + post-`collect` length check?
32513264 let maybe_rev_insts = ( 0 ..count) . map ( |_| {
32523265 let ( i, inst) = non_debug_insts. next_back ( ) ?;
3253- taken_inst_idx_range. start . set ( i) ;
3266+ if !is_peek {
3267+ taken_inst_idx_range. start . set ( i) ;
3268+ }
32543269
32553270 // HACK(eddyb) avoid the logic below that assumes only ID operands
32563271 if inst. class . opcode == Op :: CompositeExtract {
@@ -3510,15 +3525,21 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
35103525 }
35113526 } ) . collect :: < Result < _ , _ > > ( ) ?;
35123527
3513- let rev_ref_arg_ids_with_ty_and_spec = ( rev_copies_to_rt_args_array_src_ptrs
3514- . into_iter ( ) )
3515- . map ( |copy_to_rt_args_array_src_ptr| {
3528+ // HACK(eddyb) sometimes there is an extra tuple of refs,
3529+ // nowadays, but MIR opts mean it's not always guaranteed,
3530+ // hopefully it's always uniform across all the arguments.
3531+ let mut maybe_ref_args_tmp_slot_ptr = None ;
3532+
3533+ let rev_maybe_ref_arg_ids_with_ty_and_spec = ( ( 0 ..rt_args_count)
3534+ . rev ( )
3535+ . zip_eq ( rev_copies_to_rt_args_array_src_ptrs) )
3536+ . map ( |( rt_arg_idx, copy_to_rt_args_array_src_ptr) | {
35163537 let rt_arg_new_call_insts = try_rev_take ( 4 ) . ok_or_else ( || {
35173538 FormatArgsNotRecognized (
35183539 "fmt::rt::Argument::new call: ran out of instructions" . into ( ) ,
35193540 )
35203541 } ) ?;
3521- match rt_arg_new_call_insts[ ..] {
3542+ let ( ref_arg_id , ty , spec ) = match rt_arg_new_call_insts[ ..] {
35223543 [
35233544 Inst :: Call ( call_ret_id, callee_id, ref call_args) ,
35243545 Inst :: InBoundsAccessChain ( tmp_slot_field_ptr, tmp_slot_ptr, 0 ) ,
@@ -3542,36 +3563,134 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
35423563 FormatArgsNotRecognized ( format ! (
35433564 "fmt::rt::Argument::new call sequence ({rt_arg_new_call_insts:?})"
35443565 ) )
3545- } )
3566+ } ) ?;
3567+
3568+ // HACK(eddyb) `0` (an invalid ID) is later used as a
3569+ // placeholder (see also `maybe_ref_args_tmp_slot_ptr`).
3570+ assert_ne ! ( ref_arg_id, 0 ) ;
3571+
3572+ // HACK(eddyb) `try_rev_take(-2)` is "peeking", not taking.
3573+ let maybe_ref_args_tuple_load_insts = try_rev_take ( -2 ) ;
3574+ let maybe_ref_arg_id = match maybe_ref_args_tuple_load_insts. as_deref ( ) {
3575+ Some (
3576+ & [
3577+ Inst :: InBoundsAccessChain ( field_ptr, base_ptr, field_idx) ,
3578+ Inst :: Load ( ld_val, ld_src_ptr) ,
3579+ ] ,
3580+ ) if maybe_ref_args_tmp_slot_ptr
3581+ . is_none_or ( |expected| base_ptr == expected)
3582+ && field_idx as usize == rt_arg_idx
3583+ && ( ld_val, ld_src_ptr) == ( ref_arg_id, field_ptr) =>
3584+ {
3585+ // HACK(eddyb) consume the peeked instructions.
3586+ try_rev_take ( 2 ) . unwrap ( ) ;
3587+
3588+ maybe_ref_args_tmp_slot_ptr = Some ( base_ptr) ;
3589+
3590+ // HACK(eddyb) using `0` (an invalid ID) as a
3591+ // placeholder to require further processing.
3592+ 0
3593+ }
3594+ _ => ref_arg_id,
3595+ } ;
3596+
3597+ Ok ( ( maybe_ref_arg_id, ty, spec) )
35463598 } )
35473599 . collect :: < Result < _ , _ > > ( ) ?;
35483600
35493601 decoded_format_args. ref_arg_ids_with_ty_and_spec =
3550- rev_ref_arg_ids_with_ty_and_spec ;
3602+ rev_maybe_ref_arg_ids_with_ty_and_spec ;
35513603 decoded_format_args. ref_arg_ids_with_ty_and_spec . reverse ( ) ;
3604+
3605+ // HACK(eddyb) see above for context regarding the use of
3606+ // `0` as placeholders and `maybe_ref_args_tmp_slot_ptr`.
3607+ if let Some ( ref_args_tmp_slot_ptr) = maybe_ref_args_tmp_slot_ptr {
3608+ for ( rt_arg_idx, ( maybe_ref_arg_id, ..) ) in decoded_format_args
3609+ . ref_arg_ids_with_ty_and_spec
3610+ . iter_mut ( )
3611+ . enumerate ( )
3612+ . rev ( )
3613+ {
3614+ if * maybe_ref_arg_id == 0 {
3615+ let ref_arg_store_insts = try_rev_take ( 2 ) . ok_or_else ( || {
3616+ FormatArgsNotRecognized (
3617+ "fmt::rt::Argument::new argument store: ran out of instructions" . into ( ) ,
3618+ )
3619+ } ) ?;
3620+
3621+ * maybe_ref_arg_id = match ref_arg_store_insts[ ..] {
3622+ [
3623+ Inst :: InBoundsAccessChain ( field_ptr, base_ptr, field_idx) ,
3624+ Inst :: Store ( st_dst_ptr, st_val) ,
3625+ ] if base_ptr == ref_args_tmp_slot_ptr
3626+ && field_idx as usize == rt_arg_idx
3627+ && st_dst_ptr == field_ptr =>
3628+ {
3629+ Some ( st_val)
3630+ }
3631+ _ => None ,
3632+ }
3633+ . ok_or_else ( || {
3634+ FormatArgsNotRecognized ( format ! (
3635+ "fmt::rt::Argument::new argument store sequence ({ref_arg_store_insts:?})"
3636+ ) )
3637+ } ) ?;
3638+ }
3639+ }
3640+ }
35523641 }
35533642
35543643 // If the `pieces: &[&str]` slice needs a bitcast, it'll be here.
3555- let pieces_slice_ptr_id = match try_rev_take ( 1 ) . as_deref ( ) {
3556- Some ( & [ Inst :: Bitcast ( out_id, in_id) ] ) if out_id == pieces_slice_ptr_id => in_id,
3644+ // HACK(eddyb) `try_rev_take(-1)` is "peeking", not taking.
3645+ let pieces_slice_ptr_id = match try_rev_take ( -1 ) . as_deref ( ) {
3646+ Some ( & [ Inst :: Bitcast ( out_id, in_id) ] ) if out_id == pieces_slice_ptr_id => {
3647+ // HACK(eddyb) consume the peeked instructions.
3648+ try_rev_take ( 1 ) . unwrap ( ) ;
3649+
3650+ in_id
3651+ }
35573652 _ => pieces_slice_ptr_id,
35583653 } ;
35593654 decoded_format_args. const_pieces =
3560- const_slice_as_elem_ids ( pieces_slice_ptr_id, pieces_len) . and_then (
3561- |piece_ids| {
3562- piece_ids
3563- . iter ( )
3564- . map ( |& piece_id| {
3565- match self . builder . lookup_const_by_id ( piece_id) ? {
3566- SpirvConst :: Composite ( piece) => {
3567- const_str_as_utf8 ( piece. try_into ( ) . ok ( ) ?)
3568- }
3569- _ => None ,
3655+ match const_slice_as_elem_ids ( pieces_slice_ptr_id, pieces_len) {
3656+ Some ( piece_ids) => piece_ids
3657+ . iter ( )
3658+ . map (
3659+ |& piece_id| match self . builder . lookup_const_by_id ( piece_id) ? {
3660+ SpirvConst :: Composite ( piece) => {
3661+ const_str_as_utf8 ( piece. try_into ( ) . ok ( ) ?)
35703662 }
3571- } )
3572- . collect :: < Option < _ > > ( )
3663+ _ => None ,
3664+ } ,
3665+ )
3666+ . collect :: < Option < _ > > ( ) ,
3667+ // HACK(eddyb) minor upstream blunder results in at
3668+ // least one instance of a runtime `[&str; 1]` array,
3669+ // see also this comment left on the responsible PR:
3670+ // https://github.com/rust-lang/rust/pull/129658#discussion_r2181834781
3671+ // HACK(eddyb) `try_rev_take(-4)` is "peeking", not taking.
3672+ None if pieces_len == 1 => match try_rev_take ( -4 ) . as_deref ( ) {
3673+ Some (
3674+ & [
3675+ Inst :: InBoundsAccessChain2 ( field0_ptr, array_ptr_0, 0 , 0 ) ,
3676+ Inst :: Store ( st0_dst_ptr, st0_val) ,
3677+ Inst :: InBoundsAccessChain2 ( field1_ptr, array_ptr_1, 0 , 1 ) ,
3678+ Inst :: Store ( st1_dst_ptr, st1_val) ,
3679+ ] ,
3680+ ) if [ array_ptr_0, array_ptr_1] == [ pieces_slice_ptr_id; 2 ]
3681+ && st0_dst_ptr == field0_ptr
3682+ && st1_dst_ptr == field1_ptr =>
3683+ {
3684+ // HACK(eddyb) consume the peeked instructions.
3685+ try_rev_take ( 4 ) . unwrap ( ) ;
3686+
3687+ const_str_as_utf8 ( & [ st0_val, st1_val] )
3688+ . map ( |s| [ s] . into_iter ( ) . collect ( ) )
3689+ }
3690+ _ => None ,
35733691 } ,
3574- ) ;
3692+ None => None ,
3693+ } ;
35753694
35763695 // Keep all instructions up to (but not including) the last one
35773696 // confirmed above to be the first instruction of `format_args!`.
0 commit comments