Stacktrace not trimmed or trimmed stacktrace ruined by additionnal full stacktrace
See original GitHub issueType of issue: bug report Impact: no functional change Development Phase: request
Other information
Although chisel does a great job at trimming stack trace, when throwing an exception during chisel elaboration, firrtl.options.StageError
still does (additionally) throw the whole stack trace.
While I do understand it remains very convenient for internal debug purpose, it is quite a pain because the beautiful trimmed stack-trace is hidden from user.
If the current behavior is a bug, please provide the steps to reproduce the problem: Note that the additional function & custom Exception are not required to demonstrate the behaviour. There are here only to add some user-space call in the stacktrace.
import chisel3._
import chisel3.stage.ChiselStage
case class UserException(msg: String) extends Exception(msg)
class HwClass(doThrow: Boolean = false) extends MultiIOModule {
val in = IO(Input(Bool()))
val out = IO(Output(Bool()))
def f(b: Bool): Bool = {
if(doThrow) throw UserException("User error message")
b
}
out := f(in)
}
object SimpleExceptionIssue extends App {
(new ChiselStage).emitVerilog(new HwClass(doThrow = true))
}
What is the current behavior?
[info] [0,003] Elaborating design...
[error] <user error message>
[error] ...
[error] at <user code call>
// <potentially many more lines of user calls> //
[error] at <user code call>
[error] ... (Stack trace trimmed to user code only, rerun with --full-stacktrace if you wish to see the full stack trace)
[error] (run-main-4c) firrtl.options.StageError:
[error] firrtl.options.StageError:
[error] at chisel3.stage.ChiselStage.run(ChiselStage.scala:60)
[error] at firrtl.options.Stage$$anon$1.transform(Stage.scala:43)
[error] at firrtl.options.Stage$$anon$1.transform(Stage.scala:43)
[error] at firrtl.options.phases.DeletedWrapper.internalTransform(DeletedWrapper.scala:38)
[error] at firrtl.options.phases.DeletedWrapper.internalTransform(DeletedWrapper.scala:15)
[error] at firrtl.options.Translator.transform(Phase.scala:248)
[error] at firrtl.options.Translator.transform$(Phase.scala:248)
[error] at firrtl.options.phases.DeletedWrapper.transform(DeletedWrapper.scala:15)
[error] at firrtl.options.Stage.$anonfun$transform$5(Stage.scala:47)
[error] at scala.collection.LinearSeqOptimized.foldLeft(LinearSeqOptimized.scala:126)
[error] at scala.collection.LinearSeqOptimized.foldLeft$(LinearSeqOptimized.scala:122)
[error] at scala.collection.immutable.List.foldLeft(List.scala:91)
[error] at firrtl.options.Stage.$anonfun$transform$3(Stage.scala:47)
[error] at logger.Logger$.$anonfun$makeScope$2(Logger.scala:166)
[error] at scala.util.DynamicVariable.withValue(DynamicVariable.scala:62)
[error] at logger.Logger$.makeScope(Logger.scala:164)
[error] at firrtl.options.Stage.transform(Stage.scala:47)
[error] at firrtl.options.Stage.execute(Stage.scala:58)
[error] at chisel3.stage.ChiselStage.emitVerilog(ChiselStage.scala:117)
[error] at <user main code for calling chisel call>
[error] at <user main code for calling chisel call>
[error] at <user main code for calling chisel call>
[error] at <user main code for calling chisel call>
[error] at scala.Function0.apply$mcV$sp(Function0.scala:39)
[error] at scala.Function0.apply$mcV$sp$(Function0.scala:39)
[error] at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:17)
[error] at scala.App.$anonfun$main$1$adapted(App.scala:80)
[error] at scala.collection.immutable.List.foreach(List.scala:431)
[error] at scala.App.main(App.scala:80)
[error] at scala.App.main$(App.scala:78)
[error] at <user main code for calling chisel call>
[error] at <user main code for calling chisel call>
[error] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[error] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[error] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[error] at java.base/java.lang.reflect.Method.invoke(Method.java:564)
[error] Caused by: chisel3.internal.ChiselException: Exception thrown when elaborating ChiselGeneratorAnnotation
[error] at chisel3.stage.ChiselGeneratorAnnotation.elaborate(ChiselAnnotations.scala:65)
[error] at chisel3.stage.phases.Elaborate.$anonfun$transform$1(Elaborate.scala:24)
[error] at scala.collection.immutable.List.flatMap(List.scala:366)
[error] at chisel3.stage.phases.Elaborate.transform(Elaborate.scala:23)
[error] at chisel3.stage.phases.Elaborate.transform(Elaborate.scala:16)
[error] at firrtl.options.phases.DeletedWrapper.internalTransform(DeletedWrapper.scala:38)
[error] at firrtl.options.phases.DeletedWrapper.internalTransform(DeletedWrapper.scala:15)
[error] at firrtl.options.Translator.transform(Phase.scala:248)
[error] at firrtl.options.Translator.transform$(Phase.scala:248)
[error] at firrtl.options.phases.DeletedWrapper.transform(DeletedWrapper.scala:15)
[error] at firrtl.options.DependencyManager.$anonfun$transform$3(DependencyManager.scala:278)
[error] at scala.collection.LinearSeqOptimized.foldLeft(LinearSeqOptimized.scala:126)
[error] at scala.collection.LinearSeqOptimized.foldLeft$(LinearSeqOptimized.scala:122)
[error] at scala.collection.immutable.List.foldLeft(List.scala:91)
[error] at firrtl.options.DependencyManager.transform(DependencyManager.scala:269)
[error] at firrtl.options.DependencyManager.transform$(DependencyManager.scala:255)
[error] at firrtl.options.PhaseManager.transform(DependencyManager.scala:436)
[error] at chisel3.stage.ChiselStage.run(ChiselStage.scala:46)
[error] at firrtl.options.Stage$$anon$1.transform(Stage.scala:43)
[error] at firrtl.options.Stage$$anon$1.transform(Stage.scala:43)
[error] at firrtl.options.phases.DeletedWrapper.internalTransform(DeletedWrapper.scala:38)
[error] at firrtl.options.phases.DeletedWrapper.internalTransform(DeletedWrapper.scala:15)
[error] at firrtl.options.Translator.transform(Phase.scala:248)
[error] at firrtl.options.Translator.transform$(Phase.scala:248)
[error] at firrtl.options.phases.DeletedWrapper.transform(DeletedWrapper.scala:15)
[error] at firrtl.options.Stage.$anonfun$transform$5(Stage.scala:47)
[error] at scala.collection.LinearSeqOptimized.foldLeft(LinearSeqOptimized.scala:126)
[error] at scala.collection.LinearSeqOptimized.foldLeft$(LinearSeqOptimized.scala:122)
[error] at scala.collection.immutable.List.foldLeft(List.scala:91)
[error] at firrtl.options.Stage.$anonfun$transform$3(Stage.scala:47)
[error] at logger.Logger$.$anonfun$makeScope$2(Logger.scala:166)
[error] at scala.util.DynamicVariable.withValue(DynamicVariable.scala:62)
[error] at logger.Logger$.makeScope(Logger.scala:164)
[error] at firrtl.options.Stage.transform(Stage.scala:47)
[error] at firrtl.options.Stage.execute(Stage.scala:58)
[error] at chisel3.stage.ChiselStage.emitVerilog(ChiselStage.scala:117)
[error] at <user main code for calling chisel call>
[error] at <user main code for calling chisel call>
[error] at <user main code for calling chisel call>
[error] at <user main code for calling chisel call>
[error] at scala.Function0.apply$mcV$sp(Function0.scala:39)
[error] at scala.Function0.apply$mcV$sp$(Function0.scala:39)
[error] at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:17)
[error] at scala.App.$anonfun$main$1$adapted(App.scala:80)
[error] at scala.collection.immutable.List.foreach(List.scala:431)
[error] at scala.App.main(App.scala:80)
[error] at scala.App.main$(App.scala:78)
[error] at <user main code for calling chisel call>
[error] at <user main code for calling chisel call>
[error] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[error] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[error] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[error] at java.base/java.lang.reflect.Method.invoke(Method.java:564)
[error] Caused by: <user error message>
[error] at <user code call>
// <potentially many more lines of user calls> //
[error] at <user code call>
[error] at chisel3.Module$.do_apply(Module.scala:54)
[error] at chisel3.stage.ChiselGeneratorAnnotation.$anonfun$elaborate$1(ChiselAnnotations.scala:60)
[error] at chisel3.internal.Builder$.$anonfun$build$1(Builder.scala:642)
[error] at scala.util.DynamicVariable.withValue(DynamicVariable.scala:62)
[error] at chisel3.internal.Builder$.build(Builder.scala:639)
[error] at chisel3.internal.Builder$.build(Builder.scala:635)
[error] at chisel3.stage.ChiselGeneratorAnnotation.elaborate(ChiselAnnotations.scala:60)
[error] at chisel3.stage.phases.Elaborate.$anonfun$transform$1(Elaborate.scala:24)
[error] at scala.collection.immutable.List.flatMap(List.scala:366)
[error] at chisel3.stage.phases.Elaborate.transform(Elaborate.scala:23)
[error] at chisel3.stage.phases.Elaborate.transform(Elaborate.scala:16)
[error] at firrtl.options.phases.DeletedWrapper.internalTransform(DeletedWrapper.scala:38)
[error] at firrtl.options.phases.DeletedWrapper.internalTransform(DeletedWrapper.scala:15)
[error] at firrtl.options.Translator.transform(Phase.scala:248)
[error] at firrtl.options.Translator.transform$(Phase.scala:248)
[error] at firrtl.options.phases.DeletedWrapper.transform(DeletedWrapper.scala:15)
[error] at firrtl.options.DependencyManager.$anonfun$transform$3(DependencyManager.scala:278)
[error] at scala.collection.LinearSeqOptimized.foldLeft(LinearSeqOptimized.scala:126)
[error] at scala.collection.LinearSeqOptimized.foldLeft$(LinearSeqOptimized.scala:122)
[error] at scala.collection.immutable.List.foldLeft(List.scala:91)
[error] at firrtl.options.DependencyManager.transform(DependencyManager.scala:269)
[error] at firrtl.options.DependencyManager.transform$(DependencyManager.scala:255)
[error] at firrtl.options.PhaseManager.transform(DependencyManager.scala:436)
[error] at chisel3.stage.ChiselStage.run(ChiselStage.scala:46)
[error] at firrtl.options.Stage$$anon$1.transform(Stage.scala:43)
[error] at firrtl.options.Stage$$anon$1.transform(Stage.scala:43)
[error] at firrtl.options.phases.DeletedWrapper.internalTransform(DeletedWrapper.scala:38)
[error] at firrtl.options.phases.DeletedWrapper.internalTransform(DeletedWrapper.scala:15)
[error] at firrtl.options.Translator.transform(Phase.scala:248)
[error] at firrtl.options.Translator.transform$(Phase.scala:248)
[error] at firrtl.options.phases.DeletedWrapper.transform(DeletedWrapper.scala:15)
[error] at firrtl.options.Stage.$anonfun$transform$5(Stage.scala:47)
[error] at scala.collection.LinearSeqOptimized.foldLeft(LinearSeqOptimized.scala:126)
[error] at scala.collection.LinearSeqOptimized.foldLeft$(LinearSeqOptimized.scala:122)
[error] at scala.collection.immutable.List.foldLeft(List.scala:91)
[error] at firrtl.options.Stage.$anonfun$transform$3(Stage.scala:47)
[error] at logger.Logger$.$anonfun$makeScope$2(Logger.scala:166)
[error] at scala.util.DynamicVariable.withValue(DynamicVariable.scala:62)
[error] at logger.Logger$.makeScope(Logger.scala:164)
[error] at firrtl.options.Stage.transform(Stage.scala:47)
[error] at firrtl.options.Stage.execute(Stage.scala:58)
[error] at chisel3.stage.ChiselStage.emitVerilog(ChiselStage.scala:117)
[error] at <user main code for calling chisel call>
[error] at <user main code for calling chisel call>
[error] at <user main code for calling chisel call>
[error] at <user main code for calling chisel call>
[error] at scala.Function0.apply$mcV$sp(Function0.scala:39)
[error] at scala.Function0.apply$mcV$sp$(Function0.scala:39)
[error] at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:17)
[error] at scala.App.$anonfun$main$1$adapted(App.scala:80)
[error] at scala.collection.immutable.List.foreach(List.scala:431)
[error] at scala.App.main(App.scala:80)
[error] at scala.App.main$(App.scala:78)
[error] at <user main code for calling chisel call>
[error] at <user main code for calling chisel call>
[error] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[error] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[error] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[error] at java.base/java.lang.reflect.Method.invoke(Method.java:564)
[error] stack trace is suppressed; run last Compile / bgRunMain for the full output
[error] Nonzero exit code: 1
[error] (Compile / runMain) Nonzero exit code: 1
[error] Total time: 7 s, completed 27 janv. 2021 à 11:19:35
Note that this becomes even worse when using object ChiselStage
rather than class ChiselStage
such as
object SimpleExceptionIssue extends App {
ChiselStage.emitVerilog(new HwClass(doThrow = true))
}
In such a case you don’t even get the trimmed stacktrace and the user error message is hidden as a cause:
[info] running SimpleExceptionIssue
[info] [0,017] Elaborating design...
[error] (run-main-5f) chisel3.internal.ChiselException: Exception thrown when elaborating ChiselGeneratorAnnotation
[error] chisel3.internal.ChiselException: Exception thrown when elaborating ChiselGeneratorAnnotation
[error] at chisel3.stage.ChiselGeneratorAnnotation.elaborate(ChiselAnnotations.scala:65)
[error] at chisel3.stage.phases.Elaborate.$anonfun$transform$1(Elaborate.scala:24)
[error] at scala.collection.immutable.List.flatMap(List.scala:366)
[error] at chisel3.stage.phases.Elaborate.transform(Elaborate.scala:23)
[error] at chisel3.stage.phases.Elaborate.transform(Elaborate.scala:16)
[error] at firrtl.options.DependencyManager.$anonfun$transform$3(DependencyManager.scala:278)
[error] at scala.collection.LinearSeqOptimized.foldLeft(LinearSeqOptimized.scala:126)
[error] at scala.collection.LinearSeqOptimized.foldLeft$(LinearSeqOptimized.scala:122)
[error] at scala.collection.immutable.List.foldLeft(List.scala:91)
[error] at firrtl.options.DependencyManager.transform(DependencyManager.scala:269)
[error] at firrtl.options.DependencyManager.transform$(DependencyManager.scala:255)
[error] at firrtl.options.PhaseManager.transform(DependencyManager.scala:436)
[error] at chisel3.stage.ChiselStage$.emitVerilog(ChiselStage.scala:236)
[error] at fpgaTests.utils.SimpleExceptionIssue$.delayedEndpoint$fpgaTests$utils$SimpleExceptionIssue$1(PlayGroundSpec.scala:202)
[error] at fpgaTests.utils.SimpleExceptionIssue$delayedInit$body.apply(PlayGroundSpec.scala:200)
[error] at scala.Function0.apply$mcV$sp(Function0.scala:39)
[error] at scala.Function0.apply$mcV$sp$(Function0.scala:39)
[error] at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:17)
[error] at scala.App.$anonfun$main$1$adapted(App.scala:80)
[error] at scala.collection.immutable.List.foreach(List.scala:431)
[error] at scala.App.main(App.scala:80)
[error] at scala.App.main$(App.scala:78)
[error] at fpgaTests.utils.SimpleExceptionIssue$.main(PlayGroundSpec.scala:200)
[error] at fpgaTests.utils.SimpleExceptionIssue.main(PlayGroundSpec.scala)
[error] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[error] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[error] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[error] at java.base/java.lang.reflect.Method.invoke(Method.java:564)
[error] Caused by: fpgaTests.utils.UserException: User error message
[error] at fpgaTests.utils.HwClass.f(PlayGroundSpec.scala:192)
[error] at fpgaTests.utils.HwClass.$anonfun$new$8(PlayGroundSpec.scala:196)
[error] at chisel3.Data.$anonfun$$colon$eq$1(Data.scala:524)
[error] at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
[error] at chisel3.internal.prefix$.apply(prefix.scala:32)
[error] at chisel3.Data.$colon$eq(Data.scala:524)
[error] at fpgaTests.utils.HwClass.<init>(PlayGroundSpec.scala:196)
[error] at fpgaTests.utils.SimpleExceptionIssue$.$anonfun$new$9(PlayGroundSpec.scala:202)
[error] at chisel3.Module$.do_apply(Module.scala:54)
[error] at chisel3.stage.ChiselGeneratorAnnotation.$anonfun$elaborate$1(ChiselAnnotations.scala:60)
[error] at chisel3.internal.Builder$.$anonfun$build$1(Builder.scala:642)
[error] at scala.util.DynamicVariable.withValue(DynamicVariable.scala:62)
[error] at chisel3.internal.Builder$.build(Builder.scala:639)
[error] at chisel3.internal.Builder$.build(Builder.scala:635)
[error] at chisel3.stage.ChiselGeneratorAnnotation.elaborate(ChiselAnnotations.scala:60)
[error] at chisel3.stage.phases.Elaborate.$anonfun$transform$1(Elaborate.scala:24)
[error] at scala.collection.immutable.List.flatMap(List.scala:366)
[error] at chisel3.stage.phases.Elaborate.transform(Elaborate.scala:23)
[error] at chisel3.stage.phases.Elaborate.transform(Elaborate.scala:16)
[error] at firrtl.options.DependencyManager.$anonfun$transform$3(DependencyManager.scala:278)
[error] at scala.collection.LinearSeqOptimized.foldLeft(LinearSeqOptimized.scala:126)
[error] at scala.collection.LinearSeqOptimized.foldLeft$(LinearSeqOptimized.scala:122)
[error] at scala.collection.immutable.List.foldLeft(List.scala:91)
[error] at firrtl.options.DependencyManager.transform(DependencyManager.scala:269)
[error] at firrtl.options.DependencyManager.transform$(DependencyManager.scala:255)
[error] at firrtl.options.PhaseManager.transform(DependencyManager.scala:436)
[error] at chisel3.stage.ChiselStage$.emitVerilog(ChiselStage.scala:236)
[error] at fpgaTests.utils.SimpleExceptionIssue$.delayedEndpoint$fpgaTests$utils$SimpleExceptionIssue$1(PlayGroundSpec.scala:202)
[error] at fpgaTests.utils.SimpleExceptionIssue$delayedInit$body.apply(PlayGroundSpec.scala:200)
[error] at scala.Function0.apply$mcV$sp(Function0.scala:39)
[error] at scala.Function0.apply$mcV$sp$(Function0.scala:39)
[error] at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:17)
[error] at scala.App.$anonfun$main$1$adapted(App.scala:80)
[error] at scala.collection.immutable.List.foreach(List.scala:431)
[error] at scala.App.main(App.scala:80)
[error] at scala.App.main$(App.scala:78)
[error] at fpgaTests.utils.SimpleExceptionIssue$.main(PlayGroundSpec.scala:200)
[error] at fpgaTests.utils.SimpleExceptionIssue.main(PlayGroundSpec.scala)
[error] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[error] at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[error] at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[error] at java.base/java.lang.reflect.Method.invoke(Method.java:564)
[error] stack trace is suppressed; run last Test / bgRunMain for the full output
[error] Nonzero exit code: 1
[error] (Test / runMain) Nonzero exit code: 1
[error] Total time: 26 s, completed 29 janv. 2021 à 10:18:31
What is the expected behavior?
[info] [0,003] Elaborating design...
[error] <user error message>
[error] ...
[error] at <user code call>
// <potentially many more lines of user calls> //
[error] at <user code call>
[error] ... (Stack trace trimmed to user code only, rerun with --full-stacktrace if you wish to see the full stack trace)
[error] stack trace is suppressed; run last Compile / bgRunMain for the full output
[error] Nonzero exit code: 1
[error] (Compile / runMain) Nonzero exit code: 1
[error] Total time: 7 s, completed 27 janv. 2021 à 11:19:35
Please tell us about your environment:
- version: 3.4.1
- OS: Darwin 17.7.0 Darwin Kernel Version 17.7.0: Fri Oct 30 13:34:27 PDT 2020; root:xnu-4570.71.82.8~1/RELEASE_X86_64 x86_64
What is the use case for changing the behavior? Nicer errors = happier users
Issue summary
- with ChiselStage.emitVerilog => stacktrace is not trimmed and is awful (user error hidden right in the middle)
- with (new ChiselStage).emitVerilog => stacktrace is trimmed but then the whole stacktrace is appended
- underlying issue: difference of behaviour between
object
&class ChiselStage
object ChiselStage
relies onPhaseManager
class ChiselStage
relies onfirrtl.options.Stage
(who is actually the culprit for throwing the additional stacktrace)
For me ChiselStage
(and the entire Stage/Phase API) is one of the most obscure piece of code of the chisel/firrtl ecosystem although, several times, I spend hours (if not days) digging into it just to be able to customize our build flow a little bit…
And here we have… a companion object not even using its associated class but both somehow relying on the same underlying Stage/Phase API, with roughly the same result… but not quite!
There is a really serious underlying concern about trust here:
- how can we expect the generated verilog to be same in both case?
- Can we expect custom transforms to be applied in both case? In the same order?
- It’s a real nightmare.
When I first posted about this issue in https://github.com/chipsalliance/firrtl/issues/974 I thought I was looking for a quick fix… but as soon as Stage/Phase API
gets involved, suprises occur and everything gets cumbersome…
So I would be happy to help about the stacktrace issue, but this entire file https://github.com/chipsalliance/chisel3/blob/master/src/main/scala/chisel3/stage/ChiselStage.scala needs some uniformization first… and on that one I would like to request help from @seldridge or @jackkoenig ?
Issue Analytics
- State:
- Created 3 years ago
- Comments:9 (9 by maintainers)
Top GitHub Comments
Using #1771, this behavior is unified:
stdout
message indicating to use--full-stacktrace
if you don’t want trimming--full-stacktrace
, you get the full stack trace and nostdout
messageConsider:
Running all permutations of this using #1771 locally published:
You can also not use your own main and then just use
ChiselMain
directly:Hi Jean, I understand that you are frustrated with the current API and its associated inconsistencies, and I too would be frustrated and upset in your case. I think we can all agree that we are all in the business of improving Chisel, and I did want to point out that Schuyler has made many great contributions to the Chisel ecosystem and I am not sure that we should say that he maliciously disregards users and usability - after all, we have all made mistakes in API design and there is certainly always more to learn. Perhaps we could all work together to find a way to better understand the current state of the art and figure out the right path to move forward on. Just 2 cents.