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