Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a54bd68
Use specialized stelem/ldelem instructions for primitive types
T-Gro Feb 26, 2026
85a7def
Fix source-level ILVerify errors: HashMultiMap interface upcast and R…
T-Gro Feb 27, 2026
e91c70b
Revert "Fix source-level ILVerify errors: HashMultiMap interface upca…
T-Gro Feb 27, 2026
6cb3970
Fix ILVerify errors: callvirt on value types and interface join point…
T-Gro Feb 27, 2026
11e16ca
Fix ILVerify: avoid filter blocks inside finally handlers
T-Gro Feb 28, 2026
e297460
Fix witness field misalignment in state machine struct initialization
T-Gro Feb 28, 2026
51f05fd
Add ILGen codegen benchmarks comparing SDK vs local compiler
T-Gro Mar 1, 2026
6c0f56b
remove superflous comments
T-Gro Mar 2, 2026
8d4513d
Add release notes for ILVerify codegen fixes
T-Gro Mar 2, 2026
02f9ed0
fantomas
T-Gro Mar 2, 2026
55414ad
Fix CI baseline mismatches from codegen changes
T-Gro Mar 2, 2026
7cf76d2
Fix ILVerify empty baseline check: filter to error lines only
T-Gro Mar 2, 2026
91f1c0b
Merge branch 'main' into eng/tackle-ilverify-errors
T-Gro Mar 2, 2026
e815cfc
Merge branch 'main' into eng/tackle-ilverify-errors
T-Gro Mar 3, 2026
ee090de
Merge branch 'main' into eng/tackle-ilverify-errors
T-Gro Mar 3, 2026
7eaa639
Merge branch 'main' into eng/tackle-ilverify-errors
T-Gro Mar 6, 2026
2a8cfc8
Fix CI: update trimmed FSharp.Core.dll expected size and restore miss…
T-Gro Mar 6, 2026
573c0cd
Move release notes from 10.0.300 to 11.0.100 (post-freeze)
T-Gro Mar 6, 2026
b4b100f
Merge remote-tracking branch 'origin/main' into eng/tackle-ilverify-e…
T-Gro Mar 6, 2026
cace776
Fix flaky ModuleReaderCancellationTests on Linux CI
T-Gro Mar 6, 2026
9cc2880
Merge branch 'main' into eng/tackle-ilverify-errors
T-Gro Mar 6, 2026
9bf093f
Merge branch 'main' into eng/tackle-ilverify-errors
T-Gro Mar 6, 2026
b039106
Merge branch 'main' into eng/tackle-ilverify-errors
T-Gro Mar 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
### Fixed

