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

Refactor compiler to allow loading backends from separate DLLs #1708

Closed
wants to merge 12 commits into from

Conversation

cpitclaudel
Copy link
Member

@cpitclaudel cpitclaudel commented Jan 13, 2022

This is the first step towards making it easier to plug in compiler backends.

  • To minimize code churn I didn't move each existing backend to a separate DLL. Should I?
  • I'm not a huge fan of the two-layered structure (factory + actual prover), but IIUC that's needed to load a DLL (since types are not first-class values in C#)
  • There are surprising dependencies between DafnyAST.cs and the compiler (the AST depended on some values from the compiler); I tagged the corresponding commit with NEEDS REVIEW.

With this PR the /compileTarget flag can take a path to a DLL.

@cpitclaudel cpitclaudel added part: code-generation Support for transpiling Dafny to another language. If relevant, add a `lang:` tag misc: cleanup Cleanups in the implementation or in corners of the language labels Jan 13, 2022
@cpitclaudel cpitclaudel self-assigned this Jan 13, 2022
@MikaelMayer
Copy link
Member

This is the first step towards making it easier to plug in compiler backends.

  • To minimize code churn I didn't move each existing backend to a separate DLL. Should I?
  • I'm not a huge fan of the two-layered structure (factory + actual prover), but IIUC that's needed to load a DLL (since types are not first-class values in C#)
  • There are surprising dependencies between DafnyAST.cs and the compiler (the AST depended on some values from the compiler); I tagged the corresponding commit with NEEDS REVIEW.

With this PR the /compileTarget flag can take a path to a DLL.

For the moment, I would advise to not move each existing backend to a separate DLL because it makes development easier. I'm working on refactoring the compiler itself so that it's more modular (e.g. no call to a single CreateForEachLoop that is responsible of subtype checking). Hence I not only need to modify the base compiler, but also the different instances, and I run a test for all of them at the same time.

@cpitclaudel cpitclaudel force-pushed the compiler branch 3 times, most recently from 07cd7c4 to a59a9ef Compare January 16, 2022 23:08
Source/Dafny/Compilers/Compiler-Csharp.cs Outdated Show resolved Hide resolved
var okVar = FreshId("_ok");
var iterVar = FreshId("_iter");
var okVar = idGenerator.FreshId("_ok");
var iterVar = idGenerator.FreshId("_iter");
Copy link
Member

@keyboardDrummer keyboardDrummer Jan 17, 2022

Choose a reason for hiding this comment

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

Where does this _ok => _iter rename come from?

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 don't think there's a rename here?

Source/Dafny/Compilers/Compiler-Csharp.cs Outdated Show resolved Hide resolved
Source/Dafny/Compilers/SinglePassCompiler.cs Outdated Show resolved Hide resolved
Source/Dafny/DafnyOptions.cs Outdated Show resolved Hide resolved
Source/Dafny/Resolver.cs Outdated Show resolved Hide resolved
Source/DafnyDriver/DafnyDriver.cs Outdated Show resolved Hide resolved
Source/DafnyDriver/DafnyDriver.cs Outdated Show resolved Hide resolved
Test/SelfHosting/Main.cs Outdated Show resolved Hide resolved
@cpitclaudel
Copy link
Member Author

For the moment, I would advise to not move each existing backend to a separate DLL because it makes development easier.

👍, although it might be nice to split off the Java compiler, because compiling the runtime every time is a pain.

@MikaelMayer
Copy link
Member

compiling

I investigated more why I believed we should hold off splitting the compilers into different projects. It turns out I had some instability with some versions of Newtonsoft.Json.dll (DafnyDriver requires 9.0.0.0, DafnyLanguageServer requires 11.0.0.0, so if I compile DafnyDriver, the language server does not work).
So I don't have any more strong argument of not splitting the compilers.

@keyboardDrummer
Copy link
Member

For the moment, I would advise to not move each existing backend to a separate DLL because it makes development easier.

👍, although it might be nice to split off the Java compiler, because compiling the runtime every time is a pain.

What do we mean by splitting off the compilers? We'd move the compiler file to a new .NET project, sure, but where do we leave that project?

If we keep that project in the Dafny solution then it wouldn't impact development, right? It just means that Rider can build DafnyPipeline without building the Java compiler.

@cpitclaudel
Copy link
Member Author

It just means that Rider can build DafnyPipeline without building the Java compiler.

That was my hope, yes.


namespace Microsoft.Dafny;

public interface ICompiler {
Copy link
Member

Choose a reason for hiding this comment

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

This interface is so much nicer. :)

Here's an idea to consider for the very next PR (since it's not necessary here and the PR is already large): could we add a method to be invoked earlier in the pipeline, before verification, that can produce compiler-specific errors? Something like CheckCompatibility.

The use case would be a codebase using a specific compiler, especially one of the more incomplete ones like C++, where it's currently pretty frustrating to spend a ton of time getting your code to verify successfully only to then find out "oh wait, my compiler doesn't support unbounded ints" and have to start over. This is particularly easy for newer Dafny programmers to hit, since the documentation on which features are supported by which compilers is something you have to think to look up, and isn't that obvious for newcomers.

Copy link
Member Author

Choose a reason for hiding this comment

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

Here's an idea to consider for the very next PR (since it's not necessary here and the PR is already large): could we add a method to be invoked earlier in the pipeline, before verification, that can produce compiler-specific errors? Something like CheckCompatibility.

