Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package main

import (
"os"
"reflect"
)

type Unknown struct{}

//go:noinline
func (Unknown) UnknownA() {}

//go:noinline
func (Unknown) UnknownB() {}

func unknownName() string {
if len(os.Args) == 0 {
return "UnknownA"
}
return os.Args[0]
}

func main() {
_, _ = reflect.TypeOf(Unknown{}).MethodByName(unknownName())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
direct
concat
slice
forward
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// LITTEST
package main

import (
"reflect"
)

// SYMBOL-NOT: globaldce_reflect_method_by_name_ltoplugin_string_abi{{.*}}Known{{.*}}Drop
// SYMBOL-DAG: globaldce_reflect_method_by_name_ltoplugin_string_abi{{.*}}Known{{.*}}Direct
// SYMBOL-DAG: globaldce_reflect_method_by_name_ltoplugin_string_abi{{.*}}Known{{.*}}Concat
// SYMBOL-DAG: globaldce_reflect_method_by_name_ltoplugin_string_abi{{.*}}Known{{.*}}Slice
// SYMBOL-DAG: globaldce_reflect_method_by_name_ltoplugin_string_abi{{.*}}Known{{.*}}Forward
// SYMBOL-NOT: globaldce_reflect_method_by_name_ltoplugin_string_abi{{.*}}Known{{.*}}Drop

type Known struct{}

//go:noinline
func (Known) Direct() string { return "direct" }

//go:noinline
func (Known) Concat() string { return "concat" }

//go:noinline
func (Known) Slice() string { return "slice" }

//go:noinline
func (Known) Forward() string { return "forward" }

//go:noinline
func (Known) Drop() string { panic("unreachable") }

func callForward(name string) string {
out := reflect.ValueOf(Known{}).MethodByName(name).Call(nil)
return out[0].String()
}

func callConcat(prefix, suffix string) string {
out := reflect.ValueOf(Known{}).MethodByName(prefix + suffix).Call(nil)
return out[0].String()
}

func callSlice(source string) string {
out := reflect.ValueOf(Known{}).MethodByName(source[2:7]).Call(nil)
return out[0].String()
}

func main() {
v := reflect.ValueOf(Known{})
println(v.MethodByName("Direct").Call(nil)[0].String())
println(callConcat("Con", "cat"))
println(callSlice("__Slice__"))
println(callForward("Forward"))
}
93 changes: 93 additions & 0 deletions cl/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,19 @@ package cl_test

import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"

"github.com/goplus/llgo/cl/cltest"
"github.com/goplus/llgo/internal/build"
"github.com/goplus/llgo/internal/buildenv"
"github.com/goplus/llgo/internal/cabi"
"github.com/goplus/llgo/internal/llgen"
"github.com/goplus/llgo/internal/lto"
llvmenv "github.com/goplus/llgo/xtool/env/llvm"
)

