Skip to content

Commit 3f0d207

Browse files
author
Elliott Slaughter
committed
gc: Add stack walker for new garbage collector.
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.
1 parent fb8786f commit 3f0d207

File tree

12 files changed

+375
-32
lines changed

12 files changed

+375
-32
lines changed

mk/rt.mk

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ RUNTIME_CS_$(1) := \
6060
rt/rust_port.cpp \
6161
rt/rust_upcall.cpp \
6262
rt/rust_uv.cpp \
63+
rt/rust_crate_map.cpp \
6364
rt/rust_log.cpp \
65+
rt/rust_gc_metadata.cpp \
6466
rt/rust_port_selector.cpp \
6567
rt/rust_util.cpp \
6668
rt/circular_buffer.cpp \

src/libcore/core.rc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export uint, u8, u16, u32, u64;
4040
export float, f32, f64;
4141
export box, char, str, ptr, vec, at_vec, bool;
4242
export either, option, result, iter;
43-
export libc, os, io, run, rand, sys, unsafe, logging;
43+
export gc, io, libc, os, run, rand, sys, unsafe, logging;
4444
export comm, task, future, pipes;
4545
export extfmt;
4646
// The test harness links against core, so don't include runtime in tests.
@@ -216,6 +216,7 @@ mod pipes;
216216

217217
// Runtime and language-primitive support
218218

219+
mod gc;
219220
mod io;
220221
mod libc;
221222
mod os;

src/libcore/gc.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import stackwalk::Word;
2+
import libc::size_t;
3+
4+
extern mod rustrt {
5+
fn rust_annihilate_box(ptr: *Word);
6+
7+
#[rust_stack]
8+
fn rust_gc_metadata() -> *Word;
9+
10+
#[rust_stack]
11+
fn rust_call_tydesc_glue(root: *Word, tydesc: *Word, field: size_t);
12+
}
13+
14+
type SafePoint = { sp_meta: *Word, fn_meta: *Word };
15+
16+
unsafe fn is_safe_point(pc: *Word) -> Option<SafePoint> {
17+
let module_meta = rustrt::rust_gc_metadata();
18+
let num_safe_points_ptr: *u32 = unsafe::reinterpret_cast(&module_meta);
19+
let num_safe_points = *num_safe_points_ptr as Word;
20+
let safe_points: *Word =
21+
ptr::offset(unsafe::reinterpret_cast(&module_meta), 1);
22+
23+
if ptr::is_null(pc) {
24+
return None;
25+
}
26+
27+
let mut sp = 0 as Word;
28+
while sp < num_safe_points {
29+
let sp_loc = *ptr::offset(safe_points, sp*3) as *Word;
30+
if sp_loc == pc {
31+
return Some(
32+
{sp_meta: *ptr::offset(safe_points, sp*3 + 1) as *Word,
33+
fn_meta: *ptr::offset(safe_points, sp*3 + 2) as *Word});
34+
}
35+
sp += 1;
36+
}
37+
return None;
38+
}
39+
40+
type Visitor = fn(root: **Word, tydesc: *Word);
41+
42+
unsafe fn walk_safe_point(fp: *Word, sp: SafePoint, visitor: Visitor) {
43+
let fp_bytes: *u8 = unsafe::reinterpret_cast(&fp);
44+
let sp_meta_u32s: *u32 = unsafe::reinterpret_cast(&sp.sp_meta);
45+
46+
let num_stack_roots = *sp_meta_u32s as uint;
47+
let num_reg_roots = *ptr::offset(sp_meta_u32s, 1) as uint;
48+
49+
let stack_roots: *u32 =
50+
unsafe::reinterpret_cast(&ptr::offset(sp_meta_u32s, 2));
51+
let reg_roots: *u8 =
52+
unsafe::reinterpret_cast(&ptr::offset(stack_roots, num_stack_roots));
53+
let addrspaces: *Word =
54+
unsafe::reinterpret_cast(&ptr::offset(reg_roots, num_reg_roots));
55+
let tydescs: ***Word =
56+
unsafe::reinterpret_cast(&ptr::offset(addrspaces, num_stack_roots));
57+
58+
// Stack roots
59+
let mut sri = 0;
60+
while sri < num_stack_roots {
61+
if *ptr::offset(addrspaces, sri) >= 1 {
62+
let root =
63+
ptr::offset(fp_bytes, *ptr::offset(stack_roots, sri) as Word)
64+
as **Word;
65+
let tydescpp = ptr::offset(tydescs, sri);
66+
let tydesc = if ptr::is_not_null(tydescpp) &&
67+
ptr::is_not_null(*tydescpp) {
68+
**tydescpp
69+
} else {
70+
ptr::null()
71+
};
72+
visitor(root, tydesc);
73+
}
74+
sri += 1;
75+
}
76+
77+
// Register roots
78+
let mut rri = 0;
79+
while rri < num_reg_roots {
80+
if *ptr::offset(addrspaces, num_stack_roots + rri) == 1 {
81+
// FIXME(#2997): Need to find callee saved registers on the stack.
82+
}
83+
rri += 1;
84+
}
85+
}
86+
87+
type Memory = uint;
88+
89+
const task_local_heap: Memory = 1;
90+
const exchange_heap: Memory = 2;
91+
const stack: Memory = 4;
92+
93+
const need_cleanup: Memory = exchange_heap | stack;
94+
95+
unsafe fn walk_gc_roots(mem: Memory, visitor: Visitor) {
96+
let mut last_ret: *Word = ptr::null();
97+
do stackwalk::walk_stack |frame| {
98+
unsafe {
99+
if ptr::is_not_null(last_ret) {
100+
let sp = is_safe_point(last_ret);
101+
match sp {
102+
Some(sp_info) => {
103+
do walk_safe_point(frame.fp, sp_info) |root, tydesc| {
104+
if ptr::is_null(tydesc) {
105+
// Root is a generic box.
106+
let refcount = **root;
107+
if mem | task_local_heap != 0 && refcount != -1 {
108+
visitor(root, tydesc);
109+
} else if mem | exchange_heap != 0 {
110+
visitor(root, tydesc);
111+
}
112+
} else {
113+
// Root is a non-immediate.
114+
if mem | stack != 0 {
115+
visitor(root, tydesc);
116+
}
117+
}
118+
}
119+
}
120+
None => ()
121+
}
122+
}
123+
last_ret = *ptr::offset(frame.fp, 1) as *Word;
124+
}
125+
true
126+
}
127+
}
128+
129+
fn gc() {
130+
unsafe {
131+
let mut i = 0;
132+
do walk_gc_roots(task_local_heap) |_root, _tydesc| {
133+
// FIXME(#2997): Walk roots and mark them.
134+
io::stdout().write([46]); // .
135+
i += 1;
136+
}
137+
}
138+
}
139+
140+
// This should only be called from fail, as it will drop the roots
141+
// which are *live* on the stack, rather than dropping those that are
142+
// dead.
143+
fn cleanup_stack_for_failure() {
144+
unsafe {
145+
let mut i = 0;
146+
do walk_gc_roots(need_cleanup) |root, tydesc| {
147+
if ptr::is_null(tydesc) {
148+
rustrt::rust_annihilate_box(*root);
149+
} else {
150+
rustrt::rust_call_tydesc_glue(*root, tydesc, 3 as size_t);
151+
}
152+
i += 1;
153+
}
154+
}
155+
}

