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