The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Chess::Plisco::Macro - Macros/inline functions for Chess::Plisco

SYNOPSIS

    use integer;
    use Chess::Plisco qw(:all);
    use Chess::Plisco::Macro;

    my $white_pieces = cp_pos_white_pieces(Chess::Plisco->new);

    my $bitboard = '0x8080808080808080';
    my $popcount;

    cp_bitboard_popcount $bitboard, $popcount;
    cp_bitboard_popcount($bitboard, $popcount); # You can also use parentheses ...

    print "There are $popcount bits set in $bitboard.\n";

DESCRIPTION

The module Chess::Plisco::Macro is a source filter. It makes a number of macros respectively inline functions available that can be invoked without any subroutine call overhead. In fact, all invocations of these macros are translated into constant expressions at compile-time.

All the functionality available in the module is also available as instance or class methods of Chess::Plisco. You should only use the macros defined here if you have to squeeze out the last bit of performance from your code, for example, when writing a chess engine. Otherwise, using the macros has only disadvantages:

It increases the compile-time of your code significantly.
Syntax checks on your code may fail.
Seemingly correct syntax may lead to syntax errors.
Your code becomes less readable; although sometimes it may not.

If performance outweighs these problems in your use case, read on!

The source filter is probably not perfect but is able to translate at least the source code of Chess::Plisco. If you have trouble like unexpected syntax errors in your own code, you can use the function preprocess() (see below), to get a translation of your source file, and find the problem.

Please note that not all translation errors are considered a bug of the source filter. If the problem can be avoided by re-formulating your code, a fix will probably be refused.

MOTIVATION

Chess programming should be really fast, and in this context the unavoidable overhead of method or subroutine calls contributes enormously to the execution time of the software.

In the C programming language, you can use preprocessor macros or inline functions in order to avoid the calling overhead. In Perl, this can only be done for constants with the constant pragma:

    use constant CP_FILE_A => 0;
    use constant CP_SQUARE_E1 => 0x0808080808080808 | 0x00000000000000ff;

These are actually subroutines but Perl inlines them into your code, even with constant folding (see the second example), so that you pay no price for the use of these constants.

But Chess::Plisco needs parametrized macros. For example, if you want to extract the start square of a move, you have to do this:

    $from = ((($move) >> 6) & 0x3f);

But it is awkward to remember. Other computations are even more complicated. For example to get the number of bits set in a bitboard $bb, you have to do the following:

    my $_b = $bb;

    for ($popcount = 0; $_b; ++$popcount) {
        $_b &= $_b - 1;
    }

This is a well-known algorithm but you either have to implement it in a function or method and pay the price of subroutine calls, or you have to repeat it over and over in your code.

This module tries to mitigate that dilemma. If you just "use" it, all invocations of the macros defined here, are translated into a regular statements so that you can do these computations without subroutine call overhead.

Depending on the exact implementation of the macro, you can sometimes even use them as l-vales (the left-hand side of an assignment) but you should generally avoid this because the resulting code will become hard to understand.

PITFALLS

There are a number of caveats, when using this module. You had been warned before, do not use it! If you cannot help, then ...

Import Constants from Chess::Plisco

Remember that the macro calls defined in your code will be replaced by other code at compile-time and that other code makes use of constants defined in Chess::Plisco. Therefore, you should always import these constants with use Chess::Plisco ':all'.

Use integer

Almost all of the macros run a lot faster with use integer and some of them only work with use integer. Therefore, you should always enable that pragma for your code.

Do Not Use Here Documents

The translation currently chokes on here documents (<<EOF ...).

Use More Parentheses

Remember that the macros are expanded by a rather dumb search and replace. That can lead to problems with parentheses. Say you have a line like this in a test written with Test::More

    is cp_pos_material($pos), 0, "material test";

But this may be expanded into something like:

    is (($pos->[CP_POS_INFO]) >> 19), 0, "material test";

And this is not what you meant. Now, is() is called with just one argument instead of 3 like you expected.

If the parentheses were omitted in the macro definition, strange precedence problems in the expanded code would occur instead. In other words, it is not possible to fix both problems at once. In doubt, macros are wrapped into parentheses and it is therefore usually safer to also use parentheses, when you want to use macros as arguments to subroutines. The correct test code would look like this:

    is(cp_pos_material($pos), 0, "material test");

Do not Create References to Macros

The macro calls will be replaced at compile-time with other code. Therefore, it is not possible to create a reference to a macro. Doing so may or may not cause a compile-time error but it will never do what you wanted.

Do not Use the String Form of eval

You should not do that anyway but with macros it would not even work because the source code filter cannot know whether a string constant is really code.

The same applies to using the Benchmark module. If you want to benchmark the macros, you either have to translate them first with preprocess() before (see below), or somehow wrap it into a function call. Doing the latter will probably eat up the performance gain the macros give you.

