Skip to main content

qudit_expr/codegen/
codegen.rs

1use inkwell::AddressSpace;
2use inkwell::builder::Builder;
3use inkwell::values::FloatValue;
4use inkwell::values::FunctionValue;
5
6use coe::is_same;
7use qudit_core::RealScalar;
8use std::collections::HashMap;
9
10use crate::ComplexExpression;
11use crate::Expression;
12
13use super::builtins::Builtins;
14use super::module::Module;
15
16#[derive(Debug)]
17pub struct CodeGenError {
18    pub message: String,
19}
20
21impl CodeGenError {
22    pub fn new(message: &str) -> Self {
23        CodeGenError {
24            message: message.to_string(),
25        }
26    }
27}
28
29type CodeGenResult<T> = Result<T, CodeGenError>;
30
31pub struct CodeGenerator<'ctx, R: RealScalar> {
32    pub context: &'ctx Module<R>,
33    pub builder: Builder<'ctx>,
34
35    variables: HashMap<String, FloatValue<'ctx>>,
36    expressions: HashMap<String, FloatValue<'ctx>>,
37    functions: HashMap<String, FunctionValue<'ctx>>,
38    fn_value_opt: Option<FunctionValue<'ctx>>,
39    output_ptr_idx: Option<u32>,
40}
41
42impl<'ctx, R: RealScalar> CodeGenerator<'ctx, R> {
43    pub fn new(context: &'ctx Module<R>) -> Self {
44        let builder = context.context().create_builder();
45        CodeGenerator {
46            context,
47            builder,
48            variables: HashMap::new(),
49            functions: HashMap::new(),
50            expressions: HashMap::new(),
51            fn_value_opt: None,
52            output_ptr_idx: None,
53        }
54    }
55
56    fn int_type(&self) -> inkwell::types::IntType<'ctx> {
57        if is_same::<R, f32>() {
58            self.context.context().i32_type()
59        } else if is_same::<R, f64>() {
60            self.context.context().i64_type()
61        } else {
62            panic!("Unknown bit width");
63        }
64    }
65
66    fn float_type(&self) -> inkwell::types::FloatType<'ctx> {
67        if is_same::<R, f32>() {
68            self.context.context().f32_type()
69        } else if is_same::<R, f64>() {
70            self.context.context().f64_type()
71        } else {
72            panic!("Unknown bit width");
73        }
74    }
75
76    fn gen_write_func_proto(&self, name: &str) -> CodeGenResult<FunctionValue<'ctx>> {
77        let ret_type = self.context.context().void_type();
78        let ptr_type = self.context.context().ptr_type(AddressSpace::default());
79        // Match Rust WriteFunc<R> signature: (*const R, *mut R, *const u64, *const u64, u64, *const bool)
80        let param_types = vec![
81            ptr_type.into(),                          // *const R
82            ptr_type.into(),                          // *mut R
83            ptr_type.into(),                          // *const u64
84            ptr_type.into(),                          // *const u64
85            self.context.context().i64_type().into(), // u64
86            ptr_type.into(),                          // *const bool
87        ];
88        let func_type = ret_type.fn_type(&param_types, false);
89        let func = self
90            .context
91            .with_module(|module| module.add_function(name, func_type, None));
92        Ok(func)
93    }
94
95    fn build_expression(&mut self, expr: &Expression) -> CodeGenResult<FloatValue<'ctx>> {
96        let expr_str = expr.to_string();
97        let cached = self.expressions.get(&expr_str);
98        if let Some(c) = cached {
99            return Ok(*c);
100        }
101
102        let val = match expr {
103            Expression::Pi => Ok(self.float_type().const_float(std::f64::consts::PI)),
104            Expression::Constant(_) => Ok(self.float_type().const_float(expr.to_float())),
105            Expression::Variable(name) => self
106                .variables
107                .get(name)
108                .ok_or(CodeGenError::new(&format!("Variable {} not found", name)))
109                .copied(),
110            Expression::Neg(expr) => {
111                let val = self.build_expression(expr)?;
112                Ok(self.builder.build_float_neg(val, "tmp").unwrap())
113            }
114            Expression::Add(lhs, rhs) => {
115                let lhs_val = self.build_expression(lhs)?;
116                let rhs_val = self.build_expression(rhs)?;
117                Ok(self
118                    .builder
119                    .build_float_add(lhs_val, rhs_val, "tmp")
120                    .unwrap())
121            }
122            Expression::Sub(lhs, rhs) => {
123                let lhs_val = self.build_expression(lhs)?;
124                let rhs_val = self.build_expression(rhs)?;
125                Ok(self
126                    .builder
127                    .build_float_sub(lhs_val, rhs_val, "tmp")
128                    .unwrap())
129            }
130            Expression::Mul(lhs, rhs) => {
131                let lhs_val = self.build_expression(lhs)?;
132                let rhs_val = self.build_expression(rhs)?;
133                Ok(self
134                    .builder
135                    .build_float_mul(lhs_val, rhs_val, "tmp")
136                    .unwrap())
137            }
138            Expression::Div(lhs, rhs) => {
139                let lhs_val = self.build_expression(lhs)?;
140                let rhs_val = self.build_expression(rhs)?;
141                Ok(self
142                    .builder
143                    .build_float_div(lhs_val, rhs_val, "tmp")
144                    .unwrap())
145            }
146            Expression::Pow(base, exponent) => {
147                let base_val = self.build_expression(base)?;
148                let exponent_val = self.build_expression(exponent)?;
149                let pow = self.get_builtin("pow");
150                let args = [base_val.into(), exponent_val.into()];
151                let val = self
152                    .builder
153                    .build_call(pow, &args, "tmp")
154                    .unwrap()
155                    .try_as_basic_value()
156                    .basic()
157                    .unwrap()
158                    .into_float_value();
159                Ok(val)
160            }
161            Expression::Sqrt(expr) => {
162                let arg = self.build_expression(expr)?;
163                let sqrt = self.get_builtin("sqrt");
164                let val = self
165                    .builder
166                    .build_call(sqrt, &[arg.into()], "tmp")
167                    .unwrap()
168                    .try_as_basic_value()
169                    .basic()
170                    .unwrap()
171                    .into_float_value();
172                Ok(val)
173            }
174            Expression::Sin(expr) => {
175                let arg = self.build_expression(expr)?;
176                let sin = self.get_builtin("sin");
177                let val = self
178                    .builder
179                    .build_call(sin, &[arg.into()], "tmp")
180                    .unwrap()
181                    .try_as_basic_value()
182                    .basic()
183                    .unwrap()
184                    .into_float_value();
185                Ok(val)
186            }
187            Expression::Cos(expr) => {
188                let arg = self.build_expression(expr)?;
189                let cos = self.get_builtin("cos");
190                let val = self
191                    .builder
192                    .build_call(cos, &[arg.into()], "tmp")
193                    .unwrap()
194                    .try_as_basic_value()
195                    .basic()
196                    .unwrap()
197                    .into_float_value();
198                Ok(val)
199            }
200        };
201
202        if let Ok(val) = val {
203            self.expressions.insert(expr_str, val);
204        }
205        val
206    }
207
208    pub fn compile_expr(
209        &mut self,
210        expr: &ComplexExpression,
211        re_offset: usize,
212        need_to_write_real_zero: bool,
213    ) -> CodeGenResult<()> {
214        let re_offset: u64 = re_offset as u64;
215        let ptr_idx = self
216            .output_ptr_idx
217            .to_owned()
218            .expect("Output pointer index not set");
219        let ptr = self
220            .fn_value_opt
221            .unwrap()
222            .get_nth_param(ptr_idx)
223            .unwrap()
224            .into_pointer_value();
225
226        if need_to_write_real_zero || !expr.real.is_zero_fast() {
227            let val = self.build_expression(&expr.real)?;
228            let offset = self.int_type().const_int(re_offset, false);
229            let offset_ptr = unsafe {
230                self.builder
231                    .build_gep(self.float_type(), ptr, &[offset], "offset_ptr")
232                    .unwrap()
233            };
234
235            match self.builder.build_store(offset_ptr, val) {
236                Ok(_) => {}
237                Err(e) => {
238                    return Err(CodeGenError::new(&format!("Error storing value: {}", e)));
239                }
240            };
241        }
242
243        if !expr.imag.is_zero_fast() {
244            let val = self.build_expression(&expr.imag)?;
245            let offset = self.int_type().const_int(re_offset + 1, false);
246            let offset_ptr = unsafe {
247                self.builder
248                    .build_gep(self.float_type(), ptr, &[offset], "offset_ptr")
249                    .unwrap()
250            };
251
252            match self.builder.build_store(offset_ptr, val) {
253                Ok(_) => {}
254                Err(e) => {
255                    return Err(CodeGenError::new(&format!("Error storing value: {}", e)));
256                }
257            };
258        }
259
260        Ok(())
261    }
262
263    fn build_var_table(&mut self, variables: &[String]) {
264        self.variables.clear();
265        let params_ptr = self
266            .fn_value_opt
267            .unwrap()
268            .get_nth_param(0)
269            .unwrap()
270            .into_pointer_value();
271        let param_offset_ptr = self
272            .fn_value_opt
273            .unwrap()
274            .get_nth_param(2)
275            .unwrap()
276            .into_pointer_value();
277
278        for (map_idx, var_name) in variables.iter().enumerate() {
279            // Load the actual offset from param_offset_ptr using map_idx
280            // param_offset_ptr points to u64 values according to Rust signature
281            let map_idx_val = self
282                .context
283                .context()
284                .i64_type()
285                .const_int(map_idx as u64, false);
286            let actual_offset_ptr = unsafe {
287                self.builder
288                    .build_gep(
289                        self.context.context().i64_type(),
290                        param_offset_ptr,
291                        &[map_idx_val],
292                        "actual_offset_ptr_gep",
293                    )
294                    .unwrap()
295            };
296            let actual_offset_val = self
297                .builder
298                .build_load(
299                    self.context.context().i64_type(),
300                    actual_offset_ptr,
301                    "actual_offset_val",
302                )
303                .unwrap()
304                .into_int_value();
305
306            // Use the actual_offset_val to index into params_ptr
307            let var_ptr = unsafe {
308                self.builder
309                    .build_gep(
310                        self.float_type(),
311                        params_ptr,
312                        &[actual_offset_val],
313                        "var_ptr_gep",
314                    )
315                    .unwrap()
316            };
317
318            let val = self
319                .builder
320                .build_load(self.float_type(), var_ptr, var_name)
321                .unwrap()
322                .into_float_value();
323            self.variables.insert(var_name.to_owned(), val);
324        }
325    }
326
327    fn get_builtin(&mut self, name: &str) -> FunctionValue<'ctx> {
328        if let Some(f) = self.functions.get(name) {
329            return *f;
330        }
331
332        let b = match Builtins::from_str(name) {
333            Some(b) => b,
334            None => {
335                panic!("Unsupported builtin function: {}", name);
336            }
337        };
338
339        let intr = match b.intrinsic() {
340            Some(i) => i,
341            None => {
342                panic!("Unsupported builtin function: {}", name);
343            }
344        };
345
346        let decl = self
347            .context
348            .with_module(|module| intr.get_declaration(&module, &[self.float_type().into()]));
349
350        let fn_value = match decl {
351            Some(f) => f,
352            None => {
353                panic!("Unsupported builtin function: {}", name);
354            }
355        };
356
357        self.functions.insert(name.to_string(), fn_value);
358        fn_value
359    }
360
361    // fn get_expression(&mut self, name: &str) -> Option<FloatValue<'ctx>> {
362    //     if let Some(c) = self.expressions.get(name) {
363    //         return Some(c.clone());
364    //     }
365
366    //     let c = match name {
367    //         "pi" => Some(self.float_type().const_float(std::f64::consts::PI)),
368    //         "π" => Some(self.float_type().const_float(std::f64::consts::PI)),
369    //         "e" => Some(self.float_type().const_float(std::f64::consts::E)),
370    //         _ => None
371    //     };
372
373    //     if let Some(c) = c {
374    //         self.expressions.insert(name.to_string(), c);
375    //         return Some(c);
376    //     }
377
378    //     None
379    // }
380
381    pub fn gen_func(
382        &mut self,
383        fn_name: &str,
384        fn_expr: &[Expression],
385        var_table: &[String],
386        fn_len: usize,
387    ) -> CodeGenResult<()> {
388        self.expressions.clear();
389        let func = self.gen_write_func_proto(fn_name)?;
390        let entry = self.context.context().append_basic_block(func, "entry");
391        self.builder.position_at_end(entry);
392        self.fn_value_opt = Some(func);
393        // println!("name: {:?}, var_table: {:?}", fn_name, var_table);
394        self.build_var_table(var_table);
395
396        let output_ptr = self
397            .fn_value_opt
398            .unwrap()
399            .get_nth_param(1)
400            .unwrap()
401            .into_pointer_value();
402        let output_map_ptr = self
403            .fn_value_opt
404            .unwrap()
405            .get_nth_param(3)
406            .unwrap()
407            .into_pointer_value();
408        let fn_unit_offset = self
409            .fn_value_opt
410            .unwrap()
411            .get_nth_param(4)
412            .unwrap()
413            .into_int_value();
414        let const_param_ptr = self
415            .fn_value_opt
416            .unwrap()
417            .get_nth_param(5)
418            .unwrap()
419            .into_pointer_value();
420        let int_increment = self.context.context().i64_type().const_int(1u64, false);
421        let mut dyn_fn_unit_idx = self.context.context().i64_type().const_int(0u64, false);
422
423        for (fn_unit_idx, fn_unit_exprs) in fn_expr.chunks(fn_len).enumerate() {
424            let current_fn = self.fn_value_opt.unwrap();
425            let compute_block = self
426                .context
427                .context()
428                .append_basic_block(current_fn, &format!("compute_unit_{}", fn_unit_idx));
429            let skip_block = self
430                .context
431                .context()
432                .append_basic_block(current_fn, &format!("skip_unit_{}", fn_unit_idx));
433
434            if fn_unit_idx == 0 {
435                // First function unit is the function, always compute.
436                self.builder
437                    .build_unconditional_branch(compute_block)
438                    .unwrap();
439            } else if fn_unit_idx <= var_table.len() {
440                // Next var_table.len() function units are the partials in the gradient.
441                // Check if the corresponding parameter is constant.
442                let param_idx_for_const_check = self
443                    .context
444                    .context()
445                    .i64_type()
446                    .const_int((fn_unit_idx - 1) as u64, false);
447                let const_param_elem_ptr = unsafe {
448                    self.builder
449                        .build_gep(
450                            self.context.context().i8_type(),
451                            const_param_ptr,
452                            &[param_idx_for_const_check],
453                            "const_param_elem_ptr",
454                        )
455                        .unwrap()
456                };
457                let is_constant_val = self
458                    .builder
459                    .build_load(
460                        self.context.context().i8_type(),
461                        const_param_elem_ptr,
462                        "is_constant",
463                    )
464                    .unwrap()
465                    .into_int_value();
466
467                let is_constant_true = self
468                    .builder
469                    .build_int_compare(
470                        inkwell::IntPredicate::EQ,
471                        is_constant_val,
472                        self.context.context().i8_type().const_int(1, false),
473                        "is_constant_true",
474                    )
475                    .unwrap();
476
477                // If is_constant_true, branch to skip_block. Else, branch to compute_block.
478                self.builder
479                    .build_conditional_branch(is_constant_true, skip_block, compute_block)
480                    .unwrap();
481            } else {
482                // Last are the hessian units
483
484                let index = fn_unit_idx - var_table.len() - 1;
485                let param_j = ((((8 * index + 1) as f64).sqrt().floor() as usize) - 1) / 2;
486                let param_i = index - param_j * (param_j + 1) / 2;
487
488                // Check if the corresponding parameter is constant.
489                let param_idx_i_for_const_check = self
490                    .context
491                    .context()
492                    .i64_type()
493                    .const_int((param_i - 1) as u64, false);
494                let const_param_elem_ptr = unsafe {
495                    self.builder
496                        .build_gep(
497                            self.context.context().i8_type(),
498                            const_param_ptr,
499                            &[param_idx_i_for_const_check],
500                            "const_param_i_elem_ptr",
501                        )
502                        .unwrap()
503                };
504                let is_i_constant_val = self
505                    .builder
506                    .build_load(
507                        self.context.context().i8_type(),
508                        const_param_elem_ptr,
509                        "is_i_constant",
510                    )
511                    .unwrap()
512                    .into_int_value();
513
514                let is_i_constant_true = self
515                    .builder
516                    .build_int_compare(
517                        inkwell::IntPredicate::EQ,
518                        is_i_constant_val,
519                        self.context.context().i8_type().const_int(1, false),
520                        "is_i_constant_true",
521                    )
522                    .unwrap();
523
524                // Check if the corresponding parameter is constant.
525                let param_idx_j_for_const_check = self
526                    .context
527                    .context()
528                    .i64_type()
529                    .const_int((param_j - 1) as u64, false);
530                let const_param_elem_ptr = unsafe {
531                    self.builder
532                        .build_gep(
533                            self.context.context().i8_type(),
534                            const_param_ptr,
535                            &[param_idx_j_for_const_check],
536                            "const_param_j_elem_ptr",
537                        )
538                        .unwrap()
539                };
540                let is_j_constant_val = self
541                    .builder
542                    .build_load(
543                        self.context.context().i8_type(),
544                        const_param_elem_ptr,
545                        "is_j_constant",
546                    )
547                    .unwrap()
548                    .into_int_value();
549
550                let is_j_constant_true = self
551                    .builder
552                    .build_int_compare(
553                        inkwell::IntPredicate::EQ,
554                        is_j_constant_val,
555                        self.context.context().i8_type().const_int(1, false),
556                        "is_j_constant_true",
557                    )
558                    .unwrap();
559
560                let is_either_constant = self
561                    .builder
562                    .build_or(is_i_constant_true, is_j_constant_true, "is_either_constant")
563                    .unwrap();
564
565                // If is_constant_true, branch to skip_block. Else, branch to compute_block.
566                self.builder
567                    .build_conditional_branch(is_either_constant, skip_block, compute_block)
568                    .unwrap();
569            }
570
571            // Position builder at the compute block to emit the actual computation logic
572            self.builder.position_at_end(compute_block);
573
574            let this_unit_offset = self
575                .builder
576                .build_int_mul(fn_unit_offset, dyn_fn_unit_idx, "this_unit_offset")
577                .unwrap();
578            dyn_fn_unit_idx = self
579                .builder
580                .build_int_add(dyn_fn_unit_idx, int_increment, "dyn_fn_unit_idx")
581                .unwrap();
582
583            for (i, expr) in fn_unit_exprs.iter().enumerate() {
584                if expr.is_zero_fast() {
585                    continue;
586                }
587
588                let val = self.build_expression(expr)?;
589                let offset_ptr = unsafe {
590                    let output_idx = self.context.context().i64_type().const_int(i as u64, false);
591                    let output_map_elem_ptr = self
592                        .builder
593                        .build_gep(
594                            self.context.context().i64_type(),
595                            output_map_ptr,
596                            &[output_idx],
597                            "output_map_elem_ptr",
598                        )
599                        .unwrap();
600                    let output_map_offset = self
601                        .builder
602                        .build_load(
603                            self.context.context().i64_type(),
604                            output_map_elem_ptr,
605                            "output_map_offset",
606                        )
607                        .unwrap()
608                        .into_int_value();
609                    let combined_offset = self
610                        .builder
611                        .build_int_add(output_map_offset, this_unit_offset, "combined_offset")
612                        .unwrap();
613                    self.builder
614                        .build_gep(
615                            self.float_type(),
616                            output_ptr,
617                            &[combined_offset],
618                            "offset_ptr",
619                        )
620                        .unwrap()
621                };
622
623                match self.builder.build_store(offset_ptr, val) {
624                    Ok(_) => {}
625                    Err(e) => {
626                        return Err(CodeGenError::new(&format!("Error storing value: {}", e)));
627                    }
628                };
629            }
630
631            // After computation (if compute_block was taken), branch to the skip_block
632            // to ensure control flow continues to the next iteration of the loop.
633            self.builder.build_unconditional_branch(skip_block).unwrap();
634
635            // Position builder at the skip block for the next iteration.
636            self.builder.position_at_end(skip_block);
637        }
638
639        match self.builder.build_return(None) {
640            Ok(_) => Ok(()),
641            Err(e) => Err(CodeGenError::new(&format!("Error building return: {}", e))),
642        }
643    }
644}