Week 2

Do y’all remember my progress from the first week ? Reiterating, I had introduced the SymbolicExpression ttype and the Introducing SymbolicSymbol intrinsic function node . So my goal for the second week was adding support for symbolic expressions in visit_print and visit_assignment functions.

Do y’all remember how I had discussed my plan on tackling this ? Well my disucssion with Ondřej had led us to a conclusion that supporting the above operations through the LLVM backend might be a suitable approach for the problem. Though I had given y’all a big disclaimer saying that this method might or might not work and we’re still open to exploring new approaches

This is what ended up happening precisely. Halway through exploring this appraoch and trying to get the LLVM backend running, we realized that dealing with LLVM here is more stringent and imposes a lot of strict regulations and conditions on how we can solve the problem.

  • We would have to create an LLVM structure first as symbolic expressions cannot be handled using primite LLVM types. Hence would have to define a structure as follows llvm::StructType *symbolic_expr_type.
  • Then we would have to create the structure and add components based on primitive types.
  std::vector<llvm::Type*> els_symbolic = {
      llvm::Type::getInt8PtrTy(context)};
  std::vector<llvm::Type*> els_symbolic_ptr = {
      llvm::Type::getInt8PtrTy(context)->getPointerTo()};
  symbolic_expr_type = llvm::StructType::create(context, els_symbolic, "symbolic_expr");
  symbolic_expr_type_ptr = llvm::StructType::create(context, els_symbolic_ptr, "symbolic_expr_ptr");
  • Finally we would have to frame the generate_SymbolicSymbol function to return a LLVM:Value object with type symbolic_expr.

This was making the implementation more rigid an hence we decided to switch to the C backend. LPython’s C backend is as good as the LLVM backend. The C backend may also offer certain advantages over the LLVM backend, such as simpler code generation, better compatibility with existing C-based tools and libraries (such as SymEngine’s C interface), and potentially faster development time. Also we haven’t yet given up on the LLVM approach, we’ve just shifted it for the later weeks.

For supporting the C backend I had to address the following

  • Implement the SymbolicSymbol::verify_args function
  • Implement the instantiate_SymbolicSymbol function . This is because we would like to replace the IntrinsicFunction node with a FunctionCall node through our replace intrinsic function pass.
  [(=
      (Var 2 x)
      (IntrinsicFunction
          SymbolicSymbol
          [(StringConstant
              "x"
              (Character 1 1 () [])
          )]
          0
          (SymbolicExpression)
          ()
      )
      ()
  )]

  # From the above to something like the following

  [(=
      (Var 2 x)
      (FunctionCall
          1 _lcompilers_symbolic_str
          1 _lcompilers_symbolic_str
          [((StringConstant
              "x"
              (Character 1 1 () [])
          ))]
          (SymbolicExpression)
          ()
          ()
      )
      ()
  )]
  • Adding support in visit_print and visit_assignment to handle symbolic expressions to finally generate the C code. Demonstrating the assignment statement below
from lpython import S
from sympy import Symbol

def main0():
   x: S = Symbol('x')

main0()

(lf) anutosh491@spbhat68:~/lpython/lpython$ lpython --show-c examples/expr2.py 
#include <symengine/cwrapper.h>

#include <stdlib.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <lfortran_intrinsics.h>


struct dimension_descriptor
{
    int32_t lower_bound, length;
};

// Implementations
basic _lcompilers_symbolic_str(char * x)
{
    basic _lcompilers_symbolic_str;
    _lcompilers_symbolic_str = _lfortran_set_symbol(x);
    return _lcompilers_symbolic_str;
}

void main0()
{
    basic x;
    x = _lcompilers_symbolic_str("x");
}

void _lpython_main_program()
{
    main0();
}

int main(int argc, char* argv[])
{
    _lpython_set_argv(argc, argv);
    _lpython_main_program();
    return 0;
}

Finally, I would like to point out some tasks which I plan to tackle for the upcoming week.

  • Address all nitty-gritties and generate an output for the assignment and print statements from the LPython compiler.
  • Start introducing binary operators compatible with symbolic expressions.
  • Also introduce some casting functions for converting primitive data types into their symbolic forms.

Thank You for going through the blog. I hope you like it. Stick around for what’s next to come. Moving into Week 3!

Address

Mumbai, Maharashtra, India