diff --git a/cl/compile_test.go b/cl/compile_test.go index f2a7212921..bcaf3bfa63 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -31,6 +31,7 @@ import ( "github.com/goplus/llgo/internal/build" "github.com/goplus/llgo/internal/buildenv" "github.com/goplus/llgo/internal/cabi" + "github.com/goplus/llgo/internal/filecheck" "github.com/goplus/llgo/internal/llgen" "github.com/goplus/llgo/internal/lto" llvmenv "github.com/goplus/llgo/xtool/env/llvm" @@ -390,6 +391,21 @@ func TestBuildAndCheckSymbolsFromTestltoLTOPluginAggregateABI(t *testing.T) { } } +func TestBuildAndCheckSymbolsFromTestltoLTOPluginSharedSRet(t *testing.T) { + conf := testltoLTOPluginConf(t, build.ModeGen) + checkFile := filepath.Join("..", "ltoplugin", "testdata", "shared_sret.ll") + opt := filepath.Join(llvmenv.New("").BinDir(), "opt") + cmd := exec.Command(opt, "-load-pass-plugin="+conf.LTOPlugin.Path, + "-passes=llgo-lto-pre-globaldce", "-S", "-o", "-", checkFile) + out, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("run LTO plugin for shared sret: %v\n%s", err, out) + } + if err := filecheck.Match(checkFile, string(out)); err != nil { + t.Fatal(err) + } +} + func TestFilterEmulatorOutput(t *testing.T) { tests := []struct { name string diff --git a/ltoplugin/LLGOReflectMethodByNamePass.cpp b/ltoplugin/LLGOReflectMethodByNamePass.cpp index 57440ba617..4120522ef9 100644 --- a/ltoplugin/LLGOReflectMethodByNamePass.cpp +++ b/ltoplugin/LLGOReflectMethodByNamePass.cpp @@ -1,5 +1,6 @@ #include "LLGOLTOPasses.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/SmallVector.h" #include "llvm/Analysis/ConstantFolding.h" @@ -1219,6 +1220,11 @@ Value *getSRetArg(CallBase *CB) { return nullptr; } +Value *getSRetStorage(CallBase *CB) { + Value *SRet = getSRetArg(CB); + return SRet ? SRet->stripPointerCasts() : nullptr; +} + void addReflectMethodCheckedLoad(CallBase *CheckedLoad, StringRef GenericTypeID, SmallPtrSetImpl &SeenLoads, SmallVectorImpl &Loads) { @@ -1288,7 +1294,20 @@ class LLGOLTOPreGlobalDCEPass : public PassInfoMixin { bool Changed = false; const DataLayout &DL = M.getDataLayout(); + DenseMap SRetStorageByCall; + DenseMap> CallsBySRetStorage; for (CallBase *ReflectCall : Calls) { + Value *SRetStorage = getSRetStorage(ReflectCall); + SRetStorageByCall.insert({ReflectCall, SRetStorage}); + if (SRetStorage) + CallsBySRetStorage[SRetStorage].push_back(ReflectCall); + } + + SmallPtrSet ProcessedCalls; + for (CallBase *ReflectCall : Calls) { + if (!ProcessedCalls.insert(ReflectCall).second) + continue; + Attribute KindAttr = ReflectCall->getFnAttr(ReflectMethodByNameCallAttr); if (!KindAttr.isStringAttribute() || ReflectCall->arg_empty()) continue; @@ -1306,17 +1325,47 @@ class LLGOLTOPreGlobalDCEPass : public PassInfoMixin { continue; } + // Optimizations can leave several MethodByName calls sharing one sret + // slot. The checked-load walk starts from the slot and sees the loads for + // all of those calls. Refine them with the union of every provable name + // instead of letting the first call claim all of them. If any name is + // unknown, leave the generic markers intact so GlobalDCE remains + // conservative. + Value *SRetStorage = SRetStorageByCall.lookup(ReflectCall); + SmallVector SharedCalls{ReflectCall}; + if (SRetStorage) { + for (CallBase *Candidate : + CallsBySRetStorage.find(SRetStorage)->second) { + if (Candidate == ReflectCall) + continue; + Attribute CandidateKind = + Candidate->getFnAttr(ReflectMethodByNameCallAttr); + if (!CandidateKind.isStringAttribute() || + CandidateKind.getValueAsString() != Kind) + continue; + SharedCalls.push_back(Candidate); + ProcessedCalls.insert(Candidate); + } + } + SmallVector Names; - bool KnownNames = collectStringSetFromCallArgs(ReflectCall, DL, Names); + bool KnownNames = true; + // The bounded name budget applies to the combined group. Exceeding it + // leaves every generic marker intact. + for (CallBase *SharedCall : SharedCalls) { + if (!collectStringSetFromCallArgs(SharedCall, DL, Names)) { + KnownNames = false; + break; + } + } if (!KnownNames || Names.empty()) continue; SmallVector GenericLoads; SmallPtrSet SeenLoads; SmallPtrSet SeenValues; - collectSRetReflectMethodCheckedLoads(getSRetArg(ReflectCall), - GenericTypeID, SeenValues, SeenLoads, - GenericLoads); + collectSRetReflectMethodCheckedLoads(SRetStorage, GenericTypeID, + SeenValues, SeenLoads, GenericLoads); for (CallBase *GenericLoad : GenericLoads) { insertMethodNameChecks(GenericLoad, Names, Prefix); if (!eraseGenericCheckedLoad(GenericLoad)) diff --git a/ltoplugin/testdata/shared_sret.ll b/ltoplugin/testdata/shared_sret.ll new file mode 100644 index 0000000000..b1e545493d --- /dev/null +++ b/ltoplugin/testdata/shared_sret.ll @@ -0,0 +1,104 @@ +; RUN: opt -load-pass-plugin=%plugin -passes=llgo-lto-pre-globaldce -S %s | FileCheck %s + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + +%reflect.Value = type { ptr } + +@query = private unnamed_addr constant [5 x i8] c"Query" +@mutation = private unnamed_addr constant [8 x i8] c"Mutation" +@subscription = private unnamed_addr constant [12 x i8] c"Subscription" + +declare void @reflect.Value.MethodByName(ptr sret(%reflect.Value), ptr, i64) +declare { ptr, i1 } @llvm.type.checked.load(ptr, i32, metadata) +declare void @llvm.assume(i1) + +define void @shared_sret(ptr %receiver) { +entry: + %ret = alloca %reflect.Value + call void @reflect.Value.MethodByName(ptr sret(%reflect.Value) %ret, ptr "llgo.reflect.methodbyname.name"="1" @query, i64 5) #0 + %query.fn = load ptr, ptr %ret + %query.checked = call { ptr, i1 } @llvm.type.checked.load(ptr %query.fn, i32 0, metadata !"go.method.value.reflect") + %query.ok = extractvalue { ptr, i1 } %query.checked, 1 + call void @llvm.assume(i1 %query.ok) + + call void @reflect.Value.MethodByName(ptr sret(%reflect.Value) %ret, ptr "llgo.reflect.methodbyname.name"="1" @mutation, i64 8) #0 + %mutation.fn = load ptr, ptr %ret + %mutation.checked = call { ptr, i1 } @llvm.type.checked.load(ptr %mutation.fn, i32 0, metadata !"go.method.value.reflect") + %mutation.ok = extractvalue { ptr, i1 } %mutation.checked, 1 + call void @llvm.assume(i1 %mutation.ok) + + call void @reflect.Value.MethodByName(ptr sret(%reflect.Value) %ret, ptr "llgo.reflect.methodbyname.name"="1" @subscription, i64 12) #0 + %subscription.fn = load ptr, ptr %ret + %subscription.checked = call { ptr, i1 } @llvm.type.checked.load(ptr %subscription.fn, i32 0, metadata !"go.method.value.reflect") + %subscription.ok = extractvalue { ptr, i1 } %subscription.checked, 1 + call void @llvm.assume(i1 %subscription.ok) + ret void +} + +define void @shared_sret_unknown(ptr %receiver, ptr %dynamic.name, i64 %dynamic.len) { +entry: + %ret = alloca %reflect.Value + call void @reflect.Value.MethodByName(ptr sret(%reflect.Value) %ret, ptr "llgo.reflect.methodbyname.name"="1" @query, i64 5) #0 + %query.fn = load ptr, ptr %ret + %query.checked = call { ptr, i1 } @llvm.type.checked.load(ptr %query.fn, i32 0, metadata !"go.method.value.reflect") + %query.ok = extractvalue { ptr, i1 } %query.checked, 1 + call void @llvm.assume(i1 %query.ok) + + call void @reflect.Value.MethodByName(ptr sret(%reflect.Value) %ret, ptr "llgo.reflect.methodbyname.name"="1" %dynamic.name, i64 %dynamic.len) #0 + %dynamic.fn = load ptr, ptr %ret + %dynamic.checked = call { ptr, i1 } @llvm.type.checked.load(ptr %dynamic.fn, i32 0, metadata !"go.method.value.reflect") + %dynamic.ok = extractvalue { ptr, i1 } %dynamic.checked, 1 + call void @llvm.assume(i1 %dynamic.ok) + ret void +} + +define void @shared_sret_duplicate(ptr %receiver) { +entry: + %ret = alloca %reflect.Value + call void @reflect.Value.MethodByName(ptr sret(%reflect.Value) %ret, ptr "llgo.reflect.methodbyname.name"="1" @query, i64 5) #0 + %first.fn = load ptr, ptr %ret + %first.checked = call { ptr, i1 } @llvm.type.checked.load(ptr %first.fn, i32 0, metadata !"go.method.value.reflect") + %first.ok = extractvalue { ptr, i1 } %first.checked, 1 + call void @llvm.assume(i1 %first.ok) + + call void @reflect.Value.MethodByName(ptr sret(%reflect.Value) %ret, ptr "llgo.reflect.methodbyname.name"="1" @query, i64 5) #0 + %second.fn = load ptr, ptr %ret + %second.checked = call { ptr, i1 } @llvm.type.checked.load(ptr %second.fn, i32 0, metadata !"go.method.value.reflect") + %second.ok = extractvalue { ptr, i1 } %second.checked, 1 + call void @llvm.assume(i1 %second.ok) + ret void +} + +; CHECK-LABEL: define void @shared_sret( +; CHECK: call void @reflect.Value.MethodByName({{.*}}@query +; CHECK: @llvm.type.checked.load({{.*}}!"go.method.value.reflect.Query") +; CHECK: @llvm.type.checked.load({{.*}}!"go.method.value.reflect.Mutation") +; CHECK: @llvm.type.checked.load({{.*}}!"go.method.value.reflect.Subscription") +; CHECK: call void @reflect.Value.MethodByName({{.*}}@mutation +; CHECK: @llvm.type.checked.load({{.*}}!"go.method.value.reflect.Query") +; CHECK: @llvm.type.checked.load({{.*}}!"go.method.value.reflect.Mutation") +; CHECK: @llvm.type.checked.load({{.*}}!"go.method.value.reflect.Subscription") +; CHECK: call void @reflect.Value.MethodByName({{.*}}@subscription +; CHECK: @llvm.type.checked.load({{.*}}!"go.method.value.reflect.Query") +; CHECK: @llvm.type.checked.load({{.*}}!"go.method.value.reflect.Mutation") +; CHECK: @llvm.type.checked.load({{.*}}!"go.method.value.reflect.Subscription") +; CHECK-NOT: !"go.method.value.reflect" + +; CHECK-LABEL: define void @shared_sret_unknown( +; CHECK-NOT: !"go.method.value.reflect.Query" +; CHECK: @llvm.type.checked.load({{.*}}!"go.method.value.reflect") +; CHECK-NOT: !"go.method.value.reflect.Query" +; CHECK: @llvm.type.checked.load({{.*}}!"go.method.value.reflect") +; CHECK-NOT: !"go.method.value.reflect.Query" +; CHECK: ret void + +; CHECK-LABEL: define void @shared_sret_duplicate( +; CHECK: call void @reflect.Value.MethodByName({{.*}}@query +; CHECK: @llvm.type.checked.load({{.*}}!"go.method.value.reflect.Query") +; CHECK-NOT: !"go.method.value.reflect.Query" +; CHECK: call void @reflect.Value.MethodByName({{.*}}@query +; CHECK: @llvm.type.checked.load({{.*}}!"go.method.value.reflect.Query") +; CHECK-NOT: !"go.method.value.reflect.Query" +; CHECK: ret void + +attributes #0 = { "llgo.reflect.methodbyname"="value" }