Skip to content

Commit 1693d02

Browse files
committed
Handle nightly panic format_args lowering and unsized const reification
1 parent 131d5ac commit 1693d02

3 files changed

Lines changed: 263 additions & 137 deletions

File tree

crates/rustc_codegen_spirv/src/builder/format_args_decompiler.rs

Lines changed: 202 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ impl<'tcx> DecodedFormatArgs<'tcx> {
183183
enum Inst<ID> {
184184
Bitcast(ID, ID),
185185
CompositeExtract(ID, ID, u32),
186+
CompositeInsert(ID, ID, ID, u32),
186187
InBoundsAccessChain(ID, ID, u32),
187188
InBoundsAccessChain2(ID, ID, u32, u32),
188189
Store(ID, ID),
@@ -229,6 +230,18 @@ impl<'tcx> DecodedFormatArgs<'tcx> {
229230
{
230231
return Some(Inst::CompositeExtract(r, x, i));
231232
}
233+
if inst.class.opcode == Op::CompositeInsert
234+
&& let (
235+
Some(r),
236+
&[
237+
Operand::IdRef(inserted),
238+
Operand::IdRef(base),
239+
Operand::LiteralBit32(i),
240+
],
241+
) = (inst.result_id, &inst.operands[..])
242+
{
243+
return Some(Inst::CompositeInsert(r, inserted, base, i));
244+
}
232245

233246
// HACK(eddyb) all instructions accepted below
234247
// are expected to take no more than 4 operands,
@@ -289,6 +302,20 @@ impl<'tcx> DecodedFormatArgs<'tcx> {
289302
Some(&[Inst::Call(call_ret_id, _, _)]) if call_ret_id == format_args_id
290303
) {
291304
1
305+
} else if matches!(
306+
try_rev_take(-5).as_deref(),
307+
Some(&[
308+
Inst::Call(call_ret_id, _, _),
309+
Inst::CompositeExtract(extracted0, from0, 0),
310+
Inst::CompositeExtract(extracted1, from1, 1),
311+
Inst::CompositeInsert(inserted0, value0, _, 0),
312+
Inst::CompositeInsert(inserted1, value1, inserted0_prev, 1),
313+
]) if [from0, from1] == [call_ret_id; 2]
314+
&& [value0, value1] == [extracted0, extracted1]
315+
&& inserted0 == inserted0_prev
316+
&& inserted1 == format_args_id
317+
) {
318+
5
292319
} else {
293320
// HACK(eddyb) gather context for new call patterns before bailing.
294321
let mut insts = SmallVec::<[Inst<Word>; 32]>::new();
@@ -341,6 +368,27 @@ impl<'tcx> DecodedFormatArgs<'tcx> {
341368

342369
(call_args, pieces_len, rt_args_count)
343370
}
371+
[
372+
Inst::Call(call_ret_id, callee_id, ref call_args),
373+
Inst::CompositeExtract(extracted0, from0, 0),
374+
Inst::CompositeExtract(extracted1, from1, 1),
375+
Inst::CompositeInsert(inserted0, value0, _, 0),
376+
Inst::CompositeInsert(inserted1, value1, inserted0_prev, 1),
377+
] if [from0, from1] == [call_ret_id; 2]
378+
&& [value0, value1] == [extracted0, extracted1]
379+
&& inserted0 == inserted0_prev
380+
&& inserted1 == format_args_id =>
381+
{
382+
let Some(&(pieces_len, rt_args_count)) =
383+
cx.fmt_args_new_fn_ids.borrow().get(&callee_id)
384+
else {
385+
return Err(FormatArgsNotRecognized(
386+
"fmt::Arguments::new callee not registered".into(),
387+
));
388+
};
389+
390+
(call_args, pieces_len, rt_args_count)
391+
}
344392
_ => {
345393
// HACK(eddyb) this gathers more context before reporting.
346394
let mut insts = fmt_args_new_call_insts;
@@ -358,88 +406,112 @@ impl<'tcx> DecodedFormatArgs<'tcx> {
358406
)));
359407
}
360408
};
361-
let ((pieces_slice_ptr_id, pieces_len), (rt_args_slice_ptr_id, rt_args_count)) =
362-
match call_args[..] {
363-
// `<core::fmt::Arguments>::new_v1_formatted`
364-
//
365-
// HACK(eddyb) this isn't fully supported,
366-
// as that would require digging into unstable
367-
// internals of `core::fmt::rt::Placeholder`s,
368-
// but the whole call still needs to be removed,
369-
// and both const str pieces and runtime args
370-
// can still be printed (even if in jankier way).
371-
[
372-
pieces_slice_ptr_id,
373-
pieces_len_id,
374-
rt_args_slice_ptr_id,
375-
rt_args_len_id,
376-
fmt_placeholders_slice_ptr_id,
377-
fmt_placeholders_len_id,
378-
] if (pieces_len, rt_args_count) == (!0, !0) => {
379-
let [pieces_len, rt_args_len, fmt_placeholders_len] =
380-
match [pieces_len_id, rt_args_len_id, fmt_placeholders_len_id]
381-
.map(const_u32_as_usize)
382-
{
383-
[Some(a), Some(b), Some(c)] => [a, b, c],
384-
_ => {
385-
return Err(FormatArgsNotRecognized(
386-
"fmt::Arguments::new_v1_formatted \
409+
const FMT_ARGS_FROM_STR_PIECES_LEN: usize = !1;
410+
enum PiecesSource {
411+
Slice { ptr_id: Word, len: usize },
412+
DirectConstStr([Word; 2]),
413+
}
414+
let (pieces_source, (rt_args_slice_ptr_id, rt_args_count)) = match call_args[..] {
415+
// `<core::fmt::Arguments>::new_v1_formatted`
416+
//
417+
// HACK(eddyb) this isn't fully supported,
418+
// as that would require digging into unstable
419+
// internals of `core::fmt::rt::Placeholder`s,
420+
// but the whole call still needs to be removed,
421+
// and both const str pieces and runtime args
422+
// can still be printed (even if in jankier way).
423+
[
424+
pieces_slice_ptr_id,
425+
pieces_len_id,
426+
rt_args_slice_ptr_id,
427+
rt_args_len_id,
428+
fmt_placeholders_slice_ptr_id,
429+
fmt_placeholders_len_id,
430+
] if (pieces_len, rt_args_count) == (!0, !0) => {
431+
let [pieces_len, rt_args_len, fmt_placeholders_len] =
432+
match [pieces_len_id, rt_args_len_id, fmt_placeholders_len_id]
433+
.map(const_u32_as_usize)
434+
{
435+
[Some(a), Some(b), Some(c)] => [a, b, c],
436+
_ => {
437+
return Err(FormatArgsNotRecognized(
438+
"fmt::Arguments::new_v1_formatted \
387439
with dynamic lengths"
388-
.into(),
389-
));
390-
}
391-
};
440+
.into(),
441+
));
442+
}
443+
};
392444

393-
let prepare_args_insts = try_rev_take(2).ok_or_else(|| {
394-
FormatArgsNotRecognized(
395-
"fmt::Arguments::new_v1_formatted call: ran out of instructions".into(),
396-
)
397-
})?;
398-
let (rt_args_slice_ptr_id, _fmt_placeholders_slice_ptr_id) =
399-
match prepare_args_insts[..] {
400-
[
401-
Inst::Bitcast(rt_args_cast_out_id, rt_args_cast_in_id),
402-
Inst::Bitcast(placeholders_cast_out_id, placeholders_cast_in_id),
403-
] if rt_args_cast_out_id == rt_args_slice_ptr_id
404-
&& placeholders_cast_out_id == fmt_placeholders_slice_ptr_id =>
405-
{
406-
(rt_args_cast_in_id, placeholders_cast_in_id)
407-
}
408-
_ => {
409-
let mut insts = prepare_args_insts;
410-
insts.extend(fmt_args_new_call_insts);
411-
return Err(FormatArgsNotRecognized(format!(
412-
"fmt::Arguments::new_v1_formatted call sequence ({insts:?})",
413-
)));
414-
}
415-
};
445+
let prepare_args_insts = try_rev_take(2).ok_or_else(|| {
446+
FormatArgsNotRecognized(
447+
"fmt::Arguments::new_v1_formatted call: ran out of instructions".into(),
448+
)
449+
})?;
450+
let (rt_args_slice_ptr_id, _fmt_placeholders_slice_ptr_id) =
451+
match prepare_args_insts[..] {
452+
[
453+
Inst::Bitcast(rt_args_cast_out_id, rt_args_cast_in_id),
454+
Inst::Bitcast(placeholders_cast_out_id, placeholders_cast_in_id),
455+
] if rt_args_cast_out_id == rt_args_slice_ptr_id
456+
&& placeholders_cast_out_id == fmt_placeholders_slice_ptr_id =>
457+
{
458+
(rt_args_cast_in_id, placeholders_cast_in_id)
459+
}
460+
_ => {
461+
let mut insts = prepare_args_insts;
462+
insts.extend(fmt_args_new_call_insts);
463+
return Err(FormatArgsNotRecognized(format!(
464+
"fmt::Arguments::new_v1_formatted call sequence ({insts:?})",
465+
)));
466+
}
467+
};
416468

417-
decoded_format_args.has_unknown_fmt_placeholder_to_args_mapping =
418-
Some(fmt_placeholders_len);
469+
decoded_format_args.has_unknown_fmt_placeholder_to_args_mapping =
470+
Some(fmt_placeholders_len);
419471

420-
(
421-
(pieces_slice_ptr_id, pieces_len),
422-
(Some(rt_args_slice_ptr_id), rt_args_len),
423-
)
424-
}
472+
(
473+
PiecesSource::Slice {
474+
ptr_id: pieces_slice_ptr_id,
475+
len: pieces_len,
476+
},
477+
(Some(rt_args_slice_ptr_id), rt_args_len),
478+
)
479+
}
425480

426-
// `<core::fmt::Arguments>::new_v1`
427-
[pieces_slice_ptr_id, rt_args_slice_ptr_id] => (
428-
(pieces_slice_ptr_id, pieces_len),
429-
(Some(rt_args_slice_ptr_id), rt_args_count),
430-
),
481+
// `<core::fmt::Arguments>::from_str`
482+
[str_ptr_id, str_len_id]
483+
if (pieces_len, rt_args_count) == (FMT_ARGS_FROM_STR_PIECES_LEN, 0) =>
484+
{
485+
(
486+
PiecesSource::DirectConstStr([str_ptr_id, str_len_id]),
487+
(None, 0),
488+
)
489+
}
431490

432-
// `<core::fmt::Arguments>::new_const`
433-
[pieces_slice_ptr_id] if rt_args_count == 0 => {
434-
((pieces_slice_ptr_id, pieces_len), (None, rt_args_count))
435-
}
491+
// `<core::fmt::Arguments>::new_v1`
492+
[pieces_slice_ptr_id, rt_args_slice_ptr_id] => (
493+
PiecesSource::Slice {
494+
ptr_id: pieces_slice_ptr_id,
495+
len: pieces_len,
496+
},
497+
(Some(rt_args_slice_ptr_id), rt_args_count),
498+
),
436499

437-
_ => {
438-
return Err(FormatArgsNotRecognized(
439-
"fmt::Arguments::new call args".into(),
440-
));
441-
}
442-
};
500+
// `<core::fmt::Arguments>::new_const`
501+
[pieces_slice_ptr_id] if rt_args_count == 0 => (
502+
PiecesSource::Slice {
503+
ptr_id: pieces_slice_ptr_id,
504+
len: pieces_len,
505+
},
506+
(None, rt_args_count),
507+
),
508+
509+
_ => {
510+
return Err(FormatArgsNotRecognized(
511+
"fmt::Arguments::new call args".into(),
512+
));
513+
}
514+
};
443515

444516
// HACK(eddyb) this is the worst part: if we do have runtime
445517
// arguments (from e.g. new `assert!`s being added to `core`),
@@ -626,52 +698,65 @@ impl<'tcx> DecodedFormatArgs<'tcx> {
626698
}
627699
}
628700

