Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

a = f(a, b); return value sometimes interfering with argument? #3696

Open
ghost opened this issue Nov 15, 2019 · 4 comments
Open

a = f(a, b); return value sometimes interfering with argument? #3696

ghost opened this issue Nov 15, 2019 · 4 comments
Labels
use case Describes a real use case that is difficult or impossible, but does not propose a solution.
Milestone

Comments

@ghost
Copy link

ghost commented Nov 15, 2019

This code is giving different results based on implementation of concatAll. The implementations look equivalent.

I have a feeling this has something to do with result locations. Is this how things are supposed to be?

Note: The matrix code here is not important - you can just run the different implementations and see that they output different results. Also, this is probably not the most efficient way to design a matrix library, but it's convenient (when it works).

const std = @import("std");

pub fn concat(a: [16]f32, b: [16]f32) [16]f32 {
    return .{
        a[0] * b[ 0] + a[4] * b[ 1] + a[ 8] * b[ 2] + a[12] * b[ 3],
        a[1] * b[ 0] + a[5] * b[ 1] + a[ 9] * b[ 2] + a[13] * b[ 3],
        a[2] * b[ 0] + a[6] * b[ 1] + a[10] * b[ 2] + a[14] * b[ 3],
        a[3] * b[ 0] + a[7] * b[ 1] + a[11] * b[ 2] + a[15] * b[ 3],
        a[0] * b[ 4] + a[4] * b[ 5] + a[ 8] * b[ 6] + a[12] * b[ 7],
        a[1] * b[ 4] + a[5] * b[ 5] + a[ 9] * b[ 6] + a[13] * b[ 7],
        a[2] * b[ 4] + a[6] * b[ 5] + a[10] * b[ 6] + a[14] * b[ 7],
        a[3] * b[ 4] + a[7] * b[ 5] + a[11] * b[ 6] + a[15] * b[ 7],
        a[0] * b[ 8] + a[4] * b[ 9] + a[ 8] * b[10] + a[12] * b[11],
        a[1] * b[ 8] + a[5] * b[ 9] + a[ 9] * b[10] + a[13] * b[11],
        a[2] * b[ 8] + a[6] * b[ 9] + a[10] * b[10] + a[14] * b[11],
        a[3] * b[ 8] + a[7] * b[ 9] + a[11] * b[10] + a[15] * b[11],
        a[0] * b[12] + a[4] * b[13] + a[ 8] * b[14] + a[12] * b[15],
        a[1] * b[12] + a[5] * b[13] + a[ 9] * b[14] + a[13] * b[15],
        a[2] * b[12] + a[6] * b[13] + a[10] * b[14] + a[14] * b[15],
        a[3] * b[12] + a[7] * b[13] + a[11] * b[14] + a[15] * b[15],
    };
}

fn meh(a: [16]f32) [16]f32 {
    return a;
}

fn concatAll(arr: []const [16]f32) [16]f32 {
    // this gives the correct result
    var result = concat(concat(arr[0], arr[1]), arr[2]);

    // this gives a wrong result!
    //var result = arr[0];
    //result = concat(result, arr[1]);
    //result = concat(result, arr[2]);

    // this gives the correct result...
    //var result = arr[0];
    //result = concat(meh(result), arr[1]);
    //result = concat(meh(result), arr[2]);

    return result;
}

pub fn main() void {
    const a = [16]f32 {
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 9, 1,
    };
    const b = [16]f32 {
        0, -1, 0, 0,
        1, 0, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1,
    };
    const c = [16]f32 {
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        7, 0, 0, 1,
    };
    const result = concatAll([_][16]f32 { a, b, c });
    std.debug.warn("{} {} {} {}\n", result[0], result[1], result[2], result[3]);
    std.debug.warn("{} {} {} {}\n", result[4], result[5], result[6], result[7]);
    std.debug.warn("{} {} {} {}\n", result[8], result[9], result[10], result[11]);
    std.debug.warn("{} {} {} {}\n", result[12], result[13], result[14], result[15]);
}
@rohlem
Copy link
Contributor

rohlem commented Nov 16, 2019

I believe (pretty sure) this is the same phenomenon as #3234: Zig currently doesn't introduce a temporary for reassignment of a union, struct, or array,
but instead directly writes the sub-assignments through to the result variable's memory - notably also when it's used as one of the computation's inputs:

test "array reassignment"{
 var a = [_]i32{1, -1};
 a = .{a[0] + a[1], //written first: a[0] is now 0
       a[0] + a[1]}; //read afterwards, resulting in 0 + (-1)
 if(a[0] != 0 or a[1] != -1) unreachable;
}

The same thing happens in the second concatAll implementation across a function boundary.
The current workaround (/idiom if we assume it's by design) is to introduce a temporary var for storing the result, and copying it back into place when all values are resolved (note: quite unergonomic for self-referential types).

(I believe the first concatAll implementation works because the actual result location, var result, is (currently) deduced to be local to concatAll, and so is distinct from arr[0..3] (which might change with #2765 ). I think the third implementation works because its intermediate function call just outsmarts/interrupts current result location detection/flow.)

@SpexGuy
Copy link
Contributor

SpexGuy commented Mar 22, 2021

Another bug caused by this: #8330

@Vexu
Copy link
Member

Vexu commented Mar 22, 2021

Not really, this issue is about the return value's result location pointer aliasing with the argument's hidden pointer, in #8330 the issue is caused by an optimization not honoring promises about aliasing.

@SpexGuy
Copy link
Contributor

SpexGuy commented Mar 22, 2021

You're right, it's a better fit for #5973 instead. Parameter passing in 8330 is not an optimization, these are the current language semantics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
use case Describes a real use case that is difficult or impossible, but does not propose a solution.
Projects
None yet
Development

No branches or pull requests

4 participants