use libc::{c_int, c_uint, c_char};
use driver::session;
use session::session;
use lib::llvm::llvm;
use syntax::attr;
use middle::ty;
use metadata::{encoder, cstore};
use middle::trans::common::crate_ctxt;
use metadata::common::link_meta;
use std::map::HashMap;
use std::sha1::sha1;
use syntax::ast;
use syntax::print::pprust;
use lib::llvm::{ModuleRef, mk_pass_manager, mk_target_data, True, False,
        PassManagerRef, FileType};
use metadata::filesearch;
use syntax::ast_map::{path, path_mod, path_name};
use io::{Writer, WriterUtil};

enum output_type {
    output_type_none,
    output_type_bitcode,
    output_type_assembly,
    output_type_llvm_assembly,
    output_type_object,
    output_type_exe,
}

impl output_type : cmp::Eq {
    pure fn eq(other: &output_type) -> bool {
        (self as uint) == ((*other) as uint)
    }
    pure fn ne(other: &output_type) -> bool { !self.eq(other) }
}

fn llvm_err(sess: session, msg: ~str) -> ! unsafe {
    let cstr = llvm::LLVMRustGetLastError();
    if cstr == ptr::null() {
        sess.fatal(msg);
    } else { sess.fatal(msg + ~": " + str::raw::from_c_str(cstr)); }
}

fn WriteOutputFile(sess:session,
        PM: lib::llvm::PassManagerRef, M: ModuleRef,
        Triple: *c_char,
        // FIXME: When #2334 is fixed, change
        // c_uint to FileType
        Output: *c_char, FileType: c_uint,
        OptLevel: c_int,
        EnableSegmentedStacks: bool) {
    let result = llvm::LLVMRustWriteOutputFile(
            PM, M, Triple, Output, FileType, OptLevel, EnableSegmentedStacks);
    if (!result) {
        llvm_err(sess, ~"Could not write output");
    }
}

mod jit {
    #[legacy_exports];
    #[nolink]
    #[abi = "rust-intrinsic"]
    extern mod rusti {
        #[legacy_exports];
        fn morestack_addr() -> *();
    }

    struct Closure {
        code: *(),
        env: *(),
    }

    fn exec(sess: session,
            pm: PassManagerRef,
            m: ModuleRef,
            opt: c_int,
            stacks: bool) unsafe {
        let manager = llvm::LLVMRustPrepareJIT(rusti::morestack_addr());

        // We need to tell JIT where to resolve all linked
        // symbols from. The equivalent of -lstd, -lcore, etc.
        // By default the JIT will resolve symbols from the std and
        // core linked into rustc. We don't want that,
        // incase the user wants to use an older std library.

        let cstore = sess.cstore;
        for cstore::get_used_crate_files(cstore).each |cratepath| {
            let path = cratepath.to_str();

            debug!("linking: %s", path);

            let _: () = str::as_c_str(
                path,
                |buf_t| {
                    if !llvm::LLVMRustLoadCrate(manager, buf_t) {
                        llvm_err(sess, ~"Could not link");
                    }
                    debug!("linked: %s", path);
                });
        }

        // The execute function will return a void pointer
        // to the _rust_main function. We can do closure
        // magic here to turn it straight into a callable rust
        // closure. It will also cleanup the memory manager
        // for us.

        let entry = llvm::LLVMRustExecuteJIT(manager,
                                             pm, m, opt, stacks);

        if ptr::is_null(entry) {
            llvm_err(sess, ~"Could not JIT");
        } else {
            let closure = Closure {
                code: entry,
                env: ptr::null()
            };
            let func: fn(++argv: ~[~str]) = cast::transmute(move closure);

            func(~[sess.opts.binary]);
        }
    }
}

mod write {
    #[legacy_exports];
    fn is_object_or_assembly_or_exe(ot: output_type) -> bool {
        if ot == output_type_assembly || ot == output_type_object ||
               ot == output_type_exe {
            return true;
        }
        return false;
    }

