// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gc

import "cmd/internal/obj"

/*
 * range
 */
func typecheckrange(n *Node) {
	var toomany int
	var why string
	var t1 *Type
	var t2 *Type
	var v1 *Node
	var v2 *Node

	// Typechecking order is important here:
	// 0. first typecheck range expression (slice/map/chan),
	//	it is evaluated only once and so logically it is not part of the loop.
	// 1. typcheck produced values,
	//	this part can declare new vars and so it must be typechecked before body,
	//	because body can contain a closure that captures the vars.
	// 2. decldepth++ to denote loop body.
	// 3. typecheck body.
	// 4. decldepth--.

	typecheck(&n.Right, Erv)

	t := n.Right.Type
	if t == nil {
		goto out
	}

	// delicate little dance.  see typecheckas2
	for ll := n.List; ll != nil; ll = ll.Next {
		if ll.N.Name == nil || ll.N.Name.Defn != n {
			typecheck(&ll.N, Erv|Easgn)
		}
	}

	if Isptr[t.Etype] && Isfixedarray(t.Type) {
		t = t.Type
	}
	n.Type = t

	toomany = 0
	switch t.Etype {
	default:
		Yyerror("cannot range over %v", Nconv(n.Right, obj.FmtLong))
		goto out

	case TARRAY:
		t1 = Types[TINT]
		t2 = t.Type

	case TMAP:
		t1 = t.Down
		t2 = t.Type

	case TCHAN:
		if t.Chan&Crecv == 0 {
			Yyerror("invalid operation: range %v (receive from send-only type %v)", n.Right, n.Right.Type)
			goto out
		}

		t1 = t.Type
		t2 = nil
		if count(n.List) == 2 {
			toomany = 1
		}

	case TSTRING:
		t1 = Types[TINT]
		t2 = runetype
	}

	if count(n.List) > 2 || toomany != 0 {
		Yyerror("too many variables in range")
	}

	v1 = nil
	if n.List != nil {
		v1 = n.List.N
	}
	v2 = nil
	if n.List != nil && n.List.Next != nil {
		v2 = n.List.Next.N
	}

	// this is not only a optimization but also a requirement in the spec.
	// "if the second iteration variable is the blank identifier, the range
	// clause is equivalent to the same clause with only the first variable
	// present."
	if isblank(v2) {
		if v1 != nil {
			n.List = list1(v1)
		}
		v2 = nil
	}

	if v1 != nil {
		if v1.Name != nil && v1.Name.Defn == n {
			v1.Type = t1
		} else if v1.Type != nil && assignop(t1, v1.Type, &why) == 0 {
			Yyerror("cannot assign type %v to %v in range%s", t1, Nconv(v1, obj.FmtLong), why)
		}
		checkassign(n, v1)
	}

	if v2 != nil {
		if v2.Name != nil && v2.Name.Defn == n {
			v2.Type = t2
		} else if v2.Type != nil && assignop(t2, v2.Type, &why) == 0 {
			Yyerror("cannot assign type %v to %v in range%s", t2, Nconv(v2, obj.FmtLong), why)
		}
		checkassign(n, v2)
	}

	// second half of dance
out:
	n.Typecheck = 1

	for ll := n.List; ll != nil; ll = ll.Next {
		if ll.N.Typecheck == 0 {
			typecheck(&ll.N, Erv|Easgn)
		}
	}

	decldepth++
	typechecklist(n.Nbody, Etop)
	decldepth--
}

