Skip to content

Commit

Permalink
[NativeAOT-LLVM] WASI fixes (#2386)
Browse files Browse the repository at this point in the history
* Fix a number of issues with WASI libraries

1) Write and consume the exports list
2) Do not call "_initialize" twice.
3) Add "-mexec-model=reactor" to the linker command line

* Add a test

* Error out on missing SDKs

* Fully specify the target triplet

Avoids warnings such as the following:

EXEC : warning : overriding the module target triple with wasm32-unknown-wasi [-Woverride-module]

* Up node the NodeJS version for CI

v18.16 is missing the WASI package.

* Make the flag TU-local

Co-authored-by: Jan Kotas <[email protected]>

---------

Co-authored-by: Jan Kotas <[email protected]>
  • Loading branch information
SingleAccretion and jkotas authored Aug 24, 2023
1 parent 09417c1 commit 68cf326
Show file tree
Hide file tree
Showing 10 changed files with 100 additions and 34 deletions.
8 changes: 2 additions & 6 deletions eng/pipelines/common/global-build-job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ jobs:
df -h
displayName: Disk Usage before Build
- ${{ if and(eq(parameters.runtimeFlavor, 'coreclr'), eq(parameters.platform, 'browser_wasm_win')) }}:
# Install Wasm dependencies: emscripten, LLVM, NodeJS
- ${{ if and(eq(parameters.runtimeFlavor, 'coreclr'), eq(parameters.archType, 'wasm')) }}:
- script: call $(Build.SourcesDirectory)/eng/pipelines/runtimelab/install-emscripten.cmd $(Build.SourcesDirectory)\wasm-tools
displayName: Install/activate emscripten
- script: call $(Build.SourcesDirectory)/eng/pipelines/runtimelab/install-llvm.cmd $(Build.SourcesDirectory)\wasm-tools $(Build.SourcesDirectory) ${{ parameters.buildConfig }}
Expand All @@ -187,11 +187,7 @@ jobs:
displayName: Install NodeJS

- ${{ if and(eq(parameters.runtimeFlavor, 'coreclr'), eq(parameters.platform, 'wasi_wasm_win')) }}:
# Install Wasi Wasm dependencies: emscripten, LLVM, wasi-sdk
- script: call $(Build.SourcesDirectory)/eng/pipelines/runtimelab/install-emscripten.cmd $(Build.SourcesDirectory)\wasm-tools
displayName: Install/activate emscripten
- script: call $(Build.SourcesDirectory)/eng/pipelines/runtimelab/install-llvm.cmd $(Build.SourcesDirectory)\wasm-tools $(Build.SourcesDirectory) ${{ parameters.buildConfig }}
displayName: Install/build LLVM
# Install Wasi Wasm dependencies: wasi-sdk, wasmer
- script: call $(Build.SourcesDirectory)/eng/pipelines/runtimelab/install-wasi-sdk.cmd $(Build.SourcesDirectory)\wasm-tools
displayName: Install wasi-sdk
- script: call $(Build.SourcesDirectory)/eng/pipelines/runtimelab/install-wasmer.cmd $(Build.SourcesDirectory)\wasm-tools
Expand Down
2 changes: 1 addition & 1 deletion eng/pipelines/runtimelab/install-nodejs.ps1
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
$InstallPath = $Args[0]
$NodeJSVersion = "v18.16.0"
$NodeJSVersion = "v20.2.0"
$NodeJSInstallName = "node-$NodeJSVersion-win-x64"
$NodeJSZipName = "$NodeJSInstallName.zip"

Expand Down
14 changes: 11 additions & 3 deletions src/coreclr/nativeaot/Bootstrap/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,12 @@ extern "C" int __managed__Main(int argc, char* argv[]);
#else
#define NATIVEAOT_ENTRYPOINT __managed__Startup
extern "C" void __managed__Startup();

#ifdef TARGET_WASI
// _initialize is a function generated by the WASI SDK libc that calls the LLVM synthesized __wasm_call_ctors function for reactor components:
// https://github.com/WebAssembly/wasi-libc/blob/9f51a7102085ec6a6ced5778f0864c9af9f50000/libc-bottom-half/crt/crt1-reactor.c#L7-L27
// We define and call it for NATIVEAOT_DLL and TARGET_WASI to call all the global c++ static constructors. This ensures the runtime is initialized
// when calling into WebAssembly Component Model components
#if defined(NATIVEAOT_DLL) && defined(TARGET_WASI)
// when calling into WebAssembly Component Model components.
extern "C" void _initialize();

