Skip to content

Implement List return in LLVM #955

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 18, 2022
Merged

Conversation

Smit-create
Copy link
Collaborator

No description provided.

@Smit-create Smit-create requested a review from czgdp1807 August 12, 2022 07:32
@czgdp1807
Copy link
Collaborator

This needs to wait till #952 is merged.

@czgdp1807
Copy link
Collaborator

@Smit-create Update your branch from main and then try to implement the feature.

@@ -49,9 +49,29 @@ def test_list_02_string():
for i in range(50):
assert x[i] == y[i]


def foo(x: i32) -> list[i32]:
Copy link
Collaborator

@czgdp1807 czgdp1807 Aug 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a trivial list. Try returning list[list[f64]], list[tuple[f64, i32, str]], tuple[list[f64], list[str]] and list[tuple[list[f64], list[c64]]]. Its very easy to make mistakes and hard code for intrinsic data types like i32, f64. Things change when we return nested data types. Take your time, test for above combinations I suggested. If it works then great otherwise I will help.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can take inspiration from integration_tests/test_list_06.py, integration_tests/test_list_05.py and integration_tests/test_list_07.py.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I slept on the idea of implementing this and found out that we need to make a design choice. The choice will be applicable to any data structure defined by a struct (i.e., lists, tuples, class, excluding arrays though because they are already handled in array_op ASR pass). First let's note a couple of points,

  1. Data structures defined by struct should be returned by value - We cannot return pointers to struct especially for those which are created inside the function body. The reason is simple. For example in LLVM backend, we will call struct_ptr = builder->CreateAlloca(some_struct_type, nullptr) inside the function body and as soon as the function call will complete and we will return to the caller scope, struct_ptr will become invalid (because call stack will not be active anymore).
  2. To access any element of a struct we need to use pointer to it in LLVM backend - LLVM only allows accessing elements of a struct if we use a pointer to it. For example if list is a struct type, then you cannot call builder->CreateGEP(list, idx) or builder->CreateInBoundsGEP(list, idx). You must do, builder->CreateGEP(list*, idx) or builder->CreateInBoundsGEP(list*, idx).

So, as you can see returning pointers to struct is not possible and structs are only usable if we have pointers to them. So, to satisfy both the constraints there are two options which I can think of,

  1. Create a temporary variable and store the returned struct into it - Let's see the code directly,
llvm::Value* CreateCallUtil(llvm::Function* fn, std::vector<llvm::Value*>& args, ASR::ttype_t* asr_return_type) {
    llvm::Value* return_value = builder->CreateCall(fn, args);
    if( !LLVM::is_llvm_struct(asr_return_type) ) {
        return return_value;
    }
    
    llvm::Value* pointer_to_struct = builder->CreateAlloca(fn->getFunctionType()->getReturnType(), nullptr); // Call to LLVM APIs not needed to fetch the return type of the function. We can use asr_return_type as well but anyways for compactness I did it here.
    LLVM::CreateStore(*builder, return_value, pointer_to_struct);
    return pointer_to_struct;
}

So you can see how we create a pointer to struct and then store the struct returned by value into it. We can replace all, builder->CreateCall(fn, args) with the call to above function everywhere. This idea already works in main (see https://github.com/lcompilers/lpython/blob/main/integration_tests/test_tuple_02.py) but is not generalised to all types yet.

  1. Create an ASR to ASR pass to convert all functions returning list, tuple, class or anything defined by a struct in the backend to a function not returning anything and accepting the return value as the last argument with intent(out). We do this for arrays already (they are defined by a struct in C/C++ and LLVM backend). We can extend the same to list, tuple, class types as well. We already have the infrastructure to do this.

So what should we do? IMO, choice 1 is quick and easy, and it already works we just have to generalise it. Its very small code to write as well. Choice 2 that is ASR to ASR pass requires visiting the whole ASR again so higher overhead as compared to Choice 1 but it is sharable across all backends. I think we should go for choice 1 for now and then in future if we see it working then we can add an ASR to ASR pass to do the same job. @certik What do you say?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, Choise 1 seems like the way to go for now.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs some involved effort. @Smit-create Will you mind if I push the remaining commits in your PR sometime this week?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, no problem!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Benchmark updated in #857 (comment) for my MBP. No slowdowns observed.

@czgdp1807 czgdp1807 marked this pull request as draft August 15, 2022 15:37
@czgdp1807 czgdp1807 marked this pull request as ready for review August 16, 2022 07:13
@czgdp1807
Copy link
Collaborator

@certik This is ready for review.

@czgdp1807 czgdp1807 requested a review from certik August 16, 2022 07:23
@czgdp1807 czgdp1807 dismissed their stale review August 16, 2022 07:24

Resolved and review requested from @certik


tensor = (deepcopy(mat), deepcopy(vec))

return tensors
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I personally do not like using the term tensor to mean multidimensional array, but we can change that later.)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

Copy link
Contributor

@certik certik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me.

@certik
Copy link
Contributor

certik commented Aug 17, 2022

@czgdp1807 or @Smit-create go ahead and merge this if it is ready.

@czgdp1807
Copy link
Collaborator

I will confirm once that this works with changes from main. Once that happens I will merge.

czgdp1807 and others added 2 commits August 18, 2022 11:32
1. Generate correct LLVM type for ListConstant
2. Handle lists as return type

Co-authored-by: Smit Lunagariya <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants