#include "partition.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/Twine.h" #include "llvm/Support/ThreadPool.h" #include "llvm/Support/Threading.h" #include #include #include using namespace llvm; namespace scriptc { namespace { constexpr unsigned NoPartition = ~0u; enum class SymbolKind : uint8_t { Function, Variable, Declaration }; struct Symbol { SymbolKind Kind = SymbolKind::Declaration; bool Local = false; StringRef Name; // Definition header line, or the complete declaration line. StringRef Header; // Function lines after the header, through the closing brace. StringRef Body; // Declaration other partitions use for this definition. std::string Declaration; // Local definitions are rewritten with hidden visibility. std::string PromotedHeader; std::vector Refs; // The functions its body calls directly (sorted). std::vector Calls; // A variable defined with "constant", whose loads the optimizer can fold. bool Constant = false; std::vector Metadata; // The source-module group the emitter wrote before this definition, or // empty. StringRef Group; unsigned Partition = NoPartition; }; struct MetadataNode { StringRef Line; std::vector Refs; std::vector Symbols; }; bool isNameChar(char C) { return (C >= 'a' && C <= 'z') || (C >= 'A' && C <= 'Z') || (C >= '0' && C <= '9') || C == '-' || C == '$' || C == '.' || C == '_'; } bool isDigit(char C) { return C >= '0' && C <= '9'; } // Length of the unquoted symbol name starting at Text[Start], or 0. size_t nameLength(StringRef Text, size_t Start) { size_t End = Start; while (End < Text.size() && isNameChar(Text[End])) ++End; return End - Start; } uint32_t stableHash(StringRef Name) { uint32_t Hash = 0x811c9dc5u; for (char C : Name) { Hash ^= static_cast(C); Hash *= 0x01000193u; } return Hash; } constexpr unsigned MinGroupedPartitions = 32; // Places the functions whose root carries a source-module group by group: a // module's functions call each other most, so keeping them together leaves // fewer calls for the thin link to import across partitions. A group larger // than the mean partition, or than MaxChunkBytes, is divided into a power of // two of chunks by the hash of each root's name, so the division changes only // when the group's size doubles. Chunks are placed largest first, each in the // lighter of two partitions chosen by hashes of its key, after the functions // placed by name. The placement depends only on the module text and the // partition count. Modules with fewer than MinGroupedPartitions partitions // keep the placement by name, which balanceRoots evens out: with few // partitions per core, the balance of the largest partitions decides the // build time more than the imports. void placeGroups(std::vector &Symbols, ArrayRef Roots, unsigned Count) { if (Count < MinGroupedPartitions) return; auto Bytes = [](const Symbol &S) -> uint64_t { return S.Header.size() + S.Body.size(); }; std::vector Load(Count, 0); uint64_t Total = 0; StringMap GroupBytes; for (unsigned I = 0; I < Symbols.size(); ++I) { const Symbol &S = Symbols[I]; if (S.Kind != SymbolKind::Function || S.Partition == NoPartition) continue; Total += Bytes(S); StringRef Group = Symbols[Roots[I]].Group; if (Group.empty()) Load[S.Partition] += Bytes(S); else GroupBytes[Group] += Bytes(S); } if (GroupBytes.empty()) return; // A chunk is at most MaxChunkBytes, so the code a partition optimizes // together, and the inlining within it, does not grow when a program has // fewer partitions. constexpr uint64_t MaxChunkBytes = 1024 * 1024; uint64_t Chunk = std::min(MaxChunkBytes, std::max(1, Total / Count)); std::vector Keys(Symbols.size()); StringMap KeyBytes; for (unsigned I = 0; I < Symbols.size(); ++I) { const Symbol &S = Symbols[I]; if (S.Kind != SymbolKind::Function || S.Partition == NoPartition) continue; StringRef Group = Symbols[Roots[I]].Group; if (Group.empty()) continue; uint64_t Chunks = 1; while (Chunks * Chunk < GroupBytes[Group]) Chunks *= 2; Keys[I] = Chunks == 1 ? Group.str() : (Group + "#" + Twine(stableHash(Symbols[Roots[I]].Name) % Chunks)) .str(); KeyBytes[Keys[I]] += Bytes(S); } std::vector> Order; for (const auto &Entry : KeyBytes) Order.push_back({Entry.getKey(), Entry.getValue()}); llvm::sort(Order, [](const auto &A, const auto &B) { return A.second != B.second ? A.second > B.second : A.first < B.first; }); StringMap Where; for (const auto &[Key, Size] : Order) { unsigned First = stableHash(Key) % Count; unsigned Second = stableHash((Key + "/2").str()) % Count; unsigned Chosen = Load[Second] < Load[First] ? Second : First; Load[Chosen] += Size; Where[Key] = Chosen; } for (unsigned I = 0; I < Symbols.size(); ++I) if (!Keys[I].empty()) Symbols[I].Partition = Where[Keys[I]]; } // In modules of fewer than MinGroupedPartitions partitions, which // placeGroups leaves placed by name, the functions of each root (a // function and the chain that follows it) move together to another partition // when their name's partition would hold more than 1.25 times the mean // function bytes: each root probes the next partitions in turn, keeping the // first with room, else its own. An edit then recompiles about a mean // partition, and the largest partition, which bounds a cold build, stays // near the mean unless one root is larger. Roots and their bytes, and so // placement, depend only on the module text. void balanceRoots(std::vector &Symbols, ArrayRef Roots, ArrayRef Live, unsigned Count) { if (Count >= MinGroupedPartitions) return; DenseMap RootBytes; uint64_t Total = 0; for (unsigned I = 0; I < Symbols.size(); ++I) if (Symbols[I].Kind == SymbolKind::Function && Live[I]) RootBytes[Roots[I]] += Symbols[I].Header.size() + Symbols[I].Body.size(); // Each root counts in whole 4 KiB units, so that an edit that grows a root // by less than that leaves every comparison, and every other root's // partition, unchanged. constexpr uint64_t Unit = 4096; for (auto &Entry : RootBytes) { Entry.second = (Entry.second + Unit - 1) / Unit * Unit; Total += Entry.second; } std::vector Order; for (auto &Entry : RootBytes) Order.push_back(Entry.first); llvm::sort(Order, [&](unsigned A, unsigned B) { return Symbols[A].Name < Symbols[B].Name; }); uint64_t Cap = Total * 5 / 4 / Count; std::vector Load(Count, 0); DenseMap Placed; // Roots of more than half the cap are placed first, in name order, so // that small roots fill around them; the others follow in name order. auto Place = [&](unsigned Root) { uint64_t Bytes = RootBytes[Root]; unsigned Home = stableHash(Symbols[Root].Name) % Count, Chosen = Home; for (unsigned K = 0; K < Count; ++K) { unsigned P = (Home + K) % Count; if (Load[P] + Bytes <= Cap) { Chosen = P; break; } } Load[Chosen] += Bytes; Placed[Root] = Chosen; }; for (unsigned Root : Order) if (RootBytes[Root] * 2 > Cap) Place(Root); for (unsigned Root : Order) if (RootBytes[Root] * 2 <= Cap) Place(Root); for (unsigned I = 0; I < Symbols.size(); ++I) if (Symbols[I].Kind == SymbolKind::Function && Live[I]) Symbols[I].Partition = Placed[Roots[I]]; } bool startsWithLinkage(StringRef Rest) { for (StringRef Linkage : {"linkonce", "weak", "common", "appending", "extern_weak", "available_externally", "external"}) if (Rest.starts_with(Linkage)) return true; return false; } bool hasVisibilityToken(StringRef Prefix) { SmallVector Tokens; Prefix.split(Tokens, ' ', -1, false); for (StringRef Token : Tokens) if (Token == "dso_local" || Token == "dso_preemptable" || Token == "hidden" || Token == "protected" || Token == "default" || Token == "dllimport" || Token == "dllexport") return true; return false; } // Position of the first unquoted occurrence of Wanted, or npos. size_t findUnquoted(StringRef Text, char Wanted) { bool Quoted = false; for (size_t I = 0; I < Text.size(); ++I) { if (Text[I] == '"') Quoted = !Quoted; else if (!Quoted && Text[I] == Wanted) return I; } return StringRef::npos; } bool parseFunctionHeader(StringRef Line, Symbol &S) { StringRef Rest = Line.drop_front(sizeof("define ") - 1); if (Rest.consume_front("internal ") || Rest.consume_front("private ")) S.Local = true; else if (startsWithLinkage(Rest)) return false; size_t At = Rest.find('@'); if (At == StringRef::npos) return false; StringRef Prefix = Rest.take_front(At); if (S.Local && hasVisibilityToken(Prefix)) return false; size_t Length = nameLength(Rest, At + 1); size_t Open = At + 1 + Length; if (Length == 0 || Open >= Rest.size() || Rest[Open] != '(') return false; S.Name = Rest.substr(At + 1, Length); size_t Depth = 0, Close = StringRef::npos; bool Quoted = false; for (size_t I = Open; I < Rest.size(); ++I) { char C = Rest[I]; if (C == '"') Quoted = !Quoted; if (Quoted) continue; if (C == '(') ++Depth; else if (C == ')' && --Depth == 0) { Close = I; break; } } if (Close == StringRef::npos) return false; StringRef Tail = Rest.drop_front(Close + 1); size_t Brace = findUnquoted(Tail, '{'); if (Brace == StringRef::npos) return false; StringRef After = Tail.drop_front(Brace + 1).ltrim(); if (!After.empty() && After.front() != ';') return false; S.Declaration = "declare "; if (S.Local) S.Declaration += "hidden "; S.Declaration += Prefix; S.Declaration += '@'; S.Declaration += S.Name; S.Declaration += Rest.slice(Open, Close + 1); // Attribute groups and address significance carry over. Personality, // sections, comdats and debug attachments belong only to the definition. SmallVector Tokens; Tail.take_front(Brace).split(Tokens, ' ', -1, false); for (StringRef Token : Tokens) { bool Group = Token.size() > 1 && Token.front() == '#' && all_of(Token.drop_front(), isDigit); if (Group || Token == "unnamed_addr" || Token == "local_unnamed_addr") { S.Declaration += ' '; S.Declaration += Token; } } if (S.Local) S.PromotedHeader = (Twine("define hidden ") + Rest).str(); return true; } // Parses `@name = ...`. Returns false for unsupported forms. bool parseGlobal(StringRef Line, Symbol &S) { size_t Length = nameLength(Line, 1); if (Length == 0) return false; S.Name = Line.substr(1, Length); StringRef Rest = Line.drop_front(1 + Length); if (!Rest.consume_front(" = ")) return false; if (Rest.starts_with("external ") || Rest.starts_with("extern_weak ")) { S.Kind = SymbolKind::Declaration; return true; } S.Kind = SymbolKind::Variable; if (Rest.consume_front("internal ") || Rest.consume_front("private ")) S.Local = true; else if (startsWithLinkage(Rest)) return false; StringRef Cursor = Rest; std::string Qualifiers; StringRef Keyword; while (true) { if (Cursor.consume_front("global ")) { Keyword = "global"; break; } if (Cursor.consume_front("constant ")) { Keyword = "constant"; S.Constant = true; break; } size_t Space = Cursor.find(' '); if (Space == StringRef::npos) return false; StringRef Token = Cursor.take_front(Space); if (Token == "dso_local" || Token == "hidden" || Token == "protected" || Token == "default") { if (S.Local) return false; Qualifiers += Token; Qualifiers += ' '; } else if (Token.starts_with("thread_local") || Token.starts_with("addrspace(")) { Qualifiers += Token; Qualifiers += ' '; } else if (Token != "unnamed_addr" && Token != "local_unnamed_addr" && Token != "externally_initialized") { return false; } Cursor = Cursor.drop_front(Space + 1); } // The value type ends at the first space outside brackets. int Depth = 0; size_t TypeEnd = StringRef::npos; for (size_t I = 0; I < Cursor.size(); ++I) { char C = Cursor[I]; if (C == '{' || C == '[' || C == '<' || C == '(') ++Depth; else if (C == '}' || C == ']' || C == '>' || C == ')') --Depth; else if (C == ' ' && Depth == 0) { TypeEnd = I; break; } } if (TypeEnd == StringRef::npos) return false; S.Declaration = (Twine("@") + S.Name + " = external " + (S.Local ? "hidden " : "") + Qualifiers + Keyword + " " + Cursor.take_front(TypeEnd)) .str(); if (S.Local) S.PromotedHeader = (Twine("@") + S.Name + " = hidden " + Rest).str(); return true; } // Collects the symbols (sorted) and metadata nodes (in order of first // appearance) that Text references outside quoted strings, and in Calls the // symbols it names as direct callees, which the emitter writes followed by // "(". void scanReferences(StringRef Text, const StringMap &Index, std::vector &Refs, std::vector &Metadata, std::vector *Calls = nullptr) { DenseSet Seen(Metadata.begin(), Metadata.end()); bool Quoted = false; for (size_t I = 0; I < Text.size(); ++I) { char C = Text[I]; if (C == '\n') { Quoted = false; } else if (C == '"') { Quoted = !Quoted; } else if (Quoted) { continue; } else if (C == '@') { size_t Length = nameLength(Text, I + 1); if (Length != 0) { auto It = Index.find(Text.substr(I + 1, Length)); if (It != Index.end()) { Refs.push_back(It->second); if (Calls && I + 1 + Length < Text.size() && Text[I + 1 + Length] == '(') Calls->push_back(It->second); } I += Length; } } else if (C == '!' && I + 1 < Text.size() && isDigit(Text[I + 1])) { unsigned Number = 0; size_t J = I + 1; for (; J < Text.size() && isDigit(Text[J]); ++J) Number = Number * 10 + static_cast(Text[J] - '0'); if (Seen.insert(Number).second) Metadata.push_back(Number); I = J - 1; } } llvm::sort(Refs); Refs.erase(std::unique(Refs.begin(), Refs.end()), Refs.end()); if (Calls) { llvm::sort(*Calls); Calls->erase(std::unique(Calls->begin(), Calls->end()), Calls->end()); } } // Appends Text with every unquoted metadata reference renumbered by Map. void appendRenumbered(StringRef Text, const DenseMap &Map, std::string &Out) { bool Quoted = false; size_t Copied = 0; for (size_t I = 0; I < Text.size(); ++I) { char C = Text[I]; if (C == '\n') { Quoted = false; } else if (C == '"') { Quoted = !Quoted; } else if (!Quoted && C == '!' && I + 1 < Text.size() && isDigit(Text[I + 1])) { unsigned Number = 0; size_t J = I + 1; for (; J < Text.size() && isDigit(Text[J]); ++J) Number = Number * 10 + static_cast(Text[J] - '0'); auto It = Map.find(Number); if (It != Map.end()) { Out.append(Text.data() + Copied, I + 1 - Copied); Out += std::to_string(It->second); Copied = J; } I = J - 1; } } Out.append(Text.data() + Copied, Text.size() - Copied); } } // namespace bool dominatedPartitions(ArrayRef FunctionBytes) { uint64_t Total = 0, Largest = 0; for (uint64_t Bytes : FunctionBytes) { Total += Bytes; Largest = std::max(Largest, Bytes); } return Largest * 3 > Total * 2; } std::optional partitionProgramText(StringRef Text, unsigned Count, bool SkipDominated) { if (Count < 2) return std::nullopt; std::vector Header, Types, Attributes, NamedMetadata; std::vector CompileUnits; DenseMap Metadata; std::vector Symbols; StringMap Index; auto Add = [&](Symbol S) { if (!Index.try_emplace(S.Name, Symbols.size()).second) return false; Symbols.push_back(std::move(S)); return true; }; // The emitter writes "; scriptc-group " before the definitions of each // source function; the group applies until the next such line, and an // empty key ends it. constexpr StringRef GroupLine = "; scriptc-group"; StringRef Group; size_t Pos = 0; while (Pos < Text.size()) { size_t End = Text.find('\n', Pos); if (End == StringRef::npos) End = Text.size(); StringRef Line = Text.slice(Pos, End); size_t Next = End + 1; if (Line.empty() || Line.trim().empty()) { // Blank separator. } else if (Line.starts_with(GroupLine) && (Line.size() == GroupLine.size() || Line[GroupLine.size()] == ' ')) { Group = Line.drop_front(GroupLine.size()).trim(); } else if (Line.starts_with("define ")) { Symbol S; S.Kind = SymbolKind::Function; S.Header = Line; S.Group = Group; if (!parseFunctionHeader(Line, S)) return std::nullopt; // The emitter indents every body line, so the first line that is // exactly "}" closes the function. size_t Close = End; while (true) { Close = Text.find("\n}", Close); if (Close == StringRef::npos) return std::nullopt; if (Close + 2 == Text.size() || Text[Close + 2] == '\n') break; Close += 2; } S.Body = Text.slice(std::min(End + 1, Text.size()), Close + 2); Next = Close + 3; if (!Add(std::move(S))) return std::nullopt; } else if (Line.front() == '@') { Symbol S; S.Header = Line; if (!parseGlobal(Line, S)) return std::nullopt; // The emitter writes an interface dispatch table one element per // line: the definition opens its aggregate initializer at the end of // the line, and a line that is only the closing bracket ends it. StringRef Opening = Line.rtrim(); if (S.Kind == SymbolKind::Variable && (Opening.ends_with(" [") || Opening.ends_with(" {"))) { StringRef Close = Opening.back() == '[' ? "]" : "}"; size_t Cursor = Next; while (true) { if (Cursor >= Text.size()) return std::nullopt; size_t LineEnd = Text.find('\n', Cursor); if (LineEnd == StringRef::npos) LineEnd = Text.size(); StringRef Element = Text.slice(Cursor, LineEnd); if (Element == Close) { S.Body = Text.slice(Next, LineEnd); Next = LineEnd + 1; break; } if (!Element.starts_with(" ")) return std::nullopt; Cursor = LineEnd + 1; } } if (!Add(std::move(S))) return std::nullopt; } else if (Line.starts_with("declare ")) { Symbol S; S.Header = Line; size_t At = Line.find('@'); size_t Length = At == StringRef::npos ? 0 : nameLength(Line, At + 1); if (Length == 0) return std::nullopt; S.Name = Line.substr(At + 1, Length); if (!Add(std::move(S))) return std::nullopt; } else if (Line.front() == '%') { if (Line.find(" = type ") == StringRef::npos) return std::nullopt; Types.push_back(Line); } else if (Line.starts_with("attributes #")) { Attributes.push_back(Line); } else if (Line.front() == '!') { if (Line.size() > 1 && isDigit(Line[1])) { unsigned Number = 0; size_t I = 1; for (; I < Line.size() && isDigit(Line[I]); ++I) Number = Number * 10 + static_cast(Line[I] - '0'); if (!Line.drop_front(I).starts_with(" = ")) return std::nullopt; if (!Metadata.try_emplace(Number, MetadataNode{Line, {}, {}}).second) return std::nullopt; } else if (Line.starts_with("!llvm.dbg.cu = !{")) { // Each partition lists only the compile units its functions use. std::vector Unused; scanReferences(Line, Index, Unused, CompileUnits); } else { NamedMetadata.push_back(Line); } } else if (Line.front() == ';' || Line.starts_with("source_filename ") || Line.starts_with("target ")) { Header.push_back(Line); } else { return std::nullopt; } Pos = Next; } for (Symbol &S : Symbols) { if (S.Kind == SymbolKind::Declaration) continue; scanReferences(S.Header, Index, S.Refs, S.Metadata); if (!S.Body.empty()) { std::vector Refs; scanReferences(S.Body, Index, Refs, S.Metadata, &S.Calls); S.Refs.insert(S.Refs.end(), Refs.begin(), Refs.end()); llvm::sort(S.Refs); S.Refs.erase(std::unique(S.Refs.begin(), S.Refs.end()), S.Refs.end()); } } // Metadata nodes are numbered in the emitter's global order, so one added // node would renumber the references in every partition. Each partition // instead numbers the nodes it uses in order of first use, which keeps an // unchanged partition's text, and therefore its cached artifacts, intact. for (auto &Entry : Metadata) { MetadataNode &Node = Entry.second; size_t Equals = Node.Line.find(" = "); scanReferences(Node.Line.drop_front(Equals + 3), Index, Node.Symbols, Node.Refs); } std::vector NamedRoots, NamedSymbols; for (StringRef Line : NamedMetadata) scanReferences(Line, Index, NamedSymbols, NamedRoots); // An internal definition that no other definition reaches is left out of // every partition: the optimizer would delete it, and the emitter writes // variants of helpers that the program never calls. Leaving them out // shortens every partition's parse, summary and optimization, and since // placement ignores them too, an edit that adds one changes no partition. std::vector Live(Symbols.size(), 0); { std::vector Work; auto Reach = [&](unsigned I) { if (!Live[I]) { Live[I] = 1; Work.push_back(I); } }; for (unsigned I = 0; I < Symbols.size(); ++I) if (Symbols[I].Kind != SymbolKind::Declaration && !Symbols[I].Local) Reach(I); for (unsigned I : NamedSymbols) Reach(I); for (auto &Entry : Metadata) for (unsigned I : Entry.second.Symbols) Reach(I); while (!Work.empty()) { unsigned I = Work.back(); Work.pop_back(); for (unsigned Ref : Symbols[I].Refs) Reach(Ref); } } // An internal definition reached from exactly one other definition is an // inlining candidate of it, or data only it uses; keep it with that // definition so its partition can inline it without importing, and so a // helper an edit adds lands in the partition of the code that uses it. A // function that its referrer only takes the address of is not an inlining // candidate: a closure body whose address its creator passes to the // runtime, or a compiled regular expression's matcher that a mutable // global points to, gives the referrer nothing to inline, and joining it // would make every edit of the referrer optimize it again. Such a function // is placed by its own name, unless its body is under AddressOnlyBytes (a // trampoline or thread entry, which costs little to optimize again). A // chain through a constant global, whose loads the optimizer can turn into // direct calls, continues to the function that uses the global. // A function body larger than this is past LLVM's inline cost limit even // for the only call to a local function, so it is placed by its own name. // A chain stops where it would leave a function's source-module group for // a function outside every group. In a module of more than // GroupedChainBytes of LLVM text, it also stops where it would join a // function of another group: module initializers, which only the entry // point calls, keep their own partitions instead of all sharing the entry // point's. In a smaller module, a function that one function of another // source file calls stays with it, so it can still be inlined there as in // the whole module; when that gathers most of the program into one // partition, the program is optimized whole (see emitPartitions). constexpr size_t MaxInlinedBodyBytes = 256 * 1024; constexpr size_t GroupedChainBytes = 1024 * 1024; constexpr size_t AddressOnlyBytes = 512; bool GroupedChains = Text.size() > GroupedChainBytes; constexpr unsigned Shared = ~1u; std::vector Referrer(Symbols.size(), NoPartition); // Whether a definition may follow its first referrer: it is not a // function, or the referrer calls it or is a constant global, or its body // is under AddressOnlyBytes. std::vector MayFollow(Symbols.size(), 0); for (unsigned I = 0; I < Symbols.size(); ++I) { const Symbol &S = Symbols[I]; if (S.Kind == SymbolKind::Declaration || !Live[I]) continue; for (unsigned Ref : S.Refs) { if (Ref == I) continue; if (Referrer[Ref] == NoPartition) { Referrer[Ref] = I; MayFollow[Ref] = Symbols[Ref].Kind != SymbolKind::Function || Symbols[Ref].Body.size() < AddressOnlyBytes || (S.Kind == SymbolKind::Function ? std::binary_search(S.Calls.begin(), S.Calls.end(), Ref) : S.Constant); } else { Referrer[Ref] = Shared; } } } auto Follows = [&](unsigned Child, unsigned Parent) { const Symbol &S = Symbols[Child]; if (!S.Local || !MayFollow[Child] || Parent == NoPartition || Parent == Shared || Symbols[Parent].Kind == SymbolKind::Declaration) return false; if (S.Kind == SymbolKind::Function && (S.Body.size() > MaxInlinedBodyBytes || (Symbols[Parent].Kind == SymbolKind::Function && !S.Group.empty() && S.Group != Symbols[Parent].Group && (GroupedChains || Symbols[Parent].Group.empty())))) return false; return true; }; std::vector Roots(Symbols.size()); for (unsigned I = 0; I < Symbols.size(); ++I) { if (Symbols[I].Kind == SymbolKind::Declaration || !Live[I]) continue; unsigned Root = I; for (unsigned Steps = 0; Steps < 1024; ++Steps) { unsigned Parent = Referrer[Root]; if (!Follows(Root, Parent) || Parent == I) break; Root = Parent; } // A function whose chain ends at a global that is not itself placed // with a function (a table many functions use) is placed by its own // name, so the functions of one table spread over the partitions. if (Symbols[I].Kind == SymbolKind::Function && Symbols[Root].Kind != SymbolKind::Function) Root = I; Roots[I] = Root; if (Symbols[I].Kind == SymbolKind::Function) Symbols[I].Partition = stableHash(Symbols[Root].Name) % Count; } placeGroups(Symbols, Roots, Count); balanceRoots(Symbols, Roots, Live, Count); // A global follows the function its chain ends at, then the first // function that references it, then its own name. for (unsigned I = 0; I < Symbols.size(); ++I) if (Symbols[I].Kind == SymbolKind::Variable && Live[I] && Symbols[Roots[I]].Kind == SymbolKind::Function) Symbols[I].Partition = Symbols[Roots[I]].Partition; for (unsigned I = 0; I < Symbols.size(); ++I) { const Symbol &S = Symbols[I]; if (S.Kind != SymbolKind::Function || !Live[I]) continue; for (unsigned Ref : S.Refs) if (Symbols[Ref].Kind == SymbolKind::Variable && Symbols[Ref].Partition == NoPartition) Symbols[Ref].Partition = S.Partition; } for (unsigned I = 0; I < Symbols.size(); ++I) if (Symbols[I].Kind == SymbolKind::Variable && Live[I] && Symbols[I].Partition == NoPartition) Symbols[I].Partition = stableHash(Symbols[I].Name) % Count; ProgramPartitions Result; for (const Symbol &S : Symbols) if (S.Kind != SymbolKind::Declaration && !S.Local) Result.PublicSymbols.insert(S.Name); Result.Sources.resize(Count); Result.FunctionBytes.assign(Count, 0); std::vector> Owned(Count); for (unsigned I = 0; I < Symbols.size(); ++I) if (Symbols[I].Kind != SymbolKind::Declaration && Live[I]) { Owned[Symbols[I].Partition].push_back(I); if (Symbols[I].Kind == SymbolKind::Function) Result.FunctionBytes[Symbols[I].Partition] += Symbols[I].Header.size() + Symbols[I].Body.size(); } // The caller optimizes such a module whole, so its partitions' text is // not needed. if (SkipDominated && dominatedPartitions(Result.FunctionBytes)) { Result.Sources.clear(); return Result; } // An internal definition stays internal, so its partition's optimizer can // inline a function into its last caller and delete it as the whole module // would. It is promoted to hidden visibility only when a definition of // another partition or a metadata node names it. std::vector Exported(Symbols.size(), 0); for (unsigned I : NamedSymbols) Exported[I] = 1; for (auto &Entry : Metadata) for (unsigned I : Entry.second.Symbols) Exported[I] = 1; for (unsigned I = 0; I < Symbols.size(); ++I) { const Symbol &S = Symbols[I]; if (S.Kind == SymbolKind::Declaration || !Live[I]) continue; for (unsigned Ref : S.Refs) if (Symbols[Ref].Kind != SymbolKind::Declaration && Symbols[Ref].Partition != S.Partition) Exported[Ref] = 1; } auto Assemble = [&](unsigned P) { std::vector Needed(Symbols.size(), 0); DenseMap Renumber; std::vector Nodes; auto Use = [&](ArrayRef Numbers) { for (unsigned Number : Numbers) if (Metadata.count(Number) && Renumber.try_emplace(Number, Nodes.size()).second) Nodes.push_back(Number); }; Use(NamedRoots); for (unsigned Ref : NamedSymbols) Needed[Ref] = 1; size_t Bytes = 0; for (SymbolKind Kind : {SymbolKind::Variable, SymbolKind::Function}) for (unsigned I : Owned[P]) if (Symbols[I].Kind == Kind) { const Symbol &S = Symbols[I]; for (unsigned Ref : S.Refs) Needed[Ref] = 1; Use(S.Metadata); Bytes += S.Header.size() + S.Body.size() + 2; } for (size_t K = 0; K < Nodes.size(); ++K) { const MetadataNode &Node = Metadata.find(Nodes[K])->second; Use(Node.Refs); for (unsigned Ref : Node.Symbols) Needed[Ref] = 1; } std::string &Out = Result.Sources[P]; Out.reserve(Bytes + (Bytes >> 3)); auto Line = [&](StringRef Text) { if (Renumber.empty()) Out += Text; else appendRenumbered(Text, Renumber, Out); Out += '\n'; }; for (StringRef Text : Header) Line(Text); for (StringRef Text : Types) Line(Text); // Declarations follow name order rather than the emitter's first-use // order, which an edit elsewhere in the program can change. std::vector Declared; for (unsigned I = 0; I < Symbols.size(); ++I) if (Needed[I] && (Symbols[I].Kind == SymbolKind::Declaration || Symbols[I].Partition != P)) Declared.push_back(I); llvm::sort(Declared, [&](unsigned A, unsigned B) { return Symbols[A].Name < Symbols[B].Name; }); for (unsigned I : Declared) { const Symbol &S = Symbols[I]; Line(S.Kind == SymbolKind::Declaration ? S.Header : StringRef(S.Declaration)); } for (SymbolKind Kind : {SymbolKind::Variable, SymbolKind::Function}) for (unsigned I : Owned[P]) { const Symbol &S = Symbols[I]; if (S.Kind != Kind) continue; Line(S.Local && Exported[I] ? StringRef(S.PromotedHeader) : S.Header); if (!S.Body.empty()) Line(S.Body); } for (StringRef Text : Attributes) Line(Text); for (StringRef Text : NamedMetadata) Line(Text); std::string Units; for (unsigned Unit : CompileUnits) { auto It = Renumber.find(Unit); if (It == Renumber.end()) continue; Units += Units.empty() ? "!" : ", !"; Units += std::to_string(It->second); } if (!Units.empty()) Out += "!llvm.dbg.cu = !{" + Units + "}\n"; for (size_t K = 0; K < Nodes.size(); ++K) { StringRef Text = Metadata.find(Nodes[K])->second.Line; Out += '!'; Out += std::to_string(K); Line(Text.drop_front(Text.find(" = "))); } }; DefaultThreadPool Pool(hardware_concurrency()); for (unsigned P = 0; P < Count; ++P) Pool.async([&Assemble, P] { Assemble(P); }); Pool.wait(); return Result; } } // namespace scriptc