diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs
index 5e8dab0f772..5bda6daa69b 100644
--- a/src/librustc/driver/driver.rs
+++ b/src/librustc/driver/driver.rs
@@ -225,6 +225,9 @@ pub fn compile_rest(sess: Session,
         time(time_passes, ~"resolution", ||
              middle::resolve::resolve_crate(sess, lang_items, crate));
 
+    time(time_passes, ~"looking for entry point",
+         || middle::entry::find_entry_point(sess, crate, ast_map));
+
     let freevars = time(time_passes, ~"freevar finding", ||
         freevars::annotate_freevars(def_map, crate));
 
diff --git a/src/librustc/middle/entry.rs b/src/librustc/middle/entry.rs
new file mode 100644
index 00000000000..9ffd0e6f22c
--- /dev/null
+++ b/src/librustc/middle/entry.rs
@@ -0,0 +1,150 @@
+// Copyright 2012 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 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+use driver::session;
+use driver::session::Session;
+use syntax::parse::token::special_idents;
+use syntax::ast::{crate, node_id, item, item_fn};
+use syntax::codemap::span;
+use syntax::visit::{default_visitor, mk_vt, vt, Visitor, visit_crate, visit_item};
+use syntax::attr::{attrs_contains_name};
+use syntax::ast_map;
+use core::util;
+
+struct EntryContext {
+    session: Session,
+
+    ast_map: ast_map::map,
+
+    // The top-level function called 'main'
+    main_fn: Option<(node_id, span)>,
+
+    // The function that has attribute named 'main'
+    attr_main_fn: Option<(node_id, span)>,
+
+    // The function that has the attribute 'start' on it
+    start_fn: Option<(node_id, span)>,
+
+    // The functions that one might think are 'main' but aren't, e.g.
+    // main functions not defined at the top level. For diagnostics.
+    non_main_fns: ~[(node_id, span)],
+}
+
+type EntryVisitor = vt<@mut EntryContext>;
+
+pub fn find_entry_point(session: Session, crate: @crate, ast_map: ast_map::map) {
+
+    // FIXME #4404 android JNI hacks
+    if *session.building_library &&
+        session.targ_cfg.os != session::os_android {
+        // No need to find a main function
+        return;
+    }
+
+    let ctxt = @mut EntryContext {
+        session: session,
+        ast_map: ast_map,
+        main_fn: None,
+        attr_main_fn: None,
+        start_fn: None,
+        non_main_fns: ~[],
+    };
+
+    visit_crate(crate, ctxt, mk_vt(@Visitor {
+        visit_item: |item, ctxt, visitor| find_item(item, ctxt, visitor),
+        .. *default_visitor()
+    }));
+
+    configure_main(ctxt);
+}
+
+fn find_item(item: @item, ctxt: @mut EntryContext, visitor: EntryVisitor) {
+    match item.node {
+        item_fn(*) => {
+            if item.ident == special_idents::main {
+                match ctxt.ast_map.find(&item.id) {
+                    Some(&ast_map::node_item(_, path)) => {
+                        if path.len() == 0 {
+                            // This is a top-level function so can be 'main'
+                            if ctxt.main_fn.is_none() {
+                                ctxt.main_fn = Some((item.id, item.span));
+                            } else {
+                                ctxt.session.span_err(
+                                    item.span,
+                                    ~"multiple 'main' functions");
+                            }
+                        } else {
+                            // This isn't main
+                            ctxt.non_main_fns.push((item.id, item.span));
+                        }
+                    }
+                    _ => util::unreachable()
+                }
+            }
+
+            if attrs_contains_name(item.attrs, ~"main") {
+                if ctxt.attr_main_fn.is_none() {
+                    ctxt.attr_main_fn = Some((item.id, item.span));
+                } else {
+                    ctxt.session.span_err(
+                        item.span,
+                        ~"multiple 'main' functions");
+                }
+            }
+
+            if attrs_contains_name(item.attrs, ~"start") {
+                if ctxt.start_fn.is_none() {
+                    ctxt.start_fn = Some((item.id, item.span));
+                } else {
+                    ctxt.session.span_err(
+                        item.span,
+                        ~"multiple 'start' functions");
+                }
+            }
+        }
+        _ => ()
+    }
+
+    visit_item(item, ctxt, visitor);
+}
+
+fn configure_main(ctxt: @mut EntryContext) {
+    let this = &mut *ctxt;
+    if this.start_fn.is_some() {
+        *this.session.entry_fn = this.start_fn;
+        *this.session.entry_type = Some(session::EntryStart);
+    } else if this.attr_main_fn.is_some() {
+        *this.session.entry_fn = this.attr_main_fn;
+        *this.session.entry_type = Some(session::EntryMain);
+    } else if this.main_fn.is_some() {
+        *this.session.entry_fn = this.main_fn;
+        *this.session.entry_type = Some(session::EntryMain);
+    } else {
+        if !*this.session.building_library {
+            // No main function
+            this.session.err(~"main function not found");
+            if !this.non_main_fns.is_empty() {
+                // There were some functions named 'main' though. Try to give the user a hint.
+                this.session.note(~"the main function must be defined at the crate level \
+                                    but you have one or more functions named 'main' that are not \
+                                    defined at the crate level. Either move the definition or \
+                                    attach the `#[main]` attribute to override this behavior.");
+                for this.non_main_fns.each |&(_, span)| {
+                    this.session.span_note(span, ~"here is a function named 'main'");
+                }
+            }
+            this.session.abort_if_errors();
+        } else {
+            // If we *are* building a library, then we're on android where we still might
+            // optionally want to translate main $4404
+            assert!(this.session.targ_cfg.os == session::os_android);
+        }
+    }
+}
diff --git a/src/librustc/middle/resolve.rs b/src/librustc/middle/resolve.rs
index 946bf26fd27..cabe0353b8a 100644
--- a/src/librustc/middle/resolve.rs
+++ b/src/librustc/middle/resolve.rs
@@ -8,7 +8,6 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-use driver::session;
 use driver::session::Session;
 use metadata::csearch::{each_path, get_trait_method_def_ids};
 use metadata::csearch::get_method_name_and_self_ty;
@@ -794,11 +793,6 @@ pub fn Resolver(session: Session,
 
         namespaces: ~[ TypeNS, ValueNS ],
 
-        attr_main_fn: None,
-        main_fns: ~[],
-
-        start_fn: None,
-
         def_map: @mut HashMap::new(),
         export_map2: @mut HashMap::new(),
         trait_map: HashMap::new(),
@@ -856,15 +850,6 @@ pub struct Resolver {
     // The four namespaces.
     namespaces: ~[Namespace],
 
-    // The function that has attribute named 'main'
-    attr_main_fn: Option<(node_id, span)>,
-
-    // The functions that could be main functions
-    main_fns: ~[Option<(node_id, span)>],
-
-    // The function that has the attribute 'start' on it
-    start_fn: Option<(node_id, span)>,
-
     def_map: DefMap,
     export_map2: ExportMap2,
     trait_map: TraitMap,
@@ -885,7 +870,6 @@ pub impl Resolver {
         self.resolve_crate();
         self.session.abort_if_errors();
 
-        self.check_duplicate_main();
         self.check_for_unused_imports_if_necessary();
     }
 
@@ -3544,40 +3528,6 @@ pub impl Resolver {
             }
 
             item_fn(ref fn_decl, _, _, ref generics, ref block) => {
-                // If this is the main function, we must record it in the
-                // session.
-
-                // FIXME #4404 android JNI hacks
-                if !*self.session.building_library ||
-                    self.session.targ_cfg.os == session::os_android {
-
-                    if self.attr_main_fn.is_none() &&
-                           item.ident == special_idents::main {
-
-                        self.main_fns.push(Some((item.id, item.span)));
-                    }
-
-                    if attrs_contains_name(item.attrs, ~"main") {
-                        if self.attr_main_fn.is_none() {
-                            self.attr_main_fn = Some((item.id, item.span));
-                        } else {
-                            self.session.span_err(
-                                    item.span,
-                                    ~"multiple 'main' functions");
-                        }
-                    }
-
-                    if attrs_contains_name(item.attrs, ~"start") {
-                        if self.start_fn.is_none() {
-                            self.start_fn = Some((item.id, item.span));
-                        } else {
-                            self.session.span_err(
-                                    item.span,
-                                    ~"multiple 'start' functions");
-                        }
-                    }
-                }
-
                 self.resolve_function(OpaqueFunctionRibKind,
                                       Some(fn_decl),
                                       HasTypeParameters
@@ -5089,35 +5039,6 @@ pub impl Resolver {
         }
     }
 
-    //
-    // main function checking
-    //
-    // be sure that there is only one main function
-    //
-    fn check_duplicate_main(@mut self) {
-        let this = &mut *self;
-        if this.attr_main_fn.is_none() && this.start_fn.is_none() {
-            if this.main_fns.len() >= 1u {
-                let mut i = 1u;
-                while i < this.main_fns.len() {
-                    let (_, dup_main_span) = this.main_fns[i].unwrap();
-                    this.session.span_err(
-                        dup_main_span,
-                        ~"multiple 'main' functions");
-                    i += 1;
-                }
-                *this.session.entry_fn = this.main_fns[0];
-                *this.session.entry_type = Some(session::EntryMain);
-            }
-        } else if !this.start_fn.is_none() {
-            *this.session.entry_fn = this.start_fn;
-            *this.session.entry_type = Some(session::EntryStart);
-        } else {
-            *this.session.entry_fn = this.attr_main_fn;
-            *this.session.entry_type = Some(session::EntryMain);
-        }
-    }
-
     //
     // Unused import checking
     //
diff --git a/src/librustc/middle/typeck/mod.rs b/src/librustc/middle/typeck/mod.rs
index 1a152f3c291..5da14d99171 100644
--- a/src/librustc/middle/typeck/mod.rs
+++ b/src/librustc/middle/typeck/mod.rs
@@ -393,7 +393,7 @@ fn check_for_entry_fn(ccx: @mut CrateCtxt) {
               Some(session::EntryStart) => check_start_fn_ty(ccx, id, sp),
               None => tcx.sess.bug(~"entry function without a type")
           },
-          None => tcx.sess.err(~"entry function not found")
+          None => tcx.sess.bug(~"type checking without entry function")
         }
     }
 }
diff --git a/src/librustc/rustc.rc b/src/librustc/rustc.rc
index f8a19eaf374..f69a38c96dc 100644
--- a/src/librustc/rustc.rc
+++ b/src/librustc/rustc.rc
@@ -100,6 +100,7 @@ pub mod middle {
     pub mod lang_items;
     pub mod privacy;
     pub mod moves;
+    pub mod entry;
 }
 
 pub mod front {
diff --git a/src/test/compile-fail/elided-test.rs b/src/test/compile-fail/elided-test.rs
index eaae721e0e5..b62214b12f9 100644
--- a/src/test/compile-fail/elided-test.rs
+++ b/src/test/compile-fail/elided-test.rs
@@ -8,7 +8,7 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-// error-pattern: entry function not found
+// error-pattern: main function not found
 
 // Since we're not compiling a test runner this function should be elided
 // and the build will fail because main doesn't exist
diff --git a/src/test/compile-fail/issue-2995.rs b/src/test/compile-fail/issue-2995.rs
index 5c48416667f..3e771eef970 100644
--- a/src/test/compile-fail/issue-2995.rs
+++ b/src/test/compile-fail/issue-2995.rs
@@ -11,3 +11,5 @@
 fn bad (p: *int) {
     let _q: &int = p as &int; //~ ERROR non-scalar cast
 }
+
+fn main() { }
\ No newline at end of file
diff --git a/src/test/compile-fail/multiple-main.rs b/src/test/compile-fail/main-wrong-location.rs
similarity index 72%
rename from src/test/compile-fail/multiple-main.rs
rename to src/test/compile-fail/main-wrong-location.rs
index ef8cd58abf9..90ef7843d4b 100644
--- a/src/test/compile-fail/multiple-main.rs
+++ b/src/test/compile-fail/main-wrong-location.rs
@@ -8,10 +8,8 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-fn main() {
-}
-
-mod foo {
-    fn main() { //~ ERROR multiple 'main' functions
-    }
-}
+mod m {
+    // An inferred main entry point (that doesn't use #[main])
+    // must appear at the top of the crate
+    fn main() { } //~ NOTE here is a function named 'main'
+}
\ No newline at end of file
diff --git a/src/test/compile-fail/missing-main.rs b/src/test/compile-fail/missing-main.rs
index 4f1b604b507..4bfdaf69480 100644
--- a/src/test/compile-fail/missing-main.rs
+++ b/src/test/compile-fail/missing-main.rs
@@ -8,5 +8,5 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
-// error-pattern:entry function not found
+// error-pattern:main function not found
 fn mian() { }
diff --git a/src/test/compile-fail/tag-variant-disr-dup.rs b/src/test/compile-fail/tag-variant-disr-dup.rs
index be53b6a0ba3..216779fac7c 100644
--- a/src/test/compile-fail/tag-variant-disr-dup.rs
+++ b/src/test/compile-fail/tag-variant-disr-dup.rs
@@ -19,3 +19,5 @@ enum color {
     black = 0x000000,
     white = 0x000000,
 }
+
+fn main() { }
\ No newline at end of file
diff --git a/src/test/run-pass/dupe-first-attr.rc b/src/test/run-pass/dupe-first-attr.rc
index d39a2aa4476..9bd63a8d646 100644
--- a/src/test/run-pass/dupe-first-attr.rc
+++ b/src/test/run-pass/dupe-first-attr.rc
@@ -25,3 +25,5 @@ mod hello;
 
 #[cfg(target_os = "android")]
 mod hello;
+
+fn main() { }
\ No newline at end of file
diff --git a/src/test/run-pass/intrinsic-alignment.rs b/src/test/run-pass/intrinsic-alignment.rs
index adc085d2108..cce3d8066ec 100644
--- a/src/test/run-pass/intrinsic-alignment.rs
+++ b/src/test/run-pass/intrinsic-alignment.rs
@@ -22,6 +22,7 @@ mod rusti {
 #[cfg(target_os = "macos")]
 #[cfg(target_os = "freebsd")]
 mod m {
+    #[main]
     #[cfg(target_arch = "x86")]
     pub fn main() {
         unsafe {
@@ -30,6 +31,7 @@ mod m {
         }
     }
 
+    #[main]
     #[cfg(target_arch = "x86_64")]
     pub fn main() {
         unsafe {
@@ -41,6 +43,7 @@ mod m {
 
 #[cfg(target_os = "win32")]
 mod m {
+    #[main]
     #[cfg(target_arch = "x86")]
     pub fn main() {
         unsafe {
@@ -52,6 +55,7 @@ mod m {
 
 #[cfg(target_os = "android")]
 mod m {
+    #[main]
     #[cfg(target_arch = "arm")]
     pub fn main() {
         unsafe {