Skip to content

CastKind::ClosureFnPointer implemented wrong #413

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

Closed
bjorn3 opened this issue Mar 10, 2019 · 4 comments
Closed

CastKind::ClosureFnPointer implemented wrong #413

bjorn3 opened this issue Mar 10, 2019 · 4 comments
Labels
C-bug Category: This is a bug. help wanted Extra attention is needed
Milestone

Comments

@bjorn3
Copy link
Member

bjorn3 commented Mar 10, 2019

test compile
set is_pic
target x86_64-apple-darwin

function u0:0() system_v {
; symbol _ZN22closure_to_fn_coercion4main17hcc5f764723193903E
; instance Instance { def: Item(DefId(0/0:3 ~ closure_to_fn_coercion[317d]::main[0])), substs: [] }
; sig ([]; c_variadic: false)->()

; ssa {_4: (empty), _0: NOT_SSA, _2: NOT_SSA, _3: NOT_SSA, _1: (empty)}
; msg   loc.idx    param    pass mode            ssa flags  ty
; ret    _0      -          NoPass               NOT_SSA    ()
; zst    _2: [closure@rust/src/test/run-pass/functions-closures/closure-to-fn-coercion.rs:4:29: 4:42] size=0 align=1, 8

    ss0 = explicit_slot 1 ; _3: u8 size=1 align=1,1
    sig0 = (i64, i8) -> i8 system_v
    sig1 = (i8) -> i8 system_v
    fn0 = colocated u0:9 sig0 ; Instance { def: ClosureOnceShim { call_once: DefId(2/0:1000 ~ core[5e67]::ops[0]::function[0]::FnOnce[0]::call_once[0]) }, substs: [[closure@rust/src/test/run-pass/functions-closures/closure-to-fn-coercion.rs:4:29: 4:42], (u8,)] }

                                ebb0:
                                    v0 = iconst.i64 45
                                    v1 = stack_addr.i64 ss0
                                    jump ebb1

                                ebb1:
                                    nop 
; _1 = move _2 as fn(u8) -> u8 (ClosureFnPointer)
@0001                               v2 = func_addr.i64 fn0
; _4 = _1
; 
; _3 = move _4(const 31u8)
@0004                               v3 = iconst.i8 31
@0004                               v4 = call_indirect sig1, v2(v3)
@0004                               store v4, v1
@0004                               jump ebb2

                                ebb2:
@0004                               nop 
; 
; return
@0007                               return
}

The signature of fn0 doesn't match the expected signature. Somehow fn0 has a signature with a self param, while it shouldn't.

@bjorn3 bjorn3 added the C-bug Category: This is a bug. label Mar 10, 2019
@bjorn3 bjorn3 added this to the MVP milestone Mar 10, 2019
@bjorn3 bjorn3 added the help wanted Extra attention is needed label Mar 25, 2019
@bjorn3
Copy link
Member Author

bjorn3 commented Mar 25, 2019

I just don't understand how cg_llvm managed to implement it correctly. I copied the code to get the shim straight from cg_llvm.

@bjorn3
Copy link
Member Author

bjorn3 commented Mar 25, 2019

^ cc @est31 as seem to have implemented it in rust-lang/rust@1b9b322. I hope you can help me.

@est31
Copy link
Member

est31 commented Mar 25, 2019

Hi @bjorn3 thanks for doing the cranelift work. Really looking forward to trying it out one day in the future!

The signature of fn0 doesn't match the expected signature. Somehow fn0 has a signature with a self param, while it shouldn't.

I'll try to give a full explanation.

Consider this code snippet:

    let v = 32;
    let c = |n| {
        v - n
    };
    let w = c(1);
    println!("I am est{}", w);

Closures are lowered the following way:

    let v = 32;
    struct ClosureC {
        v: i16,
    }
    fn c_closure(this: ClosureC, n: i16) -> i16 {
        this.v - n
    }
    let c = ClosureC {
        v
    };
    let w = c_closure(c, 1);
    println!("I am est{}", w);

All the captured get put into a custom made struct that get's passed to the function via a this param.

When you have a non capturing closure, the struct is empty.

    let c = |n| {
        32 - n
    };
    let w = c(1);
    println!("I am est{}", w);

gets lowered to

    struct ClosureC {
        // Nothing here, the closure is non-capturing
    }
    fn c_closure(_this: ClosureC, n: i16) -> i16 {
        32 - n
    }
    let c = ClosureC {
        // Nothing here either
    };
    let w = c_closure(c, 1);
    println!("I am est{}", w);

But what does this mean on an ABI level? If you comment out the println because it creates so much irrelevant noise and open debug mode LLVM IR in the playground, you will find the following signature:

define internal i16 @_ZN10playground4main9c_closure17h39852d59d8bee113E(i16) unnamed_addr #0 !dbg !135 {

Note the single i16 param. In comparison, the same signature for the capturing closure example above:

define internal i16 @_ZN10playground4main9c_closure17hd627dc2bb779d792E(i16, i16) unnamed_addr #0 !dbg !144 {

Basically, the empty struct is just a newtype of (), it's just not allocated on the stack, nor is it actually passed to the function because there is nothing to pass.

Thus, at the ABI level it looks like:

    fn c_closure(n: i16) -> i16 {
        32 - n
    }
    let w = c_closure(1);
    println!("I am est{}", w);

You can verify that it indeed has size zero:

    struct ClosureC {
        // Nothing here, the closure is non-capturing
    }
    assert_eq!(std::mem::size_of::<ClosureC>(), 0);

Now, we can actually coerce the closure to a function pointer:

    fn c_closure(n: i16) -> i16 {
        32 - n
    }
    let ptr: fn(i16)->i16 = c_closure;
    let w = ptr(1);
    println!("I am est{}", w);

That's all there is to it. TLDR: In the type system there is a self parameter, yes, but that parameter vanishes.

I hope I could help.

@bjorn3
Copy link
Member Author

bjorn3 commented Mar 26, 2019

Thanks! That explains it. Will update cg_clif soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants