Skip to content

Rewrite local labels in inline assembly #81570

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions compiler/rustc_codegen_llvm/src/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ impl AsmBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> {

// Build the template string
let mut template_str = String::new();
let mut labels = vec![];
for piece in template {
match *piece {
InlineAsmTemplatePiece::String(ref s) => {
Expand All @@ -212,6 +213,12 @@ impl AsmBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> {
}
}
} else {
// Labels should be made local to prevent issues with inlined `asm!` and duplicate labels
// Fixes https://github.com/rust-lang/rust/issues/74262
if let Some(label) = get_llvm_label_from_str(s) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A template string piece may consist of multiple lines (e.g. raw string literals). These strings could define more than one label.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah thanks, I didn't think of this case

labels.push(label.to_owned());
}

template_str.push_str(s)
}
}
Expand Down Expand Up @@ -244,6 +251,38 @@ impl AsmBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> {
}
}

fn valid_ident_continuation(c: char) -> bool {
('a'..='z').contains(&c)
|| ('A'..='Z').contains(&c)
|| ('0'..='9').contains(&c)
|| '_' == c
|| '$' == c
|| '.' == c
|| '@' == c
}

for label in labels.iter() {
let ranges: Vec<_> = template_str
.match_indices(label)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this checks if the character before the match is a non-identifier character. This means that if you have a label bar then foobar will be replaced with foo${:private}bar${:uid} which is really not what we want.

.filter_map(|(idx, _)| {
let label_end_idx = idx + label.len();
let next_char = template_str[label_end_idx..].chars().nth(0);
if next_char.is_none() || !valid_ident_continuation(next_char.unwrap()) {
Some(idx..label_end_idx)
} else {
None
}
})
.collect();

// We add a constant length of 18 to the string every time
// from ${:private} and ${:uid}
for (i, range) in ranges.iter().enumerate() {
let real_range = range.start + (i * 18)..range.end + (i * 18);
template_str.replace_range(real_range, &format!("${{:private}}{}${{:uid}}", label))
}
}

if !options.contains(InlineAsmOptions::PRESERVES_FLAGS) {
match asm_arch {
InlineAsmArch::AArch64 | InlineAsmArch::Arm => {
Expand Down Expand Up @@ -874,3 +913,31 @@ fn llvm_fixup_output_type(
_ => layout.llvm_type(cx),
}
}

/// Gets a LLVM label from a &str if it exists
/// Uses this as reference: https://llvm.org/docs/AMDGPUOperandSyntax.html#symbols
fn get_llvm_label_from_str(s: &str) -> Option<&str> {
if let Some(colon_idx) = s.find(':') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use trim_start to strip leading whitespace from the line.

let substr = &s[..colon_idx];
let mut chars = substr.chars();
if let Some(c) = chars.next() {
// First char is [a-zA-Z_.]
if ('a'..='z').contains(&c) || ('A'..='Z').contains(&c) || '_' == c || '.' == c {
// All subsequent chars are [a-zA-Z0-9_$.@]
if chars.all(|c| {
('a'..='z').contains(&c)
|| ('A'..='Z').contains(&c)
|| ('0'..='9').contains(&c)
|| '_' == c
|| '$' == c
|| '.' == c
|| '@' == c
}) {
return Some(substr);
}
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One way to get more arbitrary stuff into a asm label is to quote it as a string, like this:

void test(void) {
    __asm__(".intel_syntax noprefix\n"
    "\"01☺☺@banana.?@!\": jmp \"01☺☺@banana.?@!\"");
}

or in Rust

#![feature(asm)]
pub fn test() {
    unsafe {
    asm!(r#"
        "☺helloworld?@!$%^&": jmp "☺helloworld?@!$%^&"
    "#);
    }
}

So that's something we should handle or forbid too.


None
}
16 changes: 16 additions & 0 deletions src/test/ui/asm/duplicate-labels-inline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// compile-flags: -g
// build-pass

#![feature(asm)]

#[inline(always)]
fn asm() {
unsafe {
asm!("duplabel: nop",);
}
}

fn main() {
asm();
asm();
}
21 changes: 21 additions & 0 deletions src/test/ui/asm/duplicate-labels.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// compile-flags: -g
// build-pass

#![feature(asm)]

fn asm1() {
unsafe {
asm!("duplabel: nop",);
}
}

fn asm2() {
unsafe {
asm!("nop", "duplabel: nop",);
}
}

fn main() {
asm1();
asm2();
}
10 changes: 10 additions & 0 deletions src/test/ui/asm/label-substr-instruction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// compile-flags: -g
// build-pass

#![feature(asm)]

fn main() {
unsafe {
asm!("jmp j", "nop", "j: nop");
}
}
10 changes: 10 additions & 0 deletions src/test/ui/asm/label-substr-label.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// compile-flags: -g
// build-pass

#![feature(asm)]

fn main() {
unsafe {
asm!("jmp l", "l: nop", "jmp l2", "l2: nop");
}
}
10 changes: 10 additions & 0 deletions src/test/ui/asm/label-use-before-declaration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// compile-flags: -g
// build-pass

#![feature(asm)]

fn main() {
unsafe {
asm!("jmp l", "nop", "l: nop");
}
}