Skip to main content

pliron_llvm/
data_layout.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) The pliron contributors
3
4//! Wrapper LLVM functions to get the type sizes and alignment for a target.
5
6use pliron::{
7    arg_err_noloc, arg_error_noloc, builtin::ops::ModuleOp, context::Context, printable::Printable,
8    result::Result, r#type::TypeHandle,
9};
10
11use crate::{
12    attributes::get_data_layout,
13    llvm_sys::{
14        core::{LLVMContext, LLVMType, llvm_type_is_sized},
15        target::LLVMTargetData,
16    },
17    to_llvm_ir::{TypeConversionContext, convert_type},
18};
19
20#[derive(Debug, thiserror::Error)]
21pub enum DataLayoutErr {
22    #[error("Cannot get the data layout of the host: {0}")]
23    NoHostLayout(String),
24    #[error("Type {0} has no size, and thus no layout")]
25    UnsizedType(String),
26}
27
28/// Target specific data layout provided by LLVM.
29pub struct DataLayout {
30    llvm_ctx: LLVMContext,
31    target_data: LLVMTargetData,
32    types: TypeConversionContext,
33}
34
35impl DataLayout {
36    /// Build [DataLayout] as described by `layout`.
37    ///
38    /// **Warning**: Aborts if `layout` is not a valid data layout string.
39    pub fn new(layout: &str) -> Self {
40        Self {
41            llvm_ctx: LLVMContext::default(),
42            target_data: LLVMTargetData::new(layout),
43            types: TypeConversionContext::default(),
44        }
45    }
46
47    /// Build [DataLayout] of *this* (the host) machine.
48    pub fn host() -> Result<Self> {
49        let target_data = LLVMTargetData::host()
50            .map_err(|err| arg_error_noloc!(DataLayoutErr::NoHostLayout(err)))?;
51        Ok(Self {
52            llvm_ctx: LLVMContext::default(),
53            target_data,
54            types: TypeConversionContext::default(),
55        })
56    }
57
58    /// Build [DataLayout] based on a `module`'s layout, if it has one.
59    /// Falls back to [Self::host] if `module` doesn't have a layout set.
60    pub fn from_module_layout(ctx: &Context, module: ModuleOp) -> Result<Self> {
61        match get_data_layout(ctx, module) {
62            Some(layout) if !layout.is_empty() => Ok(Self::new(&layout)),
63            _ => Self::host(),
64        }
65    }
66
67    /// The data layout string of this layout.
68    pub fn string_representation(&self) -> String {
69        self.target_data.copy_string_rep_of_target_data()
70    }
71
72    /// The number of bits that `ty` holds.
73    pub fn type_size_in_bits(&mut self, ctx: &Context, ty: TypeHandle) -> Result<u64> {
74        let ty = self.llvm_type(ctx, ty)?;
75        Ok(self.target_data.size_of_type_in_bits(ty))
76    }
77
78    /// The number of bytes that the data of `ty` uses.
79    pub fn type_store_size(&mut self, ctx: &Context, ty: TypeHandle) -> Result<u64> {
80        let ty = self.llvm_type(ctx, ty)?;
81        Ok(self.target_data.store_size_of_type(ty))
82    }
83
84    /// The number of bytes that one element of an array of `ty` uses.
85    pub fn type_alloc_size(&mut self, ctx: &Context, ty: TypeHandle) -> Result<u64> {
86        let ty = self.llvm_type(ctx, ty)?;
87        Ok(self.target_data.abi_size_of_type(ty))
88    }
89
90    /// The alignment in bytes of `ty`, provided by the ABI.
91    pub fn abi_type_align(&mut self, ctx: &Context, ty: TypeHandle) -> Result<u32> {
92        let ty = self.llvm_type(ctx, ty)?;
93        Ok(self.target_data.abi_alignment_of_type(ty))
94    }
95
96    /// Does an array of `ty` hold its elements with no padding between them?
97    ///
98    /// This is equivalent to [Self::type_store_size] == [Self::type_alloc_size]
99    pub fn packs_exactly(&mut self, ctx: &Context, ty: TypeHandle) -> Result<bool> {
100        let ty = self.llvm_type(ctx, ty)?;
101        Ok(self.target_data.store_size_of_type(ty) == self.target_data.abi_size_of_type(ty))
102    }
103
104    /// Get the LLVM type of `ty`, building it if the cache does not hold it.
105    fn llvm_type(&mut self, ctx: &Context, ty: TypeHandle) -> Result<LLVMType> {
106        let llvm_ty = convert_type(ctx, &self.llvm_ctx, &mut self.types, ty)?;
107        if !llvm_type_is_sized(llvm_ty) {
108            return arg_err_noloc!(DataLayoutErr::UnsizedType(ty.disp(ctx).to_string()));
109        }
110        Ok(llvm_ty)
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use pliron::{
117        builtin::types::{FP16Type, FP64Type, IntegerType, Signedness},
118        context::Context,
119        result::ExpectOk,
120    };
121
122    use crate::{
123        data_layout::DataLayout,
124        types::{ArrayType, StructType},
125    };
126
127    /// A layout of x86-64, so that the answers do not depend on the host.
128    const X86_64: &str =
129        "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128";
130
131    #[test]
132    fn sizes_of_elements() {
133        let ctx = &mut Context::new();
134        let mut layout = DataLayout::new(X86_64);
135
136        // An i24 stores in 3 bytes, but has an alloc size
137        // (the space it takes when used in an array) of 4 bytes.
138        let i24 = IntegerType::get(ctx, 24, Signedness::Signless).into();
139        assert_eq!(layout.type_size_in_bits(ctx, i24).expect_ok(ctx), 24);
140        assert_eq!(layout.type_store_size(ctx, i24).expect_ok(ctx), 3);
141        assert_eq!(layout.type_alloc_size(ctx, i24).expect_ok(ctx), 4);
142        assert_eq!(layout.abi_type_align(ctx, i24).expect_ok(ctx), 4);
143        assert!(!layout.packs_exactly(ctx, i24).expect_ok(ctx));
144
145        // Build an array and validate the difference in store size and alloc size.
146        let array = ArrayType::get(ctx, i24, 3).into();
147        assert_eq!(layout.type_store_size(ctx, array).expect_ok(ctx), 12);
148        assert_eq!(layout.type_alloc_size(ctx, array).expect_ok(ctx), 12);
149
150        // The common widths have no padding.
151        for ty in [
152            IntegerType::get(ctx, 8, Signedness::Signless).into(),
153            IntegerType::get(ctx, 32, Signedness::Signless).into(),
154            IntegerType::get(ctx, 128, Signedness::Signless).into(),
155            FP16Type::get(ctx).into(),
156            FP64Type::get(ctx).into(),
157        ] {
158            assert!(layout.packs_exactly(ctx, ty).expect_ok(ctx));
159            assert_eq!(
160                layout.type_store_size(ctx, ty).expect_ok(ctx),
161                layout.type_alloc_size(ctx, ty).expect_ok(ctx)
162            );
163        }
164    }
165
166    #[test]
167    fn unsized_type_is_an_error() {
168        let ctx = &mut Context::new();
169        let mut layout = DataLayout::new(X86_64);
170
171        let opaque = StructType::get_named(ctx, "opaque".try_into().unwrap(), None)
172            .expect_ok(ctx)
173            .into();
174        assert!(layout.type_size_in_bits(ctx, opaque).is_err());
175        assert!(layout.type_store_size(ctx, opaque).is_err());
176        assert!(layout.type_alloc_size(ctx, opaque).is_err());
177        assert!(layout.abi_type_align(ctx, opaque).is_err());
178        assert!(layout.packs_exactly(ctx, opaque).is_err());
179    }
180
181    #[test]
182    fn host_layout_is_available() {
183        let ctx = &mut Context::new();
184        let mut layout = DataLayout::host().expect_ok(ctx);
185        assert!(!layout.string_representation().is_empty());
186
187        // A pointer is as large as the host's pointer.
188        let ptr = crate::types::PointerType::get(ctx, 0).into();
189        assert_eq!(
190            layout.type_store_size(ctx, ptr).expect_ok(ctx) as usize,
191            size_of::<*const u8>()
192        );
193    }
194}