A simple introduction

Swift optimizer: Add a new String Optimization #33128 Swift optimizer: Add a new String Optimization #33128

  • When the expressionx.append(y)When x of phi is empty, use the expressionx = yInstead of it.
  • removex.append("").
  • When the expressionx.append(y)The expression is used when both x and y of the string are always onx = x + yInstead.
  • If T is known statically, it is replaced by a constant string_typeName (T.s elf).

SIL analysis

Since this optimization is done by adding a SIL Pass, which is done at the SIL level, we need to take a quick look at some SIL instructions related to the String append.

For a simple example, create a String.swift file and add the following code:


func stringTest(a) -> String {
    var string = "Hello"
    string.append("Roy")
    return string
}

Copy the code

The code is simple: create a string and add another string to the end of the string via string.append.

swiftc -emit-sil String.swift > String.sil
Copy the code

Then use the above command to convert the above SWIFT source code into SIL code, which is not much.

// stringTest()
sil hidden @$s6String10stringTestSSyF : $@convention(thin) () -> @owned String {
bb0:
  %0 = alloc_stack $String, var, name "string"    // users: %7, %24, %23, %14, %19
  %1 = string_literal utf8 "Hello"                // user: %6
  %2 = integer_literal $Builtin.Word, 5           // user: %6
  %3 = integer_literal $Builtin.Int1, -1          // user: %6
  %4 = metatype $@thin String.Type                // user: %6
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %5 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %6
  %6 = apply %5(%1, %2, %3, %4) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %7
  store %6 to %0 : $*String                       // id: %7
  %8 = string_literal utf8 "Roy"                  // user: %13
  %9 = integer_literal $Builtin.Word, 3           // user: %13
  %10 = integer_literal $Builtin.Int1, -1         // user: %13
  %11 = metatype $@thin String.Type               // user: %13
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %12 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %13
  %13 = apply %12(%8, %9, %10, %11) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // users: %18, %16
  %14 = begin_access [modify] [static] %0 : $*String // users: %17, %16
  // function_ref String.append(_:)
  %15 = function_ref @$sSS6appendyySSF : $@convention(method) (@guaranteed String, @inout String) -> () // user: %16
  %16 = apply %15(%13, %14) : $@convention(method) (@guaranteed String, @inout String) -> ()
  end_access %14 : $*String                       // id: %17
  release_value %13 : $String                     // id: %18
  %19 = begin_access [read] [static] %0 : $*String // users: %20, %22
  %20 = load %19 : $*String                       // users: %25, %21
  retain_value %20 : $String                      // id: %21
  end_access %19 : $*String                       // id: %22
  destroy_addr %0 : $*String                      // id: %23
  dealloc_stack %0 : $*String                     // id: %24
  return %20 : $String                            // id: %25
} // end sil function '$s6String10stringTestSSyF'

// String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
sil [serialized] [always_inline] [readonly] [_semantics "string.makeUTF8"] @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String

// String.append(_:)
sil [_semantics "string.append"] @$sSS6appendyySSF : $@convention(method) (@guaranteed String, @inout String) -> ()

Copy the code

Let’s do a simple analysis.

  %0 = alloc_stack $String, var, name "string"    // users: %7, %24, %23, %14, %19
  %1 = string_literal utf8 "Hello"                // user: %6
  %2 = integer_literal $Builtin.Word, 5           // user: %6
  %3 = integer_literal $Builtin.Int1, -1          // user: %6
  %4 = metatype $@thin String.Type                // user: %6
Copy the code
  • alloc_stack TAllocates (uninitialized) memory on the stack to contain T and returns the address of the allocated memory.
  • $StringWe allocate memory to it as a String, and the type in SIL begins with $.
  • %1 = string_literal utf8 "Hello" Creates a reference to a string in the global string table. The result is a pointer to data. The referenced string always ends in a null value. String literal values are specified using Swift’s string literal syntax. The encoding is UTF8.
  • integer_literal $Builtin.Word, 5 Create an integer_literal of type builtin. Word with a value of 5. This is the size of the string that we’re going to allocate memory for, because the string “Hello” is of length 5.
  • integer_literal $Builtin.Int1, -1 Create an integer_literal of type builtin. Int1 with a value of -1. Bool in SIL is also builtin. Int1, which indicates whether it is ASCII.
  • metatype $T.TypeCreate a reference T to the metatype object of Type, where we get a reference String to the type. Note that this is the actual type, because it doesn’t have any placeholder types.
 // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %5 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %6
  %6 = apply %5(%1, %2, %3, %4) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %7
  store %6 to %0 : $*String                       // id: %7
