Skip to content

[borrow-expr] Add simple support for borrow-expr and wire it up to SILGenApply #62742

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
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
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsParse.def
Original file line number Diff line number Diff line change
Expand Up @@ -1348,6 +1348,10 @@ ERROR(expected_expr_after_try, none,
"expected expression after 'try'", ())
ERROR(expected_expr_after_await, none,
"expected expression after 'await'", ())
ERROR(expected_expr_after_move, none,
"expected expression after '_move'", ())
ERROR(expected_expr_after_borrow, none,
"expected expression after '_borrow'", ())

// Cast expressions
ERROR(expected_type_after_is,none,
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -6638,6 +6638,8 @@ ERROR(moveOnly_not_allowed_here,none,
"'moveOnly' may only be applied to classes, structs, and enums", ())
ERROR(move_expression_not_passed_lvalue,none,
"'move' can only be applied to lvalues", ())
ERROR(borrow_expression_not_passed_lvalue,none,
"'borrow' can only be applied to lvalues", ())

//------------------------------------------------------------------------------
// MARK: Type Wrappers
Expand Down
31 changes: 31 additions & 0 deletions include/swift/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -2076,6 +2076,37 @@ class MoveExpr final : public IdentityExpr {
static bool classof(const Expr *e) { return e->getKind() == ExprKind::Move; }
};

/// BorrowExpr - A 'borrow' surrounding an lvalue/accessor expression at an
/// apply site marking the lvalue/accessor as being borrowed when passed to the
/// callee.
///
/// getSemanticsProvidingExpr() looks through this because it doesn't
/// provide the value and only very specific clients care where the
/// 'borrow' was written.
class BorrowExpr final : public IdentityExpr {
SourceLoc BorrowLoc;

public:
BorrowExpr(SourceLoc borrowLoc, Expr *sub, Type type = Type(),
bool implicit = false)
: IdentityExpr(ExprKind::Borrow, sub, type, implicit),
BorrowLoc(borrowLoc) {}

static BorrowExpr *createImplicit(ASTContext &ctx, SourceLoc borrowLoc,
Expr *sub, Type type = Type()) {
return new (ctx) BorrowExpr(borrowLoc, sub, type, /*implicit=*/true);
}

SourceLoc getLoc() const { return BorrowLoc; }

SourceLoc getStartLoc() const { return BorrowLoc; }
SourceLoc getEndLoc() const { return getSubExpr()->getEndLoc(); }

static bool classof(const Expr *e) {
return e->getKind() == ExprKind::Borrow;
}
};

/// TupleExpr - Parenthesized expressions like '(a: x+x)' and '(x, y, 4)'. Note
/// that expressions like '(4)' are represented with a ParenExpr.
class TupleExpr final : public Expr,
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/ExprNodes.def
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ ABSTRACT_EXPR(Identity, Expr)
EXPR(DotSelf, IdentityExpr)
EXPR(Await, IdentityExpr)
EXPR(Move, IdentityExpr)
EXPR(Borrow, IdentityExpr)
EXPR(UnresolvedMemberChainResult, IdentityExpr)
EXPR_RANGE(Identity, Paren, UnresolvedMemberChainResult)
ABSTRACT_EXPR(AnyTry, Expr)
Expand Down
6 changes: 6 additions & 0 deletions lib/AST/ASTDumper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2191,6 +2191,12 @@ class PrintExpr : public ExprVisitor<PrintExpr> {
printRec(E->getSubExpr());
PrintWithColorRAII(OS, ParenthesisColor) << ')';
}
void visitBorrowExpr(BorrowExpr *E) {
printCommon(E, "borrow_expr");
OS << '\n';
printRec(E->getSubExpr());
PrintWithColorRAII(OS, ParenthesisColor) << ')';
}
void visitUnresolvedMemberChainResultExpr(UnresolvedMemberChainResultExpr *E){
printCommon(E, "unresolved_member_chain_expr");
OS << '\n';
Expand Down
5 changes: 5 additions & 0 deletions lib/AST/ASTPrinter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4689,6 +4689,11 @@ void PrintAST::visitMoveExpr(MoveExpr *expr) {
visit(expr->getSubExpr());
}

void PrintAST::visitBorrowExpr(BorrowExpr *expr) {
Printer << "borrow ";
visit(expr->getSubExpr());
}

void PrintAST::visitInOutExpr(InOutExpr *expr) {
visit(expr->getSubExpr());
}
Expand Down
3 changes: 3 additions & 0 deletions lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ ConcreteDeclRef Expr::getReferencedDecl(bool stopAtParenExpr) const {
PASS_THROUGH_REFERENCE(DotSelf, getSubExpr);
PASS_THROUGH_REFERENCE(Await, getSubExpr);
PASS_THROUGH_REFERENCE(Move, getSubExpr);
PASS_THROUGH_REFERENCE(Borrow, getSubExpr);
PASS_THROUGH_REFERENCE(Try, getSubExpr);
PASS_THROUGH_REFERENCE(ForceTry, getSubExpr);
PASS_THROUGH_REFERENCE(OptionalTry, getSubExpr);
Expand Down Expand Up @@ -729,6 +730,7 @@ bool Expr::canAppendPostfixExpression(bool appendingPostfixOperator) const {

case ExprKind::Await:
case ExprKind::Move:
case ExprKind::Borrow:
case ExprKind::Try:
case ExprKind::ForceTry:
case ExprKind::OptionalTry:
Expand Down Expand Up @@ -914,6 +916,7 @@ bool Expr::isValidParentOfTypeExpr(Expr *typeExpr) const {
case ExprKind::Paren:
case ExprKind::Await:
case ExprKind::Move:
case ExprKind::Borrow:
case ExprKind::UnresolvedMemberChainResult:
case ExprKind::Try:
case ExprKind::ForceTry:
Expand Down
30 changes: 21 additions & 9 deletions lib/Parse/ParseExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ ParserResult<Expr> Parser::parseExprSequence(Diag<> Message,
/// 'try' '?' expr-sequence-element(Mode)
/// 'try' '!' expr-sequence-element(Mode)
/// '_move' expr-sequence-element(Mode)
/// 'borrow' expr-sequence-element(Mode)
/// expr-unary(Mode)
///
/// 'try' is not actually allowed at an arbitrary position of a
Expand Down Expand Up @@ -405,17 +406,28 @@ ParserResult<Expr> Parser::parseExprSequenceElement(Diag<> message,
return sub;
}

if (Context.LangOpts.hasFeature(Feature::MoveOnly)
&& Tok.isContextualKeyword("_move")) {
Tok.setKind(tok::contextual_keyword);
SourceLoc awaitLoc = consumeToken();
ParserResult<Expr> sub =
parseExprSequenceElement(diag::expected_expr_after_await, isExprBasic);
if (!sub.hasCodeCompletion() && !sub.isNull()) {
sub = makeParserResult(new (Context) MoveExpr(awaitLoc, sub.get()));
if (Context.LangOpts.hasFeature(Feature::MoveOnly)) {
if (Tok.isContextualKeyword("_move")) {
Tok.setKind(tok::contextual_keyword);
SourceLoc awaitLoc = consumeToken();
ParserResult<Expr> sub =
parseExprSequenceElement(diag::expected_expr_after_move, isExprBasic);
if (!sub.hasCodeCompletion() && !sub.isNull()) {
sub = makeParserResult(new (Context) MoveExpr(awaitLoc, sub.get()));
}
return sub;
}

return sub;
if (Tok.isContextualKeyword("_borrow")) {
Tok.setKind(tok::contextual_keyword);
SourceLoc awaitLoc = consumeToken();
ParserResult<Expr> sub = parseExprSequenceElement(
diag::expected_expr_after_borrow, isExprBasic);
if (!sub.hasCodeCompletion() && !sub.isNull()) {
sub = makeParserResult(new (Context) BorrowExpr(awaitLoc, sub.get()));
}
return sub;
}
}

SourceLoc tryLoc;
Expand Down
1 change: 1 addition & 0 deletions lib/SILGen/ArgumentSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ class ArgumentSource {

Expr *findStorageReferenceExprForBorrow() &&;
Expr *findStorageReferenceExprForMoveOnlyBorrow(SILGenFunction &SGF) &&;
Expr *findStorageReferenceExprForBorrowExpr(SILGenFunction &SGF) &&;

/// Given that this source is an expression, extract and clear
/// that expression.
Expand Down
65 changes: 55 additions & 10 deletions lib/SILGen/SILGenApply.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2968,6 +2968,29 @@ Expr *ArgumentSource::findStorageReferenceExprForMoveOnlyBorrow(
return lvExpr;
}

Expr *
ArgumentSource::findStorageReferenceExprForBorrowExpr(SILGenFunction &SGF) && {
if (!isExpr())
return nullptr;

auto argExpr = asKnownExpr();
auto *li = dyn_cast<LoadExpr>(argExpr);
if (!li)
return nullptr;
auto *borrowExpr = dyn_cast<BorrowExpr>(li->getSubExpr());
if (!borrowExpr)
return nullptr;

auto *lvExpr = ::findStorageReferenceExprForBorrow(borrowExpr->getSubExpr());

// Claim the value of this argument.
if (lvExpr) {
(void)std::move(*this).asKnownExpr();
}

return lvExpr;
}

Expr *ArgumentSource::findStorageReferenceExprForBorrow() && {
if (!isExpr()) return nullptr;

Expand Down Expand Up @@ -3097,18 +3120,23 @@ class ArgEmitter {
return;
}

// If this is a yield, and the yield is borrowed, emit a borrowed r-value.
if (IsYield && param.isGuaranteed()) {
if (tryEmitBorrowed(std::move(arg), loweredSubstArgType,
loweredSubstParamType, origParamType, paramSlice))
// If we have a guaranteed +0 parameter...
if (param.isGuaranteed()) {
// And this is a yield, emit a borrowed r-value.
if (IsYield) {
if (tryEmitBorrowed(std::move(arg), loweredSubstArgType,
loweredSubstParamType, origParamType, paramSlice))
return;
}

if (tryEmitBorrowExpr(std::move(arg), loweredSubstArgType,
loweredSubstParamType, origParamType, paramSlice))
return;
}

// If we have a guaranteed paramter, see if we have a move only type and can
// emit it borrow.
//
// We check for move only in tryEmitBorrowedMoveOnly.
if (param.isGuaranteed()) {
// If we have a guaranteed paramter, see if we have a move only type and
// can emit it borrow.
//
// We check for move only in tryEmitBorrowedMoveOnly.
if (tryEmitBorrowedMoveOnly(std::move(arg), loweredSubstArgType,
loweredSubstParamType, origParamType,
paramSlice))
Expand Down Expand Up @@ -3302,6 +3330,23 @@ class ArgEmitter {
return true;
}

bool tryEmitBorrowExpr(ArgumentSource &&arg, SILType loweredSubstArgType,
SILType loweredSubstParamType,
AbstractionPattern origParamType,
ClaimedParamsRef paramsSlice) {
assert(paramsSlice.size() == 1);

// Try to find an expression we can emit as a borrowed l-value.
auto lvExpr = std::move(arg).findStorageReferenceExprForBorrowExpr(SGF);
if (!lvExpr)
return false;

emitBorrowed(lvExpr, loweredSubstArgType, loweredSubstParamType,
origParamType, paramsSlice);

return true;
}

void emitBorrowed(Expr *arg, SILType loweredSubstArgType,
SILType loweredSubstParamType,
AbstractionPattern origParamType,
Expand Down
25 changes: 25 additions & 0 deletions lib/Sema/MiscDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,11 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC,
checkMoveExpr(moveExpr);
}

// Diagnose move expression uses where the sub expression is not a declref expr
if (auto *borrowExpr = dyn_cast<BorrowExpr>(E)) {
checkBorrowExpr(borrowExpr);
}

if (!HasReachedSemanticsProvidingExpr &&
E == E->getSemanticsProvidingExpr()) {
HasReachedSemanticsProvidingExpr = true;
Expand Down Expand Up @@ -408,6 +413,26 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC,
}
}

void checkBorrowExpr(BorrowExpr *borrowExpr) {
// Make sure the MoveOnly feature is set. If not, error.
// This should not currently be reached because the parse should ignore
// the _move keyword unless the feature flag is set.
if (!Ctx.LangOpts.hasFeature(Feature::MoveOnly)) {
auto error =
diag::experimental_moveonly_feature_can_only_be_used_when_enabled;
Ctx.Diags.diagnose(borrowExpr->getLoc(), error);
}

// Allow for a chain of member_ref exprs that end in a decl_ref expr.
auto *subExpr = borrowExpr->getSubExpr();
while (auto *memberRef = dyn_cast<MemberRefExpr>(subExpr))
subExpr = memberRef->getBase();
if (!isa<DeclRefExpr>(subExpr)) {
Ctx.Diags.diagnose(borrowExpr->getLoc(),
diag::borrow_expression_not_passed_lvalue);
}
}

static Expr *lookThroughArgument(Expr *arg) {
while (1) {
if (auto conv = dyn_cast<ImplicitConversionExpr>(arg))
Expand Down
19 changes: 19 additions & 0 deletions test/Parse/borrow_expr.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// RUN: %target-typecheck-verify-swift -disable-availability-checking -enable-experimental-move-only

var global: Int = 5
func testGlobal() {
let _ = _borrow global
}

func testLet() {
let t = String()
let _ = _borrow t
}

func testVar() {
var t = String()
t = String()
let _ = _borrow t
}


41 changes: 41 additions & 0 deletions test/SILGen/borrow_expr.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// RUN: %target-swift-frontend -enable-experimental-move-only -o - -emit-silgen %s | %FileCheck %s

class Klass {
func useKlass() {}
}

struct Struct {
var k = Klass()
}

func useKlass(_ k: Klass) {}

// CHECK-LABEL: sil hidden [ossa] @$s11borrow_expr10simpleTestyyF : $@convention(thin) () -> () {
// CHECK: [[ADDR:%.*]] = project_box
//
// First check without the borrow:
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[ADDR]]
// CHECK: [[GEP:%.*]] = struct_element_addr [[ACCESS]] : $*Struct, #Struct.k
// CHECK: [[VAL:%.*]] = load [copy] [[GEP]]
// CHECK: end_access [[ACCESS]]
// CHECK: [[FUNC:%.*]] = function_ref @$s11borrow_expr8useKlassyyAA0D0CF : $@convention(thin) (@guaranteed Klass) -> ()
// CHECK: apply [[FUNC]]([[VAL]])
// CHECK: destroy_value [[VAL]]
//
// Now with the borrow:
// CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[ADDR]]
// CHECK: [[GEP:%.*]] = struct_element_addr [[ACCESS]] : $*Struct, #Struct.k
// CHECK: [[VAL:%.*]] = load_borrow [[GEP]]
// CHECK: [[FUNC:%.*]] = function_ref @$s11borrow_expr8useKlassyyAA0D0CF : $@convention(thin) (@guaranteed Klass) -> ()
// CHECK: apply [[FUNC]]([[VAL]])
// CHECK: end_borrow [[VAL]]
// CHECK: end_access [[ACCESS]]
// CHECK: } // end sil function '$s11borrow_expr10simpleTestyyF'
func simpleTest() {
var s = Struct()
s = Struct()
// Without borrow.
useKlass(s.k)
// With borrow.
useKlass(_borrow s.k)
}
Loading