compiler: add tests for starting a goroutine
This commit adds a test for both WebAssembly and Cortex-M targets (which use a different way of goroutine lowering) to show how they lower goroutines. It makes it easier to show how the output changes in future commits.
Этот коммит содержится в:
родитель
6e1eb28ed0
коммит
87c2ccb0b9
4 изменённых файлов: 314 добавлений и 38 удалений
|
@ -19,6 +19,8 @@ var flagUpdate = flag.Bool("update", false, "update tests based on test output")
|
|||
// Basic tests for the compiler. Build some Go files and compare the output with
|
||||
// the expected LLVM IR for regression testing.
|
||||
func TestCompiler(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Check LLVM version.
|
||||
llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
|
||||
if err != nil {
|
||||
|
@ -32,43 +34,55 @@ func TestCompiler(t *testing.T) {
|
|||
t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version)
|
||||
}
|
||||
|
||||
target, err := compileopts.LoadTarget("wasm")
|
||||
if err != nil {
|
||||
t.Fatal("failed to load target:", err)
|
||||
}
|
||||
config := &compileopts.Config{
|
||||
Options: &compileopts.Options{},
|
||||
Target: target,
|
||||
}
|
||||
compilerConfig := &Config{
|
||||
Triple: config.Triple(),
|
||||
GOOS: config.GOOS(),
|
||||
GOARCH: config.GOARCH(),
|
||||
CodeModel: config.CodeModel(),
|
||||
RelocationModel: config.RelocationModel(),
|
||||
Scheduler: config.Scheduler(),
|
||||
FuncImplementation: config.FuncImplementation(),
|
||||
AutomaticStackSize: config.AutomaticStackSize(),
|
||||
}
|
||||
machine, err := NewTargetMachine(compilerConfig)
|
||||
if err != nil {
|
||||
t.Fatal("failed to create target machine:", err)
|
||||
tests := []struct {
|
||||
file string
|
||||
target string
|
||||
}{
|
||||
{"basic.go", ""},
|
||||
{"pointer.go", ""},
|
||||
{"slice.go", ""},
|
||||
{"string.go", ""},
|
||||
{"float.go", ""},
|
||||
{"interface.go", ""},
|
||||
{"func.go", ""},
|
||||
{"goroutine.go", "wasm"},
|
||||
{"goroutine.go", "cortex-m-qemu"},
|
||||
}
|
||||
|
||||
tests := []string{
|
||||
"basic.go",
|
||||
"pointer.go",
|
||||
"slice.go",
|
||||
"string.go",
|
||||
"float.go",
|
||||
"interface.go",
|
||||
"func.go",
|
||||
}
|
||||
for _, tc := range tests {
|
||||
name := tc.file
|
||||
targetString := "wasm"
|
||||
if tc.target != "" {
|
||||
targetString = tc.target
|
||||
name = tc.file + "-" + tc.target
|
||||
}
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
target, err := compileopts.LoadTarget(targetString)
|
||||
if err != nil {
|
||||
t.Fatal("failed to load target:", err)
|
||||
}
|
||||
config := &compileopts.Config{
|
||||
Options: &compileopts.Options{},
|
||||
Target: target,
|
||||
}
|
||||
compilerConfig := &Config{
|
||||
Triple: config.Triple(),
|
||||
GOOS: config.GOOS(),
|
||||
GOARCH: config.GOARCH(),
|
||||
CodeModel: config.CodeModel(),
|
||||
RelocationModel: config.RelocationModel(),
|
||||
Scheduler: config.Scheduler(),
|
||||
FuncImplementation: config.FuncImplementation(),
|
||||
AutomaticStackSize: config.AutomaticStackSize(),
|
||||
}
|
||||
machine, err := NewTargetMachine(compilerConfig)
|
||||
if err != nil {
|
||||
t.Fatal("failed to create target machine:", err)
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
t.Run(testCase, func(t *testing.T) {
|
||||
// Load entire program AST into memory.
|
||||
lprogram, err := loader.Load(config, []string{"./testdata/" + testCase}, config.ClangHeaders, types.Config{
|
||||
lprogram, err := loader.Load(config, []string{"./testdata/" + tc.file}, config.ClangHeaders, types.Config{
|
||||
Sizes: Sizes(machine),
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -76,13 +90,13 @@ func TestCompiler(t *testing.T) {
|
|||
}
|
||||
err = lprogram.Parse()
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse test case %s: %s", testCase, err)
|
||||
t.Fatalf("could not parse test case %s: %s", tc.file, err)
|
||||
}
|
||||
|
||||
// Compile AST to IR.
|
||||
program := lprogram.LoadSSA()
|
||||
pkg := lprogram.MainPkg()
|
||||
mod, errs := CompilePackage(testCase, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
|
||||
mod, errs := CompilePackage(tc.file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
|
||||
if errs != nil {
|
||||
for _, err := range errs {
|
||||
t.Error(err)
|
||||
|
@ -105,18 +119,22 @@ func TestCompiler(t *testing.T) {
|
|||
}
|
||||
funcPasses.FinalizeFunc()
|
||||
|
||||
outfile := "./testdata/" + testCase[:len(testCase)-3] + ".ll"
|
||||
outFilePrefix := tc.file[:len(tc.file)-3]
|
||||
if tc.target != "" {
|
||||
outFilePrefix += "-" + tc.target
|
||||
}
|
||||
outPath := "./testdata/" + outFilePrefix + ".ll"
|
||||
|
||||
// Update test if needed. Do not check the result.
|
||||
if *flagUpdate {
|
||||
err := ioutil.WriteFile(outfile, []byte(mod.String()), 0666)
|
||||
err := ioutil.WriteFile(outPath, []byte(mod.String()), 0666)
|
||||
if err != nil {
|
||||
t.Error("failed to write updated output file:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
expected, err := ioutil.ReadFile(outfile)
|
||||
expected, err := ioutil.ReadFile(outPath)
|
||||
if err != nil {
|
||||
t.Fatal("failed to read golden file:", err)
|
||||
}
|
||||
|
|
144
compiler/testdata/goroutine-cortex-m-qemu.ll
предоставленный
Обычный файл
144
compiler/testdata/goroutine-cortex-m-qemu.ll
предоставленный
Обычный файл
|
@ -0,0 +1,144 @@
|
|||
; ModuleID = 'goroutine.go'
|
||||
source_filename = "goroutine.go"
|
||||
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||
target triple = "armv7m-none-eabi"
|
||||
|
||||
@"main.regularFunctionGoroutine$pack" = private unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
|
||||
@"main.inlineFunctionGoroutine$pack" = private unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.regularFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* undef, i8* undef)
|
||||
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.regularFunction$gowrapper" to i32), i8* bitcast ({ i32, i8* }* @"main.regularFunctionGoroutine$pack" to i8*), i32 %stacksize, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @main.regularFunction(i32, i8*, i8*)
|
||||
|
||||
define linkonce_odr void @"main.regularFunction$gowrapper"(i8* %0) unnamed_addr #0 {
|
||||
entry:
|
||||
%1 = bitcast i8* %0 to i32*
|
||||
%2 = load i32, i32* %1, align 4
|
||||
%3 = getelementptr inbounds i8, i8* %0, i32 4
|
||||
%4 = bitcast i8* %3 to i8**
|
||||
%5 = load i8*, i8** %4, align 4
|
||||
call void @main.regularFunction(i32 %2, i8* %5, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i32 @"internal/task.getGoroutineStackSize"(i32, i8*, i8*)
|
||||
|
||||
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.inlineFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* undef, i8* undef)
|
||||
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.inlineFunctionGoroutine$1$gowrapper" to i32), i8* bitcast ({ i32, i8* }* @"main.inlineFunctionGoroutine$pack" to i8*), i32 %stacksize, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @"main.inlineFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define linkonce_odr void @"main.inlineFunctionGoroutine$1$gowrapper"(i8* %0) unnamed_addr #1 {
|
||||
entry:
|
||||
%1 = bitcast i8* %0 to i32*
|
||||
%2 = load i32, i32* %1, align 4
|
||||
%3 = getelementptr inbounds i8, i8* %0, i32 4
|
||||
%4 = bitcast i8* %3 to i8**
|
||||
%5 = load i8*, i8** %4, align 4
|
||||
call void @"main.inlineFunctionGoroutine$1"(i32 %2, i8* %5, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.closureFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%n = call i8* @runtime.alloc(i32 4, i8* undef, i8* null)
|
||||
%0 = bitcast i8* %n to i32*
|
||||
store i32 3, i32* %0, align 4
|
||||
%1 = call i8* @runtime.alloc(i32 8, i8* undef, i8* null)
|
||||
%2 = bitcast i8* %1 to i32*
|
||||
store i32 5, i32* %2, align 4
|
||||
%3 = getelementptr inbounds i8, i8* %1, i32 4
|
||||
%4 = bitcast i8* %3 to i8**
|
||||
store i8* %n, i8** %4, align 4
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @"main.closureFunctionGoroutine$1$gowrapper" to i32), i8* undef, i8* undef)
|
||||
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @"main.closureFunctionGoroutine$1$gowrapper" to i32), i8* nonnull %1, i32 %stacksize, i8* undef, i8* null)
|
||||
%5 = load i32, i32* %0, align 4
|
||||
call void @runtime.printint32(i32 %5, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @"main.closureFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = icmp eq i8* %context, null
|
||||
br i1 %0, label %store.throw, label %store.next
|
||||
|
||||
store.throw: ; preds = %entry
|
||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
store.next: ; preds = %entry
|
||||
%unpack.ptr = bitcast i8* %context to i32*
|
||||
store i32 7, i32* %unpack.ptr, align 4
|
||||
ret void
|
||||
}
|
||||
|
||||
define linkonce_odr void @"main.closureFunctionGoroutine$1$gowrapper"(i8* %0) unnamed_addr #2 {
|
||||
entry:
|
||||
%1 = bitcast i8* %0 to i32*
|
||||
%2 = load i32, i32* %1, align 4
|
||||
%3 = getelementptr inbounds i8, i8* %0, i32 4
|
||||
%4 = bitcast i8* %3 to i8**
|
||||
%5 = load i8*, i8** %4, align 4
|
||||
call void @"main.closureFunctionGoroutine$1"(i32 %2, i8* %5, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @runtime.printint32(i32, i8*, i8*)
|
||||
|
||||
declare void @runtime.nilPanic(i8*, i8*)
|
||||
|
||||
define hidden void @main.funcGoroutine(i8* %fn.context, void (i32, i8*, i8*)* %fn.funcptr, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = call i8* @runtime.alloc(i32 12, i8* undef, i8* null)
|
||||
%1 = bitcast i8* %0 to i32*
|
||||
store i32 5, i32* %1, align 4
|
||||
%2 = getelementptr inbounds i8, i8* %0, i32 4
|
||||
%3 = bitcast i8* %2 to i8**
|
||||
store i8* %fn.context, i8** %3, align 4
|
||||
%4 = getelementptr inbounds i8, i8* %0, i32 8
|
||||
%5 = bitcast i8* %4 to void (i32, i8*, i8*)**
|
||||
store void (i32, i8*, i8*)* %fn.funcptr, void (i32, i8*, i8*)** %5, align 4
|
||||
%stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (void (i8*)* @main.funcGoroutine.gowrapper to i32), i8* undef, i8* undef)
|
||||
call void @"internal/task.start"(i32 ptrtoint (void (i8*)* @main.funcGoroutine.gowrapper to i32), i8* nonnull %0, i32 %stacksize, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
define linkonce_odr void @main.funcGoroutine.gowrapper(i8* %0) unnamed_addr #3 {
|
||||
entry:
|
||||
%1 = bitcast i8* %0 to i32*
|
||||
%2 = load i32, i32* %1, align 4
|
||||
%3 = getelementptr inbounds i8, i8* %0, i32 4
|
||||
%4 = bitcast i8* %3 to i8**
|
||||
%5 = load i8*, i8** %4, align 4
|
||||
%6 = getelementptr inbounds i8, i8* %0, i32 8
|
||||
%7 = bitcast i8* %6 to void (i32, i8*, i8*)**
|
||||
%8 = load void (i32, i8*, i8*)*, void (i32, i8*, i8*)** %7, align 4
|
||||
call void %8(i32 %2, i8* %5, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
attributes #0 = { "tinygo-gowrapper"="main.regularFunction" }
|
||||
attributes #1 = { "tinygo-gowrapper"="main.inlineFunctionGoroutine$1" }
|
||||
attributes #2 = { "tinygo-gowrapper"="main.closureFunctionGoroutine$1" }
|
||||
attributes #3 = { "tinygo-gowrapper" }
|
90
compiler/testdata/goroutine-wasm.ll
предоставленный
Обычный файл
90
compiler/testdata/goroutine-wasm.ll
предоставленный
Обычный файл
|
@ -0,0 +1,90 @@
|
|||
; ModuleID = 'goroutine.go'
|
||||
source_filename = "goroutine.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||
target triple = "wasm32--wasi"
|
||||
|
||||
%runtime.funcValueWithSignature = type { i32, i8* }
|
||||
|
||||
@"main.regularFunctionGoroutine$pack" = private unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
|
||||
@"main.inlineFunctionGoroutine$pack" = private unnamed_addr constant { i32, i8* } { i32 5, i8* undef }
|
||||
@"reflect/types.funcid:func:{basic:int}{}" = external constant i8
|
||||
@"main.closureFunctionGoroutine$1$withSignature" = linkonce_odr constant %runtime.funcValueWithSignature { i32 ptrtoint (void (i32, i8*, i8*)* @"main.closureFunctionGoroutine$1" to i32), i8* @"reflect/types.funcid:func:{basic:int}{}" }
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.regularFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @main.regularFunction to i32), i8* bitcast ({ i32, i8* }* @"main.regularFunctionGoroutine$pack" to i8*), i32 undef, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @main.regularFunction(i32, i8*, i8*)
|
||||
|
||||
declare void @"internal/task.start"(i32, i8*, i32, i8*, i8*)
|
||||
|
||||
define hidden void @main.inlineFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main.inlineFunctionGoroutine$1" to i32), i8* bitcast ({ i32, i8* }* @"main.inlineFunctionGoroutine$pack" to i8*), i32 undef, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @"main.inlineFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @main.closureFunctionGoroutine(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%n = call i8* @runtime.alloc(i32 4, i8* undef, i8* null)
|
||||
%0 = bitcast i8* %n to i32*
|
||||
store i32 3, i32* %0, align 4
|
||||
%1 = call i8* @runtime.alloc(i32 8, i8* undef, i8* null)
|
||||
%2 = bitcast i8* %1 to i32*
|
||||
store i32 5, i32* %2, align 4
|
||||
%3 = getelementptr inbounds i8, i8* %1, i32 4
|
||||
%4 = bitcast i8* %3 to i8**
|
||||
store i8* %n, i8** %4, align 4
|
||||
call void @"internal/task.start"(i32 ptrtoint (void (i32, i8*, i8*)* @"main.closureFunctionGoroutine$1" to i32), i8* nonnull %1, i32 undef, i8* undef, i8* null)
|
||||
%5 = load i32, i32* %0, align 4
|
||||
call void @runtime.printint32(i32 %5, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
define hidden void @"main.closureFunctionGoroutine$1"(i32 %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = icmp eq i8* %context, null
|
||||
br i1 %0, label %store.throw, label %store.next
|
||||
|
||||
store.throw: ; preds = %entry
|
||||
call void @runtime.nilPanic(i8* undef, i8* null)
|
||||
unreachable
|
||||
|
||||
store.next: ; preds = %entry
|
||||
%unpack.ptr = bitcast i8* %context to i32*
|
||||
store i32 7, i32* %unpack.ptr, align 4
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @runtime.printint32(i32, i8*, i8*)
|
||||
|
||||
declare void @runtime.nilPanic(i8*, i8*)
|
||||
|
||||
define hidden void @main.funcGoroutine(i8* %fn.context, i32 %fn.funcptr, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = call i32 @runtime.getFuncPtr(i8* %fn.context, i32 %fn.funcptr, i8* nonnull @"reflect/types.funcid:func:{basic:int}{}", i8* undef, i8* null)
|
||||
%1 = call i8* @runtime.alloc(i32 8, i8* undef, i8* null)
|
||||
%2 = bitcast i8* %1 to i32*
|
||||
store i32 5, i32* %2, align 4
|
||||
%3 = getelementptr inbounds i8, i8* %1, i32 4
|
||||
%4 = bitcast i8* %3 to i8**
|
||||
store i8* %fn.context, i8** %4, align 4
|
||||
call void @"internal/task.start"(i32 %0, i8* nonnull %1, i32 undef, i8* undef, i8* null)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i32 @runtime.getFuncPtr(i8*, i32, i8* dereferenceable_or_null(1), i8*, i8*)
|
24
compiler/testdata/goroutine.go
предоставленный
Обычный файл
24
compiler/testdata/goroutine.go
предоставленный
Обычный файл
|
@ -0,0 +1,24 @@
|
|||
package main
|
||||
|
||||
func regularFunctionGoroutine() {
|
||||
go regularFunction(5)
|
||||
}
|
||||
|
||||
func inlineFunctionGoroutine() {
|
||||
go func(x int) {
|
||||
}(5)
|
||||
}
|
||||
|
||||
func closureFunctionGoroutine() {
|
||||
n := 3
|
||||
go func(x int) {
|
||||
n = 7
|
||||
}(5)
|
||||
print(n) // note: this is racy (but good enough for this test)
|
||||
}
|
||||
|
||||
func funcGoroutine(fn func(x int)) {
|
||||
go fn(5)
|
||||
}
|
||||
|
||||
func regularFunction(x int)
|
Загрузка…
Создание таблицы
Сослаться в новой задаче