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

C++: Implement proper coroutine support in IR #16301

Merged
merged 4 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions cpp/ql/lib/semmle/code/cpp/exprs/Expr.qll
Original file line number Diff line number Diff line change
Expand Up @@ -1338,6 +1338,24 @@ class CoAwaitExpr extends UnaryOperation, @co_await {
override string getOperator() { result = "co_await" }

override int getPrecedence() { result = 16 }

/**
* Gets the Boolean expression that is used to decide if the enclosing
* coroutine should be suspended.
*/
Expr getAwaitReady() { result = this.getChild(1) }

/**
* Gets the expression that represents the resume point if the enclosing
* coroutine was suspended.
*/
Expr getAwaitResume() { result = this.getChild(2) }

/**
* Gets the expression that is evaluated when the enclosing coroutine is
* suspended.
*/
Expr getAwaitSuspend() { result = this.getChild(3) }
}

/**
Expand All @@ -1352,6 +1370,24 @@ class CoYieldExpr extends UnaryOperation, @co_yield {
override string getOperator() { result = "co_yield" }

override int getPrecedence() { result = 2 }

/**
* Gets the Boolean expression that is used to decide if the enclosing
* coroutine should be suspended.
*/
Expr getAwaitReady() { result = this.getChild(1) }

/**
* Gets the expression that represents the resume point if the enclosing
* coroutine was suspended.
*/
Expr getAwaitResume() { result = this.getChild(2) }

/**
* Gets the expression that is evaluated when the enclosing coroutine is
* suspended.
*/
Expr getAwaitSuspend() { result = this.getChild(3) }
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ newtype TInstructionTag =
ImplicitDestructorTag(int index) {
exists(Expr e | exists(e.getImplicitDestructorCall(index))) or
exists(Stmt s | exists(s.getImplicitDestructorCall(index)))
}
} or
CoAwaitBranchTag()

