diff --git a/doc/goallc-llvm-goobj.md b/doc/goallc-llvm-goobj.md index 38d91165c38ed1..ead7b91cb9de44 100644 --- a/doc/goallc-llvm-goobj.md +++ b/doc/goallc-llvm-goobj.md @@ -25,6 +25,90 @@ 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 优先使用原生 linkage 和语义类型交接这些属性:Local 对应 +`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 +属性。零宽度的 linker +保活边 `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。 + +静态 interface conversion 还会把带有 `ItabInfo` 的 roots 纳入同一数据闭包。 +itab 使用 `%go.runtime.ITab` 和按实际方法数扩展的 packed LLVM struct 表达; +固定的 `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 还依赖函数上的零宽度 +`R_USEIFACE`、`R_USEIFACEMETHOD` 和 `R_USENAMEDMETHOD`。它们不对应 LLVM +机器指令或地址常量,因此 compiler 用 `!goobj.marker_relocs` 保存精确的 +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 测试覆盖。empty interface 到 concrete type 的 comma-ok assertion +直接比较动态 type word 并按 SSA 结果形状取回 data;empty interface 到 +non-empty interface 的 comma-ok assertion 复用同一 TypeAssert cache/fallback, +并覆盖成功、类型不匹配和 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;其尾部 +interface type 指针数组按实际 case 数扩展,并由多 interface case 测试覆盖。 +两个非 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 +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` 产物泛化为 +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 @@ -194,11 +278,18 @@ 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、完整 ABI/DWARF 等尚未达到通用正确性。 +- 当前 interface 范围包括 compiler 能静态生成 itab 的 concrete-to-interface + conversion、对应的 ABIInternal 间接方法调用,以及 non-empty + interface-to-interface conversion;empty-interface 到 concrete/non-empty + interface 的 comma-ok/panic-form assertion,以及包含 concrete/interface cases + 的 type switch 也已覆盖。 - 每个经 LLVM 替换的 package 都必须由 `llc` 生成完整的 `_go_.o`,不能与 原生 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..2aabbee2a8498c --- /dev/null +++ b/src/cmd/compile/internal/ssa/llvmdata.go @@ -0,0 +1,461 @@ +//go:build !compiler_bootstrap + +package ssa + +import ( + "cmd/compile/internal/base" + "cmd/internal/obj" + "cmd/internal/objabi" + "sort" + + "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 + visit(s.Gotype) + for _, r := range s.R { + visit(r.Sym) + } + } + for _, s := range base.Ctxt.Data { + if s.TypeInfo() != nil || s.ItabInfo() != nil { + visit(s) + } + } + if currentLLVMDataLowerer != nil { + for s := range currentLLVMDataLowerer.roots { + visit(s) + } + } + + if len(closure) == 0 { + emitGoObjCompilerUsed() + 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 := 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 { + 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)) + setLLVMDataLinkage(g, 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) + setGoObjWeakRelocMetadata(g, s) + setGoObjKeepMetadata(g, s) + setGoObjGotypeMetadata(g, s) + } + emitGoObjCompilerUsed() +} + +// 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() +} + +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 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 + // 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.MakeTypelink() { + flag |= 1 << 2 // goobj.SymFlagTypelink + } + if s.UsedInIface() { + flag2 |= 1 << 0 // goobj.SymFlagUsedInIface + } + 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 setGoObjWeakRelocMetadata(g llvm.Value, s *obj.LSym) { + entries := make([]llvm.Metadata, 0) + for _, r := range s.R { + switch r.Type { + 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: + base.Fatalf("unsupported Go data relocation %s in %s", r.Type, s.Name) + } + } + if len(entries) != 0 { + 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.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) { + 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) + } + target := llvmGoDataRef(r.Sym) + preserveGoObjMetadataValues(g, target) + CurrentModule.AddNamedMetadataOperand("goobj.keep", GlobalCtxt.MDNode([]llvm.Metadata{ + g.ConstantAsMetadata(), + target.ConstantAsMetadata(), + })) + } +} + +func setGoObjGotypeMetadata(g llvm.Value, s *obj.LSym) { + if s.Gotype == nil { + return + } + target := CurrentModule.NamedGlobal(s.Gotype.Name) + if target.IsNil() { + target = llvmGoDataRef(s.Gotype) + } + 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 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 { + 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) + } + 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(), + })) + } + } + 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 { + 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..b83859ebdeb045 --- /dev/null +++ b/src/cmd/compile/internal/ssa/llvmtypeddata.go @@ -0,0 +1,577 @@ +//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" + + "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 + roots 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, + 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), + } +} + +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) +} + +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) + } + if s.ItabInfo() != nil { + return l.itabInitializer(s, 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 + 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.TUNSAFEPTR], + arrayCount: tail, + }) + } + return parts +} + +func (l *llvmDataLowerer) itabType(s *obj.LSym) llvm.Type { + if result, ok := l.descriptorTypes[s]; ok { + return result + } + fields := l.descriptorFields(s, llvmItabParts(s), nil) + fieldTypes := make([]llvm.Type, len(fields)) + for i, field := range fields { + fieldTypes[i] = field.Type() + } + 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 { + _ = l.itabType(s) + return GlobalCtxt.ConstStruct(l.descriptorFields(s, llvmItabParts(s), globals), true) +} + +// 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 variableElem *types.Type + var variableCount int64 + 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 + variableElem = types.Types[types.TUNSAFEPTR] + variableCount = int64(t.NumRecvs() + t.NumParams() + t.NumResults()) + case types.TINTER: + rt = rttype.InterfaceType + 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 + variableElem = rttype.StructField + variableCount = int64(t.NumFields()) + } + + parts := []llvmDescriptorPart{{off: 0, typ: rt}} + off := rt.Size() + variableSize := int64(0) + if variableElem != nil { + variableSize = variableElem.Size() * variableCount + } + 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 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 { + 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) + } + return parts +} + +func (l *llvmDataLowerer) descriptorType(s *obj.LSym, t *types.Type) llvm.Type { + if result, ok := l.descriptorTypes[s]; ok { + return 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 := 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 { + _ = 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 { + fields := make([]llvm.Value, 0, len(parts)*2+1) + pos := int64(0) + for _, part := range parts { + 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.partValue(s, part, globals)) + pos = part.off + part.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))) + } + 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() { + 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 { + var value llvm.Value + if globals == nil { + value = llvmDataRelocZero(r) + } else { + value = llvmDataRelocValue(s, r, globals, l.data) + } + return llvmCoerceDataReloc(value, l.goType(t), s, off) + } + } + + 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)...) + 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)...) + 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 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 + } + 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) { + 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" + case rttype.ITab: + return "go.runtime.ITab" + 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 +} diff --git a/src/cmd/compile/internal/ssa/ssa2llvm.go b/src/cmd/compile/internal/ssa/ssa2llvm.go index 61fb0ffe0dc7d8..4ddaae9700e340 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,8 @@ import ( 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 @@ -22,6 +25,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. @@ -112,6 +124,34 @@ 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)< +// 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 +// LLVM-DAG: @llvm.compiler.used = appending global +// LLVM-LABEL: define goabiinternal i64 @codegen.useLLVMInterface( +// LLVM-LABEL: define goabiinternal i64 @codegen.llvmInterface.Value( +// LLVM: load ptr, ptr +// LLVM: call goabiinternal i64 % +// LLVM-SAME: (ptr +// LLVM-SAME: i64 +// LLVM-LABEL: define goabiinternal i64 @codegen.llvmInterface.Double( +// LLVM: load ptr, ptr +// LLVM: call goabiinternal i64 % +// LLVM-SAME: (ptr +// 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 + 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/codegen/interface_assertion.go b/test/codegen/interface_assertion.go new file mode 100644 index 00000000000000..125ebdabcc3250 --- /dev/null +++ b/test/codegen/interface_assertion.go @@ -0,0 +1,53 @@ +// 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 = 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 +// 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 +} + +// LLVM-DAG: call goabiinternal void @runtime.panicdottypeE( +// LLVM-DAG: call goabiinternal void @runtime.panicnildottype( +// LLVM-DAG: declare goabiinternal void @runtime.panicdottypeE(ptr, ptr, ptr) +// LLVM-DAG: declare goabiinternal void @runtime.panicnildottype(ptr) +func mustLLVMConcrete(v any) llvmAssertionValue { + return v.(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 new file mode 100644 index 00000000000000..49cb53fa8c6da8 --- /dev/null +++ b/test/codegen/interface_conversion.go @@ -0,0 +1,29 @@ +// 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 = 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 + Value(int) int +} + +type llvmTargetInterface interface { + Value(int) int +} + +func convertLLVMInterface(v llvmSourceInterface) llvmTargetInterface { + return v +} 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/codegen/interface_switch.go b/test/codegen/interface_switch.go new file mode 100644 index 00000000000000..365db21d07d3eb --- /dev/null +++ b/test/codegen/interface_switch.go @@ -0,0 +1,54 @@ +// 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 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 +} + +// 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) +// LLVM-DAG: icmp eq ptr +// 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: + return 0 + case llvmSwitchValue: + return int(x) + case string: + return len(x) + case llvmSwitchInterface: + return x.Value(1) + case llvmDoubleSwitchInterface: + return x.Double() + default: + return -1 + } +} diff --git a/test/codegen/issue22703.go b/test/codegen/issue22703.go index 0201de6530cc67..0fdc593331fc5a 100644 --- a/test/codegen/issue22703.go +++ b/test/codegen/issue22703.go @@ -6,6 +6,13 @@ package codegen +// 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) +// 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/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/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/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/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/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/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/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.go b/test/codegen/type_descriptor.go new file mode 100644 index 00000000000000..d89c8e38499f58 --- /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: @"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 +// 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..e7cd96db21f262 --- /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: @"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 +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/codegen/type_descriptor_methods.go b/test/codegen/type_descriptor_methods.go new file mode 100644 index 00000000000000..f8c4fe85e9dfb4 --- /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: @"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 + +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_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_interface_assertion.go b/test/llvm_interface_assertion.go new file mode 100644 index 00000000000000..f1e916a25027fe --- /dev/null +++ b/test/llvm_interface_assertion.go @@ -0,0 +1,54 @@ +// 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 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) + 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) + + println(int(mustLLVMConcrete(v))) + println(mustLLVMInterface(v).Value(4)) +} diff --git a/test/llvm_interface_assertion.out b/test/llvm_interface_assertion.out new file mode 100644 index 00000000000000..07b742a7b5686f --- /dev/null +++ b/test/llvm_interface_assertion.out @@ -0,0 +1,6 @@ +10 true +13 true +false +true false +10 +14 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_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_interface_switch.go b/test/llvm_interface_switch.go new file mode 100644 index 00000000000000..a5b801239178b5 --- /dev/null +++ b/test/llvm_interface_switch.go @@ -0,0 +1,52 @@ +// 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 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: + return 0 + case llvmSwitchValue: + return int(x) + case string: + return len(x) + case llvmSwitchInterface: + return x.Value(1) + case llvmDoubleSwitchInterface: + return x.Double() + default: + return -1 + } +} + +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 new file mode 100644 index 00000000000000..3aa6c1745744e3 --- /dev/null +++ b/test/llvm_interface_switch.out @@ -0,0 +1,5 @@ +0 +10 +3 +14 +-1 diff --git a/test/llvm_tests.json b/test/llvm_tests.json index 8697465998a956..7e7508bdc5fc1a 100644 --- a/test/llvm_tests.json +++ b/test/llvm_tests.json @@ -1,8 +1,46 @@ { "codegen": { "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/logic.go": "integer logic and conditional control flow" + "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" }, "blacklist": { "codegen/*": "LLVM lowering support has not reached this complete source file yet" @@ -10,7 +48,25 @@ }, "runtime": { "whitelist": { - "helloworld.go": "basic main-to-runtime call and execution" + "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", + "llvm_interface_equality.go": "empty and non-empty interface equality for matching and differing dynamic values", + "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" 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 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