Skip to content

Commit abdb2d2

Browse files
committedSep 1, 2015
[MC] Allow MCObjectWriter's output stream to be swapped out
There are occasions where it is useful to consider the entirety of the contents of a section. For example, compressed debug info needs the entire section available before it can compress it and write it out. The compressed debug info scenario was previously implemented by mirroring the implementation of writeSectionData in the ELFObjectWriter. Instead, allow the output stream to be swapped on demand. This lets callers redirect the output stream to a more convenient location before it hits the object file. No functionality change is intended. Differential Revision: http://reviews.llvm.org/D12509 llvm-svn: 246554
1 parent 491d3bf commit abdb2d2

File tree

4 files changed

+73
-93
lines changed

4 files changed

+73
-93
lines changed
 

‎llvm/include/llvm/MC/MCObjectWriter.h

+15-14
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@ class MCObjectWriter {
4040
MCObjectWriter(const MCObjectWriter &) = delete;
4141
void operator=(const MCObjectWriter &) = delete;
4242

43-
protected:
44-
raw_pwrite_stream &OS;
43+
raw_pwrite_stream *OS;
4544

45+
protected:
4646
unsigned IsLittleEndian : 1;
4747

4848
protected: // Can only create subclasses.
4949
MCObjectWriter(raw_pwrite_stream &OS, bool IsLittleEndian)
50-
: OS(OS), IsLittleEndian(IsLittleEndian) {}
50+
: OS(&OS), IsLittleEndian(IsLittleEndian) {}
5151

5252
public:
5353
virtual ~MCObjectWriter();
@@ -57,7 +57,8 @@ class MCObjectWriter {
5757

5858
bool isLittleEndian() const { return IsLittleEndian; }
5959

60-
raw_ostream &getStream() { return OS; }
60+
raw_pwrite_stream &getStream() { return *OS; }
61+
void setStream(raw_pwrite_stream &NewOS) { OS = &NewOS; }
6162

6263
/// \name High-Level API
6364
/// @{
@@ -113,30 +114,30 @@ class MCObjectWriter {
113114
/// \name Binary Output
114115
/// @{
115116

116-
void write8(uint8_t Value) { OS << char(Value); }
117+
void write8(uint8_t Value) { *OS << char(Value); }
117118

118119
void writeLE16(uint16_t Value) {
119-
support::endian::Writer<support::little>(OS).write(Value);
120+
support::endian::Writer<support::little>(*OS).write(Value);
120121
}
121122

122123
void writeLE32(uint32_t Value) {
123-
support::endian::Writer<support::little>(OS).write(Value);
124+
support::endian::Writer<support::little>(*OS).write(Value);
124125
}
125126

126127
void writeLE64(uint64_t Value) {
127-
support::endian::Writer<support::little>(OS).write(Value);
128+
support::endian::Writer<support::little>(*OS).write(Value);
128129
}
129130

130131
void writeBE16(uint16_t Value) {
131-
support::endian::Writer<support::big>(OS).write(Value);
132+
support::endian::Writer<support::big>(*OS).write(Value);
132133
}
133134

134135
void writeBE32(uint32_t Value) {
135-
support::endian::Writer<support::big>(OS).write(Value);
136+
support::endian::Writer<support::big>(*OS).write(Value);
136137
}
137138

138139
void writeBE64(uint64_t Value) {
139-
support::endian::Writer<support::big>(OS).write(Value);
140+
support::endian::Writer<support::big>(*OS).write(Value);
140141
}
141142

142143
void write16(uint16_t Value) {
@@ -164,9 +165,9 @@ class MCObjectWriter {
164165
const char Zeros[16] = {0};
165166

166167
for (unsigned i = 0, e = N / 16; i != e; ++i)
167-
OS << StringRef(Zeros, 16);
168+
*OS << StringRef(Zeros, 16);
168169

169-
OS << StringRef(Zeros, N % 16);
170+
*OS << StringRef(Zeros, N % 16);
170171
}
171172

172173
void writeBytes(const SmallVectorImpl<char> &ByteVec,
@@ -180,7 +181,7 @@ class MCObjectWriter {
180181
assert(
181182
(ZeroFillSize == 0 || Str.size() <= ZeroFillSize) &&
182183
"data size greater than fill size, unexpected large write will occur");
183-
OS << Str;
184+
*OS << Str;
184185
if (ZeroFillSize)
185186
WriteZeros(ZeroFillSize - Str.size());
186187
}

‎llvm/lib/MC/ELFObjectWriter.cpp

+32-55
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,9 @@ class ELFObjectWriter : public MCObjectWriter {
157157

158158
template <typename T> void write(T Val) {
159159
if (IsLittleEndian)
160-
support::endian::Writer<support::little>(OS).write(Val);
160+
support::endian::Writer<support::little>(getStream()).write(Val);
161161
else
162-
support::endian::Writer<support::big>(OS).write(Val);
162+
support::endian::Writer<support::big>(getStream()).write(Val);
163163
}
164164

165165
void writeHeader(const MCAssembler &Asm);
@@ -232,7 +232,7 @@ class ELFObjectWriter : public MCObjectWriter {
232232
}
233233

234234
void ELFObjectWriter::align(unsigned Alignment) {
235-
uint64_t Padding = OffsetToAlignment(OS.tell(), Alignment);
235+
uint64_t Padding = OffsetToAlignment(getStream().tell(), Alignment);
236236
WriteZeros(Padding);
237237
}
238238

@@ -764,7 +764,7 @@ void ELFObjectWriter::computeSymbolTable(
764764
SymbolTableIndex = addToSectionTable(SymtabSection);
765765

766766
align(SymtabSection->getAlignment());
767-
uint64_t SecStart = OS.tell();
767+
uint64_t SecStart = getStream().tell();
768768

769769
// The first entry is the undefined symbol entry.
770770
Writer.writeSymbol(0, 0, 0, 0, 0, 0, false);
@@ -911,7 +911,7 @@ void ELFObjectWriter::computeSymbolTable(
911911
assert(MSD.Symbol->getBinding() != ELF::STB_LOCAL);
912912
}
913913

914-
uint64_t SecEnd = OS.tell();
914+
uint64_t SecEnd = getStream().tell();
915915
SectionOffsets[SymtabSection] = std::make_pair(SecStart, SecEnd);
916916

917917
ArrayRef<uint32_t> ShndxIndexes = Writer.getShndxIndexes();
@@ -921,12 +921,12 @@ void ELFObjectWriter::computeSymbolTable(
921921
}
922922
assert(SymtabShndxSectionIndex != 0);
923923

924-
SecStart = OS.tell();
924+
SecStart = getStream().tell();
925925
const MCSectionELF *SymtabShndxSection =
926926
SectionTable[SymtabShndxSectionIndex - 1];
927927
for (uint32_t Index : ShndxIndexes)
928928
write(Index);
929-
SecEnd = OS.tell();
929+
SecEnd = getStream().tell();
930930
SectionOffsets[SymtabShndxSection] = std::make_pair(SecStart, SecEnd);
931931
}
932932

@@ -957,31 +957,6 @@ ELFObjectWriter::createRelocationSection(MCContext &Ctx,
957957
return RelaSection;
958958
}
959959

960-
static SmallVector<char, 128>
961-
getUncompressedData(const MCAsmLayout &Layout,
962-
const MCSection::FragmentListType &Fragments) {
963-
SmallVector<char, 128> UncompressedData;
964-
for (const MCFragment &F : Fragments) {
965-
const SmallVectorImpl<char> *Contents;
966-
switch (F.getKind()) {
967-
case MCFragment::FT_Data:
968-
Contents = &cast<MCDataFragment>(F).getContents();
969-
break;
970-
case MCFragment::FT_Dwarf:
971-
Contents = &cast<MCDwarfLineAddrFragment>(F).getContents();
972-
break;
973-
case MCFragment::FT_DwarfFrame:
974-
Contents = &cast<MCDwarfCallFrameFragment>(F).getContents();
975-
break;
976-
default:
977-
llvm_unreachable(
978-
"Not expecting any other fragment types in a debug_* section");
979-
}
980-
UncompressedData.append(Contents->begin(), Contents->end());
981-
}
982-
return UncompressedData;
983-
}
984-
985960
// Include the debug info compression header:
986961
// "ZLIB" followed by 8 bytes representing the uncompressed size of the section,
987962
// useful for consumers to preallocate a buffer to decompress into.
@@ -1016,27 +991,29 @@ void ELFObjectWriter::writeSectionData(const MCAssembler &Asm, MCSection &Sec,
1016991
return;
1017992
}
1018993

1019-
// Gather the uncompressed data from all the fragments.
1020-
const MCSection::FragmentListType &Fragments = Section.getFragmentList();
1021-
SmallVector<char, 128> UncompressedData =
1022-
getUncompressedData(Layout, Fragments);
994+
SmallVector<char, 128> UncompressedData;
995+
raw_svector_ostream VecOS(UncompressedData);
996+
raw_pwrite_stream &OldStream = getStream();
997+
setStream(VecOS);
998+
Asm.writeSectionData(&Section, Layout);
999+
setStream(OldStream);
10231000

10241001
SmallVector<char, 128> CompressedContents;
10251002
zlib::Status Success = zlib::compress(
10261003
StringRef(UncompressedData.data(), UncompressedData.size()),
10271004
CompressedContents);
10281005
if (Success != zlib::StatusOK) {
1029-
Asm.writeSectionData(&Section, Layout);
1006+
getStream() << UncompressedData;
10301007
return;
10311008
}
10321009

10331010
if (!prependCompressionHeader(UncompressedData.size(), CompressedContents)) {
1034-
Asm.writeSectionData(&Section, Layout);
1011+
getStream() << UncompressedData;
10351012
return;
10361013
}
10371014
Asm.getContext().renameELFSection(&Section,
10381015
(".z" + SectionName.drop_front(1)).str());
1039-
OS << CompressedContents;
1016+
getStream() << CompressedContents;
10401017
}
10411018

10421019
void ELFObjectWriter::WriteSecHdrEntry(uint32_t Name, uint32_t Type,
@@ -1100,7 +1077,7 @@ void ELFObjectWriter::writeRelocations(const MCAssembler &Asm,
11001077

11011078
const MCSectionELF *ELFObjectWriter::createStringTable(MCContext &Ctx) {
11021079
const MCSectionELF *StrtabSection = SectionTable[StringTableIndex - 1];
1103-
OS << StrTabBuilder.data();
1080+
getStream() << StrTabBuilder.data();
11041081
return StrtabSection;
11051082
}
11061083

@@ -1209,12 +1186,12 @@ void ELFObjectWriter::writeObject(MCAssembler &Asm,
12091186
align(Section.getAlignment());
12101187

12111188
// Remember the offset into the file for this section.
1212-
uint64_t SecStart = OS.tell();
1189+
uint64_t SecStart = getStream().tell();
12131190

12141191
const MCSymbolELF *SignatureSymbol = Section.getGroup();
12151192
writeSectionData(Asm, Section, Layout);
12161193

1217-
uint64_t SecEnd = OS.tell();
1194+
uint64_t SecEnd = getStream().tell();
12181195
SectionOffsets[&Section] = std::make_pair(SecStart, SecEnd);
12191196

12201197
MCSectionELF *RelSection = createRelocationSection(Ctx, Section);
@@ -1246,7 +1223,7 @@ void ELFObjectWriter::writeObject(MCAssembler &Asm,
12461223
align(Group->getAlignment());
12471224

12481225
// Remember the offset into the file for this section.
1249-
uint64_t SecStart = OS.tell();
1226+
uint64_t SecStart = getStream().tell();
12501227

12511228
const MCSymbol *SignatureSymbol = Group->getGroup();
12521229
assert(SignatureSymbol);
@@ -1256,7 +1233,7 @@ void ELFObjectWriter::writeObject(MCAssembler &Asm,
12561233
write(SecIndex);
12571234
}
12581235

1259-
uint64_t SecEnd = OS.tell();
1236+
uint64_t SecEnd = getStream().tell();
12601237
SectionOffsets[Group] = std::make_pair(SecStart, SecEnd);
12611238
}
12621239

@@ -1267,25 +1244,25 @@ void ELFObjectWriter::writeObject(MCAssembler &Asm,
12671244
align(RelSection->getAlignment());
12681245

12691246
// Remember the offset into the file for this section.
1270-
uint64_t SecStart = OS.tell();
1247+
uint64_t SecStart = getStream().tell();
12711248

12721249
writeRelocations(Asm, *RelSection->getAssociatedSection());
12731250

1274-
uint64_t SecEnd = OS.tell();
1251+
uint64_t SecEnd = getStream().tell();
12751252
SectionOffsets[RelSection] = std::make_pair(SecStart, SecEnd);
12761253
}
12771254

12781255
{
1279-
uint64_t SecStart = OS.tell();
1256+
uint64_t SecStart = getStream().tell();
12801257
const MCSectionELF *Sec = createStringTable(Ctx);
1281-
uint64_t SecEnd = OS.tell();
1258+
uint64_t SecEnd = getStream().tell();
12821259
SectionOffsets[Sec] = std::make_pair(SecStart, SecEnd);
12831260
}
12841261

12851262
uint64_t NaturalAlignment = is64Bit() ? 8 : 4;
12861263
align(NaturalAlignment);
12871264

1288-
const unsigned SectionHeaderOffset = OS.tell();
1265+
const unsigned SectionHeaderOffset = getStream().tell();
12891266

12901267
// ... then the section header table ...
12911268
writeSectionHeader(Layout, SectionIndexMap, SectionOffsets);
@@ -1301,19 +1278,19 @@ void ELFObjectWriter::writeObject(MCAssembler &Asm,
13011278
uint64_t Val = SectionHeaderOffset;
13021279
if (sys::IsLittleEndianHost != IsLittleEndian)
13031280
sys::swapByteOrder(Val);
1304-
OS.pwrite(reinterpret_cast<char *>(&Val), sizeof(Val),
1305-
offsetof(ELF::Elf64_Ehdr, e_shoff));
1281+
getStream().pwrite(reinterpret_cast<char *>(&Val), sizeof(Val),
1282+
offsetof(ELF::Elf64_Ehdr, e_shoff));
13061283
NumSectionsOffset = offsetof(ELF::Elf64_Ehdr, e_shnum);
13071284
} else {
13081285
uint32_t Val = SectionHeaderOffset;
13091286
if (sys::IsLittleEndianHost != IsLittleEndian)
13101287
sys::swapByteOrder(Val);
1311-
OS.pwrite(reinterpret_cast<char *>(&Val), sizeof(Val),
1312-
offsetof(ELF::Elf32_Ehdr, e_shoff));
1288+
getStream().pwrite(reinterpret_cast<char *>(&Val), sizeof(Val),
1289+
offsetof(ELF::Elf32_Ehdr, e_shoff));
13131290
NumSectionsOffset = offsetof(ELF::Elf32_Ehdr, e_shnum);
13141291
}
1315-
OS.pwrite(reinterpret_cast<char *>(&NumSections), sizeof(NumSections),
1316-
NumSectionsOffset);
1292+
getStream().pwrite(reinterpret_cast<char *>(&NumSections),
1293+
sizeof(NumSections), NumSectionsOffset);
13171294
}
13181295

13191296
bool ELFObjectWriter::isSymbolRefDifferenceFullyResolvedImpl(

‎llvm/lib/MC/MachObjectWriter.cpp

+20-19
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ void MachObjectWriter::writeHeader(MachO::HeaderFileType Type,
129129
// struct mach_header (28 bytes) or
130130
// struct mach_header_64 (32 bytes)
131131

132-
uint64_t Start = OS.tell();
132+
uint64_t Start = getStream().tell();
133133
(void) Start;
134134

135135
write32(is64Bit() ? MachO::MH_MAGIC_64 : MachO::MH_MAGIC);
@@ -144,8 +144,9 @@ void MachObjectWriter::writeHeader(MachO::HeaderFileType Type,
144144
if (is64Bit())
145145
write32(0); // reserved
146146

147-
assert(OS.tell() - Start ==
148-
(is64Bit()?sizeof(MachO::mach_header_64): sizeof(MachO::mach_header)));
147+
assert(
148+
getStream().tell() - Start ==
149+
(is64Bit() ? sizeof(MachO::mach_header_64) : sizeof(MachO::mach_header)));
149150
}
150151

151152
/// writeSegmentLoadCommand - Write a segment load command.
@@ -159,7 +160,7 @@ void MachObjectWriter::writeSegmentLoadCommand(
159160
// struct segment_command (56 bytes) or
160161
// struct segment_command_64 (72 bytes)
161162

162-
uint64_t Start = OS.tell();
163+
uint64_t Start = getStream().tell();
163164
(void) Start;
164165

165166
unsigned SegmentLoadCommandSize =
@@ -190,7 +191,7 @@ void MachObjectWriter::writeSegmentLoadCommand(
190191
write32(NumSections);
191192
write32(0); // flags
192193

193-
assert(OS.tell() - Start == SegmentLoadCommandSize);
194+
assert(getStream().tell() - Start == SegmentLoadCommandSize);
194195
}
195196

196197
void MachObjectWriter::writeSection(const MCAsmLayout &Layout,
@@ -210,7 +211,7 @@ void MachObjectWriter::writeSection(const MCAsmLayout &Layout,
210211
// struct section (68 bytes) or
211212
// struct section_64 (80 bytes)
212213

213-
uint64_t Start = OS.tell();
214+
uint64_t Start = getStream().tell();
214215
(void) Start;
215216

216217
writeBytes(Section.getSectionName(), 16);
@@ -234,8 +235,8 @@ void MachObjectWriter::writeSection(const MCAsmLayout &Layout,
234235
if (is64Bit())
235236
write32(0); // reserved3
236237

237-
assert(OS.tell() - Start == (is64Bit() ? sizeof(MachO::section_64) :
238-
sizeof(MachO::section)));
238+
assert(getStream().tell() - Start ==
239+
(is64Bit() ? sizeof(MachO::section_64) : sizeof(MachO::section)));
239240
}
240241

241242
void MachObjectWriter::writeSymtabLoadCommand(uint32_t SymbolOffset,
@@ -244,7 +245,7 @@ void MachObjectWriter::writeSymtabLoadCommand(uint32_t SymbolOffset,
244245
uint32_t StringTableSize) {
245246
// struct symtab_command (24 bytes)
246247

247-
uint64_t Start = OS.tell();
248+
uint64_t Start = getStream().tell();
248249
(void) Start;
249250

250251
write32(MachO::LC_SYMTAB);
@@ -254,7 +255,7 @@ void MachObjectWriter::writeSymtabLoadCommand(uint32_t SymbolOffset,
254255
write32(StringTableOffset);
255256
write32(StringTableSize);
256257

257-
assert(OS.tell() - Start == sizeof(MachO::symtab_command));
258+
assert(getStream().tell() - Start == sizeof(MachO::symtab_command));
258259
}
259260

260261
void MachObjectWriter::writeDysymtabLoadCommand(uint32_t FirstLocalSymbol,
@@ -267,7 +268,7 @@ void MachObjectWriter::writeDysymtabLoadCommand(uint32_t FirstLocalSymbol,
267268
uint32_t NumIndirectSymbols) {
268269
// struct dysymtab_command (80 bytes)
269270

270-
uint64_t Start = OS.tell();
271+
uint64_t Start = getStream().tell();
271272
(void) Start;
272273

273274
write32(MachO::LC_DYSYMTAB);
@@ -291,7 +292,7 @@ void MachObjectWriter::writeDysymtabLoadCommand(uint32_t FirstLocalSymbol,
291292
write32(0); // locreloff
292293
write32(0); // nlocrel
293294

294-
assert(OS.tell() - Start == sizeof(MachO::dysymtab_command));
295+
assert(getStream().tell() - Start == sizeof(MachO::dysymtab_command));
295296
}
296297

297298
MachObjectWriter::MachSymbolData *
@@ -387,15 +388,15 @@ void MachObjectWriter::writeNlist(MachSymbolData &MSD,
387388
void MachObjectWriter::writeLinkeditLoadCommand(uint32_t Type,
388389
uint32_t DataOffset,
389390
uint32_t DataSize) {
390-
uint64_t Start = OS.tell();
391+
uint64_t Start = getStream().tell();
391392
(void) Start;
392393

393394
write32(Type);
394395
write32(sizeof(MachO::linkedit_data_command));
395396
write32(DataOffset);
396397
write32(DataSize);
397398

398-
assert(OS.tell() - Start == sizeof(MachO::linkedit_data_command));
399+
assert(getStream().tell() - Start == sizeof(MachO::linkedit_data_command));
399400
}
400401

401402
static unsigned ComputeLinkerOptionsLoadCommandSize(
@@ -411,7 +412,7 @@ void MachObjectWriter::writeLinkerOptionsLoadCommand(
411412
const std::vector<std::string> &Options)
412413
{
413414
unsigned Size = ComputeLinkerOptionsLoadCommandSize(Options, is64Bit());
414-
uint64_t Start = OS.tell();
415+
uint64_t Start = getStream().tell();
415416
(void) Start;
416417

417418
write32(MachO::LC_LINKER_OPTION);
@@ -427,7 +428,7 @@ void MachObjectWriter::writeLinkerOptionsLoadCommand(
427428
// Pad to a multiple of the pointer size.
428429
writeBytes("", OffsetToAlignment(BytesWritten, is64Bit() ? 8 : 4));
429430

430-
assert(OS.tell() - Start == Size);
431+
assert(getStream().tell() - Start == Size);
431432
}
432433

433434
void MachObjectWriter::recordRelocation(MCAssembler &Asm,
@@ -906,12 +907,12 @@ void MachObjectWriter::writeObject(MCAssembler &Asm,
906907
// Write out the loh commands, if there is one.
907908
if (LOHSize) {
908909
#ifndef NDEBUG
909-
unsigned Start = OS.tell();
910+
unsigned Start = getStream().tell();
910911
#endif
911912
Asm.getLOHContainer().emit(*this, Layout);
912913
// Pad to a multiple of the pointer size.
913914
writeBytes("", OffsetToAlignment(LOHRawSize, is64Bit() ? 8 : 4));
914-
assert(OS.tell() - Start == LOHSize);
915+
assert(getStream().tell() - Start == LOHSize);
915916
}
916917

917918
// Write the symbol table data, if used.
@@ -947,7 +948,7 @@ void MachObjectWriter::writeObject(MCAssembler &Asm,
947948
writeNlist(Entry, Layout);
948949

949950
// Write the string table.
950-
OS << StringTable.data();
951+
getStream() << StringTable.data();
951952
}
952953
}
953954

‎llvm/lib/MC/WinCOFFObjectWriter.cpp

+6-5
Original file line numberDiff line numberDiff line change
@@ -1037,10 +1037,11 @@ void WinCOFFObjectWriter::writeObject(MCAssembler &Asm,
10371037
continue;
10381038

10391039
if ((*i)->Header.PointerToRawData != 0) {
1040-
assert(OS.tell() <= (*i)->Header.PointerToRawData &&
1040+
assert(getStream().tell() <= (*i)->Header.PointerToRawData &&
10411041
"Section::PointerToRawData is insane!");
10421042

1043-
unsigned SectionDataPadding = (*i)->Header.PointerToRawData - OS.tell();
1043+
unsigned SectionDataPadding =
1044+
(*i)->Header.PointerToRawData - getStream().tell();
10441045
assert(SectionDataPadding < 4 &&
10451046
"Should only need at most three bytes of padding!");
10461047

@@ -1050,7 +1051,7 @@ void WinCOFFObjectWriter::writeObject(MCAssembler &Asm,
10501051
}
10511052

10521053
if ((*i)->Relocations.size() > 0) {
1053-
assert(OS.tell() == (*i)->Header.PointerToRelocations &&
1054+
assert(getStream().tell() == (*i)->Header.PointerToRelocations &&
10541055
"Section::PointerToRelocations is insane!");
10551056

10561057
if ((*i)->Relocations.size() >= 0xffff) {
@@ -1071,14 +1072,14 @@ void WinCOFFObjectWriter::writeObject(MCAssembler &Asm,
10711072
}
10721073
}
10731074

1074-
assert(OS.tell() == Header.PointerToSymbolTable &&
1075+
assert(getStream().tell() == Header.PointerToSymbolTable &&
10751076
"Header::PointerToSymbolTable is insane!");
10761077

10771078
for (auto &Symbol : Symbols)
10781079
if (Symbol->getIndex() != -1)
10791080
WriteSymbol(*Symbol);
10801081

1081-
OS.write(Strings.data().data(), Strings.data().size());
1082+
getStream().write(Strings.data().data(), Strings.data().size());
10821083
}
10831084

10841085
MCWinCOFFObjectTargetWriter::MCWinCOFFObjectTargetWriter(unsigned Machine_)

0 commit comments

Comments
 (0)
Please sign in to comment.