Skip to content

Commit 85d223c

Browse files
committed
compiler: add //go:noheap pragma
1 parent 7b486e3 commit 85d223c

12 files changed

Lines changed: 135 additions & 10 deletions

File tree

builder/tools.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,12 +122,16 @@ func parseLLDErrors(text string) error {
122122
parsedError = true
123123
line, _ := strconv.Atoi(matches[3])
124124
// TODO: detect common mistakes like -gc=none?
125+
msg := "linker could not find symbol " + symbolName
126+
if symbolName == "runtime.alloc_noheap" {
127+
msg = "object allocated on the heap in //go:noheap function"
128+
}
125129
linkErrors = append(linkErrors, scanner.Error{
126130
Pos: token.Position{
127131
Filename: matches[2],
128132
Line: line,
129133
},
130-
Msg: "linker could not find symbol " + symbolName,
134+
Msg: msg,
131135
})
132136
}
133137
}

compiler/compiler.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,16 @@ func newBuilder(c *compilerContext, irbuilder llvm.Builder, f *ssa.Function) *bu
192192
}
193193
}
194194

195+
// Return the runtime.alloc function variant.
196+
// This is normally just "alloc", but is "alloc_noheap" if the //go:noheap
197+
// pragma is used.
198+
func (b *builder) allocFunc() string {
199+
if b.info.noheap {
200+
return "alloc_noheap"
201+
}
202+
return "alloc"
203+
}
204+
195205
type blockInfo struct {
196206
// entry is the LLVM basic block corresponding to the start of this *ssa.Block.
197207
entry llvm.BasicBlock
@@ -2173,7 +2183,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
21732183
}
21742184
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
21752185
layoutValue := b.createObjectLayout(typ, expr.Pos())
2176-
buf := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, layoutValue}, expr.Comment)
2186+
buf := b.createRuntimeCall(b.allocFunc(), []llvm.Value{sizeValue, layoutValue}, expr.Comment)
21772187
align := b.targetData.ABITypeAlignment(typ)
21782188
buf.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(align)))
21792189
return buf, nil
@@ -2405,7 +2415,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
24052415
}
24062416
sliceSize := b.CreateBinOp(llvm.Mul, elemSizeValue, sliceCapCast, "makeslice.cap")
24072417
layoutValue := b.createObjectLayout(llvmElemType, expr.Pos())
2408-
slicePtr := b.createRuntimeCall("alloc", []llvm.Value{sliceSize, layoutValue}, "makeslice.buf")
2418+
slicePtr := b.createRuntimeCall(b.allocFunc(), []llvm.Value{sliceSize, layoutValue}, "makeslice.buf")
24092419
slicePtr.AddCallSiteAttribute(0, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elemAlign)))
24102420

24112421
// Extend or truncate if necessary. This is safe as we've already done

