From b655c080bc4fa2e60c9bb579d2240a8121826b8e Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 28 Jul 2026 00:50:50 +0800 Subject: [PATCH 01/17] cmd/compile: lower runtime type descriptors to LLVM --- doc/goallc-llvm-goobj.md | 36 ++ src/cmd/compile/internal/gc/main.go | 7 +- src/cmd/compile/internal/ssa/llvmdata.go | 318 ++++++++++++ src/cmd/compile/internal/ssa/llvmtypeddata.go | 478 ++++++++++++++++++ src/cmd/compile/internal/ssa/ssa2llvm_stub.go | 3 + .../vendor/github.com/goallc/go-llvm/ir.go | 3 + test/codegen/type_descriptor.go | 26 + test/codegen/type_descriptor_kinds.go | 70 +++ test/llvm_tests.json | 9 +- test/llvm_type_descriptor.go | 18 + test/llvm_type_descriptor.out | 1 + test/llvm_type_descriptor_kinds.go | 46 ++ test/llvm_type_descriptor_kinds.out | 1 + test/llvm_type_descriptor_map.go | 18 + test/llvm_type_descriptor_map.out | 1 + 15 files changed, 1031 insertions(+), 4 deletions(-) create mode 100644 src/cmd/compile/internal/ssa/llvmdata.go create mode 100644 src/cmd/compile/internal/ssa/llvmtypeddata.go create mode 100644 test/codegen/type_descriptor.go create mode 100644 test/codegen/type_descriptor_kinds.go create mode 100644 test/llvm_type_descriptor.go create mode 100644 test/llvm_type_descriptor.out create mode 100644 test/llvm_type_descriptor_kinds.go create mode 100644 test/llvm_type_descriptor_kinds.out create mode 100644 test/llvm_type_descriptor_map.go create mode 100644 test/llvm_type_descriptor_map.out diff --git a/doc/goallc-llvm-goobj.md b/doc/goallc-llvm-goobj.md index 38d91165c38ed1..a8c2477e201b74 100644 --- a/doc/goallc-llvm-goobj.md +++ b/doc/goallc-llvm-goobj.md @@ -25,6 +25,40 @@ IR 和 Go archive 的 `__.PKGDEF` 后结束,不再生成原生 linker object 对被替换的 package 而言,archive 中唯一的 `_go_.o` 必须是 `llc` 生成的 GoObj;`__.PKGDEF` 仍完全由 Go compiler 生成并位于 archive 的第一个成员。 +## 类型描述符和只读数据 + +`-llvmironly` 不会跳过 compiler 的 `dumpdata` 准备阶段。`reflectdata` 仍按 +原生 compiler 的方式在 `base.Ctxt.Data` 中生成最终的 `obj.LSym` 布局;随后 +LLVM lowering 从带有 `TypeInfo` 的 runtime type descriptor roots 出发,收集 +仅由这些 roots 的 relocation 可达的数据闭包,并将其 lower 为 LLVM constant +globals。descriptor 的主体由同一份 `rttype` runtime ABI layout 构造为 packed +named LLVM structs(例如 `%go.runtime.Type`、`%go.runtime.StructType`);未建 +schema 的辅助数据仍以 bytes/relocations 表示。这个边界刻意位于 finalized LSym, +而不是重写 reflectdata 布局逻辑。 + +每个被 lower 的 LSym 保留: + +- descriptor 已知字段的 runtime ABI 类型、字段 offset、原始值和 + `.rodata` / `.data` / `.bss` 段属性; +- `R_ADDR`、`R_ADDROFF`、`R_METHODOFF` 及其 weak 变体的 addend 和目标; +- `DUPOK`、`LOCAL`、typelink、Go type、itab、`UsedInIface`、linkname 等 GoObj + symbol flags; +- runtime ABIInternal 函数引用(例如 type equality closure 中的 + `runtime.memequal64`)。 + +LLVM IR 使用 global attachment `!goobj.symbol.flags` 和 `!goobj.relocs` 交接 +这些 Go-only 属性。GoObj writer 消费该 metadata,在默认 relocation 推导前恢复 +精确的 Go relocation type,特别是 weak offset relocation。零宽度的 linker +保活边 `R_KEEP` 不伪装为地址常量,而用 `!goobj.keep` 记录其 target symbol, +由 writer 合成 GoObj relocation。最终链路仍只有 +`__.PKGDEF + _go_.o` 两个有意义的 archive members;不会生成或合并 native data +object。 + +当前实现只 lower type-rooted data closure,尚未把所有 `dumpdata` 产物泛化为 +LLVM data lowering。type descriptor 中未被当前 schema 覆盖的尾部数据,以及其他 +data roots,保守地维持 bytes/relocation fallback;新增 schema 或 root 前必须先明确 +其 relocation、GC 和 linker 契约,并为 writer 增加相应的 GoObj regression。 + ## LLVM IR 契约 LLVM IR 本身是 frontend 到 `llc` 的唯一配置载体。`compile` 设置 GoObj @@ -199,6 +233,8 @@ go test cmd/internal/testdir -run='^Test$/^LLVM$' -v 原生 compiler object 混合。 - `!goobj.config` 是这条开发链路的稳定交接点。新增 header 配置时应新增独立 metadata 字段和相应 `llc` 验证,不要恢复 wrapper 中的 header 解析逻辑。 +- 类型 descriptor data 已覆盖,但这不等同于通用 static-data lowering;目前仅 + 支持 type-rooted readonly/data closure 中已验证的 relocation 类型。 相关合入记录:Go [#3](https://github.com/goallc/go/pull/3),LLVM [#3](https://github.com/goallc/llvm-project/pull/3)。 diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index 080748036c8706..0ebf9bed744463 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -379,10 +379,13 @@ func Main(archInit func(*ssagen.ArchInfo)) { staticinit.AddKeepRelocations() } - // Write object data to disk. In LLVM-IR-only mode the toolexec wrapper - // supplies the linker object with llc, so retain only compiler export data. + // Write object data to disk. In LLVM-IR-only mode dumpdata still prepares + // reflect/type linker data as LSyms, but LLVM lowers that closure into the + // IR consumed by llc; the archive itself retains only compiler export data. base.Timer.Start("be", "dumpobj") if base.Flag.LLVMIROnly { + dumpdata() + ssa.LowerGoObjTypeData() dumpobj() } else { dumpdata() diff --git a/src/cmd/compile/internal/ssa/llvmdata.go b/src/cmd/compile/internal/ssa/llvmdata.go new file mode 100644 index 00000000000000..3d907e26d52a07 --- /dev/null +++ b/src/cmd/compile/internal/ssa/llvmdata.go @@ -0,0 +1,318 @@ +//go:build !compiler_bootstrap + +package ssa + +import ( + "cmd/compile/internal/base" + "cmd/internal/obj" + "cmd/internal/objabi" + "sort" + "strings" + + "github.com/goallc/go-llvm" +) + +// LowerGoObjTypeData lowers the complete linker-data closure rooted at runtime +// type descriptors into LLVM globals. The Go front end remains responsible for +// laying out the descriptors: this code only preserves the already-finalized +// LSym bytes, relocations, and GoObj symbol attributes in LLVM IR. +// +// Keeping this boundary at LSym is intentional. reflectdata is the source of +// truth for runtime layouts and is also used by the native backend; duplicating +// it here would make LLVM and native type descriptors drift independently. +func LowerGoObjTypeData() { + data := make(map[*obj.LSym]bool, len(base.Ctxt.Data)) + for _, s := range base.Ctxt.Data { + data[s] = true + } + + closure := make(map[*obj.LSym]bool) + var visit func(*obj.LSym) + visit = func(s *obj.LSym) { + if s == nil || closure[s] || !data[s] { + return + } + closure[s] = true + for _, r := range s.R { + visit(r.Sym) + } + } + for _, s := range base.Ctxt.Data { + if s.TypeInfo() != nil { + visit(s) + } + } + + if len(closure) == 0 { + return + } + + syms := make([]*obj.LSym, 0, len(closure)) + for s := range closure { + syms = append(syms, s) + } + sort.Slice(syms, func(i, j int) bool { return syms[i].Name < syms[j].Name }) + lowerer := newLLVMDataLowerer(data) + + globals := make(map[*obj.LSym]llvm.Value, len(syms)) + for _, s := range syms { + t := lowerer.dataType(s) + g := CurrentModule.NamedGlobal(s.Name) + if g.IsNil() { + g = llvm.AddGlobal(CurrentModule, t, s.Name) + } else if g.GlobalValueType() != t { + base.Fatalf("conflicting LLVM global type for Go data symbol %s", s.Name) + } + g.SetSection(llvmDataSection(s)) + g.SetGlobalConstant(llvmDataIsReadOnly(s)) + if s.Align != 0 { + g.SetAlignment(int(s.Align)) + } + globals[s] = g + } + + for _, s := range syms { + g := globals[s] + g.SetInitializer(lowerer.dataInitializer(s, globals)) + setGoObjDataFlags(g, s) + setGoObjRelocMetadata(g, s) + setGoObjKeepMetadata(g, s) + } +} + +func llvmDataType(s *obj.LSym) llvm.Type { + fields := llvmDataFields(s, nil, nil) + return GlobalCtxt.ConstStruct(fields, true).Type() +} + +func llvmDataInitializer(s *obj.LSym, globals map[*obj.LSym]llvm.Value, data map[*obj.LSym]bool) llvm.Value { + fields := llvmDataFields(s, globals, data) + return GlobalCtxt.ConstStruct(fields, true) +} + +// llvmDataFields models an LSym as a packed aggregate. Byte runs preserve the +// frontend's exact layout while relocation slots retain their LLVM constants. +// Using a packed aggregate is necessary because descriptors contain 32-bit +// offsets adjacent to pointer-sized fields. +func llvmDataFields(s *obj.LSym, globals map[*obj.LSym]llvm.Value, data map[*obj.LSym]bool) []llvm.Value { + dataSize := int(s.Size) + if dataSize < len(s.P) { + dataSize = len(s.P) + } + bytes := make([]byte, dataSize) + copy(bytes, s.P) + relocs := llvmDataStorageRelocs(s) + sort.Slice(relocs, func(i, j int) bool { return relocs[i].Off < relocs[j].Off }) + + fields := make([]llvm.Value, 0, len(relocs)*2+1) + pos := 0 + for _, r := range relocs { + off := int(r.Off) + end := off + int(r.Siz) + if off < pos || end > dataSize { + base.Fatalf("invalid relocation [%d,%d) in Go data symbol %s of size %d", off, end, s.Name, dataSize) + } + if off > pos { + fields = append(fields, llvmDataBytes(bytes[pos:off])) + } + if globals == nil { + fields = append(fields, llvmDataRelocZero(r)) + } else { + fields = append(fields, llvmDataRelocValue(s, r, globals, data)) + } + pos = end + } + if pos < dataSize { + fields = append(fields, llvmDataBytes(bytes[pos:])) + } + if len(fields) == 0 { + fields = append(fields, llvmDataBytes(nil)) + } + return fields +} + +func llvmDataBytes(b []byte) llvm.Value { + return GlobalCtxt.ConstString(string(b), false) +} + +func llvmDataRelocZero(r obj.Reloc) llvm.Value { + switch r.Type { + case objabi.R_ADDR, objabi.R_WEAKADDR: + if r.Siz != uint8(base.Ctxt.Arch.PtrSize) { + base.Fatalf("unsupported address relocation size %d", r.Siz) + } + return llvm.ConstPointerNull(GlobalCtxt.PointerType(0)) + case objabi.R_ADDROFF, objabi.R_WEAKADDROFF, objabi.R_METHODOFF: + if r.Siz != 4 { + base.Fatalf("unsupported offset relocation size %d", r.Siz) + } + return llvm.ConstInt(GlobalCtxt.Int32Type(), 0, false) + default: + base.Fatalf("unsupported Go data relocation %s", r.Type) + return llvm.Value{} + } +} + +func llvmDataRelocValue(source *obj.LSym, r obj.Reloc, globals map[*obj.LSym]llvm.Value, data map[*obj.LSym]bool) llvm.Value { + if r.Sym == nil { + base.Fatalf("nil relocation target in Go data symbol %s", source.Name) + } + target, ok := globals[r.Sym] + if !ok { + target = llvmExternalDataRef(r.Sym, data) + } + addr := target + if r.Add != 0 { + if !target.IsAFunction().IsNil() { + base.Fatalf("non-zero addend %d on function relocation %s -> %s", r.Add, source.Name, r.Sym.Name) + } + addr = llvm.ConstGEP(GlobalCtxt.Int8Type(), target, []llvm.Value{ + llvm.ConstInt(GlobalCtxt.Int64Type(), uint64(r.Add), true), + }) + } + + switch r.Type { + case objabi.R_ADDR, objabi.R_WEAKADDR: + if r.Siz != uint8(base.Ctxt.Arch.PtrSize) { + base.Fatalf("unsupported address relocation size %d", r.Siz) + } + return addr + case objabi.R_ADDROFF, objabi.R_WEAKADDROFF, objabi.R_METHODOFF: + if r.Siz != 4 { + base.Fatalf("unsupported offset relocation size %d", r.Siz) + } + return llvm.ConstPtrToInt(addr, GlobalCtxt.Int32Type()) + default: + base.Fatalf("unsupported Go data relocation %s in %s", r.Type, source.Name) + return llvm.Value{} + } +} + +func llvmExternalDataRef(s *obj.LSym, data map[*obj.LSym]bool) llvm.Value { + if data[s] { + base.Fatalf("unlowered Go data symbol %s", s.Name) + } + // Runtime references materialized by reflectdata are frequently still Sxxx + // at this point. Their ABI nevertheless identifies them as functions (for + // example runtime.memequal64 in an equality closure), so do not rely on + // STEXT alone here. + if s.Type == objabi.STEXT || s.Type == objabi.STEXTFIPS || s.ABI() == obj.ABIInternal { + if f := CurrentModule.NamedFunction(s.Name); !f.IsNil() { + return f + } + f := llvm.AddFunction(CurrentModule, s.Name, llvm.FunctionType(GlobalCtxt.VoidType(), nil, false)) + f.SetFunctionCallConv(llvmCallConv(s.ABI())) + return f + } + if g := CurrentModule.NamedGlobal(s.Name); !g.IsNil() { + return g + } + return llvm.AddGlobal(CurrentModule, GlobalCtxt.Int8Type(), s.Name) +} + +func llvmDataSection(s *obj.LSym) string { + switch s.Type { + case objabi.SRODATA, objabi.SRODATAFIPS, objabi.SNOPTRDATA, objabi.SNOPTRDATAFIPS: + return ".rodata" + case objabi.SDATA, objabi.SDATAFIPS: + return ".data" + case objabi.SBSS, objabi.SNOPTRBSS: + return ".bss" + default: + base.Fatalf("unsupported Go data symbol kind %s for %s", s.Type, s.Name) + return "" + } +} + +func llvmDataIsReadOnly(s *obj.LSym) bool { + switch s.Type { + case objabi.SRODATA, objabi.SRODATAFIPS, objabi.SNOPTRDATA, objabi.SNOPTRDATAFIPS: + return true + default: + return false + } +} + +func setGoObjDataFlags(g llvm.Value, s *obj.LSym) { + var flag, flag2 uint64 + if s.DuplicateOK() { + flag |= 1 << 0 // goobj.SymFlagDupok + } + if s.Local() { + flag |= 1 << 1 // goobj.SymFlagLocal + } + if s.MakeTypelink() { + flag |= 1 << 2 // goobj.SymFlagTypelink + } + if strings.HasPrefix(s.Name, "type:") && len(s.Name) > len("type:") && s.Name[5] != '.' && s.Type == objabi.SRODATA { + flag |= 1 << 6 // goobj.SymFlagGoType + } + if s.UsedInIface() { + flag2 |= 1 << 0 // goobj.SymFlagUsedInIface + } + if strings.HasPrefix(s.Name, "go:itab.") && s.Type == objabi.SRODATA { + flag2 |= 1 << 1 // goobj.SymFlagItab + } + if s.IsLinkname() { + flag2 |= 1 << 4 // goobj.SymFlagLinkname + } + if s.ABIWrapper() { + flag2 |= 1 << 5 // goobj.SymFlagABIWrapper + } + if flag == 0 && flag2 == 0 { + return + } + g.SetGlobalMetadata(GlobalCtxt.MDKindID("goobj.symbol.flags"), GlobalCtxt.MDNode([]llvm.Metadata{ + llvm.ConstInt(GlobalCtxt.Int32Type(), flag, false).ConstantAsMetadata(), + llvm.ConstInt(GlobalCtxt.Int32Type(), flag2, false).ConstantAsMetadata(), + })) +} + +func setGoObjRelocMetadata(g llvm.Value, s *obj.LSym) { + entries := make([]llvm.Metadata, 0, len(s.R)) + for _, r := range s.R { + switch r.Type { + case objabi.R_ADDR, objabi.R_WEAKADDR, objabi.R_ADDROFF, objabi.R_WEAKADDROFF, objabi.R_METHODOFF: + entries = append(entries, GlobalCtxt.MDNode([]llvm.Metadata{ + llvm.ConstInt(GlobalCtxt.Int32Type(), uint64(r.Off), false).ConstantAsMetadata(), + llvm.ConstInt(GlobalCtxt.Int32Type(), uint64(uint16(r.Type)), false).ConstantAsMetadata(), + })) + case objabi.R_KEEP: + continue + default: + base.Fatalf("unsupported Go data relocation %s in %s", r.Type, s.Name) + } + } + if len(entries) != 0 { + g.SetGlobalMetadata(GlobalCtxt.MDKindID("goobj.relocs"), GlobalCtxt.MDNode(entries)) + } +} + +// R_KEEP is a zero-width linker reachability edge, not a storage relocation. +// Keep it separate from !goobj.relocs so llc can synthesize the GoObj record +// without inventing bytes or a target-address expression in the global. +func setGoObjKeepMetadata(g llvm.Value, s *obj.LSym) { + entries := make([]llvm.Metadata, 0) + for _, r := range s.R { + if r.Type != objabi.R_KEEP { + continue + } + if r.Sym == nil { + base.Fatalf("nil R_KEEP target in %s", s.Name) + } + entries = append(entries, GlobalCtxt.MDString(r.Sym.Name)) + } + if len(entries) != 0 { + g.SetGlobalMetadata(GlobalCtxt.MDKindID("goobj.keep"), GlobalCtxt.MDNode(entries)) + } +} + +func llvmDataStorageRelocs(s *obj.LSym) []obj.Reloc { + relocs := make([]obj.Reloc, 0, len(s.R)) + for _, r := range s.R { + if r.Type != objabi.R_KEEP { + relocs = append(relocs, r) + } + } + return relocs +} diff --git a/src/cmd/compile/internal/ssa/llvmtypeddata.go b/src/cmd/compile/internal/ssa/llvmtypeddata.go new file mode 100644 index 00000000000000..2ab38f4b033adf --- /dev/null +++ b/src/cmd/compile/internal/ssa/llvmtypeddata.go @@ -0,0 +1,478 @@ +//go:build !compiler_bootstrap + +package ssa + +import ( + "cmd/compile/internal/base" + "cmd/compile/internal/rttype" + "cmd/compile/internal/types" + "cmd/internal/obj" + "cmd/internal/objabi" + "sort" + "strings" + + "github.com/goallc/go-llvm" +) + +// llvmDataLowerer provides two representations for compiler linker data. The +// generic representation is byte runs plus relocation slots. Runtime type +// descriptors additionally carry TypeInfo, so their fixed and variable layout +// can be reconstructed from the same rttype definitions that reflectdata used +// to write the LSym. This makes the important part of the IR inspectable while +// retaining the generic representation for auxiliary linker data. +type llvmDataLowerer struct { + data map[*obj.LSym]bool + runtimeTypes map[*types.Type]llvm.Type + descriptorTypes map[*obj.LSym]llvm.Type + namedRuntimeType map[*types.Type]bool +} + +func newLLVMDataLowerer(data map[*obj.LSym]bool) *llvmDataLowerer { + return &llvmDataLowerer{ + data: data, + runtimeTypes: make(map[*types.Type]llvm.Type), + descriptorTypes: make(map[*obj.LSym]llvm.Type), + namedRuntimeType: make(map[*types.Type]bool), + } +} + +func (l *llvmDataLowerer) dataType(s *obj.LSym) llvm.Type { + if t := llvmDescriptorGoType(s); t != nil { + return l.descriptorType(s, t) + } + return llvmDataType(s) +} + +func (l *llvmDataLowerer) dataInitializer(s *obj.LSym, globals map[*obj.LSym]llvm.Value) llvm.Value { + if t := llvmDescriptorGoType(s); t != nil { + return l.descriptorInitializer(s, t, globals) + } + return llvmDataInitializer(s, globals, l.data) +} + +func llvmDescriptorGoType(s *obj.LSym) *types.Type { + ti := s.TypeInfo() + if ti == nil { + return nil + } + t, _ := ti.Type.(*types.Type) + return t +} + +type llvmDescriptorPart struct { + off int64 + typ *types.Type +} + +// descriptorParts mirrors reflectdata.writeType. It uses the runtime ABI +// structures from rttype for the descriptor prefix and recognizes every +// type-specific variable array. The finalized LSym size determines whether an +// UncommonType and Method array are present. That distinction cannot be made +// from AllMethods: interface methods are IMethod entries, not uncommon methods. +// +// Type descriptors are fully modeled. If reflectdata's layout changes, reject +// the descriptor instead of silently treating a runtime field as opaque bytes. +func descriptorParts(t *types.Type, size int64) []llvmDescriptorPart { + var rt *types.Type + var variable *types.Type + switch t.Kind() { + default: + rt = rttype.Type + case types.TARRAY: + rt = rttype.ArrayType + case types.TSLICE: + rt = rttype.SliceType + case types.TCHAN: + rt = rttype.ChanType + case types.TFUNC: + rt = rttype.FuncType + variable = types.NewArray(types.Types[types.TUNSAFEPTR], int64(t.NumRecvs()+t.NumParams()+t.NumResults())) + case types.TINTER: + rt = rttype.InterfaceType + variable = types.NewArray(rttype.IMethod, int64(len(t.AllMethods()))) + case types.TMAP: + rt = rttype.MapType + case types.TPTR: + rt = rttype.PtrType + case types.TSTRUCT: + rt = rttype.StructType + variable = types.NewArray(rttype.StructField, int64(t.NumFields())) + } + + parts := []llvmDescriptorPart{{off: 0, typ: rt}} + off := rt.Size() + variableSize := int64(0) + if variable != nil { + variableSize = variable.Size() + } + trailing := size - off - variableSize + if trailing < 0 { + base.Fatalf("runtime descriptor %s is too small: have %d, fixed layout requires %d", t, size, off+variableSize) + } + + // A named type always has UncommonType. For an unnamed type, any bytes + // between its fixed/variable data and the end are the uncommon header plus + // concrete method entries. + if t.Sym() != nil || trailing != 0 { + if trailing < rttype.UncommonType.Size() { + base.Fatalf("invalid uncommon runtime descriptor size %d for %s", trailing, t) + } + parts = append(parts, llvmDescriptorPart{off: off, typ: rttype.UncommonType}) + off += rttype.UncommonType.Size() + trailing -= rttype.UncommonType.Size() + } + if variable != nil { + parts = append(parts, llvmDescriptorPart{off: off, typ: variable}) + off += variable.Size() + } + if trailing%rttype.Method.Size() != 0 { + base.Fatalf("invalid runtime method table size %d for %s", trailing, t) + } + if n := trailing / rttype.Method.Size(); n != 0 { + methods := types.NewArray(rttype.Method, n) + parts = append(parts, llvmDescriptorPart{off: off, typ: methods}) + off += methods.Size() + } + if off != size { + base.Fatalf("runtime descriptor layout for %s has size %d, want %d", t, off, size) + } + return parts +} + +func (l *llvmDataLowerer) descriptorType(s *obj.LSym, t *types.Type) llvm.Type { + if result, ok := l.descriptorTypes[s]; ok { + return result + } + name := "go.descriptor." + llvmSafeTypeName(strings.TrimPrefix(s.Name, "type:")) + result := CurrentModule.GetTypeByName(name) + if result.IsNil() { + result = GlobalCtxt.StructCreateNamed(name) + } + l.descriptorTypes[s] = result + parts := descriptorParts(t, llvmDataSize(s)) + fields := l.descriptorFields(s, parts, nil) + types := make([]llvm.Type, len(fields)) + for i, f := range fields { + types[i] = f.Type() + } + result.StructSetBody(types, true) + return result +} + +func (l *llvmDataLowerer) descriptorInitializer(s *obj.LSym, t *types.Type, globals map[*obj.LSym]llvm.Value) llvm.Value { + typ := l.descriptorType(s, t) + return llvm.ConstNamedStruct(typ, l.descriptorFields(s, descriptorParts(t, llvmDataSize(s)), globals)) +} + +func (l *llvmDataLowerer) descriptorFields(s *obj.LSym, parts []llvmDescriptorPart, globals map[*obj.LSym]llvm.Value) []llvm.Value { + fields := make([]llvm.Value, 0, len(parts)*2+1) + pos := int64(0) + for _, part := range parts { + if part.off < pos || part.off+part.typ.Size() > llvmDataSize(s) { + base.Fatalf("invalid semantic descriptor layout for %s", s.Name) + } + fields = append(fields, llvmDataRangeFields(s, pos, part.off, globals, l.data)...) + fields = append(fields, l.goValue(s, part.typ, part.off, globals)) + pos = part.off + part.typ.Size() + } + fields = append(fields, llvmDataRangeFields(s, pos, llvmDataSize(s), globals, l.data)...) + if len(fields) == 0 { + fields = append(fields, llvmDataBytes(nil)) + } + return fields +} + +func (l *llvmDataLowerer) goType(t *types.Type) llvm.Type { + if result, ok := l.runtimeTypes[t]; ok { + return result + } + + switch t.Kind() { + case types.TBOOL, types.TINT8, types.TUINT8: + return GlobalCtxt.Int8Type() + case types.TINT16, types.TUINT16: + return GlobalCtxt.Int16Type() + case types.TINT32, types.TUINT32: + return GlobalCtxt.Int32Type() + case types.TINT64, types.TUINT64: + return GlobalCtxt.Int64Type() + case types.TINT, types.TUINT, types.TUINTPTR: + return GlobalCtxt.IntType(int(t.Size() * 8)) + case types.TPTR, types.TUNSAFEPTR: + return GlobalCtxt.PointerType(0) + case types.TARRAY: + return llvm.ArrayType(l.goType(t.Elem()), int(t.NumElem())) + case types.TSLICE: + return GlobalCtxt.StructType([]llvm.Type{GlobalCtxt.PointerType(0), l.goType(types.Types[types.TINT]), l.goType(types.Types[types.TINT])}, true) + case types.TSTRING: + return GlobalCtxt.StructType([]llvm.Type{GlobalCtxt.PointerType(0), l.goType(types.Types[types.TINT])}, true) + case types.TSTRUCT: + return l.goStructType(t) + default: + base.Fatalf("unsupported runtime descriptor field type %s", t) + return llvm.Type{} + } +} + +func (l *llvmDataLowerer) goStructType(t *types.Type) llvm.Type { + if result, ok := l.runtimeTypes[t]; ok { + return result + } + name := llvmRuntimeTypeName(t) + if name == "" { + fields := l.goStructElementTypes(t) + return GlobalCtxt.StructType(fields, true) + } + result := CurrentModule.GetTypeByName(name) + if result.IsNil() { + result = GlobalCtxt.StructCreateNamed(name) + } + l.runtimeTypes[t] = result + l.namedRuntimeType[t] = true + if result.StructElementTypesCount() == 0 { + result.StructSetBody(l.goStructElementTypes(t), true) + } + return result +} + +func (l *llvmDataLowerer) goStructElementTypes(t *types.Type) []llvm.Type { + fields := make([]llvm.Type, 0, len(t.Fields())*2+1) + off := int64(0) + for _, f := range t.Fields() { + if f.Offset < off { + base.Fatalf("overlapping runtime descriptor field %s", f.Sym.Name) + } + if f.Offset > off { + fields = append(fields, llvm.ArrayType(GlobalCtxt.Int8Type(), int(f.Offset-off))) + } + fields = append(fields, l.goType(f.Type)) + off = f.Offset + f.Type.Size() + } + if off < t.Size() { + fields = append(fields, llvm.ArrayType(GlobalCtxt.Int8Type(), int(t.Size()-off))) + } + return fields +} + +func (l *llvmDataLowerer) goValue(s *obj.LSym, t *types.Type, off int64, globals map[*obj.LSym]llvm.Value) llvm.Value { + // Composite values can start with a pointer relocation (for example the + // data pointer of a slice). Recurse into those values before considering a + // relocation at their base offset. + if t.Kind() != types.TSTRUCT && t.Kind() != types.TARRAY && t.Kind() != types.TSLICE && t.Kind() != types.TSTRING { + if r, ok := llvmDataRelocAt(s, off); ok { + if globals == nil { + return llvmDataRelocZero(r) + } + return llvmDataRelocValue(s, r, globals, l.data) + } + } + + switch t.Kind() { + case types.TSTRUCT: + values := make([]llvm.Value, 0, len(t.Fields())*2+1) + pos := off + for _, f := range t.Fields() { + values = append(values, llvmDataRangeFields(s, pos, off+f.Offset, globals, l.data)...) + values = append(values, l.goValue(s, f.Type, off+f.Offset, globals)) + pos = off + f.Offset + f.Type.Size() + } + values = append(values, llvmDataRangeFields(s, pos, off+t.Size(), globals, l.data)...) + typ := l.goType(t) + if l.namedRuntimeType[t] { + llvmCheckStructInitializer(typ, values, s.Name, off) + return llvm.ConstNamedStruct(typ, values) + } + return GlobalCtxt.ConstStruct(values, true) + case types.TARRAY: + values := make([]llvm.Value, t.NumElem()) + for i := range values { + values[i] = l.goValue(s, t.Elem(), off+int64(i)*t.Elem().Size(), globals) + } + return llvm.ConstArray(l.goType(t.Elem()), values) + case types.TSLICE: + ptr := l.goValue(s, types.Types[types.TUNSAFEPTR], off, globals) + len := l.goValue(s, types.Types[types.TINT], off+int64(types.PtrSize), globals) + cap := l.goValue(s, types.Types[types.TINT], off+2*int64(types.PtrSize), globals) + return GlobalCtxt.ConstStruct([]llvm.Value{ptr, len, cap}, true) + case types.TSTRING: + ptr := l.goValue(s, types.Types[types.TUNSAFEPTR], off, globals) + len := l.goValue(s, types.Types[types.TINT], off+int64(types.PtrSize), globals) + return GlobalCtxt.ConstStruct([]llvm.Value{ptr, len}, true) + case types.TPTR, types.TUNSAFEPTR: + if !llvmDataAllZero(s, off, t.Size()) { + base.Fatalf("non-relocated pointer data in Go type descriptor %s at %d", s.Name, off) + } + return llvm.ConstPointerNull(GlobalCtxt.PointerType(0)) + case types.TBOOL, types.TINT8, types.TUINT8, types.TINT16, types.TUINT16, + types.TINT32, types.TUINT32, types.TINT64, types.TUINT64, types.TINT, + types.TUINT, types.TUINTPTR: + return llvm.ConstInt(l.goType(t), llvmDataUint(s, off, t.Size()), false) + default: + base.Fatalf("unsupported runtime descriptor constant type %s", t) + return llvm.Value{} + } +} + +func llvmCheckStructInitializer(typ llvm.Type, values []llvm.Value, symbol string, off int64) { + expected := typ.StructElementTypes() + if len(expected) != len(values) { + base.Fatalf("LLVM runtime layout mismatch for %s at %d: have %d fields, want %d", symbol, off, len(values), len(expected)) + } + for i, want := range expected { + if got := values[i].Type(); got != want { + base.Fatalf("LLVM runtime layout mismatch for %s at %d field %d: got type kind %d, want %d", symbol, off, i, got.TypeKind(), want.TypeKind()) + } + } +} + +func llvmDataRangeFields(s *obj.LSym, start, end int64, globals map[*obj.LSym]llvm.Value, data map[*obj.LSym]bool) []llvm.Value { + if start == end { + return nil + } + relocs := make([]obj.Reloc, 0) + for _, r := range s.R { + if r.Type == objabi.R_KEEP { + continue + } + if int64(r.Off) >= start && int64(r.Off) < end { + relocs = append(relocs, r) + } + } + sort.Slice(relocs, func(i, j int) bool { return relocs[i].Off < relocs[j].Off }) + fields := make([]llvm.Value, 0, len(relocs)*2+1) + pos := start + for _, r := range relocs { + off, rEnd := int64(r.Off), int64(r.Off)+int64(r.Siz) + if off < pos || rEnd > end { + base.Fatalf("relocation crosses semantic descriptor boundary in %s", s.Name) + } + if off > pos { + fields = append(fields, llvmDataBytes(llvmDataBytesAt(s, pos, off-pos))) + } + if globals == nil { + fields = append(fields, llvmDataRelocZero(r)) + } else { + fields = append(fields, llvmDataRelocValue(s, r, globals, data)) + } + pos = rEnd + } + if pos < end { + fields = append(fields, llvmDataBytes(llvmDataBytesAt(s, pos, end-pos))) + } + return fields +} + +func llvmDataRelocAt(s *obj.LSym, off int64) (obj.Reloc, bool) { + for _, r := range s.R { + if r.Type != objabi.R_KEEP && int64(r.Off) == off { + return r, true + } + } + return obj.Reloc{}, false +} + +func llvmDataSize(s *obj.LSym) int64 { + if s.Size < int64(len(s.P)) { + return int64(len(s.P)) + } + return s.Size +} + +func llvmDataBytesAt(s *obj.LSym, off, size int64) []byte { + if off < 0 || size < 0 || off+size > llvmDataSize(s) { + base.Fatalf("invalid byte range [%d,%d) in %s", off, off+size, s.Name) + } + data := make([]byte, size) + if off < int64(len(s.P)) { + copy(data, s.P[off:min(off+size, int64(len(s.P)))]) + } + return data +} + +func llvmDataAllZero(s *obj.LSym, off, size int64) bool { + for _, b := range llvmDataBytesAt(s, off, size) { + if b != 0 { + return false + } + } + return true +} + +func llvmDataUint(s *obj.LSym, off, size int64) uint64 { + b := llvmDataBytesAt(s, off, size) + switch size { + case 1: + return uint64(b[0]) + case 2: + return uint64(base.Ctxt.Arch.ByteOrder.Uint16(b)) + case 4: + return uint64(base.Ctxt.Arch.ByteOrder.Uint32(b)) + case 8: + return base.Ctxt.Arch.ByteOrder.Uint64(b) + default: + base.Fatalf("unsupported scalar size %d in Go type descriptor", size) + return 0 + } +} + +func llvmRuntimeTypeName(t *types.Type) string { + // Embedded fields are independently translated by rttype.FromReflect, so + // pointer identity alone does not recognize the Type field nested inside + // ArrayType, StructType, and friends. Match its compiler layout as well. + if t == rttype.Type || llvmSameStructLayout(t, rttype.Type) { + return "go.runtime.Type" + } + switch t { + case rttype.ArrayType: + return "go.runtime.ArrayType" + case rttype.ChanType: + return "go.runtime.ChanType" + case rttype.FuncType: + return "go.runtime.FuncType" + case rttype.InterfaceType: + return "go.runtime.InterfaceType" + case rttype.MapType: + return "go.runtime.MapType" + case rttype.PtrType: + return "go.runtime.PtrType" + case rttype.SliceType: + return "go.runtime.SliceType" + case rttype.StructType: + return "go.runtime.StructType" + case rttype.IMethod: + return "go.runtime.Imethod" + case rttype.Method: + return "go.runtime.Method" + case rttype.StructField: + return "go.runtime.StructField" + case rttype.UncommonType: + return "go.runtime.UncommonType" + default: + return "" + } +} + +func llvmSameStructLayout(a, b *types.Type) bool { + if a.Kind() != types.TSTRUCT || b.Kind() != types.TSTRUCT || a.Size() != b.Size() { + return false + } + af, bf := a.Fields(), b.Fields() + if len(af) != len(bf) { + return false + } + for i := range af { + if af[i].Sym.Name != bf[i].Sym.Name || af[i].Offset != bf[i].Offset || af[i].Type.Kind() != bf[i].Type.Kind() || af[i].Type.Size() != bf[i].Type.Size() { + return false + } + } + return true +} + +func llvmSafeTypeName(name string) string { + return strings.Map(func(r rune) rune { + if r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || r == '.' || r == '_' { + return r + } + return '_' + }, name) +} diff --git a/src/cmd/compile/internal/ssa/ssa2llvm_stub.go b/src/cmd/compile/internal/ssa/ssa2llvm_stub.go index 6afb569c3958c7..42d5b73a56fa37 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm_stub.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm_stub.go @@ -9,6 +9,9 @@ func LLVMCompile(f *Func) {} func InitModule(pkg *types.Pkg) { } +func LowerGoObjTypeData() { +} + func Output(fileName string) error { return nil } diff --git a/src/cmd/vendor/github.com/goallc/go-llvm/ir.go b/src/cmd/vendor/github.com/goallc/go-llvm/ir.go index 00a118f998ecd5..d770b9dfd12100 100644 --- a/src/cmd/vendor/github.com/goallc/go-llvm/ir.go +++ b/src/cmd/vendor/github.com/goallc/go-llvm/ir.go @@ -693,6 +693,9 @@ func (v Value) Metadata(kind int) (rv Value) { func (v Value) SetMetadata(kind int, node Metadata) { C.LLVMSetMetadata2(v.C, C.unsigned(kind), node.C) } +func (v Value) SetGlobalMetadata(kind int, node Metadata) { + C.LLVMGlobalSetMetadata(v.C, C.unsigned(kind), node.C) +} // Obtain the string value of the instruction. Same as would be printed with // Value.Dump() (with two spaces at the start but no newline at the end). diff --git a/test/codegen/type_descriptor.go b/test/codegen/type_descriptor.go new file mode 100644 index 00000000000000..b12a197923a95f --- /dev/null +++ b/test/codegen/type_descriptor.go @@ -0,0 +1,26 @@ +// asmcheck + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package codegen + +// LLVM-DAG: %go.runtime.StructType = type <{ +// LLVM-DAG: %go.runtime.UncommonType = type <{ +// LLVM-DAG: %go.runtime.StructField = type <{ +// LLVM-DAG: %go.descriptor.codegen.llvmTypeDescriptor = type <{ +// LLVM-DAG: @"type:codegen.llvmTypeDescriptor" = constant %go.descriptor.codegen.llvmTypeDescriptor{{.*}}!goobj.symbol.flags{{.*}}!goobj.relocs +// LLVM-DAG: @"type:*codegen.llvmTypeDescriptor" = constant %go.descriptor._codegen.llvmTypeDescriptor{{.*}}!goobj.symbol.flags{{.*}}!goobj.relocs + +// llvmTypeDescriptor exercises the compiler-owned reflectdata path. LLVM data +// lowering must preserve the resulting Go type descriptors and relocation +// metadata rather than reconstructing their runtime layout. +type llvmTypeDescriptor struct { + p *byte + x int +} + +func useLLVMTypeDescriptor(v llvmTypeDescriptor) int { + return v.x +} diff --git a/test/codegen/type_descriptor_kinds.go b/test/codegen/type_descriptor_kinds.go new file mode 100644 index 00000000000000..849f0bc5fef0c4 --- /dev/null +++ b/test/codegen/type_descriptor_kinds.go @@ -0,0 +1,70 @@ +// asmcheck + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package codegen + +import "unsafe" + +// LLVM-DAG: %go.runtime.Type = type <{ +// LLVM-DAG: %go.runtime.ArrayType = type <{ +// LLVM-DAG: %go.runtime.ChanType = type <{ +// LLVM-DAG: %go.runtime.FuncType = type <{ +// LLVM-DAG: %go.runtime.InterfaceType = type <{ +// LLVM-DAG: %go.runtime.MapType = type <{ +// LLVM-DAG: %go.runtime.PtrType = type <{ +// LLVM-DAG: %go.runtime.SliceType = type <{ +// LLVM-DAG: %go.runtime.StructType = type <{ +// LLVM-DAG: %go.runtime.Imethod = type <{ +// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorBool = type <{ %go.runtime.Type, %go.runtime.UncommonType }> +// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorInt8 = type <{ %go.runtime.Type, %go.runtime.UncommonType }> +// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorUintptr = type <{ %go.runtime.Type, %go.runtime.UncommonType }> +// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorFloat32 = type <{ %go.runtime.Type, %go.runtime.UncommonType }> +// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorComplex128 = type <{ %go.runtime.Type, %go.runtime.UncommonType }> +// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorString = type <{ %go.runtime.Type, %go.runtime.UncommonType }> +// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorUnsafePointer = type <{ %go.runtime.Type, %go.runtime.UncommonType }> +// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorArray = type <{ %go.runtime.ArrayType, %go.runtime.UncommonType }> +// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorChan = type <{ %go.runtime.ChanType, %go.runtime.UncommonType }> +// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorFunc = type <{ %go.runtime.FuncType, %go.runtime.UncommonType, [4 x ptr] }> +// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorInterface = type <{ %go.runtime.InterfaceType, %go.runtime.UncommonType, [0 x %go.runtime.Imethod] }> +// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorMap = type <{ %go.runtime.MapType, %go.runtime.UncommonType }> +// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorPtr = type <{ %go.runtime.PtrType, %go.runtime.UncommonType }> +// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorSlice = type <{ %go.runtime.SliceType, %go.runtime.UncommonType }> +// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorStruct = type <{ %go.runtime.StructType, %go.runtime.UncommonType, [2 x %go.runtime.StructField] }> + +type llvmDescriptorBool bool +type llvmDescriptorInt8 int8 +type llvmDescriptorInt16 int16 +type llvmDescriptorInt32 int32 +type llvmDescriptorInt64 int64 +type llvmDescriptorInt int +type llvmDescriptorUint8 uint8 +type llvmDescriptorUint16 uint16 +type llvmDescriptorUint32 uint32 +type llvmDescriptorUint64 uint64 +type llvmDescriptorUint uint +type llvmDescriptorUintptr uintptr +type llvmDescriptorFloat32 float32 +type llvmDescriptorFloat64 float64 +type llvmDescriptorComplex64 complex64 +type llvmDescriptorComplex128 complex128 +type llvmDescriptorString string +type llvmDescriptorUnsafePointer unsafe.Pointer + +type llvmDescriptorArray [3]uint16 +type llvmDescriptorChan chan int +type llvmDescriptorFunc func(int, string) (bool, error) +type llvmDescriptorInterface interface{} +type llvmDescriptorMap map[int]string +type llvmDescriptorPtr *int +type llvmDescriptorSlice []byte +type llvmDescriptorStruct struct { + x int + y *byte +} + +func useLLVMDescriptorKinds(v llvmDescriptorStruct) int { + return v.x +} diff --git a/test/llvm_tests.json b/test/llvm_tests.json index 8697465998a956..05277a60d2c034 100644 --- a/test/llvm_tests.json +++ b/test/llvm_tests.json @@ -2,7 +2,9 @@ "codegen": { "whitelist": { "codegen/divmod.go": "integer division, remainder, overflow handling, and tuple ABI", - "codegen/logic.go": "integer logic and conditional control flow" + "codegen/logic.go": "integer logic and conditional control flow", + "codegen/type_descriptor.go": "compiler-owned runtime type descriptor data and GoObj metadata", + "codegen/type_descriptor_kinds.go": "all runtime type descriptor layouts and variable descriptor data" }, "blacklist": { "codegen/*": "LLVM lowering support has not reached this complete source file yet" @@ -10,7 +12,10 @@ }, "runtime": { "whitelist": { - "helloworld.go": "basic main-to-runtime call and execution" + "helloworld.go": "basic main-to-runtime call and execution", + "llvm_type_descriptor.go": "type descriptor data, ABI-internal equality closure, GoObj link, and execution", + "llvm_type_descriptor_kinds.go": "all runtime type descriptor kinds, GoObj link, and execution", + "llvm_type_descriptor_map.go": "typed map descriptor layout, R_KEEP reachability edge, GoObj link, and execution" }, "blacklist": { "*": "LLVM lowering or runtime ABI support has not reached this test yet" diff --git a/test/llvm_type_descriptor.go b/test/llvm_type_descriptor.go new file mode 100644 index 00000000000000..7c95043814a01c --- /dev/null +++ b/test/llvm_type_descriptor.go @@ -0,0 +1,18 @@ +// run + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// This named type requires reflectdata to emit both its descriptor and the +// descriptor for its pointer type. Keep the program otherwise simple so this +// is an execution test of LLVM data lowering and GoObj linking. +type llvmTypeDescriptor struct { + value int +} + +func main() { + println(llvmTypeDescriptor{value: 7}.value) +} diff --git a/test/llvm_type_descriptor.out b/test/llvm_type_descriptor.out new file mode 100644 index 00000000000000..7f8f011eb73d60 --- /dev/null +++ b/test/llvm_type_descriptor.out @@ -0,0 +1 @@ +7 diff --git a/test/llvm_type_descriptor_kinds.go b/test/llvm_type_descriptor_kinds.go new file mode 100644 index 00000000000000..697b6c0a9edf1d --- /dev/null +++ b/test/llvm_type_descriptor_kinds.go @@ -0,0 +1,46 @@ +// run + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "unsafe" + +// These declarations exercise every Go runtime descriptor kind while keeping +// execution independent of interface, map, channel, and function operations. +type descriptorBool bool +type descriptorInt8 int8 +type descriptorInt16 int16 +type descriptorInt32 int32 +type descriptorInt64 int64 +type descriptorInt int +type descriptorUint8 uint8 +type descriptorUint16 uint16 +type descriptorUint32 uint32 +type descriptorUint64 uint64 +type descriptorUint uint +type descriptorUintptr uintptr +type descriptorFloat32 float32 +type descriptorFloat64 float64 +type descriptorComplex64 complex64 +type descriptorComplex128 complex128 +type descriptorString string +type descriptorUnsafePointer unsafe.Pointer + +type descriptorArray [3]uint16 +type descriptorChan chan int +type descriptorFunc func(int, string) (bool, error) +type descriptorInterface interface{} +type descriptorMap map[int]string +type descriptorPtr *int +type descriptorSlice []byte +type descriptorStruct struct { + x int + y *byte +} + +func main() { + println(7) +} diff --git a/test/llvm_type_descriptor_kinds.out b/test/llvm_type_descriptor_kinds.out new file mode 100644 index 00000000000000..7f8f011eb73d60 --- /dev/null +++ b/test/llvm_type_descriptor_kinds.out @@ -0,0 +1 @@ +7 diff --git a/test/llvm_type_descriptor_map.go b/test/llvm_type_descriptor_map.go new file mode 100644 index 00000000000000..5e1b43ec1a7e27 --- /dev/null +++ b/test/llvm_type_descriptor_map.go @@ -0,0 +1,18 @@ +// run + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// A named map descriptor carries an R_KEEP edge to its underlying map type. +// The static interface conversion forces reflectdata to materialize both +// descriptors without requiring map-operation lowering. +type llvmMapDescriptor map[int]string + +var keep any = (llvmMapDescriptor)(nil) + +func main() { + println(7) +} diff --git a/test/llvm_type_descriptor_map.out b/test/llvm_type_descriptor_map.out new file mode 100644 index 00000000000000..7f8f011eb73d60 --- /dev/null +++ b/test/llvm_type_descriptor_map.out @@ -0,0 +1 @@ +7 From 65254c71939f9fe9d3cebe459a969e316473b88d Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 28 Jul 2026 00:58:56 +0800 Subject: [PATCH 02/17] cmd/compile: lower uncommon method descriptors to LLVM --- src/cmd/compile/internal/ssa/ssa2llvm.go | 35 +++++++++++++++++++++++- test/codegen/type_descriptor_methods.go | 34 +++++++++++++++++++++++ test/llvm_tests.json | 6 ++-- test/llvm_type_descriptor_methods.go | 28 +++++++++++++++++++ test/llvm_type_descriptor_methods.out | 1 + 5 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 test/codegen/type_descriptor_methods.go create mode 100644 test/llvm_type_descriptor_methods.go create mode 100644 test/llvm_type_descriptor_methods.out diff --git a/src/cmd/compile/internal/ssa/ssa2llvm.go b/src/cmd/compile/internal/ssa/ssa2llvm.go index 61fb0ffe0dc7d8..1a8242b6da60b7 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm.go @@ -7,6 +7,7 @@ import ( "cmd/compile/internal/ir" "cmd/compile/internal/types" "cmd/internal/obj" + "cmd/internal/src" "internal/buildcfg" "github.com/goallc/go-llvm" @@ -15,6 +16,7 @@ import ( type LLVMFuncContext struct { BBs map[ID]llvm.BasicBlock Vs map[ID]llvm.Value + Locals map[llvmLocalKey]llvm.Value F *Func LF llvm.Value b llvm.Builder @@ -22,6 +24,15 @@ type LLVMFuncContext struct { ResultCount int } +// SSA may clone an ir.Name while retaining the same logical source +// declaration. Pointer identity is therefore not a stable stack-slot key. +// Package symbol plus declaration position distinguishes shadowed locals while +// merging those clones. +type llvmLocalKey struct { + Sym *types.Sym + Pos src.XPos +} + // LLVM's GoABIInternal calling convention has numeric ID 22. Keep the // prototype lowering on the Go register ABI so llc emits GoObj symbols that // the standard Go linker can call directly. @@ -322,7 +333,18 @@ func (lfc *LLVMFuncContext) GenLV(v *Value) llvm.Value { case OpKeepAlive: lVal = arg1() case OpLocalAddr: - lVal = lfc.b.CreateAlloca(getLLVMType(v.Type.Elem()), v.String()) + sym := auxToSym(v.Aux) + name, ok := sym.(*ir.Name) + if !ok { + v.Fatalf("local address has no stack symbol") + } + key := llvmLocalKey{Sym: name.Sym(), Pos: name.Pos()} + if slot, ok := lfc.Locals[key]; ok { + lVal = slot + } else { + lVal = lfc.b.CreateAlloca(getLLVMType(name.Type()), v.String()) + lfc.Locals[key] = lVal + } case OpArg: lVal = lfc.paramForArg(v) lVal.SetName(v.Aux.(*ir.Name).Sym().Name) @@ -507,6 +529,16 @@ func (lfc *LLVMFuncContext) GenLV(v *Value) llvm.Value { } case OpLoad: lVal = lfc.b.CreateLoad(getLLVMType(v.Type), arg0(), v.String()) + case OpNilCheck: + // Preserve Go's explicit nil-check side effect even when no following + // load uses the checked pointer. A volatile byte load faults at address + // zero and cannot be removed by LLVM. The SSA value itself is the + // original pointer. + p := arg0() + check := lfc.b.CreateLoad(GlobalCtxt.Int8Type(), p, v.String()+".nilcheck") + check.SetVolatile(true) + check.SetAlignment(1) + lVal = p case OpStore: lVal = lfc.b.CreateStore(arg1(), arg0()) case OpStructSelect: @@ -588,6 +620,7 @@ func LLVMCompile(f *Func) { FCtxt := &LLVMFuncContext{ BBs: map[ID]llvm.BasicBlock{}, Vs: map[ID]llvm.Value{}, + Locals: map[llvmLocalKey]llvm.Value{}, F: f, b: GlobalCtxt.NewBuilder(), ReturnType: sig.ReturnType, diff --git a/test/codegen/type_descriptor_methods.go b/test/codegen/type_descriptor_methods.go new file mode 100644 index 00000000000000..a3420c9e38e512 --- /dev/null +++ b/test/codegen/type_descriptor_methods.go @@ -0,0 +1,34 @@ +// asmcheck + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package codegen + +// LLVM-DAG: %go.runtime.Method = type <{ i32, i32, i32, i32 }> +// LLVM-DAG: %go.descriptor.codegen.llvmMethodType = type <{ %go.runtime.StructType, %go.runtime.UncommonType, [1 x %go.runtime.StructField], [2 x %go.runtime.Method] }> +// LLVM-DAG: %go.descriptor._codegen.llvmMethodType = type <{ %go.runtime.PtrType, %go.runtime.UncommonType, [3 x %go.runtime.Method] }> +// LLVM-DAG: @"type:codegen.llvmMethodType" = constant %go.descriptor.codegen.llvmMethodType +// LLVM-DAG: @"type:*codegen.llvmMethodType" = constant %go.descriptor._codegen.llvmMethodType +// LLVM-DAG: !{{[0-9]+}} = !{i32 {{[0-9]+}}, i32 26} + +type llvmMethodType struct { + value int +} + +func (v llvmMethodType) Value(delta int) int { + return v.value + delta +} + +func (v llvmMethodType) hidden() int { + return v.value +} + +func (v *llvmMethodType) Pointer(delta int) int { + return v.value + delta +} + +func useLLVMMethodType(v llvmMethodType) int { + return v.Value(2) + (&v).Pointer(3) +} diff --git a/test/llvm_tests.json b/test/llvm_tests.json index 05277a60d2c034..a458d706f3bbea 100644 --- a/test/llvm_tests.json +++ b/test/llvm_tests.json @@ -4,7 +4,8 @@ "codegen/divmod.go": "integer division, remainder, overflow handling, and tuple ABI", "codegen/logic.go": "integer logic and conditional control flow", "codegen/type_descriptor.go": "compiler-owned runtime type descriptor data and GoObj metadata", - "codegen/type_descriptor_kinds.go": "all runtime type descriptor layouts and variable descriptor data" + "codegen/type_descriptor_kinds.go": "all runtime type descriptor layouts and variable descriptor data", + "codegen/type_descriptor_methods.go": "uncommon type data, concrete method tables, wrappers, and R_METHODOFF" }, "blacklist": { "codegen/*": "LLVM lowering support has not reached this complete source file yet" @@ -15,7 +16,8 @@ "helloworld.go": "basic main-to-runtime call and execution", "llvm_type_descriptor.go": "type descriptor data, ABI-internal equality closure, GoObj link, and execution", "llvm_type_descriptor_kinds.go": "all runtime type descriptor kinds, GoObj link, and execution", - "llvm_type_descriptor_map.go": "typed map descriptor layout, R_KEEP reachability edge, GoObj link, and execution" + "llvm_type_descriptor_map.go": "typed map descriptor layout, R_KEEP reachability edge, GoObj link, and execution", + "llvm_type_descriptor_methods.go": "uncommon concrete methods, method wrappers, direct calls, GoObj link, and execution" }, "blacklist": { "*": "LLVM lowering or runtime ABI support has not reached this test yet" diff --git a/test/llvm_type_descriptor_methods.go b/test/llvm_type_descriptor_methods.go new file mode 100644 index 00000000000000..2c0dc96a5cb22d --- /dev/null +++ b/test/llvm_type_descriptor_methods.go @@ -0,0 +1,28 @@ +// run + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +type llvmMethodType struct { + value int +} + +//go:noinline +func (v llvmMethodType) Value(delta int) int { + return v.value + delta +} + +func (v llvmMethodType) hidden() int { + return v.value +} + +func (v *llvmMethodType) Pointer(delta int) int { + return v.value + delta +} + +func main() { + println(llvmMethodType{value: 10}.Value(2)) +} diff --git a/test/llvm_type_descriptor_methods.out b/test/llvm_type_descriptor_methods.out new file mode 100644 index 00000000000000..48082f72f087ce --- /dev/null +++ b/test/llvm_type_descriptor_methods.out @@ -0,0 +1 @@ +12 From dc081cebbcc68adda8a33b215388f810aff82f32 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 28 Jul 2026 01:21:13 +0800 Subject: [PATCH 03/17] cmd/compile: lower interface calls and static itabs to LLVM --- doc/goallc-llvm-goobj.md | 18 ++- src/cmd/compile/internal/ssa/llvmdata.go | 77 +++++++++- src/cmd/compile/internal/ssa/llvmtypeddata.go | 141 +++++++++++++++--- src/cmd/compile/internal/ssa/ssa2llvm.go | 47 +++++- test/codegen/interface.go | 52 +++++++ test/llvm_interface.go | 34 +++++ test/llvm_interface.out | 1 + test/llvm_tests.json | 6 +- 8 files changed, 351 insertions(+), 25 deletions(-) create mode 100644 test/codegen/interface.go create mode 100644 test/llvm_interface.go create mode 100644 test/llvm_interface.out diff --git a/doc/goallc-llvm-goobj.md b/doc/goallc-llvm-goobj.md index a8c2477e201b74..be834b745477b2 100644 --- a/doc/goallc-llvm-goobj.md +++ b/doc/goallc-llvm-goobj.md @@ -54,6 +54,19 @@ LLVM IR 使用 global attachment `!goobj.symbol.flags` 和 `!goobj.relocs` 交 `__.PKGDEF + _go_.o` 两个有意义的 archive members;不会生成或合并 native data object。 +静态 interface conversion 还会把带有 `ItabInfo` 的 roots 纳入同一数据闭包。 +itab 使用 `%go.runtime.ITab` 和按实际方法数扩展的 packed LLVM struct 表达; +固定的 `Fun[0]` 后面按 LSym 的最终大小追加 `uintptr` 数组,因此单方法和多方法 +itab 都保持 runtime ABI 的连续方法表布局。方法入口仍保留 weak `R_ADDR` +relocation。interface 方法调用从 itab 槽加载入口,转成 LLVM function pointer, +并以原 SSA `AuxCall` 的 ABIInternal signature 发出 indirect call。 + +Go linker 的 dead-method elimination 还依赖函数上的零宽度 +`R_USEIFACE`、`R_USEIFACEMETHOD` 和 `R_USENAMEDMETHOD`。它们不对应 LLVM +机器指令或地址常量,因此 compiler 用 `!goobj.marker_relocs` 保存精确的 +relocation type、addend 和 target name;GoObj writer 将其恢复到源函数的 +relocation 列表。普通 LLVM call graph 不能替代这项 linker 契约。 + 当前实现只 lower type-rooted data closure,尚未把所有 `dumpdata` 产物泛化为 LLVM data lowering。type descriptor 中未被当前 schema 覆盖的尾部数据,以及其他 data roots,保守地维持 bytes/relocation fallback;新增 schema 或 root 前必须先明确 @@ -228,7 +241,10 @@ go test cmd/internal/testdir -run='^Test$/^LLVM$' -v - 当前 target triple 仅配置了 `darwin/arm64` 和 `linux/amd64`。 - LLVM SSA lowering 仍不完整;复杂 SSA op、GC pointer liveness、defer/panic、 - closure/interface、完整 ABI/DWARF 等尚未达到通用正确性。 + closure、动态 interface assertion/switch、完整 ABI/DWARF 等尚未达到通用正确性。 +- 当前 interface 范围是 compiler 能静态生成 itab 的 concrete-to-interface + conversion,以及对应的 ABIInternal 间接方法调用;动态 itab construction 和 + assertion/switch 不在本阶段范围内。 - 每个经 LLVM 替换的 package 都必须由 `llc` 生成完整的 `_go_.o`,不能与 原生 compiler object 混合。 - `!goobj.config` 是这条开发链路的稳定交接点。新增 header 配置时应新增独立 diff --git a/src/cmd/compile/internal/ssa/llvmdata.go b/src/cmd/compile/internal/ssa/llvmdata.go index 3d907e26d52a07..8ac1f2f97acabf 100644 --- a/src/cmd/compile/internal/ssa/llvmdata.go +++ b/src/cmd/compile/internal/ssa/llvmdata.go @@ -38,7 +38,12 @@ func LowerGoObjTypeData() { } } for _, s := range base.Ctxt.Data { - if s.TypeInfo() != nil { + if s.TypeInfo() != nil || s.ItabInfo() != nil { + visit(s) + } + } + if currentLLVMDataLowerer != nil { + for s := range currentLLVMDataLowerer.roots { visit(s) } } @@ -52,7 +57,13 @@ func LowerGoObjTypeData() { syms = append(syms, s) } sort.Slice(syms, func(i, j int) bool { return syms[i].Name < syms[j].Name }) - lowerer := newLLVMDataLowerer(data) + lowerer := currentLLVMDataLowerer + if lowerer == nil { + lowerer = newLLVMDataLowerer(data) + currentLLVMDataLowerer = lowerer + } else { + lowerer.data = data + } globals := make(map[*obj.LSym]llvm.Value, len(syms)) for _, s := range syms { @@ -80,6 +91,44 @@ func LowerGoObjTypeData() { } } +// llvmGoDataRef returns the module-global address for a compiler LSym. Local +// data symbols use the same semantic type cache as final data lowering so an +// early OpAddr and the later initializer always agree on the global type. +func llvmGoDataRef(s *obj.LSym) llvm.Value { + if s == nil { + base.Fatalf("nil Go data symbol in LLVM lowering") + } + if g := CurrentModule.NamedGlobal(s.Name); !g.IsNil() { + return g + } + if currentLLVMDataLowerer == nil { + currentLLVMDataLowerer = newLLVMDataLowerer(make(map[*obj.LSym]bool)) + } + currentLLVMDataLowerer.roots[s] = true + local := false + for _, candidate := range base.Ctxt.Data { + if candidate == s { + local = true + break + } + } + if !local && !llvmDataSymbolKindSupported(s.Type) { + return llvm.AddGlobal(CurrentModule, GlobalCtxt.Int8Type(), s.Name) + } + currentLLVMDataLowerer.data[s] = true + return llvm.AddGlobal(CurrentModule, currentLLVMDataLowerer.dataType(s), s.Name) +} + +func llvmDataSymbolKindSupported(kind objabi.SymKind) bool { + switch kind { + case objabi.SRODATA, objabi.SRODATAFIPS, objabi.SNOPTRDATA, objabi.SNOPTRDATAFIPS, + objabi.SDATA, objabi.SDATAFIPS, objabi.SBSS, objabi.SNOPTRBSS: + return true + default: + return false + } +} + func llvmDataType(s *obj.LSym) llvm.Type { fields := llvmDataFields(s, nil, nil) return GlobalCtxt.ConstStruct(fields, true).Type() @@ -307,6 +356,30 @@ func setGoObjKeepMetadata(g llvm.Value, s *obj.LSym) { } } +// Interface dead-method elimination uses zero-width marker relocations on the +// containing function. LLVM calls and globals cannot encode those records, so +// carry the exact Go relocation type, addend, and target in function metadata. +func setGoObjFunctionRelocMetadata(fn llvm.Value, s *obj.LSym) { + entries := make([]llvm.Metadata, 0) + for _, r := range s.R { + switch r.Type { + case objabi.R_USEIFACE, objabi.R_USEIFACEMETHOD, objabi.R_USENAMEDMETHOD: + if r.Sym == nil { + base.Fatalf("nil interface marker target in %s", s.Name) + } + llvmGoDataRef(r.Sym) + entries = append(entries, GlobalCtxt.MDNode([]llvm.Metadata{ + llvm.ConstInt(GlobalCtxt.Int32Type(), uint64(uint16(r.Type)), false).ConstantAsMetadata(), + llvm.ConstInt(GlobalCtxt.Int64Type(), uint64(r.Add), true).ConstantAsMetadata(), + GlobalCtxt.MDString(r.Sym.Name), + })) + } + } + if len(entries) != 0 { + fn.SetGlobalMetadata(GlobalCtxt.MDKindID("goobj.marker_relocs"), GlobalCtxt.MDNode(entries)) + } +} + func llvmDataStorageRelocs(s *obj.LSym) []obj.Reloc { relocs := make([]obj.Reloc, 0, len(s.R)) for _, r := range s.R { diff --git a/src/cmd/compile/internal/ssa/llvmtypeddata.go b/src/cmd/compile/internal/ssa/llvmtypeddata.go index 2ab38f4b033adf..c0f8dadd4124fb 100644 --- a/src/cmd/compile/internal/ssa/llvmtypeddata.go +++ b/src/cmd/compile/internal/ssa/llvmtypeddata.go @@ -22,6 +22,7 @@ import ( // retaining the generic representation for auxiliary linker data. type llvmDataLowerer struct { data map[*obj.LSym]bool + roots map[*obj.LSym]bool runtimeTypes map[*types.Type]llvm.Type descriptorTypes map[*obj.LSym]llvm.Type namedRuntimeType map[*types.Type]bool @@ -30,6 +31,7 @@ type llvmDataLowerer struct { func newLLVMDataLowerer(data map[*obj.LSym]bool) *llvmDataLowerer { return &llvmDataLowerer{ data: data, + roots: make(map[*obj.LSym]bool), runtimeTypes: make(map[*types.Type]llvm.Type), descriptorTypes: make(map[*obj.LSym]llvm.Type), namedRuntimeType: make(map[*types.Type]bool), @@ -40,6 +42,9 @@ func (l *llvmDataLowerer) dataType(s *obj.LSym) llvm.Type { if t := llvmDescriptorGoType(s); t != nil { return l.descriptorType(s, t) } + if s.ItabInfo() != nil { + return l.itabType(s) + } return llvmDataType(s) } @@ -47,6 +52,9 @@ func (l *llvmDataLowerer) dataInitializer(s *obj.LSym, globals map[*obj.LSym]llv if t := llvmDescriptorGoType(s); t != nil { return l.descriptorInitializer(s, t, globals) } + if s.ItabInfo() != nil { + return l.itabInitializer(s, globals) + } return llvmDataInitializer(s, globals, l.data) } @@ -60,8 +68,76 @@ func llvmDescriptorGoType(s *obj.LSym) *types.Type { } type llvmDescriptorPart struct { - off int64 - typ *types.Type + off int64 + typ *types.Type + arrayElem *types.Type + arrayCount int64 +} + +func (p llvmDescriptorPart) size() int64 { + if p.typ != nil { + return p.typ.Size() + } + return p.arrayElem.Size() * p.arrayCount +} + +func (l *llvmDataLowerer) partType(p llvmDescriptorPart) llvm.Type { + if p.typ != nil { + return l.goType(p.typ) + } + return llvm.ArrayType(l.goType(p.arrayElem), int(p.arrayCount)) +} + +func (l *llvmDataLowerer) partValue(s *obj.LSym, p llvmDescriptorPart, globals map[*obj.LSym]llvm.Value) llvm.Value { + if p.typ != nil { + return l.goValue(s, p.typ, p.off, globals) + } + values := make([]llvm.Value, p.arrayCount) + for i := range values { + values[i] = l.goValue(s, p.arrayElem, p.off+int64(i)*p.arrayElem.Size(), globals) + } + return llvm.ConstArray(l.goType(p.arrayElem), values) +} + +func llvmItabParts(s *obj.LSym) []llvmDescriptorPart { + size := llvmDataSize(s) + fixed := rttype.ITab.Size() + if size < fixed || (size-fixed)%int64(types.PtrSize) != 0 { + base.Fatalf("invalid runtime itab size %d for %s", size, s.Name) + } + parts := []llvmDescriptorPart{{off: 0, typ: rttype.ITab}} + if tail := (size - fixed) / int64(types.PtrSize); tail != 0 { + parts = append(parts, llvmDescriptorPart{ + off: fixed, + arrayElem: types.Types[types.TUINTPTR], + arrayCount: tail, + }) + } + return parts +} + +func (l *llvmDataLowerer) itabType(s *obj.LSym) llvm.Type { + if result, ok := l.descriptorTypes[s]; ok { + return result + } + name := "go.itab." + llvmSafeTypeName(strings.TrimPrefix(s.Name, "go:itab.")) + result := CurrentModule.GetTypeByName(name) + if result.IsNil() { + result = GlobalCtxt.StructCreateNamed(name) + } + l.descriptorTypes[s] = result + fields := l.descriptorFields(s, llvmItabParts(s), nil) + fieldTypes := make([]llvm.Type, len(fields)) + for i, field := range fields { + fieldTypes[i] = field.Type() + } + result.StructSetBody(fieldTypes, true) + return result +} + +func (l *llvmDataLowerer) itabInitializer(s *obj.LSym, globals map[*obj.LSym]llvm.Value) llvm.Value { + typ := l.itabType(s) + return llvm.ConstNamedStruct(typ, l.descriptorFields(s, llvmItabParts(s), globals)) } // descriptorParts mirrors reflectdata.writeType. It uses the runtime ABI @@ -74,7 +150,8 @@ type llvmDescriptorPart struct { // the descriptor instead of silently treating a runtime field as opaque bytes. func descriptorParts(t *types.Type, size int64) []llvmDescriptorPart { var rt *types.Type - var variable *types.Type + var variableElem *types.Type + var variableCount int64 switch t.Kind() { default: rt = rttype.Type @@ -86,24 +163,27 @@ func descriptorParts(t *types.Type, size int64) []llvmDescriptorPart { rt = rttype.ChanType case types.TFUNC: rt = rttype.FuncType - variable = types.NewArray(types.Types[types.TUNSAFEPTR], int64(t.NumRecvs()+t.NumParams()+t.NumResults())) + variableElem = types.Types[types.TUNSAFEPTR] + variableCount = int64(t.NumRecvs() + t.NumParams() + t.NumResults()) case types.TINTER: rt = rttype.InterfaceType - variable = types.NewArray(rttype.IMethod, int64(len(t.AllMethods()))) + variableElem = rttype.IMethod + variableCount = int64(len(t.AllMethods())) case types.TMAP: rt = rttype.MapType case types.TPTR: rt = rttype.PtrType case types.TSTRUCT: rt = rttype.StructType - variable = types.NewArray(rttype.StructField, int64(t.NumFields())) + variableElem = rttype.StructField + variableCount = int64(t.NumFields()) } parts := []llvmDescriptorPart{{off: 0, typ: rt}} off := rt.Size() variableSize := int64(0) - if variable != nil { - variableSize = variable.Size() + if variableElem != nil { + variableSize = variableElem.Size() * variableCount } trailing := size - off - variableSize if trailing < 0 { @@ -121,17 +201,24 @@ func descriptorParts(t *types.Type, size int64) []llvmDescriptorPart { off += rttype.UncommonType.Size() trailing -= rttype.UncommonType.Size() } - if variable != nil { - parts = append(parts, llvmDescriptorPart{off: off, typ: variable}) - off += variable.Size() + if variableElem != nil { + parts = append(parts, llvmDescriptorPart{ + off: off, + arrayElem: variableElem, + arrayCount: variableCount, + }) + off += variableSize } if trailing%rttype.Method.Size() != 0 { base.Fatalf("invalid runtime method table size %d for %s", trailing, t) } if n := trailing / rttype.Method.Size(); n != 0 { - methods := types.NewArray(rttype.Method, n) - parts = append(parts, llvmDescriptorPart{off: off, typ: methods}) - off += methods.Size() + parts = append(parts, llvmDescriptorPart{ + off: off, + arrayElem: rttype.Method, + arrayCount: n, + }) + off += rttype.Method.Size() * n } if off != size { base.Fatalf("runtime descriptor layout for %s has size %d, want %d", t, off, size) @@ -168,12 +255,12 @@ func (l *llvmDataLowerer) descriptorFields(s *obj.LSym, parts []llvmDescriptorPa fields := make([]llvm.Value, 0, len(parts)*2+1) pos := int64(0) for _, part := range parts { - if part.off < pos || part.off+part.typ.Size() > llvmDataSize(s) { + if part.off < pos || part.off+part.size() > llvmDataSize(s) { base.Fatalf("invalid semantic descriptor layout for %s", s.Name) } fields = append(fields, llvmDataRangeFields(s, pos, part.off, globals, l.data)...) - fields = append(fields, l.goValue(s, part.typ, part.off, globals)) - pos = part.off + part.typ.Size() + fields = append(fields, l.partValue(s, part, globals)) + pos = part.off + part.size() } fields = append(fields, llvmDataRangeFields(s, pos, llvmDataSize(s), globals, l.data)...) if len(fields) == 0 { @@ -260,10 +347,13 @@ func (l *llvmDataLowerer) goValue(s *obj.LSym, t *types.Type, off int64, globals // relocation at their base offset. if t.Kind() != types.TSTRUCT && t.Kind() != types.TARRAY && t.Kind() != types.TSLICE && t.Kind() != types.TSTRING { if r, ok := llvmDataRelocAt(s, off); ok { + var value llvm.Value if globals == nil { - return llvmDataRelocZero(r) + value = llvmDataRelocZero(r) + } else { + value = llvmDataRelocValue(s, r, globals, l.data) } - return llvmDataRelocValue(s, r, globals, l.data) + return llvmCoerceDataReloc(value, l.goType(t), s, off) } } @@ -313,6 +403,17 @@ func (l *llvmDataLowerer) goValue(s *obj.LSym, t *types.Type, off int64, globals } } +func llvmCoerceDataReloc(value llvm.Value, want llvm.Type, s *obj.LSym, off int64) llvm.Value { + if value.Type() == want { + return value + } + if value.Type().TypeKind() == llvm.PointerTypeKind && want.TypeKind() == llvm.IntegerTypeKind { + return llvm.ConstPtrToInt(value, want) + } + base.Fatalf("relocation in %s at %d has LLVM type kind %d, want %d", s.Name, off, value.Type().TypeKind(), want.TypeKind()) + return llvm.Value{} +} + func llvmCheckStructInitializer(typ llvm.Type, values []llvm.Value, symbol string, off int64) { expected := typ.StructElementTypes() if len(expected) != len(values) { @@ -447,6 +548,8 @@ func llvmRuntimeTypeName(t *types.Type) string { return "go.runtime.StructField" case rttype.UncommonType: return "go.runtime.UncommonType" + case rttype.ITab: + return "go.runtime.ITab" default: return "" } diff --git a/src/cmd/compile/internal/ssa/ssa2llvm.go b/src/cmd/compile/internal/ssa/ssa2llvm.go index 1a8242b6da60b7..50d2615cf1c250 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm.go @@ -276,6 +276,38 @@ func (lfc *LLVMFuncContext) staticCall(v *Value) llvm.Value { return call } +func (lfc *LLVMFuncContext) indirectCall(v *Value, argStart int) llvm.Value { + aux := auxToCall(v.Aux) + if aux == nil { + v.Fatalf("indirect call has no ABI information") + } + if got, want := len(v.Args)-argStart-1, int(aux.NArgs()); got != want { + v.Fatalf("indirect call has %d LLVM arguments, want %d", got, want) + } + + sig := llvmSignature(aux) + cc := llvmCallConv(aux.ABI().Which()) + code := lfc.GenLV(v.Args[0]) + if code.Type().TypeKind() == llvm.IntegerTypeKind { + code = lfc.b.CreateIntToPtr(code, GlobalCtxt.PointerType(0), v.String()+".code") + } + args := make([]llvm.Value, 0, aux.NArgs()) + for i := int64(0); i < aux.NArgs(); i++ { + arg := lfc.GenLV(v.Args[argStart+int(i)]) + if got, want := arg.Type(), sig.Type.ParamTypes()[i]; got != want { + v.Fatalf("argument %d to indirect call has incompatible LLVM type", i) + } + args = append(args, arg) + } + name := v.String() + if sig.ResultCount == 0 { + name = "" + } + call := lfc.b.CreateCall(sig.Type, code, args, name) + call.SetInstructionCallConv(cc) + return call +} + var llvmBoundsPanicNames = [...]string{ BoundsIndex: "runtime.goPanicIndex", BoundsIndexU: "runtime.goPanicIndexU", @@ -345,6 +377,12 @@ func (lfc *LLVMFuncContext) GenLV(v *Value) llvm.Value { lVal = lfc.b.CreateAlloca(getLLVMType(name.Type()), v.String()) lfc.Locals[key] = lVal } + case OpAddr: + sym, ok := v.Aux.(*obj.LSym) + if !ok { + v.Fatalf("global address has non-LSym auxiliary %T", v.Aux) + } + lVal = llvmGoDataRef(sym) case OpArg: lVal = lfc.paramForArg(v) lVal.SetName(v.Aux.(*ir.Name).Sym().Name) @@ -490,13 +528,17 @@ func (lfc *LLVMFuncContext) GenLV(v *Value) llvm.Value { lVal = lfc.b.CreateGEP(getLLVMType(v.Type.Elem()), arg0(), []llvm.Value{arg1()}, v.String()) case OpStaticCall, OpStaticLECall: lVal = lfc.staticCall(v) + case OpInterCall, OpInterLECall: + // arg0 is the code pointer loaded from the itab. Interface method + // ABIs receive the interface data word as their first real argument. + lVal = lfc.indirectCall(v, 1) case OpPanicBounds: lVal = lfc.panicBounds(v) case OpSelectN: sel := int(auxIntToInt64(v.AuxInt)) src := v.Args[0] switch src.Op { - case OpStaticCall, OpStaticLECall: + case OpStaticCall, OpStaticLECall, OpInterCall, OpInterLECall: aux := auxToCall(src.Aux) call := lfc.GenLV(src) switch { @@ -632,6 +674,7 @@ func LLVMCompile(f *Func) { if FCtxt.LF.BasicBlocksCount() != 0 { f.fe.Fatalf(f.Entry.Pos, "duplicate LLVM definition for %s", f.OwnAux.Fn.Name) } + setGoObjFunctionRelocMetadata(FCtxt.LF, f.OwnAux.Fn) for _, BB := range f.Blocks { FCtxt.BBs[BB.ID] = GlobalCtxt.AddBasicBlock(FCtxt.LF, BB.String()) } @@ -661,6 +704,7 @@ func LLVMCompile(f *Func) { var CurrentModule llvm.Module var type2lTypes = map[*types.Type]llvm.Type{} var goObjConfigWritten bool +var currentLLVMDataLowerer *llvmDataLowerer var GlobalCtxt = llvm.GlobalContext() @@ -789,6 +833,7 @@ func InitModule(pkg *types.Pkg) { CurrentModule = GlobalCtxt.NewModule(pkg.Path) CurrentModule.SetTarget(goObjTargetTriple()) goObjConfigWritten = false + currentLLVMDataLowerer = newLLVMDataLowerer(make(map[*obj.LSym]bool)) } // goObjTargetTriple identifies the GoObj target that llc should use when it diff --git a/test/codegen/interface.go b/test/codegen/interface.go new file mode 100644 index 00000000000000..462ca2b91e6836 --- /dev/null +++ b/test/codegen/interface.go @@ -0,0 +1,52 @@ +// asmcheck + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package codegen + +// LLVM-DAG: %go.runtime.ITab = type <{ ptr, ptr, i32, [4 x i8], [1 x i64] }> +// LLVM-DAG: %go.itab.codegen.llvmInterfaceValue_codegen.llvmInterface = type <{ %go.runtime.ITab, [1 x i64] }> +// LLVM-DAG: @"go:itab.codegen.llvmInterfaceValue,codegen.llvmInterface" = constant %go.itab.codegen.llvmInterfaceValue_codegen.llvmInterface +// LLVM-DAG: ptrtoint (ptr @"codegen.(*llvmInterfaceValue).Double" to i64) +// LLVM-DAG: ptrtoint (ptr @"codegen.(*llvmInterfaceValue).Value" to i64) +// LLVM-DAG: !goobj.symbol.flags +// LLVM-DAG: !goobj.relocs +// LLVM-LABEL: define goabiinternal i64 @codegen.useLLVMInterface( +// LLVM-SAME: !goobj.marker_relocs +// LLVM-LABEL: define goabiinternal i64 @codegen.llvmInterface.Value( +// LLVM: inttoptr i64 +// LLVM: call goabiinternal i64 % +// LLVM-SAME: (ptr +// LLVM-SAME: i64 +// LLVM-LABEL: define goabiinternal i64 @codegen.llvmInterface.Double( +// LLVM: inttoptr i64 +// LLVM: call goabiinternal i64 % +// LLVM-SAME: (ptr +// LLVM: !{i32 23, i64 0, !"type:codegen.llvmInterfaceValue"} +// LLVM-DAG: !{i32 24, i64 {{[0-9]+}}, !"type:codegen.llvmInterface"} + +type llvmInterface interface { + Double() int + Value(int) int +} + +type llvmInterfaceValue struct { + value int +} + +//go:noinline +func (v llvmInterfaceValue) Value(delta int) int { + return v.value + delta +} + +//go:noinline +func (v llvmInterfaceValue) Double() int { + return v.value * 2 +} + +func useLLVMInterface(v llvmInterfaceValue) int { + var i llvmInterface = v + return i.Value(2) + i.Double() +} diff --git a/test/llvm_interface.go b/test/llvm_interface.go new file mode 100644 index 00000000000000..8ac52456460888 --- /dev/null +++ b/test/llvm_interface.go @@ -0,0 +1,34 @@ +// run + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +type llvmInterface interface { + Double() int + Value(int) int +} + +type llvmInterfaceValue struct { + value int +} + +//go:noinline +func (v llvmInterfaceValue) Value(delta int) int { + return v.value + delta +} + +//go:noinline +func (v llvmInterfaceValue) Double() int { + return v.value * 2 +} + +func callLLVMInterface(i llvmInterface) int { + return i.Value(2) + i.Double() +} + +func main() { + println(callLLVMInterface(llvmInterfaceValue{value: 10})) +} diff --git a/test/llvm_interface.out b/test/llvm_interface.out new file mode 100644 index 00000000000000..f5c89552bd3e62 --- /dev/null +++ b/test/llvm_interface.out @@ -0,0 +1 @@ +32 diff --git a/test/llvm_tests.json b/test/llvm_tests.json index a458d706f3bbea..9e65a3e6e1e155 100644 --- a/test/llvm_tests.json +++ b/test/llvm_tests.json @@ -5,7 +5,8 @@ "codegen/logic.go": "integer logic and conditional control flow", "codegen/type_descriptor.go": "compiler-owned runtime type descriptor data and GoObj metadata", "codegen/type_descriptor_kinds.go": "all runtime type descriptor layouts and variable descriptor data", - "codegen/type_descriptor_methods.go": "uncommon type data, concrete method tables, wrappers, and R_METHODOFF" + "codegen/type_descriptor_methods.go": "uncommon type data, concrete method tables, wrappers, and R_METHODOFF", + "codegen/interface.go": "static multi-method itabs, interface conversions and indirect method calls" }, "blacklist": { "codegen/*": "LLVM lowering support has not reached this complete source file yet" @@ -17,7 +18,8 @@ "llvm_type_descriptor.go": "type descriptor data, ABI-internal equality closure, GoObj link, and execution", "llvm_type_descriptor_kinds.go": "all runtime type descriptor kinds, GoObj link, and execution", "llvm_type_descriptor_map.go": "typed map descriptor layout, R_KEEP reachability edge, GoObj link, and execution", - "llvm_type_descriptor_methods.go": "uncommon concrete methods, method wrappers, direct calls, GoObj link, and execution" + "llvm_type_descriptor_methods.go": "uncommon concrete methods, method wrappers, direct calls, GoObj link, and execution", + "llvm_interface.go": "static multi-method itab, interface conversion, indirect calls, GoObj link, and execution" }, "blacklist": { "*": "LLVM lowering or runtime ABI support has not reached this test yet" From b43ff53827f9477aead01d7a3efc2496418e56bb Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 28 Jul 2026 01:34:38 +0800 Subject: [PATCH 04/17] cmd/compile: lower dynamic interface conversions to LLVM --- doc/goallc-llvm-goobj.md | 18 +++++-- src/cmd/compile/internal/ssa/llvmdata.go | 14 ++++++ src/cmd/compile/internal/ssa/ssa2llvm.go | 63 +++++++++++++++++++++++- test/codegen/interface_conversion.go | 28 +++++++++++ test/llvm_interface_conversion.go | 41 +++++++++++++++ test/llvm_interface_conversion.out | 3 ++ test/llvm_tests.json | 6 ++- 7 files changed, 166 insertions(+), 7 deletions(-) create mode 100644 test/codegen/interface_conversion.go create mode 100644 test/llvm_interface_conversion.go create mode 100644 test/llvm_interface_conversion.out diff --git a/doc/goallc-llvm-goobj.md b/doc/goallc-llvm-goobj.md index be834b745477b2..367115c1ff5a89 100644 --- a/doc/goallc-llvm-goobj.md +++ b/doc/goallc-llvm-goobj.md @@ -67,6 +67,17 @@ Go linker 的 dead-method elimination 还依赖函数上的零宽度 relocation type、addend 和 target name;GoObj writer 将其恢复到源函数的 relocation 列表。普通 LLVM call graph 不能替代这项 linker 契约。 +non-empty interface 到另一 non-empty interface 的转换沿用 compiler 生成的 +`internal/abi.TypeAssert` cache 和 `runtime.typeAssert` fallback。LLVM lowering +保留 cache 的 sequentially-consistent pointer load、pointer/uintptr probe 比较、 +nil 分支和 ABIInternal runtime call;cache miss 与随后命中的 fast path 均由 +runtime 测试覆盖。 + +这类可写 descriptor 还要求 GoObj symbol 的精确大小、对齐和 `AuxGotype`。 +LLVM GoObj writer 从 global layout 写入 symbol size/alignment,排除 section +padding,并通过 `!goobj.gotype` 恢复 compiler LSym 的 Go type auxiliary。 +否则 linker 无法为 `.data` 生成正确的 GC bitmap。 + 当前实现只 lower type-rooted data closure,尚未把所有 `dumpdata` 产物泛化为 LLVM data lowering。type descriptor 中未被当前 schema 覆盖的尾部数据,以及其他 data roots,保守地维持 bytes/relocation fallback;新增 schema 或 root 前必须先明确 @@ -242,9 +253,10 @@ go test cmd/internal/testdir -run='^Test$/^LLVM$' -v - 当前 target triple 仅配置了 `darwin/arm64` 和 `linux/amd64`。 - LLVM SSA lowering 仍不完整;复杂 SSA op、GC pointer liveness、defer/panic、 closure、动态 interface assertion/switch、完整 ABI/DWARF 等尚未达到通用正确性。 -- 当前 interface 范围是 compiler 能静态生成 itab 的 concrete-to-interface - conversion,以及对应的 ABIInternal 间接方法调用;动态 itab construction 和 - assertion/switch 不在本阶段范围内。 +- 当前 interface 范围包括 compiler 能静态生成 itab 的 concrete-to-interface + conversion、对应的 ABIInternal 间接方法调用,以及 non-empty + interface-to-interface conversion;concrete type assertion 和 interface switch + 不在本阶段范围内。 - 每个经 LLVM 替换的 package 都必须由 `llc` 生成完整的 `_go_.o`,不能与 原生 compiler object 混合。 - `!goobj.config` 是这条开发链路的稳定交接点。新增 header 配置时应新增独立 diff --git a/src/cmd/compile/internal/ssa/llvmdata.go b/src/cmd/compile/internal/ssa/llvmdata.go index 8ac1f2f97acabf..808bec8b24cf03 100644 --- a/src/cmd/compile/internal/ssa/llvmdata.go +++ b/src/cmd/compile/internal/ssa/llvmdata.go @@ -33,6 +33,7 @@ func LowerGoObjTypeData() { return } closure[s] = true + visit(s.Gotype) for _, r := range s.R { visit(r.Sym) } @@ -88,6 +89,7 @@ func LowerGoObjTypeData() { setGoObjDataFlags(g, s) setGoObjRelocMetadata(g, s) setGoObjKeepMetadata(g, s) + setGoObjGotypeMetadata(g, s) } } @@ -356,6 +358,18 @@ func setGoObjKeepMetadata(g llvm.Value, s *obj.LSym) { } } +func setGoObjGotypeMetadata(g llvm.Value, s *obj.LSym) { + if s.Gotype == nil { + return + } + if CurrentModule.NamedGlobal(s.Gotype.Name).IsNil() { + base.Fatalf("Go type symbol %s for %s was not included in LLVM data closure", s.Gotype.Name, s.Name) + } + g.SetGlobalMetadata(GlobalCtxt.MDKindID("goobj.gotype"), GlobalCtxt.MDNode([]llvm.Metadata{ + GlobalCtxt.MDString(s.Gotype.Name), + })) +} + // Interface dead-method elimination uses zero-width marker relocations on the // containing function. LLVM calls and globals cannot encode those records, so // carry the exact Go relocation type, addend, and target in function metadata. diff --git a/src/cmd/compile/internal/ssa/ssa2llvm.go b/src/cmd/compile/internal/ssa/ssa2llvm.go index 50d2615cf1c250..f897323f4c8d47 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm.go @@ -123,6 +123,25 @@ func (lfc *LLVMFuncContext) llvmCondition(v llvm.Value, name string) llvm.Value return lfc.b.CreateICmp(llvm.IntNE, v, zero, name) } +func (lfc *LLVMFuncContext) pointerComparisonOperands(v *Value) (llvm.Value, llvm.Value) { + return lfc.normalizePointerComparisonOperands(v, lfc.GenLV(v.Args[0]), lfc.GenLV(v.Args[1])) +} + +func (lfc *LLVMFuncContext) normalizePointerComparisonOperands(v *Value, x, y llvm.Value) (llvm.Value, llvm.Value) { + if x.Type() == y.Type() { + return x, y + } + switch { + case x.Type().TypeKind() == llvm.PointerTypeKind && y.Type().TypeKind() == llvm.IntegerTypeKind: + x = lfc.b.CreatePtrToInt(x, y.Type(), v.String()+".ptr") + case x.Type().TypeKind() == llvm.IntegerTypeKind && y.Type().TypeKind() == llvm.PointerTypeKind: + y = lfc.b.CreatePtrToInt(y, x.Type(), v.String()+".ptr") + default: + v.Fatalf("pointer comparison has incompatible LLVM operand types") + } + return x, y +} + type llvmShiftKind uint8 const ( @@ -445,10 +464,25 @@ func (lfc *LLVMFuncContext) GenLV(v *Value) llvm.Value { lVal = lfc.b.CreateNeg(arg0(), v.String()) case OpNeg32F, OpNeg64F: lVal = lfc.b.CreateFNeg(arg0(), v.String()) - case OpEq64, OpEq32, OpEq16, OpEq8, OpEqB, OpEqPtr: + case OpEq64, OpEq32, OpEq16, OpEq8, OpEqB: lVal = lfc.goBool(lfc.b.CreateICmp(llvm.IntEQ, arg0(), arg1(), v.String()+".i1"), v.String()) - case OpNeq64, OpNeq32, OpNeq16, OpNeq8, OpNeqB, OpNeqPtr: + case OpEqPtr: + x, y := lfc.pointerComparisonOperands(v) + lVal = lfc.goBool(lfc.b.CreateICmp(llvm.IntEQ, x, y, v.String()+".i1"), v.String()) + case OpNeq64, OpNeq32, OpNeq16, OpNeq8, OpNeqB: lVal = lfc.goBool(lfc.b.CreateICmp(llvm.IntNE, arg0(), arg1(), v.String()+".i1"), v.String()) + case OpNeqPtr: + x, y := lfc.pointerComparisonOperands(v) + lVal = lfc.goBool(lfc.b.CreateICmp(llvm.IntNE, x, y, v.String()+".i1"), v.String()) + case OpEqInter, OpNeqInter: + x := lfc.b.CreateExtractValue(arg0(), 0, v.String()+".x") + y := lfc.b.CreateExtractValue(arg1(), 0, v.String()+".y") + x, y = lfc.normalizePointerComparisonOperands(v, x, y) + pred := llvm.IntEQ + if v.Op == OpNeqInter { + pred = llvm.IntNE + } + lVal = lfc.goBool(lfc.b.CreateICmp(pred, x, y, v.String()+".i1"), v.String()) case OpLess64, OpLess32, OpLess16, OpLess8: lVal = lfc.goBool(lfc.b.CreateICmp(llvm.IntSLT, arg0(), arg1(), v.String()+".i1"), v.String()) case OpLess64U, OpLess32U, OpLess16U, OpLess8U: @@ -469,6 +503,8 @@ func (lfc *LLVMFuncContext) GenLV(v *Value) llvm.Value { lVal = lfc.goBool(lfc.b.CreateICmp(llvm.IntULT, arg0(), arg1(), v.String()+".i1"), v.String()) case OpIsSliceInBounds: lVal = lfc.goBool(lfc.b.CreateICmp(llvm.IntULE, arg0(), arg1(), v.String()+".i1"), v.String()) + case OpIsNonNil: + lVal = lfc.goBool(lfc.b.CreateICmp(llvm.IntNE, arg0(), llvm.ConstNull(arg0().Type()), v.String()+".i1"), v.String()) case OpLsh64x64, OpLsh64x32, OpLsh64x16, OpLsh64x8, OpLsh32x64, OpLsh32x32, OpLsh32x16, OpLsh32x8, OpLsh16x64, OpLsh16x32, OpLsh16x16, OpLsh16x8, @@ -534,6 +570,21 @@ func (lfc *LLVMFuncContext) GenLV(v *Value) llvm.Value { lVal = lfc.indirectCall(v, 1) case OpPanicBounds: lVal = lfc.panicBounds(v) + case OpSelect0, OpSelect1: + sel := 0 + if v.Op == OpSelect1 { + sel = 1 + } + src := v.Args[0] + switch src.Op { + case OpAtomicLoadPtr: + load := lfc.GenLV(src) + if sel == 0 { + lVal = load + } + default: + v.Fatalf("%s selects unsupported tuple source %s", v.Op, src.Op) + } case OpSelectN: sel := int(auxIntToInt64(v.AuxInt)) src := v.Args[0] @@ -550,6 +601,11 @@ func (lfc *LLVMFuncContext) GenLV(v *Value) llvm.Value { default: lVal = lfc.b.CreateExtractValue(call, sel, v.String()) } + case OpAtomicLoadPtr: + load := lfc.GenLV(src) + if sel == 0 { + lVal = load + } default: lVal = lfc.b.CreateExtractValue(lfc.GenLV(src), sel, v.String()) } @@ -571,6 +627,9 @@ func (lfc *LLVMFuncContext) GenLV(v *Value) llvm.Value { } case OpLoad: lVal = lfc.b.CreateLoad(getLLVMType(v.Type), arg0(), v.String()) + case OpAtomicLoadPtr: + lVal = lfc.b.CreateLoad(GlobalCtxt.PointerType(0), arg0(), v.String()) + lVal.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent) case OpNilCheck: // Preserve Go's explicit nil-check side effect even when no following // load uses the checked pointer. A volatile byte load faults at address diff --git a/test/codegen/interface_conversion.go b/test/codegen/interface_conversion.go new file mode 100644 index 00000000000000..b63bd5d1cbbf64 --- /dev/null +++ b/test/codegen/interface_conversion.go @@ -0,0 +1,28 @@ +// asmcheck + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package codegen + +// LLVM: @codegen..typeAssert.0 = global <{ ptr, ptr, [8 x i8] }> +// LLVM-SAME: !goobj.gotype +// LLVM-LABEL: define goabiinternal { ptr, ptr } @codegen.convertLLVMInterface( +// LLVM: load atomic ptr, ptr @codegen..typeAssert.0 seq_cst +// LLVM: ptrtoint ptr +// LLVM: call goabiinternal ptr @runtime.typeAssert(ptr @codegen..typeAssert.0, ptr +// LLVM-DAG: declare goabiinternal ptr @runtime.typeAssert(ptr, ptr) + +type llvmSourceInterface interface { + Double() int + Value(int) int +} + +type llvmTargetInterface interface { + Value(int) int +} + +func convertLLVMInterface(v llvmSourceInterface) llvmTargetInterface { + return v +} diff --git a/test/llvm_interface_conversion.go b/test/llvm_interface_conversion.go new file mode 100644 index 00000000000000..2b6b223a8d9936 --- /dev/null +++ b/test/llvm_interface_conversion.go @@ -0,0 +1,41 @@ +// run + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +type llvmSourceInterface interface { + Double() int + Value(int) int +} + +type llvmTargetInterface interface { + Value(int) int +} + +type llvmConversionValue int + +//go:noinline +func (v llvmConversionValue) Double() int { + return int(v) * 2 +} + +//go:noinline +func (v llvmConversionValue) Value(delta int) int { + return int(v) + delta +} + +func convertLLVMInterface(v llvmSourceInterface) llvmTargetInterface { + return v +} + +func main() { + var source llvmSourceInterface = llvmConversionValue(10) + println(convertLLVMInterface(source).Value(2)) + println(convertLLVMInterface(source).Value(3)) + + var nilSource llvmSourceInterface + println(convertLLVMInterface(nilSource) == nil) +} diff --git a/test/llvm_interface_conversion.out b/test/llvm_interface_conversion.out new file mode 100644 index 00000000000000..043d03fa9cdf91 --- /dev/null +++ b/test/llvm_interface_conversion.out @@ -0,0 +1,3 @@ +12 +13 +true diff --git a/test/llvm_tests.json b/test/llvm_tests.json index 9e65a3e6e1e155..e8f12388cfa10d 100644 --- a/test/llvm_tests.json +++ b/test/llvm_tests.json @@ -6,7 +6,8 @@ "codegen/type_descriptor.go": "compiler-owned runtime type descriptor data and GoObj metadata", "codegen/type_descriptor_kinds.go": "all runtime type descriptor layouts and variable descriptor data", "codegen/type_descriptor_methods.go": "uncommon type data, concrete method tables, wrappers, and R_METHODOFF", - "codegen/interface.go": "static multi-method itabs, interface conversions and indirect method calls" + "codegen/interface.go": "static multi-method itabs, interface conversions and indirect method calls", + "codegen/interface_conversion.go": "dynamic interface-to-interface conversion cache and runtime fallback" }, "blacklist": { "codegen/*": "LLVM lowering support has not reached this complete source file yet" @@ -19,7 +20,8 @@ "llvm_type_descriptor_kinds.go": "all runtime type descriptor kinds, GoObj link, and execution", "llvm_type_descriptor_map.go": "typed map descriptor layout, R_KEEP reachability edge, GoObj link, and execution", "llvm_type_descriptor_methods.go": "uncommon concrete methods, method wrappers, direct calls, GoObj link, and execution", - "llvm_interface.go": "static multi-method itab, interface conversion, indirect calls, GoObj link, and execution" + "llvm_interface.go": "static multi-method itab, interface conversion, indirect calls, GoObj link, and execution", + "llvm_interface_conversion.go": "dynamic interface-to-interface conversion cache, runtime fallback, nil conversion, and execution" }, "blacklist": { "*": "LLVM lowering or runtime ABI support has not reached this test yet" From fc52eee2708d339eb5f6bf517934fc81e1b1c5df Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 28 Jul 2026 01:40:32 +0800 Subject: [PATCH 05/17] cmd/compile: cover LLVM interface assertions --- doc/goallc-llvm-goobj.md | 14 +++++++--- test/codegen/interface_assertion.go | 39 ++++++++++++++++++++++++++ test/llvm_interface_assertion.go | 43 +++++++++++++++++++++++++++++ test/llvm_interface_assertion.out | 4 +++ test/llvm_tests.json | 6 ++-- 5 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 test/codegen/interface_assertion.go create mode 100644 test/llvm_interface_assertion.go create mode 100644 test/llvm_interface_assertion.out diff --git a/doc/goallc-llvm-goobj.md b/doc/goallc-llvm-goobj.md index 367115c1ff5a89..0dfd18ef90a964 100644 --- a/doc/goallc-llvm-goobj.md +++ b/doc/goallc-llvm-goobj.md @@ -71,11 +71,16 @@ non-empty interface 到另一 non-empty interface 的转换沿用 compiler 生 `internal/abi.TypeAssert` cache 和 `runtime.typeAssert` fallback。LLVM lowering 保留 cache 的 sequentially-consistent pointer load、pointer/uintptr probe 比较、 nil 分支和 ABIInternal runtime call;cache miss 与随后命中的 fast path 均由 -runtime 测试覆盖。 +runtime 测试覆盖。empty interface 到 concrete type 的 comma-ok assertion +直接比较动态 type word 并按 SSA 结果形状取回 data;empty interface 到 +non-empty interface 的 comma-ok assertion 复用同一 TypeAssert cache/fallback, +并覆盖成功、类型不匹配和 nil 输入。 这类可写 descriptor 还要求 GoObj symbol 的精确大小、对齐和 `AuxGotype`。 LLVM GoObj writer 从 global layout 写入 symbol size/alignment,排除 section padding,并通过 `!goobj.gotype` 恢复 compiler LSym 的 Go type auxiliary。 +该 auxiliary 的 target 既可以是本包定义,也可以是 external non-package +reference;后者用于 builtin 或其他 package 拥有的 type symbol。 否则 linker 无法为 `.data` 生成正确的 GC bitmap。 当前实现只 lower type-rooted data closure,尚未把所有 `dumpdata` 产物泛化为 @@ -252,11 +257,12 @@ go test cmd/internal/testdir -run='^Test$/^LLVM$' -v - 当前 target triple 仅配置了 `darwin/arm64` 和 `linux/amd64`。 - LLVM SSA lowering 仍不完整;复杂 SSA op、GC pointer liveness、defer/panic、 - closure、动态 interface assertion/switch、完整 ABI/DWARF 等尚未达到通用正确性。 + closure、interface type switch、完整 ABI/DWARF 等尚未达到通用正确性。 - 当前 interface 范围包括 compiler 能静态生成 itab 的 concrete-to-interface conversion、对应的 ABIInternal 间接方法调用,以及 non-empty - interface-to-interface conversion;concrete type assertion 和 interface switch - 不在本阶段范围内。 + interface-to-interface conversion;empty-interface 到 concrete/non-empty + interface 的 comma-ok assertion 也已覆盖。panic-form assertion 和 interface + type switch 不在本阶段范围内。 - 每个经 LLVM 替换的 package 都必须由 `llc` 生成完整的 `_go_.o`,不能与 原生 compiler object 混合。 - `!goobj.config` 是这条开发链路的稳定交接点。新增 header 配置时应新增独立 diff --git a/test/codegen/interface_assertion.go b/test/codegen/interface_assertion.go new file mode 100644 index 00000000000000..b09fd2662a8caf --- /dev/null +++ b/test/codegen/interface_assertion.go @@ -0,0 +1,39 @@ +// asmcheck + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package codegen + +type llvmAssertionInterface interface { + Value(int) int +} + +type llvmAssertionValue int + +//go:noinline +func (v llvmAssertionValue) Value(delta int) int { + return int(v) + delta +} + +// LLVM: @codegen..typeAssert.0 = global <{ ptr, ptr, [8 x i8] }> +// LLVM-SAME: !goobj.gotype +// LLVM-LABEL: define goabiinternal { { ptr, ptr }, i8 } @codegen.assertLLVMInterface( +// LLVM: load atomic ptr, ptr @codegen..typeAssert.0 seq_cst +// LLVM: call goabiinternal ptr @runtime.typeAssert(ptr @codegen..typeAssert.0, ptr +// LLVM-DAG: declare goabiinternal ptr @runtime.typeAssert(ptr, ptr) +// LLVM-LABEL: define goabiinternal { i64, i8 } @codegen.assertLLVMConcrete( +// LLVM: extractvalue { ptr, ptr } +// LLVM: icmp eq ptr +// LLVM-SAME: @"type:codegen.llvmAssertionValue" +// LLVM: load i64, ptr +func assertLLVMConcrete(v any) (llvmAssertionValue, bool) { + x, ok := v.(llvmAssertionValue) + return x, ok +} + +func assertLLVMInterface(v any) (llvmAssertionInterface, bool) { + x, ok := v.(llvmAssertionInterface) + return x, ok +} diff --git a/test/llvm_interface_assertion.go b/test/llvm_interface_assertion.go new file mode 100644 index 00000000000000..78ca24991ae4dd --- /dev/null +++ b/test/llvm_interface_assertion.go @@ -0,0 +1,43 @@ +// run + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +type llvmAssertionInterface interface { + Value(int) int +} + +type llvmAssertionValue int + +//go:noinline +func (v llvmAssertionValue) Value(delta int) int { + return int(v) + delta +} + +func assertLLVMConcrete(v any) (llvmAssertionValue, bool) { + x, ok := v.(llvmAssertionValue) + return x, ok +} + +func assertLLVMInterface(v any) (llvmAssertionInterface, bool) { + x, ok := v.(llvmAssertionInterface) + return x, ok +} + +func main() { + var v any = llvmAssertionValue(10) + x, ok := assertLLVMConcrete(v) + println(int(x), ok) + + i, ok := assertLLVMInterface(v) + println(i.Value(3), ok) + + _, ok = assertLLVMConcrete("wrong") + println(ok) + + i, ok = assertLLVMInterface(nil) + println(i == nil, ok) +} diff --git a/test/llvm_interface_assertion.out b/test/llvm_interface_assertion.out new file mode 100644 index 00000000000000..f35860e96c395f --- /dev/null +++ b/test/llvm_interface_assertion.out @@ -0,0 +1,4 @@ +10 true +13 true +false +true false diff --git a/test/llvm_tests.json b/test/llvm_tests.json index e8f12388cfa10d..742055efb0c8e1 100644 --- a/test/llvm_tests.json +++ b/test/llvm_tests.json @@ -7,7 +7,8 @@ "codegen/type_descriptor_kinds.go": "all runtime type descriptor layouts and variable descriptor data", "codegen/type_descriptor_methods.go": "uncommon type data, concrete method tables, wrappers, and R_METHODOFF", "codegen/interface.go": "static multi-method itabs, interface conversions and indirect method calls", - "codegen/interface_conversion.go": "dynamic interface-to-interface conversion cache and runtime fallback" + "codegen/interface_conversion.go": "dynamic interface-to-interface conversion cache and runtime fallback", + "codegen/interface_assertion.go": "concrete and non-empty interface assertions with comma-ok results" }, "blacklist": { "codegen/*": "LLVM lowering support has not reached this complete source file yet" @@ -21,7 +22,8 @@ "llvm_type_descriptor_map.go": "typed map descriptor layout, R_KEEP reachability edge, GoObj link, and execution", "llvm_type_descriptor_methods.go": "uncommon concrete methods, method wrappers, direct calls, GoObj link, and execution", "llvm_interface.go": "static multi-method itab, interface conversion, indirect calls, GoObj link, and execution", - "llvm_interface_conversion.go": "dynamic interface-to-interface conversion cache, runtime fallback, nil conversion, and execution" + "llvm_interface_conversion.go": "dynamic interface-to-interface conversion cache, runtime fallback, nil conversion, and execution", + "llvm_interface_assertion.go": "concrete and non-empty interface assertions, cache fallback, mismatch, nil, and execution" }, "blacklist": { "*": "LLVM lowering or runtime ABI support has not reached this test yet" From ca89ce5602d5a5029ec09da65014014577a5d448 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 28 Jul 2026 01:53:32 +0800 Subject: [PATCH 06/17] cmd/compile: lower LLVM interface type switches --- doc/goallc-llvm-goobj.md | 15 ++++++--- src/cmd/compile/internal/ssa/ssa2llvm.go | 34 +++++++++++++++++--- test/codegen/interface_assertion.go | 12 +++++++ test/codegen/interface_switch.go | 41 ++++++++++++++++++++++++ test/codegen/logic.go | 10 ++++++ test/llvm_interface_assertion.go | 11 +++++++ test/llvm_interface_assertion.out | 2 ++ test/llvm_interface_switch.go | 40 +++++++++++++++++++++++ test/llvm_interface_switch.out | 4 +++ test/llvm_tests.json | 6 ++-- 10 files changed, 165 insertions(+), 10 deletions(-) create mode 100644 test/codegen/interface_switch.go create mode 100644 test/llvm_interface_switch.go create mode 100644 test/llvm_interface_switch.out diff --git a/doc/goallc-llvm-goobj.md b/doc/goallc-llvm-goobj.md index 0dfd18ef90a964..77d165d4ba60c5 100644 --- a/doc/goallc-llvm-goobj.md +++ b/doc/goallc-llvm-goobj.md @@ -74,7 +74,14 @@ nil 分支和 ABIInternal runtime call;cache miss 与随后命中的 fast path runtime 测试覆盖。empty interface 到 concrete type 的 comma-ok assertion 直接比较动态 type word 并按 SSA 结果形状取回 data;empty interface 到 non-empty interface 的 comma-ok assertion 复用同一 TypeAssert cache/fallback, -并覆盖成功、类型不匹配和 nil 输入。 +并覆盖成功、类型不匹配和 nil 输入。panic-form assertion 沿用相同 fast path, +失败边分别调用 `runtime.panicdottypeE`、`runtime.panicdottypeI` 或 +`runtime.panicnildottype`。 + +interface type switch 对 concrete cases 使用动态 type hash/type pointer 比较; +interface case 使用 compiler 生成的 `internal/abi.InterfaceSwitch` descriptor、 +atomic cache probe 和 `runtime.interfaceSwitch` fallback。descriptor 作为带 +`AuxGotype` 的可写 data root 进入同一 LSym-to-LLVM data closure。 这类可写 descriptor 还要求 GoObj symbol 的精确大小、对齐和 `AuxGotype`。 LLVM GoObj writer 从 global layout 写入 symbol size/alignment,排除 section @@ -257,12 +264,12 @@ go test cmd/internal/testdir -run='^Test$/^LLVM$' -v - 当前 target triple 仅配置了 `darwin/arm64` 和 `linux/amd64`。 - LLVM SSA lowering 仍不完整;复杂 SSA op、GC pointer liveness、defer/panic、 - closure、interface type switch、完整 ABI/DWARF 等尚未达到通用正确性。 + closure、完整 ABI/DWARF 等尚未达到通用正确性。 - 当前 interface 范围包括 compiler 能静态生成 itab 的 concrete-to-interface conversion、对应的 ABIInternal 间接方法调用,以及 non-empty interface-to-interface conversion;empty-interface 到 concrete/non-empty - interface 的 comma-ok assertion 也已覆盖。panic-form assertion 和 interface - type switch 不在本阶段范围内。 + interface 的 comma-ok/panic-form assertion,以及包含 concrete/interface cases + 的 type switch 也已覆盖。 - 每个经 LLVM 替换的 package 都必须由 `llc` 生成完整的 `_go_.o`,不能与 原生 compiler object 混合。 - `!goobj.config` 是这条开发链路的稳定交接点。新增 header 配置时应新增独立 diff --git a/src/cmd/compile/internal/ssa/ssa2llvm.go b/src/cmd/compile/internal/ssa/ssa2llvm.go index f897323f4c8d47..651ace001987b4 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm.go @@ -123,6 +123,15 @@ func (lfc *LLVMFuncContext) llvmCondition(v llvm.Value, name string) llvm.Value return lfc.b.CreateICmp(llvm.IntNE, v, zero, name) } +func llvmConstInt(t *types.Type, value int64) llvm.Value { + bits := uint64(t.Size() * 8) + raw := uint64(value) + if bits < 64 { + raw &= uint64(1)< {{.*}}!goobj.gotype +// LLVM-DAG: load atomic ptr, ptr @codegen..interfaceSwitch.0 seq_cst +// LLVM-DAG: call goabiinternal { i64, ptr } @runtime.interfaceSwitch(ptr @codegen..interfaceSwitch.0, ptr +// LLVM-DAG: declare goabiinternal { i64, ptr } @runtime.interfaceSwitch(ptr, ptr) +// LLVM-DAG: icmp eq ptr +// LLVM-DAG: @"type:codegen.llvmSwitchValue" +// LLVM-DAG: @"type:string" +// LLVM-DAG: ret i64 -1 +func classifyLLVMInterface(v any) int { + switch x := v.(type) { + case nil: + return 0 + case llvmSwitchValue: + return int(x) + case string: + return len(x) + case llvmSwitchInterface: + return x.Value(1) + default: + return -1 + } +} diff --git a/test/codegen/logic.go b/test/codegen/logic.go index b9890dd4b7d79e..7884de3e89138d 100644 --- a/test/codegen/logic.go +++ b/test/codegen/logic.go @@ -6,6 +6,16 @@ package codegen +// LLVM-DAG: ret i64 -1 +func llvmNegativeConstant() int { + return -1 +} + +// LLVM-DAG: ret i32 -1 +func llvmNegativeInt32Constant() int32 { + return -1 +} + // LLVM-DAG: define goabiinternal i64 @codegen.andWithUse // LLVM-DAG: and i64 // LLVM-DAG: or i64 diff --git a/test/llvm_interface_assertion.go b/test/llvm_interface_assertion.go index 78ca24991ae4dd..f1e916a25027fe 100644 --- a/test/llvm_interface_assertion.go +++ b/test/llvm_interface_assertion.go @@ -27,6 +27,14 @@ func assertLLVMInterface(v any) (llvmAssertionInterface, bool) { return x, ok } +func mustLLVMConcrete(v any) llvmAssertionValue { + return v.(llvmAssertionValue) +} + +func mustLLVMInterface(v any) llvmAssertionInterface { + return v.(llvmAssertionInterface) +} + func main() { var v any = llvmAssertionValue(10) x, ok := assertLLVMConcrete(v) @@ -40,4 +48,7 @@ func main() { i, ok = assertLLVMInterface(nil) println(i == nil, ok) + + println(int(mustLLVMConcrete(v))) + println(mustLLVMInterface(v).Value(4)) } diff --git a/test/llvm_interface_assertion.out b/test/llvm_interface_assertion.out index f35860e96c395f..07b742a7b5686f 100644 --- a/test/llvm_interface_assertion.out +++ b/test/llvm_interface_assertion.out @@ -2,3 +2,5 @@ 13 true false true false +10 +14 diff --git a/test/llvm_interface_switch.go b/test/llvm_interface_switch.go new file mode 100644 index 00000000000000..bf0b9a03048214 --- /dev/null +++ b/test/llvm_interface_switch.go @@ -0,0 +1,40 @@ +// run + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +type llvmSwitchInterface interface { + Value(int) int +} + +type llvmSwitchValue int + +//go:noinline +func (v llvmSwitchValue) Value(delta int) int { + return int(v) + delta +} + +func classifyLLVMInterface(v any) int { + switch x := v.(type) { + case nil: + return 0 + case llvmSwitchValue: + return int(x) + case string: + return len(x) + case llvmSwitchInterface: + return x.Value(1) + default: + return -1 + } +} + +func main() { + println(classifyLLVMInterface(nil)) + println(classifyLLVMInterface(llvmSwitchValue(10))) + println(classifyLLVMInterface("abc")) + println(classifyLLVMInterface(struct{}{})) +} diff --git a/test/llvm_interface_switch.out b/test/llvm_interface_switch.out new file mode 100644 index 00000000000000..eccaa3e8bbe4d5 --- /dev/null +++ b/test/llvm_interface_switch.out @@ -0,0 +1,4 @@ +0 +10 +3 +-1 diff --git a/test/llvm_tests.json b/test/llvm_tests.json index 742055efb0c8e1..02ad163c1327c8 100644 --- a/test/llvm_tests.json +++ b/test/llvm_tests.json @@ -8,7 +8,8 @@ "codegen/type_descriptor_methods.go": "uncommon type data, concrete method tables, wrappers, and R_METHODOFF", "codegen/interface.go": "static multi-method itabs, interface conversions and indirect method calls", "codegen/interface_conversion.go": "dynamic interface-to-interface conversion cache and runtime fallback", - "codegen/interface_assertion.go": "concrete and non-empty interface assertions with comma-ok results" + "codegen/interface_assertion.go": "comma-ok and panic-form concrete and non-empty interface assertions", + "codegen/interface_switch.go": "concrete and interface type-switch cases with static interface-switch cache" }, "blacklist": { "codegen/*": "LLVM lowering support has not reached this complete source file yet" @@ -23,7 +24,8 @@ "llvm_type_descriptor_methods.go": "uncommon concrete methods, method wrappers, direct calls, GoObj link, and execution", "llvm_interface.go": "static multi-method itab, interface conversion, indirect calls, GoObj link, and execution", "llvm_interface_conversion.go": "dynamic interface-to-interface conversion cache, runtime fallback, nil conversion, and execution", - "llvm_interface_assertion.go": "concrete and non-empty interface assertions, cache fallback, mismatch, nil, and execution" + "llvm_interface_assertion.go": "comma-ok and panic-form assertions, cache fallback, mismatch, nil, and execution", + "llvm_interface_switch.go": "nil, concrete, interface, and default type-switch cases with execution" }, "blacklist": { "*": "LLVM lowering or runtime ABI support has not reached this test yet" From 8ef3453c1c2f0a83aa7afc8f63be51b1eadbf567 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 28 Jul 2026 01:54:26 +0800 Subject: [PATCH 07/17] cmd/compile: cover LLVM interface equality --- doc/goallc-llvm-goobj.md | 2 ++ test/codegen/interface_equality.go | 31 +++++++++++++++++++++++++ test/llvm_interface_equality.go | 37 ++++++++++++++++++++++++++++++ test/llvm_interface_equality.out | 2 ++ test/llvm_tests.json | 2 ++ 5 files changed, 74 insertions(+) create mode 100644 test/codegen/interface_equality.go create mode 100644 test/llvm_interface_equality.go create mode 100644 test/llvm_interface_equality.out diff --git a/doc/goallc-llvm-goobj.md b/doc/goallc-llvm-goobj.md index 77d165d4ba60c5..5fc806f72cad48 100644 --- a/doc/goallc-llvm-goobj.md +++ b/doc/goallc-llvm-goobj.md @@ -82,6 +82,8 @@ interface type switch 对 concrete cases 使用动态 type hash/type pointer 比 interface case 使用 compiler 生成的 `internal/abi.InterfaceSwitch` descriptor、 atomic cache probe 和 `runtime.interfaceSwitch` fallback。descriptor 作为带 `AuxGotype` 的可写 data root 进入同一 LSym-to-LLVM data closure。 +两个非 nil interface 的 equality 分别保留 `runtime.efaceeq` 或 +`runtime.ifaceeq` ABIInternal call,以动态 type/itab 和 data words 完成比较。 这类可写 descriptor 还要求 GoObj symbol 的精确大小、对齐和 `AuxGotype`。 LLVM GoObj writer 从 global layout 写入 symbol size/alignment,排除 section diff --git a/test/codegen/interface_equality.go b/test/codegen/interface_equality.go new file mode 100644 index 00000000000000..b4496c1b5ebefe --- /dev/null +++ b/test/codegen/interface_equality.go @@ -0,0 +1,31 @@ +// asmcheck + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package codegen + +type llvmEqualityInterface interface { + Value() int +} + +type llvmEqualityValue int + +func (v llvmEqualityValue) Value() int { + return int(v) +} + +// LLVM-DAG: define goabiinternal i8 @codegen.equalLLVMEmpty( +// LLVM-DAG: call goabiinternal i8 @runtime.efaceeq(ptr +// LLVM-DAG: declare goabiinternal i8 @runtime.efaceeq(ptr, ptr, ptr) +func equalLLVMEmpty(a, b any) bool { + return a == b +} + +// LLVM-DAG: define goabiinternal i8 @codegen.equalLLVMNonEmpty( +// LLVM-DAG: call goabiinternal i8 @runtime.ifaceeq(ptr +// LLVM-DAG: declare goabiinternal i8 @runtime.ifaceeq(ptr, ptr, ptr) +func equalLLVMNonEmpty(a, b llvmEqualityInterface) bool { + return a == b +} diff --git a/test/llvm_interface_equality.go b/test/llvm_interface_equality.go new file mode 100644 index 00000000000000..e5fde063152792 --- /dev/null +++ b/test/llvm_interface_equality.go @@ -0,0 +1,37 @@ +// run + +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +type llvmEqualityInterface interface { + Value() int +} + +type llvmEqualityValue int + +func (v llvmEqualityValue) Value() int { + return int(v) +} + +func equalLLVMEmpty(a, b any) bool { + return a == b +} + +func equalLLVMNonEmpty(a, b llvmEqualityInterface) bool { + return a == b +} + +func main() { + var a any = llvmEqualityValue(10) + var b any = llvmEqualityValue(10) + var c any = llvmEqualityValue(11) + println(equalLLVMEmpty(a, b), equalLLVMEmpty(a, c)) + + var x llvmEqualityInterface = llvmEqualityValue(10) + var y llvmEqualityInterface = llvmEqualityValue(10) + var z llvmEqualityInterface = llvmEqualityValue(11) + println(equalLLVMNonEmpty(x, y), equalLLVMNonEmpty(x, z)) +} diff --git a/test/llvm_interface_equality.out b/test/llvm_interface_equality.out new file mode 100644 index 00000000000000..6f6466d203387d --- /dev/null +++ b/test/llvm_interface_equality.out @@ -0,0 +1,2 @@ +true false +true false diff --git a/test/llvm_tests.json b/test/llvm_tests.json index 02ad163c1327c8..fa531d1d9496ac 100644 --- a/test/llvm_tests.json +++ b/test/llvm_tests.json @@ -9,6 +9,7 @@ "codegen/interface.go": "static multi-method itabs, interface conversions and indirect method calls", "codegen/interface_conversion.go": "dynamic interface-to-interface conversion cache and runtime fallback", "codegen/interface_assertion.go": "comma-ok and panic-form concrete and non-empty interface assertions", + "codegen/interface_equality.go": "empty and non-empty interface equality through runtime comparators", "codegen/interface_switch.go": "concrete and interface type-switch cases with static interface-switch cache" }, "blacklist": { @@ -25,6 +26,7 @@ "llvm_interface.go": "static multi-method itab, interface conversion, indirect calls, GoObj link, and execution", "llvm_interface_conversion.go": "dynamic interface-to-interface conversion cache, runtime fallback, nil conversion, and execution", "llvm_interface_assertion.go": "comma-ok and panic-form assertions, cache fallback, mismatch, nil, and execution", + "llvm_interface_equality.go": "empty and non-empty interface equality for matching and differing dynamic values", "llvm_interface_switch.go": "nil, concrete, interface, and default type-switch cases with execution" }, "blacklist": { From 5a658a99c441856232eeeaddd3d553c8567accd3 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 28 Jul 2026 01:55:35 +0800 Subject: [PATCH 08/17] cmd/compile: cover multi-case LLVM interface switches --- doc/goallc-llvm-goobj.md | 3 ++- test/codegen/interface_switch.go | 13 ++++++++++++- test/llvm_interface_switch.go | 12 ++++++++++++ test/llvm_interface_switch.out | 1 + test/llvm_tests.json | 4 ++-- 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/doc/goallc-llvm-goobj.md b/doc/goallc-llvm-goobj.md index 5fc806f72cad48..aa310681b9dd8c 100644 --- a/doc/goallc-llvm-goobj.md +++ b/doc/goallc-llvm-goobj.md @@ -81,7 +81,8 @@ non-empty interface 的 comma-ok assertion 复用同一 TypeAssert cache/fallbac interface type switch 对 concrete cases 使用动态 type hash/type pointer 比较; interface case 使用 compiler 生成的 `internal/abi.InterfaceSwitch` descriptor、 atomic cache probe 和 `runtime.interfaceSwitch` fallback。descriptor 作为带 -`AuxGotype` 的可写 data root 进入同一 LSym-to-LLVM data closure。 +`AuxGotype` 的可写 data root 进入同一 LSym-to-LLVM data closure;其尾部 +interface type 指针数组按实际 case 数扩展,并由多 interface case 测试覆盖。 两个非 nil interface 的 equality 分别保留 `runtime.efaceeq` 或 `runtime.ifaceeq` ABIInternal call,以动态 type/itab 和 data words 完成比较。 diff --git a/test/codegen/interface_switch.go b/test/codegen/interface_switch.go index 220847a50873f9..237c39a741aff2 100644 --- a/test/codegen/interface_switch.go +++ b/test/codegen/interface_switch.go @@ -10,14 +10,23 @@ type llvmSwitchInterface interface { Value(int) int } +type llvmDoubleSwitchInterface interface { + Double() int +} + type llvmSwitchValue int +type llvmDoubleSwitchValue int //go:noinline func (v llvmSwitchValue) Value(delta int) int { return int(v) + delta } -// LLVM-DAG: @codegen..interfaceSwitch.0 = global <{ ptr, [8 x i8], ptr }> {{.*}}!goobj.gotype +func (v llvmDoubleSwitchValue) Double() int { + return int(v) * 2 +} + +// LLVM-DAG: @codegen..interfaceSwitch.0 = global <{ ptr, [8 x i8], ptr, ptr }> {{.*}}!goobj.gotype // LLVM-DAG: load atomic ptr, ptr @codegen..interfaceSwitch.0 seq_cst // LLVM-DAG: call goabiinternal { i64, ptr } @runtime.interfaceSwitch(ptr @codegen..interfaceSwitch.0, ptr // LLVM-DAG: declare goabiinternal { i64, ptr } @runtime.interfaceSwitch(ptr, ptr) @@ -35,6 +44,8 @@ func classifyLLVMInterface(v any) int { return len(x) case llvmSwitchInterface: return x.Value(1) + case llvmDoubleSwitchInterface: + return x.Double() default: return -1 } diff --git a/test/llvm_interface_switch.go b/test/llvm_interface_switch.go index bf0b9a03048214..a5b801239178b5 100644 --- a/test/llvm_interface_switch.go +++ b/test/llvm_interface_switch.go @@ -10,13 +10,22 @@ type llvmSwitchInterface interface { Value(int) int } +type llvmDoubleSwitchInterface interface { + Double() int +} + type llvmSwitchValue int +type llvmDoubleSwitchValue int //go:noinline func (v llvmSwitchValue) Value(delta int) int { return int(v) + delta } +func (v llvmDoubleSwitchValue) Double() int { + return int(v) * 2 +} + func classifyLLVMInterface(v any) int { switch x := v.(type) { case nil: @@ -27,6 +36,8 @@ func classifyLLVMInterface(v any) int { return len(x) case llvmSwitchInterface: return x.Value(1) + case llvmDoubleSwitchInterface: + return x.Double() default: return -1 } @@ -36,5 +47,6 @@ func main() { println(classifyLLVMInterface(nil)) println(classifyLLVMInterface(llvmSwitchValue(10))) println(classifyLLVMInterface("abc")) + println(classifyLLVMInterface(llvmDoubleSwitchValue(7))) println(classifyLLVMInterface(struct{}{})) } diff --git a/test/llvm_interface_switch.out b/test/llvm_interface_switch.out index eccaa3e8bbe4d5..3aa6c1745744e3 100644 --- a/test/llvm_interface_switch.out +++ b/test/llvm_interface_switch.out @@ -1,4 +1,5 @@ 0 10 3 +14 -1 diff --git a/test/llvm_tests.json b/test/llvm_tests.json index fa531d1d9496ac..c033221928725c 100644 --- a/test/llvm_tests.json +++ b/test/llvm_tests.json @@ -10,7 +10,7 @@ "codegen/interface_conversion.go": "dynamic interface-to-interface conversion cache and runtime fallback", "codegen/interface_assertion.go": "comma-ok and panic-form concrete and non-empty interface assertions", "codegen/interface_equality.go": "empty and non-empty interface equality through runtime comparators", - "codegen/interface_switch.go": "concrete and interface type-switch cases with static interface-switch cache" + "codegen/interface_switch.go": "concrete and multiple interface type-switch cases with variable static cache descriptor" }, "blacklist": { "codegen/*": "LLVM lowering support has not reached this complete source file yet" @@ -27,7 +27,7 @@ "llvm_interface_conversion.go": "dynamic interface-to-interface conversion cache, runtime fallback, nil conversion, and execution", "llvm_interface_assertion.go": "comma-ok and panic-form assertions, cache fallback, mismatch, nil, and execution", "llvm_interface_equality.go": "empty and non-empty interface equality for matching and differing dynamic values", - "llvm_interface_switch.go": "nil, concrete, interface, and default type-switch cases with execution" + "llvm_interface_switch.go": "nil, concrete, multiple interface, and default type-switch cases with execution" }, "blacklist": { "*": "LLVM lowering or runtime ABI support has not reached this test yet" From 3052bcd0f573313997ec39946926263e1c4001bd Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 28 Jul 2026 09:38:01 +0800 Subject: [PATCH 09/17] cmd/compile: preserve LLVM function pointers in itabs --- doc/goallc-llvm-goobj.md | 7 +++--- src/cmd/compile/internal/ssa/llvmtypeddata.go | 22 ++++++++++++++++--- src/cmd/compile/internal/ssa/ssa2llvm.go | 18 ++++++++++++++- test/codegen/interface.go | 12 +++++----- 4 files changed, 46 insertions(+), 13 deletions(-) diff --git a/doc/goallc-llvm-goobj.md b/doc/goallc-llvm-goobj.md index aa310681b9dd8c..5d659ea6bd9e84 100644 --- a/doc/goallc-llvm-goobj.md +++ b/doc/goallc-llvm-goobj.md @@ -56,9 +56,10 @@ object。 静态 interface conversion 还会把带有 `ItabInfo` 的 roots 纳入同一数据闭包。 itab 使用 `%go.runtime.ITab` 和按实际方法数扩展的 packed LLVM struct 表达; -固定的 `Fun[0]` 后面按 LSym 的最终大小追加 `uintptr` 数组,因此单方法和多方法 -itab 都保持 runtime ABI 的连续方法表布局。方法入口仍保留 weak `R_ADDR` -relocation。interface 方法调用从 itab 槽加载入口,转成 LLVM function pointer, +固定的 `Fun[0]` 后面按 LSym 的最终大小追加 `ptr` 数组。`ptr` 与 runtime +`uintptr` 具有相同大小和对齐,因此单方法和多方法 itab 仍保持连续的 ABI +布局,同时 LLVM 能保留静态方法入口的 function-pointer 语义。方法入口仍保留 +weak `R_ADDR` relocation。interface 方法调用从 itab 槽直接加载 LLVM pointer, 并以原 SSA `AuxCall` 的 ABIInternal signature 发出 indirect call。 Go linker 的 dead-method elimination 还依赖函数上的零宽度 diff --git a/src/cmd/compile/internal/ssa/llvmtypeddata.go b/src/cmd/compile/internal/ssa/llvmtypeddata.go index c0f8dadd4124fb..fb04a7194cc66f 100644 --- a/src/cmd/compile/internal/ssa/llvmtypeddata.go +++ b/src/cmd/compile/internal/ssa/llvmtypeddata.go @@ -109,7 +109,7 @@ func llvmItabParts(s *obj.LSym) []llvmDescriptorPart { if tail := (size - fixed) / int64(types.PtrSize); tail != 0 { parts = append(parts, llvmDescriptorPart{ off: fixed, - arrayElem: types.Types[types.TUINTPTR], + arrayElem: types.Types[types.TUNSAFEPTR], arrayCount: tail, }) } @@ -332,7 +332,11 @@ func (l *llvmDataLowerer) goStructElementTypes(t *types.Type) []llvm.Type { if f.Offset > off { fields = append(fields, llvm.ArrayType(GlobalCtxt.Int8Type(), int(f.Offset-off))) } - fields = append(fields, l.goType(f.Type)) + if llvmItabFunField(t, f) { + fields = append(fields, llvm.ArrayType(GlobalCtxt.PointerType(0), int(f.Type.NumElem()))) + } else { + fields = append(fields, l.goType(f.Type)) + } off = f.Offset + f.Type.Size() } if off < t.Size() { @@ -363,7 +367,15 @@ func (l *llvmDataLowerer) goValue(s *obj.LSym, t *types.Type, off int64, globals pos := off for _, f := range t.Fields() { values = append(values, llvmDataRangeFields(s, pos, off+f.Offset, globals, l.data)...) - values = append(values, l.goValue(s, f.Type, off+f.Offset, globals)) + if llvmItabFunField(t, f) { + fun := make([]llvm.Value, f.Type.NumElem()) + for i := range fun { + fun[i] = l.goValue(s, types.Types[types.TUNSAFEPTR], off+f.Offset+int64(i*types.PtrSize), globals) + } + values = append(values, llvm.ConstArray(GlobalCtxt.PointerType(0), fun)) + } else { + values = append(values, l.goValue(s, f.Type, off+f.Offset, globals)) + } pos = off + f.Offset + f.Type.Size() } values = append(values, llvmDataRangeFields(s, pos, off+t.Size(), globals, l.data)...) @@ -403,6 +415,10 @@ func (l *llvmDataLowerer) goValue(s *obj.LSym, t *types.Type, off int64, globals } } +func llvmItabFunField(t *types.Type, f *types.Field) bool { + return t == rttype.ITab && f.Sym != nil && f.Sym.Name == "Fun" +} + func llvmCoerceDataReloc(value llvm.Value, want llvm.Type, s *obj.LSym, off int64) llvm.Value { if value.Type() == want { return value diff --git a/src/cmd/compile/internal/ssa/ssa2llvm.go b/src/cmd/compile/internal/ssa/ssa2llvm.go index 651ace001987b4..cfc90a3f606b33 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm.go @@ -17,6 +17,7 @@ type LLVMFuncContext struct { BBs map[ID]llvm.BasicBlock Vs map[ID]llvm.Value Locals map[llvmLocalKey]llvm.Value + ItabMethods map[ID]bool F *Func LF llvm.Value b llvm.Builder @@ -652,7 +653,13 @@ func (lfc *LLVMFuncContext) GenLV(v *Value) llvm.Value { lVal = lfc.b.CreatePHI(getLLVMType(v.Type), v.String()) } case OpLoad: - lVal = lfc.b.CreateLoad(getLLVMType(v.Type), arg0(), v.String()) + typ := getLLVMType(v.Type) + if lfc.ItabMethods[v.ID] { + // Native SSA uses uintptr for an itab method slot. Preserve its + // pointer-sized storage but expose the callable pointer to LLVM. + typ = GlobalCtxt.PointerType(0) + } + lVal = lfc.b.CreateLoad(typ, arg0(), v.String()) case OpAtomicLoadPtr: lVal = lfc.b.CreateLoad(GlobalCtxt.PointerType(0), arg0(), v.String()) lVal.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent) @@ -748,6 +755,7 @@ func LLVMCompile(f *Func) { BBs: map[ID]llvm.BasicBlock{}, Vs: map[ID]llvm.Value{}, Locals: map[llvmLocalKey]llvm.Value{}, + ItabMethods: map[ID]bool{}, F: f, b: GlobalCtxt.NewBuilder(), ReturnType: sig.ReturnType, @@ -762,6 +770,14 @@ func LLVMCompile(f *Func) { setGoObjFunctionRelocMetadata(FCtxt.LF, f.OwnAux.Fn) for _, BB := range f.Blocks { FCtxt.BBs[BB.ID] = GlobalCtxt.AddBasicBlock(FCtxt.LF, BB.String()) + for _, v := range BB.Values { + if (v.Op == OpInterCall || v.Op == OpInterLECall) && len(v.Args) != 0 { + code := v.Args[0] + if code.Op == OpLoad && code.Type.IsUintptr() && code.Uses == 1 { + FCtxt.ItabMethods[code.ID] = true + } + } + } } // LLVM requires all phi nodes to precede non-phi instructions in a // block. Predeclare them before recursive value emission can insert any diff --git a/test/codegen/interface.go b/test/codegen/interface.go index 462ca2b91e6836..d0c6e37d3ddca4 100644 --- a/test/codegen/interface.go +++ b/test/codegen/interface.go @@ -6,22 +6,22 @@ package codegen -// LLVM-DAG: %go.runtime.ITab = type <{ ptr, ptr, i32, [4 x i8], [1 x i64] }> -// LLVM-DAG: %go.itab.codegen.llvmInterfaceValue_codegen.llvmInterface = type <{ %go.runtime.ITab, [1 x i64] }> +// LLVM-DAG: %go.runtime.ITab = type <{ ptr, ptr, i32, [4 x i8], [1 x ptr] }> +// LLVM-DAG: %go.itab.codegen.llvmInterfaceValue_codegen.llvmInterface = type <{ %go.runtime.ITab, [1 x ptr] }> // LLVM-DAG: @"go:itab.codegen.llvmInterfaceValue,codegen.llvmInterface" = constant %go.itab.codegen.llvmInterfaceValue_codegen.llvmInterface -// LLVM-DAG: ptrtoint (ptr @"codegen.(*llvmInterfaceValue).Double" to i64) -// LLVM-DAG: ptrtoint (ptr @"codegen.(*llvmInterfaceValue).Value" to i64) +// LLVM-DAG: ptr @"codegen.(*llvmInterfaceValue).Double" +// LLVM-DAG: ptr @"codegen.(*llvmInterfaceValue).Value" // LLVM-DAG: !goobj.symbol.flags // LLVM-DAG: !goobj.relocs // LLVM-LABEL: define goabiinternal i64 @codegen.useLLVMInterface( // LLVM-SAME: !goobj.marker_relocs // LLVM-LABEL: define goabiinternal i64 @codegen.llvmInterface.Value( -// LLVM: inttoptr i64 +// LLVM: load ptr, ptr // LLVM: call goabiinternal i64 % // LLVM-SAME: (ptr // LLVM-SAME: i64 // LLVM-LABEL: define goabiinternal i64 @codegen.llvmInterface.Double( -// LLVM: inttoptr i64 +// LLVM: load ptr, ptr // LLVM: call goabiinternal i64 % // LLVM-SAME: (ptr // LLVM: !{i32 23, i64 0, !"type:codegen.llvmInterfaceValue"} From 77b95c5d2a0202f5010d3e1246db060825dc9bcf Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 28 Jul 2026 09:43:40 +0800 Subject: [PATCH 10/17] cmd/compile: encode GoObj symbol identity in LLVM IR --- doc/goallc-llvm-goobj.md | 9 ++++++--- src/cmd/compile/internal/ssa/llvmdata.go | 24 +++++++++++++----------- test/codegen/interface.go | 3 +-- test/codegen/type_descriptor.go | 2 +- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/doc/goallc-llvm-goobj.md b/doc/goallc-llvm-goobj.md index 5d659ea6bd9e84..f95344d9c545f4 100644 --- a/doc/goallc-llvm-goobj.md +++ b/doc/goallc-llvm-goobj.md @@ -46,9 +46,12 @@ schema 的辅助数据仍以 bytes/relocations 表示。这个边界刻意位于 - runtime ABIInternal 函数引用(例如 type equality closure 中的 `runtime.memequal64`)。 -LLVM IR 使用 global attachment `!goobj.symbol.flags` 和 `!goobj.relocs` 交接 -这些 Go-only 属性。GoObj writer 消费该 metadata,在默认 relocation 推导前恢复 -精确的 Go relocation type,特别是 weak offset relocation。零宽度的 linker +LLVM IR 优先使用原生 linkage 和语义类型交接这些属性:Local 对应 +`internal`,非 Local 的 DUPOK 对应 `weak`,`%go.descriptor.*` 和 +`%go.itab.*` 分别标识 Go type 与 itab。`!goobj.symbol.flags` 只保留 typelink、 +UsedInIface、linkname 以及 Local+DUPOK 重叠等没有等价 LLVM 表示的位。 +`!goobj.relocs` 仍在默认 relocation 推导前恢复精确的 Go relocation type, +特别是 weak offset relocation。零宽度的 linker 保活边 `R_KEEP` 不伪装为地址常量,而用 `!goobj.keep` 记录其 target symbol, 由 writer 合成 GoObj relocation。最终链路仍只有 `__.PKGDEF + _go_.o` 两个有意义的 archive members;不会生成或合并 native data diff --git a/src/cmd/compile/internal/ssa/llvmdata.go b/src/cmd/compile/internal/ssa/llvmdata.go index 808bec8b24cf03..06bfe4a71cabf9 100644 --- a/src/cmd/compile/internal/ssa/llvmdata.go +++ b/src/cmd/compile/internal/ssa/llvmdata.go @@ -7,7 +7,6 @@ import ( "cmd/internal/obj" "cmd/internal/objabi" "sort" - "strings" "github.com/goallc/go-llvm" ) @@ -77,6 +76,7 @@ func LowerGoObjTypeData() { } g.SetSection(llvmDataSection(s)) g.SetGlobalConstant(llvmDataIsReadOnly(s)) + setLLVMDataLinkage(g, s) if s.Align != 0 { g.SetAlignment(int(s.Align)) } @@ -284,26 +284,28 @@ func llvmDataIsReadOnly(s *obj.LSym) bool { } } +func setLLVMDataLinkage(g llvm.Value, s *obj.LSym) { + if s.Local() { + g.SetLinkage(llvm.InternalLinkage) + } else if s.DuplicateOK() { + g.SetLinkage(llvm.WeakAnyLinkage) + } +} + func setGoObjDataFlags(g llvm.Value, s *obj.LSym) { var flag, flag2 uint64 - if s.DuplicateOK() { + // Local and non-local Dupok symbols use LLVM linkage. LLVM cannot encode + // both properties at once, so only a Local+Dupok overlap needs a residual + // metadata bit. + if s.Local() && s.DuplicateOK() { flag |= 1 << 0 // goobj.SymFlagDupok } - if s.Local() { - flag |= 1 << 1 // goobj.SymFlagLocal - } if s.MakeTypelink() { flag |= 1 << 2 // goobj.SymFlagTypelink } - if strings.HasPrefix(s.Name, "type:") && len(s.Name) > len("type:") && s.Name[5] != '.' && s.Type == objabi.SRODATA { - flag |= 1 << 6 // goobj.SymFlagGoType - } if s.UsedInIface() { flag2 |= 1 << 0 // goobj.SymFlagUsedInIface } - if strings.HasPrefix(s.Name, "go:itab.") && s.Type == objabi.SRODATA { - flag2 |= 1 << 1 // goobj.SymFlagItab - } if s.IsLinkname() { flag2 |= 1 << 4 // goobj.SymFlagLinkname } diff --git a/test/codegen/interface.go b/test/codegen/interface.go index d0c6e37d3ddca4..08865495bf0b99 100644 --- a/test/codegen/interface.go +++ b/test/codegen/interface.go @@ -8,10 +8,9 @@ package codegen // LLVM-DAG: %go.runtime.ITab = type <{ ptr, ptr, i32, [4 x i8], [1 x ptr] }> // LLVM-DAG: %go.itab.codegen.llvmInterfaceValue_codegen.llvmInterface = type <{ %go.runtime.ITab, [1 x ptr] }> -// LLVM-DAG: @"go:itab.codegen.llvmInterfaceValue,codegen.llvmInterface" = constant %go.itab.codegen.llvmInterfaceValue_codegen.llvmInterface +// LLVM-DAG: @"go:itab.codegen.llvmInterfaceValue,codegen.llvmInterface" = weak constant %go.itab.codegen.llvmInterfaceValue_codegen.llvmInterface // LLVM-DAG: ptr @"codegen.(*llvmInterfaceValue).Double" // LLVM-DAG: ptr @"codegen.(*llvmInterfaceValue).Value" -// LLVM-DAG: !goobj.symbol.flags // LLVM-DAG: !goobj.relocs // LLVM-LABEL: define goabiinternal i64 @codegen.useLLVMInterface( // LLVM-SAME: !goobj.marker_relocs diff --git a/test/codegen/type_descriptor.go b/test/codegen/type_descriptor.go index b12a197923a95f..70e79d4e4abb1f 100644 --- a/test/codegen/type_descriptor.go +++ b/test/codegen/type_descriptor.go @@ -10,7 +10,7 @@ package codegen // LLVM-DAG: %go.runtime.UncommonType = type <{ // LLVM-DAG: %go.runtime.StructField = type <{ // LLVM-DAG: %go.descriptor.codegen.llvmTypeDescriptor = type <{ -// LLVM-DAG: @"type:codegen.llvmTypeDescriptor" = constant %go.descriptor.codegen.llvmTypeDescriptor{{.*}}!goobj.symbol.flags{{.*}}!goobj.relocs +// LLVM-DAG: @"type:codegen.llvmTypeDescriptor" = constant %go.descriptor.codegen.llvmTypeDescriptor{{.*}}!goobj.relocs // LLVM-DAG: @"type:*codegen.llvmTypeDescriptor" = constant %go.descriptor._codegen.llvmTypeDescriptor{{.*}}!goobj.symbol.flags{{.*}}!goobj.relocs // llvmTypeDescriptor exercises the compiler-owned reflectdata path. LLVM data From 4bd119eb95d19f6df3df1848614e7e301f67784e Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 28 Jul 2026 09:49:05 +0800 Subject: [PATCH 11/17] cmd/compile: retain only weak GoObj relocation metadata --- doc/goallc-llvm-goobj.md | 5 +++-- src/cmd/compile/internal/ssa/llvmdata.go | 22 ++++++++++++---------- test/codegen/interface.go | 2 +- test/codegen/type_descriptor.go | 5 +++-- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/doc/goallc-llvm-goobj.md b/doc/goallc-llvm-goobj.md index f95344d9c545f4..9802426fd29443 100644 --- a/doc/goallc-llvm-goobj.md +++ b/doc/goallc-llvm-goobj.md @@ -50,8 +50,9 @@ LLVM IR 优先使用原生 linkage 和语义类型交接这些属性:Local 对 `internal`,非 Local 的 DUPOK 对应 `weak`,`%go.descriptor.*` 和 `%go.itab.*` 分别标识 Go type 与 itab。`!goobj.symbol.flags` 只保留 typelink、 UsedInIface、linkname 以及 Local+DUPOK 重叠等没有等价 LLVM 表示的位。 -`!goobj.relocs` 仍在默认 relocation 推导前恢复精确的 Go relocation type, -特别是 weak offset relocation。零宽度的 linker +普通 address、type offset 和 method offset 都由 LLVM initializer 与语义类型 +直接推导;`!goobj.weak_relocs` 只记录无法由 LLVM 表达的逐 relocation weak +属性。零宽度的 linker 保活边 `R_KEEP` 不伪装为地址常量,而用 `!goobj.keep` 记录其 target symbol, 由 writer 合成 GoObj relocation。最终链路仍只有 `__.PKGDEF + _go_.o` 两个有意义的 archive members;不会生成或合并 native data diff --git a/src/cmd/compile/internal/ssa/llvmdata.go b/src/cmd/compile/internal/ssa/llvmdata.go index 06bfe4a71cabf9..1ca7a6401d9530 100644 --- a/src/cmd/compile/internal/ssa/llvmdata.go +++ b/src/cmd/compile/internal/ssa/llvmdata.go @@ -87,7 +87,7 @@ func LowerGoObjTypeData() { g := globals[s] g.SetInitializer(lowerer.dataInitializer(s, globals)) setGoObjDataFlags(g, s) - setGoObjRelocMetadata(g, s) + setGoObjWeakRelocMetadata(g, s) setGoObjKeepMetadata(g, s) setGoObjGotypeMetadata(g, s) } @@ -321,15 +321,17 @@ func setGoObjDataFlags(g llvm.Value, s *obj.LSym) { })) } -func setGoObjRelocMetadata(g llvm.Value, s *obj.LSym) { - entries := make([]llvm.Metadata, 0, len(s.R)) +func setGoObjWeakRelocMetadata(g llvm.Value, s *obj.LSym) { + entries := make([]llvm.Metadata, 0) for _, r := range s.R { switch r.Type { - case objabi.R_ADDR, objabi.R_WEAKADDR, objabi.R_ADDROFF, objabi.R_WEAKADDROFF, objabi.R_METHODOFF: - entries = append(entries, GlobalCtxt.MDNode([]llvm.Metadata{ - llvm.ConstInt(GlobalCtxt.Int32Type(), uint64(r.Off), false).ConstantAsMetadata(), - llvm.ConstInt(GlobalCtxt.Int32Type(), uint64(uint16(r.Type)), false).ConstantAsMetadata(), - })) + case objabi.R_WEAKADDR, objabi.R_WEAKADDROFF: + entries = append(entries, + llvm.ConstInt(GlobalCtxt.Int32Type(), uint64(r.Off), false).ConstantAsMetadata()) + case objabi.R_ADDR, objabi.R_ADDROFF, objabi.R_METHODOFF: + // LLVM constants carry the offset, size, target, and addend. The + // GoObj writer derives the strong relocation kind from those + // semantics and the containing descriptor type. case objabi.R_KEEP: continue default: @@ -337,12 +339,12 @@ func setGoObjRelocMetadata(g llvm.Value, s *obj.LSym) { } } if len(entries) != 0 { - g.SetGlobalMetadata(GlobalCtxt.MDKindID("goobj.relocs"), GlobalCtxt.MDNode(entries)) + g.SetGlobalMetadata(GlobalCtxt.MDKindID("goobj.weak_relocs"), GlobalCtxt.MDNode(entries)) } } // R_KEEP is a zero-width linker reachability edge, not a storage relocation. -// Keep it separate from !goobj.relocs so llc can synthesize the GoObj record +// Keep it separate from !goobj.weak_relocs so llc can synthesize the GoObj record // without inventing bytes or a target-address expression in the global. func setGoObjKeepMetadata(g llvm.Value, s *obj.LSym) { entries := make([]llvm.Metadata, 0) diff --git a/test/codegen/interface.go b/test/codegen/interface.go index 08865495bf0b99..af19a5b21db56d 100644 --- a/test/codegen/interface.go +++ b/test/codegen/interface.go @@ -11,7 +11,7 @@ package codegen // LLVM-DAG: @"go:itab.codegen.llvmInterfaceValue,codegen.llvmInterface" = weak constant %go.itab.codegen.llvmInterfaceValue_codegen.llvmInterface // LLVM-DAG: ptr @"codegen.(*llvmInterfaceValue).Double" // LLVM-DAG: ptr @"codegen.(*llvmInterfaceValue).Value" -// LLVM-DAG: !goobj.relocs +// LLVM-DAG: !goobj.weak_relocs // LLVM-LABEL: define goabiinternal i64 @codegen.useLLVMInterface( // LLVM-SAME: !goobj.marker_relocs // LLVM-LABEL: define goabiinternal i64 @codegen.llvmInterface.Value( diff --git a/test/codegen/type_descriptor.go b/test/codegen/type_descriptor.go index 70e79d4e4abb1f..02c95a3e460231 100644 --- a/test/codegen/type_descriptor.go +++ b/test/codegen/type_descriptor.go @@ -10,8 +10,9 @@ package codegen // LLVM-DAG: %go.runtime.UncommonType = type <{ // LLVM-DAG: %go.runtime.StructField = type <{ // LLVM-DAG: %go.descriptor.codegen.llvmTypeDescriptor = type <{ -// LLVM-DAG: @"type:codegen.llvmTypeDescriptor" = constant %go.descriptor.codegen.llvmTypeDescriptor{{.*}}!goobj.relocs -// LLVM-DAG: @"type:*codegen.llvmTypeDescriptor" = constant %go.descriptor._codegen.llvmTypeDescriptor{{.*}}!goobj.symbol.flags{{.*}}!goobj.relocs +// LLVM-DAG: @"type:codegen.llvmTypeDescriptor" = constant %go.descriptor.codegen.llvmTypeDescriptor +// LLVM-DAG: @"type:*codegen.llvmTypeDescriptor" = constant %go.descriptor._codegen.llvmTypeDescriptor{{.*}}!goobj.symbol.flags +// LLVM-NOT: !goobj.relocs // llvmTypeDescriptor exercises the compiler-owned reflectdata path. LLVM data // lowering must preserve the resulting Go type descriptors and relocation From 3f0bec5b0d55e1b28ce98dba153e86db55541d6a Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 28 Jul 2026 10:03:43 +0800 Subject: [PATCH 12/17] cmd/compile: model GoObj linker relations in LLVM IR --- doc/goallc-llvm-goobj.md | 8 ++- src/cmd/compile/internal/ssa/llvmdata.go | 80 +++++++++++++++++++----- src/cmd/compile/internal/ssa/ssa2llvm.go | 4 ++ test/codegen/interface.go | 8 ++- test/codegen/interface_assertion.go | 6 +- test/codegen/interface_conversion.go | 5 +- test/codegen/interface_switch.go | 4 +- 7 files changed, 91 insertions(+), 24 deletions(-) diff --git a/doc/goallc-llvm-goobj.md b/doc/goallc-llvm-goobj.md index 9802426fd29443..455acc824e66cf 100644 --- a/doc/goallc-llvm-goobj.md +++ b/doc/goallc-llvm-goobj.md @@ -53,8 +53,12 @@ UsedInIface、linkname 以及 Local+DUPOK 重叠等没有等价 LLVM 表示的 普通 address、type offset 和 method offset 都由 LLVM initializer 与语义类型 直接推导;`!goobj.weak_relocs` 只记录无法由 LLVM 表达的逐 relocation weak 属性。零宽度的 linker -保活边 `R_KEEP` 不伪装为地址常量,而用 `!goobj.keep` 记录其 target symbol, -由 writer 合成 GoObj relocation。最终链路仍只有 +保活边 `R_KEEP` 不伪装为地址常量,而用模块级 `!goobj.keep` 关系表记录; +`gotype` aux 和 interface dead-method marker 也使用模块级关系表。表中的 source +与 target 都是对 LLVM global/function 的直接引用,不再以字符串重复符号名, +因此 LLVM 重命名和 RAUW 会同步更新这些关系;关系涉及的值同时进入 +`@llvm.compiler.used`,避免 GlobalDCE 删除仅承担对象格式语义的声明。该 LLVM +特殊全局本身不生成数据,writer 再根据关系表合成 GoObj relocation/aux。最终链路仍只有 `__.PKGDEF + _go_.o` 两个有意义的 archive members;不会生成或合并 native data object。 diff --git a/src/cmd/compile/internal/ssa/llvmdata.go b/src/cmd/compile/internal/ssa/llvmdata.go index 1ca7a6401d9530..2aabbee2a8498c 100644 --- a/src/cmd/compile/internal/ssa/llvmdata.go +++ b/src/cmd/compile/internal/ssa/llvmdata.go @@ -49,6 +49,7 @@ func LowerGoObjTypeData() { } if len(closure) == 0 { + emitGoObjCompilerUsed() return } @@ -91,6 +92,7 @@ func LowerGoObjTypeData() { setGoObjKeepMetadata(g, s) setGoObjGotypeMetadata(g, s) } + emitGoObjCompilerUsed() } // llvmGoDataRef returns the module-global address for a compiler LSym. Local @@ -347,7 +349,6 @@ func setGoObjWeakRelocMetadata(g llvm.Value, s *obj.LSym) { // Keep it separate from !goobj.weak_relocs so llc can synthesize the GoObj record // without inventing bytes or a target-address expression in the global. func setGoObjKeepMetadata(g llvm.Value, s *obj.LSym) { - entries := make([]llvm.Metadata, 0) for _, r := range s.R { if r.Type != objabi.R_KEEP { continue @@ -355,10 +356,12 @@ func setGoObjKeepMetadata(g llvm.Value, s *obj.LSym) { if r.Sym == nil { base.Fatalf("nil R_KEEP target in %s", s.Name) } - entries = append(entries, GlobalCtxt.MDString(r.Sym.Name)) - } - if len(entries) != 0 { - g.SetGlobalMetadata(GlobalCtxt.MDKindID("goobj.keep"), GlobalCtxt.MDNode(entries)) + target := llvmGoDataRef(r.Sym) + preserveGoObjMetadataValues(g, target) + CurrentModule.AddNamedMetadataOperand("goobj.keep", GlobalCtxt.MDNode([]llvm.Metadata{ + g.ConstantAsMetadata(), + target.ConstantAsMetadata(), + })) } } @@ -366,17 +369,22 @@ func setGoObjGotypeMetadata(g llvm.Value, s *obj.LSym) { if s.Gotype == nil { return } - if CurrentModule.NamedGlobal(s.Gotype.Name).IsNil() { - base.Fatalf("Go type symbol %s for %s was not included in LLVM data closure", s.Gotype.Name, s.Name) + target := CurrentModule.NamedGlobal(s.Gotype.Name) + if target.IsNil() { + target = llvmGoDataRef(s.Gotype) } - g.SetGlobalMetadata(GlobalCtxt.MDKindID("goobj.gotype"), GlobalCtxt.MDNode([]llvm.Metadata{ - GlobalCtxt.MDString(s.Gotype.Name), + preserveGoObjMetadataValues(g, target) + CurrentModule.AddNamedMetadataOperand("goobj.gotype", GlobalCtxt.MDNode([]llvm.Metadata{ + g.ConstantAsMetadata(), + target.ConstantAsMetadata(), })) } // Interface dead-method elimination uses zero-width marker relocations on the // containing function. LLVM calls and globals cannot encode those records, so -// carry the exact Go relocation type, addend, and target in function metadata. +// carry the exact source, target, Go relocation type, and addend in a module +// relationship table. Direct value references let LLVM keep the relationships +// synchronized across renaming and RAUW. func setGoObjFunctionRelocMetadata(fn llvm.Value, s *obj.LSym) { entries := make([]llvm.Metadata, 0) for _, r := range s.R { @@ -385,19 +393,63 @@ func setGoObjFunctionRelocMetadata(fn llvm.Value, s *obj.LSym) { if r.Sym == nil { base.Fatalf("nil interface marker target in %s", s.Name) } - llvmGoDataRef(r.Sym) + target := llvmGoDataRef(r.Sym) + preserveGoObjMetadataValues(fn, target) entries = append(entries, GlobalCtxt.MDNode([]llvm.Metadata{ + fn.ConstantAsMetadata(), + target.ConstantAsMetadata(), llvm.ConstInt(GlobalCtxt.Int32Type(), uint64(uint16(r.Type)), false).ConstantAsMetadata(), llvm.ConstInt(GlobalCtxt.Int64Type(), uint64(r.Add), true).ConstantAsMetadata(), - GlobalCtxt.MDString(r.Sym.Name), })) } } - if len(entries) != 0 { - fn.SetGlobalMetadata(GlobalCtxt.MDKindID("goobj.marker_relocs"), GlobalCtxt.MDNode(entries)) + for _, entry := range entries { + CurrentModule.AddNamedMetadataOperand("goobj.marker_relocs", entry) } } +// Named metadata references participate in RAUW, but GlobalDCE is allowed to +// delete values that are referenced only from metadata. llvm.compiler.used is +// LLVM's native way to state that those values also have object-format +// semantics. It emits no storage; Go linker reachability remains controlled by +// the R_KEEP and marker records produced from the relationship tables. +func preserveGoObjMetadataValues(values ...llvm.Value) { + for _, v := range values { + if v.IsNil() || v.Name() == "" { + base.Fatalf("invalid LLVM value in GoObj metadata relationship") + } + if goObjCompilerUsedNames[v.Name()] { + continue + } + goObjCompilerUsedNames[v.Name()] = true + goObjCompilerUsed = append(goObjCompilerUsed, v) + } +} + +func emitGoObjCompilerUsed() { + if len(goObjCompilerUsed) == 0 { + return + } + sort.Slice(goObjCompilerUsed, func(i, j int) bool { + return goObjCompilerUsed[i].Name() < goObjCompilerUsed[j].Name() + }) + values := append([]llvm.Value(nil), goObjCompilerUsed...) + if old := CurrentModule.NamedGlobal("llvm.compiler.used"); !old.IsNil() { + init := old.Initializer() + for i := 0; i < init.OperandsCount(); i++ { + values = append(values, init.Operand(i)) + } + old.EraseFromParentAsGlobal() + } + init := llvm.ConstArray(GlobalCtxt.PointerType(0), values) + used := llvm.AddGlobal(CurrentModule, init.Type(), "llvm.compiler.used") + used.SetLinkage(llvm.AppendingLinkage) + used.SetSection("llvm.metadata") + used.SetInitializer(init) + goObjCompilerUsed = nil + goObjCompilerUsedNames = make(map[string]bool) +} + func llvmDataStorageRelocs(s *obj.LSym) []obj.Reloc { relocs := make([]obj.Reloc, 0, len(s.R)) for _, r := range s.R { diff --git a/src/cmd/compile/internal/ssa/ssa2llvm.go b/src/cmd/compile/internal/ssa/ssa2llvm.go index cfc90a3f606b33..4ddaae9700e340 100644 --- a/src/cmd/compile/internal/ssa/ssa2llvm.go +++ b/src/cmd/compile/internal/ssa/ssa2llvm.go @@ -806,6 +806,8 @@ var CurrentModule llvm.Module var type2lTypes = map[*types.Type]llvm.Type{} var goObjConfigWritten bool var currentLLVMDataLowerer *llvmDataLowerer +var goObjCompilerUsed []llvm.Value +var goObjCompilerUsedNames map[string]bool var GlobalCtxt = llvm.GlobalContext() @@ -935,6 +937,8 @@ func InitModule(pkg *types.Pkg) { CurrentModule.SetTarget(goObjTargetTriple()) goObjConfigWritten = false currentLLVMDataLowerer = newLLVMDataLowerer(make(map[*obj.LSym]bool)) + goObjCompilerUsed = nil + goObjCompilerUsedNames = make(map[string]bool) } // goObjTargetTriple identifies the GoObj target that llc should use when it diff --git a/test/codegen/interface.go b/test/codegen/interface.go index af19a5b21db56d..c94c8eb1f85e25 100644 --- a/test/codegen/interface.go +++ b/test/codegen/interface.go @@ -12,8 +12,8 @@ package codegen // LLVM-DAG: ptr @"codegen.(*llvmInterfaceValue).Double" // LLVM-DAG: ptr @"codegen.(*llvmInterfaceValue).Value" // LLVM-DAG: !goobj.weak_relocs +// LLVM-DAG: @llvm.compiler.used = appending global // LLVM-LABEL: define goabiinternal i64 @codegen.useLLVMInterface( -// LLVM-SAME: !goobj.marker_relocs // LLVM-LABEL: define goabiinternal i64 @codegen.llvmInterface.Value( // LLVM: load ptr, ptr // LLVM: call goabiinternal i64 % @@ -23,8 +23,10 @@ package codegen // LLVM: load ptr, ptr // LLVM: call goabiinternal i64 % // LLVM-SAME: (ptr -// LLVM: !{i32 23, i64 0, !"type:codegen.llvmInterfaceValue"} -// LLVM-DAG: !{i32 24, i64 {{[0-9]+}}, !"type:codegen.llvmInterface"} +// LLVM: !goobj.marker_relocs = !{ +// LLVM-DAG: !{ptr @codegen.useLLVMInterface, ptr @"type:codegen.llvmInterfaceValue", i32 23, i64 0} +// LLVM-DAG: !{ptr @codegen.llvmInterface.Value, ptr @"type:codegen.llvmInterface", i32 24, i64 {{[0-9]+}}} +// LLVM-DAG: !{ptr @codegen.llvmInterface.Double, ptr @"type:codegen.llvmInterface", i32 24, i64 {{[0-9]+}}} type llvmInterface interface { Double() int diff --git a/test/codegen/interface_assertion.go b/test/codegen/interface_assertion.go index d612e9bffd0eaf..125ebdabcc3250 100644 --- a/test/codegen/interface_assertion.go +++ b/test/codegen/interface_assertion.go @@ -17,8 +17,7 @@ func (v llvmAssertionValue) Value(delta int) int { return int(v) + delta } -// LLVM: @codegen..typeAssert.0 = global <{ ptr, ptr, [8 x i8] }> -// LLVM-SAME: !goobj.gotype +// LLVM: @codegen..typeAssert.0 = internal global <{ ptr, ptr, [8 x i8] }> // LLVM-LABEL: define goabiinternal { { ptr, ptr }, i8 } @codegen.assertLLVMInterface( // LLVM: load atomic ptr, ptr @codegen..typeAssert.0 seq_cst // LLVM: call goabiinternal ptr @runtime.typeAssert(ptr @codegen..typeAssert.0, ptr @@ -49,3 +48,6 @@ func mustLLVMConcrete(v any) llvmAssertionValue { func mustLLVMInterface(v any) llvmAssertionInterface { return v.(llvmAssertionInterface) } + +// LLVM: !goobj.gotype = !{ +// LLVM-DAG: !{ptr @codegen..typeAssert.0, ptr @ diff --git a/test/codegen/interface_conversion.go b/test/codegen/interface_conversion.go index b63bd5d1cbbf64..49cb53fa8c6da8 100644 --- a/test/codegen/interface_conversion.go +++ b/test/codegen/interface_conversion.go @@ -6,13 +6,14 @@ package codegen -// LLVM: @codegen..typeAssert.0 = global <{ ptr, ptr, [8 x i8] }> -// LLVM-SAME: !goobj.gotype +// LLVM: @codegen..typeAssert.0 = internal global <{ ptr, ptr, [8 x i8] }> // LLVM-LABEL: define goabiinternal { ptr, ptr } @codegen.convertLLVMInterface( // LLVM: load atomic ptr, ptr @codegen..typeAssert.0 seq_cst // LLVM: ptrtoint ptr // LLVM: call goabiinternal ptr @runtime.typeAssert(ptr @codegen..typeAssert.0, ptr // LLVM-DAG: declare goabiinternal ptr @runtime.typeAssert(ptr, ptr) +// LLVM: !goobj.gotype = !{ +// LLVM-DAG: !{ptr @codegen..typeAssert.0, ptr @ type llvmSourceInterface interface { Double() int diff --git a/test/codegen/interface_switch.go b/test/codegen/interface_switch.go index 237c39a741aff2..365db21d07d3eb 100644 --- a/test/codegen/interface_switch.go +++ b/test/codegen/interface_switch.go @@ -26,7 +26,7 @@ func (v llvmDoubleSwitchValue) Double() int { return int(v) * 2 } -// LLVM-DAG: @codegen..interfaceSwitch.0 = global <{ ptr, [8 x i8], ptr, ptr }> {{.*}}!goobj.gotype +// LLVM-DAG: @codegen..interfaceSwitch.0 = internal global <{ ptr, [8 x i8], ptr, ptr }> // LLVM-DAG: load atomic ptr, ptr @codegen..interfaceSwitch.0 seq_cst // LLVM-DAG: call goabiinternal { i64, ptr } @runtime.interfaceSwitch(ptr @codegen..interfaceSwitch.0, ptr // LLVM-DAG: declare goabiinternal { i64, ptr } @runtime.interfaceSwitch(ptr, ptr) @@ -34,6 +34,8 @@ func (v llvmDoubleSwitchValue) Double() int { // LLVM-DAG: @"type:codegen.llvmSwitchValue" // LLVM-DAG: @"type:string" // LLVM-DAG: ret i64 -1 +// LLVM: !goobj.gotype = !{ +// LLVM-DAG: !{ptr @codegen..interfaceSwitch.0, ptr @ func classifyLLVMInterface(v any) int { switch x := v.(type) { case nil: From 286e14344745a748f51a5c49444d35cb98576174 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 28 Jul 2026 10:35:06 +0800 Subject: [PATCH 13/17] cmd/internal/testdir: refresh importcfg for each test run --- src/cmd/internal/testdir/testdir_test.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/cmd/internal/testdir/testdir_test.go b/src/cmd/internal/testdir/testdir_test.go index 8c0b0e3f57d206..1215a61c11bc0a 100644 --- a/src/cmd/internal/testdir/testdir_test.go +++ b/src/cmd/internal/testdir/testdir_test.go @@ -121,6 +121,10 @@ func Test(t *testing.T) { goExperiment = env.GOEXPERIMENT goDebug = env.GODEBUG tmpDir = t.TempDir() + stdlibImportcfgPath = filepath.Join(tmpDir, "importcfg") + if err := os.WriteFile(stdlibImportcfgPath, []byte(stdlibImportcfg()), 0644); err != nil { + t.Fatal(err) + } common := testCommon{ gorootTestDir: filepath.Join(testenv.GOROOT(t), "test"), @@ -229,14 +233,14 @@ var stdlibImportcfg = sync.OnceValue(func() string { return string(output) }) -var stdlibImportcfgFile = sync.OnceValue(func() string { - filename := filepath.Join(tmpDir, "importcfg") - err := os.WriteFile(filename, []byte(stdlibImportcfg()), 0644) - if err != nil { - log.Fatal(err) +var stdlibImportcfgPath string + +func stdlibImportcfgFile() string { + if stdlibImportcfgPath == "" { + panic("stdlib importcfg was not initialized") } - return filename -}) + return stdlibImportcfgPath +} // linkFile links infile with the given importcfg and ldflags, writes to outfile. // infile can be the name of an object file or a go source file. From 60c4e9164171f31c592c0fa2dafc00f56432bc4f Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 28 Jul 2026 10:35:11 +0800 Subject: [PATCH 14/17] test: expand LLVM coverage with native cases --- test/codegen/addrcalc.go | 5 +++++ test/codegen/alloc.go | 7 +++++++ test/codegen/multiply.go | 7 +++++++ test/codegen/shortcircuit.go | 5 +++++ test/codegen/smallintiface.go | 6 ++++++ test/codegen/structs.go | 7 +++++++ test/codegen/type_descriptor_methods.go | 4 +++- test/llvm_tests.json | 15 +++++++++++++++ 8 files changed, 55 insertions(+), 1 deletion(-) diff --git a/test/codegen/addrcalc.go b/test/codegen/addrcalc.go index 9dddb1ba946852..850257d2780794 100644 --- a/test/codegen/addrcalc.go +++ b/test/codegen/addrcalc.go @@ -6,6 +6,11 @@ package codegen +// LLVM-LABEL: define goabiinternal ptr @codegen.f( +// LLVM: icmp ult i64 %x, 4 +// LLVM: getelementptr [2 x i64], ptr %p, i64 %x +// LLVM: getelementptr i64, ptr + // Make sure we use ADDQ instead of LEAQ when we can. func f(p *[4][2]int, x int) *int { diff --git a/test/codegen/alloc.go b/test/codegen/alloc.go index f755bb54a26795..7d62530e49ba6a 100644 --- a/test/codegen/alloc.go +++ b/test/codegen/alloc.go @@ -9,6 +9,13 @@ package codegen +// LLVM-DAG: @runtime.zerobase = external global i8 +// LLVM-DAG: define goabiinternal ptr @codegen.zeroAllocNew1() +// LLVM-DAG: ret ptr @runtime.zerobase +// LLVM-DAG: define goabiinternal ptr @codegen.zeroAllocNew2() +// LLVM-DAG: define goabiinternal { ptr, i64, i64 } @codegen.zeroAllocSliceLit() +// LLVM-DAG: ret { ptr, i64, i64 } { ptr @runtime.zerobase, i64 0, i64 0 } + func zeroAllocNew1() *struct{} { // 386:-`CALL runtime\.newobject` `LEAL runtime.zerobase` // amd64:-`CALL runtime\.newobject` `LEAQ runtime.zerobase` diff --git a/test/codegen/multiply.go b/test/codegen/multiply.go index 8c76fd9bb188b1..c6906b984c5112 100644 --- a/test/codegen/multiply.go +++ b/test/codegen/multiply.go @@ -6,6 +6,13 @@ package codegen +// LLVM-DAG: define goabiinternal i64 @codegen.m0(i64 +// LLVM-DAG: mul i64 %x, 0 +// LLVM-DAG: define goabiinternal i64 @codegen.m3(i64 +// LLVM-DAG: mul i64 %x, 3 +// LLVM-DAG: define goabiinternal i64 @codegen.mn20(i64 +// LLVM-DAG: mul i64 %x, -20 + // This file contains codegen tests related to strength // reduction of integer multiply. diff --git a/test/codegen/shortcircuit.go b/test/codegen/shortcircuit.go index e971dca31eb9ce..da661d911b3f67 100644 --- a/test/codegen/shortcircuit.go +++ b/test/codegen/shortcircuit.go @@ -6,6 +6,11 @@ package codegen +// LLVM-LABEL: define goabiinternal i64 @codegen.efaceExtract( +// LLVM: extractvalue { ptr, ptr } %e, 0 +// LLVM: icmp eq ptr %{{.*}}, @"type:int" +// LLVM: extractvalue { ptr, ptr } %e, 1 + func efaceExtract(e interface{}) int { // This should be compiled with only // a single conditional jump. diff --git a/test/codegen/smallintiface.go b/test/codegen/smallintiface.go index e1dbae5f9abea4..889b942c6a1fbc 100644 --- a/test/codegen/smallintiface.go +++ b/test/codegen/smallintiface.go @@ -2,6 +2,12 @@ package codegen +// LLVM-DAG: @runtime.staticuint64s = external global i8 +// LLVM-DAG: define goabiinternal { ptr, ptr } @codegen.booliface() +// LLVM-DAG: define goabiinternal { ptr, ptr } @codegen.smallint8iface() +// LLVM-DAG: define goabiinternal { ptr, ptr } @codegen.smalluint8iface() +// LLVM-DAG: getelementptr i8, ptr @runtime.staticuint64s, i64 + // Copyright 2020 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. diff --git a/test/codegen/structs.go b/test/codegen/structs.go index f789249ee3b776..9509d6f8e94e8d 100644 --- a/test/codegen/structs.go +++ b/test/codegen/structs.go @@ -8,6 +8,13 @@ package codegen +// LLVM-DAG: define goabiinternal void @codegen.Zero1(ptr +// LLVM-DAG: store %codegen.Z1 zeroinitializer, ptr +// LLVM-DAG: define goabiinternal void @codegen.Zero2(ptr +// LLVM-DAG: store ptr null, ptr +// LLVM-DAG: define goabiinternal void @codegen.Init1(ptr +// LLVM-DAG: store %codegen.I1 { i64 1, i64 2, i64 3, i64 4 }, ptr + // This file contains code generation tests related to the handling of // struct types. diff --git a/test/codegen/type_descriptor_methods.go b/test/codegen/type_descriptor_methods.go index a3420c9e38e512..1f21261f8112d4 100644 --- a/test/codegen/type_descriptor_methods.go +++ b/test/codegen/type_descriptor_methods.go @@ -11,7 +11,9 @@ package codegen // LLVM-DAG: %go.descriptor._codegen.llvmMethodType = type <{ %go.runtime.PtrType, %go.runtime.UncommonType, [3 x %go.runtime.Method] }> // LLVM-DAG: @"type:codegen.llvmMethodType" = constant %go.descriptor.codegen.llvmMethodType // LLVM-DAG: @"type:*codegen.llvmMethodType" = constant %go.descriptor._codegen.llvmMethodType -// LLVM-DAG: !{{[0-9]+}} = !{i32 {{[0-9]+}}, i32 26} +// LLVM-DAG: i32 ptrtoint (ptr @"codegen.(*llvmMethodType).Pointer" to i32) +// LLVM-DAG: i32 ptrtoint (ptr @"codegen.(*llvmMethodType).Value" to i32) +// LLVM-NOT: !goobj.relocs type llvmMethodType struct { value int diff --git a/test/llvm_tests.json b/test/llvm_tests.json index c033221928725c..d7c1f1cf35e736 100644 --- a/test/llvm_tests.json +++ b/test/llvm_tests.json @@ -1,8 +1,14 @@ { "codegen": { "whitelist": { + "codegen/addrcalc.go": "bounds-checked nested array address calculation", + "codegen/alloc.go": "zero-sized allocations and slice literals through runtime.zerobase", "codegen/divmod.go": "integer division, remainder, overflow handling, and tuple ABI", "codegen/logic.go": "integer logic and conditional control flow", + "codegen/multiply.go": "integer multiplication across positive, negative, and zero constants", + "codegen/shortcircuit.go": "empty-interface extraction and concrete type comparison", + "codegen/smallintiface.go": "small scalar boxing through runtime.staticuint64s", + "codegen/structs.go": "typed struct initialization and zeroing", "codegen/type_descriptor.go": "compiler-owned runtime type descriptor data and GoObj metadata", "codegen/type_descriptor_kinds.go": "all runtime type descriptor layouts and variable descriptor data", "codegen/type_descriptor_methods.go": "uncommon type data, concrete method tables, wrappers, and R_METHODOFF", @@ -18,11 +24,20 @@ }, "runtime": { "whitelist": { + "alias1.go": "byte and rune aliases in concrete interface type switches", + "char_lit.go": "character literal constants and external os.Exit reference", + "compos.go": "heap allocation of escaping composite literals and pointer inequality", + "const8.go": "implicit constant declarations and iota shadowing", "helloworld.go": "basic main-to-runtime call and execution", + "int_lit.go": "integer literal constants and external os.Exit reference", + "iota.go": "integer, float, string, boolean, and paired iota constants", "llvm_type_descriptor.go": "type descriptor data, ABI-internal equality closure, GoObj link, and execution", "llvm_type_descriptor_kinds.go": "all runtime type descriptor kinds, GoObj link, and execution", "llvm_type_descriptor_map.go": "typed map descriptor layout, R_KEEP reachability edge, GoObj link, and execution", "llvm_type_descriptor_methods.go": "uncommon concrete methods, method wrappers, direct calls, GoObj link, and execution", + "printbig.go": "signed and unsigned 64-bit boundary constants through runtime print", + "sizeof.go": "unsafe Sizeof, Alignof, and deeply embedded Offsetof constants", + "varinit.go": "nested variable initialization and lexical shadowing", "llvm_interface.go": "static multi-method itab, interface conversion, indirect calls, GoObj link, and execution", "llvm_interface_conversion.go": "dynamic interface-to-interface conversion cache, runtime fallback, nil conversion, and execution", "llvm_interface_assertion.go": "comma-ok and panic-form assertions, cache fallback, mismatch, nil, and execution", From 15c456504616f718f5c576bdd2c91a875ba82177 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 28 Jul 2026 10:49:09 +0800 Subject: [PATCH 15/17] test: whitelist all supported LLVM codegen cases --- src/cmd/internal/testdir/llvm_test.go | 7 ++++++ test/codegen/arithmetic.go | 8 ++++++ test/codegen/bitfield.go | 9 +++++++ test/codegen/compare_and_branch.go | 7 ++++++ test/codegen/issue22703.go | 8 ++++++ test/codegen/issue25378.go | 8 ++++++ test/codegen/issue31618.go | 8 ++++++ test/codegen/issue33580.go | 9 +++++++ test/codegen/issue42610.go | 8 ++++++ test/codegen/issue48054.go | 7 ++++++ test/codegen/issue52635.go | 7 ++++++ test/codegen/issue59297.go | 5 ++++ test/codegen/issue60673.go | 6 +++++ test/codegen/issue61356.go | 9 +++++++ test/codegen/issue63332.go | 5 ++++ test/codegen/issue66585.go | 8 ++++++ test/codegen/issue69635.go | 5 ++++ test/codegen/issue72832.go | 9 +++++++ test/codegen/issue74485.go | 9 +++++++ test/codegen/known_bits.go | 10 ++++++++ test/codegen/memops.go | 11 ++++++++ test/codegen/memops_bigoffset.go | 9 +++++++ test/codegen/regabi_regalloc.go | 6 +++++ test/codegen/shift.go | 8 ++++++ test/codegen/spills.go | 9 +++++++ test/llvm_tests.json | 36 ++++++++++++++++++++++----- 26 files changed, 225 insertions(+), 6 deletions(-) diff --git a/src/cmd/internal/testdir/llvm_test.go b/src/cmd/internal/testdir/llvm_test.go index 1d7884293b5e7f..038011f80f3ae0 100644 --- a/src/cmd/internal/testdir/llvm_test.go +++ b/src/cmd/internal/testdir/llvm_test.go @@ -253,6 +253,13 @@ func runLLVMCodegenTest(t *testing.T, gorootTestDir, name string) { if err != nil { t.Fatal(err) } + opt := llvmToolPath(t, "opt", "GOALLC_OPT") + cmd = exec.Command(opt, "-passes=verify", "-disable-output") + cmd.Stdin = bytes.NewReader(irBytes) + cmd.Env = append(os.Environ(), "GOENV=off", "GOFLAGS=") + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("LLVM verifier failed: %v\n%s", err, out) + } fileCheck := llvmToolPath(t, "FileCheck", "GOALLC_FILECHECK") cmd = exec.Command(fileCheck, "--check-prefix=LLVM", source) cmd.Stdin = bytes.NewReader(irBytes) diff --git a/test/codegen/arithmetic.go b/test/codegen/arithmetic.go index 54880092e953a8..031c0a52b4e7bf 100644 --- a/test/codegen/arithmetic.go +++ b/test/codegen/arithmetic.go @@ -6,6 +6,14 @@ package codegen +// LLVM-DAG: define goabiinternal { i64, i64, i64, i64, i64, i64 } @codegen.SubAddSimplify2 +// LLVM-DAG: define goabiinternal i64 @codegen.SubMem({ ptr, i64, i64 } %arr +// LLVM-DAG: extractvalue { ptr, i64, i64 } %arr, 1 +// LLVM-DAG: getelementptr i64, ptr {{%.*}}, i64 0 +// LLVM-DAG: sub i64 +// LLVM-DAG: store i64 +// LLVM-DAG: call goabiinternal void @runtime.goPanicIndex + // This file contains codegen tests related to arithmetic // simplifications and optimizations on integer types. // For codegen tests on float types, see floats.go. diff --git a/test/codegen/bitfield.go b/test/codegen/bitfield.go index c377fb3c28ae89..a049fbad59e646 100644 --- a/test/codegen/bitfield.go +++ b/test/codegen/bitfield.go @@ -6,6 +6,15 @@ package codegen +// LLVM-DAG: define goabiinternal i64 @codegen.shift(i32 %x, i16 %y, i8 %z) +// LLVM-DAG: zext i32 %x to i64 +// LLVM-DAG: lshr i64 {{%.*}}, 32 +// LLVM-DAG: zext i16 %y to i64 +// LLVM-DAG: zext i8 %z to i64 +// LLVM-DAG: define goabiinternal { i32, i32, i32 } @codegen.rev16w(i32 %c) +// LLVM-DAG: lshr i32 {{%.*}}, 8 +// LLVM-DAG: shl i32 {{%.*}}, 8 + // This file contains codegen tests related to bit field // insertion/extraction simplifications/optimizations. diff --git a/test/codegen/compare_and_branch.go b/test/codegen/compare_and_branch.go index 7ffd25ced81fb5..acbf4951f04131 100644 --- a/test/codegen/compare_and_branch.go +++ b/test/codegen/compare_and_branch.go @@ -6,6 +6,13 @@ package codegen +// LLVM-DAG: define goabiinternal void @codegen.ui64x8() +// LLVM-DAG: icmp ult i64 {{%.*}}, 128 +// LLVM-DAG: define goabiinternal void @codegen.si64x8(i64 %{{.*}}) +// LLVM-DAG: icmp slt i64 {{%.*}}, 128 +// LLVM-DAG: call goabiinternal void @codegen.dummy() +// LLVM-DAG: br i1 + //go:noinline func dummy() {} diff --git a/test/codegen/issue22703.go b/test/codegen/issue22703.go index 0201de6530cc67..4a86d0607a135e 100644 --- a/test/codegen/issue22703.go +++ b/test/codegen/issue22703.go @@ -6,6 +6,14 @@ package codegen +// LLVM-DAG: @"type:codegen.I" = constant %go.descriptor.codegen.I +// LLVM-DAG: [512 x %go.runtime.Imethod] +// LLVM-DAG: define goabiinternal void @codegen.callMethodLargeOffset({ ptr, ptr } %i) +// LLVM-DAG: getelementptr i8, ptr {{%.*}}, i64 4112 +// LLVM-DAG: define goabiinternal void @codegen.callMethodSmallOffset({ ptr, ptr } %i) +// LLVM-DAG: getelementptr i8, ptr {{%.*}}, i64 32 +// LLVM-DAG: call goabiinternal void {{%.*}}(ptr {{%.*}}) + type I interface { foo000() foo001() diff --git a/test/codegen/issue25378.go b/test/codegen/issue25378.go index ddb5cecc4018bd..4b62a589f87328 100644 --- a/test/codegen/issue25378.go +++ b/test/codegen/issue25378.go @@ -6,6 +6,14 @@ package codegen +// LLVM-DAG: @codegen.wsp = constant <{ [256 x i8] }> +// LLVM-DAG: define goabiinternal i8 @codegen.zeroExtArgUint16([2 x i16] +// LLVM-DAG: zext i16 {{%.*}} to i64 +// LLVM-DAG: icmp ult i64 {{%.*}}, 256 +// LLVM-DAG: getelementptr i8, ptr @codegen.wsp, i64 +// LLVM-DAG: define goabiinternal i8 @codegen.zeroExtArgByte([2 x i8] +// LLVM-DAG: zext i8 {{%.*}} to i64 + var wsp = [256]bool{ ' ': true, '\t': true, diff --git a/test/codegen/issue31618.go b/test/codegen/issue31618.go index 8effe2995b82c5..cc65b148ee38e3 100644 --- a/test/codegen/issue31618.go +++ b/test/codegen/issue31618.go @@ -6,6 +6,14 @@ package codegen +// LLVM-LABEL: define goabiinternal i64 @codegen.f(i64 %x) +// LLVM: ashr i64 %x, 3 +// LLVM: add i64 {{%.*}}, 5 +// LLVM: ashr i64 {{%.*}}, 3 +// LLVM: add i64 {{%.*}}, 6 +// LLVM-LABEL: define goabiinternal i64 @codegen.g(i64 %x) +// LLVM: ashr i64 %x, 3 + // Make sure we remove both inline marks in the following code. // Both +5 and +6 should map to real instructions, which can // be used as inline marks instead of explicit nops. diff --git a/test/codegen/issue33580.go b/test/codegen/issue33580.go index 1ded944c33abd0..c59202103ec8bc 100644 --- a/test/codegen/issue33580.go +++ b/test/codegen/issue33580.go @@ -9,6 +9,15 @@ package codegen +// LLVM-LABEL: define goabiinternal i64 @codegen.f(i64 %x, i64 %y) +// LLVM-DAG: and i64 %x, 7777777777777777 +// LLVM-DAG: and i64 %y, 7777777777777777 +// LLVM-DAG: and i64 %x, 8888888888888888 +// LLVM-DAG: and i64 %y, 8888888888888888 +// LLVM: mul i64 +// LLVM: mul i64 +// LLVM: mul i64 + const ( A = 7777777777777777 B = 8888888888888888 diff --git a/test/codegen/issue42610.go b/test/codegen/issue42610.go index 41d0e90dbc26f5..b1a569ec0ac965 100644 --- a/test/codegen/issue42610.go +++ b/test/codegen/issue42610.go @@ -9,6 +9,14 @@ package codegen +// LLVM-DAG: define goabiinternal void @codegen.f({ ptr, i64, i64 } %a, i64 %i) +// LLVM-DAG: and i64 %i, 1 +// LLVM-DAG: extractvalue { ptr, i64, i64 } %a, 1 +// LLVM-DAG: getelementptr i64, ptr {{%.*}}, i64 0 +// LLVM-DAG: define goabiinternal void @codegen.f32({ ptr, i64, i64 } %a, i32 %i) +// LLVM-DAG: and i32 %i, 1 +// LLVM-DAG: getelementptr i32, ptr {{%.*}}, i64 0 + func f32(a []int32, i uint32) { g := func(p int32) int32 { i = uint32(p) * (uint32(p) & (i & 1)) diff --git a/test/codegen/issue48054.go b/test/codegen/issue48054.go index 1f3a041044274f..50022c70ecb467 100644 --- a/test/codegen/issue48054.go +++ b/test/codegen/issue48054.go @@ -6,6 +6,13 @@ package codegen +// LLVM-DAG: define goabiinternal i8 @codegen.a3({ ptr, i64, i64 } %n) +// LLVM-DAG: extractvalue { ptr, i64, i64 } %n, 1 +// LLVM-DAG: icmp slt i64 {{%.*}}, 1 +// LLVM-DAG: define goabiinternal i8 @codegen.a({ ptr, i64 } %n) +// LLVM-DAG: extractvalue { ptr, i64 } %n, 1 +// LLVM-DAG: icmp slt i64 0, {{%.*}} + func a(n string) bool { // arm64:"CBZ" if len(n) > 0 { diff --git a/test/codegen/issue52635.go b/test/codegen/issue52635.go index a743db3e7ca69f..affbfe8b18e3c6 100644 --- a/test/codegen/issue52635.go +++ b/test/codegen/issue52635.go @@ -9,6 +9,13 @@ package codegen +// LLVM-DAG: define goabiinternal void @"codegen.(*T).f"(ptr %t) +// LLVM-DAG: load [10 x i64], ptr +// LLVM-DAG: call goabiinternal void @runtime.memclrNoHeapPointers(ptr {{%.*}}, i64 80) +// LLVM-DAG: extractvalue { ptr, i64, i64 } {{%.*}}, 1 +// LLVM-DAG: mul i64 {{%.*}}, 8 +// LLVM-DAG: call goabiinternal void @runtime.memclrNoHeapPointers(ptr {{%.*}}, i64 {{%.*}}) + type T struct { a *[10]int b [10]int diff --git a/test/codegen/issue59297.go b/test/codegen/issue59297.go index 91ea462791f518..1c115fd320e872 100644 --- a/test/codegen/issue59297.go +++ b/test/codegen/issue59297.go @@ -6,6 +6,11 @@ package codegen +// LLVM-LABEL: define goabiinternal void @codegen.f(i64 %x, i64 %y, ptr %p) +// LLVM: call goabiinternal void @codegen.h(i64 8, i64 %x) +// LLVM: load volatile i8, ptr %p, align 1 +// LLVM: store i64 %y, ptr %p, align 4 + func f(x, y int, p *int) { // amd64:`MOVQ AX, BX` h(8, x) diff --git a/test/codegen/issue60673.go b/test/codegen/issue60673.go index b57274319a5ca5..a36a5653342f1a 100644 --- a/test/codegen/issue60673.go +++ b/test/codegen/issue60673.go @@ -6,6 +6,12 @@ package codegen +// LLVM-LABEL: define goabiinternal void @codegen.g(ptr %p) +// LLVM: load volatile i8, ptr %p, align 1 +// LLVM: load i32, ptr %p, align 4 +// LLVM: call goabiinternal void @codegen.f(i32 {{%.*}}) +// LLVM-LABEL: define goabiinternal void @codegen.f(i32 %x) + //go:noinline func f(x int32) { } diff --git a/test/codegen/issue61356.go b/test/codegen/issue61356.go index 65753d5407560c..85565917088a06 100644 --- a/test/codegen/issue61356.go +++ b/test/codegen/issue61356.go @@ -8,6 +8,15 @@ package codegen +// LLVM-LABEL: define goabiinternal i64 @codegen.pack20(ptr %in) +// LLVM: getelementptr i64, ptr %in, i64 0 +// LLVM: shl i64 {{%.*}}, 4 +// LLVM: getelementptr i64, ptr %in, i64 1 +// LLVM: shl i64 {{%.*}}, 7 +// LLVM: or i64 +// LLVM: getelementptr i64, ptr %in, i64 19 +// LLVM: shl i64 {{%.*}}, 61 + func pack20(in *[20]uint64) uint64 { var out uint64 out |= 4 diff --git a/test/codegen/issue63332.go b/test/codegen/issue63332.go index dbe671d247e27b..ee9515ec6bbc50 100644 --- a/test/codegen/issue63332.go +++ b/test/codegen/issue63332.go @@ -6,6 +6,11 @@ package codegen +// LLVM-LABEL: define goabiinternal void @codegen.issue63332(ptr %c) +// LLVM: alloca i64, align 8 +// LLVM: store i64 2, ptr {{%.*}}, align 4 +// LLVM: call goabiinternal void @runtime.chansend1(ptr %c, ptr {{%.*}}) + func issue63332(c chan int) { x := 0 // amd64:-`MOVQ` diff --git a/test/codegen/issue66585.go b/test/codegen/issue66585.go index 867a96218c868f..0ca9c4ef54f228 100644 --- a/test/codegen/issue66585.go +++ b/test/codegen/issue66585.go @@ -6,6 +6,14 @@ package codegen +// LLVM-DAG: define goabiinternal void @codegen.init() +// LLVM-DAG: store i64 1, ptr {{%.*}}, align 4 +// LLVM-DAG: store i64 {{%.*}}, ptr @codegen.x, align 4 +// LLVM-DAG: define goabiinternal void @codegen.f(ptr %p) +// LLVM-DAG: load volatile i8, ptr %p, align 1 +// LLVM-DAG: store i64 1, ptr %p, align 4 +// LLVM-DAG: !{ptr @codegen.x, ptr @"type:int"} + var x = func() int { n := 0 f(&n) diff --git a/test/codegen/issue69635.go b/test/codegen/issue69635.go index 7617a27740070b..bf396d2335bdcf 100644 --- a/test/codegen/issue69635.go +++ b/test/codegen/issue69635.go @@ -6,6 +6,11 @@ package codegen +// LLVM-LABEL: define goabiinternal i64 @codegen.calc(i64 %a) +// LLVM: lshr i64 %a, 20 +// LLVM: and i64 {{%.*}}, 127 +// LLVM: shl i64 {{%.*}}, 3 + func calc(a uint64) uint64 { v := a >> 20 & 0x7f // amd64: `SHRQ \$17, AX$` `ANDL \$1016, AX$` diff --git a/test/codegen/issue72832.go b/test/codegen/issue72832.go index 2af76aa16f7961..dc7f01bffafd89 100644 --- a/test/codegen/issue72832.go +++ b/test/codegen/issue72832.go @@ -6,6 +6,15 @@ package codegen +// LLVM-DAG: define goabiinternal void @codegen.store_shifted(ptr %t, i32 %x) +// LLVM-DAG: lshr i32 %x, 8 +// LLVM-DAG: lshr i32 %x, 16 +// LLVM-DAG: store i16 {{%.*}}, ptr {{%.*}}, align 2 +// LLVM-DAG: define goabiinternal void @codegen.store_const(ptr %t) +// LLVM-DAG: store i8 1, ptr {{%.*}}, align 1 +// LLVM-DAG: store i8 2, ptr {{%.*}}, align 1 +// LLVM-DAG: store i16 3, ptr {{%.*}}, align 2 + type tile1 struct { a uint16 b uint16 diff --git a/test/codegen/issue74485.go b/test/codegen/issue74485.go index b2877c822be871..429e573951ca62 100644 --- a/test/codegen/issue74485.go +++ b/test/codegen/issue74485.go @@ -6,6 +6,15 @@ package codegen +// LLVM-DAG: define goabiinternal i8 @codegen.modUint8(i8 %b) +// LLVM-DAG: urem i8 %b, -128 +// LLVM-DAG: define goabiinternal i64 @codegen.modUint64(i64 %b) +// LLVM-DAG: urem i64 %b, -9223372036854775808 +// LLVM-DAG: define goabiinternal i8 @codegen.divUint8(i8 %b) +// LLVM-DAG: udiv i8 %b, -128 +// LLVM-DAG: define goabiinternal i64 @codegen.divUint64(i64 %b) +// LLVM-DAG: udiv i64 %b, -9223372036854775808 + func divUint64(b uint64) uint64 { // amd64:"SHRQ [$]63, AX" return b / 9223372036854775808 diff --git a/test/codegen/known_bits.go b/test/codegen/known_bits.go index 39b8b528673e71..2def90e19ddd45 100644 --- a/test/codegen/known_bits.go +++ b/test/codegen/known_bits.go @@ -6,6 +6,16 @@ package codegen +// LLVM-DAG: define goabiinternal i64 @codegen.knownBitsXorToggle(i8 %a, i8 %b, i8 %c) +// LLVM-DAG: xor i64 {{%.*}}, 4 +// LLVM-DAG: xor i64 {{%.*}}, 8 +// LLVM-DAG: xor i64 {{%.*}}, 16 +// LLVM-DAG: define goabiinternal i64 @codegen.knownBitsDeferPattern(i8 %a, i8 %b) +// LLVM-DAG: or i64 {{%.*}}, 4 +// LLVM-DAG: and i64 {{%.*}}, 5 +// LLVM-DAG: define goabiinternal i64 @codegen.knownBitsPhiAnd(i8 %cond) +// LLVM-DAG: and i64 {{%.*}}, 1 + func knownBitsPhiAnd(cond bool) int { x := 1 if cond { diff --git a/test/codegen/memops.go b/test/codegen/memops.go index 378521c5f76e72..df0f72835abba4 100644 --- a/test/codegen/memops.go +++ b/test/codegen/memops.go @@ -6,6 +6,17 @@ package codegen +// LLVM-DAG: define goabiinternal i64 @codegen.compMem1() +// LLVM-DAG: load i8, ptr getelementptr (i8, ptr @codegen.x8, i64 1) +// LLVM-DAG: icmp eq i8 {{%.*}}, 7 +// LLVM-DAG: load i64, ptr getelementptr (i64, ptr @codegen.x64, i64 1) +// LLVM-DAG: icmp eq i64 {{%.*}}, 7 +// LLVM-DAG: define goabiinternal void @codegen.idxInt8({ ptr, i64, i64 } %x +// LLVM-DAG: icmp ult i64 +// LLVM-DAG: call goabiinternal void @runtime.goPanicIndex +// LLVM-DAG: define goabiinternal { i64, i8 } @codegen.compMem3(ptr %x, ptr %y) +// LLVM-DAG: icmp slt i64 + var x [2]bool var x8 [2]uint8 var x16 [2]uint16 diff --git a/test/codegen/memops_bigoffset.go b/test/codegen/memops_bigoffset.go index ef9ccae55536cf..55888c16c65e5c 100644 --- a/test/codegen/memops_bigoffset.go +++ b/test/codegen/memops_bigoffset.go @@ -6,6 +6,15 @@ package codegen +// LLVM-DAG: define goabiinternal { i32, i64 } @codegen.loadLargeOffset(ptr %sw, ptr %sd) +// LLVM-DAG: getelementptr i32, ptr {{%.*}}, i64 268435456 +// LLVM-DAG: getelementptr i32, ptr {{%.*}}, i64 536870912 +// LLVM-DAG: getelementptr i64, ptr {{%.*}}, i64 134217728 +// LLVM-DAG: getelementptr i64, ptr {{%.*}}, i64 268435456 +// LLVM-DAG: define goabiinternal void @codegen.storeLargeOffset(ptr %sw, ptr %sd) +// LLVM-DAG: store i32 30, ptr +// LLVM-DAG: store i64 70, ptr + type big1 struct { w [1<<30 - 1]uint32 } diff --git a/test/codegen/regabi_regalloc.go b/test/codegen/regabi_regalloc.go index 2dfbfc356b0939..266f9a74e9a4e1 100644 --- a/test/codegen/regabi_regalloc.go +++ b/test/codegen/regabi_regalloc.go @@ -6,6 +6,12 @@ package codegen +// LLVM-DAG: define goabiinternal void @codegen.f2(i64 %a, i64 %b) +// LLVM-DAG: call goabiinternal void @codegen.g(i64 %b, i64 %b, i64 %b) +// LLVM-DAG: define goabiinternal void @codegen.g(i64 %"~p0", i64 %"~p1", i64 %"~p2") +// LLVM-DAG: define goabiinternal void @codegen.f1(i64 %a, i64 %b) +// LLVM-DAG: call goabiinternal void @codegen.g(i64 1, i64 %a, i64 %b) + //go:registerparams func f1(a, b int) { // amd64:"MOVQ BX, CX" "MOVQ AX, BX" "MOVL [$]1, AX" -"MOVQ .*DX" diff --git a/test/codegen/shift.go b/test/codegen/shift.go index 6d1bb271c6b8d3..bb0308eef58d59 100644 --- a/test/codegen/shift.go +++ b/test/codegen/shift.go @@ -6,6 +6,14 @@ package codegen +// LLVM-DAG: define goabiinternal i64 @codegen.lshConst64x64(i64 %v) +// LLVM-DAG: shl i64 %v, 33 +// LLVM-DAG: define goabiinternal i64 @codegen.rshConst64Ux64(i64 %v) +// LLVM-DAG: lshr i64 %v, 33 +// LLVM-DAG: define goabiinternal i64 @codegen.rshMask64Ux32(i64 %v, i32 %s) +// LLVM-DAG: and i32 %s, 63 +// LLVM-DAG: select i1 {{%.*}}, i64 {{%.*}}, i64 0 + // ------------------ // // constant shifts // // ------------------ // diff --git a/test/codegen/spills.go b/test/codegen/spills.go index bef3cbbbb2f71c..df5df8c68a54ea 100644 --- a/test/codegen/spills.go +++ b/test/codegen/spills.go @@ -6,6 +6,15 @@ package codegen +// LLVM-DAG: define goabiinternal float @codegen.f32(float %a, float %b) +// LLVM-DAG: fadd float %a, %b +// LLVM-DAG: define goabiinternal double @codegen.f64(double %a, double %b) +// LLVM-DAG: fadd double %a, %b +// LLVM-DAG: define goabiinternal i32 @codegen.i32(i32 %a, i32 %b) +// LLVM-DAG: add i32 %a, %b +// LLVM-DAG: define goabiinternal i64 @codegen.i64(i64 %a, i64 %b) +// LLVM-DAG: add i64 %a, %b + func i64(a, b int64) int64 { // arm64:`STP ` `LDP ` g() return a + b diff --git a/test/llvm_tests.json b/test/llvm_tests.json index d7c1f1cf35e736..7e7508bdc5fc1a 100644 --- a/test/llvm_tests.json +++ b/test/llvm_tests.json @@ -3,20 +3,44 @@ "whitelist": { "codegen/addrcalc.go": "bounds-checked nested array address calculation", "codegen/alloc.go": "zero-sized allocations and slice literals through runtime.zerobase", + "codegen/arithmetic.go": "integer arithmetic, constant folding, division, remainder, and bounds-checked memory updates", + "codegen/bitfield.go": "bitfield extraction, insertion, masking, shifts, and multi-result tuple construction", + "codegen/compare_and_branch.go": "signed and unsigned compare-and-branch control flow including channel receives", "codegen/divmod.go": "integer division, remainder, overflow handling, and tuple ABI", + "codegen/interface.go": "static multi-method itabs, interface conversions and indirect method calls", + "codegen/interface_assertion.go": "comma-ok and panic-form concrete and non-empty interface assertions", + "codegen/interface_conversion.go": "dynamic interface-to-interface conversion cache and runtime fallback", + "codegen/interface_equality.go": "empty and non-empty interface equality through runtime comparators", + "codegen/interface_switch.go": "concrete and multiple interface type-switch cases with variable static cache descriptor", + "codegen/issue22703.go": "large interface method descriptor table and small versus large itab method offsets", + "codegen/issue25378.go": "zero extension of byte and uint16 array arguments for table indexing", + "codegen/issue31618.go": "signed shifts and additions across inlining positions", + "codegen/issue33580.go": "large integer masks and chained multiplication", + "codegen/issue42610.go": "masked indexes across 32-bit and word-sized slice accesses", + "codegen/issue48054.go": "string and slice length comparisons lowered to integer predicates", + "codegen/issue52635.go": "array and slice range clearing through typed loads and memclr calls", + "codegen/issue59297.go": "direct call argument placement followed by nil-checked pointer store", + "codegen/issue60673.go": "nil-checked scalar load passed through the internal ABI", + "codegen/issue61356.go": "wide packing from pointer-indexed loads, shifts, and bitwise composition", + "codegen/issue63332.go": "channel send with a materialized scalar argument", + "codegen/issue66585.go": "dynamic package initialization and typed global metadata", + "codegen/issue69635.go": "combined unsigned shifts and mask extraction", + "codegen/issue72832.go": "typed narrow stores and shifted field extraction", + "codegen/issue74485.go": "unsigned division and remainder at integer sign-bit constants", + "codegen/known_bits.go": "phi-carried known-bit masking and boolean control flow", "codegen/logic.go": "integer logic and conditional control flow", + "codegen/memops.go": "typed indexed loads, stores, comparisons, bounds checks, and scalar memory operations", + "codegen/memops_bigoffset.go": "typed loads and stores at offsets beyond signed 32-bit byte displacement", "codegen/multiply.go": "integer multiplication across positive, negative, and zero constants", + "codegen/regabi_regalloc.go": "register ABI argument permutations in direct calls", + "codegen/shift.go": "constant, masked, guarded, widened, and combined integer shifts", "codegen/shortcircuit.go": "empty-interface extraction and concrete type comparison", "codegen/smallintiface.go": "small scalar boxing through runtime.staticuint64s", + "codegen/spills.go": "integer and floating-point values preserved across calls", "codegen/structs.go": "typed struct initialization and zeroing", "codegen/type_descriptor.go": "compiler-owned runtime type descriptor data and GoObj metadata", "codegen/type_descriptor_kinds.go": "all runtime type descriptor layouts and variable descriptor data", - "codegen/type_descriptor_methods.go": "uncommon type data, concrete method tables, wrappers, and R_METHODOFF", - "codegen/interface.go": "static multi-method itabs, interface conversions and indirect method calls", - "codegen/interface_conversion.go": "dynamic interface-to-interface conversion cache and runtime fallback", - "codegen/interface_assertion.go": "comma-ok and panic-form concrete and non-empty interface assertions", - "codegen/interface_equality.go": "empty and non-empty interface equality through runtime comparators", - "codegen/interface_switch.go": "concrete and multiple interface type-switch cases with variable static cache descriptor" + "codegen/type_descriptor_methods.go": "uncommon type data, concrete method tables, wrappers, and R_METHODOFF" }, "blacklist": { "codegen/*": "LLVM lowering support has not reached this complete source file yet" From bc8ad586e7aa3cae6a7abd56f6f482e46f445608 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 28 Jul 2026 11:48:33 +0800 Subject: [PATCH 16/17] cmd: update go-llvm vendor for global metadata --- src/cmd/go.mod | 2 +- src/cmd/go.sum | 4 +- .../github.com/goallc/go-llvm/.gitignore | 4 ++ .../github.com/goallc/go-llvm/README.markdown | 51 +++++++++++++++++++ src/cmd/vendor/modules.txt | 2 +- 5 files changed, 59 insertions(+), 4 deletions(-) create mode 100644 src/cmd/vendor/github.com/goallc/go-llvm/.gitignore create mode 100644 src/cmd/vendor/github.com/goallc/go-llvm/README.markdown diff --git a/src/cmd/go.mod b/src/cmd/go.mod index 6d15c9f6e78853..2893846e18a1de 100644 --- a/src/cmd/go.mod +++ b/src/cmd/go.mod @@ -3,7 +3,7 @@ module cmd go 1.27 require ( - github.com/goallc/go-llvm v0.0.0-20260727072003-33267b8eb4a0 + github.com/goallc/go-llvm v0.0.0-20260728032034-006181a193aa github.com/google/pprof v0.0.0-20260507013755-92041b743c96 golang.org/x/arch v0.27.1-0.20260521044007-9c1a596a2c97 golang.org/x/build v0.0.0-20260522210304-d55d0041b921 diff --git a/src/cmd/go.sum b/src/cmd/go.sum index 075f1eee83576a..1b6436eb737cd1 100644 --- a/src/cmd/go.sum +++ b/src/cmd/go.sum @@ -1,5 +1,5 @@ -github.com/goallc/go-llvm v0.0.0-20260727072003-33267b8eb4a0 h1:yaqOKHghW9oGMKtbUYHHyd2UyAWHGYZjOiHqCiDJ8tE= -github.com/goallc/go-llvm v0.0.0-20260727072003-33267b8eb4a0/go.mod h1:s70tU8u5zDo1LwIPqBxh6Qgol/tNt1kmn9caPfsTz8A= +github.com/goallc/go-llvm v0.0.0-20260728032034-006181a193aa h1:6RuEE6LD/tbgCuFTpeCqzu9FSD2y0L8ch1zkhZD25V8= +github.com/goallc/go-llvm v0.0.0-20260728032034-006181a193aa/go.mod h1:s70tU8u5zDo1LwIPqBxh6Qgol/tNt1kmn9caPfsTz8A= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/pprof v0.0.0-20260507013755-92041b743c96 h1:YDDnaZ9afWajDboPMt9Vikqca/yWAX7KAxVzb4lJU1M= diff --git a/src/cmd/vendor/github.com/goallc/go-llvm/.gitignore b/src/cmd/vendor/github.com/goallc/go-llvm/.gitignore new file mode 100644 index 00000000000000..e5b97b8d19f075 --- /dev/null +++ b/src/cmd/vendor/github.com/goallc/go-llvm/.gitignore @@ -0,0 +1,4 @@ +llvm-*.src.tar.xz +llvm-*/ +/llvm +/libLLVMGoALLC.a* diff --git a/src/cmd/vendor/github.com/goallc/go-llvm/README.markdown b/src/cmd/vendor/github.com/goallc/go-llvm/README.markdown new file mode 100644 index 00000000000000..fd90b5edc7ad16 --- /dev/null +++ b/src/cmd/vendor/github.com/goallc/go-llvm/README.markdown @@ -0,0 +1,51 @@ +# Go bindings for GoALLC LLVM + +This repository contains the LLVM Go bindings used by GoALLC. It intentionally +targets the customized LLVM build maintained by the GoALLC project; a +system-installed LLVM is not supported. + +## LLVM payload + +The binding always reads LLVM headers below `${SRCDIR}/llvm`; dynamic linking +also reads the LLVM shared library there: + + llvm/include/llvm-c + llvm/lib + +`llvm` is not committed. The caller must create it as a symlink to an LLVM +payload with this layout. GoALLC's `cmd/dist` manages that symlink from its +`-llvm-dir` option, whose default is `$GOROOT/llvm`. + +## Build tags + +The LLVM API version and link mode are independent, mandatory build-tag axes: + +* `llvm23` selects the LLVM 23 API. A future LLVM 24 port will add `llvm24` + without changing how the payload path or link mode is selected. +* `dynamicllvm` links `llvm/lib/libLLVM`; it is the GoALLC default. +* `staticllvm` links `${SRCDIR}/libLLVMGoALLC.a`. GoALLC's `cmd/dist` + assembles and caches this build artifact from the selected payload's normal + LLVM component archives with `llvm-config` and `llvm-ar`. + +For example: + + go test -tags='llvm23 dynamicllvm' ./... + go test -tags='llvm23 staticllvm' ./... + +The static command requires `libLLVMGoALLC.a` to have been assembled first. +The GoALLC toolchain does this automatically for `-llvm-link=static`. + +Do not select multiple version tags or multiple link-mode tags in one build. + +## Continuous integration + +Automatic pull-request checks currently validate the binding source only. Full +dynamic and static tests require the customized GoALLC LLVM payload and are +validated as part of integration work. Once the project publishes prebuilt LLVM +payloads, CI will download those artifacts and run both link modes without +rebuilding LLVM from source. + +## License + +These bindings originated in LLVM and remain licensed under the Apache License +2.0 with LLVM Exceptions. See `LICENSE.txt`. diff --git a/src/cmd/vendor/modules.txt b/src/cmd/vendor/modules.txt index 6542e1b5e69e12..f3dc49aab1f4ad 100644 --- a/src/cmd/vendor/modules.txt +++ b/src/cmd/vendor/modules.txt @@ -1,4 +1,4 @@ -# github.com/goallc/go-llvm v0.0.0-20260727072003-33267b8eb4a0 +# github.com/goallc/go-llvm v0.0.0-20260728032034-006181a193aa ## explicit; go 1.24 github.com/goallc/go-llvm # github.com/google/pprof v0.0.0-20260507013755-92041b743c96 From b6f6941c39ec01652ecd9d07cf057ef8ae140ff6 Mon Sep 17 00:00:00 2001 From: ZhouGuangyuan Date: Tue, 28 Jul 2026 12:17:48 +0800 Subject: [PATCH 17/17] cmd/compile: simplify LLVM descriptor wrapper types --- doc/goallc-llvm-goobj.md | 6 ++-- src/cmd/compile/internal/ssa/llvmtypeddata.go | 36 +++++-------------- test/codegen/interface.go | 3 +- test/codegen/issue22703.go | 3 +- test/codegen/type_descriptor.go | 5 ++- test/codegen/type_descriptor_kinds.go | 30 ++++++++-------- test/codegen/type_descriptor_methods.go | 6 ++-- 7 files changed, 33 insertions(+), 56 deletions(-) diff --git a/doc/goallc-llvm-goobj.md b/doc/goallc-llvm-goobj.md index 455acc824e66cf..ead7b91cb9de44 100644 --- a/doc/goallc-llvm-goobj.md +++ b/doc/goallc-llvm-goobj.md @@ -47,8 +47,10 @@ schema 的辅助数据仍以 bytes/relocations 表示。这个边界刻意位于 `runtime.memequal64`)。 LLVM IR 优先使用原生 linkage 和语义类型交接这些属性:Local 对应 -`internal`,非 Local 的 DUPOK 对应 `weak`,`%go.descriptor.*` 和 -`%go.itab.*` 分别标识 Go type 与 itab。`!goobj.symbol.flags` 只保留 typelink、 +`internal`,非 Local 的 DUPOK 对应 `weak`。Go type descriptor 与 itab 的 +外层使用匿名 packed struct,内部字段继续使用 `%go.runtime.*` ABI 类型;descriptor +和 itab 的身份由真实的 `type:*` / `go:itab.*` global symbol 表达,避免重复生成 +既长又不提供额外 LLVM 语义的 identified wrapper type。`!goobj.symbol.flags` 只保留 typelink、 UsedInIface、linkname 以及 Local+DUPOK 重叠等没有等价 LLVM 表示的位。 普通 address、type offset 和 method offset 都由 LLVM initializer 与语义类型 直接推导;`!goobj.weak_relocs` 只记录无法由 LLVM 表达的逐 relocation weak diff --git a/src/cmd/compile/internal/ssa/llvmtypeddata.go b/src/cmd/compile/internal/ssa/llvmtypeddata.go index fb04a7194cc66f..b83859ebdeb045 100644 --- a/src/cmd/compile/internal/ssa/llvmtypeddata.go +++ b/src/cmd/compile/internal/ssa/llvmtypeddata.go @@ -9,7 +9,6 @@ import ( "cmd/internal/obj" "cmd/internal/objabi" "sort" - "strings" "github.com/goallc/go-llvm" ) @@ -120,24 +119,19 @@ func (l *llvmDataLowerer) itabType(s *obj.LSym) llvm.Type { if result, ok := l.descriptorTypes[s]; ok { return result } - name := "go.itab." + llvmSafeTypeName(strings.TrimPrefix(s.Name, "go:itab.")) - result := CurrentModule.GetTypeByName(name) - if result.IsNil() { - result = GlobalCtxt.StructCreateNamed(name) - } - l.descriptorTypes[s] = result fields := l.descriptorFields(s, llvmItabParts(s), nil) fieldTypes := make([]llvm.Type, len(fields)) for i, field := range fields { fieldTypes[i] = field.Type() } - result.StructSetBody(fieldTypes, true) + result := GlobalCtxt.StructType(fieldTypes, true) + l.descriptorTypes[s] = result return result } func (l *llvmDataLowerer) itabInitializer(s *obj.LSym, globals map[*obj.LSym]llvm.Value) llvm.Value { - typ := l.itabType(s) - return llvm.ConstNamedStruct(typ, l.descriptorFields(s, llvmItabParts(s), globals)) + _ = l.itabType(s) + return GlobalCtxt.ConstStruct(l.descriptorFields(s, llvmItabParts(s), globals), true) } // descriptorParts mirrors reflectdata.writeType. It uses the runtime ABI @@ -230,25 +224,20 @@ func (l *llvmDataLowerer) descriptorType(s *obj.LSym, t *types.Type) llvm.Type { if result, ok := l.descriptorTypes[s]; ok { return result } - name := "go.descriptor." + llvmSafeTypeName(strings.TrimPrefix(s.Name, "type:")) - result := CurrentModule.GetTypeByName(name) - if result.IsNil() { - result = GlobalCtxt.StructCreateNamed(name) - } - l.descriptorTypes[s] = result parts := descriptorParts(t, llvmDataSize(s)) fields := l.descriptorFields(s, parts, nil) types := make([]llvm.Type, len(fields)) for i, f := range fields { types[i] = f.Type() } - result.StructSetBody(types, true) + result := GlobalCtxt.StructType(types, true) + l.descriptorTypes[s] = result return result } func (l *llvmDataLowerer) descriptorInitializer(s *obj.LSym, t *types.Type, globals map[*obj.LSym]llvm.Value) llvm.Value { - typ := l.descriptorType(s, t) - return llvm.ConstNamedStruct(typ, l.descriptorFields(s, descriptorParts(t, llvmDataSize(s)), globals)) + _ = l.descriptorType(s, t) + return GlobalCtxt.ConstStruct(l.descriptorFields(s, descriptorParts(t, llvmDataSize(s)), globals), true) } func (l *llvmDataLowerer) descriptorFields(s *obj.LSym, parts []llvmDescriptorPart, globals map[*obj.LSym]llvm.Value) []llvm.Value { @@ -586,12 +575,3 @@ func llvmSameStructLayout(a, b *types.Type) bool { } return true } - -func llvmSafeTypeName(name string) string { - return strings.Map(func(r rune) rune { - if r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || r == '.' || r == '_' { - return r - } - return '_' - }, name) -} diff --git a/test/codegen/interface.go b/test/codegen/interface.go index c94c8eb1f85e25..7290f36fd5386d 100644 --- a/test/codegen/interface.go +++ b/test/codegen/interface.go @@ -7,8 +7,7 @@ package codegen // LLVM-DAG: %go.runtime.ITab = type <{ ptr, ptr, i32, [4 x i8], [1 x ptr] }> -// LLVM-DAG: %go.itab.codegen.llvmInterfaceValue_codegen.llvmInterface = type <{ %go.runtime.ITab, [1 x ptr] }> -// LLVM-DAG: @"go:itab.codegen.llvmInterfaceValue,codegen.llvmInterface" = weak constant %go.itab.codegen.llvmInterfaceValue_codegen.llvmInterface +// LLVM-DAG: @"go:itab.codegen.llvmInterfaceValue,codegen.llvmInterface" = weak constant <{ %go.runtime.ITab, [1 x ptr] }> // LLVM-DAG: ptr @"codegen.(*llvmInterfaceValue).Double" // LLVM-DAG: ptr @"codegen.(*llvmInterfaceValue).Value" // LLVM-DAG: !goobj.weak_relocs diff --git a/test/codegen/issue22703.go b/test/codegen/issue22703.go index 4a86d0607a135e..0fdc593331fc5a 100644 --- a/test/codegen/issue22703.go +++ b/test/codegen/issue22703.go @@ -6,8 +6,7 @@ package codegen -// LLVM-DAG: @"type:codegen.I" = constant %go.descriptor.codegen.I -// LLVM-DAG: [512 x %go.runtime.Imethod] +// LLVM-DAG: @"type:codegen.I" = constant <{ %go.runtime.InterfaceType, %go.runtime.UncommonType, [512 x %go.runtime.Imethod] }> // LLVM-DAG: define goabiinternal void @codegen.callMethodLargeOffset({ ptr, ptr } %i) // LLVM-DAG: getelementptr i8, ptr {{%.*}}, i64 4112 // LLVM-DAG: define goabiinternal void @codegen.callMethodSmallOffset({ ptr, ptr } %i) diff --git a/test/codegen/type_descriptor.go b/test/codegen/type_descriptor.go index 02c95a3e460231..d89c8e38499f58 100644 --- a/test/codegen/type_descriptor.go +++ b/test/codegen/type_descriptor.go @@ -9,9 +9,8 @@ package codegen // LLVM-DAG: %go.runtime.StructType = type <{ // LLVM-DAG: %go.runtime.UncommonType = type <{ // LLVM-DAG: %go.runtime.StructField = type <{ -// LLVM-DAG: %go.descriptor.codegen.llvmTypeDescriptor = type <{ -// LLVM-DAG: @"type:codegen.llvmTypeDescriptor" = constant %go.descriptor.codegen.llvmTypeDescriptor -// LLVM-DAG: @"type:*codegen.llvmTypeDescriptor" = constant %go.descriptor._codegen.llvmTypeDescriptor{{.*}}!goobj.symbol.flags +// LLVM-DAG: @"type:codegen.llvmTypeDescriptor" = constant <{ %go.runtime.StructType, %go.runtime.UncommonType, [2 x %go.runtime.StructField] }> +// LLVM-DAG: @"type:*codegen.llvmTypeDescriptor" = constant <{ %go.runtime.PtrType }>{{.*}}!goobj.symbol.flags // LLVM-NOT: !goobj.relocs // llvmTypeDescriptor exercises the compiler-owned reflectdata path. LLVM data diff --git a/test/codegen/type_descriptor_kinds.go b/test/codegen/type_descriptor_kinds.go index 849f0bc5fef0c4..e7cd96db21f262 100644 --- a/test/codegen/type_descriptor_kinds.go +++ b/test/codegen/type_descriptor_kinds.go @@ -18,21 +18,21 @@ import "unsafe" // LLVM-DAG: %go.runtime.SliceType = type <{ // LLVM-DAG: %go.runtime.StructType = type <{ // LLVM-DAG: %go.runtime.Imethod = type <{ -// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorBool = type <{ %go.runtime.Type, %go.runtime.UncommonType }> -// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorInt8 = type <{ %go.runtime.Type, %go.runtime.UncommonType }> -// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorUintptr = type <{ %go.runtime.Type, %go.runtime.UncommonType }> -// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorFloat32 = type <{ %go.runtime.Type, %go.runtime.UncommonType }> -// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorComplex128 = type <{ %go.runtime.Type, %go.runtime.UncommonType }> -// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorString = type <{ %go.runtime.Type, %go.runtime.UncommonType }> -// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorUnsafePointer = type <{ %go.runtime.Type, %go.runtime.UncommonType }> -// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorArray = type <{ %go.runtime.ArrayType, %go.runtime.UncommonType }> -// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorChan = type <{ %go.runtime.ChanType, %go.runtime.UncommonType }> -// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorFunc = type <{ %go.runtime.FuncType, %go.runtime.UncommonType, [4 x ptr] }> -// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorInterface = type <{ %go.runtime.InterfaceType, %go.runtime.UncommonType, [0 x %go.runtime.Imethod] }> -// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorMap = type <{ %go.runtime.MapType, %go.runtime.UncommonType }> -// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorPtr = type <{ %go.runtime.PtrType, %go.runtime.UncommonType }> -// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorSlice = type <{ %go.runtime.SliceType, %go.runtime.UncommonType }> -// LLVM-DAG: %go.descriptor.codegen.llvmDescriptorStruct = type <{ %go.runtime.StructType, %go.runtime.UncommonType, [2 x %go.runtime.StructField] }> +// LLVM-DAG: @"type:codegen.llvmDescriptorBool" = constant <{ %go.runtime.Type, %go.runtime.UncommonType }> +// LLVM-DAG: @"type:codegen.llvmDescriptorInt8" = constant <{ %go.runtime.Type, %go.runtime.UncommonType }> +// LLVM-DAG: @"type:codegen.llvmDescriptorUintptr" = constant <{ %go.runtime.Type, %go.runtime.UncommonType }> +// LLVM-DAG: @"type:codegen.llvmDescriptorFloat32" = constant <{ %go.runtime.Type, %go.runtime.UncommonType }> +// LLVM-DAG: @"type:codegen.llvmDescriptorComplex128" = constant <{ %go.runtime.Type, %go.runtime.UncommonType }> +// LLVM-DAG: @"type:codegen.llvmDescriptorString" = constant <{ %go.runtime.Type, %go.runtime.UncommonType }> +// LLVM-DAG: @"type:codegen.llvmDescriptorUnsafePointer" = constant <{ %go.runtime.Type, %go.runtime.UncommonType }> +// LLVM-DAG: @"type:codegen.llvmDescriptorArray" = constant <{ %go.runtime.ArrayType, %go.runtime.UncommonType }> +// LLVM-DAG: @"type:codegen.llvmDescriptorChan" = constant <{ %go.runtime.ChanType, %go.runtime.UncommonType }> +// LLVM-DAG: @"type:codegen.llvmDescriptorFunc" = constant <{ %go.runtime.FuncType, %go.runtime.UncommonType, [4 x ptr] }> +// LLVM-DAG: @"type:codegen.llvmDescriptorInterface" = constant <{ %go.runtime.InterfaceType, %go.runtime.UncommonType, [0 x %go.runtime.Imethod] }> +// LLVM-DAG: @"type:codegen.llvmDescriptorMap" = constant <{ %go.runtime.MapType, %go.runtime.UncommonType }> +// LLVM-DAG: @"type:codegen.llvmDescriptorPtr" = constant <{ %go.runtime.PtrType, %go.runtime.UncommonType }> +// LLVM-DAG: @"type:codegen.llvmDescriptorSlice" = constant <{ %go.runtime.SliceType, %go.runtime.UncommonType }> +// LLVM-DAG: @"type:codegen.llvmDescriptorStruct" = constant <{ %go.runtime.StructType, %go.runtime.UncommonType, [2 x %go.runtime.StructField] }> type llvmDescriptorBool bool type llvmDescriptorInt8 int8 diff --git a/test/codegen/type_descriptor_methods.go b/test/codegen/type_descriptor_methods.go index 1f21261f8112d4..f8c4fe85e9dfb4 100644 --- a/test/codegen/type_descriptor_methods.go +++ b/test/codegen/type_descriptor_methods.go @@ -7,10 +7,8 @@ package codegen // LLVM-DAG: %go.runtime.Method = type <{ i32, i32, i32, i32 }> -// LLVM-DAG: %go.descriptor.codegen.llvmMethodType = type <{ %go.runtime.StructType, %go.runtime.UncommonType, [1 x %go.runtime.StructField], [2 x %go.runtime.Method] }> -// LLVM-DAG: %go.descriptor._codegen.llvmMethodType = type <{ %go.runtime.PtrType, %go.runtime.UncommonType, [3 x %go.runtime.Method] }> -// LLVM-DAG: @"type:codegen.llvmMethodType" = constant %go.descriptor.codegen.llvmMethodType -// LLVM-DAG: @"type:*codegen.llvmMethodType" = constant %go.descriptor._codegen.llvmMethodType +// LLVM-DAG: @"type:codegen.llvmMethodType" = constant <{ %go.runtime.StructType, %go.runtime.UncommonType, [1 x %go.runtime.StructField], [2 x %go.runtime.Method] }> +// LLVM-DAG: @"type:*codegen.llvmMethodType" = constant <{ %go.runtime.PtrType, %go.runtime.UncommonType, [3 x %go.runtime.Method] }> // LLVM-DAG: i32 ptrtoint (ptr @"codegen.(*llvmMethodType).Pointer" to i32) // LLVM-DAG: i32 ptrtoint (ptr @"codegen.(*llvmMethodType).Value" to i32) // LLVM-NOT: !goobj.relocs