Skip to content

Conversation

@auduchinok
Copy link
Member

@auduchinok auduchinok commented Apr 26, 2018

We'd like to be able to reuse ReSharper caches in FCS. The idea is to integrate into the process of reading assemblies like how it's done in F# cross-project references in FCS itself. This PR adds a shim that can override FCS assembly reading for this purpose and extracts interfaces so other IL symbols representations can be used.

As per initial review by @dsyme implementations for extracted interfaces were removed from il.fsi and thus were hidden.

  • Add assembly reader shim
  • Extract interfaces for IL definitions (IModuleDef, IAssemblyManifest, ITypeDef, etc)
  • Extract interfaces for IL refs/specs (IModuleRef, ITypeRef, ITypeSpec, etc)
  • performance test (will be done by @dsyme CompilerPerf check)

I'm opening this PR to get feedback while still working on extracting interfaces for other types in IL.

@auduchinok
Copy link
Member Author

I was initially asked to put extracting definition interfaces to a single PR. It looks, however, that the PR is really big to make a proper review feasible. Should I open separate PRs instead?

@cartermp
Copy link
Contributor

That can be @dsyme and @KevinRansom's call. Personally I think this is fine as-is though

@KevinRansom
Copy link
Contributor

It is too big to do a careful review. But it would likely be quite tough to split it apart too. I guess keep on with a single PR, and we will have to be methodical when we do the review.

@KevinRansom
Copy link
Contributor

The work so far looks good. On a fairly cursory glance.

@auduchinok
Copy link
Member Author

@KevinRansom Most changes are split by commits good enough so it could be relatively easy to split the PR by cherry picking commits and applying some cleanup if it makes things better for review.

@KevinRansom
Copy link
Contributor

Give splitting it up a whirl then, if it causes more than a little bother though. We can review it as one lump.

Copy link
Contributor

@dsyme dsyme left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left a few comments outlining some initial changes to make please - if you feel any of them are strongly wrong please let me know and we can discuss, thanks

Data: byte[]
Elements: ILAttribElem list}
/// to ILAttribElem's as best as possible.
type IAttribute =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep the names as ILAttribute etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the point here is to honor .NET guidelines which say interface names should start with I.
This would lead to IILAttribute

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please just use ILAttribute, i.e. the same names as now. Thanks

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the point here is to honor .NET guidelines which say interface names should start with I.

Yes, the idea was to follow these guidelines.

@dsyme OK, I'll change the names this way. I do, however, really like idea of using more conventional names. Would you mind returning to the naming discussion at some later point and probably having this stuff renamed in a future PR?

CustomAttrsStored: ILAttributesStored
MetadataIndex: int32 }
member CustomAttrs: ILAttributes
type IParameter =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise, please keep the name as ILParameter etc

Type: ILType
CustomAttrsStored: ILAttributesStored
MetadataIndex: int32 }
type IReturn =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ILReturn

member MaxStack: int32
member IsZeroInit: bool
/// IL Method definitions.
type IMethodDef =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ILMethodDef



[<Sealed>]
type ILAttribute(method, data, elements) =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've requested that the API use the names ILAttribute, partly to completely minimize the code churn and de-emphasize the abstraction being performed here (we do not want to consider arbitrary implementors in this codebase, just implementors that implement contracts very similar to the existing functional-object contracts, e.g. take-no-locks-and-do-no-observable-mutation).

As a result we need to use a different name here. I think it's probably best to use ILAttributeImpl etc.

CustomAttrsStored: ILAttributesStored
MetadataIndex: int32 }
member x.CustomAttrs = x.CustomAttrsStored.GetCustomAttrs x.MetadataIndex
type ILParameter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise ILParameterImpl


type ILGenericParameterDefs = ILGenericParameterDef list
[<Sealed>]
type ILGenericParameterDef
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise ILGenericParameterDefImpl

?parameters: ILParameters * ?ret: IReturn * ?body: ILLazyMethodBody * ?securityDecls: ILSecurityDecls * ?isEntryPoint:bool *
?genericParams: ILGenericParameterDefs * ?customAttrs: IAttributes -> IMethodDef

abstract WithSpecialName: IMethodDef
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not that happy about these being abstract. I think they should just be extension methods defined in terms of With. I'm not interested in trying to reason about implementations that give alternative implementations of these so they shouldn't be abstract

?newCustomAttrs: IAttributes
-> IFieldDef

abstract WithAccess: ILMemberAccess -> IFieldDef
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise I think these should be extension methods rather than abstract please

@dsyme
Copy link
Contributor

dsyme commented May 5, 2018

@auduchinok Could you add the following comment to each interface abstraction? thanks

/// Implementations of this and other similar abstractions in this API must 
/// implement functional object semantics, as if the objects are F# immutable
/// record types, where 
///   1. properties of objects can be accessed concurrently from any thread at any time 
///   2. no locks are required to access properties of objects. 
///   3. the objects are, logically, speaking, immutable 
///   4. the objects do not need to be explicitly disposed

I also would appreciate a confirmation that this is suitable for your purposes in the context of JetBrains: we discussed this over Slack but I'd like to double check.

I do wonder if a simpler abstraction might be easier, where the extension point is simply an FileName --> ILModuleDef function and you populate the existing (non-abstract) IL* types from the JetBrains caches. Why do we need each of the types to be abstract? Will the implementations really be immutable?

Separately, we will certainly need to do a performance test of this: if it results in a perf degradation

@auduchinok
Copy link
Member Author

auduchinok commented May 5, 2018

@dsyme Honestly speaking I cannot confirm our implementations are going to satisfy all the four requirements you propose.

