// Copyright 2013 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 interp

import (
	"bytes"
	"fmt"
	"go/constant"
	"go/token"
	"go/types"
	"os"
	"reflect"
	"strings"
	"unsafe"

	"golang.org/x/tools/go/ssa"
	"golang.org/x/tools/internal/typeparams"
)

// If the target program panics, the interpreter panics with this type.
type targetPanic struct {
	v value
}

func (p targetPanic) String() string {
	return toString(p.v)
}

// If the target program calls exit, the interpreter panics with this type.
type exitPanic int

// constValue returns the value of the constant with the
// dynamic type tag appropriate for c.Type().
func constValue(c *ssa.Const) value {
	if c.Value == nil {
		return zero(c.Type()) // typed zero
	}
	// c is not a type parameter so it's underlying type is basic.

	if t, ok := c.Type().Underlying().(*types.Basic); ok {
		// TODO(adonovan): eliminate untyped constants from SSA form.
		switch t.Kind() {
		case types.Bool, types.UntypedBool:
			return constant.BoolVal(c.Value)
		case types.Int, types.UntypedInt:
			// Assume sizeof(int) is same on host and target.
			return int(c.Int64())
		case types.Int8:
			return int8(c.Int64())
		case types.Int16:
			return int16(c.Int64())
		case types.Int32, types.UntypedRune:
			return int32(c.Int64())
		case types.Int64:
			return c.Int64()
		case types.Uint:
			// Assume sizeof(uint) is same on host and target.
			return uint(c.Uint64())
		case types.Uint8:
			return uint8(c.Uint64())
		case types.Uint16:
			return uint16(c.Uint64())
		case types.Uint32:
			return uint32(c.Uint64())
		case types.Uint64:
			return c.Uint64()
		case types.Uintptr:
			// Assume sizeof(uintptr) is same on host and target.
			return uintptr(c.Uint64())
		case types.Float32:
			return float32(c.Float64())
		case types.Float64, types.UntypedFloat:
			return c.Float64()
		case types.Complex64:
			return complex64(c.Complex128())
		case types.Complex128, types.UntypedComplex:
			return c.Complex128()
		case types.String, types.UntypedString:
			if c.Value.Kind() == constant.String {
				return constant.StringVal(c.Value)
			}
			return string(rune(c.Int64()))
		}
	}

	panic(fmt.Sprintf("constValue: %s", c))
}

// fitsInt returns true if x fits in type int according to sizes.
func fitsInt(x int64, sizes types.Sizes) bool {
	intSize := sizes.Sizeof(types.Typ[types.Int])
	if intSize < sizes.Sizeof(types.Typ[types.Int64]) {
		maxInt := int64(1)<<((intSize*8)-1) - 1
		minInt := -int64(1) << ((intSize * 8) - 1)
		return minInt <= x && x <= maxInt
	}
	return true
}

// asInt64 converts x, which must be an integer, to an int64.
//
// Callers that need a value directly usable as an int should combine this with fitsInt().
func asInt64(x value) int64 {
	switch x := x.(type) {
	case int:
		return int64(x)
	case int8:
		return int64(x)
	case int16:
		return int64(x)
	case int32:
		return int64(x)
	case int64:
		return x
	case uint:
		return int64(x)
	case uint8:
		return int64(x)
	case uint16:
		return int64(x)
	case uint32:
		return int64(x)
	case uint64:
		return int64(x)
	case uintptr:
		return int64(x)
	}
	panic(fmt.Sprintf("cannot convert %T to int64", x))
}

// asUint64 converts x, which must be an unsigned integer, to a uint64
// suitable for use as a bitwise shift count.
func asUint64(x value) uint64 {
	switch x := x.(type) {
	case uint:
		return uint64(x)
	case uint8:
		return uint64(x)
	case uint16:
		return uint64(x)
	case uint32:
		return uint64(x)
	case uint64:
		return x
	case uintptr:
		return uint64(x)
	}
	panic(fmt.Sprintf("cannot convert %T to uint64", x))
}

// asUnsigned returns the value of x, which must be an integer type, as its equivalent unsigned type,
// and returns true if x is non-negative.
func asUnsigned(x value) (value, bool) {
	switch x := x.(type) {
	case int:
		return uint(x), x >= 0
	case int8:
		return uint8(x), x >= 0
	case int16:
		return uint16(x), x >= 0
	case int32:
		return uint32(x), x >= 0
	case int64:
		return uint64(x), x >= 0
	case uint, uint8, uint32, uint64, uintptr:
		return x, true
	}
	panic(fmt.Sprintf("cannot convert %T to unsigned", x))
}

