1//===- compiler.go - IR generator entry point -----------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements the main IR generator entry point, (*Compiler).Compile.
10//
11//===----------------------------------------------------------------------===//
12
13package irgen
14
15import (
16	"bytes"
17	"go/ast"
18	"go/token"
19	"log"
20	"sort"
21	"strconv"
22
23	llgobuild "llvm.org/llgo/build"
24	"llvm.org/llgo/debug"
25	"llvm.org/llvm/bindings/go/llvm"
26
27	"llvm.org/llgo/third_party/gotools/go/gccgoimporter"
28	"llvm.org/llgo/third_party/gotools/go/importer"
29	"llvm.org/llgo/third_party/gotools/go/loader"
30	"llvm.org/llgo/third_party/gotools/go/ssa"
31	"llvm.org/llgo/third_party/gotools/go/types"
32)
33
34type Module struct {
35	llvm.Module
36	Path       string
37	ExportData []byte
38	Package    *types.Package
39	disposed   bool
40}
41
42func (m *Module) Dispose() {
43	if m.disposed {
44		return
45	}
46	m.Module.Dispose()
47	m.disposed = true
48}
49
50///////////////////////////////////////////////////////////////////////////////
51
52type CompilerOptions struct {
53	// TargetTriple is the LLVM triple for the target.
54	TargetTriple string
55
56	// GenerateDebug decides whether debug data is
57	// generated in the output module.
58	GenerateDebug bool
59
60	// DebugPrefixMaps is a list of mappings from source prefixes to
61	// replacement prefixes, to be applied in debug info.
62	DebugPrefixMaps []debug.PrefixMap
63
64	// Logger is a logger used for tracing compilation.
65	Logger *log.Logger
66
67	// DumpSSA is a debugging option that dumps each SSA function
68	// to stderr before generating code for it.
69	DumpSSA bool
70
71	// GccgoPath is the path to the gccgo binary whose libgo we read import
72	// data from. If blank, the caller is expected to supply an import
73	// path in ImportPaths.
74	GccgoPath string
75
76	// Whether to use the gccgo ABI.
77	GccgoABI bool
78
79	// ImportPaths is the list of additional import paths
80	ImportPaths []string
81
82	// SanitizerAttribute is an attribute to apply to functions to enable
83	// dynamic instrumentation using a sanitizer.
84	SanitizerAttribute llvm.Attribute
85
86	// Importer is the importer. If nil, the compiler will set this field
87	// automatically using MakeImporter().
88	Importer types.Importer
89
90	// InitMap is the init map used by Importer. If Importer is nil, the
91	// compiler will set this field automatically using MakeImporter().
92	// If Importer is non-nil, InitMap must be non-nil also.
93	InitMap map[*types.Package]gccgoimporter.InitData
94
95	// PackageCreated is a hook passed to the go/loader package via
96	// loader.Config, see the documentation for that package for more
97	// information.
98	PackageCreated func(*types.Package)
99
100	// DisableUnusedImportCheck disables the unused import check performed
101	// by go/types if set to true.
102	DisableUnusedImportCheck bool
103
104	// Packages is used by go/types as the imported package map if non-nil.
105	Packages map[string]*types.Package
106}
107
108type Compiler struct {
109	opts       CompilerOptions
110	dataLayout string
111}
112
113func NewCompiler(opts CompilerOptions) (*Compiler, error) {
114	compiler := &Compiler{opts: opts}
115	dataLayout, err := llvmDataLayout(compiler.opts.TargetTriple)
116	if err != nil {
117		return nil, err
118	}
119	compiler.dataLayout = dataLayout
120	return compiler, nil
121}
122
123func (c *Compiler) Compile(fset *token.FileSet, astFiles []*ast.File, importpath string) (m *Module, err error) {
124	target := llvm.NewTargetData(c.dataLayout)
125	compiler := &compiler{
126		CompilerOptions: c.opts,
127		dataLayout:      c.dataLayout,
128		target:          target,
129		llvmtypes:       NewLLVMTypeMap(llvm.GlobalContext(), target),
130	}
131	return compiler.compile(fset, astFiles, importpath)
132}
133
134type compiler struct {
135	CompilerOptions
136
137	module     *Module
138	dataLayout string
139	target     llvm.TargetData
140	fileset    *token.FileSet
141
142	runtime   *runtimeInterface
143	llvmtypes *llvmTypeMap
144	types     *TypeMap
145
146	debug *debug.DIBuilder
147}
148
149func (c *compiler) logf(format string, v ...interface{}) {
150	if c.Logger != nil {
151		c.Logger.Printf(format, v...)
152	}
153}
154
155func (c *compiler) addCommonFunctionAttrs(fn llvm.Value) {
156	fn.AddTargetDependentFunctionAttr("disable-tail-calls", "true")
157	fn.AddTargetDependentFunctionAttr("split-stack", "")
158	if c.SanitizerAttribute.GetEnumKind() != 0 {
159		fn.AddFunctionAttr(c.SanitizerAttribute)
160	}
161}
162
163// MakeImporter sets CompilerOptions.Importer to an appropriate importer
164// for the search paths given in CompilerOptions.ImportPaths, and sets
165// CompilerOptions.InitMap to an init map belonging to that importer.
166// If CompilerOptions.GccgoPath is non-empty, the importer will also use
167// the search paths for that gccgo installation.
168func (opts *CompilerOptions) MakeImporter() error {
169	opts.InitMap = make(map[*types.Package]gccgoimporter.InitData)
170	if opts.GccgoPath == "" {
171		paths := append(append([]string{}, opts.ImportPaths...), ".")
172		opts.Importer = gccgoimporter.GetImporter(paths, opts.InitMap)
173	} else {
174		var inst gccgoimporter.GccgoInstallation
175		err := inst.InitFromDriver(opts.GccgoPath)
176		if err != nil {
177			return err
178		}
179		opts.Importer = inst.GetImporter(opts.ImportPaths, opts.InitMap)
180	}
181	return nil
182}
183
184func (compiler *compiler) compile(fset *token.FileSet, astFiles []*ast.File, importpath string) (m *Module, err error) {
185	buildctx, err := llgobuild.ContextFromTriple(compiler.TargetTriple)
186	if err != nil {
187		return nil, err
188	}
189
190	if compiler.Importer == nil {
191		err = compiler.MakeImporter()
192		if err != nil {
193			return nil, err
194		}
195	}
196
197	impcfg := &loader.Config{
198		Fset: fset,
199		TypeChecker: types.Config{
200			Packages: compiler.Packages,
201			Import:   compiler.Importer,
202			Sizes:    compiler.llvmtypes,
203			DisableUnusedImportCheck: compiler.DisableUnusedImportCheck,
204		},
205		ImportFromBinary: true,
206		Build:            &buildctx.Context,
207		PackageCreated:   compiler.PackageCreated,
208	}
209	// If no import path is specified, then set the import
210	// path to be the same as the package's name.
211	if importpath == "" {
212		importpath = astFiles[0].Name.String()
213	}
214	impcfg.CreateFromFiles(importpath, astFiles...)
215	iprog, err := impcfg.Load()
216	if err != nil {
217		return nil, err
218	}
219	program := ssa.Create(iprog, ssa.BareInits)
220	mainPkginfo := iprog.InitialPackages()[0]
221	mainPkg := program.CreatePackage(mainPkginfo)
222
223	// Create a Module, which contains the LLVM module.
224	modulename := importpath
225	compiler.module = &Module{Module: llvm.NewModule(modulename), Path: modulename, Package: mainPkg.Object}
226	compiler.module.SetTarget(compiler.TargetTriple)
227	compiler.module.SetDataLayout(compiler.dataLayout)
228
229	// Create a new translation unit.
230	unit := newUnit(compiler, mainPkg)
231
232	// Create the runtime interface.
233	compiler.runtime, err = newRuntimeInterface(compiler.module.Module, compiler.llvmtypes)
234	if err != nil {
235		return nil, err
236	}
237
238	mainPkg.Build()
239
240	// Create a struct responsible for mapping static types to LLVM types,
241	// and to runtime/dynamic type values.
242	compiler.types = NewTypeMap(
243		mainPkg,
244		compiler.llvmtypes,
245		compiler.module.Module,
246		compiler.runtime,
247		MethodResolver(unit),
248	)
249
250	if compiler.GenerateDebug {
251		compiler.debug = debug.NewDIBuilder(
252			types.Sizes(compiler.llvmtypes),
253			compiler.module.Module,
254			impcfg.Fset,
255			compiler.DebugPrefixMaps,
256		)
257		defer compiler.debug.Destroy()
258		defer compiler.debug.Finalize()
259	}
260
261	unit.translatePackage(mainPkg)
262	compiler.processAnnotations(unit, mainPkginfo)
263
264	if importpath == "main" {
265		compiler.createInitMainFunction(mainPkg)
266	} else {
267		compiler.module.ExportData = compiler.buildExportData(mainPkg)
268	}
269
270	return compiler.module, nil
271}
272
273type byPriorityThenFunc []gccgoimporter.PackageInit
274
275func (a byPriorityThenFunc) Len() int      { return len(a) }
276func (a byPriorityThenFunc) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
277func (a byPriorityThenFunc) Less(i, j int) bool {
278	switch {
279	case a[i].Priority < a[j].Priority:
280		return true
281	case a[i].Priority > a[j].Priority:
282		return false
283	case a[i].InitFunc < a[j].InitFunc:
284		return true
285	default:
286		return false
287	}
288}
289
290func (c *compiler) buildPackageInitData(mainPkg *ssa.Package) gccgoimporter.InitData {
291	var inits []gccgoimporter.PackageInit
292	for _, imp := range mainPkg.Object.Imports() {
293		inits = append(inits, c.InitMap[imp].Inits...)
294	}
295	sort.Sort(byPriorityThenFunc(inits))
296
297	// Deduplicate init entries. We want to preserve the entry with the highest priority.
298	// Normally a package's priorities will be consistent among its dependencies, but it is
299	// possible for them to be different. For example, if a standard library test augments a
300	// package which is a dependency of 'regexp' (which is imported by every test main package)
301	// with additional dependencies, those dependencies may cause the package under test to
302	// receive a higher priority than indicated by its init clause in 'regexp'.
303	uniqinits := make([]gccgoimporter.PackageInit, len(inits))
304	uniqinitpos := len(inits)
305	uniqinitnames := make(map[string]struct{})
306	for i, _ := range inits {
307		init := inits[len(inits)-1-i]
308		if _, ok := uniqinitnames[init.InitFunc]; !ok {
309			uniqinitnames[init.InitFunc] = struct{}{}
310			uniqinitpos--
311			uniqinits[uniqinitpos] = init
312		}
313	}
314	uniqinits = uniqinits[uniqinitpos:]
315
316	ourprio := 1
317	if len(uniqinits) != 0 {
318		ourprio = uniqinits[len(uniqinits)-1].Priority + 1
319	}
320
321	if imp := mainPkg.Func("init"); imp != nil {
322		impname := c.types.mc.mangleFunctionName(imp)
323		uniqinits = append(uniqinits, gccgoimporter.PackageInit{mainPkg.Object.Name(), impname, ourprio})
324	}
325
326	return gccgoimporter.InitData{ourprio, uniqinits}
327}
328
329func (c *compiler) createInitMainFunction(mainPkg *ssa.Package) {
330	int8ptr := llvm.PointerType(c.types.ctx.Int8Type(), 0)
331	ftyp := llvm.FunctionType(llvm.VoidType(), []llvm.Type{int8ptr}, false)
332	initMain := llvm.AddFunction(c.module.Module, "__go_init_main", ftyp)
333	c.addCommonFunctionAttrs(initMain)
334	entry := llvm.AddBasicBlock(initMain, "entry")
335
336	builder := llvm.GlobalContext().NewBuilder()
337	defer builder.Dispose()
338	builder.SetInsertPointAtEnd(entry)
339
340	args := []llvm.Value{llvm.Undef(int8ptr)}
341
342	if !c.GccgoABI {
343		initfn := c.module.Module.NamedFunction("main..import")
344		if !initfn.IsNil() {
345			builder.CreateCall(initfn, args, "")
346		}
347		builder.CreateRetVoid()
348		return
349	}
350
351	initdata := c.buildPackageInitData(mainPkg)
352
353	for _, init := range initdata.Inits {
354		initfn := c.module.Module.NamedFunction(init.InitFunc)
355		if initfn.IsNil() {
356			initfn = llvm.AddFunction(c.module.Module, init.InitFunc, ftyp)
357		}
358		builder.CreateCall(initfn, args, "")
359	}
360
361	builder.CreateRetVoid()
362}
363
364func (c *compiler) buildExportData(mainPkg *ssa.Package) []byte {
365	exportData := importer.ExportData(mainPkg.Object)
366	b := bytes.NewBuffer(exportData)
367
368	b.WriteString("v1;\n")
369	if !c.GccgoABI {
370		return b.Bytes()
371	}
372
373	initdata := c.buildPackageInitData(mainPkg)
374	b.WriteString("priority ")
375	b.WriteString(strconv.Itoa(initdata.Priority))
376	b.WriteString(";\n")
377
378	if len(initdata.Inits) != 0 {
379		b.WriteString("init")
380		for _, init := range initdata.Inits {
381			b.WriteRune(' ')
382			b.WriteString(init.Name)
383			b.WriteRune(' ')
384			b.WriteString(init.InitFunc)
385			b.WriteRune(' ')
386			b.WriteString(strconv.Itoa(init.Priority))
387		}
388		b.WriteString(";\n")
389	}
390
391	return b.Bytes()
392}
393
394// vim: set ft=go :
395