Skip to content

Commit db0a706

Browse files
committed
compiler: implement clear builtin for slices
1 parent 9b5619d commit db0a706

File tree

6 files changed

+94
-20
lines changed

6 files changed

+94
-20
lines changed

compiler/compiler.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1600,6 +1600,41 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
16001600
cplx = b.CreateInsertValue(cplx, r, 0, "")
16011601
cplx = b.CreateInsertValue(cplx, i, 1, "")
16021602
return cplx, nil
1603+
case "clear":
1604+
value := argValues[0]
1605+
switch typ := argTypes[0].Underlying().(type) {
1606+
case *types.Slice:
1607+
elementType := b.getLLVMType(typ.Elem())
1608+
elementSize := b.targetData.TypeAllocSize(elementType)
1609+
elementAlign := b.targetData.ABITypeAlignment(elementType)
1610+
1611+
// The pointer to the data to be cleared.
1612+
llvmBuf := b.CreateExtractValue(value, 0, "buf")
1613+
if llvmBuf.Type() != b.i8ptrType { // compatibility with LLVM 14
1614+
llvmBuf = b.CreateBitCast(llvmBuf, b.i8ptrType, "")
1615+
}
1616+
1617+
// The length (in bytes) to be cleared.
1618+
llvmLen := b.CreateExtractValue(value, 1, "len")
1619+
llvmLen = b.CreateMul(llvmLen, llvm.ConstInt(llvmLen.Type(), elementSize, false), "")
1620+
1621+
// Do the clear operation using the LLVM memset builtin.
1622+
// This is also correct for nil slices: in those cases, len will be
1623+
// 0 which means the memset call is a no-op (according to the LLVM
1624+
// LangRef).
1625+
memset := b.getMemsetFunc()
1626+
call := b.createCall(memset.GlobalValueType(), memset, []llvm.Value{
1627+
llvmBuf, // dest
1628+
llvm.ConstInt(b.ctx.Int8Type(), 0, false), // val
1629+
llvmLen, // len
1630+
llvm.ConstInt(b.ctx.Int1Type(), 0, false), // isVolatile
1631+
}, "")
1632+
call.AddCallSiteAttribute(1, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elementAlign)))
1633+
1634+
return llvm.Value{}, nil
1635+
default:
1636+
return llvm.Value{}, b.makeError(pos, "unsupported type in clear builtin: "+typ.String())
1637+
}
16031638
case "copy":
16041639
dst := argValues[0]
16051640
src := argValues[1]

compiler/intrinsics.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,7 @@ func (b *builder) createMemoryCopyImpl() {
7070
// regular libc memset calls if they aren't optimized out in a different way.
7171
func (b *builder) createMemoryZeroImpl() {
7272
b.createFunctionStart(true)
73-
fnName := "llvm.memset.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
74-
if llvmutil.Major() < 15 { // compatibility with LLVM 14
75-
fnName = "llvm.memset.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
76-
}
77-
llvmFn := b.mod.NamedFunction(fnName)
78-
if llvmFn.IsNil() {
79-
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.ctx.Int8Type(), b.uintptrType, b.ctx.Int1Type()}, false)
80-
llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
81-
}
73+
llvmFn := b.getMemsetFunc()
8274
params := []llvm.Value{
8375
b.getValue(b.fn.Params[0], getPos(b.fn)),
8476
llvm.ConstInt(b.ctx.Int8Type(), 0, false),
@@ -89,6 +81,20 @@ func (b *builder) createMemoryZeroImpl() {
8981
b.CreateRetVoid()
9082
}
9183

84+
// Return the llvm.memset.p0.i8 function declaration.
85+
func (c *compilerContext) getMemsetFunc() llvm.Value {
86+
fnName := "llvm.memset.p0.i" + strconv.Itoa(c.uintptrType.IntTypeWidth())
87+
if llvmutil.Major() < 15 { // compatibility with LLVM 14
88+
fnName = "llvm.memset.p0i8.i" + strconv.Itoa(c.uintptrType.IntTypeWidth())
89+
}
90+
llvmFn := c.mod.NamedFunction(fnName)
91+
if llvmFn.IsNil() {
92+
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType, c.ctx.Int8Type(), c.uintptrType, c.ctx.Int1Type()}, false)
93+
llvmFn = llvm.AddFunction(c.mod, fnName, fnType)
94+
}
95+
return llvmFn
96+
}
97+
9298
// createKeepAlive creates the runtime.KeepAlive function. It is implemented
9399
// using inline assembly.
94100
func (b *builder) createKeepAliveImpl() {

compiler/testdata/go1.21.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,11 @@ func maxFloat32(a, b float32) float32 {
5151
func maxString(a, b string) string {
5252
return max(a, b)
5353
}
54+
55+
func clearSlice(s []int) {
56+
clear(s)
57+
}
58+
59+
func clearZeroSizedSlice(s []struct{}) {
60+
clear(s)
61+
}

compiler/testdata/go1.21.ll

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ entry:
8484
%2 = insertvalue %runtime._string zeroinitializer, ptr %b.data, 0
8585
%3 = insertvalue %runtime._string %2, i32 %b.len, 1
8686
%stackalloc = alloca i8, align 1
87-
%4 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr undef) #4
87+
%4 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr undef) #5
8888
%5 = select i1 %4, %runtime._string %1, %runtime._string %3
8989
%6 = extractvalue %runtime._string %5, 0
90-
call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #4
90+
call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #5
9191
ret %runtime._string %5
9292
}
9393