// zero returns a new "zero" value of the specified type.
func zero(t types.Type) value {
	switch t := t.(type) {
	case *types.Basic:
		if t.Kind() == types.UntypedNil {
			panic("untyped nil has no zero value")
		}
		if t.Info()&types.IsUntyped != 0 {
			// TODO(adonovan): make it an invariant that
			// this is unreachable.  Currently some
			// constants have 'untyped' types when they
			// should be defaulted by the typechecker.
			t = types.Default(t).(*types.Basic)
		}
		switch t.Kind() {
		case types.Bool:
			return false
		case types.Int:
			return int(0)
		case types.Int8:
			return int8(0)
		case types.Int16:
			return int16(0)
		case types.Int32:
			return int32(0)
		case types.Int64:
			return int64(0)
		case types.Uint:
			return uint(0)
		case types.Uint8:
			return uint8(0)
		case types.Uint16:
			return uint16(0)
		case types.Uint32:
			return uint32(0)
		case types.Uint64:
			return uint64(0)
		case types.Uintptr:
			return uintptr(0)
		case types.Float32:
			return float32(0)
		case types.Float64:
			return float64(0)
		case types.Complex64:
			return complex64(0)
		case types.Complex128:
			return complex128(0)
		case types.String:
			return ""
		case types.UnsafePointer:
			return unsafe.Pointer(nil)
		default:
			panic(fmt.Sprint("zero for unexpected type:", t))
		}
	case *types.Pointer:
		return (*value)(nil)
	case *types.Array:
		a := make(array, t.Len())
		for i := range a {
			a[i] = zero(t.Elem())
		}
		return a
	case *types.Named:
		return zero(t.Underlying())
	case *types.Alias:
		return zero(types.Unalias(t))
	case *types.Interface:
		return iface{} // nil type, methodset and value
	case *types.Slice:
		return []value(nil)
	case *types.Struct:
		s := make(structure, t.NumFields())
		for i := range s {
			s[i] = zero(t.Field(i).Type())
		}
		return s
	case *types.Tuple:
		if t.Len() == 1 {
			return zero(t.At(0).Type())
		}
		s := make(tuple, t.Len())
		for i := range s {
			s[i] = zero(t.At(i).Type())
		}
		return s
	case *types.Chan:
		return chan value(nil)
	case *types.Map:
		if usesBuiltinMap(t.Key()) {
			return map[value]value(nil)
		}
		return (*hashmap)(nil)
	case *types.Signature:
		return (*ssa.Function)(nil)
	}
	panic(fmt.Sprint("zero: unexpected ", t))
}

// slice returns x[lo:hi:max].  Any of lo, hi and max may be nil.
func slice(x, lo, hi, max value) value {
	var Len, Cap int
	switch x := x.(type) {
	case string:
		Len = len(x)
	case []value:
		Len = len(x)
		Cap = cap(x)
	case *value: // *array
		a := (*x).(array)
		Len = len(a)
		Cap = cap(a)
	}

	l := int64(0)
	if lo != nil {
		l = asInt64(lo)
	}

	h := int64(Len)
	if hi != nil {
		h = asInt64(hi)
	}

	m := int64(Cap)
	if max != nil {
		m = asInt64(max)
	}

	switch x := x.(type) {
	case string:
		return x[l:h]
	case []value:
		return x[l:h:m]
	case *value: // *array
		a := (*x).(array)
		return []value(a)[l:h:m]
	}
	panic(fmt.Sprintf("slice: unexpected X type: %T", x))
}

// lookup returns x[idx] where x is a map.
func lookup(instr *ssa.Lookup, x, idx value) value {
	switch x := x.(type) { // map or string
	case map[value]value, *hashmap:
		var v value
		var ok bool
		switch x := x.(type) {
		case map[value]value:
			v, ok = x[idx]
		case *hashmap:
			v = x.lookup(idx.(hashable))
			ok = v != nil
		}
		if !ok {
			v = zero(instr.X.Type().Underlying().(*types.Map).Elem())
		}
		if instr.CommaOk {
			v = tuple{v, ok}
		}
		return v
	}
	panic(fmt.Sprintf("unexpected x type in Lookup: %T", x))
}

