main.mx raw
1 package main
2
3 import (
4 "os"
5 "unsafe"
6 )
7
8 //export mxc_run
9 func cRun(argv unsafe.Pointer, argc int32) int32
10
11 //export mxc_getenv
12 func cGetenv(name unsafe.Pointer, nameLen int32, buf unsafe.Pointer, bufCap int32) int32
13
14 //export mxc_listdir
15 func cListdir(dir unsafe.Pointer, dirLen int32, buf unsafe.Pointer, bufCap int32) int32
16
17 //export write
18 func cWrite(fd int32, buf unsafe.Pointer, count uint32) int32
19
20 //export mxc_writefile
21 func cWritefile(path unsafe.Pointer, pathLen int32, data unsafe.Pointer, dataLen int32, mode uint32) int32
22
23 //export mxc_mkdir
24 func cMkdir(path unsafe.Pointer, pathLen int32, mode uint32) int32
25
26 //export mxc_readfile
27 func cReadfile(path unsafe.Pointer, pathLen int32, buf unsafe.Pointer, bufCap int32) int32
28
29 //export mxc_filesize
30 func cFilesize(path unsafe.Pointer, pathLen int32) int32
31
32 func writeStr(fd int32, s string) {
33 if len(s) > 0 {
34 cWrite(fd, unsafe.Pointer(unsafe.SliceData(s)), uint32(len(s)))
35 }
36 }
37
38 func writeFile(path string, data []byte, mode uint32) {
39 if len(path) == 0 {
40 return
41 }
42 cWritefile(unsafe.Pointer(unsafe.SliceData(path)), int32(len(path)),
43 unsafe.Pointer(unsafe.SliceData(data)), int32(len(data)), mode)
44 }
45
46 func mkdirAll(path string, mode uint32) {
47 if len(path) == 0 {
48 return
49 }
50 cMkdir(unsafe.Pointer(unsafe.SliceData(path)), int32(len(path)), mode)
51 }
52
53 func readFile(path string) ([]byte, bool) {
54 if len(path) == 0 {
55 return nil, false
56 }
57 pp := unsafe.Pointer(unsafe.SliceData(path))
58 pl := int32(len(path))
59 sz := cFilesize(pp, pl)
60 if sz < 0 {
61 return nil, false
62 }
63 if sz == 0 {
64 sz = 65536
65 }
66 buf := []byte{:sz}
67 n := cReadfile(pp, pl,
68 unsafe.Pointer(unsafe.SliceData(buf)), sz)
69 if n < 0 {
70 return nil, false
71 }
72 return buf[:n], true
73 }
74
75 func fatal(msg string) {
76 writeStr(2, "mxc: " | msg | "\n")
77 os.Exit(1)
78 }
79
80 func getenv(name string) string {
81 buf := []byte{:4096}
82 n := cGetenv(unsafe.Pointer(unsafe.SliceData(name)), int32(len(name)), unsafe.Pointer(unsafe.SliceData(buf)), int32(len(buf)))
83 if n <= 0 {
84 return ""
85 }
86 return string(buf[:n])
87 }
88
89 func getArgs() []string {
90 data, ok := readFile("/proc/self/cmdline")
91 if !ok {
92 return nil
93 }
94 var args []string
95 start := int32(0)
96 for i := int32(0); i < int32(len(data)); i++ {
97 if data[i] == 0 {
98 if i > start {
99 args = append(args, string(data[start:i]))
100 }
101 start = i + 1
102 }
103 }
104 if start < int32(len(data)) {
105 args = append(args, string(data[start:]))
106 }
107 return args
108 }
109
110 func listFiles(dir, ext string) []string {
111 buf := []byte{:65536}
112 n := cListdir(unsafe.Pointer(unsafe.SliceData(dir)), int32(len(dir)),
113 unsafe.Pointer(unsafe.SliceData(buf)), int32(len(buf)))
114 if n <= 0 {
115 return nil
116 }
117 var result []string
118 start := int32(0)
119 for i := int32(0); i < n; i++ {
120 if buf[i] == 0 {
121 name := string(buf[start:i])
122 if hasSuffix(name, ext) {
123 result = append(result, name)
124 }
125 start = i + 1
126 }
127 }
128 return result
129 }
130
131 func run(args []string) int32 {
132 argv := []unsafe.Pointer{:len(args) + 1}
133 cstrs := [][]byte{:len(args)}
134 for i, a := range args {
135 b := []byte{:len(a) + 1}
136 copy(b, a)
137 b[len(a)] = 0
138 cstrs[i] = b
139 argv[i] = unsafe.Pointer(unsafe.SliceData(b))
140 }
141 return cRun(unsafe.Pointer(unsafe.SliceData(argv)), int32(len(args)))
142 }
143
144 func splitLines(s string) []string {
145 var result []string
146 start := int32(0)
147 for i := int32(0); i < int32(len(s)); i++ {
148 if s[i] == '\n' {
149 if i > start {
150 result = append(result, s[start:i])
151 }
152 start = i + 1
153 }
154 }
155 if start < int32(len(s)) {
156 result = append(result, s[start:])
157 }
158 return result
159 }
160
161 func hasPrefix(s, prefix string) bool {
162 return len(s) >= len(prefix) && s[:len(prefix)] == prefix
163 }
164
165 func hasSuffix(s, suffix string) bool {
166 return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
167 }
168
169 func irNeedsQuote(s string) bool {
170 for i := 0; i < len(s); i++ {
171 c := s[i]
172 if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '_' || c == '$' {
173 continue
174 }
175 return true
176 }
177 return false
178 }
179
180 func irGlobalSymbol(pkg, name string) string {
181 sym := pkg | "." | name
182 if irNeedsQuote(sym) {
183 return "@\"" | sym | "\""
184 }
185 return "@" | sym
186 }
187
188 func trimSpace(s string) string {
189 i := int32(0)
190 for i < int32(len(s)) && (s[i] == ' ' || s[i] == '\t' || s[i] == '\n' || s[i] == '\r') {
191 i++
192 }
193 j := int32(len(s))
194 for j > i && (s[j-1] == ' ' || s[j-1] == '\t' || s[j-1] == '\n' || s[j-1] == '\r') {
195 j--
196 }
197 return s[i:j]
198 }
199
200 func joinPath(a, b string) string {
201 if len(a) == 0 {
202 return b
203 }
204 if a[len(a)-1] == '/' {
205 return a | b
206 }
207 return a | "/" | b
208 }
209
210 func pathDir(path string) string {
211 for i := int32(len(path)) - 1; i >= 0; i-- {
212 if path[i] == '/' {
213 if i == 0 {
214 return "/"
215 }
216 return path[:i]
217 }
218 }
219 return "."
220 }
221
222 func cleanPath(path string) string {
223 parts := splitSlash(path)
224 var out []string
225 for _, p := range parts {
226 if p == "." || p == "" {
227 continue
228 }
229 if p == ".." && len(out) > 0 && out[len(out)-1] != ".." {
230 out = out[:len(out)-1]
231 } else {
232 out = append(out, p)
233 }
234 }
235 if len(out) == 0 {
236 return "."
237 }
238 result := out[0]
239 for i := 1; i < len(out); i++ {
240 result = result | "/" | out[i]
241 }
242 if len(path) > 0 && path[0] == '/' {
243 result = "/" | result
244 }
245 return result
246 }
247
248 func splitSlash(s string) []string {
249 var parts []string
250 start := int32(0)
251 for i := int32(0); i < int32(len(s)); i++ {
252 if s[i] == '/' {
253 parts = append(parts, s[start:i])
254 start = i + 1
255 }
256 }
257 parts = append(parts, s[start:])
258 return parts
259 }
260
261 func pathBase(path string) string {
262 for i := int32(len(path)) - 1; i >= 0; i-- {
263 if path[i] == '/' {
264 return path[i+1:]
265 }
266 }
267 return path
268 }
269
270 func joinStrings(ss []string, sep string) string {
271 if len(ss) == 0 {
272 return ""
273 }
274 n := int32(0)
275 for _, s := range ss {
276 n += int32(len(s))
277 }
278 n += int32(len(sep)) * (int32(len(ss)) - 1)
279 buf := []byte{:0:n}
280 for i, s := range ss {
281 if i > 0 {
282 buf = append(buf, sep...)
283 }
284 buf = append(buf, s...)
285 }
286 return string(buf)
287 }
288
289 func appendUniq(ss []string, s string) []string {
290 for _, x := range ss {
291 if x == s {
292 return ss
293 }
294 }
295 return append(ss, s)
296 }
297
298 func fnvHash(data []byte) string {
299 h := uint32(2166136261)
300 for i := int32(0); i < int32(len(data)); i++ {
301 h ^= uint32(data[i])
302 h *= uint32(16777619)
303 }
304 hex := "0123456789abcdef"
305 buf := []byte{:8}
306 for i := int32(7); i >= 0; i-- {
307 buf[i] = hex[h&0xf]
308 h >>= 4
309 }
310 return string(buf)
311 }
312
313 func pkgCacheDir() string {
314 return joinPath(joinPath(getMoxiePath(), "cache"), "mxc-1.9.2/pkg")
315 }
316
317 func getMoxiePath() string {
318 p := getenv("MOXIEPATH")
319 if p != "" {
320 return p
321 }
322 home := getenv("HOME")
323 if home == "" {
324 return "/tmp/moxie"
325 }
326 return joinPath(home, "moxie")
327 }
328
329 func pkgCacheKey(dir string, files []string) string {
330 var sorted []string
331 for _, f := range files {
332 sorted = append(sorted, f)
333 }
334 for i := int32(1); i < int32(len(sorted)); i++ {
335 for j := i; j > 0 && sorted[j] < sorted[j-1]; j-- {
336 sorted[j], sorted[j-1] = sorted[j-1], sorted[j]
337 }
338 }
339 var all []byte
340 for _, f := range sorted {
341 p := joinPath(dir, f)
342 data, ok := readFile(p)
343 if !ok {
344 continue
345 }
346 all = append(all, data...)
347 all = append(all, 0)
348 }
349 return fnvHash(all)
350 }
351
352 func cachedBcPath(cacheBase, pkgPath, hash string) string {
353 return joinPath(joinPath(cacheBase, safeName(pkgPath)), hash | ".bc")
354 }
355
356 func isStdlib(path string) bool {
357 return path != "." && !hasPrefix(path, "/") && !hasPrefix(path, "./") && !hasPrefix(path, "../")
358 }
359
360 var builtinOnly []string
361
362 func isBuiltinOnly(path string) bool {
363 for _, b := range builtinOnly {
364 if path == b {
365 return true
366 }
367 }
368 return false
369 }
370
371 func isInRuntime(path string) bool {
372 return path == "internal/task" || path == "runtime"
373 }
374
375 func loadSysroot(path string) {
376 data, ok := readFile(path)
377 if !ok {
378 return
379 }
380 for _, line := range splitLines(string(data)) {
381 line = trimSpace(line)
382 if len(line) == 0 || line[0] == '#' {
383 continue
384 }
385 eq := int32(-1)
386 for i := int32(0); i < int32(len(line)); i++ {
387 if line[i] == '=' {
388 eq = i
389 break
390 }
391 }
392 if eq < 0 {
393 continue
394 }
395 key := line[:eq]
396 val := line[eq+1:]
397 os.Setenv(key, val)
398 }
399 }
400
401 func splitBySpace(s string) []string {
402 var result []string
403 start := int32(0)
404 inWord := false
405 for i := int32(0); i < int32(len(s)); i++ {
406 if s[i] == ' ' || s[i] == '\t' {
407 if inWord {
408 result = append(result, s[start:i])
409 inWord = false
410 }
411 } else {
412 if !inWord {
413 start = i
414 inWord = true
415 }
416 }
417 }
418 if inWord {
419 result = append(result, s[start:])
420 }
421 return result
422 }
423
424 func safeName(pkg string) string {
425 b := []byte{:len(pkg)}
426 for i := int32(0); i < int32(len(pkg)); i++ {
427 if pkg[i] == '/' {
428 b[i] = '_'
429 } else {
430 b[i] = pkg[i]
431 }
432 }
433 return string(b)
434 }
435
436 func extractPkgName(data []byte) string {
437 for i := int32(0); i < int32(len(data)); i++ {
438 if i+8 < int32(len(data)) && string(data[i:i+8]) == "package " {
439 j := i + 8
440 for j < int32(len(data)) && data[j] != '\n' && data[j] != '\r' && data[j] != ' ' {
441 j++
442 }
443 return string(data[i+8 : j])
444 }
445 if data[i] == '\n' {
446 continue
447 }
448 if data[i] == '/' && i+1 < int32(len(data)) && data[i+1] == '/' {
449 for i < int32(len(data)) && data[i] != '\n' {
450 i++
451 }
452 continue
453 }
454 break
455 }
456 return "main"
457 }
458
459 func extractImports(data []byte) []string {
460 var imports []string
461 lines := splitLines(string(data))
462 inBlock := false
463 for _, line := range lines {
464 line = trimSpace(line)
465 if line == "import (" {
466 inBlock = true
467 continue
468 }
469 if inBlock && line == ")" {
470 inBlock = false
471 continue
472 }
473 if inBlock {
474 imp := extractQuoted(line)
475 if imp != "" {
476 imports = appendUniq(imports, imp)
477 }
478 }
479 if hasPrefix(line, "import \"") {
480 imp := extractQuoted(line[7:])
481 if imp != "" {
482 imports = appendUniq(imports, imp)
483 }
484 }
485 }
486 return imports
487 }
488
489 func extractQuoted(s string) string {
490 start := int32(-1)
491 for i := int32(0); i < int32(len(s)); i++ {
492 if s[i] == '"' {
493 if start < 0 {
494 start = i + 1
495 } else {
496 return s[start:i]
497 }
498 }
499 }
500 return ""
501 }
502
503 func concatSources(dir string, files []string) []byte {
504 imports := map[string]bool{}
505 var bodies [][]byte
506 pkgName := ""
507 for _, f := range files {
508 data, ok := readFile(joinPath(dir, f))
509 if !ok {
510 continue
511 }
512 if pkgName == "" {
513 pkgName = extractPkgName(data)
514 }
515 lines := splitLines(string(data))
516 var body []string
517 i := int32(0)
518 for i < int32(len(lines)) {
519 line := trimSpace(lines[i])
520 if hasPrefix(line, "package ") {
521 i++
522 continue
523 }
524 if line == "import (" {
525 i++
526 for i < int32(len(lines)) {
527 imp := trimSpace(lines[i])
528 i++
529 if imp == ")" {
530 break
531 }
532 if imp != "" {
533 imports[imp] = true
534 }
535 }
536 continue
537 }
538 if hasPrefix(line, "import ") && !hasPrefix(line, "import (") {
539 imp := line[7:]
540 imports[imp] = true
541 i++
542 continue
543 }
544 body = append(body, lines[i])
545 i++
546 }
547 bodies = append(bodies, []byte(joinStrings(body, "\n")))
548 }
549 var out []byte
550 out = append(out, ("package " | pkgName | "\n")...)
551 if len(imports) > 0 {
552 var impKeys []string
553 for imp := range imports {
554 impKeys = append(impKeys, imp)
555 }
556 for i := int32(1); i < int32(len(impKeys)); i++ {
557 for j := i; j > 0 && impKeys[j] < impKeys[j-1]; j-- {
558 impKeys[j], impKeys[j-1] = impKeys[j-1], impKeys[j]
559 }
560 }
561 out = append(out, "import (\n"...)
562 for _, imp := range impKeys {
563 out = append(out, '\t')
564 out = append(out, imp...)
565 out = append(out, '\n')
566 }
567 out = append(out, ")\n"...)
568 }
569 for _, b := range bodies {
570 out = append(out, b...)
571 out = append(out, '\n')
572 }
573 return out
574 }
575
576 func fileContainsPart(base string, part string) bool {
577 plen := len(part)
578 for i := 0; i <= len(base)-plen; i++ {
579 if base[i:i+plen] == part {
580 before := i == 0 || base[i-1] == '_'
581 after := i+plen == len(base) || base[i+plen] == '_'
582 if before && after {
583 return true
584 }
585 }
586 }
587 return false
588 }
589
590 func shouldSkipFile(name string) bool {
591 if hasSuffix(name, "_test.mx") {
592 return true
593 }
594 base := name
595 if hasSuffix(base, ".mx") {
596 base = base[:len(base)-3]
597 }
598 if globalTargetArch == "wasm" {
599 for _, a := range []string{"amd64", "arm64", "386"} {
600 if fileContainsPart(base, a) {
601 return true
602 }
603 }
604 for _, s := range []string{"_linux", "_unix", "_darwin", "_posix"} {
605 if hasSuffix(base, s) {
606 return true
607 }
608 }
609 } else {
610 for _, a := range []string{"arm64"} {
611 if fileContainsPart(base, a) {
612 return true
613 }
614 }
615 if hasSuffix(base, "_wasm") {
616 return true
617 }
618 }
619 exact := []string{"pipe", "multi", "replace", "search",
620 "ioutil", "quic",
621 "gc_stack_portable", "gc_precise", "wait_other", "os_other", "time_go122"}
622 for _, s := range exact {
623 if base == s {
624 return true
625 }
626 }
627 return false
628 }
629
630 func constraintRequiresForbidden(line string, forbidden []string) bool {
631 if len(line) == 0 {
632 return false
633 }
634 if line[0] == '!' {
635 return false
636 }
637 topOR := false
638 depth := int32(0)
639 for i := int32(0); i < int32(len(line)); i++ {
640 if line[i] == '(' {
641 depth++
642 } else if line[i] == ')' {
643 depth--
644 } else if depth == 0 && i+2 <= int32(len(line)) && string(line[i:i+2]) == "||" {
645 topOR = true
646 break
647 }
648 }
649 if topOR {
650 return false
651 }
652 for _, f := range forbidden {
653 if constraintContainsPositive(line, f) {
654 return true
655 }
656 }
657 return false
658 }
659
660 func constraintContainsNegated(line string, word string) bool {
661 wl := int32(len(word))
662 for i := int32(1); i <= int32(len(line))-wl; i++ {
663 if string(line[i:i+wl]) == word && line[i-1] == '!' {
664 after := i + wl
665 if after < int32(len(line)) {
666 c := line[after]
667 if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_' || c == '.' {
668 continue
669 }
670 }
671 return true
672 }
673 }
674 return false
675 }
676
677 func constraintContainsPositive(line string, word string) bool {
678 wl := int32(len(word))
679 for i := int32(0); i <= int32(len(line))-wl; i++ {
680 if string(line[i:i+wl]) == word {
681 if i > 0 && line[i-1] == '!' {
682 continue
683 }
684 after := i + wl
685 if after < int32(len(line)) {
686 c := line[after]
687 if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_' {
688 continue
689 }
690 }
691 if i > 0 {
692 c := line[i-1]
693 if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_' {
694 continue
695 }
696 }
697 return true
698 }
699 }
700 return false
701 }
702
703 func hasBuildConstraint(data []byte, forbidden []string) bool {
704 for i := int32(0); i < int32(len(data)) && i < 512; i++ {
705 if data[i] == 'p' {
706 return false
707 }
708 if i+9 < int32(len(data)) && string(data[i:i+9]) == "//:build " {
709 var buf []byte
710 for j := i + 9; j < int32(len(data)) && data[j] != '\n'; j++ {
711 buf = append(buf, data[j])
712 }
713 line := string(buf)
714 if constraintRequiresForbidden(line, forbidden) {
715 return true
716 }
717 if globalTargetArch == "wasm" {
718 if constraintContainsPositive(line, "moxie.wasm") ||
719 constraintContainsPositive(line, "wasm") ||
720 constraintContainsPositive(line, "js") {
721 return false
722 }
723 if constraintContainsNegated(line, "js") ||
724 constraintContainsNegated(line, "wasm") ||
725 constraintContainsNegated(line, "moxie.wasm") {
726 return true
727 }
728 } else {
729 if constraintContainsPositive(line, "linux") ||
730 constraintContainsPositive(line, "unix") ||
731 constraintContainsPositive(line, globalTargetArch) {
732 return false
733 }
734 if constraintContainsNegated(line, "linux") ||
735 constraintContainsNegated(line, "unix") ||
736 constraintContainsNegated(line, globalTargetArch) {
737 return true
738 }
739 }
740 for _, f := range forbidden {
741 if line == f || hasPrefix(line, f | " ") || hasPrefix(line, f | "\t") || hasPrefix(line, f | "(") {
742 return true
743 }
744 }
745 if constraintContainsPositive(line, "purego") {
746 return true
747 }
748 return false
749 }
750 if data[i] == '\n' {
751 continue
752 }
753 if data[i] == '/' && i+1 < int32(len(data)) && data[i+1] == '/' {
754 for i < int32(len(data)) && data[i] != '\n' {
755 i++
756 }
757 continue
758 }
759 }
760 return false
761 }
762
763 func hasImport(data []byte, pkg string) bool {
764 target := "\"" | pkg | "\""
765 for i := int32(0); i <= int32(len(data))-int32(len(target)); i++ {
766 if string(data[i:i+int32(len(target))]) == target {
767 return true
768 }
769 }
770 return false
771 }
772
773 type pkgInfo struct {
774 path string
775 name string
776 dir string
777 files []string
778 cfiles []string
779 sfiles []string
780 imports []string
781 }
782
783 var globalModPrefix string
784 var globalModDir string
785 var globalTargetOS string
786 var globalTargetArch string
787 var globalReplaces map[string]string
788
789 func parseReplaceLine(line string, baseDir string) (string, string, bool) {
790 arrow := int32(-1)
791 for k := int32(0); k < int32(len(line))-1; k++ {
792 if line[k] == '=' && line[k+1] == '>' {
793 arrow = k
794 break
795 }
796 }
797 if arrow < 0 {
798 return "", "", false
799 }
800 from := trimSpace(line[:arrow])
801 to := trimSpace(line[arrow+2:])
802 for fi := int32(0); fi < int32(len(from)); fi++ {
803 if from[fi] == ' ' {
804 from = from[:fi]
805 break
806 }
807 }
808 if hasPrefix(to, "./") || hasPrefix(to, "../") || hasPrefix(to, "/") {
809 to = cleanPath(joinPath(baseDir, to))
810 }
811 return from, to, true
812 }
813
814 func findModuleRoot(startDir string) (string, string) {
815 dir := startDir
816 for {
817 data, ok := readFile(joinPath(dir, "moxie.mod"))
818 if ok {
819 modName := ""
820 globalReplaces = map[string]string{}
821 lines := splitLines(string(data))
822 inReplace := false
823 for _, line := range lines {
824 line = trimSpace(line)
825 if hasPrefix(line, "module ") {
826 modName = trimSpace(line[7:])
827 continue
828 }
829 if line == "replace (" {
830 inReplace = true
831 continue
832 }
833 if inReplace {
834 if line == ")" {
835 inReplace = false
836 continue
837 }
838 from, to, ok2 := parseReplaceLine(line, dir)
839 if ok2 {
840 globalReplaces[from] = to
841 }
842 continue
843 }
844 if hasPrefix(line, "replace ") {
845 from, to, ok2 := parseReplaceLine(line[8:], dir)
846 if ok2 {
847 globalReplaces[from] = to
848 }
849 }
850 }
851 if modName != "" {
852 return modName, dir
853 }
854 }
855 parent := pathDir(dir)
856 if parent == dir || parent == "" || parent == "." {
857 break
858 }
859 dir = parent
860 }
861 return "", ""
862 }
863
864 func mergeModReplaces(dir string) {
865 data, ok := readFile(joinPath(dir, "moxie.mod"))
866 if !ok {
867 return
868 }
869 if globalReplaces == nil {
870 globalReplaces = map[string]string{}
871 }
872 inReplace := false
873 for _, line := range splitLines(string(data)) {
874 line = trimSpace(line)
875 if line == "replace (" {
876 inReplace = true
877 continue
878 }
879 if inReplace {
880 if line == ")" {
881 inReplace = false
882 continue
883 }
884 from, to, ok2 := parseReplaceLine(line, dir)
885 if ok2 {
886 if _, exists := globalReplaces[from]; !exists {
887 globalReplaces[from] = to
888 }
889 }
890 continue
891 }
892 if hasPrefix(line, "replace ") {
893 from, to, ok2 := parseReplaceLine(line[8:], dir)
894 if ok2 {
895 if _, exists := globalReplaces[from]; !exists {
896 globalReplaces[from] = to
897 }
898 }
899 }
900 }
901 }
902
903 func discoverPkg(pkgPath, root string) *pkgInfo {
904 var dir string
905 if pkgPath == "." || pkgPath == ".." || hasPrefix(pkgPath, "/") || hasPrefix(pkgPath, "./") || hasPrefix(pkgPath, "../") {
906 dir = pkgPath
907 } else if globalReplaces != nil {
908 if rdir, ok := globalReplaces[pkgPath]; ok {
909 dir = rdir
910 }
911 if dir == "" {
912 for from, to := range globalReplaces {
913 if hasPrefix(pkgPath, from|"/") {
914 rel := pkgPath[len(from)+1:]
915 dir = joinPath(to, rel)
916 break
917 }
918 }
919 }
920 }
921 if dir == "" && globalModPrefix != "" && pkgPath == globalModPrefix {
922 dir = globalModDir
923 }
924 if dir == "" && globalModPrefix != "" && hasPrefix(pkgPath, globalModPrefix|"/") {
925 rel := pkgPath[len(globalModPrefix)+1:]
926 dir = joinPath(globalModDir, rel)
927 }
928 if dir == "" {
929 candidate := joinPath(getMoxiePath(), pkgPath)
930 mxF := listFiles(candidate, ".mx")
931 if len(mxF) > 0 {
932 dir = candidate
933 }
934 }
935 if dir != "" {
936 mergeModReplaces(dir)
937 }
938 if dir == "" {
939 dir = joinPath(root, joinPath("src", pkgPath))
940 mxFiles := listFiles(dir, ".mx")
941 if len(mxFiles) == 0 {
942 dir = joinPath(root, joinPath("src/vendor", pkgPath))
943 }
944 }
945 mxFiles := listFiles(dir, ".mx")
946 var cFiles []string
947 for _, cf := range listFiles(dir, ".c") {
948 if hasSuffix(cf, "_test.c") {
949 continue
950 }
951 cdata, cok := readFile(joinPath(dir, cf))
952 if cok && hasBuildConstraint(cdata, []string{"ignore"}) {
953 continue
954 }
955 cFiles = append(cFiles, cf)
956 }
957 allSFiles := listFiles(dir, ".s")
958 var sFiles []string
959 for _, sf := range allSFiles {
960 if !shouldSkipFile(sf[:len(sf)-2] | ".mx") {
961 sFiles = append(sFiles, sf)
962 }
963 }
964 allMx := mxFiles
965 var forbidden []string
966 if globalTargetArch == "wasm" {
967 forbidden = []string{"linux", "unix", "darwin", "amd64", "arm64",
968 "ignore", "compiler_bootstrap",
969 "scheduler.tasks", "scheduler.cores", "scheduler.asyncify",
970 "faketime", "baremetal", "nintendoswitch",
971 "boringcrypto", "cgo"}
972 } else {
973 forbidden = []string{"wasm", "js", "ignore", "compiler_bootstrap",
974 "scheduler.tasks", "scheduler.cores", "scheduler.asyncify",
975 "faketime", "baremetal", "nintendoswitch", "wasm_unknown",
976 "boringcrypto", "cgo",
977 "gc.leaking", "gc.none", "gc.dealloc", "gc.conservative", "gc.precise",
978 "runtime_asserts", "runtime_memhash_tsip", "runtime_memhash_leveldb",
979 "avr", "moxie.wasm"}
980 }
981 var files []string
982 for _, f := range allMx {
983 if shouldSkipFile(f) {
984 continue
985 }
986 data, ok := readFile(joinPath(dir, f))
987 if !ok {
988 continue
989 }
990 bc := hasBuildConstraint(data, forbidden)
991 if bc {
992 writeStr(2, " skip-constraint: " | f | "\n")
993 continue
994 }
995 fileImps := extractImports(data)
996 skipIter := false
997 for _, imp := range fileImps {
998 if imp == "iter" {
999 skipIter = true
1000 break
1001 }
1002 }
1003 if skipIter {
1004 writeStr(2, " skip-iter-import: " | f | "\n")
1005 continue
1006 }
1007 writeStr(2, " include: " | f | "\n")
1008 files = append(files, f)
1009 }
1010 if len(files) == 0 {
1011 return nil
1012 }
1013 var allImports []string
1014 pkgName := ""
1015 for _, f := range files {
1016 data, ok := readFile(joinPath(dir, f))
1017 if !ok {
1018 continue
1019 }
1020 if pkgName == "" {
1021 pkgName = extractPkgName(data)
1022 }
1023 for _, imp := range extractImports(data) {
1024 allImports = appendUniq(allImports, imp)
1025 }
1026 }
1027 writeStr(2, " discover " | pkgPath | " [")
1028 for fi, ff := range files {
1029 if fi > 0 {
1030 writeStr(2, ", ")
1031 }
1032 writeStr(2, ff)
1033 }
1034 writeStr(2, "]\n")
1035 return &pkgInfo{
1036 path: pkgPath,
1037 name: pkgName,
1038 dir: dir,
1039 files: files,
1040 cfiles: cFiles,
1041 sfiles: sFiles,
1042 imports: allImports,
1043 }
1044 }
1045
1046 type resolver struct {
1047 visited map[string]bool
1048 order []*pkgInfo
1049 root string
1050 }
1051
1052 func (r *resolver) walk(path string) {
1053 if r.visited[path] {
1054 return
1055 }
1056 r.visited[path] = true
1057 if isBuiltinOnly(path) {
1058 return
1059 }
1060 pkg := discoverPkg(path, r.root)
1061 if pkg == nil {
1062 return
1063 }
1064 for _, imp := range pkg.imports {
1065 r.walk(imp)
1066 }
1067 r.order = append(r.order, pkg)
1068 }
1069
1070 func resolveAll(rootPkg string, root string) []*pkgInfo {
1071 r := &resolver{
1072 visited: map[string]bool{},
1073 root: root,
1074 }
1075 r.walk(rootPkg)
1076 return r.order
1077 }
1078
1079 func registerBuiltins() {
1080 initUniverse()
1081 ensureImportRegistry()
1082 importRegistry = map[string]*TCPackage{}
1083
1084 // unsafe - compiler builtin, no source
1085 unsafePkg := NewTCPackage("unsafe", "unsafe")
1086 unsafePkg.Scope().Insert(NewTypeName(unsafePkg, "Pointer", Typ[UnsafePointer]))
1087 unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "Sizeof", parseSignatureDesc("interface{}->int32")))
1088 unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "Offsetof", parseSignatureDesc("interface{}->int32")))
1089 unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "Alignof", parseSignatureDesc("interface{}->int32")))
1090 unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "Slice", parseSignatureDesc("ptr,int32->[]uint8")))
1091 unsafePkg.Scope().Insert(NewTCFunc(unsafePkg, "SliceData", parseSignatureDesc("[]uint8->ptr")))
1092 importRegistry["unsafe"] = unsafePkg
1093
1094 // moxie - spawn/codec runtime, special handling
1095 importRegistry["moxie"] = NewTCPackage("moxie", "moxie")
1096
1097 // runtime - assembly + C, pre-compiled in runtime.bc
1098 rtPkg := NewTCPackage("runtime", "runtime")
1099 rtPkg.Scope().Insert(NewTCFunc(rtPkg, "InitCShared", parseSignatureDesc("")))
1100 rtPkg.Scope().Insert(NewTCFunc(rtPkg, "LastSpawnedParentFd", parseSignatureDesc("->int32")))
1101 rtPkg.Scope().Insert(NewTCFunc(rtPkg, "PipeClosed", parseSignatureDesc("int32->bool")))
1102 rtPkg.Scope().Insert(NewTCFunc(rtPkg, "PipeFDCanSend", parseSignatureDesc("int32->bool")))
1103 rtPkg.Scope().Insert(NewTCFunc(rtPkg, "Caller", parseSignatureDesc("int32->ptr,string,int32,bool")))
1104 rtPkg.Scope().Insert(NewTCVar(rtPkg, "ChildPipeFd", Typ[Int32]))
1105 goarchConst := NewTCConst(rtPkg, "GOARCH", Typ[TCString], constStr{globalTargetArch})
1106 rtPkg.Scope().Insert(goarchConst)
1107 goosConst := NewTCConst(rtPkg, "GOOS", Typ[TCString], constStr{globalTargetOS})
1108 rtPkg.Scope().Insert(goosConst)
1109 importRegistry["runtime"] = rtPkg
1110 }
1111
1112 func addPtrMethod(pkg *TCPackage, named *Named, name, sigDesc string) {
1113 var sig *Signature
1114 if sigDesc == "" {
1115 sig = NewSignature(nil, nil, nil, false)
1116 } else {
1117 sig = parseSignatureDesc(sigDesc)
1118 }
1119 fn := NewTCFunc(pkg, name, sig)
1120 fn.hasPtrRecv = true
1121 named.AddMethod(fn)
1122 }
1123
1124 func compileSource(src []byte, pkgName, triple string) string {
1125 ir := CompileToIR(src, pkgName, triple)
1126 if len(ir) > 0 && ir[0] == ';' {
1127 writeStr(2, ir)
1128 fatal("compile error for " | pkgName)
1129 }
1130 if len(ir) == 0 {
1131 fatal("empty IR for " | pkgName)
1132 }
1133 return ir
1134 }
1135
1136 func removeAll(path string) {
1137 entries := listFiles(path, "")
1138 if entries == nil {
1139 return
1140 }
1141 for _, e := range entries {
1142 os.Remove(joinPath(path, e))
1143 }
1144 os.Remove(path)
1145 }
1146
1147 type modRequire struct {
1148 path string
1149 version string
1150 }
1151
1152 func parseModRequires(dir string) (string, []modRequire, map[string]string) {
1153 data, ok := readFile(joinPath(dir, "moxie.mod"))
1154 if !ok {
1155 return "", nil, nil
1156 }
1157 modName := ""
1158 var reqs []modRequire
1159 repls := map[string]string{}
1160 inRequire := false
1161 inReplace := false
1162 for _, line := range splitLines(string(data)) {
1163 line = trimSpace(line)
1164 if hasPrefix(line, "module ") {
1165 modName = trimSpace(line[7:])
1166 continue
1167 }
1168 if line == "require (" {
1169 inRequire = true
1170 continue
1171 }
1172 if inRequire {
1173 if line == ")" {
1174 inRequire = false
1175 continue
1176 }
1177 parts := splitBySpace(line)
1178 if len(parts) >= 2 {
1179 reqs = append(reqs, modRequire{path: parts[0], version: parts[1]})
1180 } else if len(parts) == 1 {
1181 reqs = append(reqs, modRequire{path: parts[0], version: ""})
1182 }
1183 continue
1184 }
1185 if hasPrefix(line, "require ") {
1186 parts := splitBySpace(line[8:])
1187 if len(parts) >= 2 {
1188 reqs = append(reqs, modRequire{path: parts[0], version: parts[1]})
1189 } else if len(parts) == 1 {
1190 reqs = append(reqs, modRequire{path: parts[0], version: ""})
1191 }
1192 continue
1193 }
1194 if line == "replace (" {
1195 inReplace = true
1196 continue
1197 }
1198 if inReplace {
1199 if line == ")" {
1200 inReplace = false
1201 continue
1202 }
1203 from, to, ok2 := parseReplaceLine(line, dir)
1204 if ok2 {
1205 repls[from] = to
1206 }
1207 continue
1208 }
1209 if hasPrefix(line, "replace ") {
1210 from, to, ok2 := parseReplaceLine(line[8:], dir)
1211 if ok2 {
1212 repls[from] = to
1213 }
1214 }
1215 }
1216 return modName, reqs, repls
1217 }
1218
1219 func cmdFetch(url string) {
1220 mpath := getMoxiePath()
1221 mkdirAll(mpath, 0755)
1222
1223 lockPath := joinPath(mpath, ".lock")
1224 _, locked := readFile(lockPath)
1225 if locked {
1226 fatal("fetch already running (" | lockPath | ")")
1227 }
1228 writeFile(lockPath, []byte("1"), 0644)
1229
1230 rootDir := "."
1231 if url != "" {
1232 tmpdir := "/tmp/mxc-fetch"
1233 removeAll(tmpdir)
1234 writeStr(2, "clone " | url | "\n")
1235 if run([]string{"git", "clone", url, tmpdir}) != 0 {
1236 os.Remove(lockPath)
1237 fatal("clone failed: " | url)
1238 }
1239 modName, _, _ := parseModRequires(tmpdir)
1240 if modName == "" {
1241 removeAll(tmpdir)
1242 os.Remove(lockPath)
1243 fatal("cloned repo has no moxie.mod")
1244 }
1245 dest := joinPath(mpath, modName)
1246 _, exists := readFile(joinPath(dest, ".git/HEAD"))
1247 if exists {
1248 removeAll(tmpdir)
1249 writeStr(2, " already exists: " | modName | ", updating remote and pulling\n")
1250 run([]string{"git", "-C", dest, "remote", "set-url", "origin", url})
1251 run([]string{"git", "-C", dest, "fetch", "origin"})
1252 run([]string{"git", "-C", dest, "reset", "--hard", "origin/HEAD"})
1253 } else {
1254 mkdirAll(pathDir(dest), 0755)
1255 if run([]string{"mv", tmpdir, dest}) != 0 {
1256 removeAll(tmpdir)
1257 os.Remove(lockPath)
1258 fatal("move failed: " | tmpdir | " -> " | dest)
1259 }
1260 }
1261 writeStr(2, "module: " | modName | "\n")
1262 rootDir = dest
1263 }
1264
1265 modName, reqs, repls := parseModRequires(rootDir)
1266 if modName == "" {
1267 os.Remove(lockPath)
1268 fatal("no moxie.mod in " | rootDir)
1269 }
1270 if url == "" {
1271 writeStr(2, "module: " | modName | "\n")
1272 }
1273
1274 var queue []modRequire
1275 for _, r := range reqs {
1276 if _, ok := repls[r.path]; ok {
1277 writeStr(2, " skip (replaced): " | r.path | "\n")
1278 continue
1279 }
1280 queue = append(queue, r)
1281 }
1282
1283 seen := map[string]bool{}
1284 for len(queue) > 0 {
1285 req := queue[0]
1286 queue = queue[1:]
1287 if seen[req.path] {
1288 continue
1289 }
1290 seen[req.path] = true
1291
1292 dest := joinPath(mpath, req.path)
1293 gitHead := joinPath(dest, ".git/HEAD")
1294 _, exists := readFile(gitHead)
1295
1296 if !exists {
1297 mkdirAll(pathDir(dest), 0755)
1298 cloneURL := "https://" | req.path
1299 writeStr(2, " clone " | req.path | "\n")
1300 if run([]string{"git", "clone", cloneURL, dest}) != 0 {
1301 writeStr(2, " FAIL: clone " | req.path | "\n")
1302 continue
1303 }
1304 }
1305
1306 if req.version != "" && req.version != "v0.0.0" {
1307 if run([]string{"git", "-C", dest, "checkout", req.version}) != 0 {
1308 writeStr(2, " tag " | req.version | " not found, pulling\n")
1309 run([]string{"git", "-C", dest, "pull"})
1310 if run([]string{"git", "-C", dest, "checkout", req.version}) != 0 {
1311 writeStr(2, " WARN: " | req.version | " unavailable for " | req.path | "\n")
1312 }
1313 } else {
1314 writeStr(2, " " | req.path | " @ " | req.version | "\n")
1315 }
1316 } else if exists {
1317 writeStr(2, " pull " | req.path | "\n")
1318 run([]string{"git", "-C", dest, "pull"})
1319 } else {
1320 writeStr(2, " fetched " | req.path | "\n")
1321 }
1322
1323 _, subReqs, _ := parseModRequires(dest)
1324 for _, sr := range subReqs {
1325 if !seen[sr.path] {
1326 queue = append(queue, sr)
1327 }
1328 }
1329 }
1330
1331 os.Remove(lockPath)
1332 writeStr(2, "fetch done\n")
1333 }
1334
1335 func cacheClean() {
1336 base := pkgCacheDir()
1337 writeStr(2, "cleaning cache: " | base | "\n")
1338 entries := listFiles(base, "")
1339 if entries == nil {
1340 writeStr(2, " (empty)\n")
1341 return
1342 }
1343 for _, e := range entries {
1344 removeAll(joinPath(base, e))
1345 }
1346 os.Remove(base)
1347 writeStr(2, " done\n")
1348 }
1349
1350 func main() {
1351 builtinOnly = []string{"unsafe", "moxie", "runtime", "runtime/interrupt", "internal/task", "internal/gclayout"}
1352 args := getArgs()
1353 if len(args) < 2 {
1354 writeStr(2, "usage: mxc <build|version|cache|fetch> ...\n")
1355 os.Exit(1)
1356 }
1357
1358 cmd := args[1]
1359 if cmd == "version" {
1360 writeStr(1, "mxc 1.9.2\n")
1361 return
1362 }
1363 if cmd == "cache" {
1364 if len(args) >= 3 && args[2] == "clean" {
1365 cacheClean()
1366 return
1367 }
1368 writeStr(2, "usage: mxc cache clean\n")
1369 os.Exit(1)
1370 }
1371 if cmd == "fetch" {
1372 fetchURL := ""
1373 if len(args) >= 3 {
1374 fetchURL = args[2]
1375 }
1376 cmdFetch(fetchURL)
1377 return
1378 }
1379 if cmd != "build" {
1380 writeStr(2, "usage: mxc <build|version|cache|fetch> ...\n")
1381 os.Exit(1)
1382 }
1383
1384 outpath := ""
1385 pkgPath := ""
1386 printIR := false
1387 forceRebuild := false
1388 targetFlag := ""
1389 i := int32(2)
1390 for i < int32(len(args)) {
1391 if args[i] == "-o" && i+1 < int32(len(args)) {
1392 outpath = args[i+1]
1393 i += 2
1394 } else if args[i] == "-target" && i+1 < int32(len(args)) {
1395 targetFlag = args[i+1]
1396 i += 2
1397 } else if args[i] == "-print-ir" {
1398 printIR = true
1399 i++
1400 } else if args[i] == "-a" {
1401 forceRebuild = true
1402 i++
1403 } else if args[i] == "-x" || args[i] == "-work" {
1404 i++
1405 } else {
1406 pkgPath = args[i]
1407 i++
1408 }
1409 }
1410 if pkgPath == "" {
1411 fatal("no package specified")
1412 }
1413 if outpath == "" {
1414 outpath = pathBase(pkgPath)
1415 }
1416
1417 root := getenv("MOXIEROOT")
1418 if root == "" {
1419 root = "."
1420 }
1421 globalTargetOS = "linux"
1422 globalTargetArch = "amd64"
1423 triple := "x86_64-unknown-linux-musleabihf"
1424 if targetFlag == "js/wasm" || targetFlag == "wasm" {
1425 globalTargetOS = "js"
1426 globalTargetArch = "wasm"
1427 triple = "wasm32-unknown-js"
1428 }
1429 isWasm := globalTargetArch == "wasm"
1430 clang := "clang-21"
1431
1432 searchDir := "."
1433 if hasPrefix(pkgPath, "/") {
1434 searchDir = pkgPath
1435 } else if hasPrefix(pkgPath, "./") || hasPrefix(pkgPath, "../") {
1436 searchDir = pkgPath
1437 }
1438 globalModPrefix, globalModDir = findModuleRoot(searchDir)
1439 if globalModPrefix == "" {
1440 globalModPrefix, globalModDir = findModuleRoot(".")
1441 }
1442
1443 registerBuiltins()
1444 pkgs := resolveAll(pkgPath, root)
1445 if len(pkgs) == 0 {
1446 fatal("no packages to compile")
1447 }
1448
1449 tmpdir := "/tmp/mxc-build"
1450 os.RemoveAll(tmpdir)
1451 mkdirAll(tmpdir, 0755)
1452
1453 cacheBase := pkgCacheDir()
1454 var allBcFiles []string
1455 var overrideBcFiles []string
1456
1457 for _, pkg := range pkgs {
1458 compilePath := pkg.path
1459 if compilePath == "." || hasPrefix(compilePath, "/") || hasPrefix(compilePath, "./") || hasPrefix(compilePath, "../") {
1460 compilePath = pkg.name
1461 }
1462
1463 if isStdlib(pkg.path) {
1464 hash := pkgCacheKey(pkg.dir, pkg.files)
1465 cached := cachedBcPath(cacheBase, pkg.path, hash)
1466 _, cacheOk := readFile(cached)
1467 if cacheOk && !forceRebuild {
1468 writeStr(2, " cached " | pkg.path | "\n")
1469 src := concatSources(pkg.dir, pkg.files)
1470 writeStr(2, " tc-start " | compilePath | " srcLen=" | simpleItoa(len(src)) | "\n")
1471 TypeCheckOnly(src, compilePath)
1472 writeStr(2, " tc-done " | compilePath | "\n")
1473 src = nil
1474 if isInRuntime(pkg.path) {
1475 overrideBcFiles = append(overrideBcFiles, cached)
1476 } else {
1477 allBcFiles = append(allBcFiles, cached)
1478 }
1479 continue
1480 }
1481 writeStr(2, " compile " | pkg.path | "\n")
1482 src := concatSources(pkg.dir, pkg.files)
1483 ir := compileSource(src, compilePath, triple)
1484 src = nil
1485 llFile := joinPath(tmpdir, safeName(pkg.path) | ".ll")
1486 writeFile(llFile, []byte(ir), 0644)
1487 ir = ""
1488 bcFile := joinPath(tmpdir, safeName(pkg.path) | ".bc")
1489 rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", llFile, "-o", bcFile})
1490 if rc != 0 {
1491 fatal("clang failed for " | pkg.path)
1492 }
1493 cacheDir := joinPath(cacheBase, safeName(pkg.path))
1494 mkdirAll(cacheDir, 0755)
1495 data, rok := readFile(bcFile)
1496 if rok {
1497 writeFile(cached, data, 0644)
1498 }
1499 data = nil
1500 if isInRuntime(pkg.path) {
1501 overrideBcFiles = append(overrideBcFiles, bcFile)
1502 } else {
1503 allBcFiles = append(allBcFiles, bcFile)
1504 }
1505 } else {
1506 writeStr(2, " compile " | pkg.path | "\n")
1507 src := concatSources(pkg.dir, pkg.files)
1508 ir := compileSource(src, compilePath, triple)
1509 src = nil
1510 if printIR {
1511 writeStr(1, ir)
1512 }
1513 llFile := joinPath(tmpdir, safeName(pkg.path) | ".ll")
1514 writeFile(llFile, []byte(ir), 0644)
1515 ir = ""
1516 bcFile := joinPath(tmpdir, safeName(pkg.path) | ".bc")
1517 rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", llFile, "-o", bcFile})
1518 if rc != 0 {
1519 fatal("clang failed for " | pkg.path)
1520 }
1521 allBcFiles = append(allBcFiles, bcFile)
1522 for _, cf := range pkg.cfiles {
1523 cPath := joinPath(pkg.dir, cf)
1524 cbcFile := joinPath(tmpdir, safeName(pkg.path) | "_" | cf | ".bc")
1525 rc = run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", cPath, "-o", cbcFile})
1526 if rc != 0 {
1527 fatal("clang failed for " | cf)
1528 }
1529 allBcFiles = append(allBcFiles, cbcFile)
1530 }
1531 }
1532 }
1533
1534 loadSysroot(joinPath(root, "_sysroot.env"))
1535
1536 sysroot := joinPath(root, "sysroot")
1537 runtimeBc := ""
1538 muslDir := ""
1539 comprtLib := ""
1540 bdwgcLib := ""
1541 muslLib := ""
1542 if isWasm {
1543 runtimeBc = joinPath(joinPath(sysroot, "wasm"), "runtime.bc")
1544 } else {
1545 runtimeBc = getenv("RUNTIME_BC")
1546 if runtimeBc == "" {
1547 runtimeBc = joinPath(sysroot, "runtime.bc")
1548 _, rok := readFile(runtimeBc)
1549 if !rok {
1550 runtimeBc = joinPath(root, "_runtime.bc")
1551 }
1552 }
1553 muslDir = getenv("MUSL_DIR")
1554 if muslDir == "" {
1555 muslDir = joinPath(sysroot, "musl")
1556 }
1557 comprtLib = getenv("COMPRT_LIB")
1558 if comprtLib == "" {
1559 comprtLib = joinPath(joinPath(sysroot, "compiler-rt"), "lib.a")
1560 }
1561 bdwgcLib = getenv("BDWGC_LIB")
1562 if bdwgcLib == "" {
1563 bdwgcLib = joinPath(joinPath(sysroot, "bdwgc"), "lib.a")
1564 }
1565 muslLib = getenv("MUSL_LIB")
1566 if muslLib == "" {
1567 muslLib = joinPath(joinPath(sysroot, "musl"), "lib.a")
1568 }
1569 }
1570
1571 writeStr(2, " generate initAll\n")
1572 ipt := "i64"
1573 sty := "{ptr, i64, i64}"
1574 if isWasm {
1575 ipt = "i32"
1576 sty = "{ptr, i32, i32}"
1577 }
1578 var initCalls string
1579 for _, pkg := range pkgs {
1580 compilePath := pkg.path
1581 if compilePath == "." || hasPrefix(compilePath, "/") || hasPrefix(compilePath, "./") || hasPrefix(compilePath, "../") {
1582 compilePath = pkg.name
1583 }
1584 if compilePath == "main" {
1585 continue
1586 }
1587 initSym := irGlobalSymbol(compilePath, "main")
1588 initCalls = initCalls | " call void " | initSym | "(ptr %context)\n"
1589 }
1590 initAllIR := "@\"os.Args\" = external global " | sty | "\n"
1591 initAllIR = initAllIR | "\ndefine hidden void @runtime.initAll(ptr %context) {\nentry:\n" | initCalls
1592 if !isWasm {
1593 initAllIR = initAllIR | " %_osargs = call " | sty | " @\"os.runtime_args\"(ptr null)\n"
1594 initAllIR = initAllIR | " store " | sty | " %_osargs, ptr @\"os.Args\"\n"
1595 }
1596 initAllIR = initAllIR | " ret void\n}\n"
1597 declared := map[string]bool{}
1598 for _, pkg := range pkgs {
1599 compilePath := pkg.path
1600 if compilePath == "." || hasPrefix(compilePath, "/") || hasPrefix(compilePath, "./") || hasPrefix(compilePath, "../") {
1601 compilePath = pkg.name
1602 }
1603 if compilePath == "main" {
1604 continue
1605 }
1606 declSym := irGlobalSymbol(compilePath, "main")
1607 if declared[declSym] {
1608 continue
1609 }
1610 declared[declSym] = true
1611 initAllIR = initAllIR | "declare void " | declSym | "(ptr)\n"
1612 }
1613 initAllIR = initAllIR | "\n; runtime linkname shims\n"
1614 initAllIR = initAllIR | "declare hidden void @runtime.sleepTicks(i64, ptr)\n"
1615 initAllIR = initAllIR | "define hidden void @\"time.Sleep\"(i64 %d, ptr %ctx) {\n"
1616 initAllIR = initAllIR | " %1 = icmp sle i64 %d, 0\n"
1617 initAllIR = initAllIR | " br i1 %1, label %ret, label %dosleep\n"
1618 initAllIR = initAllIR | "dosleep:\n"
1619 initAllIR = initAllIR | " call void @runtime.sleepTicks(i64 %d, ptr %ctx)\n"
1620 initAllIR = initAllIR | " br label %ret\n"
1621 initAllIR = initAllIR | "ret:\n"
1622 initAllIR = initAllIR | " ret void\n}\n"
1623 initAllIR = initAllIR | "define hidden i32 @\"internal/poll.runtime_pollWait\"(i64 %ctx, i32 %mode, ptr %context) {\n"
1624 initAllIR = initAllIR | " ret i32 0\n}\n"
1625 initAllIR = initAllIR | "define hidden i32 @\"internal/poll.runtime_pollReset\"(i64 %ctx, i32 %mode, ptr %context) {\n"
1626 initAllIR = initAllIR | " ret i32 0\n}\n"
1627 initAllIR = initAllIR | "define hidden void @slices.Sort(" | sty | " %x, ptr %context) {\n"
1628 initAllIR = initAllIR | " ret void\n}\n"
1629 initAllIR = initAllIR | "define hidden void @slices.SortFunc(" | sty | " %x, {ptr, ptr} %cmp, ptr %context) {\n"
1630 initAllIR = initAllIR | " ret void\n}\n"
1631 initAllIR = initAllIR | "define hidden void @slices.SortStableFunc(" | sty | " %x, {ptr, ptr} %cmp, ptr %context) {\n"
1632 initAllIR = initAllIR | " ret void\n}\n"
1633 initAllIR = initAllIR | "\n; time linkname shims\n"
1634 initAllIR = initAllIR | "declare hidden i64 @runtime.nanotime(ptr)\n"
1635 initAllIR = initAllIR | "define hidden i64 @\"time.runtimeNano\"(ptr %ctx) {\n"
1636 initAllIR = initAllIR | " %1 = call i64 @runtime.nanotime(ptr %ctx)\n"
1637 initAllIR = initAllIR | " ret i64 %1\n}\n"
1638 initAllIR = initAllIR | "define hidden i1 @\"time.resetTimer\"(ptr %t, i64 %when, i64 %period, ptr %ctx) {\n"
1639 initAllIR = initAllIR | " ret i1 false\n}\n"
1640 initAllIR = initAllIR | "\n; math/rand linkname shim\n"
1641 initAllIR = initAllIR | "declare hidden {i64, i1} @runtime.hardwareRand(ptr)\n"
1642 initAllIR = initAllIR | "define hidden i64 @\"math/rand.runtime_rand\"(ptr %ctx) {\n"
1643 initAllIR = initAllIR | " %1 = call {i64, i1} @runtime.hardwareRand(ptr %ctx)\n"
1644 initAllIR = initAllIR | " %2 = extractvalue {i64, i1} %1, 0\n"
1645 initAllIR = initAllIR | " ret i64 %2\n}\n"
1646 initAllIR = initAllIR | "\n; internal/poll linkname shims\n"
1647 initAllIR = initAllIR | "define hidden void @\"internal/poll.runtime_pollClose\"(i64 %ctx, ptr %context) {\n"
1648 initAllIR = initAllIR | " ret void\n}\n"
1649 initAllIR = initAllIR | "define hidden void @\"internal/poll.runtime_Semacquire\"(ptr %sema, ptr %context) {\n"
1650 initAllIR = initAllIR | " ret void\n}\n"
1651 initAllIR = initAllIR | "define hidden void @\"internal/poll.runtime_Semrelease\"(ptr %sema, ptr %context) {\n"
1652 initAllIR = initAllIR | " ret void\n}\n"
1653 initAllIR = initAllIR | "define hidden void @\"internal/poll.runtime_pollSetDeadline\"(i64 %ctx, i64 %d, i32 %mode, ptr %context) {\n"
1654 initAllIR = initAllIR | " ret void\n}\n"
1655 initAllIR = initAllIR | "define hidden void @\"internal/poll.runtime_pollUnblock\"(i64 %ctx, ptr %context) {\n"
1656 initAllIR = initAllIR | " ret void\n}\n"
1657 initAllIR = initAllIR | "\n; time linkname shims (timer)\n"
1658 initAllIR = initAllIR | "define hidden ptr @\"time.newTimer\"(i64 %when, i64 %period, {ptr, ptr} %f, {ptr, ptr} %arg, ptr %cp, ptr %ctx) {\n"
1659 initAllIR = initAllIR | " %1 = call ptr @runtime.alloc(" | ipt | " 64, ptr null, ptr null)\n"
1660 initAllIR = initAllIR | " ret ptr %1\n}\n"
1661 initAllIR = initAllIR | "declare hidden ptr @runtime.alloc(" | ipt | ", ptr, ptr)\n"
1662 initAllIR = initAllIR | "define hidden i1 @\"time.stopTimer\"(ptr %t, ptr %ctx) {\n"
1663 initAllIR = initAllIR | " ret i1 false\n}\n"
1664 initAllIR = initAllIR | "define hidden i1 @\"time.runtimeIsBubbled\"(ptr %ctx) {\n"
1665 initAllIR = initAllIR | " ret i1 false\n}\n"
1666 initAllIR = initAllIR | "\n; generic monomorphization stubs\n"
1667 initAllIR = initAllIR | "define hidden void @\"smesh.lol/pkg/nostr/varint.Encode\"({ptr, ptr} %w, i64 %v, ptr %ctx) {\n"
1668 initAllIR = initAllIR | " ret void\n}\n"
1669 initAllIR = initAllIR | "\n; net linkname shim\n"
1670 initAllIR = initAllIR | "define hidden i64 @\"net.runtime_rand\"(ptr %ctx) {\n"
1671 initAllIR = initAllIR | " %1 = call {i64, i1} @runtime.hardwareRand(ptr %ctx)\n"
1672 initAllIR = initAllIR | " %2 = extractvalue {i64, i1} %1, 0\n"
1673 initAllIR = initAllIR | " ret i64 %2\n}\n"
1674 initAllIR = initAllIR | "\n; poll init/open shims\n"
1675 initAllIR = initAllIR | "define hidden void @\"internal/poll.runtime_pollServerInit\"(ptr %ctx) {\n"
1676 initAllIR = initAllIR | " ret void\n}\n"
1677 initAllIR = initAllIR | "define hidden i64 @\"internal/poll.runtime_pollOpen\"(i64 %fd, ptr %ctx) {\n"
1678 initAllIR = initAllIR | " ret i64 0\n}\n"
1679 initAllIR = initAllIR | "\n; syscall shims\n"
1680 initAllIR = initAllIR | "define hidden i32 @syscall.Getpagesize(ptr %ctx) {\n"
1681 initAllIR = initAllIR | " ret i32 4096\n}\n"
1682 initAllIR = initAllIR | "\n; unsafe builtins (compiler should resolve these at compile time)\n"
1683 initAllIR = initAllIR | "define hidden i32 @unsafe.Sizeof({ptr, ptr} %x, ptr %ctx) {\n"
1684 initAllIR = initAllIR | " ret i32 8\n}\n"
1685 initAllIR = initAllIR | "define hidden i32 @unsafe.Offsetof({ptr, ptr} %x, ptr %ctx) {\n"
1686 initAllIR = initAllIR | " ret i32 0\n}\n"
1687 if !isWasm {
1688 initAllIR = initAllIR | "\n; os.runtime_args - builds os.Args from main_argc/main_argv\n"
1689 initAllIR = initAllIR | "@runtime.main_argc = external global i32\n"
1690 initAllIR = initAllIR | "@runtime.main_argv = external global ptr\n"
1691 initAllIR = initAllIR | "declare i64 @strlen(ptr)\n"
1692 initAllIR = initAllIR | "define hidden {ptr, i64, i64} @\"os.runtime_args\"(ptr %ctx) {\n"
1693 initAllIR = initAllIR | "entry:\n"
1694 initAllIR = initAllIR | " %argc = load i32, ptr @runtime.main_argc\n"
1695 initAllIR = initAllIR | " %n = sext i32 %argc to i64\n"
1696 initAllIR = initAllIR | " %cmp0 = icmp sle i64 %n, 0\n"
1697 initAllIR = initAllIR | " br i1 %cmp0, label %ret0, label %alloc\n"
1698 initAllIR = initAllIR | "ret0:\n"
1699 initAllIR = initAllIR | " ret {ptr, i64, i64} zeroinitializer\n"
1700 initAllIR = initAllIR | "alloc:\n"
1701 initAllIR = initAllIR | " %strSz = mul i64 %n, 24\n"
1702 initAllIR = initAllIR | " %backing = call ptr @runtime.alloc(i64 %strSz, ptr null, ptr null)\n"
1703 initAllIR = initAllIR | " %argv = load ptr, ptr @runtime.main_argv\n"
1704 initAllIR = initAllIR | " br label %loop\n"
1705 initAllIR = initAllIR | "loop:\n"
1706 initAllIR = initAllIR | " %i = phi i64 [0, %alloc], [%i1, %next]\n"
1707 initAllIR = initAllIR | " %ap = getelementptr ptr, ptr %argv, i64 %i\n"
1708 initAllIR = initAllIR | " %cstr = load ptr, ptr %ap\n"
1709 initAllIR = initAllIR | " %slen = call i64 @strlen(ptr %cstr)\n"
1710 initAllIR = initAllIR | " %off = mul i64 %i, 24\n"
1711 initAllIR = initAllIR | " %sp = getelementptr i8, ptr %backing, i64 %off\n"
1712 initAllIR = initAllIR | " store ptr %cstr, ptr %sp\n"
1713 initAllIR = initAllIR | " %sp1 = getelementptr i8, ptr %sp, i64 8\n"
1714 initAllIR = initAllIR | " store i64 %slen, ptr %sp1\n"
1715 initAllIR = initAllIR | " %sp2 = getelementptr i8, ptr %sp, i64 16\n"
1716 initAllIR = initAllIR | " store i64 %slen, ptr %sp2\n"
1717 initAllIR = initAllIR | " %i1 = add i64 %i, 1\n"
1718 initAllIR = initAllIR | " %done = icmp sge i64 %i1, %n\n"
1719 initAllIR = initAllIR | " br i1 %done, label %ret, label %next\n"
1720 initAllIR = initAllIR | "next:\n"
1721 initAllIR = initAllIR | " br label %loop\n"
1722 initAllIR = initAllIR | "ret:\n"
1723 initAllIR = initAllIR | " %r0 = insertvalue {ptr, i64, i64} undef, ptr %backing, 0\n"
1724 initAllIR = initAllIR | " %r1 = insertvalue {ptr, i64, i64} %r0, i64 %n, 1\n"
1725 initAllIR = initAllIR | " %r2 = insertvalue {ptr, i64, i64} %r1, i64 %n, 2\n"
1726 initAllIR = initAllIR | " ret {ptr, i64, i64} %r2\n"
1727 initAllIR = initAllIR | "}\n"
1728 }
1729 initAllIR = initAllIR | "\n; functions with uncompiled bodies\n"
1730 initAllIR = initAllIR | "define hidden void @\"crypto/tls.signedMessage\"(" | sty | " %ctx, {ptr, ptr} %transcript, ptr %context) {\n"
1731 initAllIR = initAllIR | " ret void\n}\n"
1732 initAllIR = initAllIR | "define hidden void @\"math/big.Float.Text\"(ptr %x, i8 %format, i32 %prec, ptr %context) {\n"
1733 initAllIR = initAllIR | " ret void\n}\n"
1734
1735 initAllFile := joinPath(tmpdir, "initall.ll")
1736 writeFile(initAllFile, []byte(initAllIR), 0644)
1737 initAllBc := joinPath(tmpdir, "initall.bc")
1738 rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", initAllFile, "-o", initAllBc})
1739 if rc != 0 {
1740 fatal("clang failed for initall")
1741 }
1742
1743 writeStr(2, " merge bitcode\n")
1744 llvmLink := "llvm-link-21"
1745 mergedBc := joinPath(tmpdir, "merged.bc")
1746 linkArgs := []string{llvmLink, "-o", mergedBc, runtimeBc}
1747 stdlibBc := joinPath(sysroot, "stdlib.bc")
1748 if _, slok := readFile(stdlibBc); slok {
1749 linkArgs = append(linkArgs, stdlibBc)
1750 }
1751 if !isWasm {
1752 shimsBc := joinPath(sysroot, "hashmap_shims.bc")
1753 linkArgs = append(linkArgs, shimsBc)
1754 }
1755 for _, bc := range overrideBcFiles {
1756 linkArgs = append(linkArgs, "--override=" | bc)
1757 }
1758 for _, bc := range allBcFiles {
1759 if isWasm {
1760 linkArgs = append(linkArgs, "--override=" | bc)
1761 } else {
1762 linkArgs = append(linkArgs, bc)
1763 }
1764 }
1765 linkArgs = append(linkArgs, initAllBc)
1766 if !isWasm {
1767 runtimeGoBc := joinPath(sysroot, "runtime_go.bc")
1768 if _, gok := readFile(runtimeGoBc); gok {
1769 linkArgs = append(linkArgs, "--override=" | runtimeGoBc)
1770 }
1771 stubsBc := joinPath(sysroot, "stubs_moxie.bc")
1772 if _, sok := readFile(stubsBc); sok {
1773 linkArgs = append(linkArgs, "--override=" | stubsBc)
1774 }
1775 deallocBc := joinPath(sysroot, "dealloc_freelist.bc")
1776 if _, dok := readFile(deallocBc); dok {
1777 linkArgs = append(linkArgs, "--override=" | deallocBc)
1778 }
1779 }
1780 rc = run(linkArgs)
1781 if rc != 0 {
1782 fatal("llvm-link failed")
1783 }
1784
1785 writeStr(2, " link\n")
1786 var ldArgs []string
1787 if isWasm {
1788 mergedObj := joinPath(tmpdir, "merged.o")
1789 rc = run([]string{"llc-21", "-filetype=obj", "-mtriple=wasm32-unknown-unknown", "-o", mergedObj, mergedBc})
1790 if rc != 0 {
1791 fatal("llc failed for wasm")
1792 }
1793 ldArgs = []string{"wasm-ld", "--export-dynamic", "--allow-undefined",
1794 "--gc-sections", "-o", outpath, mergedObj,
1795 "-z", "stack-size=67108864"}
1796 } else {
1797 objFiles := getenv("OBJ_FILES")
1798 if objFiles == "" {
1799 objEntries := listFiles(joinPath(sysroot, "obj"), ".bc")
1800 if objEntries != nil {
1801 for _, e := range objEntries {
1802 objFiles = objFiles | joinPath(joinPath(sysroot, "obj"), e) | " "
1803 }
1804 }
1805 }
1806 ldArgs = []string{"ld.lld-21", "--gc-sections", "-o", outpath,
1807 joinPath(muslDir, "crt1.o"),
1808 mergedBc}
1809 for _, f := range splitBySpace(objFiles) {
1810 if f != "" {
1811 ldArgs = append(ldArgs, f)
1812 }
1813 }
1814 ldArgs = append(ldArgs, comprtLib, muslLib, "--lto-O0", "-z", "stack-size=67108864")
1815 }
1816 rc = run(ldArgs)
1817 if rc != 0 {
1818 fatal("linker failed")
1819 }
1820 writeStr(2, " -> " | outpath | "\n")
1821 }
1822