/* * ia32abicc.c * * Support for Call-outs and Call-backs from the Plugin. * Written by Eliot Miranda 11/07. */ #if defined(_MSC_VER) || defined(__MINGW32__) # include "windows.h" /* for GetSystemInfo & VirtualAlloc */ #elif __APPLE__ && __MACH__ # include /* for mprotect */ # if OBJC_DEBUG /* define this to get debug info for struct objc_class et al */ # include # include struct objc_class *baz; void setbaz(void *p) { baz = p; } void *getbaz() { return baz; } # endif # include /* for valloc */ # include /* for mprotect */ #else # include /* for valloc */ # include /* for mprotect */ #endif #include /* for memcpy et al */ #include #include /* for fprintf(stderr,...) */ #include "vmCallback.h" #include "sqAssert.h" #include "sqMemoryAccess.h" #include "sqVirtualMachine.h" #include "ia32abi.h" #if !defined(min) # define min(a,b) ((a) < (b) ? (a) : (b)) #endif #ifdef SQUEAK_BUILTIN_PLUGIN extern #endif struct VirtualMachine* interpreterProxy; #ifdef _MSC_VER # define alloca _alloca #endif #if __GNUC__ # define setsp(sp) asm volatile ("movl %0,%%esp" : : "m"(sp)) # define getsp() ({ void *esp; asm volatile ("movl %%esp,%0" : "=r"(esp) : ); esp;}) #endif #if __APPLE__ && __MACH__ && __i386__ # define STACK_ALIGN_BYTES 16 #elif __linux__ && __i386__ # define STACK_ALIGN_BYTES 16 #elif defined(WIN32) && __SSE2__ /* using sse2 instructions requires 16-byte stack alignment but on win32 there's * no guarantee that libraries preserve alignment so compensate on callback. */ # define STACK_ALIGN_HACK 1 # define STACK_ALIGN_BYTES 16 #endif #if !defined(setsp) # define setsp(ignored) 0 #endif #define moduloPOT(m,v) (((v)+(m)-1) & ~((m)-1)) #define alignModuloPOT(m,v) ((void *)moduloPOT(m,(unsigned long)(v))) #define objIsAlien(anOop) (interpreterProxy->includesBehaviorThatOf(interpreterProxy->fetchClassOf(anOop), interpreterProxy->classAlien())) #define objIsUnsafeAlien(anOop) (interpreterProxy->includesBehaviorThatOf(interpreterProxy->fetchClassOf(anOop), interpreterProxy->classUnsafeAlien())) #define sizeField(alien) (*(long *)pointerForOop((sqInt)(alien) + BaseHeaderSize)) #define dataPtr(alien) pointerForOop((sqInt)(alien) + BaseHeaderSize + BytesPerOop) #if 0 /* obsolete after adding pointer Aliens with size field == 0 */ # define isIndirectOrPointer(alien) (sizeField(alien) <= 0) # define startOfData(alien) (isIndirectOrPointer(alien) \ ? *(void **)dataPtr(alien) \ : (void *)dataPtr(alien)) #endif #define isIndirect(alien) (sizeField(alien) < 0) #define startOfParameterData(alien) (isIndirect(alien) \ ? *(void **)dataPtr(alien) \ : (void *)dataPtr(alien)) #define isIndirectSize(size) ((size) < 0) #define startOfDataWithSize(alien,size) (isIndirectSize(size) \ ? *(void **)dataPtr(alien) \ : (void *)dataPtr(alien)) #define isSmallInt(oop) ((oop)&1) #define intVal(oop) (((long)(oop))>>1) /* * Call a foreign function that answers an integral result in %eax (and * possibly %edx) according to IA32-ish ABI rules. */ sqInt callIA32IntegralReturn(SIGNATURE) { #ifdef _MSC_VER __int64 (*f)(), r; #else long long (*f)(), r; #endif #include "dabusiness.h" } /* * Call a foreign function that answers a single-precision floating-point * result in %f0 according to IA32-ish ABI rules. */ sqInt callIA32FloatReturn(SIGNATURE) { float (*f)(), r; #include "dabusiness.h" } /* * Call a foreign function that answers a double-precision floating-point * result in %f0 according to IA32-ish ABI rules. */ sqInt callIA32DoubleReturn(SIGNATURE) { double (*f)(), r; #include "dabusiness.h" } /* Queueing order for callback returns. To ensure that callback returns occur * in LIFO order we provide mostRecentCallbackContext which is tested by the * return primitive primReturnFromContextThrough. Note that in the threaded VM * this does not have to be thread-specific or locked since it is within the * bounds of the ownVM/disownVM pair. */ static VMCallbackContext *mostRecentCallbackContext = 0; VMCallbackContext * getMostRecentCallbackContext() { return mostRecentCallbackContext; } #define getRMCC(t) mostRecentCallbackContext #define setRMCC(t) (mostRecentCallbackContext = (void *)(t)) /* * Entry-point for call-back thunks. Args are thunk address and stack pointer, * where the stack pointer is pointing one word below the return address of the * thunk's callee, 4 bytes below the thunk's first argument. The stack is: * callback * arguments * retpc (thunk) <--\ * address of retpc-/ <--\ * address of address of ret pc-/ * thunkp * esp->retpc (thunkEntry) * * The stack pointer is pushed twice to keep the stack alignment to 16 bytes, a * requirement on platforms using SSE2 such as Mac OS X, and harmless elsewhere. * * This function's roles are to use setjmp/longjmp to save the call point * and return to it, to correct C stack pointer alignment if necessary (see * STACK_ALIGN_HACK), and to return any of the various values from the callback. * * Looking forward to support for x86-64, which typically has 6 register * arguments, the function would take 8 arguments, the 6 register args as * longs, followed by the thunkp and stackp passed on the stack. The register * args would get copied into a struct on the stack. A pointer to the struct * is then passed as an element of the VMCallbackContext. */ long thunkEntry(void *thunkp, long *stackp) { VMCallbackContext vmcc; VMCallbackContext *previousCallbackContext; int flags, returnType; #if STACK_ALIGN_HACK { void *sp = getsp(); int offset = (unsigned long)sp & (STACK_ALIGN_BYTES - 1); if (offset) { # if _MSC_VER _asm sub esp, dword ptr offset; # elif __GNUC__ asm("sub %0,%%esp" : : "m"(offset)); # else # error need to subtract offset from esp # endif sp = getsp(); assert(!((unsigned long)sp & (STACK_ALIGN_BYTES - 1))); } } #endif /* STACK_ALIGN_HACK */ if ((flags = interpreterProxy->ownVM(0)) < 0) { fprintf(stderr,"Warning; callback failed to own the VM\n"); return -1; } if (!(returnType = setjmp(vmcc.trampoline))) { previousCallbackContext = getRMCC(); setRMCC(&vmcc); vmcc.thunkp = thunkp; vmcc.stackp = stackp + 2; /* skip address of retpc & retpc (thunk) */ vmcc.intregargsp = 0; vmcc.floatregargsp = 0; interpreterProxy->sendInvokeCallbackContext(&vmcc); fprintf(stderr,"Warning; callback failed to invoke\n"); setRMCC(previousCallbackContext); interpreterProxy->disownVM(flags); return -1; } setRMCC(previousCallbackContext); interpreterProxy->disownVM(flags); switch (returnType) { case retword: return vmcc.rvs.valword; case retword64: { long vhigh = vmcc.rvs.valleint64.high; #if _MSC_VER _asm mov edx, dword ptr vhigh; #elif __GNUC__ asm("mov %0,%%edx" : : "m"(vhigh)); #else # error need to load edx with vmcc.rvs.valleint64.high on this compiler #endif return vmcc.rvs.valleint64.low; } case retdouble: { double valflt64 = vmcc.rvs.valflt64; #if _MSC_VER _asm fld qword ptr valflt64; #elif __GNUC__ asm("fldl %0" : : "m"(valflt64)); #else # error need to load %f0 with vmcc.rvs.valflt64 on this compiler #endif return 0; } case retstruct: memcpy( (void *)(stackp[1]), vmcc.rvs.valstruct.addr, vmcc.rvs.valstruct.size); return stackp[1]; } fprintf(stderr,"Warning; invalid callback return type\n"); return 0; } /* * Thunk allocation support. Since thunks must be exectuable and some OSs * may not provide default execute permission on memory returned by malloc * we must provide memory that is guaranteed to be executable. The abstraction * is to answer an Alien that references an executable piece of memory that * is some (possiby unitary) multiple of the pagesize. * * We assume the Smalltalk image code will manage subdividing the executable * page amongst thunks so there is no need to free these pages, since the image * will recycle parts of the page for reclaimed thunks. */ #if defined(_MSC_VER) || defined(__MINGW32__) static unsigned long pagesize = 0; #endif void * allocateExecutablePage(long *size) { void *mem; #if defined(_MSC_VER) || defined(__MINGW32__) #if !defined(MEM_TOP_DOWN) # define MEM_TOP_DOWN 0x100000 #endif if (!pagesize) { SYSTEM_INFO sysinf; GetSystemInfo(&sysinf); pagesize = sysinf.dwPageSize; } /* N.B. VirtualAlloc MEM_COMMIT initializes the memory returned to zero. */ mem = VirtualAlloc( 0, pagesize, MEM_COMMIT | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE); if (mem) *size = pagesize; #else long pagesize = getpagesize(); if (!(mem = valloc(pagesize))) return 0; memset(mem, 0, pagesize); if (mprotect(mem, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC) < 0) { free(mem); return 0; } *size = pagesize; #endif return mem; }