Copy the code
  • Register %5 is a reference to a function calledString.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:), which is the initialization method of String. This method has four parameters, respectivelyBuiltin.RawPointer.Builtin.Word,.Builtin.Int1@thin String.TypeType. The return value is String.
  • applyIs a function call that calls %5 and passes in arguments %1, %2, %3, %4. The result is stored in register %6.
  • storeIs a memory access instruction that stores the value % 6 into memory at address % 0. % 0 is of type * String, which is a pointer to String, and % 6 is of type String, which will overwrite memory at % 0.
%8 = string_literal utf8 "Roy" // user: %13 .... %13 = apply %12(%8, %9, %10, %11) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // users: % % 18, 16Copy the code

This instruction, as described earlier, is intended to initialize the string “Roy”.

  %14 = begin_access [modify] [static] %0 : $*String // users: %17, %16
  // function_ref String.append(_:)
  %15 = function_ref @$sSS6appendyySSF : $@convention(method) (@guaranteed String, @inout String) -> () // user: %16
  %16 = apply %15(%13, %14) : $@convention(method) (@guaranteed String, @inout String) -> ()
  end_access %14 : $*String                       // id: %17
  release_value %13 : $String                     // id: %18
  %19 = begin_access [read] [static] %0 : $*String // users: %20, %22

Copy the code
  • begin_accessGets the memory access permission for %0, the permission is[modify] Modification.
  • %15 is a function reference, the function isString.append(_:), the append method of String. This method has four parameters, respectively@guaranteed String@inout String, has no return value.
  • applyThis is a function call that calls %15 and passes in arguments %13, %14. Where %13 is “Roy”, %14 is “Hello”

The two parameters need to be explained here are the @guaranteed and @inout types and the begin_access directive.

@guaranteed

The SIL Ownership model allows static life cycle invariants to be expressed and enforced by the SIL IR along the SSA edge. Is a derivative of SSA, used to express ownership invariants along the def-use edge.

ValueOwnershipKind has three kinds, one of which is Guaranteed. A value with @guaranteed ownership is an immutable value with a scope of life cycle, which is valued by @owned. The life of an @guaranteed value is a constraint on the life of an @ Owned value and statically prevents the @ Owned value from being corrupted until the life of an @guaranteed value is over.

You can see from SIL that the function in %13 returns @owned String, which matches the description above.

@ inout parameters

The following is explained in master/docs/ sil.rst #inout-arguments

The @inout argument is passed to the function entry point by address. The called party does not own the referenced memory. The referenced memory must be initialized when the function enters and exits. If the @inout variable refers to a fragile physical variable, the argument is the address of that variable. If the @inout variable refers to a logical property, the argument is the address of the write-back buffer that the caller has. It is the responsibility of the caller to initialize the buffer by storing the result of the property getter before calling the function, and to write the property back on return by loading it from the buffer and calling the setter with the final value.

Func inout (_ x: inout Int) {x = 1}Copy the code

This method, for example, allows you to change the values of the parameters passed inside the method.

begin_access

Begin_access and END_access are memory access instructions, begin_access begins memory access, end_access ends memory access, and access must end uniquely on each control flow path.

StringOptimizationPass

The StringOptimizationPass class inherits from the SILFunctionTransform and is a SIL Pass.

/// The StringOptimization function pass. class StringOptimizationPass : public SILFunctionTransform { public: void run() override { SILFunction *F = getFunction(); if (! F->shouldOptimize()) return; LLVM_DEBUG(llvm::dbgs() << "*** StringOptimization on function: " << F->getName() << " ***\n"); StringOptimization stringOptimization; bool changed = stringOptimization.run(F); if (changed) { invalidateAnalysis(SILAnalysis::InvalidationKind::CallsAndInstructions); }}};Copy the code

Void Run () is the entry method. There’s not much to say about this method. Get the SILFunction object with getFunction(), exit if you don’t need optimization, and enter the Run method of the StringOptimization class. The argument is a SILFunction object.

