mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-15 20:45:45 +03:00
c9c5ee252a
Vectors are now similar to our old, pre-internal vectors, except that they are uniquely owned, not refcounted. Their name should probably change too, then. I've renamed them to vec in the runtime, will do so throughout the compiler later.
408 lines
12 KiB
C++
408 lines
12 KiB
C++
#include "rust_gc.h"
|
|
#include "rust_internal.h"
|
|
#include "rust_upcall.h"
|
|
|
|
// Upcalls.
|
|
|
|
extern "C" CDECL char const *
|
|
str_buf(rust_task *task, rust_str *s);
|
|
|
|
#ifdef __i386__
|
|
void
|
|
check_stack(rust_task *task) {
|
|
void *esp;
|
|
asm volatile("movl %%esp,%0" : "=r" (esp));
|
|
if (esp < task->stk->data)
|
|
task->kernel->fatal("Out of stack space, sorry");
|
|
}
|
|
#else
|
|
#warning "Stack checks are not supported on this architecture"
|
|
void
|
|
check_stack(rust_task *task) {
|
|
// TODO
|
|
}
|
|
#endif
|
|
|
|
extern "C" void
|
|
upcall_grow_task(rust_task *task, size_t n_frame_bytes) {
|
|
I(task->sched, false);
|
|
LOG_UPCALL_ENTRY(task);
|
|
task->grow(n_frame_bytes);
|
|
}
|
|
|
|
extern "C" CDECL
|
|
void upcall_log_int(rust_task *task, uint32_t level, int32_t i) {
|
|
LOG_UPCALL_ENTRY(task);
|
|
if (task->sched->log_lvl >= level)
|
|
task->sched->log(task, level, "rust: %" PRId32 " (0x%" PRIx32 ")",
|
|
i, i);
|
|
}
|
|
|
|
extern "C" CDECL
|
|
void upcall_log_float(rust_task *task, uint32_t level, float f) {
|
|
LOG_UPCALL_ENTRY(task);
|
|
if (task->sched->log_lvl >= level)
|
|
task->sched->log(task, level, "rust: %12.12f", f);
|
|
}
|
|
|
|
extern "C" CDECL
|
|
void upcall_log_double(rust_task *task, uint32_t level, double *f) {
|
|
LOG_UPCALL_ENTRY(task);
|
|
if (task->sched->log_lvl >= level)
|
|
task->sched->log(task, level, "rust: %12.12f", *f);
|
|
}
|
|
|
|
extern "C" CDECL void
|
|
upcall_log_str(rust_task *task, uint32_t level, rust_str *str) {
|
|
LOG_UPCALL_ENTRY(task);
|
|
if (task->sched->log_lvl >= level) {
|
|
const char *c = str_buf(task, str);
|
|
task->sched->log(task, level, "rust: %s", c);
|
|
}
|
|
}
|
|
|
|
extern "C" CDECL void
|
|
upcall_yield(rust_task *task) {
|
|
LOG_UPCALL_ENTRY(task);
|
|
LOG(task, comm, "upcall yield()");
|
|
task->yield(1);
|
|
}
|
|
|
|
extern "C" CDECL void
|
|
upcall_sleep(rust_task *task, size_t time_in_us) {
|
|
LOG_UPCALL_ENTRY(task);
|
|
LOG(task, task, "elapsed %" PRIu64 " us",
|
|
task->yield_timer.elapsed_us());
|
|
LOG(task, task, "sleep %d us", time_in_us);
|
|
task->yield(time_in_us);
|
|
}
|
|
|
|
extern "C" CDECL void
|
|
upcall_fail(rust_task *task,
|
|
char const *expr,
|
|
char const *file,
|
|
size_t line) {
|
|
LOG_UPCALL_ENTRY(task);
|
|
LOG_ERR(task, upcall, "upcall fail '%s', %s:%" PRIdPTR, expr, file, line);
|
|
task->die();
|
|
task->fail();
|
|
task->notify_tasks_waiting_to_join();
|
|
task->yield(4);
|
|
}
|
|
|
|
/**
|
|
* Called whenever a task's ref count drops to zero.
|
|
*/
|
|
extern "C" CDECL void
|
|
upcall_kill(rust_task *task, rust_task_id tid) {
|
|
LOG_UPCALL_ENTRY(task);
|
|
rust_task *target = task->kernel->get_task_by_id(tid);
|
|
target->kill();
|
|
target->deref();
|
|
}
|
|
|
|
/**
|
|
* Called by the exit glue when the task terminates.
|
|
*/
|
|
extern "C" CDECL void
|
|
upcall_exit(rust_task *task) {
|
|
LOG_UPCALL_ENTRY(task);
|
|
task->die();
|
|
task->notify_tasks_waiting_to_join();
|
|
task->yield(1);
|
|
}
|
|
|
|
extern "C" CDECL uintptr_t
|
|
upcall_malloc(rust_task *task, size_t nbytes, type_desc *td) {
|
|
LOG_UPCALL_ENTRY(task);
|
|
|
|
LOG(task, mem,
|
|
"upcall malloc(%" PRIdPTR ", 0x%" PRIxPTR ")"
|
|
" with gc-chain head = 0x%" PRIxPTR,
|
|
nbytes, td, task->gc_alloc_chain);
|
|
|
|
gc::maybe_gc(task);
|
|
|
|
// TODO: Maybe use dladdr here to find a more useful name for the
|
|
// type_desc.
|
|
|
|
void *p = task->malloc(nbytes, "tdesc", td);
|
|
|
|
LOG(task, mem,
|
|
"upcall malloc(%" PRIdPTR ", 0x%" PRIxPTR
|
|
") = 0x%" PRIxPTR
|
|
" with gc-chain head = 0x%" PRIxPTR,
|
|
nbytes, td, (uintptr_t)p, task->gc_alloc_chain);
|
|
return (uintptr_t) p;
|
|
}
|
|
|
|
/**
|
|
* Called whenever an object's ref count drops to zero.
|
|
*/
|
|
extern "C" CDECL void
|
|
upcall_free(rust_task *task, void* ptr, uintptr_t is_gc) {
|
|
LOG_UPCALL_ENTRY(task);
|
|
|
|
rust_scheduler *sched = task->sched;
|
|
DLOG(sched, mem,
|
|
"upcall free(0x%" PRIxPTR ", is_gc=%" PRIdPTR ")",
|
|
(uintptr_t)ptr, is_gc);
|
|
task->free(ptr, (bool) is_gc);
|
|
}
|
|
|
|
extern "C" CDECL uintptr_t
|
|
upcall_shared_malloc(rust_task *task, size_t nbytes, type_desc *td) {
|
|
LOG_UPCALL_ENTRY(task);
|
|
|
|
LOG(task, mem,
|
|
"upcall shared_malloc(%" PRIdPTR ", 0x%" PRIxPTR ")",
|
|
nbytes, td);
|
|
void *p = task->kernel->malloc(nbytes, "shared malloc");
|
|
LOG(task, mem,
|
|
"upcall shared_malloc(%" PRIdPTR ", 0x%" PRIxPTR
|
|
") = 0x%" PRIxPTR,
|
|
nbytes, td, (uintptr_t)p);
|
|
return (uintptr_t) p;
|
|
}
|
|
|
|
/**
|
|
* Called whenever an object's ref count drops to zero.
|
|
*/
|
|
extern "C" CDECL void
|
|
upcall_shared_free(rust_task *task, void* ptr) {
|
|
LOG_UPCALL_ENTRY(task);
|
|
|
|
rust_scheduler *sched = task->sched;
|
|
DLOG(sched, mem,
|
|
"upcall shared_free(0x%" PRIxPTR")",
|
|
(uintptr_t)ptr);
|
|
task->kernel->free(ptr);
|
|
}
|
|
|
|
rust_str *make_str(rust_task *task, char const *s, size_t fill) {
|
|
size_t alloc = next_power_of_two(sizeof(rust_str) + fill);
|
|
void *mem = task->malloc(alloc, "rust_str (make_str)");
|
|
if (!mem) {
|
|
task->fail();
|
|
return NULL;
|
|
}
|
|
rust_str *st = new (mem) rust_str(alloc, fill,
|
|
(uint8_t const *) s);
|
|
LOG(task, mem,
|
|
"upcall new_str('%s', %" PRIdPTR ") = 0x%" PRIxPTR,
|
|
s, fill, st);
|
|
return st;
|
|
}
|
|
|
|
extern "C" CDECL rust_str *
|
|
upcall_new_str(rust_task *task, char const *s, size_t fill) {
|
|
LOG_UPCALL_ENTRY(task);
|
|
return make_str(task, s, fill);
|
|
}
|
|
|
|
static rust_evec *
|
|
vec_grow(rust_task *task,
|
|
rust_evec *v,
|
|
size_t n_bytes,
|
|
uintptr_t *need_copy,
|
|
type_desc *td)
|
|
{
|
|
rust_scheduler *sched = task->sched;
|
|
LOG(task, mem,
|
|
"vec_grow(0x%" PRIxPTR ", %" PRIdPTR
|
|
"), rc=%" PRIdPTR " alloc=%" PRIdPTR ", fill=%" PRIdPTR
|
|
", need_copy=0x%" PRIxPTR,
|
|
v, n_bytes, v->ref_count, v->alloc, v->fill, need_copy);
|
|
|
|
*need_copy = 0;
|
|
size_t alloc = next_power_of_two(sizeof(rust_evec) + v->fill + n_bytes);
|
|
|
|
if (v->ref_count == 1) {
|
|
|
|
// Fastest path: already large enough.
|
|
if (v->alloc >= alloc) {
|
|
LOG(task, mem, "no-growth path");
|
|
return v;
|
|
}
|
|
|
|
// Second-fastest path: can at least realloc.
|
|
LOG(task, mem, "realloc path");
|
|
v = (rust_evec*) task->realloc(v, alloc, td->is_stateful);
|
|
if (!v) {
|
|
task->fail();
|
|
return NULL;
|
|
}
|
|
v->alloc = alloc;
|
|
|
|
} else {
|
|
/**
|
|
* Slowest path: make a new vec.
|
|
*
|
|
* 1. Allocate a new rust_evec with desired additional space.
|
|
* 2. Down-ref the shared rust_evec, point to the new one instead.
|
|
* 3. Copy existing elements into the new rust_evec.
|
|
*
|
|
* Step 3 is a bit tricky. We don't know how to properly copy the
|
|
* elements in the runtime (all we have are bits in a buffer; no
|
|
* type information and no copy glue). What we do instead is set the
|
|
* need_copy outparam flag to indicate to our caller (vec-copy glue)
|
|
* that we need the copies performed for us.
|
|
*/
|
|
LOG(task, mem, "new vec path");
|
|
void *mem = task->malloc(alloc, "rust_evec (vec_grow)", td);
|
|
if (!mem) {
|
|
task->fail();
|
|
return NULL;
|
|
}
|
|
|
|
if (v->ref_count != CONST_REFCOUNT)
|
|
v->deref();
|
|
|
|
v = new (mem) rust_evec(alloc, 0, NULL);
|
|
*need_copy = 1;
|
|
}
|
|
I(sched, sizeof(rust_evec) + v->fill <= v->alloc);
|
|
return v;
|
|
}
|
|
|
|
// Copy elements from one vector to another,
|
|
// dealing with reference counts
|
|
static inline void
|
|
copy_elements(rust_task *task, type_desc *elem_t,
|
|
void *pdst, void *psrc, size_t n)
|
|
{
|
|
char *dst = (char *)pdst, *src = (char *)psrc;
|
|
memmove(dst, src, n);
|
|
|
|
// increment the refcount of each element of the vector
|
|
if (elem_t->take_glue) {
|
|
glue_fn *take_glue = elem_t->take_glue;
|
|
size_t elem_size = elem_t->size;
|
|
const type_desc **tydescs = elem_t->first_param;
|
|
for (char *p = dst; p < dst+n; p += elem_size) {
|
|
take_glue(NULL, task, NULL, tydescs, p);
|
|
}
|
|
}
|
|
}
|
|
|
|
extern "C" CDECL void
|
|
upcall_evec_append(rust_task *task, type_desc *t, type_desc *elem_t,
|
|
rust_evec **dst_ptr, rust_evec *src, bool skip_null)
|
|
{
|
|
LOG_UPCALL_ENTRY(task);
|
|
rust_evec *dst = *dst_ptr;
|
|
uintptr_t need_copy;
|
|
size_t n_src_bytes = skip_null ? src->fill - 1 : src->fill;
|
|
size_t n_dst_bytes = skip_null ? dst->fill - 1 : dst->fill;
|
|
rust_evec *new_vec = vec_grow(task, dst, n_src_bytes, &need_copy, t);
|
|
|
|
// If src and dst are the same (due to "v += v"), then dst getting
|
|
// resized causes src to move as well.
|
|
if (dst == src && !need_copy) {
|
|
src = new_vec;
|
|
}
|
|
|
|
if (need_copy) {
|
|
// Copy any dst elements in, omitting null if doing str.
|
|
copy_elements(task, elem_t, &new_vec->data, &dst->data, n_dst_bytes);
|
|
}
|
|
|
|
// Copy any src elements in, carrying along null if doing str.
|
|
void *new_end = (void *)((char *)new_vec->data + n_dst_bytes);
|
|
copy_elements(task, elem_t, new_end, &src->data, src->fill);
|
|
new_vec->fill = n_dst_bytes + src->fill;
|
|
|
|
// Write new_vec back through the alias we were given.
|
|
*dst_ptr = new_vec;
|
|
}
|
|
|
|
// FIXME: Transitional. Please remove.
|
|
extern "C" CDECL void
|
|
upcall_vec_append(rust_task *task, type_desc *t, type_desc *elem_t,
|
|
rust_evec **dst_ptr, rust_evec *src, bool skip_null) {
|
|
upcall_evec_append(task, t, elem_t, dst_ptr, src, skip_null);
|
|
}
|
|
|
|
extern "C" CDECL type_desc *
|
|
upcall_get_type_desc(rust_task *task,
|
|
void *curr_crate, // ignored, legacy compat.
|
|
size_t size,
|
|
size_t align,
|
|
size_t n_descs,
|
|
type_desc const **descs,
|
|
uintptr_t n_obj_params) {
|
|
check_stack(task);
|
|
LOG_UPCALL_ENTRY(task);
|
|
|
|
LOG(task, cache, "upcall get_type_desc with size=%" PRIdPTR
|
|
", align=%" PRIdPTR ", %" PRIdPTR " descs", size, align,
|
|
n_descs);
|
|
rust_crate_cache *cache = task->get_crate_cache();
|
|
type_desc *td = cache->get_type_desc(size, align, n_descs, descs,
|
|
n_obj_params);
|
|
LOG(task, cache, "returning tydesc 0x%" PRIxPTR, td);
|
|
return td;
|
|
}
|
|
|
|
extern "C" CDECL void
|
|
upcall_vec_grow(rust_task* task, rust_vec** vp, size_t new_sz) {
|
|
LOG_UPCALL_ENTRY(task);
|
|
// FIXME factor this into a utility function
|
|
if (new_sz > (*vp)->alloc) {
|
|
size_t new_alloc = next_power_of_two(new_sz);
|
|
*vp = (rust_vec*)task->kernel->realloc(*vp, new_alloc +
|
|
sizeof(rust_vec));
|
|
(*vp)->alloc = new_alloc;
|
|
}
|
|
(*vp)->fill = new_sz;
|
|
}
|
|
|
|
extern "C" CDECL void
|
|
upcall_vec_push(rust_task* task, rust_vec** vp, type_desc* elt_ty,
|
|
void* elt) {
|
|
LOG_UPCALL_ENTRY(task);
|
|
rust_vec* v = *vp;
|
|
size_t new_sz = v->fill + elt_ty->size;
|
|
if (new_sz > v->alloc) {
|
|
size_t new_alloc = next_power_of_two(new_sz);
|
|
*vp = v = (rust_vec*)task->kernel->realloc(v, new_alloc +
|
|
sizeof(rust_vec));
|
|
v->alloc = new_alloc;
|
|
}
|
|
copy_elements(task, elt_ty, &v->data[0] + v->fill, elt, elt_ty->size);
|
|
v->fill += elt_ty->size;
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns a token that can be used to deallocate all of the allocated space
|
|
* space in the dynamic stack.
|
|
*/
|
|
extern "C" CDECL void *
|
|
upcall_dynastack_mark(rust_task *task) {
|
|
return task->dynastack.alloc(0);
|
|
}
|
|
|
|
/** Allocates space in the dynamic stack and returns it. */
|
|
extern "C" CDECL void *
|
|
upcall_dynastack_alloc(rust_task *task, size_t sz) {
|
|
return sz ? task->dynastack.alloc(sz) : NULL;
|
|
}
|
|
|
|
/** Frees space in the dynamic stack. */
|
|
extern "C" CDECL void
|
|
upcall_dynastack_free(rust_task *task, void *ptr) {
|
|
return task->dynastack.free(ptr);
|
|
}
|
|
|
|
//
|
|
// Local Variables:
|
|
// mode: C++
|
|
// fill-column: 78;
|
|
// indent-tabs-mode: nil
|
|
// c-basic-offset: 4
|
|
// buffer-file-coding-system: utf-8-unix
|
|
// compile-command: "make -k -C $RBUILD 2>&1 | sed -e 's/\\/x\\//x:\\//g'";
|
|
// End:
|
|
//
|