// binop implements all arithmetic and logical binary operators for
// numeric datatypes and strings.  Both operands must have identical
// dynamic type.
func binop(op token.Token, t types.Type, x, y value) value {
	switch op {
	case token.ADD:
		switch x.(type) {
		case int:
			return x.(int) + y.(int)
		case int8:
			return x.(int8) + y.(int8)
		case int16:
			return x.(int16) + y.(int16)
		case int32:
			return x.(int32) + y.(int32)
		case int64:
			return x.(int64) + y.(int64)
		case uint:
			return x.(uint) + y.(uint)
		case uint8:
			return x.(uint8) + y.(uint8)
		case uint16:
			return x.(uint16) + y.(uint16)
		case uint32:
			return x.(uint32) + y.(uint32)
		case uint64:
			return x.(uint64) + y.(uint64)
		case uintptr:
			return x.(uintptr) + y.(uintptr)
		case float32:
			return x.(float32) + y.(float32)
		case float64:
			return x.(float64) + y.(float64)
		case complex64:
			return x.(complex64) + y.(complex64)
		case complex128:
			return x.(complex128) + y.(complex128)
		case string:
			return x.(string) + y.(string)
		}

	case token.SUB:
		switch x.(type) {
		case int:
			return x.(int) - y.(int)
		case int8:
			return x.(int8) - y.(int8)
		case int16:
			return x.(int16) - y.(int16)
		case int32:
			return x.(int32) - y.(int32)
		case int64:
			return x.(int64) - y.(int64)
		case uint:
			return x.(uint) - y.(uint)
		case uint8:
			return x.(uint8) - y.(uint8)
		case uint16:
			return x.(uint16) - y.(uint16)
		case uint32:
			return x.(uint32) - y.(uint32)
		case uint64:
			return x.(uint64) - y.(uint64)
		case uintptr:
			return x.(uintptr) - y.(uintptr)
		case float32:
			return x.(float32) - y.(float32)
		case float64:
			return x.(float64) - y.(float64)
		case complex64:
			return x.(complex64) - y.(complex64)
		case complex128:
			return x.(complex128) - y.(complex128)
		}

	case token.MUL:
		switch x.(type) {
		case int:
			return x.(int) * y.(int)
		case int8:
			return x.(int8) * y.(int8)
		case int16:
			return x.(int16) * y.(int16)
		case int32:
			return x.(int32) * y.(int32)
		case int64:
			return x.(int64) * y.(int64)
		case uint:
			return x.(uint) * y.(uint)
		case uint8:
			return x.(uint8) * y.(uint8)
		case uint16:
			return x.(uint16) * y.(uint16)
		case uint32:
			return x.(uint32) * y.(uint32)
		case uint64:
			return x.(uint64) * y.(uint64)
		case uintptr:
			return x.(uintptr) * y.(uintptr)
		case float32:
			return x.(float32) * y.(float32)
		case float64:
			return x.(float64) * y.(float64)
		case complex64:
			return x.(complex64) * y.(complex64)
		case complex128:
			return x.(complex128) * y.(complex128)
		}

	case token.QUO:
		switch x.(type) {
		case int:
			return x.(int) / y.(int)
		case int8:
			return x.(int8) / y.(int8)
		case int16:
			return x.(int16) / y.(int16)
		case int32:
			return x.(int32) / y.(int32)
		case int64:
			return x.(int64) / y.(int64)
		case uint:
			return x.(uint) / y.(uint)
		case uint8:
			return x.(uint8) / y.(uint8)
		case uint16:
			return x.(uint16) / y.(uint16)
		case uint32:
			return x.(uint32) / y.(uint32)
		case uint64:
			return x.(uint64) / y.(uint64)
		case uintptr:
			return x.(uintptr) / y.(uintptr)
		case float32:
			return x.(float32) / y.(float32)
		case float64:
			return x.(float64) / y.(float64)
		case complex64:
			return x.(complex64) / y.(complex64)
		case complex128:
			return x.(complex128) / y.(complex128)
		}

	case token.REM:
		switch x.(type) {
		case int:
			return x.(int) % y.(int)
		case int8:
			return x.(int8) % y.(int8)
		case int16:
			return x.(int16) % y.(int16)
		case int32:
			return x.(int32) % y.(int32)
		case int64:
			return x.(int64) % y.(int64)
		case uint:
			return x.(uint) % y.(uint)
		case uint8:
			return x.(uint8) % y.(uint8)
		case uint16:
			return x.(uint16) % y.(uint16)
		case uint32:
			return x.(uint32) % y.(uint32)
		case uint64:
			return x.(uint64) % y.(uint64)
		case uintptr:
			return x.(uintptr) % y.(uintptr)
		}

	case token.AND:
		switch x.(type) {
		case int:
			return x.(int) & y.(int)
		case int8:
			return x.(int8) & y.(int8)
		case int16:
			return x.(int16) & y.(int16)
		case int32:
			return x.(int32) & y.(int32)
		case int64:
			return x.(int64) & y.(int64)
		case uint:
			return x.(uint) & y.(uint)
		case uint8:
			return x.(uint8) & y.(uint8)
		case uint16:
			return x.(uint16) & y.(uint16)
		case uint32:
			return x.(uint32) & y.(uint32)
		case uint64:
			return x.(uint64) & y.(uint64)
		case uintptr:
			return x.(uintptr) & y.(uintptr)
		}

	case token.OR:
		switch x.(type) {
		case int:
			return x.(int) | y.(int)
		case int8:
			return x.(int8) | y.(int8)
		case int16:
			return x.(int16) | y.(int16)
		case int32:
			return x.(int32) | y.(int32)
		case int64:
			return x.(int64) | y.(int64)
		case uint:
			return x.(uint) | y.(uint)
		case uint8:
			return x.(uint8) | y.(uint8)
		case uint16:
			return x.(uint16) | y.(uint16)
		case uint32:
			return x.(uint32) | y.(uint32)
		case uint64:
			return x.(uint64) | y.(uint64)
		case uintptr:
			return x.(uintptr) | y.(uintptr)
		}

	case token.XOR:
		switch x.(type) {
		case int:
			return x.(int) ^ y.(int)
		case int8:
			return x.(int8) ^ y.(int8)
		case int16:
			return x.(int16) ^ y.(int16)
		case int32:
			return x.(int32) ^ y.(int32)
		case int64:
			return x.(int64) ^ y.(int64)
		case uint:
			return x.(uint) ^ y.(uint)
		case uint8:
			return x.(uint8) ^ y.(uint8)
		case uint16:
			return x.(uint16) ^ y.(uint16)
		case uint32:
			return x.(uint32) ^ y.(uint32)
		case uint64:
			return x.(uint64) ^ y.(uint64)
		case uintptr:
			return x.(uintptr) ^ y.(uintptr)
		}

	case token.AND_NOT:
		switch x.(type) {
		case int:
			return x.(int) &^ y.(int)
		case int8:
			return x.(int8) &^ y.(int8)
		case int16:
			return x.(int16) &^ y.(int16)
		case int32:
			return x.(int32) &^ y.(int32)
		case int64:
			return x.(int64) &^ y.(int64)
		case uint:
			return x.(uint) &^ y.(uint)
		case uint8:
			return x.(uint8) &^ y.(uint8)
		case uint16:
			return x.(uint16) &^ y.(uint16)
		case uint32:
			return x.(uint32) &^ y.(uint32)
		case uint64:
			return x.(uint64) &^ y.(uint64)
		case uintptr:
			return x.(uintptr) &^ y.(uintptr)
		}

	case token.SHL:
		u, ok := asUnsigned(y)
		if !ok {
			panic("negative shift amount")
		}
		y := asUint64(u)
		switch x.(type) {
		case int:
			return x.(int) << y
		case int8:
			return x.(int8) << y
		case int16:
			return x.(int16) << y
		case int32:
			return x.(int32) << y
		case int64:
			return x.(int64) << y
		case uint:
			return x.(uint) << y
		case uint8:
			return x.(uint8) << y
		case uint16:
			return x.(uint16) << y
		case uint32:
			return x.(uint32) << y
		case uint64:
			return x.(uint64) << y
		case uintptr:
			return x.(uintptr) << y
		}

	case token.SHR:
		u, ok := asUnsigned(y)
		if !ok {
			panic("negative shift amount")
		}
		y := asUint64(u)
		switch x.(type) {
		case int:
			return x.(int) >> y
		case int8:
			return x.(int8) >> y
		case int16:
			return x.(int16) >> y
		case int32:
			return x.(int32) >> y
		case int64:
			return x.(int64) >> y
		case uint:
			return x.(uint) >> y
		case uint8:
			return x.(uint8) >> y
		case uint16:
			return x.(uint16) >> y
		case uint32:
			return x.(uint32) >> y
		case uint64:
			return x.(uint64) >> y
		case uintptr:
			return x.(uintptr) >> y
		}

	case token.LSS:
		switch x.(type) {
		case int:
			return x.(int) < y.(int)
		case int8:
			return x.(int8) < y.(int8)
		case int16:
			return x.(int16) < y.(int16)
		case int32:
			return x.(int32) < y.(int32)
		case int64:
			return x.(int64) < y.(int64)
		case uint:
			return x.(uint) < y.(uint)
		case uint8:
			return x.(uint8) < y.(uint8)
		case uint16:
			return x.(uint16) < y.(uint16)
		case uint32:
			return x.(uint32) < y.(uint32)
		case uint64:
			return x.(uint64) < y.(uint64)
		case uintptr:
			return x.(uintptr) < y.(uintptr)
		case float32:
			return x.(float32) < y.(float32)
		case float64:
			return x.(float64) < y.(float64)
		case string:
			return x.(string) < y.(string)
		}

	case token.LEQ:
		switch x.(type) {
		case int:
			return x.(int) <= y.(int)
		case int8:
			return x.(int8) <= y.(int8)
		case int16:
			return x.(int16) <= y.(int16)
		case int32:
			return x.(int32) <= y.(int32)
		case int64:
			return x.(int64) <= y.(int64)
		case uint:
			return x.(uint) <= y.(uint)
		case uint8:
			return x.(uint8) <= y.(uint8)
		case uint16:
			return x.(uint16) <= y.(uint16)
		case uint32:
			return x.(uint32) <= y.(uint32)
		case uint64:
			return x.(uint64) <= y.(uint64)
		case uintptr:
			return x.(uintptr) <= y.(uintptr)
		case float32:
			return x.(float32) <= y.(float32)
		case float64:
			return x.(float64) <= y.(float64)
		case string:
			return x.(string) <= y.(string)
		}

	case token.EQL:
		return eqnil(t, x, y)

	case token.NEQ:
		return !eqnil(t, x, y)

	case token.GTR:
		switch x.(type) {
		case int:
			return x.(int) > y.(int)
		case int8:
			return x.(int8) > y.(int8)
		case int16:
			return x.(int16) > y.(int16)
		case int32:
			return x.(int32) > y.(int32)
		case int64:
			return x.(int64) > y.(int64)
		case uint:
			return x.(uint) > y.(uint)
		case uint8:
			return x.(uint8) > y.(uint8)
		case uint16:
			return x.(uint16) > y.(uint16)
		case uint32:
			return x.(uint32) > y.(uint32)
		case uint64:
			return x.(uint64) > y.(uint64)
		case uintptr:
			return x.(uintptr) > y.(uintptr)
		case float32:
			return x.(float32) > y.(float32)
		case float64:
			return x.(float64) > y.(float64)
		case string:
			return x.(string) > y.(string)
		}

	case token.GEQ:
		switch x.(type) {
		case int:
			return x.(int) >= y.(int)
		case int8:
			return x.(int8) >= y.(int8)
		case int16:
			return x.(int16) >= y.(int16)
		case int32:
			return x.(int32) >= y.(int32)
		case int64:
			return x.(int64) >= y.(int64)
		case uint:
			return x.(uint) >= y.(uint)
		case uint8:
			return x.(uint8) >= y.(uint8)
		case uint16:
			return x.(uint16) >= y.(uint16)
		case uint32:
			return x.(uint32) >= y.(uint32)
		case uint64:
			return x.(uint64) >= y.(uint64)
		case uintptr:
			return x.(uintptr) >= y.(uintptr)
		case float32:
			return x.(float32) >= y.(float32)
		case float64:
			return x.(float64) >= y.(float64)
		case string:
			return x.(string) >= y.(string)
		}
	}
	panic(fmt.Sprintf("invalid binary op: %T %s %T", x, op, y))
}

