Skip to content

build: add link-time dead method elimination#1868

Open
luoliwoshang wants to merge 12 commits into
xgo-dev:mainfrom
luoliwoshang:wip/deadcode-analyzer
Open

build: add link-time dead method elimination#1868
luoliwoshang wants to merge 12 commits into
xgo-dev:mainfrom
luoliwoshang:wip/deadcode-analyzer

Conversation

@luoliwoshang
Copy link
Copy Markdown
Contributor

@luoliwoshang luoliwoshang commented May 20, 2026

Summary

  • Add package metadata summaries for DCE facts, including ordinary edges, type children, interface method demands, interface conversions, reflection method use, and ABI method slots.
  • Persist metadata in the package build cache and merge package-local summaries into a global metadata view for analysis.
  • Add a metadata-driven deadcode analyzer that computes live ABI method slots using reachable interface demands, used-in-interface types, reflection rules, and MethodByName rules.
  • Emit strong ABI type metadata overrides that clear dead method IFn/TFn references, allowing the linker to drop unused method bodies.
  • Add metadata golden tests, metadump tooling, and focused analyzer/DCE override coverage.

Notes

  • The temporary verbose cache-hit meta read timing output has been removed; -v cache hit logging now prints only the package path.

Test Plan

  • go test ./internal/deadcode ./internal/dcepass ./internal/metadata ./internal/build -count=1

Demo Size Comparison

Built _demo/go demos with DCE from this PR into _demo/go/520-dce, and with local upstream/main (ec1b2f74) into _demo/go/520-nodce.

Build command shape:

  • LLGO_BUILD_CACHE=off llgo build -a -o <output> .

Summary for demos that built successfully on both sides:

  • Matched demos: 55
  • No-DCE total size: 59,670,624 bytes
  • DCE total size: 43,519,232 bytes
  • Total saved: 16,151,392 bytes
  • Total reduction: 27.07%
  • Demos that grew: 0

Two demos failed on both sides and were excluded from the size total:

  • defer/setjmp: C source files are present but this direct llgo build . path is not using cgo/SWIG.
  • defer/test: existing source errors around c.Printf / c.Str.

All measured demos, sorted by saved bytes descending:

Demo No DCE DCE Saved Saved %
goimporter-1389 4267280 2875648 1391632 32.61%
embedunexport-1598 3056032 1762912 1293120 42.31%
gotypes 3072896 2458912 613984 19.98%
abimethod 1434896 912224 522672 36.43%
mimeheader 1527664 1005856 521808 34.16%
reflectpointerto 1719408 1236656 482752 28.08%
reflectmake 1905136 1443568 461568 24.23%
netip 1504976 1048512 456464 30.33%
gobuild-1389 1590144 1158624 431520 27.14%
randdemo 1415280 985376 429904 30.38%
gotoken 1433408 1003808 429600 29.97%
sysexec 1414528 985376 429152 30.34%
oslookpath 1414144 985008 429136 30.35%
sync 1419840 990800 429040 30.22%
createtemp-1654 1431072 1002992 428080 29.91%
gobuild 2199920 1776784 423136 19.23%
randcrypt 1557968 1143248 414720 26.62%
maphash 1576416 1162128 414288 26.28%
failed/stacktrace 1376288 963392 412896 30.00%
cgo 1376928 964128 412800 29.98%
readdir 1395248 982512 412736 29.58%
timedur 1375904 963440 412464 29.98%
mkdirdemo 1394336 981920 412416 29.58%
sysopen-1654 1394208 981840 412368 29.58%
reflectfunc 1396240 984240 412000 29.51%
reflectcopy 1413152 1002256 410896 29.08%
logdemo 1377216 980864 396352 28.78%
checkfile 1377344 981264 396080 28.76%
reflectname-1412 1376032 979968 396064 28.78%
oswritestring 1376704 981216 395488 28.73%
texttemplate 2172768 1929248 243520 11.21%
osfile 788480 554368 234112 29.69%
commandrun 1160880 938496 222384 19.16%
reflectindirect 687056 487888 199168 28.99%
reflectmethod 1716672 1543392 173280 10.09%
timer 508304 419856 88448 17.40%
async 319600 277024 42576 13.32%
gotime 354480 328496 25984 7.33%
syscall 173696 151856 21840 12.57%
ifaceconv 74736 72896 1840 2.46%
ifaceprom-1559 91120 89312 1808 1.98%
mapclosure 78416 77136 1280 1.63%
export 95072 93856 1216 1.28%
cabi 70592 69472 1120 1.59%
issue1538-floatcvtuint-over 70592 69472 1120 1.59%
defer 72384 71280 1104 1.53%
defer/stress 72320 71216 1104 1.53%
issue1538 70512 69408 1104 1.57%
complex 73440 72352 1088 1.48%
defer/setjmp/c_standard_demo/closure_test 72464 71376 1088 1.50%
math 73248 72176 1072 1.46%
defer/setjmp/c_standard_demo/llgo_test 70816 70816 0 0.00%
goroutine 71040 71040 0 0.00%
runtime 89760 89760 0 0.00%
statefn 71568 71568 0 0.00%

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented May 20, 2026