Expect Errors

Macros are expanded with the use of PPI::Document and so the technique shares all limitations of PPI::Document.

And some more esoteric usages will probaly also not work.

FUNCTIONS

preprocess

You should only use one single function of this module, the function preprocess() that does the actual translation of your code. Example:

    require Chess::Plisco::Macro;
    open my $fh, '<', 'YourModule.pm' or die;
    print Chess::Plisco::Macro::preprocess(join '', <$fh>);

This will dump the translated source code of YourModule.pm on standard output.

MACROS

Note that the source filter is theoretically able to inline constants (that are macros without arguments) as well but this feature is not used because it does not have any advantage over the regular constant pragma of Perl.

Macros for Chess::Plisco Instances

cp_pos_white_pieces(POS)

Same as "whitePieces" in Chess::Plisco.

cp_pos_black_pieces(POS)

Same as "blackPieces" in Chess::Plisco.

cp_pos_kings(POS)

Same as "kings" in Chess::Plisco.

cp_pos_queens(POS)

Same as "queens" in Chess::Plisco.

cp_pos_rooks(POS)

Same as "rooks" in Chess::Plisco.

cp_pos_bishops(POS)

Same as "bishops" in Chess::Plisco.

cp_pos_knights(POS)

Same as "knights" in Chess::Plisco.

cp_pos_pawns(POS)

Same as "pawns" in Chess::Plisco.

cp_pos_to_move(POS)

Same as "toMove" in Chess::Plisco.

cp_pos_castling_rights(POS)

Same as "castlingRights" in Chess::Plisco.

cp_pos_white_king_side_castling_right(POS)

Same as "whiteKingSideCastlingRight" in Chess::Plisco.

cp_pos_white_queen_side_castling_right(POS)

Same as "whiteQueenSideCastlingRight" in Chess::Plisco.

cp_pos_black_king_side_castling_right(POS)

Same as "blackKingSideCastlingRight" in Chess::Plisco.

cp_pos_black_queen_side_castling_right(POS)

Same as "blackQueenSideCastlingRight" in Chess::Plisco.

cp_pos_half_move_clock(POS)

Same as "halfMoveClock" in Chess::Plisco.

cp_pos_reversible_clock(POS)

Same as "reversibleClock" in Chess::Plisco.

cp_pos_half_moves(POS)

Same as "halfMoves" in Chess::Plisco.

cp_pos_en_passant_shift(POS)

Same as "enPassantShift" in Chess::Plisco.

cp_pos_king_shift(POS)

Same as "kingShift" in Chess::Plisco.

cp_pos_in_check(POS)

Same as "inCheck" in Chess::Plisco.

cp_pos_evasion(POS)

Same as "evasion" in Chess::Plisco.

cp_pos_evasion_squares(POS)

Same as "evasionSquares" in Chess::Plisco.

cp_pos_signature(POS)

Same as "signature" in Chess::Plisco.

cp_pos_material(POS)

Same as "material" in Chess::Plisco.

cp_pos_info(POS)

Same as "info" in Chess::Plisco. See also "Position Info Macros" below!

Position Info Macros

All these work directly on a position info as returned by "cp_pos_info".

cp_pos_info_castling_rights(INFO)

Same as "cp_pos_castling_rights" but work directly on position info INFO as returned by "cp_pos_info".

cp_pos_info_white_king_side_castling_right(INFO)

Same as "cp_pos_white_king_side_castling_right" but work directly on position info INFO as returned by "cp_pos_info".

cp_pos_info_white_queen_side_castling_right(INFO)

Same as "cp_pos_white_queen_side_castling_right" but work directly on position info INFO as returned by "cp_pos_info".

cp_pos_info_black_king_side_castling_right(INFO)

Same as "cp_pos_black_king_side_castling_right" but work directly on position info INFO as returned by "cp_pos_info".

cp_pos_info_black_queen_side_castling_right(INFO)

Same as "cp_pos_black_queen_side_castling_right" but work directly on position info INFO as returned by "cp_pos_info".

cp_pos_info_to_move(INFO)

Same as "cp_pos_to_move" but work directly on position info INFO as returned by "cp_pos_info".

cp_pos_info_en_passant_shift(INFO)

Same as "cp_pos_en_passant_shift" but work directly on position info INFO as returned by "cp_pos_info".

cp_pos_info_king_shift(INFO)

Same as "cp_pos_king_shift" but work directly on position info INFO as returned by "cp_pos_info".

cp_pos_info_evasion(INFO)

Same as "cp_pos_evasion" but work directly on position info INFO as returned by "cp_pos_info".

cp_pos_info_material(INFO)

Same as "cp_pos_material" but work directly on position info INFO as returned by "cp_pos_info".

Move Macros

