diff --git a/src/BuildIntegration/Microsoft.NETCore.Native.Unix.props b/src/BuildIntegration/Microsoft.NETCore.Native.Unix.props index fadd91986c0..76c280d9045 100644 --- a/src/BuildIntegration/Microsoft.NETCore.Native.Unix.props +++ b/src/BuildIntegration/Microsoft.NETCore.Native.Unix.props @@ -32,7 +32,8 @@ See the LICENSE file in the project root for more information. - + + @@ -58,5 +59,6 @@ See the LICENSE file in the project root for more information. + diff --git a/src/BuildIntegration/Microsoft.NETCore.Native.Windows.props b/src/BuildIntegration/Microsoft.NETCore.Native.Windows.props index 30365d0316e..41b0227a604 100644 --- a/src/BuildIntegration/Microsoft.NETCore.Native.Windows.props +++ b/src/BuildIntegration/Microsoft.NETCore.Native.Windows.props @@ -32,7 +32,8 @@ See the LICENSE file in the project root for more information. - + + @@ -57,6 +58,8 @@ See the LICENSE file in the project root for more information. + + diff --git a/src/BuildIntegration/Microsoft.NETCore.Native.targets b/src/BuildIntegration/Microsoft.NETCore.Native.targets index 538c373a3ae..5489fbbc572 100644 --- a/src/BuildIntegration/Microsoft.NETCore.Native.targets +++ b/src/BuildIntegration/Microsoft.NETCore.Native.targets @@ -39,9 +39,11 @@ See the LICENSE file in the project root for more information. .exe - .dll - .dylib - .so + .dll + .dylib + .so + .lib + .a .html $(NativeIntermediateOutputPath)$(TargetName)$(NativeObjectExt) @@ -119,6 +121,7 @@ See the LICENSE file in the project root for more information. + @@ -162,10 +165,22 @@ See the LICENSE file in the project root for more information. + + + + + + - - - + + + + + + + + + diff --git a/src/ILCompiler.Compiler/src/Compiler/NativeLibraryInitializerRootProvider.cs b/src/ILCompiler.Compiler/src/Compiler/NativeLibraryInitializerRootProvider.cs new file mode 100644 index 00000000000..d7460d45acc --- /dev/null +++ b/src/ILCompiler.Compiler/src/Compiler/NativeLibraryInitializerRootProvider.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +using Internal.TypeSystem.Ecma; +using Internal.TypeSystem; +using Internal.IL.Stubs.StartupCode; + +namespace ILCompiler +{ + /// + /// Provides compilation group for a native library that compiles the initialize method. + /// + public class NativeLibraryInitializerRootProvider : ICompilationRootProvider + { + /// + /// Symbolic name under which the managed initializer is exported. + /// + public const string ManagedEntryPointMethodName = "__managed__Startup"; + + private EcmaModule _module; + private IList _libraryInitializers; + + public NativeLibraryInitializerRootProvider(EcmaModule module, IList libraryInitializers) + { + _module = module; + _libraryInitializers = libraryInitializers; + } + + public void AddCompilationRoots(IRootingServiceProvider rootProvider) + { + TypeDesc owningType = _module.GetGlobalModuleType(); + NativeLibraryStartupMethod nativeLibStartupCode = new NativeLibraryStartupMethod(owningType, _libraryInitializers); + rootProvider.AddCompilationRoot(nativeLibStartupCode, "Startup Code Main Method", ManagedEntryPointMethodName); + } + } +} diff --git a/src/ILCompiler.Compiler/src/IL/Stubs/StartupCode/NativeLibraryStartupMethod.Sorting.cs b/src/ILCompiler.Compiler/src/IL/Stubs/StartupCode/NativeLibraryStartupMethod.Sorting.cs new file mode 100644 index 00000000000..d8bf6ac0363 --- /dev/null +++ b/src/ILCompiler.Compiler/src/IL/Stubs/StartupCode/NativeLibraryStartupMethod.Sorting.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; + +using Internal.TypeSystem; + +using Debug = System.Diagnostics.Debug; + +namespace Internal.IL.Stubs.StartupCode +{ + partial class NativeLibraryStartupMethod + { + protected override int ClassCode => -304225482; + + protected override int CompareToImpl(MethodDesc other, TypeSystemComparer comparer) + { + // Should be a singleton + Debug.Assert(this == other); + return 0; + } + } +} diff --git a/src/ILCompiler.Compiler/src/IL/Stubs/StartupCode/NativeLibraryStartupMethod.cs b/src/ILCompiler.Compiler/src/IL/Stubs/StartupCode/NativeLibraryStartupMethod.cs new file mode 100644 index 00000000000..80bd691a019 --- /dev/null +++ b/src/ILCompiler.Compiler/src/IL/Stubs/StartupCode/NativeLibraryStartupMethod.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; + +using Internal.TypeSystem; + +namespace Internal.IL.Stubs.StartupCode +{ + /// + /// Startup code that does initialization, Main invocation + /// and shutdown of the runtime. + /// + public sealed partial class NativeLibraryStartupMethod : ILStubMethod + { + private TypeDesc _owningType; + private MethodSignature _signature; + private IList _libraryInitializers; + + public NativeLibraryStartupMethod(TypeDesc owningType, IList libraryInitializers) + { + _owningType = owningType; + _libraryInitializers = libraryInitializers; + } + + public override TypeSystemContext Context + { + get + { + return _owningType.Context; + } + } + + public override TypeDesc OwningType + { + get + { + return _owningType; + } + } + + public override string Name + { + get + { + return "NativeLibraryStartup"; + } + } + + public override MethodIL EmitIL() + { + ILEmitter emitter = new ILEmitter(); + ILCodeStream codeStream = emitter.NewCodeStream(); + + // Allow the class library to run explicitly ordered class constructors first thing in start-up. + if (_libraryInitializers != null) + { + foreach (MethodDesc method in _libraryInitializers) + { + codeStream.Emit(ILOpcode.call, emitter.NewToken(method)); + } + } + + codeStream.Emit(ILOpcode.ret); + return emitter.Link(this); + } + + public override MethodSignature Signature + { + get + { + if (_signature == null) + { + _signature = new MethodSignature(MethodSignatureFlags.Static, 0, + Context.GetWellKnownType(WellKnownType.Void), + new TypeDesc[0]); + } + + return _signature; + } + } + + public override bool IsNativeCallable + { + get + { + return true; + } + } + } +} diff --git a/src/ILCompiler.Compiler/src/ILCompiler.Compiler.csproj b/src/ILCompiler.Compiler/src/ILCompiler.Compiler.csproj index 3a1713335ad..73568359fd5 100644 --- a/src/ILCompiler.Compiler/src/ILCompiler.Compiler.csproj +++ b/src/ILCompiler.Compiler/src/ILCompiler.Compiler.csproj @@ -315,6 +315,7 @@ + @@ -344,6 +345,8 @@ + + diff --git a/src/ILCompiler/src/Program.cs b/src/ILCompiler/src/Program.cs index dc2c0f176ca..80a30f5af40 100644 --- a/src/ILCompiler/src/Program.cs +++ b/src/ILCompiler/src/Program.cs @@ -39,6 +39,7 @@ internal class Program private string _ilDump; private string _systemModuleName = "System.Private.CoreLib"; private bool _multiFile; + private bool _nativeLib; private bool _useSharedGenerics; private bool _useScanner; private bool _noScanner; @@ -126,6 +127,7 @@ private ArgumentSyntax ParseCommandLine(string[] args) syntax.DefineOption("g", ref _enableDebugInfo, "Emit debugging information"); syntax.DefineOption("cpp", ref _isCppCodegen, "Compile for C++ code-generation"); syntax.DefineOption("wasm", ref _isWasmCodegen, "Compile for WebAssembly code-generation"); + syntax.DefineOption("nativelib", ref _nativeLib, "Compile as static or shared library"); syntax.DefineOption("dgmllog", ref _dgmlLogFileName, "Save result of dependency analysis as DGML"); syntax.DefineOption("fulllog", ref _generateFullDgmlLog, "Save detailed log of dependency analysis"); syntax.DefineOption("scandgmllog", ref _scanDgmlLogFileName, "Save result of scanner dependency analysis as DGML"); @@ -309,6 +311,12 @@ private int Run(string[] args) compilationRoots.Add(new RawMainMethodRootProvider(entrypointModule)); } } + else if (_nativeLib) + { + EcmaModule module = (EcmaModule)typeSystemContext.SystemModule; + LibraryInitializers libraryInitializers = new LibraryInitializers(typeSystemContext, _isCppCodegen); + compilationRoots.Add(new NativeLibraryInitializerRootProvider(module, libraryInitializers.LibraryInitializerMethods)); + } if (_multiFile) { @@ -330,7 +338,7 @@ private int Run(string[] args) } else { - if (entrypointModule == null) + if (entrypointModule == null && !_nativeLib) throw new Exception("No entrypoint module"); // TODO: Wasm fails to compile some of the xported methods due to missing opcodes diff --git a/src/Native/Bootstrap/CMakeLists.txt b/src/Native/Bootstrap/CMakeLists.txt index b4c98dac68a..bb7b2280d87 100644 --- a/src/Native/Bootstrap/CMakeLists.txt +++ b/src/Native/Bootstrap/CMakeLists.txt @@ -14,3 +14,4 @@ if(NOT CLR_CMAKE_PLATFORM_WASM) endif(NOT CLR_CMAKE_PLATFORM_WASM) add_subdirectory(cpp) +add_subdirectory(dll) diff --git a/src/Native/Bootstrap/dll/CMakeLists.txt b/src/Native/Bootstrap/dll/CMakeLists.txt new file mode 100644 index 00000000000..8357d0afb05 --- /dev/null +++ b/src/Native/Bootstrap/dll/CMakeLists.txt @@ -0,0 +1,16 @@ +project(bootstrapperdll) + +add_definitions(-DCORERT_DLL) + +set(SOURCES + ../main.cpp + ../common.cpp +) + +add_library(bootstrapperdll STATIC ${SOURCES}) + +# Install the static bootstrapperdll library +install (TARGETS bootstrapperdll DESTINATION sdk) +if(WIN32) + install (FILES ${CMAKE_CURRENT_BINARY_DIR}/bootstrapperdll.dir/$/bootstrapperdll.pdb DESTINATION sdk) +endif() diff --git a/src/Native/Bootstrap/main.cpp b/src/Native/Bootstrap/main.cpp index e3c1a2fc9a0..1ba5293d701 100644 --- a/src/Native/Bootstrap/main.cpp +++ b/src/Native/Bootstrap/main.cpp @@ -8,7 +8,7 @@ #include "gcenv.structs.h" #include "gcenv.base.h" -#include +#include #ifndef CPPCODEGEN @@ -262,6 +262,7 @@ extern "C" void __fail_fast() extern "C" bool RhInitialize(); extern "C" void RhpEnableConservativeStackReporting(); extern "C" void RhpShutdown(); +extern "C" void RhSetRuntimeInitializationCallback(int (*fPtr)()); #ifndef CPPCODEGEN @@ -294,13 +295,19 @@ static const pfn c_classlibFunctions[] = { extern "C" void InitializeModules(void* osModule, void ** modules, int count, void ** pClasslibFunctions, int nClasslibFunctions); +#ifndef CORERT_DLL +#define CORERT_ENTRYPOINT __managed__Main #if defined(_WIN32) extern "C" int __managed__Main(int argc, wchar_t* argv[]); -int __cdecl wmain(int argc, wchar_t* argv[]) #else extern "C" int __managed__Main(int argc, char* argv[]); -int main(int argc, char* argv[]) #endif +#else +#define CORERT_ENTRYPOINT __managed__Startup +extern "C" void __managed__Startup(); +#endif // !CORERT_DLL + +static int InitializeRuntime() { if (!RhInitialize()) return -1; @@ -310,7 +317,7 @@ int main(int argc, char* argv[]) #endif // CPPCODEGEN #ifndef CPPCODEGEN - void * osModule = PalGetModuleHandleFromPointer((void*)&__managed__Main); + void * osModule = PalGetModuleHandleFromPointer((void*)&CORERT_ENTRYPOINT); // TODO: pass struct with parameters instead of the large signature of RhRegisterOSModule if (!RhRegisterOSModule( osModule, @@ -328,6 +335,25 @@ int main(int argc, char* argv[]) InitializeModules(nullptr, (void**)RtRHeaderWrapper(), 2, nullptr, 0); #endif // !CPPCODEGEN +#ifdef CORERT_DLL + // Run startup method immediately for a native library + __managed__Startup(); +#endif // CORERT_DLL + + return 0; +} + +#ifndef CORERT_DLL +#if defined(_WIN32) +int __cdecl wmain(int argc, wchar_t* argv[]) +#else +int main(int argc, char* argv[]) +#endif +{ + int initval = InitializeRuntime(); + if (initval != 0) + return initval; + int retval; #ifdef CPPCODEGEN try @@ -348,3 +374,14 @@ int main(int argc, char* argv[]) return retval; } +#endif // !CORERT_DLL + +#ifdef CORERT_DLL +static struct InitializeRuntimePointerHelper +{ + InitializeRuntimePointerHelper() + { + RhSetRuntimeInitializationCallback(&InitializeRuntime); + } +} initializeRuntimePointerHelper; +#endif // CORERT_DLL diff --git a/src/Native/Runtime/thread.cpp b/src/Native/Runtime/thread.cpp index c106963fced..78da88b3bd4 100644 --- a/src/Native/Runtime/thread.cpp +++ b/src/Native/Runtime/thread.cpp @@ -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 (*g_RuntimeInitializationCallback)(); +static Thread* g_RuntimeInitializingThread; + #ifdef _MSC_VER extern "C" void _ReadWriteBarrier(void); #pragma intrinsic(_ReadWriteBarrier) @@ -1115,10 +1118,22 @@ FORCEINLINE bool Thread::InlineTryFastReversePInvoke(ReversePInvokeFrame * pFram return true; } +EXTERN_C void RhSetRuntimeInitializationCallback(int (*fPtr)()) +{ + g_RuntimeInitializationCallback = fPtr; +} + void Thread::ReversePInvokeAttachOrTrapThread(ReversePInvokeFrame * pFrame) { if (!IsStateSet(TSF_Attached)) + { + if (g_RuntimeInitializationCallback != NULL && g_RuntimeInitializingThread != this) + { + EnsureRuntimeInitialized(); + } + ThreadStore::AttachCurrentThread(); + } // If the thread is already in cooperative mode, this is a bad transition. if (IsCurrentThreadInCooperativeMode()) @@ -1149,6 +1164,24 @@ void Thread::ReversePInvokeAttachOrTrapThread(ReversePInvokeFrame * pFrame) } } +void Thread::EnsureRuntimeInitialized() +{ + while (PalInterlockedCompareExchangePointer((void *volatile *)&g_RuntimeInitializingThread, this, NULL) != NULL) + { + PalSleep(1); + } + + if (g_RuntimeInitializationCallback != NULL) + { + if (g_RuntimeInitializationCallback() != 0) + RhFailFast(); + + g_RuntimeInitializationCallback = NULL; + } + + PalInterlockedExchangePointer((void *volatile *)&g_RuntimeInitializingThread, NULL); +} + FORCEINLINE void Thread::InlineReversePInvokeReturn(ReversePInvokeFrame * pFrame) { m_pTransitionFrame = pFrame->m_savedPInvokeTransitionFrame; diff --git a/src/Native/Runtime/thread.h b/src/Native/Runtime/thread.h index 63a89c71529..70fd2533167 100644 --- a/src/Native/Runtime/thread.h +++ b/src/Native/Runtime/thread.h @@ -142,6 +142,7 @@ class Thread : private ThreadBuffer void ResetCachedTransitionFrame(); void CrossThreadUnhijack(); void UnhijackWorker(); + void EnsureRuntimeInitialized(); #ifdef _DEBUG bool DebugIsSuspended(); #endif diff --git a/src/Native/Runtime/unix/PalRedhawkUnix.cpp b/src/Native/Runtime/unix/PalRedhawkUnix.cpp index 0c0c035e1e9..af11b38f0f0 100644 --- a/src/Native/Runtime/unix/PalRedhawkUnix.cpp +++ b/src/Native/Runtime/unix/PalRedhawkUnix.cpp @@ -1205,8 +1205,6 @@ REDHAWK_PALEXPORT bool PalGetMaximumStackBounds(_Out_ void** ppStackLowOut, _Out return true; } -extern "C" int main(int argc, char** argv); - // retrieves the full path to the specified module, if moduleBase is NULL retreieves the full path to the // executable module of the current process. // @@ -1214,13 +1212,6 @@ extern "C" int main(int argc, char** argv); // REDHAWK_PALEXPORT Int32 PalGetModuleFileName(_Out_ const TCHAR** pModuleNameOut, HANDLE moduleBase) { - if (moduleBase == NULL) - { - // Get an address of the "main" function, which causes the dladdr to return - // path of the main executable - moduleBase = (HANDLE)&main; - } - Dl_info dl; if (dladdr(moduleBase, &dl) == 0) {