diff --git a/mx.trufflesqueak/mx_trufflesqueak.py b/mx.trufflesqueak/mx_trufflesqueak.py index 3b2f2142c..a81238e24 100644 --- a/mx.trufflesqueak/mx_trufflesqueak.py +++ b/mx.trufflesqueak/mx_trufflesqueak.py @@ -10,11 +10,15 @@ import mx import mx_gate +import mx_unittest + import mx_sdk import mx_sdk_vm import mx_sdk_vm_impl import mx_truffle -import mx_unittest + +# re-export custom mx project classes so they can be used from suite.py +from mx_cmake import CMakeNinjaProject #pylint: disable=unused-import _SUITE = mx.suite('trufflesqueak') _COMPILER = mx.suite('compiler', fatalIfMissing=False) diff --git a/mx.trufflesqueak/suite.py b/mx.trufflesqueak/suite.py index ed9a7b424..8f785a18c 100644 --- a/mx.trufflesqueak/suite.py +++ b/mx.trufflesqueak/suite.py @@ -95,6 +95,30 @@ "sha1": "acd1c78c07e894647e8dcbd72c4fa9a136e20d6d", "licence": "LGPLv21", }, + "OSVM_PLUGINS": { + "baseurl": "https://github.com/hpi-swa/trufflesqueak/releases/download/23.1.0/osvm-plugins-202312181441", + "os_arch": { + "linux": { + "amd64": { + "urls": ["{baseurl}-linux-amd64.zip"], + "digest": "sha512:5e94f289e5e1c71772b3033fda31e637cdcbea17321f2a4448a6755dff6db2db210086cffc993320249bcb6a1df395c17a2a06aedc9636159623336ca92e8008", + }, + "aarch64": { + "urls" : ["{baseurl}-linux-aarch64.zip"], + "digest" : "sha512:b4801b2a442ca383c6d5718c5a085b1446e66010e73587f166ff2726d393ecc47d7a195bba9d586e7f6c40d587e9a89c874a39adb3f65e9633a12703b40268e9", + }, + }, + "windows": { + "amd64": { + "urls": ["{baseurl}-windows-amd64.zip"], + "digest": "sha512:10ec2b4b783bb83a814866ea237a424138802a99ee63b3cfbe2d2b2c6607e94ea000922f58f8a159108f66c0509764bc48b62885337d2a198534337eb2ed6f8e", + }, + }, + "": { + "": {"optional": True} + }, + }, + }, "TRUFFLE-ENTERPRISE": { "digest": "sha512:b883d3ead84778617f9b09edaa43634183f07cdc6ae0666cb2f4edabc52fca913138c4a7a8f9ada1adbd4a9bbe7d16fb4a1b3ceac13446f4e0c47f3d1a20469f", "maven": { @@ -167,24 +191,17 @@ }, "de.hpi.swa.trufflesqueak.ffi.native": { "subDir": "src", - "native": "shared_lib", - "deliverable": "SqueakFFIPrims", + "class": "CMakeNinjaProject", + "vpath": True, + "ninja_targets": ["all"], "os_arch": { - "windows": { - "": { - "cflags": [] - } - }, - "linux": { - "": { - "cflags": ["-g", "-Wall", "-Werror", "-D_GNU_SOURCE"], - "ldlibs": ["-ldl"], - }, - }, "": { "": { - "cflags": ["-g", "-Wall", "-Werror"], - "ldlibs": ["-ldl"], + "cmakeConfig": {}, + "results": [ + "", + "", + ], }, }, }, @@ -308,7 +325,14 @@ "layout": { "LICENSE_TRUFFLESQUEAK.txt": "file:LICENSE", "README_TRUFFLESQUEAK.md": "file:README.md", - "lib/": "dependency:de.hpi.swa.trufflesqueak.ffi.native", + "lib/": [ + "dependency:de.hpi.swa.trufflesqueak.ffi.native/*", + { + "source_type": "extracted-dependency", + "dependency": "OSVM_PLUGINS", + "path": "*", + }, + ], }, "maven": False, }, diff --git a/src/de.hpi.swa.trufflesqueak.ffi.native/CMakeLists.txt b/src/de.hpi.swa.trufflesqueak.ffi.native/CMakeLists.txt new file mode 100644 index 000000000..52fb00c83 --- /dev/null +++ b/src/de.hpi.swa.trufflesqueak.ffi.native/CMakeLists.txt @@ -0,0 +1,29 @@ +# +# Copyright (c) 2023-2024 Software Architecture Group, Hasso Plattner Institute +# Copyright (c) 2023-2024 Oracle and/or its affiliates +# +# Licensed under the MIT License. +# + +cmake_minimum_required(VERSION 3.22) +project(de.hpi.swa.trufflesqueak.ffi.native) + +if(NOT DEFINED SRC_DIR) + set(SRC_DIR "${CMAKE_SOURCE_DIR}") +endif() + +set(CMAKE_C_STANDARD 11) + +# don't install into the system but into the MX project's output dir +set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR}) + +set(CAPI_SRC "${SRC_DIR}/src") + +function(build_library LIB_NAME SOURCE_FILE) + add_library(${LIB_NAME} SHARED ${SOURCE_FILE}) + target_include_directories(${LIB_NAME} PUBLIC include) +endfunction() + + +build_library(InterpreterProxy "${CAPI_SRC}/InterpreterProxy.c") +build_library(SqueakFFIPrims "${CAPI_SRC}/SqueakFFIPrims.c") \ No newline at end of file diff --git a/src/de.hpi.swa.trufflesqueak.ffi.native/include/sqMemoryAccess.h b/src/de.hpi.swa.trufflesqueak.ffi.native/include/sqMemoryAccess.h new file mode 100644 index 000000000..f6f0d5b5b --- /dev/null +++ b/src/de.hpi.swa.trufflesqueak.ffi.native/include/sqMemoryAccess.h @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2023-2024 Software Architecture Group, Hasso Plattner Institute + * Copyright (c) 2023-2024 Oracle and/or its affiliates + * + * Licensed under the MIT License. + */ +#define SIZEOF_VOID_P 8 + +//////////// from here on: copied from +//////////// https://github.com/OpenSmalltalk/opensmalltalk-vm/blob/ec421b99cf41fc5f2f5fb734b536d6233cdde809/platforms/Cross/vm/sqMemoryAccess.h + +#ifndef SIZEOF_LONG +# if LLP64 +# define SIZEOF_LONG 4 +# else +# define SIZEOF_LONG SIZEOF_VOID_P /* default is sizeof(long)==sizeof(void *) */ +# endif +#endif + +#if (SQ_VI_BYTES_PER_WORD == 4) +# define SQ_IMAGE32 1 +# define SQ_IMAGE64 0 +#else +# define SQ_IMAGE64 1 +# define SQ_IMAGE32 0 +#endif + +#if (SQ_IMAGE64 || SPURVM) +# define OBJECTS_64BIT_ALIGNED 1 +# define OBJECTS_32BIT_ALIGNED 0 +#else +# define OBJECTS_32BIT_ALIGNED 1 +# define OBJECTS_64BIT_ALIGNED 0 +#endif + +#if (SIZEOF_VOID_P == 4) +# define SQ_HOST32 1 +#elif (SIZEOF_VOID_P == 8) +# define SQ_HOST64 1 +#else +# error host is neither 32- nor 64-bit? +#endif + +/* sqInt is a signed integer with size adequate for holding an Object Oriented Pointer (or immediate value) + - that is 32bits long on a 32bits image or 64bits long on a 64bits image + we could use C99 int32_t and int64_t once retiring legacy compiler support this time has not yet come + usqInt is the unsigned flavour + SQABS is a macro for taking absolute value of an sqInt */ +#if SQ_IMAGE32 + typedef int sqInt; + typedef unsigned int usqInt; +#define PRIdSQINT "d" +#define PRIuSQINT "u" +#define PRIxSQINT "x" +#define PRIXSQINT "X" +# define SQABS abs +#elif SQ_HOST64 && (SIZEOF_LONG == 8) + typedef long sqInt; + typedef unsigned long usqInt; +#define PRIdSQINT "ld" +#define PRIuSQINT "lu" +#define PRIxSQINT "lx" +#define PRIXSQINT "lX" +# define SQABS labs +#elif (SIZEOF_LONG_LONG != 8) +# error long long integers are not 64-bits wide? +#else + typedef long long sqInt; + typedef unsigned long long usqInt; +#define PRIdSQINT "lld" +#define PRIuSQINT "llu" +#define PRIxSQINT "llx" +#define PRIXSQINT "llX" +# define SQABS llabs +#endif + +/* sqLong is a signed integer with at least 64bits on both 32 and 64 bits images + usqLong is the unsigned flavour + SQLABS is a macro for taking absolute value of a sqLong */ +#if !defined(sqLong) +# if SIZEOF_LONG == 8 +# define sqLong long +# define usqLong unsigned long +# define SQLABS labs +# elif _MSC_VER +# define sqLong __int64 +# define usqLong unsigned __int64 +# define SQLABS llabs +# else +# define sqLong long long +# define usqLong unsigned long long +# define SQLABS llabs +# endif +#endif /* !defined(sqLong) */ + +/* sqIntptr_t is a signed integer with enough bits to hold a pointer + usqIntptr_t is the unsigned flavour + this is essentially C99 intptr_t and uintptr_t but we support legacy compilers + the C99 printf formats macros are also defined with SQ prefix */ +#if SIZEOF_LONG == SIZEOF_VOID_P +typedef long sqIntptr_t; +typedef unsigned long usqIntptr_t; +#define PRIdSQPTR "ld" +#define PRIuSQPTR "lu" +#define PRIxSQPTR "lx" +#define PRIXSQPTR "lX" +#else +typedef long long sqIntptr_t; +typedef unsigned long long usqIntptr_t; +#define PRIdSQPTR "lld" +#define PRIuSQPTR "llu" +#define PRIxSQPTR "llx" +#define PRIXSQPTR "llX" +#endif diff --git a/src/de.hpi.swa.trufflesqueak.ffi.native/include/sqVirtualMachine.h b/src/de.hpi.swa.trufflesqueak.ffi.native/include/sqVirtualMachine.h new file mode 100644 index 000000000..db2e7e43d --- /dev/null +++ b/src/de.hpi.swa.trufflesqueak.ffi.native/include/sqVirtualMachine.h @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2023-2024 Software Architecture Group, Hasso Plattner Institute + * Copyright (c) 2023-2024 Oracle and/or its affiliates + * + * Licensed under the MIT License. + */ +#include "vmCallback.h" + +//////////// from here on: copied from +//////////// https://github.com/OpenSmalltalk/opensmalltalk-vm/blob/ec421b99cf41fc5f2f5fb734b536d6233cdde809/platforms/Cross/vm/sqVirtualMachine.h + +#if SPURVM +# define VM_VERSION "5.0" +#else +# define VM_VERSION "4.5" +#endif + +#ifndef VM_PROXY_MAJOR +/* Increment the following number if you change the order of + functions listed or if you remove functions */ +# define VM_PROXY_MAJOR 1 +#endif + +#ifndef VM_PROXY_MINOR +/* Increment the following number if you add functions at the end */ +# if SPURVM +# define VM_PROXY_MINOR 13 +# else +# define VM_PROXY_MINOR 12 +# endif +#endif + +#include "sqMemoryAccess.h" + +#if VM_PROXY_MINOR > 8 +// Primitive error codes; see interp.h +# define PrimNoErr 0 + +/* VMCallbackContext opaque type avoids all including setjmp.h & vmCallback.h */ +typedef struct _VMCallbackContext *vmccp; +#endif + +typedef sqInt (*CompilerHook)(void); + +typedef struct VirtualMachine { + sqInt (*minorVersion)(void); + sqInt (*majorVersion)(void); + + /* InterpreterProxy methodsFor: 'stack access' */ + + sqInt (*pop)(sqInt nItems); + sqInt (*popthenPush)(sqInt nItems, sqInt oop); + sqInt (*push)(sqInt object); + sqInt (*pushBool)(sqInt trueOrFalse); + sqInt (*pushFloat)(double f); + sqInt (*pushInteger)(sqInt integerValue); + double (*stackFloatValue)(sqInt offset); + sqInt (*stackIntegerValue)(sqInt offset); + sqInt (*stackObjectValue)(sqInt offset); + sqInt (*stackValue)(sqInt offset); + + /* InterpreterProxy methodsFor: 'object access' */ + + sqInt (*argumentCountOf)(sqInt methodPointer); + void *(*arrayValueOf)(sqInt oop); + sqInt (*byteSizeOf)(sqInt oop); + void *(*fetchArrayofObject)(sqInt fieldIndex, sqInt objectPointer); + sqInt (*fetchClassOf)(sqInt oop); + double (*fetchFloatofObject)(sqInt fieldIndex, sqInt objectPointer); + sqInt (*fetchIntegerofObject)(sqInt fieldIndex, sqInt objectPointer); + sqInt (*fetchPointerofObject)(sqInt fieldIndex, sqInt oop); +#if OLD_FOR_REFERENCE +/* sqInt (*fetchWordofObject)(sqInt fieldFieldIndex, sqInt oop); * + * has been rescinded as of VMMaker 3.8 and the 64bitclean VM * + * work. To support old plugins we keep a valid function in * + * the same location in the VM struct but rename it to * + * something utterly horrible to scare off the natives. A new * + * equivalent but 64 bit valid function is added as * + * 'fetchLong32OfObject' */ + sqInt (*obsoleteDontUseThisFetchWordofObject)(sqInt fieldFieldIndex, sqInt oop); +#else /* since there is no legacy plugin problem back to 3.8 we repurpose... */ + void (*error)(const char *); +#endif + void *(*firstFixedField)(sqInt oop); + void *(*firstIndexableField)(sqInt oop); + sqInt (*literalofMethod)(sqInt offset, sqInt methodPointer); + sqInt (*literalCountOf)(sqInt methodPointer); + sqInt (*methodArgumentCount)(void); + sqInt (*methodPrimitiveIndex)(void); + sqInt (*primitiveIndexOf)(sqInt methodPointer); + sqInt (*sizeOfSTArrayFromCPrimitive)(void *cPtr); + sqInt (*slotSizeOf)(sqInt oop); + sqInt (*stObjectat)(sqInt array, sqInt fieldIndex); + sqInt (*stObjectatput)(sqInt array, sqInt fieldIndex, sqInt value); + sqInt (*stSizeOf)(sqInt oop); + sqInt (*storeIntegerofObjectwithValue)(sqInt fieldIndex, sqInt oop, sqInt integer); + sqInt (*storePointerofObjectwithValue)(sqInt fieldIndex, sqInt oop, sqInt valuePointer); + + /* InterpreterProxy methodsFor: 'testing' */ + + sqInt (*isKindOf)(sqInt oop, char *aString); + sqInt (*isMemberOf)(sqInt oop, char *aString); + sqInt (*isBytes)(sqInt oop); + sqInt (*isFloatObject)(sqInt oop); + sqInt (*isIndexable)(sqInt oop); + sqInt (*isIntegerObject)(sqInt oop); + sqInt (*isIntegerValue)(sqInt intValue); + sqInt (*isPointers)(sqInt oop); + sqInt (*isWeak)(sqInt oop); + sqInt (*isWords)(sqInt oop); + sqInt (*isWordsOrBytes)(sqInt oop); + + /* InterpreterProxy methodsFor: 'converting' */ + + sqInt (*booleanValueOf)(sqInt obj); + sqInt (*checkedIntegerValueOf)(sqInt intOop); + sqInt (*floatObjectOf)(double aFloat); + double (*floatValueOf)(sqInt oop); + sqInt (*integerObjectOf)(sqInt value); + sqInt (*integerValueOf)(sqInt oop); + sqInt (*positive32BitIntegerFor)(unsigned int integerValue); + usqInt (*positive32BitValueOf)(sqInt oop); + + /* InterpreterProxy methodsFor: 'special objects' */ + + sqInt (*characterTable)(void); + sqInt (*displayObject)(void); + sqInt (*falseObject)(void); + sqInt (*nilObject)(void); + sqInt (*trueObject)(void); + + /* InterpreterProxy methodsFor: 'special classes' */ + + sqInt (*classArray)(void); + sqInt (*classBitmap)(void); + sqInt (*classByteArray)(void); + sqInt (*classCharacter)(void); + sqInt (*classFloat)(void); + sqInt (*classLargePositiveInteger)(void); + sqInt (*classPoint)(void); + sqInt (*classSemaphore)(void); + sqInt (*classSmallInteger)(void); + sqInt (*classString)(void); + + /* InterpreterProxy methodsFor: 'instance creation' */ + + sqInt (*cloneObject)(sqInt oop); + sqInt (*instantiateClassindexableSize)(sqInt classPointer, sqInt size); + sqInt (*makePointwithxValueyValue)(sqInt xValue, sqInt yValue); + sqInt (*popRemappableOop)(void); + sqInt (*pushRemappableOop)(sqInt oop); + + /* InterpreterProxy methodsFor: 'other' */ + + sqInt (*becomewith)(sqInt array1, sqInt array2); + sqInt (*byteSwapped)(sqInt w); + sqInt (*failed)(void); + sqInt (*fullDisplayUpdate)(void); + void (*fullGC)(void); + void (*incrementalGC)(void); + sqInt (*primitiveFail)(void); + sqInt (*showDisplayBitsLeftTopRightBottom)(sqInt aForm, sqInt l, sqInt t, sqInt r, sqInt b); + sqInt (*signalSemaphoreWithIndex)(sqInt semaIndex); + sqInt (*success)(sqInt aBoolean); + sqInt (*superclassOf)(sqInt classPointer); + +# if VM_PROXY_MINOR > 13 + /* Reuse these now that Cog provides a production JIT. */ + sqInt (*statNumGCs)(void); + sqInt (*stringForCString)(char *nullTerminatedCString); +# else + /* InterpreterProxy methodsFor: 'compiler' */ + + CompilerHook *(*compilerHookVector)(void); + sqInt (*setCompilerInitialized)(sqInt initFlag); +# endif + +#if VM_PROXY_MINOR > 1 + + /* InterpreterProxy methodsFor: 'BitBlt support' */ + + sqInt (*loadBitBltFrom)(sqInt bbOop); + sqInt (*copyBits)(void); + sqInt (*copyBitsFromtoat)(sqInt leftX, sqInt rightX, sqInt yValue); + +#endif + +#if VM_PROXY_MINOR > 2 + + sqInt (*classLargeNegativeInteger)(void); + sqInt (*signed32BitIntegerFor)(sqInt integerValue); + int (*signed32BitValueOf)(sqInt oop); + sqInt (*includesBehaviorThatOf)(sqInt aClass, sqInt aSuperClass); + sqInt (*primitiveMethod)(void); + + /* InterpreterProxy methodsFor: 'FFI support' */ + + sqInt (*classExternalAddress)(void); + sqInt (*classExternalData)(void); + sqInt (*classExternalFunction)(void); + sqInt (*classExternalLibrary)(void); + sqInt (*classExternalStructure)(void); + void *(*ioLoadModuleOfLength)(sqInt modIndex, sqInt modLength); + void *(*ioLoadSymbolOfLengthFromModule)(sqInt fnIndex, sqInt fnLength, void *handle); + sqInt (*isInMemory)(sqInt address); + +#endif + +#if VM_PROXY_MINOR > 3 + + void *(*ioLoadFunctionFrom)(char *fnName, char *modName); + unsigned int (*ioMicroMSecs)(void); + +#endif + +#if VM_PROXY_MINOR > 4 + +# if !defined(sqLong) +# if _MSC_VER +# define sqLong __int64 +# define usqLong unsigned __int64 +# else +# define sqLong long long +# define usqLong unsigned long long +# endif +# endif + + sqInt (*positive64BitIntegerFor)(usqLong integerValue); + usqLong(*positive64BitValueOf)(sqInt oop); + sqInt (*signed64BitIntegerFor)(sqLong integerValue); + sqLong (*signed64BitValueOf)(sqInt oop); + +#endif + +#if VM_PROXY_MINOR > 5 + sqInt (*isArray)(sqInt oop); + void (*forceInterruptCheck)(void); +#endif + +#if VM_PROXY_MINOR > 6 + sqInt (*fetchLong32ofObject)(sqInt fieldFieldIndex, sqInt oop); + sqInt (*getThisSessionID)(void); + sqInt (*ioFilenamefromStringofLengthresolveAliases)(char* aCharBuffer, char* filenameIndex, sqInt filenameLength, sqInt resolveFlag); + sqInt (*vmEndianness)(void); +#endif + +#if VM_PROXY_MINOR > 7 + /* New methods for proxy version 1.8 */ + + /* callbackEnter: Re-enter the interpreter loop for a callback. + Arguments: + callbackID: Pointer to a location receiving the callback ID + used in callbackLeave + Returns: True if successful, false otherwise */ + sqInt (*callbackEnter)(sqInt *callbackID); + +#if OLD_FOR_REFERENCE + /* N.B. callbackLeave is only ever called from the interpreter. Further, it + * and callbackEnter are obsoleted by Alien/FFI callbacks that are simpler + * and faster. + */ + /* callbackLeave: Leave the interpreter from a previous callback + Arguments: + callbackID: The ID of the callback received from callbackEnter() + Returns: True if succcessful, false otherwise. */ + sqInt (*callbackLeave)(sqInt callbackID); +#else + sqInt (*primitiveFailForwithSecondary)(sqInt failCode, sqLong secondaryCode); +#endif + + /* addGCRoot: Add a variable location to the garbage collector. + The contents of the variable location will be updated accordingly. + Arguments: + varLoc: Pointer to the variable location + Returns: True if successful, false otherwise. */ + sqInt (*addGCRoot)(sqInt *varLoc); + + /* removeGCRoot: Remove a variable location from the garbage collector. + Arguments: + varLoc: Pointer to the variable location + Returns: True if successful, false otherwise. + */ + sqInt (*removeGCRoot)(sqInt *varLoc); +#endif + +#if VM_PROXY_MINOR > 8 + /* See interp.h and above for standard error codes. */ + sqInt (*primitiveFailFor)(sqInt code); + void *(*setInterruptCheckChain)(void (*aFunction)(void)); + sqInt (*classAlien)(void); + sqInt (*classUnsafeAlien)(void); +# if OLD_FOR_REFERENCE /* slot repurposed for storeLong32ofObjectwithValue */ + sqInt (*sendInvokeCallbackStackRegistersJmpbuf)(sqInt thunkPtrAsInt, sqInt stackPtrAsInt, sqInt regsPtrAsInt, sqInt jmpBufPtrAsInt); +# else + usqInt (*storeLong32ofObjectwithValue)(sqInt index, sqInt oop, usqInt); +# endif + sqInt (*reestablishContextPriorToCallback)(sqInt callbackContext); + sqInt *(*getStackPointer)(void); + sqInt (*isOopImmutable)(sqInt oop); + sqInt (*isOopMutable)(sqInt oop); +#endif + +#if VM_PROXY_MINOR > 9 +# if VM_PROXY_MINOR > 13 /* OS Errors available in primitives; easy return forms */ + sqInt (*methodReturnBool)(sqInt); + sqInt (*methodReturnFloat)(double); + sqInt (*methodReturnInteger)(sqInt); + sqInt (*methodReturnString)(char *); +# define returnSelf() methodReturnValue(0) +# else + sqInt (*methodArg) (sqInt index); /* These ended up never being used. */ + sqInt (*objectArg) (sqInt index); + sqInt (*integerArg) (sqInt index); + double (*floatArg) (sqInt index); +# endif + sqInt (*methodReturnValue) (sqInt oop); + sqInt (*topRemappableOop) (void); +#endif + +#if VM_PROXY_MINOR > 10 + sqInt (*disownVM)(sqInt flags); + sqInt (*ownVM) (sqInt threadIdAndFlags); + void (*addHighPriorityTickee)(void (*ticker)(void), unsigned periodms); + void (*addSynchronousTickee)(void (*ticker)(void), unsigned periodms, unsigned roundms); + usqLong (*utcMicroseconds)(void); + void (*tenuringIncrementalGC)(void); + sqInt (*isYoung) (sqInt anOop); + sqInt (*isKindOfClass)(sqInt oop, sqInt aClass); + sqInt (*primitiveErrorTable)(void); + sqInt (*primitiveFailureCode)(void); + sqInt (*instanceSizeOf)(sqInt aClass); +#endif + +#if VM_PROXY_MINOR > 11 +/* VMCallbackContext opaque type avoids all including setjmp.h & vmCallback.h */ + sqInt (*sendInvokeCallbackContext)(vmccp); + sqInt (*returnAsThroughCallbackContext)(sqInt, vmccp, sqInt); + sqIntptr_t (*signedMachineIntegerValueOf)(sqInt); + sqIntptr_t (*stackSignedMachineIntegerValue)(sqInt); + usqIntptr_t (*positiveMachineIntegerValueOf)(sqInt); + usqIntptr_t (*stackPositiveMachineIntegerValue)(sqInt); + sqInt (*getInterruptPending)(void); + char *(*cStringOrNullFor)(sqInt); + void *(*startOfAlienData)(sqInt); + usqInt (*sizeOfAlienData)(sqInt); + sqInt (*signalNoResume)(sqInt); +#endif + +#if VM_PROXY_MINOR > 12 /* Spur */ + sqInt (*isImmediate)(sqInt objOop); + sqInt (*characterObjectOf)(int charCode); + sqInt (*characterValueOf)(sqInt objOop); + sqInt (*isCharacterObject)(sqInt objOop); + sqInt (*isCharacterValue)(int charCode); + sqInt (*isPinned)(sqInt objOop); + sqInt (*pinObject)(sqInt objOop); + sqInt (*unpinObject)(sqInt objOop); +#endif + +#if VM_PROXY_MINOR > 13 /* OS Errors available in primitives; easy return forms (see above) */ + sqInt (*primitiveFailForOSError)(sqLong osErrorCode); + sqInt (*methodReturnReceiver)(void); + sqInt (*primitiveFailForFFIExceptionat)(usqLong exceptionCode, usqInt pc); +#endif + +#if VM_PROXY_MINOR > 14 /* SmartSyntaxPlugin validation rewrite support */ + sqInt (*isBooleanObject)(sqInt oop); + sqInt (*isPositiveMachineIntegerObject)(sqInt); +#endif +#if VM_PROXY_MINOR > 15 /* Spur integer and float array classes */ + sqInt (*classDoubleByteArray)(void); + sqInt (*classWordArray)(void); + sqInt (*classDoubleWordArray)(void); + sqInt (*classFloat32Array)(void); + sqInt (*classFloat64Array)(void); +#endif +#if VM_PROXY_MINOR > 16 /* Spur isShorts, isLong64s testing, hash etc */ + sqInt (*isShorts)(sqInt oop); + sqInt (*isLong64s)(sqInt oop); + sqInt (*identityHashOf)(sqInt oop); + sqInt (*isWordsOrShorts)(sqInt oop); /* for SoundPlugin et al */ + sqInt (*bytesPerElement)(sqInt oop); /* for SocketPugin et al */ + sqInt (*fileTimesInUTC)(void); /* for FilePlugin et al */ +#endif +} VirtualMachine; diff --git a/src/de.hpi.swa.trufflesqueak.ffi.native/include/vmCallback.h b/src/de.hpi.swa.trufflesqueak.ffi.native/include/vmCallback.h new file mode 100644 index 000000000..f7bb67f5f --- /dev/null +++ b/src/de.hpi.swa.trufflesqueak.ffi.native/include/vmCallback.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023-2024 Software Architecture Group, Hasso Plattner Institute + * Copyright (c) 2023-2024 Oracle and/or its affiliates + * + * Licensed under the MIT License. + */ +#include +#include "sqMemoryAccess.h" + +//////////// from here on: copied from +//////////// https://github.com/OpenSmalltalk/opensmalltalk-vm/blob/ec421b99cf41fc5f2f5fb734b536d6233cdde809/src/spur64.cog/vmCallback.h + +/* Automatically generated by + CCodeGeneratorGlobalStructure VMMaker.oscog-eem.3150 uuid: 832f44e4-6d22-4545-ae94-d8453b49d54f + */ + +#define VM_CALLBACK_INC 1 + +typedef struct _VMCallbackContext { + void *thunkp; + sqIntptr_t *stackp; + sqIntptr_t *intregargsp; + double *floatregargsp; + void *savedCStackPointer; + void *savedCFramePointer; + union { + sqIntptr_t valword; + struct { int low, high; } valleint64; + struct { int high, low; } valbeint64; + double valflt64; + struct { void *addr; sqIntptr_t size; } valstruct; + }rvs; + void *savedMostRecentCallbackContext; + jmp_buf trampoline; + jmp_buf savedReenterInterpreter; + } VMCallbackContext; + +/* The callback return type codes */ +#define retword 1 +#define retword64 2 +#define retdouble 3 +#define retstruct 4 \ No newline at end of file diff --git a/src/de.hpi.swa.trufflesqueak.ffi.native/src/InterpreterProxy.c b/src/de.hpi.swa.trufflesqueak.ffi.native/src/InterpreterProxy.c new file mode 100644 index 000000000..54a4474a1 --- /dev/null +++ b/src/de.hpi.swa.trufflesqueak.ffi.native/src/InterpreterProxy.c @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2023-2024 Software Architecture Group, Hasso Plattner Institute + * Copyright (c) 2023-2024 Oracle and/or its affiliates + * + * Licensed under the MIT License. + */ +#include + +#define VM_PROXY_MAJOR 1 +#define VM_PROXY_MINOR 17 +#include "sqVirtualMachine.h" + +VirtualMachine* createInterpreterProxy( + // sorted alphabetically, identical to getExecutables in + // src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/InterpreterProxy.java + sqInt (*booleanValueOf)(sqInt oop), + sqInt (*byteSizeOf)(sqInt oop), + sqInt (*classAlien)(void), + sqInt (*classArray)(void), + sqInt (*classBitmap)(void), + sqInt (*classByteArray)(void), + sqInt (*classCharacter)(void), + sqInt (*classDoubleByteArray)(void), + sqInt (*classDoubleWordArray)(void), + sqInt (*classExternalAddress)(void), + sqInt (*classExternalData)(void), + sqInt (*classExternalFunction)(void), + sqInt (*classExternalLibrary)(void), + sqInt (*classExternalStructure)(void), + sqInt (*classFloat)(void), + sqInt (*classFloat32Array)(void), + sqInt (*classFloat64Array)(void), + sqInt (*classLargeNegativeInteger)(void), + sqInt (*classLargePositiveInteger)(void), + sqInt (*classPoint)(void), + sqInt (*classSemaphore)(void), + sqInt (*classSmallInteger)(void), + sqInt (*classString)(void), + sqInt (*classUnsafeAlien)(void), + sqInt (*classWordArray)(void), + sqInt (*failed)(void), + sqInt (*falseObject)(void), + sqInt (*fetchIntegerofObject)(sqInt fieldIndex, sqInt objectPointer), + sqInt (*fetchLong32ofObject)(sqInt fieldIndex, sqInt oop), + sqInt (*fetchPointerofObject)(sqInt index, sqInt oop), + void *(*firstIndexableField)(sqInt oop), + double (*floatValueOf)(sqInt oop), + sqInt (*instantiateClassindexableSize)(sqInt classPointer, sqInt size), + sqInt (*integerObjectOf)(sqInt value), + sqInt (*integerValueOf)(sqInt oop), + void *(*ioLoadFunctionFrom)(char *functionName, char *moduleName), + sqInt (*isArray)(sqInt oop), + sqInt (*isBytes)(sqInt oop), + sqInt (*isIntegerObject)(sqInt oop), + sqInt (*isPointers)(sqInt oop), + sqInt (*isPositiveMachineIntegerObject)(sqInt oop), + sqInt (*isWords)(sqInt oop), + sqInt (*isWordsOrBytes)(sqInt oop), + sqInt (*majorVersion)(void), + sqInt (*methodArgumentCount)(void), + sqInt (*methodReturnBool)(sqInt value), + sqInt (*methodReturnFloat)(double value), + sqInt (*methodReturnInteger)(sqInt value), + sqInt (*methodReturnReceiver)(void), + sqInt (*methodReturnString)(char *value), + sqInt (*methodReturnValue)(sqInt oop), + sqInt (*minorVersion)(void), + sqInt (*nilObject)(void), + sqInt (*pop)(sqInt nItems), + sqInt (*popthenPush)(sqInt nItems, sqInt oop), + sqInt (*positive32BitIntegerFor)(unsigned int integerValue), + usqInt (*positive32BitValueOf)(sqInt oop), + sqInt (*positive64BitIntegerFor)(usqLong integerValue), + usqLong (*positive64BitValueOf)(sqInt oop), + sqInt (*primitiveFail)(void), + sqInt (*primitiveFailFor)(sqInt reasonCode), + sqInt (*push)(sqInt object), + sqInt (*pushInteger)(sqInt integerValue), + sqInt (*showDisplayBitsLeftTopRightBottom)(sqInt aForm, sqInt l, sqInt t, sqInt r, sqInt b), + sqInt (*signed32BitIntegerFor)(sqInt integerValue), + int (*signed32BitValueOf)(sqInt oop), + sqInt (*slotSizeOf)(sqInt oop), + sqInt (*stackIntegerValue)(sqInt offset), + sqInt (*stackObjectValue)(sqInt offset), + sqInt (*stackValue)(sqInt offset), + sqInt (*statNumGCs)(void), + sqInt (*stringForCString)(char* nullTerminatedCString), + sqInt (*storeIntegerofObjectwithValue)(sqInt index, sqInt oop, sqInt integer), + usqInt (*storeLong32ofObjectwithValue)(sqInt fieldIndex, sqInt oop, usqInt anInteger), + sqInt (*trueObject)(void) +) { + VirtualMachine* interpreterProxy = (VirtualMachine*)calloc(1, sizeof(VirtualMachine)); + + interpreterProxy->booleanValueOf = booleanValueOf; + interpreterProxy->byteSizeOf = byteSizeOf; + interpreterProxy->classAlien = classAlien; + interpreterProxy->classArray = classArray; + interpreterProxy->classBitmap = classBitmap; + interpreterProxy->classByteArray = classByteArray; + interpreterProxy->classCharacter = classCharacter; + interpreterProxy->classDoubleByteArray = classDoubleByteArray; + interpreterProxy->classDoubleWordArray = classDoubleWordArray; + interpreterProxy->classExternalAddress = classExternalAddress; + interpreterProxy->classExternalData = classExternalData; + interpreterProxy->classExternalFunction = classExternalFunction; + interpreterProxy->classExternalLibrary = classExternalLibrary; + interpreterProxy->classExternalStructure = classExternalStructure; + interpreterProxy->classFloat = classFloat; + interpreterProxy->classFloat32Array = classFloat32Array; + interpreterProxy->classFloat64Array = classFloat64Array; + interpreterProxy->classLargeNegativeInteger = classLargeNegativeInteger; + interpreterProxy->classLargePositiveInteger = classLargePositiveInteger; + interpreterProxy->classPoint = classPoint; + interpreterProxy->classSemaphore = classSemaphore; + interpreterProxy->classSmallInteger = classSmallInteger; + interpreterProxy->classString = classString; + interpreterProxy->classUnsafeAlien = classUnsafeAlien; + interpreterProxy->classWordArray = classWordArray; + interpreterProxy->failed = failed; + interpreterProxy->falseObject = falseObject; + interpreterProxy->fetchIntegerofObject = fetchIntegerofObject; + interpreterProxy->fetchLong32ofObject = fetchLong32ofObject; + interpreterProxy->fetchPointerofObject = fetchPointerofObject; + interpreterProxy->firstIndexableField = firstIndexableField; + interpreterProxy->floatValueOf = floatValueOf; + interpreterProxy->instantiateClassindexableSize = instantiateClassindexableSize; + interpreterProxy->integerObjectOf = integerObjectOf; + interpreterProxy->integerValueOf = integerValueOf; + interpreterProxy->ioLoadFunctionFrom = ioLoadFunctionFrom; + interpreterProxy->isArray = isArray; + interpreterProxy->isBytes = isBytes; + interpreterProxy->isIntegerObject = isIntegerObject; + interpreterProxy->isPointers = isPointers; + interpreterProxy->isPositiveMachineIntegerObject = isPositiveMachineIntegerObject; + interpreterProxy->isWords = isWords; + interpreterProxy->isWordsOrBytes = isWordsOrBytes; + interpreterProxy->majorVersion = majorVersion; + interpreterProxy->methodArgumentCount = methodArgumentCount; + interpreterProxy->methodReturnBool = methodReturnBool; + interpreterProxy->methodReturnFloat = methodReturnFloat; + interpreterProxy->methodReturnInteger = methodReturnInteger; + interpreterProxy->methodReturnReceiver = methodReturnReceiver; + interpreterProxy->methodReturnString = methodReturnString; + interpreterProxy->methodReturnValue = methodReturnValue; + interpreterProxy->minorVersion = minorVersion; + interpreterProxy->nilObject = nilObject; + interpreterProxy->pop = pop; + interpreterProxy->popthenPush = popthenPush; + interpreterProxy->positive32BitIntegerFor = positive32BitIntegerFor; + interpreterProxy->positive32BitValueOf = positive32BitValueOf; + interpreterProxy->positive64BitIntegerFor = positive64BitIntegerFor; + interpreterProxy->positive64BitValueOf = positive64BitValueOf; + interpreterProxy->primitiveFail = primitiveFail; + interpreterProxy->primitiveFailFor = primitiveFailFor; + interpreterProxy->push = push; + interpreterProxy->pushInteger = pushInteger; + interpreterProxy->showDisplayBitsLeftTopRightBottom = showDisplayBitsLeftTopRightBottom; + interpreterProxy->signed32BitIntegerFor = signed32BitIntegerFor; + interpreterProxy->signed32BitValueOf = signed32BitValueOf; + interpreterProxy->slotSizeOf = slotSizeOf; + interpreterProxy->stackIntegerValue = stackIntegerValue; + interpreterProxy->stackObjectValue = stackObjectValue; + interpreterProxy->stackValue = stackValue; + interpreterProxy->statNumGCs = statNumGCs; + interpreterProxy->stringForCString = stringForCString; + interpreterProxy->storeIntegerofObjectwithValue = storeIntegerofObjectwithValue; + interpreterProxy->storeLong32ofObjectwithValue = storeLong32ofObjectwithValue; + interpreterProxy->trueObject = trueObject; + + return interpreterProxy; +} diff --git a/src/de.hpi.swa.trufflesqueak/.checkstyle_checks.xml b/src/de.hpi.swa.trufflesqueak/.checkstyle_checks.xml index 6e01ed725..5fe8aa177 100644 --- a/src/de.hpi.swa.trufflesqueak/.checkstyle_checks.xml +++ b/src/de.hpi.swa.trufflesqueak/.checkstyle_checks.xml @@ -188,6 +188,7 @@ + diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/image/SqueakImageContext.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/image/SqueakImageContext.java index 04dbd25f6..c49a20b8c 100644 --- a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/image/SqueakImageContext.java +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/image/SqueakImageContext.java @@ -11,6 +11,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; +import java.util.Map; import com.oracle.truffle.api.CompilerAsserts; import com.oracle.truffle.api.CompilerDirectives; @@ -19,6 +20,7 @@ import com.oracle.truffle.api.TruffleFile; import com.oracle.truffle.api.TruffleLanguage.ContextReference; import com.oracle.truffle.api.TruffleLanguage.ParsingRequest; +import com.oracle.truffle.api.frame.MaterializedFrame; import com.oracle.truffle.api.instrumentation.AllocationReporter; import com.oracle.truffle.api.interop.InteropLibrary; import com.oracle.truffle.api.library.Message; @@ -64,6 +66,7 @@ import de.hpi.swa.trufflesqueak.nodes.plugins.BitBlt; import de.hpi.swa.trufflesqueak.nodes.plugins.JPEGReader; import de.hpi.swa.trufflesqueak.nodes.plugins.Zip; +import de.hpi.swa.trufflesqueak.nodes.plugins.ffi.InterpreterProxy; import de.hpi.swa.trufflesqueak.shared.SqueakImageLocator; import de.hpi.swa.trufflesqueak.tools.SqueakMessageInterceptor; import de.hpi.swa.trufflesqueak.util.ArrayUtils; @@ -165,6 +168,8 @@ public final class SqueakImageContext { @CompilationFinal private ClassObject wideStringClass; /* Plugins */ + @CompilationFinal private InterpreterProxy interpreterProxy; + public final Map loadedLibraries = new HashMap<>(); public final B2D b2d = new B2D(this); public final BitBlt bitblt = new BitBlt(this); public String[] dropPluginFileList = new String[0]; @@ -621,6 +626,14 @@ public boolean supportsNFI() { return env.getInternalLanguages().containsKey("nfi"); } + public InterpreterProxy getInterpreterProxy(final MaterializedFrame frame, final int numReceiverAndArguments) { + if (interpreterProxy == null) { + CompilerDirectives.transferToInterpreterAndInvalidate(); + interpreterProxy = new InterpreterProxy(this); + } + return interpreterProxy.instanceFor(frame, numReceiverAndArguments); + } + public PointersObject getScheduler() { if (scheduler == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/accessing/SqueakObjectNewNode.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/accessing/SqueakObjectNewNode.java index 59cb852df..41f464362 100644 --- a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/accessing/SqueakObjectNewNode.java +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/accessing/SqueakObjectNewNode.java @@ -53,6 +53,10 @@ public final AbstractSqueakObjectWithClassAndHash execute(final Node node, final return image.reportAllocation(executeAllocation(node, image, classObject, extraSize)); } + public static final AbstractSqueakObjectWithClassAndHash executeUncached(final SqueakImageContext image, final ClassObject classObject, final int extraSize) { + return SqueakObjectNewNodeGen.getUncached().execute(null, image, classObject, extraSize); + } + protected abstract AbstractSqueakObjectWithClassAndHash executeAllocation(Node node, SqueakImageContext image, ClassObject classObject, int extraSize); @Specialization(guards = "classObject.isZeroSized()") diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/InterpreterProxy.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/InterpreterProxy.java new file mode 100644 index 000000000..dc19c9ae1 --- /dev/null +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/InterpreterProxy.java @@ -0,0 +1,701 @@ +/* + * Copyright (c) 2023-2024 Software Architecture Group, Hasso Plattner Institute + * Copyright (c) 2023-2024 Oracle and/or its affiliates + * + * Licensed under the MIT License. + */ +package de.hpi.swa.trufflesqueak.nodes.plugins.ffi; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.frame.MaterializedFrame; +import com.oracle.truffle.api.interop.ArityException; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnknownIdentifierException; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.interop.UnsupportedTypeException; + +import de.hpi.swa.trufflesqueak.image.SqueakImageContext; +import de.hpi.swa.trufflesqueak.io.SqueakDisplay; +import de.hpi.swa.trufflesqueak.model.AbstractPointersObject; +import de.hpi.swa.trufflesqueak.model.AbstractSqueakObject; +import de.hpi.swa.trufflesqueak.model.ArrayObject; +import de.hpi.swa.trufflesqueak.model.BooleanObject; +import de.hpi.swa.trufflesqueak.model.ClassObject; +import de.hpi.swa.trufflesqueak.model.FloatObject; +import de.hpi.swa.trufflesqueak.model.LargeIntegerObject; +import de.hpi.swa.trufflesqueak.model.NativeObject; +import de.hpi.swa.trufflesqueak.model.NilObject; +import de.hpi.swa.trufflesqueak.model.PointersObject; +import de.hpi.swa.trufflesqueak.nodes.accessing.SqueakObjectAt0Node; +import de.hpi.swa.trufflesqueak.nodes.accessing.SqueakObjectNewNode; +import de.hpi.swa.trufflesqueak.nodes.plugins.ffi.wrappers.NativeObjectStorage; +import de.hpi.swa.trufflesqueak.util.FrameAccess; +import de.hpi.swa.trufflesqueak.util.LogUtils; +import de.hpi.swa.trufflesqueak.util.MiscUtils; +import de.hpi.swa.trufflesqueak.util.NFIUtils; +import de.hpi.swa.trufflesqueak.util.NFIUtils.TruffleClosure; +import de.hpi.swa.trufflesqueak.util.NFIUtils.TruffleExecutable; + +public final class InterpreterProxy { + private final SqueakImageContext context; + private MaterializedFrame frame; + private int numReceiverAndArguments; + private final ArrayList postPrimitiveCleanups = new ArrayList<>(); + // should not be local, as the references are needed to keep the native closures alive + // since this class is a singleton, a private instance variable will suffice + @SuppressWarnings("FieldCanBeLocal") private final TruffleClosure[] closures; + private final Object interpreterProxyPointer; + + /////////////////////////// + // INTERPRETER VARIABLES // + /////////////////////////// + private final ArrayList objectRegistry = new ArrayList<>(); + private long primFailCode; + + /////////////////////// + // INSTANCE CREATION // + /////////////////////// + + public InterpreterProxy(final SqueakImageContext context) { + this.context = context; + try { + final TruffleExecutable[] executables = getExecutables(); + closures = new TruffleClosure[executables.length]; + for (int i = 0; i < executables.length; i++) { + closures[i] = executables[i].createClosure(context); + } + + final String truffleExecutablesSignatures = Arrays.stream(closures).map(obj -> obj.executable.nfiSignature).collect(Collectors.joining(",")); + final Object interpreterProxy = NFIUtils.loadLibrary(context, "InterpreterProxy", + "{ createInterpreterProxy(" + truffleExecutablesSignatures + "):POINTER; }"); + assert interpreterProxy != null : "InterpreterProxy module not found!"; + + final InteropLibrary interpreterProxyLibrary = NFIUtils.getInteropLibrary(interpreterProxy); + interpreterProxyPointer = interpreterProxyLibrary.invokeMember( + interpreterProxy, "createInterpreterProxy", (Object[]) closures); + } catch (UnsupportedMessageException | UnknownIdentifierException | UnsupportedTypeException | ArityException e) { + throw CompilerDirectives.shouldNotReachHere(e); + } + } + + private TruffleExecutable[] getExecutables() { + // sorted alphabetically, identical to createInterpreterProxy in + // src/de.hpi.swa.trufflesqueak.ffi.native/src/InterpreterProxy.c + return new TruffleExecutable[]{ + TruffleExecutable.wrap("(SINT64):SINT64", this::booleanValueOf), + TruffleExecutable.wrap("(SINT64):SINT64", this::byteSizeOf), + TruffleExecutable.wrap("():SINT64", this::classAlien), + TruffleExecutable.wrap("():SINT64", this::classArray), + TruffleExecutable.wrap("():SINT64", this::classBitmap), + TruffleExecutable.wrap("():SINT64", this::classByteArray), + TruffleExecutable.wrap("():SINT64", this::classCharacter), + TruffleExecutable.wrap("():SINT64", this::classDoubleByteArray), + TruffleExecutable.wrap("():SINT64", this::classDoubleWordArray), + TruffleExecutable.wrap("():SINT64", this::classExternalAddress), + TruffleExecutable.wrap("():SINT64", this::classExternalData), + TruffleExecutable.wrap("():SINT64", this::classExternalFunction), + TruffleExecutable.wrap("():SINT64", this::classExternalLibrary), + TruffleExecutable.wrap("():SINT64", this::classExternalStructure), + TruffleExecutable.wrap("():SINT64", this::classFloat), + TruffleExecutable.wrap("():SINT64", this::classFloat32Array), + TruffleExecutable.wrap("():SINT64", this::classFloat64Array), + TruffleExecutable.wrap("():SINT64", this::classLargeNegativeInteger), + TruffleExecutable.wrap("():SINT64", this::classLargePositiveIntegerClass), + TruffleExecutable.wrap("():SINT64", this::classPoint), + TruffleExecutable.wrap("():SINT64", this::classSemaphore), + TruffleExecutable.wrap("():SINT64", this::classSmallInteger), + TruffleExecutable.wrap("():SINT64", this::classString), + TruffleExecutable.wrap("():SINT64", this::classUnsafeAlien), + TruffleExecutable.wrap("():SINT64", this::classWordArray), + TruffleExecutable.wrap("():SINT64", this::failed), + TruffleExecutable.wrap("():SINT64", this::falseObject), + TruffleExecutable.wrap("(SINT64,SINT64):SINT64", this::fetchIntegerofObject), + TruffleExecutable.wrap("(SINT64,SINT64):SINT64", this::fetchLong32ofObject), + TruffleExecutable.wrap("(SINT64,SINT64):SINT64", this::fetchPointerofObject), + TruffleExecutable.wrap("(SINT64):POINTER", this::firstIndexableField), + TruffleExecutable.wrap("(SINT64):DOUBLE", this::floatValueOf), + TruffleExecutable.wrap("(SINT64,SINT64):SINT64", this::instantiateClassindexableSize), + TruffleExecutable.wrap("(SINT64):SINT64", this::integerObjectOf), + TruffleExecutable.wrap("(SINT64):SINT64", this::integerValueOf), + TruffleExecutable.wrap("(STRING,STRING):POINTER", this::ioLoadFunctionFrom), + TruffleExecutable.wrap("(SINT64):SINT64", this::isArray), + TruffleExecutable.wrap("(SINT64):SINT64", this::isBytes), + TruffleExecutable.wrap("(SINT64):SINT64", this::isIntegerObject), + TruffleExecutable.wrap("(SINT64):SINT64", this::isPointers), + TruffleExecutable.wrap("(SINT64):SINT64", this::isPositiveMachineIntegerObject), + TruffleExecutable.wrap("(SINT64):SINT64", this::isWords), + TruffleExecutable.wrap("(SINT64):SINT64", this::isWordsOrBytes), + TruffleExecutable.wrap("():SINT64", this::majorVersion), + TruffleExecutable.wrap("():SINT64", this::methodArgumentCount), + TruffleExecutable.wrap("(SINT64):SINT64", this::methodReturnBool), + TruffleExecutable.wrap("(DOUBLE):SINT64", this::methodReturnFloat), + TruffleExecutable.wrap("(SINT64):SINT64", this::methodReturnInteger), + TruffleExecutable.wrap("():SINT64", this::methodReturnReceiver), + TruffleExecutable.wrap("(STRING):SINT64", this::methodReturnString), + TruffleExecutable.wrap("(SINT64):SINT64", this::methodReturnValue), + TruffleExecutable.wrap("():SINT64", this::minorVersion), + TruffleExecutable.wrap("():SINT64", this::nilObject), + TruffleExecutable.wrap("(SINT64):SINT64", this::pop), + TruffleExecutable.wrap("(SINT64,SINT64):SINT64", this::popthenPush), + TruffleExecutable.wrap("(UINT64):SINT64", this::positive32BitIntegerFor), + TruffleExecutable.wrap("(SINT64):UINT64", this::positive32BitValueOf), + TruffleExecutable.wrap("(SINT64):UINT64", this::positive64BitIntegerFor), + TruffleExecutable.wrap("(SINT64):UINT64", this::positive64BitValueOf), + TruffleExecutable.wrap("():SINT64", this::primitiveFail), + TruffleExecutable.wrap("(SINT64):SINT64", this::primitiveFailFor), + TruffleExecutable.wrap("(SINT64):SINT64", this::push), + TruffleExecutable.wrap("(SINT64):SINT64", this::pushInteger), + TruffleExecutable.wrap("(SINT64,SINT64,SINT64,SINT64,SINT64):SINT64", this::showDisplayBitsLeftTopRightBottom), + TruffleExecutable.wrap("(SINT64):SINT64", this::signed32BitIntegerFor), + TruffleExecutable.wrap("(SINT64):SINT64", this::signed32BitValueOf), + TruffleExecutable.wrap("(SINT64):SINT64", this::slotSizeOf), + TruffleExecutable.wrap("(SINT64):SINT64", this::stackIntegerValue), + TruffleExecutable.wrap("(SINT64):SINT64", this::stackObjectValue), + TruffleExecutable.wrap("(SINT64):SINT64", this::stackValue), + TruffleExecutable.wrap("():SINT64", this::statNumGCs), + TruffleExecutable.wrap("(STRING):UINT64", this::stringForCString), + TruffleExecutable.wrap("(SINT64,SINT64,SINT64):SINT64", this::storeIntegerofObjectwithValue), + TruffleExecutable.wrap("(SINT64,SINT64,UINT64):UINT64", this::storeLong32ofObjectwithValue), + TruffleExecutable.wrap("():SINT64", this::trueObject), + }; + } + + public InterpreterProxy instanceFor(final MaterializedFrame currentFrame, final int currentNumReceiverAndArguments) { + this.frame = currentFrame; + this.numReceiverAndArguments = currentNumReceiverAndArguments; + return this; + } + + /////////////////// + // MISCELLANEOUS // + /////////////////// + + public Object getPointer() { + return interpreterProxyPointer; + } + + public void postPrimitiveCleanups() { + postPrimitiveCleanups.forEach(NativeObjectStorage::cleanup); + postPrimitiveCleanups.clear(); + } + + private boolean hasSucceeded() { + return failed() == 0; + } + + private Object global(final String name) { + return context.lookup(name); + } + + ///////////////////////////// + // OBJECT REGISTRY HELPERS // + ///////////////////////////// + + private Object objectRegistryGet(final long oop) { + return objectRegistry.get((int) oop); + } + + private int addObjectToRegistry(final Object object) { + final int oop = objectRegistry.size(); + objectRegistry.add(object); + return oop; + } + + private int oopFor(final Object object) { + for (int oop = 0; oop < objectRegistry.size(); oop++) { + if (objectRegistry.get(oop) == object) { + return oop; + } + } + return addObjectToRegistry(object); + } + + /////////////////// + // STACK HELPERS // + /////////////////// + + private int getStackPointer() { + return FrameAccess.getStackPointer(frame); + } + + private void setStackPointer(final int stackPointer) { + FrameAccess.setStackPointer(frame, stackPointer); + } + + private void pushObject(final Object object) { + final int stackPointer = getStackPointer(); + setStackPointer(stackPointer + 1); + // push to the original stack pointer, as it always points to the slot where the next object + // is pushed + FrameAccess.setStackSlot(frame, stackPointer, object); + } + + private Object getObjectOnStack(final long reverseStackIndex) { + if (reverseStackIndex < 0) { + primitiveFail(); + return null; + } + // the stack pointer is the index of the object that is pushed onto the stack next, + // so we subtract 1 to get the index of the object that was last pushed onto the stack + final int stackIndex = getStackPointer() - 1 - (int) reverseStackIndex; + if (stackIndex < 0) { + primitiveFail(); + return null; + } + return FrameAccess.getStackValue(frame, stackIndex, FrameAccess.getNumArguments(frame)); + } + + private long methodReturnObject(final Object object) { + assert hasSucceeded(); + pop(numReceiverAndArguments); + pushObject(object); + return 0; + } + + private static long returnBoolean(final boolean bool) { + return bool ? 1L : 0L; + } + + private static long returnVoid() { + // For functions that do not have a defined return value + return 0L; + } + + private static long returnNull() { + // For functions that should return null (=0) + return 0L; + } + + //////////////////////// + // CONVERSION HELPERS // + //////////////////////// + + private long objectToInteger(final Object object) { + if (object instanceof final Long longObject) { + return longObject; + } + LogUtils.PRIMITIVES.severe(() -> "Object to long called with non-Long: " + object); + primitiveFail(); + return returnNull(); + } + + private double objectToFloat(final Object object) { + if (object instanceof final FloatObject floatObject) { + return floatObject.getValue(); + } + LogUtils.PRIMITIVES.severe(() -> "Object to long called with non-FloatObject: " + object); + primitiveFail(); + return returnNull(); + } + + private static Object integerToObject(final long integer) { + return integer; // encoded as Long in TruffleSqueak + } + + private static Object boolToObject(final boolean bool) { + return BooleanObject.wrap(bool); + } + + private static Object boolToObject(final long bool) { + return boolToObject(bool != 0); + } + + private Object floatToObject(final double value) { + return new FloatObject(context, value); + } + + private Object stringToObject(final String string) { + return context.asByteString(string); + } + + /////////////////////// + // ACCESSING HELPERS // + /////////////////////// + + private static Object objectAt0(final Object object, final long index) { + return SqueakObjectAt0Node.executeUncached(object, index); + } + + //////////////////////// + // TYPE CHECK HELPERS // + //////////////////////// + + private long instanceOfCheck(final long oop, final Class klass) { + final Object object = objectRegistryGet(oop); + return returnBoolean(klass.isInstance(object)); + } + + private long nativeObjectCheck(final long oop, final Predicate predicate) { + final Object object = objectRegistryGet(oop); + if (object instanceof final NativeObject nativeObject) { + return returnBoolean(predicate.test(nativeObject)); + } + return returnVoid(); + } + + /////////////////////////////// + // INTERPRETER PROXY METHODS // + /////////////////////////////// + + private long booleanValueOf(final long oop) { + final Object object = objectRegistryGet(oop); + if (object instanceof final Boolean bool) { + return returnBoolean(bool); + } + primitiveFail(); + return returnNull(); + } + + private long byteSizeOf(final long oop) { + if (objectRegistryGet(oop) instanceof final NativeObject nativeObject) { + return NativeObjectStorage.from(nativeObject).byteSizeOf(); + } + // type is not supported (yet) + primitiveFail(); + return 0L; + } + + private long classAlien() { + return oopFor(global("Alien")); + } + + private long classArray() { + return oopFor(context.arrayClass); + } + + private long classBitmap() { + return oopFor(context.bitmapClass); + } + + private long classByteArray() { + return oopFor(context.byteArrayClass); + } + + private long classCharacter() { + return oopFor(context.characterClass); + } + + private long classDoubleByteArray() { + return oopFor(global("DoubleByteArray")); + } + + private long classDoubleWordArray() { + return oopFor(global("DoubleWordArray")); + } + + private long classExternalAddress() { + return oopFor(global("ExternalAddress")); + } + + private long classExternalData() { + return oopFor(global("ExternalData")); + } + + private long classExternalFunction() { + return oopFor(global("ExternalFunction")); + } + + private long classExternalLibrary() { + return oopFor(global("ExternalLibrary")); + } + + private long classExternalStructure() { + return oopFor(global("ExternalStructure")); + } + + private long classFloat() { + return oopFor(context.floatClass); + } + + private long classFloat32Array() { + return oopFor(global("FloatArray")); + } + + private long classFloat64Array() { + return oopFor(global("Float64Array")); + } + + private long classLargeNegativeInteger() { + return oopFor(context.largeNegativeIntegerClass); + } + + private long classLargePositiveIntegerClass() { + return oopFor(context.largePositiveIntegerClass); + } + + private long classPoint() { + return oopFor(context.pointClass); + } + + private long classSemaphore() { + return oopFor(context.semaphoreClass); + } + + private long classSmallInteger() { + return oopFor(context.smallIntegerClass); + } + + private long classString() { + return oopFor(context.byteStringClass); + } + + private long classUnsafeAlien() { + return oopFor(global("UnsafeAlien")); + } + + private long classWordArray() { + return oopFor(global("WordArray")); + } + + public long failed() { + return primFailCode; + } + + private long falseObject() { + return oopFor(BooleanObject.FALSE); + } + + private long fetchIntegerofObject(final long fieldIndex, final long objectPointer) { + return objectToInteger(objectAt0(objectRegistryGet(objectPointer), fieldIndex)); + } + + private long fetchLong32ofObject(final long fieldIndex, final long oop) { + return (int) fetchIntegerofObject(fieldIndex, oop); + } + + private long fetchPointerofObject(final long index, final long oop) { + return oopFor(objectAt0(objectRegistryGet(oop), index)); + } + + private NativeObjectStorage firstIndexableField(final long oop) { + if (objectRegistryGet(oop) instanceof final NativeObject nativeObject) { + final NativeObjectStorage storage = NativeObjectStorage.from(nativeObject); + postPrimitiveCleanups.add(storage); + return storage; + } + return null; + } + + private double floatValueOf(final long oop) { + return objectToFloat(objectRegistryGet(oop)); + } + + private long instantiateClassindexableSize(final long classPointer, final long size) { + final Object object = objectRegistryGet(classPointer); + if (object instanceof final ClassObject classObject) { + final AbstractSqueakObject newObject = SqueakObjectNewNode.executeUncached(context, classObject, (int) size); + return oopFor(newObject); + } + LogUtils.PRIMITIVES.severe(() -> "instantiateClassindexableSize called with non-ClassObject: " + object); + primitiveFail(); + return returnVoid(); + } + + private long integerObjectOf(final long value) { + return oopFor(integerToObject(value)); + } + + private long integerValueOf(final long oop) { + return objectToInteger(objectRegistryGet(oop)); + } + + private NativeObjectStorage ioLoadFunctionFrom(final String functionName, final String moduleName) { + /* TODO */ + LogUtils.PRIMITIVES.severe(() -> "Missing implementation for ioLoadFunctionFrom: %s>>%s".formatted(functionName, moduleName)); + return null; + } + + private long isArray(final long oop) { + return instanceOfCheck(oop, ArrayObject.class); + } + + private long isBytes(final long oop) { + return nativeObjectCheck(oop, NativeObject::isByteType); + } + + private long isIntegerObject(final long oop) { + return returnBoolean(objectRegistryGet(oop) instanceof Long); + } + + private long isPointers(final long oop) { + return instanceOfCheck(oop, AbstractPointersObject.class); + } + + private long isPositiveMachineIntegerObject(final long oop) { + final Object object = objectRegistryGet(oop); + if (object instanceof final Long integer) { + return returnBoolean(integer >= 0L); + } + if (object instanceof final LargeIntegerObject largeInteger) { + return returnBoolean(largeInteger.isZeroOrPositive() && largeInteger.fitsIntoLong()); + } + return returnBoolean(false); + } + + private long isWords(final long oop) { + return nativeObjectCheck(oop, NativeObject::isLongType); + } + + private long isWordsOrBytes(final long oop) { + return nativeObjectCheck(oop, no -> no.isIntType() || no.isByteType()); + } + + private long majorVersion() { + return 1L; + } + + private long methodArgumentCount() { + return numReceiverAndArguments - 1; + } + + private long methodReturnBool(final long bool) { + return methodReturnObject(boolToObject(bool)); + } + + private long methodReturnFloat(final double value) { + return methodReturnObject(floatToObject(value)); + } + + private long methodReturnInteger(final long integer) { + return methodReturnObject(integerToObject(integer)); + } + + private long methodReturnReceiver() { + assert hasSucceeded(); + pop(numReceiverAndArguments - 1); // leave the receiver on the stack + return 0L; + } + + private long methodReturnString(final String string) { + return methodReturnObject(stringToObject(string)); + } + + private long methodReturnValue(final long oop) { + return methodReturnObject(objectRegistryGet(oop)); + } + + private long minorVersion() { + return 17L; + } + + private long nilObject() { + return oopFor(NilObject.SINGLETON); + } + + private long pop(final long nItems) { + setStackPointer(getStackPointer() - (int) nItems); + return returnNull(); + } + + private long popthenPush(final long nItems, final long oop) { + pop(nItems); + push(oop); + return returnVoid(); + } + + private long positive32BitIntegerFor(final long integerValue) { + return integerObjectOf(integerValue & Integer.MAX_VALUE); + } + + private long positive32BitValueOf(final long oop) { + return integerValueOf(oop) & Integer.MAX_VALUE; + } + + private long positive64BitIntegerFor(final long integerValue) { + return integerObjectOf(Math.abs(integerValue)); + } + + private long positive64BitValueOf(final long oop) { + return Math.abs(integerValueOf(oop)); + } + + private long primitiveFail() { + if (primFailCode == 0) { + primitiveFailFor(1L); + } + return 0L; + } + + private long primitiveFailFor(final long reasonCode) { + LogUtils.PRIMITIVES.info(() -> "Primitive failed with code: " + reasonCode); + return primFailCode = reasonCode; + } + + private long push(final long oop) { + pushObject(objectRegistryGet(oop)); + return returnVoid(); + } + + private long pushInteger(final long integer) { + pushObject(integerToObject(integer)); + return returnNull(); + } + + private long showDisplayBitsLeftTopRightBottom(final long aFormOop, final long l, final long t, final long r, final long b) { + final Object aFormObject = objectRegistryGet(aFormOop); + if (aFormObject instanceof final PointersObject aForm) { + final SqueakDisplay display = context.getDisplay(); + if (aForm.isDisplay(context) && !display.getDeferUpdates()) { + display.showDisplayRect((int) l, (int) t, (int) r, (int) b); + } + } + return returnVoid(); + } + + private long signed32BitIntegerFor(final long integerValue) { + return integerObjectOf((int) integerValue); + } + + private long signed32BitValueOf(final long oop) { + return (int) integerValueOf(oop); + } + + private long slotSizeOf(final long oop) { + /* TODO */ + LogUtils.PRIMITIVES.warning(() -> "Missing implementation for slotSizeOf: " + oop); + return returnVoid(); + } + + private long stackIntegerValue(final long offset) { + return objectToInteger(getObjectOnStack(offset)); + } + + private long stackObjectValue(final long offset) { + /* TODO */ + LogUtils.PRIMITIVES.warning(() -> "Missing implementation for stackObjectValue: " + offset); + return returnVoid(); + } + + private long stackValue(final long offset) { + return oopFor(getObjectOnStack(offset)); + } + + private long statNumGCs() { + return MiscUtils.getCollectionCount(); + } + + private long stringForCString(final String string) { + return oopFor(stringToObject(string)); + } + + private long storeIntegerofObjectwithValue(final long index, final long oop, final long integer) { + /* TODO */ + LogUtils.PRIMITIVES.warning(() -> "Missing implementation for storeIntegerofObjectwithValue: %s, %s, %s".formatted(index, oop, integer)); + return returnVoid(); + } + + private long storeLong32ofObjectwithValue(final long fieldIndex, final long oop, final long anInteger) { + /* TODO */ + LogUtils.PRIMITIVES.warning(() -> "Missing implementation for storeLong32ofObjectwithValue: %s, %s, %s".formatted(fieldIndex, oop, anInteger)); + return returnVoid(); + } + + private long trueObject() { + return oopFor(BooleanObject.TRUE); + } +} diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/PrimExternalCallNode.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/PrimExternalCallNode.java new file mode 100644 index 000000000..baa2793e3 --- /dev/null +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/PrimExternalCallNode.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2023-2024 Software Architecture Group, Hasso Plattner Institute + * Copyright (c) 2023-2024 Oracle and/or its affiliates + * + * Licensed under the MIT License. + */ +package de.hpi.swa.trufflesqueak.nodes.plugins.ffi; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.exception.AbstractTruffleException; +import com.oracle.truffle.api.frame.MaterializedFrame; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.interop.ArityException; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.UnknownIdentifierException; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.interop.UnsupportedTypeException; + +import de.hpi.swa.trufflesqueak.exceptions.PrimitiveFailed; +import de.hpi.swa.trufflesqueak.image.SqueakImageContext; +import de.hpi.swa.trufflesqueak.nodes.primitives.AbstractPrimitiveNode; +import de.hpi.swa.trufflesqueak.util.FrameAccess; +import de.hpi.swa.trufflesqueak.util.NFIUtils; + +public final class PrimExternalCallNode extends AbstractPrimitiveNode { + private final Object functionSymbol; + private final InteropLibrary functionInteropLibrary; + private final int numReceiverAndArguments; + + public PrimExternalCallNode(final Object functionSymbol, final InteropLibrary functionInteropLibrary, final int numReceiverAndArguments) { + this.functionSymbol = functionSymbol; + this.functionInteropLibrary = functionInteropLibrary; + this.numReceiverAndArguments = numReceiverAndArguments; + } + + public static PrimExternalCallNode load(final String moduleName, final String functionName, final int numReceiverAndArguments) { + final SqueakImageContext context = SqueakImageContext.getSlow(); + final Object moduleLibrary = lookupModuleLibrary(context, moduleName); + if (moduleLibrary == null) { + return null; // module not found + } + try { + final Object functionSymbol = NFIUtils.loadMember(context, moduleLibrary, functionName, "():SINT64"); + final InteropLibrary functionInteropLibrary = NFIUtils.getInteropLibrary(functionSymbol); + return new PrimExternalCallNode(functionSymbol, functionInteropLibrary, numReceiverAndArguments); + } catch (UnknownIdentifierException e) { + return null; // function not found + } + } + + private static Object lookupModuleLibrary(final SqueakImageContext context, final String moduleName) { + final Object moduleLibrary = context.loadedLibraries.computeIfAbsent(moduleName, (String s) -> { + if (context.loadedLibraries.containsKey(moduleName)) { + // if moduleName was associated with null + return null; + } + final Object library; + try { + library = NFIUtils.loadLibrary(context, moduleName, "{ setInterpreter(POINTER):SINT64; }"); + } catch (AbstractTruffleException e) { + if (e.getMessage().equals("Unknown identifier: setInterpreter")) { + // module has no setInterpreter, cannot be loaded + return null; + } + throw e; + } + if (library == null) { + return null; + } + try { + // TODO: also call shutdownModule():SINT64 at some point + final Object initialiseModuleSymbol = NFIUtils.loadMember(context, library, "initialiseModule", "():SINT64"); + final InteropLibrary initialiseModuleInteropLibrary = NFIUtils.getInteropLibrary(initialiseModuleSymbol); + initialiseModuleInteropLibrary.execute(initialiseModuleSymbol); + } catch (UnknownIdentifierException e) { + // module has no initializer, ignore + } catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) { + throw CompilerDirectives.shouldNotReachHere(e); + } + try { + final InteropLibrary moduleInteropLibrary = NFIUtils.getInteropLibrary(library); + moduleInteropLibrary.invokeMember(library, "setInterpreter", context.getInterpreterProxy(null, 0).getPointer()); + } catch (UnsupportedMessageException | ArityException | UnsupportedTypeException | UnknownIdentifierException e) { + throw CompilerDirectives.shouldNotReachHere(e); + } + return library; + }); + // computeIfAbsent would not put null value + context.loadedLibraries.putIfAbsent(moduleName, moduleLibrary); + return moduleLibrary; + } + + @Override + public Object execute(final VirtualFrame frame) { + return doExternalCall(frame.materialize()); + } + + @Override + public Object executeWithArguments(final VirtualFrame frame, final Object... receiverAndArguments) { + // arguments are handled via manipulation of the stack pointer, see below + return execute(frame); + } + + @TruffleBoundary + private Object doExternalCall(final MaterializedFrame frame) { + InterpreterProxy interpreterProxy = null; + try { + interpreterProxy = getContext().getInterpreterProxy(frame, numReceiverAndArguments); + + // A send (AbstractSendNode.executeVoid) will decrement the stack pointer by + // numReceiverAndArguments + // before transferring control. We need the stack pointer to point at the last argument, + // since the C code expects that. Therefore, we undo the decrement operation here. + FrameAccess.setStackPointer(frame, FrameAccess.getStackPointer(frame) + numReceiverAndArguments); + + // return value is unused, the actual return value is pushed onto the stack (see below) + functionInteropLibrary.execute(functionSymbol); + + // The return value is pushed onto the stack by the plugin via the InterpreterProxy, but + // TruffleSqueak expects the return value to be returned by this function + // (AbstractSendNode.executeVoid). Pop the return value and return it. + final Object returnValue = FrameAccess.getStackValue(frame, FrameAccess.getStackPointer(frame) - 1, FrameAccess.getNumArguments(frame)); + FrameAccess.setStackPointer(frame, FrameAccess.getStackPointer(frame) - 1); + final long failReason = interpreterProxy.failed(); + if (failReason != 0) { + throw PrimitiveFailed.andTransferToInterpreter((int) failReason); + } + return returnValue; + } catch (UnsupportedMessageException | ArityException | UnsupportedTypeException e) { + throw CompilerDirectives.shouldNotReachHere(e); + } finally { + if (interpreterProxy != null) { + interpreterProxy.postPrimitiveCleanups(); + } + } + } +} diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/wrappers/ByteStorage.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/wrappers/ByteStorage.java new file mode 100644 index 000000000..9cad993eb --- /dev/null +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/wrappers/ByteStorage.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023-2024 Software Architecture Group, Hasso Plattner Institute + * Copyright (c) 2023-2024 Oracle and/or its affiliates + * + * Licensed under the MIT License. + */ +package de.hpi.swa.trufflesqueak.nodes.plugins.ffi.wrappers; + +import de.hpi.swa.trufflesqueak.util.UnsafeUtils; + +final class ByteStorage extends NativeObjectStorage { + private final byte[] storage; + + ByteStorage(final byte[] storage) { + this.storage = storage; + } + + @Override + public int byteSizeOf() { + return storage.length * Byte.BYTES; + } + + @Override + protected long allocate() { + return UnsafeUtils.allocateNativeBytes(storage); + } + + @Override + public void cleanup() { + UnsafeUtils.copyNativeBytesBackAndFree(nativeAddress, storage); + } +} diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/wrappers/IntStorage.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/wrappers/IntStorage.java new file mode 100644 index 000000000..569d5be4a --- /dev/null +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/wrappers/IntStorage.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023-2024 Software Architecture Group, Hasso Plattner Institute + * Copyright (c) 2023-2024 Oracle and/or its affiliates + * + * Licensed under the MIT License. + */ +package de.hpi.swa.trufflesqueak.nodes.plugins.ffi.wrappers; + +import de.hpi.swa.trufflesqueak.util.UnsafeUtils; + +final class IntStorage extends NativeObjectStorage { + private final int[] storage; + + IntStorage(final int[] storage) { + this.storage = storage; + } + + @Override + public int byteSizeOf() { + return storage.length * Integer.BYTES; + } + + @Override + protected long allocate() { + return UnsafeUtils.allocateNativeInts(storage); + } + + @Override + public void cleanup() { + UnsafeUtils.copyNativeIntsBackAndFree(nativeAddress, storage); + } +} diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/wrappers/LongStorage.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/wrappers/LongStorage.java new file mode 100644 index 000000000..59bdfb7d0 --- /dev/null +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/wrappers/LongStorage.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023-2024 Software Architecture Group, Hasso Plattner Institute + * Copyright (c) 2023-2024 Oracle and/or its affiliates + * + * Licensed under the MIT License. + */ +package de.hpi.swa.trufflesqueak.nodes.plugins.ffi.wrappers; + +import de.hpi.swa.trufflesqueak.util.UnsafeUtils; + +final class LongStorage extends NativeObjectStorage { + private final long[] storage; + + LongStorage(final long[] storage) { + this.storage = storage; + } + + @Override + public int byteSizeOf() { + return storage.length * Long.BYTES; + } + + @Override + protected long allocate() { + return UnsafeUtils.allocateNativeLongs(storage); + } + + @Override + public void cleanup() { + UnsafeUtils.copyNativeLongsBackAndFree(nativeAddress, storage); + } +} diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/wrappers/NativeObjectStorage.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/wrappers/NativeObjectStorage.java new file mode 100644 index 000000000..8c7a689b2 --- /dev/null +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/wrappers/NativeObjectStorage.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2023-2024 Software Architecture Group, Hasso Plattner Institute + * Copyright (c) 2023-2024 Oracle and/or its affiliates + * + * Licensed under the MIT License. + */ +package de.hpi.swa.trufflesqueak.nodes.plugins.ffi.wrappers; + +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.library.ExportLibrary; +import com.oracle.truffle.api.library.ExportMessage; + +import de.hpi.swa.trufflesqueak.model.NativeObject; + +@ExportLibrary(InteropLibrary.class) +public abstract class NativeObjectStorage implements TruffleObject { + protected long nativeAddress; + private boolean isAllocated; + + public static NativeObjectStorage from(final NativeObject object) { + if (object.isByteType()) { + return new ByteStorage(object.getByteStorage()); + } else if (object.isIntType()) { + return new IntStorage(object.getIntStorage()); + } else if (object.isLongType()) { + return new LongStorage(object.getLongStorage()); + } else if (object.isShortType()) { + return new ShortStorage(object.getShortStorage()); + } else { + throw new IllegalArgumentException("Object storage type is not supported."); + } + } + + @ExportMessage + public boolean isPointer() { + return isAllocated; + } + + @ExportMessage + public long asPointer() { + return nativeAddress; + } + + @ExportMessage + public void toNative() { + if (isAllocated) { + return; + } + nativeAddress = allocate(); + isAllocated = true; + } + + public abstract int byteSizeOf(); + + protected abstract long allocate(); + + public abstract void cleanup(); +} diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/wrappers/ShortStorage.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/wrappers/ShortStorage.java new file mode 100644 index 000000000..fb9523433 --- /dev/null +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/plugins/ffi/wrappers/ShortStorage.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2023-2024 Software Architecture Group, Hasso Plattner Institute + * Copyright (c) 2023-2024 Oracle and/or its affiliates + * + * Licensed under the MIT License. + */ +package de.hpi.swa.trufflesqueak.nodes.plugins.ffi.wrappers; + +import de.hpi.swa.trufflesqueak.util.UnsafeUtils; + +final class ShortStorage extends NativeObjectStorage { + private final short[] storage; + + ShortStorage(final short[] storage) { + this.storage = storage; + } + + @Override + public int byteSizeOf() { + return storage.length * Short.BYTES; + } + + @Override + protected long allocate() { + return UnsafeUtils.allocateNativeShorts(storage); + } + + @Override + public void cleanup() { + UnsafeUtils.copyNativeShortsBackAndFree(nativeAddress, storage); + } +} diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/primitives/PrimitiveNodeFactory.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/primitives/PrimitiveNodeFactory.java index 2eb0c7bf2..4891a0c07 100644 --- a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/primitives/PrimitiveNodeFactory.java +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/nodes/primitives/PrimitiveNodeFactory.java @@ -6,14 +6,7 @@ */ package de.hpi.swa.trufflesqueak.nodes.primitives; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.graalvm.collections.EconomicMap; - import com.oracle.truffle.api.dsl.NodeFactory; - import de.hpi.swa.trufflesqueak.model.ArrayObject; import de.hpi.swa.trufflesqueak.model.CompiledCodeObject; import de.hpi.swa.trufflesqueak.model.NativeObject; @@ -47,6 +40,7 @@ import de.hpi.swa.trufflesqueak.nodes.plugins.UnixOSProcessPlugin; import de.hpi.swa.trufflesqueak.nodes.plugins.Win32OSProcessPlugin; import de.hpi.swa.trufflesqueak.nodes.plugins.ZipPlugin; +import de.hpi.swa.trufflesqueak.nodes.plugins.ffi.PrimExternalCallNode; import de.hpi.swa.trufflesqueak.nodes.plugins.network.SocketPlugin; import de.hpi.swa.trufflesqueak.nodes.primitives.impl.ArithmeticPrimitives; import de.hpi.swa.trufflesqueak.nodes.primitives.impl.ArrayStreamPrimitives; @@ -57,6 +51,11 @@ import de.hpi.swa.trufflesqueak.nodes.primitives.impl.MiscellaneousPrimitives; import de.hpi.swa.trufflesqueak.nodes.primitives.impl.StoragePrimitives; import de.hpi.swa.trufflesqueak.util.OS; +import org.graalvm.collections.EconomicMap; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; public final class PrimitiveNodeFactory { public static final int PRIMITIVE_SIMULATION_GUARD_INDEX = 19; @@ -193,6 +192,12 @@ public static AbstractPrimitiveNode getOrCreateNamed(final CompiledCodeObject me } final String moduleName = values[NAMED_PRIMITIVE_MODULE_NAME_INDEX] instanceof final NativeObject m ? m.asStringUnsafe() : NULL_MODULE_NAME; final String functionName = ((NativeObject) values[NAMED_PRIMITIVE_FUNCTION_NAME_INDEX]).asStringUnsafe(); + + final PrimExternalCallNode externalCallNode = PrimExternalCallNode.load(moduleName, functionName, numReceiverAndArguments); + if (externalCallNode != null) { + return externalCallNode; + } + if (numReceiverAndArguments == 1) { // Check for singleton plugin primitive final AbstractPrimitiveNode primitiveNode = SINGLETON_PLUGIN_MAP.get(moduleName, EconomicMap.emptyMap()).get(functionName); if (primitiveNode != null) { @@ -202,9 +207,8 @@ public static AbstractPrimitiveNode getOrCreateNamed(final CompiledCodeObject me final NodeFactory nodeFactory = PLUGIN_MAP.get(moduleName, EconomicMap.emptyMap()).get(functionName, EconomicMap.emptyMap()).get(numReceiverAndArguments); if (nodeFactory != null) { return createNode(nodeFactory, location, numReceiverAndArguments); - } else { - return null; } + return null; } private static boolean isLoadInstVar(final int primitiveIndex) { diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/util/MiscUtils.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/util/MiscUtils.java index a1428f3b5..9a65e227c 100644 --- a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/util/MiscUtils.java +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/util/MiscUtils.java @@ -71,6 +71,16 @@ public static String format(final String format, final Object... args) { return String.format(format, args); } + @TruffleBoundary + public static long getCollectionCount() { + long totalGCCount = 0; + for (final GarbageCollectorMXBean gcBean : ManagementFactory.getGarbageCollectorMXBeans()) { + final long count = gcBean.getCollectionCount(); + totalGCCount += Math.max(count, 0); + } + return totalGCCount; + } + @TruffleBoundary public static long getCollectionCount(final String[] names) { final GarbageCollectorMXBean mxBean = getGarbageCollectorMXBean(names); diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/util/NFIUtils.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/util/NFIUtils.java new file mode 100644 index 000000000..9ba2e8d8b --- /dev/null +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/util/NFIUtils.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2023-2024 Software Architecture Group, Hasso Plattner Institute + * Copyright (c) 2023-2024 Oracle and/or its affiliates + * + * Licensed under the MIT License. + */ +package de.hpi.swa.trufflesqueak.util; + +import java.io.File; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.TruffleFile; +import com.oracle.truffle.api.interop.ArityException; +import com.oracle.truffle.api.interop.InteropLibrary; +import com.oracle.truffle.api.interop.TruffleObject; +import com.oracle.truffle.api.interop.UnknownIdentifierException; +import com.oracle.truffle.api.interop.UnsupportedMessageException; +import com.oracle.truffle.api.interop.UnsupportedTypeException; +import com.oracle.truffle.api.library.ExportLibrary; +import com.oracle.truffle.api.library.ExportMessage; +import com.oracle.truffle.api.source.Source; + +import de.hpi.swa.trufflesqueak.image.SqueakImageContext; + +public final class NFIUtils { + + @ExportLibrary(InteropLibrary.class) + public static class TruffleExecutable implements TruffleObject { + public final String nfiSignature; + final ITruffleExecutable executable; + + public TruffleExecutable(final String nfiSignature, final ITruffleExecutable executable) { + this.nfiSignature = nfiSignature; + this.executable = executable; + } + + public static TruffleExecutable wrap(final String nfiSignature, final TruffleSupplier supplier) { + return new TruffleExecutable(nfiSignature, supplier); + } + + public static TruffleExecutable wrap(final String nfiSignature, final TruffleFunction function) { + return new TruffleExecutable(nfiSignature, function); + } + + public static TruffleExecutable wrap(final String nfiSignature, final TruffleBiFunction function) { + return new TruffleExecutable(nfiSignature, function); + } + + public static TruffleExecutable wrap(final String nfiSignature, final TruffleTriFunction function) { + return new TruffleExecutable(nfiSignature, function); + } + + public static TruffleExecutable wrap(final String nfiSignature, final TruffleQuadFunction function) { + return new TruffleExecutable(nfiSignature, function); + } + + public static TruffleExecutable wrap(final String nfiSignature, final TruffleQuintFunction function) { + return new TruffleExecutable(nfiSignature, function); + } + + @ExportMessage + boolean isExecutable() { + return true; + } + + @ExportMessage + @TruffleBoundary + Object execute(final Object... arguments) { + return executable.execute(arguments); + } + + public TruffleClosure createClosure(final SqueakImageContext context) throws UnsupportedTypeException { + return new TruffleClosure(context, this); + } + } + + @ExportLibrary(value = InteropLibrary.class, delegateTo = "closure") + public static class TruffleClosure implements TruffleObject { + public final TruffleExecutable executable; + + final Object closure; + + public TruffleClosure(final SqueakImageContext context, final TruffleExecutable executable) + throws UnsupportedTypeException { + this.executable = executable; + this.closure = createClosure(context, executable, executable.nfiSignature); + } + } + + public interface ITruffleExecutable { + Object execute(Object... arguments); + } + + @FunctionalInterface + public interface TruffleSupplier extends ITruffleExecutable { + R run(); + + default Object execute(final Object... arguments) { + assert arguments.length == 0; + return run(); + } + } + + @FunctionalInterface + public interface TruffleFunction extends ITruffleExecutable { + R run(T argument); + + @SuppressWarnings("unchecked") + default Object execute(final Object... arguments) { + assert arguments.length == 1; + return run((T) arguments[0]); + } + } + + @FunctionalInterface + public interface TruffleBiFunction extends ITruffleExecutable { + R run(S argument1, T argument2); + + @SuppressWarnings("unchecked") + default Object execute(final Object... arguments) { + assert arguments.length == 2; + return run((S) arguments[0], (T) arguments[1]); + } + } + + @FunctionalInterface + public interface TruffleTriFunction extends ITruffleExecutable { + R run(S argument1, T argument2, U argument3); + + @SuppressWarnings("unchecked") + default Object execute(final Object... arguments) { + assert arguments.length == 3; + return run((S) arguments[0], (T) arguments[1], (U) arguments[2]); + } + } + + @FunctionalInterface + public interface TruffleQuadFunction extends ITruffleExecutable { + R run(S argument1, T argument2, U argument3, V argument4); + + @SuppressWarnings("unchecked") + default Object execute(final Object... arguments) { + assert arguments.length == 4; + return run((S) arguments[0], (T) arguments[1], (U) arguments[2], (V) arguments[3]); + } + } + + @FunctionalInterface + public interface TruffleQuintFunction extends ITruffleExecutable { + R run(S argument1, T argument2, U argument3, V argument4, W argument5); + + @SuppressWarnings("unchecked") + default Object execute(final Object... arguments) { + assert arguments.length == 5; + return run((S) arguments[0], (T) arguments[1], (U) arguments[2], (V) arguments[3], (W) arguments[4]); + } + } + + public static Object executeNFI(final SqueakImageContext context, final String nfiCode) { + final Source source = Source.newBuilder("nfi", nfiCode, "native").build(); + return context.env.parseInternal(source).call(); + } + + public static Object loadLibrary(final SqueakImageContext context, final String moduleName, final String boundSymbols) { + final String libName = System.mapLibraryName(moduleName); + TruffleFile libPath = context.getHomePath().resolve("lib" + File.separatorChar + libName); + if (!libPath.exists()) { + // to preserve compatibility with plugins from opensmalltalk + // also check without 'lib' prefix + if (!libName.startsWith("lib")) { + return null; + } + libPath = context.getHomePath().resolve("lib" + File.separatorChar + libName.substring(3)); + if (!libPath.exists()) { + return null; + } + } + final String nfiCode = "load \"" + libPath.getAbsoluteFile().getPath() + "\" " + boundSymbols; + return executeNFI(context, nfiCode); + } + + public static Object createSignature(final SqueakImageContext context, final String signature) { + return executeNFI(context, signature); + } + + public static Object invokeSignatureMethod(final SqueakImageContext context, final String signature, final String method, final Object... args) + throws UnsupportedMessageException, UnknownIdentifierException, UnsupportedTypeException, ArityException { + final Object nfiSignature = createSignature(context, signature); + final InteropLibrary signatureInteropLibrary = getInteropLibrary(nfiSignature); + return signatureInteropLibrary.invokeMember(nfiSignature, method, args); + } + + public static Object createClosure(final SqueakImageContext context, final Object executable, final String signature) + throws UnsupportedTypeException { + try { + return invokeSignatureMethod(context, signature, "createClosure", executable); + } catch (UnsupportedMessageException | UnknownIdentifierException | ArityException e) { + throw CompilerDirectives.shouldNotReachHere(e); + } + } + + public static Object loadMember(final SqueakImageContext context, final Object library, final String name, final String signature) + throws UnknownIdentifierException { + final InteropLibrary interopLibrary = getInteropLibrary(library); + try { + final Object symbol = interopLibrary.readMember(library, name); + return invokeSignatureMethod(context, signature, "bind", symbol); + } catch (UnsupportedMessageException | UnsupportedTypeException | ArityException e) { + throw CompilerDirectives.shouldNotReachHere(e); + } + } + + public static InteropLibrary getInteropLibrary(final Object loadedLibrary) { + return InteropLibrary.getFactory().getUncached(loadedLibrary); + } +} diff --git a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/util/UnsafeUtils.java b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/util/UnsafeUtils.java index 84f09d3a1..ee3c0101b 100644 --- a/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/util/UnsafeUtils.java +++ b/src/de.hpi.swa.trufflesqueak/src/de/hpi/swa/trufflesqueak/util/UnsafeUtils.java @@ -64,6 +64,58 @@ public static void copyShorts(final short[] src, final long srcPos, final short[ dest, Unsafe.ARRAY_SHORT_BASE_OFFSET + destPos * Unsafe.ARRAY_SHORT_INDEX_SCALE, Short.BYTES * length); } + public static long allocateNativeBytes(final byte[] src) { + final long numBytes = src.length * Byte.BYTES; + final long address = UNSAFE.allocateMemory(numBytes); + UNSAFE.copyMemory(src, Unsafe.ARRAY_BYTE_BASE_OFFSET, null, address, numBytes); + return address; + } + + public static long allocateNativeShorts(final short[] src) { + final long numBytes = (long) src.length * Short.BYTES; + final long address = UNSAFE.allocateMemory(numBytes); + UNSAFE.copyMemory(src, Unsafe.ARRAY_SHORT_BASE_OFFSET, null, address, numBytes); + return address; + } + + public static long allocateNativeInts(final int[] src) { + final long numBytes = (long) src.length * Integer.BYTES; + final long address = UNSAFE.allocateMemory(numBytes); + UNSAFE.copyMemory(src, Unsafe.ARRAY_INT_BASE_OFFSET, null, address, numBytes); + return address; + } + + public static long allocateNativeLongs(final long[] src) { + final long numBytes = (long) src.length * Long.BYTES; + final long address = UNSAFE.allocateMemory(numBytes); + UNSAFE.copyMemory(src, Unsafe.ARRAY_LONG_BASE_OFFSET, null, address, numBytes); + return address; + } + + public static void copyNativeBytesBackAndFree(final long address, final byte[] dest) { + final long numBytes = dest.length * Byte.BYTES; + UNSAFE.copyMemory(null, address, dest, Unsafe.ARRAY_BYTE_BASE_OFFSET, numBytes); + UNSAFE.freeMemory(address); + } + + public static void copyNativeShortsBackAndFree(final long address, final short[] dest) { + final long numBytes = (long) dest.length * Short.BYTES; + UNSAFE.copyMemory(null, address, dest, Unsafe.ARRAY_SHORT_BASE_OFFSET, numBytes); + UNSAFE.freeMemory(address); + } + + public static void copyNativeIntsBackAndFree(final long address, final int[] dest) { + final long numBytes = (long) dest.length * Integer.BYTES; + UNSAFE.copyMemory(null, address, dest, Unsafe.ARRAY_INT_BASE_OFFSET, numBytes); + UNSAFE.freeMemory(address); + } + + public static void copyNativeLongsBackAndFree(final long address, final long[] dest) { + final long numBytes = (long) dest.length * Long.BYTES; + UNSAFE.copyMemory(null, address, dest, Unsafe.ARRAY_LONG_BASE_OFFSET, numBytes); + UNSAFE.freeMemory(address); + } + public static long fromLongsOffset(final long offset) { return (offset - Unsafe.ARRAY_LONG_BASE_OFFSET) / Unsafe.ARRAY_LONG_INDEX_SCALE; }