Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize metadata reading for extension methods #16168

Merged
merged 8 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 @@ -34,3 +34,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
Loading