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




Reply via email to