You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Initial support for dynamically linked crates
This PR is an initial implementation of rust-lang/rfcs#3435 proposal.
### component 1: interface generator
Interface generator - a tool for generating a stripped version of crate source code. The interface is like a C header with only "exported" items included, and function bodies are omitted. For example, initial crate:
```rust
#[export]
#[repr(C)]
pub struct S {
pub x: i32
}
#[export]
pub extern "C" fn foo(x: S) {
m1::bar(x);
}
pub fn bar(x: crate::S) {
// some computations
}
```
generated interface:
```rust
#[export]
#[repr(C)]
pub struct S {
pub x: i32,
}
#[export]
pub extern "C" fn foo(x: S);
```
In this example `bar` function is not a part of the interface as it doesn't have `#[export]` attribute.
To emit interface use a new `sdylib` crate type which is basically the same as `dylib`, but it also produces the interface as a second artifact. The current interface name is `lib{crate_name}.rs`.
Interface generator was implemented as part of the HIR pretty-printer. In order to filter out unnecessary items, the `PpAnn` trait was used.
#### Why was it decided to use a design with an auto-generated interface?
One of the main objectives of this proposal is to allow building the library and the application with different compiler versions. This requires either a metadata format compatible across rustc versions or some form of a source code. The option with a stable metadata format was rejected because it is not a part of the RFC. ([discussion](rust-lang/rfcs#3435 (comment)))
Regarding the design with interfaces there are 2 possibilities: manually written or auto-generated. I would personally prefer the auto-generated interface for the following reason: we can put it in the dynamic library instead of metadata, which will make it completely invisible to users. (this was my initial plan, but since the PR is already big enough, I decided to postpone it)
But even if we end up with a different design, I believe the interface generator could be a useful tool for testing and experimenting with the entire feature.
### component 2: crate loader
When building dynamic dependencies, the crate loader searches for the interface in the file system, builds the interface without codegen and loads it's metadata. For now, it's assumed that interface and dynamic lib are located in the same directory. `extern dyn crate` annotation serves as a signal for the building of a dynamic dependency.
Here are the code and commands that corresponds to the compilation process:
```rust
// simple-lib.rs
#![crate_type = "sdylib"]
#[extern]
pub extern "C" fn foo() -> i32 {
42
}
```
``` rust
// app.rs
extern dyn crate simple_lib;
fn main() {
assert!(simple_lib::foo(), 42);
}
```
```
// Generate interface, build library.
rustc +toolchain1 lib.rs -Csymbol-mangling-version=v0
// Build app. Perhaps with a different compiler version.
rustc +toolchain2 app.rs -L. -Csymbol-mangling-version=v0
```
P.S. The interface name/format and rules for file system routing can be changed further.
### component 3: exportable items collector
Query for collecting exportable items. Which items are exportable is defined [here](https://github.com/m-ou-se/rfcs/blob/export/text/0000-export.md#the-export-attribute) .
### component 4: "stable" mangling scheme
The mangling scheme proposed in the RFC consists of two parts: a mangled item path and a hash of the signature.
#### mangled item path
For the first part of the symbol it has been decided to reuse the `v0` mangling scheme as it is _mostly_ independent of the compiler internals.
The first exception is an impl's disambiguation. During symbol mangling rustc uses a special index to distinguish between two impls of the same type in the same module(See `DisambiguatedDefPathData`). The calculation of this index may depend on private items, but private items should not affect the ABI. Example:
```rust
#[export]
#[repr(C)]
pub struct S<T>(pub T);
struct S1;
pub struct S2;
// This impl is not part of the interface.
impl S<S1> {
extern "C" fn foo() -> i32 {
1
}
}
#[export]
impl S<S2> {
// Different symbol names can be generated for this item
// when compiling the interface and source code.
pub extern "C" fn foo() -> i32 {
2
}
}
```
In order to make disambiguation independent of the compiler version we can assign an id to each impl according to their relative order in the source code. However, I have not found the right way to get this order, so I decided to use:
1. The sequential number of an impl during the `intravisit::Visitor` traversal.
2. A new attribute `#[rustc_stable_impl_id]` that outputs this sequential number as a compiler error. If the visitor's implementation is changed, the corresponding test will fail. Then you will need to rewrite the implementation of `stable_order_of_exportable_impls` query to preserve order.
P.S. is it possible to compare spans instead?
The second exception is `StableCrateId` which is used to disambiguate different crates. `StableCrateId` consists of crate name, `-Cmetadata` arguments and compiler version. At the moment, I have decided to keep only the crate name, but a more consistent approach to crate disambiguation could be added in the future.
#### hash of the signature
Second part of the symbol name is 128 bit hash containing _relevant_ type information. For now, it includes:
- hash of the type name for primitive types
- for ADT types with public fields the implementation follows [this](https://github.com/m-ou-se/rfcs/blob/export/text/0000-export.md#types-with-public-fields) rules
`#[export(unsafe_stable_abi = "hash")]` syntax for ADT types with private fields is not yet implemented.
0 commit comments