/* Upcalls These are runtime functions that the compiler knows about and generates calls to. They are called on the Rust stack and, in most cases, immediately switch to the C stack. */ #include "rust_globals.h" #include "rust_task.h" #include "rust_sched_loop.h" #include "rust_unwind.h" #include "rust_upcall.h" #include "rust_util.h" #ifdef __GNUC__ #define LOG_UPCALL_ENTRY(task) \ LOG(task, upcall, \ "> UPCALL %s - task: %s 0x%" PRIxPTR \ " retpc: x%" PRIxPTR, \ __FUNCTION__, \ (task)->name, (task), \ __builtin_return_address(0)); #else #define LOG_UPCALL_ENTRY(task) \ LOG(task, upcall, "> UPCALL task: %s @x%" PRIxPTR, \ (task)->name, (task)); #endif #define UPCALL_SWITCH_STACK(T, A, F) \ call_upcall_on_c_stack(T, (void*)A, (void*)F) inline void call_upcall_on_c_stack(rust_task *task, void *args, void *fn_ptr) { task->call_on_c_stack(args, fn_ptr); } /********************************************************************** * Switches to the C-stack and invokes |fn_ptr|, passing |args| as argument. * This is used by the C compiler to call foreign functions and by other * upcalls to switch to the C stack. The return value is passed through a * field in the args parameter. This upcall is specifically for switching * to the shim functions generated by rustc. */ extern "C" CDECL void upcall_call_shim_on_c_stack(void *args, void *fn_ptr) { rust_task *task = rust_get_current_task(); try { task->call_on_c_stack(args, fn_ptr); } catch (...) { // Logging here is not reliable assert(false && "Foreign code threw an exception"); } } /* * The opposite of above. Starts on a C stack and switches to the Rust * stack. This is the only upcall that runs from the C stack. */ extern "C" CDECL void upcall_call_shim_on_rust_stack(void *args, void *fn_ptr) { rust_task *task = rust_get_current_task(); try { task->call_on_rust_stack(args, fn_ptr); } catch (...) { // We can't count on being able to unwind through arbitrary // code. Our best option is to just fail hard. // Logging here is not reliable assert(false && "Rust task failed after reentering the Rust stack"); } } /**********************************************************************/ struct s_fail_args { rust_task *task; char const *expr; char const *file; size_t line; }; extern "C" CDECL void upcall_s_fail(s_fail_args *args) { rust_task *task = args->task; LOG_UPCALL_ENTRY(task); task->fail(args->expr, args->file, args->line); } extern "C" CDECL void upcall_fail(char const *expr, char const *file, size_t line) { rust_task *task = rust_get_current_task(); s_fail_args args = {task,expr,file,line}; UPCALL_SWITCH_STACK(task, &args, upcall_s_fail); } // FIXME (#2861): Alias used by libcore/rt.rs to avoid naming conflicts with // autogenerated wrappers for upcall_fail. Remove this when we fully move away // away from the C upcall path. extern "C" CDECL void rust_upcall_fail(char const *expr, char const *file, size_t line) { upcall_fail(expr, file, line); } struct s_trace_args { rust_task *task; char const *msg; char const *file; size_t line; }; extern "C" CDECL void upcall_s_trace(s_trace_args *args) { rust_task *task = args->task; LOG_UPCALL_ENTRY(task); LOG(task, trace, "Trace %s:%d: %s", args->file, args->line, args->msg); } extern "C" CDECL void upcall_trace(char const *msg, char const *file, size_t line) { rust_task *task = rust_get_current_task(); s_trace_args args = {task,msg,file,line}; UPCALL_SWITCH_STACK(task, &args, upcall_s_trace); } /********************************************************************** * Allocate an object in the exchange heap */ struct s_exchange_malloc_args { rust_task *task; uintptr_t retval; type_desc *td; uintptr_t size; }; extern "C" CDECL void upcall_s_exchange_malloc(s_exchange_malloc_args *args) { rust_task *task = args->task; LOG_UPCALL_ENTRY(task); size_t total_size = get_box_size(args->size, args->td->align); // FIXME--does this have to be calloc? (Issue #2682) void *p = task->kernel->calloc(total_size, "exchange malloc"); rust_opaque_box *header = static_cast(p); header->ref_count = -1; // This is not ref counted header->td = args->td; header->prev = 0; header->next = 0; LOG(task, mem, "exchange malloced %p of size %" PRIuPTR, header, args->size); args->retval = (uintptr_t)header; } extern "C" CDECL uintptr_t upcall_exchange_malloc(type_desc *td, uintptr_t size) { rust_task *task = rust_get_current_task(); s_exchange_malloc_args args = {task, 0, td, size}; UPCALL_SWITCH_STACK(task, &args, upcall_s_exchange_malloc); return args.retval; } // FIXME (#2861): Alias used by libcore/rt.rs to avoid naming conflicts with // autogenerated wrappers for upcall_exchange_malloc. Remove this when we // fully move away away from the C upcall path. extern "C" CDECL uintptr_t rust_upcall_exchange_malloc(type_desc *td, uintptr_t size) { return upcall_exchange_malloc(td, size); } struct s_exchange_free_args { rust_task *task; void *ptr; }; extern "C" CDECL void upcall_s_exchange_free(s_exchange_free_args *args) { rust_task *task = args->task; LOG_UPCALL_ENTRY(task); LOG(task, mem, "exchange freed %p", args->ptr); task->kernel->free(args->ptr); } extern "C" CDECL void upcall_exchange_free(void *ptr) { rust_task *task = rust_get_current_task(); s_exchange_free_args args = {task,ptr}; UPCALL_SWITCH_STACK(task, &args, upcall_s_exchange_free); } // FIXME (#2861): Alias used by libcore/rt.rs to avoid naming conflicts with // autogenerated wrappers for upcall_exchange_free. Remove this when we fully // move away away from the C upcall path. extern "C" CDECL void rust_upcall_exchange_free(void *ptr) { return upcall_exchange_free(ptr); } /********************************************************************** * Allocate an object in the task-local heap. */ struct s_malloc_args { rust_task *task; uintptr_t retval; type_desc *td; uintptr_t size; }; extern "C" CDECL void upcall_s_malloc(s_malloc_args *args) { rust_task *task = args->task; LOG_UPCALL_ENTRY(task); LOG(task, mem, "upcall malloc(0x%" PRIxPTR ")", args->td); // FIXME--does this have to be calloc? (Issue #2682) rust_opaque_box *box = task->boxed.calloc(args->td, args->size); void *body = box_body(box); debug::maybe_track_origin(task, box); LOG(task, mem, "upcall malloc(0x%" PRIxPTR ") = box 0x%" PRIxPTR " with body 0x%" PRIxPTR, args->td, (uintptr_t)box, (uintptr_t)body); args->retval = (uintptr_t)box; } extern "C" CDECL uintptr_t upcall_malloc(type_desc *td, uintptr_t size) { rust_task *task = rust_get_current_task(); s_malloc_args args = {task, 0, td, size}; UPCALL_SWITCH_STACK(task, &args, upcall_s_malloc); return args.retval; } // FIXME (#2861): Alias used by libcore/rt.rs to avoid naming conflicts with // autogenerated wrappers for upcall_malloc. Remove this when we fully move // away away from the C upcall path. extern "C" CDECL uintptr_t rust_upcall_malloc(type_desc *td, uintptr_t size) { return upcall_malloc(td, size); } /********************************************************************** * Called whenever an object in the task-local heap is freed. */ struct s_free_args { rust_task *task; void *ptr; }; extern "C" CDECL void upcall_s_free(s_free_args *args) { rust_task *task = args->task; LOG_UPCALL_ENTRY(task); rust_sched_loop *sched_loop = task->sched_loop; DLOG(sched_loop, mem, "upcall free(0x%" PRIxPTR ", is_gc=%" PRIdPTR ")", (uintptr_t)args->ptr); debug::maybe_untrack_origin(task, args->ptr); rust_opaque_box *box = (rust_opaque_box*) args->ptr; task->boxed.free(box); } extern "C" CDECL void upcall_free(void* ptr) { rust_task *task = rust_get_current_task(); s_free_args args = {task,ptr}; UPCALL_SWITCH_STACK(task, &args, upcall_s_free); } // FIXME (#2861): Alias used by libcore/rt.rs to avoid naming conflicts with // autogenerated wrappers for upcall_free. Remove this when we fully move away // away from the C upcall path. extern "C" CDECL void rust_upcall_free(void* ptr) { upcall_free(ptr); } /********************************************************************** * Sanity checks on boxes, insert when debugging possible * use-after-free bugs. See maybe_validate_box() in trans.rs. */ extern "C" CDECL void upcall_validate_box(rust_opaque_box* ptr) { if (ptr) { assert(ptr->ref_count > 0); assert(ptr->td != NULL); assert(ptr->td->align <= 8); assert(ptr->td->size <= 4096); // might not really be true... } } /**********************************************************************/ struct s_str_new_uniq_args { rust_task *task; const char *cstr; size_t len; rust_str *retval; }; extern "C" CDECL void upcall_s_str_new_uniq(s_str_new_uniq_args *args) { rust_task *task = args->task; LOG_UPCALL_ENTRY(task); args->retval = make_str(task->kernel, args->cstr, args->len, "str_new_uniq"); } extern "C" CDECL rust_str* upcall_str_new_uniq(const char *cstr, size_t len) { rust_task *task = rust_get_current_task(); s_str_new_uniq_args args = { task, cstr, len, 0 }; UPCALL_SWITCH_STACK(task, &args, upcall_s_str_new_uniq); return args.retval; } extern "C" CDECL rust_str* upcall_str_new(const char *cstr, size_t len) { rust_task *task = rust_get_current_task(); s_str_new_uniq_args args = { task, cstr, len, 0 }; UPCALL_SWITCH_STACK(task, &args, upcall_s_str_new_uniq); return args.retval; } struct s_str_new_shared_args { rust_task *task; const char *cstr; size_t len; rust_opaque_box *retval; }; extern "C" CDECL void upcall_s_str_new_shared(s_str_new_shared_args *args) { rust_task *task = args->task; LOG_UPCALL_ENTRY(task); size_t str_fill = args->len + 1; size_t str_alloc = str_fill; args->retval = (rust_opaque_box *) task->boxed.malloc(&str_body_tydesc, str_fill + sizeof(rust_vec)); rust_str *str = (rust_str *)args->retval; str->body.fill = str_fill; str->body.alloc = str_alloc; memcpy(&str->body.data, args->cstr, args->len); str->body.data[args->len] = '\0'; } extern "C" CDECL rust_opaque_box* upcall_str_new_shared(const char *cstr, size_t len) { rust_task *task = rust_get_current_task(); s_str_new_shared_args args = { task, cstr, len, 0 }; UPCALL_SWITCH_STACK(task, &args, upcall_s_str_new_shared); return args.retval; } extern "C" _Unwind_Reason_Code __gxx_personality_v0(int version, _Unwind_Action actions, uint64_t exception_class, _Unwind_Exception *ue_header, _Unwind_Context *context); struct s_rust_personality_args { _Unwind_Reason_Code retval; int version; _Unwind_Action actions; uint64_t exception_class; _Unwind_Exception *ue_header; _Unwind_Context *context; }; extern "C" void upcall_s_rust_personality(s_rust_personality_args *args) { args->retval = __gxx_personality_v0(args->version, args->actions, args->exception_class, args->ue_header, args->context); } /** The exception handling personality function. It figures out what to do with each landing pad. Just a stack-switching wrapper around the C++ personality function. */ extern "C" _Unwind_Reason_Code upcall_rust_personality(int version, _Unwind_Action actions, uint64_t exception_class, _Unwind_Exception *ue_header, _Unwind_Context *context) { s_rust_personality_args args = {(_Unwind_Reason_Code)0, version, actions, exception_class, ue_header, context}; rust_task *task = rust_get_current_task(); // The personality function is run on the stack of the // last function that threw or landed, which is going // to sometimes be the C stack. If we're on the Rust stack // then switch to the C stack. if (task->on_rust_stack()) { UPCALL_SWITCH_STACK(task, &args, upcall_s_rust_personality); } else { upcall_s_rust_personality(&args); } return args.retval; } extern "C" void shape_cmp_type(int8_t *result, const type_desc *tydesc, uint8_t *data_0, uint8_t *data_1, uint8_t cmp_type); struct s_cmp_type_args { int8_t *result; const type_desc *tydesc; uint8_t *data_0; uint8_t *data_1; uint8_t cmp_type; }; extern "C" void upcall_s_cmp_type(s_cmp_type_args *args) { shape_cmp_type(args->result, args->tydesc, args->data_0, args->data_1, args->cmp_type); } extern "C" void upcall_cmp_type(int8_t *result, const type_desc *tydesc, uint8_t *data_0, uint8_t *data_1, uint8_t cmp_type) { rust_task *task = rust_get_current_task(); s_cmp_type_args args = {result, tydesc, data_0, data_1, cmp_type}; UPCALL_SWITCH_STACK(task, &args, upcall_s_cmp_type); } extern "C" void shape_log_type(const type_desc *tydesc, uint8_t *data, uint32_t level); struct s_log_type_args { const type_desc *tydesc; uint8_t *data; uint32_t level; }; extern "C" void upcall_s_log_type(s_log_type_args *args) { shape_log_type(args->tydesc, args->data, args->level); } extern "C" void upcall_log_type(const type_desc *tydesc, uint8_t *data, uint32_t level) { rust_task *task = rust_get_current_task(); s_log_type_args args = {tydesc, data, level}; UPCALL_SWITCH_STACK(task, &args, upcall_s_log_type); } // NB: This needs to be blazing fast. Don't switch stacks extern "C" CDECL void * upcall_new_stack(size_t stk_sz, void *args_addr, size_t args_sz) { rust_task *task = rust_get_current_task(); return task->next_stack(stk_sz, args_addr, args_sz); } // NB: This needs to be blazing fast. Don't switch stacks extern "C" CDECL void upcall_del_stack() { rust_task *task = rust_get_current_task(); task->prev_stack(); } // Landing pads need to call this to insert the // correct limit into TLS. // NB: This must run on the Rust stack because it // needs to acquire the value of the stack pointer extern "C" CDECL void upcall_reset_stack_limit() { rust_task *task = rust_get_current_task(); task->reset_stack_limit(); } // // Local Variables: // mode: C++ // fill-column: 78; // indent-tabs-mode: nil // c-basic-offset: 4 // buffer-file-coding-system: utf-8-unix // End: //