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.
Этот коммит содержится в:
родитель
33489d6344
коммит
c43958972c
6 изменённых файлов: 120 добавлений и 21 удалений
|
@ -89,10 +89,11 @@ func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen i
|
||||||
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic")
|
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic")
|
||||||
}
|
}
|
||||||
|
|
||||||
// createUnsafeSliceCheck inserts a runtime check used for unsafe.Slice. This
|
// createUnsafeSliceStringCheck inserts a runtime check used for unsafe.Slice
|
||||||
// function must panic if the ptr/len parameters are invalid.
|
// and unsafe.String. This function must panic if the ptr/len parameters are
|
||||||
func (b *builder) createUnsafeSliceCheck(ptr, len llvm.Value, elementType llvm.Type, lenType *types.Basic) {
|
// invalid.
|
||||||
// From the documentation of unsafe.Slice:
|
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
|
// > At run time, if len is negative, or if ptr is nil and len is not
|
||||||
// > zero, a run-time panic occurs.
|
// > zero, a run-time panic occurs.
|
||||||
// However, in practice, it is also necessary to check that the length is
|
// 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, "")
|
lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "")
|
||||||
assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
|
assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
|
||||||
assert = b.CreateOr(assert, lenOutOfBounds, "")
|
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
|
// createChanBoundsCheck creates a bounds check before creating a new channel to
|
||||||
|
|
|
@ -1646,20 +1646,20 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c
|
||||||
case "Sizeof": // unsafe.Sizeof
|
case "Sizeof": // unsafe.Sizeof
|
||||||
size := b.targetData.TypeAllocSize(argValues[0].Type())
|
size := b.targetData.TypeAllocSize(argValues[0].Type())
|
||||||
return llvm.ConstInt(b.uintptrType, size, false), nil
|
return llvm.ConstInt(b.uintptrType, size, false), nil
|
||||||
case "Slice": // unsafe.Slice
|
case "Slice", "String": // unsafe.Slice, unsafe.String
|
||||||
// This creates a slice from a pointer and a length.
|
// This creates a slice or string from a pointer and a length.
|
||||||
// Note that the exception mentioned in the documentation (if the
|
// Note that the exception mentioned in the documentation (if the
|
||||||
// pointer and length are nil, the slice is also nil) is trivially
|
// pointer and length are nil, the slice is also nil) is trivially
|
||||||
// already the case.
|
// already the case.
|
||||||
ptr := argValues[0]
|
ptr := argValues[0]
|
||||||
len := argValues[1]
|
len := argValues[1]
|
||||||
slice := llvm.Undef(b.ctx.StructType([]llvm.Type{
|
var elementType llvm.Type
|
||||||
ptr.Type(),
|
if callName == "Slice" {
|
||||||
b.uintptrType,
|
elementType = b.getLLVMType(argTypes[0].Underlying().(*types.Pointer).Elem())
|
||||||
b.uintptrType,
|
} else {
|
||||||
}, false))
|
elementType = b.ctx.Int8Type()
|
||||||
elementType := b.getLLVMType(argTypes[0].Underlying().(*types.Pointer).Elem())
|
}
|
||||||
b.createUnsafeSliceCheck(ptr, len, elementType, argTypes[1].Underlying().(*types.Basic))
|
b.createUnsafeSliceStringCheck("unsafe."+callName, ptr, len, elementType, argTypes[1].Underlying().(*types.Basic))
|
||||||
if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
|
if len.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
|
||||||
// Too small, zero-extend len.
|
// Too small, zero-extend len.
|
||||||
len = b.CreateZExt(len, b.uintptrType, "")
|
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.
|
// Too big, truncate len.
|
||||||
len = b.CreateTrunc(len, b.uintptrType, "")
|
len = b.CreateTrunc(len, b.uintptrType, "")
|
||||||
}
|
}
|
||||||
slice = b.CreateInsertValue(slice, ptr, 0, "")
|
if callName == "Slice" {
|
||||||
slice = b.CreateInsertValue(slice, len, 1, "")
|
slice := llvm.Undef(b.ctx.StructType([]llvm.Type{
|
||||||
slice = b.CreateInsertValue(slice, len, 2, "")
|
ptr.Type(),
|
||||||
return slice, nil
|
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:
|
default:
|
||||||
return llvm.Value{}, b.makeError(pos, "todo: builtin: "+callName)
|
return llvm.Value{}, b.makeError(pos, "todo: builtin: "+callName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
"github.com/tinygo-org/tinygo/compileopts"
|
||||||
|
"github.com/tinygo-org/tinygo/goenv"
|
||||||
"github.com/tinygo-org/tinygo/loader"
|
"github.com/tinygo-org/tinygo/loader"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
@ -27,6 +28,12 @@ type testCase struct {
|
||||||
func TestCompiler(t *testing.T) {
|
func TestCompiler(t *testing.T) {
|
||||||
t.Parallel()
|
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.
|
// Determine which tests to run, depending on the Go and LLVM versions.
|
||||||
tests := []testCase{
|
tests := []testCase{
|
||||||
{"basic.go", "", ""},
|
{"basic.go", "", ""},
|
||||||
|
@ -43,6 +50,9 @@ func TestCompiler(t *testing.T) {
|
||||||
{"channel.go", "", ""},
|
{"channel.go", "", ""},
|
||||||
{"gc.go", "", ""},
|
{"gc.go", "", ""},
|
||||||
}
|
}
|
||||||
|
if goMinor >= 20 {
|
||||||
|
tests = append(tests, testCase{"go1.20.go", "", ""})
|
||||||
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
name := tc.file
|
name := tc.file
|
||||||
|
|
15
compiler/testdata/go1.20.go
предоставленный
Обычный файл
15
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)
|
||||||
|
}
|
58
compiler/testdata/go1.20.ll
предоставленный
Обычный файл
58
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 }
|
|
@ -143,10 +143,11 @@ func sliceToArrayPointerPanic() {
|
||||||
runtimePanic("slice smaller than array")
|
runtimePanic("slice smaller than array")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Panic when calling unsafe.Slice() (Go 1.17+) with a len that's too large
|
// Panic when calling unsafe.Slice() (Go 1.17+) or unsafe.String() (Go 1.20+)
|
||||||
// (which includes if the ptr is nil and len is nonzero).
|
// with a len that's too large (which includes if the ptr is nil and len is
|
||||||
|
// nonzero).
|
||||||
func unsafeSlicePanic() {
|
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.
|
// Panic when trying to create a new channel that is too big.
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче