diff --git a/eng/Versions.props b/eng/Versions.props index a7c00e5d82ee79..7b78ac2770c964 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -166,7 +166,7 @@ 2.0.4 4.12.0 2.14.3 - 6.0.100-rc.2.21463.12 + 6.0.100-rc.2.21474.31 6.0.0-preview-20210916.1 diff --git a/eng/testing/scenarios/BuildWasmAppsJobsList.txt b/eng/testing/scenarios/BuildWasmAppsJobsList.txt index c4c5a1875c3bfc..8afeea6cdeeb13 100644 --- a/eng/testing/scenarios/BuildWasmAppsJobsList.txt +++ b/eng/testing/scenarios/BuildWasmAppsJobsList.txt @@ -11,6 +11,7 @@ Wasm.Build.Tests.LocalEMSDKTests Wasm.Build.Tests.MainWithArgsTests Wasm.Build.Tests.NativeBuildTests Wasm.Build.Tests.NativeLibraryTests +Wasm.Build.Tests.PInvokeTableGeneratorTests Wasm.Build.Tests.RebuildTests Wasm.Build.Tests.SatelliteAssembliesTests Wasm.Build.Tests.WasmBuildAppTest diff --git a/src/libraries/workloads-testing.targets b/src/libraries/workloads-testing.targets index d3dc57267a608a..6a8fa5055bbbb3 100644 --- a/src/libraries/workloads-testing.targets +++ b/src/libraries/workloads-testing.targets @@ -16,7 +16,6 @@ - @@ -41,7 +40,6 @@ - diff --git a/src/mono/wasm/BlazorOverwrite.targets b/src/mono/wasm/BlazorOverwrite.targets deleted file mode 100644 index a276d385723725..00000000000000 --- a/src/mono/wasm/BlazorOverwrite.targets +++ /dev/null @@ -1,741 +0,0 @@ - - - - - true - - - true - - - - - $(MSBuildThisFileDirectory)..\ - <_BlazorWebAssemblySdkTasksTFM Condition=" '$(MSBuildRuntimeType)' == 'Core'">net6.0 - <_BlazorWebAssemblySdkTasksTFM Condition=" '$(MSBuildRuntimeType)' != 'Core'">net472 - <_BlazorWebAssemblySdkTasksAssembly>$(BlazorWebAssemblySdkDirectoryRoot)tools\$(_BlazorWebAssemblySdkTasksTFM)\Microsoft.NET.Sdk.BlazorWebAssembly.Tasks.dll - <_BlazorWebAssemblySdkToolAssembly>$(BlazorWebAssemblySdkDirectoryRoot)tools\net6.0\Microsoft.NET.Sdk.BlazorWebAssembly.Tool.dll - - - - - - - - - - - - - - true - true - - - false - false - true - false - false - false - <_AggressiveAttributeTrimming Condition="'$(_AggressiveAttributeTrimming)' == ''">true - false - true - - - false - false - false - false - true - - - false - - <_TargetingNET60OrLater Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp' AND $([MSBuild]::VersionGreaterThanOrEquals('$(TargetFrameworkVersion)', '6.0'))">true - - - - false - - true - true - false - ComputeFilesToPublish;_GatherWasmFilesToPublish;$(WasmNestedPublishAppDependsOn) - <_ScrambleDotnetJsFileNameAfterThisTarget Condition="'$(UsingBrowserRuntimeWorkload)' != 'true'">ResolveRuntimePackAssets - <_ScrambleDotnetJsFileNameAfterThisTarget Condition="'$(UsingBrowserRuntimeWorkload)' == 'true'">WasmBuildApp - - - Publish - - - - - - - - - - - - $(ResolveStaticWebAssetsInputsDependsOn); - _AddBlazorWasmStaticWebAssets; - - - - _GenerateBuildBlazorBootJson; - $(StaticWebAssetsPrepareForRunDependsOn) - - - - $(ResolvePublishStaticWebAssetsDependsOn); - ProcessPublishFilesForBlazor; - ComputeBlazorExtensions; - _AddPublishBlazorBootJsonToStaticWebAssets; - - - - $(GenerateStaticWebAssetsPublishManifestDependsOn); - GeneratePublishBlazorBootJson; - - - - - - - - - - <_DotNetJsVersion>$(BundledNETCoreAppPackageVersion) - <_DotNetJsVersion Condition="'$(RuntimeFrameworkVersion)' != ''">$(RuntimeFrameworkVersion) - <_BlazorDotnetJsFileName>dotnet.$(_DotNetJsVersion).js - - - - <_DotNetJsItem Remove="@(_DotNetJsItem)" /> - <_DotNetJsItem Include="@(WasmNativeAsset)" Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js'"> - _framework/$(_BlazorDotnetJsFileName) - native - true - - - - - <_DotnetJsStaticWebAssetCandidate Remove="@(_DotnetJsStaticWebAssetCandidate)" /> - <_DotnetJsCopyCandidates Remove="@(_DotnetJsCopyCandidates)" /> - - - - - - - - - - - - <_DotnetJsStaticWebAsset Include="@(_DotnetJsStaticWebAssetCandidate->'%(ContentRoot)_framework\dotnet.js')" /> - <_BlazorStaticWebAsset Include="@(_DotnetJsStaticWebAsset)" /> - - - - - - - <_DotNetJsVersion>$(BundledNETCoreAppPackageVersion) - <_DotNetJsVersion Condition="'$(RuntimeFrameworkVersion)' != ''">$(RuntimeFrameworkVersion) - <_BlazorDotnetJsFileName>dotnet.$(_DotNetJsVersion).js - - - - <_DotNetJsItem Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.DestinationSubPath)' == 'dotnet.js' AND '%(ReferenceCopyLocalPaths.AssetType)' == 'native'"> - _framework/$(_BlazorDotnetJsFileName) - - - - - <_DotNetJsItem Remove="@(_DotNetJsItem)" /> - <_DotNetJsItem Include="@(WasmNativeAsset)" Condition="'%(WasmNativeAsset.FileName)%(WasmNativeAsset.Extension)' == 'dotnet.js'"> - _framework/$(_BlazorDotnetJsFileName) - native - true - - - - - - - - - - - - - <_DotnetJsStaticWebAsset Include="@(_DotnetJsStaticWebAssetCandidate->'%(ContentRoot)_framework\dotnet.js')" /> - <_BlazorStaticWebAsset Include="@(_DotnetJsStaticWebAsset)" /> - - - - - - - - - - - - - - - - <_BlazorEnableTimeZoneSupport>$(BlazorEnableTimeZoneSupport) - <_BlazorEnableTimeZoneSupport Condition="'$(_BlazorEnableTimeZoneSupport)' == ''">true - <_BlazorInvariantGlobalization>$(InvariantGlobalization) - <_BlazorInvariantGlobalization Condition="'$(_BlazorInvariantGlobalization)' == ''">true - <_BlazorCopyOutputSymbolsToOutputDirectory>$(CopyOutputSymbolsToOutputDirectory) - <_BlazorCopyOutputSymbolsToOutputDirectory Condition="'$(_BlazorCopyOutputSymbolsToOutputDirectory)'==''">true - <_BlazorWebAssemblyLoadAllGlobalizationData>$(BlazorWebAssemblyLoadAllGlobalizationData) - <_BlazorWebAssemblyLoadAllGlobalizationData Condition="'$(_BlazorWebAssemblyLoadAllGlobalizationData)' == ''">false - - - $(OutputPath)$(PublishDirName)\ - - - - - - <_BlazorJSFile Include="$(BlazorWebAssemblyJSPath)" /> - <_BlazorJSFile Include="$(BlazorWebAssemblyJSMapPath)" Condition="Exists('$(BlazorWebAssemblyJSMapPath)')" /> - <_BlazorJsFile> - _framework/%(Filename)%(Extension) - - - - - - <_BlazorConfigFileCandidates Include="@(StaticWebAsset)" Condition="'%(SourceType)' == 'Discovered'" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_BlazorBuildGZipCompressDirectory>$(IntermediateOutputPath)build-gz\ - - - - - - - - - - - - - - - - <_BlazorBuildGZipCompressedFile> - %(RelatedAsset) - - - <_BlazorGzipStaticWebAsset Include="@(_BlazorBuildGZipCompressedFile->'%(FullPath)')" /> - - - - <_BlazorBuildBootJsonPath>$(IntermediateOutputPath)blazor.boot.json - - - - <_BuildBlazorBootJson - Include="$(_BlazorBuildBootJsonPath)" - RelativePath="_framework/blazor.boot.json" /> - - - - - - - - - - - - - - - - - - - - <_BlazorBuildBootJsonPath>$(IntermediateOutputPath)blazor.boot.json - <_BlazorWebAssemblyLoadAllGlobalizationData Condition="'$(BlazorWebAssemblyLoadAllGlobalizationData)' == ''">false - - - - <_BlazorJsModuleCandidatesForBuild - Include="@(StaticWebAsset)" - Condition="'%(StaticWebAsset.AssetTraitName)' == 'JSModule' and '%(StaticWebAsset.AssetTraitValue)' == 'JSLibraryModule' and '%(AssetKind)' != 'Publish'" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_BlazorTypeGranularTrimmerDescriptorFile>$(IntermediateOutputPath)typegranularity.trimmerdescriptor.xml - - - - <_BlazorTypeGranularAssembly - Include="@(ManagedAssemblyToLink)" - Condition="'%(Extension)' == '.dll' AND $([System.String]::Copy('%(Filename)').StartsWith('Microsoft.AspNetCore.'))"> - false - all - - - - - - - - - - - - - - - - - <_BlazorAotEnabled>$(UsingBrowserRuntimeWorkload) - <_BlazorAotEnabled Condition="'$(_BlazorAotEnabled)' == ''">false - <_BlazorLinkerEnabled>$(PublishTrimmed) - <_BlazorLinkerEnabled Condition="'$(_BlazorLinkerEnabled)' == ''">true - - - - - - <_BlazorPublishPrefilteredAssets - Include="@(StaticWebAsset)" - Condition="'%(StaticWebAsset.AssetTraitName)' == 'BlazorWebAssemblyResource' or '%(StaticWebAsset.AssetTraitName)' == 'Culture' or '%(AssetRole)' == 'Alternative'" /> - - - - - - - - - - - - - - - - - <_BlazorExtensionsCandidate Include="@(BlazorPublishExtension->'%(FullPath)')"> - $(PackageId) - Computed - $(PublishDir)wwwroot - $(StaticWebAssetBasePath) - %(BlazorPublishExtension.RelativePath) - Publish - All - Primary - BlazorWebAssemblyResource - extension:%(BlazorPublishExtension.ExtensionName) - Never - PreserveNewest - %(BlazorPublishExtension.Identity) - - - - - - - - - - - - - - - - - - - <_PublishBlazorBootJson - Include="$(IntermediateOutputPath)blazor.publish.boot.json" - RelativePath="_framework/blazor.boot.json" /> - - - - - - - - - - - <_BlazorPublishAsset - Include="@(StaticWebAsset)" - Condition="'%(AssetKind)' != 'Build' and '%(StaticWebAsset.AssetTraitValue)' != 'manifest' and ('%(StaticWebAsset.AssetTraitName)' == 'BlazorWebAssemblyResource' or '%(StaticWebAsset.AssetTraitName)' == 'Culture') and '%(StaticWebAsset.AssetTraitValue)' != 'boot'" /> - - <_BlazorPublishConfigFile - Include="@(StaticWebAsset)" - Condition="'%(StaticWebAsset.AssetTraitName)' == 'BlazorWebAssemblyResource' and '%(StaticWebAsset.AssetTraitValue)' == 'settings'"/> - - <_BlazorJsModuleCandidatesForPublish - Include="@(StaticWebAsset)" - Condition="'%(StaticWebAsset.AssetTraitName)' == 'JSModule' and '%(StaticWebAsset.AssetTraitValue)' == 'JSLibraryModule' and '%(AssetKind)' != 'Build'" /> - - - <_BlazorPublishAsset Remove="@(_BlazorExtensionsCandidatesForPublish)" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - <_CompressedFileOutputPath>$(IntermediateOutputPath)compress\ - <_BlazorWebAssemblyBrotliIncremental>true - - - - <_DotNetHostDirectory>$(NetCoreRoot) - <_DotNetHostFileName>dotnet - <_DotNetHostFileName Condition="'$(OS)' == 'Windows_NT'">dotnet.exe - - - - - - - - <_GzipFileToCompressForPublish Include="@(StaticWebAsset)" - Condition="'%(AssetKind)' != 'Build' and ('%(StaticWebAsset.AssetTraitName)' == 'BlazorWebAssemblyResource' or '%(StaticWebAsset.AssetTraitName)' == 'Culture')" > - %(Identity) - Alternative - Content-Encoding - gzip - - - <_BrotliFileToCompressForPublish Include="@(_GzipFileToCompressForPublish)" Condition="'%(AssetKind)' != 'Build'"> - br - - - - <_AlreadyGzipCompressedAssets - Include="@(StaticWebAsset)" - Condition="'%(AssetKind)' != 'Build' and ('%(StaticWebAsset.AssetTraitName)' == 'Content-Encoding' and '%(StaticWebAsset.AssetTraitValue)' == 'gzip')" /> - <_GzipFileToCompressForPublish Remove="@(_AlreadyGzipCompressedAssets->'%(RelatedAsset)')" /> - - - - - - - - - - - - - - - - <_BlazorPublishGZipCompressedFile> - %(RelatedAsset) - - <_BlazorPublishBrotliCompressedFile> - %(RelatedAsset) - - - - - - - - diff --git a/src/mono/wasm/build/WasmApp.Native.targets b/src/mono/wasm/build/WasmApp.Native.targets index e4bb5ffb278368..4c74fdde056b9a 100644 --- a/src/mono/wasm/build/WasmApp.Native.targets +++ b/src/mono/wasm/build/WasmApp.Native.targets @@ -143,6 +143,7 @@ <_MonoAotCrossCompilerPath>@(MonoAotCrossCompiler->WithMetadataValue('RuntimeIdentifier','browser-wasm')) <_EmccDefaultFlagsRsp>$([MSBuild]::NormalizePath($(_WasmRuntimePackSrcDir), 'emcc-default.rsp')) + false true true $(WasmBuildNative) @@ -156,8 +157,7 @@ <_EmccAssertionLevelDefault>0 <_EmccOptimizationFlagDefault Condition="'$(_WasmDevel)' == 'true'">-O0 -s ASSERTIONS=$(_EmccAssertionLevelDefault) - <_EmccOptimizationFlagDefault Condition="'$(_EmccOptimizationFlagDefault)' == '' and '$(OS)' != 'Windows_NT' and '$(Configuration)' == 'Debug'">-Os - <_EmccOptimizationFlagDefault Condition="'$(_EmccOptimizationFlagDefault)' == '' and '$(Configuration)' != 'Debug'">-Oz + <_EmccOptimizationFlagDefault Condition="'$(_EmccOptimizationFlagDefault)' == '' and '$(Configuration)' == 'Debug' and '$(WasmBuildingForNestedPublish)' != 'true'">-O1 <_EmccOptimizationFlagDefault Condition="'$(_EmccOptimizationFlagDefault)' == ''">-Oz $(_EmccOptimizationFlagDefault) @@ -206,6 +206,9 @@ <_EmccLDFlags Include="@(_EmccCommonFlags)" /> <_EmccLDFlags Include="-s TOTAL_MEMORY=$(EmccTotalMemory)" /> + + <_EmccLDFlags Include="-s ERROR_ON_UNDEFINED_SYMBOLS=0" Condition="'$(WasmBuildingForNestedPublish)' != 'true'" /> + <_DriverCDependencies Include="$(_WasmPInvokeHPath);$(_WasmICallTablePath)" /> <_DriverCDependencies Include="$(_DriverGenCPath)" Condition="'$(_DriverGenCNeeded)' == 'true'" /> @@ -297,7 +300,8 @@ Inputs="@(_BitcodeFile);$(_EmccDefaultFlagsRsp);$(_EmccCompileBitcodeRsp)" Outputs="@(_BitcodeFile->'%(ObjectFile)')" Condition="'$(_WasmShouldAOT)' == 'true' and @(_BitcodeFile->Count()) > 0" - DependsOnTargets="_WasmWriteRspForCompilingBitcode"> + DependsOnTargets="_WasmWriteRspForCompilingBitcode" + Returns="@(FileWrites)"> <_BitCodeFile Dependencies="%(_BitCodeFile.Dependencies);$(_EmccDefaultFlagsRsp);$(_EmccCompileBitcodeRsp)" /> @@ -363,7 +367,8 @@ + DependsOnTargets="_WasmSelectRuntimeComponentsForLinking;_WasmCompileAssemblyBitCodeFilesForAOT;_WasmWriteRspFilesForLinking" + Returns="@(FileWrites)" > diff --git a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs index 59f8edae779585..230e8ce3664729 100644 --- a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs +++ b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs @@ -325,6 +325,13 @@ private bool ProcessAndValidateArguments() throw new LogAsErrorException($"'{nameof(OutputType)}=Library' can not be used with '{nameof(UseStaticLinking)}=true'."); } + foreach (var asmItem in Assemblies) + { + string? fullPath = asmItem.GetMetadata("FullPath"); + if (!File.Exists(fullPath)) + throw new LogAsErrorException($"Could not find {fullPath} to AOT"); + } + return !Log.HasLoggedErrors; } @@ -339,6 +346,12 @@ public override bool Execute() Log.LogError(laee.Message); return false; } + finally + { + if (_cache != null && _cache.Save(CacheFilePath!)) + _fileWrites.Add(CacheFilePath!); + FileWrites = _fileWrites.ToArray(); + } } private bool ExecuteInternal() @@ -347,6 +360,7 @@ private bool ExecuteInternal() return false; _assembliesToCompile = EnsureAndGetAssembliesInTheSameDir(Assemblies); + _assembliesToCompile = FilterAssemblies(_assembliesToCompile); if (!string.IsNullOrEmpty(AotModulesTablePath) && !GenerateAotModulesTable(_assembliesToCompile, Profilers, AotModulesTablePath)) return false; @@ -372,42 +386,29 @@ private bool ExecuteInternal() } else { - int allowedParallelism = Math.Min(_assembliesToCompile.Count, Environment.ProcessorCount); + int allowedParallelism = DisableParallelAot ? 1 : Math.Min(_assembliesToCompile.Count, Environment.ProcessorCount); if (BuildEngine is IBuildEngine9 be9) allowedParallelism = be9.RequestCores(allowedParallelism); - if (DisableParallelAot || allowedParallelism == 1) + ParallelLoopResult result = Parallel.ForEach( + argsList, + new ParallelOptions { MaxDegreeOfParallelism = allowedParallelism }, + (args, state) => PrecompileLibraryParallel(args, state)); + + Log.LogMessage(MessageImportance.High, $"result: {result.IsCompleted}"); + if (result.IsCompleted) { - foreach (var args in argsList) - { - if (!PrecompileLibrarySerial(args)) - return !Log.HasLoggedErrors; - } + int numUnchanged = _totalNumAssemblies - _numCompiled; + if (numUnchanged > 0 && numUnchanged != _totalNumAssemblies) + Log.LogMessage(MessageImportance.High, $"[{numUnchanged}/{_totalNumAssemblies}] skipped unchanged assemblies."); } - else + else if (!Log.HasLoggedErrors) { - ParallelLoopResult result = Parallel.ForEach( - argsList, - new ParallelOptions { MaxDegreeOfParallelism = allowedParallelism }, - (args, state) => PrecompileLibraryParallel(args, state)); - - if (!result.IsCompleted) - { - return false; - } + Log.LogError($"Precompiling failed due to unknown reasons. Check log for more info"); } - - int numUnchanged = _totalNumAssemblies - _numCompiled; - if (numUnchanged > 0 && numUnchanged != _totalNumAssemblies) - Log.LogMessage(MessageImportance.High, $"[{numUnchanged}/{_totalNumAssemblies}] skipped unchanged assemblies."); } CompiledAssemblies = ConvertAssembliesDictToOrderedList(compiledAssemblies, _assembliesToCompile).ToArray(); - - if (_cache.Save(CacheFilePath!)) - _fileWrites.Add(CacheFilePath!); - FileWrites = _fileWrites.ToArray(); - return !Log.HasLoggedErrors; } @@ -428,31 +429,44 @@ static bool IsNewerThanOutput(string inFile, string outFile) (File.GetLastWriteTimeUtc(inFile) > File.GetLastWriteTimeUtc(outFile)); } - private IList EnsureAndGetAssembliesInTheSameDir(ITaskItem[] originalAssemblies) + private IList FilterAssemblies(IEnumerable assemblies) { List filteredAssemblies = new(); - string firstAsmDir = Path.GetDirectoryName(originalAssemblies[0].GetMetadata("FullPath")) ?? string.Empty; - bool allInSameDir = true; - - foreach (var origAsm in originalAssemblies) + foreach (var asmItem in assemblies) { - if (allInSameDir && Path.GetDirectoryName(origAsm.GetMetadata("FullPath")) != firstAsmDir) - allInSameDir = false; - - if (ShouldSkip(origAsm)) + if (ShouldSkip(asmItem)) { if (parsedAotMode == MonoAotMode.LLVMOnly) - throw new LogAsErrorException($"Building in AOTMode=LLVMonly is not compatible with excluding any assemblies for AOT. Excluded assembly: {origAsm.ItemSpec}"); + throw new LogAsErrorException($"Building in AOTMode=LLVMonly is not compatible with excluding any assemblies for AOT. Excluded assembly: {asmItem.ItemSpec}"); - Log.LogMessage(MessageImportance.Low, $"Skipping {origAsm.ItemSpec} because it has %(AOT_InternalForceToInterpret)=true"); + Log.LogMessage(MessageImportance.Low, $"Skipping {asmItem.ItemSpec} because it has %(AOT_InternalForceToInterpret)=true"); continue; } - filteredAssemblies.Add(origAsm); + string assemblyPath = asmItem.GetMetadata("FullPath"); + using var assemblyFile = File.OpenRead(assemblyPath); + using PEReader reader = new(assemblyFile, PEStreamOptions.Default); + if (!reader.HasMetadata) + { + Log.LogWarning($"Skipping unmanaged {assemblyPath} for AOT"); + continue; + } + + filteredAssemblies.Add(asmItem); } + return filteredAssemblies; + + static bool ShouldSkip(ITaskItem asmItem) + => bool.TryParse(asmItem.GetMetadata("AOT_InternalForceToInterpret"), out bool skip) && skip; + } + + private IList EnsureAndGetAssembliesInTheSameDir(IList assemblies) + { + string firstAsmDir = Path.GetDirectoryName(assemblies.First().GetMetadata("FullPath")) ?? string.Empty; + bool allInSameDir = assemblies.All(asm => Path.GetDirectoryName(asm.GetMetadata("FullPath")) == firstAsmDir); if (allInSameDir) - return filteredAssemblies; + return assemblies; // Copy to aot-in @@ -460,28 +474,23 @@ private IList EnsureAndGetAssembliesInTheSameDir(ITaskItem[] original Directory.CreateDirectory(aotInPath); List newAssemblies = new(); - foreach (var origAsm in originalAssemblies) + foreach (var asmItem in assemblies) { - string asmPath = origAsm.GetMetadata("FullPath"); + string asmPath = asmItem.GetMetadata("FullPath"); string newPath = Path.Combine(aotInPath, Path.GetFileName(asmPath)); // FIXME: delete files not in originalAssemblies though // FIXME: or .. just delete the whole dir? if (Utils.CopyIfDifferent(asmPath, newPath, useHash: true)) Log.LogMessage(MessageImportance.Low, $"Copying {asmPath} to {newPath}"); + _fileWrites.Add(newPath); - if (!ShouldSkip(origAsm)) - { - ITaskItem newAsm = new TaskItem(newPath); - origAsm.CopyMetadataTo(newAsm); - newAssemblies.Add(newAsm); - } + ITaskItem newAsm = new TaskItem(newPath); + asmItem.CopyMetadataTo(newAsm); + newAssemblies.Add(newAsm); } return newAssemblies; - - static bool ShouldSkip(ITaskItem asmItem) - => bool.TryParse(asmItem.GetMetadata("AOT_InternalForceToInterpret"), out bool skip) && skip; } private PrecompileArguments GetPrecompileArgumentsFor(ITaskItem assemblyItem, string? monoPaths) @@ -773,28 +782,6 @@ private bool PrecompileLibrary(PrecompileArguments args) return true; } - private bool PrecompileLibrarySerial(PrecompileArguments args) - { - try - { - if (PrecompileLibrary(args)) - return true; - } - catch (LogAsErrorException laee) - { - Log.LogError($"Precompile failed for {args.AOTAssembly}: {laee.Message}"); - } - catch (Exception ex) - { - if (Log.HasLoggedErrors) - Log.LogMessage(MessageImportance.Low, $"Precompile failed for {args.AOTAssembly}: {ex}"); - else - Log.LogError($"Precompile failed for {args.AOTAssembly}: {ex}"); - } - - return false; - } - private void PrecompileLibraryParallel(PrecompileArguments args, ParallelLoopState state) { try @@ -804,14 +791,14 @@ private void PrecompileLibraryParallel(PrecompileArguments args, ParallelLoopSta } catch (LogAsErrorException laee) { - Log.LogError($"Precompile failed for {args.AOTAssembly}: {laee.Message}"); + Log.LogError($"Precompiling failed for {args.AOTAssembly}: {laee.Message}"); } catch (Exception ex) { if (Log.HasLoggedErrors) Log.LogMessage(MessageImportance.Low, $"Precompile failed for {args.AOTAssembly}: {ex}"); else - Log.LogError($"Precompile failed for {args.AOTAssembly}: {ex}"); + Log.LogError($"Precompiling failed for {args.AOTAssembly}: {ex}"); } state.Break(); @@ -940,10 +927,8 @@ private static IList ConvertAssembliesDictToOrderedList(ConcurrentDic List outItems = new(originalAssemblies.Count); foreach (ITaskItem item in originalAssemblies) { - if (!dict.TryGetValue(item.GetMetadata("FullPath"), out ITaskItem? dictItem)) - throw new LogAsErrorException($"Bug: Could not find item in the dict with key {item.ItemSpec}"); - - outItems.Add(dictItem); + if (dict.TryGetValue(item.GetMetadata("FullPath"), out ITaskItem? dictItem)) + outItems.Add(dictItem); } return outItems; } @@ -993,7 +978,7 @@ public FileCache(string? cacheFilePath, TaskLoggingHelper log) } _oldCache ??= new(); - _newCache = new(); + _newCache = new(_oldCache.FileHashes); } public bool ShouldCopy(ProxyFile proxyFile, [NotNullWhen(true)] out string? cause) @@ -1110,6 +1095,10 @@ public enum MonoAotModulesTableLanguage internal class CompilerCache { + public CompilerCache() => FileHashes = new(); + public CompilerCache(IDictionary oldHashes) + => FileHashes = new(oldHashes); + [JsonPropertyName("file_hashes")] - public ConcurrentDictionary FileHashes { get; set; } = new(); + public ConcurrentDictionary FileHashes { get; set; } } diff --git a/src/tasks/WasmAppBuilder/EmccCompile.cs b/src/tasks/WasmAppBuilder/EmccCompile.cs index a5d6af6c3b473a..66d4743d81b4d4 100644 --- a/src/tasks/WasmAppBuilder/EmccCompile.cs +++ b/src/tasks/WasmAppBuilder/EmccCompile.cs @@ -95,6 +95,7 @@ private bool ExecuteActual() if (!ShouldCompile(srcFile, objFile, depFiles, out string reason)) { Log.LogMessage(MessageImportance.Low, $"Skipping {srcFile} because {reason}."); + outputItems.Add(CreateOutputItemFor(srcFile, objFile)); } else { @@ -107,7 +108,8 @@ private bool ExecuteActual() if (_numCompiled == _totalFiles) { // nothing to do! - return true; + OutputFiles = outputItems.ToArray(); + return !Log.HasLoggedErrors; } if (_numCompiled > 0) @@ -123,31 +125,20 @@ private bool ExecuteActual() _tempPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); Directory.CreateDirectory(_tempPath); - int allowedParallelism = Math.Min(SourceFiles.Length, Environment.ProcessorCount); + int allowedParallelism = DisableParallelCompile ? 1 : Math.Min(SourceFiles.Length, Environment.ProcessorCount); if (BuildEngine is IBuildEngine9 be9) allowedParallelism = be9.RequestCores(allowedParallelism); - if (DisableParallelCompile || allowedParallelism == 1) - { - foreach ((string srcFile, string outFile) in filesToCompile) - { - if (!ProcessSourceFile(srcFile, outFile)) - return false; - } - } - else + ParallelLoopResult result = Parallel.ForEach(filesToCompile, + new ParallelOptions { MaxDegreeOfParallelism = allowedParallelism }, + (toCompile, state) => { - ParallelLoopResult result = Parallel.ForEach(filesToCompile, - new ParallelOptions { MaxDegreeOfParallelism = allowedParallelism }, - (toCompile, state) => - { - if (!ProcessSourceFile(toCompile.Item1, toCompile.Item2)) - state.Stop(); - }); + if (!ProcessSourceFile(toCompile.Item1, toCompile.Item2)) + state.Stop(); + }); - if (!result.IsCompleted && !Log.HasLoggedErrors) - Log.LogError("Unknown failure occured while compiling. Check logs to get more details."); - } + if (!result.IsCompleted && !Log.HasLoggedErrors) + Log.LogError("Unknown failure occured while compiling. Check logs to get more details."); if (!Log.HasLoggedErrors) { @@ -200,9 +191,7 @@ bool ProcessSourceFile(string srcFile, string objFile) else Log.LogMessage(MessageImportance.Low, $"Copied {tmpObjFile} to {objFile}"); - ITaskItem newItem = new TaskItem(objFile); - newItem.SetMetadata("SourceFile", srcFile); - outputItems.Add(newItem); + outputItems.Add(CreateOutputItemFor(srcFile, objFile)); int count = Interlocked.Increment(ref _numCompiled); Log.LogMessage(MessageImportance.High, $"[{count}/{_totalFiles}] {Path.GetFileName(srcFile)} -> {Path.GetFileName(objFile)} [took {elapsedSecs:F}s]"); @@ -219,6 +208,13 @@ bool ProcessSourceFile(string srcFile, string objFile) File.Delete(tmpObjFile); } } + + ITaskItem CreateOutputItemFor(string srcFile, string objFile) + { + ITaskItem newItem = new TaskItem(objFile); + newItem.SetMetadata("SourceFile", srcFile); + return newItem; + } } private bool ShouldCompile(string srcFile, string objFile, string[] depFiles, out string reason) diff --git a/src/tasks/WasmAppBuilder/IcallTableGenerator.cs b/src/tasks/WasmAppBuilder/IcallTableGenerator.cs index cc4d8af1ac7faa..a9f5f95161575d 100644 --- a/src/tasks/WasmAppBuilder/IcallTableGenerator.cs +++ b/src/tasks/WasmAppBuilder/IcallTableGenerator.cs @@ -45,7 +45,7 @@ public void GenIcallTable(string runtimeIcallTableFile, string[] assemblies) { ReadTable (runtimeIcallTableFile); var resolver = new PathAssemblyResolver(assemblies); - var mlc = new MetadataLoadContext(resolver, "System.Private.CoreLib"); + using var mlc = new MetadataLoadContext(resolver, "System.Private.CoreLib"); foreach (var aname in assemblies) { var a = mlc.LoadFromAssemblyPath(aname); @@ -107,11 +107,8 @@ private void EmitTable (StreamWriter w) // Read the icall table generated by mono --print-icall-table private void ReadTable (string filename) { - JsonDocument json; - using (var stream = File.Open (filename, FileMode.Open)) - { - json = JsonDocument.Parse (stream); - } + using var stream = File.OpenRead (filename); + using JsonDocument json = JsonDocument.Parse (stream); var arr = json.RootElement; foreach (var v in arr.EnumerateArray ()) diff --git a/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs b/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs index 825de67d6e2704..feb2dda21d8528 100644 --- a/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs +++ b/src/tasks/WasmAppBuilder/PInvokeTableGenerator.cs @@ -3,13 +3,10 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; -using System.Text.Json; using System.Reflection; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -65,7 +62,7 @@ public void GenPInvokeTable(string[] pinvokeModules, string[] assemblies) var callbacks = new List(); var resolver = new PathAssemblyResolver(assemblies); - var mlc = new MetadataLoadContext(resolver, "System.Private.CoreLib"); + using var mlc = new MetadataLoadContext(resolver, "System.Private.CoreLib"); foreach (var aname in assemblies) { var a = mlc.LoadFromAssemblyPath(aname); @@ -121,25 +118,40 @@ private void EmitPInvokeTable(StreamWriter w, Dictionary modules w.WriteLine("// GENERATED FILE, DO NOT MODIFY"); w.WriteLine(); - var decls = new HashSet(); - foreach (var pinvoke in pinvokes.OrderBy(l => l.EntryPoint)) + var pinvokesGroupedByEntryPoint = pinvokes + .Where(l => modules.ContainsKey(l.Module)) + .OrderBy(l => l.EntryPoint) + .GroupBy(l => l.EntryPoint); + + var comparer = new PInvokeComparer(); + foreach (IGrouping group in pinvokesGroupedByEntryPoint) { - if (modules.ContainsKey(pinvoke.Module)) { - try - { - var decl = GenPInvokeDecl(pinvoke); - if (decls.Contains(decl)) - continue; + var candidates = group.Distinct(comparer).ToArray(); + PInvoke first = candidates[0]; + if (ShouldTreatAsVariadic(candidates)) + { + string imports = string.Join(Environment.NewLine, + candidates.Select( + p => $" {p.Method} (in [{p.Method.DeclaringType?.Assembly.GetName().Name}] {p.Method.DeclaringType})")); + Log.LogWarning($"Found a native function ({first.EntryPoint}) with varargs in {first.Module}." + + " Calling such functions is not supported, and will fail at runtime." + + $" Managed DllImports: {Environment.NewLine}{imports}"); - w.WriteLine(decl); - decls.Add(decl); - } - catch (NotSupportedException) - { - // See the FIXME in GenPInvokeDecl - Log.LogWarning($"Cannot handle function pointer arguments/return value in pinvoke method '{pinvoke.Method}' in type '{pinvoke.Method.DeclaringType}'."); - pinvoke.Skip = true; - } + foreach (var c in candidates) + c.Skip = true; + + continue; + } + + var decls = new HashSet(); + foreach (var candidate in candidates) + { + var decl = GenPInvokeDecl(candidate); + if (decl == null || decls.Contains(decl)) + continue; + + w.WriteLine(decl); + decls.Add(decl); } } @@ -186,6 +198,22 @@ static string ModuleNameToId(string name) return fixedName; } + + static bool ShouldTreatAsVariadic(PInvoke[] candidates) + { + if (candidates.Length < 2) + return false; + + PInvoke first = candidates[0]; + if (TryIsMethodGetParametersUnsupported(first.Method, out _)) + return false; + + int firstNumArgs = first.Method.GetParameters().Length; + return candidates + .Skip(1) + .Any(c => !TryIsMethodGetParametersUnsupported(c.Method, out _) && + c.Method.GetParameters().Length != firstNumArgs); + } } private string MapType (Type t) @@ -205,7 +233,29 @@ private string MapType (Type t) return "int"; } - private string GenPInvokeDecl(PInvoke pinvoke) + // FIXME: System.Reflection.MetadataLoadContext can't decode function pointer types + // https://github.com/dotnet/runtime/issues/43791 + private static bool TryIsMethodGetParametersUnsupported(MethodInfo method, [NotNullWhen(true)] out string? reason) + { + try + { + method.GetParameters(); + } + catch (NotSupportedException nse) + { + reason = nse.Message; + return true; + } + catch + { + // not concerned with other exceptions + } + + reason = null; + return false; + } + + private string? GenPInvokeDecl(PInvoke pinvoke) { var sb = new StringBuilder(); var method = pinvoke.Method; @@ -215,6 +265,14 @@ private string GenPInvokeDecl(PInvoke pinvoke) sb.Append($"int {pinvoke.EntryPoint} (int, int, int, int, int);"); return sb.ToString(); } + + if (TryIsMethodGetParametersUnsupported(pinvoke.Method, out string? reason)) + { + Log.LogWarning($"Skipping the following DllImport because '{reason}'. {Environment.NewLine} {pinvoke.Method}"); + pinvoke.Skip = true; + return null; + } + sb.Append(MapType(method.ReturnType)); sb.Append($" {pinvoke.EntryPoint} ("); int pindex = 0; @@ -366,7 +424,7 @@ private static bool IsBlittable (Type type) private static void Error (string msg) => throw new LogAsErrorException(msg); } -internal class PInvoke +internal class PInvoke : IEquatable { public PInvoke(string entryPoint, string module, MethodInfo method) { @@ -379,6 +437,30 @@ public PInvoke(string entryPoint, string module, MethodInfo method) public string Module; public MethodInfo Method; public bool Skip; + + public bool Equals(PInvoke? other) + => other != null && + string.Equals(EntryPoint, other.EntryPoint, StringComparison.Ordinal) && + string.Equals(Module, other.Module, StringComparison.Ordinal) && + string.Equals(Method.ToString(), other.Method.ToString(), StringComparison.Ordinal); + + public override string ToString() => $"{{ EntryPoint: {EntryPoint}, Module: {Module}, Method: {Method}, Skip: {Skip} }}"; +} + +internal class PInvokeComparer : IEqualityComparer +{ + public bool Equals(PInvoke? x, PInvoke? y) + { + if (x == null && y == null) + return true; + if (x == null || y == null) + return false; + + return x.Equals(y); + } + + public int GetHashCode(PInvoke pinvoke) + => $"{pinvoke.EntryPoint}{pinvoke.Module}{pinvoke.Method}".GetHashCode(); } internal class PInvokeCallback diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs index 687429a3fca482..d4fb7cf49875cf 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmBuildPublishTests.cs @@ -1,9 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.IO; using System.Linq; +using System.Text.RegularExpressions; using Xunit; using Xunit.Abstractions; @@ -171,6 +171,60 @@ public void WithNativeReference_AOTOnCommandLine(string config) BlazorPublish(id, config, expectedFileType: NativeFilesType.Relinked); } + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [InlineData("Debug")] + [InlineData("Release")] + public void WithDllImportInMainAssembly(string config) + { + // Based on https://github.com/dotnet/runtime/issues/59255 + string id = $"blz_dllimp_{config}"; + string projectFile = CreateProjectWithNativeReference(id); + string nativeSource = @" + #include + + extern ""C"" { + int cpp_add(int a, int b) { + return a + b; + } + }"; + + File.WriteAllText(Path.Combine(_projectDir!, "mylib.cpp"), nativeSource); + + string myDllImportCs = @$" + using System.Runtime.InteropServices; + namespace {id}; + + public static class MyDllImports + {{ + [DllImport(""mylib"")] + public static extern int cpp_add(int a, int b); + }}"; + + File.WriteAllText(Path.Combine(_projectDir!, "Pages", "MyDllImport.cs"), myDllImportCs); + + AddItemsPropertiesToProject(projectFile, extraItems: @""); + + BlazorBuild(id, config, expectedFileType: NativeFilesType.Relinked); + CheckNativeFileLinked(forPublish: false); + + BlazorPublish(id, config, expectedFileType: NativeFilesType.Relinked); + CheckNativeFileLinked(forPublish: true); + + void CheckNativeFileLinked(bool forPublish) + { + // very crude way to check that the native file was linked in + // needed because we don't run the blazor app yet + string objBuildDir = Path.Combine(_projectDir!, "obj", config, "net6.0", "wasm", forPublish ? "for-publish" : "for-build"); + string pinvokeTableHPath = Path.Combine(objBuildDir, "pinvoke-table.h"); + Assert.True(File.Exists(pinvokeTableHPath), $"Could not find {pinvokeTableHPath}"); + + string pinvokeTableHContents = File.ReadAllText(pinvokeTableHPath); + string pattern = $"\"cpp_add\".*{id}"; + Assert.True(Regex.IsMatch(pinvokeTableHContents, pattern), + $"Could not find {pattern} in {pinvokeTableHPath}"); + } + } + private string CreateProjectWithNativeReference(string id) { CreateBlazorWasmTemplateProject(id); diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs index 6c7e3ed788be56..727e6589c4c6d2 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/BlazorWasmTests.cs @@ -79,6 +79,7 @@ private CommandResult PublishForRequiresWorkloadTest(string config, string extra [Theory] [InlineData("Debug")] [InlineData("Release")] + [ActiveIssue("https://github.com/dotnet/runtime/issues/59538")] public void Net50Projects_NativeReference(string config) => BuildNet50Project(config, aot: false, expectError: true, @""); @@ -92,6 +93,7 @@ public void Net50Projects_NativeReference(string config) [Theory] [MemberData(nameof(Net50TestData))] + [ActiveIssue("https://github.com/dotnet/runtime/issues/59538")] public void Net50Projects_AOT(string config, bool aot, bool expectError) => BuildNet50Project(config, aot: aot, expectError: expectError); diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs index 3ccf400ee92113..eb306286ba5e0d 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeLibraryTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.IO; using Xunit; using Xunit.Abstractions; diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs index 9047fd062a82e4..cd0d3ac4396349 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NativeRebuildTestsBase.cs @@ -77,6 +77,7 @@ protected string Rebuild(bool nativeRelink, bool invariant, BuildArgs buildArgs, buildArgs = newBuildArgs; _testOutput.WriteLine($"{Environment.NewLine}Rebuilding with no changes ..{Environment.NewLine}"); + Console.WriteLine($"{Environment.NewLine}Rebuilding with no changes ..{Environment.NewLine}"); (_, string output) = BuildProject(buildArgs, id: id, dotnetWasmFromRuntimePack: false, @@ -137,6 +138,18 @@ internal void CompareStat(IDictionary oldStat, IDictionary GetFilesTable(bool unchanged, params string[] baseDirs) + { + var dict = new Dictionary(); + foreach (var baseDir in baseDirs) + { + foreach (var file in Directory.EnumerateFiles(baseDir, "*", new EnumerationOptions { RecurseSubdirectories = true })) + dict[Path.GetFileName(file)] = (file, unchanged); + } + + return dict; + } + internal IDictionary GetFilesTable(BuildArgs buildArgs, BuildPaths paths, bool unchanged) { List files = new() diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs index 96f6d41fa10025..3131dec390ce05 100644 --- a/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/NativeRebuildTests/NoopNativeRebuildTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.IO; using System.Linq; using Wasm.Build.Tests; using Xunit; @@ -33,5 +34,60 @@ public void NoOpRebuildForNativeBuilds(BuildArgs buildArgs, bool nativeRelink, b CompareStat(originalStat, newStat, pathsDict.Values); RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); } + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [InlineData("Debug")] + [InlineData("Release")] + public void BlazorNoopRebuild(string config) + { + string id = $"blz_rebuild_{config}"; + string projectFile = CreateBlazorWasmTemplateProject(id); + AddItemsPropertiesToProject(projectFile, extraProperties: "true"); + + string objDir = Path.Combine(_projectDir!, "obj", config, "net6.0", "wasm"); + + BlazorBuild(id, config, NativeFilesType.Relinked); + File.Move(Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-build.binlog"), + Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-build-first.binlog")); + + var pathsDict = GetFilesTable(true, objDir); + pathsDict.Remove("runtime-icall-table.h"); + var originalStat = StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + + // build again + BlazorBuild(id, config, NativeFilesType.Relinked); + var newStat = StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + + CompareStat(originalStat, newStat, pathsDict.Values); + } + + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [InlineData("Debug")] + [InlineData("Release")] + public void BlazorOnlyLinkRebuild(string config) + { + string id = $"blz_relink_{config}"; + string projectFile = CreateBlazorWasmTemplateProject(id); + AddItemsPropertiesToProject(projectFile, extraProperties: "true"); + + string objDir = Path.Combine(_projectDir!, "obj", config, "net6.0", "wasm"); + + BlazorBuild(id, config, NativeFilesType.Relinked); + File.Move(Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-build.binlog"), + Path.Combine(s_buildEnv.LogRootPath, id, $"{id}-build-first.binlog")); + + var pathsDict = GetFilesTable(true, objDir); + pathsDict.Remove("runtime-icall-table.h"); + pathsDict.UpdateTo(unchanged: false, "dotnet.wasm", "dotnet.js", "emcc-link.rsp"); + + var originalStat = StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + + // build again + BlazorBuild(id, config, NativeFilesType.Relinked, "-p:EmccLinkOptimizationFlag=-O1"); + var newStat = StatFiles(pathsDict.Select(kvp => kvp.Value.fullPath)); + + CompareStat(originalStat, newStat, pathsDict.Values); + } } } diff --git a/src/tests/BuildWasmApps/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs b/src/tests/BuildWasmApps/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs new file mode 100644 index 00000000000000..27783ae85c95ad --- /dev/null +++ b/src/tests/BuildWasmApps/Wasm.Build.Tests/PInvokeTableGeneratorTests.cs @@ -0,0 +1,139 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using Xunit; +using Xunit.Abstractions; + +#nullable enable + +namespace Wasm.Build.Tests +{ + public class PInvokeTableGeneratorTests : BuildTestBase + { + public PInvokeTableGeneratorTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext) + : base(output, buildContext) + { + } + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [BuildAndRun(host: RunHost.V8)] + public void NativeLibraryWithVariadicFunctions(BuildArgs buildArgs, RunHost host, string id) + { + string code = @" + using System; + using System.Runtime.InteropServices; + public class Test + { + public static int Main(string[] args) + { + Console.WriteLine($""Main running""); + if (args.Length > 0) + { + // We don't want to run this, because we can't call variadic functions + Console.WriteLine($""sum_three: {sum_three(7, 14, 21)}""); + Console.WriteLine($""sum_two: {sum_two(3, 6)}""); + Console.WriteLine($""sum_one: {sum_one(5)}""); + } + return 42; + } + + [DllImport(""variadic"", EntryPoint=""sum"")] public static extern int sum_one(int a); + [DllImport(""variadic"", EntryPoint=""sum"")] public static extern int sum_two(int a, int b); + [DllImport(""variadic"", EntryPoint=""sum"")] public static extern int sum_three(int a, int b, int c); + }"; + + (buildArgs, string output) = BuildForVariadicFunctionTests(code, + buildArgs with { ProjectName = $"variadic_{buildArgs.Config}_{id}" }, + id); + Assert.Matches("warning.*native function.*sum.*varargs", output); + Assert.Matches("warning.*sum_(one|two|three)", output); + + output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); + Assert.Contains("Main running", output); + } + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [BuildAndRun(host: RunHost.V8)] + public void DllImportWithFunctionPointersCompilesWithWarning(BuildArgs buildArgs, RunHost host, string id) + { + string code = @" + using System; + using System.Runtime.InteropServices; + public class Test + { + public static int Main() + { + Console.WriteLine($""Main running""); + return 42; + } + + [DllImport(""variadic"", EntryPoint=""sum"")] + public unsafe static extern int using_sum_one(delegate* unmanaged callback); + + [DllImport(""variadic"", EntryPoint=""sum"")] + public static extern int sum_one(int a, int b); + }"; + + (buildArgs, string output) = BuildForVariadicFunctionTests(code, + buildArgs with { ProjectName = $"fnptr_{buildArgs.Config}_{id}" }, + id); + Assert.Matches("warning.*Skipping.*because.*function pointer", output); + Assert.Matches("warning.*using_sum_one", output); + + output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); + Assert.Contains("Main running", output); + } + + [ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))] + [BuildAndRun(host: RunHost.V8)] + public void DllImportWithFunctionPointers_ForVariadicFunction_CompilesWithWarning(BuildArgs buildArgs, RunHost host, string id) + { + string code = @" + using System; + using System.Runtime.InteropServices; + public class Test + { + public static int Main() + { + Console.WriteLine($""Main running""); + return 42; + } + + [DllImport(""variadic"", EntryPoint=""sum"")] + public unsafe static extern int using_sum_one(delegate* unmanaged callback); + }"; + + (buildArgs, string output) = BuildForVariadicFunctionTests(code, + buildArgs with { ProjectName = $"fnptr_variadic_{buildArgs.Config}_{id}" }, + id); + Assert.Matches("warning.*Skipping.*because.*function pointer", output); + Assert.Matches("warning.*using_sum_one", output); + + output = RunAndTestWasmApp(buildArgs, buildDir: _projectDir, expectedExitCode: 42, host: host, id: id); + Assert.Contains("Main running", output); + } + + private (BuildArgs, string) BuildForVariadicFunctionTests(string programText, BuildArgs buildArgs, string id) + { + string filename = "variadic.o"; + buildArgs = ExpandBuildArgs(buildArgs, + extraItems: $"", + extraProperties: "true<_WasmDevel>true"); + + (_, string output) = BuildProject(buildArgs, + initProject: () => + { + File.WriteAllText(Path.Combine(_projectDir!, "Program.cs"), programText); + File.Copy(Path.Combine(BuildEnvironment.TestAssetsPath, "native-libs", filename), + Path.Combine(_projectDir!, filename)); + }, + publish: buildArgs.AOT, + id: id, + dotnetWasmFromRuntimePack: false); + + return (buildArgs, output); + } + } +} diff --git a/src/tests/BuildWasmApps/testassets/native-libs/variadic.c b/src/tests/BuildWasmApps/testassets/native-libs/variadic.c new file mode 100644 index 00000000000000..cd4009439d19da --- /dev/null +++ b/src/tests/BuildWasmApps/testassets/native-libs/variadic.c @@ -0,0 +1,14 @@ +#include + +int sum(int n, ...) +{ + int result = 0; + va_list ptr; + va_start(ptr, n); + + for (int i = 0; i < n; i++) + result += va_arg(ptr, int); + + va_end(ptr); + return result; +} diff --git a/src/tests/BuildWasmApps/testassets/native-libs/variadic.o b/src/tests/BuildWasmApps/testassets/native-libs/variadic.o new file mode 100644 index 00000000000000..b4558ce3519793 Binary files /dev/null and b/src/tests/BuildWasmApps/testassets/native-libs/variadic.o differ