Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/llgo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ jobs:
test:
name: test (${{ matrix.lane }}, ${{ matrix.os }}, LLVM ${{ matrix.llvm }}, Go ${{ matrix.go }}, shard ${{ matrix.shard }})
continue-on-error: ${{ matrix.lane == 'compatibility' }}
timeout-minutes: 30
timeout-minutes: ${{ startsWith(matrix.os, 'macos') && 45 || 30 }}
strategy:
matrix:
# Keep compatibility and primary toolchains pinned to exact patches.
Expand Down
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())
}
1 change: 1 addition & 0 deletions cl/_testlto/defer_dwarf/expect.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[2 1]
19 changes: 19 additions & 0 deletions cl/_testlto/defer_dwarf/in.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package main

import "fmt"

func record(values *[]int, value int) {
*values = append(*values, value)
}

func deferredValues(addSecond bool) (values []int) {
defer record(&values, 1)
if addSecond {
defer record(&values, 2)
}
return
}

func main() {
fmt.Println(deferredValues(true))
}
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"))
}
5 changes: 5 additions & 0 deletions cl/cltest/cltest.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,11 @@ func withModuleCapture(conf *build.Config, pkgDir string) (*build.Config, *strin
conf = build.NewDefaultConf(build.ModeRun)
}
localConf := *conf
// Existing IR fixtures describe executable instructions, not debug records.
// Keep their snapshots stable unless a test explicitly requests DWARF.
if localConf.LinkOptions.DWARF == build.DWARFDefault {
localConf.LinkOptions.DWARF = build.DWARFOmit
}
var module string
prevHook := localConf.ModuleHook
localConf.ModuleHook = func(pkg build.Package) {
Expand Down
26 changes: 26 additions & 0 deletions cl/cltest/cltest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,34 @@ import (
"path/filepath"
"runtime"
"testing"

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

func TestWithModuleCaptureDWARFMode(t *testing.T) {
for _, test := range []struct {
name string
mode build.DWARFMode
want build.DWARFMode
}{
{name: "default", mode: build.DWARFDefault, want: build.DWARFOmit},
{name: "preserve", mode: build.DWARFPreserve, want: build.DWARFPreserve},
{name: "omit", mode: build.DWARFOmit, want: build.DWARFOmit},
} {
t.Run(test.name, func(t *testing.T) {
conf := build.NewDefaultConf(build.ModeRun)
conf.LinkOptions.DWARF = test.mode
got, _ := withModuleCapture(conf, t.TempDir())
if got.LinkOptions.DWARF != test.want {
t.Fatalf("DWARF mode = %v, want %v", got.LinkOptions.DWARF, test.want)
}
if conf.LinkOptions.DWARF != test.mode {
t.Fatalf("input DWARF mode = %v, want %v", conf.LinkOptions.DWARF, test.mode)
}
})
}
}

func TestReadGoldenUsesToolchainVersion(t *testing.T) {
dir := t.TempDir()
file := filepath.Join(dir, "expect.txt")
Expand Down
5 changes: 1 addition & 4 deletions cl/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -1618,10 +1618,7 @@ func (p *context) jumpTo(v *ssa.Jump) llssa.BasicBlock {
}

func (p *context) getDebugLocScope(v *ssa.Function, pos token.Pos) *types.Scope {
if v.Object() == nil {
return nil
}
funcScope := v.Object().(*types.Func).Scope()
funcScope := debugFunctionScope(v)
if funcScope == nil {
return nil
}
Expand Down
109 changes: 109 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,24 @@ func TestRunAndTestFromTestlto(t *testing.T) {
cltest.RunAndTestFromDir(t, "", "./_testlto", ignore, cltest.WithRunConfig(conf))
}

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

func TestRunAndTestFromTestltoDWARF(t *testing.T) {
runTestltoDWARF(t, "reflectmk_runtime")
}

func TestRunAndTestFromTestltoDeferDWARF(t *testing.T) {
runTestltoDWARF(t, "defer_dwarf")
}

var testltoSymbolChecks = []string{
"globaldce_interface_matrix",
"globaldce_interface_slots",
Expand All @@ -225,9 +248,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 +291,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 Expand Up @@ -412,6 +514,13 @@ func TestRunAndTestFromTestpy(t *testing.T) {
cltest.RunAndTestFromDir(t, "", "./_testpy", nil)
}

func TestRunAndTestFromTestpyDWARF(t *testing.T) {
conf := build.NewDefaultConf(build.ModeRun)
conf.LinkOptions.DWARF = build.DWARFPreserve
cltest.RunAndTestFromDir(t, "", "./_testpy", nil,
cltest.WithRunConfig(conf), cltest.WithIRCheck(false))
}

func TestRunAndTestFromTestlibgo(t *testing.T) {
cltest.RunAndTestFromDir(t, "", "./_testlibgo", nil)
}
Expand Down
Loading
Loading