/* ----------------------------------------------------------------------------- * libffi-based adjustor thunk logic. * ---------------------------------------------------------------------------*/ #include "PosixSource.h" #include "Rts.h" #include "RtsUtils.h" #include "StablePtr.h" #include "sm/Storage.h" #include "Hash.h" #include "ffi.h" #include /* Maps AdjustorExecutable* to AdjustorWritable*. */ static HashTable* allocatedExecs; static AdjustorWritable allocate_adjustor(AdjustorExecutable *exec_ret) { AdjustorWritable writ; ffi_closure* cl; ACQUIRE_SM_LOCK; cl = writ = ffi_closure_alloc(sizeof(ffi_closure), exec_ret); if (cl != NULL) { if (allocatedExecs == NULL) { allocatedExecs = allocHashTable(); } insertHashTable(allocatedExecs, (StgWord)*exec_ret, writ); } RELEASE_SM_LOCK; return writ; } static AdjustorWritable exec_to_writable(AdjustorExecutable exec) { AdjustorWritable writ; ACQUIRE_SM_LOCK; if (allocatedExecs == NULL || (writ = lookupHashTable(allocatedExecs, (StgWord)exec)) == NULL) { RELEASE_SM_LOCK; barf("exec_to_writable: not found"); } RELEASE_SM_LOCK; return writ; } static void free_adjustor(AdjustorExecutable exec) { AdjustorWritable writ; ffi_closure* cl; cl = writ = exec_to_writable(exec); ACQUIRE_SM_LOCK; removeHashTable(allocatedExecs, (StgWord)exec, writ); ffi_closure_free(cl); RELEASE_SM_LOCK; } /* Note [Freeing libffi adjustors] * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * HOW ADJUSTORS/CLOSURES WORK ON LIBFFI: * libffi's ffi_closure_alloc() function gives you two pointers to a closure, * 1. the writable pointer, and 2. the executable pointer. You write the * closure into the writable pointer (and ffi_prep_closure_loc() will do this * for you) and you execute it at the executable pointer. * * THE PROBLEM: * The RTS deals only with the executable pointer, but when it comes time to * free the closure, libffi wants the writable pointer back that it gave you * when you allocated it. * * On Linux we used to solve this problem by storing the address of the writable * mapping into itself, then returning both writable and executable pointers * plus 1 machine word for preparing the closure for use by the RTS (see the * Linux version of allocateExec() in rts/sm/Storage.c). When we want to recover * the writable address, we subtract 1 word from the executable address and * fetch. This works because Linux kernel magic gives us two pointers with * different addresses that refer to the same memory. Whatever you write into * the writable address can be read back at the executable address. This method * is very efficient. * * On iOS this breaks for two reasons: 1. the two pointers do not refer to * the same memory (so we can't retrieve anything stored into the writable * pointer if we only have the exec pointer), and 2. libffi's * ffi_closure_alloc() assumes the pointer it has returned you is a * ffi_closure structure and treats it as such: It uses that memory to * communicate with ffi_prep_closure_loc(). On Linux by contrast * ffi_closure_alloc() is viewed simply as a memory allocation, and only * ffi_prep_closure_loc() deals in ffi_closure structures. Each of these * differences is enough make the efficient way used on Linux not work on iOS. * Instead on iOS we use hash tables to recover the writable address from the * executable one. This method is conservative and would almost certainly work * on any platform. This is what we now do everywhere. */ void freeHaskellFunctionPtr(void* ptr) { ffi_closure *cl; cl = exec_to_writable(ptr); freeStablePtr(cl->user_data); stgFree(cl->cif->arg_types); stgFree(cl->cif); free_adjustor(ptr); } static ffi_type * char_to_ffi_type(char c) { switch (c) { case 'v': return &ffi_type_void; case 'f': return &ffi_type_float; case 'd': return &ffi_type_double; case 'L': return &ffi_type_sint64; case 'l': return &ffi_type_uint64; case 'W': return &ffi_type_sint32; case 'w': return &ffi_type_uint32; case 'S': return &ffi_type_sint16; case 's': return &ffi_type_uint16; case 'B': return &ffi_type_sint8; case 'b': return &ffi_type_uint8; case 'p': return &ffi_type_pointer; default: barf("char_to_ffi_type: unknown type '%c'", c); } } void* createAdjustor (int cconv, StgStablePtr hptr, StgFunPtr wptr, char *typeString) { ffi_cif *cif; ffi_type **arg_types; uint32_t n_args, i; ffi_type *result_type; ffi_closure *cl; int r, abi; void *code; n_args = strlen(typeString) - 1; cif = stgMallocBytes(sizeof(ffi_cif), "createAdjustor"); arg_types = stgMallocBytes(n_args * sizeof(ffi_type*), "createAdjustor"); result_type = char_to_ffi_type(typeString[0]); for (i=0; i < n_args; i++) { arg_types[i] = char_to_ffi_type(typeString[i+1]); } switch (cconv) { #if defined(mingw32_HOST_OS) && defined(i386_HOST_ARCH) case 0: /* stdcall */ abi = FFI_STDCALL; break; #endif /* defined(mingw32_HOST_OS) && defined(i386_HOST_ARCH) */ case 1: /* ccall */ abi = FFI_DEFAULT_ABI; break; default: barf("createAdjustor: convention %d not supported on this platform", cconv); } r = ffi_prep_cif(cif, abi, n_args, result_type, arg_types); if (r != FFI_OK) barf("ffi_prep_cif failed: %d", r); cl = allocate_adjustor(&code); if (cl == NULL) { barf("createAdjustor: failed to allocate memory"); } r = ffi_prep_closure_loc(cl, cif, (void*)wptr, hptr/*userdata*/, code); if (r != FFI_OK) barf("ffi_prep_closure_loc failed: %d", r); return (void*)code; }