func walkrange(n *Node) {
	// variable name conventions:
	//	ohv1, hv1, hv2: hidden (old) val 1, 2
	//	ha, hit: hidden aggregate, iterator
	//	hn, hp: hidden len, pointer
	//	hb: hidden bool
	//	a, v1, v2: not hidden aggregate, val 1, 2

	t := n.Type

	a := n.Right
	lno := int(setlineno(a))
	n.Right = nil

	var v1 *Node
	if n.List != nil {
		v1 = n.List.N
	}
	var v2 *Node
	if n.List != nil && n.List.Next != nil && !isblank(n.List.Next.N) {
		v2 = n.List.Next.N
	}

	// n->list has no meaning anymore, clear it
	// to avoid erroneous processing by racewalk.
	n.List = nil

	var body *NodeList
	var init *NodeList
	switch t.Etype {
	default:
		Fatal("walkrange")

		// Lower n into runtime·memclr if possible, for
	// fast zeroing of slices and arrays (issue 5373).
	// Look for instances of
	//
	// for i := range a {
	// 	a[i] = zero
	// }
	//
	// in which the evaluation of a is side-effect-free.
	case TARRAY:
		if Debug['N'] == 0 {
			if flag_race == 0 {
				if v1 != nil {
					if v2 == nil {
						if n.Nbody != nil {
							if n.Nbody.N != nil { // at least one statement in body
								if n.Nbody.Next == nil { // at most one statement in body
									tmp := n.Nbody.N // first statement of body
									if tmp.Op == OAS {
										if tmp.Left.Op == OINDEX {
											if samesafeexpr(tmp.Left.Left, a) {
												if samesafeexpr(tmp.Left.Right, v1) {
													if t.Type.Width > 0 {
														if iszero(tmp.Right) {
															// Convert to
															// if len(a) != 0 {
															// 	hp = &a[0]
															// 	hn = len(a)*sizeof(elem(a))
															// 	memclr(hp, hn)
															// 	i = len(a) - 1
															// }
															n.Op = OIF

															n.Nbody = nil
															n.Left = Nod(ONE, Nod(OLEN, a, nil), Nodintconst(0))

															// hp = &a[0]
															hp := temp(Ptrto(Types[TUINT8]))

															tmp := Nod(OINDEX, a, Nodintconst(0))
															tmp.Bounded = true
															tmp = Nod(OADDR, tmp, nil)
															tmp = Nod(OCONVNOP, tmp, nil)
															tmp.Type = Ptrto(Types[TUINT8])
															n.Nbody = list(n.Nbody, Nod(OAS, hp, tmp))

															// hn = len(a) * sizeof(elem(a))
															hn := temp(Types[TUINTPTR])

															tmp = Nod(OLEN, a, nil)
															tmp = Nod(OMUL, tmp, Nodintconst(t.Type.Width))
															tmp = conv(tmp, Types[TUINTPTR])
															n.Nbody = list(n.Nbody, Nod(OAS, hn, tmp))

															// memclr(hp, hn)
															fn := mkcall("memclr", nil, nil, hp, hn)

															n.Nbody = list(n.Nbody, fn)

															// i = len(a) - 1
															v1 = Nod(OAS, v1, Nod(OSUB, Nod(OLEN, a, nil), Nodintconst(1)))

															n.Nbody = list(n.Nbody, v1)

															typecheck(&n.Left, Erv)
															typechecklist(n.Nbody, Etop)
															walkstmt(&n)
															lineno = int32(lno)
															return
														}
													}
												}
											}
										}
									}
								}
							}
						}
					}
				}
			}
		}

		// orderstmt arranged for a copy of the array/slice variable if needed.
		ha := a

		hv1 := temp(Types[TINT])
		hn := temp(Types[TINT])
		var hp *Node

		init = list(init, Nod(OAS, hv1, nil))
		init = list(init, Nod(OAS, hn, Nod(OLEN, ha, nil)))
		if v2 != nil {
			hp = temp(Ptrto(n.Type.Type))
			tmp := Nod(OINDEX, ha, Nodintconst(0))
			tmp.Bounded = true
			init = list(init, Nod(OAS, hp, Nod(OADDR, tmp, nil)))
		}

		n.Left = Nod(OLT, hv1, hn)
		n.Right = Nod(OAS, hv1, Nod(OADD, hv1, Nodintconst(1)))
		if v1 == nil {
			body = nil
		} else if v2 == nil {
			body = list1(Nod(OAS, v1, hv1))
		} else {
			a := Nod(OAS2, nil, nil)
			a.List = list(list1(v1), v2)
			a.Rlist = list(list1(hv1), Nod(OIND, hp, nil))
			body = list1(a)

			// Advance pointer as part of increment.
			// We used to advance the pointer before executing the loop body,
			// but doing so would make the pointer point past the end of the
			// array during the final iteration, possibly causing another unrelated
			// piece of memory not to be garbage collected until the loop finished.
			// Advancing during the increment ensures that the pointer p only points
			// pass the end of the array during the final "p++; i++; if(i >= len(x)) break;",
			// after which p is dead, so it cannot confuse the collector.
			tmp := Nod(OADD, hp, Nodintconst(t.Type.Width))

			tmp.Type = hp.Type
			tmp.Typecheck = 1
			tmp.Right.Type = Types[Tptr]
			tmp.Right.Typecheck = 1
			a = Nod(OAS, hp, tmp)
			typecheck(&a, Etop)
			n.Right.Ninit = list1(a)
		}

		// orderstmt allocated the iterator for us.
	// we only use a once, so no copy needed.
	case TMAP:
		ha := a

		th := hiter(t)
		hit := prealloc[n]
		hit.Type = th
		n.Left = nil
		keyname := newname(th.Type.Sym)      // depends on layout of iterator struct.  See reflect.go:hiter
		valname := newname(th.Type.Down.Sym) // ditto

		fn := syslook("mapiterinit", 1)

		substArgTypes(fn, t.Down, t.Type, th)
		init = list(init, mkcall1(fn, nil, nil, typename(t), ha, Nod(OADDR, hit, nil)))
		n.Left = Nod(ONE, Nod(ODOT, hit, keyname), nodnil())

		fn = syslook("mapiternext", 1)
		substArgTypes(fn, th)
		n.Right = mkcall1(fn, nil, nil, Nod(OADDR, hit, nil))

		key := Nod(ODOT, hit, keyname)
		key = Nod(OIND, key, nil)
		if v1 == nil {
			body = nil
		} else if v2 == nil {
			body = list1(Nod(OAS, v1, key))
		} else {
			val := Nod(ODOT, hit, valname)
			val = Nod(OIND, val, nil)
			a := Nod(OAS2, nil, nil)
			a.List = list(list1(v1), v2)
			a.Rlist = list(list1(key), val)
			body = list1(a)
		}

		// orderstmt arranged for a copy of the channel variable.
	case TCHAN:
		ha := a

		n.Left = nil

		hv1 := temp(t.Type)
		hv1.Typecheck = 1
		if haspointers(t.Type) {
			init = list(init, Nod(OAS, hv1, nil))
		}
		hb := temp(Types[TBOOL])

		n.Left = Nod(ONE, hb, Nodbool(false))
		a := Nod(OAS2RECV, nil, nil)
		a.Typecheck = 1
		a.List = list(list1(hv1), hb)
		a.Rlist = list1(Nod(ORECV, ha, nil))
		n.Left.Ninit = list1(a)
		if v1 == nil {
			body = nil
		} else {
			body = list1(Nod(OAS, v1, hv1))
		}

		// orderstmt arranged for a copy of the string variable.
	case TSTRING:
		ha := a

		ohv1 := temp(Types[TINT])

		hv1 := temp(Types[TINT])
		init = list(init, Nod(OAS, hv1, nil))

		var a *Node
		var hv2 *Node
		if v2 == nil {
			a = Nod(OAS, hv1, mkcall("stringiter", Types[TINT], nil, ha, hv1))
		} else {
			hv2 = temp(runetype)
			a = Nod(OAS2, nil, nil)
			a.List = list(list1(hv1), hv2)
			fn := syslook("stringiter2", 0)
			a.Rlist = list1(mkcall1(fn, getoutargx(fn.Type), nil, ha, hv1))
		}

		n.Left = Nod(ONE, hv1, Nodintconst(0))
		n.Left.Ninit = list(list1(Nod(OAS, ohv1, hv1)), a)

		body = nil
		if v1 != nil {
			body = list1(Nod(OAS, v1, ohv1))
		}
		if v2 != nil {
			body = list(body, Nod(OAS, v2, hv2))
		}
	}

	n.Op = OFOR
	typechecklist(init, Etop)
	n.Ninit = concat(n.Ninit, init)
	typechecklist(n.Left.Ninit, Etop)
	typecheck(&n.Left, Erv)
	typecheck(&n.Right, Etop)
	typechecklist(body, Etop)
	n.Nbody = concat(body, n.Nbody)
	walkstmt(&n)

	lineno = int32(lno)
}