These macros operate on scalar moves for Chess::Plisco which are just plain integers. They do not work for instances of a Chess::Plisco::Move!

cp_move_to(MOVE)

Same as "moveTo" in Chess::Plisco.

cp_move_set_to(MOVE, TO)

Same as "moveSetTo" in Chess::Plisco but modifies MOVE directly.

cp_move_from(MOVE)

Same as "moveFrom" in Chess::Plisco.

cp_move_set_from(MOVE, FROM)

Same as "moveSetFrom" in Chess::Plisco but modifies MOVE directly.

cp_move_promote(MOVE)

Same as "movePromote" in Chess::Plisco.

cp_move_set_promote(MOVE, PROMOTE)

Same as "moveSetPromote" in Chess::Plisco but modifies MOVE directly.

cp_move_piece(MOVE)

Same as "movePiece" in Chess::Plisco.

cp_move_set_piece(MOVE, PIECE)

Same as "moveSetPiece" in Chess::Plisco but modifies MOVE directly.

cp_move_captured(MOVE)

Same as "moveCaptured" in Chess::Plisco.

cp_move_set_captured(MOVE, PIECE)

Same as "moveSetCaptured" in Chess::Plisco but modifies MOVE directly.

cp_move_color(MOVE)

Same as "moveCaptured" in Chess::Plisco.

cp_move_set_color(MOVE, COLOR)

Same as "moveSetColor" in Chess::Plisco but modifies MOVE directly.

cp_move_equivalent(MOVE)

Same as "moveEquivalent" in Chess::Plisco.

cp_move_significant(MOVE)

Same as "moveSignificant" in Chess::Plisco.

cp_move_coordinate_notation(MOVE)

Same as "moveCoordinateNotation" in Chess::Plisco.

Macros for Magic Moves Resp. Magic Bitboards

cp_mm_bmagic(SHIFT, OCCUPANCY)

Same as "bMagic" in Chess::Plisco.

cp_mm_rmagic(SHIFT, OCCUPANCY)

Same as "rMagic" in Chess::Plisco.

Bitboard Macros

cp_bitboard_popcount(BITBOARD, COUNT)

Count the number of bits set in BITBOARD and store it in COUNT.

Example:

    cp_bitboard_popcount $bitboard, $count;
    print "There are $count bits set in $bitboard.\n";

See also "bitboardPopcount" in Chess::Plisco!

Important! This works only under the "use integer" pragma!

cp_bitboard_clear_but_least_set(BITBOARD)

Same as "bitboardclearButLeastSet" in Chess::Plisco but modifies BITBOARD directly.

Important! This works only under the "use integer" pragma!

cp_bitboard_clear_but_most_set(BITBOARD)

Same as "bitboardclearButMostSet" in Chess::Plisco but modifies BITBOARD directly.

Important! This works only under the "use integer" pragma!

cp_bitboard_clear_least_set(BITBOARD)

Same as "bitboardClearLeastSet" in Chess::Plisco but modifies BITBOARD directly.

Important! This works only under the "use integer" pragma!

cp_bitboard_count_trailing_zbits(BITBOARD)

Same as "bitboardCountTrailingZbits" in Chess::Plisco.

cp_bitboard_count_isolated_trailing_zbits(BITBOARD)

Same as "bitboardCountIsolatedTrailingZbits" in Chess::Plisco.

cp_bitboard_more_than_one_set(BITBOARD)

Same as "bitboardMoreThanOneSet" in Chess::Plisco.

Miscellaneous Macros

cp_coordinates_to_shift(FILE, RANK)

Calculate a bit shift offset (0-63) for the square that FILE (0-7) and RANK (0-7) point to.

cp_shift_to_coordinates(SHIFT)

Calculate the file (0-7) and rank (0-7) for the bit shift offset SHIFT (0-63).

cp_coordinates_to_square(FILE, RANK)

Calculate the square in coordinate notation ("e4", "c5", ...) for FILE (0-7) and RANK (0-7).

cp_square_to_coordinates(SQUARE)

Calculate the file (0-7) and rank (0-7) for the SQUARE in coordinate notation ("e4", "c5", ...).

cp_square_to_shift(SQUARE)

Calculate the bit shift offset (0-63) for the SQUARE in coordinate notation ("e4", "c5", ...).

cp_shift_to_square(SHIFT)

Calculate the square in coordinate notation ("e4", "c5", ...) for the bit shift offset SHIFT (0-63).

cp_abs(INTEGER)

Calculate the absolve value of INTEGER.

cp_max(A, B)

Calculate the maximum of A and B.

cp_min(A, B)

Calculate the minimum of A and B.

COPYRIGHT

Copyright (C) 2021 Guido Flohr <guido.flohr@cantanea.com>.

SEE ALSO

Chess::Plisco, constant, Filter::Util::Call, perl(1)