1// Copyright 2016 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
5package reflectdata
6
7import (
8	"fmt"
9
10	"cmd/compile/internal/base"
11	"cmd/compile/internal/compare"
12	"cmd/compile/internal/ir"
13	"cmd/compile/internal/objw"
14	"cmd/compile/internal/typecheck"
15	"cmd/compile/internal/types"
16	"cmd/internal/obj"
17	"cmd/internal/src"
18)
19
20// AlgType returns the fixed-width AMEMxx variants instead of the general
21// AMEM kind when possible.
22func AlgType(t *types.Type) types.AlgKind {
23	a := types.AlgType(t)
24	if a == types.AMEM {
25		if t.Alignment() < int64(base.Ctxt.Arch.Alignment) && t.Alignment() < t.Size() {
26			// For example, we can't treat [2]int16 as an int32 if int32s require
27			// 4-byte alignment. See issue 46283.
28			return a
29		}
30		switch t.Size() {
31		case 0:
32			return types.AMEM0
33		case 1:
34			return types.AMEM8
35		case 2:
36			return types.AMEM16
37		case 4:
38			return types.AMEM32
39		case 8:
40			return types.AMEM64
41		case 16:
42			return types.AMEM128
43		}
44	}
45
46	return a
47}
48
49// genhash returns a symbol which is the closure used to compute
50// the hash of a value of type t.
51// Note: the generated function must match runtime.typehash exactly.
52func genhash(t *types.Type) *obj.LSym {
53	switch AlgType(t) {
54	default:
55		// genhash is only called for types that have equality
56		base.Fatalf("genhash %v", t)
57	case types.AMEM0:
58		return sysClosure("memhash0")
59	case types.AMEM8:
60		return sysClosure("memhash8")
61	case types.AMEM16:
62		return sysClosure("memhash16")
63	case types.AMEM32:
64		return sysClosure("memhash32")
65	case types.AMEM64:
66		return sysClosure("memhash64")
67	case types.AMEM128:
68		return sysClosure("memhash128")
69	case types.ASTRING:
70		return sysClosure("strhash")
71	case types.AINTER:
72		return sysClosure("interhash")
73	case types.ANILINTER:
74		return sysClosure("nilinterhash")
75	case types.AFLOAT32:
76		return sysClosure("f32hash")
77	case types.AFLOAT64:
78		return sysClosure("f64hash")
79	case types.ACPLX64:
80		return sysClosure("c64hash")
81	case types.ACPLX128:
82		return sysClosure("c128hash")
83	case types.AMEM:
84		// For other sizes of plain memory, we build a closure
85		// that calls memhash_varlen. The size of the memory is
86		// encoded in the first slot of the closure.
87		closure := TypeLinksymLookup(fmt.Sprintf(".hashfunc%d", t.Size()))
88		if len(closure.P) > 0 { // already generated
89			return closure
90		}
91		if memhashvarlen == nil {
92			memhashvarlen = typecheck.LookupRuntimeFunc("memhash_varlen")
93		}
94		ot := 0
95		ot = objw.SymPtr(closure, ot, memhashvarlen, 0)
96		ot = objw.Uintptr(closure, ot, uint64(t.Size())) // size encoded in closure
97		objw.Global(closure, int32(ot), obj.DUPOK|obj.RODATA)
98		return closure
99	case types.ASPECIAL:
100		break
101	}
102
103	closure := TypeLinksymPrefix(".hashfunc", t)
104	if len(closure.P) > 0 { // already generated
105		return closure
106	}
107
108	// Generate hash functions for subtypes.
109	// There are cases where we might not use these hashes,
110	// but in that case they will get dead-code eliminated.
111	// (And the closure generated by genhash will also get
112	// dead-code eliminated, as we call the subtype hashers
113	// directly.)
114	switch t.Kind() {
115	case types.TARRAY:
116		genhash(t.Elem())
117	case types.TSTRUCT:
118		for _, f := range t.Fields() {
119			genhash(f.Type)
120		}
121	}
122
123	if base.Flag.LowerR != 0 {
124		fmt.Printf("genhash %v %v\n", closure, t)
125	}
126
127	fn := hashFunc(t)
128
129	// Build closure. It doesn't close over any variables, so
130	// it contains just the function pointer.
131	objw.SymPtr(closure, 0, fn.Linksym(), 0)
132	objw.Global(closure, int32(types.PtrSize), obj.DUPOK|obj.RODATA)
133
134	return closure
135}
136
137func hashFunc(t *types.Type) *ir.Func {
138	sym := TypeSymPrefix(".hash", t)
139	if sym.Def != nil {
140		return sym.Def.(*ir.Name).Func
141	}
142
143	pos := base.AutogeneratedPos // less confusing than end of input
144	base.Pos = pos
145
146	// func sym(p *T, h uintptr) uintptr
147	fn := ir.NewFunc(pos, pos, sym, types.NewSignature(nil,
148		[]*types.Field{
149			types.NewField(pos, typecheck.Lookup("p"), types.NewPtr(t)),
150			types.NewField(pos, typecheck.Lookup("h"), types.Types[types.TUINTPTR]),
151		},
152		[]*types.Field{
153			types.NewField(pos, nil, types.Types[types.TUINTPTR]),
154		},
155	))
156	sym.Def = fn.Nname
157	fn.Pragma |= ir.Noinline // TODO(mdempsky): We need to emit this during the unified frontend instead, to allow inlining.
158
159	typecheck.DeclFunc(fn)
160	np := fn.Dcl[0]
161	nh := fn.Dcl[1]
162
163	switch t.Kind() {
164	case types.TARRAY:
165		// An array of pure memory would be handled by the
166		// standard algorithm, so the element type must not be
167		// pure memory.
168		hashel := hashfor(t.Elem())
169
170		// for i := 0; i < nelem; i++
171		ni := typecheck.TempAt(base.Pos, ir.CurFunc, types.Types[types.TINT])
172		init := ir.NewAssignStmt(base.Pos, ni, ir.NewInt(base.Pos, 0))
173		cond := ir.NewBinaryExpr(base.Pos, ir.OLT, ni, ir.NewInt(base.Pos, t.NumElem()))
174		post := ir.NewAssignStmt(base.Pos, ni, ir.NewBinaryExpr(base.Pos, ir.OADD, ni, ir.NewInt(base.Pos, 1)))
175		loop := ir.NewForStmt(base.Pos, nil, cond, post, nil, false)
176		loop.PtrInit().Append(init)
177
178		// h = hashel(&p[i], h)
179		call := ir.NewCallExpr(base.Pos, ir.OCALL, hashel, nil)
180
181		nx := ir.NewIndexExpr(base.Pos, np, ni)
182		nx.SetBounded(true)
183		na := typecheck.NodAddr(nx)
184		call.Args.Append(na)
185		call.Args.Append(nh)
186		loop.Body.Append(ir.NewAssignStmt(base.Pos, nh, call))
187
188		fn.Body.Append(loop)
189
190	case types.TSTRUCT:
191		// Walk the struct using memhash for runs of AMEM
192		// and calling specific hash functions for the others.
193		for i, fields := 0, t.Fields(); i < len(fields); {
194			f := fields[i]
195
196			// Skip blank fields.
197			if f.Sym.IsBlank() {
198				i++
199				continue
200			}
201
202			// Hash non-memory fields with appropriate hash function.
203			if !compare.IsRegularMemory(f.Type) {
204				hashel := hashfor(f.Type)
205				call := ir.NewCallExpr(base.Pos, ir.OCALL, hashel, nil)
206				na := typecheck.NodAddr(typecheck.DotField(base.Pos, np, i))
207				call.Args.Append(na)
208				call.Args.Append(nh)
209				fn.Body.Append(ir.NewAssignStmt(base.Pos, nh, call))
210				i++
211				continue
212			}
213
214			// Otherwise, hash a maximal length run of raw memory.
215			size, next := compare.Memrun(t, i)
216
217			// h = hashel(&p.first, size, h)
218			hashel := hashmem(f.Type)
219			call := ir.NewCallExpr(base.Pos, ir.OCALL, hashel, nil)
220			na := typecheck.NodAddr(typecheck.DotField(base.Pos, np, i))
221			call.Args.Append(na)
222			call.Args.Append(nh)
223			call.Args.Append(ir.NewInt(base.Pos, size))
224			fn.Body.Append(ir.NewAssignStmt(base.Pos, nh, call))
225
226			i = next
227		}
228	}
229
230	r := ir.NewReturnStmt(base.Pos, nil)
231	r.Results.Append(nh)
232	fn.Body.Append(r)
233
234	if base.Flag.LowerR != 0 {
235		ir.DumpList("genhash body", fn.Body)
236	}
237
238	typecheck.FinishFuncBody()
239
240	fn.SetDupok(true)
241
242	ir.WithFunc(fn, func() {
243		typecheck.Stmts(fn.Body)
244	})
245
246	fn.SetNilCheckDisabled(true)
247
248	return fn
249}
250
251func runtimeHashFor(name string, t *types.Type) *ir.Name {
252	return typecheck.LookupRuntime(name, t)
253}
254
255// hashfor returns the function to compute the hash of a value of type t.
256func hashfor(t *types.Type) *ir.Name {
257	switch types.AlgType(t) {
258	case types.AMEM:
259		base.Fatalf("hashfor with AMEM type")
260	case types.AINTER:
261		return runtimeHashFor("interhash", t)
262	case types.ANILINTER:
263		return runtimeHashFor("nilinterhash", t)
264	case types.ASTRING:
265		return runtimeHashFor("strhash", t)
266	case types.AFLOAT32:
267		return runtimeHashFor("f32hash", t)
268	case types.AFLOAT64:
269		return runtimeHashFor("f64hash", t)
270	case types.ACPLX64:
271		return runtimeHashFor("c64hash", t)
272	case types.ACPLX128:
273		return runtimeHashFor("c128hash", t)
274	}
275
276	fn := hashFunc(t)
277	return fn.Nname
278}
279
280// sysClosure returns a closure which will call the
281// given runtime function (with no closed-over variables).
282func sysClosure(name string) *obj.LSym {
283	s := typecheck.LookupRuntimeVar(name + "·f")
284	if len(s.P) == 0 {
285		f := typecheck.LookupRuntimeFunc(name)
286		objw.SymPtr(s, 0, f, 0)
287		objw.Global(s, int32(types.PtrSize), obj.DUPOK|obj.RODATA)
288	}
289	return s
290}
291
292// geneq returns a symbol which is the closure used to compute
293// equality for two objects of type t.
294func geneq(t *types.Type) *obj.LSym {
295	switch AlgType(t) {
296	case types.ANOEQ, types.ANOALG:
297		// The runtime will panic if it tries to compare
298		// a type with a nil equality function.
299		return nil
300	case types.AMEM0:
301		return sysClosure("memequal0")
302	case types.AMEM8:
303		return sysClosure("memequal8")
304	case types.AMEM16:
305		return sysClosure("memequal16")
306	case types.AMEM32:
307		return sysClosure("memequal32")
308	case types.AMEM64:
309		return sysClosure("memequal64")
310	case types.AMEM128:
311		return sysClosure("memequal128")
312	case types.ASTRING:
313		return sysClosure("strequal")
314	case types.AINTER:
315		return sysClosure("interequal")
316	case types.ANILINTER:
317		return sysClosure("nilinterequal")
318	case types.AFLOAT32:
319		return sysClosure("f32equal")
320	case types.AFLOAT64:
321		return sysClosure("f64equal")
322	case types.ACPLX64:
323		return sysClosure("c64equal")
324	case types.ACPLX128:
325		return sysClosure("c128equal")
326	case types.AMEM:
327		// make equality closure. The size of the type
328		// is encoded in the closure.
329		closure := TypeLinksymLookup(fmt.Sprintf(".eqfunc%d", t.Size()))
330		if len(closure.P) != 0 {
331			return closure
332		}
333		if memequalvarlen == nil {
334			memequalvarlen = typecheck.LookupRuntimeFunc("memequal_varlen")
335		}
336		ot := 0
337		ot = objw.SymPtr(closure, ot, memequalvarlen, 0)
338		ot = objw.Uintptr(closure, ot, uint64(t.Size()))
339		objw.Global(closure, int32(ot), obj.DUPOK|obj.RODATA)
340		return closure
341	case types.ASPECIAL:
342		break
343	}
344
345	closure := TypeLinksymPrefix(".eqfunc", t)
346	if len(closure.P) > 0 { // already generated
347		return closure
348	}
349
350	if base.Flag.LowerR != 0 {
351		fmt.Printf("geneq %v\n", t)
352	}
353
354	fn := eqFunc(t)
355
356	// Generate a closure which points at the function we just generated.
357	objw.SymPtr(closure, 0, fn.Linksym(), 0)
358	objw.Global(closure, int32(types.PtrSize), obj.DUPOK|obj.RODATA)
359	return closure
360}
361
362func eqFunc(t *types.Type) *ir.Func {
363	// Autogenerate code for equality of structs and arrays.
364	sym := TypeSymPrefix(".eq", t)
365	if sym.Def != nil {
366		return sym.Def.(*ir.Name).Func
367	}
368
369	pos := base.AutogeneratedPos // less confusing than end of input
370	base.Pos = pos
371
372	// func sym(p, q *T) bool
373	fn := ir.NewFunc(pos, pos, sym, types.NewSignature(nil,
374		[]*types.Field{
375			types.NewField(pos, typecheck.Lookup("p"), types.NewPtr(t)),
376			types.NewField(pos, typecheck.Lookup("q"), types.NewPtr(t)),
377		},
378		[]*types.Field{
379			types.NewField(pos, typecheck.Lookup("r"), types.Types[types.TBOOL]),
380		},
381	))
382	sym.Def = fn.Nname
383	fn.Pragma |= ir.Noinline // TODO(mdempsky): We need to emit this during the unified frontend instead, to allow inlining.
384
385	typecheck.DeclFunc(fn)
386	np := fn.Dcl[0]
387	nq := fn.Dcl[1]
388	nr := fn.Dcl[2]
389
390	// Label to jump to if an equality test fails.
391	neq := typecheck.AutoLabel(".neq")
392
393	// We reach here only for types that have equality but
394	// cannot be handled by the standard algorithms,
395	// so t must be either an array or a struct.
396	switch t.Kind() {
397	default:
398		base.Fatalf("geneq %v", t)
399
400	case types.TARRAY:
401		nelem := t.NumElem()
402
403		// checkAll generates code to check the equality of all array elements.
404		// If unroll is greater than nelem, checkAll generates:
405		//
406		// if eq(p[0], q[0]) && eq(p[1], q[1]) && ... {
407		// } else {
408		//   goto neq
409		// }
410		//
411		// And so on.
412		//
413		// Otherwise it generates:
414		//
415		// iterateTo := nelem/unroll*unroll
416		// for i := 0; i < iterateTo; i += unroll {
417		//   if eq(p[i+0], q[i+0]) && eq(p[i+1], q[i+1]) && ... && eq(p[i+unroll-1], q[i+unroll-1]) {
418		//   } else {
419		//     goto neq
420		//   }
421		// }
422		// if eq(p[iterateTo+0], q[iterateTo+0]) && eq(p[iterateTo+1], q[iterateTo+1]) && ... {
423		// } else {
424		//    goto neq
425		// }
426		//
427		checkAll := func(unroll int64, last bool, eq func(pi, qi ir.Node) ir.Node) {
428			// checkIdx generates a node to check for equality at index i.
429			checkIdx := func(i ir.Node) ir.Node {
430				// pi := p[i]
431				pi := ir.NewIndexExpr(base.Pos, np, i)
432				pi.SetBounded(true)
433				pi.SetType(t.Elem())
434				// qi := q[i]
435				qi := ir.NewIndexExpr(base.Pos, nq, i)
436				qi.SetBounded(true)
437				qi.SetType(t.Elem())
438				return eq(pi, qi)
439			}
440
441			iterations := nelem / unroll
442			iterateTo := iterations * unroll
443			// If a loop is iterated only once, there shouldn't be any loop at all.
444			if iterations == 1 {
445				iterateTo = 0
446			}
447
448			if iterateTo > 0 {
449				// Generate an unrolled for loop.
450				// for i := 0; i < nelem/unroll*unroll; i += unroll
451				i := typecheck.TempAt(base.Pos, ir.CurFunc, types.Types[types.TINT])
452				init := ir.NewAssignStmt(base.Pos, i, ir.NewInt(base.Pos, 0))
453				cond := ir.NewBinaryExpr(base.Pos, ir.OLT, i, ir.NewInt(base.Pos, iterateTo))
454				loop := ir.NewForStmt(base.Pos, nil, cond, nil, nil, false)
455				loop.PtrInit().Append(init)
456
457				// if eq(p[i+0], q[i+0]) && eq(p[i+1], q[i+1]) && ... && eq(p[i+unroll-1], q[i+unroll-1]) {
458				// } else {
459				//   goto neq
460				// }
461				for j := int64(0); j < unroll; j++ {
462					// if check {} else { goto neq }
463					nif := ir.NewIfStmt(base.Pos, checkIdx(i), nil, nil)
464					nif.Else.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, neq))
465					loop.Body.Append(nif)
466					post := ir.NewAssignStmt(base.Pos, i, ir.NewBinaryExpr(base.Pos, ir.OADD, i, ir.NewInt(base.Pos, 1)))
467					loop.Body.Append(post)
468				}
469
470				fn.Body.Append(loop)
471
472				if nelem == iterateTo {
473					if last {
474						fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, ir.NewBool(base.Pos, true)))
475					}
476					return
477				}
478			}
479
480			// Generate remaining checks, if nelem is not a multiple of unroll.
481			if last {
482				// Do last comparison in a different manner.
483				nelem--
484			}
485			// if eq(p[iterateTo+0], q[iterateTo+0]) && eq(p[iterateTo+1], q[iterateTo+1]) && ... {
486			// } else {
487			//    goto neq
488			// }
489			for j := iterateTo; j < nelem; j++ {
490				// if check {} else { goto neq }
491				nif := ir.NewIfStmt(base.Pos, checkIdx(ir.NewInt(base.Pos, j)), nil, nil)
492				nif.Else.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, neq))
493				fn.Body.Append(nif)
494			}
495			if last {
496				fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, checkIdx(ir.NewInt(base.Pos, nelem))))
497			}
498		}
499
500		switch t.Elem().Kind() {
501		case types.TSTRING:
502			// Do two loops. First, check that all the lengths match (cheap).
503			// Second, check that all the contents match (expensive).
504			checkAll(3, false, func(pi, qi ir.Node) ir.Node {
505				// Compare lengths.
506				eqlen, _ := compare.EqString(pi, qi)
507				return eqlen
508			})
509			checkAll(1, true, func(pi, qi ir.Node) ir.Node {
510				// Compare contents.
511				_, eqmem := compare.EqString(pi, qi)
512				return eqmem
513			})
514		case types.TFLOAT32, types.TFLOAT64:
515			checkAll(2, true, func(pi, qi ir.Node) ir.Node {
516				// p[i] == q[i]
517				return ir.NewBinaryExpr(base.Pos, ir.OEQ, pi, qi)
518			})
519		case types.TSTRUCT:
520			isCall := func(n ir.Node) bool {
521				return n.Op() == ir.OCALL || n.Op() == ir.OCALLFUNC
522			}
523			var expr ir.Node
524			var hasCallExprs bool
525			allCallExprs := true
526			and := func(cond ir.Node) {
527				if expr == nil {
528					expr = cond
529				} else {
530					expr = ir.NewLogicalExpr(base.Pos, ir.OANDAND, expr, cond)
531				}
532			}
533
534			var tmpPos src.XPos
535			pi := ir.NewIndexExpr(tmpPos, np, ir.NewInt(tmpPos, 0))
536			pi.SetBounded(true)
537			pi.SetType(t.Elem())
538			qi := ir.NewIndexExpr(tmpPos, nq, ir.NewInt(tmpPos, 0))
539			qi.SetBounded(true)
540			qi.SetType(t.Elem())
541			flatConds, canPanic := compare.EqStruct(t.Elem(), pi, qi)
542			for _, c := range flatConds {
543				if isCall(c) {
544					hasCallExprs = true
545				} else {
546					allCallExprs = false
547				}
548			}
549			if !hasCallExprs || allCallExprs || canPanic {
550				checkAll(1, true, func(pi, qi ir.Node) ir.Node {
551					// p[i] == q[i]
552					return ir.NewBinaryExpr(base.Pos, ir.OEQ, pi, qi)
553				})
554			} else {
555				checkAll(4, false, func(pi, qi ir.Node) ir.Node {
556					expr = nil
557					flatConds, _ := compare.EqStruct(t.Elem(), pi, qi)
558					if len(flatConds) == 0 {
559						return ir.NewBool(base.Pos, true)
560					}
561					for _, c := range flatConds {
562						if !isCall(c) {
563							and(c)
564						}
565					}
566					return expr
567				})
568				checkAll(2, true, func(pi, qi ir.Node) ir.Node {
569					expr = nil
570					flatConds, _ := compare.EqStruct(t.Elem(), pi, qi)
571					for _, c := range flatConds {
572						if isCall(c) {
573							and(c)
574						}
575					}
576					return expr
577				})
578			}
579		default:
580			checkAll(1, true, func(pi, qi ir.Node) ir.Node {
581				// p[i] == q[i]
582				return ir.NewBinaryExpr(base.Pos, ir.OEQ, pi, qi)
583			})
584		}
585
586	case types.TSTRUCT:
587		flatConds, _ := compare.EqStruct(t, np, nq)
588		if len(flatConds) == 0 {
589			fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, ir.NewBool(base.Pos, true)))
590		} else {
591			for _, c := range flatConds[:len(flatConds)-1] {
592				// if cond {} else { goto neq }
593				n := ir.NewIfStmt(base.Pos, c, nil, nil)
594				n.Else.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, neq))
595				fn.Body.Append(n)
596			}
597			fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, flatConds[len(flatConds)-1]))
598		}
599	}
600
601	// ret:
602	//   return
603	ret := typecheck.AutoLabel(".ret")
604	fn.Body.Append(ir.NewLabelStmt(base.Pos, ret))
605	fn.Body.Append(ir.NewReturnStmt(base.Pos, nil))
606
607	// neq:
608	//   r = false
609	//   return (or goto ret)
610	fn.Body.Append(ir.NewLabelStmt(base.Pos, neq))
611	fn.Body.Append(ir.NewAssignStmt(base.Pos, nr, ir.NewBool(base.Pos, false)))
612	if compare.EqCanPanic(t) || anyCall(fn) {
613		// Epilogue is large, so share it with the equal case.
614		fn.Body.Append(ir.NewBranchStmt(base.Pos, ir.OGOTO, ret))
615	} else {
616		// Epilogue is small, so don't bother sharing.
617		fn.Body.Append(ir.NewReturnStmt(base.Pos, nil))
618	}
619	// TODO(khr): the epilogue size detection condition above isn't perfect.
620	// We should really do a generic CL that shares epilogues across
621	// the board. See #24936.
622
623	if base.Flag.LowerR != 0 {
624		ir.DumpList("geneq body", fn.Body)
625	}
626
627	typecheck.FinishFuncBody()
628
629	fn.SetDupok(true)
630
631	ir.WithFunc(fn, func() {
632		typecheck.Stmts(fn.Body)
633	})
634
635	// Disable checknils while compiling this code.
636	// We are comparing a struct or an array,
637	// neither of which can be nil, and our comparisons
638	// are shallow.
639	fn.SetNilCheckDisabled(true)
640	return fn
641}
642
643// EqFor returns ONAME node represents type t's equal function, and a boolean
644// to indicates whether a length needs to be passed when calling the function.
645func EqFor(t *types.Type) (ir.Node, bool) {
646	switch types.AlgType(t) {
647	case types.AMEM:
648		return typecheck.LookupRuntime("memequal", t, t), true
649	case types.ASPECIAL:
650		fn := eqFunc(t)
651		return fn.Nname, false
652	}
653	base.Fatalf("EqFor %v", t)
654	return nil, false
655}
656
657func anyCall(fn *ir.Func) bool {
658	return ir.Any(fn, func(n ir.Node) bool {
659		// TODO(rsc): No methods?
660		op := n.Op()
661		return op == ir.OCALL || op == ir.OCALLFUNC
662	})
663}
664
665func hashmem(t *types.Type) ir.Node {
666	return typecheck.LookupRuntime("memhash", t)
667}
668