src/libcore/rt.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ use libc::c_void;
88
use libc::size_t;
99
use libc::uintptr_t;
1010

11+
import gc::gc;
12+
import gc::cleanup_stack_for_failure;
13+
1114
#[allow(non_camel_case_types)]
1215
type rust_task = c_void;
1316

@@ -33,6 +36,7 @@ extern mod rustrt {
3336
// gather_rust_rtcalls.
3437
#[rt(fail)]
3538
fn rt_fail(expr: *c_char, file: *c_char, line: size_t) {
39+
cleanup_stack_for_failure();
3640
rustrt::rust_upcall_fail(expr, file, line);
3741
}
3842

src/rt/rust.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "rust_kernel.h"
88
#include "rust_util.h"
99
#include "rust_scheduler.h"
10+
#include "rust_gc_metadata.h"
1011

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

89+
update_gc_metadata(crate_map);
90+
8891
update_log_settings(crate_map, env->logspec);
8992

9093
// Maybe turn on typestate claim checking

src/rt/rust_crate_map.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#include "rust_crate_map.h"
2+
3+
void iter_module_map(const mod_entry* map,
4+
void (*fn)(const mod_entry* entry, void *cookie),
5+
void *cookie) {
6+
for (const mod_entry* cur = map; cur->name; cur++) {
7+
fn(cur, cookie);
8+
}
9+
}
10+
11+
void iter_crate_map(const cratemap* map,
12+
void (*fn)(const mod_entry* map, void *cookie),
13+
void *cookie) {
14+
// First iterate this crate
15+
iter_module_map(map->entries, fn, cookie);
16+
// Then recurse on linked crates
17+
// FIXME (#2673) this does double work in diamond-shaped deps. could
18+
// keep a set of visited addresses, if it turns out to be actually
19+
// slow
20+
for (size_t i = 0; map->children[i]; i++) {
21+
iter_crate_map(map->children[i], fn, cookie);
22+
}
23+
}
24+
25+
//
26+
// Local Variables:
27+
// mode: C++
28+
// fill-column: 78;
29+
// indent-tabs-mode: nil
30+
// c-basic-offset: 4
31+
// buffer-file-coding-system: utf-8-unix
32+
// End:
33+
//

src/rt/rust_crate_map.h

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#ifndef RUST_CRATE_MAP_H
2+
#define RUST_CRATE_MAP_H
3+
4+
#include "rust_log.h"
5+
6+
struct mod_entry {
7+
const char* name;
8+
uint32_t* state;
9+
};
10+
11+
struct cratemap {
12+
const mod_entry* entries;
13+
const cratemap* children[1];
14+
};
15+
16+
void iter_module_map(const mod_entry* map,
17+
void (*fn)(const mod_entry* entry, void *cookie),
18+
void *cookie);
19+
20+
void iter_crate_map(const cratemap* map,
21+
void (*fn)(const mod_entry* entry, void *cookie),
22+
void *cookie);
23+
24+
//
25+
// Local Variables:
26+
// mode: C++
27+
// fill-column: 78;
28+
// indent-tabs-mode: nil
29+
// c-basic-offset: 4
30+
// buffer-file-coding-system: utf-8-unix
31+
// End:
32+
//
33+
34+
#endif /* RUST_CRATE_MAP_H */

src/rt/rust_gc_metadata.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#include "rust_gc_metadata.h"
2+
#include "rust_crate_map.h"
3+
#include "rust_globals.h"
4+
5+
#include <algorithm>
6+
#include <vector>
7+
8+
struct safe_point {
9+
size_t safe_point_loc;
10+
size_t safe_point_meta;
11+
size_t function_meta;
12+
};
13+
14+
struct update_gc_entry_args {
15+
std::vector<safe_point> *safe_points;
16+
};
17+
18+
static void
19+
update_gc_entry(const mod_entry* entry, void *cookie) {
20+
update_gc_entry_args *args = (update_gc_entry_args *)cookie;
21+
if (!strcmp(entry->name, "_gc_module_metadata")) {
22+
size_t *next = entry->state;
23+
uint32_t num_safe_points = *(uint32_t *)next;
24+
next++;
25+
26+
for (uint32_t i = 0; i < num_safe_points; i++) {
27+
safe_point sp = { next[0], next[1], next[2] };
28+
next += 3;
29+
30+
args->safe_points->push_back(sp);
31+
}
32+
}
33+
}
34+
35+
static bool
36+
cmp_safe_point(safe_point a, safe_point b) {
37+
return a.safe_point_loc < b.safe_point_loc;
38+
}
39+
40+
size_t *global_safe_points = 0;
41+
42+
void
43+
update_gc_metadata(const void* map) {
44+
std::vector<safe_point> safe_points;
45+
update_gc_entry_args args = { &safe_points };
46+
47+
// Extract list of safe points from each module.
48+
iter_crate_map((const cratemap *)map, update_gc_entry, (void *)&args);
49+
std::sort(safe_points.begin(), safe_points.end(), cmp_safe_point);
50+
51+
// Serialize safe point list into format expected by runtime.
52+
global_safe_points =
53+
(size_t *)malloc((safe_points.size()*3 + 1)*sizeof(size_t));
54+
if (!global_safe_points) return;
55+
56+
size_t *next = global_safe_points;
57+
*(uint32_t *)next = safe_points.size();
58+
next++;
59+
for (uint32_t i = 0; i < safe_points.size(); i++) {
60+
next[0] = safe_points[i].safe_point_loc;
61+
next[1] = safe_points[i].safe_point_meta;
62+
next[2] = safe_points[i].function_meta;
63+
next += 3;
64+
}
65+
}
66+
67+
extern "C" CDECL void *
68+
rust_gc_metadata() {
69+
return (void *)global_safe_points;
70+
}
71+
72+
//
73+
// Local Variables:
74+
// mode: C++
75+
// fill-column: 78;
76+
// indent-tabs-mode: nil
77+
// c-basic-offset: 4
78+
// buffer-file-coding-system: utf-8-unix
79+
// End:
80+
//

src/rt/rust_gc_metadata.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#ifndef RUST_GC_METADATA_H
2+
#define RUST_GC_METADATA_H
3+
4+
void update_gc_metadata(const void* map);
5+
6+
//
7+
// Local Variables:
8+
// mode: C++
9+
// fill-column: 78;
10+
// indent-tabs-mode: nil
11+
// c-basic-offset: 4
12+
// buffer-file-coding-system: utf-8-unix
13+
// End:
14+
//
15+
16+
#endif /* RUST_GC_METADATA_H */

0 commit comments

Comments
 (0)