mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-18 19:44:00 +00:00
compiler, runtime: optimize zero-sized allocations
Instead of referring to an unused global, use a constant value. This is safe even when using `-gc=none` (since no actual memory gets allocated) which wasn't the case before. It should also reduce binary size by a few bytes for most programs.
This commit is contained in:
committed by
Ron Evans
parent
221ea6a54c
commit
134de98ba5
@@ -42,7 +42,7 @@ func TestBinarySize(t *testing.T) {
|
|||||||
// This is a small number of very diverse targets that we want to test.
|
// This is a small number of very diverse targets that we want to test.
|
||||||
tests := []sizeTest{
|
tests := []sizeTest{
|
||||||
// microcontrollers
|
// microcontrollers
|
||||||
{"hifive1b", "examples/echo", 3705, 299, 0, 2252},
|
{"hifive1b", "examples/echo", 3699, 297, 0, 2252},
|
||||||
{"microbit", "examples/serial", 2736, 356, 8, 2248},
|
{"microbit", "examples/serial", 2736, 356, 8, 2248},
|
||||||
{"wioterminal", "examples/pininterrupt", 7960, 1652, 132, 7480},
|
{"wioterminal", "examples/pininterrupt", 7960, 1652, 132, 7480},
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ func (b *builder) createAlloc(sizeValue, layoutValue llvm.Value, align int, comm
|
|||||||
allocFunc = "alloc_noheap"
|
allocFunc = "alloc_noheap"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Allocs that don't allocate anything can return an architecture-specific
|
||||||
|
// sentinel value.
|
||||||
|
if !sizeValue.IsAConstantInt().IsNil() && sizeValue.ZExtValue() == 0 {
|
||||||
|
allocFunc = "alloc_zero"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the runtime call.
|
||||||
call := b.createRuntimeCall(allocFunc, []llvm.Value{sizeValue, layoutValue}, comment)
|
call := b.createRuntimeCall(allocFunc, []llvm.Value{sizeValue, layoutValue}, comment)
|
||||||
if align != 0 {
|
if align != 0 {
|
||||||
// TODO: make sure all callsites set the correct alignment.
|
// TODO: make sure all callsites set the correct alignment.
|
||||||
|
|||||||
+1
-1
@@ -162,7 +162,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
|
|||||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||||
case "machine.keepAliveNoEscape", "machine.unsafeNoEscape":
|
case "machine.keepAliveNoEscape", "machine.unsafeNoEscape":
|
||||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||||
case "runtime.alloc", "runtime.alloc_noheap":
|
case "runtime.alloc", "runtime.alloc_noheap", "runtime.alloc_zero":
|
||||||
// Tell the optimizer that runtime.alloc is an allocator, meaning that it
|
// Tell the optimizer that runtime.alloc is an allocator, meaning that it
|
||||||
// returns values that are never null and never alias to an existing value.
|
// returns values that are never null and never alias to an existing value.
|
||||||
for _, attrName := range []string{"noalias", "nonnull"} {
|
for _, attrName := range []string{"noalias", "nonnull"} {
|
||||||
|
|||||||
Vendored
+4
-1
@@ -75,7 +75,7 @@ entry:
|
|||||||
define hidden void @main.newStruct(ptr %context) unnamed_addr #1 {
|
define hidden void @main.newStruct(ptr %context) unnamed_addr #1 {
|
||||||
entry:
|
entry:
|
||||||
%stackalloc = alloca i8, align 1
|
%stackalloc = alloca i8, align 1
|
||||||
%new = call align 1 ptr @runtime.alloc(i32 0, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
%new = call align 1 ptr @runtime.alloc_zero(i32 0, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||||
call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3
|
call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #3
|
||||||
store ptr %new, ptr @main.struct1, align 4
|
store ptr %new, ptr @main.struct1, align 4
|
||||||
%new1 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
%new1 = call align 4 dereferenceable(8) ptr @runtime.alloc(i32 8, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #3
|
||||||
@@ -93,6 +93,9 @@ entry:
|
|||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
|
||||||
|
declare noalias nonnull ptr @runtime.alloc_zero(i32, ptr, ptr) #2
|
||||||
|
|
||||||
; Function Attrs: nounwind
|
; Function Attrs: nounwind
|
||||||
define hidden ptr @main.newFuncValue(ptr %context) unnamed_addr #1 {
|
define hidden ptr @main.newFuncValue(ptr %context) unnamed_addr #1 {
|
||||||
entry:
|
entry:
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
|
|||||||
// means that monotonic time in the time package is counted from
|
// means that monotonic time in the time package is counted from
|
||||||
// time.Time{}.Sub(1), which should be fine.
|
// time.Time{}.Sub(1), which should be fine.
|
||||||
locals[inst.localIndex] = literalValue{uint64(0)}
|
locals[inst.localIndex] = literalValue{uint64(0)}
|
||||||
case callFn.name == "runtime.alloc" || callFn.name == "runtime.alloc_noheap":
|
case callFn.name == "runtime.alloc" || callFn.name == "runtime.alloc_noheap" || callFn.name == "runtime.alloc_zero":
|
||||||
// Allocate heap memory. At compile time, this is instead done
|
// Allocate heap memory. At compile time, this is instead done
|
||||||
// by creating a global variable.
|
// by creating a global variable.
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ const GOARCH = "arm" // avr pretends to be arm
|
|||||||
// The bitness of the CPU (e.g. 8, 32, 64).
|
// The bitness of the CPU (e.g. 8, 32, 64).
|
||||||
const TargetBits = 8
|
const TargetBits = 8
|
||||||
|
|
||||||
|
const zeroSizeAllocPtr uintptr = 16 // points into the register file or I/O sapce (which is memory mapped but shouldn't be accessed directly)
|
||||||
|
|
||||||
const deferExtraRegs = 1 // the frame pointer (Y register) also needs to be stored
|
const deferExtraRegs = 1 // the frame pointer (Y register) also needs to be stored
|
||||||
|
|
||||||
const callInstSize = 2 // "call" is 4 bytes, "rcall" is 2 bytes
|
const callInstSize = 2 // "call" is 4 bytes, "rcall" is 2 bytes
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ const GOARCH = "arm"
|
|||||||
// The bitness of the CPU (e.g. 8, 32, 64).
|
// The bitness of the CPU (e.g. 8, 32, 64).
|
||||||
const TargetBits = 32
|
const TargetBits = 32
|
||||||
|
|
||||||
|
const zeroSizeAllocPtr uintptr = 16 // part of the interrupt vector
|
||||||
|
|
||||||
const deferExtraRegs = 0
|
const deferExtraRegs = 0
|
||||||
|
|
||||||
const callInstSize = 4 // "bl someFunction" is 4 bytes
|
const callInstSize = 4 // "bl someFunction" is 4 bytes
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ package runtime
|
|||||||
|
|
||||||
import "device/riscv"
|
import "device/riscv"
|
||||||
|
|
||||||
|
const zeroSizeAllocPtr uintptr = 0xffff_fff0 // should be unused on most RISC-V chips
|
||||||
|
|
||||||
const deferExtraRegs = 0
|
const deferExtraRegs = 0
|
||||||
|
|
||||||
const callInstSize = 4 // 8 without relaxation, maybe 4 with relaxation
|
const callInstSize = 4 // 8 without relaxation, maybe 4 with relaxation
|
||||||
|
|||||||
@@ -11,6 +11,13 @@ const GOARCH = "wasm"
|
|||||||
// The bitness of the CPU (e.g. 8, 32, 64).
|
// The bitness of the CPU (e.g. 8, 32, 64).
|
||||||
const TargetBits = 32
|
const TargetBits = 32
|
||||||
|
|
||||||
|
// zeroSizedAlloc a sentinel that gets returned when allocating 0 bytes.
|
||||||
|
// Using this instead of a constant value since I can't easily find a memory
|
||||||
|
// location that is definitely not going to end up as a valid pointer.
|
||||||
|
var zeroSizedAlloc uint8
|
||||||
|
|
||||||
|
var zeroSizeAllocPtr = &zeroSizedAlloc
|
||||||
|
|
||||||
const deferExtraRegs = 0
|
const deferExtraRegs = 0
|
||||||
|
|
||||||
const callInstSize = 1 // unknown and irrelevant (llvm.returnaddress doesn't work), so make something up
|
const callInstSize = 1 // unknown and irrelevant (llvm.returnaddress doesn't work), so make something up
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import "device"
|
|||||||
|
|
||||||
const GOARCH = "arm" // xtensa pretends to be arm
|
const GOARCH = "arm" // xtensa pretends to be arm
|
||||||
|
|
||||||
|
const zeroSizeAllocPtr uintptr = 16 // part of early flash: partition table, etc
|
||||||
|
|
||||||
// The bitness of the CPU (e.g. 8, 32, 64).
|
// The bitness of the CPU (e.g. 8, 32, 64).
|
||||||
const TargetBits = 32
|
const TargetBits = 32
|
||||||
|
|
||||||
|
|||||||
@@ -8,3 +8,22 @@ import "unsafe"
|
|||||||
// It is used instead of normal alloc in //go:noheap functions, and must either
|
// It is used instead of normal alloc in //go:noheap functions, and must either
|
||||||
// be optimized away or throw a linker error.
|
// be optimized away or throw a linker error.
|
||||||
func alloc_noheap(size uintptr, layout unsafe.Pointer) unsafe.Pointer
|
func alloc_noheap(size uintptr, layout unsafe.Pointer) unsafe.Pointer
|
||||||
|
|
||||||
|
// Special alloc function that returns a sentinel value that can never be on the
|
||||||
|
// heap or match any other valid pointer. An alloc(0, xxx) call can be safely
|
||||||
|
// converted to an alloc_zero(0, xxx) call as an optimization.
|
||||||
|
//
|
||||||
|
// It is always a good idea to inline this function, since the result is a
|
||||||
|
// constant. Marking it as go:inline to be sure even though the compiler should
|
||||||
|
// already be doing this.
|
||||||
|
//
|
||||||
|
//go:inline
|
||||||
|
func alloc_zero(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
|
||||||
|
// Returning a constant here is safe, since the Go spec does not require
|
||||||
|
// multiple zero-sized allocations to be unequal when compared for equality:
|
||||||
|
//
|
||||||
|
// > Pointers to distinct zero-size variables may or may not be equal.
|
||||||
|
//
|
||||||
|
// Source: https://go.dev/ref/spec#Comparison_operators
|
||||||
|
return unsafe.Pointer(zeroSizeAllocPtr)
|
||||||
|
}
|
||||||
|
|||||||
@@ -58,9 +58,6 @@ var (
|
|||||||
gcLock task.PMutex // lock to avoid race conditions on multicore systems
|
gcLock task.PMutex // lock to avoid race conditions on multicore systems
|
||||||
)
|
)
|
||||||
|
|
||||||
// zeroSizedAlloc is just a sentinel that gets returned when allocating 0 bytes.
|
|
||||||
var zeroSizedAlloc uint8
|
|
||||||
|
|
||||||
// Provide some abstraction over heap blocks.
|
// Provide some abstraction over heap blocks.
|
||||||
|
|
||||||
// blockState stores the four states in which a block can be.
|
// blockState stores the four states in which a block can be.
|
||||||
@@ -405,7 +402,7 @@ func calculateHeapAddresses() {
|
|||||||
//go:noinline
|
//go:noinline
|
||||||
func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
|
func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
return unsafe.Pointer(&zeroSizedAlloc)
|
return alloc_zero(size, layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
if interrupt.In() {
|
if interrupt.In() {
|
||||||
|
|||||||
@@ -26,9 +26,6 @@ import (
|
|||||||
|
|
||||||
const needsStaticHeap = false
|
const needsStaticHeap = false
|
||||||
|
|
||||||
// zeroSizedAlloc is just a sentinel that gets returned when allocating 0 bytes.
|
|
||||||
var zeroSizedAlloc uint8
|
|
||||||
|
|
||||||
var gcLock task.PMutex
|
var gcLock task.PMutex
|
||||||
|
|
||||||
func initHeap() {
|
func initHeap() {
|
||||||
@@ -67,7 +64,7 @@ func markCurrentGoroutineStack(sp uintptr) {
|
|||||||
//go:noinline
|
//go:noinline
|
||||||
func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
|
func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
return unsafe.Pointer(&zeroSizedAlloc)
|
return alloc_zero(size, layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
gcLock.Lock()
|
gcLock.Lock()
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import "unsafe"
|
|||||||
|
|
||||||
const GOOS = "darwin"
|
const GOOS = "darwin"
|
||||||
|
|
||||||
|
const zeroSizeAllocPtr uintptr = 16 // part of the first protected page
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// See https://github.com/golang/go/blob/master/src/syscall/zerrors_darwin_amd64.go
|
// See https://github.com/golang/go/blob/master/src/syscall/zerrors_darwin_amd64.go
|
||||||
flag_PROT_READ = 0x1
|
flag_PROT_READ = 0x1
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
|
|
||||||
const GOOS = "linux"
|
const GOOS = "linux"
|
||||||
|
|
||||||
|
const zeroSizeAllocPtr uintptr = 16 // part of the first protected page
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// See https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/mman-common.h
|
// See https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/mman-common.h
|
||||||
flag_PROT_READ = 0x1
|
flag_PROT_READ = 0x1
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import "unsafe"
|
|||||||
|
|
||||||
const GOOS = "windows"
|
const GOOS = "windows"
|
||||||
|
|
||||||
|
const zeroSizeAllocPtr uintptr = 16 // part of the first protected page
|
||||||
|
|
||||||
//export GetModuleHandleExA
|
//export GetModuleHandleExA
|
||||||
func _GetModuleHandleExA(dwFlags uint32, lpModuleName unsafe.Pointer, phModule **exeHeader) bool
|
func _GetModuleHandleExA(dwFlags uint32, lpModuleName unsafe.Pointer, phModule **exeHeader) bool
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ var _sidata [0]byte
|
|||||||
//go:extern _edata
|
//go:extern _edata
|
||||||
var _edata [0]byte
|
var _edata [0]byte
|
||||||
|
|
||||||
|
const zeroSizeAllocPtr uintptr = 16 // points somewhere in the BIOS which is not readable
|
||||||
|
|
||||||
// Entry point for Go. Initialize all packages and call main.main().
|
// Entry point for Go. Initialize all packages and call main.main().
|
||||||
//
|
//
|
||||||
//export main
|
//export main
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ package runtime
|
|||||||
|
|
||||||
import "unsafe"
|
import "unsafe"
|
||||||
|
|
||||||
|
// Not sure whether there is anything on this location, but it doesn't look like
|
||||||
|
// it according to the memory map:
|
||||||
|
// https://switchbrew.org/wiki/Memory_layout
|
||||||
|
const zeroSizeAllocPtr uintptr = 16
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Handles
|
// Handles
|
||||||
infoTypeTotalMemorySize = 6 // Total amount of memory available for process.
|
infoTypeTotalMemorySize = 6 // Total amount of memory available for process.
|
||||||
|
|||||||
Reference in New Issue
Block a user