@luoliwoshang luoliwoshang changed the title wip: add metadata-driven deadcode overrides build: add metadata-driven link-time DCE May 20, 2026
@luoliwoshang luoliwoshang changed the title build: add metadata-driven link-time DCE build: add link-time dead method elimination May 20, 2026
@luoliwoshang
Copy link
Copy Markdown
Contributor Author

补充一个 DCE 方法槽修复点:

对于被判定为 dead 的方法槽,IFn/TFn 不应该写成 null。这里需要和 Go linker 的做法一致,重定向到一个专门的 runtime.unreachableMethod

原因是运行时构造 itab 时,fun[0] == 0 有特殊含义:表示该类型没有实现目标接口。如果 DCE 把匹配到的方法函数指针清成 0,类型方法签名虽然仍然匹配,但生成出来的 itab 会被当成“不完整实现”,不会进入 itab cache。

这会导致同一个 concrete type 到同一个接口的转换生成多个不同 itab。典型表现是 reflect.Type 作为 map key 时出现不一致:

t0 == t1 // true
m[t1]    // false

最终会影响标准库里这类逻辑,例如 testing.F.Add(int) 中的:

supportedTypes[reflect.TypeOf(5)]

所以现在的策略是:dead method slot 仍保留非零函数入口,指向 runtime.unreachableMethod。如果该方法真的在运行时被调用,会像 Go 一样明确 throw;同时 itab 的实现判断和缓存语义不会被破坏。

@luoliwoshang
Copy link
Copy Markdown
Contributor Author

Additional experiment on _demo/go: temporarily degraded the DCE interface-method rule to only match reachable interface method demands by method name + method type (MethodSig), without checking whether the concrete type fully implements the demanded interface. Metadata collection and cache inputs were kept unchanged; only the analyzer matching rule was changed for this experiment.

Summary over the same demo set:

matched demos:          55
failed demos:           2 (defer/setjmp, defer/test)
No DCE total:           59,670,624 bytes
Precise DCE total:      43,519,232 bytes
Name+Sig DCE total:     43,602,096 bytes

Precise DCE saved:      16,151,392 bytes (27.07%)
Name+Sig DCE saved:     16,068,528 bytes (26.93%)
Extra vs precise DCE:   82,864 bytes
Lost precise saving:    0.51% of the precise-DCE saved bytes

Top cases where name+sig keeps more than the precise rule:

Demo Precise DCE Name+Sig DCE Extra vs precise
gobuild 1,776,784 1,795,344 18,560
timedur 963,440 981,184 17,744
reflectmake 1,443,568 1,447,424 3,856
abimethod 912,224 915,056 2,832
mimeheader 1,005,856 1,008,192 2,336
goimporter-1389 2,875,648 2,877,904 2,256
gotypes 2,458,912 2,460,928 2,016
embedunexport-1598 1,762,912 1,764,816 1,904

Conclusion from this demo set: the more precise interface implementation check does reduce output size, but the measured delta is small here: about 83KB total, or 0.51% of the bytes saved by precise DCE.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants