From d7918fb2e889c5ccb58ab8b97d2581cc763f2306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sun, 3 Dec 2017 22:16:24 +0100 Subject: [PATCH] Implements RFC 1937: `?` in `main` This is the first part of the RFC 1937 that supports new `Termination` trait in the rust `main` function. --- src/librustc/middle/lang_items.rs | 2 + src/librustc_mir/monomorphize/collector.rs | 84 ++++++++++++++----- src/librustc_mir/monomorphize/partitioning.rs | 11 ++- src/librustc_trans/base.rs | 34 ++++++-- src/librustc_typeck/check/mod.rs | 24 +++++- src/librustc_typeck/lib.rs | 16 ---- src/libstd/lib.rs | 6 ++ src/libstd/rt.rs | 45 +++++++++- src/libstd/termination.rs | 74 ++++++++++++++++ .../termination-trait-not-satisfied.rs | 15 ++++ .../run-pass/termination-trait-for-empty.rs | 13 +++ .../run-pass/termination-trait-for-i32.rs | 15 ++++ .../run-pass/termination-trait-for-result.rs | 17 ++++ 13 files changed, 304 insertions(+), 52 deletions(-) create mode 100644 src/libstd/termination.rs create mode 100644 src/test/compile-fail/termination-trait-not-satisfied.rs create mode 100644 src/test/run-pass/termination-trait-for-empty.rs create mode 100644 src/test/run-pass/termination-trait-for-i32.rs create mode 100644 src/test/run-pass/termination-trait-for-result.rs diff --git a/src/librustc/middle/lang_items.rs b/src/librustc/middle/lang_items.rs index f8933d06360..dca676130b9 100644 --- a/src/librustc/middle/lang_items.rs +++ b/src/librustc/middle/lang_items.rs @@ -338,6 +338,8 @@ language_item_table! { U128ShloFnLangItem, "u128_shlo", u128_shlo_fn; I128ShroFnLangItem, "i128_shro", i128_shro_fn; U128ShroFnLangItem, "u128_shro", u128_shro_fn; + + TerminationTraitLangItem, "termination", termination; } impl<'a, 'tcx, 'gcx> TyCtxt<'a, 'tcx, 'gcx> { diff --git a/src/librustc_mir/monomorphize/collector.rs b/src/librustc_mir/monomorphize/collector.rs index 0b8666800a5..056c6989aba 100644 --- a/src/librustc_mir/monomorphize/collector.rs +++ b/src/librustc_mir/monomorphize/collector.rs @@ -194,11 +194,13 @@ use rustc::hir::itemlikevisit::ItemLikeVisitor; use rustc::hir::map as hir_map; use rustc::hir::def_id::DefId; use rustc::middle::const_val::ConstVal; -use rustc::middle::lang_items::{ExchangeMallocFnLangItem}; +use rustc::middle::lang_items::{ExchangeMallocFnLangItem,StartFnLangItem}; +use rustc::middle::trans::TransItem; use rustc::traits; -use rustc::ty::subst::Substs; +use rustc::ty::subst::{Substs, Kind}; use rustc::ty::{self, TypeFoldable, Ty, TyCtxt}; use rustc::ty::adjustment::CustomCoerceUnsized; +use rustc::session::config; use rustc::mir::{self, Location}; use rustc::mir::visit::Visitor as MirVisitor; use rustc::mir::mono::MonoItem; @@ -212,6 +214,8 @@ use rustc_data_structures::bitvec::BitVector; use syntax::attr; +use std::iter; + #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] pub enum MonoItemCollectionMode { Eager, @@ -329,6 +333,8 @@ fn collect_roots<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, tcx.hir.local_def_id(node_id) }); + debug!("collect_roots: entry_fn = {:?}", entry_fn); + let mut visitor = RootCollector { tcx, mode, @@ -951,16 +957,8 @@ impl<'b, 'a, 'v> ItemLikeVisitor<'v> for RootCollector<'b, 'a, 'v> { // actually used somewhere. Just declaring them is insufficient. } hir::ItemFn(..) => { - let tcx = self.tcx; - let def_id = tcx.hir.local_def_id(item.id); - - if self.is_root(def_id) { - debug!("RootCollector: ItemFn({})", - def_id_to_string(tcx, def_id)); - - let instance = Instance::mono(tcx, def_id); - self.output.push(MonoItem::Fn(instance)); - } + let def_id = self.tcx.hir.local_def_id(item.id); + self.push_if_root(def_id); } } } @@ -973,16 +971,8 @@ impl<'b, 'a, 'v> ItemLikeVisitor<'v> for RootCollector<'b, 'a, 'v> { fn visit_impl_item(&mut self, ii: &'v hir::ImplItem) { match ii.node { hir::ImplItemKind::Method(hir::MethodSig { .. }, _) => { - let tcx = self.tcx; - let def_id = tcx.hir.local_def_id(ii.id); - - if self.is_root(def_id) { - debug!("RootCollector: MethodImplItem({})", - def_id_to_string(tcx, def_id)); - - let instance = Instance::mono(tcx, def_id); - self.output.push(MonoItem::Fn(instance)); - } + let def_id = self.tcx.hir.local_def_id(ii.id); + self.push_if_root(def_id); } _ => { /* Nothing to do here */ } } @@ -1003,6 +993,56 @@ impl<'b, 'a, 'v> RootCollector<'b, 'a, 'v> { } } } + + /// If `def_id` represents a root, then push it onto the list of + /// outputs. (Note that all roots must be monomorphic.) + fn push_if_root(&mut self, def_id: DefId) { + if self.is_root(def_id) { + debug!("RootCollector::push_if_root: found root def_id={:?}", def_id); + + let instance = Instance::mono(self.tcx, def_id); + self.output.push(create_fn_trans_item(instance)); + + self.push_extra_entry_roots(def_id); + } + } + + /// As a special case, when/if we encounter the + /// `main()` function, we also have to generate a + /// monomorphized copy of the start lang item based on + /// the return type of `main`. This is not needed when + /// the user writes their own `start` manually. + fn push_extra_entry_roots(&mut self, def_id: DefId) { + if self.entry_fn != Some(def_id) { + return; + } + + if self.tcx.sess.entry_type.get() != Some(config::EntryMain) { + return; + } + + let start_def_id = match self.tcx.lang_items().require(StartFnLangItem) { + Ok(s) => s, + Err(err) => self.tcx.sess.fatal(&err), + }; + let main_ret_ty = self.tcx.fn_sig(def_id).output(); + + // Given that `main()` has no arguments, + // then its return type cannot have + // late-bound regions, since late-bound + // regions must appear in the argument + // listing. + let main_ret_ty = self.tcx.no_late_bound_regions(&main_ret_ty).unwrap(); + + let start_instance = Instance::resolve( + self.tcx, + ty::ParamEnv::empty(traits::Reveal::All), + start_def_id, + self.tcx.mk_substs(iter::once(Kind::from(main_ret_ty))) + ).unwrap(); + + self.output.push(create_fn_trans_item(start_instance)); + } } fn item_has_type_parameters<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, def_id: DefId) -> bool { diff --git a/src/librustc_mir/monomorphize/partitioning.rs b/src/librustc_mir/monomorphize/partitioning.rs index b1fef274cba..e34698da4da 100644 --- a/src/librustc_mir/monomorphize/partitioning.rs +++ b/src/librustc_mir/monomorphize/partitioning.rs @@ -106,7 +106,8 @@ use monomorphize::collector::InliningMap; use rustc::dep_graph::WorkProductId; use rustc::hir::def_id::DefId; use rustc::hir::map::DefPathData; -use rustc::mir::mono::{Linkage, Visibility}; +use rustc::middle::lang_items::StartFnLangItem; +use rustc::middle::trans::{Linkage, Visibility}; use rustc::ty::{self, TyCtxt, InstanceDef}; use rustc::ty::item_path::characteristic_def_id_of_type; use rustc::util::nodemap::{FxHashMap, FxHashSet}; @@ -312,7 +313,13 @@ fn place_root_translation_items<'a, 'tcx, I>(tcx: TyCtxt<'a, 'tcx, 'tcx>, MonoItem::Fn(ref instance) => { let visibility = match instance.def { InstanceDef::Item(def_id) => { - if def_id.is_local() { + let start_def_id = tcx.lang_items().require(StartFnLangItem); + + // If we encounter the lang start item, we set the visibility to + // default. + if start_def_id == Ok(def_id) { + Visibility::Default + } else if def_id.is_local() { if tcx.is_exported_symbol(def_id) { Visibility::Default } else { diff --git a/src/librustc_trans/base.rs b/src/librustc_trans/base.rs index 0efe5f9a5cb..b2d516e7868 100644 --- a/src/librustc_trans/base.rs +++ b/src/librustc_trans/base.rs @@ -44,6 +44,7 @@ use rustc::ty::{self, Ty, TyCtxt}; use rustc::ty::layout::{self, Align, TyLayout, LayoutOf}; use rustc::ty::maps::Providers; use rustc::dep_graph::{DepNode, DepConstructor}; +use rustc::ty::subst::Kind; use rustc::middle::cstore::{self, LinkMeta, LinkagePreference}; use rustc::util::common::{time, print_time_passes_entry}; use rustc::session::config::{self, NoDebugInfo}; @@ -79,6 +80,7 @@ use std::str; use std::sync::Arc; use std::time::{Instant, Duration}; use std::i32; +use std::iter; use std::sync::mpsc; use syntax_pos::Span; use syntax_pos::symbol::InternedString; @@ -540,17 +542,28 @@ fn maybe_create_entry_wrapper(ccx: &CrateContext) { let et = ccx.sess().entry_type.get().unwrap(); match et { - config::EntryMain => create_entry_fn(ccx, span, main_llfn, true), - config::EntryStart => create_entry_fn(ccx, span, main_llfn, false), + config::EntryMain => create_entry_fn(ccx, span, main_llfn, main_def_id, true), + config::EntryStart => create_entry_fn(ccx, span, main_llfn, main_def_id, false), config::EntryNone => {} // Do nothing. } - fn create_entry_fn(ccx: &CrateContext, + fn create_entry_fn<'ccx>(ccx: &'ccx CrateContext, sp: Span, rust_main: ValueRef, + rust_main_def_id: DefId, use_start_lang_item: bool) { - // Signature of native main(), corresponding to C's `int main(int, char **)` - let llfty = Type::func(&[Type::c_int(ccx), Type::i8p(ccx).ptr_to()], &Type::c_int(ccx)); + // The libstd lang_start function does not return anything, while user defined lang start + // returns a isize + let start_output_ty = if use_start_lang_item { Type::void(ccx) } else { Type::c_int(ccx) }; + let llfty = Type::func(&[Type::c_int(ccx), Type::i8p(ccx).ptr_to()], &start_output_ty); + + let main_ret_ty = ccx.tcx().fn_sig(rust_main_def_id).output(); + // Given that `main()` has no arguments, + // then its return type cannot have + // late-bound regions, since late-bound + // regions must appear in the argument + // listing. + let main_ret_ty = ccx.tcx().no_late_bound_regions(&main_ret_ty).unwrap(); if declare::get_defined_value(ccx, "main").is_some() { // FIXME: We should be smart and show a better diagnostic here. @@ -577,8 +590,8 @@ fn maybe_create_entry_wrapper(ccx: &CrateContext) { let (start_fn, args) = if use_start_lang_item { let start_def_id = ccx.tcx().require_lang_item(StartFnLangItem); - let start_instance = Instance::mono(ccx.tcx(), start_def_id); - let start_fn = callee::get_fn(ccx, start_instance); + let start_fn = callee::resolve_and_get_fn(ccx, start_def_id, ccx.tcx().mk_substs( + iter::once(Kind::from(main_ret_ty)))); (start_fn, vec![bld.pointercast(rust_main, Type::i8p(ccx).ptr_to()), arg_argc, arg_argv]) } else { @@ -588,8 +601,11 @@ fn maybe_create_entry_wrapper(ccx: &CrateContext) { let result = bld.call(start_fn, &args, None); - // Return rust start function's result from native main() - bld.ret(bld.intcast(result, Type::c_int(ccx), true)); + if use_start_lang_item { + bld.ret_void(); + } else { + bld.ret(bld.intcast(result, Type::c_int(ccx), true)); + } } } diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs index ab81d26e771..159340d19a7 100644 --- a/src/librustc_typeck/check/mod.rs +++ b/src/librustc_typeck/check/mod.rs @@ -93,17 +93,18 @@ use rustc::infer::{self, InferCtxt, InferOk, RegionVariableOrigin}; use rustc::infer::anon_types::AnonTypeDecl; use rustc::infer::type_variable::{TypeVariableOrigin}; use rustc::middle::region; +use rustc::middle::lang_items::TerminationTraitLangItem; use rustc::ty::subst::{Kind, Subst, Substs}; use rustc::traits::{self, FulfillmentContext, ObligationCause, ObligationCauseCode}; use rustc::ty::{ParamTy, LvaluePreference, NoPreference, PreferMutLvalue}; -use rustc::ty::{self, Ty, TyCtxt, Visibility}; +use rustc::ty::{self, Ty, TyCtxt, Visibility, ToPredicate}; use rustc::ty::adjustment::{Adjust, Adjustment, AutoBorrow}; use rustc::ty::fold::TypeFoldable; use rustc::ty::maps::Providers; use rustc::ty::util::{Representability, IntTypeExt}; use errors::{DiagnosticBuilder, DiagnosticId}; use require_c_abi_if_variadic; -use session::{CompileIncomplete, Session}; +use session::{CompileIncomplete, config, Session}; use TypeAndSubsts; use lint; use util::common::{ErrorReported, indenter}; @@ -115,6 +116,7 @@ use std::collections::hash_map::Entry; use std::cmp; use std::fmt::Display; use std::mem::replace; +use std::iter; use std::ops::{self, Deref}; use syntax::abi::Abi; use syntax::ast; @@ -1064,6 +1066,24 @@ fn check_fn<'a, 'gcx, 'tcx>(inherited: &'a Inherited<'a, 'gcx, 'tcx>, } fcx.demand_suptype(span, ret_ty, actual_return_ty); + if let Some((id, _)) = *fcx.tcx.sess.entry_fn.borrow() { + if id == fn_id { + match fcx.sess().entry_type.get() { + Some(config::EntryMain) => { + let term_id = fcx.tcx.require_lang_item(TerminationTraitLangItem); + + let substs = fcx.tcx.mk_substs(iter::once(Kind::from(ret_ty))); + let trait_ref = ty::TraitRef::new(term_id, substs); + let cause = traits::ObligationCause::new(span, fn_id, + ObligationCauseCode::MainFunctionType); + inherited.register_predicate( + traits::Obligation::new(cause, param_env, trait_ref.to_predicate())); + }, + _ => {}, + } + } + } + (fcx, gen_ty) } diff --git a/src/librustc_typeck/lib.rs b/src/librustc_typeck/lib.rs index 129511ee64c..47022eb152d 100644 --- a/src/librustc_typeck/lib.rs +++ b/src/librustc_typeck/lib.rs @@ -115,7 +115,6 @@ use syntax::ast; use syntax::abi::Abi; use syntax_pos::Span; -use std::iter; // NB: This module needs to be declared first so diagnostics are // registered before they are used. mod diagnostics; @@ -200,21 +199,6 @@ fn check_main_fn_ty<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, } _ => () } - let se_ty = tcx.mk_fn_ptr(ty::Binder( - tcx.mk_fn_sig( - iter::empty(), - tcx.mk_nil(), - false, - hir::Unsafety::Normal, - Abi::Rust - ) - )); - - require_same_types( - tcx, - &ObligationCause::new(main_span, main_id, ObligationCauseCode::MainFunctionType), - se_ty, - tcx.mk_fn_ptr(tcx.fn_sig(main_def_id))); } _ => { span_bug!(main_span, diff --git a/src/libstd/lib.rs b/src/libstd/lib.rs index 12e6231136e..3a7a57fe2b8 100644 --- a/src/libstd/lib.rs +++ b/src/libstd/lib.rs @@ -308,6 +308,7 @@ #![feature(str_char)] #![feature(str_internals)] #![feature(str_utf16)] +#![feature(termination_trait)] #![feature(test, rustc_private)] #![feature(thread_local)] #![feature(toowned_clone_into)] @@ -499,6 +500,11 @@ mod memchr; // The runtime entry point and a few unstable public functions used by the // compiler pub mod rt; +// The trait to support returning arbitrary types in the main function +mod termination; + +#[unstable(feature = "termination_trait", issue = "0")] +pub use self::termination::Termination; // Include a number of private modules that exist solely to provide // the rustdoc documentation for primitive types. Using `include!` diff --git a/src/libstd/rt.rs b/src/libstd/rt.rs index 40b24cedcdc..2b75201ad2b 100644 --- a/src/libstd/rt.rs +++ b/src/libstd/rt.rs @@ -26,7 +26,50 @@ // Reexport some of our utilities which are expected by other crates. pub use panicking::{begin_panic, begin_panic_fmt, update_panic_count}; -#[cfg(not(test))] +#[cfg(not(any(test, stage0)))] +#[lang = "start"] +fn lang_start + (main: fn() -> T, argc: isize, argv: *const *const u8) -> ! +{ + use panic; + use sys; + use sys_common; + use sys_common::thread_info; + use thread::Thread; + use process; + #[cfg(not(feature = "backtrace"))] + use mem; + + sys::init(); + + process::exit(unsafe { + let main_guard = sys::thread::guard::init(); + sys::stack_overflow::init(); + + // Next, set up the current Thread with the guard information we just + // created. Note that this isn't necessary in general for new threads, + // but we just do this to name the main thread and to give it correct + // info about the stack bounds. + let thread = Thread::new(Some("main".to_owned())); + thread_info::set(main_guard, thread); + + // Store our args if necessary in a squirreled away location + sys::args::init(argc, argv); + + // Let's run some code! + #[cfg(feature = "backtrace")] + let exit_code = panic::catch_unwind(|| { + ::sys_common::backtrace::__rust_begin_short_backtrace(move || main().report()) + }); + #[cfg(not(feature = "backtrace"))] + let exit_code = panic::catch_unwind(mem::transmute::<_, fn()>(main).report()); + + sys_common::cleanup(); + exit_code.unwrap_or(101) + }); +} + +#[cfg(all(not(test), stage0))] #[lang = "start"] fn lang_start(main: fn(), argc: isize, argv: *const *const u8) -> isize { use panic; diff --git a/src/libstd/termination.rs b/src/libstd/termination.rs new file mode 100644 index 00000000000..a08d3dd2d52 --- /dev/null +++ b/src/libstd/termination.rs @@ -0,0 +1,74 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use error::Error; +use libc; + +/// A trait for implementing arbitrary return types in the `main` function. +/// +/// The c-main function only supports to return integers as return type. +/// So, every type implementing the `Termination` trait has to be converted +/// to an integer. +/// +/// The default implementations are returning `libc::EXIT_SUCCESS` to indicate +/// a successful execution. In case of a failure, `libc::EXIT_FAILURE` is returned. +#[cfg_attr(not(stage0), lang = "termination")] +#[unstable(feature = "termination_trait", issue = "0")] +pub trait Termination { + /// Is called to get the representation of the value as status code. + /// This status code is returned to the operating system. + fn report(self) -> i32; +} + +#[unstable(feature = "termination_trait", issue = "0")] +impl Termination for () { + fn report(self) -> i32 { libc::EXIT_SUCCESS } +} + +#[unstable(feature = "termination_trait", issue = "0")] +impl Termination for Result { + fn report(self) -> i32 { + match self { + Ok(val) => val.report(), + Err(err) => { + print_error(err); + libc::EXIT_FAILURE + } + } + } +} + +#[unstable(feature = "termination_trait", issue = "0")] +fn print_error(err: E) { + eprintln!("Error: {}", err.description()); + + if let Some(ref err) = err.cause() { + eprintln!("Caused by: {}", err.description()); + } +} + +#[unstable(feature = "termination_trait", issue = "0")] +impl Termination for ! { + fn report(self) -> i32 { unreachable!(); } +} + +#[unstable(feature = "termination_trait", issue = "0")] +impl Termination for bool { + fn report(self) -> i32 { + if self { libc::EXIT_SUCCESS } else { libc::EXIT_FAILURE } + } +} + +#[unstable(feature = "termination_trait", issue = "0")] +impl Termination for i32 { + fn report(self) -> i32 { + self + } +} diff --git a/src/test/compile-fail/termination-trait-not-satisfied.rs b/src/test/compile-fail/termination-trait-not-satisfied.rs new file mode 100644 index 00000000000..178a9b8cf59 --- /dev/null +++ b/src/test/compile-fail/termination-trait-not-satisfied.rs @@ -0,0 +1,15 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +struct ReturnType {} + +fn main() -> ReturnType { //~ ERROR `ReturnType: std::Termination` is not satisfied + ReturnType {} +} diff --git a/src/test/run-pass/termination-trait-for-empty.rs b/src/test/run-pass/termination-trait-for-empty.rs new file mode 100644 index 00000000000..5e534da0128 --- /dev/null +++ b/src/test/run-pass/termination-trait-for-empty.rs @@ -0,0 +1,13 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(termination_trait)] + +fn main() {} diff --git a/src/test/run-pass/termination-trait-for-i32.rs b/src/test/run-pass/termination-trait-for-i32.rs new file mode 100644 index 00000000000..fa7cb023b44 --- /dev/null +++ b/src/test/run-pass/termination-trait-for-i32.rs @@ -0,0 +1,15 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(termination_trait)] + +fn main() -> i32 { + 0 +} diff --git a/src/test/run-pass/termination-trait-for-result.rs b/src/test/run-pass/termination-trait-for-result.rs new file mode 100644 index 00000000000..751db0fb500 --- /dev/null +++ b/src/test/run-pass/termination-trait-for-result.rs @@ -0,0 +1,17 @@ +// Copyright 2017 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![feature(termination_trait)] + +use std::io::Error; + +fn main() -> Result<(), Error> { + Ok(()) +}