Skip to content

Commit

Permalink
gc: Add stack walker for new garbage collector.
Browse files Browse the repository at this point in the history
Safe points are exported in a per-module list via the crate map. A C
runtime call walks the crate map at startup and aggregates the list of
safe points for the program.

Currently the GC doesn't actually deallocate memory on malloc and
free. Adding the GC at this stage is primarily of testing value.

The GC does attempt to clean up exchange heap and stack-allocated
resource on failure.

A result of this patch is that the user now needs to be careful about
what code they write in destructors, because the GC and/or failure
cleanup may need to call destructors. Specifically, calls to malloc
are considered unsafe and may result in infinite loops or segfaults.
  • Loading branch information
Elliott Slaughter committed Sep 7, 2012
1 parent fb8786f commit 3f0d207
Show file tree
Hide file tree
Showing 12 changed files with 375 additions and 32 deletions.
2 changes: 2 additions & 0 deletions mk/rt.mk
Expand Up @@ -60,7 +60,9 @@ RUNTIME_CS_$(1) := \
rt/rust_port.cpp \
rt/rust_upcall.cpp \
rt/rust_uv.cpp \
rt/rust_crate_map.cpp \
rt/rust_log.cpp \
rt/rust_gc_metadata.cpp \
rt/rust_port_selector.cpp \
rt/rust_util.cpp \
rt/circular_buffer.cpp \
Expand Down
3 changes: 2 additions & 1 deletion src/libcore/core.rc
Expand Up @@ -40,7 +40,7 @@ export uint, u8, u16, u32, u64;
export float, f32, f64;
export box, char, str, ptr, vec, at_vec, bool;
export either, option, result, iter;
export libc, os, io, run, rand, sys, unsafe, logging;
export gc, io, libc, os, run, rand, sys, unsafe, logging;
export comm, task, future, pipes;
export extfmt;
// The test harness links against core, so don't include runtime in tests.
Expand Down Expand Up @@ -216,6 +216,7 @@ mod pipes;

// Runtime and language-primitive support

mod gc;
mod io;
mod libc;
mod os;
Expand Down
155 changes: 155 additions & 0 deletions src/libcore/gc.rs
@@ -0,0 +1,155 @@
import stackwalk::Word;
import libc::size_t;

extern mod rustrt {
fn rust_annihilate_box(ptr: *Word);

#[rust_stack]
fn rust_gc_metadata() -> *Word;

#[rust_stack]
fn rust_call_tydesc_glue(root: *Word, tydesc: *Word, field: size_t);
}

type SafePoint = { sp_meta: *Word, fn_meta: *Word };

unsafe fn is_safe_point(pc: *Word) -> Option<SafePoint> {
let module_meta = rustrt::rust_gc_metadata();
let num_safe_points_ptr: *u32 = unsafe::reinterpret_cast(&module_meta);
let num_safe_points = *num_safe_points_ptr as Word;
let safe_points: *Word =
ptr::offset(unsafe::reinterpret_cast(&module_meta), 1);

if ptr::is_null(pc) {
return None;
}

let mut sp = 0 as Word;
while sp < num_safe_points {
let sp_loc = *ptr::offset(safe_points, sp*3) as *Word;
if sp_loc == pc {
return Some(
{sp_meta: *ptr::offset(safe_points, sp*3 + 1) as *Word,
fn_meta: *ptr::offset(safe_points, sp*3 + 2) as *Word});
}
sp += 1;
}
return None;
}

type Visitor = fn(root: **Word, tydesc: *Word);

