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

Distinguish fn item types to allow reification from nothing to fn pointers. #31710

Merged
merged 19 commits into from Mar 10, 2016

Conversation

Projects
None yet
8 participants
@eddyb
Copy link
Member

eddyb commented Feb 16, 2016

Type-checking and translation of zero-sized function item types (fixes #19925).

The first commit is a rebase of #26284, except for files that have moved since.

This is a [breaking-change], due to:

  • each FFI function has a distinct type, like all other functions currently do
  • all generic parameters on functions are recorded in their item types, e.g.:
    size_of::<u8> & size_of::<i8>'s types differ despite their identical signature.
  • function items are zero-sized, which will stop transmutes from working on them

The first two cases are handled in most cases with the new coerce-unify logic,
which will combine incompatible function item types into function pointers,
at the outer-most level of if-else chains, match arms and array literals.

The last case is specially handled during type-checking such that transmutes
from a function item type to a pointer or integer type will continue to work for
another release cycle, but are being linted against. To get rid of warnings and
ensure your code will continue to compile, cast to a pointer before transmuting.

@rust-highfive

This comment has been minimized.

Copy link
Collaborator

rust-highfive commented Feb 16, 2016

r? @arielb1

(rust_highfive has picked a reviewer for you, use r? to override)

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Feb 16, 2016

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Feb 16, 2016

Triggering a crater run.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Feb 16, 2016

Crater run results: https://gist.github.com/nikomatsakis/0902ea970301a841231e

One regression (fann).

@eddyb eddyb force-pushed the eddyb:reify branch 6 times, most recently from 12fa405 to dd04d6a Feb 17, 2016

@bors

This comment has been minimized.

Copy link
Contributor

bors commented Feb 18, 2016

☔️ The latest upstream changes (presumably #31394) made this pull request unmergeable. Please resolve the merge conflicts.

@eddyb eddyb force-pushed the eddyb:reify branch 4 times, most recently from e9fe0fc to 8d806be Feb 18, 2016

@eddyb

This comment has been minimized.

Copy link
Member Author

eddyb commented Feb 23, 2016

The regression should be handled by the unify-coerce logic, however I wish I could enable the full extent of it without triggering LLVM asserts 😞.

@eddyb

This comment has been minimized.

Copy link
Member Author

eddyb commented Feb 23, 2016

Turns out that using fcx.resolve_type_vars_if_possible(...) on both types in coercion::try_unify solves the problem.
That is worrying, because demand::coerce does this and it appears to be the reason normalizations happen at all.

The problem in question starts with &[1, 2, 3][..] being lowered to Index::index(&[1, 2, 3] as &[i32], ..: $Idx): &$Output with an obligation of <&[i32] as Index<$Idx>>::Output == $Output.
Immediately afterwards, $Idx is unified to RangeFull. This is unchanged by my PR.
This specific sequence of events means $Output is an inference variable and not a projection or [i32].

My code was mistakenly assigning Vec<i32> to $Output.
That should error. Instead, the obligation gets dropped or otherwise ignored.

What usually happens is demand::coerce (called from anywhere in typeck) calls resolve_type_vars_if_possible which resolves $Output and nothing has time to assign $Output something incorrect.

I've come up with this testcase, which should trigger the same sequence of events, but does not:

fn main() {
    let mut x = unsafe {std::mem::zeroed()};
    let mut y = &[1, 2, 3][x];
    y = &vec![];
    x = ..;
}

It errors with "type mismatch resolving <[_] as Index<RangeFull>>::Output == Vec<_>" before and after this PR.

cc @rust-lang/compiler Even though my PR appears to work now, I dread leaving an obligation-dropping bug around.

@eddyb eddyb force-pushed the eddyb:reify branch from 8d806be to 33ad2f7 Feb 23, 2016

@eddyb

This comment has been minimized.

Copy link
Member Author

eddyb commented Feb 23, 2016

Found the culprit: it's this resolve_type_vars_if_possible call, which ironically I added a long time ago (although I'm not sure how the fulfillment context worked at that point, if it even existed).

        let resolved_t = match unresolved_type_action {
            UnresolvedTypeAction::Error => {
                structurally_resolved_type(fcx, sp, t)
            }
            UnresolvedTypeAction::Ignore => {
                // We can continue even when the type cannot be resolved
                // (i.e. it is an inference variable) because `Ty::builtin_deref`
                // and `try_overloaded_deref` both simply return `None`
                // in such a case without producing spurious errors.
                fcx.resolve_type_vars_if_possible(t)
            }
        };

UnresolvedTypeAction::Ignore is only used by the coercion code, which is incidentally (AFAICT) the sole caller of autoderef from within a transaction (commit_if_ok).

fcx.resolve_type_vars_if_possible, unlike its infcx counterpart, also tries to select obligations.
Obligations selected inside a transaction have their type bindings lost if the transaction is rolled back.

Usually that's not an issue because a failed coercion is fatal, so at worst you get an ICE after a legitimate error.

But I had code which tried coercions and continued in a different manner, resulting in projection obligations successfully selected but then their effects completely rolled back and thus &[1, 2, 3][..]: &Vec<i32> managed to sneak in undetected.

The fix is very simple: replace that line with fcx.infcx().resolve_type_vars_if_possible(&t).

However, I would like a permanent solution, that doesn't allow access to fulfillment_cx unless there is no transaction going on. Sticking fulfillment_cx in infcx might make this cleaner, but atm there's no easy/efficient way of determining whether you're already in a transaction.

@eddyb eddyb force-pushed the eddyb:reify branch 2 times, most recently from 307dabf to 4d44144 Feb 23, 2016

@eddyb

This comment has been minimized.

Copy link
Member Author

eddyb commented Feb 23, 2016

@nikomatsakis Tests appear to pass, can I get another crater run? If we see any regressions caused by the scheme I chose, we'll have to tone it down, perhaps limit it to two function item types (although I really like this scheme, especially now that I've plucked the bugs out and it seems to "just work").

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Feb 23, 2016

