mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-09-11 06:59:31 +00:00
runtime: precisely scan globals on all platforms
This commit is contained in:
@@ -1293,6 +1293,9 @@ func makeGlobalsModule(ctx llvm.Context, globals map[string]map[string]string, m
|
|||||||
global := llvm.AddGlobal(mod, stringType, globalName)
|
global := llvm.AddGlobal(mod, stringType, globalName)
|
||||||
global.SetInitializer(initializer)
|
global.SetInitializer(initializer)
|
||||||
global.SetAlignment(targetData.PrefTypeAlignment(stringType))
|
global.SetAlignment(targetData.PrefTypeAlignment(stringType))
|
||||||
|
// Keep external linkage for module resolution. Hidden visibility permits internalization.
|
||||||
|
// See https://llvm.org/docs/LangRef.html#visibility-styles.
|
||||||
|
global.SetVisibility(llvm.HiddenVisibility)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
target package code rodata data bss
|
target package code rodata data bss
|
||||||
hifive1b examples/echo 4321 323 0 2268
|
hifive1b examples/echo 4405 323 0 2268
|
||||||
microbit examples/serial 2842 382 8 2264
|
microbit examples/serial 2922 382 8 2264
|
||||||
wioterminal examples/pininterrupt 8039 1669 132 7496
|
wioterminal examples/pininterrupt 8251 1717 148 7496
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
//go:build (baremetal || tinygo.wasm) && !uefi
|
|
||||||
|
|
||||||
package runtime
|
|
||||||
|
|
||||||
// This file implements findGlobals for all systems where the start and end of
|
|
||||||
// the globals section can be found through linker-defined symbols.
|
|
||||||
|
|
||||||
// findGlobals finds all globals (which are reachable by definition) and calls
|
|
||||||
// the callback for them.
|
|
||||||
//
|
|
||||||
// This implementation marks all globals conservatively and assumes it can use
|
|
||||||
// linker-defined symbols for the start and end of the .data section.
|
|
||||||
func findGlobals(found func(start, end uintptr)) {
|
|
||||||
found(globalsStart, globalsEnd)
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build (gc.conservative || gc.precise) && tinygo.wasm
|
//go:build gc.conservative || gc.precise
|
||||||
|
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build gc.boehm && tinygo.wasm
|
//go:build gc.boehm
|
||||||
|
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
//go:build gc.custom && tinygo.wasm
|
//go:build gc.custom
|
||||||
|
|
||||||
package runtime
|
package runtime
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
//go:build gc.leaking || gc.none
|
||||||
|
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
func markGlobals() {
|
||||||
|
}
|
||||||
@@ -26,7 +26,7 @@ func gcMarkReachable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Scan globals.
|
// Scan globals.
|
||||||
findGlobals(markRoots)
|
markGlobals()
|
||||||
|
|
||||||
// Nothing more to do: the other cores haven't started yet.
|
// Nothing more to do: the other cores haven't started yet.
|
||||||
return
|
return
|
||||||
@@ -57,7 +57,7 @@ func gcMarkReachable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Scan globals.
|
// Scan globals.
|
||||||
findGlobals(markRoots)
|
markGlobals()
|
||||||
|
|
||||||
// Signal each core in turn that they can scan the stack.
|
// Signal each core in turn that they can scan the stack.
|
||||||
for i := uint32(0); i < numCPU; i++ {
|
for i := uint32(0); i < numCPU; i++ {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ var gcScanState atomic.Uint32
|
|||||||
|
|
||||||
func gcMarkReachable() {
|
func gcMarkReachable() {
|
||||||
markStack()
|
markStack()
|
||||||
findGlobals(markRoots)
|
markGlobals()
|
||||||
}
|
}
|
||||||
|
|
||||||
// markStack marks all root pointers found on the stack.
|
// markStack marks all root pointers found on the stack.
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ func gcMarkReachable() {
|
|||||||
//
|
//
|
||||||
//go:linkname gcScanGlobals internal/task.gcScanGlobals
|
//go:linkname gcScanGlobals internal/task.gcScanGlobals
|
||||||
func gcScanGlobals() {
|
func gcScanGlobals() {
|
||||||
findGlobals(markRoots)
|
markGlobals()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function called from assembly with all registers pushed, to actually scan the
|
// Function called from assembly with all registers pushed, to actually scan the
|
||||||
|
|||||||
@@ -31,97 +31,6 @@ const (
|
|||||||
sig_SIGSEGV = 11
|
sig_SIGSEGV = 11
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://opensource.apple.com/source/xnu/xnu-7195.141.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html
|
|
||||||
type machHeader struct {
|
|
||||||
magic uint32
|
|
||||||
cputype uint32
|
|
||||||
cpusubtype uint32
|
|
||||||
filetype uint32
|
|
||||||
ncmds uint32
|
|
||||||
sizeofcmds uint32
|
|
||||||
flags uint32
|
|
||||||
reserved uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Struct for the LC_SEGMENT_64 load command.
|
|
||||||
type segmentLoadCommand struct {
|
|
||||||
cmd uint32 // LC_SEGMENT_64
|
|
||||||
cmdsize uint32
|
|
||||||
segname [16]byte
|
|
||||||
vmaddr uintptr
|
|
||||||
vmsize uintptr
|
|
||||||
fileoff uintptr
|
|
||||||
filesize uintptr
|
|
||||||
maxprot uint32
|
|
||||||
initprot uint32
|
|
||||||
nsects uint32
|
|
||||||
flags uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// MachO header of the currently running process.
|
|
||||||
//
|
|
||||||
//go:extern _mh_execute_header
|
|
||||||
var libc_mh_execute_header machHeader
|
|
||||||
|
|
||||||
// Find global variables in .data/.bss sections.
|
|
||||||
// The MachO linker doesn't seem to provide symbols for the start and end of the
|
|
||||||
// data section. There is get_etext, get_edata, and get_end, but these are
|
|
||||||
// undocumented and don't work with ASLR (which is enabled by default).
|
|
||||||
// Therefore, read the MachO header directly.
|
|
||||||
func findGlobals(found func(start, end uintptr)) {
|
|
||||||
// Here is a useful blog post to understand the MachO file format:
|
|
||||||
// https://h3adsh0tzz.com/2020/01/macho-file-format/
|
|
||||||
|
|
||||||
const (
|
|
||||||
MH_MAGIC_64 = 0xfeedfacf
|
|
||||||
LC_SEGMENT_64 = 0x19
|
|
||||||
VM_PROT_WRITE = 0x02
|
|
||||||
)
|
|
||||||
|
|
||||||
// Sanity check that we're actually looking at a MachO header.
|
|
||||||
if gcAsserts && libc_mh_execute_header.magic != MH_MAGIC_64 {
|
|
||||||
runtimeFatal("gc: unexpected MachO header")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate through the load commands.
|
|
||||||
// Because we're only interested in LC_SEGMENT_64 load commands, cast the
|
|
||||||
// pointer to that struct in advance.
|
|
||||||
var offset uintptr
|
|
||||||
var hasOffset bool
|
|
||||||
cmd := (*segmentLoadCommand)(unsafe.Pointer(uintptr(unsafe.Pointer(&libc_mh_execute_header)) + unsafe.Sizeof(machHeader{})))
|
|
||||||
for i := libc_mh_execute_header.ncmds; i != 0; i-- {
|
|
||||||
if cmd.cmd == LC_SEGMENT_64 {
|
|
||||||
if cmd.fileoff == 0 && cmd.nsects != 0 {
|
|
||||||
// Detect ASLR offset by checking fileoff and nsects. This
|
|
||||||
// locates the __TEXT segment. This matches getsectiondata:
|
|
||||||
// https://opensource.apple.com/source/cctools/cctools-973.0.1/libmacho/getsecbyname.c.auto.html
|
|
||||||
offset = uintptr(unsafe.Pointer(&libc_mh_execute_header)) - cmd.vmaddr
|
|
||||||
hasOffset = true
|
|
||||||
}
|
|
||||||
if cmd.maxprot&VM_PROT_WRITE != 0 {
|
|
||||||
// Found a writable segment, which may contain Go globals.
|
|
||||||
if gcAsserts && !hasOffset {
|
|
||||||
// No ASLR offset detected. Did the __TEXT segment come
|
|
||||||
// after the __DATA segment?
|
|
||||||
// Note that when ASLR is disabled (for example, when
|
|
||||||
// running inside lldb), the offset is zero. That's why we
|
|
||||||
// need a separate hasOffset for this assert.
|
|
||||||
runtimeFatal("gc: did not detect ASLR offset")
|
|
||||||
}
|
|
||||||
// Scan this segment for GC roots.
|
|
||||||
// This could be improved by only reading the memory areas
|
|
||||||
// covered by sections. That would reduce the amount of memory
|
|
||||||
// scanned a little bit (up to a single VM page).
|
|
||||||
found(offset+cmd.vmaddr, offset+cmd.vmaddr+cmd.vmsize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move on to the next load command (which may or may not be a
|
|
||||||
// LC_SEGMENT_64).
|
|
||||||
cmd = (*segmentLoadCommand)(unsafe.Add(unsafe.Pointer(cmd), cmd.cmdsize))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func hardwareRand() (n uint64, ok bool) {
|
func hardwareRand() (n uint64, ok bool) {
|
||||||
n |= uint64(libc_arc4random())
|
n |= uint64(libc_arc4random())
|
||||||
n |= uint64(libc_arc4random()) << 32
|
n |= uint64(libc_arc4random()) << 32
|
||||||
|
|||||||
@@ -34,101 +34,11 @@ const (
|
|||||||
sig_SIGSEGV = linux_SIGSEGV
|
sig_SIGSEGV = linux_SIGSEGV
|
||||||
)
|
)
|
||||||
|
|
||||||
// For the definition of the various header structs, see:
|
|
||||||
// https://refspecs.linuxfoundation.org/elf/elf.pdf
|
|
||||||
// Also useful:
|
|
||||||
// https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
|
|
||||||
type elfHeader struct {
|
|
||||||
ident_magic uint32
|
|
||||||
ident_class uint8
|
|
||||||
ident_data uint8
|
|
||||||
ident_version uint8
|
|
||||||
ident_osabi uint8
|
|
||||||
ident_abiversion uint8
|
|
||||||
_ [7]byte // reserved
|
|
||||||
filetype uint16
|
|
||||||
machine uint16
|
|
||||||
version uint32
|
|
||||||
entry uintptr
|
|
||||||
phoff uintptr
|
|
||||||
shoff uintptr
|
|
||||||
flags uint32
|
|
||||||
ehsize uint16
|
|
||||||
phentsize uint16
|
|
||||||
phnum uint16
|
|
||||||
shentsize uint16
|
|
||||||
shnum uint16
|
|
||||||
shstrndx uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
type elfProgramHeader64 struct {
|
|
||||||
_type uint32
|
|
||||||
flags uint32
|
|
||||||
offset uintptr
|
|
||||||
vaddr uintptr
|
|
||||||
paddr uintptr
|
|
||||||
filesz uintptr
|
|
||||||
memsz uintptr
|
|
||||||
align uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
type elfProgramHeader32 struct {
|
|
||||||
_type uint32
|
|
||||||
offset uintptr
|
|
||||||
vaddr uintptr
|
|
||||||
paddr uintptr
|
|
||||||
filesz uintptr
|
|
||||||
memsz uintptr
|
|
||||||
flags uint32
|
|
||||||
align uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
// ELF header of the currently running process.
|
|
||||||
//
|
|
||||||
//go:extern __ehdr_start
|
|
||||||
var ehdr_start elfHeader
|
|
||||||
|
|
||||||
// int *__errno_location(void);
|
// int *__errno_location(void);
|
||||||
//
|
//
|
||||||
//export __errno_location
|
//export __errno_location
|
||||||
func libc_errno_location() *int32
|
func libc_errno_location() *int32
|
||||||
|
|
||||||
// findGlobals finds globals in the .data/.bss sections.
|
|
||||||
// It parses the ELF program header to find writable segments.
|
|
||||||
func findGlobals(found func(start, end uintptr)) {
|
|
||||||
// Relevant constants from the ELF specification.
|
|
||||||
// See: https://refspecs.linuxfoundation.org/elf/elf.pdf
|
|
||||||
const (
|
|
||||||
PT_LOAD = 1
|
|
||||||
PF_W = 0x2 // program flag: write access
|
|
||||||
)
|
|
||||||
|
|
||||||
headerPtr := unsafe.Pointer(uintptr(unsafe.Pointer(&ehdr_start)) + ehdr_start.phoff)
|
|
||||||
for i := 0; i < int(ehdr_start.phnum); i++ {
|
|
||||||
// Look for a writable segment and scan its contents.
|
|
||||||
// There is a little bit of duplication here, which is unfortunate. But
|
|
||||||
// the alternative would be to put elfProgramHeader in separate files
|
|
||||||
// which is IMHO a lot uglier. If only the ELF spec was consistent
|
|
||||||
// between 32-bit and 64-bit...
|
|
||||||
if TargetBits == 64 {
|
|
||||||
header := (*elfProgramHeader64)(headerPtr)
|
|
||||||
if header._type == PT_LOAD && header.flags&PF_W != 0 {
|
|
||||||
start := header.vaddr
|
|
||||||
end := start + header.memsz
|
|
||||||
found(start, end)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
header := (*elfProgramHeader32)(headerPtr)
|
|
||||||
if header._type == PT_LOAD && header.flags&PF_W != 0 {
|
|
||||||
start := header.vaddr
|
|
||||||
end := start + header.memsz
|
|
||||||
found(start, end)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
headerPtr = unsafe.Add(headerPtr, ehdr_start.phentsize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//export getpagesize
|
//export getpagesize
|
||||||
func libc_getpagesize() int
|
func libc_getpagesize() int
|
||||||
|
|
||||||
|
|||||||
@@ -6,36 +6,6 @@ const GOOS = "windows"
|
|||||||
|
|
||||||
const zeroSizeAllocPtr uintptr = 16 // part of the first protected page
|
const zeroSizeAllocPtr uintptr = 16 // part of the first protected page
|
||||||
|
|
||||||
//export GetModuleHandleExA
|
|
||||||
func _GetModuleHandleExA(dwFlags uint32, lpModuleName unsafe.Pointer, phModule **exeHeader) bool
|
|
||||||
|
|
||||||
// Mark global variables.
|
|
||||||
// Unfortunately, the linker doesn't provide symbols for the start and end of
|
|
||||||
// the data/bss sections. Therefore these addresses need to be determined at
|
|
||||||
// runtime. This might seem complex and it kind of is, but it only compiles to
|
|
||||||
// around 160 bytes of amd64 instructions.
|
|
||||||
// Most of this function is based on the documentation in
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format.
|
|
||||||
func findGlobals(found func(start, end uintptr)) {
|
|
||||||
// Constants used in this function.
|
|
||||||
const (
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandleexa
|
|
||||||
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 0x00000002
|
|
||||||
)
|
|
||||||
|
|
||||||
if module == nil {
|
|
||||||
// Obtain a handle to the currently executing image. What we're getting
|
|
||||||
// here is really just __ImageBase, but it's probably better to obtain
|
|
||||||
// it using GetModuleHandle to account for ASLR etc.
|
|
||||||
result := _GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, nil, &module)
|
|
||||||
if gcAsserts && (!result || module.signature != 0x5A4D) { // 0x4D5A is "MZ"
|
|
||||||
runtimeFatal("cannot get module handle")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
findGlobalsForPE(found)
|
|
||||||
}
|
|
||||||
|
|
||||||
type systeminfo struct {
|
type systeminfo struct {
|
||||||
anon0 [4]byte
|
anon0 [4]byte
|
||||||
dwpagesize uint32
|
dwpagesize uint32
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
//go:build windows || uefi
|
|
||||||
|
|
||||||
package runtime
|
|
||||||
|
|
||||||
import "unsafe"
|
|
||||||
|
|
||||||
// MS-DOS stub with PE header offset:
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#ms-dos-stub-image-only
|
|
||||||
type exeHeader struct {
|
|
||||||
signature uint16
|
|
||||||
_ [58]byte // skip DOS header
|
|
||||||
peHeader uint32 // at offset 0x3C
|
|
||||||
}
|
|
||||||
|
|
||||||
// COFF file header:
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#file-headers
|
|
||||||
type peHeader struct {
|
|
||||||
magic uint32
|
|
||||||
machine uint16
|
|
||||||
numberOfSections uint16
|
|
||||||
timeDateStamp uint32
|
|
||||||
pointerToSymbolTable uint32
|
|
||||||
numberOfSymbols uint32
|
|
||||||
sizeOfOptionalHeader uint16
|
|
||||||
characteristics uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// COFF section header:
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#section-table-section-headers
|
|
||||||
type peSection struct {
|
|
||||||
name [8]byte
|
|
||||||
virtualSize uint32
|
|
||||||
virtualAddress uint32
|
|
||||||
sizeOfRawData uint32
|
|
||||||
pointerToRawData uint32
|
|
||||||
pointerToRelocations uint32
|
|
||||||
pointerToLinenumbers uint32
|
|
||||||
numberOfRelocations uint16
|
|
||||||
numberOfLinenumbers uint16
|
|
||||||
characteristics uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
var module *exeHeader
|
|
||||||
|
|
||||||
func findGlobalsForPE(found func(start, end uintptr)) {
|
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
|
|
||||||
const imageSCNMemWrite = 0x80000000
|
|
||||||
|
|
||||||
pe := (*peHeader)(unsafe.Add(unsafe.Pointer(module), module.peHeader))
|
|
||||||
if gcAsserts && pe.magic != 0x00004550 { // 0x4550 is "PE"
|
|
||||||
runtimeFatal("cannot find PE header")
|
|
||||||
}
|
|
||||||
|
|
||||||
section := (*peSection)(unsafe.Pointer(uintptr(unsafe.Pointer(pe)) + uintptr(pe.sizeOfOptionalHeader) + unsafe.Sizeof(peHeader{})))
|
|
||||||
for i := 0; i < int(pe.numberOfSections); i++ {
|
|
||||||
if section.characteristics&imageSCNMemWrite != 0 {
|
|
||||||
start := uintptr(unsafe.Pointer(module)) + uintptr(section.virtualAddress)
|
|
||||||
end := uintptr(unsafe.Pointer(module)) + uintptr(section.virtualAddress) + uintptr(section.virtualSize)
|
|
||||||
found(start, end)
|
|
||||||
}
|
|
||||||
section = (*peSection)(unsafe.Add(unsafe.Pointer(section), unsafe.Sizeof(peSection{})))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -257,31 +257,6 @@ func getHeapEnd() uintptr {
|
|||||||
return heapEnd
|
return heapEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:extern __data_start
|
|
||||||
var dataStartSymbol [0]byte
|
|
||||||
|
|
||||||
//go:extern __data_end
|
|
||||||
var dataEndSymbol [0]byte
|
|
||||||
|
|
||||||
//go:extern __bss_start
|
|
||||||
var bssStartSymbol [0]byte
|
|
||||||
|
|
||||||
//go:extern __bss_end
|
|
||||||
var bssEndSymbol [0]byte
|
|
||||||
|
|
||||||
// Find global variables.
|
|
||||||
// The linker script provides __*_start and __*_end symbols that can be used to
|
|
||||||
// scan the given sections. They are already aligned so don't need to be
|
|
||||||
// manually aligned here.
|
|
||||||
func findGlobals(found func(start, end uintptr)) {
|
|
||||||
dataStart := uintptr(unsafe.Pointer(&dataStartSymbol))
|
|
||||||
dataEnd := uintptr(unsafe.Pointer(&dataEndSymbol))
|
|
||||||
found(dataStart, dataEnd)
|
|
||||||
bssStart := uintptr(unsafe.Pointer(&bssStartSymbol))
|
|
||||||
bssEnd := uintptr(unsafe.Pointer(&bssEndSymbol))
|
|
||||||
found(bssStart, bssEnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getContextPtr returns the hblauncher context
|
// getContextPtr returns the hblauncher context
|
||||||
// this is externally linked by gonx
|
// this is externally linked by gonx
|
||||||
func getContextPtr() uintptr {
|
func getContextPtr() uintptr {
|
||||||
|
|||||||
@@ -58,7 +58,9 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error {
|
|||||||
// LLVM 17 doesn't have the no-verify-fixpoint flag.
|
// LLVM 17 doesn't have the no-verify-fixpoint flag.
|
||||||
optPasses = "globaldce,globalopt,ipsccp,instcombine,adce,function-attrs"
|
optPasses = "globaldce,globalopt,ipsccp,instcombine,adce,function-attrs"
|
||||||
}
|
}
|
||||||
|
blockGlobalAllocPromotion(mod)
|
||||||
err := mod.RunPasses(optPasses, llvm.TargetMachine{}, po)
|
err := mod.RunPasses(optPasses, llvm.TargetMachine{}, po)
|
||||||
|
removeGlobalAllocPromotionMarker(mod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
|
return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
|
||||||
}
|
}
|
||||||
@@ -80,7 +82,9 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error {
|
|||||||
// After interfaces are lowered, there are many more opportunities for
|
// After interfaces are lowered, there are many more opportunities for
|
||||||
// interprocedural optimizations. To get them to work, function
|
// interprocedural optimizations. To get them to work, function
|
||||||
// attributes have to be updated first.
|
// attributes have to be updated first.
|
||||||
|
blockGlobalAllocPromotion(mod)
|
||||||
err = mod.RunPasses(optPasses, llvm.TargetMachine{}, po)
|
err = mod.RunPasses(optPasses, llvm.TargetMachine{}, po)
|
||||||
|
removeGlobalAllocPromotionMarker(mod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
|
return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
|
||||||
}
|
}
|
||||||
@@ -162,7 +166,9 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error {
|
|||||||
po := llvm.NewPassBuilderOptions()
|
po := llvm.NewPassBuilderOptions()
|
||||||
defer po.Dispose()
|
defer po.Dispose()
|
||||||
passes := fmt.Sprintf("thinlto-pre-link<%s>", optLevel)
|
passes := fmt.Sprintf("thinlto-pre-link<%s>", optLevel)
|
||||||
|
blockGlobalAllocPromotion(mod)
|
||||||
err := mod.RunPasses(passes, llvm.TargetMachine{}, po)
|
err := mod.RunPasses(passes, llvm.TargetMachine{}, po)
|
||||||
|
removeGlobalAllocPromotionMarker(mod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
|
return []error{fmt.Errorf("could not build pass pipeline: %w", err)}
|
||||||
}
|
}
|
||||||
@@ -177,6 +183,64 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func blockGlobalAllocPromotion(mod llvm.Module) {
|
||||||
|
ctx := mod.Context()
|
||||||
|
ptrType := llvm.PointerType(ctx.Int8Type(), 0)
|
||||||
|
marker := llvm.AddFunction(mod, "tinygo.gc.alloc.marker", llvm.FunctionType(ctx.VoidType(), []llvm.Type{ptrType}, false))
|
||||||
|
|
||||||
|
builder := ctx.NewBuilder()
|
||||||
|
defer builder.Dispose()
|
||||||
|
var marked bool
|
||||||
|
for _, name := range []string{"runtime.alloc", "runtime.alloc_noheap"} {
|
||||||
|
alloc := mod.NamedFunction(name)
|
||||||
|
if alloc.IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, call := range getUses(alloc) {
|
||||||
|
if call.IsACallInst().IsNil() || call.CalledValue() != alloc {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isPointerFreeAllocation(call) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// GlobalOpt may otherwise turn this allocation into an untyped
|
||||||
|
// global, hiding its pointer fields from makeGCGlobalRoots.
|
||||||
|
next := llvm.NextInstruction(call)
|
||||||
|
if next.IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
builder.SetInsertPointBefore(next)
|
||||||
|
builder.CreateCall(marker.GlobalValueType(), marker, []llvm.Value{call}, "")
|
||||||
|
marked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !marked {
|
||||||
|
marker.EraseFromParentAsFunction()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPointerFreeAllocation(call llvm.Value) bool {
|
||||||
|
const noPointerLayout = 3
|
||||||
|
|
||||||
|
layout := call.Operand(1)
|
||||||
|
return !layout.IsAConstantExpr().IsNil() &&
|
||||||
|
layout.Opcode() == llvm.IntToPtr &&
|
||||||
|
!layout.Operand(0).IsAConstantInt().IsNil() &&
|
||||||
|
layout.Operand(0).ZExtValue() == noPointerLayout
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeGlobalAllocPromotionMarker(mod llvm.Module) {
|
||||||
|
marker := mod.NamedFunction("tinygo.gc.alloc.marker")
|
||||||
|
if marker.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, call := range getUses(marker) {
|
||||||
|
call.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
marker.EraseFromParentAsFunction()
|
||||||
|
}
|
||||||
|
|
||||||
// functionsUsedInTransform is a list of function symbols that may be used
|
// functionsUsedInTransform is a list of function symbols that may be used
|
||||||
// during TinyGo optimization passes so they have to be marked as external
|
// during TinyGo optimization passes so they have to be marked as external
|
||||||
// linkage until all TinyGo passes have finished.
|
// linkage until all TinyGo passes have finished.
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package transform
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBlockGlobalAllocPromotionUses(t *testing.T) {
|
||||||
|
ctx := llvm.NewContext()
|
||||||
|
defer ctx.Dispose()
|
||||||
|
buf, err := llvm.NewMemoryBufferFromFile("testdata/optimizer-alloc-uses.ll")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
mod, err := ctx.ParseIR(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer mod.Dispose()
|
||||||
|
|
||||||
|
blockGlobalAllocPromotion(mod)
|
||||||
|
if err := llvm.VerifyModule(mod, llvm.ReturnStatusAction); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
marker := mod.NamedFunction("tinygo.gc.alloc.marker")
|
||||||
|
if marker.IsNil() {
|
||||||
|
t.Fatal("allocation marker was not created")
|
||||||
|
}
|
||||||
|
if uses := getUses(marker); len(uses) != 1 {
|
||||||
|
t.Fatalf("got %d marker uses, want 1", len(uses))
|
||||||
|
}
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||||
|
target triple = "wasm32-unknown-unknown-wasm"
|
||||||
|
|
||||||
|
@allocFunction = constant ptr @runtime.alloc
|
||||||
|
|
||||||
|
declare ptr @runtime.alloc(i32, ptr)
|
||||||
|
|
||||||
|
declare void @use(ptr)
|
||||||
|
|
||||||
|
define void @passAllocator() {
|
||||||
|
entry:
|
||||||
|
call void @use(ptr @runtime.alloc)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
||||||
|
define ptr @allocate() {
|
||||||
|
entry:
|
||||||
|
%allocation = call ptr @runtime.alloc(i32 4, ptr inttoptr (i32 5 to ptr))
|
||||||
|
ret ptr %allocation
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user