Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 19 additions & 18 deletions doc/goallc-llvm-goobj.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,10 @@ use 的 local 不生成 LLVM 栈槽,避免无意义地增大 frame。

GoObj stack growth、SP 恢复和当前 stack-map 路径只支持编译期固定 frame。
因此 amd64 和 arm64 GoObj target 对 variable-sized `alloca` 都确定性报错,
不能把动态栈分配交给 LLVM 通用 lowering。当前 GoObj writer 的 args/locals
pointer maps 仍为空;本实现只保证现有固定栈槽的 dominance、frame placement
和 stack-growth 约束,不代表已经支持完整 precise-GC stack maps、goroutine
defer 或 panic unwind。
不能把动态栈分配交给 LLVM 通用 lowering。当前 GoObj writer 已为所有 GoObj Go
函数生成类型推导的入口 ArgsPointerMaps,并为已支持的普通 statepoint 和固定
`alloca` 生成 LocalsPointerMaps;这仍不代表已经支持完整 precise-GC stack maps、
goroutine、defer 或 panic unwind。

对应回归包括:

Expand Down Expand Up @@ -273,9 +273,10 @@ pass plugin 默认从 `llc` 所属 LLVM payload 的
wrapper 覆盖的 compile action 重新编译。

插件的功能性源码和测试位于 Go 仓库
`src/cmd/llvmplugin`,不放入 LLVM 源码树。LLVM 只提供通用的
`-load-pass-plugin` 和 pre-codegen callback;GoALLC statepoint rewrite 及其
pass 顺序都应继续在这个 Go-owned 工程中实现。当前
`src/cmd/llvmplugin`,不放入 LLVM 源码树。LLVM 提供通用的
`-load-pass-plugin`、pre-codegen callback,以及 GoObj 对 Machine StackMaps
的对象格式适配;GoALLC statepoint rewrite 及其 pass 顺序都应继续在这个
Go-owned 工程中实现。当前
`runPreCodeGenPipeline` 调用 Go-owned statepoint pass:它对 Go ABI 函数执行
CFG 逆向数据流活跃性分析,为普通调用分配稳定 callsite ID,生成
`gc.statepoint` / `gc.relocate`,并识别 `gc-leaf-function`。受管指针分类当前
Expand All @@ -285,21 +286,21 @@ aggregate、`invoke`、`musttail` 和非 leaf inline asm fail closed。未来
compile 进程内集成 LLVM 时直接复用该 core 入口,不经过 plugin adapter。

机器位置不通过修改 LLVM 通用 `StackMaps.cpp` 截获。SSA→LLVM IR 前端为 Go ABI
函数声明 `gc "goallc"` 和 `go-stack-growth-statepoint`,插件只负责注册对应的
GC strategy 与 `GCMetadataPrinter::emitStackMaps`,并消费这些前端标记。在
函数声明 `gc "goallc"`;GoObj Go 函数默认采用原生 Go 的可扩栈策略,只有
`go-nosplit`、`go-systemstack` 这样的例外策略需要额外属性。插件负责注册对应的
GC strategy、执行 statepoint rewrite 并消费这些前端标记。在 LLVM GoObj
AsmPrinter 模块收尾阶段
读取标准 `FnInfos/CSInfos`,跳过 statepoint 的 CC、flags 和 deopt 前缀后,把
原始 GC locations 写入 MCContext。GoObj writer 在最终 layout 后完成 SP
校验、`Direct`/`Indirect` 解释、LocalsPointerMaps 和 PCDATA_StackMapIndex
编码。GoALLC 要求 StackMaps 记录 CALL 起点;map 从 CALL 开始。前端添加的
`go-stack-growth-statepoint` 属性使 LLVM 在 PEI 阶段把
`runtime.morestack` 调用生成为物理 MIR `STATEPOINT`。其 deopt 和 GC alloca
区为空,GC pointer 区则记录类型推导出的入口参数 home。它从 morestack CALL
起点选择入口 ArgsPointerMaps 和空 locals bitmap,因此普通调用与栈增长调用
走同一 Machine StackMaps 链路,且不依赖 return PC 反推调用范围。已有
Machine `STATEPOINT` 但缺少该前端属性时,
LLVM target lowering 会 fail closed;不再使用 slow-path reset label 兼容普通
morestack CALL。GoObj 先写索引 0 的
编码。GoALLC 要求 StackMaps 记录 CALL 起点;map 从 CALL 开始。LLVM target
formal lowering 为每个 GoObj Go 函数推导入口参数 home,并用一个独立、零字节的
`EntryArgsStackMapID` 表达函数级 ArgsPointerMaps;该记录不是调用点,不生成
PCDATA。非 nosplit 函数在 PEI 阶段额外把 `runtime.morestack` 调用生成为物理、
root-free 的 `StackGrowthStatepointID`,从 CALL 起点选择入口 ArgsPointerMaps 和
空 locals bitmap。nosplit 函数不得包含这个 statepoint。这样普通调用与栈增长
调用仍走同一 Machine StackMaps 链路,且不依赖 return PC 反推调用范围,同时
函数级入口图不再伪装成 morestack 调用。GoObj 先写索引 0 的
`PCDATA_UnsafePoint`(当前恒为 safe 的 `-1`),再写索引 1 的
`PCDATA_StackMapIndex`;不能只写后一张表,否则 linker 会把它误认成索引 0。
`Direct SP+offset` 是栈地址本身,不表示该 slot 存有 pointer,因此不会设置
Expand Down
69 changes: 56 additions & 13 deletions src/cmd/compile/internal/ssa/llvmdata.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package ssa

import (
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/typecheck"
"cmd/internal/goobj"
"cmd/internal/obj"
Expand All @@ -18,27 +19,65 @@ import (
"github.com/goallc/go-llvm"
)

type llvmGoObjSymbolKey struct {
name string
abi obj.ABI
}

var llvmGoObjLocalDefinitions map[llvmGoObjSymbolKey]bool

func llvmGoObjSymbolKeyFor(s *obj.LSym) llvmGoObjSymbolKey {
return llvmGoObjSymbolKey{name: s.Name, abi: s.ABI()}
}

// initLLVMGoObjLocalDefinitions records the distinction needed only by
// linkname: a declaration without a local definition is a pull, while a local
// function or data definition is a push. Builtin references do not consult
// this set.
func initLLVMGoObjLocalDefinitions() {
llvmGoObjLocalDefinitions = make(map[llvmGoObjSymbolKey]bool)
for _, fn := range typecheck.Target.Funcs {
if fn == nil || fn.Nname == nil || len(fn.Body) == 0 {
continue
}
s := fn.LinksymABI(fn.ABI)
llvmGoObjLocalDefinitions[llvmGoObjSymbolKeyFor(s)] = true
}
for _, name := range typecheck.Target.Externs {
if name == nil || name.Op() != ir.ONAME || name.Class != ir.PEXTERN {
continue
}
s := name.Linksym()
llvmGoObjLocalDefinitions[llvmGoObjSymbolKeyFor(s)] = true
}
}

func llvmGoObjLinknameReference(s *obj.LSym) bool {
return s != nil && (s.IsLinkname() || s.IsLinknameStd()) &&
!llvmGoObjLocalDefinitions[llvmGoObjSymbolKeyFor(s)]
}

// llvmGoObjReferenceName is the single naming boundary for undefined Go
// symbols in LLVM IR. Go's symbol model remains unchanged; the LLVM-only
// suffix tells the GoObj writer to serialize a surviving relocation through
// the predefined builtin index table.
// symbols in LLVM IR. Go's symbol model remains unchanged; the LLVM-only name
// records whether the GoObj writer must serialize a surviving relocation as a
// builtin-index or linkname pull.
func llvmGoObjReferenceName(s *obj.LSym) string {
if s == nil {
base.Fatalf("nil GoObj symbol reference")
}
if strings.Contains(s.Name, goobj.BuiltinSymbolSuffixPrefix) {
base.Fatalf("Go symbol name %q uses reserved LLVM builtin suffix", s.Name)
if strings.Contains(s.Name, goobj.BuiltinSymbolSuffixPrefix) ||
strings.Contains(s.Name, goobj.LinknameSymbolSuffix) {
base.Fatalf("Go symbol name %q uses reserved LLVM reference suffix", s.Name)
}
if base.Ctxt.Flag_linkshared {
return s.Name
}
if name, ok := goobj.BuiltinSymbolName(s.Name, int(s.ABI())); ok {
return name
}
// Linkname references currently retain their ordinary linker name. A
// runtime implementation may itself be linknamed while compiler-generated
// references to the same logical symbol still use the builtin table, so the
// builtin lookup above deliberately takes precedence over this attribute.
if llvmGoObjLinknameReference(s) {
return s.Name + goobj.LinknameSymbolSuffix
}
return s.Name
}

Expand Down Expand Up @@ -69,21 +108,22 @@ func emitGoObjCgoModuleAsm() {
}

// attachGoObjSymbolRef attaches the part of an undefined imported Go symbol's
// identity that cannot be recovered from an LLVM relocation. Builtin identity
// is carried by the declaration name instead.
// identity that cannot be recovered from an LLVM relocation. Builtin and
// linkname identity is carried by the declaration name instead.
func attachGoObjSymbolRef(value llvm.Value, s *obj.LSym) {
if value.IsNil() || s == nil {
base.Fatalf("invalid LLVM value in GoObj symbol reference")
}

if strings.Contains(value.Name(), goobj.BuiltinSymbolSuffixPrefix) {
if strings.Contains(value.Name(), goobj.BuiltinSymbolSuffixPrefix) ||
strings.Contains(value.Name(), goobj.LinknameSymbolSuffix) {
return
}
// Linknamed symbols live in GoObj's non-package namespace even when the
// compiler learned about them through an imported package. Their export
// symbol index addresses that package's ordinary symbol block and must not
// be attached to the LLVM declaration as an imported reference.
if s.PkgIdx == goobj.PkgIdxNone || s.IsLinkname() {
if s.PkgIdx == goobj.PkgIdxNone || s.IsLinkname() || s.IsLinknameStd() {
return
}
localPkg := objabi.PathToPrefix(base.Ctxt.Pkgpath)
Expand Down Expand Up @@ -656,6 +696,9 @@ func setGoObjDataFlags(g llvm.Value, s *obj.LSym) {
if s.IsLinkname() {
flag2 |= 1 << 4 // goobj.SymFlagLinkname
}
if s.IsLinknameStd() {
flag2 |= goobj.SymFlagLinknameStd
}
if s.ABIWrapper() {
flag2 |= 1 << 5 // goobj.SymFlagABIWrapper
}
Expand Down
20 changes: 18 additions & 2 deletions src/cmd/compile/internal/ssa/llvmdebug.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,22 @@ func (lfc *LLVMFuncContext) setDebugLocation(xpos src.XPos) {
}

locationScope := llvmDIScopeForPos(scope, pos, 0)
lfc.b.SetCurrentDebugLocationMetadata(GlobalCtxt.CreateDebugLocation(
pos.RelLine(), pos.RelCol(), locationScope, inlinedAt))
location := GlobalCtxt.CreateDebugLocation(
pos.RelLine(), pos.RelCol(), locationScope, inlinedAt)
lfc.b.SetCurrentDebugLocationMetadata(location)

// Generic LLVM optimization may combine instructions from different Go
// inline frames and keep only one of their DILocations. Record one complete
// frontend location for every inline node independently of the instruction
// stream. The final machine pass uses this only when an inline edge has
// otherwise disappeared, so optimization remains unconstrained while Go's
// pcinline tree still has a real final-layout PC for every source edge.
if len(chain) != 0 && !lfc.RequiredInlinePos[pos.Base().InliningIndex()] {
lfc.RequiredInlinePos[pos.Base().InliningIndex()] = true
CurrentModule.AddNamedMetadataOperand(goObjDebugInlineRequiredMD,
GlobalCtxt.MDNode([]llvm.Metadata{
lfc.LF.ConstantAsMetadata(),
location,
}))
}
}
Loading
Loading