Skip to content

Commit d578ce9

Browse files
committed
fix clobbered or lateout registers overlapping with input registers
1 parent 7f05920 commit d578ce9

File tree

2 files changed

+79
-25
lines changed

2 files changed

+79
-25
lines changed

src/asm.rs

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ use crate::type_of::LayoutGccExt;
3636
//
3737
// 3. Clobbers. GCC has a separate list of clobbers, and clobbers don't have indexes.
3838
// Contrary, Rust expresses clobbers through "out" operands that aren't tied to
39-
// a variable (`_`), and such "clobbers" do have index.
39+
// a variable (`_`), and such "clobbers" do have index. Input operands cannot also
40+
// be clobbered.
4041
//
4142
// 4. Furthermore, GCC Extended Asm does not support explicit register constraints
4243
// (like `out("eax")`) directly, offering so-called "local register variables"
@@ -161,6 +162,16 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> {
161162
// Also, we don't emit any asm operands immediately; we save them to
162163
// the one of the buffers to be emitted later.
163164

165+
let mut input_registers = vec![];
166+
167+
for op in rust_operands.iter() {
168+
if let InlineAsmOperandRef::In { reg, .. } = op {
169+
if let ConstraintOrRegister::Register(reg_name) = reg_to_gcc(*reg) {
170+
input_registers.push(reg_name);
171+
}
172+
}
173+
}
174+
164175
// 1. Normal variables (and saving operands to buffers).
165176
for (rust_idx, op) in rust_operands.iter().enumerate() {
166177
match *op {
@@ -183,25 +194,40 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> {
183194
continue;
184195
}
185196
(Register(reg_name), None) => {
186-
// `clobber_abi` can add lots of clobbers that are not supported by the target,
187-
// such as AVX-512 registers, so we just ignore unsupported registers
188-
let is_target_supported =
189-
reg.reg_class().supported_types(asm_arch, true).iter().any(
190-
|&(_, feature)| {
191-
if let Some(feature) = feature {
192-
self.tcx
193-
.asm_target_features(instance.def_id())
194-
.contains(&feature)
195-
} else {
196-
true // Register class is unconditionally supported
197-
}
198-
},
197+
if input_registers.contains(&reg_name) {
198+
// the `clobber_abi` operand is converted into a series of
199+
// `lateout("reg") _` operands. Of course, a user could also
200+
// explicitly define such an output operand.
201+
//
202+
// GCC does not allow input registers to be clobbered, so if this out register
203+
// is also used as an in register, do not add it to the clobbers list.
204+
// it will be treated as a lateout register with `out_place: None`
205+
assert!(
206+
late,
207+
"input registers can only be used as lateout regisers"
199208
);
200-
201-
if is_target_supported && !clobbers.contains(&reg_name) {
202-
clobbers.push(reg_name);
209+
("r", dummy_output_type(self.cx, reg.reg_class()))
210+
} else {
211+
// `clobber_abi` can add lots of clobbers that are not supported by the target,
212+
// such as AVX-512 registers, so we just ignore unsupported registers
213+
let is_target_supported =
214+
reg.reg_class().supported_types(asm_arch, true).iter().any(
215+
|&(_, feature)| {
216+
if let Some(feature) = feature {
217+
self.tcx
218+
.asm_target_features(instance.def_id())
219+
.contains(&feature)
220+
} else {
221+
true // Register class is unconditionally supported
222+
}
223+
},
224+
);
225+
226+
if is_target_supported && !clobbers.contains(&reg_name) {
227+
clobbers.push(reg_name);
228+
}
229+
continue;
203230
}
204-
continue;
205231
}
206232
};
207233

@@ -230,13 +256,10 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> {
230256
}
231257

232258
InlineAsmOperandRef::InOut { reg, late, in_value, out_place } => {
233-
let constraint =
234-
if let ConstraintOrRegister::Constraint(constraint) = reg_to_gcc(reg) {
235-
constraint
236-
} else {
237-
// left for the next pass
238-
continue;
239-
};
259+
let ConstraintOrRegister::Constraint(constraint) = reg_to_gcc(reg) else {
260+
// left for the next pass
261+
continue;
262+
};
240263

241264
// Rustc frontend guarantees that input and output types are "compatible",
242265
// so we can just use input var's type for the output variable.

tests/run/asm.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,37 @@ fn asm() {
174174
mem_cpy(array2.as_mut_ptr(), array1.as_ptr(), 3);
175175
}
176176
assert_eq!(array1, array2);
177+
178+
// in and clobber registers cannot overlap. This tests that the lateout register without an
179+
// output place (indicated by the `_`) is not added to the list of clobbered registers
180+
let x = 8;
181+
let y: i32;
182+
unsafe {
183+
asm!(
184+
"mov rax, rdi",
185+
in("rdi") x,
186+
lateout("rdi") _,
187+
out("rax") y,
188+
);
189+
}
190+
assert_eq!((x, y), (8, 8));
191+
192+
// sysv64 is the default calling convention on unix systems. The rdi register is
193+
// used to pass arguments in the sysv64 calling convention, so this register will be clobbered
194+
#[cfg(unix)]
195+
{
196+
let x = 16;
197+
let y: i32;
198+
unsafe {
199+
asm!(
200+
"mov rax, rdi",
201+
in("rdi") x,
202+
out("rax") y,
203+
clobber_abi("sysv64"),
204+
);
205+
}
206+
assert_eq!((x, y), (16, 16));
207+
}
177208
}
178209

179210
#[cfg(not(target_arch = "x86_64"))]

0 commit comments

Comments
 (0)