Source file src/cmd/compile/internal/ssacompile/decomposeaddr_test.go

     1  // Copyright 2026 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ssacompile_test
     6  
     7  import (
     8  	"encoding/binary"
     9  	"testing"
    10  	"unsafe"
    11  )
    12  
    13  type point struct {
    14  	x, y int
    15  }
    16  
    17  //go:noinline
    18  func simpleStructAddr(c bool) int {
    19  	p := point{10, 20}
    20  	ptr := &p
    21  	if c {
    22  		ptr.x += 1
    23  	} else {
    24  		ptr.y += 1
    25  	}
    26  	return ptr.x + ptr.y
    27  }
    28  
    29  //go:noinline
    30  func simpleStructReg(c bool) int {
    31  	x, y := 10, 20
    32  	if c {
    33  		x += 1
    34  	} else {
    35  		y += 1
    36  	}
    37  	return x + y
    38  }
    39  
    40  type inner struct {
    41  	a, b int
    42  }
    43  
    44  type outer struct {
    45  	in inner
    46  	c  int
    47  }
    48  
    49  //go:noinline
    50  func nestedStructAddr(c bool) int {
    51  	o := outer{inner{1, 2}, 3}
    52  	ptr := &o
    53  	if c {
    54  		ptr.in.a += 10
    55  	} else {
    56  		ptr.in.b += 20
    57  	}
    58  	return ptr.in.a + ptr.in.b + ptr.c
    59  }
    60  
    61  //go:noinline
    62  func nestedStructReg(c bool) int {
    63  	a, b, c_val := 1, 2, 3
    64  	if c {
    65  		a += 10
    66  	} else {
    67  		b += 20
    68  	}
    69  	return a + b + c_val
    70  }
    71  
    72  type mixed struct {
    73  	a int32
    74  	b float64
    75  }
    76  
    77  //go:noinline
    78  func mixedStructAddr(c bool) float64 {
    79  	m := mixed{1, 2.0}
    80  	ptr := &m
    81  	if c {
    82  		ptr.a = 10
    83  	} else {
    84  		ptr.b = 20.0
    85  	}
    86  	return float64(ptr.a) + ptr.b
    87  }
    88  
    89  //go:noinline
    90  func mixedStructReg(c bool) float64 {
    91  	a := int32(1)
    92  	b := 2.0
    93  	if c {
    94  		a = 10
    95  	} else {
    96  		b = 20.0
    97  	}
    98  	return float64(a) + b
    99  }
   100  
   101  //go:noinline
   102  func arrayAddr(c bool) int {
   103  	var a [3]int
   104  	a[0] = 10
   105  	a[1] = 20
   106  	a[2] = 30
   107  	if c {
   108  		a[1] += 5
   109  	}
   110  	return a[0] + a[1] + a[2]
   111  }
   112  
   113  //go:noinline
   114  func arrayReg(c bool) int {
   115  	a0, a1, a2 := 10, 20, 30
   116  	if c {
   117  		a1 += 5
   118  	}
   119  	return a0 + a1 + a2
   120  }
   121  
   122  //go:noinline
   123  func zeroStructAddr(c bool) int {
   124  	var p point
   125  	ptr := &p
   126  	if c {
   127  		ptr.x = 10
   128  	} else {
   129  		ptr.y = 20
   130  	}
   131  	return ptr.x + ptr.y
   132  }
   133  
   134  //go:noinline
   135  func zeroStructReg(c bool) int {
   136  	var x, y int
   137  	if c {
   138  		x = 10
   139  	} else {
   140  		y = 20
   141  	}
   142  	return x + y
   143  }
   144  
   145  //go:noinline
   146  func moveStructAddr(c bool) int {
   147  	p1 := point{10, 20}
   148  	var p2 point
   149  	if c {
   150  		p2 = p1
   151  	} else {
   152  		p2 = point{30, 40}
   153  	}
   154  	return p2.x + p2.y
   155  }
   156  
   157  //go:noinline
   158  func moveStructReg(c bool) int {
   159  	p1x, p1y := 10, 20
   160  	var p2x, p2y int
   161  	if c {
   162  		p2x, p2y = p1x, p1y
   163  	} else {
   164  		p2x, p2y = 30, 40
   165  	}
   166  	return p2x + p2y
   167  }
   168  
   169  //go:noinline
   170  func shadowedStructAddr(c bool) int {
   171  	p := point{10, 20}
   172  	if c {
   173  		p := point{30, 40}
   174  		return p.x + p.y
   175  	}
   176  	return p.x + p.y
   177  }
   178  
   179  //go:noinline
   180  func shadowedStructReg(c bool) int {
   181  	px, py := 10, 20
   182  	if c {
   183  		px2, py2 := 30, 40
   184  		return px2 + py2
   185  	}
   186  	return px + py
   187  }
   188  
   189  type big struct {
   190  	a [100]int
   191  }
   192  
   193  //go:noinline
   194  func tooBigStructAddr() int {
   195  	var b big
   196  	b.a[0] = 10
   197  	return b.a[0]
   198  }
   199  
   200  //go:noinline
   201  func tooBigStructReg() int {
   202  	var a0 int = 10
   203  	return a0
   204  }
   205  
   206  type arrayStruct struct {
   207  	a [2]int
   208  	b int
   209  }
   210  
   211  //go:noinline
   212  func arrayStructAddr(c bool) int {
   213  	s := arrayStruct{[2]int{1, 2}, 3}
   214  	if c {
   215  		s.a[0] += 10
   216  	} else {
   217  		s.b += 20
   218  	}
   219  	return s.a[0] + s.a[1] + s.b
   220  }
   221  
   222  //go:noinline
   223  func arrayStructReg(c bool) int {
   224  	a0, a1, b := 1, 2, 3
   225  	if c {
   226  		a0 += 10
   227  	} else {
   228  		b += 20
   229  	}
   230  	return a0 + a1 + b
   231  }
   232  
   233  type stringStruct struct {
   234  	s string
   235  	i int
   236  }
   237  
   238  //go:noinline
   239  func stringStructAddr(c bool) int {
   240  	// The string field splits into a pointer leaf and a length leaf,
   241  	// so the whole struct decomposes into three scalar variables.
   242  	ss := stringStruct{"hello", 10}
   243  	if c {
   244  		ss.i += 5
   245  	}
   246  	return len(ss.s) + ss.i
   247  }
   248  
   249  //go:noinline
   250  func sliceStructAddr(c bool) int {
   251  	type sliceStruct struct {
   252  		sl []int
   253  		i  int
   254  	}
   255  	ss := sliceStruct{[]int{1, 2, 3}, 10}
   256  	p := &ss
   257  	if c {
   258  		p.i += 5
   259  	}
   260  	return len(p.sl) + cap(p.sl) + p.sl[0] + p.i
   261  }
   262  
   263  //go:noinline
   264  func sliceStructReg(c bool) int {
   265  	sl, i := []int{1, 2, 3}, 10
   266  	if c {
   267  		i += 5
   268  	}
   269  	return len(sl) + cap(sl) + sl[0] + i
   270  }
   271  
   272  //go:noinline
   273  func ifaceStructAddr(c bool) int {
   274  	type ifaceStruct struct {
   275  		e any
   276  		i int
   277  	}
   278  	is := ifaceStruct{e: 42, i: 10}
   279  	p := &is
   280  	if c {
   281  		p.i += 5
   282  	}
   283  	return p.e.(int) + p.i
   284  }
   285  
   286  //go:noinline
   287  func ifaceStructReg(c bool) int {
   288  	var e any = 42
   289  	i := 10
   290  	if c {
   291  		i += 5
   292  	}
   293  	return e.(int) + i
   294  }
   295  
   296  //go:noinline
   297  func complexStructAddr(c bool) int {
   298  	type complexStruct struct {
   299  		z complex128
   300  		i int
   301  	}
   302  	cs := complexStruct{z: complex(3, 4), i: 10}
   303  	p := &cs
   304  	if c {
   305  		p.i += 5
   306  	}
   307  	return int(real(p.z)) + int(imag(p.z)) + p.i
   308  }
   309  
   310  //go:noinline
   311  func complexStructReg(c bool) int {
   312  	z, i := complex(3.0, 4.0), 10
   313  	if c {
   314  		i += 5
   315  	}
   316  	return int(real(z)) + int(imag(z)) + i
   317  }
   318  
   319  //go:noinline
   320  func stringStructReg(c bool) int {
   321  	s, i := "hello", 10
   322  	if c {
   323  		i += 5
   324  	}
   325  	return len(s) + i
   326  }
   327  
   328  //go:noinline
   329  func partialZeroAddr(bool) int {
   330  	type T struct {
   331  		a int
   332  		b [2]int
   333  		c int
   334  	}
   335  
   336  	t := T{a: 9, c: 9} // ERROR "promoted t"
   337  	p := &t.b          // OffPtr
   338  	*p = [2]int{}      // Zero
   339  	return t.a * t.c
   340  }
   341  
   342  //go:noinline
   343  func partialMoveAddr(bool) int {
   344  	type T struct {
   345  		a int
   346  		b [2]int
   347  		c int
   348  	}
   349  
   350  	b2 := [2]int{1, 2} // ERROR "promoted b2"
   351  
   352  	t := T{a: 9, c: 9} // ERROR "promoted t"
   353  	p := &t.b          // OffPtr
   354  	*p = b2            // Move
   355  	return t.a * t.c
   356  }
   357  
   358  //go:noinline
   359  func unsafeSelfMoveAddr(c bool) int {
   360  	var s struct {
   361  		a [2]uint16
   362  		b uint32
   363  	}
   364  	s.a[0], s.a[1] = 1, 2
   365  	*(*[4]byte)(unsafe.Pointer(&s.b)) = *(*[4]byte)(unsafe.Pointer(&s.a))
   366  	if c {
   367  		s.b++
   368  	}
   369  	return int(s.b)
   370  }
   371  
   372  func unsafeAliasMoveAddr(c bool) int {
   373  	// A Move between two differently-shaped candidates via an unsafe
   374  	// cast: breaking the Move down by one side's leaves creates
   375  	// accesses that straddle the other side's leaves, so only one of
   376  	// the two may be decomposed (see the Move partner disqualification
   377  	// in decomposeAddr).
   378  	var x [4]byte
   379  	x[0], x[1], x[2], x[3] = 1, 2, 3, 4
   380  	var y [2]uint16
   381  	y = *(*[2]uint16)(unsafe.Pointer(&x))
   382  	if c {
   383  		y[0]++
   384  	}
   385  	return int(y[0]) + int(y[1])
   386  }
   387  
   388  //go:noinline
   389  func unsafeAliasMoveReg(c bool) int {
   390  	// x's bytes reinterpreted as [2]uint16, in the target's byte order.
   391  	v0 := binary.NativeEndian.Uint16([]byte{1, 2})
   392  	v1 := binary.NativeEndian.Uint16([]byte{3, 4})
   393  	if c {
   394  		v0++
   395  	}
   396  	return int(v0) + int(v1)
   397  }
   398  
   399  //go:noinline
   400  func bareComplexAddr(c bool) int {
   401  	// A bare multi-word builtin composite (no enclosing struct) splits
   402  	// into word leaves just like a composite-typed field does.
   403  	x := complex(3.0, 4.0)
   404  	q := &x
   405  	if c {
   406  		q = &x
   407  	}
   408  	return int(real(*q)) + int(imag(*q))
   409  }
   410  
   411  //go:noinline
   412  func bareComplexReg(c bool) int {
   413  	re, im := 3.0, 4.0
   414  	_ = c
   415  	return int(re) + int(im)
   416  }
   417  
   418  //go:noinline
   419  func bareStringAddr(c bool) int {
   420  	s := "hello"
   421  	p := &s
   422  	if c {
   423  		p = &s
   424  	}
   425  	return len(*p)
   426  }
   427  
   428  //go:noinline
   429  func bareStringReg(c bool) int {
   430  	_ = c
   431  	return 5
   432  }
   433  
   434  func TestDecomposeAddr(t *testing.T) {
   435  	tests := []struct {
   436  		name string
   437  		f1   func(bool) int
   438  		f2   func(bool) int
   439  	}{
   440  		{"simpleStruct", simpleStructAddr, simpleStructReg},
   441  		{"nestedStruct", nestedStructAddr, nestedStructReg},
   442  		{"array", arrayAddr, arrayReg},
   443  		{"zeroStruct", zeroStructAddr, zeroStructReg},
   444  		{"moveStruct", moveStructAddr, moveStructReg},
   445  		{"shadowedStruct", shadowedStructAddr, shadowedStructReg},
   446  		{"arrayStruct", arrayStructAddr, arrayStructReg},
   447  		{"stringStruct", stringStructAddr, stringStructReg},
   448  		{"unsafeAliasMove", unsafeAliasMoveAddr, unsafeAliasMoveReg},
   449  		{"unsafeSelfMove", unsafeSelfMoveAddr, func(c bool) int {
   450  			// s.a's bytes reinterpreted as uint32, in the target's byte order.
   451  			b := binary.NativeEndian.Uint32(binary.NativeEndian.AppendUint16(binary.NativeEndian.AppendUint16(nil, 1), 2))
   452  			if c {
   453  				return int(b + 1)
   454  			}
   455  			return int(b)
   456  		}},
   457  		{"bareComplex", bareComplexAddr, bareComplexReg},
   458  		{"bareString", bareStringAddr, bareStringReg},
   459  		{"sliceStruct", sliceStructAddr, sliceStructReg},
   460  		{"ifaceStruct", ifaceStructAddr, ifaceStructReg},
   461  		{"complexStruct", complexStructAddr, complexStructReg},
   462  		{"partialZero", partialZeroAddr, func(bool) int { return 81 }},
   463  		{"partialMove", partialMoveAddr, func(bool) int { return 81 }},
   464  	}
   465  
   466  	for _, tc := range tests {
   467  		for _, c := range []bool{true, false} {
   468  			if got, want := tc.f1(c), tc.f2(c); got != want {
   469  				t.Errorf("%s(%v): got %d, want %d", tc.name, c, got, want)
   470  			}
   471  		}
   472  	}
   473  
   474  	if got, want := mixedStructAddr(true), mixedStructReg(true); got != want {
   475  		t.Errorf("mixedStruct(true): got %f, want %f", got, want)
   476  	}
   477  	if got, want := mixedStructAddr(false), mixedStructReg(false); got != want {
   478  		t.Errorf("mixedStruct(false): got %f, want %f", got, want)
   479  	}
   480  
   481  	if got, want := tooBigStructAddr(), tooBigStructReg(); got != want {
   482  		t.Errorf("tooBigStruct: got %d, want %d", got, want)
   483  	}
   484  }
   485  

View as plain text