Skip to content

transform (coroutines): fix memory corruption for tail calls that reference stack allocations #2117

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
Sep 21, 2021

Conversation

niaow
Copy link
Member

@niaow niaow commented Sep 15, 2021

This change fixes a bug in which alloca memory lifetimes would not extend past the suspend of an asynchronous tail call. This would typically manifest as memory corruption, and could happen with or without normal suspending calls within the function.

Fixes a bug from #2101

…erence stack allocations

This change fixes a bug in which `alloca` memory lifetimes would not extend past the suspend of an asynchronous tail call.
This would typically manifest as memory corruption, and could happen with or without normal suspending calls within the function.
@niaow niaow marked this pull request as draft September 15, 2021 18:32
@niaow niaow marked this pull request as ready for review September 15, 2021 18:33
Copy link
Member

@aykevl aykevl 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, apart from those two comments.

@@ -600,11 +600,11 @@ func (c *coroutineLoweringPass) lowerFuncsPass() {
continue
}

if len(fn.normalCalls) == 0 {
// No suspend points. Lower without turning it into a coroutine.
if len(fn.normalCalls) == 0 && fn.fn.FirstBasicBlock().FirstInstruction().IsAAllocaInst().IsNil() {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think alloca instructions are always at the start of the entry block. I think it would be safer to check the entire entry block for alloca instructions, just in case some are not the first instruction.

(Technically they can be anywhere in the function but we check in other places that this isn't possible).

Copy link
Member Author

@niaow niaow Sep 15, 2021

Choose a reason for hiding this comment

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

If there are any alloca instructions that are not at the start of the entry block, the coroutine lowering pass crashes iirc.

Copy link
Member

Choose a reason for hiding this comment

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

IIRC it crashes if they are not in the entry block. but I think they are allowed if they are not directly at the start of the entry block.

// Replace terminator with branch to cleanup.
// Replace terminator with a branch to the exit.
var exit llvm.BasicBlock
if ret.kind == returnNormal || ret.kind == returnVoid || fn.fn.FirstBasicBlock().FirstInstruction().IsAAllocaInst().IsNil() {
Copy link
Member

Choose a reason for hiding this comment

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

Same here.

@deadprogram
Copy link
Member

deadprogram commented Sep 17, 2021

@niaow any chance of addressing this feedback from @aykevl? I really really want to merge this! 😺

@niaow
Copy link
Member Author

niaow commented Sep 17, 2021

I added a pre-processing step which ensures that all allocas in the entry block exist at the start. I had previously made sure that this never happened because a while back we hit an issue where a basic block split moved some allocas out of the entry block.

@deadprogram
Copy link
Member

That seems like a good solution to me, @niaow

@aykevl are we good to merge?

Comment on lines +771 to +773
for !inst.IsAAllocaInst().IsNil() {
inst = llvm.NextInstruction(inst)
}
Copy link
Member

@aykevl aykevl Sep 21, 2021

Choose a reason for hiding this comment

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

I believe this won't work correctly if there are no alloca instructions in the entry block: it will eventually reach the last instruction in the block and crash on a nil pointer dereference.

Copy link
Member Author

Choose a reason for hiding this comment

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

The loop condition is the other way around. If there are no alloca instructions this will run 0 times.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, I see. I got confused by the double negation.

Comment on lines +775 to +784
// Find any other alloca instructions and move them after the other allocas.
c.builder.SetInsertPointBefore(inst)
for !inst.IsNil() {
next := llvm.NextInstruction(inst)
if !inst.IsAAllocaInst().IsNil() {
inst.RemoveFromParentAsInstruction()
c.builder.Insert(inst)
}
inst = next
}
Copy link
Member

Choose a reason for hiding this comment

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

This moves alloca instructions together, which I guess is fine, but I don't see how this addresses the issue I raised before?

I don't think alloca instructions are always at the start of the entry block. I think it would be safer to check the entire entry block for alloca instructions, just in case some are not the first instruction.

Copy link
Member Author

Choose a reason for hiding this comment

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

This moves all alloca instructions to the start of the entry block, so later the check does correctly apply to all allocas in the entry block. This also fixes the potential issues where an alloca could be moved by a later transformation into another block by SplitBasicBlock.

Copy link
Member

@aykevl aykevl left a comment

Choose a reason for hiding this comment

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

Ok, seems good to me!

Comment on lines +771 to +773
for !inst.IsAAllocaInst().IsNil() {
inst = llvm.NextInstruction(inst)
}
Copy link
Member

Choose a reason for hiding this comment

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

Oh, I see. I got confused by the double negation.

@aykevl aykevl merged commit 1573826 into tinygo-org:dev Sep 21, 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