Eliot Miranda uploaded a new version of VMMaker to project VM Maker: http://source.squeak.org/VMMaker/VMMaker.oscog-eem.2760.mcz ==================== Summary ==================== Name: VMMaker.oscog-eem.2760 Author: eem Time: 15 June 2020, 4:41:45.142399 pm UUID: af828f7f-26c7-4016-9cf1-6175d46e5ab4 Ancestors: VMMaker.oscog-eem.2759 ThreadedFFIPlugin: Include the version info in the module name. Fix a speeling rorre =============== Diff against VMMaker.oscog-eem.2759 =============== Item was changed: ----- Method: CogARMCompiler>>leafCallStackPointerDelta (in category 'abi') ----- leafCallStackPointerDelta "Answer the delta from the stack pointer after a call to the stack pointer immediately prior to the call. This is used to compute the stack pointer + immediately prior to a call from within a leaf routine, which in turn is used - immediately prior to call from within a leaf routine, which in turn is used to capture the c stack pointer to use in trampolines back into the C run-time." "This might actually be false, since directly after a call, lr, fp and variable registers need be pushed onto the stack. It depends on the implementation of call." ^0! Item was changed: ----- Method: CogIA32Compiler>>leafCallStackPointerDelta (in category 'abi') ----- leafCallStackPointerDelta "Answer the delta from the stack pointer after a call to the stack pointer immediately prior to the call. This is used to compute the stack pointer + immediately prior to a call from within a leaf routine, which in turn is used - immediately prior to call from within a leaf routine, which in turn is used to capture the c stack pointer to use in trampolines back into the C run-time." ^4! Item was changed: ----- Method: CogMIPSELCompiler>>leafCallStackPointerDelta (in category 'abi') ----- leafCallStackPointerDelta "Answer the delta from the stack pointer after a call to the stack pointer immediately prior to the call. This is used to compute the stack pointer + immediately prior to a call from within a leaf routine, which in turn is used - immediately prior to call from within a leaf routine, which in turn is used to capture the c stack pointer to use in trampolines back into the C run-time." "This might actually be false, since directly after a call, lr, fp and variable registers need be pushed onto the stack. It depends on the implementation of call." ^0! Item was changed: ----- Method: CogX64Compiler>>leafCallStackPointerDelta (in category 'abi') ----- leafCallStackPointerDelta "Answer the delta from the stack pointer after a call to the stack pointer immediately prior to the call. This is used to compute the stack pointer + immediately prior to a call from within a leaf routine, which in turn is used - immediately prior to call from within a leaf routine, which in turn is used to capture the c stack pointer to use in trampolines back into the C run-time." ^8! Item was changed: InterpreterPlugin subclass: #ThreadedFFIPlugin instanceVariableNames: 'ffiLogEnabled externalFunctionInstSize ffiLastError allocationMap' + classVariableNames: 'DefaultMaxStackSize ExternalFunctionAddressIndex ExternalFunctionArgTypesIndex ExternalFunctionFlagsIndex ExternalFunctionStackSizeIndex MaxNumArgs PluginVersionInfo' - classVariableNames: 'DefaultMaxStackSize ExternalFunctionAddressIndex ExternalFunctionArgTypesIndex ExternalFunctionFlagsIndex ExternalFunctionStackSizeIndex MaxNumArgs' poolDictionaries: 'FFIConstants' category: 'VMMaker-Plugins-FFI'! !ThreadedFFIPlugin commentStamp: 'eem 2/6/2019 16:40' prior: 0! This plugin provides access to foreign function interfaces on those platforms that provide such. For example Windows DLLs and unix .so's. This version is designed to support reentrancy and threading, and so uses alloca to stack allocate all memory needed for a given callout. Specific platforms are implemented by concrete subclasses. Threaded calls can only be provided within the context of the threaded VM; othewise calls must be blocking. So code specific to threading is guarded with a self cppIf: COGMTVM ifTrue: [...] form to arrange that it is only compiled in the threaded VM context. The callout primitives consume a type spec that defines the signature of the function to be called and a vector of arguments. The type spec may be extracted from the method containing an FFI pragma or as an explicit parameter. The arguments to be passed may either be arguments to the the method containing an FFI pragma or as an explicit Array of parameters. Space is allocated to house the marshalled parameters and the type spec and arguments are then parsed to marshall the actual parameters into that space. The space is some combination of alloca'ed (stack allocated) memory for parameters passed on the stack and a "callout state" struct (an instance of ThreadedFFICalloutState) to hold any parameters to be passed in registers. By using C's facilities appropriately we can arrange that the C compiler generates code for passing all parameters, avoiding having to descend to the assembler or machine-code level (*). The basic scheme is to use alloca to stack allocate space for passing stacked parameters, since the memory allocated by alloca is at top-of-stack and in exactly the right place for parameter passing (*), and to invoke the function to be called with as many arguments as there are integer register parameters in the calling convention. For example, on x86/IA32 there are no register parameters and all arguments are passed on the stack, while on ARMv4/5/6 there are four integer register parameters. Since float results are typically answered through a floating-point register and integer/pointer results answered through one (or, on 32-bits for 64-bit results, two) register(s). So on x86 the function to be called is invoked using floatRet := self dispatchFunctionPointer: (self cCoerceSimple: procAddr to: 'double (*)()') or intRet := self dispatchFunctionPointer: (self cCoerceSimple: procAddr to: 'usqLong (*)()') On ARMv4/5/6 it is invoked with floatRet := self dispatchFunctionPointer: (self cCoerceSimple: procAddr to: 'float (*)(sqIntptr_t, sqIntptr_t, sqIntptr_t, sqIntptr_t)') with: (calloutState integerRegisters at: 0) with: (calloutState integerRegisters at: 1) with: (calloutState integerRegisters at: 2) with: (calloutState integerRegisters at: 3) or floatRet := self dispatchFunctionPointer: (self cCoerceSimple: procAddr to: 'double (*)(sqIntptr_t, sqIntptr_t, sqIntptr_t, sqIntptr_t)') with: (calloutState integerRegisters at: 0) with: (calloutState integerRegisters at: 1) with: (calloutState integerRegisters at: 2) with: (calloutState integerRegisters at: 3) or intRet := self dispatchFunctionPointer: (self cCoerceSimple: procAddr to: 'usqIntptr_t (*)(sqIntptr_t, sqIntptr_t, sqIntptr_t, sqIntptr_t)') with: (calloutState integerRegisters at: 0) with: (calloutState integerRegisters at: 1) with: (calloutState integerRegisters at: 2) with: (calloutState integerRegisters at: 3) Hence the C compiler generates the code to pass register parameters appropriately. All we have to do is provide sufficient register parameters and give the function (pointer) to be called a suitable type. Likewise we also persuade the C compiler to generate code to load any floating-point register arguments by preceding any call of a function that takes floating-point arguments passed in registers with a call to loadFloatRegs with as many floating-point parameters as there are floating-point parameter registers. loadFloatRegs is implemented in platforms//Cross/plugins/SqueakFFIPrims/sqFFIPlugin.c as an empty function. So floating-point registers are passed via calls such as: calloutState floatRegisterIndex > 0 ifTrue: [self loadFloatRegs: ((self cCoerceSimple: (self addressOf: (calloutState floatRegisters at: 0)) to: #'double *') at: 0) _: ((self cCoerceSimple: (self addressOf: (calloutState floatRegisters at: 2)) to: #'double *') at: 0) _: ((self cCoerceSimple: (self addressOf: (calloutState floatRegisters at: 4)) to: #'double *') at: 0) _: ((self cCoerceSimple: (self addressOf: (calloutState floatRegisters at: 6)) to: #'double *') at: 0) _: ((self cCoerceSimple: (self addressOf: (calloutState floatRegisters at: 8)) to: #'double *') at: 0) _: ((self cCoerceSimple: (self addressOf: (calloutState floatRegisters at: 10)) to: #'double *') at: 0) _: ((self cCoerceSimple: (self addressOf: (calloutState floatRegisters at: 12)) to: #'double *') at: 0) _: ((self cCoerceSimple: (self addressOf: (calloutState floatRegisters at: 14)) to: #'double *') at: 0) (*) some implementations of alloca do not answer the actual top-of-stack, but may answer an address a word or two away. On these implementations we merely derive the top-of-stack instead of using the result of alloca, or perhaps set the stack pointer to the result of alloca, as befits the platform. Callbacks are not handled by this plugin. Instead they are handled by image-level code to create Alien "thunks", small sequences of machine code, that invoke a function thunkEntry, implemented as required by each platform in one of the files in platforms/Cross/plugins/IA32ABI (a regrettable name; it should be something like platforms/Cross/plugins/AlienCallbacks). Each platform's thunkEntry has a signature that takes all integer register parameters (if any), all floating-point register parameters (if any), a pointer to the thunk, and the stack pointer at the point the thunk was invoiked. To pass a callback to foreign code, the marshaller in this plugin passes the address of the thunk. When external code calls that address the think is invoked, and it invokes thunkEntry as required. thunkEntry then saves its parameters locally in an instance of VMCallbackContext, which includes a jmpbuf. thunkEntry then does a setjmp and enters the VM via sendInvokeCallbackContext[:], which cr eates an activation of invokeCallbackContext: to execute the callback. This method then extracts the address of the thunk from the VMCallbackContext and invokes machinery in Callback to match the address with a Smalltalk block. The block is then evaluated with the parameters extracted from the VMCallbackContext, marshalled by methods in Callback subclasses signatures protocol, which know whether a parameter would be passed in a register or on the stack. The result of the block is then passed back to thunkEntry by storing it in a field in the VMCallbackContext and invoking primSignal:andReturnAs:fromContext:, which uses longjmp to jump back to thunkEntry which then extracts the result from its VMCallbackContext and returns. For example, the signature of thunkEntry on x86/IA32 (platforms/Cross/plugins/IA32ABI/ia32abicc.c) is long thunkEntry(void *thunkp, sqIntptr_t *stackp) whereas on ARMv4/5/6 (platforms/Cross/plugins/IA32ABI/arm32abicc.c) it is long long thunkEntry(long r0, long r1, long r2, long r3, double d0, double d1, double d2, double d3, double d4, double d5, double d6, double d7, void *thunkpPlus16, sqIntptr_t *stackp)! Item was changed: ----- Method: ThreadedFFIPlugin class>>initialize (in category 'class initialization') ----- initialize "c.f. ExternalFunction allInstVarNames old: #('handle' 'flags' 'argTypes') new: #('handle' 'flags' 'argTypes' 'stackSize')" ExternalFunctionAddressIndex := 0. ExternalFunctionFlagsIndex := 1. ExternalFunctionArgTypesIndex := 2. ExternalFunctionStackSizeIndex := 3. "c.f. e.g. CoInterpreter class initializeMiscConstants" MaxNumArgs := 15. + DefaultMaxStackSize := 1024 * 16. + + PluginVersionInfo := CCodeGenerator shortMonticelloDescriptionForClass: self. + PluginVersionInfo := PluginVersionInfo allButFirst: (PluginVersionInfo indexOf: Character space) - 1! - DefaultMaxStackSize := 1024 * 16! Item was changed: ----- Method: ThreadedFFIPlugin>>getModuleName (in category 'initialize') ----- getModuleName "Note: This is hardcoded so it can be run from Squeak. The module name is used for validating a module *after* it is loaded to check if it does really contain the module we're thinking it contains. This is important!!" <returnTypeC: 'const char *'> <export: true> + ^'SqueakFFIPrims', PluginVersionInfo! - ^'SqueakFFIPrims'! |
Free forum by Nabble | Edit this page |