Skip to content

Commit

Permalink
Don't pass the state and process as an argument
Browse files Browse the repository at this point in the history
The compiler generated code no longer passes the runtime state and the
current process as hidden arguments. Instead, the state is stored in a
global variable when the program starts up. For the current process we
change the stack layout to the following:

    ╭───────────────────╮
    │    Private page   │
    ├───────────────────┤
    │     Guard page    │
    ├───────────────────┤
    │     Stack data    │ ↑ Stack grows towards the guard
    ╰───────────────────╯

The private page stores extra data, such as a pointer to the process
that owns the stack and the epoch at which it started running.

This entire chunk of data is then aligned to its size. This makes it
possible to get a pointer to the private data page by applying a bitmask
to the stack pointer. The bitmask depends on the stack size, which is
runtime configurable and depends on the page size, and is loaded into a
global variable at startup.

This entire approach removes the need for more expensive thread-local
operations, which we can't use anyway due to Rust's "thread_local"
attribute not being stable (and likely not becoming stable for another
few years).

This fixes #617.

Changelog: changed
  • Loading branch information
yorickpeterse committed Feb 20, 2024
1 parent 01bd04e commit a3613d6
Show file tree
Hide file tree
Showing 19 changed files with 490 additions and 306 deletions.
13 changes: 6 additions & 7 deletions compiler/src/llvm/constants.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
pub(crate) const STATE_EPOCH_OFFSET: u32 = 4;
pub(crate) const PROCESS_EPOCH_OFFSET: u32 = 1;

pub(crate) const STATE_EPOCH_INDEX: u32 = 4;
pub(crate) const FIELD_OFFSET: usize = 1;
pub(crate) const PROCESS_FIELD_OFFSET: usize = 2;

pub(crate) const PROCESS_FIELD_OFFSET: usize = 3;
pub(crate) const STACK_DATA_PROCESS_INDEX: u32 = 0;
pub(crate) const STACK_DATA_EPOCH_INDEX: u32 = 2;

pub(crate) const HEADER_CLASS_INDEX: u32 = 0;
pub(crate) const HEADER_REFS_INDEX: u32 = 1;
Expand All @@ -14,9 +14,8 @@ pub(crate) const CLASS_METHODS_INDEX: u32 = 3;
pub(crate) const METHOD_HASH_INDEX: u32 = 0;
pub(crate) const METHOD_FUNCTION_INDEX: u32 = 1;

pub(crate) const CONTEXT_STATE_INDEX: u32 = 0;
pub(crate) const CONTEXT_PROCESS_INDEX: u32 = 1;
pub(crate) const CONTEXT_ARGS_INDEX: u32 = 2;
pub(crate) const CONTEXT_PROCESS_INDEX: u32 = 0;
pub(crate) const CONTEXT_ARGS_INDEX: u32 = 1;

pub(crate) const MESSAGE_ARGUMENTS_INDEX: u32 = 2;
pub(crate) const DROPPER_INDEX: u32 = 0;
Expand Down
38 changes: 18 additions & 20 deletions compiler/src/llvm/layouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ pub(crate) struct Layouts<'ctx> {

/// The layout of messages sent to processes.
pub(crate) message: StructType<'ctx>,

/// The layout of a process' stack data.
pub(crate) process_stack_data: StructType<'ctx>,
}

impl<'ctx> Layouts<'ctx> {
Expand Down Expand Up @@ -114,9 +117,8 @@ impl<'ctx> Layouts<'ctx> {
]);

let context_layout = context.struct_type(&[
state_layout.ptr_type(space).into(), // State
context.pointer_type().into(), // Process
context.pointer_type().into(), // Arguments pointer
context.pointer_type().into(), // Process
context.pointer_type().into(), // Arguments pointer
]);

let method_counts_layout = context.struct_type(&[
Expand All @@ -130,6 +132,12 @@ impl<'ctx> Layouts<'ctx> {
context.pointer_type().array_type(0).into(), // Arguments
]);

let stack_data_layout = context.struct_type(&[
context.pointer_type().into(), // Process
context.pointer_type().into(), // Thread
context.i32_type().into(), // Epoch
]);

// We generate the bare structs first, that way method signatures can
// refer to them, regardless of the order in which methods/classes are
// defined.
Expand Down Expand Up @@ -182,22 +190,23 @@ impl<'ctx> Layouts<'ctx> {
method_counts: method_counts_layout,
methods: vec![dummy_method; num_methods],
message: message_layout,
process_stack_data: stack_data_layout,
};

let process_size = match state.config.target.os {
OperatingSystem::Linux | OperatingSystem::Freebsd => {
// Mutexes are smaller on Linux, resulting in a smaller process
// size, so we have to take that into account when calculating
// field offsets.
120
104
}
_ => 136,
_ => 120,
};

// The size of the data of a process that isn't exposed to the generated
// code (i.e. because it involves Rust types of which the layout isn't
// stable).
let process_private_size = process_size - HEADER_SIZE - 4;
let process_private_size = process_size - HEADER_SIZE;

for id in mir.classes.keys() {
// String is a built-in class, but it's defined like a regular one,
Expand Down Expand Up @@ -233,14 +242,11 @@ impl<'ctx> Layouts<'ctx> {
// +--------------------------+
// | header (16 bytes) |
// +--------------------------+
// | start epoch (4 bytes) |
// +--------------------------+
// | private data (N bytes) |
// +--------------------------+
// | user-defined fields |
// +--------------------------+
if kind.is_async() {
types.push(context.i32_type().into());
types.push(
context
.i8_type()
Expand All @@ -263,9 +269,7 @@ impl<'ctx> Layouts<'ctx> {
for calls in mir.dynamic_calls.values() {
for (method, _) in calls {
let mut args: Vec<BasicMetadataTypeEnum> = vec![
state_layout.ptr_type(space).into(), // State
context.pointer_type().into(), // Process
context.pointer_type().into(), // Receiver
context.pointer_type().into(), // Receiver
];

for &typ in method.argument_types(db) {
Expand Down Expand Up @@ -300,10 +304,7 @@ impl<'ctx> Layouts<'ctx> {
false,
)
} else {
let mut args: Vec<BasicMetadataTypeEnum> = vec![
state_layout.ptr_type(space).into(), // State
context.pointer_type().into(), // Process
];
let mut args: Vec<BasicMetadataTypeEnum> = Vec::new();

// For instance methods, the receiver is passed as an
// explicit argument before any user-defined arguments.
Expand Down Expand Up @@ -336,10 +337,7 @@ impl<'ctx> Layouts<'ctx> {
}

for &method in mir.methods.keys().filter(|m| m.is_static(db)) {
let mut args: Vec<BasicMetadataTypeEnum> = vec![
state_layout.ptr_type(space).into(), // State
context.pointer_type().into(), // Process
];
let mut args: Vec<BasicMetadataTypeEnum> = Vec::new();

for &typ in method.argument_types(db) {
args.push(context.llvm_type(db, &layouts, typ).into());
Expand Down
4 changes: 1 addition & 3 deletions compiler/src/llvm/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,7 @@ impl<'a, 'ctx> Module<'a, 'ctx> {
if let Some(func) = self.inner.get_function(name) {
func
} else {
let space = AddressSpace::default();
let args = [self.layouts.state.ptr_type(space).into()];
let typ = self.context.void_type().fn_type(&args, false);
let typ = self.context.void_type().fn_type(&[], false);

self.inner.add_function(name, typ, None)
}
Expand Down
Loading

0 comments on commit a3613d6

Please sign in to comment.