diff --git a/lld/COFF/InputFiles.cpp b/lld/COFF/InputFiles.cpp --- a/lld/COFF/InputFiles.cpp +++ b/lld/COFF/InputFiles.cpp @@ -1067,9 +1067,9 @@ void BitcodeFile::parse() { std::vector> comdat(obj->getComdatTable().size()); for (size_t i = 0; i != obj->getComdatTable().size(); ++i) - // FIXME: lto::InputFile doesn't keep enough data to do correct comdat - // selection handling. - comdat[i] = symtab->addComdat(this, saver.save(obj->getComdatTable()[i])); + // FIXME: Check nodeduplicate + comdat[i] = + symtab->addComdat(this, saver.save(obj->getComdatTable()[i].first)); for (const lto::InputFile::Symbol &objSym : obj->symbols()) { StringRef symName = saver.save(objSym.getName()); int comdatIndex = objSym.getComdatIndex(); @@ -1090,7 +1090,7 @@ Symbol *alias = symtab->addUndefined(saver.save(fallback)); checkAndSetWeakAlias(symtab, this, sym, alias); } else if (comdatIndex != -1) { - if (symName == obj->getComdatTable()[comdatIndex]) { + if (symName == obj->getComdatTable()[comdatIndex].first) { sym = comdat[comdatIndex].first; if (cast(sym)->data == nullptr) cast(sym)->data = &fakeSC->repl; diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp --- a/lld/ELF/InputFiles.cpp +++ b/lld/ELF/InputFiles.cpp @@ -1744,9 +1744,12 @@ template void BitcodeFile::parse() { std::vector keptComdats; - for (StringRef s : obj->getComdatTable()) + for (std::pair s : obj->getComdatTable()) { keptComdats.push_back( - symtab->comdatGroups.try_emplace(CachedHashStringRef(s), this).second); + s.second == Comdat::NoDeduplicate || + symtab->comdatGroups.try_emplace(CachedHashStringRef(s.first), this) + .second); + } for (const lto::InputFile::Symbol &objSym : obj->symbols()) symbols.push_back(createBitcodeSymbol(keptComdats, objSym, *this)); diff --git a/lld/test/ELF/lto/comdat-nodeduplicate.ll b/lld/test/ELF/lto/comdat-nodeduplicate.ll new file mode 100644 --- /dev/null +++ b/lld/test/ELF/lto/comdat-nodeduplicate.ll @@ -0,0 +1,127 @@ +; REQUIRES: x86, shell + +;; Keep __profd_foo in a nodeduplicate comdat, despite a comdat of the same name +;; in a previous object file. + +;; Regular LTO + +; RUN: rm -rf %t && split-file %s %t +; RUN: llvm-as %t/a.ll -o %t/a.bc +; RUN: llvm-as %t/b.ll -o %t/b.bc +; RUN: llvm-as %t/c.ll -o %t/c.bc + +; RUN: ld.lld --save-temps -u foo %t/a.bc --start-lib %t/b.bc --end-lib -o %t/ab +; RUN: FileCheck %s --check-prefix=RESOL_AB < %t/ab.resolution.txt +; RUN: llvm-readelf -x .data %t/ab | FileCheck %s --check-prefix=DATA + +; RESOL_AB: -r={{.*}}b.bc,__profc_foo,pl{{$}} + +;; .data contains a.bc:data. b.bc:data and c.bc:data are discarded. +; DATA: 0x[[#%x,]] 01000000 00000000 ........ + +;; __profc_foo from c.bc is non-prevailing and thus discarded. +; RUN: ld.lld --save-temps -u foo -u c %t/a.bc --start-lib %t/b.bc %t/c.bc --end-lib -o %t/abc +; RUN: FileCheck %s --check-prefix=RESOL_ABC < %t/abc.resolution.txt +; RUN: llvm-readelf -x .data %t/abc | FileCheck %s --check-prefix=DATA + +; RESOL_ABC: -r={{.*}}b.bc,__profc_foo,pl{{$}} +; RESOL_ABC: -r={{.*}}c.bc,__profc_foo,{{$}} + +;; ThinLTO + +; RUN: rm -rf %t && split-file %s %t +; RUN: opt --module-summary %t/a.ll -o %t/a.bc +; RUN: opt --module-summary %t/b.ll -o %t/b.bc +; RUN: opt --module-summary %t/c.ll -o %t/c.bc + +; RUN: ld.lld --thinlto-index-only --save-temps -u foo %t/a.bc %t/b.bc -o %t/ab +; RUN: FileCheck %s --check-prefix=RESOL_AB < %t/ab.resolution.txt +; RUN: (llvm-dis < %t/b.bc && llvm-dis < %t/b.bc.thinlto.bc) | FileCheck %s --check-prefix=IR_AB +; RUN: ld.lld -u foo %t/a.bc %t/b.bc -o %t/ab +; RUN: llvm-readelf -x .data %t/ab | FileCheck %s --check-prefix=DATA + +; RUN: ld.lld --thinlto-index-only --save-temps -u foo %t/a.bc --start-lib %t/b.bc --end-lib -o %t/ab +; RUN: FileCheck %s --check-prefix=RESOL_AB < %t/ab.resolution.txt +; RUN: (llvm-dis < %t/b.bc && llvm-dis < %t/b.bc.thinlto.bc) | FileCheck %s --check-prefix=IR_AB +; RUN: ld.lld -u foo %t/a.bc --start-lib %t/b.bc --end-lib -o %t/ab +; RUN: llvm-readelf -x .data %t/ab | FileCheck %s --check-prefix=DATA + +; RUN: ld.lld --thinlto-index-only --save-temps -u foo -u c %t/a.bc --start-lib %t/b.bc %t/c.bc --end-lib -o %t/abc +; RUN: FileCheck %s --check-prefix=RESOL_ABC < %t/abc.resolution.txt +; RUN: (llvm-dis < %t/b.bc && llvm-dis < %t/b.bc.thinlto.bc) | FileCheck %s --check-prefix=IR_ABC +; RUN: ld.lld -u foo %t/a.bc --start-lib %t/b.bc %t/c.bc --end-lib -o %t/abc +; RUN: llvm-readelf -x .data %t/abc | FileCheck %s --check-prefix=DATA + +; IR_AB: gv: (name: "__profd_foo", {{.*}} guid = [[PROFD:[0-9]+]] +; IR_AB: gv: (name: "__profc_foo", {{.*}} guid = [[PROFC:[0-9]+]] + +;; Check extra attributes. b.bc:__profc_foo is prevailing, so it can be internalized. +; IR_AB: gv: (guid: [[PROFD]], summaries: (variable: (module: ^0, flags: (linkage: private, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 1, canAutoHide: 0), varFlags: (readonly: 0, writeonly: 0, constant: 0), +; IR_AB: gv: (guid: [[PROFC]], summaries: (variable: (module: ^0, flags: (linkage: internal, visibility: hidden, notEligibleToImport: 0, live: 1, dsoLocal: 1, canAutoHide: 0), varFlags: (readonly: 0, writeonly: 0, constant: 0)))) + +; IR_ABC: gv: (name: "__profd_foo", {{.*}} guid = [[PROFD:[0-9]+]] +; IR_ABC: gv: (name: "__profc_foo", {{.*}} guid = [[PROFC:[0-9]+]] + +;; b.bc:__profc_foo prevails c.bc:__profc_foo, so it is exported and therefore not internalized. +; IR_ABC: gv: (guid: [[PROFD]], summaries: (variable: (module: ^0, flags: (linkage: private, visibility: default, notEligibleToImport: 0, live: 0, dsoLocal: 1, canAutoHide: 0), varFlags: (readonly: 0, writeonly: 0, constant: 0), +; IR_ABC: gv: (guid: [[PROFC]], summaries: (variable: (module: ^0, flags: (linkage: weak, visibility: hidden, notEligibleToImport: 0, live: 1, dsoLocal: 1, canAutoHide: 0), varFlags: (readonly: 0, writeonly: 0, constant: 0)))) + +;--- a.ll +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +$__profc_foo = comdat nodeduplicate +@__profc_foo = private global i64 1, comdat, align 8 +@__profd_foo = private global i64* @__profc_foo, comdat($__profc_foo), align 8 + +declare void @b() + +define i64 @foo() { + %v = load i64, i64* @__profc_foo + %inc = add i64 1, %v + store i64 %inc, i64* @__profc_foo + ret i64 %inc +} + +define void @_start() { + call void @b() + ret void +} + +;--- b.ll +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +$__profc_foo = comdat nodeduplicate +@__profc_foo = weak hidden global i64 2, comdat, align 8 +@__profd_foo = private global i64* @__profc_foo, comdat($__profc_foo) + +define weak i64 @foo() { + %v = load i64, i64* @__profc_foo + %inc = add i64 1, %v + store i64 %inc, i64* @__profc_foo + ret i64 %inc +} + +define void @b() { + ret void +} + +;--- c.ll +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +$__profc_foo = comdat nodeduplicate +@__profc_foo = weak hidden global i64 3, comdat, align 8 +@__profd_foo = private global i64* @__profc_foo, comdat($__profc_foo) + +define weak i64 @foo() { + %v = load i64, i64* @__profc_foo + %inc = add i64 1, %v + store i64 %inc, i64* @__profc_foo + ret i64 %inc +} + +define void @c() { + ret void +} diff --git a/lld/wasm/InputFiles.cpp b/lld/wasm/InputFiles.cpp --- a/lld/wasm/InputFiles.cpp +++ b/lld/wasm/InputFiles.cpp @@ -758,8 +758,9 @@ } checkArch(t.getArch()); std::vector keptComdats; - for (StringRef s : obj->getComdatTable()) - keptComdats.push_back(symtab->addComdat(s)); + // TODO Support nodeduplicate https://bugs.llvm.org/show_bug.cgi?id=50531 + for (std::pair s : obj->getComdatTable()) + keptComdats.push_back(symtab->addComdat(s.first)); for (const lto::InputFile::Symbol &objSym : obj->symbols()) symbols.push_back(createBitcodeSymbol(keptComdats, objSym, *this)); diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h --- a/llvm/include/llvm/LTO/LTO.h +++ b/llvm/include/llvm/LTO/LTO.h @@ -119,7 +119,7 @@ StringRef TargetTriple, SourceFileName, COFFLinkerOpts; std::vector DependentLibraries; - std::vector ComdatTable; + std::vector> ComdatTable; public: ~InputFile(); @@ -172,7 +172,9 @@ StringRef getSourceFileName() const { return SourceFileName; } // Returns a table with all the comdats used by this file. - ArrayRef getComdatTable() const { return ComdatTable; } + ArrayRef> getComdatTable() const { + return ComdatTable; + } // Returns the only BitcodeModule from InputFile. BitcodeModule &getSingleBitcodeModule(); diff --git a/llvm/include/llvm/Object/IRSymtab.h b/llvm/include/llvm/Object/IRSymtab.h --- a/llvm/include/llvm/Object/IRSymtab.h +++ b/llvm/include/llvm/Object/IRSymtab.h @@ -26,6 +26,7 @@ #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/iterator_range.h" +#include "llvm/IR/Comdat.h" #include "llvm/IR/GlobalValue.h" #include "llvm/Object/SymbolicFile.h" #include "llvm/Support/Allocator.h" @@ -80,6 +81,9 @@ /// This is equivalent to an IR comdat. struct Comdat { Str Name; + + // llvm::Comdat::SelectionKind + Word SelectionKind; }; /// Contains the information needed by linkers for symbol resolution, as well as @@ -132,7 +136,7 @@ /// when the format changes, but it does not need to be incremented if a /// change to LLVM would cause it to create a different symbol table. Word Version; - enum { kCurrentVersion = 2 }; + enum { kCurrentVersion = 3 }; /// The producer's version string (LLVM_VERSION_STRING " " LLVM_REVISION). /// Consumers should rebuild the symbol table from IR if the producer's @@ -280,11 +284,13 @@ StringRef getSourceFileName() const { return str(header().SourceFileName); } /// Returns a table with all the comdats used by this file. - std::vector getComdatTable() const { - std::vector ComdatTable; + std::vector> + getComdatTable() const { + std::vector> ComdatTable; ComdatTable.reserve(Comdats.size()); for (auto C : Comdats) - ComdatTable.push_back(str(C.Name)); + ComdatTable.push_back({str(C.Name), llvm::Comdat::SelectionKind( + uint32_t(C.SelectionKind))}); return ComdatTable; } diff --git a/llvm/lib/Object/IRSymtab.cpp b/llvm/lib/Object/IRSymtab.cpp --- a/llvm/lib/Object/IRSymtab.cpp +++ b/llvm/lib/Object/IRSymtab.cpp @@ -199,6 +199,7 @@ storage::Comdat Comdat; setStr(Comdat.Name, Saver.save(Name)); + Comdat.SelectionKind = C->getSelectionKind(); Comdats.push_back(Comdat); } diff --git a/llvm/test/LTO/Resolution/X86/symtab.ll b/llvm/test/LTO/Resolution/X86/symtab.ll --- a/llvm/test/LTO/Resolution/X86/symtab.ll +++ b/llvm/test/LTO/Resolution/X86/symtab.ll @@ -18,7 +18,7 @@ } ; CHECK: D------X @fun2@8 -; CHECK-NEXT: comdat @fun2@8 +; CHECK-NEXT: comdat any @fun2@8 $fun2 = comdat any define x86_fastcallcc i32 @fun2(i32 inreg %a, i32 inreg %b) comdat { entry: @@ -52,7 +52,7 @@ @g8 = common global i32 0, align 8 ; CHECK: D------- _g9 -; CHECK-NEXT: comdat _g9 +; CHECK-NEXT: comdat any _g9 $g9 = comdat any @g9 = global i32 0, comdat @@ -64,7 +64,12 @@ ; CHECK-NOT: comdat @g11 = global i32 0, comdat($g10) +; CHECK: D------- _g12 +; CHECK-NEXT: comdat nodeduplicate _g12 +$g12 = comdat nodeduplicate +@g12 = global i32 0, comdat + ; CHECK: D--WI--- _a1 -; CHECK-NEXT: comdat _g9 +; CHECK-NEXT: comdat any _g9 ; CHECK-NEXT: fallback _g9 @a1 = weak alias i32, i32* @g9 diff --git a/llvm/test/Object/X86/irsymtab.ll b/llvm/test/Object/X86/irsymtab.ll --- a/llvm/test/Object/X86/irsymtab.ll +++ b/llvm/test/Object/X86/irsymtab.ll @@ -9,13 +9,13 @@ ; BCA: blob data = '\x02\x00\x00\x00\x06\x00\x00\x00\x08\x00\x00\x00L\x00\x00\x00\x01\x00\x00\x00X\x00\x00\x00\x00\x00\x00\x00X\x00\x00\x00\x02\x00\x00\x00\x88\x00\x00\x00\x00\x00\x00\x00\x0E\x00\x00\x00\x18\x00\x00\x00&\x00\x00\x00\x0B\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x88\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\xFF\xFF\xFF\xFF\x00$\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\xFF\xFF\xFF\xFF\x08$\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00' +; BCA-NEXT: blob data = '\x03\x00\x00\x00\x06\x00\x00\x00\x08\x00\x00\x00L\x00\x00\x00\x01\x00\x00\x00X\x00\x00\x00\x00\x00\x00\x00X\x00\x00\x00\x02\x00\x00\x00\x88\x00\x00\x00\x00\x00\x00\x00\x0E\x00\x00\x00\x18\x00\x00\x00&\x00\x00\x00\x0B\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x88\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\xFF\xFF\xFF\xFF\x00$\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\xFF\xFF\xFF\xFF\x08$\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00\x03\x00\x00\x00' ; BCA-NEXT: ; BCA-NEXT: blob data = 'foobarproducerx86_64-unknown-linux-gnuirsymtab.ll' ; BCA-NEXT: -; SYMTAB: version: 2 +; SYMTAB: version: 3 ; SYMTAB-NEXT: producer: producer ; SYMTAB-NEXT: target triple: x86_64-unknown-linux-gnu ; SYMTAB-NEXT: source filename: irsymtab.ll diff --git a/llvm/test/tools/gold/X86/comdat-nodeduplicate.ll b/llvm/test/tools/gold/X86/comdat-nodeduplicate.ll new file mode 100644 --- /dev/null +++ b/llvm/test/tools/gold/X86/comdat-nodeduplicate.ll @@ -0,0 +1,100 @@ +;; Keep __profd_foo in a nodeduplicate comdat, despite a comdat of the same name +;; in a previous object file. + +;; Regular LTO + +; RUN: rm -rf %t && split-file %s %t +; RUN: llvm-as %t/a.ll -o %t/a.bc +; RUN: llvm-as %t/b.ll -o %t/b.bc +; RUN: llvm-as %t/c.ll -o %t/c.bc + +; RUN: %gold -plugin %llvmshlibdir/LLVMgold%shlibext --plugin-opt=save-temps \ +; RUN: -u foo %t/a.bc --start-lib %t/b.bc --end-lib -o %t/ab + +; RUN: FileCheck %s --check-prefix=RESOL_AB < %t/ab.resolution.txt +; RUN: llvm-readelf -x .data %t/ab | FileCheck %s --check-prefix=DATA + +; RESOL_AB: -r={{.*}}b.bc,__profc_foo,pl{{$}} + +;; .data contains a.bc:data. b.bc:data and c.bc:data are discarded. +; DATA: 0x[[#%x,]] 01000000 00000000 ........ + +;; __profc_foo from c.bc is non-prevailing and thus discarded. +; RUN: %gold -plugin %llvmshlibdir/LLVMgold%shlibext --plugin-opt=save-temps \ +; RUN: -u foo -u c %t/a.bc --start-lib %t/b.bc %t/c.bc --end-lib -o %t/abc +; RUN: FileCheck %s --check-prefix=RESOL_ABC < %t/abc.resolution.txt +; RUN: llvm-readelf -x .data %t/abc | FileCheck %s --check-prefix=DATA + +; RESOL_ABC: -r={{.*}}b.bc,__profc_foo,pl{{$}} +; RESOL_ABC: -r={{.*}}c.bc,__profc_foo,l{{$}} + +;; ThinLTO + +; RUN: rm -rf %t && split-file %s %t +; RUN: opt --module-summary %t/a.ll -o %t/a.bc +; RUN: opt --module-summary %t/b.ll -o %t/b.bc +; RUN: opt --module-summary %t/c.ll -o %t/c.bc + +; RUN: %gold -plugin %llvmshlibdir/LLVMgold%shlibext \ +; RUN: -u foo %t/a.bc --start-lib %t/b.bc %t/c.bc --end-lib -o %t/abc +; RUN: llvm-readelf -x .data %t/abc | FileCheck %s --check-prefix=DATA + +;--- a.ll +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +$__profc_foo = comdat nodeduplicate +@__profc_foo = private global i64 1, comdat, align 8 +@__profd_foo = private global i64* @__profc_foo, comdat($__profc_foo), align 8 + +declare void @b() + +define i64 @foo() { + %v = load i64, i64* @__profc_foo + %inc = add i64 1, %v + store i64 %inc, i64* @__profc_foo + ret i64 %inc +} + +define void @_start() { + call void @b() + ret void +} + +;--- b.ll +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +$__profc_foo = comdat nodeduplicate +@__profc_foo = weak hidden global i64 2, comdat, align 8 +@__profd_foo = private global i64* @__profc_foo, comdat($__profc_foo) + +define weak i64 @foo() { + %v = load i64, i64* @__profc_foo + %inc = add i64 1, %v + store i64 %inc, i64* @__profc_foo + ret i64 %inc +} + +define void @b() { + ret void +} + +;--- c.ll +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +$__profc_foo = comdat nodeduplicate +@__profc_foo = weak hidden global i64 3, comdat, align 8 +@__profd_foo = private global i64* @__profc_foo, comdat($__profc_foo) + +define weak i64 @foo() { + %v = load i64, i64* @__profc_foo + %inc = add i64 1, %v + store i64 %inc, i64* @__profc_foo + ret i64 %inc +} + +define void @c() { + ret void +} diff --git a/llvm/tools/gold/gold-plugin.cpp b/llvm/tools/gold/gold-plugin.cpp --- a/llvm/tools/gold/gold-plugin.cpp +++ b/llvm/tools/gold/gold-plugin.cpp @@ -623,8 +623,10 @@ sym.comdat_key = nullptr; int CI = Sym.getComdatIndex(); if (CI != -1) { - StringRef C = Obj->getComdatTable()[CI]; - sym.comdat_key = strdup(C.str().c_str()); + // Not setting comdat_key for nodeduplicate ensuress we don't deduplicate. + std::pair C = Obj->getComdatTable()[CI]; + if (C.second != Comdat::NoDeduplicate) + sym.comdat_key = strdup(C.first.str().c_str()); } sym.resolution = LDPR_UNKNOWN; diff --git a/llvm/tools/llvm-lto2/llvm-lto2.cpp b/llvm/tools/llvm-lto2/llvm-lto2.cpp --- a/llvm/tools/llvm-lto2/llvm-lto2.cpp +++ b/llvm/tools/llvm-lto2/llvm-lto2.cpp @@ -418,7 +418,8 @@ outs() << '\n'; } - std::vector ComdatTable = Input->getComdatTable(); + ArrayRef> ComdatTable = + Input->getComdatTable(); for (const InputFile::Symbol &Sym : Input->symbols()) { switch (Sym.getVisibility()) { case GlobalValue::HiddenVisibility: @@ -447,8 +448,27 @@ << Sym.getCommonAlignment() << '\n'; int Comdat = Sym.getComdatIndex(); - if (Comdat != -1) - outs() << " comdat " << ComdatTable[Comdat] << '\n'; + if (Comdat != -1) { + outs() << " comdat "; + switch (ComdatTable[Comdat].second) { + case Comdat::Any: + outs() << "any"; + break; + case Comdat::ExactMatch: + outs() << "exactmatch"; + break; + case Comdat::Largest: + outs() << "largest"; + break; + case Comdat::NoDeduplicate: + outs() << "nodeduplicate"; + break; + case Comdat::SameSize: + outs() << "samesize"; + break; + } + outs() << ' ' << ComdatTable[Comdat].first << '\n'; + } if (TT.isOSBinFormatCOFF() && Sym.isWeak() && Sym.isIndirect()) outs() << " fallback " << Sym.getCOFFWeakExternalFallback() << '\n';