// eqnil returns the comparison x == y using the equivalence relation
// appropriate for type t.
// If t is a reference type, at most one of x or y may be a nil value
// of that type.
func eqnil(t types.Type, x, y value) bool {
	switch t.Underlying().(type) {
	case *types.Map, *types.Signature, *types.Slice:
		// Since these types don't support comparison,
		// one of the operands must be a literal nil.
		switch x := x.(type) {
		case *hashmap:
			return (x != nil) == (y.(*hashmap) != nil)
		case map[value]value:
			return (x != nil) == (y.(map[value]value) != nil)
		case *ssa.Function:
			switch y := y.(type) {
			case *ssa.Function:
				return (x != nil) == (y != nil)
			case *closure:
				return true
			}
		case *closure:
			return (x != nil) == (y.(*ssa.Function) != nil)
		case []value:
			return (x != nil) == (y.([]value) != nil)
		}
		panic(fmt.Sprintf("eqnil(%s): illegal dynamic type: %T", t, x))
	}

	return equals(t, x, y)
}

func unop(instr *ssa.UnOp, x value) value {
	switch instr.Op {
	case token.ARROW: // receive
		v, ok := <-x.(chan value)
		if !ok {
			v = zero(instr.X.Type().Underlying().(*types.Chan).Elem())
		}
		if instr.CommaOk {
			v = tuple{v, ok}
		}
		return v
	case token.SUB:
		switch x := x.(type) {
		case int:
			return -x
		case int8:
			return -x
		case int16:
			return -x
		case int32:
			return -x
		case int64:
			return -x
		case uint:
			return -x
		case uint8:
			return -x
		case uint16:
			return -x
		case uint32:
			return -x
		case uint64:
			return -x
		case uintptr:
			return -x
		case float32:
			return -x
		case float64:
			return -x
		case complex64:
			return -x
		case complex128:
			return -x
		}
	case token.MUL:
		return load(typeparams.MustDeref(instr.X.Type()), x.(*value))
	case token.NOT:
		return !x.(bool)
	case token.XOR:
		switch x := x.(type) {
		case int:
			return ^x
		case int8:
			return ^x
		case int16:
			return ^x
		case int32:
			return ^x
		case int64:
			return ^x
		case uint:
			return ^x
		case uint8:
			return ^x
		case uint16:
			return ^x
		case uint32:
			return ^x
		case uint64:
			return ^x
		case uintptr:
			return ^x
		}
	}
	panic(fmt.Sprintf("invalid unary op %s %T", instr.Op, x))
}

