Skip to content
Closed
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,4 @@ go.work*
*.uf2
*.img
*.zip
expect.txt.new
234 changes: 222 additions & 12 deletions chore/gentests/gentests.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,31 @@
package main

import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"sync"

"github.com/goplus/llgo/cl/cltest"
"github.com/goplus/llgo/internal/llgen"
"github.com/goplus/mod"
)

var (
llgoBin string
llgenBin string
)

func main() {
dir, _, err := mod.FindGoMod(".")
check(err)
llgoBin = ensureLLGoBinary(dir)
llgenBin = ensureLLGenBinary(dir)

llgenDir(dir + "/cl/_testcall")
llgenDir(dir + "/cl/_testlibc")
llgenDir(dir + "/cl/_testlibgo")
llgenDir(dir + "/cl/_testrt")
Expand All @@ -44,19 +55,49 @@ func main() {
func llgenDir(dir string) {
fis, err := os.ReadDir(dir)
check(err)
type task struct {
relPath string
testDir string
}
tasks := make([]task, 0, len(fis))
for _, fi := range fis {
name := fi.Name()
if !fi.IsDir() || strings.HasPrefix(name, "_") {
continue
}
testDir := dir + "/" + name
fmt.Fprintln(os.Stderr, "llgen", testDir)
check(os.Chdir(testDir))
llgen.SmartDoFile(testDir)
tasks = append(tasks, task{
relPath: filepath.ToSlash(testDir),
testDir: testDir,
})
}
if len(tasks) == 0 {
return
}
workerCount := gentestWorkers(len(tasks))
workCh := make(chan task)
var wg sync.WaitGroup
for i := 0; i < workerCount; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for t := range workCh {
if err := runLLGen(t.testDir); err != nil {
fmt.Fprintln(os.Stderr, "error:", t.relPath, err)
}
}
}()
}
for _, t := range tasks {
fmt.Fprintln(os.Stderr, "llgen", t.relPath)
workCh <- t
}
close(workCh)
wg.Wait()
}

func genExpects(root string) {
runExpectDir(root, "cl/_testcall")
runExpectDir(root, "cl/_testlibc")
runExpectDir(root, "cl/_testlibgo")
runExpectDir(root, "cl/_testrt")
Expand All @@ -69,22 +110,191 @@ func runExpectDir(root, relDir string) {
dir := filepath.Join(root, relDir)
fis, err := os.ReadDir(dir)
check(err)
type task struct {
relPath string
testDir string
}
tasks := make([]task, 0, len(fis))
for _, fi := range fis {
name := fi.Name()
if !fi.IsDir() || strings.HasPrefix(name, "_") {
continue
}
relPath := filepath.ToSlash(filepath.Join(relDir, name))
testDir := filepath.Join(dir, name)
fmt.Fprintln(os.Stderr, "expect", relPath)
pkgPath := "./" + relPath
output, err := cltest.RunAndCapture(pkgPath, testDir)
if err != nil {
fmt.Fprintln(os.Stderr, "error:", relPath, err)
output = []byte{';'}
if shouldSkipExpect(testDir) {
continue
}
check(os.WriteFile(filepath.Join(testDir, "expect.txt"), output, 0644))
tasks = append(tasks, task{relPath: relPath, testDir: testDir})
}
if len(tasks) == 0 {
return
}

workerCount := gentestWorkers(len(tasks))
workCh := make(chan task)
var wg sync.WaitGroup
for i := 0; i < workerCount; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for t := range workCh {
output, err := runExpect(t.testDir)
if err != nil {
fmt.Fprintln(os.Stderr, "error:", t.relPath, err)
output = []byte{';'}
}
check(os.WriteFile(filepath.Join(t.testDir, "expect.txt"), output, 0644))
}
}()
}
for _, t := range tasks {
fmt.Fprintln(os.Stderr, "expect", t.relPath)
workCh <- t
}
close(workCh)
wg.Wait()
}

func runExpect(pkgDir string) ([]byte, error) {
cacheDir, err := os.MkdirTemp("", "llgo-gocache-*")
if err != nil {
return nil, err
}
defer os.RemoveAll(cacheDir)

relPkg := "."
if _, err := os.Stat(filepath.Join(pkgDir, "in.go")); err == nil {
relPkg = "in.go"
} else if _, err := os.Stat(filepath.Join(pkgDir, "main.go")); err == nil {
relPkg = "main.go"
}

args := []string{"run", "-a", relPkg}
cmd := exec.Command(llgoBin, args...)
cmd.Dir = pkgDir
cmd.Env = append(os.Environ(),
"GOCACHE="+cacheDir,
"LLGO_BUILD_CACHE=off",
)
output, err := cmd.CombinedOutput()
output = filterExpectOutput(output)
if err != nil {
return output, fmt.Errorf("run failed: %w", err)
}
return output, nil
}

func shouldSkipExpect(testDir string) bool {
data, err := os.ReadFile(filepath.Join(testDir, "expect.txt"))
if err != nil {
return false
}
trimmed := bytes.TrimSpace(data)
return bytes.Equal(trimmed, []byte(";"))
}

func filterExpectOutput(output []byte) []byte {
var filtered []byte
for _, line := range bytes.Split(output, []byte("\n")) {
if bytes.HasPrefix(line, []byte("ld64.lld: warning:")) {
continue
}
if bytes.HasPrefix(line, []byte("WARNING: Using LLGO root for devel:")) {
continue
}
if len(filtered) > 0 || len(line) > 0 {
if len(filtered) > 0 {
filtered = append(filtered, '\n')
}
filtered = append(filtered, line...)
}
}
return filtered
}

func gentestWorkers(taskCount int) int {
if taskCount <= 1 {
return 1
}
if v := strings.TrimSpace(os.Getenv("LLGO_GENTESTS_JOBS")); v != "" {
if n, err := strconv.Atoi(v); err == nil && n > 0 {
if n > taskCount {
return taskCount
}
return n
}
}
n := runtime.GOMAXPROCS(0)
if n < 1 {
n = 1
}
if n > taskCount {
return taskCount
}
return n
}

func ensureLLGoBinary(root string) string {
gobin := strings.TrimSpace(goEnv(root, "GOBIN"))
if gobin == "" {
gopath := strings.TrimSpace(goEnv(root, "GOPATH"))
if gopath == "" {
panic("GOBIN and GOPATH are both empty")
}
gopath = strings.Split(gopath, string(os.PathListSeparator))[0]
gobin = filepath.Join(gopath, "bin")
}
exe := "llgo"
if runtime.GOOS == "windows" {
exe += ".exe"
}
path := filepath.Join(gobin, exe)
cmd := exec.Command("go", "install", "-tags=dev", "./cmd/llgo")
cmd.Dir = root
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
check(cmd.Run())
return path
}

func ensureLLGenBinary(root string) string {
gobin := strings.TrimSpace(goEnv(root, "GOBIN"))
if gobin == "" {
gopath := strings.TrimSpace(goEnv(root, "GOPATH"))
if gopath == "" {
panic("GOBIN and GOPATH are both empty")
}
gopath = strings.Split(gopath, string(os.PathListSeparator))[0]
gobin = filepath.Join(gopath, "bin")
}
exe := "llgen"
if runtime.GOOS == "windows" {
exe += ".exe"
}
path := filepath.Join(gobin, exe)
cmd := exec.Command("go", "install", "./chore/llgen")
cmd.Dir = root
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
check(cmd.Run())
return path
}

func runLLGen(testDir string) error {
cmd := exec.Command(llgenBin, ".")
cmd.Dir = testDir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}

func goEnv(root, key string) string {
cmd := exec.Command("go", "env", key)
cmd.Dir = root
out, err := cmd.Output()
check(err)
return string(bytes.TrimSpace(out))
}

func check(err error) {
Expand Down
Empty file added cl/_testcall/ccall/expect.txt
Empty file.
10 changes: 10 additions & 0 deletions cl/_testcall/ccall/in.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

import _ "unsafe" // for go:linkname

//go:linkname cAbs C.abs
func cAbs(x int32) int32

func main() {
_ = cAbs(-5)
}
25 changes: 25 additions & 0 deletions cl/_testcall/ccall/out.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
; ModuleID = 'github.com/goplus/llgo/cl/_testcall/ccall'
source_filename = "github.com/goplus/llgo/cl/_testcall/ccall"

@"github.com/goplus/llgo/cl/_testcall/ccall.init$guard" = global i1 false, align 1

define void @"github.com/goplus/llgo/cl/_testcall/ccall.init"() {
_llgo_0:
%0 = load i1, ptr @"github.com/goplus/llgo/cl/_testcall/ccall.init$guard", align 1
br i1 %0, label %_llgo_2, label %_llgo_1

_llgo_1: ; preds = %_llgo_0
store i1 true, ptr @"github.com/goplus/llgo/cl/_testcall/ccall.init$guard", align 1
br label %_llgo_2

_llgo_2: ; preds = %_llgo_1, %_llgo_0
ret void
}

define void @"github.com/goplus/llgo/cl/_testcall/ccall.main"() {
_llgo_0:
%0 = call i32 @abs(i32 -5)
ret void
}

declare i32 @abs(i32)
Empty file.
17 changes: 17 additions & 0 deletions cl/_testcall/closureparam/in.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package main

// testClosureAsParam: closure passed as parameter (higher-order function)
func main() {
applyFn := func(nums []int, f func(int) int) []int {
res := make([]int, len(nums))
for i, n := range nums {
res[i] = f(n)
}
return res
}
factor := 3
nums := []int{1, 2, 3}
_ = applyFn(nums, func(n int) int {
return n * factor
})
}
Loading
Loading