提交 f6e9485b 编写于 作者: B bors

Auto merge of #55627 - sunfishcode:cg-llvm-gen, r=nikomatsakis

rustc_codegen_llvm: traitification of LLVM-specific CodegenCx and Builder methods

This PR is the continuation of #54012 and earlier PRs, in the grand plan of #45274 to allow for multiple codegen backends.

High-level summary: interpose a set of traits between Rust's codegen logic and the LLVM APIs, allowing another backend to implement the traits and share most of the codegen logic. These traits are currently somewhat LLVM-specific, but once this refactoring is in place, they can evolve to be more general.

See [this README](https://github.com/rust-lang/rust/blob/756f84d7cef90b7364ae88ca707e59670dde4c92/src/librustc_codegen_ssa/README.md) for a writeup on the current trait organization.
......@@ -2122,9 +2122,20 @@ dependencies = [
"memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_codegen_ssa 0.0.0",
"rustc_llvm 0.0.0",
]
[[package]]
name = "rustc_codegen_ssa"
version = "0.0.0"
dependencies = [
"cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
"memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rustc_codegen_utils"
version = "0.0.0"
......@@ -2132,13 +2143,11 @@ dependencies = [
"flate2 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc 0.0.0",
"rustc_allocator 0.0.0",
"rustc_data_structures 0.0.0",
"rustc_incremental 0.0.0",
"rustc_metadata 0.0.0",
"rustc_mir 0.0.0",
"rustc_target 0.0.0",
"serialize 0.0.0",
"syntax 0.0.0",
"syntax_pos 0.0.0",
]
......
......@@ -13,6 +13,7 @@ test = false
cc = "1.0.1"
num_cpus = "1.0"
rustc-demangle = "0.1.4"
rustc_codegen_ssa = { path = "../librustc_codegen_ssa" }
rustc_llvm = { path = "../librustc_llvm" }
memmap = "0.6"
......
......@@ -9,18 +9,20 @@
// except according to those terms.
use llvm::{self, AttributePlace};
use base;
use builder::{Builder, MemFlags};
use common::C_usize;
use rustc_codegen_ssa::MemFlags;
use builder::Builder;
use context::CodegenCx;
use mir::place::PlaceRef;
use mir::operand::OperandValue;
use rustc_codegen_ssa::mir::place::PlaceRef;
use rustc_codegen_ssa::mir::operand::OperandValue;
use type_::Type;
use type_of::{LayoutLlvmExt, PointerKind};
use value::Value;
use rustc_target::abi::call::ArgType;
use rustc_codegen_ssa::traits::*;
use rustc_target::abi::{HasDataLayout, LayoutOf, Size, TyLayout, Abi as LayoutAbi};
use rustc::ty::{self, Ty};
use rustc::ty::{self, Ty, Instance};
use rustc::ty::layout;
use libc::c_uint;
......@@ -110,16 +112,16 @@ pub trait LlvmType {
impl LlvmType for Reg {
fn llvm_type(&self, cx: &CodegenCx<'ll, '_>) -> &'ll Type {
match self.kind {
RegKind::Integer => Type::ix(cx, self.size.bits()),
RegKind::Integer => cx.type_ix(self.size.bits()),
RegKind::Float => {
match self.size.bits() {
32 => Type::f32(cx),
64 => Type::f64(cx),
32 => cx.type_f32(),
64 => cx.type_f64(),
_ => bug!("unsupported float: {:?}", self)
}
}
RegKind::Vector => {
Type::vector(Type::i8(cx), self.size.bytes())
cx.type_vector(cx.type_i8(), self.size.bytes())
}
}
}
......@@ -143,7 +145,7 @@ fn llvm_type(&self, cx: &CodegenCx<'ll, '_>) -> &'ll Type {
// Simplify to array when all chunks are the same size and type
if rem_bytes == 0 {
return Type::array(rest_ll_unit, rest_count);
return cx.type_array(rest_ll_unit, rest_count);
}
}
......@@ -158,17 +160,27 @@ fn llvm_type(&self, cx: &CodegenCx<'ll, '_>) -> &'ll Type {
if rem_bytes != 0 {
// Only integers can be really split further.
assert_eq!(self.rest.unit.kind, RegKind::Integer);
args.push(Type::ix(cx, rem_bytes * 8));
args.push(cx.type_ix(rem_bytes * 8));
}
Type::struct_(cx, &args, false)
cx.type_struct(&args, false)
}
}
pub trait ArgTypeExt<'ll, 'tcx> {
fn memory_ty(&self, cx: &CodegenCx<'ll, 'tcx>) -> &'ll Type;
fn store(&self, bx: &Builder<'_, 'll, 'tcx>, val: &'ll Value, dst: PlaceRef<'ll, 'tcx>);
fn store_fn_arg(&self, bx: &Builder<'_, 'll, 'tcx>, idx: &mut usize, dst: PlaceRef<'ll, 'tcx>);
fn store(
&self,
bx: &mut Builder<'_, 'll, 'tcx>,
val: &'ll Value,
dst: PlaceRef<'tcx, &'ll Value>,
);
fn store_fn_arg(
&self,
bx: &mut Builder<'_, 'll, 'tcx>,
idx: &mut usize,
dst: PlaceRef<'tcx, &'ll Value>,
);
}
impl ArgTypeExt<'ll, 'tcx> for ArgType<'tcx, Ty<'tcx>> {
......@@ -182,11 +194,15 @@ fn memory_ty(&self, cx: &CodegenCx<'ll, 'tcx>) -> &'ll Type {
/// place for the original Rust type of this argument/return.
/// Can be used for both storing formal arguments into Rust variables
/// or results of call/invoke instructions into their destinations.
fn store(&self, bx: &Builder<'_, 'll, 'tcx>, val: &'ll Value, dst: PlaceRef<'ll, 'tcx>) {
fn store(
&self,
bx: &mut Builder<'_, 'll, 'tcx>,
val: &'ll Value,
dst: PlaceRef<'tcx, &'ll Value>,
) {
if self.is_ignore() {
return;
}
let cx = bx.cx;
if self.is_sized_indirect() {
OperandValue::Ref(val, None, self.layout.align).store(bx, dst)
} else if self.is_unsized_indirect() {
......@@ -196,7 +212,8 @@ fn store(&self, bx: &Builder<'_, 'll, 'tcx>, val: &'ll Value, dst: PlaceRef<'ll,
// uses it for i16 -> {i8, i8}, but not for i24 -> {i8, i8, i8}.
let can_store_through_cast_ptr = false;
if can_store_through_cast_ptr {
let cast_dst = bx.pointercast(dst.llval, cast.llvm_type(cx).ptr_to());
let cast_ptr_llty = bx.cx().type_ptr_to(cast.llvm_type(bx.cx()));
let cast_dst = bx.pointercast(dst.llval, cast_ptr_llty);
bx.store(val, cast_dst, self.layout.align);
} else {
// The actual return type is a struct, but the ABI
......@@ -214,22 +231,23 @@ fn store(&self, bx: &Builder<'_, 'll, 'tcx>, val: &'ll Value, dst: PlaceRef<'ll,
// bitcasting to the struct type yields invalid cast errors.
// We instead thus allocate some scratch space...
let scratch_size = cast.size(cx);
let scratch_align = cast.align(cx);
let llscratch = bx.alloca(cast.llvm_type(cx), "abi_cast", scratch_align);
let scratch_size = cast.size(bx.cx());
let scratch_align = cast.align(bx.cx());
let llscratch = bx.alloca(cast.llvm_type(bx.cx()), "abi_cast", scratch_align);
bx.lifetime_start(llscratch, scratch_size);
// ...where we first store the value...
bx.store(val, llscratch, scratch_align);
// ...and then memcpy it to the intended destination.
base::call_memcpy(bx,
bx.pointercast(dst.llval, Type::i8p(cx)),
self.layout.align,
bx.pointercast(llscratch, Type::i8p(cx)),
scratch_align,
C_usize(cx, self.layout.size.bytes()),
MemFlags::empty());
bx.memcpy(
dst.llval,
self.layout.align,
llscratch,
scratch_align,
bx.cx().const_usize(self.layout.size.bytes()),
MemFlags::empty()
);
bx.lifetime_end(llscratch, scratch_size);
}
......@@ -238,7 +256,12 @@ fn store(&self, bx: &Builder<'_, 'll, 'tcx>, val: &'ll Value, dst: PlaceRef<'ll,
}
}
fn store_fn_arg(&self, bx: &Builder<'a, 'll, 'tcx>, idx: &mut usize, dst: PlaceRef<'ll, 'tcx>) {
fn store_fn_arg(
&self,
bx: &mut Builder<'a, 'll, 'tcx>,
idx: &mut usize,
dst: PlaceRef<'tcx, &'ll Value>,
) {
let mut next = || {
let val = llvm::get_param(bx.llfn(), *idx as c_uint);
*idx += 1;
......@@ -259,6 +282,27 @@ fn store_fn_arg(&self, bx: &Builder<'a, 'll, 'tcx>, idx: &mut usize, dst: PlaceR
}
}
impl ArgTypeMethods<'tcx> for Builder<'a, 'll, 'tcx> {
fn store_fn_arg(
&mut self,
ty: &ArgType<'tcx, Ty<'tcx>>,
idx: &mut usize, dst: PlaceRef<'tcx, Self::Value>
) {
ty.store_fn_arg(self, idx, dst)
}
fn store_arg_ty(
&mut self,
ty: &ArgType<'tcx, Ty<'tcx>>,
val: &'ll Value,
dst: PlaceRef<'tcx, &'ll Value>
) {
ty.store(self, val, dst)
}
fn memory_ty(&self, ty: &ArgType<'tcx, Ty<'tcx>>) -> &'ll Type {
ty.memory_ty(self.cx())
}
}
pub trait FnTypeExt<'tcx> {
fn of_instance(cx: &CodegenCx<'ll, 'tcx>, instance: &ty::Instance<'tcx>) -> Self;
fn new(cx: &CodegenCx<'ll, 'tcx>,
......@@ -280,7 +324,7 @@ fn adjust_for_abi(&mut self,
fn ptr_to_llvm_type(&self, cx: &CodegenCx<'ll, 'tcx>) -> &'ll Type;
fn llvm_cconv(&self) -> llvm::CallConv;
fn apply_attrs_llfn(&self, llfn: &'ll Value);
fn apply_attrs_callsite(&self, bx: &Builder<'a, 'll, 'tcx>, callsite: &'ll Value);
fn apply_attrs_callsite(&self, bx: &mut Builder<'a, 'll, 'tcx>, callsite: &'ll Value);
}
impl<'tcx> FnTypeExt<'tcx> for FnType<'tcx, Ty<'tcx>> {
......@@ -614,14 +658,14 @@ fn llvm_type(&self, cx: &CodegenCx<'ll, 'tcx>) -> &'ll Type {
);
let llreturn_ty = match self.ret.mode {
PassMode::Ignore => Type::void(cx),
PassMode::Ignore => cx.type_void(),
PassMode::Direct(_) | PassMode::Pair(..) => {
self.ret.layout.immediate_llvm_type(cx)
}
PassMode::Cast(cast) => cast.llvm_type(cx),
PassMode::Indirect(..) => {
llargument_tys.push(self.ret.memory_ty(cx).ptr_to());
Type::void(cx)
llargument_tys.push(cx.type_ptr_to(self.ret.memory_ty(cx)));
cx.type_void()
}
};
......@@ -647,15 +691,15 @@ fn llvm_type(&self, cx: &CodegenCx<'ll, 'tcx>) -> &'ll Type {
continue;
}
PassMode::Cast(cast) => cast.llvm_type(cx),
PassMode::Indirect(_, None) => arg.memory_ty(cx).ptr_to(),
PassMode::Indirect(_, None) => cx.type_ptr_to(arg.memory_ty(cx)),
};
llargument_tys.push(llarg_ty);
}
if self.variadic {
Type::variadic_func(&llargument_tys, llreturn_ty)
cx.type_variadic_func(&llargument_tys, llreturn_ty)
} else {
Type::func(&llargument_tys, llreturn_ty)
cx.type_func(&llargument_tys, llreturn_ty)
}
}
......@@ -717,7 +761,7 @@ fn apply_attrs_llfn(&self, llfn: &'ll Value) {
}
}
fn apply_attrs_callsite(&self, bx: &Builder<'a, 'll, 'tcx>, callsite: &'ll Value) {
fn apply_attrs_callsite(&self, bx: &mut Builder<'a, 'll, 'tcx>, callsite: &'ll Value) {
let mut i = 0;
let mut apply = |attrs: &ArgAttributes| {
attrs.apply_callsite(llvm::AttributePlace::Argument(i), callsite);
......@@ -736,7 +780,7 @@ fn apply_attrs_callsite(&self, bx: &Builder<'a, 'll, 'tcx>, callsite: &'ll Value
// by the LLVM verifier.
if let layout::Int(..) = scalar.value {
if !scalar.is_bool() {
let range = scalar.valid_range_exclusive(bx.cx);
let range = scalar.valid_range_exclusive(bx.cx());
if range.start != range.end {
bx.range_metadata(callsite, range);
}
......@@ -769,3 +813,29 @@ fn apply_attrs_callsite(&self, bx: &Builder<'a, 'll, 'tcx>, callsite: &'ll Value
}
}
}
impl AbiMethods<'tcx> for CodegenCx<'ll, 'tcx> {
fn new_fn_type(&self, sig: ty::FnSig<'tcx>, extra_args: &[Ty<'tcx>]) -> FnType<'tcx, Ty<'tcx>> {
FnType::new(&self, sig, extra_args)
}
fn new_vtable(
&self,
sig: ty::FnSig<'tcx>,
extra_args: &[Ty<'tcx>]
) -> FnType<'tcx, Ty<'tcx>> {
FnType::new_vtable(&self, sig, extra_args)
}
fn fn_type_of_instance(&self, instance: &Instance<'tcx>) -> FnType<'tcx, Ty<'tcx>> {
FnType::of_instance(&self, instance)
}
}
impl AbiBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> {
fn apply_attrs_callsite(
&mut self,
ty: &FnType<'tcx, Ty<'tcx>>,
callsite: Self::Value
) {
ty.apply_attrs_callsite(self, callsite)
}
}
......@@ -9,126 +9,123 @@
// except according to those terms.
use llvm;
use common::*;
use type_::Type;
use context::CodegenCx;
use type_of::LayoutLlvmExt;
use builder::Builder;
use value::Value;
use rustc::hir;
use rustc_codegen_ssa::traits::*;
use mir::place::PlaceRef;
use mir::operand::OperandValue;
use rustc_codegen_ssa::mir::place::PlaceRef;
use rustc_codegen_ssa::mir::operand::OperandValue;
use std::ffi::CString;
use syntax::ast::AsmDialect;
use libc::{c_uint, c_char};
// Take an inline assembly expression and splat it out via LLVM
pub fn codegen_inline_asm(
bx: &Builder<'a, 'll, 'tcx>,
ia: &hir::InlineAsm,
outputs: Vec<PlaceRef<'ll, 'tcx>>,
mut inputs: Vec<&'ll Value>
) -> bool {
let mut ext_constraints = vec![];
let mut output_types = vec![];
// Prepare the output operands
let mut indirect_outputs = vec![];
for (i, (out, place)) in ia.outputs.iter().zip(&outputs).enumerate() {
if out.is_rw {
inputs.push(place.load(bx).immediate());
ext_constraints.push(i.to_string());
impl AsmBuilderMethods<'tcx> for Builder<'a, 'll, 'tcx> {
fn codegen_inline_asm(
&mut self,
ia: &hir::InlineAsm,
outputs: Vec<PlaceRef<'tcx, &'ll Value>>,
mut inputs: Vec<&'ll Value>
) -> bool {
let mut ext_constraints = vec![];
let mut output_types = vec![];
// Prepare the output operands
let mut indirect_outputs = vec![];
for (i, (out, &place)) in ia.outputs.iter().zip(&outputs).enumerate() {
if out.is_rw {
inputs.push(self.load_operand(place).immediate());
ext_constraints.push(i.to_string());
}
if out.is_indirect {
indirect_outputs.push(self.load_operand(place).immediate());
} else {
output_types.push(place.layout.llvm_type(self.cx()));
}
}
if out.is_indirect {
indirect_outputs.push(place.load(bx).immediate());
} else {
output_types.push(place.layout.llvm_type(bx.cx));
if !indirect_outputs.is_empty() {
indirect_outputs.extend_from_slice(&inputs);
inputs = indirect_outputs;
}
}
if !indirect_outputs.is_empty() {
indirect_outputs.extend_from_slice(&inputs);
inputs = indirect_outputs;
}
let clobbers = ia.clobbers.iter()
.map(|s| format!("~{{{}}}", &s));
// Default per-arch clobbers
// Basically what clang does
let arch_clobbers = match &bx.sess().target.target.arch[..] {
"x86" | "x86_64" => vec!["~{dirflag}", "~{fpsr}", "~{flags}"],
"mips" | "mips64" => vec!["~{$1}"],
_ => Vec::new()
};
let all_constraints =
ia.outputs.iter().map(|out| out.constraint.to_string())
.chain(ia.inputs.iter().map(|s| s.to_string()))
.chain(ext_constraints)
.chain(clobbers)
.chain(arch_clobbers.iter().map(|s| s.to_string()))
.collect::<Vec<String>>().join(",");
debug!("Asm Constraints: {}", &all_constraints);
// Depending on how many outputs we have, the return type is different
let num_outputs = output_types.len();
let output_type = match num_outputs {
0 => Type::void(bx.cx),
1 => output_types[0],
_ => Type::struct_(bx.cx, &output_types, false)
};
let dialect = match ia.dialect {
AsmDialect::Att => llvm::AsmDialect::Att,
AsmDialect::Intel => llvm::AsmDialect::Intel,
};
let asm = CString::new(ia.asm.as_str().as_bytes()).unwrap();
let constraint_cstr = CString::new(all_constraints).unwrap();
let r = bx.inline_asm_call(
asm.as_ptr(),
constraint_cstr.as_ptr(),
&inputs,
output_type,
ia.volatile,
ia.alignstack,
dialect
);
if r.is_none() {
return false;
}
let r = r.unwrap();
let clobbers = ia.clobbers.iter()
.map(|s| format!("~{{{}}}", &s));
// Default per-arch clobbers
// Basically what clang does
let arch_clobbers = match &self.cx().sess().target.target.arch[..] {
"x86" | "x86_64" => vec!["~{dirflag}", "~{fpsr}", "~{flags}"],
"mips" | "mips64" => vec!["~{$1}"],
_ => Vec::new()
};
let all_constraints =
ia.outputs.iter().map(|out| out.constraint.to_string())
.chain(ia.inputs.iter().map(|s| s.to_string()))
.chain(ext_constraints)
.chain(clobbers)
.chain(arch_clobbers.iter().map(|s| s.to_string()))
.collect::<Vec<String>>().join(",");
debug!("Asm Constraints: {}", &all_constraints);
// Depending on how many outputs we have, the return type is different
let num_outputs = output_types.len();
let output_type = match num_outputs {
0 => self.cx().type_void(),
1 => output_types[0],
_ => self.cx().type_struct(&output_types, false)
};
let asm = CString::new(ia.asm.as_str().as_bytes()).unwrap();
let constraint_cstr = CString::new(all_constraints).unwrap();
let r = self.inline_asm_call(
&asm,
&constraint_cstr,
&inputs,
output_type,
ia.volatile,
ia.alignstack,
ia.dialect
);
if r.is_none() {
return false;
}
let r = r.unwrap();
// Again, based on how many outputs we have
let outputs = ia.outputs.iter().zip(&outputs).filter(|&(ref o, _)| !o.is_indirect);
for (i, (_, &place)) in outputs.enumerate() {
let v = if num_outputs == 1 { r } else { bx.extract_value(r, i as u64) };
OperandValue::Immediate(v).store(bx, place);
}
// Again, based on how many outputs we have
let outputs = ia.outputs.iter().zip(&outputs).filter(|&(ref o, _)| !o.is_indirect);
for (i, (_, &place)) in outputs.enumerate() {
let v = if num_outputs == 1 { r } else { self.extract_value(r, i as u64) };
OperandValue::Immediate(v).store(self, place);
}
// Store mark in a metadata node so we can map LLVM errors
// back to source locations. See #17552.
unsafe {
let key = "srcloc";
let kind = llvm::LLVMGetMDKindIDInContext(bx.cx.llcx,
key.as_ptr() as *const c_char, key.len() as c_uint);
// Store mark in a metadata node so we can map LLVM errors
// back to source locations. See #17552.
unsafe {
let key = "srcloc";
let kind = llvm::LLVMGetMDKindIDInContext(self.cx().llcx,
key.as_ptr() as *const c_char, key.len() as c_uint);
let val: &'ll Value = C_i32(bx.cx, ia.ctxt.outer().as_u32() as i32);
let val: &'ll Value = self.cx().const_i32(ia.ctxt.outer().as_u32() as i32);
llvm::LLVMSetMetadata(r, kind,
llvm::LLVMMDNodeInContext(bx.cx.llcx, &val, 1));
}
llvm::LLVMSetMetadata(r, kind,
llvm::LLVMMDNodeInContext(self.cx().llcx, &val, 1));
}
return true;
true
}
}
pub fn codegen_global_asm<'a, 'tcx>(cx: &CodegenCx<'a, 'tcx>,
ga: &hir::GlobalAsm) {
let asm = CString::new(ga.asm.as_str().as_bytes()).unwrap();
unsafe {
llvm::LLVMRustAppendModuleInlineAsm(cx.llmod, asm.as_ptr());
impl AsmMethods<'tcx> for CodegenCx<'ll, 'tcx> {
fn codegen_global_asm(&self, ga: &hir::GlobalAsm) {
let asm = CString::new(ga.asm.as_str().as_bytes()).unwrap();
unsafe {
llvm::LLVMRustAppendModuleInlineAsm(self.llmod, asm.as_ptr());
}
}
}
......@@ -21,6 +21,7 @@
use rustc_data_structures::sync::Lrc;
use rustc_data_structures::fx::FxHashMap;
use rustc_target::spec::PanicStrategy;
use rustc_codegen_ssa::traits::*;
use attributes;
use llvm::{self, Attribute};
......
......@@ -18,6 +18,7 @@
use std::str;
use back::bytecode::RLIB_BYTECODE_EXTENSION;
use rustc_codegen_ssa::back::archive::find_library;
use libc;
use llvm::archive_ro::{ArchiveRO, Child};
use llvm::{self, ArchiveKind};
......@@ -52,7 +53,6 @@ enum Addition {
},
}
fn is_relevant_child(c: &Child) -> bool {
match c.name() {
Some(name) => !name.contains("SYMDEF"),
......@@ -107,7 +107,7 @@ fn src_archive(&mut self) -> Option<&ArchiveRO> {
/// Adds all of the contents of a native library to this archive. This will
/// search in the relevant locations for a library named `name`.
pub fn add_native_library(&mut self, name: &str) {
let location = ::rustc_codegen_utils::find_library(name, &self.config.lib_search_paths,
let location = find_library(name, &self.config.lib_search_paths,
self.config.sess);
self.add_archive(&location, |_| false).unwrap_or_else(|e| {
self.config.sess.fatal(&format!("failed to add native library {}: {}",
......
......@@ -9,9 +9,12 @@
// except according to those terms.
use back::wasm;
use cc::windows_registry;
use super::archive::{ArchiveBuilder, ArchiveConfig};
use super::bytecode::RLIB_BYTECODE_EXTENSION;
use rustc_codegen_ssa::back::linker::Linker;
use rustc_codegen_ssa::back::link::{remove, ignored_for_lto, each_linked_rlib, linker_and_flavor,
get_linker};
use rustc_codegen_ssa::back::command::Command;
use super::rpath::RPathConfig;
use super::rpath;
use metadata::METADATA_FILENAME;
......@@ -20,17 +23,15 @@
use rustc::session::filesearch;
use rustc::session::search_paths::PathKind;
use rustc::session::Session;
use rustc::middle::cstore::{NativeLibrary, LibSource, NativeLibraryKind};
use rustc::middle::cstore::{NativeLibrary, NativeLibraryKind};
use rustc::middle::dependency_format::Linkage;
use {CodegenResults, CrateInfo};
use rustc_codegen_ssa::CodegenResults;
use rustc::util::common::time;
use rustc_fs_util::fix_windows_verbatim_for_gcc;
use rustc::hir::def_id::CrateNum;
use tempfile::{Builder as TempFileBuilder, TempDir};
use rustc_target::spec::{PanicStrategy, RelroLevel, LinkerFlavor};
use rustc_data_structures::fx::FxHashSet;
use rustc_codegen_utils::linker::Linker;
use rustc_codegen_utils::command::Command;
use context::get_reloc_model;
use llvm;
......@@ -50,69 +51,6 @@
invalid_output_for_target, filename_for_metadata,
out_filename, check_file_is_writeable};
// The third parameter is for env vars, used on windows to set up the
// path for MSVC to find its DLLs, and gcc to find its bundled
// toolchain
pub fn get_linker(sess: &Session, linker: &Path, flavor: LinkerFlavor) -> (PathBuf, Command) {
let msvc_tool = windows_registry::find_tool(&sess.opts.target_triple.triple(), "link.exe");
// If our linker looks like a batch script on Windows then to execute this
// we'll need to spawn `cmd` explicitly. This is primarily done to handle
// emscripten where the linker is `emcc.bat` and needs to be spawned as
// `cmd /c emcc.bat ...`.
//
// This worked historically but is needed manually since #42436 (regression
// was tagged as #42791) and some more info can be found on #44443 for
// emscripten itself.
let mut cmd = match linker.to_str() {
Some(linker) if cfg!(windows) && linker.ends_with(".bat") => Command::bat_script(linker),
_ => match flavor {
LinkerFlavor::Lld(f) => Command::lld(linker, f),
LinkerFlavor::Msvc
if sess.opts.cg.linker.is_none() && sess.target.target.options.linker.is_none() =>
{
Command::new(msvc_tool.as_ref().map(|t| t.path()).unwrap_or(linker))
},
_ => Command::new(linker),
}
};
// The compiler's sysroot often has some bundled tools, so add it to the
// PATH for the child.
let mut new_path = sess.host_filesearch(PathKind::All)
.get_tools_search_paths();
let mut msvc_changed_path = false;
if sess.target.target.options.is_like_msvc {
if let Some(ref tool) = msvc_tool {
cmd.args(tool.args());
for &(ref k, ref v) in tool.env() {
if k == "PATH" {
new_path.extend(env::split_paths(v));
msvc_changed_path = true;
} else {
cmd.env(k, v);
}
}
}
}
if !msvc_changed_path {
if let Some(path) = env::var_os("PATH") {
new_path.extend(env::split_paths(&path));
}
}
cmd.env("PATH", env::join_paths(new_path).unwrap());
(linker.to_path_buf(), cmd)
}
pub fn remove(sess: &Session, path: &Path) {
if let Err(e) = fs::remove_file(path) {
sess.err(&format!("failed to remove {}: {}",
path.display(),
e));
}
}
/// Perform the linkage portion of the compilation phase. This will generate all
/// of the requested outputs for this compilation session.
......@@ -214,60 +152,6 @@ fn preserve_objects_for_their_debuginfo(sess: &Session) -> bool {
false
}
pub(crate) fn each_linked_rlib(sess: &Session,
info: &CrateInfo,
f: &mut dyn FnMut(CrateNum, &Path)) -> Result<(), String> {
let crates = info.used_crates_static.iter();
let fmts = sess.dependency_formats.borrow();
let fmts = fmts.get(&config::CrateType::Executable)
.or_else(|| fmts.get(&config::CrateType::Staticlib))
.or_else(|| fmts.get(&config::CrateType::Cdylib))
.or_else(|| fmts.get(&config::CrateType::ProcMacro));
let fmts = match fmts {
Some(f) => f,
None => return Err("could not find formats for rlibs".to_string())
};
for &(cnum, ref path) in crates {
match fmts.get(cnum.as_usize() - 1) {
Some(&Linkage::NotLinked) |
Some(&Linkage::IncludedFromDylib) => continue,
Some(_) => {}
None => return Err("could not find formats for rlibs".to_string())
}
let name = &info.crate_name[&cnum];
let path = match *path {
LibSource::Some(ref p) => p,
LibSource::MetadataOnly => {
return Err(format!("could not find rlib for: `{}`, found rmeta (metadata) file",
name))
}
LibSource::None => {
return Err(format!("could not find rlib for: `{}`", name))
}
};
f(cnum, &path);
}
Ok(())
}
/// Returns a boolean indicating whether the specified crate should be ignored
/// during LTO.
///
/// Crates ignored during LTO are not lumped together in the "massive object
/// file" that we create and are linked in their normal rlib states. See
/// comments below for what crates do not participate in LTO.
///
/// It's unusual for a crate to not participate in LTO. Typically only
/// compiler-specific and unstable crates have a reason to not participate in
/// LTO.
pub(crate) fn ignored_for_lto(sess: &Session, info: &CrateInfo, cnum: CrateNum) -> bool {
// If our target enables builtin function lowering in LLVM then the
// crates providing these functions don't participate in LTO (e.g.
// no_builtins or compiler builtins crates).
!sess.target.target.options.no_builtins &&
(info.compiler_builtins == Some(cnum) || info.is_no_builtins.contains(&cnum))
}
fn link_binary_output(sess: &Session,
codegen_results: &CodegenResults,
crate_type: config::CrateType,
......@@ -352,8 +236,11 @@ fn archive_config<'a>(sess: &'a Session,
/// building an `.rlib` (stomping over one another), or writing an `.rmeta` into a
/// directory being searched for `extern crate` (observing an incomplete file).
/// The returned path is the temporary file containing the complete metadata.
fn emit_metadata<'a>(sess: &'a Session, codegen_results: &CodegenResults, tmpdir: &TempDir)
-> PathBuf {
fn emit_metadata<'a>(
sess: &'a Session,
codegen_results: &CodegenResults,
tmpdir: &TempDir
) -> PathBuf {
let out_filename = tmpdir.path().join(METADATA_FILENAME);
let result = fs::write(&out_filename, &codegen_results.metadata.raw_data);
......@@ -575,69 +462,6 @@ fn print_native_static_libs(sess: &Session, all_native_libs: &[NativeLibrary]) {
}
}
pub fn linker_and_flavor(sess: &Session) -> (PathBuf, LinkerFlavor) {
fn infer_from(
sess: &Session,
linker: Option<PathBuf>,
flavor: Option<LinkerFlavor>,
) -> Option<(PathBuf, LinkerFlavor)> {
match (linker, flavor) {
(Some(linker), Some(flavor)) => Some((linker, flavor)),
// only the linker flavor is known; use the default linker for the selected flavor
(None, Some(flavor)) => Some((PathBuf::from(match flavor {
LinkerFlavor::Em => if cfg!(windows) { "emcc.bat" } else { "emcc" },
LinkerFlavor::Gcc => "cc",
LinkerFlavor::Ld => "ld",
LinkerFlavor::Msvc => "link.exe",
LinkerFlavor::Lld(_) => "lld",
}), flavor)),
(Some(linker), None) => {
let stem = linker.file_stem().and_then(|stem| stem.to_str()).unwrap_or_else(|| {
sess.fatal("couldn't extract file stem from specified linker");
}).to_owned();
let flavor = if stem == "emcc" {
LinkerFlavor::Em
} else if stem == "gcc" || stem.ends_with("-gcc") {
LinkerFlavor::Gcc
} else if stem == "ld" || stem == "ld.lld" || stem.ends_with("-ld") {
LinkerFlavor::Ld
} else if stem == "link" || stem == "lld-link" {
LinkerFlavor::Msvc
} else if stem == "lld" || stem == "rust-lld" {
LinkerFlavor::Lld(sess.target.target.options.lld_flavor)
} else {
// fall back to the value in the target spec
sess.target.target.linker_flavor
};
Some((linker, flavor))
},
(None, None) => None,
}
}
// linker and linker flavor specified via command line have precedence over what the target
// specification specifies
if let Some(ret) = infer_from(
sess,
sess.opts.cg.linker.clone(),
sess.opts.debugging_opts.linker_flavor,
) {
return ret;
}
if let Some(ret) = infer_from(
sess,
sess.target.target.options.linker.clone().map(PathBuf::from),
Some(sess.target.target.linker_flavor),
) {
return ret;
}
bug!("Not enough information provided to determine how to invoke the linker");
}
// Create a dynamic library or executable
//
// This will invoke the system linker/cc to create the resulting file. This
......
此差异已折叠。
......@@ -15,18 +15,14 @@
//! closure.
use attributes;
use common::{self, CodegenCx};
use consts;
use declare;
use llvm;
use monomorphize::Instance;
use type_of::LayoutLlvmExt;
use context::CodegenCx;
use value::Value;
use rustc_codegen_ssa::traits::*;
use rustc::hir::def_id::DefId;
use rustc::ty::{self, TypeFoldable};
use rustc::ty::layout::LayoutOf;
use rustc::ty::subst::Substs;
use rustc::ty::TypeFoldable;
use rustc::ty::layout::{LayoutOf, HasTyCtxt};
/// Codegens a reference to a fn/method item, monomorphizing and
/// inlining as it goes.
......@@ -39,7 +35,7 @@ pub fn get_fn(
cx: &CodegenCx<'ll, 'tcx>,
instance: Instance<'tcx>,
) -> &'ll Value {
let tcx = cx.tcx;
let tcx = cx.tcx();
debug!("get_fn(instance={:?})", instance);
......@@ -47,8 +43,8 @@ pub fn get_fn(
assert!(!instance.substs.has_escaping_bound_vars());
assert!(!instance.substs.has_param_types());
let sig = instance.fn_sig(cx.tcx);
if let Some(&llfn) = cx.instances.borrow().get(&instance) {
let sig = instance.fn_sig(cx.tcx());
if let Some(&llfn) = cx.instances().borrow().get(&instance) {
return llfn;
}
......@@ -57,9 +53,9 @@ pub fn get_fn(
// Create a fn pointer with the substituted signature.
let fn_ptr_ty = tcx.mk_fn_ptr(sig);
let llptrty = cx.layout_of(fn_ptr_ty).llvm_type(cx);
let llptrty = cx.backend_type(cx.layout_of(fn_ptr_ty));
let llfn = if let Some(llfn) = declare::get_declared_value(cx, &sym) {
let llfn = if let Some(llfn) = cx.get_declared_value(&sym) {
// This is subtle and surprising, but sometimes we have to bitcast
// the resulting fn pointer. The reason has to do with external
// functions. If you have two crates that both bind the same C
......@@ -83,16 +79,16 @@ pub fn get_fn(
// This can occur on either a crate-local or crate-external
// reference. It also occurs when testing libcore and in some
// other weird situations. Annoying.
if common::val_ty(llfn) != llptrty {
if cx.val_ty(llfn) != llptrty {
debug!("get_fn: casting {:?} to {:?}", llfn, llptrty);
consts::ptrcast(llfn, llptrty)
cx.static_ptrcast(llfn, llptrty)
} else {
debug!("get_fn: not casting pointer!");
llfn
}
} else {
let llfn = declare::declare_fn(cx, &sym, sig);
assert_eq!(common::val_ty(llfn), llptrty);
let llfn = cx.declare_fn(&sym, sig);
assert_eq!(cx.val_ty(llfn), llptrty);
debug!("get_fn: not casting pointer!");
if instance.def.is_inline(tcx) {
......@@ -204,35 +200,3 @@ pub fn get_fn(
llfn
}
pub fn resolve_and_get_fn(
cx: &CodegenCx<'ll, 'tcx>,
def_id: DefId,
substs: &'tcx Substs<'tcx>,
) -> &'ll Value {
get_fn(
cx,
ty::Instance::resolve(
cx.tcx,
ty::ParamEnv::reveal_all(),
def_id,
substs
).unwrap()
)
}
pub fn resolve_and_get_fn_for_vtable(
cx: &CodegenCx<'ll, 'tcx>,
def_id: DefId,
substs: &'tcx Substs<'tcx>,
) -> &'ll Value {
get_fn(
cx,
ty::Instance::resolve_for_vtable(
cx.tcx,
ty::ParamEnv::reveal_all(),
def_id,
substs
).unwrap()
)
}
此差异已折叠。
此差异已折叠。
......@@ -8,12 +8,12 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use super::{FunctionDebugContext, FunctionDebugContextData};
use rustc_codegen_ssa::debuginfo::{FunctionDebugContext, FunctionDebugContextData, MirDebugScope};
use super::metadata::file_metadata;
use super::utils::{DIB, span_start};
use llvm;
use llvm::debuginfo::DIScope;
use llvm::debuginfo::{DIScope, DISubprogram};
use common::CodegenCx;
use rustc::mir::{Mir, SourceScope};
......@@ -26,28 +26,13 @@
use syntax_pos::BytePos;
#[derive(Clone, Copy, Debug)]
pub struct MirDebugScope<'ll> {
pub scope_metadata: Option<&'ll DIScope>,
// Start and end offsets of the file to which this DIScope belongs.
// These are used to quickly determine whether some span refers to the same file.
pub file_start_pos: BytePos,
pub file_end_pos: BytePos,
}
impl MirDebugScope<'ll> {
pub fn is_valid(&self) -> bool {
self.scope_metadata.is_some()
}
}
/// Produce DIScope DIEs for each MIR Scope which has variables defined in it.
/// If debuginfo is disabled, the returned vector is empty.
pub fn create_mir_scopes(
cx: &CodegenCx<'ll, '_>,
mir: &Mir,
debug_context: &FunctionDebugContext<'ll>,
) -> IndexVec<SourceScope, MirDebugScope<'ll>> {
debug_context: &FunctionDebugContext<&'ll DISubprogram>,
) -> IndexVec<SourceScope, MirDebugScope<&'ll DIScope>> {
let null_scope = MirDebugScope {
scope_metadata: None,
file_start_pos: BytePos(0),
......@@ -82,9 +67,9 @@ pub fn create_mir_scopes(
fn make_mir_scope(cx: &CodegenCx<'ll, '_>,
mir: &Mir,
has_variables: &BitSet<SourceScope>,
debug_context: &FunctionDebugContextData<'ll>,
debug_context: &FunctionDebugContextData<&'ll DISubprogram>,
scope: SourceScope,
scopes: &mut IndexVec<SourceScope, MirDebugScope<'ll>>) {
scopes: &mut IndexVec<SourceScope, MirDebugScope<&'ll DIScope>>) {
if scopes[scope].is_valid() {
return;
}
......
......@@ -12,24 +12,23 @@
use llvm;
use common::{C_bytes, CodegenCx, C_i32};
use common::CodegenCx;
use builder::Builder;
use declare;
use rustc::session::config::DebugInfo;
use type_::Type;
use value::Value;
use rustc_codegen_ssa::traits::*;
use syntax::attr;
/// Inserts a side-effect free instruction sequence that makes sure that the
/// .debug_gdb_scripts global is referenced, so it isn't removed by the linker.
pub fn insert_reference_to_gdb_debug_scripts_section_global(bx: &Builder) {
if needs_gdb_debug_scripts_section(bx.cx) {
let gdb_debug_scripts_section = get_or_insert_gdb_debug_scripts_section_global(bx.cx);
pub fn insert_reference_to_gdb_debug_scripts_section_global(bx: &mut Builder) {
if needs_gdb_debug_scripts_section(bx.cx()) {
let gdb_debug_scripts_section = get_or_insert_gdb_debug_scripts_section_global(bx.cx());
// Load just the first byte as that's all that's necessary to force
// LLVM to keep around the reference to the global.
let indices = [C_i32(bx.cx, 0), C_i32(bx.cx, 0)];
let indices = [bx.cx().const_i32(0), bx.cx().const_i32(0)];
let element = bx.inbounds_gep(gdb_debug_scripts_section, &indices);
let volative_load_instruction = bx.volatile_load(element);
unsafe {
......@@ -55,15 +54,15 @@ pub fn get_or_insert_gdb_debug_scripts_section_global(cx: &CodegenCx<'ll, '_>)
let section_contents = b"\x01gdb_load_rust_pretty_printers.py\0";
unsafe {
let llvm_type = Type::array(Type::i8(cx),
let llvm_type = cx.type_array(cx.type_i8(),
section_contents.len() as u64);
let section_var = declare::define_global(cx, section_var_name,
let section_var = cx.define_global(section_var_name,
llvm_type).unwrap_or_else(||{
bug!("symbol `{}` is already defined", section_var_name)
});
llvm::LLVMSetSection(section_var, section_name.as_ptr() as *const _);
llvm::LLVMSetInitializer(section_var, C_bytes(cx, section_contents));
llvm::LLVMSetInitializer(section_var, cx.const_bytes(section_contents));
llvm::LLVMSetGlobalConstant(section_var, llvm::True);
llvm::LLVMSetUnnamedAddr(section_var, llvm::True);
llvm::LLVMRustSetLinkage(section_var, llvm::Linkage::LinkOnceODRLinkage);
......
......@@ -17,6 +17,7 @@
use super::namespace::mangled_name_of_instance;
use super::type_names::compute_debuginfo_type_name;
use super::{CrateDebugContext};
use rustc_codegen_ssa::traits::*;
use abi;
use value::Value;
......@@ -32,7 +33,7 @@
use rustc::ich::NodeIdHashingMode;
use rustc_data_structures::fingerprint::Fingerprint;
use rustc::ty::Instance;
use common::{CodegenCx, C_u64};
use common::CodegenCx;
use rustc::ty::{self, AdtKind, ParamEnv, Ty, TyCtxt};
use rustc::ty::layout::{self, Align, HasDataLayout, Integer, IntegerExt, LayoutOf,
PrimitiveExt, Size, TyLayout};
......@@ -1810,7 +1811,7 @@ fn set_members_of_composite_type(cx: &CodegenCx<'ll, '_>,
member_description.offset.bits(),
match member_description.discriminant {
None => None,
Some(value) => Some(C_u64(cx, value)),
Some(value) => Some(cx.const_u64(value)),
},
member_description.flags,
member_description.type_metadata))
......@@ -1966,22 +1967,6 @@ pub fn create_global_var_metadata(
}
}
// Creates an "extension" of an existing DIScope into another file.
pub fn extend_scope_to_file(
cx: &CodegenCx<'ll, '_>,
scope_metadata: &'ll DIScope,
file: &syntax_pos::SourceFile,
defining_crate: CrateNum,
) -> &'ll DILexicalBlock {
let file_metadata = file_metadata(cx, &file.name, defining_crate);
unsafe {
llvm::LLVMRustDIBuilderCreateLexicalBlockFile(
DIB(cx),
scope_metadata,
file_metadata)
}
}
/// Creates debug information for the given vtable, which is for the
/// given type.
///
......@@ -2037,3 +2022,19 @@ pub fn create_vtable_metadata(
0);
}
}
// Creates an "extension" of an existing DIScope into another file.
pub fn extend_scope_to_file(
cx: &CodegenCx<'ll, '_>,
scope_metadata: &'ll DIScope,
file: &syntax_pos::SourceFile,
defining_crate: CrateNum,
) -> &'ll DILexicalBlock {
let file_metadata = file_metadata(cx, &file.name, defining_crate);
unsafe {
llvm::LLVMRustDIBuilderCreateLexicalBlockFile(
DIB(cx),
scope_metadata,
file_metadata)
}
}
......@@ -12,11 +12,12 @@
use super::utils::{debug_context, span_start};
use super::metadata::UNKNOWN_COLUMN_NUMBER;
use super::FunctionDebugContext;
use rustc_codegen_ssa::debuginfo::FunctionDebugContext;
use llvm;
use llvm::debuginfo::DIScope;
use builder::Builder;
use rustc_codegen_ssa::traits::*;
use libc::c_uint;
use syntax_pos::{Span, Pos};
......@@ -24,8 +25,8 @@
/// Sets the current debug location at the beginning of the span.
///
/// Maps to a call to llvm::LLVMSetCurrentDebugLocation(...).
pub fn set_source_location(
debug_context: &FunctionDebugContext<'ll>,
pub fn set_source_location<D>(
debug_context: &FunctionDebugContext<D>,
bx: &Builder<'_, 'll, '_>,
scope: Option<&'ll DIScope>,
span: Span,
......@@ -40,8 +41,8 @@ pub fn set_source_location(
};
let dbg_loc = if function_debug_context.source_locations_enabled.get() {
debug!("set_source_location: {}", bx.sess().source_map().span_to_string(span));
let loc = span_start(bx.cx, span);
debug!("set_source_location: {}", bx.cx().sess().source_map().span_to_string(span));
let loc = span_start(bx.cx(), span);
InternalDebugLocation::new(scope.unwrap(), loc.line, loc.col.to_usize())
} else {
UnknownLocation
......@@ -49,18 +50,6 @@ pub fn set_source_location(
set_debug_location(bx, dbg_loc);
}
/// Enables emitting source locations for the given functions.
///
/// Since we don't want source locations to be emitted for the function prelude,
/// they are disabled when beginning to codegen a new function. This functions
/// switches source location emitting on and must therefore be called before the
/// first real statement/expression of the function is codegened.
pub fn start_emitting_source_locations(dbg_context: &FunctionDebugContext<'ll>) {
if let FunctionDebugContext::RegularContext(ref data) = *dbg_context {
data.source_locations_enabled.set(true);
}
}
#[derive(Copy, Clone, PartialEq)]
pub enum InternalDebugLocation<'ll> {
......@@ -78,13 +67,16 @@ pub fn new(scope: &'ll DIScope, line: usize, col: usize) -> Self {
}
}
pub fn set_debug_location(bx: &Builder<'_, 'll, '_>, debug_location: InternalDebugLocation<'ll>) {
pub fn set_debug_location(
bx: &Builder<'_, 'll, '_>,
debug_location: InternalDebugLocation<'ll>
) {
let metadata_node = match debug_location {
KnownLocation { scope, line, col } => {
// For MSVC, set the column number to zero.
// Otherwise, emit it. This mimics clang behaviour.
// See discussion in https://github.com/rust-lang/rust/issues/42921
let col_used = if bx.cx.sess().target.target.options.is_like_msvc {
let col_used = if bx.cx().sess().target.target.options.is_like_msvc {
UNKNOWN_COLUMN_NUMBER
} else {
col as c_uint
......@@ -93,7 +85,7 @@ pub fn set_debug_location(bx: &Builder<'_, 'll, '_>, debug_location: InternalDeb
unsafe {
Some(llvm::LLVMRustDIBuilderCreateDebugLocation(
debug_context(bx.cx).llcontext,
debug_context(bx.cx()).llcontext,
line as c_uint,
col_used,
scope,
......
......@@ -14,6 +14,7 @@
use rustc::hir::def_id::DefId;
use rustc::ty::subst::Substs;
use rustc::ty::{self, Ty};
use rustc_codegen_ssa::traits::*;
use rustc::hir;
......
......@@ -19,6 +19,7 @@
use llvm;
use llvm::debuginfo::{DIScope, DIBuilder, DIDescriptor, DIArray};
use common::{CodegenCx};
use rustc_codegen_ssa::traits::*;
use syntax_pos::{self, Span};
......
......@@ -47,37 +47,4 @@ fn main() {
```
"##,
E0668: r##"
Malformed inline assembly rejected by LLVM.
LLVM checks the validity of the constraints and the assembly string passed to
it. This error implies that LLVM seems something wrong with the inline
assembly call.
In particular, it can happen if you forgot the closing bracket of a register
constraint (see issue #51430):
```ignore (error-emitted-at-codegen-which-cannot-be-handled-by-compile_fail)
#![feature(asm)]
fn main() {
let rax: u64;
unsafe {
asm!("" :"={rax"(rax));
println!("Accumulator is: {}", rax);
}
}
```
"##,
E0669: r##"
Cannot convert inline assembly operand to a single LLVM value.
This error usually happens when trying to pass in a value to an input inline
assembly operand that is actually a pair of values. In particular, this can
happen when trying to pass in a slice, for instance a `&str`. In Rust, these
values are represented internally as a pair of values, the pointer and its
length. When passed as an input operand, this pair of values can not be
coerced into a register and thus we must fail with an error.
"##,
}
此差异已折叠。
此差异已折叠。
[package]
authors = ["The Rust Project Developers"]
name = "rustc_codegen_ssa"
version = "0.0.0"
[lib]
name = "rustc_codegen_ssa"
path = "lib.rs"
test = false
[dependencies]
cc = "1.0.1"
num_cpus = "1.0"
rustc-demangle = "0.1.4"
memmap = "0.6"
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
// Copyright 2018 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.
pub mod write;
pub mod linker;
pub mod lto;
pub mod link;
pub mod command;
pub mod symbol_export;
pub mod archive;
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册