What compiles into a method body?
An individual Midje fact (test case, roughly) macroexpands into a form like this: (record-fact-existence! (letfn [(fun1 [] ...test code is here...) (fun2 [] (with-meta fun1 {...metadata...}))] (fun2)) Tabular facts in Midje are done via unification. A table with 8 rows turns into 8 individual facts (each of that structure), surrounded by an outer level fact that runs each of them. This is wasteful of space but allows unusually expressive tests. Something I've done recently seems to have pushed some tabular facts over the Java method-length limit: Caused by: java.lang.ClassFormatError: Invalid method Code length 81209 in class file midje/t_checkers$eval24254$this_function_here_24253__24497 (This is actually surprising, since I don't see what I've done today that could push the size so *much* over 64K.) I hope my two breaking tabular facts are atypically large, but I fear not. So, some questions / thoughts: 1. Do mutually recursive `letfn` functions get compiled into a single method? More generally, I hope that any nested function definitions turn into separate methods. Do they? 2. The metadata is rather large - can it somehow end up increasing the method bytecodes? What if it's constructed via merging literals, like this: (clojure.core/merge '{:midje/body-source ((+ 1 1) = 2), ...} {:midje/top-level-fact? true})))] The guts of assertion checking also involves merging maps: (check-one (clojure.core/merge {:position :expected-result-form '2, :expected-result 2, :function-under-test (clojure.core/fn [] (+ 1 1))} {:arrow '=, :call-form '1} (hash-map-duplicates-ok :position (line-number-known 2 3. Midje goes to a lot of trouble to obey lexical scoping, so that you can write, for example: (let [a 2] (fact (let [b 2] (+ a b) = (* a b))) Do closed-over lexical environments contribute unduly to method size? 4. How can I get a look at what a big fact compiles into? (I suppose I need to AOT-compile a test namespace, but I've never had much luck with that.) Looking for 1/2-time employment as a Clojure programmer Latest book: /Functional Programming for the Object-Oriented Programmer/ https://leanpub.com/fp-oo -- -- You received this message because you are subscribed to the Google Groups Clojure group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups Clojure group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/groups/opt_out.
Re: What compiles into a method body?
Basically each lambda compiles into a method body, and the size of its lexical environment doesn't matter very much, only the amount of code inside the lambda's (fully macroexpanded) body. So in (fn a [x] (foo (letfn [(b [] (bar)) (c [] (baz))])), a only pays for foo, and b and c pay for bar and baz respectively. So the only cause of too-large methods is a single large lambda that doesn't delegate to other lambdas. I checked out midje and tried macroexpanding the test that's giving you problems: it expands to 140KB of Clojure source! The culprit is indeed the creation of your metadata maps: the :midje/body-source and :midje/source keys are each around 40KB literals. I don't know the details of how quoted values get put into the bytecode, but it's possible that code is emitted to generate them at runtime; perhaps they go into a static initializer, but that probably has the same code size limits anyway. On Monday, February 4, 2013 2:53:04 PM UTC-8, Brian Marick wrote: An individual Midje fact (test case, roughly) macroexpands into a form like this: (record-fact-existence! (letfn [(fun1 [] ...test code is here...) (fun2 [] (with-meta fun1 {...metadata...}))] (fun2)) Tabular facts in Midje are done via unification. A table with 8 rows turns into 8 individual facts (each of that structure), surrounded by an outer level fact that runs each of them. This is wasteful of space but allows unusually expressive tests. Something I've done recently seems to have pushed some tabular facts over the Java method-length limit: Caused by: java.lang.ClassFormatError: Invalid method Code length 81209 in class file midje/t_checkers$eval24254$this_function_here_24253__24497 (This is actually surprising, since I don't see what I've done today that could push the size so *much* over 64K.) I hope my two breaking tabular facts are atypically large, but I fear not. So, some questions / thoughts: 1. Do mutually recursive `letfn` functions get compiled into a single method? More generally, I hope that any nested function definitions turn into separate methods. Do they? 2. The metadata is rather large - can it somehow end up increasing the method bytecodes? What if it's constructed via merging literals, like this: (clojure.core/merge '{:midje/body-source ((+ 1 1) = 2), ...} {:midje/top-level-fact? true})))] The guts of assertion checking also involves merging maps: (check-one (clojure.core/merge {:position :expected-result-form '2, :expected-result 2, :function-under-test (clojure.core/fn [] (+ 1 1))} {:arrow '=, :call-form '1} (hash-map-duplicates-ok :position (line-number-known 2 3. Midje goes to a lot of trouble to obey lexical scoping, so that you can write, for example: (let [a 2] (fact (let [b 2] (+ a b) = (* a b))) Do closed-over lexical environments contribute unduly to method size? 4. How can I get a look at what a big fact compiles into? (I suppose I need to AOT-compile a test namespace, but I've never had much luck with that.) Looking for 1/2-time employment as a Clojure programmer Latest book: /Functional Programming for the Object-Oriented Programmer/ https://leanpub.com/fp-oo -- -- You received this message because you are subscribed to the Google Groups Clojure group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups Clojure group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/groups/opt_out.
Re: What compiles into a method body?
I checked out midje and tried macroexpanding the test that's giving you problems: it expands to 140KB of Clojure source! The culprit is indeed the creation of your metadata maps: the :midje/body-source and :midje/source keys are each around 40KB literals. You are my hero. Somehow the :midje/source gets stored as the post-macroexpansion version, instead of the pre-macroexpansion. And :midje/body-source should be a SHA1 hash anyway. Looking for 1/2-time employment as a Clojure programmer Latest book: /Functional Programming for the Object-Oriented Programmer/ https://leanpub.com/fp-oo -- -- You received this message because you are subscribed to the Google Groups Clojure group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en --- You received this message because you are subscribed to the Google Groups Clojure group. To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/groups/opt_out.