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