Skip to content

Commit e940f30

Browse files
committed
compiler: add range param attr to slice/string len/cap
This attaches the maximum valid len/cap to string/slice fuction parameters. The compiler can use this to: 1. Prove that index math will not overflow 2. Prove that signed and unsigned comparisons are equivalent
1 parent cfd74c2 commit e940f30

File tree

14 files changed

+58
-33
lines changed

14 files changed

+58
-33
lines changed

builder/sizes_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func TestBinarySize(t *testing.T) {
4444
// microcontrollers
4545
{"hifive1b", "examples/echo", 3668, 280, 0, 2244},
4646
{"microbit", "examples/serial", 2694, 342, 8, 2248},
47-
{"wioterminal", "examples/pininterrupt", 6837, 1491, 120, 6888},
47+
{"wioterminal", "examples/pininterrupt", 6835, 1493, 120, 6888},
4848

4949
// TODO: also check wasm. Right now this is difficult, because
5050
// wasm binaries are run through wasm-opt and therefore the

compiler/calls.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type paramInfo struct {
2121
llvmType llvm.Type
2222
name string // name, possibly with suffixes for e.g. struct fields
2323
elemSize uint64 // size of pointer element type, or 0 if this isn't a pointer
24+
max uint64 // maximum value (for len/cap)
2425
flags paramFlags // extra flags for this parameter
2526
}
2627

@@ -171,6 +172,8 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType
171172
}
172173
suffix := strconv.Itoa(i)
173174
isString := false
175+
hasLengths := false
176+
var maxLen uint64
174177
if goType != nil {
175178
// Try to come up with a good suffix for this struct field,
176179
// depending on which Go type it's based on.
@@ -179,6 +182,9 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType
179182
suffix = []string{"typecode", "value"}[i]
180183
case *types.Slice:
181184
suffix = []string{"data", "len", "cap"}[i]
185+
maxLen = c.maxLen
186+
maxLen /= max(c.targetData.TypeAllocSize(c.getLLVMType(goType.Elem())), 1)
187+
hasLengths = true
182188
case *types.Struct:
183189
suffix = goType.Field(i).Name()
184190
case *types.Basic:
@@ -188,6 +194,8 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType
188194
case types.String:
189195
suffix = []string{"data", "len"}[i]
190196
isString = true
197+
hasLengths = true
198+
maxLen = c.maxLen
191199
}
192200
case *types.Signature:
193201
suffix = []string{"context", "funcptr"}[i]
@@ -197,6 +205,10 @@ func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType
197205
if isString {
198206
subInfos[0].flags |= paramIsReadonly
199207
}
208+
if hasLengths && i > 0 {
209+
// Add the max to a len/cap
210+
subInfos[0].max = maxLen
211+
}
200212
paramInfos = append(paramInfos, subInfos...)
201213
}
202214
return paramInfos

compiler/compiler.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ type compilerContext struct {
8484
funcPtrType llvm.Type // pointer in function address space (1 for AVR, 0 elsewhere)
8585
funcPtrAddrSpace int
8686
uintptrType llvm.Type
87+
maxLen uint64
8788
program *ssa.Program
8889
diagnostics []error
8990
functionInfos map[*ssa.Function]functionInfo
@@ -128,6 +129,7 @@ func newCompilerContext(moduleName string, machine llvm.TargetMachine, config *C
128129
panic("unknown pointer size")
129130
}
130131
c.dataPtrType = llvm.PointerType(c.ctx.Int8Type(), 0)
132+
c.maxLen = (uint64(1) << (c.uintptrType.IntTypeWidth() - 1)) - 1
131133

132134
dummyFuncType := llvm.FunctionType(c.ctx.VoidType(), nil, false)
133135
dummyFunc := llvm.AddFunction(c.mod, "tinygo.dummy", dummyFuncType)

compiler/symbol.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,18 @@ func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value)
128128
c.addStandardDeclaredAttributes(llvmFn)
129129

