Skip to content

Commit 1f98329

Browse files
authored
Merge pull request #628 from folkertdev/clobber-input-registers
fix clobbered or lateout registers overlapping with input registers
2 parents b08a0c6 + 3ecc012 commit 1f98329

File tree

4 files changed

+80
-28
lines changed

4 files changed

+80
-28
lines changed

Diff for: libgccjit.version

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
e607be166673a8de9fc07f6f02c60426e556c5f2
1+
d6f5a708104a98199ac0f01a3b6b279a0f7c66d3

Diff for: src/asm.rs

+48-26
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 {
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,39 @@ 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-
},
199-
);
200-
201-
if is_target_supported && !clobbers.contains(&reg_name) {
202-
clobbers.push(reg_name);
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+
if !late {
206+
bug!("input registers can only be used as lateout regisers");
207+
}
208+
("r", dummy_output_type(self.cx, reg.reg_class()))
209+
} else {
210+
// `clobber_abi` can add lots of clobbers that are not supported by the target,
211+
// such as AVX-512 registers, so we just ignore unsupported registers
212+
let is_target_supported =
213+
reg.reg_class().supported_types(asm_arch, true).iter().any(
214+
|&(_, feature)| {
215+
if let Some(feature) = feature {
216+
self.tcx
217+
.asm_target_features(instance.def_id())
218+
.contains(&feature)
219+
} else {
220+
true // Register class is unconditionally supported
221+
}
222+
},
223+
);
224+
225+
if is_target_supported && !clobbers.contains(&reg_name) {
226+
clobbers.push(reg_name);
227+
}
228+
continue;
203229
}
204-
continue;
205230
}
206231
};
207232

@@ -230,13 +255,10 @@ impl<'a, 'gcc, 'tcx> AsmBuilderMethods<'tcx> for Builder<'a, 'gcc, 'tcx> {
230255
}
231256

232257
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-
};
258+
let ConstraintOrRegister::Constraint(constraint) = reg_to_gcc(reg) else {
259+
// left for the next pass
260+
continue;
261+
};
240262

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

Diff for: tests/failing-ui-tests.txt

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
tests/ui/allocator/no_std-alloc-error-handler-custom.rs
22
tests/ui/allocator/no_std-alloc-error-handler-default.rs
33
tests/ui/asm/may_unwind.rs
4-
tests/ui/asm/x86_64/multiple-clobber-abi.rs
54
tests/ui/functions-closures/parallel-codegen-closures.rs
65
tests/ui/linkage-attr/linkage1.rs
76
tests/ui/lto/dylib-works.rs

Diff for: tests/run/asm.rs

+31
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)