    fn run_passes(sess: session, llmod: ModuleRef, output: &Path) {
        let opts = sess.opts;
        if sess.time_llvm_passes() { llvm::LLVMRustEnableTimePasses(); }
        let mut pm = mk_pass_manager();
        let td = mk_target_data(
            sess.targ_cfg.target_strs.data_layout);
        llvm::LLVMAddTargetData(td.lltd, pm.llpm);
        // FIXME (#2812): run the linter here also, once there are llvm-c
        // bindings for it.

        // Generate a pre-optimization intermediate file if -save-temps was
        // specified.


        if opts.save_temps {
            match opts.output_type {
              output_type_bitcode => {
                if opts.optimize != session::No {
                    let filename = output.with_filetype("no-opt.bc");
                    str::as_c_str(filename.to_str(), |buf| {
                        llvm::LLVMWriteBitcodeToFile(llmod, buf)
                    });
                }
              }
              _ => {
                let filename = output.with_filetype("bc");
                str::as_c_str(filename.to_str(), |buf| {
                    llvm::LLVMWriteBitcodeToFile(llmod, buf)
                });
              }
            }
        }
        if !sess.no_verify() { llvm::LLVMAddVerifierPass(pm.llpm); }
        // FIXME (#2396): This is mostly a copy of the bits of opt's -O2 that
        // are available in the C api.
        // Also: We might want to add optimization levels like -O1, -O2,
        // -Os, etc
        // Also: Should we expose and use the pass lists used by the opt
        // tool?

        if opts.optimize != session::No {
            let fpm = mk_pass_manager();
            llvm::LLVMAddTargetData(td.lltd, fpm.llpm);

            let FPMB = llvm::LLVMPassManagerBuilderCreate();
            llvm::LLVMPassManagerBuilderSetOptLevel(FPMB, 2u as c_uint);
            llvm::LLVMPassManagerBuilderPopulateFunctionPassManager(FPMB,
                                                                    fpm.llpm);
            llvm::LLVMPassManagerBuilderDispose(FPMB);

            llvm::LLVMRunPassManager(fpm.llpm, llmod);
            let mut threshold = 225;
            if opts.optimize == session::Aggressive { threshold = 275; }

            let MPMB = llvm::LLVMPassManagerBuilderCreate();
            llvm::LLVMPassManagerBuilderSetOptLevel(MPMB,
                                                    opts.optimize as c_uint);
            llvm::LLVMPassManagerBuilderSetSizeLevel(MPMB, False);
            llvm::LLVMPassManagerBuilderSetDisableUnitAtATime(MPMB, False);
            llvm::LLVMPassManagerBuilderSetDisableUnrollLoops(MPMB, False);
            llvm::LLVMPassManagerBuilderSetDisableSimplifyLibCalls(MPMB,
                                                                   False);

            if threshold != 0u {
                llvm::LLVMPassManagerBuilderUseInlinerWithThreshold
                    (MPMB, threshold as c_uint);
            }
            llvm::LLVMPassManagerBuilderPopulateModulePassManager(MPMB,
                                                                  pm.llpm);

            llvm::LLVMPassManagerBuilderDispose(MPMB);
        }
        if !sess.no_verify() { llvm::LLVMAddVerifierPass(pm.llpm); }
        if is_object_or_assembly_or_exe(opts.output_type) || opts.jit {
            let LLVMOptNone       = 0 as c_int; // -O0
            let LLVMOptLess       = 1 as c_int; // -O1
            let LLVMOptDefault    = 2 as c_int; // -O2, -Os
            let LLVMOptAggressive = 3 as c_int; // -O3

            let mut CodeGenOptLevel = match opts.optimize {
              session::No => LLVMOptNone,
              session::Less => LLVMOptLess,
              session::Default => LLVMOptDefault,
              session::Aggressive => LLVMOptAggressive
            };

            if opts.jit {
                // If we are using JIT, go ahead and create and
                // execute the engine now.
                // JIT execution takes ownership of the module,
                // so don't dispose and return.

                jit::exec(sess, pm.llpm, llmod, CodeGenOptLevel, true);

                if sess.time_llvm_passes() {
                    llvm::LLVMRustPrintPassTimings();
                }
                return;
            }

            let mut FileType;
            if opts.output_type == output_type_object ||
                   opts.output_type == output_type_exe {
               FileType = lib::llvm::ObjectFile;
            } else { FileType = lib::llvm::AssemblyFile; }
            // Write optimized bitcode if --save-temps was on.

            if opts.save_temps {
                // Always output the bitcode file with --save-temps

                let filename = output.with_filetype("opt.bc");
                llvm::LLVMRunPassManager(pm.llpm, llmod);
                str::as_c_str(filename.to_str(), |buf| {
                    llvm::LLVMWriteBitcodeToFile(llmod, buf)
                });
                pm = mk_pass_manager();
                // Save the assembly file if -S is used

                if opts.output_type == output_type_assembly {
                    let _: () = str::as_c_str(
                        sess.targ_cfg.target_strs.target_triple,
                        |buf_t| {
                            str::as_c_str(output.to_str(), |buf_o| {
                                WriteOutputFile(
                                    sess,
                                    pm.llpm,
                                    llmod,
                                    buf_t,
                                    buf_o,
                                    lib::llvm::AssemblyFile as c_uint,
                                    CodeGenOptLevel,
                                    true)
                            })
                        });
                }


                // Save the object file for -c or --save-temps alone
                // This .o is needed when an exe is built
                if opts.output_type == output_type_object ||
                       opts.output_type == output_type_exe {
                    let _: () = str::as_c_str(
                        sess.targ_cfg.target_strs.target_triple,
                        |buf_t| {
                            str::as_c_str(output.to_str(), |buf_o| {
                                WriteOutputFile(
                                    sess,
                                    pm.llpm,
                                    llmod,
                                    buf_t,
                                    buf_o,
                                    lib::llvm::ObjectFile as c_uint,
                                    CodeGenOptLevel,
                                    true)
                            })
                        });
                }
            } else {
                // If we aren't saving temps then just output the file
                // type corresponding to the '-c' or '-S' flag used

                let _: () = str::as_c_str(
                    sess.targ_cfg.target_strs.target_triple,
                    |buf_t| {
                        str::as_c_str(output.to_str(), |buf_o| {
                            WriteOutputFile(
                                sess,
                                pm.llpm,
                                llmod,
                                buf_t,
                                buf_o,
                                FileType as c_uint,
                                CodeGenOptLevel,
                                true)
                        })
                    });
            }
            // Clean up and return

            llvm::LLVMDisposeModule(llmod);
            if sess.time_llvm_passes() { llvm::LLVMRustPrintPassTimings(); }
            return;
        }

        if opts.output_type == output_type_llvm_assembly {
            // Given options "-S --emit-llvm": output LLVM assembly
            str::as_c_str(output.to_str(), |buf_o| {
                llvm::LLVMRustAddPrintModulePass(pm.llpm, llmod, buf_o)});
        } else {
            // If only a bitcode file is asked for by using the '--emit-llvm'
            // flag, then output it here
            llvm::LLVMRunPassManager(pm.llpm, llmod);
            str::as_c_str(output.to_str(),
                        |buf| llvm::LLVMWriteBitcodeToFile(llmod, buf) );
        }

        llvm::LLVMDisposeModule(llmod);
        if sess.time_llvm_passes() { llvm::LLVMRustPrintPassTimings(); }
    }
}


