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

eddyb
Copy link
Member

@eddyb 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.

@eddyb eddyb added the S-waiting-on-crater Status: Waiting on a crater run to be completed. label Feb 16, 2016
@rust-highfive
Copy link
Collaborator

r? @arielb1

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

@nikomatsakis
Copy link
Contributor

r? @nikomatsakis

@nikomatsakis
Copy link
Contributor

Triggering a crater run.

@nikomatsakis
Copy link
Contributor

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

One regression (fann).

@eddyb eddyb force-pushed the reify branch 6 times, most recently from 12fa405 to dd04d6a Compare February 18, 2016 06:30
@bors
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 reify branch 4 times, most recently from e9fe0fc to 8d806be Compare February 23, 2016 00:11
@eddyb
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
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
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 reify branch 2 times, most recently from 307dabf to 4d44144 Compare February 23, 2016 15:56
@eddyb
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
Copy link
Contributor

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

@nikomatsakis
Copy link
Contributor

@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
Copy link
Contributor

@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
Copy link
Member Author

eddyb commented Mar 9, 2016

@bors r=nikomatsakis

@bors
Copy link
Contributor

bors commented Mar 9, 2016

📌 Commit 3855fa9 has been approved by nikomatsakis

@arielb1 arielb1 added the relnotes Marks issues that should be documented in the release notes of the next release. label Mar 9, 2016
@bors
Copy link
Contributor

bors commented Mar 9, 2016

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

@bors
Copy link
Contributor

bors commented Mar 10, 2016

⛄ The build was interrupted to prioritize another pull request.

@bors
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
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
@eddyb eddyb deleted the reify branch March 10, 2016 09:33
@nikomatsakis nikomatsakis removed the S-waiting-on-crater Status: Waiting on a crater run to be completed. label Mar 10, 2016
SSheldon added a commit to SSheldon/rust-objc that referenced this pull request Mar 20, 2016
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
Copy link
Contributor

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
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
Labels
relnotes Marks issues that should be documented in the release notes of the next release.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Set transmute from fn item to type fn lint a hard error
8 participants