Skip to content

Commit 61fd4b1

Browse files
committed
[OSLog][Test] Update the new oslog overlay implementation to use
@_semantics("constant_evaluable") annotation to denote constant evaluable functions. Add a test suite that uses the sil-opt pass ConstantEvaluableSubsetChecker.cpp to check the constant evaluability of function in the OSLog overlay.
1 parent bcafb78 commit 61fd4b1

File tree

11 files changed

+130
-36
lines changed

11 files changed

+130
-36
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -402,9 +402,11 @@ NOTE(constexpr_unsupported_instruction_found, none,
402402
NOTE(constexpr_unsupported_instruction_found_here,none, "operation"
403403
"%select{| used by this call is}0 not supported by the evaluator", (bool))
404404

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

410412
NOTE(constexpr_untracked_sil_value_use_found, none,
@@ -428,7 +430,10 @@ NOTE(constexpr_returned_by_unevaluated_instruction,none,
428430
"return value of an unevaluated instruction is not a constant", ())
429431
NOTE(constexpr_mutated_by_unevaluated_instruction,none, "value mutable by an "
430432
"unevaluated instruction is not a constant", ())
433+
431434
ERROR(not_constant_evaluable, none, "not constant evaluable", ())
435+
ERROR(constexpr_imported_func_not_onone, none, "imported constant evaluable "
436+
"function '%0' must be annotated '@_optimize(none)'", (StringRef))
432437

433438
ERROR(non_physical_addressof,none,
434439
"addressof only works with purely physical lvalues; "

lib/SIL/SILConstants.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ using namespace swift;
2020

2121
namespace swift {
2222
llvm::cl::opt<unsigned>
23-
ConstExprLimit("constexpr-limit", llvm::cl::init(512),
23+
ConstExprLimit("constexpr-limit", llvm::cl::init(1024),
2424
llvm::cl::desc("Number of instructions interpreted in a"
2525
" constexpr function"));
2626
}
@@ -711,10 +711,10 @@ void SymbolicValue::emitUnknownDiagnosticNotes(SILLocation fallbackLoc) {
711711
SILFunction *callee = unknownReason.getCalleeWithoutImplmentation();
712712
std::string demangledCalleeName =
713713
Demangle::demangleSymbolAsString(callee->getName());
714-
diagnose(ctx, diagLoc, diag::constexpr_unknown_function_called,
714+
diagnose(ctx, diagLoc, diag::constexpr_found_callee_with_no_body,
715715
StringRef(demangledCalleeName));
716716
if (emitTriggerLocInDiag)
717-
diagnose(ctx, triggerLoc, diag::constexpr_unknown_function_called_here,
717+
diagnose(ctx, triggerLoc, diag::constexpr_callee_with_no_body,
718718
triggerLocSkipsInternalLocs);
719719
return;
720720
}

lib/SILOptimizer/Mandatory/OSLogOptimization.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ static bool shouldAttemptEvaluation(SILInstruction *inst) {
249249
return false;
250250

251251
return calleeFun->hasSemanticsAttrThatStartsWith("string.") ||
252-
calleeFun->hasSemanticsAttrThatStartsWith("oslog.");
252+
calleeFun->hasSemanticsAttr("constant_evaluable");
253253
}
254254

255255
/// Skip or evaluate the given instruction based on the evaluation policy and

lib/SILOptimizer/UtilityPasses/ConstantEvaluableSubsetChecker.cpp

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,18 @@ class ConstantEvaluableSubsetChecker : public SILModuleTransform {
6565
SymbolicValueBumpAllocator allocator;
6666
ConstExprStepEvaluator stepEvaluator(allocator, fun,
6767
/*trackCallees*/ true);
68+
bool previousEvaluationHadFatalError = false;
6869

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

7273
if (isa<ReturnInst>(inst))
7374
break;
7475

76+
assert(!previousEvaluationHadFatalError &&
77+
"cannot continue evaluation of test driver as previous call "
78+
"resulted in non-skippable evaluation error.");
79+
7580
auto *applyInst = dyn_cast<ApplyInst>(inst);
7681
SILFunction *callee = nullptr;
7782
if (applyInst) {
@@ -85,7 +90,11 @@ class ConstantEvaluableSubsetChecker : public SILModuleTransform {
8590
!callee->hasSemanticsAttr(constantEvaluableSemanticsAttr)) {
8691
std::tie(nextInstOpt, errorVal) =
8792
stepEvaluator.tryEvaluateOrElseMakeEffectsNonConstant(currI);
88-
assert(nextInstOpt && "non-constant control flow in the test driver");
93+
if (!nextInstOpt) {
94+
// This indicates an error in the test driver.
95+
errorVal->emitUnknownDiagnosticNotes(inst->getLoc());
96+
assert(false && "non-constant control flow in the test driver");
97+
}
8998
currI = nextInstOpt.getValue();
9099
continue;
91100
}
@@ -101,17 +110,40 @@ class ConstantEvaluableSubsetChecker : public SILModuleTransform {
101110
errorVal->emitUnknownDiagnosticNotes(inst->getLoc());
102111
}
103112

104-
if (!nextInstOpt) {
105-
break; // This instruction should be the end of the test driver.
113+
if (nextInstOpt) {
114+
currI = nextInstOpt.getValue();
115+
continue;
106116
}
107-
currI = nextInstOpt.getValue();
117+
118+
// Here, a non-skippable error like "instruction-limit exceeded" has been
119+
// encountered during evaluation. Proceed to the next instruction but
120+
// ensure that an assertion failure occurs if there is any instruction
121+
// to evaluate after this instruction.
122+
++currI;
123+
previousEvaluationHadFatalError = true;
108124
}
109125

110-
// Record functions that were called during this evaluation to detect
111-
// whether the test drivers in the SILModule cover all function annotated
112-
// as "constant_evaluable".
126+
// For every function seen during the evaluation of this constant evaluable
127+
// function:
128+
// 1. Record it so as to detect whether the test drivers in the SILModule
129+
// cover all function annotated as "constant_evaluable".
130+
//
131+
// 2. If the callee is annotated as constant_evaluable and is imported from
132+
// a different Swift module (other than stdlib), check that the function is
133+
// marked as Onone. Otherwise, it could have been optimized, which will
134+
// break constant evaluability.
113135
for (SILFunction *callee : stepEvaluator.getFuncsCalledDuringEvaluation()) {
114136
evaluatedFunctions.insert(callee);
137+
138+
SILModule &calleeModule = callee->getModule();
139+
if (callee->isAvailableExternally() &&
140+
callee->hasSemanticsAttr(constantEvaluableSemanticsAttr) &&
141+
callee->getOptimizationMode() != OptimizationMode::NoOptimization) {
142+
diagnose(calleeModule.getASTContext(),
143+
callee->getLocation().getSourceLoc(),
144+
diag::constexpr_imported_func_not_onone,
145+
demangleSymbolName(callee->getName()));
146+
}
115147
}
116148
}
117149

lib/SILOptimizer/Utils/ConstExpr.cpp

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,8 +1534,17 @@ ConstExprFunctionState::evaluateInstructionAndGetNext(
15341534
assert(value.getKind() == SymbolicValue::Enum ||
15351535
value.getKind() == SymbolicValue::EnumWithPayload);
15361536

1537-
auto *caseBB = switchInst->getCaseDestination(value.getEnumValue());
1537+
SILBasicBlock *caseBB =
1538+
switchInst->getCaseDestination(value.getEnumValue());
1539+
if (caseBB->getNumArguments() == 0)
1540+
return {caseBB->begin(), None};
1541+
15381542
// Set up the arguments.
1543+
1544+
// When there are multiple payload components, they form a single
1545+
// tuple-typed argument.
1546+
assert(caseBB->getNumArguments() == 1);
1547+
15391548
if (caseBB->getParent()->hasOwnership() &&
15401549
switchInst->getDefaultBBOrNull() == caseBB) {
15411550
// If we are visiting the default block and we are in ossa, then we may
@@ -1545,13 +1554,7 @@ ConstExprFunctionState::evaluateInstructionAndGetNext(
15451554
return {caseBB->begin(), None};
15461555
}
15471556

1548-
if (caseBB->getNumArguments() == 0)
1549-
return {caseBB->begin(), None};
1550-
15511557
assert(value.getKind() == SymbolicValue::EnumWithPayload);
1552-
// When there are multiple payload components, they form a single
1553-
// tuple-typed argument.
1554-
assert(caseBB->getNumArguments() == 1);
15551558
auto argument = value.getEnumPayloadValue();
15561559
assert(argument.isConstant());
15571560
setValue(caseBB->getArgument(0), argument);

stdlib/private/OSLog/OSLogIntegerTypes.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ extension OSLogInterpolation {
106106
/// This function must be constant evaluable and all its arguments
107107
/// must be known at compile time.
108108
@inlinable
109-
@_semantics("oslog.interpolation.getFormatSpecifier")
109+
@_semantics("constant_evaluable")
110110
@_effects(readonly)
111111
@_optimize(none)
112112
internal func getIntegerFormatSpecifier<T>(
@@ -150,7 +150,7 @@ extension OSLogArguments {
150150
/// Return the number of bytes needed for serializing an integer argument as
151151
/// specified by os_log. This function must be constant evaluable.
152152
@inlinable
153-
@_semantics("oslog.integers.sizeForEncoding")
153+
@_semantics("constant_evaluable")
154154
@_effects(readonly)
155155
@_optimize(none)
156156
internal func sizeForEncoding<T>(

stdlib/private/OSLog/OSLogMessage.swift

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
9595
/// argument header. The first two bits are used to indicate privacy and
9696
/// the other two are reserved.
9797
@usableFromInline
98-
@_frozen
98+
@frozen
9999
internal enum ArgumentFlag {
100100
case privateFlag
101101
case publicFlag
@@ -117,7 +117,7 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
117117
/// (Note that an auto-generated rawValue is not constant evaluable because
118118
/// it cannot be annotated so.)
119119
@usableFromInline
120-
@_frozen
120+
@frozen
121121
internal enum ArgumentType {
122122
case scalar, count, string, pointer, object
123123

@@ -147,7 +147,7 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
147147
/// mask indicate whether there is an argument that is private, and whether
148148
/// there is an argument that is non-scalar: String, NSObject or Pointer.
149149
@usableFromInline
150-
@_frozen
150+
@frozen
151151
internal enum PreambleBitMask {
152152
case privateBitMask
153153
case nonScalarBitMask
@@ -195,7 +195,7 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
195195
/// An internal initializer that should be used only when there are no
196196
/// interpolated expressions. This function must be constant evaluable.
197197
@inlinable
198-
@_semantics("oslog.interpolation.init")
198+
@_semantics("constant_evaluable")
199199
@_optimize(none)
200200
internal init() {
201201
formatString = ""
@@ -216,7 +216,7 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
216216
/// Return true if and only if the parameter is .private.
217217
/// This function must be constant evaluable.
218218
@inlinable
219-
@_semantics("oslog.interpolation.isPrivate")
219+
@_semantics("constant_evaluable")
220220
@_effects(readonly)
221221
@_optimize(none)
222222
internal func isPrivate(_ privacy: Privacy) -> Bool {
@@ -233,7 +233,7 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
233233
/// of the header byte, respectively.
234234
/// This function should be constant evaluable.
235235
@inlinable
236-
@_semantics("oslog.interpolation.getArgumentHeader")
236+
@_semantics("constant_evaluable")
237237
@_effects(readonly)
238238
@_optimize(none)
239239
internal func getArgumentHeader(
@@ -248,7 +248,7 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
248248
/// Compute the new preamble based whether the current argument is private
249249
/// or not. This function must be constant evaluable.
250250
@inlinable
251-
@_semantics("oslog.interpolation.getUpdatedPreamble")
251+
@_semantics("constant_evaluable")
252252
@_effects(readonly)
253253
@_optimize(none)
254254
internal func getUpdatedPreamble(
@@ -268,7 +268,8 @@ public struct OSLogInterpolation : StringInterpolationProtocol {
268268

269269
extension String {
270270
/// Replace all percents "%" in the string by "%%" so that the string can be
271-
/// interpreted as a C format string.
271+
/// interpreted as a C format string. This function is constant evaluable
272+
/// and its semantics is modeled within the evaluator.
272273
public var percentEscapedString: String {
273274
@_semantics("string.escapePercent.get")
274275
@_effects(readonly)
@@ -292,6 +293,7 @@ public struct OSLogMessage :
292293
@inlinable
293294
@_optimize(none)
294295
@_semantics("oslog.message.init_interpolation")
296+
@_semantics("constant_evaluable")
295297
public init(stringInterpolation: OSLogInterpolation) {
296298
self.interpolation = stringInterpolation
297299
}
@@ -301,6 +303,7 @@ public struct OSLogMessage :
301303
@inlinable
302304
@_optimize(none)
303305
@_semantics("oslog.message.init_stringliteral")
306+
@_semantics("constant_evaluable")
304307
public init(stringLiteral value: String) {
305308
var s = OSLogInterpolation()
306309
s.appendLiteral(value)
@@ -332,7 +335,7 @@ internal struct OSLogArguments {
332335

333336
/// This function must be constant evaluable.
334337
@inlinable
335-
@_semantics("oslog.arguments.init_empty")
338+
@_semantics("constant_evaluable")
336339
@_optimize(none)
337340
internal init() {
338341
argumentClosures = nil

stdlib/private/OSLog/OSLogStringTypes.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ extension OSLogInterpolation {
7474
/// This function must be constant evaluable and all its arguments
7575
/// must be known at compile time.
7676
@inlinable
77-
@_semantics("oslog.interpolation.getFormatSpecifier")
77+
@_semantics("constant_evaluable")
7878
@_effects(readonly)
7979
@_optimize(none)
8080
internal func getStringFormatSpecifier(_ isPrivate: Bool) -> String {
@@ -102,7 +102,7 @@ extension OSLogArguments {
102102
@inlinable
103103
@_optimize(none)
104104
@_effects(readonly)
105-
@_semantics("oslog.string.sizeForEncoding")
105+
@_semantics("constant_evaluable")
106106
internal func sizeForEncoding() -> Int {
107107
return Int.bitWidth &>> logBitsPerByte
108108
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend -emit-silgen -primary-file %s -o %t/OSLogConstantEvaluableTest_silgen.sil
3+
//
4+
// Run the (mandatory) passes on which constant evaluator depends, and run the
5+
// constant evaluator on the SIL produced after the dependent passes are run.
6+
//
7+
// 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
8+
//
9+
// RUN: %FileCheck %s < %t/error-output
10+
//
11+
// REQUIRES: OS=macosx || OS=ios || OS=tvos || OS=watchos
12+
13+
// Test that the functions defined in the OSLogPrototype overlay annotated as
14+
// constant evaluable are so (with the constexpr-limit defined above).
15+
// This test is meant to catch regressions in the OSLog overlay implementation
16+
// affecting the constant evaluability of functions that are expected to be so.
17+
18+
import OSLogPrototype
19+
20+
// CHECK-LABEL: @init(stringLiteral: String) -> OSLogMessage
21+
// CHECK-NOT: error:
22+
@_semantics("test_driver")
23+
func osLogMessageStringLiteralInitTest() -> OSLogMessage {
24+
return "A string literal"
25+
}
26+
27+
// CHECK-LABEL: @isPrivate(Privacy) -> Bool
28+
// CHECK-NOT: error:
29+
// CHECK-LABEL: @getIntegerFormatSpecifier<A where A: FixedWidthInteger>(A.Type, IntFormat, Bool) -> String
30+
// CHECK-NOT: error:
31+
// CHECK-LABEL: @sizeForEncoding<A where A: FixedWidthInteger>(A.Type) -> Int
32+
// CHECK-NOT: error:
33+
// CHECK-LABEL: @getArgumentHeader(isPrivate: Bool, type: ArgumentType) -> UInt8
34+
// CHECK-NOT: error:
35+
// CHECK-LABEL: @getUpdatedPreamble(isPrivate: Bool, isScalar: Bool) -> UInt8
36+
// CHECK-NOT: error:
37+
// CHECK-LABEL: @init(stringInterpolation: OSLogInterpolation) -> OSLogMessage
38+
// CHECK-NOT: error:
39+
@_semantics("test_driver")
40+
func intValueInterpolationTest() -> OSLogMessage {
41+
return "An integer value \(10)"
42+
}
43+
44+
// CHECK-LABEL: @getStringFormatSpecifier(Bool) -> String
45+
// CHECK-NOT: error:
46+
// CHECK-LABEL: @sizeForEncoding() -> Int
47+
// CHECK-NOT: error:
48+
@_semantics("test_driver")
49+
func stringValueInterpolationTest() -> OSLogMessage {
50+
return "A string value \("xyz")"
51+
}

test/SILOptimizer/constant_evaluator_test.sil

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ bb0:
279279
%0 = integer_literal $Builtin.Int64, -5
280280
%2 = function_ref @factorial : $@convention(thin) (Builtin.Int64) -> Builtin.Int64
281281
%3 = apply %2(%0) : $@convention(thin) (Builtin.Int64) -> Builtin.Int64
282-
// CHECK: {{.*}}:[[@LINE-1]]:{{.*}}: note: exceeded instruction limit: 512 when evaluating the expression at compile time
282+
// CHECK: {{.*}}:[[@LINE-1]]:{{.*}}: note: exceeded instruction limit: {{.*}} when evaluating the expression at compile time
283283
// CHECK: {{.*}}: note: limit exceeded here
284284
return %3 : $Builtin.Int64
285285
}

test/SILOptimizer/pound_assert_test_recursive.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
// match what we actually want.
1111

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

0 commit comments

Comments
 (0)