// Copyright 2023 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 objabi

import "sync"

// PkgSpecial indicates special build properties of a given runtime-related
// package.
type PkgSpecial struct {
	// Runtime indicates that this package is "runtime" or imported by
	// "runtime". This has several effects (which maybe should be split out):
	//
	// - Implicit allocation is disallowed.
	//
	// - Various runtime pragmas are enabled.
	//
	// - Optimizations are always enabled.
	//
	// - Checkptr is always disabled.
	//
	// This should be set for runtime and all packages it imports, and may be
	// set for additional packages.
	Runtime bool

	// NoInstrument indicates this package should not receive sanitizer
	// instrumentation. In many of these, instrumentation could cause infinite
	// recursion. This is all runtime packages, plus those that support the
	// sanitizers.
	NoInstrument bool

	// NoRaceFunc indicates functions in this package should not get
	// racefuncenter/racefuncexit instrumentation Memory accesses in these
	// packages are either uninteresting or will cause false positives.
	NoRaceFunc bool

	// AllowAsmABI indicates that assembly in this package is allowed to use ABI
	// selectors in symbol names. Generally this is needed for packages that
	// interact closely with the runtime package or have performance-critical
	// assembly.
	AllowAsmABI bool
}

var runtimePkgs = []string{
	// TODO(panjf2000): consider syncing the list inside the
	// 	isAsyncSafePoint in preempt.go based on this list?

	"runtime",

	"internal/runtime/atomic",
	"internal/runtime/exithook",
	"internal/runtime/maps",
	"internal/runtime/math",
	"internal/runtime/sys",
	"internal/runtime/syscall",

	"internal/abi",
	"internal/bytealg",
	"internal/byteorder",
	"internal/chacha8rand",
	"internal/coverage/rtcov",
	"internal/cpu",
	"internal/goarch",
	"internal/godebugs",
	"internal/goexperiment",
	"internal/goos",
	"internal/profilerecord",
	"internal/stringslite",
}

// extraNoInstrumentPkgs is the set of packages in addition to runtimePkgs that
// should have NoInstrument set.
var extraNoInstrumentPkgs = []string{
	"runtime/race",
	"runtime/msan",
	"runtime/asan",
	// We omit bytealg even though it's imported by runtime because it also
	// backs a lot of package bytes. Currently we don't have a way to omit race
	// instrumentation when used from the runtime while keeping race
	// instrumentation when used from user code. Somehow this doesn't seem to
	// cause problems, though we may be skating on thin ice. See #61204.
	"-internal/bytealg",
}

var noRaceFuncPkgs = []string{"sync", "sync/atomic", "internal/sync", "internal/runtime/atomic"}

var allowAsmABIPkgs = []string{
	"runtime",
	"reflect",
	"syscall",
	"internal/bytealg",
	"internal/chacha8rand",
	"internal/runtime/syscall",
	"runtime/internal/startlinetest",
}

// LookupPkgSpecial returns special build properties for the given package path.
func LookupPkgSpecial(pkgPath string) PkgSpecial {
	return pkgSpecialsOnce()[pkgPath]
}

var pkgSpecialsOnce = sync.OnceValue(func() map[string]PkgSpecial {
	// Construct pkgSpecials from various package lists. This lets us use
	// more flexible logic, while keeping the final map simple, and avoids
	// the init-time cost of a map.
	pkgSpecials := make(map[string]PkgSpecial)
	set := func(elt string, f func(*PkgSpecial)) {
		s := pkgSpecials[elt]
		f(&s)
		pkgSpecials[elt] = s
	}
	for _, pkg := range runtimePkgs {
		set(pkg, func(ps *PkgSpecial) { ps.Runtime = true; ps.NoInstrument = true })
	}
	for _, pkg := range extraNoInstrumentPkgs {
		if pkg[0] == '-' {
			set(pkg[1:], func(ps *PkgSpecial) { ps.NoInstrument = false })
		} else {
			set(pkg, func(ps *PkgSpecial) { ps.NoInstrument = true })
		}
	}
	for _, pkg := range noRaceFuncPkgs {
		set(pkg, func(ps *PkgSpecial) { ps.NoRaceFunc = true })
	}
	for _, pkg := range allowAsmABIPkgs {
		set(pkg, func(ps *PkgSpecial) { ps.AllowAsmABI = true })
	}
	return pkgSpecials
})