unsafe fn walk_safe_point(fp: *Word, sp: SafePoint, visitor: Visitor) {
let fp_bytes: *u8 = unsafe::reinterpret_cast(&fp);
let sp_meta_u32s: *u32 = unsafe::reinterpret_cast(&sp.sp_meta);

let num_stack_roots = *sp_meta_u32s as uint;
let num_reg_roots = *ptr::offset(sp_meta_u32s, 1) as uint;

let stack_roots: *u32 =
unsafe::reinterpret_cast(&ptr::offset(sp_meta_u32s, 2));
let reg_roots: *u8 =
unsafe::reinterpret_cast(&ptr::offset(stack_roots, num_stack_roots));
let addrspaces: *Word =
unsafe::reinterpret_cast(&ptr::offset(reg_roots, num_reg_roots));
let tydescs: ***Word =
unsafe::reinterpret_cast(&ptr::offset(addrspaces, num_stack_roots));

// Stack roots
let mut sri = 0;
while sri < num_stack_roots {
if *ptr::offset(addrspaces, sri) >= 1 {
let root =
ptr::offset(fp_bytes, *ptr::offset(stack_roots, sri) as Word)
as **Word;
let tydescpp = ptr::offset(tydescs, sri);
let tydesc = if ptr::is_not_null(tydescpp) &&
ptr::is_not_null(*tydescpp) {
**tydescpp
} else {
ptr::null()
};
visitor(root, tydesc);
}
sri += 1;
}

// Register roots
let mut rri = 0;
while rri < num_reg_roots {
if *ptr::offset(addrspaces, num_stack_roots + rri) == 1 {
// FIXME(#2997): Need to find callee saved registers on the stack.
}
rri += 1;
}
}

type Memory = uint;

const task_local_heap: Memory = 1;
const exchange_heap: Memory = 2;
const stack: Memory = 4;

const need_cleanup: Memory = exchange_heap | stack;

unsafe fn walk_gc_roots(mem: Memory, visitor: Visitor) {
let mut last_ret: *Word = ptr::null();
do stackwalk::walk_stack |frame| {
unsafe {
if ptr::is_not_null(last_ret) {
let sp = is_safe_point(last_ret);
match sp {
Some(sp_info) => {
do walk_safe_point(frame.fp, sp_info) |root, tydesc| {
if ptr::is_null(tydesc) {
// Root is a generic box.
let refcount = **root;
if mem | task_local_heap != 0 && refcount != -1 {
visitor(root, tydesc);
} else if mem | exchange_heap != 0 {
visitor(root, tydesc);
}
} else {
// Root is a non-immediate.
if mem | stack != 0 {
visitor(root, tydesc);
}
}
}
}
None => ()
}
}
last_ret = *ptr::offset(frame.fp, 1) as *Word;
}
true
}
}

fn gc() {
unsafe {
let mut i = 0;
do walk_gc_roots(task_local_heap) |_root, _tydesc| {
// FIXME(#2997): Walk roots and mark them.
io::stdout().write([46]); // .
i += 1;
}
}
}

// This should only be called from fail, as it will drop the roots
// which are *live* on the stack, rather than dropping those that are
// dead.
fn cleanup_stack_for_failure() {
unsafe {
let mut i = 0;
do walk_gc_roots(need_cleanup) |root, tydesc| {
if ptr::is_null(tydesc) {
rustrt::rust_annihilate_box(*root);
} else {
rustrt::rust_call_tydesc_glue(*root, tydesc, 3 as size_t);
}
i += 1;
}
}
}
4 changes: 4 additions & 0 deletions src/libcore/rt.rs
Expand Up @@ -8,6 +8,9 @@ use libc::c_void;
use libc::size_t;
use libc::uintptr_t;

import gc::gc;
import gc::cleanup_stack_for_failure;

#[allow(non_camel_case_types)]
type rust_task = c_void;