OK, I've reviewed as far as ea2510c (though I'm still reading through d619ebe, which seems to be the "meaty" commit) -- it all seems quite nice thus far.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Feb 25, 2016

@eddyb the crater run seems to have results now, maybe it just wasn't done before:

https://gist.github.com/nikomatsakis/54fbfe9a0d09b8f51cc6

  • 4148 crates tested: 2469 working / 1389 broken / 235 regressed / 1 fixed / 54 unknown.
  • There are 27 root regressions
  • There are 235 regressions
@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Mar 1, 2016

@eddyb so we were talking about this in the @rust-lang/lang meeting -- if we want to abide by our usual policies, we may want to think about a compatibility mode for transmute where we permit transmutes from a (zero-sized) fn to a ptr but issue a "forward compatibility" lint warning about phasing this change out.

I wish there was something more ergonomic than transmute(foo as *mut _) in any case, which seems rather awkward.

@eddyb eddyb force-pushed the eddyb:reify branch from 01f814a to 93b45e4 Mar 9, 2016

@eddyb eddyb force-pushed the eddyb:reify branch from 93b45e4 to 3855fa9 Mar 9, 2016

@eddyb

This comment has been minimized.

Copy link
Member Author

eddyb commented Mar 9, 2016

@bors r=nikomatsakis

@bors

This comment has been minimized.

Copy link
Contributor

bors commented Mar 9, 2016

📌 Commit 3855fa9 has been approved by nikomatsakis

@bors

This comment has been minimized.

Copy link
Contributor

bors commented Mar 9, 2016

⌛️ Testing commit 3855fa9 with merge 3dc9398...

bors added a commit that referenced this pull request Mar 9, 2016

Auto merge of #31710 - eddyb:reify, r=nikomatsakis
Distinguish fn item types to allow reification from nothing to fn pointers.

The first commit is a rebase of #26284, except for files that have moved since.

