Skip to content
This repository has been archived by the owner on Aug 20, 2024. It is now read-only.

Annotations as Options Replacement, Immutable Option Parsing #765

Closed
wants to merge 77 commits into from

Conversation

seldridge
Copy link
Member

@seldridge seldridge commented Mar 19, 2018

Overview

Annotations and command line options should be fungible. A user calling the FIRRTL compiler should be able to interact with the compiler in the same way that an earlier frontend that produces annotations, but does not interact with a Driver (and vice versa). This does the following high level refactors:

  • The old mutable scopt parsing is replaced with immutable parsing
  • All command line options are parsed into an AnnotationSeq

The latter change enables immutable option parsing to be split across disjoint traits while using the same parser (e.g., HasFirrtlOptions, HasChiselExecutionOptions both parse using a shared OptionParser[AnnotationSeq]). The drivers or transforms that consume command line options are then expected to extract what they need from the command-line created AnnotationSeq, deleting what is specific to them. This then stages for a logical refactor of Driver subclassing Transform.

API changes

  • Removes methods for mutating options (e.g., setTopName)
  • A warning (as opposed to an error) results if the implicit annotation file is not found
  • All custom transforms must be globally visible (no anonymous transforms)
  • All custom transforms will be constructed (specific transform instances cannot be run)
  • An ExecutionOptionsManager can only be constructed using (String, Array[String])
  • Case classes for driver options (e.g., FirrtlOptions) use Option[T] to store optional data, as opposed to empty strings
  • CommonOptions no longer exists and has been rolled into FirrtlExecutionOptions

API Deprecations

  • FirrtlExecutionOptions.{getInputFileName, getOutputConfig, getTargetFile} (moved to HasFirrtlOptions)

API additions

  • Annotation files may include annotation files via InputAnnotationFileAnnotation with automatic include guard checking
  • Adds Driver.execute(args: String)
  • HasFirrtlOptions.{getInputFileName, getOutputConfig, getTargetFile}

Miscellaneous changes

  • Checks that no long or short options conflict with each other (with tests)
  • Bump scopt from 3.6.0 to 3.7.0 as we need some new methods to extract information to run the checks in the preceding bullet
  • Factors out HasFirrtlOptions from ExecutionOptionsManager.scala into FirrtlExecutionOptions.scala
  • Options vals (commonOptions, firrtlOptions) are necessarily lazily evaluated
  • Lots of documentation

Issues

