Source file src/simd/archsimd/_gen/simdgen/sve/analyze.go

     1  // Copyright 2026 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 sve
     6  
     7  import (
     8  	"fmt"
     9  	"sort"
    10  	"strings"
    11  
    12  	"simd/archsimd/_gen/unify"
    13  )
    14  
    15  // classify decides what to do with an instruction and why. It returns the
    16  // emitted defs (nil if skipped), a human-readable reason, and whether the
    17  // reason is an anomaly, i.e. a case the loader does not understand and that
    18  // should be investigated. Skips that are understood but deferred, e.g. memory,
    19  // immediate, register list, special or non-uniform operands are not anomalies.
    20  //
    21  // emitAll and analyze share this logic so that what gets emitted and what gets
    22  // reported can never drift apart.
    23  func (inst *Instruction) classify() (defs []*unify.Value, reason string, anomaly bool) {
    24  	if !inst.isSVE() {
    25  		return nil, "not an SVE instruction", false
    26  	}
    27  	if inst.isAlias() {
    28  		return nil, "alias", false
    29  	}
    30  	allEncOps := inst.allEncodingOperands()
    31  	if len(allEncOps) == 0 {
    32  		// Nullary or unsized forms (e.g. SETFFR): nothing to emit, but not a
    33  		// parse failure.
    34  		return nil, "no operands (deferred)", false
    35  	}
    36  
    37  	// emit every distinct encoding form. An anomaly in any form is reported for
    38  	// the whole instruction; a form that is merely deferred contributes no defs
    39  	// but does not fail the others.
    40  	var skip string
    41  	for _, ops := range allEncOps {
    42  		d, r, a := inst.classifyOperands(ops)
    43  		if a {
    44  			return nil, r, true
    45  		}
    46  		if len(d) == 0 {
    47  			if skip == "" {
    48  				skip = r
    49  			}
    50  			continue
    51  		}
    52  		defs = append(defs, d...)
    53  	}
    54  	if len(defs) == 0 {
    55  		return nil, skip, false
    56  	}
    57  	return defs, "", false
    58  }
    59  
    60  // classifyOperands is classify for a single encoding form's operands.
    61  func (inst *Instruction) classifyOperands(ops []Operand) (defs []*unify.Value, reason string, anomaly bool) {
    62  	// Unrecognized operands are anomalies.
    63  	for _, op := range ops {
    64  		if op.Class == "unknown" {
    65  			return nil, fmt.Sprintf("unknown operand %q", op.Raw), true
    66  		}
    67  	}
    68  	// Register lists are not modeled yet.
    69  	// TODO: emit list operands, which needs regalloc support for register lists,
    70  	// instead of skipping the instruction.
    71  	if hasClass(ops, "reglist") {
    72  		return nil, "register list (deferred, TODO)", false
    73  	}
    74  
    75  	// Every arrangement symbol used by an operand must resolve to a real size
    76  	// table; if not, the loader does not understand the instruction.
    77  	for _, link := range arngLinks(ops) {
    78  		if len(inst.resolveArrangementTable(link)) == 0 {
    79  			return nil, fmt.Sprintf("arrangement %q resolves to empty domain", link), true
    80  		}
    81  	}
    82  
    83  	defs = inst.emitVariants(ops)
    84  	if len(defs) == 0 {
    85  		return nil, "no defs emitted (all rows reserved/filtered)", false
    86  	}
    87  	return defs, "", false
    88  }
    89  
    90  // report is the outcome of analyzing a corpus of SVE instructions.
    91  type report struct {
    92  	Total   int            // SVE instructions considered
    93  	Emitted int            // instructions that produced at least one def
    94  	Defs    int            // total defs emitted
    95  	Reasons map[string]int // skip/emit reason -> instruction count
    96  	// Anomalies lists "<mnemonic> (<file title>): <reason>" for every
    97  	// instruction the loader did not understand.
    98  	Anomalies []string
    99  }
   100  
   101  // analyze parses the ARM64 ISA XML files at path and reports, for every SVE
   102  // instruction, whether it was emitted or skipped and why, collecting the
   103  // unrecognized cases in report.Anomalies. Used by the corpus test.
   104  func analyze(path string) (*report, error) {
   105  	insts, err := parseInstructions(path)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  	r := &report{Reasons: map[string]int{}}
   110  	for _, inst := range insts {
   111  		r.Total++
   112  		defs, reason, anomaly := inst.classify()
   113  		key := reason
   114  		if key == "" {
   115  			key = "emitted"
   116  			r.Emitted++
   117  			r.Defs += len(defs)
   118  		}
   119  		r.Reasons[key]++
   120  		if anomaly {
   121  			r.Anomalies = append(r.Anomalies,
   122  				fmt.Sprintf("%s (%s): %s", inst.mnemonic(), inst.Title, reason))
   123  		}
   124  	}
   125  	sort.Strings(r.Anomalies)
   126  	return r, nil
   127  }
   128  
   129  // String renders a human-readable summary of the report.
   130  func (r *report) String() string {
   131  	var b strings.Builder
   132  	fmt.Fprintf(&b, "SVE instructions: %d (%d emitted -> %d defs)\n", r.Total, r.Emitted, r.Defs)
   133  	fmt.Fprintf(&b, "disposition:\n")
   134  	keys := make([]string, 0, len(r.Reasons))
   135  	for k := range r.Reasons {
   136  		keys = append(keys, k)
   137  	}
   138  	sort.Slice(keys, func(i, j int) bool {
   139  		if r.Reasons[keys[i]] != r.Reasons[keys[j]] {
   140  			return r.Reasons[keys[i]] > r.Reasons[keys[j]]
   141  		}
   142  		return keys[i] < keys[j]
   143  	})
   144  	for _, k := range keys {
   145  		fmt.Fprintf(&b, "  %5d  %s\n", r.Reasons[k], k)
   146  	}
   147  	fmt.Fprintf(&b, "anomalies: %d\n", len(r.Anomalies))
   148  	for _, a := range r.Anomalies {
   149  		fmt.Fprintf(&b, "  ! %s\n", a)
   150  	}
   151  	return b.String()
   152  }
   153  

View as plain text