Skip to content

Commit de24a89

Browse files
committed
builder: handle fmt::Arguments::new_v1_formatted (for complex format_args!).
1 parent 7e11b40 commit de24a89

File tree

2 files changed

+204
-50
lines changed

2 files changed

+204
-50
lines changed

crates/rustc_codegen_spirv/src/builder/builder_methods.rs

Lines changed: 198 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::builder_spirv::{BuilderCursor, SpirvConst, SpirvValue, SpirvValueExt,
77
use crate::codegen_cx::CodegenCx;
88
use crate::custom_insts::{CustomInst, CustomOp};
99
use crate::spirv_type::SpirvType;
10-
use itertools::Itertools;
10+
use itertools::{Either, Itertools};
1111
use rspirv::dr::{InsertPoint, Instruction, Operand};
1212
use rspirv::spirv::{Capability, MemoryModel, MemorySemantics, Op, Scope, StorageClass, Word};
1313
use rustc_apfloat::{Float, Round, Status, ieee};
@@ -3230,6 +3230,16 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
32303230
/// * `&a` with `typeof a` and ' ',
32313231
/// * `&b` with `typeof b` and 'x'
32323232
ref_arg_ids_with_ty_and_spec: SmallVec<[(Word, Ty<'tcx>, char); 2]>,
3233+
3234+
/// If `fmt::Arguments::new_v1_formatted` was used, this holds
3235+
/// the length of the `&[fmt::rt::Placeholder]` slice, which
3236+
/// currently cannot be directly supported, and therefore even
3237+
/// if all of `ref_arg_ids_with_ty_and_spec` are printable,
3238+
/// a much jankier fallback still has to be used, as it it were:
3239+
///
3240+
/// `format!("a{{0}}b{{1}}c\n with {{…}} from: {}, {}", x, y)`
3241+
/// (w/ `const_pieces = ["a", "b", "c"]` & `ref_args = [&x, &y]`).
3242+
has_unknown_fmt_placeholder_to_args_mapping: Option<usize>,
32333243
}
32343244
struct FormatArgsNotRecognized(String);
32353245

@@ -3455,6 +3465,105 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
34553465
};
34563466

34573467
match call_args[..] {
3468+
// `<core::fmt::Arguments>::new_v1_formatted`
3469+
//
3470+
// HACK(eddyb) this isn't fully supported,
3471+
// as that would require digging into unstable
3472+
// internals of `core::fmt::rt::Placeholder`s,
3473+
// but the whole call still needs to be removed,
3474+
// and both const str pieces and runtime args
3475+
// can still be printed (even if in jankier way).
3476+
[
3477+
pieces_slice_ptr_id,
3478+
pieces_len_id,
3479+
rt_args_slice_ptr_id,
3480+
rt_args_len_id,
3481+
_fmt_placeholders_slice_ptr_id,
3482+
fmt_placeholders_len_id,
3483+
] if (pieces_len, rt_args_count) == (!0, !0) => {
3484+
let [pieces_len, rt_args_len, fmt_placeholders_len] = match [
3485+
pieces_len_id,
3486+
rt_args_len_id,
3487+
fmt_placeholders_len_id,
3488+
]
3489+
.map(const_u32_as_usize)
3490+
{
3491+
[Some(a), Some(b), Some(c)] => [a, b, c],
3492+
_ => {
3493+
return Err(FormatArgsNotRecognized(
3494+
"fmt::Arguments::new_v1_formatted \
3495+
with dynamic lengths"
3496+
.into(),
3497+
));
3498+
}
3499+
};
3500+
3501+
// FIXME(eddyb) simplify the logic below after
3502+
// https://github.com/rust-lang/rust/pull/139131
3503+
// (~1.88) as it makes `&[rt::Placeholder]`
3504+
// constant (via promotion to 'static).
3505+
3506+
// HACK(eddyb) this accounts for all of these:
3507+
// - `rt::Placeholder` copies into array: 2 insts each
3508+
// - `rt::UnsafeArg::new()` call: 1 inst
3509+
// - runtime args array->slice ptr cast: 1 inst
3510+
// - placeholders array->slice ptr cast: 1 inst
3511+
let extra_insts = try_rev_take(3 + fmt_placeholders_len * 2).ok_or_else(|| {
3512+
FormatArgsNotRecognized(
3513+
"fmt::Arguments::new_v1_formatted call: ran out of instructions".into(),
3514+
)
3515+
})?;
3516+
let rt_args_slice_ptr_id = match extra_insts[..] {
3517+
[.., Inst::Bitcast(out_id, in_id), Inst::Bitcast(..)]
3518+
if out_id == rt_args_slice_ptr_id =>
3519+
{
3520+
in_id
3521+
}
3522+
_ => {
3523+
let mut insts = extra_insts;
3524+
insts.extend(fmt_args_new_call_insts);
3525+
return Err(FormatArgsNotRecognized(format!(
3526+
"fmt::Arguments::new_v1_formatted call sequence ({insts:?})",
3527+
)));
3528+
}
3529+
};
3530+
3531+
// HACK(eddyb) even worse, each call made to
3532+
// `rt::Placeholder::new(...)` takes anywhere
3533+
// between 16 and 20 instructions each, due
3534+
// to `enum`s represented as scalar pairs.
3535+
for _ in 0..fmt_placeholders_len {
3536+
try_rev_take(16).and_then(|insts| {
3537+
let scalar_pairs_with_used_2nd_field = insts
3538+
.iter()
3539+
.take_while(|inst| {
3540+
!matches!(inst, Inst::Load(..))
3541+
})
3542+
.filter(|inst| {
3543+
matches!(inst, Inst::InBoundsAccessChain(.., 1))
3544+
})
3545+
.count();
3546+
try_rev_take(scalar_pairs_with_used_2nd_field * 2)?;
3547+
Some(())
3548+
})
3549+
.ok_or_else(|| {
3550+
FormatArgsNotRecognized(
3551+
"fmt::rt::Placeholder::new call: ran out of instructions"
3552+
.into(),
3553+
)
3554+
})?;
3555+
}
3556+
3557+
decoded_format_args
3558+
.has_unknown_fmt_placeholder_to_args_mapping =
3559+
Some(fmt_placeholders_len);
3560+
3561+
(
3562+
(pieces_slice_ptr_id, pieces_len),
3563+
(Some(rt_args_slice_ptr_id), rt_args_len),
3564+
)
3565+
}
3566+
34583567
// `<core::fmt::Arguments>::new_v1`
34593568
[pieces_slice_ptr_id, rt_args_slice_ptr_id] => (
34603569
(pieces_slice_ptr_id, pieces_len),
@@ -3608,58 +3717,97 @@ impl<'a, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'tcx> {
36083717
let mut debug_printf_args = SmallVec::<[_; 2]>::new();
36093718
let message = match try_decode_and_remove_format_args() {
36103719
Ok(DecodedFormatArgs {
3611-
const_pieces,
3720+
const_pieces: None, ..
3721+
}) => "<unknown message>".into(),
3722+
3723+
Ok(DecodedFormatArgs {
3724+
const_pieces: Some(const_pieces),
36123725
ref_arg_ids_with_ty_and_spec,
3726+
has_unknown_fmt_placeholder_to_args_mapping,
36133727
}) => {
3614-
match const_pieces {
3615-
Some(const_pieces) => {
3616-
const_pieces
3617-
.into_iter()
3618-
.map(|s| Cow::Owned(s.replace('%', "%%")))
3619-
.interleave(ref_arg_ids_with_ty_and_spec.iter().map(
3620-
|&(ref_id, ty, spec)| {
3621-
use rustc_target::abi::{
3622-
Float::*, Integer::*, Primitive::*,
3623-
};
3624-
3625-
let layout = self.layout_of(ty);
3626-
3627-
let scalar = match layout.backend_repr {
3628-
BackendRepr::Scalar(scalar) => Some(scalar.primitive()),
3629-
_ => None,
3630-
};
3631-
let debug_printf_fmt = match (spec, scalar) {
3632-
// FIXME(eddyb) support more of these,
3633-
// potentially recursing to print ADTs.
3634-
(' ' | '?', Some(Int(I32, false))) => "%u",
3635-
('x', Some(Int(I32, false))) => "%x",
3636-
(' ' | '?', Some(Int(I32, true))) => "%i",
3637-
(' ' | '?', Some(Float(F32))) => "%f",
3638-
3639-
_ => "",
3640-
};
3641-
3642-
if debug_printf_fmt.is_empty() {
3643-
return Cow::Owned(
3644-
format!("{{/* unprintable {ty} */:{spec}}}")
3645-
.replace('%', "%%"),
3646-
);
3647-
}
3728+
let args = ref_arg_ids_with_ty_and_spec
3729+
.iter()
3730+
.map(|&(ref_id, ty, spec)| {
3731+
use rustc_target::abi::{Float::*, Integer::*, Primitive::*};
36483732

3649-
let spirv_type = layout.spirv_type(self.span(), self);
3650-
debug_printf_args.push(
3651-
self.emit()
3652-
.load(spirv_type, None, ref_id, None, [])
3653-
.unwrap()
3654-
.with_type(spirv_type),
3655-
);
3656-
Cow::Borrowed(debug_printf_fmt)
3657-
},
3658-
))
3659-
.collect::<String>()
3660-
}
3661-
None => "<unknown message>".into(),
3662-
}
3733+
let layout = self.layout_of(ty);
3734+
3735+
let scalar = match layout.backend_repr {
3736+
BackendRepr::Scalar(scalar) => Some(scalar.primitive()),
3737+
_ => None,
3738+
};
3739+
let debug_printf_fmt = match (spec, scalar) {
3740+
// FIXME(eddyb) support more of these,
3741+
// potentially recursing to print ADTs.
3742+
(' ' | '?', Some(Int(I32, false))) => "%u",
3743+
('x', Some(Int(I32, false))) => "%x",
3744+
(' ' | '?', Some(Int(I32, true))) => "%i",
3745+
(' ' | '?', Some(Float(F32))) => "%f",
3746+
3747+
_ => "",
3748+
};
3749+
3750+
if debug_printf_fmt.is_empty() {
3751+
return Cow::Owned(
3752+
format!("{{/* unprintable {ty} */:{spec}}}").replace('%', "%%"),
3753+
);
3754+
}
3755+
3756+
let spirv_type = layout.spirv_type(self.span(), self);
3757+
debug_printf_args.push(
3758+
self.emit()
3759+
.load(spirv_type, None, ref_id, None, [])
3760+
.unwrap()
3761+
.with_type(spirv_type),
3762+
);
3763+
Cow::Borrowed(debug_printf_fmt)
3764+
});
3765+
3766+
// HACK(eddyb) due to `fmt::Arguments::new_v1_formatted`,
3767+
// we can't always assume that all the formatting arguments
3768+
// are used 1:1 as placeholders (i.e. between `const_pieces`).
3769+
let (placeholder_count, placeholders_are_args) =
3770+
match has_unknown_fmt_placeholder_to_args_mapping {
3771+
Some(count) => (count, false),
3772+
None => (args.len(), true),
3773+
};
3774+
3775+
// HACK(eddyb) extra sanity check to avoid visual mishaps.
3776+
let valid_placeholder_count = placeholder_count
3777+
.clamp(const_pieces.len().saturating_sub(1), const_pieces.len());
3778+
let placeholders_are_args =
3779+
placeholders_are_args && placeholder_count == valid_placeholder_count;
3780+
3781+
// FIXME(eddyb) stop using `itertools`'s `intersperse`,
3782+
// when it gets stabilized on `Iterator` instead.
3783+
#[allow(unstable_name_collisions)]
3784+
let (placeholders, suffix) = if placeholders_are_args {
3785+
(Either::Left(args), None)
3786+
} else {
3787+
// See also `has_unknown_fmt_placeholder_to_args_mapping`
3788+
// comment (which has an example for 3 pieces and 2 args).
3789+
//
3790+
// FIXME(eddyb) this could definitely be improved, but
3791+
// so far this only really gets hit in esoteric `core`
3792+
// internals (UB checks and `char::encode_utf{8,16}`).
3793+
(
3794+
Either::Right(
3795+
(0..valid_placeholder_count).map(|i| format!("{{{i}}}").into()),
3796+
),
3797+
Some(
3798+
["\n with {…} from: ".into()]
3799+
.into_iter()
3800+
.chain(args.intersperse(", ".into())),
3801+
),
3802+
)
3803+
};
3804+
3805+
const_pieces
3806+
.into_iter()
3807+
.map(|s| Cow::Owned(s.replace('%', "%%")))
3808+
.interleave(placeholders)
3809+
.chain(suffix.into_iter().flatten())
3810+
.collect::<String>()
36633811
}
36643812

36653813
Err(FormatArgsNotRecognized(step)) => {

crates/rustc_codegen_spirv/src/codegen_cx/declare.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,12 @@ impl<'tcx> CodegenCx<'tcx> {
229229
(pieces_len.parse().unwrap(), rt_args_len.parse().unwrap()),
230230
);
231231
}
232+
if demangled_symbol_name == "<core::fmt::Arguments>::new_v1_formatted" {
233+
// HACK(eddyb) `!0` used as a placeholder value to indicate "dynamic".
234+
self.fmt_args_new_fn_ids
235+
.borrow_mut()
236+
.insert(fn_id, (!0, !0));
237+
}
232238

233239
// HACK(eddyb) there is no good way to identify these definitions
234240
// (e.g. no `#[lang = "..."]` attribute), but this works well enough.

0 commit comments

Comments
 (0)