aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2023-07-07 15:15:49 +0200
committerRon Evans <[email protected]>2023-08-04 11:59:11 +0200
commita2f886a67a645add0c2e42289e20f89fe402294d (patch)
tree0d8e48a86eda24290e683c623a0fee3494916c26
parentf791c821ff5d393c875fe41889b94c19cf5f508d (diff)
downloadtinygo-a2f886a67a645add0c2e42289e20f89fe402294d.tar.gz
tinygo-a2f886a67a645add0c2e42289e20f89fe402294d.zip
compiler: implement clear builtin for slices
-rw-r--r--compiler/compiler.go35
-rw-r--r--compiler/intrinsics.go24
-rw-r--r--compiler/testdata/go1.21.go8
-rw-r--r--compiler/testdata/go1.21.ll40
-rw-r--r--testdata/go1.21.go6
-rw-r--r--testdata/go1.21.txt1
6 files changed, 94 insertions, 20 deletions
diff --git a/compiler/compiler.go b/compiler/compiler.go
index 46c3a0672..6af6debe8 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 5761a438f..af3a57de1 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 5541b489d..3d92b69b1 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 5d4a70197..73a368383 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 885e588da..184bb2d8a 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 ad81dcfe9..459631a30 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