Skip to content

[Constant Evaluator][Tests] Add tests to check constant evaluability of Swift code snippets #27208

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
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
2 changes: 2 additions & 0 deletions include/swift/AST/Builtins.def
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ BUILTIN_BINARY_OPERATION(ExactUDiv, "udiv_exact", "n", IntegerOrVector)
BUILTIN_BINARY_OPERATION(URem, "urem", "n", Integer)
BUILTIN_BINARY_OPERATION(FRem, "frem", "n", FloatOrVector)
BUILTIN_BINARY_OPERATION(Xor, "xor", "n", IntegerOrVector)
// This builtin is an optimizer hint and always returns the first argument.
BUILTIN_BINARY_OPERATION(Expect, "int_expect", "n", Integer)
#undef BUILTIN_BINARY_OPERATION

/// These builtins are analogous the similarly named llvm intrinsics. The
Expand Down
52 changes: 33 additions & 19 deletions include/swift/AST/DiagnosticsSIL.def
Original file line number Diff line number Diff line change
Expand Up @@ -360,61 +360,75 @@ ERROR(pound_assert_failure,none,
NOTE(constexpr_unknown_reason_default,none,
"cannot evaluate expression as constant here", ())
NOTE(constexpr_unevaluable_operation,none,
"cannot constant evaluate operation", ())
"cannot constant evaluate operation%select{| used by this call}0", (bool))

NOTE(constexpr_too_many_instructions,none,
"exceeded instruction limit: %0 when evaluating the expression "
"at compile time", (unsigned))
NOTE(constexpr_limit_exceeding_instruction,none, "limit exceeded here", ())
NOTE(constexpr_limit_exceeding_instruction,none, "limit exceeded "
"%select{here|during this call}0", (bool))

NOTE(constexpr_loop_found_note,none,
"control-flow loop found during evaluation ", ())
NOTE(constexpr_loop_instruction,none, "found loop here", ())
NOTE(constexpr_loop_instruction,none, "found loop "
"%select{here|inside this call}0", (bool))

NOTE(constexpr_overflow,none, "integer overflow detected", ())
NOTE(constexpr_overflow_operation,none, "operation overflows", ())
NOTE(constexpr_overflow_operation,none, "operation"
"%select{| performed during this call}0 overflows", (bool))

NOTE(constexpr_trap,none, "trap detected", ())
NOTE(constexpr_trap_operation,none, "operation traps", ())
NOTE(constexpr_trap_operation,none, "operation"
"%select{| performed during this call}0 traps", (bool))

NOTE(constexpr_assertion_failed, none, "assertion failed with message: %0",
(StringRef))
NOTE(constexpr_assertion_failed_here, none, "assertion failed"
"%select{ here| during this call}0 ", (bool))

NOTE(constexpr_invalid_operand_seen, none,
"operation with invalid operands encountered during evaluation",())
NOTE(constexpr_operand_invalid_here, none,
"operation with invalid operands encountered here",())
"operation with invalid operands encountered "
"%select{here|during this call}0", (bool))

NOTE(constexpr_value_unknown_at_top_level,none,
"cannot evaluate top-level value as constant here",())
NOTE(constexpr_multiple_writers_found_at_top_level,none,
"top-level value has multiple assignments",())

NOTE(constexpr_unsupported_instruction_found, none,
"encountered operation not supported by the evaluator", ())
NOTE(constexpr_unsupported_instruction_found_here,none,
"operation not supported by the evaluator", ())
"encountered operation not supported by the evaluator: %0", (StringRef))
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 a function whose body is not available", ())
"encountered call to '%0' whose body is not available", (StringRef))
NOTE(constexpr_unknown_function_called_here, none,
"call to a function whose body is not available", ())
"%select{|calls a }0function whose body is not available", (bool))

NOTE(constexpr_untracked_sil_value_use_found, none,
"encountered use of a variable not tracked by the evaluator", ())
NOTE(constexpr_untracked_sil_value_used_here, none,
"untracked variable used here", ())

