lcompilers / lpython

Python compiler
https://lpython.org/
Other
1.51k stars 164 forks source link

Implement pass-by-reference by returning tuples in lpython #1367

Open dylon opened 1 year ago

dylon commented 1 year ago

Unlike Python, C does not support unpacking tuple return values. To work around this and avoid the overhead of using tuples, most C programmers pass parameters by reference that would otherwise be returned as tuple parameters in Python. Conversely, Python does not support passing by reference, so values are returned as tuple parameters that would otherwise be passed by reference in C. We may take advantage of these by specifying lpython function signatures that return tuples to maintain Python coherence while transforming them into references in the generated C functions.

As a thought, we could provide square bracket notation on the ltype hints to give them parameter names that may be referenced in the return type hints and thusly understood to be references. For example:

SUCCESS: i32 = i32(0)

def mutate_parameter(x: i32["x"]) -> tuple[i32, i32["x"]]:
    x = 2 * x
    return SUCCESS, x

x: i32 = i32(4)
return_code: i32
return_code, x = mutate_parameter(x)
if return_code == SUCCESS:
    print("x =", x)
else:
    print("Failed to mutate the value of x")

The generated C function and call might look something like the following:

int32_t SUCCESS = 0;

int32_t mutate_parameter(int32_t *x)
{
    *x = 2 * (*x);
    return SUCCESS;
}

// ...
    int32_t x;
    int32_t return_code;
    x = 4;
    return_code = mutate_parameter(&x);
    if (return_code == SUCCESS) {
        printf("%s%s%s\n", "x =", " ", x);
    }
    else {
        printf("%s\n", "Failed to mutate the value of x");
    }
// ...

Then, we could do other things like pass parameters by reference only into the function while passing others with the intent to mutate them. For example, the following Python code:

def mutate_x_but_not_y(x: i32["x"], y: i32["y"]) -> tuple[i32, i32["x"]]:
    x = 3 * y
    return SUCCESS, x

x: i32 = i32(0)
y: i32 = i32(3)
return_code: i32
return_code, x = mutate_x_by_not_y(x, y)

might be transformed into the following C code:

int32_t mutate_x_by_not_y(int32_t *x, int32_t *y)
{
    *x = 3 * (*y);
    return SUCCESS;
}

// ...
    int32_t x;
    int32_t y;
    int32_t return_code;
    x = 0;
    y = 3;
    return_code = mutate_x_by_not_y(&x, &y);
// ...

EDIT: 17-Dec-2022

An alternative syntax might look like the following:

def mutate_x_but_not_y(x: InOut[i32], y: In[i32]) -> tuple[i32, Out[i32]]:
    x = 3 * y;
    return SUCCESS, x
czgdp1807 commented 1 year ago

In my opinion, pass by reference should be implemented for all the types like list, dict and not just tuples. It should only be done for the arguments specified by the user. Doing it by an automatic ASR pass might create problems when the function call is a part of bigger expression. In fact, we faced this problem when implementing ASR to Julia code generation in LFortran.