130130
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
131+
rangeKind := llvm.AttributeKindID("range")
131132
for i, paramInfo := range paramInfos {
132133
if paramInfo.elemSize != 0 {
133134
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, paramInfo.elemSize)
134135
llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull)
135136
}
137+
if paramInfo.max != 0 {
138+
rangeAttr := c.ctx.CreateSmallRangeAttribute(rangeKind, uint(paramInfo.llvmType.IntTypeWidth()), 0, paramInfo.max+1)
139+
if !rangeAttr.IsNil() {
140+
llvmFn.AddAttributeAtIndex(i+1, rangeAttr)
141+
}
142+
}
136143
if info.noescape && paramInfo.flags&paramIsGoParam != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind {
137144
// Parameters to functions with a //go:noescape parameter should get
138145
// the nocapture attribute. However, the context parameter should

compiler/testdata/channel.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ select.body: ; preds = %select.next
105105
br label %select.done
106106
}
107107

108-
declare { i32, i1 } @runtime.chanSelect(ptr, ptr, i32, i32, ptr, i32, i32, ptr) #1
108+
declare { i32, i1 } @runtime.chanSelect(ptr, ptr, i32 range(i32 0, 268435456), i32 range(i32 0, 268435456), ptr, i32 range(i32 0, 134217728), i32 range(i32 0, 134217728), ptr) #1
109109

110110
attributes #0 = { 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" }
111111
attributes #1 = { "target-features"="+bulk-memory,+bulk-memory-opt,+call-indirect-overlong,+mutable-globals,+nontrapping-fptoint,+sign-ext,-multivalue,-reference-types" }

compiler/testdata/go1.20.ll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ entry:
1717
}
1818

1919
; Function Attrs: nounwind
20-
define hidden ptr @main.unsafeSliceData(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 {
20+
define hidden ptr @main.unsafeSliceData(ptr %s.data, i32 range(i32 0, 536870912) %s.len, i32 range(i32 0, 536870912) %s.cap, ptr %context) unnamed_addr #2 {
2121
entry:
2222
%stackalloc = alloca i8, align 1
2323
call void @runtime.trackPointer(ptr %s.data, ptr nonnull %stackalloc, ptr undef) #3
@@ -50,7 +50,7 @@ unsafe.String.throw: ; preds = %entry
5050
declare void @runtime.unsafeSlicePanic(ptr) #1
5151

5252
; Function Attrs: nounwind
53-
define hidden ptr @main.unsafeStringData(ptr readonly %s.data, i32 %s.len, ptr %context) unnamed_addr #2 {
53+
define hidden ptr @main.unsafeStringData(ptr readonly %s.data, i32 range(i32 0, -2147483648) %s.len, ptr %context) unnamed_addr #2 {
5454
entry:
5555
%stackalloc = alloca i8, align 1
5656
call void @runtime.trackPointer(ptr %s.data, ptr nonnull %stackalloc, ptr undef) #3

compiler/testdata/go1.21.ll

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ entry:
9090
declare double @llvm.minimum.f64(double, double) #3
9191

9292
; Function Attrs: nounwind
93-
define hidden %runtime._string @main.minString(ptr readonly %a.data, i32 %a.len, ptr readonly %b.data, i32 %b.len, ptr %context) unnamed_addr #2 {
93+
define hidden %runtime._string @main.minString(ptr readonly %a.data, i32 range(i32 0, -2147483648) %a.len, ptr readonly %b.data, i32 range(i32 0, -2147483648) %b.len, ptr %context) unnamed_addr #2 {
9494
entry:
9595
%0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0
9696
%1 = insertvalue %runtime._string %0, i32 %a.len, 1
@@ -104,7 +104,7 @@ entry:
104104
ret %runtime._string %5
105105
}
106106

107-
declare i1 @runtime.stringLess(ptr readonly, i32, ptr readonly, i32, ptr) #1
107+
declare i1 @runtime.stringLess(ptr readonly, i32 range(i32 0, -2147483648), ptr readonly, i32 range(i32 0, -2147483648), ptr) #1
108108

109109
; Function Attrs: nounwind
110110
define hidden i32 @main.maxInt(i32 %a, i32 %b, ptr %context) unnamed_addr #2 {
@@ -137,7 +137,7 @@ entry:
137137
declare float @llvm.maximum.f32(float, float) #3
138138

139139
; Function Attrs: nounwind
140-
define hidden %runtime._string @main.maxString(ptr readonly %a.data, i32 %a.len, ptr readonly %b.data, i32 %b.len, ptr %context) unnamed_addr #2 {
140+
define hidden %runtime._string @main.maxString(ptr readonly %a.data, i32 range(i32 0, -2147483648) %a.len, ptr readonly %b.data, i32 range(i32 0, -2147483648) %b.len, ptr %context) unnamed_addr #2 {
141141
entry:
142142
%0 = insertvalue %runtime._string zeroinitializer, ptr %a.data, 0
143143
%1 = insertvalue %runtime._string %0, i32 %a.len, 1
@@ -152,9 +152,9 @@ entry:
152152
}
153153

