diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
index ed1e382d6c7..439f1eb82c0 100644
--- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
+++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
@@ -1,5 +1,7 @@
### Fixed
+* Fix `NativePtr.stackalloc` used inside the `with` handler, filter or `finally` block of a `try` expression producing an assembly that throws `InvalidProgramException` at method load. The `localloc` IL instruction it emits is rejected by the runtime inside an exception-handling region, so this is now reported at compile time as error FS3916. ([Issue #20295](https://github.com/dotnet/fsharp/issues/20295))
+* Fix `NativePtr.stackalloc` used as a chained base-constructor argument producing an assembly that throws `InvalidProgramException` at method load; the base-constructor arguments are now hoisted into locals before the uninitialized `this` is pushed. ([Issue #20295](https://github.com/dotnet/fsharp/issues/20295))
* Fix `NativePtr.stackalloc` nested in a larger expression (e.g. a call argument or the right of an assignment) producing an assembly that throws `InvalidProgramException` at load. ([Issue #8083](https://github.com/dotnet/fsharp/issues/8083), [PR #20302](https://github.com/dotnet/fsharp/pull/20302))
* Fix internal error "Unexpected generalized type variables when compiling an active pattern" when an active pattern is used in a `let` binding whose right-hand side is a generic value, e.g. `let (T) = id`. Such a binding is now checked like the equivalent `match` and is not generalized. ([Issue #16856](https://github.com/dotnet/fsharp/issues/16856), [PR #20383](https://github.com/dotnet/fsharp/pull/20383))
* Fix Release-only (`--optimize+`) `System.InvalidProgramException` from `Seq.collect` / `yield!` over a value-type (struct) collection implementing `seq<'T>` (e.g. `ImmutableArray<_>`) when materialised with `List.ofSeq` / `Seq.toList` / `Seq.toArray` or a list/array comprehension. The collector lowering now boxes a struct sub-collection to `seq<'T>` before calling `AddMany`/`AddManyAndClose` (matching the coercion the type checker already inserts for `yield!`), and uses `unit` as the try/finally result type instead of the body type (removing a spurious `ldnull` store). ([Issue #20203](https://github.com/dotnet/fsharp/issues/20203))
diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs
index ceeec0a69ec..b38575123f7 100644
--- a/src/Compiler/CodeGen/IlxGen.fs
+++ b/src/Compiler/CodeGen/IlxGen.fs
@@ -1261,6 +1261,12 @@ and IlxGenEnv =
/// Are we under the scope of a try, catch or finally? If so we can't tailcall. SEH = structured exception handling
withinSEH: bool
+ /// Are we within the 'with'/filter/'finally'/fault handler region of a 'try' (but not merely its try body)?
+ /// The JIT rejects the 'localloc' IL instruction (emitted by NativePtr.stackalloc) inside such a region, so
+ /// emitting it here is reported as error FS3916. This is checked at codegen, after inlining and closure
+ /// conversion, so an escaping closure whose 'localloc' lives in its own method stays legal.
+ withinExnHandler: bool
+
/// Suppresses filter block emission inside finally/fault handlers (workaround for dotnet/runtime#112406).
insideFinallyOrFaultHandler: bool
@@ -3055,6 +3061,7 @@ let CodeGenThen (cenv: cenv) mgbuf (entryPointInfo, methodName, eenv, alreadyUse
cgbuf
{ eenv with
withinSEH = false
+ withinExnHandler = false
insideFinallyOrFaultHandler = false
liveLocals = IntMap.empty ()
innerVals = innerVals
@@ -3166,6 +3173,21 @@ let ComputeDebugPointForBinding g bind =
// Generate expressions
//-------------------------------------------------------------------------
+/// True if evaluating this expression may emit the 'localloc' IL instruction (e.g. NativePtr.stackalloc,
+/// which the optimizer inlines to inline IL containing 'localloc' before IlxGen runs).
+let exprMayLocalloc expr =
+ (false, expr)
+ ||> FoldExpr
+ { ExprFolder0 with
+ exprIntercept =
+ (fun _exprF noInterceptF z expr ->
+ z
+ || (match expr with
+ | Expr.Op(TOp.ILAsm(instrs, _), _, _, _) -> instrs |> List.contains I_localloc
+ | _ -> false)
+ || noInterceptF false expr)
+ }
+
let rec GenExpr cenv cgbuf eenv (expr: Expr) sequel =
cenv.stackGuard.Guard(fun () ->
@@ -4717,21 +4739,47 @@ and GenApp (cenv: cenv) cgbuf eenv (f, fty, tyargs, curriedArgs, m) sequel =
else
mspec.DeclaringType
- if isSuperInit || isSelfInit then
- CG.EmitInstr cgbuf (pop 0) (Push [ thisTy ]) mkLdarg0
-
let pendingUninitializedThis = (isSuperInit || isSelfInit) && not valu
- if pendingUninitializedThis then
+ let genArgs () =
+ if not cenv.g.generateWitnesses || witnessInfos.IsEmpty then
+ () // no witness args
+ else
+ let _ctyargs, mtyargs = List.splitAt ctps.Length tyargs
+ GenWitnessArgs cenv cgbuf eenv m mtps mtyargs
+
+ GenUntupledArgsDiscardingLoneUnit cenv cgbuf eenv m vref.NumObjArgs curriedArgInfos nowArgs
+
+ // An uninitialized 'this' cannot be spilled, so a 'localloc' emitted by a base/self-ctor
+ // argument while 'this' is pending on the stack yields invalid IL (InvalidProgramException at
+ // load). When that can happen, evaluate the args into locals first (at a clean stack), then
+ // push 'this' and reload them; left-to-right evaluation order is preserved and ordinary ctors
+ // are unaffected.
+ let hoistArgsBeforeThis =
+ pendingUninitializedThis && List.exists exprMayLocalloc nowArgs
+
+ if hoistArgsBeforeThis then
+ let stackBefore = cgbuf.GetCurrentStack()
+ genArgs ()
+
+ let argTys =
+ let stackAfter = cgbuf.GetCurrentStack()
+ stackAfter |> List.truncate (stackAfter.Length - stackBefore.Length)
+
+ let argLocals = [ for ty in argTys -> cgbuf.SpillToLocal(ty, false) ]
+ CG.EmitInstr cgbuf (pop 0) (Push [ thisTy ]) mkLdarg0
cgbuf.StartUninitializedThisOnStack()
- if not cenv.g.generateWitnesses || witnessInfos.IsEmpty then
- () // no witness args
+ for local in List.rev argLocals do
+ cgbuf.ReloadFromLocal local
else
- let _ctyargs, mtyargs = List.splitAt ctps.Length tyargs
- GenWitnessArgs cenv cgbuf eenv m mtps mtyargs
+ if isSuperInit || isSelfInit then
+ CG.EmitInstr cgbuf (pop 0) (Push [ thisTy ]) mkLdarg0
+
+ if pendingUninitializedThis then
+ cgbuf.StartUninitializedThisOnStack()
- GenUntupledArgsDiscardingLoneUnit cenv cgbuf eenv m vref.NumObjArgs curriedArgInfos nowArgs
+ genArgs ()
// Generate laterArgs (for effects) and save
LocalScope "callstack" cgbuf (fun scopeMarks ->
@@ -5178,6 +5226,7 @@ and GenTryWith cenv cgbuf eenv (e1, valForFilter: Val, filterExpr, valForHandler
let eenvinner =
{ eenvinner with
+ withinExnHandler = true
exitSequel = sequelOnBranches
}
// We emit the debug point for the 'with' keyword span on the start of the filter
@@ -5248,6 +5297,7 @@ and GenTryWith cenv cgbuf eenv (e1, valForFilter: Val, filterExpr, valForHandler
let eenvinner =
{ eenvinner with
+ withinExnHandler = true
exitSequel = exitSequel
}
@@ -5293,6 +5343,7 @@ and GenTryFinally cenv cgbuf eenv (bodyExpr, handlerExpr, m, resTy, spTry, spFin
let eenvHandler =
{ eenvinner with
+ withinExnHandler = true
insideFinallyOrFaultHandler = true
}
@@ -5600,6 +5651,13 @@ and GenAsmCode cenv cgbuf eenv (il, tyargs, args, returnTys, m) sequel =
&& ilReturnTys |> List.forall (fun ty -> ty <> ILType.Void)
->
+ // The JIT rejects 'localloc' inside an exception-handling region, producing an
+ // InvalidProgramException at method load. By this point inlining and closure conversion have run,
+ // so eenv.withinExnHandler reflects the true handler region: an escaping closure carrying the
+ // 'localloc' into its own method has had the flag reset and stays legal.
+ if eenv.withinExnHandler then
+ errorR (Error(FSComp.SR.chkNativePtrStackallocInHandler (), m))
+
CG.EmitLocallocCode cgbuf (fun () ->
GenExprs cenv cgbuf eenv args
CG.EmitInstrs cgbuf (pop args.Length) (Push ilReturnTys) ilAfterInst)
@@ -5809,17 +5867,41 @@ and GenILCall
else
ilMethSpec.DeclaringType
- // Load the 'this' pointer to pass to the superclass constructor. This argument is not
- // in the expression tree since it can't be treated like an ordinary value
- if isSuperInit then
- CG.EmitInstr cgbuf (pop 0) (Push [ thisTy ]) mkLdarg0
-
+ // An uninitialized 'this' cannot be spilled, so a 'localloc' emitted by a base-ctor argument while
+ // 'this' is pending on the stack produces invalid IL (InvalidProgramException at load). When that
+ // can happen, evaluate the args into locals first (at a clean stack), then push 'this' and reload
+ // them. Left-to-right evaluation order is preserved; ordinary base ctors are unaffected.
let pendingUninitializedThis = isSuperInit && not valu
- if pendingUninitializedThis then
+ let hoistArgsBeforeThis =
+ pendingUninitializedThis && List.exists exprMayLocalloc argExprs
+
+ if hoistArgsBeforeThis then
+ let g = cenv.g
+
+ let argLocals =
+ [
+ for argExpr in argExprs ->
+ let ilTy = argExpr |> tyOfExpr g |> GenType cenv m eenv.tyenv
+ GenExpr cenv cgbuf eenv argExpr Continue
+ cgbuf.SpillToLocal(ilTy, false)
+ ]
+
+ CG.EmitInstr cgbuf (pop 0) (Push [ thisTy ]) mkLdarg0
cgbuf.StartUninitializedThisOnStack()
- GenExprs cenv cgbuf eenv argExprs
+ for local in argLocals do
+ cgbuf.ReloadFromLocal local
+ else
+ // Load the 'this' pointer to pass to the superclass constructor. This argument is not
+ // in the expression tree since it can't be treated like an ordinary value
+ if isSuperInit then
+ CG.EmitInstr cgbuf (pop 0) (Push [ thisTy ]) mkLdarg0
+
+ if pendingUninitializedThis then
+ cgbuf.StartUninitializedThisOnStack()
+
+ GenExprs cenv cgbuf eenv argExprs
let il =
if newobj then
@@ -13066,6 +13148,7 @@ let GetEmptyIlxGenEnv (g: TcGlobals) ccu =
innerVals = []
sigToImplRemapInfo = [] (* "module remap info" *)
withinSEH = false
+ withinExnHandler = false
insideFinallyOrFaultHandler = false
isInLoop = false
initLocals = true
diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt
index 7a53eae36f3..06c4e93c112 100644
--- a/src/Compiler/FSComp.txt
+++ b/src/Compiler/FSComp.txt
@@ -1821,3 +1821,4 @@ featureRecordSpreads,"record type and expression spreads"
3913,tcExtendedLayoutCannotBeUsedOnUnions,"The 'ExtendedLayoutAttribute' cannot be applied to discriminated unions"
3914,tcExtendedLayoutStructMustHaveInstanceField,"A struct with the 'ExtendedLayoutAttribute' must have at least one instance field"
3915,tcTupleTypeExtensionTooManyElements,"Tuple type extensions are supported only for tuples of up to 7 elements, but this tuple type has %d elements. Extensions of larger tuples are not supported."
+3916,chkNativePtrStackallocInHandler,"'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded."
diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf
index 9649b05a722..8ada5a8834d 100644
--- a/src/Compiler/xlf/FSComp.txt.cs.xlf
+++ b/src/Compiler/xlf/FSComp.txt.cs.xlf
@@ -132,6 +132,11 @@
The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface.
+
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+
+ The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way.Člen nebo funkce „{0}“ má atribut „TailCallAttribute“, ale nepoužívá se koncovým (tail) rekurzivním způsobem.
diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf
index 80c89f42d06..a1f16d17807 100644
--- a/src/Compiler/xlf/FSComp.txt.de.xlf
+++ b/src/Compiler/xlf/FSComp.txt.de.xlf
@@ -132,6 +132,11 @@
The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface.
+
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+
+ The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way.Der Member oder die Funktion "{0}" weist das Attribut "TailCallAttribute" auf, wird jedoch nicht endrekursiv verwendet.
diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf
index 3452a9f4074..afb4e5f07af 100644
--- a/src/Compiler/xlf/FSComp.txt.es.xlf
+++ b/src/Compiler/xlf/FSComp.txt.es.xlf
@@ -132,6 +132,11 @@
The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface.
+
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+
+ The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way.El miembro o la función “{0}” tiene el atributo “TailCallAttribute”, pero no se usa de forma de recursión de cola.
diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf
index 1c029f7d57c..b7828962564 100644
--- a/src/Compiler/xlf/FSComp.txt.fr.xlf
+++ b/src/Compiler/xlf/FSComp.txt.fr.xlf
@@ -132,6 +132,11 @@
The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface.
+
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+
+ The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way.Le membre ou la fonction « {0} » possède l'attribut « TailCallAttribute », mais n'est pas utilisé de manière récursive.
diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf
index 8006c24faa0..94a7c0fbab6 100644
--- a/src/Compiler/xlf/FSComp.txt.it.xlf
+++ b/src/Compiler/xlf/FSComp.txt.it.xlf
@@ -132,6 +132,11 @@
The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface.
+
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+
+ The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way.Il membro o la funzione "{0}" ha l'attributo "TailCallAttribute", ma non è in uso in modo ricorsivo finale.
diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf
index 5dffc6c4d0a..2a8e7665298 100644
--- a/src/Compiler/xlf/FSComp.txt.ja.xlf
+++ b/src/Compiler/xlf/FSComp.txt.ja.xlf
@@ -132,6 +132,11 @@
The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface.
+
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+
+ The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way.メンバーまたは関数 '{0}' には 'TailCallAttribute' 属性がありますが、末尾の再帰的な方法では使用されていません。
diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf
index 841f583de03..8677a6549f9 100644
--- a/src/Compiler/xlf/FSComp.txt.ko.xlf
+++ b/src/Compiler/xlf/FSComp.txt.ko.xlf
@@ -132,6 +132,11 @@
The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface.
+
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+
+ The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way.멤버 또는 함수 '{0}'에 'TailCallAttribute' 특성이 있지만 비상 재귀적인 방식으로 사용되고 있지 않습니다.
diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf
index 0696a3f904f..53c7cb6f5e9 100644
--- a/src/Compiler/xlf/FSComp.txt.pl.xlf
+++ b/src/Compiler/xlf/FSComp.txt.pl.xlf
@@ -132,6 +132,11 @@
The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface.
+
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+
+ The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way.Składowa lub funkcja „{0}” ma atrybut „TailCallAttribute”, ale nie jest używana w sposób cykliczny końca.
diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf
index 92fc3e6c918..48a7a706712 100644
--- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf
+++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf
@@ -132,6 +132,11 @@
The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface.
+
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+
+ The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way.O membro ou a função "{0}" tem o atributo "TailCallAttribute", mas não está sendo usado de maneira recursiva em cauda.
diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf
index e4fefadb0bf..6962d93e1ff 100644
--- a/src/Compiler/xlf/FSComp.txt.ru.xlf
+++ b/src/Compiler/xlf/FSComp.txt.ru.xlf
@@ -132,6 +132,11 @@
The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface.
+
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+
+ The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way.Элемент или функция "{0}" содержит атрибут "TailCallAttribute", но не используется в рекурсивном хвостовом режиме.
diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf
index fbcca933879..986fca815dc 100644
--- a/src/Compiler/xlf/FSComp.txt.tr.xlf
+++ b/src/Compiler/xlf/FSComp.txt.tr.xlf
@@ -132,6 +132,11 @@
The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface.
+
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+
+ The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way.Üye veya '{0}' işlevi, 'TailCallAttribute' özniteliğine sahip ancak kuyruk özyinelemeli bir şekilde kullanılmıyor.
diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf
index e38cf4f9d73..2f509732719 100644
--- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf
+++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf
@@ -132,6 +132,11 @@
The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface.
+
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+
+ The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way.成员或函数“{0}”具有 "TailCallAttribute" 属性,但未以尾递归方式使用。
diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf
index cafc3b32a6a..836c32e0f30 100644
--- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf
+++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf
@@ -132,6 +132,11 @@
The interface '{0}' cannot be used as a type argument because the static abstract member '{1}' does not have a most specific implementation in the interface.
+
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+ 'NativePtr.stackalloc' cannot be used inside the 'with' handler, filter or 'finally' block of a 'try' expression. The 'localloc' IL instruction this emits is rejected inside an exception-handling region, producing a 'System.InvalidProgramException' when the method is loaded.
+
+ The member or function '{0}' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way.成員或函式 '{0}' 具有 'TailCallAttribute' 屬性,但未以尾遞迴方式使用。
diff --git a/tests/FSharp.Compiler.ComponentTests/Libraries/NativeInterop.fs b/tests/FSharp.Compiler.ComponentTests/Libraries/NativeInterop.fs
index f44304fad85..593d8ebcf08 100644
--- a/tests/FSharp.Compiler.ComponentTests/Libraries/NativeInterop.fs
+++ b/tests/FSharp.Compiler.ComponentTests/Libraries/NativeInterop.fs
@@ -171,3 +171,204 @@ let call (s: Sink) =
IL_000f: callvirt instance int32 Test/Sink::Put(native int)
IL_0014: ret
}""" ]
+
+ // Regression tests for https://github.com/dotnet/fsharp/issues/20295 (Case 1): 'NativePtr.stackalloc'
+ // emits the 'localloc' IL instruction, which the JIT rejects inside an exception-handling region.
+ // Such code used to compile and then throw InvalidProgramException at method load; it must now be
+ // rejected at compile time with FS3916.
+ []
+ [ NativePtr.stackalloc 1 |> ignore")>]
+ [ NativePtr.stackalloc 1 |> ignore")>]
+ [ 1 |> ignore")>]
+ [ (try () with _ -> NativePtr.stackalloc 1 |> ignore)")>]
+ // An immediately-applied lambda in a handler is inlined into the handler's IL region by the
+ // optimizer, so its 'localloc' still lands inside the exception region and must be rejected.
+ [ (fun () -> NativePtr.stackalloc 1 |> ignore) ()")>]
+ let ``stackalloc in a handler is rejected`` (handler: string) =
+ $"""
+module Test
+open Microsoft.FSharp.NativeInterop
+let f () = {handler}
+"""
+ |> FSharp
+ |> withNoWarn 9
+ |> compile
+ |> shouldFail
+ |> withErrorCode 3916
+
+ // A 'let inline' wrapper around 'stackalloc' is inlined into the handler's IL region, so its
+ // 'localloc' still lands inside the exception region and must be rejected. The pre-codegen syntactic
+ // check missed this because the wrapper hid the 'stackalloc' call behind an inlinable function.
+ []
+ let ``stackalloc via an inline wrapper inside a handler is rejected`` () =
+ FSharp """
+module Test
+open Microsoft.FSharp.NativeInterop
+let inline alloc () = NativePtr.stackalloc 1 |> ignore
+let f () = try () with _ -> alloc ()
+"""
+ |> withNoWarn 9
+ |> compile
+ |> shouldFail
+ |> withErrorCode 3916
+
+ // An escaping closure defined in a handler is compiled to its own method, so its 'localloc' lives
+ // outside the exception region and is legal. Such code must not be rejected (regression guard against
+ // the pre-codegen syntactic check's false positive).
+ []
+ let ``stackalloc in an escaping closure inside a handler is allowed`` () =
+ FSharp """
+module Test
+open Microsoft.FSharp.NativeInterop
+let f () =
+ try ()
+ with _ ->
+ let g = fun () -> NativePtr.stackalloc 1 |> ignore
+ System.Action(g).Invoke()
+[]
+let main _ =
+ f ()
+ printfn "ran-closure"
+ 0
+"""
+ |> withNoWarn 9
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "ran-closure"
+
+ // 'localloc' is legal in the protected 'try' body itself (only handler/filter/finally/fault
+ // regions reject it), so 'stackalloc' directly inside a 'try' must still compile.
+ []
+ let ``stackalloc in the try body is allowed`` () =
+ FSharp """
+module Test
+open Microsoft.FSharp.NativeInterop
+let f () = try NativePtr.stackalloc 1 |> ignore with _ -> ()
+"""
+ |> withNoWarn 9
+ |> compile
+ |> shouldSucceed
+
+ []
+ let ``stackalloc in an object-expression method inside a handler is allowed`` () =
+ FSharp """
+module Test
+open Microsoft.FSharp.NativeInterop
+let f () =
+ try ()
+ with _ ->
+ let d = { new System.IDisposable with member _.Dispose() = NativePtr.stackalloc 1 |> ignore }
+ d.Dispose()
+[]
+let main _ =
+ f ()
+ printfn "ok"
+ 0
+"""
+ |> withNoWarn 9
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "ok"
+
+ []
+ let ``stackalloc outside any try compiles and runs`` () =
+ FSharp """
+module Test
+open Microsoft.FSharp.NativeInterop
+[]
+let main _ =
+ NativePtr.stackalloc 1 |> ignore
+ printfn "ok"
+ 0
+"""
+ |> withNoWarn 9
+ |> compileExeAndRun
+ |> shouldSucceed
+
+ []
+ let ``handler without stackalloc is unaffected`` () =
+ FSharp """
+module Test
+let f () = try () with _ -> printfn "handled"
+"""
+ |> compile
+ |> shouldSucceed
+
+ // Regression tests for https://github.com/dotnet/fsharp/issues/20295 (Case 2): a 'NativePtr.stackalloc'
+ // used as a chained base-constructor argument loads the uninitialized 'this' before evaluating the
+ // argument, so its 'localloc' ran with 'this' pending on the stack and could not be spilled - the
+ // emitted IL threw InvalidProgramException at load. The args are now hoisted into locals before 'this'.
+ []
+ // simple nativeptr base-ctor arg
+ [) = class end",
+ "type B() = inherit A(NativePtr.stackalloc 1)")>]
+ // stackalloc as one of several base-ctor args
+ [) = class end",
+ "type B() = inherit A(1, NativePtr.stackalloc 1)")>]
+ // generic base type instantiated concretely
+ [(p: nativeptr<'T>) = class end",
+ "type B() = inherit A(NativePtr.stackalloc 1)")>]
+ let ``stackalloc as a base-ctor argument compiles and runs`` (baseType: string) (derived: string) =
+ $"""
+module Test
+open Microsoft.FSharp.NativeInterop
+{baseType}
+{derived}
+[]
+let main _ =
+ B() |> ignore
+ printfn "ok"
+ 0
+"""
+ |> FSharp
+ |> withNoWarn 9
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "ok"
+
+ // The hoist evaluates the base-ctor args left-to-right into locals before pushing 'this'; a
+ // side-effecting normal arg before the stackalloc arg must still run first.
+ []
+ let ``stackalloc base-ctor argument preserves left-to-right order`` () =
+ FSharp """
+module Test
+open Microsoft.FSharp.NativeInterop
+let trace = System.Text.StringBuilder()
+let step (name: string) x = trace.Append name |> ignore; x
+type A(n: int, p: nativeptr) = class end
+type B() = inherit A(step "a" 1, step "b" (NativePtr.stackalloc 1))
+[]
+let main _ =
+ B() |> ignore
+ if string trace <> "ab" then failwithf "wrong order: %O" trace
+ printfn "ok"
+ 0
+"""
+ |> withNoWarn 9
+ |> compileExeAndRun
+ |> shouldSucceed
+ |> withStdOutContains "ok"
+
+ // No-regression: an ordinary base ctor without a 'localloc' argument must not hoist - the arg is
+ // pushed directly onto 'this', with no extra local introduced by the hoist.
+ []
+ let ``ordinary base-ctor argument is not hoisted`` () =
+ FSharp """
+module Test
+type A(n: int) = class end
+type B() = inherit A(1)
+"""
+ |> compile
+ |> shouldSucceed
+ |> verifyILContains [
+ """.method public specialname rtspecialname instance void .ctor() cil managed
+ {
+
+ .maxstack 8
+ IL_0000: ldarg.0
+ IL_0001: ldc.i4.1
+ IL_0002: callvirt instance void Test/A::.ctor(int32)
+ IL_0007: ldarg.0
+ IL_0008: pop
+ IL_0009: ret
+ }""" ]