1use alloc::{boxed::Box, vec, vec::Vec};
7
8use pliron::{
9 arg_err,
10 builtin::{
11 op_interfaces::{OneResultInterface, SymbolTableInterface},
12 types::{IntegerType, Signedness},
13 },
14 context::Context,
15 ident,
16 identifier::Identifier,
17 irbuild::inserter::Inserter,
18 result::Result,
19 symbol_table::SymbolTableCollection,
20 r#type::TypeHandle,
21 value::Value,
22};
23
24use crate::{
25 op_interfaces::CastOpInterface,
26 ops::{FuncOp, GepIndex, GetElementPtrOp, PtrToIntOp, ZeroOp},
27 types::{FuncType, PointerType, VoidType},
28};
29
30#[derive(Debug, thiserror::Error)]
31pub enum LookupOrInsertFunctionError {
32 #[error("Symbol '{0}' found but is not a function")]
33 SymbolNotFunction(Identifier),
34 #[error("Existing function '{0}' has a different type than the one being inserted")]
35 FunctionTypeMismatch(Identifier),
36}
37
38pub fn lookup_or_insert_function(
42 ctx: &mut Context,
43 symbol_table_collection: &mut SymbolTableCollection,
44 symbol_table_op: Box<dyn SymbolTableInterface>,
45 name: Identifier,
46 return_type: TypeHandle,
47 param_types: Vec<TypeHandle>,
48 is_var_arg: bool,
49) -> Result<FuncOp> {
50 let loc = symbol_table_op.loc(ctx);
51 let func_ty = FuncType::get(ctx, return_type, param_types, is_var_arg);
52 let symbol_table = symbol_table_collection.get_symbol_table(ctx, symbol_table_op.clone());
53 if let Some(func) = symbol_table.lookup(&name) {
54 if let Some(func_op) = func.as_any().downcast_ref::<FuncOp>() {
55 let existing_func_ty = func_op.get_type(ctx);
56 if existing_func_ty != func_ty {
57 return arg_err!(loc, LookupOrInsertFunctionError::FunctionTypeMismatch(name));
58 }
59 Ok(*func_op)
60 } else {
61 arg_err!(loc, LookupOrInsertFunctionError::SymbolNotFunction(name))
62 }
63 } else {
64 let func = FuncOp::new(ctx, name.clone(), func_ty);
65 symbol_table.insert(ctx, Box::new(func), None)?;
66 Ok(func)
67 }
68}
69
70pub fn get_size_type(ctx: &mut Context) -> TypeHandle {
72 IntegerType::get(ctx, 64, Signedness::Signless).into()
73}
74
75pub fn lookup_or_create_malloc_fn(
78 ctx: &mut Context,
79 symbol_table_collection: &mut SymbolTableCollection,
80 symbol_table_op: Box<dyn SymbolTableInterface>,
81) -> Result<FuncOp> {
82 let size_ty = get_size_type(ctx);
83 let ret_ty = PointerType::get(ctx, 0).into();
84 lookup_or_insert_function(
85 ctx,
86 symbol_table_collection,
87 symbol_table_op,
88 ident!("malloc"),
89 ret_ty,
90 vec![size_ty],
91 false,
92 )
93}
94
95pub fn lookup_or_create_free_fn(
98 ctx: &mut Context,
99 symbol_table_collection: &mut SymbolTableCollection,
100 symbol_table_op: Box<dyn SymbolTableInterface>,
101) -> Result<FuncOp> {
102 let ptr_ty = PointerType::get(ctx, 0).into();
103 lookup_or_insert_function(
104 ctx,
105 symbol_table_collection,
106 symbol_table_op,
107 ident!("free"),
108 VoidType::get(ctx).into(),
109 vec![ptr_ty],
110 false,
111 )
112}
113
114pub fn compute_type_size_in_bytes(
116 ctx: &mut Context,
117 inserter: &mut dyn Inserter,
118 ty: TypeHandle,
119) -> Value {
120 let size_ty = get_size_type(ctx);
125 let pointer_ty = PointerType::get(ctx, 0).into();
126 let zero_op = ZeroOp::new(ctx, pointer_ty);
127 inserter.append_op(ctx, &zero_op);
128 let gep_op = GetElementPtrOp::new(
129 ctx,
130 zero_op.get_result(ctx),
131 vec![GepIndex::Constant(1)],
132 ty,
133 );
134 inserter.append_op(ctx, &gep_op);
135 let ptr_to_int_op = PtrToIntOp::new(ctx, gep_op.get_result(ctx), size_ty);
136 inserter.append_op(ctx, &ptr_to_int_op);
137 ptr_to_int_op.get_result(ctx)
138}
139
140#[cfg(all(test, feature = "llvm-sys"))]
141mod tests {
142 use expect_test::expect;
143 use pliron::{
144 builtin::{
145 op_interfaces::{
146 CallOpCallable, OneResultInterface, SingleBlockRegionInterface, SymbolOpInterface,
147 },
148 ops::ModuleOp,
149 types::FP64Type,
150 },
151 context::Context,
152 ident, init_env_logger_for_tests,
153 irbuild::{
154 inserter::{IRInserter, Inserter, OpInsertionPoint},
155 listener::DummyListener,
156 },
157 op::{Op, verify_op},
158 result::ExpectOk,
159 };
160
161 use crate::{
162 function_call_utils::{
163 compute_type_size_in_bytes, get_size_type, lookup_or_create_free_fn,
164 lookup_or_create_malloc_fn,
165 },
166 llvm_sys::{core::LLVMContext, lljit::SimpleJIT},
167 ops::{CallOp, FuncOp, ReturnOp},
168 to_llvm_ir::{ToLLVMOptions, convert_module},
169 types::FuncType,
170 };
171
172 #[test]
173 fn test_malloc_and_free_integration() {
174 init_env_logger_for_tests!();
175 let mut ctx = Context::new();
176 let mut symbol_table_collection = pliron::symbol_table::SymbolTableCollection::new();
177
178 let module = ModuleOp::new(&mut ctx, ident!("test_module"));
180 let module_box = Box::new(module);
181
182 let malloc_fn =
184 lookup_or_create_malloc_fn(&mut ctx, &mut symbol_table_collection, module_box.clone())
185 .expect("Failed to create malloc function");
186
187 let free_fn =
189 lookup_or_create_free_fn(&mut ctx, &mut symbol_table_collection, module_box.clone())
190 .expect("Failed to create free function");
191
192 assert_eq!(malloc_fn.get_symbol_name(&ctx), ident!("malloc"));
194 assert_eq!(free_fn.get_symbol_name(&ctx), ident!("free"));
195
196 let malloc_fn_2 =
198 lookup_or_create_malloc_fn(&mut ctx, &mut symbol_table_collection, module_box.clone())
199 .expect("Failed to get malloc function again");
200
201 assert!(
202 malloc_fn == malloc_fn_2,
203 "Expected to get the same malloc function on second lookup"
204 );
205
206 let return_type = get_size_type(&mut ctx);
208 let func_ty = FuncType::get(&ctx, return_type, vec![], false);
209 let main_fn = FuncOp::new(&mut ctx, ident!("main"), func_ty);
210 main_fn
211 .get_operation()
212 .insert_at_front(module.get_body(&ctx, 0), &ctx);
213
214 let entry = main_fn.get_or_create_entry_block(&mut ctx);
216 let mut inserter = IRInserter::<DummyListener>::new(OpInsertionPoint::AtBlockEnd(entry));
217
218 let fp_ty = FP64Type::get(&ctx);
219 let fp_ty_size = compute_type_size_in_bytes(&mut ctx, &mut inserter, fp_ty.into());
220
221 let callee = CallOpCallable::Direct(malloc_fn.get_symbol_name(&ctx));
222 let callee_ty = malloc_fn.get_type(&ctx);
223 let args = vec![fp_ty_size];
224 let malloc_call = CallOp::new(&mut ctx, callee, callee_ty, args);
225 inserter.append_op(&ctx, &malloc_call);
226
227 let ptr_result = malloc_call.get_result(&ctx);
228 let free_callee = CallOpCallable::Direct(free_fn.get_symbol_name(&ctx));
229 let free_callee_ty = free_fn.get_type(&ctx);
230 let free_args = vec![ptr_result];
231 let free_call = CallOp::new(&mut ctx, free_callee, free_callee_ty, free_args);
232 inserter.append_op(&ctx, &free_call);
233
234 let ret_op = ReturnOp::new(&mut ctx, Some(fp_ty_size));
235 inserter.append_op(&ctx, &ret_op);
236
237 verify_op(&module, &ctx).expect_ok(&ctx);
238
239 let llvm_ctx = LLVMContext::default();
241 let llvm_ir =
242 convert_module(&ctx, &llvm_ctx, module, ToLLVMOptions::default()).expect_ok(&ctx);
243
244 expect![[r#"
245 ; ModuleID = 'test_module'
246 source_filename = "test_module"
247
248 define i64 @main() {
249 entry_block2v1:
250 %v3 = call ptr @malloc(i64 ptrtoint (ptr getelementptr (double, ptr null, i32 1) to i64))
251 call void @free(ptr %v3)
252 ret i64 ptrtoint (ptr getelementptr (double, ptr null, i32 1) to i64)
253 }
254
255 declare ptr @malloc(i64)
256
257 declare void @free(ptr)
258 "#]]
259 .assert_eq(&llvm_ir.to_string());
260 llvm_ir.verify().expect("Generated LLVM IR is invalid");
261
262 let jit = SimpleJIT::new(llvm_ctx, llvm_ir).expect("SimpleJIT creation failed");
264 let main_fn = unsafe {
265 jit.lookup_symbol::<fn() -> u64>("main")
266 .expect("main not found in JIT")
267 };
268 let fp_ty_size = main_fn();
269 assert_eq!(
270 fp_ty_size, 8,
271 "Expected size of double type to be 8 bytes, got {}",
272 fp_ty_size
273 );
274 }
275}