12cab237bSDimitry Andric //===- GlobalMerge.cpp - Internal globals merging -------------------------===//
291bc56edSDimitry Andric //
391bc56edSDimitry Andric // The LLVM Compiler Infrastructure
491bc56edSDimitry Andric //
591bc56edSDimitry Andric // This file is distributed under the University of Illinois Open Source
691bc56edSDimitry Andric // License. See LICENSE.TXT for details.
791bc56edSDimitry Andric //
891bc56edSDimitry Andric //===----------------------------------------------------------------------===//
92cab237bSDimitry Andric //
1091bc56edSDimitry Andric // This pass merges globals with internal linkage into one. This way all the
1191bc56edSDimitry Andric // globals which were merged into a biggest one can be addressed using offsets
1291bc56edSDimitry Andric // from the same base pointer (no need for separate base pointer for each of the
1391bc56edSDimitry Andric // global). Such a transformation can significantly reduce the register pressure
1491bc56edSDimitry Andric // when many globals are involved.
1591bc56edSDimitry Andric //
1691bc56edSDimitry Andric // For example, consider the code which touches several global variables at
1791bc56edSDimitry Andric // once:
1891bc56edSDimitry Andric //
1991bc56edSDimitry Andric // static int foo[N], bar[N], baz[N];
2091bc56edSDimitry Andric //
2191bc56edSDimitry Andric // for (i = 0; i < N; ++i) {
2291bc56edSDimitry Andric // foo[i] = bar[i] * baz[i];
2391bc56edSDimitry Andric // }
2491bc56edSDimitry Andric //
2591bc56edSDimitry Andric // On ARM the addresses of 3 arrays should be kept in the registers, thus
2691bc56edSDimitry Andric // this code has quite large register pressure (loop body):
2791bc56edSDimitry Andric //
2891bc56edSDimitry Andric // ldr r1, [r5], #4
2991bc56edSDimitry Andric // ldr r2, [r6], #4
3091bc56edSDimitry Andric // mul r1, r2, r1
3191bc56edSDimitry Andric // str r1, [r0], #4
3291bc56edSDimitry Andric //
3391bc56edSDimitry Andric // Pass converts the code to something like:
3491bc56edSDimitry Andric //
3591bc56edSDimitry Andric // static struct {
3691bc56edSDimitry Andric // int foo[N];
3791bc56edSDimitry Andric // int bar[N];
3891bc56edSDimitry Andric // int baz[N];
3991bc56edSDimitry Andric // } merged;
4091bc56edSDimitry Andric //
4191bc56edSDimitry Andric // for (i = 0; i < N; ++i) {
4291bc56edSDimitry Andric // merged.foo[i] = merged.bar[i] * merged.baz[i];
4391bc56edSDimitry Andric // }
4491bc56edSDimitry Andric //
4591bc56edSDimitry Andric // and in ARM code this becomes:
4691bc56edSDimitry Andric //
4791bc56edSDimitry Andric // ldr r0, [r5, #40]
4891bc56edSDimitry Andric // ldr r1, [r5, #80]
4991bc56edSDimitry Andric // mul r0, r1, r0
5091bc56edSDimitry Andric // str r0, [r5], #4
5191bc56edSDimitry Andric //
5291bc56edSDimitry Andric // note that we saved 2 registers here almostly "for free".
53ff0cc061SDimitry Andric //
54ff0cc061SDimitry Andric // However, merging globals can have tradeoffs:
55ff0cc061SDimitry Andric // - it confuses debuggers, tools, and users
56ff0cc061SDimitry Andric // - it makes linker optimizations less useful (order files, LOHs, ...)
57ff0cc061SDimitry Andric // - it forces usage of indexed addressing (which isn't necessarily "free")
58ff0cc061SDimitry Andric // - it can increase register pressure when the uses are disparate enough.
59ff0cc061SDimitry Andric //
60ff0cc061SDimitry Andric // We use heuristics to discover the best global grouping we can (cf cl::opts).
612cab237bSDimitry Andric //
6291bc56edSDimitry Andric // ===---------------------------------------------------------------------===//
6391bc56edSDimitry Andric
642cab237bSDimitry Andric #include "llvm/ADT/BitVector.h"
65ff0cc061SDimitry Andric #include "llvm/ADT/DenseMap.h"
6691bc56edSDimitry Andric #include "llvm/ADT/SmallPtrSet.h"
672cab237bSDimitry Andric #include "llvm/ADT/SmallVector.h"
6891bc56edSDimitry Andric #include "llvm/ADT/Statistic.h"
692cab237bSDimitry Andric #include "llvm/ADT/StringRef.h"
702cab237bSDimitry Andric #include "llvm/ADT/Triple.h"
712cab237bSDimitry Andric #include "llvm/ADT/Twine.h"
7239d628a0SDimitry Andric #include "llvm/CodeGen/Passes.h"
732cab237bSDimitry Andric #include "llvm/IR/BasicBlock.h"
7491bc56edSDimitry Andric #include "llvm/IR/Constants.h"
7591bc56edSDimitry Andric #include "llvm/IR/DataLayout.h"
7691bc56edSDimitry Andric #include "llvm/IR/DerivedTypes.h"
7791bc56edSDimitry Andric #include "llvm/IR/Function.h"
782cab237bSDimitry Andric #include "llvm/IR/GlobalAlias.h"
792cab237bSDimitry Andric #include "llvm/IR/GlobalValue.h"
8091bc56edSDimitry Andric #include "llvm/IR/GlobalVariable.h"
812cab237bSDimitry Andric #include "llvm/IR/Instruction.h"
8291bc56edSDimitry Andric #include "llvm/IR/Module.h"
832cab237bSDimitry Andric #include "llvm/IR/Type.h"
842cab237bSDimitry Andric #include "llvm/IR/Use.h"
852cab237bSDimitry Andric #include "llvm/IR/User.h"
8691bc56edSDimitry Andric #include "llvm/Pass.h"
872cab237bSDimitry Andric #include "llvm/Support/Casting.h"
8891bc56edSDimitry Andric #include "llvm/Support/CommandLine.h"
89ff0cc061SDimitry Andric #include "llvm/Support/Debug.h"
90ff0cc061SDimitry Andric #include "llvm/Support/raw_ostream.h"
914ba319b5SDimitry Andric #include "llvm/Target/TargetLoweringObjectFile.h"
922cab237bSDimitry Andric #include "llvm/Target/TargetMachine.h"
93ff0cc061SDimitry Andric #include <algorithm>
942cab237bSDimitry Andric #include <cassert>
952cab237bSDimitry Andric #include <cstddef>
962cab237bSDimitry Andric #include <cstdint>
972cab237bSDimitry Andric #include <string>
982cab237bSDimitry Andric #include <vector>
992cab237bSDimitry Andric
10091bc56edSDimitry Andric using namespace llvm;
10191bc56edSDimitry Andric
10291bc56edSDimitry Andric #define DEBUG_TYPE "global-merge"
10391bc56edSDimitry Andric
104ff0cc061SDimitry Andric // FIXME: This is only useful as a last-resort way to disable the pass.
10591bc56edSDimitry Andric static cl::opt<bool>
10691bc56edSDimitry Andric EnableGlobalMerge("enable-global-merge", cl::Hidden,
107ff0cc061SDimitry Andric cl::desc("Enable the global merge pass"),
108ff0cc061SDimitry Andric cl::init(true));
109ff0cc061SDimitry Andric
1103ca95b02SDimitry Andric static cl::opt<unsigned>
1113ca95b02SDimitry Andric GlobalMergeMaxOffset("global-merge-max-offset", cl::Hidden,
1123ca95b02SDimitry Andric cl::desc("Set maximum offset for global merge pass"),
1133ca95b02SDimitry Andric cl::init(0));
1143ca95b02SDimitry Andric
115ff0cc061SDimitry Andric static cl::opt<bool> GlobalMergeGroupByUse(
116ff0cc061SDimitry Andric "global-merge-group-by-use", cl::Hidden,
117ff0cc061SDimitry Andric cl::desc("Improve global merge pass to look at uses"), cl::init(true));
118ff0cc061SDimitry Andric
119ff0cc061SDimitry Andric static cl::opt<bool> GlobalMergeIgnoreSingleUse(
120ff0cc061SDimitry Andric "global-merge-ignore-single-use", cl::Hidden,
121ff0cc061SDimitry Andric cl::desc("Improve global merge pass to ignore globals only used alone"),
12291bc56edSDimitry Andric cl::init(true));
12391bc56edSDimitry Andric
12491bc56edSDimitry Andric static cl::opt<bool>
12591bc56edSDimitry Andric EnableGlobalMergeOnConst("global-merge-on-const", cl::Hidden,
12691bc56edSDimitry Andric cl::desc("Enable global merge pass on constants"),
12791bc56edSDimitry Andric cl::init(false));
12891bc56edSDimitry Andric
12991bc56edSDimitry Andric // FIXME: this could be a transitional option, and we probably need to remove
13091bc56edSDimitry Andric // it if only we are sure this optimization could always benefit all targets.
1317d523365SDimitry Andric static cl::opt<cl::boolOrDefault>
13291bc56edSDimitry Andric EnableGlobalMergeOnExternal("global-merge-on-external", cl::Hidden,
1337d523365SDimitry Andric cl::desc("Enable global merge pass on external linkage"));
13491bc56edSDimitry Andric
13591bc56edSDimitry Andric STATISTIC(NumMerged, "Number of globals merged");
1362cab237bSDimitry Andric
13791bc56edSDimitry Andric namespace {
1382cab237bSDimitry Andric
13991bc56edSDimitry Andric class GlobalMerge : public FunctionPass {
1402cab237bSDimitry Andric const TargetMachine *TM = nullptr;
1412cab237bSDimitry Andric
142ff0cc061SDimitry Andric // FIXME: Infer the maximum possible offset depending on the actual users
143ff0cc061SDimitry Andric // (these max offsets are different for the users inside Thumb or ARM
144ff0cc061SDimitry Andric // functions), see the code that passes in the offset in the ARM backend
145ff0cc061SDimitry Andric // for more information.
146ff0cc061SDimitry Andric unsigned MaxOffset;
14791bc56edSDimitry Andric
14897bc6c73SDimitry Andric /// Whether we should try to optimize for size only.
14997bc6c73SDimitry Andric /// Currently, this applies a dead simple heuristic: only consider globals
15097bc6c73SDimitry Andric /// used in minsize functions for merging.
15197bc6c73SDimitry Andric /// FIXME: This could learn about optsize, and be used in the cost model.
1522cab237bSDimitry Andric bool OnlyOptimizeForSize = false;
15397bc6c73SDimitry Andric
1547d523365SDimitry Andric /// Whether we should merge global variables that have external linkage.
1552cab237bSDimitry Andric bool MergeExternalGlobals = false;
1567d523365SDimitry Andric
1573ca95b02SDimitry Andric bool IsMachO;
1583ca95b02SDimitry Andric
15991bc56edSDimitry Andric bool doMerge(SmallVectorImpl<GlobalVariable*> &Globals,
16091bc56edSDimitry Andric Module &M, bool isConst, unsigned AddrSpace) const;
1612cab237bSDimitry Andric
1624ba319b5SDimitry Andric /// Merge everything in \p Globals for which the corresponding bit
163ff0cc061SDimitry Andric /// in \p GlobalSet is set.
1647d523365SDimitry Andric bool doMerge(const SmallVectorImpl<GlobalVariable *> &Globals,
165ff0cc061SDimitry Andric const BitVector &GlobalSet, Module &M, bool isConst,
166ff0cc061SDimitry Andric unsigned AddrSpace) const;
16791bc56edSDimitry Andric
1684ba319b5SDimitry Andric /// Check if the given variable has been identified as must keep
16991bc56edSDimitry Andric /// \pre setMustKeepGlobalVariables must have been called on the Module that
17091bc56edSDimitry Andric /// contains GV
isMustKeepGlobalVariable(const GlobalVariable * GV) const17191bc56edSDimitry Andric bool isMustKeepGlobalVariable(const GlobalVariable *GV) const {
17291bc56edSDimitry Andric return MustKeepGlobalVariables.count(GV);
17391bc56edSDimitry Andric }
17491bc56edSDimitry Andric
17591bc56edSDimitry Andric /// Collect every variables marked as "used" or used in a landing pad
17691bc56edSDimitry Andric /// instruction for this Module.
17791bc56edSDimitry Andric void setMustKeepGlobalVariables(Module &M);
17891bc56edSDimitry Andric
17991bc56edSDimitry Andric /// Collect every variables marked as "used"
1804ba319b5SDimitry Andric void collectUsedGlobalVariables(Module &M, StringRef Name);
18191bc56edSDimitry Andric
18291bc56edSDimitry Andric /// Keep track of the GlobalVariable that must not be merged away
18391bc56edSDimitry Andric SmallPtrSet<const GlobalVariable *, 16> MustKeepGlobalVariables;
18491bc56edSDimitry Andric
18591bc56edSDimitry Andric public:
18691bc56edSDimitry Andric static char ID; // Pass identification, replacement for typeid.
1872cab237bSDimitry Andric
GlobalMerge()1883ca95b02SDimitry Andric explicit GlobalMerge()
1892cab237bSDimitry Andric : FunctionPass(ID), MaxOffset(GlobalMergeMaxOffset) {
1903ca95b02SDimitry Andric initializeGlobalMergePass(*PassRegistry::getPassRegistry());
1913ca95b02SDimitry Andric }
1923ca95b02SDimitry Andric
GlobalMerge(const TargetMachine * TM,unsigned MaximalOffset,bool OnlyOptimizeForSize,bool MergeExternalGlobals)1933ca95b02SDimitry Andric explicit GlobalMerge(const TargetMachine *TM, unsigned MaximalOffset,
1943ca95b02SDimitry Andric bool OnlyOptimizeForSize, bool MergeExternalGlobals)
195875ed548SDimitry Andric : FunctionPass(ID), TM(TM), MaxOffset(MaximalOffset),
1967d523365SDimitry Andric OnlyOptimizeForSize(OnlyOptimizeForSize),
1977d523365SDimitry Andric MergeExternalGlobals(MergeExternalGlobals) {
19891bc56edSDimitry Andric initializeGlobalMergePass(*PassRegistry::getPassRegistry());
19991bc56edSDimitry Andric }
20091bc56edSDimitry Andric
20191bc56edSDimitry Andric bool doInitialization(Module &M) override;
20291bc56edSDimitry Andric bool runOnFunction(Function &F) override;
20391bc56edSDimitry Andric bool doFinalization(Module &M) override;
20491bc56edSDimitry Andric
getPassName() const205d88c1a5aSDimitry Andric StringRef getPassName() const override { return "Merge internal globals"; }
20691bc56edSDimitry Andric
getAnalysisUsage(AnalysisUsage & AU) const20791bc56edSDimitry Andric void getAnalysisUsage(AnalysisUsage &AU) const override {
20891bc56edSDimitry Andric AU.setPreservesCFG();
20991bc56edSDimitry Andric FunctionPass::getAnalysisUsage(AU);
21091bc56edSDimitry Andric }
21191bc56edSDimitry Andric };
2122cab237bSDimitry Andric
21391bc56edSDimitry Andric } // end anonymous namespace
21491bc56edSDimitry Andric
21591bc56edSDimitry Andric char GlobalMerge::ID = 0;
2162cab237bSDimitry Andric
217302affcbSDimitry Andric INITIALIZE_PASS(GlobalMerge, DEBUG_TYPE, "Merge global variables", false, false)
21891bc56edSDimitry Andric
doMerge(SmallVectorImpl<GlobalVariable * > & Globals,Module & M,bool isConst,unsigned AddrSpace) const21991bc56edSDimitry Andric bool GlobalMerge::doMerge(SmallVectorImpl<GlobalVariable*> &Globals,
22091bc56edSDimitry Andric Module &M, bool isConst, unsigned AddrSpace) const {
221875ed548SDimitry Andric auto &DL = M.getDataLayout();
22291bc56edSDimitry Andric // FIXME: Find better heuristics
2237d523365SDimitry Andric std::stable_sort(Globals.begin(), Globals.end(),
224875ed548SDimitry Andric [&DL](const GlobalVariable *GV1, const GlobalVariable *GV2) {
2257d523365SDimitry Andric return DL.getTypeAllocSize(GV1->getValueType()) <
2267d523365SDimitry Andric DL.getTypeAllocSize(GV2->getValueType());
22791bc56edSDimitry Andric });
22891bc56edSDimitry Andric
229ff0cc061SDimitry Andric // If we want to just blindly group all globals together, do so.
230ff0cc061SDimitry Andric if (!GlobalMergeGroupByUse) {
231ff0cc061SDimitry Andric BitVector AllGlobals(Globals.size());
232ff0cc061SDimitry Andric AllGlobals.set();
233ff0cc061SDimitry Andric return doMerge(Globals, AllGlobals, M, isConst, AddrSpace);
234ff0cc061SDimitry Andric }
235ff0cc061SDimitry Andric
236ff0cc061SDimitry Andric // If we want to be smarter, look at all uses of each global, to try to
237ff0cc061SDimitry Andric // discover all sets of globals used together, and how many times each of
2387d523365SDimitry Andric // these sets occurred.
239ff0cc061SDimitry Andric //
240ff0cc061SDimitry Andric // Keep this reasonably efficient, by having an append-only list of all sets
241ff0cc061SDimitry Andric // discovered so far (UsedGlobalSet), and mapping each "together-ness" unit of
242ff0cc061SDimitry Andric // code (currently, a Function) to the set of globals seen so far that are
243ff0cc061SDimitry Andric // used together in that unit (GlobalUsesByFunction).
244ff0cc061SDimitry Andric //
2454ba319b5SDimitry Andric // When we look at the Nth global, we know that any new set is either:
246ff0cc061SDimitry Andric // - the singleton set {N}, containing this global only, or
247ff0cc061SDimitry Andric // - the union of {N} and a previously-discovered set, containing some
248ff0cc061SDimitry Andric // combination of the previous N-1 globals.
249ff0cc061SDimitry Andric // Using that knowledge, when looking at the Nth global, we can keep:
250ff0cc061SDimitry Andric // - a reference to the singleton set {N} (CurGVOnlySetIdx)
251ff0cc061SDimitry Andric // - a list mapping each previous set to its union with {N} (EncounteredUGS),
252ff0cc061SDimitry Andric // if it actually occurs.
253ff0cc061SDimitry Andric
254ff0cc061SDimitry Andric // We keep track of the sets of globals used together "close enough".
255ff0cc061SDimitry Andric struct UsedGlobalSet {
256ff0cc061SDimitry Andric BitVector Globals;
2572cab237bSDimitry Andric unsigned UsageCount = 1;
2582cab237bSDimitry Andric
2592cab237bSDimitry Andric UsedGlobalSet(size_t Size) : Globals(Size) {}
260ff0cc061SDimitry Andric };
261ff0cc061SDimitry Andric
262ff0cc061SDimitry Andric // Each set is unique in UsedGlobalSets.
263ff0cc061SDimitry Andric std::vector<UsedGlobalSet> UsedGlobalSets;
264ff0cc061SDimitry Andric
265ff0cc061SDimitry Andric // Avoid repeating the create-global-set pattern.
266ff0cc061SDimitry Andric auto CreateGlobalSet = [&]() -> UsedGlobalSet & {
267ff0cc061SDimitry Andric UsedGlobalSets.emplace_back(Globals.size());
268ff0cc061SDimitry Andric return UsedGlobalSets.back();
269ff0cc061SDimitry Andric };
270ff0cc061SDimitry Andric
271ff0cc061SDimitry Andric // The first set is the empty set.
272ff0cc061SDimitry Andric CreateGlobalSet().UsageCount = 0;
273ff0cc061SDimitry Andric
274ff0cc061SDimitry Andric // We define "close enough" to be "in the same function".
275ff0cc061SDimitry Andric // FIXME: Grouping uses by function is way too aggressive, so we should have
276ff0cc061SDimitry Andric // a better metric for distance between uses.
277ff0cc061SDimitry Andric // The obvious alternative would be to group by BasicBlock, but that's in
278ff0cc061SDimitry Andric // turn too conservative..
279ff0cc061SDimitry Andric // Anything in between wouldn't be trivial to compute, so just stick with
280ff0cc061SDimitry Andric // per-function grouping.
281ff0cc061SDimitry Andric
282ff0cc061SDimitry Andric // The value type is an index into UsedGlobalSets.
283ff0cc061SDimitry Andric // The default (0) conveniently points to the empty set.
284ff0cc061SDimitry Andric DenseMap<Function *, size_t /*UsedGlobalSetIdx*/> GlobalUsesByFunction;
285ff0cc061SDimitry Andric
286ff0cc061SDimitry Andric // Now, look at each merge-eligible global in turn.
287ff0cc061SDimitry Andric
288ff0cc061SDimitry Andric // Keep track of the sets we already encountered to which we added the
289ff0cc061SDimitry Andric // current global.
290ff0cc061SDimitry Andric // Each element matches the same-index element in UsedGlobalSets.
291ff0cc061SDimitry Andric // This lets us efficiently tell whether a set has already been expanded to
292ff0cc061SDimitry Andric // include the current global.
293ff0cc061SDimitry Andric std::vector<size_t> EncounteredUGS;
294ff0cc061SDimitry Andric
295ff0cc061SDimitry Andric for (size_t GI = 0, GE = Globals.size(); GI != GE; ++GI) {
296ff0cc061SDimitry Andric GlobalVariable *GV = Globals[GI];
297ff0cc061SDimitry Andric
298ff0cc061SDimitry Andric // Reset the encountered sets for this global...
299ff0cc061SDimitry Andric std::fill(EncounteredUGS.begin(), EncounteredUGS.end(), 0);
300ff0cc061SDimitry Andric // ...and grow it in case we created new sets for the previous global.
301ff0cc061SDimitry Andric EncounteredUGS.resize(UsedGlobalSets.size());
302ff0cc061SDimitry Andric
303ff0cc061SDimitry Andric // We might need to create a set that only consists of the current global.
304ff0cc061SDimitry Andric // Keep track of its index into UsedGlobalSets.
305ff0cc061SDimitry Andric size_t CurGVOnlySetIdx = 0;
306ff0cc061SDimitry Andric
307ff0cc061SDimitry Andric // For each global, look at all its Uses.
308ff0cc061SDimitry Andric for (auto &U : GV->uses()) {
309ff0cc061SDimitry Andric // This Use might be a ConstantExpr. We're interested in Instruction
310ff0cc061SDimitry Andric // users, so look through ConstantExpr...
311ff0cc061SDimitry Andric Use *UI, *UE;
312ff0cc061SDimitry Andric if (ConstantExpr *CE = dyn_cast<ConstantExpr>(U.getUser())) {
31397bc6c73SDimitry Andric if (CE->use_empty())
31497bc6c73SDimitry Andric continue;
315ff0cc061SDimitry Andric UI = &*CE->use_begin();
316ff0cc061SDimitry Andric UE = nullptr;
317ff0cc061SDimitry Andric } else if (isa<Instruction>(U.getUser())) {
318ff0cc061SDimitry Andric UI = &U;
319ff0cc061SDimitry Andric UE = UI->getNext();
320ff0cc061SDimitry Andric } else {
321ff0cc061SDimitry Andric continue;
322ff0cc061SDimitry Andric }
323ff0cc061SDimitry Andric
324ff0cc061SDimitry Andric // ...to iterate on all the instruction users of the global.
325ff0cc061SDimitry Andric // Note that we iterate on Uses and not on Users to be able to getNext().
326ff0cc061SDimitry Andric for (; UI != UE; UI = UI->getNext()) {
327ff0cc061SDimitry Andric Instruction *I = dyn_cast<Instruction>(UI->getUser());
328ff0cc061SDimitry Andric if (!I)
329ff0cc061SDimitry Andric continue;
330ff0cc061SDimitry Andric
331ff0cc061SDimitry Andric Function *ParentFn = I->getParent()->getParent();
33297bc6c73SDimitry Andric
33397bc6c73SDimitry Andric // If we're only optimizing for size, ignore non-minsize functions.
3347d523365SDimitry Andric if (OnlyOptimizeForSize && !ParentFn->optForMinSize())
33597bc6c73SDimitry Andric continue;
33697bc6c73SDimitry Andric
337ff0cc061SDimitry Andric size_t UGSIdx = GlobalUsesByFunction[ParentFn];
338ff0cc061SDimitry Andric
339ff0cc061SDimitry Andric // If this is the first global the basic block uses, map it to the set
340ff0cc061SDimitry Andric // consisting of this global only.
341ff0cc061SDimitry Andric if (!UGSIdx) {
342ff0cc061SDimitry Andric // If that set doesn't exist yet, create it.
343ff0cc061SDimitry Andric if (!CurGVOnlySetIdx) {
344ff0cc061SDimitry Andric CurGVOnlySetIdx = UsedGlobalSets.size();
345ff0cc061SDimitry Andric CreateGlobalSet().Globals.set(GI);
346ff0cc061SDimitry Andric } else {
347ff0cc061SDimitry Andric ++UsedGlobalSets[CurGVOnlySetIdx].UsageCount;
348ff0cc061SDimitry Andric }
349ff0cc061SDimitry Andric
350ff0cc061SDimitry Andric GlobalUsesByFunction[ParentFn] = CurGVOnlySetIdx;
351ff0cc061SDimitry Andric continue;
352ff0cc061SDimitry Andric }
353ff0cc061SDimitry Andric
354ff0cc061SDimitry Andric // If we already encountered this BB, just increment the counter.
355ff0cc061SDimitry Andric if (UsedGlobalSets[UGSIdx].Globals.test(GI)) {
356ff0cc061SDimitry Andric ++UsedGlobalSets[UGSIdx].UsageCount;
357ff0cc061SDimitry Andric continue;
358ff0cc061SDimitry Andric }
359ff0cc061SDimitry Andric
360ff0cc061SDimitry Andric // If not, the previous set wasn't actually used in this function.
361ff0cc061SDimitry Andric --UsedGlobalSets[UGSIdx].UsageCount;
362ff0cc061SDimitry Andric
363ff0cc061SDimitry Andric // If we already expanded the previous set to include this global, just
364ff0cc061SDimitry Andric // reuse that expanded set.
365ff0cc061SDimitry Andric if (size_t ExpandedIdx = EncounteredUGS[UGSIdx]) {
366ff0cc061SDimitry Andric ++UsedGlobalSets[ExpandedIdx].UsageCount;
367ff0cc061SDimitry Andric GlobalUsesByFunction[ParentFn] = ExpandedIdx;
368ff0cc061SDimitry Andric continue;
369ff0cc061SDimitry Andric }
370ff0cc061SDimitry Andric
371ff0cc061SDimitry Andric // If not, create a new set consisting of the union of the previous set
372ff0cc061SDimitry Andric // and this global. Mark it as encountered, so we can reuse it later.
373ff0cc061SDimitry Andric GlobalUsesByFunction[ParentFn] = EncounteredUGS[UGSIdx] =
374ff0cc061SDimitry Andric UsedGlobalSets.size();
375ff0cc061SDimitry Andric
376ff0cc061SDimitry Andric UsedGlobalSet &NewUGS = CreateGlobalSet();
377ff0cc061SDimitry Andric NewUGS.Globals.set(GI);
378ff0cc061SDimitry Andric NewUGS.Globals |= UsedGlobalSets[UGSIdx].Globals;
379ff0cc061SDimitry Andric }
380ff0cc061SDimitry Andric }
381ff0cc061SDimitry Andric }
382ff0cc061SDimitry Andric
383ff0cc061SDimitry Andric // Now we found a bunch of sets of globals used together. We accumulated
384ff0cc061SDimitry Andric // the number of times we encountered the sets (i.e., the number of blocks
385ff0cc061SDimitry Andric // that use that exact set of globals).
386ff0cc061SDimitry Andric //
387ff0cc061SDimitry Andric // Multiply that by the size of the set to give us a crude profitability
388ff0cc061SDimitry Andric // metric.
3892cab237bSDimitry Andric std::stable_sort(UsedGlobalSets.begin(), UsedGlobalSets.end(),
390ff0cc061SDimitry Andric [](const UsedGlobalSet &UGS1, const UsedGlobalSet &UGS2) {
391ff0cc061SDimitry Andric return UGS1.Globals.count() * UGS1.UsageCount <
392ff0cc061SDimitry Andric UGS2.Globals.count() * UGS2.UsageCount;
393ff0cc061SDimitry Andric });
394ff0cc061SDimitry Andric
395ff0cc061SDimitry Andric // We can choose to merge all globals together, but ignore globals never used
396ff0cc061SDimitry Andric // with another global. This catches the obviously non-profitable cases of
397ff0cc061SDimitry Andric // having a single global, but is aggressive enough for any other case.
398ff0cc061SDimitry Andric if (GlobalMergeIgnoreSingleUse) {
399ff0cc061SDimitry Andric BitVector AllGlobals(Globals.size());
400ff0cc061SDimitry Andric for (size_t i = 0, e = UsedGlobalSets.size(); i != e; ++i) {
401ff0cc061SDimitry Andric const UsedGlobalSet &UGS = UsedGlobalSets[e - i - 1];
402ff0cc061SDimitry Andric if (UGS.UsageCount == 0)
403ff0cc061SDimitry Andric continue;
404ff0cc061SDimitry Andric if (UGS.Globals.count() > 1)
405ff0cc061SDimitry Andric AllGlobals |= UGS.Globals;
406ff0cc061SDimitry Andric }
407ff0cc061SDimitry Andric return doMerge(Globals, AllGlobals, M, isConst, AddrSpace);
408ff0cc061SDimitry Andric }
409ff0cc061SDimitry Andric
410ff0cc061SDimitry Andric // Starting from the sets with the best (=biggest) profitability, find a
411ff0cc061SDimitry Andric // good combination.
412ff0cc061SDimitry Andric // The ideal (and expensive) solution can only be found by trying all
413ff0cc061SDimitry Andric // combinations, looking for the one with the best profitability.
414ff0cc061SDimitry Andric // Don't be smart about it, and just pick the first compatible combination,
415ff0cc061SDimitry Andric // starting with the sets with the best profitability.
416ff0cc061SDimitry Andric BitVector PickedGlobals(Globals.size());
417ff0cc061SDimitry Andric bool Changed = false;
418ff0cc061SDimitry Andric
419ff0cc061SDimitry Andric for (size_t i = 0, e = UsedGlobalSets.size(); i != e; ++i) {
420ff0cc061SDimitry Andric const UsedGlobalSet &UGS = UsedGlobalSets[e - i - 1];
421ff0cc061SDimitry Andric if (UGS.UsageCount == 0)
422ff0cc061SDimitry Andric continue;
423ff0cc061SDimitry Andric if (PickedGlobals.anyCommon(UGS.Globals))
424ff0cc061SDimitry Andric continue;
425ff0cc061SDimitry Andric PickedGlobals |= UGS.Globals;
426ff0cc061SDimitry Andric // If the set only contains one global, there's no point in merging.
427ff0cc061SDimitry Andric // Ignore the global for inclusion in other sets though, so keep it in
428ff0cc061SDimitry Andric // PickedGlobals.
429ff0cc061SDimitry Andric if (UGS.Globals.count() < 2)
430ff0cc061SDimitry Andric continue;
431ff0cc061SDimitry Andric Changed |= doMerge(Globals, UGS.Globals, M, isConst, AddrSpace);
432ff0cc061SDimitry Andric }
433ff0cc061SDimitry Andric
434ff0cc061SDimitry Andric return Changed;
435ff0cc061SDimitry Andric }
436ff0cc061SDimitry Andric
doMerge(const SmallVectorImpl<GlobalVariable * > & Globals,const BitVector & GlobalSet,Module & M,bool isConst,unsigned AddrSpace) const4377d523365SDimitry Andric bool GlobalMerge::doMerge(const SmallVectorImpl<GlobalVariable *> &Globals,
438ff0cc061SDimitry Andric const BitVector &GlobalSet, Module &M, bool isConst,
439ff0cc061SDimitry Andric unsigned AddrSpace) const {
4407d523365SDimitry Andric assert(Globals.size() > 1);
441ff0cc061SDimitry Andric
44291bc56edSDimitry Andric Type *Int32Ty = Type::getInt32Ty(M.getContext());
4434ba319b5SDimitry Andric Type *Int8Ty = Type::getInt8Ty(M.getContext());
444875ed548SDimitry Andric auto &DL = M.getDataLayout();
44591bc56edSDimitry Andric
4464ba319b5SDimitry Andric LLVM_DEBUG(dbgs() << " Trying to merge set, starts with #"
447ff0cc061SDimitry Andric << GlobalSet.find_first() << "\n");
448ff0cc061SDimitry Andric
4494ba319b5SDimitry Andric bool Changed = false;
450ff0cc061SDimitry Andric ssize_t i = GlobalSet.find_first();
451ff0cc061SDimitry Andric while (i != -1) {
452ff0cc061SDimitry Andric ssize_t j = 0;
45391bc56edSDimitry Andric uint64_t MergedSize = 0;
45491bc56edSDimitry Andric std::vector<Type*> Tys;
45591bc56edSDimitry Andric std::vector<Constant*> Inits;
4564ba319b5SDimitry Andric std::vector<unsigned> StructIdxs;
45791bc56edSDimitry Andric
458d88c1a5aSDimitry Andric bool HasExternal = false;
459d88c1a5aSDimitry Andric StringRef FirstExternalName;
4604ba319b5SDimitry Andric unsigned MaxAlign = 1;
4614ba319b5SDimitry Andric unsigned CurIdx = 0;
462ff0cc061SDimitry Andric for (j = i; j != -1; j = GlobalSet.find_next(j)) {
4637d523365SDimitry Andric Type *Ty = Globals[j]->getValueType();
464*b5893f02SDimitry Andric
465*b5893f02SDimitry Andric // Make sure we use the same alignment AsmPrinter would use.
4664ba319b5SDimitry Andric unsigned Align = DL.getPreferredAlignment(Globals[j]);
4674ba319b5SDimitry Andric unsigned Padding = alignTo(MergedSize, Align) - MergedSize;
4684ba319b5SDimitry Andric MergedSize += Padding;
469875ed548SDimitry Andric MergedSize += DL.getTypeAllocSize(Ty);
47091bc56edSDimitry Andric if (MergedSize > MaxOffset) {
47191bc56edSDimitry Andric break;
47291bc56edSDimitry Andric }
4734ba319b5SDimitry Andric if (Padding) {
4744ba319b5SDimitry Andric Tys.push_back(ArrayType::get(Int8Ty, Padding));
4754ba319b5SDimitry Andric Inits.push_back(ConstantAggregateZero::get(Tys.back()));
4764ba319b5SDimitry Andric ++CurIdx;
4774ba319b5SDimitry Andric }
47891bc56edSDimitry Andric Tys.push_back(Ty);
47991bc56edSDimitry Andric Inits.push_back(Globals[j]->getInitializer());
4804ba319b5SDimitry Andric StructIdxs.push_back(CurIdx++);
4814ba319b5SDimitry Andric
4824ba319b5SDimitry Andric MaxAlign = std::max(MaxAlign, Align);
483d88c1a5aSDimitry Andric
484d88c1a5aSDimitry Andric if (Globals[j]->hasExternalLinkage() && !HasExternal) {
485d88c1a5aSDimitry Andric HasExternal = true;
486d88c1a5aSDimitry Andric FirstExternalName = Globals[j]->getName();
487d88c1a5aSDimitry Andric }
48891bc56edSDimitry Andric }
48991bc56edSDimitry Andric
4904ba319b5SDimitry Andric // Exit early if there is only one global to merge.
4914ba319b5SDimitry Andric if (Tys.size() < 2) {
4924ba319b5SDimitry Andric i = j;
4934ba319b5SDimitry Andric continue;
4944ba319b5SDimitry Andric }
4954ba319b5SDimitry Andric
496d88c1a5aSDimitry Andric // If merged variables doesn't have external linkage, we needn't to expose
497d88c1a5aSDimitry Andric // the symbol after merging.
498d88c1a5aSDimitry Andric GlobalValue::LinkageTypes Linkage = HasExternal
499d88c1a5aSDimitry Andric ? GlobalValue::ExternalLinkage
500d88c1a5aSDimitry Andric : GlobalValue::InternalLinkage;
5014ba319b5SDimitry Andric // Use a packed struct so we can control alignment.
5024ba319b5SDimitry Andric StructType *MergedTy = StructType::get(M.getContext(), Tys, true);
50391bc56edSDimitry Andric Constant *MergedInit = ConstantStruct::get(MergedTy, Inits);
50491bc56edSDimitry Andric
505d88c1a5aSDimitry Andric // On Darwin external linkage needs to be preserved, otherwise
506d88c1a5aSDimitry Andric // dsymutil cannot preserve the debug info for the merged
507d88c1a5aSDimitry Andric // variables. If they have external linkage, use the symbol name
508d88c1a5aSDimitry Andric // of the first variable merged as the suffix of global symbol
509d88c1a5aSDimitry Andric // name. This avoids a link-time naming conflict for the
510d88c1a5aSDimitry Andric // _MergedGlobals symbols.
511d88c1a5aSDimitry Andric Twine MergedName =
512d88c1a5aSDimitry Andric (IsMachO && HasExternal)
513d88c1a5aSDimitry Andric ? "_MergedGlobals_" + FirstExternalName
514d88c1a5aSDimitry Andric : "_MergedGlobals";
515d88c1a5aSDimitry Andric auto MergedLinkage = IsMachO ? Linkage : GlobalValue::PrivateLinkage;
516d88c1a5aSDimitry Andric auto *MergedGV = new GlobalVariable(
517d88c1a5aSDimitry Andric M, MergedTy, isConst, MergedLinkage, MergedInit, MergedName, nullptr,
518d88c1a5aSDimitry Andric GlobalVariable::NotThreadLocal, AddrSpace);
519d88c1a5aSDimitry Andric
5204ba319b5SDimitry Andric MergedGV->setAlignment(MaxAlign);
521*b5893f02SDimitry Andric MergedGV->setSection(Globals[i]->getSection());
52291bc56edSDimitry Andric
5234ba319b5SDimitry Andric const StructLayout *MergedLayout = DL.getStructLayout(MergedTy);
5247d523365SDimitry Andric for (ssize_t k = i, idx = 0; k != j; k = GlobalSet.find_next(k), ++idx) {
52591bc56edSDimitry Andric GlobalValue::LinkageTypes Linkage = Globals[k]->getLinkage();
52691bc56edSDimitry Andric std::string Name = Globals[k]->getName();
5274ba319b5SDimitry Andric GlobalValue::DLLStorageClassTypes DLLStorage =
5284ba319b5SDimitry Andric Globals[k]->getDLLStorageClass();
52991bc56edSDimitry Andric
530d88c1a5aSDimitry Andric // Copy metadata while adjusting any debug info metadata by the original
531d88c1a5aSDimitry Andric // global's offset within the merged global.
5324ba319b5SDimitry Andric MergedGV->copyMetadata(Globals[k],
5334ba319b5SDimitry Andric MergedLayout->getElementOffset(StructIdxs[idx]));
534d88c1a5aSDimitry Andric
53591bc56edSDimitry Andric Constant *Idx[2] = {
53691bc56edSDimitry Andric ConstantInt::get(Int32Ty, 0),
5374ba319b5SDimitry Andric ConstantInt::get(Int32Ty, StructIdxs[idx]),
53891bc56edSDimitry Andric };
539ff0cc061SDimitry Andric Constant *GEP =
540ff0cc061SDimitry Andric ConstantExpr::getInBoundsGetElementPtr(MergedTy, MergedGV, Idx);
54191bc56edSDimitry Andric Globals[k]->replaceAllUsesWith(GEP);
54291bc56edSDimitry Andric Globals[k]->eraseFromParent();
54391bc56edSDimitry Andric
5447d523365SDimitry Andric // When the linkage is not internal we must emit an alias for the original
5457d523365SDimitry Andric // variable name as it may be accessed from another object. On non-Mach-O
5467d523365SDimitry Andric // we can also emit an alias for internal linkage as it's safe to do so.
5477d523365SDimitry Andric // It's not safe on Mach-O as the alias (and thus the portion of the
5487d523365SDimitry Andric // MergedGlobals variable) may be dead stripped at link time.
5493ca95b02SDimitry Andric if (Linkage != GlobalValue::InternalLinkage || !IsMachO) {
5504ba319b5SDimitry Andric GlobalAlias *GA = GlobalAlias::create(Tys[StructIdxs[idx]], AddrSpace,
5514ba319b5SDimitry Andric Linkage, Name, GEP, &M);
5524ba319b5SDimitry Andric GA->setDLLStorageClass(DLLStorage);
55391bc56edSDimitry Andric }
55491bc56edSDimitry Andric
55591bc56edSDimitry Andric NumMerged++;
55691bc56edSDimitry Andric }
5574ba319b5SDimitry Andric Changed = true;
55891bc56edSDimitry Andric i = j;
55991bc56edSDimitry Andric }
56091bc56edSDimitry Andric
5614ba319b5SDimitry Andric return Changed;
56291bc56edSDimitry Andric }
56391bc56edSDimitry Andric
collectUsedGlobalVariables(Module & M,StringRef Name)5644ba319b5SDimitry Andric void GlobalMerge::collectUsedGlobalVariables(Module &M, StringRef Name) {
56591bc56edSDimitry Andric // Extract global variables from llvm.used array
5664ba319b5SDimitry Andric const GlobalVariable *GV = M.getGlobalVariable(Name);
56791bc56edSDimitry Andric if (!GV || !GV->hasInitializer()) return;
56891bc56edSDimitry Andric
56991bc56edSDimitry Andric // Should be an array of 'i8*'.
57091bc56edSDimitry Andric const ConstantArray *InitList = cast<ConstantArray>(GV->getInitializer());
57191bc56edSDimitry Andric
57291bc56edSDimitry Andric for (unsigned i = 0, e = InitList->getNumOperands(); i != e; ++i)
57391bc56edSDimitry Andric if (const GlobalVariable *G =
57491bc56edSDimitry Andric dyn_cast<GlobalVariable>(InitList->getOperand(i)->stripPointerCasts()))
57591bc56edSDimitry Andric MustKeepGlobalVariables.insert(G);
57691bc56edSDimitry Andric }
57791bc56edSDimitry Andric
setMustKeepGlobalVariables(Module & M)57891bc56edSDimitry Andric void GlobalMerge::setMustKeepGlobalVariables(Module &M) {
5794ba319b5SDimitry Andric collectUsedGlobalVariables(M, "llvm.used");
5804ba319b5SDimitry Andric collectUsedGlobalVariables(M, "llvm.compiler.used");
58191bc56edSDimitry Andric
582d88c1a5aSDimitry Andric for (Function &F : M) {
583d88c1a5aSDimitry Andric for (BasicBlock &BB : F) {
584d88c1a5aSDimitry Andric Instruction *Pad = BB.getFirstNonPHI();
585d88c1a5aSDimitry Andric if (!Pad->isEHPad())
586d88c1a5aSDimitry Andric continue;
58791bc56edSDimitry Andric
588d88c1a5aSDimitry Andric // Keep globals used by landingpads and catchpads.
589d88c1a5aSDimitry Andric for (const Use &U : Pad->operands()) {
59091bc56edSDimitry Andric if (const GlobalVariable *GV =
591d88c1a5aSDimitry Andric dyn_cast<GlobalVariable>(U->stripPointerCasts()))
59291bc56edSDimitry Andric MustKeepGlobalVariables.insert(GV);
59391bc56edSDimitry Andric }
59491bc56edSDimitry Andric }
59591bc56edSDimitry Andric }
596d88c1a5aSDimitry Andric }
59791bc56edSDimitry Andric
doInitialization(Module & M)59891bc56edSDimitry Andric bool GlobalMerge::doInitialization(Module &M) {
59991bc56edSDimitry Andric if (!EnableGlobalMerge)
60091bc56edSDimitry Andric return false;
60191bc56edSDimitry Andric
6023ca95b02SDimitry Andric IsMachO = Triple(M.getTargetTriple()).isOSBinFormatMachO();
6033ca95b02SDimitry Andric
604875ed548SDimitry Andric auto &DL = M.getDataLayout();
605*b5893f02SDimitry Andric DenseMap<std::pair<unsigned, StringRef>, SmallVector<GlobalVariable *, 16>>
606*b5893f02SDimitry Andric Globals, ConstGlobals, BSSGlobals;
60791bc56edSDimitry Andric bool Changed = false;
60891bc56edSDimitry Andric setMustKeepGlobalVariables(M);
60991bc56edSDimitry Andric
61091bc56edSDimitry Andric // Grab all non-const globals.
6117d523365SDimitry Andric for (auto &GV : M.globals()) {
61291bc56edSDimitry Andric // Merge is safe for "normal" internal or external globals only
613*b5893f02SDimitry Andric if (GV.isDeclaration() || GV.isThreadLocal() || GV.hasImplicitSection())
61491bc56edSDimitry Andric continue;
61591bc56edSDimitry Andric
6166d97bb29SDimitry Andric // It's not safe to merge globals that may be preempted
6176d97bb29SDimitry Andric if (TM && !TM->shouldAssumeDSOLocal(M, &GV))
6186d97bb29SDimitry Andric continue;
6196d97bb29SDimitry Andric
6207d523365SDimitry Andric if (!(MergeExternalGlobals && GV.hasExternalLinkage()) &&
6217d523365SDimitry Andric !GV.hasInternalLinkage())
62291bc56edSDimitry Andric continue;
62391bc56edSDimitry Andric
6247d523365SDimitry Andric PointerType *PT = dyn_cast<PointerType>(GV.getType());
62591bc56edSDimitry Andric assert(PT && "Global variable is not a pointer!");
62691bc56edSDimitry Andric
62791bc56edSDimitry Andric unsigned AddressSpace = PT->getAddressSpace();
628*b5893f02SDimitry Andric StringRef Section = GV.getSection();
62991bc56edSDimitry Andric
63091bc56edSDimitry Andric // Ignore all 'special' globals.
6317d523365SDimitry Andric if (GV.getName().startswith("llvm.") ||
6327d523365SDimitry Andric GV.getName().startswith(".llvm."))
63391bc56edSDimitry Andric continue;
63491bc56edSDimitry Andric
63591bc56edSDimitry Andric // Ignore all "required" globals:
6367d523365SDimitry Andric if (isMustKeepGlobalVariable(&GV))
63791bc56edSDimitry Andric continue;
63891bc56edSDimitry Andric
6394ba319b5SDimitry Andric Type *Ty = GV.getValueType();
640875ed548SDimitry Andric if (DL.getTypeAllocSize(Ty) < MaxOffset) {
6413ca95b02SDimitry Andric if (TM &&
642*b5893f02SDimitry Andric TargetLoweringObjectFile::getKindForGlobal(&GV, *TM).isBSS())
643*b5893f02SDimitry Andric BSSGlobals[{AddressSpace, Section}].push_back(&GV);
6447d523365SDimitry Andric else if (GV.isConstant())
645*b5893f02SDimitry Andric ConstGlobals[{AddressSpace, Section}].push_back(&GV);
64691bc56edSDimitry Andric else
647*b5893f02SDimitry Andric Globals[{AddressSpace, Section}].push_back(&GV);
64891bc56edSDimitry Andric }
64991bc56edSDimitry Andric }
65091bc56edSDimitry Andric
6517d523365SDimitry Andric for (auto &P : Globals)
6527d523365SDimitry Andric if (P.second.size() > 1)
653*b5893f02SDimitry Andric Changed |= doMerge(P.second, M, false, P.first.first);
65491bc56edSDimitry Andric
6557d523365SDimitry Andric for (auto &P : BSSGlobals)
6567d523365SDimitry Andric if (P.second.size() > 1)
657*b5893f02SDimitry Andric Changed |= doMerge(P.second, M, false, P.first.first);
65891bc56edSDimitry Andric
65991bc56edSDimitry Andric if (EnableGlobalMergeOnConst)
6607d523365SDimitry Andric for (auto &P : ConstGlobals)
6617d523365SDimitry Andric if (P.second.size() > 1)
662*b5893f02SDimitry Andric Changed |= doMerge(P.second, M, true, P.first.first);
66391bc56edSDimitry Andric
66491bc56edSDimitry Andric return Changed;
66591bc56edSDimitry Andric }
66691bc56edSDimitry Andric
runOnFunction(Function & F)66791bc56edSDimitry Andric bool GlobalMerge::runOnFunction(Function &F) {
66891bc56edSDimitry Andric return false;
66991bc56edSDimitry Andric }
67091bc56edSDimitry Andric
doFinalization(Module & M)67191bc56edSDimitry Andric bool GlobalMerge::doFinalization(Module &M) {
67291bc56edSDimitry Andric MustKeepGlobalVariables.clear();
67391bc56edSDimitry Andric return false;
67491bc56edSDimitry Andric }
67591bc56edSDimitry Andric
createGlobalMergePass(const TargetMachine * TM,unsigned Offset,bool OnlyOptimizeForSize,bool MergeExternalByDefault)67697bc6c73SDimitry Andric Pass *llvm::createGlobalMergePass(const TargetMachine *TM, unsigned Offset,
6777d523365SDimitry Andric bool OnlyOptimizeForSize,
6787d523365SDimitry Andric bool MergeExternalByDefault) {
6797d523365SDimitry Andric bool MergeExternal = (EnableGlobalMergeOnExternal == cl::BOU_UNSET) ?
6807d523365SDimitry Andric MergeExternalByDefault : (EnableGlobalMergeOnExternal == cl::BOU_TRUE);
6817d523365SDimitry Andric return new GlobalMerge(TM, Offset, OnlyOptimizeForSize, MergeExternal);
68291bc56edSDimitry Andric }
683