diff --git a/src/sort/gen_sort_variants.go b/src/sort/gen_sort_variants.go
new file mode 100644
index 0000000000000000000000000000000000000000..5f817221e119b8eeab60fc26c1d62bf96906b6ef
--- /dev/null
+++ b/src/sort/gen_sort_variants.go
@@ -0,0 +1,526 @@
+// Copyright 2022 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 ignore
+// +build ignore
+
+// This program is run via "go generate" (via a directive in sort.go)
+// to generate implementation variants of the underlying sorting algorithm.
+// When passed the -generic flag it generates generic variants of sorting;
+// otherwise it generates the non-generic variants used by the sort package.
+
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"go/format"
+	"log"
+	"os"
+	"text/template"
+)
+
+type Variant struct {
+	// Name is the variant name: should be unique among variants.
+	Name string
+
+	// Path is the file path into which the generator will emit the code for this
+	// variant.
+	Path string
+
+	// Package is the package this code will be emitted into.
+	Package string
+
+	// Imports is the imports needed for this package.
+	Imports string
+
+	// FuncSuffix is appended to all function names in this variant's code. All
+	// suffixes should be unique within a package.
+	FuncSuffix string
+
+	// DataType is the type of the data parameter of functions in this variant's
+	// code.
+	DataType string
+
+	// TypeParam is the optional type parameter for the function.
+	TypeParam string
+
+	// ExtraParam is an extra parameter to pass to the function. Should begin with
+	// ", " to separate from other params.
+	ExtraParam string
+
+	// ExtraArg is an extra argument to pass to calls between functions; typically
+	// it invokes ExtraParam. Should begin with ", " to separate from other args.
+	ExtraArg string
+
+	// Funcs is a map of functions used from within the template. The following
+	// functions are expected to exist:
+	//
+	//    Less (name, i, j):
+	//      emits a comparison expression that checks if the value `name` at
+	//      index `i` is smaller than at index `j`.
+	//
+	//    Swap (name, i, j):
+	//      emits a statement that performs a data swap between elements `i` and
+	//      `j` of the value `name`.
+	Funcs template.FuncMap
+}
+
+func main() {
+	genGeneric := flag.Bool("generic", false, "generate generic versions")
+	flag.Parse()
+
+	if *genGeneric {
+		generate(&Variant{
+			Name:       "generic_ordered",
+			Path:       "zsortordered.go",
+			Package:    "slices",
+			Imports:    "import \"constraints\"\n",
+			FuncSuffix: "Ordered",
+			TypeParam:  "[Elem constraints.Ordered]",
+			ExtraParam: "",
+			ExtraArg:   "",
+			DataType:   "[]Elem",
+			Funcs: template.FuncMap{
+				"Less": func(name, i, j string) string {
+					return fmt.Sprintf("(%s[%s] < %s[%s])", name, i, name, j)
+				},
+				"Swap": func(name, i, j string) string {
+					return fmt.Sprintf("%s[%s], %s[%s] = %s[%s], %s[%s]", name, i, name, j, name, j, name, i)
+				},
+			},
+		})
+
+		generate(&Variant{
+			Name:       "generic_func",
+			Path:       "zsortanyfunc.go",
+			Package:    "slices",
+			FuncSuffix: "LessFunc",
+			TypeParam:  "[Elem any]",
+			ExtraParam: ", less func(a, b Elem) bool",
+			ExtraArg:   ", less",
+			DataType:   "[]Elem",
+			Funcs: template.FuncMap{
+				"Less": func(name, i, j string) string {
+					return fmt.Sprintf("less(%s[%s], %s[%s])", name, i, name, j)
+				},
+				"Swap": func(name, i, j string) string {
+					return fmt.Sprintf("%s[%s], %s[%s] = %s[%s], %s[%s]", name, i, name, j, name, j, name, i)
+				},
+			},
+		})
+	} else {
+		generate(&Variant{
+			Name:       "interface",
+			Path:       "zsortinterface.go",
+			Package:    "sort",
+			Imports:    "",
+			FuncSuffix: "",
+			TypeParam:  "",
+			ExtraParam: "",
+			ExtraArg:   "",
+			DataType:   "Interface",
+			Funcs: template.FuncMap{
+				"Less": func(name, i, j string) string {
+					return fmt.Sprintf("%s.Less(%s, %s)", name, i, j)
+				},
+				"Swap": func(name, i, j string) string {
+					return fmt.Sprintf("%s.Swap(%s, %s)", name, i, j)
+				},
+			},
+		})
+
+		generate(&Variant{
+			Name:       "func",
+			Path:       "zsortfunc.go",
+			Package:    "sort",
+			Imports:    "",
+			FuncSuffix: "_func",
+			TypeParam:  "",
+			ExtraParam: "",
+			ExtraArg:   "",
+			DataType:   "lessSwap",
+			Funcs: template.FuncMap{
+				"Less": func(name, i, j string) string {
+					return fmt.Sprintf("%s.Less(%s, %s)", name, i, j)
+				},
+				"Swap": func(name, i, j string) string {
+					return fmt.Sprintf("%s.Swap(%s, %s)", name, i, j)
+				},
+			},
+		})
+	}
+}
+
+// generate generates the code for variant `v` into a file named by `v.Path`.
+func generate(v *Variant) {
+	// Parse templateCode anew for each variant because Parse requires Funcs to be
+	// registered, and it helps type-check the funcs.
+	tmpl, err := template.New("gen").Funcs(v.Funcs).Parse(templateCode)
+	if err != nil {
+		log.Fatal("template Parse:", err)
+	}
+
+	var out bytes.Buffer
+	err = tmpl.Execute(&out, v)
+	if err != nil {
+		log.Fatal("template Execute:", err)
+	}
+
+	formatted, err := format.Source(out.Bytes())
+	if err != nil {
+		log.Fatal("format:", err)
+	}
+
+	if err := os.WriteFile(v.Path, formatted, 0644); err != nil {
+		log.Fatal("WriteFile:", err)
+	}
+}
+
+var templateCode = `// Code generated by gen_sort_variants.go; DO NOT EDIT.
+
+// Copyright 2022 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 {{.Package}}
+
+{{.Imports}}
+
+// insertionSort{{.FuncSuffix}} sorts data[a:b] using insertion sort.
+func insertionSort{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b int {{.ExtraParam}}) {
+	for i := a + 1; i < b; i++ {
+		for j := i; j > a && {{Less "data" "j" "j-1"}}; j-- {
+			{{Swap "data" "j" "j-1"}}
+		}
+	}
+}
+
+// siftDown{{.FuncSuffix}} implements the heap property on data[lo:hi].
+// first is an offset into the array where the root of the heap lies.
+func siftDown{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, lo, hi, first int {{.ExtraParam}}) {
+	root := lo
+	for {
+		child := 2*root + 1
+		if child >= hi {
+			break
+		}
+		if child+1 < hi && {{Less "data" "first+child" "first+child+1"}} {
+			child++
+		}
+		if !{{Less "data" "first+root" "first+child"}} {
+			return
+		}
+		{{Swap "data" "first+root" "first+child"}}
+		root = child
+	}
+}
+
+func heapSort{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b int {{.ExtraParam}}) {
+	first := a
+	lo := 0
+	hi := b - a
+
+	// Build heap with greatest element at top.
+	for i := (hi - 1) / 2; i >= 0; i-- {
+		siftDown{{.FuncSuffix}}(data, i, hi, first {{.ExtraArg}})
+	}
+
+	// Pop elements, largest first, into end of data.
+	for i := hi - 1; i >= 0; i-- {
+		{{Swap "data" "first" "first+i"}}
+		siftDown{{.FuncSuffix}}(data, lo, i, first {{.ExtraArg}})
+	}
+}
+
+// Quicksort, loosely following Bentley and McIlroy,
+// "Engineering a Sort Function" SP&E November 1993.
+
+// medianOfThree{{.FuncSuffix}} moves the median of the three values data[m0], data[m1], data[m2] into data[m1].
+func medianOfThree{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, m1, m0, m2 int {{.ExtraParam}}) {
+	// sort 3 elements
+	if {{Less "data" "m1" "m0"}} {
+		{{Swap "data" "m1" "m0"}}
+	}
+	// data[m0] <= data[m1]
+	if {{Less "data" "m2" "m1"}} {
+		{{Swap "data" "m2" "m1"}}
+		// data[m0] <= data[m2] && data[m1] < data[m2]
+		if {{Less "data" "m1" "m0"}} {
+			{{Swap "data" "m1" "m0"}}
+		}
+	}
+	// now data[m0] <= data[m1] <= data[m2]
+}
+
+func swapRange{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b, n int {{.ExtraParam}}) {
+	for i := 0; i < n; i++ {
+		{{Swap "data" "a+i" "b+i"}}
+	}
+}
+
+func doPivot{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, lo, hi int {{.ExtraParam}}) (midlo, midhi int) {
+	m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow.
+	if hi-lo > 40 {
+		// Tukey's "Ninther" median of three medians of three.
+		s := (hi - lo) / 8
+		medianOfThree{{.FuncSuffix}}(data, lo, lo+s, lo+2*s {{.ExtraArg}})
+		medianOfThree{{.FuncSuffix}}(data, m, m-s, m+s {{.ExtraArg}})
+		medianOfThree{{.FuncSuffix}}(data, hi-1, hi-1-s, hi-1-2*s {{.ExtraArg}})
+	}
+	medianOfThree{{.FuncSuffix}}(data, lo, m, hi-1 {{.ExtraArg}})
+
+	// Invariants are:
+	//	data[lo] = pivot (set up by ChoosePivot)
+	//	data[lo < i < a] < pivot
+	//	data[a <= i < b] <= pivot
+	//	data[b <= i < c] unexamined
+	//	data[c <= i < hi-1] > pivot
+	//	data[hi-1] >= pivot
+	pivot := lo
+	a, c := lo+1, hi-1
+
+	for ; a < c && {{Less "data" "a" "pivot"}}; a++ {
+	}
+	b := a
+	for {
+		for ; b < c && !{{Less "data" "pivot" "b"}}; b++ { // data[b] <= pivot
+		}
+		for ; b < c && {{Less "data" "pivot" "c-1"}}; c-- { // data[c-1] > pivot
+		}
+		if b >= c {
+			break
+		}
+		// data[b] > pivot; data[c-1] <= pivot
+		{{Swap "data" "b" "c-1"}}
+		b++
+		c--
+	}
+	// If hi-c<3 then there are duplicates (by property of median of nine).
+	// Let's be a bit more conservative, and set border to 5.
+	protect := hi-c < 5
+	if !protect && hi-c < (hi-lo)/4 {
+		// Lets test some points for equality to pivot
+		dups := 0
+		if !{{Less "data" "pivot" "hi-1"}} { // data[hi-1] = pivot
+			{{Swap "data" "c" "hi-1"}}
+			c++
+			dups++
+		}
+		if !{{Less "data" "b-1" "pivot"}} { // data[b-1] = pivot
+			b--
+			dups++
+		}
+		// m-lo = (hi-lo)/2 > 6
+		// b-lo > (hi-lo)*3/4-1 > 8
+		// ==> m < b ==> data[m] <= pivot
+		if !{{Less "data" "m" "pivot"}} { // data[m] = pivot
+			{{Swap "data" "m" "b-1"}}
+			b--
+			dups++
+		}
+		// if at least 2 points are equal to pivot, assume skewed distribution
+		protect = dups > 1
+	}
+	if protect {
+		// Protect against a lot of duplicates
+		// Add invariant:
+		//	data[a <= i < b] unexamined
+		//	data[b <= i < c] = pivot
+		for {
+			for ; a < b && !{{Less "data" "b-1" "pivot"}}; b-- { // data[b] == pivot
+			}
+			for ; a < b && {{Less "data" "a" "pivot"}}; a++ { // data[a] < pivot
+			}
+			if a >= b {
+				break
+			}
+			// data[a] == pivot; data[b-1] < pivot
+			{{Swap "data" "a" "b-1"}}
+			a++
+			b--
+		}
+	}
+	// Swap pivot into middle
+	{{Swap "data" "pivot" "b-1"}}
+	return b - 1, c
+}
+
+func quickSort{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b, maxDepth int {{.ExtraParam}}) {
+	for b-a > 12 { // Use ShellSort for slices <= 12 elements
+		if maxDepth == 0 {
+			heapSort{{.FuncSuffix}}(data, a, b {{.ExtraArg}})
+			return
+		}
+		maxDepth--
+		mlo, mhi := doPivot{{.FuncSuffix}}(data, a, b {{.ExtraArg}})
+		// Avoiding recursion on the larger subproblem guarantees
+		// a stack depth of at most lg(b-a).
+		if mlo-a < b-mhi {
+			quickSort{{.FuncSuffix}}(data, a, mlo, maxDepth {{.ExtraArg}})
+			a = mhi // i.e., quickSort{{.FuncSuffix}}(data, mhi, b)
+		} else {
+			quickSort{{.FuncSuffix}}(data, mhi, b, maxDepth {{.ExtraArg}})
+			b = mlo // i.e., quickSort{{.FuncSuffix}}(data, a, mlo)
+		}
+	}
+	if b-a > 1 {
+		// Do ShellSort pass with gap 6
+		// It could be written in this simplified form cause b-a <= 12
+		for i := a + 6; i < b; i++ {
+			if {{Less "data" "i" "i-6"}} {
+				{{Swap "data" "i" "i-6"}}
+			}
+		}
+		insertionSort{{.FuncSuffix}}(data, a, b {{.ExtraArg}})
+	}
+}
+
+func stable{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, n int {{.ExtraParam}}) {
+	blockSize := 20 // must be > 0
+	a, b := 0, blockSize
+	for b <= n {
+		insertionSort{{.FuncSuffix}}(data, a, b {{.ExtraArg}})
+		a = b
+		b += blockSize
+	}
+	insertionSort{{.FuncSuffix}}(data, a, n {{.ExtraArg}})
+
+	for blockSize < n {
+		a, b = 0, 2*blockSize
+		for b <= n {
+			symMerge{{.FuncSuffix}}(data, a, a+blockSize, b {{.ExtraArg}})
+			a = b
+			b += 2 * blockSize
+		}
+		if m := a + blockSize; m < n {
+			symMerge{{.FuncSuffix}}(data, a, m, n {{.ExtraArg}})
+		}
+		blockSize *= 2
+	}
+}
+
+// symMerge{{.FuncSuffix}} merges the two sorted subsequences data[a:m] and data[m:b] using
+// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum
+// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz
+// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in
+// Computer Science, pages 714-723. Springer, 2004.
+//
+// Let M = m-a and N = b-n. Wolog M < N.
+// The recursion depth is bound by ceil(log(N+M)).
+// The algorithm needs O(M*log(N/M + 1)) calls to data.Less.
+// The algorithm needs O((M+N)*log(M)) calls to data.Swap.
+//
+// The paper gives O((M+N)*log(M)) as the number of assignments assuming a
+// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation
+// in the paper carries through for Swap operations, especially as the block
+// swapping rotate uses only O(M+N) Swaps.
+//
+// symMerge assumes non-degenerate arguments: a < m && m < b.
+// Having the caller check this condition eliminates many leaf recursion calls,
+// which improves performance.
+func symMerge{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, m, b int {{.ExtraParam}}) {
+	// Avoid unnecessary recursions of symMerge
+	// by direct insertion of data[a] into data[m:b]
+	// if data[a:m] only contains one element.
+	if m-a == 1 {
+		// Use binary search to find the lowest index i
+		// such that data[i] >= data[a] for m <= i < b.
+		// Exit the search loop with i == b in case no such index exists.
+		i := m
+		j := b
+		for i < j {
+			h := int(uint(i+j) >> 1)
+			if {{Less "data" "h" "a"}} {
+				i = h + 1
+			} else {
+				j = h
+			}
+		}
+		// Swap values until data[a] reaches the position before i.
+		for k := a; k < i-1; k++ {
+			{{Swap "data" "k" "k+1"}}
+		}
+		return
+	}
+
+	// Avoid unnecessary recursions of symMerge
+	// by direct insertion of data[m] into data[a:m]
+	// if data[m:b] only contains one element.
+	if b-m == 1 {
+		// Use binary search to find the lowest index i
+		// such that data[i] > data[m] for a <= i < m.
+		// Exit the search loop with i == m in case no such index exists.
+		i := a
+		j := m
+		for i < j {
+			h := int(uint(i+j) >> 1)
+			if !{{Less "data" "m" "h"}} {
+				i = h + 1
+			} else {
+				j = h
+			}
+		}
+		// Swap values until data[m] reaches the position i.
+		for k := m; k > i; k-- {
+			{{Swap "data" "k" "k-1"}}
+		}
+		return
+	}
+
+	mid := int(uint(a+b) >> 1)
+	n := mid + m
+	var start, r int
+	if m > mid {
+		start = n - b
+		r = mid
+	} else {
+		start = a
+		r = m
+	}
+	p := n - 1
+
+	for start < r {
+		c := int(uint(start+r) >> 1)
+		if !{{Less "data" "p-c" "c"}} {
+			start = c + 1
+		} else {
+			r = c
+		}
+	}
+
+	end := n - start
+	if start < m && m < end {
+		rotate{{.FuncSuffix}}(data, start, m, end {{.ExtraArg}})
+	}
+	if a < start && start < mid {
+		symMerge{{.FuncSuffix}}(data, a, start, mid {{.ExtraArg}})
+	}
+	if mid < end && end < b {
+		symMerge{{.FuncSuffix}}(data, mid, end, b {{.ExtraArg}})
+	}
+}
+
+// rotate{{.FuncSuffix}} rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data:
+// Data of the form 'x u v y' is changed to 'x v u y'.
+// rotate performs at most b-a many calls to data.Swap,
+// and it assumes non-degenerate arguments: a < m && m < b.
+func rotate{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, m, b int {{.ExtraParam}}) {
+	i := m - a
+	j := b - m
+
+	for i != j {
+		if i > j {
+			swapRange{{.FuncSuffix}}(data, m-i, m, j {{.ExtraArg}})
+			i -= j
+		} else {
+			swapRange{{.FuncSuffix}}(data, m-i, m+j-i, i {{.ExtraArg}})
+			j -= i
+		}
+	}
+	// i == j
+	swapRange{{.FuncSuffix}}(data, m-i, m, i {{.ExtraArg}})
+}
+`
diff --git a/src/sort/genzfunc.go b/src/sort/genzfunc.go
deleted file mode 100644
index ed04e33568f4c6f6b5a7fa04790c76f7da29b10a..0000000000000000000000000000000000000000
--- a/src/sort/genzfunc.go
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2016 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 ignore
-// +build ignore
-
-// This program is run via "go generate" (via a directive in sort.go)
-// to generate zfuncversion.go.
-//
-// It copies sort.go to zfuncversion.go, only retaining funcs which
-// take a "data Interface" parameter, and renaming each to have a
-// "_func" suffix and taking a "data lessSwap" instead. It then rewrites
-// each internal function call to the appropriate _func variants.
-
-package main
-
-import (
-	"bytes"
-	"go/ast"
-	"go/format"
-	"go/parser"
-	"go/token"
-	"log"
-	"os"
-	"regexp"
-)
-
-var fset = token.NewFileSet()
-
-func main() {
-	af, err := parser.ParseFile(fset, "sort.go", nil, 0)
-	if err != nil {
-		log.Fatal(err)
-	}
-	af.Doc = nil
-	af.Imports = nil
-	af.Comments = nil
-
-	var newDecl []ast.Decl
-	for _, d := range af.Decls {
-		fd, ok := d.(*ast.FuncDecl)
-		if !ok {
-			continue
-		}
-		if fd.Recv != nil || fd.Name.IsExported() {
-			continue
-		}
-		typ := fd.Type
-		if len(typ.Params.List) < 1 {
-			continue
-		}
-		arg0 := typ.Params.List[0]
-		arg0Name := arg0.Names[0].Name
-		arg0Type := arg0.Type.(*ast.Ident)
-		if arg0Name != "data" || arg0Type.Name != "Interface" {
-			continue
-		}
-		arg0Type.Name = "lessSwap"
-
-		newDecl = append(newDecl, fd)
-	}
-	af.Decls = newDecl
-	ast.Walk(visitFunc(rewriteCalls), af)
-
-	var out bytes.Buffer
-	if err := format.Node(&out, fset, af); err != nil {
-		log.Fatalf("format.Node: %v", err)
-	}
-
-	// Get rid of blank lines after removal of comments.
-	src := regexp.MustCompile(`\n{2,}`).ReplaceAll(out.Bytes(), []byte("\n"))
-
-	// Add comments to each func, for the lost reader.
-	// This is so much easier than adding comments via the AST
-	// and trying to get position info correct.
-	src = regexp.MustCompile(`(?m)^func (\w+)`).ReplaceAll(src, []byte("\n// Auto-generated variant of sort.go:$1\nfunc ${1}_func"))
-
-	// Final gofmt.
-	src, err = format.Source(src)
-	if err != nil {
-		log.Fatalf("format.Source: %v on\n%s", err, src)
-	}
-
-	out.Reset()
-	out.WriteString(`// Code generated from sort.go using genzfunc.go; DO NOT EDIT.
-
-// Copyright 2016 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.
-
-`)
-	out.Write(src)
-
-	const target = "zfuncversion.go"
-	if err := os.WriteFile(target, out.Bytes(), 0644); err != nil {
-		log.Fatal(err)
-	}
-}
-
-type visitFunc func(ast.Node) ast.Visitor
-
-func (f visitFunc) Visit(n ast.Node) ast.Visitor { return f(n) }
-
-func rewriteCalls(n ast.Node) ast.Visitor {
-	ce, ok := n.(*ast.CallExpr)
-	if ok {
-		rewriteCall(ce)
-	}
-	return visitFunc(rewriteCalls)
-}
-
-func rewriteCall(ce *ast.CallExpr) {
-	ident, ok := ce.Fun.(*ast.Ident)
-	if !ok {
-		// e.g. skip SelectorExpr (data.Less(..) calls)
-		return
-	}
-	// skip casts
-	if ident.Name == "int" || ident.Name == "uint" {
-		return
-	}
-	if len(ce.Args) < 1 {
-		return
-	}
-	ident.Name += "_func"
-}
diff --git a/src/sort/sort.go b/src/sort/sort.go
index 749310764aff2b7e00f0e6c89781f2d7d306c753..2c197afc033e7bfe19cd88e2d2616f3371d2926b 100644
--- a/src/sort/sort.go
+++ b/src/sort/sort.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:generate go run genzfunc.go
+//go:generate go run gen_sort_variants.go
 
 // Package sort provides primitives for sorting slices and user-defined collections.
 package sort
