Skip to content

Commit c5230d1

Browse files
authored
Rollup merge of rust-lang#131523 - nbdd0121:asm, r=compiler-errors
Fix asm goto with outputs and move it to a separate feature gate Tracking issue: rust-lang#119364 This PR addresses 3 aspects of asm goto with outputs: * Codegen is fixed. My initial implementation has an oversight which cause the output to be only stored in fallthrough path, but not in label blocks. * Outputs can now be used with `options(noreturn)` if a label block is given. * All of this is moved to a new feature gate, because we likely want to stabilise `asm_goto` before asm goto with outputs. `@rustbot` labels: +A-inline-assembly +F-asm
2 parents 28fc2ba + 0178ba2 commit c5230d1

File tree

11 files changed

+178
-50
lines changed

11 files changed

+178
-50
lines changed

compiler/rustc_ast_lowering/messages.ftl

+2
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ ast_lowering_underscore_expr_lhs_assign =
181181
.label = `_` not allowed here
182182
183183
ast_lowering_unstable_inline_assembly = inline assembly is not stable yet on this architecture
184+
ast_lowering_unstable_inline_assembly_label_operand_with_outputs =
185+
using both label and output operands for inline assembly is unstable
184186
ast_lowering_unstable_inline_assembly_label_operands =
185187
label operands for inline assembly are unstable
186188
ast_lowering_unstable_may_unwind = the `may_unwind` option is unstable

compiler/rustc_ast_lowering/src/asm.rs

+35-9
Original file line numberDiff line numberDiff line change
@@ -239,15 +239,6 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
239239
}
240240
}
241241
InlineAsmOperand::Label { block } => {
242-
if !self.tcx.features().asm_goto() {
243-
feature_err(
244-
sess,
245-
sym::asm_goto,
246-
*op_sp,
247-
fluent::ast_lowering_unstable_inline_assembly_label_operands,
248-
)
249-
.emit();
250-
}
251242
hir::InlineAsmOperand::Label { block: self.lower_block(block, false) }
252243
}
253244
};
@@ -466,6 +457,41 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
466457
}
467458
}
468459

460+
// Feature gate checking for asm goto.
461+
if let Some((_, op_sp)) =
462+
operands.iter().find(|(op, _)| matches!(op, hir::InlineAsmOperand::Label { .. }))
463+
{
464+
if !self.tcx.features().asm_goto() {
465+
feature_err(
466+
sess,
467+
sym::asm_goto,
468+
*op_sp,
469+
fluent::ast_lowering_unstable_inline_assembly_label_operands,
470+
)
471+
.emit();
472+
}
473+
474+
// In addition, check if an output operand is used.
475+
// This is gated behind an additional feature.
476+
let output_operand_used = operands.iter().any(|(op, _)| {
477+
matches!(
478+
op,
479+
hir::InlineAsmOperand::Out { expr: Some(_), .. }
480+
| hir::InlineAsmOperand::InOut { .. }
481+
| hir::InlineAsmOperand::SplitInOut { out_expr: Some(_), .. }
482+
)
483+
});
484+
if output_operand_used && !self.tcx.features().asm_goto_with_outputs() {
485+
feature_err(
486+
sess,
487+
sym::asm_goto_with_outputs,
488+
*op_sp,
489+
fluent::ast_lowering_unstable_inline_assembly_label_operand_with_outputs,
490+
)
491+
.emit();
492+
}
493+
}
494+
469495
let operands = self.arena.alloc_from_iter(operands);
470496
let template = self.arena.alloc_from_iter(asm.template.iter().cloned());
471497
let template_strs = self.arena.alloc_from_iter(

compiler/rustc_builtin_macros/src/asm.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,10 @@ pub fn parse_asm_args<'a>(
300300
if args.options.contains(ast::InlineAsmOptions::PURE) && !have_real_output {
301301
dcx.emit_err(errors::AsmPureNoOutput { spans: args.options_spans.clone() });
302302
}
303-
if args.options.contains(ast::InlineAsmOptions::NORETURN) && !outputs_sp.is_empty() {
303+
if args.options.contains(ast::InlineAsmOptions::NORETURN)
304+
&& !outputs_sp.is_empty()
305+
&& labels_sp.is_empty()
306+
{
304307
let err = dcx.create_err(errors::AsmNoReturn { outputs_sp });
305308
// Bail out now since this is likely to confuse MIR
306309
return Err(err);

compiler/rustc_codegen_llvm/src/asm.rs

+25-17
Original file line numberDiff line numberDiff line change
@@ -342,24 +342,32 @@ impl<'ll, 'tcx> AsmBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> {
342342
}
343343
attributes::apply_to_callsite(result, llvm::AttributePlace::Function, &{ attrs });
344344

345-
// Switch to the 'normal' basic block if we did an `invoke` instead of a `call`
346-
if let Some(dest) = dest {
347-
self.switch_to_block(dest);
348-
}
345+
// Write results to outputs. We need to do this for all possible control flow.
346+
//
347+
// Note that `dest` maybe populated with unreachable_block when asm goto with outputs
348+
// is used (because we need to codegen callbr which always needs a destination), so
349+
// here we use the NORETURN option to determine if `dest` should be used.
350+
for block in (if options.contains(InlineAsmOptions::NORETURN) { None } else { Some(dest) })
351+
.into_iter()
352+
.chain(labels.iter().copied().map(Some))
353+
{
354+
if let Some(block) = block {
355+
self.switch_to_block(block);
356+
}
349357

350-
// Write results to outputs
351-
for (idx, op) in operands.iter().enumerate() {
352-
if let InlineAsmOperandRef::Out { reg, place: Some(place), .. }
353-
| InlineAsmOperandRef::InOut { reg, out_place: Some(place), .. } = *op
354-
{
355-
let value = if output_types.len() == 1 {
356-
result
357-
} else {
358-
self.extract_value(result, op_idx[&idx] as u64)
359-
};
360-
let value =
361-
llvm_fixup_output(self, value, reg.reg_class(), &place.layout, instance);
362-
OperandValue::Immediate(value).store(self, place);
358+
for (idx, op) in operands.iter().enumerate() {
359+
if let InlineAsmOperandRef::Out { reg, place: Some(place), .. }
360+
| InlineAsmOperandRef::InOut { reg, out_place: Some(place), .. } = *op
361+
{
362+
let value = if output_types.len() == 1 {
363+
result
364+
} else {
365+
self.extract_value(result, op_idx[&idx] as u64)
366+
};
367+
let value =
368+
llvm_fixup_output(self, value, reg.reg_class(), &place.layout, instance);
369+
OperandValue::Immediate(value).store(self, place);
370+
}
363371
}
364372
}
365373
}

compiler/rustc_feature/src/unstable.rs

+2
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,8 @@ declare_features! (
378378
(unstable, asm_experimental_arch, "1.58.0", Some(93335)),
379379
/// Allows using `label` operands in inline assembly.
380380
(unstable, asm_goto, "1.78.0", Some(119364)),
381+
/// Allows using `label` operands in inline assembly together with output operands.
382+
(unstable, asm_goto_with_outputs, "CURRENT_RUSTC_VERSION", Some(119364)),
381383
/// Allows the `may_unwind` option in inline assembly.
382384
(unstable, asm_unwind, "1.58.0", Some(93334)),
383385
/// Allows users to enforce equality of associated constants `TraitImpl<AssocConst=3>`.

compiler/rustc_span/src/symbol.rs

+1
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ symbols! {
417417
asm_const,
418418
asm_experimental_arch,
419419
asm_goto,
420+
asm_goto_with_outputs,
420421
asm_sym,
421422
asm_unwind,
422423
assert,

tests/codegen/asm/goto.rs

+25-13
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,10 @@
22
//@ only-x86_64
33

44
#![crate_type = "rlib"]
5-
#![feature(asm_goto)]
5+
#![feature(asm_goto, asm_goto_with_outputs)]
66

77
use std::arch::asm;
88

9-
#[no_mangle]
10-
pub extern "C" fn panicky() {}
11-
12-
struct Foo;
13-
14-
impl Drop for Foo {
15-
fn drop(&mut self) {
16-
println!();
17-
}
18-
}
19-
209
// CHECK-LABEL: @asm_goto
2110
#[no_mangle]
2211
pub unsafe fn asm_goto() {
@@ -38,14 +27,37 @@ pub unsafe fn asm_goto_with_outputs() -> u64 {
3827
out
3928
}
4029

30+
// CHECK-LABEL: @asm_goto_with_outputs_use_in_label
31+
#[no_mangle]
32+
pub unsafe fn asm_goto_with_outputs_use_in_label() -> u64 {
33+
let out: u64;
34+
// CHECK: [[RES:%[0-9]+]] = callbr i64 asm sideeffect alignstack inteldialect "
35+
// CHECK-NEXT: to label %[[FALLTHROUGHBB:[a-b0-9]+]] [label %[[JUMPBB:[a-b0-9]+]]]
36+
asm!("{} /* {} */", out(reg) out, label { return out; });
37+
// CHECK: [[JUMPBB]]:
38+
// CHECK-NEXT: [[RET:%.+]] = phi i64 [ 1, %[[FALLTHROUGHBB]] ], [ [[RES]], %start ]
39+
// CHECK-NEXT: ret i64 [[RET]]
40+
1
41+
}
42+
4143
// CHECK-LABEL: @asm_goto_noreturn
4244
#[no_mangle]
4345
pub unsafe fn asm_goto_noreturn() -> u64 {
44-
let out: u64;
4546
// CHECK: callbr void asm sideeffect alignstack inteldialect "
4647
// CHECK-NEXT: to label %unreachable [label %[[JUMPBB:[a-b0-9]+]]]
4748
asm!("jmp {}", label { return 1; }, options(noreturn));
4849
// CHECK: [[JUMPBB]]:
4950
// CHECK-NEXT: ret i64 1
51+
}
52+
53+
// CHECK-LABEL: @asm_goto_noreturn_with_outputs
54+
#[no_mangle]
55+
pub unsafe fn asm_goto_noreturn_with_outputs() -> u64 {
56+
let out: u64;
57+
// CHECK: [[RES:%[0-9]+]] = callbr i64 asm sideeffect alignstack inteldialect "
58+
// CHECK-NEXT: to label %[[FALLTHROUGHBB:[a-b0-9]+]] [label %[[JUMPBB:[a-b0-9]+]]]
59+
asm!("mov {}, 1", "jmp {}", out(reg) out, label { return out; });
60+
// CHECK: [[JUMPBB]]:
61+
// CHECK-NEXT: ret i64 [[RES]]
5062
out
5163
}

tests/ui/asm/x86_64/goto.rs

+56-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//@ needs-asm-support
44

55
#![deny(unreachable_code)]
6-
#![feature(asm_goto)]
6+
#![feature(asm_goto, asm_goto_with_outputs)]
77

88
use std::arch::asm;
99

@@ -31,10 +31,6 @@ fn goto_jump() {
3131
}
3232
}
3333

34-
// asm goto with outputs cause miscompilation in LLVM. UB can be triggered
35-
// when outputs are used inside the label block when optimisation is enabled.
36-
// See: https://github.com/llvm/llvm-project/issues/74483
37-
/*
3834
fn goto_out_fallthrough() {
3935
unsafe {
4036
let mut out: usize;
@@ -68,7 +64,57 @@ fn goto_out_jump() {
6864
assert!(value);
6965
}
7066
}
71-
*/
67+
68+
fn goto_out_jump_noreturn() {
69+
unsafe {
70+
let mut value = false;
71+
let mut out: usize;
72+
asm!(
73+
"lea {}, [{} + 1]",
74+
"jmp {}",
75+
out(reg) out,
76+
in(reg) 0x12345678usize,
77+
label {
78+
value = true;
79+
assert_eq!(out, 0x12345679);
80+
},
81+
options(noreturn)
82+
);
83+
assert!(value);
84+
}
85+
}
86+
87+
// asm goto with outputs cause miscompilation in LLVM when multiple outputs are present.
88+
// The code sample below is adapted from https://github.com/llvm/llvm-project/issues/74483
89+
// and does not work with `-C opt-level=0`
90+
#[expect(unused)]
91+
fn goto_multi_out() {
92+
#[inline(never)]
93+
#[allow(unused)]
94+
fn goto_multi_out(a: usize, b: usize) -> usize {
95+
let mut x: usize;
96+
let mut y: usize;
97+
let mut z: usize;
98+
unsafe {
99+
core::arch::asm!(
100+
"mov {x}, {a}",
101+
"test {a}, {a}",
102+
"jnz {label1}",
103+
"/* {y} {z} {b} {label2} */",
104+
x = out(reg) x,
105+
y = out(reg) y,
106+
z = out(reg) z,
107+
a = in(reg) a,
108+
b = in(reg) b,
109+
label1 = label { return x },
110+
label2 = label { return 1 },
111+
);
112+
0
113+
}
114+
}
115+
116+
assert_eq!(goto_multi_out(11, 22), 11);
117+
}
72118

73119
fn goto_noreturn() {
74120
unsafe {
@@ -102,8 +148,10 @@ fn goto_noreturn_diverge() {
102148
fn main() {
103149
goto_fallthough();
104150
goto_jump();
105-
// goto_out_fallthrough();
106-
// goto_out_jump();
151+
goto_out_fallthrough();
152+
goto_out_jump();
153+
goto_out_jump_noreturn();
154+
// goto_multi_out();
107155
goto_noreturn();
108156
goto_noreturn_diverge();
109157
}

tests/ui/asm/x86_64/goto.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
warning: unreachable statement
2-
--> $DIR/goto.rs:97:9
2+
--> $DIR/goto.rs:143:9
33
|
44
LL | / asm!(
55
LL | | "jmp {}",
@@ -13,7 +13,7 @@ LL | unreachable!();
1313
| ^^^^^^^^^^^^^^ unreachable statement
1414
|
1515
note: the lint level is defined here
16-
--> $DIR/goto.rs:87:8
16+
--> $DIR/goto.rs:133:8
1717
|
1818
LL | #[warn(unreachable_code)]
1919
| ^^^^^^^^^^^^^^^^
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//@ only-x86_64
2+
3+
#![feature(asm_goto)]
4+
5+
use std::arch::asm;
6+
7+
fn main() {
8+
let mut _out: u64;
9+
unsafe {
10+
asm!("mov {}, 1", "jmp {}", out(reg) _out, label {});
11+
//~^ ERROR using both label and output operands for inline assembly is unstable
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
error[E0658]: using both label and output operands for inline assembly is unstable
2+
--> $DIR/feature-gate-asm_goto_with_outputs.rs:10:52
3+
|
4+
LL | asm!("mov {}, 1", "jmp {}", out(reg) _out, label {});
5+
| ^^^^^^^^
6+
|
7+
= note: see issue #119364 <https://github.com/rust-lang/rust/issues/119364> for more information
8+
= help: add `#![feature(asm_goto_with_outputs)]` to the crate attributes to enable
9+
= note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
10+
11+
error: aborting due to 1 previous error
12+
13+
For more information about this error, try `rustc --explain E0658`.

0 commit comments

Comments
 (0)