diff --git a/include/swift/AST/SemanticAttrs.def b/include/swift/AST/SemanticAttrs.def index 0069674b8a030..e810eb99c94e4 100644 --- a/include/swift/AST/SemanticAttrs.def +++ b/include/swift/AST/SemanticAttrs.def @@ -34,6 +34,8 @@ SEMANTICS_ATTR(STRING_PLUS_EQUALS, "string.plusequals") SEMANTICS_ATTR(FIND_STRING_SWITCH_CASE, "findStringSwitchCase") SEMANTICS_ATTR(FIND_STRING_SWITCH_CASE_WITH_CACHE, "findStringSwitchCaseWithCache") +SEMANTICS_ATTR(BINARY_INTEGER_DESCRIPTION, "binaryInteger.description") + SEMANTICS_ATTR(SWIFT_CONCURRENT_ASYNC, "swift.concurrent.async") SEMANTICS_ATTR(SWIFT_CONCURRENT_SAFE, "swift.concurrent.safe") SEMANTICS_ATTR(SWIFT_CONCURRENT, "swift.concurrent") diff --git a/lib/SILOptimizer/Utils/ConstExpr.cpp b/lib/SILOptimizer/Utils/ConstExpr.cpp index 8efe5fc27b65e..9bdea531f2418 100644 --- a/lib/SILOptimizer/Utils/ConstExpr.cpp +++ b/lib/SILOptimizer/Utils/ConstExpr.cpp @@ -56,6 +56,8 @@ enum class WellKnownFunction { StringEquals, // String.percentEscapedString.getter StringEscapePercent, + // BinaryInteger.description.getter + BinaryIntegerDescription, // _assertionFailure(_: StaticString, _: StaticString, file: StaticString,...) AssertionFailure, // A function taking one argument that prints the symbolic value of the @@ -83,6 +85,8 @@ static llvm::Optional classifyFunction(SILFunction *fn) { return WellKnownFunction::StringEquals; if (fn->hasSemanticsAttr(semantics::STRING_ESCAPE_PERCENT_GET)) return WellKnownFunction::StringEscapePercent; + if (fn->hasSemanticsAttr(semantics::BINARY_INTEGER_DESCRIPTION)) + return WellKnownFunction::BinaryIntegerDescription; if (fn->hasSemanticsAttrThatStartsWith("programtermination_point")) return WellKnownFunction::AssertionFailure; // A call to a function with the following semantics annotation will be @@ -789,6 +793,28 @@ static Type getArrayElementType(Type ty) { return Type(); } +/// Check if the given type \p ty is a stdlib integer type and if so return +/// whether the type is signed. Returns \c None if \p ty is not a stdlib integer +/// type, \c true if it is a signed integer type and \c false if it is an +/// unsigned integer type. +static Optional getSignIfStdlibIntegerType(Type ty) { + StructDecl *decl = ty->getStructOrBoundGenericStruct(); + if (!decl) + return None; + ASTContext &astCtx = ty->getASTContext(); + if (decl == astCtx.getIntDecl() || decl == astCtx.getInt8Decl() || + decl == astCtx.getInt16Decl() || decl == astCtx.getInt32Decl() || + decl == astCtx.getInt64Decl()) { + return true; + } + if (decl == astCtx.getUIntDecl() || decl == astCtx.getUInt8Decl() || + decl == astCtx.getUInt16Decl() || decl == astCtx.getUInt32Decl() || + decl == astCtx.getUInt64Decl()) { + return false; + } + return None; +} + /// Given a call to a well known function, collect its arguments as constants, /// fold it, and return None. If any of the arguments are not constants, marks /// the call's results as Unknown, and return an Unknown with information about @@ -1064,6 +1090,42 @@ ConstExprFunctionState::computeWellKnownCallResult(ApplyInst *apply, setValue(apply, resultVal); return None; } + case WellKnownFunction::BinaryIntegerDescription: { + // BinaryInteger.description.getter + assert(conventions.getNumDirectSILResults() == 1 && + conventions.getNumIndirectSILResults() == 0 && + conventions.getNumParameters() == 1 && apply->hasSubstitutions() && + "unexpected BinaryInteger.description.getter signature"); + // Get the type of the argument and check if it is a signed or + // unsigned integer. + SILValue integerArgument = apply->getOperand(1); + CanType argumentType = substituteGenericParamsAndSimpify( + integerArgument->getType().getASTType()); + Optional isSignedIntegerType = + getSignIfStdlibIntegerType(argumentType); + if (!isSignedIntegerType.hasValue()) { + return getUnknown(evaluator, (SILInstruction *)apply, + UnknownReason::InvalidOperandValue); + } + // Load the stdlib integer's value and convert it to a string. + SymbolicValue stdlibIntegerValue = + getConstAddrAndLoadResult(integerArgument); + if (!stdlibIntegerValue.isConstant()) { + return stdlibIntegerValue; + } + SymbolicValue builtinIntegerValue = + stdlibIntegerValue.lookThroughSingleElementAggregates(); + assert(builtinIntegerValue.getKind() == SymbolicValue::Integer && + "stdlib integer type must store only a builtin integer"); + APInt integer = builtinIntegerValue.getIntegerValue(); + SmallString<8> integerString; + isSignedIntegerType.getValue() ? integer.toStringSigned(integerString) + : integer.toStringUnsigned(integerString); + SymbolicValue resultVal = + SymbolicValue::getString(integerString.str(), evaluator.getAllocator()); + setValue(apply, resultVal); + return None; + } case WellKnownFunction::DebugPrint: { assert(apply->getNumArguments() == 1 && "debug_print function must take exactly one argument"); diff --git a/stdlib/public/core/Integers.swift b/stdlib/public/core/Integers.swift index 7a9ac0035e10e..56e85f0dfc57d 100644 --- a/stdlib/public/core/Integers.swift +++ b/stdlib/public/core/Integers.swift @@ -1557,6 +1557,7 @@ extension BinaryInteger { } /// A textual representation of this value. + @_semantics("binaryInteger.description") public var description: String { return _description(radix: 10, uppercase: false) } diff --git a/test/SILOptimizer/constant_evaluable_subset_test.swift b/test/SILOptimizer/constant_evaluable_subset_test.swift index b1603de445de7..4c77079039fbc 100644 --- a/test/SILOptimizer/constant_evaluable_subset_test.swift +++ b/test/SILOptimizer/constant_evaluable_subset_test.swift @@ -911,3 +911,17 @@ func interpretMetaTypeCast() -> Bool { func interpretMetaTypeCast2() -> Bool { return testMetaTypeCast(((Int) -> Int).self) } + +// CHECK-LABEL: @testBinaryIntegerDescription +// CHECK-NOT: error: +@_semantics("constant_evaluable") +func testBinaryIntegerDescription(_ x: T) -> String { + return x.description +} + +@_semantics("test_driver") +func interpretBinaryIntegerDescription() -> String { + var str = testBinaryIntegerDescription(-10) + str += testBinaryIntegerDescription(UInt(20)) + return str +} diff --git a/test/SILOptimizer/constant_evaluator_test.sil b/test/SILOptimizer/constant_evaluator_test.sil index b1a3a9a1123e2..fb555685f89a1 100644 --- a/test/SILOptimizer/constant_evaluator_test.sil +++ b/test/SILOptimizer/constant_evaluator_test.sil @@ -1503,3 +1503,31 @@ bb0: %3 = apply %2(%1) : $@convention(thin) <τ_0_0> (@thick τ_0_0.Type) -> Builtin.Int1 return %3 : $Builtin.Int1 } // CHECK: Returns int: -1 + +// CHECK-LABEL: @interpretBinaryIntegerDescription +sil [ossa] @interpretBinaryIntegerDescription : $@convention(thin) () -> @owned String { +bb0: + %0 = integer_literal $Builtin.Int64, -10 + %1 = struct $Int64 (%0 : $Builtin.Int64) + %2 = alloc_stack $Int64 + store %1 to [trivial] %2 : $*Int64 + %4 = function_ref @binaryIntegerDescription : $@convention(method) <τ_0_0 where τ_0_0 : BinaryInteger> (@in_guaranteed τ_0_0) -> @owned String + %5 = apply %4(%2) : $@convention(method) <τ_0_0 where τ_0_0 : BinaryInteger> (@in_guaranteed τ_0_0) -> @owned String + dealloc_stack %2 : $*Int64 + return %5 : $String +} // CHECK: Returns string: "-10" + +sil [_semantics "binaryInteger.description"] @binaryIntegerDescription : $@convention(method) <τ_0_0 where τ_0_0 : BinaryInteger> (@in_guaranteed τ_0_0) -> @owned String + +// CHECK-LABEL: @interpretUnsignedBinaryIntegerDescription +sil [ossa] @interpretUnsignedBinaryIntegerDescription : $@convention(thin) () -> @owned String { +bb0: + %0 = integer_literal $Builtin.Int64, 0xffffffffffffffff + %1 = struct $UInt64 (%0 : $Builtin.Int64) + %2 = alloc_stack $UInt64 + store %1 to [trivial] %2 : $*UInt64 + %4 = function_ref @binaryIntegerDescription : $@convention(method) <τ_0_0 where τ_0_0 : BinaryInteger> (@in_guaranteed τ_0_0) -> @owned String + %5 = apply %4(%2) : $@convention(method) <τ_0_0 where τ_0_0 : BinaryInteger> (@in_guaranteed τ_0_0) -> @owned String + dealloc_stack %2 : $*UInt64 + return %5 : $String +} // CHECK: Returns string: "18446744073709551615"