Skip to content

Commit 47f65a8

Browse files
committed
Merge remote-tracking branch 'tari/naked-fns'
2 parents 1dd5f1d + 1cc857f commit 47f65a8

File tree

1 file changed

+218
-0
lines changed

1 file changed

+218
-0
lines changed

text/0000-naked-fns.md

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
- Feature Name: `naked_fns`
2+
- Start Date: 2015-07-10
3+
- RFC PR: (leave this empty)
4+
- Rust Issue: (leave this empty)
5+
6+
# Summary
7+
8+
Add support for generating naked (prologue/epilogue-free) functions via a new
9+
function attribute.
10+
11+
# Motivation
12+
13+
Some systems programming tasks require that the programmer have complete control
14+
over function stack layout and interpretation, generally in cases where the
15+
compiler lacks support for a specific use case. While these cases can be
16+
addressed by building the requisite code with external tools and linking with
17+
Rust, it is advantageous to allow the Rust compiler to drive the entire process,
18+
particularly in that code may be generated via monomorphization or macro
19+
expansion.
20+
21+
When writing interrupt handlers for example, most systems require additional
22+
state be saved beyond the usual ABI requirements. To avoid corrupting program
23+
state, the interrupt handler must save the registers which might be modified
24+
before handing control to compiler-generated code. Consider a contrived
25+
interrupt handler for x86\_64:
26+
27+
```rust
28+
unsafe fn isr_nop() {
29+
asm!("push %rax"
30+
/* Additional pushes elided */ :::: "volatile");
31+
let n = 0u64;
32+
asm!("pop %rax"
33+
/* Additional pops elided */ :::: "volatile");
34+
}
35+
```
36+
37+
The generated assembly for this function might resemble the following
38+
(simplified for readability):
39+
40+
```x86
41+
isr_nop:
42+
sub $8, %rsp
43+
push %rax
44+
movq $0, 0(%rsp)
45+
pop %rax
46+
add $8, %rsp
47+
retq
48+
```
49+
50+
Here the programmer's need to save machine state conflicts with the compiler's
51+
assumption that it has complete control over stack layout, with the result that
52+
the saved value of `rax` is clobbered by the compiler. Given that details of
53+
stack layout for any given function are not predictable (and may change with
54+
compiler version or optimization settings), attempting to predict the stack
55+
layout to sidestep this issue is infeasible.
56+
57+
When interacting with FFIs that are not natively supported by the compiler,
58+
a similar situation arises where the programmer knows the expected calling
59+
convention and can implement a translation between the foreign ABI and one
60+
supported by the compiler.
61+
62+
Support for naked functions also allows programmers to write functions that
63+
would otherwise be unsafe, such as the following snippet which returns the
64+
address of its caller when called with the C ABI on x86.
65+
66+
```
67+
mov 4(%ebp), %eax
68+
ret
69+
```
70+
71+
---
72+
73+
Because the compiler depends on a function prologue and epilogue to maintain
74+
storage for local variable bindings, it is generally unsafe to write anything
75+
but inline assembly inside a naked function. The [LLVM language
76+
reference](http://llvm.org/docs/LangRef.html#function-attributes) describes this
77+
feature as having "very system-specific consequences", which the programmer must
78+
be aware of.
79+
80+
# Detailed design
81+
82+
Add a new function attribute to the language, `#[naked]`, indicating the
83+
function should have prologue/epilogue emission disabled.
84+
85+
Because the calling convention of a naked function is not guaranteed to match
86+
any calling convention the compiler is compatible with, calls to naked functions
87+
from within Rust code are forbidden unless the function is also declared with
88+
a well-defined ABI.
89+
90+
Defining a naked function with the default (Rust) ABI is an error, because the
91+
Rust ABI is unspecified and the programmer can never write a function which is
92+
guaranteed to be compatible. For example, The function declaration of `foo` in
93+
the following code block is an error.
94+
95+
```rust
96+
#[naked]
97+
unsafe fn foo() { }
98+
```
99+
100+
The following variant is not an error because the C calling convention is
101+
well-defined and it is thus possible for the programmer to write a conforming
102+
function:
103+
104+
```rust
105+
#[naked]
106+
extern "C" fn foo() { }
107+
```
108+
109+
---
110+
111+
Because the compiler cannot verify the correctness of code written in a naked
112+
function (since it may have an unknown calling convention), naked functions must
113+
be declared `unsafe` or contain no non-`unsafe` statements in the body. The
114+
function `error` in the following code block is a compile-time error, whereas
115+
the functions `correct1` and `correct2` are permitted.
116+
117+
```
118+
#[naked]
119+
extern "C" fn error(x: &mut u8) {
120+
*x += 1;
121+
}
122+
123+
#[naked]
124+
unsafe extern "C" fn correct1(x: &mut u8) {
125+
*x += 1;
126+
}
127+
128+
#[naked]
129+
extern "C" fn correct2() {
130+
unsafe {
131+
*x += 1;
132+
}
133+
}
134+
```
135+
136+
## Example
137+
138+
The following example illustrates the possible use of a naked function for
139+
implementation of an interrupt service routine on 32-bit x86.
140+
141+
```rust
142+
use std::intrinsics;
143+
use std::sync::atomic::{self, AtomicUsize, Ordering};
144+
145+
#[naked]
146+
#[cfg(target_arch="x86")]
147+
unsafe fn isr_3() {
148+
asm!("pushad
149+
call increment_breakpoint_count
150+
popad
151+
iretd" :::: "volatile");
152+
intrinsics::unreachable();
153+
}
154+
155+
static bp_count: AtomicUsize = ATOMIC_USIZE_INIT;
156+
157+
#[no_mangle]
158+
pub fn increment_breakpoint_count() {
159+
bp_count.fetch_add(1, Ordering::Relaxed);
160+
}
161+
162+
fn register_isr(vector: u8, handler: fn() -> ()) { /* ... */ }
163+
164+
fn main() {
165+
register_isr(3, isr_3);
166+
// ...
167+
}
168+
```
169+
170+
## Implementation Considerations
171+
172+
The current support for `extern` functions in `rustc` generates a minimum of two
173+
basic blocks for any function declared in Rust code with a non-default calling
174+
convention: a trampoline which translates the declared calling convention to the
175+
Rust convention, and a Rust ABI version of the function containing the actual
176+
implementation. Calls to the function from Rust code call the Rust ABI version
177+
directly.
178+
179+
For naked functions, it is impossible for the compiler to generate a Rust ABI
180+
version of the function because the implementation may depend on the calling
181+
convention. In cases where calling a naked function from Rust is permitted, the
182+
compiler must be able to use the target calling convention directly rather than
183+
call the same function with the Rust convention.
184+
185+
# Drawbacks
186+
187+
The utility of this feature is extremely limited to most users, and it might be
188+
misused if the implications of writing a naked function are not carefully
189+
considered.
190+
191+
# Alternatives
192+
193+
Do nothing. The required functionality for the use case outlined can be
194+
implemented outside Rust code and linked in as needed. Support for additional
195+
calling conventions could be added to the compiler as needed, or emulated with
196+
external libraries such as `libffi`.
197+
198+
# Unresolved questions
199+
200+
It is easy to quietly generate wrong code in naked functions, such as by causing
201+
the compiler to allocate stack space for temporaries where none were
202+
anticipated. There is currently no restriction on writing Rust statements inside
203+
a naked function, while most compilers supporting similar features either
204+
require or strongly recommend that authors write only inline assembly inside
205+
naked functions to ensure no code is generated that assumes a particular stack
206+
layout. It may be desirable to place further restrictions on what statements are
207+
permitted in the body of a naked function, such as permitting only `asm!`
208+
statements.
209+
210+
The `unsafe` requirement on naked functions may not be desirable in all cases.
211+
However, relaxing that requirement in the future would not be a breaking change.
212+
213+
Because a naked function may use a calling convention unknown to the compiler,
214+
it may be useful to add a "unknown" calling convention to the compiler which is
215+
illegal to call directly. Absent this feature, functions implementing an unknown
216+
ABI would need to be declared with a calling convention which is known to be
217+
incorrect and depend on the programmer to avoid calling such a function
218+
incorrectly since it cannot be prevented statically.

0 commit comments

Comments
 (0)