Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0676680
wip: add metadata package summary model
luoliwoshang May 20, 2026
3b5392c
wip: persist package summary metadata in build cache
luoliwoshang May 20, 2026
b1ea4db
wip: emit semantic metadata summaries
luoliwoshang May 20, 2026
4e788ff
wip: emit type children metadata
luoliwoshang May 20, 2026
ba87854
wip: extract ordinary metadata edges
luoliwoshang May 20, 2026
23cfb43
wip: add metadata cache debug tooling
luoliwoshang May 20, 2026
fde3580
wip: add metadata-driven deadcode overrides
luoliwoshang May 20, 2026
8a1843f
fix deadcode interface method matching
luoliwoshang May 20, 2026
bb16367
fix deadcode propagation for interface-used type refs
luoliwoshang May 20, 2026
ed040b9
remove cache meta read timing log
luoliwoshang May 20, 2026
83b0ae0
fix dce unreachable method slots
luoliwoshang May 20, 2026
ae572d9
fix dce runtime init root
luoliwoshang May 21, 2026
7b1bcc3
Merge branch 'main' into wip/deadcode-analyzer
luoliwoshang Jun 21, 2026
dfb4ae2
test: update metadata expectations for method stubs
luoliwoshang Jun 21, 2026
40a546b
fix: record interface use from pointer boxing
luoliwoshang Jun 21, 2026
21d5f01
feat: add internal/meta package with mmap-based package summary cache…
luoliwoshang Jun 26, 2026
3552666
feat: migrate DCE pipeline from internal/metadata to internal/meta
luoliwoshang Jun 27, 2026
9861150
perf: make MethodSlots/InterfaceMethods lazy in GlobalSummary
luoliwoshang Jun 27, 2026
3c14a84
refactor: make PackageMeta an internal view; add N-prefix count methods
luoliwoshang Jun 27, 2026
1542da5
style: go fmt
luoliwoshang Jun 27, 2026
38b5154
style: go fmt collect_test.go
luoliwoshang Jun 27, 2026
782c8e8
Merge remote-tracking branch 'upstream/main' into wip/deadcode-mmap
luoliwoshang Jun 29, 2026
7fd02e9
fix(ssa): emit interface metadata from type descriptors
luoliwoshang Jun 30, 2026
55a77fe
fix(ssa): align named interface metadata
luoliwoshang Jun 30, 2026
400407f
fix(ssa): keep interface metadata descriptor stable
luoliwoshang Jun 30, 2026
eb81ecb
fix deadcode interface method demands
luoliwoshang Jun 30, 2026
d2b514d
fix deadcode interface info for method demands
luoliwoshang Jun 30, 2026
019f13a
update deadcode meta golden files
luoliwoshang Jun 30, 2026
092af96
Emit interface metadata from defining packages
luoliwoshang Jul 1, 2026
77f72ae
Add generic interface metadata coverage
luoliwoshang Jul 1, 2026
6bd134a
test: add deadcode drop regression suite
luoliwoshang Jul 1, 2026
fd8d8cb
meta: split function demands from ordinary edges
luoliwoshang Jul 2, 2026
9379516
meta: emit interface info at callsites
luoliwoshang Jul 2, 2026
ea939c3
test: fix generic interface meta golden newline
luoliwoshang Jul 3, 2026
6e76191
meta: remove legacy metadata package
luoliwoshang Jul 3, 2026
8653a64
test: drop interface hash patch path experiment
luoliwoshang Jul 4, 2026
3b029f8
meta: record interface method demands structurally
luoliwoshang Jul 4, 2026
9b1783d
meta: canonicalize patched interface demands
luoliwoshang Jul 4, 2026
8d9d092
test: update reflect meta golden
luoliwoshang Jul 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions chore/gentests/gentests.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func main() {
llgenDir(dir + "/cl/_testgo")
llgenDir(dir + "/cl/_testpy")
llgenDir(dir + "/cl/_testdata")
genMetaDir(dir + "/cl/_testmeta")

genExpects(dir)
}
Expand Down Expand Up @@ -77,6 +78,34 @@ func genExpects(root string) {
runExpectDir(root, "cl/_testdata")
}

func genMetaDir(dir string) {
fis, err := os.ReadDir(dir)
check(err)
for _, fi := range fis {
name := fi.Name()
if !fi.IsDir() || strings.HasPrefix(name, "_") {
continue
}
testDir := filepath.Join(dir, name)
if _, err := os.Stat(filepath.Join(testDir, "in.go")); err != nil {
if os.IsNotExist(err) {
continue
}
check(err)
}
relPath, err := filepath.Rel(filepath.Dir(filepath.Dir(dir)), testDir)
check(err)
relPath = filepath.ToSlash(relPath)
fmt.Fprintln(os.Stderr, "meta", relPath)
meta, err := cltest.CaptureMeta("./"+relPath, testDir)
if err != nil {
fmt.Fprintln(os.Stderr, "error:", relPath, err)
continue
}
check(os.WriteFile(filepath.Join(testDir, "meta-expect.txt"), []byte(meta), 0644))
}
}

