Source file src/cmd/compile/internal/ssa/cpufeatures.go

     1  // Copyright 2025 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package ssa
     6  
     7  import (
     8  	"cmd/compile/internal/types"
     9  	"cmd/internal/obj"
    10  	"fmt"
    11  	"internal/goarch"
    12  )
    13  
    14  type localEffect struct {
    15  	start    CPUfeatures    // features present at beginning of block
    16  	internal CPUfeatures    // features implied by execution of block
    17  	end      [2]CPUfeatures // for BlockIf, features present on outgoing edges
    18  	visited  bool           // On the first iteration this will be false for backedges.
    19  }
    20  
    21  func (e localEffect) String() string {
    22  	return fmt.Sprintf("visited=%v, start=%v, internal=%v, end[0]=%v, end[1]=%v", e.visited, e.start, e.internal, e.end[0], e.end[1])
    23  }
    24  
    25  // ifEffect pattern matches for a BlockIf conditional on a load
    26  // of a field from internal/cpu.X86 and returns the corresponding
    27  // effect.
    28  func ifEffect(b *Block) (features CPUfeatures, taken int) {
    29  	// TODO generalize for other architectures.
    30  	if b.Kind != BlockIf {
    31  		return
    32  	}
    33  	c := b.Controls[0]
    34  
    35  	if c.Op == OpNot {
    36  		taken = 1
    37  		c = c.Args[0]
    38  	}
    39  	if c.Op != OpLoad {
    40  		return
    41  	}
    42  	offPtr := c.Args[0]
    43  	if offPtr.Op != OpOffPtr {
    44  		return
    45  	}
    46  	addr := offPtr.Args[0]
    47  	if addr.Op != OpAddr || addr.Args[0].Op != OpSB {
    48  		return
    49  	}
    50  	sym := addr.Aux.(*obj.LSym)
    51  	if sym.Name != "internal/cpu.X86" {
    52  		return
    53  	}
    54  	o := offPtr.AuxInt
    55  	t := addr.Type
    56  	if !t.IsPtr() {
    57  		b.Func.Fatalf("The symbol %s is not a pointer, found %v instead", sym.Name, t)
    58  	}
    59  	t = t.Elem()
    60  	if !t.IsStruct() {
    61  		b.Func.Fatalf("The referent of symbol %s is not a struct, found %v instead", sym.Name, t)
    62  	}
    63  	match := ""
    64  	for _, f := range t.Fields() {
    65  		if o == f.Offset && f.Sym != nil {
    66  			match = f.Sym.Name
    67  			break
    68  		}
    69  	}
    70  
    71  	switch match {
    72  
    73  	case "HasAVX":
    74  		features = CPUavx
    75  	case "HasAVXVNNI":
    76  		features = CPUavx | CPUavxvnni
    77  	case "HasAVX2":
    78  		features = CPUavx2 | CPUavx
    79  
    80  		// Compiler currently treats these all alike.
    81  	case "HasAVX512", "HasAVX512F", "HasAVX512CD", "HasAVX512BW",
    82  		"HasAVX512DQ", "HasAVX512VL", "HasAVX512VPCLMULQDQ":
    83  		features = CPUavx512 | CPUavx2 | CPUavx
    84  
    85  	case "HasAVX512GFNI":
    86  		features = CPUavx512 | CPUgfni | CPUavx2 | CPUavx
    87  	case "HasAVX512VNNI":
    88  		features = CPUavx512 | CPUavx512vnni | CPUavx2 | CPUavx
    89  	case "HasAVX512VBMI":
    90  		features = CPUavx512 | CPUvbmi | CPUavx2 | CPUavx
    91  	case "HasAVX512VBMI2":
    92  		features = CPUavx512 | CPUvbmi2 | CPUavx2 | CPUavx
    93  	case "HasAVX512BITALG":
    94  		features = CPUavx512 | CPUbitalg | CPUavx2 | CPUavx
    95  	case "HasAVX512VPOPCNTDQ":
    96  		features = CPUavx512 | CPUvpopcntdq | CPUavx2 | CPUavx
    97  
    98  	case "HasBMI1":
    99  		features = CPUvbmi
   100  	case "HasBMI2":
   101  		features = CPUvbmi2
   102  
   103  		// Features that are not currently interesting to the compiler.
   104  	case "HasAES", "HasADX", "HasERMS", "HasFSRM", "HasFMA", "HasGFNI", "HasOSXSAVE",
   105  		"HasPCLMULQDQ", "HasPOPCNT", "HasRDTSCP", "HasSHA",
   106  		"HasSSE3", "HasSSSE3", "HasSSE41", "HasSSE42":
   107  
   108  	}
   109  	if b.Func.pass.debug > 2 {
   110  		b.Func.Warnl(b.Pos, "%s, block b%v has features offset %d, match is %s, features is %v", b.Func.Name, b.ID, o, match, features)
   111  	}
   112  	return
   113  }
   114  
   115  func cpufeatures(f *Func) {
   116  	arch := f.Config.Ctxt().Arch.Family
   117  	// TODO there are other SIMD architectures
   118  	if arch != goarch.AMD64 {
   119  		return
   120  	}
   121  
   122  	po := f.Postorder()
   123  
   124  	effects := make([]localEffect, 1+f.NumBlocks(), 1+f.NumBlocks())
   125  
   126  	features := func(t *types.Type) CPUfeatures {
   127  		if t.IsSIMD() {
   128  			switch t.Size() {
   129  			case 16, 32:
   130  				return CPUavx
   131  			case 64:
   132  				return CPUavx512 | CPUavx2 | CPUavx
   133  			}
   134  		}
   135  		return CPUNone
   136  	}
   137  
   138  	// visit blocks in reverse post order
   139  	// when b is visited, all of its predecessors (except for loop back edges)
   140  	// will have been visited
   141  	for i := len(po) - 1; i >= 0; i-- {
   142  		b := po[i]
   143  
   144  		var feat CPUfeatures
   145  
   146  		if b == f.Entry {
   147  			// Check the types of inputs and outputs, as well as annotations.
   148  			// Start with none and union all that is implied by all the types seen.
   149  			if f.Type != nil { // a problem for SSA tests
   150  				for _, field := range f.Type.RecvParamsResults() {
   151  					feat |= features(field.Type)
   152  				}
   153  			}
   154  
   155  		} else {
   156  			// Start with all and intersect over predecessors
   157  			feat = CPUAll
   158  			for _, p := range b.Preds {
   159  				pb := p.Block()
   160  				if !effects[pb.ID].visited {
   161  
   162  					continue
   163  				}
   164  				pi := p.Index()
   165  				if pb.Kind != BlockIf {
   166  					pi = 0
   167  				}
   168  
   169  				feat &= effects[pb.ID].end[pi]
   170  			}
   171  		}
   172  
   173  		e := localEffect{start: feat, visited: true}
   174  
   175  		// Separately capture the internal effects of this block
   176  		var internal CPUfeatures
   177  		for _, v := range b.Values {
   178  			// the rule applied here is, if the block contains any
   179  			// instruction that would fault if the feature (avx, avx512)
   180  			// were not present, then assume that the feature is present
   181  			// for all the instructions in the block, a fault is a fault.
   182  			t := v.Type
   183  			if t.IsResults() {
   184  				for i := 0; i < t.NumFields(); i++ {
   185  					feat |= features(t.FieldType(i))
   186  				}
   187  			} else {
   188  				internal |= features(v.Type)
   189  			}
   190  		}
   191  		e.internal = internal
   192  		feat |= internal
   193  
   194  		branchEffect, taken := ifEffect(b)
   195  		e.end = [2]CPUfeatures{feat, feat}
   196  		e.end[taken] |= branchEffect
   197  
   198  		effects[b.ID] = e
   199  		if f.pass.debug > 1 && feat != CPUNone {
   200  			f.Warnl(b.Pos, "%s, block b%v has features %v", b.Func.Name, b.ID, feat)
   201  		}
   202  
   203  		b.CPUfeatures = feat
   204  		f.maxCPUFeatures |= feat // not necessary to refine this estimate below
   205  	}
   206  
   207  	// If the flow graph is irreducible, things can still change on backedges.
   208  	change := true
   209  	for change {
   210  		change = false
   211  		for i := len(po) - 1; i >= 0; i-- {
   212  			b := po[i]
   213  
   214  			if b == f.Entry {
   215  				continue // cannot change
   216  			}
   217  			feat := CPUAll
   218  			for _, p := range b.Preds {
   219  				pb := p.Block()
   220  				pi := p.Index()
   221  				if pb.Kind != BlockIf {
   222  					pi = 0
   223  				}
   224  				feat &= effects[pb.ID].end[pi]
   225  			}
   226  			e := effects[b.ID]
   227  			if feat == e.start {
   228  				continue
   229  			}
   230  			e.start = feat
   231  			effects[b.ID] = e
   232  			// uh-oh, something changed
   233  			if f.pass.debug > 1 {
   234  				f.Warnl(b.Pos, "%s, block b%v saw predecessor feature change", b.Func.Name, b.ID)
   235  			}
   236  
   237  			feat |= e.internal
   238  			if feat == e.end[0]&e.end[1] {
   239  				continue
   240  			}
   241  
   242  			branchEffect, taken := ifEffect(b)
   243  			e.end = [2]CPUfeatures{feat, feat}
   244  			e.end[taken] |= branchEffect
   245  
   246  			effects[b.ID] = e
   247  			b.CPUfeatures = feat
   248  			if f.pass.debug > 1 {
   249  				f.Warnl(b.Pos, "%s, block b%v has new features %v", b.Func.Name, b.ID, feat)
   250  			}
   251  			change = true
   252  		}
   253  	}
   254  	if f.pass.debug > 0 {
   255  		for _, b := range f.Blocks {
   256  			if b.CPUfeatures != CPUNone {
   257  				f.Warnl(b.Pos, "%s, block b%v has features %v", b.Func.Name, b.ID, b.CPUfeatures)
   258  			}
   259  
   260  		}
   261  	}
   262  }
   263  

View as plain text