-
Notifications
You must be signed in to change notification settings - Fork 509
Allow creating static or shared native library #4718
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't looked at the build system and bootstrapper changes in detail yet, but I like this!
|
||
public LibraryRootProvider(EcmaModule module) | ||
{ | ||
_module = module; | ||
} | ||
|
||
public LibraryRootProvider(EcmaModule module, IList<MethodDesc> libraryInitializers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this we should just create a separate ICompilationRootProvider
instead (e.g. a NativeLibraryInitializerRootProvider
that looks similar to the existing MainMethodRootProvider
).
LibraryRootProvider
would go over all the methods and types in the provided module and root them for compilation. What you need here is just a root for the Initialize method (the new root provider will do that) and a root for the NativeCallable
methods (existing ExportedMethodsRootProvider
does that). You'll get natural tree shaking that way (things that are unreachable don't get compiled).
_libraryInitializers = libraryInitializers; | ||
} | ||
|
||
protected override int ClassCode => -304225482; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We put ClassCode
and CompareToImpl
in separate dot files so that once we stand up a JIT (that doesn't need to sort) we can compile the JIT codebase without support for this. Granted, it's unlikely we would use this particular class in the JIT codebase, but we've been doing it everywhere for consistency.
} | ||
} | ||
|
||
// Simply return 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could also just make this return void
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I return 0
to signify a success exit code. That way this line: https://github.com/tonerdo/corert/blob/static-shared-library/src/Native/Bootstrap/main.cpp#L388 will return the necessary value for the main function to continue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this can never return failure exit code, it is not very useful for it to return success exit code...
Also, it would be nice to change this method to take no arguments since they are unused and always null. It should make some of the ifdefs in the boostrapper simpler.
src/ILCompiler/src/Program.cs
Outdated
else if (_nativeLib) | ||
{ | ||
EcmaModule module = null; | ||
// Get first (and only) module |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this limitation necessary? We could go over all of them and create a ExportedMethodsRootProvider
for each.
Also, could you sign the Contribution License Agreement at https://cla2.dotnetfoundation.org? I know you contributed in the past (with CLA signed), but something in the system thinks you didn't sign it yet. |
@MichalStrehovsky I think it's because I changed my GitHub username. Signed it now |
src/Native/Runtime/thread.cpp
Outdated
// Standard calling convention variant of RhpReversePInvoke | ||
COOP_PINVOKE_HELPER(void, RhpReversePInvoke2, (ReversePInvokeFrame * pFrame)) | ||
{ | ||
// Entrypoint for reverse PInvoke | ||
// ensure runtime is initialized | ||
InitializeRuntime(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a hot fast path. The runtime initialization should to be done in RhpReversePInvokeAttachOrTrapThread2
- similar to how the thread initialization is done.
src/Native/Bootstrap/main.cpp
Outdated
|
||
extern "C" int InitializeRuntime() | ||
{ | ||
if (RUNTIME_INITIALIZED) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be thread safe. If one thread is initializing the runtime, the other thread should wait for it. I think the logic to do that should be in the Runtime, not in the bootstrapper.
src/Native/Runtime/thread.cpp
Outdated
@@ -1295,9 +1295,15 @@ EXTERN_C NOINLINE void FASTCALL RhpReversePInvokeAttachOrTrapThread2(ReversePInv | |||
// PInvoke | |||
// | |||
|
|||
extern "C" int InitializeRuntime(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This creates a static dependency from Runtime to bootstrapper. These dependencies are problematic. (E.g. for .NET Native on Windows UWP, we build runtime as separate .dll that does not include bootstrapper.)
Can InitializeRuntime here be a function pointer that the bootstrapper sets - e.g. in a static constructor?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jkotas sounds reasonable. Is there a part in the code where something similar is done? I might be able to get some ideas from there
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not think there is anything like that in CoreRT today. There are number of examples out there on how to achieve it: https://www.bing.com/search?q=__attribute__%20((constructor))
What's your Ubuntu version - what are the errors you are getting? |
@jkotas I'm using Ubuntu 14.04 in a docker container running on macOS. Very little helpful error message, just says that clang exited with a non-zero code |
@jkotas my bad I've now been able to build CoreRT on Ubuntu. However, trying to build a shared library fails (static library works). Will address comments once I fix that |
aa4a40e
to
20b1280
Compare
src/Native/Runtime/thread.cpp
Outdated
// Standard calling convention variant and actual implementation for RhpReversePInvokeAttachOrTrapThread | ||
EXTERN_C NOINLINE void FASTCALL RhpReversePInvokeAttachOrTrapThread2(ReversePInvokeFrame * pFrame) | ||
{ | ||
// Entrypoint for reverse PInvoke | ||
// ensure runtime is initialized | ||
InitializeRuntime(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would push it even lower into Thread::ReversePInvokeAttachOrTrapThread
to the path that is initializing the thread.
src/Native/gc/env/gcenv.base.h
Outdated
@@ -39,6 +39,8 @@ | |||
|
|||
typedef uint32_t BOOL; | |||
typedef uint32_t DWORD; | |||
typedef HANDLE HINSTANCE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be added here.
src/Native/Bootstrap/main.cpp
Outdated
|
||
#ifdef CORERT_DLL | ||
#if defined(_WIN32) | ||
extern "C" BOOL WINAPI DllMain( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoever is linking the static library may want to have custom DllMain too. This will make it impossible.
Have you tried using static constructor for the initialization?
static struct InitializeRuntimePointerHelper
{
InitializeRuntimePointerHelper()
{
...
}
} initializeRuntimePointerHelper;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jkotas won't this mean that there'll still be a static dependency on the bootstrapper (e.g. with .NET Native on Windows UWP)? Would this static construct be run when this library is loaded?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dependencies from runtime to the bootstrapper are the problematic ones. If you add the static constructor to the boostrapper, I do not see any problems with dependencies.
* Include nativelib switch for compiler * suppress checking for entrypoint when -nativelib
* Fixes windows build error
All comments addressed. I'm still looking into the build failure for shared libraries on Ubuntu, although at this point it could be an issue on its own. |
src/Native/Runtime/thread.cpp
Outdated
@@ -1118,7 +1121,15 @@ FORCEINLINE bool Thread::InlineTryFastReversePInvoke(ReversePInvokeFrame * pFram | |||
void Thread::ReversePInvokeAttachOrTrapThread(ReversePInvokeFrame * pFrame) | |||
{ | |||
if (!IsStateSet(TSF_Attached)) | |||
{ | |||
if (*InitializeRuntimePtr != NULL && RuntimeInitializingThread != this) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not thread safe. If two threads happen to be first to call a managed method at the same time, both of them will start initializing the runtime and bad things will happen. There needs to be logic to wait for the initialization to finish that will kick in for the second thread. A spin wait loop is probably the easiest way to do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is problem even without the race actually. The RuntimeInitializingThread != this
condition will be false for the second thread calling here and the initialization will be done second time.
src/Native/Bootstrap/main.cpp
Outdated
} | ||
|
||
#ifdef CORERT_DLL | ||
static struct InitializeRuntimePointerHelper |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this work with optimizations on? I would think there needs to be something to keep the static constructor alive, e.g. linker directive on Windows - pragma(comment(linker,"/include...
mentioned in one of the articles I linked earlier.
src/Native/Runtime/thread.cpp
Outdated
@@ -33,6 +33,9 @@ | |||
EXTERN_C REDHAWK_API void* REDHAWK_CALLCONV RhpHandleAlloc(void* pObject, int type); | |||
EXTERN_C REDHAWK_API void REDHAWK_CALLCONV RhHandleFree(void*); | |||
|
|||
EXTERN_C int (*InitializeRuntimePtr)() = NULL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would look better - more consistent with the overall style of the CoreRT runtime - to keep the pointer static without EXTERN_C
, and expose the setter as a method - like RhSetRuntimeInitializationCallback
- that is called from the bootstrapper.
src/Native/Runtime/thread.cpp
Outdated
@@ -33,6 +33,9 @@ | |||
EXTERN_C REDHAWK_API void* REDHAWK_CALLCONV RhpHandleAlloc(void* pObject, int type); | |||
EXTERN_C REDHAWK_API void REDHAWK_CALLCONV RhHandleFree(void*); | |||
|
|||
EXTERN_C int (*InitializeRuntimePtr)() = NULL; | |||
static Thread* RuntimeInitializingThread; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: g_
prefix maybe nice (I know that we are not 100% consistent).
src/Native/Runtime/thread.cpp
Outdated
if (*InitializeRuntimePtr != NULL && RuntimeInitializingThread != this) | ||
{ | ||
RuntimeInitializingThread = this; | ||
(*InitializeRuntimePtr)(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Set the RuntimeInitializingThread to NULL once you are done?
src/Native/Bootstrap/main.cpp
Outdated
@@ -293,15 +293,58 @@ static const pfn c_classlibFunctions[] = { | |||
#endif // !CPPCODEGEN | |||
|
|||
extern "C" void InitializeModules(void* osModule, void ** modules, int count, void ** pClasslibFunctions, int nClasslibFunctions); | |||
extern "C" int InitializeRuntime(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can move the implementation of InitializeRuntime up to avoid the forward declaration.
src/Native/Bootstrap/main.cpp
Outdated
int retval; | ||
#ifdef CPPCODEGEN | ||
try | ||
#endif | ||
{ | ||
#ifndef CORERT_DLL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is in #ifndef CORERT_DLL
already
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, thanks!
src/Native/Runtime/thread.cpp
Outdated
@@ -33,6 +33,9 @@ | |||
EXTERN_C REDHAWK_API void* REDHAWK_CALLCONV RhpHandleAlloc(void* pObject, int type); | |||
EXTERN_C REDHAWK_API void REDHAWK_CALLCONV RhHandleFree(void*); | |||
|
|||
static int (*InitializeRuntimePtr)(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: g_RuntimeInitializationCallback
A normal build on Windows works just as well as on Nix. How do I turn on optimizations? |
Use Release build of the runtime: "build.cmd Release" |
Is there any special Method called from the DllMain method in the native dll on Windows? Like idk a Main equivalent method in the managed code which is called when DllMain is called? |
There is not, and we do not plan to add one. It is not a good idea to execute managed code during DllMain - it leads to deadlocks with Windows loader lock. |
I can confirm that this works with optimizations on (release build of CoreRT) |
Is generating exports working right now? I built a simple dll file with However there is no export created for that single functions. |
@Fabi You'll want to set the entrypoint name. Otherwise you get a function that is native callable (you can take a function pointer to it and give it to native code and it will work), but doesn't have a symbolic name. Don't forget to also pass a def file, if you're on Windows. |
src/Native/Runtime/thread.cpp
Outdated
void Thread::ReversePInvokeAttachOrTrapThread(ReversePInvokeFrame * pFrame) | ||
{ | ||
if (!IsStateSet(TSF_Attached)) | ||
{ | ||
if (*InitializeRuntimePtr != NULL && g_RuntimeInitializingThread != this) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make this thread-safe, you can use something like this:
...
if (g_RuntimeInitializationCallback != NULL && g_RuntimeInitializingThread != this)
{
EnsureRuntimeInitialized();
}
...
void Thread::EnsureRuntimeInitialized()
{
while (PalInterlockedCompareExchangePointer(&g_RuntimeInitializingThread, this, NULL) != NULL)
{
PalSleep(1);
}
if (g_RuntimeInitializationCallback != NULL)
{
g_RuntimeInitializationCallback();
g_RuntimeInitializationCallback = NULL;
}
PalInterlockedExchangePointer(&g_RuntimeInitializingThread, NULL);
}
src/Native/Bootstrap/main.cpp
Outdated
#ifdef CORERT_DLL | ||
static struct InitializeRuntimePointerHelper | ||
{ | ||
InitializeRuntimePointerHelper() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The indentation is off here. Tabs vs. spaces?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spaces 😄
src/Native/Bootstrap/main.cpp
Outdated
extern "C" void __managed__Startup(); | ||
#endif // !CORERT_DLL | ||
|
||
int InitializeRuntime() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static int InitializeRuntime()
src/Native/Runtime/thread.cpp
Outdated
|
||
if (g_RuntimeInitializationCallback != NULL) | ||
{ | ||
g_RuntimeInitializationCallback(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The callback returns int
, but the return value is not checked. Change the return value of the callback to void
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or keep it as int
, check the return and fail fast it is non-zero.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going with the second option, makes sense to act when runtime initialization fails
src/Native/Bootstrap/main.cpp
Outdated
@@ -310,7 +315,11 @@ int main(int argc, char* argv[]) | |||
#endif // CPPCODEGEN | |||
|
|||
#ifndef CPPCODEGEN | |||
#ifndef CORERT_DLL | |||
void * osModule = PalGetModuleHandleFromPointer((void*)&__managed__Main); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#define CORERT_ENTRYPOINT __managed__Main / #define CORERT_ENTRYPOINT __managed__Startup
in the ifdef above, so that we do not need to have ifdef here?
Windows build is failing with |
This looks good to me, modulo the last set of comments. Thank you for pushing on this! |
@MichalStrehovsky Could you please take a final look at this as well? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, apart from the nits.
We'll also want to do something about the library initializers (we probably don't need to inject System.Private.DeveloperExperience.Console into a non-console app), but that can be fixed in subsequent pull requests.
|
||
using Internal.TypeSystem; | ||
|
||
using AssemblyName = System.Reflection.AssemblyName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: unneeded using
|
||
using Internal.TypeSystem; | ||
|
||
using AssemblyName = System.Reflection.AssemblyName; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: unneeded using
All done!!! |
This PR adds support for creating static libraries (.lib, .a) and dynamic libraries (.dll, .dylib, .so) with CoreRT.
Commands
dotnet build /t:LinkNative /p:NativeLib=Shared /p:DefFile=<path-to-def-exports-file>
dotnet build /t:LinkNative /p:NativeLib=Shared
dotnet build /t:LinkNative /p:NativeLib=Static
dlopen
on Unix andLoadLibrary
on Windows are able to call any .NET method (within the generated native shared library) with theNativeCallable
attribute applied. Generated static library (.lib, .a) would need to also be linked with the other CoreRT static libraries as well as your C++ code for all links to be resolved.At this time there are errors when you try build a shared library on Ubuntu. Shared and static libraries can be successfully built on both Windows and macOS, only static libraries on Ubuntu
cc @jkotas @MichalStrehovsky
#1285