go/src/simd/_gen/unify/closure.go
Austin Clements b7c8698549 [dev.simd] simd/_gen: migrate simdgen from x/arch
This moves the simdgen tool and its supporting unify package from
golang.org/x/arch/internal as of CL 695619 to simd/_gen in the main repo.

The simdgen tool was started in x/arch to live next to xeddata and a
few other assembler generators that already lived there. However, as
we've been developing simdgen, we've discovered that there's a
tremendous amount of process friction coordinating commits to x/arch
with the corresponding generated files in the main repo.

Many of the existing generators in x/arch were started before modules
existed. In GOPATH world, it was impractical for them to live in the
main repo because they have dependencies that are not allowed in the
main repo. However, now that we have modules and can use small
submodules in the main repo, we can isolate these dependencies to just
the generators, making it practical for them to live in the main repo.

This commit was generated by the following script:

	# Checks
	set -e
	if [[ ! -d src/simd ]]; then
	    echo >&2 "$PWD is not the root of the main repo on dev.simd"
	    exit 1
	fi
	if [[ -z "$XEDDATA" ]]; then
	    echo >&2 "Must set \$XEDDATA"
	    exit 1
	fi
	which go >/dev/null

	# Move simdgen from x/arch
	xarch=$(mktemp -d)
	git clone https://go.googlesource.com/arch $xarch
	xarchCL=$(git -C $xarch log -1 --format=%b | awk -F/ '/^Reviewed-on:/ {print $NF}')
	echo >&2 "x/arch CL: $xarchCL"
	mv $xarch/internal src/simd/_gen
	sed --in-place s,golang.org/x/arch/internal/,simd/_gen/, src/simd/_gen/*/*.go
	# Create self-contained module
	cat > src/simd/_gen/go.mod <<EOF
	module simd/_gen

	go 1.24
	EOF
	cd src/simd/_gen
	go mod tidy
	git add .
	git gofmt
	# Regenerate file
	go run -C simdgen . -xedPath $XEDDATA -o godefs -goroot $(go env GOROOT) go.yaml types.yaml categories.yaml
	go run -C ../../cmd/compile/internal/ssa/_gen .

Change-Id: I56dd8473e913a9eb1978d9b3b3518ed632972f6f
Reviewed-on: https://go-review.googlesource.com/c/go/+/695975
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
2025-08-13 13:56:26 -07:00

154 lines
4.1 KiB
Go

// Copyright 2025 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 unify
import (
"fmt"
"iter"
"maps"
"slices"
)
type Closure struct {
val *Value
env envSet
}
func NewSum(vs ...*Value) Closure {
id := &ident{name: "sum"}
return Closure{NewValue(Var{id}), topEnv.bind(id, vs...)}
}
// IsBottom returns whether c consists of no values.
func (c Closure) IsBottom() bool {
return c.val.Domain == nil
}
// Summands returns the top-level Values of c. This assumes the top-level of c
// was constructed as a sum, and is mostly useful for debugging.
func (c Closure) Summands() iter.Seq[*Value] {
return func(yield func(*Value) bool) {
var rec func(v *Value, env envSet) bool
rec = func(v *Value, env envSet) bool {
switch d := v.Domain.(type) {
case Var:
parts := env.partitionBy(d.id)
for _, part := range parts {
// It may be a sum of sums. Walk into this value.
if !rec(part.value, part.env) {
return false
}
}
return true
default:
return yield(v)
}
}
rec(c.val, c.env)
}
}
// All enumerates all possible concrete values of c by substituting variables
// from the environment.
//
// E.g., enumerating this Value
//
// a: !sum [1, 2]
// b: !sum [3, 4]
//
// results in
//
// - {a: 1, b: 3}
// - {a: 1, b: 4}
// - {a: 2, b: 3}
// - {a: 2, b: 4}
func (c Closure) All() iter.Seq[*Value] {
// In order to enumerate all concrete values under all possible variable
// bindings, we use a "non-deterministic continuation passing style" to
// implement this. We use CPS to traverse the Value tree, threading the
// (possibly narrowing) environment through that CPS following an Euler
// tour. Where the environment permits multiple choices, we invoke the same
// continuation for each choice. Similar to a yield function, the
// continuation can return false to stop the non-deterministic walk.
return func(yield func(*Value) bool) {
c.val.all1(c.env, func(v *Value, e envSet) bool {
return yield(v)
})
}
}
func (v *Value) all1(e envSet, cont func(*Value, envSet) bool) bool {
switch d := v.Domain.(type) {
default:
panic(fmt.Sprintf("unknown domain type %T", d))
case nil:
return true
case Top, String:
return cont(v, e)
case Def:
fields := d.keys()
// We can reuse this parts slice because we're doing a DFS through the
// state space. (Otherwise, we'd have to do some messy threading of an
// immutable slice-like value through allElt.)
parts := make(map[string]*Value, len(fields))
// TODO: If there are no Vars or Sums under this Def, then nothing can
// change the Value or env, so we could just cont(v, e).
var allElt func(elt int, e envSet) bool
allElt = func(elt int, e envSet) bool {
if elt == len(fields) {
// Build a new Def from the concrete parts. Clone parts because
// we may reuse it on other non-deterministic branches.
nVal := newValueFrom(Def{maps.Clone(parts)}, v)
return cont(nVal, e)
}
return d.fields[fields[elt]].all1(e, func(v *Value, e envSet) bool {
parts[fields[elt]] = v
return allElt(elt+1, e)
})
}
return allElt(0, e)
case Tuple:
// Essentially the same as Def.
if d.repeat != nil {
// There's nothing we can do with this.
return cont(v, e)
}
parts := make([]*Value, len(d.vs))
var allElt func(elt int, e envSet) bool
allElt = func(elt int, e envSet) bool {
if elt == len(d.vs) {
// Build a new tuple from the concrete parts. Clone parts because
// we may reuse it on other non-deterministic branches.
nVal := newValueFrom(Tuple{vs: slices.Clone(parts)}, v)
return cont(nVal, e)
}
return d.vs[elt].all1(e, func(v *Value, e envSet) bool {
parts[elt] = v
return allElt(elt+1, e)
})
}
return allElt(0, e)
case Var:
// Go each way this variable can be bound.
for _, ePart := range e.partitionBy(d.id) {
// d.id is no longer bound in this environment partition. We'll may
// need it later in the Euler tour, so bind it back to this single
// value.
env := ePart.env.bind(d.id, ePart.value)
if !ePart.value.all1(env, cont) {
return false
}
}
return true
}
}