Skip to content

draft: inline asm mode #146215

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
154 changes: 154 additions & 0 deletions llvm/docs/InlineAsmSafetyDirective.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Inline Assembly Safety Directive

## Overview

The `.inline_asm_mode` directive provides enhanced safety for inline assembly blocks by warning about potentially unsafe label usage. This directive helps prevent common errors where programmers create non-numeric labels in inline assembly that could be inadvertently jumped to from external code.

## Syntax

```assembly
.inline_asm_mode strict # Enable strict mode - warn on non-numeric labels
.inline_asm_mode relaxed # Disable strict mode (default)
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm especially willing to consider a better name for this directive.

```

## Description

When `.inline_asm_mode strict` is active, the assembler will emit warnings for labels that are considered potentially unsafe for inline assembly:

- **Safe labels** (no warnings):
- Numeric labels (e.g., `1:`, `42:`, `999:`)

- **Unsafe labels** (warnings emitted):
- All non-numeric labels including:
- Global labels (e.g., `my_function:`, `loop:`)
- Local labels (e.g., `.L_loop`, `.L_end`)
- Special prefixed labels (e.g., `$symbol`, `__symbol`)
- Any label that doesn't start with a digit

## Use Cases

### Frontend Integration

Compiler frontends can use this directive when generating inline assembly:

```c++
// Emitted by the compiler: .inline_asm_mode strict
// C++ inline assembly example
asm(
"1:\n" // Safe - numeric label
" add %0, %1\n"
" jne 1b\n" // Safe - numeric jump
"exit:\n" // Warning - non-numeric label
: "=r"(result) : "r"(input));
```
// Emitted by the compiler: .inline_asm_mode relaxed

### Assembly Development

Assembly programmers can use this directive for safer inline assembly blocks:

```assembly
.inline_asm_mode strict

# Safe labels - no warnings (numeric only)
1: # Loop start
inc %eax
dec %ebx
jnz 1b # Jump back to label 1

2: # Alternative path
nop
jmp 3f # Jump forward to label 3

3: # End label
ret

# Unsafe labels - will generate warnings
# unsafe_global: # Warning: non-numeric label
# .L_local: # Warning: non-numeric label
# $special: # Warning: non-numeric label

.inline_asm_mode relaxed
```

## Rationale

Inline assembly blocks are often embedded within larger functions or modules. Non-numeric labels in these blocks can create several problems:

1. **Naming conflicts**: Named labels may conflict with other symbols in the compilation unit
2. **Unintended control flow**: External code might accidentally jump to named labels intended for internal use
3. **Maintenance issues**: Named labels make inline assembly less self-contained
4. **Assembly convention**: Numeric labels are the standard convention for temporary, local labels in assembly

The strict mode helps identify these potential issues during compilation, encouraging developers to use safer numeric labels instead.

## Error Handling

Invalid directive usage will produce parse errors:

```assembly
.inline_asm_mode invalid_mode
# Error: expected 'strict' or 'relaxed'

.inline_asm_mode
# Error: expected 'strict' or 'relaxed' after '.inline_asm_mode'
```

## Implementation Details

- The directive affects only subsequent label definitions until changed
- Default mode is `relaxed` (no additional warnings)
- The directive state is maintained in the MC streamer
- Warnings are emitted through the standard LLVM diagnostic system

## Examples

### Complete Example

```assembly
.text
.globl example_function
example_function:
# Regular function labels (outside inline asm) - no warnings

# Simulate inline assembly block with safety
.inline_asm_mode strict

# Only numeric labels are safe
1: # Safe - numeric label
mov $1, %eax
test %eax, %eax
jz 2f

3: # Safe - numeric label
inc %eax
cmp $10, %eax
jl 3b

2: # Safe - numeric label
# End of safe inline block

# These would generate warnings
# .L_inline_start: # Warning - non-numeric
# global_inline_label: # Warning - non-numeric

.inline_asm_mode relaxed

# Back to normal mode
ret
```

## Integration with Build Systems

Build systems can use standard LLVM warning controls to manage these diagnostics:

```bash
# Treat inline assembly warnings as errors
llvm-mc -Werror=inline-asm-unsafe-label input.s

# Suppress inline assembly warnings
llvm-mc -Wno-inline-asm-unsafe-label input.s
```

Note: The specific warning flag names are implementation-dependent and may vary.

