1// Copyright 2022 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 compare contains code for generating comparison
6// routines for structs, strings and interfaces.
7package compare
8
9import (
10	"cmd/compile/internal/base"
11	"cmd/compile/internal/ir"
12	"cmd/compile/internal/typecheck"
13	"cmd/compile/internal/types"
14	"fmt"
15	"math/bits"
16	"sort"
17)
18
19// IsRegularMemory reports whether t can be compared/hashed as regular memory.
20func IsRegularMemory(t *types.Type) bool {
21	return types.AlgType(t) == types.AMEM
22}
23
24// Memrun finds runs of struct fields for which memory-only algs are appropriate.
25// t is the parent struct type, and start is the field index at which to start the run.
26// size is the length in bytes of the memory included in the run.
27// next is the index just after the end of the memory run.
28func Memrun(t *types.Type, start int) (size int64, next int) {
29	next = start
30	for {
31		next++
32		if next == t.NumFields() {
33			break
34		}
35		// Stop run after a padded field.
36		if types.IsPaddedField(t, next-1) {
37			break
38		}
39		// Also, stop before a blank or non-memory field.
40		if f := t.Field(next); f.Sym.IsBlank() || !IsRegularMemory(f.Type) {
41			break
42		}
43		// For issue 46283, don't combine fields if the resulting load would
44		// require a larger alignment than the component fields.
45		if base.Ctxt.Arch.Alignment > 1 {
46			align := t.Alignment()
47			if off := t.Field(start).Offset; off&(align-1) != 0 {
48				// Offset is less aligned than the containing type.
49				// Use offset to determine alignment.
50				align = 1 << uint(bits.TrailingZeros64(uint64(off)))
51			}
52			size := t.Field(next).End() - t.Field(start).Offset
53			if size > align {
54				break
55			}
56		}
57	}
58	return t.Field(next-1).End() - t.Field(start).Offset, next
59}
60
61// EqCanPanic reports whether == on type t could panic (has an interface somewhere).
62// t must be comparable.
63func EqCanPanic(t *types.Type) bool {
64	switch t.Kind() {
65	default:
66		return false
67	case types.TINTER:
68		return true
69	case types.TARRAY:
70		return EqCanPanic(t.Elem())
71	case types.TSTRUCT:
72		for _, f := range t.Fields() {
73			if !f.Sym.IsBlank() && EqCanPanic(f.Type) {
74				return true
75			}
76		}
77		return false
78	}
79}
80
81// EqStructCost returns the cost of an equality comparison of two structs.
82//
83// The cost is determined using an algorithm which takes into consideration
84// the size of the registers in the current architecture and the size of the
85// memory-only fields in the struct.
86func EqStructCost(t *types.Type) int64 {
87	cost := int64(0)
88
89	for i, fields := 0, t.Fields(); i < len(fields); {
90		f := fields[i]
91
92		// Skip blank-named fields.
93		if f.Sym.IsBlank() {
94			i++
95			continue
96		}
97
98		n, _, next := eqStructFieldCost(t, i)
99
100		cost += n
101		i = next
102	}
103
104	return cost
105}
106
107// eqStructFieldCost returns the cost of an equality comparison of two struct fields.
108// t is the parent struct type, and i is the index of the field in the parent struct type.
109// eqStructFieldCost may compute the cost of several adjacent fields at once. It returns
110// the cost, the size of the set of fields it computed the cost for (in bytes), and the
111// index of the first field not part of the set of fields for which the cost
112// has already been calculated.
113func eqStructFieldCost(t *types.Type, i int) (int64, int64, int) {
114	var (
115		cost    = int64(0)
116		regSize = int64(types.RegSize)
117
118		size int64
119		next int
120	)
121
122	if base.Ctxt.Arch.CanMergeLoads {
123		// If we can merge adjacent loads then we can calculate the cost of the
124		// comparison using the size of the memory run and the size of the registers.
125		size, next = Memrun(t, i)
126		cost = size / regSize
127		if size%regSize != 0 {
128			cost++
129		}
130		return cost, size, next
131	}
132
133	// If we cannot merge adjacent loads then we have to use the size of the
134	// field and take into account the type to determine how many loads and compares
135	// are needed.
136	ft := t.Field(i).Type
137	size = ft.Size()
138	next = i + 1
139
140	return calculateCostForType(ft), size, next
141}
142
143func calculateCostForType(t *types.Type) int64 {
144	var cost int64
145	switch t.Kind() {
146	case types.TSTRUCT:
147		return EqStructCost(t)
148	case types.TSLICE:
149		// Slices are not comparable.
150		base.Fatalf("calculateCostForType: unexpected slice type")
151	case types.TARRAY:
152		elemCost := calculateCostForType(t.Elem())
153		cost = t.NumElem() * elemCost
154	case types.TSTRING, types.TINTER, types.TCOMPLEX64, types.TCOMPLEX128:
155		cost = 2
156	case types.TINT64, types.TUINT64:
157		cost = 8 / int64(types.RegSize)
158	default:
159		cost = 1
160	}
161	return cost
162}
163
164// EqStruct compares two structs np and nq for equality.
165// It works by building a list of boolean conditions to satisfy.
166// Conditions must be evaluated in the returned order and
167// properly short-circuited by the caller.
168// The first return value is the flattened list of conditions,
169// the second value is a boolean indicating whether any of the
170// comparisons could panic.
171func EqStruct(t *types.Type, np, nq ir.Node) ([]ir.Node, bool) {
172	// The conditions are a list-of-lists. Conditions are reorderable
173	// within each inner list. The outer lists must be evaluated in order.
174	var conds [][]ir.Node
175	conds = append(conds, []ir.Node{})
176	and := func(n ir.Node) {
177		i := len(conds) - 1
178		conds[i] = append(conds[i], n)
179	}
180
181	// Walk the struct using memequal for runs of AMEM
182	// and calling specific equality tests for the others.
183	for i, fields := 0, t.Fields(); i < len(fields); {
184		f := fields[i]
185
186		// Skip blank-named fields.
187		if f.Sym.IsBlank() {
188			i++
189			continue
190		}
191
192		typeCanPanic := EqCanPanic(f.Type)
193
194		// Compare non-memory fields with field equality.
195		if !IsRegularMemory(f.Type) {
196			if typeCanPanic {
197				// Enforce ordering by starting a new set of reorderable conditions.
198				conds = append(conds, []ir.Node{})
199			}
200			switch {
201			case f.Type.IsString():
202				p := typecheck.DotField(base.Pos, typecheck.Expr(np), i)
203				q := typecheck.DotField(base.Pos, typecheck.Expr(nq), i)
204				eqlen, eqmem := EqString(p, q)
205				and(eqlen)
206				and(eqmem)
207			default:
208				and(eqfield(np, nq, i))
209			}
210			if typeCanPanic {
211				// Also enforce ordering after something that can panic.
212				conds = append(conds, []ir.Node{})
213			}
214			i++
215			continue
216		}
217
218		cost, size, next := eqStructFieldCost(t, i)
219		if cost <= 4 {
220			// Cost of 4 or less: use plain field equality.
221			for j := i; j < next; j++ {
222				and(eqfield(np, nq, j))
223			}
224		} else {
225			// Higher cost: use memequal.
226			cc := eqmem(np, nq, i, size)
227			and(cc)
228		}
229		i = next
230	}
231
232	// Sort conditions to put runtime calls last.
233	// Preserve the rest of the ordering.
234	var flatConds []ir.Node
235	for _, c := range conds {
236		isCall := func(n ir.Node) bool {
237			return n.Op() == ir.OCALL || n.Op() == ir.OCALLFUNC
238		}
239		sort.SliceStable(c, func(i, j int) bool {
240			return !isCall(c[i]) && isCall(c[j])
241		})
242		flatConds = append(flatConds, c...)
243	}
244	return flatConds, len(conds) > 1
245}
246
247// EqString returns the nodes
248//
249//	len(s) == len(t)
250//
251// and
252//
253//	memequal(s.ptr, t.ptr, len(s))
254//
255// which can be used to construct string equality comparison.
256// eqlen must be evaluated before eqmem, and shortcircuiting is required.
257func EqString(s, t ir.Node) (eqlen *ir.BinaryExpr, eqmem *ir.CallExpr) {
258	s = typecheck.Conv(s, types.Types[types.TSTRING])
259	t = typecheck.Conv(t, types.Types[types.TSTRING])
260	sptr := ir.NewUnaryExpr(base.Pos, ir.OSPTR, s)
261	tptr := ir.NewUnaryExpr(base.Pos, ir.OSPTR, t)
262	slen := typecheck.Conv(ir.NewUnaryExpr(base.Pos, ir.OLEN, s), types.Types[types.TUINTPTR])
263	tlen := typecheck.Conv(ir.NewUnaryExpr(base.Pos, ir.OLEN, t), types.Types[types.TUINTPTR])
264
265	// Pick the 3rd arg to memequal. Both slen and tlen are fine to use, because we short
266	// circuit the memequal call if they aren't the same. But if one is a constant some
267	// memequal optimizations are easier to apply.
268	probablyConstant := func(n ir.Node) bool {
269		if n.Op() == ir.OCONVNOP {
270			n = n.(*ir.ConvExpr).X
271		}
272		if n.Op() == ir.OLITERAL {
273			return true
274		}
275		if n.Op() != ir.ONAME {
276			return false
277		}
278		name := n.(*ir.Name)
279		if name.Class != ir.PAUTO {
280			return false
281		}
282		if def := name.Defn; def == nil {
283			// n starts out as the empty string
284			return true
285		} else if def.Op() == ir.OAS && (def.(*ir.AssignStmt).Y == nil || def.(*ir.AssignStmt).Y.Op() == ir.OLITERAL) {
286			// n starts out as a constant string
287			return true
288		}
289		return false
290	}
291	cmplen := slen
292	if probablyConstant(t) && !probablyConstant(s) {
293		cmplen = tlen
294	}
295
296	fn := typecheck.LookupRuntime("memequal", types.Types[types.TUINT8], types.Types[types.TUINT8])
297	call := typecheck.Call(base.Pos, fn, []ir.Node{sptr, tptr, ir.Copy(cmplen)}, false).(*ir.CallExpr)
298
299	cmp := ir.NewBinaryExpr(base.Pos, ir.OEQ, slen, tlen)
300	cmp = typecheck.Expr(cmp).(*ir.BinaryExpr)
301	cmp.SetType(types.Types[types.TBOOL])
302	return cmp, call
303}
304
305// EqInterface returns the nodes
306//
307//	s.tab == t.tab (or s.typ == t.typ, as appropriate)
308//
309// and
310//
311//	ifaceeq(s.tab, s.data, t.data) (or efaceeq(s.typ, s.data, t.data), as appropriate)
312//
313// which can be used to construct interface equality comparison.
314// eqtab must be evaluated before eqdata, and shortcircuiting is required.
315func EqInterface(s, t ir.Node) (eqtab *ir.BinaryExpr, eqdata *ir.CallExpr) {
316	if !types.Identical(s.Type(), t.Type()) {
317		base.Fatalf("EqInterface %v %v", s.Type(), t.Type())
318	}
319	// func ifaceeq(tab *uintptr, x, y unsafe.Pointer) (ret bool)
320	// func efaceeq(typ *uintptr, x, y unsafe.Pointer) (ret bool)
321	var fn ir.Node
322	if s.Type().IsEmptyInterface() {
323		fn = typecheck.LookupRuntime("efaceeq")
324	} else {
325		fn = typecheck.LookupRuntime("ifaceeq")
326	}
327
328	stab := ir.NewUnaryExpr(base.Pos, ir.OITAB, s)
329	ttab := ir.NewUnaryExpr(base.Pos, ir.OITAB, t)
330	sdata := ir.NewUnaryExpr(base.Pos, ir.OIDATA, s)
331	tdata := ir.NewUnaryExpr(base.Pos, ir.OIDATA, t)
332	sdata.SetType(types.Types[types.TUNSAFEPTR])
333	tdata.SetType(types.Types[types.TUNSAFEPTR])
334	sdata.SetTypecheck(1)
335	tdata.SetTypecheck(1)
336
337	call := typecheck.Call(base.Pos, fn, []ir.Node{stab, sdata, tdata}, false).(*ir.CallExpr)
338
339	cmp := ir.NewBinaryExpr(base.Pos, ir.OEQ, stab, ttab)
340	cmp = typecheck.Expr(cmp).(*ir.BinaryExpr)
341	cmp.SetType(types.Types[types.TBOOL])
342	return cmp, call
343}
344
345// eqfield returns the node
346//
347//	p.field == q.field
348func eqfield(p, q ir.Node, field int) ir.Node {
349	nx := typecheck.DotField(base.Pos, typecheck.Expr(p), field)
350	ny := typecheck.DotField(base.Pos, typecheck.Expr(q), field)
351	return typecheck.Expr(ir.NewBinaryExpr(base.Pos, ir.OEQ, nx, ny))
352}
353
354// eqmem returns the node
355//
356//	memequal(&p.field, &q.field, size)
357func eqmem(p, q ir.Node, field int, size int64) ir.Node {
358	nx := typecheck.Expr(typecheck.NodAddr(typecheck.DotField(base.Pos, p, field)))
359	ny := typecheck.Expr(typecheck.NodAddr(typecheck.DotField(base.Pos, q, field)))
360
361	fn, needsize := eqmemfunc(size, nx.Type().Elem())
362	call := ir.NewCallExpr(base.Pos, ir.OCALL, fn, nil)
363	call.Args.Append(nx)
364	call.Args.Append(ny)
365	if needsize {
366		call.Args.Append(ir.NewInt(base.Pos, size))
367	}
368
369	return call
370}
371
372func eqmemfunc(size int64, t *types.Type) (fn *ir.Name, needsize bool) {
373	if !base.Ctxt.Arch.CanMergeLoads && t.Alignment() < int64(base.Ctxt.Arch.Alignment) && t.Alignment() < t.Size() {
374		// We can't use larger comparisons if the value might not be aligned
375		// enough for the larger comparison. See issues 46283 and 67160.
376		size = 0
377	}
378	switch size {
379	case 1, 2, 4, 8, 16:
380		buf := fmt.Sprintf("memequal%d", int(size)*8)
381		return typecheck.LookupRuntime(buf, t, t), false
382	}
383
384	return typecheck.LookupRuntime("memequal", t, t), true
385}
386