class InstructionTag extends TInstructionTag {
final string toString() { result = getInstructionTagId(this) }
Expand Down Expand Up @@ -186,6 +187,8 @@ string getInstructionTagId(TInstructionTag tag) {
or
tag = BoolConversionCompareTag() and result = "BoolConvComp"
or
tag = ResultCopyTag() and result = "ResultCopy"
or
Comment on lines +190 to +191
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was a drive-by fix I did while debugging. This tag didn't have a result previously.

tag = LoadTag() and result = "Load" // Implicit load due to lvalue-to-rvalue conversion
or
tag = CatchTag() and result = "Catch"
Expand Down Expand Up @@ -263,4 +266,6 @@ string getInstructionTagId(TInstructionTag tag) {
exists(int index |
tag = ImplicitDestructorTag(index) and result = "ImplicitDestructor(" + index + ")"
)
or
tag = CoAwaitBranchTag() and result = "CoAwaitBranch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -1259,9 +1259,7 @@ class TranslatedUnaryExpr extends TranslatedSingleInstructionExpr {
expr instanceof NotExpr or
expr instanceof ComplementExpr or
expr instanceof UnaryPlusExpr or
expr instanceof UnaryMinusExpr or
expr instanceof CoAwaitExpr or
expr instanceof CoYieldExpr
expr instanceof UnaryMinusExpr
}

final override Instruction getFirstInstruction(EdgeKind kind) {
Expand Down Expand Up @@ -1301,19 +1299,153 @@ class TranslatedUnaryExpr extends TranslatedSingleInstructionExpr {
expr instanceof UnaryPlusExpr and result instanceof Opcode::CopyValue
or
expr instanceof UnaryMinusExpr and result instanceof Opcode::Negate
or
// TODO: Use a new opcode to represent "awaiting the value"
expr instanceof CoAwaitExpr and result instanceof Opcode::CopyValue
or
// TODO: Use a new opcode to represent "awaiting the value"
expr instanceof CoYieldExpr and result instanceof Opcode::CopyValue
}

private TranslatedExpr getOperand() {
result = getTranslatedExpr(expr.(UnaryOperation).getOperand().getFullyConverted())
}
}

/**
* IR translation of a `co_await` or `co_yield` expression.
*
* The translation of `x = co_await ...` is essentially:
* ```cpp
* if (!awaiter.await_ready()) {
* awaiter.await_suspend();
* }
* x = awaiter.await_resume();
* ```
* where `awaiter` is an object constructed from programmer-supplied
* input, and for IR construction purposes these are resolved by the C/C++
* front-end.
*
* See https://en.cppreference.com/w/cpp/language/coroutines#co_await for the
* specification on how `awaiter` is obtained.
*/
abstract private class TranslatedCoExpr extends TranslatedNonConstantExpr {
/** Gets the operand of this operation. */
abstract Expr getOperand();

/**
* Gets the expression that decides if the enclosing coroutine should be
* suspended.
*/
abstract Expr getAwaitReady();

/**
* Gets the expression that is evaluated when the enclosing coroutine is
* suspended.
*/
abstract Expr getAwaitSuspend();

/**
* Gets the expression that represents the resume point if the enclosing
* coroutine was suspended.
*/
abstract Expr getAwaitResume();

final override Instruction getFirstInstruction(EdgeKind kind) {
result = this.getTranslatedOperand().getFirstInstruction(kind)
}

override Instruction getALastInstructionInternal() {
result = this.getTranslatedAwaitResume().getALastInstruction()
}

final override TranslatedElement getChildInternal(int id) {
id = 0 and result = this.getTranslatedOperand()
or
id = 1 and result = this.getTranslatedAwaitReady()
or
id = 2 and result = this.getTranslatedAwaitResume()
or
id = 3 and result = this.getTranslatedAwaitSuspend()
}

final override Instruction getInstructionSuccessorInternal(InstructionTag tag, EdgeKind kind) {
tag = CoAwaitBranchTag() and
(
kind instanceof TrueEdge and
result = this.getTranslatedAwaitResume().getFirstInstruction(any(GotoEdge goto))
or
kind instanceof FalseEdge and
result = this.getTranslatedAwaitSuspend().getFirstInstruction(any(GotoEdge goto))
)
}

override Instruction getResult() { result = this.getTranslatedAwaitResume().getResult() }

final override Instruction getChildSuccessorInternal(TranslatedElement child, EdgeKind kind) {
child = this.getTranslatedOperand() and
result = this.getTranslatedAwaitReady().getFirstInstruction(kind)
or
child = this.getTranslatedAwaitReady() and
kind instanceof GotoEdge and
result = this.getInstruction(CoAwaitBranchTag())
or
child = this.getTranslatedAwaitSuspend() and
result = this.getTranslatedAwaitResume().getFirstInstruction(kind)
or
child = this.getTranslatedAwaitResume() and
result = this.getParent().getChildSuccessor(this, kind)
}

override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType resultType) {
tag = CoAwaitBranchTag() and
opcode instanceof Opcode::ConditionalBranch and
resultType = getVoidType()
}

override Instruction getInstructionRegisterOperand(InstructionTag tag, OperandTag operandTag) {
tag = CoAwaitBranchTag() and
operandTag instanceof ConditionOperandTag and
result = this.getTranslatedAwaitReady().getResult()
}

private TranslatedExpr getTranslatedOperand() {
result = getTranslatedExpr(this.getOperand().getFullyConverted())
}

private TranslatedExpr getTranslatedAwaitReady() {
result = getTranslatedExpr(this.getAwaitReady().getFullyConverted())
}

private TranslatedExpr getTranslatedAwaitResume() {
result = getTranslatedExpr(this.getAwaitResume().getFullyConverted())
}

private TranslatedExpr getTranslatedAwaitSuspend() {
result = getTranslatedExpr(this.getAwaitSuspend().getFullyConverted())
}
}

/** IR translation of `co_await`. */
Dismissed Show dismissed Hide dismissed
class TranslatedCoAwaitExpr extends TranslatedCoExpr {
override CoAwaitExpr expr;

final override Expr getOperand() { result = expr.getOperand() }

final override Expr getAwaitReady() { result = expr.getAwaitReady() }

final override Expr getAwaitSuspend() { result = expr.getAwaitSuspend() }

final override Expr getAwaitResume() { result = expr.getAwaitResume() }
}

/** IR translation of `co_yield`. */
Dismissed Show dismissed Hide dismissed
class TranslatedCoYieldxpr extends TranslatedCoExpr {
override CoYieldExpr expr;

final override Expr getOperand() { result = expr.getOperand() }

final override Expr getAwaitReady() { result = expr.getAwaitReady() }

final override Expr getAwaitSuspend() { result = expr.getAwaitSuspend() }

final override Expr getAwaitResume() { result = expr.getAwaitResume() }
}

abstract class TranslatedConversion extends TranslatedNonConstantExpr {
override Conversion expr;

Expand Down
Loading
Loading