Skip to content

Implement Loop statement #1290

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 5 commits into from
Nov 13, 2022
Merged

Conversation

Thirumalai-Shaktivel
Copy link
Collaborator

No description provided.

Comment on lines 132 to 143
void visit_Br(uint32_t /*label_index*/) {
// Branch is used to jump to the `loop.head`.
m_a.asm_jmp_label(".loop.head_" + unique_id.rbegin()[1]);
}

Copy link
Collaborator Author

@Thirumalai-Shaktivel Thirumalai-Shaktivel Nov 12, 2022

Choose a reason for hiding this comment

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

What is the use of branch?
@Shaikh-Ubaid

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I see, the label_index is not constant, it changes based on the statements inside the loop.

Copy link
Collaborator

Choose a reason for hiding this comment

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

What is the use of branch?

It is like a jump instruction. Everytime we wish to continue a loop in wasm, we need to branch (only if the loop condition is true) to the loop.

https://samrat.me/posts/2020-03-29-webassembly-control-instr-examples/ offers a good explanation.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I see, the label_index is not constant, it changes based on the statements inside the loop.

Yes, the label_index is used as the target of the branch.

// From WebAssembly Docs:
// Unlike with other index spaces, indexing of labels is relative by
// nesting depth, that is, label 0 refers to the innermost structured
// control instruction enclosing the referring branch instruction, while
// increasing indices refer to those farther out.

As shared in the above comment, the label index counts from the inner most block (A block could be a loop or an if-else construct). In our case, a label_index of 0 points to the if-else condition (which is the loop condition) and a label_index of 1 points to the actual loop.

Also, on branching to a loop, the control passes to the beginning of the loop, where as on branching to an if block, the control passes to the end of it.

Copy link
Collaborator

Choose a reason for hiding this comment

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

// Unlike with other index spaces, indexing of labels is relative by
// nesting depth, that is, label 0 refers to the innermost structured
// control instruction enclosing the referring branch instruction, while
// increasing indices refer to those farther out.

The following example should (hopefully) explain the above more clearly.

// Code in some dummy language, (might) not be exactly in `WebAssembly Text Format`

(loop // label index = 3
    (if // label index = 2
        (if // label index = 1
            (if // label index = 0
                br some_label_index
            )
        )
    )
) 

Depending on where we would like to branch, we can have appropriate value for some_label_index.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I got it, great explanation. Thanks

@Thirumalai-Shaktivel Thirumalai-Shaktivel marked this pull request as ready for review November 12, 2022 11:48
@Thirumalai-Shaktivel Thirumalai-Shaktivel added the ready for review PRs that are ready for review label Nov 12, 2022
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 is great. @Shaikh-Ubaid you should still review this.

@certik certik merged commit b67b871 into lcompilers:main Nov 13, 2022
@Thirumalai-Shaktivel Thirumalai-Shaktivel deleted the loop_stmt branch November 13, 2022 05:40
@Thirumalai-Shaktivel
Copy link
Collaborator Author

Thanks for reviewing and merging this PR

@ubaidsk
Copy link
Collaborator

ubaidsk commented Nov 13, 2022

@Shaikh-Ubaid you should still review this.

Yes, I will review this in the evening today.

Comment on lines +146 to +157
/*
The loop statement starts with `loop.head`. The `loop.body` and
`loop.branch` are enclosed within the `if.block`. If the condition
fails, the loop is exited through `else.block`.
.head
.If
# Statements
.Br
.Else
.endIf
.end
*/
Copy link
Collaborator

Choose a reason for hiding this comment

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

Great note, Thirumalai !. It explains the loop structure very beautifully. Thank you so much for it.