/*
 * Name mangling and its relationship to metadata. This is complex. Read
 * carefully.
 *
 * The semantic model of Rust linkage is, broadly, that "there's no global
 * namespace" between crates. Our aim is to preserve the illusion of this
 * model despite the fact that it's not *quite* possible to implement on
 * modern linkers. We initially didn't use system linkers at all, but have
 * been convinced of their utility.
 *
 * There are a few issues to handle:
 *
 *  - Linkers operate on a flat namespace, so we have to flatten names.
 *    We do this using the C++ namespace-mangling technique. Foo::bar
 *    symbols and such.
 *
 *  - Symbols with the same name but different types need to get different
 *    linkage-names. We do this by hashing a string-encoding of the type into
 *    a fixed-size (currently 16-byte hex) cryptographic hash function (CHF:
 *    we use SHA1) to "prevent collisions". This is not airtight but 16 hex
 *    digits on uniform probability means you're going to need 2**32 same-name
 *    symbols in the same process before you're even hitting birthday-paradox
 *    collision probability.
 *
 *  - Symbols in different crates but with same names "within" the crate need
 *    to get different linkage-names.
 *
 * So here is what we do:
 *
 *  - Separate the meta tags into two sets: exported and local. Only work with
 *    the exported ones when considering linkage.
 *
 *  - Consider two exported tags as special (and mandatory): name and vers.
 *    Every crate gets them; if it doesn't name them explicitly we infer them
 *    as basename(crate) and "0.1", respectively. Call these CNAME, CVERS.
 *
 *  - Define CMETA as all the non-name, non-vers exported meta tags in the
 *    crate (in sorted order).
 *
 *  - Define CMH as hash(CMETA + hashes of dependent crates).
 *
 *  - Compile our crate to lib CNAME-CMH-CVERS.so
 *
 *  - Define STH(sym) as hash(CNAME, CMH, type_str(sym))
 *
 *  - Suffix a mangled sym with ::STH@CVERS, so that it is unique in the
 *    name, non-name metadata, and type sense, and versioned in the way
 *    system linkers understand.
 *
 */

fn build_link_meta(sess: session, c: ast::crate, output: &Path,
                   symbol_hasher: &hash::State) -> link_meta {

    type provided_metas =
        {name: Option<~str>,
         vers: Option<~str>,
         cmh_items: ~[@ast::meta_item]};

    fn provided_link_metas(sess: session, c: ast::crate) ->
       provided_metas {
        let mut name: Option<~str> = None;
        let mut vers: Option<~str> = None;
        let mut cmh_items: ~[@ast::meta_item] = ~[];
        let linkage_metas = attr::find_linkage_metas(c.node.attrs);
        attr::require_unique_names(sess.diagnostic(), linkage_metas);
        for linkage_metas.each |meta| {
            if attr::get_meta_item_name(*meta) == ~"name" {
                match attr::get_meta_item_value_str(*meta) {
                  Some(v) => { name = Some(v); }
                  None => cmh_items.push(*meta)
                }
            } else if attr::get_meta_item_name(*meta) == ~"vers" {
                match attr::get_meta_item_value_str(*meta) {
                  Some(v) => { vers = Some(v); }
                  None => cmh_items.push(*meta)
                }
            } else { cmh_items.push(*meta); }
        }
        return {name: name, vers: vers, cmh_items: cmh_items};
    }

    // This calculates CMH as defined above
    fn crate_meta_extras_hash(symbol_hasher: &hash::State,
                              _crate: ast::crate,
                              metas: provided_metas,
                              dep_hashes: ~[~str]) -> ~str {
        fn len_and_str(s: ~str) -> ~str {
            return fmt!("%u_%s", str::len(s), s);
        }

        fn len_and_str_lit(l: ast::lit) -> ~str {
            return len_and_str(pprust::lit_to_str(@l));
        }

        let cmh_items = attr::sort_meta_items(metas.cmh_items);

        symbol_hasher.reset();
        for cmh_items.each |m| {
            match m.node {
              ast::meta_name_value(key, value) => {
                symbol_hasher.write_str(len_and_str(key));
                symbol_hasher.write_str(len_and_str_lit(value));
              }
              ast::meta_word(name) => {
                symbol_hasher.write_str(len_and_str(name));
              }
              ast::meta_list(_, _) => {
                // FIXME (#607): Implement this
                fail ~"unimplemented meta_item variant";
              }
            }
        }

        for dep_hashes.each |dh| {
            symbol_hasher.write_str(len_and_str(*dh));
        }

        return truncated_hash_result(symbol_hasher);
    }

    fn warn_missing(sess: session, name: ~str, default: ~str) {
        if !sess.building_library { return; }
        sess.warn(fmt!("missing crate link meta `%s`, using `%s` as default",
                       name, default));
    }

    fn crate_meta_name(sess: session, _crate: ast::crate,
                       output: &Path, metas: provided_metas) -> ~str {
        return match metas.name {
              Some(v) => v,
              None => {
                let name = match output.filestem() {
                  None => sess.fatal(fmt!("output file name `%s` doesn't\
                                           appear to have a stem",
                                          output.to_str())),
                  Some(s) => s
                };
                warn_missing(sess, ~"name", name);
                name
              }
            };
    }

    fn crate_meta_vers(sess: session, _crate: ast::crate,
                       metas: provided_metas) -> ~str {
        return match metas.vers {
              Some(v) => v,
              None => {
                let vers = ~"0.0";
                warn_missing(sess, ~"vers", vers);
                vers
              }
            };
    }

    let provided_metas = provided_link_metas(sess, c);
    let name = crate_meta_name(sess, c, output, provided_metas);
    let vers = crate_meta_vers(sess, c, provided_metas);
    let dep_hashes = cstore::get_dep_hashes(sess.cstore);
    let extras_hash =
        crate_meta_extras_hash(symbol_hasher, c, provided_metas, dep_hashes);

    return {name: name, vers: vers, extras_hash: extras_hash};
}