The compiler is fast and verification errors lead to runtime (not compile-time) errors, so we could just run the compiler first or in parallel with verification instead of running verification first and only then running the compiler. So instead of resolve → verify → translate → compile we could just run

resolve → verify
        → translate → compile

Or even resolve → translate → verify → compile if we don't want concurrency.

Copy link
Member Author

Choose a reason for hiding this comment

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

Probably a good entry for your compiler planning doc!

Copy link
Member

Choose a reason for hiding this comment

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

Yup, you're absolutely right that there's other ways to get the same result (and it would be better to not have another method to implement if possible). I'm not sure I believe "verification errors lead to runtime (not compile-time) errors" is always true, or that we want to ensure it is always true in the future, but we can work that into the design. :)

Copy link
Member

Choose a reason for hiding this comment

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

Our compilers are actually "translators", the two are synonyms. When you say "compiler", do you mean the actual language-to-bytecode compiler?

@keyboardDrummer
Copy link
Member

It just means that Rider can build DafnyPipeline without building the Java compiler.

That was my hope, yes.

Sounds great, but let's do it in a separate PR

Copy link
Member

@MikaelMayer MikaelMayer left a comment

Choose a reason for hiding this comment

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

Also, how will it work to provide new compiler assemblies? Are you reusing the same compileTarget option?


namespace Microsoft.Dafny;

public interface ICompiler {
Copy link
Member

Choose a reason for hiding this comment

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

Our compilers are actually "translators", the two are synonyms. When you say "compiler", do you mean the actual language-to-bytecode compiler?

}

public CompilationTarget CompileTarget = CompilationTarget.Csharp;
public CompilerFactory CompilerFactoryInstance;
Copy link
Member

Choose a reason for hiding this comment

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

With flags it was obvious we could run multiple compilers at the same time.
How are you going to do it with this? I suggest you use a comma-separated list of compilers

Copy link
Member Author

Choose a reason for hiding this comment

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

With flags it was obvious we could run multiple compilers at the same time.

I don't think we ever could actually run multiple compilers at the same time, could we?


namespace Microsoft.Dafny;

public interface ICompiler {
Copy link
Member

Choose a reason for hiding this comment

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

Consider renaming ICompiler to ICodeGenerator. "Compiler" is generally used to refer to an entire compilation pipeline, including parsing source code, right?

@cpitclaudel
Copy link
Member Author

Also, how will it work to provide new compiler assemblies? Are you reusing the same compileTarget option?

That's right, you just pass /compileTarget:path/to/a/library.dll

@cpitclaudel
Copy link
Member Author

@MikaelMayer and I disucssed this today. We think it's better if instead of reusing the compileTarget flag to give a DLL we instead make a new flag plugins that takes a list of DLLs, each of which exposes an ICompiler. Then we can resolve the value of compileTarget by looking for an ICompiler with the appropriate SupportedLanguage. So you'd write /plugin:XYZCompiler.dll /compileTarget:xyz.

This same plugin flag can be used for other kinds of plugins, and @MikaelMayer will implement it as part of #1739 . Then I will rebase this PR on top of that.

Finally we decided it would be simpler to get rid of the factory and instantiate an ICompiler instance (renamed to ICodeGenerator) with no arguments right from the beginning of the process, (and later on pass the reporter). I will make these changes after @MikaelMayer completes his.

@keyboardDrummer
Copy link
Member

This same plugin flag can be used for other kinds of plugins, and @MikaelMayer will implement it as part of #1739 . Then I will rebase this PR on top of that.

Why don't we merge this PR first, since this is already approved?

@MikaelMayer
Copy link
Member

Why don't we merge this PR first, since this is already approved?

Maybe because 1) the tests are not working yet, and 2) offering an API to implement a factory and then immediately switching to a plugin strategy would be not backward compatible with the factory (for example, /compilationTarget won't accept a DLL anymore once we switch to the plugin system). and 3) it makes sense to accept DLLs as a separate argument rather than changing the CompilationTarget.

@MikaelMayer
Copy link
Member

I'm voluntarily not writing "changes requested" because I don't want to block this PR, but we are still in discussion about the plugins system which would make this PR easier to implement.

@cpitclaudel
Copy link
Member Author

Why don't we merge this PR first, since this is already approved?

The only client of this PR is my compiler work, which can happen in a branch. The plugin API is the result of discussing with @MikaelMayer how to unify his resolver plugins and my compiler plugins, so I don't think we should merge this until the plugin work is done :)

@cpitclaudel
Copy link
Member Author

Closing this, since it was superseded by my later PR on top of Mikael's one

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
misc: cleanup Cleanups in the implementation or in corners of the language part: code-generation Support for transpiling Dafny to another language. If relevant, add a `lang:` tag
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants