-
Notifications
You must be signed in to change notification settings - Fork 13.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make librustc_codegen_llvm
aware of LLVM address spaces.
#51576
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
librustc_codegen_llvm
aware of LLVM address spaces.
In order to not require overloading functions based on their argument's address space (among other things), we require the presence of a "flat" (ie an address space which is shared with every other address space) address space. This isn't exposed in any way to Rust code. This just makes Rust compatible with LLVM target machines which, for example, place allocas in a different address space. `amdgcn-amd-amdhsa-amdgiz` is a specific example, which places allocas in address space 5 or the private (at the work item level) address space.
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
pub use self::Integer::*; | ||
pub use self::Primitive::*; | ||
|
||
use spec::Target; | ||
use spec::{Target, AddrSpaceIdx, }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: you have these There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok |
||
|
||
use std::fmt; | ||
use std::ops::{Add, Deref, Sub, Mul, AddAssign, Range, RangeInclusive}; | ||
|
@@ -22,13 +22,16 @@ pub struct TargetDataLayout { | |
pub i128_align: AbiAndPrefAlign, | ||
pub f32_align: AbiAndPrefAlign, | ||
pub f64_align: AbiAndPrefAlign, | ||
pub pointers: Vec<Option<(Size, AbiAndPrefAlign)>>, | ||
pub pointer_size: Size, | ||
pub pointer_align: AbiAndPrefAlign, | ||
pub aggregate_align: AbiAndPrefAlign, | ||
|
||
/// Alignments for vector types. | ||
pub vector_align: Vec<(Size, AbiAndPrefAlign)>, | ||
|
||
pub alloca_address_space: AddrSpaceIdx, | ||
|
||
pub instruction_address_space: u32, | ||
} | ||
|
||
|
@@ -46,9 +49,11 @@ impl Default for TargetDataLayout { | |
i128_align: AbiAndPrefAlign { abi: align(32), pref: align(64) }, | ||
f32_align: AbiAndPrefAlign::new(align(32)), | ||
f64_align: AbiAndPrefAlign::new(align(64)), | ||
pointers: vec![], | ||
pointer_size: Size::from_bits(64), | ||
pointer_align: AbiAndPrefAlign::new(align(64)), | ||
aggregate_align: AbiAndPrefAlign { abi: align(0), pref: align(64) }, | ||
alloca_address_space: Default::default(), | ||
vector_align: vec![ | ||
(Size::from_bits(64), AbiAndPrefAlign::new(align(64))), | ||
(Size::from_bits(128), AbiAndPrefAlign::new(align(128))), | ||
|
@@ -60,14 +65,6 @@ impl Default for TargetDataLayout { | |
|
||
impl TargetDataLayout { | ||
pub fn parse(target: &Target) -> Result<TargetDataLayout, String> { | ||
// Parse an address space index from a string. | ||
let parse_address_space = |s: &str, cause: &str| { | ||
s.parse::<u32>().map_err(|err| { | ||
format!("invalid address space `{}` for `{}` in \"data-layout\": {}", | ||
s, cause, err) | ||
}) | ||
}; | ||
|
||
// Parse a bit count from a string. | ||
let parse_bits = |s: &str, kind: &str, cause: &str| { | ||
s.parse::<u64>().map_err(|err| { | ||
|
@@ -100,23 +97,38 @@ impl TargetDataLayout { | |
}) | ||
}; | ||
|
||
fn resize_and_set<T>(vec: &mut Vec<T>, idx: usize, v: T) | ||
where T: Default, | ||
{ | ||
while idx >= vec.len() { | ||
vec.push(T::default()); | ||
} | ||
|
||
vec[idx] = v; | ||
} | ||
|
||
let mut dl = TargetDataLayout::default(); | ||
let mut i128_align_src = 64; | ||
for spec in target.data_layout.split('-') { | ||
match spec.split(':').collect::<Vec<_>>()[..] { | ||
["e"] => dl.endian = Endian::Little, | ||
["E"] => dl.endian = Endian::Big, | ||
[p] if p.starts_with("P") => { | ||
dl.instruction_address_space = parse_address_space(&p[1..], "P")? | ||
} | ||
["a", ref a..] => dl.aggregate_align = align(a, "a")?, | ||
["f32", ref a..] => dl.f32_align = align(a, "f32")?, | ||
["f64", ref a..] => dl.f64_align = align(a, "f64")?, | ||
[p @ "p", s, ref a..] | [p @ "p0", s, ref a..] => { | ||
dl.pointer_size = size(s, p)?; | ||
dl.pointer_align = align(a, p)?; | ||
} | ||
[s, ref a..] if s.starts_with("i") => { | ||
resize_and_set(&mut dl.pointers, 0, Some((dl.pointer_size, | ||
dl.pointer_align))); | ||
}, | ||
[p, s, ref a..] if p.starts_with('p') => { | ||
let idx = parse_bits(&p[1..], "u32", "address space index")? as usize; | ||
let size = size(s, p)?; | ||
let align = align(a, p)?; | ||
resize_and_set(&mut dl.pointers, idx, Some((size, align))); | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would we lose anything by enforcing that all address spaces have the same size & alignment for pointers? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, AMDGPU targets use non-uniform pointer sizes/alignments. Perhaps we don't need to keep the sizes/alignments around after this function returns, but I'd argue that it isn't worth the effort to remove it just for this PR: I'll just be adding it right back in and it's so trivial compared to the compiler as a whole. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why would you be adding it back in? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Vega10 has a 64kB LDS (LDS is an explicitly managed CU (compute unit, conceptually similar to a thread/core) local cache) which has throughput in excess of 13TB/s (according to AMD documentation). Vs 484GB/s to Vega10's HDM2 main memory. Vega10 also has a cross CU GDS cache, but I don't have figures for its throughput. Of AMD Gpus, all GCN archs feature a LDS, and I'm reasonably certain Nvidia has something similar. Use of these memories is absolutely critical for maximizing GPGPU performance in memory throughput constrained code. To use LDS, what is typically done is a global variable is declared and the input is copied into it at the start of a kernel, after which a barrier is executed before the real work of the kernel utilizing the LDS global is performed. On |
||
[s, ref a..] if s.starts_with("i") => { | ||
let bits = match s[1..].parse::<u64>() { | ||
Ok(bits) => bits, | ||
Err(_) => { | ||
|
@@ -149,7 +161,13 @@ impl TargetDataLayout { | |
} | ||
// No existing entry, add a new one. | ||
dl.vector_align.push((v_size, a)); | ||
} | ||
}, | ||
[s, ..] if s.starts_with("A") => { | ||
// default alloca address space | ||
let idx = parse_bits(&s[1..], "u32", | ||
"default alloca address space")? as u32; | ||
dl.alloca_address_space = AddrSpaceIdx(idx); | ||
}, | ||
_ => {} // Ignore everything else. | ||
} | ||
} | ||
|
@@ -171,9 +189,37 @@ impl TargetDataLayout { | |
dl.pointer_size.bits(), target.target_pointer_width)); | ||
} | ||
|
||
// We don't specialize pointer sizes for specific address spaces, | ||
// so enforce that the default address space can hold all the bits | ||
// of any other spaces. Similar for alignment. | ||
{ | ||
let ptrs_iter = dl.pointers.iter().enumerate() | ||
.filter_map(|(idx, ptrs)| { | ||
ptrs.map(|(s, a)| (idx, s, a) ) | ||
}); | ||
for (idx, size, align) in ptrs_iter { | ||
if size > dl.pointer_size { | ||
return Err(format!("Address space {} pointer is bigger than the default \ | ||
pointer: {} vs {}", | ||
idx, size.bits(), dl.pointer_size.bits())); | ||
} | ||
if align.abi > dl.pointer_align.abi { | ||
return Err(format!("Address space {} pointer alignment is bigger than the \ | ||
default pointer: {} vs {}", | ||
idx, align.abi.bits(), dl.pointer_align.abi.bits())); | ||
} | ||
} | ||
} | ||
|
||
Ok(dl) | ||
} | ||
|
||
pub fn pointer_info(&self, addr_space: AddrSpaceIdx) -> (Size, AbiAndPrefAlign) { | ||
self.pointers.get(addr_space.0 as usize) | ||
.and_then(|&v| v ) | ||
.unwrap_or((self.pointer_size, self.pointer_align)) | ||
} | ||
|
||
/// Return exclusive upper bound on object size. | ||
/// | ||
/// The theoretical maximum object size is defined as the maximum positive `isize` value. | ||
|
@@ -940,3 +986,82 @@ impl<'a, Ty> TyLayout<'a, Ty> { | |
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
use spec::{Target, TargetTriple, }; | ||
|
||
#[test] | ||
fn pointer_size_align() { | ||
// amdgcn-amd-amdhsa-amdgiz | ||
const DL: &'static str = "e-p:64:64-p1:64:64-p2:64:64-p3:32:32-\ | ||
p4:32:32-p5:32:32-i64:64-v16:16-v24:32-\ | ||
v32:32-v48:64-v96:128-v192:256-v256:256-\ | ||
v512:512-v1024:1024-v2048:2048-n32:64-A5"; | ||
|
||
// Doesn't need to be real... | ||
let triple = TargetTriple::TargetTriple("x86_64-unknown-linux-gnu".into()); | ||
let mut target = Target::search(&triple).unwrap(); | ||
target.data_layout = DL.into(); | ||
|
||
let dl = TargetDataLayout::parse(&target); | ||
assert!(dl.is_ok()); | ||
let dl = dl.unwrap(); | ||
|
||
let default = (dl.pointer_size, dl.pointer_align); | ||
|
||
let thirty_two_size = Size::from_bits(32); | ||
let thirty_two_align = AbiAndPrefAlign::new(Align::from_bits(32).unwrap()); | ||
let thirty_two = (thirty_two_size, thirty_two_align); | ||
let sixty_four_size = Size::from_bits(64); | ||
let sixty_four_align = AbiAndPrefAlign::new(Align::from_bits(64).unwrap()); | ||
let sixty_four = (sixty_four_size, sixty_four_align); | ||
|
||
assert_eq!(dl.pointer_info(AddrSpaceIdx(0)), default); | ||
assert_eq!(dl.pointer_info(AddrSpaceIdx(0)), sixty_four); | ||
assert_eq!(dl.pointer_info(AddrSpaceIdx(1)), sixty_four); | ||
assert_eq!(dl.pointer_info(AddrSpaceIdx(2)), sixty_four); | ||
assert_eq!(dl.pointer_info(AddrSpaceIdx(3)), thirty_two); | ||
assert_eq!(dl.pointer_info(AddrSpaceIdx(4)), thirty_two); | ||
assert_eq!(dl.pointer_info(AddrSpaceIdx(5)), thirty_two); | ||
|
||
// unknown address spaces need to be the same as the default: | ||
assert_eq!(dl.pointer_info(AddrSpaceIdx(7)), default); | ||
} | ||
|
||
#[test] | ||
fn default_is_biggest() { | ||
// Note p1 is 128 bits. | ||
const DL: &'static str = "e-p:64:64-p1:128:128-p2:64:64-p3:32:32-\ | ||
p4:32:32-p5:32:32-i64:64-v16:16-v24:32-\ | ||
v32:32-v48:64-v96:128-v192:256-v256:256-\ | ||
v512:512-v1024:1024-v2048:2048-n32:64-A5"; | ||
|
||
// Doesn't need to be real... | ||
let triple = TargetTriple::TargetTriple("x86_64-unknown-linux-gnu".into()); | ||
let mut target = Target::search(&triple).unwrap(); | ||
target.data_layout = DL.into(); | ||
|
||
assert!(TargetDataLayout::parse(&target).is_err()); | ||
} | ||
#[test] | ||
fn alloca_addr_space() { | ||
// amdgcn-amd-amdhsa-amdgiz | ||
const DL: &'static str = "e-p:64:64-p1:64:64-p2:64:64-p3:32:32-\ | ||
p4:32:32-p5:32:32-i64:64-v16:16-v24:32-\ | ||
v32:32-v48:64-v96:128-v192:256-v256:256-\ | ||
v512:512-v1024:1024-v2048:2048-n32:64-A5"; | ||
|
||
// Doesn't need to be real... | ||
let triple = TargetTriple::TargetTriple("x86_64-unknown-linux-gnu".into()); | ||
let mut target = Target::search(&triple).unwrap(); | ||
target.data_layout = DL.into(); | ||
|
||
let dl = TargetDataLayout::parse(&target); | ||
assert!(dl.is_ok()); | ||
let dl = dl.unwrap(); | ||
|
||
assert_eq!(dl.alloca_address_space, AddrSpaceIdx(5)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,11 +35,12 @@ | |
//! to the list specified by the target, rather than replace. | ||
use serialize::json::{Json, ToJson}; | ||
use std::collections::BTreeMap; | ||
use std::collections::{BTreeMap, BTreeSet}; | ||
use std::default::Default; | ||
use std::{fmt, io}; | ||
use std::path::{Path, PathBuf}; | ||
use std::str::FromStr; | ||
use std::ops::{Deref, DerefMut, }; | ||
use spec::abi::{Abi, lookup as lookup_abi}; | ||
|
||
pub mod abi; | ||
|
@@ -260,6 +261,158 @@ impl ToJson for MergeFunctions { | |
pub type LinkArgs = BTreeMap<LinkerFlavor, Vec<String>>; | ||
pub type TargetResult = Result<Target, String>; | ||
|
||
#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] | ||
pub struct AddrSpaceIdx(pub u32); | ||
impl Default for AddrSpaceIdx { | ||
fn default() -> Self { | ||
AddrSpaceIdx(0) | ||
} | ||
} | ||
impl fmt::Display for AddrSpaceIdx { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
write!(f, "{}", self.0) | ||
} | ||
} | ||
impl FromStr for AddrSpaceIdx { | ||
type Err = <u32 as FromStr>::Err; | ||
fn from_str(s: &str) -> Result<AddrSpaceIdx, Self::Err> { | ||
Ok(AddrSpaceIdx(u32::from_str(s)?)) | ||
} | ||
} | ||
|
||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] | ||
pub enum AddrSpaceKind { | ||
Flat, | ||
Alloca, | ||
/// aka constant | ||
ReadOnly, | ||
/// aka global | ||
ReadWrite, | ||
Named(String), | ||
} | ||
|
||
impl FromStr for AddrSpaceKind { | ||
type Err = String; | ||
fn from_str(s: &str) -> Result<AddrSpaceKind, String> { | ||
Ok(match s { | ||
"flat" => AddrSpaceKind::Flat, | ||
"alloca" => AddrSpaceKind::Alloca, | ||
"readonly" => AddrSpaceKind::ReadOnly, | ||
"readwrite" => AddrSpaceKind::ReadWrite, | ||
named => AddrSpaceKind::Named(named.into()), | ||
}) | ||
} | ||
} | ||
impl fmt::Display for AddrSpaceKind { | ||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
write!(f, "{}", match self { | ||
&AddrSpaceKind::Flat => "flat", | ||
&AddrSpaceKind::Alloca => "alloca", | ||
&AddrSpaceKind::ReadOnly => "readonly", | ||
&AddrSpaceKind::ReadWrite => "readwrite", | ||
&AddrSpaceKind::Named(ref s) => s, | ||
}) | ||
} | ||
} | ||
impl ToJson for AddrSpaceKind { | ||
fn to_json(&self) -> Json { | ||
Json::String(format!("{}", self)) | ||
} | ||
} | ||
|
||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] | ||
pub struct AddrSpaceProps { | ||
pub index: AddrSpaceIdx, | ||
/// Indicates which addr spaces this addr space can be addrspacecast-ed to. | ||
pub shared_with: BTreeSet<AddrSpaceKind>, | ||
} | ||
|
||
impl AddrSpaceProps { | ||
pub fn from_json(json: &Json) -> Result<Self, String> { | ||
let index = json.find("index").and_then(|v| v.as_u64() ) | ||
.ok_or_else(|| { | ||
"invalid address space index, expected an unsigned integer" | ||
})?; | ||
|
||
let mut shared_with = vec![]; | ||
if let Some(shared) = json.find("shared-with").and_then(|v| v.as_array() ) { | ||
for s in shared { | ||
let s = s.as_string() | ||
.ok_or_else(|| { | ||
"expected string for address space kind" | ||
})?; | ||
|
||
let kind = AddrSpaceKind::from_str(s)?; | ||
shared_with.push(kind); | ||
} | ||
} | ||
|
||
Ok(AddrSpaceProps { | ||
index: AddrSpaceIdx(index as u32), | ||
shared_with: shared_with.into_iter().collect(), | ||
}) | ||
} | ||
} | ||
impl ToJson for AddrSpaceProps { | ||
fn to_json(&self) -> Json { | ||
let mut obj = BTreeMap::new(); | ||
obj.insert("index".to_string(), self.index.0.to_json()); | ||
let mut shared_with = vec![]; | ||
for sw in self.shared_with.iter() { | ||
shared_with.push(sw.to_json()); | ||
} | ||
obj.insert("shared-with".to_string(), Json::Array(shared_with)); | ||
|
||
Json::Object(obj) | ||
} | ||
} | ||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] | ||
pub struct AddrSpaces(pub BTreeMap<AddrSpaceKind, AddrSpaceProps>); | ||
impl Deref for AddrSpaces { | ||
type Target = BTreeMap<AddrSpaceKind, AddrSpaceProps>; | ||
fn deref(&self) -> &Self::Target { &self.0 } | ||
} | ||
impl DerefMut for AddrSpaces { | ||
fn deref_mut(&mut self) -> &mut BTreeMap<AddrSpaceKind, AddrSpaceProps> { &mut self.0 } | ||
} | ||
impl ToJson for AddrSpaces { | ||
fn to_json(&self) -> Json { | ||
let obj = self.iter() | ||
.map(|(k, v)| { | ||
(format!("{}", k), v.to_json()) | ||
}) | ||
.collect(); | ||
Json::Object(obj) | ||
} | ||
} | ||
impl Default for AddrSpaces { | ||
fn default() -> Self { | ||
let mut asp = BTreeMap::new(); | ||
|
||
let kinds = vec![AddrSpaceKind::ReadOnly, | ||
AddrSpaceKind::ReadWrite, | ||
AddrSpaceKind::Alloca, | ||
AddrSpaceKind::Flat, ]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing |
||
|
||
let insert = |asp: &mut BTreeMap<_, _>, kind, idx| { | ||
let props = AddrSpaceProps { | ||
index: idx, | ||
shared_with: kinds.clone() | ||
.into_iter() | ||
.filter(|k| *k != kind) | ||
.collect(), | ||
}; | ||
assert!(asp.insert(kind, props).is_none()); | ||
}; | ||
|
||
for kind in kinds.iter() { | ||
insert(&mut asp, kind.clone(), Default::default()); | ||
} | ||
|
||
AddrSpaces(asp) | ||
} | ||
} | ||
|
||
macro_rules! supported_targets { | ||
( $(($triple:expr, $module:ident),)+ ) => ( | ||
$(mod $module;)* | ||
|
@@ -732,6 +885,11 @@ pub struct TargetOptions { | |
/// the usual logic to figure this out from the crate itself. | ||
pub override_export_symbols: Option<Vec<String>>, | ||
|
||
/// Description of all address spaces and how they are shared with one another. | ||
/// Defaults to a single, flat, address space. Note it is generally assumed that | ||
/// the address space `0` is your flat address space. | ||
pub addr_spaces: AddrSpaces, | ||
|
||
/// Determines how or whether the MergeFunctions LLVM pass should run for | ||
/// this target. Either "disabled", "trampolines", or "aliases". | ||
/// The MergeFunctions pass is generally useful, but some targets may need | ||
|
@@ -821,6 +979,7 @@ impl Default for TargetOptions { | |
requires_uwtable: false, | ||
simd_types_indirect: true, | ||
override_export_symbols: None, | ||
addr_spaces: Default::default(), | ||
merge_functions: MergeFunctions::Aliases, | ||
} | ||
} | ||
|
@@ -1051,6 +1210,16 @@ impl Target { | |
} | ||
} | ||
} ); | ||
($key_name:ident, addr_spaces) => ( { | ||
let name = (stringify!($key_name)).replace("_", "-"); | ||
if let Some(obj) = obj.find(&name[..]).and_then(|o| o.as_object() ) { | ||
for (k, v) in obj { | ||
let k = AddrSpaceKind::from_str(&k).unwrap(); | ||
let props = AddrSpaceProps::from_json(v)?; | ||
base.options.$key_name.insert(k, props); | ||
} | ||
} | ||
} ); | ||
} | ||
|
||
key!(is_builtin, bool); | ||
|
@@ -1126,6 +1295,7 @@ impl Target { | |
key!(requires_uwtable, bool); | ||
key!(simd_types_indirect, bool); | ||
key!(override_export_symbols, opt_list); | ||
key!(addr_spaces, addr_spaces); | ||
key!(merge_functions, MergeFunctions)?; | ||
|
||
if let Some(array) = obj.find("abi-blacklist").and_then(Json::as_array) { | ||
|
@@ -1338,6 +1508,7 @@ impl ToJson for Target { | |
target_option_val!(requires_uwtable); | ||
target_option_val!(simd_types_indirect); | ||
target_option_val!(override_export_symbols); | ||
target_option_val!(addr_spaces); | ||
target_option_val!(merge_functions); | ||
|
||
if default.abi_blacklist != self.options.abi_blacklist { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this not always
0
? Is there anything guaranteed about0
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0
is always the default (therefore always present), but not necessarily theflat
space. I think a lot of code elsewhere assumes this (and it's true for AMDGPU), but I'm pretty sure it can be false.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if you enforce it when parsing the
spec
file? Then we wouldn't just be blindly assuming, and we can keep a lot of code simpler.