fn truncated_hash_result(symbol_hasher: &hash::State) -> ~str unsafe {
    symbol_hasher.result_str()
}


// This calculates STH for a symbol, as defined above
fn symbol_hash(tcx: ty::ctxt, symbol_hasher: &hash::State, t: ty::t,
               link_meta: link_meta) -> ~str {
    // NB: do *not* use abbrevs here as we want the symbol names
    // to be independent of one another in the crate.

    symbol_hasher.reset();
    symbol_hasher.write_str(link_meta.name);
    symbol_hasher.write_str(~"-");
    symbol_hasher.write_str(link_meta.extras_hash);
    symbol_hasher.write_str(~"-");
    symbol_hasher.write_str(encoder::encoded_ty(tcx, t));
    let hash = truncated_hash_result(symbol_hasher);
    // Prefix with _ so that it never blends into adjacent digits

    return ~"_" + hash;
}

fn get_symbol_hash(ccx: @crate_ctxt, t: ty::t) -> ~str {
    match ccx.type_hashcodes.find(t) {
      Some(h) => return h,
      None => {
        let hash = symbol_hash(ccx.tcx, ccx.symbol_hasher, t, ccx.link_meta);
        ccx.type_hashcodes.insert(t, hash);
        return hash;
      }
    }
}


// Name sanitation. LLVM will happily accept identifiers with weird names, but
// gas doesn't!
fn sanitize(s: ~str) -> ~str {
    let mut result = ~"";
    for str::chars_each(s) |c| {
        match c {
          '@' => result += ~"_sbox_",
          '~' => result += ~"_ubox_",
          '*' => result += ~"_ptr_",
          '&' => result += ~"_ref_",
          ',' => result += ~"_",

          '{' | '(' => result += ~"_of_",
          'a' .. 'z'
          | 'A' .. 'Z'
          | '0' .. '9'
          | '_' => str::push_char(&mut result, c),
          _ => {
            if c > 'z' && char::is_XID_continue(c) {
                str::push_char(&mut result, c);
            }
          }
        }
    }

    // Underscore-qualify anything that didn't start as an ident.
    if result.len() > 0u &&
        result[0] != '_' as u8 &&
        ! char::is_XID_start(result[0] as char) {
        return ~"_" + result;
    }

    return result;
}

fn mangle(sess: session, ss: path) -> ~str {
    // Follow C++ namespace-mangling style

    let mut n = ~"_ZN"; // Begin name-sequence.

    for ss.each |s| {
        match *s { path_name(s) | path_mod(s) => {
          let sani = sanitize(sess.str_of(s));
          n += fmt!("%u%s", str::len(sani), sani);
        } }
    }
    n += ~"E"; // End name-sequence.
    n
}

