On Fri Jan 29 06:50:08 2016, pawel.pab...@getresponse.com wrote: > multi sub infix:<==> (Int $a, Str $b) { $a == $b } > > Will never finish compilation on Rakudo 6.c and causes severe memory > leak. I found that: > > 1. Types must be different in signature > (Int $a, Str $b) # hangs > (Str $a, Int $b) # hangs > (Int $a, Int $b) # compiles > > 2. Operator must be the same in sub and block > multi sub infix:<eq> (Int $a, Str $b) { $a eq $b } # hangs > multi sub infix:<eq> (Int $a, Str $b) { $a == $b } # compiles
Thanks for the report. I'm unsure whether this would qualify as a bug, but jnthn++ wanted to know more about this behaviour, in case it suggests buggy behaviour elsewhere, so what follows are my findings. The body of the multi you're creating calls itself, which causes infinite recursion, hence the hang and memory growth. The reason you see this happen during compile time is due to routine inlining optimizations. You can see that by turning off the optimizer: perl6 --optimize=0 -e 'multi sub infix:<==> (Int $a, Str $b) { $a == $b }' # doesn't hang The reason (Int $a, Int $b) compiles is because it's ambiguous with the Int:D, Int:D candidate defined in core, so the optimization infini-loop for it does not occur. Using a different operator, as you described in point (2), simply avoids using the same operator in the body, and so the multi never ends up calling itself. ---- Digging into the optimizer, it inlines the hanging multi, then runs the optimizer over it again, inlines it again, and just keeps doing that, forever inlining and digging into it. Here's the call graph for the infini-looping portion: visit_op ├───$optype eq 'chain' -> 'call' ├───self.visit_op_children($op) │ └───visit_children($op) │ ├───self.visit_var($visit) │ │ └──@!block_var_stack[$top].add_usage($var); │ └───self.visit_var($visit) │ └──@!block_var_stack[$top].add_usage($var); │ └───self.optimize_call($op) ├───$!symbols.find_lexical($op.name) │ └───self.force_value(%sym, $name, 1) │ │ # inside if $dispatcher && $obj.onlystar {...} ├───my @ct_arg_info := self.analyze_args_for_ct_call($op) └───self.inline_call($op, $chosen) └───$!symbols.faking_top_routine └───self.visit_children($inlined) <-- optimizing newly inlined code └───self.visit_op($visit) └───self.visit_op_children($op) <-- we're back where we started I also added a print statement to start of every method and sub in Optimizer.nqp and here's the repeating portion of the calls: CALLING -> in visit_op CALLING -> in optimize CALLING -> in is_outer_foldable CALLING -> in visit_op_children CALLING -> in visit_children CALLING -> in visit_var CALLING -> in add_usage CALLING -> in visit_var CALLING -> in add_usage CALLING -> in optimize_call CALLING -> in find_lexical CALLING -> in force_value CALLING -> in analyze_args_for_ct_call CALLING -> in inline_call CALLING -> in faking_top_routine CALLING -> in visit_children CALLING -> in visit_op CALLING -> in optimize CALLING -> in is_outer_foldable CALLING -> in visit_op_children CALLING -> in visit_children CALLING -> in visit_op CALLING -> in optimize CALLING -> in is_outer_foldable CALLING -> in visit_op_children CALLING -> in visit_children CALLING -> in visit_children CALLING -> in visit_children Cheers, ZZ