diff --git a/chore/gentests/gentests.go b/chore/gentests/gentests.go index 1c094b46d8..819dfaf5ed 100644 --- a/chore/gentests/gentests.go +++ b/chore/gentests/gentests.go @@ -40,6 +40,7 @@ func main() { llgenDir(dir + "/cl/_testgo") llgenDir(dir + "/cl/_testpy") llgenDir(dir + "/cl/_testdata") + genMetaDir(dir + "/cl/_testmeta") genExpects(dir) } @@ -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) diff --git a/chore/metadump/main.go b/chore/metadump/main.go new file mode 100644 index 0000000000..30ef950321 --- /dev/null +++ b/chore/metadump/main.go @@ -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 +} diff --git a/cl/_testdrop/direct_func/expect.txt b/cl/_testdrop/direct_func/expect.txt new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/cl/_testdrop/direct_func/expect.txt @@ -0,0 +1 @@ +42 diff --git a/cl/_testdrop/direct_func/in.go b/cl/_testdrop/direct_func/in.go new file mode 100644 index 0000000000..cf8410a738 --- /dev/null +++ b/cl/_testdrop/direct_func/in.go @@ -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()) +} diff --git a/cl/_testdrop/direct_method/expect.txt b/cl/_testdrop/direct_method/expect.txt new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/cl/_testdrop/direct_method/expect.txt @@ -0,0 +1 @@ +42 diff --git a/cl/_testdrop/direct_method/in.go b/cl/_testdrop/direct_method/in.go new file mode 100644 index 0000000000..a7ddb0cba5 --- /dev/null +++ b/cl/_testdrop/direct_method/in.go @@ -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)) +} diff --git a/cl/_testdrop/exported_method_crosspkg/api/api.go b/cl/_testdrop/exported_method_crosspkg/api/api.go new file mode 100644 index 0000000000..1080c2b72f --- /dev/null +++ b/cl/_testdrop/exported_method_crosspkg/api/api.go @@ -0,0 +1,9 @@ +package api + +type Keeper interface { + Keep() int +} + +func Use(k Keeper) int { + return k.Keep() +} diff --git a/cl/_testdrop/exported_method_crosspkg/expect.txt b/cl/_testdrop/exported_method_crosspkg/expect.txt new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/cl/_testdrop/exported_method_crosspkg/expect.txt @@ -0,0 +1 @@ +42 diff --git a/cl/_testdrop/exported_method_crosspkg/in.go b/cl/_testdrop/exported_method_crosspkg/in.go new file mode 100644 index 0000000000..76a7b84f54 --- /dev/null +++ b/cl/_testdrop/exported_method_crosspkg/in.go @@ -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})) +} diff --git a/cl/_testdrop/generic_interface_crosspkg/api/api.go b/cl/_testdrop/generic_interface_crosspkg/api/api.go new file mode 100644 index 0000000000..7e9c6a4ae1 --- /dev/null +++ b/cl/_testdrop/generic_interface_crosspkg/api/api.go @@ -0,0 +1,9 @@ +package api + +type I[T any] interface { + Value() T +} + +func UseInt(v I[int]) int { + return v.Value() +} diff --git a/cl/_testdrop/generic_interface_crosspkg/expect.txt b/cl/_testdrop/generic_interface_crosspkg/expect.txt new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/cl/_testdrop/generic_interface_crosspkg/expect.txt @@ -0,0 +1 @@ +42 diff --git a/cl/_testdrop/generic_interface_crosspkg/in.go b/cl/_testdrop/generic_interface_crosspkg/in.go new file mode 100644 index 0000000000..9190be00ff --- /dev/null +++ b/cl/_testdrop/generic_interface_crosspkg/in.go @@ -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)) +} diff --git a/cl/_testdrop/generic_interface_crosspkg/model/model.go b/cl/_testdrop/generic_interface_crosspkg/model/model.go new file mode 100644 index 0000000000..d9182b41f7 --- /dev/null +++ b/cl/_testdrop/generic_interface_crosspkg/model/model.go @@ -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") +} diff --git a/cl/_testdrop/generic_interface_func_crosspkg/api/api.go b/cl/_testdrop/generic_interface_func_crosspkg/api/api.go new file mode 100644 index 0000000000..b5a2b8bf70 --- /dev/null +++ b/cl/_testdrop/generic_interface_func_crosspkg/api/api.go @@ -0,0 +1,9 @@ +package api + +type I[T any] interface { + Value() T +} + +func Use[T any](v I[T]) T { + return v.Value() +} diff --git a/cl/_testdrop/generic_interface_func_crosspkg/expect.txt b/cl/_testdrop/generic_interface_func_crosspkg/expect.txt new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/cl/_testdrop/generic_interface_func_crosspkg/expect.txt @@ -0,0 +1 @@ +42 diff --git a/cl/_testdrop/generic_interface_func_crosspkg/in.go b/cl/_testdrop/generic_interface_func_crosspkg/in.go new file mode 100644 index 0000000000..e4e82e4aeb --- /dev/null +++ b/cl/_testdrop/generic_interface_func_crosspkg/in.go @@ -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) +} diff --git a/cl/_testdrop/generic_interface_func_crosspkg/model/model.go b/cl/_testdrop/generic_interface_func_crosspkg/model/model.go new file mode 100644 index 0000000000..375164f214 --- /dev/null +++ b/cl/_testdrop/generic_interface_func_crosspkg/model/model.go @@ -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") +} diff --git a/cl/_testdrop/iface_flow_crosspkg/api/api.go b/cl/_testdrop/iface_flow_crosspkg/api/api.go new file mode 100644 index 0000000000..1080c2b72f --- /dev/null +++ b/cl/_testdrop/iface_flow_crosspkg/api/api.go @@ -0,0 +1,9 @@ +package api + +type Keeper interface { + Keep() int +} + +func Use(k Keeper) int { + return k.Keep() +} diff --git a/cl/_testdrop/iface_flow_crosspkg/expect.txt b/cl/_testdrop/iface_flow_crosspkg/expect.txt new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/cl/_testdrop/iface_flow_crosspkg/expect.txt @@ -0,0 +1 @@ +42 diff --git a/cl/_testdrop/iface_flow_crosspkg/factory/factory.go b/cl/_testdrop/iface_flow_crosspkg/factory/factory.go new file mode 100644 index 0000000000..580c798553 --- /dev/null +++ b/cl/_testdrop/iface_flow_crosspkg/factory/factory.go @@ -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} +} diff --git a/cl/_testdrop/iface_flow_crosspkg/in.go b/cl/_testdrop/iface_flow_crosspkg/in.go new file mode 100644 index 0000000000..876e3220bf --- /dev/null +++ b/cl/_testdrop/iface_flow_crosspkg/in.go @@ -0,0 +1,18 @@ +// LITTEST +package main + +import ( + "github.com/goplus/llgo/cl/_testdrop/iface_flow_crosspkg/api" + "github.com/goplus/llgo/cl/_testdrop/iface_flow_crosspkg/factory" +) + +// SYMBOL-NOT: testdrop/iface_flow_crosspkg/factory{{.*}}T{{.*}}Drop +// SYMBOL-DAG: testdrop/iface_flow_crosspkg/factory{{.*}}T{{.*}}Keep +// SYMBOL-NOT: testdrop/iface_flow_crosspkg/factory{{.*}}T{{.*}}Drop + +func main() { + // factory.Make performs the concrete-to-interface conversion in another + // package. api.Use performs the interface method call in a third package. + // The global analysis must merge both facts before preserving T.Keep. + println(api.Use(factory.Make(41))) +} diff --git a/cl/_testdrop/interface_demand_fixedpoint/api/api.go b/cl/_testdrop/interface_demand_fixedpoint/api/api.go new file mode 100644 index 0000000000..d48ce376f6 --- /dev/null +++ b/cl/_testdrop/interface_demand_fixedpoint/api/api.go @@ -0,0 +1,28 @@ +package api + +type First interface { + Run() int +} + +type Second interface { + Next() int +} + +type Third interface { + Done() int +} + +//go:noinline +func UseFirst(f First) int { + return f.Run() +} + +//go:noinline +func UseSecond(s Second) int { + return s.Next() +} + +//go:noinline +func UseThird(t Third) int { + return t.Done() +} diff --git a/cl/_testdrop/interface_demand_fixedpoint/expect.txt b/cl/_testdrop/interface_demand_fixedpoint/expect.txt new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/cl/_testdrop/interface_demand_fixedpoint/expect.txt @@ -0,0 +1 @@ +42 diff --git a/cl/_testdrop/interface_demand_fixedpoint/flow/flow.go b/cl/_testdrop/interface_demand_fixedpoint/flow/flow.go new file mode 100644 index 0000000000..f7df2ef700 --- /dev/null +++ b/cl/_testdrop/interface_demand_fixedpoint/flow/flow.go @@ -0,0 +1,45 @@ +package flow + +import "github.com/goplus/llgo/cl/_testdrop/interface_demand_fixedpoint/api" + +//go:noinline +func Step(n int) int { + // This creates the api.Second.Next demand, but Step itself is reached only + // through the ordinary edge from dynamically kept Runner.Run. + return api.UseSecond(Worker{N: n}) +} + +type Worker struct { + N int +} + +//go:noinline +func (w Worker) Next() int { + // Keeping Worker.Next exposes another ordinary edge, which then reaches a + // third interface method demand. + return api.UseThird(Finisher{N: w.N}) +} + +//go:noinline +func (w Worker) Drop() int { + panic("Worker.Drop should be unreachable") +} + +type Finisher struct { + N int +} + +//go:noinline +func (f Finisher) Done() int { + return finalValue(f.N) +} + +//go:noinline +func (f Finisher) Drop() int { + panic("Finisher.Drop should be unreachable") +} + +//go:noinline +func finalValue(n int) int { + return n + 1 +} diff --git a/cl/_testdrop/interface_demand_fixedpoint/in.go b/cl/_testdrop/interface_demand_fixedpoint/in.go new file mode 100644 index 0000000000..cbd2030487 --- /dev/null +++ b/cl/_testdrop/interface_demand_fixedpoint/in.go @@ -0,0 +1,29 @@ +// LITTEST +package main + +import ( + "github.com/goplus/llgo/cl/_testdrop/interface_demand_fixedpoint/api" + "github.com/goplus/llgo/cl/_testdrop/interface_demand_fixedpoint/flow" + "github.com/goplus/llgo/cl/_testdrop/interface_demand_fixedpoint/model" +) + +// SYMBOL-NOT: testdrop/interface_demand_fixedpoint/model{{.*}}Runner{{.*}}Drop +// SYMBOL-NOT: testdrop/interface_demand_fixedpoint/flow{{.*}}Worker{{.*}}Drop +// SYMBOL-NOT: testdrop/interface_demand_fixedpoint/flow{{.*}}Finisher{{.*}}Drop +// SYMBOL-DAG: testdrop/interface_demand_fixedpoint/model{{.*}}Runner{{.*}}Run +// SYMBOL-DAG: testdrop/interface_demand_fixedpoint/flow{{.*}}Step +// SYMBOL-DAG: testdrop/interface_demand_fixedpoint/flow{{.*}}Worker{{.*}}Next +// SYMBOL-DAG: testdrop/interface_demand_fixedpoint/flow{{.*}}Finisher{{.*}}Done +// SYMBOL-NOT: testdrop/interface_demand_fixedpoint/model{{.*}}Runner{{.*}}Drop +// SYMBOL-NOT: testdrop/interface_demand_fixedpoint/flow{{.*}}Worker{{.*}}Drop +// SYMBOL-NOT: testdrop/interface_demand_fixedpoint/flow{{.*}}Finisher{{.*}}Drop + +var sink any + +func main() { + // Keep a second Worker type descriptor reachable without directly creating + // a Second.Next demand from main. The actual Worker.Next demand is produced + // only after Runner.Run is kept and reaches flow.Step. + sink = flow.Worker{N: 0} + println(api.UseFirst(model.NewRunner())) +} diff --git a/cl/_testdrop/interface_demand_fixedpoint/model/model.go b/cl/_testdrop/interface_demand_fixedpoint/model/model.go new file mode 100644 index 0000000000..95b48bc4ef --- /dev/null +++ b/cl/_testdrop/interface_demand_fixedpoint/model/model.go @@ -0,0 +1,21 @@ +package model + +import "github.com/goplus/llgo/cl/_testdrop/interface_demand_fixedpoint/flow" + +type Runner struct{} + +func NewRunner() Runner { + return Runner{} +} + +//go:noinline +func (Runner) Run() int { + // This static call is only reachable after Runner.Run is dynamically kept + // for the api.First.Run interface demand. + return flow.Step(41) +} + +//go:noinline +func (Runner) Drop() int { + panic("Runner.Drop should be unreachable") +} diff --git a/cl/_testdrop/interface_match/expect.txt b/cl/_testdrop/interface_match/expect.txt new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/cl/_testdrop/interface_match/expect.txt @@ -0,0 +1 @@ +42 diff --git a/cl/_testdrop/interface_match/in.go b/cl/_testdrop/interface_match/in.go new file mode 100644 index 0000000000..6c55a0c369 --- /dev/null +++ b/cl/_testdrop/interface_match/in.go @@ -0,0 +1,72 @@ +// LITTEST +package main + +// SYMBOL-NOT: testdrop/interface_match{{.*}}OnlyReader{{.*}}Read +// SYMBOL-NOT: testdrop/interface_match{{.*}}OnlyReader{{.*}}Drop +// SYMBOL-NOT: testdrop/interface_match{{.*}}Full{{.*}}Write +// SYMBOL-NOT: testdrop/interface_match{{.*}}Full{{.*}}Drop +// SYMBOL-DAG: testdrop/interface_match{{.*}}Full{{.*}}Read +// SYMBOL-NOT: testdrop/interface_match{{.*}}OnlyReader{{.*}}Read +// SYMBOL-NOT: testdrop/interface_match{{.*}}OnlyReader{{.*}}Drop +// SYMBOL-NOT: testdrop/interface_match{{.*}}Full{{.*}}Write +// SYMBOL-NOT: testdrop/interface_match{{.*}}Full{{.*}}Drop + +type Reader interface { + Read() int +} + +type ReadWriter interface { + Read() int + Write() int +} + +var sink any + +// OnlyReader has a Read method and reaches interface-typed metadata through +// any, but it does not implement ReadWriter. A ReadWriter.Read demand must not +// keep OnlyReader.Read alive by name alone. +type OnlyReader struct { + n int +} + +//go:noinline +func (r OnlyReader) Read() int { + return r.n + 1 +} + +//go:noinline +func (r OnlyReader) Drop() int { + panic("OnlyReader.Drop should be unreachable") +} + +// Full implements ReadWriter. The only reachable dynamic interface call is +// ReadWriter.Read, so Full.Read remains live while Full.Write and Full.Drop are +// not needed by the final method demand set. +type Full struct { + n int +} + +//go:noinline +func (f Full) Read() int { + return f.n + 10 +} + +//go:noinline +func (f Full) Write() int { + panic("Full.Write should be unreachable") +} + +//go:noinline +func (f Full) Drop() int { + panic("Full.Drop should be unreachable") +} + +func use(rw ReadWriter) int { + return rw.Read() +} + +func main() { + var _ Reader = OnlyReader{} + sink = OnlyReader{n: 1} + println(use(Full{n: 32})) +} diff --git a/cl/_testdrop/interface_slot/expect.txt b/cl/_testdrop/interface_slot/expect.txt new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/cl/_testdrop/interface_slot/expect.txt @@ -0,0 +1 @@ +42 diff --git a/cl/_testdrop/interface_slot/in.go b/cl/_testdrop/interface_slot/in.go new file mode 100644 index 0000000000..183adb1dac --- /dev/null +++ b/cl/_testdrop/interface_slot/in.go @@ -0,0 +1,35 @@ +// LITTEST +package main + +// SYMBOL-NOT: testdrop/interface_slot{{.*}}T{{.*}}Drop +// SYMBOL-DAG: testdrop/interface_slot{{.*}}T{{.*}}Keep +// SYMBOL-NOT: testdrop/interface_slot{{.*}}T{{.*}}Drop + +type I interface { + Keep() int +} + +// This case converts T to a non-empty interface and calls I.Keep. T implements +// I, so T.Keep must remain reachable, while T.Drop is not required by the +// reachable interface method demand. +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 use(i I) int { + return i.Keep() +} + +func main() { + println(use(T{n: 41})) +} diff --git a/cl/_testdrop/promoted_method_wrapper/expect.txt b/cl/_testdrop/promoted_method_wrapper/expect.txt new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/cl/_testdrop/promoted_method_wrapper/expect.txt @@ -0,0 +1 @@ +42 diff --git a/cl/_testdrop/promoted_method_wrapper/in.go b/cl/_testdrop/promoted_method_wrapper/in.go new file mode 100644 index 0000000000..8760bdecdd --- /dev/null +++ b/cl/_testdrop/promoted_method_wrapper/in.go @@ -0,0 +1,39 @@ +// LITTEST +package main + +// SYMBOL-NOT: testdrop/promoted_method_wrapper{{.*}}Wrapper{{.*}}Drop +// SYMBOL-DAG: testdrop/promoted_method_wrapper{{.*}}Wrapper{{.*}}Keep +// SYMBOL-NOT: testdrop/promoted_method_wrapper{{.*}}Wrapper{{.*}}Drop + +type I interface { + Keep() int +} + +type T struct { + n int +} + +//go:noinline +func (t *T) Keep() int { + return t.n + 1 +} + +//go:noinline +func (t *T) Drop() int { + panic("T.Drop should be unreachable") +} + +type Wrapper struct { + *T +} + +// This case converts Wrapper, not *T, to a non-empty interface. Wrapper has no +// declared Keep method; its method table slot is a promoted wrapper forwarding +// to (*T).Keep. DCE must keep that wrapper slot alive. +func use(i I) int { + return i.Keep() +} + +func main() { + println(use(Wrapper{T: &T{n: 41}})) +} diff --git a/cl/_testdrop/reflect_dynamic_iface_crosspkg/api/api.go b/cl/_testdrop/reflect_dynamic_iface_crosspkg/api/api.go new file mode 100644 index 0000000000..d51dcf51fe --- /dev/null +++ b/cl/_testdrop/reflect_dynamic_iface_crosspkg/api/api.go @@ -0,0 +1,12 @@ +package api + +type Reflector interface { + ReflectKeep() int +} + +var Sink Reflector + +//go:noinline +func Accept(r Reflector) { + Sink = r +} diff --git a/cl/_testdrop/reflect_dynamic_iface_crosspkg/expect.txt b/cl/_testdrop/reflect_dynamic_iface_crosspkg/expect.txt new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/cl/_testdrop/reflect_dynamic_iface_crosspkg/expect.txt @@ -0,0 +1 @@ +42 diff --git a/cl/_testdrop/reflect_dynamic_iface_crosspkg/in.go b/cl/_testdrop/reflect_dynamic_iface_crosspkg/in.go new file mode 100644 index 0000000000..ed0dac0d0d --- /dev/null +++ b/cl/_testdrop/reflect_dynamic_iface_crosspkg/in.go @@ -0,0 +1,33 @@ +// LITTEST +package main + +import ( + "reflect" + + "github.com/goplus/llgo/cl/_testdrop/reflect_dynamic_iface_crosspkg/api" + "github.com/goplus/llgo/cl/_testdrop/reflect_dynamic_iface_crosspkg/model" +) + +// SYMBOL-NOT: testdrop/reflect_dynamic_iface_crosspkg/model{{.*}}Unused{{.*}}ReflectKeep +// SYMBOL-DAG: testdrop/reflect_dynamic_iface_crosspkg/model{{.*}}Used{{.*}}ReflectKeep +// SYMBOL-NOT: testdrop/reflect_dynamic_iface_crosspkg/model{{.*}}Unused{{.*}}ReflectKeep + +//go:noinline +func dynamicName(name string) string { + return name +} + +func main() { + // api.Accept makes model.Used enter a reachable non-empty interface without + // calling ReflectKeep through that interface. The dynamic MethodByName below + // must conservatively keep exported methods for interface-domain types. + api.Accept(model.NewUsed(0)) + + // model.Unused also implements api.Reflector, but it is only used as a + // concrete value. It must not be retained just because a reachable interface + // has the same exported method. + unused := model.UseUnused(model.NewUnused(0)) + + out := reflect.ValueOf(model.NewUsed(41)).MethodByName(dynamicName("ReflectKeep")).Call(nil) + println(out[0].Int() + int64(unused)) +} diff --git a/cl/_testdrop/reflect_dynamic_iface_crosspkg/model/model.go b/cl/_testdrop/reflect_dynamic_iface_crosspkg/model/model.go new file mode 100644 index 0000000000..0836cd63ca --- /dev/null +++ b/cl/_testdrop/reflect_dynamic_iface_crosspkg/model/model.go @@ -0,0 +1,31 @@ +package model + +type Used struct { + n int +} + +func NewUsed(n int) Used { + return Used{n: n} +} + +//go:noinline +func (u Used) ReflectKeep() int { + return u.n + 1 +} + +type Unused struct { + n int +} + +func NewUnused(n int) Unused { + return Unused{n: n} +} + +//go:noinline +func (u Unused) ReflectKeep() int { + panic("Unused.ReflectKeep should be unreachable") +} + +func UseUnused(u Unused) int { + return u.n +} diff --git a/cl/_testdrop/reflect_named_method/expect.txt b/cl/_testdrop/reflect_named_method/expect.txt new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/cl/_testdrop/reflect_named_method/expect.txt @@ -0,0 +1 @@ +42 diff --git a/cl/_testdrop/reflect_named_method/in.go b/cl/_testdrop/reflect_named_method/in.go new file mode 100644 index 0000000000..59a2cbfd25 --- /dev/null +++ b/cl/_testdrop/reflect_named_method/in.go @@ -0,0 +1,28 @@ +// LITTEST +package main + +import "reflect" + +// SYMBOL-NOT: testdrop/reflect_named_method{{.*}}T{{.*}}Drop +// SYMBOL-DAG: testdrop/reflect_named_method{{.*}}T{{.*}}Keep +// SYMBOL-NOT: testdrop/reflect_named_method{{.*}}T{{.*}}Drop + +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() { + v := reflect.ValueOf(T{n: 41}) + out := v.MethodByName("Keep").Call(nil) + println(out[0].Int()) +} diff --git a/cl/_testdrop/source64_crosspkg/api/api.go b/cl/_testdrop/source64_crosspkg/api/api.go new file mode 100644 index 0000000000..9e125093e2 --- /dev/null +++ b/cl/_testdrop/source64_crosspkg/api/api.go @@ -0,0 +1,28 @@ +package api + +type Source interface { + Int63() int64 + Seed(int64) +} + +type Source64 interface { + Source + Uint64() uint64 +} + +type Rand struct { + src Source + s64 Source64 +} + +func New(src Source) *Rand { + s64, _ := src.(Source64) + return &Rand{src: src, s64: s64} +} + +func (r *Rand) Uint64() uint64 { + if r.s64 != nil { + return r.s64.Uint64() + } + return uint64(r.src.Int63()) +} diff --git a/cl/_testdrop/source64_crosspkg/expect.txt b/cl/_testdrop/source64_crosspkg/expect.txt new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/cl/_testdrop/source64_crosspkg/expect.txt @@ -0,0 +1 @@ +42 diff --git a/cl/_testdrop/source64_crosspkg/in.go b/cl/_testdrop/source64_crosspkg/in.go new file mode 100644 index 0000000000..8743c2646c --- /dev/null +++ b/cl/_testdrop/source64_crosspkg/in.go @@ -0,0 +1,31 @@ +// LITTEST +package main + +import ( + "github.com/goplus/llgo/cl/_testdrop/source64_crosspkg/api" + "github.com/goplus/llgo/cl/_testdrop/source64_crosspkg/model" +) + +// SYMBOL-NOT: testdrop/source64_crosspkg/model{{.*}}RuntimeSource{{.*}}Drop +// SYMBOL-NOT: testdrop/source64_crosspkg/model{{.*}}Uint64Only{{.*}}Uint64 +// SYMBOL-DAG: testdrop/source64_crosspkg/model{{.*}}RuntimeSource{{.*}}Uint64 +// SYMBOL-NOT: testdrop/source64_crosspkg/model{{.*}}RuntimeSource{{.*}}Drop +// SYMBOL-NOT: testdrop/source64_crosspkg/model{{.*}}Uint64Only{{.*}}Uint64 + +var sink any + +func main() { + // This mirrors math/rand's Source/Source64 shape without importing rand. + // RuntimeSource enters the interface domain as api.Source, then api.New + // discovers the wider api.Source64 interface and Rand.Uint64 performs the + // dynamic Source64.Uint64 call. + r := api.New(model.NewRuntimeSource(41)) + + // Uint64Only reaches type metadata through any and has the same Uint64 + // method name, but it does not fully implement api.Source64. The Source64 + // demand must not keep this method alive by name alone. + only := model.NewUint64Only(100) + sink = only + + println(r.Uint64() + model.UseUint64Only(only) - 100) +} diff --git a/cl/_testdrop/source64_crosspkg/model/model.go b/cl/_testdrop/source64_crosspkg/model/model.go new file mode 100644 index 0000000000..02dfe20578 --- /dev/null +++ b/cl/_testdrop/source64_crosspkg/model/model.go @@ -0,0 +1,48 @@ +package model + +import "github.com/goplus/llgo/cl/_testdrop/source64_crosspkg/api" + +type RuntimeSource struct { + n uint64 +} + +func NewRuntimeSource(n uint64) api.Source { + return &RuntimeSource{n: n} +} + +//go:noinline +func (s *RuntimeSource) Int63() int64 { + return int64(s.n) +} + +//go:noinline +func (s *RuntimeSource) Seed(seed int64) { + s.n = uint64(seed) +} + +//go:noinline +func (s *RuntimeSource) Uint64() uint64 { + return s.n + 1 +} + +//go:noinline +func (s *RuntimeSource) Drop() uint64 { + panic("RuntimeSource.Drop should be unreachable") +} + +type Uint64Only struct { + n uint64 +} + +func NewUint64Only(n uint64) Uint64Only { + return Uint64Only{n: n} +} + +func UseUint64Only(v Uint64Only) uint64 { + return v.n +} + +//go:noinline +func (v Uint64Only) Uint64() uint64 { + panic("Uint64Only.Uint64 should be unreachable") +} diff --git a/cl/_testdrop/unexported_method_identity/api/api.go b/cl/_testdrop/unexported_method_identity/api/api.go new file mode 100644 index 0000000000..9a030cada9 --- /dev/null +++ b/cl/_testdrop/unexported_method_identity/api/api.go @@ -0,0 +1,22 @@ +package api + +type hiddenIface interface { + hidden() int +} + +type Good struct { + n int +} + +//go:noinline +func (g Good) hidden() int { + return g.n + 1 +} + +func NewGood(n int) Good { + return Good{n: n} +} + +func Use(v hiddenIface) int { + return v.hidden() +} diff --git a/cl/_testdrop/unexported_method_identity/expect.txt b/cl/_testdrop/unexported_method_identity/expect.txt new file mode 100644 index 0000000000..d81cc0710e --- /dev/null +++ b/cl/_testdrop/unexported_method_identity/expect.txt @@ -0,0 +1 @@ +42 diff --git a/cl/_testdrop/unexported_method_identity/in.go b/cl/_testdrop/unexported_method_identity/in.go new file mode 100644 index 0000000000..39d9b7997f --- /dev/null +++ b/cl/_testdrop/unexported_method_identity/in.go @@ -0,0 +1,27 @@ +// LITTEST +package main + +import "github.com/goplus/llgo/cl/_testdrop/unexported_method_identity/api" + +// SYMBOL-NOT: testdrop/unexported_method_identity{{.*}}Local{{.*}}hidden +// SYMBOL-DAG: testdrop/unexported_method_identity/api{{.*}}Good{{.*}}hidden +// SYMBOL-NOT: testdrop/unexported_method_identity{{.*}}Local{{.*}}hidden + +var sink any + +// Local has a same-spelled unexported hidden method and its type metadata is +// reachable through any. The api.hiddenIface demand must not keep Local.hidden +// alive because unexported method identity includes the defining package. +type Local struct { + n int +} + +//go:noinline +func (l Local) hidden() int { + panic("Local.hidden should be unreachable") +} + +func main() { + sink = Local{n: 1} + println(api.Use(api.NewGood(41))) +} diff --git a/cl/_testmeta/ifaceuse_basic/in.go b/cl/_testmeta/ifaceuse_basic/in.go new file mode 100644 index 0000000000..ab6ca52475 --- /dev/null +++ b/cl/_testmeta/ifaceuse_basic/in.go @@ -0,0 +1,11 @@ +package main + +type T struct{} + +func sink(v any) { + _ = v +} + +func main() { + sink(T{}) +} diff --git a/cl/_testmeta/ifaceuse_basic/meta-expect.txt b/cl/_testmeta/ifaceuse_basic/meta-expect.txt new file mode 100644 index 0000000000..434b667c9e --- /dev/null +++ b/cl/_testmeta/ifaceuse_basic/meta-expect.txt @@ -0,0 +1,26 @@ +[TypeChildren] +*_llgo_github.com/goplus/llgo/cl/_testmeta/ifaceuse_basic.T: + _llgo_github.com/goplus/llgo/cl/_testmeta/ifaceuse_basic.T + +[OrdinaryEdges] +*_llgo_github.com/goplus/llgo/cl/_testmeta/ifaceuse_basic.T: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_github.com/goplus/llgo/cl/_testmeta/ifaceuse_basic.T +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal0: + github.com/goplus/llgo/runtime/internal/runtime.memequal0 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr: + github.com/goplus/llgo/runtime/internal/runtime.memequalptr +_llgo_github.com/goplus/llgo/cl/_testmeta/ifaceuse_basic.T: + *_llgo_github.com/goplus/llgo/cl/_testmeta/ifaceuse_basic.T + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal0 +github.com/goplus/llgo/cl/_testmeta/ifaceuse_basic.init: + github.com/goplus/llgo/cl/_testmeta/ifaceuse_basic.init$guard +github.com/goplus/llgo/cl/_testmeta/ifaceuse_basic.main: + _llgo_github.com/goplus/llgo/cl/_testmeta/ifaceuse_basic.T + github.com/goplus/llgo/cl/_testmeta/ifaceuse_basic.sink + github.com/goplus/llgo/runtime/internal/runtime.AllocU + +[UseIface] +github.com/goplus/llgo/cl/_testmeta/ifaceuse_basic.main: + _llgo_github.com/goplus/llgo/cl/_testmeta/ifaceuse_basic.T + diff --git a/cl/_testmeta/interface_anyonmous/in.go b/cl/_testmeta/interface_anyonmous/in.go new file mode 100644 index 0000000000..01c93e8514 --- /dev/null +++ b/cl/_testmeta/interface_anyonmous/in.go @@ -0,0 +1,18 @@ +package main + +type T struct{} + +func (T) M() {} +func (T) N() {} + +func use(v interface { + M() + N() +}) { + v.M() + v.N() +} + +func main() { + use(T{}) +} diff --git a/cl/_testmeta/interface_anyonmous/meta-expect.txt b/cl/_testmeta/interface_anyonmous/meta-expect.txt new file mode 100644 index 0000000000..f20d5aaf97 --- /dev/null +++ b/cl/_testmeta/interface_anyonmous/meta-expect.txt @@ -0,0 +1,86 @@ +[TypeChildren] +*_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T: + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T +*_llgo_iface$f14WsslTA1u5wwC83jLU0HU2u2mmAWxBVE38vPBbRAo: + _llgo_iface$f14WsslTA1u5wwC83jLU0HU2u2mmAWxBVE38vPBbRAo +_llgo_iface$f14WsslTA1u5wwC83jLU0HU2u2mmAWxBVE38vPBbRAo: + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac + +[OrdinaryEdges] +*_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T +*_llgo_iface$f14WsslTA1u5wwC83jLU0HU2u2mmAWxBVE38vPBbRAo: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_iface$f14WsslTA1u5wwC83jLU0HU2u2mmAWxBVE38vPBbRAo +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.(*T).M: + github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.(*T).M +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.(*T).N: + github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.(*T).N +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T.M: + github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T.M +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T.N: + github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T.N +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal: + github.com/goplus/llgo/runtime/internal/runtime.interequal +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal0: + github.com/goplus/llgo/runtime/internal/runtime.memequal0 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr: + github.com/goplus/llgo/runtime/internal/runtime.memequalptr +_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + *_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +_llgo_github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T: + *_llgo_github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal0 +_llgo_iface$f14WsslTA1u5wwC83jLU0HU2u2mmAWxBVE38vPBbRAo: + *_llgo_iface$f14WsslTA1u5wwC83jLU0HU2u2mmAWxBVE38vPBbRAo + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal + _llgo_iface$f14WsslTA1u5wwC83jLU0HU2u2mmAWxBVE38vPBbRAo$imethods +_llgo_iface$f14WsslTA1u5wwC83jLU0HU2u2mmAWxBVE38vPBbRAo$imethods: + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.(*T).M: + github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T.M + github.com/goplus/llgo/runtime/internal/runtime.AssertNilDeref + github.com/goplus/llgo/runtime/internal/runtime.PanicWrapNilPointer +github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.(*T).N: + github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T.N + github.com/goplus/llgo/runtime/internal/runtime.AssertNilDeref + github.com/goplus/llgo/runtime/internal/runtime.PanicWrapNilPointer +github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.init: + github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.init$guard +github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.main: + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T + _llgo_iface$f14WsslTA1u5wwC83jLU0HU2u2mmAWxBVE38vPBbRAo + github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.use + github.com/goplus/llgo/runtime/internal/runtime.AllocU + github.com/goplus/llgo/runtime/internal/runtime.NewItab +github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.use: + github.com/goplus/llgo/runtime/internal/runtime.IfacePtrData + +[UseIface] +github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.main: + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T + +[UseIfaceMethod] +github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.use: + _llgo_iface$f14WsslTA1u5wwC83jLU0HU2u2mmAWxBVE38vPBbRAo M _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac + _llgo_iface$f14WsslTA1u5wwC83jLU0HU2u2mmAWxBVE38vPBbRAo N _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac + +[MethodInfo] +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T: + 0 M _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.(*T).M __llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.(*T).M + 1 N _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.(*T).N __llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.(*T).N +_llgo_github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T: + 0 M _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.(*T).M __llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T.M + 1 N _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.(*T).N __llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_anyonmous.T.N + +[InterfaceInfo] +_llgo_iface$f14WsslTA1u5wwC83jLU0HU2u2mmAWxBVE38vPBbRAo: + M _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac + N _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac + diff --git a/cl/_testmeta/interface_exported_var/in.go b/cl/_testmeta/interface_exported_var/in.go new file mode 100644 index 0000000000..b8ab613bac --- /dev/null +++ b/cl/_testmeta/interface_exported_var/in.go @@ -0,0 +1,8 @@ +package main + +import "encoding/binary" + +func main() { + var order binary.ByteOrder = binary.LittleEndian + _ = order.Uint16([]byte{1, 2}) +} diff --git a/cl/_testmeta/interface_exported_var/meta-expect.txt b/cl/_testmeta/interface_exported_var/meta-expect.txt new file mode 100644 index 0000000000..4be1e14c08 --- /dev/null +++ b/cl/_testmeta/interface_exported_var/meta-expect.txt @@ -0,0 +1,356 @@ +[TypeChildren] +*[]_llgo_uint8: + []_llgo_uint8 +*_llgo_encoding/binary.littleEndian: + _llgo_encoding/binary.littleEndian +*_llgo_func$HQem8FNvPqrEVQ_c0XssBDFXIDOnET_Ex7o3PlV9bSs: + _llgo_func$HQem8FNvPqrEVQ_c0XssBDFXIDOnET_Ex7o3PlV9bSs +*_llgo_func$ICfGJV9Kp4o-2SMi1iuyC9xBBX1c5utHD63uVbyrfEc: + _llgo_func$ICfGJV9Kp4o-2SMi1iuyC9xBBX1c5utHD63uVbyrfEc +*_llgo_func$JXgl4jz35cOkksEkyj-ImVTqIDNL16JrZ25HHa-Yekw: + _llgo_func$JXgl4jz35cOkksEkyj-ImVTqIDNL16JrZ25HHa-Yekw +*_llgo_func$YjgNCugJxKXYLk39KOJyRyLtvcHU1d7KRz4inhHdVgg: + _llgo_func$YjgNCugJxKXYLk39KOJyRyLtvcHU1d7KRz4inhHdVgg +*_llgo_func$_JswjMs_mFNKWtFb56TJlZa479nBYWhoAxYBwUTwOyc: + _llgo_func$_JswjMs_mFNKWtFb56TJlZa479nBYWhoAxYBwUTwOyc +*_llgo_func$hV5ojfmZDe6MffI0ylemgDF5GZlZiYaJbQvka2A0Gus: + _llgo_func$hV5ojfmZDe6MffI0ylemgDF5GZlZiYaJbQvka2A0Gus +*_llgo_func$ibbVVENlZHDgol7ROnCbrRmXPuO818NXcpl5dfFoKhU: + _llgo_func$ibbVVENlZHDgol7ROnCbrRmXPuO818NXcpl5dfFoKhU +*_llgo_func$mBhSCdZCFK2IHVQVA73dFmon0gMcig2Q387khsbzmm8: + _llgo_func$mBhSCdZCFK2IHVQVA73dFmon0gMcig2Q387khsbzmm8 +*_llgo_func$mjkdaEUHPtpOYlUrWGfnskhhvdyc7k9Fk-vwWj3VftE: + _llgo_func$mjkdaEUHPtpOYlUrWGfnskhhvdyc7k9Fk-vwWj3VftE +*_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to: + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to +*_llgo_iface$J1wM-rGcIPemx5jloXBmH7pUzUCSqpgNkOdb0QIFTxw: + _llgo_iface$J1wM-rGcIPemx5jloXBmH7pUzUCSqpgNkOdb0QIFTxw +*_llgo_string: + _llgo_string +*_llgo_uint16: + _llgo_uint16 +*_llgo_uint32: + _llgo_uint32 +*_llgo_uint64: + _llgo_uint64 +*_llgo_uint8: + _llgo_uint8 +[]_llgo_uint8: + _llgo_uint8 +_llgo_func$HQem8FNvPqrEVQ_c0XssBDFXIDOnET_Ex7o3PlV9bSs: + []_llgo_uint8 + _llgo_uint64 +_llgo_func$ICfGJV9Kp4o-2SMi1iuyC9xBBX1c5utHD63uVbyrfEc: + []_llgo_uint8 + _llgo_uint32 +_llgo_func$JXgl4jz35cOkksEkyj-ImVTqIDNL16JrZ25HHa-Yekw: + []_llgo_uint8 + _llgo_uint16 +_llgo_func$YjgNCugJxKXYLk39KOJyRyLtvcHU1d7KRz4inhHdVgg: + []_llgo_uint8 + _llgo_uint32 +_llgo_func$_JswjMs_mFNKWtFb56TJlZa479nBYWhoAxYBwUTwOyc: + []_llgo_uint8 + _llgo_uint32 +_llgo_func$hV5ojfmZDe6MffI0ylemgDF5GZlZiYaJbQvka2A0Gus: + []_llgo_uint8 + _llgo_uint16 +_llgo_func$ibbVVENlZHDgol7ROnCbrRmXPuO818NXcpl5dfFoKhU: + []_llgo_uint8 + _llgo_uint16 +_llgo_func$mBhSCdZCFK2IHVQVA73dFmon0gMcig2Q387khsbzmm8: + []_llgo_uint8 + _llgo_uint64 +_llgo_func$mjkdaEUHPtpOYlUrWGfnskhhvdyc7k9Fk-vwWj3VftE: + []_llgo_uint8 + _llgo_uint64 +_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to: + _llgo_string +_llgo_iface$J1wM-rGcIPemx5jloXBmH7pUzUCSqpgNkOdb0QIFTxw: + _llgo_func$ICfGJV9Kp4o-2SMi1iuyC9xBBX1c5utHD63uVbyrfEc + _llgo_func$YjgNCugJxKXYLk39KOJyRyLtvcHU1d7KRz4inhHdVgg + _llgo_func$hV5ojfmZDe6MffI0ylemgDF5GZlZiYaJbQvka2A0Gus + _llgo_func$ibbVVENlZHDgol7ROnCbrRmXPuO818NXcpl5dfFoKhU + _llgo_func$mBhSCdZCFK2IHVQVA73dFmon0gMcig2Q387khsbzmm8 + _llgo_func$mjkdaEUHPtpOYlUrWGfnskhhvdyc7k9Fk-vwWj3VftE + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to + +[OrdinaryEdges] +*[]_llgo_uint8: + []_llgo_uint8 + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr +*_llgo_encoding/binary.littleEndian: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_encoding/binary.littleEndian +*_llgo_func$HQem8FNvPqrEVQ_c0XssBDFXIDOnET_Ex7o3PlV9bSs: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$HQem8FNvPqrEVQ_c0XssBDFXIDOnET_Ex7o3PlV9bSs +*_llgo_func$ICfGJV9Kp4o-2SMi1iuyC9xBBX1c5utHD63uVbyrfEc: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$ICfGJV9Kp4o-2SMi1iuyC9xBBX1c5utHD63uVbyrfEc +*_llgo_func$JXgl4jz35cOkksEkyj-ImVTqIDNL16JrZ25HHa-Yekw: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$JXgl4jz35cOkksEkyj-ImVTqIDNL16JrZ25HHa-Yekw +*_llgo_func$YjgNCugJxKXYLk39KOJyRyLtvcHU1d7KRz4inhHdVgg: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$YjgNCugJxKXYLk39KOJyRyLtvcHU1d7KRz4inhHdVgg +*_llgo_func$_JswjMs_mFNKWtFb56TJlZa479nBYWhoAxYBwUTwOyc: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$_JswjMs_mFNKWtFb56TJlZa479nBYWhoAxYBwUTwOyc +*_llgo_func$hV5ojfmZDe6MffI0ylemgDF5GZlZiYaJbQvka2A0Gus: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$hV5ojfmZDe6MffI0ylemgDF5GZlZiYaJbQvka2A0Gus +*_llgo_func$ibbVVENlZHDgol7ROnCbrRmXPuO818NXcpl5dfFoKhU: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$ibbVVENlZHDgol7ROnCbrRmXPuO818NXcpl5dfFoKhU +*_llgo_func$mBhSCdZCFK2IHVQVA73dFmon0gMcig2Q387khsbzmm8: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$mBhSCdZCFK2IHVQVA73dFmon0gMcig2Q387khsbzmm8 +*_llgo_func$mjkdaEUHPtpOYlUrWGfnskhhvdyc7k9Fk-vwWj3VftE: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$mjkdaEUHPtpOYlUrWGfnskhhvdyc7k9Fk-vwWj3VftE +*_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to +*_llgo_iface$J1wM-rGcIPemx5jloXBmH7pUzUCSqpgNkOdb0QIFTxw: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_iface$J1wM-rGcIPemx5jloXBmH7pUzUCSqpgNkOdb0QIFTxw +*_llgo_string: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_string +*_llgo_uint16: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_uint16 +*_llgo_uint32: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_uint32 +*_llgo_uint64: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_uint64 +*_llgo_uint8: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_uint8 +[]_llgo_uint8: + *[]_llgo_uint8 + _llgo_uint8 +__llgo_stub.encoding/binary.(*littleEndian).AppendUint16: + encoding/binary.(*littleEndian).AppendUint16 +__llgo_stub.encoding/binary.(*littleEndian).AppendUint32: + encoding/binary.(*littleEndian).AppendUint32 +__llgo_stub.encoding/binary.(*littleEndian).AppendUint64: + encoding/binary.(*littleEndian).AppendUint64 +__llgo_stub.encoding/binary.(*littleEndian).GoString: + encoding/binary.(*littleEndian).GoString +__llgo_stub.encoding/binary.(*littleEndian).PutUint16: + encoding/binary.(*littleEndian).PutUint16 +__llgo_stub.encoding/binary.(*littleEndian).PutUint32: + encoding/binary.(*littleEndian).PutUint32 +__llgo_stub.encoding/binary.(*littleEndian).PutUint64: + encoding/binary.(*littleEndian).PutUint64 +__llgo_stub.encoding/binary.(*littleEndian).String: + encoding/binary.(*littleEndian).String +__llgo_stub.encoding/binary.(*littleEndian).Uint16: + encoding/binary.(*littleEndian).Uint16 +__llgo_stub.encoding/binary.(*littleEndian).Uint32: + encoding/binary.(*littleEndian).Uint32 +__llgo_stub.encoding/binary.(*littleEndian).Uint64: + encoding/binary.(*littleEndian).Uint64 +__llgo_stub.encoding/binary.littleEndian.AppendUint16: + encoding/binary.littleEndian.AppendUint16 +__llgo_stub.encoding/binary.littleEndian.AppendUint32: + encoding/binary.littleEndian.AppendUint32 +__llgo_stub.encoding/binary.littleEndian.AppendUint64: + encoding/binary.littleEndian.AppendUint64 +__llgo_stub.encoding/binary.littleEndian.GoString: + encoding/binary.littleEndian.GoString +__llgo_stub.encoding/binary.littleEndian.PutUint16: + encoding/binary.littleEndian.PutUint16 +__llgo_stub.encoding/binary.littleEndian.PutUint32: + encoding/binary.littleEndian.PutUint32 +__llgo_stub.encoding/binary.littleEndian.PutUint64: + encoding/binary.littleEndian.PutUint64 +__llgo_stub.encoding/binary.littleEndian.String: + encoding/binary.littleEndian.String +__llgo_stub.encoding/binary.littleEndian.Uint16: + encoding/binary.littleEndian.Uint16 +__llgo_stub.encoding/binary.littleEndian.Uint32: + encoding/binary.littleEndian.Uint32 +__llgo_stub.encoding/binary.littleEndian.Uint64: + encoding/binary.littleEndian.Uint64 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal: + github.com/goplus/llgo/runtime/internal/runtime.interequal +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal0: + github.com/goplus/llgo/runtime/internal/runtime.memequal0 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal16: + github.com/goplus/llgo/runtime/internal/runtime.memequal16 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal32: + github.com/goplus/llgo/runtime/internal/runtime.memequal32 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal64: + github.com/goplus/llgo/runtime/internal/runtime.memequal64 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal8: + github.com/goplus/llgo/runtime/internal/runtime.memequal8 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr: + github.com/goplus/llgo/runtime/internal/runtime.memequalptr +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.strequal: + github.com/goplus/llgo/runtime/internal/runtime.strequal +_llgo_encoding/binary.littleEndian: + *_llgo_encoding/binary.littleEndian + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal0 +_llgo_func$HQem8FNvPqrEVQ_c0XssBDFXIDOnET_Ex7o3PlV9bSs: + *_llgo_func$HQem8FNvPqrEVQ_c0XssBDFXIDOnET_Ex7o3PlV9bSs + _llgo_func$HQem8FNvPqrEVQ_c0XssBDFXIDOnET_Ex7o3PlV9bSs$in + _llgo_func$HQem8FNvPqrEVQ_c0XssBDFXIDOnET_Ex7o3PlV9bSs$out +_llgo_func$HQem8FNvPqrEVQ_c0XssBDFXIDOnET_Ex7o3PlV9bSs$in: + []_llgo_uint8 + _llgo_uint64 +_llgo_func$HQem8FNvPqrEVQ_c0XssBDFXIDOnET_Ex7o3PlV9bSs$out: + []_llgo_uint8 +_llgo_func$ICfGJV9Kp4o-2SMi1iuyC9xBBX1c5utHD63uVbyrfEc: + *_llgo_func$ICfGJV9Kp4o-2SMi1iuyC9xBBX1c5utHD63uVbyrfEc + _llgo_func$ICfGJV9Kp4o-2SMi1iuyC9xBBX1c5utHD63uVbyrfEc$in + _llgo_func$ICfGJV9Kp4o-2SMi1iuyC9xBBX1c5utHD63uVbyrfEc$out +_llgo_func$ICfGJV9Kp4o-2SMi1iuyC9xBBX1c5utHD63uVbyrfEc$in: + []_llgo_uint8 +_llgo_func$ICfGJV9Kp4o-2SMi1iuyC9xBBX1c5utHD63uVbyrfEc$out: + _llgo_uint32 +_llgo_func$JXgl4jz35cOkksEkyj-ImVTqIDNL16JrZ25HHa-Yekw: + *_llgo_func$JXgl4jz35cOkksEkyj-ImVTqIDNL16JrZ25HHa-Yekw + _llgo_func$JXgl4jz35cOkksEkyj-ImVTqIDNL16JrZ25HHa-Yekw$in + _llgo_func$JXgl4jz35cOkksEkyj-ImVTqIDNL16JrZ25HHa-Yekw$out +_llgo_func$JXgl4jz35cOkksEkyj-ImVTqIDNL16JrZ25HHa-Yekw$in: + []_llgo_uint8 + _llgo_uint16 +_llgo_func$JXgl4jz35cOkksEkyj-ImVTqIDNL16JrZ25HHa-Yekw$out: + []_llgo_uint8 +_llgo_func$YjgNCugJxKXYLk39KOJyRyLtvcHU1d7KRz4inhHdVgg: + *_llgo_func$YjgNCugJxKXYLk39KOJyRyLtvcHU1d7KRz4inhHdVgg + _llgo_func$YjgNCugJxKXYLk39KOJyRyLtvcHU1d7KRz4inhHdVgg$in +_llgo_func$YjgNCugJxKXYLk39KOJyRyLtvcHU1d7KRz4inhHdVgg$in: + []_llgo_uint8 + _llgo_uint32 +_llgo_func$_JswjMs_mFNKWtFb56TJlZa479nBYWhoAxYBwUTwOyc: + *_llgo_func$_JswjMs_mFNKWtFb56TJlZa479nBYWhoAxYBwUTwOyc + _llgo_func$_JswjMs_mFNKWtFb56TJlZa479nBYWhoAxYBwUTwOyc$in + _llgo_func$_JswjMs_mFNKWtFb56TJlZa479nBYWhoAxYBwUTwOyc$out +_llgo_func$_JswjMs_mFNKWtFb56TJlZa479nBYWhoAxYBwUTwOyc$in: + []_llgo_uint8 + _llgo_uint32 +_llgo_func$_JswjMs_mFNKWtFb56TJlZa479nBYWhoAxYBwUTwOyc$out: + []_llgo_uint8 +_llgo_func$hV5ojfmZDe6MffI0ylemgDF5GZlZiYaJbQvka2A0Gus: + *_llgo_func$hV5ojfmZDe6MffI0ylemgDF5GZlZiYaJbQvka2A0Gus + _llgo_func$hV5ojfmZDe6MffI0ylemgDF5GZlZiYaJbQvka2A0Gus$in + _llgo_func$hV5ojfmZDe6MffI0ylemgDF5GZlZiYaJbQvka2A0Gus$out +_llgo_func$hV5ojfmZDe6MffI0ylemgDF5GZlZiYaJbQvka2A0Gus$in: + []_llgo_uint8 +_llgo_func$hV5ojfmZDe6MffI0ylemgDF5GZlZiYaJbQvka2A0Gus$out: + _llgo_uint16 +_llgo_func$ibbVVENlZHDgol7ROnCbrRmXPuO818NXcpl5dfFoKhU: + *_llgo_func$ibbVVENlZHDgol7ROnCbrRmXPuO818NXcpl5dfFoKhU + _llgo_func$ibbVVENlZHDgol7ROnCbrRmXPuO818NXcpl5dfFoKhU$in +_llgo_func$ibbVVENlZHDgol7ROnCbrRmXPuO818NXcpl5dfFoKhU$in: + []_llgo_uint8 + _llgo_uint16 +_llgo_func$mBhSCdZCFK2IHVQVA73dFmon0gMcig2Q387khsbzmm8: + *_llgo_func$mBhSCdZCFK2IHVQVA73dFmon0gMcig2Q387khsbzmm8 + _llgo_func$mBhSCdZCFK2IHVQVA73dFmon0gMcig2Q387khsbzmm8$in +_llgo_func$mBhSCdZCFK2IHVQVA73dFmon0gMcig2Q387khsbzmm8$in: + []_llgo_uint8 + _llgo_uint64 +_llgo_func$mjkdaEUHPtpOYlUrWGfnskhhvdyc7k9Fk-vwWj3VftE: + *_llgo_func$mjkdaEUHPtpOYlUrWGfnskhhvdyc7k9Fk-vwWj3VftE + _llgo_func$mjkdaEUHPtpOYlUrWGfnskhhvdyc7k9Fk-vwWj3VftE$in + _llgo_func$mjkdaEUHPtpOYlUrWGfnskhhvdyc7k9Fk-vwWj3VftE$out +_llgo_func$mjkdaEUHPtpOYlUrWGfnskhhvdyc7k9Fk-vwWj3VftE$in: + []_llgo_uint8 +_llgo_func$mjkdaEUHPtpOYlUrWGfnskhhvdyc7k9Fk-vwWj3VftE$out: + _llgo_uint64 +_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to: + *_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to$out +_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to$out: + _llgo_string +_llgo_iface$J1wM-rGcIPemx5jloXBmH7pUzUCSqpgNkOdb0QIFTxw: + *_llgo_iface$J1wM-rGcIPemx5jloXBmH7pUzUCSqpgNkOdb0QIFTxw + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal + _llgo_iface$J1wM-rGcIPemx5jloXBmH7pUzUCSqpgNkOdb0QIFTxw$imethods +_llgo_iface$J1wM-rGcIPemx5jloXBmH7pUzUCSqpgNkOdb0QIFTxw$imethods: + _llgo_func$ICfGJV9Kp4o-2SMi1iuyC9xBBX1c5utHD63uVbyrfEc + _llgo_func$YjgNCugJxKXYLk39KOJyRyLtvcHU1d7KRz4inhHdVgg + _llgo_func$hV5ojfmZDe6MffI0ylemgDF5GZlZiYaJbQvka2A0Gus + _llgo_func$ibbVVENlZHDgol7ROnCbrRmXPuO818NXcpl5dfFoKhU + _llgo_func$mBhSCdZCFK2IHVQVA73dFmon0gMcig2Q387khsbzmm8 + _llgo_func$mjkdaEUHPtpOYlUrWGfnskhhvdyc7k9Fk-vwWj3VftE + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to +_llgo_string: + *_llgo_string + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.strequal +_llgo_uint16: + *_llgo_uint16 + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal16 +_llgo_uint32: + *_llgo_uint32 + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal32 +_llgo_uint64: + *_llgo_uint64 + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal64 +_llgo_uint8: + *_llgo_uint8 + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal8 +github.com/goplus/llgo/cl/_testmeta/interface_exported_var.init: + encoding/binary.init + github.com/goplus/llgo/cl/_testmeta/interface_exported_var.init$guard +github.com/goplus/llgo/cl/_testmeta/interface_exported_var.main: + _llgo_encoding/binary.littleEndian + _llgo_iface$J1wM-rGcIPemx5jloXBmH7pUzUCSqpgNkOdb0QIFTxw + encoding/binary.LittleEndian + github.com/goplus/llgo/runtime/internal/runtime.AllocU + github.com/goplus/llgo/runtime/internal/runtime.AllocZ + github.com/goplus/llgo/runtime/internal/runtime.IfacePtrData + github.com/goplus/llgo/runtime/internal/runtime.NewItab + github.com/goplus/llgo/runtime/internal/runtime.Typedmemmove + +[UseIface] +github.com/goplus/llgo/cl/_testmeta/interface_exported_var.main: + _llgo_encoding/binary.littleEndian + +[UseIfaceMethod] +github.com/goplus/llgo/cl/_testmeta/interface_exported_var.main: + _llgo_iface$J1wM-rGcIPemx5jloXBmH7pUzUCSqpgNkOdb0QIFTxw Uint16 _llgo_func$hV5ojfmZDe6MffI0ylemgDF5GZlZiYaJbQvka2A0Gus + +[MethodInfo] +*_llgo_encoding/binary.littleEndian: + 0 AppendUint16 _llgo_func$JXgl4jz35cOkksEkyj-ImVTqIDNL16JrZ25HHa-Yekw encoding/binary.(*littleEndian).AppendUint16 __llgo_stub.encoding/binary.(*littleEndian).AppendUint16 + 1 AppendUint32 _llgo_func$_JswjMs_mFNKWtFb56TJlZa479nBYWhoAxYBwUTwOyc encoding/binary.(*littleEndian).AppendUint32 __llgo_stub.encoding/binary.(*littleEndian).AppendUint32 + 2 AppendUint64 _llgo_func$HQem8FNvPqrEVQ_c0XssBDFXIDOnET_Ex7o3PlV9bSs encoding/binary.(*littleEndian).AppendUint64 __llgo_stub.encoding/binary.(*littleEndian).AppendUint64 + 3 GoString _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to encoding/binary.(*littleEndian).GoString __llgo_stub.encoding/binary.(*littleEndian).GoString + 4 PutUint16 _llgo_func$ibbVVENlZHDgol7ROnCbrRmXPuO818NXcpl5dfFoKhU encoding/binary.(*littleEndian).PutUint16 __llgo_stub.encoding/binary.(*littleEndian).PutUint16 + 5 PutUint32 _llgo_func$YjgNCugJxKXYLk39KOJyRyLtvcHU1d7KRz4inhHdVgg encoding/binary.(*littleEndian).PutUint32 __llgo_stub.encoding/binary.(*littleEndian).PutUint32 + 6 PutUint64 _llgo_func$mBhSCdZCFK2IHVQVA73dFmon0gMcig2Q387khsbzmm8 encoding/binary.(*littleEndian).PutUint64 __llgo_stub.encoding/binary.(*littleEndian).PutUint64 + 7 String _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to encoding/binary.(*littleEndian).String __llgo_stub.encoding/binary.(*littleEndian).String + 8 Uint16 _llgo_func$hV5ojfmZDe6MffI0ylemgDF5GZlZiYaJbQvka2A0Gus encoding/binary.(*littleEndian).Uint16 __llgo_stub.encoding/binary.(*littleEndian).Uint16 + 9 Uint32 _llgo_func$ICfGJV9Kp4o-2SMi1iuyC9xBBX1c5utHD63uVbyrfEc encoding/binary.(*littleEndian).Uint32 __llgo_stub.encoding/binary.(*littleEndian).Uint32 + 10 Uint64 _llgo_func$mjkdaEUHPtpOYlUrWGfnskhhvdyc7k9Fk-vwWj3VftE encoding/binary.(*littleEndian).Uint64 __llgo_stub.encoding/binary.(*littleEndian).Uint64 +_llgo_encoding/binary.littleEndian: + 0 AppendUint16 _llgo_func$JXgl4jz35cOkksEkyj-ImVTqIDNL16JrZ25HHa-Yekw encoding/binary.(*littleEndian).AppendUint16 __llgo_stub.encoding/binary.littleEndian.AppendUint16 + 1 AppendUint32 _llgo_func$_JswjMs_mFNKWtFb56TJlZa479nBYWhoAxYBwUTwOyc encoding/binary.(*littleEndian).AppendUint32 __llgo_stub.encoding/binary.littleEndian.AppendUint32 + 2 AppendUint64 _llgo_func$HQem8FNvPqrEVQ_c0XssBDFXIDOnET_Ex7o3PlV9bSs encoding/binary.(*littleEndian).AppendUint64 __llgo_stub.encoding/binary.littleEndian.AppendUint64 + 3 GoString _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to encoding/binary.(*littleEndian).GoString __llgo_stub.encoding/binary.littleEndian.GoString + 4 PutUint16 _llgo_func$ibbVVENlZHDgol7ROnCbrRmXPuO818NXcpl5dfFoKhU encoding/binary.(*littleEndian).PutUint16 __llgo_stub.encoding/binary.littleEndian.PutUint16 + 5 PutUint32 _llgo_func$YjgNCugJxKXYLk39KOJyRyLtvcHU1d7KRz4inhHdVgg encoding/binary.(*littleEndian).PutUint32 __llgo_stub.encoding/binary.littleEndian.PutUint32 + 6 PutUint64 _llgo_func$mBhSCdZCFK2IHVQVA73dFmon0gMcig2Q387khsbzmm8 encoding/binary.(*littleEndian).PutUint64 __llgo_stub.encoding/binary.littleEndian.PutUint64 + 7 String _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to encoding/binary.(*littleEndian).String __llgo_stub.encoding/binary.littleEndian.String + 8 Uint16 _llgo_func$hV5ojfmZDe6MffI0ylemgDF5GZlZiYaJbQvka2A0Gus encoding/binary.(*littleEndian).Uint16 __llgo_stub.encoding/binary.littleEndian.Uint16 + 9 Uint32 _llgo_func$ICfGJV9Kp4o-2SMi1iuyC9xBBX1c5utHD63uVbyrfEc encoding/binary.(*littleEndian).Uint32 __llgo_stub.encoding/binary.littleEndian.Uint32 + 10 Uint64 _llgo_func$mjkdaEUHPtpOYlUrWGfnskhhvdyc7k9Fk-vwWj3VftE encoding/binary.(*littleEndian).Uint64 __llgo_stub.encoding/binary.littleEndian.Uint64 + +[InterfaceInfo] +_llgo_iface$J1wM-rGcIPemx5jloXBmH7pUzUCSqpgNkOdb0QIFTxw: + PutUint16 _llgo_func$ibbVVENlZHDgol7ROnCbrRmXPuO818NXcpl5dfFoKhU + PutUint32 _llgo_func$YjgNCugJxKXYLk39KOJyRyLtvcHU1d7KRz4inhHdVgg + PutUint64 _llgo_func$mBhSCdZCFK2IHVQVA73dFmon0gMcig2Q387khsbzmm8 + String _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to + Uint16 _llgo_func$hV5ojfmZDe6MffI0ylemgDF5GZlZiYaJbQvka2A0Gus + Uint32 _llgo_func$ICfGJV9Kp4o-2SMi1iuyC9xBBX1c5utHD63uVbyrfEc + Uint64 _llgo_func$mjkdaEUHPtpOYlUrWGfnskhhvdyc7k9Fk-vwWj3VftE + diff --git a/cl/_testmeta/interface_generic/in.go b/cl/_testmeta/interface_generic/in.go new file mode 100644 index 0000000000..ebf78edde5 --- /dev/null +++ b/cl/_testmeta/interface_generic/in.go @@ -0,0 +1,21 @@ +package main + +type Box[T any] struct { + value T +} + +func (b *Box[T]) Value() T { + return b.value +} + +type I[T any] interface { + Value() T +} + +func useInt(v I[int]) int { + return v.Value() +} + +func main() { + _ = useInt(&Box[int]{value: 42}) +} diff --git a/cl/_testmeta/interface_generic/meta-expect.txt b/cl/_testmeta/interface_generic/meta-expect.txt new file mode 100644 index 0000000000..d1ddbb64fa --- /dev/null +++ b/cl/_testmeta/interface_generic/meta-expect.txt @@ -0,0 +1,84 @@ +[TypeChildren] +*_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA: + _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic.Box[int]: + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic.Box[int] +*_llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8: + _llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8 +*_llgo_int: + _llgo_int +_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA: + _llgo_int +_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic.Box[int]: + _llgo_int +_llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8: + _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA + +[OrdinaryEdges] +*_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic.Box[int]: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic.Box[int] +*_llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8 +*_llgo_int: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_int +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_generic.(*Box[int]).Value: + github.com/goplus/llgo/cl/_testmeta/interface_generic.(*Box[int]).Value +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal: + github.com/goplus/llgo/runtime/internal/runtime.interequal +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal64: + github.com/goplus/llgo/runtime/internal/runtime.memequal64 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr: + github.com/goplus/llgo/runtime/internal/runtime.memequalptr +_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA: + *_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA + _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA$out +_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA$out: + _llgo_int +_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic.Box[int]: + *_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic.Box[int] + github.com/goplus/llgo/cl/_testmeta/interface_generic.struct$lOhriNu2BrWBR2Mh8k-KggMYlAw0Wx-2ftE2_gNyubg$fields + github.com/goplus/llgo/runtime/internal/runtime.structequal +_llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8: + *_llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8 + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal + _llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8$imethods +_llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8$imethods: + _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA +_llgo_int: + *_llgo_int + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal64 +github.com/goplus/llgo/cl/_testmeta/interface_generic.init: + github.com/goplus/llgo/cl/_testmeta/interface_generic.init$guard +github.com/goplus/llgo/cl/_testmeta/interface_generic.main: + *_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic.Box[int] + _llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8 + github.com/goplus/llgo/cl/_testmeta/interface_generic.useInt + github.com/goplus/llgo/runtime/internal/runtime.AllocZ + github.com/goplus/llgo/runtime/internal/runtime.NewItab +github.com/goplus/llgo/cl/_testmeta/interface_generic.struct$lOhriNu2BrWBR2Mh8k-KggMYlAw0Wx-2ftE2_gNyubg$fields: + _llgo_int +github.com/goplus/llgo/cl/_testmeta/interface_generic.useInt: + github.com/goplus/llgo/runtime/internal/runtime.IfacePtrData + +[UseIface] +github.com/goplus/llgo/cl/_testmeta/interface_generic.main: + *_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic.Box[int] + +[UseIfaceMethod] +github.com/goplus/llgo/cl/_testmeta/interface_generic.useInt: + _llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8 Value _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA + +[MethodInfo] +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic.Box[int]: + 0 Value _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA github.com/goplus/llgo/cl/_testmeta/interface_generic.(*Box[int]).Value __llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_generic.(*Box[int]).Value + +[InterfaceInfo] +_llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8: + Value _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA + diff --git a/cl/_testmeta/interface_generic_crosspkg/api/api.go b/cl/_testmeta/interface_generic_crosspkg/api/api.go new file mode 100644 index 0000000000..7e9c6a4ae1 --- /dev/null +++ b/cl/_testmeta/interface_generic_crosspkg/api/api.go @@ -0,0 +1,9 @@ +package api + +type I[T any] interface { + Value() T +} + +func UseInt(v I[int]) int { + return v.Value() +} diff --git a/cl/_testmeta/interface_generic_crosspkg/api/meta-expect.txt b/cl/_testmeta/interface_generic_crosspkg/api/meta-expect.txt new file mode 100644 index 0000000000..69a0b8ba7a --- /dev/null +++ b/cl/_testmeta/interface_generic_crosspkg/api/meta-expect.txt @@ -0,0 +1,14 @@ +[OrdinaryEdges] +github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/api.UseInt: + github.com/goplus/llgo/runtime/internal/runtime.IfacePtrData +github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/api.init: + github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/api.init$guard + +[UseIfaceMethod] +github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/api.UseInt: + _llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8 Value _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA + +[InterfaceInfo] +_llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8: + Value _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA + diff --git a/cl/_testmeta/interface_generic_crosspkg/in.go b/cl/_testmeta/interface_generic_crosspkg/in.go new file mode 100644 index 0000000000..bee45b9fbe --- /dev/null +++ b/cl/_testmeta/interface_generic_crosspkg/in.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/api" + "github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model" +) + +var sink any + +func main() { + n := api.UseInt(model.NewIntBox(40)) + text := model.NewStringBox("go") + sink = text + println(n + model.UseStringBox(text)) +} diff --git a/cl/_testmeta/interface_generic_crosspkg/meta-expect.txt b/cl/_testmeta/interface_generic_crosspkg/meta-expect.txt new file mode 100644 index 0000000000..914b8ba2e0 --- /dev/null +++ b/cl/_testmeta/interface_generic_crosspkg/meta-expect.txt @@ -0,0 +1,140 @@ +[TypeChildren] +*_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA: + _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA +*_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to: + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[int]: + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[int] +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[string]: + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[string] +*_llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8: + _llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8 +*_llgo_int: + _llgo_int +*_llgo_string: + _llgo_string +_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA: + _llgo_int +_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to: + _llgo_string +_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[int]: + _llgo_int +_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[string]: + _llgo_string +_llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8: + _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA + +[OrdinaryEdges] +*_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA +*_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[int]: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[int] +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[string]: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[string] +*_llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8 +*_llgo_int: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_int +*_llgo_string: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_string +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[int]).Drop: + github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[int]).Drop +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[int]).Value: + github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[int]).Value +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[string]).Drop: + github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[string]).Drop +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[string]).Value: + github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[string]).Value +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal: + github.com/goplus/llgo/runtime/internal/runtime.interequal +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal64: + github.com/goplus/llgo/runtime/internal/runtime.memequal64 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr: + github.com/goplus/llgo/runtime/internal/runtime.memequalptr +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.strequal: + github.com/goplus/llgo/runtime/internal/runtime.strequal +_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA: + *_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA + _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA$out +_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA$out: + _llgo_int +_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to: + *_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to$out +_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to$out: + _llgo_string +_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[int]: + *_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[int] + github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.struct$lOhriNu2BrWBR2Mh8k-KggMYlAw0Wx-2ftE2_gNyubg$fields + github.com/goplus/llgo/runtime/internal/runtime.structequal +_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[string]: + *_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[string] + github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.struct$XNpHe-3-A25gBmHjYdF74H8M4k3Eik680huOzY6OtaU$fields + github.com/goplus/llgo/runtime/internal/runtime.structequal +_llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8: + *_llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8 + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal + _llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8$imethods +_llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8$imethods: + _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA +_llgo_int: + *_llgo_int + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal64 +_llgo_string: + *_llgo_string + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.strequal +github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg.init: + github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg.init$guard + github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/api.init + github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.init +github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg.main: + *_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[int] + *_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[string] + _llgo_iface$Jvxc0PCI_drlfK7S5npMGdZkQLeRkQ_x2e2CifPE6w8 + github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg.sink + github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/api.UseInt + github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.NewIntBox + github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.NewStringBox + github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.UseStringBox + github.com/goplus/llgo/runtime/internal/runtime.NewItab + github.com/goplus/llgo/runtime/internal/runtime.PrintByte + github.com/goplus/llgo/runtime/internal/runtime.PrintInt +github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[int]).Drop: + _llgo_string + github.com/goplus/llgo/runtime/internal/runtime.AllocU + github.com/goplus/llgo/runtime/internal/runtime.Panic +github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[string]).Drop: + _llgo_string + github.com/goplus/llgo/runtime/internal/runtime.AllocU + github.com/goplus/llgo/runtime/internal/runtime.Panic +github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.struct$XNpHe-3-A25gBmHjYdF74H8M4k3Eik680huOzY6OtaU$fields: + _llgo_string +github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.struct$lOhriNu2BrWBR2Mh8k-KggMYlAw0Wx-2ftE2_gNyubg$fields: + _llgo_int + +[UseIface] +github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg.main: + *_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[int] + *_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[string] +github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[int]).Drop: + _llgo_string +github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[string]).Drop: + _llgo_string + +[MethodInfo] +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[int]: + 0 Drop _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[int]).Drop __llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[int]).Drop + 1 Value _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[int]).Value __llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[int]).Value +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.Box[string]: + 0 Drop _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[string]).Drop __llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[string]).Drop + 1 Value _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[string]).Value __llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.(*Box[string]).Value + diff --git a/cl/_testmeta/interface_generic_crosspkg/model/meta-expect.txt b/cl/_testmeta/interface_generic_crosspkg/model/meta-expect.txt new file mode 100644 index 0000000000..094b8c759e --- /dev/null +++ b/cl/_testmeta/interface_generic_crosspkg/model/meta-expect.txt @@ -0,0 +1,8 @@ +[OrdinaryEdges] +github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.NewIntBox: + github.com/goplus/llgo/runtime/internal/runtime.AllocZ +github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.NewStringBox: + github.com/goplus/llgo/runtime/internal/runtime.AllocZ +github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.init: + github.com/goplus/llgo/cl/_testmeta/interface_generic_crosspkg/model.init$guard + diff --git a/cl/_testmeta/interface_generic_crosspkg/model/model.go b/cl/_testmeta/interface_generic_crosspkg/model/model.go new file mode 100644 index 0000000000..d9182b41f7 --- /dev/null +++ b/cl/_testmeta/interface_generic_crosspkg/model/model.go @@ -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") +} diff --git a/cl/_testmeta/interface_imported/api/api.go b/cl/_testmeta/interface_imported/api/api.go new file mode 100644 index 0000000000..e6ce2af1bc --- /dev/null +++ b/cl/_testmeta/interface_imported/api/api.go @@ -0,0 +1,15 @@ +package api + +type Reader interface { + Read([]byte) (int, error) +} + +type Source struct{} + +func (Source) Read([]byte) (int, error) { + return 7, nil +} + +func (Source) Close() error { + return nil +} diff --git a/cl/_testmeta/interface_imported/api/meta-expect.txt b/cl/_testmeta/interface_imported/api/meta-expect.txt new file mode 100644 index 0000000000..e30a53db60 --- /dev/null +++ b/cl/_testmeta/interface_imported/api/meta-expect.txt @@ -0,0 +1,12 @@ +[OrdinaryEdges] +github.com/goplus/llgo/cl/_testmeta/interface_imported/api.(*Source).Close: + github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source.Close + github.com/goplus/llgo/runtime/internal/runtime.AssertNilDeref + github.com/goplus/llgo/runtime/internal/runtime.PanicWrapNilPointer +github.com/goplus/llgo/cl/_testmeta/interface_imported/api.(*Source).Read: + github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source.Read + github.com/goplus/llgo/runtime/internal/runtime.AssertNilDeref + github.com/goplus/llgo/runtime/internal/runtime.PanicWrapNilPointer +github.com/goplus/llgo/cl/_testmeta/interface_imported/api.init: + github.com/goplus/llgo/cl/_testmeta/interface_imported/api.init$guard + diff --git a/cl/_testmeta/interface_imported/in.go b/cl/_testmeta/interface_imported/in.go new file mode 100644 index 0000000000..d54efd2f07 --- /dev/null +++ b/cl/_testmeta/interface_imported/in.go @@ -0,0 +1,12 @@ +package main + +import "github.com/goplus/llgo/cl/_testmeta/interface_imported/api" + +func use(r api.Reader) int { + n, _ := r.Read(nil) + return n +} + +func main() { + _ = use(api.Source{}) +} diff --git a/cl/_testmeta/interface_imported/meta-expect.txt b/cl/_testmeta/interface_imported/meta-expect.txt new file mode 100644 index 0000000000..91ec9613ac --- /dev/null +++ b/cl/_testmeta/interface_imported/meta-expect.txt @@ -0,0 +1,165 @@ +[TypeChildren] +*[]_llgo_uint8: + []_llgo_uint8 +*_llgo_error: + _llgo_error +*_llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w: + _llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w +*_llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk: + _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk +*_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to: + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source: + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source +*_llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw: + _llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw +*_llgo_int: + _llgo_int +*_llgo_string: + _llgo_string +*_llgo_uint8: + _llgo_uint8 +[]_llgo_uint8: + _llgo_uint8 +_llgo_error: + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to +_llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w: + _llgo_error +_llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk: + []_llgo_uint8 + _llgo_error + _llgo_int +_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to: + _llgo_string +_llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw: + _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk + +[OrdinaryEdges] +*[]_llgo_uint8: + []_llgo_uint8 + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr +*_llgo_error: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_error +*_llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w +*_llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk +*_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source +*_llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw +*_llgo_int: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_int +*_llgo_string: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_string +*_llgo_uint8: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_uint8 +[]_llgo_uint8: + *[]_llgo_uint8 + _llgo_uint8 +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_imported/api.(*Source).Close: + github.com/goplus/llgo/cl/_testmeta/interface_imported/api.(*Source).Close +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_imported/api.(*Source).Read: + github.com/goplus/llgo/cl/_testmeta/interface_imported/api.(*Source).Read +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source.Close: + github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source.Close +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source.Read: + github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source.Read +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal: + github.com/goplus/llgo/runtime/internal/runtime.interequal +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal0: + github.com/goplus/llgo/runtime/internal/runtime.memequal0 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal64: + github.com/goplus/llgo/runtime/internal/runtime.memequal64 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal8: + github.com/goplus/llgo/runtime/internal/runtime.memequal8 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr: + github.com/goplus/llgo/runtime/internal/runtime.memequalptr +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.strequal: + github.com/goplus/llgo/runtime/internal/runtime.strequal +_llgo_error: + *_llgo_error + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal + _llgo_error$imethods +_llgo_error$imethods: + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to +_llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w: + *_llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w + _llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w$out +_llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w$out: + _llgo_error +_llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk: + *_llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk + _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk$in + _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk$out +_llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk$in: + []_llgo_uint8 +_llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk$out: + _llgo_error + _llgo_int +_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to: + *_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to$out +_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to$out: + _llgo_string +_llgo_github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source: + *_llgo_github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal0 +_llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw: + *_llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal + _llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw$imethods +_llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw$imethods: + _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk +_llgo_int: + *_llgo_int + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal64 +_llgo_string: + *_llgo_string + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.strequal +_llgo_uint8: + *_llgo_uint8 + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal8 +github.com/goplus/llgo/cl/_testmeta/interface_imported.init: + github.com/goplus/llgo/cl/_testmeta/interface_imported.init$guard + github.com/goplus/llgo/cl/_testmeta/interface_imported/api.init +github.com/goplus/llgo/cl/_testmeta/interface_imported.main: + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source + _llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw + github.com/goplus/llgo/cl/_testmeta/interface_imported.use + github.com/goplus/llgo/runtime/internal/runtime.AllocU + github.com/goplus/llgo/runtime/internal/runtime.NewItab +github.com/goplus/llgo/cl/_testmeta/interface_imported.use: + github.com/goplus/llgo/runtime/internal/runtime.IfacePtrData + +[UseIface] +github.com/goplus/llgo/cl/_testmeta/interface_imported.main: + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source + +[UseIfaceMethod] +github.com/goplus/llgo/cl/_testmeta/interface_imported.use: + _llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw Read _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk + +[MethodInfo] +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source: + 0 Close _llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w github.com/goplus/llgo/cl/_testmeta/interface_imported/api.(*Source).Close __llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_imported/api.(*Source).Close + 1 Read _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk github.com/goplus/llgo/cl/_testmeta/interface_imported/api.(*Source).Read __llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_imported/api.(*Source).Read +_llgo_github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source: + 0 Close _llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w github.com/goplus/llgo/cl/_testmeta/interface_imported/api.(*Source).Close __llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source.Close + 1 Read _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk github.com/goplus/llgo/cl/_testmeta/interface_imported/api.(*Source).Read __llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_imported/api.Source.Read + +[InterfaceInfo] +_llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw: + Read _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk + diff --git a/cl/_testmeta/interface_named/in.go b/cl/_testmeta/interface_named/in.go new file mode 100644 index 0000000000..dc91d644f0 --- /dev/null +++ b/cl/_testmeta/interface_named/in.go @@ -0,0 +1,17 @@ +package main + +type I interface { + M() +} + +type T struct{} + +func (T) M() {} + +func use(v I) { + v.M() +} + +func main() { + use(T{}) +} diff --git a/cl/_testmeta/interface_named/meta-expect.txt b/cl/_testmeta/interface_named/meta-expect.txt new file mode 100644 index 0000000000..e13b51bb7c --- /dev/null +++ b/cl/_testmeta/interface_named/meta-expect.txt @@ -0,0 +1,74 @@ +[TypeChildren] +*_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_named.T: + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_named.T +*_llgo_iface$anEWstLioBmxcO9rxTXClbAzDIEQLw2tApwq0mcSt88: + _llgo_iface$anEWstLioBmxcO9rxTXClbAzDIEQLw2tApwq0mcSt88 +_llgo_iface$anEWstLioBmxcO9rxTXClbAzDIEQLw2tApwq0mcSt88: + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac + +[OrdinaryEdges] +*_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_named.T: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_named.T +*_llgo_iface$anEWstLioBmxcO9rxTXClbAzDIEQLw2tApwq0mcSt88: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_iface$anEWstLioBmxcO9rxTXClbAzDIEQLw2tApwq0mcSt88 +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_named.(*T).M: + github.com/goplus/llgo/cl/_testmeta/interface_named.(*T).M +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_named.T.M: + github.com/goplus/llgo/cl/_testmeta/interface_named.T.M +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal: + github.com/goplus/llgo/runtime/internal/runtime.interequal +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal0: + github.com/goplus/llgo/runtime/internal/runtime.memequal0 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr: + github.com/goplus/llgo/runtime/internal/runtime.memequalptr +_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + *_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +_llgo_github.com/goplus/llgo/cl/_testmeta/interface_named.T: + *_llgo_github.com/goplus/llgo/cl/_testmeta/interface_named.T + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal0 +_llgo_iface$anEWstLioBmxcO9rxTXClbAzDIEQLw2tApwq0mcSt88: + *_llgo_iface$anEWstLioBmxcO9rxTXClbAzDIEQLw2tApwq0mcSt88 + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal + _llgo_iface$anEWstLioBmxcO9rxTXClbAzDIEQLw2tApwq0mcSt88$imethods +_llgo_iface$anEWstLioBmxcO9rxTXClbAzDIEQLw2tApwq0mcSt88$imethods: + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +github.com/goplus/llgo/cl/_testmeta/interface_named.(*T).M: + github.com/goplus/llgo/cl/_testmeta/interface_named.T.M + github.com/goplus/llgo/runtime/internal/runtime.AssertNilDeref + github.com/goplus/llgo/runtime/internal/runtime.PanicWrapNilPointer +github.com/goplus/llgo/cl/_testmeta/interface_named.init: + github.com/goplus/llgo/cl/_testmeta/interface_named.init$guard +github.com/goplus/llgo/cl/_testmeta/interface_named.main: + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_named.T + _llgo_iface$anEWstLioBmxcO9rxTXClbAzDIEQLw2tApwq0mcSt88 + github.com/goplus/llgo/cl/_testmeta/interface_named.use + github.com/goplus/llgo/runtime/internal/runtime.AllocU + github.com/goplus/llgo/runtime/internal/runtime.NewItab +github.com/goplus/llgo/cl/_testmeta/interface_named.use: + github.com/goplus/llgo/runtime/internal/runtime.IfacePtrData + +[UseIface] +github.com/goplus/llgo/cl/_testmeta/interface_named.main: + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_named.T + +[UseIfaceMethod] +github.com/goplus/llgo/cl/_testmeta/interface_named.use: + _llgo_iface$anEWstLioBmxcO9rxTXClbAzDIEQLw2tApwq0mcSt88 M _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac + +[MethodInfo] +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_named.T: + 0 M _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac github.com/goplus/llgo/cl/_testmeta/interface_named.(*T).M __llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_named.(*T).M +_llgo_github.com/goplus/llgo/cl/_testmeta/interface_named.T: + 0 M _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac github.com/goplus/llgo/cl/_testmeta/interface_named.(*T).M __llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_named.T.M + +[InterfaceInfo] +_llgo_iface$anEWstLioBmxcO9rxTXClbAzDIEQLw2tApwq0mcSt88: + M _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac + diff --git a/cl/_testmeta/interface_unexported/in.go b/cl/_testmeta/interface_unexported/in.go new file mode 100644 index 0000000000..18778f7891 --- /dev/null +++ b/cl/_testmeta/interface_unexported/in.go @@ -0,0 +1,17 @@ +package main + +type I interface { + m() +} + +type T struct{} + +func (T) m() {} + +func use(v I) { + v.m() +} + +func main() { + use(T{}) +} diff --git a/cl/_testmeta/interface_unexported/meta-expect.txt b/cl/_testmeta/interface_unexported/meta-expect.txt new file mode 100644 index 0000000000..26ce33099c --- /dev/null +++ b/cl/_testmeta/interface_unexported/meta-expect.txt @@ -0,0 +1,74 @@ +[TypeChildren] +*_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_unexported.T: + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_unexported.T +*github.com/goplus/llgo/cl/_testmeta/interface_unexported.iface$SE5y-KS93u1u9p1qAf9LRB1hncS44rJIq27_JZbIQVo: + github.com/goplus/llgo/cl/_testmeta/interface_unexported.iface$SE5y-KS93u1u9p1qAf9LRB1hncS44rJIq27_JZbIQVo +github.com/goplus/llgo/cl/_testmeta/interface_unexported.iface$SE5y-KS93u1u9p1qAf9LRB1hncS44rJIq27_JZbIQVo: + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac + +[OrdinaryEdges] +*_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_unexported.T: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_unexported.T +*github.com/goplus/llgo/cl/_testmeta/interface_unexported.iface$SE5y-KS93u1u9p1qAf9LRB1hncS44rJIq27_JZbIQVo: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + github.com/goplus/llgo/cl/_testmeta/interface_unexported.iface$SE5y-KS93u1u9p1qAf9LRB1hncS44rJIq27_JZbIQVo +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_unexported.(*T).m: + github.com/goplus/llgo/cl/_testmeta/interface_unexported.(*T).m +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_unexported.T.m: + github.com/goplus/llgo/cl/_testmeta/interface_unexported.T.m +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal: + github.com/goplus/llgo/runtime/internal/runtime.interequal +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal0: + github.com/goplus/llgo/runtime/internal/runtime.memequal0 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr: + github.com/goplus/llgo/runtime/internal/runtime.memequalptr +_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + *_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +_llgo_github.com/goplus/llgo/cl/_testmeta/interface_unexported.T: + *_llgo_github.com/goplus/llgo/cl/_testmeta/interface_unexported.T + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal0 +github.com/goplus/llgo/cl/_testmeta/interface_unexported.(*T).m: + github.com/goplus/llgo/cl/_testmeta/interface_unexported.T.m + github.com/goplus/llgo/runtime/internal/runtime.AssertNilDeref + github.com/goplus/llgo/runtime/internal/runtime.PanicWrapNilPointer +github.com/goplus/llgo/cl/_testmeta/interface_unexported.iface$SE5y-KS93u1u9p1qAf9LRB1hncS44rJIq27_JZbIQVo: + *github.com/goplus/llgo/cl/_testmeta/interface_unexported.iface$SE5y-KS93u1u9p1qAf9LRB1hncS44rJIq27_JZbIQVo + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal + github.com/goplus/llgo/cl/_testmeta/interface_unexported.iface$SE5y-KS93u1u9p1qAf9LRB1hncS44rJIq27_JZbIQVo$imethods +github.com/goplus/llgo/cl/_testmeta/interface_unexported.iface$SE5y-KS93u1u9p1qAf9LRB1hncS44rJIq27_JZbIQVo$imethods: + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +github.com/goplus/llgo/cl/_testmeta/interface_unexported.init: + github.com/goplus/llgo/cl/_testmeta/interface_unexported.init$guard +github.com/goplus/llgo/cl/_testmeta/interface_unexported.main: + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_unexported.T + github.com/goplus/llgo/cl/_testmeta/interface_unexported.iface$SE5y-KS93u1u9p1qAf9LRB1hncS44rJIq27_JZbIQVo + github.com/goplus/llgo/cl/_testmeta/interface_unexported.use + github.com/goplus/llgo/runtime/internal/runtime.AllocU + github.com/goplus/llgo/runtime/internal/runtime.NewItab +github.com/goplus/llgo/cl/_testmeta/interface_unexported.use: + github.com/goplus/llgo/runtime/internal/runtime.IfacePtrData + +[UseIface] +github.com/goplus/llgo/cl/_testmeta/interface_unexported.main: + _llgo_github.com/goplus/llgo/cl/_testmeta/interface_unexported.T + +[UseIfaceMethod] +github.com/goplus/llgo/cl/_testmeta/interface_unexported.use: + github.com/goplus/llgo/cl/_testmeta/interface_unexported.iface$SE5y-KS93u1u9p1qAf9LRB1hncS44rJIq27_JZbIQVo github.com/goplus/llgo/cl/_testmeta/interface_unexported.m _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac + +[MethodInfo] +*_llgo_github.com/goplus/llgo/cl/_testmeta/interface_unexported.T: + 0 github.com/goplus/llgo/cl/_testmeta/interface_unexported.m _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac github.com/goplus/llgo/cl/_testmeta/interface_unexported.(*T).m __llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_unexported.(*T).m +_llgo_github.com/goplus/llgo/cl/_testmeta/interface_unexported.T: + 0 github.com/goplus/llgo/cl/_testmeta/interface_unexported.m _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac github.com/goplus/llgo/cl/_testmeta/interface_unexported.(*T).m __llgo_stub.github.com/goplus/llgo/cl/_testmeta/interface_unexported.T.m + +[InterfaceInfo] +github.com/goplus/llgo/cl/_testmeta/interface_unexported.iface$SE5y-KS93u1u9p1qAf9LRB1hncS44rJIq27_JZbIQVo: + github.com/goplus/llgo/cl/_testmeta/interface_unexported.m _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac + diff --git a/cl/_testmeta/methodinfo_imported/in.go b/cl/_testmeta/methodinfo_imported/in.go new file mode 100644 index 0000000000..b243bd18fb --- /dev/null +++ b/cl/_testmeta/methodinfo_imported/in.go @@ -0,0 +1,11 @@ +package main + +import ( + "bytes" + "io" +) + +func main() { + var r io.Reader = bytes.NewBuffer(nil) + _, _ = r.Read(nil) +} diff --git a/cl/_testmeta/methodinfo_imported/meta-expect.txt b/cl/_testmeta/methodinfo_imported/meta-expect.txt new file mode 100644 index 0000000000..49a0fe2c86 --- /dev/null +++ b/cl/_testmeta/methodinfo_imported/meta-expect.txt @@ -0,0 +1,555 @@ +[TypeChildren] +*[]_llgo_uint8: + []_llgo_uint8 +*_llgo_bool: + _llgo_bool +*_llgo_bytes.Buffer: + _llgo_bytes.Buffer +*_llgo_bytes.readOp: + _llgo_bytes.readOp +*_llgo_error: + _llgo_error +*_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +*_llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w: + _llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w +*_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA: + _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA +*_llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk: + _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk +*_llgo_func$TBlCn7YTQdraI1HMiBWmkrqIGG-8UgD1UVyJy62Z_0o: + _llgo_func$TBlCn7YTQdraI1HMiBWmkrqIGG-8UgD1UVyJy62Z_0o +*_llgo_func$VZ-8VPNF1RaLICwxc1Ghn7BbgyFX3v762OCdx127EkA: + _llgo_func$VZ-8VPNF1RaLICwxc1Ghn7BbgyFX3v762OCdx127EkA +*_llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk: + _llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk +*_llgo_func$Z_-7GWzB37LCYRTQLsSYmEihg_hqBK8o_GbT88pqnPY: + _llgo_func$Z_-7GWzB37LCYRTQLsSYmEihg_hqBK8o_GbT88pqnPY +*_llgo_func$aJkaU3jhXr0Q2QraTe2_TTdupeMMW2MD66UwBxynRM0: + _llgo_func$aJkaU3jhXr0Q2QraTe2_TTdupeMMW2MD66UwBxynRM0 +*_llgo_func$d4kMA_oCkLwnd1j8nVlv1hwRarEVuCIrDCpnHhDz9UY: + _llgo_func$d4kMA_oCkLwnd1j8nVlv1hwRarEVuCIrDCpnHhDz9UY +*_llgo_func$ekGNsrYBSzltfAjxbl6T8H6Yq8j16wzqS3nDj2xxGMU: + _llgo_func$ekGNsrYBSzltfAjxbl6T8H6Yq8j16wzqS3nDj2xxGMU +*_llgo_func$lukqSsfDYBoIp_R8GMojGkZnrYDqaq2iHn8RkCjW7iQ: + _llgo_func$lukqSsfDYBoIp_R8GMojGkZnrYDqaq2iHn8RkCjW7iQ +*_llgo_func$q-bw-_pPYBCXnr1TXIF8sOD4fVVzzIlpHqD-A13AB4Y: + _llgo_func$q-bw-_pPYBCXnr1TXIF8sOD4fVVzzIlpHqD-A13AB4Y +*_llgo_func$qVJ5SH6qhXP_h0AM41vpBGzQEMp-fQIfvwQEJy5NI8M: + _llgo_func$qVJ5SH6qhXP_h0AM41vpBGzQEMp-fQIfvwQEJy5NI8M +*_llgo_func$thH5FBpdXzJNnCpSfiLU5ItTntFU6LWp0RJhDm2XJjw: + _llgo_func$thH5FBpdXzJNnCpSfiLU5ItTntFU6LWp0RJhDm2XJjw +*_llgo_func$uVmBDI0DMcrui3Q9y-g_hbtVN8JckQ18V2wmO5_G7A8: + _llgo_func$uVmBDI0DMcrui3Q9y-g_hbtVN8JckQ18V2wmO5_G7A8 +*_llgo_func$uf8yw1UkUdbDuCneSpNKIq_NThWIEVE7f1IYfJGz_bw: + _llgo_func$uf8yw1UkUdbDuCneSpNKIq_NThWIEVE7f1IYfJGz_bw +*_llgo_func$vSv85k0UY6JWccAc3T-lvdCx9J-4GM-oZC9zGLrxW1M: + _llgo_func$vSv85k0UY6JWccAc3T-lvdCx9J-4GM-oZC9zGLrxW1M +*_llgo_func$w4tN9iibS_UimF5vLUWoKP0uAk2tJZF26VqETo_8LVg: + _llgo_func$w4tN9iibS_UimF5vLUWoKP0uAk2tJZF26VqETo_8LVg +*_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to: + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to +*_llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw: + _llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw +*_llgo_int: + _llgo_int +*_llgo_int32: + _llgo_int32 +*_llgo_int64: + _llgo_int64 +*_llgo_io.Reader: + _llgo_io.Reader +*_llgo_io.Writer: + _llgo_io.Writer +*_llgo_string: + _llgo_string +*_llgo_uint8: + _llgo_uint8 +[]_llgo_uint8: + _llgo_uint8 +_llgo_bytes.Buffer: + []_llgo_uint8 + _llgo_bytes.readOp + _llgo_int +_llgo_error: + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to +_llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w: + _llgo_error +_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA: + _llgo_int +_llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk: + []_llgo_uint8 + _llgo_error + _llgo_int +_llgo_func$TBlCn7YTQdraI1HMiBWmkrqIGG-8UgD1UVyJy62Z_0o: + _llgo_error + _llgo_string + _llgo_uint8 +_llgo_func$VZ-8VPNF1RaLICwxc1Ghn7BbgyFX3v762OCdx127EkA: + _llgo_int +_llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk: + _llgo_bool +_llgo_func$Z_-7GWzB37LCYRTQLsSYmEihg_hqBK8o_GbT88pqnPY: + []_llgo_uint8 +_llgo_func$aJkaU3jhXr0Q2QraTe2_TTdupeMMW2MD66UwBxynRM0: + []_llgo_uint8 + _llgo_error + _llgo_uint8 +_llgo_func$d4kMA_oCkLwnd1j8nVlv1hwRarEVuCIrDCpnHhDz9UY: + []_llgo_uint8 + _llgo_int +_llgo_func$ekGNsrYBSzltfAjxbl6T8H6Yq8j16wzqS3nDj2xxGMU: + _llgo_int +_llgo_func$lukqSsfDYBoIp_R8GMojGkZnrYDqaq2iHn8RkCjW7iQ: + _llgo_error + _llgo_uint8 +_llgo_func$q-bw-_pPYBCXnr1TXIF8sOD4fVVzzIlpHqD-A13AB4Y: + _llgo_error + _llgo_int + _llgo_int32 +_llgo_func$qVJ5SH6qhXP_h0AM41vpBGzQEMp-fQIfvwQEJy5NI8M: + _llgo_bool + _llgo_int +_llgo_func$thH5FBpdXzJNnCpSfiLU5ItTntFU6LWp0RJhDm2XJjw: + _llgo_error + _llgo_int + _llgo_string +_llgo_func$uVmBDI0DMcrui3Q9y-g_hbtVN8JckQ18V2wmO5_G7A8: + _llgo_error + _llgo_int64 + _llgo_io.Reader +_llgo_func$uf8yw1UkUdbDuCneSpNKIq_NThWIEVE7f1IYfJGz_bw: + _llgo_error + _llgo_int + _llgo_int32 +_llgo_func$vSv85k0UY6JWccAc3T-lvdCx9J-4GM-oZC9zGLrxW1M: + _llgo_error + _llgo_int64 + _llgo_io.Writer +_llgo_func$w4tN9iibS_UimF5vLUWoKP0uAk2tJZF26VqETo_8LVg: + _llgo_error + _llgo_uint8 +_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to: + _llgo_string +_llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw: + _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk +_llgo_io.Reader: + _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk +_llgo_io.Writer: + _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk + +[OrdinaryEdges] +*[]_llgo_uint8: + []_llgo_uint8 + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr +*_llgo_bool: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_bool +*_llgo_bytes.Buffer: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_bytes.Buffer +*_llgo_bytes.readOp: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_bytes.readOp +*_llgo_error: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_error +*_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +*_llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w +*_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA +*_llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk +*_llgo_func$TBlCn7YTQdraI1HMiBWmkrqIGG-8UgD1UVyJy62Z_0o: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$TBlCn7YTQdraI1HMiBWmkrqIGG-8UgD1UVyJy62Z_0o +*_llgo_func$VZ-8VPNF1RaLICwxc1Ghn7BbgyFX3v762OCdx127EkA: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$VZ-8VPNF1RaLICwxc1Ghn7BbgyFX3v762OCdx127EkA +*_llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk +*_llgo_func$Z_-7GWzB37LCYRTQLsSYmEihg_hqBK8o_GbT88pqnPY: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$Z_-7GWzB37LCYRTQLsSYmEihg_hqBK8o_GbT88pqnPY +*_llgo_func$aJkaU3jhXr0Q2QraTe2_TTdupeMMW2MD66UwBxynRM0: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$aJkaU3jhXr0Q2QraTe2_TTdupeMMW2MD66UwBxynRM0 +*_llgo_func$d4kMA_oCkLwnd1j8nVlv1hwRarEVuCIrDCpnHhDz9UY: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$d4kMA_oCkLwnd1j8nVlv1hwRarEVuCIrDCpnHhDz9UY +*_llgo_func$ekGNsrYBSzltfAjxbl6T8H6Yq8j16wzqS3nDj2xxGMU: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$ekGNsrYBSzltfAjxbl6T8H6Yq8j16wzqS3nDj2xxGMU +*_llgo_func$lukqSsfDYBoIp_R8GMojGkZnrYDqaq2iHn8RkCjW7iQ: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$lukqSsfDYBoIp_R8GMojGkZnrYDqaq2iHn8RkCjW7iQ +*_llgo_func$q-bw-_pPYBCXnr1TXIF8sOD4fVVzzIlpHqD-A13AB4Y: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$q-bw-_pPYBCXnr1TXIF8sOD4fVVzzIlpHqD-A13AB4Y +*_llgo_func$qVJ5SH6qhXP_h0AM41vpBGzQEMp-fQIfvwQEJy5NI8M: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$qVJ5SH6qhXP_h0AM41vpBGzQEMp-fQIfvwQEJy5NI8M +*_llgo_func$thH5FBpdXzJNnCpSfiLU5ItTntFU6LWp0RJhDm2XJjw: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$thH5FBpdXzJNnCpSfiLU5ItTntFU6LWp0RJhDm2XJjw +*_llgo_func$uVmBDI0DMcrui3Q9y-g_hbtVN8JckQ18V2wmO5_G7A8: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$uVmBDI0DMcrui3Q9y-g_hbtVN8JckQ18V2wmO5_G7A8 +*_llgo_func$uf8yw1UkUdbDuCneSpNKIq_NThWIEVE7f1IYfJGz_bw: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$uf8yw1UkUdbDuCneSpNKIq_NThWIEVE7f1IYfJGz_bw +*_llgo_func$vSv85k0UY6JWccAc3T-lvdCx9J-4GM-oZC9zGLrxW1M: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$vSv85k0UY6JWccAc3T-lvdCx9J-4GM-oZC9zGLrxW1M +*_llgo_func$w4tN9iibS_UimF5vLUWoKP0uAk2tJZF26VqETo_8LVg: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$w4tN9iibS_UimF5vLUWoKP0uAk2tJZF26VqETo_8LVg +*_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to +*_llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw +*_llgo_int: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_int +*_llgo_int32: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_int32 +*_llgo_int64: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_int64 +*_llgo_io.Reader: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_io.Reader +*_llgo_io.Writer: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_io.Writer +*_llgo_string: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_string +*_llgo_uint8: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_uint8 +[]_llgo_uint8: + *[]_llgo_uint8 + _llgo_uint8 +__llgo_stub.bytes.(*Buffer).Available: + bytes.(*Buffer).Available +__llgo_stub.bytes.(*Buffer).AvailableBuffer: + bytes.(*Buffer).AvailableBuffer +__llgo_stub.bytes.(*Buffer).Bytes: + bytes.(*Buffer).Bytes +__llgo_stub.bytes.(*Buffer).Cap: + bytes.(*Buffer).Cap +__llgo_stub.bytes.(*Buffer).Grow: + bytes.(*Buffer).Grow +__llgo_stub.bytes.(*Buffer).Len: + bytes.(*Buffer).Len +__llgo_stub.bytes.(*Buffer).Next: + bytes.(*Buffer).Next +__llgo_stub.bytes.(*Buffer).Read: + bytes.(*Buffer).Read +__llgo_stub.bytes.(*Buffer).ReadByte: + bytes.(*Buffer).ReadByte +__llgo_stub.bytes.(*Buffer).ReadBytes: + bytes.(*Buffer).ReadBytes +__llgo_stub.bytes.(*Buffer).ReadFrom: + bytes.(*Buffer).ReadFrom +__llgo_stub.bytes.(*Buffer).ReadRune: + bytes.(*Buffer).ReadRune +__llgo_stub.bytes.(*Buffer).ReadString: + bytes.(*Buffer).ReadString +__llgo_stub.bytes.(*Buffer).Reset: + bytes.(*Buffer).Reset +__llgo_stub.bytes.(*Buffer).String: + bytes.(*Buffer).String +__llgo_stub.bytes.(*Buffer).Truncate: + bytes.(*Buffer).Truncate +__llgo_stub.bytes.(*Buffer).UnreadByte: + bytes.(*Buffer).UnreadByte +__llgo_stub.bytes.(*Buffer).UnreadRune: + bytes.(*Buffer).UnreadRune +__llgo_stub.bytes.(*Buffer).Write: + bytes.(*Buffer).Write +__llgo_stub.bytes.(*Buffer).WriteByte: + bytes.(*Buffer).WriteByte +__llgo_stub.bytes.(*Buffer).WriteRune: + bytes.(*Buffer).WriteRune +__llgo_stub.bytes.(*Buffer).WriteString: + bytes.(*Buffer).WriteString +__llgo_stub.bytes.(*Buffer).WriteTo: + bytes.(*Buffer).WriteTo +__llgo_stub.bytes.(*Buffer).empty: + bytes.(*Buffer).empty +__llgo_stub.bytes.(*Buffer).grow: + bytes.(*Buffer).grow +__llgo_stub.bytes.(*Buffer).readSlice: + bytes.(*Buffer).readSlice +__llgo_stub.bytes.(*Buffer).tryGrowByReslice: + bytes.(*Buffer).tryGrowByReslice +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal: + github.com/goplus/llgo/runtime/internal/runtime.interequal +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal32: + github.com/goplus/llgo/runtime/internal/runtime.memequal32 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal64: + github.com/goplus/llgo/runtime/internal/runtime.memequal64 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal8: + github.com/goplus/llgo/runtime/internal/runtime.memequal8 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr: + github.com/goplus/llgo/runtime/internal/runtime.memequalptr +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.strequal: + github.com/goplus/llgo/runtime/internal/runtime.strequal +_llgo_bool: + *_llgo_bool + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal8 +_llgo_bytes.Buffer: + *_llgo_bytes.Buffer + bytes.struct$8M6lRFZ7Fk2XCr2laNI9Y7uQtk2A8VDBrezMuq2Fkuo$fields +_llgo_bytes.readOp: + *_llgo_bytes.readOp + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal8 +_llgo_error: + *_llgo_error + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal + _llgo_error$imethods +_llgo_error$imethods: + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to +_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + *_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +_llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w: + *_llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w + _llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w$out +_llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w$out: + _llgo_error +_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA: + *_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA + _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA$out +_llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA$out: + _llgo_int +_llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk: + *_llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk + _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk$in + _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk$out +_llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk$in: + []_llgo_uint8 +_llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk$out: + _llgo_error + _llgo_int +_llgo_func$TBlCn7YTQdraI1HMiBWmkrqIGG-8UgD1UVyJy62Z_0o: + *_llgo_func$TBlCn7YTQdraI1HMiBWmkrqIGG-8UgD1UVyJy62Z_0o + _llgo_func$TBlCn7YTQdraI1HMiBWmkrqIGG-8UgD1UVyJy62Z_0o$in + _llgo_func$TBlCn7YTQdraI1HMiBWmkrqIGG-8UgD1UVyJy62Z_0o$out +_llgo_func$TBlCn7YTQdraI1HMiBWmkrqIGG-8UgD1UVyJy62Z_0o$in: + _llgo_uint8 +_llgo_func$TBlCn7YTQdraI1HMiBWmkrqIGG-8UgD1UVyJy62Z_0o$out: + _llgo_error + _llgo_string +_llgo_func$VZ-8VPNF1RaLICwxc1Ghn7BbgyFX3v762OCdx127EkA: + *_llgo_func$VZ-8VPNF1RaLICwxc1Ghn7BbgyFX3v762OCdx127EkA + _llgo_func$VZ-8VPNF1RaLICwxc1Ghn7BbgyFX3v762OCdx127EkA$in +_llgo_func$VZ-8VPNF1RaLICwxc1Ghn7BbgyFX3v762OCdx127EkA$in: + _llgo_int +_llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk: + *_llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk + _llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk$out +_llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk$out: + _llgo_bool +_llgo_func$Z_-7GWzB37LCYRTQLsSYmEihg_hqBK8o_GbT88pqnPY: + *_llgo_func$Z_-7GWzB37LCYRTQLsSYmEihg_hqBK8o_GbT88pqnPY + _llgo_func$Z_-7GWzB37LCYRTQLsSYmEihg_hqBK8o_GbT88pqnPY$out +_llgo_func$Z_-7GWzB37LCYRTQLsSYmEihg_hqBK8o_GbT88pqnPY$out: + []_llgo_uint8 +_llgo_func$aJkaU3jhXr0Q2QraTe2_TTdupeMMW2MD66UwBxynRM0: + *_llgo_func$aJkaU3jhXr0Q2QraTe2_TTdupeMMW2MD66UwBxynRM0 + _llgo_func$aJkaU3jhXr0Q2QraTe2_TTdupeMMW2MD66UwBxynRM0$in + _llgo_func$aJkaU3jhXr0Q2QraTe2_TTdupeMMW2MD66UwBxynRM0$out +_llgo_func$aJkaU3jhXr0Q2QraTe2_TTdupeMMW2MD66UwBxynRM0$in: + _llgo_uint8 +_llgo_func$aJkaU3jhXr0Q2QraTe2_TTdupeMMW2MD66UwBxynRM0$out: + []_llgo_uint8 + _llgo_error +_llgo_func$d4kMA_oCkLwnd1j8nVlv1hwRarEVuCIrDCpnHhDz9UY: + *_llgo_func$d4kMA_oCkLwnd1j8nVlv1hwRarEVuCIrDCpnHhDz9UY + _llgo_func$d4kMA_oCkLwnd1j8nVlv1hwRarEVuCIrDCpnHhDz9UY$in + _llgo_func$d4kMA_oCkLwnd1j8nVlv1hwRarEVuCIrDCpnHhDz9UY$out +_llgo_func$d4kMA_oCkLwnd1j8nVlv1hwRarEVuCIrDCpnHhDz9UY$in: + _llgo_int +_llgo_func$d4kMA_oCkLwnd1j8nVlv1hwRarEVuCIrDCpnHhDz9UY$out: + []_llgo_uint8 +_llgo_func$ekGNsrYBSzltfAjxbl6T8H6Yq8j16wzqS3nDj2xxGMU: + *_llgo_func$ekGNsrYBSzltfAjxbl6T8H6Yq8j16wzqS3nDj2xxGMU + _llgo_func$ekGNsrYBSzltfAjxbl6T8H6Yq8j16wzqS3nDj2xxGMU$in + _llgo_func$ekGNsrYBSzltfAjxbl6T8H6Yq8j16wzqS3nDj2xxGMU$out +_llgo_func$ekGNsrYBSzltfAjxbl6T8H6Yq8j16wzqS3nDj2xxGMU$in: + _llgo_int +_llgo_func$ekGNsrYBSzltfAjxbl6T8H6Yq8j16wzqS3nDj2xxGMU$out: + _llgo_int +_llgo_func$lukqSsfDYBoIp_R8GMojGkZnrYDqaq2iHn8RkCjW7iQ: + *_llgo_func$lukqSsfDYBoIp_R8GMojGkZnrYDqaq2iHn8RkCjW7iQ + _llgo_func$lukqSsfDYBoIp_R8GMojGkZnrYDqaq2iHn8RkCjW7iQ$out +_llgo_func$lukqSsfDYBoIp_R8GMojGkZnrYDqaq2iHn8RkCjW7iQ$out: + _llgo_error + _llgo_uint8 +_llgo_func$q-bw-_pPYBCXnr1TXIF8sOD4fVVzzIlpHqD-A13AB4Y: + *_llgo_func$q-bw-_pPYBCXnr1TXIF8sOD4fVVzzIlpHqD-A13AB4Y + _llgo_func$q-bw-_pPYBCXnr1TXIF8sOD4fVVzzIlpHqD-A13AB4Y$out +_llgo_func$q-bw-_pPYBCXnr1TXIF8sOD4fVVzzIlpHqD-A13AB4Y$out: + _llgo_error + _llgo_int + _llgo_int32 +_llgo_func$qVJ5SH6qhXP_h0AM41vpBGzQEMp-fQIfvwQEJy5NI8M: + *_llgo_func$qVJ5SH6qhXP_h0AM41vpBGzQEMp-fQIfvwQEJy5NI8M + _llgo_func$qVJ5SH6qhXP_h0AM41vpBGzQEMp-fQIfvwQEJy5NI8M$in + _llgo_func$qVJ5SH6qhXP_h0AM41vpBGzQEMp-fQIfvwQEJy5NI8M$out +_llgo_func$qVJ5SH6qhXP_h0AM41vpBGzQEMp-fQIfvwQEJy5NI8M$in: + _llgo_int +_llgo_func$qVJ5SH6qhXP_h0AM41vpBGzQEMp-fQIfvwQEJy5NI8M$out: + _llgo_bool + _llgo_int +_llgo_func$thH5FBpdXzJNnCpSfiLU5ItTntFU6LWp0RJhDm2XJjw: + *_llgo_func$thH5FBpdXzJNnCpSfiLU5ItTntFU6LWp0RJhDm2XJjw + _llgo_func$thH5FBpdXzJNnCpSfiLU5ItTntFU6LWp0RJhDm2XJjw$in + _llgo_func$thH5FBpdXzJNnCpSfiLU5ItTntFU6LWp0RJhDm2XJjw$out +_llgo_func$thH5FBpdXzJNnCpSfiLU5ItTntFU6LWp0RJhDm2XJjw$in: + _llgo_string +_llgo_func$thH5FBpdXzJNnCpSfiLU5ItTntFU6LWp0RJhDm2XJjw$out: + _llgo_error + _llgo_int +_llgo_func$uVmBDI0DMcrui3Q9y-g_hbtVN8JckQ18V2wmO5_G7A8: + *_llgo_func$uVmBDI0DMcrui3Q9y-g_hbtVN8JckQ18V2wmO5_G7A8 + _llgo_func$uVmBDI0DMcrui3Q9y-g_hbtVN8JckQ18V2wmO5_G7A8$in + _llgo_func$uVmBDI0DMcrui3Q9y-g_hbtVN8JckQ18V2wmO5_G7A8$out +_llgo_func$uVmBDI0DMcrui3Q9y-g_hbtVN8JckQ18V2wmO5_G7A8$in: + _llgo_io.Reader +_llgo_func$uVmBDI0DMcrui3Q9y-g_hbtVN8JckQ18V2wmO5_G7A8$out: + _llgo_error + _llgo_int64 +_llgo_func$uf8yw1UkUdbDuCneSpNKIq_NThWIEVE7f1IYfJGz_bw: + *_llgo_func$uf8yw1UkUdbDuCneSpNKIq_NThWIEVE7f1IYfJGz_bw + _llgo_func$uf8yw1UkUdbDuCneSpNKIq_NThWIEVE7f1IYfJGz_bw$in + _llgo_func$uf8yw1UkUdbDuCneSpNKIq_NThWIEVE7f1IYfJGz_bw$out +_llgo_func$uf8yw1UkUdbDuCneSpNKIq_NThWIEVE7f1IYfJGz_bw$in: + _llgo_int32 +_llgo_func$uf8yw1UkUdbDuCneSpNKIq_NThWIEVE7f1IYfJGz_bw$out: + _llgo_error + _llgo_int +_llgo_func$vSv85k0UY6JWccAc3T-lvdCx9J-4GM-oZC9zGLrxW1M: + *_llgo_func$vSv85k0UY6JWccAc3T-lvdCx9J-4GM-oZC9zGLrxW1M + _llgo_func$vSv85k0UY6JWccAc3T-lvdCx9J-4GM-oZC9zGLrxW1M$in + _llgo_func$vSv85k0UY6JWccAc3T-lvdCx9J-4GM-oZC9zGLrxW1M$out +_llgo_func$vSv85k0UY6JWccAc3T-lvdCx9J-4GM-oZC9zGLrxW1M$in: + _llgo_io.Writer +_llgo_func$vSv85k0UY6JWccAc3T-lvdCx9J-4GM-oZC9zGLrxW1M$out: + _llgo_error + _llgo_int64 +_llgo_func$w4tN9iibS_UimF5vLUWoKP0uAk2tJZF26VqETo_8LVg: + *_llgo_func$w4tN9iibS_UimF5vLUWoKP0uAk2tJZF26VqETo_8LVg + _llgo_func$w4tN9iibS_UimF5vLUWoKP0uAk2tJZF26VqETo_8LVg$in + _llgo_func$w4tN9iibS_UimF5vLUWoKP0uAk2tJZF26VqETo_8LVg$out +_llgo_func$w4tN9iibS_UimF5vLUWoKP0uAk2tJZF26VqETo_8LVg$in: + _llgo_uint8 +_llgo_func$w4tN9iibS_UimF5vLUWoKP0uAk2tJZF26VqETo_8LVg$out: + _llgo_error +_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to: + *_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to + _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to$out +_llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to$out: + _llgo_string +_llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw: + *_llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal + _llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw$imethods +_llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw$imethods: + _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk +_llgo_int: + *_llgo_int + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal64 +_llgo_int32: + *_llgo_int32 + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal32 +_llgo_int64: + *_llgo_int64 + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal64 +_llgo_io.Reader: + *_llgo_io.Reader + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal + _llgo_io.Reader$imethods +_llgo_io.Reader$imethods: + _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk +_llgo_io.Writer: + *_llgo_io.Writer + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.interequal + _llgo_io.Writer$imethods +_llgo_io.Writer$imethods: + _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk +_llgo_string: + *_llgo_string + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.strequal +_llgo_uint8: + *_llgo_uint8 + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal8 +bytes.struct$8M6lRFZ7Fk2XCr2laNI9Y7uQtk2A8VDBrezMuq2Fkuo$fields: + []_llgo_uint8 + _llgo_bytes.readOp + _llgo_int +github.com/goplus/llgo/cl/_testmeta/methodinfo_imported.init: + bytes.init + github.com/goplus/llgo/cl/_testmeta/methodinfo_imported.init$guard + io.init +github.com/goplus/llgo/cl/_testmeta/methodinfo_imported.main: + *_llgo_bytes.Buffer + _llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw + bytes.NewBuffer + github.com/goplus/llgo/runtime/internal/runtime.IfacePtrData + github.com/goplus/llgo/runtime/internal/runtime.NewItab + +[UseIface] +github.com/goplus/llgo/cl/_testmeta/methodinfo_imported.main: + *_llgo_bytes.Buffer + +[UseIfaceMethod] +github.com/goplus/llgo/cl/_testmeta/methodinfo_imported.main: + _llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw Read _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk + +[MethodInfo] +*_llgo_bytes.Buffer: + 0 Available _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA bytes.(*Buffer).Available __llgo_stub.bytes.(*Buffer).Available + 1 AvailableBuffer _llgo_func$Z_-7GWzB37LCYRTQLsSYmEihg_hqBK8o_GbT88pqnPY bytes.(*Buffer).AvailableBuffer __llgo_stub.bytes.(*Buffer).AvailableBuffer + 2 Bytes _llgo_func$Z_-7GWzB37LCYRTQLsSYmEihg_hqBK8o_GbT88pqnPY bytes.(*Buffer).Bytes __llgo_stub.bytes.(*Buffer).Bytes + 3 Cap _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA bytes.(*Buffer).Cap __llgo_stub.bytes.(*Buffer).Cap + 4 Grow _llgo_func$VZ-8VPNF1RaLICwxc1Ghn7BbgyFX3v762OCdx127EkA bytes.(*Buffer).Grow __llgo_stub.bytes.(*Buffer).Grow + 5 Len _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA bytes.(*Buffer).Len __llgo_stub.bytes.(*Buffer).Len + 6 Next _llgo_func$d4kMA_oCkLwnd1j8nVlv1hwRarEVuCIrDCpnHhDz9UY bytes.(*Buffer).Next __llgo_stub.bytes.(*Buffer).Next + 7 Read _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk bytes.(*Buffer).Read __llgo_stub.bytes.(*Buffer).Read + 8 ReadByte _llgo_func$lukqSsfDYBoIp_R8GMojGkZnrYDqaq2iHn8RkCjW7iQ bytes.(*Buffer).ReadByte __llgo_stub.bytes.(*Buffer).ReadByte + 9 ReadBytes _llgo_func$aJkaU3jhXr0Q2QraTe2_TTdupeMMW2MD66UwBxynRM0 bytes.(*Buffer).ReadBytes __llgo_stub.bytes.(*Buffer).ReadBytes + 10 ReadFrom _llgo_func$uVmBDI0DMcrui3Q9y-g_hbtVN8JckQ18V2wmO5_G7A8 bytes.(*Buffer).ReadFrom __llgo_stub.bytes.(*Buffer).ReadFrom + 11 ReadRune _llgo_func$q-bw-_pPYBCXnr1TXIF8sOD4fVVzzIlpHqD-A13AB4Y bytes.(*Buffer).ReadRune __llgo_stub.bytes.(*Buffer).ReadRune + 12 ReadString _llgo_func$TBlCn7YTQdraI1HMiBWmkrqIGG-8UgD1UVyJy62Z_0o bytes.(*Buffer).ReadString __llgo_stub.bytes.(*Buffer).ReadString + 13 Reset _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac bytes.(*Buffer).Reset __llgo_stub.bytes.(*Buffer).Reset + 14 String _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to bytes.(*Buffer).String __llgo_stub.bytes.(*Buffer).String + 15 Truncate _llgo_func$VZ-8VPNF1RaLICwxc1Ghn7BbgyFX3v762OCdx127EkA bytes.(*Buffer).Truncate __llgo_stub.bytes.(*Buffer).Truncate + 16 UnreadByte _llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w bytes.(*Buffer).UnreadByte __llgo_stub.bytes.(*Buffer).UnreadByte + 17 UnreadRune _llgo_func$8rsrSd_r3UHd_2DiYTyaOKR7BYkei4zw5ysG35KF38w bytes.(*Buffer).UnreadRune __llgo_stub.bytes.(*Buffer).UnreadRune + 18 Write _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk bytes.(*Buffer).Write __llgo_stub.bytes.(*Buffer).Write + 19 WriteByte _llgo_func$w4tN9iibS_UimF5vLUWoKP0uAk2tJZF26VqETo_8LVg bytes.(*Buffer).WriteByte __llgo_stub.bytes.(*Buffer).WriteByte + 20 WriteRune _llgo_func$uf8yw1UkUdbDuCneSpNKIq_NThWIEVE7f1IYfJGz_bw bytes.(*Buffer).WriteRune __llgo_stub.bytes.(*Buffer).WriteRune + 21 WriteString _llgo_func$thH5FBpdXzJNnCpSfiLU5ItTntFU6LWp0RJhDm2XJjw bytes.(*Buffer).WriteString __llgo_stub.bytes.(*Buffer).WriteString + 22 WriteTo _llgo_func$vSv85k0UY6JWccAc3T-lvdCx9J-4GM-oZC9zGLrxW1M bytes.(*Buffer).WriteTo __llgo_stub.bytes.(*Buffer).WriteTo + 23 bytes.empty _llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk bytes.(*Buffer).empty __llgo_stub.bytes.(*Buffer).empty + 24 bytes.grow _llgo_func$ekGNsrYBSzltfAjxbl6T8H6Yq8j16wzqS3nDj2xxGMU bytes.(*Buffer).grow __llgo_stub.bytes.(*Buffer).grow + 25 bytes.readSlice _llgo_func$aJkaU3jhXr0Q2QraTe2_TTdupeMMW2MD66UwBxynRM0 bytes.(*Buffer).readSlice __llgo_stub.bytes.(*Buffer).readSlice + 26 bytes.tryGrowByReslice _llgo_func$qVJ5SH6qhXP_h0AM41vpBGzQEMp-fQIfvwQEJy5NI8M bytes.(*Buffer).tryGrowByReslice __llgo_stub.bytes.(*Buffer).tryGrowByReslice + +[InterfaceInfo] +_llgo_iface$uycIKA3bbxRhudEjW1hHKWKdLqHQsCVy8NdW1bkQmNw: + Read _llgo_func$G2hch9Iy9DrhKKsg70PbL54bK-XSl-1IUUORN17J2Dk + diff --git a/cl/_testmeta/reflect_dynamic/in.go b/cl/_testmeta/reflect_dynamic/in.go new file mode 100644 index 0000000000..15ed1f1a5f --- /dev/null +++ b/cl/_testmeta/reflect_dynamic/in.go @@ -0,0 +1,15 @@ +package main + +import "reflect" + +type T struct{} + +func (T) M() {} + +func use(name string) { + _ = reflect.ValueOf(T{}).MethodByName(name) +} + +func main() { + use("M") +} diff --git a/cl/_testmeta/reflect_dynamic/meta-expect.txt b/cl/_testmeta/reflect_dynamic/meta-expect.txt new file mode 100644 index 0000000000..7b3a5180a6 --- /dev/null +++ b/cl/_testmeta/reflect_dynamic/meta-expect.txt @@ -0,0 +1,54 @@ +[TypeChildren] +*_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +*_llgo_github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.T: + _llgo_github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.T + +[OrdinaryEdges] +*_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +*_llgo_github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.T: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.T +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.(*T).M: + github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.(*T).M +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.T.M: + github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.T.M +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal0: + github.com/goplus/llgo/runtime/internal/runtime.memequal0 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr: + github.com/goplus/llgo/runtime/internal/runtime.memequalptr +_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + *_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +_llgo_github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.T: + *_llgo_github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.T + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal0 +github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.(*T).M: + github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.T.M + github.com/goplus/llgo/runtime/internal/runtime.AssertNilDeref + github.com/goplus/llgo/runtime/internal/runtime.PanicWrapNilPointer +github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.init: + github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.init$guard + reflect.init +github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.main: + github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.use +github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.use: + _llgo_github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.T + github.com/goplus/llgo/runtime/internal/runtime.AllocU + reflect.Value.MethodByName + reflect.ValueOf + +[UseIface] +github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.use: + _llgo_github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.T + +[MethodInfo] +*_llgo_github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.T: + 0 M _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.(*T).M __llgo_stub.github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.(*T).M +_llgo_github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.T: + 0 M _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.(*T).M __llgo_stub.github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.T.M + +[Reflect] + github.com/goplus/llgo/cl/_testmeta/reflect_dynamic.use + diff --git a/cl/_testmeta/reflect_named/in.go b/cl/_testmeta/reflect_named/in.go new file mode 100644 index 0000000000..28c989074d --- /dev/null +++ b/cl/_testmeta/reflect_named/in.go @@ -0,0 +1,13 @@ +package main + +import "reflect" + +type T struct{} + +func (T) M() {} +func (T) m() {} + +func main() { + _, _ = reflect.TypeOf(T{}).MethodByName("M") + _, _ = reflect.TypeOf(T{}).MethodByName("m") +} diff --git a/cl/_testmeta/reflect_named/meta-expect.txt b/cl/_testmeta/reflect_named/meta-expect.txt new file mode 100644 index 0000000000..9633334568 --- /dev/null +++ b/cl/_testmeta/reflect_named/meta-expect.txt @@ -0,0 +1,110 @@ +[TypeChildren] +*_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +*_llgo_github.com/goplus/llgo/cl/_testmeta/reflect_named.T: + _llgo_github.com/goplus/llgo/cl/_testmeta/reflect_named.T + +[OrdinaryEdges] +*_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +*_llgo_github.com/goplus/llgo/cl/_testmeta/reflect_named.T: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_github.com/goplus/llgo/cl/_testmeta/reflect_named.T +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/reflect_named.(*T).M: + github.com/goplus/llgo/cl/_testmeta/reflect_named.(*T).M +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/reflect_named.(*T).m: + github.com/goplus/llgo/cl/_testmeta/reflect_named.(*T).m +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/reflect_named.T.M: + github.com/goplus/llgo/cl/_testmeta/reflect_named.T.M +__llgo_stub.github.com/goplus/llgo/cl/_testmeta/reflect_named.T.m: + github.com/goplus/llgo/cl/_testmeta/reflect_named.T.m +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal0: + github.com/goplus/llgo/runtime/internal/runtime.memequal0 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr: + github.com/goplus/llgo/runtime/internal/runtime.memequalptr +_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac: + *_llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac +_llgo_github.com/goplus/llgo/cl/_testmeta/reflect_named.T: + *_llgo_github.com/goplus/llgo/cl/_testmeta/reflect_named.T + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal0 +github.com/goplus/llgo/cl/_testmeta/reflect_named.(*T).M: + github.com/goplus/llgo/cl/_testmeta/reflect_named.T.M + github.com/goplus/llgo/runtime/internal/runtime.AssertNilDeref + github.com/goplus/llgo/runtime/internal/runtime.PanicWrapNilPointer +github.com/goplus/llgo/cl/_testmeta/reflect_named.(*T).m: + github.com/goplus/llgo/cl/_testmeta/reflect_named.T.m + github.com/goplus/llgo/runtime/internal/runtime.AssertNilDeref + github.com/goplus/llgo/runtime/internal/runtime.PanicWrapNilPointer +github.com/goplus/llgo/cl/_testmeta/reflect_named.init: + github.com/goplus/llgo/cl/_testmeta/reflect_named.init$guard + reflect.init +github.com/goplus/llgo/cl/_testmeta/reflect_named.main: + _llgo_github.com/goplus/llgo/cl/_testmeta/reflect_named.T + github.com/goplus/llgo/runtime/internal/runtime.AllocU + github.com/goplus/llgo/runtime/internal/runtime.IfacePtrData + reflect.TypeOf + +[UseIface] +github.com/goplus/llgo/cl/_testmeta/reflect_named.main: + _llgo_github.com/goplus/llgo/cl/_testmeta/reflect_named.T + _llgo_github.com/goplus/llgo/cl/_testmeta/reflect_named.T + +[UseIfaceMethod] +github.com/goplus/llgo/cl/_testmeta/reflect_named.main: + github.com/goplus/llgo/runtime/internal/lib/reflect.iface$iKaf3qt2OaMUwQszh7IENnt26Bv3ufKgLDSVmXKN7gI MethodByName _llgo_func$aM2cVUtLQbPq1YHtnabQiM7XJ5Cg5RyV6BIDWrqey7E + github.com/goplus/llgo/runtime/internal/lib/reflect.iface$iKaf3qt2OaMUwQszh7IENnt26Bv3ufKgLDSVmXKN7gI MethodByName _llgo_func$aM2cVUtLQbPq1YHtnabQiM7XJ5Cg5RyV6BIDWrqey7E + +[UseNamedMethod] +github.com/goplus/llgo/cl/_testmeta/reflect_named.main: + M + m + +[MethodInfo] +*_llgo_github.com/goplus/llgo/cl/_testmeta/reflect_named.T: + 0 M _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac github.com/goplus/llgo/cl/_testmeta/reflect_named.(*T).M __llgo_stub.github.com/goplus/llgo/cl/_testmeta/reflect_named.(*T).M + 1 github.com/goplus/llgo/cl/_testmeta/reflect_named.m _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac github.com/goplus/llgo/cl/_testmeta/reflect_named.(*T).m __llgo_stub.github.com/goplus/llgo/cl/_testmeta/reflect_named.(*T).m +_llgo_github.com/goplus/llgo/cl/_testmeta/reflect_named.T: + 0 M _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac github.com/goplus/llgo/cl/_testmeta/reflect_named.(*T).M __llgo_stub.github.com/goplus/llgo/cl/_testmeta/reflect_named.T.M + 1 github.com/goplus/llgo/cl/_testmeta/reflect_named.m _llgo_func$2_iS07vIlF2_rZqWB5eU0IvP_9HviM4MYZNkXZDvbac github.com/goplus/llgo/cl/_testmeta/reflect_named.(*T).m __llgo_stub.github.com/goplus/llgo/cl/_testmeta/reflect_named.T.m + +[InterfaceInfo] +github.com/goplus/llgo/runtime/internal/lib/reflect.iface$iKaf3qt2OaMUwQszh7IENnt26Bv3ufKgLDSVmXKN7gI: + Align _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA + AssignableTo _llgo_func$Kxk9fspGkjXcoNWf2ucHG1vOQ5VHxVtYionfm-DnvWE + Bits _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA + CanSeq _llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk + CanSeq2 _llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk + ChanDir _llgo_func$JO3khPIbANSMBmoN6P7ybYAeUBd3Gv6toVUqNeE7qbE + Comparable _llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk + ConvertibleTo _llgo_func$Kxk9fspGkjXcoNWf2ucHG1vOQ5VHxVtYionfm-DnvWE + Elem _llgo_func$b6KOG2Oj7wt8ogb9H8QPbhEfXhxMMjdxRZgPLK_UOwI + Field _llgo_func$Q3NYrysaKgu1MtMuLQwb-k5QcKGHihnt-tV_NlNJQFA + FieldAlign _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA + FieldByIndex _llgo_func$LPPtiM49dEPl48CC3WRhXm3YPnfUJEZE_k8Tx3rMuSk + FieldByName _llgo_func$dEvABJ5r0MMUlf4smWpIDG5dO8AuGklGdNJ1xneL3UM + FieldByNameFunc _llgo_func$xySrXVFC_2LK2oP71R2UryKi6UmdEJUo9k6aQuz4TvI + Implements _llgo_func$Kxk9fspGkjXcoNWf2ucHG1vOQ5VHxVtYionfm-DnvWE + In _llgo_func$dPYu3A0LoGTV2Hd8PW4KPw2ITiUSo9q-4Bg9ZrPITnY + IsVariadic _llgo_func$YHeRw3AOvQtzv982-ZO3Yn8vh3Fx89RM3VvI8E4iKVk + Key _llgo_func$b6KOG2Oj7wt8ogb9H8QPbhEfXhxMMjdxRZgPLK_UOwI + Kind _llgo_func$w8Mj2LK8G5p7MIiGWR6MYjyXy3L8SVVzYlT1bb6KNXk + Len _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA + Method _llgo_func$FmJJGomlX5kINJGxQdQDCAkD89ySoMslAYFrziWInVc + MethodByName _llgo_func$aM2cVUtLQbPq1YHtnabQiM7XJ5Cg5RyV6BIDWrqey7E + Name _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to + NumField _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA + NumIn _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA + NumMethod _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA + NumOut _llgo_func$ETeB8WwW04JEq0ztcm-XPTJtuYvtpkjIsAc0-2NT9zA + Out _llgo_func$dPYu3A0LoGTV2Hd8PW4KPw2ITiUSo9q-4Bg9ZrPITnY + OverflowComplex _llgo_func$cGkbH-2LQOLoq64Rqj3WeO56U8al7FfVkf5K1FFbPpE + OverflowFloat _llgo_func$uk7PgUVap9GZdvS8R_mZCDbAbqnAbcNryqybtDogUNI + OverflowInt _llgo_func$odFOIClZoEVGbTP_BEfZxVM5ex3r8Fj1afUEeP_awp8 + OverflowUint _llgo_func$7I97sofX8UqJA96mVIy89KPUfSM_efkrR-mJQ9qaHfk + PkgPath _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to + Size _llgo_func$1kITCsyu7hFLMxHLR7kDlvu4SOra_HtrtdFUQH9P13s + String _llgo_func$zNDVRsWTIpUPKouNUS805RGX--IV9qVK8B31IZbg5to + reflect.common _llgo_func$w6XuV-1SmW103DbauPseXBpW50HpxXAEsUsGFibl0Uw + reflect.uncommon _llgo_func$iG49bujiXjI2lVflYdE0hPXlCAABL-XKRANSNJEKOio + diff --git a/cl/_testmeta/typechildren_basic/in.go b/cl/_testmeta/typechildren_basic/in.go new file mode 100644 index 0000000000..03536eead9 --- /dev/null +++ b/cl/_testmeta/typechildren_basic/in.go @@ -0,0 +1,16 @@ +package main + +type Inner struct { + S string +} + +type Outer struct { + I Inner + N int +} + +func use(any) {} + +func main() { + use(Outer{}) +} diff --git a/cl/_testmeta/typechildren_basic/meta-expect.txt b/cl/_testmeta/typechildren_basic/meta-expect.txt new file mode 100644 index 0000000000..80ebfa64ca --- /dev/null +++ b/cl/_testmeta/typechildren_basic/meta-expect.txt @@ -0,0 +1,64 @@ +[TypeChildren] +*_llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Inner: + _llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Inner +*_llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Outer: + _llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Outer +*_llgo_int: + _llgo_int +*_llgo_string: + _llgo_string +_llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Inner: + _llgo_string +_llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Outer: + _llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Inner + _llgo_int + +[OrdinaryEdges] +*_llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Inner: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Inner +*_llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Outer: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Outer +*_llgo_int: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_int +*_llgo_string: + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr + _llgo_string +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal64: + github.com/goplus/llgo/runtime/internal/runtime.memequal64 +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequalptr: + github.com/goplus/llgo/runtime/internal/runtime.memequalptr +__llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.strequal: + github.com/goplus/llgo/runtime/internal/runtime.strequal +_llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Inner: + *_llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Inner + _llgo_struct$MTAKiWNMPODH0G9-y5PI3BUGLd6hRciSbX1QTpCDOZg$fields + github.com/goplus/llgo/runtime/internal/runtime.structequal +_llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Outer: + *_llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Outer + _llgo_struct$VaHvQdd7oPMFznC6BSA9M_OXdmuO77w_Ys1GnWZpjRM$fields + github.com/goplus/llgo/runtime/internal/runtime.structequal +_llgo_int: + *_llgo_int + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.memequal64 +_llgo_string: + *_llgo_string + __llgo_stub.github.com/goplus/llgo/runtime/internal/runtime.strequal +_llgo_struct$MTAKiWNMPODH0G9-y5PI3BUGLd6hRciSbX1QTpCDOZg$fields: + _llgo_string +_llgo_struct$VaHvQdd7oPMFznC6BSA9M_OXdmuO77w_Ys1GnWZpjRM$fields: + _llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Inner + _llgo_int +github.com/goplus/llgo/cl/_testmeta/typechildren_basic.init: + github.com/goplus/llgo/cl/_testmeta/typechildren_basic.init$guard +github.com/goplus/llgo/cl/_testmeta/typechildren_basic.main: + _llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Outer + github.com/goplus/llgo/cl/_testmeta/typechildren_basic.use + github.com/goplus/llgo/runtime/internal/runtime.AllocU + +[UseIface] +github.com/goplus/llgo/cl/_testmeta/typechildren_basic.main: + _llgo_github.com/goplus/llgo/cl/_testmeta/typechildren_basic.Outer + diff --git a/cl/cltest/cltest.go b/cl/cltest/cltest.go index 51f199bf94..921cf8f2c5 100644 --- a/cl/cltest/cltest.go +++ b/cl/cltest/cltest.go @@ -68,6 +68,7 @@ type runOptions struct { filter func(string) string checkIR bool checkOutput bool + checkMeta bool } // RunOption customizes directory-based test behavior. @@ -87,7 +88,7 @@ func WithOutputFilter(filter func(string) string) RunOption { } } -// WithOutputCheck enables or disables runtime output golden checks in RunAndTestFromDir. +// WithOutputCheck enables or disables runtime output golden checks. func WithOutputCheck(enabled bool) RunOption { return func(opts *runOptions) { opts.checkOutput = enabled @@ -101,6 +102,13 @@ func WithIRCheck(enabled bool) RunOption { } } +// WithMetaCheck enables or disables package metadata golden checks. +func WithMetaCheck(enabled bool) RunOption { + return func(opts *runOptions) { + opts.checkMeta = enabled + } +} + // FilterEmulatorOutput strips emulator boot logs by returning output after "entry 0x...". func FilterEmulatorOutput(output string) string { output = strings.ReplaceAll(output, "\r\n", "\n") @@ -277,12 +285,26 @@ func testRunAndTestFrom(t *testing.T, pkgDir, relPkg, sel string, opts runOption if opts.checkIR { testFrom(t, pkgDir, sel) } + if opts.checkMeta { + metaDirs, err := findMetaCheckDirs(pkgDir) + if err != nil { + t.Fatal("Find meta check dirs failed:", err) + } + conf, capturedMetas := withMetaCaptures(opts.conf, metaDirs) + output, err := runWithConf(relPkg, pkgDir, conf) + if err != nil { + t.Logf("raw output:\n%s", string(output)) + t.Fatalf("run failed: %v\noutput: %s", err, string(output)) + } + assertExpectedMetas(t, pkgDir, relPkg, capturedMetas) + } return } var irSpec littest.Spec conf := opts.conf var capturedIR *string + var capturedMeta *string var checkIR bool if opts.checkIR { irSpec, checkIR, err = readIRSpec(pkgDir) @@ -290,9 +312,12 @@ func testRunAndTestFrom(t *testing.T, pkgDir, relPkg, sel string, opts runOption t.Fatal("LoadSpec failed:", err) } if checkIR { - conf, capturedIR = withModuleCapture(opts.conf, pkgDir) + conf, capturedIR, capturedMeta = withModuleCapture(opts.conf, pkgDir) } } + if opts.checkMeta && capturedMeta == nil { + conf, capturedIR, capturedMeta = withModuleCapture(conf, pkgDir) + } output, err := runWithConf(relPkg, pkgDir, conf) if err != nil { @@ -301,6 +326,9 @@ func testRunAndTestFrom(t *testing.T, pkgDir, relPkg, sel string, opts runOption } assertExpectedOutput(t, pkgDir, expectedOutput, output, opts) + if opts.checkMeta { + assertExpectedMeta(t, pkgDir, relPkg, capturedMeta) + } if !checkIR { return } @@ -313,22 +341,73 @@ func testRunAndTestFrom(t *testing.T, pkgDir, relPkg, sel string, opts runOption } } +func assertExpectedMeta(t *testing.T, pkgDir, relPkg string, capturedMeta *string) { + t.Helper() + expectedMeta, hasMeta, err := readGolden(filepath.Join(pkgDir, "meta-expect.txt")) + if err != nil { + t.Fatal("ReadFile failed:", err) + } + if !hasMeta { + t.Fatal("missing meta-expect.txt") + } + if capturedMeta == nil { + t.Fatalf("metadata snapshot missing for %s", relPkg) + } + if test.Diff(t, filepath.Join(pkgDir, "meta-expect.txt.new"), []byte(*capturedMeta), expectedMeta) { + t.Fatal("metadata: unexpected result") + } +} + +func assertExpectedMetas(t *testing.T, rootDir, relRoot string, capturedMetas map[string]*string) { + t.Helper() + if len(capturedMetas) == 0 { + t.Fatal("missing meta-expect.txt") + } + dirs := make([]string, 0, len(capturedMetas)) + for dir := range capturedMetas { + dirs = append(dirs, dir) + } + slices.Sort(dirs) + for _, dir := range dirs { + relPkg := relRoot + if dir != rootDir { + rel, err := filepath.Rel(rootDir, dir) + if err != nil { + t.Fatal("Rel failed:", err) + } + relPkg = strings.TrimSuffix(relRoot, "/") + "/" + filepath.ToSlash(rel) + } + assertExpectedMeta(t, dir, relPkg, capturedMetas[dir]) + } +} + func RunAndCapture(relPkg, pkgDir string) ([]byte, error) { conf := build.NewDefaultConf(build.ModeRun) return RunAndCaptureWithConf(relPkg, pkgDir, conf) } +// CaptureMeta builds relPkg and returns the package metadata captured for pkgDir. +func CaptureMeta(relPkg, pkgDir string) (string, error) { + conf, _, capturedMeta := withModuleCapture(build.NewDefaultConf(build.ModeRun), pkgDir) + output, err := runWithConf(relPkg, pkgDir, conf) + if err != nil { + return "", fmt.Errorf("%w\noutput: %s", err, string(output)) + } + return *capturedMeta, nil +} + // RunAndCaptureWithConf runs llgo with a custom build config and captures output. func RunAndCaptureWithConf(relPkg, pkgDir string, conf *build.Config) ([]byte, error) { return runWithConf(relPkg, pkgDir, conf) } -func withModuleCapture(conf *build.Config, pkgDir string) (*build.Config, *string) { +func withModuleCapture(conf *build.Config, pkgDir string) (*build.Config, *string, *string) { if conf == nil { conf = build.NewDefaultConf(build.ModeRun) } localConf := *conf var module string + var meta string prevHook := localConf.ModuleHook localConf.ModuleHook = func(pkg build.Package) { if prevHook != nil { @@ -338,9 +417,60 @@ func withModuleCapture(conf *build.Config, pkgDir string) (*build.Config, *strin return filepath.Dir(file) == pkgDir }) { module = pkg.LPkg.String() + meta = pkg.Meta.String() } } - return &localConf, &module + return &localConf, &module, &meta +} + +func findMetaCheckDirs(pkgDir string) ([]string, error) { + var dirs []string + err := filepath.WalkDir(pkgDir, func(path string, d os.DirEntry, err error) error { + if err != nil { + return err + } + if !d.IsDir() { + return nil + } + if _, err := os.Stat(filepath.Join(path, "meta-expect.txt")); err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + return err + } + dirs = append(dirs, filepath.Clean(path)) + return nil + }) + if err != nil { + return nil, err + } + slices.Sort(dirs) + return dirs, nil +} + +func withMetaCaptures(conf *build.Config, pkgDirs []string) (*build.Config, map[string]*string) { + if conf == nil { + conf = build.NewDefaultConf(build.ModeRun) + } + localConf := *conf + localConf.ForceRebuild = true + metas := make(map[string]*string, len(pkgDirs)) + for _, pkgDir := range pkgDirs { + metas[filepath.Clean(pkgDir)] = new(string) + } + prevHook := localConf.ModuleHook + localConf.ModuleHook = func(pkg build.Package) { + if prevHook != nil { + prevHook(pkg) + } + for _, file := range pkg.Package.GoFiles { + if meta := metas[filepath.Clean(filepath.Dir(file))]; meta != nil { + *meta = pkg.Meta.String() + return + } + } + } + return &localConf, metas } func testBuildAndCheckSymbolsFrom(t *testing.T, pkgDir, relPkg, sel, symbolSpec string, opts runOptions) { @@ -348,6 +478,20 @@ func testBuildAndCheckSymbolsFrom(t *testing.T, pkgDir, relPkg, sel, symbolSpec if sel != "" && !strings.Contains(pkgDir, sel) { return } + var ( + expectedOutput []byte + checkOutput bool + err error + ) + if opts.checkOutput { + expectedOutput, checkOutput, err = readGolden(filepath.Join(pkgDir, "expect.txt")) + if err != nil { + t.Fatal("ReadFile failed:", err) + } + if !checkOutput { + t.Fatal("missing expect.txt") + } + } outFile := filepath.Join(t.TempDir(), filepath.Base(pkgDir)) output, err := buildWithConf(relPkg, pkgDir, opts.conf, outFile) if err != nil { @@ -355,6 +499,25 @@ func testBuildAndCheckSymbolsFrom(t *testing.T, pkgDir, relPkg, sel, symbolSpec t.Fatalf("build failed: %v\noutput: %s", err, string(output)) } assertExpectedSymbols(t, outFile, symbolSpec) + if checkOutput { + output, err := runBuiltBinary(outFile, pkgDir) + if err != nil { + t.Logf("raw output:\n%s", string(output)) + t.Fatalf("run built binary failed: %v\noutput: %s", err, string(output)) + } + assertExpectedOutput(t, pkgDir, expectedOutput, output, opts) + } +} + +func runBuiltBinary(bin, dir string) ([]byte, error) { + cmd := exec.Command(bin) + cmd.Dir = dir + output, err := cmd.CombinedOutput() + output = filterRunOutput(output) + if err != nil { + return output, err + } + return output, nil } func buildWithConf(relPkg, pkgDir string, conf *build.Config, outFile string) ([]byte, error) { diff --git a/cl/compile.go b/cl/compile.go index 434a25d773..fe17d6a608 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -32,6 +32,7 @@ import ( "github.com/goplus/llgo/cl/blocks" "github.com/goplus/llgo/cl/ssawrap" "github.com/goplus/llgo/internal/goembed" + "github.com/goplus/llgo/internal/meta" "github.com/goplus/llgo/internal/typepatch" "golang.org/x/tools/go/ssa" @@ -1680,17 +1681,17 @@ func NewPackage(prog llssa.Program, pkg *ssa.Package, files []*ast.File) (ret ll // The rewrites map uses short variable names (without package qualifier) and // only affects string-typed globals defined in the current package. func NewPackageEx(prog llssa.Program, patches Patches, rewrites map[string]string, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, externs []string, err error) { - return newPackageEx(prog, patches, rewrites, pkg, files, nil) + return newPackageEx(prog, patches, rewrites, pkg, files, nil, nil) } // NewPackageExWithEmbed compiles a package using pre-loaded go:embed metadata. // // This avoids re-scanning directives when the caller already loaded them. -func NewPackageExWithEmbed(prog llssa.Program, patches Patches, rewrites map[string]string, pkg *ssa.Package, files []*ast.File, embedMap goembed.VarMap) (ret llssa.Package, externs []string, err error) { - return newPackageEx(prog, patches, rewrites, pkg, files, &embedMap) +func NewPackageExWithEmbed(prog llssa.Program, patches Patches, rewrites map[string]string, pkg *ssa.Package, files []*ast.File, embedMap goembed.VarMap, metaBuilder *meta.Builder) (ret llssa.Package, externs []string, err error) { + return newPackageEx(prog, patches, rewrites, pkg, files, &embedMap, metaBuilder) } -func newPackageEx(prog llssa.Program, patches Patches, rewrites map[string]string, pkg *ssa.Package, files []*ast.File, embedMap *goembed.VarMap) (ret llssa.Package, externs []string, err error) { +func newPackageEx(prog llssa.Program, patches Patches, rewrites map[string]string, pkg *ssa.Package, files []*ast.File, embedMap *goembed.VarMap, metaBuilder *meta.Builder) (ret llssa.Package, externs []string, err error) { pkgProg := pkg.Prog pkgTypes := pkg.Pkg oldTypes := pkgTypes @@ -1705,6 +1706,7 @@ func newPackageEx(prog llssa.Program, patches Patches, rewrites map[string]strin prog.SetRuntime(pkgTypes) } ret = prog.NewPackage(pkgName, pkgPath) + ret.MetaBuilder = metaBuilder if enableDbg { ret.InitDebug(pkgName, pkgPath, pkgProg.Fset) } diff --git a/cl/compile_test.go b/cl/compile_test.go index 5eccc11707..622c69d194 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -175,6 +175,14 @@ func TestRunAndTestFromTestgo(t *testing.T) { cltest.RunAndTestFromDir(t, "", "./_testgo", nil) } +func TestRunAndTestFromTestmeta(t *testing.T) { + cltest.RunAndTestFromDir(t, "", "./_testmeta", nil, + cltest.WithOutputCheck(false), + cltest.WithIRCheck(false), + cltest.WithMetaCheck(true), + ) +} + func TestRunAndTestFromTestlto(t *testing.T) { conf := build.NewDefaultConf(build.ModeRun) conf.LTO = lto.Full @@ -215,6 +223,32 @@ func TestBuildAndCheckSymbolsFromTestlto(t *testing.T) { cltest.BuildAndCheckSymbolsFromDir(t, "", "./_testlto", testltoSymbolChecks, cltest.WithRunConfig(conf)) } +var testdropSymbolChecks = []string{ + "direct_func", + "direct_method", + "exported_method_crosspkg", + "generic_interface_crosspkg", + "generic_interface_func_crosspkg", + "iface_flow_crosspkg", + "interface_demand_fixedpoint", + "interface_match", + "interface_slot", + "promoted_method_wrapper", + "reflect_dynamic_iface_crosspkg", + "reflect_named_method", + "source64_crosspkg", + "unexported_method_identity", +} + +func TestBuildAndCheckSymbolsFromTestdrop(t *testing.T) { + conf := build.NewDefaultConf(build.ModeBuild) + conf.ForceRebuild = true + cltest.BuildAndCheckSymbolsFromDir(t, "", "./_testdrop", testdropSymbolChecks, + cltest.WithRunConfig(conf), + cltest.WithOutputCheck(true), + ) +} + func TestFilterEmulatorOutput(t *testing.T) { tests := []struct { name string diff --git a/cl/embed_with_map_compile_test.go b/cl/embed_with_map_compile_test.go index 069be194d1..203a07fa25 100644 --- a/cl/embed_with_map_compile_test.go +++ b/cl/embed_with_map_compile_test.go @@ -57,7 +57,7 @@ var files embed.FS prog := ssatest.NewProgramEx(t, nil, imp) prog.TypeSizes(types.SizesFor("gc", runtime.GOARCH)) - ret, _, err := cl.NewPackageExWithEmbed(prog, nil, nil, fooPkg, files, embedMap) + ret, _, err := cl.NewPackageExWithEmbed(prog, nil, nil, fooPkg, files, embedMap, nil) if err != nil { t.Fatalf("NewPackageExWithEmbed failed: %v", err) } diff --git a/cl/instr.go b/cl/instr.go index b7fc52abd3..0046bdb40c 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -63,6 +63,65 @@ func constBool(v ssa.Value) (ret bool, ok bool) { return } +func isNamedType(t types.Type, pkgPath, name string) bool { + named, ok := types.Unalias(t).(*types.Named) + if !ok { + return false + } + obj := named.Obj() + return obj != nil && obj.Name() == name && obj.Pkg() != nil && obj.Pkg().Path() == pkgPath +} + +func staticCallMethod(call *ssa.CallCommon) (recv types.Type, method string, ok bool) { + fn := call.StaticCallee() + if fn == nil || fn.Signature == nil || fn.Signature.Recv() == nil { + return nil, "", false + } + return fn.Signature.Recv().Type(), fn.Name(), true +} + +func reflectMethodNameArg(call *ssa.CallCommon) (nameArg ssa.Value, ok bool) { + if method := call.Method; method != nil { + if !isNamedType(call.Value.Type(), "reflect", "Type") { + return nil, false + } + if method.Name() != "MethodByName" { + return nil, method.Name() == "Method" + } + return call.Args[0], true + } + + recv, methodName, ok := staticCallMethod(call) + if !ok || !isNamedType(recv, "reflect", "Value") { + return nil, false + } + if methodName != "MethodByName" { + return nil, methodName == "Method" + } + return call.Args[len(call.Args)-1], true +} + +func (p *context) markReflectMethodCall(call *ssa.CallCommon) { + if p.fn == nil || p.pkg.MetaBuilder == nil { + return + } + nameArg, ok := reflectMethodNameArg(call) + if !ok { + return + } + mb := p.pkg.MetaBuilder + owner := mb.Sym(p.fn.Name()) + if nameArg == nil { + mb.MarkReflect(owner) + return + } + if name, ok := constStr(nameArg); ok { + mb.AddNamedMethodEdge(owner, name) + return + } + mb.MarkReflect(owner) +} + // func pystr(string) *py.Object func pystr(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) { if len(args) == 1 { @@ -1049,6 +1108,7 @@ func collectMethodNilDerefChecks(fn *ssa.Function) map[*ssa.UnOp]none { } func (p *context) callEx(b llssa.Builder, act llssa.DoAction, call *ssa.CallCommon, ds *explicitDeferStack) (ret llssa.Expr) { + p.markReflectMethodCall(call) cv := call.Value if mthd := call.Method; mthd != nil { reflectCheck := p.reflectTypeMethodCheck(call, mthd) diff --git a/doc/package-summary-cache-format.md b/doc/package-summary-cache-format.md new file mode 100644 index 0000000000..62bcf003aa --- /dev/null +++ b/doc/package-summary-cache-format.md @@ -0,0 +1,447 @@ +# LLGo Package Summary Cache Format + +## 概述 + +本文档定义 LLGo 包级摘要缓存(Package Summary Cache)的二进制文件格式。该格式服务于全图方法可达性分析(Whole-Program DCE),其核心设计目标是: + +- **零反序列化**:文件通过 mmap 映射到内存,字节直接作为内存对象访问 +- **O(1) 查询**:所有 per-symbol 数据通过 CSR 布局实现直接寻址 +- **极低合并开销**:GlobalSummary 合并阶段只 intern 字符串表,不遍历重写所有边 + +设计参考了 Go 官方 linker(`cmd/internal/goobj`)的 `RelocIndex + Reloc` CSR 布局,并结合 LLGo 的实际需求做了简化。 + +--- + +## 文件布局 + +``` +┌──────────────────────────────────────────────────────┐ +│ Header │ +├──────────────────────────────────────────────────────┤ +│ stringTable │ +├──────────────────────────────────────────────────────┤ +│ Symbols │ +├──────────────────────────────────────────────────────┤ +│ Edges (CSR) │ +├──────────────────────────────────────────────────────┤ +│ TypeChildren (CSR) │ +├──────────────────────────────────────────────────────┤ +│ MethodInfo (CSR) │ +├──────────────────────────────────────────────────────┤ +│ InterfaceInfo (CSR) │ +├──────────────────────────────────────────────────────┤ +│ ReflectBitmap │ +└──────────────────────────────────────────────────────┘ +``` + +所有整数均为 **little-endian**。所有 section 4 字节对齐。 + +--- + +## Header + +``` +Header { + Magic [4]byte // "LLPM" + Version uint32 // 当前版本 = 1 + SectionOffsets [8]uint32 // 各 section 在文件中的起始字节位置 + // [0]=stringTable [1]=Symbols [2]=Edges + // [3]=TypeChildren [4]=MethodInfo + // [5]=InterfaceInfo [6]=ReflectBitmap [7]=reserved +} +``` + +Header 总大小固定 = 4 + 4 + 8×4 = **40 字节**。 + +`SectionOffsets` 让读取方能直接跳到任意 section,无需顺序扫描。 + +--- + +## stringTable section + +``` +stringTable { + data []byte // 所有字符串连续拼接的原始字节流 +} +``` + +字符串不做任何结构化:直接把所有符号名拼接在一起。 +`Symbols` section 里的每条 record 通过 `(NameOff, NameLen)` 引用对应字节区间。 + +--- + +## Symbols section + +``` +Symbols { + NSyms uint32 + Records [NSyms]SymbolRecord +} + +SymbolRecord { // 12 字节定长 + NameOff uint32 // 符号名在 stringTable 中的起始偏移 + NameLen uint32 // 符号名长度(字节) + _ [4]byte // 保留,对齐到 12 字节 +} +``` + +**LocalSymbol = Records 数组的下标(uint32)**,在所有其他 section 中统一使用。 + +### 设计要点 + +- **内外部符号不做区分**:无论是本包定义的还是外部包引用的符号,都在此表中分配 LocalSymbol,写法完全一致。 +- **不存 flag/属性**:符号的语义属性(是否是类型、是否是接口)通过它在哪些 section 中出现来隐式表达(见「符号语义识别」章节)。 +- **幂等注册**:Builder 阶段同一个名字多次调用 `Symbol()` 返回相同的 LocalSymbol。 + +--- + +## CSR 布局(通用) + +Edges、TypeChildren、MethodInfo、InterfaceInfo 四个 section 均采用 CSR(Compressed Sparse Row)布局: + +``` +{SectionName} { + NSyms uint32 // 符号数量(= Symbols.NSyms) + Offsets [NSyms+1]uint32 // CSR offsets 数组,Offsets[NSyms] 为尾哨兵 + Data [...]T // 定长 record 的连续数组,T 因 section 而异 +} +``` + +**查询 symbol i 的数据**: + +``` +start = Offsets[i] +end = Offsets[i+1] +result = Data[start : end] // 零拷贝切片 +``` + +若 `start == end`,该符号在此 section 中无数据(正常情况,不代表错误)。 + +每个符号在 Offsets 数组中有且仅有一个 entry,**包括没有数据的符号**(它们的相邻 offset 相等)。这是 Go linker 的标准做法,每符号成本为 4 字节(一个 uint32),简单且查询 O(1)。 + +--- + +## Edges section + +``` +Edges { + NSyms uint32 + Offsets [NSyms+1]uint32 + Data []Edge +} + +Edge { // 12 字节定长 + Target uint32 // LocalSymbol(EdgeOrdinary/UseIface/UseIfaceMethod) + // 或 Name 引用(UseNamedMethod,此时为 stringTable offset) + Extra uint32 // 仅 UseIfaceMethod 使用:目标接口的方法 index + // 其他 Kind 为 0 + Kind uint8 // 边的种类(见下表) + _ [3]byte // padding +} +``` + +### Kind 定义 + +| Kind | 值 | 含义 | Target | Extra | +|------|---|------|--------|-------| +| `EdgeOrdinary` | 0 | 普通符号引用 | 目标 LocalSymbol | 0 | +| `EdgeUseIface` | 1 | 该函数将 Target 类型转为接口 | 类型 LocalSymbol | 0 | +| `EdgeUseIfaceMethod` | 2 | 该函数调用了 Target 接口的某个方法 | 接口 LocalSymbol | 方法 index | +| `EdgeUseNamedMethod` | 3 | 该函数按名调用方法(MethodByName 常量) | stringTable offset | 0 | + +### 说明 + +- `EdgeOrdinary` 涵盖所有普通符号引用:函数调用、全局变量引用、类型描述符引用等。 +- OrdinaryEdges 来自两个来源:cl/ssa 编译阶段(类型/接口相关事实)和 build 层扫描 LLVM Module 指令(普通调用/引用关系)。 +- 对应 Go linker 中 `R_CALL`、`R_ADDR` 等普通 reloc 类型(EdgeOrdinary),以及 `R_USEIFACE`、`R_USEIFACEMETHOD`、`R_USENAMEDMETHOD` marker reloc(其余三种 Kind)。 + +--- + +## TypeChildren section + +``` +TypeChildren { + NSyms uint32 + Offsets [NSyms+1]uint32 + Data []uint32 // 子类型 LocalSymbol 的连续数组 +} +``` + +记录类型描述符中包含的子类型引用: +- struct 类型 → 各字段的类型 +- 指针类型 → 指向的元素类型 +- slice/array 类型 → 元素类型 +- map 类型 → key 和 value 类型 +- chan 类型 → 元素类型 + +### 双重用途 + +1. **子类型传播**:当父类型被标记为 `usedInIface` 时,沿 TypeChildren 把 `usedInIface` 传播给所有子类型。 +2. **隐式类型识别**:`TypeChildren[sym]` 非空,则 sym 是 composite type。**不需要额外 flag**。 + +对于没有子类型的 named primitive type(如 `type Foo int`),TypeChildren 为空,`usedInIface` 只能通过 `EdgeUseIface` 直接设置,无需通过父类型传播——这是正确的语义。 + +--- + +## MethodInfo section + +``` +MethodInfo { + NSyms uint32 + Offsets [NSyms+1]uint32 + Data []MethodSlot +} + +MethodSlot { // 16 字节定长 + NameRef uint32 // 方法短名(stringTable offset) + MType uint32 // 方法函数类型符号(LocalSymbol) + IFn uint32 // 接口调用入口符号(LocalSymbol) + TFn uint32 // 类型方法入口符号(LocalSymbol) +} +``` + +- 槽位写入顺序与 Go runtime 中该类型的 `abi.Method` 表顺序**严格一致**,槽位 index 即 `abi.Method` 表中的位置。 +- **MethodInfo 中有 entry 的符号即 ConcreteType**(有方法的具体类型),不需要额外 flag。 + +--- + +## InterfaceInfo section + +``` +InterfaceInfo { + NSyms uint32 + Offsets [NSyms+1]uint32 + Data []MethodSig +} + +MethodSig { // 8 字节定长 + NameRef uint32 // 方法短名(stringTable offset) + MType uint32 // 方法函数类型符号(LocalSymbol) +} +``` + +- **InterfaceInfo 中有 entry 的符号即 Interface type**,不需要额外 flag。 +- `EdgeUseIfaceMethod` 中的 `Extra` 字段(方法 index)直接索引此处对应接口的 Data 切片。 + +--- + +## ReflectBitmap section + +``` +ReflectBitmap { + NSyms uint32 + Bitmap [(NSyms+7)/8]uint8 +} +``` + +bit `i` 为 1 表示 symbol `i` 触发了无法静态确定方法名的反射调用(`reflect.Type.Method(index)`、非常量 `MethodByName` 等)。 + +查询: + +```go +func hasReflectMethod(sym LocalSymbol) bool { + return bitmap[sym/8] & (1 << (sym%8)) != 0 +} +``` + +--- + +## 符号语义识别(无 flag 设计) + +DCE 阶段所有符号类型判断**通过 section 存在性隐式表达**,不依赖任何 flag 字段: + +| 判断 | 实现 | +|------|------| +| sym 是 composite type | `TypeChildren.Offsets[i] != TypeChildren.Offsets[i+1]` | +| sym 是 concrete type(有方法) | `MethodInfo.Offsets[i] != MethodInfo.Offsets[i+1]` | +| sym 是 interface type | `InterfaceInfo.Offsets[i] != InterfaceInfo.Offsets[i+1]` | +| sym 使用了反射 | `ReflectBitmap bit i == 1` | + +这个设计对齐了当前 MVP `analyze.go` 中 `TypeChildren`、`MethodSlots`、`InterfaceMethods` 的使用方式,无需引入额外的属性机制。 + +--- + +## mmap 读取 + +```go +type PackageMeta struct { + raw []byte // mmap 映射的字节区域,或 Builder.Build() 产出的字节缓冲 + + // 解析 Header 后缓存的各 section 起始偏移(构造时一次性计算) + strOff uint32 + symOff uint32 + edgesOff uint32 + childrenOff uint32 + methodOff uint32 + ifaceOff uint32 + reflectOff uint32 + + nsyms uint32 // 符号数量,各 section 共享 +} + +// 从文件 mmap +func ReadMeta(path string) (*PackageMeta, error) { + f, _ := os.Open(path) + raw, _ := syscall.Mmap(int(f.Fd()), 0, size, syscall.PROT_READ, syscall.MAP_SHARED) + pm := &PackageMeta{raw: raw, file: f} + pm.parseHeader() + return pm, nil +} + +// 从 Builder 直接构造(Cache MISS 路径) +func (b *Builder) Build() (*PackageMeta, error) { + raw := b.serialize() // Builder 内部 map → wire format + pm := &PackageMeta{raw: raw} + pm.parseHeader() + return pm, nil +} + +// 落盘 +func (pm *PackageMeta) Bytes() []byte { return pm.raw } + +// 查询示例(零分配,直接切 mmap 区域) +func (pm *PackageMeta) TypeChildren(sym LocalSymbol) []LocalSymbol { + section := pm.raw[pm.childrenOff:] + // nsyms := binary.LittleEndian.Uint32(section[:4]) + offsetsBase := section[4:] + start := binary.LittleEndian.Uint32(offsetsBase[sym*4:]) + end := binary.LittleEndian.Uint32(offsetsBase[(sym+1)*4:]) + if start == end { return nil } + dataBase := offsetsBase[(pm.nsyms+1)*4:] + return unsafe.Slice((*LocalSymbol)(unsafe.Pointer(&dataBase[start*4])), end-start) +} +``` + +两条路径(Cache HIT / Cache MISS)产出同一个 `*PackageMeta` 类型,后续 GlobalSummary 合并完全透明。 + +--- + +## GlobalSummary 合并(Lazy Remap) + +合并阶段**只做字符串 intern,不遍历重写边**: + +```go +type GlobalSummary struct { + intern map[string]Symbol // 字符串 → 全局 Symbol ID(O(unique_symbols) 建立) + locToGlb [][]Symbol // [pkgIdx][localSym] → global Symbol + pkgs []*PackageMeta // 各包原始数据保留,不拷贝 + + interfaces []Symbol // InterfaceInfo 有 entry 的全局 Symbol + concreteTypes []Symbol // MethodInfo 有 entry 的全局 Symbol +} + +func NewGlobalSummary(pkgs []*PackageMeta) *GlobalSummary { + g := &GlobalSummary{pkgs: pkgs} + + // 第一步:intern 所有包的字符串表,建立 locToGlb 映射 + // 成本 = O(所有包字符串总数) = O(unique_symbols 数量级) + // 不触碰任何 Edge + for pkgIdx, pkg := range pkgs { + g.locToGlb[pkgIdx] = make([]Symbol, pkg.NSyms()) + for localID := range pkg.NSyms() { + name := pkg.SymbolName(LocalSymbol(localID)) + gid := g.intern.GetOrInsert(name) + g.locToGlb[pkgIdx][localID] = gid + } + } + + // 第二步:收集 interfaces 和 concreteTypes + // 扫 InterfaceInfo 和 MethodInfo 的 key,remap 到全局 Symbol + g.buildTypeIndex() + return g +} + +// 查询时按需翻译(lazy),不预先重写 +func (g *GlobalSummary) Edges(sym Symbol) iter.Seq[Edge] { + return func(yield func(Edge) bool) { + pkgIdx, localSym := g.ownerOf(sym) + pkg := g.pkgs[pkgIdx] + for _, edge := range pkg.Edges(localSym) { + // 翻译 Target 的 LocalSymbol → global Symbol + globalEdge := Edge{ + Target: g.locToGlb[pkgIdx][edge.Target], + Extra: edge.Extra, + Kind: edge.Kind, + } + if !yield(globalEdge) { return } + } + } +} +``` + +合并成本从 **O(total_edges)** 降为 **O(unique_symbols)**,消除前面测量到的 42ms 全局合并开销。 + +--- + +## Cache 集成流程 + +``` +buildPkg(pkg): + metaPath = cachePath(actionID, ".meta") + + if exists(metaPath): + ── Cache HIT ── + aPkg.Meta = ReadMeta(metaPath) // mmap,~微秒级,无反序列化 + else: + ── Cache MISS ── + builder = NewBuilder() + ret = cl.NewPackageEx(..., builder) // cl/ssa 填入类型相关事实 + extractOrdinaryEdges(builder, ret.Module) // 扫 IR 补充普通引用边 + aPkg.Meta = builder.Build() + writeFile(metaPath, aPkg.Meta.Bytes()) // 落盘,下次命中用 + runLLVMPipeline(ret.Module) // 正常 LLVM 编译流程 + + allMetas.append(aPkg.Meta) + +全图阶段: + gs = NewGlobalSummary(allMetas) // O(unique_symbols) intern + liveSlots = deadcode.Analyze(gs) // DCE 算法,~毫秒级 + applyDCE(liveSlots) +``` + +--- + +## 版本与兼容性 + +- `Version` 字段标识格式版本,当前为 1。 +- 读到不支持的 version,直接放弃此缓存,回退到重新编译路径。 +- 格式 version 作为编译器内嵌常量参与 actionID 计算,version 升级自动 invalidate 旧缓存。 + +--- + +## 与 MVP 的对比 + +| 维度 | MVP | 本格式 | +|------|-----|--------| +| 读取方式 | 逐字节反序列化(uvarint) | mmap 零拷贝 | +| 合并方式 | 遍历全部边重写 global ID(O(edges),~42ms) | 只 intern 字符串(O(symbols),~微秒) | +| 反序列化成本 | ~50ms(read + decode) | ~微秒(mmap syscall) | +| 符号属性 | `map[Symbol]struct{}` in memory | 通过 section 存在性隐式表达,零额外存储 | +| TypeChildren | 独立 section + 函数递归展开 | 独立 section + worklist(无 Go 函数递归) | +| 是否类型判断 | `typeSymbols map` | `TypeChildren[i] 非空` | +| ReflectMethod | 独立 section | bitmap(每符号 1 bit) | +| UseIface/UseIfaceMethod/UseNamedMethod | 独立 section | 合并进 Edges,Kind 字段区分 | + +--- + +## 空间估算 + +以中等规模包(1000 符号、5000 边、200 类型、20 接口)为例: + +| Section | 大小 | +|---------|------| +| Header | 40 B | +| stringTable | ~50 KB | +| Symbols | 12 × 1000 = 12 KB | +| Edges offsets | 4 × 1001 = ~4 KB | +| Edges data | 12 × 5000 = 60 KB | +| TypeChildren offsets | 4 × 1001 = ~4 KB | +| TypeChildren data | 4 × ~500 = ~2 KB | +| MethodInfo offsets | 4 × 1001 = ~4 KB | +| MethodInfo data | 16 × 1000 = 16 KB | +| InterfaceInfo offsets | 4 × 1001 = ~4 KB | +| InterfaceInfo data | 8 × 80 = ~1 KB | +| ReflectBitmap | ~125 B | +| **合计** | **~157 KB / 包** | + +100 个包约 ~15 MB,mmap 无压力。 diff --git a/internal/build/build.go b/internal/build/build.go index 149411815f..12e999cd69 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -24,6 +24,7 @@ import ( "go/constant" "go/token" "go/types" + "io" "log" "os" "os/exec" @@ -34,6 +35,7 @@ import ( "strings" "sync" "sync/atomic" + "time" "golang.org/x/tools/go/ssa" @@ -42,12 +44,15 @@ import ( "github.com/goplus/llgo/internal/cabi" "github.com/goplus/llgo/internal/clang" "github.com/goplus/llgo/internal/crosscompile" + "github.com/goplus/llgo/internal/dcepass" + "github.com/goplus/llgo/internal/deadcode" "github.com/goplus/llgo/internal/env" "github.com/goplus/llgo/internal/firmware" "github.com/goplus/llgo/internal/flash" "github.com/goplus/llgo/internal/goembed" "github.com/goplus/llgo/internal/header" "github.com/goplus/llgo/internal/lto" + "github.com/goplus/llgo/internal/meta" "github.com/goplus/llgo/internal/mockable" "github.com/goplus/llgo/internal/monitor" "github.com/goplus/llgo/internal/optlevel" @@ -145,6 +150,7 @@ type Config struct { Verbose bool PrintCommands bool GenLL bool // generate pkg .ll files + DCE bool // enable experimental Go-like link-time DCE CheckLLFiles bool // check .ll files valid CheckLinkArgs bool // check linkargs valid ForceEspClang bool // force to use esp-clang @@ -196,6 +202,7 @@ func NewDefaultConf(mode Mode) *Config { Mode: mode, BuildMode: BuildModeExe, AbiMode: cabi.ModeAllFunc, + DCE: true, } return conf } @@ -251,6 +258,9 @@ func Do(args []string, conf *Config) ([]Package, error) { if conf.BuildMode == "" { conf.BuildMode = BuildModeExe } + if conf.BuildMode != BuildModeExe { + conf.DCE = false + } if conf.SizeReport && conf.SizeFormat == "" { conf.SizeFormat = "text" } @@ -1051,6 +1061,11 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, outputPa methodByName: methodByName, abiSymbols: linkedModuleGlobals(linkedOrder), }) + if ctx.buildConf.DCE { + if err := applyDCEOverrides(ctx, pkg, linkedOrder, entryPkg, needRuntime, verbose); err != nil { + return err + } + } entryObjFile, err := exportObject(ctx, "entry_main", entryPkg.ExportFile, entryPkg.LPkg) if err != nil { return err @@ -1106,6 +1121,104 @@ func linkedModuleGlobals(pkgs []Package) map[string]none { return seen } +func applyDCEOverrides(ctx *context, mainPkg *packages.Package, pkgs []Package, entryPkg Package, needRuntime bool, verbose bool) error { + metas := linkedPackageMetas(pkgs) + if len(metas) == 0 { + return nil + } + mergeStart := time.Now() + summary, err := meta.NewGlobalSummary(metas) + if err != nil { + return err + } + mergeDur := time.Since(mergeStart) + + roots := dceEntryRootCandidates(mainPkg, needRuntime) + analyzeStart := time.Now() + liveSlots := deadcode.Analyze(summary, roots) + analyzeDur := time.Since(analyzeStart) + + if len(liveSlots) == 0 { + return nil + } + if verbose { + liveCount := 0 + for _, slots := range liveSlots { + liveCount += len(slots) + } + fmt.Fprintf(os.Stderr, "[dce] packages=%d roots=%s merge=%v analyze=%v live method slots=%d types=%d\n", + len(metas), strings.Join(roots, ","), mergeDur, analyzeDur, liveCount, len(liveSlots)) + err := dcepass.EmitStrongTypeOverridesDebug(entryPkg.LPkg.Module(), dceSourceModules(pkgs), liveSlots, os.Stderr) + printDCEMetaInputs(ctx, pkgs, os.Stderr) + return err + } + return dcepass.EmitStrongTypeOverrides(entryPkg.LPkg.Module(), dceSourceModules(pkgs), liveSlots) +} + +func printDCEMetaInputs(ctx *context, pkgs []Package, w io.Writer) { + cm := ctx.ensureCacheManager() + targetTriple := ctx.targetTriple() + fmt.Fprintf(w, "[dce] meta inputs:\n") + for _, pkg := range pkgs { + if pkg == nil || pkg.Meta == nil { + continue + } + if pkg.Fingerprint == "" || pkg.Name == "main" { + fmt.Fprintf(w, "[dce] %s memory\n", pkg.PkgPath) + continue + } + paths := cm.PackagePaths(targetTriple, pkg.PkgPath, pkg.Fingerprint) + state := "miss" + if pkg.CacheHit { + state = "hit" + } + exists := "missing" + if _, err := os.Stat(paths.Meta); err == nil { + exists = "exists" + } + fmt.Fprintf(w, "[dce] %s %s %s %s\n", pkg.PkgPath, state, exists, paths.Meta) + } +} + +func linkedPackageMetas(pkgs []Package) []*meta.PackageMeta { + metas := make([]*meta.PackageMeta, 0, len(pkgs)) + for _, pkg := range pkgs { + if pkg == nil || pkg.Meta == nil { + continue + } + metas = append(metas, pkg.Meta) + } + return metas +} + +func dceSourceModules(pkgs []Package) []gllvm.Module { + mods := make([]gllvm.Module, 0, len(pkgs)) + seen := make(map[gllvm.Module]bool) + for _, pkg := range pkgs { + if pkg == nil || pkg.LPkg == nil { + continue + } + mod := pkg.LPkg.Module() + if mod.IsNil() || seen[mod] { + continue + } + seen[mod] = true + mods = append(mods, mod) + } + return mods +} + +func dceEntryRootCandidates(pkg *packages.Package, needRuntime bool) []string { + if pkg == nil || pkg.PkgPath == "" { + return nil + } + roots := []string{pkg.PkgPath + ".init", pkg.PkgPath + ".main"} + if needRuntime { + roots = append(roots, llssa.PkgRuntime+".init") + } + return roots +} + // isRuntimePkg reports whether the package path belongs to the llgo runtime tree. func isRuntimePkg(pkgPath string) bool { rtRoot := env.LLGoRuntimePkg @@ -1293,10 +1406,22 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error { return fmt.Errorf("load go:embed directives for %s failed: %w", pkgPath, err) } - ret, externs, err := cl.NewPackageExWithEmbed(ctx.prog, ctx.patches, aPkg.rewriteVars, aPkg.SSA, syntax, embedMap) + var metaBuilder *meta.Builder + if !aPkg.CacheHit { + metaBuilder = meta.NewBuilder() + } + ret, externs, err := cl.NewPackageExWithEmbed(ctx.prog, ctx.patches, aPkg.rewriteVars, aPkg.SSA, syntax, embedMap, metaBuilder) check(err) aPkg.LPkg = ret + if metaBuilder != nil { + extractOrdinaryEdges(metaBuilder, ret.Module()) + pm, err := metaBuilder.Build() + if err != nil { + return fmt.Errorf("build meta for %s: %w", pkgPath, err) + } + aPkg.Meta = pm + } if hook := ctx.buildConf.ModuleHook; hook != nil { hook(aPkg) } @@ -1602,6 +1727,7 @@ type aPackage struct { LinkArgs []string ObjFiles []string // object files: .o or .ll (output of compiler, input to archiver) ArchiveFile string // archive file: .a (output of archiver, used for linking) + Meta *meta.PackageMeta rewriteVars map[string]string // Cache related fields diff --git a/internal/build/cache.go b/internal/build/cache.go index f8870f6d92..f6750d3917 100644 --- a/internal/build/cache.go +++ b/internal/build/cache.go @@ -25,12 +25,14 @@ import ( "strings" "github.com/goplus/llgo/internal/env" + "github.com/goplus/llgo/internal/meta" ) const ( cacheBuildDirName = "build" cacheArchiveExt = ".a" cacheManifestExt = ".manifest" + cacheMetaExt = ".meta" ) // cacheRootFunc can be overridden for testing @@ -56,6 +58,7 @@ type cachePaths struct { Dir string // Directory containing cache files Archive string // Path to .a file Manifest string // Path to .manifest file + Meta string // Path to .meta file } // PackagePaths returns the cache paths for a package @@ -66,6 +69,7 @@ func (cm *cacheManager) PackagePaths(targetTriple, pkgPath, fingerprint string) Dir: dir, Archive: filepath.Join(dir, fingerprint+cacheArchiveExt), Manifest: filepath.Join(dir, fingerprint+cacheManifestExt), + Meta: filepath.Join(dir, fingerprint+cacheMetaExt), } } @@ -174,15 +178,55 @@ func readManifest(path string) (string, error) { return string(content), nil } +// writeMeta writes package summary metadata to a file atomically. +func writeMeta(path string, pm *meta.PackageMeta) error { + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + return fmt.Errorf("create meta dir: %w", err) + } + tmp, err := os.CreateTemp(filepath.Dir(path), filepath.Base(path)+".tmp-*") + if err != nil { + return fmt.Errorf("create temp meta: %w", err) + } + tmpName := tmp.Name() + cleanup := true + defer func() { + if cleanup { + tmp.Close() + os.Remove(tmpName) + } + }() + + if _, err := tmp.Write(pm.Bytes()); err != nil { + return fmt.Errorf("write meta: %w", err) + } + if err := tmp.Close(); err != nil { + return fmt.Errorf("close meta: %w", err) + } + if err := os.Rename(tmpName, path); err != nil { + return fmt.Errorf("publish meta: %w", err) + } + + cleanup = false + return nil +} + +// readMeta reads package summary metadata from a file. +func readMeta(path string) (*meta.PackageMeta, error) { + return meta.ReadMeta(path) +} + // cacheExists checks if a valid cache entry exists func (cm *cacheManager) cacheExists(paths cachePaths) bool { - // Both archive and manifest must exist + // Archive, manifest, and valid package metadata must exist. if _, err := os.Stat(paths.Archive); err != nil { return false } if _, err := os.Stat(paths.Manifest); err != nil { return false } + if _, err := readMeta(paths.Meta); err != nil { + return false + } return true } diff --git a/internal/build/cache_test.go b/internal/build/cache_test.go index 6c6d4a0eb2..5bf7600594 100644 --- a/internal/build/cache_test.go +++ b/internal/build/cache_test.go @@ -23,6 +23,8 @@ import ( "path/filepath" "strings" "testing" + + "github.com/goplus/llgo/internal/meta" ) func TestSanitizePkgPath(t *testing.T) { @@ -69,6 +71,11 @@ func TestCacheManager_PackagePaths(t *testing.T) { if paths.Manifest != expectedManifest { t.Errorf("Manifest = %q, want %q", paths.Manifest, expectedManifest) } + + expectedMeta := filepath.Join(expectedDir, "abc123.meta") + if paths.Meta != expectedMeta { + t.Errorf("Meta = %q, want %q", paths.Meta, expectedMeta) + } } func TestCacheManager_EnsureDir(t *testing.T) { @@ -177,9 +184,34 @@ func TestCacheManager_CacheExists(t *testing.T) { // Create manifest os.WriteFile(paths.Manifest, []byte("manifest"), 0644) + // Still should not exist (meta missing) + if cm.cacheExists(paths) { + t.Error("cache should not exist without meta") + } + + // Create invalid meta + os.WriteFile(paths.Meta, []byte("bad meta"), 0644) + if cm.cacheExists(paths) { + t.Error("cache should not exist with invalid meta") + } + + // Create valid meta + writeTestMetaFile(t, paths.Meta) + // Now should exist if !cm.cacheExists(paths) { - t.Error("cache should exist with both files") + t.Error("cache should exist with archive, manifest, and valid meta") + } +} + +func writeTestMetaFile(t *testing.T, path string) { + t.Helper() + pm, err := meta.NewBuilder().Build() + if err != nil { + t.Fatalf("Build: %v", err) + } + if err := os.WriteFile(path, pm.Bytes(), 0o644); err != nil { + t.Fatalf("WriteFile %s: %v", path, err) } } diff --git a/internal/build/collect.go b/internal/build/collect.go index ab6d7076f6..ecd0ed3b88 100644 --- a/internal/build/collect.go +++ b/internal/build/collect.go @@ -27,6 +27,7 @@ import ( "strings" "github.com/goplus/llgo/internal/env" + "github.com/goplus/llgo/internal/meta" "github.com/goplus/llgo/internal/packages" gopackages "golang.org/x/tools/go/packages" ) @@ -347,6 +348,10 @@ func (c *context) tryLoadFromCache(pkg *aPackage) bool { if err != nil { return false } + pkgMeta, err := readMeta(paths.Meta) + if err != nil { + return false + } // Parse metadata from manifest [Package] section (INI format) meta, err := parseManifestMetadata(content) @@ -359,6 +364,7 @@ func (c *context) tryLoadFromCache(pkg *aPackage) bool { pkg.LinkArgs = meta.LinkArgs pkg.NeedRt = meta.NeedRt pkg.NeedPyInit = meta.NeedPyInit + pkg.Meta = pkgMeta pkg.CacheHit = true return true @@ -466,6 +472,13 @@ func (c *context) saveToCache(pkg *aPackage) error { return nil } + if pkg.Meta == nil { + pkg.Meta, _ = meta.NewBuilder().Build() + } + if err := writeMeta(paths.Meta, pkg.Meta); err != nil { + return err + } + // Append metadata to existing manifest (pkg.Manifest was built in collectFingerprint). manifestContent := pkg.Manifest if manifestContent == "" { diff --git a/internal/build/collect_test.go b/internal/build/collect_test.go index bfa52ead8c..5524878fc9 100644 --- a/internal/build/collect_test.go +++ b/internal/build/collect_test.go @@ -29,6 +29,7 @@ import ( "github.com/goplus/llgo/internal/buildenv" "github.com/goplus/llgo/internal/crosscompile" "github.com/goplus/llgo/internal/lto" + "github.com/goplus/llgo/internal/meta" "github.com/goplus/llgo/internal/packages" gopackages "golang.org/x/tools/go/packages" ) @@ -467,6 +468,7 @@ func TestTryLoadFromCache_ForceRebuild(t *testing.T) { m.pkg.PkgPath = "example.com/cached" return m.Build() }(), + Meta: func() *meta.PackageMeta { pm, _ := meta.NewBuilder().Build(); return pm }(), } // Create a temporary .o file @@ -639,6 +641,7 @@ func TestSaveToCache_Success(t *testing.T) { return m.Build() }(), ObjFiles: []string{objFile.Name()}, + Meta: func() *meta.PackageMeta { pm, _ := meta.NewBuilder().Build(); return pm }(), } if err := ctx.saveToCache(pkg); err != nil { @@ -669,6 +672,137 @@ func TestSaveToCache_Success(t *testing.T) { if _, err := os.Stat(paths.Archive); err != nil { t.Errorf("archive should exist: %v", err) } + + metaFile, err := os.Open(paths.Meta) + if err != nil { + t.Errorf("meta should exist: %v", err) + } else { + defer metaFile.Close() + if _, err := meta.ReadMeta(metaFile.Name()); err != nil { + t.Errorf("meta should be readable: %v", err) + } + } +} + +func TestTryLoadFromCache_LoadsPackageMeta(t *testing.T) { + td := t.TempDir() + oldFunc := cacheRootFunc + cacheRootFunc = func() string { return td } + defer func() { cacheRootFunc = oldFunc }() + + ctx := &context{ + conf: &packages.Config{}, + buildConf: &Config{ + Goos: "darwin", + Goarch: "arm64", + }, + crossCompile: crosscompile.Export{ + LLVMTarget: "arm64-apple-darwin", + }, + } + + objFile, err := os.CreateTemp(td, "test-*.o") + if err != nil { + t.Fatalf("CreateTemp: %v", err) + } + objFile.WriteString("fake object file") + objFile.Close() + + builder := meta.NewBuilder() + main := builder.Sym("pkg.main") + helper := builder.Sym("pkg.helper") + builder.AddEdge(main, helper, meta.EdgeOrdinary, 0) + + pkg := &aPackage{ + Package: &packages.Package{ + PkgPath: "example.com/loadmeta", + Name: "loadmeta", + }, + Fingerprint: "loadmeta123", + Manifest: func() string { + m := newManifestBuilder() + m.env.Goos = "darwin" + m.pkg.PkgPath = "example.com/loadmeta" + return m.Build() + }(), + ObjFiles: []string{objFile.Name()}, + } + pkg.Meta, _ = builder.Build() + + if err := ctx.saveToCache(pkg); err != nil { + t.Fatalf("saveToCache: %v", err) + } + + pkg.ObjFiles = nil + pkg.ArchiveFile = "" + pkg.CacheHit = false + pkg.Meta = nil + + if !ctx.tryLoadFromCache(pkg) { + t.Fatal("tryLoadFromCache = false, want true") + } + if pkg.Meta == nil { + t.Fatal("Meta was not loaded from cache") + } + summary, err := meta.NewGlobalSummary([]*meta.PackageMeta{pkg.Meta}) + if err != nil { + t.Fatalf("NewGlobalSummary: %v", err) + } + mainSym, ok := summary.LookupSymbol("pkg.main") + if !ok { + t.Fatal("pkg.main not found in cached metadata") + } + edges := summary.OrdinaryEdges(mainSym) + if len(edges) != 1 || summary.SymbolName(edges[0]) != "pkg.helper" { + t.Fatalf("cached metadata edge mismatch: %#v", edges) + } +} + +func TestTryLoadFromCacheRejectsBadMeta(t *testing.T) { + td := t.TempDir() + oldFunc := cacheRootFunc + cacheRootFunc = func() string { return td } + defer func() { cacheRootFunc = oldFunc }() + + ctx := &context{ + conf: &packages.Config{}, + buildConf: &Config{ + Goos: "darwin", + Goarch: "arm64", + }, + crossCompile: crosscompile.Export{ + LLVMTarget: "arm64-apple-darwin", + }, + } + + pkg := &aPackage{ + Package: &packages.Package{ + PkgPath: "example.com/badmeta", + Name: "badmeta", + }, + Fingerprint: "badmeta123", + } + cm := ctx.ensureCacheManager() + paths := cm.PackagePaths("arm64-apple-darwin", "example.com/badmeta", "badmeta123") + if err := cm.EnsureDir(paths); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(paths.Archive, []byte("archive"), 0o644); err != nil { + t.Fatal(err) + } + m := newManifestBuilder() + m.env.Goos = "darwin" + m.pkg.PkgPath = "example.com/badmeta" + if err := writeManifest(paths.Manifest, m.Build()); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(paths.Meta, []byte("bad meta"), 0o644); err != nil { + t.Fatal(err) + } + + if ctx.tryLoadFromCache(pkg) { + t.Fatal("tryLoadFromCache accepted invalid meta") + } } func TestGetLLVMVersion(t *testing.T) { diff --git a/internal/build/dce_override_test.go b/internal/build/dce_override_test.go new file mode 100644 index 0000000000..5640eda737 --- /dev/null +++ b/internal/build/dce_override_test.go @@ -0,0 +1,123 @@ +//go:build !llgo +// +build !llgo + +package build + +import ( + "strings" + "testing" + + "github.com/goplus/llgo/internal/meta" + "github.com/goplus/llgo/internal/packages" + llssa "github.com/goplus/llgo/ssa" + "github.com/xgo-dev/llvm" +) + +func TestApplyDCEOverridesWritesStrongTypeOverride(t *testing.T) { + llssa.Initialize(llssa.InitAll) + ctx := &context{ + prog: llssa.NewProgram(nil), + buildConf: &Config{ + BuildMode: BuildModeExe, + Goos: "linux", + Goarch: "amd64", + DCE: true, + }, + } + + srcPkg := ctx.prog.NewPackage("pkg", "pkg") + srcMod := srcPkg.Module() + addMethodTypeGlobal(t, srcMod, "_llgo_pkg.T") + + pkgMeta := buildDCEMeta() + srcAPkg := &aPackage{ + Package: &packages.Package{PkgPath: "pkg"}, + LPkg: srcPkg, + Meta: pkgMeta, + } + entryPkg := genMainModule(ctx, llssa.PkgRuntime, &packages.Package{ + PkgPath: "pkg", + ExportFile: "pkg.a", + }, &genConfig{}) + + if err := applyDCEOverrides(ctx, &packages.Package{PkgPath: "pkg"}, []Package{srcAPkg}, entryPkg, false, false); err != nil { + t.Fatalf("applyDCEOverrides: %v", err) + } + + out := entryPkg.LPkg.Module().String() + if !strings.Contains(out, `@_llgo_pkg.T = constant`) { + t.Fatalf("entry module missing strong type override:\n%s", out) + } + if !strings.Contains(out, `ptr @"pkg.(*T).M", ptr @pkg.T.M`) { + t.Fatalf("live method slot was not preserved:\n%s", out) + } + if strings.Contains(out, `ptr @"pkg.(*T).N"`) || strings.Contains(out, `ptr @pkg.T.N`) { + t.Fatalf("dead method slot still references N functions:\n%s", out) + } +} + +func TestDCEEntryRootCandidatesIncludesRuntimeWhenNeeded(t *testing.T) { + roots := dceEntryRootCandidates(&packages.Package{PkgPath: "pkg"}, true) + want := []string{"pkg.init", "pkg.main", llssa.PkgRuntime + ".init"} + if strings.Join(roots, "\n") != strings.Join(want, "\n") { + t.Fatalf("roots mismatch:\ngot %q\nwant %q", roots, want) + } +} + +func TestDCEEntryRootCandidatesSkipsRuntimeWhenNotNeeded(t *testing.T) { + roots := dceEntryRootCandidates(&packages.Package{PkgPath: "pkg"}, false) + want := []string{"pkg.init", "pkg.main"} + if strings.Join(roots, "\n") != strings.Join(want, "\n") { + t.Fatalf("roots mismatch:\ngot %q\nwant %q", roots, want) + } +} + +func buildDCEMeta() *meta.PackageMeta { + b := meta.NewBuilder() + main := b.Sym("pkg.main") + use := b.Sym("pkg.use") + typ := b.Sym("_llgo_pkg.T") + iface := b.Sym("_llgo_iface$I") + mtype := b.Sym("_llgo_func$X") + + b.AddIfaceMethod(iface, "M", mtype) + b.AddMethodSlot(typ, "M", mtype, b.Sym("pkg.(*T).M"), b.Sym("pkg.T.M")) + b.AddMethodSlot(typ, "N", mtype, b.Sym("pkg.(*T).N"), b.Sym("pkg.T.N")) + b.AddEdge(main, use, meta.EdgeOrdinary, 0) + b.AddEdge(main, typ, meta.EdgeOrdinary, 0) + b.AddEdge(main, typ, meta.EdgeUseIface, 0) + b.AddEdge(use, iface, meta.EdgeUseIfaceMethod, 0) // M is index 0 in iface + pm, _ := b.Build() + return pm +} + +func addMethodTypeGlobal(t *testing.T, mod llvm.Module, name string) { + t.Helper() + ctx := mod.Context() + voidTy := ctx.VoidType() + fnTy := llvm.FunctionType(voidTy, nil, false) + ptrTy := llvm.PointerType(fnTy, 0) + stringTy := ctx.StructCreateNamed("runtime/internal/runtime.String") + stringTy.StructSetBody([]llvm.Type{llvm.PointerType(ctx.Int8Type(), 0), ctx.Int64Type()}, false) + methodTy := ctx.StructCreateNamed("github.com/goplus/llgo/runtime/abi.Method") + methodTy.StructSetBody([]llvm.Type{stringTy, ptrTy, ptrTy, ptrTy}, false) + + mtyp := llvm.AddGlobal(mod, ptrTy, "mtyp") + ifnM := llvm.AddFunction(mod, "pkg.(*T).M", fnTy) + tfnM := llvm.AddFunction(mod, "pkg.T.M", fnTy) + ifnN := llvm.AddFunction(mod, "pkg.(*T).N", fnTy) + tfnN := llvm.AddFunction(mod, "pkg.T.N", fnTy) + methods := llvm.ConstArray(methodTy, []llvm.Value{ + llvm.ConstNamedStruct(methodTy, []llvm.Value{llvm.ConstNull(stringTy), mtyp, ifnM, tfnM}), + llvm.ConstNamedStruct(methodTy, []llvm.Value{llvm.ConstNull(stringTy), mtyp, ifnN, tfnN}), + }) + typeTy := ctx.StructCreateNamed("pkg.T.type") + typeTy.StructSetBody([]llvm.Type{ctx.Int8Type(), methods.Type()}, false) + typeDesc := llvm.AddGlobal(mod, typeTy, name) + typeDesc.SetGlobalConstant(true) + typeDesc.SetLinkage(llvm.LinkOnceODRLinkage) + typeDesc.SetInitializer(llvm.ConstNamedStruct(typeTy, []llvm.Value{ + llvm.ConstNull(ctx.Int8Type()), + methods, + })) +} diff --git a/internal/build/metadata_edges.go b/internal/build/metadata_edges.go new file mode 100644 index 0000000000..426a47eaa1 --- /dev/null +++ b/internal/build/metadata_edges.go @@ -0,0 +1,141 @@ +package build + +import ( + "strings" + + "github.com/goplus/llgo/internal/meta" + "github.com/xgo-dev/llvm" +) + +func extractOrdinaryEdges(builder *meta.Builder, mod llvm.Module) { + if builder == nil { + return + } + for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { + src := fn.Name() + if src == "" || fn.IsDeclaration() { + continue + } + collector := ordinaryEdgeCollector{builder: builder, src: src} + for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) { + for instr := bb.FirstInstruction(); !instr.IsNil(); instr = llvm.NextInstruction(instr) { + collector.scanOperands(instr) + } + } + } + for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) { + src := global.Name() + if src == "" { + continue + } + init := global.Initializer() + if init.IsNil() { + continue + } + collector := ordinaryEdgeCollector{builder: builder, src: src} + collector.scanGlobalInitializer(init) + } +} + +type ordinaryEdgeCollector struct { + builder *meta.Builder + src string + seen map[llvm.Value]struct{} + addedDst map[string]struct{} // dedup (src, dst) pairs +} + +func (c *ordinaryEdgeCollector) scanGlobalInitializer(v llvm.Value) { + if isUncommonTypeInitializer(v) { + for i, n := 0, v.OperandsCount(); i < n; i++ { + if i == 2 { + continue + } + c.scan(v.Operand(i)) + } + return + } + c.scan(v) +} + +func (c *ordinaryEdgeCollector) scanOperands(v llvm.Value) { + for i, n := 0, v.OperandsCount(); i < n; i++ { + c.scan(v.Operand(i)) + } +} + +func (c *ordinaryEdgeCollector) scan(v llvm.Value) { + if v.IsNil() { + return + } + if isMethodTable(v) { + return + } + if name := namedModuleSymbol(v); name != "" { + c.add(name) + return + } + if v.IsAConstant().IsNil() { + return + } + if c.seen == nil { + c.seen = make(map[llvm.Value]struct{}) + } + if _, ok := c.seen[v]; ok { + return + } + c.seen[v] = struct{}{} + for i, n := 0, v.OperandsCount(); i < n; i++ { + c.scan(v.Operand(i)) + } +} + +func (c *ordinaryEdgeCollector) add(dst string) { + if dst == "" || dst == c.src { + return + } + if c.addedDst == nil { + c.addedDst = make(map[string]struct{}) + } + if _, ok := c.addedDst[dst]; ok { + return + } + c.addedDst[dst] = struct{}{} + c.builder.AddEdge(c.builder.Sym(c.src), c.builder.Sym(dst), meta.EdgeOrdinary, 0) +} + +func namedModuleSymbol(v llvm.Value) string { + if !v.IsAFunction().IsNil() || !v.IsAGlobalVariable().IsNil() { + return v.Name() + } + return "" +} + +func isUncommonTypeInitializer(v llvm.Value) bool { + if v.IsAConstantStruct().IsNil() || v.OperandsCount() != 3 { + return false + } + return isMethodTable(v.Operand(2)) +} + +func isMethodTable(v llvm.Value) bool { + if v.IsNil() || v.IsAConstantArray().IsNil() || v.OperandsCount() == 0 { + return false + } + methodTy := v.Type().ElementType() + if methodTy.TypeKind() != llvm.StructTypeKind { + return false + } + if strings.HasSuffix(methodTy.StructName(), ".Method") { + return true + } + for i, n := 0, v.OperandsCount(); i < n; i++ { + method := v.Operand(i) + if method.IsAConstantStruct().IsNil() || method.OperandsCount() != 4 { + return false + } + if namedModuleSymbol(method.Operand(2)) == "" || namedModuleSymbol(method.Operand(3)) == "" { + return false + } + } + return true +} diff --git a/internal/build/metadata_edges_test.go b/internal/build/metadata_edges_test.go new file mode 100644 index 0000000000..7ee9abb55c --- /dev/null +++ b/internal/build/metadata_edges_test.go @@ -0,0 +1,102 @@ +package build + +import ( + "testing" + + "github.com/goplus/llgo/internal/meta" + "github.com/xgo-dev/llvm" +) + +func TestExtractOrdinaryEdgesFromFunctionAndGlobal(t *testing.T) { + ctx := llvm.NewContext() + defer ctx.Dispose() + mod := ctx.NewModule("ordinary") + defer mod.Dispose() + + voidTy := ctx.VoidType() + fnTy := llvm.FunctionType(voidTy, nil, false) + mainFn := llvm.AddFunction(mod, "pkg.main", fnTy) + helperFn := llvm.AddFunction(mod, "pkg.helper", fnTy) + + b := ctx.NewBuilder() + defer b.Dispose() + entry := ctx.AddBasicBlock(mainFn, "entry") + b.SetInsertPointAtEnd(entry) + b.CreateCall(fnTy, helperFn, nil, "") + b.CreateRetVoid() + + global := llvm.AddGlobal(mod, llvm.PointerType(fnTy, 0), "pkg.global") + global.SetInitializer(helperFn) + + mb := meta.NewBuilder() + extractOrdinaryEdges(mb, mod) + pm, _ := mb.Build() + + if !hasOrdinaryEdge(pm, "pkg.main", "pkg.helper") { + t.Fatalf("missing ordinary edge pkg.main -> pkg.helper") + } + if !hasOrdinaryEdge(pm, "pkg.global", "pkg.helper") { + t.Fatalf("missing ordinary edge pkg.global -> pkg.helper") + } +} + +func TestExtractOrdinaryEdgesSkipsUncommonMethodTable(t *testing.T) { + ctx := llvm.NewContext() + defer ctx.Dispose() + mod := ctx.NewModule("ordinary") + defer mod.Dispose() + + voidTy := ctx.VoidType() + fnTy := llvm.FunctionType(voidTy, nil, false) + ifn := llvm.AddFunction(mod, "pkg.(*T).M", fnTy) + tfn := llvm.AddFunction(mod, "pkg.T.M", fnTy) + + i8ptrTy := llvm.PointerType(ctx.Int8Type(), 0) + methodTy := ctx.StructCreateNamed("runtime/internal/runtime.Method") + methodTy.StructSetBody([]llvm.Type{i8ptrTy, i8ptrTy, llvm.PointerType(fnTy, 0), llvm.PointerType(fnTy, 0)}, false) + methods := llvm.ConstArray(methodTy, []llvm.Value{ + llvm.ConstNamedStruct(methodTy, []llvm.Value{ + llvm.ConstNull(i8ptrTy), + llvm.ConstNull(i8ptrTy), + ifn, + tfn, + }), + }) + + typeTy := ctx.StructCreateNamed("pkg.T.type") + typeTy.StructSetBody([]llvm.Type{i8ptrTy, i8ptrTy, methods.Type()}, false) + typeDesc := llvm.AddGlobal(mod, typeTy, "_llgo_pkg.T") + typeDesc.SetInitializer(llvm.ConstNamedStruct(typeTy, []llvm.Value{ + llvm.ConstNull(i8ptrTy), + llvm.ConstNull(i8ptrTy), + methods, + })) + + mb := meta.NewBuilder() + extractOrdinaryEdges(mb, mod) + pm, _ := mb.Build() + + if hasOrdinaryEdge(pm, "_llgo_pkg.T", "pkg.(*T).M") { + t.Fatalf("method table IFn was recorded as an ordinary edge") + } + if hasOrdinaryEdge(pm, "_llgo_pkg.T", "pkg.T.M") { + t.Fatalf("method table TFn was recorded as an ordinary edge") + } +} + +func hasOrdinaryEdge(pm *meta.PackageMeta, srcName, dstName string) bool { + summary, err := meta.NewGlobalSummary([]*meta.PackageMeta{pm}) + if err != nil { + return false + } + src, ok := summary.LookupSymbol(srcName) + if !ok { + return false + } + for _, dst := range summary.OrdinaryEdges(src) { + if summary.SymbolName(dst) == dstName { + return true + } + } + return false +} diff --git a/internal/dcepass/dcepass.go b/internal/dcepass/dcepass.go new file mode 100644 index 0000000000..507069fc5a --- /dev/null +++ b/internal/dcepass/dcepass.go @@ -0,0 +1,332 @@ +// Package dcepass rewrites ABI metadata so link-time dead code elimination can +// drop method bodies that are no longer referenced by live method slots. +package dcepass + +import ( + "fmt" + "io" + "strings" + + "github.com/xgo-dev/llvm" +) + +const unreachableMethodName = "github.com/goplus/llgo/runtime/internal/runtime.unreachableMethod" + +// EmitStrongTypeOverrides emits method-pruned strong ABI type symbols into dst. +// +// srcMods contain the original package modules. For each constant ABI type +// global with a method array, this function creates a same-name strong global in +// dst and clears IFn/TFn for method slots not listed in liveSlots[typeName]. +func EmitStrongTypeOverrides(dst llvm.Module, srcMods []llvm.Module, liveSlots map[string][]int) error { + return EmitStrongTypeOverridesDebug(dst, srcMods, liveSlots, nil) +} + +// EmitStrongTypeOverridesDebug is like EmitStrongTypeOverrides, and writes one +// debug line per method slot whose IFn/TFn references are cleared when logw is +// non-nil. +func EmitStrongTypeOverridesDebug(dst llvm.Module, srcMods []llvm.Module, liveSlots map[string][]int, logw io.Writer) error { + if dst.IsNil() { + return fmt.Errorf("destination module is nil") + } + emitted := make(map[string]bool) + emitter := newOverrideEmitter(dst) + for _, src := range srcMods { + if src.IsNil() { + continue + } + for g := src.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) { + name := g.Name() + if name == "" || emitted[name] || !g.IsGlobalConstant() { + continue + } + init := g.Initializer() + if init.IsNil() { + continue + } + methodsVal, elemTy, ok := methodArray(init) + if !ok || methodsVal.OperandsCount() == 0 { + continue + } + if err := emitter.emitTypeOverride(g, methodsVal, elemTy, liveSlotSet(liveSlots[name]), logw); err != nil { + return fmt.Errorf("emit override %q: %w", name, err) + } + emitted[name] = true + } + } + return nil +} + +type overrideEmitter struct { + dst llvm.Module + values map[llvm.Value]llvm.Value +} + +func newOverrideEmitter(dst llvm.Module) *overrideEmitter { + return &overrideEmitter{ + dst: dst, + values: make(map[llvm.Value]llvm.Value), + } +} + +func (e *overrideEmitter) emitTypeOverride(srcType, methodsVal llvm.Value, elemTy llvm.Type, keepIdx map[int]bool, logw io.Writer) error { + init := srcType.Initializer() + dstType, err := e.ensureOverrideGlobal(srcType) + if err != nil { + return err + } + e.values[srcType] = dstType + + fieldCount := init.OperandsCount() + fields := make([]llvm.Value, fieldCount) + for i := 0; i < fieldCount-1; i++ { + clone, err := e.cloneConst(init.Operand(i)) + if err != nil { + return err + } + fields[i] = clone + } + + elemFields := elemTy.StructElementTypes() + if len(elemFields) < 4 { + return fmt.Errorf("method element type has %d fields", len(elemFields)) + } + unreachableMethod := e.unreachableMethod(elemFields[2]) + methodCount := methodsVal.OperandsCount() + methods := make([]llvm.Value, methodCount) + for i := 0; i < methodCount; i++ { + orig := methodsVal.Operand(i) + if keepIdx[i] { + clone, err := e.cloneConst(orig) + if err != nil { + return err + } + methods[i] = clone + continue + } + if logw != nil { + fmt.Fprintf(logw, "[dce] drop method %s[%d] ifn=%s tfn=%s\n", srcType.Name(), i, valueName(orig.Operand(2)), valueName(orig.Operand(3))) + } + nameField, err := e.cloneConst(orig.Operand(0)) + if err != nil { + return err + } + mtypField, err := e.cloneConst(orig.Operand(1)) + if err != nil { + return err + } + methods[i] = llvm.ConstNamedStruct(elemTy, []llvm.Value{nameField, mtypField, unreachableMethod, unreachableMethod}) + } + fields[fieldCount-1] = llvm.ConstArray(elemTy, methods) + + dstType.SetInitializer(constStructOfType(init.Type(), fields)) + dstType.SetGlobalConstant(true) + dstType.SetLinkage(llvm.ExternalLinkage) + copyGlobalAttrs(dstType, srcType) + return nil +} + +func (e *overrideEmitter) unreachableMethod(ptrTy llvm.Type) llvm.Value { + fn := e.dst.NamedFunction(unreachableMethodName) + if fn.IsNil() { + fnTy := llvm.FunctionType(e.dst.Context().VoidType(), nil, false) + fn = llvm.AddFunction(e.dst, unreachableMethodName, fnTy) + } + if fn.Type() == ptrTy { + return fn + } + return llvm.ConstBitCast(fn, ptrTy) +} + +func (e *overrideEmitter) ensureOverrideGlobal(src llvm.Value) (llvm.Value, error) { + name := src.Name() + if name == "" { + return llvm.Value{}, fmt.Errorf("type global has empty name") + } + dst := e.dst.NamedGlobal(name) + if dst.IsNil() { + dst = llvm.AddGlobal(e.dst, src.GlobalValueType(), name) + } + e.values[src] = dst + return dst, nil +} + +func (e *overrideEmitter) cloneConst(v llvm.Value) (llvm.Value, error) { + if v.IsNil() { + return llvm.Value{}, nil + } + if mapped, ok := e.values[v]; ok { + return mapped, nil + } + if gv := v.IsAGlobalValue(); !gv.IsNil() { + clone, err := e.cloneGlobalValue(gv) + if err != nil { + return llvm.Value{}, err + } + e.values[v] = clone + return clone, nil + } + if !v.IsAConstantStruct().IsNil() { + ops, err := e.cloneOperands(v) + if err != nil { + return llvm.Value{}, err + } + clone := constStructOfType(v.Type(), ops) + e.values[v] = clone + return clone, nil + } + return v, nil +} + +func (e *overrideEmitter) cloneOperands(v llvm.Value) ([]llvm.Value, error) { + n := v.OperandsCount() + ops := make([]llvm.Value, n) + for i := 0; i < n; i++ { + clone, err := e.cloneConst(v.Operand(i)) + if err != nil { + return nil, err + } + ops[i] = clone + } + return ops, nil +} + +func (e *overrideEmitter) cloneGlobalValue(v llvm.Value) (llvm.Value, error) { + if mapped, ok := e.values[v]; ok { + return mapped, nil + } + if fn := v.IsAFunction(); !fn.IsNil() { + name := fn.Name() + if name == "" { + return llvm.Value{}, fmt.Errorf("function ref has empty name") + } + dstFn := e.dst.NamedFunction(name) + if dstFn.IsNil() { + dstFn = llvm.AddFunction(e.dst, name, fn.GlobalValueType()) + } + e.values[v] = dstFn + return dstFn, nil + } + if gv := v.IsAGlobalVariable(); !gv.IsNil() { + clone, err := e.cloneGlobalVariable(gv) + if err != nil { + return llvm.Value{}, err + } + e.values[v] = clone + return clone, nil + } + name := v.Name() + if name == "" { + return llvm.Value{}, fmt.Errorf("unsupported unnamed global ref") + } + dstG := e.dst.NamedGlobal(name) + if dstG.IsNil() { + dstG = llvm.AddGlobal(e.dst, v.GlobalValueType(), name) + dstG.SetLinkage(llvm.ExternalLinkage) + } + e.values[v] = dstG + return dstG, nil +} + +func (e *overrideEmitter) cloneGlobalVariable(src llvm.Value) (llvm.Value, error) { + if mapped, ok := e.values[src]; ok { + return mapped, nil + } + name := src.Name() + local := name == "" || isLocalLinkage(src.Linkage()) + if !local { + dst := e.dst.NamedGlobal(name) + if dst.IsNil() { + dst = llvm.AddGlobal(e.dst, src.GlobalValueType(), name) + dst.SetLinkage(llvm.ExternalLinkage) + } + e.values[src] = dst + return dst, nil + } + + dst := llvm.AddGlobal(e.dst, src.GlobalValueType(), "") + e.values[src] = dst + copyGlobalAttrs(dst, src) + dst.SetLinkage(src.Linkage()) + dst.SetGlobalConstant(src.IsGlobalConstant()) + if init := src.Initializer(); !init.IsNil() { + cloneInit, err := e.cloneConst(init) + if err != nil { + return llvm.Value{}, err + } + dst.SetInitializer(cloneInit) + } + return dst, nil +} + +func methodArray(init llvm.Value) (llvm.Value, llvm.Type, bool) { + fieldCount := init.OperandsCount() + if fieldCount == 0 { + return llvm.Value{}, llvm.Type{}, false + } + methodsVal := init.Operand(fieldCount - 1) + if methodsVal.Type().TypeKind() != llvm.ArrayTypeKind { + return llvm.Value{}, llvm.Type{}, false + } + elemTy := methodsVal.Type().ElementType() + if elemTy.TypeKind() != llvm.StructTypeKind { + return llvm.Value{}, llvm.Type{}, false + } + if elemTy.StructElementTypesCount() != 4 { + return llvm.Value{}, llvm.Type{}, false + } + if !strings.Contains(elemTy.StructName(), "runtime/abi.Method") { + return llvm.Value{}, llvm.Type{}, false + } + return methodsVal, elemTy, true +} + +func liveSlotSet(slots []int) map[int]bool { + out := make(map[int]bool, len(slots)) + for _, slot := range slots { + out[slot] = true + } + return out +} + +func copyGlobalAttrs(dst, src llvm.Value) { + dst.SetVisibility(src.Visibility()) + if sec := src.Section(); sec != "" { + dst.SetSection(sec) + } + dst.SetThreadLocal(src.IsThreadLocal()) + if align := src.Alignment(); align > 0 { + dst.SetAlignment(align) + } +} + +func isLocalLinkage(linkage llvm.Linkage) bool { + return linkage == llvm.PrivateLinkage || linkage == llvm.InternalLinkage +} + +func valueName(v llvm.Value) string { + if v.IsNil() { + return "" + } + if !v.IsAGlobalValue().IsNil() { + if name := v.Name(); name != "" { + return name + } + } + if !v.IsAConstantExpr().IsNil() && v.OperandsCount() > 0 { + return valueName(v.Operand(0)) + } + if !v.IsAConstantPointerNull().IsNil() || v.IsNull() { + return "" + } + if name := v.Name(); name != "" { + return name + } + return v.String() +} + +func constStructOfType(typ llvm.Type, fields []llvm.Value) llvm.Value { + if typ.StructName() != "" { + return llvm.ConstNamedStruct(typ, fields) + } + return llvm.ConstStruct(fields, typ.IsStructPacked()) +} diff --git a/internal/dcepass/dcepass_test.go b/internal/dcepass/dcepass_test.go new file mode 100644 index 0000000000..38e7456c2c --- /dev/null +++ b/internal/dcepass/dcepass_test.go @@ -0,0 +1,127 @@ +package dcepass + +import ( + "bytes" + "strings" + "testing" + + "github.com/xgo-dev/llvm" +) + +func TestEmitStrongTypeOverridesPrunesDeadMethodSlots(t *testing.T) { + ctx := llvm.NewContext() + defer ctx.Dispose() + + src := ctx.NewModule("src") + defer src.Dispose() + dst := ctx.NewModule("dst") + defer dst.Dispose() + + voidTy := ctx.VoidType() + fnTy := llvm.FunctionType(voidTy, nil, false) + ptrTy := llvm.PointerType(fnTy, 0) + stringTy := ctx.StructCreateNamed("runtime/internal/runtime.String") + stringTy.StructSetBody([]llvm.Type{llvm.PointerType(ctx.Int8Type(), 0), ctx.Int64Type()}, false) + methodTy := ctx.StructCreateNamed("github.com/goplus/llgo/runtime/abi.Method") + methodTy.StructSetBody([]llvm.Type{stringTy, ptrTy, ptrTy, ptrTy}, false) + + mtyp := llvm.AddGlobal(src, ptrTy, "mtyp") + ifnM := llvm.AddFunction(src, "pkg.(*T).M", fnTy) + tfnM := llvm.AddFunction(src, "pkg.T.M", fnTy) + ifnN := llvm.AddFunction(src, "pkg.(*T).N", fnTy) + tfnN := llvm.AddFunction(src, "pkg.T.N", fnTy) + + methods := llvm.ConstArray(methodTy, []llvm.Value{ + llvm.ConstNamedStruct(methodTy, []llvm.Value{ + llvm.ConstNull(stringTy), + mtyp, + ifnM, + tfnM, + }), + llvm.ConstNamedStruct(methodTy, []llvm.Value{ + llvm.ConstNull(stringTy), + mtyp, + ifnN, + tfnN, + }), + }) + typeTy := ctx.StructCreateNamed("pkg.T.type") + typeTy.StructSetBody([]llvm.Type{ctx.Int8Type(), methods.Type()}, false) + typeDesc := llvm.AddGlobal(src, typeTy, "_llgo_pkg.T") + typeDesc.SetGlobalConstant(true) + typeDesc.SetLinkage(llvm.LinkOnceODRLinkage) + typeDesc.SetInitializer(llvm.ConstNamedStruct(typeTy, []llvm.Value{ + llvm.ConstNull(ctx.Int8Type()), + methods, + })) + + liveSlots := map[string][]int{"_llgo_pkg.T": {0}} + if err := EmitStrongTypeOverrides(dst, []llvm.Module{src}, liveSlots); err != nil { + t.Fatalf("EmitStrongTypeOverrides: %v", err) + } + + out := dst.String() + if !strings.Contains(out, `@_llgo_pkg.T = constant`) { + t.Fatalf("override type global was not emitted as a strong constant:\n%s", out) + } + if !strings.Contains(out, `ptr @"pkg.(*T).M", ptr @pkg.T.M`) { + t.Fatalf("live method slot was not preserved:\n%s", out) + } + if strings.Contains(out, `ptr @"pkg.(*T).N"`) || strings.Contains(out, `ptr @pkg.T.N`) { + t.Fatalf("dead method slot still references N functions:\n%s", out) + } + if !strings.Contains(out, `ptr @"github.com/goplus/llgo/runtime/internal/runtime.unreachableMethod"`) { + t.Fatalf("dead method slot was not redirected to unreachableMethod:\n%s", out) + } +} + +func TestEmitStrongTypeOverridesLogsDroppedMethodSlots(t *testing.T) { + ctx := llvm.NewContext() + defer ctx.Dispose() + + src := ctx.NewModule("src") + defer src.Dispose() + dst := ctx.NewModule("dst") + defer dst.Dispose() + + voidTy := ctx.VoidType() + fnTy := llvm.FunctionType(voidTy, nil, false) + ptrTy := llvm.PointerType(fnTy, 0) + stringTy := ctx.StructCreateNamed("runtime/internal/runtime.String") + stringTy.StructSetBody([]llvm.Type{llvm.PointerType(ctx.Int8Type(), 0), ctx.Int64Type()}, false) + methodTy := ctx.StructCreateNamed("github.com/goplus/llgo/runtime/abi.Method") + methodTy.StructSetBody([]llvm.Type{stringTy, ptrTy, ptrTy, ptrTy}, false) + + mtyp := llvm.AddGlobal(src, ptrTy, "mtyp") + ifnM := llvm.AddFunction(src, "pkg.(*T).M", fnTy) + tfnM := llvm.AddFunction(src, "pkg.T.M", fnTy) + + methods := llvm.ConstArray(methodTy, []llvm.Value{ + llvm.ConstNamedStruct(methodTy, []llvm.Value{ + llvm.ConstNull(stringTy), + mtyp, + ifnM, + tfnM, + }), + }) + typeTy := ctx.StructCreateNamed("pkg.T.type") + typeTy.StructSetBody([]llvm.Type{ctx.Int8Type(), methods.Type()}, false) + typeDesc := llvm.AddGlobal(src, typeTy, "_llgo_pkg.T") + typeDesc.SetGlobalConstant(true) + typeDesc.SetLinkage(llvm.LinkOnceODRLinkage) + typeDesc.SetInitializer(llvm.ConstNamedStruct(typeTy, []llvm.Value{ + llvm.ConstNull(ctx.Int8Type()), + methods, + })) + + var log bytes.Buffer + if err := EmitStrongTypeOverridesDebug(dst, []llvm.Module{src}, nil, &log); err != nil { + t.Fatalf("EmitStrongTypeOverridesDebug: %v", err) + } + + got := log.String() + want := `[dce] drop method _llgo_pkg.T[0] ifn=pkg.(*T).M tfn=pkg.T.M` + if !strings.Contains(got, want) { + t.Fatalf("debug log missing dropped method slot\nwant: %s\ngot:\n%s", want, got) + } +} diff --git a/internal/deadcode/analyze.go b/internal/deadcode/analyze.go new file mode 100644 index 0000000000..894ad0fa2a --- /dev/null +++ b/internal/deadcode/analyze.go @@ -0,0 +1,310 @@ +package deadcode + +import ( + "go/token" + "sort" + + "github.com/goplus/llgo/internal/meta" +) + +type ifaceMethodKey struct { + iface meta.Symbol + sig meta.GMethodSig +} + +type ifaceMethodName struct { + iface meta.Symbol + name meta.Name +} + +type methodID struct { + owner meta.Symbol + slot int +} + +type methodRef struct { + owner meta.Symbol + slot int + slotInfo meta.GMethodSlot +} + +type pass struct { + info *meta.GlobalSummary + + methodImplKeys map[methodID][]ifaceMethodKey + methodRefs map[meta.GMethodSig][]meta.Symbol // sig → []iface (built eagerly) + ifaceMethodCounts map[meta.Symbol]int // iface → unique method name count + typeSymbols map[meta.Symbol]struct{} + + reachable map[meta.Symbol]struct{} + usedInIface map[meta.Symbol]struct{} + processedIfaceTy map[meta.Symbol]struct{} + workQueue []meta.Symbol + + ifaceMethod map[ifaceMethodKey]struct{} + genericIfaceMethod map[meta.Name]struct{} + reflectSeen bool + + markableMethods []methodRef + markedMethods map[methodID]struct{} + liveSlots map[meta.Symbol][]int +} + +// Analyze returns live ABI method slot indexes by concrete type symbol name. +func Analyze(info *meta.GlobalSummary, rootNames []string) map[string][]int { + roots := make([]meta.Symbol, 0, len(rootNames)) + for _, name := range rootNames { + if sym, ok := info.LookupSymbol(name); ok { + roots = append(roots, sym) + } + } + + liveSlots := deadcode(info, roots) + out := make(map[string][]int, len(liveSlots)) + for typ, slots := range liveSlots { + name := info.SymbolName(typ) + sorted := append([]int(nil), slots...) + sort.Ints(sorted) + out[name] = sorted + } + return out +} + +func deadcode(info *meta.GlobalSummary, roots []meta.Symbol) map[meta.Symbol][]int { + d := &pass{ + info: info, + methodImplKeys: make(map[methodID][]ifaceMethodKey), + methodRefs: make(map[meta.GMethodSig][]meta.Symbol), + ifaceMethodCounts: make(map[meta.Symbol]int), + typeSymbols: make(map[meta.Symbol]struct{}), + reachable: make(map[meta.Symbol]struct{}), + usedInIface: make(map[meta.Symbol]struct{}), + processedIfaceTy: make(map[meta.Symbol]struct{}), + ifaceMethod: make(map[ifaceMethodKey]struct{}), + genericIfaceMethod: make(map[meta.Name]struct{}), + markedMethods: make(map[methodID]struct{}), + liveSlots: make(map[meta.Symbol][]int), + } + d.buildMethodRefs() + + for _, root := range roots { + d.markReachable(root) + } + + for { + d.flood() + changed := d.methodMarkingLoop() + if len(d.workQueue) == 0 && !changed { + return d.liveSlots + } + } +} + +// buildMethodRefs builds the methodRefs reverse index (sig → []iface) from all +// interfaces. This is cheap (tens of interfaces, hundreds of sigs) and must be +// eager — every concrete type needs it to check implementation relationships. +// +// Concrete type methodImplKeys are NOT computed here. They are built lazily in +// computeMethodImplKeys when a type first enters usedInIface. +func (d *pass) buildMethodRefs() { + for _, iface := range d.info.Interfaces() { + d.typeSymbols[iface] = struct{}{} + seenNames := make(map[meta.Name]struct{}) + for _, sig := range d.info.InterfaceMethods(iface) { + d.methodRefs[sig] = appendSymbolUnique(d.methodRefs[sig], iface) + if _, ok := seenNames[sig.Name]; ok { + continue + } + seenNames[sig.Name] = struct{}{} + d.ifaceMethodCounts[iface]++ + } + } +} + +// computeMethodImplKeys lazily builds methodImplKeys for a single concrete type +// that has entered usedInIface. Called at most once per type. +func (d *pass) computeMethodImplKeys(typ meta.Symbol, slots []meta.GMethodSlot) { + if _, done := d.methodImplKeys[methodID{owner: typ, slot: 0}]; done { + // Already computed — check skip by looking at slot 0. If slot 0 has + // an entry, the whole type was processed (we always process all slots + // of a type at once). + // Fine print: a type with 0 slots will never reach here because + // markUsedInIface only calls this when slots is non-empty. + return + } + // Mark the type and compute all slots at once. + d.typeSymbols[typ] = struct{}{} + impls := make(map[meta.Symbol]int) + seen := make(map[ifaceMethodName]struct{}) + + for _, slot := range slots { + sig := meta.GMethodSig{Name: slot.Name, MType: slot.MType} + for _, iface := range d.methodRefs[sig] { + key := ifaceMethodName{iface: iface, name: slot.Name} + if _, ok := seen[key]; ok { + continue + } + seen[key] = struct{}{} + impls[iface]++ + } + } + + for slotIndex, slot := range slots { + id := methodID{owner: typ, slot: slotIndex} + sig := meta.GMethodSig{Name: slot.Name, MType: slot.MType} + for _, iface := range d.methodRefs[sig] { + if impls[iface] == d.ifaceMethodCounts[iface] { + key := ifaceMethodKey{iface: iface, sig: sig} + d.methodImplKeys[id] = append(d.methodImplKeys[id], key) + } + } + } +} + +func (d *pass) flood() { + for len(d.workQueue) > 0 { + sym := d.popWork() + + if d.info.HasReflectMethod(sym) { + d.reflectSeen = true + } + + _, usedInIface := d.usedInIface[sym] + for _, dst := range d.info.OrdinaryEdges(sym) { + if usedInIface { + d.markTypeUsedInIface(dst) + } + d.markReachable(dst) + } + + for _, typ := range d.info.UseIface(sym) { + d.markUsedInIface(typ) + } + + for _, demand := range d.info.UseIfaceMethod(sym) { + key := ifaceMethodKey{iface: demand.Target, sig: demand.Sig} + d.ifaceMethod[key] = struct{}{} + } + + for _, name := range d.info.UseNamedMethod(sym) { + d.genericIfaceMethod[name] = struct{}{} + } + + if _, used := d.usedInIface[sym]; used { + if _, processed := d.processedIfaceTy[sym]; !processed { + d.processedIfaceTy[sym] = struct{}{} + slots := d.info.MethodSlots(sym) + if len(slots) > 0 { + d.computeMethodImplKeys(sym, slots) + } + for slot, slotInfo := range slots { + d.markableMethods = append(d.markableMethods, methodRef{ + owner: sym, + slot: slot, + slotInfo: slotInfo, + }) + } + } + } + } +} + +func (d *pass) methodMarkingLoop() bool { + changed := false + rem := d.markableMethods[:0] + + for _, method := range d.markableMethods { + if d.shouldKeep(method) { + if d.markMethod(method) { + changed = true + } + continue + } + rem = append(rem, method) + } + + d.markableMethods = rem + return changed +} + +func (d *pass) shouldKeep(method methodRef) bool { + if d.reflectSeen && token.IsExported(d.info.Name(method.slotInfo.Name)) { + return true + } + + if _, ok := d.genericIfaceMethod[method.slotInfo.Name]; ok { + return true + } + + id := methodID{owner: method.owner, slot: method.slot} + for _, key := range d.methodImplKeys[id] { + if _, ok := d.ifaceMethod[key]; ok { + return true + } + } + return false +} + +func (d *pass) markMethod(method methodRef) bool { + id := methodID{owner: method.owner, slot: method.slot} + if _, ok := d.markedMethods[id]; ok { + return false + } + d.markedMethods[id] = struct{}{} + d.liveSlots[method.owner] = append(d.liveSlots[method.owner], method.slot) + + d.markReachable(method.slotInfo.MType) + d.markReachable(method.slotInfo.IFn) + d.markReachable(method.slotInfo.TFn) + return true +} + +func (d *pass) markReachable(sym meta.Symbol) { + if _, ok := d.reachable[sym]; ok { + return + } + d.reachable[sym] = struct{}{} + d.workQueue = append(d.workQueue, sym) +} + +func (d *pass) markUsedInIface(typ meta.Symbol) { + if _, ok := d.usedInIface[typ]; ok { + return + } + d.usedInIface[typ] = struct{}{} + if _, ok := d.reachable[typ]; ok { + d.workQueue = append(d.workQueue, typ) + } + for _, child := range d.info.TypeChildren(typ) { + d.markUsedInIface(child) + } +} + +func (d *pass) markTypeUsedInIface(sym meta.Symbol) { + if _, ok := d.typeSymbols[sym]; ok { + d.markUsedInIface(sym) + return + } + // Lazy check: interfaces are in typeSymbols, concrete types are detected + // by the presence of MethodSlots. + if len(d.info.MethodSlots(sym)) > 0 { + d.typeSymbols[sym] = struct{}{} + d.markUsedInIface(sym) + } +} + +func (d *pass) popWork() meta.Symbol { + sym := d.workQueue[0] + copy(d.workQueue, d.workQueue[1:]) + d.workQueue = d.workQueue[:len(d.workQueue)-1] + return sym +} + +func appendSymbolUnique(items []meta.Symbol, item meta.Symbol) []meta.Symbol { + for _, existing := range items { + if existing == item { + return items + } + } + return append(items, item) +} diff --git a/internal/deadcode/analyze_test.go b/internal/deadcode/analyze_test.go new file mode 100644 index 0000000000..b1495fa968 --- /dev/null +++ b/internal/deadcode/analyze_test.go @@ -0,0 +1,505 @@ +package deadcode + +import ( + "reflect" + "testing" + + "github.com/goplus/llgo/internal/meta" +) + +func TestAnalyze(t *testing.T) { + tests := []deadcodeCase{ + // type I interface{ M() } + // type T struct{} + // func (T) M() {} + // func (T) N() {} + // func main() { use(T{}) } + // func use(i I) { i.M() } + { + name: "keeps interface method implementation", + roots: []string{"pkg.main"}, + summary: buildPackage(func(b *pkgBuilder) { + main := b.sym("pkg.main") + use := b.sym("pkg.use") + typ := b.sym("_llgo_pkg.T") + iface := b.sym("_llgo_iface$I") + mSig := methodSig(b, "M") + nSig := methodSig(b, "N") + + b.addIfaceEntry(iface, []pkgSig{mSig}) + b.addMethodInfo(typ, []pkgSlot{ + methodSlot(b, mSig, "pkg.(*T).M", "pkg.T.M"), + methodSlot(b, nSig, "pkg.(*T).N", "pkg.T.N"), + }) + b.addEdge(main, use) + b.addEdge(main, typ) + b.addUseIface(main, typ) + b.addUseIfaceMethod(use, iface, mSig) + }), + want: map[string][]int{"_llgo_pkg.T": {0}}, + }, + // type I interface{ M(); N() } + // type J interface{ M() } + // type T struct{} + // func (T) M() {} + // func main() { useJ(T{}); useI(nil) } + // func useJ(j J) {} + // func useI(i I) { i.M() } + { + name: "requires concrete type to implement whole interface", + roots: []string{"pkg.main"}, + summary: buildPackage(func(b *pkgBuilder) { + main := b.sym("pkg.main") + useJ := b.sym("pkg.useJ") + useI := b.sym("pkg.useI") + typ := b.sym("_llgo_pkg.T") + iface := b.sym("_llgo_iface$I") + compatibleIface := b.sym("_llgo_iface$J") + mSig := methodSig(b, "M") + nSig := methodSig(b, "N") + + b.addIfaceEntry(iface, []pkgSig{mSig, nSig}) + b.addIfaceEntry(compatibleIface, []pkgSig{mSig}) + b.addMethodInfo(typ, []pkgSlot{ + methodSlot(b, mSig, "pkg.(*T).M", "pkg.T.M"), + }) + b.addEdge(main, useJ) + b.addEdge(main, useI) + b.addEdge(main, typ) + b.addUseIface(main, typ) + b.addUseIfaceMethod(useI, iface, mSig) + }), + want: map[string][]int{}, + }, + // type I interface{ M(int) } + // type T struct{} + // func (T) M(string) {} + // func main() { use(T{}) } + // func use(i I) { i.M(0) } + { + name: "requires method type to match", + roots: []string{"pkg.main"}, + summary: buildPackage(func(b *pkgBuilder) { + main := b.sym("pkg.main") + use := b.sym("pkg.use") + typ := b.sym("_llgo_pkg.T") + iface := b.sym("_llgo_iface$I") + ifaceMSig := methodSigWithType(b, "M", "_llgo_func$int") + typMSig := methodSigWithType(b, "M", "_llgo_func$string") + + b.addIfaceEntry(iface, []pkgSig{ifaceMSig}) + b.addMethodInfo(typ, []pkgSlot{ + methodSlot(b, typMSig, "pkg.(*T).M", "pkg.T.M"), + }) + b.addEdge(main, use) + b.addEdge(main, typ) + b.addUseIface(main, typ) + b.addUseIfaceMethod(use, iface, ifaceMSig) + }), + want: map[string][]int{}, + }, + // type I interface{ M() } + // type T struct{} + // func (T) M() {} + // func main() { useI(); makeIface() } + // func useI(i I) { i.M() } + // func makeIface() any { return T{} } + { + name: "matches demand recorded before type enters interface semantics", + roots: []string{"pkg.main"}, + summary: buildPackage(func(b *pkgBuilder) { + main := b.sym("pkg.main") + useI := b.sym("pkg.useI") + makeIface := b.sym("pkg.makeIface") + typ := b.sym("_llgo_pkg.T") + iface := b.sym("_llgo_iface$I") + mSig := methodSig(b, "M") + + b.addIfaceEntry(iface, []pkgSig{mSig}) + b.addMethodInfo(typ, []pkgSlot{ + methodSlot(b, mSig, "pkg.(*T).M", "pkg.T.M"), + }) + b.addEdge(main, useI) + b.addEdge(main, makeIface) + b.addEdge(makeIface, typ) + b.addUseIfaceMethod(useI, iface, mSig) + b.addUseIface(makeIface, typ) + }), + want: map[string][]int{"_llgo_pkg.T": {0}}, + }, + // type I interface{ M() } + // type Child struct{} + // func (Child) M() {} + // type T struct{ Child } + // func (T) M() {} + // func main() { use(T{}) } + // func use(i I) { i.M() } + { + name: "propagates interface use through type children", + roots: []string{"pkg.main"}, + summary: buildPackage(func(b *pkgBuilder) { + main := b.sym("pkg.main") + use := b.sym("pkg.use") + typ := b.sym("_llgo_pkg.T") + child := b.sym("_llgo_pkg.Child") + iface := b.sym("_llgo_iface$I") + mSig := methodSig(b, "M") + + b.addIfaceEntry(iface, []pkgSig{mSig}) + b.addMethodInfo(typ, []pkgSlot{ + methodSlot(b, mSig, "pkg.(*T).M", "pkg.T.M"), + }) + b.addMethodInfo(child, []pkgSlot{ + methodSlot(b, mSig, "pkg.(*Child).M", "pkg.Child.M"), + }) + b.b.AddTypeChild(typ, child) + b.addEdge(main, use) + b.addEdge(main, typ) + b.addEdge(typ, child) + b.addUseIface(main, typ) + b.addUseIfaceMethod(use, iface, mSig) + }), + want: map[string][]int{ + "_llgo_pkg.Child": {0}, + "_llgo_pkg.T": {0}, + }, + }, + // type Unmarshaler interface{ UnmarshalJSON([]byte) error } + // type RawMessage []byte + // func (*RawMessage) UnmarshalJSON([]byte) error { return nil } + // type Container struct{ Raw RawMessage } + // func main() { unmarshal(&Container{}) } + // func unmarshal(v any) { field.Addr().Interface().(Unmarshaler).UnmarshalJSON(nil) } + { + name: "value type entering interface semantics keeps pointer method implementation", + roots: []string{"pkg.main"}, + summary: buildPackage(func(b *pkgBuilder) { + main := b.sym("pkg.main") + unmarshal := b.sym("pkg.unmarshal") + containerPtr := b.sym("*_llgo_pkg.Container") + container := b.sym("_llgo_pkg.Container") + raw := b.sym("_llgo_pkg.RawMessage") + rawPtr := b.sym("*_llgo_pkg.RawMessage") + iface := b.sym("_llgo_pkg.Unmarshaler") + unmarshalSig := methodSigWithType(b, "UnmarshalJSON", "_llgo_func$bytes_error") + + b.addIfaceEntry(iface, []pkgSig{unmarshalSig}) + b.addMethodInfo(rawPtr, []pkgSlot{ + methodSlot(b, unmarshalSig, "pkg.(*RawMessage).UnmarshalJSON", "pkg.(*RawMessage).UnmarshalJSON"), + }) + b.b.AddTypeChild(containerPtr, container) + b.b.AddTypeChild(container, raw) + b.addEdge(main, unmarshal) + b.addEdge(main, containerPtr) + b.addEdge(containerPtr, container) + b.addEdge(container, raw) + b.addEdge(raw, rawPtr) + b.addUseIface(main, containerPtr) + b.addUseIfaceMethod(unmarshal, iface, unmarshalSig) + }), + want: map[string][]int{"*_llgo_pkg.RawMessage": {0}}, + }, + // type T struct{} + // func (T) M() {} + // func (T) N() {} + // func main() { use(T{}) } + // func use(v any) { reflect.ValueOf(v).MethodByName("M") } + { + name: "constant MethodByName keeps same-name method", + roots: []string{"pkg.main"}, + summary: buildPackage(func(b *pkgBuilder) { + main := b.sym("pkg.main") + use := b.sym("pkg.use") + typ := b.sym("_llgo_pkg.T") + mSig := methodSig(b, "M") + nSig := methodSig(b, "N") + + b.addMethodInfo(typ, []pkgSlot{ + methodSlot(b, mSig, "pkg.(*T).M", "pkg.T.M"), + methodSlot(b, nSig, "pkg.(*T).N", "pkg.T.N"), + }) + b.addEdge(main, use) + b.addEdge(main, typ) + b.addUseIface(main, typ) + b.b.AddNamedMethodEdge(use, mSig.name) + }), + want: map[string][]int{"_llgo_pkg.T": {0}}, + }, + // type T struct{} + // func (T) M() {} + // func (T) N() {} + // func (T) m() {} + // func main() { use(T{}) } + // func use(v any) { reflect.ValueOf(v).Method(0) } + { + name: "reflection keeps exported methods only", + roots: []string{"pkg.main"}, + summary: buildPackage(func(b *pkgBuilder) { + main := b.sym("pkg.main") + use := b.sym("pkg.use") + typ := b.sym("_llgo_pkg.T") + mSig := methodSig(b, "M") + nSig := methodSig(b, "N") + unexportedSig := methodSig(b, "m") + + b.addMethodInfo(typ, []pkgSlot{ + methodSlot(b, mSig, "pkg.(*T).M", "pkg.T.M"), + methodSlot(b, nSig, "pkg.(*T).N", "pkg.T.N"), + methodSlot(b, unexportedSig, "pkg.(*T).m", "pkg.T.m"), + }) + b.addEdge(main, use) + b.addEdge(main, typ) + b.addUseIface(main, typ) + b.b.MarkReflect(use) + }), + want: map[string][]int{"_llgo_pkg.T": {0, 1}}, + }, + // type I interface{ M() } + // type J interface{ N() } + // type T struct{} + // type U struct{} + // func (T) M() { callU() } + // func (U) N() {} + // func main() { useT(T{}) } + // func useT(i I) { i.M() } + // func callU() { useU(U{}) } + // func useU(j J) { j.N() } + { + name: "live method body can introduce new interface demands", + roots: []string{"pkg.main"}, + summary: buildPackage(func(b *pkgBuilder) { + main := b.sym("pkg.main") + useT := b.sym("pkg.useT") + callU := b.sym("pkg.callU") + useU := b.sym("pkg.useU") + typT := b.sym("_llgo_pkg.T") + typU := b.sym("_llgo_pkg.U") + ifaceI := b.sym("_llgo_iface$I") + ifaceJ := b.sym("_llgo_iface$J") + mSig := methodSig(b, "M") + nSig := methodSig(b, "N") + + b.addIfaceEntry(ifaceI, []pkgSig{mSig}) + b.addIfaceEntry(ifaceJ, []pkgSig{nSig}) + b.addMethodInfo(typT, []pkgSlot{ + methodSlot(b, mSig, "pkg.(*T).M", "pkg.T.M"), + }) + b.addMethodInfo(typU, []pkgSlot{ + methodSlot(b, nSig, "pkg.(*U).N", "pkg.U.N"), + }) + b.addEdge(main, useT) + b.addEdge(main, typT) + b.addUseIface(main, typT) + b.addUseIfaceMethod(useT, ifaceI, mSig) + b.addEdge(b.sym("pkg.T.M"), callU) + b.addEdge(callU, useU) + b.addEdge(callU, typU) + b.addUseIface(callU, typU) + b.addUseIfaceMethod(useU, ifaceJ, nSig) + }), + want: map[string][]int{ + "_llgo_pkg.T": {0}, + "_llgo_pkg.U": {0}, + }, + }, + // type Type interface{ Elem() Type } + // type rtype struct{} + // func (rtype) Elem() Type { return toType() } + // func main() { init(); toType() } + // func init() { reflectType.Elem() } + // func toType() Type { return rtype{} } + { + name: "interface demand and conversion from different reachable functions meet", + roots: []string{"pkg.main"}, + summary: buildPackage(func(b *pkgBuilder) { + main := b.sym("pkg.main") + init := b.sym("pkg.init") + toType := b.sym("pkg.toType") + typ := b.sym("_llgo_pkg.rtype") + iface := b.sym("_llgo_pkg.Type") + elemSig := methodSig(b, "Elem") + + b.addIfaceEntry(iface, []pkgSig{elemSig}) + b.addMethodInfo(typ, []pkgSlot{ + methodSlot(b, elemSig, "pkg.(*rtype).Elem", "pkg.rtype.Elem"), + }) + b.addEdge(main, init) + b.addEdge(main, toType) + b.addEdge(toType, typ) + b.addUseIface(toType, typ) + b.addUseIfaceMethod(init, iface, elemSig) + }), + want: map[string][]int{"_llgo_pkg.rtype": {0}}, + }, + // type Type interface{ Elem() Type; Kind() Kind } + // type rtype struct{} + // func (rtype) Elem() Type { return toType() } + // func (rtype) Kind() Kind { return 0 } + // func main() { init(); toType() } + // func init() { reflectType.Elem() } + // func toType() Type { return rtype{} } + { + name: "duplicate interface method names do not inflate interface size", + roots: []string{"pkg.main"}, + summary: buildPackage(func(b *pkgBuilder) { + main := b.sym("pkg.main") + init := b.sym("pkg.init") + toType := b.sym("pkg.toType") + typ := b.sym("_llgo_pkg.rtype") + iface := b.sym("_llgo_pkg.Type") + elemSig := methodSig(b, "Elem") + kindSig := methodSigWithType(b, "Kind", "_llgo_func$kind") + altKindSig := methodSigWithType(b, "Kind", "_llgo_func$altKind") + + b.addIfaceEntry(iface, []pkgSig{elemSig, kindSig, altKindSig}) + b.addMethodInfo(typ, []pkgSlot{ + methodSlot(b, elemSig, "pkg.(*rtype).Elem", "pkg.rtype.Elem"), + methodSlot(b, kindSig, "pkg.(*rtype).Kind", "pkg.rtype.Kind"), + }) + b.addEdge(main, init) + b.addEdge(main, toType) + b.addEdge(toType, typ) + b.addUseIface(toType, typ) + b.addUseIfaceMethod(init, iface, elemSig) + }), + want: map[string][]int{"_llgo_pkg.rtype": {0}}, + }, + // type I interface{ M() } + // type T struct{} + // func (T) M() {} + // func main() { _ = T{} } + // func unreachable(i I) { i.M(); reflect.ValueOf(i).MethodByName("M") } + { + name: "ignores unreachable semantic facts", + roots: []string{"pkg.main"}, + summary: buildPackage(func(b *pkgBuilder) { + main := b.sym("pkg.main") + unreachable := b.sym("pkg.unreachable") + typ := b.sym("_llgo_pkg.T") + iface := b.sym("_llgo_iface$I") + mSig := methodSig(b, "M") + + b.addIfaceEntry(iface, []pkgSig{mSig}) + b.addMethodInfo(typ, []pkgSlot{ + methodSlot(b, mSig, "pkg.(*T).M", "pkg.T.M"), + }) + b.addEdge(main, typ) + b.addUseIface(unreachable, typ) + b.addUseIfaceMethod(unreachable, iface, mSig) + b.b.AddNamedMethodEdge(unreachable, mSig.name) + b.b.MarkReflect(unreachable) + }), + want: map[string][]int{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Analyze(newSummary(t, tt.summary), tt.roots) + if !reflect.DeepEqual(got, tt.want) { + t.Fatalf("Analyze() = %#v, want %#v", got, tt.want) + } + }) + } +} + +// ── test builder helpers ────────────────────────────────────────────────────── + +type pkgSig struct { + name string + mtype meta.LocalSymbol +} + +type pkgSlot struct { + sig pkgSig + ifn meta.LocalSymbol + tfn meta.LocalSymbol +} + +// pkgBuilder wraps meta.Builder and tracks iface method order so that +// addUseIfaceMethod can look up the sig index required by EdgeUseIfaceMethod. +type pkgBuilder struct { + b *meta.Builder + ifaceOrder map[meta.LocalSymbol][]pkgSig +} + +func newPkgBuilder() *pkgBuilder { + return &pkgBuilder{ + b: meta.NewBuilder(), + ifaceOrder: make(map[meta.LocalSymbol][]pkgSig), + } +} + +func (p *pkgBuilder) sym(name string) meta.LocalSymbol { return p.b.Sym(name) } + +func (p *pkgBuilder) addEdge(src, dst meta.LocalSymbol) { + p.b.AddEdge(src, dst, meta.EdgeOrdinary, 0) +} + +func (p *pkgBuilder) addUseIface(fn, typ meta.LocalSymbol) { + p.b.AddEdge(fn, typ, meta.EdgeUseIface, 0) +} + +// addUseIfaceMethod records a demand for iface.sig from fn. The sig index is +// determined by the order in which sigs were registered via addIfaceEntry. +func (p *pkgBuilder) addUseIfaceMethod(fn, iface meta.LocalSymbol, sig pkgSig) { + sigs := p.ifaceOrder[iface] + for i, s := range sigs { + if s.name == sig.name && s.mtype == sig.mtype { + p.b.AddEdge(fn, iface, meta.EdgeUseIfaceMethod, uint32(i)) + return + } + } + panic("addUseIfaceMethod: sig not found in iface — call addIfaceEntry first") +} + +func (p *pkgBuilder) addIfaceEntry(iface meta.LocalSymbol, sigs []pkgSig) { + p.ifaceOrder[iface] = sigs + for _, sig := range sigs { + p.b.AddIfaceMethod(iface, sig.name, sig.mtype) + } +} + +func (p *pkgBuilder) addMethodInfo(typ meta.LocalSymbol, slots []pkgSlot) { + for _, slot := range slots { + p.b.AddMethodSlot(typ, slot.sig.name, slot.sig.mtype, slot.ifn, slot.tfn) + } +} + +func methodSig(b *pkgBuilder, name string) pkgSig { + return methodSigWithType(b, name, "_llgo_func$X") +} + +func methodSigWithType(b *pkgBuilder, name, mtype string) pkgSig { + return pkgSig{name: name, mtype: b.sym(mtype)} +} + +func methodSlot(b *pkgBuilder, sig pkgSig, ifn, tfn string) pkgSlot { + return pkgSlot{sig: sig, ifn: b.sym(ifn), tfn: b.sym(tfn)} +} + +type deadcodeCase struct { + name string + summary *meta.PackageMeta + roots []string + want map[string][]int +} + +func buildPackage(fn func(*pkgBuilder)) *meta.PackageMeta { + b := newPkgBuilder() + fn(b) + pm, err := b.b.Build() + if err != nil { + panic("buildPackage: " + err.Error()) + } + return pm +} + +func newSummary(t *testing.T, pkgs ...*meta.PackageMeta) *meta.GlobalSummary { + t.Helper() + summary, err := meta.NewGlobalSummary(pkgs) + if err != nil { + t.Fatalf("NewGlobalSummary: %v", err) + } + return summary +} diff --git a/internal/meta/build.go b/internal/meta/build.go new file mode 100644 index 0000000000..e81f587a7a --- /dev/null +++ b/internal/meta/build.go @@ -0,0 +1,257 @@ +package meta + +import "encoding/binary" + +// Build serializes all accumulated facts into a PackageMeta. +// +// The process is: +// 1. Calculate the byte size of every section. +// 2. Derive each section's start offset. +// 3. Allocate one []byte for the whole file. +// 4. Write header + every section directly into the buffer — no intermediate +// allocations, no copies. +func (b *Builder) Build() (*PackageMeta, error) { + nsyms := uint32(len(b.symNames)) + + // ── 1. calculate section sizes ──────────────────────────────────────────── + + // stringTable is padded to a 4-byte boundary so every following section + // starts 4-byte aligned, enabling zero-copy unsafe access (e.g. TypeChildren). + strSize := align4(uint32(len(b.strData))) + + symSize := 4 + nsyms*12 // nsyms u32 + N×SymbolRecord(12) + + totalOrdinary := uint32(0) + for _, es := range b.ordinaryEdges { + totalOrdinary += uint32(len(es)) + } + ordinarySize := 4 + (nsyms+1)*4 + totalOrdinary*4 // nsyms + offsets[N+1] + N×LocalSymbol(4) + + totalDemands := uint32(0) + for _, ds := range b.funcDemands { + totalDemands += uint32(len(ds)) + } + demandSize := 4 + (nsyms+1)*4 + totalDemands*12 // nsyms + offsets[N+1] + N×FuncDemand(12) + + totalChildren := uint32(0) + for _, cs := range b.typeChildren { + totalChildren += uint32(len(cs)) + } + childSize := 4 + (nsyms+1)*4 + totalChildren*4 + + totalSlots := uint32(0) + for _, ms := range b.methodInfo { + totalSlots += uint32(len(ms)) + } + methodSize := 4 + (nsyms+1)*4 + totalSlots*20 // N×MethodSlot(20: NameRef(8)+mtype+ifn+tfn) + + totalSigs := uint32(0) + for _, ss := range b.ifaceInfo { + totalSigs += uint32(len(ss)) + } + ifaceSize := 4 + (nsyms+1)*4 + totalSigs*12 // N×MethodSig(12: NameRef(8)+mtype) + + // ── 2. calculate section offsets ───────────────────────────────────────── + + var offsets [numSections]uint32 + cur := uint32(headerSize) + offsets[SecStringTable] = cur + cur += strSize + offsets[SecSymbols] = cur + cur += symSize + offsets[SecOrdinaryEdges] = cur + cur += ordinarySize + offsets[SecFuncDemand] = cur + cur += demandSize + offsets[SecTypeChildren] = cur + cur += childSize + offsets[SecMethodInfo] = cur + cur += methodSize + offsets[SecIfaceInfo] = cur + cur += ifaceSize + + // ── 3. allocate one buffer ──────────────────────────────────────────────── + + raw := make([]byte, cur) + + // ── 4. write header ─────────────────────────────────────────────────────── + + copy(raw[0:4], Magic) + binary.LittleEndian.PutUint32(raw[4:8], Version) + for i, off := range offsets { + binary.LittleEndian.PutUint32(raw[8+i*4:], off) + } + + // ── 5. write each section directly into raw ─────────────────────────────── + + writeStringTable(raw[offsets[SecStringTable]:], b) + writeSymbols(raw[offsets[SecSymbols]:], b, nsyms) + writeOrdinaryEdges(raw[offsets[SecOrdinaryEdges]:], b, nsyms) + writeFuncDemand(raw[offsets[SecFuncDemand]:], b, nsyms) + writeTypeChildren(raw[offsets[SecTypeChildren]:], b, nsyms) + writeMethodInfo(raw[offsets[SecMethodInfo]:], b, nsyms) + writeIfaceInfo(raw[offsets[SecIfaceInfo]:], b, nsyms) + + return newPackageMeta(raw) +} + +// ── section writers ─────────────────────────────────────────────────────────── +// Each writer receives a slice starting exactly at its section's offset. +// It writes directly into that slice — no allocation, no copy. + +func writeStringTable(dst []byte, b *Builder) { + // dst may be longer than strData (padding); padding bytes stay zero. + copy(dst, b.strData) +} + +// align4 rounds n up to the next multiple of 4. +func align4(n uint32) uint32 { + return (n + 3) &^ 3 +} + +// writeSymbols writes: +// +// nsyms u32 +// [nsyms] { nameOff u32, nameLen u32, _ [4]byte } (12 bytes each) +func writeSymbols(dst []byte, b *Builder, nsyms uint32) { + binary.LittleEndian.PutUint32(dst, nsyms) + const rec = 12 + for i, e := range b.symNames { + base := 4 + i*rec + binary.LittleEndian.PutUint32(dst[base:], e.nameOff) + binary.LittleEndian.PutUint32(dst[base+4:], e.nameLen) + // dst[base+8 : base+12] reserved, already zero + } +} + +// writeCSRHeader writes: +// +// nsyms u32 +// offsets [nsyms+1] u32 +// +// and returns the slice starting at the data area (after the offsets array). +// cur accumulates the running data index as each symbol's entries are counted. +func writeCSROffsets(dst []byte, nsyms uint32, counts []int) []byte { + binary.LittleEndian.PutUint32(dst, nsyms) + offsetBase := dst[4:] + cur := uint32(0) + for i, c := range counts { + binary.LittleEndian.PutUint32(offsetBase[i*4:], cur) + cur += uint32(c) + } + // sentinel + binary.LittleEndian.PutUint32(offsetBase[len(counts)*4:], cur) + // return slice starting at data area + return dst[4+(nsyms+1)*4:] +} + +// writeOrdinaryEdges writes the OrdinaryEdges section. +// +// nsyms u32 +// offsets [nsyms+1] u32 +// data [] u32 (LocalSymbol) +func writeOrdinaryEdges(dst []byte, b *Builder, nsyms uint32) { + counts := make([]int, nsyms) + for i := range b.ordinaryEdges { + counts[i] = len(b.ordinaryEdges[i]) + } + data := writeCSROffsets(dst, nsyms, counts) + pos := 0 + for _, es := range b.ordinaryEdges { + for _, target := range es { + binary.LittleEndian.PutUint32(data[pos:], uint32(target)) + pos += 4 + } + } +} + +// writeFuncDemand writes the FuncDemand section. +// +// nsyms u32 +// offsets [nsyms+1] u32 +// data [] { kind u32, target u32, extra u32 } (12 bytes each) +func writeFuncDemand(dst []byte, b *Builder, nsyms uint32) { + counts := make([]int, nsyms) + for i := range b.funcDemands { + counts[i] = len(b.funcDemands[i]) + } + data := writeCSROffsets(dst, nsyms, counts) + const rec = 12 + pos := 0 + for _, ds := range b.funcDemands { + for _, d := range ds { + binary.LittleEndian.PutUint32(data[pos:], d.kind) + binary.LittleEndian.PutUint32(data[pos+4:], d.target) + binary.LittleEndian.PutUint32(data[pos+8:], d.extra) + pos += rec + } + } +} + +// writeTypeChildren writes the TypeChildren section. +// +// nsyms u32 +// offsets [nsyms+1] u32 +// data [] u32 (LocalSymbol) +func writeTypeChildren(dst []byte, b *Builder, nsyms uint32) { + counts := make([]int, nsyms) + for i := range b.typeChildren { + counts[i] = len(b.typeChildren[i]) + } + data := writeCSROffsets(dst, nsyms, counts) + pos := 0 + for _, cs := range b.typeChildren { + for _, child := range cs { + binary.LittleEndian.PutUint32(data[pos:], uint32(child)) + pos += 4 + } + } +} + +// writeMethodInfo writes the MethodInfo section. +// +// nsyms u32 +// offsets [nsyms+1] u32 +// data [] { nameOff u32, nameLen u32, mtype u32, ifn u32, tfn u32 } (20 bytes each) +func writeMethodInfo(dst []byte, b *Builder, nsyms uint32) { + counts := make([]int, nsyms) + for i := range b.methodInfo { + counts[i] = len(b.methodInfo[i]) + } + data := writeCSROffsets(dst, nsyms, counts) + const rec = 20 + pos := 0 + for _, slots := range b.methodInfo { + for _, slot := range slots { + binary.LittleEndian.PutUint32(data[pos:], slot.name.Off) + binary.LittleEndian.PutUint32(data[pos+4:], slot.name.Len) + binary.LittleEndian.PutUint32(data[pos+8:], slot.mtype) + binary.LittleEndian.PutUint32(data[pos+12:], slot.ifn) + binary.LittleEndian.PutUint32(data[pos+16:], slot.tfn) + pos += rec + } + } +} + +// writeIfaceInfo writes the InterfaceInfo section. +// +// nsyms u32 +// offsets [nsyms+1] u32 +// data [] { nameOff u32, nameLen u32, mtype u32 } (12 bytes each) +func writeIfaceInfo(dst []byte, b *Builder, nsyms uint32) { + counts := make([]int, nsyms) + for i := range b.ifaceInfo { + counts[i] = len(b.ifaceInfo[i]) + } + data := writeCSROffsets(dst, nsyms, counts) + const rec = 12 + pos := 0 + for _, sigs := range b.ifaceInfo { + for _, sig := range sigs { + binary.LittleEndian.PutUint32(data[pos:], sig.name.Off) + binary.LittleEndian.PutUint32(data[pos+4:], sig.name.Len) + binary.LittleEndian.PutUint32(data[pos+8:], sig.mtype) + pos += rec + } + } +} diff --git a/internal/meta/builder.go b/internal/meta/builder.go new file mode 100644 index 0000000000..2ca6f83171 --- /dev/null +++ b/internal/meta/builder.go @@ -0,0 +1,199 @@ +package meta + +// Builder accumulates per-package metadata facts and serializes them into +// the binary wire format understood by PackageMeta. +// +// Typical usage: +// +// b := NewBuilder() +// fn := b.DefSym("main.main") +// callee := b.RefSym("fmt.Println") +// b.AddEdge(fn, callee, EdgeOrdinary, 0) +// pm, err := b.Build() +type Builder struct { + // string interning + strData []byte // raw byte stream, all strings concatenated + strMap map[string]uint32 // string → offset in strData + + // symbol table + symNames []symEntry // indexed by LocalSymbol + symMap map[string]LocalSymbol // name → LocalSymbol + + // per-symbol ordinary edge lists (source LocalSymbol → target LocalSymbols) + ordinaryEdges [][]LocalSymbol + + // per-symbol function demand lists (source LocalSymbol → demand facts) + funcDemands [][]bFuncDemand + + // per-symbol TypeChildren lists + typeChildren [][]LocalSymbol + typeChildrenSet map[[2]LocalSymbol]struct{} // dedup (parent, child) pairs + + // per-symbol MethodInfo (only concrete types) + methodInfo [][]bMethodSlot + + // per-symbol InterfaceInfo (only interface types) + ifaceInfo [][]bMethodSig +} + +type symEntry struct { + nameOff uint32 + nameLen uint32 +} + +type bFuncDemand struct { + kind uint32 + target uint32 // LocalSymbol or stringTable offset (DemandNamedMethod) + extra uint32 +} + +type bMethodSlot struct { + name NameRef // method short name + mtype uint32 // LocalSymbol + ifn uint32 // LocalSymbol + tfn uint32 // LocalSymbol +} + +type bMethodSig struct { + name NameRef // method short name + mtype uint32 // LocalSymbol +} + +// NewBuilder creates an empty Builder. +func NewBuilder() *Builder { + return &Builder{ + strMap: make(map[string]uint32), + symMap: make(map[string]LocalSymbol), + typeChildrenSet: make(map[[2]LocalSymbol]struct{}), + } +} + +// internStr adds s to the string byte stream (idempotent) and returns its offset. +func (b *Builder) internStr(s string) uint32 { + if off, ok := b.strMap[s]; ok { + return off + } + off := uint32(len(b.strData)) + b.strData = append(b.strData, s...) + b.strMap[s] = off + return off +} + +// internName registers a name string and returns a NameRef. +func (b *Builder) internName(s string) NameRef { + return NameRef{Off: b.internStr(s), Len: uint32(len(s))} +} + +// Sym registers a symbol by name and returns its LocalSymbol. +// Calling Sym with the same name twice returns the same LocalSymbol. +// Whether the symbol is defined in this package or referenced from another +// makes no difference to the metadata format. +func (b *Builder) Sym(name string) LocalSymbol { + return b.sym(name) +} + +func (b *Builder) sym(name string) LocalSymbol { + if id, ok := b.symMap[name]; ok { + return id + } + id := LocalSymbol(len(b.symNames)) + off := b.internStr(name) + b.symNames = append(b.symNames, symEntry{nameOff: off, nameLen: uint32(len(name))}) + b.symMap[name] = id + // grow all per-symbol structures in sync with the symbol table + b.ordinaryEdges = append(b.ordinaryEdges, nil) + b.funcDemands = append(b.funcDemands, nil) + b.typeChildren = append(b.typeChildren, nil) + b.methodInfo = append(b.methodInfo, nil) + b.ifaceInfo = append(b.ifaceInfo, nil) + return id +} + +// AddEdge records a directed edge from src to dst with the given kind and extra. +// +// - EdgeOrdinary: dst is a LocalSymbol; extra = 0 +// - EdgeUseIface: dst is a LocalSymbol (type); extra = 0 +// - EdgeUseIfaceMethod: dst is a LocalSymbol (interface); extra = method index +// - EdgeUseNamedMethod: dst is a string-table offset; extra = string length +func (b *Builder) AddEdge(src, dst LocalSymbol, kind uint8, extra uint32) { + switch kind { + case EdgeOrdinary: + b.ordinaryEdges[src] = append(b.ordinaryEdges[src], dst) + case EdgeUseIface: + b.funcDemands[src] = append(b.funcDemands[src], bFuncDemand{ + kind: DemandUseIface, + target: uint32(dst), + extra: extra, + }) + case EdgeUseIfaceMethod: + b.funcDemands[src] = append(b.funcDemands[src], bFuncDemand{ + kind: DemandIfaceMethod, + target: uint32(dst), + extra: extra, + }) + case EdgeUseNamedMethod: + b.funcDemands[src] = append(b.funcDemands[src], bFuncDemand{ + kind: DemandNamedMethod, + target: uint32(dst), + extra: extra, + }) + } +} + +// AddNamedMethodEdge records an EdgeUseNamedMethod edge where the target is a +// method name string rather than a LocalSymbol. The name's byte offset is stored +// in target and its length in extra, together forming a NameRef. +func (b *Builder) AddNamedMethodEdge(src LocalSymbol, methodName string) { + ref := b.internName(methodName) + b.funcDemands[src] = append(b.funcDemands[src], bFuncDemand{ + kind: DemandNamedMethod, + target: ref.Off, + extra: ref.Len, + }) +} + +// AddTypeChild records that parent type structurally contains child type. +// Idempotent: duplicate (parent, child) pairs are silently ignored. +func (b *Builder) AddTypeChild(parent, child LocalSymbol) { + key := [2]LocalSymbol{parent, child} + if _, ok := b.typeChildrenSet[key]; ok { + return + } + b.typeChildrenSet[key] = struct{}{} + b.typeChildren[parent] = append(b.typeChildren[parent], child) +} + +// AddMethodSlot records one ABI method slot for a concrete type. +// Slots must be appended in abi.Method table order. +func (b *Builder) AddMethodSlot(typ LocalSymbol, methodName string, mtype, ifn, tfn LocalSymbol) { + b.methodInfo[typ] = append(b.methodInfo[typ], bMethodSlot{ + name: b.internName(methodName), + mtype: uint32(mtype), + ifn: uint32(ifn), + tfn: uint32(tfn), + }) +} + +// AddIfaceMethod records one method in an interface's method set. +// Idempotent: if the same (name, mtype) pair is already registered for iface, +// this call is a no-op — the Builder deduplicates internally. +func (b *Builder) AddIfaceMethod(iface LocalSymbol, methodName string, mtype LocalSymbol) { + ref := b.internName(methodName) + mt := uint32(mtype) + for _, s := range b.ifaceInfo[iface] { + if s.name == ref && s.mtype == mt { + return + } + } + b.ifaceInfo[iface] = append(b.ifaceInfo[iface], bMethodSig{ + name: ref, + mtype: mt, + }) +} + +// MarkReflect marks sym as triggering conservative reflection handling. +func (b *Builder) MarkReflect(sym LocalSymbol) { + b.funcDemands[sym] = append(b.funcDemands[sym], bFuncDemand{ + kind: DemandReflectMethod, + }) +} diff --git a/internal/meta/format.go b/internal/meta/format.go new file mode 100644 index 0000000000..bbc0682fed --- /dev/null +++ b/internal/meta/format.go @@ -0,0 +1,181 @@ +package meta + +import ( + "fmt" + "sort" + "strings" +) + +// String returns a human-readable representation for testing and debugging. +func (pm *PackageMeta) String() string { + if pm == nil { + return "" + } + var sb strings.Builder + formatMeta(&sb, pm) + return sb.String() +} + +// formatMeta writes a human-readable representation of pm to w, +// grouped by section (TypeChildren, OrdinaryEdges, UseIface, etc.) +// to match the original metadata format used by golden file tests. +func formatMeta(w *strings.Builder, pm *PackageMeta) { + n := pm.nsyms + symName := func(sym LocalSymbol) string { return pm.symbolName(sym) } + + // collect per-sym edge lists by kind + type kindMap = map[string][]string // src → []dst (sorted) + ordinary := make(map[string][]string) + useIface := make(map[string][]string) + useIfaceMethod := make(map[string][]string) // src → ["iface[idx]", ...] + useNamed := make(map[string][]string) + + for i := LocalSymbol(0); i < LocalSymbol(n); i++ { + src := symName(i) + for _, dst := range pm.ordinaryEdges(i) { + ordinary[src] = append(ordinary[src], symName(dst)) + } + for _, d := range pm.funcDemands(i) { + switch d.Kind { + case DemandUseIface: + useIface[src] = append(useIface[src], symName(LocalSymbol(d.Target))) + case DemandIfaceMethod: + ifaceSym := LocalSymbol(d.Target) + iface := symName(ifaceSym) + sigs := pm.ifaceMethods(ifaceSym) + if int(d.Extra) < len(sigs) { + s := sigs[d.Extra] + useIfaceMethod[src] = append(useIfaceMethod[src], + fmt.Sprintf("%s %s %s", iface, pm.nameString(s.Name), symName(s.MType))) + } else { + useIfaceMethod[src] = append(useIfaceMethod[src], + fmt.Sprintf("%s[%d]", iface, d.Extra)) + } + case DemandNamedMethod: + name := pm.nameString(NameRef{Off: d.Target, Len: d.Extra}) + useNamed[src] = append(useNamed[src], name) + } + } + } + + // collect TypeChildren + typeChildren := make(map[string][]string) + for i := LocalSymbol(0); i < LocalSymbol(n); i++ { + parent := symName(i) + for _, c := range pm.typeChildren(i) { + typeChildren[parent] = append(typeChildren[parent], symName(c)) + } + } + + // collect MethodInfo + type slotInfo struct{ name, mtype, ifn, tfn string } + methodInfo := make(map[string][]slotInfo) + for i := LocalSymbol(0); i < LocalSymbol(n); i++ { + typ := symName(i) + for _, s := range pm.methodSlots(i) { + methodInfo[typ] = append(methodInfo[typ], slotInfo{ + name: pm.nameString(s.Name), + mtype: symName(s.MType), + ifn: symName(s.IFn), + tfn: symName(s.TFn), + }) + } + } + + // collect InterfaceInfo + type sigInfo struct{ name, mtype string } + ifaceInfo := make(map[string][]sigInfo) + for i := LocalSymbol(0); i < LocalSymbol(n); i++ { + iface := symName(i) + for _, s := range pm.ifaceMethods(i) { + ifaceInfo[iface] = append(ifaceInfo[iface], sigInfo{ + name: pm.nameString(s.Name), + mtype: symName(s.MType), + }) + } + } + + // collect Reflect + var reflectSyms []string + for i := LocalSymbol(0); i < LocalSymbol(n); i++ { + for _, d := range pm.funcDemands(i) { + if d.Kind == DemandReflectMethod { + reflectSyms = append(reflectSyms, symName(i)) + break + } + } + } + + printSection := func(title string, m map[string][]string) { + if len(m) == 0 { + return + } + fmt.Fprintf(w, "[%s]\n", title) + keys := sortedKeys(m) + for _, k := range keys { + vals := m[k] + sort.Strings(vals) + fmt.Fprintf(w, "%s:\n", k) + for _, v := range vals { + fmt.Fprintf(w, " %s\n", v) + } + } + fmt.Fprintln(w) + } + + printSection("TypeChildren", typeChildren) + printSection("OrdinaryEdges", ordinary) + printSection("UseIface", useIface) + printSection("UseIfaceMethod", useIfaceMethod) + printSection("UseNamedMethod", useNamed) + + if len(methodInfo) > 0 { + fmt.Fprintln(w, "[MethodInfo]") + keys := make([]string, 0, len(methodInfo)) + for k := range methodInfo { + keys = append(keys, k) + } + sort.Strings(keys) + for _, typ := range keys { + fmt.Fprintf(w, "%s:\n", typ) + for idx, s := range methodInfo[typ] { + fmt.Fprintf(w, " %d %s %s %s %s\n", idx, s.name, s.mtype, s.ifn, s.tfn) + } + } + fmt.Fprintln(w) + } + + if len(ifaceInfo) > 0 { + fmt.Fprintln(w, "[InterfaceInfo]") + keys := make([]string, 0, len(ifaceInfo)) + for k := range ifaceInfo { + keys = append(keys, k) + } + sort.Strings(keys) + for _, iface := range keys { + fmt.Fprintf(w, "%s:\n", iface) + for _, s := range ifaceInfo[iface] { + fmt.Fprintf(w, " %s %s\n", s.name, s.mtype) + } + } + fmt.Fprintln(w) + } + + if len(reflectSyms) > 0 { + sort.Strings(reflectSyms) + fmt.Fprintln(w, "[Reflect]") + for _, r := range reflectSyms { + fmt.Fprintf(w, " %s\n", r) + } + fmt.Fprintln(w) + } +} + +func sortedKeys(m map[string][]string) []string { + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} diff --git a/internal/meta/global.go b/internal/meta/global.go new file mode 100644 index 0000000000..6b642da152 --- /dev/null +++ b/internal/meta/global.go @@ -0,0 +1,366 @@ +package meta + +// GlobalSummary is a whole-program metadata view over multiple PackageMetas, +// in one unified symbol/name space. +// +// Merge strategy: +// - Symbols are interned into a global Symbol space; each package's local +// symbols are mapped via locToGlb. Edges, TypeChildren, MethodSlots and +// InterfaceMethods are NOT rewritten at merge time — they are translated +// lazily on query. Only the strings are interned up front. +// - Duplicate symbols (e.g. linkonce type descriptors emitted by several +// packages) are assigned one owner by fact strength. Function-demand, +// MethodInfo, InterfaceInfo and TypeChildren facts outrank ordinary edges, +// so descriptor references do not hide the package that carries semantic +// method/interface facts. +type GlobalSummary struct { + pkgs []*PackageMeta + + // symbol space + symIntern map[string]Symbol + symStrings []string // Symbol → text + locToGlb [][]Symbol // [pkgIdx][localSym] → global Symbol + owner []symLoc // global Symbol → owning (pkg, local); pkg<0 if none + ownerRank []uint8 + + // method-name space (distinct from symbols) + nameIntern map[string]Name + nameStrings []string // Name → text + + // per-type flags set at merge time (no translation, just CSR range checks) + isInterface []bool // global Symbol → true if has iface methods + + // lazily translated, cached on first access + methodInfo map[Symbol][]GMethodSlot + interfaceInfo map[Symbol][]GMethodSig + + interfaces []Symbol +} + +// Symbol is a whole-program symbol ID in GlobalSummary's unified namespace. +type Symbol uint32 + +// Name is a whole-program method-name ID, in a namespace distinct from Symbol. +type Name uint32 + +// GMethodSlot is a method slot in the global namespace. +type GMethodSlot struct { + Name Name + MType Symbol + IFn Symbol + TFn Symbol +} + +// GMethodSig is an interface method signature in the global namespace. +type GMethodSig struct { + Name Name + MType Symbol +} + +// IfaceMethodDemand is a reachable interface method call: a demand that some +// type's method matching Sig on interface Target be kept. +type IfaceMethodDemand struct { + Target Symbol + Sig GMethodSig +} + +// symLoc identifies a (package, local symbol) pair. pkg < 0 means "no owner". +type symLoc struct { + pkg int32 + local LocalSymbol +} + +// NewGlobalSummary merges package-local metadata into a whole-program view. +// +// Phase 1 interns all symbol names and builds the locToGlb mapping, owner +// indices, and type-kind flags. No per-symbol data is translated — only +// string interning and CSR range checks happen here. +// +// MethodSlots / InterfaceMethods / reflect are translated lazily on first +// access and cached. This avoids translating thousands of unused slots in +// the common case where DCE only reaches a fraction of all types. +func NewGlobalSummary(pkgs []*PackageMeta) (*GlobalSummary, error) { + g := &GlobalSummary{ + pkgs: pkgs, + symIntern: make(map[string]Symbol), + nameIntern: make(map[string]Name), + locToGlb: make([][]Symbol, len(pkgs)), + methodInfo: make(map[Symbol][]GMethodSlot), + interfaceInfo: make(map[Symbol][]GMethodSig), + } + + // Phase 1: intern symbols, build locToGlb and owner, mark type kinds. + // Touches no edges, translates no slot/sig data. + for pi, pm := range pkgs { + if pm == nil { + continue + } + n := pm.nsyms + tab := make([]Symbol, n) + for li := LocalSymbol(0); li < LocalSymbol(n); li++ { + gs := g.internSymbol(pm.symbolName(li)) + tab[li] = gs + rank := ownerRank(pm, li) + if rank > g.ownerRank[gs] { + g.owner[gs] = symLoc{pkg: int32(pi), local: li} + g.ownerRank[gs] = rank + } + + // mark type kinds (no translation, just CSR range checks) + if pm.nifaceMethod(li) > 0 && !g.isInterface[gs] { + g.isInterface[gs] = true + g.interfaces = append(g.interfaces, gs) + } + } + g.locToGlb[pi] = tab + } + return g, nil +} + +// ownerRank reports whether li carries facts in pm and how authoritative those +// facts are for lazy global queries. Higher rank wins; equal rank keeps the +// first package, preserving deterministic first-wins behavior for equivalent +// duplicate ordinary/linkonce facts. +func ownerRank(pm *PackageMeta, li LocalSymbol) uint8 { + switch { + case pm.hasFuncDemand(li): + return 5 + case pm.nmethodSlot(li) > 0: + return 4 + case pm.nifaceMethod(li) > 0: + return 3 + case pm.ntypeChild(li) > 0: + return 2 + case pm.hasOrdinaryEdges(li): + return 1 + default: + return 0 + } +} + +func (g *GlobalSummary) internSymbol(s string) Symbol { + if id, ok := g.symIntern[s]; ok { + return id + } + id := Symbol(len(g.symStrings)) + g.symIntern[s] = id + g.symStrings = append(g.symStrings, s) + g.owner = append(g.owner, symLoc{pkg: -1}) + g.ownerRank = append(g.ownerRank, 0) + g.isInterface = append(g.isInterface, false) + return id +} + +func (g *GlobalSummary) internName(s string) Name { + if id, ok := g.nameIntern[s]; ok { + return id + } + id := Name(len(g.nameStrings)) + g.nameIntern[s] = id + g.nameStrings = append(g.nameStrings, s) + return id +} + +// ownerData returns the owning package and locToGlb table for sym. +func (g *GlobalSummary) ownerData(sym Symbol) (*PackageMeta, []Symbol, LocalSymbol) { + if int(sym) >= len(g.owner) { + return nil, nil, 0 + } + loc := g.owner[sym] + if loc.pkg < 0 { + return nil, nil, 0 + } + return g.pkgs[loc.pkg], g.locToGlb[loc.pkg], loc.local +} + +func (g *GlobalSummary) translateSlots(tab []Symbol, pm *PackageMeta, li LocalSymbol) []GMethodSlot { + local := pm.methodSlots(li) + out := make([]GMethodSlot, len(local)) + for i, s := range local { + out[i] = GMethodSlot{ + Name: g.internName(pm.nameString(s.Name)), + MType: tab[s.MType], + IFn: tab[s.IFn], + TFn: tab[s.TFn], + } + } + return out +} + +func (g *GlobalSummary) translateSigs(tab []Symbol, pm *PackageMeta, li LocalSymbol) []GMethodSig { + local := pm.ifaceMethods(li) + out := make([]GMethodSig, len(local)) + for i, s := range local { + out[i] = GMethodSig{ + Name: g.internName(pm.nameString(s.Name)), + MType: tab[s.MType], + } + } + return out +} + +// ── symbol / name identity ──────────────────────────────────────────────────── + +// LookupSymbol returns the global Symbol for a module-level symbol name. +func (g *GlobalSummary) LookupSymbol(name string) (Symbol, bool) { + id, ok := g.symIntern[name] + return id, ok +} + +// SymbolName returns the text of a global Symbol. +func (g *GlobalSummary) SymbolName(sym Symbol) string { + if int(sym) < len(g.symStrings) { + return g.symStrings[sym] + } + return "" +} + +// Name returns the text of a global Name. +func (g *GlobalSummary) Name(n Name) string { + if int(n) < len(g.nameStrings) { + return g.nameStrings[n] + } + return "" +} + +// ── enumeration ─────────────────────────────────────────────────────────────── + +// Interfaces returns all interface type symbols. +func (g *GlobalSummary) Interfaces() []Symbol { return g.interfaces } + +// ── lazy per-type queries ───────────────────────────────────────────────────── + +// MethodSlots returns the ABI method slots for concrete type typ. +// Translated lazily on first access, cached thereafter. +func (g *GlobalSummary) MethodSlots(typ Symbol) []GMethodSlot { + if slots, ok := g.methodInfo[typ]; ok { + return slots + } + pm, tab, li := g.ownerData(typ) + if pm == nil { + return nil + } + slots := g.translateSlots(tab, pm, li) + g.methodInfo[typ] = slots + return slots +} + +// InterfaceMethods returns the method set for interface iface. +// Translated lazily on first access, cached thereafter. +func (g *GlobalSummary) InterfaceMethods(iface Symbol) []GMethodSig { + if sigs, ok := g.interfaceInfo[iface]; ok { + return sigs + } + pm, tab, li := g.ownerData(iface) + if pm == nil { + return nil + } + sigs := g.translateSigs(tab, pm, li) + g.interfaceInfo[iface] = sigs + return sigs +} + +// HasReflectMethod reports whether sym triggers conservative reflection handling. +func (g *GlobalSummary) HasReflectMethod(sym Symbol) bool { + _, _, demands := g.ownerDemands(sym) + for _, d := range demands { + if d.Kind == DemandReflectMethod { + return true + } + } + return false +} + +// ── lazy edge queries ───────────────────────────────────────────────────────── + +// ownerOrdinary returns the owning package's locToGlb table and raw local +// ordinary edges for sym, or nil if sym has no owner. +func (g *GlobalSummary) ownerOrdinary(sym Symbol) ([]Symbol, []LocalSymbol) { + pm, tab, li := g.ownerData(sym) + if pm == nil { + return nil, nil + } + return tab, pm.ordinaryEdges(li) +} + +// ownerDemands returns the owning package, its locToGlb table, and raw local +// FuncDemand records for sym, or nil if sym has no owner. +func (g *GlobalSummary) ownerDemands(sym Symbol) (*PackageMeta, []Symbol, []FuncDemand) { + pm, tab, li := g.ownerData(sym) + if pm == nil { + return nil, nil, nil + } + return pm, tab, pm.funcDemands(li) +} + +// OrdinaryEdges returns plain reachability targets from sym (global Symbols). +func (g *GlobalSummary) OrdinaryEdges(sym Symbol) []Symbol { + tab, edges := g.ownerOrdinary(sym) + var out []Symbol + for _, dst := range edges { + out = append(out, tab[dst]) + } + return out +} + +// UseIface returns concrete types converted to interfaces by sym. +func (g *GlobalSummary) UseIface(sym Symbol) []Symbol { + _, tab, demands := g.ownerDemands(sym) + var out []Symbol + for _, d := range demands { + if d.Kind == DemandUseIface { + out = append(out, tab[d.Target]) + } + } + return out +} + +// UseIfaceMethod returns interface method demands emitted by sym. +func (g *GlobalSummary) UseIfaceMethod(sym Symbol) []IfaceMethodDemand { + _, tab, demands := g.ownerDemands(sym) + var out []IfaceMethodDemand + for _, d := range demands { + if d.Kind != DemandIfaceMethod { + continue + } + iface := tab[d.Target] + sigs := g.InterfaceMethods(iface) + if int(d.Extra) < len(sigs) { + out = append(out, IfaceMethodDemand{Target: iface, Sig: sigs[d.Extra]}) + } + } + return out +} + +// UseNamedMethod returns constant MethodByName names referenced by sym. +func (g *GlobalSummary) UseNamedMethod(sym Symbol) []Name { + pm, _, demands := g.ownerDemands(sym) + if pm == nil { + return nil + } + var out []Name + for _, d := range demands { + if d.Kind == DemandNamedMethod { + name := pm.nameString(NameRef{Off: d.Target, Len: d.Extra}) + out = append(out, g.internName(name)) + } + } + return out +} + +// TypeChildren returns child type symbols for typ (global Symbols). +func (g *GlobalSummary) TypeChildren(typ Symbol) []Symbol { + pm, tab, li := g.ownerData(typ) + if pm == nil { + return nil + } + local := pm.typeChildren(li) + if len(local) == 0 { + return nil + } + out := make([]Symbol, len(local)) + for i, c := range local { + out[i] = tab[c] + } + return out +} diff --git a/internal/meta/global_test.go b/internal/meta/global_test.go new file mode 100644 index 0000000000..fb2c4fb22f --- /dev/null +++ b/internal/meta/global_test.go @@ -0,0 +1,217 @@ +package meta_test + +import ( + "testing" + + "github.com/goplus/llgo/internal/meta" +) + +// buildPkgMain builds a "main" package that references a symbol from "runtime" +// and converts a type to an interface defined locally. +func buildPkgMain(t *testing.T) *meta.PackageMeta { + t.Helper() + b := meta.NewBuilder() + + main := b.Sym("main.main") + allocZ := b.Sym("runtime.AllocZ") // defined in runtime, referenced here + myType := b.Sym("*main.Stringer") // defined here + reader := b.Sym("main.Reader") // interface defined here + readT := b.Sym("_llgo_func$Read") + + // main calls runtime.AllocZ, converts *Stringer to Reader, calls Reader.Read + b.AddEdge(main, allocZ, meta.EdgeOrdinary, 0) + b.AddEdge(main, myType, meta.EdgeUseIface, 0) + b.AddEdge(main, reader, meta.EdgeUseIfaceMethod, 0) // Reader.Read = index 0 + + // Reader interface: { Read } + b.AddIfaceMethod(reader, "Read", readT) + + // *Stringer concrete type: slot 0 = Read + rifn := b.Sym("(*Stringer).Read$ifn") + rtfn := b.Sym("(*Stringer).Read$tfn") + b.AddMethodSlot(myType, "Read", readT, rifn, rtfn) + + pm, err := b.Build() + if err != nil { + t.Fatalf("build main: %v", err) + } + return pm +} + +// buildPkgRuntime builds a "runtime" package that defines AllocZ. +func buildPkgRuntime(t *testing.T) *meta.PackageMeta { + t.Helper() + b := meta.NewBuilder() + + allocZ := b.Sym("runtime.AllocZ") // defined here, with a body edge + mallocgc := b.Sym("runtime.mallocgc") + b.AddEdge(allocZ, mallocgc, meta.EdgeOrdinary, 0) + + pm, err := b.Build() + if err != nil { + t.Fatalf("build runtime: %v", err) + } + return pm +} + +func TestGlobalSummaryMerge(t *testing.T) { + mainPkg := buildPkgMain(t) + rtPkg := buildPkgRuntime(t) + defer mainPkg.Close() + defer rtPkg.Close() + + g, err := meta.NewGlobalSummary([]*meta.PackageMeta{mainPkg, rtPkg}) + if err != nil { + t.Fatalf("NewGlobalSummary: %v", err) + } + + sym := func(name string) meta.Symbol { + s, ok := g.LookupSymbol(name) + if !ok { + t.Fatalf("LookupSymbol(%q) not found", name) + } + return s + } + + main := sym("main.main") + allocZ := sym("runtime.AllocZ") + mallocgc := sym("runtime.mallocgc") + myType := sym("*main.Stringer") + reader := sym("main.Reader") + + // ── lazy OrdinaryEdges: main → runtime.AllocZ (cross-package) ────────────── + mainEdges := g.OrdinaryEdges(main) + if len(mainEdges) != 1 || mainEdges[0] != allocZ { + t.Errorf("OrdinaryEdges(main) = %v, want [runtime.AllocZ=%d]", mainEdges, allocZ) + } + + // ── allocZ's edges come from the runtime package (owner) ─────────────────── + azEdges := g.OrdinaryEdges(allocZ) + if len(azEdges) != 1 || azEdges[0] != mallocgc { + t.Errorf("OrdinaryEdges(allocZ) = %v, want [runtime.mallocgc=%d]", azEdges, mallocgc) + } + + // ── UseIface: main converts *Stringer ────────────────────────────────────── + ui := g.UseIface(main) + if len(ui) != 1 || ui[0] != myType { + t.Errorf("UseIface(main) = %v, want [*main.Stringer=%d]", ui, myType) + } + + // ── UseIfaceMethod: main demands Reader.Read ─────────────────────────────── + demands := g.UseIfaceMethod(main) + if len(demands) != 1 { + t.Fatalf("UseIfaceMethod(main): got %d, want 1", len(demands)) + } + if demands[0].Target != reader { + t.Errorf("demand.Target = %d, want reader=%d", demands[0].Target, reader) + } + if g.Name(demands[0].Sig.Name) != "Read" { + t.Errorf("demand.Sig.Name = %q, want \"Read\"", g.Name(demands[0].Sig.Name)) + } + + // ── MethodSlots: *Stringer has Read, name interned globally ──────────────── + slots := g.MethodSlots(myType) + if len(slots) != 1 { + t.Fatalf("MethodSlots(myType): got %d, want 1", len(slots)) + } + if g.Name(slots[0].Name) != "Read" { + t.Errorf("slot name = %q, want \"Read\"", g.Name(slots[0].Name)) + } + // the method name "Read" must intern to the SAME global Name in both the + // interface sig and the concrete slot, so DCE can match them. + if slots[0].Name != demands[0].Sig.Name { + t.Errorf("method name not unified: slot=%d demand=%d", slots[0].Name, demands[0].Sig.Name) + } + + // ── enumeration ──────────────────────────────────────────────────────────── + if len(g.Interfaces()) != 1 || g.Interfaces()[0] != reader { + t.Errorf("Interfaces() = %v, want [reader=%d]", g.Interfaces(), reader) + } + if len(g.MethodSlots(myType)) == 0 { + t.Errorf("MethodSlots(myType) = empty, want non-empty") + } +} + +// TestGlobalSummaryLinkonce verifies first-wins for a symbol defined (with +// facts) in two packages — a linkonce type descriptor. +func TestGlobalSummaryLinkonce(t *testing.T) { + build := func() *meta.PackageMeta { + b := meta.NewBuilder() + typ := b.Sym("*shared.Foo") + child := b.Sym("shared.Bar") + b.AddTypeChild(typ, child) + mt := b.Sym("_llgo_func$M") + b.AddMethodSlot(typ, "M", mt, b.Sym("ifn"), b.Sym("tfn")) + pm, err := b.Build() + if err != nil { + t.Fatal(err) + } + return pm + } + a, bp := build(), build() + defer a.Close() + defer bp.Close() + + g, err := meta.NewGlobalSummary([]*meta.PackageMeta{a, bp}) + if err != nil { + t.Fatalf("NewGlobalSummary: %v", err) + } + + foo, _ := g.LookupSymbol("*shared.Foo") + + // only one MethodInfo entry survives (first-wins), no duplicate concrete type + if got := len(g.MethodSlots(foo)); got != 1 { + t.Errorf("MethodSlots(foo) len = %d, want 1 (first-wins)", got) + } + if got := len(g.MethodSlots(foo)); got != 1 { + t.Errorf("MethodSlots(foo) len = %d, want 1", got) + } + // TypeChildren resolves through the owner + if got := len(g.TypeChildren(foo)); got != 1 { + t.Errorf("TypeChildren(foo) len = %d, want 1", got) + } +} + +func TestGlobalSummaryOwnerPrefersInterfaceInfoOverOrdinaryEdges(t *testing.T) { + descriptorPkg := func() *meta.PackageMeta { + b := meta.NewBuilder() + iface := b.Sym("_llgo_io.Reader") + dep := b.Sym("runtime.descriptor") + b.AddEdge(iface, dep, meta.EdgeOrdinary, 0) + pm, err := b.Build() + if err != nil { + t.Fatal(err) + } + return pm + }() + defPkg := func() *meta.PackageMeta { + b := meta.NewBuilder() + iface := b.Sym("_llgo_io.Reader") + readT := b.Sym("_llgo_func$Read") + b.AddIfaceMethod(iface, "Read", readT) + pm, err := b.Build() + if err != nil { + t.Fatal(err) + } + return pm + }() + defer descriptorPkg.Close() + defer defPkg.Close() + + g, err := meta.NewGlobalSummary([]*meta.PackageMeta{descriptorPkg, defPkg}) + if err != nil { + t.Fatalf("NewGlobalSummary: %v", err) + } + + iface, ok := g.LookupSymbol("_llgo_io.Reader") + if !ok { + t.Fatal("LookupSymbol(_llgo_io.Reader) not found") + } + methods := g.InterfaceMethods(iface) + if len(methods) != 1 { + t.Fatalf("InterfaceMethods(_llgo_io.Reader) len = %d, want 1", len(methods)) + } + if got := g.Name(methods[0].Name); got != "Read" { + t.Fatalf("InterfaceMethods(_llgo_io.Reader)[0].Name = %q, want Read", got) + } +} diff --git a/internal/meta/meta.go b/internal/meta/meta.go new file mode 100644 index 0000000000..cd780afbcf --- /dev/null +++ b/internal/meta/meta.go @@ -0,0 +1,269 @@ +package meta + +import ( + "encoding/binary" + "fmt" + "os" + "syscall" + "unsafe" +) + +// PackageMeta is a zero-copy view over a .meta file byte slice. +// The underlying bytes may come from an mmap'd file or from Builder.Build(). +// All query methods read directly from the byte slice with no allocation. +type PackageMeta struct { + raw []byte + mmap bool // true → must Munmap on Close + + nsyms uint32 + + // cached section start offsets (parsed once from header) + strOff uint32 + symOff uint32 + ordinaryOff uint32 + demandOff uint32 + childOff uint32 + methodOff uint32 + ifaceOff uint32 +} + +// FuncDemand is a decoded function-demand record. Its in-memory layout +// (Kind@0, Target@4, Extra@8, size 12) must match the on-disk wire layout exactly +// so funcDemands can reinterpret the mmap bytes as []FuncDemand with no copy. +type FuncDemand struct { + Kind uint32 + Target uint32 // LocalSymbol or stringTable offset (DemandNamedMethod) + Extra uint32 +} + +// Compile-time assertion: FuncDemand must be exactly 12 bytes. If either const +// goes negative the build fails, pinning the wire/struct layout match. +const ( + _ = uint(unsafe.Sizeof(FuncDemand{}) - 12) + _ = uint(12 - unsafe.Sizeof(FuncDemand{})) +) + +// MethodSlot is a decoded method slot record. Its layout (NameRef@0..8, +// MType@8, IFn@12, TFn@16, size 20) must match the on-disk wire layout for +// zero-copy reads. +type MethodSlot struct { + Name NameRef // method short name + MType LocalSymbol + IFn LocalSymbol + TFn LocalSymbol +} + +// MethodSig is a decoded interface method signature. Layout: NameRef@0..8, +// MType@8, size 12 — must match the on-disk wire layout for zero-copy reads. +type MethodSig struct { + Name NameRef // method short name + MType LocalSymbol +} + +// Compile-time assertions pinning the wire/struct layout for zero-copy reads. +// If a struct's size drifts, one of these uint consts goes negative and the +// build fails. +const ( + _ = uint(unsafe.Sizeof(MethodSlot{}) - 20) + _ = uint(20 - unsafe.Sizeof(MethodSlot{})) + _ = uint(unsafe.Sizeof(MethodSig{}) - 12) + _ = uint(12 - unsafe.Sizeof(MethodSig{})) +) + +// ReadMeta opens path, mmaps it, and returns a PackageMeta view. +// Call Close when done to release the mapping. +func ReadMeta(path string) (*PackageMeta, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + fi, err := f.Stat() + if err != nil { + return nil, err + } + size := int(fi.Size()) + if size < headerSize { + return nil, fmt.Errorf("meta: file too small: %s", path) + } + + raw, err := syscall.Mmap(int(f.Fd()), 0, size, syscall.PROT_READ, syscall.MAP_SHARED) + if err != nil { + return nil, fmt.Errorf("meta: mmap %s: %w", path, err) + } + + pm, err := newPackageMeta(raw) + if err != nil { + _ = syscall.Munmap(raw) + return nil, err + } + pm.mmap = true + return pm, nil +} + +// Bytes returns the underlying raw byte slice (for writing to disk). +func (pm *PackageMeta) Bytes() []byte { return pm.raw } + +// Close releases the mmap mapping if one was used. +func (pm *PackageMeta) Close() error { + if pm.mmap && pm.raw != nil { + err := syscall.Munmap(pm.raw) + pm.raw = nil + return err + } + return nil +} + +// symbolName returns the name of sym as a zero-copy view into the string table. +// The returned string points directly into the mmap region and is only valid for +// the lifetime of pm — do not retain it after Close. +func (pm *PackageMeta) symbolName(sym LocalSymbol) string { + if uint32(sym) >= pm.nsyms { + return "" + } + const recSize = 12 + base := pm.symOff + 4 + uint32(sym)*recSize + nameOff := binary.LittleEndian.Uint32(pm.raw[base+0:]) + nameLen := binary.LittleEndian.Uint32(pm.raw[base+4:]) + return unsafe.String(&pm.raw[pm.strOff+nameOff], int(nameLen)) +} + +// NameString returns the string referenced by a NameRef as a zero-copy view +// into the string table. The returned string points directly into the mmap +// region and is only valid for the lifetime of pm — do not retain it after Close. +func (pm *PackageMeta) nameString(ref NameRef) string { + return unsafe.String(&pm.raw[pm.strOff+ref.Off], int(ref.Len)) +} + +// NOrdinaryEdge returns the number of plain reachability edges from sym. +func (pm *PackageMeta) nordinaryEdge(sym LocalSymbol) uint32 { + s, e := pm.csrRange(pm.ordinaryOff, sym) + return e - s +} + +// ordinaryEdges returns all plain reachability targets from sym as a zero-copy +// view into the mmap region. +func (pm *PackageMeta) ordinaryEdges(sym LocalSymbol) []LocalSymbol { + return csrSlice[LocalSymbol](pm, pm.ordinaryOff, sym, 4) +} + +// NFuncDemand returns the number of method/interface/reflection demands from sym. +func (pm *PackageMeta) nfuncDemand(sym LocalSymbol) uint32 { + s, e := pm.csrRange(pm.demandOff, sym) + return e - s +} + +// funcDemands returns all method/interface/reflection demands from sym as a +// zero-copy view into the mmap region. +func (pm *PackageMeta) funcDemands(sym LocalSymbol) []FuncDemand { + return csrSlice[FuncDemand](pm, pm.demandOff, sym, 12) +} + +// NTypeChild returns the number of type children for sym, or 0 if none. +func (pm *PackageMeta) ntypeChild(sym LocalSymbol) uint32 { + s, e := pm.csrRange(pm.childOff, sym) + return e - s +} + +// TypeChildren returns the child type LocalSymbols for sym as a zero-copy view +// into the mmap region. +func (pm *PackageMeta) typeChildren(sym LocalSymbol) []LocalSymbol { + return csrSlice[LocalSymbol](pm, pm.childOff, sym, 4) +} + +// NMethodSlot returns the number of ABI method slots for sym, or 0 if none. +func (pm *PackageMeta) nmethodSlot(sym LocalSymbol) uint32 { + s, e := pm.csrRange(pm.methodOff, sym) + return e - s +} + +// MethodSlots returns the ABI method slots for concrete type sym as a zero-copy +// view into the mmap region. +func (pm *PackageMeta) methodSlots(sym LocalSymbol) []MethodSlot { + return csrSlice[MethodSlot](pm, pm.methodOff, sym, 20) +} + +// NIfaceMethod returns the number of methods in an interface, or 0 if sym is +// not an interface. +func (pm *PackageMeta) nifaceMethod(sym LocalSymbol) uint32 { + s, e := pm.csrRange(pm.ifaceOff, sym) + return e - s +} + +// IfaceMethods returns the method signatures for interface sym as a zero-copy +// view into the mmap region. +func (pm *PackageMeta) ifaceMethods(sym LocalSymbol) []MethodSig { + return csrSlice[MethodSig](pm, pm.ifaceOff, sym, 12) +} + +// HasReflect reports whether sym triggers conservative reflection handling. +func (pm *PackageMeta) hasReflect(sym LocalSymbol) bool { + for _, d := range pm.funcDemands(sym) { + if d.Kind == DemandReflectMethod { + return true + } + } + return false +} + +// HasOrdinaryEdges reports whether sym has any plain reachability edges. +func (pm *PackageMeta) hasOrdinaryEdges(sym LocalSymbol) bool { + return pm.nordinaryEdge(sym) > 0 +} + +// HasFuncDemand reports whether sym has any method/interface/reflection demand. +func (pm *PackageMeta) hasFuncDemand(sym LocalSymbol) bool { + return pm.nfuncDemand(sym) > 0 +} + +// ── internal helpers ────────────────────────────────────────────────────────── + +// newPackageMeta parses the header of raw and returns a PackageMeta. +func newPackageMeta(raw []byte) (*PackageMeta, error) { + if len(raw) < headerSize { + return nil, fmt.Errorf("meta: raw too small (%d bytes)", len(raw)) + } + if string(raw[0:4]) != Magic { + return nil, fmt.Errorf("meta: bad magic %q", raw[0:4]) + } + ver := binary.LittleEndian.Uint32(raw[4:8]) + if ver != Version { + return nil, fmt.Errorf("meta: unsupported version %d", ver) + } + + pm := &PackageMeta{raw: raw} + pm.strOff = binary.LittleEndian.Uint32(raw[8+SecStringTable*4:]) + pm.symOff = binary.LittleEndian.Uint32(raw[8+SecSymbols*4:]) + pm.ordinaryOff = binary.LittleEndian.Uint32(raw[8+SecOrdinaryEdges*4:]) + pm.demandOff = binary.LittleEndian.Uint32(raw[8+SecFuncDemand*4:]) + pm.childOff = binary.LittleEndian.Uint32(raw[8+SecTypeChildren*4:]) + pm.methodOff = binary.LittleEndian.Uint32(raw[8+SecMethodInfo*4:]) + pm.ifaceOff = binary.LittleEndian.Uint32(raw[8+SecIfaceInfo*4:]) + + // read nsyms from Symbols section header + pm.nsyms = binary.LittleEndian.Uint32(raw[pm.symOff:]) + return pm, nil +} + +// csrSlice returns a zero-copy []T view into a CSR section. recSize is the +// on-disk size of one record (must match unsafe.Sizeof(T)). +func csrSlice[T any](pm *PackageMeta, sectionOff uint32, sym LocalSymbol, recSize uintptr) []T { + if uint32(sym) >= pm.nsyms { + return nil + } + start, end := pm.csrRange(sectionOff, sym) + if start == end { + return nil + } + dataBase := sectionOff + 4 + (pm.nsyms+1)*4 + p := (*T)(unsafe.Pointer(&pm.raw[dataBase+uint32(uintptr(start)*recSize)])) + return unsafe.Slice(p, end-start) +} + +func (pm *PackageMeta) csrRange(sectionOff uint32, sym LocalSymbol) (start, end uint32) { + offsetsBase := sectionOff + 4 // skip nsyms u32 + start = binary.LittleEndian.Uint32(pm.raw[offsetsBase+uint32(sym)*4:]) + end = binary.LittleEndian.Uint32(pm.raw[offsetsBase+(uint32(sym)+1)*4:]) + return +} diff --git a/internal/meta/meta_test.go b/internal/meta/meta_test.go new file mode 100644 index 0000000000..ab332a25b9 --- /dev/null +++ b/internal/meta/meta_test.go @@ -0,0 +1,269 @@ +package meta + +import ( + "os" + "testing" + "unsafe" +) + +// TestWireLayout verifies the zero-copy structs match their on-disk byte layout: +// correct total size and field offsets. If these drift, unsafe reinterpretation +// of mmap bytes would silently corrupt — so we assert them explicitly. +func TestWireLayout(t *testing.T) { + if got := unsafe.Sizeof(FuncDemand{}); got != 12 { + t.Errorf("sizeof(FuncDemand) = %d, want 12", got) + } + if got := unsafe.Offsetof(FuncDemand{}.Kind); got != 0 { + t.Errorf("FuncDemand.Kind offset = %d, want 0", got) + } + if got := unsafe.Offsetof(FuncDemand{}.Target); got != 4 { + t.Errorf("FuncDemand.Target offset = %d, want 4", got) + } + if got := unsafe.Offsetof(FuncDemand{}.Extra); got != 8 { + t.Errorf("FuncDemand.Extra offset = %d, want 8", got) + } + + if got := unsafe.Sizeof(MethodSlot{}); got != 20 { + t.Errorf("sizeof(MethodSlot) = %d, want 20", got) + } + if got := unsafe.Offsetof(MethodSlot{}.MType); got != 8 { + t.Errorf("MethodSlot.MType offset = %d, want 8", got) + } + if got := unsafe.Offsetof(MethodSlot{}.TFn); got != 16 { + t.Errorf("MethodSlot.TFn offset = %d, want 16", got) + } + + if got := unsafe.Sizeof(MethodSig{}); got != 12 { + t.Errorf("sizeof(MethodSig) = %d, want 12", got) + } + if got := unsafe.Offsetof(MethodSig{}.MType); got != 8 { + t.Errorf("MethodSig.MType offset = %d, want 8", got) + } +} + +// TestTypeChildrenAlignment uses symbol names of irregular total length so the +// string table is unlikely to land on a 4-byte boundary on its own, verifying +// that stringTable padding keeps the zero-copy TypeChildren view correctly aligned. +func TestTypeChildrenAlignment(t *testing.T) { + for _, pad := range []string{"a", "ab", "abc", "abcd", "abcde"} { + b := NewBuilder() + // a symbol whose name length varies, to shift the string table size + b.Sym("x." + pad) + parent := b.Sym("*pkg.Parent") + c0 := b.Sym("pkg.C0") + c1 := b.Sym("pkg.C1") + c2 := b.Sym("pkg.C2") + b.AddTypeChild(parent, c0) + b.AddTypeChild(parent, c1) + b.AddTypeChild(parent, c2) + + pm, err := b.Build() + if err != nil { + t.Fatalf("pad=%q build: %v", pad, err) + } + got := pm.typeChildren(parent) + want := []LocalSymbol{c0, c1, c2} + if len(got) != len(want) { + t.Fatalf("pad=%q TypeChildren len = %d, want %d", pad, len(got), len(want)) + } + for i := range want { + if got[i] != want[i] { + t.Errorf("pad=%q child[%d] = %d, want %d", pad, i, got[i], want[i]) + } + } + } +} + +// TestRoundTrip builds a small package summary, serializes it, then reads it +// back and verifies every query returns the expected values. +func TestRoundTrip(t *testing.T) { + b := NewBuilder() + + // symbols + main := b.Sym("main.main") + helper := b.Sym("main.helper") + allocZ := b.Sym("runtime.AllocZ") + myType := b.Sym("*_llgo_main.MyStruct") + myField := b.Sym("_llgo_main.Inner") + myIface := b.Sym("_llgo_iface$Reader") + mtype := b.Sym("_llgo_func$Read") + ifn := b.Sym("(*MyStruct).Read$ifn") + tfn := b.Sym("(*MyStruct).Read$tfn") + + // ordinary edges + b.AddEdge(main, helper, EdgeOrdinary, 0) + b.AddEdge(main, allocZ, EdgeOrdinary, 0) + + // interface conversion + b.AddEdge(main, myType, EdgeUseIface, 0) + + // interface method call: Reader.Read is method index 0 + b.AddEdge(main, myIface, EdgeUseIfaceMethod, 0) + + // named method call + b.AddNamedMethodEdge(helper, "ServeHTTP") + + // TypeChildren: *MyStruct contains Inner + b.AddTypeChild(myType, myField) + + // MethodInfo for *MyStruct: slot 0 = Read + b.AddMethodSlot(myType, "Read", mtype, ifn, tfn) + + // InterfaceInfo for Reader: method 0 = Read + b.AddIfaceMethod(myIface, "Read", mtype) + + // reflect + b.MarkReflect(helper) + + // build + pm, err := b.Build() + if err != nil { + t.Fatalf("Build: %v", err) + } + + // ── verify Symbols ──────────────────────────────────────────────────────── + + checkName := func(sym LocalSymbol, want string) { + t.Helper() + if got := pm.symbolName(sym); got != want { + t.Errorf("SymbolName(%d) = %q, want %q", sym, got, want) + } + } + checkName(main, "main.main") + checkName(helper, "main.helper") + checkName(allocZ, "runtime.AllocZ") + checkName(myType, "*_llgo_main.MyStruct") + + // ── verify OrdinaryEdges / FuncDemand ───────────────────────────────────── + + mainEdges := pm.ordinaryEdges(main) + if len(mainEdges) != 2 { + t.Fatalf("OrdinaryEdges(main): got %d edges, want 2", len(mainEdges)) + } + if mainEdges[0] != helper { + t.Errorf("ordinary[0] = %d, want helper=%d", mainEdges[0], helper) + } + if mainEdges[1] != allocZ { + t.Errorf("ordinary[1] = %d, want allocZ=%d", mainEdges[1], allocZ) + } + + mainDemands := pm.funcDemands(main) + if len(mainDemands) != 2 { + t.Fatalf("FuncDemand(main): got %d demands, want 2", len(mainDemands)) + } + if d := mainDemands[0]; d.Kind != DemandUseIface || LocalSymbol(d.Target) != myType { + t.Errorf("demand[0] = %+v, want {Kind:UseIface Target:%d}", d, myType) + } + if d := mainDemands[1]; d.Kind != DemandIfaceMethod || LocalSymbol(d.Target) != myIface || d.Extra != 0 { + t.Errorf("demand[1] = %+v, want {Kind:IfaceMethod Target:%d Extra:0}", d, myIface) + } + + helperDemands := pm.funcDemands(helper) + if len(helperDemands) != 2 { + t.Fatalf("FuncDemand(helper): got %d, want 2", len(helperDemands)) + } + if d := helperDemands[0]; d.Kind != DemandNamedMethod { + t.Errorf("helper demand[0].Kind = %d, want NamedMethod", d.Kind) + } + // For UseNamedMethod, target=NameRef.Off and extra=NameRef.Len. + gotName := pm.nameString(NameRef{Off: helperDemands[0].Target, Len: helperDemands[0].Extra}) + if gotName != "ServeHTTP" { + t.Errorf("UseNamedMethod target name = %q, want \"ServeHTTP\"", gotName) + } + if d := helperDemands[1]; d.Kind != DemandReflectMethod { + t.Errorf("helper demand[1].Kind = %d, want ReflectMethod", d.Kind) + } + if got := pm.ordinaryEdges(allocZ); len(got) != 0 { + t.Errorf("OrdinaryEdges(allocZ): got %d, want 0", len(got)) + } + + // ── verify TypeChildren ─────────────────────────────────────────────────── + + children := pm.typeChildren(myType) + if len(children) != 1 || children[0] != myField { + t.Errorf("TypeChildren(myType) = %v, want [%d]", children, myField) + } + if pm.typeChildren(main) != nil { + t.Errorf("TypeChildren(main) should be nil") + } + if pm.ntypeChild(myType) == 0 { + t.Errorf("NTypeChild(myType) = 0, want >0") + } + if pm.ntypeChild(main) > 0 { + t.Errorf("NTypeChild(main) > 0, want 0") + } + + // ── verify MethodSlots ──────────────────────────────────────────────────── + + slots := pm.methodSlots(myType) + if len(slots) != 1 { + t.Fatalf("MethodSlots(myType): got %d, want 1", len(slots)) + } + slot := slots[0] + if pm.nameString(slot.Name) != "Read" { + t.Errorf("slot.Name = %q, want \"Read\"", pm.nameString(slot.Name)) + } + if slot.MType != mtype || slot.IFn != ifn || slot.TFn != tfn { + t.Errorf("slot = %+v, unexpected symbols", slot) + } + if len(pm.methodSlots(myType)) == 0 { + t.Errorf("MethodSlots(myType) = empty, want non-empty") + } + + // ── verify IfaceMethods ─────────────────────────────────────────────────── + + sigs := pm.ifaceMethods(myIface) + if len(sigs) != 1 { + t.Fatalf("IfaceMethods(myIface): got %d, want 1", len(sigs)) + } + if pm.nameString(sigs[0].Name) != "Read" { + t.Errorf("iface method name = %q, want \"Read\"", pm.nameString(sigs[0].Name)) + } + if pm.nifaceMethod(myIface) == 0 { + t.Errorf("NIfaceMethod(myIface) = 0, want >0") + } + if pm.nifaceMethod(main) > 0 { + t.Errorf("NIfaceMethod(main) > 0, want 0") + } + + // ── verify reflect demand ───────────────────────────────────────────────── + + if !pm.hasReflect(helper) { + t.Errorf("HasReflect(helper) = false, want true") + } + if pm.hasReflect(main) { + t.Errorf("HasReflect(main) = true, want false") + } +} + +// TestRoundTripFile writes the meta to disk and reads it back via ReadMeta. +func TestRoundTripFile(t *testing.T) { + b := NewBuilder() + fn := b.Sym("pkg.Fn") + dep := b.Sym("runtime.X") + b.AddEdge(fn, dep, EdgeOrdinary, 0) + + pm, err := b.Build() + if err != nil { + t.Fatalf("Build: %v", err) + } + + path := t.TempDir() + "/test.meta" + if err := os.WriteFile(path, pm.Bytes(), 0644); err != nil { + t.Fatalf("write: %v", err) + } + + pm2, err := ReadMeta(path) + if err != nil { + t.Fatalf("ReadMeta: %v", err) + } + defer pm2.Close() + + if got := pm2.symbolName(fn); got != "pkg.Fn" { + t.Errorf("SymbolName after file round-trip = %q, want \"pkg.Fn\"", got) + } + edges := pm2.ordinaryEdges(fn) + if len(edges) != 1 || edges[0] != dep { + t.Errorf("OrdinaryEdges after file round-trip = %v", edges) + } +} diff --git a/internal/meta/types.go b/internal/meta/types.go new file mode 100644 index 0000000000..5c69090d04 --- /dev/null +++ b/internal/meta/types.go @@ -0,0 +1,61 @@ +// Package meta defines the binary format and in-memory view for LLGo package +// summary cache files (.meta). The format is designed for zero-copy access via +// mmap: the file layout is the memory layout. +package meta + +// LocalSymbol is a package-local symbol ID, equal to its index in the Symbols +// section. Valid within one PackageMeta only; use GlobalSummary for cross-package +// references. +type LocalSymbol uint32 + +// NameRef references a name string by its byte range in the string table. +// It is used for method short names, which are matched by value across packages +// — names are not module-level symbols and live in their own namespace. +type NameRef struct { + Off uint32 + Len uint32 +} + +// Edge/demand kinds used by Builder.AddEdge for compatibility with existing +// emit sites. EdgeOrdinary is written to OrdinaryEdges; the other kinds are +// written to FuncDemand. +const ( + // EdgeOrdinary is a plain symbol reference (call, type use, global var, etc.). + EdgeOrdinary uint8 = 0 + // EdgeUseIface marks that the source converts Target type to an interface. + EdgeUseIface uint8 = 1 + // EdgeUseIfaceMethod marks a call to method Extra of interface Target. + EdgeUseIfaceMethod uint8 = 2 + // EdgeUseNamedMethod marks a constant MethodByName call; Target is a + // stringTable byte offset (not a LocalSymbol). + EdgeUseNamedMethod uint8 = 3 +) + +// FuncDemand kinds used in the FuncDemand section. +const ( + DemandUseIface uint32 = uint32(EdgeUseIface) + DemandIfaceMethod uint32 = uint32(EdgeUseIfaceMethod) + DemandNamedMethod uint32 = uint32(EdgeUseNamedMethod) + DemandReflectMethod uint32 = 4 +) + +// Magic is the 4-byte file signature. +const Magic = "LLPM" + +// Version is the current binary format version. +const Version = 2 + +// Section index constants for Header.SectionOffsets. +const ( + SecStringTable = 0 + SecSymbols = 1 + SecOrdinaryEdges = 2 + SecFuncDemand = 3 + SecTypeChildren = 4 + SecMethodInfo = 5 + SecIfaceInfo = 6 + numSections = 7 +) + +// headerSize = magic(4) + version(4) + sectionOffsets(numSections×4) +const headerSize = 4 + 4 + numSections*4 diff --git a/runtime/internal/runtime/z_face.go b/runtime/internal/runtime/z_face.go index 7c48989fd2..f118f90bfb 100644 --- a/runtime/internal/runtime/z_face.go +++ b/runtime/internal/runtime/z_face.go @@ -142,7 +142,7 @@ func NewItab(inter *InterfaceType, typ *Type) *Itab { // matched but no function pointer (e.g. stripped/unreachable after DCE); // keep itab entry with a placeholder so the itab stays intact // and only panics on call, not at assertion time. - fn = abi.Text(uintptr(0)) + fn = abi.Text(c.Func(unreachableMethod)) } *c.Advance(data, i) = uintptr(fn) } @@ -171,6 +171,12 @@ func findMethod(mthds []abi.Method, im abi.Imethod) (abi.Text, bool) { return nil, false } +// unreachableMethod matches Go's runtime.unreachableMethod: method entries +// proven unreachable by link-time DCE are redirected here and should never run. +func unreachableMethod() { + throw("unreachable method called. linker bug?") +} + func IfaceType(i iface) *abi.Type { if i.tab == nil { return nil diff --git a/ssa/abitype.go b/ssa/abitype.go index f4660c3fec..17300f9573 100644 --- a/ssa/abitype.go +++ b/ssa/abitype.go @@ -199,13 +199,15 @@ type Imethod struct { } */ -func (b Builder) abiInterfaceImethods(t *types.Interface, name string) llvm.Value { +func (b Builder) abiInterfaceImethods(t *types.Interface, typeName string) llvm.Value { prog := b.Prog n := t.NumMethods() if n == 0 { return prog.Nil(prog.rtType("Slice")).impl } - g := b.Pkg.VarOf(name) + + imethodsName := typeName + "$imethods" + g := b.Pkg.VarOf(imethodsName) if g == nil { ft := prog.rtType("Imethod") fields := make([]llvm.Value, n) @@ -223,7 +225,7 @@ func (b Builder) abiInterfaceImethods(t *types.Interface, name string) llvm.Valu } atyp := prog.rawType(types.NewArray(ft.RawType(), int64(n))) data := Expr{llvm.ConstArray(ft.ll, fields), atyp} - g = b.Pkg.doNewVar(name, prog.Pointer(atyp)) + g = b.Pkg.doNewVar(imethodsName, prog.Pointer(atyp)) g.Init(data) g.impl.SetGlobalConstant(true) g.impl.SetLinkage(llvm.WeakODRLinkage) @@ -236,6 +238,20 @@ func (b Builder) abiInterfaceImethods(t *types.Interface, name string) llvm.Valu }) } +func (p Package) recordInterfaceInfo(t *types.Interface, typeName string) { + mb := p.MetaBuilder + if mb == nil { + return + } + prog := p.Prog + intfSym := mb.Sym(typeName) + for i := 0; i < t.NumMethods(); i++ { + f := t.Method(i) + ftypName, _ := prog.abi.TypeName(funcType(prog, f.Type())) + mb.AddIfaceMethod(intfSym, mthName(f), mb.Sym(ftypName)) + } +} + func (b Builder) abiTuples(t *types.Tuple, name string) llvm.Value { prog := b.Prog n := t.Len() @@ -330,10 +346,9 @@ func (b Builder) abiExtendedFields(t types.Type, name string, global llvm.Value) b.abiStructFields(t, name+"$fields"), } case *types.Interface: - name, _ = prog.abi.TypeName(t) fields = []llvm.Value{ b.Str(pkg.Path()).impl, - b.abiInterfaceImethods(t, name+"$imethods"), + b.abiInterfaceImethods(t, name), } case *types.Named: return b.abiExtendedFields(t.Underlying(), name, global) @@ -341,6 +356,71 @@ func (b Builder) abiExtendedFields(t types.Type, name string, global llvm.Value) return } +func (b Builder) recordTypeChildren(parentName string, t types.Type) { + mb := b.Pkg.MetaBuilder + if mb == nil { + return + } + parent := mb.Sym(parentName) + for _, child := range b.directTypeChildren(t) { + childName, _ := b.Prog.abi.TypeName(child) + mb.AddTypeChild(parent, mb.Sym(childName)) + } +} + +func (b Builder) directTypeChildren(t types.Type) []types.Type { + switch t := types.Unalias(t).(type) { + case *types.Basic: + return nil + case *types.Pointer: + return []types.Type{abi.PublicType(t.Elem())} + case *types.Chan: + return []types.Type{abi.PublicType(t.Elem())} + case *types.Slice: + return []types.Type{abi.PublicType(t.Elem())} + case *types.Array: + elem := abi.PublicType(t.Elem()) + return []types.Type{elem, types.NewSlice(elem)} + case *types.Map: + return []types.Type{ + abi.PublicType(t.Key()), + abi.PublicType(t.Elem()), + // Map type descriptors reference their runtime bucket type too. + b.Prog.abi.MapBucket(t), + } + case *types.Signature: + var children []types.Type + children = appendTupleTypeChildren(children, t.Params()) + children = appendTupleTypeChildren(children, t.Results()) + return children + case *types.Struct: + children := make([]types.Type, 0, t.NumFields()) + for i := 0; i < t.NumFields(); i++ { + children = append(children, abi.PublicType(t.Field(i).Type())) + } + return children + case *types.Interface: + children := make([]types.Type, 0, t.NumMethods()) + for i := 0; i < t.NumMethods(); i++ { + children = append(children, funcType(b.Prog, t.Method(i).Type())) + } + return children + case *types.Named: + return b.directTypeChildren(t.Underlying()) + } + return nil +} + +func appendTupleTypeChildren(children []types.Type, tuple *types.Tuple) []types.Type { + if tuple == nil { + return children + } + for i := 0; i < tuple.Len(); i++ { + children = append(children, abi.PublicType(tuple.At(i).Type())) + } + return children +} + func (b Builder) abiUncommonPkg(t types.Type) (*types.Package, string) { retry: switch typ := types.Unalias(t).(type) { @@ -432,6 +512,7 @@ func (b Builder) abiUncommonMethods(t types.Type, mset *types.MethodSet) llvm.Va ft := prog.rtType("Method") n := mset.Len() fields := make([]llvm.Value, n) + typeName, _ := prog.abi.TypeName(t) pkg, _ := b.abiUncommonPkg(t) anonymous := pkg == nil if anonymous { @@ -439,11 +520,12 @@ func (b Builder) abiUncommonMethods(t types.Type, mset *types.MethodSet) llvm.Va } for i := 0; i < n; i++ { m := mset.At(i) - obj := m.Obj() + obj := m.Obj().(*types.Func) mName := obj.Name() + fullName := mthName(obj) name := b.Str(mName).impl if !token.IsExported(mName) { - name = b.Str(abi.FullName(obj.Pkg(), mName)).impl + name = b.Str(fullName).impl } mSig := m.Type().(*types.Signature) var tfn, ifn llvm.Value @@ -463,6 +545,10 @@ func (b Builder) abiUncommonMethods(t types.Type, mset *types.MethodSet) llvm.Va values = append(values, ifn) values = append(values, tfn) fields[i] = llvm.ConstNamedStruct(ft.ll, values) + if mb := b.Pkg.MetaBuilder; mb != nil { + mtypeName, _ := prog.abi.TypeName(ftyp) + mb.AddMethodSlot(mb.Sym(typeName), fullName, mb.Sym(mtypeName), mb.Sym(ifn.Name()), mb.Sym(tfn.Name())) + } } return llvm.ConstArray(ft.ll, fields) } @@ -473,6 +559,14 @@ func funcType(prog Program, typ types.Type) types.Type { return ftyp.raw.Type.(*types.Struct).Field(0).Type() } +func mthName(method *types.Func) string { + name := method.Name() + if token.IsExported(name) { + return name + } + return abi.FullName(method.Pkg(), name) +} + func methodExprSignature(sig *types.Signature) *types.Signature { recv := sig.Recv() if recv == nil { @@ -522,6 +616,7 @@ func (b Builder) abiType(t types.Type) Expr { if prog.patchType != nil { t = prog.patchType(t) } + b.recordTypeChildren(name, t) mset, hasUncommon := b.abiUncommonMethodSet(t) methodCount := 0 if mset != nil { diff --git a/ssa/expr.go b/ssa/expr.go index eafadf2767..a9f45f2e34 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -1310,9 +1310,15 @@ func (b Builder) checkReflect(fn Expr, args []Expr) (check ReflectMethodCheck) { if v, ok := extractConstInt(args[1].impl); ok { pkg.RecordReflectMethodByIndex(v) reflectKind = ReflectMethodByIndex + if mb := pkg.MetaBuilder; mb != nil { + mb.MarkReflect(mb.Sym(b.Func.Name())) + } break } reflectKind = ReflectMethodDynamic + if mb := pkg.MetaBuilder; mb != nil { + mb.MarkReflect(mb.Sym(b.Func.Name())) + } } case "reflect.Value.MethodByName": if len(args) == 2 { @@ -1320,9 +1326,15 @@ func (b Builder) checkReflect(fn Expr, args []Expr) (check ReflectMethodCheck) { pkg.RecordReflectMethodByName(v) reflectKind = ReflectMethodByName check.Name = v + if mb := pkg.MetaBuilder; mb != nil { + mb.AddNamedMethodEdge(mb.Sym(b.Func.Name()), v) + } break } reflectKind = ReflectMethodDynamic + if mb := pkg.MetaBuilder; mb != nil { + mb.MarkReflect(mb.Sym(b.Func.Name())) + } } } pkg.NeedAbiInit |= reflectKind diff --git a/ssa/interface.go b/ssa/interface.go index 2539c3187e..14d5714cff 100644 --- a/ssa/interface.go +++ b/ssa/interface.go @@ -20,6 +20,7 @@ import ( "go/token" "go/types" + "github.com/goplus/llgo/internal/meta" "github.com/goplus/llgo/ssa/abi" "github.com/xgo-dev/llvm" ) @@ -65,7 +66,9 @@ func iMethodOf(rawIntf *types.Interface, name string) int { // Imethod returns closure of an interface method. func (b Builder) Imethod(intf Expr, method *types.Func) Expr { prog := b.Prog - rawIntf := intf.raw.Type.Underlying().(*types.Interface) + intfType := types.Unalias(intf.raw.Type) + patchedIntfType := prog.patch(intfType) + rawIntf := patchedIntfType.Underlying().(*types.Interface) sig := method.Type().(*types.Signature) if sig.Recv() == nil && sig.Params().Len() > 0 { pt := types.Unalias(sig.Params().At(0).Type()) @@ -79,9 +82,16 @@ func (b Builder) Imethod(intf Expr, method *types.Func) Expr { } } tclosure := prog.Type(sig, InGo) + i := iMethodOf(rawIntf, method.Name()) + if mb := b.Pkg.MetaBuilder; mb != nil { + intfSymName := func() string { n, _ := prog.abi.TypeName(rawIntf); return n }() + intfSym := mb.Sym(intfSymName) + b.Pkg.recordInterfaceInfo(rawIntf, intfSymName) + // Record which interface method is demanded. i is the method's index in rawIntf. + mb.AddEdge(mb.Sym(b.Func.Name()), intfSym, meta.EdgeUseIfaceMethod, uint32(i)) + } data := b.InlineCall(b.Pkg.rtFunc("IfacePtrData"), intf) var fn Expr - i := iMethodOf(rawIntf, method.Name()) impl := intf.impl itab := Expr{b.faceItab(impl), prog.VoidPtrPtr()} pfn := b.Advance(itab, prog.IntVal(uint64(i+3), prog.Int())) @@ -123,6 +133,7 @@ func (b Builder) MakeInterface(tinter Type, x Expr) (ret Expr) { } prog := b.Prog typ := x.Type + b.recordUseIface(typ) tabi := b.abiType(typ.raw.Type) if !directIfaceType(typ.raw.Type) { vptr := b.AllocU(typ) @@ -172,6 +183,7 @@ func (b Builder) MakeInterfaceFromPtr(tinter Type, ptr Expr) (ret Expr) { return b.MakeInterface(tinter, b.Load(ptr)) } + b.recordUseIface(typ) vptr := b.AllocU(typ) dst := b.Convert(prog.VoidPtr(), vptr) src := b.Convert(prog.VoidPtr(), ptr) @@ -179,6 +191,15 @@ func (b Builder) MakeInterfaceFromPtr(tinter Type, ptr Expr) (ret Expr) { return Expr{b.unsafeInterface(rawIntf, tabi, vptr.impl), tinter} } +func (b Builder) recordUseIface(typ Type) { + if mb := b.Pkg.MetaBuilder; mb != nil { + if _, ok := types.Unalias(typ.raw.Type).Underlying().(*types.Interface); !ok { + typeName, _ := b.Prog.abi.TypeName(typ.raw.Type) + mb.AddEdge(mb.Sym(b.Func.Name()), mb.Sym(typeName), meta.EdgeUseIface, 0) + } + } +} + func (b Builder) valFromData(typ Type, data llvm.Value) Expr { prog := b.Prog if !directIfaceType(typ.raw.Type) { diff --git a/ssa/package.go b/ssa/package.go index 74256eddcc..cf14eecdcf 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -26,6 +26,7 @@ import ( "unsafe" "github.com/goplus/llgo/internal/env" + "github.com/goplus/llgo/internal/meta" "github.com/goplus/llgo/ssa/abi" "github.com/xgo-dev/llvm" "golang.org/x/tools/go/types/typeutil" @@ -744,6 +745,7 @@ type aPackage struct { NeedAbiInit int // bitmask of Reflect* flags indicating which reflect type-construction operations are used MethodByIndex map[int]none MethodByName map[string]none + MetaBuilder *meta.Builder export map[string]string // pkgPath.nameInPkg => exportname preserveSyms map[string]struct{} // set of exported symbol names diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index 74d4242286..317aef651f 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -34,6 +34,7 @@ import ( "unsafe" "github.com/goplus/gogen/packages" + "github.com/goplus/llgo/internal/meta" rtabi "github.com/goplus/llgo/runtime/abi" "github.com/xgo-dev/llvm" ) @@ -1190,6 +1191,7 @@ func TestIfaceMethodClosureCallIR(t *testing.T) { recvMeth := types.NewFunc(0, pkgTypes, "Printf", recvSig) pkg := prog.NewPackage("bar", "foo/bar") + pkg.MetaBuilder = meta.NewBuilder() callerSig := types.NewSignatureType(nil, nil, nil, types.NewTuple(types.NewVar(0, pkgTypes, "i", namedIface)), types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int])), false) @@ -1199,6 +1201,22 @@ func TestIfaceMethodClosureCallIR(t *testing.T) { ret := b.Call(closure, prog.Val(100), prog.Val(200)) b.Return(ret) + pm, err := pkg.MetaBuilder.Build() + if err != nil { + t.Fatal(err) + } + defer pm.Close() + gotMeta := pm.String() + if strings.Contains(gotMeta, "_llgo_foo/bar.IFmt Printf ") { + t.Fatalf("UseIfaceMethod should not target named interface symbol, got:\n%s", gotMeta) + } + if !strings.Contains(gotMeta, "iface$") || !strings.Contains(gotMeta, " Printf ") { + t.Fatalf("UseIfaceMethod should target structural interface symbol and resolve its signature, got:\n%s", gotMeta) + } + if !strings.Contains(gotMeta, "iface$") || !strings.Contains(gotMeta, ":\n Printf ") { + t.Fatalf("UseIfaceMethod callsite should record InterfaceInfo for the same structural symbol, got:\n%s", gotMeta) + } + assertPkg(t, pkg, `; ModuleID = 'foo/bar' source_filename = "foo/bar"