Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
10 changes: 6 additions & 4 deletions internal/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -466,17 +466,15 @@ func Do(args []string, conf *Config) ([]Package, error) {

buildMode := ssaBuildMode
cabiOptimize := true
passOpt := true
if emitDebugInfo || mode == ModeGen {
passOpt = false
}
passOpt := shouldRunLLVMPasses(mode)
if emitDebugInfo {
buildMode |= ssa.GlobalDebug
cabiOptimize = false
}
if !IsOptimizeEnabled() {
buildMode |= ssa.NaiveForm
}
prog.SetDebugInfoOptimized(passOpt && conf.OptLevel != optlevel.O0)
progSSA := ssa.NewProgram(initial[0].Fset, buildMode)
patches := make(cl.Patches, len(altPkgPaths))
altSSAPkgs(progSSA, patches, altPkgs[1:], conf, verbose)
Expand Down Expand Up @@ -2240,6 +2238,10 @@ func llvmPassPipeline(level optlevel.Level, ltoMode lto.Mode) string {
}
}

func shouldRunLLVMPasses(mode Mode) bool {
return mode != ModeGen
}

func IsWasiThreadsEnabled() bool {
return isEnvOn(llgoWasiThreads, true)
}
Expand Down
Loading
Loading