-
Notifications
You must be signed in to change notification settings - Fork 510
Automatic generation of method export file for shared libraries #5154
Conversation
|
||
private string GetExportsFileExtenstion() | ||
{ | ||
if (_targetDetails.OperatingSystem == TargetOS.Windows) |
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 file name should be an argument on the command line.
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.
Won't this be an extra cognitive load for the developer? I was thinking that just having everything done automatically might be better, experience wise
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 meant it should be argument on ilc.exe command and passed in by the msbuild script. The msbuild script is computing the name already and passing it to the linker, so it can pass it to ilc.exe as well.
Most developers won't invoke ilc.exe directly, so they won't really notice this argument.
if (!factory.NodeAliases.Values.Contains("__managed__Startup")) | ||
return; | ||
|
||
var nativeCallables = factory.NodeAliases.Where(n => n.Key is IMethodNode) |
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 should get this list in ExportedMethodsRootProvider as it is rooting the native exports instead of trying to reverse engineer it here.
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.
Will an internal static field on ExportedMethodsRootProvider
suffice?
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 should not be static field. It should be instance field.
Maybe the function to produce the exports file can be moved to ExportedMethodsRootProvider - it does not need to be in separate type.
I now also pass |
@@ -70,7 +70,8 @@ See the LICENSE file in the project root for more information. | |||
<LinkerArg Include="-luuid" Condition="'$(TargetOS)' != 'OSX'" /> | |||
<LinkerArg Include="-lrt" Condition="'$(TargetOS)' != 'OSX'" /> | |||
<LinkerArg Include="-licucore" Condition="'$(TargetOS)' == 'OSX'" /> | |||
<LinkerArg Include="-shared" Condition="'$(NativeLib)' == 'Shared'" /> | |||
<LinkerArg Include="-dynamiclib" Condition="'$(TargetOS)' == 'OSX' and '$(NativeLib)' == 'Shared'" /> |
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 though that -shared
works on OSX too. Just for my education - why did you change this to -dynamiclib
?
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.
Oh yeah I know it works. I guess I was just trying to be as platform accurate as possible, most Apple docs use -dynamiclib
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.
Ok, that's fine.
@@ -151,7 +156,8 @@ See the LICENSE file in the project root for more information. | |||
<IlcArg Condition="$(DebugSymbols) == 'true'" Include="-g" /> | |||
<IlcArg Condition="$(IlcGenerateMapFile) == 'true'" Include="--map:$(NativeIntermediateOutputPath)%(ManagedBinary.Filename).map.xml" /> | |||
<IlcArg Condition="$(RdXmlFile) != ''" Include="--rdxml:$(RdXmlFile)" /> | |||
<IlcArg Condition="$(OutputType) == 'Library' and $(NativeLib) != ''" Include="--nativelib" /> | |||
<IlcArg Condition="$(OutputType) == 'Library' and $(NativeLib) != ''" Include="--nativelib" /> | |||
<IlcArg Condition="$(OutputType) == 'Library' and $(NativeLib) == 'Shared'" Include="--exportsfile:$(ExportsFile)" /> |
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 change the condition to $(ExportsFile) != ''
to make it simpler and more flexible
@@ -193,6 +199,8 @@ See the LICENSE file in the project root for more information. | |||
<CustomLinkerArg Include="$(NativeObject)" /> | |||
<CustomLinkerArg Include="-o $(NativeBinary)" Condition="'$(OS)' != 'Windows_NT'" /> | |||
<CustomLinkerArg Include="/OUT:$(NativeBinary)" Condition="'$(OS)' == 'Windows_NT'" /> | |||
<CustomLinkerArg Include="/DEF:$(ExportsFile)" Condition="'$(OS)' == 'Windows_NT' and '$(NativeLib)' == 'Shared'" /> | |||
<CustomLinkerArg Include="-exported_symbols_list $(ExportsFile)" Condition="'$(TargetOS)' != 'Windows_NT' and '$(NativeLib)' == 'Shared'" /> |
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.
Same here
|
||
// Only emit exported methods for user supplied assemblies | ||
// _exportsFile is null when we're not doing a shared library build | ||
if (_module != _module.Context.SystemModule && _exportsFile != 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.
_module != _module.Context.SystemModule
part of the condition should not be required (once the other part of the feedback is addressed).
src/ILCompiler/src/Program.cs
Outdated
@@ -356,7 +358,7 @@ private int Run(string[] args) | |||
// TODO: Wasm fails to compile some of the xported methods due to missing opcodes | |||
if (!_isWasmCodegen) | |||
{ | |||
compilationRoots.Add(new ExportedMethodsRootProvider((EcmaModule)typeSystemContext.SystemModule)); | |||
compilationRoots.Add(new ExportedMethodsRootProvider((EcmaModule)typeSystemContext.SystemModule, _exportsFile)); |
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.
Do not pass in _exportsFile
argument here - pass in null, or make null to be default value of the argument.
// Only emit exported methods for user supplied assemblies | ||
// _exportsFile is null when we're not doing a shared library build | ||
if (_module != _module.Context.SystemModule && _exportsFile != null) | ||
EmitExportedMethods(); |
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.
@MichalStrehovsky What do you think about this call here? Do you think it would be better to make it an explicit call from the compiler driver?
private void EmitExportedMethods() | ||
{ | ||
string moduleName = Path.GetFileNameWithoutExtension(_exportsFile); | ||
StringBuilder stringBuilder = new StringBuilder(); |
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: It is better to write to the stream directly as you go instead of creating the whole string in memory and the writing it at once. (It does not matter a lot here since the file will be relatively small.)
using (var writer = new StreamWriter(fileName))
{
writer.Write(...);
writer.WriteLine(...);
}
if (_module.Context.Target.IsWindows) | ||
{ | ||
stringBuilder.Append("LIBRARY "); | ||
stringBuilder.AppendLine(moduleName.ToUpper()); |
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 ToUpperInvariant
so that it does not depend on the current OS culture.
|
||
stringBuilder.AppendLine("EXPORTS"); | ||
foreach (var method in _methods) | ||
stringBuilder.AppendLine(" " + method.GetNativeCallableExportName()); |
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: You can consider using interpolated strings for all these - it looks better:
AppendLine($" {method.GetNativeCallableExportName()}");
<NativeObject>$(NativeIntermediateOutputPath)$(TargetName)$(NativeObjectExt)</NativeObject> | ||
<NativeBinary>$(NativeOutputPath)$(TargetName)$(NativeBinaryExt)</NativeBinary> | ||
<ExportsFile>$(NativeIntermediateOutputPath)$(TargetName)$(ExportsFiletExt)</ExportsFile> |
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.
Only set this if it is empty and we are producing SharedLib. (It allows users to supply custom exports file.)
{ | ||
if (_module.Context.Target.IsWindows) | ||
{ | ||
streamWriter.Write("LIBRARY "); |
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 the LIBRARY
line actually needed? It seem unnecessary.
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 created it as specified in the DEF file documentation. I'll do a test and get back to you
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.
Number of the .def files in CoreCLR do not have the LIBRARY line. e.g. https://github.com/dotnet/coreclr/blob/master/src/dlls/dbgshim/dbgshim.ntdef
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.
Ah, that's fine then
private void EmitExportedMethods() | ||
{ | ||
string moduleName = Path.GetFileNameWithoutExtension(_exportsFile); | ||
FileStream fileStream = new FileStream(_exportsFile, FileMode.OpenOrCreate); |
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 FileMode.Create
. We want to overwrite the file if it exists already.
src/ILCompiler/src/Program.cs
Outdated
@@ -305,7 +307,7 @@ private int Run(string[] args) | |||
// TODO: Wasm fails to compile some of the exported methods due to missing opcodes | |||
if (!_isWasmCodegen) | |||
{ | |||
compilationRoots.Add(new ExportedMethodsRootProvider(module)); | |||
compilationRoots.Add(new ExportedMethodsRootProvider(module, _exportsFile)); |
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 there are multiple assemblies specified in InputFilePaths
that have NativeCallable
methods in them, they're going to stomp over each other's writes into the exports file.
I would prefer a scheme where ExportedMethodsRootProvider
has a public property that returns an IEnumerable<string>
of all the exported symbols.
Once we're done adding to the compilationRoots
list here, we pass the list of compilation roots to a helper method that does a foreach
over the list of root provider, finds the list of exported methods from each ExportedMethodsRootProvider
, and writes it to a file.
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.
Ah thanks for this, totally overlooked the multiple input file paths scenario. On what type do you suggest the helper method should be?
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.
On what type do you suggest the helper method should be?
I would probably just add a DefFileWriter.cs
or something like that to the ILCompiler project (it probably doesn't even need to be in the ILCompiler.Compiler project). I also wouldn't mind much if it was in Program.cs, but that file is starting to be a dumping ground for lots of short methods and maybe we should stop doing that.
64606e2
to
4f80359
Compare
src/ILCompiler/src/Program.cs
Outdated
@@ -302,7 +304,11 @@ private int Run(string[] args) | |||
entrypointModule = module; | |||
} | |||
|
|||
compilationRoots.Add(new ExportedMethodsRootProvider(module)); | |||
// TODO: Wasm fails to compile some of the exported methods due to missing opcodes | |||
if (!_isWasmCodegen) |
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 coming back looks like a bad merge.
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.
Thanks for this. Been trying to fix the Windows build error
{ | ||
get | ||
{ | ||
return _methods; |
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.
Could you factor this class in a way that we can't get different results depending on when ExportedMethods
is accessed? It's not a problem right now, but I can see someone getting burned by this at some point.
if (nativeCallableExportName != null) | ||
{ | ||
if (ecmaMethod.Module != _module.Context.SystemModule) |
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.
Skipping exports from the system module is a policy decision. This class should ideally be policy-free (some other component might want this list too and a filtered list might not be what they intended). Could we filter in DefFileWriter.cs?
|
||
namespace ILCompiler | ||
{ | ||
public class DefFileWriter |
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 is called ExportsFile everywhere else in this change. This should be called ExportsFileWriter to match.
src/ILCompiler/src/Program.cs
Outdated
DefFileWriter defFileWriter = new DefFileWriter(typeSystemContext, _exportsFile); | ||
foreach (var compilationRoot in compilationRoots) | ||
{ | ||
if (compilationRoot is ExportedMethodsRootProvider) |
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: You can use new C# features to avoid the cast.
if (compilationRoot is ExportedMethodsRootProvider provider)
defFileWriter.AddExportedMethod(provider);
src/ILCompiler/src/Program.cs
Outdated
@@ -446,6 +456,17 @@ private int Run(string[] args) | |||
ObjectDumper dumper = _mapFileName != null ? new ObjectDumper(_mapFileName) : null; | |||
|
|||
CompilationResults compilationResults = compilation.Compile(_outputFilePath, dumper); | |||
if (_nativeLib) |
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 condition should be _exportsFile != null
.
@jkotas @MichalStrehovsky I can't for the life of me figure out why my latest change causes the WASM tests to fail. It's just a refactoring that doesn't change the end result of the |
if (_methods != null) | ||
return _methods; | ||
|
||
_methods = new List<EcmaMethod>(); |
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: It is a better pattern to create the list in a local, and only assign it to the field once it is done. (Slightly better performance and reliability.)
} | ||
} | ||
|
||
foreach (var ecmaMethod in ExportedMethods) |
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 we need to be differentiating between NativeCallable and RuntimeExports here. They can be treated the same.
I do not see anything obvious either. I have kicked a fresh CI run on #5171 to see whether it is a problem that is affecting all PRs. |
The issue only happens with my inclusion of the |
foreach (var method in type.GetMethods()) | ||
{ | ||
EcmaMethod ecmaMethod = (EcmaMethod)method; | ||
var methods = new List<EcmaMethod>(); |
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.
Building the List of methods made sense when it was cached. Without cache, it would be better to yield return
the methods.
Either keep the cache, or replace List with yield return
.
I think the WASM tests failed because there were two different loops for |
Anything else required of me on this? |
@MichalStrehovsky Does this look good to you? |
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 good minus the nit.
@@ -48,8 +49,12 @@ See the LICENSE file in the project root for more information. | |||
<NativeBinaryExt Condition="'$(OutputType)' != 'Exe' and '$(TargetOS)' != 'Windows_NT' and $(NativeLib) == 'Static'">.a</NativeBinaryExt> | |||
<NativeBinaryExt Condition="'$(NativeCodeGen)' == 'wasm'">.html</NativeBinaryExt> | |||
|
|||
<ExportsFiletExt Condition="'$(OutputType)' != 'Exe' and '$(TargetOS)' == 'Windows_NT' and '$(NativeLib)' == 'Shared'">.def</ExportsFiletExt> |
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: typo: Filet -> File
@tonerdo Thank you! |
This PR adds automatic generation of
.def
,.exports
and.map
passed with linker options/DEF
,exported_symbols_list
andversion-script
for building shared libraries on Windows, macOS and Linux respectively.Building shared libraries on all platforms is now a simple:
A good side effect of this change is that on non-Windows platforms it shaves off about 2MB from the produced binary because internally used
NativeCallable
methods are no longer exported by defaultFixes #4986