This is a [breaking-change], due to:
* each FFI function has a distinct type, like all other functions currently do
* all generic parameters on functions are recorded in their item types, e.g.:
`size_of::<u8>` & `size_of::<i8>`'s types differ despite their identical signature.
* function items are zero-sized, which will stop transmutes from working on them

The first two cases are handled in most cases with the new coerce-unify logic,
which will combine incompatible function item types into function pointers,
at the outer-most level of if-else chains, match arms and array literals.

The last case is specially handled during type-checking such that transmutes
from a function item type to a pointer or integer type will continue to work for
another release cycle, but are being linted against. To get rid of warnings and
ensure your code will continue to compile, cast to a pointer before transmuting.
@bors

This comment has been minimized.

Copy link
Contributor

bors commented Mar 10, 2016

⛄️ The build was interrupted to prioritize another pull request.

@bors

This comment has been minimized.

Copy link
Contributor

bors commented Mar 10, 2016

⌛️ Testing commit 3855fa9 with merge bcda58f...

bors added a commit that referenced this pull request Mar 10, 2016

Auto merge of #31710 - eddyb:reify, r=nikomatsakis
Distinguish fn item types to allow reification from nothing to fn pointers.

The first commit is a rebase of #26284, except for files that have moved since.

This is a [breaking-change], due to:
* each FFI function has a distinct type, like all other functions currently do
* all generic parameters on functions are recorded in their item types, e.g.:
`size_of::<u8>` & `size_of::<i8>`'s types differ despite their identical signature.
* function items are zero-sized, which will stop transmutes from working on them

The first two cases are handled in most cases with the new coerce-unify logic,
which will combine incompatible function item types into function pointers,
at the outer-most level of if-else chains, match arms and array literals.

The last case is specially handled during type-checking such that transmutes
from a function item type to a pointer or integer type will continue to work for
another release cycle, but are being linted against. To get rid of warnings and
ensure your code will continue to compile, cast to a pointer before transmuting.

@bors bors merged commit 3855fa9 into rust-lang:master Mar 10, 2016

2 checks passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
homu Test successful
Details

@eddyb eddyb deleted the eddyb:reify branch Mar 10, 2016

@mcarton mcarton referenced this pull request Mar 10, 2016

Merged

Rustup to 2016-03-11 #754

SSheldon added a commit to SSheldon/rust-objc that referenced this pull request Mar 20, 2016

Remove old dispatch prototypes to avoid a transmute.
This fixes the warning appearing from rust-lang/rust#31710.

The objc_msgSend functions aren't intended to be used directly, so since
OSX 10.8 they are defined empty like this as long as you don't define
OBJC_OLD_DISPATCH_PROTOTYPES.

SSheldon added a commit to SSheldon/rust-objc-exception that referenced this pull request Mar 20, 2016

@SSheldon

This comment has been minimized.

Copy link
Contributor

SSheldon commented Jun 25, 2016

FYI, special casing the transmute from functions did not prevent all regressions. I had some code that started segfaulting in 1.9.0, fixed by SSheldon/rust-objc-foundation@f918819. Basically, it boils down to looking like this:

#[link(name = "objc", kind = "dylib")]
extern {
    fn objc_msgSend();
}

unsafe fn msg_send<T>(t: T) {
    let f: unsafe extern fn() = objc_msgSend;
    let f: unsafe extern fn(T) = ::std::mem::transmute(f);
    f(t)
}

fn hello() {
    println!("hello");
}

fn main() {
    unsafe {
        msg_send(hello);
    }
}

That's a case where what used to be a function pointer silently becomes a 0-sized type.

Anyways, fixed now, hopefully no one else is writing code like this! But figured I'd make it known in case anyone else runs into something similar.

@eddyb

This comment has been minimized.

Copy link
Member Author

eddyb commented Jun 25, 2016

@SSheldon Interesting example. While there is transmute in there, it has to do with passing generic arguments to C functions. A similar situation arose with C variadics in FFI, see #32201.
You could also notice this problem if you were doing (*(&rust_function as *const fn()))().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.