From db0a706b59c2cd5cfd01c5b1d12054091605c982 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Fri, 7 Jul 2023 15:15:49 +0200 Subject: [PATCH] compiler: implement clear builtin for slices --- compiler/compiler.go | 35 ++++++++++++++++++++++++++++++++ compiler/intrinsics.go | 24 +++++++++++++--------- compiler/testdata/go1.21.go | 8 ++++++++ compiler/testdata/go1.21.ll | 40 +++++++++++++++++++++++++++---------- testdata/go1.21.go | 6 ++++++ testdata/go1.21.txt | 1 + 6 files changed, 94 insertions(+), 20 deletions(-) diff --git a/compiler/compiler.go b/compiler/compiler.go index 46c3a0672e..6af6debe89 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1600,6 +1600,41 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c cplx = b.CreateInsertValue(cplx, r, 0, "") cplx = b.CreateInsertValue(cplx, i, 1, "") return cplx, nil + case "clear": + value := argValues[0] + switch typ := argTypes[0].Underlying().(type) { + case *types.Slice: + elementType := b.getLLVMType(typ.Elem()) + elementSize := b.targetData.TypeAllocSize(elementType) + elementAlign := b.targetData.ABITypeAlignment(elementType) + + // The pointer to the data to be cleared. + llvmBuf := b.CreateExtractValue(value, 0, "buf") + if llvmBuf.Type() != b.i8ptrType { // compatibility with LLVM 14 + llvmBuf = b.CreateBitCast(llvmBuf, b.i8ptrType, "") + } + + // The length (in bytes) to be cleared. + llvmLen := b.CreateExtractValue(value, 1, "len") + llvmLen = b.CreateMul(llvmLen, llvm.ConstInt(llvmLen.Type(), elementSize, false), "") + + // Do the clear operation using the LLVM memset builtin. + // This is also correct for nil slices: in those cases, len will be + // 0 which means the memset call is a no-op (according to the LLVM + // LangRef). + memset := b.getMemsetFunc() + call := b.createCall(memset.GlobalValueType(), memset, []llvm.Value{ + llvmBuf, // dest + llvm.ConstInt(b.ctx.Int8Type(), 0, false), // val + llvmLen, // len + llvm.ConstInt(b.ctx.Int1Type(), 0, false), // isVolatile + }, "") + call.AddCallSiteAttribute(1, b.ctx.CreateEnumAttribute(llvm.AttributeKindID("align"), uint64(elementAlign))) + + return llvm.Value{}, nil + default: + return llvm.Value{}, b.makeError(pos, "unsupported type in clear builtin: "+typ.String()) + } case "copy": dst := argValues[0] src := argValues[1] diff --git a/compiler/intrinsics.go b/compiler/intrinsics.go index 5761a438f1..af3a57de1b 100644 --- a/compiler/intrinsics.go +++ b/compiler/intrinsics.go @@ -70,15 +70,7 @@ func (b *builder) createMemoryCopyImpl() { // regular libc memset calls if they aren't optimized out in a different way. func (b *builder) createMemoryZeroImpl() { b.createFunctionStart(true) - fnName := "llvm.memset.p0.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) - if llvmutil.Major() < 15 { // compatibility with LLVM 14 - fnName = "llvm.memset.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth()) - } - llvmFn := b.mod.NamedFunction(fnName) - if llvmFn.IsNil() { - fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.ctx.Int8Type(), b.uintptrType, b.ctx.Int1Type()}, false) - llvmFn = llvm.AddFunction(b.mod, fnName, fnType) - } + llvmFn := b.getMemsetFunc() params := []llvm.Value{ b.getValue(b.fn.Params[0], getPos(b.fn)), llvm.ConstInt(b.ctx.Int8Type(), 0, false), @@ -89,6 +81,20 @@ func (b *builder) createMemoryZeroImpl() { b.CreateRetVoid() } +// Return the llvm.memset.p0.i8 function declaration. +func (c *compilerContext) getMemsetFunc() llvm.Value { + fnName := "llvm.memset.p0.i" + strconv.Itoa(c.uintptrType.IntTypeWidth()) + if llvmutil.Major() < 15 { // compatibility with LLVM 14 + fnName = "llvm.memset.p0i8.i" + strconv.Itoa(c.uintptrType.IntTypeWidth()) + } + llvmFn := c.mod.NamedFunction(fnName) + if llvmFn.IsNil() { + fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType, c.ctx.Int8Type(), c.uintptrType, c.ctx.Int1Type()}, false) + llvmFn = llvm.AddFunction(c.mod, fnName, fnType) + } + return llvmFn +} + // createKeepAlive creates the runtime.KeepAlive function. It is implemented // using inline assembly. func (b *builder) createKeepAliveImpl() { diff --git a/compiler/testdata/go1.21.go b/compiler/testdata/go1.21.go index 5541b489d1..3d92b69b17 100644 --- a/compiler/testdata/go1.21.go +++ b/compiler/testdata/go1.21.go @@ -51,3 +51,11 @@ func maxFloat32(a, b float32) float32 { func maxString(a, b string) string { return max(a, b) } + +func clearSlice(s []int) { + clear(s) +} + +func clearZeroSizedSlice(s []struct{}) { + clear(s) +} diff --git a/compiler/testdata/go1.21.ll b/compiler/testdata/go1.21.ll index 5d4a70197e..73a3683839 100644 --- a/compiler/testdata/go1.21.ll +++ b/compiler/testdata/go1.21.ll @@ -84,10 +84,10 @@ entry: %2 = insertvalue %runtime._string zeroinitializer, ptr %b.data, 0 %3 = insertvalue %runtime._string %2, i32 %b.len, 1 %stackalloc = alloca i8, align 1 - %4 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr undef) #4 + %4 = call i1 @runtime.stringLess(ptr %a.data, i32 %a.len, ptr %b.data, i32 %b.len, ptr undef) #5 %5 = select i1 %4, %runtime._string %1, %runtime._string %3 %6 = extractvalue %runtime._string %5, 0 - call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #4 + call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #5 ret %runtime._string %5 } @@ -123,30 +123,48 @@ entry: %2 = insertvalue %runtime._string zeroinitializer, ptr %b.data, 0 %3 = insertvalue %runtime._string %2, i32 %b.len, 1 %stackalloc = alloca i8, align 1 - %4 = call i1 @runtime.stringLess(ptr %b.data, i32 %b.len, ptr %a.data, i32 %a.len, ptr undef) #4 + %4 = call i1 @runtime.stringLess(ptr %b.data, i32 %b.len, ptr %a.data, i32 %a.len, ptr undef) #5 %5 = select i1 %4, %runtime._string %1, %runtime._string %3 %6 = extractvalue %runtime._string %5, 0 - call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #4 + call void @runtime.trackPointer(ptr %6, ptr nonnull %stackalloc, ptr undef) #5 ret %runtime._string %5 } +; Function Attrs: nounwind +define hidden void @main.clearSlice(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 { +entry: + %0 = shl i32 %s.len, 2 + call void @llvm.memset.p0.i32(ptr align 4 %s.data, i8 0, i32 %0, i1 false) + ret void +} + +; Function Attrs: argmemonly nocallback nofree nounwind willreturn writeonly +declare void @llvm.memset.p0.i32(ptr nocapture writeonly, i8, i32, i1 immarg) #3 + +; Function Attrs: nounwind +define hidden void @main.clearZeroSizedSlice(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #2 { +entry: + ret void +} + ; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn -declare i32 @llvm.smin.i32(i32, i32) #3 +declare i32 @llvm.smin.i32(i32, i32) #4 ; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn -declare i8 @llvm.umin.i8(i8, i8) #3 +declare i8 @llvm.umin.i8(i8, i8) #4 ; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn -declare i32 @llvm.umin.i32(i32, i32) #3 +declare i32 @llvm.umin.i32(i32, i32) #4 ; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn -declare i32 @llvm.smax.i32(i32, i32) #3 +declare i32 @llvm.smax.i32(i32, i32) #4 ; Function Attrs: nocallback nofree nosync nounwind readnone speculatable willreturn -declare i32 @llvm.umax.i32(i32, i32) #3 +declare i32 @llvm.umax.i32(i32, i32) #4 attributes #0 = { allockind("alloc,zeroed") allocsize(0) "alloc-family"="runtime.alloc" "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } attributes #1 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } attributes #2 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } -attributes #3 = { nocallback nofree nosync nounwind readnone speculatable willreturn } -attributes #4 = { nounwind } +attributes #3 = { argmemonly nocallback nofree nounwind willreturn writeonly } +attributes #4 = { nocallback nofree nosync nounwind readnone speculatable willreturn } +attributes #5 = { nounwind } diff --git a/testdata/go1.21.go b/testdata/go1.21.go index 885e588da8..184bb2d8a7 100644 --- a/testdata/go1.21.go +++ b/testdata/go1.21.go @@ -1,6 +1,7 @@ package main func main() { + // The new min/max builtins. ia := 1 ib := 5 ic := -3 @@ -9,4 +10,9 @@ func main() { fc := -3.0 println("min/max:", min(ia, ib, ic), max(ia, ib, ic)) println("min/max:", min(fa, fb, fc), max(fa, fb, fc)) + + // The clear builtin, for slices. + s := []int{1, 2, 3, 4, 5} + clear(s[:3]) + println("cleared s[:3]:", s[0], s[1], s[2], s[3], s[4]) } diff --git a/testdata/go1.21.txt b/testdata/go1.21.txt index ad81dcfe96..459631a302 100644 --- a/testdata/go1.21.txt +++ b/testdata/go1.21.txt @@ -1,2 +1,3 @@ min/max: -3 5 min/max: -3.000000e+000 +5.000000e+000 +cleared s[:3]: 0 0 0 4 5