aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2023-01-13 17:52:18 +0000
committerRon Evans <[email protected]>2023-01-17 08:38:54 +0100
commitc43958972c3ffcd51e65414a346e53779edb9f97 (patch)
tree8335e533ad0d96e4f074f49190af3e707d4785bc
parent33489d6344c05e14aaea8faf33a2b5b73c886215 (diff)
downloadtinygo-c43958972c3ffcd51e65414a346e53779edb9f97.tar.gz
tinygo-c43958972c3ffcd51e65414a346e53779edb9f97.zip
compiler: add support for new unsafe slice/string functions
This adds support for unsafe.SliceData, unsafe.String, and unsafe.SringData that were introduced in Go 1.20.
-rw-r--r--compiler/asserts.go11
-rw-r--r--compiler/compiler.go40
-rw-r--r--compiler/compiler_test.go10
-rw-r--r--compiler/testdata/go1.20.go15
-rw-r--r--compiler/testdata/go1.20.ll58
-rw-r--r--src/runtime/panic.go7
6 files changed, 120 insertions, 21 deletions
diff --git a/compiler/asserts.go b/compiler/asserts.go
index ba4824943..0fb112e0b 100644
--- a/compiler/asserts.go
+++ b/compiler/asserts.go
@@ -89,10 +89,11 @@ func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen i
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic")
}
-// createUnsafeSliceCheck inserts a runtime check used for unsafe.Slice. This
-// function must panic if the ptr/len parameters are invalid.
-func (b *builder) createUnsafeSliceCheck(ptr, len llvm.Value, elementType llvm.Type, lenType *types.Basic) {
- // From the documentation of unsafe.Slice:
+// createUnsafeSliceStringCheck inserts a runtime check used for unsafe.Slice
+// and unsafe.String. This function must panic if the ptr/len parameters are
+// invalid.
+func (b *builder) createUnsafeSliceStringCheck(name string, ptr, len llvm.Value, elementType llvm.Type, lenType *types.Basic) {
+ // From the documentation of unsafe.Slice and unsafe.String:
// > At run time, if len is negative, or if ptr is nil and len is not
// > zero, a run-time panic occurs.
// However, in practice, it is also necessary to check that the length is
@@ -117,7 +118,7 @@ func (b *builder) createUnsafeSliceCheck(ptr, len llvm.Value, elementType llvm.T
lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "")
assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
assert = b.CreateOr(assert, lenOutOfBounds, "")
- b.createRuntimeAssert(assert, "unsafe.Slice", "unsafeSlicePanic")
+ b.createRuntimeAssert(assert, name, "unsafeSlicePanic")
}
// createChanBoundsCheck creates a bounds check before creating a new channel to
diff --git a/compiler/compiler.go b/compiler/compiler.go
index 4abfdd559..aad5e0d9a 100644
--- a/compiler/compiler.go
+++ b/compiler/compiler.go
@@ -1646,20 +1646,20 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
case "Sizeof": // unsafe.Sizeof
size := b.targetData.TypeAllocSize(argValues[0].Type())
return llvm.ConstInt(b.uintptrType, size, false), nil
- case "Slice": // unsafe.Slice
- // This creates a slice from a pointer and a length.
+ case "Slice", "String": // unsafe.Slice, unsafe.String
+ // This creates a slice or string from a pointer and a length.
// Note that the exception mentioned in the documentation (if the
// pointer and length are nil, the slice is also nil) is trivially
// already the case.
ptr := argValues[0]
len := argValues[1]
- slice := llvm.Undef(b.ctx.StructType([]llvm.Type{
- ptr.Type(),
- b.uintptrType,
- b.uintptrType,
- }, false))
- elementType := b.getLLVMType(argTypes[0].Underlying().(*types.Pointer).Elem())
- b.createUnsafeSliceCheck(ptr, len, elementType, argTypes[1].Underlying().(*types.Basic))
+ var elementType llvm.Type
+ if callName == "Slice" {
+ elementType = b.getLLVMType(argTypes[0].Underlying().(*types.Pointer).Elem())
+ } else {
+ elementType = b.ctx.Int8Type()
+ }
+ b.createUnsafeSliceStringCheck("unsafe."+callName, ptr, len, elementType, argTypes[1].Underlying().(*types.Basic))
if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
// Too small, zero-extend len.
len = b.CreateZExt(len, b.uintptrType, "")
@@ -1667,10 +1667,24 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
// Too big, truncate len.
len = b.CreateTrunc(len, b.uintptrType, "")
}
- slice = b.CreateInsertValue(slice, ptr, 0, "")
- slice = b.CreateInsertValue(slice, len, 1, "")
- slice = b.CreateInsertValue(slice, len, 2, "")
- return slice, nil
+ if callName == "Slice" {
+ slice := llvm.Undef(b.ctx.StructType([]llvm.Type{
+ ptr.Type(),
+ b.uintptrType,
+ b.uintptrType,
+ }, false))
+ slice = b.CreateInsertValue(slice, ptr, 0, "")
+ slice = b.CreateInsertValue(slice, len, 1, "")
+ slice = b.CreateInsertValue(slice, len, 2, "")
+ return slice, nil
+ } else {
+ str := llvm.Undef(b.getLLVMRuntimeType("_string"))
+ str = b.CreateInsertValue(str, argValues[0], 0, "")
+ str = b.CreateInsertValue(str, len, 1, "")
+ return str, nil
+ }
+ case "SliceData", "StringData": // unsafe.SliceData, unsafe.StringData
+ return b.CreateExtractValue(argValues[0], 0, "slice.data"), nil
default:
return llvm.Value{}, b.makeError(pos, "todo: builtin: "+callName)
}
diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go
index a5cd9c6bf..f8221a08e 100644
--- a/compiler/compiler_test.go
+++ b/compiler/compiler_test.go
@@ -9,6 +9,7 @@ import (
"testing"
"github.com/tinygo-org/tinygo/compileopts"
+ "github.com/tinygo-org/tinygo/goenv"
"github.com/tinygo-org/tinygo/loader"
"tinygo.org/x/go-llvm"
)
@@ -27,6 +28,12 @@ type testCase struct {
func TestCompiler(t *testing.T) {
t.Parallel()
+ // Determine Go minor version (e.g. 16 in go1.16.3).
+ _, goMinor, err := goenv.GetGorootVersion(goenv.Get("GOROOT"))
+ if err != nil {
+ t.Fatal("could not read Go version:", err)
+ }
+
// Determine which tests to run, depending on the Go and LLVM versions.
tests := []testCase{
{"basic.go", "", ""},
@@ -43,6 +50,9 @@ func TestCompiler(t *testing.T) {
{"channel.go", "", ""},
{"gc.go", "", ""},
}
+ if goMinor >= 20 {
+ tests = append(tests, testCase{"go1.20.go", "", ""})
+ }
for _, tc := range tests {
name := tc.file
diff --git a/compiler/testdata/go1.20.go b/compiler/testdata/go1.20.go
new file mode 100644
index 000000000..be65363ec
--- /dev/null
+++ b/compiler/testdata/go1.20.go
@@ -0,0 +1,15 @@
+package main
+
+import "unsafe"
+
+func unsafeSliceData(s []int) *int {
+ return unsafe.SliceData(s)
+}
+
+func unsafeString(ptr *byte, len int16) string {
+ return unsafe.String(ptr, len)
+}
+
+func unsafeStringData(s string) *byte {
+ return unsafe.StringData(s)
+}
diff --git a/compiler/testdata/go1.20.ll b/compiler/testdata/go1.20.ll
new file mode 100644
index 000000000..fc66b47c5
--- /dev/null
+++ b/compiler/testdata/go1.20.ll
@@ -0,0 +1,58 @@
+; ModuleID = 'go1.20.go'
+source_filename = "go1.20.go"
+target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20"
+target triple = "wasm32-unknown-wasi"
+
+%runtime._string = type { ptr, i32 }
+
+declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0
+
+declare void @runtime.trackPointer(ptr nocapture readonly, ptr) #0
+
+; Function Attrs: nounwind
+define hidden void @main.init(ptr %context) unnamed_addr #1 {
+entry:
+ ret void
+}
+
+; Function Attrs: nounwind
+define hidden ptr @main.unsafeSliceData(ptr %s.data, i32 %s.len, i32 %s.cap, ptr %context) unnamed_addr #1 {
+entry:
+ call void @runtime.trackPointer(ptr %s.data, ptr undef) #2
+ ret ptr %s.data
+}
+
+; Function Attrs: nounwind
+define hidden %runtime._string @main.unsafeString(ptr dereferenceable_or_null(1) %ptr, i16 %len, ptr %context) unnamed_addr #1 {
+entry:
+ %0 = icmp slt i16 %len, 0
+ %1 = icmp eq ptr %ptr, null
+ %2 = icmp ne i16 %len, 0
+ %3 = and i1 %1, %2
+ %4 = or i1 %3, %0
+ br i1 %4, label %unsafe.String.throw, label %unsafe.String.next
+
+unsafe.String.next: ; preds = %entry
+ %5 = zext i16 %len to i32
+ %6 = insertvalue %runtime._string undef, ptr %ptr, 0
+ %7 = insertvalue %runtime._string %6, i32 %5, 1
+ call void @runtime.trackPointer(ptr %ptr, ptr undef) #2
+ ret %runtime._string %7
+
+unsafe.String.throw: ; preds = %entry
+ call void @runtime.unsafeSlicePanic(ptr undef) #2
+ unreachable
+}
+
+declare void @runtime.unsafeSlicePanic(ptr) #0
+
+; Function Attrs: nounwind
+define hidden ptr @main.unsafeStringData(ptr %s.data, i32 %s.len, ptr %context) unnamed_addr #1 {
+entry:
+ call void @runtime.trackPointer(ptr %s.data, ptr undef) #2
+ ret ptr %s.data
+}
+
+attributes #0 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
+attributes #1 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" }
+attributes #2 = { nounwind }
diff --git a/src/runtime/panic.go b/src/runtime/panic.go
index d7ba9ded8..c9c69363b 100644
--- a/src/runtime/panic.go
+++ b/src/runtime/panic.go
@@ -143,10 +143,11 @@ func sliceToArrayPointerPanic() {
runtimePanic("slice smaller than array")
}
-// Panic when calling unsafe.Slice() (Go 1.17+) with a len that's too large
-// (which includes if the ptr is nil and len is nonzero).
+// Panic when calling unsafe.Slice() (Go 1.17+) or unsafe.String() (Go 1.20+)
+// with a len that's too large (which includes if the ptr is nil and len is
+// nonzero).
func unsafeSlicePanic() {
- runtimePanic("unsafe.Slice: len out of range")
+ runtimePanic("unsafe.Slice/String: len out of range")
}
// Panic when trying to create a new channel that is too big.