1 package compiler
2 3 // This file manages symbols, that is, functions and globals. It reads their
4 // pragmas, determines the link name, etc.
5 6 import (
7 "fmt"
8 "go/ast"
9 "go/token"
10 "go/types"
11 "strconv"
12 "strings"
13 14 "moxie/compiler/llvmutil"
15 "moxie/goenv"
16 "moxie/loader"
17 "golang.org/x/tools/go/ssa"
18 "tinygo.org/x/go-llvm"
19 )
20 21 // functionInfo contains some information about a function or method. In
22 // particular, it contains information obtained from pragmas.
23 //
24 // The linkName value contains a valid link name, even if //go:linkname is not
25 // present.
26 type functionInfo struct {
27 wasmModule string // go:wasm-module
28 wasmName string // wasm-export-name or wasm-import-name in the IR
29 wasmExport string // go:wasmexport is defined (export is unset, this adds an exported wrapper)
30 wasmExportPos token.Pos // position of //go:wasmexport comment
31 linkName string // go:linkname, go:export - the IR function name
32 section string // go:section - object file section name
33 exported bool // go:export, CGo
34 interrupt bool // go:interrupt
35 nobounds bool // go:nobounds
36 noescape bool // go:noescape
37 variadic bool // go:variadic (CGo only)
38 inline inlineType // go:inline
39 }
40 41 type inlineType int
42 43 // How much to inline.
44 const (
45 // Default behavior. The compiler decides for itself whether any given
46 // function will be inlined. Whether any function is inlined depends on the
47 // optimization level.
48 inlineDefault inlineType = iota
49 50 // Inline hint, just like the C inline keyword (signalled using
51 // //go:inline). The compiler will be more likely to inline this function,
52 // but it is not a guarantee.
53 inlineHint
54 55 // Don't inline, just like the GCC noinline attribute. Signalled using
56 // //go:noinline.
57 inlineNone
58 )
59 60 // Values for the allockind attribute. Source:
61 // https://github.com/llvm/llvm-project/blob/release/16.x/llvm/include/llvm/IR/Attributes.h#L49
62 const (
63 allocKindAlloc = 1 << iota
64 allocKindRealloc
65 allocKindFree
66 allocKindUninitialized
67 allocKindZeroed
68 allocKindAligned
69 )
70 71 // getFunction returns the LLVM function for the given *ssa.Function, creating
72 // it if needed. It can later be filled with compilerContext.createFunction().
73 func (c *compilerContext) getFunction(fn *ssa.Function) (llvm.Type, llvm.Value) {
74 info := c.getFunctionInfo(fn)
75 llvmFn := c.mod.NamedFunction(info.linkName)
76 if !llvmFn.IsNil() {
77 return llvmFn.GlobalValueType(), llvmFn
78 }
79 80 var retType llvm.Type
81 if fn.Signature.Results() == nil {
82 retType = c.ctx.VoidType()
83 } else if fn.Signature.Results().Len() == 1 {
84 retType = c.getLLVMType(fn.Signature.Results().At(0).Type())
85 } else {
86 results := make([]llvm.Type, 0, fn.Signature.Results().Len())
87 for i := 0; i < fn.Signature.Results().Len(); i++ {
88 results = append(results, c.getLLVMType(fn.Signature.Results().At(i).Type()))
89 }
90 retType = c.ctx.StructType(results, false)
91 }
92 93 var paramInfos []paramInfo
94 for _, param := range getParams(fn.Signature) {
95 paramType := c.getLLVMType(param.Type())
96 paramFragmentInfos := c.expandFormalParamType(paramType, param.Name(), param.Type())
97 paramInfos = append(paramInfos, paramFragmentInfos...)
98 }
99 100 // Add an extra parameter as the function context. This context is used in
101 // closures and bound methods, but should be optimized away when not used.
102 if !info.exported && !strings.HasPrefix(info.linkName, "llvm.") {
103 paramInfos = append(paramInfos, paramInfo{llvmType: c.dataPtrType, name: "context", elemSize: 0})
104 }
105 106 var paramTypes []llvm.Type
107 for _, info := range paramInfos {
108 paramTypes = append(paramTypes, info.llvmType)
109 }
110 111 fnType := llvm.FunctionType(retType, paramTypes, info.variadic)
112 llvmFn = llvm.AddFunction(c.mod, info.linkName, fnType)
113 if strings.HasPrefix(c.Triple, "wasm") {
114 // C functions without prototypes like this:
115 // void foo();
116 // are actually variadic functions. However, it appears that it has been
117 // decided in WebAssembly that such prototype-less functions are not
118 // allowed in WebAssembly.
119 // In C, this can only happen when there are zero parameters, hence this
120 // check here. For more information:
121 // https://reviews.llvm.org/D48443
122 // https://github.com/WebAssembly/tool-conventions/issues/16
123 if info.variadic && len(fn.Params) == 0 {
124 attr := c.ctx.CreateStringAttribute("no-prototype", "")
125 llvmFn.AddFunctionAttr(attr)
126 }
127 }
128 c.addStandardDeclaredAttributes(llvmFn)
129 130 dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
131 nocaptureKind := llvm.AttributeKindID("nocapture")
132 for i, paramInfo := range paramInfos {
133 if paramInfo.elemSize != 0 {
134 dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, paramInfo.elemSize)
135 llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull)
136 }
137 if info.noescape && paramInfo.flags¶mIsGoParam != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind {
138 if nocaptureKind != 0 {
139 nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
140 llvmFn.AddAttributeAtIndex(i+1, nocapture)
141 }
142 }
143 if paramInfo.flags¶mIsReadonly != 0 && paramInfo.llvmType.TypeKind() == llvm.PointerTypeKind {
144 readonly := c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0)
145 llvmFn.AddAttributeAtIndex(i+1, readonly)
146 }
147 }
148 149 // Set a number of function or parameter attributes, depending on the
150 // function. These functions are runtime functions that are known to have
151 // certain attributes that might not be inferred by the compiler.
152 switch info.linkName {
153 case "abort":
154 // On *nix systems, the "abort" functuion in libc is used to handle fatal panics.
155 // Mark it as noreturn so LLVM can optimize away code.
156 llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0))
157 case "internal/abi.NoEscape":
158 if nocaptureKind != 0 {
159 llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(nocaptureKind, 0))
160 }
161 case "machine.keepAliveNoEscape", "machine.unsafeNoEscape":
162 if nocaptureKind != 0 {
163 llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(nocaptureKind, 0))
164 }
165 case "runtime.alloc":
166 // Tell the optimizer that runtime.alloc is an allocator, meaning that it
167 // returns values that are never null and never alias to an existing value.
168 for _, attrName := range []string{"noalias", "nonnull"} {
169 llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0))
170 }
171 // Add attributes to signal to LLVM that this is an allocator function.
172 // This enables a number of optimizations.
173 llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allockind"), allocKindAlloc|allocKindZeroed))
174 llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("alloc-family", "runtime.alloc"))
175 // Use a special value to indicate the first parameter:
176 // > allocsize has two integer arguments, but because they're both 32 bits, we can
177 // > pack them into one 64-bit value, at the cost of making said value
178 // > nonsensical.
179 // >
180 // > In order to do this, we need to reserve one value of the second (optional)
181 // > allocsize argument to signify "not present."
182 llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("allocsize"), 0x0000_0000_ffff_ffff))
183 case "runtime.sliceAppend":
184 if nocaptureKind != 0 {
185 llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(nocaptureKind, 0))
186 }
187 llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
188 case "runtime.sliceCopy":
189 llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("writeonly"), 0))
190 if nocaptureKind != 0 {
191 llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(nocaptureKind, 0))
192 llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(nocaptureKind, 0))
193 }
194 llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
195 case "runtime.stringFromRunes":
196 if nocaptureKind != 0 {
197 llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(nocaptureKind, 0))
198 }
199 llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
200 case "__mulsi3", "__divmodsi4", "__udivmodsi4":
201 if strings.Split(c.Triple, "-")[0] == "avr" {
202 // These functions are compiler-rt/libgcc functions that are
203 // currently implemented in Go. Assembly versions should appear in
204 // LLVM 17 hopefully. Until then, they need to be made available to
205 // the linker and the best way to do that is llvm.compiler.used.
206 // I considered adding a pragma for this, but the LLVM language
207 // reference explicitly says that this feature should not be exposed
208 // to source languages:
209 // > This is a rare construct that should only be used in rare
210 // > circumstances, and should not be exposed to source languages.
211 llvmutil.AppendToGlobal(c.mod, "llvm.compiler.used", llvmFn)
212 }
213 case "GetModuleHandleExA", "GetProcAddress", "GetSystemInfo", "GetSystemTimeAsFileTime", "LoadLibraryExW", "QueryPerformanceCounter", "QueryPerformanceFrequency", "QueryUnbiasedInterruptTime", "SetEnvironmentVariableA", "Sleep", "SystemFunction036", "VirtualAlloc":
214 // On Windows we need to use a special calling convention for some
215 // external calls.
216 if c.GOOS == "windows" && c.GOARCH == "386" {
217 llvmFn.SetFunctionCallConv(llvm.X86StdcallCallConv)
218 }
219 }
220 221 // External/exported functions may not retain pointer values.
222 // https://golang.org/cmd/cgo/#hdr-Passing_pointers
223 if info.exported {
224 if c.archFamily() == "wasm32" && len(fn.Blocks) == 0 {
225 // We need to add the wasm-import-module and the wasm-import-name
226 // attributes.
227 if info.wasmModule != "" {
228 llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-module", info.wasmModule))
229 }
230 231 llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("wasm-import-name", info.wasmName))
232 }
233 if nocaptureKind != 0 {
234 nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
235 for i, typ := range paramTypes {
236 if typ.TypeKind() == llvm.PointerTypeKind {
237 llvmFn.AddAttributeAtIndex(i+1, nocapture)
238 }
239 }
240 }
241 }
242 243 // Build the function if needed.
244 c.maybeCreateSyntheticFunction(fn, llvmFn)
245 246 return fnType, llvmFn
247 }
248 249 // If this is a synthetic function (such as a generic function or a wrapper),
250 // create it now.
251 func (c *compilerContext) maybeCreateSyntheticFunction(fn *ssa.Function, llvmFn llvm.Value) {
252 // Synthetic functions are functions that do not appear in the source code,
253 // they are artificially constructed. Usually they are wrapper functions
254 // that are not referenced anywhere except in a SSA call instruction so
255 // should be created right away.
256 // The exception is the package initializer, which does appear in the
257 // *ssa.Package members and so shouldn't be created here.
258 if fn.Synthetic == "from type information" && c.isFFIFunction(fn) {
259 c.createFFIFunction(fn, llvmFn)
260 return
261 }
262 if fn.Synthetic != "" && fn.Synthetic != "package initializer" && fn.Synthetic != "generic function" && fn.Synthetic != "range-over-func yield" && fn.Synthetic != "from type information" {
263 if origin := fn.Origin(); origin != nil && origin.RelString(nil) == "internal/abi.Escape" {
264 // This is a special implementation or internal/abi.Escape, which
265 // can only really be implemented in the compiler.
266 // For simplicity we'll only implement pointer parameters for now.
267 if _, ok := fn.Params[0].Type().Underlying().(*types.Pointer); ok {
268 irbuilder := c.ctx.NewBuilder()
269 defer irbuilder.Dispose()
270 b := newBuilder(c, irbuilder, fn)
271 b.createAbiEscapeImpl()
272 llvmFn.SetLinkage(llvm.LinkOnceODRLinkage)
273 llvmFn.SetUnnamedAddr(true)
274 }
275 // If the parameter is not of a pointer type, it will be left
276 // unimplemented. This will result in a linker error if the function
277 // is really called, making it clear it needs to be implemented.
278 return
279 }
280 if len(fn.Blocks) == 0 {
281 c.addError(fn.Pos(), "missing function body")
282 return
283 }
284 irbuilder := c.ctx.NewBuilder()
285 b := newBuilder(c, irbuilder, fn)
286 b.createFunction()
287 irbuilder.Dispose()
288 llvmFn.SetLinkage(llvm.LinkOnceODRLinkage)
289 llvmFn.SetUnnamedAddr(true)
290 }
291 }
292 293 // getFunctionInfo returns information about a function that is not directly
294 // present in *ssa.Function, such as the link name and whether it should be
295 // exported.
296 func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
297 if info, ok := c.functionInfos[f]; ok {
298 return info
299 }
300 info := functionInfo{
301 linkName: f.RelString(nil),
302 }
303 304 if f.Package() != nil && f.Package().Pkg != nil {
305 pkgPath := f.Package().Pkg.Path()
306 if loader.MXHSymbol(pkgPath, f.Name()) != "" {
307 info.exported = true
308 c.functionInfos[f] = info
309 return info
310 }
311 }
312 313 // Check for a few runtime functions that are treated specially.
314 if info.linkName == "runtime.wasmEntryReactor" && c.BuildMode == "c-shared" {
315 info.linkName = "_initialize"
316 info.wasmName = "_initialize"
317 info.exported = true
318 }
319 if info.linkName == "runtime.wasmEntryCommand" && c.BuildMode == "default" {
320 info.linkName = "_start"
321 info.wasmName = "_start"
322 info.exported = true
323 }
324 if info.linkName == "runtime.wasmEntryLegacy" && c.BuildMode == "wasi-legacy" {
325 info.linkName = "_start"
326 info.wasmName = "_start"
327 info.exported = true
328 }
329 330 // Check for //go: pragmas, which may change the link name (among others).
331 c.parsePragmas(&info, f)
332 333 c.functionInfos[f] = info
334 return info
335 }
336 337 // parsePragmas is used by getFunctionInfo to parse function pragmas such as
338 // //export or //go:noinline.
339 func (c *compilerContext) parsePragmas(info *functionInfo, f *ssa.Function) {
340 syntax := f.Syntax()
341 if f.Origin() != nil {
342 syntax = f.Origin().Syntax()
343 }
344 if syntax == nil {
345 return
346 }
347 348 // Read all pragmas of this function.
349 var pragmas []*ast.Comment
350 hasWasmExport := false
351 if decl, ok := syntax.(*ast.FuncDecl); ok && decl.Doc != nil {
352 for _, comment := range decl.Doc.List {
353 text := comment.Text
354 if strings.HasPrefix(text, "//go:") || strings.HasPrefix(text, "//export ") {
355 pragmas = append(pragmas, comment)
356 if strings.HasPrefix(comment.Text, "//go:wasmexport ") {
357 hasWasmExport = true
358 }
359 }
360 }
361 }
362 363 // Parse each pragma.
364 for _, comment := range pragmas {
365 parts := strings.Fields(comment.Text)
366 switch parts[0] {
367 case "//export", "//go:export":
368 if len(parts) != 2 {
369 continue
370 }
371 if hasWasmExport {
372 // //go:wasmexport overrides //export.
373 continue
374 }
375 376 info.linkName = parts[1]
377 info.wasmName = info.linkName
378 info.exported = true
379 case "//go:interrupt":
380 if hasUnsafeImport(f.Pkg.Pkg) {
381 info.interrupt = true
382 }
383 case "//go:wasm-module":
384 // Alternative comment for setting the import module.
385 // This is deprecated, use //go:wasmimport instead.
386 if len(parts) != 2 {
387 continue
388 }
389 info.wasmModule = parts[1]
390 case "//go:wasmimport":
391 // Import a WebAssembly function, for example a WASI function.
392 // Original proposal: https://github.com/golang/go/issues/38248
393 // Allow globally: https://github.com/golang/go/issues/59149
394 if len(parts) != 3 {
395 continue
396 }
397 if f.Blocks != nil {
398 // Defined functions cannot be exported.
399 c.addError(f.Pos(), "can only use //go:wasmimport on declarations")
400 continue
401 }
402 c.checkWasmImportExport(f, comment.Text)
403 info.exported = true
404 info.wasmModule = parts[1]
405 info.wasmName = parts[2]
406 case "//go:wasmexport":
407 if f.Blocks == nil {
408 c.addError(f.Pos(), "can only use //go:wasmexport on definitions")
409 continue
410 }
411 if len(parts) != 2 {
412 c.addError(f.Pos(), fmt.Sprintf("expected one parameter to //go:wasmexport, not %d", len(parts)-1))
413 continue
414 }
415 name := parts[1]
416 if name == "_start" || name == "_initialize" {
417 c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow %#v", name))
418 continue
419 }
420 if c.BuildMode != "c-shared" && f.RelString(nil) == "main.main" {
421 c.addError(f.Pos(), fmt.Sprintf("//go:wasmexport does not allow main.main to be exported with -buildmode=%s", c.BuildMode))
422 continue
423 }
424 if c.archFamily() != "wasm32" {
425 c.addError(f.Pos(), "//go:wasmexport is only supported on wasm")
426 }
427 c.checkWasmImportExport(f, comment.Text)
428 info.wasmExport = name
429 info.wasmExportPos = comment.Slash
430 case "//go:inline":
431 info.inline = inlineHint
432 case "//go:noinline":
433 info.inline = inlineNone
434 case "//go:linkname":
435 if len(parts) != 3 || parts[1] != f.Name() {
436 continue
437 }
438 // Only enable go:linkname when the package imports "unsafe".
439 if hasUnsafeImport(f.Pkg.Pkg) {
440 info.linkName = parts[2]
441 }
442 case "//go:section":
443 // Only enable go:section when the package imports "unsafe".
444 // go:section also implies go:noinline since inlining could
445 // move the code to a different section than that requested.
446 if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
447 info.section = parts[1]
448 info.inline = inlineNone
449 }
450 case "//go:nobounds":
451 // Skip bounds checking in this function. Useful for some
452 // runtime functions.
453 // This is somewhat dangerous and thus only imported in packages
454 // that import unsafe.
455 if hasUnsafeImport(f.Pkg.Pkg) {
456 info.nobounds = true
457 }
458 case "//go:noescape":
459 // Don't let pointer parameters escape.
460 // Following the upstream Go implementation, we only do this for
461 // declarations, not definitions.
462 if len(f.Blocks) == 0 {
463 info.noescape = true
464 }
465 }
466 }
467 468 if c.Nobounds {
469 info.nobounds = true
470 }
471 }
472 473 // Check whether this function can be used in //go:wasmimport or
474 // //go:wasmexport. It will add an error if this is not the case.
475 //
476 // The list of allowed types is based on this proposal:
477 // https://github.com/golang/go/issues/59149
478 func (c *compilerContext) checkWasmImportExport(f *ssa.Function, pragma string) {
479 if c.pkg.Path() == "runtime" || c.pkg.Path() == "syscall/js" || c.pkg.Path() == "syscall" || c.pkg.Path() == "crypto/internal/sysrand" {
480 // The runtime is a special case. Allow all kinds of parameters
481 // (importantly, including pointers).
482 return
483 }
484 if f.Signature.Results().Len() > 1 {
485 c.addError(f.Signature.Results().At(1).Pos(), fmt.Sprintf("%s: too many return values", pragma))
486 } else if f.Signature.Results().Len() == 1 {
487 result := f.Signature.Results().At(0)
488 if !c.isValidWasmType(result.Type(), siteResult) {
489 c.addError(result.Pos(), fmt.Sprintf("%s: unsupported result type %s", pragma, result.Type().String()))
490 }
491 }
492 for _, param := range f.Params {
493 // Check whether the type is allowed.
494 // Only a very limited number of types can be mapped to WebAssembly.
495 if !c.isValidWasmType(param.Type(), siteParam) {
496 c.addError(param.Pos(), fmt.Sprintf("%s: unsupported parameter type %s", pragma, param.Type().String()))
497 }
498 }
499 }
500 501 // Check whether the type maps directly to a WebAssembly type.
502 //
503 // This reflects the relaxed type restrictions proposed here (except for structs.HostLayout):
504 // https://github.com/golang/go/issues/66984
505 //
506 // This previously reflected the additional restrictions documented here:
507 // https://github.com/golang/go/issues/59149
508 func (c *compilerContext) isValidWasmType(typ types.Type, site wasmSite) bool {
509 switch typ := typ.Underlying().(type) {
510 case *types.Basic:
511 switch typ.Kind() {
512 case types.Bool:
513 return true
514 case types.Int8, types.Uint8, types.Int16, types.Uint16:
515 return site == siteIndirect
516 case types.Int32, types.Uint32, types.Int64, types.Uint64:
517 return true
518 case types.Float32, types.Float64:
519 return true
520 case types.Uintptr, types.UnsafePointer:
521 return true
522 case types.String:
523 // string flattens to three values (ptr, len, cap), so disallowed as a result
524 return site == siteParam || site == siteIndirect
525 }
526 case *types.Array:
527 return site == siteIndirect && c.isValidWasmType(typ.Elem(), siteIndirect)
528 case *types.Struct:
529 if site != siteIndirect {
530 return false
531 }
532 // Structs with no fields do not need structs.HostLayout
533 if typ.NumFields() == 0 {
534 return true
535 }
536 hasHostLayout := true // default to true before detecting Go version
537 // (*types.Package).GoVersion added in go1.21
538 if gv, ok := any(c.pkg).(interface{ GoVersion() string }); ok {
539 if goenv.Compare(gv.GoVersion(), "go1.23") >= 0 {
540 hasHostLayout = false // package structs added in go1.23
541 }
542 }
543 for i := 0; i < typ.NumFields(); i++ {
544 ftyp := typ.Field(i).Type()
545 if ftyp.String() == "structs.HostLayout" {
546 hasHostLayout = true
547 continue
548 }
549 if !c.isValidWasmType(ftyp, siteIndirect) {
550 return false
551 }
552 }
553 return hasHostLayout
554 case *types.Pointer:
555 return c.isValidWasmType(typ.Elem(), siteIndirect)
556 }
557 return false
558 }
559 560 type wasmSite int
561 562 const (
563 siteParam wasmSite = iota
564 siteResult
565 siteIndirect // pointer or field
566 )
567 568 // getParams returns the function parameters, including the receiver at the
569 // start. This is an alternative to the Params member of *ssa.Function, which is
570 // not yet populated when the package has not yet been built.
571 func getParams(sig *types.Signature) []*types.Var {
572 params := []*types.Var{}
573 if sig.Recv() != nil {
574 params = append(params, sig.Recv())
575 }
576 for i := 0; i < sig.Params().Len(); i++ {
577 params = append(params, sig.Params().At(i))
578 }
579 return params
580 }
581 582 // addStandardDeclaredAttributes adds attributes that are set for any function,
583 // whether declared or defined.
584 func (c *compilerContext) addStandardDeclaredAttributes(llvmFn llvm.Value) {
585 if c.SizeLevel >= 1 {
586 // Set the "optsize" attribute to make slightly smaller binaries at the
587 // cost of minimal performance loss (-Os in Clang).
588 kind := llvm.AttributeKindID("optsize")
589 attr := c.ctx.CreateEnumAttribute(kind, 0)
590 llvmFn.AddFunctionAttr(attr)
591 }
592 if c.SizeLevel >= 2 {
593 // Set the "minsize" attribute to reduce code size even further,
594 // regardless of performance loss (-Oz in Clang).
595 kind := llvm.AttributeKindID("minsize")
596 attr := c.ctx.CreateEnumAttribute(kind, 0)
597 llvmFn.AddFunctionAttr(attr)
598 }
599 if c.CPU != "" {
600 llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("target-cpu", c.CPU))
601 }
602 if c.Features != "" {
603 llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("target-features", c.Features))
604 }
605 }
606 607 // addStandardDefinedAttributes adds the set of attributes that are added to
608 // every function defined by Moxie (even thunks/wrappers), possibly depending
609 // on the architecture. It does not set attributes only set for declared
610 // functions, use addStandardDeclaredAttributes for this.
611 func (c *compilerContext) addStandardDefinedAttributes(llvmFn llvm.Value) {
612 // Moxie does not currently raise exceptions, so set the 'nounwind' flag.
613 // This behavior matches Clang when compiling C source files.
614 // It reduces binary size on Linux a little bit on non-x86_64 targets by
615 // eliminating exception tables for these functions.
616 llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nounwind"), 0))
617 if strings.Split(c.Triple, "-")[0] == "x86_64" {
618 // Required by the ABI.
619 // The uwtable has two possible values: sync (1) or async (2). We use
620 // sync because we currently don't use async unwind tables.
621 // For details, see: https://llvm.org/docs/LangRef.html#function-attributes
622 llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 1))
623 }
624 }
625 626 // addStandardAttributes adds all attributes added to defined functions.
627 func (c *compilerContext) addStandardAttributes(llvmFn llvm.Value) {
628 c.addStandardDeclaredAttributes(llvmFn)
629 c.addStandardDefinedAttributes(llvmFn)
630 }
631 632 // globalInfo contains some information about a specific global. By default,
633 // linkName is equal to .RelString(nil) on a global and extern is false, but for
634 // some symbols this is different (due to //go:extern for example).
635 type globalInfo struct {
636 linkName string // go:extern, go:linkname
637 extern bool // go:extern
638 align int // go:align
639 section string // go:section
640 }
641 642 // loadASTComments loads comments on globals from the AST, for use later in the
643 // program. In particular, they are required for //go:extern pragmas on globals.
644 func (c *compilerContext) loadASTComments(pkg *loader.Package) {
645 for _, file := range pkg.Files {
646 for _, decl := range file.Decls {
647 switch decl := decl.(type) {
648 case *ast.GenDecl:
649 switch decl.Tok {
650 case token.VAR:
651 if len(decl.Specs) != 1 {
652 continue
653 }
654 for _, spec := range decl.Specs {
655 switch spec := spec.(type) {
656 case *ast.ValueSpec: // decl.Tok == token.VAR
657 for _, name := range spec.Names {
658 id := pkg.Pkg.Path() + "." + name.Name
659 c.astComments[id] = decl.Doc
660 }
661 }
662 }
663 }
664 }
665 }
666 }
667 }
668 669 // getGlobal returns a LLVM IR global value for a Go SSA global. It is added to
670 // the LLVM IR if it has not been added already.
671 func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
672 info := c.getGlobalInfo(g)
673 llvmGlobal := c.mod.NamedGlobal(info.linkName)
674 if llvmGlobal.IsNil() {
675 typ := g.Type().(*types.Pointer).Elem()
676 llvmType := c.getLLVMType(typ)
677 llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
678 679 // Set alignment from the //go:align comment.
680 alignment := c.targetData.ABITypeAlignment(llvmType)
681 if info.align > alignment {
682 alignment = info.align
683 }
684 if alignment <= 0 || alignment&(alignment-1) != 0 {
685 // Check for power-of-two (or 0).
686 // See: https://stackoverflow.com/a/108360
687 c.addError(g.Pos(), "global variable alignment must be a positive power of two")
688 } else {
689 // Set the alignment only when it is a power of two.
690 llvmGlobal.SetAlignment(alignment)
691 }
692 693 if c.Debug && !info.extern {
694 // Add debug info.
695 pos := c.program.Fset.Position(g.Pos())
696 diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
697 Name: g.RelString(nil),
698 LinkageName: info.linkName,
699 File: c.getDIFile(pos.Filename),
700 Line: pos.Line,
701 Type: c.getDIType(typ),
702 LocalToUnit: false,
703 Expr: c.dibuilder.CreateExpression(nil),
704 AlignInBits: uint32(alignment) * 8,
705 })
706 llvmGlobal.AddMetadata(0, diglobal)
707 }
708 }
709 return llvmGlobal
710 }
711 712 // getGlobalInfo returns some information about a specific global.
713 func (c *compilerContext) getGlobalInfo(g *ssa.Global) globalInfo {
714 info := globalInfo{
715 // Pick the default linkName.
716 linkName: g.RelString(nil),
717 }
718 // Check for //go: pragmas, which may change the link name (among others).
719 doc := c.astComments[info.linkName]
720 if doc != nil {
721 info.parsePragmas(doc, c, g)
722 }
723 return info
724 }
725 726 // Parse //go: pragma comments from the source. In particular, it parses the
727 // //go:extern and //go:linkname pragmas on globals.
728 func (info *globalInfo) parsePragmas(doc *ast.CommentGroup, c *compilerContext, g *ssa.Global) {
729 for _, comment := range doc.List {
730 if !strings.HasPrefix(comment.Text, "//go:") {
731 continue
732 }
733 parts := strings.Fields(comment.Text)
734 switch parts[0] {
735 case "//go:extern":
736 info.extern = true
737 if len(parts) == 2 {
738 info.linkName = parts[1]
739 }
740 case "//go:align":
741 align, err := strconv.Atoi(parts[1])
742 if err == nil {
743 info.align = align
744 }
745 case "//go:section":
746 if len(parts) == 2 {
747 info.section = parts[1]
748 }
749 case "//go:linkname":
750 if len(parts) != 3 || parts[1] != g.Name() {
751 continue
752 }
753 // Only enable go:linkname when the package imports "unsafe".
754 // This is a slightly looser requirement than what gc uses: gc
755 // requires the file to import "unsafe", not the package as a
756 // whole.
757 if hasUnsafeImport(g.Pkg.Pkg) {
758 info.linkName = parts[2]
759 }
760 }
761 }
762 }
763 764 // Get all methods of a type.
765 func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
766 ms := prog.MethodSets.MethodSet(typ)
767 methods := make([]*types.Selection, ms.Len())
768 for i := 0; i < ms.Len(); i++ {
769 methods[i] = ms.At(i)
770 }
771 return methods
772 }
773 774 // Return true if this package imports "unsafe", false otherwise.
775 func hasUnsafeImport(pkg *types.Package) bool {
776 for _, imp := range pkg.Imports() {
777 if imp == types.Unsafe {
778 return true
779 }
780 }
781 return false
782 }
783