154154
; Function Attrs: nounwind
155-
define hidden void @main.clearSlice(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 {
155+
define hidden void @main.clearSlice(ptr %s.data, i32 range(i32 0, 536870912) %s.len, i32 range(i32 0, 536870912) %s.cap, ptr %context) unnamed_addr #2 {
156156
entry:
157-
%0 = shl i32 %s.len, 2
157+
%0 = shl nuw nsw i32 %s.len, 2
158158
call void @llvm.memset.p0.i32(ptr align 4 %s.data, i8 0, i32 %0, i1 false)
159159
ret void
160160
}
@@ -163,7 +163,7 @@ entry:
163163
declare void @llvm.memset.p0.i32(ptr nocapture writeonly, i8, i32, i1 immarg) #4
164164

165165
; Function Attrs: nounwind
166-
define hidden void @main.clearZeroSizedSlice(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 {
166+
define hidden void @main.clearZeroSizedSlice(ptr %s.data, i32 range(i32 0, -2147483648) %s.len, i32 range(i32 0, -2147483648) %s.cap, ptr %context) unnamed_addr #2 {
167167
entry:
168168
ret void
169169
}

compiler/testdata/goroutine-cortex-m-qemu-tasks.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ entry:
132132
}
133133

134134
; Function Attrs: nounwind
135-
define hidden void @main.copyBuiltinGoroutine(ptr %dst.data, i32 %dst.len, i32 %dst.cap, ptr %src.data, i32 %src.len, i32 %src.cap, ptr %context) unnamed_addr #1 {
135+
define hidden void @main.copyBuiltinGoroutine(ptr %dst.data, i32 range(i32 0, -2147483648) %dst.len, i32 range(i32 0, -2147483648) %dst.cap, ptr %src.data, i32 range(i32 0, -2147483648) %src.len, i32 range(i32 0, -2147483648) %src.cap, ptr %context) unnamed_addr #1 {
136136
entry:
137137
%copy.n = call i32 @llvm.umin.i32(i32 %dst.len, i32 %src.len)
138138
call void @llvm.memmove.p0.p0.i32(ptr align 1 %dst.data, ptr align 1 %src.data, i32 %copy.n, i1 false)

compiler/testdata/goroutine-wasm-asyncify.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ entry:
141141
}
142142

143143
; Function Attrs: nounwind
144-
define hidden void @main.copyBuiltinGoroutine(ptr %dst.data, i32 %dst.len, i32 %dst.cap, ptr %src.data, i32 %src.len, i32 %src.cap, ptr %context) unnamed_addr #2 {
144+
define hidden void @main.copyBuiltinGoroutine(ptr %dst.data, i32 range(i32 0, -2147483648) %dst.len, i32 range(i32 0, -2147483648) %dst.cap, ptr %src.data, i32 range(i32 0, -2147483648) %src.len, i32 range(i32 0, -2147483648) %src.cap, ptr %context) unnamed_addr #2 {
145145
entry:
146146
%copy.n = call i32 @llvm.umin.i32(i32 %dst.len, i32 %src.len)
147147
call void @llvm.memmove.p0.p0.i32(ptr align 1 %dst.data, ptr align 1 %src.data, i32 %copy.n, i1 false)

compiler/testdata/pragma.ll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ 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(36), ptr nocapture, ptr) #1
88+
declare void @main.doesNotEscapeParam(ptr nocapture dereferenceable_or_null(4), ptr nocapture, i32 range(i32 0, 536870912), i32 range(i32 0, 536870912), ptr nocapture dereferenceable_or_null(36), ptr nocapture, ptr) #1
8989

9090
; 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(36) %c, ptr %d, ptr %context) unnamed_addr #2 {
91+
define hidden void @main.stillEscapes(ptr dereferenceable_or_null(4) %a, ptr %b.data, i32 range(i32 0, 536870912) %b.len, i32 range(i32 0, 536870912) %b.cap, ptr dereferenceable_or_null(36) %c, ptr %d, ptr %context) unnamed_addr #2 {
9292
entry:
9393
ret void
9494
}

0 commit comments

Comments
 (0)