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