Skip to content

Commit

Permalink
Optimize metadata reading for extension methods (#16168)
Browse files Browse the repository at this point in the history
Co-authored-by: Petr <psfinaki@users.noreply.github.com>
  • Loading branch information
DedSec256 and psfinaki authored Jun 26, 2024
1 parent 54a1997 commit db21cf2
Show file tree
Hide file tree
Showing 12 changed files with 221 additions and 80 deletions.
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/8.0.400.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@
* Reduce allocations in compiler checking via `ValueOption` usage ([PR #16822](https://github.com/dotnet/fsharp/pull/16822))
* Use AsyncLocal instead of ThreadStatic to hold Cancellable.Token ([PR #17156](https://github.com/dotnet/fsharp/pull/17156))
* Showing and inserting correct name of entities from unopened namespace/module ([Issue #14375](https://github.com/dotnet/fsharp/issues/14375), [PR #17261](https://github.com/dotnet/fsharp/pull/17261))
* Support lazy custom attributes calculation for `ILTypeDef` public API, improve `ExtensionAttribute` presence detecting perf. ([PR #16168](https://github.com/dotnet/fsharp/pull/16168))
42 changes: 29 additions & 13 deletions src/Compiler/AbstractIL/il.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1258,6 +1258,9 @@ let storeILCustomAttrs (attrs: ILAttributes) =
else
ILAttributesStored.Given attrs

let mkILCustomAttrsComputed f =
ILAttributesStored.Reader(fun _ -> f ())

let mkILCustomAttrsReader f = ILAttributesStored.Reader f

type ILCodeLabel = int
Expand Down Expand Up @@ -2611,6 +2614,14 @@ let convertInitSemantics (init: ILTypeInit) =
| ILTypeInit.BeforeField -> TypeAttributes.BeforeFieldInit
| ILTypeInit.OnAny -> enum 0

[<Flags>]
type ILTypeDefAdditionalFlags =
| None = 0
| IsKnownToBeAttribute = 1
/// The type can contain extension methods,
/// or this information may not be available at the time the ILTypeDef is created
| CanContainExtensionMethods = 2

[<NoComparison; NoEquality; StructuredFormatDisplay("{DebugText}")>]
type ILTypeDef
(
Expand All @@ -2626,14 +2637,16 @@ type ILTypeDef
methodImpls: ILMethodImplDefs,
events: ILEventDefs,
properties: ILPropertyDefs,
isKnownToBeAttribute: bool,
additionalFlags: ILTypeDefAdditionalFlags,
securityDeclsStored: ILSecurityDeclsStored,
customAttrsStored: ILAttributesStored,
metadataIndex: int32
) =

let mutable customAttrsStored = customAttrsStored

let hasFlag flag = additionalFlags &&& flag = flag

new(name,
attributes,
layout,
Expand All @@ -2646,7 +2659,7 @@ type ILTypeDef
methodImpls,
events,
properties,
isKnownToBeAttribute,
additionalFlags,
securityDecls,
customAttrs) =
ILTypeDef(
Expand All @@ -2662,9 +2675,9 @@ type ILTypeDef
methodImpls,
events,
properties,
isKnownToBeAttribute,
additionalFlags,
storeILSecurityDecls securityDecls,
storeILCustomAttrs customAttrs,
customAttrs,
NoMetadataIdx
)

Expand Down Expand Up @@ -2694,7 +2707,10 @@ type ILTypeDef

member _.Properties = properties

member _.IsKnownToBeAttribute = isKnownToBeAttribute
member _.IsKnownToBeAttribute = hasFlag ILTypeDefAdditionalFlags.IsKnownToBeAttribute

member _.CanContainExtensionMethods =
hasFlag ILTypeDefAdditionalFlags.CanContainExtensionMethods

member _.CustomAttrsStored = customAttrsStored

Expand All @@ -2714,7 +2730,7 @@ type ILTypeDef
?methodImpls,
?events,
?properties,
?isKnownToBeAttribute,
?newAdditionalFlags,
?customAttrs,
?securityDecls
) =
Expand All @@ -2732,11 +2748,11 @@ type ILTypeDef
methodImpls = defaultArg methodImpls x.MethodImpls,
events = defaultArg events x.Events,
properties = defaultArg properties x.Properties,
isKnownToBeAttribute = defaultArg isKnownToBeAttribute x.IsKnownToBeAttribute,
customAttrs = defaultArg customAttrs x.CustomAttrs
additionalFlags = defaultArg newAdditionalFlags additionalFlags,
customAttrs = defaultArg customAttrs (storeILCustomAttrs x.CustomAttrs)
)

member x.CustomAttrs =
member x.CustomAttrs: ILAttributes =
match customAttrsStored with
| ILAttributesStored.Reader f ->
let res = ILAttributes(f x.MetadataIndex)
Expand Down Expand Up @@ -4220,11 +4236,11 @@ let mkILGenericClass (nm, access, genparams, extends, impl, methods, fields, nes
methods = methods,
fields = fields,
nestedTypes = nestedTypes,
customAttrs = attrs,
customAttrs = storeILCustomAttrs attrs,
methodImpls = emptyILMethodImpls,
properties = props,
events = events,
isKnownToBeAttribute = false,
additionalFlags = ILTypeDefAdditionalFlags.None,
securityDecls = emptyILSecurityDecls
)

Expand All @@ -4244,11 +4260,11 @@ let mkRawDataValueTypeDef (iltyp_ValueType: ILType) (nm, size, pack) =
methods = emptyILMethods,
fields = emptyILFields,
nestedTypes = emptyILTypeDefs,
customAttrs = emptyILCustomAttrs,
customAttrs = emptyILCustomAttrsStored,
methodImpls = emptyILMethodImpls,
properties = emptyILProperties,
events = emptyILEvents,
isKnownToBeAttribute = false,
additionalFlags = ILTypeDefAdditionalFlags.None,
securityDecls = emptyILSecurityDecls
)

Expand Down
20 changes: 15 additions & 5 deletions src/Compiler/AbstractIL/il.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
module rec FSharp.Compiler.AbstractIL.IL

open System
open FSharp.Compiler.IO
open System.Collections.Generic
open System.Reflection
Expand Down Expand Up @@ -1481,6 +1482,12 @@ type ILTypeDefs =
/// Calls to <c>ExistsByName</c> will result in all the ILPreTypeDefs being read.
member internal ExistsByName: string -> bool

[<Flags>]
type ILTypeDefAdditionalFlags =
| None = 0
| IsKnownToBeAttribute = 1
| CanContainExtensionMethods = 2

/// Represents IL Type Definitions.
[<NoComparison; NoEquality>]
type ILTypeDef =
Expand All @@ -1499,7 +1506,7 @@ type ILTypeDef =
methodImpls: ILMethodImplDefs *
events: ILEventDefs *
properties: ILPropertyDefs *
isKnownToBeAttribute: bool *
additionalFlags: ILTypeDefAdditionalFlags *
securityDeclsStored: ILSecurityDeclsStored *
customAttrsStored: ILAttributesStored *
metadataIndex: int32 ->
Expand All @@ -1519,9 +1526,9 @@ type ILTypeDef =
methodImpls: ILMethodImplDefs *
events: ILEventDefs *
properties: ILPropertyDefs *
isKnownToBeAttribute: bool *
additionalFlags: ILTypeDefAdditionalFlags *
securityDecls: ILSecurityDecls *
customAttrs: ILAttributes ->
customAttrs: ILAttributesStored ->
ILTypeDef

member Name: string
Expand Down Expand Up @@ -1556,6 +1563,7 @@ type ILTypeDef =
member HasSecurity: bool
member Encoding: ILDefaultPInvokeEncoding
member IsKnownToBeAttribute: bool
member CanContainExtensionMethods: bool

member internal WithAccess: ILTypeDefAccess -> ILTypeDef
member internal WithNestedAccess: ILMemberAccess -> ILTypeDef
Expand Down Expand Up @@ -1584,8 +1592,8 @@ type ILTypeDef =
?methodImpls: ILMethodImplDefs *
?events: ILEventDefs *
?properties: ILPropertyDefs *
?isKnownToBeAttribute: bool *
?customAttrs: ILAttributes *
?newAdditionalFlags: ILTypeDefAdditionalFlags *
?customAttrs: ILAttributesStored *
?securityDecls: ILSecurityDecls ->
ILTypeDef

Expand Down Expand Up @@ -2212,8 +2220,10 @@ val internal mkILTypeForGlobalFunctions: ILScopeRef -> ILType
val mkILCustomAttrs: ILAttribute list -> ILAttributes
val mkILCustomAttrsFromArray: ILAttribute[] -> ILAttributes
val storeILCustomAttrs: ILAttributes -> ILAttributesStored
val mkILCustomAttrsComputed: (unit -> ILAttribute[]) -> ILAttributesStored
val internal mkILCustomAttrsReader: (int32 -> ILAttribute[]) -> ILAttributesStored
val emptyILCustomAttrs: ILAttributes
val emptyILCustomAttrsStored: ILAttributesStored

val mkILSecurityDecls: ILSecurityDecl list -> ILSecurityDecls
val emptyILSecurityDecls: ILSecurityDecls
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/AbstractIL/ilmorph.fs
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ let rec tdef_ty2ty_ilmbody2ilmbody_mdefs2mdefs isInKnownSet enc fs (tdef: ILType
methodImpls = mimpls_ty2ty fTyInCtxtR tdef.MethodImpls,
events = edefs_ty2ty fTyInCtxtR tdef.Events,
properties = pdefs_ty2ty fTyInCtxtR tdef.Properties,
customAttrs = cattrs_ty2ty fTyInCtxtR tdef.CustomAttrs
customAttrs = storeILCustomAttrs (cattrs_ty2ty fTyInCtxtR tdef.CustomAttrs)
)

and tdefs_ty2ty_ilmbody2ilmbody_mdefs2mdefs isInKnownSet enc fs tdefs =
Expand Down
117 changes: 97 additions & 20 deletions src/Compiler/AbstractIL/ilread.fs
Original file line number Diff line number Diff line change
Expand Up @@ -858,10 +858,10 @@ let hsCompare (TaggedIndex(t1: HasSemanticsTag, idx1: int)) (TaggedIndex(t2: Has
elif idx1 > idx2 then 1
else compare t1.Tag t2.Tag

let hcaCompare (TaggedIndex(t1: HasCustomAttributeTag, idx1: int)) (TaggedIndex(t2: HasCustomAttributeTag, idx2)) =
if idx1 < idx2 then -1
elif idx1 > idx2 then 1
else compare t1.Tag t2.Tag
let inline hcaCompare (t1: TaggedIndex<HasCustomAttributeTag>) (t2: TaggedIndex<HasCustomAttributeTag>) =
if t1.index < t2.index then -1
elif t1.index > t2.index then 1
else compare t1.tag t2.tag

let mfCompare (TaggedIndex(t1: MemberForwardedTag, idx1: int)) (TaggedIndex(t2: MemberForwardedTag, idx2)) =
if idx1 < idx2 then -1
Expand Down Expand Up @@ -2112,9 +2112,82 @@ and typeDefReader ctxtH : ILTypeDefStored =
let layout = typeLayoutOfFlags ctxt mdv flags idx

let hasLayout =
(match layout with
| ILTypeDefLayout.Explicit _ -> true
| _ -> false)
match layout with
| ILTypeDefLayout.Explicit _ -> true
| _ -> false

let containsExtensionMethods =
let mutable containsExtensionMethods = false
let searchedKey = TaggedIndex(hca_TypeDef, idx)

let attributesSearcher =
{ new ISeekReadIndexedRowReader<int, int, int> with
member _.GetRow(i, rowIndex) = rowIndex <- i
member _.GetKey(rowIndex) = rowIndex

member _.CompareKey(rowIndex) =
let mutable addr = ctxt.rowAddr TableNames.CustomAttribute rowIndex
// read parentIndex
let key = seekReadHasCustomAttributeIdx ctxt mdv &addr
hcaCompare searchedKey key

member _.ConvertRow(i) = i
}

let attrsStartIdx, attrsEndIdx =
seekReadIndexedRowsRange
(ctxt.getNumRows TableNames.CustomAttribute)
(isSorted ctxt TableNames.CustomAttribute)
attributesSearcher

if attrsStartIdx <= 0 || attrsEndIdx < attrsStartIdx then
false
else
let mutable attrIdx = attrsStartIdx

let looksLikeSystemAssembly =
ctxt.fileName.EndsWith("System.Runtime.dll")
|| ctxt.fileName.EndsWith("mscorlib.dll")
|| ctxt.fileName.EndsWith("netstandard.dll")

while attrIdx <= attrsEndIdx && not containsExtensionMethods do
let mutable addr = ctxt.rowAddr TableNames.CustomAttribute attrIdx
// skip parentIndex to read typeIndex
seekReadHasCustomAttributeIdx ctxt mdv &addr |> ignore
let attrTypeIndex = seekReadCustomAttributeTypeIdx ctxt mdv &addr
let attrCtorIdx = attrTypeIndex.index

let name =
if attrTypeIndex.tag = cat_MethodDef then
// the ExtensionAttribute constructor can be cat_MethodDef if the metadata is read from the assembly
// in which the corresponding attribute is defined -- from the system library
if not looksLikeSystemAssembly then
""
else
let _, (_, nameIdx, namespaceIdx, _, _, _) = seekMethodDefParent ctxt attrCtorIdx
readBlobHeapAsTypeName ctxt (nameIdx, namespaceIdx)
else
let mutable addr = ctxt.rowAddr TableNames.MemberRef attrCtorIdx
let mrpTag = seekReadMemberRefParentIdx ctxt mdv &addr

if mrpTag.tag <> mrp_TypeRef then
""
else
let _, nameIdx, namespaceIdx = seekReadTypeRefRow ctxt mdv mrpTag.index
readBlobHeapAsTypeName ctxt (nameIdx, namespaceIdx)

if name = "System.Runtime.CompilerServices.ExtensionAttribute" then
containsExtensionMethods <- true

attrIdx <- attrIdx + 1

containsExtensionMethods

let additionalFlags =
if containsExtensionMethods then
ILTypeDefAdditionalFlags.CanContainExtensionMethods
else
ILTypeDefAdditionalFlags.None

let mdefs = seekReadMethods ctxt numTypars methodsIdx endMethodsIdx
let fdefs = seekReadFields ctxt (numTypars, hasLayout) fieldsIdx endFieldsIdx
Expand All @@ -2138,7 +2211,7 @@ and typeDefReader ctxtH : ILTypeDefStored =
methodImpls = mimpls,
events = events,
properties = props,
isKnownToBeAttribute = false,
additionalFlags = additionalFlags,
customAttrsStored = ctxt.customAttrsReader_TypeDef,
metadataIndex = idx
))
Expand Down Expand Up @@ -2797,22 +2870,26 @@ and seekReadMemberRefAsFieldSpecUncached ctxtH (MemberRefAsFspecIdx(numTypars, i
// method-range and field-range start/finish indexes
and seekReadMethodDefAsMethodData ctxt idx = ctxt.seekReadMethodDefAsMethodData idx

and seekMethodDefParent (ctxt: ILMetadataReader) methodIdx =
seekReadIndexedRow (
ctxt.getNumRows TableNames.TypeDef,
(fun i -> i, seekReadTypeDefRow ctxt i),
id,
(fun (i, (_, _, _, _, _, methodsIdx as info)) ->
if methodsIdx > methodIdx then
-1
else
let struct (_, endMethodsIdx) = seekReadTypeDefRowExtents ctxt info i
if endMethodsIdx <= methodIdx then 1 else 0),
true,
id
)

and seekReadMethodDefAsMethodDataUncached ctxtH idx =
let (ctxt: ILMetadataReader) = getHole ctxtH
let mdv = ctxt.mdfile.GetView()
// Look for the method def parent.
let tidx =
seekReadIndexedRow (
ctxt.getNumRows TableNames.TypeDef,
(fun i -> i, seekReadTypeDefRowWithExtents ctxt i),
id,
(fun (_, ((_, _, _, _, _, methodsIdx), (_, endMethodsIdx))) ->
if endMethodsIdx <= idx then 1
elif methodsIdx <= idx && idx < endMethodsIdx then 0
else -1),
true,
fst
)
let tidx, _ = seekMethodDefParent ctxt idx
// Create a formal instantiation if needed
let typeGenericArgs = seekReadGenericParams ctxt 0 (tomd_TypeDef, tidx)
let typeGenericArgsCount = typeGenericArgs.Length
Expand Down
5 changes: 4 additions & 1 deletion src/Compiler/Checking/NameResolution.fs
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,10 @@ let NextExtensionMethodPriority() = uint64 (newStamp())
/// Checks if the type is used for C# style extension members.
let IsTyconRefUsedForCSharpStyleExtensionMembers g m (tcref: TyconRef) =
// Type must be non-generic and have 'Extension' attribute
isNil(tcref.Typars m) && TyconRefHasAttribute g m g.attrib_ExtensionAttribute tcref
match metadataOfTycon tcref.Deref with
| ILTypeMetadata(TILObjectReprData(_, _, tdef)) -> tdef.CanContainExtensionMethods
| _ -> true
&& isNil(tcref.Typars m) && TyconRefHasAttribute g m g.attrib_ExtensionAttribute tcref

/// Checks if the type is used for C# style extension members.
let IsTypeUsedForCSharpStyleExtensionMembers g m ty =
Expand Down
Loading

0 comments on commit db21cf2

Please sign in to comment.