Skip to content

Implement if-else in wasm_to_x86 backend #1264

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 7 commits into from
Nov 10, 2022

Conversation

Thirumalai-Shaktivel
Copy link
Collaborator

Related #1222

while (cur_byte != 0x0B) {
if (cur_byte == 0x05) {
cur_byte = wasm::read_b8(code, offset_);
if (cur_byte == 0x0B || cur_byte == 0x41) {
Copy link
Collaborator Author

@Thirumalai-Shaktivel Thirumalai-Shaktivel Nov 4, 2022

Choose a reason for hiding this comment

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

I have a doubt:
Does wasm always have an else block (even though else is empty) in the if statement?
image
@Shaikh-Ubaid
If so, how Assert is handled in asr_to_wasm?

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 asked this because, the else block is not empty in the visit_assert(), it has 0x41

Copy link
Collaborator

Choose a reason for hiding this comment

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

Does wasm always have an else block (even though else is empty) in the if statement?

Currently, if there is no else block, then in the wat generated, we would just have if <some instructions> end.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I asked this because, the else block is not empty in the visit_assert(), it has 0x41

Do you mean visit_Assert() in ASR->WASM backend?

Copy link
Collaborator Author

@Thirumalai-Shaktivel Thirumalai-Shaktivel Nov 4, 2022

Choose a reason for hiding this comment

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

Nah, After the wasm code is generated and captured in the wasm->x86

Comment on lines 81 to 91
while (cur_byte != 0x0B) {
if (cur_byte == 0x05) {
cur_byte = wasm::read_b8(code, offset_);
if (cur_byte == 0x0B || cur_byte == 0x41) {
return false;
} else {
return true;
}
}
cur_byte = wasm::read_b8(code, offset_);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

But this logic, detects the else block, even for the following syntax!!

if True:
	pass

Copy link
Collaborator Author

@Thirumalai-Shaktivel Thirumalai-Shaktivel Nov 4, 2022

Choose a reason for hiding this comment

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

That is the reason, I check the next byte of the visit_else to be 0x0B (which checks the end of the if stmt).

Copy link
Collaborator

Choose a reason for hiding this comment

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

At present, if there is no else block, then visit_Else() should not be visited. I guess, we might be missing something.

@ubaidsk
Copy link
Collaborator

ubaidsk commented Nov 4, 2022

I looked into the PR. It seems, it would be better if wasm backend adds an else block always (it would be empty when it is not needed).

If an else is always present, then there would not be any need of checking if any else is present (since, it would be always present (although empty when not needed)).

@ubaidsk
Copy link
Collaborator

ubaidsk commented Nov 4, 2022

I will rebase this on top of #1262 (so that we can print some value inside the if-else ladder), make the necessary updates to the wasm backend and push to this PR.

@Thirumalai-Shaktivel
Copy link
Collaborator Author

This looks great! Thanks for updating.

@ubaidsk
Copy link
Collaborator

ubaidsk commented Nov 4, 2022

I will rebase this on top of #1262

I rebased on top of latest main. Rebasing on top of #1262 is including many commits of that PR (it also makes this PR dependent on it).

@Thirumalai-Shaktivel
Copy link
Collaborator Author

Let's implement visit_Assert() in this PR itself.
Assertion in wasm_x86, behaves differently!

def Main0():
    assert 1 == 1

Main0()

Output:

$ lpython examples/expr2.py --backend wasm_x86 && ./a.out
AssertionError
def Main0():
    assert 0 == 1

Main0()
$ lpython examples/expr2.py --backend wasm_x86 && ./a.out

Comment on lines 2106 to 2113
void visit_Assert(const ASR::Assert_t &x) {
this->visit_expr(*x.m_test);
wasm::emit_i32_eqz(m_code_section, m_al);
wasm::emit_b8(m_code_section, m_al, 0x04); // emit if start
wasm::emit_b8(m_code_section, m_al, 0x40); // empty block type
wasm::emit_b8(m_code_section, m_al, 0x05); // starting of else
if (x.m_msg) {
std::string msg =
ASR::down_cast<ASR::StringConstant_t>(x.m_msg)->m_s;
Copy link
Collaborator

Choose a reason for hiding this comment

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

The previous version was more correct, right? The previous version would say -> If Assert condition failed, raise an error (which is what assert does, I think).

Copy link
Collaborator Author

@Thirumalai-Shaktivel Thirumalai-Shaktivel Nov 6, 2022

Choose a reason for hiding this comment

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

The main motive for these changes was that visit_I32Eqz() in the wasm_to_x86 is not working correctly.
Here, the changes imply:

If True:
	// Do nothing
else:
	print("AssertionError")
	quit(1)

which is similar to assert False

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The same approach as we do in the LLVM backend:

void visit_Assert(const ASR::Assert_t &x) {
this->visit_expr_wrapper(x.m_test, true);
create_if_else(tmp, []() {}, [=]() {
if (x.m_msg) {
char* s = ASR::down_cast<ASR::StringConstant_t>(x.m_msg)->m_s;
llvm::Value *fmt_ptr = builder->CreateGlobalStringPtr("AssertionError: %s\n");
llvm::Value *fmt_ptr2 = builder->CreateGlobalStringPtr(s);
print_error(context, *module, *builder, {fmt_ptr, fmt_ptr2});
} else {
llvm::Value *fmt_ptr = builder->CreateGlobalStringPtr("AssertionError\n");
print_error(context, *module, *builder, {fmt_ptr});
}
int exit_code_int = 1;
llvm::Value *exit_code = llvm::ConstantInt::get(context,
llvm::APInt(32, exit_code_int));
exit(context, *module, *builder, exit_code);
});
}

Comment on lines 2117 to 2123
}
wasm::emit_i32_const(m_code_section, m_al, 1); // non-zero exit code
exit();
wasm::emit_b8(m_code_section, m_al, 0x05); // starting of else
wasm::emit_expr_end(m_code_section, m_al); // emit if end
}
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

The else portion was empty in the previous version.

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.

The right way to support Assert would instead be to implement the visit_I32Eqz in the WASM->X86 backend.

void visit_I32Eqz() {
        m_a.asm_pop_r32(X86Reg::eax);
        m_a.asm_mov_r32_imm32(X86Reg::ebx, 0U);
        m_a.asm_cmp_r32_r32(X86Reg::eax, X86Reg::ebx);
        m_a.asm_push_r32(X86Reg::eax);
    }

@certik
Copy link
Contributor

certik commented Nov 5, 2022

Thanks @Thirumalai-Shaktivel for working on this, very helpful. Thanks @Shaikh-Ubaid for the review.

@Thirumalai-Shaktivel
Copy link
Collaborator Author

Thirumalai-Shaktivel commented Nov 6, 2022

The right way to support Assert would instead be to implement the visit_I32Eqz in the WASM->X86 backend.

void visit_I32Eqz() {
        m_a.asm_pop_r32(X86Reg::eax);
        m_a.asm_mov_r32_imm32(X86Reg::ebx, 0U);
        m_a.asm_cmp_r32_r32(X86Reg::eax, X86Reg::ebx);
        m_a.asm_push_r32(X86Reg::eax);
    }

This seems not working in the wasm_x86 backend.

@Thirumalai-Shaktivel
Copy link
Collaborator Author

Thirumalai-Shaktivel commented Nov 6, 2022

What does I32Eqz do? isn't it a NOT operator?

@ubaidsk
Copy link
Collaborator

ubaidsk commented Nov 6, 2022

What does I32Eqz do? isn't it a NOT operator?

It checks if the stack top is equal to zero. Yes, it works similar to a not operator.

The same approach as we do in the LLVM backend:

void visit_Assert(const ASR::Assert_t &x) {
this->visit_expr_wrapper(x.m_test, true);
create_if_else(tmp, []() {}, [=]() {
if (x.m_msg) {
char* s = ASR::down_cast<ASR::StringConstant_t>(x.m_msg)->m_s;
llvm::Value *fmt_ptr = builder->CreateGlobalStringPtr("AssertionError: %s\n");
llvm::Value *fmt_ptr2 = builder->CreateGlobalStringPtr(s);
print_error(context, *module, *builder, {fmt_ptr, fmt_ptr2});
} else {
llvm::Value *fmt_ptr = builder->CreateGlobalStringPtr("AssertionError\n");
print_error(context, *module, *builder, {fmt_ptr});
}
int exit_code_int = 1;
llvm::Value *exit_code = llvm::ConstantInt::get(context,
llvm::APInt(32, exit_code_int));
exit(context, *module, *builder, exit_code);
});
}

If it is similar to the LLVM backend, we can follow it.

void visit_I32Eqz() {
        m_a.asm_pop_r32(X86Reg::eax);
        m_a.asm_mov_r32_imm32(X86Reg::ebx, 0U);
        m_a.asm_cmp_r32_r32(X86Reg::eax, X86Reg::ebx);
        m_a.asm_push_r32(X86Reg::eax);
}.

This seems not working in the wasm_x86 backend

It should (hopefully) work. I tested it locally on my system before sharing here. If we are following the LLVM approach, then we might not need it (I guess) and we can add it later when needed.

@Thirumalai-Shaktivel
Copy link
Collaborator Author

It should (hopefully) work. I tested it locally on my system before sharing here.

Were you able to get "AssertionError" for assert False?
This is weird, it didn't work for me!

@Thirumalai-Shaktivel
Copy link
Collaborator Author

Thirumalai-Shaktivel commented Nov 6, 2022

For the code:

def Test_assert_01():
    if True:
        assert True

    assert 1 != 1, "One is equal to one."

def Verify():
    Test_assert_01()

Verify()

I get:

$ lpython examples/expr2.py --backend wasm_x86 && ./a.out
Call to imported function with index 4 not yet supported
Call to imported function with index 4 not yet supported

@Thirumalai-Shaktivel
Copy link
Collaborator Author

Thirumalai-Shaktivel commented Nov 6, 2022

Sorry my bad, print_str doesn't work yet in this PR! I will rebase that PR and report back.

@ubaidsk
Copy link
Collaborator

ubaidsk commented Nov 6, 2022

Sorry my bad, print_str doesn't work yet in this PR! I will rebase that PR and report back.

Let's wait for the PRs to be merged. It seems many PRs are interdependent. Let's work on Assertion in another PR once all the necessary PRs are merged. For Assertion we would also be needing support for set_exit_code() (since, if the assertion failed, we should exit with code 1). It would be better if we support Assert separately, I think.

@Thirumalai-Shaktivel
Copy link
Collaborator Author

Thirumalai-Shaktivel commented Nov 6, 2022

Yup! Now, I feel the same.
I will submit another PR for Assert once the print_str is merged

@Thirumalai-Shaktivel
Copy link
Collaborator Author

Thank you for your approval and guidance!

@@ -16,6 +16,7 @@ class X86Visitor : public WASMDecoder<X86Visitor>,
public:
X86Assembler &m_a;
uint32_t cur_func_idx;
std::vector<uint32_t> unique_id;
Copy link
Contributor

Choose a reason for hiding this comment

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

Later:

Suggested change
std::vector<uint32_t> unique_id;
uint32_t unique_id;

void visit_EmtpyBlockType() {}

void visit_If() {
unique_id.push_back(offset);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
unique_id.push_back(offset);
unique_id++;
uint32_t label = unique_id;

m_a.asm_cmp_r32_imm8(LFortran::X86Reg::eax, 1);
m_a.asm_je_label(".then_" + std::to_string(unique_id.back()));
m_a.asm_jmp_label(".else_" + std::to_string(unique_id.back()));
m_a.add_label(".then_" + std::to_string(unique_id.back()));
Copy link
Contributor

Choose a reason for hiding this comment

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

For later (but probably quite soon): we should not be using strings for labels, but just unique integers. The unique integer can just be a counter in this visitor, just an integer, see above.

Copy link
Contributor

Choose a reason for hiding this comment

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

(we might need to have a stack of unique ids...)

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.

Looks good to me, thanks!

@certik certik merged commit 272951e into lcompilers:main Nov 10, 2022
@Thirumalai-Shaktivel Thirumalai-Shaktivel deleted the if_stmt branch November 11, 2022 07:51
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