symbol.go raw

   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&paramIsGoParam != 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&paramIsReadonly != 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