Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 2 additions & 13 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,16 +192,6 @@ func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *bu
}
}

// Return the runtime.alloc function variant.
// This is normally just "alloc", but is "alloc_noheap" if the //go:noheap
// pragma is used.
func (b *builder) allocFunc() string {
if b.info.noheap {
return "alloc_noheap"
}
return "alloc"
}

type blockInfo struct {
// entry is the LLVM basic block corresponding to the start of this *ssa.Block.
entry llvm.BasicBlock
Expand Down Expand Up @@ -2183,9 +2173,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
}
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
layoutValue := b.createObjectLayout(typ, expr.Pos())
buf := b.createRuntimeCall(b.allocFunc(), []llvm.Value{sizeValue, layoutValue}, expr.Comment)
align := b.targetData.ABITypeAlignment(typ)
buf.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
buf := b.createAlloc(sizeValue, layoutValue, align, expr.Comment)
return buf, nil
} else {
buf := llvmutil.CreateEntryBlockAlloca(b.Builder, typ, expr.Comment)
Expand Down Expand Up @@ -2415,7 +2404,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
}
sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap")
layoutValue := b.createObjectLayout(llvmElemType, expr.Pos())
slicePtr := b.createRuntimeCall(b.allocFunc(), []llvm.Value{sliceSize, layoutValue}, "makeslice.buf")
slicePtr := b.createAlloc(sliceSize, layoutValue, 0, "makeslice.buf")
slicePtr.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elemAlign)))

// Extend or truncate if necessary. This is safe as we've already done
Expand Down
2 changes: 1 addition & 1 deletion compiler/defer.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
size := b.targetData.TypeAllocSize(deferredCallType)
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
nilPtr := llvm.ConstNull(b.dataPtrType)
alloca = b.createRuntimeCall(b.allocFunc(), []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
alloca = b.createAlloc(sizeValue, nilPtr, 0, "defer.alloc.call")
}
if b.NeedsStackObjects {
b.trackPointer(alloca)
Expand Down
26 changes: 26 additions & 0 deletions compiler/gc.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,32 @@ import (
"tinygo.org/x/go-llvm"
)

// Heap-allocate a buffer of the given size. This will typically call
// runtime.alloc.
func (b *builder) createAlloc(sizeValue, layoutValue llvm.Value, align int, comment string) llvm.Value {
// Normally allocate using "runtime.alloc", but use "runtime.alloc_noheap"
// if the //go:noheap pragma is used.
allocFunc := "alloc"
if b.info.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)
if align != 0 {
// TODO: make sure all callsites set the correct alignment.
call.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
}

return call
}