func testCompile(t *testing.T, src, expected string) {
Expand Down Expand Up @@ -187,6 +191,7 @@ func TestRunAndTestFromTestlto(t *testing.T) {
"./_testlto/globaldce_reflect_method_by_name_ltoplugin_param",
"./_testlto/globaldce_reflect_method_by_name_ltoplugin_range_literal",
"./_testlto/globaldce_reflect_method_by_name_ltoplugin_slice",
"./_testlto/globaldce_reflect_method_by_name_ltoplugin_string_abi",
"./_testlto/globaldce_reflect_method_by_name_ltoplugin_switch",
}
if !buildenv.Dev {
Expand All @@ -206,6 +211,15 @@ func TestRunAndTestFromTestlto(t *testing.T) {
cltest.RunAndTestFromDir(t, "", "./_testlto", ignore, cltest.WithRunConfig(conf))
}

func TestRunAndTestFromTestltoDWARF(t *testing.T) {
t.Setenv("LLGO_BUILD_CACHE", "off")
conf := build.NewDefaultConf(build.ModeRun)
conf.LTO = lto.Full
conf.LinkOptions.DWARF = build.DWARFPreserve
cltest.RunAndTestFromDir(t, "reflectmk_runtime", "./_testlto", nil,
cltest.WithRunConfig(conf), cltest.WithIRCheck(false))
}

var testltoSymbolChecks = []string{
"globaldce_interface_matrix",
"globaldce_interface_slots",
Expand All @@ -225,9 +239,17 @@ var testltoLTOPluginTests = []string{
"globaldce_reflect_method_by_name_ltoplugin_param",
"globaldce_reflect_method_by_name_ltoplugin_range_literal",
"globaldce_reflect_method_by_name_ltoplugin_slice",
"globaldce_reflect_method_by_name_ltoplugin_string_abi",
"globaldce_reflect_method_by_name_ltoplugin_switch",
}

var testltoLTOPluginDWARFTests = []string{
"globaldce_reflect_method_by_name_ltoplugin_concat",
"globaldce_reflect_method_by_name_ltoplugin_global_slice",
"globaldce_reflect_method_by_name_ltoplugin_param",
"globaldce_reflect_method_by_name_ltoplugin_slice",
}

func TestBuildAndCheckSymbolsFromTestlto(t *testing.T) {
if !buildenv.Dev {
t.Skip("globaldce symbol checks require dev build")
Expand Down Expand Up @@ -260,13 +282,84 @@ func TestRunAndTestFromTestltoLTOPlugin(t *testing.T) {
)
}

func TestRunAndTestFromTestltoLTOPluginDWARF(t *testing.T) {
t.Setenv("LLGO_BUILD_CACHE", "off")
conf := testltoLTOPluginConf(t, build.ModeRun)
conf.LinkOptions.DWARF = build.DWARFPreserve
cltest.RunAndTestFromDir(t, "ltoplugin_switch", "./_testlto", nil,
cltest.WithRunConfig(conf), cltest.WithIRCheck(false))
}

func TestBuildAndCheckSymbolsFromTestltoLTOPlugin(t *testing.T) {
buildConf := testltoLTOPluginConf(t, build.ModeBuild)
cltest.BuildAndCheckSymbolsFromDir(t, "", "./_testlto", testltoLTOPluginTests,
cltest.WithRunConfig(buildConf),
)
}

func runTestltoLTOPluginAggregateABI(t *testing.T, fixture string) string {
t.Helper()
conf := testltoLTOPluginConf(t, build.ModeGen)
// Apply a fixed LP64 ABI below so the aggregate form is tested on every host.
conf.AbiMode = cabi.ModeNone
plugin := conf.LTOPlugin.Path
pkgs, err := build.Do([]string{fixture}, conf)
if err != nil {
t.Fatalf("generate aggregate string module: %v", err)
}
if len(pkgs) != 1 {
t.Fatalf("generate aggregate string module: got %d packages", len(pkgs))
}
cabi.NewTransformer(pkgs[0].LPkg.Prog, "arm64-unknown-linux", "", cabi.ModeAllFunc, true).
TransformModule(pkgs[0].PkgPath, pkgs[0].LPkg.Module())
aggregateIR := pkgs[0].LPkg.String()
pkgs[0].LPkg.Prog.Dispose()
if !strings.Contains(aggregateIR, `runtime.String" "llgo.reflect.methodbyname.name"`) {
t.Fatalf("MethodByName string argument was not captured in aggregate form:\n%s", aggregateIR)
}

opt := filepath.Join(llvmenv.New("").BinDir(), "opt")
cmd := exec.Command(opt, "-load-pass-plugin="+plugin,
"-passes=llgo-lto-pre-globaldce", "-S", "-o", "-")
cmd.Stdin = strings.NewReader(aggregateIR)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("run LTO plugin for aggregate ABI: %v\n%s", err, out)
}
return string(out)
}

func TestBuildAndCheckSymbolsFromTestltoLTOPluginAggregateABI(t *testing.T) {
result := runTestltoLTOPluginAggregateABI(t,
"./_testlto/globaldce_reflect_method_by_name_ltoplugin_string_abi")
for _, name := range []string{"Direct", "Concat", "Slice", "Forward"} {
marker := `metadata !"go.method.value.reflect.` + name + `"`
if !strings.Contains(result, marker) {
t.Fatalf("aggregate ABI output missing %s\n%s", marker, result)
}
}
if strings.Contains(result, `metadata !"go.method.value.reflect"`) {
t.Fatalf("aggregate ABI output retained the generic value marker\n%s", result)
}

// A generic marker conservatively retains every matching method, so test
// unknown-name fallback in a separate module from the Drop symbol check.
unknownResult := runTestltoLTOPluginAggregateABI(t,
"./_testlto/_globaldce_reflect_method_by_name_ltoplugin_string_abi_unknown")
if !strings.Contains(unknownResult, `metadata !"go.method.type.reflect"`) {
t.Fatalf("aggregate ABI output lost the unknown-name type marker\n%s", unknownResult)
}
}

func TestBuildAndCheckSymbolsFromTestltoLTOPluginDWARF(t *testing.T) {
t.Setenv("LLGO_BUILD_CACHE", "off")
buildConf := testltoLTOPluginConf(t, build.ModeBuild)
buildConf.LinkOptions.DWARF = build.DWARFPreserve
cltest.BuildAndCheckSymbolsFromDir(t, "", "./_testlto", testltoLTOPluginDWARFTests,
cltest.WithRunConfig(buildConf),
)
}

func TestFilterEmulatorOutput(t *testing.T) {
tests := []struct {
name string
Expand Down
2 changes: 1 addition & 1 deletion cl/instr.go
Original file line number Diff line number Diff line change
Expand Up @@ -1590,7 +1590,7 @@ func (p *context) emitPCLineLabel(b llssa.Builder, pos token.Pos) {
pushSection = ".pushsection __DATA,__llgo_pcl,regular,live_support"
recordSymbol = "l_llgo_pcline_rec_${:uid}:\n"
}
b.InlineAsm(
b.InlineAsmNoDebug(
asmLabel + ":\n" +
pushSection + "\n" +
".p2align " + align + "\n" +
Expand Down
74 changes: 73 additions & 1 deletion cl/rewrite_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,17 @@ func init() {
}

func compileWithRewrites(t *testing.T, src string, rewrites map[string]string) string {
return compileWithRewritesMode(t, src, rewrites, ssa.SanityCheckFunctions|ssa.InstantiateGenerics)
}

func compileWithRewritesMode(t *testing.T, src string, rewrites map[string]string, mode ssa.BuilderMode) string {
t.Helper()
fset := token.NewFileSet()
file, err := parser.ParseFile(fset, "rewrite.go", src, parser.ParseComments)
if err != nil {
t.Fatalf("parse failed: %v", err)
}
importer := gpackages.NewImporter(fset)
mode := ssa.SanityCheckFunctions | ssa.InstantiateGenerics
pkg, _, err := ssautil.BuildPackage(&types.Config{Importer: importer}, fset,
types.NewPackage(file.Name.Name, file.Name.Name), []*ast.File{file}, mode)
if err != nil {
Expand Down Expand Up @@ -154,6 +157,75 @@ func Use() callbackType { return CallbackTypes[1] }
}
}

func TestStaticGlobalSliceLiteralInitWithDebugRefs(t *testing.T) {
const src = `package staticinit

var CallbackTypes = []string{"BeforeCreate", "AfterCreate"}

func Use() string { return CallbackTypes[1] }
`
ir := compileWithRewritesMode(t, src, nil,
ssa.SanityCheckFunctions|ssa.InstantiateGenerics|ssa.GlobalDebug)
for _, want := range []string{
`@"staticinit.CallbackTypes$data" = global [2 x %"github.com/goplus/llgo/runtime/internal/runtime.String"]`,
`@staticinit.CallbackTypes = global %"github.com/goplus/llgo/runtime/internal/runtime.Slice" { ptr @"staticinit.CallbackTypes$data", i64 2, i64 2 }`,
`c"BeforeCreate"`,
`c"AfterCreate"`,
} {
if !strings.Contains(ir, want) {
t.Fatalf("missing static slice initializer %q with debug refs:\n%s", want, ir)
}
}
assertNoStoreToGlobal(t, ir, "@staticinit.CallbackTypes")
if strings.Contains(ir, "runtime.AllocZ") {
t.Fatalf("static slice initializer allocates at runtime with debug refs:\n%s", ir)
}
}

func TestStaticSliceInitRejectsExecutableReferrers(t *testing.T) {
const src = `package foo

var Values []int

func useSlice([]int) {}
func usePointer(*int) {}

func sliceUser() {
backing := [2]int{1, 2}
values := backing[:]
Values = values
useSlice(values)
}

func elementUser() {
var backing [2]int
elem := &backing[0]
*elem = 1
usePointer(elem)
Values = backing[:]
}
`
ssapkg := buildSSAPackage(t, src)
global := ssapkg.Members["Values"].(*ssa.Global)
for _, name := range []string{"sliceUser", "elementUser"} {
fn := ssapkg.Func(name)
var globalStore *ssa.Store
for _, block := range fn.Blocks {
for _, instr := range block.Instrs {
if store, ok := instr.(*ssa.Store); ok && store.Addr == global {
globalStore = store
}
}
}
if globalStore == nil {
t.Fatalf("%s: store to Values not found", name)
}
if _, ok := staticSliceInitOf(globalStore); ok {
t.Fatalf("%s: static slice init accepted an executable referrer", name)
}
}
}

func TestStaticGlobalZeroSizedSliceLiteralFallsBack(t *testing.T) {
const src = `package staticinit

Expand Down
16 changes: 8 additions & 8 deletions cl/static_init.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,16 +204,16 @@ func staticSliceInitOf(store *ssa.Store) (*staticSliceInit, bool) {
values: make(map[int]*ssa.Const),
instrs: []ssa.Instruction{alloc, slice, store},
}
sliceRefs := slice.Referrers()
if sliceRefs == nil || len(*sliceRefs) != 1 || (*sliceRefs)[0] != store {
sliceRefs, ok := nonDebugReferrers(slice)
if !ok || len(sliceRefs) != 1 || sliceRefs[0] != store {
return nil, false
}
refs := alloc.Referrers()
if refs == nil {
refs, ok := nonDebugReferrers(alloc)
if !ok {
return nil, false
}
seenSlice := false
for _, ref := range *refs {
for _, ref := range refs {
switch ref := ref.(type) {
case *ssa.Slice:
if ref != slice || seenSlice {
Expand All @@ -228,11 +228,11 @@ func staticSliceInitOf(store *ssa.Store) (*staticSliceInit, bool) {
if !ok || index >= int(array.Len()) {
return nil, false
}
indexRefs := ref.Referrers()
if indexRefs == nil || len(*indexRefs) != 1 {
indexRefs, ok := nonDebugReferrers(ref)
if !ok || len(indexRefs) != 1 {
return nil, false
}
elemStore, ok := (*indexRefs)[0].(*ssa.Store)
elemStore, ok := indexRefs[0].(*ssa.Store)
if !ok || elemStore.Addr != ref {
return nil, false
}
Expand Down
Loading
Loading