-
Notifications
You must be signed in to change notification settings - Fork 603
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
bundle reflection should not get width as data field #456
Conversation
@ducky64 care to review + merge (or come up with a different solution)? Would like to just get this out of the way... |
I'm going to do a bit more testing. From what I traced through the code this may be indicative of a larger problem:
|
FYI, a (temporary) patch is better for the end user than broken code, so it's quite frustrating that something like this must sit around until a better solution happens... (who knows when). --> Fork! |
Working through it now, hopefully will have an alternative PR up by end of day. The problem seems to be getPublicFields is filtering on methods based on method name, but your |
This is a case of having to work around edge cases in how scalac actually generates the underlying java classes and more specifically, working around the tricks it uses to add features that java doesn't have. (Keep in mind that java doesn't have the finer grained access modifiers of scala, compiler-enforced UAP, "body is constructor" semantics, closure rules, etc.) As far as I know, there is no well-defined spec for this since it is a moving target across JVM and scala major versions. The underlying scenario is that getDeclaredFields returns all the fields in the java class. For a scala val in the class body, scalac will reliably (as it kind of has to in order to have stable storage) create a corresponding private field in the class and then a getter for it. The getter (to maintain a semblance of java interop) will have the same name as the scala val identifier. We are fortunate in that (currently always in all cases we have seen) the underlying field in the java class has the same name as the getter. However, scalac needs to make other private fields in that java class to implement other scala features, like closures from the constructor args into class methods. However, because these fields are being used to implement closures inside the class's own methods, there is no need to generate a getter. This explains why we usually do not see these fields in the reflection. In this case, however, there is a 'pseudo-private' (package private in scala but I think public in java since java won't let you say an item is private to an enclosing package) 0-arg method called 'width' in Data. The subclass constructor also takes an argument called width, which it closes on and must store (privately). We then end up with a private field width and a public function width that thus looks like a getter for that field (even though both are unrelated). Calling that then leads to a stack-overflow induced by an infinite loop (width asks for elements, which invokes width again, etc.) Now the matter becomes how to avoid this case. (And really, because of the arcane nature of which scalac generates java classes is more of an art/whack-a-mole problem than exact science. We could reverse engineer scalac but there is no guarantee they won't change the finer points of java interop in later versions. Keep in mind they definitely did this between 2.11 and 2.12, for example.) Candidates were:
There are likely other ways to analyze and filter the methods/fields (java reflection has a plethora of other functions to play with) but they are all still best guesses of what scalac is actually doing. (None of them are called '.isGetter' because that concept doesn't really exist in base java classes and we can't safely call all single-argument functions.) |
It seems that a solution using the Scala reflection API (rather than the Java reflection API) might be better, like https://stackoverflow.com/questions/31324664/how-can-i-get-all-non-final-object-vals-and-subobject-vals-using-reflection-in |
We initially experimented internally with an scala reflection (I actually still have the CL where I tried it hanging around in a zombie state). It worked on the unit tests and then failed catastrophically at other use cases: The scala.reflect threw an internal state corruption error deep in the internals It was actually suggestions from @jsuereth to look into using java reflection as java's reflect api is substantially more reliable (albeit with the caveat that it can't see everything scala does). Also, the scala.reflect API is marked as experimental and, according to the 2.12 release notes, they are not shy about changing it. PS: I just re-ran the example. scala.reflect got really confused on an anonymous class caused due to a refinement on Bundle that added fields). |
Is there a minimal test case that could be added into the standard chisel regressions? It would be worth figuring out exactly what's failing and possibly bypassing that - for example, if we can still do: im.symbol.asClass.typeSignature.members filter (s => s.isTerm && s.asTerm.isAccessor) and convert the result to a string, that might still be better than a getDeclaraedFields check. If Scala reflection is a non-starter, then as far as this PR goes it may be worth putting the check in chiselFrontend/src/main/scala/chisel3/internal/Builder.scala, replacing def isPublicVal(m: java.lang.reflect.Method) =
m.getParameterTypes.isEmpty && valNames.contains(m.getName) && m.getDeclaringClass.isAssignableFrom(rootClass) since getPublicFields is also used in Module, which may be vulnerable to a similar bug. But this kind of feels like a game of whackamole. Also, private[core] object Bundle {
val keywords = List("flip", "asInput", "asOutput", "cloneType", "chiselCloneType", "toBits",
"widthOption", "signalName", "signalPathName", "signalParent", "signalComponent")
} in Aggregate.scala should be removed, it's dead code. |
I think it is a bit odd that the reflection functions are in Builder (and inherited by almost all chisel) and not some reflection utilities that only Bundle and Module call. I had done experimenting with the scala apis here (internal version applied to chisel is basically the same): https://github.com/sdtwigg/gama/blob/master/chiselfrontend/src/main/scala/frontend/implementation/Reflection.scala#L20 Regardless, this discussion has veered into being about a cleanup refactoring and somewhat out of scope for fixing the bug at hand here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is fine if it gets moved into Builder.
Removing the unused keywords from Aggregate.scala is nice but not required. All the other discussions are probably way out of scope.
case Some(d: Data) => Some(d) | ||
case _ => None | ||
private def getBundleField(m: java.lang.reflect.Method): Option[Data] = { | ||
if (m.getDeclaringClass.isAssignableFrom(classOf[Bundle])) None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this into Builder's getPublicFields, so the entire class of bugs is stomped out (rather than just for Bundles)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So should I just do what you said earlier? i.e.
def isPublicVal(m: java.lang.reflect.Method) =
m.getParameterTypes.isEmpty && valNames.contains(m.getName) && m.getDeclaringClass.isAssignableFrom(rootClass)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, so it solves the problem (within the limits of the current architecture) at the root, and so the fix also applies for Module reflection.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I just got to trying what you suggested and no tests pass (compiles OK, but runtime derps):
[info] chisel3.core.Binding$BindingException: 'this' (chisel3.core.UInt@7): Not bound to synthesizable node, currently only Type description
[info] at chisel3.core.Binding$.checkSynthesizable(Binding.scala:186)
[info] at chisel3.core.Bits.do_apply(Bits.scala:100)
[info] at chisel3.core.Bits.do_apply(Bits.scala:112)
[info] at chiselTests.CompatibiltySpec$$anonfun$13$RequireIOWrapModule$1.<init>(CompatibiltySpec.scala:206)
[info] at chiselTests.CompatibiltySpec$$anonfun$13$$anonfun$apply$mcV$sp$8.apply(CompatibiltySpec.scala:208)
[info] at chiselTests.CompatibiltySpec$$anonfun$13$$anonfun$apply$mcV$sp$8.apply(CompatibiltySpec.scala:208)
[info] at chisel3.core.Module$.do_apply(Module.scala:42)
[info] at chisel3.Driver$$anonfun$elaborate$1.apply(Driver.scala:91)
info] - Bundles with same data but different, underlying elements should compare as UInt *** FAILED ***
[info] chisel3.core.Binding$BindingException: 'this' (chisel3.core.UInt@a): Not bound to synthesizable node, currently only Type description
[info] at chisel3.core.Binding$.checkSynthesizable(Binding.scala:186)
[info] at chisel3.core.Data.connect(Data.scala:186)
[info] at chisel3.core.Data.$colon$eq(Data.scala:258)
[info] at chiselTests.BundleToUnitTester.<init>(BundleWire.scala:38)
[info] at chiselTests.BundleToUIntSpec$$anonfun$3$$anonfun$apply$mcV$sp$2.apply(BundleWire.scala:70)
[info] at chiselTests.BundleToUIntSpec$$anonfun$3$$anonfun$apply$mcV$sp$2.apply(BundleWire.scala:70)
Pushed changes to https://github.com/ucb-bar/chisel3/commits/builderreflectionfix if you want to take a look. If I'm not doing something stupid (copy pasting incorrectly), then maybe it's better to just consider merging this PR... unless you care to figure out what's going on, b/c reflection stuff is kind of over my head.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nevermind. Stephen caught the derp. Should be !.
Superseded by #515 |
Fixes #454 thx to @sdtwigg :)