Skip to content

Commit abaac31

Browse files
eddybFirestar99
authored andcommitted
linker/inline: fix OpVariable debuginfo collection and insertion.
1 parent ea63e1d commit abaac31

File tree

1 file changed

+147
-46
lines changed
  • crates/rustc_codegen_spirv/src/linker

1 file changed

+147
-46
lines changed

crates/rustc_codegen_spirv/src/linker/inline.rs

Lines changed: 147 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet};
1515
use rustc_errors::ErrorGuaranteed;
1616
use rustc_session::Session;
1717
use smallvec::SmallVec;
18-
use std::mem::{self, take};
18+
use std::mem;
1919

2020
type FunctionMap = FxHashMap<Word, Function>;
2121

@@ -653,12 +653,13 @@ impl Inliner<'_, '_> {
653653
if let Some(call_result_type) = call_result_type {
654654
// Generate the storage space for the return value: Do this *after* the split above,
655655
// because if block_idx=0, inserting a variable here shifts call_index.
656-
insert_opvariables(&mut caller.blocks[0], [Instruction::new(
656+
let ret_var_inst = Instruction::new(
657657
Op::Variable,
658658
Some(self.ptr_ty(call_result_type)),
659659
Some(return_variable.unwrap()),
660660
vec![Operand::StorageClass(StorageClass::Function)],
661-
)]);
661+
);
662+
self.insert_opvariables(&mut caller.blocks[0], [ret_var_inst]);
662663
}
663664

664665
// Insert non-entry inlined callee blocks just after the pre-call block.
@@ -704,52 +705,115 @@ impl Inliner<'_, '_> {
704705
// Fuse the inlined callee entry block into the pre-call block.
705706
// This is okay because it's illegal to branch to the first BB in a function.
706707
{
708+
// NOTE(eddyb) `OpExtInst`s have a result ID, even if unused, and
709+
// it has to be unique, so this allocates new IDs as-needed.
710+
let instantiate_debuginfo = |this: &mut Self, inst: &Instruction| {
711+
let mut inst = inst.clone();
712+
if let Some(id) = &mut inst.result_id {
713+
*id = this.id();
714+
}
715+
inst
716+
};
717+
718+
let custom_inst_to_inst = |this: &mut Self, inst: CustomInst<_>| {
719+
Instruction::new(
720+
Op::ExtInst,
721+
Some(this.op_type_void_id),
722+
Some(this.id()),
723+
[
724+
Operand::IdRef(this.custom_ext_inst_set_import),
725+
Operand::LiteralExtInstInteger(inst.op() as u32),
726+
]
727+
.into_iter()
728+
.chain(inst.into_operands())
729+
.collect(),
730+
)
731+
};
732+
707733
// Return the subsequence of `insts` made from `OpVariable`s, and any
708734
// debuginfo instructions (which may apply to them), while removing
709735
// *only* `OpVariable`s from `insts` (and keeping debuginfo in both).
710736
let mut steal_vars = |insts: &mut Vec<Instruction>| {
711-
let mut vars_and_debuginfo = vec![];
712-
insts.retain_mut(|inst| {
713-
let is_debuginfo = match inst.class.opcode {
714-
Op::Line | Op::NoLine => true,
715-
Op::ExtInst => {
716-
inst.operands[0].unwrap_id_ref() == self.custom_ext_inst_set_import
717-
&& CustomOp::decode_from_ext_inst(inst).is_debuginfo()
718-
}
719-
_ => false,
720-
};
721-
if is_debuginfo {
722-
// NOTE(eddyb) `OpExtInst`s have a result ID,
723-
// even if unused, and it has to be unique.
724-
let mut inst = inst.clone();
725-
if let Some(id) = &mut inst.result_id {
726-
*id = self.id();
737+
// HACK(eddyb) this duplicates some code from `get_inlined_blocks`,
738+
// but that will be removed once the inliner is refactored to be
739+
// inside-out instead of outside-in (already finished in a branch).
740+
let mut enclosing_inlined_frames = SmallVec::<[_; 8]>::new();
741+
let mut current_debug_src_loc_inst = None;
742+
let mut vars_and_debuginfo_range = 0..0;
743+
while vars_and_debuginfo_range.end < insts.len() {
744+
let inst = &insts[vars_and_debuginfo_range.end];
745+
match inst.class.opcode {
746+
Op::Line => current_debug_src_loc_inst = Some(inst),
747+
Op::NoLine => current_debug_src_loc_inst = None,
748+
Op::ExtInst
749+
if inst.operands[0].unwrap_id_ref()
750+
== self.custom_ext_inst_set_import =>
751+
{
752+
match CustomOp::decode_from_ext_inst(inst) {
753+
CustomOp::SetDebugSrcLoc => current_debug_src_loc_inst = Some(inst),
754+
CustomOp::ClearDebugSrcLoc => current_debug_src_loc_inst = None,
755+
CustomOp::PushInlinedCallFrame => {
756+
enclosing_inlined_frames
757+
.push((current_debug_src_loc_inst.take(), inst));
758+
}
759+
CustomOp::PopInlinedCallFrame => {
760+
if let Some((callsite_debug_src_loc_inst, _)) =
761+
enclosing_inlined_frames.pop()
762+
{
763+
current_debug_src_loc_inst = callsite_debug_src_loc_inst;
764+
}
765+
}
766+
CustomOp::Abort => break,
767+
}
727768
}
728-
vars_and_debuginfo.push(inst);
729-
true
730-
} else if inst.class.opcode == Op::Variable {
731-
// HACK(eddyb) we're removing this `Instruction` from
732-
// `inst`, so it doesn't really matter what we use here.
733-
vars_and_debuginfo.push(mem::replace(
734-
inst,
735-
Instruction::new(Op::Nop, None, None, vec![]),
736-
));
737-
false
738-
} else {
739-
true
769+
Op::Variable => {}
770+
_ => break,
740771
}
741-
});
742-
vars_and_debuginfo
772+
vars_and_debuginfo_range.end += 1;
773+
}
774+
775+
// `vars_and_debuginfo_range.end` indicates where `OpVariable`s
776+
// end and other instructions start (module debuginfo), but to
777+
// split the block in two, both sides of the "cut" need "repair":
778+
// - the variables are missing "inlined call frames" pops, that
779+
// may happen later in the block, and have to be synthesized
780+
// - the non-variables are missing "inlined call frames" pushes,
781+
// that must be recreated to avoid ending up with dangling pops
782+
//
783+
// FIXME(eddyb) this only collects to avoid borrow conflicts,
784+
// between e.g. `enclosing_inlined_frames` and mutating `insts`,
785+
// but also between different uses of `self`.
786+
let all_pops_after_vars: SmallVec<[_; 8]> = enclosing_inlined_frames
787+
.iter()
788+
.map(|_| custom_inst_to_inst(self, CustomInst::PopInlinedCallFrame))
789+
.collect();
790+
let all_repushes_before_non_vars: SmallVec<[_; 8]> =
791+
(enclosing_inlined_frames.into_iter().flat_map(
792+
|(callsite_debug_src_loc_inst, push_inlined_call_frame_inst)| {
793+
(callsite_debug_src_loc_inst.into_iter())
794+
.chain([push_inlined_call_frame_inst])
795+
},
796+
))
797+
.chain(current_debug_src_loc_inst)
798+
.map(|inst| instantiate_debuginfo(self, inst))
799+
.collect();
800+
801+
let vars_and_debuginfo =
802+
insts.splice(vars_and_debuginfo_range, all_repushes_before_non_vars);
803+
let repaired_vars_and_debuginfo = vars_and_debuginfo.chain(all_pops_after_vars);
804+
805+
// FIXME(eddyb) collecting shouldn't be necessary but this is
806+
// nested in a closure, and `splice` borrows the original `Vec`.
807+
repaired_vars_and_debuginfo.collect::<SmallVec<[_; 8]>>()
743808
};
744809

745810
let [mut inlined_callee_entry_block]: [_; 1] =
746811
inlined_callee_blocks.try_into().unwrap();
747812

748813
// Move the `OpVariable`s of the callee to the caller.
749-
insert_opvariables(
750-
&mut caller.blocks[0],
751-
steal_vars(&mut inlined_callee_entry_block.instructions),
752-
);
814+
let callee_vars_and_debuginfo =
815+
steal_vars(&mut inlined_callee_entry_block.instructions);
816+
self.insert_opvariables(&mut caller.blocks[0], callee_vars_and_debuginfo);
753817

754818
caller.blocks[pre_call_block_idx]
755819
.instructions
@@ -977,15 +1041,52 @@ impl Inliner<'_, '_> {
9771041
caller_restore_debuginfo_after_call,
9781042
)
9791043
}
980-
}
9811044

982-
fn insert_opvariables(block: &mut Block, insts: impl IntoIterator<Item = Instruction>) {
983-
let first_non_variable = block
984-
.instructions
985-
.iter()
986-
.position(|inst| inst.class.opcode != Op::Variable);
987-
let i = first_non_variable.unwrap_or(block.instructions.len());
988-
block.instructions.splice(i..i, insts);
1045+
fn insert_opvariables(&self, block: &mut Block, insts: impl IntoIterator<Item = Instruction>) {
1046+
// HACK(eddyb) this isn't as efficient as it could be in theory, but it's
1047+
// very important to make sure sure to never insert new instructions in
1048+
// the middle of debuginfo (as it would be affected by it).
1049+
let mut inlined_frames_depth = 0usize;
1050+
let mut outermost_has_debug_src_loc = false;
1051+
let mut last_debugless_var_insertion_point_candidate = None;
1052+
for (i, inst) in block.instructions.iter().enumerate() {
1053+
last_debugless_var_insertion_point_candidate =
1054+
(inlined_frames_depth == 0 && !outermost_has_debug_src_loc).then_some(i);
1055+
1056+
let changed_has_debug_src_loc = match inst.class.opcode {
1057+
Op::Line => true,
1058+
Op::NoLine => false,
1059+
Op::ExtInst
1060+
if inst.operands[0].unwrap_id_ref() == self.custom_ext_inst_set_import =>
1061+
{
1062+
match CustomOp::decode_from_ext_inst(inst) {
1063+
CustomOp::SetDebugSrcLoc => true,
1064+
CustomOp::ClearDebugSrcLoc => false,
1065+
CustomOp::PushInlinedCallFrame => {
1066+
inlined_frames_depth += 1;
1067+
continue;
1068+
}
1069+
CustomOp::PopInlinedCallFrame => {
1070+
inlined_frames_depth = inlined_frames_depth.saturating_sub(1);
1071+
continue;
1072+
}
1073+
CustomOp::Abort => break,
1074+
}
1075+
}
1076+
Op::Variable => continue,
1077+
_ => break,
1078+
};
1079+
1080+
if inlined_frames_depth == 0 {
1081+
outermost_has_debug_src_loc = changed_has_debug_src_loc;
1082+
}
1083+
}
1084+
1085+
// HACK(eddyb) fallback to inserting at the start, which should be correct.
1086+
// FIXME(eddyb) some level of debuginfo repair could prevent needing this.
1087+
let i = last_debugless_var_insertion_point_candidate.unwrap_or(0);
1088+
block.instructions.splice(i..i, insts);
1089+
}
9891090
}
9901091

9911092
fn fuse_trivial_branches(function: &mut Function) {
@@ -1020,7 +1121,7 @@ fn fuse_trivial_branches(function: &mut Function) {
10201121
};
10211122
let pred_insts = &function.blocks[pred].instructions;
10221123
if pred_insts.last().unwrap().class.opcode == Op::Branch {
1023-
let mut dest_insts = take(&mut function.blocks[dest_block].instructions);
1124+
let mut dest_insts = mem::take(&mut function.blocks[dest_block].instructions);
10241125
let pred_insts = &mut function.blocks[pred].instructions;
10251126
pred_insts.pop(); // pop the branch
10261127
pred_insts.append(&mut dest_insts);

0 commit comments

Comments
 (0)