compiler/defer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,7 @@ func (b *builder) createDefer(instr *ssa.Defer) {
488488
size := b.targetData.TypeAllocSize(deferredCallType)
489489
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
490490
nilPtr := llvm.ConstNull(b.dataPtrType)
491-
alloca = b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
491+
alloca = b.createRuntimeCall(b.allocFunc(), []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
492492
}
493493
if b.NeedsStackObjects {
494494
b.trackPointer(alloca)

compiler/llvm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
129129
// Packed data is bigger than a pointer, so allocate it on the heap.
130130
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
131131
align := b.targetData.ABITypeAlignment(packedType)
132-
packedAlloc := b.createRuntimeCall("alloc", []llvm.Value{
132+
packedAlloc := b.createRuntimeCall(b.allocFunc(), []llvm.Value{
133133
sizeValue,
134134
llvm.ConstNull(b.dataPtrType),
135135
}, "")

compiler/symbol.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type functionInfo struct {
3434
interrupt bool // go:interrupt
3535
nobounds bool // go:nobounds
3636
noescape bool // go:noescape
37+
noheap bool // go:noheap
3738
variadic bool // go:variadic (CGo only)
3839
inline inlineType // go:inline
3940
}
@@ -161,7 +162,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
161162
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
162163
case "machine.keepAliveNoEscape", "machine.unsafeNoEscape":
163164
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
164-
case "runtime.alloc":
165+
case "runtime.alloc", "runtime.alloc_noheap":
165166
// Tell the optimizer that runtime.alloc is an allocator, meaning that it
166167
// returns values that are never null and never alias to an existing value.
167168
for _, attrName := range []string{"noalias", "nonnull"} {
@@ -476,6 +477,9 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
476477
if len(f.Blocks) == 0 {
477478
info.noescape = true
478479
}
480+
case "//go:noheap":
481+
// Ensure this function does not allocate on the heap.
482+
info.noheap = true
479483
case "//go:variadic":
480484
// The //go:variadic pragma is emitted by the CGo preprocessing
481485
// pass for C variadic functions. This includes both explicit

compiler/testdata/pragma.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,8 @@ func doesNotEscapeParam(a *int, b []int, c chan int, d *[0]byte)
115115
//go:noescape
116116
func stillEscapes(a *int, b []int, c chan int, d *[0]byte) {
117117
}
118+
119+
//go:noheap
120+
func doesHeapAlloc() *int {
121+
return new(int)
122+
}

compiler/testdata/pragma.ll

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,18 @@ entry:
9090
ret void
9191
}
9292

93+
; Function Attrs: nounwind
94+
define hidden ptr @main.doesHeapAlloc(ptr %context) unnamed_addr #1 {
95+
entry:
96+
%stackalloc = alloca i8, align 1
97+
%new = call align 4 dereferenceable(4) ptr @runtime.alloc_noheap(i32 4, ptr nonnull inttoptr (i32 3 to ptr), ptr undef) #10
98+
call void @runtime.trackPointer(ptr nonnull %new, ptr nonnull %stackalloc, ptr undef) #10
99+
ret ptr %new
100+
}
101+
102+
; Function Attrs: allockind("alloc,zeroed") allocsize(0)
103+
declare noalias nonnull ptr @runtime.alloc_noheap(i32, ptr, ptr) #9
104+
93105
attributes #0 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
94106
attributes #1 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
95107
attributes #2 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-export-name"="extern_func" }
@@ -99,3 +111,5 @@ attributes #5 = { noinline nounwind "target-features"="+bulk-memory,+bulk-memory
99111
attributes #6 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-import-module"="modulename" "wasm-import-name"="import1" }
100112
attributes #7 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-import-module"="foobar" "wasm-import-name"="imported" }
101113
attributes #8 = { nounwind "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" "wasm-export-name"="exported" }
114+
attributes #9 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }
115+
attributes #10 = { nounwind }

errors_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ func TestErrors(t *testing.T) {
3838
{name: "loader-invaliddep"},
3939
{name: "loader-invalidpackage"},
4040
{name: "loader-nopackage"},
41+
{name: "noheap", target: "cortex-m-qemu"},
4142
{name: "optimizer"},
4243
{name: "syntax"},
4344
{name: "types"},

interp/interpreter.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent
299299
// means that monotonic time in the time package is counted from
300300
// time.Time{}.Sub(1), which should be fine.
301301
locals[inst.localIndex] = literalValue{uint64(0)}
302-
case callFn.name == "runtime.alloc":
302+
case callFn.name == "runtime.alloc" || callFn.name == "runtime.alloc_noheap":
303303
// Allocate heap memory. At compile time, this is instead done
304304
// by creating a global variable.
305305

src/runtime/runtime.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ func llvm_sponentry() unsafe.Pointer
6666
//export strlen
6767
func strlen(ptr unsafe.Pointer) uintptr
6868

69+
// Special alloc function that should never actually be called.
70+
// It is used instead of normal alloc in //go:noheap functions, and must either
71+
// be optimized away or throw a linker error.
72+
func alloc_noheap(size uintptr, layout unsafe.Pointer) unsafe.Pointer
73+
6974
//export malloc
7075
func malloc(size uintptr) unsafe.Pointer
7176

0 commit comments

Comments
 (0)