Hacking on Perl 6 (Rakudo and Roast)

I found a bug in Perl 6 recently. Really I independently discovered one that was already reported.

Here’s how to trigger it:

$ perl6 -e 'say <2147483648/3>'
Cannot find method 'compile_time_value'

Any numerator of 231 or greater causes that error. Note that Perl 6 is perfectly happy to represent rationals of that size or larger:

$ perl6 -e 'say Rat.new(2147483648, 3)'

So the problem was clearly somewhere in the compiler.

Here’s a quick guide to how I fixed this.

First, I added a test to the Perl 6 test suite. Unlike many programming languages, the primary test suite for Perl 6 is not in the same repo as the compiler. Instead, it has its own repo, roast. Roast is The Official Perl 6 Test Suite, and any toolchain which can pass the test suite is a valid Perl 6.

In this particular case, I added a test to S32-num/rat.t. The test makes sure that a Rat value with a large numerator can round trip via the .perl method and EVAL:

is(Rat.new(807412079564, 555).perl.EVAL, Rat.new(807412079564, 555), '807412079564/555 round trips without a compile_time_value error');

Running this with the latest Rakudo caused the test to blow up with the same error as my first example. Success! Well, failure, but success at failing the way I wanted it to.

Next I had to figure out where this error was happening. This can be a bit tricky with compiler errors like this. The best way to get a clue to the problem’s location is to pass --ll-exception as a CLI flag:

$ perl6 --ll-exception -e 'say <2147483648/3>'
Cannot find method 'compile_time_value'
   at gen/moar/m-Perl6-Actions.nqp:7230  (/home/autarch/.rakudobrew/moar-nom/install/share/nqp/lib/Perl6/Actions.moarvm:bare_rat_number:35)
 from gen/moar/stage2/QRegex.nqp:1342  (/home/autarch/.rakudobrew/moar-nom/install/share/nqp/lib/QRegex.moarvm:!reduce:29)
 from gen/moar/stage2/QRegex.nqp:1303  (/home/autarch/.rakudobrew/moar-nom/install/share/nqp/lib/QRegex.moarvm:!cursor_pass:47)
 from :1  (/home/autarch/.rakudobrew/moar-nom/install/share/nqp/lib/Perl6/Grammar.moarvm:bare_rat_number:133)
 from :1  (/home/autarch/.rakudobrew/moar-nom/install/share/nqp/lib/Perl6/Grammar.moarvm:rat_number:64)
 ... [many more lines]

If we look at the top of the trace we see references to Perl6/Actions.moarvm:bare_rat_number. Looking in the rakudo repo, we can find a src/Perl6/Actions.nqp file that contains method bare_rat_number($/) {...}. This seemed like a pretty good guess at where the error was coming from.

Here’s the method in full before my patch:

method bare_rat_number($/) {
    my $nu := @($.ast)[0].compile_time_value;
    my $de := $.ast;
    my $ast := $*W.add_constant('Rat', 'type_new', $nu, $de, :nocache(1));
    make $ast;

After doing some dumping of values in the AST with code like note($.dump), I realized that the numerator could end up being passed in as either a QAST::Want or QAST::WVal object. What are these and how do they differ? Why is there a break at 231? I have no clue!

However, I could see that while a QAST::Want object could be treated as an array, a QAST::WVal could not. Fortunately, both objects support a compile_time_value method. Looking at this method’s implementation in QAST::Want I could see that it was getting the first array element from the object’s internals, while QAST::WVal implemented this differently. But since they both implement the same method why not just call it and be done with it?

Here’s the patched method:

method bare_rat_number($/) {
    my $nu := $.ast.compile_time_value;
    my $de := $.ast;
    my $ast := $*W.add_constant('Rat', 'type_new', $nu, $de, :nocache(1));
    make $ast;

All the tests passed, so I think I fixed it.

Overall, this wasn’t too hard. Because much of Perl 6 is either written in Perl 6 or in NQP (a subset of Perl 6), fixing the core can be much easier than with many other languages, especially most dynamic languages which are implemented in C.

One comment

Comments are closed.