@@ -123,30 +123,48 @@ entry:
123123
%2 = insertvalue %runtime._string zeroinitializer, ptr %b.data, 0
124124
%3 = insertvalue %runtime._string %2, i32 %b.len, 1
125125
%stackalloc = alloca i8, align 1
126-
%4 = call i1 @runtime.stringLess(ptr %b.data, i32 %b.len, ptr %a.data, i32 %a.len, ptr undef) #4
126+
%4 = call i1 @runtime.stringLess(ptr %b.data, i32 %b.len, ptr %a.data, i32 %a.len, ptr undef) #5
127127
%5 = select i1 %4, %runtime._string %1, %runtime._string %3
128128
%6 = extractvalue %runtime._string %5, 0
129-
call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #4
129+
call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #5
130130
ret %runtime._string %5
131131
}
132132

133+
; Function Attrs: nounwind
134+
define hidden void @main.clearSlice(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 {
135+
entry:
136+
%0 = shl i32 %s.len, 2
137+
call void @llvm.memset.p0.i32(ptr align 4 %s.data, i8 0, i32 %0, i1 false)
138+
ret void
139+
}
140+
141+
; Function Attrs: argmemonly nocallback nofree nounwind willreturn writeonly
142+
declare void @llvm.memset.p0.i32(ptr nocapture writeonly, i8, i32, i1 immarg) #3
143+
144+
; Function Attrs: nounwind
145+
define hidden void @main.clearZeroSizedSlice(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 {
146+
entry:
147+
ret void
148+
}
149+
133150
; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn
134-
declare i32 @llvm.smin.i32(i32, i32) #3
151+
declare i32 @llvm.smin.i32(i32, i32) #4
135152

136153
; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn
137-
declare i8 @llvm.umin.i8(i8, i8) #3
154+
declare i8 @llvm.umin.i8(i8, i8) #4
138155

139156
; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn
140-
declare i32 @llvm.umin.i32(i32, i32) #3
157+
declare i32 @llvm.umin.i32(i32, i32) #4
141158

142159
; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn
143-
declare i32 @llvm.smax.i32(i32, i32) #3
160+
declare i32 @llvm.smax.i32(i32, i32) #4
144161

145162
; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn
146-
declare i32 @llvm.umax.i32(i32, i32) #3
163+
declare i32 @llvm.umax.i32(i32, i32) #4
147164

148165
attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
149166
attributes #1 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
150167
attributes #2 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
151-
attributes #3 = { nocallback nofree nosync nounwind readnone speculatable willreturn }
152-
attributes #4 = { nounwind }
168+
attributes #3 = { argmemonly nocallback nofree nounwind willreturn writeonly }
169+
attributes #4 = { nocallback nofree nosync nounwind readnone speculatable willreturn }
170+
attributes #5 = { nounwind }

testdata/go1.21.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
func main() {
4+
// The new min/max builtins.
45
ia := 1
56
ib := 5
67
ic := -3
@@ -9,4 +10,9 @@ func main() {
910
fc := -3.0
1011
println("min/max:", min(ia, ib, ic), max(ia, ib, ic))
1112
println("min/max:", min(fa, fb, fc), max(fa, fb, fc))
13+
14+
// The clear builtin, for slices.
15+
s := []int{1, 2, 3, 4, 5}
16+
clear(s[:3])
17+
println("cleared s[:3]:", s[0], s[1], s[2], s[3], s[4])
1218
}

testdata/go1.21.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
min/max: -3 5
22
min/max: -3.000000e+000 +5.000000e+000
3+
cleared s[:3]: 0 0 0 4 5

0 commit comments

Comments
 (0)