Skip to content

Commit 6b622b3

Browse files
committed
draft: inline asm mode
1 parent 96ec1c2 commit 6b622b3

File tree

6 files changed

+240
-0
lines changed

6 files changed

+240
-0
lines changed

llvm/docs/InlineAsmSafetyDirective.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Inline Assembly Safety Directive
2+
3+
## Overview
4+
5+
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-local labels in inline assembly that could be inadvertently jumped to from external code.
6+
7+
## Syntax
8+
9+
```assembly
10+
.inline_asm_mode strict # Enable strict mode - warn on non-local labels
11+
.inline_asm_mode relaxed # Disable strict mode (default)
12+
```
13+
14+
## Description
15+
16+
When `.inline_asm_mode strict` is active, the assembler will emit warnings for labels that are considered potentially unsafe for inline assembly:
17+
18+
- **Safe labels** (no warnings):
19+
- Local labels starting with `.L` (e.g., `.L_loop`, `.L_end`)
20+
- Numeric labels (e.g., `1:`, `42:`)
21+
- Labels starting with special prefixes (`$`, `__`)
22+
- Labels starting with `.` (local scope)
23+
24+
- **Unsafe labels** (warnings emitted):
25+
- Global labels without special prefixes (e.g., `my_function:`, `loop:`)
26+
- Labels that could be accessed from outside the inline assembly block
27+
28+
## Use Cases
29+
30+
### Frontend Integration
31+
32+
Compiler frontends can use this directive when generating inline assembly:
33+
34+
```c++
35+
// Emitted by the compiler: .inline_asm_mode strict
36+
// C++ inline assembly example
37+
asm(
38+
".L_loop:\n" // Safe - no warning
39+
" add %0, %1\n"
40+
" jne .L_loop\n" // Safe - local jump
41+
"exit:\n" // Warning
42+
: "=r"(result) : "r"(input));
43+
```
44+
// Emitted by the compiler: .inline_asm_mode relaxed
45+
46+
## Rationale
47+
48+
Inline assembly blocks are often embedded within larger functions or modules. Non-local labels in these blocks can create several problems:
49+
50+
1. **Naming conflicts**: Global labels may conflict with other symbols in the compilation unit
51+
2. **Unintended control flow**: External code might accidentally jump to labels intended for internal use
52+
3. **Maintenance issues**: Global labels make inline assembly less encapsulated
53+
54+
The strict mode helps identify these potential issues during compilation, allowing developers to use safer local labels instead.
55+
56+
## Error Handling
57+
58+
Invalid directive usage will produce parse errors:
59+
60+
```assembly
61+
.inline_asm_mode invalid_mode
62+
# Error: expected 'strict' or 'relaxed'
63+
64+
.inline_asm_mode
65+
# Error: expected 'strict' or 'relaxed' after '.inline_asm_mode'
66+
```
67+
68+
## Implementation Details
69+
70+
- The directive affects only subsequent label definitions until changed
71+
- Default mode is `relaxed` (no additional warnings)
72+
- The directive state is maintained in the MC streamer
73+
- Warnings are emitted through the standard LLVM diagnostic system
74+
75+
## Examples
76+
77+
### Complete Example
78+
79+
```assembly
80+
.text
81+
.globl example_function
82+
example_function:
83+
# Regular function labels (outside inline asm) - no warnings
84+
85+
# Simulate inline assembly block with safety
86+
.inline_asm_mode strict
87+
88+
# These are safe
89+
.L_inline_start:
90+
mov $1, %eax
91+
test %eax, %eax
92+
jz .L_inline_end
93+
94+
1: # Numeric label
95+
inc %eax
96+
cmp $10, %eax
97+
jl 1b
98+
99+
.L_inline_end:
100+
# End of safe inline block
101+
102+
# This would generate a warning
103+
# global_inline_label: # Warning would be emitted
104+
105+
.inline_asm_mode relaxed
106+
107+
# Back to normal mode
108+
ret
109+
```
110+