// trackExpr inserts pointer tracking intrinsics for the GC if the expression is
// one of the expressions that need this.
func (b *builder) trackExpr(expr ssa.Value, value llvm.Value) {
Expand Down
6 changes: 1 addition & 5 deletions compiler/llvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,7 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
// Packed data is bigger than a pointer, so allocate it on the heap.
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
align := b.targetData.ABITypeAlignment(packedType)
packedAlloc := b.createRuntimeCall(b.allocFunc(), []llvm.Value{
sizeValue,
llvm.ConstNull(b.dataPtrType),
}, "")
packedAlloc.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
packedAlloc := b.createAlloc(sizeValue, llvm.ConstNull(b.dataPtrType), align, "")
if b.NeedsStackObjects {
b.trackPointer(packedAlloc)
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/symbol.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
case "machine.keepAliveNoEscape", "machine.unsafeNoEscape":
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
// returns values that are never null and never alias to an existing value.
for _, attrName := range []string{"noalias", "nonnull"} {
Expand Down
5 changes: 4 additions & 1 deletion compiler/testdata/gc.ll
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ entry:
define hidden void @main.newStruct(ptr %context) unnamed_addr #1 {
entry:
%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
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
Expand All @@ -93,6 +93,9 @@ entry:
ret void
}

; Function Attrs: allockind("alloc,zeroed") allocsize(0)
declare noalias nonnull ptr @runtime.alloc_zero(i32, ptr, ptr) #2

; Function Attrs: nounwind
define hidden ptr @main.newFuncValue(ptr %context) unnamed_addr #1 {
entry:
Expand Down
2 changes: 1 addition & 1 deletion interp/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
// time.Time{}.Sub(1), which should be fine.
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
// by creating a global variable.

Expand Down
2 changes: 2 additions & 0 deletions src/runtime/arch_avr.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const GOARCH = "arm" // avr pretends to be arm
// The bitness of the CPU (e.g. 8, 32, 64).
const TargetBits = 8

const zeroSizeAllocPtr uintptr = 16 // part of the first protected page

const deferExtraRegs = 1 // the frame pointer (Y register) also needs to be stored

const callInstSize = 2 // "call" is 4 bytes, "rcall" is 2 bytes
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/arch_cortexm.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const GOARCH = "arm"
// The bitness of the CPU (e.g. 8, 32, 64).
const TargetBits = 32

const zeroSizeAllocPtr uintptr = 16 // part of the interrupt vector

const deferExtraRegs = 0

const callInstSize = 4 // "bl someFunction" is 4 bytes
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/arch_tinygoriscv.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ package runtime

import "device/riscv"

const zeroSizeAllocPtr uintptr = 0xffff_fff0 // should be unused on most RISC-V chips

const deferExtraRegs = 0

const callInstSize = 4 // 8 without relaxation, maybe 4 with relaxation
Expand Down
7 changes: 7 additions & 0 deletions src/runtime/arch_tinygowasm.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ const GOARCH = "wasm"
// The bitness of the CPU (e.g. 8, 32, 64).
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 callInstSize = 1 // unknown and irrelevant (llvm.returnaddress doesn't work), so make something up
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/arch_xtensa.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import "device"

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).
const TargetBits = 32

Expand Down
29 changes: 29 additions & 0 deletions src/runtime/gc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package runtime

// Shared code for the various garbage collectors.

import "unsafe"

// Special alloc function that should never actually be called.
// It is used instead of normal alloc in //go:noheap functions, and must either
// be optimized away or throw a linker error.
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)
}
5 changes: 1 addition & 4 deletions src/runtime/gc_blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,6 @@ var (
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.

// blockState stores the four states in which a block can be.
Expand Down Expand Up @@ -391,7 +388,7 @@ func calculateHeapAddresses() {
//go:noinline
func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
if size == 0 {
return unsafe.Pointer(&zeroSizedAlloc)
return alloc_zero(size, layout)
}

if interrupt.In() {
Expand Down
5 changes: 1 addition & 4 deletions src/runtime/gc_boehm.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ import (

const needsStaticHeap = false

// zeroSizedAlloc is just a sentinel that gets returned when allocating 0 bytes.
var zeroSizedAlloc uint8

var gcLock task.PMutex

func initHeap() {
Expand Down Expand Up @@ -67,7 +64,7 @@ func markCurrentGoroutineStack(sp uintptr) {
//go:noinline
func alloc(size uintptr, layout unsafe.Pointer) unsafe.Pointer {
if size == 0 {
return unsafe.Pointer(&zeroSizedAlloc)
return alloc_zero(size, layout)
}

gcLock.Lock()
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/os_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import "unsafe"

const GOOS = "darwin"

const zeroSizeAllocPtr uintptr = 16 // part of the first protected page

const (
// See https://github.com/golang/go/blob/master/src/syscall/zerrors_darwin_amd64.go
flag_PROT_READ = 0x1
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/os_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (

const GOOS = "linux"

const zeroSizeAllocPtr uintptr = 16 // part of the first protected page

const (
// See https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/mman-common.h
flag_PROT_READ = 0x1
Expand Down
2 changes: 2 additions & 0 deletions src/runtime/os_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import "unsafe"

const GOOS = "windows"

const zeroSizeAllocPtr uintptr = 16 // part of the first protected page

//export GetModuleHandleExA
func _GetModuleHandleExA(dwFlags uint32, lpModuleName unsafe.Pointer, phModule **exeHeader) bool

Expand Down
5 changes: 0 additions & 5 deletions src/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,6 @@ func llvm_sponentry() unsafe.Pointer
//export strlen
func strlen(ptr unsafe.Pointer) uintptr

// Special alloc function that should never actually be called.
// It is used instead of normal alloc in //go:noheap functions, and must either
// be optimized away or throw a linker error.
func alloc_noheap(size uintptr, layout unsafe.Pointer) unsafe.Pointer

//export malloc
func malloc(size uintptr) unsafe.Pointer

Expand Down
2 changes: 2 additions & 0 deletions src/runtime/runtime_arm7tdmi.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ var _sidata [0]byte
//go:extern _edata
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().
//
//export main
Expand Down
5 changes: 5 additions & 0 deletions src/runtime/runtime_nintendoswitch.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ package runtime

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 (
// Handles
infoTypeTotalMemorySize = 6 // Total amount of memory available for process.
Expand Down
Loading