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
andvisit_Print
to support these operations. - Introducing the
_lfortran_set_symbol
function in thelfortran_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 isCRCPBasic
, which represents a reference-counted pointer toconst Basic
. - A
basic
type should be initialized usingbasic_new_stack()
, before any function is called. his ensures that the necessary memory is properly allocated and initialized for theRCP<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. Rememberbasic
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 followsbasic* _lcompilers_symbolic_str(const char* x)
. The function would then create a newbasic
object on theheap
usingbasic_new_heap()
, set the symbol value usingsymbol_set()
, and returns thepointer
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 withvoid 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!