Skip to content

[OSLog][Test] Add test to check constant evaluability of functions in the new OSLog overlay #27228

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
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
11 changes: 8 additions & 3 deletions include/swift/AST/DiagnosticsSIL.def
Original file line number Diff line number Diff line change
Expand Up @@ -402,9 +402,11 @@ NOTE(constexpr_unsupported_instruction_found, none,
NOTE(constexpr_unsupported_instruction_found_here,none, "operation"
"%select{| used by this call is}0 not supported by the evaluator", (bool))

NOTE(constexpr_unknown_function_called, none,
"encountered call to '%0' whose body is not available", (StringRef))
NOTE(constexpr_unknown_function_called_here, none,
NOTE(constexpr_found_callee_with_no_body, none,
"encountered call to '%0' whose body is not available. "
"Imported functions must be marked '@inlinable' to constant evaluate.",
(StringRef))
NOTE(constexpr_callee_with_no_body, none,
"%select{|calls a }0function whose body is not available", (bool))

NOTE(constexpr_untracked_sil_value_use_found, none,
Expand All @@ -428,7 +430,10 @@ NOTE(constexpr_returned_by_unevaluated_instruction,none,
"return value of an unevaluated instruction is not a constant", ())
NOTE(constexpr_mutated_by_unevaluated_instruction,none, "value mutable by an "
"unevaluated instruction is not a constant", ())

ERROR(not_constant_evaluable, none, "not constant evaluable", ())
ERROR(constexpr_imported_func_not_onone, none, "imported constant evaluable "
"function '%0' must be annotated '@_optimize(none)'", (StringRef))

ERROR(non_physical_addressof,none,
"addressof only works with purely physical lvalues; "
Expand Down
6 changes: 3 additions & 3 deletions lib/SIL/SILConstants.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ using namespace swift;

namespace swift {
llvm::cl::opt<unsigned>
ConstExprLimit("constexpr-limit", llvm::cl::init(512),
ConstExprLimit("constexpr-limit", llvm::cl::init(1024),
llvm::cl::desc("Number of instructions interpreted in a"
" constexpr function"));
}
Expand Down Expand Up @@ -711,10 +711,10 @@ void SymbolicValue::emitUnknownDiagnosticNotes(SILLocation fallbackLoc) {
SILFunction *callee = unknownReason.getCalleeWithoutImplmentation();
std::string demangledCalleeName =
Demangle::demangleSymbolAsString(callee->getName());
diagnose(ctx, diagLoc, diag::constexpr_unknown_function_called,
diagnose(ctx, diagLoc, diag::constexpr_found_callee_with_no_body,
StringRef(demangledCalleeName));
if (emitTriggerLocInDiag)
diagnose(ctx, triggerLoc, diag::constexpr_unknown_function_called_here,
diagnose(ctx, triggerLoc, diag::constexpr_callee_with_no_body,
triggerLocSkipsInternalLocs);
return;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/SILOptimizer/Mandatory/OSLogOptimization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ static bool shouldAttemptEvaluation(SILInstruction *inst) {
return false;

return calleeFun->hasSemanticsAttrThatStartsWith("string.") ||
calleeFun->hasSemanticsAttrThatStartsWith("oslog.");
calleeFun->hasSemanticsAttr("constant_evaluable");
}

/// Skip or evaluate the given instruction based on the evaluation policy and
Expand Down
46 changes: 39 additions & 7 deletions lib/SILOptimizer/UtilityPasses/ConstantEvaluableSubsetChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,18 @@ class ConstantEvaluableSubsetChecker : public SILModuleTransform {
SymbolicValueBumpAllocator allocator;
ConstExprStepEvaluator stepEvaluator(allocator, fun,
/*trackCallees*/ true);
bool previousEvaluationHadFatalError = false;

for (auto currI = fun->getEntryBlock()->begin();;) {
auto *inst = &(*currI);

if (isa<ReturnInst>(inst))
break;

assert(!previousEvaluationHadFatalError &&
"cannot continue evaluation of test driver as previous call "
"resulted in non-skippable evaluation error.");

auto *applyInst = dyn_cast<ApplyInst>(inst);
SILFunction *callee = nullptr;
if (applyInst) {
Expand All @@ -85,7 +90,11 @@ class ConstantEvaluableSubsetChecker : public SILModuleTransform {
!callee->hasSemanticsAttr(constantEvaluableSemanticsAttr)) {
std::tie(nextInstOpt, errorVal) =
stepEvaluator.tryEvaluateOrElseMakeEffectsNonConstant(currI);
assert(nextInstOpt && "non-constant control flow in the test driver");
if (!nextInstOpt) {
// This indicates an error in the test driver.
errorVal->emitUnknownDiagnosticNotes(inst->getLoc());
assert(false && "non-constant control flow in the test driver");
}
currI = nextInstOpt.getValue();
continue;
}
Expand All @@ -101,17 +110,40 @@ class ConstantEvaluableSubsetChecker : public SILModuleTransform {
errorVal->emitUnknownDiagnosticNotes(inst->getLoc());
}

if (!nextInstOpt) {
break; // This instruction should be the end of the test driver.
if (nextInstOpt) {
currI = nextInstOpt.getValue();
continue;
}
currI = nextInstOpt.getValue();

// Here, a non-skippable error like "instruction-limit exceeded" has been
// encountered during evaluation. Proceed to the next instruction but
// ensure that an assertion failure occurs if there is any instruction
// to evaluate after this instruction.
++currI;
previousEvaluationHadFatalError = true;
}

// Record functions that were called during this evaluation to detect
// whether the test drivers in the SILModule cover all function annotated
// as "constant_evaluable".
// For every function seen during the evaluation of this constant evaluable
// function:
// 1. Record it so as to detect whether the test drivers in the SILModule
// cover all function annotated as "constant_evaluable".
//
// 2. If the callee is annotated as constant_evaluable and is imported from
// a different Swift module (other than stdlib), check that the function is
// marked as Onone. Otherwise, it could have been optimized, which will
// break constant evaluability.
for (SILFunction *callee : stepEvaluator.getFuncsCalledDuringEvaluation()) {
evaluatedFunctions.insert(callee);

SILModule &calleeModule = callee->getModule();
if (callee->isAvailableExternally() &&
callee->hasSemanticsAttr(constantEvaluableSemanticsAttr) &&
callee->getOptimizationMode() != OptimizationMode::NoOptimization) {
diagnose(calleeModule.getASTContext(),
callee->getLocation().getSourceLoc(),
diag::constexpr_imported_func_not_onone,
demangleSymbolName(callee->getName()));
}
}
}

Expand Down
17 changes: 10 additions & 7 deletions lib/SILOptimizer/Utils/ConstExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1534,8 +1534,17 @@ ConstExprFunctionState::evaluateInstructionAndGetNext(
assert(value.getKind() == SymbolicValue::Enum ||
value.getKind() == SymbolicValue::EnumWithPayload);

auto *caseBB = switchInst->getCaseDestination(value.getEnumValue());
SILBasicBlock *caseBB =
switchInst->getCaseDestination(value.getEnumValue());
if (caseBB->getNumArguments() == 0)
return {caseBB->begin(), None};

// Set up the arguments.

// When there are multiple payload components, they form a single
// tuple-typed argument.
assert(caseBB->getNumArguments() == 1);

if (caseBB->getParent()->hasOwnership() &&
switchInst->getDefaultBBOrNull() == caseBB) {
// If we are visiting the default block and we are in ossa, then we may
Expand All @@ -1545,13 +1554,7 @@ ConstExprFunctionState::evaluateInstructionAndGetNext(
return {caseBB->begin(), None};
}

if (caseBB->getNumArguments() == 0)
return {caseBB->begin(), None};

assert(value.getKind() == SymbolicValue::EnumWithPayload);
// When there are multiple payload components, they form a single
// tuple-typed argument.
assert(caseBB->getNumArguments() == 1);
auto argument = value.getEnumPayloadValue();
assert(argument.isConstant());
setValue(caseBB->getArgument(0), argument);
Expand Down
4 changes: 2 additions & 2 deletions stdlib/private/OSLog/OSLogIntegerTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ extension OSLogInterpolation {
/// This function must be constant evaluable and all its arguments
/// must be known at compile time.
@inlinable
@_semantics("oslog.interpolation.getFormatSpecifier")
@_semantics("constant_evaluable")
@_effects(readonly)
@_optimize(none)
internal func getIntegerFormatSpecifier<T>(
Expand Down Expand Up @@ -150,7 +150,7 @@ extension OSLogArguments {
/// Return the number of bytes needed for serializing an integer argument as
/// specified by os_log. This function must be constant evaluable.
@inlinable
@_semantics("oslog.integers.sizeForEncoding")
@_semantics("constant_evaluable")
@_effects(readonly)
@_optimize(none)
internal func sizeForEncoding<T>(
Expand Down
21 changes: 12 additions & 9 deletions stdlib/private/OSLog/OSLogMessage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
/// argument header. The first two bits are used to indicate privacy and
/// the other two are reserved.
@usableFromInline
@_frozen
@frozen
internal enum ArgumentFlag {
case privateFlag
case publicFlag
Expand All @@ -117,7 +117,7 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
/// (Note that an auto-generated rawValue is not constant evaluable because
/// it cannot be annotated so.)
@usableFromInline
@_frozen
@frozen
internal enum ArgumentType {
case scalar, count, string, pointer, object

Expand Down Expand Up @@ -147,7 +147,7 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
/// mask indicate whether there is an argument that is private, and whether
/// there is an argument that is non-scalar: String, NSObject or Pointer.
@usableFromInline
@_frozen
@frozen
internal enum PreambleBitMask {
case privateBitMask
case nonScalarBitMask
Expand Down Expand Up @@ -195,7 +195,7 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
/// An internal initializer that should be used only when there are no
/// interpolated expressions. This function must be constant evaluable.
@inlinable
@_semantics("oslog.interpolation.init")
@_semantics("constant_evaluable")
@_optimize(none)
internal init() {
formatString = ""
Expand All @@ -216,7 +216,7 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
/// Return true if and only if the parameter is .private.
/// This function must be constant evaluable.
@inlinable
@_semantics("oslog.interpolation.isPrivate")
@_semantics("constant_evaluable")
@_effects(readonly)
@_optimize(none)
internal func isPrivate(_ privacy: Privacy) -> Bool {
Expand All @@ -233,7 +233,7 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
/// of the header byte, respectively.
/// This function should be constant evaluable.
@inlinable
@_semantics("oslog.interpolation.getArgumentHeader")
@_semantics("constant_evaluable")
@_effects(readonly)
@_optimize(none)
internal func getArgumentHeader(
Expand All @@ -248,7 +248,7 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
/// Compute the new preamble based whether the current argument is private
/// or not. This function must be constant evaluable.
@inlinable
@_semantics("oslog.interpolation.getUpdatedPreamble")
@_semantics("constant_evaluable")
@_effects(readonly)
@_optimize(none)
internal func getUpdatedPreamble(
Expand All @@ -268,7 +268,8 @@ public struct OSLogInterpolation : StringInterpolationProtocol {

extension String {
/// Replace all percents "%" in the string by "%%" so that the string can be
/// interpreted as a C format string.
/// interpreted as a C format string. This function is constant evaluable
/// and its semantics is modeled within the evaluator.
public var percentEscapedString: String {
@_semantics("string.escapePercent.get")
@_effects(readonly)
Expand All @@ -292,6 +293,7 @@ public struct OSLogMessage :
@inlinable
@_optimize(none)
@_semantics("oslog.message.init_interpolation")
@_semantics("constant_evaluable")
public init(stringInterpolation: OSLogInterpolation) {
self.interpolation = stringInterpolation
}
Expand All @@ -301,6 +303,7 @@ public struct OSLogMessage :
@inlinable
@_optimize(none)
@_semantics("oslog.message.init_stringliteral")
@_semantics("constant_evaluable")
public init(stringLiteral value: String) {
var s = OSLogInterpolation()
s.appendLiteral(value)
Expand Down Expand Up @@ -332,7 +335,7 @@ internal struct OSLogArguments {

/// This function must be constant evaluable.
@inlinable
@_semantics("oslog.arguments.init_empty")
@_semantics("constant_evaluable")
@_optimize(none)
internal init() {
argumentClosures = nil
Expand Down
4 changes: 2 additions & 2 deletions stdlib/private/OSLog/OSLogStringTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ extension OSLogInterpolation {
/// This function must be constant evaluable and all its arguments
/// must be known at compile time.
@inlinable
@_semantics("oslog.interpolation.getFormatSpecifier")
@_semantics("constant_evaluable")
@_effects(readonly)
@_optimize(none)
internal func getStringFormatSpecifier(_ isPrivate: Bool) -> String {
Expand Down Expand Up @@ -102,7 +102,7 @@ extension OSLogArguments {
@inlinable
@_optimize(none)
@_effects(readonly)
@_semantics("oslog.string.sizeForEncoding")
@_semantics("constant_evaluable")
internal func sizeForEncoding() -> Int {
return Int.bitWidth &>> logBitsPerByte
}
Expand Down
51 changes: 51 additions & 0 deletions test/SILOptimizer/OSLogConstantEvaluableTest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend -emit-silgen -primary-file %s -o %t/OSLogConstantEvaluableTest_silgen.sil
//
// Run the (mandatory) passes on which constant evaluator depends, and run the
// constant evaluator on the SIL produced after the dependent passes are run.
//
// RUN: %target-sil-opt -silgen-cleanup -raw-sil-inst-lowering -allocbox-to-stack -mandatory-inlining -constexpr-limit 1024 -test-constant-evaluable-subset %t/OSLogConstantEvaluableTest_silgen.sil > %t/OSLogConstantEvaluableTest.sil 2> %t/error-output
//
// RUN: %FileCheck %s < %t/error-output
//
// REQUIRES: OS=macosx || OS=ios || OS=tvos || OS=watchos

// Test that the functions defined in the OSLogPrototype overlay annotated as
// constant evaluable are so (with the constexpr-limit defined above).
// This test is meant to catch regressions in the OSLog overlay implementation
// affecting the constant evaluability of functions that are expected to be so.

import OSLogPrototype

// CHECK-LABEL: @init(stringLiteral: String) -> OSLogMessage
// CHECK-NOT: error:
@_semantics("test_driver")
func osLogMessageStringLiteralInitTest() -> OSLogMessage {
return "A string literal"
}

// CHECK-LABEL: @isPrivate(Privacy) -> Bool
// CHECK-NOT: error:
// CHECK-LABEL: @getIntegerFormatSpecifier<A where A: FixedWidthInteger>(A.Type, IntFormat, Bool) -> String
// CHECK-NOT: error:
// CHECK-LABEL: @sizeForEncoding<A where A: FixedWidthInteger>(A.Type) -> Int
// CHECK-NOT: error:
// CHECK-LABEL: @getArgumentHeader(isPrivate: Bool, type: ArgumentType) -> UInt8
// CHECK-NOT: error:
// CHECK-LABEL: @getUpdatedPreamble(isPrivate: Bool, isScalar: Bool) -> UInt8
// CHECK-NOT: error:
// CHECK-LABEL: @init(stringInterpolation: OSLogInterpolation) -> OSLogMessage
// CHECK-NOT: error:
@_semantics("test_driver")
func intValueInterpolationTest() -> OSLogMessage {
return "An integer value \(10)"
}

// CHECK-LABEL: @getStringFormatSpecifier(Bool) -> String
// CHECK-NOT: error:
// CHECK-LABEL: @sizeForEncoding() -> Int
// CHECK-NOT: error:
@_semantics("test_driver")
func stringValueInterpolationTest() -> OSLogMessage {
return "A string value \("xyz")"
}
2 changes: 1 addition & 1 deletion test/SILOptimizer/constant_evaluator_test.sil
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ bb0:
%0 = integer_literal $Builtin.Int64, -5
%2 = function_ref @factorial : $@convention(thin) (Builtin.Int64) -> Builtin.Int64
%3 = apply %2(%0) : $@convention(thin) (Builtin.Int64) -> Builtin.Int64
// CHECK: {{.*}}:[[@LINE-1]]:{{.*}}: note: exceeded instruction limit: 512 when evaluating the expression at compile time
// CHECK: {{.*}}:[[@LINE-1]]:{{.*}}: note: exceeded instruction limit: {{.*}} when evaluating the expression at compile time
// CHECK: {{.*}}: note: limit exceeded here
return %3 : $Builtin.Int64
}
Expand Down
2 changes: 1 addition & 1 deletion test/SILOptimizer/pound_assert_test_recursive.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
// match what we actually want.

// CHECK: error: #assert condition not constant
// CHECK: note: exceeded instruction limit: 512 when evaluating the expression at compile time
// CHECK: note: exceeded instruction limit: {{.*}} when evaluating the expression at compile time
// CHECK: limit exceeded here
func recursive(a: Int) -> Int {
return a == 0 ? 0 : recursive(a: a-1)
Expand Down