629-
// If the `pieces: &[&str]` slice needs a bitcast, it'll be here.
630-
// HACK(eddyb) `try_rev_take(-1)` is "peeking", not taking.
631-
let pieces_slice_ptr_id = match try_rev_take(-1).as_deref() {
632-
Some(&[Inst::Bitcast(out_id, in_id)]) if out_id == pieces_slice_ptr_id => {
633-
// HACK(eddyb) consume the peeked instructions.
634-
try_rev_take(1).unwrap();
635-
636-
in_id
701+
decoded_format_args.const_pieces = match pieces_source {
702+
PiecesSource::DirectConstStr(str_ref) => {
703+
const_str_as_utf8(&str_ref).map(|s| [s].into_iter().collect())
637704
}
638-
_ => pieces_slice_ptr_id,
639-
};
640-
decoded_format_args.const_pieces =
641-
match const_slice_as_elem_ids(pieces_slice_ptr_id, pieces_len) {
642-
Some(piece_ids) => piece_ids
643-
.iter()
644-
.map(|&piece_id| match cx.builder.lookup_const_by_id(piece_id)? {
645-
SpirvConst::Composite(piece) => const_str_as_utf8(piece.try_into().ok()?),
646-
_ => None,
647-
})
648-
.collect::<Option<_>>(),
649-
// HACK(eddyb) minor upstream blunder results in at
650-
// least one instance of a runtime `[&str; 1]` array,
651-
// see also this comment left on the responsible PR:
652-
// https://github.com/rust-lang/rust/pull/129658#discussion_r2181834781
653-
// HACK(eddyb) `try_rev_take(-4)` is "peeking", not taking.
654-
None if pieces_len == 1 => match try_rev_take(-4).as_deref() {
655-
Some(
656-
&[
657-
Inst::InBoundsAccessChain2(field0_ptr, array_ptr_0, 0, 0),
658-
Inst::Store(st0_dst_ptr, st0_val),
659-
Inst::InBoundsAccessChain2(field1_ptr, array_ptr_1, 0, 1),
660-
Inst::Store(st1_dst_ptr, st1_val),
661-
],
662-
) if [array_ptr_0, array_ptr_1] == [pieces_slice_ptr_id; 2]
663-
&& st0_dst_ptr == field0_ptr
664-
&& st1_dst_ptr == field1_ptr =>
665-
{
705+
PiecesSource::Slice {
706+
ptr_id: pieces_slice_ptr_id,
707+
len: pieces_len,
708+
} => {
709+
// If the `pieces: &[&str]` slice needs a bitcast, it'll be here.
710+
// HACK(eddyb) `try_rev_take(-1)` is "peeking", not taking.
711+
let pieces_slice_ptr_id = match try_rev_take(-1).as_deref() {
712+
Some(&[Inst::Bitcast(out_id, in_id)]) if out_id == pieces_slice_ptr_id => {
666713
// HACK(eddyb) consume the peeked instructions.
667-
try_rev_take(4).unwrap();
714+
try_rev_take(1).unwrap();
668715

669-
const_str_as_utf8(&[st0_val, st1_val]).map(|s| [s].into_iter().collect())
716+
in_id
670717
}
671-
_ => None,
672-
},
673-
None => None,
674-
};
718+
_ => pieces_slice_ptr_id,
719+
};
720+
721+
match const_slice_as_elem_ids(pieces_slice_ptr_id, pieces_len) {
722+
Some(piece_ids) => piece_ids
723+
.iter()
724+
.map(|&piece_id| match cx.builder.lookup_const_by_id(piece_id)? {
725+
SpirvConst::Composite(piece) => {
726+
const_str_as_utf8(piece.try_into().ok()?)
727+
}
728+
_ => None,
729+
})
730+
.collect::<Option<_>>(),
731+
// HACK(eddyb) minor upstream blunder results in at
732+
// least one instance of a runtime `[&str; 1]` array,
733+
// see also this comment left on the responsible PR:
734+
// https://github.com/rust-lang/rust/pull/129658#discussion_r2181834781
735+
// HACK(eddyb) `try_rev_take(-4)` is "peeking", not taking.
736+
None if pieces_len == 1 => match try_rev_take(-4).as_deref() {
737+
Some(
738+
&[
739+
Inst::InBoundsAccessChain2(field0_ptr, array_ptr_0, 0, 0),
740+
Inst::Store(st0_dst_ptr, st0_val),
741+
Inst::InBoundsAccessChain2(field1_ptr, array_ptr_1, 0, 1),
742+
Inst::Store(st1_dst_ptr, st1_val),
743+
],
744+
) if [array_ptr_0, array_ptr_1] == [pieces_slice_ptr_id; 2]
745+
&& st0_dst_ptr == field0_ptr
746+
&& st1_dst_ptr == field1_ptr =>
747+
{
748+
// HACK(eddyb) consume the peeked instructions.
749+
try_rev_take(4).unwrap();
750+
751+
const_str_as_utf8(&[st0_val, st1_val])
752+
.map(|s| [s].into_iter().collect())
753+
}
754+
_ => None,
755+
},
756+
None => None,
757+
}
758+
}
759+
};
675760

676761
// Keep all instructions up to (but not including) the last one
677762
// confirmed above to be the first instruction of `format_args!`.

0 commit comments

Comments
 (0)