Index: include/clang/AST/Expr.h =================================================================== --- include/clang/AST/Expr.h +++ include/clang/AST/Expr.h @@ -925,6 +925,14 @@ /// place. Expr *getSourceExpr() const { return SourceExpr; } + void setIsUnique(bool V) { + assert((!V || SourceExpr) && + "unique OVEs are expected to have source expressions"); + OpaqueValueExprBits.IsUnique = V; + } + + bool isUnique() const { return OpaqueValueExprBits.IsUnique; } + static bool classof(const Stmt *T) { return T->getStmtClass() == OpaqueValueExprClass; } Index: include/clang/AST/Stmt.h =================================================================== --- include/clang/AST/Stmt.h +++ include/clang/AST/Stmt.h @@ -237,6 +237,16 @@ unsigned ResultIndex : 32 - 8 - NumExprBits; }; + class OpaqueValueExprBitfields { + friend class OpaqueValueExpr; + + unsigned : NumExprBits; + + /// The OVE is a unique semantic reference to its source expressio if this + /// bit is set to true. + unsigned IsUnique : 1; + }; + class ObjCIndirectCopyRestoreExprBitfields { friend class ObjCIndirectCopyRestoreExpr; @@ -294,6 +304,7 @@ CallExprBitfields CallExprBits; ExprWithCleanupsBitfields ExprWithCleanupsBits; PseudoObjectExprBitfields PseudoObjectExprBits; + OpaqueValueExprBitfields OpaqueValueExprBits; ObjCIndirectCopyRestoreExprBitfields ObjCIndirectCopyRestoreExprBits; InitListExprBitfields InitListExprBits; TypeTraitExprBitfields TypeTraitExprBits; Index: lib/CodeGen/CGExpr.cpp =================================================================== --- lib/CodeGen/CGExpr.cpp +++ lib/CodeGen/CGExpr.cpp @@ -4167,7 +4167,35 @@ LValue CodeGenFunction::EmitOpaqueValueLValue(const OpaqueValueExpr *e) { assert(OpaqueValueMappingData::shouldBindAsLValue(e)); - return getOpaqueLValueMapping(e); + return getOrCreateOpaqueLValueMapping(e); +} + +LValue +CodeGenFunction::getOrCreateOpaqueLValueMapping(const OpaqueValueExpr *e) { + assert(OpaqueValueMapping::shouldBindAsLValue(e)); + + llvm::DenseMap::iterator + it = OpaqueLValues.find(e); + + if (it != OpaqueLValues.end()) + return it->second; + + assert(e->isUnique() && "LValue for a nonunique OVE hasn't been emitted"); + return OpaqueLValues[e] = EmitLValue(e->getSourceExpr()); +} + +RValue +CodeGenFunction::getOrCreateOpaqueRValueMapping(const OpaqueValueExpr *e) { + assert(!OpaqueValueMapping::shouldBindAsLValue(e)); + + llvm::DenseMap::iterator + it = OpaqueRValues.find(e); + + if (it != OpaqueRValues.end()) + return it->second; + + assert(e->isUnique() && "RValue for a nonunique OVE hasn't been emitted"); + return OpaqueRValues[e] = EmitAnyExpr(e->getSourceExpr()); } RValue CodeGenFunction::EmitRValueForField(LValue LV, @@ -4699,6 +4727,15 @@ struct LValueOrRValue { LValue LV; RValue RV; + bool IsSet = false; + void setLV(LValue V) { + LV = V; + IsSet = true; + } + void setRV(RValue V) { + RV = V; + IsSet = true; + } }; } @@ -4719,6 +4756,9 @@ // If this semantic expression is an opaque value, bind it // to the result of its source expression. if (const auto *ov = dyn_cast(semantic)) { + // Skip unique OVEs. + if (ov->isUnique()) + continue; // If this is the result expression, we may need to evaluate // directly into the slot. @@ -4730,7 +4770,7 @@ LValue LV = CGF.MakeAddrLValue(slot.getAddress(), ov->getType(), AlignmentSource::Decl); opaqueData = OVMA::bind(CGF, ov, LV); - result.RV = slot.asRValue(); + result.setRV(slot.asRValue()); // Otherwise, emit as normal. } else { @@ -4739,9 +4779,9 @@ // If this is the result, also evaluate the result now. if (ov == resultExpr) { if (forLValue) - result.LV = CGF.EmitLValue(ov); + result.setLV(CGF.EmitLValue(ov)); else - result.RV = CGF.EmitAnyExpr(ov, slot); + result.setRV(CGF.EmitAnyExpr(ov, slot)); } } @@ -4751,9 +4791,9 @@ // and remember the result. } else if (semantic == resultExpr) { if (forLValue) - result.LV = CGF.EmitLValue(semantic); + result.setLV(CGF.EmitLValue(semantic)); else - result.RV = CGF.EmitAnyExpr(semantic, slot); + result.setRV(CGF.EmitAnyExpr(semantic, slot)); // Otherwise, evaluate the expression in an ignored context. } else { @@ -4761,6 +4801,19 @@ } } + if (resultExpr && !result.IsSet) { + const auto *OVE = cast(resultExpr); + if (forLValue) + result.setLV(CGF.getOpaqueLValueMapping(OVE)); + else { + // Aggregates are bound as LValues. + if (CodeGenFunction::OpaqueValueMapping::shouldBindAsLValue(OVE)) + result.setRV(CGF.getOpaqueLValueMapping(OVE).asAggregateRValue()); + else + result.setRV(CGF.getOpaqueRValueMapping(OVE)); + } + } + // Unbind all the opaques now. for (unsigned i = 0, e = opaques.size(); i != e; ++i) opaques[i].unbind(CGF); Index: lib/CodeGen/CGExprAgg.cpp =================================================================== --- lib/CodeGen/CGExprAgg.cpp +++ lib/CodeGen/CGExprAgg.cpp @@ -606,7 +606,15 @@ } void AggExprEmitter::VisitOpaqueValueExpr(OpaqueValueExpr *e) { - EmitFinalDestCopy(e->getType(), CGF.getOpaqueLValueMapping(e)); + // If this is a unique OVE, emit an LValue for it now. + if (e->isUnique()) { + Visit(e->getSourceExpr()); + LValue DstLV = CGF.MakeAddrLValue( + Dest.getAddress(), Dest.isVolatile() ? e->getType().withVolatile() + : e->getType()); + CodeGenFunction::OpaqueValueMappingData::bind(CGF, e, DstLV); + } else + EmitFinalDestCopy(e->getType(), CGF.getOrCreateOpaqueLValueMapping(e)); } void Index: lib/CodeGen/CGExprComplex.cpp =================================================================== --- lib/CodeGen/CGExprComplex.cpp +++ lib/CodeGen/CGExprComplex.cpp @@ -155,8 +155,8 @@ } ComplexPairTy VisitOpaqueValueExpr(OpaqueValueExpr *E) { if (E->isGLValue()) - return EmitLoadOfLValue(CGF.getOpaqueLValueMapping(E), E->getExprLoc()); - return CGF.getOpaqueRValueMapping(E).getComplexVal(); + return EmitLoadOfLValue(CGF.getOrCreateOpaqueLValueMapping(E), E->getExprLoc()); + return CGF.getOrCreateOpaqueRValueMapping(E).getComplexVal(); } ComplexPairTy VisitPseudoObjectExpr(PseudoObjectExpr *E) { Index: lib/CodeGen/CGExprScalar.cpp =================================================================== --- lib/CodeGen/CGExprScalar.cpp +++ lib/CodeGen/CGExprScalar.cpp @@ -422,10 +422,10 @@ Value *VisitOpaqueValueExpr(OpaqueValueExpr *E) { if (E->isGLValue()) - return EmitLoadOfLValue(CGF.getOpaqueLValueMapping(E), E->getExprLoc()); + return EmitLoadOfLValue(CGF.getOrCreateOpaqueLValueMapping(E), E->getExprLoc()); // Otherwise, assume the mapping is the scalar directly. - return CGF.getOpaqueRValueMapping(E).getScalarVal(); + return CGF.getOrCreateOpaqueRValueMapping(E).getScalarVal(); } Value *emitConstant(const CodeGenFunction::ConstantEmission &Constant, Index: lib/CodeGen/CodeGenFunction.h =================================================================== --- lib/CodeGen/CodeGenFunction.h +++ lib/CodeGen/CodeGenFunction.h @@ -2133,6 +2133,8 @@ return it->second; } + LValue getOrCreateOpaqueLValueMapping(const OpaqueValueExpr *e); + /// getOpaqueRValueMapping - Given an opaque value expression (which /// must be mapped to an r-value), return its mapping. const RValue &getOpaqueRValueMapping(const OpaqueValueExpr *e) { @@ -2144,6 +2146,8 @@ return it->second; } + RValue getOrCreateOpaqueRValueMapping(const OpaqueValueExpr *e); + /// Get the index of the current ArrayInitLoopExpr, if any. llvm::Value *getArrayInitIndex() { return ArrayInitIndex; } Index: lib/Sema/SemaPseudoObject.cpp =================================================================== --- lib/Sema/SemaPseudoObject.cpp +++ lib/Sema/SemaPseudoObject.cpp @@ -190,11 +190,12 @@ Sema &S; unsigned ResultIndex; SourceLocation GenericLoc; + bool IsUnique; SmallVector Semantics; - PseudoOpBuilder(Sema &S, SourceLocation genericLoc) + PseudoOpBuilder(Sema &S, SourceLocation genericLoc, bool IsUnique) : S(S), ResultIndex(PseudoObjectExpr::NoResult), - GenericLoc(genericLoc) {} + GenericLoc(genericLoc), IsUnique(IsUnique) {} virtual ~PseudoOpBuilder() {} @@ -274,10 +275,10 @@ Selector GetterSelector; public: - ObjCPropertyOpBuilder(Sema &S, ObjCPropertyRefExpr *refExpr) : - PseudoOpBuilder(S, refExpr->getLocation()), RefExpr(refExpr), - SyntacticRefExpr(nullptr), InstanceReceiver(nullptr), Getter(nullptr), - Setter(nullptr) { + ObjCPropertyOpBuilder(Sema &S, ObjCPropertyRefExpr *refExpr, bool IsUnique) + : PseudoOpBuilder(S, refExpr->getLocation(), IsUnique), + RefExpr(refExpr), SyntacticRefExpr(nullptr), + InstanceReceiver(nullptr), Getter(nullptr), Setter(nullptr) { } ExprResult buildRValueOperation(Expr *op); @@ -314,11 +315,10 @@ Selector AtIndexSetterSelector; public: - ObjCSubscriptOpBuilder(Sema &S, ObjCSubscriptRefExpr *refExpr) : - PseudoOpBuilder(S, refExpr->getSourceRange().getBegin()), - RefExpr(refExpr), - InstanceBase(nullptr), InstanceKey(nullptr), - AtIndexGetter(nullptr), AtIndexSetter(nullptr) {} + ObjCSubscriptOpBuilder(Sema &S, ObjCSubscriptRefExpr *refExpr, bool IsUnique) + : PseudoOpBuilder(S, refExpr->getSourceRange().getBegin(), IsUnique), + RefExpr(refExpr), InstanceBase(nullptr), InstanceKey(nullptr), + AtIndexGetter(nullptr), AtIndexSetter(nullptr) {} ExprResult buildRValueOperation(Expr *op); ExprResult buildAssignmentOperation(Scope *Sc, @@ -342,11 +342,11 @@ MSPropertyRefExpr *getBaseMSProperty(MSPropertySubscriptExpr *E); public: - MSPropertyOpBuilder(Sema &S, MSPropertyRefExpr *refExpr) : - PseudoOpBuilder(S, refExpr->getSourceRange().getBegin()), - RefExpr(refExpr), InstanceBase(nullptr) {} - MSPropertyOpBuilder(Sema &S, MSPropertySubscriptExpr *refExpr) - : PseudoOpBuilder(S, refExpr->getSourceRange().getBegin()), + MSPropertyOpBuilder(Sema &S, MSPropertyRefExpr *refExpr, bool IsUnique) + : PseudoOpBuilder(S, refExpr->getSourceRange().getBegin(), IsUnique), + RefExpr(refExpr), InstanceBase(nullptr) {} + MSPropertyOpBuilder(Sema &S, MSPropertySubscriptExpr *refExpr, bool IsUnique) + : PseudoOpBuilder(S, refExpr->getSourceRange().getBegin(), IsUnique), InstanceBase(nullptr) { RefExpr = getBaseMSProperty(refExpr); } @@ -365,7 +365,9 @@ new (S.Context) OpaqueValueExpr(GenericLoc, e->getType(), e->getValueKind(), e->getObjectKind(), e); - + if (IsUnique) + captured->setIsUnique(true); + // Make sure we bind that in the semantics. addSemanticExpr(captured); return captured; @@ -1528,20 +1530,20 @@ Expr *opaqueRef = E->IgnoreParens(); if (ObjCPropertyRefExpr *refExpr = dyn_cast(opaqueRef)) { - ObjCPropertyOpBuilder builder(*this, refExpr); + ObjCPropertyOpBuilder builder(*this, refExpr, true); return builder.buildRValueOperation(E); } else if (ObjCSubscriptRefExpr *refExpr = dyn_cast(opaqueRef)) { - ObjCSubscriptOpBuilder builder(*this, refExpr); + ObjCSubscriptOpBuilder builder(*this, refExpr, true); return builder.buildRValueOperation(E); } else if (MSPropertyRefExpr *refExpr = dyn_cast(opaqueRef)) { - MSPropertyOpBuilder builder(*this, refExpr); + MSPropertyOpBuilder builder(*this, refExpr, true); return builder.buildRValueOperation(E); } else if (MSPropertySubscriptExpr *RefExpr = dyn_cast(opaqueRef)) { - MSPropertyOpBuilder Builder(*this, RefExpr); + MSPropertyOpBuilder Builder(*this, RefExpr, true); return Builder.buildRValueOperation(E); } else { llvm_unreachable("unknown pseudo-object kind!"); @@ -1560,18 +1562,18 @@ Expr *opaqueRef = op->IgnoreParens(); if (ObjCPropertyRefExpr *refExpr = dyn_cast(opaqueRef)) { - ObjCPropertyOpBuilder builder(*this, refExpr); + ObjCPropertyOpBuilder builder(*this, refExpr, false); return builder.buildIncDecOperation(Sc, opcLoc, opcode, op); } else if (isa(opaqueRef)) { Diag(opcLoc, diag::err_illegal_container_subscripting_op); return ExprError(); } else if (MSPropertyRefExpr *refExpr = dyn_cast(opaqueRef)) { - MSPropertyOpBuilder builder(*this, refExpr); + MSPropertyOpBuilder builder(*this, refExpr, false); return builder.buildIncDecOperation(Sc, opcLoc, opcode, op); } else if (MSPropertySubscriptExpr *RefExpr = dyn_cast(opaqueRef)) { - MSPropertyOpBuilder Builder(*this, RefExpr); + MSPropertyOpBuilder Builder(*this, RefExpr, false); return Builder.buildIncDecOperation(Sc, opcLoc, opcode, op); } else { llvm_unreachable("unknown pseudo-object kind!"); @@ -1594,22 +1596,23 @@ RHS = result.get(); } + bool IsSimpleAssign = opcode == BO_Assign; Expr *opaqueRef = LHS->IgnoreParens(); if (ObjCPropertyRefExpr *refExpr = dyn_cast(opaqueRef)) { - ObjCPropertyOpBuilder builder(*this, refExpr); + ObjCPropertyOpBuilder builder(*this, refExpr, IsSimpleAssign); return builder.buildAssignmentOperation(S, opcLoc, opcode, LHS, RHS); } else if (ObjCSubscriptRefExpr *refExpr = dyn_cast(opaqueRef)) { - ObjCSubscriptOpBuilder builder(*this, refExpr); + ObjCSubscriptOpBuilder builder(*this, refExpr, IsSimpleAssign); return builder.buildAssignmentOperation(S, opcLoc, opcode, LHS, RHS); } else if (MSPropertyRefExpr *refExpr = dyn_cast(opaqueRef)) { - MSPropertyOpBuilder builder(*this, refExpr); + MSPropertyOpBuilder builder(*this, refExpr, IsSimpleAssign); return builder.buildAssignmentOperation(S, opcLoc, opcode, LHS, RHS); } else if (MSPropertySubscriptExpr *RefExpr = dyn_cast(opaqueRef)) { - MSPropertyOpBuilder Builder(*this, RefExpr); + MSPropertyOpBuilder Builder(*this, RefExpr, IsSimpleAssign); return Builder.buildAssignmentOperation(S, opcLoc, opcode, LHS, RHS); } else { llvm_unreachable("unknown pseudo-object kind!"); Index: lib/Serialization/ASTReaderStmt.cpp =================================================================== --- lib/Serialization/ASTReaderStmt.cpp +++ lib/Serialization/ASTReaderStmt.cpp @@ -1667,6 +1667,7 @@ VisitExpr(E); E->SourceExpr = Record.readSubExpr(); E->Loc = ReadSourceLocation(); + E->setIsUnique(Record.readInt()); } void ASTStmtReader::VisitTypoExpr(TypoExpr *E) { Index: lib/Serialization/ASTWriterStmt.cpp =================================================================== --- lib/Serialization/ASTWriterStmt.cpp +++ lib/Serialization/ASTWriterStmt.cpp @@ -1699,6 +1699,7 @@ VisitExpr(E); Record.AddStmt(E->getSourceExpr()); Record.AddSourceLocation(E->getLocation()); + Record.push_back(E->isUnique()); Code = serialization::EXPR_OPAQUE_VALUE; } Index: test/CodeGenCXX/ms-property.cpp =================================================================== --- test/CodeGenCXX/ms-property.cpp +++ test/CodeGenCXX/ms-property.cpp @@ -75,11 +75,11 @@ // CHECK: call void @"\01??$foo@H@@YAXHH@Z"(i32 %{{.+}}, i32 %{{.+}}) foo(argc, (int)argv[0][0]); // CHECK: [[P2:%.+]] = load %class.St*, %class.St** % - // CHECK: [[T_X:%.+]] = call i32 @"\01?get_x@Test1@@QEBAHXZ"(%class.Test1* %{{.+}}) // CHECK: [[P1:%.+]] = load %class.S*, %class.S** % // CHECK: [[P1_X_22_33:%.+]] = call i32 @"\01?GetX@S@@QEAAHHH@Z"(%class.S* [[P1]], i32 22, i32 33) // CHECK: [[CAST:%.+]] = sitofp i32 [[P1_X_22_33]] to double // CHECK: [[ARGC:%.+]] = load i32, i32* % + // CHECK: [[T_X:%.+]] = call i32 @"\01?get_x@Test1@@QEBAHXZ"(%class.Test1* %{{.+}}) // CHECK: [[CAST2:%.+]] = trunc i32 [[T_X]] to i8 // CHECK: call void @"\01?PutY@?$St@M@@QEAAXDHN@Z"(%class.St* [[P2]], i8 [[CAST2]], i32 [[ARGC]], double [[CAST]]) p2->y[t.X][argc] = p1->x[22][33]; Index: test/CodeGenObjC/objc-container-subscripting-1.m =================================================================== --- test/CodeGenObjC/objc-container-subscripting-1.m +++ test/CodeGenObjC/objc-container-subscripting-1.m @@ -46,8 +46,8 @@ val = (dictionary[key] = newObject); // CHECK: [[TWELVE:%.*]] = load {{%.*}} [[DICTIONARY]], align 8 -// CHECK-NEXT: [[THIRTEEN:%.*]] = load i8*, i8** [[KEY]], align 8 // CHECK-NEXT: [[FOURTEEN:%.*]] = load i8*, i8** [[NEWOBJECT:%.*]], align 8 +// CHECK-NEXT: [[THIRTEEN:%.*]] = load i8*, i8** [[KEY]], align 8 // CHECK-NEXT: [[SIXTEEN:%.*]] = load i8*, i8** @OBJC_SELECTOR_REFERENCES_.6 // CHECK-NEXT: [[SEVENTEEN:%.*]] = bitcast {{%.*}} [[TWELVE]] to i8* // CHECK-NEXT: call void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*, i8*, i8*)*)(i8* [[SEVENTEEN]], i8* [[SIXTEEN]], i8* [[FOURTEEN]], i8* [[THIRTEEN]]) Index: test/CodeGenObjCXX/property-dot-copy-elision.mm =================================================================== --- /dev/null +++ test/CodeGenObjCXX/property-dot-copy-elision.mm @@ -0,0 +1,39 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -emit-llvm -std=c++1z -fobjc-arc -o - %s | FileCheck %s + +struct S0 { + id f; +}; + +struct S1 { + S1(); + S1(S0); + id f; +}; + +@interface C +@property S1 f; +@end +@implementation C +@end + +// CHECK-LABEL: define void @_Z5test0P1C( +// CHECK: %{{.*}} = alloca % +// CHECK: %[[AGG_TMP:.*]] = alloca %[[STRUCT_S1:.*]], align +// CHECK: %[[AGG_TMP_1:.*]] = alloca %[[STRUCT_S0:.*]], align +// CHECK: call void @_ZN2S0C1Ev(%[[STRUCT_S0]]* %[[AGG_TMP_1]]) +// CHECK: call void @_ZN2S1C1E2S0(%[[STRUCT_S1]]* %[[AGG_TMP]], %[[STRUCT_S0]]* %[[AGG_TMP_1]]) +// CHECK: call void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*, %[[STRUCT_S1]]*)*)(i8* %{{.*}}, i8* %{{.*}}, %[[STRUCT_S1]]* %[[AGG_TMP]]) + +void test0(C *c) { + c.f = S0(); +} + +// CHECK: define void @_Z5test1P1C( +// CHECK: %{{.*}} = alloca % +// CHECK: %[[TEMP_LVALUE:.*]] = alloca %[[STRUCT_S1:.*]], align +// CHECK: call void @_ZN2S1C1Ev(%[[STRUCT_S1]]* %[[TEMP_LVALUE]]) +// CHECK: call void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*, %[[STRUCT_S1]]*)*)(i8* %{{.*}}, i8* %{{.*}}, %[[STRUCT_S1]]* %[[TEMP_LVALUE]]) + +void test1(C *c) { + c.f = S1(); +} Index: test/CodeGenObjCXX/property-objects.mm =================================================================== --- test/CodeGenObjCXX/property-objects.mm +++ test/CodeGenObjCXX/property-objects.mm @@ -72,7 +72,7 @@ // rdar://8379892 // CHECK-LABEL: define void @_Z1fP1A -// CHECK: call void @_ZN1XC1Ev(%struct.X* [[LVTEMP:%[a-zA-Z0-9\.]+]]) +// CHECK: call void @_ZN1XC1Ev(%struct.X* [[LVTEMP:%.+]]) // CHECK: call void @_ZN1XC1ERKS_(%struct.X* [[AGGTMP:%[a-zA-Z0-9\.]+]], %struct.X* dereferenceable({{[0-9]+}}) [[LVTEMP]]) // CHECK: call void bitcast (i8* (i8*, i8*, ...)* @objc_msgSend to void (i8*, i8*, %struct.X*)*)({{.*}} %struct.X* [[AGGTMP]]) struct X { @@ -118,15 +118,14 @@ } // CHECK: define void @_Z6testB0P1B([[B:%.*]]* // CHECK: [[BVAR:%.*]] = alloca [[B]]*, align 8 -// CHECK: [[TEMP:%.*]] = alloca [[B0:%.*]], align 8 +// CHECK: alloca [[B0:%.*]], align 8 +// CHECK: [[TEMP:%.*]] = alloca [[B0]], align 8 // CHECK: load [[B]]*, [[B]]** [[BVAR]] // CHECK-NEXT: [[X:%.*]] = getelementptr inbounds [[B0]], [[B0]]* [[TEMP]], i32 0, i32 0 // CHECK-NEXT: [[T0:%.*]] = call i32 @_Z9b_makeIntv() // CHECK-NEXT: [[T1:%.*]] = sext i32 [[T0]] to i64 // CHECK-NEXT: store i64 [[T1]], i64* [[X]], align 8 // CHECK-NOT: call -// CHECK: call void @llvm.memcpy -// CHECK-NOT: call // CHECK: call void bitcast {{.*}} @objc_msgSend // CHECK-NOT: call // CHECK: ret void @@ -168,8 +167,6 @@ // CHECK-NOT: call // CHECK: store i64 [[T0]], // CHECK-NOT: call -// CHECK: call void @llvm.memcpy -// CHECK-NOT: call // CHECK: call void bitcast {{.*}} @objc_msgSend // CHECK-NOT: call // CHECK: ret void