Even creating current IL* types objects would require taking a read lock on my side. The good point is we've had a few discussions in Rider/ReSharper teams about it and it looks like we're able to deal with it and we'll manage to allow FCS to take a read lock on any thread.

Our implementations are going to be wrappers for ReSharper caches which are logically mutable, however, these wrappers are going to be logically immutable as long as the assembly write timestamp isn't changed in the file system shim and I'm going to update timestamps on changes that are observable by FCS (which in turn would invalidate the project).

@dsyme
Copy link
Contributor

dsyme commented May 8, 2018

Even creating current IL* types objects would require taking a read lock on my side. The good point is we've had a few discussions in Rider/ReSharper teams about it and it looks like we're able to deal with it and we'll manage to allow FCS to take a read lock on any thread.

I think it's ok if they take a read lock that has nothing to do with FCS.

Our implementations are going to be wrappers for ReSharper caches which are logically mutable, however, these wrappers are going to be logically immutable as long as the assembly write timestamp isn't changed in the file system shim and I'm going to update timestamps on changes that are observable by FCS (which in turn would invalidate the project).

That's probably ok.

@dsyme
Copy link
Contributor

dsyme commented May 9, 2018

@auduchinok What do you think of this?

I do wonder if a simpler abstraction might be easier, where the extension point is simply an FileName --> ILModuleDef function and you populate the existing (non-abstract) IL* types from the JetBrains caches. Why do we need each of the types to be abstract? Will the implementations really be immutable?

i.e. do we really need to make all of these interfaces? Is it that you need to capture context in each object, similar to MetadataIndex for the AbsIL reader?

@dsyme
Copy link
Contributor

dsyme commented May 9, 2018

@dsyme OK, I'll change the names this way. I do, however, really like idea of using more conventional names. Would you mind returning to the naming discussion at some later point and probably having this stuff renamed in a future PR?

From my perspective as compiler contributor I don't want to think of these as interfaces in the normal .NET sense.

If it were possible I'd really rather we didn't open this extension point up at all - it causes chaos when trying to reason about changes to the compiler. If we could truly constrain implementers to implement functional-object semantics then that would be ok. But we can't, so we are opening up a hole in the compiler that means just about any change can be observed as a breaking change by a "bad" implementer.

So I'm still loathe to take this and especially loathe to "advertise" it by sprinkling IFooBar throughout the compiler code....

@auduchinok
Copy link
Member Author

@auduchinok What do you think of this?
i.e. do we really need to make all of these interfaces? Is it that you need to capture context in each object, similar to MetadataIndex for the AbsIL reader?

@dsyme Yes, indeed, we need to capture a cache symbol pointer in a similar manner.

@7sharp9
Copy link
Contributor

7sharp9 commented May 9, 2018 via email

@dsyme
Copy link
Contributor

dsyme commented May 9, 2018

@dsyme Yes, indeed, we need to capture a cache symbol pointer in a similar manner.

So if we changed MetadataIdx: int32 to MetadataContext: obj or MetadataContext: IntPtr (and passed it through on read as in mkILTypeDefReader, mkILCustomAttrsReader, ) then we could avoid making the majority of them interfaces?

e.g.

member MetadataIndex: int32 
val mkILCustomAttrsReader: (int32 -> ILAttribute[]) -> ILAttributesStored
val mkILSecurityDeclsReader: (int32 -> ILSecurityDecl[]) -> ILSecurityDeclsStored
val mkILTypeDefReader: (int32 -> ILTypeDef) -> ILTypeDefStored

become

[<Struct>]
type MetadataContext =MetadataContext of obj

member MetadataContext: MetadataContext
val mkILCustomAttrsReader: (MetadataContext -> ILAttribute[]) -> ILAttributesStored
val mkILSecurityDeclsReader: (MetadataContext -> ILSecurityDecl[]) -> ILSecurityDeclsStored
val mkILTypeDefReader: (MetadataContext -> ILTypeDef) -> ILTypeDefStored

or

type MetadataContext =
    | NoMetadataContext 
    | MetadataIndex of int32
    | MetadataHandle of obj

member MetadataContext: MetadataContext
val mkILCustomAttrsReader: (MetadataContext -> ILAttribute[]) -> ILAttributesStored
val mkILSecurityDeclsReader: (MetadataContext -> ILSecurityDecl[]) -> ILSecurityDeclsStored
val mkILTypeDefReader: (MetadataContext -> ILTypeDef) -> ILTypeDefStored

or just

type MetadataContext = IntPtr

member MetadataContext: MetadataContext
val mkILCustomAttrsReader: (MetadataContext -> ILAttribute[]) -> ILAttributesStored
val mkILSecurityDeclsReader: (MetadataContext -> ILSecurityDecl[]) -> ILSecurityDeclsStored

etc. I think I would much prefer that solution if it is feasible because it is very non-intrusive. There are downsides to the above but I'm really, really concerned about opening up a large extension point that we would need to maintain.

@auduchinok auduchinok mentioned this pull request May 15, 2018
@auduchinok auduchinok changed the title Add assembly reader shim, extract interfaces for IL symbols [WIP] Extract interfaces for IL symbol definitions May 17, 2018
@auduchinok
Copy link
Member Author

auduchinok commented Sep 11, 2018

@dsyme Yes, after a while I do really like your suggestions and they seem to be the way to go. Let's keep this PR open until I open another one with one of the suggested approaches used.

I think I would still like to propose several changes in these types but these wouldn't be as intrusive as this PR was intended to be. An example of thing I'd like to address is a need to encode and decode custom attributes data — it's better to produce "decoded" values beforehand.

@dsyme
Copy link
Contributor

dsyme commented Sep 19, 2018

@auduchinok I'll close this since I know you will submit a different PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants