// Copyright 2018 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.

// Indexed package import.
// See iexport.go for the export data format.

package gc

import (
	"cmd/compile/internal/types"
	"cmd/internal/bio"
	"cmd/internal/goobj"
	"cmd/internal/obj"
	"cmd/internal/src"
	"encoding/binary"
	"fmt"
	"io"
	"math/big"
	"os"
	"strings"
)

// An iimporterAndOffset identifies an importer and an offset within
// its data section.
type iimporterAndOffset struct {
	p   *iimporter
	off uint64
}

var (
	// declImporter maps from imported identifiers to an importer
	// and offset where that identifier's declaration can be read.
	declImporter = map[*types.Sym]iimporterAndOffset{}

	// inlineImporter is like declImporter, but for inline bodies
	// for function and method symbols.
	inlineImporter = map[*types.Sym]iimporterAndOffset{}
)

func expandDecl(n *Node) {
	if n.Op != ONONAME {
		return
	}

	r := importReaderFor(n, declImporter)
	if r == nil {
		// Can happen if user tries to reference an undeclared name.
		return
	}

	r.doDecl(n)
}

func expandInline(fn *Node) {
	if fn.Func.Inl.Body != nil {
		return
	}

	r := importReaderFor(fn, inlineImporter)
	if r == nil {
		Fatalf("missing import reader for %v", fn)
	}

	r.doInline(fn)
}

func importReaderFor(n *Node, importers map[*types.Sym]iimporterAndOffset) *importReader {
	x, ok := importers[n.Sym]
	if !ok {
		return nil
	}

	return x.p.newReader(x.off, n.Sym.Pkg)
}

type intReader struct {
	*bio.Reader
	pkg *types.Pkg
}

func (r *intReader) int64() int64 {
	i, err := binary.ReadVarint(r.Reader)
	if err != nil {
		yyerror("import %q: read error: %v", r.pkg.Path, err)
		errorexit()
	}
	return i
}

func (r *intReader) uint64() uint64 {
	i, err := binary.ReadUvarint(r.Reader)
	if err != nil {
		yyerror("import %q: read error: %v", r.pkg.Path, err)
		errorexit()
	}
	return i
}

func iimport(pkg *types.Pkg, in *bio.Reader) (fingerprint goobj.FingerprintType) {
	ir := &intReader{in, pkg}

	version := ir.uint64()
	if version != iexportVersion {
		yyerror("import %q: unknown export format version %d", pkg.Path, version)
		errorexit()
	}

	sLen := ir.uint64()
	dLen := ir.uint64()

	// Map string (and data) section into memory as a single large
	// string. This reduces heap fragmentation and allows
	// returning individual substrings very efficiently.
	data, err := mapFile(in.File(), in.Offset(), int64(sLen+dLen))
	if err != nil {
		yyerror("import %q: mapping input: %v", pkg.Path, err)
		errorexit()
	}
	stringData := data[:sLen]
	declData := data[sLen:]

	in.MustSeek(int64(sLen+dLen), os.SEEK_CUR)

	p := &iimporter{
		ipkg: pkg,

		pkgCache:     map[uint64]*types.Pkg{},
		posBaseCache: map[uint64]*src.PosBase{},
		typCache:     map[uint64]*types.Type{},

		stringData: stringData,
		declData:   declData,
	}

	for i, pt := range predeclared() {
		p.typCache[uint64(i)] = pt
	}

	// Declaration index.
	for nPkgs := ir.uint64(); nPkgs > 0; nPkgs-- {
		pkg := p.pkgAt(ir.uint64())
		pkgName := p.stringAt(ir.uint64())
		pkgHeight := int(ir.uint64())
		if pkg.Name == "" {
			pkg.Name = pkgName
			pkg.Height = pkgHeight
			numImport[pkgName]++

			// TODO(mdempsky): This belongs somewhere else.
			pkg.Lookup("_").Def = asTypesNode(nblank)
		} else {
			if pkg.Name != pkgName {
				Fatalf("conflicting package names %v and %v for path %q", pkg.Name, pkgName, pkg.Path)
			}
			if pkg.Height != pkgHeight {
				Fatalf("conflicting package heights %v and %v for path %q", pkg.Height, pkgHeight, pkg.Path)
			}
		}

		for nSyms := ir.uint64(); nSyms > 0; nSyms-- {
			s := pkg.Lookup(p.stringAt(ir.uint64()))
			off := ir.uint64()

			if _, ok := declImporter[s]; ok {
				continue
			}
			declImporter[s] = iimporterAndOffset{p, off}

			// Create stub declaration. If used, this will
			// be overwritten by expandDecl.
			if s.Def != nil {
				Fatalf("unexpected definition for %v: %v", s, asNode(s.Def))
			}
			s.Def = asTypesNode(npos(src.NoXPos, dclname(s)))
		}
	}

	// Inline body index.
	for nPkgs := ir.uint64(); nPkgs > 0; nPkgs-- {
		pkg := p.pkgAt(ir.uint64())

		for nSyms := ir.uint64(); nSyms > 0; nSyms-- {
			s := pkg.Lookup(p.stringAt(ir.uint64()))
			off := ir.uint64()

			if _, ok := inlineImporter[s]; ok {
				continue
			}
			inlineImporter[s] = iimporterAndOffset{p, off}
		}
	}

	// Fingerprint.
	_, err = io.ReadFull(in, fingerprint[:])
	if err != nil {
		yyerror("import %s: error reading fingerprint", pkg.Path)
		errorexit()
	}
	return fingerprint
}

type iimporter struct {
	ipkg *types.Pkg

	pkgCache     map[uint64]*types.Pkg
	posBaseCache map[uint64]*src.PosBase
	typCache     map[uint64]*types.Type

	stringData string
	declData   string
}

func (p *iimporter) stringAt(off uint64) string {
	var x [binary.MaxVarintLen64]byte
	n := copy(x[:], p.stringData[off:])

	slen, n := binary.Uvarint(x[:n])
	if n <= 0 {
		Fatalf("varint failed")
	}
	spos := off + uint64(n)
	return p.stringData[spos : spos+slen]
}

func (p *iimporter) posBaseAt(off uint64) *src.PosBase {
	if posBase, ok := p.posBaseCache[off]; ok {
		return posBase
	}

	file := p.stringAt(off)
	posBase := src.NewFileBase(file, file)
	p.posBaseCache[off] = posBase
	return posBase
}

func (p *iimporter) pkgAt(off uint64) *types.Pkg {
	if pkg, ok := p.pkgCache[off]; ok {
		return pkg
	}

	pkg := p.ipkg
	if pkgPath := p.stringAt(off); pkgPath != "" {
		pkg = types.NewPkg(pkgPath, "")
	}
	p.pkgCache[off] = pkg
	return pkg
}

// An importReader keeps state for reading an individual imported
// object (declaration or inline body).
type importReader struct {
	strings.Reader
	p *iimporter

	currPkg    *types.Pkg
	prevBase   *src.PosBase
	prevLine   int64
	prevColumn int64
}

func (p *iimporter) newReader(off uint64, pkg *types.Pkg) *importReader {
	r := &importReader{
		p:       p,
		currPkg: pkg,
	}
	// (*strings.Reader).Reset wasn't added until Go 1.7, and we
	// need to build with Go 1.4.
	r.Reader = *strings.NewReader(p.declData[off:])
	return r
}

func (r *importReader) string() string        { return r.p.stringAt(r.uint64()) }
func (r *importReader) posBase() *src.PosBase { return r.p.posBaseAt(r.uint64()) }
func (r *importReader) pkg() *types.Pkg       { return r.p.pkgAt(r.uint64()) }

func (r *importReader) setPkg() {
	r.currPkg = r.pkg()
}

func (r *importReader) doDecl(n *Node) {
	if n.Op != ONONAME {
		Fatalf("doDecl: unexpected Op for %v: %v", n.Sym, n.Op)
	}

	tag := r.byte()
	pos := r.pos()

	switch tag {
	case 'A':
		typ := r.typ()

		importalias(r.p.ipkg, pos, n.Sym, typ)

	case 'C':
		typ, val := r.value()

		importconst(r.p.ipkg, pos, n.Sym, typ, val)

	case 'F':
		typ := r.signature(nil)

		importfunc(r.p.ipkg, pos, n.Sym, typ)
		r.funcExt(n)

	case 'T':
		// Types can be recursive. We need to setup a stub
		// declaration before recursing.
		t := importtype(r.p.ipkg, pos, n.Sym)

		// We also need to defer width calculations until
		// after the underlying type has been assigned.
		defercheckwidth()
		underlying := r.typ()
		setUnderlying(t, underlying)
		resumecheckwidth()

		if underlying.IsInterface() {
			r.typeExt(t)
			break
		}

		ms := make([]*types.Field, r.uint64())
		for i := range ms {
			mpos := r.pos()
			msym := r.ident()
			recv := r.param()
			mtyp := r.signature(recv)

			f := types.NewField()
			f.Pos = mpos
			f.Sym = msym
			f.Type = mtyp
			ms[i] = f

			m := newfuncnamel(mpos, methodSym(recv.Type, msym))
			m.Type = mtyp
			m.SetClass(PFUNC)
			// methodSym already marked m.Sym as a function.

			// (comment from parser.go)
			// inl.C's inlnode in on a dotmeth node expects to find the inlineable body as
			// (dotmeth's type).Nname.Inl, and dotmeth's type has been pulled
			// out by typecheck's lookdot as this $$.ttype. So by providing
			// this back link here we avoid special casing there.
			mtyp.SetNname(asTypesNode(m))
		}
		t.Methods().Set(ms)

		r.typeExt(t)
		for _, m := range ms {
			r.methExt(m)
		}

	case 'V':
		typ := r.typ()

		importvar(r.p.ipkg, pos, n.Sym, typ)
		r.varExt(n)

	default:
		Fatalf("unexpected tag: %v", tag)
	}
}

func (p *importReader) value() (typ *types.Type, v Val) {
	typ = p.typ()

	switch constTypeOf(typ) {
	case CTNIL:
		v.U = &NilVal{}
	case CTBOOL:
		v.U = p.bool()
	case CTSTR:
		v.U = p.string()
	case CTINT:
		x := new(Mpint)
		x.Rune = typ == types.UntypedRune
		p.mpint(&x.Val, typ)
		v.U = x
	case CTFLT:
		x := newMpflt()
		p.float(x, typ)
		v.U = x
	case CTCPLX:
		x := newMpcmplx()
		p.float(&x.Real, typ)
		p.float(&x.Imag, typ)
		v.U = x
	}
	return
}

func (p *importReader) mpint(x *big.Int, typ *types.Type) {
	signed, maxBytes := intSize(typ)

	maxSmall := 256 - maxBytes
	if signed {
		maxSmall = 256 - 2*maxBytes
	}
	if maxBytes == 1 {
		maxSmall = 256
	}

	n, _ := p.ReadByte()
	if uint(n) < maxSmall {
		v := int64(n)
		if signed {
			v >>= 1
			if n&1 != 0 {
				v = ^v
			}
		}
		x.SetInt64(v)
		return
	}

	v := -n
	if signed {
		v = -(n &^ 1) >> 1
	}
	if v < 1 || uint(v) > maxBytes {
		Fatalf("weird decoding: %v, %v => %v", n, signed, v)
	}
	b := make([]byte, v)
	p.Read(b)
	x.SetBytes(b)
	if signed && n&1 != 0 {
		x.Neg(x)
	}
}

func (p *importReader) float(x *Mpflt, typ *types.Type) {
	var mant big.Int
	p.mpint(&mant, typ)
	m := x.Val.SetInt(&mant)
	if m.Sign() == 0 {
		return
	}
	m.SetMantExp(m, int(p.int64()))
}

