Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve static library publishing #70277

Closed
Tracked by #79570
am11 opened this issue Jun 6, 2022 · 40 comments
Closed
Tracked by #79570

Improve static library publishing #70277

am11 opened this issue Jun 6, 2022 · 40 comments
Labels
area-NativeAOT-coreclr in-pr There is an active PR which will close this issue when it is merged
Milestone

Comments

@am11
Copy link
Member

am11 commented Jun 6, 2022

Setup:

# On Ubuntu x64, using .NET 7 preview 5

$ dotnet new classlib -n lib1 && cd lib1
$ cat > Class1.cs <<EOF
using System;
using System.Runtime.InteropServices;

namespace lib1;
public class EntrypointWithCallback
{
    [UnmanagedCallersOnly(EntryPoint = "myadd")]
    public unsafe static int Add(int a, int b, delegate* unmanaged<int, int, int, int> callback)
    {
        Console.WriteLine("Hello from C# Add!");
        return callback (a, b, a + b);
    }
}
EOF
$ dotnet publish -c Release --use-current-runtime -o dist  -p:PublishAot=true -p:NativeLib=static

$ cat > test.c <<EOF
#include<stdio.h>

typedef int(*callback_iii)(int, int, int);
extern int myadd(int, int, callback_iii);

int foo(int x, int y, int sum)
{
  printf("Hello from C callback!\n");
  return sum;
}

int main()
{
  printf("sum is: %d\n", myadd(1, 2, &foo));
  return 0;
}
EOF

Compiling test.c with dist/lib1.a is not straightforward. I figured it out using hints from -p:NativeLib=shared -v:diag. This is how the working command looks like:

$ cc test.c dist/lib1.a \
  ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.5.22301.12/sdk/libbootstrapperdll.a \
  ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.5.22301.12/sdk/libRuntime.WorkstationGC.a \
  ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.5.22301.12/framework/libSystem.Native.a \
  ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.5.22301.12/framework/libSystem.Globalization.Native.a \
  ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.5.22301.12/framework/libSystem.IO.Compression.Native.a \
  ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.5.22301.12/framework/libSystem.Net.Security.Native.a \
  ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.5.22301.12/framework/libSystem.Security.Cryptography.Native.OpenSsl.a \
  -Wall -pthread -lstdc++ -ldl -lm -Wl,--require-defined,NativeAOT_StaticInitialization

$ ./a.out
Hello from C# Add!
Hello from C callback!
sum is: 3

I think we should copy all the required .a files into the publish directory and create a helper script (.sh) for the user.

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Jun 6, 2022
@agocke agocke added this to AppModel Jun 14, 2022
@MichalStrehovsky MichalStrehovsky added this to the 8.0.0 milestone Jun 16, 2022
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Jun 16, 2022
@MichalStrehovsky
Copy link
Member

Yeah, the experience for building static libraries could be better. It's not fully fleshed out. Maybe we could also just generate a lib that merges all the other libs in it.

We're not going to document/support this option for .NET 7. I'm moving this to .NET 8.

@am11
Copy link
Member Author

am11 commented Jun 16, 2022

I have found an interesting approach for composite archives on SO https://stackoverflow.com/a/68407916/863980, which resorts to partial linking. However, it suffers from size regression. It is very likely that I have missing some option which could avoid this regression..

Consider a solution with two C# library projects having <PublishAot>true and <NativeLib>static, that -- upon publish -- produces dist/one.a and dist/two.a.

Compiling all archives at once like this (note: we need --allow-redefinition for this case too):

$ gcc -O2 glue.c dist/one.a dist/two.a \
  ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.6.22312.1/sdk/libbootstrapperdll.a   ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.6.22312.1/sdk/libRuntime.WorkstationGC.a   ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.6.22312.1/framework/libSystem.Native.a   ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.6.22312.1/framework/libSystem.Globalization.Native.a   ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.6.22312.1/framework/libSystem.IO.Compression.Native.a   ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.6.22312.1/framework/libSystem.Net.Security.Native.a   ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.6.22312.1/framework/libSystem.Security.Cryptography.Native.OpenSsl.a  \
  -Wall -pthread -lstdc++ -ldl -lm \
  -Wl,--require-defined,NativeAOT_StaticInitialization -Wl,--allow-multiple-definition

gives us an 18 MB binary (a.out).
With partial linking (where we definitely need --allow-redefinition):

$ gcc -r -o nativeaotruntime.o -Wl,--whole-archive \
  ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.6.22312.1/sdk/libbootstrapperdll.a \
  ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.6.22312.1/sdk/libRuntime.WorkstationGC.a \
  ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.6.22312.1/framework/libSystem.Native.a \
  ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.6.22312.1/framework/libSystem.Globalization.Native.a \
  ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.6.22312.1/framework/libSystem.IO.Compression.Native.a \
  ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.6.22312.1/framework/libSystem.Net.Security.Native.a \
  ~/.nuget/packages/runtime.linux-x64.microsoft.dotnet.ilcompiler/7.0.0-preview.6.22312.1/framework/libSystem.Security.Cryptography.Native.OpenSsl.a

$ ar cr libnativeaotruntime.a nativeaotruntime.o

