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 79543c63afb..4085ae6b287 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 FS3918. ([Issue #20295](https://github.com/dotnet/fsharp/issues/20295), [PR #20491](https://github.com/dotnet/fsharp/pull/20491)) +* 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), [PR #20491](https://github.com/dotnet/fsharp/pull/20491)) * Fix internal error FS0192 "Iterate2D" when a `[]` parameter auto-quotes an argument that captures a not-yet-generalized use of an inferred generically-recursive function. The auto-quoted (`Expr.WithValue`) copy now keeps a fresh link to the recursive-value use so it receives the same inferred type arguments as the executable expression at the letrec point. ([Issue #20379](https://github.com/dotnet/fsharp/issues/20379)) * Fix `StackOverflowException` when checking a long `seq { ... }` body. The `expr; expr; ...` spine of a sequence expression was recursed without a stack guard, and the post-inference `CheckNoReraise` walked the resulting right-nested `Seq.append`/`Seq.delay` tree with unguarded free-variable options, so a few hundred (Debug) to a few thousand (Release) implicit-yield elements overflowed a 1 MB thread-pool thread in the IDE. `tcSequenceExprBodyAsSequenceOrStatement` now runs under `cenv.stackGuard` and `CheckNoReraise` uses `CollectLocalsWithStackGuard`. * Fix `StackOverflowException` when checking a long `seq { ... }` body. The `expr; expr; ...` spine of a sequence expression was recursed without a stack guard, and the post-inference `CheckNoReraise` walked the resulting right-nested `Seq.append`/`Seq.delay` tree with unguarded free-variable options, so a few hundred (Debug) to a few thousand (Release) implicit-yield elements overflowed a 1 MB thread-pool thread in the IDE. `tcSequenceExprBodyAsSequenceOrStatement` now runs under `cenv.stackGuard` and `CheckNoReraise` uses `CollectLocalsWithStackGuard`. ([PR #20480](https://github.com/dotnet/fsharp/pull/20480)) diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index 271b592e9da..0b2642bfabc 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -1274,6 +1274,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 FS3918. 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 @@ -3068,6 +3074,7 @@ let CodeGenThen (cenv: cenv) mgbuf (entryPointInfo, methodName, eenv, alreadyUse cgbuf { eenv with withinSEH = false + withinExnHandler = false insideFinallyOrFaultHandler = false liveLocals = IntMap.empty () innerVals = innerVals @@ -3179,6 +3186,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 () -> @@ -4730,21 +4752,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 -> @@ -5191,6 +5239,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 @@ -5261,6 +5310,7 @@ and GenTryWith cenv cgbuf eenv (e1, valForFilter: Val, filterExpr, valForHandler let eenvinner = { eenvinner with + withinExnHandler = true exitSequel = exitSequel } @@ -5306,6 +5356,7 @@ and GenTryFinally cenv cgbuf eenv (bodyExpr, handlerExpr, m, resTy, spTry, spFin let eenvHandler = { eenvinner with + withinExnHandler = true insideFinallyOrFaultHandler = true } @@ -5613,6 +5664,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) @@ -5822,17 +5880,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 @@ -13096,6 +13178,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 10f1a8f0c7e..3d8ea307681 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1818,3 +1818,4 @@ featureTypeArgumentDependencyOrdering,"ordering explicit type arguments by their 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,tcOptimizeClosureIfNotInlinedRequiresInlineIfLambdaAndMultiArg,"The 'OptimizeClosureIfNotInlined' attribute requires 'InlineIfLambda' on a separately curried parameter of an inline function or method whose type is a curried F# function of arity 2 to 5." 3917,implMissingOptimizeClosureIfNotInlined,"The 'OptimizeClosureIfNotInlined' attribute is present in the signature but not the implementation." +3918,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 8127baedfd9..22c2137ffa6 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 cb336b2298d..20734f0521f 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 b22962bcceb..d0a055793fe 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 ecd8f38051e..c91eda01c14 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 72af8575618..6a31a358f45 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 4a155e4c728..9931e0170f6 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 3b72d4a6fdb..e367e70ef29 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 395ef0edb6a..492cdb9f0ed 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 2f0dab2febb..493264ab585 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 01be9751089..265fc1c96cf 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 17d16c559e6..c55dba1d12c 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 f528e6707f7..78663a7d00b 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 f1054fb729f..c2596fbdae1 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..d1a349e41ee 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 FS3918. + [] + [ 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 3918 + + // 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 3918 + + // 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 + }""" ]