@@ -34,195 +34,6 @@ type Interface interface {
 	Swap(i, j int)
 }
 
-// insertionSort sorts data[a:b] using insertion sort.
-func insertionSort(data Interface, a, b int) {
-	for i := a + 1; i < b; i++ {
-		for j := i; j > a && data.Less(j, j-1); j-- {
-			data.Swap(j, j-1)
-		}
-	}
-}
-
-// siftDown implements the heap property on data[lo:hi].
-// first is an offset into the array where the root of the heap lies.
-func siftDown(data Interface, lo, hi, first int) {
-	root := lo
-	for {
-		child := 2*root + 1
-		if child >= hi {
-			break
-		}
-		if child+1 < hi && data.Less(first+child, first+child+1) {
-			child++
-		}
-		if !data.Less(first+root, first+child) {
-			return
-		}
-		data.Swap(first+root, first+child)
-		root = child
-	}
-}
-
-func heapSort(data Interface, a, b int) {
-	first := a
-	lo := 0
-	hi := b - a
-
-	// Build heap with greatest element at top.
-	for i := (hi - 1) / 2; i >= 0; i-- {
-		siftDown(data, i, hi, first)
-	}
-
-	// Pop elements, largest first, into end of data.
-	for i := hi - 1; i >= 0; i-- {
-		data.Swap(first, first+i)
-		siftDown(data, lo, i, first)
-	}
-}
-
-// Quicksort, loosely following Bentley and McIlroy,
-// ``Engineering a Sort Function,'' SP&E November 1993.
-
-// medianOfThree moves the median of the three values data[m0], data[m1], data[m2] into data[m1].
-func medianOfThree(data Interface, m1, m0, m2 int) {
-	// sort 3 elements
-	if data.Less(m1, m0) {
-		data.Swap(m1, m0)
-	}
-	// data[m0] <= data[m1]
-	if data.Less(m2, m1) {
-		data.Swap(m2, m1)
-		// data[m0] <= data[m2] && data[m1] < data[m2]
-		if data.Less(m1, m0) {
-			data.Swap(m1, m0)
-		}
-	}
-	// now data[m0] <= data[m1] <= data[m2]
-}
-
-func swapRange(data Interface, a, b, n int) {
-	for i := 0; i < n; i++ {
-		data.Swap(a+i, b+i)
-	}
-}
-
-func doPivot(data Interface, lo, hi int) (midlo, midhi int) {
-	m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow.
-	if hi-lo > 40 {
-		// Tukey's ``Ninther,'' median of three medians of three.
-		s := (hi - lo) / 8
-		medianOfThree(data, lo, lo+s, lo+2*s)
-		medianOfThree(data, m, m-s, m+s)
-		medianOfThree(data, hi-1, hi-1-s, hi-1-2*s)
-	}
-	medianOfThree(data, lo, m, hi-1)
-
-	// Invariants are:
-	//	data[lo] = pivot (set up by ChoosePivot)
-	//	data[lo < i < a] < pivot
-	//	data[a <= i < b] <= pivot
-	//	data[b <= i < c] unexamined
-	//	data[c <= i < hi-1] > pivot
-	//	data[hi-1] >= pivot
-	pivot := lo
-	a, c := lo+1, hi-1
-
-	for ; a < c && data.Less(a, pivot); a++ {
-	}
-	b := a
-	for {
-		for ; b < c && !data.Less(pivot, b); b++ { // data[b] <= pivot
-		}
-		for ; b < c && data.Less(pivot, c-1); c-- { // data[c-1] > pivot
-		}
-		if b >= c {
-			break
-		}
-		// data[b] > pivot; data[c-1] <= pivot
-		data.Swap(b, c-1)
-		b++
-		c--
-	}
-	// If hi-c<3 then there are duplicates (by property of median of nine).
-	// Let's be a bit more conservative, and set border to 5.
-	protect := hi-c < 5
-	if !protect && hi-c < (hi-lo)/4 {
-		// Lets test some points for equality to pivot
-		dups := 0
-		if !data.Less(pivot, hi-1) { // data[hi-1] = pivot
-			data.Swap(c, hi-1)
-			c++
-			dups++
-		}
-		if !data.Less(b-1, pivot) { // data[b-1] = pivot
-			b--
-			dups++
-		}
-		// m-lo = (hi-lo)/2 > 6
-		// b-lo > (hi-lo)*3/4-1 > 8
-		// ==> m < b ==> data[m] <= pivot
-		if !data.Less(m, pivot) { // data[m] = pivot
-			data.Swap(m, b-1)
-			b--
-			dups++
-		}
-		// if at least 2 points are equal to pivot, assume skewed distribution
-		protect = dups > 1
-	}
-	if protect {
-		// Protect against a lot of duplicates
-		// Add invariant:
-		//	data[a <= i < b] unexamined
-		//	data[b <= i < c] = pivot
-		for {
-			for ; a < b && !data.Less(b-1, pivot); b-- { // data[b] == pivot
-			}
-			for ; a < b && data.Less(a, pivot); a++ { // data[a] < pivot
-			}
-			if a >= b {
-				break
-			}
-			// data[a] == pivot; data[b-1] < pivot
-			data.Swap(a, b-1)
-			a++
-			b--
-		}
-	}
-	// Swap pivot into middle
-	data.Swap(pivot, b-1)
-	return b - 1, c
-}
-
-func quickSort(data Interface, a, b, maxDepth int) {
-	for b-a > 12 { // Use ShellSort for slices <= 12 elements
-		if maxDepth == 0 {
-			heapSort(data, a, b)
-			return
-		}
-		maxDepth--
-		mlo, mhi := doPivot(data, a, b)
-		// Avoiding recursion on the larger subproblem guarantees
-		// a stack depth of at most lg(b-a).
-		if mlo-a < b-mhi {
-			quickSort(data, a, mlo, maxDepth)
-			a = mhi // i.e., quickSort(data, mhi, b)
-		} else {
-			quickSort(data, mhi, b, maxDepth)
-			b = mlo // i.e., quickSort(data, a, mlo)
-		}
-	}
-	if b-a > 1 {
-		// Do ShellSort pass with gap 6
-		// It could be written in this simplified form cause b-a <= 12
-		for i := a + 6; i < b; i++ {
-			if data.Less(i, i-6) {
-				data.Swap(i, i-6)
-			}
-		}
-		insertionSort(data, a, b)
-	}
-}
-
 // Sort sorts data in ascending order as determined by the Less method.
 // It makes one call to data.Len to determine n and O(n*log(n)) calls to
 // data.Less and data.Swap. The sort is not guaranteed to be stable.