$ gcc -O2 glue.c dist/one.a dist/two.a libnativeaotruntime.a  \
    -Wall -pthread -lstdc++ -ldl -lz -lm \
    -Wl,--allow-multiple-definition

this gives us 22 mb.
(for some reason, partial linking didn't require NativeAOT_StaticInitialization "required" definition 🤔)

@MichalStrehovsky
Copy link
Member

Consider a solution with two C# library projects having <PublishAot>true and <NativeLib>static, that -- upon publish -- produces dist/one.a and dist/two.a.

That looks like an antipattern - we really only want one .a file produced by NativeAOT in the app. Otherwise one gets duplicate framework, duplicate System.Object, etc.

It works, as long as one makes sure the .a is produced by the exact same version of the ILCompiler package, but if they're not matched, there will be horrible failure modes. Kind of like trying to statically link multiple versions of libc.

@am11
Copy link
Member Author

am11 commented Jun 17, 2022

Turned out the 4 MB size regression is not related to number of archive files, but rather partial linking. Linking a single .a with the combined libnativeaotruntime.a produces 24 MB binary while linking all archives manually is 20 MB.

@xylobol
Copy link

xylobol commented Dec 20, 2022

Maybe we could also just generate a lib that merges all the other libs in it.

Why not make it a flag?

@am11
Copy link
Member Author

am11 commented Dec 20, 2022

One way is to combine all the static libs / archives (minus libstdc++-compat.a which is swappable with system libstdc++ in .NET 8): libtool -static -o sfx_combined.a framework/*.a sdk/*.a or a thin variant ar -rcT sfx_combined.a framework/*.a sdk/*.a (which apparently has a downside) as a single shared framework static lib and add it to the ILCompiler package. Then the instructions for end-user would be simplified:

link your code with ~/.nuget/packages/runtime.<RID>.microsoft.dotnet.ilcompiler/<Version>/sfx_combined.a

Considering we can overcome that 4 MB size regression (#70277 (comment)), we won't be needing most of the individual static libs in the package (framework/*.a and sdk/*.a).

@DrewRidley
Copy link

DrewRidley commented Jun 3, 2023

Sort of related, but I am having issues linking with some of the stuff from the localization library. I added "libSystem.Globalization.Native.a" to my list of linked dependencies, and it resolved 99% of the errors I was getting, but theres still a few lingering ones and I was wondering if this is due to incomplete support for NativeAOT?

 = note: Undefined symbols for architecture arm64:
            "_NSLocaleCalendar", referenced from:
                _GlobalizationNative_GetLocaleInfoIntNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
            "_NSLocaleIdentifier", referenced from:
                _GlobalizationNative_GetLocaleInfoStringNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
            "_NSLocaleLanguageCode", referenced from:
                _GlobalizationNative_GetLocaleInfoStringNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                _GlobalizationNative_GetLocaleInfoIntNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
            "_NSLocaleMeasurementSystem", referenced from:
                _GlobalizationNative_GetLocaleInfoIntNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
            "_OBJC_CLASS_$_NSDateFormatter", referenced from:
                objc-class-ref in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
            "_OBJC_CLASS_$_NSLocale", referenced from:
                objc-class-ref in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
            "_OBJC_CLASS_$_NSNumberFormatter", referenced from:
                objc-class-ref in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
            "_OBJC_CLASS_$_NSString", referenced from:
                objc-class-ref in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
            "___CFConstantStringClassReference", referenced from:
                CFString in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                CFString in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                CFString in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                CFString in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                CFString in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
            "_objc_alloc", referenced from:
                _GlobalizationNative_GetLocaleNameNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                _GlobalizationNative_GetLocaleInfoStringNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                _GlobalizationNative_GetLocaleInfoIntNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                _GlobalizationNative_GetLocaleInfoPrimaryGroupingSizeNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                _GlobalizationNative_GetLocaleInfoSecondaryGroupingSizeNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                _GlobalizationNative_GetLocaleTimeFormatNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
            "_objc_alloc_init", referenced from:
                _GlobalizationNative_GetLocaleInfoStringNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                _GlobalizationNative_GetLocaleInfoIntNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                _GlobalizationNative_GetLocaleInfoPrimaryGroupingSizeNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                _GlobalizationNative_GetLocaleInfoSecondaryGroupingSizeNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                _GlobalizationNative_GetLocaleTimeFormatNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
            "_objc_msgSend", referenced from:
                _DetectDefaultAppleLocaleName in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                _GlobalizationNative_GetLocaleNameNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                _GlobalizationNative_GetLocaleInfoStringNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                _GlobalizationNative_GetLocaleInfoIntNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                _GlobalizationNative_GetLocaleInfoPrimaryGroupingSizeNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                _GlobalizationNative_GetLocaleInfoSecondaryGroupingSizeNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                _GlobalizationNative_GetLocaleTimeFormatNative in libbepuvy_sys-6c63fa1fde51d6fb.rlib(pal_locale.m.o)
                ...
          ld: symbol(s) not found for architecture arm64
          clang: error: linker command failed with exit code 1 (use -v to see invocation)
          

From my preliminary research, it seems all of these symbols are specific to MacOS? If that is the case, I am more inclined to believe its an architecture support issue.

Any help is much appreciated.
Thanks!

UPDATE: I think I figured it out (didn't pass the framework argument). Ill leave this here just in case. Thank you again for your hard work making NativeAOT possible everyone!

@am11
Copy link
Member Author

am11 commented Jun 4, 2023

Things have changed a bit in .NET 8. Here are the new command for daily build (net8.0 preview 6, osx-arm64):

Invariant globalization

# using code from top post

AOTBASE=~/.nuget/packages/runtime.osx-arm64.microsoft.dotnet.ilcompiler/8.0.0-preview.6.23279.6

dotnet publish -c Release --use-current-runtime -o dist \
  -p:PublishAot=true \
  -p:NativeLib=static \
  -p:EnableNativeEventPipe=false \
  -p:AllowUnsafeBlocks=true \
  -p:InvariantGlobalization=true

cc test.c dist/lib1.a \
  $AOTBASE/sdk/libbootstrapperdll.a \
  $AOTBASE/sdk/libRuntime.WorkstationGC.a \
  $AOTBASE/sdk/libstdc++compat.a \
  $AOTBASE/sdk/libeventpipe-disabled.a \
  $AOTBASE/framework/libSystem.Native.a \
  $AOTBASE/framework/libSystem.IO.Compression.Native.a \
  $AOTBASE/framework/libSystem.Net.Security.Native.a \
  $AOTBASE/framework/libSystem.Security.Cryptography.Native.OpenSsl.a \
  -Wall -pthread -ldl -lm -Wl,-u,_NativeAOT_StaticInitialization

With globalization

# using code from top post

AOTBASE=~/.nuget/packages/runtime.osx-arm64.microsoft.dotnet.ilcompiler/8.0.0-preview.6.23279.6

dotnet publish -c Release --use-current-runtime -o dist \
  -p:PublishAot=true \
  -p:NativeLib=static \
  -p:EnableNativeEventPipe=false \
  -p:AllowUnsafeBlocks=true

cc test.c dist/lib1.a \
  $AOTBASE/sdk/libbootstrapperdll.a \
  $AOTBASE/sdk/libRuntime.WorkstationGC.a \
  $AOTBASE/sdk/libeventpipe-disabled.a \
  $AOTBASE/framework/libSystem.Native.a \
  $AOTBASE/framework/libSystem.IO.Compression.Native.a \
  $AOTBASE/framework/libSystem.Globalization.Native.a \
  $AOTBASE/framework/libSystem.Net.Security.Native.a \
  $AOTBASE/framework/libSystem.Security.Cryptography.Native.OpenSsl.a \
  -Wall -pthread -ldl -lm -lstdc++ -Wl,-u,_NativeAOT_StaticInitialization \
  -framework Foundation

@DrewRidley
Copy link

Well I got it to compile with all the extra flags but it segfaults, any ideas?

Hello, world!
AddressSanitizer:DEADLYSIGNAL
=================================================================
==37810==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x00010226e578 bp 0x00016db967d0 sp 0x00016db967b0 T0)
==37810==The signal is caused by a READ memory access.
==37810==Hint: address points to the zero page.
    #0 0x10226e578 in RuntimeInstance::GetThreadStore()+0x0 (bepuvy_test:arm64+0x100006578) (BuildId: 55fafb27221c3a948c5a29df25e085e832000000200000000100000000000d00)
    #1 0x102270464 in Thread::ReversePInvokeAttachOrTrapThread(ReversePInvokeFrame*)+0x9c (bepuvy_test:arm64+0x100008464) (BuildId: 55fafb27221c3a948c5a29df25e085e832000000200000000100000000000d00)
    #2 0x1022ba3f8 in AbominationInterop_AbominationInterop_Entrypoints__Initialize+0x18 (bepuvy_test:arm64+0x1000523f8) (BuildId: 55fafb27221c3a948c5a29df25e085e832000000200000000100000000000d00)
    #3 0x10226b510 in bepuvy_test::main::he6eb0bc3b5c3db5b main.rs:5
    #4 0x10226ba4c in core::ops::function::FnOnce::call_once::he36a4519e0cfe0b0 function.rs:250
    #5 0x10226ba94 in std::sys_common::backtrace::__rust_begin_short_backtrace::hdc9d4a3854a882a0 backtrace.rs:135
    #6 0x10226be40 in std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::hded0f81690914588 rt.rs:166
    #7 0x10229341c in std::rt::lang_start_internal::hc6fc6502a4506b67+0x284 (bepuvy_test:arm64+0x10002b41c) (BuildId: 55fafb27221c3a948c5a29df25e085e832000000200000000100000000000d00)
    #8 0x10226bc54 in std::rt::lang_start::h8997bdcfd441c157 rt.rs:165
    #9 0x10226b5ac in main+0x20 (bepuvy_test:arm64+0x1000035ac) (BuildId: 55fafb27221c3a948c5a29df25e085e832000000200000000100000000000d00)
    #10 0x18ab7ff24  (<unknown module>)
    #11 0xeb2afffffffffffc  (<unknown module>)

==37810==Register values:
 x[0] = 0x0000000000000000   x[1] = 0x0000613000000130   x[2] = 0x000000018aed3d14   x[3] = 0x0000603000004b58  
 x[4] = 0x0000603000004b58   x[5] = 0x0000000000000001   x[6] = 0x000000016d39c000   x[7] = 0x0000000000000001  
 x[8] = 0x000000010453b000   x[9] = 0x0000000000000000  x[10] = 0x0000000000800000  x[11] = 0x0000000000004000  
x[12] = 0x0000000000000000  x[13] = 0x0000000000000000  x[14] = 0x0000007000020001  x[15] = 0x00000fffffffffff  
x[16] = 0x0000000000000128  x[17] = 0x0000613000000040  x[18] = 0x0000000000000000  x[19] = 0x0000613000000088  
x[20] = 0x000000018aedfe60  x[21] = 0x0000000000004000  x[22] = 0x000000016d39c000  x[23] = 0x0000000000000000  
x[24] = 0x0000000000004000  x[25] = 0x0000000000000001  x[26] = 0x0000000000000000  x[27] = 0x0000000000000000  
x[28] = 0x0000000000000000     fp = 0x000000016db967d0     lr = 0x0000000102270640     sp = 0x000000016db967b0  
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (bepuvy_test:arm64+0x100006578) (BuildId: 55fafb27221c3a948c5a29df25e085e832000000200000000100000000000d00) in RuntimeInstance::GetThreadStore()+0x0
==37810==ABORTING
zsh: abort      RUSTFLAGS=-Zsanitizer=ad

It seems to segfault at RuntimeInstance which sounds managed

@am11
Copy link
Member Author

am11 commented Jun 4, 2023

You seem to be missing -Wl,-u,_NativeAOT_StaticInitialization. If I drop it, I get the same sigsegv, otherwise if you copy & paste the repro in posts above verbatim, it runs fine:

$ uname -v
Darwin Kernel Version 22.2.0: Fri Nov 11 02:03:51 PST 2022; root:xnu-8792.61.2~4/RELEASE_ARM64_T6000

$ ./a.out 
Hello from C# Add!
Hello from C callback!
sum is: 3

@DrewRidley
Copy link

DrewRidley commented Jun 4, 2023

unning `/Users/drewridley/.rustup/toolchains/nightly-aarch64-apple-darwin/bin/rustc --crate-name bepuvy_sys --edition=2021 /Users/drewridley/Documents/Projects/bepuvy-sys/src/lib.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts,future-incompat --diagnostic-width=138 --crate-type lib --emit=dep-info,metadata,link -C embed-bitcode=no -C split-debuginfo=unpacked -C debuginfo=2 -C metadata=6c63fa1fde51d6fb -C extra-filename=-6c63fa1fde51d6fb --out-dir /Users/drewridley/Documents/Projects/bepuvy_test/target/debug/deps -C incremental=/Users/drewridley/Documents/Projects/bepuvy_test/target/debug/incremental -L dependency=/Users/drewridley/Documents/Projects/bepuvy_test/target/debug/deps --extern directories=/Users/drewridley/Documents/Projects/bepuvy_test/target/debug/deps/libdirectories-6f597290506bc747.rmeta --extern walkdir=/Users/drewridley/Documents/Projects/bepuvy_test/target/debug/deps/libwalkdir-ab61f61c3075b372.rmeta -L /usr/lib/swift -l 'static:+verbatim=/Users/drewridley/.nuget/packages/runtime.osx-arm64.microsoft.dotnet.ilcompiler/8.0.0-preview.4.23259.5/sdk/libbootstrapperdll.a' -l 'static:+verbatim=/Users/drewridley/.nuget/packages/runtime.osx-arm64.microsoft.dotnet.ilcompiler/8.0.0-preview.4.23259.5/sdk/libRuntime.WorkstationGC.a' -l 'static:+verbatim=/Users/drewridley/.nuget/packages/runtime.osx-arm64.microsoft.dotnet.ilcompiler/8.0.0-preview.4.23259.5/sdk/libeventpipe-disabled.a' -l 'static:+verbatim=/Users/drewridley/.nuget/packages/runtime.osx-arm64.microsoft.dotnet.ilcompiler/8.0.0-preview.4.23259.5/sdk/libstdc++compat.a' -l 'static:+verbatim=/Users/drewridley/.nuget/packages/runtime.osx-arm64.microsoft.dotnet.ilcompiler/8.0.0-preview.4.23259.5/framework/libSystem.Native.a' -l 'static:+verbatim=/Users/drewridley/.nuget/packages/runtime.osx-arm64.microsoft.dotnet.ilcompiler/8.0.0-preview.4.23259.5/framework/libSystem.IO.Compression.Native.a' -l 'static:+verbatim=/Users/drewridley/.nuget/packages/runtime.osx-arm64.microsoft.dotnet.ilcompiler/8.0.0-preview.4.23259.5/framework/libSystem.Net.Security.Native.a' -l 'static:+verbatim=/Users/drewridley/.nuget/packages/runtime.osx-arm64.microsoft.dotnet.ilcompiler/8.0.0-preview.4.23259.5/framework/libSystem.Security.Cryptography.Native.Apple.a' -l 'static:+verbatim=/Users/drewridley/.nuget/packages/runtime.osx-arm64.microsoft.dotnet.ilcompiler/8.0.0-preview.4.23259.5/framework/libSystem.Security.Cryptography.Native.OpenSsl.a' -l 'static:+verbatim=/Users/drewridley/.nuget/packages/runtime.osx-arm64.microsoft.dotnet.ilcompiler/8.0.0-preview.4.23259.5/framework/libSystem.Globalization.Native.a' -l 'static:+verbatim=/Volumes/T7/Projects/scratchpad/Abomination/AbominationInterop/AbominationInterop/bin/Release/net8.0/osx.13-arm64/publish/AbominationInterop.a' -l objc -l swiftCore -l swiftFoundation -l icucore -C link-arg=_NativeAOT_StaticInitialization -C link-arg=-u -C link-arg=-Wl`

Just added it and can confirm it ends up getting passed in to the linker correctly. Still having the same issue.

println!("cargo:rustc-link-args=-u,-Wl,_NativeAOT_StaticInitialization");
I have that in my build.rs file.

@am11
Copy link
Member Author

am11 commented Jun 4, 2023

 link-arg=_NativeAOT_StaticInitialization 

this part should be:

 link-arg="-u _NativeAOT_StaticInitialization "

and drop -C link-arg=-u -C link-arg=-Wl part.

@am11
Copy link
Member Author

am11 commented Jun 4, 2023

println!("cargo:rustc-link-args=-u,-Wl,_NativeAOT_StaticInitialization");

should be:

println!("cargo:rustc-link-args=-Wl,-u,_NativeAOT_StaticInitialization");

@DrewRidley
Copy link

DrewRidley commented Jun 4, 2023

Tried that, but I get:

  = note: Undefined symbols for architecture arm64:
            " _NativeAOT_StaticInitialization", referenced from:
               -u command line option
          ld: symbol(s) not found for architecture arm64

Wait, I apologize. It seems it injected a whitespace somehow. Working with rust is honestly super challenging because its very hard to get consistent results when trying to solve issues

htly-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/liballoc-f9204ed0d2c947ca.rlib" "/Users/drewridley/.rustup/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/librustc_std_workspace_core-70a0087d6881bc8d.rlib" "/Users/drewridley/.rustup/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libcore-6fc8bef838a5948d.rlib" "/Users/drewridley/.rustup/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib/libcompiler_builtins-d1be01496bebb995.rlib" "-lobjc" "-lswiftCore" "-lswiftFoundation" "-licucore" "-lSystem" "-lc" "-lm" "-L" "/Users/drewridley/.rustup/toolchains/nightly-aarch64-apple-darwin/lib/rustlib/aarch64-apple-darwin/lib" "-o" "/Users/drewridley/Documents/Projects/bepuvy_test/target/debug/deps/bepuvy_test-782ac07648dc1bbc" "-Wl,-dead_strip" "-nodefaultlibs" "-u _NativeAOT_StaticInitialization"
  = note: Undefined symbols for architecture arm64:
            " _NativeAOT_StaticInitialization", referenced from:
               -u command line option
          ld: symbol(s) not found for architecture arm64
          clang: error: linker command failed with exit code 1

theres the new output with your recommended argument command

@am11
Copy link
Member Author

am11 commented Jun 4, 2023

`"-u _NativeAOT_StaticInitialization"

Instead, try -Wl,-u,_NativeAOT_StaticInitialization"

With compiler driver (clang, gcc etc.), -Wl option tells its command-line parser to set the following command (-u,_NativeAOT_StaticInitialization) as linker (similarly -Wa is for assembler commands). After that , is replaced with space. Meaning, the linker (ld64) should see -u _NativeAOT_StaticInitialization (space after -u).

@DrewRidley
Copy link

Thank you SOOOO much! I have spent the last 2 days trying to get this to work. Really appreciate your help!

@DrewRidley
Copy link

DrewRidley commented Jun 9, 2023

Hey, unfortunately, I'm back.

I rewrote my FFI library like 3 times to try to see if I simply had my types wrong for FFI, and was able to make some progress. I managed to get some basic passing of data and function calls to work, and my tests in my FFI crate all seem to pass without incident.

Unfortunately, when I move the exact same code in the unit test over to a separate binary, it falls apart. I get the aforementioned error, despite linking with all the desired flags and arguments suggested above.

RuntimeInstance::GetThreadStore()

Specifically, this method tries to load a register with a null pointer causing a segfault. If anyone could shed some light into the specific circumstances in which this might happen, it is possible I would be able to diagnose the issue further. I am suspicious of my linker because of this errors ties to previous missing linker arguments, but I am not convinced because I am passing the same arguments in both cases.

As usual any help is much appreciated,
Thanks.

UPDATE: I believe I may understand the problem, but not the solution. I think rust when compiling the unit test will compile directly against the static library with all of the linker args, whereas the intermediate crate seems to just use a .rlib generated. My suspicion is that rust strips this symbol when generating and caching that rlib so when it hits the second crate, the argument is missing hence the segfault.
I will investigate further but any pointers would still be greatly appreciated.

I filed an issue for rust over here for anyone else experiencing similar issues:
rust-lang/rust#112446

@DrewRidley
Copy link

DrewRidley commented Jun 9, 2023

Turns out it was some strange undocumented behavior where rust did not properly transient linker arguments, ontop of the fact that args does not work in dependent crates. Still unsure as to why this happened.

Adding the linker args, I now get a segfault somewhere else in my code, but only when integrating with a specific existing crate:

; Symbol: RhpNewFast
; Source: /Users/runner/work/1/s/src/coreclr/nativeaot/Runtime/arm64/AllocFast.S:27

Seems very related to:

#67232

@am11 , Any ideas on how to solve this? I saw you actively involved in the other issue so I figured you might be able to help.

I am on 8.0.100-preview.4.23260.5 [/usr/local/share/dotnet/sdk] which from what I understand is supposed to already include the fix in the other issue?

@DrewRidley
Copy link

Ah, I have a suspicion that I am actually not using the latest preview of NativeAOT despite it being installed.

<configuration>
    <packageSources>
        <add key="dotnet8" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json" />
    </packageSources>
</configuration>

I added that to my nuget.config and added <PackageReference Include="Microsoft.DotNet.ILCompiler" Version="8.0.0-*" /> to my sln file. When running dotnet publish, it still prints with the old versions. My nuget cache does however have a folder for the preview. Is there something I need to do to ensure the preview is used?

@DrewRidley
Copy link

DrewRidley commented Jun 9, 2023

(lldb) bt
* thread #17, stop reason = signal SIGSEGV
  * frame #0: 0x00000001022ca1b4 bepuvy_test`RhpNewFast at AllocFast.S:57
    frame #1: 0x000000010348a16c bepuvy_test`BepuUtilities_System_Runtime_CompilerServices_RefSafetyRulesAttribute____GetFieldHelper + 795116
    frame #2: 0x0000000103489d2c bepuvy_test`_S_P_CoreLib_System_Enum___c__1_1<UInt16>___GetEnumInfo_b__1_0(this=<unavailable>, underlyingType=<unavailable>, names=<unavailable>, valuesAsObject=<unavailable>, isFlags=<unavailable>) + 794028
    frame #3: 0x000000010227f44c bepuvy_test`InitializeRuntime() at main.cpp:182:5 [opt]
    frame #4: 0x0000000102286308 bepuvy_test`Thread::ReversePInvokeAttachOrTrapThread(ReversePInvokeFrame*) [inlined] Thread::EnsureRuntimeInitialized(this=0x00000001281dac90) at thread.cpp:1219:13 [opt]
    frame #5: 0x00000001022862c0 bepuvy_test`Thread::ReversePInvokeAttachOrTrapThread(this=0x00000001281dac90, pFrame=0x00000001717341d8) at thread.cpp:1181:13 [opt]
    frame #6: 0x00000001034d5fb4 bepuvy_test`BepuUtilities_System_Runtime_CompilerServices_RefSafetyRulesAttribute____GetFieldHelper + 1105972
    frame #7: 0x000000010227f334 bepuvy_test`bepuvy_sys::bepu::setup_pyramid::h25b5969c498212b6 at mod.rs:42:9

There is the full stack trace if it helps. Rust is not doing anything but calling the function that returns nothing, setup_pyramid.

GetFieldHelper sort of sounded like some kind of reflection, but upon further investigation it seems to be some internal marshalling function that the CoreCLR uses:

Instance fields

    * CORINFO_FLG_HELPER This is used if the class is MarshalByRef, which means that the object might be a
        proxy to the real object in some other appdomain or process. If the field has this set, then the JIT
        must call getFieldHelper and call the returned helper with the object ref. If the helper returned is
        helpers that are for structures the args are as follow

from: https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/corinfo.h

@am11
Copy link
Member Author

am11 commented Jun 9, 2023

My nuget cache does however have a folder for the preview. Is there something I need to do to ensure the preview is used?

You can create a new project: dotnet8 new console -n app1 (dotnet8 is an alias to ~/.dotnet8/dotnet in my setup) and publish it as AOT app dotnet8 publish -p:PublishAot=true -o app1/dist. Open app1/app1.csproj and adapt the same in your real project. It will ensure that you are picking up the correct binaries for the given version.

Rust is not doing anything but calling the function that returns nothing, setup_pyramid.

If you could isolate the reproducible project (preferably one .cs file and a .c file) and make an SSCCE out of it, that would make the investigation go faster. Feel free to post your findings in a new issue (since it is getting a bit off-topic in this thread).

ps - currently I'm looking into #87333 (ongoing native AOT issue on osx-arm64 in main branch)

@DrewRidley
Copy link

Its not a single C# file, but
https://github.com/DrewRidley/bepuvy-bepu

with:

namespace Bepu {
    extern "C" void SetupPyramidDemo();
}


int main() {
    Bepu::SetupPyramidDemo();
    return 0;
}

Reproduces the issue

@am11
Copy link
Member Author

am11 commented Jun 9, 2023

With:

$ cd bepuvy-bepu
$ dotnet8 publish -c Release --use-current-runtime -o dist \
  -p:PublishAot=true \
  -p:NativeLib=static \
  -p:EnableNativeEventPipe=false

$ cat > glue.cpp <<EOF
namespace Bepu {
    extern "C" void SetupPyramidDemo();
}


int main() {
    Bepu::SetupPyramidDemo();
    return 0;
}
EOF

$ AOTBASE=~/.nuget/packages/runtime.osx-arm64.microsoft.dotnet.ilcompiler/8.0.0-preview.6.23307.4 
$ cc glue.cpp dist/Bepuvy.a \
  $AOTBASE/sdk/libbootstrapperdll.a \
  $AOTBASE/sdk/libRuntime.WorkstationGC.a \
  $AOTBASE/sdk/libeventpipe-disabled.a \
  $AOTBASE/framework/libSystem.Native.a \
  $AOTBASE/framework/libSystem.IO.Compression.Native.a \
  $AOTBASE/framework/libSystem.Globalization.Native.a \
  $AOTBASE/framework/libSystem.Net.Security.Native.a \
  $AOTBASE/framework/libSystem.Security.Cryptography.Native.OpenSsl.a \
  -Wall -pthread -ldl -lm -lstdc++ -Wl,-u,_NativeAOT_StaticInitialization \
  -framework Foundation

it works:

$ ./a.out
$ echo $?
0

@DrewRidley
Copy link

Huh, your reproduction works. I will note that the linker error I got earlier with the latest preview was associated with a github issue you recently posted. I will take it from here, and appreciate your help on the matter.

@am11
Copy link
Member Author

am11 commented Jun 9, 2023

Note: when we omit libSystem.Globalization.Native.a and -lstdc++, we need to add libstdc++compat.a, i.e.

# publish with invariant culture: -p:InvariantGlobalization=true
$ cc glue.cpp dist/Bepuvy.a \
  $AOTBASE/sdk/libbootstrapperdll.a \
  $AOTBASE/sdk/libRuntime.WorkstationGC.a \
  $AOTBASE/sdk/libstdc++compat.a \      
  $AOTBASE/sdk/libeventpipe-disabled.a \ 
  $AOTBASE/framework/libSystem.Native.a \               
  $AOTBASE/framework/libSystem.IO.Compression.Native.a \
  $AOTBASE/framework/libSystem.Net.Security.Native.a \                 
  $AOTBASE/framework/libSystem.Security.Cryptography.Native.OpenSsl.a \
  -Wall -pthread -ldl -lm -Wl,-u,_NativeAOT_StaticInitialization

# publish without `-p:InvariantGlobalization=true` line
$ cc glue.cpp dist/Bepuvy.a \
  $AOTBASE/sdk/libbootstrapperdll.a \
  $AOTBASE/sdk/libRuntime.WorkstationGC.a \
  $AOTBASE/sdk/libeventpipe-disabled.a \
  $AOTBASE/framework/libSystem.Native.a \
  $AOTBASE/framework/libSystem.IO.Compression.Native.a \
  $AOTBASE/framework/libSystem.Globalization.Native.a \
  $AOTBASE/framework/libSystem.Net.Security.Native.a \
  $AOTBASE/framework/libSystem.Security.Cryptography.Native.OpenSsl.a \
  -Wall -pthread -ldl -lm -lstdc++ -Wl,-u,_NativeAOT_StaticInitialization \
  -framework Foundation

@DrewRidley
Copy link

DrewRidley commented Jun 9, 2023

Yeah. I am still not totally sure what is going on but it seems something to do with how rust handles linking. Rust seems to have problems with it despite being fed the same linker arguments.
More investigation will certainly be needed on my end. I do not at this time believe that this is a problem with NativeAOT.

        println!("cargo:rustc-link-lib=objc");
        println!("cargo:rustc-link-lib=swiftCore");
        println!("cargo:rustc-link-lib=swiftFoundation");
        println!("cargo:rustc-link-lib=icucore");
        println!("cargo:rustc-link-search=/usr/lib/swift")

I think it might come down to this being different than -framework Foundation? I tried using ghdira to open up the binary created with rust but it must be totally corrupt because I cant even import it

I am still not totally sure what is going on but it seems something to do with how rust handles linking. Rust seems to have problems with it despite being fed the same linker arguments.
More investigation will certainly be needed on my end. I do not at this time believe that this is a problem with NativeAOT.
https://gist.github.com/DrewRidley/668a4c673021b2e5aa47e08381ce7673

Theres my build.rs script. If you create a new rust binary, apply the build.rs and replace your main with the following:

extern "C" {
   fn SetupPyramid();
}

fn main() {
  SetupPyramid();
} 

it will reproduce the issue.

@DrewRidley
Copy link

DrewRidley commented Jun 10, 2023

I finally seemed to have figured it out. For some really obscene reason the version of ld that rust ships with (or the mold linker) does not treat -lc++ the same as the linker shipped by MacOS. For now I will just have to use the built in linker for this specific program.

Theres some silent difference between the linker Apple ships with Mac and the one rust ships (or mold for that matter). Was never able to figure out the difference but it has to do with libc++ afaik

Thank you again with your extensive help on this long journey and I appreciate everything you have done for me.

@MichalStrehovsky MichalStrehovsky modified the milestones: 8.0.0, Future Jul 11, 2023
@serefarikan
Copy link

Just wanted to thank you @DrewRidley for sharing the details. I'm working on a new design for a poc and this thread helped!.

@DrewRidley
Copy link

No worries, if it helps I found out the problem was with rust, although I'm not really sure how changing the linker changed the behavior.

Rust apparently strips unused symbols by default but this is poorly documented and not well known.

@serefarikan
Copy link

I'll keep an eye on that. I have a scenario where I'd like to call c# code from Rust and there are just too many footguns lying around :)

@taodongl
Copy link

taodongl commented Nov 7, 2023

How about Windows? There aren't *.lib (for example: System.Native.lib) in C:\Users\taodongl\.nuget\packages\runtime.win-x64.microsoft.dotnet.ilcompiler\8.0.0-rc.2.23479.6
So C code fails to link static library generated by dotnet AOT: dotnet publish /p:NativeLib=Static -r win-x64 -c Release because error LNK2001: unresolved external symbol

error LNK2001: unresolved external symbol BCryptHashData
error LNK2001: unresolved external symbol GlobalizationNative_IanaIdToWindowsId
error LNK2001: unresolved external symbol RhpRegisterOsModule
... ...

@serefarikan
Copy link

@taodongl That sounds like static library support for Native AOT not working on Windows. I am nowhere near capable of giving it a go to confirm your situation but if that is that is the case I'm sure the team would appreciate a dedicated issue 😃 Though, I would be surprised if this was the case with 8.0 final release, but what do I know?

@MichalStrehovsky
Copy link
Member

There aren't *.lib

There must be *.lib files or building EXE files wouldn't work either (we pass the same LIB files to the linker). Did you try following these instructions to get the list of paths to pass to linker? ("You can find a list of additional framework libraries by publishing the project as shared library (/p:NativeLib=Shared) with detailed verbosity (-v d), and looking at the output generated by the LinkNative target.")

@jkotas jkotas changed the title Improve static library publishing on Unix Improve static library publishing Dec 24, 2023
@lambdageek
Copy link
Member

Another .NET 8 update. Since #89291, it's not necessary to add any linker flags regarding_NativeAOT_StaticInitialization

@lambdageek
Copy link
Member

lambdageek commented Feb 9, 2024

@MichalStrehovsky it would be useful if some msbuild item contained the list of static libraries and objects (either relative to IlcSdkPath/IlcFrameworkPath or absolute paths) even for a static library build. Then an interested project could write the items out to a file that the native build system could consume. I'm doing something like that below, but having to hardcode the names of the specific libs. it would be better if I could get them from the build:

https://github.com/lambdageek/naothello/blob/80b575a1b1105d95541df71bc43829211524842a/libnaothello/libnaothello.csproj#L23-L25

and

https://github.com/lambdageek/naothello/blob/7707d05b618bc499c77d4080338b228e4f7b0ca3/app/nativeaot.cmake#L4-L25

@am11
Copy link
Member Author

am11 commented Feb 9, 2024

@lambdageek, I was thinking emitting a "helper script" file with -p:NativeLib=static in the output dir from BuildIntegration targets. We can also print a README file with instructions instead. Something helpful for various use-cases discussed here and the common ones out in the wild.

@DrewRidley
Copy link

Hey all, back here again unfortunately,
I recently revisited an old NativeAOT project, and unfortunately have hit a roadblock witth the linker errors of my past days.

     /usr/bin/ld: (__managedcode+0x27bd10d): undefined reference to `RhpCheckedAssignRef'
          /usr/bin/ld: (__managedcode+0x27bd119): undefined reference to `__security_cookie'
          /usr/bin/ld: (__managedcode+0x27bd127): undefined reference to `RhpFallbackFailFast'
          /usr/bin/ld: (__managedcode+0x27bd151): undefined reference to `RhpNewArray'
          /usr/bin/ld: (__managedcode+0x27bd1b9): undefined reference to `RhpCheckedAssignRef'
          /usr/bin/ld: (__managedcode+0x27bd1c5): undefined reference to `__security_cookie'
          /usr/bin/ld: (__managedcode+0x27bd1d3): undefined reference to `RhpFallbackFailFast'
          /usr/bin/ld: (__managedcode+0x27bd1ef): undefined reference to `__security_cookie'
          /usr/bin/ld: (__managedcode+0x27bd1fd): undefined reference to `RhpFallbackFailFast'

I managed to follow the same steps from before, explicitly adding all the required paths for the static libraries needed, but am prompted with these internal symbols being undefined at link time. I had a peek around and found some .s files in the runtime, but it wasn't clear where these symbols came from.

Any help is much appreciated. Thanks

@MichalStrehovsky
Copy link
Member

I managed to follow the same steps from before, explicitly adding all the required paths for the static libraries needed, but am prompted with these internal symbols being undefined at link time

These instructions should guide you to what's needed - first publish as a shared library with diagnostic verbosity and then find the required arguments to pass to clang in the verbose log of LinkNative target.

@dotnet-policy-service dotnet-policy-service bot added the in-pr There is an active PR which will close this issue when it is merged label Jun 25, 2024
@am11 am11 closed this as not planned Won't fix, can't repro, duplicate, stale Jul 1, 2024
@github-actions github-actions bot locked and limited conversation to collaborators Aug 1, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-NativeAOT-coreclr in-pr There is an active PR which will close this issue when it is merged
Projects
Archived in project
Development

Successfully merging a pull request may close this issue.

7 participants