Browse Source

compiler: implement clear builtin for slices

pull/3853/head
Ayke van Laethem 1 year ago
committed by Ron Evans
parent
commit
a2f886a67a
  1. 35
      compiler/compiler.go
  2. 24
      compiler/intrinsics.go
  3. 8
      compiler/testdata/go1.21.go
  4. 40
      compiler/testdata/go1.21.ll
  5. 6
      testdata/go1.21.go
  6. 1
      testdata/go1.21.txt

35
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]

24
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() {

8
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)
}

40
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 }

6
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])
}

1
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

Loading…
Cancel
Save