StringOptimization

run

Bool StringOptimization::run(SILFunction *F) {// NominalTypeDecl *stringDecl = F->getModule().getastContext ().getStringDecl(); // return false if (! stringDecl) return false; stringType = SILType::getPrimitiveObjectType( CanType(stringDecl->getDeclaredType())); Bool changed = false; / / / traverse SILFunction SILBasicBlock, optimize the SILBasicBlock for (SILBasicBlock & block: * F) {changed | = optimizeBlock (block); } return changed; }Copy the code

optimizeBlock

/ / / optimize the basic block bool StringOptimization: : optimizeBlock (SILBasicBlock & block) {bool changed = false; /// a DenseMap Map table that identifies objects (alloc_stack, Inout argument) maps to string values LLVM ::DenseMap<SILValue, SILValue> storedStrings stored in these objects; For (auto iter = block.begin(); for (auto iter = block.begin(); iter ! = block.end();) { SILInstruction *inst = &*iter++; / / / find StoreInst if (StoreInst * store = isStringStoreToIdentifyableObject (inst.)) {/ / / storedStrings storage store StoredStrings [store->getDest()] = store->getSrc(); continue; } // find the string. Append for apply Instruction, If (ApplyInst *append = isSemanticCall(inst, semantics::STRING_APPEND, Append if (optimizeStringAppend(Append, storedStrings)) {changed = true; continue; } // find the typeName for apply instruction, If (ApplyInst *typeName = isSemanticCall(inst, semantics:: typeName, 2)) { if (optimizeTypeName(typeName)) { changed = true; continue; }} // If inst overwrites (or may overwrite) a String stored in a recognizable object, the item is removed from storedStrings. invalidateModifiedObjects(inst, storedStrings); } return changed; }Copy the code
StoreInst *store = isStringStoreToIdentifyableObject(inst)

Copy the code

Store %6 to %0: $*String

storedStrings[store->getDest()] = store->getSrc();

Copy the code
enum {
    /// the value being stored
    Src,
    /// the lvalue being stored to
    Dest
  };

  SILValue getSrc() const { return Operands[Src].get(); }
  SILValue getDest() const { return Operands[Dest].get(); }
Copy the code

Store ->getDest() is the destination value of the store directive, which is %0. Store -> getSrc() is the value that the store directive needs to store, i.e. %6.

ApplyInst *append = isSemanticCall(inst, semantics::STRING_APPEND, 2)
Copy the code

Semantics: : STRING_APPEND is SEMANTICS_ATTR (STRING_APPEND, “string. Append”), in include/swift/AST/SemanticAttrs def, SIL is defined:

// String.append(_:)
sil [_semantics "string.append"] @$sSS6appendyySSF : $@convention(method) (@guaranteed String, @inout String) -> ()

Copy the code

So this appen is actually %16.

Semantics: : TYPENAME is the newly added, PR is SEMANTICS_ATTR (TYPENAME, “TYPENAME”), are also in include/swift/AST/SemanticAttrs def. It’s one of the optimizations that I started with, but I won’t go into detail.

isStringStoreToIdentifyableObject

StoreInst *StringOptimization:: isStringStoreToIdentifyableObject(SILInstruction *inst) { auto *store = dyn_cast<StoreInst>(inst); // if (! store) return nullptr; If (store->getSrc()->getType()! = stringType) return nullptr; SILValue destAddr = store->getDest(); /// We only deal with the indirect function arguments of alloc_stack. You can ensure that all users do not have aliases simply by checking them. AllocStackInst if (! isa<AllocStackInst>(destAddr) && ! isExclusiveArgument(destAddr)) return nullptr; // If there is a cache, return it directly. If not return after the cache if (identifyableObjectsCache. Count (destAddr)! = 0) { return identifyableObjectsCache[destAddr] ? store : nullptr; } /// Check whether it is an object of the identity yable. This is a case that only has users: Stores and applies in a simple way that we can trace. for (Operand *use : destAddr->getUses()) { SILInstruction *user = use->getUser(); switch (user->getKind()) { case SILInstructionKind::DebugValueAddrInst: case SILInstructionKind::DeallocStackInst: case SILInstructionKind::LoadInst: break; default: if (! mayWriteToIdentifyableObject(inst)) { // We don't handle user. It is some instruction which may write to // destAddr or let destAddr "escape" (like an address projection). identifyableObjectsCache[destAddr] = false; return nullptr; } break; } } identifyableObjectsCache[destAddr] = true; return store; }Copy the code
store->getSrc()->getType() ! = stringTypeCopy the code

Store needs to store a String of type, i.e. %6, initialized by string.init.

isa<AllocStackInst>(destAddr)
Copy the code

%0 = alloc_STACK $String, var, name “String”.

isSemanticCall

If \ pinst is a call to a function with the semantic attribute \ prinr and exactly \ p numArgs parameters, the apply directive is returned.

ApplyInst *StringOptimization::isSemanticCall(SILInstruction *inst, StringRef attr, unsigned numArgs) { auto apply = dyn_cast<ApplyInst>(inst); if (! apply || apply->getNumArguments() ! = numArgs) return nullptr; SILFunction *callee = apply->getReferencedFunctionOrNull(); if (callee && callee->hasSemanticsAttr(attr)) return apply; return nullptr; }Copy the code

optimizeStringAppend

Optimize the String. Appends mentioned earlier

bool StringOptimization::optimizeStringAppend(ApplyInst *appendCall, llvm::DenseMap<SILValue, SILValue RHS = appendCall->getArgument(0); StringInfo rhsString = getStringInfo(RHS); // If RHS is empty in lhs.append(RHS), remove appendCall. Is the second case that needs to be optimized. if (rhsString.isEmpty()) { appendCall->eraseFromParent(); return true; SILValue lhsAddr = appendCall->getArgument(1); StringInfo lhsString = getStringInfo(storedStrings[lhsAddr]); // The following two optimizations are a trade-off: Performance-wise it may be // benefitial to initialize an empty string with reserved capacity and then // append multiple other string components. // Removing the empty string (with the reserved capacity) might result in more // allocations. // So we just do this optimization up to a certain capacity limit (found by // experiment). if (lhsString.reservedCapacity > 50) return false; // If LHS is empty in lhs.append(RHS), use 'LHS = RHS' instead. Is the first case to be optimized. If (lhsString.isEmpty()) {replaceAppendWith(appendCall, RHS, /*copyNewValue*/ true); storedStrings[lhsAddr] = rhs; return true; } // If LHS and RHS are constant strings in lhs.append(RHS), If (lhsString.isconstant () &&rhsString.isconstant ()) {STD ::string concat = lhsString.str; /// Add strings concat += rhsString. STR; If (ApplyInst *stringInit = createStringInit(concat, appendCall)) {// Replace String. Append with String call. Return true replaceAppendWith(appendCall, stringInit, /*copyNewValue*/ false); storedStrings[lhsAddr] = stringInit; return true; } } return false; }Copy the code

ApplyInst *appendCall is 16% of the SIL section.

// function_ref String.append(_:)
  %15 = function_ref @$sSS6appendyySSF : $@convention(method) (@guaranteed String, @inout String) -> () // user: %16
  %16 = apply %15(%13, %14) : $@convention(method) (@guaranteed String, @inout String) -> ()
Copy the code

It has two parameters, namely 13% and 14%. 13% is the “Roy” address and 14% is the “Hello” address %0.

  SILValue rhs = appendCall->getArgument(0);
  StringInfo rhsString = getStringInfo(rhs);
   if (rhsString.isEmpty()) {
    appendCall->eraseFromParent();
    return true;
  }
Copy the code

RHS is 13%, which is also the address of “Roy”, rhsString is the string details of “Roy”, if this string is empty remove appendCall. Remove x. apend (“”).

  SILValue lhsAddr = appendCall->getArgument(1);
  StringInfo lhsString = getStringInfo(storedStrings[lhsAddr]);
Copy the code

LhsAddr is %14, which is also the address of %0.

store %6 to %0 : $*String  
Copy the code

When combined with the store directive, storedStrings[lhsAddr] is %6, which is the “Hello” address, lhsString”Hello” string details.

replaceAppendWith

/// Replace a String.append() with a store of \p newValue to the destination. void StringOptimization::replaceAppendWith(ApplyInst *appendCall, SILValue newValue, bool copyNewValue) { SILBuilder builder(appendCall); AppendCall SILLocation SILLocation loc = appendCall->getLoc(); SILValue destAddr = appendCall->getArgument(1); if (appendCall->getFunction()->hasOwnership()) { if (copyNewValue) newValue = builder.createCopyValue(loc, newValue); builder.createStore(loc, newValue, destAddr, StoreOwnershipQualifier::Assign); } else { if (copyNewValue) builder.createRetainValue(loc, newValue, builder.getDefaultAtomicity()); builder.createDestroyAddr(loc, destAddr); builder.createStore(loc, newValue, destAddr, StoreOwnershipQualifier::Unqualified); } appendCall->eraseFromParent(); }Copy the code

Replace instructions are built through SILBuilder

appendCall->getFunction()->hasOwnership()

Copy the code
/// Returns true if this function has qualified ownership instructions in it.
  bool hasOwnership() const { return HasOwnership; }

Copy the code

Returns true if appendCall has ownership. The first parameter here is @guaranteed, which belongs to the Ownership instruction.

builder.createDestroyAddr(loc, destAddr);
Copy the code

The purpose is to create the Destroy_ADDR directive. Because there is @guaranteed, the original SIL has created a destroy_ADDR to release the memory address pointed to by %0, so there is no need to repeat the creation.

destroy_addr %0 : $*String                      // id: %23
Copy the code
builder.createStore(loc, newValue, destAddr,
                            StoreOwnershipQualifier::Assign);
Copy the code

Create a store directive to store newValue to destAddr. StoreOwnershipQualifier is Assign.

createStringInit

Creates a string initializer function call instruction

/// Creates a call to a string initializer. ApplyInst *StringOptimization::createStringInit(StringRef str, SILInstruction *beforeInst) { SILBuilder builder(beforeInst); SILLocation loc = beforeInst->getLoc(); SILModule &module = beforeInst->getFunction()->getModule(); ASTContext &ctxt = module.getASTContext(); if (! makeUTF8Func) { // Find the String initializer which takes a string_literal as argument. ConstructorDecl *makeUTF8Decl =  ctxt.getMakeUTF8StringDecl(); if (! makeUTF8Decl) return nullptr; auto Mangled = SILDeclRef(makeUTF8Decl, SILDeclRef::Kind::Allocator).mangle(); makeUTF8Func = module.findFunction(Mangled, SILLinkage::PublicExternal); if (! makeUTF8Func) return nullptr; } auto *literal = builder.createStringLiteral(loc, str, StringLiteralInst::Encoding::UTF8); auto *length = builder.createIntegerLiteral(loc, SILType::getBuiltinWordType(ctxt), literal->getCodeUnitCount()); auto *isAscii = builder.createIntegerLiteral(loc, SILType::getBuiltinIntegerType(1, ctxt), intmax_t(ctxt.isASCIIString(str))); SILType stringMetaType = SILType::getPrimitiveObjectType( CanType(MetatypeType::get(stringType.getASTType(), MetatypeRepresentation::Thin))); auto *metaTypeInst = builder.createMetatype(loc, stringMetaType); auto *functionRef = builder.createFunctionRefFor(loc, makeUTF8Func); return builder.createApply(loc, functionRef, SubstitutionMap(), { literal, length, isAscii, metaTypeInst }); }Copy the code

This will not be discussed in detail, combined with the SIL two create string initialization call instructions can be seen. They are %1 to %6 and %8 to %13 respectively.

getStringInfo

Returns string details if it is a constant string

/// Returns information about value if it's a constant string. StringOptimization::StringInfo StringOptimization::getStringInfo(SILValue value) { // Start with a non-constant result. StringInfo result; auto *apply = dyn_cast_or_null<ApplyInst>(value); if (! apply) return result; SILFunction *callee = apply->getReferencedFunctionOrNull(); if (! callee) return result; // For initializing an empty string, set result.numCodeUnits = 0; if (callee->hasSemanticsAttr(semantics::STRING_INIT_EMPTY)) { result.numCodeUnits = 0; return result; } // If the null-size string is initialized and the capacity size is set, result.numCodeUnits = 0; if (callee->hasSemanticsAttr(semantics::STRING_INIT_EMPTY_WITH_CAPACITY)) { result.numCodeUnits = 0; result.reservedCapacity = std::numeric_limits<int>::max(); if (apply->getNumArguments() > 0) { if (Optional<int> capacity = getIntConstant(apply->getArgument(0))) // Result.reservedcapacity specifies the initial capacity result.reservedCapacity = capacity.getValue(); result.reservedCapacity = capacity. } return result; } string literal initializer if (callee->hasSemanticsAttr(Semantics ::STRING_MAKE_UTF8)) {SILValue stringVal = apply->getArgument(0); auto *stringLiteral = dyn_cast<StringLiteralInst>(stringVal); SILValue lengthVal = apply->getArgument(1); auto *intLiteral = dyn_cast<IntegerLiteralInst>(lengthVal); if (intLiteral && stringLiteral && // For simplicity, we only support UTF8 string literals. stringLiteral->getEncoding() == StringLiteralInst::Encoding::UTF8) { result.str = stringLiteral->getValue(); result.numCodeUnits = intLiteral->getValue().getSExtValue(); return result; } } return result; }Copy the code

Semantics ::STRING_INIT_EMPTY is SEMANTICS_ATTR(STRING_INIT_EMPTY, “string.init_empty”), In include/swift/AST/SemanticAttrs. Def.

Semantics: : STRING_INIT_EMPTY_WITH_CAPACITY is SEMANTICS_ATTR (STRING_INIT_EMPTY_WITH_CAPACITY, “String. Init_empty_with_capacity”), in include/swift/AST/SemanticAttrs def. An initialization method that creates an empty string but sets the capacity size.

Semantics ::STRING_MAKE_UTF8 is a string literal alizer. SIL is as follows:

// String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:) sil [serialized] [always_inline] [readonly] [_semantics  "string.makeUTF8"] @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned StringCopy the code

The apply directive is as follows:

 %6 = apply %5(%1, %2, %3, %4) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %7
Copy the code

SILValue stringVal = apply->getArgument(0); The argument 0 is %1, which is IntegerLiteralInst. SILValue lengthVal = apply->getArgument(1); , the argument 1 is %2 and the string length is 5. result.str = stringLiteral->getValue(); Get the string, which is “Hello”.

ASTContext.cpp

Astcontext.cpp added a getMakeUTF8StringDecl() method to getMakeUTF8StringDec, which is intended to be used in the createStringInit method to manually create a String initialization call.

ConstructorDecl *ASTContext::getMakeUTF8StringDecl() const { if (getImpl().MakeUTF8StringDecl) return getImpl().MakeUTF8StringDecl; / / get the initialization auto initializers = getStringDecl () - > lookupDirect (DeclBaseName: : createConstructor ()); for (Decl *initializer : initializers) { auto *constructor = cast<ConstructorDecl>(initializer); auto Attrs = constructor->getAttrs(); for (auto *A : Attrs.getAttributes<SemanticsAttr, false>()) { if (A->Value ! = semantics::STRING_MAKE_UTF8) continue; auto ParamList = constructor->getParameters(); if (ParamList->size() ! = 3) continue; ParamDecl *param = constructor->getParameters()->get(0); if (param->getArgumentName().str() ! = "_builtinStringLiteral") continue; getImpl().MakeUTF8StringDecl = constructor; return constructor; } } return nullptr; }Copy the code

Semantics: : STRING_MAKE_UTF8 is SEMANTICS_ATTR (STRING_MAKE_UTF8, “string. MakeUTF8”), in include/swift/AST/SemanticAttrs def. The SIL code is as follows:

// String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:) sil [serialized] [always_inline] [readonly] [_semantics  "string.makeUTF8"] @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned StringCopy the code
ParamList->size() ! = 3Copy the code

Check whether the argument is 3, and from SIL, it is.

ParamDecl *param = constructor->getParameters()->get(0); if (param->getArgumentName().str() ! = "_builtinStringLiteral")Copy the code

Get the parameter name of parameter 0 from the parameter list and check if it is “_builtinStringLiteral”, also from SIL.

Add Pass to PassManager

Finally in include/swift/SILOptimizer/PassManager/Passes. Add Pass def

PASS(StringOptimization, "string-optimization",
     "Optimization for String operations")
Copy the code