LPython Semantics - lcompilers/lpython GitHub Wiki

LPython is designed to compile such a subset of Python so that we can modify the Python's semantics to always copy things over for a = b (no reference counting), and still be equivalent to Python. This is done for performance reasons. The idea is that LPython will not compile your code if it does not follow the restricted subset. If you want reference counting, then one must ask for it explicitly.

List Example

>>> src = [] # fine
>>> dest = src # fine
>>> dest.append(1) # fine
>>> src # error --- LPython and CPython would differ here, so we must disallow at compile time

If you want reference counting, then we could do something like:

>>> src = rcp([]) # fine
>>> dest = rcp(src) # fine
>>> dest.append(1) # fine
>>> src # fine

Later, we can add ASR->ASR optimizations that turn this:

src = []
dest = src # deep copy
dest.append(1)
print(dest)

To this:

src = []
src.append(1)
print(src)

We can also use deepcopy in both CPython and LPython as follows:

>>> from copy import deepcopy
>>> src = [] # fine
>>> dest = deepcopy(src) # fine
>>> dest.append(1) # fine
>>> src # fine

So if you want to use both src and dest at the same time in LPython, you have two options:

  • Use deepcopy
  • Use rcp

In both cases you have to do this explicitly. Without being explicit, you can only use one at the time, so that the LPython's default "copy" semantics and the CPython's default "rcp" semantics are equivalent.

Array Example

The same applies to arrays:

>>> src: i32[3] = array([1, 2, 3]) # fine
>>> dst = src # fine
>>> dst[1] = 5 # fine
>>> src # error

You need to either do a copy:

>>> src: i32[3] = array([1, 2, 3]) # fine
>>> dst = src[:] # fine
>>> dst[1] = 5 # fine
>>> src # fine

Or use reference counting:

>>> src: RCP[i32[3]] = rcp(array([1, 2, 3])) # fine
>>> dst: RCP[i32[3]] = rcp(src) # fine
>>> dst[1] = 5 # fine
>>> src # fine

Reference Counting in LPython

Reference counting (rcp) is not implemented yet in LPython. But we plan to implemented roughly along the lines above to allow such use cases. One probably has to put it into the type such as RCP[i32[3]] as well as use the rcp function to create such a type. For making a copy, either we could do a = b, or a = rcp(b).

One could possibly just denote this in the type itself, since LPython should have all the information to know what to do, and for CPython the rcp() function is a no-op:

>>> src: RCP[i32[3]] = array([1, 2, 3]) # fine
>>> dst: RCP[i32[3]] = src # fine
>>> dst[1] = 5 # fine
>>> src # fine

The down side of this approach is that it is no longer clear from the RHS what the type should be on the LHS. For this reason, perhaps using rcp (once) like this might be a good idea so that the types can always be strictly inferred from the RHS:

>>> src: RCP[i32[3]] = rcp(array([1, 2, 3])) # fine
>>> dst: RCP[i32[3]] = src # fine
>>> dst[1] = 5 # fine
>>> src # fine