src/cmd/compile/internal/midway/analysis.go | 111 +++++++++++++++++++++++++++++++++++++++-------------- src/cmd/compile/internal/midway/deepcopy.go | 12 ++++++++---- src/cmd/compile/internal/midway/rewrite.go | 162 ++++++++++++++++++++++++++++++++++++++++++++++++----- src/cmd/compile/internal/types2/call.go | 2 +- src/cmd/compile/internal/types2/index.go | 4 ++-- src/cmd/go/alldocs.go | 4 ++++ src/cmd/go/internal/cacheprog/cacheprog.go | 2 ++ src/cmd/go/internal/help/helpdoc.go | 4 ++++ src/go/types/call.go | 2 +- src/go/types/index.go | 4 ++-- src/internal/types/testdata/fixedbugs/issue80042.go | 19 +++++++++++++++++++ src/net/http/request.go | 4 ++++ src/net/http/requestwrite_test.go | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/net/http/responsewrite_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ src/net/http/transfer.go | 7 +++++++ src/simd/archsimd/_gen/midway/comments.yaml | 4 ++-- src/simd/doc.go | 91 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/simd/simd_stubs.go | 40 ++++++++++++++++++++-------------------- src/simd/sizeof_test.go => src/simd/testdata/sizeof_test.go | 8 ++++---- src/simd/testdata/iface/iface.go | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/simd/testdata/iface_test.go | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/simd/testdata/mains/compiles.go => src/simd/testdata/compiles_test.go | 12 +++++++----- src/simd/testdata/mains/errors.go => src/simd/testdata/errors_test.go | 9 +++++---- src/simd/testdata/v.go => src/simd/testdata/pkg/v.go | 2 +- src/simd/testdata_test.go | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++ diff --git a/src/cmd/compile/default.pgo b/src/cmd/compile/default.pgo index 2c2588704f3f6ee52e2f9e9e0d3a3efcc441374a..06e63b47811f70e63f4e2e941c83b4462960b6ae 100644 Binary files a/src/cmd/compile/default.pgo and b/src/cmd/compile/default.pgo differ diff --git a/src/cmd/compile/internal/midway/analysis.go b/src/cmd/compile/internal/midway/analysis.go index 30b4288d10238e5b78c6772597111d29c5e17ecf..ab7482161d68954e0d74cecc00201054c0d01580 100644 --- a/src/cmd/compile/internal/midway/analysis.go +++ b/src/cmd/compile/internal/midway/analysis.go @@ -12,35 +12,51 @@ ) // Analyzer holds the state for SIMD dependency analysis type Analyzer struct { - pkg *types2.Package - info *types2.Info - dependentObj map[types2.Object]bool - visited map[types2.Type]bool - inSimd bool + pkg *types2.Package + info *types2.Info + isDependentObj map[types2.Object]bool // does an Object depend on a simd type in some way? + isDependentMethod map[types2.Object]bool // is this dependent Object also a method? (methods are not renamed, their types are) + hasDependentMethod map[types2.Type]bool // is this a type that has a dependent method? + visited map[types2.Type]bool // if in map, type has been visited, and value is whether type is dependent + inSimd bool // true if the current package is the simd package (which is a special case) } func NewAnalyzer(pkg *types2.Package, info *types2.Info) *Analyzer { return &Analyzer{ - pkg: pkg, - info: info, - dependentObj: make(map[types2.Object]bool), - visited: make(map[types2.Type]bool), - inSimd: pkg.Path() == simdPkg, + pkg: pkg, + info: info, + isDependentObj: make(map[types2.Object]bool), + isDependentMethod: make(map[types2.Object]bool), + hasDependentMethod: make(map[types2.Type]bool), + visited: make(map[types2.Type]bool), + inSimd: pkg.Path() == simdPkg, } } // Analyze builds the set of SIMD-dependent objects func (a *Analyzer) Analyze(files []*syntax.File) bool { // Phase 1: Seed dependence from types and signatures - for _, obj := range a.info.Defs { - if obj != nil { - a.markIfDependent(obj) + hdmsize := len(a.hasDependentMethod) + + for { + for _, obj := range a.info.Defs { + if obj != nil { + a.markIfDependent(obj) + } } - } - for _, obj := range a.info.Uses { - if obj != nil { - a.markIfDependent(obj) + for _, obj := range a.info.Uses { + if obj != nil { + a.markIfDependent(obj) + } } + if hdmsize == len(a.hasDependentMethod) { + break + } + if base.Debug.Simd > 0 { + base.Warn("hasDependentMethod increased from %d to %d", hdmsize, len(a.hasDependentMethod)) + } + hdmsize = len(a.hasDependentMethod) + clear(a.visited) } // Phase 2: Transitive closure via function bodies @@ -54,12 +70,12 @@ if fn.Name == nil { continue } obj := a.info.Defs[fn.Name] - if obj == nil || a.dependentObj[obj] { + if obj == nil || a.isDependentObj[obj] { continue } if a.hasBodyDependency(fn) { - a.dependentObj[obj] = true + a.isDependentObj[obj] = true changed = true } } @@ -67,7 +83,7 @@ } } } - return len(a.dependentObj) > 0 + return len(a.isDependentObj) > 0 } func (a *Analyzer) hasBodyDependency(fn *syntax.FuncDecl) bool { @@ -85,7 +101,7 @@ obj = a.info.Defs[id] } if obj != nil { if _, isFunc := obj.(*types2.Func); !isFunc { - if a.dependentObj[obj] { + if a.isDependentObj[obj] { found = true return false } @@ -98,10 +114,12 @@ } } if a.isDependentType(obj.Type()) { // Whatever this is, it makes the outer object dependent. - // If this is a variable with dependent type, mark the variable as - // dependent, so that references to it become dependent. - if obj, ok := obj.(*types2.Var); ok { - a.dependentObj[obj] = true + // If this is a package variable with dependent type, mark the + // variable as dependent, so that references to it become dependent. + if obj, ok := obj.(*types2.Var); ok && obj.Kind() == types2.PackageVar { + // everything else is nested within a dependent function/struct/scope + // and does not need its own renaming + a.isDependentObj[obj] = true } found = true return false @@ -118,11 +136,12 @@ return found } func (a *Analyzer) markIfDependent(obj types2.Object) bool { - if a.dependentObj[obj] { + if a.isDependentObj[obj] { return true } isDep := false + isDepMeth := false switch obj := obj.(type) { case *types2.Var: if obj.Pkg() == a.pkg && obj.Parent() == a.pkg.Scope() { @@ -139,6 +158,11 @@ if rcv := sig.Recv(); rcv == nil { isDep = true } else if named, ok := rcv.Type().(*types2.Named); !ok || !isBaseSimdType(named) { isDep = true + t := rcv.Type() + if !a.isDependentType(t) { + a.markHasMethod(t) + } + isDepMeth = true } } } @@ -150,9 +174,15 @@ } if isDep { if base.Debug.Simd > 0 { - base.Warn("%v is simd-dependent", obj) + base.Warn("%s: %v is simd-dependent", obj.Pos().String(), obj) + } + a.isDependentObj[obj] = true + } + if isDepMeth { + if base.Debug.Simd > 0 { + base.Warn("%s: %v is simd-dependent method", obj.Pos().String(), obj) } - a.dependentObj[obj] = true + a.isDependentMethod[obj] = true } return isDep } @@ -164,6 +194,9 @@ func (a *Analyzer) checkTypeRecursive(t types2.Type) bool { if t == nil { return false + } + if a.hasDependentMethod[t] { + a.visited[t] = true } if b, ok := a.visited[t]; ok { return b // Break cycles @@ -217,6 +250,28 @@ case *types2.Alias: return memo(a.checkTypeRecursive(types2.Unalias(t))) } return false +} + +// This attempts to mark types that are not otherwise dependent +// as being dependent, if they have a method with a dependent +// signature. +func (a *Analyzer) markHasMethod(t types2.Type) { + if t == nil { + return + } + if a.hasDependentMethod[t] { + return + } + + a.hasDependentMethod[t] = true + + switch t := t.(type) { + case *types2.Pointer: + a.markHasMethod(t.Elem()) + case *types2.Alias: + a.markHasMethod(t.Rhs()) + } + return } func isBaseSimdType(t *types2.Named) bool { diff --git a/src/cmd/compile/internal/midway/deepcopy.go b/src/cmd/compile/internal/midway/deepcopy.go index ba00742cc8a749823404be60bde8ef2abc4a5474..99f6ef0a6f471ff0ee0764551409f2d45b23791c 100644 --- a/src/cmd/compile/internal/midway/deepcopy.go +++ b/src/cmd/compile/internal/midway/deepcopy.go @@ -72,12 +72,16 @@ } if obj == nil { return nil } + // Don't rename methods of dependent types + if c.analyzer.isDependentMethod[obj] { + return nil + } - if c.analyzer.dependentObj[obj] || isBaseSimdTypeObj(obj) { + if c.analyzer.isDependentObj[obj] || isBaseSimdTypeObj(obj) { newId := syntax.NewName(id.Pos(), id.Value+c.suffix) // Object link will be handled manually in deepcopier Use/Def mapper if base.Debug.Simd > 0 { - base.Warn("Rewriting name %s to %s", id.Value, newId.Value) + base.Warn("%s: rewriting name %s to %s", id.Pos().String(), id.Value, newId.Value) } return newId } @@ -126,11 +130,11 @@ return newSel } } - if c.analyzer.dependentObj[obj] { + if c.analyzer.isDependentObj[obj] { newId := syntax.NewName(id.Pos(), id.Value+c.suffix) // Object link will be handled manually in deepcopier Use/Def mapper if base.Debug.Simd > 0 { - base.Warn("Rewriting name %s to %s", id.Value, newId.Value) + base.Warn("%s: rewriting name %s to %s", id.Pos().String(), id.Value, newId.Value) } return newId } diff --git a/src/cmd/compile/internal/midway/rewrite.go b/src/cmd/compile/internal/midway/rewrite.go index 796635c708925d16e2b432c3a9b910e3ee990638..e80930bcd79346b6d34107c3652f4c427b83f766 100644 --- a/src/cmd/compile/internal/midway/rewrite.go +++ b/src/cmd/compile/internal/midway/rewrite.go @@ -5,6 +5,7 @@ package midway import ( + "cmd/compile/internal/base" "cmd/compile/internal/syntax" "cmd/compile/internal/types2" "fmt" @@ -36,6 +37,128 @@ // build system limits visibility to unrelated "internal" packages and can be // modified to allow access in special cases (like this one). This allows the // rewritten code to reference types, functions, and methods that are not // accessible otherwise. +// +// The rewrite works in phases. The first is "analysis", to discover functions, +// types, methods, and variables that depend on "simd" types. "Depend on" means +// any mention of a simd type, and for types, also includes types that have a +// simd-dependent method. Dependent functions are split into two categories; +// those whose dependence includes their signature, and those that do not. +// The second category forms the boundary between code that depends on simd and +// code that does not. Notice that there cannot be a boundary method, because +// (by design) the receiver type is simd-dependent and thus a dependent method +// also has a dependent type in its signature. +// +// The second phase rewrites such "boundary" functions into a "dispatch" version +// and (later, third phase) "specialized" versions. The dispatch function +// will choose which specialized version to call based on which simd implementation +// has been chosen, and forward parameters and results to/from that specialized version +// of the function. The dispatch version shares the same name as the original function. +// Note that this applies to functions only, and not methods. + +// The third phase specializes dependent functions (both kinds), methods, +// global variables, and types into size/emulation/feature-specific variants. +// Except for methods, this is done by adding a suffix beginning with "@" to +// the name. Because "@" cannot appear in legal Go identifiers this removes +// the risk of a naming overlap. Methods are specialized, but not renamed, +// because their receiver type is renamed instead. Not changing method names +// preserves interface satisfaction, for example in the case of generic interfaces. +// +// Non-boundary dependent function and methods are not rewritten into dispatch +// functions/methods, but remain in the generated code because they must be +// present in the export data so that other packages that import them will still +// compile before rewriting. Their bodies are replaced with panic(...) to allow +// compilation while preventing even worse chaos in the event of a bug either in +// the compiler or through ambitious use of reflection or assembly language. +// + +/* Example rewrites + +// Type alias, global variable, and init function: + +// before: +type MyInt8s = simd.Int8s +func Generic[T haslen](x int) int { + var v T + return x + v.Len() +} +var VL int +func init() { + VL = Generic[MyInt8s](1) +} +// dispatch: +func init() { + switch simd.VectorBitSize() { + case + 128: + init@simd128() + return + case 256: + init@simd256() + return + case 512: + init@simd512() + return + default: + panic("unsupported vector size") + } +} +// specialized (128) +type MyInt8s@simd128 = archsimd.Int8x16 +func init@simd128() { + VL = Generic[MyInt8s@simd128](1) +} + + +// structure containing simd fields, and with simd methods + +// before +// A struct dependent on SIMD +type VectorC struct { + Field simd.Float32s +} +func (v *VectorC) MethodOfSimd() bool { + return false +} +func (v VectorC) Data() simd.Float32s { + return v.Field +} +func (v VectorC) Foo(x VectorC) VectorC { + return VectorC{Field: v.Field.Add(x.Field)} +} + +// dispatch +// technically there is none, but functions with panicking bodies +// remain because code must pass type checking before rewriting. +type VectorC struct { + Field simd.Float32s +} +func (v *VectorC) MethodOfSimd() bool { + panic(...) +} +func (v VectorC) Data() simd.Float32s { + panic(...) +} +func (v VectorC) Foo(x VectorC) VectorC { + panic(...) +} + +// specialized (128) + +// A struct dependent on SIMD +type VectorC@simd128 struct { + Field bridge.Float32x4 +} +func (v *VectorC@simd128) MethodOfSimd() bool { + return false +} +func (v VectorC@simd128) Data() bridge.Float32x4 { + return v.Field +} +func (v VectorC@simd128) Foo(x VectorC@simd128) VectorC@simd128 { + return VectorC@simd128{Field: v.Field.Add(x.Field)} +} + +*/ type Rewriter struct { pkg *types2.Package @@ -64,6 +187,7 @@ newDecls = r.generateForSize(fileAST, k, newDecls) } // Then replace original functions with dispatchers. + // This also edits the DeclList of fileAST. r.generateDispatchers(fileAST) fileAST.DeclList = append(fileAST.DeclList, newDecls...) @@ -73,6 +197,8 @@ func (r *Rewriter) generateDispatchers(fileAST *syntax.File) { var newDecls []syntax.Decl + change := false + for _, decl := range fileAST.DeclList { switch d := decl.(type) { case *syntax.FuncDecl: @@ -81,7 +207,7 @@ newDecls = append(newDecls, d) continue } obj := r.info.Defs[d.Name] - if !r.analyzer.dependentObj[obj] || r.analyzer.inSimd { + if !r.analyzer.isDependentObj[obj] || r.analyzer.inSimd { newDecls = append(newDecls, d) continue } @@ -92,12 +218,13 @@ newDecls = append(newDecls, d) continue } + change = true if r.analyzer.HasDependentSignature(sig) { - if o := r.info.Defs[d.Name]; o != nil && !o.Exported() { - // Drop unexported dependent signatures entirely - continue + if base.Debug.Simd > 0 { + base.Warn("%s: removing body of dependent-sig original function %v", d.Pos().String(), d.Name.Value) } - d.Body = r.blockOf(d.Pos(), r.panicStmt(d.Pos(), "unexpected call of original function rewritten to specialized SIMD")) + d.Body = r.blockOf(d.Pos(), r.panicStmt(d.Pos(), + "unexpected call of original function rewritten to specialized SIMD")) newDecls = append(newDecls, d) continue } @@ -110,33 +237,42 @@ case *syntax.VarDecl: // Keep var decls even if rewritten, so that pre-rewrite code parses correctly. // TODO figure out how to deal with side-effects in initializers. newDecls = append(newDecls, d) + case *syntax.TypeDecl: - if !r.analyzer.dependentObj[r.info.Defs[d.Name]] || r.analyzer.inSimd { - newDecls = append(newDecls, d) - } + // Keep all types; we need the untranslated copy if a method referencing it + // needs to typecheck pre-translation. + newDecls = append(newDecls, d) default: newDecls = append(newDecls, decl) } } + if !change { + return + } + fileAST.DeclList = newDecls if !r.analyzer.inSimd { // Inject an import to the bridge package (if not exists) hasArchSimd := false var simdImport *syntax.ImportDecl + p := fileAST.Pos() for _, decl := range fileAST.DeclList { if imp, ok := decl.(*syntax.ImportDecl); ok { if imp.Path.Value == `"`+archFullPkg+`"` { hasArchSimd = true + if simdImport == nil { + p = imp.Pos() + } } if imp.Path.Value == `"`+simdPkg+`"` { simdImport = imp + p = imp.Pos() } - } } - p := simdImport.Pos() + if !hasArchSimd { r.injectImport(fileAST, archFullPkg, p) } @@ -363,13 +499,13 @@ switch d := decl.(type) { case *syntax.FuncDecl: if d.Name != nil { - return r.analyzer.dependentObj[r.info.Defs[d.Name]] + return r.analyzer.isDependentObj[r.info.Defs[d.Name]] } case *syntax.TypeDecl: - return r.analyzer.dependentObj[r.info.Defs[d.Name]] + return r.analyzer.isDependentObj[r.info.Defs[d.Name]] case *syntax.VarDecl: for _, name := range d.NameList { - if r.analyzer.dependentObj[r.info.Defs[name]] { + if r.analyzer.isDependentObj[r.info.Defs[name]] { return true } } diff --git a/src/cmd/compile/internal/types2/call.go b/src/cmd/compile/internal/types2/call.go index 945d8bebc4d0fbeb1ab8df1400f0f68c5f6378e5..87c68ce9da349155b765bf2049110953d020585b 100644 --- a/src/cmd/compile/internal/types2/call.go +++ b/src/cmd/compile/internal/types2/call.go @@ -388,7 +388,7 @@ }() } // Before Go 1.21, uninstantiated or partially instantiated argument functions are - // nor permitted. Checker.funcInst must infer missing type arguments in that case. + // not permitted. Checker.funcInst must infer missing type arguments in that case. infer := true // for -lang < go1.21 n := len(elist) if n > 0 && check.allowVersion(go1_21) { diff --git a/src/cmd/compile/internal/types2/index.go b/src/cmd/compile/internal/types2/index.go index 0ea6372b1df36b3e85897fe5726122219b35b0e8..7be7ca61f884caa8b63d8a9592000d0e066b9645 100644 --- a/src/cmd/compile/internal/types2/index.go +++ b/src/cmd/compile/internal/types2/index.go @@ -114,7 +114,7 @@ x.invalidate() return false } var key operand - check.expr(nil, &key, index) + check.genericExpr(&key, index, nil) check.assignment(&key, typ.key, "map index") // ok to continue even if indexing failed - map element type is known x.mode_ = mapindex @@ -188,7 +188,7 @@ x.invalidate() return false } var k operand - check.expr(nil, &k, index) + check.genericExpr(&k, index, nil) check.assignment(&k, key, "map index") // ok to continue even if indexing failed - map element type is known x.mode_ = mapindex diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go index 74cafe9613917db0ee0db2c268b0110d580ae090..44cec1ae79e7108d8ccff1440b6c113dd8925ab0 100644 --- a/src/cmd/go/alldocs.go +++ b/src/cmd/go/alldocs.go @@ -2401,6 +2401,10 @@ // // GODEBUG=gocachetest=1 causes the go command to print details of its // decisions about whether to reuse a cached test result. // +// The GOCACHEPROG environment variable can be used to provide an +// externally managed build cache. For details see: +// "go doc cmd/go/internal/cacheprog". +// // # Environment variables // // The go command and the tools it invokes consult environment variables diff --git a/src/cmd/go/internal/cacheprog/cacheprog.go b/src/cmd/go/internal/cacheprog/cacheprog.go index 9379636e5ab6628025d187f7ce877a07dfd95ffb..168df83b9d3f0833625d5564274756660a13fb3e 100644 --- a/src/cmd/go/internal/cacheprog/cacheprog.go +++ b/src/cmd/go/internal/cacheprog/cacheprog.go @@ -122,5 +122,7 @@ // For "get" and "put" requests. // DiskPath is the absolute path on disk of the body corresponding to a // "get" (on cache hit) or "put" request's ActionID. + // By convention, cached files are stored without any filename extensions. + // Some tools may filter out files with extensions. DiskPath string `json:",omitempty"` } diff --git a/src/cmd/go/internal/help/helpdoc.go b/src/cmd/go/internal/help/helpdoc.go index 4f76f7f9c1fc68c6a4d853d6a4fb6c6c7c50e507..b305457a23d1045a4e00a99b91f7e03fa65330a4 100644 --- a/src/cmd/go/internal/help/helpdoc.go +++ b/src/cmd/go/internal/help/helpdoc.go @@ -924,6 +924,10 @@ The output is voluminous but can be useful for debugging the cache. GODEBUG=gocachetest=1 causes the go command to print details of its decisions about whether to reuse a cached test result. + +The GOCACHEPROG environment variable can be used to provide an +externally managed build cache. For details see: +"go doc cmd/go/internal/cacheprog". `, } diff --git a/src/go/types/call.go b/src/go/types/call.go index 530b833fbf2194e5a4376902a314bbe9ad4739d9..4d0bda0d4042695749b541c30df05d0badc22f95 100644 --- a/src/go/types/call.go +++ b/src/go/types/call.go @@ -390,7 +390,7 @@ }() } // Before Go 1.21, uninstantiated or partially instantiated argument functions are - // nor permitted. Checker.funcInst must infer missing type arguments in that case. + // not permitted. Checker.funcInst must infer missing type arguments in that case. infer := true // for -lang < go1.21 n := len(elist) if n > 0 && check.allowVersion(go1_21) { diff --git a/src/go/types/index.go b/src/go/types/index.go index 524e93153a0c2ea3e318b4be3acfa34261b764bc..4ed3df46454d961b93c2e1ded98df46a7b4ef245 100644 --- a/src/go/types/index.go +++ b/src/go/types/index.go @@ -115,7 +115,7 @@ x.invalidate() return false } var key operand - check.expr(nil, &key, index) + check.genericExpr(&key, index, nil) check.assignment(&key, typ.key, "map index") // ok to continue even if indexing failed - map element type is known x.mode_ = mapindex @@ -189,7 +189,7 @@ x.invalidate() return false } var k operand - check.expr(nil, &k, index) + check.genericExpr(&k, index, nil) check.assignment(&k, key, "map index") // ok to continue even if indexing failed - map element type is known x.mode_ = mapindex diff --git a/src/internal/types/testdata/fixedbugs/issue80042.go b/src/internal/types/testdata/fixedbugs/issue80042.go new file mode 100644 index 0000000000000000000000000000000000000000..665628dc93e0d26b54e21b770d12a589fb794ba2 --- /dev/null +++ b/src/internal/types/testdata/fixedbugs/issue80042.go @@ -0,0 +1,19 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package p + +func f[P any](_ P) {} + +func _(x map[func /* ERROR "invalid map key type func(int)" */ (int)]bool) { + // While functions are not valid map keys, the intended type for P + // is still unambiguous here. Avoid "cannot use generic function f + // without instantiation". + x[f] = true +} + +// same thinking applies through a type parameter +func _[P map[func /* ERROR "invalid map key type func(int)" */ (int)]bool](x P) { + x[f] = true +} diff --git a/src/net/http/request.go b/src/net/http/request.go index eb6e59888bae14524994137ba564f2f99d52ce84..a95e9916813667fe390892d12dd89cd81dfa7463 100644 --- a/src/net/http/request.go +++ b/src/net/http/request.go @@ -279,6 +279,10 @@ // After the HTTP request is sent the map values can be updated while // the request body is read. Once the body returns EOF, the caller must // not mutate Trailer. // + // Writing a request whose Trailer contains a key with invalid bytes + // (such as CR or LF), or such a value present when Write begins, + // returns an error. + // // Few HTTP clients, servers, or proxies support HTTP trailers. Trailer Header diff --git a/src/net/http/requestwrite_test.go b/src/net/http/requestwrite_test.go index 8b097cd5e15d1f6f4e5ddb68095c9c322b18ab8f..8903f2c9266abed3010d97ca1f41129ee3f0c1a6 100644 --- a/src/net/http/requestwrite_test.go +++ b/src/net/http/requestwrite_test.go @@ -608,6 +608,121 @@ "Host: example.com\r\n" + "User-Agent: Go-http-client/1.1\r\n" + "Content-Length: 0\r\n\r\n", }, + + // Valid Trailer keeps working after trailer validation. Issue #78775 + 27: { + Req: Request{ + Method: "POST", + URL: &url.URL{ + Scheme: "http", + Host: "example.com", + Path: "/", + }, + ProtoMajor: 1, + ProtoMinor: 1, + Header: Header{}, + TransferEncoding: []string{"chunked"}, + Trailer: Header{"X-Trailer": {"ok"}}, + }, + + Body: []byte("abcdef"), + + WantWrite: "POST / HTTP/1.1\r\n" + + "Host: example.com\r\n" + + "User-Agent: Go-http-client/1.1\r\n" + + "Transfer-Encoding: chunked\r\n" + + "Trailer: X-Trailer\r\n\r\n" + + chunk("abcdef") + + "0\r\n" + + "X-Trailer: ok\r\n" + + "\r\n", + }, + + // Trailer names with control characters must not reach the wire, + // where they would permit header injection on the "Trailer:" line. + // Issue #78775 + 28: { + Req: Request{ + Method: "POST", + URL: &url.URL{ + Scheme: "http", + Host: "example.com", + Path: "/", + }, + ProtoMajor: 1, + ProtoMinor: 1, + Header: Header{}, + TransferEncoding: []string{"chunked"}, + Trailer: Header{"X-Trailer\r\nInjected: 1": {"ok"}}, + }, + + Body: []byte("abcdef"), + + WantError: errors.New(`net/http: invalid trailer field name "X-Trailer\r\nInjected: 1"`), + }, + + // Trailer values with control characters are rejected as well. Issue #78775 + 29: { + Req: Request{ + Method: "POST", + URL: &url.URL{ + Scheme: "http", + Host: "example.com", + Path: "/", + }, + ProtoMajor: 1, + ProtoMinor: 1, + Header: Header{}, + TransferEncoding: []string{"chunked"}, + Trailer: Header{"X-Trailer": {"evil\r\nInjected: 1"}}, + }, + + Body: []byte("abcdef"), + + WantError: errors.New(`net/http: invalid trailer field value for "X-Trailer"`), + }, + + // An empty Trailer name is rejected. Issue #78775 + 30: { + Req: Request{ + Method: "POST", + URL: &url.URL{ + Scheme: "http", + Host: "example.com", + Path: "/", + }, + ProtoMajor: 1, + ProtoMinor: 1, + Header: Header{}, + TransferEncoding: []string{"chunked"}, + Trailer: Header{"": {"ok"}}, + }, + + Body: []byte("abcdef"), + + WantError: errors.New(`net/http: invalid trailer field name ""`), + }, + + // A later (non-first) value in a Trailer is validated too. Issue #78775 + 31: { + Req: Request{ + Method: "POST", + URL: &url.URL{ + Scheme: "http", + Host: "example.com", + Path: "/", + }, + ProtoMajor: 1, + ProtoMinor: 1, + Header: Header{}, + TransferEncoding: []string{"chunked"}, + Trailer: Header{"X-Trailer": {"ok", "evil\r\nInjected: 1"}}, + }, + + Body: []byte("abcdef"), + + WantError: errors.New(`net/http: invalid trailer field value for "X-Trailer"`), + }, } func TestRequestWrite(t *testing.T) { diff --git a/src/net/http/responsewrite_test.go b/src/net/http/responsewrite_test.go index 226ad7225b2d71c37b06933839440c563107b985..84e0f3b43ae1ffe7cd254f621aa7a0dfafc6a600 100644 --- a/src/net/http/responsewrite_test.go +++ b/src/net/http/responsewrite_test.go @@ -288,3 +288,45 @@ continue } } } + +// Response.Write shares the trailer validation added for Issue #78775 with +// Request.Write, so an invalid trailer name or value must be rejected rather +// than written. +func TestResponseWriteInvalidTrailer(t *testing.T) { + tests := []struct { + name string + trailer Header + wantErr string + }{ + { + name: "key", + trailer: Header{"X-Trailer\r\nInjected: 1": {"ok"}}, + wantErr: `net/http: invalid trailer field name "X-Trailer\r\nInjected: 1"`, + }, + { + name: "value", + trailer: Header{"X-Trailer": {"evil\r\nInjected: 1"}}, + wantErr: `net/http: invalid trailer field value for "X-Trailer"`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resp := Response{ + StatusCode: 200, + ProtoMajor: 1, + ProtoMinor: 1, + Request: dummyReq("GET"), + Header: Header{}, + Body: io.NopCloser(strings.NewReader("abcdef")), + ContentLength: -1, + TransferEncoding: []string{"chunked"}, + Trailer: tt.trailer, + } + var b strings.Builder + err := resp.Write(&b) + if err == nil || err.Error() != tt.wantErr { + t.Fatalf("Response.Write error = %v, want %q", err, tt.wantErr) + } + }) + } +} diff --git a/src/net/http/transfer.go b/src/net/http/transfer.go index 675551287fa3d642ed2e347236c4578f58e10f37..faa1c3853a134ea6d08f77322387ebca3366b4b6 100644 --- a/src/net/http/transfer.go +++ b/src/net/http/transfer.go @@ -146,6 +146,13 @@ if !chunked(t.TransferEncoding) { t.Trailer = nil } + // Validate Trailer names and values. The names are later written + // unmodified on the "Trailer:" line of the header, so invalid bytes + // (in particular CR and LF) would permit header injection. (Issue 78775.) + if err := validateHeaders(t.Trailer); err != "" { + return nil, fmt.Errorf("net/http: invalid trailer %s", err) + } + return t, nil } diff --git a/src/simd/archsimd/_gen/midway/comments.yaml b/src/simd/archsimd/_gen/midway/comments.yaml index cba83922926900b313d8b6cb4341cf844c64192c..14056990521d60de476a96a8ae2eefb2a6dd48ef 100644 --- a/src/simd/archsimd/_gen/midway/comments.yaml +++ b/src/simd/archsimd/_gen/midway/comments.yaml @@ -28,8 +28,8 @@ RotateAllRight: "RotatesAllRight rotates all elements right by y bits." ShiftAllLeft: "ShiftAllLeft shifts all elements left by y bits." ShiftAllRight: "ShiftAllRight shifts all elements right by y bits." Sqrt: "Sqrt returns the element-wise square root of x." - Store: "StoreSlice stores the vector elements into the slice s." - StorePart: "StoreSlicePart stores a partial vector into the slice s." + Store: "Store stores the vector elements into the slice s." + StorePart: "StorePart stores a partial vector into the slice s and returns the number actually stored." String: "String returns a string representation of the vector." Sub: "Sub returns the element-wise difference of x and y." SubSaturated: "SubSaturated returns the element-wise saturated difference of x and y." diff --git a/src/simd/doc.go b/src/simd/doc.go new file mode 100644 index 0000000000000000000000000000000000000000..a40603ec1b61de59db3c91fb586988c9e42d9e12 --- /dev/null +++ b/src/simd/doc.go @@ -0,0 +1,91 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build goexperiment.simd + +/* +Package simd implements portable and vector-size-agnostic SIMD types, +and functions and methods for working with these types. SIMD types +are either implemented in hardware (for example, arm64 "Neon" or +amd64 "AVX/AVX2/AVX512") using the corresponding types in the +[simd/archsimd] package, or emulated in pure Go. In all cases, the +vector length is at least 128 bits, and within a given program +execution, all vectors have the same length. + +# SIMD Types + +There is a simd type corresponding to each primitive numeric type, +except for complex64 and complex128. Each of these is the type name, +capitalized, with an "s" suffix, for example [Int8s], [Uint16s], +or [Float64s]. + +There are also simd "mask" types that abstract the mask registers +present in some architectures, otherwise these will be implemented +as bit masks. + +# Obtaining SIMD values + +The zero value of a SIMD vector type is valid and represents a zero vector. + +For each SIMD type, "Load(s []) " +loads a full verctor of the type from a long-enough slice. + +For slices that are not long enough, "LoadPart(s []) (, int)" +will load as many elements as are available from the slice, fill the remainder +with zero, and also return the number that were loaded. + +For each SIMD type, "Broadcast(x type) " +returns a vector whose elements are all initialized to x. + +Examples: + - [LoadInt8s] + - [LoadUint16sPart] + - [BroadcastFloat32s] + +# Operations + +SIMD types provide methods for unary ([Float32s.Abs], [Int16s.Not]), binary +([Float64s.Add], [Uint32s.GreaterEqual]), and ternary operations ([Int64s.IfElse], [Float32s.MulAdd]). +Relational operations produce masks. + +SIMD types also support conversions between types, both those that are +mostly value-preserving ([Int32s.ConvertToFloat32], [Float32s.ConvertToInt32]) and +those that change types without altering the underlying vector bit +pattern. + +Signed integer types convert to mask types ([Int16s.ToMask]), but this is a +comparison against zero, not a simple bitwise conversion. Mask types +convert to signed integers in an operation ([Mask32s.ToInt32s]) that may be a +zero-cost bitwise conversion, or not, depending on the underlying hardware. + +# Storing + +SIMD vector types have two methods, one for storing the entire vector into a slice +"Store([])"" and a second for storing part of a vector into a slice "StorePart([]) int". +StorePart returns the number of elements actually stored. + +# String conversion + +Vectors and masks provide a String method for conversion to strings. + +# Conversion to and from simd/archsimd types. + +Each SIMD vector type has a "ToArch() any" method that returns the type +supported by the current hardware as an "any". Code using +these methods must be build-tagged to the relevant architecture(s) +and type-assert the returned value to the appropriate type. + +The simd package also includes generic functions for converting an +architecture-dependent simd/archsimd value (e.g. [archsimd.Float32x4]) +into the corresponding simd type. This function will panic if the +correspondence is incorrect. + +For an example of converting between [simd] and [arch/simd] types, +see the test file sum_amd64_test.go. +*/ +package simd + +// BUG(reflection): Calls won't work, and there may be other bugs. +// BUG(global initialization): SIMD-dependent var initializers don't work. +// BUG(modified names): Modified names may appear in stack traces and debugging. diff --git a/src/simd/simd_stubs.go b/src/simd/simd_stubs.go index 6cb59214fe8b4bf3a5131a82a3f3c188e41e7579..5206290a57b52f45d0f58dd34c96cfacf690c43b 100644 --- a/src/simd/simd_stubs.go +++ b/src/simd/simd_stubs.go @@ -78,10 +78,10 @@ // Or returns the bitwise OR of x and y. func (x Int8s) Or(y Int8s) Int8s -// StoreSlice stores the vector elements into the slice s. +// Store stores the vector elements into the slice s. func (x Int8s) Store(s []int8) -// StoreSlicePart stores a partial vector into the slice s. +// StorePart stores a partial vector into the slice s and returns the number actually stored. func (x Int8s) StorePart(s []int8) int // String returns a string representation of the vector. @@ -186,10 +186,10 @@ // ShiftAllRight shifts all elements right by y bits. func (x Int16s) ShiftAllRight(shift uint64) Int16s -// StoreSlice stores the vector elements into the slice s. +// Store stores the vector elements into the slice s. func (x Int16s) Store(s []int16) -// StoreSlicePart stores a partial vector into the slice s. +// StorePart stores a partial vector into the slice s and returns the number actually stored. func (x Int16s) StorePart(s []int16) int // String returns a string representation of the vector. @@ -294,10 +294,10 @@ // ShiftAllRight shifts all elements right by y bits. func (x Int32s) ShiftAllRight(shift uint64) Int32s -// StoreSlice stores the vector elements into the slice s. +// Store stores the vector elements into the slice s. func (x Int32s) Store(s []int32) -// StoreSlicePart stores a partial vector into the slice s. +// StorePart stores a partial vector into the slice s and returns the number actually stored. func (x Int32s) StorePart(s []int32) int // String returns a string representation of the vector. @@ -381,10 +381,10 @@ // ShiftAllLeft shifts all elements left by y bits. func (x Int64s) ShiftAllLeft(shift uint64) Int64s -// StoreSlice stores the vector elements into the slice s. +// Store stores the vector elements into the slice s. func (x Int64s) Store(s []int64) -// StoreSlicePart stores a partial vector into the slice s. +// StorePart stores a partial vector into the slice s and returns the number actually stored. func (x Int64s) StorePart(s []int64) int // String returns a string representation of the vector. @@ -471,10 +471,10 @@ // ReshapeToUint64s reinterprets the vector bits as a Uint64s vector. func (x Uint8s) ReshapeToUint64s() Uint64s -// StoreSlice stores the vector elements into the slice s. +// Store stores the vector elements into the slice s. func (x Uint8s) Store(s []uint8) -// StoreSlicePart stores a partial vector into the slice s. +// StorePart stores a partial vector into the slice s and returns the number actually stored. func (x Uint8s) StorePart(s []uint8) int // String returns a string representation of the vector. @@ -582,10 +582,10 @@ // ShiftAllRight shifts all elements right by y bits. func (x Uint16s) ShiftAllRight(shift uint64) Uint16s -// StoreSlice stores the vector elements into the slice s. +// Store stores the vector elements into the slice s. func (x Uint16s) Store(s []uint16) -// StoreSlicePart stores a partial vector into the slice s. +// StorePart stores a partial vector into the slice s and returns the number actually stored. func (x Uint16s) StorePart(s []uint16) int // String returns a string representation of the vector. @@ -690,10 +690,10 @@ // ShiftAllRight shifts all elements right by y bits. func (x Uint32s) ShiftAllRight(shift uint64) Uint32s -// StoreSlice stores the vector elements into the slice s. +// Store stores the vector elements into the slice s. func (x Uint32s) Store(s []uint32) -// StoreSlicePart stores a partial vector into the slice s. +// StorePart stores a partial vector into the slice s and returns the number actually stored. func (x Uint32s) StorePart(s []uint32) int // String returns a string representation of the vector. @@ -818,10 +818,10 @@ // ShiftAllRight shifts all elements right by y bits. func (x Uint64s) ShiftAllRight(shift uint64) Uint64s -// StoreSlice stores the vector elements into the slice s. +// Store stores the vector elements into the slice s. func (x Uint64s) Store(s []uint64) -// StoreSlicePart stores a partial vector into the slice s. +// StorePart stores a partial vector into the slice s and returns the number actually stored. func (x Uint64s) StorePart(s []uint64) int // String returns a string representation of the vector. @@ -899,10 +899,10 @@ // Sqrt returns the element-wise square root of x. func (x Float32s) Sqrt() Float32s -// StoreSlice stores the vector elements into the slice s. +// Store stores the vector elements into the slice s. func (x Float32s) Store(s []float32) -// StoreSlicePart stores a partial vector into the slice s. +// StorePart stores a partial vector into the slice s and returns the number actually stored. func (x Float32s) StorePart(s []float32) int // String returns a string representation of the vector. @@ -977,10 +977,10 @@ // Sqrt returns the element-wise square root of x. func (x Float64s) Sqrt() Float64s -// StoreSlice stores the vector elements into the slice s. +// Store stores the vector elements into the slice s. func (x Float64s) Store(s []float64) -// StoreSlicePart stores a partial vector into the slice s. +// StorePart stores a partial vector into the slice s and returns the number actually stored. func (x Float64s) StorePart(s []float64) int // String returns a string representation of the vector. diff --git a/src/simd/sizeof_test.go b/src/simd/testdata/sizeof_test.go rename from src/simd/sizeof_test.go rename to src/simd/testdata/sizeof_test.go index 4202dc2939203483d702f354a50505fb18ff1e50..8d8fad980445acf4d46cb2832451f16dd2fffdc8 100644 --- a/src/simd/sizeof_test.go +++ b/src/simd/testdata/sizeof_test.go @@ -4,11 +4,11 @@ // license that can be found in the LICENSE file. //go:build goexperiment.simd -package simd_test +package testdata_test import ( "simd" - "simd/testdata" + "simd/testdata/pkg" "testing" "unsafe" ) @@ -19,8 +19,8 @@ func TestSizeof(t *testing.T) { var f float32 sv0 := int(unsafe.Sizeof(v)) sv1 := v.Len() * int(unsafe.Sizeof(f)) - sV := int(unsafe.Sizeof(testdata.V)) - sF := int(unsafe.Sizeof(testdata.F())) + sV := int(unsafe.Sizeof(pkg.V)) + sF := int(unsafe.Sizeof(pkg.F())) if sv0 != sv1 { t.Errorf("sv0=%d and sv1=%d should be equal but are not", sv0, sv1) } diff --git a/src/simd/testdata/iface/iface.go b/src/simd/testdata/iface/iface.go new file mode 100644 index 0000000000000000000000000000000000000000..697ba6b5eb5366f015bbbe3ed4d4d75dd33d1f6f --- /dev/null +++ b/src/simd/testdata/iface/iface.go @@ -0,0 +1,111 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build goexperiment.simd + +package iface + +import ( + "simd" +) + +// A SIMD-dependent type alias +type MyInt8s = simd.Int8s + +func Generic[T haslen](x int) int { + var v T + return x + v.Len() +} + +// VL = Generic[MyInt8s](1) doesn't currently work. +// TODO: automatically transform those initializers into what is done here instead. +var VL int + +func init() { + VL = Generic[MyInt8s](1) +} + +// A struct dependent on SIMD +type VectorC struct { + Field simd.Float32s +} + +type Ftype func(x any) any + +var Fvar Ftype + +// A dependent function with a dependent signature +func (v *VectorC) MethodOfSimd() bool { + return false +} + +func (v VectorC) Data() simd.Float32s { + return v.Field +} + +func (v VectorC) Foo(x VectorC) VectorC { + return VectorC{Field: v.Field.Add(x.Field)} +} + +func (v VectorC) Bar(x VectorC) VectorC { + return VectorC{Field: v.Field.Add(x.Field)} +} + +type Vint interface { + MethodOfSimd() bool +} + +type haslen interface { + Len() int +} + +type HasFoo[T any] interface { + Foo(x T) T +} + +type HasBar interface { + Bar(x VectorC) VectorC +} + +//go:noinline +func MakeHasFoo[T HasFoo[T]](v T) HasFoo[T] { + return v +} + +func MakeHasBar(v VectorC) HasBar { + return v +} + +func VC(x simd.Float32s) VectorC { + return VectorC{x} +} + +type EmbedBar struct { + HasBar +} + +type EmbedFoo[T HasFoo[T]] struct { + HasFoo[T] +} + +func MakeHasEmbedFoo[T HasFoo[T]](v T) EmbedFoo[T] { + return EmbedFoo[T]{MakeHasFoo[T](v)} +} + +//go:noinline +func MakeHasEmbedBar(v VectorC) EmbedBar { + return EmbedBar{MakeHasBar(v)} +} + +type HasQux[T any] interface { + Qux(x T) HasFoo[T] +} + +type Q struct { + q *Q +} + +func (q *Q) Qux(v VectorC) HasFoo[VectorC] { + return v +} diff --git a/src/simd/testdata/iface_test.go b/src/simd/testdata/iface_test.go new file mode 100644 index 0000000000000000000000000000000000000000..bec1e5316bada94e636b385141d6b1c216eb998c --- /dev/null +++ b/src/simd/testdata/iface_test.go @@ -0,0 +1,136 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build goexperiment.simd + +package testdata_test + +import ( + "reflect" + "simd" + "simd/testdata/iface" + "testing" +) + +func TestIFaceFoo(t *testing.T) { + u := simd.BroadcastFloat32s(4) + v := simd.BroadcastFloat32s(1) + vc := iface.VC(v) + uc := iface.VC(u) + + hv := iface.MakeHasFoo(vc) // generic interface w/ Foo method + + sum := hv.Foo(uc) + + s := make([]float32, u.Len()) + + sum.Data().Store(s) // The method of a dependent type works. + + if s[0] != 5 { + t.Errorf("(from Data()) expected 5, got %f", s[0]) + } + + sum.Field.Store(s) + + if s[0] != 5 { + t.Errorf("(from Field) expected 5, got %f", s[0]) + } +} + +func TestIFaceBar(t *testing.T) { + u := simd.BroadcastFloat32s(4) + v := simd.BroadcastFloat32s(1) + vc := iface.VC(v) + uc := iface.VC(u) + + hv := iface.MakeHasBar(vc) // non-generic interface w/ Foo method + + sum := hv.Bar(uc) + + s := make([]float32, u.Len()) + + sum.Data().Store(s) // The method of a dependent type works. + + if s[0] != 5 { + t.Errorf("(from Data()) expected 5, got %f", s[0]) + } + + sum.Field.Store(s) + + if s[0] != 5 { + t.Errorf("(from Field) expected 5, got %f", s[0]) + } +} + +func TestIFaceEmbedFoo(t *testing.T) { + u := simd.BroadcastFloat32s(4) + v := simd.BroadcastFloat32s(1) + vc := iface.VC(v) + uc := iface.VC(u) + + hv := iface.MakeHasEmbedFoo(vc) // generic interface w/ Foo method + + sum := hv.Foo(uc) + + s := make([]float32, u.Len()) + + sum.Data().Store(s) // The method of a dependent type works. + + if s[0] != 5 { + t.Errorf("(from Data()) expected 5, got %f", s[0]) + } + + sum.Field.Store(s) + + if s[0] != 5 { + t.Errorf("(from Field) expected 5, got %f", s[0]) + } + + rv := reflect.ValueOf(hv) + rt := rv.Type() + + t.Logf("reflect.value is %v", rv) + t.Logf("reflect.type is %v", rt) +} + +func TestIFaceEmbedBar(t *testing.T) { + u := simd.BroadcastFloat32s(4) + v := simd.BroadcastFloat32s(1) + vc := iface.VC(v) + uc := iface.VC(u) + + hv := iface.MakeHasEmbedBar(vc) // generic interface w/ Foo method + + sum := hv.Bar(uc) + + s := make([]float32, u.Len()) + + sum.Data().Store(s) // The method of a dependent type works. + + if s[0] != 5 { + t.Errorf("(from Data()) expected 5, got %f", s[0]) + } + + sum.Field.Store(s) + + if s[0] != 5 { + t.Errorf("(from Field) expected 5, got %f", s[0]) + } + + rv := reflect.ValueOf(hv) + rt := rv.Type() + + t.Logf("reflect.value is %v", rv) + t.Logf("reflect.type is %v", rt) +} + +func TestIFaceVL(t *testing.T) { + var v simd.Int8s + if a, b := iface.VL, iface.Generic[simd.Int8s](1); a != b { + t.Errorf("expected iface.VL [%d] == iface.Generic[simd.Int8s](1) [%d], but not true", a, b) + } + if a, b := iface.VL, v.Len()+1; a != b { + t.Errorf("expected iface.VL [%d] == v.Len()+1 [%d], but not true", a, b) + } +} diff --git a/src/simd/testdata/mains/compiles.go b/src/simd/testdata/compiles_test.go rename from src/simd/testdata/mains/compiles.go rename to src/simd/testdata/compiles_test.go index 1ce17c0f67fe219b4a44f5e2cfc0e1191b0e0a74..73b28e033c6cca0b0900bd44c74804d8ae908662 100644 --- a/src/simd/testdata/mains/compiles.go +++ b/src/simd/testdata/compiles_test.go @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file. //go:build goexperiment.simd -package main +package testdata_test // For testing purposes, this SHOULD compile, because // the "simd" type whose unsafe.Sizeof is used in a @@ -13,14 +13,16 @@ // name (but not path) happens to be "simd". import ( "simd/testdata/simd" + "testing" "unsafe" ) -var v [1]simd.HasConstantSize24 -var u [unsafe.Sizeof(v)]byte +var v_for_sizeof [1]simd.HasConstantSize24 +var u [unsafe.Sizeof(v_for_sizeof)]byte -func main() { +func TestCompiles(t *testing.T) { + if len(u) != 24 { - println("FAIL") + t.Errorf("len(u) is %d, instead of expected 24", len(u)) } } diff --git a/src/simd/testdata/mains/errors.go b/src/simd/testdata/errors_test.go rename from src/simd/testdata/mains/errors.go rename to src/simd/testdata/errors_test.go index 9e72119f6eecbfe5c259267e1f81c7f70b252b33..9524bbea51b5c96fb6db57d1a7b7eebdd82ded9c 100644 --- a/src/simd/testdata/mains/errors.go +++ b/src/simd/testdata/errors_test.go @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file. //go:build goexperiment.simd -package main +package testdata_test // For testing purposes, this should NOT compile, because // it uses the unsafe.Sizeof of a "simd" type in a constant @@ -12,13 +12,14 @@ // context (as an array size). import ( "simd" + "testing" "unsafe" ) -var v [1]simd.Int8s -var u [unsafe.Sizeof(v)]byte +var v_from_simd [1]simd.Int8s -func main() { +func TestItDoesNotCompile(t *testing.T) { + var u [unsafe.Sizeof(v_from_simd)]byte if len(u) != 16 { println("FAIL") } diff --git a/src/simd/testdata/v.go b/src/simd/testdata/pkg/v.go rename from src/simd/testdata/v.go rename to src/simd/testdata/pkg/v.go index 1c6f7ddbb60fa2d6905cf7a982dcf3d1ef14a569..2bd6078a93d0145b3c09a6f6988997eecf9973c9 100644 --- a/src/simd/testdata/v.go +++ b/src/simd/testdata/pkg/v.go @@ -4,7 +4,7 @@ // license that can be found in the LICENSE file. //go:build goexperiment.simd -package testdata +package pkg // For testing purposes, F and V are exported simd types, // and should have the proper (variable) unsafe.Sizeof diff --git a/src/simd/testdata_test.go b/src/simd/testdata_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b8623cfcee995df25915dabe6147b7128654e00c --- /dev/null +++ b/src/simd/testdata_test.go @@ -0,0 +1,63 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build goexperiment.simd + +package simd_test + +import ( + "internal/testenv" + "os" + "strings" + "testing" +) + +func common(t *testing.T, dir, what, failWith string) { + t.Helper() + t.Logf("subprocess test in testdata") + testenv.MustHaveGoRun(t) + args := []string{"test", "-C", dir} + if testing.Verbose() { + args = append(args, "-v") + } + args = append(args, what) + cmd := testenv.Command(t, testenv.GoToolPath(t), args...) + + if failWith == "" { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + t.Error(err) + } + } else { + combined, err := cmd.CombinedOutput() + combinedString := string(combined) + sawFailure := strings.Contains(combinedString, failWith) + if err == nil && !sawFailure { + t.Errorf("Saw no error and did not see expected failure string '%s' in '%s'", failWith, combinedString) + } else if err == nil && sawFailure { + t.Errorf("Saw no error but did see expected failure string '%s'", failWith) + } else if err != nil && !sawFailure { + t.Errorf("Saw error %v but did see expected failure string '%s' in '%s'", err, failWith, combinedString) + } else /* err != nil && sawFailure */ { + t.Logf("Saw error %v and expected failure string '%s'", err, failWith) + } + } +} +func TestIFace(t *testing.T) { + common(t, "testdata", "iface_test.go", "") +} + +func TestSizeof(t *testing.T) { + common(t, "testdata", "sizeof_test.go", "") +} + +func TestCompileOk(t *testing.T) { + common(t, "testdata", "compiles_test.go", "") +} + +func TestCompileError(t *testing.T) { + common(t, "testdata", "errors_test.go", + "array length unsafe.Sizeof(v_from_simd) (value of type uintptr) must be constant") +}