-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Add limited CTFE #253
Closed
Closed
Add limited CTFE #253
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
- Start Date: (fill me in with today's date, YYYY-MM-DD) | ||
- RFC PR: (leave this empty) | ||
- Rust Issue: (leave this empty) | ||
|
||
# Summary | ||
|
||
Implement compile time evaluation of a limited set of compiler-internal | ||
functions: | ||
|
||
- `size_of` | ||
- `min_align_of` | ||
- `pref_align_of` | ||
|
||
# Motivation | ||
|
||
Today it is not possible to use the size of a type in a constant expression. | ||
|
||
C11 defines two "functions" that can be used in constant expressions: | ||
|
||
- `sizeof` | ||
- `_Alignof` | ||
|
||
For example, glibc defines the following struct | ||
```c | ||
# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int))) | ||
typedef struct | ||
{ | ||
unsigned long int __val[_SIGSET_NWORDS]; | ||
} __sigset_t; | ||
``` | ||
which cannot be used in rust FFI without knowing the size of `long` at compile | ||
time. This size could be defined in `liblibc`, but this is error prone an | ||
stops working when the argument of `sizeof` is an aggregate type. | ||
|
||
Another use case is the following type which could be used to store types whose | ||
destructor should not run in all cases. (Note that there are other problems | ||
with this implementation that might make this impossible.) | ||
|
||
```rust | ||
#[repr(C)] | ||
struct ManuallyDrop<T> { | ||
data: [u8, ..size_of::<T>()], | ||
_ty: [T, ..0], | ||
} | ||
``` | ||
|
||
This could then be used in a `SmallVec` implementation: | ||
```rust | ||
struct SmallVec<T> { | ||
// Used when there are more than 5 elements | ||
ptr: *mut T, | ||
len: uint, | ||
cap: uint, | ||
small: [ManuallyDrop<T>, ..5], | ||
} | ||
``` | ||
|
||
Even better: You could use this to create a poor man's `SmallVec<N, T>`: | ||
```rust | ||
struct SmallVec<Size, T> { | ||
// ... | ||
small: [ManuallyDrop<T>, ..size_of::<Size>()], | ||
} | ||
// seven elements on the stack | ||
let x: SmallVec<[u8, ..7], int> = SmallVec::new(); | ||
``` | ||
|
||
# Detailed design | ||
|
||
Consider a constant expression which contains an expression `expr` of the form | ||
```rust | ||
PATH::<T>() | ||
``` | ||
where `PATH` resolves to one of the following functions | ||
```rust | ||
extern "rust-intrinsic" { | ||
fn size_of<T>() -> uint; | ||
fn min_align_of<T>() -> uint; | ||
fn pref_align_of<T>() -> uint; | ||
} | ||
``` | ||
and where `T` is a `Sized` type or type parameter. | ||
|
||
1. For each expression `expr` where `T` is not a type parameter, the compiler | ||
evaluates `expr` as if it had been evaluated at runtime and then behaves as | ||
if `expr` had been replaced by the fully qualified result of the | ||
computation. | ||
|
||
1. If at least one of the `expr` contains a `T` which is a type parameter, the | ||
compiler behaves as if it were implemented in the following way: | ||
|
||
* Type checking: Check if the constant expression were valid if all `expr` | ||
were replaced by `0u`. | ||
* Store the constant expression in some implementation defined way such | ||
that the process in 1. can be applied after monomorphization. | ||
|
||
# Examples | ||
|
||
The following examples contain two marked lines of code each and the compiler | ||
treats the second line as if it had been replaced by the first one in the | ||
original source code. | ||
|
||
### Example 1 | ||
|
||
```rust | ||
static I8_SIZE: uint = 1u; // After CTFE | ||
static I8_SIZE: uint = core::intrinsics::size_of::<i8>(); // Before CTFE | ||
``` | ||
|
||
### Example 2 | ||
|
||
```rust | ||
use core::intrinsics::{size_of}; | ||
|
||
static I8_SIZE: uint = 1u; // After CTFE | ||
static I8_SIZE: uint = size_of::<i8>(); // Before CTFE | ||
``` | ||
|
||
### Example 3 | ||
|
||
```rust | ||
#![no_std] | ||
extern "rust-intrinsic" { | ||
fn size_of<T>() -> uint; | ||
} | ||
|
||
static I8_SIZE: uint = 1u; // After CTFE | ||
static I8_SIZE: uint = size_of::<i8>(); // Before CTFE | ||
``` | ||
|
||
### Example 4 | ||
|
||
#### Original Code | ||
|
||
```rust | ||
#[repr(C)] | ||
struct ManuallyDrop<T> { | ||
data: [u8, ..size_of::<T>()], | ||
_ty: [T, ..0], | ||
} | ||
``` | ||
|
||
#### After compilation | ||
|
||
```rust | ||
#[repr(C)] | ||
struct ManuallyDrop<T> { | ||
data: [u8, ..ConstExpr<T>], // Will be evaluated after monomorphization.. | ||
_ty: [T, ..0], | ||
} | ||
``` | ||
|
||
#### After monomorphization | ||
|
||
```rust | ||
#[repr(C)] | ||
struct ManuallyDrop<u64> { | ||
data: [u8, ..8u], | ||
_ty: [u64, ..0], | ||
} | ||
``` | ||
|
||
### Example 5 | ||
|
||
Compile time error. | ||
|
||
```rust | ||
fn f<T>() { | ||
static i: int = size_of::<T>(); | ||
} | ||
``` | ||
``` | ||
mismatched types: expected `int`, found `uint` (expected int, found uint) | ||
<anon>:2 static i: int = size_of::<T>(); | ||
^~~~~~~~~~~~~~ | ||
``` | ||
|
||
# Drawbacks | ||
|
||
It is unlikely that this can be replaced by CTFE in a backwards compatible way | ||
because `size_of` and friends are extern functions and calling them normally | ||
requires an unsafe block. Unfortunately you cannot instead check if `PATH` in | ||
the detailed description above resolves to `core::mem::size_of` because some | ||
programs might wish to avoid even `libcore`. | ||
|
||
Thus, this feature should live behind a feature gate. | ||
|
||
# Alternatives | ||
|
||
- Add new keywords `sizeof` and `alignof` to the language that are evaluated at | ||
compile time. | ||
- Add new language items `#[lang(sizeof)]` and `#[lang(alignof)]` that can be | ||
applied to functions. When these functions are used in constant expressions, | ||
instead of evaluating the functions themselves, some compiler internal function | ||
is executed and the compiler behaves as described in the description above. | ||
These language items would then be applied to `core::mem::{size_of, align_of}`. | ||
If someone wishes to not use `libcore`, they can apply them to two dummy | ||
functions. | ||
|
||
# Unresolved questions | ||
|
||
- These functions are still not as powerful as `sizeof` and `_Alignof` which can | ||
take arbitrary expressions as arguments, e.g., `sizeof(1L) == sizeof(long)`. | ||
Rust also has a function that can do this: `size_of_val<T>(val: T)` which then | ||
calls `size_of::<T>()`. Unfortunately `size_of_val` is a real function and not | ||
a `extern "rust-intrinsic"`. Thus you cannot use it in programs that wish to | ||
avoid `libcore`. How could this be done? Note that this would not be a problem with | ||
the two alternatives above. | ||
- There are many more intrinsic (math) functions which could be evaluated at | ||
compile time. Should these functions be allowed in constant expressions? Note that | ||
this is not possible with the two alternatives above. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All new features are going to end up landing behind a feature gate at first, so this part isn't really relevant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant that this should be behind a feature gate until real CTFE has been implemented (if ever). Real CTFE would immediately replace this, but because this is not 100% compatible you would have to continue to maintain this if it's not marked unstable in some way.