// typeAssert checks whether dynamic type of itf is instr.AssertedType.
// It returns the extracted value on success, and panics on failure,
// unless instr.CommaOk, in which case it always returns a "value,ok" tuple.
func typeAssert(instr *ssa.TypeAssert, itf iface) value {
	var v value
	err := ""
	if itf.t == nil {
		err = fmt.Sprintf("interface conversion: interface is nil, not %s", instr.AssertedType)

	} else if idst, ok := instr.AssertedType.Underlying().(*types.Interface); ok {
		v = itf
		err = checkInterface(idst, itf)

	} else if types.Identical(itf.t, instr.AssertedType) {
		v = itf.v // extract value

	} else {
		err = fmt.Sprintf("interface conversion: interface is %s, not %s", itf.t, instr.AssertedType)
	}
	// Note: if instr.Underlying==true ever becomes reachable from interp check that
	// types.Identical(itf.t.Underlying(), instr.AssertedType)

	if err != "" {
		if !instr.CommaOk {
			panic(err)
		}
		return tuple{zero(instr.AssertedType), false}
	}
	if instr.CommaOk {
		return tuple{v, true}
	}
	return v
}

// This variable is no longer used but remains to prevent build breakage.
var CapturedOutput *bytes.Buffer

// callBuiltin interprets a call to builtin fn with arguments args,
// returning its result.
func callBuiltin(caller *frame, fn *ssa.Builtin, args []value) value {
	switch fn.Name() {
	case "append":
		if len(args) == 1 {
			return args[0]
		}
		if s, ok := args[1].(string); ok {
			// append([]byte, ...string) []byte
			arg0 := args[0].([]value)
			for i := 0; i < len(s); i++ {
				arg0 = append(arg0, s[i])
			}
			return arg0
		}
		// append([]T, ...[]T) []T
		return append(args[0].([]value), args[1].([]value)...)

	case "copy": // copy([]T, []T) int or copy([]byte, string) int
		src := args[1]
		if _, ok := src.(string); ok {
			params := fn.Type().(*types.Signature).Params()
			src = conv(params.At(0).Type(), params.At(1).Type(), src)
		}
		return copy(args[0].([]value), src.([]value))

	case "close": // close(chan T)
		close(args[0].(chan value))
		return nil

	case "delete": // delete(map[K]value, K)
		switch m := args[0].(type) {
		case map[value]value:
			delete(m, args[1])
		case *hashmap:
			m.delete(args[1].(hashable))
		default:
			panic(fmt.Sprintf("illegal map type: %T", m))
		}
		return nil

	case "print", "println": // print(any, ...)
		ln := fn.Name() == "println"
		var buf bytes.Buffer
		for i, arg := range args {
			if i > 0 && ln {
				buf.WriteRune(' ')
			}
			buf.WriteString(toString(arg))
		}
		if ln {
			buf.WriteRune('\n')
		}
		os.Stderr.Write(buf.Bytes())
		return nil

	case "len":
		switch x := args[0].(type) {
		case string:
			return len(x)
		case array:
			return len(x)
		case *value:
			return len((*x).(array))
		case []value:
			return len(x)
		case map[value]value:
			return len(x)
		case *hashmap:
			return x.len()
		case chan value:
			return len(x)
		default:
			panic(fmt.Sprintf("len: illegal operand: %T", x))
		}

	case "cap":
		switch x := args[0].(type) {
		case array:
			return cap(x)
		case *value:
			return cap((*x).(array))
		case []value:
			return cap(x)
		case chan value:
			return cap(x)
		default:
			panic(fmt.Sprintf("cap: illegal operand: %T", x))
		}

	case "min":
		return foldLeft(min, args)
	case "max":
		return foldLeft(max, args)

	case "real":
		switch c := args[0].(type) {
		case complex64:
			return real(c)
		case complex128:
			return real(c)
		default:
			panic(fmt.Sprintf("real: illegal operand: %T", c))
		}

	case "imag":
		switch c := args[0].(type) {
		case complex64:
			return imag(c)
		case complex128:
			return imag(c)
		default:
			panic(fmt.Sprintf("imag: illegal operand: %T", c))
		}

	case "complex":
		switch f := args[0].(type) {
		case float32:
			return complex(f, args[1].(float32))
		case float64:
			return complex(f, args[1].(float64))
		default:
			panic(fmt.Sprintf("complex: illegal operand: %T", f))
		}

	case "panic":
		// ssa.Panic handles most cases; this is only for "go
		// panic" or "defer panic".
		panic(targetPanic{args[0]})

	case "recover":
		return doRecover(caller)

	case "ssa:wrapnilchk":
		recv := args[0]
		if recv.(*value) == nil {
			recvType := args[1]
			methodName := args[2]
			panic(fmt.Sprintf("value method (%s).%s called using nil *%s pointer",
				recvType, methodName, recvType))
		}
		return recv

	case "ssa:deferstack":
		return &caller.defers
	}

	panic("unknown built-in: " + fn.Name())
}

