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