Skip to content

build: add link-time dead method elimination#1868

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

build: add link-time dead method elimination#1868
luoliwoshang wants to merge 11 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.

Top reductions:

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%

@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 的实现判断和缓存语义不会被破坏。

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