1 change: 1 addition & 0 deletions llvm/docs/Reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ LLVM and API reference documentation.
HowToSetUpLLVMStyleRTTI
HowToUseAttributes
InAlloca
InlineAsmSafetyDirective
InterfaceExportAnnotations
LangRef
LibFuzzer
Expand Down
3 changes: 2 additions & 1 deletion llvm/include/llvm/MC/MCSectionGOFF.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ class MCSectionGOFF final : public MCSection {

// Returns the text style for a section. Only defined for ED and PR sections.
GOFF::ESDTextStyle getTextStyle() const {
assert(isED() || isPR() || isVirtualSection() && "Expect ED or PR section");
assert((isED() || isPR() || isVirtualSection()) &&
"Expect ED or PR section");
if (isED())
return EDAttributes.TextStyle;
if (isPR())
Expand Down
8 changes: 8 additions & 0 deletions llvm/include/llvm/MC/MCStreamer.h
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,11 @@ class LLVM_ABI MCStreamer {
/// discussion for future inclusion.
bool AllowAutoPadding = false;

/// Is strict inline assembly mode enabled? When enabled, the assembler
/// will warn about non-local labels that could be unsafe jump targets
/// in inline assembly blocks.
bool InlineAsmStrictMode = false;

protected:
// Symbol of the current epilog for which we are processing SEH directives.
WinEH::FrameInfo::Epilog *CurrentWinEpilog = nullptr;
Expand Down Expand Up @@ -325,6 +330,9 @@ class LLVM_ABI MCStreamer {
void setAllowAutoPadding(bool v) { AllowAutoPadding = v; }
bool getAllowAutoPadding() const { return AllowAutoPadding; }

void setInlineAsmMode(bool v) { InlineAsmStrictMode = v; }
bool getInlineAsmMode() const { return InlineAsmStrictMode; }

MCSymbol *emitLineTableLabel();

/// When emitting an object file, create and emit a real label. When emitting
Expand Down
28 changes: 28 additions & 0 deletions llvm/lib/MC/MCParser/AsmParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ class AsmParser : public MCAsmParser {
DK_LTO_SET_CONDITIONAL,
DK_CFI_MTE_TAGGED_FRAME,
DK_MEMTAG,
DK_INLINE_ASM_MODE,
DK_END
};

Expand Down Expand Up @@ -703,6 +704,9 @@ class AsmParser : public MCAsmParser {
// ".lto_discard"
bool parseDirectiveLTODiscard();

// ".inline_asm_mode"
bool parseDirectiveInlineAsmMode(SMLoc DirectiveLoc);

// Directives to support address-significance tables.
bool parseDirectiveAddrsig();
bool parseDirectiveAddrsigSym();
Expand Down Expand Up @@ -2222,6 +2226,8 @@ bool AsmParser::parseStatement(ParseStatementInfo &Info,
return parseDirectiveLTODiscard();
case DK_MEMTAG:
return parseDirectiveSymbolAttribute(MCSA_Memtag);
case DK_INLINE_ASM_MODE:
return parseDirectiveInlineAsmMode(IDLoc);
}

return Error(IDLoc, "unknown directive");
Expand Down Expand Up @@ -5590,6 +5596,7 @@ void AsmParser::initializeDirectiveKindMap() {
DirectiveKindMap[".lto_discard"] = DK_LTO_DISCARD;
DirectiveKindMap[".lto_set_conditional"] = DK_LTO_SET_CONDITIONAL;
DirectiveKindMap[".memtag"] = DK_MEMTAG;
DirectiveKindMap[".inline_asm_mode"] = DK_INLINE_ASM_MODE;
}

MCAsmMacro *AsmParser::parseMacroLikeBody(SMLoc DirectiveLoc) {
Expand Down Expand Up @@ -5912,6 +5919,27 @@ bool AsmParser::parseDirectiveLTODiscard() {
return parseMany(ParseOp);
}

/// parseDirectiveInlineAsmMode
/// ::= ".inline_asm_mode" ( "strict" | "relaxed" )
bool AsmParser::parseDirectiveInlineAsmMode(SMLoc DirectiveLoc) {
if (getLexer().isNot(AsmToken::Identifier)) {
return Error(DirectiveLoc,
"expected 'strict' or 'relaxed' after '.inline_asm_mode'");
}

StringRef Mode = getTok().getIdentifier();
if (Mode == "strict") {
getStreamer().setInlineAsmMode(true);
} else if (Mode == "relaxed") {
getStreamer().setInlineAsmMode(false);
} else {
return Error(getTok().getLoc(), "expected 'strict' or 'relaxed'");
}

Lex(); // consume mode identifier
return false;
}

// We are comparing pointers, but the pointers are relative to a single string.
// Thus, this should always be deterministic.
static int rewritesSort(const AsmRewrite *AsmRewriteA,
Expand Down
15 changes: 15 additions & 0 deletions llvm/lib/MC/MCStreamer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/raw_ostream.h"
#include <cassert>
#include <cctype>
#include <cstdint>
#include <cstdlib>
#include <optional>
Expand Down Expand Up @@ -400,6 +401,20 @@ void MCStreamer::emitLabel(MCSymbol *Symbol, SMLoc Loc) {
return getContext().reportError(Loc, "symbol '" + Twine(Symbol->getName()) +
"' is already defined");

if (InlineAsmStrictMode) {
StringRef Name = Symbol->getName();
// Only numeric labels are considered safe in inline assembly
// Skip compiler-generated temporary labels (like .Ltmp0, .Ltmp1, etc.)
if (!Name.empty() && !std::isdigit(Name.front()) &&
!Name.starts_with(".Ltmp")) {
getContext().reportWarning(
Loc, "non-numeric label '" + Name +
"' in inline assembly strict mode may be unsafe for "
"external jumps; consider using numeric labels (1:, 2:, "
"etc.) instead");
}
}

assert(!Symbol->isVariable() && "Cannot emit a variable symbol!");
assert(getCurrentSectionOnly() && "Cannot emit before setting section!");
assert(!Symbol->getFragment() && "Unexpected fragment on symbol data!");
Expand Down
27 changes: 27 additions & 0 deletions llvm/test/MC/X86/inline-asm-mode-basic.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# RUN: llvm-mc -triple x86_64-unknown-unknown %s 2>&1 | FileCheck %s

# Test basic .inline_asm_mode directive functionality

.text

# Test that the directive is parsed correctly
.inline_asm_mode strict
.inline_asm_mode relaxed

# Test strict mode warnings
.inline_asm_mode strict

# This should produce a warning (non-numeric label)
# CHECK: warning: non-numeric label 'unsafe_global' in inline assembly strict mode may be unsafe for external jumps; consider using numeric labels (1:, 2:, etc.) instead
unsafe_global:
nop

# This should also warn
# CHECK: warning: non-numeric label '.L_unsafe_local' in inline assembly strict mode may be unsafe for external jumps; consider using numeric labels (1:, 2:, etc.) instead
.L_unsafe_local:
nop

# No warning
1:
nop

54 changes: 54 additions & 0 deletions llvm/test/MC/X86/inline-asm-mode-directive.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# RUN: llvm-mc -triple x86_64-unknown-unknown %s 2>&1 | FileCheck %s

# Test the .inline_asm_mode directive for safer inline assembly label handling

.text

# Test relaxed mode (default) - no warnings
.inline_asm_mode relaxed

# These labels should not produce warnings in relaxed mode
my_label:
nop
global_symbol:
nop
.L_local_label:
nop


# Test strict mode - should warn about non-numeric labels
.inline_asm_mode strict

# Only numeric labels are safe - should not warn
1:
nop
42:
nop
999:
nop

# All other labels should warn
# CHECK: :[[@LINE+1]]:1: warning: non-numeric label '.L_local1' in inline assembly strict mode may be unsafe for external jumps; consider using numeric labels (1:, 2:, etc.) instead
.L_local1:
nop

# CHECK: :[[@LINE+1]]:1: warning: non-numeric label '.L_local_with_numbers_123' in inline assembly strict mode may be unsafe for external jumps; consider using numeric labels (1:, 2:, etc.) instead
.L_local_with_numbers_123:
nop

# CHECK: :[[@LINE+1]]:1: warning: non-numeric label 'unsafe_label' in inline assembly strict mode may be unsafe for external jumps; consider using numeric labels (1:, 2:, etc.) instead
unsafe_label:
nop

# CHECK: :[[@LINE+1]]:1: warning: non-numeric label 'another_global' in inline assembly strict mode may be unsafe for external jumps; consider using numeric labels (1:, 2:, etc.) instead
another_global:
nop

# Switch back to relaxed mode
.inline_asm_mode relaxed

# This should not warn again
yet_another_label:
nop


13 changes: 13 additions & 0 deletions llvm/test/MC/X86/inline-asm-mode-errors.s
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# RUN: not llvm-mc -triple x86_64-unknown-unknown %s 2>&1 | FileCheck %s

# Test error handling for .inline_asm_mode directive

.text

# Test invalid mode
.inline_asm_mode invalid
# CHECK: error: expected 'strict' or 'relaxed'

# Test missing mode
.inline_asm_mode
# CHECK: error: expected 'strict' or 'relaxed' after '.inline_asm_mode'