func (r *importReader) ident() *types.Sym {
	name := r.string()
	if name == "" {
		return nil
	}
	pkg := r.currPkg
	if types.IsExported(name) {
		pkg = localpkg
	}
	return pkg.Lookup(name)
}

func (r *importReader) qualifiedIdent() *types.Sym {
	name := r.string()
	pkg := r.pkg()
	return pkg.Lookup(name)
}

func (r *importReader) pos() src.XPos {
	delta := r.int64()
	r.prevColumn += delta >> 1
	if delta&1 != 0 {
		delta = r.int64()
		r.prevLine += delta >> 1
		if delta&1 != 0 {
			r.prevBase = r.posBase()
		}
	}

	if (r.prevBase == nil || r.prevBase.AbsFilename() == "") && r.prevLine == 0 && r.prevColumn == 0 {
		// TODO(mdempsky): Remove once we reliably write
		// position information for all nodes.
		return src.NoXPos
	}

	if r.prevBase == nil {
		Fatalf("missing posbase")
	}
	pos := src.MakePos(r.prevBase, uint(r.prevLine), uint(r.prevColumn))
	return Ctxt.PosTable.XPos(pos)
}

func (r *importReader) typ() *types.Type {
	return r.p.typAt(r.uint64())
}

func (p *iimporter) typAt(off uint64) *types.Type {
	t, ok := p.typCache[off]
	if !ok {
		if off < predeclReserved {
			Fatalf("predeclared type missing from cache: %d", off)
		}
		t = p.newReader(off-predeclReserved, nil).typ1()
		p.typCache[off] = t
	}
	return t
}

func (r *importReader) typ1() *types.Type {
	switch k := r.kind(); k {
	default:
		Fatalf("unexpected kind tag in %q: %v", r.p.ipkg.Path, k)
		return nil

	case definedType:
		// We might be called from within doInline, in which
		// case Sym.Def can point to declared parameters
		// instead of the top-level types. Also, we don't
		// support inlining functions with local defined
		// types. Therefore, this must be a package-scope
		// type.
		n := asNode(r.qualifiedIdent().PkgDef())
		if n.Op == ONONAME {
			expandDecl(n)
		}
		if n.Op != OTYPE {
			Fatalf("expected OTYPE, got %v: %v, %v", n.Op, n.Sym, n)
		}
		return n.Type
	case pointerType:
		return types.NewPtr(r.typ())
	case sliceType:
		return types.NewSlice(r.typ())
	case arrayType:
		n := r.uint64()
		return types.NewArray(r.typ(), int64(n))
	case chanType:
		dir := types.ChanDir(r.uint64())
		return types.NewChan(r.typ(), dir)
	case mapType:
		return types.NewMap(r.typ(), r.typ())

	case signatureType:
		r.setPkg()
		return r.signature(nil)

	case structType:
		r.setPkg()

		fs := make([]*types.Field, r.uint64())
		for i := range fs {
			pos := r.pos()
			sym := r.ident()
			typ := r.typ()
			emb := r.bool()
			note := r.string()

			f := types.NewField()
			f.Pos = pos
			f.Sym = sym
			f.Type = typ
			if emb {
				f.Embedded = 1
			}
			f.Note = note
			fs[i] = f
		}

		t := types.New(TSTRUCT)
		t.SetPkg(r.currPkg)
		t.SetFields(fs)
		return t

	case interfaceType:
		r.setPkg()

		embeddeds := make([]*types.Field, r.uint64())
		for i := range embeddeds {
			pos := r.pos()
			typ := r.typ()

			f := types.NewField()
			f.Pos = pos
			f.Type = typ
			embeddeds[i] = f
		}

		methods := make([]*types.Field, r.uint64())
		for i := range methods {
			pos := r.pos()
			sym := r.ident()
			typ := r.signature(fakeRecvField())

			f := types.NewField()
			f.Pos = pos
			f.Sym = sym
			f.Type = typ
			methods[i] = f
		}

		t := types.New(TINTER)
		t.SetPkg(r.currPkg)
		t.SetInterface(append(embeddeds, methods...))

		// Ensure we expand the interface in the frontend (#25055).
		checkwidth(t)
		return t
	}
}

