`inline for` behaves unexpectedly when calling a function

fn is_even(n: usize) bool {
    return (n % 2 == 0);
}

pub fn main() void {
    const numbers = .{ 1, 2, 3, 4, 5, 6 };

    inline for (numbers) |n|
        if (n % 2 == 0) @compileLog(n); // 2, 4, 6

    inline for (numbers) |n|
        if (is_even(n)) @compileLog(n); // 1, 2, 3, 4, 5, 6
}

The first loop logs 2, 4, 6, which is expected.
The second logs 1, 2, 3, 4, 5, 6.

Why does wrapping the predicate in a function change the behaviour? I would’ve expected it to be evaluated given n is comptime known.

Version: 0.17.0-dev.956+2dca73595

You need to call the function at comptime

1 Like

The is_even() function call is evaluated at runtime. Because of this, the compiler evaluates the code, for both possibilities of the return value of is_even(). You get 2, 4, 6 if you do if (comptime is_even(n)) @compileLog(n);.

4 Likes

huh??? i don’t get this.

1 Like

The question or the solution?

1 Like

the solution

Just manually unroll the loop and it will make sense.

pub fn main() void {
    const numbers = .{ 1, 2, 3, 4, 5, 6 };

    // First loop
    if (1 % 2 == 0) {
        // Known at compile time to be false,
        // so the compiler doesn't even look
        // at what's inside here.
        // The compiler never reaches this
        // `compileLog`, so the 1 doesn't get logged.
        @compileLog(1); 
    }
    if (2 % 2 == 0) {
         //Known at compile time to be true,
         // so the block gets compiled, and the compiler
         // sees the `compileLog` and logs 2
         @compileLog(2); 
    //...

    // Second loop
        if (is_even(1)) {
             // Not known at compile time, so the compiler
             // must compile what's inside, and it
             // reaches the `compileLog`.
             // Even if this branch gets removed
             // by optimizations, the logging has 
             // already been done.
            @compileLog(1);
        }
    // Same for all other elements, so all get logged
}
4 Likes

ok… it is some @compilelog / compiletime thingy. not a logical thing.
if we replace @compilelog with print we get the same output for both loops.

and… but… still don’t get it! my brain refuses to understand what is happening.

Dead code elimination.

    inline for (numbers) |n|
        if (n % 2 == 0) @compileLog(n); // 2, 4, 6

This code is being evaluated at comptime. It can detect the dead branches, and they are eliminated. The inner block is never even seen by the compiler. The @compileLog is never even reached by the compiler.

    inline for (numbers) |n|
        if (is_even(n)) @compileLog(n); // 1, 2, 3, 4, 5, 6

This is not being evaluated at comptime, therefore the compiler goes through the normal processing of the branches. It is not dead code that gets eliminated at the if, so the @compileLog is reached for each occurrence due to the loop being unrolled with inline for.

i will play around with this and watch what is happening. then maybe some neurons will click :slight_smile: