Skip to content

Commit 83ec9e8

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 0087d4c commit 83ec9e8

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
@@ -33,6 +33,7 @@ type functionInfo struct {
3333
exported bool // go:export, CGo
3434
interrupt bool // go:interrupt
3535
nobounds bool // go:nobounds
36+
noescape bool // go:noescape
3637
variadic bool // go:variadic (CGo only)
3738
inline inlineType // go:inline
3839
}
@@ -127,11 +128,20 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
127128
c.addStandardDeclaredAttributes(llvmFn)
128129

129130
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
130-
for i, info := range paramInfos {
131-
if info.elemSize != 0 {
132-
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, info.elemSize)
131+
for i, paramInfo := range paramInfos {
132+
if paramInfo.elemSize != 0 {
133+
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, paramInfo.elemSize)
133134
llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull)
134135
}
136+
if info.noescape && paramInfo.flags&paramIsGoParam != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind {
137+
// Parameters to functions with a //go:noescape parameter should get
138+
// the nocapture attribute. However, the context parameter should
139+
// not.
140+
// (It may be safe to add the nocapture parameter to the context
141+
// parameter, but I'd like to stay on the safe side here).
142+
nocapture := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0)
143+
llvmFn.AddAttributeAtIndex(i+1, nocapture)
144+
}
135145
}
136146

137147
// Set a number of function or parameter attributes, depending on the
@@ -394,6 +404,13 @@ func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
394404
if hasUnsafeImport(f.Pkg.Pkg) {
395405
info.nobounds = true
396406
}
407+
case "//go:noescape":
408+
// Don't let pointer parameters escape.
409+
// Following the upstream Go implementation, we only do this for
410+
// declarations, not definitions.
411+
if len(f.Blocks) == 0 {
412+
info.noescape = true
413+
}
397414
case "//go:variadic":
398415
// The //go:variadic pragma is emitted by the CGo preprocessing
399416
// pass for C variadic functions. This includes both explicit

compiler/testdata/pragma.go

+9
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,12 @@ var undefinedGlobalNotInSection uint32
106106
//go:align 1024
107107
//go:section .global_section
108108
var multipleGlobalPragmas uint32
109+
110+
//go:noescape
111+
func doesNotEscapeParam(a *int, b []int, c chan int, d *[0]byte)
112+
113+
// The //go:noescape pragma only works on declarations, not definitions.
114+
//
115+
//go:noescape
116+
func stillEscapes(a *int, b []int, c chan int, d *[0]byte) {
117+
}

compiler/testdata/pragma.ll

+8
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,14 @@ entry:
8585

8686
declare void @main.undefinedFunctionNotInSection(ptr) #1
8787

88+
declare void @main.doesNotEscapeParam(ptr nocapture dereferenceable_or_null(4), ptr nocapture, i32, i32, ptr nocapture dereferenceable_or_null(32), ptr nocapture, ptr) #1
89+
90+
; Function Attrs: nounwind
91+
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 {
92+
entry:
93+
ret void
94+
}
95+
8896
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
8997
attributes #1 = { "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }
9098
attributes #2 = { nounwind "target-features"="+bulk-memory,+mutable-globals,+nontrapping-fptoint,+sign-ext" }

0 commit comments

Comments
 (0)