func (r *importReader) kind() itag {
	return itag(r.uint64())
}

func (r *importReader) signature(recv *types.Field) *types.Type {
	params := r.paramList()
	results := r.paramList()
	if n := len(params); n > 0 {
		params[n-1].SetIsDDD(r.bool())
	}
	t := functypefield(recv, params, results)
	t.SetPkg(r.currPkg)
	return t
}

func (r *importReader) paramList() []*types.Field {
	fs := make([]*types.Field, r.uint64())
	for i := range fs {
		fs[i] = r.param()
	}
	return fs
}

func (r *importReader) param() *types.Field {
	f := types.NewField()
	f.Pos = r.pos()
	f.Sym = r.ident()
	f.Type = r.typ()
	return f
}

func (r *importReader) bool() bool {
	return r.uint64() != 0
}

func (r *importReader) int64() int64 {
	n, err := binary.ReadVarint(r)
	if err != nil {
		Fatalf("readVarint: %v", err)
	}
	return n
}

func (r *importReader) uint64() uint64 {
	n, err := binary.ReadUvarint(r)
	if err != nil {
		Fatalf("readVarint: %v", err)
	}
	return n
}

func (r *importReader) byte() byte {
	x, err := r.ReadByte()
	if err != nil {
		Fatalf("declReader.ReadByte: %v", err)
	}
	return x
}

// Compiler-specific extensions.

func (r *importReader) varExt(n *Node) {
	r.linkname(n.Sym)
	r.symIdx(n.Sym)
}

func (r *importReader) funcExt(n *Node) {
	r.linkname(n.Sym)
	r.symIdx(n.Sym)

	// Escape analysis.
	for _, fs := range &types.RecvsParams {
		for _, f := range fs(n.Type).FieldSlice() {
			f.Note = r.string()
		}
	}

	// Inline body.
	if u := r.uint64(); u > 0 {
		n.Func.Inl = &Inline{
			Cost: int32(u - 1),
		}
		n.Func.Endlineno = r.pos()
	}
}

func (r *importReader) methExt(m *types.Field) {
	if r.bool() {
		m.SetNointerface(true)
	}
	r.funcExt(asNode(m.Type.Nname()))
}

func (r *importReader) linkname(s *types.Sym) {
	s.Linkname = r.string()
}

func (r *importReader) symIdx(s *types.Sym) {
	lsym := s.Linksym()
	idx := int32(r.int64())
	if idx != -1 {
		if s.Linkname != "" {
			Fatalf("bad index for linknamed symbol: %v %d\n", lsym, idx)
		}
		lsym.SymIdx = idx
		lsym.Set(obj.AttrIndexed, true)
	}
}

func (r *importReader) typeExt(t *types.Type) {
	t.SetNotInHeap(r.bool())
	i, pi := r.int64(), r.int64()
	if i != -1 && pi != -1 {
		typeSymIdx[t] = [2]int64{i, pi}
	}
}

// Map imported type T to the index of type descriptor symbols of T and *T,
// so we can use index to reference the symbol.
var typeSymIdx = make(map[*types.Type][2]int64)

func (r *importReader) doInline(n *Node) {
	if len(n.Func.Inl.Body) != 0 {
		Fatalf("%v already has inline body", n)
	}

	funchdr(n)
	body := r.stmtList()
	funcbody()
	if body == nil {
		//
		// Make sure empty body is not interpreted as
		// no inlineable body (see also parser.fnbody)
		// (not doing so can cause significant performance
		// degradation due to unnecessary calls to empty
		// functions).
		body = []*Node{}
	}
	n.Func.Inl.Body = body

	importlist = append(importlist, n)

	if Debug.E > 0 && Debug.m > 2 {
		if Debug.m > 3 {
			fmt.Printf("inl body for %v %#v: %+v\n", n, n.Type, asNodes(n.Func.Inl.Body))
		} else {
			fmt.Printf("inl body for %v %#v: %v\n", n, n.Type, asNodes(n.Func.Inl.Body))
		}
	}
}