// CustomNativeMain programs are built using the same libbootstrapperdll as NATIVEAOT_DLL but wasi-libc will not provide an _initialize implementation,
Expand All @@ -181,13 +182,20 @@ __attribute__((weak)) void _initialize()
{
}

// Guard the "_initialize" call so that well-behaving hosts do not get affected by this workaround.
static bool g_CalledInitialize = false;
struct WasiInitializationFlag { WasiInitializationFlag() { *(volatile bool*)&g_CalledInitialize = true; } };
WasiInitializationFlag g_WasiInitializationFlag;
#endif // TARGET_WASI
#endif // !NATIVEAOT_DLL

static int InitializeRuntime()
{
#if defined(NATIVEAOT_DLL) && defined(TARGET_WASI)
_initialize();
if (!g_CalledInitialize)
{
_initialize();
}
#endif

if (!RhInitialize())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ The .NET Foundation licenses this file to you under the MIT license.
<WasmOptimizationSetting Condition="$(Optimize) == 'true' and $(OptimizationPreference) != 'Size' and '$(_targetOS)' == 'wasi'">-O2</WasmOptimizationSetting>
<WasmOptimizationSetting Condition="$(Optimize) == 'true' and $(OptimizationPreference) == 'Size'">-Oz</WasmOptimizationSetting>

<IlcLlvmTarget Condition="'$(_targetOS)' == 'wasi'">wasm32-wasi</IlcLlvmTarget>
<IlcLlvmTarget Condition="'$(_targetOS)' == 'wasi'">wasm32-unknown-wasi</IlcLlvmTarget>
</PropertyGroup>

<PropertyGroup Condition="'$(IlcCompileDependsOn)'=='' and '$(NativeCompilationDuringPublish)' != 'false'">
Expand Down Expand Up @@ -371,10 +371,17 @@ The .NET Foundation licenses this file to you under the MIT license.
</ItemGroup>
</Target>

<Target Name="CheckWasmSdks">
<Error Text="Emscripten not found, not compiling to WebAssembly. To enable WebAssembly compilation, install Emscripten and ensure the EMSDK environment variable points to the directory containing upstream/emscripten/emcc.bat"
Condition="'$(EMSDK)' == ''" />
<Error Text="Wasi SDK not found, not compiling to WebAssembly. To enable WebAssembly compilation, install Wasi SDK and ensure the WASI_SDK_PATH environment variable points to the directory containing share/wasi-sysroot"
Condition="'$(WASI_SDK_PATH)' == '' and '$(_targetOS)' == 'wasi'" />
</Target>

<Target Name="CompileWasmObjects"
Inputs="@(LlvmObjects)"
Outputs="@(LlvmObjects->'%(NativeObject)')"
DependsOnTargets="IlcCompile;GenerateResFile"
DependsOnTargets="IlcCompile;GenerateResFile;CheckWasmSdks"
Condition="'$(NativeCodeGen)' == 'llvm'">

<MakeDir Directories="$([System.IO.Path]::GetDirectoryName($(NativeBinary)))" />
Expand All @@ -393,7 +400,7 @@ The .NET Foundation licenses this file to you under the MIT license.
</PropertyGroup>

<PropertyGroup Condition="'$(_targetOS)' == 'wasi'">
<CompileWasmArgs>-fvisibility=default -mllvm -combiner-global-alias-analysis=false -mllvm -disable-lsr --sysroot=&quot;$(WASI_SDK_PATH)/share/wasi-sysroot&quot; -target wasm32-wasi -c</CompileWasmArgs>
<CompileWasmArgs>-fvisibility=default -mllvm -combiner-global-alias-analysis=false -mllvm -disable-lsr --sysroot=&quot;$(WASI_SDK_PATH)/share/wasi-sysroot&quot; -target $(IlcLlvmTarget) -c</CompileWasmArgs>
<CompileWasmArgs>$(CompileWasmArgs) $(WasmOptimizationSetting)</CompileWasmArgs>
<CompileWasmArgs Condition="'$(NativeDebugSymbols)' == 'true'">$(CompileWasmArgs) -g3</CompileWasmArgs>

Expand All @@ -413,11 +420,7 @@ The .NET Foundation licenses this file to you under the MIT license.
</ExecProjects>
</ItemGroup>

<MSBuild Projects="@(ExecProjects)" BuildInParallel="true" Condition="'$(EMSDK)' != ''" />
<Message Text="Emscripten not found, not linking WebAssembly. To enable WebAssembly linking, install Emscripten and ensure the EMSDK environment variable points to the directory containing upstream/emscripten/emcc.bat"
Condition="'$(EMSDK)' == '' and '$(_targetOS)' == 'browser'" />
<Message Text="Wasi SDK not found, not linking WebAssembly. To enable WebAssembly linking, install Wasi SDK and ensure the WASI_SDK_PATH environment variable points to the directory containing share/wasi-sysroot"
Condition="'$(WASI_SDK_PATH)' == '' and '$(_targetOS)' == 'wasi'" />
<MSBuild Projects="@(ExecProjects)" BuildInParallel="true" />
</Target>

<Target Name="LinkNativeSingle" Condition="'$(_targetOS)' != 'browser' and '$(_targetOS)' != 'wasi'"
Expand Down Expand Up @@ -522,26 +525,27 @@ The .NET Foundation licenses this file to you under the MIT license.
<CustomLinkerArg Condition="'$(WasmEnableExceptionHandling)' == 'true'" Include="-fwasm-exceptions" />
</ItemGroup>

<!-- wasm-ld only supports listing exports on the command line -->
<ReadLinesFromFile File="$(ExportsFile)" Condition="'$(_targetOS)' == 'wasi' and '$(ExportsFile)' != ''">
<Output TaskParameter="Lines" ItemName="CustomLinkerArgExport" />
</ReadLinesFromFile>

<ItemGroup Condition ="'$(_targetOS)' == 'wasi'" >
<!-- Wasi has lots of undefined symbols currently, mostly globalization -->
<CustomLinkerArg Include="-Wl,--unresolved-symbols=ignore-all -lstdc++ -Wl,--error-limit=0" />
<CustomLinkerArg Include="--sysroot=&quot;$(WASI_SDK_PATH.Replace(&quot;\&quot;, &quot;/&quot;))/share/wasi-sysroot&quot;" />
<CustomLinkerArg Include="-Wl,--max-memory=2147483648" />
<CustomLinkerArg Include="-lwasi-emulated-process-clocks -lwasi-emulated-signal -lwasi-emulated-mman -lwasi-emulated-getpid" />
<CustomLinkerArg Include="-mexec-model=reactor" Condition="'$(NativeLib)' == 'Shared'" />
<CustomLinkerArg Include="@(CustomLinkerArgExport->'-Wl,--export=%(Identity)')" />
</ItemGroup>

<ItemGroup>
<CustomLinkerArg Include="@(LinkerArg)" />
</ItemGroup>

<WriteLinesToFile File="$(NativeIntermediateOutputPath)link.rsp" Lines="@(CustomLinkerArg)" Overwrite="true" Encoding="utf-8" />

<Exec Command="$(WasmLinkerPath) @$(NativeIntermediateOutputPath)link.rsp $(EmccExtraArgs)"
Condition="('$(_targetOS)' == 'browser' and '$(EMSDK)' != '') or ('$(_targetOS)' == 'wasi' and '$(WASI_SDK_PATH)' != '')" />
<Message Text="Emscripten not found, not linking WebAssembly. To enable WebAssembly linking, install Emscripten and ensure the EMSDK environment variable points to the directory containing upstream/emscripten/emcc.bat"
Condition="'$(NativeCodeGen)' == 'llvm' and '$(_targetOS)' == 'browser'and '$(EMSDK)' == ''" Importance="High" />
<Message Text="Wasi SDK not found, not linking WebAssembly. To enable WebAssembly linking, install the Wasi SDK and ensure the WASI_SDK_PATH environment variable points to the directory containing share/wasi-sysroot"
Condition="'$(NativeCodeGen)' == 'llvm' and '$(_targetOS)' == 'wasi'and '$(WASI_SDK_PATH)' == ''" Importance="High" />
<Exec Command="$(WasmLinkerPath) @$(NativeIntermediateOutputPath)link.rsp $(EmccExtraArgs)" />
</Target>

<Target Name="LinkNative"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,16 @@ public void EmitExportedMethods()
foreach (var method in _methods)
streamWriter.WriteLine($" {method.GetUnmanagedCallersOnlyExportName()}");
}
else if(_context.Target.IsOSXLike || _context.Target.OperatingSystem == TargetOS.Browser)
else if (_context.Target.IsOSXLike || _context.Target.OperatingSystem == TargetOS.Browser)
{
foreach (var method in _methods)
streamWriter.WriteLine($"_{method.GetUnmanagedCallersOnlyExportName()}");
}
else if (_context.Target.OperatingSystem == TargetOS.Wasi)
{
foreach (var method in _methods)
streamWriter.WriteLine(method.GetUnmanagedCallersOnlyExportName());
}
else
{
streamWriter.WriteLine("V1.0 {");
Expand Down
2 changes: 1 addition & 1 deletion src/tests/Common/dirs.proj
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
</ItemGroup>

<ItemGroup Condition="'$(TestBuildMode)' == 'nativeaot' and '$(TargetArchitecture)' == 'wasm'">
<DisabledProjects Include="$(TestRoot)nativeaot\SmokeTests\SharedLibrary\SharedLibrary.csproj" />
<DisabledProjects Include="$(TestRoot)nativeaot\SmokeTests\SharedLibrary\SharedLibrary.csproj" Condition="'$(TargetOS)' == 'browser'" />
<DisabledProjects Include="$(TestRoot)nativeaot\GenerateUnmanagedEntryPoints\GenerateUnmanagedEntryPoints.csproj" />

<!-- Manual test. -->
Expand Down
5 changes: 5 additions & 0 deletions src/tests/Common/scripts/nativeaottest.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ set __JsFileName=%2
set __JsFileName=%__JsFileName:~0,-4%.js
set __JsFilePath=%1\native\%__JsFileName%

REM also probe for .mjs
if not exist %__JsFilePath% (
set __JsFilePath=%__JsFilePath:~0,-3%.mjs
)

set __WasmFileName=%2
set __WasmFileName=%__WasmFileName:~0,-4%.wasm
set __WasmFilePath=%1native\%__WasmFileName%
Expand Down
1 change: 0 additions & 1 deletion src/tests/nativeaot/SmokeTests/HelloWasm/HelloWasm.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<CLRTestKind>BuildAndRun</CLRTestKind>
<CLRTestPriority>0</CLRTestPriority>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>

<!-- IL3049 warns about mismatched signatures; we are intentionally testing they are handled correctly
Expand Down
22 changes: 16 additions & 6 deletions src/tests/nativeaot/SmokeTests/SharedLibrary/SharedLibrary.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,36 @@
<CLRTestPriority>0</CLRTestPriority>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NativeLib>Shared</NativeLib>

<WasiReactorTestUsingNodeJS Condition="'$(TargetOS)' == 'wasi'">true</WasiReactorTestUsingNodeJS>
</PropertyGroup>

<ItemGroup>
<Compile Include="SharedLibrary.cs" />
</ItemGroup>

<PropertyGroup>
<NativeExtension Condition="'$(WasiReactorTestUsingNodeJS)' == 'true'">.mjs</NativeExtension>
<NativeExtension Condition="'$(TargetOS)' == 'windows'">.exe</NativeExtension>

<CLRTestBatchPreCommands><![CDATA[
$(CLRTestBatchPreCommands)
mkdir native 2>nul
copy /y SharedLibraryDriver.exe native\SharedLibrary.exe
copy /y SharedLibraryDriver$(NativeExtension) native\SharedLibrary$(NativeExtension)
]]></CLRTestBatchPreCommands>

<CLRTestBashPreCommands><![CDATA[
$(CLRTestBashPreCommands)
mkdir -p native
cp SharedLibraryDriver native/SharedLibrary
cp SharedLibraryDriver$(NativeExtension) native/SharedLibrary$(NativeExtension)
]]></CLRTestBashPreCommands>
</PropertyGroup>

<ItemGroup>
<Compile Include="SharedLibrary.cs" />
</ItemGroup>
<ItemGroup>
<ItemGroup Condition="'$(WasiReactorTestUsingNodeJS)' != 'true'">
<CMakeProjectReference Include="CMakeLists.txt" />
</ItemGroup>

<ItemGroup Condition="'$(WasiReactorTestUsingNodeJS)' == 'true'">
<Content Include="SharedLibraryDriver.mjs" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { readFile } from 'node:fs/promises';
import { WASI } from 'wasi';
import { argv, env } from 'node:process';

const wasi = new WASI({
version: 'preview1',
args: argv,
env
});

const wasm = await WebAssembly.compile(
await readFile(new URL("./SharedLibrary.wasm", import.meta.url)),
);

const instance = await WebAssembly.instantiate(wasm, wasi.getImportObject());

wasi.initialize(instance);

if (instance.exports.ReturnsPrimitiveInt() != 10)
process.exit(1);

if (instance.exports.ReturnsPrimitiveBool() != 1)
process.exit(2);

if (instance.exports.ReturnsPrimitiveChar() != 97) // 'a'
process.exit(3);

// As long as no unmanaged exception is thrown managed class loaders were initialized successfully.
instance.exports.EnsureManagedClassLoaders();

// #if !CODEGEN_WASI - for some reason tries to create a background thread?
// if (instance.exports.CheckSimpleGCCollect() != 100)
// process.exit(4);

// #if !CODEGEN_WASI - enable when we support exception handling
// if (instance.exports.CheckSimpleExceptionHandling() != 100)
// process.exit(5);

process.exit(100);

0 comments on commit 68cf326

Please sign in to comment.