Skip to main content

pliron_llvm/
builtin_to_llvm.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) The pliron contributors
3
4//! Dialect conversion from builtin to LLVM dialect
5
6use pliron::{
7    builtin::{
8        op_interfaces::{OneRegionInterface, SymbolOpInterface},
9        ops::{ConstantOp as BuiltinConstantOp, FuncOp as BuiltinFuncOp, ModuleOp},
10        type_interfaces::FunctionTypeInterface,
11        types::{FunctionType as BuiltinFunctionType, UnitType},
12    },
13    common_traits::Verify,
14    context::{Context, Ptr},
15    derive::{op_interface_impl, type_interface_impl},
16    input_err_noloc, input_error_noloc,
17    irbuild::{
18        dialect_conversion::{self, DialectConversion, DialectConversionRewriter, OperandsInfo},
19        inserter::Inserter,
20        rewriter::Rewriter,
21    },
22    op::{Op, op_impls},
23    operation::Operation,
24    pass::{GuardedPass, OpGuard, OpPass, Pass, PassResult},
25    region::Region,
26    result::{Error, ErrorKind, Result},
27    r#type::{TypeHandle, TypedHandle, type_cast},
28};
29
30use crate::{
31    ToLLVMDialect, ToLLVMType,
32    ops::{ConstantOp as LLVMConstantOp, FuncOp as LLVMFuncOp},
33    types::{FuncType as LLVMFuncType, VoidType},
34};
35
36#[derive(thiserror::Error, Debug)]
37pub enum BuiltinToLLVMConversionError {
38    #[error("Invalid function type, cannot be converted to LLVM function type")]
39    InvalidFunctionType,
40}
41
42#[type_interface_impl]
43impl ToLLVMType for BuiltinFunctionType {
44    fn convert(&self, ctx: &Context) -> Result<TypeHandle> {
45        let arg_types = self.arg_types();
46        let res_types = self.res_types();
47
48        let convert_type_to_llvm = |ty: TypeHandle| {
49            type_cast::<dyn ToLLVMType>(&*ty.deref(ctx))
50                .map(|ty| ty.convert(ctx))
51                .unwrap_or(Ok(ty))
52        };
53
54        let arg_types = arg_types
55            .into_iter()
56            .map(convert_type_to_llvm)
57            .collect::<Result<Vec<_>>>()?;
58        let res_types = res_types
59            .into_iter()
60            .map(convert_type_to_llvm)
61            .collect::<Result<Vec<_>>>()?;
62
63        if res_types.is_empty() || res_types.len() > 1 {
64            return input_err_noloc!(BuiltinToLLVMConversionError::InvalidFunctionType);
65        }
66        let result_type = res_types[0];
67
68        let llvm_func_type = LLVMFuncType::get(ctx, result_type, arg_types, false);
69        Ok(llvm_func_type.into())
70    }
71}
72
73#[type_interface_impl]
74impl ToLLVMType for UnitType {
75    fn convert(&self, ctx: &Context) -> Result<TypeHandle> {
76        Ok(VoidType::get(ctx).to_handle())
77    }
78}
79
80/// Convert builtin.constant to llvm.constant
81#[op_interface_impl]
82impl ToLLVMDialect for BuiltinConstantOp {
83    fn rewrite(
84        &self,
85        ctx: &mut Context,
86        rewriter: &mut DialectConversionRewriter,
87        _operands_info: &OperandsInfo,
88    ) -> Result<()> {
89        let const_value = self.get_value(ctx);
90
91        // Create the LLVM constant operation with the same value
92        let llvm_const = LLVMConstantOp::new(ctx, const_value);
93
94        if let Err(e @ Error { .. }) = llvm_const.verify(ctx) {
95            return Err(Error {
96                kind: ErrorKind::InvalidInput,
97                // We reset the error origin to be from here
98                backtrace: pliron::std_deps::backtrace::Backtrace::capture(),
99                ..e
100            });
101        }
102
103        // Insert the new operation before the current one
104        rewriter.insert_operation(ctx, llvm_const.get_operation());
105
106        // Replace the old operation with the new one
107        let old_op = self.get_operation();
108        rewriter.replace_operation(ctx, old_op, llvm_const.get_operation());
109
110        Ok(())
111    }
112}
113
114/// Convert builtin.func to llvm.func
115#[op_interface_impl]
116impl ToLLVMDialect for BuiltinFuncOp {
117    fn rewrite(
118        &self,
119        ctx: &mut Context,
120        rewriter: &mut DialectConversionRewriter,
121        _operands_info: &OperandsInfo,
122    ) -> Result<()> {
123        // Get the function name
124        let func_name = self.get_symbol_name(ctx);
125
126        // Get the function type from builtin.func
127        let builtin_func_type = self.get_type(ctx);
128        let llvm_func_type = type_cast::<dyn ToLLVMType>(&*builtin_func_type.deref(ctx))
129            .ok_or_else(|| {
130                input_error_noloc!("builtin.func type does not implement ToLLVMType interface")
131            })?
132            .convert(ctx)?;
133        let llvm_func_type = TypedHandle::from_handle(llvm_func_type, ctx)?;
134
135        // Create the LLVM function operation
136        let llvm_func = LLVMFuncOp::new(ctx, func_name, llvm_func_type);
137
138        // Move the region from the builtin.func to the llvm.func
139        Region::move_to_op(self.get_region(ctx), llvm_func.get_operation(), ctx);
140
141        // Get the old operation
142        let old_op = self.get_operation();
143
144        // Insert the new operation before the current one
145        rewriter.insert_operation(ctx, llvm_func.get_operation());
146
147        // Replace the old operation with the new one
148        rewriter.replace_operation(ctx, old_op, llvm_func.get_operation());
149
150        Ok(())
151    }
152}
153
154/// Dialect conversion pattern for converting builtin ops to LLVM ops
155#[derive(Default)]
156pub struct BuiltinToLLVMConversion;
157
158impl DialectConversion for BuiltinToLLVMConversion {
159    fn can_convert_op(&self, ctx: &Context, op: Ptr<Operation>) -> bool {
160        let op_dyn = Operation::get_op_dyn(op, ctx);
161        let op_ref = op_dyn.op_ref();
162
163        // Check if this operation implements ToLLVMDialect
164        op_impls::<dyn ToLLVMDialect>(op_ref)
165    }
166
167    fn rewrite(
168        &mut self,
169        ctx: &mut Context,
170        rewriter: &mut DialectConversionRewriter,
171        op: Ptr<Operation>,
172        operands_info: &OperandsInfo,
173    ) -> Result<()> {
174        let op_dyn = Operation::get_op_dyn(op, ctx);
175        let op_ref = op_dyn.op_ref();
176
177        // Cast to the ToLLVMDialect interface and call rewrite
178        if let Some(to_llvm) = pliron::op::op_cast::<dyn ToLLVMDialect>(op_ref) {
179            to_llvm.rewrite(ctx, rewriter, operands_info)?;
180        }
181
182        Ok(())
183    }
184}
185
186/// Apply dialect conversion from builtin to LLVM on a [ModuleOp].
187pub fn convert_builtin_to_llvm(ctx: &mut Context, module: ModuleOp) -> Result<PassResult> {
188    builtin_to_llvm_pass().run(
189        module.get_operation(),
190        ctx,
191        &mut pliron::pass::AnalysisManager::default(),
192    )
193}
194
195/// A [ModuleOp] pass that applies the builtin to LLVM dialect conversion
196/// on every [Operation] in the module.
197pub fn builtin_to_llvm_pass()
198-> OpPass<ModuleOp, dialect_conversion::PassWrapper<BuiltinToLLVMConversion>> {
199    let pass = dialect_conversion::PassWrapper::<BuiltinToLLVMConversion>::new(
200        "builtin_to_llvm",
201        BuiltinToLLVMConversion,
202    );
203    GuardedPass::new(OpGuard::<ModuleOp>::default(), pass)
204}