main.mx raw

   1  package main
   2  
   3  import (
   4  	"os"
   5  	"runtime"
   6  	"unsafe"
   7  
   8  	"git.smesh.lol/moxie/pkg/mxutil"
   9  	. "git.smesh.lol/moxie/pkg/types"
  10  )
  11  
  12  // buildState holds all build-configuration and persistent state that
  13  // outlives individual package compilations. Allocated once at init and
  14  // never reassigned; fields are mutated through the pointer.
  15  type buildState struct {
  16  	selfHash        string
  17  	globalModPrefix string
  18  	globalModDir    string
  19  	globalReplaces  map[string]string
  20  	buildJobs       int32
  21  	quietDiscover   bool
  22  	wasmRtPkgs      map[string]bool
  23  	builtinOnly     []string
  24  	pkgKeyMemo      map[string]string
  25  	tlsProbeVar     int32
  26  }
  27  
  28  var bst *buildState
  29  
  30  func initBuildState() {
  31  	if bst == nil {
  32  		bst = &buildState{}
  33  	}
  34  }
  35  
  36  //export mxc_run
  37  func cRun(argv unsafe.Pointer, argc int32) (n int32)
  38  
  39  //export mxc_run_async
  40  func cRunAsync(argv unsafe.Pointer, argc int32) (n int32)
  41  
  42  //export mxc_fork
  43  func cFork() (n int32)
  44  
  45  //export mxc_wait_any
  46  func cWaitAny() (n int32)
  47  
  48  //export mxc_getenv
  49  func cGetenv(name unsafe.Pointer, nameLen int32, buf unsafe.Pointer, bufCap int32) (n int32)
  50  
  51  //export mxc_listdir
  52  func cListdir(dir unsafe.Pointer, dirLen int32, buf unsafe.Pointer, bufCap int32) (n int32)
  53  
  54  //export mxc_writefile
  55  func cWritefile(path unsafe.Pointer, pathLen int32, data unsafe.Pointer, dataLen int32, mode uint32) (n int32)
  56  
  57  //export mxc_mkdir
  58  func cMkdir(path unsafe.Pointer, pathLen int32, mode uint32) (n int32)
  59  
  60  // Ring buffer FFI
  61  //export mxc_ring_create
  62  func cRingCreate(dataSize int32) (p unsafe.Pointer)
  63  //export mxc_ring_destroy
  64  func cRingDestroy(ring unsafe.Pointer)
  65  //export mxc_ring_close
  66  func cRingClose(ring unsafe.Pointer)
  67  //export mxc_ring_closed
  68  func cRingClosed(ring unsafe.Pointer) (n int32)
  69  //export mxc_ring_send
  70  func cRingSend(ring unsafe.Pointer, data unsafe.Pointer, dlen int32) (n int32)
  71  //export mxc_ring_peek_len
  72  func cRingPeekLen(ring unsafe.Pointer) (n int32)
  73  //export mxc_ring_recv
  74  func cRingRecv(ring unsafe.Pointer, buf unsafe.Pointer, bufcap int32) (n int32)
  75  
  76  // Thread FFI
  77  //export mxc_spawn_thread
  78  func cSpawnThread(fn unsafe.Pointer, arg unsafe.Pointer, handleOut unsafe.Pointer, statusOut unsafe.Pointer) (n int32)
  79  //export mxc_thread_alive
  80  func cThreadAlive(statusPtr unsafe.Pointer) (n int32)
  81  //export mxc_thread_join
  82  func cThreadJoin(handlePtr unsafe.Pointer, statusPtr unsafe.Pointer)
  83  //export mxc_usleep
  84  func cUsleep(us int32)
  85  //export mxc_spawn_worker
  86  func cSpawnWorker(arg unsafe.Pointer, handleOut unsafe.Pointer, statusOut unsafe.Pointer) (n int32)
  87  //export mxc_detect_tls
  88  func cDetectTLS() (n int32)
  89  
  90  // TLS probe: the C side spawns a thread that calls mxc_tls_probe_write,
  91  // then the parent calls mxc_tls_probe_read. If the global is TLS, the
  92  // child's write is invisible to the parent.
  93  
  94  //export mxc_tls_probe_write
  95  func tlsProbeWrite() {
  96  	bst.tlsProbeVar = 1
  97  }
  98  
  99  //export mxc_tls_probe_read
 100  func tlsProbeRead() (n int32) {
 101  	return bst.tlsProbeVar
 102  }
 103  
 104  func writeFile(path string, data []byte, mode uint32) {
 105  	if len(path) == 0 {
 106  		return
 107  	}
 108  	cWritefile(unsafe.Pointer(unsafe.SliceData([]byte(path))), int32(len(path)),
 109  		unsafe.Pointer(unsafe.SliceData([]byte(data))), int32(len(data)), mode)
 110  }
 111  
 112  func mkdirAll(path string, mode uint32) {
 113  	if len(path) == 0 {
 114  		return
 115  	}
 116  	cMkdir(unsafe.Pointer(unsafe.SliceData([]byte(path))), int32(len(path)), mode)
 117  }
 118  
 119  func fatal(msg string) {
 120  	mxutil.WriteStr(2, "mxc: " | msg | "\n")
 121  	os.Exit(1)
 122  }
 123  
 124  func clangCompileToObj(clang, triple, bcFile string) (s string) {
 125  	oFile := bcFile | ".o"
 126  	rc := run([]string{clang, "--target=" | triple, "-c", bcFile, "-o", oFile})
 127  	if rc != 0 {
 128  		fatal("clang -c failed for " | bcFile)
 129  	}
 130  	return oFile
 131  }
 132  
 133  func getenv(name string) (s string) {
 134  	buf := []byte{:4096}
 135  	n := cGetenv(unsafe.Pointer(unsafe.SliceData([]byte(name))), int32(len(name)), unsafe.Pointer(unsafe.SliceData(buf)), int32(len(buf)))
 136  	if n <= 0 {
 137  		return ""
 138  	}
 139  	return string(buf[:n])
 140  }
 141  
 142  
 143  func listFiles(dir, ext string) (ss []string) {
 144  	buf := []byte{:65536}
 145  	n := cListdir(unsafe.Pointer(unsafe.SliceData([]byte(dir))), int32(len(dir)),
 146  		unsafe.Pointer(unsafe.SliceData(buf)), int32(len(buf)))
 147  	if n <= 0 {
 148  		return nil
 149  	}
 150  	// Count null-separated entries matching ext.
 151  	nc := int32(0)
 152  	start := int32(0)
 153  	for i := int32(0); i < n; i++ {
 154  		if buf[i] == 0 {
 155  			if mxutil.HasSuffix(string(buf[start:i]), ext) {
 156  				nc++
 157  			}
 158  			start = i + 1
 159  		}
 160  	}
 161  	if nc == 0 {
 162  		return nil
 163  	}
 164  	result := []string{:0:nc}
 165  	start = 0
 166  	for i := int32(0); i < n; i++ {
 167  		if buf[i] == 0 {
 168  			name := string(buf[start:i])
 169  			if mxutil.HasSuffix(name, ext) {
 170  				push(result, name)
 171  			}
 172  			start = i + 1
 173  		}
 174  	}
 175  	return result
 176  }
 177  
 178  func run(args []string) (n int32) {
 179  	argv := []unsafe.Pointer{:len(args) + 1}
 180  	cstrs := [][]byte{:len(args)}
 181  	for i, a := range args {
 182  		b := []byte{:len(a) + 1}
 183  		copy(b, a)
 184  		b[len(a)] = 0
 185  		cstrs[i] = b
 186  		argv[i] = unsafe.Pointer(unsafe.SliceData(b))
 187  	}
 188  	return cRun(unsafe.Pointer(unsafe.SliceData(argv)), int32(len(args)))
 189  }
 190  
 191  // runAsync forks+execs args without waiting. Returns child pid, or -1 on
 192  // fork failure. The forked child copies the argv buffers, so parent-side
 193  // lifetimes end at return.
 194  func runAsync(args []string) (pid int32) {
 195  	argv := []unsafe.Pointer{:len(args) + 1}
 196  	cstrs := [][]byte{:len(args)}
 197  	for i, a := range args {
 198  		b := []byte{:len(a) + 1}
 199  		copy(b, a)
 200  		b[len(a)] = 0
 201  		cstrs[i] = b
 202  		argv[i] = unsafe.Pointer(unsafe.SliceData(b))
 203  	}
 204  	return cRunAsync(unsafe.Pointer(unsafe.SliceData(argv)), int32(len(args)))
 205  }
 206  
 207  // runPool runs cmds with at most jobs concurrent children. Returns false if
 208  // any command exits nonzero (remaining in-flight children are drained, no
 209  // new ones are dispatched).
 210  func runPool(cmds [][]string, jobs int32) (ok bool) {
 211  	if jobs < 1 {
 212  		jobs = 1
 213  	}
 214  	next := int32(0)
 215  	running := int32(0)
 216  	failed := false
 217  	for {
 218  		for !failed && running < jobs && next < int32(len(cmds)) {
 219  			pid := runAsync(cmds[next])
 220  			if pid < 0 {
 221  				failed = true
 222  				break
 223  			}
 224  			running++
 225  			next++
 226  		}
 227  		if running == 0 {
 228  			break
 229  		}
 230  		r := cWaitAny()
 231  		if r == -1 {
 232  			return false
 233  		}
 234  		running--
 235  		if r < 0 {
 236  			failed = true
 237  		}
 238  	}
 239  	return !failed
 240  }
 241  
 242  func parseInt32(s string) (n int32) {
 243  	v := int32(0)
 244  	for i := int32(0); i < int32(len(s)); i++ {
 245  		if s[i] < '0' || s[i] > '9' {
 246  			return 0
 247  		}
 248  		v = v*10 + int32(s[i]-'0')
 249  	}
 250  	return v
 251  }
 252  
 253  func pathBase(path string) (s string) {
 254  	for i := int32(len(path)) - 1; i >= 0; i-- {
 255  		if path[i] == '/' {
 256  			return path[i+1:]
 257  		}
 258  	}
 259  	return path
 260  }
 261  
 262  func pathDir(path string) (s string) {
 263  	for i := int32(len(path)) - 1; i >= 0; i-- {
 264  		if path[i] == '/' {
 265  			if i == 0 {
 266  				return "/"
 267  			}
 268  			return path[:i]
 269  		}
 270  	}
 271  	return "."
 272  }
 273  
 274  func splitSlash(s string) (ss []string) {
 275  	var parts []string
 276  	start := int32(0)
 277  	for i := int32(0); i < int32(len(s)); i++ {
 278  		if s[i] == '/' {
 279  			push(parts, s[start:i])
 280  			start = i + 1
 281  		}
 282  	}
 283  	push(parts, s[start:])
 284  	return parts
 285  }
 286  
 287  func cleanPath(path string) (s string) {
 288  	parts := splitSlash(path)
 289  	var out []string
 290  	for _, p := range parts {
 291  		if p == "." || p == "" {
 292  			continue
 293  		}
 294  		if p == ".." && len(out) > 0 && out[len(out)-1] != ".." {
 295  			out = out[:len(out)-1]
 296  		} else {
 297  			push(out, p)
 298  		}
 299  	}
 300  	if len(out) == 0 {
 301  		return "."
 302  	}
 303  	result := out[0]
 304  	for i := 1; i < len(out); i++ {
 305  		result = result | "/" | out[i]
 306  	}
 307  	if len(path) > 0 && path[0] == '/' {
 308  		result = "/" | result
 309  	}
 310  	return result
 311  }
 312  
 313  func getMoxiePath() (s string) {
 314  	p := getenv("MOXIEPATH")
 315  	if p != "" {
 316  		return p
 317  	}
 318  	home := getenv("HOME")
 319  	if home == "" {
 320  		return "/tmp/moxie"
 321  	}
 322  	return mxutil.JoinPath(home, "moxie")
 323  }
 324  
 325  func mxcVersion() (s string) { return "1.9.10" }
 326  
 327  
 328  func compilerHash() (s string) {
 329  	if bst.selfHash != "" {
 330  		return bst.selfHash
 331  	}
 332  	bin, ok := mxutil.ReadFile("/proc/self/exe")
 333  	if !ok {
 334  		bst.selfHash = "unknown"
 335  		return bst.selfHash
 336  	}
 337  	bst.selfHash = fnvHash(bin)
 338  	return bst.selfHash
 339  }
 340  
 341  func moxieCacheDir() (s string) {
 342  	return mxutil.JoinPath(mxutil.JoinPath(getMoxiePath(), "cache"), "mxc-" | mxcVersion() | "-" | compilerHash())
 343  }
 344  
 345  
 346  func parseReplaceLine(line string, baseDir string) (from string, to string, ok bool) {
 347  	arrow := int32(-1)
 348  	for k := int32(0); k < int32(len(line))-1; k++ {
 349  		if line[k] == '=' && line[k+1] == '>' {
 350  			arrow = k
 351  			break
 352  		}
 353  	}
 354  	if arrow < 0 {
 355  		return "", "", false
 356  	}
 357  	from := mxutil.TrimSpace(line[:arrow])
 358  	to := mxutil.TrimSpace(line[arrow+2:])
 359  	for fi := int32(0); fi < int32(len(from)); fi++ {
 360  		if from[fi] == ' ' {
 361  			from = from[:fi]
 362  			break
 363  		}
 364  	}
 365  	if mxutil.HasPrefix(to, "./") || mxutil.HasPrefix(to, "../") || mxutil.HasPrefix(to, "/") {
 366  		to = cleanPath(mxutil.JoinPath(baseDir, to))
 367  	}
 368  	return from, to, true
 369  }
 370  
 371  func findModuleRoot(startDir string) (modName string, dir string) {
 372  	dir = startDir
 373  	for {
 374  		data, ok := mxutil.ReadFile(mxutil.JoinPath(dir, "moxie.mod"))
 375  		if ok {
 376  			modName = ""
 377  			bst.globalReplaces = map[string]string{}
 378  			lines := mxutil.SplitLines(string(data))
 379  			inReplace := false
 380  			for _, line := range lines {
 381  				line = mxutil.TrimSpace(line)
 382  				if mxutil.HasPrefix(line, "module ") {
 383  					modName = mxutil.TrimSpace(line[7:])
 384  					continue
 385  				}
 386  				if line == "replace (" {
 387  					inReplace = true
 388  					continue
 389  				}
 390  				if inReplace {
 391  					if line == ")" {
 392  						inReplace = false
 393  						continue
 394  					}
 395  					from, to, ok2 := parseReplaceLine(line, dir)
 396  					if ok2 {
 397  						bst.globalReplaces[from] = to
 398  					}
 399  					continue
 400  				}
 401  				if mxutil.HasPrefix(line, "replace ") {
 402  					from, to, ok2 := parseReplaceLine(line[8:], dir)
 403  					if ok2 {
 404  						bst.globalReplaces[from] = to
 405  					}
 406  				}
 407  			}
 408  			if modName != "" {
 409  				return modName, dir
 410  			}
 411  		}
 412  		parent := pathDir(dir)
 413  		if parent == dir || parent == "" || parent == "." {
 414  			break
 415  		}
 416  		dir = parent
 417  	}
 418  	return "", ""
 419  }
 420  
 421  func mergeModReplaces(dir string) {
 422  	data, ok := mxutil.ReadFile(mxutil.JoinPath(dir, "moxie.mod"))
 423  	if !ok {
 424  		return
 425  	}
 426  	if bst.globalReplaces == nil {
 427  		bst.globalReplaces = map[string]string{}
 428  	}
 429  	inReplace := false
 430  	for _, line := range mxutil.SplitLines(string(data)) {
 431  		line = mxutil.TrimSpace(line)
 432  		if line == "replace (" {
 433  			inReplace = true
 434  			continue
 435  		}
 436  		if inReplace {
 437  			if line == ")" {
 438  				inReplace = false
 439  				continue
 440  			}
 441  			from, to, ok2 := parseReplaceLine(line, dir)
 442  			if ok2 {
 443  				if _, exists := bst.globalReplaces[from]; !exists {
 444  					bst.globalReplaces[from] = to
 445  				}
 446  			}
 447  			continue
 448  		}
 449  		if mxutil.HasPrefix(line, "replace ") {
 450  			from, to, ok2 := parseReplaceLine(line[8:], dir)
 451  			if ok2 {
 452  				if _, exists := bst.globalReplaces[from]; !exists {
 453  					bst.globalReplaces[from] = to
 454  				}
 455  			}
 456  		}
 457  	}
 458  }
 459  
 460  type modRequire struct {
 461  	path    string
 462  	version string
 463  }
 464  
 465  func parseModRequires(dir string) (modName string, reqs []modRequire, repls map[string]string) {
 466  	data, ok := mxutil.ReadFile(mxutil.JoinPath(dir, "moxie.mod"))
 467  	if !ok {
 468  		return "", nil, nil
 469  	}
 470  	modName = ""
 471  	repls = map[string]string{}
 472  	inRequire := false
 473  	inReplace := false
 474  	for _, line := range mxutil.SplitLines(string(data)) {
 475  		line = mxutil.TrimSpace(line)
 476  		if mxutil.HasPrefix(line, "module ") {
 477  			modName = mxutil.TrimSpace(line[7:])
 478  			continue
 479  		}
 480  		if line == "require (" {
 481  			inRequire = true
 482  			continue
 483  		}
 484  		if inRequire {
 485  			if line == ")" {
 486  				inRequire = false
 487  				continue
 488  			}
 489  			parts := splitBySpace(line)
 490  			if len(parts) >= 2 {
 491  				push(reqs, modRequire{path: parts[0], version: parts[1]})
 492  			} else if len(parts) == 1 {
 493  				push(reqs, modRequire{path: parts[0], version: ""})
 494  			}
 495  			continue
 496  		}
 497  		if mxutil.HasPrefix(line, "require ") {
 498  			parts := splitBySpace(line[8:])
 499  			if len(parts) >= 2 {
 500  				push(reqs, modRequire{path: parts[0], version: parts[1]})
 501  			} else if len(parts) == 1 {
 502  				push(reqs, modRequire{path: parts[0], version: ""})
 503  			}
 504  			continue
 505  		}
 506  		if line == "replace (" {
 507  			inReplace = true
 508  			continue
 509  		}
 510  		if inReplace {
 511  			if line == ")" {
 512  				inReplace = false
 513  				continue
 514  			}
 515  			from, to, ok2 := parseReplaceLine(line, dir)
 516  			if ok2 {
 517  				repls[from] = to
 518  			}
 519  			continue
 520  		}
 521  		if mxutil.HasPrefix(line, "replace ") {
 522  			from, to, ok2 := parseReplaceLine(line[8:], dir)
 523  			if ok2 {
 524  				repls[from] = to
 525  			}
 526  		}
 527  	}
 528  	return modName, reqs, repls
 529  }
 530  
 531  func isRemotePath(path string) (ok bool) {
 532  	slash := int32(-1)
 533  	for i := int32(0); i < int32(len(path)); i++ {
 534  		if path[i] == '/' {
 535  			slash = i
 536  			break
 537  		}
 538  	}
 539  	if slash < 0 {
 540  		return false
 541  	}
 542  	host := path[:slash]
 543  	for i := int32(0); i < int32(len(host)); i++ {
 544  		if host[i] == '.' {
 545  			return true
 546  		}
 547  	}
 548  	return false
 549  }
 550  
 551  func repoLockPath(mpath, repoPath string) (s string) {
 552  	return mxutil.JoinPath(mpath, repoPath | ".lock")
 553  }
 554  
 555  func acquireRepoLock(mpath, repoPath string) (ok bool) {
 556  	lp := repoLockPath(mpath, repoPath)
 557  	_, locked := mxutil.ReadFile(lp)
 558  	if locked {
 559  		return false
 560  	}
 561  	mkdirAll(pathDir(lp), 0755)
 562  	writeFile(lp, []byte("1"), 0644)
 563  	return true
 564  }
 565  
 566  func releaseRepoLock(mpath, repoPath string) {
 567  	os.Remove(repoLockPath(mpath, repoPath))
 568  }
 569  
 570  func autoFetchRepo(pkgPath, version string) (s string) {
 571  	mpath := getMoxiePath()
 572  	repoPath := pkgPath
 573  	for {
 574  		d := mxutil.JoinPath(mpath, repoPath)
 575  		gh := mxutil.JoinPath(d, ".git/HEAD")
 576  		_, ex := mxutil.ReadFile(gh)
 577  		if ex {
 578  			break
 579  		}
 580  		parent := pathDir(repoPath)
 581  		if parent == repoPath || parent == "." || parent == "" {
 582  			break
 583  		}
 584  		parentDest := mxutil.JoinPath(mpath, parent)
 585  		parentGit := mxutil.JoinPath(parentDest, ".git/HEAD")
 586  		_, parentExists := mxutil.ReadFile(parentGit)
 587  		if parentExists {
 588  			repoPath = parent
 589  			break
 590  		}
 591  		repoPath = parent
 592  	}
 593  
 594  	if !acquireRepoLock(mpath, repoPath) {
 595  		mxutil.WriteStr(2, "  waiting for lock: " | repoPath | "\n")
 596  		fatal("repo locked: " | repoPath)
 597  	}
 598  
 599  	dest := mxutil.JoinPath(mpath, repoPath)
 600  	gitHead := mxutil.JoinPath(dest, ".git/HEAD")
 601  	_, exists := mxutil.ReadFile(gitHead)
 602  
 603  	if !exists {
 604  		mkdirAll(pathDir(dest), 0755)
 605  		url := "https://" | repoPath
 606  		mxutil.WriteStr(2, "  auto-fetch " | repoPath | "\n")
 607  		if run([]string{"git", "clone", url, dest}) != 0 {
 608  			releaseRepoLock(mpath, repoPath)
 609  			return ""
 610  		}
 611  	}
 612  
 613  	if version != "" && version != "v0.0.0" {
 614  		if run([]string{"git", "-C", dest, "checkout", version}) != 0 {
 615  			run([]string{"git", "-C", dest, "fetch", "--tags"})
 616  			run([]string{"git", "-C", dest, "pull"})
 617  			run([]string{"git", "-C", dest, "checkout", version})
 618  		}
 619  	}
 620  
 621  	releaseRepoLock(mpath, repoPath)
 622  	return mxutil.JoinPath(mpath, pkgPath)
 623  }
 624  
 625  func gitURLToModPath(url string) (s string) {
 626  	u := url
 627  	// strip scheme
 628  	for _, scheme := range []string{"ssh://", "https://", "http://", "git://"} {
 629  		if mxutil.HasPrefix(u, scheme) {
 630  			u = u[len(scheme):]
 631  			break
 632  		}
 633  	}
 634  	// strip user@
 635  	for i := int32(0); i < int32(len(u)); i++ {
 636  		if u[i] == '@' {
 637  			u = u[i+1:]
 638  			break
 639  		}
 640  		if u[i] == '/' {
 641  			break
 642  		}
 643  	}
 644  	// host:port/path or host:path (scp-style) or host/path
 645  	// extract host, skip port if numeric, keep path
 646  	host := ""
 647  	rest := ""
 648  	for i := int32(0); i < int32(len(u)); i++ {
 649  		if u[i] == ':' {
 650  			host = u[:i]
 651  			after := u[i+1:]
 652  			// skip port (digits until /)
 653  			j := int32(0)
 654  			for j < int32(len(after)) && after[j] >= '0' && after[j] <= '9' {
 655  				j++
 656  			}
 657  			if j > 0 && j < int32(len(after)) && after[j] == '/' {
 658  				rest = after[j+1:]
 659  			} else if j > 0 && j == int32(len(after)) {
 660  				rest = ""
 661  			} else {
 662  				rest = after
 663  			}
 664  			break
 665  		}
 666  		if u[i] == '/' {
 667  			host = u[:i]
 668  			rest = u[i+1:]
 669  			break
 670  		}
 671  	}
 672  	if host == "" {
 673  		return ""
 674  	}
 675  	// strip ~/ prefix from path
 676  	if mxutil.HasPrefix(rest, "~/") {
 677  		rest = rest[2:]
 678  	}
 679  	// strip .git suffix
 680  	if mxutil.HasSuffix(rest, ".git") {
 681  		rest = rest[:len(rest)-4]
 682  	}
 683  	if rest == "" {
 684  		return ""
 685  	}
 686  	return host | "/" | rest
 687  }
 688  
 689  func currentBranch(dest string) (s string) {
 690  	data, ok := mxutil.ReadFile(mxutil.JoinPath(dest, ".git/HEAD"))
 691  	if !ok {
 692  		return ""
 693  	}
 694  	line := mxutil.TrimSpace(string(data))
 695  	if !mxutil.HasPrefix(line, "ref: refs/heads/") {
 696  		return ""
 697  	}
 698  	return line[len("ref: refs/heads/"):]
 699  }
 700  
 701  func cmdFetch(url string) {
 702  	mpath := getMoxiePath()
 703  	mkdirAll(mpath, 0755)
 704  
 705  	rootDir := "."
 706  	if url != "" {
 707  		modPath := gitURLToModPath(url)
 708  		if modPath == "" {
 709  			fatal("cannot derive module path from URL: " | url)
 710  		}
 711  		dest := mxutil.JoinPath(mpath, modPath)
 712  		_, exists := mxutil.ReadFile(mxutil.JoinPath(dest, ".git/HEAD"))
 713  		if exists {
 714  			mxutil.WriteStr(2, modPath | ": fetch\n")
 715  			run([]string{"git", "-C", dest, "remote", "set-url", "origin", url})
 716  			run([]string{"git", "-C", dest, "fetch", "origin"})
 717  			// older clones lack the origin/HEAD symbolic ref; resolve it
 718  			// from the remote before resetting to it
 719  			run([]string{"git", "-C", dest, "remote", "set-head", "origin", "-a"})
 720  			if run([]string{"git", "-C", dest, "reset", "--hard", "origin/HEAD"}) != 0 {
 721  				// server advertises no HEAD (default branch unset);
 722  				// fall back to the upstream of the current branch
 723  				br := currentBranch(dest)
 724  				if br != "" {
 725  					run([]string{"git", "-C", dest, "reset", "--hard", "origin/" | br})
 726  				}
 727  			}
 728  		} else {
 729  			mxutil.WriteStr(2, modPath | ": clone\n")
 730  			mkdirAll(pathDir(dest), 0755)
 731  			if run([]string{"git", "clone", url, dest}) != 0 {
 732  				fatal("clone failed: " | url)
 733  			}
 734  		}
 735  		rootDir = dest
 736  	}
 737  
 738  	modName, reqs, repls := parseModRequires(rootDir)
 739  	if modName == "" {
 740  		fatal("no moxie.mod in " | rootDir)
 741  	}
 742  	if url == "" {
 743  		mxutil.WriteStr(2, "module: " | modName | "\n")
 744  	}
 745  
 746  	queue := []modRequire{:0:len(reqs)}
 747  	for _, r := range reqs {
 748  		if _, ok := repls[r.path]; ok {
 749  			mxutil.WriteStr(2, "  skip (replaced): " | r.path | "\n")
 750  			continue
 751  		}
 752  		push(queue, r)
 753  	}
 754  
 755  	seen := map[string]bool{}
 756  	for len(queue) > 0 {
 757  		req := queue[0]
 758  		queue = queue[1:]
 759  		if seen[req.path] {
 760  			continue
 761  		}
 762  		seen[req.path] = true
 763  
 764  		if !acquireRepoLock(mpath, req.path) {
 765  			mxutil.WriteStr(2, "  locked: " | req.path | " (another build in progress)\n")
 766  			fatal("repo locked: " | req.path)
 767  		}
 768  
 769  		dest := mxutil.JoinPath(mpath, req.path)
 770  		gitHead := mxutil.JoinPath(dest, ".git/HEAD")
 771  		_, exists := mxutil.ReadFile(gitHead)
 772  
 773  		if !exists {
 774  			mkdirAll(pathDir(dest), 0755)
 775  			cloneURL := "https://" | req.path
 776  			mxutil.WriteStr(2, "  clone " | req.path | "\n")
 777  			if run([]string{"git", "clone", cloneURL, dest}) != 0 {
 778  				releaseRepoLock(mpath, req.path)
 779  				mxutil.WriteStr(2, "  FAIL: clone " | req.path | "\n")
 780  				continue
 781  			}
 782  		}
 783  
 784  		if req.version != "" && req.version != "v0.0.0" {
 785  			if run([]string{"git", "-C", dest, "checkout", req.version}) != 0 {
 786  				mxutil.WriteStr(2, "  tag " | req.version | " not found, pulling\n")
 787  				run([]string{"git", "-C", dest, "pull"})
 788  				if run([]string{"git", "-C", dest, "checkout", req.version}) != 0 {
 789  					mxutil.WriteStr(2, "  WARN: " | req.version | " unavailable for " | req.path | "\n")
 790  				}
 791  			} else {
 792  				mxutil.WriteStr(2, "  " | req.path | " @ " | req.version | "\n")
 793  			}
 794  		} else if exists {
 795  			mxutil.WriteStr(2, "  pull " | req.path | "\n")
 796  			run([]string{"git", "-C", dest, "pull"})
 797  		} else {
 798  			mxutil.WriteStr(2, "  fetched " | req.path | "\n")
 799  		}
 800  
 801  		releaseRepoLock(mpath, req.path)
 802  
 803  		_, subReqs, _ := parseModRequires(dest)
 804  		for _, sr := range subReqs {
 805  			if !seen[sr.path] {
 806  				push(queue, sr)
 807  			}
 808  		}
 809  	}
 810  
 811  	mxutil.WriteStr(2, "fetch done\n")
 812  }
 813  
 814  func joinStrings(ss []string, sep string) (s string) {
 815  	if len(ss) == 0 {
 816  		return ""
 817  	}
 818  	n := int32(0)
 819  	for _, v := range ss {
 820  		n += int32(len(v))
 821  	}
 822  	n += int32(len(sep)) * (int32(len(ss)) - 1)
 823  	buf := []byte{:0:n}
 824  	for i, v := range ss {
 825  		if i > 0 {
 826  			buf = buf | sep
 827  		}
 828  		buf = buf | v
 829  	}
 830  	return string(buf)
 831  }
 832  
 833  func appendUniq(ss []string, s string) (ss2 []string) {
 834  	for _, x := range ss {
 835  		if x == s {
 836  			return ss
 837  		}
 838  	}
 839  	push(ss, s)
 840  	return ss
 841  }
 842  
 843  func fnvHash(data []byte) (s string) {
 844  	h := uint32(2166136261)
 845  	for i := int32(0); i < int32(len(data)); i++ {
 846  		h ^= uint32(data[i])
 847  		h *= uint32(16777619)
 848  	}
 849  	hex := "0123456789abcdef"
 850  	buf := []byte{:8}
 851  	for i := int32(7); i >= 0; i-- {
 852  		buf[i] = hex[h&0xf]
 853  		h >>= 4
 854  	}
 855  	return string(buf)
 856  }
 857  
 858  func pkgCacheDir() (s string) {
 859  	if mxutil.TargetArch == "wasm" {
 860  		return mxutil.JoinPath(moxieCacheDir(), "pkg-wasm")
 861  	}
 862  	return mxutil.JoinPath(moxieCacheDir(), "pkg")
 863  }
 864  
 865  func pkgCacheKey(dir string, files []string) (s string) {
 866  	var sorted []string
 867  	for _, f := range files {
 868  		push(sorted, f)
 869  	}
 870  	for i := int32(1); i < int32(len(sorted)); i++ {
 871  		for j := i; j > 0 && sorted[j] < sorted[j-1]; j-- {
 872  			sorted[j], sorted[j-1] = sorted[j-1], sorted[j]
 873  		}
 874  	}
 875  	var all []byte
 876  	for _, f := range sorted {
 877  		p := mxutil.JoinPath(dir, f)
 878  		data, ok := mxutil.ReadFile(p)
 879  		if !ok {
 880  			continue
 881  		}
 882  		all = all | data
 883  		push(all, 0)
 884  	}
 885  	return fnvHash(all)
 886  }
 887  
 888  // pkgKeyMemo caches each package's transitive hash by import path. Filled
 889  // in dependency order (resolver emits deps before dependents), so a
 890  // dependent's key always sees its deps' final hashes. Without dep hashes in
 891  // the key, a dependency's layout change would leave a dependent's cached .bc
 892  // reusable with a stale ABI - silent struct-offset corruption at link time.
 893  
 894  func pkgHash(p *pkgInfo) (s string) {
 895  	if bst.pkgKeyMemo == nil {
 896  		bst.pkgKeyMemo = map[string]string{}
 897  	}
 898  	if h, ok := bst.pkgKeyMemo[p.path]; ok {
 899  		return h
 900  	}
 901  	var sorted []string
 902  	for _, imp := range p.imports {
 903  		push(sorted, imp)
 904  	}
 905  	for i := int32(1); i < int32(len(sorted)); i++ {
 906  		for j := i; j > 0 && sorted[j] < sorted[j-1]; j-- {
 907  			sorted[j], sorted[j-1] = sorted[j-1], sorted[j]
 908  		}
 909  	}
 910  	acc := []byte(pkgCacheKey(p.dir, p.files))
 911  	for _, imp := range sorted {
 912  		if dh, ok := bst.pkgKeyMemo[imp]; ok {
 913  			push(acc, '|')
 914  			acc = acc | dh
 915  		}
 916  	}
 917  	hv := fnvHash(acc)
 918  	bst.pkgKeyMemo[p.path] = hv
 919  	return hv
 920  }
 921  
 922  func versionedCacheName(hash, version, ext string) (s string) {
 923  	if version != "" {
 924  		return hash | "_" | version | ext
 925  	}
 926  	return hash | ext
 927  }
 928  
 929  func cachedBcPath(cacheBase, pkgPath, hash, version string) (s string) {
 930  	return mxutil.JoinPath(mxutil.JoinPath(cacheBase, safeName(pkgPath)), versionedCacheName(hash, version, ".bc"))
 931  }
 932  
 933  func isStdlib(path string) (ok bool) {
 934  	return !mxutil.HasPrefix(path, "/") && !mxutil.HasPrefix(path, "./") && !mxutil.HasPrefix(path, "../")
 935  }
 936  
 937  
 938  func isBuiltinOnly(path string) (ok bool) {
 939  	for _, b := range bst.builtinOnly {
 940  		if path == b {
 941  			return true
 942  		}
 943  	}
 944  	return false
 945  }
 946  
 947  func loadSysroot(path string) {
 948  	data, ok := mxutil.ReadFile(path)
 949  	if !ok {
 950  		return
 951  	}
 952  	for _, line := range mxutil.SplitLines(string(data)) {
 953  		line = mxutil.TrimSpace(line)
 954  		if len(line) == 0 || line[0] == '#' {
 955  			continue
 956  		}
 957  		eq := int32(-1)
 958  		for i := int32(0); i < int32(len(line)); i++ {
 959  			if line[i] == '=' {
 960  				eq = i
 961  				break
 962  			}
 963  		}
 964  		if eq < 0 {
 965  			continue
 966  		}
 967  		key := line[:eq]
 968  		val := line[eq+1:]
 969  		os.Setenv(key, val)
 970  	}
 971  }
 972  
 973  func splitBySpace(s string) (ss []string) {
 974  	var result []string
 975  	start := int32(0)
 976  	inWord := false
 977  	for i := int32(0); i < int32(len(s)); i++ {
 978  		if s[i] == ' ' || s[i] == '\t' {
 979  			if inWord {
 980  				push(result, s[start:i])
 981  				inWord = false
 982  			}
 983  		} else {
 984  			if !inWord {
 985  				start = i
 986  				inWord = true
 987  			}
 988  		}
 989  	}
 990  	if inWord {
 991  		push(result, s[start:])
 992  	}
 993  	return result
 994  }
 995  
 996  func safeName(pkg string) (s string) {
 997  	b := []byte{:len(pkg)}
 998  	for i := int32(0); i < int32(len(pkg)); i++ {
 999  		if pkg[i] == '/' {
1000  			b[i] = '_'
1001  		} else {
1002  			b[i] = pkg[i]
1003  		}
1004  	}
1005  	return string(b)
1006  }
1007  
1008  func extractPkgName(data []byte) (s string) {
1009  	for i := int32(0); i < int32(len(data)); i++ {
1010  		if i+8 < int32(len(data)) && string(data[i:i+8]) == "package " {
1011  			j := i + 8
1012  			for j < int32(len(data)) && data[j] != '\n' && data[j] != '\r' && data[j] != ' ' {
1013  				j++
1014  			}
1015  			return string(data[i+8 : j])
1016  		}
1017  		if data[i] == '\n' {
1018  			continue
1019  		}
1020  		if data[i] == '/' && i+1 < int32(len(data)) && data[i+1] == '/' {
1021  			for i < int32(len(data)) && data[i] != '\n' {
1022  				i++
1023  			}
1024  			continue
1025  		}
1026  		// Block comment: skip to closing */ (fmt/doc.mx has its package
1027  		// clause below a /* ... */ doc comment).
1028  		if data[i] == '/' && i+1 < int32(len(data)) && data[i+1] == '*' {
1029  			i += 2
1030  			for i+1 < int32(len(data)) && !(data[i] == '*' && data[i+1] == '/') {
1031  				i++
1032  			}
1033  			i++ // lands on '/', loop increment moves past it
1034  			continue
1035  		}
1036  		if data[i] == ' ' || data[i] == '\t' || data[i] == '\r' {
1037  			continue
1038  		}
1039  		break
1040  	}
1041  	return "main"
1042  }
1043  
1044  func extractImports(data []byte) (ss []string) {
1045  	imports := []string{:0:32}
1046  	off := int32(0)
1047  	for off < int32(len(data)) {
1048  		ls := off
1049  		for off < int32(len(data)) && data[off] != '\n' {
1050  			off++
1051  		}
1052  		le := off
1053  		if off < int32(len(data)) {
1054  			off++
1055  		}
1056  		// Trim
1057  		for ls < le && (data[ls] == ' ' || data[ls] == '\t') {
1058  			ls++
1059  		}
1060  		for le > ls && (data[le-1] == ' ' || data[le-1] == '\t' || data[le-1] == '\r') {
1061  			le--
1062  		}
1063  		line := string(data[ls:le])
1064  		if line == "import (" {
1065  			// Scan block
1066  			for off < int32(len(data)) {
1067  				bls := off
1068  				for off < int32(len(data)) && data[off] != '\n' {
1069  					off++
1070  				}
1071  				ble := off
1072  				if off < int32(len(data)) {
1073  					off++
1074  				}
1075  				for bls < ble && (data[bls] == ' ' || data[bls] == '\t') {
1076  					bls++
1077  				}
1078  				for ble > bls && (data[ble-1] == ' ' || data[ble-1] == '\t' || data[ble-1] == '\r') {
1079  					ble--
1080  				}
1081  				bt := string(data[bls:ble])
1082  				if bt == ")" {
1083  					break
1084  				}
1085  				imp := mxutil.ExtractQuoted(bt)
1086  				if imp != "" {
1087  					imports = appendUniq(imports, imp)
1088  				}
1089  			}
1090  			continue
1091  		}
1092  		if mxutil.HasPrefix(line, "import ") {
1093  			imp := mxutil.ExtractQuoted(line[7:])
1094  			if imp != "" {
1095  				imports = appendUniq(imports, imp)
1096  			}
1097  		}
1098  	}
1099  	return imports
1100  }
1101  
1102  type srcSpan struct {
1103  	file      string
1104  	outStart  int32
1105  	srcLine   int32
1106  }
1107  
1108  
1109  func concatLineToFile(concatLine int32) (file string, srcLine int32) {
1110  	if len(cctx.concatSourceMap) == 0 {
1111  		return "", concatLine
1112  	}
1113  	best := int32(0)
1114  	for i, s := range cctx.concatSourceMap {
1115  		if s.outStart <= concatLine {
1116  			best = int32(i)
1117  		}
1118  	}
1119  	sp := cctx.concatSourceMap[best]
1120  	return sp.file, sp.srcLine + (concatLine - sp.outStart)
1121  }
1122  
1123  // scanFileRegions scans file bytes to find the body start offset and
1124  // extract import specs. No SplitLines, no per-line copies. Returns
1125  // bodyStart (byte offset of first non-package/import line) and the
1126  // source line number of that offset (1-based).
1127  func scanFileRegions(data []byte, imports map[string]bool) (bodyStart int32, bodyLine int32, fimps []string) {
1128  	off := int32(0)
1129  	lineNum := int32(1)
1130  	bodyStart = -1
1131  	for off < int32(len(data)) {
1132  		// Find line extent
1133  		lineStart := off
1134  		for off < int32(len(data)) && data[off] != '\n' {
1135  			off++
1136  		}
1137  		lineEnd := off
1138  		if off < int32(len(data)) {
1139  			off++ // skip \n
1140  		}
1141  		// Trim whitespace for classification
1142  		ls := lineStart
1143  		for ls < lineEnd && (data[ls] == ' ' || data[ls] == '\t') {
1144  			ls++
1145  		}
1146  		le := lineEnd
1147  		for le > ls && (data[le-1] == ' ' || data[le-1] == '\t' || data[le-1] == '\r') {
1148  			le--
1149  		}
1150  		trimmed := data[ls:le]
1151  		if len(trimmed) >= 8 && string(trimmed[:8]) == "package " {
1152  			lineNum++
1153  			continue
1154  		}
1155  		if string(trimmed) == "import (" {
1156  			lineNum++
1157  			// Scan import block
1158  			for off < int32(len(data)) {
1159  				bls := off
1160  				for off < int32(len(data)) && data[off] != '\n' {
1161  					off++
1162  				}
1163  				ble := off
1164  				if off < int32(len(data)) {
1165  					off++
1166  				}
1167  				// Trim
1168  				for bls < ble && (data[bls] == ' ' || data[bls] == '\t') {
1169  					bls++
1170  				}
1171  				for ble > bls && (data[ble-1] == ' ' || data[ble-1] == '\t' || data[ble-1] == '\r') {
1172  					ble--
1173  				}
1174  				bt := data[bls:ble]
1175  				lineNum++
1176  				if string(bt) == ")" {
1177  					break
1178  				}
1179  				if len(bt) > 0 {
1180  					spec := string(bt)
1181  					imports[spec] = true
1182  					push(fimps, spec)
1183  				}
1184  			}
1185  			continue
1186  		}
1187  		if len(trimmed) >= 7 && string(trimmed[:7]) == "import " && (len(trimmed) < 8 || trimmed[7] != '(') {
1188  			spec := string(trimmed[7:])
1189  			imports[spec] = true
1190  			push(fimps, spec)
1191  			lineNum++
1192  			continue
1193  		}
1194  		// First body line
1195  		if bodyStart < 0 {
1196  			bodyStart = lineStart
1197  			bodyLine = lineNum
1198  		}
1199  		lineNum++
1200  	}
1201  	if bodyStart < 0 {
1202  		bodyStart = int32(len(data))
1203  		bodyLine = lineNum
1204  	}
1205  	return bodyStart, bodyLine, fimps
1206  }
1207  
1208  func concatSources(dir string, files []string) (buf []byte) {
1209  	imports := map[string]bool{}
1210  	pkgName := ""
1211  	// First pass: scan each file for imports and body boundaries.
1212  	// Store body as a byte slice reference into the file data.
1213  	type fileBody struct {
1214  		name      string
1215  		data      []byte // full file data (freed after copy into output)
1216  		bodyStart int32
1217  		bodyLine  int32
1218  		fimps     []string
1219  	}
1220  	var fb []fileBody
1221  	for _, f := range files {
1222  		data, ok := mxutil.ReadFile(mxutil.JoinPath(dir, f))
1223  		if !ok {
1224  			continue
1225  		}
1226  		if pkgName == "" {
1227  			pkgName = extractPkgName(data)
1228  		}
1229  		bs, bl, fimps := scanFileRegions(data, imports)
1230  		push(fb, fileBody{name: f, data: data, bodyStart: bs, bodyLine: bl, fimps: fimps})
1231  	}
1232  	// Estimate total size for pre-allocation
1233  	totalSize := int32(64) // header
1234  	for _, f := range fb {
1235  		totalSize += int32(len(f.data)) - f.bodyStart + 1
1236  	}
1237  	for imp := range imports {
1238  		totalSize += int32(len(imp)) + 2
1239  	}
1240  	var out []byte
1241  	out = out | ("package " | pkgName | "\n")
1242  	if len(imports) > 0 {
1243  		var impKeys []string
1244  		for imp := range imports {
1245  			push(impKeys, imp)
1246  		}
1247  		for i := int32(1); i < int32(len(impKeys)); i++ {
1248  			for j := i; j > 0 && impKeys[j] < impKeys[j-1]; j-- {
1249  				impKeys[j], impKeys[j-1] = impKeys[j-1], impKeys[j]
1250  			}
1251  		}
1252  		out = out | "import (\n"
1253  		for _, imp := range impKeys {
1254  			push(out, '\t')
1255  			out = out | imp
1256  			push(out, '\n')
1257  		}
1258  		out = out | ")\n"
1259  	}
1260  	cctx.concatSourceMap = nil
1261  	mxutil.ResetConcatImportSegs()
1262  	outLine := int32(1)
1263  	for j := int32(0); j < int32(len(out)); j++ {
1264  		if out[j] == '\n' {
1265  			outLine++
1266  		}
1267  	}
1268  	var bodyChunks [][]byte
1269  	push(bodyChunks, out)
1270  	for i := range fb {
1271  		body := fb[i].data[fb[i].bodyStart:]
1272  		push(cctx.concatSourceMap, srcSpan{
1273  			file:     fb[i].name,
1274  			outStart: outLine,
1275  			srcLine:  fb[i].bodyLine,
1276  		})
1277  		mxutil.AppendConcatImportSeg(mxutil.ImportSeg{
1278  			OutStart: outLine,
1279  			Specs:    fb[i].fimps,
1280  		})
1281  		for j := int32(0); j < int32(len(body)); j++ {
1282  			if body[j] == '\n' {
1283  				outLine++
1284  			}
1285  		}
1286  		outLine++
1287  		push(bodyChunks, body)
1288  		push(bodyChunks, []byte("\n"))
1289  	}
1290  	return joinChunks(bodyChunks)
1291  }
1292  
1293  func fileContainsPart(base string, part string) (ok bool) {
1294  	plen := len(part)
1295  	for i := 0; i <= len(base)-plen; i++ {
1296  		if base[i:i+plen] == part {
1297  			before := i == 0 || base[i-1] == '_'
1298  			after := i+plen == len(base) || base[i+plen] == '_'
1299  			if before && after {
1300  				return true
1301  			}
1302  		}
1303  	}
1304  	return false
1305  }
1306  
1307  func shouldSkipFile(name string) (ok bool) {
1308  	if mxutil.HasSuffix(name, "_test.mx") {
1309  		return true
1310  	}
1311  	base := name
1312  	if mxutil.HasSuffix(base, ".mx") {
1313  		base = base[:len(base)-3]
1314  	}
1315  	if mxutil.TargetArch == "wasm" {
1316  		for _, a := range []string{"amd64", "arm64", "386"} {
1317  			if fileContainsPart(base, a) {
1318  				return true
1319  			}
1320  		}
1321  		for _, s := range []string{"_linux", "_unix", "_darwin", "_posix"} {
1322  			if mxutil.HasSuffix(base, s) {
1323  				return true
1324  			}
1325  		}
1326  	} else {
1327  		for _, a := range []string{"arm64"} {
1328  			if fileContainsPart(base, a) {
1329  				return true
1330  			}
1331  		}
1332  		if mxutil.HasSuffix(base, "_wasm") {
1333  			return true
1334  		}
1335  	}
1336  	return false
1337  }
1338  
1339  func isTagChar(c byte) (ok bool) {
1340  	return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '_' || c == '.'
1341  }
1342  
1343  func constraintContainsPositive(line string, word string) (ok bool) {
1344  	wl := int32(len(word))
1345  	for i := int32(0); i <= int32(len(line))-wl; i++ {
1346  		if string(line[i:i+wl]) == word {
1347  			if i > 0 && line[i-1] == '!' {
1348  				continue
1349  			}
1350  			after := i + wl
1351  			if after < int32(len(line)) {
1352  				if isTagChar(line[after]) {
1353  					continue
1354  				}
1355  			}
1356  			if i > 0 {
1357  				if isTagChar(line[i-1]) {
1358  					continue
1359  				}
1360  			}
1361  			// check if inside a negated group !(...)
1362  			if isInsideNegatedGroup(line, i) {
1363  				continue
1364  			}
1365  			return true
1366  		}
1367  	}
1368  	return false
1369  }
1370  
1371  func isInsideNegatedGroup(line string, pos int32) (result bool) {
1372  	// scan backwards from pos for !(, counting parens
1373  	for j := pos - 1; j >= 1; j-- {
1374  		if line[j] == '(' && line[j-1] == '!' {
1375  			// found !(, now check the matching ) is after pos
1376  			depth := int32(1)
1377  			k := j + 1
1378  			for k < int32(len(line)) && depth > 0 {
1379  				if line[k] == '(' { depth++ }
1380  				if line[k] == ')' { depth-- }
1381  				k++
1382  			}
1383  			if k-1 > pos { // closing ) is after our position
1384  				return true
1385  			}
1386  		}
1387  	}
1388  	return false
1389  }
1390  
1391  func constraintContainsNegated(line string, word string) (ok bool) {
1392  	wl := int32(len(word))
1393  	// check direct negation: !word
1394  	for i := int32(1); i <= int32(len(line))-wl; i++ {
1395  		if string(line[i:i+wl]) == word && line[i-1] == '!' {
1396  			after := i + wl
1397  			if after < int32(len(line)) {
1398  				if isTagChar(line[after]) {
1399  					continue
1400  				}
1401  			}
1402  			return true
1403  		}
1404  	}
1405  	// check parenthesized negation: !(... word ...)
1406  	for i := int32(0); i+1 < int32(len(line)); i++ {
1407  		if line[i] == '!' && line[i+1] == '(' {
1408  			depth := int32(1)
1409  			j := i + 2
1410  			for j < int32(len(line)) && depth > 0 {
1411  				if line[j] == '(' { depth++ }
1412  				if line[j] == ')' { depth-- }
1413  				j++
1414  			}
1415  			group := string(line[i+2 : j-1])
1416  			if constraintContainsPositive(group, word) {
1417  				return true
1418  			}
1419  		}
1420  	}
1421  	return false
1422  }
1423  
1424  func hasBuildConstraint(data []byte, forbidden []string) (ok bool) {
1425  	for i := int32(0); i < int32(len(data)) && i < 512; i++ {
1426  		if data[i] == 'p' {
1427  			return false
1428  		}
1429  		skip := int32(0)
1430  		if i+9 < int32(len(data)) && string(data[i:i+9]) == "//:build " {
1431  			skip = 9
1432  		}
1433  		if skip > 0 {
1434  			var buf []byte
1435  			for j := i + skip; j < int32(len(data)) && data[j] != '\n'; j++ {
1436  				push(buf, data[j])
1437  			}
1438  			line := string(buf)
1439  			if mxutil.TargetArch == "wasm" {
1440  				// Tags true for all wasm: wasm, moxie.wasm, go1.23, !linux, !baremetal
1441  				// js tag only true when mxutil.TargetOS == "js" (js/wasm target)
1442  				if constraintContainsPositive(line, "moxie.wasm") ||
1443  					constraintContainsPositive(line, "wasm") ||
1444  					constraintContainsPositive(line, "go1.23") {
1445  					// Only reject js requirement when it's conjunctive (js && wasm)
1446  					// not disjunctive (js || wasm)
1447  					if constraintContainsPositive(line, "js") && mxutil.TargetOS != "js" {
1448  						// Check if js and wasm are in a conjunction
1449  						isConjunction := false
1450  						for ci := int32(0); ci < int32(len(line))-1; ci++ {
1451  							if line[ci] == '&' && line[ci+1] == '&' {
1452  								isConjunction = true
1453  								break
1454  							}
1455  						}
1456  						if isConjunction {
1457  							return true
1458  						}
1459  					}
1460  					return false
1461  				}
1462  				if mxutil.TargetOS == "js" && constraintContainsPositive(line, "js") {
1463  					return false
1464  				}
1465  				if constraintContainsNegated(line, "js") ||
1466  					constraintContainsNegated(line, "wasm") ||
1467  					constraintContainsNegated(line, "moxie.wasm") ||
1468  					constraintContainsNegated(line, "go1.23") {
1469  					return true
1470  				}
1471  				// Architecture tags: amd64, arm64, 386 are false on wasm.
1472  				for _, arch := range []string{"amd64", "arm64", "386"} {
1473  					if constraintContainsPositive(line, arch) {
1474  						return true
1475  					}
1476  				}
1477  				// !linux is true for wasm - include files that need it
1478  				if constraintContainsNegated(line, "linux") {
1479  					return false
1480  				}
1481  				// linux-only constraints don't apply to wasm
1482  				if constraintContainsPositive(line, "linux") {
1483  					return true
1484  				}
1485  			}
1486  			for _, f := range forbidden {
1487  				if line == f || mxutil.HasPrefix(line, f | " ") || mxutil.HasPrefix(line, f | "\t") || mxutil.HasPrefix(line, f | "(") {
1488  					return true
1489  				}
1490  			}
1491  			return false
1492  		}
1493  		if data[i] == '\n' {
1494  			continue
1495  		}
1496  		if data[i] == '/' && i+1 < int32(len(data)) && data[i+1] == '/' {
1497  			for i < int32(len(data)) && data[i] != '\n' {
1498  				i++
1499  			}
1500  			continue
1501  		}
1502  	}
1503  	return false
1504  }
1505  
1506  func hasImport(data []byte, pkg string) (ok bool) {
1507  	target := "\"" | pkg | "\""
1508  	for i := int32(0); i <= int32(len(data))-int32(len(target)); i++ {
1509  		if string(data[i:i+int32(len(target))]) == target {
1510  			return true
1511  		}
1512  	}
1513  	return false
1514  }
1515  
1516  // quietDiscover suppresses per-file discovery chatter. Set by build-pkg
1517  // workers, which re-run discovery the orchestrator already printed.
1518  
1519  // buildJobs is the max number of concurrent child processes (workers and
1520  // clang) used by build/test. Set by the -j flag; 1 forces the serial path.
1521  
1522  // Packages baked into sysroot/wasm/runtime.bc (compiled by the old
1523  // TinyGo-based compiler). Stage4 type-checks these but does not compile
1524  // them - the prebuilt versions are used instead. This avoids ABI
1525  // mismatches between old-compiler and stage4-compiled code (different
1526  // context-pointer conventions).
1527  
1528  type pkgInfo struct {
1529  	path    string
1530  	name    string
1531  	dir     string
1532  	files   []string
1533  	cfiles  []string
1534  	bcfiles []string
1535  	imports []string
1536  	version string
1537  }
1538  
1539  func discoverPkg(pkgPath, root string) (p *pkgInfo) {
1540  	var dir string
1541  	if pkgPath == "." || pkgPath == ".." || mxutil.HasPrefix(pkgPath, "/") || mxutil.HasPrefix(pkgPath, "./") || mxutil.HasPrefix(pkgPath, "../") {
1542  		dir = pkgPath
1543  	} else if bst.globalReplaces != nil {
1544  		if rdir, ok := bst.globalReplaces[pkgPath]; ok {
1545  			dir = rdir
1546  		}
1547  		if dir == "" {
1548  			for from, to := range bst.globalReplaces {
1549  				if mxutil.HasPrefix(pkgPath, from|"/") {
1550  					rel := pkgPath[len(from)+1:]
1551  					dir = mxutil.JoinPath(to, rel)
1552  					break
1553  				}
1554  			}
1555  		}
1556  	}
1557  	if dir == "" && bst.globalModPrefix != "" && pkgPath == bst.globalModPrefix {
1558  		dir = bst.globalModDir
1559  	}
1560  	if dir == "" && bst.globalModPrefix != "" && mxutil.HasPrefix(pkgPath, bst.globalModPrefix|"/") {
1561  		rel := pkgPath[len(bst.globalModPrefix)+1:]
1562  		dir = mxutil.JoinPath(bst.globalModDir, rel)
1563  	}
1564  	if dir == "" {
1565  		candidate := mxutil.JoinPath(getMoxiePath(), pkgPath)
1566  		mxF := listFiles(candidate, ".mx")
1567  		if len(mxF) > 0 {
1568  			dir = candidate
1569  		}
1570  	}
1571  	if dir == "" && isRemotePath(pkgPath) {
1572  		ver := ""
1573  		if bst.globalModDir != "" {
1574  			_, reqs, _ := parseModRequires(bst.globalModDir)
1575  			for _, r := range reqs {
1576  				if r.path == pkgPath {
1577  					ver = r.version
1578  					break
1579  				}
1580  			}
1581  		}
1582  		fetched := autoFetchRepo(pkgPath, ver)
1583  		if fetched != "" {
1584  			mxF := listFiles(fetched, ".mx")
1585  			if len(mxF) > 0 {
1586  				dir = fetched
1587  			}
1588  		}
1589  	}
1590  	if dir != "" && dir != pkgPath {
1591  		mergeModReplaces(dir)
1592  	}
1593  	if dir == "" {
1594  		dir = mxutil.JoinPath(root, mxutil.JoinPath("src", pkgPath))
1595  		mxf := listFiles(dir, ".mx")
1596  		if len(mxf) == 0 {
1597  			dir = mxutil.JoinPath(root, mxutil.JoinPath("src/vendor", pkgPath))
1598  		}
1599  	}
1600  	mxFiles := listFiles(dir, ".mx")
1601  	bcFiles := listFiles(dir, ".bc")
1602  	allMx := mxFiles
1603  	var forbidden []string
1604  	if mxutil.TargetArch == "wasm" {
1605  		forbidden = []string{"none", "linux", "unix", "darwin", "amd64", "arm64",
1606  			"ignore", "compiler_bootstrap",
1607  			"scheduler.tasks", "scheduler.cores", "scheduler.asyncify",
1608  			"faketime", "baremetal", "nintendoswitch",
1609  			"boringcrypto", "cgo",
1610  			"runtime_memhash_leveldb", "runtime_memhash_tsip",
1611  			"runtime_asserts", "moxie.stringer"}
1612  	} else {
1613  		forbidden = []string{"none", "wasm", "js", "ignore", "compiler_bootstrap",
1614  			"scheduler.tasks", "scheduler.cores", "scheduler.asyncify",
1615  			"faketime", "baremetal", "nintendoswitch", "boringcrypto", "gc"}
1616  	}
1617  	cFilesAll := listFiles(dir, ".c")
1618  	cFiles := []string{:0:len(cFilesAll)}
1619  	for _, cf := range cFilesAll {
1620  		if mxutil.HasSuffix(cf, "_test.c") {
1621  			continue
1622  		}
1623  		if shouldSkipFile(cf) {
1624  			continue
1625  		}
1626  		cdata, cok := mxutil.ReadFile(mxutil.JoinPath(dir, cf))
1627  		if cok && hasBuildConstraint(cdata, forbidden) {
1628  			continue
1629  		}
1630  		push(cFiles, cf)
1631  	}
1632  	files := []string{:0:len(allMx)}
1633  	for _, f := range allMx {
1634  		if shouldSkipFile(f) {
1635  			continue
1636  		}
1637  		data, ok := mxutil.ReadFile(mxutil.JoinPath(dir, f))
1638  		if !ok {
1639  			continue
1640  		}
1641  		bc := hasBuildConstraint(data, forbidden)
1642  		if bc {
1643  			if !bst.quietDiscover {
1644  				mxutil.WriteStr(2, "    skip-constraint: " | f | "\n")
1645  			}
1646  			continue
1647  		}
1648  		fileImps := extractImports(data)
1649  		skipFile := ""
1650  		for _, imp := range fileImps {
1651  			if imp == "iter" || imp == "weak" || imp == "crypto/internal/fips140cache" {
1652  				skipFile = imp
1653  				break
1654  			}
1655  		}
1656  		if skipFile != "" {
1657  			if !bst.quietDiscover {
1658  				mxutil.WriteStr(2, "    skip-import(" | skipFile | "): " | f | "\n")
1659  			}
1660  			continue
1661  		}
1662  		if !bst.quietDiscover {
1663  			mxutil.WriteStr(2, "    include: " | f | "\n")
1664  		}
1665  		push(files, f)
1666  	}
1667  	if len(files) == 0 {
1668  		return nil
1669  	}
1670  	allImports := []string{:0:len(files) * 4}
1671  	pkgName := ""
1672  	for _, f := range files {
1673  		data, ok := mxutil.ReadFile(mxutil.JoinPath(dir, f))
1674  		if !ok {
1675  			continue
1676  		}
1677  		if pkgName == "" {
1678  			pkgName = extractPkgName(data)
1679  		}
1680  		for _, imp := range extractImports(data) {
1681  			allImports = appendUniq(allImports, imp)
1682  		}
1683  	}
1684  	if !bst.quietDiscover {
1685  		mxutil.WriteStr(2, "  discover " | pkgPath | " [")
1686  		for fi, ff := range files {
1687  			if fi > 0 {
1688  				mxutil.WriteStr(2, ", ")
1689  			}
1690  			mxutil.WriteStr(2, ff)
1691  		}
1692  		mxutil.WriteStr(2, "]\n")
1693  	}
1694  	version := ""
1695  	if isRemotePath(pkgPath) && bst.globalModDir != "" {
1696  		_, reqs, _ := parseModRequires(bst.globalModDir)
1697  		for _, r := range reqs {
1698  			if r.path == pkgPath || mxutil.HasPrefix(pkgPath, r.path|"/") {
1699  				version = r.version
1700  				break
1701  			}
1702  		}
1703  	}
1704  	return &pkgInfo{
1705  		path:    pkgPath,
1706  		name:    pkgName,
1707  		dir:     dir,
1708  		files:   files,
1709  		cfiles:  cFiles,
1710  		bcfiles: bcFiles,
1711  		imports: allImports,
1712  		version: version,
1713  	}
1714  }
1715  
1716  type resolver struct {
1717  	visited map[string]bool
1718  	order   []*pkgInfo
1719  	root    string
1720  }
1721  
1722  func (r *resolver) walk(path string) {
1723  	if r.visited[path] {
1724  		return
1725  	}
1726  	r.visited[path] = true
1727  	if isBuiltinOnly(path) {
1728  		return
1729  	}
1730  	pkg := discoverPkg(path, r.root)
1731  	if pkg == nil {
1732  		return
1733  	}
1734  	for _, imp := range pkg.imports {
1735  		r.walk(imp)
1736  	}
1737  	push(r.order, pkg)
1738  }
1739  
1740  func resolveAll(rootPkg string, root string) (ss []*pkgInfo) {
1741  	r := &resolver{
1742  		visited: map[string]bool{},
1743  		order:   []*pkgInfo{:0:64},
1744  		root:    root,
1745  	}
1746  	r.walk(rootPkg)
1747  	return r.order
1748  }
1749  
1750  func registerBuiltins() {
1751  	for k := range cctx.universe.Registry {
1752  		delete(cctx.universe.Registry, k)
1753  	}
1754  
1755  	// unsafe - compiler builtin, no source
1756  	unsafePkg := NewTCPackage("unsafe", "unsafe")
1757  	unsafePkg.Scope.Insert(NewTypeName(unsafePkg, "Pointer", Typ[UnsafePointer]))
1758  	unsafePkg.Scope.Insert(NewTCFunc(unsafePkg, "Sizeof", parseSignatureDesc("interface{}->int32")))
1759  	unsafePkg.Scope.Insert(NewTCFunc(unsafePkg, "Offsetof", parseSignatureDesc("interface{}->int32")))
1760  	unsafePkg.Scope.Insert(NewTCFunc(unsafePkg, "Alignof", parseSignatureDesc("interface{}->int32")))
1761  	unsafePkg.Scope.Insert(NewTCFunc(unsafePkg, "Slice", parseSignatureDesc("ptr,int32->[]uint8")))
1762  	unsafePkg.Scope.Insert(NewTCFunc(unsafePkg, "SliceData", parseSignatureDesc("[]uint8->ptr")))
1763  	cctx.universe.Registry["unsafe"] = unsafePkg
1764  
1765  	// moxie - spawn/codec runtime, types and constants from protocol header
1766  	mroot := getenv("MOXIEROOT")
1767  	if mroot == "" {
1768  		mroot = "."
1769  	}
1770  	if !loadMxh(mxutil.JoinPath(mxutil.JoinPath(mxutil.JoinPath(mroot, "src"), "moxie"), "moxie.mxh")) {
1771  		fatal("cannot load src/moxie/moxie.mxh (MOXIEROOT=" | mroot | ")")
1772  	}
1773  
1774  	// runtime - assembly + C, pre-compiled in runtime.bc
1775  	rtPkg := NewTCPackage("runtime", "runtime")
1776  	rtPkg.Scope.Insert(NewTCFunc(rtPkg, "InitCShared", parseSignatureDesc("")))
1777  	rtPkg.Scope.Insert(NewTCFunc(rtPkg, "LastSpawnedParentFd", parseSignatureDesc("->int32")))
1778  	rtPkg.Scope.Insert(NewTCFunc(rtPkg, "PipeClosed", parseSignatureDesc("int32->bool")))
1779  	rtPkg.Scope.Insert(NewTCFunc(rtPkg, "PipeFDCanSend", parseSignatureDesc("int32->bool")))
1780  	rtPkg.Scope.Insert(NewTCFunc(rtPkg, "LockOSThread", parseSignatureDesc("")))
1781  	rtPkg.Scope.Insert(NewTCFunc(rtPkg, "UnlockOSThread", parseSignatureDesc("")))
1782  	rtPkg.Scope.Insert(NewTCFunc(rtPkg, "KeepAlive", parseSignatureDesc("interface{}")))
1783  	rtPkg.Scope.Insert(NewTCFunc(rtPkg, "GOROOT", parseSignatureDesc("->string")))
1784  	rtPkg.Scope.Insert(NewTCFunc(rtPkg, "Gosched", parseSignatureDesc("")))
1785  	rtPkg.Scope.Insert(NewTCFunc(rtPkg, "Version", parseSignatureDesc("->string")))
1786  	rtPkg.Scope.Insert(NewTCVar(rtPkg, "GOOS", Typ[TCString]))
1787  	rtPkg.Scope.Insert(NewTCVar(rtPkg, "GOARCH", Typ[TCString]))
1788  	rtPkg.Scope.Insert(NewTCVar(rtPkg, "ChildPipeFd", Typ[Int32]))
1789  	cctx.universe.Registry["runtime"] = rtPkg
1790  }
1791  
1792  func addPtrMethod(pkg *TCPackage, named *Named, name, sigDesc string) {
1793  	var sig *Signature
1794  	if sigDesc == "" {
1795  		sig = NewSignature(nil, nil, nil, false)
1796  	} else {
1797  		sig = parseSignatureDesc(sigDesc)
1798  	}
1799  	recvType := NewPointer(named)
1800  	sig = NewSignature(NewTCVar(pkg, "", recvType), sig.Params, sig.Results, sig.Variadic)
1801  	fn := NewTCFunc(pkg, name, sig)
1802  	fn.PtrRecv = true
1803  	named.AddMethod(fn)
1804  }
1805  
1806  func remapErrorLines(msg string) (s string) {
1807  	if len(cctx.concatSourceMap) == 0 {
1808  		return msg
1809  	}
1810  	lines := mxutil.SplitLines(msg)
1811  	var out []string
1812  	for _, line := range lines {
1813  		push(out, remapOneLine(line))
1814  	}
1815  	return joinStrings(out, "\n")
1816  }
1817  
1818  func remapOneLine(line string) (s string) {
1819  	if len(cctx.concatSourceMap) == 0 {
1820  		return line
1821  	}
1822  	colonCount := int32(0)
1823  	firstColon := int32(-1)
1824  	secondColon := int32(-1)
1825  	for i := int32(0); i < int32(len(line)); i++ {
1826  		if line[i] == ':' {
1827  			colonCount++
1828  			if colonCount == 1 {
1829  				firstColon = i
1830  			} else if colonCount == 2 {
1831  				secondColon = i
1832  				break
1833  			}
1834  		}
1835  	}
1836  	if secondColon < 0 {
1837  		return line
1838  	}
1839  	numStr := line[firstColon+1 : secondColon]
1840  	n := int32(0)
1841  	valid := len(numStr) > 0
1842  	for i := int32(0); i < int32(len(numStr)); i++ {
1843  		if numStr[i] < '0' || numStr[i] > '9' {
1844  			valid = false
1845  			break
1846  		}
1847  		n = n*10 + int32(numStr[i]-'0')
1848  	}
1849  	if !valid {
1850  		return line
1851  	}
1852  	file, srcLine := concatLineToFile(n)
1853  	if file == "" {
1854  		return line
1855  	}
1856  	return file | ":" | simpleItoa(srcLine) | line[secondColon:]
1857  }
1858  
1859  func compileSource(src []byte, pkgName, triple, pkgDir string) (s string) {
1860  	cctx.compilePkgDir = pkgDir
1861  	ir := CompileToIR(src, pkgName, triple)
1862  	if len(ir) > 0 && ir[0] == ';' {
1863  		mxutil.WriteStr(2, remapErrorLines(ir))
1864  		fatal("compile error for " | pkgName)
1865  	}
1866  	// When streaming (irOutputFile set), IR is written to file directly.
1867  	// Empty ir string is expected - the file has the content.
1868  	if len(ir) == 0 && cctx.irOutputFile == "" {
1869  		fatal("empty IR for " | pkgName)
1870  	}
1871  	return ir
1872  }
1873  
1874  
1875  // mxhBuf accumulates mxh output. Pre-sized buffer with offset write.
1876  // No growth, no abandoned arrays under arena allocation.
1877  type mxhBuf struct {
1878  	data []byte
1879  	off  int32
1880  }
1881  
1882  func (b *mxhBuf) w(s string) {
1883  	need := b.off + len(s)
1884  	if need > len(b.data) {
1885  		newCap := int32(len(b.data)) * 2
1886  		if newCap < need {
1887  			newCap = need + 4096
1888  		}
1889  		nd := []byte{:newCap}
1890  		for i := int32(0); i < b.off; i++ {
1891  			nd[i] = b.data[i]
1892  		}
1893  		b.data = nd
1894  	}
1895  	for j := int32(0); j < len(s); j++ {
1896  		b.data[b.off+j] = s[j]
1897  	}
1898  	b.off += len(s)
1899  }
1900  
1901  func (b *mxhBuf) nl() {
1902  	if b.off >= len(b.data) {
1903  		push(b.data, '\n')
1904  		b.off++
1905  		return
1906  	}
1907  	b.data[b.off] = '\n'
1908  	b.off++
1909  }
1910  
1911  // typeDescStr returns the type descriptor for simple/leaf types.
1912  // For compound types that recurse, use typeDescWrite.
1913  func typeDescStr(t Type) (s string) {
1914  	var b mxhBuf
1915  	b.data = []byte{:256}
1916  	typeDescWrite(&b, t)
1917  	return string(b.data[:b.off])
1918  }
1919  
1920  func typeDescWrite(b *mxhBuf, t Type) {
1921  	if t == nil {
1922  		b.w("interface{}")
1923  		return
1924  	}
1925  	switch v := t.(type) {
1926  	case *Basic:
1927  		switch v.Kind {
1928  		case Bool:
1929  			b.w("bool")
1930  		case Int8:
1931  			b.w("int8")
1932  		case Int16:
1933  			b.w("int16")
1934  		case Int32:
1935  			b.w("int32")
1936  		case Int64:
1937  			b.w("int64")
1938  		case Uint8:
1939  			b.w("uint8")
1940  		case Uint16:
1941  			b.w("uint16")
1942  		case Uint32:
1943  			b.w("uint32")
1944  		case Uint64:
1945  			b.w("uint64")
1946  		case Float32:
1947  			b.w("float32")
1948  		case Float64:
1949  			b.w("float64")
1950  		case TCString:
1951  			b.w("string")
1952  		case UnsafePointer:
1953  			b.w("ptr")
1954  		default:
1955  			b.w("int32")
1956  		}
1957  	case *Slice:
1958  		b.w("[]")
1959  		typeDescWrite(b, v.Elem)
1960  	case *Array:
1961  		b.w("[")
1962  		b.w(simpleItoa64(v.Len))
1963  		b.w("]")
1964  		typeDescWrite(b, v.Elem)
1965  	case *Pointer:
1966  		b.w("*")
1967  		typeDescWrite(b, v.Base)
1968  	case *TCStruct:
1969  		b.w("struct{")
1970  		for i := int32(0); i < v.NumFields(); i++ {
1971  			if i > 0 {
1972  				b.w(",")
1973  			}
1974  			f := v.Field(i)
1975  			if f.Anonymous {
1976  				b.w("@")
1977  			}
1978  			b.w(f.Name)
1979  			b.w(":")
1980  			typeDescWrite(b, f.Typ)
1981  		}
1982  		b.w("}")
1983  	case *TCInterface:
1984  		if v.IsEmpty() {
1985  			b.w("interface{}")
1986  			return
1987  		}
1988  		hasError := false
1989  		for mi := int32(0); mi < v.NumMethods(); mi++ {
1990  			if v.Method(mi).Name == "Error" {
1991  				hasError = true
1992  				break
1993  			}
1994  		}
1995  		if hasError {
1996  			b.w("error")
1997  		} else {
1998  			b.w("interface{}")
1999  		}
2000  	case *TCChan:
2001  		switch v.Dir {
2002  		case TCSendOnly:
2003  			b.w("chan<-:")
2004  		case TCRecvOnly:
2005  			b.w("<-chan:")
2006  		default:
2007  			b.w("chan:")
2008  		}
2009  		typeDescWrite(b, v.Elem)
2010  	case *TCMap:
2011  		b.w("map[")
2012  		typeDescWrite(b, v.Key)
2013  		b.w("]")
2014  		typeDescWrite(b, v.Elem)
2015  	case *Signature:
2016  		b.w("func(")
2017  		sigDescWrite(b, v)
2018  		b.w(")")
2019  	case *TypeParam:
2020  		b.w("$")
2021  		b.w(v.String())
2022  	case *Named:
2023  		if v.Obj != nil {
2024  			if cctx.typeDescPkgPath != "" && v.Obj.Pkg != nil && v.Obj.Pkg.Path != cctx.typeDescPkgPath {
2025  				b.w(v.Obj.Pkg.Path)
2026  				b.w(".")
2027  			}
2028  			b.w(v.Obj.Name)
2029  			return
2030  		}
2031  		typeDescWrite(b, v.Under)
2032  	default:
2033  		b.w("interface{}")
2034  	}
2035  }
2036  
2037  func sigDescWrite(b *mxhBuf, sig *Signature) {
2038  	if sig.Params != nil {
2039  		for i := int32(0); i < sig.Params.Len(); i++ {
2040  			if i > 0 {
2041  				b.w(",")
2042  			}
2043  			pt := sig.Params.At(i).Typ
2044  			if sig.Variadic && i == sig.Params.Len()-1 {
2045  				if sl, ok := pt.(*Slice); ok {
2046  					b.w("...")
2047  					typeDescWrite(b, sl.Elem)
2048  					continue
2049  				}
2050  			}
2051  			typeDescWrite(b, pt)
2052  		}
2053  	}
2054  	if sig.Results != nil && sig.Results.Len() > 0 {
2055  		b.w("->")
2056  		for i := int32(0); i < sig.Results.Len(); i++ {
2057  			if i > 0 {
2058  				b.w(",")
2059  			}
2060  			typeDescWrite(b, sig.Results.At(i).Typ)
2061  		}
2062  	}
2063  }
2064  
2065  // sigDescStr kept for callers that need a string.
2066  func sigDescStr(sig *Signature) (s string) {
2067  	var b mxhBuf
2068  	b.data = []byte{:256}
2069  	sigDescWrite(&b, sig)
2070  	return string(b.data[:b.off])
2071  }
2072  
2073  func generateMxh(pkg *TCPackage) (s string) {
2074  	path := pkg.Path
2075  	name := pkg.Name
2076  	cctx.typeDescPkgPath = path
2077  	var b mxhBuf
2078  	b.data = []byte{:int32(len(pkg.Scope.Names())) * 80 + 4096}
2079  	b.w("package ") ; b.w(path) ; b.w(" ") ; b.w(name) ; b.nl()
2080  	for _, ip := range cctx.universe.DirectImportPaths(pkg) {
2081  		b.w("import ") ; b.w(ip) ; b.nl()
2082  	}
2083  	for _, objName := range pkg.Scope.Names() {
2084  		if len(objName) == 0 {
2085  			continue
2086  		}
2087  		exported := objName[0] >= 'A' && objName[0] <= 'Z'
2088  		obj := pkg.Scope.Lookup(objName)
2089  		if obj == nil {
2090  			continue
2091  		}
2092  		if !exported {
2093  			if tn, ok := obj.(*TypeName); ok {
2094  				if named, ok2 := tn.Typ.(*Named); ok2 {
2095  					_, isStruct := named.Under.(*TCStruct)
2096  					if named.NumMethods() == 0 && !isStruct {
2097  						continue
2098  					}
2099  				} else {
2100  					continue
2101  				}
2102  			} else {
2103  				continue
2104  			}
2105  		}
2106  		if fn, ok := obj.(*TCFunc); ok {
2107  			sig := fn.Signature()
2108  			if sig != nil {
2109  				b.w("func ") ; b.w(objName) ; b.w(" ")
2110  				sigDescWrite(&b, sig) ; b.nl()
2111  			}
2112  		} else if vr, ok2 := obj.(*TCVar); ok2 {
2113  			if vr == nil || vr.Typ == nil {
2114  				continue
2115  			}
2116  			b.w("var ") ; b.w(objName) ; b.w(" ")
2117  			typeDescWrite(&b, vr.Typ) ; b.nl()
2118  		} else if cn, ok3 := obj.(*TCConst); ok3 {
2119  			if cn == nil || cn.Typ == nil {
2120  				continue
2121  			}
2122  			b.w("const ") ; b.w(objName) ; b.w(" ")
2123  			typeDescWrite(&b, cn.Typ) ; b.w(" ")
2124  			if cn.Val != nil {
2125  				b.w(cn.Val.String())
2126  			}
2127  			b.nl()
2128  		} else if tn2, ok4 := obj.(*TypeName); ok4 {
2129  			mxhWriteTypeName(&b, objName, tn2)
2130  		}
2131  	}
2132  	return string(b.data[:b.off])
2133  }
2134  
2135  func mxhWriteTypeName(b *mxhBuf, objName string, v *TypeName) {
2136  	if v == nil {
2137  		return
2138  	}
2139  	typ := v.Typ
2140  	if named, ok := typ.(*Named); ok {
2141  		if ui, ok2 := named.Under.(*TCInterface); ok2 && !ui.IsEmpty() {
2142  			complete := true
2143  			for mi := int32(0); mi < ui.NumMethods(); mi++ {
2144  				if ui.Method(mi).Sig == nil {
2145  					complete = false
2146  					break
2147  				}
2148  			}
2149  			if complete {
2150  				b.w("iface ") ; b.w(objName)
2151  				mxhWriteTParams(b, named.TParams)
2152  				b.w(" ")
2153  				mxhWriteIfaceMethods(b, ui)
2154  				b.nl()
2155  			}
2156  		} else {
2157  			b.w("type ") ; b.w(objName)
2158  			mxhWriteTParams(b, named.TParams)
2159  			b.w(" ")
2160  			if named.Under != nil {
2161  				typeDescWrite(b, named.Under)
2162  			}
2163  			b.nl()
2164  			if len(named.TParams) == 0 {
2165  				for _, m := range named.Methods {
2166  					msig := m.Signature()
2167  					if msig != nil {
2168  						ptrRecv := "0"
2169  						if m.PtrRecv {
2170  							ptrRecv = "1"
2171  						}
2172  						b.w("method ") ; b.w(objName) ; b.w(" ")
2173  						b.w(m.Name) ; b.w(" ")
2174  						sigDescWrite(b, msig) ; b.w(" ")
2175  						b.w(ptrRecv) ; b.nl()
2176  					}
2177  				}
2178  			}
2179  		}
2180  	} else if iface, ok3 := typ.(*TCInterface); ok3 {
2181  		complete := true
2182  		for mi := int32(0); mi < iface.NumMethods(); mi++ {
2183  			if iface.Method(mi).Sig == nil {
2184  				complete = false
2185  				break
2186  			}
2187  		}
2188  		if complete {
2189  			b.w("iface ") ; b.w(objName) ; b.w(" ")
2190  			mxhWriteIfaceMethods(b, iface)
2191  			b.nl()
2192  		}
2193  	}
2194  }
2195  
2196  func mxhWriteTParams(b *mxhBuf, tparams []*TypeParam) {
2197  	if len(tparams) == 0 {
2198  		return
2199  	}
2200  	b.w("[")
2201  	for i, tp := range tparams {
2202  		if i > 0 {
2203  			b.w(",")
2204  		}
2205  		b.w("$")
2206  		b.w(tp.String())
2207  	}
2208  	b.w("]")
2209  }
2210  
2211  func mxhWriteIfaceMethods(b *mxhBuf, ui *TCInterface) {
2212  	for mi := int32(0); mi < ui.NumMethods(); mi++ {
2213  		m := ui.Method(mi)
2214  		if mi > 0 {
2215  			b.w(";")
2216  		}
2217  		b.w(m.Name)
2218  		b.w("=")
2219  		sigDescWrite(b, m.Sig)
2220  	}
2221  }
2222  
2223  // parseMxhTParams splits "Curve[$P,$Q]" into ("Curve", [TypeParam P, TypeParam Q]).
2224  // For plain names like "Foo" it returns ("Foo", nil).
2225  func parseMxhTParams(raw string) (name string, tparams []*TypeParam) {
2226  	bracketIdx := int32(-1)
2227  	for i := int32(0); i < int32(len(raw)); i++ {
2228  		if raw[i] == '[' {
2229  			bracketIdx = i
2230  			break
2231  		}
2232  	}
2233  	if bracketIdx < 0 {
2234  		return raw, nil
2235  	}
2236  	name = raw[:bracketIdx]
2237  	inner := raw[bracketIdx+1 : int32(len(raw))-1]
2238  	parts := splitCommaBalanced(inner)
2239  	for _, p := range parts {
2240  		if len(p) > 1 && p[0] == '$' {
2241  			tpn := p[1:]
2242  			push(tparams, NewTypeParam(NewTypeName(nil, tpn, nil), nil))
2243  		}
2244  	}
2245  	return name, tparams
2246  }
2247  
2248  func loadMxh(path string) (ok bool) {
2249  	data, ok := mxutil.ReadFile(path)
2250  	if !ok {
2251  		return false
2252  	}
2253  	return loadMxhData(data)
2254  }
2255  
2256  // mxh index-walking primitives. All return index pairs into the original
2257  // buffer d. No intermediate string/slice allocations.
2258  
2259  // mxhNextLine returns the start and end of the next line at or after pos,
2260  // and the position after the newline. end == start when no content remains.
2261  func mxhNextLine(d string, pos int32) (ls int32, le int32, next int32) {
2262  	ls = pos
2263  	i := pos
2264  	for i < int32(len(d)) && d[i] != '\n' {
2265  		i++
2266  	}
2267  	le = i
2268  	next = i
2269  	if next < int32(len(d)) {
2270  		next++
2271  	}
2272  	return ls, le, next
2273  }
2274  
2275  // mxhNextWord returns the start and end of the next whitespace-delimited
2276  // word within d[from:limit]. Returns (limit, limit) when no word found.
2277  func mxhNextWord(d string, from, limit int32) (ws, we int32) {
2278  	i := from
2279  	for i < limit && (d[i] == ' ' || d[i] == '\t') {
2280  		i++
2281  	}
2282  	ws = i
2283  	for i < limit && d[i] != ' ' && d[i] != '\t' {
2284  		i++
2285  	}
2286  	we = i
2287  	return ws, we
2288  }
2289  
2290  // mxhHasPrefix checks whether d[start:end] begins with prefix.
2291  func mxhHasPrefix(d string, start, end int32, prefix string) (ok bool) {
2292  	plen := int32(len(prefix))
2293  	if end-start < plen {
2294  		return false
2295  	}
2296  	for i := int32(0); i < plen; i++ {
2297  		if d[start+i] != prefix[i] {
2298  			return false
2299  		}
2300  	}
2301  	return true
2302  }
2303  
2304  // mxhEq checks whether d[start:end] equals s.
2305  func mxhEq(d string, start, end int32, s string) (eq bool) {
2306  	if end-start != int32(len(s)) {
2307  		return false
2308  	}
2309  	for i := int32(0); i < end-start; i++ {
2310  		if d[start+i] != s[i] {
2311  			return false
2312  		}
2313  	}
2314  	return true
2315  }
2316  
2317  // mxhParseIfaceMethods parses "name1=sig1;name2=sig2" from d[start:end]
2318  // without allocating a []string for the semicolon split.
2319  func mxhParseIfaceMethods(d string, start, end int32) (methods []*IfaceMethod) {
2320  	if start >= end {
2321  		return nil
2322  	}
2323  	// Count methods (semicolon-separated).
2324  	n := int32(1)
2325  	for i := start; i < end; i++ {
2326  		if d[i] == ';' {
2327  			n++
2328  		}
2329  	}
2330  	methods = []*IfaceMethod{:0:n}
2331  	seg := start
2332  	for i := start; i <= end; i++ {
2333  		if i == end || d[i] == ';' {
2334  			eqIdx := int32(-1)
2335  			for k := seg; k < i; k++ {
2336  				if d[k] == '=' {
2337  					eqIdx = k
2338  					break
2339  				}
2340  			}
2341  			if eqIdx > seg {
2342  				mname := string(d[seg:eqIdx])
2343  				msig := parseSignatureDesc(string(d[eqIdx+1 : i]))
2344  				push(methods, NewTCIfaceMethod(mname, msig))
2345  			}
2346  			seg = i + 1
2347  		}
2348  	}
2349  	return methods
2350  }
2351  
2352  // mxhCountWords counts space-delimited words in d[from:limit].
2353  func mxhCountWords(d string, from, limit int32) (n int32) {
2354  	i := from
2355  	for i < limit {
2356  		for i < limit && (d[i] == ' ' || d[i] == '\t') {
2357  			i++
2358  		}
2359  		if i >= limit {
2360  			break
2361  		}
2362  		n++
2363  		for i < limit && d[i] != ' ' && d[i] != '\t' {
2364  			i++
2365  		}
2366  	}
2367  	return n
2368  }
2369  
2370  func loadMxhData(data []byte) (ok bool) {
2371  	// Types created here must survive on the root arena - they're stored
2372  	// in cctx.universe.Registry which outlives any compile/typecheck arena.
2373  	mxhSavedArena := runtime.CurrentArena()
2374  	runtime.SetCurrentArena(runtime.RootArena())
2375  
2376  	d := string(data)
2377  	dlen := int32(len(d))
2378  	if dlen == 0 {
2379  		runtime.SetCurrentArena(mxhSavedArena)
2380  		return false
2381  	}
2382  
2383  	// Parse header: "package <path> <name>"
2384  	ls, le, pos := mxhNextLine(d, 0)
2385  	if !mxhHasPrefix(d, ls, le, "package ") {
2386  		return false
2387  	}
2388  	ws, we := mxhNextWord(d, ls+8, le)
2389  	if ws == we {
2390  		return false
2391  	}
2392  	pkgPath := string(d[ws:we])
2393  	ns, ne := mxhNextWord(d, we, le)
2394  	if ns == ne {
2395  		return false
2396  	}
2397  	pkgName := string(d[ns:ne])
2398  	pkg := NewTCPackage(pkgPath, pkgName)
2399  
2400  	// Collect direct imports.
2401  	var dimps []string
2402  	scanPos := pos
2403  	for scanPos < dlen {
2404  		ls2, le2, next2 := mxhNextLine(d, scanPos)
2405  		if mxhHasPrefix(d, ls2, le2, "import ") {
2406  			push(dimps, string(d[ls2+7:le2]))
2407  		}
2408  		scanPos = next2
2409  	}
2410  	cctx.universe.DirectImports[pkgPath] = dimps
2411  
2412  	oldScope := cctx.parseTypePkgScope
2413  	cctx.parseTypePkgScope = pkg.Scope
2414  
2415  	// Pass 1a: register all type and interface names with placeholder types.
2416  	scanPos = pos
2417  	for scanPos < dlen {
2418  		ls2, le2, next2 := mxhNextLine(d, scanPos)
2419  		scanPos = next2
2420  		if mxhHasPrefix(d, ls2, le2, "type ") {
2421  			ws2, we2 := mxhNextWord(d, ls2+5, le2)
2422  			if ws2 < we2 {
2423  				actualName, tparams := parseMxhTParams(string(d[ws2:we2]))
2424  				tn := NewTypeName(pkg, actualName, nil)
2425  				named := NewNamed(tn, nil)
2426  				named.TParams = tparams
2427  				pkg.Scope.Insert(tn)
2428  			}
2429  		} else if mxhHasPrefix(d, ls2, le2, "iface ") {
2430  			ws2, we2 := mxhNextWord(d, ls2+6, le2)
2431  			if ws2 < we2 {
2432  				actualName, tparams := parseMxhTParams(string(d[ws2:we2]))
2433  				placeholder := NewTCInterface(nil, nil)
2434  				placeholder.Complete()
2435  				tn := NewTypeName(pkg, actualName, nil)
2436  				named := NewNamed(tn, placeholder)
2437  				named.TParams = tparams
2438  				pkg.Scope.Insert(tn)
2439  			}
2440  		}
2441  	}
2442  
2443  	// Pass 1b: resolve underlying types and interface methods.
2444  	scanPos = pos
2445  	for scanPos < dlen {
2446  		ls2, le2, next2 := mxhNextLine(d, scanPos)
2447  		scanPos = next2
2448  		if mxhHasPrefix(d, ls2, le2, "type ") {
2449  			ws2, we2 := mxhNextWord(d, ls2+5, le2)
2450  			if ws2 >= we2 {
2451  				continue
2452  			}
2453  			ts, te := mxhNextWord(d, we2, le2)
2454  			actualName, _ := parseMxhTParams(string(d[ws2:we2]))
2455  			obj := pkg.Scope.Lookup(actualName)
2456  			if obj == nil {
2457  				continue
2458  			}
2459  			tn, istn := obj.(*TypeName)
2460  			if !istn {
2461  				continue
2462  			}
2463  			named, isn := tn.Typ.(*Named)
2464  			if !isn {
2465  				continue
2466  			}
2467  			if ts >= te {
2468  				named.Under = NewTCStruct(nil, nil)
2469  			} else {
2470  				named.Under = parseTypeDesc(string(d[ts:te]))
2471  			}
2472  		} else if mxhHasPrefix(d, ls2, le2, "iface ") {
2473  			ws2, we2 := mxhNextWord(d, ls2+6, le2)
2474  			if ws2 >= we2 {
2475  				continue
2476  			}
2477  			ifaceName, _ := parseMxhTParams(string(d[ws2:we2]))
2478  			// Methods descriptor is the second word (may contain semicolons).
2479  			ms, me := mxhNextWord(d, we2, le2)
2480  			methods := mxhParseIfaceMethods(d, ms, me)
2481  			iface := NewTCInterface(methods, nil)
2482  			iface.Complete()
2483  			obj := pkg.Scope.Lookup(ifaceName)
2484  			if obj == nil {
2485  				continue
2486  			}
2487  			if tn, istn := obj.(*TypeName); istn {
2488  				if named, isn := tn.Typ.(*Named); isn {
2489  					named.Under = iface
2490  				} else {
2491  					tn.Typ = iface
2492  				}
2493  			}
2494  		}
2495  	}
2496  
2497  	// Pass 2: funcs, methods, vars, consts.
2498  	scanPos = pos
2499  	for scanPos < dlen {
2500  		ls2, le2, next2 := mxhNextLine(d, scanPos)
2501  		scanPos = next2
2502  		if mxhHasPrefix(d, ls2, le2, "func ") {
2503  			ws2, we2 := mxhNextWord(d, ls2+5, le2)
2504  			if ws2 >= we2 {
2505  				continue
2506  			}
2507  			fname := string(d[ws2:we2])
2508  			ss, se := mxhNextWord(d, we2, le2)
2509  			if ss < se {
2510  				sig := parseSignatureDesc(string(d[ss:se]))
2511  				pkg.Scope.Insert(NewTCFunc(pkg, fname, sig))
2512  			} else {
2513  				sig := parseSignatureDesc("")
2514  				pkg.Scope.Insert(NewTCFunc(pkg, fname, sig))
2515  			}
2516  		} else if mxhHasPrefix(d, ls2, le2, "var ") {
2517  			ws2, we2 := mxhNextWord(d, ls2+4, le2)
2518  			if ws2 >= we2 {
2519  				continue
2520  			}
2521  			vname := string(d[ws2:we2])
2522  			ts, te := mxhNextWord(d, we2, le2)
2523  			if ts < te {
2524  				typ := parseTypeDesc(string(d[ts:te]))
2525  				pkg.Scope.Insert(NewTCVar(pkg, vname, typ))
2526  			}
2527  		} else if mxhHasPrefix(d, ls2, le2, "const ") {
2528  			ws2, we2 := mxhNextWord(d, ls2+6, le2)
2529  			if ws2 >= we2 {
2530  				continue
2531  			}
2532  			cname := string(d[ws2:we2])
2533  			ts, te := mxhNextWord(d, we2, le2)
2534  			if ts >= te {
2535  				continue
2536  			}
2537  			typ := parseTypeDesc(string(d[ts:te]))
2538  			// Remaining words after type are the const value.
2539  			vs, ve := mxhNextWord(d, te, le2)
2540  			var val ConstVal
2541  			if vs < ve {
2542  				// Reassemble value from remaining words. Const values
2543  				// may contain spaces (string literals). Walk remaining
2544  				// words and join with space - but use index range to
2545  				// avoid per-word | concat.
2546  				valStart := vs
2547  				valEnd := le2
2548  				// trim trailing whitespace
2549  				for valEnd > valStart && (d[valEnd-1] == ' ' || d[valEnd-1] == '\t') {
2550  					valEnd--
2551  				}
2552  				cval := string(d[valStart:valEnd])
2553  				isNum := true
2554  				for ci := int32(0); ci < int32(len(cval)); ci++ {
2555  					ch := cval[ci]
2556  					if !((ch >= '0' && ch <= '9') || ch == '-' || ch == '+' || ch == 'x' || ch == 'X' || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F') || ch == ' ') {
2557  						isNum = false
2558  						break
2559  					}
2560  				}
2561  				if isNum {
2562  					n := ssaParseInt64(cval)
2563  					val = &ConstInt{V: n}
2564  				} else {
2565  					if b, isb := typ.(*Basic); isb && (b.Kind == TCString || b.Kind == UntypedString) {
2566  						val = &ConstStr{S: cval}
2567  					} else {
2568  						val = &ConstInt{V: 0}
2569  					}
2570  				}
2571  			} else {
2572  				val = &ConstInt{V: 0}
2573  			}
2574  			pkg.Scope.Insert(NewTCConst(pkg, cname, typ, val))
2575  		} else if mxhHasPrefix(d, ls2, le2, "method ") {
2576  			nwords := mxhCountWords(d, ls2+7, le2)
2577  			if nwords < 3 {
2578  				continue
2579  			}
2580  			w1s, w1e := mxhNextWord(d, ls2+7, le2)
2581  			w2s, w2e := mxhNextWord(d, w1e, le2)
2582  			w3s, w3e := mxhNextWord(d, w2e, le2)
2583  			typeName := string(d[w1s:w1e])
2584  			methodName := string(d[w2s:w2e])
2585  			sigDesc := string(d[w3s:w3e])
2586  			ptrRecv := false
2587  			if nwords >= 4 {
2588  				w4s, w4e := mxhNextWord(d, w3e, le2)
2589  				if mxhEq(d, w4s, w4e, "1") {
2590  					ptrRecv = true
2591  				}
2592  			}
2593  			if nwords == 3 && (sigDesc == "0" || sigDesc == "1") {
2594  				ptrRecv = sigDesc == "1"
2595  				sigDesc = ""
2596  			}
2597  			obj := pkg.Scope.Lookup(typeName)
2598  			if obj == nil {
2599  				continue
2600  			}
2601  			tn, istn := obj.(*TypeName)
2602  			if !istn {
2603  				continue
2604  			}
2605  			named, isn := tn.Typ.(*Named)
2606  			if !isn {
2607  				continue
2608  			}
2609  			sig := parseSignatureDesc(sigDesc)
2610  			var recvType Type = named
2611  			if ptrRecv {
2612  				recvType = NewPointer(named)
2613  			}
2614  			sig = NewSignature(NewTCVar(pkg, "", recvType), sig.Params, sig.Results, sig.Variadic)
2615  			fn := NewTCFunc(pkg, methodName, sig)
2616  			fn.PtrRecv = ptrRecv
2617  			named.AddMethod(fn)
2618  		}
2619  	}
2620  
2621  	cctx.parseTypePkgScope = oldScope
2622  	cctx.universe.Registry[pkgPath] = pkg
2623  	runtime.SetCurrentArena(mxhSavedArena)
2624  	return true
2625  }
2626  
2627  func cachedMxhPath(cacheBase, pkgPath, hash, version string) (s string) {
2628  	return mxutil.JoinPath(mxutil.JoinPath(cacheBase, safeName(pkgPath)), versionedCacheName(hash, version, ".mxh"))
2629  }
2630  
2631  func removeAll(path string) {
2632  	entries := listFiles(path, "")
2633  	if entries == nil {
2634  		return
2635  	}
2636  	for _, e := range entries {
2637  		os.Remove(mxutil.JoinPath(path, e))
2638  	}
2639  	os.Remove(path)
2640  }
2641  
2642  func walkStdlibPkgs(srcDir, prefix string, out []string) (result []string) {
2643  	entries := listFiles(srcDir, "")
2644  	if entries == nil {
2645  		return out
2646  	}
2647  	mxFiles := listFiles(srcDir, ".mx")
2648  	if len(mxFiles) > 0 && prefix != "" {
2649  		push(out, prefix)
2650  	}
2651  	for _, e := range entries {
2652  		if e[0] == '.' || e[0] == '_' {
2653  			continue
2654  		}
2655  		sub := mxutil.JoinPath(srcDir, e)
2656  		subEntries := listFiles(sub, "")
2657  		if subEntries != nil {
2658  			var subPrefix string
2659  			if prefix == "" {
2660  				subPrefix = e
2661  			} else {
2662  				subPrefix = prefix | "/" | e
2663  			}
2664  			out = walkStdlibPkgs(sub, subPrefix, out)
2665  		}
2666  	}
2667  	return out
2668  }
2669  
2670  func cmdCacheWarm() {
2671  	root := getenv("MOXIEROOT")
2672  	if root == "" {
2673  		root = "."
2674  	}
2675  	srcDir := mxutil.JoinPath(root, "src")
2676  	mxutil.WriteStr(2, "warming stdlib cache from " | srcDir | " ...\n")
2677  	pkgPaths := walkStdlibPkgs(srcDir, "", nil)
2678  	if len(pkgPaths) == 0 {
2679  		mxutil.WriteStr(2, "  no stdlib packages found\n")
2680  		return
2681  	}
2682  	triple := "x86_64-unknown-linux-musleabihf"
2683  	clang := "clang-22"
2684  	tmpdir := "/tmp/mxc-build"
2685  	mkdirAll(tmpdir, 0755)
2686  	cacheBase := pkgCacheDir()
2687  	registerBuiltins()
2688  	compiled := int32(0)
2689  	skipped := int32(0)
2690  	failed := int32(0)
2691  	for _, pkgPath := range pkgPaths {
2692  		if isBuiltinOnly(pkgPath) {
2693  			continue
2694  		}
2695  		pkg := discoverPkg(pkgPath, root)
2696  		if pkg == nil {
2697  			skipped++
2698  			continue
2699  		}
2700  		hash := pkgHash(pkg)
2701  		cached := cachedBcPath(cacheBase, pkg.path, hash, pkg.version)
2702  		cachedMxh := cachedMxhPath(cacheBase, pkg.path, hash, pkg.version)
2703  		bcOk := mxutil.FileExists(cached)
2704  		mxhOk := mxutil.FileExists(cachedMxh)
2705  		if bcOk && mxhOk {
2706  			skipped++
2707  			continue
2708  		}
2709  		compilePath := pkg.path
2710  		if mxutil.HasPrefix(compilePath, "/") || mxutil.HasPrefix(compilePath, "./") || mxutil.HasPrefix(compilePath, "../") {
2711  			compilePath = pkg.name
2712  		}
2713  		src := concatSources(pkg.dir, pkg.files)
2714  		ir := compileSource(src, compilePath, triple, pkg.dir)
2715  		llFile := mxutil.JoinPath(tmpdir, safeName(pkg.path) | ".ll")
2716  		writeFile(llFile, []byte(ir), 0644)
2717  		bcFile := mxutil.JoinPath(tmpdir, safeName(pkg.path) | ".bc")
2718  		rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", llFile, "-o", bcFile})
2719  		if rc != 0 {
2720  			mxutil.WriteStr(2, "  FAIL: " | pkg.path | "\n")
2721  			failed++
2722  			continue
2723  		}
2724  		cdir := mxutil.JoinPath(cacheBase, safeName(pkg.path))
2725  		mkdirAll(cdir, 0755)
2726  		data, rok := mxutil.ReadFile(bcFile)
2727  		if rok {
2728  			writeFile(cached, data, 0644)
2729  		}
2730  		if len(cctx.lastMxhData) > 0 {
2731  			writeFile(cachedMxh, []byte(cctx.lastMxhData), 0644)
2732  			cctx.lastMxhData = ""
2733  		}
2734  		compiled++
2735  		mxutil.WriteStr(2, "  " | pkg.path | "\n")
2736  	}
2737  	mxutil.WriteStr(2, "warm done: " | simpleItoa(compiled) | " compiled, " | simpleItoa(skipped) | " cached, " | simpleItoa(failed) | " failed\n")
2738  }
2739  
2740  func cacheClean() {
2741  	base := pkgCacheDir()
2742  	mxutil.WriteStr(2, "cleaning cache: " | base | "\n")
2743  	entries := listFiles(base, "")
2744  	if entries == nil {
2745  		mxutil.WriteStr(2, "  (empty)\n")
2746  		return
2747  	}
2748  	for _, e := range entries {
2749  		removeAll(mxutil.JoinPath(base, e))
2750  	}
2751  	os.Remove(base)
2752  	mxutil.WriteStr(2, "  done\n")
2753  }
2754  
2755  func cmdUpdate(rebuildAll bool) {
2756  	cacheRoot := mxutil.JoinPath(getMoxiePath(), "cache")
2757  	currentDir := "mxc-" | mxcVersion()
2758  	entries := listFiles(cacheRoot, "")
2759  	if entries == nil {
2760  		mxutil.WriteStr(2, "cache is empty\n")
2761  		return
2762  	}
2763  	for _, e := range entries {
2764  		if mxutil.HasPrefix(e, "mxc-") && e != currentDir {
2765  			mxutil.WriteStr(2, "  remove old: " | e | "\n")
2766  			fullPath := mxutil.JoinPath(cacheRoot, e)
2767  			pkgEntries := listFiles(fullPath, "")
2768  			if pkgEntries != nil {
2769  				for _, pe := range pkgEntries {
2770  					removeAll(mxutil.JoinPath(fullPath, pe))
2771  				}
2772  			}
2773  			os.Remove(fullPath)
2774  		}
2775  	}
2776  	if !rebuildAll {
2777  		mxutil.WriteStr(2, "update done\n")
2778  		return
2779  	}
2780  	mxutil.WriteStr(2, "rebuilding all cached packages...\n")
2781  	mpath := getMoxiePath()
2782  	root := getenv("MOXIEROOT")
2783  	if root == "" {
2784  		root = "."
2785  	}
2786  	cacheBase := pkgCacheDir()
2787  	pkgDirs := listFiles(cacheBase, "")
2788  	if pkgDirs == nil {
2789  		mxutil.WriteStr(2, "  no packages to rebuild\n")
2790  		return
2791  	}
2792  	triple := "x86_64-unknown-linux-musleabihf"
2793  	clang := "clang-22"
2794  	tmpdir := "/tmp/mxc-build"
2795  	mkdirAll(tmpdir, 0755)
2796  	registerBuiltins()
2797  	for _, pkgDir := range pkgDirs {
2798  		pkgPath := ""
2799  		for i := 0; i < len(pkgDir); i++ {
2800  			if pkgDir[i] == '_' {
2801  				pkgPath = pkgPath | "/"
2802  			} else {
2803  				pkgPath = pkgPath | string([]byte{pkgDir[i]})
2804  			}
2805  		}
2806  		if len(pkgPath) > 0 && pkgPath[0] == '/' {
2807  			continue
2808  		}
2809  		mxutil.WriteStr(2, "  rebuild " | pkgPath | "\n")
2810  		pkg := discoverPkg(pkgPath, root)
2811  		if pkg == nil {
2812  			candidate := mxutil.JoinPath(mpath, pkgPath)
2813  			mxF := listFiles(candidate, ".mx")
2814  			if len(mxF) > 0 {
2815  				pkg = discoverPkg(pkgPath, root)
2816  			}
2817  		}
2818  		if pkg == nil {
2819  			mxutil.WriteStr(2, "    skip (source not found)\n")
2820  			continue
2821  		}
2822  		compilePath := pkg.path
2823  		if compilePath == "." || compilePath == ".." || mxutil.HasPrefix(compilePath, "/") || mxutil.HasPrefix(compilePath, "./") || mxutil.HasPrefix(compilePath, "../") {
2824  			compilePath = pkg.name
2825  		}
2826  		src := concatSources(pkg.dir, pkg.files)
2827  		ir := compileSource(src, compilePath, triple, pkg.dir)
2828  		llFile := mxutil.JoinPath(tmpdir, safeName(pkg.path) | ".ll")
2829  		writeFile(llFile, []byte(ir), 0644)
2830  		bcFile := mxutil.JoinPath(tmpdir, safeName(pkg.path) | ".bc")
2831  		rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", llFile, "-o", bcFile})
2832  		if rc != 0 {
2833  			mxutil.WriteStr(2, "    FAIL: clang for " | pkg.path | "\n")
2834  			continue
2835  		}
2836  		hash := pkgHash(pkg)
2837  		cached := cachedBcPath(cacheBase, pkg.path, hash, pkg.version)
2838  		cdir := mxutil.JoinPath(cacheBase, safeName(pkg.path))
2839  		mkdirAll(cdir, 0755)
2840  		data, rok := mxutil.ReadFile(bcFile)
2841  		if rok {
2842  			writeFile(cached, data, 0644)
2843  		}
2844  		if len(cctx.lastMxhData) > 0 {
2845  			cachedMxh := cachedMxhPath(cacheBase, pkg.path, hash, pkg.version)
2846  			writeFile(cachedMxh, []byte(cctx.lastMxhData), 0644)
2847  			cctx.lastMxhData = ""
2848  		}
2849  	}
2850  	mxutil.WriteStr(2, "rebuild done\n")
2851  }
2852  
2853  func scanTestFuncs(data []byte) (ss []string) {
2854  	var funcs []string
2855  	lines := mxutil.SplitLines(string(data))
2856  	for _, line := range lines {
2857  		line = mxutil.TrimSpace(line)
2858  		if !mxutil.HasPrefix(line, "func Test") {
2859  			continue
2860  		}
2861  		paren := int32(-1)
2862  		for i := int32(5); i < int32(len(line)); i++ {
2863  			if line[i] == '(' {
2864  				paren = i
2865  				break
2866  			}
2867  		}
2868  		if paren < 0 {
2869  			continue
2870  		}
2871  		name := line[5:paren] // "func " is 5 chars, so name starts at "Test..."
2872  		if len(name) == 0 {
2873  			continue
2874  		}
2875  		if name[0] < 'A' || name[0] > 'Z' {
2876  			continue
2877  		}
2878  		rest := line[paren:]
2879  		if mxutil.HasPrefix(rest, "(t *testing.T)") {
2880  			push(funcs, name)
2881  		}
2882  	}
2883  	return funcs
2884  }
2885  
2886  func generateTestMain(pkgName string, testFuncs []string) (buf []byte) {
2887  	var out []byte
2888  	out = out | ("package " | pkgName | "\n\nimport \"testing\"\n\nfunc main() {\n\ttests := []testing.InternalTest{\n")
2889  	for _, name := range testFuncs {
2890  		out = out | ("\t\t{Name: \"" | name | "\", F: " | name | "},\n")
2891  	}
2892  	out = out | "\t}\n\ttesting.Main(tests)\n}\n"
2893  	return out
2894  }
2895  
2896  func cmdTest(args []string) {
2897  	pkgPath := ""
2898  	forceRebuild := false
2899  	verbose := false
2900  	i := int32(2)
2901  	for i < int32(len(args)) {
2902  		if args[i] == "-a" {
2903  			forceRebuild = true
2904  			i++
2905  		} else if args[i] == "-v" {
2906  			verbose = true
2907  			i++
2908  		} else {
2909  			pkgPath = args[i]
2910  			i++
2911  		}
2912  	}
2913  	if pkgPath == "" {
2914  		pkgPath = "."
2915  	}
2916  	_ = verbose
2917  
2918  	root := getenv("MOXIEROOT")
2919  	if root == "" {
2920  		root = "."
2921  	}
2922  	triple := "x86_64-unknown-linux-musleabihf"
2923  	clang := "clang-22"
2924  
2925  	if mxutil.HasPrefix(pkgPath, "./") || mxutil.HasPrefix(pkgPath, "../") || pkgPath == "." || mxutil.HasPrefix(pkgPath, "/") {
2926  		absDir := pkgPath
2927  		if absDir == "." {
2928  			absDir = ""
2929  		}
2930  		bst.globalModPrefix, bst.globalModDir = findModuleRoot(absDir)
2931  	}
2932  
2933  	registerBuiltins()
2934  
2935  	var testPkgDir string
2936  	if pkgPath == "." || pkgPath == ".." || mxutil.HasPrefix(pkgPath, "/") || mxutil.HasPrefix(pkgPath, "./") || mxutil.HasPrefix(pkgPath, "../") {
2937  		testPkgDir = pkgPath
2938  	} else {
2939  		testPkgDir = mxutil.JoinPath(root, mxutil.JoinPath("src", pkgPath))
2940  	}
2941  
2942  	testFiles := listFiles(testPkgDir, "_test.mx")
2943  	if len(testFiles) == 0 {
2944  		mxutil.WriteStr(2, "no test files found in " | testPkgDir | "\n")
2945  		return
2946  	}
2947  
2948  	var allTestFuncs []string
2949  	for _, tf := range testFiles {
2950  		data, ok := mxutil.ReadFile(mxutil.JoinPath(testPkgDir, tf))
2951  		if !ok {
2952  			continue
2953  		}
2954  		funcs := scanTestFuncs(data)
2955  		for _, fn := range funcs {
2956  			push(allTestFuncs, fn)
2957  		}
2958  	}
2959  	if len(allTestFuncs) == 0 {
2960  		mxutil.WriteStr(2, "no Test* functions found\n")
2961  		return
2962  	}
2963  
2964  	mxutil.WriteStr(2, "=== test: " | pkgPath | " ===\n")
2965  	for _, fn := range allTestFuncs {
2966  		mxutil.WriteStr(2, "  found " | fn | "\n")
2967  	}
2968  
2969  	pkgName := ""
2970  	for _, tf := range testFiles {
2971  		data, ok := mxutil.ReadFile(mxutil.JoinPath(testPkgDir, tf))
2972  		if ok && pkgName == "" {
2973  			pkgName = extractPkgName(data)
2974  		}
2975  	}
2976  	if pkgName == "" {
2977  		pkgName = "main"
2978  	}
2979  
2980  	tmpdir := "/tmp/mxc-testbuild"
2981  	mkdirAll(tmpdir, 0755)
2982  
2983  	testMainSrc := generateTestMain(pkgName, allTestFuncs)
2984  	testMainFile := mxutil.JoinPath(tmpdir, "_testmain.mx")
2985  	writeFile(testMainFile, testMainSrc, 0644)
2986  
2987  	pkg := discoverPkg(pkgPath, root)
2988  	if pkg == nil {
2989  		fatal("cannot find package " | pkgPath)
2990  	}
2991  
2992  	for _, tf := range testFiles {
2993  		push(pkg.files, tf)
2994  	}
2995  	push(pkg.files, testMainFile)
2996  	pkg.name = "main"
2997  
2998  	if !hasImportStr(pkg.imports, "testing") {
2999  		push(pkg.imports, "testing")
3000  	}
3001  
3002  	pkgs := resolveTestDeps(pkg, root)
3003  
3004  	cacheBase := pkgCacheDir()
3005  
3006  	for _, dp := range pkgs {
3007  		compilePath := dp.path
3008  		if compilePath == "." || compilePath == ".." || mxutil.HasPrefix(compilePath, "/") || mxutil.HasPrefix(compilePath, "./") || mxutil.HasPrefix(compilePath, "../") {
3009  			compilePath = dp.name
3010  		}
3011  		if dp == pkg {
3012  			compilePath = "main"
3013  		}
3014  		hash := pkgHash(dp)
3015  		cachedMxh := cachedMxhPath(cacheBase, dp.path, hash, dp.version)
3016  		if mxhOk := mxutil.FileExists(cachedMxh); mxhOk && !forceRebuild && dp != pkg {
3017  			loadMxh(cachedMxh)
3018  			src := concatSources(dp.dir, dp.files)
3019  			ScanGenericDecls(src, compilePath)
3020  		} else {
3021  			var src []byte
3022  			if dp == pkg {
3023  				src = concatTestSources(dp.dir, dp.files, testMainFile)
3024  			} else {
3025  				src = concatSources(dp.dir, dp.files)
3026  			}
3027  			TypeCheckOnly(src, compilePath, "")
3028  		}
3029  	}
3030  
3031  	var allBcFiles []string
3032  	hasRuntimePkg := false
3033  
3034  	for _, dp := range pkgs {
3035  		if dp.path == "runtime" {
3036  			hasRuntimePkg = true
3037  		}
3038  		compilePath := dp.path
3039  		if compilePath == "." || compilePath == ".." || mxutil.HasPrefix(compilePath, "/") || mxutil.HasPrefix(compilePath, "./") || mxutil.HasPrefix(compilePath, "../") {
3040  			compilePath = dp.name
3041  		}
3042  		if dp == pkg {
3043  			compilePath = "main"
3044  		}
3045  
3046  		hash := pkgHash(dp)
3047  		cached := cachedBcPath(cacheBase, dp.path, hash, dp.version)
3048  		cachedMxh := cachedMxhPath(cacheBase, dp.path, hash, dp.version)
3049  
3050  		if dp != pkg {
3051  			bcOk := mxutil.FileExists(cached)
3052  			mxhOk := mxutil.FileExists(cachedMxh)
3053  			if bcOk && mxhOk && !forceRebuild {
3054  				mxutil.WriteStr(2, "  cached " | dp.path | "\n")
3055  				loadMxh(cachedMxh)
3056  				push(allBcFiles, cached)
3057  				for _, cf := range dp.cfiles {
3058  					cPath := mxutil.JoinPath(dp.dir, cf)
3059  					cbcFile := mxutil.JoinPath(tmpdir, safeName(dp.path) | "_" | cf | ".bc")
3060  					crc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", cPath, "-o", cbcFile})
3061  					if crc == 0 {
3062  						push(allBcFiles, cbcFile)
3063  					}
3064  				}
3065  				for _, bf := range dp.bcfiles {
3066  					push(allBcFiles, mxutil.JoinPath(dp.dir, bf))
3067  				}
3068  				continue
3069  			}
3070  
3071  			if isStdlib(dp.path) && bcOk && !forceRebuild {
3072  				mxutil.WriteStr(2, "  cached " | dp.path | "\n")
3073  				csrc := concatSources(dp.dir, dp.files)
3074  				cdir := mxutil.JoinPath(cacheBase, safeName(dp.path))
3075  				mkdirAll(cdir, 0755)
3076  				TypeCheckOnly(csrc, compilePath, cachedMxh)
3077  				push(allBcFiles, cached)
3078  				continue
3079  			}
3080  		}
3081  
3082  		mxutil.WriteStr(2, "  compile " | dp.path | "\n")
3083  		var src []byte
3084  		if dp == pkg {
3085  			src = concatTestSources(dp.dir, dp.files, testMainFile)
3086  		} else {
3087  			src = concatSources(dp.dir, dp.files)
3088  		}
3089  		ir := compileSource(src, compilePath, triple, dp.dir)
3090  		llFile := mxutil.JoinPath(tmpdir, safeName(dp.path) | ".ll")
3091  		writeFile(llFile, []byte(ir), 0644)
3092  		bcFile := mxutil.JoinPath(tmpdir, safeName(dp.path) | ".bc")
3093  		drc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", llFile, "-o", bcFile})
3094  		if drc != 0 {
3095  			fatal("clang failed for " | dp.path)
3096  		}
3097  		if dp != pkg {
3098  			cacheDir := mxutil.JoinPath(cacheBase, safeName(dp.path))
3099  			mkdirAll(cacheDir, 0755)
3100  			data, rok := mxutil.ReadFile(bcFile)
3101  			if rok {
3102  				writeFile(cached, data, 0644)
3103  			}
3104  			if len(cctx.lastMxhData) > 0 {
3105  				writeFile(cachedMxh, []byte(cctx.lastMxhData), 0644)
3106  				cctx.lastMxhData = ""
3107  			}
3108  		}
3109  		push(allBcFiles, bcFile)
3110  		for _, cf := range dp.cfiles {
3111  			cPath := mxutil.JoinPath(dp.dir, cf)
3112  			cbcFile := mxutil.JoinPath(tmpdir, safeName(dp.path) | "_" | cf | ".bc")
3113  			drc = run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", cPath, "-o", cbcFile})
3114  			if drc != 0 {
3115  				fatal("clang failed for " | cf)
3116  			}
3117  			push(allBcFiles, cbcFile)
3118  		}
3119  		for _, bf := range dp.bcfiles {
3120  			push(allBcFiles, mxutil.JoinPath(dp.dir, bf))
3121  		}
3122  	}
3123  
3124  	loadSysroot(mxutil.JoinPath(root, "_sysroot.env"))
3125  
3126  	sysroot := mxutil.JoinPath(root, "sysroot")
3127  	runtimeBc := getenv("RUNTIME_BC")
3128  	if runtimeBc == "" {
3129  		runtimeBc = mxutil.JoinPath(sysroot, "runtime.bc")
3130  	}
3131  	muslDir := getenv("MUSL_DIR")
3132  	if muslDir == "" {
3133  		muslDir = mxutil.JoinPath(sysroot, "musl")
3134  	}
3135  	comprtLib := getenv("COMPRT_LIB")
3136  	if comprtLib == "" {
3137  		comprtLib = mxutil.JoinPath(mxutil.JoinPath(sysroot, "compiler-rt"), "lib.a")
3138  	}
3139  	muslLib := getenv("MUSL_LIB")
3140  	if muslLib == "" {
3141  		muslLib = mxutil.JoinPath(mxutil.JoinPath(sysroot, "musl"), "lib.a")
3142  	}
3143  
3144  	mxutil.WriteStr(2, "  generate initAll\n")
3145  	var initCalls string
3146  	for _, dp := range pkgs {
3147  		compilePath := dp.path
3148  		if compilePath == "." || compilePath == ".." || mxutil.HasPrefix(compilePath, "/") || mxutil.HasPrefix(compilePath, "./") || mxutil.HasPrefix(compilePath, "../") {
3149  			compilePath = dp.name
3150  		}
3151  		if dp == pkg {
3152  			continue
3153  		}
3154  		initSym := irGlobalSymbol(compilePath, "main")
3155  		initCalls = initCalls | "  call void " | initSym | "(ptr null)\n"
3156  	}
3157  	initAllIR := "\ndefine dso_local void @runtime.initAll(ptr %_ctx) {\nentry:\n" | initCalls | "  ret void\n}\n" |
3158  		"declare ptr @llvm.stacksave.p0()\n" |
3159  		"define ptr @runtime.stacksave(ptr %_ctx) {\n" |
3160  		"  %1 = call ptr @llvm.stacksave.p0()\n" |
3161  		"  ret ptr %1\n" |
3162  		"}\n" |
3163  		"define void @runtime.relocCallFixup(i64 %fn, ptr %obj, ptr %rs, ptr %ctx) {\n" |
3164  		"  %fp = inttoptr i64 %fn to ptr\n" |
3165  		"  call void %fp(ptr %obj, ptr %rs, ptr null)\n" |
3166  		"  ret void\n" |
3167  		"}\n"
3168  	declared := map[string]bool{}
3169  	for _, dp := range pkgs {
3170  		compilePath := dp.path
3171  		if compilePath == "." || compilePath == ".." || mxutil.HasPrefix(compilePath, "/") || mxutil.HasPrefix(compilePath, "./") || mxutil.HasPrefix(compilePath, "../") {
3172  			compilePath = dp.name
3173  		}
3174  		if dp == pkg {
3175  			continue
3176  		}
3177  		declSym := irGlobalSymbol(compilePath, "main")
3178  		if declared[declSym] {
3179  			continue
3180  		}
3181  		declared[declSym] = true
3182  		initAllIR = initAllIR | "declare void " | declSym | "(ptr)\n"
3183  	}
3184  	initAllFile := mxutil.JoinPath(tmpdir, "initall.ll")
3185  	writeFile(initAllFile, []byte(initAllIR), 0644)
3186  	initAllBc := mxutil.JoinPath(tmpdir, "initall.bc")
3187  	rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", initAllFile, "-o", initAllBc})
3188  	if rc != 0 {
3189  		fatal("clang failed for initall")
3190  	}
3191  
3192  	mxutil.WriteStr(2, "  compile objects\n")
3193  	var objList []string
3194  	if !hasRuntimePkg {
3195  		runtimeGoBc := mxutil.JoinPath(sysroot, "runtime_go.bc")
3196  		if _, gok := mxutil.ReadFile(runtimeGoBc); gok {
3197  			push(objList, clangCompileToObj(clang, triple,runtimeGoBc))
3198  		} else {
3199  			push(objList, clangCompileToObj(clang, triple,runtimeBc))
3200  		}
3201  	}
3202  	if !hasRuntimePkg {
3203  		rtShims := mxutil.JoinPath(sysroot, "rt_shims.bc")
3204  		if _, rsk := mxutil.ReadFile(rtShims); rsk {
3205  			push(objList, clangCompileToObj(clang, triple,rtShims))
3206  		}
3207  	}
3208  	if bst.buildJobs > 1 {
3209  		var objCmds [][]string
3210  		for _, bf := range allBcFiles {
3211  			push(objCmds, []string{clang, "--target=" | triple,
3212  				"-c", "-O0", bf, "-o", bf | ".o"})
3213  		}
3214  		if !runPool(objCmds, bst.buildJobs) {
3215  			fatal("clang -c failed")
3216  		}
3217  		for _, bf := range allBcFiles {
3218  			push(objList, bf|".o")
3219  		}
3220  	} else {
3221  		for _, bf := range allBcFiles {
3222  			push(objList, clangCompileToObj(clang, triple,bf))
3223  		}
3224  	}
3225  	push(objList, clangCompileToObj(clang, triple,initAllBc))
3226  	if !hasRuntimePkg {
3227  		cstubDir := mxutil.JoinPath(root, "src/runtime")
3228  		cstubFiles := listFiles(cstubDir, ".c")
3229  		for _, cf := range cstubFiles {
3230  			cPath := mxutil.JoinPath(cstubDir, cf)
3231  			coFile := mxutil.JoinPath(tmpdir, "cstub_" | cf | ".o")
3232  			rc = run([]string{clang, "--target=" | triple, "-c", "-O0", cPath, "-o", coFile})
3233  			if rc == 0 {
3234  				push(objList, coFile)
3235  			}
3236  		}
3237  	}
3238  	objDir := mxutil.JoinPath(sysroot, "obj")
3239  	objEntries := listFiles(objDir, ".bc")
3240  	if objEntries != nil {
3241  		for _, e := range objEntries {
3242  			push(objList, clangCompileToObj(clang, triple,mxutil.JoinPath(objDir, e)))
3243  		}
3244  	}
3245  	oEntries := listFiles(objDir, ".o")
3246  	if oEntries != nil {
3247  		for _, e := range oEntries {
3248  			push(objList, mxutil.JoinPath(objDir, e))
3249  		}
3250  	}
3251  	outpath := mxutil.JoinPath(tmpdir, "test.bin")
3252  	mxutil.WriteStr(2, "  link\n")
3253  	linker := "ld.lld-22"
3254  	ldArgs := []string{linker, "--gc-sections", "-o", outpath,
3255  		mxutil.JoinPath(muslDir, "crt1.o")}
3256  	for _, o := range objList {
3257  		push(ldArgs, o)
3258  	}
3259  	push(ldArgs, comprtLib, muslLib, "-z", "stack-size=67108864")
3260  	rc = run(ldArgs)
3261  	if rc != 0 {
3262  		fatal("linker failed")
3263  	}
3264  
3265  	mxutil.WriteStr(2, "  run tests\n")
3266  	rc = run([]string{outpath})
3267  	os.Exit(int32(rc))
3268  }
3269  
3270  func hasImportStr(imports []string, pkg string) (ok bool) {
3271  	for _, imp := range imports {
3272  		if imp == pkg {
3273  			return true
3274  		}
3275  	}
3276  	return false
3277  }
3278  
3279  func resolveTestDeps(testPkg *pkgInfo, root string) (ss []*pkgInfo) {
3280  	r := &resolver{
3281  		visited: map[string]bool{},
3282  		root:    root,
3283  	}
3284  	for _, imp := range testPkg.imports {
3285  		r.walk(imp)
3286  	}
3287  	push(r.order, testPkg)
3288  	return r.order
3289  }
3290  
3291  func concatTestSources(dir string, files []string, testMainFile string) (buf []byte) {
3292  	imports := map[string]bool{}
3293  	var bodies [][]byte
3294  	var bodyFiles []string
3295  	var bodyStartLines []int32
3296  	var bodyImports [][]string
3297  	pkgName := "main"
3298  
3299  	allFiles := []string{:0:len(files) + 1}
3300  	for _, f := range files {
3301  		if f == testMainFile {
3302  			continue
3303  		}
3304  		push(allFiles, f)
3305  	}
3306  
3307  	for _, f := range allFiles {
3308  		var data []byte
3309  		var ok bool
3310  		if mxutil.HasPrefix(f, "/") {
3311  			data, ok = mxutil.ReadFile(f)
3312  		} else {
3313  			data, ok = mxutil.ReadFile(mxutil.JoinPath(dir, f))
3314  		}
3315  		if !ok {
3316  			continue
3317  		}
3318  		lines := mxutil.SplitLines(string(data))
3319  		var body []string
3320  		var fimps []string
3321  		i := int32(0)
3322  		firstBodyLine := int32(0)
3323  		for i < int32(len(lines)) {
3324  			line := mxutil.TrimSpace(lines[i])
3325  			if mxutil.HasPrefix(line, "package ") {
3326  				i++
3327  				continue
3328  			}
3329  			if line == "import (" {
3330  				i++
3331  				for i < int32(len(lines)) {
3332  					imp := mxutil.TrimSpace(lines[i])
3333  					i++
3334  					if imp == ")" {
3335  						break
3336  					}
3337  					if imp != "" {
3338  						imports[imp] = true
3339  						push(fimps, imp)
3340  					}
3341  				}
3342  				continue
3343  			}
3344  			if mxutil.HasPrefix(line, "import ") && !mxutil.HasPrefix(line, "import (") {
3345  				imp := line[7:]
3346  				imports[imp] = true
3347  				push(fimps, imp)
3348  				i++
3349  				continue
3350  			}
3351  			if len(body) == 0 {
3352  				firstBodyLine = i + 1
3353  			}
3354  			push(body, lines[i])
3355  			i++
3356  		}
3357  		push(bodies, []byte(joinStrings(body, "\n")))
3358  		push(bodyFiles, f)
3359  		push(bodyStartLines, firstBodyLine)
3360  		push(bodyImports, fimps)
3361  	}
3362  
3363  	tmData, tmOk := mxutil.ReadFile(testMainFile)
3364  	if tmOk {
3365  		lines := mxutil.SplitLines(string(tmData))
3366  		var body []string
3367  		var fimps []string
3368  		i := int32(0)
3369  		firstBodyLine := int32(0)
3370  		for i < int32(len(lines)) {
3371  			line := mxutil.TrimSpace(lines[i])
3372  			if mxutil.HasPrefix(line, "package ") {
3373  				i++
3374  				continue
3375  			}
3376  			if line == "import (" {
3377  				i++
3378  				for i < int32(len(lines)) {
3379  					imp := mxutil.TrimSpace(lines[i])
3380  					i++
3381  					if imp == ")" {
3382  						break
3383  					}
3384  					if imp != "" {
3385  						imports[imp] = true
3386  						push(fimps, imp)
3387  					}
3388  				}
3389  				continue
3390  			}
3391  			if mxutil.HasPrefix(line, "import ") && !mxutil.HasPrefix(line, "import (") {
3392  				imp := line[7:]
3393  				imports[imp] = true
3394  				push(fimps, imp)
3395  				i++
3396  				continue
3397  			}
3398  			if len(body) == 0 {
3399  				firstBodyLine = i + 1
3400  			}
3401  			push(body, lines[i])
3402  			i++
3403  		}
3404  		push(bodies, []byte(joinStrings(body, "\n")))
3405  		push(bodyFiles, "_testmain.mx")
3406  		push(bodyStartLines, firstBodyLine)
3407  		push(bodyImports, fimps)
3408  	}
3409  
3410  	var out []byte
3411  	out = out | ("package " | pkgName | "\n")
3412  	if len(imports) > 0 {
3413  		var impKeys []string
3414  		for imp := range imports {
3415  			push(impKeys, imp)
3416  		}
3417  		for i := int32(1); i < int32(len(impKeys)); i++ {
3418  			for j := i; j > 0 && impKeys[j] < impKeys[j-1]; j-- {
3419  				impKeys[j], impKeys[j-1] = impKeys[j-1], impKeys[j]
3420  			}
3421  		}
3422  		out = out | "import (\n"
3423  		for _, imp := range impKeys {
3424  			push(out, '\t')
3425  			out = out | imp
3426  			push(out, '\n')
3427  		}
3428  		out = out | ")\n"
3429  	}
3430  	cctx.concatSourceMap = nil
3431  	mxutil.ResetConcatImportSegs()
3432  	outLine := int32(1)
3433  	for j := int32(0); j < int32(len(out)); j++ {
3434  		if out[j] == '\n' {
3435  			outLine++
3436  		}
3437  	}
3438  	for i, b := range bodies {
3439  		push(cctx.concatSourceMap, srcSpan{
3440  			file:     bodyFiles[i],
3441  			outStart: outLine,
3442  			srcLine:  bodyStartLines[i],
3443  		})
3444  		mxutil.AppendConcatImportSeg(mxutil.ImportSeg{
3445  			OutStart: outLine,
3446  			Specs:    bodyImports[i],
3447  		})
3448  		for j := int32(0); j < int32(len(b)); j++ {
3449  			if b[j] == '\n' {
3450  				outLine++
3451  			}
3452  		}
3453  		outLine++
3454  		out = out | b
3455  		push(out, '\n')
3456  		bodies[i] = nil
3457  	}
3458  	bodies = nil
3459  	imports = nil
3460  	return out
3461  }
3462  
3463  func main() {
3464  	// Root arena is the domain root's arena, set up by the runtime.
3465  	// 2GB virtual - pages committed on touch by the MMU.
3466  	initCompileCtx()
3467  	cctx.universe = InitUniverse()
3468  	SetUniverseGlobals(cctx.universe)
3469  	initBuildState()
3470  	bst.buildJobs = int32(4)
3471  	bst.wasmRtPkgs = map[string]bool{
3472  		"runtime": true, "runtime/interrupt": true,
3473  		"internal/task": true, "internal/itoa": true,
3474  		"math": true, "math/bits": true,
3475  		"unicode/utf8": true, "strconv": true,
3476  		"errors": true, "io": true, "io/fs": true,
3477  		"os": true, "syscall": true, "time": true,
3478  		"fmt": true, "path": true,
3479  		"internal/bytealg": true, "internal/byteorder": true,
3480  		"internal/goarch": true, "internal/binary": true,
3481  		"internal/gclayout": true, "internal/oserror": true,
3482  		"internal/stringslite": true, "internal/testlog": true,
3483  	}
3484  	bst.builtinOnly = []string{"unsafe"}
3485  	args := os.Args
3486  	if len(args) < 2 {
3487  		mxutil.WriteStr(2, "usage: mxc <build|test|version|cache|fetch|update> ...\n")
3488  		os.Exit(1)
3489  	}
3490  
3491  	cmd := args[1]
3492  	if cmd == "version" {
3493  		mxutil.WriteStr(1, "mxc " | mxcVersion() | " (" | compilerHash() | ")\n")
3494  		return
3495  	}
3496  	if cmd == "cache" {
3497  		if len(args) >= 3 && args[2] == "clean" {
3498  			cacheClean()
3499  			return
3500  		}
3501  		if len(args) >= 3 && args[2] == "warm" {
3502  			cmdCacheWarm()
3503  			return
3504  		}
3505  		mxutil.WriteStr(2, "usage: mxc cache <clean|warm>\n")
3506  		os.Exit(1)
3507  	}
3508  	if cmd == "fetch" {
3509  		fetchURL := ""
3510  		if len(args) >= 3 {
3511  			fetchURL = args[2]
3512  		}
3513  		cmdFetch(fetchURL)
3514  		return
3515  	}
3516  	if cmd == "update" {
3517  		rebuildAll := false
3518  		for ai := int32(2); ai < int32(len(args)); ai++ {
3519  			if args[ai] == "--all" {
3520  				rebuildAll = true
3521  			}
3522  		}
3523  		cmdUpdate(rebuildAll)
3524  		return
3525  	}
3526  	if cmd == "test" {
3527  		cmdTest(args)
3528  		return
3529  	}
3530  	if cmd == "build-runtime" {
3531  		cmdBuildRuntime()
3532  		return
3533  	}
3534  	if cmd != "build" {
3535  		mxutil.WriteStr(2, "usage: mxc <build|test|version|cache|fetch|update|build-runtime> ...\n")
3536  		os.Exit(1)
3537  	}
3538  
3539  	outpath := ""
3540  	pkgPath := ""
3541  	printIR := false
3542  	forceRebuild := false
3543  	targetFlag := ""
3544  	i := int32(2)
3545  	for i < int32(len(args)) {
3546  		if args[i] == "-o" && i+1 < int32(len(args)) {
3547  			outpath = args[i+1]
3548  			i += 2
3549  		} else if args[i] == "-target" && i+1 < int32(len(args)) {
3550  			targetFlag = args[i+1]
3551  			i += 2
3552  		} else if args[i] == "-print-ir" {
3553  			printIR = true
3554  			i++
3555  		} else if args[i] == "-a" {
3556  			forceRebuild = true
3557  			i++
3558  		} else if args[i] == "-j" && i+1 < int32(len(args)) {
3559  			bst.buildJobs = parseInt32(args[i+1])
3560  			if bst.buildJobs < 1 {
3561  				bst.buildJobs = 1
3562  			}
3563  			i += 2
3564  		} else if args[i] == "-gc=dealloc" || args[i] == "-buildmode=c-shared" || args[i] == "-x" || args[i] == "-work" {
3565  			i++
3566  		} else {
3567  			pkgPath = args[i]
3568  			i++
3569  		}
3570  	}
3571  	if printIR {
3572  		// -print-ir needs the in-process compile path.
3573  		bst.buildJobs = 1
3574  	}
3575  	if pkgPath == "" {
3576  		fatal("no package specified")
3577  	}
3578  	if outpath == "" {
3579  		outpath = pathBase(pkgPath)
3580  	}
3581  
3582  	root := getenv("MOXIEROOT")
3583  	if root == "" {
3584  		root = "."
3585  	}
3586  	mxutil.SetTarget("linux", "amd64")
3587  	triple := "x86_64-unknown-linux-musleabihf"
3588  	if targetFlag == "js/wasm" || targetFlag == "wasm" {
3589  		mxutil.SetTarget("js", "wasm")
3590  		triple = "wasm32-unknown-wasi"
3591  	} else if targetFlag == "wasi/wasm" {
3592  		mxutil.SetTarget("wasi", "wasm")
3593  		triple = "wasm32-unknown-wasi"
3594  	}
3595  	isWasm := mxutil.TargetArch == "wasm"
3596  	clang := "clang-22"
3597  
3598  	if mxutil.HasPrefix(pkgPath, "./") || mxutil.HasPrefix(pkgPath, "../") || pkgPath == "." || mxutil.HasPrefix(pkgPath, "/") {
3599  		absDir := pkgPath
3600  		if absDir == "." {
3601  			absDir = ""
3602  		}
3603  		bst.globalModPrefix, bst.globalModDir = findModuleRoot(absDir)
3604  	}
3605  
3606  	registerBuiltins()
3607  	pkgs := resolveAll(pkgPath, root)
3608  	// wasm: runtime must be compiled from source (no prebuilt runtime.bc)
3609  	if isWasm {
3610  		hasRT := false
3611  		for _, p := range pkgs {
3612  			if p.path == "runtime" {
3613  				hasRT = true
3614  				break
3615  			}
3616  		}
3617  		if !hasRT {
3618  			rtPkgs := resolveAll("runtime", root)
3619  			// prepend runtime deps before user packages
3620  			var merged []*pkgInfo
3621  			seen := map[string]bool{}
3622  			for _, p := range rtPkgs {
3623  				if !seen[p.path] {
3624  					seen[p.path] = true
3625  					push(merged, p)
3626  				}
3627  			}
3628  			for _, p := range pkgs {
3629  				if !seen[p.path] {
3630  					seen[p.path] = true
3631  					push(merged, p)
3632  				}
3633  			}
3634  			pkgs = merged
3635  		}
3636  	}
3637  	if len(pkgs) == 0 {
3638  		fatal("no packages to compile")
3639  	}
3640  
3641  	tmpdir := "/tmp/mxc-build"
3642  	mkdirAll(tmpdir, 0755)
3643  
3644  	cacheBase := pkgCacheDir()
3645  	// TLS probe: if this binary's globals aren't thread_local, the parallel
3646  	// path would corrupt shared state. Fall back to serial.
3647  	if bst.buildJobs > 1 && cDetectTLS() == 0 {
3648  		mxutil.WriteStr(2, "  note: globals not TLS-isolated, forcing -j 1 (rebuild with self-compiled mxc for parallel)\n")
3649  		bst.buildJobs = 1
3650  	}
3651  	var allBcFiles []string
3652  	var hasRuntimePkg bool
3653  	if bst.buildJobs > 1 {
3654  		a, h := buildPkgsParallel(pkgs, triple, clang, tmpdir, cacheBase, forceRebuild, bst.buildJobs)
3655  		allBcFiles = a
3656  		hasRuntimePkg = h
3657  	} else {
3658  		a, h := buildPkgsSerial(pkgs, triple, clang, tmpdir, cacheBase, forceRebuild, printIR)
3659  		allBcFiles = a
3660  		hasRuntimePkg = h
3661  	}
3662  	loadSysroot(mxutil.JoinPath(root, "_sysroot.env"))
3663  
3664  	sysroot := mxutil.JoinPath(root, "sysroot")
3665  	runtimeBc := ""
3666  	muslDir := ""
3667  	comprtLib := ""
3668  	muslLib := ""
3669  	if !isWasm {
3670  		runtimeBc = getenv("RUNTIME_BC")
3671  		if runtimeBc == "" {
3672  			runtimeBc = mxutil.JoinPath(sysroot, "runtime.bc")
3673  		}
3674  		muslDir = getenv("MUSL_DIR")
3675  		if muslDir == "" {
3676  			muslDir = mxutil.JoinPath(sysroot, "musl")
3677  		}
3678  		comprtLib = getenv("COMPRT_LIB")
3679  		if comprtLib == "" {
3680  			comprtLib = mxutil.JoinPath(mxutil.JoinPath(sysroot, "compiler-rt"), "lib.a")
3681  		}
3682  		muslLib = getenv("MUSL_LIB")
3683  		if muslLib == "" {
3684  			muslLib = mxutil.JoinPath(mxutil.JoinPath(sysroot, "musl"), "lib.a")
3685  		}
3686  	}
3687  
3688  	mxutil.WriteStr(2, "  generate initAll\n")
3689  	var initCalls string
3690  	for _, pkg := range pkgs {
3691  		compilePath := pkg.path
3692  		if compilePath == "." || compilePath == ".." || mxutil.HasPrefix(compilePath, "/") || mxutil.HasPrefix(compilePath, "./") || mxutil.HasPrefix(compilePath, "../") {
3693  			compilePath = pkg.name
3694  		}
3695  		if compilePath == "main" {
3696  			continue
3697  		}
3698  		initSym := irGlobalSymbol(compilePath, "main")
3699  		initCalls = initCalls | "  call void " | initSym | "(ptr null)\n"
3700  	}
3701  	initAllIR := "\ndefine dso_local void @runtime.initAll(ptr %_ctx) {\nentry:\n" | initCalls | "  ret void\n}\n" |
3702  		"declare ptr @llvm.stacksave.p0()\n" |
3703  		"define ptr @runtime.stacksave(ptr %_ctx) {\n" |
3704  		"  %1 = call ptr @llvm.stacksave.p0()\n" |
3705  		"  ret ptr %1\n" |
3706  		"}\n" |
3707  		"define void @runtime.relocCallFixup(i64 %fn, ptr %obj, ptr %rs, ptr %ctx) {\n" |
3708  		"  %fp = inttoptr i64 %fn to ptr\n" |
3709  		"  call void %fp(ptr %obj, ptr %rs, ptr null)\n" |
3710  		"  ret void\n" |
3711  		"}\n"
3712  	declared := map[string]bool{}
3713  	for _, pkg := range pkgs {
3714  		compilePath := pkg.path
3715  		if compilePath == "." || compilePath == ".." || mxutil.HasPrefix(compilePath, "/") || mxutil.HasPrefix(compilePath, "./") || mxutil.HasPrefix(compilePath, "../") {
3716  			compilePath = pkg.name
3717  		}
3718  		if compilePath == "main" {
3719  			continue
3720  		}
3721  		declSym := irGlobalSymbol(compilePath, "main")
3722  		if declared[declSym] {
3723  			continue
3724  		}
3725  		declared[declSym] = true
3726  		initAllIR = initAllIR | "declare void " | declSym | "(ptr)\n"
3727  	}
3728  	initAllFile := mxutil.JoinPath(tmpdir, "initall.ll")
3729  	writeFile(initAllFile, []byte(initAllIR), 0644)
3730  	initAllBc := mxutil.JoinPath(tmpdir, "initall.bc")
3731  	rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", initAllFile, "-o", initAllBc})
3732  	if rc != 0 {
3733  		fatal("clang failed for initall")
3734  	}
3735  
3736  	if isWasm {
3737  		// wasm: compile each .bc to .o individually, then wasm-ld
3738  		mxutil.WriteStr(2, "  compile objects\n")
3739  		wasmTriple := "wasm32-unknown-wasi"
3740  		var objList []string
3741  		if bst.buildJobs > 1 {
3742  			var objCmds [][]string
3743  			for _, bf := range allBcFiles {
3744  				push(objCmds, []string{clang, "--target=" | wasmTriple,
3745  					"-c", bf, "-o", bf | ".o"})
3746  			}
3747  			if !runPool(objCmds, bst.buildJobs) {
3748  				fatal("clang -c failed")
3749  			}
3750  			for _, bf := range allBcFiles {
3751  				push(objList, bf|".o")
3752  			}
3753  		} else {
3754  			for _, bf := range allBcFiles {
3755  				push(objList, clangCompileToObj(clang, wasmTriple,bf))
3756  			}
3757  		}
3758  		push(objList, clangCompileToObj(clang, wasmTriple,initAllBc))
3759  		// Stubs for symbols not provided by stage4-compiled runtime
3760  		wasiStubs := mxutil.JoinPath(tmpdir, "wasi_stubs.ll")
3761  		writeFile(wasiStubs, []byte(
3762  			"target datalayout = \"e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20\"\n" |
3763  			"target triple = \"wasm32-unknown-wasi\"\n" |
3764  			"declare ptr @llvm.stacksave.p0()\n" |
3765  			"define ptr @runtime.stacksave(ptr %_ctx) {\n" |
3766  			"  %1 = call ptr @llvm.stacksave.p0()\n" |
3767  			"  ret ptr %1\n" |
3768  			"}\n" |
3769  			"define void @moxie_longjmp(ptr %0, ptr %1) {\n" |
3770  			"  call void @llvm.trap()\n" |
3771  			"  unreachable\n" |
3772  			"}\n" |
3773  			"define i32 @\"time.timezoneOffsetMinutes\"() {\n" |
3774  			"  ret i32 0\n" |
3775  			"}\n" |
3776  			"declare ptr @runtime.alloc(i32, ptr, ptr)\n" |
3777  			"define ptr @malloc(i32 %size, ptr %_ctx) {\n" |
3778  			"  %p = call ptr @runtime.alloc(i32 %size, ptr null, ptr null)\n" |
3779  			"  ret ptr %p\n" |
3780  			"}\n" |
3781  			"define i32 @strlen(ptr %s, ptr %_ctx) {\n" |
3782  			"entry:\n" |
3783  			"  br label %loop\n" |
3784  			"loop:\n" |
3785  			"  %i = phi i32 [0, %entry], [%i1, %next]\n" |
3786  			"  %cp = getelementptr i8, ptr %s, i32 %i\n" |
3787  			"  %c = load i8, ptr %cp\n" |
3788  			"  %z = icmp eq i8 %c, 0\n" |
3789  			"  br i1 %z, label %done, label %next\n" |
3790  			"next:\n" |
3791  			"  %i1 = add i32 %i, 1\n" |
3792  			"  br label %loop\n" |
3793  			"done:\n" |
3794  			"  ret i32 %i\n" |
3795  			"}\n" |
3796  			"declare void @llvm.trap()\n"), 0644)
3797  		push(objList, clangCompileToObj(clang, wasmTriple,wasiStubs))
3798  		mxutil.WriteStr(2, "  link\n")
3799  		ldArgs := []string{"wasm-ld", "--export-dynamic", "--allow-undefined",
3800  			"--gc-sections", "-o", outpath}
3801  		for _, o := range objList {
3802  			push(ldArgs, o)
3803  		}
3804  		push(ldArgs, "-z", "stack-size=67108864")
3805  		rc = run(ldArgs)
3806  		if rc != 0 {
3807  			fatal("linker failed")
3808  		}
3809  	} else {
3810  		// native: compile each .bc to .o, then ld.lld
3811  		mxutil.WriteStr(2, "  compile objects\n")
3812  		var objList []string
3813  		if !hasRuntimePkg {
3814  			runtimeGoBc := mxutil.JoinPath(sysroot, "runtime_go.bc")
3815  			if _, gok := mxutil.ReadFile(runtimeGoBc); gok {
3816  				push(objList, clangCompileToObj(clang, triple,runtimeGoBc))
3817  			} else {
3818  				push(objList, clangCompileToObj(clang, triple,runtimeBc))
3819  			}
3820  		}
3821  		rtShims := mxutil.JoinPath(sysroot, "rt_shims.bc")
3822  		if _, rsk := mxutil.ReadFile(rtShims); rsk {
3823  			push(objList, clangCompileToObj(clang, triple,rtShims))
3824  		}
3825  		if bst.buildJobs > 1 {
3826  			var objCmds [][]string
3827  			for _, bf := range allBcFiles {
3828  				push(objCmds, []string{clang, "--target=" | triple,
3829  					"-c", "-O0", bf, "-o", bf | ".o"})
3830  			}
3831  			if !runPool(objCmds, bst.buildJobs) {
3832  				fatal("clang -c failed")
3833  			}
3834  			for _, bf := range allBcFiles {
3835  				push(objList, bf|".o")
3836  			}
3837  		} else {
3838  			for _, bf := range allBcFiles {
3839  				push(objList, clangCompileToObj(clang, triple,bf))
3840  			}
3841  		}
3842  		push(objList, clangCompileToObj(clang, triple,initAllBc))
3843  		if !hasRuntimePkg {
3844  			cstubDir := mxutil.JoinPath(root, "src/runtime")
3845  			cstubFiles := listFiles(cstubDir, ".c")
3846  			for _, cf := range cstubFiles {
3847  				cPath := mxutil.JoinPath(cstubDir, cf)
3848  				coFile := mxutil.JoinPath(tmpdir, "cstub_" | cf | ".o")
3849  				rc = run([]string{clang, "--target=" | triple, "-c", "-O0", cPath, "-o", coFile})
3850  				if rc == 0 {
3851  					push(objList, coFile)
3852  				}
3853  			}
3854  		}
3855  		objDir := mxutil.JoinPath(sysroot, "obj")
3856  		objEntries := listFiles(objDir, ".bc")
3857  		if objEntries != nil {
3858  			for _, e := range objEntries {
3859  				push(objList, clangCompileToObj(clang, triple,mxutil.JoinPath(objDir, e)))
3860  			}
3861  		}
3862  		oEntries := listFiles(objDir, ".o")
3863  		if oEntries != nil {
3864  			for _, e := range oEntries {
3865  				push(objList, mxutil.JoinPath(objDir, e))
3866  			}
3867  		}
3868  		mxutil.WriteStr(2, "  link\n")
3869  		ldArgs := []string{"ld.lld-22", "--gc-sections", "-o", outpath,
3870  			mxutil.JoinPath(muslDir, "crt1.o")}
3871  		for _, o := range objList {
3872  			push(ldArgs, o)
3873  		}
3874  		push(ldArgs, comprtLib, muslLib, "-z", "stack-size=67108864")
3875  		rc = run(ldArgs)
3876  		if rc != 0 {
3877  			fatal("linker failed")
3878  		}
3879  	}
3880  	mxutil.WriteStr(2, "  -> " | outpath | "\n")
3881  }
3882  
3883  // buildPkgsSerial type-checks and compiles each package in dependency order
3884  // in a single pass. Each package's working set dies before the next begins.
3885  func buildPkgsSerial(pkgs []*pkgInfo, triple, clang, tmpdir, cacheBase string, forceRebuild, printIR bool) (bcs []string, hasRT bool) {
3886  	var deferredClang [][]string
3887  	isWasmTarget := mxutil.TargetArch == "wasm"
3888  	for _, pkg := range pkgs {
3889  		if pkg.path == "runtime" {
3890  			if !isWasmTarget {
3891  				hasRT = true
3892  			}
3893  		}
3894  		compilePath := pkg.path
3895  		if compilePath == "." || compilePath == ".." || mxutil.HasPrefix(compilePath, "/") || mxutil.HasPrefix(compilePath, "./") || mxutil.HasPrefix(compilePath, "../") {
3896  			compilePath = pkg.name
3897  		}
3898  
3899  		hash := pkgHash(pkg)
3900  		cached := cachedBcPath(cacheBase, pkg.path, hash, pkg.version)
3901  		cachedMxh := cachedMxhPath(cacheBase, pkg.path, hash, pkg.version)
3902  
3903  		bcOk := mxutil.FileExists(cached)
3904  		mxhOk := mxutil.FileExists(cachedMxh)
3905  
3906  		if bcOk && mxhOk && !forceRebuild {
3907  			mxutil.WriteStr(2, "  cached " | pkg.path)
3908  			if pkg.version != "" {
3909  				mxutil.WriteStr(2, " @ " | pkg.version)
3910  			}
3911  			mxutil.WriteStr(2, "\n")
3912  			loadMxh(cachedMxh)
3913  			gsrc := concatSources(pkg.dir, pkg.files)
3914  			ScanGenericDecls(gsrc, compilePath)
3915  			push(bcs, cached)
3916  			for _, cf := range pkg.cfiles {
3917  				cPath := mxutil.JoinPath(pkg.dir, cf)
3918  				cbcFile := mxutil.JoinPath(tmpdir, safeName(pkg.path) | "_" | cf | ".bc")
3919  				crc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", cPath, "-o", cbcFile})
3920  				if crc == 0 {
3921  					push(bcs, cbcFile)
3922  				}
3923  			}
3924  			for _, bf := range pkg.bcfiles {
3925  				push(bcs, mxutil.JoinPath(pkg.dir, bf))
3926  			}
3927  			continue
3928  		}
3929  
3930  		if isStdlib(pkg.path) && bcOk && !forceRebuild {
3931  			mxutil.WriteStr(2, "  cached " | pkg.path | "\n")
3932  			csrc := concatSources(pkg.dir, pkg.files)
3933  			cdir := mxutil.JoinPath(cacheBase, safeName(pkg.path))
3934  			mkdirAll(cdir, 0755)
3935  			TypeCheckOnly(csrc, compilePath, cachedMxh)
3936  			if cctx.compileArena != nil {
3937  				runtime.ArenaFree(cctx.compileArena)
3938  				cctx.compileArena = nil
3939  			}
3940  			push(bcs, cached)
3941  			continue
3942  		}
3943  
3944  		mxutil.WriteStr(2, "  compile " | pkg.path | "\n")
3945  		src := concatSources(pkg.dir, pkg.files)
3946  		llFile := mxutil.JoinPath(tmpdir, safeName(pkg.path) | ".ll")
3947  		// Stream IR directly to .ll file to avoid accumulating entire
3948  		// package IR in memory (unicode/tables.mx produces ~10MB IR).
3949  		cctx.irOutputFile = llFile
3950  		ir := compileSource(src, compilePath, triple, pkg.dir)
3951  		cctx.irOutputFile = ""
3952  		if printIR && len(ir) > 0 {
3953  			mxutil.WriteStr(1, ir)
3954  		}
3955  		if len(ir) > 0 {
3956  			// Non-streaming fallback (error messages start with ';')
3957  			writeFile(llFile, []byte(ir), 0644)
3958  		}
3959  		bcFile := mxutil.JoinPath(tmpdir, safeName(pkg.path) | ".bc")
3960  		// Defer clang call - collect for parallel execution after all IR emitted.
3961  		push(deferredClang, []string{clang, "--target=" | triple, "-emit-llvm", "-c", llFile, "-o", bcFile})
3962  		// mxh written inside CompileToIR's arena before it dies.
3963  		if len(cctx.lastMxhData) > 0 {
3964  			cdir := mxutil.JoinPath(cacheBase, safeName(pkg.path))
3965  			mkdirAll(cdir, 0755)
3966  			writeFile(cachedMxh, []byte(cctx.lastMxhData), 0644)
3967  			cctx.lastMxhData = ""
3968  		}
3969  		push(bcs, bcFile)
3970  		for _, cf := range pkg.cfiles {
3971  			cPath := mxutil.JoinPath(pkg.dir, cf)
3972  			cbcFile := mxutil.JoinPath(tmpdir, safeName(pkg.path) | "_" | cf | ".bc")
3973  			push(deferredClang, []string{clang, "--target=" | triple, "-emit-llvm", "-c", cPath, "-o", cbcFile})
3974  			// rc = run(...)  -- deferred
3975  			if false {
3976  				fatal("clang failed for " | cf)
3977  			}
3978  			push(bcs, cbcFile)
3979  		}
3980  		for _, bf := range pkg.bcfiles {
3981  			push(bcs, mxutil.JoinPath(pkg.dir, bf))
3982  		}
3983  	}
3984  	// Run all deferred clang calls in parallel.
3985  	if len(deferredClang) > 0 {
3986  		nproc := int32(8)
3987  		if int32(len(deferredClang)) < nproc {
3988  			nproc = int32(len(deferredClang))
3989  		}
3990  		mxutil.WriteStr(2, "  clang: " | simpleItoa(len(deferredClang)) | " files, " | simpleItoa(nproc) | " parallel\n")
3991  		if !runPool(deferredClang, nproc) {
3992  			fatal("clang failed")
3993  		}
3994  		// Cache the .bc files
3995  		for _, pkg := range pkgs {
3996  			compilePath := pkg.path
3997  			if compilePath == "." || compilePath == ".." || mxutil.HasPrefix(compilePath, "/") || mxutil.HasPrefix(compilePath, "./") || mxutil.HasPrefix(compilePath, "../") {
3998  				compilePath = pkg.name
3999  			}
4000  			hash := pkgHash(pkg)
4001  			cached := cachedBcPath(cacheBase, compilePath, hash, pkg.version)
4002  			bcFile := mxutil.JoinPath(tmpdir, safeName(pkg.path) | ".bc")
4003  			if mxutil.FileExists(bcFile) {
4004  				cacheDir := mxutil.JoinPath(cacheBase, safeName(pkg.path))
4005  				mkdirAll(cacheDir, 0755)
4006  				data, rok := mxutil.ReadFile(bcFile)
4007  				if rok {
4008  					writeFile(cached, data, 0644)
4009  				}
4010  			}
4011  		}
4012  	}
4013  	return bcs, hasRT
4014  }
4015  
4016  // --- Ring buffer helpers ---
4017  
4018  func ringSend(ring unsafe.Pointer, data []byte) (ok bool) {
4019  	var dp unsafe.Pointer
4020  	if len(data) > 0 {
4021  		dp = unsafe.Pointer(unsafe.SliceData(data))
4022  	}
4023  	for {
4024  		rc := cRingSend(ring, dp, int32(len(data)))
4025  		if rc == 0 {
4026  			return true
4027  		}
4028  		if rc < 0 {
4029  			return false
4030  		}
4031  		cUsleep(100) // backpressure
4032  	}
4033  }
4034  
4035  func ringRecv(ring unsafe.Pointer) (data []byte, ok bool) {
4036  	for {
4037  		pn := cRingPeekLen(ring)
4038  		if pn > 0 {
4039  			buf := []byte{:pn}
4040  			n := cRingRecv(ring, unsafe.Pointer(unsafe.SliceData(buf)), pn)
4041  			if n > 0 {
4042  				return buf[:n], true
4043  			}
4044  			if n < 0 {
4045  				return nil, false
4046  			}
4047  		}
4048  		if pn < 0 {
4049  			return nil, false
4050  		}
4051  		cUsleep(100)
4052  	}
4053  }
4054  
4055  // ringRecvNonblock polls once. Returns (data, true) on message, (nil, true)
4056  // if empty but open, (nil, false) if closed+empty.
4057  // Allocates a buffer of exactly the right size for the message.
4058  func ringRecvNonblock(ring unsafe.Pointer) (data []byte, ok bool) {
4059  	pn := cRingPeekLen(ring)
4060  	if pn == 0 {
4061  		return nil, true // empty but open
4062  	}
4063  	if pn < 0 {
4064  		return nil, false // closed+empty
4065  	}
4066  	buf := []byte{:pn}
4067  	n := cRingRecv(ring, unsafe.Pointer(unsafe.SliceData(buf)), pn)
4068  	if n > 0 {
4069  		return buf[:n], true
4070  	}
4071  	if n < 0 {
4072  		return nil, false
4073  	}
4074  	return nil, true
4075  }
4076  
4077  func ringSendInt32(ring unsafe.Pointer, v int32) (ok bool) {
4078  	var b [4]byte
4079  	b[0] = byte(v >> 24)
4080  	b[1] = byte(v >> 16)
4081  	b[2] = byte(v >> 8)
4082  	b[3] = byte(v)
4083  	return ringSend(ring, b[:])
4084  }
4085  
4086  func ringRecvInt32(ring unsafe.Pointer) (v int32, ok bool) {
4087  	d, rok := ringRecv(ring)
4088  	if !rok || len(d) < 4 {
4089  		return -1, false
4090  	}
4091  	v = int32(d[0])<<24 | int32(d[1])<<16 | int32(d[2])<<8 | int32(d[3])
4092  	return v, true
4093  }
4094  
4095  // --- Package metadata serialization ---
4096  // Text format: newline-separated fields. All strings are raw UTF-8.
4097  // Layout:
4098  //   numPkgs\n
4099  //   triple\n clang\n tmpdir\n cacheBase\n root\n modPrefix\n modDir\n
4100  //   for each pkg: path\n name\n dir\n version\n
4101  //     numFiles\n file1\n ... numCfiles\n cf1\n ... numBcfiles\n bf1\n ...
4102  //     numImports\n imp1\n ...
4103  
4104  func serializePkgMeta(pkgs []*pkgInfo, triple, clang, tmpdir, cacheBase, root, modPrefix, modDir string) (data []byte) {
4105  	var b []byte
4106  	b = b | simpleItoa(len(pkgs))
4107  	push(b, '\n')
4108  	b = b | triple
4109  	push(b, '\n')
4110  	b = b | clang
4111  	push(b, '\n')
4112  	b = b | tmpdir
4113  	push(b, '\n')
4114  	b = b | cacheBase
4115  	push(b, '\n')
4116  	b = b | root
4117  	push(b, '\n')
4118  	b = b | modPrefix
4119  	push(b, '\n')
4120  	b = b | modDir
4121  	push(b, '\n')
4122  	for _, p := range pkgs {
4123  		b = b | p.path
4124  		push(b, '\n')
4125  		b = b | p.name
4126  		push(b, '\n')
4127  		b = b | p.dir
4128  		push(b, '\n')
4129  		b = b | p.version
4130  		push(b, '\n')
4131  		b = b | simpleItoa(len(p.files))
4132  		push(b, '\n')
4133  		for _, f := range p.files {
4134  			b = b | f
4135  			push(b, '\n')
4136  		}
4137  		b = b | simpleItoa(len(p.cfiles))
4138  		push(b, '\n')
4139  		for _, f := range p.cfiles {
4140  			b = b | f
4141  			push(b, '\n')
4142  		}
4143  		b = b | simpleItoa(len(p.bcfiles))
4144  		push(b, '\n')
4145  		for _, f := range p.bcfiles {
4146  			b = b | f
4147  			push(b, '\n')
4148  		}
4149  		b = b | simpleItoa(len(p.imports))
4150  		push(b, '\n')
4151  		for _, im := range p.imports {
4152  			b = b | im
4153  			push(b, '\n')
4154  		}
4155  	}
4156  	return b
4157  }
4158  
4159  // deserLine reads the next line from data[pos:], returns it and advances pos.
4160  func deserLine(data []byte, pos int32) (line string, next int32) {
4161  	start := pos
4162  	for pos < int32(len(data)) && data[pos] != '\n' {
4163  		pos++
4164  	}
4165  	line = string(data[start:pos])
4166  	if pos < int32(len(data)) {
4167  		pos++ // skip \n
4168  	}
4169  	return line, pos
4170  }
4171  
4172  func deserInt(data []byte, pos int32) (v int32, next int32) {
4173  	s, np := deserLine(data, pos)
4174  	return parseInt32(s), np
4175  }
4176  
4177  type workerMeta struct {
4178  	pkgs      []*pkgInfo
4179  	triple    string
4180  	clang     string
4181  	tmpdir    string
4182  	cacheBase string
4183  	root      string
4184  	modPrefix string
4185  	modDir    string
4186  }
4187  
4188  func deserializePkgMeta(data []byte) (m *workerMeta) {
4189  	pos := int32(0)
4190  	numPkgs, pos := deserInt(data, pos)
4191  	m = &workerMeta{}
4192  	m.triple, pos = deserLine(data, pos)
4193  	m.clang, pos = deserLine(data, pos)
4194  	m.tmpdir, pos = deserLine(data, pos)
4195  	m.cacheBase, pos = deserLine(data, pos)
4196  	m.root, pos = deserLine(data, pos)
4197  	m.modPrefix, pos = deserLine(data, pos)
4198  	m.modDir, pos = deserLine(data, pos)
4199  	m.pkgs = []*pkgInfo{:numPkgs}
4200  	for i := int32(0); i < numPkgs; i++ {
4201  		p := &pkgInfo{}
4202  		p.path, pos = deserLine(data, pos)
4203  		p.name, pos = deserLine(data, pos)
4204  		p.dir, pos = deserLine(data, pos)
4205  		p.version, pos = deserLine(data, pos)
4206  		nf, np := deserInt(data, pos)
4207  		pos = np
4208  		if nf > 0 {
4209  			p.files = []string{:nf}
4210  			for j := int32(0); j < nf; j++ {
4211  				p.files[j], pos = deserLine(data, pos)
4212  			}
4213  		}
4214  		nc, npc := deserInt(data, pos)
4215  		pos = npc
4216  		if nc > 0 {
4217  			p.cfiles = []string{:nc}
4218  			for j := int32(0); j < nc; j++ {
4219  				p.cfiles[j], pos = deserLine(data, pos)
4220  			}
4221  		}
4222  		nb, npb := deserInt(data, pos)
4223  		pos = npb
4224  		if nb > 0 {
4225  			p.bcfiles = []string{:nb}
4226  			for j := int32(0); j < nb; j++ {
4227  				p.bcfiles[j], pos = deserLine(data, pos)
4228  			}
4229  		}
4230  		ni, npi := deserInt(data, pos)
4231  		pos = npi
4232  		if ni > 0 {
4233  			p.imports = []string{:ni}
4234  			for j := int32(0); j < ni; j++ {
4235  				p.imports[j], pos = deserLine(data, pos)
4236  			}
4237  		}
4238  		m.pkgs[i] = p
4239  	}
4240  	return m
4241  }
4242  
4243  // --- Worker entry (runs in a new thread) ---
4244  // Receives metadata via ringA, then receives job indices. Compiles each
4245  // package, writes .bc + .mxh to cache, sends result on ringB.
4246  // ringA/ringB pointers are passed as a 2-element unsafe.Pointer array.
4247  
4248  type workerRings struct {
4249  	ringA unsafe.Pointer // parent -> worker (jobs)
4250  	ringB unsafe.Pointer // worker -> parent (results)
4251  }
4252  
4253  //export mxc_worker_entry
4254  func workerEntryExport(arg unsafe.Pointer) {
4255  	// Initialize this thread's heap and package state (TLS globals start zeroed).
4256  	// Must happen before any Moxie allocation. allocateHeap() mmaps a fresh
4257  	// region, initHeap() sets the bump pointer, initAll() runs package inits.
4258  	runtime.InitCShared()
4259  	initBuildState()
4260  	initCompileCtx()
4261  
4262  	wr := (*workerRings)(arg)
4263  	ringA := wr.ringA
4264  	ringB := wr.ringB
4265  
4266  	metaData, metaOk := ringRecv(ringA)
4267  	if !metaOk {
4268  		return
4269  	}
4270  	meta := deserializePkgMeta(metaData)
4271  	metaData = nil
4272  
4273  	// Set up module context from metadata
4274  	bst.globalModPrefix = meta.modPrefix
4275  	bst.globalModDir = meta.modDir
4276  	bst.quietDiscover = true
4277  
4278  	// Set target globals from triple (needed by registerBuiltins for uintptr sizing)
4279  	if len(meta.triple) >= 4 && meta.triple[:4] == "wasm" {
4280  		mxutil.SetTarget("js", "wasm")
4281  	} else {
4282  		mxutil.SetTarget("linux", "amd64")
4283  	}
4284  
4285  	// Initialize this thread's compiler state (TLS globals are zeroed)
4286  	registerBuiltins()
4287  
4288  	// Build idxByPath for this thread's copy
4289  	idxByPath := map[string]int32{}
4290  	for i := int32(0); i < int32(len(meta.pkgs)); i++ {
4291  		idxByPath[meta.pkgs[i].path] = i
4292  	}
4293  
4294  	// Compute hashes for all packages (worker's own bst.pkgKeyMemo)
4295  	hashes := []string{:len(meta.pkgs)}
4296  	for i := int32(0); i < int32(len(meta.pkgs)); i++ {
4297  		hashes[i] = pkgHash(meta.pkgs[i])
4298  	}
4299  
4300  	// Work loop: receive job index, compile, send result
4301  	for {
4302  		idx, ok := ringRecvInt32(ringA)
4303  		if !ok {
4304  			break // ring closed, shutdown
4305  		}
4306  		if idx < 0 || idx >= int32(len(meta.pkgs)) {
4307  			workerSendResult(ringB, idx, "invalid package index")
4308  			continue
4309  		}
4310  		pkg := meta.pkgs[idx]
4311  		errMsg := workerCompilePkg(pkg, hashes[idx], meta, idxByPath, hashes)
4312  		workerSendResult(ringB, idx, errMsg)
4313  	}
4314  }
4315  
4316  // workerSendResult sends idx + error text on ringB.
4317  // Empty errMsg = success. Non-empty = error description.
4318  // Wire format: [idx:4 BE] [errlen:4 BE] [error text]
4319  func workerSendResult(ring unsafe.Pointer, idx int32, errMsg string) {
4320  	elen := int32(len(errMsg))
4321  	msg := []byte{:8 + elen}
4322  	msg[0] = byte(idx >> 24)
4323  	msg[1] = byte(idx >> 16)
4324  	msg[2] = byte(idx >> 8)
4325  	msg[3] = byte(idx)
4326  	msg[4] = byte(elen >> 24)
4327  	msg[5] = byte(elen >> 16)
4328  	msg[6] = byte(elen >> 8)
4329  	msg[7] = byte(elen)
4330  	for i := int32(0); i < elen; i++ {
4331  		msg[8+i] = errMsg[i]
4332  	}
4333  	ringSend(ring, msg)
4334  }
4335  
4336  // workerLoadDeps recursively loads .mxh files for a package's imports,
4337  // ensuring transitive deps are loaded before the package that references them.
4338  func workerLoadDeps(pkg *pkgInfo, meta *workerMeta, idxByPath map[string]int32, hashes []string) (errMsg string) {
4339  	for _, imp := range pkg.imports {
4340  		di, ok := idxByPath[imp]
4341  		if !ok {
4342  			continue
4343  		}
4344  		dep := meta.pkgs[di]
4345  		compilePath := dep.path
4346  		if compilePath == "." || compilePath == ".." || mxutil.HasPrefix(compilePath, "/") || mxutil.HasPrefix(compilePath, "./") || mxutil.HasPrefix(compilePath, "../") {
4347  			compilePath = dep.name
4348  		}
4349  		if cctx.universe.Registry[compilePath] != nil {
4350  			continue
4351  		}
4352  		// Recursively load this dep's deps first
4353  		if depErr := workerLoadDeps(dep, meta, idxByPath, hashes); depErr != "" {
4354  			return depErr
4355  		}
4356  		depHash := hashes[di]
4357  		cachedMxh := cachedMxhPath(meta.cacheBase, dep.path, depHash, dep.version)
4358  		if !loadMxh(cachedMxh) {
4359  			return "missing dep .mxh for " | dep.path
4360  		}
4361  		src := concatSources(dep.dir, dep.files)
4362  		ScanGenericDecls(src, compilePath)
4363  	}
4364  	return ""
4365  }
4366  
4367  // workerCompilePkg compiles one package in the worker thread. All deps must
4368  // already have .mxh in cache. Returns empty string on success, error text on failure.
4369  func workerCompilePkg(pkg *pkgInfo, hash string, meta *workerMeta, idxByPath map[string]int32, hashes []string) (errMsg string) {
4370  	// Load all dependency .mxh from cache (recursively, transitive deps first)
4371  	if loadErr := workerLoadDeps(pkg, meta, idxByPath, hashes); loadErr != "" {
4372  		return loadErr
4373  	}
4374  
4375  	compilePath := pkg.path
4376  	if compilePath == "." || compilePath == ".." || mxutil.HasPrefix(compilePath, "/") || mxutil.HasPrefix(compilePath, "./") || mxutil.HasPrefix(compilePath, "../") {
4377  		compilePath = pkg.name
4378  	}
4379  
4380  	mxutil.WriteStr(2, "  compile " | pkg.path | "\n")
4381  	src := concatSources(pkg.dir, pkg.files)
4382  	cctx.compilePkgDir = pkg.dir
4383  	ir := CompileToIR(src, compilePath, meta.triple)
4384  	if len(ir) > 0 && ir[0] == ';' {
4385  		return remapErrorLines(ir)
4386  	}
4387  	if len(ir) == 0 {
4388  		return "empty IR for " | pkg.path
4389  	}
4390  
4391  	llFile := mxutil.JoinPath(meta.tmpdir, safeName(pkg.path) | ".ll")
4392  	writeFile(llFile, []byte(ir), 0644)
4393  
4394  	bcFile := mxutil.JoinPath(meta.tmpdir, safeName(pkg.path) | ".bc")
4395  	rc := run([]string{meta.clang, "--target=" | meta.triple, "-emit-llvm", "-c", llFile, "-o", bcFile})
4396  	if rc != 0 {
4397  		return "clang failed for " | pkg.path
4398  	}
4399  
4400  	// Cache .bc
4401  	cacheDir := mxutil.JoinPath(meta.cacheBase, safeName(pkg.path))
4402  	mkdirAll(cacheDir, 0755)
4403  	cached := cachedBcPath(meta.cacheBase, pkg.path, hash, pkg.version)
4404  	bcData, rok := mxutil.ReadFile(bcFile)
4405  	if rok {
4406  		writeFile(cached, bcData, 0644)
4407  	}
4408  
4409  	// Cache .mxh (already generated inside CompileToIR's arena)
4410  	if len(cctx.lastMxhData) > 0 {
4411  		cachedMxh := cachedMxhPath(meta.cacheBase, pkg.path, hash, pkg.version)
4412  		writeFile(cachedMxh, []byte(cctx.lastMxhData), 0644)
4413  		cctx.lastMxhData = ""
4414  	}
4415  
4416  	// Compile C files
4417  	for _, cf := range pkg.cfiles {
4418  		cPath := mxutil.JoinPath(pkg.dir, cf)
4419  		cbcFile := mxutil.JoinPath(meta.tmpdir, safeName(pkg.path) | "_" | cf | ".bc")
4420  		rc = run([]string{meta.clang, "--target=" | meta.triple, "-emit-llvm", "-c", cPath, "-o", cbcFile})
4421  		if rc != 0 {
4422  			return "clang failed for " | cf
4423  		}
4424  	}
4425  
4426  	return ""
4427  }
4428  
4429  // --- Worker info for the orchestrator ---
4430  
4431  type workerInfo struct {
4432  	ringA    unsafe.Pointer // parent -> worker
4433  	ringB    unsafe.Pointer // worker -> parent
4434  	handle   uint64
4435  	status   uint64
4436  	busy     bool
4437  	busyIdx  int32
4438  }
4439  
4440  // buildPkgsParallel spawns worker threads with ring buffer IPC. Workers
4441  // receive package metadata at startup, then job indices. Each worker
4442  // independently loads deps from cache, compiles, writes .bc + .mxh to cache.
4443  // No shared mutable state - this IS the Moxie spawn model.
4444  func buildPkgsParallel(pkgs []*pkgInfo, triple, clang, tmpdir, cacheBase string, forceRebuild bool, jobs int32) (bcs []string, hasRT bool) {
4445  	n := int32(len(pkgs))
4446  	done := []bool{:n}
4447  	needBuild := []bool{:n}
4448  	idxByPath := map[string]int32{}
4449  	for i := int32(0); i < n; i++ {
4450  		idxByPath[pkgs[i].path] = i
4451  	}
4452  
4453  	// Phase 1: check cache
4454  	for i := int32(0); i < n; i++ {
4455  		pkg := pkgs[i]
4456  		hash := pkgHash(pkg)
4457  		cached := cachedBcPath(cacheBase, pkg.path, hash, pkg.version)
4458  		cachedMxh := cachedMxhPath(cacheBase, pkg.path, hash, pkg.version)
4459  		bcOk := mxutil.FileExists(cached)
4460  		mxhOk := mxutil.FileExists(cachedMxh)
4461  		if bcOk && mxhOk && !forceRebuild {
4462  			done[i] = true
4463  			mxutil.WriteStr(2, "  cached " | pkg.path)
4464  			if pkg.version != "" {
4465  				mxutil.WriteStr(2, " @ " | pkg.version)
4466  			}
4467  			mxutil.WriteStr(2, "\n")
4468  		} else {
4469  			needBuild[i] = true
4470  		}
4471  	}
4472  
4473  	// Phase 1b: compute transitive dep indices for dispatch readiness.
4474  	// A package is ready when ALL transitive deps (not just direct) are done.
4475  	transDeps := [][]int32{:n}
4476  	for i := int32(0); i < n; i++ {
4477  		visited := map[int32]bool{}
4478  		stack := []int32{:0:8}
4479  		for _, imp := range pkgs[i].imports {
4480  			if j, ok := idxByPath[imp]; ok {
4481  				push(stack, j)
4482  			}
4483  		}
4484  		for len(stack) > 0 {
4485  			top := stack[len(stack)-1]
4486  			stack = stack[:len(stack)-1]
4487  			if visited[top] {
4488  				continue
4489  			}
4490  			visited[top] = true
4491  			for _, imp := range pkgs[top].imports {
4492  				if j, ok := idxByPath[imp]; ok && !visited[j] {
4493  					push(stack, j)
4494  				}
4495  			}
4496  		}
4497  		td := []int32{:0:len(visited)}
4498  		for j := range visited {
4499  			push(td, j)
4500  		}
4501  		transDeps[i] = td
4502  	}
4503  
4504  	// Phase 2: serialize metadata and spawn workers
4505  	root := getenv("MOXIEROOT")
4506  	if root == "" {
4507  		root = "."
4508  	}
4509  	meta := serializePkgMeta(pkgs, triple, clang, tmpdir, cacheBase, root,
4510  		bst.globalModPrefix, bst.globalModDir)
4511  
4512  	workers := []*workerInfo{:jobs}
4513  	for wi := int32(0); wi < jobs; wi++ {
4514  		w := &workerInfo{}
4515  		w.ringA = cRingCreate(262144) // 256KB jobs+metadata
4516  		w.ringB = cRingCreate(262144) // 256KB results+errors
4517  		if w.ringA == nil || w.ringB == nil {
4518  			fatal("ring_create failed for worker " | simpleItoa(wi))
4519  		}
4520  
4521  		// Pack rings into arg struct (lives until thread reads it)
4522  		wr := &workerRings{ringA: w.ringA, ringB: w.ringB}
4523  
4524  		rc := cSpawnWorker(unsafe.Pointer(wr), unsafe.Pointer(&w.handle), unsafe.Pointer(&w.status))
4525  		if rc != 0 {
4526  			fatal("spawn_worker failed for worker " | simpleItoa(wi))
4527  		}
4528  
4529  		// Send metadata to worker via ringA
4530  		if !ringSend(w.ringA, meta) {
4531  			fatal("failed to send metadata to worker " | simpleItoa(wi))
4532  		}
4533  
4534  		workers[wi] = w
4535  	}
4536  	meta = nil
4537  
4538  	// Phase 3: three-check dispatch loop
4539  	failed := false
4540  	for {
4541  		allDone := true
4542  		for i := int32(0); i < n; i++ {
4543  			if needBuild[i] && !done[i] {
4544  				allDone = false
4545  				break
4546  			}
4547  		}
4548  		if allDone {
4549  			break
4550  		}
4551  
4552  		// On failure: close all ringAs to signal workers to stop, drain busy
4553  		if failed {
4554  			for wi := int32(0); wi < int32(len(workers)); wi++ {
4555  				w := workers[wi]
4556  				if w != nil {
4557  					cRingClose(w.ringA) // idempotent
4558  				}
4559  			}
4560  			anyBusy := false
4561  			for wi := int32(0); wi < int32(len(workers)); wi++ {
4562  				w := workers[wi]
4563  				if w != nil && w.busy {
4564  					anyBusy = true
4565  					break
4566  				}
4567  			}
4568  			if !anyBusy {
4569  				break
4570  			}
4571  			// Fall through to collection to drain in-flight results
4572  		}
4573  
4574  		if !failed {
4575  			// Check 1: death check
4576  			for wi := int32(0); wi < int32(len(workers)); wi++ {
4577  				w := workers[wi]
4578  				if w == nil {
4579  					continue
4580  				}
4581  				alive := cThreadAlive(unsafe.Pointer(&w.status))
4582  				if alive < 0 {
4583  					if w.busy {
4584  						mxutil.WriteStr(2, "  CRASHED worker " | simpleItoa(wi) | " compiling " | pkgs[w.busyIdx].path | "\n")
4585  					} else {
4586  						mxutil.WriteStr(2, "  CRASHED worker " | simpleItoa(wi) | "\n")
4587  					}
4588  					failed = true
4589  					workers[wi] = nil
4590  				}
4591  			}
4592  
4593  			// Check 2: dispatch jobs to idle workers
4594  			if !failed {
4595  				for wi := int32(0); wi < int32(len(workers)); wi++ {
4596  					w := workers[wi]
4597  					if w == nil || w.busy {
4598  						continue
4599  					}
4600  					// Find a dispatchable package
4601  					di := int32(-1)
4602  					for i := int32(0); i < n; i++ {
4603  						if done[i] || !needBuild[i] {
4604  							continue
4605  						}
4606  						// Check not already in flight
4607  						inFlight := false
4608  						for wj := int32(0); wj < int32(len(workers)); wj++ {
4609  							wk := workers[wj]
4610  							if wk != nil && wk.busy && wk.busyIdx == i {
4611  								inFlight = true
4612  								break
4613  							}
4614  						}
4615  						if inFlight {
4616  							continue
4617  						}
4618  						// Check all transitive deps done
4619  						ready := true
4620  						for _, j := range transDeps[i] {
4621  							if !done[j] {
4622  								ready = false
4623  								break
4624  							}
4625  						}
4626  						if ready {
4627  							di = i
4628  							break
4629  						}
4630  					}
4631  					if di < 0 {
4632  						continue
4633  					}
4634  					// Dispatch
4635  					if !ringSendInt32(w.ringA, di) {
4636  						mxutil.WriteStr(2, "  dispatch failed for " | pkgs[di].path | "\n")
4637  						failed = true
4638  						break
4639  					}
4640  					w.busy = true
4641  					w.busyIdx = di
4642  				}
4643  			}
4644  		}
4645  
4646  		// Check 3: collect results from busy workers (always runs - drains on failure)
4647  		progress := false
4648  		for wi := int32(0); wi < int32(len(workers)); wi++ {
4649  			w := workers[wi]
4650  			if w == nil || !w.busy {
4651  				continue
4652  			}
4653  			rd, rok := ringRecvNonblock(w.ringB)
4654  			if rd == nil {
4655  				if !rok {
4656  					mxutil.WriteStr(2, "  worker " | simpleItoa(wi) | " ringB closed\n")
4657  					failed = true
4658  				}
4659  				continue
4660  			}
4661  			if len(rd) < 8 {
4662  				continue
4663  			}
4664  			ridx := int32(rd[0])<<24 | int32(rd[1])<<16 | int32(rd[2])<<8 | int32(rd[3])
4665  			elen := int32(rd[4])<<24 | int32(rd[5])<<16 | int32(rd[6])<<8 | int32(rd[7])
4666  			w.busy = false
4667  			progress = true
4668  			if elen > 0 && int32(len(rd)) >= 8+elen {
4669  				errText := string(rd[8 : 8+elen])
4670  				mxutil.WriteStr(2, errText)
4671  				mxutil.WriteStr(2, "mxc: compile error for " | pkgs[ridx].path | "\n")
4672  				failed = true
4673  			} else if elen > 0 {
4674  				mxutil.WriteStr(2, "mxc: compile error for " | pkgs[ridx].path | "\n")
4675  				failed = true
4676  			} else {
4677  				done[ridx] = true
4678  			}
4679  		}
4680  
4681  		// Join point
4682  		if !progress {
4683  			cUsleep(1000)
4684  		}
4685  	}
4686  
4687  	// Phase 4: shutdown - close rings, join threads
4688  	for wi := int32(0); wi < int32(len(workers)); wi++ {
4689  		w := workers[wi]
4690  		if w == nil {
4691  			continue
4692  		}
4693  		cRingClose(w.ringA)
4694  		cThreadJoin(unsafe.Pointer(&w.handle), unsafe.Pointer(&w.status))
4695  		cRingDestroy(w.ringA)
4696  		cRingDestroy(w.ringB)
4697  	}
4698  	workers = nil
4699  
4700  	if failed {
4701  		fatal("parallel build failed")
4702  	}
4703  	for i := int32(0); i < n; i++ {
4704  		if needBuild[i] && !done[i] {
4705  			fatal("dependency cycle: cannot schedule " | pkgs[i].path)
4706  		}
4707  	}
4708  
4709  	// Phase 5: assemble .bc list
4710  	for i := int32(0); i < n; i++ {
4711  		pkg := pkgs[i]
4712  		if pkg.path == "runtime" {
4713  			hasRT = true
4714  		}
4715  		if needBuild[i] {
4716  			push(bcs, mxutil.JoinPath(tmpdir, safeName(pkg.path) | ".bc"))
4717  			for _, cf := range pkg.cfiles {
4718  				push(bcs, mxutil.JoinPath(tmpdir, safeName(pkg.path) | "_" | cf | ".bc"))
4719  			}
4720  		} else {
4721  			hash := pkgHash(pkg)
4722  			push(bcs, cachedBcPath(cacheBase, pkg.path, hash, pkg.version))
4723  			for _, cf := range pkg.cfiles {
4724  				cPath := mxutil.JoinPath(pkg.dir, cf)
4725  				cbcFile := mxutil.JoinPath(tmpdir, safeName(pkg.path) | "_" | cf | ".bc")
4726  				rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", cPath, "-o", cbcFile})
4727  				if rc == 0 {
4728  					push(bcs, cbcFile)
4729  				}
4730  			}
4731  		}
4732  		for _, bf := range pkg.bcfiles {
4733  			push(bcs, mxutil.JoinPath(pkg.dir, bf))
4734  		}
4735  	}
4736  	return bcs, hasRT
4737  }
4738  
4739  func cmdBuildRuntime() {
4740  	root := getenv("MOXIEROOT")
4741  	if root == "" {
4742  		root = "."
4743  	}
4744  	triple := "x86_64-unknown-linux-musleabihf"
4745  	clang := "clang-22"
4746  	sysroot := mxutil.JoinPath(root, "sysroot")
4747  	tmpdir := "/tmp/mxc-rt-build"
4748  	mkdirAll(tmpdir, 0755)
4749  
4750  	registerBuiltins()
4751  
4752  	rtPkgs := []string{"internal/task", "runtime"}
4753  	var bcFiles []string
4754  	for _, pkg := range rtPkgs {
4755  		dir := mxutil.JoinPath(root, mxutil.JoinPath("src", pkg))
4756  		info := discoverPkg(pkg, root)
4757  		if info == nil || len(info.files) == 0 {
4758  			mxutil.WriteStr(2, "  skip " | pkg | " (no files)\n")
4759  			continue
4760  		}
4761  		mxutil.WriteStr(2, "  compile " | pkg | "\n")
4762  		src := concatSources(dir, info.files)
4763  		ir := compileSource(src, pkg, triple, dir)
4764  		llFile := mxutil.JoinPath(tmpdir, safeName(pkg) | ".ll")
4765  		writeFile(llFile, []byte(ir), 0644)
4766  		bcFile := mxutil.JoinPath(tmpdir, safeName(pkg) | ".bc")
4767  		rc := run([]string{clang, "--target=" | triple, "-emit-llvm", "-c", llFile, "-o", bcFile})
4768  		if rc != 0 {
4769  			fatal("clang failed for " | pkg)
4770  		}
4771  		push(bcFiles, bcFile)
4772  	}
4773  	if len(bcFiles) == 0 {
4774  		fatal("no runtime packages compiled")
4775  	}
4776  	outBc := mxutil.JoinPath(sysroot, "runtime_go.bc")
4777  	if len(bcFiles) == 1 {
4778  		data, ok := mxutil.ReadFile(bcFiles[0])
4779  		if !ok {
4780  			fatal("cannot read " | bcFiles[0])
4781  		}
4782  		writeFile(outBc, data, 0644)
4783  	} else {
4784  		linkArgs := []string{"llvm-link-22", "-o", outBc}
4785  		for _, bf := range bcFiles {
4786  			push(linkArgs, bf)
4787  		}
4788  		rc := run(linkArgs)
4789  		if rc != 0 {
4790  			fatal("llvm-link failed merging runtime")
4791  		}
4792  	}
4793  	mxutil.WriteStr(2, "  -> " | outBc | "\n")
4794  }
4795