1 //===- lib/MC/MCPseudoProbe.cpp - Pseudo probe encoding support ----------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "llvm/MC/MCPseudoProbe.h"
10 #include "llvm/MC/MCAsmInfo.h"
11 #include "llvm/MC/MCContext.h"
12 #include "llvm/MC/MCExpr.h"
13 #include "llvm/MC/MCFragment.h"
14 #include "llvm/MC/MCObjectFileInfo.h"
15 #include "llvm/MC/MCObjectStreamer.h"
16 #include "llvm/Support/Endian.h"
17 #include "llvm/Support/LEB128.h"
18 #include "llvm/Support/raw_ostream.h"
19 #include <limits>
20 #include <memory>
21 #include <sstream>
22 
23 #define DEBUG_TYPE "mcpseudoprobe"
24 
25 using namespace llvm;
26 using namespace support;
27 
28 #ifndef NDEBUG
29 int MCPseudoProbeTable::DdgPrintIndent = 0;
30 #endif
31 
32 static const MCExpr *buildSymbolDiff(MCObjectStreamer *MCOS, const MCSymbol *A,
33                                      const MCSymbol *B) {
34   MCContext &Context = MCOS->getContext();
35   MCSymbolRefExpr::VariantKind Variant = MCSymbolRefExpr::VK_None;
36   const MCExpr *ARef = MCSymbolRefExpr::create(A, Variant, Context);
37   const MCExpr *BRef = MCSymbolRefExpr::create(B, Variant, Context);
38   const MCExpr *AddrDelta =
39       MCBinaryExpr::create(MCBinaryExpr::Sub, ARef, BRef, Context);
40   return AddrDelta;
41 }
42 
43 void MCPseudoProbe::emit(MCObjectStreamer *MCOS,
44                          const MCPseudoProbe *LastProbe) const {
45   // Emit Index
46   MCOS->emitULEB128IntValue(Index);
47   // Emit Type and the flag:
48   // Type (bit 0 to 3), with bit 4 to 6 for attributes.
49   // Flag (bit 7, 0 - code address, 1 - address delta). This indicates whether
50   // the following field is a symbolic code address or an address delta.
51   assert(Type <= 0xF && "Probe type too big to encode, exceeding 15");
52   assert(Attributes <= 0x7 &&
53          "Probe attributes too big to encode, exceeding 7");
54   uint8_t PackedType = Type | (Attributes << 4);
55   uint8_t Flag = LastProbe ? ((int8_t)MCPseudoProbeFlag::AddressDelta << 7) : 0;
56   MCOS->emitInt8(Flag | PackedType);
57 
58   if (LastProbe) {
59     // Emit the delta between the address label and LastProbe.
60     const MCExpr *AddrDelta =
61         buildSymbolDiff(MCOS, Label, LastProbe->getLabel());
62     int64_t Delta;
63     if (AddrDelta->evaluateAsAbsolute(Delta, MCOS->getAssemblerPtr())) {
64       MCOS->emitSLEB128IntValue(Delta);
65     } else {
66       MCOS->insert(new MCPseudoProbeAddrFragment(AddrDelta));
67     }
68   } else {
69     // Emit label as a symbolic code address.
70     MCOS->emitSymbolValue(
71         Label, MCOS->getContext().getAsmInfo()->getCodePointerSize());
72   }
73 
74   LLVM_DEBUG({
75     dbgs().indent(MCPseudoProbeTable::DdgPrintIndent);
76     dbgs() << "Probe: " << Index << "\n";
77   });
78 }
79 
80 void MCPseudoProbeInlineTree::addPseudoProbe(
81     const MCPseudoProbe &Probe, const MCPseudoProbeInlineStack &InlineStack) {
82   // The function should not be called on the root.
83   assert(isRoot() && "Should not be called on root");
84 
85   // When it comes here, the input look like:
86   //    Probe: GUID of C, ...
87   //    InlineStack: [88, A], [66, B]
88   // which means, Function A inlines function B at call site with a probe id of
89   // 88, and B inlines C at probe 66. The tri-tree expects a tree path like {[0,
90   // A], [88, B], [66, C]} to locate the tree node where the probe should be
91   // added. Note that the edge [0, A] means A is the top-level function we are
92   // emitting probes for.
93 
94   // Make a [0, A] edge.
95   // An empty inline stack means the function that the probe originates from
96   // is a top-level function.
97   InlineSite Top;
98   if (InlineStack.empty()) {
99     Top = InlineSite(Probe.getGuid(), 0);
100   } else {
101     Top = InlineSite(std::get<0>(InlineStack.front()), 0);
102   }
103 
104   auto *Cur = getOrAddNode(Top);
105 
106   // Make interior edges by walking the inline stack. Once it's done, Cur should
107   // point to the node that the probe originates from.
108   if (!InlineStack.empty()) {
109     auto Iter = InlineStack.begin();
110     auto Index = std::get<1>(*Iter);
111     Iter++;
112     for (; Iter != InlineStack.end(); Iter++) {
113       // Make an edge by using the previous probe id and current GUID.
114       Cur = Cur->getOrAddNode(InlineSite(std::get<0>(*Iter), Index));
115       Index = std::get<1>(*Iter);
116     }
117     Cur = Cur->getOrAddNode(InlineSite(Probe.getGuid(), Index));
118   }
119 
120   Cur->Probes.push_back(Probe);
121 }
122 
123 void MCPseudoProbeInlineTree::emit(MCObjectStreamer *MCOS,
124                                    const MCPseudoProbe *&LastProbe) {
125   LLVM_DEBUG({
126     dbgs().indent(MCPseudoProbeTable::DdgPrintIndent);
127     dbgs() << "Group [\n";
128     MCPseudoProbeTable::DdgPrintIndent += 2;
129   });
130   // Emit probes grouped by GUID.
131   if (Guid != 0) {
132     LLVM_DEBUG({
133       dbgs().indent(MCPseudoProbeTable::DdgPrintIndent);
134       dbgs() << "GUID: " << Guid << "\n";
135     });
136     // Emit Guid
137     MCOS->emitInt64(Guid);
138     // Emit number of probes in this node
139     MCOS->emitULEB128IntValue(Probes.size());
140     // Emit number of direct inlinees
141     MCOS->emitULEB128IntValue(Children.size());
142     // Emit probes in this group
143     for (const auto &Probe : Probes) {
144       Probe.emit(MCOS, LastProbe);
145       LastProbe = &Probe;
146     }
147   } else {
148     assert(Probes.empty() && "Root should not have probes");
149   }
150 
151   // Emit sorted descendant
152   // InlineSite is unique for each pair,
153   // so there will be no ordering of Inlinee based on MCPseudoProbeInlineTree*
154   std::map<InlineSite, MCPseudoProbeInlineTree *> Inlinees;
155   for (auto &Child : Children)
156     Inlinees[Child.first] = Child.second.get();
157 
158   for (const auto &Inlinee : Inlinees) {
159     if (Guid) {
160       // Emit probe index
161       MCOS->emitULEB128IntValue(std::get<1>(Inlinee.first));
162       LLVM_DEBUG({
163         dbgs().indent(MCPseudoProbeTable::DdgPrintIndent);
164         dbgs() << "InlineSite: " << std::get<1>(Inlinee.first) << "\n";
165       });
166     }
167     // Emit the group
168     Inlinee.second->emit(MCOS, LastProbe);
169   }
170 
171   LLVM_DEBUG({
172     MCPseudoProbeTable::DdgPrintIndent -= 2;
173     dbgs().indent(MCPseudoProbeTable::DdgPrintIndent);
174     dbgs() << "]\n";
175   });
176 }
177 
178 void MCPseudoProbeSection::emit(MCObjectStreamer *MCOS) {
179   MCContext &Ctx = MCOS->getContext();
180 
181   for (auto &ProbeSec : MCProbeDivisions) {
182     const MCPseudoProbe *LastProbe = nullptr;
183     if (auto *S =
184             Ctx.getObjectFileInfo()->getPseudoProbeSection(ProbeSec.first)) {
185       // Switch to the .pseudoprobe section or a comdat group.
186       MCOS->SwitchSection(S);
187       // Emit probes grouped by GUID.
188       ProbeSec.second.emit(MCOS, LastProbe);
189     }
190   }
191 }
192 
193 //
194 // This emits the pseudo probe tables.
195 //
196 void MCPseudoProbeTable::emit(MCObjectStreamer *MCOS) {
197   MCContext &Ctx = MCOS->getContext();
198   auto &ProbeTable = Ctx.getMCPseudoProbeTable();
199 
200   // Bail out early so we don't switch to the pseudo_probe section needlessly
201   // and in doing so create an unnecessary (if empty) section.
202   auto &ProbeSections = ProbeTable.getProbeSections();
203   if (ProbeSections.empty())
204     return;
205 
206   LLVM_DEBUG(MCPseudoProbeTable::DdgPrintIndent = 0);
207 
208   // Put out the probe.
209   ProbeSections.emit(MCOS);
210 }
211 
212 static StringRef getProbeFNameForGUID(const GUIDProbeFunctionMap &GUID2FuncMAP,
213                                       uint64_t GUID) {
214   auto It = GUID2FuncMAP.find(GUID);
215   assert(It != GUID2FuncMAP.end() &&
216          "Probe function must exist for a valid GUID");
217   return It->second.FuncName;
218 }
219 
220 void MCPseudoProbeFuncDesc::print(raw_ostream &OS) {
221   OS << "GUID: " << FuncGUID << " Name: " << FuncName << "\n";
222   OS << "Hash: " << FuncHash << "\n";
223 }
224 
225 void MCDecodedPseudoProbe::getInlineContext(
226     SmallVectorImpl<MCPseduoProbeFrameLocation> &ContextStack,
227     const GUIDProbeFunctionMap &GUID2FuncMAP) const {
228   uint32_t Begin = ContextStack.size();
229   MCDecodedPseudoProbeInlineTree *Cur = InlineTree;
230   // It will add the string of each node's inline site during iteration.
231   // Note that it won't include the probe's belonging function(leaf location)
232   while (Cur->hasInlineSite()) {
233     StringRef FuncName =
234         getProbeFNameForGUID(GUID2FuncMAP, std::get<0>(Cur->ISite));
235     ContextStack.emplace_back(
236         MCPseduoProbeFrameLocation(FuncName, std::get<1>(Cur->ISite)));
237     Cur = static_cast<MCDecodedPseudoProbeInlineTree *>(Cur->Parent);
238   }
239   // Make the ContextStack in caller-callee order
240   std::reverse(ContextStack.begin() + Begin, ContextStack.end());
241 }
242 
243 std::string MCDecodedPseudoProbe::getInlineContextStr(
244     const GUIDProbeFunctionMap &GUID2FuncMAP) const {
245   std::ostringstream OContextStr;
246   SmallVector<MCPseduoProbeFrameLocation, 16> ContextStack;
247   getInlineContext(ContextStack, GUID2FuncMAP);
248   for (auto &Cxt : ContextStack) {
249     if (OContextStr.str().size())
250       OContextStr << " @ ";
251     OContextStr << Cxt.first.str() << ":" << Cxt.second;
252   }
253   return OContextStr.str();
254 }
255 
256 static const char *PseudoProbeTypeStr[3] = {"Block", "IndirectCall",
257                                             "DirectCall"};
258 
259 void MCDecodedPseudoProbe::print(raw_ostream &OS,
260                                  const GUIDProbeFunctionMap &GUID2FuncMAP,
261                                  bool ShowName) const {
262   OS << "FUNC: ";
263   if (ShowName) {
264     StringRef FuncName = getProbeFNameForGUID(GUID2FuncMAP, Guid);
265     OS << FuncName.str() << " ";
266   } else {
267     OS << Guid << " ";
268   }
269   OS << "Index: " << Index << "  ";
270   OS << "Type: " << PseudoProbeTypeStr[static_cast<uint8_t>(Type)] << "  ";
271   std::string InlineContextStr = getInlineContextStr(GUID2FuncMAP);
272   if (InlineContextStr.size()) {
273     OS << "Inlined: @ ";
274     OS << InlineContextStr;
275   }
276   OS << "\n";
277 }
278 
279 template <typename T> ErrorOr<T> MCPseudoProbeDecoder::readUnencodedNumber() {
280   if (Data + sizeof(T) > End) {
281     return std::error_code();
282   }
283   T Val = endian::readNext<T, little, unaligned>(Data);
284   return ErrorOr<T>(Val);
285 }
286 
287 template <typename T> ErrorOr<T> MCPseudoProbeDecoder::readUnsignedNumber() {
288   unsigned NumBytesRead = 0;
289   uint64_t Val = decodeULEB128(Data, &NumBytesRead);
290   if (Val > std::numeric_limits<T>::max() || (Data + NumBytesRead > End)) {
291     return std::error_code();
292   }
293   Data += NumBytesRead;
294   return ErrorOr<T>(static_cast<T>(Val));
295 }
296 
297 template <typename T> ErrorOr<T> MCPseudoProbeDecoder::readSignedNumber() {
298   unsigned NumBytesRead = 0;
299   int64_t Val = decodeSLEB128(Data, &NumBytesRead);
300   if (Val > std::numeric_limits<T>::max() || (Data + NumBytesRead > End)) {
301     return std::error_code();
302   }
303   Data += NumBytesRead;
304   return ErrorOr<T>(static_cast<T>(Val));
305 }
306 
307 ErrorOr<StringRef> MCPseudoProbeDecoder::readString(uint32_t Size) {
308   StringRef Str(reinterpret_cast<const char *>(Data), Size);
309   if (Data + Size > End) {
310     return std::error_code();
311   }
312   Data += Size;
313   return ErrorOr<StringRef>(Str);
314 }
315 
316 bool MCPseudoProbeDecoder::buildGUID2FuncDescMap(const uint8_t *Start,
317                                                  std::size_t Size) {
318   // The pseudo_probe_desc section has a format like:
319   // .section .pseudo_probe_desc,"",@progbits
320   // .quad -5182264717993193164   // GUID
321   // .quad 4294967295             // Hash
322   // .uleb 3                      // Name size
323   // .ascii "foo"                 // Name
324   // .quad -2624081020897602054
325   // .quad 174696971957
326   // .uleb 34
327   // .ascii "main"
328 
329   Data = Start;
330   End = Data + Size;
331 
332   while (Data < End) {
333     auto ErrorOrGUID = readUnencodedNumber<uint64_t>();
334     if (!ErrorOrGUID)
335       return false;
336 
337     auto ErrorOrHash = readUnencodedNumber<uint64_t>();
338     if (!ErrorOrHash)
339       return false;
340 
341     auto ErrorOrNameSize = readUnsignedNumber<uint32_t>();
342     if (!ErrorOrNameSize)
343       return false;
344     uint32_t NameSize = std::move(*ErrorOrNameSize);
345 
346     auto ErrorOrName = readString(NameSize);
347     if (!ErrorOrName)
348       return false;
349 
350     uint64_t GUID = std::move(*ErrorOrGUID);
351     uint64_t Hash = std::move(*ErrorOrHash);
352     StringRef Name = std::move(*ErrorOrName);
353 
354     // Initialize PseudoProbeFuncDesc and populate it into GUID2FuncDescMap
355     GUID2FuncDescMap.emplace(GUID, MCPseudoProbeFuncDesc(GUID, Hash, Name));
356   }
357   assert(Data == End && "Have unprocessed data in pseudo_probe_desc section");
358   return true;
359 }
360 
361 bool MCPseudoProbeDecoder::buildAddress2ProbeMap(
362     MCDecodedPseudoProbeInlineTree *Cur, uint64_t &LastAddr,
363     std::unordered_set<uint64_t> &GuildFilter) {
364   // The pseudo_probe section encodes an inline forest and each tree has a
365   // format like:
366   //  FUNCTION BODY (one for each uninlined function present in the text
367   //  section)
368   //     GUID (uint64)
369   //         GUID of the function
370   //     NPROBES (ULEB128)
371   //         Number of probes originating from this function.
372   //     NUM_INLINED_FUNCTIONS (ULEB128)
373   //         Number of callees inlined into this function, aka number of
374   //         first-level inlinees
375   //     PROBE RECORDS
376   //         A list of NPROBES entries. Each entry contains:
377   //           INDEX (ULEB128)
378   //           TYPE (uint4)
379   //             0 - block probe, 1 - indirect call, 2 - direct call
380   //           ATTRIBUTE (uint3)
381   //             1 - tail call, 2 - dangling
382   //           ADDRESS_TYPE (uint1)
383   //             0 - code address, 1 - address delta
384   //           CODE_ADDRESS (uint64 or ULEB128)
385   //             code address or address delta, depending on Flag
386   //     INLINED FUNCTION RECORDS
387   //         A list of NUM_INLINED_FUNCTIONS entries describing each of the
388   //         inlined callees.  Each record contains:
389   //           INLINE SITE
390   //             Index of the callsite probe (ULEB128)
391   //           FUNCTION BODY
392   //             A FUNCTION BODY entry describing the inlined function.
393 
394   uint32_t Index = 0;
395   if (Cur == &DummyInlineRoot) {
396     // Use a sequential id for top level inliner.
397     Index = Cur->getChildren().size();
398   } else {
399     // Read inline site for inlinees
400     auto ErrorOrIndex = readUnsignedNumber<uint32_t>();
401     if (!ErrorOrIndex)
402       return false;
403     Index = std::move(*ErrorOrIndex);
404   }
405 
406   // Read guid
407   auto ErrorOrCurGuid = readUnencodedNumber<uint64_t>();
408   if (!ErrorOrCurGuid)
409     return false;
410   uint64_t Guid = std::move(*ErrorOrCurGuid);
411 
412   // Decide if top-level node should be disgarded.
413   if (Cur == &DummyInlineRoot && !GuildFilter.empty() &&
414       !GuildFilter.count(Guid))
415     Cur = nullptr;
416 
417   // If the incoming node is null, all its children nodes should be disgarded.
418   if (Cur) {
419     // Switch/add to a new tree node(inlinee)
420     Cur = Cur->getOrAddNode(std::make_tuple(Cur->Guid, Index));
421     Cur->Guid = Guid;
422   }
423 
424   // Read number of probes in the current node.
425   auto ErrorOrNodeCount = readUnsignedNumber<uint32_t>();
426   if (!ErrorOrNodeCount)
427     return false;
428   uint32_t NodeCount = std::move(*ErrorOrNodeCount);
429   // Read number of direct inlinees
430   auto ErrorOrCurChildrenToProcess = readUnsignedNumber<uint32_t>();
431   if (!ErrorOrCurChildrenToProcess)
432     return false;
433   // Read all probes in this node
434   for (std::size_t I = 0; I < NodeCount; I++) {
435     // Read index
436     auto ErrorOrIndex = readUnsignedNumber<uint32_t>();
437     if (!ErrorOrIndex)
438       return false;
439     uint32_t Index = std::move(*ErrorOrIndex);
440     // Read type | flag.
441     auto ErrorOrValue = readUnencodedNumber<uint8_t>();
442     if (!ErrorOrValue)
443       return false;
444     uint8_t Value = std::move(*ErrorOrValue);
445     uint8_t Kind = Value & 0xf;
446     uint8_t Attr = (Value & 0x70) >> 4;
447     // Read address
448     uint64_t Addr = 0;
449     if (Value & 0x80) {
450       auto ErrorOrOffset = readSignedNumber<int64_t>();
451       if (!ErrorOrOffset)
452         return false;
453       int64_t Offset = std::move(*ErrorOrOffset);
454       Addr = LastAddr + Offset;
455     } else {
456       auto ErrorOrAddr = readUnencodedNumber<int64_t>();
457       if (!ErrorOrAddr)
458         return false;
459       Addr = std::move(*ErrorOrAddr);
460     }
461 
462     if (Cur) {
463       // Populate Address2ProbesMap
464       auto &Probes = Address2ProbesMap[Addr];
465       Probes.emplace_back(Addr, Cur->Guid, Index, PseudoProbeType(Kind), Attr,
466                           Cur);
467       Cur->addProbes(&Probes.back());
468     }
469     LastAddr = Addr;
470   }
471 
472   uint32_t ChildrenToProcess = std::move(*ErrorOrCurChildrenToProcess);
473   for (uint32_t I = 0; I < ChildrenToProcess; I++) {
474     buildAddress2ProbeMap(Cur, LastAddr, GuildFilter);
475   }
476 
477   return true;
478 }
479 
480 bool MCPseudoProbeDecoder::buildAddress2ProbeMap(
481     const uint8_t *Start, std::size_t Size,
482     std::unordered_set<uint64_t> &GuildFilter) {
483   Data = Start;
484   End = Data + Size;
485   uint64_t LastAddr = 0;
486   while (Data < End)
487     buildAddress2ProbeMap(&DummyInlineRoot, LastAddr, GuildFilter);
488   assert(Data == End && "Have unprocessed data in pseudo_probe section");
489   return true;
490 }
491 
492 bool MCPseudoProbeDecoder::buildAddress2ProbeMap(const uint8_t *Start,
493                                                  std::size_t Size) {
494   std::unordered_set<uint64_t> GuildFilter;
495   return buildAddress2ProbeMap(Start, Size, GuildFilter);
496 }
497 
498 void MCPseudoProbeDecoder::printGUID2FuncDescMap(raw_ostream &OS) {
499   OS << "Pseudo Probe Desc:\n";
500   // Make the output deterministic
501   std::map<uint64_t, MCPseudoProbeFuncDesc> OrderedMap(GUID2FuncDescMap.begin(),
502                                                        GUID2FuncDescMap.end());
503   for (auto &I : OrderedMap) {
504     I.second.print(OS);
505   }
506 }
507 
508 void MCPseudoProbeDecoder::printProbeForAddress(raw_ostream &OS,
509                                                 uint64_t Address) {
510   auto It = Address2ProbesMap.find(Address);
511   if (It != Address2ProbesMap.end()) {
512     for (auto &Probe : It->second) {
513       OS << " [Probe]:\t";
514       Probe.print(OS, GUID2FuncDescMap, true);
515     }
516   }
517 }
518 
519 void MCPseudoProbeDecoder::printProbesForAllAddresses(raw_ostream &OS) {
520   std::vector<uint64_t> Addresses;
521   for (auto Entry : Address2ProbesMap)
522     Addresses.push_back(Entry.first);
523   std::sort(Addresses.begin(), Addresses.end());
524   for (auto K : Addresses) {
525     OS << "Address:\t";
526     OS << K;
527     OS << "\n";
528     printProbeForAddress(OS, K);
529   }
530 }
531 
532 const MCDecodedPseudoProbe *
533 MCPseudoProbeDecoder::getCallProbeForAddr(uint64_t Address) const {
534   auto It = Address2ProbesMap.find(Address);
535   if (It == Address2ProbesMap.end())
536     return nullptr;
537   const auto &Probes = It->second;
538 
539   const MCDecodedPseudoProbe *CallProbe = nullptr;
540   for (const auto &Probe : Probes) {
541     if (Probe.isCall()) {
542       assert(!CallProbe &&
543              "There should be only one call probe corresponding to address "
544              "which is a callsite.");
545       CallProbe = &Probe;
546     }
547   }
548   return CallProbe;
549 }
550 
551 const MCPseudoProbeFuncDesc *
552 MCPseudoProbeDecoder::getFuncDescForGUID(uint64_t GUID) const {
553   auto It = GUID2FuncDescMap.find(GUID);
554   assert(It != GUID2FuncDescMap.end() && "Function descriptor doesn't exist");
555   return &It->second;
556 }
557 
558 void MCPseudoProbeDecoder::getInlineContextForProbe(
559     const MCDecodedPseudoProbe *Probe,
560     SmallVectorImpl<MCPseduoProbeFrameLocation> &InlineContextStack,
561     bool IncludeLeaf) const {
562   Probe->getInlineContext(InlineContextStack, GUID2FuncDescMap);
563   if (!IncludeLeaf)
564     return;
565   // Note that the context from probe doesn't include leaf frame,
566   // hence we need to retrieve and prepend leaf if requested.
567   const auto *FuncDesc = getFuncDescForGUID(Probe->getGuid());
568   InlineContextStack.emplace_back(
569       MCPseduoProbeFrameLocation(FuncDesc->FuncName, Probe->getIndex()));
570 }
571 
572 const MCPseudoProbeFuncDesc *MCPseudoProbeDecoder::getInlinerDescForProbe(
573     const MCDecodedPseudoProbe *Probe) const {
574   MCDecodedPseudoProbeInlineTree *InlinerNode = Probe->getInlineTreeNode();
575   if (!InlinerNode->hasInlineSite())
576     return nullptr;
577   return getFuncDescForGUID(std::get<0>(InlinerNode->ISite));
578 }
579