Comment on lines +135 to +141
if (if_unique_id.size() - loop_unique_id.size() == label_index - 1) {
// cycle/continue or loop.end
m_a.asm_jmp_label(".loop.head_" + loop_unique_id.back());
} else {
// exit/break
m_a.asm_jmp_label(".loop.end_" + loop_unique_id.back());
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

This if-else condition inside the visit_Br() is not clear to me. @Thirumalai-Shaktivel, please, could you possibly share how this if-else works and chooses whether to iterate the loop or end it?

Copy link
Collaborator Author

@Thirumalai-Shaktivel Thirumalai-Shaktivel Nov 14, 2022

Choose a reason for hiding this comment

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

Consider this example:

while i < 10:
    j = 0
    while j < 10:
        k += 1
        if i == 0:
            if j == 3:
                continue
            else:
                j += 1
        j += 1
    i += 1
assert k == 95

Output printed using:

diff --git a/src/libasr/codegen/wasm_to_x86.cpp b/src/libasr/codegen/wasm_to_x86.cpp
index 9db9d2b4b..48f947076 100644
--- a/src/libasr/codegen/wasm_to_x86.cpp
+++ b/src/libasr/codegen/wasm_to_x86.cpp
@@ -131,6 +131,9 @@ class X86Visitor : public WASMDecoder<X86Visitor>,
     void visit_EmtpyBlockType() {}
 
     void visit_Br(uint32_t label_index) {
+std::cout << "label_index: " << label_index << "\n";
+std::cout << "Loop_size: " << loop_unique_id.size() << "\n";
+std::cout << "If_size: " << if_unique_id.size() << "\n";
         // Branch is used to jump to the `loop.head` or `loop.end`.
$ lpython examples/expr2.py --backend wasm_x86 && ./expr2.out
label_index: 3
Loop_size: 2
If_size: 4
label_index: 1
Loop_size: 2
If_size: 2
label_index: 1
Loop_size: 1
If_size: 1

We have 2 loops and 2 if's. But in the backend, we get 2 loops and 4 ifs, right?
The first br comes from the continue, if we subtract 4 if's with 2 loops: we get the actual 2 if's. the label_index is 3, we do 3 - 1 = 2: jump to head and So on...

But if the break is encountered the label_index is 2, So we jump to the end of the loop
Any doubts, please ask

Copy link
Collaborator Author

@Thirumalai-Shaktivel Thirumalai-Shaktivel Nov 14, 2022

Choose a reason for hiding this comment

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

how this if-else works and chooses whether to iterate the loop or end it?

Every time, the loop is iterated using the Br. Also, I didn't analyze how the loop is being exited. I will look into it soon.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I see, the Loop is exited through the else block:

.loop.head_297:
    mov eax, [ebp-4]
    push eax
    push 0x0000000a
    pop ebx
    pop eax
    cmp eax, ebx
    jl .compare_1303
    push 0x00
    jmp .compare.end_303
.compare_1303:
    push 0x01
.compare.end_303:
    pop eax
    cmp eax, 0x01
    je .then_304
    jmp .else_304
.then_304:
    mov eax, [ebp-4]
    push eax
    push 0x00000001
    pop ebx
    pop eax
    add eax, ebx
    push eax
    pop eax
    mov [ebp-4], eax
    jmp .loop.head_297
    jmp .endif_304
.else_304:
.endif_304:
.loop.end_297:
    mov esp, ebp
    pop ebp
    ret

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I have a doubt, here the jump and label .endif_304 is useless? is it ok? @Shaikh-Ubaid

Copy link
Collaborator Author

@Thirumalai-Shaktivel Thirumalai-Shaktivel Nov 14, 2022

Choose a reason for hiding this comment

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

@Shaikh-Ubaid, were you able to understand or get the point?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I have a doubt, here the jump and label .endif_304 is useless? is it ok? @Shaikh-Ubaid

I think for the moment that should be fine.

@Shaikh-Ubaid, were you able to understand or get the point?

Yup, thank you so much for the explanation, Thirumalai.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems this condition (if_unique_id.size() - loop_unique_id.size() == label_index - 1) might not be perfect to determine to continue or end the loop. Consider the following example where we have a loop inside an if construct:

def main0():
    x: i32
    x = (2+3)*5
    j: i32 = 0
    if True:
        while x > 0:
            j += 2
            x -= 1
    print(x)
    print(j)

main0()

Output:

(lp) ubaid@ubaid-Lenovo-ideapad-330-15ARR:~/OpenSource/lpython$ lpython examples/expr2.py
0
50
(lp) ubaid@ubaid-Lenovo-ideapad-330-15ARR:~/OpenSource/lpython$ lpython examples/expr2.py --backend wasm_x86 -o loop
(lp) ubaid@ubaid-Lenovo-ideapad-330-15ARR:~/OpenSource/lpython$ ./loop 
24
2
(lp) ubaid@ubaid-Lenovo-ideapad-330-15ARR:~/OpenSource/lpython$ 

The loop in this case just executes once and then exits. This is because in this case (when visit_Br() is visited) the if count would be 2 and loop count would be 1. The label_index would be 1. The condition would be 2 - 1 == 1 - 1, that is 1 == 0, which is not true and hence the loop exits.

I think we might need to simulate how wasm works in x86. We might need to use same label indexes (or same label index space) for if's and loop's (just like wasm does).

Copy link
Collaborator Author

@Thirumalai-Shaktivel Thirumalai-Shaktivel Nov 14, 2022

Choose a reason for hiding this comment

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

It seems this condition (if_unique_id.size() - loop_unique_id.size() == label_index - 1) might not be perfect to determine to continue or end the loop.

+1; I agree with this.
Also, I didn't test the loop within the if statement.
There you go, you've found the actual bug in the loop statement.
Thank you for testing this. I will make this a new issue. Let's work on this and fix it.

@ubaidsk
Copy link
Collaborator

ubaidsk commented Nov 13, 2022

I wonder if we need the changes (replacing print statements with assert) in the file integration_tests/if_01.py. Previously, this if test case just had print statements. This was useful to us in the way that if we add a new backend (in the future) and if we add support for if-else in this new backend (with no support for exit code yet), we could easily enable this integration_tests/if_01.py. Now, with assert being used in this test case we would either need to write a new simple if-else test case or also add support for assert before enabling if-else test case.

It seems we write new backends less frequently, hence the above seems to be more of a discussion topic/point. I would like to request your views/suggestions on it.

Copy link
Collaborator

@ubaidsk ubaidsk left a comment

Choose a reason for hiding this comment

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

This is an Amazing PR, Thirumalai! Great Work! Thank you so much for this.

@Thirumalai-Shaktivel
Copy link
Collaborator Author

Thirumalai-Shaktivel commented Nov 14, 2022

While we were reviewing the if_stmt PR, @certik, said to replace the print with an assert statement. An assert statement would be the best approach to catch the error easily (in the CI). So, I replaced it.

@ubaidsk
Copy link
Collaborator

ubaidsk commented Nov 14, 2022

While we were reviewing the PR, @certik, said to replace the print with an assert statement. An assert statement would be the best approach to catch the error easily (in the CI). So, I replaced it.

Yup, got it.

This was useful to us in the way that if we add a new backend (in the future) and if we add support for if-else in this new backend (with no support for exit code yet), we could easily enable this integration_tests/if_01.py.

This is more about for future backends. Nothing to worry about at this point. When we add new backends, we would like to enable as many integration_tests as possible. In lpython, many (or most) of the lpython integration test files test multiple features in the same test. Thus, when we add a small feature (since, we would be building a backend incrementally), we are needing to add a new test case which just tests that particular feature. We are unable to enable other tests testing this feature because those tests also test some other feature for which we do not have support yet. I experienced this issue when adding the wasm backend and the wasm_x86 backend. I would like to request everyone's views on this.

@Thirumalai-Shaktivel
Copy link
Collaborator Author

Thirumalai-Shaktivel commented Nov 14, 2022

Exactly, I experienced the same issue, while implementing this PR. The problem was that in the integration_tests there is a file named loop_01.py. It had both integers and strings to iterate in the loop. As wasm_x86 doesn't support StringLen, I was not able to use it. I created a new file and added all the tests there.

Also, why don't we have more simpler tests?

I think all the if, loop, ... are being tested more robustly in LFortran.
Now, there are two options: either we add more tests in LPython or submit the PR for new backends to LFortran.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ready for review PRs that are ready for review wasm_x86
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants