main.go raw

   1  package main
   2  
   3  import (
   4  	"fmt"
   5  	"os"
   6  	"path/filepath"
   7  	"runtime"
   8  	"unsafe"
   9  
  10  	"github.com/ebitengine/purego"
  11  )
  12  
  13  func main() {
  14  	if len(os.Args) < 2 {
  15  		fmt.Fprintf(os.Stderr, "usage: %s <path-to-compileopts.so>\n", os.Args[0])
  16  		os.Exit(1)
  17  	}
  18  	soPath := os.Args[1]
  19  	if !filepath.IsAbs(soPath) {
  20  		wd, _ := os.Getwd()
  21  		soPath = filepath.Join(wd, soPath)
  22  	}
  23  
  24  	lib, err := openLib(soPath)
  25  	if err != nil {
  26  		fmt.Fprintf(os.Stderr, "dlopen %s: %v\n", soPath, err)
  27  		os.Exit(1)
  28  	}
  29  
  30  	var targetLoad func(uintptr, int32, uintptr, int32, int32) int32
  31  	purego.RegisterLibFunc(&targetLoad, lib, "moxie_target_load")
  32  
  33  	var targetTriple func(int32, uintptr, int32) int32
  34  	purego.RegisterLibFunc(&targetTriple, lib, "moxie_target_triple")
  35  
  36  	var targetCPU func(int32, uintptr, int32) int32
  37  	purego.RegisterLibFunc(&targetCPU, lib, "moxie_target_cpu")
  38  
  39  	var targetFeatures func(int32, uintptr, int32) int32
  40  	purego.RegisterLibFunc(&targetFeatures, lib, "moxie_target_features")
  41  
  42  	var targetGC func(int32, uintptr, int32) int32
  43  	purego.RegisterLibFunc(&targetGC, lib, "moxie_target_gc")
  44  
  45  	var targetLinker func(int32, uintptr, int32) int32
  46  	purego.RegisterLibFunc(&targetLinker, lib, "moxie_target_linker")
  47  
  48  	var targetLibc func(int32, uintptr, int32) int32
  49  	purego.RegisterLibFunc(&targetLibc, lib, "moxie_target_libc")
  50  
  51  	var targetStackSize func(int32) int64
  52  	purego.RegisterLibFunc(&targetStackSize, lib, "moxie_target_stack_size")
  53  
  54  	var targetLdflagCount func(int32) int32
  55  	purego.RegisterLibFunc(&targetLdflagCount, lib, "moxie_target_ldflag_count")
  56  
  57  	var targetLdflag func(int32, int32, uintptr, int32) int32
  58  	purego.RegisterLibFunc(&targetLdflag, lib, "moxie_target_ldflag")
  59  
  60  	var targetExtraFileCount func(int32) int32
  61  	purego.RegisterLibFunc(&targetExtraFileCount, lib, "moxie_target_extra_file_count")
  62  
  63  	var targetExtraFile func(int32, int32, uintptr, int32) int32
  64  	purego.RegisterLibFunc(&targetExtraFile, lib, "moxie_target_extra_file")
  65  
  66  	var targetBuildTagCount func(int32) int32
  67  	purego.RegisterLibFunc(&targetBuildTagCount, lib, "moxie_target_build_tag_count")
  68  
  69  	var targetBuildTag func(int32, int32, uintptr, int32) int32
  70  	purego.RegisterLibFunc(&targetBuildTag, lib, "moxie_target_build_tag")
  71  
  72  	var targetOptLevel func(uintptr, int32, uintptr, uintptr) int32
  73  	purego.RegisterLibFunc(&targetOptLevel, lib, "moxie_target_opt_level")
  74  
  75  	var targetCanonicalArch func(uintptr, int32, uintptr, int32) int32
  76  	purego.RegisterLibFunc(&targetCanonicalArch, lib, "moxie_target_canonical_arch")
  77  
  78  	var targetFree func(int32)
  79  	purego.RegisterLibFunc(&targetFree, lib, "moxie_target_free")
  80  
  81  	// Test linux/amd64
  82  	h := loadTarget(targetLoad, "linux", "amd64", 0)
  83  	expectS(targetTriple, h, "x86_64-unknown-linux-musleabihf", "linux/amd64 triple")
  84  	expectS(targetCPU, h, "x86-64", "linux/amd64 cpu")
  85  	expectS(targetGC, h, "boehm", "linux/amd64 gc")
  86  	expectS(targetLinker, h, "ld.lld", "linux/amd64 linker")
  87  	expectS(targetLibc, h, "musl", "linux/amd64 libc")
  88  	expectI64(targetStackSize(h), 65536, "linux/amd64 stack_size")
  89  	expectContainsFile(targetExtraFile, targetExtraFileCount, h, "src/runtime/asm_amd64.S", "linux/amd64 asm file")
  90  	expectContainsFile(targetExtraFile, targetExtraFileCount, h, "src/runtime/gc_boehm.c", "linux/amd64 gc file")
  91  	targetFree(h)
  92  
  93  	// Test darwin/arm64
  94  	h = loadTarget(targetLoad, "darwin", "arm64", 0)
  95  	expectS(targetTriple, h, "arm64-apple-macosx11.0.0", "darwin/arm64 triple")
  96  	expectS(targetCPU, h, "generic", "darwin/arm64 cpu")
  97  	expectS(targetGC, h, "boehm", "darwin/arm64 gc")
  98  	expectS(targetLibc, h, "darwin-libSystem", "darwin/arm64 libc")
  99  	targetFree(h)
 100  
 101  	// Test js/wasm
 102  	h = loadTarget(targetLoad, "js", "wasm", 0)
 103  	expectS(targetTriple, h, "wasm32-unknown-js", "js/wasm triple")
 104  	expectS(targetCPU, h, "generic", "js/wasm cpu")
 105  	expectS(targetGC, h, "leaking", "js/wasm gc")
 106  	expectS(targetLinker, h, "wasm-ld", "js/wasm linker")
 107  	targetFree(h)
 108  
 109  	// Test c-shared mode adds --shared ldflag
 110  	h = loadTarget(targetLoad, "linux", "amd64", 1)
 111  	expectContainsLdflag(targetLdflag, targetLdflagCount, h, "--shared", "c-shared --shared ldflag")
 112  	targetFree(h)
 113  
 114  	// Test opt levels
 115  	testOptLevel(targetOptLevel, "0", 0, 0)
 116  	testOptLevel(targetOptLevel, "1", 1, 0)
 117  	testOptLevel(targetOptLevel, "2", 2, 0)
 118  	testOptLevel(targetOptLevel, "s", 2, 1)
 119  	testOptLevel(targetOptLevel, "z", 2, 2)
 120  	testOptLevel(targetOptLevel, "none", 0, 0)
 121  
 122  	// Test canonical arch
 123  	testCanonicalArch(targetCanonicalArch, "x86_64-unknown-linux-musleabihf", "x86_64")
 124  	testCanonicalArch(targetCanonicalArch, "arm64-apple-macosx11.0.0", "aarch64")
 125  	testCanonicalArch(targetCanonicalArch, "wasm32-unknown-js", "wasm32")
 126  
 127  	// Test invalid inputs
 128  	goos := []byte("windows")
 129  	goarch := []byte("amd64")
 130  	bad := targetLoad(uintptr(unsafe.Pointer(&goos[0])), int32(len(goos)),
 131  		uintptr(unsafe.Pointer(&goarch[0])), int32(len(goarch)), 0)
 132  	if bad != -1 {
 133  		fmt.Fprintf(os.Stderr, "FAIL: windows/amd64 should fail\n")
 134  		os.Exit(1)
 135  	}
 136  	fmt.Println("PASS: windows/amd64 correctly rejected")
 137  
 138  	fmt.Println("\nAll compileopts roundtrip tests passed.")
 139  }
 140  
 141  func loadTarget(fn func(uintptr, int32, uintptr, int32, int32) int32, goos, goarch string, buildMode int32) int32 {
 142  	g := []byte(goos)
 143  	a := []byte(goarch)
 144  	h := fn(uintptr(unsafe.Pointer(&g[0])), int32(len(g)),
 145  		uintptr(unsafe.Pointer(&a[0])), int32(len(a)), buildMode)
 146  	if h < 0 {
 147  		fmt.Fprintf(os.Stderr, "FAIL: target_load(%s, %s) returned %d\n", goos, goarch, h)
 148  		os.Exit(1)
 149  	}
 150  	fmt.Printf("PASS: target_load(%s/%s) = handle %d\n", goos, goarch, h)
 151  	return h
 152  }
 153  
 154  func getString(fn func(int32, uintptr, int32) int32, h int32) string {
 155  	buf := make([]byte, 512)
 156  	n := fn(h, uintptr(unsafe.Pointer(&buf[0])), int32(len(buf)))
 157  	if n < 0 {
 158  		return ""
 159  	}
 160  	return string(buf[:n])
 161  }
 162  
 163  func getStringIdx(fn func(int32, int32, uintptr, int32) int32, h, idx int32) string {
 164  	buf := make([]byte, 512)
 165  	n := fn(h, idx, uintptr(unsafe.Pointer(&buf[0])), int32(len(buf)))
 166  	if n < 0 {
 167  		return ""
 168  	}
 169  	return string(buf[:n])
 170  }
 171  
 172  func expectS(fn func(int32, uintptr, int32) int32, h int32, want, label string) {
 173  	got := getString(fn, h)
 174  	if got != want {
 175  		fmt.Fprintf(os.Stderr, "FAIL: %s = %q, want %q\n", label, got, want)
 176  		os.Exit(1)
 177  	}
 178  	fmt.Printf("PASS: %s = %q\n", label, got)
 179  }
 180  
 181  func expectI64(got, want int64, label string) {
 182  	if got != want {
 183  		fmt.Fprintf(os.Stderr, "FAIL: %s = %d, want %d\n", label, got, want)
 184  		os.Exit(1)
 185  	}
 186  	fmt.Printf("PASS: %s = %d\n", label, got)
 187  }
 188  
 189  func expectContainsFile(fileFn func(int32, int32, uintptr, int32) int32, countFn func(int32) int32, h int32, want, label string) {
 190  	count := countFn(h)
 191  	for i := int32(0); i < count; i++ {
 192  		got := getStringIdx(fileFn, h, i)
 193  		if got == want {
 194  			fmt.Printf("PASS: %s found at index %d\n", label, i)
 195  			return
 196  		}
 197  	}
 198  	fmt.Fprintf(os.Stderr, "FAIL: %s not found in %d extra files\n", label, count)
 199  	os.Exit(1)
 200  }
 201  
 202  func expectContainsLdflag(flagFn func(int32, int32, uintptr, int32) int32, countFn func(int32) int32, h int32, want, label string) {
 203  	count := countFn(h)
 204  	for i := int32(0); i < count; i++ {
 205  		got := getStringIdx(flagFn, h, i)
 206  		if got == want {
 207  			fmt.Printf("PASS: %s found at index %d\n", label, i)
 208  			return
 209  		}
 210  	}
 211  	fmt.Fprintf(os.Stderr, "FAIL: %s not found in %d ldflags\n", label, count)
 212  	os.Exit(1)
 213  }
 214  
 215  func testOptLevel(fn func(uintptr, int32, uintptr, uintptr) int32, opt string, wantSpeed, wantSize int32) {
 216  	data := []byte(opt)
 217  	var speed, size int32
 218  	rc := fn(uintptr(unsafe.Pointer(&data[0])), int32(len(data)),
 219  		uintptr(unsafe.Pointer(&speed)), uintptr(unsafe.Pointer(&size)))
 220  	if rc != 0 {
 221  		fmt.Fprintf(os.Stderr, "FAIL: opt_level(%q) returned %d\n", opt, rc)
 222  		os.Exit(1)
 223  	}
 224  	if speed != wantSpeed || size != wantSize {
 225  		fmt.Fprintf(os.Stderr, "FAIL: opt_level(%q) = (%d,%d), want (%d,%d)\n", opt, speed, size, wantSpeed, wantSize)
 226  		os.Exit(1)
 227  	}
 228  	fmt.Printf("PASS: opt_level(%q) = speed=%d, size=%d\n", opt, speed, size)
 229  }
 230  
 231  func testCanonicalArch(fn func(uintptr, int32, uintptr, int32) int32, triple, want string) {
 232  	data := []byte(triple)
 233  	buf := make([]byte, 128)
 234  	n := fn(uintptr(unsafe.Pointer(&data[0])), int32(len(data)),
 235  		uintptr(unsafe.Pointer(&buf[0])), int32(len(buf)))
 236  	got := string(buf[:n])
 237  	if got != want {
 238  		fmt.Fprintf(os.Stderr, "FAIL: canonical_arch(%q) = %q, want %q\n", triple, got, want)
 239  		os.Exit(1)
 240  	}
 241  	fmt.Printf("PASS: canonical_arch(%q) = %q\n", triple, got)
 242  }
 243  
 244  func openLib(path string) (uintptr, error) {
 245  	switch runtime.GOOS {
 246  	case "linux", "darwin":
 247  		return purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_GLOBAL)
 248  	default:
 249  		return 0, fmt.Errorf("unsupported OS: %s", runtime.GOOS)
 250  	}
 251  }
 252