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 + }""" ]