Skip to content

[4.2] Optimize string constants #17086

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 6 commits into from
Jun 10, 2018
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
5 changes: 5 additions & 0 deletions include/swift/AST/Builtins.def
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,11 @@ BUILTIN_MISC_OPERATION(StaticReport, "staticReport", "", Special)
BUILTIN_MISC_OPERATION(AssertConf, "assert_configuration", "n", Special)

/// StringObjectOr has type (T,T) -> T.
/// Sets bits in a string object. The first operand is bit-cast string literal
/// pointer to an integer. The second operand is the bit mask to be or'd into
/// the high bits of the pointer.
/// It is required that the or'd bits are all 0 in the first operand. So this
/// or-operation is actually equivalent to an addition.
BUILTIN_MISC_OPERATION(StringObjectOr, "stringObjectOr", "n", Integer)

/// Special truncation builtins that check for sign and overflow errors. These
Expand Down
30 changes: 26 additions & 4 deletions lib/IRGen/GenConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,32 @@ static llvm::Constant *emitConstantValue(IRGenModule &IGM, SILValue operand) {
} else if (auto *SLI = dyn_cast<StringLiteralInst>(operand)) {
return emitAddrOfConstantString(IGM, SLI);
} else if (auto *BI = dyn_cast<BuiltinInst>(operand)) {
assert(IGM.getSILModule().getBuiltinInfo(BI->getName()).ID ==
BuiltinValueKind::PtrToInt);
llvm::Constant *ptr = emitConstantValue(IGM, BI->getArguments()[0]);
return llvm::ConstantExpr::getPtrToInt(ptr, IGM.IntPtrTy);
switch (IGM.getSILModule().getBuiltinInfo(BI->getName()).ID) {
case BuiltinValueKind::PtrToInt: {
llvm::Constant *ptr = emitConstantValue(IGM, BI->getArguments()[0]);
return llvm::ConstantExpr::getPtrToInt(ptr, IGM.IntPtrTy);
}
case BuiltinValueKind::ZExtOrBitCast: {
llvm::Constant *value = emitConstantValue(IGM, BI->getArguments()[0]);
return llvm::ConstantExpr::getZExtOrBitCast(value, IGM.getStorageType(BI->getType()));
}
case BuiltinValueKind::StringObjectOr: {
llvm::Constant *lhs = emitConstantValue(IGM, BI->getArguments()[0]);
llvm::Constant *rhs = emitConstantValue(IGM, BI->getArguments()[1]);
// It is a requirement that the or'd bits in the left argument are
// initialized with 0. Therefore the or-operation is equivalent to an
// addition. We need an addition to generate a valid relocation.
return llvm::ConstantExpr::getAdd(lhs, rhs);
}
default:
llvm_unreachable("unsupported builtin for constant expression");
}
} else if (auto *VTBI = dyn_cast<ValueToBridgeObjectInst>(operand)) {
auto *SI = cast<StructInst>(VTBI->getOperand());
assert(SI->getElements().size() == 1);
auto *val = emitConstantValue(IGM, SI->getElements()[0]);
auto *sTy = IGM.getTypeInfo(VTBI->getType()).getStorageType();
return llvm::ConstantExpr::getIntToPtr(val, sTy);
} else {
llvm_unreachable("Unsupported SILInstruction in static initializer!");
}
Expand Down
12 changes: 12 additions & 0 deletions lib/SIL/SILGlobalVariable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@ bool SILGlobalVariable::isValidStaticInitializerInst(const SILInstruction *I,
if (isa<LiteralInst>(bi->getArguments()[0]))
return true;
break;
case BuiltinValueKind::StringObjectOr:
// The first operand can be a string literal (i.e. a pointer), but the
// second operand must be a constant. This enables creating a
// a pointer+offset relocation.
// Note that StringObjectOr requires the or'd bits in the first
// operand to be 0, so the operation is equivalent to an addition.
if (isa<IntegerLiteralInst>(bi->getArguments()[1]))
return true;
break;
case BuiltinValueKind::ZExtOrBitCast:
return true;
default:
break;
}
Expand All @@ -107,6 +118,7 @@ bool SILGlobalVariable::isValidStaticInitializerInst(const SILInstruction *I,
case SILInstructionKind::IntegerLiteralInst:
case SILInstructionKind::FloatLiteralInst:
case SILInstructionKind::ObjectInst:
case SILInstructionKind::ValueToBridgeObjectInst:
return true;
default:
return false;
Expand Down
6 changes: 6 additions & 0 deletions lib/SILOptimizer/PassManager/PassPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,12 @@ static void addClosureSpecializePassPipeline(SILPassPipelinePlan &P) {
P.addDeadFunctionElimination();
P.addDeadObjectElimination();

// These few passes are needed to cleanup between loop unrolling and GlobalOpt.
P.addSimplifyCFG();
P.addSILCombine();
P.addPerformanceConstantPropagation();
P.addSimplifyCFG();

// Hoist globals out of loops.
// Global-init functions should not be inlined GlobalOpt is done.
P.addGlobalOpt();
Expand Down
3 changes: 3 additions & 0 deletions lib/SILOptimizer/SILCombiner/SILCombiner.h
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,15 @@ class SILCombiner :
SILInstruction *visitPartialApplyInst(PartialApplyInst *AI);
SILInstruction *visitApplyInst(ApplyInst *AI);
SILInstruction *visitTryApplyInst(TryApplyInst *AI);
SILInstruction *optimizeStringObject(BuiltinInst *BI);
SILInstruction *visitBuiltinInst(BuiltinInst *BI);
SILInstruction *visitCondFailInst(CondFailInst *CFI);
SILInstruction *visitStrongRetainInst(StrongRetainInst *SRI);
SILInstruction *visitRefToRawPointerInst(RefToRawPointerInst *RRPI);
SILInstruction *visitUpcastInst(UpcastInst *UCI);
SILInstruction *optimizeLoadFromStringLiteral(LoadInst *LI);
SILInstruction *visitLoadInst(LoadInst *LI);
SILInstruction *visitIndexAddrInst(IndexAddrInst *IA);
SILInstruction *visitAllocStackInst(AllocStackInst *AS);
SILInstruction *visitAllocRefInst(AllocRefInst *AR);
SILInstruction *visitSwitchEnumAddrInst(SwitchEnumAddrInst *SEAI);
Expand Down
84 changes: 84 additions & 0 deletions lib/SILOptimizer/SILCombiner/SILCombinerBuiltinVisitors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,87 @@ SILInstruction *optimizeBitOp(BuiltinInst *BI,
return nullptr;
}

/// Returns a 64-bit integer constant if \p op is an integer_literal instruction
/// with a value which fits into 64 bits.
static Optional<uint64_t> getIntConst(SILValue op) {
if (auto *ILI = dyn_cast<IntegerLiteralInst>(op)) {
if (ILI->getValue().getActiveBits() <= 64)
return ILI->getValue().getZExtValue();
}
return None;
}

/// Optimize the bit extract of a string object. Example in SIL pseudo-code,
/// omitting the type-conversion instructions:
///
/// %0 = string_literal
/// %1 = integer_literal 0x8000000000000000
/// %2 = builtin "stringObjectOr_Int64" (%0, %1)
/// %3 = integer_literal 0x4000000000000000
/// %4 = builtin "and_Int64" (%2, %3)
///
/// optimizes to an integer_literal of 0.
SILInstruction *SILCombiner::optimizeStringObject(BuiltinInst *BI) {
assert(BI->getBuiltinInfo().ID == BuiltinValueKind::And);
auto AndOp = getIntConst(BI->getArguments()[1]);
if (!AndOp)
return nullptr;

uint64_t andBits = AndOp.getValue();

// TODO: It's bad that we have to hardcode the payload bit mask here.
// Instead we should introduce builtins (or instructions) to extract the
// payload and extra bits, respectively.
const uint64_t payloadBits = 0x00ffffffffffffffll;
if ((andBits & payloadBits) != 0)
return nullptr;

uint64_t setBits = 0;
SILValue val = BI->getArguments()[0];
while (val->getKind() != ValueKind::StringLiteralInst) {
switch (val->getKind()) {
// Look through all the type conversion and projection instructions.
case ValueKind::StructExtractInst:
case ValueKind::UncheckedTrivialBitCastInst:
case ValueKind::ValueToBridgeObjectInst:
val = cast<SingleValueInstruction>(val)->getOperand(0);
break;
case ValueKind::StructInst: {
auto *SI = cast<StructInst>(val);
if (SI->getNumOperands() != 1)
return nullptr;
val = SI->getOperand(0);
break;
}
case ValueKind::BuiltinInst: {
auto *B = cast<BuiltinInst>(val);
switch (B->getBuiltinInfo().ID) {
case BuiltinValueKind::StringObjectOr:
// Note that it is a requirement that the or'd bits of the left
// operand are initially zero.
if (auto opVal = getIntConst(B->getArguments()[1])) {
setBits |= opVal.getValue();
} else {
return nullptr;
}
LLVM_FALLTHROUGH;
case BuiltinValueKind::ZExtOrBitCast:
case BuiltinValueKind::PtrToInt:
val = B->getArguments()[0];
break;
default:
return nullptr;
}
break;
}
default:
return nullptr;
}
}
return Builder.createIntegerLiteral(BI->getLoc(), BI->getType(),
setBits & andBits);
}

SILInstruction *SILCombiner::visitBuiltinInst(BuiltinInst *I) {
if (I->getBuiltinInfo().ID == BuiltinValueKind::CanBeObjCClass)
return optimizeBuiltinCanBeObjCClass(I);
Expand Down Expand Up @@ -496,6 +577,9 @@ SILInstruction *SILCombiner::visitBuiltinInst(BuiltinInst *I) {
break;
}
case BuiltinValueKind::And:
if (SILInstruction *optimized = optimizeStringObject(I))
return optimized;

return optimizeBitOp(I,
[](APInt &left, const APInt &right) { left &= right; } /* combine */,
[](const APInt &i) -> bool { return i.isAllOnesValue(); } /* isNeutral */,
Expand Down
77 changes: 77 additions & 0 deletions lib/SILOptimizer/SILCombiner/SILCombinerMiscVisitors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,58 @@ SILInstruction *SILCombiner::visitAllocRefInst(AllocRefInst *AR) {
return nullptr;
}

/// Returns the base address if \p val is an index_addr with constant index.
static SILValue isConstIndexAddr(SILValue val, unsigned &index) {
auto *IA = dyn_cast<IndexAddrInst>(val);
if (!IA)
return nullptr;
auto *Index = dyn_cast<IntegerLiteralInst>(IA->getIndex());

// Limiting to 32 bits is more than enough. The reason why not limiting to 64
// bits is to let room for overflow when we add two indices.
if (!Index || Index->getValue().getActiveBits() > 32)
return nullptr;

index = Index->getValue().getZExtValue();
return IA->getBase();
}

/// Optimize loading bytes from a string literal.
/// Example in SIL pseudo code:
/// %0 = string_literal "abc"
/// %1 = integer_literal 2
/// %2 = index_addr %0, %2
/// %3 = load %2
/// ->
/// %3 = integer_literal 'c'
SILInstruction *SILCombiner::optimizeLoadFromStringLiteral(LoadInst *LI) {
auto *SEA = dyn_cast<StructElementAddrInst>(LI->getOperand());
if (!SEA)
return nullptr;

SILValue addr = SEA->getOperand();
unsigned index = 0;
if (SILValue iaBase = isConstIndexAddr(addr, index))
addr = iaBase;

auto *PTA = dyn_cast<PointerToAddressInst>(addr);
if (!PTA)
return nullptr;
auto *Literal = dyn_cast<StringLiteralInst>(PTA->getOperand());
if (!Literal || Literal->getEncoding() != StringLiteralInst::Encoding::UTF8)
return nullptr;

BuiltinIntegerType *BIType = LI->getType().getAs<BuiltinIntegerType>();
if (!BIType || !BIType->isFixedWidth(8))
return nullptr;

StringRef str = Literal->getValue();
if (index >= str.size())
return nullptr;

return Builder.createIntegerLiteral(LI->getLoc(), LI->getType(), str[index]);
}

SILInstruction *SILCombiner::visitLoadInst(LoadInst *LI) {
// (load (upcast-ptr %x)) -> (upcast-ref (load %x))
Builder.setCurrentDebugScope(LI->getDebugScope());
Expand All @@ -474,6 +526,9 @@ SILInstruction *SILCombiner::visitLoadInst(LoadInst *LI) {
return Builder.createUpcast(LI->getLoc(), NewLI, LI->getType());
}

if (SILInstruction *I = optimizeLoadFromStringLiteral(LI))
return I;

// Given a load with multiple struct_extracts/tuple_extracts and no other
// uses, canonicalize the load into several (struct_element_addr (load))
// pairs.
Expand Down Expand Up @@ -535,6 +590,28 @@ SILInstruction *SILCombiner::visitLoadInst(LoadInst *LI) {
return eraseInstFromFunction(*LI);
}

/// Optimize nested index_addr instructions:
/// Example in SIL pseudo code:
/// %1 = index_addr %ptr, x
/// %2 = index_addr %1, y
/// ->
/// %2 = index_addr %ptr, x+y
SILInstruction *SILCombiner::visitIndexAddrInst(IndexAddrInst *IA) {
unsigned index = 0;
SILValue base = isConstIndexAddr(IA, index);
if (!base)
return nullptr;

unsigned index2 = 0;
SILValue base2 = isConstIndexAddr(base, index2);
if (!base2)
return nullptr;

auto *newIndex = Builder.createIntegerLiteral(IA->getLoc(),
IA->getIndex()->getType(), index + index2);
return Builder.createIndexAddr(IA->getLoc(), base2, newIndex);
}

SILInstruction *SILCombiner::visitReleaseValueInst(ReleaseValueInst *RVI) {
SILValue Operand = RVI->getOperand();
SILType OperandTy = Operand->getType();
Expand Down
5 changes: 2 additions & 3 deletions stdlib/public/core/StringObject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,7 @@ extension _StringObject {
internal
static var _variantMask: UInt {
@inline(__always)
get { return UInt(Builtin.stringObjectOr_Int64(
_isValueBit._value, _subVariantBit._value)) }
get { return _isValueBit | _subVariantBit }
}

@inlinable
Expand Down Expand Up @@ -861,7 +860,7 @@ extension _StringObject {
self.init(.strong(Builtin.reinterpretCast(_payloadBits)), bits)
#else
_sanityCheck(_payloadBits & ~_StringObject._payloadMask == 0)
var rawBits = _payloadBits & _StringObject._payloadMask
var rawBits = _payloadBits
if isValue {
var rawBitsBuiltin = Builtin.stringObjectOr_Int64(
rawBits._value, _StringObject._isValueBit._value)
Expand Down
Loading