func rangeIter(x value) iter {
	switch x := x.(type) {
	case map[value]value:
		return &mapIter{iter: reflect.ValueOf(x).MapRange()}
	case *hashmap:
		return &hashmapIter{iter: reflect.ValueOf(x.entries()).MapRange()}
	case string:
		return &stringIter{Reader: strings.NewReader(x)}
	}
	panic(fmt.Sprintf("cannot range over %T", x))
}

// widen widens a basic typed value x to the widest type of its
// category, one of:
//
//	bool, int64, uint64, float64, complex128, string.
//
// This is inefficient but reduces the size of the cross-product of
// cases we have to consider.
func widen(x value) value {
	switch y := x.(type) {
	case bool, int64, uint64, float64, complex128, string, unsafe.Pointer:
		return x
	case int:
		return int64(y)
	case int8:
		return int64(y)
	case int16:
		return int64(y)
	case int32:
		return int64(y)
	case uint:
		return uint64(y)
	case uint8:
		return uint64(y)
	case uint16:
		return uint64(y)
	case uint32:
		return uint64(y)
	case uintptr:
		return uint64(y)
	case float32:
		return float64(y)
	case complex64:
		return complex128(y)
	}
	panic(fmt.Sprintf("cannot widen %T", x))
}

// conv converts the value x of type t_src to type t_dst and returns
// the result.
// Possible cases are described with the ssa.Convert operator.
func conv(t_dst, t_src types.Type, x value) value {
	ut_src := t_src.Underlying()
	ut_dst := t_dst.Underlying()

	// Destination type is not an "untyped" type.
	if b, ok := ut_dst.(*types.Basic); ok && b.Info()&types.IsUntyped != 0 {
		panic("oops: conversion to 'untyped' type: " + b.String())
	}

	// Nor is it an interface type.
	if _, ok := ut_dst.(*types.Interface); ok {
		if _, ok := ut_src.(*types.Interface); ok {
			panic("oops: Convert should be ChangeInterface")
		} else {
			panic("oops: Convert should be MakeInterface")
		}
	}

	// Remaining conversions:
	//    + untyped string/number/bool constant to a specific
	//      representation.
	//    + conversions between non-complex numeric types.
	//    + conversions between complex numeric types.
	//    + integer/[]byte/[]rune -> string.
	//    + string -> []byte/[]rune.
	//
	// All are treated the same: first we extract the value to the
	// widest representation (int64, uint64, float64, complex128,
	// or string), then we convert it to the desired type.

	switch ut_src := ut_src.(type) {
	case *types.Pointer:
		switch ut_dst := ut_dst.(type) {
		case *types.Basic:
			// *value to unsafe.Pointer?
			if ut_dst.Kind() == types.UnsafePointer {
				return unsafe.Pointer(x.(*value))
			}
		}

	case *types.Slice:
		// []byte or []rune -> string
		switch ut_src.Elem().Underlying().(*types.Basic).Kind() {
		case types.Byte:
			x := x.([]value)
			b := make([]byte, 0, len(x))
			for i := range x {
				b = append(b, x[i].(byte))
			}
			return string(b)

		case types.Rune:
			x := x.([]value)
			r := make([]rune, 0, len(x))
			for i := range x {
				r = append(r, x[i].(rune))
			}
			return string(r)
		}

	case *types.Basic:
		x = widen(x)

		// integer -> string?
		if ut_src.Info()&types.IsInteger != 0 {
			if ut_dst, ok := ut_dst.(*types.Basic); ok && ut_dst.Kind() == types.String {
				return fmt.Sprintf("%c", x)
			}
		}

		// string -> []rune, []byte or string?
		if s, ok := x.(string); ok {
			switch ut_dst := ut_dst.(type) {
			case *types.Slice:
				var res []value
				switch ut_dst.Elem().Underlying().(*types.Basic).Kind() {
				case types.Rune:
					for _, r := range []rune(s) {
						res = append(res, r)
					}
					return res
				case types.Byte:
					for _, b := range []byte(s) {
						res = append(res, b)
					}
					return res
				}
			case *types.Basic:
				if ut_dst.Kind() == types.String {
					return x.(string)
				}
			}
			break // fail: no other conversions for string
		}

		// unsafe.Pointer -> *value
		if ut_src.Kind() == types.UnsafePointer {
			// TODO(adonovan): this is wrong and cannot
			// really be fixed with the current design.
			//
			// return (*value)(x.(unsafe.Pointer))
			// creates a new pointer of a different
			// type but the underlying interface value
			// knows its "true" type and so cannot be
			// meaningfully used through the new pointer.
			//
			// To make this work, the interpreter needs to
			// simulate the memory layout of a real
			// compiled implementation.
			//
			// To at least preserve type-safety, we'll
			// just return the zero value of the
			// destination type.
			return zero(t_dst)
		}

		// Conversions between complex numeric types?
		if ut_src.Info()&types.IsComplex != 0 {
			switch ut_dst.(*types.Basic).Kind() {
			case types.Complex64:
				return complex64(x.(complex128))
			case types.Complex128:
				return x.(complex128)
			}
			break // fail: no other conversions for complex
		}

		// Conversions between non-complex numeric types?
		if ut_src.Info()&types.IsNumeric != 0 {
			kind := ut_dst.(*types.Basic).Kind()
			switch x := x.(type) {
			case int64: // signed integer -> numeric?
				switch kind {
				case types.Int:
					return int(x)
				case types.Int8:
					return int8(x)
				case types.Int16:
					return int16(x)
				case types.Int32:
					return int32(x)
				case types.Int64:
					return int64(x)
				case types.Uint:
					return uint(x)
				case types.Uint8:
					return uint8(x)
				case types.Uint16:
					return uint16(x)
				case types.Uint32:
					return uint32(x)
				case types.Uint64:
					return uint64(x)
				case types.Uintptr:
					return uintptr(x)
				case types.Float32:
					return float32(x)
				case types.Float64:
					return float64(x)
				}

			case uint64: // unsigned integer -> numeric?
				switch kind {
				case types.Int:
					return int(x)
				case types.Int8:
					return int8(x)
				case types.Int16:
					return int16(x)
				case types.Int32:
					return int32(x)
				case types.Int64:
					return int64(x)
				case types.Uint:
					return uint(x)
				case types.Uint8:
					return uint8(x)
				case types.Uint16:
					return uint16(x)
				case types.Uint32:
					return uint32(x)
				case types.Uint64:
					return uint64(x)
				case types.Uintptr:
					return uintptr(x)
				case types.Float32:
					return float32(x)
				case types.Float64:
					return float64(x)
				}

			case float64: // floating point -> numeric?
				switch kind {
				case types.Int:
					return int(x)
				case types.Int8:
					return int8(x)
				case types.Int16:
					return int16(x)
				case types.Int32:
					return int32(x)
				case types.Int64:
					return int64(x)
				case types.Uint:
					return uint(x)
				case types.Uint8:
					return uint8(x)
				case types.Uint16:
					return uint16(x)
				case types.Uint32:
					return uint32(x)
				case types.Uint64:
					return uint64(x)
				case types.Uintptr:
					return uintptr(x)
				case types.Float32:
					return float32(x)
				case types.Float64:
					return float64(x)
				}
			}
		}
	}

	panic(fmt.Sprintf("unsupported conversion: %s  -> %s, dynamic type %T", t_src, t_dst, x))
}