// ----------------------------------------------------------------------------
// Inlined function bodies

// Approach: Read nodes and use them to create/declare the same data structures
// as done originally by the (hidden) parser by closely following the parser's
// original code. In other words, "parsing" the import data (which happens to
// be encoded in binary rather textual form) is the best way at the moment to
// re-establish the syntax tree's invariants. At some future point we might be
// able to avoid this round-about way and create the rewritten nodes directly,
// possibly avoiding a lot of duplicate work (name resolution, type checking).
//
// Refined nodes (e.g., ODOTPTR as a refinement of OXDOT) are exported as their
// unrefined nodes (since this is what the importer uses). The respective case
// entries are unreachable in the importer.

func (r *importReader) stmtList() []*Node {
	var list []*Node
	for {
		n := r.node()
		if n == nil {
			break
		}
		// OBLOCK nodes may be created when importing ODCL nodes - unpack them
		if n.Op == OBLOCK {
			list = append(list, n.List.Slice()...)
		} else {
			list = append(list, n)
		}

	}
	return list
}

func (r *importReader) caseList(sw *Node) []*Node {
	namedTypeSwitch := sw.Op == OSWITCH && sw.Left != nil && sw.Left.Op == OTYPESW && sw.Left.Left != nil

	cases := make([]*Node, r.uint64())
	for i := range cases {
		cas := nodl(r.pos(), OCASE, nil, nil)
		cas.List.Set(r.stmtList())
		if namedTypeSwitch {
			// Note: per-case variables will have distinct, dotted
			// names after import. That's okay: swt.go only needs
			// Sym for diagnostics anyway.
			caseVar := newnamel(cas.Pos, r.ident())
			declare(caseVar, dclcontext)
			cas.Rlist.Set1(caseVar)
			caseVar.Name.Defn = sw.Left
		}
		cas.Nbody.Set(r.stmtList())
		cases[i] = cas
	}
	return cases
}

func (r *importReader) exprList() []*Node {
	var list []*Node
	for {
		n := r.expr()
		if n == nil {
			break
		}
		list = append(list, n)
	}
	return list
}

func (r *importReader) expr() *Node {
	n := r.node()
	if n != nil && n.Op == OBLOCK {
		Fatalf("unexpected block node: %v", n)
	}
	return n
}