Todo

  • Align with Chisel3
    • Remove [skip chisel tests] (I'm not sure this can be merged without this...)
  • Tests
    • Multiple Annotations/Transforms try to claim the same option
    • Check that we catch a recursive annotation file import
  • Read annotation files directly during option parsing as opposed to delaying this until the Driver
  • Documentation
  • Cleanup
  • [ ] All FIRRTL options should be deleted after use (I'm opting to leave this in for now---it's unclear to me if other transforms need these)
  • Integrate @chick toy-options registration
  • Remove usage of ExecutionOptionsManager with X with Y with Z as a parameter
  • [ ] Seal Annotations that should not be emitted by Transforms, e.g., RunFirrtlTransform
    • I don't think that we can reasonably assert what a user will want to do here. Also, there are complications with RunFirrtlTransformAnnotation as Chisel's Driver uses this. Similar logic applies for "Driver-only" annotations, like TopNameAnnotation. A transform could reasonably change this. There's a lot of power here, but I think the better approach would be to roll this into adding checks for weirdness at the form levels.

@seldridge seldridge changed the title [RFC] Prototyping Annotations as Options Replacement [RFC/DoNotMerge] Prototyping Annotations as Options Replacement Mar 19, 2018
@seldridge seldridge changed the title [RFC/DoNotMerge] Prototyping Annotations as Options Replacement [wip] Annotations as Options Replacement, Immutable Option Parsing Mar 27, 2018
@seldridge seldridge force-pushed the issue-764 branch 3 times, most recently from 96c9617 to 02ec860 Compare March 27, 2018 22:19
@jackkoenig
Copy link
Contributor

We should also think about possible command-line option collisions that could occur if two custom transforms (likely in different projects), try to claim the same option. I'm sure scopt errors when this occurs, but I wonder if there's some way we can prevent this from happening. Would it be possible to enforce that custom transforms have a prefix on their option or something? This might be really hard or onerous to do, but maybe it's possible!

@seldridge
Copy link
Member Author

Good thought, @jackkoenig. While I think that this will be unlikely for some time, it's a huge issue...

Surprisingly, scopt does not error for this... All that appears to be happening is the last callback is the one that wins. This, unfortunately, appears to happen entirely silently...

I think that the only way to utterly disambiguate things is to use the full class path. That seems reasonable to me, even if it's verbose. It's just a question of whether this should be enforced and how it should be enforced.

@seldridge
Copy link
Member Author

seldridge commented Mar 28, 2018

Scala 2.12.4 seems to be bumping up against Travis memory limits. The closest that I've gotten to replicating is that I can locally run myself out of metaspace if I repeatedly run tests in the same sbt session. Not sure if this is what is going on or something else. The problems with 2.12.4 clearly seem to start with the ClassFind commits in the patch set.

@seldridge seldridge force-pushed the issue-764 branch 4 times, most recently from 9caba74 to c4e9340 Compare April 2, 2018 06:06
@jackkoenig
Copy link
Contributor

Did you remove the class-finder stuff due to 2.12.4 issues with Travis memory limits?

@seldridge
Copy link
Member Author

@jackkoenig: No, not motivated by that (should have clarified in the commit). After discussing this last week, the consensus was that this was not the right approach for the following two reasons:

  • It's way too slow generally if you have to look through all classes. Theoretically, this should work and be fast to find options in arbitrary libraries pulled in from a Maven repo. However, ClassFinder churning through every possible class searching for any transform that may provide options is abhorrently slow. I was skirting around this by only looking inside FIRRTL.
  • There's got to be a better way than to inspect everything. Reflection can very quickly find anything that mixes in a sealed trait via knownDirectSubclasses, but that's not suitable here either as the traits can be anywhere.

Summarily, everyone thought that there should exist a cleaner solution and that this PR should be separated from that. I'm working to get this more towards only dealing with refactoring the options manager to provide:

  1. Immutability of option parsing
  2. Representation of all options as annotations (i.e., the parser is an OptionParser[AnnotationSeq])

@jackkoenig
Copy link
Contributor

jackkoenig commented Apr 9, 2018

@seldridge Ah, sorry I missed the meeting--had a big paper push going on.

I'm totally down with automatically finding such transforms to a separate PR since this it is a fairly separate piece of work. It also relates to potentially improving annotation deserialization and some way of automatically instantiating transforms based on the presence of certain annotations (ie. moving that functionality from Chisel to FIRRTL).

Just for my own understanding, how slow is it? I wanted to use ClassFinder for finding classes that extend Annotation, but if it's really that slow then I guess it won't work. As you alluded to above, maybe it will be fast enough if we can be judicious in which packages we search (wg. explicitly exclude sbt, scala, and specific plugins)...

@seldridge
Copy link
Member Author

It's actually not that bad, but it was an annoying shock from not using it.

It takes about 2 seconds to do the class path search with ClassFinder. That is searching everything locally (including ~/.ivy2). It speeds up a little once things are cached (e.g., when running a test suite).

Now that I think about it, this may not be all that bad from a usability perspective. It's basically the same as import tensorflow. This is assumedly linear with respect to the number of classes, however... Larger projects would get a larger hit.

@jackkoenig
Copy link
Contributor

I see. Well we can take a look at a later time! This PR looks to be shaping up well!

@seldridge seldridge changed the title [wip] Annotations as Options Replacement, Immutable Option Parsing Annotations as Options Replacement, Immutable Option Parsing Apr 10, 2018
@seldridge seldridge force-pushed the issue-764 branch 6 times, most recently from 6a40f9d to e8d70e4 Compare April 13, 2018 19:47
Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
parser.opt[Unit]("show-registrations")
.hidden()
.action{ (_, c) =>
println(s"""|Registered Libraries:
Copy link
Contributor

Choose a reason for hiding this comment

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

println or should this be logger.info? logger.warn?

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 think this is safe to leave in as it is. I could wrap this in a logger, but this is really information that I want to get to the command line for debugging before a logger may exist and I want this to happen even if a logger doesn't exist or I have the incorrect log level.

I.e., I think I can wrap the scopt parse call with a logger, but I'd want to pass in an explicit log level which would seem to violate the motivation for having a logger at all.

This adds support for libraries (a class internal to FIRRTL or external in
some project or library dependency) or transforms to register via
java.util.ServiceLoader that they contain options that should be included
on the command line. During the generation of an AnnotationSeq, these
will be parsed.

To facilitate debugging, a hidden option, --show-registrations, is added
to the parent ExecutionOptionsManager that will verbosely list all
registered libraries and transforms that it was able to find.

This should be everything necessary for chipsalliance#552, chipsalliance#558.

h/t @ucbjrl for finding the ServiceLoader approach and @chick for
implementing a concrete example using ServiceLoader

Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
This changes an ExecutionOptionsManager to not contain toolOptions (e.g.,
FirrtlExecutionOptionsManager no longer contains firrtlOptions). An
ExecutionOptionsManager is now a container of option parsing that converts
command line options to an AnnotationSeq. Conversion to toolOptions exists
in the Driver of a specific tool.

Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
This uses an explicit AnnotationSeq as opposed to an implicit one when
performing a view[T]. This provides greater flexibility for passing the
annotations as an argument.

This removes a kludge necessary to pass annotations implicitly for the
private Driver.execute method.

Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
This creates an abstract class representing a Big5 Driver and uses it as
the parent class for FIRRTL's Driver.

Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
This changes the API of RunFirrtlTransformAnnotation to use an actual
Transform class as opposed to a stringly-typed Transform class name. This
aligns with (and subsequently replaces) Chisel's RunFirrtlTransform.

Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
Using default arguments limits the ability of a user extending
options.Driver.execute to provide alternative execute methods with default
arguments. This avoids that by using an execute method without defaults.

Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
This converts FirrtlExecutionOptions method signatures to use
AnnotationSeq instead of Seq[Annotation]. The former is more canonical and
gives us some control over the underlying implementation.

Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
This aligns the behavior of BlackBoxTargetDirAnnotation to match
TargetDirAnnotation for both command line options and annotations.

The correct behavior is supposed to be that the default value of
BlackBoxTargetDirAnno is the value of TargetDirAnnotation. This would
previously be the case for command line options, but not for annotations.
In the latter case, the BlackBoxTargetDirAnno would get the default
TargetDirAnnotation string, ".".

Now, the option parsing for TargetDirAnnotation ("--target-dir/-td")
generates _only_ a TargetDirAnnotation. The default value is now fully
handled when constructing a FirrtlExecutionOptions view.

This adds one test case that ensures that the correct
BlackBoxTargetDirAnnotation is generated.

Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
/* Check that no long or short option declarations are duplicated */
override def parse(args: Seq[String], init: AnnotationSeq): Option[AnnotationSeq] = {
val longDups = options.map(_.name).groupBy(identity).collect{ case (k, v) if v.size > 1 && k != "" => k }
val shortDups = options.map(_.shortOpt).flatten.groupBy(identity).collect{ case (k, v) if v.size > 1 => k }
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems to be responsible for:

An exception or error caused a run to abort: scopt.OptionDef.shortOpt()Lscala/Option; 
java.lang.NoSuchMethodError: scopt.OptionDef.shortOpt()Lscala/Option;
	at firrtl.options.DuplicateHandling$$anonfun$5.apply(OptionParser.scala:22)
	at firrtl.options.DuplicateHandling$$anonfun$5.apply(OptionParser.scala:22)
	at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
	at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:234)
	at scala.collection.immutable.List.foreach(List.scala:392)
	at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35)
	at scala.collection.mutable.ListBuffer.foreach(ListBuffer.scala:45)
	at scala.collection.TraversableLike$class.map(TraversableLike.scala:234)
	at scala.collection.AbstractTraversable.map(Traversable.scala:104)
	at firrtl.options.DuplicateHandling$class.parse(OptionParser.scala:22)
	at firrtl.options.ExecutionOptionsManager$$anon$1.parse(ExecutionOptionsManager.scala:20)
	at firrtl.options.ExecutionOptionsManager.parse(ExecutionOptionsManager.scala:68)
	at firrtl_interpreter.Driver$.execute(Driver.scala:33)
	at firrtl_interpreter.DriverSpec$$anonfun$1$$anonfun$apply$mcV$sp$1.apply$mcV$sp(DriverSpec.scala:21)
	at firrtl_interpreter.DriverSpec$$anonfun$1$$anonfun$apply$mcV$sp$1.apply(DriverSpec.scala:10)
	at firrtl_interpreter.DriverSpec$$anonfun$1$$anonfun$apply$mcV$sp$1.apply(DriverSpec.scala:10)

Copy link
Contributor

@ucbjrl ucbjrl Aug 20, 2018

Choose a reason for hiding this comment

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

False alarm. This was due to IntelliJ using an old version of scopt for the firrtl-interpreter build.

Copy link
Member Author

@seldridge seldridge Aug 20, 2018

Choose a reason for hiding this comment

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

Possibly a stale scopt version? That method, shortOpt, showed up in 3.7.0.

Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
@seldridge seldridge force-pushed the issue-764 branch 2 times, most recently from 2c2ad06 to a709b21 Compare August 21, 2018 23:59
case a => a
}
}

Copy link
Member Author

Choose a reason for hiding this comment

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

@chick: Here's what I came up with for using Annotations for internal tool data movement of things which we can't serialize with json4s (currently). The idea here is that you mix this into an Annotation and before this is serialized to JSON, it will be converted to an annotation that is serializable. This is damn ugly as I wasn't able to think of a way to do this with type checking. Maybe there's a way with proper variances, but nothing was jumping out at me. This should enable something (which I'll add to Chisel) of a ChiselCircuitGeneratorAnnotation(dut: () => RawModule) that will serialize to a ChiselCircuitAnnotation(circuit: Circuit) but can be used for Chisel testers.

@jackkoenig: This is to address a narrow problem of passing annotations into the Driver of Chisel testers. It needs a () => MultiIOModule to pass to a PeekPokeTester and this is information that can't be contained in a serializable annotation without completely rewriting how Chisel testers works. Can you think of any way to add type safety here?

This adds a new trait that indicates a particular annotation is incapable
of being serialized into JSON. This includes an abstract, idempotent
method to convert this to another annotation that is safe for
serialization (indicated as it not subclassing Unserializable).

This includes a round trip serialization test.

The intended (very limited) use of this is to pass around metadata that
cannot be automatically serialized by json4s (though we could, eventually
serialize it with custom annotations). The motivating use case is
providing an annotation containing a dut generator anonymous function,
i.e., `() => Module`.

There may be a better way to do this with appropriate variances.

Signed-off-by: Schuyler Eldridge <schuyler.eldridge@ibm.com>
@seldridge
Copy link
Member Author

Ping @jackkoenig, @chick: I have a separate branch with this PR rebased and refactored massively. I could PR this as either sequential commits or about 9 PRs (possibly more...). Note that only one of those 9 PRs would have an actual functional impact on FIRRTL (the one that migrates to the "options are annotations" pattern). Everything else is either small patches or adding the framework to enable "options are annotations".

I haven't tested that every commit builds, but they are supposed to (or that's the intention). I'll spoon feed them to Travis to get those ✔️s.

Would it be a good idea to close this and reopen? Any thoughts on separate PRs or one PR with a chain of sequential, reasoned patches?

See: master...seldridge:issue-764-refactor

FWIW, if anyone else is trying to do this type of rebasing, instead of fighting with rebasing a dumpster 🔥 of 77 commits with endless merge conflicts, I had luck with applying the equivalent patch onto a new branch and then repeatedly rebasing to break out logical items.

@seldridge
Copy link
Member Author

This makes sense to me. I'm opting for the series of PRs approach. I'll re-open if people feel differently.

@seldridge seldridge closed this Aug 22, 2018
@jackkoenig
Copy link
Contributor

I think the series of PRs sounds great, thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants