Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

constants wont optimize

by patcat88 (Deacon)
on Jul 09, 2011 at 22:54 UTC ( [id://913554]=perlquestion: print w/replies, xml ) Need Help??

patcat88 has asked for the wisdom of the Perl Monks concerning the following question:

under perl 5.12.2, with deparse, I see constants that are not being optimized. There is also an issue with "if(func)" vs "if(func())".

No parentheses, with module. this works.
BEGIN { package mod; $VERSION = 1; package main; } BEGIN { if (scalar(%mod::) && $mod::VERSION ) {eval " sub haveMod () { return 1; }" ;} else {eval "sub haveMod () { return 0; }" ;} sub checkMod () { if(haveMod) {return "goodMod";} else {return "badMod";} } } print checkMod;
C:\Documents and Settings\Owner\Desktop>perl -MO=Deparse, consttest.p +l & perl c onsttest.pl BEGIN { $^W = 1; } sub BEGIN { package mod; $VERSION = 1; } sub BEGIN { if (scalar %mod:: and $mod::VERSION) { eval ' sub haveMod () { return 1; }'; } else { eval 'sub haveMod () { return 0; }'; } } sub checkMod () { do { return 'goodMod' }; } print checkMod; consttest.pl syntax OK goodMod C:\Documents and Settings\Owner\Desktop>


No module, no parentheses. This totally doesn't work. I got goodMod instead of badMod.
#!/usr/bin/perl -w #COMMENTED OUT #BEGIN { # package mod; # $VERSION = 1; # package main; #} BEGIN { if (scalar(%mod::) && $mod::VERSION ) { eval " sub haveMod () { return 1; }" ;} else { eval "sub haveMod () { return 0; }" ;} sub checkMod () { if(haveMod) {return "goodMod";} else {return "badMod";} } } print checkMod;
C:\Documents and Settings\Owner\Desktop>perl -MO=Deparse, consttest.p +l & perl c onsttest.pl BEGIN { $^W = 1; } sub BEGIN { if (scalar %{'mod::'} and $mod::VERSION) { eval ' sub haveMod () { return 1; }'; } else { eval 'sub haveMod () { return 0; }'; } } sub checkMod () { do { return 'goodMod' }; } print checkMod; consttest.pl syntax OK goodMod C:\Documents and Settings\Owner\Desktop>
With parentheses, with module. This works, but didn't optimize.
#!/usr/bin/perl -w BEGIN { package mod; $VERSION = 1; package main; } BEGIN { if (scalar(%mod::) && $mod::VERSION ) { eval " sub haveMod () { return 1; }" ;} else { eval "sub haveMod () { return 0; }" ;} sub checkMod () { #NOTE THE PARENTHESES if(haveMod()) {return "goodMod";} else {return "badMod";} } } print checkMod;
C:\Documents and Settings\Owner\Desktop>perl -MO=Deparse, consttest.p +l & perl c onsttest.pl BEGIN { $^W = 1; } sub BEGIN { package mod; $VERSION = 1; } sub BEGIN { if (scalar %mod:: and $mod::VERSION) { eval ' sub haveMod () { return 1; }'; } else { eval 'sub haveMod () { return 0; }'; } } sub checkMod () { if (haveMod) { return 'goodMod'; } else { return 'badMod'; } } print checkMod; consttest.pl syntax OK goodMod C:\Documents and Settings\Owner\Desktop>
With parenthesis, no module, this works didn't optimize.
C:\Documents and Settings\Owner\Desktop>cat consttest.pl & echo: & ech +o: & echo: & perl -MO=Deparse, consttest.pl & perl consttest.pl #!/usr/bin/perl -w #BEGIN { # package mod; # $VERSION = 1; # package main; #} BEGIN { if (scalar(%mod::) && $mod::VERSION ) { eval " sub haveMod () { return 1; }" ;} else { eval "sub haveMod () { return 0; }" ;} sub checkMod () { if(haveMod()) {return "goodMod";} else {return "badMod";} } } print checkMod; BEGIN { $^W = 1; } sub BEGIN { if (scalar %{'mod::'} and $mod::VERSION) { eval ' sub haveMod () { return 1; }'; } else { eval 'sub haveMod () { return 0; }'; } } sub checkMod () { if (haveMod) { return 'goodMod'; } else { return 'badMod'; } } print checkMod; consttest.pl syntax OK badMod C:\Documents and Settings\Owner\Desktop>
A not, no parenthesis, wrong result.
C:\Documents and Settings\Owner\Desktop>cat consttest.pl & echo: & ech +o: & echo: & perl -MO=Deparse, consttest.pl & perl consttest.pl #!/usr/bin/perl -w #BEGIN { # package mod; # $VERSION = 1; # package main; #} BEGIN { if (scalar(%mod::) && $mod::VERSION ) { eval " sub haveMod () { return 1; }" ;} else { eval "sub haveMod () { return 0; }" ;} sub checkMod () { if(! haveMod) {return "badMod";} else {return "goodMod";} } } print checkMod; BEGIN { $^W = 1; } sub BEGIN { if (scalar %{'mod::'} and $mod::VERSION) { eval ' sub haveMod () { return 1; }'; } else { eval 'sub haveMod () { return 0; }'; } } sub checkMod () { if (not 'haveMod') { return 'badMod'; } else { return 'goodMod'; } } print checkMod; consttest.pl syntax OK goodMod C:\Documents and Settings\Owner\Desktop>
So how do you do compile time constant folding with perl? Conditionally eval large complicated subroutines such as checkMod() in my example? And why does "if(func)" seem to check if the function is defined, and optimizes, rather than run it?

Replies are listed 'Best First'.
Re: constants wont optimize
by Juerd (Abbot) on Jul 09, 2011 at 23:54 UTC

    Drop the "return" keyword:

    juerd@lanova:~$ perl -MO=Deparse -e'BEGIN { eval "sub foo () { return +42; }" } print foo;' sub BEGIN { eval 'sub foo () { return 42; }'; } print foo; -e syntax OK juerd@lanova:~$ perl -MO=Deparse -e'BEGIN { eval "sub foo () { 42 }" } + print foo;' sub BEGIN { eval 'sub foo () { 42 }'; } print 42; -e syntax OK
    Also, there's a better way than using eval. You can assign a code reference to a glob:
    *glob = sub () { value };
    juerd@lanova:~$ perl -MO=Deparse -e'BEGIN { *foo = sub () { 42 } } pri +nt foo;' sub BEGIN { *foo = sub () { 42 } ; } print 42; -e syntax OK

    Juerd

      I think I found a solution, but I dont know why it works. The fix is to throw another BEGIN around the part that sets the constant. I would like to know why it works.
      C:\Documents and Settings\Owner\Desktop>cat consttest.pl & echo: & ech +o: & echo: & perl -MO=Deparse, consttest.pl & perl consttest.pl #!/usr/bin/perl -w #BEGIN { # package mod; # $VERSION = 1; # package main; #} BEGIN { BEGIN { if (scalar(%mod::) && $mod::VERSION ) { eval " sub haveMod () { 1; }" ;} else { eval "sub haveMod () { 0; }" ;} } sub checkMod () { if(haveMod()) {return "goodMod";} else {return "badMod";} } } print checkMod; BEGIN { $^W = 1; } sub BEGIN { sub BEGIN { sub checkMod () { do { return 'badMod' }; } } if (scalar %{'mod::'} and $mod::VERSION) { eval ' sub haveMod () { 1; }'; } else { eval 'sub haveMod () { 0; }'; } } print checkMod; consttest.pl syntax OK badMod C:\Documents and Settings\Owner\Desktop>
      Getting rid of the returns doesn't fix the problem, the return is always implied. Last statement of a subroutine is the return result in Perl.
      C:\Documents and Settings\Owner\Desktop>cat consttest.pl & echo: & ech +o: & echo: & perl -MO=Deparse, consttest.pl & perl consttest.pl #!/usr/bin/perl -w #BEGIN { # package mod; # $VERSION = 1; # package main; #} BEGIN { if (scalar(%mod::) && $mod::VERSION ) { eval " sub haveMod () { 1; }" ;} else { eval "sub haveMod () { 0; }" ;} sub checkMod () { if(haveMod()) {return "goodMod";} else {return "badMod";} } } print checkMod; BEGIN { $^W = 1; } sub BEGIN { if (scalar %{'mod::'} and $mod::VERSION) { eval ' sub haveMod () { 1; }'; } else { eval 'sub haveMod () { 0; }'; } } sub checkMod () { if (haveMod) { return 'goodMod'; } else { return 'badMod'; } } print checkMod; consttest.pl syntax OK badMod C:\Documents and Settings\Owner\Desktop>
        The fix is to throw another BEGIN around the part that sets the constant. I would like to know why it works.

        eval is a runtime operation. Sub declaration is a compile time operation. BEGIN does nothing to change that unless you use a BEGIN to perform the runtime eval before the compile time declaration occurs.

        All of the operations within a single BEGIN block occur, with respect to each other, in the order they would occur if not for the BEGIN block.

Re: constants wont optimize
by ikegami (Patriarch) on Jul 10, 2011 at 02:40 UTC

    It's much much simpler than that:

    use constant haveMod => $mod::VERSION; print haveMod ? "goodMod" : "badMod";
      I am trying to NOT use constant. use if eval { require mod; 1 }, qw' constant haveMod 1 '; can't get very complicated, cosmetically, For example, during running/compiling of the require of a use for a PM, probing the capabilities of the current OS/perl installation, including, calling XSUBs, setting the constant subs, then defining the subs that rely on those constant subs and having the perl compiler delete the subroutine branches for never to be used code path.

      I looked through the source of constant.pm, its doing something black magic, "mro::method_changed_in($classname)" and Internals::SvREADONLY to make its constant subs. Not the public Constant Functions way.

        Actually, as you've found out, your way is the complicated one.

        Simple:

        use constant haveMod => eval { require mod; };

        And for the non-trivial:

        my $haveMod; BEGIN { ...[ something complicated ]... $haveMod = ...; } use constant haveMod => $haveMod;
        or even
        BEGIN { ...[ something complicated ]... require constant; import constant haveMod => ...; }

        PS - require always returns true, so the "1" is redundant.

Re: constants wont optimize
by Somni (Friar) on Jul 10, 2011 at 00:13 UTC
    Ah, Juerd's comment about return explains why I was unable to replicate your problem with any version of perl I have. I automatically corrected it when writing my test code.

    Anyways, in case someone is interested, here's the test code. I've modified it to show the problem with return; it won't work on Windows as is, you'll have to remove the 2>/dev/null.

    #!/usr/bin/perl -w use strict; { my $const_return = 'BEGIN { eval "sub HAS () { return 1 }" }'; my $const_noreturn = 'BEGIN { eval "sub HAS () { 1 }" }'; my $const_use = 'use constant; BEGIN { constant->import( HAS +=> 1 ) }'; my $paren = 'print HAS;'; my $noparen = 'print HAS();'; print( 'return, with parens ', run($const_return, $paren ), +"\n", 'return, without parens ', run($const_return, $noparen), +"\n", 'noreturn, with parens ', run($const_noreturn, $paren ), +"\n", 'noreturn, without parens ', run($const_noreturn, $noparen), +"\n", 'use, with parens ', run($const_use, $paren ), +"\n", 'use, without parens ', run($const_use, $noparen), +"\n", ); } sub run { my($code) = join ' ', @_; my @normal = qx{perl -wle \Q$code\E }; my @deparse = qx{perl -MO=Deparse -wle \Q$code\E 2>/dev/null}; chomp(my $normal_output = $normal[-1]); chomp(my $deparse_output = $deparse[-1]); return join ' ', $normal_output, $deparse_output; }

    And some example output, with 5.12.2:

    > px 5.12.2 ./constant-folding.pl return, with parens 1 print HAS; return, without parens 1 print HAS; noreturn, with parens 1 print 1; noreturn, without parens 1 print 1; use, with parens 1 print 1; use, without parens 1 print 1;
      on Windows as is, you'll have to remove the 2>/dev/null

      On Windows the equivalent of the Unix /dev/null device, the "bit bucket", is the "file" called NUL. Note one 'L'. 2>NUL is same as 2>/dev/null on Unix.

      The case of the name NUL can be anything, nUl, etc. This name is reserved by Windows in all directories - you cannot make a file called NUL. ">type NUL" prints nothing instead of printing "file not found".

        $ perl -MFile::Spec -le " print File::Spec->devnull" nul
        perlport - Writing portable Perl
Re: constants wont optimize
by Anonymous Monk on Jul 10, 2011 at 01:26 UTC
    use if eval { require mod; 1 }, qw' constant haveMod 1 '; use if not exist $INC{'mod.pm'}, qw' constant haveMod 0 '; use Module::Loaded qw' is_loaded '; use constant haveMod => !! is_loaded('mod');

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://913554]
Approved by davido
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others pondering the Monastery: (6)
As of 2024-04-23 07:26 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found