NOTE(constexpr_witness_call_with_no_conformance_found, none,
"cannot find concrete conformance for a witness method call", ())
NOTE(constexpr_witness_call_with_no_target_found, none,
"cannot resolve a witness method call to a concrete function", ())
NOTE(constexpr_witness_call_found_here, none,
"witness method call found here", ())
"untracked variable used %select{here|by this call}0", (bool))

NOTE(constexpr_unresolvable_witness_call, none,
"encountered unresolvable witness method call: '%0'", (StringRef))
NOTE(constexpr_no_witness_table_entry, none, "cannot find witness table entry "
"%select{for this call|for a witness-method invoked during this call}0",
(bool))
NOTE(constexpr_witness_call_with_no_conformance, none,
"cannot find concrete conformance "
"%select{for this call|for a witness-method invoked during this call}0",
(bool))

NOTE(constexpr_unknown_control_flow_due_to_skip,none, "branch depends on "
"non-constant value produced by an unevaluated instructions", ())
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(non_physical_addressof,none,
"addressof only works with purely physical lvalues; "
Expand Down
145 changes: 99 additions & 46 deletions include/swift/SIL/SILConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,63 +41,119 @@ extern llvm::cl::opt<unsigned> ConstExprLimit;
/// allowing the caller to produce a specific diagnostic. The "Unknown"
/// SymbolicValue representation also includes a pointer to the SILNode in
/// question that was problematic.
enum class UnknownReason {
// TODO: Eliminate the default code, by making classifications for each
// failure mode.
Default,
class UnknownReason {
public:
enum UnknownKind {
// TODO: Eliminate the default kind, by making classifications for each
// failure mode.
Default,

/// The constant expression was too big. This is reported on a random
/// instruction within the constexpr that triggered the issue.
TooManyInstructions,

/// A control flow loop was found.
Loop,

/// Integer overflow detected.
Overflow,

/// Unspecified trap detected.
Trap,

/// Assertion failure detected. These have an associated message unlike
/// traps.
AssertionFailure,

/// The constant expression was too big. This is reported on a random
/// instruction within the constexpr that triggered the issue.
TooManyInstructions,
/// An operation was applied over operands whose symbolic values were
/// constants but were not valid for the operation.
InvalidOperandValue,

/// A control flow loop was found.
Loop,
/// Encountered an instruction not supported by the interpreter.
UnsupportedInstruction,

/// Integer overflow detected.
Overflow,
/// Encountered a function call where the body of the called function is
/// not available.
CalleeImplementationUnknown,

/// Unspecified trap detected.
Trap,
/// Attempted to load from/store into a SIL value that was not tracked by
/// the interpreter.
UntrackedSILValue,

/// An operation was applied over operands whose symbolic values were
/// constants but were not valid for the operation.
InvalidOperandValue,
/// Attempted to find a concrete protocol conformance for a witness method
/// and failed.
UnknownWitnessMethodConformance,

/// Encountered an instruction not supported by the interpreter.
UnsupportedInstruction,
/// Attempted to determine the SIL function of a witness method and failed.
NoWitnesTableEntry,

/// Encountered a function call where the body of the called function is
/// not available.
CalleeImplementationUnknown,
/// The value of a top-level variable cannot be determined to be a constant.
/// This is only relevant in the backward evaluation mode, which is used by
/// #assert.
NotTopLevelConstant,

/// Attempted to load from/store into a SIL value that was not tracked by
/// the interpreter.
UntrackedSILValue,
/// A top-level value has multiple writers. This is only relevant in the
/// non-flow-sensitive evaluation mode, which is used by #assert.
MutipleTopLevelWriters,

/// Attempted to find a concrete protocol conformance for a witness method
/// and failed.
UnknownWitnessMethodConformance,
/// Indicates the return value of an instruction that was not evaluated
/// during interpretation.
ReturnedByUnevaluatedInstruction,

/// Attempted to determine the SIL function of a witness method (based on a
/// concrete protocol conformance) and failed.
UnresolvableWitnessMethod,
/// Indicates that the value was possibly modified by an instruction
/// that was not evaluated during the interpretation.
MutatedByUnevaluatedInstruction,
};

/// The value of a top-level variable cannot be determined to be a constant.
/// This is only relevant in the backward evaluation mode, which is used by
/// #assert.
NotTopLevelConstant,
private:
UnknownKind kind;

/// A top-level value has multiple writers. This is only relevant in the
/// non-flow-sensitive evaluation mode, which is used by #assert.
MutipleTopLevelWriters,
// Auxiliary information for different unknown kinds.
union {
SILFunction *function;
const char *failedAssertMessage;
} payload;

/// Indicates the return value of an instruction that was not evaluated during
/// interpretation.
ReturnedByUnevaluatedInstruction,
public:
UnknownKind getKind() { return kind; }

/// Indicates that the value was possibly modified by an instruction
/// that was not evaluated during the interpretation.
MutatedByUnevaluatedInstruction,
static bool isUnknownKindWithPayload(UnknownKind kind) {
return kind == UnknownKind::CalleeImplementationUnknown;
}

static UnknownReason create(UnknownKind kind) {
assert(!isUnknownKindWithPayload(kind));
UnknownReason reason;
reason.kind = kind;
return reason;
}

static UnknownReason createCalleeImplementationUnknown(SILFunction *callee) {
assert(callee);
UnknownReason reason;
reason.kind = UnknownKind::CalleeImplementationUnknown;
reason.payload.function = callee;
return reason;
}

SILFunction *getCalleeWithoutImplmentation() {
assert(kind == UnknownKind::CalleeImplementationUnknown);
return payload.function;
}

static UnknownReason createAssertionFailure(const char *message,
size_t size) {
assert(message[size] == '\0' && "message must be null-terminated");
UnknownReason reason;
reason.kind = UnknownKind::AssertionFailure;
reason.payload.failedAssertMessage = message;
return reason;
}

const char *getAssertionFailureMessage() {
assert(kind == UnknownKind::AssertionFailure);
return payload.failedAssertMessage;
}
};

/// An abstract class that exposes functions for allocating symbolic values.
Expand Down Expand Up @@ -241,9 +297,6 @@ class SymbolicValue {
RepresentationKind representationKind : 8;

union {
/// This is the reason code for RK_Unknown values.
UnknownReason unknownReason : 32;

/// This is the number of bits in an RK_Integer or RK_IntegerInline
/// representation, which makes the number of entries in the list derivable.
unsigned integerBitwidth;
Expand Down
2 changes: 2 additions & 0 deletions include/swift/SILOptimizer/PassManager/Passes.def
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ PASS(ConditionForwarding, "condition-forwarding",
"Conditional Branch Forwarding to Fold SIL switch_enum")
PASS(ConstantEvaluatorTester, "test-constant-evaluator",
"Test constant evaluator")
PASS(ConstantEvaluableSubsetChecker, "test-constant-evaluable-subset",
"Test Swift code snippets expected to be constant evaluable")
PASS(CopyForwarding, "copy-forwarding",
"Copy Forwarding to Remove Redundant Copies")
PASS(CopyPropagation, "copy-propagation",
Expand Down
45 changes: 36 additions & 9 deletions include/swift/SILOptimizer/Utils/ConstExpr.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class SILNode;
class SymbolicValue;
class SymbolicValueAllocator;
class ConstExprFunctionState;
enum class UnknownReason;
class UnknownReason;

/// This class is the main entrypoint for evaluating constant expressions. It
/// also handles caching of previously computed constexpr results.
Expand All @@ -48,10 +48,17 @@ class ConstExprEvaluator {
/// The current call stack, used for providing accurate diagnostics.
llvm::SmallVector<SourceLoc, 4> callStack;

/// When set to true, keep track of all functions called during an evaluation.
bool trackCallees;
/// Functions called during the evaluation. This is an auxiliary information
/// provided to the clients.
llvm::SmallPtrSet<SILFunction *, 2> calledFunctions;

void operator=(const ConstExprEvaluator &) = delete;

public:
explicit ConstExprEvaluator(SymbolicValueAllocator &alloc);
explicit ConstExprEvaluator(SymbolicValueAllocator &alloc,
bool trackCallees = false);
~ConstExprEvaluator();

explicit ConstExprEvaluator(const ConstExprEvaluator &other);
Expand All @@ -75,12 +82,23 @@ class ConstExprEvaluator {
/// This is done in code that is not necessarily itself a constexpr
/// function. The results are added to the results list which is a parallel
/// structure to the input values.
///
/// TODO: Return information about which callees were found to be
/// constexprs, which would allow the caller to delete dead calls to them
/// that occur after after folding them.
void computeConstantValues(ArrayRef<SILValue> values,
SmallVectorImpl<SymbolicValue> &results);

void recordCalledFunctionIfEnabled(SILFunction *callee) {
if (trackCallees) {
calledFunctions.insert(callee);
}
}

/// If the evaluator was initialized with \c trackCallees enabled, return the
/// SIL functions encountered during the evaluations performed with this
/// evaluator. The returned functions include those that were called but
/// failed to complete successfully.
const SmallPtrSetImpl<SILFunction *> &getFuncsCalledDuringEvaluation() const {
assert(trackCallees && "evaluator not configured to track callees");
return calledFunctions;
}
};

/// A constant-expression evaluator that can be used to step through a control
Expand All @@ -106,7 +124,7 @@ class ConstExprStepEvaluator {
/// Constructs a step evaluator given an allocator and a non-null pointer to a
/// SILFunction.
explicit ConstExprStepEvaluator(SymbolicValueAllocator &alloc,
SILFunction *fun);
SILFunction *fun, bool trackCallees = false);
~ConstExprStepEvaluator();

/// Evaluate an instruction in the current interpreter state.
Expand Down Expand Up @@ -162,8 +180,6 @@ class ConstExprStepEvaluator {

Optional<SymbolicValue> lookupConstValue(SILValue value);

bool isKnownFunction(SILFunction *fun);

/// Returns true if and only if `errorVal` denotes an error that requires
/// aborting interpretation and returning the error. Skipping an instruction
/// that produces such errors is not a valid behavior.
Expand All @@ -175,7 +191,18 @@ class ConstExprStepEvaluator {
/// Note that 'skipByMakingEffectsNonConstant' operation is not considered
/// as an evaluation.
unsigned instructionsEvaluatedByLastEvaluation() { return stepsEvaluated; }

/// If the evaluator was initialized with \c trackCallees enabled, return the
/// SIL functions encountered during the evaluations performed with this
/// evaluator. The returned functions include those that were called but
/// failed to complete successfully. Targets of skipped apply instructions
/// will not be included in the returned set.
const SmallPtrSetImpl<SILFunction *> &getFuncsCalledDuringEvaluation() {
return evaluator.getFuncsCalledDuringEvaluation();
}
};

bool isKnownConstantEvaluableFunction(SILFunction *fun);

} // end namespace swift
#endif
2 changes: 1 addition & 1 deletion lib/AST/Builtins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1779,7 +1779,7 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
#define BUILTIN_BINARY_OPERATION(id, name, attrs, overload) case BuiltinValueKind::id:
#include "swift/AST/Builtins.def"
if (Types.size() != 1) return nullptr;
return getBinaryOperation(Id, Types[0]);
return getBinaryOperation(Id, Types[0]);

#define BUILTIN(id, name, Attrs)
#define BUILTIN_BINARY_OPERATION_WITH_OVERFLOW(id, name, _, attrs, overload) case BuiltinValueKind::id:
Expand Down
1 change: 1 addition & 0 deletions lib/SIL/OperandOwnership.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,7 @@ ANY_OWNERSHIP_BUILTIN(SRem)
ANY_OWNERSHIP_BUILTIN(SSubOver)
ANY_OWNERSHIP_BUILTIN(SToSCheckedTrunc)
ANY_OWNERSHIP_BUILTIN(SToUCheckedTrunc)
ANY_OWNERSHIP_BUILTIN(Expect)
ANY_OWNERSHIP_BUILTIN(Shl)
ANY_OWNERSHIP_BUILTIN(Sizeof)
ANY_OWNERSHIP_BUILTIN(StaticReport)
Expand Down
Loading