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