Skip to content

Commit dfa9eb7

Browse files
committed
compiler: add //go:noescape pragma
This only works on declarations, not definitions. This is intentional: it follows the upstream Go implemetation. However, we might want to loosen this requirement at some point: TinyGo sometimes stores pointers in memory mapped I/O knowing they won't actually escape, but the compiler doesn't know about this.
1 parent 6384eca commit dfa9eb7

File tree

4 files changed

+44
-8
lines changed

4 files changed

+44
-8
lines changed

compiler/calls.go

+7-5
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,19 @@ const maxFieldsPerParam = 3
1919
// useful while declaring or defining a function.
2020
type paramInfo struct {
2121
llvmType llvm.Type
22-
name string // name, possibly with suffixes for e.g. struct fields
23-
elemSize uint64 // size of pointer element type, or 0 if this isn't a pointer
22+
name string // name, possibly with suffixes for e.g. struct fields
23+
elemSize uint64 // size of pointer element type, or 0 if this isn't a pointer
24+
flags paramFlags // extra flags for this parameter
2425
}
2526

2627
// paramFlags identifies parameter attributes for flags. Most importantly, it
2728
// determines which parameters are dereferenceable_or_null and which aren't.
2829
type paramFlags uint8
2930

3031
const (
31-
// Parameter may have the deferenceable_or_null attribute. This attribute
32-
// cannot be applied to unsafe.Pointer and to the data pointer of slices.
33-
paramIsDeferenceableOrNull = 1 << iota
32+
// Whether this is a full or partial Go parameter (int, slice, etc).
33+
// The extra context parameter is not a Go parameter.
34+
paramIsGoParam = 1 << iota
3435
)
3536

3637
// createRuntimeCallCommon creates a runtime call. Use createRuntimeCall or
@@ -195,6 +196,7 @@ func (c *compilerContext) getParamInfo(t llvm.Type, name string, goType types.Ty
195196
info := paramInfo{
196197
llvmType: t,
197198
name: name,
199+
flags: paramIsGoParam,
198200
}
199201
if goType != nil {
200202
switch underlying := goType.Underlying().(type) {

compiler/symbol.go

+20-3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type functionInfo struct {
3030
exported bool // go:export, CGo
3131
interrupt bool // go:interrupt
3232
nobounds bool // go:nobounds
33+
noescape bool // go:noescape
3334
variadic bool // go:variadic (CGo only)
3435
inline inlineType // go:inline
3536
}
@@ -124,11 +125,20 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
124125
c.addStandardDeclaredAttributes(llvmFn)
125126

126127
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
127-
for i, info := range paramInfos {
128-
if info.elemSize != 0 {
129-
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, info.elemSize)
128+
for i, paramInfo := range paramInfos {
129+
if paramInfo.elemSize != 0 {
130+
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, paramInfo.elemSize)
130131
llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull)
131132
}
133+
if info.noescape && paramInfo.flags&paramIsGoParam != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind {
134+
// Parameters to functions with a //go:noescape parameter should get
135+
// the nocapture attribute. However, the context parameter should
136+
// not.
137+
// (It may be safe to add the nocapture parameter to the context
138+
// parameter, but I'd like to stay on the safe side here).
139+
nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)
140+
llvmFn.AddAttributeAtIndex(i+1, nocapture)
141+
}
132142
}
133143

134144
// Set a number of function or parameter attributes, depending on the
@@ -325,6 +335,13 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
325335
if hasUnsafeImport(f.Pkg.Pkg) {
326336
info.nobounds = true
327337
}
338+
case "//go:noescape":
339+
// Don't let pointer parameters escape.
340+
// Following the upstream Go implementation, we only do this for
341+
// declarations, not definitions.
342+
if len(f.Blocks) == 0 {
343+
info.noescape = true
344+
}
328345
case "//go:variadic":
329346
// The //go:variadic pragma is emitted by the CGo preprocessing
330347
// pass for C variadic functions. This includes both explicit

compiler/testdata/pragma.go

+9
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,12 @@ var undefinedGlobalNotInSection uint32
9090
//go:align 1024
9191
//go:section .global_section
9292
var multipleGlobalPragmas uint32
93+
94+
//go:noescape
95+
func doesNotEscapeParam(a *int, b []int, c chan int, d *[0]byte)
96+
97+
// The //go:noescape pragma only works on declarations, not definitions.
98+
//
99+
//go:noescape
100+
func stillEscapes(a *int, b []int, c chan int, d *[0]byte) {
101+
}

compiler/testdata/pragma.ll

+8
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@ entry:
7272

7373
declare void @main.undefinedFunctionNotInSection(ptr) #1
7474

75+
declare void @main.doesNotEscapeParam(ptr nocapture dereferenceable_or_null(4), ptr nocapture, i32, i32, ptr nocapture dereferenceable_or_null(32), ptr nocapture, ptr) #1
76+
77+
; Function Attrs: nounwind
78+
define hidden void @main.stillEscapes(ptr dereferenceable_or_null(4) %a, ptr %b.data, i32 %b.len, i32 %b.cap, ptr dereferenceable_or_null(32) %c, ptr %d, ptr %context) unnamed_addr #2 {
79+
entry:
80+
ret void
81+
}
82+
7583
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
7684
attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
7785
attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }

0 commit comments

Comments
 (0)