Skip to content
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

Improve exception handling in memory.grow #737

Merged
merged 1 commit into from
Feb 23, 2021
Merged

Conversation

gumb0
Copy link
Collaborator

@gumb0 gumb0 commented Feb 16, 2021

No description provided.

@codecov
Copy link

codecov bot commented Feb 16, 2021

Codecov Report

Merging #737 (3864569) into master (25205ab) will decrease coverage by 0.09%.
The diff coverage is 50.00%.

@@            Coverage Diff             @@
##           master     #737      +/-   ##
==========================================
- Coverage   99.35%   99.26%   -0.10%     
==========================================
  Files          73       73              
  Lines       11388    11398      +10     
==========================================
- Hits        11315    11314       -1     
- Misses         73       84      +11     
Flag Coverage Δ
rust 99.85% <ø> (ø)
spectests 90.68% <50.00%> (-0.41%) ⬇️
unittests 99.22% <50.00%> (-0.11%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
lib/fizzy/execute.cpp 98.70% <50.00%> (-1.30%) ⬇️

{
ret = static_cast<uint32_t>(-1);
}
catch (const std::length_error&)
Copy link
Collaborator Author

@gumb0 gumb0 Feb 16, 2021

Choose a reason for hiding this comment

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

Doesn't look possible to cover it, for me std::string::max_size() returns 2^62-1 bytes (2^46 pages), we can't allocate more than that with wasm.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this is worth having considering max_size() is not very useful.
It may be just better to add a unit test that checks if wasm memory max size is not smaller than std::string::max_size().
Alternatively, you can catch std::exception here.

Copy link
Collaborator

Choose a reason for hiding this comment

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

On 32-bit build the value is 2^31-1.

@gumb0 gumb0 requested review from chfast and axic February 16, 2021 20:08
@gumb0 gumb0 force-pushed the memory-grow-exceptions branch from d5e66c6 to 3951f10 Compare February 17, 2021 10:49
@@ -840,7 +840,7 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, const Value* args,
throw std::bad_alloc();
memory->resize(new_pages * PageSize);
}
catch (std::bad_alloc const&)
catch (const std::exception&)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add a comment that std::bad_alloc and std::legth_error are expected.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The ... catches everything so it guarantees noexcept outside. The std::exception does not, so there will be another compiler generated check ending in std::terminate or something.

So my final call is to use ... and also add an assert that the exception is one of the two expected types.

Although, checking the exception type may be tricky. Probably a helper is needed as presented in https://en.cppreference.com/w/cpp/error/rethrow_exception

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Well then in the helper catch std::length_error will still be not covered, it's simpler then to have here catch (bad_alloc) + catch (length_error) + catch (...) { 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.

Well then in the helper catch std::length_error will still be not covered, it's simpler then to have here catch (bad_alloc) + catch (length_error) + catch (...) { assert(false); } .

But yeah execute will be smaller if extra catches go into helper, and helper will should be omitted in release build...

@gumb0 gumb0 force-pushed the memory-grow-exceptions branch from 3951f10 to 71e4ac2 Compare February 17, 2021 11:06
@gumb0 gumb0 force-pushed the memory-grow-exceptions branch from 71e4ac2 to ce2bca6 Compare February 22, 2021 20:21
@@ -499,6 +499,25 @@ inline bool invoke_function(const FuncType& func_type, uint32_t func_idx, Instan
return true;
}

[[maybe_unused]] bool is_resize_exception(std::exception_ptr exception)
Copy link
Member

Choose a reason for hiding this comment

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

Nice! Perhaps you want to add a comment here explaining our use case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added a comment.

@@ -840,8 +859,9 @@ ExecutionResult execute(Instance& instance, FuncIdx func_idx, const Value* args,
throw std::bad_alloc();
Copy link
Member

Choose a reason for hiding this comment

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

Should this use std::bad_alloc or std::length_error ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

The bad_alloc is better but not perfect fit out of these two.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Actually, here I'd see another refactoring: do not use throw.

uint32_t ret = (new_pages <= instance.memory_pages_limit) ? static_cast<uint32_t>(cur_pages) : static_cast<uint32_t>(-1);
try {
  memory->resize(new_pages * PageSize);
} catch (...) { ... }

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It shouldn't go to resize though, if it's over the limit.
I can make a version with if, but it looks somewhat uglier.

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 bad_alloc is better but not perfect fit out of these two.

Why it it better and what's the "fit" here? I'd say requirement for memory.grow to fail in case requested size is out of bounds is similar to requirement for basic_string to throw length_error in case count > max_size().
bad_alloc represents failure of some lower level allocation mechanism.

But I think in practice it doesn't matter and I'm fine with either.

@gumb0 gumb0 force-pushed the memory-grow-exceptions branch from ce2bca6 to 7d5cd5a Compare February 23, 2021 10:50
Copy link
Collaborator

@chfast chfast left a comment

Choose a reason for hiding this comment

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

Not very beautiful, but I think this is fine.

It is rather visible that some Memory abstraction type would be nice. This can be done together with "guarded pages" later.

There is also an issue on 32-bit architectures where string/bytes can only allocate up to 2GB of memory.

}
catch (std::bad_alloc const&)
{
uint32_t ret;
Copy link
Member

@axic axic Feb 23, 2021

Choose a reason for hiding this comment

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

Hmm, wondering if the following would be more readable:

uint32_t ret = static_cast<uint32_t>(-1);
if (new_pages <= instance.memory_pages_limit)
{
  try
  {
    memory->resize(new_pages * PageSize);
    ret = static_cast<uint32_t>(cur_pages);
  }
  catch (...)
  {
    assert(is_resize_exception(std::current_exception()));
  }
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks fine, I don't mind.

The best is to wrap this into uint32_t Memory::grow(something);. Then you eliminate ret housekeeping.

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 can move it into a simple helper for now.

@gumb0 gumb0 force-pushed the memory-grow-exceptions branch from 6af194f to 9d92654 Compare February 23, 2021 18:15
ret = static_cast<uint32_t>(-1);
}
stack.push(ret);
stack.push(grow_memory(*memory, delta, instance.memory_pages_limit));
Copy link
Member

Choose a reason for hiding this comment

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

At this point could just use the stack pointer and no pop/push is needed 🙊

@gumb0 gumb0 force-pushed the memory-grow-exceptions branch from 9d92654 to c43e129 Compare February 23, 2021 18:27
}
}

inline uint32_t grow_memory(
Copy link
Member

Choose a reason for hiding this comment

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

If we are going with this helper, please document it. What is important to document that it returns page count prior to expansion.

@gumb0 gumb0 force-pushed the memory-grow-exceptions branch from c43e129 to 3864569 Compare February 23, 2021 18:37
Copy link
Member

@axic axic left a comment

Choose a reason for hiding this comment

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

I wonder how this affects benchmarks as it could mean quite a different binary layout 🙊

@gumb0 gumb0 requested a review from chfast February 23, 2021 18:39
@gumb0 gumb0 merged commit 47401da into master Feb 23, 2021
@gumb0 gumb0 deleted the memory-grow-exceptions branch February 23, 2021 19:35
@gumb0 gumb0 mentioned this pull request Feb 24, 2021
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