Index: test/wasm/data-layout.ll =================================================================== --- test/wasm/data-layout.ll +++ test/wasm/data-layout.ll @@ -56,6 +56,17 @@ ; CHECK-NEXT: Content: 68656C6C6F0A00 ; CHECK-NEXT: - Type: CUSTOM +; RUN: wasm-ld -no-gc-sections --check-signatures -z stack-size=1024 --stack-first --allow-undefined -o %t.wasm %t.o %t.hello.o +; RUN: obj2yaml %t.wasm | FileCheck %s -check-prefix=CHECK-STACK-FIRST + +; CHECK-STACK-FIRST: - Type: GLOBAL +; CHECK-STACK-FIRST-NEXT: Globals: +; CHECK-STACK-FIRST-NEXT: - Index: 0 +; CHECK-STACK-FIRST-NEXT: Type: I32 +; CHECK-STACK-FIRST-NEXT: Mutable: true +; CHECK-STACK-FIRST-NEXT: InitExpr: +; CHECK-STACK-FIRST-NEXT: Opcode: I32_CONST +; CHECK-STACK-FIRST-NEXT: Value: 1024 ; RUN: wasm-ld -no-gc-sections --check-signatures --allow-undefined \ ; RUN: --initial-memory=131072 --max-memory=131072 -o %t_max.wasm %t.o \ Index: wasm/Config.h =================================================================== --- wasm/Config.h +++ wasm/Config.h @@ -29,6 +29,7 @@ bool Relocatable; bool StripAll; bool StripDebug; + bool StackFirst; uint32_t GlobalBase; uint32_t InitialMemory; uint32_t MaxMemory; Index: wasm/Driver.cpp =================================================================== --- wasm/Driver.cpp +++ wasm/Driver.cpp @@ -298,6 +298,7 @@ Config->SearchPaths = args::getStrings(Args, OPT_L); Config->StripAll = Args.hasArg(OPT_strip_all); Config->StripDebug = Args.hasArg(OPT_strip_debug); + Config->StackFirst = Args.hasArg(OPT_stack_first); errorHandler().Verbose = Args.hasArg(OPT_verbose); ThreadsEnabled = Args.hasFlag(OPT_threads, OPT_no_threads, true); Index: wasm/Options.td =================================================================== --- wasm/Options.td +++ wasm/Options.td @@ -124,6 +124,9 @@ def no_entry: F<"no-entry">, HelpText<"Do not output any entry point">; +def stack_first: F<"stack-first">, + HelpText<"Place stack at start of linear memory rather than after data">; + // Aliases def alias_entry_e: JoinedOrSeparate<["-"], "e">, Alias; def alias_entry_entry: J<"entry=">, Alias; Index: wasm/Writer.cpp =================================================================== --- wasm/Writer.cpp +++ wasm/Writer.cpp @@ -580,22 +580,47 @@ // Fix the memory layout of the output binary. This assigns memory offsets // to each of the input data sections as well as the explicit stack region. -// The memory layout is as follows, from low to high. +// The default memory layout is as follows, from low to high. // - initialized data (starting at Config->GlobalBase) // - BSS data (not currently implemented in llvm) // - explicit stack (Config->ZStackSize) // - heap start / unallocated +// +// The --stack-first option means that stack is placed before the any data. +// This can be useful since it means that stack overlow traps immediately rather +// than overwriting global data, but also increases code size since all static +// data loads and stores requires lorger offsets. void Writer::layoutMemory() { + createOutputSegments(); + uint32_t MemoryPtr = 0; - MemoryPtr = Config->GlobalBase; - log("mem: global base = " + Twine(Config->GlobalBase)); - createOutputSegments(); + auto PlaceStack = [&]() { + if (Config->Relocatable) + return; + MemoryPtr = alignTo(MemoryPtr, kStackAlignment); + if (Config->ZStackSize != alignTo(Config->ZStackSize, kStackAlignment)) + error("stack size must be " + Twine(kStackAlignment) + "-byte aligned"); + log("mem: stack size = " + Twine(Config->ZStackSize)); + log("mem: stack base = " + Twine(MemoryPtr)); + MemoryPtr += Config->ZStackSize; + WasmSym::StackPointer->Global->Global.InitExpr.Value.Int32 = MemoryPtr; + log("mem: stack top = " + Twine(MemoryPtr)); + }; + + if (Config->StackFirst) { + PlaceStack(); + } else { + MemoryPtr = Config->GlobalBase; + log("mem: global base = " + Twine(Config->GlobalBase)); + } + + uint32_t DataStart = MemoryPtr; // Arbitrarily set __dso_handle handle to point to the start of the data // segments. if (WasmSym::DsoHandle) - WasmSym::DsoHandle->setVirtualAddress(MemoryPtr); + WasmSym::DsoHandle->setVirtualAddress(DataStart); for (OutputSegment *Seg : Segments) { MemoryPtr = alignTo(MemoryPtr, Seg->Alignment); @@ -609,22 +634,15 @@ if (WasmSym::DataEnd) WasmSym::DataEnd->setVirtualAddress(MemoryPtr); - log("mem: static data = " + Twine(MemoryPtr - Config->GlobalBase)); + log("mem: static data = " + Twine(MemoryPtr - DataStart)); - // Stack comes after static data and bss - if (!Config->Relocatable) { - MemoryPtr = alignTo(MemoryPtr, kStackAlignment); - if (Config->ZStackSize != alignTo(Config->ZStackSize, kStackAlignment)) - error("stack size must be " + Twine(kStackAlignment) + "-byte aligned"); - log("mem: stack size = " + Twine(Config->ZStackSize)); - log("mem: stack base = " + Twine(MemoryPtr)); - MemoryPtr += Config->ZStackSize; - WasmSym::StackPointer->Global->Global.InitExpr.Value.Int32 = MemoryPtr; - log("mem: stack top = " + Twine(MemoryPtr)); + if (!Config->StackFirst) + PlaceStack(); - // Set `__heap_base` to directly follow the end of the stack. We don't - // allocate any heap memory up front, but instead really on the malloc/brk - // implementation growing the memory at runtime. + // Set `__heap_base` to directly follow the end of the stack or global data. + // The fact that this comes last means that a malloc/brk implementation + // and grow the heap at runtime. + if (!Config->Relocatable) { WasmSym::HeapBase->setVirtualAddress(MemoryPtr); log("mem: heap base = " + Twine(MemoryPtr)); }