fn exported_name(sess: session, path: path, hash: ~str, vers: ~str) -> ~str {
    return mangle(sess,
                  vec::append_one(
                      vec::append_one(path, path_name(sess.ident_of(hash))),
                      path_name(sess.ident_of(vers))));
}

fn mangle_exported_name(ccx: @crate_ctxt, path: path, t: ty::t) -> ~str {
    let hash = get_symbol_hash(ccx, t);
    return exported_name(ccx.sess, path, hash, ccx.link_meta.vers);
}

fn mangle_internal_name_by_type_only(ccx: @crate_ctxt,
                                     t: ty::t, name: ~str) ->
   ~str {
    let s = util::ppaux::ty_to_short_str(ccx.tcx, t);
    let hash = get_symbol_hash(ccx, t);
    return mangle(ccx.sess,
                  ~[path_name(ccx.sess.ident_of(name)),
                    path_name(ccx.sess.ident_of(s)),
                    path_name(ccx.sess.ident_of(hash))]);
}

fn mangle_internal_name_by_path_and_seq(ccx: @crate_ctxt, path: path,
                                        flav: ~str) -> ~str {
    return mangle(ccx.sess,
                  vec::append_one(path, path_name(ccx.names(flav))));
}

fn mangle_internal_name_by_path(ccx: @crate_ctxt, path: path) -> ~str {
    return mangle(ccx.sess, path);
}

fn mangle_internal_name_by_seq(ccx: @crate_ctxt, flav: ~str) -> ~str {
    return fmt!("%s_%u", flav, ccx.names(flav).repr);
}

