diff --git a/docs/SIL.rst b/docs/SIL.rst index ccee30459cf98..3fcf54802d69a 100644 --- a/docs/SIL.rst +++ b/docs/SIL.rst @@ -4200,6 +4200,40 @@ object which is created by an ``alloc_ref`` with ``tail_elems``. It is undefined behavior if the class instance does not have tail-allocated arrays or if the element-types do not match. +copy_to_ref +``````````` +:: + + sil-instruction ::= 'copy_to_ref' sil-value 'to' sil-operand + + copy_to_ref %0 to %1 : $C + // %0 must be a pointer. + // %1 must be class type. + +Given an instance of a tuple or struct, bitcasts the "from" value to a byte +pointer and emits a memcopy to the "to" operand which is a reference. The "from" value must have all stored properties of the "to" operand. I.e. the following would be allowed: + + class Foo { + var x: Int + var y: Int + var z: Int + var cond: Bool + } + + %f = alloc_ref $Foo + %sa = alloc_stack $(Int, Int, Int, Bool) + %t = tuple (%0 : $Int, %1 : Int, %2 : Int, %3 : Bool) + store %t to %sa : $*(Int, Int, Int, Bool) + + copy_to_ref %sa to %f : $Foo + +And the following is not allowed: + + // ... + %t = tuple (%0 : $Int, %1 : Int, %2 : Int) + store %t to %sa : $*(Int, Int, Int) + copy_to_ref %sa to %f : $Foo // Error! + Enums ~~~~~ diff --git a/include/swift/SIL/SILBuilder.h b/include/swift/SIL/SILBuilder.h index 61174385d77a9..da7ee0b389096 100644 --- a/include/swift/SIL/SILBuilder.h +++ b/include/swift/SIL/SILBuilder.h @@ -967,6 +967,14 @@ class SILBuilder { getSILDebugLocation(Loc), srcAddr, destAddr, isTake, isInitialize)); } + CopyToRefInst *createCopyToRef(SILLocation Loc, SILValue srcAddr, + SILValue destRef) { + assert(srcAddr->getType().isAddress()); + assert(destRef->getType().isAnyClassReferenceType()); + return insert(new (getModule()) CopyToRefInst(getSILDebugLocation(Loc), + srcAddr, destRef)); + } + BindMemoryInst *createBindMemory(SILLocation Loc, SILValue base, SILValue index, SILType boundType) { return insert(BindMemoryInst::create(getSILDebugLocation(Loc), base, index, diff --git a/include/swift/SIL/SILCloner.h b/include/swift/SIL/SILCloner.h index cf85fdec14f5d..c30ae0c7cd9c0 100644 --- a/include/swift/SIL/SILCloner.h +++ b/include/swift/SIL/SILCloner.h @@ -1389,6 +1389,15 @@ SILCloner::visitCopyAddrInst(CopyAddrInst *Inst) { Inst->isInitializationOfDest())); } +template +void SILCloner::visitCopyToRefInst(CopyToRefInst *inst) { + getBuilder().setCurrentDebugScope(getOpScope(inst->getDebugScope())); + recordClonedInstruction( + inst, getBuilder().createCopyToRef(getOpLocation(inst->getLoc()), + getOpValue(inst->getSrc()), + getOpValue(inst->getDest()))); +} + template void SILCloner::visitBindMemoryInst(BindMemoryInst *Inst) { diff --git a/include/swift/SIL/SILInstruction.h b/include/swift/SIL/SILInstruction.h index 4634d8c60aa87..df2b1fa1b4b09 100644 --- a/include/swift/SIL/SILInstruction.h +++ b/include/swift/SIL/SILInstruction.h @@ -4210,6 +4210,40 @@ class CopyAddrInst MutableArrayRef getAllOperands() { return Operands.asArray(); } }; +/// CopyToRef - Represents a copy from one memory location to a reference. This +/// is similar to: +/// copy_addr %0 to %1 : $Foo +/// but is valid for reference types. +class CopyToRefInst : public InstructionBase { + friend SILBuilder; + +public: + enum { + /// The lvalue being loaded from. + src, + + /// The lvalue being stored to. + dest + }; + +private: + FixedOperandList<2> Operands; + + CopyToRefInst(SILDebugLocation debugLog, SILValue src, SILValue dest) + : InstructionBase(debugLog), Operands(this, src, dest) {} + +public: + SILValue getSrc() const { return Operands[src].get(); } + SILValue getDest() const { return Operands[dest].get(); } + + void setSrc(SILValue val) { Operands[src].set(val); } + void setDest(SILValue val) { Operands[dest].set(val); } + + ArrayRef getAllOperands() const { return Operands.asArray(); } + MutableArrayRef getAllOperands() { return Operands.asArray(); } +}; + /// BindMemoryInst - /// "bind_memory %0 : $Builtin.RawPointer, %1 : $Builtin.Word to $T" /// Binds memory at the raw pointer %0 to type $T with enough capacity diff --git a/include/swift/SIL/SILNodes.def b/include/swift/SIL/SILNodes.def index d85bdb0bac175..74a8e8b2eaad9 100644 --- a/include/swift/SIL/SILNodes.def +++ b/include/swift/SIL/SILNodes.def @@ -646,6 +646,8 @@ ABSTRACT_VALUE_AND_INST(SingleValueInstruction, ValueBase, SILInstruction) SingleValueInstruction, None, DoesNotRelease) SINGLE_VALUE_INST(RefTailAddrInst, ref_tail_addr, SingleValueInstruction, None, DoesNotRelease) + NON_VALUE_INST(CopyToRefInst, copy_to_ref, + SILInstruction, MayHaveSideEffects, DoesNotRelease) // Enums SINGLE_VALUE_INST(EnumInst, enum, diff --git a/lib/IRGen/IRGenSIL.cpp b/lib/IRGen/IRGenSIL.cpp index aa06d47d78b0c..1136b437c021f 100644 --- a/lib/IRGen/IRGenSIL.cpp +++ b/lib/IRGen/IRGenSIL.cpp @@ -982,6 +982,7 @@ class IRGenSILFunction : void visitDeallocPartialRefInst(DeallocPartialRefInst *i); void visitCopyAddrInst(CopyAddrInst *i); + void visitCopyToRefInst(CopyToRefInst *i); void visitDestroyAddrInst(DestroyAddrInst *i); void visitBindMemoryInst(BindMemoryInst *i); @@ -5587,6 +5588,33 @@ void IRGenSILFunction::visitCopyAddrInst(swift::CopyAddrInst *i) { } } +void IRGenSILFunction::visitCopyToRefInst(swift::CopyToRefInst *i) { + // If the class is empty we don't have to do anything. + if (i->getDest() + ->getType() + .getClassOrBoundGenericClass() + ->getStoredProperties() + .empty()) + return; + // Otherwise, get the soruce address and the destination reference. + Address src = getLoweredAddress(i->getSrc()); + Explosion classRefExplosion = getLoweredExplosion(i->getDest()); + llvm::Value *classRef = classRefExplosion.claimNext(); + // Get the size of the source object for memcpy. + const auto &size = + getTypeInfo(i->getSrc()->getType().getAddressType()) + .getSize(*this, i->getSrc()->getType().getAddressType()); + // Find where we want to copy our source into. This is the first element of + // the struct, the one directly after the refcounted object. + auto start = Builder.CreateGEP(classRef, {IGM.getInt32(0), IGM.getInt32(1)}); + // Bitcast both to a byte pointer for memcpy. + auto bytePtr = Builder.CreateBitCast(src.getAddress(), IGM.Int8PtrTy); + start = Builder.CreateBitCast(start, IGM.Int8PtrTy); + // Copy the whole source object into the start of the stored properties in the + // reference class. + emitMemCpy(start, bytePtr, size, src.getAlignment()); +} + // This is a no-op because we do not lower Swift TBAA info to LLVM IR, and it // does not produce any values. void IRGenSILFunction::visitBindMemoryInst(swift::BindMemoryInst *) {} diff --git a/lib/ParseSIL/ParseSIL.cpp b/lib/ParseSIL/ParseSIL.cpp index 049cd14000227..dc525bd5ff050 100644 --- a/lib/ParseSIL/ParseSIL.cpp +++ b/lib/ParseSIL/ParseSIL.cpp @@ -4352,6 +4352,36 @@ bool SILParser::parseSpecificSILInstruction(SILBuilder &B, IsInitialization_t(IsInit)); break; } + case SILInstructionKind::CopyToRefInst: { + SILValue srcVal; + SILValue destVal; + SourceLoc toLoc, srcLoc, destLoc; + Identifier toToken; + if (parseTypedValueRef(srcVal, srcLoc, B) || + parseSILIdentifier(toToken, toLoc, diag::expected_tok_in_sil_instr, + "to") || + parseTypedValueRef(destVal, destLoc, B) || + parseSILDebugLocation(InstLoc, B)) + return true; + + if (toToken.str() != "to") { + P.diagnose(toLoc, diag::expected_tok_in_sil_instr, "to"); + return true; + } + + if (!destVal->getType().isAnyClassReferenceType()) { + P.diagnose(destLoc, diag::sil_invalid_instr_operands); + return true; + } + + if (!srcVal->getType().isAddress()) { + P.diagnose(destLoc, diag::sil_invalid_instr_operands); + return true; + } + + ResultVal = B.createCopyToRef(InstLoc, srcVal, destVal); + break; + } case SILInstructionKind::BindMemoryInst: { SILValue IndexVal; Identifier ToToken; diff --git a/lib/SIL/IR/OperandOwnership.cpp b/lib/SIL/IR/OperandOwnership.cpp index dc5ba790c88b3..72438738e2c40 100644 --- a/lib/SIL/IR/OperandOwnership.cpp +++ b/lib/SIL/IR/OperandOwnership.cpp @@ -179,6 +179,7 @@ CONSTANT_OWNERSHIP_INST(None, MustBeLive, BindMemory) CONSTANT_OWNERSHIP_INST(None, MustBeLive, CheckedCastAddrBranch) CONSTANT_OWNERSHIP_INST(None, MustBeLive, CondFail) CONSTANT_OWNERSHIP_INST(None, MustBeLive, CopyAddr) +CONSTANT_OWNERSHIP_INST(None, MustBeLive, CopyToRef) CONSTANT_OWNERSHIP_INST(None, MustBeLive, DeallocStack) CONSTANT_OWNERSHIP_INST(None, MustBeLive, DebugValueAddr) CONSTANT_OWNERSHIP_INST(None, MustBeLive, DeinitExistentialAddr) diff --git a/lib/SIL/IR/SILPrinter.cpp b/lib/SIL/IR/SILPrinter.cpp index 871bb1222cb2f..27ceb686b55bb 100644 --- a/lib/SIL/IR/SILPrinter.cpp +++ b/lib/SIL/IR/SILPrinter.cpp @@ -1465,6 +1465,11 @@ class SILPrinter : public SILInstructionVisitor { *this << getIDAndType(CI->getDest()); } + void visitCopyToRefInst(CopyToRefInst *copy) { + *this << Ctx.getID(copy->getSrc()) << " to " + << getIDAndType(copy->getDest()); + } + void visitBindMemoryInst(BindMemoryInst *BI) { *this << getIDAndType(BI->getBase()) << ", "; *this << getIDAndType(BI->getIndex()) << " to "; diff --git a/lib/SIL/Verifier/SILVerifier.cpp b/lib/SIL/Verifier/SILVerifier.cpp index a901adf9539d3..c0aa4af297e5d 100644 --- a/lib/SIL/Verifier/SILVerifier.cpp +++ b/lib/SIL/Verifier/SILVerifier.cpp @@ -2265,6 +2265,59 @@ class SILVerifier : public SILVerifierBase { "cannot directly copy type with inaccessible ABI"); } + void checkCopyToRefInst(CopyToRefInst *copy) { + require(copy->getSrc()->getType().isAddress(), + "Src value should be an address"); + require(copy->getDest()->getType().isAnyClassReferenceType(), + "Dest value should be a class reference type"); + + auto classDecl = copy->getDest()->getType().getClassOrBoundGenericClass(); + if (classDecl->getStoredProperties().size() == 1) { + require( + classDecl->getStoredProperties().front()->getType().getPointer() == + copy->getSrc()->getType().getASTType().getPointer(), + "The source type must be the same as the class's only property type"); + return; + } + + Type srcType = copy->getSrc()->getType().getAs(); + if (!srcType) { + srcType = copy->getSrc()->getType().getAs(); + } + require(!srcType.isNull(), + "The source value must be either a tuple or struct if there is " + "not one stored property in the class"); + + unsigned i = 0; + for (auto *prop : classDecl->getStoredProperties()) { + Type srcPropType; + if (auto tupleType = srcType->getAs()) { + srcPropType = tupleType->getElement(i++).getType(); + } else { + srcPropType = srcType->getStructOrBoundGenericStruct() + ->getStoredProperties()[i++] + ->getType(); + } + require( + srcPropType.getPointer() == prop->getType().getPointer(), + "All stored properties must exist as operands of the source value"); + } + + unsigned numElements; + if (auto tupleType = srcType->getAs()) { + numElements = tupleType->getNumElements(); + } else { + numElements = srcType->getStructOrBoundGenericStruct() + ->getStoredProperties() + .size(); + } + require(i == numElements, "Source operands must only be stored properties " + "of the destination type"); + + require(F.isTypeABIAccessible(copy->getDest()->getType()), + "cannot directly copy type with inaccessible ABI"); + } + void checkRetainValueInst(RetainValueInst *I) { require(I->getOperand()->getType().isObject(), "Source value should be an object value"); diff --git a/lib/SILOptimizer/UtilityPasses/SerializeSILPass.cpp b/lib/SILOptimizer/UtilityPasses/SerializeSILPass.cpp index ed80be81e0981..e59f4adfa5443 100644 --- a/lib/SILOptimizer/UtilityPasses/SerializeSILPass.cpp +++ b/lib/SILOptimizer/UtilityPasses/SerializeSILPass.cpp @@ -314,6 +314,7 @@ static bool hasOpaqueArchetype(TypeExpansionContext context, case SILInstructionKind::Store##Name##Inst: #include "swift/AST/ReferenceStorage.def" case SILInstructionKind::CopyAddrInst: + case SILInstructionKind::CopyToRefInst: case SILInstructionKind::DestroyAddrInst: case SILInstructionKind::EndLifetimeInst: case SILInstructionKind::InjectEnumAddrInst: diff --git a/lib/SILOptimizer/Utils/SILInliner.cpp b/lib/SILOptimizer/Utils/SILInliner.cpp index 29df76e4b416f..8db3170c9e467 100644 --- a/lib/SILOptimizer/Utils/SILInliner.cpp +++ b/lib/SILOptimizer/Utils/SILInliner.cpp @@ -806,6 +806,7 @@ InlineCost swift::instructionInlineCost(SILInstruction &I) { case SILInstructionKind::CopyBlockInst: case SILInstructionKind::CopyBlockWithoutEscapingInst: case SILInstructionKind::CopyAddrInst: + case SILInstructionKind::CopyToRefInst: case SILInstructionKind::RetainValueInst: case SILInstructionKind::RetainValueAddrInst: case SILInstructionKind::UnmanagedRetainValueInst: diff --git a/lib/Serialization/DeserializeSIL.cpp b/lib/Serialization/DeserializeSIL.cpp index 7cb208358bd1f..179bb72088ea1 100644 --- a/lib/Serialization/DeserializeSIL.cpp +++ b/lib/Serialization/DeserializeSIL.cpp @@ -1968,6 +1968,15 @@ bool SILDeserializer::readSILInstruction(SILFunction *Fn, SILBasicBlock *BB, IsInitialization_t(isInit)); break; } + case SILInstructionKind::CopyToRefInst: { + SILType addrType = + getSILType(MF->getType(TyID), (SILValueCategory)TyCategory, Fn); + SILType referenceType = + getSILType(MF->getType(TyID2), (SILValueCategory)TyCategory, Fn); + ResultVal = Builder.createCopyToRef(Loc, getLocalValue(ValID, addrType), + getLocalValue(ValID2, referenceType)); + break; + } case SILInstructionKind::AssignInst: { auto Ty = MF->getType(TyID); SILType addrType = getSILType(Ty, (SILValueCategory)TyCategory, Fn); diff --git a/lib/Serialization/SerializeSIL.cpp b/lib/Serialization/SerializeSIL.cpp index 86535115c605f..7a8c46160ec46 100644 --- a/lib/Serialization/SerializeSIL.cpp +++ b/lib/Serialization/SerializeSIL.cpp @@ -1718,6 +1718,7 @@ void SILSerializer::writeSILInstruction(const SILInstruction &SI) { #include "swift/AST/ReferenceStorage.def" case SILInstructionKind::AssignInst: case SILInstructionKind::CopyAddrInst: + case SILInstructionKind::CopyToRefInst: case SILInstructionKind::StoreInst: case SILInstructionKind::StoreBorrowInst: { SILValue operand, value; diff --git a/test/IRGen/copy_to_ref.sil b/test/IRGen/copy_to_ref.sil new file mode 100644 index 0000000000000..2906e1150c1c1 --- /dev/null +++ b/test/IRGen/copy_to_ref.sil @@ -0,0 +1,147 @@ +// RUN: %swift %s -emit-ir -O -module-name run | %FileCheck %s + +sil_stage canonical + +import Builtin +import Swift + +class SingleInt { + var x : Int +} + +// CHECK-LABEL: define swiftcc void @test_int +// CHECK: [[ALLOC:%.*]] = alloca %T3run9SingleIntC +// CHECK-NEXT: [[META:%.*]] = tail call swiftcc %swift.metadata_response @"$s3run9SingleIntCMa" +// CHECK-NEXT: [[META_E:%.*]] = extractvalue %swift.metadata_response [[META]] +// CHECK-NEXT: [[REF_COUNT:%.*]] = getelementptr inbounds %T3run9SingleIntC, %T3run9SingleIntC* [[ALLOC]] +// CHECK-NEXT: [[OBJ_REF:%.*]] = call %swift.refcounted* @swift_initStackObject(%swift.type* [[META_E]], %swift.refcounted* nonnull [[REF_COUNT]]) +// CHECK-NEXT: [[SROA_IDX:%.*]] = getelementptr inbounds %swift.refcounted, %swift.refcounted* [[OBJ_REF]], [[IT:i32|i64]] 1 +// CHECK-NEXT: [[X_PTR:%.*]] = bitcast %swift.refcounted* [[SROA_IDX]] to [[IT]]* +// CHECK-NEXT: store [[IT]] 1, [[IT]]* [[X_PTR]] +// CHECK: ret void +sil @test_int : $@convention(thin) () -> () { +bb0: + %0 = integer_literal $Builtin.Int64, 1 + %1 = struct $Int (%0 : $Builtin.Int64) + + %tsa = alloc_stack $Int + %f = alloc_ref [stack] $SingleInt + + store %1 to %tsa : $*Int + + copy_to_ref %tsa : $*Int to %f : $SingleInt + + dealloc_ref [stack] %f : $SingleInt + dealloc_stack %tsa : $*Int + + %999 = tuple () + return %999 : $() +} + +class XYZClass { + var x : Int + var y : Int + var z : Int +} + +// Check both tuple and struct (tuple at the bottom of the file): + +// CHECK-LABEL: define swiftcc void @test_xyzcheck_struct +// CHECK: [[ALLOC:%.*]] = alloca %T3run8XYZClassC +// CHECK-NEXT: [[META:%.*]] = tail call swiftcc %swift.metadata_response @"$s3run8XYZClassCMa" +// CHECK-NEXT: [[META_E:%.*]] = extractvalue %swift.metadata_response [[META]] +// CHECK-NEXT: [[REF_COUNT:%.*]] = getelementptr inbounds %T3run8XYZClassC, %T3run8XYZClassC* [[ALLOC]] +// CHECK-NEXT: [[OBJ_REF:%.*]] = call %swift.refcounted* @swift_initStackObject(%swift.type* [[META_E]], %swift.refcounted* nonnull [[REF_COUNT]]) +// CHECK-NEXT: [[SROA_IDX:%.*]] = getelementptr inbounds %swift.refcounted, %swift.refcounted* [[OBJ_REF]], [[IT:i32|i64]] 1 +// CHECK-NEXT: [[XY_PTR:%.*]] = bitcast %swift.refcounted* [[SROA_IDX]] to <2 x [[IT]]>* +// CHECK-NEXT: store <2 x [[IT]]> <[[IT]] 1, [[IT]] 2>, <2 x [[IT]]>* [[XY_PTR]] +// CHECK-NEXT: [[Z_PTR:%.*]] = getelementptr inbounds %swift.refcounted, %swift.refcounted* [[OBJ_REF]], i64 2 +// CHECK-NEXT: [[Z_IPTR:%.*]] = bitcast %swift.refcounted* [[Z_PTR]] to [[IT]]* +// CHECK-NEXT: store [[IT]] 2, [[IT]]* [[Z_IPTR]] +// CHECK: ret void + +sil @test_xyzcheck_tuple : $@convention(thin) () -> () { +bb0: + %0 = integer_literal $Builtin.Int64, 1 + %1 = struct $Int (%0 : $Builtin.Int64) + %2 = integer_literal $Builtin.Int64, 2 + %3 = struct $Int (%2 : $Builtin.Int64) + %4 = tuple (%1 : $Int, %3 : $Int, %3 : $Int) + + %tsa = alloc_stack $(Int, Int, Int) + %f = alloc_ref [stack] $XYZClass + + store %4 to %tsa : $*(Int, Int, Int) + + copy_to_ref %tsa : $*(Int, Int, Int) to %f : $XYZClass + + dealloc_ref [stack] %f : $XYZClass + dealloc_stack %tsa : $*(Int, Int, Int) + + %999 = tuple () + return %999 : $() +} + +struct XYZStruct { + var x : Int + var y : Int + var z : Int +} + +sil @test_xyzcheck_struct : $@convention(thin) () -> () { +bb0: + %0 = integer_literal $Builtin.Int64, 1 + %1 = struct $Int (%0 : $Builtin.Int64) + %2 = integer_literal $Builtin.Int64, 2 + %3 = struct $Int (%2 : $Builtin.Int64) + %4 = struct $XYZStruct (%1 : $Int, %3 : $Int, %3 : $Int) + + %tsa = alloc_stack $XYZStruct + %f = alloc_ref [stack] $XYZClass + + store %4 to %tsa : $*XYZStruct + + copy_to_ref %tsa : $*XYZStruct to %f : $XYZClass + + dealloc_ref [stack] %f : $XYZClass + dealloc_stack %tsa : $*XYZStruct + + %999 = tuple () + return %999 : $() +} + +class Empty { } + +// CHECK-LABEL: define swiftcc void @test_empty +// CHECK: [[ALLOC:%.*]] = alloca %swift.refcounted +// CHECK-NEXT: [[META:%.*]] = tail call swiftcc %swift.metadata_response @"$s3run5EmptyCMa" +// CHECK-NEXT: [[META_E:%.*]] = extractvalue %swift.metadata_response [[META]] +// CHECK-NEXT: [[OBJ_REF:%.*]] = call %swift.refcounted* @swift_initStackObject(%swift.type* [[META_E]], %swift.refcounted* nonnull [[ALLOC]]) +// CHECK-NEXT: bitcast +// CHECK-NEXT: llvm.lifetime.end +// CHECK: ret void +sil @test_empty : $@convention(thin) () -> () { +bb0: + %empty_tuple = tuple () + + %tsa = alloc_stack $() + %f = alloc_ref [stack] $Empty + + store %empty_tuple to %tsa : $*() + + copy_to_ref %tsa : $*() to %f : $Empty + + dealloc_ref [stack] %f : $Empty + dealloc_stack %tsa : $*() + + %999 = tuple () + return %999 : $() +} + +sil_vtable SingleInt { } +sil_vtable XYZClass { } +sil_vtable Empty { } + +// CHECK-LABEL: define swiftcc void @test_xyzcheck_tuple +// CHECK: tail call swiftcc void @test_xyzcheck_struct() +// CHECK-NEXT: ret void