modasm.mx raw

   1  package iskra
   2  
   3  import (
   4  	"bytes"
   5  	"os"
   6  	"path/filepath"
   7  )
   8  
   9  func ExtractInitAndStrings(data []byte, pkg string) (initFuncs [][]byte, stringGlobals []byte, closures [][]byte) {
  10  	lines := bytes.Split(data, []byte("\n"))
  11  	prefix := []byte(pkg | ".")
  12  	initMarker := []byte("@" | pkg | ".init")
  13  	closurePrefix := []byte("@\"" | pkg | ".")
  14  	closureBare := []byte("@" | pkg | ".")
  15  	i := 0
  16  	for i < len(lines) {
  17  		trimmed := bytes.TrimSpace(lines[i])
  18  		if len(trimmed) == 0 {
  19  			i++
  20  			continue
  21  		}
  22  		if bytes.HasPrefix(trimmed, []byte("define ")) {
  23  			isInit := bytes.Contains(trimmed, initMarker)
  24  			isClosure := !isInit && bytes.Contains(trimmed, []byte("$")) &&
  25  				(bytes.Contains(trimmed, closurePrefix) || bytes.Contains(trimmed, closureBare))
  26  			if isInit || isClosure {
  27  				var funcBody []byte
  28  				depth := 0
  29  				for i < len(lines) {
  30  					funcBody = append(funcBody, removeDbgRefs(lines[i])...)
  31  					funcBody = append(funcBody, '\n')
  32  					for _, ch := range string(lines[i]) {
  33  						if ch == '{' {
  34  							depth++
  35  						} else if ch == '}' {
  36  							depth--
  37  						}
  38  					}
  39  					i++
  40  					if depth == 0 {
  41  						break
  42  					}
  43  				}
  44  				stripped := StripDebugMetadata(funcBody)
  45  				if isInit {
  46  					initFuncs = append(initFuncs, stripped)
  47  				} else {
  48  					closures = append(closures, stripped)
  49  				}
  50  				continue
  51  			}
  52  		}
  53  		if trimmed[0] == '@' && bytes.Contains(trimmed, []byte("$string")) && bytes.HasPrefix(trimmed[1:], prefix) {
  54  			stringGlobals = append(stringGlobals, removeDbgRefs(trimmed)...)
  55  			stringGlobals = append(stringGlobals, '\n')
  56  		}
  57  		i++
  58  	}
  59  	return
  60  }
  61  
  62  func extractIRPkgPrefix(data []byte) string {
  63  	idx := bytes.Index(data, []byte("define "))
  64  	if idx < 0 {
  65  		return ""
  66  	}
  67  	line := data[idx:]
  68  	nl := bytes.IndexByte(line, '\n')
  69  	if nl >= 0 {
  70  		line = line[:nl]
  71  	}
  72  	atIdx := bytes.IndexByte(line, '@')
  73  	if atIdx < 0 {
  74  		return ""
  75  	}
  76  	rest := line[atIdx+1:]
  77  	if len(rest) > 0 && rest[0] == '"' {
  78  		rest = rest[1:]
  79  	}
  80  	dotIdx := bytes.LastIndexByte(rest, '.')
  81  	if dotIdx < 0 {
  82  		return ""
  83  	}
  84  	candidate := rest[:dotIdx]
  85  	parenIdx := bytes.IndexByte(candidate, '(')
  86  	if parenIdx >= 0 {
  87  		return ""
  88  	}
  89  	return string(candidate)
  90  }
  91  
  92  func ExtractModuleScaffold(data []byte) []byte {
  93  	lines := bytes.Split(data, []byte("\n"))
  94  	var out []byte
  95  	i := 0
  96  	for i < len(lines) {
  97  		trimmed := bytes.TrimSpace(lines[i])
  98  		if len(trimmed) > 0 && trimmed[0] == '^' {
  99  			i++
 100  			continue
 101  		}
 102  		if bytes.HasPrefix(trimmed, []byte("define ")) {
 103  			decl := defineToDecl(trimmed)
 104  			if len(decl) > 0 {
 105  				out = append(out, decl...)
 106  				out = append(out, '\n')
 107  			}
 108  			depth := 0
 109  			for i < len(lines) {
 110  				for _, ch := range string(lines[i]) {
 111  					if ch == '{' {
 112  						depth++
 113  					} else if ch == '}' {
 114  						depth--
 115  					}
 116  				}
 117  				i++
 118  				if depth == 0 {
 119  					break
 120  				}
 121  			}
 122  			continue
 123  		}
 124  		out = append(out, removeDbgRefs(lines[i])...)
 125  		out = append(out, '\n')
 126  		i++
 127  	}
 128  	return out
 129  }
 130  
 131  func defineToDecl(line []byte) []byte {
 132  	braceIdx := bytes.LastIndexByte(line, '{')
 133  	if braceIdx < 0 {
 134  		return nil
 135  	}
 136  	sig := bytes.TrimSpace(line[:braceIdx])
 137  	// strip "define" and any linkage/visibility qualifiers
 138  	rest := sig[len("define "):]
 139  	for {
 140  		if bytes.HasPrefix(rest, []byte("internal ")) {
 141  			rest = rest[len("internal "):]
 142  		} else if bytes.HasPrefix(rest, []byte("hidden ")) {
 143  			rest = rest[len("hidden "):]
 144  		} else if bytes.HasPrefix(rest, []byte("linkonce_odr ")) {
 145  			rest = rest[len("linkonce_odr "):]
 146  		} else if bytes.HasPrefix(rest, []byte("weak_odr ")) {
 147  			rest = rest[len("weak_odr "):]
 148  		} else if bytes.HasPrefix(rest, []byte("available_externally ")) {
 149  			rest = rest[len("available_externally "):]
 150  		} else {
 151  			break
 152  		}
 153  	}
 154  	parenClose := bytes.LastIndexByte(rest, ')')
 155  	if parenClose > 0 {
 156  		rest = rest[:parenClose+1]
 157  	}
 158  	rest = stripParamNames(rest)
 159  	var decl []byte
 160  	decl = append(decl, "declare "...)
 161  	decl = append(decl, rest...)
 162  	return decl
 163  }
 164  
 165  func stripParamNames(sig []byte) []byte {
 166  	openParen := bytes.IndexByte(sig, '(')
 167  	if openParen < 0 {
 168  		return sig
 169  	}
 170  	closeParen := bytes.LastIndexByte(sig, ')')
 171  	if closeParen < 0 {
 172  		return sig
 173  	}
 174  	prefix := sig[:openParen+1]
 175  	params := sig[openParen+1 : closeParen]
 176  	suffix := sig[closeParen:]
 177  
 178  	parts := bytes.Split(params, []byte(","))
 179  	var cleaned [][]byte
 180  	for _, p := range parts {
 181  		p = bytes.TrimSpace(p)
 182  		if len(p) == 0 {
 183  			continue
 184  		}
 185  		cleaned = append(cleaned, stripOneParamName(p))
 186  	}
 187  	var out []byte
 188  	out = append(out, prefix...)
 189  	for i, c := range cleaned {
 190  		if i > 0 {
 191  			out = append(out, ", "...)
 192  		}
 193  		out = append(out, c...)
 194  	}
 195  	out = append(out, suffix...)
 196  	return out
 197  }
 198  
 199  func stripOneParamName(param []byte) []byte {
 200  	if bytes.Equal(param, []byte("...")) {
 201  		return param
 202  	}
 203  	// param is like "ptr %context" or "i64 %n" or "ptr nocapture readonly %p"
 204  	// or just "ptr" or "i64"
 205  	// strip %name at the end
 206  	lastSpace := bytes.LastIndexByte(param, ' ')
 207  	if lastSpace < 0 {
 208  		return param
 209  	}
 210  	lastWord := param[lastSpace+1:]
 211  	if len(lastWord) > 0 && lastWord[0] == '%' {
 212  		return bytes.TrimSpace(param[:lastSpace])
 213  	}
 214  	return param
 215  }
 216  
 217  func AssembleModule(scaffolds [][]byte, funcs [][]byte, definedFuncs map[string]bool, externalize bool) []byte {
 218  	if len(scaffolds) == 0 {
 219  		var out []byte
 220  		for _, f := range funcs {
 221  			out = append(out, f...)
 222  			out = append(out, '\n')
 223  		}
 224  		return out
 225  	}
 226  
 227  	// First scaffold provides base (target triple, data layout)
 228  	var out []byte
 229  	seen := map[string]bool{}
 230  
 231  	for si, scaffold := range scaffolds {
 232  		lines := bytes.Split(scaffold, []byte("\n"))
 233  		for _, line := range lines {
 234  			trimmed := bytes.TrimSpace(line)
 235  			if len(trimmed) == 0 {
 236  				if si == 0 {
 237  					out = append(out, '\n')
 238  				}
 239  				continue
 240  			}
 241  
 242  			key := ""
 243  			if trimmed[0] == '%' && bytes.Contains(trimmed, []byte(" = type ")) {
 244  				eqIdx := bytes.Index(trimmed, []byte(" = type "))
 245  				if eqIdx > 0 {
 246  					key = "T:" | string(trimmed[:eqIdx])
 247  				}
 248  			} else if trimmed[0] == '@' {
 249  				gname := extractGlobalDefName(trimmed)
 250  				if gname != "" {
 251  					key = "G:" | gname
 252  				}
 253  			} else if bytes.HasPrefix(trimmed, []byte("declare ")) {
 254  				declName := ExtractAtName(trimmed)
 255  				if declName != "" {
 256  					funcName := NormalizeLLVMName(declName)
 257  					if definedFuncs != nil && definedFuncs[funcName] {
 258  						continue
 259  					}
 260  					key = "D:" | funcName
 261  				}
 262  			} else if bytes.HasPrefix(trimmed, []byte("source_filename")) || bytes.HasPrefix(trimmed, []byte("; ModuleID")) {
 263  				if si > 0 {
 264  					continue
 265  				}
 266  			} else if bytes.HasPrefix(trimmed, []byte("target ")) {
 267  				if si > 0 {
 268  					continue
 269  				}
 270  			}
 271  
 272  			// Skip debug metadata and attribute groups from non-primary scaffolds
 273  			if si > 0 {
 274  				if trimmed[0] == '!' {
 275  					continue
 276  				}
 277  				if bytes.HasPrefix(trimmed, []byte("attributes #")) {
 278  					continue
 279  				}
 280  			}
 281  
 282  			if key != "" {
 283  				if seen[key] {
 284  					continue
 285  				}
 286  				seen[key] = true
 287  			}
 288  
 289  			if externalize && trimmed[0] == '@' && bytes.Contains(trimmed, []byte(" global ")) {
 290  				gname := extractGlobalDefName(trimmed)
 291  				isAppGlobal := bytes.HasPrefix([]byte(gname), []byte("@main.")) ||
 292  					bytes.HasPrefix([]byte(gname), []byte("@\"main.")) ||
 293  					bytes.HasPrefix([]byte(gname), []byte("@\"main$"))
 294  				if isAppGlobal {
 295  					line = bytes.Replace(line, []byte(" internal global "), []byte(" global "), 1)
 296  					line = bytes.Replace(line, []byte(" internal unnamed_addr "), []byte(" unnamed_addr "), 1)
 297  					out = append(out, line...)
 298  					out = append(out, '\n')
 299  					continue
 300  				}
 301  				ext := externalizeGlobal(trimmed)
 302  				if len(ext) > 0 {
 303  					out = append(out, ext...)
 304  					out = append(out, '\n')
 305  					continue
 306  				}
 307  			}
 308  
 309  			out = append(out, line...)
 310  			out = append(out, '\n')
 311  		}
 312  	}
 313  
 314  	out = append(out, '\n')
 315  	for _, f := range funcs {
 316  		if externalize {
 317  			f = promoteInternalFuncs(f)
 318  		}
 319  		out = append(out, f...)
 320  		out = append(out, '\n')
 321  	}
 322  	return patchUndefinedRefs(out)
 323  }
 324  
 325  func externalizeGlobal(line []byte) []byte {
 326  	gname := extractGlobalDefName(line)
 327  	if gname == "" {
 328  		return nil
 329  	}
 330  	globalIdx := bytes.Index(line, []byte(" global "))
 331  	if globalIdx < 0 {
 332  		return nil
 333  	}
 334  	typeStart := globalIdx + len(" global ")
 335  	rest := line[typeStart:]
 336  	typeName := extractLLVMType(rest)
 337  	if typeName == "" {
 338  		return nil
 339  	}
 340  	var out []byte
 341  	out = append(out, gname...)
 342  	out = append(out, " = external global "...)
 343  	out = append(out, typeName...)
 344  	return out
 345  }
 346  
 347  func extractLLVMType(data []byte) string {
 348  	data = bytes.TrimSpace(data)
 349  	if len(data) == 0 {
 350  		return ""
 351  	}
 352  	if data[0] == '{' {
 353  		depth := 0
 354  		for i := 0; i < len(data); i++ {
 355  			if data[i] == '{' {
 356  				depth++
 357  			} else if data[i] == '}' {
 358  				depth--
 359  				if depth == 0 {
 360  					return string(data[:i+1])
 361  				}
 362  			}
 363  		}
 364  		return ""
 365  	}
 366  	if data[0] == '[' {
 367  		end := bytes.IndexByte(data, ']')
 368  		if end > 0 {
 369  			return string(data[:end+1])
 370  		}
 371  		return ""
 372  	}
 373  	if data[0] == '%' || data[0] == 'i' || bytes.HasPrefix(data, []byte("ptr")) || bytes.HasPrefix(data, []byte("float")) || bytes.HasPrefix(data, []byte("double")) {
 374  		for i := 0; i < len(data); i++ {
 375  			if data[i] == ' ' || data[i] == ',' {
 376  				return string(data[:i])
 377  			}
 378  		}
 379  		return string(data)
 380  	}
 381  	return ""
 382  }
 383  
 384  func promoteInternalFuncs(block []byte) []byte {
 385  	marker := []byte("define internal ")
 386  	repl := []byte("define ")
 387  	if !bytes.Contains(block, marker) {
 388  		return block
 389  	}
 390  	lines := bytes.Split(block, []byte("\n"))
 391  	var out []byte
 392  	for i, line := range lines {
 393  		trimmed := bytes.TrimSpace(line)
 394  		if bytes.HasPrefix(trimmed, marker) {
 395  			line = bytes.Replace(line, marker, repl, 1)
 396  		}
 397  		out = append(out, line...)
 398  		if i < len(lines)-1 {
 399  			out = append(out, '\n')
 400  		}
 401  	}
 402  	return out
 403  }
 404  
 405  func patchUndefinedRefs(module []byte) []byte {
 406  	defined := map[string]bool{}
 407  	lines := bytes.Split(module, []byte("\n"))
 408  	for _, line := range lines {
 409  		trimmed := bytes.TrimSpace(line)
 410  		if len(trimmed) == 0 {
 411  			continue
 412  		}
 413  		if bytes.HasPrefix(trimmed, []byte("define ")) {
 414  			name := NormalizeLLVMName(ExtractAtName(trimmed))
 415  			if name != "" {
 416  				defined[name] = true
 417  			}
 418  		} else if bytes.HasPrefix(trimmed, []byte("declare ")) {
 419  			name := NormalizeLLVMName(ExtractAtName(trimmed))
 420  			if name != "" {
 421  				defined[name] = true
 422  			}
 423  		} else if trimmed[0] == '@' {
 424  			gname := NormalizeLLVMName(extractGlobalDefName(trimmed))
 425  			if gname != "" {
 426  				defined[gname] = true
 427  			}
 428  		}
 429  	}
 430  
 431  	refs := map[string]bool{}
 432  	for _, line := range lines {
 433  		collectAtRefs(line, refs)
 434  	}
 435  
 436  	var stubs []byte
 437  	for ref := range refs {
 438  		if len(ref) > 5 && ref[1:5] == "llvm" {
 439  			continue
 440  		}
 441  		nref := NormalizeLLVMName(ref)
 442  		if !defined[nref] {
 443  			stubs = append(stubs, "declare void "...)
 444  			stubs = append(stubs, ref...)
 445  			stubs = append(stubs, "()\n"...)
 446  			defined[nref] = true
 447  		}
 448  	}
 449  	if len(stubs) == 0 {
 450  		return module
 451  	}
 452  	var out []byte
 453  	out = append(out, module...)
 454  	out = append(out, stubs...)
 455  	return out
 456  }
 457  
 458  func ExtractAtName(line []byte) string {
 459  	atIdx := bytes.IndexByte(line, '@')
 460  	if atIdx < 0 {
 461  		return ""
 462  	}
 463  	rest := line[atIdx:]
 464  	if len(rest) > 1 && rest[1] == '"' {
 465  		end := bytes.IndexByte(rest[2:], '"')
 466  		if end > 0 {
 467  			return string(rest[:end+3])
 468  		}
 469  		return ""
 470  	}
 471  	for i := 1; i < len(rest); i++ {
 472  		ch := rest[i]
 473  		if ch == '(' || ch == ' ' || ch == '\n' || ch == ',' {
 474  			return string(rest[:i])
 475  		}
 476  	}
 477  	return string(rest)
 478  }
 479  
 480  func extractGlobalDefName(line []byte) string {
 481  	if len(line) == 0 || line[0] != '@' {
 482  		return ""
 483  	}
 484  	if len(line) > 1 && line[1] == '"' {
 485  		end := bytes.IndexByte(line[2:], '"')
 486  		if end > 0 {
 487  			return string(line[:end+3])
 488  		}
 489  		return ""
 490  	}
 491  	spIdx := bytes.IndexByte(line, ' ')
 492  	if spIdx > 0 {
 493  		return string(line[:spIdx])
 494  	}
 495  	return ""
 496  }
 497  
 498  func collectAtRefs(line []byte, refs map[string]bool) {
 499  	pos := 0
 500  	for pos < len(line) {
 501  		idx := bytes.IndexByte(line[pos:], '@')
 502  		if idx < 0 {
 503  			break
 504  		}
 505  		abs := pos + idx
 506  		if abs > 0 && line[abs-1] != ' ' && line[abs-1] != ',' && line[abs-1] != '(' && line[abs-1] != '{' && line[abs-1] != '[' {
 507  			pos = abs + 1
 508  			continue
 509  		}
 510  		rest := line[abs:]
 511  		var name string
 512  		if len(rest) > 1 && rest[1] == '"' {
 513  			end := bytes.IndexByte(rest[2:], '"')
 514  			if end > 0 {
 515  				name = string(rest[:end+3])
 516  			}
 517  		} else {
 518  			valid := true
 519  			for i := 1; i < len(rest); i++ {
 520  				ch := rest[i]
 521  				if ch == '(' || ch == ' ' || ch == '\n' || ch == ',' || ch == ')' || ch == '}' || ch == ']' {
 522  					name = string(rest[:i])
 523  					break
 524  				}
 525  				if ch > 127 || (ch != '_' && ch != '.' && ch != '$' && ch != '-' && !(ch >= 'a' && ch <= 'z') && !(ch >= 'A' && ch <= 'Z') && !(ch >= '0' && ch <= '9')) {
 526  					valid = false
 527  					break
 528  				}
 529  			}
 530  			if valid && name == "" && len(rest) > 1 {
 531  				name = string(rest)
 532  			}
 533  		}
 534  		if len(name) > 1 && name != "@" {
 535  			refs[name] = true
 536  		}
 537  		pos = abs + len(name)
 538  		if pos <= abs {
 539  			pos = abs + 1
 540  		}
 541  	}
 542  }
 543  
 544  func FindRuntimeSources(moxieRoot string) (mxFiles, cFiles, sFiles []string) {
 545  	rtDir := filepath.Join(moxieRoot, "src", "runtime")
 546  	entries, err := os.ReadDir(rtDir)
 547  	if err != nil {
 548  		return
 549  	}
 550  	for _, e := range entries {
 551  		if e.IsDir() {
 552  			continue
 553  		}
 554  		name := e.Name()
 555  		full := filepath.Join(rtDir, name)
 556  		if bytes.HasSuffix([]byte(name), []byte(".mx")) {
 557  			if shouldSkipBuildTag(name) {
 558  				continue
 559  			}
 560  			mxFiles = append(mxFiles, full)
 561  		} else if bytes.HasSuffix([]byte(name), []byte(".c")) {
 562  			if shouldSkipCFile(name) {
 563  				continue
 564  			}
 565  			cFiles = append(cFiles, full)
 566  		} else if bytes.HasSuffix([]byte(name), []byte(".S")) {
 567  			if shouldSkipAsmFile(name) {
 568  				continue
 569  			}
 570  			sFiles = append(sFiles, full)
 571  		}
 572  	}
 573  
 574  	extraCDirs := []string{
 575  		filepath.Join(moxieRoot, "src", "internal", "internal", "futex"),
 576  		filepath.Join(moxieRoot, "src", "internal", "cpu"),
 577  		filepath.Join(moxieRoot, "src", "internal", "runtime", "syscall"),
 578  		filepath.Join(moxieRoot, "src", "syscall"),
 579  	}
 580  	for _, dir := range extraCDirs {
 581  		extras, err := os.ReadDir(dir)
 582  		if err != nil {
 583  			continue
 584  		}
 585  		for _, e := range extras {
 586  			if e.IsDir() {
 587  				continue
 588  			}
 589  			name := e.Name()
 590  			full := filepath.Join(dir, name)
 591  			if bytes.HasSuffix([]byte(name), []byte(".c")) && !shouldSkipCFile(name) {
 592  				cFiles = append(cFiles, full)
 593  			} else if bytes.HasSuffix([]byte(name), []byte(".S")) && !shouldSkipAsmFile(name) {
 594  				sFiles = append(sFiles, full)
 595  			}
 596  		}
 597  	}
 598  	return
 599  }
 600  
 601  func shouldSkipBuildTag(name string) bool {
 602  	skip := []string{
 603  		"_wasm.mx", "_arm64.mx", "_darwin.mx",
 604  		"_windows.mx", "_baremetal.mx",
 605  		"_test.mx",
 606  	}
 607  	for _, s := range skip {
 608  		if bytes.HasSuffix([]byte(name), []byte(s)) {
 609  			return true
 610  		}
 611  	}
 612  	skipExact := []string{
 613  		"gc_none.mx", "gc_leaking.mx", "gc_boehm.mx",
 614  		"gc_custom.mx", "gc_precise.mx",
 615  		"gc_stack_raw.mx",
 616  	}
 617  	for _, s := range skipExact {
 618  		if name == s {
 619  			return true
 620  		}
 621  	}
 622  	return false
 623  }
 624  
 625  func shouldSkipCFile(name string) bool {
 626  	if bytes.Contains([]byte(name), []byte("darwin")) {
 627  		return true
 628  	}
 629  	return false
 630  }
 631  
 632  func shouldSkipAsmFile(name string) bool {
 633  	if bytes.Contains([]byte(name), []byte("arm64")) {
 634  		return true
 635  	}
 636  	return false
 637  }
 638  
 639  
 640  func DiscoverMxFiles(dir string) ([]string, error) {
 641  	entries, err := os.ReadDir(dir)
 642  	if err != nil {
 643  		return nil, err
 644  	}
 645  	var files []string
 646  	for _, e := range entries {
 647  		if e.IsDir() {
 648  			continue
 649  		}
 650  		if bytes.HasSuffix([]byte(e.Name()), []byte(".mx")) {
 651  			if bytes.HasSuffix([]byte(e.Name()), []byte("_test.mx")) {
 652  				continue
 653  			}
 654  			files = append(files, filepath.Join(dir, e.Name()))
 655  		}
 656  	}
 657  	return files, nil
 658  }
 659  
 660  func ReadModulePath(dir string) string {
 661  	modFile := filepath.Join(dir, "moxie.mod")
 662  	data, err := os.ReadFile(modFile)
 663  	if err != nil {
 664  		return ""
 665  	}
 666  	lines := bytes.Split(data, []byte("\n"))
 667  	for _, line := range lines {
 668  		line = bytes.TrimSpace(line)
 669  		if bytes.HasPrefix(line, []byte("module ")) {
 670  			return string(line[7:])
 671  		}
 672  	}
 673  	return ""
 674  }
 675  
 676  type UnmatchedFunc struct {
 677  	SrcFile string
 678  	Name    string
 679  }
 680  
 681  type CompileResult struct {
 682  	Matched       int
 683  	Unmatched     int
 684  	IR            []byte
 685  	UnmatchedList []UnmatchedFunc
 686  	PkgRenames    map[string]bool
 687  }
 688  
 689  func FindRuntimeScaffold(t *Tree) []byte {
 690  	for i := range t.RecMeta {
 691  		m := &t.RecMeta[i]
 692  		if m.StageTag != StageIR || m.Kind != KindPkg {
 693  			continue
 694  		}
 695  		rec := t.db.GetRecord(uint32(i))
 696  		if rec == nil {
 697  			continue
 698  		}
 699  		form := FormFromRecord(rec, t.StringPool)
 700  		if form == "runtime.__module__" {
 701  			return t.GetContent(uint32(i))
 702  		}
 703  	}
 704  	return nil
 705  }
 706  
 707  func CompileFiles(t *Tree, srcFiles []string, pkgFilter string, outPkg string, externalize bool, modPath ...string) CompileResult {
 708  	var result CompileResult
 709  	var funcs [][]byte
 710  	var scaffolds [][]byte
 711  	definedFuncs := map[string]bool{}
 712  	seenPkgModule := map[uint32]bool{}
 713  
 714  	rtScaffold := FindRuntimeScaffold(t)
 715  	if len(rtScaffold) > 0 {
 716  		scaffolds = append(scaffolds, ExtractModuleScaffold(rtScaffold))
 717  	}
 718  
 719  	for _, srcFile := range srcFiles {
 720  		data, err := os.ReadFile(srcFile)
 721  		if err != nil {
 722  			continue
 723  		}
 724  		decls := SplitDecls(data)
 725  		for _, decl := range decls {
 726  			name := DeclName(decl)
 727  			if name == "" {
 728  				continue
 729  			}
 730  			dump := GenAST(decl)
 731  			if len(dump) == 0 {
 732  				continue
 733  			}
 734  			if !bytes.HasPrefix(dump, []byte("FuncDecl")) {
 735  				continue
 736  			}
 737  
 738  			var matches []uint32
 739  			if pkgFilter != "" {
 740  				matches = FindBySignatureFromASTInPkg(t, string(dump), pkgFilter)
 741  			} else {
 742  				matches = FindBySignatureFromAST(t, string(dump))
 743  			}
 744  			if len(matches) == 0 {
 745  				result.Unmatched++
 746  				result.UnmatchedList = append(result.UnmatchedList, UnmatchedFunc{SrcFile: srcFile, Name: name})
 747  				continue
 748  			}
 749  
 750  			RankMatches(t, matches, 0, name)
 751  
 752  			matchMeta := matches[0]
 753  			matchAST := t.GetContent(matchMeta)
 754  
 755  			// Find IR version via key-implicit cross-stage lookup:
 756  			// same name hash, StageIR, same branch.
 757  			var irTokens []uint32
 758  			var pkgModuleMeta uint32
 759  			if astKey, ok := t.db.RecKey[matchMeta]; ok {
 760  				branch := KindToBranch(t.RecMeta[matchMeta].Kind)
 761  				irKey := MakeCodeKey(StageIR, KeyHash(astKey))
 762  				irRecIdx := t.LookupRecIdx(branch, irKey)
 763  				if irRecIdx != NullLatticeRec {
 764  					irTokens = t.GetTokenSeq(irRecIdx)
 765  					// Find the package module: scan for KindPkg+StageIR entries.
 766  					pkgModuleMeta = findPkgModuleMeta(t, irRecIdx)
 767  				}
 768  			}
 769  			if len(irTokens) == 0 {
 770  				result.Unmatched++
 771  				result.UnmatchedList = append(result.UnmatchedList, UnmatchedFunc{SrcFile: srcFile, Name: name})
 772  				continue
 773  			}
 774  
 775  			if pkgModuleMeta != 0 && !seenPkgModule[pkgModuleMeta] {
 776  				seenPkgModule[pkgModuleMeta] = true
 777  				pkgIR := t.GetContent(pkgModuleMeta)
 778  				if len(pkgIR) > 0 {
 779  					scaffolds = append(scaffolds, ExtractModuleScaffold(pkgIR))
 780  					if len(modPath) > 0 && modPath[0] != "" {
 781  						rec := t.db.GetRecord(pkgModuleMeta)
 782  						if rec != nil {
 783  							modName := FormFromRecord(rec, t.StringPool)
 784  							modPkg := ""
 785  							dotIdx := bytes.LastIndexByte([]byte(modName), '.')
 786  							if dotIdx >= 0 {
 787  								modPkg = modName[:dotIdx]
 788  							}
 789  							if modPkg == outPkg {
 790  								inits, strGlobals, cls := ExtractInitAndStrings(pkgIR, outPkg)
 791  								for _, initF := range inits {
 792  									funcs = append(funcs, initF)
 793  									defName := extractDefineName(initF)
 794  									if defName != "" {
 795  										definedFuncs[defName] = true
 796  									}
 797  								}
 798  								for _, cl := range cls {
 799  									funcs = append(funcs, cl)
 800  									defName := extractDefineName(cl)
 801  									if defName != "" {
 802  										definedFuncs[defName] = true
 803  									}
 804  								}
 805  								if len(strGlobals) > 0 {
 806  									scaffolds = append(scaffolds, strGlobals)
 807  								}
 808  							}
 809  						}
 810  					}
 811  				}
 812  			}
 813  
 814  			irContent := t.Dict.Decode(irTokens)
 815  			templateIRName := extractIRFuncName(irContent)
 816  			if templateIRName == "" {
 817  				result.Unmatched++
 818  				result.UnmatchedList = append(result.UnmatchedList, UnmatchedFunc{SrcFile: srcFile, Name: name})
 819  				continue
 820  			}
 821  			var targetIRName string
 822  			if outPkg != "" {
 823  				targetIRName = buildTargetIRName(templateIRName, outPkg, name)
 824  			} else {
 825  				targetIRName = replaceLastComponent(templateIRName, name)
 826  			}
 827  
 828  			subst := BuildTokenSubst(t.Dict, string(matchAST), string(dump), templateIRName, targetIRName)
 829  			resultTokens := subst.Apply(irTokens)
 830  			decoded := subst.Decode(resultTokens)
 831  
 832  			if outPkg != "" && len(modPath) > 0 && modPath[0] != "" {
 833  				templatePkg := extractPkgPrefix(templateIRName)
 834  				if templatePkg == modPath[0] && templatePkg != outPkg {
 835  					if result.PkgRenames == nil {
 836  						result.PkgRenames = map[string]bool{}
 837  					}
 838  					result.PkgRenames[templatePkg] = true
 839  				}
 840  			}
 841  
 842  			decoded = StripDebugMetadata(decoded)
 843  
 844  			defName := extractDefineName(decoded)
 845  			if defName != "" {
 846  				if definedFuncs[defName] {
 847  					continue
 848  				}
 849  				definedFuncs[defName] = true
 850  			}
 851  			funcs = append(funcs, decoded)
 852  			result.Matched++
 853  		}
 854  	}
 855  
 856  	if len(funcs) > 0 {
 857  		if outPkg != "" && len(result.PkgRenames) > 0 {
 858  			for oldPkg := range result.PkgRenames {
 859  				old := []byte(oldPkg | ".")
 860  				new := []byte(outPkg | ".")
 861  				for i := range scaffolds {
 862  					scaffolds[i] = bytes.ReplaceAll(scaffolds[i], old, new)
 863  				}
 864  				for i := range funcs {
 865  					funcs[i] = bytes.ReplaceAll(funcs[i], old, new)
 866  				}
 867  				newDefined := map[string]bool{}
 868  				for k, v := range definedFuncs {
 869  					nk := string(bytes.ReplaceAll([]byte(k), old, new))
 870  					newDefined[nk] = v
 871  				}
 872  				definedFuncs = newDefined
 873  			}
 874  		}
 875  		result.IR = AssembleModule(scaffolds, funcs, definedFuncs, externalize)
 876  		if externalize && outPkg != "" {
 877  			result.IR = deexternalizePkg(result.IR, outPkg)
 878  		}
 879  	}
 880  	return result
 881  }
 882  
 883  func deexternalizePkg(module []byte, pkg string) []byte {
 884  	prefix := []byte("@" | pkg | ".")
 885  	qprefix := []byte("@\"" | pkg | ".")
 886  	lines := bytes.Split(module, []byte("\n"))
 887  	var out []byte
 888  	for _, line := range lines {
 889  		trimmed := bytes.TrimSpace(line)
 890  		if len(trimmed) > 0 && trimmed[0] == '@' &&
 891  			(bytes.HasPrefix(trimmed, prefix) || bytes.HasPrefix(trimmed, qprefix)) &&
 892  			bytes.Contains(trimmed, []byte(" = external global ")) {
 893  			extIdx := bytes.Index(line, []byte(" = external global "))
 894  			typePart := bytes.TrimSpace(line[extIdx+len(" = external global "):])
 895  			var newLine []byte
 896  			newLine = append(newLine, line[:extIdx]...)
 897  			newLine = append(newLine, " = global "...)
 898  			newLine = append(newLine, typePart...)
 899  			newLine = append(newLine, " zeroinitializer"...)
 900  			line = newLine
 901  		}
 902  		out = append(out, line...)
 903  		out = append(out, '\n')
 904  	}
 905  	return out
 906  }
 907  
 908  func StripDebugMetadata(ir []byte) []byte {
 909  	lines := bytes.Split(ir, []byte("\n"))
 910  	var out []byte
 911  	for _, line := range lines {
 912  		// Remove !dbg !NNN references
 913  		cleaned := removeDbgRefs(line)
 914  		// Skip #dbg_value lines
 915  		trimmed := bytes.TrimSpace(cleaned)
 916  		if bytes.HasPrefix(trimmed, []byte("#dbg_value")) || bytes.HasPrefix(trimmed, []byte("#dbg_declare")) {
 917  			continue
 918  		}
 919  		out = append(out, cleaned...)
 920  		out = append(out, '\n')
 921  	}
 922  	return out
 923  }
 924  
 925  func removeDbgRefs(line []byte) []byte {
 926  	for {
 927  		idx := bytes.Index(line, []byte(", !dbg !"))
 928  		if idx < 0 {
 929  			idx = bytes.Index(line, []byte(" !dbg !"))
 930  			if idx < 0 {
 931  				break
 932  			}
 933  		}
 934  		end := idx + 7 // past "!dbg !"
 935  		if line[idx] == ',' {
 936  			end = idx + 8
 937  		}
 938  		for end < len(line) && line[end] >= '0' && line[end] <= '9' {
 939  			end++
 940  		}
 941  		var newLine []byte
 942  		newLine = append(newLine, line[:idx]...)
 943  		newLine = append(newLine, line[end:]...)
 944  		line = newLine
 945  	}
 946  	return line
 947  }
 948  
 949  func extractDefineName(ir []byte) string {
 950  	idx := bytes.Index(ir, []byte("define "))
 951  	if idx < 0 {
 952  		return ""
 953  	}
 954  	atIdx := bytes.IndexByte(ir[idx:], '@')
 955  	if atIdx < 0 {
 956  		return ""
 957  	}
 958  	rest := ir[idx+atIdx:]
 959  	if len(rest) > 1 && rest[1] == '"' {
 960  		closeQuote := bytes.IndexByte(rest[2:], '"')
 961  		if closeQuote < 0 {
 962  			return ""
 963  		}
 964  		return NormalizeLLVMName(string(rest[:closeQuote+3]))
 965  	}
 966  	parenIdx := bytes.IndexByte(rest, '(')
 967  	if parenIdx < 0 {
 968  		return ""
 969  	}
 970  	return NormalizeLLVMName(string(rest[:parenIdx]))
 971  }
 972  
 973  func NormalizeLLVMName(name string) string {
 974  	if len(name) > 2 && name[0] == '@' && name[1] == '"' && name[len(name)-1] == '"' {
 975  		return "@" | name[2:len(name)-1]
 976  	}
 977  	return name
 978  }
 979  
 980  func extractIRFuncName(ir []byte) string {
 981  	firstLine := ir
 982  	nl := bytes.IndexByte(ir, '\n')
 983  	if nl >= 0 {
 984  		firstLine = ir[:nl]
 985  	}
 986  	idx := bytes.Index(firstLine, []byte("@\""))
 987  	if idx >= 0 {
 988  		rest := firstLine[idx+2:]
 989  		end := bytes.IndexByte(rest, '"')
 990  		if end > 0 {
 991  			return string(rest[:end])
 992  		}
 993  	}
 994  	idx = bytes.IndexByte(firstLine, '@')
 995  	if idx < 0 {
 996  		return ""
 997  	}
 998  	rest := firstLine[idx+1:]
 999  	end := bytes.IndexByte(rest, '(')
1000  	if end < 0 {
1001  		return ""
1002  	}
1003  	return string(rest[:end])
1004  }
1005  
1006  func buildTargetIRName(templateIRName, outPkg, name string) string {
1007  	if len(templateIRName) > 2 && templateIRName[0] == '(' {
1008  		closeIdx := -1
1009  		for i := 0; i < len(templateIRName); i++ {
1010  			if templateIRName[i] == ')' {
1011  				closeIdx = i
1012  				break
1013  			}
1014  		}
1015  		if closeIdx > 0 {
1016  			inner := templateIRName[1:closeIdx]
1017  			ptr := ""
1018  			if len(inner) > 0 && inner[0] == '*' {
1019  				ptr = "*"
1020  				inner = inner[1:]
1021  			}
1022  			lastDot := -1
1023  			for i := len(inner) - 1; i >= 0; i-- {
1024  				if inner[i] == '.' {
1025  					lastDot = i
1026  					break
1027  				}
1028  			}
1029  			typeName := inner
1030  			if lastDot >= 0 {
1031  				typeName = inner[lastDot+1:]
1032  			}
1033  			return "(" | ptr | outPkg | "." | typeName | ")." | name
1034  		}
1035  	}
1036  	return outPkg | "." | name
1037  }
1038  
1039  func extractPkgPrefix(irName string) string {
1040  	dot := -1
1041  	for i := len(irName) - 1; i >= 0; i-- {
1042  		if irName[i] == '.' {
1043  			dot = i
1044  			break
1045  		}
1046  	}
1047  	if dot < 0 {
1048  		return ""
1049  	}
1050  	return irName[:dot]
1051  }
1052  
1053  func replaceLastComponent(qualName, newFunc string) string {
1054  	dot := -1
1055  	for i := len(qualName) - 1; i >= 0; i-- {
1056  		if qualName[i] == '.' {
1057  			dot = i
1058  			break
1059  		}
1060  	}
1061  	if dot < 0 {
1062  		return newFunc
1063  	}
1064  	return qualName[:dot+1] | newFunc
1065  }
1066  
1067  // findPkgModuleMeta finds the package module meta index (KindPkg+StageIR)
1068  // by scanning all RecMeta entries. The module entry name contains ".__module__".
1069  // Returns 0 if not found (0 is NullIndex convention here for uint32 CostMap keying).
1070  func findPkgModuleMeta(t *Tree, nearRecIdx uint32) uint32 {
1071  	for i := range t.RecMeta {
1072  		m := &t.RecMeta[i]
1073  		if m.StageTag != StageIR || m.Kind != KindPkg {
1074  			continue
1075  		}
1076  		rec := t.db.GetRecord(uint32(i))
1077  		if rec == nil {
1078  			continue
1079  		}
1080  		form := FormFromRecord(rec, t.StringPool)
1081  		if bytes.HasSuffix([]byte(form), []byte(".__module__")) {
1082  			return uint32(i)
1083  		}
1084  	}
1085  	return 0
1086  }
1087  
1088