// TODO(gri) split into expr and stmt
func (r *importReader) node() *Node {
	switch op := r.op(); op {
	// expressions
	// case OPAREN:
	// 	unreachable - unpacked by exporter

	case OLITERAL:
		pos := r.pos()
		typ, val := r.value()

		n := npos(pos, nodlit(val))
		n.Type = typ
		return n

	case ONONAME:
		return mkname(r.qualifiedIdent())

	case ONAME:
		return mkname(r.ident())

	// case OPACK, ONONAME:
	// 	unreachable - should have been resolved by typechecking

	case OTYPE:
		return typenod(r.typ())

	case OTYPESW:
		n := nodl(r.pos(), OTYPESW, nil, nil)
		if s := r.ident(); s != nil {
			n.Left = npos(n.Pos, newnoname(s))
		}
		n.Right, _ = r.exprsOrNil()
		return n

	// case OTARRAY, OTMAP, OTCHAN, OTSTRUCT, OTINTER, OTFUNC:
	//      unreachable - should have been resolved by typechecking

	// case OCLOSURE:
	//	unimplemented

	// case OPTRLIT:
	//	unreachable - mapped to case OADDR below by exporter

	case OSTRUCTLIT:
		// TODO(mdempsky): Export position information for OSTRUCTKEY nodes.
		savedlineno := lineno
		lineno = r.pos()
		n := nodl(lineno, OCOMPLIT, nil, typenod(r.typ()))
		n.List.Set(r.elemList()) // special handling of field names
		lineno = savedlineno
		return n

	// case OARRAYLIT, OSLICELIT, OMAPLIT:
	// 	unreachable - mapped to case OCOMPLIT below by exporter

	case OCOMPLIT:
		n := nodl(r.pos(), OCOMPLIT, nil, typenod(r.typ()))
		n.List.Set(r.exprList())
		return n

	case OKEY:
		pos := r.pos()
		left, right := r.exprsOrNil()
		return nodl(pos, OKEY, left, right)

	// case OSTRUCTKEY:
	//	unreachable - handled in case OSTRUCTLIT by elemList

	// case OCALLPART:
	//	unreachable - mapped to case OXDOT below by exporter

	// case OXDOT, ODOT, ODOTPTR, ODOTINTER, ODOTMETH:
	// 	unreachable - mapped to case OXDOT below by exporter

	case OXDOT:
		// see parser.new_dotname
		return npos(r.pos(), nodSym(OXDOT, r.expr(), r.ident()))

	// case ODOTTYPE, ODOTTYPE2:
	// 	unreachable - mapped to case ODOTTYPE below by exporter

	case ODOTTYPE:
		n := nodl(r.pos(), ODOTTYPE, r.expr(), nil)
		n.Type = r.typ()
		return n

	// case OINDEX, OINDEXMAP, OSLICE, OSLICESTR, OSLICEARR, OSLICE3, OSLICE3ARR:
	// 	unreachable - mapped to cases below by exporter

	case OINDEX:
		return nodl(r.pos(), op, r.expr(), r.expr())

	case OSLICE, OSLICE3:
		n := nodl(r.pos(), op, r.expr(), nil)
		low, high := r.exprsOrNil()
		var max *Node
		if n.Op.IsSlice3() {
			max = r.expr()
		}
		n.SetSliceBounds(low, high, max)
		return n

	// case OCONV, OCONVIFACE, OCONVNOP, OBYTES2STR, ORUNES2STR, OSTR2BYTES, OSTR2RUNES, ORUNESTR:
	// 	unreachable - mapped to OCONV case below by exporter

	case OCONV:
		n := nodl(r.pos(), OCONV, r.expr(), nil)
		n.Type = r.typ()
		return n

	case OCOPY, OCOMPLEX, OREAL, OIMAG, OAPPEND, OCAP, OCLOSE, ODELETE, OLEN, OMAKE, ONEW, OPANIC, ORECOVER, OPRINT, OPRINTN:
		n := npos(r.pos(), builtinCall(op))
		n.List.Set(r.exprList())
		if op == OAPPEND {
			n.SetIsDDD(r.bool())
		}
		return n

	// case OCALL, OCALLFUNC, OCALLMETH, OCALLINTER, OGETG:
	// 	unreachable - mapped to OCALL case below by exporter

	case OCALL:
		n := nodl(r.pos(), OCALL, nil, nil)
		n.Ninit.Set(r.stmtList())
		n.Left = r.expr()
		n.List.Set(r.exprList())
		n.SetIsDDD(r.bool())
		return n

	case OMAKEMAP, OMAKECHAN, OMAKESLICE:
		n := npos(r.pos(), builtinCall(OMAKE))
		n.List.Append(typenod(r.typ()))
		n.List.Append(r.exprList()...)
		return n

	// unary expressions
	case OPLUS, ONEG, OADDR, OBITNOT, ODEREF, ONOT, ORECV:
		return nodl(r.pos(), op, r.expr(), nil)

	// binary expressions
	case OADD, OAND, OANDAND, OANDNOT, ODIV, OEQ, OGE, OGT, OLE, OLT,
		OLSH, OMOD, OMUL, ONE, OOR, OOROR, ORSH, OSEND, OSUB, OXOR:
		return nodl(r.pos(), op, r.expr(), r.expr())

	case OADDSTR:
		pos := r.pos()
		list := r.exprList()
		x := npos(pos, list[0])
		for _, y := range list[1:] {
			x = nodl(pos, OADD, x, y)
		}
		return x

	// --------------------------------------------------------------------
	// statements
	case ODCL:
		pos := r.pos()
		lhs := npos(pos, dclname(r.ident()))
		typ := typenod(r.typ())
		return npos(pos, liststmt(variter([]*Node{lhs}, typ, nil))) // TODO(gri) avoid list creation

	// case ODCLFIELD:
	//	unimplemented

	// case OAS, OASWB:
	// 	unreachable - mapped to OAS case below by exporter

	case OAS:
		return nodl(r.pos(), OAS, r.expr(), r.expr())

	case OASOP:
		n := nodl(r.pos(), OASOP, nil, nil)
		n.SetSubOp(r.op())
		n.Left = r.expr()
		if !r.bool() {
			n.Right = nodintconst(1)
			n.SetImplicit(true)
		} else {
			n.Right = r.expr()
		}
		return n

	// case OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV:
	// 	unreachable - mapped to OAS2 case below by exporter

	case OAS2:
		n := nodl(r.pos(), OAS2, nil, nil)
		n.List.Set(r.exprList())
		n.Rlist.Set(r.exprList())
		return n

	case ORETURN:
		n := nodl(r.pos(), ORETURN, nil, nil)
		n.List.Set(r.exprList())
		return n

	// case ORETJMP:
	// 	unreachable - generated by compiler for trampolin routines (not exported)

	case OGO, ODEFER:
		return nodl(r.pos(), op, r.expr(), nil)

	case OIF:
		n := nodl(r.pos(), OIF, nil, nil)
		n.Ninit.Set(r.stmtList())
		n.Left = r.expr()
		n.Nbody.Set(r.stmtList())
		n.Rlist.Set(r.stmtList())
		return n

	case OFOR:
		n := nodl(r.pos(), OFOR, nil, nil)
		n.Ninit.Set(r.stmtList())
		n.Left, n.Right = r.exprsOrNil()
		n.Nbody.Set(r.stmtList())
		return n

	case ORANGE:
		n := nodl(r.pos(), ORANGE, nil, nil)
		n.List.Set(r.stmtList())
		n.Right = r.expr()
		n.Nbody.Set(r.stmtList())
		return n

	case OSELECT, OSWITCH:
		n := nodl(r.pos(), op, nil, nil)
		n.Ninit.Set(r.stmtList())
		n.Left, _ = r.exprsOrNil()
		n.List.Set(r.caseList(n))
		return n

	// case OCASE:
	//	handled by caseList

	case OFALL:
		n := nodl(r.pos(), OFALL, nil, nil)
		return n

	case OBREAK, OCONTINUE:
		pos := r.pos()
		left, _ := r.exprsOrNil()
		if left != nil {
			left = newname(left.Sym)
		}
		return nodl(pos, op, left, nil)

	// case OEMPTY:
	// 	unreachable - not emitted by exporter

	case OGOTO, OLABEL:
		n := nodl(r.pos(), op, nil, nil)
		n.Sym = lookup(r.string())
		return n

	case OEND:
		return nil

	default:
		Fatalf("cannot import %v (%d) node\n"+
			"\t==> please file an issue and assign to gri@", op, int(op))
		panic("unreachable") // satisfy compiler
	}
}

func (r *importReader) op() Op {
	return Op(r.uint64())
}

func (r *importReader) elemList() []*Node {
	c := r.uint64()
	list := make([]*Node, c)
	for i := range list {
		s := r.ident()
		list[i] = nodSym(OSTRUCTKEY, r.expr(), s)
	}
	return list
}

func (r *importReader) exprsOrNil() (a, b *Node) {
	ab := r.uint64()
	if ab&1 != 0 {
		a = r.expr()
	}
	if ab&2 != 0 {
		b = r.node()
	}
	return
}