// sliceToArrayPointer converts the value x of type slice to type t_dst
// a pointer to array and returns the result.
func sliceToArrayPointer(t_dst, t_src types.Type, x value) value {
	if _, ok := t_src.Underlying().(*types.Slice); ok {
		if ptr, ok := t_dst.Underlying().(*types.Pointer); ok {
			if arr, ok := ptr.Elem().Underlying().(*types.Array); ok {
				x := x.([]value)
				if arr.Len() > int64(len(x)) {
					panic("array length is greater than slice length")
				}
				if x == nil {
					return zero(t_dst)
				}
				v := value(array(x[:arr.Len()]))
				return &v
			}
		}
	}

	panic(fmt.Sprintf("unsupported conversion: %s  -> %s, dynamic type %T", t_src, t_dst, x))
}

// checkInterface checks that the method set of x implements the
// interface itype.
// On success it returns "", on failure, an error message.
func checkInterface(itype *types.Interface, x iface) string {
	if meth, _ := types.MissingMethod(x.t, itype, true); meth != nil {
		return fmt.Sprintf("interface conversion: %v is not %v: missing method %s",
			x.t, itype, meth.Name())
	}
	return "" // ok
}

func foldLeft(op func(value, value) value, args []value) value {
	x := args[0]
	for _, arg := range args[1:] {
		x = op(x, arg)
	}
	return x
}

