Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Go 1.20 support #3391

Merged
merged 5 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,18 @@ jobs:
- test-linux:
llvm: "14"
resource_class: large
test-llvm15-go120:
docker:
- image: golang:1.20-rc-buster
steps:
- test-linux:
llvm: "15"
resource_class: large

workflows:
test-all:
jobs:
# This tests our lowest supported versions of Go and LLVM, to make sure at
# least the smoke tests still pass.
- test-llvm14-go118
- test-llvm15-go120
1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,6 @@ TEST_PACKAGES_FAST = \
container/list \
container/ring \
crypto/des \
crypto/internal/subtle \
crypto/md5 \
crypto/rc4 \
crypto/sha1 \
Expand Down
4 changes: 2 additions & 2 deletions builder/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
if err != nil {
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
}
if major != 1 || minor < 18 || minor > 19 {
return nil, fmt.Errorf("requires go version 1.18 through 1.19, got go%d.%d", major, minor)
if major != 1 || minor < 18 || minor > 20 {
return nil, fmt.Errorf("requires go version 1.18 through 1.20, got go%d.%d", major, minor)
}

clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
Expand Down
15 changes: 11 additions & 4 deletions cgo/cgo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"go/types"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"testing"
Expand All @@ -21,9 +22,15 @@ var flagUpdate = flag.Bool("update", false, "Update images based on test output.

// normalizeResult normalizes Go source code that comes out of tests across
// platforms and Go versions.
func normalizeResult(result string) string {
actual := strings.ReplaceAll(result, "\r\n", "\n")
return actual
func normalizeResult(t *testing.T, result string) string {
result = strings.ReplaceAll(result, "\r\n", "\n")

// This changed to 'undefined:', in Go 1.20.
result = strings.ReplaceAll(result, ": undeclared name:", ": undefined:")
// Go 1.20 added a bit more detail
result = regexp.MustCompile(`(unknown field z in struct literal).*`).ReplaceAllString(result, "$1")

return result
}

func TestCGo(t *testing.T) {
Expand Down Expand Up @@ -88,7 +95,7 @@ func TestCGo(t *testing.T) {
if err != nil {
t.Errorf("could not write out CGo AST: %v", err)
}
actual := normalizeResult(buf.String())
actual := normalizeResult(t, buf.String())

// Read the file with the expected output, to compare against.
outfile := filepath.Join("testdata", name+".out.go")
Expand Down
4 changes: 2 additions & 2 deletions cgo/testdata/errors.out.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
// Type checking errors after CGo processing:
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as C.char value in variable declaration (overflows)
// testdata/errors.go:105: unknown field z in struct literal
// testdata/errors.go:108: undeclared name: C.SOME_CONST_1
// testdata/errors.go:108: undefined: C.SOME_CONST_1
// testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows)
// testdata/errors.go:112: undeclared name: C.SOME_CONST_4
// testdata/errors.go:112: undefined: C.SOME_CONST_4

package main

Expand Down
11 changes: 6 additions & 5 deletions compiler/asserts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
40 changes: 27 additions & 13 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -1646,31 +1646,45 @@ 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, "")
} else if len.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
// 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)
}
Expand Down
10 changes: 10 additions & 0 deletions compiler/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand All @@ -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", "", ""},
Expand All @@ -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
Expand Down
15 changes: 15 additions & 0 deletions compiler/testdata/go1.20.go
Original file line number Diff line number Diff line change
@@ -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 changes: 58 additions & 0 deletions compiler/testdata/go1.20.ll
Original file line number Diff line number Diff line change
@@ -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 }
8 changes: 8 additions & 0 deletions src/runtime/algorithm.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ import (
"unsafe"
)

// This function is needed by math/rand since Go 1.20.
// See: https://github.com/golang/go/issues/54880
//
//go:linkname rand_fastrand64 math/rand.fastrand64
func rand_fastrand64() uint64 {
return fastrand64()
}

// This function is used by hash/maphash.
func fastrand() uint32 {
xorshift32State = xorshift32(xorshift32State)
Expand Down
32 changes: 22 additions & 10 deletions src/runtime/env_linux.go → src/runtime/env_unix.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,40 @@
//go:build linux
//go:build linux || darwin

package runtime

// Update the C environment if cgo is loaded.
// Called from syscall.Setenv.
// Called from Go 1.20 and above.
//
//go:linkname syscall_setenv_c syscall.setenv_c
func syscall_setenv_c(key string, val string) {
//go:linkname syscallSetenv syscall.runtimeSetenv
func syscallSetenv(key, value string) {
keydata := cstring(key)
valdata := cstring(val)
valdata := cstring(value)
// ignore any errors
libc_setenv(&keydata[0], &valdata[0], 1)
return
}

// Update the C environment if cgo is loaded.
// Called from syscall.Unsetenv.
// Called from Go 1.20 and above.
//
//go:linkname syscall_unsetenv_c syscall.unsetenv_c
func syscall_unsetenv_c(key string) {
//go:linkname syscallUnsetenv syscall.runtimeUnsetenv
func syscallUnsetenv(key string) {
keydata := cstring(key)
// ignore any errors
libc_unsetenv(&keydata[0])
return
}

// Compatibility with Go 1.19 and below.
//
//go:linkname syscall_setenv_c syscall.setenv_c
func syscall_setenv_c(key string, val string) {
syscallSetenv(key, val)
}

// Compatibility with Go 1.19 and below.
//
//go:linkname syscall_unsetenv_c syscall.unsetenv_c
func syscall_unsetenv_c(key string) {
syscallUnsetenv(key)
}

// cstring converts a Go string to a C string.
Expand Down
7 changes: 4 additions & 3 deletions src/runtime/panic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 6 additions & 0 deletions src/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,9 @@ func KeepAlive(x interface{}) {
func SetFinalizer(obj interface{}, finalizer interface{}) {
// Unimplemented.
}

//go:linkname godebug_setUpdate internal/godebug.setUpdate
func godebug_setUpdate(update func(string, string)) {
// Unimplemented. The 'update' function needs to be called whenever the
// GODEBUG environment variable changes (for example, via os.Setenv).
}
Loading