func runExpectDir(root, relDir string, configure ...func(*build.Config)) {
dir := filepath.Join(root, relDir)
fis, err := os.ReadDir(dir)
Expand Down
63 changes: 63 additions & 0 deletions chore/metadump/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2024 The XGo Authors (xgo.dev). All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package main

import (
"flag"
"fmt"
"os"

"github.com/goplus/llgo/internal/meta"
)

func main() {
var out string
flag.StringVar(&out, "o", "", "write decoded metadata to file")
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage: metadump [-o output] file.meta\n")
flag.PrintDefaults()
}
flag.Parse()

if flag.NArg() != 1 {
flag.Usage()
os.Exit(2)
}

if err := run(flag.Arg(0), out); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

func run(input, output string) error {
pm, err := meta.ReadMeta(input)
if err != nil {
return fmt.Errorf("read %s: %w", input, err)
}
defer pm.Close()
text := pm.String()

if output == "" {
fmt.Print(text)
return nil
}
if err := os.WriteFile(output, []byte(text), 0o644); err != nil {
return fmt.Errorf("write %s: %w", output, err)
}
return nil
}
1 change: 1 addition & 0 deletions cl/_testdrop/direct_func/expect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
42
22 changes: 22 additions & 0 deletions cl/_testdrop/direct_func/in.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// LITTEST
package main

// SYMBOL-NOT: testdrop/direct_func{{.*}}Drop
// SYMBOL-DAG: testdrop/direct_func{{.*}}Keep
// SYMBOL-NOT: testdrop/direct_func{{.*}}Drop

var keepFunc = Keep

//go:noinline
func Keep() int {
return 42
}

//go:noinline
func Drop() int {
panic("Drop should be unreachable")
}

func main() {
println(keepFunc())
}
1 change: 1 addition & 0 deletions cl/_testdrop/direct_method/expect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
42
32 changes: 32 additions & 0 deletions cl/_testdrop/direct_method/in.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// LITTEST
package main

// SYMBOL-NOT: testdrop/direct_method{{.*}}T{{.*}}Drop
// SYMBOL-DAG: testdrop/direct_method{{.*}}T{{.*}}Keep
// SYMBOL-NOT: testdrop/direct_method{{.*}}T{{.*}}Drop

var sink any
var keepMethod = T.Keep

// This case keeps T's runtime type metadata reachable through an empty
// interface conversion. T.Drop must still be removed because no interface or
// direct call path requires that method slot.
type T struct {
n int
}

//go:noinline
func (t T) Keep() int {
return t.n + 1
}

//go:noinline
func (t T) Drop() int {
panic("Drop should be unreachable")
}

func main() {
t := T{n: 41}
sink = t
println(keepMethod(t))
}
9 changes: 9 additions & 0 deletions cl/_testdrop/exported_method_crosspkg/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package api

type Keeper interface {
Keep() int
}

func Use(k Keeper) int {
return k.Keep()
}
1 change: 1 addition & 0 deletions cl/_testdrop/exported_method_crosspkg/expect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
42
29 changes: 29 additions & 0 deletions cl/_testdrop/exported_method_crosspkg/in.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// LITTEST
package main

import "github.com/goplus/llgo/cl/_testdrop/exported_method_crosspkg/api"

// SYMBOL-NOT: testdrop/exported_method_crosspkg{{.*}}T{{.*}}Drop
// SYMBOL-DAG: testdrop/exported_method_crosspkg{{.*}}T{{.*}}Keep
// SYMBOL-NOT: testdrop/exported_method_crosspkg{{.*}}T{{.*}}Drop

// The interface type and dynamic call live in package api, while T and its
// method table live in this package. Exported method identity must let the
// global analysis match api.Keeper.Keep to T.Keep across package boundaries.
type T struct {
n int
}

//go:noinline
func (t T) Keep() int {
return t.n + 1
}

//go:noinline
func (t T) Drop() int {
panic("Drop should be unreachable")
}

func main() {
println(api.Use(T{n: 41}))
}
9 changes: 9 additions & 0 deletions cl/_testdrop/generic_interface_crosspkg/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package api

type I[T any] interface {
Value() T
}

func UseInt(v I[int]) int {
return v.Value()
}
1 change: 1 addition & 0 deletions cl/_testdrop/generic_interface_crosspkg/expect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
42
30 changes: 30 additions & 0 deletions cl/_testdrop/generic_interface_crosspkg/in.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// LITTEST
package main

import (
"github.com/goplus/llgo/cl/_testdrop/generic_interface_crosspkg/api"
"github.com/goplus/llgo/cl/_testdrop/generic_interface_crosspkg/model"
)

// SYMBOL-NOT: testdrop/generic_interface_crosspkg/model{{.*}}Box{{.*}}int{{.*}}Drop
// SYMBOL-NOT: testdrop/generic_interface_crosspkg/model{{.*}}Box{{.*}}string{{.*}}Value
// SYMBOL-DAG: testdrop/generic_interface_crosspkg/model{{.*}}Box{{.*}}int{{.*}}Value
// SYMBOL-NOT: testdrop/generic_interface_crosspkg/model{{.*}}Box{{.*}}int{{.*}}Drop
// SYMBOL-NOT: testdrop/generic_interface_crosspkg/model{{.*}}Box{{.*}}string{{.*}}Value

var sink any

func main() {
// api.UseInt demands I[int].Value, so the instantiated interface method
// signature is Value() int, not Value() T.
n := api.UseInt(model.NewIntBox(40))

// Box[string] reaches interface-domain metadata through any, but the only
// reachable generic interface demand is I[int].Value. Box[string].Value has
// the same method name and a different instantiated signature, so it must
// not be retained by name alone.
text := model.NewStringBox("go")
sink = text

println(n + model.UseStringBox(text))
}
27 changes: 27 additions & 0 deletions cl/_testdrop/generic_interface_crosspkg/model/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package model

type Box[T any] struct {
value T
}

func NewIntBox(v int) *Box[int] {
return &Box[int]{value: v}
}

func NewStringBox(v string) *Box[string] {
return &Box[string]{value: v}
}

func UseStringBox(v *Box[string]) int {
return len(v.value)
}

//go:noinline
func (b *Box[T]) Value() T {
return b.value
}

//go:noinline
func (b *Box[T]) Drop() T {
panic("Box.Drop should be unreachable")
}
9 changes: 9 additions & 0 deletions cl/_testdrop/generic_interface_func_crosspkg/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package api

type I[T any] interface {
Value() T
}

func Use[T any](v I[T]) T {
return v.Value()
}
1 change: 1 addition & 0 deletions cl/_testdrop/generic_interface_func_crosspkg/expect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
42
38 changes: 38 additions & 0 deletions cl/_testdrop/generic_interface_func_crosspkg/in.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// LITTEST
package main

import (
"github.com/goplus/llgo/cl/_testdrop/generic_interface_func_crosspkg/api"
"github.com/goplus/llgo/cl/_testdrop/generic_interface_func_crosspkg/model"
)

// SYMBOL-NOT: testdrop/generic_interface_func_crosspkg/model{{.*}}Box{{.*}}int{{.*}}Drop
// SYMBOL-NOT: testdrop/generic_interface_func_crosspkg/model{{.*}}Box{{.*}}uint{{.*}}Drop
// SYMBOL-NOT: testdrop/generic_interface_func_crosspkg/model{{.*}}Box{{.*}}string{{.*}}Value
// SYMBOL-DAG: testdrop/generic_interface_func_crosspkg/model{{.*}}Box{{.*}}int{{.*}}Value
// SYMBOL-DAG: testdrop/generic_interface_func_crosspkg/model{{.*}}Box{{.*}}uint{{.*}}Value
// SYMBOL-NOT: testdrop/generic_interface_func_crosspkg/model{{.*}}Box{{.*}}int{{.*}}Drop
// SYMBOL-NOT: testdrop/generic_interface_func_crosspkg/model{{.*}}Box{{.*}}uint{{.*}}Drop
// SYMBOL-NOT: testdrop/generic_interface_func_crosspkg/model{{.*}}Box{{.*}}string{{.*}}Value

var sink any

func main() {
// api.Use is generic, but this call instantiates it as Use[int]. The
// interface method demand inside Use is therefore I[int].Value, whose ABI
// method signature is Value() int.
n := api.Use[int](model.NewIntBox(40))

// This call relies on type argument inference. The compiler still
// instantiates api.Use as Use[uint], so it should produce an independent
// I[uint].Value demand.
u := api.Use(model.NewUintBox(1))

// Keep Box[string] metadata reachable without creating an I[string].Value
// demand. This guards the instantiated generic path against matching only
// by the method name Value.
text := model.NewStringBox("go")
sink = text

println(n + int(u) + model.UseStringBox(text) - 1)
}
31 changes: 31 additions & 0 deletions cl/_testdrop/generic_interface_func_crosspkg/model/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package model

type Box[T any] struct {
value T
}

func NewIntBox(v int) *Box[int] {
return &Box[int]{value: v}
}

func NewUintBox(v uint) *Box[uint] {
return &Box[uint]{value: v}
}

func NewStringBox(v string) *Box[string] {
return &Box[string]{value: v}
}

func UseStringBox(v *Box[string]) int {
return len(v.value)
}

//go:noinline
func (b *Box[T]) Value() T {
return b.value
}

//go:noinline
func (b *Box[T]) Drop() T {
panic("Box.Drop should be unreachable")
}
9 changes: 9 additions & 0 deletions cl/_testdrop/iface_flow_crosspkg/api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package api

type Keeper interface {
Keep() int
}

func Use(k Keeper) int {
return k.Keep()
}
1 change: 1 addition & 0 deletions cl/_testdrop/iface_flow_crosspkg/expect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
42
21 changes: 21 additions & 0 deletions cl/_testdrop/iface_flow_crosspkg/factory/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package factory

import "github.com/goplus/llgo/cl/_testdrop/iface_flow_crosspkg/api"

type T struct {
n int
}

//go:noinline
func (t T) Keep() int {
return t.n + 1
}

//go:noinline
func (t T) Drop() int {
panic("Drop should be unreachable")
}

func Make(n int) api.Keeper {
return T{n: n}
}
Loading
Loading