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

eof: Fix stack height calculation for non-returning function. #15636

Merged
merged 2 commits into from
Dec 18, 2024

Conversation

rodiazet
Copy link
Contributor

@rodiazet rodiazet commented Dec 10, 2024

OptimizedEVMCodeTransform requires Assembly stack height for non-returning function call to be equal to rets-args. For EVM Assembly for EOF it's calculated based on CodeSection inputs and outputs. It means that we need to store number of rets for a code section equal to number of return values based in function declaration. Additional flag canContinue is needed to properly generate code section type in EOF container and to decide if we should use JumpF or CallF when creating AssemblyItem for the function call.

Depends on #15635. Merged.

This comment was marked as resolved.

@cameel cameel added has dependencies The PR depends on other PRs that must be merged first EOF labels Dec 11, 2024
@rodiazet rodiazet force-pushed the eof-fix-stack-height-for-non-ret branch from c74a224 to 909e56a Compare December 11, 2024 10:05
@cameel cameel removed the has dependencies The PR depends on other PRs that must be merged first label Dec 11, 2024
@rodiazet rodiazet force-pushed the eof-fix-stack-height-for-non-ret branch from 909e56a to c926671 Compare December 12, 2024 09:44
cameel
cameel previously approved these changes Dec 13, 2024
Copy link
Member

@cameel cameel left a comment

Choose a reason for hiding this comment

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

Approving since this seems correct and none of the comments below are critical.

Still, reading this made me realize that we probably have another bug in stack height calculation, this time in calculateMaxStackHeight() because it does not take into account terminating instructions.

We could also use some tweaks to asserts and naming.

libevmasm/Assembly.cpp Outdated Show resolved Hide resolved
libevmasm/AssemblyItem.h Outdated Show resolved Hide resolved
@@ -249,7 +249,7 @@ size_t AssemblyItem::returnValues() const
return 1;
case JumpF:
case CallF:
return functionSignature().canContinue() ? functionSignature().retsNum : 0;
return functionSignature().retsNum;
Copy link
Member

Choose a reason for hiding this comment

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

This made me realize something: in Assembly::calculateMaxStackHeight() we're applying the stack height change even for terminating instructions, especially JUMPF, but the spec say:

terminating instructions do not need to update stack heights.

This must be making the calculation wrong in some cases and I'm also not confident that our semantic tests would catch that, so it would be good to cover that with some extra tests. For example a non-returning JUMPF with tons of inputs should affect the max height in a way that cause a mismatch with EVM's validation.

Fixing that might be a separate PR though. It's related to this one since it's also stack height calculation but I guess we could consider it a separate bug.

libyul/backends/evm/NoOutputAssembly.cpp Outdated Show resolved Hide resolved
libyul/backends/evm/OptimizedEVMCodeTransform.cpp Outdated Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

Just to make sure I understand what we're fixing here. inline_assembly_in_modifiers.sol triggers this assert:

yulAssert(m_assembly.stackHeight() == static_cast<int>(m_stack.size()), "");

The assert failing means that Assembly::stackHeight() (which is calculated by adding deposit(), i.e. returnValue() - arguments(), for each appended assembly item) does not match the stack height tracked internally by the Yul->EVM transform.

And the mismatch comes from the fact that for non-returning calls the transform uses the number of returns declared by the function while the assembly uses 0. The fix here is to change the calculation in assembly to use the number from function definition as well.

This means that in AssemblyItem::FunctionSignature the number of returns will never be 0x80, but the actual number of returns. Since AssemblyItem::returnValues() was the only place that checked for that special value, canContinue() is now unused and can be removed.

On the other hand in Assembly::CodeSection we never need to know the number of returns of non-returning functions so we could have left it as is. But changing it to match FunctionSignature and replacing the special value with a canContinue flag makes things simpler (e.g. we don't need to special-case it when comparing them).

Am I missing anything?

Copy link
Contributor Author

@rodiazet rodiazet Dec 13, 2024

Choose a reason for hiding this comment

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

You get it right. Potentially we could leave CodeSection definition unchanged because the flag canContinue (nonReturning) is needed only to decide on CallF or JumpF when adding function call but I would leave it as it is now. It's clearer IMO.

libevmasm/Assembly.h Outdated Show resolved Hide resolved
@rodiazet rodiazet force-pushed the eof-fix-stack-height-for-non-ret branch 3 times, most recently from f9fd98b to 8ec746d Compare December 17, 2024 09:36
cameel
cameel previously approved these changes Dec 18, 2024
Copy link
Member

@cameel cameel left a comment

Choose a reason for hiding this comment

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

Just one more assert to get rid of. Then we can merge.

@rodiazet rodiazet force-pushed the eof-fix-stack-height-for-non-ret branch from f01e864 to e45133c Compare December 18, 2024 20:58
@cameel cameel merged commit dd92972 into ethereum:develop Dec 18, 2024
73 checks passed
@rodiazet rodiazet deleted the eof-fix-stack-height-for-non-ret branch December 19, 2024 08:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants