From 96d41b140fd7b0adbd150d25f512176f8ba63d68 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Tue, 25 Aug 2026 17:45:29 +0200 Subject: [PATCH] Avoid redundant FreeVars record allocation for local vals accFreeLocalVal and boundLocalVal accumulated the free type variables of the val's type and then separately added/removed the val from FreeLocals, allocating two FreeVars records whenever the type contributed a free type variable. Fold both into a single record update, factoring the shared type-variable accumulation into `accFreeTyvarsInVal` (used by both) and dropping the now-unused accFreevarsInVal helper. Output is byte-for-byte identical: accFreeInVal only accumulates into FreeTyvars, never FreeLocals, so computing the type contribution before the FreeLocals membership check is order-independent. Measured on a 65,880-LOC / 120-file compile (net11.0, Release): 177,250 redundant FreeVars records eliminated per compile (~11 MB); FreeVars record allocation down 5.4% in a gc-verbose trace. Byte identity confirmed by comparing per-file deterministic (--deterministic+) output hashes across all 120 files (manifest hash unchanged vs HEAD). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../TypedTree/TypedTreeOps.Remapping.fs | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs b/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs index 00dbae22c43..cb314ff5043 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.Remapping.fs @@ -873,26 +873,33 @@ module internal ExprFreeVars = let accFreevarsInTycon opts tcref acc = accFreeTyvars opts accFreeTycon tcref acc - let accFreevarsInVal opts v acc = accFreeTyvars opts accFreeInVal v acc - let accFreeVarsInTraitSln opts tys acc = accFreeTyvars opts accFreeInTraitSln tys acc let accFreeVarsInTraitInfo opts tys acc = accFreeTyvars opts accFreeInTrait tys acc + let inline accFreeTyvarsInVal opts v ftyvs = + if opts.collectInTypes then + accFreeInVal opts v ftyvs + else + ftyvs + let boundLocalVal opts v fvs = if not opts.includeLocals then fvs else - let fvs = accFreevarsInVal opts v fvs + let ftyvs = accFreeTyvarsInVal opts v fvs.FreeTyvars - if not (Zset.contains v fvs.FreeLocals) then - fvs - else + if Zset.contains v fvs.FreeLocals then { fvs with + FreeTyvars = ftyvs FreeLocals = Zset.remove v fvs.FreeLocals } + elif ftyvs === fvs.FreeTyvars then + fvs + else + { fvs with FreeTyvars = ftyvs } let boundProtect fvs = if fvs.UsesMethodLocalConstructs || fvs.ContainsILFieldAccess then @@ -943,14 +950,13 @@ module internal ExprFreeVars = if opts.canCache then tryGetCacheValue cache else ValueNone let accFreeLocalVal opts v fvs = - if not opts.includeLocals then - fvs - else if Zset.contains v fvs.FreeLocals then + if not opts.includeLocals || Zset.contains v fvs.FreeLocals then fvs else - let fvs = accFreevarsInVal opts v fvs + let ftyvs = accFreeTyvarsInVal opts v fvs.FreeTyvars { fvs with + FreeTyvars = ftyvs FreeLocals = Zset.add v fvs.FreeLocals }