From d52dacd2459bd5a0793296f1f87942f4b83988dc Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 19 Aug 2026 16:15:21 +0200 Subject: [PATCH 1/7] Make zero-based Array2D operations trimming- and AOT-clean Array2D.create and Array2D.init allocated through createBased, which reaches Array.CreateInstance(Type, int[], int[]). That overload is annotated RequiresDynamicCode, so every app that called them took an IL3050 AOT analysis warning even though the arrays involved are always zero-based. They now allocate with zeroCreate and fill in place, and rebase benefits through init. zeroCreateBased keeps the based-array path, but on net9.0 and up it uses Array.CreateInstanceFromArrayType. Passing the array type rather than the element type lets AOT compilation see the concrete instantiation, so that overload carries no RequiresDynamicCode. This clears the same warning from map, mapi, copy, createBased and initBased on FSharp.Core's net asset. This removes a warning; it does not add capability. NativeAOT does not support non-zero-based arrays at all, and allocating one still throws PlatformNotSupportedException. The NativeAOT test gains an Array2D mini program covering both cases. It targets FSharpNetCoreProductTargetFramework and resolves FSharp.Core's shipped net asset, so its publish guards the CreateInstanceFromArrayType change. That asset stays warning-free whether or not create and init route through the based path, so those two are pinned separately by the CodeGenRenamings01 IL baselines, which now assert Array2DModule::Create and ::Initialize instead of their based counterparts. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b61ef94c-e4e0-4424-a645-a08a8b463d67 --- docs/release-notes/.FSharp.Core/11.0.100.md | 1 + src/FSharp.Core/array2.fs | 24 +- tests/AheadOfTime/NativeAOT/Array2DProgram.fs | 60 ++++ .../NativeAOT/NativeAOT_Test.fsproj | 3 +- tests/AheadOfTime/NativeAOT/Program.fs | 3 +- tests/AheadOfTime/NativeAOT/check.ps1 | 37 ++- .../EmittedIL/Misc/CodeGenRenamings01.fs | 4 + ...ealInternalSignatureOff.OptimizeOff.il.bsl | 52 +++- ...RealInternalSignatureOff.OptimizeOn.il.bsl | 291 +++++++++++------- ...RealInternalSignatureOn.OptimizeOff.il.bsl | 52 +++- ....RealInternalSignatureOn.OptimizeOn.il.bsl | 291 +++++++++++------- .../Array2Module.fs | 41 ++- 12 files changed, 586 insertions(+), 273 deletions(-) create mode 100644 tests/AheadOfTime/NativeAOT/Array2DProgram.fs diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md index 521901da210..368a2717f2e 100644 --- a/docs/release-notes/.FSharp.Core/11.0.100.md +++ b/docs/release-notes/.FSharp.Core/11.0.100.md @@ -5,6 +5,7 @@ ### Fixed * Mirror compiler-semantic attributes (e.g. `[]`, `[]`) in `.fsi` signature files to match `.fs` implementations. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880)) +* Remove the `IL3050` dynamic-code warning from `Array2D`: `create` and `init` no longer allocate through a based-array path, and the `net10.0` asset allocates based arrays with `Array.CreateInstanceFromArrayType`. ([Language Suggestion #1454](https://github.com/fsharp/fslang-suggestions/issues/1454), [PR #20338](https://github.com/dotnet/fsharp/pull/20338)) * Fix `Array.exists2` documentation examples to use equal-length arrays; the previous examples would throw `ArgumentException` at runtime instead of returning the documented `false`/`true` values. ([PR #19672](https://github.com/dotnet/fsharp/pull/19672)) * Move `Async.StartChild` to the "Starting Async Computations" docs category alongside `Async.StartChildAsTask`. ([Issue #19667](https://github.com/dotnet/fsharp/issues/19667)) * Add `InlineIfLambda` to `Array.init` ([PR #19869](https://github.com/dotnet/fsharp/pull/19869)) diff --git a/src/FSharp.Core/array2.fs b/src/FSharp.Core/array2.fs index 76d20e74cfc..7f6e7f3555a 100644 --- a/src/FSharp.Core/array2.fs +++ b/src/FSharp.Core/array2.fs @@ -53,7 +53,14 @@ module Array2D = if base1 = 0 && base2 = 0 then zeroCreate length1 length2 else +#if NET9_0_OR_GREATER + // Passing the array type rather than the element type lets trimming and AOT compilation + // see the concrete 'T[,] instantiation, so this overload carries no RequiresDynamicCode + // annotation. + (Array.CreateInstanceFromArrayType(typeof<'T[,]>, [|length1;length2|], [|base1;base2|]) :?> 'T[,]) +#else (Array.CreateInstance(typeof<'T>, [|length1;length2|], [|base1;base2|]) :?> 'T[,]) +#endif [] let createBased base1 base2 length1 length2 (initial:'T) = @@ -73,12 +80,21 @@ module Array2D = array [] - let create length1 length2 (value:'T) = - createBased 0 0 length1 length2 value + let create length1 length2 (value:'T) = + let array = zeroCreate length1 length2 + for i = 0 to length1 - 1 do + for j = 0 to length2 - 1 do + array.[i, j] <- value + array [] - let init length1 length2 initializer = - initBased 0 0 length1 length2 initializer + let init length1 length2 initializer = + let array = zeroCreate length1 length2 + let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt(initializer) + for i = 0 to length1 - 1 do + for j = 0 to length2 - 1 do + array.[i, j] <- f.Invoke(i, j) + array [] let iter action array = diff --git a/tests/AheadOfTime/NativeAOT/Array2DProgram.fs b/tests/AheadOfTime/NativeAOT/Array2DProgram.fs new file mode 100644 index 00000000000..311765cd233 --- /dev/null +++ b/tests/AheadOfTime/NativeAOT/Array2DProgram.fs @@ -0,0 +1,60 @@ +module Array2DProgram + +open System + +// A small grid program, run under NativeAOT so that the publish itself is the assertion. +// check.ps1 explains which calls are guarded and why. + +let private check name (actual: int) (expected: int) = + if actual <> expected then + Console.WriteLine $"FAILED {name}: expected {expected} but got {actual}" + +// Summing through iteri gives every grid below an observable result. Without that, the optimizer +// or ILC could drop an unused allocation and silently take the code this guards out of the +// analyzed program. +let private sum (grid: int[,]) = + let total = ref 0 + grid |> Array2D.iteri (fun _ _ v -> total.Value <- total.Value + v) + total.Value + +let run (argv: string[]) = + // An empty argv means 3x4, but neither the optimizer nor ILC can prove that, so the + // allocations cannot be folded to constants. The expected values below are the ones for an + // empty argv, so check.ps1 has to run the app without arguments. + let rows = 3 + argv.Length + let cols = 4 + argv.Length + + let table = Array2D.init rows cols (fun i j -> (i + 1) * (j + 1)) + check "init.rows" (Array2D.length1 table) 3 + check "init.cols" (Array2D.length2 table) 4 + check "init" (sum table) 60 + + let board = Array2D.create rows cols 2 + check "create" (sum board) 24 + + Array2D.blit table 0 0 board 1 1 2 2 + check "blit" (sum board) 25 + check "blit.cell" board.[2, 2] 4 + + let rebased = Array2D.rebase table + check "rebase" (sum rebased) 60 + check "rebase.base" (Array2D.base1 rebased + Array2D.base2 rebased) 0 + + let doubled = Array2D.map (fun v -> v * 2) table + check "map" (sum doubled) 120 + + let skewed = Array2D.mapi (fun i j v -> v + i - j) table + check "mapi" (sum skewed) 54 + + let copied = Array2D.copy table + check "copy" (sum copied) 60 + check "copy.distinct" (if Object.ReferenceEquals(copied, table) then 1 else 0) 0 + + let based = + try + Array2D.createBased 1 2 rows cols 7 |> ignore + 0 + with :? PlatformNotSupportedException -> + 1 + + check "createBased.throws" based 1 diff --git a/tests/AheadOfTime/NativeAOT/NativeAOT_Test.fsproj b/tests/AheadOfTime/NativeAOT/NativeAOT_Test.fsproj index 18451f28110..e175b7b281c 100644 --- a/tests/AheadOfTime/NativeAOT/NativeAOT_Test.fsproj +++ b/tests/AheadOfTime/NativeAOT/NativeAOT_Test.fsproj @@ -2,7 +2,7 @@ Exe - net10.0 + $(FSharpNetCoreProductTargetFramework) preview true @@ -24,6 +24,7 @@ + diff --git a/tests/AheadOfTime/NativeAOT/Program.fs b/tests/AheadOfTime/NativeAOT/Program.fs index dce1bbaf53e..40ec41ba92c 100644 --- a/tests/AheadOfTime/NativeAOT/Program.fs +++ b/tests/AheadOfTime/NativeAOT/Program.fs @@ -27,8 +27,9 @@ let runChecks () = // check ($"value = %A{x}", "value = 42") [] -let main _ = +let main argv = runChecks () + Array2DProgram.run argv // Success sentinel; a failed check above printed a "FAILED" line first, so the output won't be just this. Console.WriteLine "Finished" 0 diff --git a/tests/AheadOfTime/NativeAOT/check.ps1 b/tests/AheadOfTime/NativeAOT/check.ps1 index c31773529dd..4ccd9c0e1aa 100644 --- a/tests/AheadOfTime/NativeAOT/check.ps1 +++ b/tests/AheadOfTime/NativeAOT/check.ps1 @@ -5,46 +5,55 @@ # printf engine. If it regresses to printf, FSharp.Reflection becomes statically reachable, # NativeAOT analysis emits IL2026/IL2070/IL3050, TreatWarningsAsErrors turns them into errors, # and this publish fails. +# +# Array2D is guarded the same way: the FSharp.Core asset this resolves must not reach +# Array.CreateInstance, which is [RequiresDynamicCode]. The app exercises the zero-based +# operations and one based allocation, which is expected to throw PlatformNotSupportedException. $ErrorActionPreference = "Stop" $root = "NativeAOT_Test" -$tfm = "net10.0" $cwd = Get-Location Set-Location $PSScriptRoot -dotnet publish -restore -c release -f:$tfm "$root.fsproj" -bl:"$PSScriptRoot/../../../artifacts/log/Release/AheadOfTime/NativeAOT/$root.binlog" +dotnet publish -restore -c release "$root.fsproj" -bl:"$PSScriptRoot/../../../artifacts/log/Release/AheadOfTime/NativeAOT/$root.binlog" if (-not ($LASTEXITCODE -eq 0)) { Set-Location $cwd Write-Error "NativeAOT publish failed with exit code $LASTEXITCODE" -ErrorAction Stop } -# Prove this is a genuine net10 dogfood: a net10.0 consumer must resolve the locally-packed -# FSharp.Core's lib/net10.0 asset, not the netstandard2.1 fallback. Assert on the *selected* +# Read both target frameworks back from the project instead of hardcoding them: the app targets +# $(FSharpNetCoreProductTargetFramework) and FSharp.Core ships $(FSharpCoreShippedNetTargetFramework). +# Passing -f: above would set TargetFramework as a global property and silently override the former. +$props = dotnet msbuild "$root.fsproj" -getProperty:TargetFramework -getProperty:FSharpCoreShippedNetTargetFramework -nologo | ConvertFrom-Json +$tfm = $props.Properties.TargetFramework +$coreAsset = "lib/$($props.Properties.FSharpCoreShippedNetTargetFramework)/FSharp.Core.dll" + +# Prove this is a genuine dogfood: the consumer must resolve the locally-packed FSharp.Core's +# shipped net asset, not the netstandard2.1 fallback. Assert on the *selected* # compile/runtime asset under "targets" (the "libraries" manifest lists every lib folder, so a # plain substring search would false-pass even when ns2.1 was chosen). Without this the test would -# still succeed on the ns2.1 asset and the net10 target framework would go unexercised. +# still succeed on the ns2.1 asset and the shipped net asset would go unexercised. $assets = Join-Path $PSScriptRoot "obj/project.assets.json" $assetsJson = Get-Content $assets -Raw | ConvertFrom-Json -$net10Selected = $false +$netAssetSelected = $false foreach ($target in $assetsJson.targets.PSObject.Properties) { foreach ($package in $target.Value.PSObject.Properties) { if ($package.Name -like "FSharp.Core/*") { $compileKeys = if ($package.Value.compile) { @($package.Value.compile.PSObject.Properties.Name) } else { @() } $runtimeKeys = if ($package.Value.runtime) { @($package.Value.runtime.PSObject.Properties.Name) } else { @() } # FSharp.Core packs only lib/{tfm} (no ref/, no runtimes/), so compile and runtime resolve - # from the same folder; require both to be net10 - anything else means a fallback was chosen. - if (($compileKeys -contains "lib/net10.0/FSharp.Core.dll") -and - ($runtimeKeys -contains "lib/net10.0/FSharp.Core.dll")) { - $net10Selected = $true + # from the same folder; require both - anything else means a fallback was chosen. + if (($compileKeys -contains $coreAsset) -and ($runtimeKeys -contains $coreAsset)) { + $netAssetSelected = $true } } } } -if (-not $net10Selected) { +if (-not $netAssetSelected) { Set-Location $cwd - Write-Error "The net10.0 consumer did not resolve the lib/net10.0 FSharp.Core asset (it fell back to a lower target framework). The net10 FSharp.Core target framework is not being dogfooded. See $assets." -ErrorAction Stop + Write-Error "The $tfm consumer did not resolve the $coreAsset FSharp.Core asset (it fell back to a lower target framework). The shipped net FSharp.Core target framework is not being dogfooded. See $assets." -ErrorAction Stop } $exe = Join-Path $PSScriptRoot "bin/release/$tfm/win-x64/publish/$root.exe" @@ -58,7 +67,7 @@ if (-not ($exitCode -eq 0)) { } if ($output.Trim() -ne "Finished") { - Write-Error "NativeAOT interpolation checks failed.`nOutput:`n$output" -ErrorAction Stop + Write-Error "NativeAOT behavior checks failed.`nOutput:`n$output" -ErrorAction Stop } -Write-Host "NativeAOT interpolated-string test passed." +Write-Host "NativeAOT test passed." diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs index 21d38f3bb8d..ba94c0374eb 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs @@ -37,3 +37,7 @@ Array3D.get array3D 0 0 0 |> Array3D.set array3D 0 0 0 // Array4D Renamings (Array4D.length1 array4D, Array4D.length2 array4D, Array4D.length3 array4D, Array4D.length4 array4D) |> ignore Array4D.get array4D 0 0 0 0 |> Array4D.set array4D 0 0 0 0 + +// Pins Array2DModule::Initialize, so a regression of Array2D.init back to a +// based-array forwarder shows up here. +Array2D.init 2 2 (fun i j -> i + j) |> ignore diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOff.OptimizeOff.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOff.OptimizeOff.il.bsl index 2cce273bc65..78f8e90b15c 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOff.OptimizeOff.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOff.OptimizeOff.il.bsl @@ -190,6 +190,42 @@ } + .class auto ansi serializable sealed nested assembly beforefieldinit 'Pipe #8 input at line 43@43' + extends class [FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures/FSharpFunc`3 + { + .field static assembly initonly class assembly/'Pipe #8 input at line 43@43' @_instance + .method assembly specialname rtspecialname instance void .ctor() cil managed + { + .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 ) + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void class [FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures/FSharpFunc`3::.ctor() + IL_0006: ret + } + + .method public strict virtual instance int32 Invoke(int32 i, int32 j) cil managed + { + + .maxstack 8 + IL_0000: ldarg.1 + IL_0001: ldarg.2 + IL_0002: add + IL_0003: ret + } + + .method private specialname rtspecialname static void .cctor() cil managed + { + + .maxstack 10 + IL_0000: newobj instance void assembly/'Pipe #8 input at line 43@43'::.ctor() + IL_0005: stsfld class assembly/'Pipe #8 input at line 43@43' assembly/'Pipe #8 input at line 43@43'::@_instance + IL_000a: ret + } + + } + .method public specialname static class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 get_alist() cil managed { @@ -397,7 +433,9 @@ int32 V_20, class [runtime]System.Tuple`4 V_21, class [runtime]System.Tuple`4 V_22, - int32 V_23) + int32 V_23, + int32[0...,0...] V_24, + int32[0...,0...] V_25) IL_0000: ldc.i4.0 IL_0001: conv.i8 IL_0002: stloc.s V_12 @@ -669,7 +707,16 @@ int32, !!0) IL_0254: nop - IL_0255: ret + IL_0255: ldc.i4.2 + IL_0256: ldc.i4.2 + IL_0257: ldsfld class assembly/'Pipe #8 input at line 43@43' assembly/'Pipe #8 input at line 43@43'::@_instance + IL_025c: call !!0[0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array2DModule::Initialize(int32, + int32, + class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>) + IL_0261: stloc.s V_24 + IL_0263: ldloc.s V_24 + IL_0265: stloc.s V_25 + IL_0267: ret } } @@ -678,4 +725,3 @@ - diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOff.OptimizeOn.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOff.OptimizeOn.il.bsl index deb49d2adb1..c99c62c16fe 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOff.OptimizeOn.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOff.OptimizeOn.il.bsl @@ -195,6 +195,42 @@ } + .class auto ansi serializable sealed nested assembly beforefieldinit clo@43 + extends class [FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures/FSharpFunc`3 + { + .field static assembly initonly class assembly/clo@43 @_instance + .method assembly specialname rtspecialname instance void .ctor() cil managed + { + .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 ) + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void class [FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures/FSharpFunc`3::.ctor() + IL_0006: ret + } + + .method public strict virtual instance int32 Invoke(int32 i, int32 j) cil managed + { + + .maxstack 8 + IL_0000: ldarg.1 + IL_0001: ldarg.2 + IL_0002: add + IL_0003: ret + } + + .method private specialname rtspecialname static void .cctor() cil managed + { + + .maxstack 10 + IL_0000: newobj instance void assembly/clo@43::.ctor() + IL_0005: stsfld class assembly/clo@43 assembly/clo@43::@_instance + IL_000a: ret + } + + } + .method public specialname static class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 get_alist() cil managed { @@ -371,6 +407,14 @@ IL_0005: ret } + .method assembly specialname static int32[0...,0...] get_arg@1() cil managed + { + + .maxstack 8 + IL_0000: ldsfld int32[0...,0...] ''.$assembly::arg@1 + IL_0005: ret + } + .property class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 alist() { @@ -486,6 +530,11 @@ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 09 00 00 00 00 00 ) .get int32 assembly::'get_arg_3@38-1'() } + .property int32[0...,0...] arg@1() + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 09 00 00 00 00 00 ) + .get int32[0...,0...] assembly::get_arg@1() + } } .class private abstract auto ansi sealed ''.$assembly @@ -535,6 +584,8 @@ .custom instance void [runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) .field static assembly int32 'arg_3@38-1' .custom instance void [runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) + .field static assembly int32[0...,0...] arg@1 + .custom instance void [runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) .field static assembly int32 init@ .custom instance void [runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) @@ -634,156 +685,159 @@ !1) IL_00a0: stelem class [runtime]System.Tuple`2 IL_00a5: stsfld class [runtime]System.Tuple`2[] ''.$assembly::array1@10 - IL_00aa: ldc.i4.0 - IL_00ab: ldc.i4.0 - IL_00ac: ldc.i4.2 - IL_00ad: ldc.i4.2 - IL_00ae: ldc.i4.0 - IL_00af: call !!0[0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array2DModule::CreateBased(int32, - int32, - int32, - int32, - !!0) - IL_00b4: stsfld int32[0...,0...] ''.$assembly::a3@11 + IL_00aa: ldc.i4.2 + IL_00ab: ldc.i4.2 + IL_00ac: ldc.i4.0 + IL_00ad: call !!0[0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array2DModule::Create(int32, + int32, + !!0) + IL_00b2: stsfld int32[0...,0...] ''.$assembly::a3@11 + IL_00b7: ldc.i4.3 + IL_00b8: ldc.i4.3 IL_00b9: ldc.i4.3 - IL_00ba: ldc.i4.3 - IL_00bb: ldc.i4.3 - IL_00bc: ldc.i4.0 - IL_00bd: call !!0[0...,0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array3DModule::Create(int32, + IL_00ba: ldc.i4.0 + IL_00bb: call !!0[0...,0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array3DModule::Create(int32, int32, int32, !!0) - IL_00c2: stsfld int32[0...,0...,0...] ''.$assembly::array3D@12 + IL_00c0: stsfld int32[0...,0...,0...] ''.$assembly::array3D@12 + IL_00c5: ldc.i4.4 + IL_00c6: ldc.i4.4 IL_00c7: ldc.i4.4 IL_00c8: ldc.i4.4 - IL_00c9: ldc.i4.4 - IL_00ca: ldc.i4.4 - IL_00cb: ldc.i4.0 - IL_00cc: call !!0[0...,0...,0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array4DModule::Create(int32, + IL_00c9: ldc.i4.0 + IL_00ca: call !!0[0...,0...,0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array4DModule::Create(int32, int32, int32, int32, !!0) - IL_00d1: stsfld int32[0...,0...,0...,0...] ''.$assembly::array4D@13 - IL_00d6: call int32[] assembly::get_array() - IL_00db: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 [FSharp.Core]Microsoft.FSharp.Collections.ListModule::OfArray(!!0[]) - IL_00e0: pop - IL_00e1: call class [runtime]System.Collections.Generic.IEnumerable`1 assembly::get_aseq() - IL_00e6: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 [FSharp.Core]Microsoft.FSharp.Collections.SeqModule::ToList(class [runtime]System.Collections.Generic.IEnumerable`1) - IL_00eb: pop - IL_00ec: call class [runtime]System.Tuple`2[] assembly::get_array1() - IL_00f1: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpMap`2 [FSharp.Core]Microsoft.FSharp.Collections.MapModule::OfArray(class [runtime]System.Tuple`2[]) - IL_00f6: pop - IL_00f7: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1> assembly::get_list1() - IL_00fc: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpMap`2 [FSharp.Core]Microsoft.FSharp.Collections.MapModule::OfList(class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1>) - IL_0101: pop - IL_0102: call class [runtime]System.Collections.Generic.IEnumerable`1> assembly::get_seq1() - IL_0107: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpMap`2 [FSharp.Core]Microsoft.FSharp.Collections.MapModule::OfSeq(class [runtime]System.Collections.Generic.IEnumerable`1>) - IL_010c: pop - IL_010d: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 assembly::get_alist() - IL_0112: call !!0[] [FSharp.Core]Microsoft.FSharp.Collections.ArrayModule::OfList(class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1) - IL_0117: stsfld int32[] ''.$assembly::a1@25 - IL_011c: call class [runtime]System.Collections.Generic.IEnumerable`1 assembly::get_aseq() - IL_0121: call !!0[] [FSharp.Core]Microsoft.FSharp.Collections.ArrayModule::OfSeq(class [runtime]System.Collections.Generic.IEnumerable`1) - IL_0126: stsfld int32[] ''.$assembly::a2@26 - IL_012b: call int32[] assembly::get_a2() - IL_0130: ldc.i4.0 - IL_0131: call int32[] assembly::get_a1() - IL_0136: ldc.i4.0 - IL_0137: ldelem.i4 - IL_0138: stelem.i4 - IL_0139: call int32[0...,0...] assembly::get_a3() - IL_013e: ldc.i4.s 0 - IL_0140: call instance int32 [runtime]System.Array::GetLength(int32) - IL_0145: stsfld int32 ''.$assembly::arg_0@30 - IL_014a: call int32[0...,0...] assembly::get_a3() - IL_014f: ldc.i4.s 1 - IL_0151: call instance int32 [runtime]System.Array::GetLength(int32) - IL_0156: stsfld int32 ''.$assembly::arg_1@30 - IL_015b: call int32[0...,0...] assembly::get_a3() - IL_0160: ldc.i4.0 - IL_0161: callvirt instance int32 [netstandard]System.Array::GetLowerBound(int32) - IL_0166: stsfld int32 ''.$assembly::arg_2@30 - IL_016b: call int32[0...,0...] assembly::get_a3() - IL_0170: ldc.i4.1 - IL_0171: callvirt instance int32 [netstandard]System.Array::GetLowerBound(int32) - IL_0176: stsfld int32 ''.$assembly::arg_3@30 - IL_017b: call int32[0...,0...] assembly::get_a3() - IL_0180: ldc.i4.0 - IL_0181: ldc.i4.0 - IL_0182: call int32[0...,0...] assembly::get_a3() - IL_0187: ldc.i4.0 - IL_0188: ldc.i4.0 - IL_0189: call instance int32 int32[0...,0...]::Get(int32, + IL_00cf: stsfld int32[0...,0...,0...,0...] ''.$assembly::array4D@13 + IL_00d4: call int32[] assembly::get_array() + IL_00d9: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 [FSharp.Core]Microsoft.FSharp.Collections.ListModule::OfArray(!!0[]) + IL_00de: pop + IL_00df: call class [runtime]System.Collections.Generic.IEnumerable`1 assembly::get_aseq() + IL_00e4: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 [FSharp.Core]Microsoft.FSharp.Collections.SeqModule::ToList(class [runtime]System.Collections.Generic.IEnumerable`1) + IL_00e9: pop + IL_00ea: call class [runtime]System.Tuple`2[] assembly::get_array1() + IL_00ef: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpMap`2 [FSharp.Core]Microsoft.FSharp.Collections.MapModule::OfArray(class [runtime]System.Tuple`2[]) + IL_00f4: pop + IL_00f5: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1> assembly::get_list1() + IL_00fa: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpMap`2 [FSharp.Core]Microsoft.FSharp.Collections.MapModule::OfList(class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1>) + IL_00ff: pop + IL_0100: call class [runtime]System.Collections.Generic.IEnumerable`1> assembly::get_seq1() + IL_0105: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpMap`2 [FSharp.Core]Microsoft.FSharp.Collections.MapModule::OfSeq(class [runtime]System.Collections.Generic.IEnumerable`1>) + IL_010a: pop + IL_010b: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 assembly::get_alist() + IL_0110: call !!0[] [FSharp.Core]Microsoft.FSharp.Collections.ArrayModule::OfList(class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1) + IL_0115: stsfld int32[] ''.$assembly::a1@25 + IL_011a: call class [runtime]System.Collections.Generic.IEnumerable`1 assembly::get_aseq() + IL_011f: call !!0[] [FSharp.Core]Microsoft.FSharp.Collections.ArrayModule::OfSeq(class [runtime]System.Collections.Generic.IEnumerable`1) + IL_0124: stsfld int32[] ''.$assembly::a2@26 + IL_0129: call int32[] assembly::get_a2() + IL_012e: ldc.i4.0 + IL_012f: call int32[] assembly::get_a1() + IL_0134: ldc.i4.0 + IL_0135: ldelem.i4 + IL_0136: stelem.i4 + IL_0137: call int32[0...,0...] assembly::get_a3() + IL_013c: ldc.i4.s 0 + IL_013e: call instance int32 [runtime]System.Array::GetLength(int32) + IL_0143: stsfld int32 ''.$assembly::arg_0@30 + IL_0148: call int32[0...,0...] assembly::get_a3() + IL_014d: ldc.i4.s 1 + IL_014f: call instance int32 [runtime]System.Array::GetLength(int32) + IL_0154: stsfld int32 ''.$assembly::arg_1@30 + IL_0159: call int32[0...,0...] assembly::get_a3() + IL_015e: ldc.i4.0 + IL_015f: callvirt instance int32 [netstandard]System.Array::GetLowerBound(int32) + IL_0164: stsfld int32 ''.$assembly::arg_2@30 + IL_0169: call int32[0...,0...] assembly::get_a3() + IL_016e: ldc.i4.1 + IL_016f: callvirt instance int32 [netstandard]System.Array::GetLowerBound(int32) + IL_0174: stsfld int32 ''.$assembly::arg_3@30 + IL_0179: call int32[0...,0...] assembly::get_a3() + IL_017e: ldc.i4.0 + IL_017f: ldc.i4.0 + IL_0180: call int32[0...,0...] assembly::get_a3() + IL_0185: ldc.i4.0 + IL_0186: ldc.i4.0 + IL_0187: call instance int32 int32[0...,0...]::Get(int32, int32) - IL_018e: call instance void int32[0...,0...]::Set(int32, + IL_018c: call instance void int32[0...,0...]::Set(int32, int32, int32) - IL_0193: call int32[0...,0...,0...] assembly::get_array3D() - IL_0198: ldc.i4.s 0 - IL_019a: call instance int32 [runtime]System.Array::GetLength(int32) - IL_019f: stsfld int32 ''.$assembly::'arg_0@34-1' - IL_01a4: call int32[0...,0...,0...] assembly::get_array3D() - IL_01a9: ldc.i4.s 1 - IL_01ab: call instance int32 [runtime]System.Array::GetLength(int32) - IL_01b0: stsfld int32 ''.$assembly::'arg_1@34-1' - IL_01b5: call int32[0...,0...,0...] assembly::get_array3D() - IL_01ba: ldc.i4.s 2 - IL_01bc: call instance int32 [runtime]System.Array::GetLength(int32) - IL_01c1: stsfld int32 ''.$assembly::'arg_2@34-1' - IL_01c6: call int32[0...,0...,0...] assembly::get_array3D() + IL_0191: call int32[0...,0...,0...] assembly::get_array3D() + IL_0196: ldc.i4.s 0 + IL_0198: call instance int32 [runtime]System.Array::GetLength(int32) + IL_019d: stsfld int32 ''.$assembly::'arg_0@34-1' + IL_01a2: call int32[0...,0...,0...] assembly::get_array3D() + IL_01a7: ldc.i4.s 1 + IL_01a9: call instance int32 [runtime]System.Array::GetLength(int32) + IL_01ae: stsfld int32 ''.$assembly::'arg_1@34-1' + IL_01b3: call int32[0...,0...,0...] assembly::get_array3D() + IL_01b8: ldc.i4.s 2 + IL_01ba: call instance int32 [runtime]System.Array::GetLength(int32) + IL_01bf: stsfld int32 ''.$assembly::'arg_2@34-1' + IL_01c4: call int32[0...,0...,0...] assembly::get_array3D() + IL_01c9: ldc.i4.0 + IL_01ca: ldc.i4.0 IL_01cb: ldc.i4.0 - IL_01cc: ldc.i4.0 - IL_01cd: ldc.i4.0 - IL_01ce: call int32[0...,0...,0...] assembly::get_array3D() + IL_01cc: call int32[0...,0...,0...] assembly::get_array3D() + IL_01d1: ldc.i4.0 + IL_01d2: ldc.i4.0 IL_01d3: ldc.i4.0 - IL_01d4: ldc.i4.0 - IL_01d5: ldc.i4.0 - IL_01d6: call instance int32 int32[0...,0...,0...]::Get(int32, + IL_01d4: call instance int32 int32[0...,0...,0...]::Get(int32, int32, int32) - IL_01db: call instance void int32[0...,0...,0...]::Set(int32, + IL_01d9: call instance void int32[0...,0...,0...]::Set(int32, int32, int32, int32) - IL_01e0: call int32[0...,0...,0...,0...] assembly::get_array4D() - IL_01e5: ldc.i4.s 0 - IL_01e7: call instance int32 [runtime]System.Array::GetLength(int32) - IL_01ec: stsfld int32 ''.$assembly::'arg_0@38-2' - IL_01f1: call int32[0...,0...,0...,0...] assembly::get_array4D() - IL_01f6: ldc.i4.s 1 - IL_01f8: call instance int32 [runtime]System.Array::GetLength(int32) - IL_01fd: stsfld int32 ''.$assembly::'arg_1@38-2' - IL_0202: call int32[0...,0...,0...,0...] assembly::get_array4D() - IL_0207: ldc.i4.s 2 - IL_0209: call instance int32 [runtime]System.Array::GetLength(int32) - IL_020e: stsfld int32 ''.$assembly::'arg_2@38-2' - IL_0213: call int32[0...,0...,0...,0...] assembly::get_array4D() - IL_0218: ldc.i4.s 3 - IL_021a: call instance int32 [runtime]System.Array::GetLength(int32) - IL_021f: stsfld int32 ''.$assembly::'arg_3@38-1' - IL_0224: call int32[0...,0...,0...,0...] assembly::get_array4D() + IL_01de: call int32[0...,0...,0...,0...] assembly::get_array4D() + IL_01e3: ldc.i4.s 0 + IL_01e5: call instance int32 [runtime]System.Array::GetLength(int32) + IL_01ea: stsfld int32 ''.$assembly::'arg_0@38-2' + IL_01ef: call int32[0...,0...,0...,0...] assembly::get_array4D() + IL_01f4: ldc.i4.s 1 + IL_01f6: call instance int32 [runtime]System.Array::GetLength(int32) + IL_01fb: stsfld int32 ''.$assembly::'arg_1@38-2' + IL_0200: call int32[0...,0...,0...,0...] assembly::get_array4D() + IL_0205: ldc.i4.s 2 + IL_0207: call instance int32 [runtime]System.Array::GetLength(int32) + IL_020c: stsfld int32 ''.$assembly::'arg_2@38-2' + IL_0211: call int32[0...,0...,0...,0...] assembly::get_array4D() + IL_0216: ldc.i4.s 3 + IL_0218: call instance int32 [runtime]System.Array::GetLength(int32) + IL_021d: stsfld int32 ''.$assembly::'arg_3@38-1' + IL_0222: call int32[0...,0...,0...,0...] assembly::get_array4D() + IL_0227: ldc.i4.0 + IL_0228: ldc.i4.0 IL_0229: ldc.i4.0 IL_022a: ldc.i4.0 - IL_022b: ldc.i4.0 - IL_022c: ldc.i4.0 - IL_022d: call int32[0...,0...,0...,0...] assembly::get_array4D() + IL_022b: call int32[0...,0...,0...,0...] assembly::get_array4D() + IL_0230: ldc.i4.0 + IL_0231: ldc.i4.0 IL_0232: ldc.i4.0 IL_0233: ldc.i4.0 - IL_0234: ldc.i4.0 - IL_0235: ldc.i4.0 - IL_0236: call instance int32 int32[0...,0...,0...,0...]::Get(int32, + IL_0234: call instance int32 int32[0...,0...,0...,0...]::Get(int32, int32, int32, int32) - IL_023b: call void [FSharp.Core]Microsoft.FSharp.Collections.Array4DModule::Set(!!0[0...,0...,0...,0...], + IL_0239: call void [FSharp.Core]Microsoft.FSharp.Collections.Array4DModule::Set(!!0[0...,0...,0...,0...], int32, int32, int32, int32, !!0) - IL_0240: nop - IL_0241: ret + IL_023e: nop + IL_023f: ldc.i4.2 + IL_0240: ldc.i4.2 + IL_0241: ldsfld class assembly/clo@43 assembly/clo@43::@_instance + IL_0246: call !!0[0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array2DModule::Initialize(int32, + int32, + class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>) + IL_024b: stsfld int32[0...,0...] ''.$assembly::arg@1 + IL_0250: ret } } @@ -792,4 +846,3 @@ - diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOn.OptimizeOff.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOn.OptimizeOff.il.bsl index b8e51a0b509..2edc51c7c44 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOn.OptimizeOff.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOn.OptimizeOff.il.bsl @@ -190,6 +190,42 @@ } + .class auto ansi serializable sealed nested assembly beforefieldinit 'Pipe #8 input at line 43@43' + extends class [FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures/FSharpFunc`3 + { + .field static assembly initonly class assembly/'Pipe #8 input at line 43@43' @_instance + .method assembly specialname rtspecialname instance void .ctor() cil managed + { + .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 ) + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void class [FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures/FSharpFunc`3::.ctor() + IL_0006: ret + } + + .method public strict virtual instance int32 Invoke(int32 i, int32 j) cil managed + { + + .maxstack 8 + IL_0000: ldarg.1 + IL_0001: ldarg.2 + IL_0002: add + IL_0003: ret + } + + .method private specialname rtspecialname static void .cctor() cil managed + { + + .maxstack 10 + IL_0000: newobj instance void assembly/'Pipe #8 input at line 43@43'::.ctor() + IL_0005: stsfld class assembly/'Pipe #8 input at line 43@43' assembly/'Pipe #8 input at line 43@43'::@_instance + IL_000a: ret + } + + } + .field static assembly class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 alist@5 .custom instance void [runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) .field static assembly int32[] array@6 @@ -327,7 +363,9 @@ int32 V_9, class [runtime]System.Tuple`4 V_10, class [runtime]System.Tuple`4 V_11, - int32 V_12) + int32 V_12, + int32[0...,0...] V_13, + int32[0...,0...] V_14) IL_0000: ldc.i4.0 IL_0001: conv.i8 IL_0002: stloc.1 @@ -577,7 +615,16 @@ int32, !!0) IL_022d: nop - IL_022e: ret + IL_022e: ldc.i4.2 + IL_022f: ldc.i4.2 + IL_0230: ldsfld class assembly/'Pipe #8 input at line 43@43' assembly/'Pipe #8 input at line 43@43'::@_instance + IL_0235: call !!0[0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array2DModule::Initialize(int32, + int32, + class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>) + IL_023a: stloc.s V_13 + IL_023c: ldloc.s V_13 + IL_023e: stloc.s V_14 + IL_0240: ret } .property class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 @@ -664,4 +711,3 @@ - diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOn.OptimizeOn.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOn.OptimizeOn.il.bsl index c8843431ed9..daaa8cd6324 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOn.OptimizeOn.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOn.OptimizeOn.il.bsl @@ -195,6 +195,42 @@ } + .class auto ansi serializable sealed nested assembly beforefieldinit clo@43 + extends class [FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures/FSharpFunc`3 + { + .field static assembly initonly class assembly/clo@43 @_instance + .method assembly specialname rtspecialname instance void .ctor() cil managed + { + .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) + .custom instance void [runtime]System.Diagnostics.DebuggerNonUserCodeAttribute::.ctor() = ( 01 00 00 00 ) + + .maxstack 8 + IL_0000: ldarg.0 + IL_0001: call instance void class [FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures/FSharpFunc`3::.ctor() + IL_0006: ret + } + + .method public strict virtual instance int32 Invoke(int32 i, int32 j) cil managed + { + + .maxstack 8 + IL_0000: ldarg.1 + IL_0001: ldarg.2 + IL_0002: add + IL_0003: ret + } + + .method private specialname rtspecialname static void .cctor() cil managed + { + + .maxstack 10 + IL_0000: newobj instance void assembly/clo@43::.ctor() + IL_0005: stsfld class assembly/clo@43 assembly/clo@43::@_instance + IL_000a: ret + } + + } + .field static assembly class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 alist@5 .custom instance void [runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) .field static assembly int32[] array@6 @@ -239,6 +275,8 @@ .custom instance void [runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) .field static assembly int32 'arg_3@38-1' .custom instance void [runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) + .field static assembly int32[0...,0...] arg@1 + .custom instance void [runtime]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [runtime]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) .method public specialname static class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 get_alist() cil managed { @@ -415,6 +453,14 @@ IL_0005: ret } + .method assembly specialname static int32[0...,0...] get_arg@1() cil managed + { + + .maxstack 8 + IL_0000: ldsfld int32[0...,0...] assembly::arg@1 + IL_0005: ret + } + .method private specialname rtspecialname static void .cctor() cil managed { @@ -520,156 +566,159 @@ !1) IL_00a0: stelem class [runtime]System.Tuple`2 IL_00a5: stsfld class [runtime]System.Tuple`2[] assembly::array1@10 - IL_00aa: ldc.i4.0 - IL_00ab: ldc.i4.0 - IL_00ac: ldc.i4.2 - IL_00ad: ldc.i4.2 - IL_00ae: ldc.i4.0 - IL_00af: call !!0[0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array2DModule::CreateBased(int32, - int32, - int32, - int32, - !!0) - IL_00b4: stsfld int32[0...,0...] assembly::a3@11 + IL_00aa: ldc.i4.2 + IL_00ab: ldc.i4.2 + IL_00ac: ldc.i4.0 + IL_00ad: call !!0[0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array2DModule::Create(int32, + int32, + !!0) + IL_00b2: stsfld int32[0...,0...] assembly::a3@11 + IL_00b7: ldc.i4.3 + IL_00b8: ldc.i4.3 IL_00b9: ldc.i4.3 - IL_00ba: ldc.i4.3 - IL_00bb: ldc.i4.3 - IL_00bc: ldc.i4.0 - IL_00bd: call !!0[0...,0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array3DModule::Create(int32, + IL_00ba: ldc.i4.0 + IL_00bb: call !!0[0...,0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array3DModule::Create(int32, int32, int32, !!0) - IL_00c2: stsfld int32[0...,0...,0...] assembly::array3D@12 + IL_00c0: stsfld int32[0...,0...,0...] assembly::array3D@12 + IL_00c5: ldc.i4.4 + IL_00c6: ldc.i4.4 IL_00c7: ldc.i4.4 IL_00c8: ldc.i4.4 - IL_00c9: ldc.i4.4 - IL_00ca: ldc.i4.4 - IL_00cb: ldc.i4.0 - IL_00cc: call !!0[0...,0...,0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array4DModule::Create(int32, + IL_00c9: ldc.i4.0 + IL_00ca: call !!0[0...,0...,0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array4DModule::Create(int32, int32, int32, int32, !!0) - IL_00d1: stsfld int32[0...,0...,0...,0...] assembly::array4D@13 - IL_00d6: call int32[] assembly::get_array() - IL_00db: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 [FSharp.Core]Microsoft.FSharp.Collections.ListModule::OfArray(!!0[]) - IL_00e0: pop - IL_00e1: call class [runtime]System.Collections.Generic.IEnumerable`1 assembly::get_aseq() - IL_00e6: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 [FSharp.Core]Microsoft.FSharp.Collections.SeqModule::ToList(class [runtime]System.Collections.Generic.IEnumerable`1) - IL_00eb: pop - IL_00ec: call class [runtime]System.Tuple`2[] assembly::get_array1() - IL_00f1: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpMap`2 [FSharp.Core]Microsoft.FSharp.Collections.MapModule::OfArray(class [runtime]System.Tuple`2[]) - IL_00f6: pop - IL_00f7: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1> assembly::get_list1() - IL_00fc: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpMap`2 [FSharp.Core]Microsoft.FSharp.Collections.MapModule::OfList(class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1>) - IL_0101: pop - IL_0102: call class [runtime]System.Collections.Generic.IEnumerable`1> assembly::get_seq1() - IL_0107: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpMap`2 [FSharp.Core]Microsoft.FSharp.Collections.MapModule::OfSeq(class [runtime]System.Collections.Generic.IEnumerable`1>) - IL_010c: pop - IL_010d: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 assembly::get_alist() - IL_0112: call !!0[] [FSharp.Core]Microsoft.FSharp.Collections.ArrayModule::OfList(class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1) - IL_0117: stsfld int32[] assembly::a1@25 - IL_011c: call class [runtime]System.Collections.Generic.IEnumerable`1 assembly::get_aseq() - IL_0121: call !!0[] [FSharp.Core]Microsoft.FSharp.Collections.ArrayModule::OfSeq(class [runtime]System.Collections.Generic.IEnumerable`1) - IL_0126: stsfld int32[] assembly::a2@26 - IL_012b: call int32[] assembly::get_a2() - IL_0130: ldc.i4.0 - IL_0131: call int32[] assembly::get_a1() - IL_0136: ldc.i4.0 - IL_0137: ldelem.i4 - IL_0138: stelem.i4 - IL_0139: call int32[0...,0...] assembly::get_a3() - IL_013e: ldc.i4.s 0 - IL_0140: call instance int32 [runtime]System.Array::GetLength(int32) - IL_0145: stsfld int32 assembly::arg_0@30 - IL_014a: call int32[0...,0...] assembly::get_a3() - IL_014f: ldc.i4.s 1 - IL_0151: call instance int32 [runtime]System.Array::GetLength(int32) - IL_0156: stsfld int32 assembly::arg_1@30 - IL_015b: call int32[0...,0...] assembly::get_a3() - IL_0160: ldc.i4.0 - IL_0161: callvirt instance int32 [netstandard]System.Array::GetLowerBound(int32) - IL_0166: stsfld int32 assembly::arg_2@30 - IL_016b: call int32[0...,0...] assembly::get_a3() - IL_0170: ldc.i4.1 - IL_0171: callvirt instance int32 [netstandard]System.Array::GetLowerBound(int32) - IL_0176: stsfld int32 assembly::arg_3@30 - IL_017b: call int32[0...,0...] assembly::get_a3() - IL_0180: ldc.i4.0 - IL_0181: ldc.i4.0 - IL_0182: call int32[0...,0...] assembly::get_a3() - IL_0187: ldc.i4.0 - IL_0188: ldc.i4.0 - IL_0189: call instance int32 int32[0...,0...]::Get(int32, + IL_00cf: stsfld int32[0...,0...,0...,0...] assembly::array4D@13 + IL_00d4: call int32[] assembly::get_array() + IL_00d9: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 [FSharp.Core]Microsoft.FSharp.Collections.ListModule::OfArray(!!0[]) + IL_00de: pop + IL_00df: call class [runtime]System.Collections.Generic.IEnumerable`1 assembly::get_aseq() + IL_00e4: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 [FSharp.Core]Microsoft.FSharp.Collections.SeqModule::ToList(class [runtime]System.Collections.Generic.IEnumerable`1) + IL_00e9: pop + IL_00ea: call class [runtime]System.Tuple`2[] assembly::get_array1() + IL_00ef: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpMap`2 [FSharp.Core]Microsoft.FSharp.Collections.MapModule::OfArray(class [runtime]System.Tuple`2[]) + IL_00f4: pop + IL_00f5: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1> assembly::get_list1() + IL_00fa: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpMap`2 [FSharp.Core]Microsoft.FSharp.Collections.MapModule::OfList(class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1>) + IL_00ff: pop + IL_0100: call class [runtime]System.Collections.Generic.IEnumerable`1> assembly::get_seq1() + IL_0105: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpMap`2 [FSharp.Core]Microsoft.FSharp.Collections.MapModule::OfSeq(class [runtime]System.Collections.Generic.IEnumerable`1>) + IL_010a: pop + IL_010b: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 assembly::get_alist() + IL_0110: call !!0[] [FSharp.Core]Microsoft.FSharp.Collections.ArrayModule::OfList(class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1) + IL_0115: stsfld int32[] assembly::a1@25 + IL_011a: call class [runtime]System.Collections.Generic.IEnumerable`1 assembly::get_aseq() + IL_011f: call !!0[] [FSharp.Core]Microsoft.FSharp.Collections.ArrayModule::OfSeq(class [runtime]System.Collections.Generic.IEnumerable`1) + IL_0124: stsfld int32[] assembly::a2@26 + IL_0129: call int32[] assembly::get_a2() + IL_012e: ldc.i4.0 + IL_012f: call int32[] assembly::get_a1() + IL_0134: ldc.i4.0 + IL_0135: ldelem.i4 + IL_0136: stelem.i4 + IL_0137: call int32[0...,0...] assembly::get_a3() + IL_013c: ldc.i4.s 0 + IL_013e: call instance int32 [runtime]System.Array::GetLength(int32) + IL_0143: stsfld int32 assembly::arg_0@30 + IL_0148: call int32[0...,0...] assembly::get_a3() + IL_014d: ldc.i4.s 1 + IL_014f: call instance int32 [runtime]System.Array::GetLength(int32) + IL_0154: stsfld int32 assembly::arg_1@30 + IL_0159: call int32[0...,0...] assembly::get_a3() + IL_015e: ldc.i4.0 + IL_015f: callvirt instance int32 [netstandard]System.Array::GetLowerBound(int32) + IL_0164: stsfld int32 assembly::arg_2@30 + IL_0169: call int32[0...,0...] assembly::get_a3() + IL_016e: ldc.i4.1 + IL_016f: callvirt instance int32 [netstandard]System.Array::GetLowerBound(int32) + IL_0174: stsfld int32 assembly::arg_3@30 + IL_0179: call int32[0...,0...] assembly::get_a3() + IL_017e: ldc.i4.0 + IL_017f: ldc.i4.0 + IL_0180: call int32[0...,0...] assembly::get_a3() + IL_0185: ldc.i4.0 + IL_0186: ldc.i4.0 + IL_0187: call instance int32 int32[0...,0...]::Get(int32, int32) - IL_018e: call instance void int32[0...,0...]::Set(int32, + IL_018c: call instance void int32[0...,0...]::Set(int32, int32, int32) - IL_0193: call int32[0...,0...,0...] assembly::get_array3D() - IL_0198: ldc.i4.s 0 - IL_019a: call instance int32 [runtime]System.Array::GetLength(int32) - IL_019f: stsfld int32 assembly::'arg_0@34-1' - IL_01a4: call int32[0...,0...,0...] assembly::get_array3D() - IL_01a9: ldc.i4.s 1 - IL_01ab: call instance int32 [runtime]System.Array::GetLength(int32) - IL_01b0: stsfld int32 assembly::'arg_1@34-1' - IL_01b5: call int32[0...,0...,0...] assembly::get_array3D() - IL_01ba: ldc.i4.s 2 - IL_01bc: call instance int32 [runtime]System.Array::GetLength(int32) - IL_01c1: stsfld int32 assembly::'arg_2@34-1' - IL_01c6: call int32[0...,0...,0...] assembly::get_array3D() + IL_0191: call int32[0...,0...,0...] assembly::get_array3D() + IL_0196: ldc.i4.s 0 + IL_0198: call instance int32 [runtime]System.Array::GetLength(int32) + IL_019d: stsfld int32 assembly::'arg_0@34-1' + IL_01a2: call int32[0...,0...,0...] assembly::get_array3D() + IL_01a7: ldc.i4.s 1 + IL_01a9: call instance int32 [runtime]System.Array::GetLength(int32) + IL_01ae: stsfld int32 assembly::'arg_1@34-1' + IL_01b3: call int32[0...,0...,0...] assembly::get_array3D() + IL_01b8: ldc.i4.s 2 + IL_01ba: call instance int32 [runtime]System.Array::GetLength(int32) + IL_01bf: stsfld int32 assembly::'arg_2@34-1' + IL_01c4: call int32[0...,0...,0...] assembly::get_array3D() + IL_01c9: ldc.i4.0 + IL_01ca: ldc.i4.0 IL_01cb: ldc.i4.0 - IL_01cc: ldc.i4.0 - IL_01cd: ldc.i4.0 - IL_01ce: call int32[0...,0...,0...] assembly::get_array3D() + IL_01cc: call int32[0...,0...,0...] assembly::get_array3D() + IL_01d1: ldc.i4.0 + IL_01d2: ldc.i4.0 IL_01d3: ldc.i4.0 - IL_01d4: ldc.i4.0 - IL_01d5: ldc.i4.0 - IL_01d6: call instance int32 int32[0...,0...,0...]::Get(int32, + IL_01d4: call instance int32 int32[0...,0...,0...]::Get(int32, int32, int32) - IL_01db: call instance void int32[0...,0...,0...]::Set(int32, + IL_01d9: call instance void int32[0...,0...,0...]::Set(int32, int32, int32, int32) - IL_01e0: call int32[0...,0...,0...,0...] assembly::get_array4D() - IL_01e5: ldc.i4.s 0 - IL_01e7: call instance int32 [runtime]System.Array::GetLength(int32) - IL_01ec: stsfld int32 assembly::'arg_0@38-2' - IL_01f1: call int32[0...,0...,0...,0...] assembly::get_array4D() - IL_01f6: ldc.i4.s 1 - IL_01f8: call instance int32 [runtime]System.Array::GetLength(int32) - IL_01fd: stsfld int32 assembly::'arg_1@38-2' - IL_0202: call int32[0...,0...,0...,0...] assembly::get_array4D() - IL_0207: ldc.i4.s 2 - IL_0209: call instance int32 [runtime]System.Array::GetLength(int32) - IL_020e: stsfld int32 assembly::'arg_2@38-2' - IL_0213: call int32[0...,0...,0...,0...] assembly::get_array4D() - IL_0218: ldc.i4.s 3 - IL_021a: call instance int32 [runtime]System.Array::GetLength(int32) - IL_021f: stsfld int32 assembly::'arg_3@38-1' - IL_0224: call int32[0...,0...,0...,0...] assembly::get_array4D() + IL_01de: call int32[0...,0...,0...,0...] assembly::get_array4D() + IL_01e3: ldc.i4.s 0 + IL_01e5: call instance int32 [runtime]System.Array::GetLength(int32) + IL_01ea: stsfld int32 assembly::'arg_0@38-2' + IL_01ef: call int32[0...,0...,0...,0...] assembly::get_array4D() + IL_01f4: ldc.i4.s 1 + IL_01f6: call instance int32 [runtime]System.Array::GetLength(int32) + IL_01fb: stsfld int32 assembly::'arg_1@38-2' + IL_0200: call int32[0...,0...,0...,0...] assembly::get_array4D() + IL_0205: ldc.i4.s 2 + IL_0207: call instance int32 [runtime]System.Array::GetLength(int32) + IL_020c: stsfld int32 assembly::'arg_2@38-2' + IL_0211: call int32[0...,0...,0...,0...] assembly::get_array4D() + IL_0216: ldc.i4.s 3 + IL_0218: call instance int32 [runtime]System.Array::GetLength(int32) + IL_021d: stsfld int32 assembly::'arg_3@38-1' + IL_0222: call int32[0...,0...,0...,0...] assembly::get_array4D() + IL_0227: ldc.i4.0 + IL_0228: ldc.i4.0 IL_0229: ldc.i4.0 IL_022a: ldc.i4.0 - IL_022b: ldc.i4.0 - IL_022c: ldc.i4.0 - IL_022d: call int32[0...,0...,0...,0...] assembly::get_array4D() + IL_022b: call int32[0...,0...,0...,0...] assembly::get_array4D() + IL_0230: ldc.i4.0 + IL_0231: ldc.i4.0 IL_0232: ldc.i4.0 IL_0233: ldc.i4.0 - IL_0234: ldc.i4.0 - IL_0235: ldc.i4.0 - IL_0236: call instance int32 int32[0...,0...,0...,0...]::Get(int32, + IL_0234: call instance int32 int32[0...,0...,0...,0...]::Get(int32, int32, int32, int32) - IL_023b: call void [FSharp.Core]Microsoft.FSharp.Collections.Array4DModule::Set(!!0[0...,0...,0...,0...], + IL_0239: call void [FSharp.Core]Microsoft.FSharp.Collections.Array4DModule::Set(!!0[0...,0...,0...,0...], int32, int32, int32, int32, !!0) - IL_0240: nop - IL_0241: ret + IL_023e: nop + IL_023f: ldc.i4.2 + IL_0240: ldc.i4.2 + IL_0241: ldsfld class assembly/clo@43 assembly/clo@43::@_instance + IL_0246: call !!0[0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array2DModule::Initialize(int32, + int32, + class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>) + IL_024b: stsfld int32[0...,0...] assembly::arg@1 + IL_0250: ret } .property class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1 @@ -787,6 +836,11 @@ .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 09 00 00 00 00 00 ) .get int32 assembly::'get_arg_3@38-1'() } + .property int32[0...,0...] arg@1() + { + .custom instance void [FSharp.Core]Microsoft.FSharp.Core.CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.FSharp.Core.SourceConstructFlags) = ( 01 00 09 00 00 00 00 00 ) + .get int32[0...,0...] assembly::get_arg@1() + } } .class private abstract auto ansi sealed ''.$assembly @@ -811,4 +865,3 @@ - diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/Array2Module.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/Array2Module.fs index b68336b3064..7b7f7df7aeb 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/Array2Module.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/Array2Module.fs @@ -187,8 +187,9 @@ type Array2Module() = [] member this.Copy() = // integer array - let intArr = Array2D.init 2 3 (fun i j -> i*100 + j) + let intArr = Array2D.initBased 3 7 2 3 (fun i j -> i*100 + j) let resultInt = Array2D.copy intArr + if obj.ReferenceEquals(intArr, resultInt) then Assert.Fail() if resultInt <> intArr then Assert.Fail() @@ -196,6 +197,13 @@ type Array2Module() = let strArr = Array2D.init 2 3 (fun i j -> i.ToString() + "-" + j.ToString()) let resultStr = Array2D.copy strArr if resultStr <> strArr then Assert.Fail() + + // Guards against rewriting copy as Array.Clone to escape its IL3050 warning: Clone would keep + // the runtime string[,] type and throw here, where copy allocates obj[,] and accepts the box. + let covariantSource = Array2D.create 1 1 "value" |> box |> unbox + let covariantCopy = Array2D.copy covariantSource + covariantCopy.[0, 0] <- box 42 + Assert.Equal(box 42, covariantCopy.[0, 0]) // empty array let eptArr = Array2D.create 0 0 1 @@ -225,7 +233,12 @@ type Array2Module() = let eptArr = Array2D.create 0 0 1 let resultEpt = Array2D.create 0 0 1 if resultEpt <> eptArr then Assert.Fail() + shouldBeEmpty resultEpt + // invalid arguments + CheckThrowsArgumentException (fun () -> Array2D.create -1 2 0 |> ignore) + CheckThrowsArgumentException (fun () -> Array2D.create 1 -2 0 |> ignore) + () [] @@ -284,6 +297,14 @@ type Array2Module() = // string array let strArr = Array2D.init 2 3 (fun i j -> i.ToString() + "-" + j.ToString()) if strArr.[1,1] <> "1-1" then Assert.Fail() + + // empty array + let eptArr = Array2D.init 0 0 (fun i j -> i + j) + shouldBeEmpty eptArr + + // invalid arguments + CheckThrowsArgumentException (fun () -> Array2D.init -1 2 (fun i j -> i + j) |> ignore) + CheckThrowsArgumentException (fun () -> Array2D.init 1 -2 (fun i j -> i + j) |> ignore) () [] @@ -409,10 +430,10 @@ type Array2Module() = [] member this.Map() = // integer array - let intArr = Array2D.init 2 3 (fun i j -> i*100 + j) + let intArr = Array2D.initBased 3 7 2 3 (fun i j -> i*100 + j) let funInt x = x.ToString() let resultInt = Array2D.map funInt intArr - if resultInt <> (Array2D.init 2 3 (fun i j -> (i*100 + j).ToString())) then Assert.Fail() + if resultInt <> (Array2D.initBased 3 7 2 3 (fun i j -> (i*100 + j).ToString())) then Assert.Fail() // string array @@ -435,10 +456,12 @@ type Array2Module() = [] member this.Mapi() = // integer array - let intArr = Array2D.init 2 3 (fun i j -> i*100 + j) - let funInt x y z = x+y+z - let resultInt = Array2D.mapi funInt intArr - if resultInt <> (Array2D.init 2 3 (fun i j -> i*100 + j + i + j)) then Assert.Fail() + let intArr = Array2D.initBased 3 7 2 3 (fun i j -> i*100 + j) + // Maps int -> string, so the result element type differs from the source and mapi has to + // allocate a new based array rather than reuse the input's. + let funIntToStr x y z = sprintf "%d,%d:%d" x y z + let resultInt = Array2D.mapi funIntToStr intArr + if resultInt <> (Array2D.initBased 3 7 2 3 (fun i j -> sprintf "%d,%d:%d" i j (i*100 + j))) then Assert.Fail() // string array @@ -449,8 +472,8 @@ type Array2Module() = // empty array let eptArr = Array2D.create 0 0 1 - let resultEpt = Array2D.mapi funInt eptArr - if resultEpt <> Array2D.create 0 0 1 then Assert.Fail() + let resultEpt = Array2D.mapi funIntToStr eptArr + if resultEpt <> Array2D.create 0 0 "" then Assert.Fail() // null array let nullArr = null:string[,] From 6c47ea8fae8c4481f91416f49fbb78556d7d9ec4 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Tue, 25 Aug 2026 16:05:19 +0200 Subject: [PATCH 2/7] Warn at the call site when Array2D allocates a based array Array.CreateInstanceFromArrayType carries no RequiresDynamicCode, so the non-zero-based path lost its IL3050 and failed only at runtime, with a PlatformNotSupportedException. Annotating the based entry points directly would have put that warning back onto zero-based map/mapi/copy, which route through initBased to preserve the bounds of their input. Splitting out private unannotated cores lets the three public based entry points carry RequiresDynamicCode on their own, so callers get IL3050 at their own call site while the zero-based operations stay clean. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b61ef94c-e4e0-4424-a645-a08a8b463d67 --- docs/release-notes/.FSharp.Core/11.0.100.md | 2 +- src/FSharp.Core/array2.fs | 59 ++++++++++++++----- tests/AheadOfTime/NativeAOT/Array2DProgram.fs | 20 ++++--- tests/AheadOfTime/NativeAOT/check.ps1 | 6 +- .../Array2Module.fs | 20 +++++++ 5 files changed, 79 insertions(+), 28 deletions(-) diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md index 368a2717f2e..c727c5c09e3 100644 --- a/docs/release-notes/.FSharp.Core/11.0.100.md +++ b/docs/release-notes/.FSharp.Core/11.0.100.md @@ -5,7 +5,7 @@ ### Fixed * Mirror compiler-semantic attributes (e.g. `[]`, `[]`) in `.fsi` signature files to match `.fs` implementations. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880)) -* Remove the `IL3050` dynamic-code warning from `Array2D`: `create` and `init` no longer allocate through a based-array path, and the `net10.0` asset allocates based arrays with `Array.CreateInstanceFromArrayType`. ([Language Suggestion #1454](https://github.com/fsharp/fslang-suggestions/issues/1454), [PR #20338](https://github.com/dotnet/fsharp/pull/20338)) +* Remove the `IL3050` dynamic-code warning from zero-based `Array2D`: `create` and `init` no longer allocate through a based-array path, and the `net10.0` asset allocates based arrays with `Array.CreateInstanceFromArrayType`. On that asset `createBased`, `initBased` and `zeroCreateBased` are annotated `RequiresDynamicCode`, so callers get `IL3050` at their own call site instead of a runtime failure — a new build error for AOT-analysing consumers that treat warnings as errors. ([Language Suggestion #1454](https://github.com/fsharp/fslang-suggestions/issues/1454), [PR #20338](https://github.com/dotnet/fsharp/pull/20338)) * Fix `Array.exists2` documentation examples to use equal-length arrays; the previous examples would throw `ArgumentException` at runtime instead of returning the documented `false`/`true` values. ([PR #19672](https://github.com/dotnet/fsharp/pull/19672)) * Move `Async.StartChild` to the "Starting Async Computations" docs category alongside `Async.StartChildAsTask`. ([Issue #19667](https://github.com/dotnet/fsharp/issues/19667)) * Add `InlineIfLambda` to `Array.init` ([PR #19869](https://github.com/dotnet/fsharp/pull/19869)) diff --git a/src/FSharp.Core/array2.fs b/src/FSharp.Core/array2.fs index 7f6e7f3555a..abbd80afd48 100644 --- a/src/FSharp.Core/array2.fs +++ b/src/FSharp.Core/array2.fs @@ -6,6 +6,9 @@ open System open Microsoft.FSharp.Core open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators open Microsoft.FSharp.Core.Operators.Checked +#if NET9_0_OR_GREATER +open System.Diagnostics.CodeAnalysis +#endif #nowarn "3218" // mismatch of parameter name where 'count1' --> 'length1' would shadow function in module of same name @@ -13,6 +16,12 @@ open Microsoft.FSharp.Core.Operators.Checked [] module Array2D = +#if NET9_0_OR_GREATER + [] + let private nonZeroBasedRequiresDynamicCode = + "Arrays with a non-zero lower bound cannot be created in an AOT-compiled application. Use Array2D.zeroCreate, Array2D.create or Array2D.init instead." +#endif + let inline checkNonNull argName arg = if isNull arg then nullArg argName @@ -48,36 +57,54 @@ module Array2D = if length2 < 0 then invalidArgInputMustBeNonNegative "length2" length2 (# "newarr.multi 2 !0" type ('T) length1 length2 : 'T[,] #) - [] - let zeroCreateBased (base1:int) (base2:int) (length1:int) (length2:int) = + // Not annotated: map, mapi and copy reach this through initBasedCore to preserve the bounds + // of their input, and must stay AOT-clean for the zero-based case. The public based entry + // points carry the annotation instead. + let private zeroCreateBasedCore (base1:int) (base2:int) (length1:int) (length2:int) : 'T[,] = if base1 = 0 && base2 = 0 then zeroCreate length1 length2 else #if NET9_0_OR_GREATER - // Passing the array type rather than the element type lets trimming and AOT compilation - // see the concrete 'T[,] instantiation, so this overload carries no RequiresDynamicCode - // annotation. + // Passing the array type rather than the element type keeps this overload free of + // RequiresDynamicCode. ILC cannot see the zero-base branch above, so the choice of + // overload, not that branch, is what keeps map/mapi/copy AOT-clean. (Array.CreateInstanceFromArrayType(typeof<'T[,]>, [|length1;length2|], [|base1;base2|]) :?> 'T[,]) #else (Array.CreateInstance(typeof<'T>, [|length1;length2|], [|base1;base2|]) :?> 'T[,]) #endif + let private initBasedCore base1 base2 length1 length2 initializer : 'T[,] = + let array = zeroCreateBasedCore base1 base2 length1 length2 + let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(initializer) + for i = base1 to base1 + length1 - 1 do + for j = base2 to base2 + length2 - 1 do + array.[i, j] <- f.Invoke(i, j) + array + +#if NET9_0_OR_GREATER + [] +#endif + [] + let zeroCreateBased (base1:int) (base2:int) (length1:int) (length2:int) : 'T[,] = + zeroCreateBasedCore base1 base2 length1 length2 + +#if NET9_0_OR_GREATER + [] +#endif [] let createBased base1 base2 length1 length2 (initial:'T) = - let array = (zeroCreateBased base1 base2 length1 length2 : 'T[,]) + let array = (zeroCreateBasedCore base1 base2 length1 length2 : 'T[,]) for i = base1 to base1 + length1 - 1 do for j = base2 to base2 + length2 - 1 do array.[i, j] <- initial array +#if NET9_0_OR_GREATER + [] +#endif [] - let initBased base1 base2 length1 length2 initializer = - let array = (zeroCreateBased base1 base2 length1 length2 : 'T[,]) - let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(initializer) - for i = base1 to base1 + length1 - 1 do - for j = base2 to base2 + length2 - 1 do - array.[i, j] <- f.Invoke(i, j) - array + let initBased base1 base2 length1 length2 initializer : 'T[,] = + initBasedCore base1 base2 length1 length2 initializer [] let create length1 length2 (value:'T) = @@ -122,18 +149,18 @@ module Array2D = [] let map mapping array = checkNonNull "array" array - initBased (base1 array) (base2 array) (length1 array) (length2 array) (fun i j -> mapping array.[i,j]) + initBasedCore (base1 array) (base2 array) (length1 array) (length2 array) (fun i j -> mapping array.[i,j]) [] let mapi mapping array = checkNonNull "array" array let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt(mapping) - initBased (base1 array) (base2 array) (length1 array) (length2 array) (fun i j -> f.Invoke(i, j, array.[i,j])) + initBasedCore (base1 array) (base2 array) (length1 array) (length2 array) (fun i j -> f.Invoke(i, j, array.[i,j])) [] let copy array = checkNonNull "array" array - initBased (base1 array) (base2 array) (length1 array) (length2 array) (fun i j -> array.[i,j]) + initBasedCore (base1 array) (base2 array) (length1 array) (length2 array) (fun i j -> array.[i,j]) [] let rebase array = diff --git a/tests/AheadOfTime/NativeAOT/Array2DProgram.fs b/tests/AheadOfTime/NativeAOT/Array2DProgram.fs index 311765cd233..c5c5a0959a8 100644 --- a/tests/AheadOfTime/NativeAOT/Array2DProgram.fs +++ b/tests/AheadOfTime/NativeAOT/Array2DProgram.fs @@ -17,6 +17,17 @@ let private sum (grid: int[,]) = grid |> Array2D.iteri (fun _ _ v -> total.Value <- total.Value + v) total.Value +// Array2D.createBased is annotated RequiresDynamicCode, so this is the one call in the program +// that has to suppress IL3050. Everything above it must publish clean. +[] +let private createBasedThrows rows cols = + try + Array2D.createBased 1 2 rows cols 7 |> ignore + 0 + with :? PlatformNotSupportedException -> + 1 + let run (argv: string[]) = // An empty argv means 3x4, but neither the optimizer nor ILC can prove that, so the // allocations cannot be folded to constants. The expected values below are the ones for an @@ -50,11 +61,4 @@ let run (argv: string[]) = check "copy" (sum copied) 60 check "copy.distinct" (if Object.ReferenceEquals(copied, table) then 1 else 0) 0 - let based = - try - Array2D.createBased 1 2 rows cols 7 |> ignore - 0 - with :? PlatformNotSupportedException -> - 1 - - check "createBased.throws" based 1 + check "createBased.throws" (createBasedThrows rows cols) 1 diff --git a/tests/AheadOfTime/NativeAOT/check.ps1 b/tests/AheadOfTime/NativeAOT/check.ps1 index 4ccd9c0e1aa..49a2c4c1e87 100644 --- a/tests/AheadOfTime/NativeAOT/check.ps1 +++ b/tests/AheadOfTime/NativeAOT/check.ps1 @@ -6,9 +6,9 @@ # NativeAOT analysis emits IL2026/IL2070/IL3050, TreatWarningsAsErrors turns them into errors, # and this publish fails. # -# Array2D is guarded the same way: the FSharp.Core asset this resolves must not reach -# Array.CreateInstance, which is [RequiresDynamicCode]. The app exercises the zero-based -# operations and one based allocation, which is expected to throw PlatformNotSupportedException. +# Array2D is guarded the same way: the zero-based operations must not reach Array.CreateInstance, +# which is [RequiresDynamicCode]. The based allocation is annotated, so the app suppresses IL3050 +# at that one call site and asserts the PlatformNotSupportedException the annotation warns about. $ErrorActionPreference = "Stop" diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/Array2Module.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/Array2Module.fs index 7b7f7df7aeb..54e0083f58b 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/Array2Module.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/Array2Module.fs @@ -790,3 +790,23 @@ type Array2Module() = [| 5.; 100.; 7.; 8. |]; [| 9.; 100.; 11.; 12. |] |]) + +#if NET9_0_OR_GREATER + // The based entry points carry RequiresDynamicCode so that consumers get IL3050 at their own + // call site; the zero-based ones must not, or every AOT publish warns again. Both directions + // are pinned here because only a Windows-only NativeAOT leg would otherwise catch a change. + [] + [] + [] + [] + [] + [] + [] + [] + [] + [] + member this.RequiresDynamicCodeIsOnBasedApisOnly(name: string, expected: bool) = + let m = typeof.Assembly.GetType("Microsoft.FSharp.Collections.Array2DModule").GetMethod(name) + Assert.NotNull m + Assert.Equal(expected, m.IsDefined(typeof, false)) +#endif From c806ae86a0f175486a42cac08278e7b2467dab14 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 26 Aug 2026 10:28:19 +0200 Subject: [PATCH 3/7] Trim Array2D comments and rename the private based helpers zeroCreateBasedCore/initBasedCore become zeroCreateBasedAux/initBasedAux, the suffix used for private helpers elsewhere in FSharp.Core and the compiler. The comments explained the change rather than the code. Two of them were also wrong: the AOT program claimed everything above the suppressed call had to publish clean, when the calls it guards are below it, and the EmittedIL pin claimed to catch Array2D.init regressing to a based forwarder, which it cannot since init is not inline and its body stays in FSharp.Core. The closure names in the IL baselines embed the source line, so shortening a comment moved clo@43 to clo@42. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b61ef94c-e4e0-4424-a645-a08a8b463d67 --- docs/release-notes/.FSharp.Core/11.0.100.md | 2 +- src/FSharp.Core/array2.fs | 27 +++++++++---------- tests/AheadOfTime/NativeAOT/Array2DProgram.fs | 14 +++------- tests/AheadOfTime/NativeAOT/check.ps1 | 18 +++---------- .../EmittedIL/Misc/CodeGenRenamings01.fs | 3 +-- ...ealInternalSignatureOff.OptimizeOff.il.bsl | 10 +++---- ...RealInternalSignatureOff.OptimizeOn.il.bsl | 10 +++---- ...RealInternalSignatureOn.OptimizeOff.il.bsl | 10 +++---- ....RealInternalSignatureOn.OptimizeOn.il.bsl | 10 +++---- .../Array2Module.fs | 9 ++----- 10 files changed, 43 insertions(+), 70 deletions(-) diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md index c727c5c09e3..40c9f143a71 100644 --- a/docs/release-notes/.FSharp.Core/11.0.100.md +++ b/docs/release-notes/.FSharp.Core/11.0.100.md @@ -5,7 +5,7 @@ ### Fixed * Mirror compiler-semantic attributes (e.g. `[]`, `[]`) in `.fsi` signature files to match `.fs` implementations. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880)) -* Remove the `IL3050` dynamic-code warning from zero-based `Array2D`: `create` and `init` no longer allocate through a based-array path, and the `net10.0` asset allocates based arrays with `Array.CreateInstanceFromArrayType`. On that asset `createBased`, `initBased` and `zeroCreateBased` are annotated `RequiresDynamicCode`, so callers get `IL3050` at their own call site instead of a runtime failure — a new build error for AOT-analysing consumers that treat warnings as errors. ([Language Suggestion #1454](https://github.com/fsharp/fslang-suggestions/issues/1454), [PR #20338](https://github.com/dotnet/fsharp/pull/20338)) +* Fix `Array2D` AOT compatibility on `net10.0`: zero-based operations no longer emit `IL3050`, and the `*Based` ones now warn at the call site instead of failing at runtime. ([Language Suggestion #1454](https://github.com/fsharp/fslang-suggestions/issues/1454), [PR #20338](https://github.com/dotnet/fsharp/pull/20338)) * Fix `Array.exists2` documentation examples to use equal-length arrays; the previous examples would throw `ArgumentException` at runtime instead of returning the documented `false`/`true` values. ([PR #19672](https://github.com/dotnet/fsharp/pull/19672)) * Move `Async.StartChild` to the "Starting Async Computations" docs category alongside `Async.StartChildAsTask`. ([Issue #19667](https://github.com/dotnet/fsharp/issues/19667)) * Add `InlineIfLambda` to `Array.init` ([PR #19869](https://github.com/dotnet/fsharp/pull/19869)) diff --git a/src/FSharp.Core/array2.fs b/src/FSharp.Core/array2.fs index abbd80afd48..eeef48ebbbb 100644 --- a/src/FSharp.Core/array2.fs +++ b/src/FSharp.Core/array2.fs @@ -57,24 +57,21 @@ module Array2D = if length2 < 0 then invalidArgInputMustBeNonNegative "length2" length2 (# "newarr.multi 2 !0" type ('T) length1 length2 : 'T[,] #) - // Not annotated: map, mapi and copy reach this through initBasedCore to preserve the bounds - // of their input, and must stay AOT-clean for the zero-based case. The public based entry - // points carry the annotation instead. - let private zeroCreateBasedCore (base1:int) (base2:int) (length1:int) (length2:int) : 'T[,] = + // map, mapi and copy must stay on this unannotated path; the public based entry points + // carry RequiresDynamicCode instead. + let private zeroCreateBasedAux (base1:int) (base2:int) (length1:int) (length2:int) : 'T[,] = if base1 = 0 && base2 = 0 then zeroCreate length1 length2 else #if NET9_0_OR_GREATER - // Passing the array type rather than the element type keeps this overload free of - // RequiresDynamicCode. ILC cannot see the zero-base branch above, so the choice of - // overload, not that branch, is what keeps map/mapi/copy AOT-clean. + // Unlike Array.CreateInstance, this overload carries no RequiresDynamicCode. (Array.CreateInstanceFromArrayType(typeof<'T[,]>, [|length1;length2|], [|base1;base2|]) :?> 'T[,]) #else (Array.CreateInstance(typeof<'T>, [|length1;length2|], [|base1;base2|]) :?> 'T[,]) #endif - let private initBasedCore base1 base2 length1 length2 initializer : 'T[,] = - let array = zeroCreateBasedCore base1 base2 length1 length2 + let private initBasedAux base1 base2 length1 length2 initializer : 'T[,] = + let array = zeroCreateBasedAux base1 base2 length1 length2 let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(initializer) for i = base1 to base1 + length1 - 1 do for j = base2 to base2 + length2 - 1 do @@ -86,14 +83,14 @@ module Array2D = #endif [] let zeroCreateBased (base1:int) (base2:int) (length1:int) (length2:int) : 'T[,] = - zeroCreateBasedCore base1 base2 length1 length2 + zeroCreateBasedAux base1 base2 length1 length2 #if NET9_0_OR_GREATER [] #endif [] let createBased base1 base2 length1 length2 (initial:'T) = - let array = (zeroCreateBasedCore base1 base2 length1 length2 : 'T[,]) + let array = (zeroCreateBasedAux base1 base2 length1 length2 : 'T[,]) for i = base1 to base1 + length1 - 1 do for j = base2 to base2 + length2 - 1 do array.[i, j] <- initial @@ -104,7 +101,7 @@ module Array2D = #endif [] let initBased base1 base2 length1 length2 initializer : 'T[,] = - initBasedCore base1 base2 length1 length2 initializer + initBasedAux base1 base2 length1 length2 initializer [] let create length1 length2 (value:'T) = @@ -149,18 +146,18 @@ module Array2D = [] let map mapping array = checkNonNull "array" array - initBasedCore (base1 array) (base2 array) (length1 array) (length2 array) (fun i j -> mapping array.[i,j]) + initBasedAux (base1 array) (base2 array) (length1 array) (length2 array) (fun i j -> mapping array.[i,j]) [] let mapi mapping array = checkNonNull "array" array let f = OptimizedClosures.FSharpFunc<_, _, _, _>.Adapt(mapping) - initBasedCore (base1 array) (base2 array) (length1 array) (length2 array) (fun i j -> f.Invoke(i, j, array.[i,j])) + initBasedAux (base1 array) (base2 array) (length1 array) (length2 array) (fun i j -> f.Invoke(i, j, array.[i,j])) [] let copy array = checkNonNull "array" array - initBasedCore (base1 array) (base2 array) (length1 array) (length2 array) (fun i j -> array.[i,j]) + initBasedAux (base1 array) (base2 array) (length1 array) (length2 array) (fun i j -> array.[i,j]) [] let rebase array = diff --git a/tests/AheadOfTime/NativeAOT/Array2DProgram.fs b/tests/AheadOfTime/NativeAOT/Array2DProgram.fs index c5c5a0959a8..6c6b5153e72 100644 --- a/tests/AheadOfTime/NativeAOT/Array2DProgram.fs +++ b/tests/AheadOfTime/NativeAOT/Array2DProgram.fs @@ -2,23 +2,16 @@ module Array2DProgram open System -// A small grid program, run under NativeAOT so that the publish itself is the assertion. -// check.ps1 explains which calls are guarded and why. - let private check name (actual: int) (expected: int) = if actual <> expected then Console.WriteLine $"FAILED {name}: expected {expected} but got {actual}" -// Summing through iteri gives every grid below an observable result. Without that, the optimizer -// or ILC could drop an unused allocation and silently take the code this guards out of the -// analyzed program. +// Consume every grid, so ILC cannot drop the allocations this test exists to exercise. let private sum (grid: int[,]) = let total = ref 0 grid |> Array2D.iteri (fun _ _ v -> total.Value <- total.Value + v) total.Value -// Array2D.createBased is annotated RequiresDynamicCode, so this is the one call in the program -// that has to suppress IL3050. Everything above it must publish clean. [] let private createBasedThrows rows cols = @@ -29,9 +22,8 @@ let private createBasedThrows rows cols = 1 let run (argv: string[]) = - // An empty argv means 3x4, but neither the optimizer nor ILC can prove that, so the - // allocations cannot be folded to constants. The expected values below are the ones for an - // empty argv, so check.ps1 has to run the app without arguments. + // Derive the dimensions from argv, so ILC cannot constant-fold the allocations away. + // The expected values below assume check.ps1 runs the app with no arguments. let rows = 3 + argv.Length let cols = 4 + argv.Length diff --git a/tests/AheadOfTime/NativeAOT/check.ps1 b/tests/AheadOfTime/NativeAOT/check.ps1 index 49a2c4c1e87..32d6fe532d8 100644 --- a/tests/AheadOfTime/NativeAOT/check.ps1 +++ b/tests/AheadOfTime/NativeAOT/check.ps1 @@ -5,10 +5,6 @@ # printf engine. If it regresses to printf, FSharp.Reflection becomes statically reachable, # NativeAOT analysis emits IL2026/IL2070/IL3050, TreatWarningsAsErrors turns them into errors, # and this publish fails. -# -# Array2D is guarded the same way: the zero-based operations must not reach Array.CreateInstance, -# which is [RequiresDynamicCode]. The based allocation is annotated, so the app suppresses IL3050 -# at that one call site and asserts the PlatformNotSupportedException the annotation warns about. $ErrorActionPreference = "Stop" @@ -23,18 +19,13 @@ if (-not ($LASTEXITCODE -eq 0)) { Write-Error "NativeAOT publish failed with exit code $LASTEXITCODE" -ErrorAction Stop } -# Read both target frameworks back from the project instead of hardcoding them: the app targets -# $(FSharpNetCoreProductTargetFramework) and FSharp.Core ships $(FSharpCoreShippedNetTargetFramework). -# Passing -f: above would set TargetFramework as a global property and silently override the former. +# Read the frameworks from the project; passing -f above would override TargetFramework globally. $props = dotnet msbuild "$root.fsproj" -getProperty:TargetFramework -getProperty:FSharpCoreShippedNetTargetFramework -nologo | ConvertFrom-Json $tfm = $props.Properties.TargetFramework $coreAsset = "lib/$($props.Properties.FSharpCoreShippedNetTargetFramework)/FSharp.Core.dll" -# Prove this is a genuine dogfood: the consumer must resolve the locally-packed FSharp.Core's -# shipped net asset, not the netstandard2.1 fallback. Assert on the *selected* -# compile/runtime asset under "targets" (the "libraries" manifest lists every lib folder, so a -# plain substring search would false-pass even when ns2.1 was chosen). Without this the test would -# still succeed on the ns2.1 asset and the shipped net asset would go unexercised. +# Assert on the selected compile/runtime asset, not the "libraries" manifest, which lists every +# lib folder and would false-pass on the netstandard2.1 fallback. $assets = Join-Path $PSScriptRoot "obj/project.assets.json" $assetsJson = Get-Content $assets -Raw | ConvertFrom-Json $netAssetSelected = $false @@ -43,8 +34,7 @@ foreach ($target in $assetsJson.targets.PSObject.Properties) { if ($package.Name -like "FSharp.Core/*") { $compileKeys = if ($package.Value.compile) { @($package.Value.compile.PSObject.Properties.Name) } else { @() } $runtimeKeys = if ($package.Value.runtime) { @($package.Value.runtime.PSObject.Properties.Name) } else { @() } - # FSharp.Core packs only lib/{tfm} (no ref/, no runtimes/), so compile and runtime resolve - # from the same folder; require both - anything else means a fallback was chosen. + # FSharp.Core packs only lib/{tfm}, so both must resolve there; anything else is a fallback. if (($compileKeys -contains $coreAsset) -and ($runtimeKeys -contains $coreAsset)) { $netAssetSelected = $true } diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs index ba94c0374eb..703bbe742a4 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs @@ -38,6 +38,5 @@ Array3D.get array3D 0 0 0 |> Array3D.set array3D 0 0 0 (Array4D.length1 array4D, Array4D.length2 array4D, Array4D.length3 array4D, Array4D.length4 array4D) |> ignore Array4D.get array4D 0 0 0 0 |> Array4D.set array4D 0 0 0 0 -// Pins Array2DModule::Initialize, so a regression of Array2D.init back to a -// based-array forwarder shows up here. +// Array2D.init renaming, appended here rather than above to limit IL baseline churn. Array2D.init 2 2 (fun i j -> i + j) |> ignore diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOff.OptimizeOff.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOff.OptimizeOff.il.bsl index 78f8e90b15c..983254a8a28 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOff.OptimizeOff.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOff.OptimizeOff.il.bsl @@ -190,10 +190,10 @@ } - .class auto ansi serializable sealed nested assembly beforefieldinit 'Pipe #8 input at line 43@43' + .class auto ansi serializable sealed nested assembly beforefieldinit 'Pipe #8 input at line 42@42' extends class [FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures/FSharpFunc`3 { - .field static assembly initonly class assembly/'Pipe #8 input at line 43@43' @_instance + .field static assembly initonly class assembly/'Pipe #8 input at line 42@42' @_instance .method assembly specialname rtspecialname instance void .ctor() cil managed { .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) @@ -219,8 +219,8 @@ { .maxstack 10 - IL_0000: newobj instance void assembly/'Pipe #8 input at line 43@43'::.ctor() - IL_0005: stsfld class assembly/'Pipe #8 input at line 43@43' assembly/'Pipe #8 input at line 43@43'::@_instance + IL_0000: newobj instance void assembly/'Pipe #8 input at line 42@42'::.ctor() + IL_0005: stsfld class assembly/'Pipe #8 input at line 42@42' assembly/'Pipe #8 input at line 42@42'::@_instance IL_000a: ret } @@ -709,7 +709,7 @@ IL_0254: nop IL_0255: ldc.i4.2 IL_0256: ldc.i4.2 - IL_0257: ldsfld class assembly/'Pipe #8 input at line 43@43' assembly/'Pipe #8 input at line 43@43'::@_instance + IL_0257: ldsfld class assembly/'Pipe #8 input at line 42@42' assembly/'Pipe #8 input at line 42@42'::@_instance IL_025c: call !!0[0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array2DModule::Initialize(int32, int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOff.OptimizeOn.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOff.OptimizeOn.il.bsl index c99c62c16fe..5b8728e82ff 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOff.OptimizeOn.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOff.OptimizeOn.il.bsl @@ -195,10 +195,10 @@ } - .class auto ansi serializable sealed nested assembly beforefieldinit clo@43 + .class auto ansi serializable sealed nested assembly beforefieldinit clo@42 extends class [FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures/FSharpFunc`3 { - .field static assembly initonly class assembly/clo@43 @_instance + .field static assembly initonly class assembly/clo@42 @_instance .method assembly specialname rtspecialname instance void .ctor() cil managed { .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) @@ -224,8 +224,8 @@ { .maxstack 10 - IL_0000: newobj instance void assembly/clo@43::.ctor() - IL_0005: stsfld class assembly/clo@43 assembly/clo@43::@_instance + IL_0000: newobj instance void assembly/clo@42::.ctor() + IL_0005: stsfld class assembly/clo@42 assembly/clo@42::@_instance IL_000a: ret } @@ -832,7 +832,7 @@ IL_023e: nop IL_023f: ldc.i4.2 IL_0240: ldc.i4.2 - IL_0241: ldsfld class assembly/clo@43 assembly/clo@43::@_instance + IL_0241: ldsfld class assembly/clo@42 assembly/clo@42::@_instance IL_0246: call !!0[0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array2DModule::Initialize(int32, int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOn.OptimizeOff.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOn.OptimizeOff.il.bsl index 2edc51c7c44..dbbc0980da4 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOn.OptimizeOff.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOn.OptimizeOff.il.bsl @@ -190,10 +190,10 @@ } - .class auto ansi serializable sealed nested assembly beforefieldinit 'Pipe #8 input at line 43@43' + .class auto ansi serializable sealed nested assembly beforefieldinit 'Pipe #8 input at line 42@42' extends class [FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures/FSharpFunc`3 { - .field static assembly initonly class assembly/'Pipe #8 input at line 43@43' @_instance + .field static assembly initonly class assembly/'Pipe #8 input at line 42@42' @_instance .method assembly specialname rtspecialname instance void .ctor() cil managed { .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) @@ -219,8 +219,8 @@ { .maxstack 10 - IL_0000: newobj instance void assembly/'Pipe #8 input at line 43@43'::.ctor() - IL_0005: stsfld class assembly/'Pipe #8 input at line 43@43' assembly/'Pipe #8 input at line 43@43'::@_instance + IL_0000: newobj instance void assembly/'Pipe #8 input at line 42@42'::.ctor() + IL_0005: stsfld class assembly/'Pipe #8 input at line 42@42' assembly/'Pipe #8 input at line 42@42'::@_instance IL_000a: ret } @@ -617,7 +617,7 @@ IL_022d: nop IL_022e: ldc.i4.2 IL_022f: ldc.i4.2 - IL_0230: ldsfld class assembly/'Pipe #8 input at line 43@43' assembly/'Pipe #8 input at line 43@43'::@_instance + IL_0230: ldsfld class assembly/'Pipe #8 input at line 42@42' assembly/'Pipe #8 input at line 42@42'::@_instance IL_0235: call !!0[0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array2DModule::Initialize(int32, int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOn.OptimizeOn.il.bsl b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOn.OptimizeOn.il.bsl index daaa8cd6324..c8b95b40235 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOn.OptimizeOn.il.bsl +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/Misc/CodeGenRenamings01.fs.RealInternalSignatureOn.OptimizeOn.il.bsl @@ -195,10 +195,10 @@ } - .class auto ansi serializable sealed nested assembly beforefieldinit clo@43 + .class auto ansi serializable sealed nested assembly beforefieldinit clo@42 extends class [FSharp.Core]Microsoft.FSharp.Core.OptimizedClosures/FSharpFunc`3 { - .field static assembly initonly class assembly/clo@43 @_instance + .field static assembly initonly class assembly/clo@42 @_instance .method assembly specialname rtspecialname instance void .ctor() cil managed { .custom instance void [runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) @@ -224,8 +224,8 @@ { .maxstack 10 - IL_0000: newobj instance void assembly/clo@43::.ctor() - IL_0005: stsfld class assembly/clo@43 assembly/clo@43::@_instance + IL_0000: newobj instance void assembly/clo@42::.ctor() + IL_0005: stsfld class assembly/clo@42 assembly/clo@42::@_instance IL_000a: ret } @@ -713,7 +713,7 @@ IL_023e: nop IL_023f: ldc.i4.2 IL_0240: ldc.i4.2 - IL_0241: ldsfld class assembly/clo@43 assembly/clo@43::@_instance + IL_0241: ldsfld class assembly/clo@42 assembly/clo@42::@_instance IL_0246: call !!0[0...,0...] [FSharp.Core]Microsoft.FSharp.Collections.Array2DModule::Initialize(int32, int32, class [FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2>) diff --git a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/Array2Module.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/Array2Module.fs index 54e0083f58b..37684e8a7a0 100644 --- a/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/Array2Module.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Collections/Array2Module.fs @@ -198,8 +198,7 @@ type Array2Module() = let resultStr = Array2D.copy strArr if resultStr <> strArr then Assert.Fail() - // Guards against rewriting copy as Array.Clone to escape its IL3050 warning: Clone would keep - // the runtime string[,] type and throw here, where copy allocates obj[,] and accepts the box. + // copy must return obj[,], not preserve the source's runtime string[,] type. let covariantSource = Array2D.create 1 1 "value" |> box |> unbox let covariantCopy = Array2D.copy covariantSource covariantCopy.[0, 0] <- box 42 @@ -457,8 +456,6 @@ type Array2Module() = member this.Mapi() = // integer array let intArr = Array2D.initBased 3 7 2 3 (fun i j -> i*100 + j) - // Maps int -> string, so the result element type differs from the source and mapi has to - // allocate a new based array rather than reuse the input's. let funIntToStr x y z = sprintf "%d,%d:%d" x y z let resultInt = Array2D.mapi funIntToStr intArr if resultInt <> (Array2D.initBased 3 7 2 3 (fun i j -> sprintf "%d,%d:%d" i j (i*100 + j))) then Assert.Fail() @@ -792,9 +789,7 @@ type Array2Module() = #if NET9_0_OR_GREATER - // The based entry points carry RequiresDynamicCode so that consumers get IL3050 at their own - // call site; the zero-based ones must not, or every AOT publish warns again. Both directions - // are pinned here because only a Windows-only NativeAOT leg would otherwise catch a change. + // Only a Windows-only NativeAOT leg would otherwise catch a change to either direction. [] [] [] From 9119554a63ce0acdee4386dc0e8aa9f1f5ed0feb Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 26 Aug 2026 15:30:31 +0200 Subject: [PATCH 4/7] Polyfill RequiresDynamicCode instead of guarding each use site FSharp.Core already shims BCL trim attributes as internal types in prim-types (DynamicallyAccessedMembers, since PR #14874) and applies them unguarded elsewhere. Follow that pattern for RequiresDynamicCode so array2.fs keeps a single #if for the actual API switch. ILC and ILLink match these attributes by namespace + name only, so the internal shim is honoured on netstandard consumers too. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b61ef94c-e4e0-4424-a645-a08a8b463d67 --- docs/release-notes/.FSharp.Core/11.0.100.md | 2 +- src/FSharp.Core/array2.fs | 10 ---------- src/FSharp.Core/prim-types.fs | 18 ++++++++++++++++++ src/FSharp.Core/prim-types.fsi | 18 ++++++++++++++++++ 4 files changed, 37 insertions(+), 11 deletions(-) diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md index 40c9f143a71..9f35e7800ec 100644 --- a/docs/release-notes/.FSharp.Core/11.0.100.md +++ b/docs/release-notes/.FSharp.Core/11.0.100.md @@ -5,7 +5,7 @@ ### Fixed * Mirror compiler-semantic attributes (e.g. `[]`, `[]`) in `.fsi` signature files to match `.fs` implementations. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880)) -* Fix `Array2D` AOT compatibility on `net10.0`: zero-based operations no longer emit `IL3050`, and the `*Based` ones now warn at the call site instead of failing at runtime. ([Language Suggestion #1454](https://github.com/fsharp/fslang-suggestions/issues/1454), [PR #20338](https://github.com/dotnet/fsharp/pull/20338)) +* Fix `Array2D` AOT compatibility: `create` and `init` no longer emit `IL3050`, and the `*Based` operations now warn at the call site instead of failing at runtime. ([Language Suggestion #1454](https://github.com/fsharp/fslang-suggestions/issues/1454), [PR #20338](https://github.com/dotnet/fsharp/pull/20338)) * Fix `Array.exists2` documentation examples to use equal-length arrays; the previous examples would throw `ArgumentException` at runtime instead of returning the documented `false`/`true` values. ([PR #19672](https://github.com/dotnet/fsharp/pull/19672)) * Move `Async.StartChild` to the "Starting Async Computations" docs category alongside `Async.StartChildAsTask`. ([Issue #19667](https://github.com/dotnet/fsharp/issues/19667)) * Add `InlineIfLambda` to `Array.init` ([PR #19869](https://github.com/dotnet/fsharp/pull/19869)) diff --git a/src/FSharp.Core/array2.fs b/src/FSharp.Core/array2.fs index eeef48ebbbb..7c1cb85b7be 100644 --- a/src/FSharp.Core/array2.fs +++ b/src/FSharp.Core/array2.fs @@ -6,9 +6,7 @@ open System open Microsoft.FSharp.Core open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators open Microsoft.FSharp.Core.Operators.Checked -#if NET9_0_OR_GREATER open System.Diagnostics.CodeAnalysis -#endif #nowarn "3218" // mismatch of parameter name where 'count1' --> 'length1' would shadow function in module of same name @@ -16,11 +14,9 @@ open System.Diagnostics.CodeAnalysis [] module Array2D = -#if NET9_0_OR_GREATER [] let private nonZeroBasedRequiresDynamicCode = "Arrays with a non-zero lower bound cannot be created in an AOT-compiled application. Use Array2D.zeroCreate, Array2D.create or Array2D.init instead." -#endif let inline checkNonNull argName arg = if isNull arg then @@ -78,16 +74,12 @@ module Array2D = array.[i, j] <- f.Invoke(i, j) array -#if NET9_0_OR_GREATER [] -#endif [] let zeroCreateBased (base1:int) (base2:int) (length1:int) (length2:int) : 'T[,] = zeroCreateBasedAux base1 base2 length1 length2 -#if NET9_0_OR_GREATER [] -#endif [] let createBased base1 base2 length1 length2 (initial:'T) = let array = (zeroCreateBasedAux base1 base2 length1 length2 : 'T[,]) @@ -96,9 +88,7 @@ module Array2D = array.[i, j] <- initial array -#if NET9_0_OR_GREATER [] -#endif [] let initBased base1 base2 length1 length2 initializer : 'T[,] = initBasedAux base1 base2 length1 length2 initializer diff --git a/src/FSharp.Core/prim-types.fs b/src/FSharp.Core/prim-types.fs index a4488a73fd7..3f37a596b9d 100644 --- a/src/FSharp.Core/prim-types.fs +++ b/src/FSharp.Core/prim-types.fs @@ -450,6 +450,24 @@ namespace System.Diagnostics.CodeAnalysis #endif +#if !NET7_0_OR_GREATER +namespace System.Diagnostics.CodeAnalysis + + open System + open Microsoft.FSharp.Core + + /// + /// Indicates that the specified member requires the ability to generate new code at runtime. + /// + [] + type internal RequiresDynamicCodeAttribute (message: string) = + inherit Attribute () + + member _.Message = message + +#endif + namespace Microsoft.FSharp.Core open System open System.Collections diff --git a/src/FSharp.Core/prim-types.fsi b/src/FSharp.Core/prim-types.fsi index 9a3d99a566c..c7534407596 100644 --- a/src/FSharp.Core/prim-types.fsi +++ b/src/FSharp.Core/prim-types.fsi @@ -1062,6 +1062,24 @@ namespace System.Diagnostics.CodeAnalysis #endif +#if !NET7_0_OR_GREATER +namespace System.Diagnostics.CodeAnalysis + + open System + open Microsoft.FSharp.Core + + /// + /// Indicates that the specified member requires the ability to generate new code at runtime. + /// + [] + type internal RequiresDynamicCodeAttribute = + inherit Attribute + new: string -> RequiresDynamicCodeAttribute + member Message: string + +#endif + namespace Microsoft.FSharp.Core open System From 6cc32685eaf2eace36cc77700f279a092619a0ce Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 26 Aug 2026 15:41:08 +0200 Subject: [PATCH 5/7] Correct the Array2D release note wording The based operations still fail at runtime under AOT; the warning arrives before that failure rather than replacing it. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b61ef94c-e4e0-4424-a645-a08a8b463d67 --- docs/release-notes/.FSharp.Core/11.0.100.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md index 9f35e7800ec..86508b0a575 100644 --- a/docs/release-notes/.FSharp.Core/11.0.100.md +++ b/docs/release-notes/.FSharp.Core/11.0.100.md @@ -5,7 +5,7 @@ ### Fixed * Mirror compiler-semantic attributes (e.g. `[]`, `[]`) in `.fsi` signature files to match `.fs` implementations. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880)) -* Fix `Array2D` AOT compatibility: `create` and `init` no longer emit `IL3050`, and the `*Based` operations now warn at the call site instead of failing at runtime. ([Language Suggestion #1454](https://github.com/fsharp/fslang-suggestions/issues/1454), [PR #20338](https://github.com/dotnet/fsharp/pull/20338)) +* Fix `Array2D` AOT compatibility: `create` and `init` no longer emit `IL3050`, and the `*Based` operations now warn at the call site before the runtime failure they already had. ([Language Suggestion #1454](https://github.com/fsharp/fslang-suggestions/issues/1454), [PR #20338](https://github.com/dotnet/fsharp/pull/20338)) * Fix `Array.exists2` documentation examples to use equal-length arrays; the previous examples would throw `ArgumentException` at runtime instead of returning the documented `false`/`true` values. ([PR #19672](https://github.com/dotnet/fsharp/pull/19672)) * Move `Async.StartChild` to the "Starting Async Computations" docs category alongside `Async.StartChildAsTask`. ([Issue #19667](https://github.com/dotnet/fsharp/issues/19667)) * Add `InlineIfLambda` to `Array.init` ([PR #19869](https://github.com/dotnet/fsharp/pull/19869)) From 85550e46e87ef22849f9fb3e2e75cbf984427299 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Wed, 26 Aug 2026 15:53:49 +0200 Subject: [PATCH 6/7] Scope the release note per asset and use the AOT suppression category map, mapi and copy only become IL3050-clean in the net10.0 asset; the netstandard assets keep Array.CreateInstance. rebase goes through init and is clean everywhere. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b61ef94c-e4e0-4424-a645-a08a8b463d67 --- docs/release-notes/.FSharp.Core/11.0.100.md | 2 +- tests/AheadOfTime/NativeAOT/Array2DProgram.fs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/release-notes/.FSharp.Core/11.0.100.md b/docs/release-notes/.FSharp.Core/11.0.100.md index 86508b0a575..4a2a8cc02be 100644 --- a/docs/release-notes/.FSharp.Core/11.0.100.md +++ b/docs/release-notes/.FSharp.Core/11.0.100.md @@ -5,7 +5,7 @@ ### Fixed * Mirror compiler-semantic attributes (e.g. `[]`, `[]`) in `.fsi` signature files to match `.fs` implementations. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880)) -* Fix `Array2D` AOT compatibility: `create` and `init` no longer emit `IL3050`, and the `*Based` operations now warn at the call site before the runtime failure they already had. ([Language Suggestion #1454](https://github.com/fsharp/fslang-suggestions/issues/1454), [PR #20338](https://github.com/dotnet/fsharp/pull/20338)) +* Fix `Array2D` AOT compatibility: `create`, `init` and `rebase` no longer emit `IL3050`; `map`, `mapi` and `copy` no longer emit it on `net10.0`; the `*Based` operations now warn at the call site before the runtime failure they already had. ([Language Suggestion #1454](https://github.com/fsharp/fslang-suggestions/issues/1454), [PR #20338](https://github.com/dotnet/fsharp/pull/20338)) * Fix `Array.exists2` documentation examples to use equal-length arrays; the previous examples would throw `ArgumentException` at runtime instead of returning the documented `false`/`true` values. ([PR #19672](https://github.com/dotnet/fsharp/pull/19672)) * Move `Async.StartChild` to the "Starting Async Computations" docs category alongside `Async.StartChildAsTask`. ([Issue #19667](https://github.com/dotnet/fsharp/issues/19667)) * Add `InlineIfLambda` to `Array.init` ([PR #19869](https://github.com/dotnet/fsharp/pull/19869)) diff --git a/tests/AheadOfTime/NativeAOT/Array2DProgram.fs b/tests/AheadOfTime/NativeAOT/Array2DProgram.fs index 6c6b5153e72..e148c85c103 100644 --- a/tests/AheadOfTime/NativeAOT/Array2DProgram.fs +++ b/tests/AheadOfTime/NativeAOT/Array2DProgram.fs @@ -12,7 +12,7 @@ let private sum (grid: int[,]) = grid |> Array2D.iteri (fun _ _ v -> total.Value <- total.Value + v) total.Value -[] let private createBasedThrows rows cols = try From 791342d9bac60ed700eafb6a1441e34536d7318a Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 10 Sep 2026 14:18:41 +0200 Subject: [PATCH 7/7] Test NativeAOT against the shipped FSharp.Core target framework Keep the AOT consumer on the shipped Core framework rather than the compiler's development framework. This avoids requesting unavailable development NativeAOT packs while retaining the local compiler and selected-asset checks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/AheadOfTime/NativeAOT/NativeAOT_Test.fsproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/AheadOfTime/NativeAOT/NativeAOT_Test.fsproj b/tests/AheadOfTime/NativeAOT/NativeAOT_Test.fsproj index e175b7b281c..514972a780a 100644 --- a/tests/AheadOfTime/NativeAOT/NativeAOT_Test.fsproj +++ b/tests/AheadOfTime/NativeAOT/NativeAOT_Test.fsproj @@ -2,7 +2,7 @@ Exe - $(FSharpNetCoreProductTargetFramework) + $(FSharpCoreShippedNetTargetFramework) preview true