llvm/include/llvm/MC/MCStreamer.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,11 @@ class LLVM_ABI MCStreamer {
260260
/// discussion for future inclusion.
261261
bool AllowAutoPadding = false;
262262

263+
/// Is strict inline assembly mode enabled? When enabled, the assembler
264+
/// will warn about non-local labels that could be unsafe jump targets
265+
/// in inline assembly blocks.
266+
bool InlineAsmStrictMode = false;
267+
263268
protected:
264269
// Symbol of the current epilog for which we are processing SEH directives.
265270
WinEH::FrameInfo::Epilog *CurrentWinEpilog = nullptr;
@@ -325,6 +330,9 @@ class LLVM_ABI MCStreamer {
325330
void setAllowAutoPadding(bool v) { AllowAutoPadding = v; }
326331
bool getAllowAutoPadding() const { return AllowAutoPadding; }
327332

333+
void setInlineAsmMode(bool v) { InlineAsmStrictMode = v; }
334+
bool getInlineAsmMode() const { return InlineAsmStrictMode; }
335+
328336
MCSymbol *emitLineTableLabel();
329337

330338
/// When emitting an object file, create and emit a real label. When emitting

llvm/lib/MC/MCParser/AsmParser.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,7 @@ class AsmParser : public MCAsmParser {
533533
DK_LTO_SET_CONDITIONAL,
534534
DK_CFI_MTE_TAGGED_FRAME,
535535
DK_MEMTAG,
536+
DK_INLINE_ASM_MODE,
536537
DK_END
537538
};
538539

@@ -703,6 +704,9 @@ class AsmParser : public MCAsmParser {
703704
// ".lto_discard"
704705
bool parseDirectiveLTODiscard();
705706

707+
// ".inline_asm_mode"
708+
bool parseDirectiveInlineAsmMode(SMLoc DirectiveLoc);
709+
706710
// Directives to support address-significance tables.
707711
bool parseDirectiveAddrsig();
708712
bool parseDirectiveAddrsigSym();
@@ -2222,6 +2226,8 @@ bool AsmParser::parseStatement(ParseStatementInfo &Info,
22222226
return parseDirectiveLTODiscard();
22232227
case DK_MEMTAG:
22242228
return parseDirectiveSymbolAttribute(MCSA_Memtag);
2229+
case DK_INLINE_ASM_MODE:
2230+
return parseDirectiveInlineAsmMode(IDLoc);
22252231
}
22262232

22272233
return Error(IDLoc, "unknown directive");
@@ -5590,6 +5596,7 @@ void AsmParser::initializeDirectiveKindMap() {
55905596
DirectiveKindMap[".lto_discard"] = DK_LTO_DISCARD;
55915597
DirectiveKindMap[".lto_set_conditional"] = DK_LTO_SET_CONDITIONAL;
55925598
DirectiveKindMap[".memtag"] = DK_MEMTAG;
5599+
DirectiveKindMap[".inline_asm_mode"] = DK_INLINE_ASM_MODE;
55935600
}
55945601

55955602
MCAsmMacro *AsmParser::parseMacroLikeBody(SMLoc DirectiveLoc) {
@@ -5912,6 +5919,27 @@ bool AsmParser::parseDirectiveLTODiscard() {
59125919
return parseMany(ParseOp);
59135920
}
59145921

5922+
/// parseDirectiveInlineAsmMode
5923+
/// ::= ".inline_asm_mode" ( "strict" | "relaxed" )
5924+
bool AsmParser::parseDirectiveInlineAsmMode(SMLoc DirectiveLoc) {
5925+
if (getLexer().isNot(AsmToken::Identifier)) {
5926+
return Error(DirectiveLoc,
5927+
"expected 'strict' or 'relaxed' after '.inline_asm_mode'");
5928+
}
5929+
5930+
StringRef Mode = getTok().getIdentifier();
5931+
if (Mode == "strict") {
5932+
getStreamer().setInlineAsmMode(true);
5933+
} else if (Mode == "relaxed") {
5934+
getStreamer().setInlineAsmMode(false);
5935+
} else {
5936+
return Error(getTok().getLoc(), "expected 'strict' or 'relaxed'");
5937+
}
5938+
5939+
Lex(); // consume mode identifier
5940+
return false;
5941+
}
5942+
59155943
// We are comparing pointers, but the pointers are relative to a single string.
59165944
// Thus, this should always be deterministic.
59175945
static int rewritesSort(const AsmRewrite *AsmRewriteA,

llvm/lib/MC/MCStreamer.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,18 @@ void MCStreamer::emitLabel(MCSymbol *Symbol, SMLoc Loc) {
400400
return getContext().reportError(Loc, "symbol '" + Twine(Symbol->getName()) +
401401
"' is already defined");
402402

403+
if (InlineAsmStrictMode) {
404+
StringRef Name = Symbol->getName();
405+
if (!Name.empty() && !Name.starts_with(".L") && !Name.starts_with("L..") &&
406+
!Name.starts_with("$") && !Name.starts_with("__") &&
407+
Name.front() != '.' && !std::isdigit(Name.front())) {
408+
getContext().reportWarning(
409+
Loc, "non-local label '" + Name +
410+
"' in inline assembly strict mode may be unsafe for "
411+
"external jumps; consider using local labels (.L*) instead");
412+
}
413+
}
414+
403415
assert(!Symbol->isVariable() && "Cannot emit a variable symbol!");
404416
assert(getCurrentSectionOnly() && "Cannot emit before setting section!");
405417
assert(!Symbol->getFragment() && "Unexpected fragment on symbol data!");
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# RUN: llvm-mc -triple x86_64-unknown-unknown %s 2>&1 | FileCheck %s
2+
3+
# Test basic .inline_asm_mode directive functionality
4+
5+
.text
6+
7+
# Test that the directive is parsed correctly
8+
.inline_asm_mode strict
9+
.inline_asm_mode relaxed
10+
11+
# Test strict mode warnings
12+
.inline_asm_mode strict
13+
14+
# This should produce a warning
15+
# CHECK: warning: non-local label 'unsafe_global' in inline assembly strict mode may be unsafe for external jumps; consider using local labels (.L*) instead
16+
unsafe_global:
17+
nop
18+
19+
# This should not warn (local label)
20+
.L_safe_local:
21+
nop
22+
23+
# Test error handling
24+
.inline_asm_mode invalid
25+
# CHECK: error: expected 'strict' or 'relaxed'
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# RUN: llvm-mc -triple x86_64-unknown-unknown %s 2>&1 | FileCheck %s --check-prefix=RELAX
2+
# RUN: llvm-mc -triple x86_64-unknown-unknown %s 2>&1 | FileCheck %s --check-prefix=STRICT
3+
4+
# Test the .inline_asm_mode directive for safer inline assembly label handling
5+
6+
.text
7+
8+
# Test relaxed mode (default) - no warnings
9+
.inline_asm_mode relaxed
10+
11+
# These labels should not produce warnings in relaxed mode
12+
my_label:
13+
nop
14+
global_symbol:
15+
nop
16+
.L_local_label:
17+
nop
18+
19+
# RELAX-NOT: warning
20+
21+
# Test strict mode - should warn about non-local labels
22+
.inline_asm_mode strict
23+
24+
# Local labels - should not warn
25+
.L_local1:
26+
nop
27+
.L_local_with_numbers_123:
28+
nop
29+
1:
30+
nop
31+
42:
32+
nop
33+
34+
# Non-local labels - should warn
35+
# STRICT: :[[@LINE+1]]:1: warning: non-local label 'unsafe_label' in inline assembly strict mode may be unsafe for external jumps; consider using local labels (.L*) instead
36+
unsafe_label:
37+
nop
38+
39+
# STRICT: :[[@LINE+1]]:1: warning: non-local label 'another_global' in inline assembly strict mode may be unsafe for external jumps; consider using local labels (.L*) instead
40+
another_global:
41+
nop
42+
43+
# Switch back to relaxed mode
44+
.inline_asm_mode relaxed
45+
46+
# This should not warn again
47+
yet_another_label:
48+
nop
49+
50+
# RELAX-NOT: warning
51+
52+
# Test error cases
53+
.inline_asm_mode invalid_mode
54+
# CHECK: :[[@LINE-1]]:18: error: expected 'strict' or 'relaxed'
55+
56+
.inline_asm_mode
57+
# CHECK: :[[@LINE-1]]:17: error: expected 'strict' or 'relaxed' after '.inline_asm_mode'

0 commit comments

Comments
 (0)