Index: test/wasm/init-array-empty.ll =================================================================== --- /dev/null +++ test/wasm/init-array-empty.ll @@ -0,0 +1,74 @@ +; RUN: llc -filetype=obj %s -o %t.o +; RUN: lld -flavor wasm -o %t.wasm %t.o +; RUN: obj2yaml %t.wasm | FileCheck %s + +; ==== Generated from ==== +; extern void (*const __init_array_start)(void), (*const __init_array_end)(void); +; +; typedef unsigned long uintptr_t; +; extern "C" void _start(void) { +; for (void (*const *i)(void) = &__init_array_start; +; (uintptr_t)i < (uintptr_t)&__init_array_end; +; ++i) { +; (*i)(); +; } +; } +; ======================== + + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +@__init_array_start = external constant void ()*, align 4 +@__init_array_end = external constant void ()*, align 4 + +; Function Attrs: optsize +define hidden void @_start() local_unnamed_addr #0 { +entry: + br i1 icmp ult (void ()** @__init_array_start, void ()** @__init_array_end), label %for.body.preheader, label %for.cond.cleanup + +for.body.preheader: ; preds = %entry + br label %for.body + +for.cond.cleanup: ; preds = %for.body, %entry + ret void + +for.body: ; preds = %for.body.preheader, %for.body + %i.04 = phi void ()** [ %incdec.ptr, %for.body ], [ @__init_array_start, %for.body.preheader ] + %0 = load void ()*, void ()** %i.04, align 4, !tbaa !2 + tail call void %0() #1 + %incdec.ptr = getelementptr inbounds void ()*, void ()** %i.04, i32 1 + %cmp = icmp ult void ()** %incdec.ptr, @__init_array_end + br i1 %cmp, label %for.body, label %for.cond.cleanup +} + +attributes #0 = { optsize "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="generic" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #1 = { optsize } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 6.0.0 (trunk 318652)"} +!2 = !{!3, !3, i64 0} +!3 = !{!"any pointer", !4, i64 0} +!4 = !{!"omnipotent char", !5, i64 0} +!5 = !{!"Simple C++ TBAA"} + + +; CHECK: - Type: EXPORT +; CHECK-NEXT: Exports: +; CHECK-NEXT: - Name: memory +; CHECK-NEXT: Kind: MEMORY +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: _start +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Type: START +; CHECK-NEXT: StartFunction: 0 +; CHECK-NEXT: - Type: CODE +; CHECK-NEXT: Functions: +; CHECK-NEXT: - Locals: +; CHECK-NEXT: - Type: I32 +; CHECK-NEXT: Count: 1 +; CHECK-NEXT: Body: 418080808000210002404180808080004180808080004F0D000340200028020011808080800000200041046A2200418080808000490D000B0B0B Index: test/wasm/init-array.ll =================================================================== --- /dev/null +++ test/wasm/init-array.ll @@ -0,0 +1,150 @@ +; RUN: llc -filetype=obj %s -o %t.o +; RUN: lld -flavor wasm -o %t.wasm %t.o --allow-undefined +; RUN: obj2yaml %t.wasm | FileCheck %s + +; ==== Generated from ==== +; extern void externalFn(void); // Avoid optimising out ctor calls +; +; struct StaticCtor { +; StaticCtor(); +; }; +; // Not inline to avoid a COMDAT: +; StaticCtor::StaticCtor() { externalFn(); } +; +; static StaticCtor staticCtor; +; +; void __attribute__((constructor)) ctorFunction() { externalFn(); } +; +; extern void (*const __init_array_start)(void), (*const __init_array_end)(void); +; +; typedef unsigned long uintptr_t; +; extern "C" void _start(void) { +; for (void (*const *i)(void) = &__init_array_start; +; (uintptr_t)i < (uintptr_t)&__init_array_end; +; ++i) { +; (*i)(); +; } +; } +; ======================== + + +target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" +target triple = "wasm32-unknown-unknown-wasm" + +%struct.StaticCtor = type { i8 } + +@__init_array_start = external constant void ()*, align 4 +@__init_array_end = external constant void ()*, align 4 +@llvm.global_ctors = appending global [2 x { i32, void ()*, i8* }] [{ i32, void ()*, i8* } { i32 65535, void ()* @_Z12ctorFunctionv, i8* null }, { i32, void ()*, i8* } { i32 65535, void ()* @_GLOBAL__sub_I_init_test.cxx, i8* null }] + +@_ZN10StaticCtorC1Ev = hidden alias %struct.StaticCtor* (%struct.StaticCtor*), %struct.StaticCtor* (%struct.StaticCtor*)* @_ZN10StaticCtorC2Ev + +; Function Attrs: optsize +define hidden %struct.StaticCtor* @_ZN10StaticCtorC2Ev(%struct.StaticCtor* readnone returned %this) unnamed_addr #0 { +entry: + tail call void @_Z10externalFnv() #2 + ret %struct.StaticCtor* %this +} + +; Function Attrs: optsize +declare void @_Z10externalFnv() local_unnamed_addr #1 + +; Function Attrs: optsize +define hidden void @_Z12ctorFunctionv() #0 { +entry: + tail call void @_Z10externalFnv() #2 + ret void +} + +; Function Attrs: optsize +define hidden void @_start() local_unnamed_addr #0 { +entry: + br i1 icmp ult (void ()** @__init_array_start, void ()** @__init_array_end), label %for.body.preheader, label %for.cond.cleanup + +for.body.preheader: ; preds = %entry + br label %for.body + +for.cond.cleanup: ; preds = %for.body, %entry + ret void + +for.body: ; preds = %for.body.preheader, %for.body + %i.04 = phi void ()** [ %incdec.ptr, %for.body ], [ @__init_array_start, %for.body.preheader ] + %0 = load void ()*, void ()** %i.04, align 4, !tbaa !2 + tail call void %0() #2 + %incdec.ptr = getelementptr inbounds void ()*, void ()** %i.04, i32 1 + %cmp = icmp ult void ()** %incdec.ptr, @__init_array_end + br i1 %cmp, label %for.body, label %for.cond.cleanup +} + +; Function Attrs: optsize +define internal void @_GLOBAL__sub_I_init_test.cxx() #0 section ".text.__startup" { +entry: + tail call void @_Z10externalFnv() #2 + ret void +} + +attributes #0 = { optsize "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="generic" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #1 = { optsize "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="generic" "unsafe-fp-math"="false" "use-soft-float"="false" } +attributes #2 = { optsize } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 6.0.0 (trunk 318652)"} +!2 = !{!3, !3, i64 0} +!3 = !{!"any pointer", !4, i64 0} +!4 = !{!"omnipotent char", !5, i64 0} +!5 = !{!"Simple C++ TBAA"} + +; CHECK: - Type: IMPORT +; CHECK-NEXT: Imports: +; CHECK-NEXT: - Module: env +; CHECK-NEXT: Field: _Z10externalFnv +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: SigIndex: 1 +; CHECK: - Type: EXPORT +; CHECK-NEXT: Exports: +; CHECK-NEXT: - Name: memory +; CHECK-NEXT: Kind: MEMORY +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: _ZN10StaticCtorC2Ev +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 1 +; CHECK-NEXT: - Name: _Z12ctorFunctionv +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 2 +; CHECK-NEXT: - Name: _start +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 3 +; CHECK-NEXT: - Name: _ZN10StaticCtorC1Ev +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 1 +; CHECK-NEXT: - Type: START +; CHECK-NEXT: StartFunction: 3 +; CHECK-NEXT: - Type: ELEM +; CHECK-NEXT: Segments: +; CHECK-NEXT: - Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1 +; CHECK-NEXT: Functions: [ 2, 4 ] +; CHECK-NEXT: - Type: CODE +; CHECK-NEXT: Functions: +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 10808080800020000B +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 1080808080000B +; CHECK-NEXT: - Locals: +; CHECK-NEXT: - Type: I32 +; CHECK-NEXT: Count: 1 +; CHECK-NEXT: Body: 418088808000210002404180888080004188888080004F0D000340200028020011818080800000200041046A2200418888808000490D000B0B0B +; CHECK-NEXT: - Locals: +; CHECK-NEXT: Body: 1080808080000B +; CHECK-NEXT: - Type: DATA +; CHECK-NEXT: Segments: +; CHECK-NEXT: - SectionOffset: 7 +; CHECK-NEXT: MemoryIndex: 0 +; CHECK-NEXT: Offset: +; CHECK-NEXT: Opcode: I32_CONST +; CHECK-NEXT: Value: 1024 +; CHECK-NEXT: Content: '0100000002000000' Index: wasm/Driver.cpp =================================================================== --- wasm/Driver.cpp +++ wasm/Driver.cpp @@ -206,6 +206,18 @@ Symtab->addUndefinedFunction(Name); } +// Inject a new global into the link - which will not be exported, but will +// be used to resolve any references to the symbol. The WasmSymbols are the +// notional signatures of the symbols we're exporting. +static std::vector SyntheticSymbols; +static void addSyntheticSegmentMarker(StringRef Name) { + log("injecting segment marker: " + Name); + SyntheticSymbols.emplace_back(Name, WasmSymbol::SymbolType::GLOBAL_EXPORT, 0, 0); + WasmSymbol* Sym = &SyntheticSymbols.back(); + Sym->Flags = WASM_SYMBOL_BINDING_WEAK | WASM_SYMBOL_VISIBILITY_HIDDEN; + Symtab->addDefined(nullptr, Sym); +} + static void printHelp(const char *Argv0) { WasmOptTable Table; Table.PrintHelp(outs(), Argv0, "LLVM Linker", false); @@ -332,6 +344,10 @@ addSyntheticUndefinedFunction(S); addSyntheticGlobal("__stack_pointer", 0); + addSyntheticSegmentMarker("__init_array_start"); + addSyntheticSegmentMarker("__init_array_end"); + addSyntheticSegmentMarker("__fini_array_start"); + addSyntheticSegmentMarker("__fini_array_end"); } createFiles(Args); Index: wasm/Symbols.h =================================================================== --- wasm/Symbols.h +++ wasm/Symbols.h @@ -80,6 +80,10 @@ // space of the output object. void setOutputIndex(uint32_t Index); + // Set the virtual address for a "synthetic" global variable - not used + // normally for symbols defined in the usual way. + void setVirtualAddress(uint32_t Addr); + void update(Kind K, InputFile *F = nullptr, const WasmSymbol *Sym = nullptr, const InputSegment *Segment = nullptr); @@ -100,6 +104,7 @@ const WasmSymbol *Sym = nullptr; const InputSegment *Segment = nullptr; llvm::Optional OutputIndex; + llvm::Optional VirtualAddress; }; } // namespace wasm Index: wasm/Symbols.cpp =================================================================== --- wasm/Symbols.cpp +++ wasm/Symbols.cpp @@ -51,6 +51,8 @@ DEBUG(dbgs() << "getVirtualAddress: " << getName() << "\n"); if (isUndefined()) return UINT32_MAX; + if (VirtualAddress.hasValue()) + return VirtualAddress.getValue(); assert(Sym != nullptr); ObjFile *Obj = cast(File); @@ -73,6 +75,12 @@ OutputIndex = Index; } +void Symbol::setVirtualAddress(uint32_t Addr) { + DEBUG(dbgs() << "setVirtualAddress " << Name << " -> " << Addr << "\n"); + assert(!VirtualAddress.hasValue()); + VirtualAddress = Addr; +} + void Symbol::update(Kind K, InputFile *F, const WasmSymbol *WasmSym, const InputSegment *Seg) { SymbolKind = K; Index: wasm/Writer.cpp =================================================================== --- wasm/Writer.cpp +++ wasm/Writer.cpp @@ -78,6 +78,7 @@ void calculateTypes(); void createOutputSegments(); void layoutMemory(); + void layoutSegmentMarkers(); void createHeader(); void createSections(); SyntheticSection *createSyntheticSection(uint32_t Type, @@ -501,6 +502,7 @@ Seg->Name.str().c_str(), MemoryPtr, Seg->Size, Seg->Alignment); MemoryPtr += Seg->Size; } + layoutSegmentMarkers(); DataSize = MemoryPtr; if (!Config->Relocatable) @@ -524,6 +526,35 @@ debugPrint("mem: total pages = %d\n", NumMemoryPages); } +// Layout the special symbols that are used to detect the start/end of the +// .init_array and .fini_array segments. +void Writer::layoutSegmentMarkers() { + struct Marker { StringRef SymName, SegName; bool Start; }; + const Marker Markers[] = { + { "__init_array_start", ".init_array", true }, + { "__init_array_end", ".init_array", false }, + { "__fini_array_start", ".fini_array", true }, + { "__fini_array_end", ".fini_array", false }, + }; + for (const Marker& M : Markers) { + Symbol* Sym = Symtab->find(M.SymName); + // In the "Relocatable" case we won't have created the symbol. We also skip + // it if it's defined by another source, such someone has linked in an + // object like crtbegin.o that defines __init_array_start manually. + if (!Sym || Sym->getFile()) + continue; + auto SegIt = SegmentMap.find(M.SegName); + // If .init_array is empty, define __init_array_start/end to be null; this + // is the normal case for a C executable (no "ctor" functions defined, but + // libc checks anyway). + uint32_t Addr = 0; + if (SegIt != SegmentMap.end()) + Addr = SegIt->second->StartVA + (M.Start ? 0 : SegIt->second->Size); + Sym->setVirtualAddress(Addr); + Sym->setOutputIndex(-1); // A bit hacky, ideally wouldn't be needed... + } +} + SyntheticSection *Writer::createSyntheticSection(uint32_t Type, std::string Name) { auto Sec = make(Type, Name);