// 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 pkginit

import (
	"cmd/compile/internal/base"
	"cmd/compile/internal/ir"
	"cmd/compile/internal/noder"
	"cmd/compile/internal/objw"
	"cmd/compile/internal/staticinit"
	"cmd/compile/internal/typecheck"
	"cmd/compile/internal/types"
	"cmd/internal/obj"
	"cmd/internal/src"
)

// MakeInit creates a synthetic init function to handle any
// package-scope initialization statements.
//
// TODO(mdempsky): Move into noder, so that the types2-based frontends
// can use Info.InitOrder instead.
func MakeInit() {
	nf := initOrder(typecheck.Target.Decls)
	if len(nf) == 0 {
		return
	}

	// Make a function that contains all the initialization statements.
	base.Pos = nf[0].Pos() // prolog/epilog gets line number of first init stmt
	initializers := typecheck.Lookup("init")
	fn := typecheck.DeclFunc(initializers, nil, nil, nil)
	for _, dcl := range typecheck.InitTodoFunc.Dcl {
		dcl.Curfn = fn
	}
	fn.Dcl = append(fn.Dcl, typecheck.InitTodoFunc.Dcl...)
	typecheck.InitTodoFunc.Dcl = nil

	// Suppress useless "can inline" diagnostics.
	// Init functions are only called dynamically.
	fn.SetInlinabilityChecked(true)

	fn.Body = nf
	typecheck.FinishFuncBody()

	typecheck.Func(fn)
	ir.WithFunc(fn, func() {
		typecheck.Stmts(nf)
	})
	typecheck.Target.Decls = append(typecheck.Target.Decls, fn)

	// Prepend to Inits, so it runs first, before any user-declared init
	// functions.
	typecheck.Target.Inits = append([]*ir.Func{fn}, typecheck.Target.Inits...)

	if typecheck.InitTodoFunc.Dcl != nil {
		// We only generate temps using InitTodoFunc if there
		// are package-scope initialization statements, so
		// something's weird if we get here.
		base.Fatalf("InitTodoFunc still has declarations")
	}
	typecheck.InitTodoFunc = nil
}