func min(x, y value) value {
	switch x := x.(type) {
	case float32:
		return fmin(x, y.(float32))
	case float64:
		return fmin(x, y.(float64))
	}

	// return (y < x) ? y : x
	if binop(token.LSS, nil, y, x).(bool) {
		return y
	}
	return x
}

func max(x, y value) value {
	switch x := x.(type) {
	case float32:
		return fmax(x, y.(float32))
	case float64:
		return fmax(x, y.(float64))
	}

	// return (y > x) ? y : x
	if binop(token.GTR, nil, y, x).(bool) {
		return y
	}
	return x
}

// copied from $GOROOT/src/runtime/minmax.go

type floaty interface{ ~float32 | ~float64 }

func fmin[F floaty](x, y F) F {
	if y != y || y < x {
		return y
	}
	if x != x || x < y || x != 0 {
		return x
	}
	// x and y are both ±0
	// if either is -0, return -0; else return +0
	return forbits(x, y)
}

func fmax[F floaty](x, y F) F {
	if y != y || y > x {
		return y
	}
	if x != x || x > y || x != 0 {
		return x
	}
	// x and y are both ±0
	// if both are -0, return -0; else return +0
	return fandbits(x, y)
}

func forbits[F floaty](x, y F) F {
	switch unsafe.Sizeof(x) {
	case 4:
		*(*uint32)(unsafe.Pointer(&x)) |= *(*uint32)(unsafe.Pointer(&y))
	case 8:
		*(*uint64)(unsafe.Pointer(&x)) |= *(*uint64)(unsafe.Pointer(&y))
	}
	return x
}

func fandbits[F floaty](x, y F) F {
	switch unsafe.Sizeof(x) {
	case 4:
		*(*uint32)(unsafe.Pointer(&x)) &= *(*uint32)(unsafe.Pointer(&y))
	case 8:
		*(*uint64)(unsafe.Pointer(&x)) &= *(*uint64)(unsafe.Pointer(&y))
	}
	return x
}