Expand All @@ -33,6 +36,7 @@ extern mod rustrt {
// gather_rust_rtcalls.
#[rt(fail)]
fn rt_fail(expr: *c_char, file: *c_char, line: size_t) {
cleanup_stack_for_failure();
rustrt::rust_upcall_fail(expr, file, line);
}

Expand Down
3 changes: 3 additions & 0 deletions src/rt/rust.cpp
Expand Up @@ -7,6 +7,7 @@
#include "rust_kernel.h"
#include "rust_util.h"
#include "rust_scheduler.h"
#include "rust_gc_metadata.h"

// Creates a rust argument vector from the platform argument vector
struct
Expand Down Expand Up @@ -85,6 +86,8 @@ rust_start(uintptr_t main_fn, int argc, char **argv, void* crate_map) {
// line as well.
rust_env *env = load_env();

update_gc_metadata(crate_map);

update_log_settings(crate_map, env->logspec);

// Maybe turn on typestate claim checking
Expand Down
33 changes: 33 additions & 0 deletions src/rt/rust_crate_map.cpp
@@ -0,0 +1,33 @@
#include "rust_crate_map.h"

void iter_module_map(const mod_entry* map,
void (*fn)(const mod_entry* entry, void *cookie),
void *cookie) {
for (const mod_entry* cur = map; cur->name; cur++) {
fn(cur, cookie);
}
}

void iter_crate_map(const cratemap* map,
void (*fn)(const mod_entry* map, void *cookie),
void *cookie) {
// First iterate this crate
iter_module_map(map->entries, fn, cookie);
// Then recurse on linked crates
// FIXME (#2673) this does double work in diamond-shaped deps. could
// keep a set of visited addresses, if it turns out to be actually
// slow
for (size_t i = 0; map->children[i]; i++) {
iter_crate_map(map->children[i], fn, cookie);
}
}

//
// Local Variables:
// mode: C++
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// End:
//
34 changes: 34 additions & 0 deletions src/rt/rust_crate_map.h
@@ -0,0 +1,34 @@
#ifndef RUST_CRATE_MAP_H
#define RUST_CRATE_MAP_H

#include "rust_log.h"

struct mod_entry {
const char* name;
uint32_t* state;
};

struct cratemap {
const mod_entry* entries;
const cratemap* children[1];
};

void iter_module_map(const mod_entry* map,
void (*fn)(const mod_entry* entry, void *cookie),
void *cookie);

void iter_crate_map(const cratemap* map,
void (*fn)(const mod_entry* entry, void *cookie),
void *cookie);

//
// Local Variables:
// mode: C++
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// End:
//

#endif /* RUST_CRATE_MAP_H */
80 changes: 80 additions & 0 deletions src/rt/rust_gc_metadata.cpp
@@ -0,0 +1,80 @@
#include "rust_gc_metadata.h"
#include "rust_crate_map.h"
#include "rust_globals.h"

#include <algorithm>
#include <vector>

struct safe_point {
size_t safe_point_loc;
size_t safe_point_meta;
size_t function_meta;
};

struct update_gc_entry_args {
std::vector<safe_point> *safe_points;
};

static void
update_gc_entry(const mod_entry* entry, void *cookie) {
update_gc_entry_args *args = (update_gc_entry_args *)cookie;
if (!strcmp(entry->name, "_gc_module_metadata")) {
size_t *next = entry->state;
uint32_t num_safe_points = *(uint32_t *)next;
next++;

for (uint32_t i = 0; i < num_safe_points; i++) {
safe_point sp = { next[0], next[1], next[2] };
next += 3;

args->safe_points->push_back(sp);
}
}
}

static bool
cmp_safe_point(safe_point a, safe_point b) {
return a.safe_point_loc < b.safe_point_loc;
}

size_t *global_safe_points = 0;

void
update_gc_metadata(const void* map) {
std::vector<safe_point> safe_points;
update_gc_entry_args args = { &safe_points };

// Extract list of safe points from each module.
iter_crate_map((const cratemap *)map, update_gc_entry, (void *)&args);
std::sort(safe_points.begin(), safe_points.end(), cmp_safe_point);

// Serialize safe point list into format expected by runtime.
global_safe_points =
(size_t *)malloc((safe_points.size()*3 + 1)*sizeof(size_t));
if (!global_safe_points) return;

size_t *next = global_safe_points;
*(uint32_t *)next = safe_points.size();
next++;
for (uint32_t i = 0; i < safe_points.size(); i++) {
next[0] = safe_points[i].safe_point_loc;
next[1] = safe_points[i].safe_point_meta;
next[2] = safe_points[i].function_meta;
next += 3;
}
}

extern "C" CDECL void *
rust_gc_metadata() {
return (void *)global_safe_points;
}

//
// Local Variables:
// mode: C++
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// End:
//
16 changes: 16 additions & 0 deletions src/rt/rust_gc_metadata.h
@@ -0,0 +1,16 @@
#ifndef RUST_GC_METADATA_H
#define RUST_GC_METADATA_H

void update_gc_metadata(const void* map);

//
// Local Variables:
// mode: C++
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// End:
//

#endif /* RUST_GC_METADATA_H */

0 comments on commit 3f0d207

Please sign in to comment.