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