Skip to content

const evaluator: string values and init operations #21711

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 2 commits into from
Jan 12, 2019
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
22 changes: 22 additions & 0 deletions include/swift/SIL/SILConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ class SymbolicValue {
/// This value is represented with an inline integer representation.
RK_IntegerInline,

/// This value is represented with a bump-pointer allocated char array
/// representing a UTF-8 encoded string.
RK_String,

/// This value is a struct or tuple of constants. This is tracked by the
/// "aggregate" member of the value union.
RK_Aggregate,
Expand Down Expand Up @@ -124,6 +128,10 @@ class SymbolicValue {
/// This holds the bits of an integer for an inline representation.
uint64_t integerInline;

/// When this SymbolicValue is of "String" kind, this pointer stores
/// information about the StringRef value it holds.
const char *string;

/// When this SymbolicValue is of "Aggregate" kind, this pointer stores
/// information about the array elements and count.
const SymbolicValue *aggregate;
Expand All @@ -147,6 +155,9 @@ class SymbolicValue {
/// representation, which makes the number of entries in the list derivable.
unsigned integerBitwidth;

/// This is the number of bytes for an RK_String representation.
unsigned stringNumBytes;

/// This is the number of elements for an RK_Aggregate representation.
unsigned aggregateNumElements;
} auxInfo;
Expand All @@ -168,6 +179,10 @@ class SymbolicValue {
/// This is an integer constant.
Integer,

/// String values may have SIL type of Builtin.RawPointer or Builtin.Word
/// type.
String,

/// This can be an array, struct, tuple, etc.
Aggregate,

Expand Down Expand Up @@ -242,6 +257,13 @@ class SymbolicValue {
APInt getIntegerValue() const;
unsigned getIntegerValueBitWidth() const;

/// Returns a SymbolicValue representing a UTF-8 encoded string.
static SymbolicValue getString(StringRef string,
ASTContext &astContext);

/// Returns the UTF-8 encoded string underlying a SymbolicValue.
StringRef getStringValue() const;

/// This returns an aggregate value with the specified elements in it. This
/// copies the elements into the specified ASTContext.
static SymbolicValue getAggregate(ArrayRef<SymbolicValue> elements,
Expand Down
35 changes: 35 additions & 0 deletions lib/SIL/SILConstants.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ void SymbolicValue::print(llvm::raw_ostream &os, unsigned indent) const {
case RK_IntegerInline:
os << "int: " << getIntegerValue() << "\n";
return;
case RK_String:
os << "string: \"" << getStringValue() << "\"\n";
return;
case RK_Aggregate: {
ArrayRef<SymbolicValue> elements = getAggregateValue();
switch (elements.size()) {
Expand Down Expand Up @@ -111,6 +114,8 @@ SymbolicValue::Kind SymbolicValue::getKind() const {
case RK_Integer:
case RK_IntegerInline:
return Integer;
case RK_String:
return String;
case RK_DirectAddress:
case RK_DerivedAddress:
return Address;
Expand All @@ -131,6 +136,8 @@ SymbolicValue::cloneInto(ASTContext &astContext) const {
case RK_IntegerInline:
case RK_Integer:
return SymbolicValue::getInteger(getIntegerValue(), astContext);
case RK_String:
return SymbolicValue::getString(getStringValue(), astContext);
case RK_Aggregate: {
auto elts = getAggregateValue();
SmallVector<SymbolicValue, 4> results;
Expand Down Expand Up @@ -215,6 +222,34 @@ unsigned SymbolicValue::getIntegerValueBitWidth() const {
return auxInfo.integerBitwidth;
}

//===----------------------------------------------------------------------===//
// Strings
//===----------------------------------------------------------------------===//

// Returns a SymbolicValue representing a UTF-8 encoded string.
SymbolicValue SymbolicValue::getString(StringRef string,
ASTContext &astContext) {
// TODO: Could have an inline representation for strings if thre was demand,
// just store a char[8] as the storage.

auto *resultPtr = astContext.Allocate<char>(string.size()).data();
std::uninitialized_copy(string.begin(), string.end(), resultPtr);

SymbolicValue result;
result.representationKind = RK_String;
result.value.string = resultPtr;
result.auxInfo.stringNumBytes = string.size();
return result;
}

// Returns the UTF-8 encoded string underlying a SymbolicValue.
StringRef SymbolicValue::getStringValue() const {
assert(getKind() == String);

assert(representationKind == RK_String);
return StringRef(value.string, auxInfo.stringNumBytes);
}

//===----------------------------------------------------------------------===//
// Aggregates
//===----------------------------------------------------------------------===//
Expand Down
64 changes: 64 additions & 0 deletions lib/SILOptimizer/Utils/ConstExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ evaluateAndCacheCall(SILFunction &fn, SubstitutionMap substitutionMap,
// ConstantFolding.h/cpp files should be subsumed by this, as this is a more
// general framework.

enum class WellKnownFunction {
// String.init()
StringInitEmpty,
// String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
StringMakeUTF8
};

static llvm::Optional<WellKnownFunction> classifyFunction(SILFunction *fn) {
if (fn->hasSemanticsAttr("string.init_empty"))
return WellKnownFunction::StringInitEmpty;
if (fn->hasSemanticsAttr("string.makeUTF8"))
return WellKnownFunction::StringMakeUTF8;
return None;
}

//===----------------------------------------------------------------------===//
// ConstExprFunctionState implementation.
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -115,6 +130,9 @@ class ConstExprFunctionState {
llvm::Optional<SymbolicValue> computeOpaqueCallResult(ApplyInst *apply,
SILFunction *callee);

llvm::Optional<SymbolicValue>
computeWellKnownCallResult(ApplyInst *apply, WellKnownFunction callee);

SymbolicValue getSingleWriterAddressValue(SILValue addr);
SymbolicValue getConstAddrAndLoadResult(SILValue addr);
SymbolicValue loadAddrValue(SILValue addr, SymbolicValue addrVal);
Expand Down Expand Up @@ -149,6 +167,8 @@ SymbolicValue ConstExprFunctionState::computeConstantValue(SILValue value) {
// immediately.
if (auto *ili = dyn_cast<IntegerLiteralInst>(value))
return SymbolicValue::getInteger(ili->getValue(), evaluator.getASTContext());
if (auto *sli = dyn_cast<StringLiteralInst>(value))
return SymbolicValue::getString(sli->getValue(), evaluator.getASTContext());

if (auto *fri = dyn_cast<FunctionRefInst>(value))
return SymbolicValue::getFunction(fri->getReferencedFunction());
Expand Down Expand Up @@ -523,6 +543,46 @@ ConstExprFunctionState::computeOpaqueCallResult(ApplyInst *apply,
return evaluator.getUnknown((SILInstruction *)apply, UnknownReason::Default);
}

/// 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
/// the error.
llvm::Optional<SymbolicValue>
ConstExprFunctionState::computeWellKnownCallResult(ApplyInst *apply,
WellKnownFunction callee) {
auto conventions = apply->getSubstCalleeConv();
switch (callee) {
case WellKnownFunction::StringInitEmpty: { // String.init()
assert(conventions.getNumDirectSILResults() == 1 &&
conventions.getNumIndirectSILResults() == 0 &&
"unexpected String.init() signature");
auto result = SymbolicValue::getString("", evaluator.getASTContext());
setValue(apply, result);
return None;
}
case WellKnownFunction::StringMakeUTF8: {
// String.init(_builtinStringLiteral start: Builtin.RawPointer,
// utf8CodeUnitCount: Builtin.Word,
// isASCII: Builtin.Int1)
assert(conventions.getNumDirectSILResults() == 1 &&
conventions.getNumIndirectSILResults() == 0 &&
conventions.getNumParameters() == 4 && "unexpected signature");
auto literal = getConstantValue(apply->getOperand(1));
if (literal.getKind() != SymbolicValue::String)
break;
auto literalVal = literal.getStringValue();

auto byteCount = getConstantValue(apply->getOperand(2));
if (byteCount.getKind() != SymbolicValue::Integer ||
byteCount.getIntegerValue().getLimitedValue() != literalVal.size())
break;
setValue(apply, literal);
return None;
}
}
llvm_unreachable("unhandled WellKnownFunction");
}

/// Given a call to a function, determine whether it is a call to a constexpr
/// function. If so, collect its arguments as constants, fold it and return
/// None. If not, mark the results as Unknown, and return an Unknown with
Expand All @@ -539,6 +599,10 @@ ConstExprFunctionState::computeCallResult(ApplyInst *apply) {

SILFunction *callee = calleeFn.getFunctionValue();

// If this is a well-known function, do not step into it.
if (auto wellKnownFunction = classifyFunction(callee))
return computeWellKnownCallResult(apply, *wellKnownFunction);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@marcrasi I wonder if it would be better to make a separate function for lines 560 to 591 that handles well-known functions. As we plan to add more well-known functions, it would make this more modular.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

// Verify that we can fold all of the arguments to the call.
SmallVector<SymbolicValue, 4> paramConstants;
for (unsigned i = 0, e = apply->getNumOperands() - 1; i != e; ++i) {
Expand Down
1 change: 1 addition & 0 deletions stdlib/public/core/String.swift
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ public struct String {
/// let empty = ""
/// let alsoEmpty = String()
@inlinable @inline(__always)
@_semantics("string.init_empty")
public init() { self.init(_StringGuts()) }
}

Expand Down
42 changes: 42 additions & 0 deletions test/SILOptimizer/pound_assert.swift
Original file line number Diff line number Diff line change
Expand Up @@ -455,3 +455,45 @@ func testStructPassedAsProtocols() {
#assert(callProtoSimpleMethod(s) == 0) // expected-error {{#assert condition not constant}}
// expected-note@-1 {{could not fold operation}}
}

//===----------------------------------------------------------------------===//
// Strings
//
// TODO: The constant evaluator does not implement string accesses/comparisons
// so theses tests cannot test that the implemented string operations produce
// correct values in the arrays. These tests only test that the implemented
// string operations do not crash or produce unknown values. As soon as we have
// string accesses/comparisons, modify these tests to check the values in the
// strings.
//===----------------------------------------------------------------------===//

struct ContainsString {
let x: Int
let str: String
}

func stringInitEmptyTopLevel() {
let c = ContainsString(x: 1, str: "")
#assert(c.x == 1)
}

func stringInitNonEmptyTopLevel() {
let c = ContainsString(x: 1, str: "hello world")
#assert(c.x == 1)
}

func stringInitEmptyFlowSensitive() -> ContainsString {
return ContainsString(x: 1, str: "")
}

func invokeStringInitEmptyFlowSensitive() {
#assert(stringInitEmptyFlowSensitive().x == 1)
}

func stringInitNonEmptyFlowSensitive() -> ContainsString {
return ContainsString(x: 1, str: "hello world")
}

func invokeStringInitNonEmptyFlowSensitive() {
#assert(stringInitNonEmptyFlowSensitive().x == 1)
}