Skip to content

Commit 81a7816

Browse files
committed
auto merge of #8535 : nikomatsakis/rust/issue-3678-wrappers-be-gone-2, r=graydon
Long-standing branch to remove foreign function wrappers altogether. Calls to C functions are done "in place" with no stack manipulation; the scheme relies entirely on the correct use of `#[fixed_stack_segment]` to guarantee adequate stack space. A linter is added to detect when `#[fixed_stack_segment]` annotations are missing. An `externfn!` macro is added to make it easier to declare foreign fns and wrappers in one go: this macro may need some refinement, though, for example it might be good to be able to declare a group of foreign fns. I leave that for future work (hopefully somebody else's work :) ). Fixes #3678.
2 parents 3e4f40e + 0479d94 commit 81a7816

File tree

103 files changed

+2558
-1623
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

103 files changed

+2558
-1623
lines changed

doc/tutorial-ffi.md

+141
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ extern {
1919
fn snappy_max_compressed_length(source_length: size_t) -> size_t;
2020
}
2121
22+
#[fixed_stack_segment]
2223
fn main() {
2324
let x = unsafe { snappy_max_compressed_length(100) };
2425
println(fmt!("max compressed length of a 100 byte buffer: %?", x));
@@ -35,6 +36,11 @@ interfaces that aren't thread-safe, and almost any function that takes a pointer
3536
valid for all possible inputs since the pointer could be dangling, and raw pointers fall outside of
3637
Rust's safe memory model.
3738

39+
Finally, the `#[fixed_stack_segment]` annotation that appears on
40+
`main()` instructs the Rust compiler that when `main()` executes, it
41+
should request a "very large" stack segment. More details on
42+
stack management can be found in the following sections.
43+
3844
When declaring the argument types to a foreign function, the Rust compiler will not check if the
3945
declaration is correct, so specifying it correctly is part of keeping the binding correct at
4046
runtime.
@@ -75,6 +81,8 @@ length is number of elements currently contained, and the capacity is the total
7581
the allocated memory. The length is less than or equal to the capacity.
7682

7783
~~~~ {.xfail-test}
84+
#[fixed_stack_segment]
85+
#[inline(never)]
7886
pub fn validate_compressed_buffer(src: &[u8]) -> bool {
7987
unsafe {
8088
snappy_validate_compressed_buffer(vec::raw::to_ptr(src), src.len() as size_t) == 0
@@ -86,6 +94,36 @@ The `validate_compressed_buffer` wrapper above makes use of an `unsafe` block, b
8694
guarantee that calling it is safe for all inputs by leaving off `unsafe` from the function
8795
signature.
8896

97+
The `validate_compressed_buffer` wrapper is also annotated with two
98+
attributes `#[fixed_stack_segment]` and `#[inline(never)]`. The
99+
purpose of these attributes is to guarantee that there will be
100+
sufficient stack for the C function to execute. This is necessary
101+
because Rust, unlike C, does not assume that the stack is allocated in
102+
one continuous chunk. Instead, we rely on a *segmented stack* scheme,
103+
in which the stack grows and shrinks as necessary. C code, however,
104+
expects one large stack, and so callers of C functions must request a
105+
large stack segment to ensure that the C routine will not run off the
106+
end of the stack.
107+
108+
The compiler includes a lint mode that will report an error if you
109+
call a C function without a `#[fixed_stack_segment]` attribute. More
110+
details on the lint mode are given in a later section.
111+
112+
You may be wondering why we include a `#[inline(never)]` directive.
113+
This directive informs the compiler never to inline this function.
114+
While not strictly necessary, it is usually a good idea to use an
115+
`#[inline(never)]` directive in concert with `#[fixed_stack_segment]`.
116+
The reason is that if a fn annotated with `fixed_stack_segment` is
117+
inlined, then its caller also inherits the `fixed_stack_segment`
118+
annotation. This means that rather than requesting a large stack
119+
segment only for the duration of the call into C, the large stack
120+
segment would be used for the entire duration of the caller. This is
121+
not necessarily *bad* -- it can for example be more efficient,
122+
particularly if `validate_compressed_buffer()` is called multiple
123+
times in a row -- but it does work against the purpose of the
124+
segmented stack scheme, which is to keep stacks small and thus
125+
conserve address space.
126+
89127
The `snappy_compress` and `snappy_uncompress` functions are more complex, since a buffer has to be
90128
allocated to hold the output too.
91129

@@ -96,6 +134,8 @@ the true length after compression for setting the length.
96134

97135
~~~~ {.xfail-test}
98136
pub fn compress(src: &[u8]) -> ~[u8] {
137+
#[fixed_stack_segment]; #[inline(never)];
138+
99139
unsafe {
100140
let srclen = src.len() as size_t;
101141
let psrc = vec::raw::to_ptr(src);
@@ -116,6 +156,8 @@ format and `snappy_uncompressed_length` will retrieve the exact buffer size requ
116156

117157
~~~~ {.xfail-test}
118158
pub fn uncompress(src: &[u8]) -> Option<~[u8]> {
159+
#[fixed_stack_segment]; #[inline(never)];
160+
119161
unsafe {
120162
let srclen = src.len() as size_t;
121163
let psrc = vec::raw::to_ptr(src);
@@ -139,6 +181,99 @@ pub fn uncompress(src: &[u8]) -> Option<~[u8]> {
139181
For reference, the examples used here are also available as an [library on
140182
GitHub](https://github.com/thestinger/rust-snappy).
141183

184+
# Automatic wrappers
185+
186+
Sometimes writing Rust wrappers can be quite tedious. For example, if
187+
function does not take any pointer arguments, often there is no need
188+
for translating types. In such cases, it is usually still a good idea
189+
to have a Rust wrapper so as to manage the segmented stacks, but you
190+
can take advantage of the (standard) `externfn!` macro to remove some
191+
of the tedium.
192+
193+
In the initial section, we showed an extern block that added a call
194+
to a specific snappy API:
195+
196+
~~~~ {.xfail-test}
197+
use std::libc::size_t;
198+
199+
#[link_args = "-lsnappy"]
200+
extern {
201+
fn snappy_max_compressed_length(source_length: size_t) -> size_t;
202+
}
203+
204+
#[fixed_stack_segment]
205+
fn main() {
206+
let x = unsafe { snappy_max_compressed_length(100) };
207+
println(fmt!("max compressed length of a 100 byte buffer: %?", x));
208+
}
209+
~~~~
210+
211+
To avoid the need to create a wrapper fn for `snappy_max_compressed_length()`,
212+
and also to avoid the need to think about `#[fixed_stack_segment]`, we
213+
could simply use the `externfn!` macro instead, as shown here:
214+
215+
~~~~ {.xfail-test}
216+
use std::libc::size_t;
217+
218+
externfn!(#[link_args = "-lsnappy"]
219+
fn snappy_max_compressed_length(source_length: size_t) -> size_t)
220+
221+
fn main() {
222+
let x = unsafe { snappy_max_compressed_length(100) };
223+
println(fmt!("max compressed length of a 100 byte buffer: %?", x));
224+
}
225+
~~~~
226+
227+
As you can see from the example, `externfn!` replaces the extern block
228+
entirely. After macro expansion, it will create something like this:
229+
230+
~~~~ {.xfail-test}
231+
use std::libc::size_t;
232+
233+
// Automatically generated by
234+
// externfn!(#[link_args = "-lsnappy"]
235+
// fn snappy_max_compressed_length(source_length: size_t) -> size_t)
236+
unsafe fn snappy_max_compressed_length(source_length: size_t) -> size_t {
237+
#[fixed_stack_segment]; #[inline(never)];
238+
return snappy_max_compressed_length(source_length);
239+
240+
#[link_args = "-lsnappy"]
241+
extern {
242+
fn snappy_max_compressed_length(source_length: size_t) -> size_t;
243+
}
244+
}
245+
246+
fn main() {
247+
let x = unsafe { snappy_max_compressed_length(100) };
248+
println(fmt!("max compressed length of a 100 byte buffer: %?", x));
249+
}
250+
~~~~
251+
252+
# Segmented stacks and the linter
253+
254+
By default, whenever you invoke a non-Rust fn, the `cstack` lint will
255+
check that one of the following conditions holds:
256+
257+
1. The call occurs inside of a fn that has been annotated with
258+
`#[fixed_stack_segment]`;
259+
2. The call occurs inside of an `extern fn`;
260+
3. The call occurs within a stack closure created by some other
261+
safe fn.
262+
263+
All of these conditions ensure that you are running on a large stack
264+
segmented. However, they are sometimes too strict. If your application
265+
will be making many calls into C, it is often beneficial to promote
266+
the `#[fixed_stack_segment]` attribute higher up the call chain. For
267+
example, the Rust compiler actually labels main itself as requiring a
268+
`#[fixed_stack_segment]`. In such cases, the linter is just an
269+
annoyance, because all C calls that occur from within the Rust
270+
compiler are made on a large stack. Another situation where this
271+
frequently occurs is on a 64-bit architecture, where large stacks are
272+
the default. In cases, you can disable the linter by including a
273+
`#[allow(cstack)]` directive somewhere, which permits violations of
274+
the "cstack" rules given above (you can also use `#[warn(cstack)]` to
275+
convert the errors into warnings, if you prefer).
276+
142277
# Destructors
143278

144279
Foreign libraries often hand off ownership of resources to the calling code,
@@ -161,6 +296,9 @@ pub struct Unique<T> {
161296
162297
impl<T: Send> Unique<T> {
163298
pub fn new(value: T) -> Unique<T> {
299+
#[fixed_stack_segment];
300+
#[inline(never)];
301+
164302
unsafe {
165303
let ptr = malloc(std::sys::size_of::<T>() as size_t) as *mut T;
166304
assert!(!ptr::is_null(ptr));
@@ -184,6 +322,9 @@ impl<T: Send> Unique<T> {
184322
#[unsafe_destructor]
185323
impl<T: Send> Drop for Unique<T> {
186324
fn drop(&self) {
325+
#[fixed_stack_segment];
326+
#[inline(never)];
327+
187328
unsafe {
188329
let x = intrinsics::init(); // dummy value to swap in
189330
// moving the object out is needed to call the destructor

src/libextra/c_vec.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -155,12 +155,20 @@ mod tests {
155155
use std::libc;
156156

157157
fn malloc(n: size_t) -> CVec<u8> {
158+
#[fixed_stack_segment];
159+
#[inline(never)];
160+
158161
unsafe {
159162
let mem = libc::malloc(n);
160163

161164
assert!(mem as int != 0);
162165

163-
c_vec_with_dtor(mem as *mut u8, n as uint, || free(mem))
166+
return c_vec_with_dtor(mem as *mut u8, n as uint, || f(mem));
167+
}
168+
169+
fn f(mem: *c_void) {
170+
#[fixed_stack_segment]; #[inline(never)];
171+
unsafe { libc::free(mem) }
164172
}
165173
}
166174

src/libextra/flate.rs

+4
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ static TINFL_FLAG_PARSE_ZLIB_HEADER : c_int = 0x1; // parse zlib header and adle
4747
static TDEFL_WRITE_ZLIB_HEADER : c_int = 0x01000; // write zlib header and adler32 checksum
4848

4949
fn deflate_bytes_internal(bytes: &[u8], flags: c_int) -> ~[u8] {
50+
#[fixed_stack_segment]; #[inline(never)];
51+
5052
do bytes.as_imm_buf |b, len| {
5153
unsafe {
5254
let mut outsz : size_t = 0;
@@ -73,6 +75,8 @@ pub fn deflate_bytes_zlib(bytes: &[u8]) -> ~[u8] {
7375
}
7476

7577
fn inflate_bytes_internal(bytes: &[u8], flags: c_int) -> ~[u8] {
78+
#[fixed_stack_segment]; #[inline(never)];
79+
7680
do bytes.as_imm_buf |b, len| {
7781
unsafe {
7882
let mut outsz : size_t = 0;

src/libextra/rl.rs

+19-9
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,25 @@ use std::str;
1919
pub mod rustrt {
2020
use std::libc::{c_char, c_int};
2121

22-
extern {
23-
pub fn linenoise(prompt: *c_char) -> *c_char;
24-
pub fn linenoiseHistoryAdd(line: *c_char) -> c_int;
25-
pub fn linenoiseHistorySetMaxLen(len: c_int) -> c_int;
26-
pub fn linenoiseHistorySave(file: *c_char) -> c_int;
27-
pub fn linenoiseHistoryLoad(file: *c_char) -> c_int;
28-
pub fn linenoiseSetCompletionCallback(callback: *u8);
29-
pub fn linenoiseAddCompletion(completions: *(), line: *c_char);
22+
#[cfg(stage0)]
23+
mod macro_hack {
24+
#[macro_escape];
25+
macro_rules! externfn(
26+
(fn $name:ident ($($arg_name:ident : $arg_ty:ty),*) $(-> $ret_ty:ty),*) => (
27+
extern {
28+
fn $name($($arg_name : $arg_ty),*) $(-> $ret_ty),*;
29+
}
30+
)
31+
)
3032
}
33+
34+
externfn!(fn linenoise(prompt: *c_char) -> *c_char)
35+
externfn!(fn linenoiseHistoryAdd(line: *c_char) -> c_int)
36+
externfn!(fn linenoiseHistorySetMaxLen(len: c_int) -> c_int)
37+
externfn!(fn linenoiseHistorySave(file: *c_char) -> c_int)
38+
externfn!(fn linenoiseHistoryLoad(file: *c_char) -> c_int)
39+
externfn!(fn linenoiseSetCompletionCallback(callback: *u8))
40+
externfn!(fn linenoiseAddCompletion(completions: *(), line: *c_char))
3141
}
3242

3343
/// Add a line to history
@@ -84,7 +94,7 @@ pub unsafe fn complete(cb: CompletionCb) {
8494
rustrt::linenoiseAddCompletion(completions, buf);
8595
}
8696
}
87-
}
97+
}
8898
}
8999
}
90100

src/libextra/test.rs

+2
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ fn optgroups() -> ~[getopts::groups::OptGroup] {
188188
}
189189

190190
fn usage(binary: &str, helpstr: &str) -> ! {
191+
#[fixed_stack_segment]; #[inline(never)];
192+
191193
let message = fmt!("Usage: %s [OPTIONS] [FILTER]", binary);
192194
println(groups::usage(message, optgroups()));
193195
println("");

src/libextra/time.rs

+12
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ impl Ord for Timespec {
6464
* nanoseconds since 1970-01-01T00:00:00Z.
6565
*/
6666
pub fn get_time() -> Timespec {
67+
#[fixed_stack_segment]; #[inline(never)];
68+
6769
unsafe {
6870
let mut sec = 0i64;
6971
let mut nsec = 0i32;
@@ -78,6 +80,8 @@ pub fn get_time() -> Timespec {
7880
* in nanoseconds since an unspecified epoch.
7981
*/
8082
pub fn precise_time_ns() -> u64 {
83+
#[fixed_stack_segment]; #[inline(never)];
84+
8185
unsafe {
8286
let mut ns = 0u64;
8387
rustrt::precise_time_ns(&mut ns);
@@ -95,6 +99,8 @@ pub fn precise_time_s() -> float {
9599
}
96100

97101
pub fn tzset() {
102+
#[fixed_stack_segment]; #[inline(never)];
103+
98104
unsafe {
99105
rustrt::rust_tzset();
100106
}
@@ -135,6 +141,8 @@ pub fn empty_tm() -> Tm {
135141

136142
/// Returns the specified time in UTC
137143
pub fn at_utc(clock: Timespec) -> Tm {
144+
#[fixed_stack_segment]; #[inline(never)];
145+
138146
unsafe {
139147
let Timespec { sec, nsec } = clock;
140148
let mut tm = empty_tm();
@@ -150,6 +158,8 @@ pub fn now_utc() -> Tm {
150158

151159
/// Returns the specified time in the local timezone
152160
pub fn at(clock: Timespec) -> Tm {
161+
#[fixed_stack_segment]; #[inline(never)];
162+
153163
unsafe {
154164
let Timespec { sec, nsec } = clock;
155165
let mut tm = empty_tm();
@@ -176,6 +186,8 @@ pub fn strftime(format: &str, tm: &Tm) -> ~str {
176186
impl Tm {
177187
/// Convert time to the seconds from January 1, 1970
178188
pub fn to_timespec(&self) -> Timespec {
189+
#[fixed_stack_segment]; #[inline(never)];
190+
179191
unsafe {
180192
let sec = match self.tm_gmtoff {
181193
0_i32 => rustrt::rust_timegm(self),

src/librust/rust.rs

+2
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ fn usage() {
229229
}
230230

231231
pub fn main() {
232+
#[fixed_stack_segment]; #[inline(never)];
233+
232234
let os_args = os::args();
233235

234236
if (os_args.len() > 1 && (os_args[1] == ~"-v" || os_args[1] == ~"--version")) {

src/librustc/back/upcall.rs

-5
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ use lib::llvm::{ModuleRef, ValueRef};
1616

1717
pub struct Upcalls {
1818
trace: ValueRef,
19-
call_shim_on_c_stack: ValueRef,
20-
call_shim_on_rust_stack: ValueRef,
2119
rust_personality: ValueRef,
2220
reset_stack_limit: ValueRef
2321
}
@@ -47,9 +45,6 @@ pub fn declare_upcalls(targ_cfg: @session::config, llmod: ModuleRef) -> @Upcalls
4745

4846
@Upcalls {
4947
trace: upcall!(fn trace(opaque_ptr, opaque_ptr, int_ty) -> Type::void()),
50-
call_shim_on_c_stack: upcall!(fn call_shim_on_c_stack(opaque_ptr, opaque_ptr) -> int_ty),
51-
call_shim_on_rust_stack:
52-
upcall!(fn call_shim_on_rust_stack(opaque_ptr, opaque_ptr) -> int_ty),
5348
rust_personality: upcall!(nothrow fn rust_personality -> Type::i32()),
5449
reset_stack_limit: upcall!(nothrow fn reset_stack_limit -> Type::void())
5550
}

0 commit comments

Comments
 (0)