// If the user wants an exe generated we need to invoke
// cc to link the object file with some libs
fn link_binary(sess: session,
               obj_filename: &Path,
               out_filename: &Path,
               lm: link_meta) {
    // Converts a library file-stem into a cc -l argument
    fn unlib(config: @session::config, stem: ~str) -> ~str {
        if stem.starts_with("lib") &&
            config.os != session::os_win32 {
            stem.slice(3, stem.len())
        } else {
            stem
        }
    }

    let output = if sess.building_library {
        let long_libname =
            os::dll_filename(fmt!("%s-%s-%s",
                                  lm.name, lm.extras_hash, lm.vers));
        debug!("link_meta.name:  %s", lm.name);
        debug!("long_libname: %s", long_libname);
        debug!("out_filename: %s", out_filename.to_str());
        debug!("dirname(out_filename): %s", out_filename.dir_path().to_str());

        out_filename.dir_path().push(long_libname)
    } else {
        *out_filename
    };

    log(debug, ~"output: " + output.to_str());

    // The default library location, we need this to find the runtime.
    // The location of crates will be determined as needed.
    let stage: ~str = ~"-L" + sess.filesearch.get_target_lib_path().to_str();

    // In the future, FreeBSD will use clang as default compiler.
    // It would be flexible to use cc (system's default C compiler)
    // instead of hard-coded gcc.
    // For win32, there is no cc command,
    // so we add a condition to make it use gcc.
    let cc_prog: ~str =
        if sess.targ_cfg.os == session::os_win32 { ~"gcc" } else { ~"cc" };
    // The invocations of cc share some flags across platforms

    let mut cc_args =
        vec::append(~[stage], sess.targ_cfg.target_strs.cc_args);
    cc_args.push(~"-o");
    cc_args.push(output.to_str());
    cc_args.push(obj_filename.to_str());

    let mut lib_cmd;
    let os = sess.targ_cfg.os;
    if os == session::os_macos {
        lib_cmd = ~"-dynamiclib";
    } else {
        lib_cmd = ~"-shared";
    }

    // # Crate linking

    let cstore = sess.cstore;
    for cstore::get_used_crate_files(cstore).each |cratepath| {
        if cratepath.filetype() == Some(~".rlib") {
            cc_args.push(cratepath.to_str());
            loop;
        }
        let dir = cratepath.dirname();
        if dir != ~"" { cc_args.push(~"-L" + dir); }
        let libarg = unlib(sess.targ_cfg, cratepath.filestem().get());
        cc_args.push(~"-l" + libarg);
    }

    let ula = cstore::get_used_link_args(cstore);
    for ula.each |arg| { cc_args.push(*arg); }

    // # Extern library linking

    // User-supplied library search paths (-L on the cammand line) These are
    // the same paths used to find Rust crates, so some of them may have been
    // added already by the previous crate linking code. This only allows them
    // to be found at compile time so it is still entirely up to outside
    // forces to make sure that library can be found at runtime.

    let addl_paths = sess.opts.addl_lib_search_paths;
    for addl_paths.each |path| { cc_args.push(~"-L" + path.to_str()); }

    // The names of the extern libraries
    let used_libs = cstore::get_used_libraries(cstore);
    for used_libs.each |l| { cc_args.push(~"-l" + *l); }

    if sess.building_library {
        cc_args.push(lib_cmd);

        // On mac we need to tell the linker to let this library
        // be rpathed
        if sess.targ_cfg.os == session::os_macos {
            cc_args.push(~"-Wl,-install_name,@rpath/"
                      + output.filename().get());
        }
    }

    // Always want the runtime linked in
    cc_args.push(~"-lrustrt");

    // On linux librt and libdl are an indirect dependencies via rustrt,
    // and binutils 2.22+ won't add them automatically
    if sess.targ_cfg.os == session::os_linux {
        cc_args.push_all(~[~"-lrt", ~"-ldl"]);

        // LLVM implements the `frem` instruction as a call to `fmod`,
        // which lives in libm. Similar to above, on some linuxes we
        // have to be explicit about linking to it. See #2510
        cc_args.push(~"-lm");
    }

    if sess.targ_cfg.os == session::os_freebsd {
        cc_args.push_all(~[~"-pthread", ~"-lrt",
                                ~"-L/usr/local/lib", ~"-lexecinfo",
                                ~"-L/usr/local/lib/gcc46",
                                ~"-L/usr/local/lib/gcc44", ~"-lstdc++",
                                ~"-Wl,-z,origin",
                                ~"-Wl,-rpath,/usr/local/lib/gcc46",
                                ~"-Wl,-rpath,/usr/local/lib/gcc44"]);
    }

    // OS X 10.6 introduced 'compact unwind info', which is produced by the
    // linker from the dwarf unwind info. Unfortunately, it does not seem to
    // understand how to unwind our __morestack frame, so we have to turn it
    // off. This has impacted some other projects like GHC.
    if sess.targ_cfg.os == session::os_macos {
        cc_args.push(~"-Wl,-no_compact_unwind");
    }

    // Stack growth requires statically linking a __morestack function
    cc_args.push(~"-lmorestack");

    // FIXME (#2397): At some point we want to rpath our guesses as to where
    // extern libraries might live, based on the addl_lib_search_paths
    cc_args.push_all(rpath::get_rpath_flags(sess, &output));

    debug!("%s link args: %s", cc_prog, str::connect(cc_args, ~" "));
    // We run 'cc' here
    let prog = run::program_output(cc_prog, cc_args);
    if 0 != prog.status {
        sess.err(fmt!("linking with `%s` failed with code %d",
                      cc_prog, prog.status));
        sess.note(fmt!("%s arguments: %s",
                       cc_prog, str::connect(cc_args, ~" ")));
        sess.note(prog.err + prog.out);
        sess.abort_if_errors();
    }

    // Clean up on Darwin
    if sess.targ_cfg.os == session::os_macos {
        run::run_program(~"dsymutil", ~[output.to_str()]);
    }

    // Remove the temporary object file if we aren't saving temps
    if !sess.opts.save_temps {
        if ! os::remove_file(obj_filename) {
            sess.warn(fmt!("failed to delete object file `%s`",
                           obj_filename.to_str()));
        }
    }
}
//
// Local Variables:
// mode: rust
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// End:
//
