Week 3

Summarizing the change in approach over the past week, we had decided to switch from the LLVM backend to the C backend as it offers certian advantages such as simpler code generation and better compatibility with existing C-based tools and libraries (such as SymEngine’s C interface).

Hence I started working with the C backend and I was able to successfully generate corresponding C code for handling simple assignment and print statement as follows

from sympy import Symbol
def main0():
   x: S = Symbol('x')
   print(x)

main0()


# The relevant functions from the generated code
basic _lcompilers_symbolic_str(char * x)
{
    basic _lcompilers_symbolic_str;
    _lfortran_set_symbol(_lcompilers_symbolic_str, x);
    return _lcompilers_symbolic_str;
}

void main0()
{
    basic x;
    x = _lcompilers_symbolic_str("x");
    printf("%s\n", basic_str(x));
}

Well, this implementation involved the following steps

  • Implementing the instantiate_SymbolicSymbol function, which would end up defining the _lcompilers_symbolic_str function and the function body for the same.
  • Making some changes in the visit_Assignment and visit_Print to support these operations.
  • Introducing the _lfortran_set_symbol function in the lfortran_intrinsics.c file as follows
LFORTRAN_API void _lfortran_set_symbol(basic symbol, const char* name) {
{
    basic_new_stack(name);
    symbol_set(symbol, name);
}

After introducing the following implementation, I thought we are essentially done with this task. But during my weekly meeting with Ondřej, we ended up discovering some downsides with this method. We ended up discussing and going through the implementation for SymEngine’s C interface and these are some points we could make note off

  • Assignments through SymEngine’s C interface work quite differently from how it does through SymPy.
  • In the SymEngine library, the basic type is implemented as a size 1 array.The type used for this array is CRCPBasic, which represents a reference-counted pointer to const Basic.
  • A basic type should be initialized using basic_new_stack(), before any function is called. his ensures that the necessary memory is properly allocated and initialized for the RCP<const Basic> object.
  • Hence we couldn’t use the following statement in our C code
    • x = _lcompilers_symbolic_str("x"); : This is because in C, arrays cannot be directly assigned (right to left) using the = operator. Remember basic is an array type (basic_struct[1])
    • basic _lcompilers_symbolic_str(char * x) : Such a function declartion wouldn’t have been possible because in C, you cannot directly return array types from functions.

Hence we had two options here

  • Either to use the basic_new_heap() function and define the function as follows basic* _lcompilers_symbolic_str(const char* x) . The function would then create a new basic object on the heap using basic_new_heap(), set the symbol value using symbol_set(), and returns the pointer to the created object.
  • Or simply get rid of the instantiate_SymbolicSymbol function and the wrappers introduced . If you think about it, we essentially don’t need to introduce _lcompilers_symbolic_str or _lfortran_set_symbol. We could simply work with
    void main0() {
    basic x;
    basic_new_stack(x);
    symbol_set(x, "x");
    printf("%s\n", basic_str(x));
    }
    

Hence, we went forward with the 2nd approach here. I ended up introducing an helper funcion called get_deepcopy_symbolic here to handle any assignments dealing with symbolic expressions. Hence we now have the following:

from sympy import Symbol
def main0():
    x: S = Symbol('x')
    y: S = Symbol('y')
    x = y
    print(x)

main0()

# C code implementations
void main0()
{
    basic x;
    basic_new_stack(x);
    basic y;
    basic_new_stack(y);
    symbol_set(x, "x");
    symbol_set(y, "y");
    basic_assign(x, y);
    printf("%s\n", basic_str(x));
}

void _lpython_main_program()
{
    main0();
}

This was followed by introducing symbolic binary operators. I introduced a SymbolicAdd node and we have the following ASR as of now. I am still working on the C code generation for the same

from sympy import Symbol
def main0():
    x: S = Symbol('x')
    y: S = Symbol('y')
    z: S = x + y

main0()

# Corresponding ASR
  [(=
      (Var 2 x)
      (IntrinsicFunction
          SymbolicSymbol
          [(StringConstant
              "x"
              (Character 1 1 () [])
          )]
          0
          (SymbolicExpression)
          ()
      )
      ()
  )
  (=
      (Var 2 y)
      (IntrinsicFunction
          SymbolicSymbol
          [(StringConstant
              "y"
              (Character 1 1 () [])
          )]
          0
          (SymbolicExpression)
          ()
      )
      ()
  )
  (=
      (Var 2 z)
      (IntrinsicFunction
          SymbolicAdd
          [(Var 2 x)
          (Var 2 y)]
          0
          (SymbolicExpression)
          ()
      )
      ()
  )]

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

  • Work on generating correct C code for the symbolic addition operation.
  • Introduce other binary operators like SymbolicMul etc.
  • 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 4!

Address

Mumbai, Maharashtra, India