@@ -379,152 +190,6 @@ func Stable(data Interface) {
 	stable(data, data.Len())
 }
 
-func stable(data Interface, n int) {
-	blockSize := 20 // must be > 0
-	a, b := 0, blockSize
-	for b <= n {
-		insertionSort(data, a, b)
-		a = b
-		b += blockSize
-	}
-	insertionSort(data, a, n)
-
-	for blockSize < n {
-		a, b = 0, 2*blockSize
-		for b <= n {
-			symMerge(data, a, a+blockSize, b)
-			a = b
-			b += 2 * blockSize
-		}
-		if m := a + blockSize; m < n {
-			symMerge(data, a, m, n)
-		}
-		blockSize *= 2
-	}
-}
-
-// symMerge merges the two sorted subsequences data[a:m] and data[m:b] using
-// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum
-// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz
-// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in
-// Computer Science, pages 714-723. Springer, 2004.
-//
-// Let M = m-a and N = b-n. Wolog M < N.
-// The recursion depth is bound by ceil(log(N+M)).
-// The algorithm needs O(M*log(N/M + 1)) calls to data.Less.
-// The algorithm needs O((M+N)*log(M)) calls to data.Swap.
-//
-// The paper gives O((M+N)*log(M)) as the number of assignments assuming a
-// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation
-// in the paper carries through for Swap operations, especially as the block
-// swapping rotate uses only O(M+N) Swaps.
-//
-// symMerge assumes non-degenerate arguments: a < m && m < b.
-// Having the caller check this condition eliminates many leaf recursion calls,
-// which improves performance.
-func symMerge(data Interface, a, m, b int) {
-	// Avoid unnecessary recursions of symMerge
-	// by direct insertion of data[a] into data[m:b]
-	// if data[a:m] only contains one element.
-	if m-a == 1 {
-		// Use binary search to find the lowest index i
-		// such that data[i] >= data[a] for m <= i < b.
-		// Exit the search loop with i == b in case no such index exists.
-		i := m
-		j := b
-		for i < j {
-			h := int(uint(i+j) >> 1)
-			if data.Less(h, a) {
-				i = h + 1
-			} else {
-				j = h
-			}
-		}
-		// Swap values until data[a] reaches the position before i.
-		for k := a; k < i-1; k++ {
-			data.Swap(k, k+1)
-		}
-		return
-	}
-
-	// Avoid unnecessary recursions of symMerge
-	// by direct insertion of data[m] into data[a:m]
-	// if data[m:b] only contains one element.
-	if b-m == 1 {
-		// Use binary search to find the lowest index i
-		// such that data[i] > data[m] for a <= i < m.
-		// Exit the search loop with i == m in case no such index exists.
-		i := a
-		j := m
-		for i < j {
-			h := int(uint(i+j) >> 1)
-			if !data.Less(m, h) {
-				i = h + 1
-			} else {
-				j = h
-			}
-		}
-		// Swap values until data[m] reaches the position i.
-		for k := m; k > i; k-- {
-			data.Swap(k, k-1)
-		}
-		return
-	}
-
-	mid := int(uint(a+b) >> 1)
-	n := mid + m
-	var start, r int
-	if m > mid {
-		start = n - b
-		r = mid
-	} else {
-		start = a
-		r = m
-	}
-	p := n - 1
-
-	for start < r {
-		c := int(uint(start+r) >> 1)
-		if !data.Less(p-c, c) {
-			start = c + 1
-		} else {
-			r = c
-		}
-	}
-
-	end := n - start
-	if start < m && m < end {
-		rotate(data, start, m, end)
-	}
-	if a < start && start < mid {
-		symMerge(data, a, start, mid)
-	}
-	if mid < end && end < b {
-		symMerge(data, mid, end, b)
-	}
-}
-
-// rotate rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data:
-// Data of the form 'x u v y' is changed to 'x v u y'.
-// rotate performs at most b-a many calls to data.Swap,
-// and it assumes non-degenerate arguments: a < m && m < b.
-func rotate(data Interface, a, m, b int) {
-	i := m - a
-	j := b - m
-
-	for i != j {
-		if i > j {
-			swapRange(data, m-i, m, j)
-			i -= j
-		} else {
-			swapRange(data, m-i, m+j-i, i)
-			j -= i
-		}
-	}
-	// i == j
-	swapRange(data, m-i, m, i)
-}
-
 /*
 Complexity of Stable Sorting
 
diff --git a/src/sort/zfuncversion.go b/src/sort/zfuncversion.go
deleted file mode 100644
index 30067cbe077bd0e1c8b570cc5c71450fae3ce16c..0000000000000000000000000000000000000000
--- a/src/sort/zfuncversion.go
+++ /dev/null
@@ -1,265 +0,0 @@
-// Code generated from sort.go using genzfunc.go; DO NOT EDIT.
-
-// Copyright 2016 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 sort
-
-// Auto-generated variant of sort.go:insertionSort
-func insertionSort_func(data lessSwap, a, b int) {
-	for i := a + 1; i < b; i++ {
-		for j := i; j > a && data.Less(j, j-1); j-- {
-			data.Swap(j, j-1)
-		}
-	}
-}
-
-// Auto-generated variant of sort.go:siftDown
-func siftDown_func(data lessSwap, lo, hi, first int) {
-	root := lo
-	for {
-		child := 2*root + 1
-		if child >= hi {
-			break
-		}
-		if child+1 < hi && data.Less(first+child, first+child+1) {
-			child++
-		}
-		if !data.Less(first+root, first+child) {
-			return
-		}
-		data.Swap(first+root, first+child)
-		root = child
-	}
-}
-
-// Auto-generated variant of sort.go:heapSort
-func heapSort_func(data lessSwap, a, b int) {
-	first := a
-	lo := 0
-	hi := b - a
-	for i := (hi - 1) / 2; i >= 0; i-- {
-		siftDown_func(data, i, hi, first)
-	}
-	for i := hi - 1; i >= 0; i-- {
-		data.Swap(first, first+i)
-		siftDown_func(data, lo, i, first)
-	}
-}
-
-// Auto-generated variant of sort.go:medianOfThree
-func medianOfThree_func(data lessSwap, m1, m0, m2 int) {
-	if data.Less(m1, m0) {
-		data.Swap(m1, m0)
-	}
-	if data.Less(m2, m1) {
-		data.Swap(m2, m1)
-		if data.Less(m1, m0) {
-			data.Swap(m1, m0)
-		}
-	}
-}
-
-// Auto-generated variant of sort.go:swapRange
-func swapRange_func(data lessSwap, a, b, n int) {
-	for i := 0; i < n; i++ {
-		data.Swap(a+i, b+i)
-	}
-}
-
-// Auto-generated variant of sort.go:doPivot
-func doPivot_func(data lessSwap, lo, hi int) (midlo, midhi int) {
-	m := int(uint(lo+hi) >> 1)
-	if hi-lo > 40 {
-		s := (hi - lo) / 8
-		medianOfThree_func(data, lo, lo+s, lo+2*s)
-		medianOfThree_func(data, m, m-s, m+s)
-		medianOfThree_func(data, hi-1, hi-1-s, hi-1-2*s)
-	}
-	medianOfThree_func(data, lo, m, hi-1)
-	pivot := lo
-	a, c := lo+1, hi-1
-	for ; a < c && data.Less(a, pivot); a++ {
-	}
-	b := a
-	for {
-		for ; b < c && !data.Less(pivot, b); b++ {
-		}
-		for ; b < c && data.Less(pivot, c-1); c-- {
-		}
-		if b >= c {
-			break
-		}
-		data.Swap(b, c-1)
-		b++
-		c--
-	}
-	protect := hi-c < 5
-	if !protect && hi-c < (hi-lo)/4 {
-		dups := 0
-		if !data.Less(pivot, hi-1) {
-			data.Swap(c, hi-1)
-			c++
-			dups++
-		}
-		if !data.Less(b-1, pivot) {
-			b--
-			dups++
-		}
-		if !data.Less(m, pivot) {
-			data.Swap(m, b-1)
-			b--
-			dups++
-		}
-		protect = dups > 1
-	}
-	if protect {
-		for {
-			for ; a < b && !data.Less(b-1, pivot); b-- {
-			}
-			for ; a < b && data.Less(a, pivot); a++ {
-			}
-			if a >= b {
-				break
-			}
-			data.Swap(a, b-1)
-			a++
-			b--
-		}
-	}
-	data.Swap(pivot, b-1)
-	return b - 1, c
-}
-
-// Auto-generated variant of sort.go:quickSort
-func quickSort_func(data lessSwap, a, b, maxDepth int) {
-	for b-a > 12 {
-		if maxDepth == 0 {
-			heapSort_func(data, a, b)
-			return
-		}
-		maxDepth--
-		mlo, mhi := doPivot_func(data, a, b)
-		if mlo-a < b-mhi {
-			quickSort_func(data, a, mlo, maxDepth)
-			a = mhi
-		} else {
-			quickSort_func(data, mhi, b, maxDepth)
-			b = mlo
-		}
-	}
-	if b-a > 1 {
-		for i := a + 6; i < b; i++ {
-			if data.Less(i, i-6) {
-				data.Swap(i, i-6)
-			}
-		}
-		insertionSort_func(data, a, b)
-	}
-}
-
-// Auto-generated variant of sort.go:stable
-func stable_func(data lessSwap, n int) {
-	blockSize := 20
-	a, b := 0, blockSize
-	for b <= n {
-		insertionSort_func(data, a, b)
-		a = b
-		b += blockSize
-	}
-	insertionSort_func(data, a, n)
-	for blockSize < n {
-		a, b = 0, 2*blockSize
-		for b <= n {
-			symMerge_func(data, a, a+blockSize, b)
-			a = b
-			b += 2 * blockSize
-		}
-		if m := a + blockSize; m < n {
-			symMerge_func(data, a, m, n)
-		}
-		blockSize *= 2
-	}
-}
-
-// Auto-generated variant of sort.go:symMerge
-func symMerge_func(data lessSwap, a, m, b int) {
-	if m-a == 1 {
-		i := m
-		j := b
-		for i < j {
-			h := int(uint(i+j) >> 1)
-			if data.Less(h, a) {
-				i = h + 1
-			} else {
-				j = h
-			}
-		}
-		for k := a; k < i-1; k++ {
-			data.Swap(k, k+1)
-		}
-		return
-	}
-	if b-m == 1 {
-		i := a
-		j := m
-		for i < j {
-			h := int(uint(i+j) >> 1)
-			if !data.Less(m, h) {
-				i = h + 1
-			} else {
-				j = h
-			}
-		}
-		for k := m; k > i; k-- {
-			data.Swap(k, k-1)
-		}
-		return
-	}
-	mid := int(uint(a+b) >> 1)
-	n := mid + m
-	var start, r int
-	if m > mid {
-		start = n - b
-		r = mid
-	} else {
-		start = a
-		r = m
-	}
-	p := n - 1
-	for start < r {
-		c := int(uint(start+r) >> 1)
-		if !data.Less(p-c, c) {
-			start = c + 1
-		} else {
-			r = c
-		}
-	}
-	end := n - start
-	if start < m && m < end {
-		rotate_func(data, start, m, end)
-	}
-	if a < start && start < mid {
-		symMerge_func(data, a, start, mid)
-	}
-	if mid < end && end < b {
-		symMerge_func(data, mid, end, b)
-	}
-}
-
-// Auto-generated variant of sort.go:rotate
-func rotate_func(data lessSwap, a, m, b int) {
-	i := m - a
-	j := b - m
-	for i != j {
-		if i > j {
-			swapRange_func(data, m-i, m, j)
-			i -= j
-		} else {
-			swapRange_func(data, m-i, m+j-i, i)
-			j -= i
-		}
-	}
-	swapRange_func(data, m-i, m, i)
-}
diff --git a/src/sort/zsortfunc.go b/src/sort/zsortfunc.go
new file mode 100644
index 0000000000000000000000000000000000000000..80c8a77995987d2d32ccc3aed72db8d402b708a7
--- /dev/null
+++ b/src/sort/zsortfunc.go
@@ -0,0 +1,342 @@
+// Code generated by gen_sort_variants.go; DO NOT EDIT.
+
+// Copyright 2022 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 sort
+
+// insertionSort_func sorts data[a:b] using insertion sort.
+func insertionSort_func(data lessSwap, a, b int) {
+	for i := a + 1; i < b; i++ {
+		for j := i; j > a && data.Less(j, j-1); j-- {
+			data.Swap(j, j-1)
+		}
+	}
+}
+
+// siftDown_func implements the heap property on data[lo:hi].
+// first is an offset into the array where the root of the heap lies.
+func siftDown_func(data lessSwap, lo, hi, first int) {
+	root := lo
+	for {
+		child := 2*root + 1
+		if child >= hi {
+			break
+		}
+		if child+1 < hi && data.Less(first+child, first+child+1) {
+			child++
+		}
+		if !data.Less(first+root, first+child) {
+			return
+		}
+		data.Swap(first+root, first+child)
+		root = child
+	}
+}
+
+func heapSort_func(data lessSwap, a, b int) {
+	first := a
+	lo := 0
+	hi := b - a
+
+	// Build heap with greatest element at top.
+	for i := (hi - 1) / 2; i >= 0; i-- {
+		siftDown_func(data, i, hi, first)
+	}
+
+	// Pop elements, largest first, into end of data.
+	for i := hi - 1; i >= 0; i-- {
+		data.Swap(first, first+i)
+		siftDown_func(data, lo, i, first)
+	}
+}
+
+// Quicksort, loosely following Bentley and McIlroy,
+// "Engineering a Sort Function" SP&E November 1993.
+
+// medianOfThree_func moves the median of the three values data[m0], data[m1], data[m2] into data[m1].
+func medianOfThree_func(data lessSwap, m1, m0, m2 int) {
+	// sort 3 elements
+	if data.Less(m1, m0) {
+		data.Swap(m1, m0)
+	}
+	// data[m0] <= data[m1]
+	if data.Less(m2, m1) {
+		data.Swap(m2, m1)
+		// data[m0] <= data[m2] && data[m1] < data[m2]
+		if data.Less(m1, m0) {
+			data.Swap(m1, m0)
+		}
+	}
+	// now data[m0] <= data[m1] <= data[m2]
+}
+
+func swapRange_func(data lessSwap, a, b, n int) {
+	for i := 0; i < n; i++ {
+		data.Swap(a+i, b+i)
+	}
+}
+
+func doPivot_func(data lessSwap, lo, hi int) (midlo, midhi int) {
+	m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow.
+	if hi-lo > 40 {
+		// Tukey's "Ninther" median of three medians of three.
+		s := (hi - lo) / 8
+		medianOfThree_func(data, lo, lo+s, lo+2*s)
+		medianOfThree_func(data, m, m-s, m+s)
+		medianOfThree_func(data, hi-1, hi-1-s, hi-1-2*s)
+	}
+	medianOfThree_func(data, lo, m, hi-1)
+
+	// Invariants are:
+	//	data[lo] = pivot (set up by ChoosePivot)
+	//	data[lo < i < a] < pivot
+	//	data[a <= i < b] <= pivot
+	//	data[b <= i < c] unexamined
+	//	data[c <= i < hi-1] > pivot
+	//	data[hi-1] >= pivot
+	pivot := lo
+	a, c := lo+1, hi-1
+
+	for ; a < c && data.Less(a, pivot); a++ {
+	}
+	b := a
+	for {
+		for ; b < c && !data.Less(pivot, b); b++ { // data[b] <= pivot
+		}
+		for ; b < c && data.Less(pivot, c-1); c-- { // data[c-1] > pivot
+		}
+		if b >= c {
+			break
+		}
+		// data[b] > pivot; data[c-1] <= pivot
+		data.Swap(b, c-1)
+		b++
+		c--
+	}
+	// If hi-c<3 then there are duplicates (by property of median of nine).
+	// Let's be a bit more conservative, and set border to 5.
+	protect := hi-c < 5
+	if !protect && hi-c < (hi-lo)/4 {
+		// Lets test some points for equality to pivot
+		dups := 0
+		if !data.Less(pivot, hi-1) { // data[hi-1] = pivot
+			data.Swap(c, hi-1)
+			c++
+			dups++
+		}
+		if !data.Less(b-1, pivot) { // data[b-1] = pivot
+			b--
+			dups++
+		}
+		// m-lo = (hi-lo)/2 > 6
+		// b-lo > (hi-lo)*3/4-1 > 8
+		// ==> m < b ==> data[m] <= pivot
+		if !data.Less(m, pivot) { // data[m] = pivot
+			data.Swap(m, b-1)
+			b--
+			dups++
+		}
+		// if at least 2 points are equal to pivot, assume skewed distribution
+		protect = dups > 1
+	}
+	if protect {
+		// Protect against a lot of duplicates
+		// Add invariant:
+		//	data[a <= i < b] unexamined
+		//	data[b <= i < c] = pivot
+		for {
+			for ; a < b && !data.Less(b-1, pivot); b-- { // data[b] == pivot
+			}
+			for ; a < b && data.Less(a, pivot); a++ { // data[a] < pivot
+			}
+			if a >= b {
+				break
+			}
+			// data[a] == pivot; data[b-1] < pivot
+			data.Swap(a, b-1)
+			a++
+			b--
+		}
+	}
+	// Swap pivot into middle
+	data.Swap(pivot, b-1)
+	return b - 1, c
+}
+
+func quickSort_func(data lessSwap, a, b, maxDepth int) {
+	for b-a > 12 { // Use ShellSort for slices <= 12 elements
+		if maxDepth == 0 {
+			heapSort_func(data, a, b)
+			return
+		}
+		maxDepth--
+		mlo, mhi := doPivot_func(data, a, b)
+		// Avoiding recursion on the larger subproblem guarantees
+		// a stack depth of at most lg(b-a).
+		if mlo-a < b-mhi {
+			quickSort_func(data, a, mlo, maxDepth)
+			a = mhi // i.e., quickSort_func(data, mhi, b)
+		} else {
+			quickSort_func(data, mhi, b, maxDepth)
+			b = mlo // i.e., quickSort_func(data, a, mlo)
+		}
+	}
+	if b-a > 1 {
+		// Do ShellSort pass with gap 6
+		// It could be written in this simplified form cause b-a <= 12
+		for i := a + 6; i < b; i++ {
+			if data.Less(i, i-6) {
+				data.Swap(i, i-6)
+			}
+		}
+		insertionSort_func(data, a, b)
+	}
+}
+
+func stable_func(data lessSwap, n int) {
+	blockSize := 20 // must be > 0
+	a, b := 0, blockSize
+	for b <= n {
+		insertionSort_func(data, a, b)
+		a = b
+		b += blockSize
+	}
+	insertionSort_func(data, a, n)
+
+	for blockSize < n {
+		a, b = 0, 2*blockSize
+		for b <= n {
+			symMerge_func(data, a, a+blockSize, b)
+			a = b
+			b += 2 * blockSize
+		}
+		if m := a + blockSize; m < n {
+			symMerge_func(data, a, m, n)
+		}
+		blockSize *= 2
+	}
+}
+
+// symMerge_func merges the two sorted subsequences data[a:m] and data[m:b] using
+// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum
+// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz
+// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in
+// Computer Science, pages 714-723. Springer, 2004.
+//
+// Let M = m-a and N = b-n. Wolog M < N.
+// The recursion depth is bound by ceil(log(N+M)).
+// The algorithm needs O(M*log(N/M + 1)) calls to data.Less.
+// The algorithm needs O((M+N)*log(M)) calls to data.Swap.
+//
+// The paper gives O((M+N)*log(M)) as the number of assignments assuming a
+// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation
+// in the paper carries through for Swap operations, especially as the block
+// swapping rotate uses only O(M+N) Swaps.
+//
+// symMerge assumes non-degenerate arguments: a < m && m < b.
+// Having the caller check this condition eliminates many leaf recursion calls,
+// which improves performance.
+func symMerge_func(data lessSwap, a, m, b int) {
+	// Avoid unnecessary recursions of symMerge
+	// by direct insertion of data[a] into data[m:b]
+	// if data[a:m] only contains one element.
+	if m-a == 1 {
+		// Use binary search to find the lowest index i
+		// such that data[i] >= data[a] for m <= i < b.
+		// Exit the search loop with i == b in case no such index exists.
+		i := m
+		j := b
+		for i < j {
+			h := int(uint(i+j) >> 1)
+			if data.Less(h, a) {
+				i = h + 1
+			} else {
+				j = h
+			}
+		}
+		// Swap values until data[a] reaches the position before i.
+		for k := a; k < i-1; k++ {
+			data.Swap(k, k+1)
+		}
+		return
+	}
+
+	// Avoid unnecessary recursions of symMerge
+	// by direct insertion of data[m] into data[a:m]
+	// if data[m:b] only contains one element.
+	if b-m == 1 {
+		// Use binary search to find the lowest index i
+		// such that data[i] > data[m] for a <= i < m.
+		// Exit the search loop with i == m in case no such index exists.
+		i := a
+		j := m
+		for i < j {
+			h := int(uint(i+j) >> 1)
+			if !data.Less(m, h) {
+				i = h + 1
+			} else {
+				j = h
+			}
+		}
+		// Swap values until data[m] reaches the position i.
+		for k := m; k > i; k-- {
+			data.Swap(k, k-1)
+		}
+		return
+	}
+
+	mid := int(uint(a+b) >> 1)
+	n := mid + m
+	var start, r int
+	if m > mid {
+		start = n - b
+		r = mid
+	} else {
+		start = a
+		r = m
+	}
+	p := n - 1
+
+	for start < r {
+		c := int(uint(start+r) >> 1)
+		if !data.Less(p-c, c) {
+			start = c + 1
+		} else {
+			r = c
+		}
+	}
+
+	end := n - start
+	if start < m && m < end {
+		rotate_func(data, start, m, end)
+	}
+	if a < start && start < mid {
+		symMerge_func(data, a, start, mid)
+	}
+	if mid < end && end < b {
+		symMerge_func(data, mid, end, b)
+	}
+}
+
+// rotate_func rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data:
+// Data of the form 'x u v y' is changed to 'x v u y'.
+// rotate performs at most b-a many calls to data.Swap,
+// and it assumes non-degenerate arguments: a < m && m < b.
+func rotate_func(data lessSwap, a, m, b int) {
+	i := m - a
+	j := b - m
+
+	for i != j {
+		if i > j {
+			swapRange_func(data, m-i, m, j)
+			i -= j
+		} else {
+			swapRange_func(data, m-i, m+j-i, i)
+			j -= i
+		}
+	}
+	// i == j
+	swapRange_func(data, m-i, m, i)
+}
diff --git a/src/sort/zsortinterface.go b/src/sort/zsortinterface.go
new file mode 100644
index 0000000000000000000000000000000000000000..e0d70936785493e4a3500411b5978def8c7f79b8
--- /dev/null
+++ b/src/sort/zsortinterface.go
@@ -0,0 +1,342 @@
+// Code generated by gen_sort_variants.go; DO NOT EDIT.
+
+// Copyright 2022 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 sort
+
+// insertionSort sorts data[a:b] using insertion sort.
+func insertionSort(data Interface, a, b int) {
+	for i := a + 1; i < b; i++ {
+		for j := i; j > a && data.Less(j, j-1); j-- {
+			data.Swap(j, j-1)
+		}
+	}
+}
+
+// siftDown implements the heap property on data[lo:hi].
+// first is an offset into the array where the root of the heap lies.
+func siftDown(data Interface, lo, hi, first int) {
+	root := lo
+	for {
+		child := 2*root + 1
+		if child >= hi {
+			break
+		}
+		if child+1 < hi && data.Less(first+child, first+child+1) {
+			child++
+		}
+		if !data.Less(first+root, first+child) {
+			return
+		}
+		data.Swap(first+root, first+child)
+		root = child
+	}
+}
+
+func heapSort(data Interface, a, b int) {
+	first := a
+	lo := 0
+	hi := b - a
+
+	// Build heap with greatest element at top.
+	for i := (hi - 1) / 2; i >= 0; i-- {
+		siftDown(data, i, hi, first)
+	}
+
+	// Pop elements, largest first, into end of data.
+	for i := hi - 1; i >= 0; i-- {
+		data.Swap(first, first+i)
+		siftDown(data, lo, i, first)
+	}
+}
+
+// Quicksort, loosely following Bentley and McIlroy,
+// "Engineering a Sort Function" SP&E November 1993.
+
+// medianOfThree moves the median of the three values data[m0], data[m1], data[m2] into data[m1].
+func medianOfThree(data Interface, m1, m0, m2 int) {
+	// sort 3 elements
+	if data.Less(m1, m0) {
+		data.Swap(m1, m0)
+	}
+	// data[m0] <= data[m1]
+	if data.Less(m2, m1) {
+		data.Swap(m2, m1)
+		// data[m0] <= data[m2] && data[m1] < data[m2]
+		if data.Less(m1, m0) {
+			data.Swap(m1, m0)
+		}
+	}
+	// now data[m0] <= data[m1] <= data[m2]
+}
+
+func swapRange(data Interface, a, b, n int) {
+	for i := 0; i < n; i++ {
+		data.Swap(a+i, b+i)
+	}
+}
+
+func doPivot(data Interface, lo, hi int) (midlo, midhi int) {
+	m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow.
+	if hi-lo > 40 {
+		// Tukey's "Ninther" median of three medians of three.
+		s := (hi - lo) / 8
+		medianOfThree(data, lo, lo+s, lo+2*s)
+		medianOfThree(data, m, m-s, m+s)
+		medianOfThree(data, hi-1, hi-1-s, hi-1-2*s)
+	}
+	medianOfThree(data, lo, m, hi-1)
+
+	// Invariants are:
+	//	data[lo] = pivot (set up by ChoosePivot)
+	//	data[lo < i < a] < pivot
+	//	data[a <= i < b] <= pivot
+	//	data[b <= i < c] unexamined
+	//	data[c <= i < hi-1] > pivot
+	//	data[hi-1] >= pivot
+	pivot := lo
+	a, c := lo+1, hi-1
+
+	for ; a < c && data.Less(a, pivot); a++ {
+	}
+	b := a
+	for {
+		for ; b < c && !data.Less(pivot, b); b++ { // data[b] <= pivot
+		}
+		for ; b < c && data.Less(pivot, c-1); c-- { // data[c-1] > pivot
+		}
+		if b >= c {
+			break
+		}
+		// data[b] > pivot; data[c-1] <= pivot
+		data.Swap(b, c-1)
+		b++
+		c--
+	}
+	// If hi-c<3 then there are duplicates (by property of median of nine).
+	// Let's be a bit more conservative, and set border to 5.
+	protect := hi-c < 5
+	if !protect && hi-c < (hi-lo)/4 {
+		// Lets test some points for equality to pivot
+		dups := 0
+		if !data.Less(pivot, hi-1) { // data[hi-1] = pivot
+			data.Swap(c, hi-1)
+			c++
+			dups++
+		}
+		if !data.Less(b-1, pivot) { // data[b-1] = pivot
+			b--
+			dups++
+		}
+		// m-lo = (hi-lo)/2 > 6
+		// b-lo > (hi-lo)*3/4-1 > 8
+		// ==> m < b ==> data[m] <= pivot
+		if !data.Less(m, pivot) { // data[m] = pivot
+			data.Swap(m, b-1)
+			b--
+			dups++
+		}
+		// if at least 2 points are equal to pivot, assume skewed distribution
+		protect = dups > 1
+	}
+	if protect {
+		// Protect against a lot of duplicates
+		// Add invariant:
+		//	data[a <= i < b] unexamined
+		//	data[b <= i < c] = pivot
+		for {
+			for ; a < b && !data.Less(b-1, pivot); b-- { // data[b] == pivot
+			}
+			for ; a < b && data.Less(a, pivot); a++ { // data[a] < pivot
+			}
+			if a >= b {
+				break
+			}
+			// data[a] == pivot; data[b-1] < pivot
+			data.Swap(a, b-1)
+			a++
+			b--
+		}
+	}
+	// Swap pivot into middle
+	data.Swap(pivot, b-1)
+	return b - 1, c
+}
+
+func quickSort(data Interface, a, b, maxDepth int) {
+	for b-a > 12 { // Use ShellSort for slices <= 12 elements
+		if maxDepth == 0 {
+			heapSort(data, a, b)
+			return
+		}
+		maxDepth--
+		mlo, mhi := doPivot(data, a, b)
+		// Avoiding recursion on the larger subproblem guarantees
+		// a stack depth of at most lg(b-a).
+		if mlo-a < b-mhi {
+			quickSort(data, a, mlo, maxDepth)
+			a = mhi // i.e., quickSort(data, mhi, b)
+		} else {
+			quickSort(data, mhi, b, maxDepth)
+			b = mlo // i.e., quickSort(data, a, mlo)
+		}
+	}
+	if b-a > 1 {
+		// Do ShellSort pass with gap 6
+		// It could be written in this simplified form cause b-a <= 12
+		for i := a + 6; i < b; i++ {
+			if data.Less(i, i-6) {
+				data.Swap(i, i-6)
+			}
+		}
+		insertionSort(data, a, b)
+	}
+}
+
+func stable(data Interface, n int) {
+	blockSize := 20 // must be > 0
+	a, b := 0, blockSize
+	for b <= n {
+		insertionSort(data, a, b)
+		a = b
+		b += blockSize
+	}
+	insertionSort(data, a, n)
+
+	for blockSize < n {
+		a, b = 0, 2*blockSize
+		for b <= n {
+			symMerge(data, a, a+blockSize, b)
+			a = b
+			b += 2 * blockSize
+		}
+		if m := a + blockSize; m < n {
+			symMerge(data, a, m, n)
+		}
+		blockSize *= 2
+	}
+}
+
+// symMerge merges the two sorted subsequences data[a:m] and data[m:b] using
+// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum
+// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz
+// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in
+// Computer Science, pages 714-723. Springer, 2004.
+//
+// Let M = m-a and N = b-n. Wolog M < N.
+// The recursion depth is bound by ceil(log(N+M)).
+// The algorithm needs O(M*log(N/M + 1)) calls to data.Less.
+// The algorithm needs O((M+N)*log(M)) calls to data.Swap.
+//
+// The paper gives O((M+N)*log(M)) as the number of assignments assuming a
+// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation
+// in the paper carries through for Swap operations, especially as the block
+// swapping rotate uses only O(M+N) Swaps.
+//
+// symMerge assumes non-degenerate arguments: a < m && m < b.
+// Having the caller check this condition eliminates many leaf recursion calls,
+// which improves performance.
+func symMerge(data Interface, a, m, b int) {
+	// Avoid unnecessary recursions of symMerge
+	// by direct insertion of data[a] into data[m:b]
+	// if data[a:m] only contains one element.
+	if m-a == 1 {
+		// Use binary search to find the lowest index i
+		// such that data[i] >= data[a] for m <= i < b.
+		// Exit the search loop with i == b in case no such index exists.
+		i := m
+		j := b
+		for i < j {
+			h := int(uint(i+j) >> 1)
+			if data.Less(h, a) {
+				i = h + 1
+			} else {
+				j = h
+			}
+		}
+		// Swap values until data[a] reaches the position before i.
+		for k := a; k < i-1; k++ {
+			data.Swap(k, k+1)
+		}
+		return
+	}
+
+	// Avoid unnecessary recursions of symMerge
+	// by direct insertion of data[m] into data[a:m]
+	// if data[m:b] only contains one element.
+	if b-m == 1 {
+		// Use binary search to find the lowest index i
+		// such that data[i] > data[m] for a <= i < m.
+		// Exit the search loop with i == m in case no such index exists.
+		i := a
+		j := m
+		for i < j {
+			h := int(uint(i+j) >> 1)
+			if !data.Less(m, h) {
+				i = h + 1
+			} else {
+				j = h
+			}
+		}
+		// Swap values until data[m] reaches the position i.
+		for k := m; k > i; k-- {
+			data.Swap(k, k-1)
+		}
+		return
+	}
+
+	mid := int(uint(a+b) >> 1)
+	n := mid + m
+	var start, r int
+	if m > mid {
+		start = n - b
+		r = mid
+	} else {
+		start = a
+		r = m
+	}
+	p := n - 1
+
+	for start < r {
+		c := int(uint(start+r) >> 1)
+		if !data.Less(p-c, c) {
+			start = c + 1
+		} else {
+			r = c
+		}
+	}
+
+	end := n - start
+	if start < m && m < end {
+		rotate(data, start, m, end)
+	}
+	if a < start && start < mid {
+		symMerge(data, a, start, mid)
+	}
+	if mid < end && end < b {
+		symMerge(data, mid, end, b)
+	}
+}
+
+// rotate rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data:
+// Data of the form 'x u v y' is changed to 'x v u y'.
+// rotate performs at most b-a many calls to data.Swap,
+// and it assumes non-degenerate arguments: a < m && m < b.
+func rotate(data Interface, a, m, b int) {
+	i := m - a
+	j := b - m
+
+	for i != j {
+		if i > j {
+			swapRange(data, m-i, m, j)
+			i -= j
+		} else {
+			swapRange(data, m-i, m+j-i, i)
+			j -= i
+		}
+	}
+	// i == j
+	swapRange(data, m-i, m, i)
+}