diff --git a/mk/rt.mk b/mk/rt.mk index 40146c2548092..ba20684df0139 100644 --- a/mk/rt.mk +++ b/mk/rt.mk @@ -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 \ diff --git a/src/libcore/core.rc b/src/libcore/core.rc index 6cf9c82a51117..4da21d7f5afbb 100644 --- a/src/libcore/core.rc +++ b/src/libcore/core.rc @@ -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. @@ -216,6 +216,7 @@ mod pipes; // Runtime and language-primitive support +mod gc; mod io; mod libc; mod os; diff --git a/src/libcore/gc.rs b/src/libcore/gc.rs new file mode 100644 index 0000000000000..eacd0bc62d319 --- /dev/null +++ b/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 { + 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; + } + } +} diff --git a/src/libcore/rt.rs b/src/libcore/rt.rs index 60253f0c0ddea..0f074bc5c1dc5 100644 --- a/src/libcore/rt.rs +++ b/src/libcore/rt.rs @@ -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; @@ -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); } diff --git a/src/rt/rust.cpp b/src/rt/rust.cpp index 150156ddae953..705a96303d4ee 100644 --- a/src/rt/rust.cpp +++ b/src/rt/rust.cpp @@ -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 @@ -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 diff --git a/src/rt/rust_crate_map.cpp b/src/rt/rust_crate_map.cpp new file mode 100644 index 0000000000000..633361bb932d4 --- /dev/null +++ b/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: +// diff --git a/src/rt/rust_crate_map.h b/src/rt/rust_crate_map.h new file mode 100644 index 0000000000000..453feb36e5722 --- /dev/null +++ b/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 */ diff --git a/src/rt/rust_gc_metadata.cpp b/src/rt/rust_gc_metadata.cpp new file mode 100644 index 0000000000000..a4da879daa0bf --- /dev/null +++ b/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 +#include + +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_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_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: +// diff --git a/src/rt/rust_gc_metadata.h b/src/rt/rust_gc_metadata.h new file mode 100644 index 0000000000000..ddeb5b382ff0f --- /dev/null +++ b/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 */ diff --git a/src/rt/rust_log.cpp b/src/rt/rust_log.cpp index 5074b1f40c6a2..666183fcdd39e 100644 --- a/src/rt/rust_log.cpp +++ b/src/rt/rust_log.cpp @@ -4,6 +4,7 @@ #include "rust_log.h" +#include "rust_crate_map.h" #include "util/array_list.h" #include "rust_util.h" #include "rust_task.h" @@ -160,16 +161,6 @@ rust_log::trace_ln(rust_task *task, uint32_t level, char *message) { // Reading log directives and setting log level vars -struct mod_entry { - const char* name; - uint32_t* state; -}; - -struct cratemap { - const mod_entry* entries; - const cratemap* children[1]; -}; - struct log_directive { char* name; size_t level; @@ -212,33 +203,36 @@ size_t parse_logging_spec(char* spec, log_directive* dirs) { return dir; } -void update_module_map(const mod_entry* map, log_directive* dirs, - size_t n_dirs, size_t *n_matches) { - for (const mod_entry* cur = map; cur->name; cur++) { - size_t level = default_log_level, longest_match = 0; - for (size_t d = 0; d < n_dirs; d++) { - if (strstr(cur->name, dirs[d].name) == cur->name && - strlen(dirs[d].name) > longest_match) { - longest_match = strlen(dirs[d].name); - level = dirs[d].level; - } +struct update_entry_args { + log_directive* dirs; + size_t n_dirs; + size_t *n_matches; +}; + +static void update_entry(const mod_entry* entry, void *cookie) { + update_entry_args *args = (update_entry_args *)cookie; + size_t level = default_log_level, longest_match = 0; + for (size_t d = 0; d < args->n_dirs; d++) { + if (strstr(entry->name, args->dirs[d].name) == entry->name && + strlen(args->dirs[d].name) > longest_match) { + longest_match = strlen(args->dirs[d].name); + level = args->dirs[d].level; } - *cur->state = level; - (*n_matches)++; } + *entry->state = level; + (*args->n_matches)++; +} + +void update_module_map(const mod_entry* map, log_directive* dirs, + size_t n_dirs, size_t *n_matches) { + update_entry_args args = { dirs, n_dirs, n_matches }; + iter_module_map(map, update_entry, &args); } void update_crate_map(const cratemap* map, log_directive* dirs, size_t n_dirs, size_t *n_matches) { - // First update log levels for this crate - update_module_map(map->entries, dirs, n_dirs, n_matches); - // 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++) { - update_crate_map(map->children[i], dirs, n_dirs, n_matches); - } + update_entry_args args = { dirs, n_dirs, n_matches }; + iter_crate_map(map, update_entry, &args); } void print_crate_log_map(const cratemap* map) { diff --git a/src/rustc/middle/trans/base.rs b/src/rustc/middle/trans/base.rs index acf51691a968c..315b496e66195 100644 --- a/src/rustc/middle/trans/base.rs +++ b/src/rustc/middle/trans/base.rs @@ -1595,6 +1595,7 @@ fn trans_closure(ccx: @crate_ctxt, path: path, decl: ast::fn_decl, do str::as_c_str("generic") |strategy| { llvm::LLVMSetGC(fcx.llfn, strategy); } + ccx.uses_gc = true; } // Create the first basic block in the function and keep a handle on it to @@ -2438,6 +2439,20 @@ fn gather_rtcalls(ccx: @crate_ctxt, crate: @ast::crate) { } } +fn decl_gc_metadata(ccx: @crate_ctxt, llmod_id: ~str) { + if !ccx.sess.opts.gc || !ccx.uses_gc { + return; + } + + let gc_metadata_name = ~"_gc_module_metadata_" + llmod_id; + let gc_metadata = do str::as_c_str(gc_metadata_name) |buf| { + llvm::LLVMAddGlobal(ccx.llmod, T_i32(), buf) + }; + llvm::LLVMSetGlobalConstant(gc_metadata, True); + lib::llvm::SetLinkage(gc_metadata, lib::llvm::ExternalLinkage); + ccx.module_data.insert(~"_gc_module_metadata", gc_metadata); +} + fn create_module_map(ccx: @crate_ctxt) -> ValueRef { let elttype = T_struct(~[ccx.int_type, ccx.int_type]); let maptype = T_array(elttype, ccx.module_data.size() + 1u); @@ -2679,6 +2694,7 @@ fn trans_crate(sess: session::session, builder: BuilderRef_res(llvm::LLVMCreateBuilder()), shape_cx: mk_ctxt(llmod), crate_map: crate_map, + mut uses_gc: false, dbg_cx: dbg_cx, class_ctors: int_hash::(), mut do_not_commit_warning_issued: false}; @@ -2696,6 +2712,7 @@ fn trans_crate(sess: session::session, trans_mod(ccx, crate.node.module); } + decl_gc_metadata(ccx, llmod_id); fill_crate_map(ccx, crate_map); // NB: Must call force_declare_tydescs before emit_tydescs to break // cyclical dependency with shape code! See shape.rs for details. diff --git a/src/rustc/middle/trans/common.rs b/src/rustc/middle/trans/common.rs index 6b3e4063d67a9..7e612051565f2 100644 --- a/src/rustc/middle/trans/common.rs +++ b/src/rustc/middle/trans/common.rs @@ -162,6 +162,10 @@ type crate_ctxt = { builder: BuilderRef_res, shape_cx: shape::ctxt, crate_map: ValueRef, + // Set when at least one function uses GC. Needed so that + // decl_gc_metadata knows whether to link to the module metadata, which + // is not emitted by LLVM's GC pass when no functions use GC. + mut uses_gc: bool, dbg_cx: Option, // Mapping from class constructors to parent class -- // used in base::trans_closure