Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,13 @@ jobs:
- name: Test tsvc (amd64, SIMD)
run: make tsvc-test

- name: Install qemu-user-static
run: sudo apt-get update && sudo apt-get install -y qemu-user-static
# ubuntu-26.04 has no qemu-user-static: qemu-user ships the static
# emulators there, without the -static names the Makefile runs.
- name: Install qemu-user
run: |
sudo apt-get update && sudo apt-get install -y qemu-user
command -v qemu-aarch64-static || sudo ln -s "$(command -v qemu-aarch64)" /usr/local/bin/qemu-aarch64-static
qemu-aarch64-static --version

- name: Test tsvc (arm64, qemu)
run: make tsvc-test-qemu-arm64
Expand Down
6 changes: 3 additions & 3 deletions BUGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

loopvec rewrites code to use `GOEXPERIMENT=simd`, which is new, so most of what it hits
is in the Go toolchain. This file lists the Go issues that affect loopvec. "tip" means
gotip, "1.27.1" is the release toolchain loopvec is tested against.
gotip, "1.27.3" is the release toolchain loopvec is tested against.

| Issue | Status | Affects | loopvec's answer |
|---|---|---|---|
| [#80657](https://github.com/golang/go/issues/80657) simd in a method: internal compiler error | fixed on tip (CL 839405), still crashes on 1.27.1 | `-methods` | methods are skipped unless `-methods` is passed |
| [#80657](https://github.com/golang/go/issues/80657) simd in a method: internal compiler error | fixed on tip (CL 839405), still crashes on 1.27.3 | `-methods` | methods are skipped unless `-methods` is passed |
| [#80835](https://github.com/golang/go/issues/80835) legacy SSE encodings in functions using simd cause AVX-SSE transition penalties | open, fix pending (CLs 825145, 825146, in tip) | speed of any scalar float code that runs after a rewritten loop, in the same function or another | none; the portable `simd` package has no `ClearAVXUpperBits` |
| [#81844](https://github.com/golang/go/issues/81844) blank identifier parameters rejected | fixed on tip (CL 841145) | every rewrite of a function with a `_` parameter | the generated file renames them `_p0`, `_p1`, ... |
| [#81845](https://github.com/golang/go/issues/81845) calls left unresolved when inlined into a package initializer | closed as a duplicate of [#80689](https://github.com/golang/go/issues/80689), which is open | loops in package-level `var x = func() {...}()` | not rewritten (the generated initializer fails to link), see `package_initializer_no_rewrite.txt` and `toolexec_package_initializer.txt` |
Expand All @@ -19,7 +19,7 @@ gotip, "1.27.1" is the release toolchain loopvec is tested against.

A function with a receiver that contains simd code failed to compile with
`internal compiler error: missing Types entry`. loopvec does not rewrite loops in methods
by default; `-methods` turns it on. The fix is on tip. On 1.27.1 the crash is still there,
by default; `-methods` turns it on. The fix is on tip. On 1.27.3 the crash is still there,
and `flag_methods_ice.txt` pins it, so that test starts failing when the pinned toolchain
gets the fix. Reported by someone else; the fix is [CL 839405](https://go.dev/cl/839405).

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export GOTOOLCHAIN ?= go1.27.1
export GOTOOLCHAIN ?= go1.27.3

.DEFAULT_GOAL := build

Expand Down
1 change: 1 addition & 0 deletions cmd/loopvec-toolexec/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ func check(fset *token.FileSet, files []*ast.File, c compileArgs, cfg *importcfg
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
Scopes: make(map[ast.Node]*types.Scope),
}
var errs []error
goarch := os.Getenv("GOARCH")
Expand Down
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/mauri870/loopvec
go 1.27.1

require (
golang.org/x/tools v0.51.0
golang.org/x/tools v0.52.1-0.20261030190145-0f433775b36d
rsc.io/script v0.0.2
)

Expand Down Expand Up @@ -207,10 +207,10 @@ require (
go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v3 v3.0.5 // indirect
golang.org/x/exp/typeparams v0.0.0-20260811152304-ee035b5b010f // indirect
golang.org/x/mod v0.41.0 // indirect
golang.org/x/sync v0.23.0 // indirect
golang.org/x/sys v0.48.0 // indirect
golang.org/x/telemetry v0.0.0-20260924152758-ed294f943157 // indirect
golang.org/x/mod v0.42.0 // indirect
golang.org/x/sync v0.24.0 // indirect
golang.org/x/sys v0.49.0 // indirect
golang.org/x/telemetry v0.0.0-20261009151841-97e8ea77aa2a // indirect
golang.org/x/text v0.41.0 // indirect
google.golang.org/protobuf v1.36.12 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
Expand Down
24 changes: 12 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -688,8 +688,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
golang.org/x/mod v0.42.0 h1:ICeL3t2L30ljSPHHxDnR4fqVcBt/zR2zJAb9juavmQg=
golang.org/x/mod v0.42.0/go.mod h1:u5fzIrRInkL8MaO1cbRkUHT64v303YIT5KEqpNr7jRs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down Expand Up @@ -724,8 +724,8 @@ golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.59.0 h1:5zfYln+w5XCxwrnMMJPufRgNoXEaGxl0wo5GqPXyues=
golang.org/x/net v0.59.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg=
golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw=
golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand All @@ -745,8 +745,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.23.0 h1:KameEIfc1IkluZyXWLn39Wd4tURc6GbCiISGiZm2bQk=
golang.org/x/sync v0.23.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
Expand Down Expand Up @@ -790,10 +790,10 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/telemetry v0.0.0-20260924152758-ed294f943157 h1:DftxASHKl6JolFPqoN+2WrFO53JvkHwRXvFqf8gsivA=
golang.org/x/telemetry v0.0.0-20260924152758-ed294f943157/go.mod h1:i+ivNqjDnTF3WTElsdk5g9V5DTSBYgdNo7xTU9SDwYA=
golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/telemetry v0.0.0-20261009151841-97e8ea77aa2a h1:wnveMBKHwx8/nIb8WYT5P9vtefdXOvIWxzycCkc5n6Q=
golang.org/x/telemetry v0.0.0-20261009151841-97e8ea77aa2a/go.mod h1:i+ivNqjDnTF3WTElsdk5g9V5DTSBYgdNo7xTU9SDwYA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
Expand Down Expand Up @@ -857,8 +857,8 @@ golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0t
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.51.0 h1:k4Xc/1Om9jwkBJBo4NVLMSARBoWtK10mx+W5BnXCeAI=
golang.org/x/tools v0.51.0/go.mod h1:9eEncMayCV6zRMGhR5eZEC2iBx98qWcF1HZ9Z7wJOoA=
golang.org/x/tools v0.52.1-0.20261030190145-0f433775b36d h1:F5ot9G6RM6pgx3TTdKF6IZa2nocit4tcsrVQlq4Zh5w=
golang.org/x/tools v0.52.1-0.20261030190145-0f433775b36d/go.mod h1:ItcP5UNaVGG+Yc4eIYmaFLNJ4soEopkx5Y2d4lcEurs=
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
Expand Down
27 changes: 23 additions & 4 deletions internal/analysis/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ func Analyze(file *ast.File, info *types.Info, opts Options) []*loopir.Plan {
continue
}
results := loopir.NamedResults(fn, info)
reserved := loopir.UsesReservedNames(fn)
ast.Inspect(fn.Body, func(n ast.Node) bool {
if stmt, ok := n.(ast.Stmt); ok {
if plan, _ := analyzeLoop(stmt, info, results, opts.Plan()); plan != nil {
if plan, _ := analyzeLoop(stmt, info, info.Scopes[file], results, reserved, opts.Plan()); plan != nil {
plans = append(plans, plan)
}
}
Expand All @@ -55,8 +56,10 @@ func Analyze(file *ast.File, info *types.Info, opts Options) []*loopir.Plan {
}

// analyzeLoop lowers stmt, which must be a for or range statement, and plans
// it. It returns the reason when the loop is not vectorizable.
func analyzeLoop(stmt ast.Stmt, info *types.Info, results map[types.Object]bool, plan loopir.Options) (*loopir.Plan, loopir.Reason) {
// it. It returns the reason when the loop is not vectorizable. reserved is set
// when the enclosing function uses a name that begins with an underscore (see
// loopir.UsesReservedNames); file is the scope of the file stmt is in.
func analyzeLoop(stmt ast.Stmt, info *types.Info, file *types.Scope, results map[types.Object]bool, reserved bool, plan loopir.Options) (*loopir.Plan, loopir.Reason) {
switch stmt.(type) {
case *ast.RangeStmt, *ast.ForStmt:
default:
Expand All @@ -66,5 +69,21 @@ func analyzeLoop(stmt ast.Stmt, info *types.Info, results map[types.Object]bool,
if reason != "" {
return nil, reason
}
return loopir.NewPlan(loop, plan)
p, reason := loopir.NewPlan(loop, plan)
if p == nil {
return nil, reason
}
// The names are checked last, so that a loop that cannot be vectorized
// anyway keeps the reason that matters.
if reserved {
return nil, loopir.ReasonReservedName
}
var scope *types.Scope
if file != nil {
scope = file.Innermost(stmt.Pos())
}
if loopir.ShadowsEmittedName(scope, stmt.Pos()) || loopir.HeaderShadowsName(stmt) {
return nil, loopir.ReasonShadowedName
}
return p, reason
}
3 changes: 2 additions & 1 deletion internal/analysis/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func AnalyzeExplain(fset *token.FileSet, file *ast.File, info *types.Info, opts
}
isMethod := fn.Recv != nil
results := loopir.NamedResults(fn, info)
reserved := loopir.UsesReservedNames(fn)
ast.Inspect(fn.Body, func(n ast.Node) bool {
var body *ast.BlockStmt
switch stmt := n.(type) {
Expand All @@ -54,7 +55,7 @@ func AnalyzeExplain(fset *token.FileSet, file *ast.File, info *types.Info, opts
return true
}

plan, reason := analyzeLoop(n.(ast.Stmt), info, results, opts.Plan())
plan, reason := analyzeLoop(n.(ast.Stmt), info, info.Scopes[file], results, reserved, opts.Plan())
c.Vectorized = plan != nil
c.Reason = reason
out = append(out, c)
Expand Down
2 changes: 1 addition & 1 deletion internal/loopir/caps_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

141 changes: 141 additions & 0 deletions internal/loopir/lower.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"go/token"
"go/types"
"math"
"slices"
"strconv"
"strings"
)
Expand Down Expand Up @@ -286,6 +287,12 @@ func (l *lowerer) bound(e ast.Expr) (bound, bool) {
}
b := bound{text: types.ExprString(e)}
b.value, b.isConst = l.constant(e)
if b.isConst {
// A named constant or a constant expression (s0, 1 - 1) is spelled as its
// value, so the checks that read a constant start or limit see it and the
// emitted code never indexes at a negative constant they would have caught.
b.text = strconv.FormatInt(b.value, 10)
}
if call, ok := ast.Unparen(e).(*ast.CallExpr); ok {
b.slice, _ = l.lenOf(call)
}
Expand Down Expand Up @@ -370,6 +377,121 @@ func isIntType(t types.Type) bool {
return ok && (basic.Kind() == types.Int || basic.Kind() == types.UntypedInt)
}

// UsesReservedNames reports whether fn mentions an identifier that begins with
// an underscore, other than the blank identifier: every name loopvec adds does,
// so code it inserts into fn could capture one.
func UsesReservedNames(fn *ast.FuncDecl) bool {
found := false
ast.Inspect(fn, func(n ast.Node) bool {
if id, ok := n.(*ast.Ident); ok && len(id.Name) > 1 && id.Name[0] == '_' {
found = true
}
return !found
})
return found
}

// emittedNames are the predeclared and package names the rewritten code uses:
// len, min, max and copy, uint64 for shift counts, the element types of
// reductions (byte and rune among them, as go/types spells those), and the
// simd and unsafe packages.
var emittedNames = []string{
"len", "min", "max", "copy", "uint64",
"int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "float32", "float64",
"byte", "rune",
"simd", "unsafe",
}

// HeaderShadowsName reports whether a variable stmt, a for or range statement,
// declares in its header could capture a name of the rewritten code. The
// scalar loop that finishes a reduction declares the same variables as stmt and
// evaluates the bound inside their scope, while stmt evaluates its range
// operand, or the start it counts down from, outside: an index named len would
// break len(a), and for n := range n or for n := n - 1; n >= 0; n-- would
// compare the index with itself. So stmt must not declare one of emittedNames,
// nor a name used by its range operand or its initial values.
func HeaderShadowsName(stmt ast.Stmt) bool {
var vars, outside []ast.Expr
switch s := stmt.(type) {
case *ast.RangeStmt:
if s.Tok == token.DEFINE {
vars = []ast.Expr{s.Key, s.Value}
}
outside = []ast.Expr{s.X}
case *ast.ForStmt:
if init, ok := s.Init.(*ast.AssignStmt); ok && init.Tok == token.DEFINE {
vars, outside = init.Lhs, init.Rhs
}
}
for _, v := range vars {
id, ok := v.(*ast.Ident)
if !ok || id.Name == "_" {
continue
}
if slices.Contains(emittedNames, id.Name) {
return true
}
for _, e := range outside {
if mentions(e, id.Name) {
return true
}
}
}
return false
}

// mentions reports whether e uses an identifier named name.
func mentions(e ast.Expr, name string) bool {
found := false
ast.Inspect(e, func(n ast.Node) bool {
if id, ok := n.(*ast.Ident); ok && id.Name == name {
found = true
}
return !found
})
return found
}

// helperNames are the predeclared names that _loopvecOverlap, which loopvec
// declares at package level, uses besides emittedNames.
var helperNames = []string{"any", "bool", "false", "int", "uintptr"}

// ShadowsEmittedName reports whether, at pos in scope, a name the rewritten code
// uses means something else than the code needs: a predeclared name declared
// again in an enclosing scope, the package or an import, or simd or unsafe naming
// something other than those packages. scope is the innermost scope containing
// pos, or nil when the type information has no scopes, which counts as shadowed.
// The names only the overlap helper uses matter at package level, where it is
// declared.
func ShadowsEmittedName(scope *types.Scope, pos token.Pos) bool {
if scope == nil {
return true
}
pkg := scope
for pkg.Parent() != nil && pkg.Parent() != types.Universe {
pkg = pkg.Parent()
}
for _, name := range helperNames {
if pkg.Lookup(name) != nil {
return true
}
}
for _, name := range emittedNames {
_, obj := scope.LookupParent(name, pos)
switch name {
case "simd", "unsafe":
if pkg, ok := obj.(*types.PkgName); obj != nil && (!ok || pkg.Imported().Path() != name) {
return true
}
default:
if obj != types.Universe.Lookup(name) {
return true
}
}
}
return false
}

// NamedResults returns the named result variables of fn and of every function
// literal inside it.
func NamedResults(fn *ast.FuncDecl, info *types.Info) map[types.Object]bool {
Expand Down Expand Up @@ -532,6 +654,25 @@ func (l *lowerer) body(node ast.Stmt, block *ast.BlockStmt, trip Trip, step int6
}
}

// A constant offset that puts an index below zero makes the scalar loop panic
// when it runs: on its first iteration when the start is a constant and the
// first index is negative, or on every iteration when the limit is a constant
// and even the last index is. It is left alone, since the vector loop would
// index or slice at a negative constant, which does not compile.
start, errStart := strconv.ParseInt(trip.Start, 10, 64)
limit, errLimit := strconv.ParseInt(trip.Limit, 10, 64)
negative := func(offset string) bool {
off, err := strconv.ParseInt(offset, 10, 64)
return err == nil && (errStart == nil && start+off < 0 || errLimit == nil && limit+off <= 0)
}
for _, stmt := range body {
for _, leaf := range Leaves(stmtValue(stmt)) {
if load, ok := leaf.(*Load); ok && negative(load.Off) {
return nil, ReasonNegativeIndex
}
}
}

loop := &Loop{
Node: node,
Ind: Induction{Var: l.iv, Step: step, Trip: trip},
Expand Down
Loading
Loading