go/ssa: lift structs, arrays and typeparams Lift locals with underlying types of struct, array and typeparam. Changes to vta tests are due to the tests being sensitive to register names. Change-Id: I558c0e6897b62f93ae1e700307e6e1e4dea008d0 Reviewed-on: https://go-review.googlesource.com/c/tools/+/451996 Run-TryBot: Tim King <taking@google.com> Commit-Queue: Tim King <taking@google.com> gopls-CI: kokoro <noreply+kokoro@google.com> Reviewed-by: Alan Donovan <adonovan@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com>
diff --git a/go/callgraph/vta/graph_test.go b/go/callgraph/vta/graph_test.go index 8b8c697..da574d7 100644 --- a/go/callgraph/vta/graph_test.go +++ b/go/callgraph/vta/graph_test.go
@@ -152,21 +152,21 @@ return vgs } -// subGraph checks if a graph `g1` is a subgraph of graph `g2`. -// Assumes that each element in `g1` and `g2` is an edge set -// for a particular node in a fixed yet arbitrary format. -func subGraph(g1, g2 []string) bool { - m := make(map[string]bool) - for _, s := range g2 { - m[s] = true +// setdiff returns the set difference of `X-Y` or {s | s ∈ X, s ∉ Y }. +func setdiff(X, Y []string) []string { + y := make(map[string]bool) + var delta []string + for _, s := range Y { + y[s] = true } - for _, s := range g1 { - if _, ok := m[s]; !ok { - return false + for _, s := range X { + if _, ok := y[s]; !ok { + delta = append(delta, s) } } - return true + sort.Strings(delta) + return delta } func TestVTAGraphConstruction(t *testing.T) { @@ -201,8 +201,9 @@ } g, _ := typePropGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog)) - if gs := vtaGraphStr(g); !subGraph(want, gs) { - t.Errorf("`%s`: want superset of %v;\n got %v", file, want, gs) + got := vtaGraphStr(g) + if diff := setdiff(want, got); len(diff) > 0 { + t.Errorf("`%s`: want superset of %v;\n got %v\ndiff: %v", file, want, got, diff) } }) }
diff --git a/go/callgraph/vta/testdata/src/callgraph_collections.go b/go/callgraph/vta/testdata/src/callgraph_collections.go index bc418e3..6177a32 100644 --- a/go/callgraph/vta/testdata/src/callgraph_collections.go +++ b/go/callgraph/vta/testdata/src/callgraph_collections.go
@@ -37,31 +37,5 @@ x[len(x)-1].Foo() } -// Relevant SSA: -// func Baz(a A, b B): -// ... -// t4 = Do(t2, t3) -// t5 = range t4 -// jump 1 -// 1: -// t6 = phi [0: nil:[]I, 2: t16] #x -// t7 = next t5 -// t8 = extract t7 #0 -// if t8 goto 2 else 3 -// 2: -// t9 = extract t7 #1 -// t10 = extract t7 #2 -// t11 = invoke t9.Foo() -// t12 = invoke t10.Foo() -// ... -// jump 1 -// 3: -// t17 = len(t6) -// t18 = t17 - 1:int -// t19 = &t6[t18] -// t20 = *t19 -// t21 = invoke t20.Foo() -// return - // WANT: -// Baz: Do(t2, t3) -> Do; invoke t10.Foo() -> B.Foo; invoke t20.Foo() -> A.Foo, B.Foo; invoke t9.Foo() -> A.Foo, B.Foo +// Baz: Do(a, b) -> Do; invoke t16.Foo() -> A.Foo, B.Foo; invoke t5.Foo() -> A.Foo, B.Foo; invoke t6.Foo() -> B.Foo
diff --git a/go/callgraph/vta/testdata/src/callgraph_fields.go b/go/callgraph/vta/testdata/src/callgraph_fields.go index 00aa649..9149bdd 100644 --- a/go/callgraph/vta/testdata/src/callgraph_fields.go +++ b/go/callgraph/vta/testdata/src/callgraph_fields.go
@@ -31,61 +31,6 @@ a.Do() } -// Relevant SSA: -// func Baz(b B): -// t0 = local B (b) -// *t0 = b -// t1 = *t0 -// t2 = NewA(t1) -// t3 = (*A).Do(t2) -// return -// -// func (a *A) Do(): -// t0 = &a.I [#0] -// t1 = *t0 -// t2 = invoke t1.Foo() -// return -// -// Name: (testdata.A).Foo -// Synthetic: wrapper for func (testdata.I).Foo() -// Location: testdata/callgraph_fields.go:10:2 -// func (arg0 testdata.A) Foo(): -// t0 = local testdata.A () -// *t0 = arg0 -// t1 = &t0.I [#0] -// t2 = *t1 -// t3 = invoke t2.Foo() -// return -// -// Name: (*testdata.A).Foo -// Synthetic: wrapper for func (testdata.I).Foo() -// Location: testdata/callgraph_fields.go:10:2 -// func (arg0 *testdata.A) Foo(): -// t0 = &arg0.I [#0] -// t1 = *t0 -// t2 = invoke t1.Foo() -// return -// -// func (b B) Foo(): -// t0 = local B (b) -// *t0 = b -// return -// -// func (b *testdata.B) Foo(): -// t0 = ssa:wrapnilchk(b, "testdata.B":string, "Foo":string) -// t1 = *t0 -// t2 = (testdata.B).Foo(t1) -// return -// -// func NewA(b B) *A: -// t0 = new B (b) -// *t0 = b -// t1 = new A (complit) -// t2 = &t1.I [#0] -// t3 = make I <- *B (t0) -// *t2 = t3 -// return t1 - // WANT: -// Baz: (*A).Do(t2) -> A.Do; NewA(t1) -> NewA +// Baz: (*A).Do(t0) -> A.Do; NewA(b) -> NewA // A.Do: invoke t1.Foo() -> B.Foo
diff --git a/go/callgraph/vta/testdata/src/callgraph_generics.go b/go/callgraph/vta/testdata/src/callgraph_generics.go index da3dca5..a0ac8fd 100644 --- a/go/callgraph/vta/testdata/src/callgraph_generics.go +++ b/go/callgraph/vta/testdata/src/callgraph_generics.go
@@ -39,33 +39,24 @@ // Relevant SSA: //func Foo(a A, b B): -// t0 = local A (a) -// *t0 = a -// t1 = local B (b) -// *t1 = b -// t2 = new bool (x) -// *t2 = true:bool -// t3 = instantiated[bool](t2) -// t4 = new int (y) -// *t4 = 1:int -// t5 = instantiated[int](t4) -// t6 = *t0 -// t7 = interfaceInstantiated[testdata.A](t6) -// t8 = *t1 -// t9 = interfaceInstantiated[testdata.B](t8) +// t0 = new bool (x) +// *t0 = true:bool +// t1 = instantiated[bool](t2) +// t1 = new int (y) +// *t2 = 1:int +// t3 = instantiated[[int]](t4) +// t4 = interfaceInstantiated[testdata.A](a) +// t5 = interfaceInstantiated[testdata.B](b) // return // -//func interfaceInstantiated[testdata.B](x B): -// t0 = local B (x) -// *t0 = x -// t1 = *t0 -// t2 = (B).Bar(t1) +//func interfaceInstantiated[[testdata.B]](x B): +// t0 = (B).Bar(b) // return // //func interfaceInstantiated[X I](x X): // (external) // WANT: -// Foo: instantiated[bool](t2) -> instantiated[bool]; instantiated[int](t4) -> instantiated[int]; interfaceInstantiated[testdata.A](t6) -> interfaceInstantiated[testdata.A]; interfaceInstantiated[testdata.B](t8) -> interfaceInstantiated[testdata.B] -// interfaceInstantiated[testdata.B]: (B).Bar(t1) -> B.Bar -// interfaceInstantiated[testdata.A]: (A).Bar(t1) -> A.Bar +// Foo: instantiated[bool](t0) -> instantiated[bool]; instantiated[int](t2) -> instantiated[int]; interfaceInstantiated[testdata.A](a) -> interfaceInstantiated[testdata.A]; interfaceInstantiated[testdata.B](b) -> interfaceInstantiated[testdata.B] +// interfaceInstantiated[testdata.B]: (B).Bar(x) -> B.Bar +// interfaceInstantiated[testdata.A]: (A).Bar(x) -> A.Bar
diff --git a/go/callgraph/vta/testdata/src/callgraph_interfaces.go b/go/callgraph/vta/testdata/src/callgraph_interfaces.go index 123468c..c272ff0 100644 --- a/go/callgraph/vta/testdata/src/callgraph_interfaces.go +++ b/go/callgraph/vta/testdata/src/callgraph_interfaces.go
@@ -49,13 +49,11 @@ // func Do(b bool) I: // ... -// t3 = local C (c) -// t4 = *t3 -// t5 = (C).Foo(t4) -// t6 = NewB() -// t7 = make I <- B (t6) -// return t7 +// t1 = (C).Foo(struct{}{}:C) +// t2 = NewB() +// t3 = make I <- B (t2) +// return t3 // WANT: // Baz: Do(b) -> Do; invoke t0.Foo() -> A.Foo, B.Foo -// Do: (C).Foo(t4) -> C.Foo; NewB() -> NewB +// Do: (C).Foo(struct{}{}:C) -> C.Foo; NewB() -> NewB
diff --git a/go/callgraph/vta/testdata/src/callgraph_pointers.go b/go/callgraph/vta/testdata/src/callgraph_pointers.go index e07f969..ae8fe43 100644 --- a/go/callgraph/vta/testdata/src/callgraph_pointers.go +++ b/go/callgraph/vta/testdata/src/callgraph_pointers.go
@@ -35,37 +35,9 @@ (*x).Foo() } -// Relevant SSA: -// func Baz(a A, b B, c bool): -// t0 = local A (a) -// ... -// t5 = Do(t2, t4, c) -// t6 = *t5 -// t7 = invoke t6.Foo() -// return - -// func Do(a A, i I, c bool) *I: -// t0 = local A (a) -// *t0 = a -// ... -// if c goto 1 else 3 -// 1: -// t2 = &t0.f [#0] -// ... -// jump 2 -// 2: -// t6 = &t0.f [#0] -// ... -// t9 = invoke t8.Foo() -// return t1 -// 3: -// t10 = &t0.f [#0] alias between A.f and t10 -// *t10 = t1 alias between t10 and t1 -// jump 2 - // The command a.f = &i introduces aliasing that results in // A and B reaching both *A.f and return value of Do(a, b, c). // WANT: -// Baz: Do(t2, t4, c) -> Do; invoke t6.Foo() -> A.Foo, B.Foo +// Baz: Do(a, t0, c) -> Do; invoke t2.Foo() -> A.Foo, B.Foo // Do: invoke t8.Foo() -> A.Foo, B.Foo
diff --git a/go/callgraph/vta/testdata/src/callgraph_static.go b/go/callgraph/vta/testdata/src/callgraph_static.go index 1ed904f..147ed46 100644 --- a/go/callgraph/vta/testdata/src/callgraph_static.go +++ b/go/callgraph/vta/testdata/src/callgraph_static.go
@@ -20,11 +20,9 @@ // Relevant SSA: // func Baz(a A): -// ... -// t2 = (A).foo(t1) -// t3 = Bar() -// ... -// t6 = Baz(t5) +// t0 = (A).foo(a) +// t1 = Bar() +// t2 = Baz(struct{}{}:A) // WANT: -// Baz: (A).foo(t1) -> A.foo; Bar() -> Bar; Baz(t5) -> Baz +// Baz: (A).foo(a) -> A.foo; Bar() -> Bar; Baz(struct{}{}:A) -> Baz
diff --git a/go/callgraph/vta/testdata/src/dynamic_calls.go b/go/callgraph/vta/testdata/src/dynamic_calls.go index b8c14b2..f8f8898 100644 --- a/go/callgraph/vta/testdata/src/dynamic_calls.go +++ b/go/callgraph/vta/testdata/src/dynamic_calls.go
@@ -27,23 +27,22 @@ return h() } +var g *B = &B{} // ensure *B.foo is created. + // Relevant SSA: // func Baz(x B, h func() I, i I) I: -// t0 = local B (x) -// *t0 = x -// t1 = *t0 -// t2 = make I <- B (t1) -// t3 = invoke i.foo(t2) -// t4 = h() -// return t4 +// t0 = make I <- B (x) +// t1 = invoke i.foo(t0) +// t2 = h() +// return t2 -// Local(t2) has seemingly duplicates of successors. This +// Local(t0) has seemingly duplicates of successors. This // happens in stringification of type propagation graph. // Due to CHA, we analyze A.foo and *A.foo as well as B.foo // and *B.foo, which have similar bodies and hence similar // type flow that gets merged together during stringification. // WANT: -// Local(t2) -> Local(ai), Local(ai), Local(bi), Local(bi) -// Constant(testdata.I) -> Local(t4) -// Local(t1) -> Local(t2) +// Local(t0) -> Local(ai), Local(ai), Local(bi), Local(bi) +// Constant(testdata.I) -> Local(t2) +// Local(x) -> Local(t0)
diff --git a/go/callgraph/vta/testdata/src/maps.go b/go/callgraph/vta/testdata/src/maps.go index b7354dc..f5f51a3 100644 --- a/go/callgraph/vta/testdata/src/maps.go +++ b/go/callgraph/vta/testdata/src/maps.go
@@ -30,23 +30,16 @@ // Relevant SSA: // func Baz(m map[I]I, b1 B, b2 B, n map[string]*J) *J: -// t0 = local B (b1) -// *t0 = b1 -// t1 = local B (b2) -// *t1 = b2 -// t2 = *t0 -// t3 = make I <- B (t2) -// t4 = *t1 -// t5 = make I <- B (t4) -// m[t3] = t5 -// t6 = *t0 -// t7 = (B).Foo(t6) -// t8 = n[t7] -// return t8 +// t0 = make I <- B (b1) +// t1 = make I <- B (b2) +// m[t0] = t1 +// t2 = (B).Foo(b1) +// t3 = n[t2] +// return t3 // WANT: -// Local(t4) -> Local(t5) -// Local(t5) -> MapValue(testdata.I) -// Local(t3) -> MapKey(testdata.I) -// Local(t8) -> MapValue(*testdata.J) -// MapValue(*testdata.J) -> Local(t8) +// Local(b2) -> Local(t1) +// Local(t1) -> MapValue(testdata.I) +// Local(t0) -> MapKey(testdata.I) +// Local(t3) -> MapValue(*testdata.J) +// MapValue(*testdata.J) -> Local(t3)
diff --git a/go/callgraph/vta/testdata/src/panic.go b/go/callgraph/vta/testdata/src/panic.go index 5ef3548..1a987d0 100644 --- a/go/callgraph/vta/testdata/src/panic.go +++ b/go/callgraph/vta/testdata/src/panic.go
@@ -50,18 +50,14 @@ // return // // func Baz(i I): -// t0 = local A (a) -// *t0 = a // defer recover1() -// defer recover() -// t1 = *t0 -// t2 = make interface{} <- A (t1) +// t0 = make interface{} <- A (a) // panic t2 -// t2 argument to panic in Baz gets ultimately connected to recover +// t0 argument to panic in Baz gets ultimately connected to recover // registers t1 in recover1() and t0 in recover2(). // WANT: // Panic -> Recover -// Local(t2) -> Panic +// Local(t0) -> Panic // Recover -> Local(t0), Local(t1)
diff --git a/go/callgraph/vta/testdata/src/phi.go b/go/callgraph/vta/testdata/src/phi.go index 2144a2c..a4a3efd 100644 --- a/go/callgraph/vta/testdata/src/phi.go +++ b/go/callgraph/vta/testdata/src/phi.go
@@ -28,28 +28,23 @@ // Relevant SSA: // func Baz(b B, c bool): // 0: -// t0 = local B (b) -// *t0 = b // if c goto 1 else 3 // // 1: -// t1 = *t0 -// t2 = make I <- B (t1) +// t0 = make I <- B (b) // jump 2 // // 2: -// t3 = phi [1: t2, 3: t7] #i -// t4 = invoke t3.foo() +// t1 = phi [1: t0, 3: t3] #i +// t2 = invoke t1.foo() // return // // 3: -// t5 = local A (a) -// t6 = *t5 -// t7 = make I <- A (t6) +// t3 = make I <- A (struct{}{}:A) // jump 2 // WANT: -// Local(t1) -> Local(t2) -// Local(t2) -> Local(t3) -// Local(t7) -> Local(t3) -// Local(t6) -> Local(t7) +// Local(b) -> Local(t0) +// Local(t0) -> Local(t1) +// Local(t3) -> Local(t1) +// Constant(testdata.A) -> Local(t3)
diff --git a/go/callgraph/vta/testdata/src/select.go b/go/callgraph/vta/testdata/src/select.go index 50586d3..8774aec 100644 --- a/go/callgraph/vta/testdata/src/select.go +++ b/go/callgraph/vta/testdata/src/select.go
@@ -39,22 +39,20 @@ // Relevant SSA: // func Baz(b1 B, b2 B, c1 chan I, c2 chan J): // ... -// t2 = *t0 -// t3 = make I <- B (t2) -// t4 = *t1 -// t5 = make J <- B (t4) -// t6 = select blocking [c1<-t3, c2<-t5, <-c1, <-c2] (index int, ok bool, I, J) -// t7 = extract t6 #0 -// t8 = t7 == 0:int -// if t8 goto 2 else 3 +// t0 = make I <- B (b1) +// t1 = make J <- B (b2) +// t2 = select blocking [c1<-t0, c2<-t1, <-c1, <-c2] (index int, ok bool, I, J) +// t3 = extract t2 #0 +// t4 = t73== 0:int +// if t4 goto 2 else 3 // ... // 8: -// t15 = extract t6 #3 -// t16 = invoke t15.Foo() -// t17 = print(t18) +// t12 = extract t2 #3 +// t13 = invoke t12.Foo() +// t14 = print(t15) // WANT: -// Local(t3) -> Channel(chan testdata.I) -// Local(t5) -> Channel(chan testdata.J) -// Channel(chan testdata.I) -> Local(t6[2]) -// Channel(chan testdata.J) -> Local(t6[3]) +// Local(t0) -> Channel(chan testdata.I) +// Local(t1) -> Channel(chan testdata.J) +// Channel(chan testdata.I) -> Local(t2[2]) +// Channel(chan testdata.J) -> Local(t2[3])
diff --git a/go/callgraph/vta/testdata/src/store.go b/go/callgraph/vta/testdata/src/store.go index 8a36d4e..322e9e5 100644 --- a/go/callgraph/vta/testdata/src/store.go +++ b/go/callgraph/vta/testdata/src/store.go
@@ -26,16 +26,14 @@ } // Relevant SSA: -// t0 = local A (a) -// t1 = new I (i) -// t2 = *t0 no interesting flow: concrete types -// t3 = make I <- A (t2) t2 -> t3 -// *t1 = t3 t3 -> t1 -// t4 = *t1 t1 -> t4 -// t5 = invoke t4.foo() +// t0 = new I (i) +// t1 = make I <- A (struct{}{}:A) A -> t1 +// *t0 = t1 t1 -> t0 +// t2 = *t0 t0 -> t2 +// t3 = invoke t2.foo() // return // WANT: -// Local(t2) -> Local(t3) -// Local(t3) -> Local(t1) -// Local(t1) -> Local(t4) +// Constant(testdata.A) -> Local(t1) +// Local(t1) -> Local(t0) +// Local(t0) -> Local(t2)
diff --git a/go/callgraph/vta/testdata/src/store_load_alias.go b/go/callgraph/vta/testdata/src/store_load_alias.go index b9814f0..71f843b 100644 --- a/go/callgraph/vta/testdata/src/store_load_alias.go +++ b/go/callgraph/vta/testdata/src/store_load_alias.go
@@ -27,26 +27,24 @@ // t1 = new *I (j) // *t1 = t0 // t2 = *t1 -// t3 = local A (complit) -// t4 = *t3 -// t5 = make I <- A (t4) -// *t2 = t5 -// t6 = *t0 -// t7 = invoke t6.foo() -// t8 = *t1 -// t9 = *t8 -// t10 = invoke t9.foo() +// t3 = make I <- A (struct{}{}:A) I +// *t2 = t3 +// t4 = *t0 +// t5 = invoke t4.foo() +// t6 = *t1 +// t7 = *t6 +// t8 = invoke t7.foo() // Flow chain showing that A reaches i.foo(): -// t4 (A) -> t5 -> t2 <-> PtrInterface(I) <-> t0 -> t6 +// Constant(A) -> t3 -> t2 <-> PtrInterface(I) <-> t0 -> t4 // Flow chain showing that A reaches (**k).foo(): -// t4 (A) -> t5 -> t2 <-> PtrInterface(I) <-> t8 -> t9 +// Constant(A) -> t3 -> t2 <-> PtrInterface(I) <-> t6 -> t7 // WANT: // Local(i) -> Local(t0) -// Local(t0) -> Local(t6), PtrInterface(testdata.I) -// PtrInterface(testdata.I) -> Local(t0), Local(t2), Local(t8) +// Local(t0) -> Local(t4), PtrInterface(testdata.I) +// PtrInterface(testdata.I) -> Local(t0), Local(t2), Local(t6) // Local(t2) -> PtrInterface(testdata.I) -// Local(t4) -> Local(t5) -// Local(t5) -> Local(t2) -// Local(t8) -> Local(t9), PtrInterface(testdata.I) +// Constant(testdata.A) -> Local(t3) +// Local(t3) -> Local(t2) +// Local(t6) -> Local(t7), PtrInterface(testdata.I)
diff --git a/go/callgraph/vta/testdata/src/type_conversions.go b/go/callgraph/vta/testdata/src/type_conversions.go index e18077f..3dbfb0c 100644 --- a/go/callgraph/vta/testdata/src/type_conversions.go +++ b/go/callgraph/vta/testdata/src/type_conversions.go
@@ -60,26 +60,22 @@ // t5 = change interface X <- Y (t4) // t6 = invoke t5.Foo() // -// t7 = local A (complit) -// t8 = *t7 -// t9 = make Y <- A (t8) -// *t0 = t9 -// t10 = changetype *W <- *Y (t0) -// t11 = local B (complit) -// t12 = *t11 -// t13 = make W <- B (t12) -// *t10 = t13 -// t14 = *t0 -// t15 = invoke t14.Foo() -// t16 = *t10 -// t17 = invoke t16.Foo() +// t7 = make Y <- A (struct{}{}:A) +// *t0 = t7 +// t8 = changetype *W <- *Y (t0) +// t9 = make W <- B (struct{}{}:B) +// *t8 = t9 +// t10 = *t0 +// t11 = invoke t10.Foo() +// t12 = *t8 +// t13 = invoke t12.Foo() // return // WANT: // Local(t1) -> Local(t2) // Local(t4) -> Local(t5) -// Local(t0) -> Local(t1), Local(t10), Local(t14), Local(t4) +// Local(t0) -> Local(t1), Local(t10), Local(t4), Local(t8) // Local(y) -> Local(t0) -// Local(t8) -> Local(t9) -// Local(t9) -> Local(t0) -// Local(t13) -> Local(t10) +// Constant(testdata.A) -> Local(t7) +// Local(t7) -> Local(t0) +// Local(t9) -> Local(t8)
diff --git a/go/callgraph/vta/vta_go117_test.go b/go/callgraph/vta/vta_go117_test.go index 04f6980..6a5af2c 100644 --- a/go/callgraph/vta/vta_go117_test.go +++ b/go/callgraph/vta/vta_go117_test.go
@@ -26,7 +26,8 @@ } g, _ := typePropGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog)) - if gs := vtaGraphStr(g); !subGraph(want, gs) { - t.Errorf("`%s`: want superset of %v;\n got %v", file, want, gs) + got := vtaGraphStr(g) + if diff := setdiff(want, got); len(diff) != 0 { + t.Errorf("`%s`: want superset of %v;\n got %v", file, want, got) } }
diff --git a/go/callgraph/vta/vta_test.go b/go/callgraph/vta/vta_test.go index 549c4af..75a8ceb 100644 --- a/go/callgraph/vta/vta_test.go +++ b/go/callgraph/vta/vta_test.go
@@ -38,8 +38,9 @@ } g := CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog)) - if got := callGraphStr(g); !subGraph(want, got) { - t.Errorf("computed callgraph %v should contain %v", got, want) + got := callGraphStr(g) + if diff := setdiff(want, got); len(diff) > 0 { + t.Errorf("computed callgraph %v should contain %v (diff: %v)", got, want, diff) } }) } @@ -61,8 +62,9 @@ g := CallGraph(allFuncs, cha.CallGraph(prog)) // VTA over the whole program will produce a call graph that // includes Baz:(**i).Foo -> A.Foo, B.Foo. - if got := callGraphStr(g); !subGraph(want, got) { - t.Errorf("computed callgraph %v should contain %v", got, want) + got := callGraphStr(g) + if diff := setdiff(want, got); len(diff) > 0 { + t.Errorf("computed callgraph %v should contain %v (diff: %v)", got, want, diff) } // Prune the set of program functions to exclude Bar(). This should @@ -78,8 +80,9 @@ } want = []string{"Baz: Do(i) -> Do; invoke t2.Foo() -> A.Foo"} g = CallGraph(noBarFuncs, cha.CallGraph(prog)) - if got := callGraphStr(g); !subGraph(want, got) { - t.Errorf("pruned callgraph %v should contain %v", got, want) + got = callGraphStr(g) + if diff := setdiff(want, got); len(diff) > 0 { + t.Errorf("pruned callgraph %v should contain %v (diff: %v)", got, want, diff) } } @@ -131,7 +134,8 @@ } g := CallGraph(ssautil.AllFunctions(prog), cha.CallGraph(prog)) - if got := callGraphStr(g); !subGraph(want, got) { - t.Errorf("computed callgraph %v should contain %v", got, want) + got := callGraphStr(g) + if diff := setdiff(want, got); len(diff) != 0 { + t.Errorf("computed callgraph %v should contain %v (diff: %v)", got, want, diff) } }
diff --git a/go/ssa/builder.go b/go/ssa/builder.go index be8d36a..ffa6667 100644 --- a/go/ssa/builder.go +++ b/go/ssa/builder.go
@@ -1282,8 +1282,7 @@ case *types.Struct: if !isZero && len(e.Elts) != t.NumFields() { // memclear - sb.store(&address{addr, e.Lbrace, nil}, - zeroValue(fn, deref(addr.Type()))) + sb.store(&address{addr, e.Lbrace, nil}, zeroConst(deref(addr.Type()))) isZero = true } for i, e := range e.Elts { @@ -1327,8 +1326,7 @@ if !isZero && int64(len(e.Elts)) != at.Len() { // memclear - sb.store(&address{array, e.Lbrace, nil}, - zeroValue(fn, deref(array.Type()))) + sb.store(&address{array, e.Lbrace, nil}, zeroConst(deref(array.Type()))) } }
diff --git a/go/ssa/emit.go b/go/ssa/emit.go index 1731c79..4ba049d 100644 --- a/go/ssa/emit.go +++ b/go/ssa/emit.go
@@ -537,15 +537,46 @@ return v } -// zeroValue emits to f code to produce a zero value of type t, -// and returns it. -func zeroValue(f *Function, t types.Type) Value { - switch t.Underlying().(type) { - case *types.Struct, *types.Array: - return emitLoad(f, f.addLocal(t, token.NoPos)) - default: - return zeroConst(t) - } +// emitSliceToArray emits to f code to convert a slice value to an array value. +// +// Precondition: all types in type set of typ are arrays and convertible to all +// types in the type set of val.Type(). +func emitSliceToArray(f *Function, val Value, typ types.Type) Value { + // Emit the following: + // if val == nil && len(typ) == 0 { + // ptr = &[0]T{} + // } else { + // ptr = SliceToArrayPointer(val) + // } + // v = *ptr + + ptype := types.NewPointer(typ) + p := &SliceToArrayPointer{X: val} + p.setType(ptype) + ptr := f.emit(p) + + nilb := f.newBasicBlock("slicetoarray.nil") + nonnilb := f.newBasicBlock("slicetoarray.nonnil") + done := f.newBasicBlock("slicetoarray.done") + + cond := emitCompare(f, token.EQL, ptr, zeroConst(ptype), token.NoPos) + emitIf(f, cond, nilb, nonnilb) + f.currentBlock = nilb + + zero := f.addLocal(typ, token.NoPos) + emitJump(f, done) + f.currentBlock = nonnilb + + emitJump(f, done) + f.currentBlock = done + + phi := &Phi{Edges: []Value{zero, ptr}, Comment: "slicetoarray"} + phi.pos = val.Pos() + phi.setType(typ) + x := f.emit(phi) + unOp := &UnOp{Op: token.MUL, X: x} + unOp.setType(typ) + return f.emit(unOp) } // createRecoverBlock emits to f a block of code to return after a @@ -577,7 +608,7 @@ T := R.At(i).Type() // Return zero value of each result type. - results = append(results, zeroValue(f, T)) + results = append(results, zeroConst(T)) } } f.emit(&Return{Results: results})
diff --git a/go/ssa/lift.go b/go/ssa/lift.go index 945536b..b9cf7bc 100644 --- a/go/ssa/lift.go +++ b/go/ssa/lift.go
@@ -41,11 +41,8 @@ import ( "fmt" "go/token" - "go/types" "math/big" "os" - - "golang.org/x/tools/internal/typeparams" ) // If true, show diagnostic information at each step of lifting. @@ -383,12 +380,6 @@ // // fresh is a source of fresh ids for phi nodes. func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap, fresh *int) bool { - // TODO(taking): zero constants of aggregated types can now be lifted. - switch deref(alloc.Type()).Underlying().(type) { - case *types.Array, *types.Struct, *typeparams.TypeParam: - return false - } - // Don't lift named return values in functions that defer // calls that may recover from panic. if fn := alloc.Parent(); fn.Recover != nil {
diff --git a/go/ssa/testdata/valueforexpr.go b/go/ssa/testdata/valueforexpr.go index 243ec61..703c316 100644 --- a/go/ssa/testdata/valueforexpr.go +++ b/go/ssa/testdata/valueforexpr.go
@@ -94,18 +94,18 @@ _ = & /*@Slice*/ ([]int{}) // 2. Arrays - print( /*@UnOp*/ ([1]int{})) + print( /*@Const*/ ([1]int{})) print( /*@Alloc*/ (&[1]int{})) print(& /*@Alloc*/ ([1]int{})) - arr1 := /*@Alloc*/ ([1]int{}) + arr1 := /*@Const*/ ([1]int{}) arr2 := /*@Alloc*/ (&[1]int{}) arr3 := & /*@Alloc*/ ([1]int{}) _, _, _ = arr1, arr2, arr3 - _ = /*@UnOp*/ ([1]int{}) - _ = /*@Alloc*/ (& /*@Alloc*/ ([1]int{})) - _ = & /*@Alloc*/ ([1]int{}) + _ = /*@Const*/ ([1]int{}) + _ = /*@nil*/ (& /*@Const*/ ([1]int{})) // & optimized away + _ = & /*@Const*/ ([1]int{}) // 3. Maps type M map[int]int @@ -123,18 +123,18 @@ _ = & /*@MakeMap*/ (M{}) // 4. Structs - print( /*@UnOp*/ (struct{}{})) + print( /*@Const*/ (struct{}{})) print( /*@Alloc*/ (&struct{}{})) print(& /*@Alloc*/ (struct{}{})) - s1 := /*@Alloc*/ (struct{}{}) + s1 := /*@Const*/ (struct{}{}) s2 := /*@Alloc*/ (&struct{}{}) s3 := & /*@Alloc*/ (struct{}{}) _, _, _ = s1, s2, s3 - _ = /*@UnOp*/ (struct{}{}) - _ = /*@Alloc*/ (& /*@Alloc*/ (struct{}{})) - _ = & /*@Alloc*/ (struct{}{}) + _ = /*@Const*/ (struct{}{}) + _ = /*@nil*/ (& /*@Const*/ (struct{}{})) // & optimized away + _ = & /*@Const*/ (struct{}{}) } type t struct{ x int }