// Task makes and returns an initialization record for the package.
// See runtime/proc.go:initTask for its layout.
// The 3 tasks for initialization are:
//  1. Initialize all of the packages the current package depends on.
//  2. Initialize all the variables that have initializers.
//  3. Run any init functions.
func Task() *ir.Name {
	var deps []*obj.LSym // initTask records for packages the current package depends on
	var fns []*obj.LSym  // functions to call for package initialization

	// Find imported packages with init tasks.
	for _, pkg := range typecheck.Target.Imports {
		n := typecheck.Resolve(ir.NewIdent(base.Pos, pkg.Lookup(".inittask")))
		if n.Op() == ir.ONONAME {
			continue
		}
		if n.Op() != ir.ONAME || n.(*ir.Name).Class != ir.PEXTERN {
			base.Fatalf("bad inittask: %v", n)
		}
		deps = append(deps, n.(*ir.Name).Linksym())
	}
	if base.Flag.ASan {
		// Make an initialization function to call runtime.asanregisterglobals to register an
		// array of instrumented global variables when -asan is enabled. An instrumented global
		// variable is described by a structure.
		// See the _asan_global structure declared in src/runtime/asan/asan.go.
		//
		// func init {
		// 		var globals []_asan_global {...}
		// 		asanregisterglobals(&globals[0], len(globals))
		// }
		for _, n := range typecheck.Target.Externs {
			if canInstrumentGlobal(n) {
				name := n.Sym().Name
				InstrumentGlobalsMap[name] = n
				InstrumentGlobalsSlice = append(InstrumentGlobalsSlice, n)
			}
		}
		ni := len(InstrumentGlobalsMap)
		if ni != 0 {
			// Make an init._ function.
			base.Pos = base.AutogeneratedPos
			typecheck.DeclContext = ir.PEXTERN
			name := noder.Renameinit()
			fnInit := typecheck.DeclFunc(name, nil, nil, nil)

			// Get an array of intrumented global variables.
			globals := instrumentGlobals(fnInit)

			// Call runtime.asanregisterglobals function to poison redzones.
			// runtime.asanregisterglobals(unsafe.Pointer(&globals[0]), ni)
			asanf := typecheck.NewName(ir.Pkgs.Runtime.Lookup("asanregisterglobals"))
			ir.MarkFunc(asanf)
			asanf.SetType(types.NewSignature(types.NoPkg, nil, nil, []*types.Field{
				types.NewField(base.Pos, nil, types.Types[types.TUNSAFEPTR]),
				types.NewField(base.Pos, nil, types.Types[types.TUINTPTR]),
			}, nil))
			asancall := ir.NewCallExpr(base.Pos, ir.OCALL, asanf, nil)
			asancall.Args.Append(typecheck.ConvNop(typecheck.NodAddr(
				ir.NewIndexExpr(base.Pos, globals, ir.NewInt(0))), types.Types[types.TUNSAFEPTR]))
			asancall.Args.Append(typecheck.ConvNop(ir.NewInt(int64(ni)), types.Types[types.TUINTPTR]))

			fnInit.Body.Append(asancall)
			typecheck.FinishFuncBody()
			typecheck.Func(fnInit)
			ir.CurFunc = fnInit
			typecheck.Stmts(fnInit.Body)
			ir.CurFunc = nil

			typecheck.Target.Decls = append(typecheck.Target.Decls, fnInit)
			typecheck.Target.Inits = append(typecheck.Target.Inits, fnInit)
		}
	}

	// Record user init functions.
	for _, fn := range typecheck.Target.Inits {
		if fn.Sym().Name == "init" {
			// Synthetic init function for initialization of package-scope
			// variables. We can use staticinit to optimize away static
			// assignments.
			s := staticinit.Schedule{
				Plans: make(map[ir.Node]*staticinit.Plan),
				Temps: make(map[ir.Node]*ir.Name),
			}
			for _, n := range fn.Body {
				s.StaticInit(n)
			}
			fn.Body = s.Out
			ir.WithFunc(fn, func() {
				typecheck.Stmts(fn.Body)
			})

			if len(fn.Body) == 0 {
				fn.Body = []ir.Node{ir.NewBlockStmt(src.NoXPos, nil)}
			}
		}

		// Skip init functions with empty bodies.
		if len(fn.Body) == 1 {
			if stmt := fn.Body[0]; stmt.Op() == ir.OBLOCK && len(stmt.(*ir.BlockStmt).List) == 0 {
				continue
			}
		}
		fns = append(fns, fn.Nname.Linksym())
	}

	if len(deps) == 0 && len(fns) == 0 && types.LocalPkg.Path != "main" && types.LocalPkg.Path != "runtime" {
		return nil // nothing to initialize
	}

	// Make an .inittask structure.
	sym := typecheck.Lookup(".inittask")
	task := typecheck.NewName(sym)
	task.SetType(types.Types[types.TUINT8]) // fake type
	task.Class = ir.PEXTERN
	sym.Def = task
	lsym := task.Linksym()
	ot := 0
	ot = objw.Uintptr(lsym, ot, 0) // state: not initialized yet
	ot = objw.Uintptr(lsym, ot, uint64(len(deps)))
	ot = objw.Uintptr(lsym, ot, uint64(len(fns)))
	for _, d := range deps {
		ot = objw.SymPtr(lsym, ot, d, 0)
	}
	for _, f := range fns {
		ot = objw.SymPtr(lsym, ot, f, 0)
	}
	// An initTask has pointers, but none into the Go heap.
	// It's not quite read only, the state field must be modifiable.
	objw.Global(lsym, int32(ot), obj.NOPTR)
	return task
}
