AnyDSL / thorin

The Higher-Order Intermediate Representation
https://anydsl.github.io
GNU Lesser General Public License v3.0
150 stars 15 forks source link

Parameter elimination broken #19

Closed madmann91 closed 9 years ago

madmann91 commented 9 years ago

Parameter elimination assumes that every use of a lambda is itself a lambda. While loops generate select nodes which take lambda as parameters. Hence, parameter elimination breaks on these nodes. Here is an example taken from the traversal code:

while_body_8328(mem mem_8329)
    pop_node_4421 mem_8329, stack_8289, while_body_8332

while_body_8332(mem mem_8333, qs32 id_8334)
    is_empty_4402 mem_8333, stack_8289, while_head_8339

while_head_8339(mem mem_8340, bool is_empty_8341)
    fn(mem) _8342 = @ is_empty_8341 select next_8326, while_body_8328
    _8342 mem_8340

The lambda while_body_8328 is used as an operand for the select node. Then the following part of parameter elimination breaks (thorin/transform/cleanup_world.cpp:61):

for (auto use : olambda->uses()) {
    auto ulambda = use->as_lambda();
    assert(use.index() == 0 && "deleted param of lambda used as argument");
    ulambda->jump(nlambda, ulambda->args().cut(proxy_idx));
}

If I replace it by this it seems to work:

for (auto use : olambda->uses()) {
    if (auto ulambda = use->isa_lambda()) {
        assert(use.index() == 0 && "deleted param of lambda used as argument");
        ulambda->jump(nlambda, ulambda->args().cut(proxy_idx));
    }
}

Shall I push this patch ?

leissa commented 9 years ago

I am a bit puzzled, why while_body_8328 gets a parameter at all. How can I reproduce this bug?

madmann91 commented 9 years ago

See traversal/cleanup_bug branch. Compile with backend = nvvm, accel = bvh, and vectorize = 1. I have stripped down the code a bit.

madmann91 commented 9 years ago

So I figured out what the problem could be. This code is accepted, while it should not be :

extern "C" {
    fn plausible_number() -> i32;
    fn print_number(i32) -> ();
}

fn this_function(a: i32, b: i32, c: fn (i32, i32) -> ()) -> () {
    c(a + b, plausible_number());
}

extern fn lolz(k: i32) -> () {
    for i in this_function(0, k) { // Should not be accepted : 'c' takes 2 parameters in function 'broken'
       print_number(i)
    }
}
madmann91 commented 9 years ago

Well, I updated the code and this doesn't solve the bug. The parameter elimination phase still breaks.

madmann91 commented 9 years ago

Now I have a minimal example that triggers the bug :

struct Thing {
    count: i32
}

fn is_empty(thing: &Thing) -> bool {
    thing.count == 0
}

fn call_me(body: fn() -> ()) -> () {
    body();
}

fn test(mut i: &i32, body: fn() -> ()) -> () @{
    call_me(|| {
        @body();
        *i = 42;
    });
}

extern fn cleanup_bug(i: &i32) -> () @{
    for test(i) {
        let mut thing : Thing;

        // Only breaks if it is a 'while' loop
        while !is_empty(&thing) {
        }

        // This, for instance, works :
        /*fn loop(return: fn()) {
            if !is_empty(&thing) {
                loop(return)
            }
        }
        loop(continue)*/
    }
}
leissa commented 9 years ago

thanks. I will look into as soon as I have time.

leissa commented 9 years ago

Today, I finally had time to look into this issue.

Bit hard to explain what's exactly going on but long story short: It's perfectly fine that while_body gets a parameter. This select user of while_body is dead code. So I think for the time being the simplest solution is your proposed fix.