* Fix codegen to produce IL passing ILVerify: specialized stelem/ldelem for primitives, callvirt→call on value types, castclass at interface join points, filter→catch inside finally handlers, witness field alignment in state machine structs. ([PR #19372](https://github.com/dotnet/fsharp/pull/19372))

### Added

* Added warning FS3884 when a function or delegate value is used as an interpolated string argument. ([PR #19289](https://github.com/dotnet/fsharp/pull/19289))
Expand Down
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Core/10.0.300.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* Fix EvaluateQuotation to handle Sequential expressions, void method calls (unit return), and other patterns that were previously throwing NotSupportedException. Also properly handles unit-returning expressions by using Action delegates instead of Func delegates. ([Issue #19099](https://github.com/dotnet/fsharp/issues/19099))
* Fix query conditionals without else branch (if-then only) that were causing type mismatch errors. Now properly extracts element type from IQueryable for creating empty sequences. ([Issue #3445](https://github.com/dotnet/fsharp/issues/3445))
* Fix `Seq.empty` rendering as `"EmptyEnumerable"` in serializers by delegating to `System.Linq.Enumerable.Empty<'T>()` instead of using a custom DU type. ([Issue #17864](https://github.com/dotnet/fsharp/issues/17864), [PR #19317](https://github.com/dotnet/fsharp/pull/19317))
* Ensure culture-independent parsing of .NET-style interpolated string holes. ([Issue #19367](https://github.com/dotnet/fsharp/issues/19367), [PR #19370](https://github.com/dotnet/fsharp/pull/19370))

### Added

Expand Down
25 changes: 23 additions & 2 deletions src/Compiler/AbstractIL/ilwrite.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1924,6 +1924,23 @@ module Codebuf =
| Unaligned2 -> emitInstrCode codebuf i_unaligned; codebuf.EmitByte 0x2
| Unaligned4 -> emitInstrCode codebuf i_unaligned; codebuf.EmitByte 0x4

let tryPrimitiveAsBasicType (ilg: ILGlobals) (ty: ILType) =
if isILBoolTy ilg ty then Some DT_U1
elif isILSByteTy ilg ty then Some DT_I1
elif isILByteTy ilg ty then Some DT_U1
elif isILCharTy ilg ty then Some DT_U2
elif isILInt16Ty ilg ty then Some DT_I2
elif isILUInt16Ty ilg ty then Some DT_U2
elif isILInt32Ty ilg ty then Some DT_I4
elif isILUInt32Ty ilg ty then Some DT_U4
elif isILInt64Ty ilg ty then Some DT_I8
elif isILUInt64Ty ilg ty then Some DT_U8
elif isILSingleTy ilg ty then Some DT_R4
elif isILDoubleTy ilg ty then Some DT_R8
elif isILIntPtrTy ilg ty then Some DT_I
elif isILUIntPtrTy ilg ty then Some DT_U
else None

let rec emitInstr cenv codebuf env instr =
match instr with
| si when isNoArgInstr si ->
Expand Down Expand Up @@ -2108,14 +2125,18 @@ module Codebuf =

| I_stelem_any (shape, ty) ->
if (shape = ILArrayShape.SingleDimensional) then
emitTypeInstr cenv codebuf env i_stelem_any ty
match tryPrimitiveAsBasicType cenv.ilg ty with
| Some dt -> emitInstr cenv codebuf env (I_stelem dt)
| None -> emitTypeInstr cenv codebuf env i_stelem_any ty
else
let args = List.init (shape.Rank+1) (fun i -> if i < shape.Rank then cenv.ilg.typ_Int32 else ty)
emitMethodSpecInfoInstr cenv codebuf env i_call ("Set", mkILArrTy(ty, shape), ILCallingConv.Instance, args, ILType.Void, None, [])

| I_ldelem_any (shape, ty) ->
if (shape = ILArrayShape.SingleDimensional) then
emitTypeInstr cenv codebuf env i_ldelem_any ty
match tryPrimitiveAsBasicType cenv.ilg ty with
| Some dt -> emitInstr cenv codebuf env (I_ldelem dt)
| None -> emitTypeInstr cenv codebuf env i_ldelem_any ty
else
let args = List.init shape.Rank (fun _ -> cenv.ilg.typ_Int32)
emitMethodSpecInfoInstr cenv codebuf env i_call ("Get", mkILArrTy(ty, shape), ILCallingConv.Instance, args, ty, None, [])
Expand Down
71 changes: 62 additions & 9 deletions src/Compiler/CodeGen/IlxGen.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,9 @@ and sequel =
/// Branch to the given mark
| Br of Mark

/// Emit castclass to interface type then branch, ensuring correct merge type at join points (ECMA-335 III.1.8.1.3).
| CastThenBr of ILType * Mark

/// Execute the given comparison-then-branch instructions on the result of the expression
/// If the branch isn't taken then drop through.
| CmpThenBrOrContinue of Pops * ILInstr list
Expand Down Expand Up @@ -1245,6 +1248,9 @@ 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

/// Suppresses filter block emission inside finally/fault handlers (workaround for dotnet/runtime#112406).
insideFinallyOrFaultHandler: bool

/// Are we inside of a recursive let binding, while loop, or a for loop?
isInLoop: bool

Expand Down Expand Up @@ -2833,6 +2839,7 @@ let CodeGenThen (cenv: cenv) mgbuf (entryPointInfo, methodName, eenv, alreadyUse
cgbuf
{ eenv with
withinSEH = false
insideFinallyOrFaultHandler = false
liveLocals = IntMap.empty ()
innerVals = innerVals
}
Expand Down Expand Up @@ -3267,6 +3274,7 @@ and StringOfSequel sequel =
| Return -> "Return"
| EndLocalScope(sq, Mark k) -> "EndLocalScope(" + StringOfSequel sq + "," + formatCodeLabel k + ")"
| Br(Mark x) -> sprintf "Br L%s" (formatCodeLabel x)
| CastThenBr(_, Mark x) -> sprintf "CastThenBr L%s" (formatCodeLabel x)
| LeaveHandler _ -> "LeaveHandler"
| EndFilter -> "EndFilter"

Expand All @@ -3292,6 +3300,15 @@ and GenSequel cenv cloc cgbuf sequel =

CG.EmitInstr cgbuf (pop 0) Push0 (I_br x.CodeLabel)

| CastThenBr(ilTy, x) ->
CG.EmitInstr cgbuf (pop 1) (Push [ ilTy ]) (I_castclass ilTy)

if cgbuf.mgbuf.cenv.options.generateDebugSymbols then
cgbuf.EmitStartOfHiddenCode()
CG.EmitInstr cgbuf (pop 0) Push0 AI_nop

CG.EmitInstr cgbuf (pop 0) Push0 (I_br x.CodeLabel)

| LeaveHandler(isFinally, whereToSaveResultOpt, afterHandler, hasResult) ->
if hasResult then
if isFinally then
Expand Down Expand Up @@ -3544,6 +3561,19 @@ and GenLinearExpr cenv cgbuf eenv expr sequel preSteps (contf: FakeUnit -> FakeU
let sequelOnBranches, afterJoin, stackAfterJoin, sequelAfterJoin =
GenJoinPoint cenv cgbuf "match" eenv ty m sequel

let sequelOnBranches =
match sequelOnBranches with
| Br mark when
isInterfaceTy cenv.g ty
&& targets.Length > 1
&& targets
|> Array.forall (fun (TTarget(_, body, _)) ->
let bodyTy = tyOfExpr cenv.g body
not (isStructTy cenv.g bodyTy) && not (isUnitTy cenv.g bodyTy))
->
CastThenBr(GenType cenv m eenv.tyenv ty, mark)
| _ -> sequelOnBranches

// Stack: "stackAtTargets" is "stack prior to any match-testing" and also "stack at the start of each branch-RHS".
// match-testing (dtrees) should not contribute to the stack.
// Each branch-RHS (targets) may contribute to the stack, leaving it in the "stackAfterJoin" state, for the join point.
Expand Down Expand Up @@ -4422,9 +4452,12 @@ and GenApp (cenv: cenv) cgbuf eenv (f, fty, tyargs, curriedArgs, m) sequel =
let ilThisTy = GenType cenv m eenv.tyenv ty
I_callconstraint(useICallVirt, isTailCall, ilThisTy, mspec, None)
| _ ->
if newobj then I_newobj(mspec, None)
elif useICallVirt then I_callvirt(isTailCall, mspec, None)
else I_call(isTailCall, mspec, None)
if newobj then
I_newobj(mspec, None)
elif useICallVirt && boxity <> AsValue then
I_callvirt(isTailCall, mspec, None)
else
I_call(isTailCall, mspec, None)

// ok, now we're ready to generate
if isSuperInit || isSelfInit then
Expand Down Expand Up @@ -4872,7 +4905,10 @@ and GenTryWith cenv cgbuf eenv (e1, valForFilter: Val, filterExpr, valForHandler
GenTry cenv cgbuf eenv scopeMarks (e1, m, resTy, spTry)

let seh =
if cenv.options.generateFilterBlocks || eligibleForFilter cenv filterExpr then
if
not eenv.insideFinallyOrFaultHandler
&& (cenv.options.generateFilterBlocks || eligibleForFilter cenv filterExpr)
then
let startOfFilter = CG.GenerateMark cgbuf "startOfFilter"
let afterFilter = CG.GenerateDelayMark cgbuf "afterFilter"

Expand Down Expand Up @@ -4993,7 +5029,13 @@ and GenTryFinally cenv cgbuf eenv (bodyExpr, handlerExpr, m, resTy, spTry, spFin
| DebugPointAtFinally.No -> ()

let exitSequel = LeaveHandler(true, whereToSaveOpt, afterHandler, true)
GenExpr cenv cgbuf eenvinner handlerExpr exitSequel

let eenvHandler =
{ eenvinner with
insideFinallyOrFaultHandler = true
}

GenExpr cenv cgbuf eenvHandler handlerExpr exitSequel
let endOfHandler = CG.GenerateMark cgbuf "endOfHandler"
let handlerMarks = (startOfHandler.CodeLabel, endOfHandler.CodeLabel)

Expand Down Expand Up @@ -5501,7 +5543,7 @@ and GenILCall
let ilObjArgTy = GenType cenv m eenv.tyenv objArgTy
I_callconstraint(useICallVirt, tail, ilObjArgTy, ilMethSpec, None)
| None ->
if useICallVirt then
if useICallVirt && not valu then
I_callvirt(tail, ilMethSpec, None)
else
I_call(tail, ilMethSpec, None)
Expand Down Expand Up @@ -6358,8 +6400,17 @@ and GenStructStateMachine cenv cgbuf eenvouter (res: LoweredStateMachine) sequel
CG.EmitInstr cgbuf (pop 0) (Push [ ilMachineAddrTy ]) (I_ldloca(uint16 locIdx))
CG.EmitInstr cgbuf (pop 1) (Push []) (I_stloc(uint16 locIdx2))

// Initialize the closure variables
for fv, ilv in Seq.zip cloFreeVars cloinfo.ilCloAllFreeVars do
// Initialize witness closure variables (these come first in ilCloAllFreeVars)
let nWitnesses = cloinfo.cloWitnessInfos.Length

for i in 0 .. nWitnesses - 1 do
let ilv = cloinfo.ilCloAllFreeVars.[i]
CG.EmitInstr cgbuf (pop 0) (Push [ ilMachineAddrTy ]) (I_ldloc(uint16 locIdx2))
GenWitnessArgFromWitnessInfo cenv cgbuf eenvouter m cloinfo.cloWitnessInfos.[i]
CG.EmitInstr cgbuf (pop 2) (Push []) (mkNormalStfld (mkILFieldSpecInTy (ilCloTy, ilv.fvName, ilv.fvType)))

// Initialize the regular closure variables (skip witness entries in ilCloAllFreeVars)
for fv, ilv in Seq.zip cloFreeVars (cloinfo.ilCloAllFreeVars |> Seq.skip nWitnesses) do
if stateVarsSet.Contains fv then
// zero-initialize the state var
if realloc then
Expand Down Expand Up @@ -7410,6 +7461,7 @@ and IsSequelImmediate sequel =
| Return
| ReturnVoid
| Br _
| CastThenBr _
| LeaveHandler _ -> true
| DiscardThen sequel -> IsSequelImmediate sequel
| _ -> false
Expand Down Expand Up @@ -7445,7 +7497,7 @@ and GenJoinPoint cenv cgbuf pos eenv ty m sequel =
let pushed = GenType cenv m eenv.tyenv ty
let stackAfterJoin = (pushed :: cgbuf.GetCurrentStack())
let afterJoin = CG.GenerateDelayMark cgbuf (pos + "_join")
// go to the join point

Br afterJoin, afterJoin, stackAfterJoin, sequel

// Accumulate the decision graph as we go
Expand Down Expand Up @@ -12057,6 +12109,7 @@ let GetEmptyIlxGenEnv (g: TcGlobals) ccu =
innerVals = []
sigToImplRemapInfo = [] (* "module remap info" *)
withinSEH = false
insideFinallyOrFaultHandler = false
isInLoop = false
initLocals = true
imports = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1679,32 +1679,32 @@
IL_000e: stloc.1
IL_000f: ldc.i4.0
IL_0010: stloc.2
IL_0011: br.s IL_0027
IL_0011: br.s IL_0023

IL_0013: ldloc.1
IL_0014: ldloc.2
IL_0015: ldloc.0
IL_0016: ldloc.2
IL_0017: ldelem [runtime]System.Boolean
IL_001c: stloc.3
IL_001d: ldloc.3
IL_001e: brfalse.s IL_0020
IL_0017: ldelem.u1
IL_0018: stloc.3
IL_0019: ldloc.3
IL_001a: brfalse.s IL_001c

IL_0020: ldc.i4.0
IL_0021: nop
IL_0022: stelem.i4
IL_001c: ldc.i4.0
IL_001d: nop
IL_001e: stelem.i4
IL_001f: ldloc.2
IL_0020: ldc.i4.1
IL_0021: add
IL_0022: stloc.2
IL_0023: ldloc.2
IL_0024: ldc.i4.1
IL_0025: add
IL_0026: stloc.2
IL_0027: ldloc.2
IL_0028: ldloc.1
IL_0029: ldlen
IL_002a: conv.i4
IL_002b: blt.s IL_0013
IL_0024: ldloc.1
IL_0025: ldlen
IL_0026: conv.i4
IL_0027: blt.s IL_0013

IL_002d: ldloc.1
IL_002e: ret
IL_0029: ldloc.1
IL_002a: ret
}

.method public static int32[] 'for true | _ in ...'() cil managed
Expand All @@ -1724,32 +1724,32 @@
IL_000e: stloc.1
IL_000f: ldc.i4.0
IL_0010: stloc.2
IL_0011: br.s IL_0027
IL_0011: br.s IL_0023

IL_0013: ldloc.1
IL_0014: ldloc.2
IL_0015: ldloc.0
IL_0016: ldloc.2
IL_0017: ldelem [runtime]System.Boolean
IL_001c: stloc.3
IL_001d: ldloc.3
IL_001e: brfalse.s IL_0020
IL_0017: ldelem.u1
IL_0018: stloc.3
IL_0019: ldloc.3
IL_001a: brfalse.s IL_001c

IL_0020: ldc.i4.0
IL_0021: nop
IL_0022: stelem.i4
IL_001c: ldc.i4.0
IL_001d: nop
IL_001e: stelem.i4
IL_001f: ldloc.2
IL_0020: ldc.i4.1
IL_0021: add
IL_0022: stloc.2
IL_0023: ldloc.2
IL_0024: ldc.i4.1
IL_0025: add
IL_0026: stloc.2
IL_0027: ldloc.2
IL_0028: ldloc.1
IL_0029: ldlen
IL_002a: conv.i4
IL_002b: blt.s IL_0013
IL_0024: ldloc.1
IL_0025: ldlen
IL_0026: conv.i4
IL_0027: blt.s IL_0013

IL_002d: ldloc.1
IL_002e: ret
IL_0029: ldloc.1
IL_002a: ret
}

.method public static int32[] 'for _ | true in ...'() cil managed
Expand Down Expand Up @@ -2340,4 +2340,3 @@




Loading
Loading