1use pliron::{
7 arg_err,
8 builtin::{
9 op_interfaces::{OneResultInterface, SymbolTableInterface},
10 types::{IntegerType, Signedness},
11 },
12 context::Context,
13 identifier::Identifier,
14 irbuild::inserter::Inserter,
15 result::Result,
16 symbol_table::SymbolTableCollection,
17 r#type::TypeHandle,
18 value::Value,
19};
20
21use crate::{
22 op_interfaces::CastOpInterface,
23 ops::{FuncOp, GepIndex, GetElementPtrOp, PtrToIntOp, ZeroOp},
24 types::{FuncType, PointerType, VoidType},
25};
26
27#[derive(Debug, thiserror::Error)]
28pub enum LookupOrInsertFunctionError {
29 #[error("Symbol '{0}' found but is not a function")]
30 SymbolNotFunction(Identifier),
31 #[error("Existing function '{0}' has a different type than the one being inserted")]
32 FunctionTypeMismatch(Identifier),
33}
34
35pub fn lookup_or_insert_function(
39 ctx: &mut Context,
40 symbol_table_collection: &mut SymbolTableCollection,
41 symbol_table_op: Box<dyn SymbolTableInterface>,
42 name: Identifier,
43 return_type: TypeHandle,
44 param_types: Vec<TypeHandle>,
45 is_var_arg: bool,
46) -> Result<FuncOp> {
47 let loc = symbol_table_op.loc(ctx);
48 let func_ty = FuncType::get(ctx, return_type, param_types, is_var_arg);
49 let symbol_table = symbol_table_collection.get_symbol_table(ctx, symbol_table_op.clone());
50 if let Some(func) = symbol_table.lookup(&name) {
51 if let Some(func_op) = func.as_any().downcast_ref::<FuncOp>() {
52 let existing_func_ty = func_op.get_type(ctx);
53 if existing_func_ty != func_ty {
54 return arg_err!(loc, LookupOrInsertFunctionError::FunctionTypeMismatch(name));
55 }
56 Ok(*func_op)
57 } else {
58 arg_err!(loc, LookupOrInsertFunctionError::SymbolNotFunction(name))
59 }
60 } else {
61 let func = FuncOp::new(ctx, name.clone(), func_ty);
62 symbol_table.insert(ctx, Box::new(func), None)?;
63 Ok(func)
64 }
65}
66
67pub fn get_size_type(ctx: &mut Context) -> TypeHandle {
69 IntegerType::get(ctx, 64, Signedness::Signless).into()
70}
71
72pub fn lookup_or_create_malloc_fn(
75 ctx: &mut Context,
76 symbol_table_collection: &mut SymbolTableCollection,
77 symbol_table_op: Box<dyn SymbolTableInterface>,
78) -> Result<FuncOp> {
79 let size_ty = get_size_type(ctx);
80 let ret_ty = PointerType::get(ctx, 0).into();
81 lookup_or_insert_function(
82 ctx,
83 symbol_table_collection,
84 symbol_table_op,
85 "malloc".try_into().unwrap(),
86 ret_ty,
87 vec![size_ty],
88 false,
89 )
90}
91
92pub fn lookup_or_create_free_fn(
95 ctx: &mut Context,
96 symbol_table_collection: &mut SymbolTableCollection,
97 symbol_table_op: Box<dyn SymbolTableInterface>,
98) -> Result<FuncOp> {
99 let ptr_ty = PointerType::get(ctx, 0).into();
100 lookup_or_insert_function(
101 ctx,
102 symbol_table_collection,
103 symbol_table_op,
104 "free".try_into().unwrap(),
105 VoidType::get(ctx).into(),
106 vec![ptr_ty],
107 false,
108 )
109}
110
111pub fn compute_type_size_in_bytes(
113 ctx: &mut Context,
114 inserter: &mut dyn Inserter,
115 ty: TypeHandle,
116) -> Value {
117 let size_ty = get_size_type(ctx);
122 let pointer_ty = PointerType::get(ctx, 0).into();
123 let zero_op = ZeroOp::new(ctx, pointer_ty);
124 inserter.append_op(ctx, &zero_op);
125 let gep_op = GetElementPtrOp::new(
126 ctx,
127 zero_op.get_result(ctx),
128 vec![GepIndex::Constant(1)],
129 ty,
130 );
131 inserter.append_op(ctx, &gep_op);
132 let ptr_to_int_op = PtrToIntOp::new(ctx, gep_op.get_result(ctx), size_ty);
133 inserter.append_op(ctx, &ptr_to_int_op);
134 ptr_to_int_op.get_result(ctx)
135}
136
137#[cfg(all(test, feature = "llvm-sys"))]
138mod tests {
139 use expect_test::expect;
140 use pliron::{
141 builtin::{
142 op_interfaces::{
143 CallOpCallable, OneResultInterface, SingleBlockRegionInterface, SymbolOpInterface,
144 },
145 ops::ModuleOp,
146 types::FP64Type,
147 },
148 context::Context,
149 init_env_logger_for_tests,
150 irbuild::{
151 inserter::{IRInserter, Inserter, OpInsertionPoint},
152 listener::DummyListener,
153 },
154 op::{Op, verify_op},
155 result::ExpectOk,
156 };
157
158 use crate::{
159 function_call_utils::{
160 compute_type_size_in_bytes, get_size_type, lookup_or_create_free_fn,
161 lookup_or_create_malloc_fn,
162 },
163 llvm_sys::{core::LLVMContext, lljit::LLVMLLJIT, target},
164 ops::{CallOp, FuncOp, ReturnOp},
165 to_llvm_ir::convert_module,
166 types::FuncType,
167 };
168
169 #[test]
170 fn test_malloc_and_free_integration() {
171 init_env_logger_for_tests!();
172 let mut ctx = Context::new();
173 let mut symbol_table_collection = pliron::symbol_table::SymbolTableCollection::new();
174
175 let module = ModuleOp::new(&mut ctx, "test_module".try_into().unwrap());
177 let module_box = Box::new(module);
178
179 let malloc_fn =
181 lookup_or_create_malloc_fn(&mut ctx, &mut symbol_table_collection, module_box.clone())
182 .expect("Failed to create malloc function");
183
184 let free_fn =
186 lookup_or_create_free_fn(&mut ctx, &mut symbol_table_collection, module_box.clone())
187 .expect("Failed to create free function");
188
189 assert_eq!(
191 malloc_fn.get_symbol_name(&ctx),
192 "malloc".try_into().unwrap()
193 );
194 assert_eq!(free_fn.get_symbol_name(&ctx), "free".try_into().unwrap());
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, "main".try_into().unwrap(), 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 = convert_module(&ctx, &llvm_ctx, module).expect_ok(&ctx);
242
243 expect![[r#"
244 ; ModuleID = 'test_module'
245 source_filename = "test_module"
246
247 define i64 @main() {
248 entry_block2v1:
249 %v3 = call ptr @malloc(i64 ptrtoint (ptr getelementptr (double, ptr null, i32 1) to i64))
250 call void @free(ptr %v3)
251 ret i64 ptrtoint (ptr getelementptr (double, ptr null, i32 1) to i64)
252 }
253
254 declare ptr @malloc(i64)
255
256 declare void @free(ptr)
257 "#]]
258 .assert_eq(&llvm_ir.to_string());
259 llvm_ir.verify().expect("Generated LLVM IR is invalid");
260
261 target::initialize_native().expect("Failed to initialize native target for JIT");
263 let jit = LLVMLLJIT::new_with_default_builder().expect("Failed to create LLJIT instance");
264 jit.add_module(llvm_ir)
265 .expect("Failed to add module to JIT");
266 let main_addr = jit
267 .lookup_symbol("main")
268 .expect("Failed to lookup 'main' symbol");
269 let main_fn = unsafe { core::mem::transmute::<u64, fn() -> u64>(main_addr) };
270 let fp_ty_size = main_fn();
271 assert_eq!(
272 fp_ty_size, 8,
273 "Expected size of double type to be 8 bytes, got {}",
274 fp_ty_size
275 );
276 }
277}