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