all: merge master (49d48a0) into gopls-release-branch.0.8

Also update gopls/go.mod with a replace directive.

For golang/go#51659

Merge List:

+ 2022-03-14 49d48a0b go/analysis/passes/composite: allow InternalFuzzTarget
+ 2022-03-11 198cae37 go/ssa: split pkg() into different cases for *Package and *types.Package
+ 2022-03-10 ee31f706 internal/lsp: add completion for use directives
+ 2022-03-10 622cf7b3 internal/lsp/cache: copy workFile when invalidating workspace
+ 2022-03-10 e7a12a33 go/ssa: add type substitution
+ 2022-03-10 c773560c internal/lsp/cache: set GOWORK=off when mutating modfiles
+ 2022-03-10 613589de internal/lsp: more precise completions for *testing.F fuzz methods
+ 2022-03-10 9f99e956 go/analysis: add analyzer for f.Add
+ 2022-03-10 7b442e3c go/callgraph/vta: fix package doc
+ 2022-03-09 1670aada go/analysis: fix bug with finding field type
+ 2022-03-09 fd72fd66 go/ssa/interp: adding external functions for tests
+ 2022-03-09 b105aac5 internal/lsp: use regexp to add go mod edit -go quick fix
+ 2022-03-09 03d333aa internal/lsp: add snippet completion for function type parameters
+ 2022-03-09 94322c4f go/internal/gcimporter: pass -p to compiler in test
+ 2022-03-07 b59c441b internal/lsp/cache: always consider go.work files for ws expansion
+ 2022-03-04 e155b03a cmd/getgo: exec main from TestMain instead of running 'go build' in tests
+ 2022-03-04 e5622765 internal/lsp: add hover for go.work use statements
+ 2022-03-04 121d1e44 internal/lsp: report diagnostics on go.work files
+ 2022-03-04 0eabed70 cmd/file2fuzz: exec main from TestMain instead of running 'go build' in tests
+ 2022-03-04 19fe2d77 gopls: update xurls dependency, remove \b workaround
+ 2022-03-03 3ce77287 internal/lsp/source: support stubbing concrete type params
+ 2022-03-03 32869278 internal/lsp/cache: construct workspace even when go.work has error
+ 2022-03-02 09e02016 go/ssa: determine whether a type is parameterized.
+ 2022-03-02 e43402d2 go/ssa: changes Package.values to objects.
+ 2022-03-02 a972457a go/ssa: adds *types.Info field to ssa.Function.
+ 2022-03-02 fc479468 go/ssa: Move BasicBlock operations into block.go
+ 2022-03-02 7103138b gopls: add regtest for edit go directive quick fix
+ 2022-03-02 4a5e7f0d internal/lsp: correctly apply file edits for edit go directive
+ 2022-03-02 6a6eb596 go/ssa: Put type canonicalization on own mutex.
+ 2022-03-02 afc5fce2 gopls/doc: address additional comments on workspace.md
+ 2022-03-02 abbbcaf7 gopls/doc: update the documentation for workspaces to suggest go.work
+ 2022-03-02 72442fe0 gopls: update neovim documentation for imitating goimports
+ 2022-03-01 ffa170dc internal/jsonrpc2_v2: fix a racy map assignment in readIncoming
+ 2022-03-01 625c871d gopls: update neovim documentation for using go.work
+ 2022-03-01 fb3622a9 signature-generator: add Go func signature fuzzing tools
+ 2022-03-01 5d35a750 internal/jsonrpc2_v2: clarify documentation

Change-Id: Iff6ca42c407f6e63b3f99453d523dec4bcb44b1f
diff --git a/cmd/file2fuzz/main_test.go b/cmd/file2fuzz/main_test.go
index 55d824c..fe2c103 100644
--- a/cmd/file2fuzz/main_test.go
+++ b/cmd/file2fuzz/main_test.go
@@ -5,67 +5,41 @@
 package main
 
 import (
-	"fmt"
 	"io/ioutil"
 	"os"
 	"os/exec"
 	"path/filepath"
-	"runtime"
 	"strings"
 	"sync"
 	"testing"
 )
 
-// The setup for this test is mostly cribbed from x/exp/txtar.
+func TestMain(m *testing.M) {
+	if os.Getenv("GO_FILE2FUZZ_TEST_IS_FILE2FUZZ") != "" {
+		main()
+		os.Exit(0)
+	}
 
-var buildBin struct {
+	os.Exit(m.Run())
+}
+
+var f2f struct {
 	once sync.Once
-	name string
+	path string
 	err  error
 }
 
-func binPath(t *testing.T) string {
-	t.Helper()
-	if _, err := exec.LookPath("go"); err != nil {
-		t.Skipf("cannot build file2fuzz binary: %v", err)
-	}
-
-	buildBin.once.Do(func() {
-		exe, err := ioutil.TempFile("", "file2fuzz-*.exe")
-		if err != nil {
-			buildBin.err = err
-			return
-		}
-		exe.Close()
-		buildBin.name = exe.Name()
-
-		cmd := exec.Command("go", "build", "-o", buildBin.name, ".")
-		out, err := cmd.CombinedOutput()
-		if err != nil {
-			buildBin.err = fmt.Errorf("%s: %v\n%s", strings.Join(cmd.Args, " "), err, out)
-		}
-	})
-
-	if buildBin.err != nil {
-		if runtime.GOOS == "android" {
-			t.Skipf("skipping test after failing to build file2fuzz binary: go_android_exec may have failed to copy needed dependencies (see https://golang.org/issue/37088)")
-		}
-		t.Fatal(buildBin.err)
-	}
-	return buildBin.name
-}
-
-func TestMain(m *testing.M) {
-	os.Exit(m.Run())
-	if buildBin.name != "" {
-		os.Remove(buildBin.name)
-	}
-}
-
 func file2fuzz(t *testing.T, dir string, args []string, stdin string) (string, bool) {
-	t.Helper()
-	cmd := exec.Command(binPath(t), args...)
+	f2f.once.Do(func() {
+		f2f.path, f2f.err = os.Executable()
+	})
+	if f2f.err != nil {
+		t.Fatal(f2f.err)
+	}
+
+	cmd := exec.Command(f2f.path, args...)
 	cmd.Dir = dir
+	cmd.Env = append(os.Environ(), "PWD="+dir, "GO_FILE2FUZZ_TEST_IS_FILE2FUZZ=1")
 	if stdin != "" {
 		cmd.Stdin = strings.NewReader(stdin)
 	}
diff --git a/cmd/getgo/.gitignore b/cmd/getgo/.gitignore
index d4984ab..47fe984 100644
--- a/cmd/getgo/.gitignore
+++ b/cmd/getgo/.gitignore
@@ -1,3 +1,2 @@
 build
-testgetgo
 getgo
diff --git a/cmd/getgo/main_test.go b/cmd/getgo/main_test.go
index 0c0e8b9..fc28c5d 100644
--- a/cmd/getgo/main_test.go
+++ b/cmd/getgo/main_test.go
@@ -13,50 +13,27 @@
 	"io/ioutil"
 	"os"
 	"os/exec"
-	"runtime"
 	"testing"
 )
 
-const (
-	testbin = "testgetgo"
-)
-
-var (
-	exeSuffix string // ".exe" on Windows
-)
-
-func init() {
-	if runtime.GOOS == "windows" {
-		exeSuffix = ".exe"
-	}
-}
-
-// TestMain creates a getgo command for testing purposes and
-// deletes it after the tests have been run.
 func TestMain(m *testing.M) {
+	if os.Getenv("GO_GETGO_TEST_IS_GETGO") != "" {
+		main()
+		os.Exit(0)
+	}
+
 	if os.Getenv("GOGET_INTEGRATION") == "" {
 		fmt.Fprintln(os.Stderr, "main_test: Skipping integration tests with GOGET_INTEGRATION unset")
 		return
 	}
 
-	args := []string{"build", "-tags", testbin, "-o", testbin + exeSuffix}
-	out, err := exec.Command("go", args...).CombinedOutput()
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "building %s failed: %v\n%s", testbin, err, out)
-		os.Exit(2)
-	}
-
 	// Don't let these environment variables confuse the test.
 	os.Unsetenv("GOBIN")
 	os.Unsetenv("GOPATH")
 	os.Unsetenv("GIT_ALLOW_PROTOCOL")
 	os.Unsetenv("PATH")
 
-	r := m.Run()
-
-	os.Remove(testbin + exeSuffix)
-
-	os.Exit(r)
+	os.Exit(m.Run())
 }
 
 func createTmpHome(t *testing.T) string {
@@ -72,12 +49,18 @@
 // doRun runs the test getgo command, recording stdout and stderr and
 // returning exit status.
 func doRun(t *testing.T, args ...string) error {
+	exe, err := os.Executable()
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Helper()
+
+	t.Logf("running getgo %v", args)
 	var stdout, stderr bytes.Buffer
-	t.Logf("running %s %v", testbin, args)
-	cmd := exec.Command("./"+testbin+exeSuffix, args...)
+	cmd := exec.Command(exe, args...)
 	cmd.Stdout = &stdout
 	cmd.Stderr = &stderr
-	cmd.Env = os.Environ()
+	cmd.Env = append(os.Environ(), "GO_GETGO_TEST_IS_GETGO=1")
 	status := cmd.Run()
 	if stdout.Len() > 0 {
 		t.Log("standard output:")
diff --git a/cmd/signature-fuzzer/README.md b/cmd/signature-fuzzer/README.md
new file mode 100644
index 0000000..a7de540
--- /dev/null
+++ b/cmd/signature-fuzzer/README.md
@@ -0,0 +1,159 @@
+# signature-fuzzer
+
+This directory contains utilities for fuzz testing of Go function signatures, for use in developing/testing a Go compiler.
+
+The basic idea of the fuzzer is that it emits source code for a stand-alone Go program; this generated program is a series of pairs of functions, a "Caller" function and a "Checker" function. The signature of the Checker function is generated randomly (random number of parameters and returns, each with randomly chosen types). The "Caller" func contains invocations of the "Checker" function, each passing randomly chosen values to the params of the "Checker", then the caller verifies that expected values are returned correctly. The "Checker" function in turn has code to verify that the expected values (more details below).
+
+There are three main parts to the fuzzer: a generator package, a driver package, and a runner package.
+
+The "generator" contains the guts of the fuzzer, the bits that actually emit the random code.
+
+The "driver" is a stand-alone program that invokes the generator to create a single test program. It is not terribly useful on its own (since it doesn't actually build or run the generated program), but it is handy for debugging the generator or looking at examples of the emitted code.
+
+The "runner" is a more complete test harness; it repeatedly runs the generator to create a new test program, builds the test program, then runs it (checking for errors along the way). If at any point a build or test fails, the "runner" harness attempts a minimization process to try to narrow down the failure to a single package and/or function.
+
+## What the generated code looks like
+
+Generated Go functions will have an "interesting" set of signatures (mix of
+arrays, scalars, structs), intended to pick out corner cases and odd bits in the
+Go compiler's code that handles function calls and returns.
+
+The first generated file is genChecker.go, which contains function that look something
+like this (simplified):
+
+```
+type StructF4S0 struct {
+F0 float64
+F1 int16
+F2 uint16
+}
+
+// 0 returns 2 params
+func Test4(p0 int8, p1 StructF4S0)  {
+  c0 := int8(-1)
+  if p0 != c0 {
+    NoteFailure(4, "parm", 0)
+  }
+  c1 := StructF4S0{float64(2), int16(-3), uint16(4)}
+  if p1 != c1 {
+    NoteFailure(4, "parm", 1)
+  }
+  return
+}
+```
+
+Here the test generator has randomly selected 0 return values and 2 params, then randomly generated types for the params.
+
+The generator then emits code on the calling side into the file "genCaller.go", which might look like:
+
+```
+func Caller4() {
+var p0 int8
+p0 = int8(-1)
+var p1 StructF4S0
+p1 = StructF4S0{float64(2), int16(-3), uint16(4)}
+// 0 returns 2 params
+Test4(p0, p1)
+}
+```
+
+The generator then emits some utility functions (ex: NoteFailure) and a main routine that cycles through all of the tests.
+
+## Trying a single run of the generator
+
+To generate a set of source files just to see what they look like, you can build and run the test generator as follows. This creates a new directory "cabiTest" containing generated test files:
+
+```
+$ git clone https://golang.org/x/tools
+$ cd tools/cmd/signature-fuzzer/fuzz-driver
+$ go build .
+$ ./fuzz-driver -numpkgs 3 -numfcns 5 -seed 12345 -outdir /tmp/sigfuzzTest -pkgpath foobar
+$ cd /tmp/sigfuzzTest
+$ find . -type f -print
+./genCaller1/genCaller1.go
+./genUtils/genUtils.go
+./genChecker1/genChecker1.go
+./genChecker0/genChecker0.go
+./genCaller2/genCaller2.go
+./genCaller0/genCaller0.go
+./genMain.go
+./go.mod
+./genChecker2/genChecker2.go
+$
+```
+
+You can build and run the generated files in the usual way:
+
+```
+$ cd /tmp/sigfuzzTest
+$ go build .
+$ ./foobar
+starting main
+finished 15 tests
+$
+
+```
+
+## Example usage for the test runner
+
+The test runner orchestrates multiple runs of the fuzzer, iteratively emitting code, building it, and testing the resulting binary. To use the runner, build and invoke it with a specific number of iterations; it will select a new random seed on each invocation. The runner will terminate as soon as it finds a failure. Example:
+
+```
+$ git clone https://golang.org/x/tools
+$ cd tools/cmd/signature-fuzzer/fuzz-runner
+$ go build .
+$ ./fuzz-runner -numit=3
+... begin iteration 0 with current seed 67104558
+starting main
+finished 1000 tests
+... begin iteration 1 with current seed 67104659
+starting main
+finished 1000 tests
+... begin iteration 2 with current seed 67104760
+starting main
+finished 1000 tests
+$
+```
+
+If the runner encounters a failure, it will try to perform test-case "minimization", e.g. attempt to isolate the failure
+
+```
+$ cd tools/cmd/signature-fuzzer/fuzz-runner
+$ go build .
+$ ./fuzz-runner -n=10
+./fuzz-runner -n=10
+... begin iteration 0 with current seed 40661762
+Error: fail [reflect] |20|3|1| =Checker3.Test1= return 1
+error executing cmd ./fzTest: exit status 1
+... starting minimization for failed directory /tmp/fuzzrun1005327337/fuzzTest
+package minimization succeeded: found bad pkg 3
+function minimization succeeded: found bad fcn 1
+$
+```
+
+Here the runner has generates a failure, minimized it down to a single function and package, and left the resulting program in the output directory /tmp/fuzzrun1005327337/fuzzTest.
+
+## Limitations, future work
+
+No support yet for variadic functions.
+
+The set of generated types is still a bit thin; it has fairly limited support for interface values, and doesn't include channels.
+
+Todos:
+
+- better interface value coverage
+
+- implement testing of reflect.MakeFunc
+
+- extend to work with generic code of various types
+
+- extend to work in a debugging scenario (e.g. instead of just emitting code,
+  emit a script of debugger commands to run the program with expected
+  responses from the debugger)
+
+- rework things so that instead of always checking all of a given parameter
+  value, we sometimes skip over elements (or just check the length of a slice
+  or string as opposed to looking at its value)
+
+- consider adding runtime.GC() calls at some points in the generated code
+
diff --git a/cmd/signature-fuzzer/fuzz-driver/driver.go b/cmd/signature-fuzzer/fuzz-driver/driver.go
new file mode 100644
index 0000000..f61ca4b
--- /dev/null
+++ b/cmd/signature-fuzzer/fuzz-driver/driver.go
@@ -0,0 +1,168 @@
+// Copyright 2021 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.
+
+// Stand-alone driver for emitting function-signature test code.  This
+// program is mainly just a wrapper around the code that lives in the
+// fuzz-generator package; it is useful for generating a specific bad
+// code scenario for a given seed, or for doing development on the
+// fuzzer, but for doing actual fuzz testing, better to use
+// fuzz-runner.
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"log"
+	"math/rand"
+	"os"
+	"time"
+
+	generator "golang.org/x/tools/cmd/signature-fuzzer/internal/fuzz-generator"
+)
+
+// Basic options
+var numfcnflag = flag.Int("numfcns", 10, "Number of test func pairs to emit in each package")
+var numpkgflag = flag.Int("numpkgs", 1, "Number of test packages to emit")
+var seedflag = flag.Int64("seed", -1, "Random seed")
+var tagflag = flag.String("tag", "gen", "Prefix name of go files/pkgs to generate")
+var outdirflag = flag.String("outdir", "", "Output directory for generated files")
+var pkgpathflag = flag.String("pkgpath", "gen", "Base package path for generated files")
+
+// Options used for test case minimization.
+var fcnmaskflag = flag.String("fcnmask", "", "Mask containing list of fcn numbers to emit")
+var pkmaskflag = flag.String("pkgmask", "", "Mask containing list of pkg numbers to emit")
+
+// Options used to control which features are used in the generated code.
+var reflectflag = flag.Bool("reflect", true, "Include testing of reflect.Call.")
+var deferflag = flag.Bool("defer", true, "Include testing of defer stmts.")
+var recurflag = flag.Bool("recur", true, "Include testing of recursive calls.")
+var takeaddrflag = flag.Bool("takeaddr", true, "Include functions that take the address of their parameters and results.")
+var methodflag = flag.Bool("method", true, "Include testing of method calls.")
+var inlimitflag = flag.Int("inmax", -1, "Max number of input params.")
+var outlimitflag = flag.Int("outmax", -1, "Max number of input params.")
+var pragmaflag = flag.String("pragma", "", "Tag generated test routines with pragma //go:<value>.")
+var maxfailflag = flag.Int("maxfail", 10, "Maximum runtime failures before test self-terminates")
+var stackforceflag = flag.Bool("forcestackgrowth", true, "Use hooks to force stack growth.")
+
+// Debugging options
+var verbflag = flag.Int("v", 0, "Verbose trace output level")
+
+// Debugging/testing options. These tell the generator to emit "bad" code so as to
+// test the logic for detecting errors and/or minimization (in the fuzz runner).
+var emitbadflag = flag.Int("emitbad", 0, "[Testing only] force generator to emit 'bad' code.")
+var selbadpkgflag = flag.Int("badpkgidx", 0, "[Testing only] select index of bad package (used with -emitbad)")
+var selbadfcnflag = flag.Int("badfcnidx", 0, "[Testing only] select index of bad function (used with -emitbad)")
+
+// Misc options
+var goimpflag = flag.Bool("goimports", false, "Run 'goimports' on generated code.")
+var randctlflag = flag.Int("randctl", generator.RandCtlChecks|generator.RandCtlPanic, "Wraprand control flag")
+
+func verb(vlevel int, s string, a ...interface{}) {
+	if *verbflag >= vlevel {
+		fmt.Printf(s, a...)
+		fmt.Printf("\n")
+	}
+}
+
+func usage(msg string) {
+	if len(msg) > 0 {
+		fmt.Fprintf(os.Stderr, "error: %s\n", msg)
+	}
+	fmt.Fprintf(os.Stderr, "usage: fuzz-driver [flags]\n\n")
+	flag.PrintDefaults()
+	fmt.Fprintf(os.Stderr, "Example:\n\n")
+	fmt.Fprintf(os.Stderr, "  fuzz-driver -numpkgs=23 -numfcns=19 -seed 10101 -outdir gendir\n\n")
+	fmt.Fprintf(os.Stderr, "  \tgenerates a Go program with 437 test cases (23 packages, each \n")
+	fmt.Fprintf(os.Stderr, "  \twith 19 functions, for a total of 437 funcs total) into a set of\n")
+	fmt.Fprintf(os.Stderr, "  \tsub-directories in 'gendir', using random see 10101\n")
+
+	os.Exit(2)
+}
+
+func setupTunables() {
+	tunables := generator.DefaultTunables()
+	if !*reflectflag {
+		tunables.DisableReflectionCalls()
+	}
+	if !*deferflag {
+		tunables.DisableDefer()
+	}
+	if !*recurflag {
+		tunables.DisableRecursiveCalls()
+	}
+	if !*takeaddrflag {
+		tunables.DisableTakeAddr()
+	}
+	if !*methodflag {
+		tunables.DisableMethodCalls()
+	}
+	if *inlimitflag != -1 {
+		tunables.LimitInputs(*inlimitflag)
+	}
+	if *outlimitflag != -1 {
+		tunables.LimitOutputs(*outlimitflag)
+	}
+	generator.SetTunables(tunables)
+}
+
+func main() {
+	log.SetFlags(0)
+	log.SetPrefix("fuzz-driver: ")
+	flag.Parse()
+	generator.Verbctl = *verbflag
+	if *outdirflag == "" {
+		usage("select an output directory with -o flag")
+	}
+	verb(1, "in main verblevel=%d", *verbflag)
+	if *seedflag == -1 {
+		// user has not selected a specific seed -- pick one.
+		now := time.Now()
+		*seedflag = now.UnixNano() % 123456789
+		verb(0, "selected seed: %d", *seedflag)
+	}
+	rand.Seed(*seedflag)
+	if flag.NArg() != 0 {
+		usage("unknown extra arguments")
+	}
+	verb(1, "tag is %s", *tagflag)
+
+	fcnmask, err := generator.ParseMaskString(*fcnmaskflag, "fcn")
+	if err != nil {
+		usage(fmt.Sprintf("mangled fcn mask arg: %v", err))
+	}
+	pkmask, err := generator.ParseMaskString(*pkmaskflag, "pkg")
+	if err != nil {
+		usage(fmt.Sprintf("mangled pkg mask arg: %v", err))
+	}
+	verb(2, "pkg mask is %v", pkmask)
+	verb(2, "fn mask is %v", fcnmask)
+
+	verb(1, "starting generation")
+	setupTunables()
+	config := generator.GenConfig{
+		PkgPath:          *pkgpathflag,
+		Tag:              *tagflag,
+		OutDir:           *outdirflag,
+		NumTestPackages:  *numpkgflag,
+		NumTestFunctions: *numfcnflag,
+		Seed:             *seedflag,
+		Pragma:           *pragmaflag,
+		FcnMask:          fcnmask,
+		PkgMask:          pkmask,
+		MaxFail:          *maxfailflag,
+		ForceStackGrowth: *stackforceflag,
+		RandCtl:          *randctlflag,
+		RunGoImports:     *goimpflag,
+		EmitBad:          *emitbadflag,
+		BadPackageIdx:    *selbadpkgflag,
+		BadFuncIdx:       *selbadfcnflag,
+	}
+	errs := generator.Generate(config)
+	if errs != 0 {
+		log.Fatal("errors during generation")
+	}
+	verb(1, "... files written to directory %s", *outdirflag)
+	verb(1, "leaving main")
+}
diff --git a/cmd/signature-fuzzer/fuzz-driver/drv_test.go b/cmd/signature-fuzzer/fuzz-driver/drv_test.go
new file mode 100644
index 0000000..7de74c6
--- /dev/null
+++ b/cmd/signature-fuzzer/fuzz-driver/drv_test.go
@@ -0,0 +1,73 @@
+// Copyright 2021 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 main
+
+import (
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"testing"
+
+	"golang.org/x/tools/internal/testenv"
+)
+
+// buildDriver builds the fuzz-driver executable, returning its path.
+func buildDriver(t *testing.T) string {
+	t.Helper()
+	if runtime.GOOS == "android" {
+		t.Skipf("the dependencies are not available on android")
+		return ""
+	}
+	bindir := filepath.Join(t.TempDir(), "bin")
+	err := os.Mkdir(bindir, os.ModePerm)
+	if err != nil {
+		t.Fatal(err)
+	}
+	binary := filepath.Join(bindir, "driver")
+	if runtime.GOOS == "windows" {
+		binary += ".exe"
+	}
+	cmd := exec.Command("go", "build", "-o", binary)
+	if err := cmd.Run(); err != nil {
+		t.Fatalf("Building fuzz-driver: %v", err)
+	}
+	return binary
+}
+
+func TestEndToEndIntegration(t *testing.T) {
+	testenv.NeedsTool(t, "go")
+	td := t.TempDir()
+
+	// Build the fuzz-driver binary.
+	// Note: if more tests are added to this package, move this to single setup fcn, so
+	// that we don't have to redo the build each time.
+	binary := buildDriver(t)
+
+	// Kick off a run.
+	gendir := filepath.Join(td, "gen")
+	args := []string{"-numfcns", "3", "-numpkgs", "1", "-seed", "101", "-outdir", gendir}
+	c := exec.Command(binary, args...)
+	b, err := c.CombinedOutput()
+	if err != nil {
+		t.Fatalf("error invoking fuzz-driver: %v\n%s", err, b)
+	}
+
+	found := ""
+	walker := func(path string, info os.FileInfo, err error) error {
+		found = found + ":" + info.Name()
+		return nil
+	}
+
+	// Make sure it emits something.
+	err2 := filepath.Walk(gendir, walker)
+	if err2 != nil {
+		t.Fatalf("error from filepath.Walk: %v", err2)
+	}
+	const expected = ":gen:genCaller0:genCaller0.go:genChecker0:genChecker0.go:genMain.go:genUtils:genUtils.go:go.mod"
+	if found != expected {
+		t.Errorf("walk of generated code: got %s want %s", found, expected)
+	}
+}
diff --git a/cmd/signature-fuzzer/fuzz-runner/rnr_test.go b/cmd/signature-fuzzer/fuzz-runner/rnr_test.go
new file mode 100644
index 0000000..2bab5b4
--- /dev/null
+++ b/cmd/signature-fuzzer/fuzz-runner/rnr_test.go
@@ -0,0 +1,145 @@
+// Copyright 2021 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 main
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"strings"
+	"testing"
+
+	"golang.org/x/tools/internal/testenv"
+)
+
+func canRace(t *testing.T) bool {
+	_, err := exec.Command("go", "run", "-race", "./testdata/himom.go").CombinedOutput()
+	return err == nil
+}
+
+// buildRunner builds the fuzz-runner executable, returning its path.
+func buildRunner(t *testing.T) string {
+	bindir := filepath.Join(t.TempDir(), "bin")
+	err := os.Mkdir(bindir, os.ModePerm)
+	if err != nil {
+		t.Fatal(err)
+	}
+	binary := filepath.Join(bindir, "runner")
+	if runtime.GOOS == "windows" {
+		binary += ".exe"
+	}
+	cmd := exec.Command("go", "build", "-o", binary)
+	if err := cmd.Run(); err != nil {
+		t.Fatalf("Building fuzz-runner: %v", err)
+	}
+	return binary
+}
+
+// TestRunner builds the binary, then kicks off a collection of sub-tests that invoke it.
+func TestRunner(t *testing.T) {
+	testenv.NeedsTool(t, "go")
+	if runtime.GOOS == "android" {
+		t.Skipf("the dependencies are not available on android")
+	}
+	binaryPath := buildRunner(t)
+
+	// Sub-tests using the binary built above.
+	t.Run("Basic", func(t *testing.T) { testBasic(t, binaryPath) })
+	t.Run("Race", func(t *testing.T) { testRace(t, binaryPath) })
+	t.Run("Minimization1", func(t *testing.T) { testMinimization1(t, binaryPath) })
+	t.Run("Minimization2", func(t *testing.T) { testMinimization2(t, binaryPath) })
+}
+
+func testBasic(t *testing.T, binaryPath string) {
+	t.Parallel()
+	args := []string{"-numit=1", "-numfcns=1", "-numpkgs=1", "-seed=103", "-cleancache=0"}
+	c := exec.Command(binaryPath, args...)
+	b, err := c.CombinedOutput()
+	t.Logf("%s\n", b)
+	if err != nil {
+		t.Fatalf("error invoking fuzz-runner: %v", err)
+	}
+}
+
+func testRace(t *testing.T, binaryPath string) {
+	t.Parallel()
+	// For this test to work, the current test platform has to support the
+	// race detector. Check to see if that is the case by running a very
+	// simple Go program through it.
+	if !canRace(t) {
+		t.Skip("current platform does not appear to support the race detector")
+	}
+
+	args := []string{"-v=1", "-numit=1", "-race", "-numfcns=3", "-numpkgs=3", "-seed=987", "-cleancache=0"}
+	c := exec.Command(binaryPath, args...)
+	b, err := c.CombinedOutput()
+	t.Logf("%s\n", b)
+	if err != nil {
+		t.Fatalf("error invoking fuzz-runner: %v", err)
+	}
+}
+
+func testMinimization1(t *testing.T, binaryPath string) {
+	if binaryPath == "" {
+		t.Skipf("No runner binary")
+	}
+	t.Parallel()
+	// Fire off the runner passing it -emitbad=1, so that the generated code
+	// contains illegal Go code (which will force the build to fail). Verify that
+	// it does fail, that the error reflects the nature of the failure, and that
+	// we can minimize the error down to a single package.
+	args := []string{"-emitbad=1", "-badfcnidx=2", "-badpkgidx=2",
+		"-forcetmpclean", "-cleancache=0",
+		"-numit=1", "-numfcns=3", "-numpkgs=3", "-seed=909"}
+	invocation := fmt.Sprintf("%s %v", binaryPath, args)
+	c := exec.Command(binaryPath, args...)
+	b, err := c.CombinedOutput()
+	t.Logf("%s\n", b)
+	if err == nil {
+		t.Fatalf("unexpected pass of fuzz-runner (invocation %q): %v", invocation, err)
+	}
+	result := string(b)
+	if !strings.Contains(result, "syntax error") {
+		t.Fatalf("-emitbad=1 did not trigger syntax error (invocation %q): output: %s", invocation, result)
+	}
+	if !strings.Contains(result, "package minimization succeeded: found bad pkg 2") {
+		t.Fatalf("failed to minimize package (invocation %q): output: %s", invocation, result)
+	}
+	if !strings.Contains(result, "function minimization succeeded: found bad fcn 2") {
+		t.Fatalf("failed to minimize package (invocation %q): output: %s", invocation, result)
+	}
+}
+
+func testMinimization2(t *testing.T, binaryPath string) {
+	if binaryPath == "" {
+		t.Skipf("No runner binary")
+	}
+	t.Parallel()
+	// Fire off the runner passing it -emitbad=2, so that the
+	// generated code forces a runtime error. Verify that it does
+	// fail, and that the error is reflective.
+	args := []string{"-emitbad=2", "-badfcnidx=1", "-badpkgidx=1",
+		"-forcetmpclean", "-cleancache=0",
+		"-numit=1", "-numfcns=3", "-numpkgs=3", "-seed=55909"}
+	invocation := fmt.Sprintf("%s %v", binaryPath, args)
+	c := exec.Command(binaryPath, args...)
+	b, err := c.CombinedOutput()
+	t.Logf("%s\n", b)
+	if err == nil {
+		t.Fatalf("unexpected pass of fuzz-runner (invocation %q): %v", invocation, err)
+	}
+	result := string(b)
+	if !strings.Contains(result, "Error: fail") || !strings.Contains(result, "Checker1.Test1") {
+		t.Fatalf("-emitbad=2 did not trigger runtime error (invocation %q): output: %s", invocation, result)
+	}
+	if !strings.Contains(result, "package minimization succeeded: found bad pkg 1") {
+		t.Fatalf("failed to minimize package (invocation %q): output: %s", invocation, result)
+	}
+	if !strings.Contains(result, "function minimization succeeded: found bad fcn 1") {
+		t.Fatalf("failed to minimize package (invocation %q): output: %s", invocation, result)
+	}
+}
diff --git a/cmd/signature-fuzzer/fuzz-runner/runner.go b/cmd/signature-fuzzer/fuzz-runner/runner.go
new file mode 100644
index 0000000..4e5b413
--- /dev/null
+++ b/cmd/signature-fuzzer/fuzz-runner/runner.go
@@ -0,0 +1,443 @@
+// Copyright 2021 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.
+
+// Program for performing test runs using "fuzz-driver".
+// Main loop iteratively runs "fuzz-driver" to create a corpus,
+// then builds and runs the code. If a failure in the run is
+// detected, then a testcase minimization phase kicks in.
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"strconv"
+	"strings"
+	"time"
+
+	generator "golang.org/x/tools/cmd/signature-fuzzer/internal/fuzz-generator"
+)
+
+const pkName = "fzTest"
+
+// Basic options
+var verbflag = flag.Int("v", 0, "Verbose trace output level")
+var loopitflag = flag.Int("numit", 10, "Number of main loop iterations to run")
+var seedflag = flag.Int64("seed", -1, "Random seed")
+var execflag = flag.Bool("execdriver", false, "Exec fuzz-driver binary instead of invoking generator directly")
+var numpkgsflag = flag.Int("numpkgs", 50, "Number of test packages")
+var numfcnsflag = flag.Int("numfcns", 20, "Number of test functions per package.")
+
+// Debugging/testing options. These tell the generator to emit "bad" code so as to
+// test the logic for detecting errors and/or minimization.
+var emitbadflag = flag.Int("emitbad", -1, "[Testing only] force generator to emit 'bad' code.")
+var selbadpkgflag = flag.Int("badpkgidx", 0, "[Testing only] select index of bad package (used with -emitbad)")
+var selbadfcnflag = flag.Int("badfcnidx", 0, "[Testing only] select index of bad function (used with -emitbad)")
+var forcetmpcleanflag = flag.Bool("forcetmpclean", false, "[Testing only] force cleanup of temp dir")
+var cleancacheflag = flag.Bool("cleancache", true, "[Testing only] don't clean the go cache")
+var raceflag = flag.Bool("race", false, "[Testing only] build generated code with -race")
+
+func verb(vlevel int, s string, a ...interface{}) {
+	if *verbflag >= vlevel {
+		fmt.Printf(s, a...)
+		fmt.Printf("\n")
+	}
+}
+
+func warn(s string, a ...interface{}) {
+	fmt.Fprintf(os.Stderr, s, a...)
+	fmt.Fprintf(os.Stderr, "\n")
+}
+
+func fatal(s string, a ...interface{}) {
+	fmt.Fprintf(os.Stderr, s, a...)
+	fmt.Fprintf(os.Stderr, "\n")
+	os.Exit(1)
+}
+
+type config struct {
+	generator.GenConfig
+	tmpdir       string
+	gendir       string
+	buildOutFile string
+	runOutFile   string
+	gcflags      string
+	nerrors      int
+}
+
+func usage(msg string) {
+	if len(msg) > 0 {
+		fmt.Fprintf(os.Stderr, "error: %s\n", msg)
+	}
+	fmt.Fprintf(os.Stderr, "usage: fuzz-runner [flags]\n\n")
+	flag.PrintDefaults()
+	fmt.Fprintf(os.Stderr, "Example:\n\n")
+	fmt.Fprintf(os.Stderr, "  fuzz-runner -numit=500 -numpkgs=11 -numfcns=13 -seed=10101\n\n")
+	fmt.Fprintf(os.Stderr, "  \tRuns 500 rounds of test case generation\n")
+	fmt.Fprintf(os.Stderr, "  \tusing random see 10101, in each round emitting\n")
+	fmt.Fprintf(os.Stderr, "  \t11 packages each with 13 function pairs.\n")
+
+	os.Exit(2)
+}
+
+// docmd executes the specified command in the dir given and pipes the
+// output to stderr. return status is 0 if command passed, 1
+// otherwise.
+func docmd(cmd []string, dir string) int {
+	verb(2, "docmd: %s", strings.Join(cmd, " "))
+	c := exec.Command(cmd[0], cmd[1:]...)
+	if dir != "" {
+		c.Dir = dir
+	}
+	b, err := c.CombinedOutput()
+	st := 0
+	if err != nil {
+		warn("error executing cmd %s: %v",
+			strings.Join(cmd, " "), err)
+		st = 1
+	}
+	os.Stderr.Write(b)
+	return st
+}
+
+// docodmout forks and execs command 'cmd' in dir 'dir', redirecting
+// stderr and stdout from the execution to file 'outfile'.
+func docmdout(cmd []string, dir string, outfile string) int {
+	of, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
+	if err != nil {
+		fatal("opening outputfile %s: %v", outfile, err)
+	}
+	c := exec.Command(cmd[0], cmd[1:]...)
+	defer of.Close()
+	if dir != "" {
+		verb(2, "setting cmd.Dir to %s", dir)
+		c.Dir = dir
+	}
+	verb(2, "docmdout: %s > %s", strings.Join(cmd, " "), outfile)
+	c.Stdout = of
+	c.Stderr = of
+	err = c.Run()
+	st := 0
+	if err != nil {
+		warn("error executing cmd %s: %v",
+			strings.Join(cmd, " "), err)
+		st = 1
+	}
+	return st
+}
+
+// gen is the main hook for kicking off code generation. For
+// non-minimization runs, 'singlepk' and 'singlefn' will both be -1
+// (indicating that we want all functions and packages to be
+// generated).  If 'singlepk' is set to a non-negative value, then
+// code generation will be restricted to the single package with that
+// index (as a try at minimization), similarly with 'singlefn'
+// restricting the codegen to a single specified function.
+func (c *config) gen(singlepk int, singlefn int) {
+
+	// clean the output dir
+	verb(2, "cleaning outdir %s", c.gendir)
+	if err := os.RemoveAll(c.gendir); err != nil {
+		fatal("error cleaning gen dir %s: %v", c.gendir, err)
+	}
+
+	// emit code into the output dir. Here we either invoke the
+	// generator directly, or invoke fuzz-driver if -execflag is
+	// set.  If the code generation process itself fails, this is
+	// typically a bug in the fuzzer itself, so it gets reported
+	// as a fatal error.
+	if *execflag {
+		args := []string{"fuzz-driver",
+			"-numpkgs", strconv.Itoa(c.NumTestPackages),
+			"-numfcns", strconv.Itoa(c.NumTestFunctions),
+			"-seed", strconv.Itoa(int(c.Seed)),
+			"-outdir", c.OutDir,
+			"-pkgpath", pkName,
+			"-maxfail", strconv.Itoa(c.MaxFail)}
+		if singlepk != -1 {
+			args = append(args, "-pkgmask", strconv.Itoa(singlepk))
+		}
+		if singlefn != -1 {
+			args = append(args, "-fcnmask", strconv.Itoa(singlefn))
+		}
+		if *emitbadflag != 0 {
+			args = append(args, "-emitbad", strconv.Itoa(*emitbadflag),
+				"-badpkgidx", strconv.Itoa(*selbadpkgflag),
+				"-badfcnidx", strconv.Itoa(*selbadfcnflag))
+		}
+		verb(1, "invoking fuzz-driver with args: %v", args)
+		st := docmd(args, "")
+		if st != 0 {
+			fatal("fatal error: generation failed, cmd was: %v", args)
+		}
+	} else {
+		if singlepk != -1 {
+			c.PkgMask = map[int]int{singlepk: 1}
+		}
+		if singlefn != -1 {
+			c.FcnMask = map[int]int{singlefn: 1}
+		}
+		verb(1, "invoking generator.Generate with config: %v", c.GenConfig)
+		errs := generator.Generate(c.GenConfig)
+		if errs != 0 {
+			log.Fatal("errors during generation")
+		}
+	}
+}
+
+// action performs a selected action/command in the generated code dir.
+func (c *config) action(cmd []string, outfile string, emitout bool) int {
+	st := docmdout(cmd, c.gendir, outfile)
+	if emitout {
+		content, err := ioutil.ReadFile(outfile)
+		if err != nil {
+			log.Fatal(err)
+		}
+		fmt.Fprintf(os.Stderr, "%s", content)
+	}
+	return st
+}
+
+func binaryName() string {
+	if runtime.GOOS == "windows" {
+		return pkName + ".exe"
+	} else {
+		return "./" + pkName
+	}
+}
+
+// build builds a generated corpus of Go code. If 'emitout' is set, then dump out the
+// results of the build after it completes (during minimization emitout is set to false,
+// since there is no need to see repeated errors).
+func (c *config) build(emitout bool) int {
+	// Issue a build of the generated code.
+	c.buildOutFile = filepath.Join(c.tmpdir, "build.err.txt")
+	cmd := []string{"go", "build", "-o", binaryName()}
+	if c.gcflags != "" {
+		cmd = append(cmd, "-gcflags=all="+c.gcflags)
+	}
+	if *raceflag {
+		cmd = append(cmd, "-race")
+	}
+	cmd = append(cmd, ".")
+	verb(1, "build command is: %v", cmd)
+	return c.action(cmd, c.buildOutFile, emitout)
+}
+
+// run invokes a binary built from a generated corpus of Go code. If
+// 'emitout' is set, then dump out the results of the run after it
+// completes.
+func (c *config) run(emitout bool) int {
+	// Issue a run of the generated code.
+	c.runOutFile = filepath.Join(c.tmpdir, "run.err.txt")
+	cmd := []string{filepath.Join(c.gendir, binaryName())}
+	verb(1, "run command is: %v", cmd)
+	return c.action(cmd, c.runOutFile, emitout)
+}
+
+type minimizeMode int
+
+const (
+	minimizeBuildFailure = iota
+	minimizeRuntimeFailure
+)
+
+// minimize tries to minimize a failing scenario down to a single
+// package and/or function if possible. This is done using an
+// iterative search. Here 'minimizeMode' tells us whether we're
+// looking for a compile-time error or a runtime error.
+func (c *config) minimize(mode minimizeMode) int {
+
+	verb(0, "... starting minimization for failed directory %s", c.gendir)
+
+	foundPkg := -1
+	foundFcn := -1
+
+	// Locate bad package. Uses brute-force linear search, could do better...
+	for pidx := 0; pidx < c.NumTestPackages; pidx++ {
+		verb(1, "minimization: trying package %d", pidx)
+		c.gen(pidx, -1)
+		st := c.build(false)
+		if mode == minimizeBuildFailure {
+			if st != 0 {
+				// Found.
+				foundPkg = pidx
+				c.nerrors++
+				break
+			}
+		} else {
+			if st != 0 {
+				warn("run minimization: unexpected build failed while searching for bad pkg")
+				return 1
+			}
+			st := c.run(false)
+			if st != 0 {
+				// Found.
+				c.nerrors++
+				verb(1, "run minimization found bad package: %d", pidx)
+				foundPkg = pidx
+				break
+			}
+		}
+	}
+	if foundPkg == -1 {
+		verb(0, "** minimization failed, could not locate bad package")
+		return 1
+	}
+	warn("package minimization succeeded: found bad pkg %d", foundPkg)
+
+	// clean unused packages
+	for pidx := 0; pidx < c.NumTestPackages; pidx++ {
+		if pidx != foundPkg {
+			chp := filepath.Join(c.gendir, fmt.Sprintf("%s%s%d", c.Tag, generator.CheckerName, pidx))
+			if err := os.RemoveAll(chp); err != nil {
+				fatal("failed to clean pkg subdir %s: %v", chp, err)
+			}
+			clp := filepath.Join(c.gendir, fmt.Sprintf("%s%s%d", c.Tag, generator.CallerName, pidx))
+			if err := os.RemoveAll(clp); err != nil {
+				fatal("failed to clean pkg subdir %s: %v", clp, err)
+			}
+		}
+	}
+
+	// Locate bad function. Again, brute force.
+	for fidx := 0; fidx < c.NumTestFunctions; fidx++ {
+		c.gen(foundPkg, fidx)
+		st := c.build(false)
+		if mode == minimizeBuildFailure {
+			if st != 0 {
+				// Found.
+				verb(1, "build minimization found bad function: %d", fidx)
+				foundFcn = fidx
+				break
+			}
+		} else {
+			if st != 0 {
+				warn("run minimization: unexpected build failed while searching for bad fcn")
+				return 1
+			}
+			st := c.run(false)
+			if st != 0 {
+				// Found.
+				verb(1, "run minimization found bad function: %d", fidx)
+				foundFcn = fidx
+				break
+			}
+		}
+		// not the function we want ... continue the hunt
+	}
+	if foundFcn == -1 {
+		verb(0, "** function minimization failed, could not locate bad function")
+		return 1
+	}
+	warn("function minimization succeeded: found bad fcn %d", foundFcn)
+
+	return 0
+}
+
+// cleanTemp removes the temp dir we've been working with.
+func (c *config) cleanTemp() {
+	if !*forcetmpcleanflag {
+		if c.nerrors != 0 {
+			verb(1, "preserving temp dir %s", c.tmpdir)
+			return
+		}
+	}
+	verb(1, "cleaning temp dir %s", c.tmpdir)
+	os.RemoveAll(c.tmpdir)
+}
+
+// perform is the top level driver routine for the program, containing the
+// main loop. Each iteration of the loop performs a generate/build/run
+// sequence, and then updates the seed afterwards if no failure is found.
+// If a failure is detected, we try to minimize it and then return without
+// attempting any additional tests.
+func (c *config) perform() int {
+	defer c.cleanTemp()
+
+	// Main loop
+	for iter := 0; iter < *loopitflag; iter++ {
+		if iter != 0 && iter%50 == 0 {
+			// Note: cleaning the Go cache periodically is
+			// pretty much a requirement if you want to do
+			// things like overnight runs of the fuzzer,
+			// but it is also a very unfriendly thing do
+			// to if we're executing as part of a unit
+			// test run (in which case there may be other
+			// tests running in parallel with this
+			// one). Check the "cleancache" flag before
+			// doing this.
+			if *cleancacheflag {
+				docmd([]string{"go", "clean", "-cache"}, "")
+			}
+		}
+		verb(0, "... begin iteration %d with current seed %d", iter, c.Seed)
+		c.gen(-1, -1)
+		st := c.build(true)
+		if st != 0 {
+			c.minimize(minimizeBuildFailure)
+			return 1
+		}
+		st = c.run(true)
+		if st != 0 {
+			c.minimize(minimizeRuntimeFailure)
+			return 1
+		}
+		// update seed so that we get different code on the next iter.
+		c.Seed += 101
+	}
+	return 0
+}
+
+func main() {
+	log.SetFlags(0)
+	log.SetPrefix("fuzz-runner: ")
+	flag.Parse()
+	if flag.NArg() != 0 {
+		usage("unknown extra arguments")
+	}
+	verb(1, "in main, verblevel=%d", *verbflag)
+
+	tmpdir, err := ioutil.TempDir("", "fuzzrun")
+	if err != nil {
+		fatal("creation of tempdir failed: %v", err)
+	}
+	gendir := filepath.Join(tmpdir, "fuzzTest")
+
+	// select starting seed
+	if *seedflag == -1 {
+		now := time.Now()
+		*seedflag = now.UnixNano() % 123456789
+	}
+
+	// set up params for this run
+	c := &config{
+		GenConfig: generator.GenConfig{
+			NumTestPackages:  *numpkgsflag, // 100
+			NumTestFunctions: *numfcnsflag, // 20
+			Seed:             *seedflag,
+			OutDir:           gendir,
+			Pragma:           "-maxfail=9999",
+			PkgPath:          pkName,
+			EmitBad:          *emitbadflag,
+			BadPackageIdx:    *selbadpkgflag,
+			BadFuncIdx:       *selbadfcnflag,
+		},
+		tmpdir: tmpdir,
+		gendir: gendir,
+	}
+
+	// kick off the main loop.
+	st := c.perform()
+
+	// done
+	verb(1, "leaving main, num errors=%d", c.nerrors)
+	os.Exit(st)
+}
diff --git a/cmd/signature-fuzzer/fuzz-runner/testdata/himom.go b/cmd/signature-fuzzer/fuzz-runner/testdata/himom.go
new file mode 100644
index 0000000..5ba783d
--- /dev/null
+++ b/cmd/signature-fuzzer/fuzz-runner/testdata/himom.go
@@ -0,0 +1,9 @@
+// Copyright 2021 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 main
+
+func main() {
+	println("hi mom!")
+}
diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/arrayparm.go b/cmd/signature-fuzzer/internal/fuzz-generator/arrayparm.go
new file mode 100644
index 0000000..32ccf7e
--- /dev/null
+++ b/cmd/signature-fuzzer/internal/fuzz-generator/arrayparm.go
@@ -0,0 +1,108 @@
+// Copyright 2021 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 generator
+
+import (
+	"bytes"
+	"fmt"
+)
+
+// arrayparm describes a parameter of array type; it implements the
+// "parm" interface.
+type arrayparm struct {
+	aname     string
+	qname     string
+	nelements uint8
+	eltype    parm
+	slice     bool
+	isBlank
+	addrTakenHow
+	isGenValFunc
+	skipCompare
+}
+
+func (p arrayparm) IsControl() bool {
+	return false
+}
+
+func (p arrayparm) TypeName() string {
+	return p.aname
+}
+
+func (p arrayparm) QualName() string {
+	return p.qname
+}
+
+func (p arrayparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) {
+	n := p.aname
+	if caller {
+		n = p.qname
+	}
+	b.WriteString(fmt.Sprintf("%s %s%s", prefix, n, suffix))
+}
+
+func (p arrayparm) String() string {
+	return fmt.Sprintf("%s %d-element array of %s", p.aname, p.nelements, p.eltype.String())
+}
+
+func (p arrayparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) {
+	var buf bytes.Buffer
+
+	verb(5, "arrayparm.GenValue(%d)", value)
+
+	n := p.aname
+	if caller {
+		n = p.qname
+	}
+	buf.WriteString(fmt.Sprintf("%s{", n))
+	for i := 0; i < int(p.nelements); i++ {
+		var valstr string
+		valstr, value = s.GenValue(f, p.eltype, value, caller)
+		writeCom(&buf, i)
+		buf.WriteString(valstr)
+	}
+	buf.WriteString("}")
+	return buf.String(), value
+}
+
+func (p arrayparm) GenElemRef(elidx int, path string) (string, parm) {
+	ene := p.eltype.NumElements()
+	verb(4, "begin GenElemRef(%d,%s) on %s ene %d", elidx, path, p.String(), ene)
+
+	// For empty arrays, convention is to return empty string
+	if ene == 0 {
+		return "", &p
+	}
+
+	// Find slot within array of element of interest
+	slot := elidx / ene
+
+	// If this is the element we're interested in, return it
+	if ene == 1 {
+		verb(4, "hit scalar element")
+		epath := fmt.Sprintf("%s[%d]", path, slot)
+		if path == "_" || p.IsBlank() {
+			epath = "_"
+		}
+		return epath, p.eltype
+	}
+
+	verb(4, "recur slot=%d GenElemRef(%d,...)", slot, elidx-(slot*ene))
+
+	// Otherwise our victim is somewhere inside the slot
+	ppath := fmt.Sprintf("%s[%d]", path, slot)
+	if p.IsBlank() {
+		ppath = "_"
+	}
+	return p.eltype.GenElemRef(elidx-(slot*ene), ppath)
+}
+
+func (p arrayparm) NumElements() int {
+	return p.eltype.NumElements() * int(p.nelements)
+}
+
+func (p arrayparm) HasPointer() bool {
+	return p.eltype.HasPointer() || p.slice
+}
diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/gen_test.go b/cmd/signature-fuzzer/internal/fuzz-generator/gen_test.go
new file mode 100644
index 0000000..4bd5bab
--- /dev/null
+++ b/cmd/signature-fuzzer/internal/fuzz-generator/gen_test.go
@@ -0,0 +1,322 @@
+// Copyright 2021 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 generator
+
+import (
+	"bytes"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"runtime"
+	"testing"
+
+	"golang.org/x/tools/internal/testenv"
+)
+
+func mkGenState() *genstate {
+
+	return &genstate{
+		GenConfig: GenConfig{
+			Tag:              "gen",
+			OutDir:           "/tmp",
+			NumTestPackages:  1,
+			NumTestFunctions: 10,
+		},
+		ipref:       "foo/",
+		derefFuncs:  make(map[string]string),
+		assignFuncs: make(map[string]string),
+		allocFuncs:  make(map[string]string),
+		globVars:    make(map[string]string),
+	}
+}
+
+func TestBasic(t *testing.T) {
+	checkTunables(tunables)
+	s := mkGenState()
+	for i := 0; i < 1000; i++ {
+		s.wr = NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic)
+		fp := s.GenFunc(i, i)
+		var buf bytes.Buffer
+		var b *bytes.Buffer = &buf
+		wr := NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic)
+		s.wr = wr
+		s.emitCaller(fp, b, i)
+		s.wr = NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic)
+		s.emitChecker(fp, b, i, true)
+		wr.Check(s.wr)
+	}
+	if s.errs != 0 {
+		t.Errorf("%d errors during Generate", s.errs)
+	}
+}
+
+func TestMoreComplicated(t *testing.T) {
+	saveit := tunables
+	defer func() { tunables = saveit }()
+
+	checkTunables(tunables)
+	s := mkGenState()
+	for i := 0; i < 10000; i++ {
+		s.wr = NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic)
+		fp := s.GenFunc(i, i)
+		var buf bytes.Buffer
+		var b *bytes.Buffer = &buf
+		wr := NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic)
+		s.wr = wr
+		s.emitCaller(fp, b, i)
+		verb(1, "finished iter %d caller", i)
+		s.wr = NewWrapRand(int64(i), RandCtlChecks|RandCtlPanic)
+		s.emitChecker(fp, b, i, true)
+		verb(1, "finished iter %d checker", i)
+		wr.Check(s.wr)
+		if s.errs != 0 {
+			t.Errorf("%d errors during Generate iter %d", s.errs, i)
+		}
+	}
+}
+
+func TestIsBuildable(t *testing.T) {
+	testenv.NeedsTool(t, "go")
+	if runtime.GOOS == "android" {
+		t.Skipf("the dependencies are not available on android")
+	}
+
+	td := t.TempDir()
+	verb(1, "generating into temp dir %s", td)
+	checkTunables(tunables)
+	pack := filepath.Base(td)
+	s := GenConfig{
+		Tag:              "x",
+		OutDir:           td,
+		PkgPath:          pack,
+		NumTestFunctions: 10,
+		NumTestPackages:  10,
+		MaxFail:          10,
+		RandCtl:          RandCtlChecks | RandCtlPanic,
+	}
+	errs := Generate(s)
+	if errs != 0 {
+		t.Errorf("%d errors during Generate", errs)
+	}
+
+	verb(1, "building %s\n", td)
+
+	cmd := exec.Command("go", "run", ".")
+	cmd.Dir = td
+	coutput, cerr := cmd.CombinedOutput()
+	if cerr != nil {
+		t.Errorf("go build command failed: %s\n", string(coutput))
+	}
+	verb(1, "output is: %s\n", string(coutput))
+}
+
+// TestExhaustive does a series of code genreation runs, starting with
+// (relatively) simple code and then getting progressively more
+// complex (more params, deeper structs, turning on additional
+// features such as address-taken vars and reflect testing). The
+// intent here is mainly to insure that the tester still works if you
+// turn things on and off, e.g. that each feature is separately
+// controllable and not linked to other things.
+func TestExhaustive(t *testing.T) {
+	testenv.NeedsTool(t, "go")
+	if runtime.GOOS == "android" {
+		t.Skipf("the dependencies are not available on android")
+	}
+
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+
+	td := t.TempDir()
+	verb(1, "generating into temp dir %s", td)
+
+	scenarios := []struct {
+		name     string
+		adjuster func()
+	}{
+		{
+			"minimal",
+			func() {
+				tunables.nParmRange = 3
+				tunables.nReturnRange = 3
+				tunables.structDepth = 1
+				tunables.recurPerc = 0
+				tunables.methodPerc = 0
+				tunables.doReflectCall = false
+				tunables.doDefer = false
+				tunables.takeAddress = false
+				tunables.doFuncCallValues = false
+				tunables.doSkipCompare = false
+				checkTunables(tunables)
+			},
+		},
+		{
+			"moreparms",
+			func() {
+				tunables.nParmRange = 15
+				tunables.nReturnRange = 7
+				tunables.structDepth = 3
+				checkTunables(tunables)
+			},
+		},
+		{
+			"addrecur",
+			func() {
+				tunables.recurPerc = 20
+				checkTunables(tunables)
+			},
+		},
+		{
+			"addmethod",
+			func() {
+				tunables.methodPerc = 25
+				tunables.pointerMethodCallPerc = 30
+				checkTunables(tunables)
+			},
+		},
+		{
+			"addtakeaddr",
+			func() {
+				tunables.takeAddress = true
+				tunables.takenFraction = 20
+				checkTunables(tunables)
+			},
+		},
+		{
+			"addreflect",
+			func() {
+				tunables.doReflectCall = true
+				checkTunables(tunables)
+			},
+		},
+		{
+			"adddefer",
+			func() {
+				tunables.doDefer = true
+				checkTunables(tunables)
+			},
+		},
+		{
+			"addfuncval",
+			func() {
+				tunables.doFuncCallValues = true
+				checkTunables(tunables)
+			},
+		},
+		{
+			"addfuncval",
+			func() {
+				tunables.doSkipCompare = true
+				checkTunables(tunables)
+			},
+		},
+	}
+
+	// Loop over scenarios and make sure each one works properly.
+	for i, s := range scenarios {
+		t.Logf("running %s\n", s.name)
+		s.adjuster()
+		os.RemoveAll(td)
+		pack := filepath.Base(td)
+		c := GenConfig{
+			Tag:              "x",
+			OutDir:           td,
+			PkgPath:          pack,
+			NumTestFunctions: 10,
+			NumTestPackages:  10,
+			Seed:             int64(i + 9),
+			MaxFail:          10,
+			RandCtl:          RandCtlChecks | RandCtlPanic,
+		}
+		errs := Generate(c)
+		if errs != 0 {
+			t.Errorf("%d errors during scenarios %q Generate", errs, s.name)
+		}
+		cmd := exec.Command("go", "run", ".")
+		cmd.Dir = td
+		coutput, cerr := cmd.CombinedOutput()
+		if cerr != nil {
+			t.Fatalf("run failed for scenario %q:  %s\n", s.name, string(coutput))
+		}
+		verb(1, "output is: %s\n", string(coutput))
+	}
+}
+
+func TestEmitBadBuildFailure(t *testing.T) {
+	testenv.NeedsTool(t, "go")
+	if runtime.GOOS == "android" {
+		t.Skipf("the dependencies are not available on android")
+	}
+
+	td := t.TempDir()
+	verb(1, "generating into temp dir %s", td)
+
+	checkTunables(tunables)
+	pack := filepath.Base(td)
+	s := GenConfig{
+		Tag:              "x",
+		OutDir:           td,
+		PkgPath:          pack,
+		NumTestFunctions: 10,
+		NumTestPackages:  10,
+		MaxFail:          10,
+		RandCtl:          RandCtlChecks | RandCtlPanic,
+		EmitBad:          1,
+	}
+	errs := Generate(s)
+	if errs != 0 {
+		t.Errorf("%d errors during Generate", errs)
+	}
+
+	cmd := exec.Command("go", "build", ".")
+	cmd.Dir = td
+	coutput, cerr := cmd.CombinedOutput()
+	if cerr == nil {
+		t.Errorf("go build command passed, expected failure. output: %s\n", string(coutput))
+	}
+}
+
+func TestEmitBadRunFailure(t *testing.T) {
+	testenv.NeedsTool(t, "go")
+	if runtime.GOOS == "android" {
+		t.Skipf("the dependencies are not available on android")
+	}
+
+	td := t.TempDir()
+	verb(1, "generating into temp dir %s", td)
+
+	checkTunables(tunables)
+	pack := filepath.Base(td)
+	s := GenConfig{
+		Tag:              "x",
+		OutDir:           td,
+		PkgPath:          pack,
+		NumTestFunctions: 10,
+		NumTestPackages:  10,
+		MaxFail:          10,
+		RandCtl:          RandCtlChecks | RandCtlPanic,
+		EmitBad:          2,
+	}
+	errs := Generate(s)
+	if errs != 0 {
+		t.Errorf("%d errors during Generate", errs)
+	}
+
+	// build
+	cmd := exec.Command("go", "build", ".")
+	cmd.Dir = td
+	coutput, cerr := cmd.CombinedOutput()
+	if cerr != nil {
+		t.Fatalf("build failed: %s\n", string(coutput))
+	}
+
+	// run
+	cmd = exec.Command("./" + pack)
+	cmd.Dir = td
+	coutput, cerr = cmd.CombinedOutput()
+	if cerr == nil {
+		t.Fatalf("run passed, expected failure -- run output: %s", string(coutput))
+	}
+}
diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/generator.go b/cmd/signature-fuzzer/internal/fuzz-generator/generator.go
new file mode 100644
index 0000000..bbe53fb
--- /dev/null
+++ b/cmd/signature-fuzzer/internal/fuzz-generator/generator.go
@@ -0,0 +1,2269 @@
+// Copyright 2021 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.
+
+// This package generates source code for a stand-alone Go program
+// useful for function signature fuzzing. The generated program is a
+// series of function pairs, a "Caller" function and a "Checker"
+// function. The signature of the Checker function is generated
+// randomly (random number of parameters and returns, each with
+// randomly chosen types). The "Caller" func contains invocations of
+// the "Checker" function, each passing randomly chosen values to the
+// params of the "Checker", then the caller verifies that expected
+// values are returned correctly.  The "Checker" function in turn has
+// code to verify that the expected values arrive correctly, and so
+// on.
+//
+// The main exported items of interest for this package are:
+//
+// - the Generate function, which takes a GenConfig object and emits
+//   code according to the config's specification
+//
+// - the GenConfig struct, which is basically a large collection of
+//   knobs/switches to control the mechanics of how/where code is
+//   generated
+//
+// - the TunableParams struct, which controls the nature of the
+//   generated code (for example, the maximum number of function
+//   parameters, etc), and the SetTunables func which tells the
+//   package what tunable parameters to use.
+
+// Notes for posterity:
+// - many parts of this package would have been better off being written
+//   using text/template instead of generating code directly; perhaps
+//   at some point it could be converted over (big job).
+// - for the various 'fractions' fields in the TunableParams struct,
+//   it would be good to have a named type of some sort, with methods
+//   for managing things like checking to make sure values sum to 100.
+
+package generator
+
+import (
+	"bytes"
+	"crypto/sha1"
+	"errors"
+	"fmt"
+	"html/template"
+	"log"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strconv"
+	"strings"
+)
+
+// GenConfig contains configuration parameters relating to the
+// mechanics of the code generation, e.g. how many packages/functions
+// to emit, path to a directory into which we place the generated
+// code, prefixes/packagenames for the generate code, and so on.
+type GenConfig struct {
+	// Tag is a string prefix prepended to functions within
+	// the generated code.
+	Tag string
+
+	// Output directory in to which we'll emit generated code.
+	// This will be created if it does not exist.
+	OutDir string
+
+	// Packagepath prefix given to the generated code.
+	PkgPath string
+
+	// Number of test packages created within the generated corpus.
+	// Each test package is essentially an independent collection
+	// generated code; the point of having multiple packages is to
+	// be able to get faster builds (more parallelism), and to avoid
+	// the compile time issues that crop up with 'giant' packages.
+	NumTestPackages int
+
+	// Number of test function pairs within each generated test package.
+	// Each pair consists of a "caller" function and  "callee" function.
+	NumTestFunctions int
+
+	// Seed for random number generator.
+	Seed int64
+
+	// Pragma is a "// go:..." compiler directive to apply to the
+	// callee function as part of a generated function pair.
+	Pragma string
+
+	// Function and package mask used for minimization purposes.
+	// If a given mask is non-nil, then the generator will only
+	// emit code for a given func or package if its index is
+	// present in the mask map.
+	FcnMask map[int]int
+	PkgMask map[int]int
+
+	// Maximum number of failures to encounter before bailing out.
+	MaxFail int
+
+	// forcestackgrowth if set tells the generator to insert
+	// calls to runtime.gcTestMoveStackOnNextCall at various points
+	// in the generated code.
+	ForceStackGrowth bool
+
+	// Random number generator control flag (debugging)
+	RandCtl int
+
+	// Tells the generator to run "goimports" on the emitted code.
+	RunGoImports bool
+
+	// Debugging/testing hook. If set to 1, emit code that will cause the
+	// build to fail; if set to 2, emit code that will cause a test to fail.
+	EmitBad int
+
+	// If EmitBad above is set, then these can be used to select the ID of
+	// a specific bad func/package.
+	BadPackageIdx int
+	BadFuncIdx    int
+}
+
+const CallerName = "Caller"
+const CheckerName = "Checker"
+
+// TunableParams contains configuration parameters that control the
+// flavor of code generated for a given test function. This includes
+// things like the number of params/returns, the percentages of types
+// (int, struct, etc) of the params/returns, and so on.
+type TunableParams struct {
+	// between 0 and N params
+	nParmRange uint8
+
+	// between 0 and N returns
+	nReturnRange uint8
+
+	// structs have between 0 and N members
+	nStructFields uint8
+
+	// arrays/slices have between 0 and N elements
+	nArrayElements uint8
+
+	// fraction of slices vs arrays. This is a value between 0 and 100 (0 meaning
+	// no slices [only arrays] and 100 meaning all slices, no arrays).
+	sliceFraction uint8
+
+	// Controls how often "int" vars wind up as 8/16/32/64, should
+	// add up to 100. Ex: 100 0 0 0 means all ints are 8 bit, 25
+	// 25 25 25 means equal likelihood of all types.
+	intBitRanges [4]uint8
+
+	// Similar to the above but for 32/64 float types
+	floatBitRanges [2]uint8
+
+	// Similar to the above but for unsigned, signed ints.
+	unsignedRanges [2]uint8
+
+	// Percentage of params, struct fields that should be "_". Ranges
+	// from 0 to 100.
+	blankPerc uint8
+
+	// How deeply structs are allowed to be nested (ranges from 0 to N).
+	structDepth uint8
+
+	// Fraction of param and return types assigned to each of:
+	// struct/array/map/pointer/int/float/complex/byte/string at the
+	// top level. If nesting precludes using a struct, other types
+	// are chosen from instead according to same proportions. The sum
+	// of typeFractions values should add up to 100.
+	typeFractions [9]uint8
+
+	// Percentage of the time we'll emit recursive calls, from 0 to 100.
+	recurPerc uint8
+
+	// Percentage of time that we turn the test function into a method,
+	// and if it is a method, fraction of time that we use a pointer
+	// method call vs value method call. Each range from 0 to 100.
+	methodPerc            uint8
+	pointerMethodCallPerc uint8
+
+	// If true, test reflect.Call path as well.
+	doReflectCall bool
+
+	// If true, then randomly take addresses of params/returns.
+	takeAddress bool
+
+	// Fraction of the time that any params/returns are address taken.
+	// Ranges from 0 to 100.
+	takenFraction uint8
+
+	// For a given address-taken param or return, controls the
+	// manner in which the indirect read or write takes
+	// place. This is a set of percentages for
+	// not/simple/passed/heap, where "not" means not address
+	// taken, "simple" means a simple read or write, "passed"
+	// means that the address is passed to a well-behaved
+	// function, and "heap" means that the address is assigned to
+	// a global. Values in addrFractions should add up to 100.
+	addrFractions [4]uint8
+
+	// If true, then perform testing of go/defer statements.
+	doDefer bool
+
+	// fraction of test functions for which we emit a defer. Ranges from 0 to 100.
+	deferFraction uint8
+
+	// If true, randomly pick between emitting a value by literal
+	// (e.g. "int(1)" vs emitting a call to a function that
+	// will produce the same value (e.g. "myHelperEmitsInt1()").
+	doFuncCallValues bool
+
+	// Fraction of the time that we emit a function call to create
+	// a param value vs emitting a literal. Ranges from 0 to 100.
+	funcCallValFraction uint8
+
+	// If true, randomly decide to not check selected components of
+	// a composite value (e.g. for a struct, check field F1 but not F2).
+	// The intent is to generate partially live values.
+	doSkipCompare bool
+
+	// Fraction of the time that we decided to skip sub-components of
+	// composite values. Ranges from 0 to 100.
+	skipCompareFraction uint8
+}
+
+// SetTunables accepts a TunableParams object, checks to make sure
+// that the settings in it are sane/logical, and applies the
+// parameters for any subsequent calls to the Generate function. This
+// function will issue a fatal error if any of the tunable params are
+// incorrect/insane (for example, a 'percentage' value outside the
+// range of 0-100).
+func SetTunables(t TunableParams) {
+	checkTunables(t)
+	tunables = t
+}
+
+var defaultTypeFractions = [9]uint8{
+	10, // struct
+	10, // array
+	10, // map
+	15, // pointer
+	20, // numeric
+	15, // float
+	5,  // complex
+	5,  // byte
+	10, // string
+}
+
+const (
+	// Param not address taken.
+	StructTfIdx = iota
+	ArrayTfIdx
+	MapTfIdx
+	PointerTfIdx
+	NumericTfIdx
+	FloatTfIdx
+	ComplexTfIdx
+	ByteTfIdx
+	StringTfIdx
+)
+
+var tunables = TunableParams{
+	nParmRange:            15,
+	nReturnRange:          7,
+	nStructFields:         7,
+	nArrayElements:        5,
+	sliceFraction:         50,
+	intBitRanges:          [4]uint8{30, 20, 20, 30},
+	floatBitRanges:        [2]uint8{50, 50},
+	unsignedRanges:        [2]uint8{50, 50},
+	blankPerc:             15,
+	structDepth:           3,
+	typeFractions:         defaultTypeFractions,
+	recurPerc:             20,
+	methodPerc:            10,
+	pointerMethodCallPerc: 50,
+	doReflectCall:         true,
+	doDefer:               true,
+	takeAddress:           true,
+	doFuncCallValues:      true,
+	takenFraction:         20,
+	deferFraction:         30,
+	funcCallValFraction:   5,
+	doSkipCompare:         true,
+	skipCompareFraction:   10,
+	addrFractions:         [4]uint8{50, 25, 15, 10},
+}
+
+func DefaultTunables() TunableParams {
+	return tunables
+}
+
+func checkTunables(t TunableParams) {
+	var s int = 0
+
+	for _, v := range t.intBitRanges {
+		s += int(v)
+	}
+	if s != 100 {
+		log.Fatal(errors.New("intBitRanges tunable does not sum to 100"))
+	}
+
+	s = 0
+	for _, v := range t.unsignedRanges {
+		s += int(v)
+	}
+	if s != 100 {
+		log.Fatal(errors.New("unsignedRanges tunable does not sum to 100"))
+	}
+
+	if t.blankPerc > 100 {
+		log.Fatal(errors.New("blankPerc bad value, over 100"))
+	}
+	if t.recurPerc > 100 {
+		log.Fatal(errors.New("recurPerc bad value, over 100"))
+	}
+	if t.methodPerc > 100 {
+		log.Fatal(errors.New("methodPerc bad value, over 100"))
+	}
+	if t.pointerMethodCallPerc > 100 {
+		log.Fatal(errors.New("pointerMethodCallPerc bad value, over 100"))
+	}
+
+	s = 0
+	for _, v := range t.floatBitRanges {
+		s += int(v)
+	}
+	if s != 100 {
+		log.Fatal(errors.New("floatBitRanges tunable does not sum to 100"))
+	}
+
+	s = 0
+	for _, v := range t.typeFractions {
+		s += int(v)
+	}
+	if s != 100 {
+		panic(errors.New("typeFractions tunable does not sum to 100"))
+	}
+
+	s = 0
+	for _, v := range t.addrFractions {
+		s += int(v)
+	}
+	if s != 100 {
+		log.Fatal(errors.New("addrFractions tunable does not sum to 100"))
+	}
+	if t.takenFraction > 100 {
+		log.Fatal(errors.New("takenFraction not between 0 and 100"))
+	}
+	if t.deferFraction > 100 {
+		log.Fatal(errors.New("deferFraction not between 0 and 100"))
+	}
+	if t.sliceFraction > 100 {
+		log.Fatal(errors.New("sliceFraction not between 0 and 100"))
+	}
+	if t.skipCompareFraction > 100 {
+		log.Fatal(errors.New("skipCompareFraction not between 0 and 100"))
+	}
+}
+
+func (t *TunableParams) DisableReflectionCalls() {
+	t.doReflectCall = false
+}
+
+func (t *TunableParams) DisableRecursiveCalls() {
+	t.recurPerc = 0
+}
+
+func (t *TunableParams) DisableMethodCalls() {
+	t.methodPerc = 0
+}
+
+func (t *TunableParams) DisableTakeAddr() {
+	t.takeAddress = false
+}
+
+func (t *TunableParams) DisableDefer() {
+	t.doDefer = false
+}
+
+func (t *TunableParams) LimitInputs(n int) error {
+	if n > 100 {
+		return fmt.Errorf("value %d passed to LimitInputs is too large *(max 100)", n)
+	}
+	if n < 0 {
+		return fmt.Errorf("value %d passed to LimitInputs is invalid", n)
+	}
+	t.nParmRange = uint8(n)
+	return nil
+}
+
+func (t *TunableParams) LimitOutputs(n int) error {
+	if n > 100 {
+		return fmt.Errorf("value %d passed to LimitOutputs is too large *(max 100)", n)
+	}
+	if n < 0 {
+		return fmt.Errorf("value %d passed to LimitOutputs is invalid", n)
+	}
+	t.nReturnRange = uint8(n)
+	return nil
+}
+
+// ParseMaskString parses a string of the form K,J,...,M-N,Q-R,...,Z
+// e.g. comma-separated integers or ranges of integers, returning the
+// result in a form suitable for FcnMask or PkgMask fields in a
+// Config. Here "tag" holds the mask flavor (fcn or pkg) and "arg" is
+// the string argument to be parsed.
+func ParseMaskString(arg string, tag string) (map[int]int, error) {
+	if arg == "" {
+		return nil, nil
+	}
+	verb(1, "%s mask is %s", tag, arg)
+	m := make(map[int]int)
+	ss := strings.Split(arg, ":")
+	for _, s := range ss {
+		if strings.Contains(s, "-") {
+			rng := strings.Split(s, "-")
+			if len(rng) != 2 {
+				return nil, fmt.Errorf("malformed range %s in %s mask arg", s, tag)
+			}
+			i, err := strconv.Atoi(rng[0])
+			if err != nil {
+				return nil, fmt.Errorf("malformed range value %s in %s mask arg", rng[0], tag)
+			}
+			j, err2 := strconv.Atoi(rng[1])
+			if err2 != nil {
+				return nil, fmt.Errorf("malformed range value %s in %s mask arg", rng[1], tag)
+			}
+			for k := i; k < j; k++ {
+				m[k] = 1
+			}
+		} else {
+			i, err := strconv.Atoi(s)
+			if err != nil {
+				return nil, fmt.Errorf("malformed value %s in %s mask arg", s, tag)
+			}
+			m[i] = 1
+		}
+	}
+	return m, nil
+}
+
+func writeCom(b *bytes.Buffer, i int) {
+	if i != 0 {
+		b.WriteString(", ")
+	}
+}
+
+var Verbctl int = 0
+
+func verb(vlevel int, s string, a ...interface{}) {
+	if Verbctl >= vlevel {
+		fmt.Printf(s, a...)
+		fmt.Printf("\n")
+	}
+}
+
+type funcdef struct {
+	idx         int
+	structdefs  []structparm
+	arraydefs   []arrayparm
+	typedefs    []typedefparm
+	mapdefs     []mapparm
+	mapkeytypes []parm
+	mapkeytmps  []string
+	mapkeyts    string
+	receiver    parm
+	params      []parm
+	returns     []parm
+	values      []int
+	dodefc      uint8
+	dodefp      []uint8
+	rstack      int
+	recur       bool
+	isMethod    bool
+}
+
+type genstate struct {
+	GenConfig
+	ipref string
+	//tag            string
+	//numtpk         int
+	pkidx int
+	errs  int
+	//pragma         string
+	//sforce         bool
+	//randctl        int
+	tunables       TunableParams
+	tstack         []TunableParams
+	derefFuncs     map[string]string
+	newDerefFuncs  []funcdesc
+	assignFuncs    map[string]string
+	newAssignFuncs []funcdesc
+	allocFuncs     map[string]string
+	newAllocFuncs  []funcdesc
+	genvalFuncs    map[string]string
+	newGenvalFuncs []funcdesc
+	globVars       map[string]string
+	newGlobVars    []funcdesc
+	wr             *wraprand
+}
+
+func (s *genstate) intFlavor() string {
+	which := uint8(s.wr.Intn(100))
+	if which < s.tunables.unsignedRanges[0] {
+		return "uint"
+	}
+	return "int"
+}
+
+func (s *genstate) intBits() uint32 {
+	which := uint8(s.wr.Intn(100))
+	var t uint8 = 0
+	var bits uint32 = 8
+	for _, v := range s.tunables.intBitRanges {
+		t += v
+		if which < t {
+			return bits
+		}
+		bits *= 2
+	}
+	return uint32(s.tunables.intBitRanges[3])
+}
+
+func (s *genstate) floatBits() uint32 {
+	which := uint8(s.wr.Intn(100))
+	if which < s.tunables.floatBitRanges[0] {
+		return uint32(32)
+	}
+	return uint32(64)
+}
+
+func (s *genstate) genAddrTaken() addrTakenHow {
+	which := uint8(s.wr.Intn(100))
+	res := notAddrTaken
+	var t uint8 = 0
+	for _, v := range s.tunables.addrFractions {
+		t += v
+		if which < t {
+			return res
+		}
+		res++
+	}
+	return notAddrTaken
+}
+
+func (s *genstate) pushTunables() {
+	s.tstack = append(s.tstack, s.tunables)
+}
+
+func (s *genstate) popTunables() {
+	if len(s.tstack) == 0 {
+		panic("untables stack underflow")
+	}
+	s.tunables = s.tstack[0]
+	s.tstack = s.tstack[1:]
+}
+
+// redistributeFraction accepts a value 'toIncorporate' and updates
+// 'typeFraction' to add in the values from 'toIncorporate' equally to
+// all slots not in 'avoid'. This is done by successively walking
+// through 'typeFraction' adding 1 to each non-avoid slot, then
+// repeating until we've added a total of 'toIncorporate' elements.
+// See precludeSelectedTypes below for more info.
+func (s *genstate) redistributeFraction(toIncorporate uint8, avoid []int) {
+	inavoid := func(j int) bool {
+		for _, k := range avoid {
+			if j == k {
+				return true
+			}
+		}
+		return false
+	}
+
+	doredis := func() {
+		for {
+			for i := range s.tunables.typeFractions {
+				if inavoid(i) {
+					continue
+				}
+				s.tunables.typeFractions[i]++
+				toIncorporate--
+				if toIncorporate == 0 {
+					return
+				}
+			}
+		}
+	}
+	doredis()
+	checkTunables(s.tunables)
+}
+
+// precludeSelectedTypes accepts a set of values (t, t2, ...)
+// corresponding to slots in 'typeFractions', sums up the values from
+// the slots, zeroes out the slots, and finally takes the values and
+// redistributes them equally to the other slots.  For example,
+// suppose 'typeFractions' starts as [10, 10, 10, 15, 20, 15, 5, 5, 10],
+// then we decide we want to eliminate or 'knock out' map types and
+// pointer types (slots 2 and 3 in the array above) going forward.  To
+// restore the invariant that values in 'typeFractions' sum to 100, we
+// take the values from slots 2 and 3 (a total of 25) and evenly
+// distribute those values to the other slots in the array.
+func (s *genstate) precludeSelectedTypes(t int, t2 ...int) {
+	avoid := []int{t}
+	avoid = append(avoid, t2...)
+	f := uint8(0)
+	for _, idx := range avoid {
+		f += s.tunables.typeFractions[idx]
+		s.tunables.typeFractions[idx] = 0
+	}
+	s.redistributeFraction(f, avoid)
+}
+
+func (s *genstate) GenMapKeyType(f *funcdef, depth int, pidx int) parm {
+	s.pushTunables()
+	defer s.popTunables()
+	// maps we can't allow at all; pointers might be possible but
+	//  would be too much work to arrange. Avoid slices as well.
+	s.tunables.sliceFraction = 0
+	s.precludeSelectedTypes(MapTfIdx, PointerTfIdx)
+	return s.GenParm(f, depth+1, false, pidx)
+}
+
+func (s *genstate) GenParm(f *funcdef, depth int, mkctl bool, pidx int) parm {
+
+	// Enforcement for struct/array/map/pointer array nesting depth.
+	toodeep := depth >= int(s.tunables.structDepth)
+	if toodeep {
+		s.pushTunables()
+		defer s.popTunables()
+		s.precludeSelectedTypes(StructTfIdx, ArrayTfIdx, MapTfIdx, PointerTfIdx)
+	}
+
+	// Convert tf into a cumulative sum
+	tf := s.tunables.typeFractions
+	sum := uint8(0)
+	for i := 0; i < len(tf); i++ {
+		sum += tf[i]
+		tf[i] = sum
+	}
+
+	isblank := uint8(s.wr.Intn(100)) < s.tunables.blankPerc
+	addrTaken := notAddrTaken
+	if depth == 0 && tunables.takeAddress && !isblank {
+		addrTaken = s.genAddrTaken()
+	}
+	isGenValFunc := tunables.doFuncCallValues &&
+		uint8(s.wr.Intn(100)) < s.tunables.funcCallValFraction
+
+	// Make adjusted selection (pick a bucket within tf)
+	which := uint8(s.wr.Intn(100))
+	verb(3, "which=%d", which)
+	var retval parm
+	switch {
+	case which < tf[StructTfIdx]:
+		{
+			if toodeep {
+				panic("should not be here")
+			}
+			var sp structparm
+			ns := len(f.structdefs)
+			sp.sname = fmt.Sprintf("StructF%dS%d", f.idx, ns)
+			sp.qname = fmt.Sprintf("%s.StructF%dS%d",
+				s.checkerPkg(pidx), f.idx, ns)
+			f.structdefs = append(f.structdefs, sp)
+			tnf := int64(s.tunables.nStructFields) / int64(depth+1)
+			nf := int(s.wr.Intn(tnf))
+			for fi := 0; fi < nf; fi++ {
+				fp := s.GenParm(f, depth+1, false, pidx)
+				skComp := tunables.doSkipCompare &&
+					uint8(s.wr.Intn(100)) < s.tunables.skipCompareFraction
+				if skComp && checkableElements(fp) != 0 {
+					fp.SetSkipCompare(SkipAll)
+				}
+				sp.fields = append(sp.fields, fp)
+			}
+			f.structdefs[ns] = sp
+			retval = &sp
+		}
+	case which < tf[ArrayTfIdx]:
+		{
+			if toodeep {
+				panic("should not be here")
+			}
+			var ap arrayparm
+			ns := len(f.arraydefs)
+			nel := uint8(s.wr.Intn(int64(s.tunables.nArrayElements)))
+			issl := uint8(s.wr.Intn(100)) < s.tunables.sliceFraction
+			ap.aname = fmt.Sprintf("ArrayF%dS%dE%d", f.idx, ns, nel)
+			ap.qname = fmt.Sprintf("%s.ArrayF%dS%dE%d", s.checkerPkg(pidx),
+				f.idx, ns, nel)
+			f.arraydefs = append(f.arraydefs, ap)
+			ap.nelements = nel
+			ap.slice = issl
+			ap.eltype = s.GenParm(f, depth+1, false, pidx)
+			ap.eltype.SetBlank(false)
+			skComp := tunables.doSkipCompare &&
+				uint8(s.wr.Intn(100)) < s.tunables.skipCompareFraction
+			if skComp && checkableElements(ap.eltype) != 0 {
+				if issl {
+					ap.SetSkipCompare(SkipPayload)
+				}
+			}
+			f.arraydefs[ns] = ap
+			retval = &ap
+		}
+	case which < tf[MapTfIdx]:
+		{
+			if toodeep {
+				panic("should not be here")
+			}
+			var mp mapparm
+			ns := len(f.mapdefs)
+
+			// append early, since calls below might also append
+			f.mapdefs = append(f.mapdefs, mp)
+			f.mapkeytmps = append(f.mapkeytmps, "")
+			f.mapkeytypes = append(f.mapkeytypes, mp.keytype)
+			mp.aname = fmt.Sprintf("MapF%dM%d", f.idx, ns)
+			if f.mapkeyts == "" {
+				f.mapkeyts = fmt.Sprintf("MapKeysF%d", f.idx)
+			}
+			mp.qname = fmt.Sprintf("%s.MapF%dM%d", s.checkerPkg(pidx),
+				f.idx, ns)
+			mkt := fmt.Sprintf("Mk%dt%d", f.idx, ns)
+			mp.keytmp = mkt
+			mk := s.GenMapKeyType(f, depth+1, pidx)
+			mp.keytype = mk
+			mp.valtype = s.GenParm(f, depth+1, false, pidx)
+			mp.valtype.SetBlank(false)
+			mp.keytype.SetBlank(false)
+			// now update the previously appended placeholders
+			f.mapdefs[ns] = mp
+			f.mapkeytypes[ns] = mk
+			f.mapkeytmps[ns] = mkt
+			retval = &mp
+		}
+	case which < tf[PointerTfIdx]:
+		{
+			if toodeep {
+				panic("should not be here")
+			}
+			pp := mkPointerParm(s.GenParm(f, depth+1, false, pidx))
+			retval = &pp
+		}
+	case which < tf[NumericTfIdx]:
+		{
+			var ip numparm
+			ip.tag = s.intFlavor()
+			ip.widthInBits = s.intBits()
+			if mkctl {
+				ip.ctl = true
+			}
+			retval = &ip
+		}
+	case which < tf[FloatTfIdx]:
+		{
+			var fp numparm
+			fp.tag = "float"
+			fp.widthInBits = s.floatBits()
+			retval = &fp
+		}
+	case which < tf[ComplexTfIdx]:
+		{
+			var fp numparm
+			fp.tag = "complex"
+			fp.widthInBits = s.floatBits() * 2
+			retval = &fp
+		}
+	case which < tf[ByteTfIdx]:
+		{
+			var bp numparm
+			bp.tag = "byte"
+			bp.widthInBits = 8
+			retval = &bp
+		}
+	case which < tf[StringTfIdx]:
+		{
+			var sp stringparm
+			sp.tag = "string"
+			skComp := tunables.doSkipCompare &&
+				uint8(s.wr.Intn(100)) < s.tunables.skipCompareFraction
+			if skComp {
+				sp.SetSkipCompare(SkipPayload)
+			}
+			retval = &sp
+		}
+	default:
+		{
+			// fallback
+			var ip numparm
+			ip.tag = "uint"
+			ip.widthInBits = 8
+			retval = &ip
+		}
+	}
+	if !mkctl {
+		retval.SetBlank(isblank)
+	}
+	retval.SetAddrTaken(addrTaken)
+	retval.SetIsGenVal(isGenValFunc)
+	return retval
+}
+
+func (s *genstate) GenReturn(f *funcdef, depth int, pidx int) parm {
+	return s.GenParm(f, depth, false, pidx)
+}
+
+// GenFunc cooks up the random signature (and other attributes) of a
+// given checker function, returning a funcdef object that describes
+// the new fcn.
+func (s *genstate) GenFunc(fidx int, pidx int) *funcdef {
+	f := new(funcdef)
+	f.idx = fidx
+	numParams := int(s.wr.Intn(int64(1 + int(s.tunables.nParmRange))))
+	numReturns := int(s.wr.Intn(int64(1 + int(s.tunables.nReturnRange))))
+	f.recur = uint8(s.wr.Intn(100)) < s.tunables.recurPerc
+	f.isMethod = uint8(s.wr.Intn(100)) < s.tunables.methodPerc
+	genReceiverType := func() {
+		// Receiver type can't be pointer type. Temporarily update
+		// tunables to eliminate that possibility.
+		s.pushTunables()
+		defer s.popTunables()
+		s.precludeSelectedTypes(PointerTfIdx)
+		target := s.GenParm(f, 0, false, pidx)
+		target.SetBlank(false)
+		f.receiver = s.makeTypedefParm(f, target, pidx)
+		if f.receiver.IsBlank() {
+			f.recur = false
+		}
+	}
+	if f.isMethod {
+		genReceiverType()
+	}
+	needControl := f.recur
+	f.dodefc = uint8(s.wr.Intn(100))
+	pTaken := uint8(s.wr.Intn(100)) < s.tunables.takenFraction
+	for pi := 0; pi < numParams; pi++ {
+		newparm := s.GenParm(f, 0, needControl, pidx)
+		if !pTaken {
+			newparm.SetAddrTaken(notAddrTaken)
+		}
+		if newparm.IsControl() {
+			needControl = false
+		}
+		f.params = append(f.params, newparm)
+		f.dodefp = append(f.dodefp, uint8(s.wr.Intn(100)))
+	}
+	if f.recur && needControl {
+		f.recur = false
+	}
+
+	rTaken := uint8(s.wr.Intn(100)) < s.tunables.takenFraction
+	for ri := 0; ri < numReturns; ri++ {
+		r := s.GenReturn(f, 0, pidx)
+		if !rTaken {
+			r.SetAddrTaken(notAddrTaken)
+		}
+		f.returns = append(f.returns, r)
+	}
+	spw := uint(s.wr.Intn(11))
+	rstack := 1 << spw
+	if rstack < 4 {
+		rstack = 4
+	}
+	f.rstack = rstack
+	return f
+}
+
+func genDeref(p parm) (parm, string) {
+	curp := p
+	star := ""
+	for {
+		if pp, ok := curp.(*pointerparm); ok {
+			star += "*"
+			curp = pp.totype
+		} else {
+			return curp, star
+		}
+	}
+}
+
+func (s *genstate) eqFuncRef(f *funcdef, t parm, caller bool) string {
+	cp := ""
+	if f.mapkeyts != "" {
+		cp = "mkt."
+	} else if caller {
+		cp = s.checkerPkg(s.pkidx) + "."
+	}
+	return cp + "Equal" + t.TypeName()
+}
+
+// emitCompareFunc creates an 'equals' function for a specific
+// generated type (this is basically a way to compare objects that
+// contain pointer fields / pointery things).
+func (s *genstate) emitCompareFunc(f *funcdef, b *bytes.Buffer, p parm) {
+	if !p.HasPointer() {
+		return
+	}
+
+	tn := p.TypeName()
+	b.WriteString(fmt.Sprintf("// equal func for %s\n", tn))
+	b.WriteString("//go:noinline\n")
+	rcvr := ""
+	if f.mapkeyts != "" {
+		rcvr = fmt.Sprintf("(mkt *%s) ", f.mapkeyts)
+	}
+	b.WriteString(fmt.Sprintf("func %sEqual%s(left %s, right %s) bool {\n", rcvr, tn, tn, tn))
+	b.WriteString("  return ")
+	numel := p.NumElements()
+	ncmp := 0
+	for i := 0; i < numel; i++ {
+		lelref, lelparm := p.GenElemRef(i, "left")
+		relref, _ := p.GenElemRef(i, "right")
+		if lelref == "" || lelref == "_" {
+			continue
+		}
+		basep, star := genDeref(lelparm)
+		// Handle *p where p is an empty struct.
+		if basep.NumElements() == 0 {
+			continue
+		}
+		if ncmp != 0 {
+			b.WriteString("  && ")
+		}
+		ncmp++
+		if basep.HasPointer() {
+			efn := s.eqFuncRef(f, basep, false)
+			b.WriteString(fmt.Sprintf(" %s(%s%s, %s%s)", efn, star, lelref, star, relref))
+		} else {
+			b.WriteString(fmt.Sprintf("%s%s == %s%s", star, lelref, star, relref))
+		}
+	}
+	if ncmp == 0 {
+		b.WriteString("true")
+	}
+	b.WriteString("\n}\n\n")
+}
+
+// emitStructAndArrayDefs writes out definitions of the random types
+// we happened to cook up while generating code for a specific
+// function pair.
+func (s *genstate) emitStructAndArrayDefs(f *funcdef, b *bytes.Buffer) {
+	for _, str := range f.structdefs {
+		b.WriteString(fmt.Sprintf("type %s struct {\n", str.sname))
+		for fi, sp := range str.fields {
+			sp.Declare(b, "  "+str.FieldName(fi), "\n", false)
+		}
+		b.WriteString("}\n\n")
+		s.emitCompareFunc(f, b, &str)
+	}
+	for _, a := range f.arraydefs {
+		elems := fmt.Sprintf("%d", a.nelements)
+		if a.slice {
+			elems = ""
+		}
+		b.WriteString(fmt.Sprintf("type %s [%s]%s\n\n", a.aname,
+			elems, a.eltype.TypeName()))
+		s.emitCompareFunc(f, b, &a)
+	}
+	for _, a := range f.mapdefs {
+		b.WriteString(fmt.Sprintf("type %s map[%s]%s\n\n", a.aname,
+			a.keytype.TypeName(), a.valtype.TypeName()))
+		s.emitCompareFunc(f, b, &a)
+	}
+	for _, td := range f.typedefs {
+		b.WriteString(fmt.Sprintf("type %s %s\n\n", td.aname,
+			td.target.TypeName()))
+		s.emitCompareFunc(f, b, &td)
+	}
+	if f.mapkeyts != "" {
+		b.WriteString(fmt.Sprintf("type %s struct {\n", f.mapkeyts))
+		for i := range f.mapkeytypes {
+			f.mapkeytypes[i].Declare(b, "  "+f.mapkeytmps[i], "\n", false)
+		}
+		b.WriteString("}\n\n")
+	}
+}
+
+// GenValue method of genstate wraps the parm method of the same
+// name, but optionally returns a call to a function to produce
+// the value as opposed to a literal value.
+func (s *genstate) GenValue(f *funcdef, p parm, value int, caller bool) (string, int) {
+	var valstr string
+	valstr, value = p.GenValue(s, f, value, caller)
+	if !s.tunables.doFuncCallValues || !p.IsGenVal() || caller {
+		return valstr, value
+	}
+
+	mkInvoc := func(fname string) string {
+		meth := ""
+		if f.mapkeyts != "" {
+			meth = "mkt."
+		}
+		return fmt.Sprintf("%s%s()", meth, fname)
+	}
+
+	b := bytes.NewBuffer(nil)
+	p.Declare(b, "x", "", false)
+	h := sha1.New()
+	h.Write([]byte(valstr))
+	h.Write(b.Bytes())
+	if f.mapkeyts != "" {
+		h.Write([]byte(f.mapkeyts))
+	}
+	h.Write(b.Bytes())
+	bs := h.Sum(nil)
+	hashstr := fmt.Sprintf("%x", bs)
+	b.WriteString(hashstr)
+	tag := b.String()
+	fname, ok := s.genvalFuncs[tag]
+	if ok {
+		return mkInvoc(fname), value
+	}
+
+	fname = fmt.Sprintf("genval_%d", len(s.genvalFuncs))
+	s.newGenvalFuncs = append(s.newGenvalFuncs, funcdesc{p: p, name: fname, tag: tag, payload: valstr})
+	s.genvalFuncs[tag] = fname
+	return mkInvoc(fname), value
+}
+
+func (s *genstate) emitMapKeyTmps(f *funcdef, b *bytes.Buffer, pidx int, value int, caller bool) int {
+	if f.mapkeyts == "" {
+		return value
+	}
+	// map key tmps
+	cp := ""
+	if caller {
+		cp = s.checkerPkg(pidx) + "."
+	}
+	b.WriteString("  var mkt " + cp + f.mapkeyts + "\n")
+	for i, t := range f.mapkeytypes {
+		var keystr string
+		keystr, value = s.GenValue(f, t, value, caller)
+		tname := f.mapkeytmps[i]
+		b.WriteString(fmt.Sprintf("  %s := %s\n", tname, keystr))
+		b.WriteString(fmt.Sprintf("  mkt.%s = %s\n", tname, tname))
+	}
+	return value
+}
+
+func (s *genstate) emitCheckReturnsInCaller(f *funcdef, b *bytes.Buffer, pidx int, reflectCall bool) {
+	cm := f.complexityMeasure()
+	rvalp := func(ri int) string {
+		if reflectCall {
+			return fmt.Sprintf("rr%dv", ri)
+		}
+		return fmt.Sprintf("r%d", ri)
+	}
+	failTag := "\"return\""
+	if reflectCall {
+		failTag = "\"reflect return\""
+	}
+	for ri, rp := range f.returns {
+		if reflectCall {
+			b.WriteString(fmt.Sprintf("  rr%di := rvslice[%d].Interface()\n", ri, ri))
+			b.WriteString(fmt.Sprintf("  rr%dv:= rr%di.(", ri, ri))
+			rp.Declare(b, "", "", true)
+			b.WriteString(")\n")
+		}
+		pfc := ""
+		curp, star := genDeref(rp)
+		// Handle *p where p is an empty struct.
+		if curp.NumElements() == 0 {
+			b.WriteString(fmt.Sprintf("  _, _ = %s, c%d // zero size\n", rvalp(ri), ri))
+			continue
+		}
+		if star != "" {
+			pfc = fmt.Sprintf("ParamFailCount[%d] == 0 && ", pidx)
+		}
+		if curp.HasPointer() {
+			efn := "!" + s.eqFuncRef(f, curp, true)
+			b.WriteString(fmt.Sprintf("  if %s%s(%s%s, %sc%d) {\n", pfc, efn, star, rvalp(ri), star, ri))
+		} else {
+			b.WriteString(fmt.Sprintf("  if %s%s%s != %sc%d {\n", pfc, star, rvalp(ri), star, ri))
+		}
+		b.WriteString(fmt.Sprintf("    NoteFailure(%d, %d, %d, \"%s\", %s, %d, true, uint64(0))\n", cm, pidx, f.idx, s.checkerPkg(pidx), failTag, ri))
+		b.WriteString("  }\n")
+	}
+}
+
+func (s *genstate) emitCaller(f *funcdef, b *bytes.Buffer, pidx int) {
+
+	b.WriteString(fmt.Sprintf("func %s%d(mode string) {\n", CallerName, f.idx))
+
+	b.WriteString(fmt.Sprintf("  BeginFcn(%d)\n", pidx))
+
+	if s.EmitBad == 1 {
+		if s.BadPackageIdx == pidx && s.BadFuncIdx == f.idx {
+			b.WriteString("  bad code here, should cause build failure <<==\n")
+		}
+	}
+
+	var value int = 1
+
+	s.wr.Checkpoint("before mapkeytmps")
+	value = s.emitMapKeyTmps(f, b, pidx, value, true)
+
+	// generate return constants
+	s.wr.Checkpoint("before return constants")
+	for ri, r := range f.returns {
+		rc := fmt.Sprintf("c%d", ri)
+		value = s.emitVarAssign(f, b, r, rc, value, true)
+	}
+
+	// generate param constants
+	s.wr.Checkpoint("before param constants")
+	for pi, p := range f.params {
+		verb(4, "emitCaller gen p%d value=%d", pi, value)
+		if p.IsControl() {
+			_ = uint8(s.wr.Intn(100)) < 50
+			p.Declare(b, fmt.Sprintf("  var p%d ", pi), " = 10\n", true)
+		} else {
+			pc := fmt.Sprintf("p%d", pi)
+			value = s.emitVarAssign(f, b, p, pc, value, true)
+		}
+		f.values = append(f.values, value)
+	}
+
+	// generate receiver constant if applicable
+	if f.isMethod {
+		s.wr.Checkpoint("before receiver constant")
+		f.receiver.Declare(b, "  var rcvr", "\n", true)
+		valstr, value := s.GenValue(f, f.receiver, value, true)
+		b.WriteString(fmt.Sprintf("  rcvr = %s\n", valstr))
+		f.values = append(f.values, value)
+	}
+
+	b.WriteString(fmt.Sprintf("  Mode[%d] = \"\"\n", pidx))
+
+	// calling code
+	b.WriteString(fmt.Sprintf("  // %d returns %d params\n",
+		len(f.returns), len(f.params)))
+	if s.ForceStackGrowth {
+		b.WriteString("  hackStack() // force stack growth on next call\n")
+	}
+	b.WriteString("  if mode == \"normal\" {\n")
+	b.WriteString("  ")
+	for ri := range f.returns {
+		writeCom(b, ri)
+		b.WriteString(fmt.Sprintf("r%d", ri))
+	}
+	if len(f.returns) > 0 {
+		b.WriteString(" := ")
+	}
+	pref := s.checkerPkg(pidx)
+	if f.isMethod {
+		pref = "rcvr"
+	}
+	b.WriteString(fmt.Sprintf("%s.Test%d(", pref, f.idx))
+	for pi := range f.params {
+		writeCom(b, pi)
+		b.WriteString(fmt.Sprintf("p%d", pi))
+	}
+	b.WriteString(")\n")
+
+	// check values returned (normal call case)
+	s.emitCheckReturnsInCaller(f, b, pidx, false /* not a reflect call */)
+	b.WriteString("  }") // end of 'if normal call' block
+	if s.tunables.doReflectCall {
+		b.WriteString("else {\n") // beginning of reflect call block
+		// now make the same call via reflection
+		b.WriteString("  // same call via reflection\n")
+		b.WriteString(fmt.Sprintf("  Mode[%d] = \"reflect\"\n", pidx))
+		if f.isMethod {
+			b.WriteString("  rcv := reflect.ValueOf(rcvr)\n")
+			b.WriteString(fmt.Sprintf("  rc := rcv.MethodByName(\"Test%d\")\n", f.idx))
+		} else {
+			b.WriteString(fmt.Sprintf("  rc := reflect.ValueOf(%s.Test%d)\n",
+				s.checkerPkg(pidx), f.idx))
+		}
+		b.WriteString("  ")
+		if len(f.returns) > 0 {
+			b.WriteString("rvslice := ")
+		}
+		b.WriteString("  rc.Call([]reflect.Value{")
+		for pi := range f.params {
+			writeCom(b, pi)
+			b.WriteString(fmt.Sprintf("reflect.ValueOf(p%d)", pi))
+		}
+		b.WriteString("})\n")
+
+		// check values returned (reflect call case)
+		s.emitCheckReturnsInCaller(f, b, pidx, true /* is a reflect call */)
+		b.WriteString("}\n") // end of reflect call block
+	}
+
+	b.WriteString(fmt.Sprintf("\n  EndFcn(%d)\n", pidx))
+
+	b.WriteString("}\n\n")
+}
+
+func checkableElements(p parm) int {
+	if p.IsBlank() {
+		return 0
+	}
+	sp, isstruct := p.(*structparm)
+	if isstruct {
+		s := 0
+		for fi := range sp.fields {
+			s += checkableElements(sp.fields[fi])
+		}
+		return s
+	}
+	ap, isarray := p.(*arrayparm)
+	if isarray {
+		if ap.nelements == 0 {
+			return 0
+		}
+		return int(ap.nelements) * checkableElements(ap.eltype)
+	}
+	return 1
+}
+
+// funcdesc describes an auto-generated helper function or global
+// variable, such as an allocation function (returns new(T)) or a
+// pointer assignment function (assigns value of T to type *T). Here
+// 'p' is a param type T, 'pp' is a pointer type *T, 'name' is the
+// name within the generated code of the function or variable and
+// 'tag' is a descriptive tag used to look up the entity in a map (so
+// that we don't have to emit multiple copies of a function that
+// assigns int to *int, for example).
+type funcdesc struct {
+	p       parm
+	pp      parm
+	name    string
+	tag     string
+	payload string
+}
+
+func (s *genstate) emitDerefFuncs(b *bytes.Buffer, emit bool) {
+	b.WriteString("// dereference helpers\n")
+	for _, fd := range s.newDerefFuncs {
+		if !emit {
+			b.WriteString(fmt.Sprintf("\n// skip derefunc %s\n", fd.name))
+			delete(s.derefFuncs, fd.tag)
+			continue
+		}
+		b.WriteString("\n//go:noinline\n")
+		b.WriteString(fmt.Sprintf("func %s(", fd.name))
+		fd.pp.Declare(b, "x", "", false)
+		b.WriteString(") ")
+		fd.p.Declare(b, "", "", false)
+		b.WriteString(" {\n")
+		b.WriteString("  return *x\n")
+		b.WriteString("}\n")
+	}
+	s.newDerefFuncs = nil
+}
+
+func (s *genstate) emitAssignFuncs(b *bytes.Buffer, emit bool) {
+	b.WriteString("// assign helpers\n")
+	for _, fd := range s.newAssignFuncs {
+		if !emit {
+			b.WriteString(fmt.Sprintf("\n// skip assignfunc %s\n", fd.name))
+			delete(s.assignFuncs, fd.tag)
+			continue
+		}
+		b.WriteString("\n//go:noinline\n")
+		b.WriteString(fmt.Sprintf("func %s(", fd.name))
+		fd.pp.Declare(b, "x", "", false)
+		b.WriteString(", ")
+		fd.p.Declare(b, "v", "", false)
+		b.WriteString(") {\n")
+		b.WriteString("  *x = v\n")
+		b.WriteString("}\n")
+	}
+	s.newAssignFuncs = nil
+}
+
+func (s *genstate) emitNewFuncs(b *bytes.Buffer, emit bool) {
+	b.WriteString("// 'new' funcs\n")
+	for _, fd := range s.newAllocFuncs {
+		if !emit {
+			b.WriteString(fmt.Sprintf("\n// skip newfunc %s\n", fd.name))
+			delete(s.allocFuncs, fd.tag)
+			continue
+		}
+		b.WriteString("\n//go:noinline\n")
+		b.WriteString(fmt.Sprintf("func %s(", fd.name))
+		fd.p.Declare(b, "i", "", false)
+		b.WriteString(") ")
+		fd.pp.Declare(b, "", "", false)
+		b.WriteString(" {\n")
+		b.WriteString("  x := new(")
+		fd.p.Declare(b, "", "", false)
+		b.WriteString(")\n")
+		b.WriteString("  *x = i\n")
+		b.WriteString("  return x\n")
+		b.WriteString("}\n\n")
+	}
+	s.newAllocFuncs = nil
+}
+
+func (s *genstate) emitGlobalVars(b *bytes.Buffer, emit bool) {
+	b.WriteString("// global vars\n")
+	for _, fd := range s.newGlobVars {
+		if !emit {
+			b.WriteString(fmt.Sprintf("\n// skip gvar %s\n", fd.name))
+			delete(s.globVars, fd.tag)
+			continue
+		}
+		b.WriteString("var ")
+		fd.pp.Declare(b, fd.name, "", false)
+		b.WriteString("\n")
+	}
+	s.newGlobVars = nil
+	b.WriteString("\n")
+}
+
+func (s *genstate) emitGenValFuncs(f *funcdef, b *bytes.Buffer, emit bool) {
+	b.WriteString("// genval helpers\n")
+	for _, fd := range s.newGenvalFuncs {
+		if !emit {
+			b.WriteString(fmt.Sprintf("\n// skip genvalfunc %s\n", fd.name))
+			delete(s.genvalFuncs, fd.tag)
+			continue
+		}
+		b.WriteString("\n//go:noinline\n")
+		rcvr := ""
+		if f.mapkeyts != "" {
+			rcvr = fmt.Sprintf("(mkt *%s) ", f.mapkeyts)
+		}
+		b.WriteString(fmt.Sprintf("func %s%s() ", rcvr, fd.name))
+		fd.p.Declare(b, "", "", false)
+		b.WriteString(" {\n")
+		if f.mapkeyts != "" {
+			contained := containedParms(fd.p)
+			for _, cp := range contained {
+				mp, ismap := cp.(*mapparm)
+				if ismap {
+					b.WriteString(fmt.Sprintf("  %s := mkt.%s\n",
+						mp.keytmp, mp.keytmp))
+					b.WriteString(fmt.Sprintf("  _ = %s\n", mp.keytmp))
+				}
+			}
+		}
+		b.WriteString(fmt.Sprintf("  return %s\n", fd.payload))
+		b.WriteString("}\n")
+	}
+	s.newGenvalFuncs = nil
+}
+
+func (s *genstate) emitAddrTakenHelpers(f *funcdef, b *bytes.Buffer, emit bool) {
+	b.WriteString("// begin addr taken helpers\n")
+	s.emitDerefFuncs(b, emit)
+	s.emitAssignFuncs(b, emit)
+	s.emitNewFuncs(b, emit)
+	s.emitGlobalVars(b, emit)
+	s.emitGenValFuncs(f, b, emit)
+	b.WriteString("// end addr taken helpers\n")
+}
+
+func (s *genstate) genGlobVar(p parm) string {
+	var pp parm
+	ppp := mkPointerParm(p)
+	pp = &ppp
+	b := bytes.NewBuffer(nil)
+	pp.Declare(b, "gv", "", false)
+	tag := b.String()
+	gv, ok := s.globVars[tag]
+	if ok {
+		return gv
+	}
+	gv = fmt.Sprintf("gvar_%d", len(s.globVars))
+	s.newGlobVars = append(s.newGlobVars, funcdesc{pp: pp, p: p, name: gv, tag: tag})
+	s.globVars[tag] = gv
+	return gv
+}
+
+func (s *genstate) genParamDerefFunc(p parm) string {
+	var pp parm
+	ppp := mkPointerParm(p)
+	pp = &ppp
+	b := bytes.NewBuffer(nil)
+	pp.Declare(b, "x", "", false)
+	tag := b.String()
+	f, ok := s.derefFuncs[tag]
+	if ok {
+		return f
+	}
+	f = fmt.Sprintf("deref_%d", len(s.derefFuncs))
+	s.newDerefFuncs = append(s.newDerefFuncs, funcdesc{pp: pp, p: p, name: f, tag: tag})
+	s.derefFuncs[tag] = f
+	return f
+}
+
+func (s *genstate) genAssignFunc(p parm) string {
+	var pp parm
+	ppp := mkPointerParm(p)
+	pp = &ppp
+	b := bytes.NewBuffer(nil)
+	pp.Declare(b, "x", "", false)
+	tag := b.String()
+	f, ok := s.assignFuncs[tag]
+	if ok {
+		return f
+	}
+	f = fmt.Sprintf("retassign_%d", len(s.assignFuncs))
+	s.newAssignFuncs = append(s.newAssignFuncs, funcdesc{pp: pp, p: p, name: f, tag: tag})
+	s.assignFuncs[tag] = f
+	return f
+}
+
+func (s *genstate) genAllocFunc(p parm) string {
+	var pp parm
+	ppp := mkPointerParm(p)
+	pp = &ppp
+	b := bytes.NewBuffer(nil)
+	pp.Declare(b, "x", "", false)
+	tag := b.String()
+	f, ok := s.allocFuncs[tag]
+	if ok {
+		return f
+	}
+	f = fmt.Sprintf("New_%d", len(s.allocFuncs))
+	s.newAllocFuncs = append(s.newAllocFuncs, funcdesc{pp: pp, p: p, name: f, tag: tag})
+	s.allocFuncs[tag] = f
+	return f
+}
+
+func (s *genstate) genParamRef(p parm, idx int) string {
+	switch p.AddrTaken() {
+	case notAddrTaken:
+		return fmt.Sprintf("p%d", idx)
+	case addrTakenSimple, addrTakenHeap:
+		return fmt.Sprintf("(*ap%d)", idx)
+	case addrTakenPassed:
+		f := s.genParamDerefFunc(p)
+		return fmt.Sprintf("%s(ap%d)", f, idx)
+	default:
+		panic("bad")
+	}
+}
+
+func (s *genstate) genReturnAssign(b *bytes.Buffer, r parm, idx int, val string) {
+	switch r.AddrTaken() {
+	case notAddrTaken:
+		b.WriteString(fmt.Sprintf("  r%d = %s\n", idx, val))
+	case addrTakenSimple, addrTakenHeap:
+		b.WriteString(fmt.Sprintf("  (*ar%d) = %v\n", idx, val))
+	case addrTakenPassed:
+		f := s.genAssignFunc(r)
+		b.WriteString(fmt.Sprintf("  %s(ar%d, %v)\n", f, idx, val))
+	default:
+		panic("bad")
+	}
+}
+
+func (s *genstate) emitParamElemCheck(f *funcdef, b *bytes.Buffer, p parm, pvar string, cvar string, paramidx int, elemidx int) {
+	if p.SkipCompare() == SkipAll {
+		b.WriteString(fmt.Sprintf("  // selective skip of %s\n", pvar))
+		b.WriteString(fmt.Sprintf("  _ = %s\n", cvar))
+		return
+	} else if p.SkipCompare() == SkipPayload {
+		switch p.(type) {
+		case *stringparm, *arrayparm:
+			b.WriteString(fmt.Sprintf("  if len(%s) != len(%s) { // skip payload\n",
+				pvar, cvar))
+		default:
+			panic("should never happen")
+		}
+	} else {
+		basep, star := genDeref(p)
+		// Handle *p where p is an empty struct.
+		if basep.NumElements() == 0 {
+			return
+		}
+		if basep.HasPointer() {
+			efn := s.eqFuncRef(f, basep, false)
+			b.WriteString(fmt.Sprintf("  if !%s(%s%s, %s%s) {\n",
+				efn, star, pvar, star, cvar))
+		} else {
+			b.WriteString(fmt.Sprintf("  if %s%s != %s%s {\n",
+				star, pvar, star, cvar))
+		}
+	}
+	cm := f.complexityMeasure()
+	b.WriteString(fmt.Sprintf("    NoteFailureElem(%d, %d, %d, \"%s\", \"parm\", %d, %d, false, pad[0])\n", cm, s.pkidx, f.idx, s.checkerPkg(s.pkidx), paramidx, elemidx))
+	b.WriteString("    return\n")
+	b.WriteString("  }\n")
+}
+
+func (s *genstate) emitParamChecks(f *funcdef, b *bytes.Buffer, pidx int, value int) (int, bool) {
+	var valstr string
+	haveControl := false
+	dangling := []int{}
+	for pi, p := range f.params {
+		verb(4, "emitting parmcheck p%d numel=%d pt=%s value=%d",
+			pi, p.NumElements(), p.TypeName(), value)
+		// To balance code in caller
+		_ = uint8(s.wr.Intn(100)) < 50
+		if p.IsControl() {
+			b.WriteString(fmt.Sprintf("  if %s == 0 {\n",
+				s.genParamRef(p, pi)))
+			s.emitReturn(f, b, false)
+			b.WriteString("  }\n")
+			haveControl = true
+
+		} else if p.IsBlank() {
+			valstr, value = s.GenValue(f, p, value, false)
+			if f.recur {
+				b.WriteString(fmt.Sprintf("  brc%d := %s\n", pi, valstr))
+			} else {
+				b.WriteString(fmt.Sprintf("  _ = %s\n", valstr))
+			}
+		} else {
+			numel := p.NumElements()
+			cel := checkableElements(p)
+			for i := 0; i < numel; i++ {
+				verb(4, "emitting check-code for p%d el %d value=%d", pi, i, value)
+				elref, elparm := p.GenElemRef(i, s.genParamRef(p, pi))
+				valstr, value = s.GenValue(f, elparm, value, false)
+				if elref == "" || elref == "_" || cel == 0 {
+					b.WriteString(fmt.Sprintf("  // blank skip: %s\n", valstr))
+					continue
+				} else {
+					basep, _ := genDeref(elparm)
+					// Handle *p where p is an empty struct.
+					if basep.NumElements() == 0 {
+						continue
+					}
+					cvar := fmt.Sprintf("p%df%dc", pi, i)
+					b.WriteString(fmt.Sprintf("  %s := %s\n", cvar, valstr))
+					s.emitParamElemCheck(f, b, elparm, elref, cvar, pi, i)
+				}
+			}
+			if p.AddrTaken() != notAddrTaken {
+				dangling = append(dangling, pi)
+			}
+		}
+		if value != f.values[pi] {
+			fmt.Fprintf(os.Stderr, "internal error: checker/caller value mismatch after emitting param %d func Test%d pkg %s: caller %d checker %d\n", pi, f.idx, s.checkerPkg(pidx), f.values[pi], value)
+			s.errs++
+		}
+	}
+	for _, pi := range dangling {
+		b.WriteString(fmt.Sprintf("  _ = ap%d // ref\n", pi))
+	}
+
+	// receiver value check
+	if f.isMethod {
+		numel := f.receiver.NumElements()
+		for i := 0; i < numel; i++ {
+			verb(4, "emitting check-code for rcvr el %d value=%d", i, value)
+			elref, elparm := f.receiver.GenElemRef(i, "rcvr")
+			valstr, value = s.GenValue(f, elparm, value, false)
+			if elref == "" || strings.HasPrefix(elref, "_") || f.receiver.IsBlank() {
+				verb(4, "empty skip rcvr el %d", i)
+				continue
+			} else {
+
+				basep, _ := genDeref(elparm)
+				// Handle *p where p is an empty struct.
+				if basep.NumElements() == 0 {
+					continue
+				}
+				cvar := fmt.Sprintf("rcvrf%dc", i)
+				b.WriteString(fmt.Sprintf("  %s := %s\n", cvar, valstr))
+				s.emitParamElemCheck(f, b, elparm, elref, cvar, -1, i)
+			}
+		}
+	}
+
+	return value, haveControl
+}
+
+// emitDeferChecks creates code like
+//
+//     defer func(...args...) {
+//       check arg
+//       check param
+//     }(...)
+//
+// where we randomly choose to either pass a param through to the
+// function literal, or have the param captured by the closure, then
+// check its value in the defer.
+func (s *genstate) emitDeferChecks(f *funcdef, b *bytes.Buffer, pidx int, value int) int {
+
+	if len(f.params) == 0 {
+		return value
+	}
+
+	// make a pass through the params and randomly decide which will be passed into the func.
+	passed := []bool{}
+	for i := range f.params {
+		p := f.dodefp[i] < 50
+		passed = append(passed, p)
+	}
+
+	b.WriteString("  defer func(")
+	pc := 0
+	for pi, p := range f.params {
+		if p.IsControl() || p.IsBlank() {
+			continue
+		}
+		if passed[pi] {
+			writeCom(b, pc)
+			n := fmt.Sprintf("p%d", pi)
+			p.Declare(b, n, "", false)
+			pc++
+		}
+	}
+	b.WriteString(") {\n")
+
+	for pi, p := range f.params {
+		if p.IsControl() || p.IsBlank() {
+			continue
+		}
+		which := "passed"
+		if !passed[pi] {
+			which = "captured"
+		}
+		b.WriteString("  // check parm " + which + "\n")
+		numel := p.NumElements()
+		cel := checkableElements(p)
+		for i := 0; i < numel; i++ {
+			elref, elparm := p.GenElemRef(i, s.genParamRef(p, pi))
+			if elref == "" || elref == "_" || cel == 0 {
+				verb(4, "empty skip p%d el %d", pi, i)
+				continue
+			} else {
+				basep, _ := genDeref(elparm)
+				// Handle *p where p is an empty struct.
+				if basep.NumElements() == 0 {
+					continue
+				}
+				cvar := fmt.Sprintf("p%df%dc", pi, i)
+				s.emitParamElemCheck(f, b, elparm, elref, cvar, pi, i)
+			}
+		}
+	}
+	b.WriteString("  } (")
+	pc = 0
+	for pi, p := range f.params {
+		if p.IsControl() || p.IsBlank() {
+			continue
+		}
+		if passed[pi] {
+			writeCom(b, pc)
+			b.WriteString(fmt.Sprintf("p%d", pi))
+			pc++
+		}
+	}
+	b.WriteString(")\n\n")
+
+	return value
+}
+
+func (s *genstate) emitVarAssign(f *funcdef, b *bytes.Buffer, r parm, rname string, value int, caller bool) int {
+	var valstr string
+	isassign := uint8(s.wr.Intn(100)) < 50
+	if rmp, ismap := r.(*mapparm); ismap && isassign {
+		// emit: var m ... ; m[k] = v
+		r.Declare(b, "  "+rname+" := make(", ")\n", caller)
+		valstr, value = s.GenValue(f, rmp.valtype, value, caller)
+		b.WriteString(fmt.Sprintf("  %s[mkt.%s] = %s\n",
+			rname, rmp.keytmp, valstr))
+	} else {
+		// emit r = c
+		valstr, value = s.GenValue(f, r, value, caller)
+		b.WriteString(fmt.Sprintf("  %s := %s\n", rname, valstr))
+	}
+	return value
+}
+
+func (s *genstate) emitChecker(f *funcdef, b *bytes.Buffer, pidx int, emit bool) {
+	verb(4, "emitting struct and array defs")
+	s.emitStructAndArrayDefs(f, b)
+	b.WriteString(fmt.Sprintf("// %d returns %d params\n", len(f.returns), len(f.params)))
+	if s.Pragma != "" {
+		b.WriteString("//go:" + s.Pragma + "\n")
+	}
+	b.WriteString("//go:noinline\n")
+
+	b.WriteString("func")
+
+	if f.isMethod {
+		b.WriteString(" (")
+		n := "rcvr"
+		if f.receiver.IsBlank() {
+			n = "_"
+		}
+		f.receiver.Declare(b, n, "", false)
+		b.WriteString(")")
+	}
+
+	b.WriteString(fmt.Sprintf(" Test%d(", f.idx))
+
+	verb(4, "emitting checker p%d/Test%d", pidx, f.idx)
+
+	// params
+	for pi, p := range f.params {
+		writeCom(b, pi)
+		n := fmt.Sprintf("p%d", pi)
+		if p.IsBlank() {
+			n = "_"
+		}
+		p.Declare(b, n, "", false)
+	}
+	b.WriteString(") ")
+
+	// returns
+	if len(f.returns) > 0 {
+		b.WriteString("(")
+	}
+	for ri, r := range f.returns {
+		writeCom(b, ri)
+		r.Declare(b, fmt.Sprintf("r%d", ri), "", false)
+	}
+	if len(f.returns) > 0 {
+		b.WriteString(")")
+	}
+	b.WriteString(" {\n")
+
+	// local storage
+	b.WriteString("  // consume some stack space, so as to trigger morestack\n")
+	b.WriteString(fmt.Sprintf("  var pad [%d]uint64\n", f.rstack))
+	b.WriteString(fmt.Sprintf("  pad[FailCount[%d] & 0x1]++\n", pidx))
+
+	value := 1
+
+	// generate map key tmps
+	s.wr.Checkpoint("before map key temps")
+	value = s.emitMapKeyTmps(f, b, pidx, value, false)
+
+	// generate return constants
+	s.wr.Checkpoint("before return constants")
+	for ri, r := range f.returns {
+		rc := fmt.Sprintf("rc%d", ri)
+		value = s.emitVarAssign(f, b, r, rc, value, false)
+	}
+
+	// Prepare to reference params/returns by address.
+	lists := [][]parm{f.params, f.returns}
+	names := []string{"p", "r"}
+	var aCounts [2]int
+	for i, lst := range lists {
+		for pi, p := range lst {
+			if p.AddrTaken() == notAddrTaken {
+				continue
+			}
+			aCounts[i]++
+			n := names[i]
+			b.WriteString(fmt.Sprintf("  a%s%d := &%s%d\n", n, pi, n, pi))
+			if p.AddrTaken() == addrTakenHeap {
+				gv := s.genGlobVar(p)
+				b.WriteString(fmt.Sprintf("  %s = a%s%d\n", gv, n, pi))
+			}
+		}
+	}
+
+	if s.EmitBad == 2 {
+		if s.BadPackageIdx == pidx && s.BadFuncIdx == f.idx {
+			b.WriteString("  // force runtime failure here (debugging)\n")
+			b.WriteString(fmt.Sprintf("    NoteFailure(%d, %d, %d, \"%s\", \"artificial\", %d, true, uint64(0))\n", f.complexityMeasure(), pidx, f.idx, s.checkerPkg(pidx), 0))
+		}
+	}
+
+	// parameter checking code
+	var haveControl bool
+	s.wr.Checkpoint("before param checks")
+	value, haveControl = s.emitParamChecks(f, b, pidx, value)
+
+	// defer testing
+	if s.tunables.doDefer && f.dodefc < s.tunables.deferFraction {
+		s.wr.Checkpoint("before defer checks")
+		_ = s.emitDeferChecks(f, b, pidx, value)
+	}
+
+	// returns
+	s.emitReturn(f, b, haveControl)
+
+	b.WriteString(fmt.Sprintf("  // %d addr-taken params, %d addr-taken returns\n",
+		aCounts[0], aCounts[1]))
+
+	b.WriteString("}\n\n")
+
+	// emit any new helper funcs referenced by this test function
+	s.emitAddrTakenHelpers(f, b, emit)
+}
+
+// complexityMeasure returns an integer that estimates how complex a
+// given test function is relative to some other function. The more
+// parameters + returns and the more complicated the types of the
+// params/returns, the higher the number returned here. In theory this
+// could be worked into the minimization process (e.g. pick the least
+// complex func that reproduces the failure), but for now that isn't
+// wired up yet.
+func (f *funcdef) complexityMeasure() int {
+	v := int(0)
+	if f.isMethod {
+		v += f.receiver.NumElements()
+	}
+	for _, p := range f.params {
+		v += p.NumElements()
+	}
+	for _, r := range f.returns {
+		v += r.NumElements()
+	}
+	return v
+}
+
+// emitRecursiveCall generates a recursive call to the test function in question.
+func (s *genstate) emitRecursiveCall(f *funcdef) string {
+	b := bytes.NewBuffer(nil)
+	rcvr := ""
+	if f.isMethod {
+		rcvr = "rcvr."
+	}
+	b.WriteString(fmt.Sprintf(" %sTest%d(", rcvr, f.idx))
+	for pi, p := range f.params {
+		writeCom(b, pi)
+		if p.IsControl() {
+			b.WriteString(fmt.Sprintf(" %s-1", s.genParamRef(p, pi)))
+		} else {
+			if !p.IsBlank() {
+				b.WriteString(fmt.Sprintf(" %s", s.genParamRef(p, pi)))
+			} else {
+				b.WriteString(fmt.Sprintf(" brc%d", pi))
+			}
+		}
+	}
+	b.WriteString(")")
+	return b.String()
+}
+
+// emitReturn generates a return sequence.
+func (s *genstate) emitReturn(f *funcdef, b *bytes.Buffer, doRecursiveCall bool) {
+	// If any of the return values are address-taken, then instead of
+	//
+	//   return x, y, z
+	//
+	// we emit
+	//
+	//   r1 = ...
+	//   r2 = ...
+	//   ...
+	//   return
+	//
+	// Make an initial pass through the returns to see if we need to do this.
+	// Figure out the final return values in the process.
+	indirectReturn := false
+	retvals := []string{}
+	for ri, r := range f.returns {
+		if r.AddrTaken() != notAddrTaken {
+			indirectReturn = true
+		}
+		t := ""
+		if doRecursiveCall {
+			t = "t"
+		}
+		retvals = append(retvals, fmt.Sprintf("rc%s%d", t, ri))
+	}
+
+	// generate the recursive call itself if applicable
+	if doRecursiveCall {
+		b.WriteString("  // recursive call\n  ")
+		if s.ForceStackGrowth {
+			b.WriteString("  hackStack() // force stack growth on next call\n")
+		}
+		rcall := s.emitRecursiveCall(f)
+		if indirectReturn {
+			for ri := range f.returns {
+				writeCom(b, ri)
+				b.WriteString(fmt.Sprintf("  rct%d", ri))
+			}
+			b.WriteString(" := ")
+			b.WriteString(rcall)
+			b.WriteString("\n")
+		} else {
+			if len(f.returns) == 0 {
+				b.WriteString(fmt.Sprintf("%s\n  return\n", rcall))
+			} else {
+				b.WriteString(fmt.Sprintf("  return %s\n", rcall))
+			}
+			return
+		}
+	}
+
+	// now the actual return
+	if indirectReturn {
+		for ri, r := range f.returns {
+			s.genReturnAssign(b, r, ri, retvals[ri])
+		}
+		b.WriteString("  return\n")
+	} else {
+		b.WriteString("  return ")
+		for ri := range f.returns {
+			writeCom(b, ri)
+			b.WriteString(retvals[ri])
+		}
+		b.WriteString("\n")
+	}
+}
+
+func (s *genstate) GenPair(calloutfile *os.File, checkoutfile *os.File, fidx int, pidx int, b *bytes.Buffer, seed int64, emit bool) int64 {
+
+	verb(1, "gen fidx %d pidx %d", fidx, pidx)
+
+	checkTunables(tunables)
+	s.tunables = tunables
+
+	// Generate a function with a random number of params and returns
+	s.wr = NewWrapRand(seed, s.RandCtl)
+	s.wr.tag = "genfunc"
+	fp := s.GenFunc(fidx, pidx)
+
+	// Emit caller side
+	wrcaller := NewWrapRand(seed, s.RandCtl)
+	s.wr = wrcaller
+	s.wr.tag = "caller"
+	s.emitCaller(fp, b, pidx)
+	if emit {
+		b.WriteTo(calloutfile)
+	}
+	b.Reset()
+
+	// Emit checker side
+	wrchecker := NewWrapRand(seed, s.RandCtl)
+	s.wr = wrchecker
+	s.wr.tag = "checker"
+	s.emitChecker(fp, b, pidx, emit)
+	if emit {
+		b.WriteTo(checkoutfile)
+	}
+	b.Reset()
+	wrchecker.Check(wrcaller)
+
+	return seed + 1
+}
+
+func (s *genstate) openOutputFile(filename string, pk string, imports []string, ipref string) *os.File {
+	iprefix := func(f string) string {
+		if ipref == "" {
+			return f
+		}
+		return ipref + "/" + f
+	}
+	verb(1, "opening %s", filename)
+	outf, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
+	if err != nil {
+		log.Fatal(err)
+	}
+	haveunsafe := false
+	outf.WriteString(fmt.Sprintf("package %s\n\n", pk))
+	for _, imp := range imports {
+		if imp == "reflect" {
+			outf.WriteString("import \"reflect\"\n")
+			continue
+		}
+		if imp == "unsafe" {
+			outf.WriteString("import _ \"unsafe\"\n")
+			haveunsafe = true
+			continue
+		}
+		if imp == s.utilsPkg() {
+
+			outf.WriteString(fmt.Sprintf("import . \"%s\"\n", iprefix(imp)))
+			continue
+		}
+		outf.WriteString(fmt.Sprintf("import \"%s\"\n", iprefix(imp)))
+	}
+	outf.WriteString("\n")
+	if s.ForceStackGrowth && haveunsafe {
+		outf.WriteString("// Hack: reach into runtime to grab this testing hook.\n")
+		outf.WriteString("//go:linkname hackStack runtime.gcTestMoveStackOnNextCall\n")
+		outf.WriteString("func hackStack()\n\n")
+	}
+	return outf
+}
+
+type miscVals struct {
+	NumTpk   int
+	MaxFail  int
+	NumTests int
+}
+
+const utilsTemplate = `
+
+import (
+  "fmt"
+  "os"
+)
+
+type UtilsType int
+var ParamFailCount [{{.NumTpk}}]int
+var ReturnFailCount [{{.NumTpk}}]int
+var FailCount [{{.NumTpk}}]int
+var Mode [{{.NumTpk}}]string
+
+//go:noinline
+func NoteFailure(cm int, pidx int, fidx int, pkg string, pref string, parmNo int, isret bool, _ uint64) {
+	if isret {
+		if ParamFailCount[pidx] != 0 {
+			return
+		}
+		ReturnFailCount[pidx]++
+	} else {
+		ParamFailCount[pidx]++
+	}
+	fmt.Fprintf(os.Stderr, "Error: fail %s |%d|%d|%d| =%s.Test%d= %s %d\n", Mode, cm, pidx, fidx, pkg, fidx, pref, parmNo)
+
+	if ParamFailCount[pidx]+FailCount[pidx]+ReturnFailCount[pidx] > {{.MaxFail}} {
+		os.Exit(1)
+	}
+}
+
+//go:noinline
+func NoteFailureElem(cm int, pidx int, fidx int, pkg string, pref string, parmNo int, elem int, isret bool, _ uint64) {
+
+	if isret {
+		if ParamFailCount[pidx] != 0 {
+			return
+		}
+		ReturnFailCount[pidx]++
+	} else {
+		ParamFailCount[pidx]++
+	}
+	fmt.Fprintf(os.Stderr, "Error: fail %s |%d|%d|%d| =%s.Test%d= %s %d elem %d\n", Mode, cm, pidx, fidx, pkg, fidx, pref, parmNo, elem)
+
+	if ParamFailCount[pidx]+FailCount[pidx]+ReturnFailCount[pidx] > {{.MaxFail}} {
+		os.Exit(1)
+	}
+}
+
+func BeginFcn(p int) {
+	ParamFailCount[p] = 0
+	ReturnFailCount[p] = 0
+}
+
+func EndFcn(p int) {
+	FailCount[p] += ParamFailCount[p]
+	FailCount[p] += ReturnFailCount[p]
+}
+`
+
+func (s *genstate) emitUtils(outf *os.File, maxfail int, numtpk int) {
+	vals := miscVals{
+		NumTpk:  numtpk,
+		MaxFail: maxfail,
+	}
+	t := template.Must(template.New("utils").Parse(utilsTemplate))
+	err := t.Execute(outf, vals)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+const mainPreamble = `
+
+import (
+	"fmt"
+	"os"
+)
+
+func main() {
+  fmt.Fprintf(os.Stderr, "starting main\n")
+`
+
+func (s *genstate) emitMain(outf *os.File, numit int, fcnmask map[int]int, pkmask map[int]int) {
+	fmt.Fprintf(outf, "%s", mainPreamble)
+	fmt.Fprintf(outf, "  pch := make(chan bool, %d)\n", s.NumTestPackages)
+	for k := 0; k < s.NumTestPackages; k++ {
+		cp := fmt.Sprintf("%s%s%d", s.Tag, CallerName, k)
+		fmt.Fprintf(outf, "  go func(ch chan bool) {\n")
+		for i := 0; i < numit; i++ {
+			if shouldEmitFP(i, k, fcnmask, pkmask) {
+				fmt.Fprintf(outf, "    %s.%s%d(\"normal\")\n", cp, CallerName, i)
+				if s.tunables.doReflectCall {
+					fmt.Fprintf(outf, "    %s.%s%d(\"reflect\")\n", cp, CallerName, i)
+				}
+			}
+		}
+		fmt.Fprintf(outf, "    pch <- true\n")
+		fmt.Fprintf(outf, "  }(pch)\n")
+	}
+	fmt.Fprintf(outf, "  for pidx := 0; pidx < %d; pidx++ {\n", s.NumTestPackages)
+	fmt.Fprintf(outf, "    _ = <- pch\n")
+	fmt.Fprintf(outf, "  }\n")
+	fmt.Fprintf(outf, "  tf := 0\n")
+	fmt.Fprintf(outf, "  for pidx := 0; pidx < %d; pidx++ {\n", s.NumTestPackages)
+	fmt.Fprintf(outf, "    tf += FailCount[pidx]\n")
+	fmt.Fprintf(outf, "  }\n")
+	fmt.Fprintf(outf, "  if tf != 0 {\n")
+	fmt.Fprintf(outf, "    fmt.Fprintf(os.Stderr, \"FAILURES: %%d\\n\", tf)\n")
+	fmt.Fprintf(outf, "    os.Exit(2)\n")
+	fmt.Fprintf(outf, "  }\n")
+	fmt.Fprintf(outf, "  fmt.Fprintf(os.Stderr, \"finished %d tests\\n\")\n", numit*s.NumTestPackages)
+	fmt.Fprintf(outf, "}\n")
+}
+
+func makeDir(d string) {
+	fi, err := os.Stat(d)
+	if err == nil && fi.IsDir() {
+		return
+	}
+	verb(1, "creating %s", d)
+	if err := os.Mkdir(d, 0777); err != nil {
+		log.Fatal(err)
+	}
+}
+
+func (s *genstate) callerPkg(which int) string {
+	return s.Tag + CallerName + strconv.Itoa(which)
+}
+
+func (s *genstate) callerFile(which int) string {
+	cp := s.callerPkg(which)
+	return filepath.Join(s.OutDir, cp, cp+".go")
+}
+
+func (s *genstate) checkerPkg(which int) string {
+	return s.Tag + CheckerName + strconv.Itoa(which)
+}
+
+func (s *genstate) checkerFile(which int) string {
+	cp := s.checkerPkg(which)
+	return filepath.Join(s.OutDir, cp, cp+".go")
+}
+
+func (s *genstate) utilsPkg() string {
+	return s.Tag + "Utils"
+}
+
+func (s *genstate) beginPackage(pkidx int) {
+	s.pkidx = pkidx
+	s.derefFuncs = make(map[string]string)
+	s.assignFuncs = make(map[string]string)
+	s.allocFuncs = make(map[string]string)
+	s.globVars = make(map[string]string)
+	s.genvalFuncs = make(map[string]string)
+}
+
+func runImports(files []string) {
+	verb(1, "... running goimports")
+	args := make([]string, 0, len(files)+1)
+	args = append(args, "-w")
+	args = append(args, files...)
+	cmd := exec.Command("goimports", args...)
+	coutput, cerr := cmd.CombinedOutput()
+	if cerr != nil {
+		log.Fatalf("goimports command failed: %s", string(coutput))
+	}
+	verb(1, "... goimports run complete")
+}
+
+// shouldEmitFP returns true if we should actually emit code for the function
+// with the specified package + fcn indices. For "regular" runs, fcnmask and pkmask
+// will be empty, meaning we want to emit every function in every package. The
+// fuzz-runner program also tries to do testcase "minimization", which means that it
+// will try to whittle down the set of packages and functions (by running the generator
+// using the fcnmask and pkmask options) to emit only specific packages or functions.
+func shouldEmitFP(fn int, pk int, fcnmask map[int]int, pkmask map[int]int) bool {
+	emitpk := true
+	emitfn := true
+	if len(pkmask) != 0 {
+		emitpk = false
+		if _, ok := pkmask[pk]; ok {
+			emitpk = true
+		}
+	}
+	if len(fcnmask) != 0 {
+		emitfn = false
+		if _, ok := fcnmask[fn]; ok {
+			emitfn = true
+		}
+	}
+	doemit := emitpk && emitfn
+	verb(2, "shouldEmitFP(F=%d,P=%d) returns %v", fn, pk, doemit)
+	return doemit
+}
+
+// Generate is the top level code generation hook for this package.
+// Emits code according to the schema in config object 'c'.
+func Generate(c GenConfig) int {
+	mainpkg := c.Tag + "Main"
+
+	var ipref string
+	if len(c.PkgPath) > 0 {
+		ipref = c.PkgPath
+	}
+
+	s := genstate{
+		GenConfig: c,
+		ipref:     ipref,
+	}
+
+	if s.OutDir != "." {
+		verb(1, "creating %s", s.OutDir)
+		makeDir(s.OutDir)
+	}
+
+	mainimports := []string{}
+	for i := 0; i < s.NumTestPackages; i++ {
+		if shouldEmitFP(-1, i, nil, s.PkgMask) {
+			makeDir(s.OutDir + "/" + s.callerPkg(i))
+			makeDir(s.OutDir + "/" + s.checkerPkg(i))
+			makeDir(s.OutDir + "/" + s.utilsPkg())
+			mainimports = append(mainimports, s.callerPkg(i))
+		}
+	}
+	mainimports = append(mainimports, s.utilsPkg())
+
+	// Emit utils package.
+	verb(1, "emit utils")
+	utilsfile := s.OutDir + "/" + s.utilsPkg() + "/" + s.utilsPkg() + ".go"
+	utilsoutfile := s.openOutputFile(utilsfile, s.utilsPkg(), []string{}, "")
+	s.emitUtils(utilsoutfile, s.MaxFail, s.NumTestPackages)
+	utilsoutfile.Close()
+
+	mainfile := s.OutDir + "/" + mainpkg + ".go"
+	mainoutfile := s.openOutputFile(mainfile, "main", mainimports, ipref)
+
+	allfiles := []string{mainfile, utilsfile}
+	for k := 0; k < s.NumTestPackages; k++ {
+		callerImports := []string{s.checkerPkg(k), s.utilsPkg()}
+		checkerImports := []string{s.utilsPkg()}
+		if tunables.doReflectCall {
+			callerImports = append(callerImports, "reflect")
+		}
+		if s.ForceStackGrowth {
+			callerImports = append(callerImports, "unsafe")
+			checkerImports = append(checkerImports, "unsafe")
+		}
+		var calleroutfile, checkeroutfile *os.File
+		if shouldEmitFP(-1, k, nil, s.PkgMask) {
+			calleroutfile = s.openOutputFile(s.callerFile(k), s.callerPkg(k),
+				callerImports, ipref)
+			checkeroutfile = s.openOutputFile(s.checkerFile(k), s.checkerPkg(k),
+				checkerImports, ipref)
+			allfiles = append(allfiles, s.callerFile(k), s.checkerFile(k))
+		}
+
+		s.beginPackage(k)
+
+		var b bytes.Buffer
+		for i := 0; i < s.NumTestFunctions; i++ {
+			doemit := shouldEmitFP(i, k, s.FcnMask, s.PkgMask)
+			s.Seed = s.GenPair(calleroutfile, checkeroutfile, i, k,
+				&b, s.Seed, doemit)
+		}
+
+		// When minimization is in effect, we sometimes wind
+		// up eliminating all refs to the utils package. Add a
+		// dummy to help with this.
+		fmt.Fprintf(calleroutfile, "\n// dummy\nvar Dummy UtilsType\n")
+		fmt.Fprintf(checkeroutfile, "\n// dummy\nvar Dummy UtilsType\n")
+		calleroutfile.Close()
+		checkeroutfile.Close()
+	}
+	s.emitMain(mainoutfile, s.NumTestFunctions, s.FcnMask, s.PkgMask)
+
+	// emit go.mod
+	verb(1, "opening go.mod")
+	fn := s.OutDir + "/go.mod"
+	outf, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
+	if err != nil {
+		log.Fatal(err)
+	}
+	outf.WriteString(fmt.Sprintf("module %s\n\ngo 1.17\n", s.PkgPath))
+	outf.Close()
+
+	verb(1, "closing files")
+	mainoutfile.Close()
+
+	if s.errs == 0 && s.RunGoImports {
+		runImports(allfiles)
+	}
+
+	return s.errs
+}
diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/mapparm.go b/cmd/signature-fuzzer/internal/fuzz-generator/mapparm.go
new file mode 100644
index 0000000..9626475
--- /dev/null
+++ b/cmd/signature-fuzzer/internal/fuzz-generator/mapparm.go
@@ -0,0 +1,91 @@
+// Copyright 2021 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 generator
+
+import (
+	"bytes"
+	"fmt"
+)
+
+// mapparm describes a parameter of map type; it implements the
+// "parm" interface.
+type mapparm struct {
+	aname   string
+	qname   string
+	keytype parm
+	valtype parm
+	keytmp  string
+	isBlank
+	addrTakenHow
+	isGenValFunc
+	skipCompare
+}
+
+func (p mapparm) IsControl() bool {
+	return false
+}
+
+func (p mapparm) TypeName() string {
+	return p.aname
+}
+
+func (p mapparm) QualName() string {
+	return p.qname
+}
+
+func (p mapparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) {
+	n := p.aname
+	if caller {
+		n = p.qname
+	}
+	b.WriteString(fmt.Sprintf("%s %s%s", prefix, n, suffix))
+}
+
+func (p mapparm) String() string {
+	return fmt.Sprintf("%s map[%s]%s", p.aname,
+		p.keytype.String(), p.valtype.String())
+}
+
+func (p mapparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) {
+	var buf bytes.Buffer
+
+	verb(5, "mapparm.GenValue(%d)", value)
+
+	n := p.aname
+	if caller {
+		n = p.qname
+	}
+	buf.WriteString(fmt.Sprintf("%s{", n))
+	buf.WriteString(p.keytmp + ": ")
+
+	var valstr string
+	valstr, value = s.GenValue(f, p.valtype, value, caller)
+	buf.WriteString(valstr + "}")
+	return buf.String(), value
+}
+
+func (p mapparm) GenElemRef(elidx int, path string) (string, parm) {
+	vne := p.valtype.NumElements()
+	verb(4, "begin GenElemRef(%d,%s) on %s %d", elidx, path, p.String(), vne)
+
+	ppath := fmt.Sprintf("%s[mkt.%s]", path, p.keytmp)
+
+	// otherwise dig into the value
+	verb(4, "recur GenElemRef(%d,...)", elidx)
+
+	// Otherwise our victim is somewhere inside the value
+	if p.IsBlank() {
+		ppath = "_"
+	}
+	return p.valtype.GenElemRef(elidx, ppath)
+}
+
+func (p mapparm) NumElements() int {
+	return p.valtype.NumElements()
+}
+
+func (p mapparm) HasPointer() bool {
+	return true
+}
diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/numparm.go b/cmd/signature-fuzzer/internal/fuzz-generator/numparm.go
new file mode 100644
index 0000000..6be0d91
--- /dev/null
+++ b/cmd/signature-fuzzer/internal/fuzz-generator/numparm.go
@@ -0,0 +1,144 @@
+// Copyright 2021 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 generator
+
+import (
+	"bytes"
+	"fmt"
+	"math"
+)
+
+// numparm describes a numeric parameter type; it implements the
+// "parm" interface.
+type numparm struct {
+	tag         string
+	widthInBits uint32
+	ctl         bool
+	isBlank
+	addrTakenHow
+	isGenValFunc
+	skipCompare
+}
+
+var f32parm *numparm = &numparm{
+	tag:         "float",
+	widthInBits: uint32(32),
+	ctl:         false,
+}
+var f64parm *numparm = &numparm{
+	tag:         "float",
+	widthInBits: uint32(64),
+	ctl:         false,
+}
+
+func (p numparm) TypeName() string {
+	if p.tag == "byte" {
+		return "byte"
+	}
+	return fmt.Sprintf("%s%d", p.tag, p.widthInBits)
+}
+
+func (p numparm) QualName() string {
+	return p.TypeName()
+}
+
+func (p numparm) String() string {
+	if p.tag == "byte" {
+		return "byte"
+	}
+	ctl := ""
+	if p.ctl {
+		ctl = " [ctl=yes]"
+	}
+	return fmt.Sprintf("%s%s", p.TypeName(), ctl)
+}
+
+func (p numparm) NumElements() int {
+	return 1
+}
+
+func (p numparm) IsControl() bool {
+	return p.ctl
+}
+
+func (p numparm) GenElemRef(elidx int, path string) (string, parm) {
+	return path, &p
+}
+
+func (p numparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) {
+	t := fmt.Sprintf("%s%d%s", p.tag, p.widthInBits, suffix)
+	if p.tag == "byte" {
+		t = fmt.Sprintf("%s%s", p.tag, suffix)
+	}
+	b.WriteString(prefix + " " + t)
+}
+
+func (p numparm) genRandNum(s *genstate, value int) (string, int) {
+	which := uint8(s.wr.Intn(int64(100)))
+	if p.tag == "int" {
+		var v int64
+		if which < 3 {
+			// max
+			v = (1 << (p.widthInBits - 1)) - 1
+
+		} else if which < 5 {
+			// min
+			v = (-1 << (p.widthInBits - 1))
+		} else {
+			nrange := int64(1 << (p.widthInBits - 2))
+			v = s.wr.Intn(nrange)
+			if value%2 != 0 {
+				v = -v
+			}
+		}
+		return fmt.Sprintf("%s%d(%d)", p.tag, p.widthInBits, v), value + 1
+	}
+	if p.tag == "uint" || p.tag == "byte" {
+		nrange := int64(1 << (p.widthInBits - 2))
+		v := s.wr.Intn(nrange)
+		if p.tag == "byte" {
+			return fmt.Sprintf("%s(%d)", p.tag, v), value + 1
+		}
+		return fmt.Sprintf("%s%d(0x%x)", p.tag, p.widthInBits, v), value + 1
+	}
+	if p.tag == "float" {
+		if p.widthInBits == 32 {
+			rf := s.wr.Float32() * (math.MaxFloat32 / 4)
+			if value%2 != 0 {
+				rf = -rf
+			}
+			return fmt.Sprintf("%s%d(%v)", p.tag, p.widthInBits, rf), value + 1
+		}
+		if p.widthInBits == 64 {
+			return fmt.Sprintf("%s%d(%v)", p.tag, p.widthInBits,
+				s.wr.NormFloat64()), value + 1
+		}
+		panic("unknown float type")
+	}
+	if p.tag == "complex" {
+		if p.widthInBits == 64 {
+			f1, v2 := f32parm.genRandNum(s, value)
+			f2, v3 := f32parm.genRandNum(s, v2)
+			return fmt.Sprintf("complex(%s,%s)", f1, f2), v3
+		}
+		if p.widthInBits == 128 {
+			f1, v2 := f64parm.genRandNum(s, value)
+			f2, v3 := f64parm.genRandNum(s, v2)
+			return fmt.Sprintf("complex(%v,%v)", f1, f2), v3
+		}
+		panic("unknown complex type")
+	}
+	panic("unknown numeric type")
+}
+
+func (p numparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) {
+	r, nv := p.genRandNum(s, value)
+	verb(5, "numparm.GenValue(%d) = %s", value, r)
+	return r, nv
+}
+
+func (p numparm) HasPointer() bool {
+	return false
+}
diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/parm.go b/cmd/signature-fuzzer/internal/fuzz-generator/parm.go
new file mode 100644
index 0000000..7ee2224
--- /dev/null
+++ b/cmd/signature-fuzzer/internal/fuzz-generator/parm.go
@@ -0,0 +1,216 @@
+// Copyright 2021 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 generator
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"sort"
+)
+
+// parm is an interface describing an abstract parameter var or return
+// var; there will be concrete types of various sorts that implement
+// this interface.
+type parm interface {
+
+	// Declare emits text containing a declaration of this param
+	// or return var into the specified buffer. Prefix is a tag to
+	// prepend before the declaration (for example a variable
+	// name) followed by a space; suffix is an arbitrary string to
+	// tack onto the end of the param's type text. Here 'caller'
+	// is set to true if we're emitting the caller part of a test
+	// pair as opposed to the checker.
+	Declare(b *bytes.Buffer, prefix string, suffix string, caller bool)
+
+	// GenElemRef returns a pair [X,Y] corresponding to a
+	// component piece of some composite parm, where X is a string
+	// forming the reference (ex: ".field" if we're picking out a
+	// struct field) and Y is a parm object corresponding to the
+	// type of the element.
+	GenElemRef(elidx int, path string) (string, parm)
+
+	// GenValue constructs a new concrete random value appropriate
+	// for the type in question and returns it, along with a
+	// sequence number indicating how many random decisions we had
+	// to make. Here "s" is the current generator state, "f" is
+	// the current function we're emitting, value is a sequence
+	// number indicating how many random decisions have been made
+	// up until this point, and 'caller' is set to true if we're
+	// emitting the caller part of a test pair as opposed to the
+	// checker.  Return value is a pair [V,I] where V is the text
+	// if the value, and I is a new sequence number reflecting any
+	// additional random choices we had to make.  For example, if
+	// the parm is something like "type Foo struct { f1 int32; f2
+	// float64 }" then we might expect GenValue to emit something
+	// like "Foo{int32(-9), float64(123.123)}".
+	GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int)
+
+	// IsControl returns true if this specific param has been marked
+	// as the single param that controls recursion for a recursive
+	// checker function. The test code doesn't check this param for a specific
+	// value, but instead returns early if it has value 0 or decrements it
+	// on a recursive call.
+	IsControl() bool
+
+	// NumElements returns the total number of discrete elements contained
+	// in this parm. For non-composite types, this will always be 1.
+	NumElements() int
+
+	// String returns a descriptive string for this parm.
+	String() string
+
+	// TypeName returns the non-qualified type name for this parm.
+	TypeName() string
+
+	// QualName returns a package-qualified type name for this parm.
+	QualName() string
+
+	// HasPointer returns true if this parm is of pointer type, or
+	// if it is a composite that has a pointer element somewhere inside.
+	// Strings and slices return true for this hook.
+	HasPointer() bool
+
+	// IsBlank() returns true if the name of this parm is "_" (that is,
+	// if we randomly chose to make it a blank). SetBlank() is used
+	// to set the 'blank' property for this parm.
+	IsBlank() bool
+	SetBlank(v bool)
+
+	// AddrTaken() return a token indicating whether this parm should
+	// be address taken or not, the nature of the address-taken-ness (see
+	// below at the def of addrTakenHow). SetAddrTaken is used to set
+	// the address taken property of the parm.
+	AddrTaken() addrTakenHow
+	SetAddrTaken(val addrTakenHow)
+
+	// IsGenVal() returns true if the values of this type should
+	// be obtained by calling a helper func, as opposed to
+	// emitting code inline (as one would for things like numeric
+	// types). SetIsGenVal is used to set the gen-val property of
+	// the parm.
+	IsGenVal() bool
+	SetIsGenVal(val bool)
+
+	// SkipCompare() returns true if we've randomly decided that
+	// we don't want to compare the value for this param or
+	// return.  SetSkipCompare is used to set the skip-compare
+	// property of the parm.
+	SkipCompare() skipCompare
+	SetSkipCompare(val skipCompare)
+}
+
+type addrTakenHow uint8
+
+const (
+	// Param not address taken.
+	notAddrTaken addrTakenHow = 0
+
+	// Param address is taken and used for simple reads/writes.
+	addrTakenSimple addrTakenHow = 1
+
+	// Param address is taken and passed to a well-behaved function.
+	addrTakenPassed addrTakenHow = 2
+
+	// Param address is taken and stored to a global var.
+	addrTakenHeap addrTakenHow = 3
+)
+
+func (a *addrTakenHow) AddrTaken() addrTakenHow {
+	return *a
+}
+
+func (a *addrTakenHow) SetAddrTaken(val addrTakenHow) {
+	*a = val
+}
+
+type isBlank bool
+
+func (b *isBlank) IsBlank() bool {
+	return bool(*b)
+}
+
+func (b *isBlank) SetBlank(val bool) {
+	*b = isBlank(val)
+}
+
+type isGenValFunc bool
+
+func (g *isGenValFunc) IsGenVal() bool {
+	return bool(*g)
+}
+
+func (g *isGenValFunc) SetIsGenVal(val bool) {
+	*g = isGenValFunc(val)
+}
+
+type skipCompare int
+
+const (
+	// Param not address taken.
+	SkipAll     = -1
+	SkipNone    = 0
+	SkipPayload = 1
+)
+
+func (s *skipCompare) SkipCompare() skipCompare {
+	return skipCompare(*s)
+}
+
+func (s *skipCompare) SetSkipCompare(val skipCompare) {
+	*s = skipCompare(val)
+}
+
+// containedParms takes an arbitrary param 'p' and returns a slice
+// with 'p' itself plus any component parms contained within 'p'.
+func containedParms(p parm) []parm {
+	visited := make(map[string]parm)
+	worklist := []parm{p}
+
+	addToWork := func(p parm) {
+		if p == nil {
+			panic("not expected")
+		}
+		if _, ok := visited[p.TypeName()]; !ok {
+			worklist = append(worklist, p)
+		}
+	}
+
+	for len(worklist) != 0 {
+		cp := worklist[0]
+		worklist = worklist[1:]
+		if _, ok := visited[cp.TypeName()]; ok {
+			continue
+		}
+		visited[cp.TypeName()] = cp
+		switch x := cp.(type) {
+		case *mapparm:
+			addToWork(x.keytype)
+			addToWork(x.valtype)
+		case *structparm:
+			for _, fld := range x.fields {
+				addToWork(fld)
+			}
+		case *arrayparm:
+			addToWork(x.eltype)
+		case *pointerparm:
+			addToWork(x.totype)
+		case *typedefparm:
+			addToWork(x.target)
+		}
+	}
+	rv := []parm{}
+	for _, v := range visited {
+		rv = append(rv, v)
+	}
+	sort.Slice(rv, func(i, j int) bool {
+		if rv[i].TypeName() == rv[j].TypeName() {
+			fmt.Fprintf(os.Stderr, "%d %d %+v %+v %s %s\n", i, j, rv[i], rv[i].String(), rv[j], rv[j].String())
+			panic("unexpected")
+		}
+		return rv[i].TypeName() < rv[j].TypeName()
+	})
+	return rv
+}
diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/pointerparm.go b/cmd/signature-fuzzer/internal/fuzz-generator/pointerparm.go
new file mode 100644
index 0000000..1ec61e5
--- /dev/null
+++ b/cmd/signature-fuzzer/internal/fuzz-generator/pointerparm.go
@@ -0,0 +1,75 @@
+// Copyright 2021 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 generator
+
+import (
+	"bytes"
+	"fmt"
+)
+
+// pointerparm describes a parameter of pointer type; it implements the
+// "parm" interface.
+type pointerparm struct {
+	tag    string
+	totype parm
+	isBlank
+	addrTakenHow
+	isGenValFunc
+	skipCompare
+}
+
+func (p pointerparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) {
+	n := p.totype.TypeName()
+	if caller {
+		n = p.totype.QualName()
+	}
+	b.WriteString(fmt.Sprintf("%s *%s%s", prefix, n, suffix))
+}
+
+func (p pointerparm) GenElemRef(elidx int, path string) (string, parm) {
+	return path, &p
+}
+
+func (p pointerparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) {
+	pref := ""
+	if caller {
+		pref = s.checkerPkg(s.pkidx) + "."
+	}
+	var valstr string
+	valstr, value = s.GenValue(f, p.totype, value, caller)
+	fname := s.genAllocFunc(p.totype)
+	return fmt.Sprintf("%s%s(%s)", pref, fname, valstr), value
+}
+
+func (p pointerparm) IsControl() bool {
+	return false
+}
+
+func (p pointerparm) NumElements() int {
+	return 1
+}
+
+func (p pointerparm) String() string {
+	return fmt.Sprintf("*%s", p.totype)
+}
+
+func (p pointerparm) TypeName() string {
+	return fmt.Sprintf("*%s", p.totype.TypeName())
+}
+
+func (p pointerparm) QualName() string {
+	return fmt.Sprintf("*%s", p.totype.QualName())
+}
+
+func mkPointerParm(to parm) pointerparm {
+	var pp pointerparm
+	pp.tag = "pointer"
+	pp.totype = to
+	return pp
+}
+
+func (p pointerparm) HasPointer() bool {
+	return true
+}
diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/stringparm.go b/cmd/signature-fuzzer/internal/fuzz-generator/stringparm.go
new file mode 100644
index 0000000..2da541d
--- /dev/null
+++ b/cmd/signature-fuzzer/internal/fuzz-generator/stringparm.go
@@ -0,0 +1,64 @@
+// Copyright 2021 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 generator
+
+import (
+	"bytes"
+)
+
+// stringparm describes a parameter of string type; it implements the
+// "parm" interface
+type stringparm struct {
+	tag string
+	isBlank
+	addrTakenHow
+	isGenValFunc
+	skipCompare
+}
+
+func (p stringparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) {
+	b.WriteString(prefix + " string" + suffix)
+}
+
+func (p stringparm) GenElemRef(elidx int, path string) (string, parm) {
+	return path, &p
+}
+
+var letters = []rune("�꿦3򂨃f6ꂅ8ˋ<􂊇񊶿(z̽|ϣᇊ񁗇򟄼q񧲥筁{ЂƜĽ")
+
+func (p stringparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) {
+	ns := len(letters) - 9
+	nel := int(s.wr.Intn(8))
+	st := int(s.wr.Intn(int64(ns)))
+	en := st + nel
+	if en > ns {
+		en = ns
+	}
+	return "\"" + string(letters[st:en]) + "\"", value + 1
+}
+
+func (p stringparm) IsControl() bool {
+	return false
+}
+
+func (p stringparm) NumElements() int {
+	return 1
+}
+
+func (p stringparm) String() string {
+	return "string"
+}
+
+func (p stringparm) TypeName() string {
+	return "string"
+}
+
+func (p stringparm) QualName() string {
+	return "string"
+}
+
+func (p stringparm) HasPointer() bool {
+	return false
+}
diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/structparm.go b/cmd/signature-fuzzer/internal/fuzz-generator/structparm.go
new file mode 100644
index 0000000..df90107
--- /dev/null
+++ b/cmd/signature-fuzzer/internal/fuzz-generator/structparm.go
@@ -0,0 +1,163 @@
+// Copyright 2021 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 generator
+
+import (
+	"bytes"
+	"fmt"
+	"strings"
+)
+
+// structparm describes a parameter of struct type; it implements the
+// "parm" interface.
+type structparm struct {
+	sname  string
+	qname  string
+	fields []parm
+	isBlank
+	addrTakenHow
+	isGenValFunc
+	skipCompare
+}
+
+func (p structparm) TypeName() string {
+	return p.sname
+}
+
+func (p structparm) QualName() string {
+	return p.qname
+}
+
+func (p structparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) {
+	n := p.sname
+	if caller {
+		n = p.qname
+	}
+	b.WriteString(fmt.Sprintf("%s %s%s", prefix, n, suffix))
+}
+
+func (p structparm) FieldName(i int) string {
+	if p.fields[i].IsBlank() {
+		return "_"
+	}
+	return fmt.Sprintf("F%d", i)
+}
+
+func (p structparm) String() string {
+	var buf bytes.Buffer
+
+	buf.WriteString(fmt.Sprintf("struct %s {\n", p.sname))
+	for fi, f := range p.fields {
+		buf.WriteString(fmt.Sprintf("%s %s\n", p.FieldName(fi), f.String()))
+	}
+	buf.WriteString("}")
+	return buf.String()
+}
+
+func (p structparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) {
+	var buf bytes.Buffer
+
+	verb(5, "structparm.GenValue(%d)", value)
+
+	n := p.sname
+	if caller {
+		n = p.qname
+	}
+	buf.WriteString(fmt.Sprintf("%s{", n))
+	nbfi := 0
+	for fi, fld := range p.fields {
+		var valstr string
+		valstr, value = s.GenValue(f, fld, value, caller)
+		if p.fields[fi].IsBlank() {
+			buf.WriteString("/* ")
+			valstr = strings.ReplaceAll(valstr, "/*", "[[")
+			valstr = strings.ReplaceAll(valstr, "*/", "]]")
+		} else {
+			writeCom(&buf, nbfi)
+		}
+		buf.WriteString(p.FieldName(fi) + ": ")
+		buf.WriteString(valstr)
+		if p.fields[fi].IsBlank() {
+			buf.WriteString(" */")
+		} else {
+			nbfi++
+		}
+	}
+	buf.WriteString("}")
+	return buf.String(), value
+}
+
+func (p structparm) IsControl() bool {
+	return false
+}
+
+func (p structparm) NumElements() int {
+	ne := 0
+	for _, f := range p.fields {
+		ne += f.NumElements()
+	}
+	return ne
+}
+
+func (p structparm) GenElemRef(elidx int, path string) (string, parm) {
+	ct := 0
+	verb(4, "begin GenElemRef(%d,%s) on %s", elidx, path, p.String())
+
+	for fi, f := range p.fields {
+		fne := f.NumElements()
+
+		//verb(4, "+ examining field %d fne %d ct %d", fi, fne, ct)
+
+		// Empty field. Continue on.
+		if elidx == ct && fne == 0 {
+			continue
+		}
+
+		// Is this field the element we're interested in?
+		if fne == 1 && elidx == ct {
+
+			// The field in question may be a composite that has only
+			// multiple elements but a single non-zero-sized element.
+			// If this is the case, keep going.
+			if sp, ok := f.(*structparm); ok {
+				if len(sp.fields) > 1 {
+					ppath := fmt.Sprintf("%s.F%d", path, fi)
+					if p.fields[fi].IsBlank() || path == "_" {
+						ppath = "_"
+					}
+					return f.GenElemRef(elidx-ct, ppath)
+				}
+			}
+
+			verb(4, "found field %d type %s in GenElemRef(%d,%s)", fi, f.TypeName(), elidx, path)
+			ppath := fmt.Sprintf("%s.F%d", path, fi)
+			if p.fields[fi].IsBlank() || path == "_" {
+				ppath = "_"
+			}
+			return ppath, f
+		}
+
+		// Is the element we want somewhere inside this field?
+		if fne > 1 && elidx >= ct && elidx < ct+fne {
+			ppath := fmt.Sprintf("%s.F%d", path, fi)
+			if p.fields[fi].IsBlank() || path == "_" {
+				ppath = "_"
+			}
+			return f.GenElemRef(elidx-ct, ppath)
+		}
+
+		ct += fne
+	}
+	panic(fmt.Sprintf("GenElemRef failed for struct %s elidx %d", p.TypeName(), elidx))
+}
+
+func (p structparm) HasPointer() bool {
+	for _, f := range p.fields {
+		if f.HasPointer() {
+			return true
+		}
+	}
+	return false
+}
diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/typedefparm.go b/cmd/signature-fuzzer/internal/fuzz-generator/typedefparm.go
new file mode 100644
index 0000000..27cea64
--- /dev/null
+++ b/cmd/signature-fuzzer/internal/fuzz-generator/typedefparm.go
@@ -0,0 +1,90 @@
+// Copyright 2021 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 generator
+
+import (
+	"bytes"
+	"fmt"
+)
+
+// typedefparm describes a parameter that is a typedef of some other
+// type; it implements the "parm" interface
+type typedefparm struct {
+	aname  string
+	qname  string
+	target parm
+	isBlank
+	addrTakenHow
+	isGenValFunc
+	skipCompare
+}
+
+func (p typedefparm) Declare(b *bytes.Buffer, prefix string, suffix string, caller bool) {
+	n := p.aname
+	if caller {
+		n = p.qname
+	}
+	b.WriteString(fmt.Sprintf("%s %s%s", prefix, n, suffix))
+}
+
+func (p typedefparm) GenElemRef(elidx int, path string) (string, parm) {
+	_, isarr := p.target.(*arrayparm)
+	_, isstruct := p.target.(*structparm)
+	_, ismap := p.target.(*mapparm)
+	rv, rp := p.target.GenElemRef(elidx, path)
+	// this is hacky, but I don't see a nicer way to do this
+	if isarr || isstruct || ismap {
+		return rv, rp
+	}
+	rp = &p
+	return rv, rp
+}
+
+func (p typedefparm) GenValue(s *genstate, f *funcdef, value int, caller bool) (string, int) {
+	n := p.aname
+	if caller {
+		n = p.qname
+	}
+	rv, v := s.GenValue(f, p.target, value, caller)
+	rv = n + "(" + rv + ")"
+	return rv, v
+}
+
+func (p typedefparm) IsControl() bool {
+	return false
+}
+
+func (p typedefparm) NumElements() int {
+	return p.target.NumElements()
+}
+
+func (p typedefparm) String() string {
+	return fmt.Sprintf("%s typedef of %s", p.aname, p.target.String())
+
+}
+
+func (p typedefparm) TypeName() string {
+	return p.aname
+
+}
+
+func (p typedefparm) QualName() string {
+	return p.qname
+}
+
+func (p typedefparm) HasPointer() bool {
+	return p.target.HasPointer()
+}
+
+func (s *genstate) makeTypedefParm(f *funcdef, target parm, pidx int) parm {
+	var tdp typedefparm
+	ns := len(f.typedefs)
+	tdp.aname = fmt.Sprintf("MyTypeF%dS%d", f.idx, ns)
+	tdp.qname = fmt.Sprintf("%s.MyTypeF%dS%d", s.checkerPkg(pidx), f.idx, ns)
+	tdp.target = target
+	tdp.SetBlank(uint8(s.wr.Intn(100)) < tunables.blankPerc)
+	f.typedefs = append(f.typedefs, tdp)
+	return &tdp
+}
diff --git a/cmd/signature-fuzzer/internal/fuzz-generator/wraprand.go b/cmd/signature-fuzzer/internal/fuzz-generator/wraprand.go
new file mode 100644
index 0000000..bba178d
--- /dev/null
+++ b/cmd/signature-fuzzer/internal/fuzz-generator/wraprand.go
@@ -0,0 +1,136 @@
+// Copyright 2021 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 generator
+
+import (
+	"fmt"
+	"math/rand"
+	"os"
+	"runtime"
+	"strings"
+)
+
+const (
+	RandCtlNochecks = 0
+	RandCtlChecks   = 1 << iota
+	RandCtlCapture
+	RandCtlPanic
+)
+
+func NewWrapRand(seed int64, ctl int) *wraprand {
+	rand.Seed(seed)
+	return &wraprand{seed: seed, ctl: ctl}
+}
+
+type wraprand struct {
+	f32calls  int
+	f64calls  int
+	intncalls int
+	seed      int64
+	tag       string
+	calls     []string
+	ctl       int
+}
+
+func (w *wraprand) captureCall(tag string, val string) {
+	call := tag + ": " + val + "\n"
+	pc := make([]uintptr, 10)
+	n := runtime.Callers(1, pc)
+	if n == 0 {
+		panic("why?")
+	}
+	pc = pc[:n] // pass only valid pcs to runtime.CallersFrames
+	frames := runtime.CallersFrames(pc)
+	for {
+		frame, more := frames.Next()
+		if strings.Contains(frame.File, "testing.") {
+			break
+		}
+		call += fmt.Sprintf("%s %s:%d\n", frame.Function, frame.File, frame.Line)
+		if !more {
+			break
+		}
+
+	}
+	w.calls = append(w.calls, call)
+}
+
+func (w *wraprand) Intn(n int64) int64 {
+	w.intncalls++
+	rv := rand.Int63n(n)
+	if w.ctl&RandCtlCapture != 0 {
+		w.captureCall("Intn", fmt.Sprintf("%d", rv))
+	}
+	return rv
+}
+
+func (w *wraprand) Float32() float32 {
+	w.f32calls++
+	rv := rand.Float32()
+	if w.ctl&RandCtlCapture != 0 {
+		w.captureCall("Float32", fmt.Sprintf("%f", rv))
+	}
+	return rv
+}
+
+func (w *wraprand) NormFloat64() float64 {
+	w.f64calls++
+	rv := rand.NormFloat64()
+	if w.ctl&RandCtlCapture != 0 {
+		w.captureCall("NormFloat64", fmt.Sprintf("%f", rv))
+	}
+	return rv
+}
+
+func (w *wraprand) emitCalls(fn string) {
+	outf, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
+	if err != nil {
+		panic(err)
+	}
+	for _, c := range w.calls {
+		fmt.Fprint(outf, c)
+	}
+	outf.Close()
+}
+
+func (w *wraprand) Equal(w2 *wraprand) bool {
+	return w.f32calls == w2.f32calls &&
+		w.f64calls == w2.f64calls &&
+		w.intncalls == w2.intncalls
+}
+
+func (w *wraprand) Check(w2 *wraprand) {
+	if w.ctl != 0 && !w.Equal(w2) {
+		fmt.Fprintf(os.Stderr, "wraprand consistency check failed:\n")
+		t := "w"
+		if w.tag != "" {
+			t = w.tag
+		}
+		t2 := "w2"
+		if w2.tag != "" {
+			t2 = w2.tag
+		}
+		fmt.Fprintf(os.Stderr, " %s: {f32:%d f64:%d i:%d}\n", t,
+			w.f32calls, w.f64calls, w.intncalls)
+		fmt.Fprintf(os.Stderr, " %s: {f32:%d f64:%d i:%d}\n", t2,
+			w2.f32calls, w2.f64calls, w2.intncalls)
+		if w.ctl&RandCtlCapture != 0 {
+			f := fmt.Sprintf("/tmp/%s.txt", t)
+			f2 := fmt.Sprintf("/tmp/%s.txt", t2)
+			w.emitCalls(f)
+			w2.emitCalls(f2)
+			fmt.Fprintf(os.Stderr, "=-= emitted calls to %s, %s\n", f, f2)
+		}
+		if w.ctl&RandCtlPanic != 0 {
+			panic("bad")
+		}
+	}
+}
+
+func (w *wraprand) Checkpoint(tag string) {
+	if w.ctl&RandCtlCapture != 0 {
+		w.calls = append(w.calls, "=-=\n"+tag+"\n=-=\n")
+	}
+}
diff --git a/go/analysis/passes/composite/testdata/src/a/a_fuzz_test.go b/go/analysis/passes/composite/testdata/src/a/a_fuzz_test.go
new file mode 100644
index 0000000..20b652e
--- /dev/null
+++ b/go/analysis/passes/composite/testdata/src/a/a_fuzz_test.go
@@ -0,0 +1,16 @@
+// 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 go1.18
+// +build go1.18
+
+package a
+
+import "testing"
+
+var fuzzTargets = []testing.InternalFuzzTarget{
+	{"Fuzz", Fuzz},
+}
+
+func Fuzz(f *testing.F) {}
diff --git a/go/analysis/passes/composite/whitelist.go b/go/analysis/passes/composite/whitelist.go
index 1e5f5fd..f84c187 100644
--- a/go/analysis/passes/composite/whitelist.go
+++ b/go/analysis/passes/composite/whitelist.go
@@ -26,9 +26,10 @@
 	"unicode.Range16": true,
 	"unicode.Range32": true,
 
-	// These three structs are used in generated test main files,
+	// These four structs are used in generated test main files,
 	// but the generator can be trusted.
-	"testing.InternalBenchmark": true,
-	"testing.InternalExample":   true,
-	"testing.InternalTest":      true,
+	"testing.InternalBenchmark":  true,
+	"testing.InternalExample":    true,
+	"testing.InternalTest":       true,
+	"testing.InternalFuzzTarget": true,
 }
diff --git a/go/analysis/passes/tests/testdata/src/a/go118_test.go b/go/analysis/passes/tests/testdata/src/a/go118_test.go
index 059bd08..c00e71d 100644
--- a/go/analysis/passes/tests/testdata/src/a/go118_test.go
+++ b/go/analysis/passes/tests/testdata/src/a/go118_test.go
@@ -20,6 +20,11 @@
 }
 
 func FuzzFuncWithArgs(f *testing.F) {
+	f.Add()                                        // want `wrong number of values in call to \(\*testing.F\)\.Add: 0, fuzz target expects 2`
+	f.Add(1, 2, 3, 4)                              // want `wrong number of values in call to \(\*testing.F\)\.Add: 4, fuzz target expects 2`
+	f.Add(5, 5)                                    // want `mismatched type in call to \(\*testing.F\)\.Add: int, fuzz target expects \[\]byte`
+	f.Add([]byte("hello"), 5)                      // want `mismatched types in call to \(\*testing.F\)\.Add: \[\[\]byte int\], fuzz target expects \[int \[\]byte\]`
+	f.Add(5, []byte("hello"))                      // OK
 	f.Fuzz(func(t *testing.T, i int, b []byte) {}) // OK "arguments in func are allowed"
 }
 
@@ -51,6 +56,10 @@
 	f.Fuzz(func(t *testing.T, i []int) {}) // want "fuzzing arguments can only have the following types: string, bool, float32, float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, \\[\\]byte"
 }
 
+func FuzzFuncConsecutiveArgNotAllowed(f *testing.F) {
+	f.Fuzz(func(t *testing.T, i, j string, k complex64) {}) // want "fuzzing arguments can only have the following types: string, bool, float32, float64, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, \\[\\]byte"
+}
+
 func FuzzFuncInner(f *testing.F) {
 	innerFunc := func(t *testing.T, i float32) {}
 	f.Fuzz(innerFunc) // ok
diff --git a/go/analysis/passes/tests/tests.go b/go/analysis/passes/tests/tests.go
index dcc38ca..bfa55d2 100644
--- a/go/analysis/passes/tests/tests.go
+++ b/go/analysis/passes/tests/tests.go
@@ -7,6 +7,7 @@
 package tests
 
 import (
+	"fmt"
 	"go/ast"
 	"go/token"
 	"go/types"
@@ -76,13 +77,21 @@
 			// run fuzz tests diagnostics only for 1.18 i.e. when analysisinternal.DiagnoseFuzzTests is turned on.
 			if strings.HasPrefix(fn.Name.Name, "Fuzz") && analysisinternal.DiagnoseFuzzTests {
 				checkTest(pass, fn, "Fuzz")
-				checkFuzzCall(pass, fn)
+				checkFuzz(pass, fn)
 			}
 		}
 	}
 	return nil, nil
 }
 
+// Checks the contents of a fuzz function.
+func checkFuzz(pass *analysis.Pass, fn *ast.FuncDecl) {
+	params := checkFuzzCall(pass, fn)
+	if params != nil {
+		checkAddCalls(pass, fn, params)
+	}
+}
+
 // Check the arguments of f.Fuzz() calls :
 // 1. f.Fuzz() should call a function and it should be of type (*testing.F).Fuzz().
 // 2. The called function in f.Fuzz(func(){}) should not return result.
@@ -90,7 +99,8 @@
 // 4. Second argument onwards should be of type []byte, string, bool, byte,
 //	  rune, float32, float64, int, int8, int16, int32, int64, uint, uint8, uint16,
 //	  uint32, uint64
-func checkFuzzCall(pass *analysis.Pass, fn *ast.FuncDecl) {
+// Returns the list of parameters to the fuzz function, if they are valid fuzz parameters.
+func checkFuzzCall(pass *analysis.Pass, fn *ast.FuncDecl) (params *types.Tuple) {
 	ast.Inspect(fn, func(n ast.Node) bool {
 		call, ok := n.(*ast.CallExpr)
 		if ok {
@@ -122,7 +132,55 @@
 				pass.ReportRangef(expr, "fuzz target must have 1 or more argument")
 				return true
 			}
-			validateFuzzArgs(pass, tSign.Params(), expr)
+			ok := validateFuzzArgs(pass, tSign.Params(), expr)
+			if ok && params == nil {
+				params = tSign.Params()
+			}
+		}
+		return true
+	})
+	return params
+}
+
+// Check that the arguments of f.Add() calls have the same number and type of arguments as
+// the signature of the function passed to (*testing.F).Fuzz
+func checkAddCalls(pass *analysis.Pass, fn *ast.FuncDecl, params *types.Tuple) {
+	ast.Inspect(fn, func(n ast.Node) bool {
+		call, ok := n.(*ast.CallExpr)
+		if ok {
+			if !isFuzzTargetDotAdd(pass, call) {
+				return true
+			}
+
+			// The first argument to function passed to (*testing.F).Fuzz is (*testing.T).
+			if len(call.Args) != params.Len()-1 {
+				pass.ReportRangef(call, "wrong number of values in call to (*testing.F).Add: %d, fuzz target expects %d", len(call.Args), params.Len()-1)
+				return true
+			}
+			var mismatched []int
+			for i, expr := range call.Args {
+				if pass.TypesInfo.Types[expr].Type == nil {
+					return true
+				}
+				t := pass.TypesInfo.Types[expr].Type
+				if !types.Identical(t, params.At(i+1).Type()) {
+					mismatched = append(mismatched, i)
+				}
+			}
+			// If just one of the types is mismatched report for that
+			// type only. Otherwise report for the whole call to (*testing.F).Add
+			if len(mismatched) == 1 {
+				i := mismatched[0]
+				expr := call.Args[i]
+				t := pass.TypesInfo.Types[expr].Type
+				pass.ReportRangef(expr, fmt.Sprintf("mismatched type in call to (*testing.F).Add: %v, fuzz target expects %v", t, params.At(i+1).Type()))
+			} else if len(mismatched) > 1 {
+				var gotArgs, wantArgs []types.Type
+				for i := 0; i < len(call.Args); i++ {
+					gotArgs, wantArgs = append(gotArgs, pass.TypesInfo.Types[call.Args[i]].Type), append(wantArgs, params.At(i+1).Type())
+				}
+				pass.ReportRangef(call, fmt.Sprintf("mismatched types in call to (*testing.F).Add: %v, fuzz target expects %v", gotArgs, wantArgs))
+			}
 		}
 		return true
 	})
@@ -130,11 +188,21 @@
 
 // isFuzzTargetDotFuzz reports whether call is (*testing.F).Fuzz().
 func isFuzzTargetDotFuzz(pass *analysis.Pass, call *ast.CallExpr) bool {
+	return isFuzzTargetDot(pass, call, "Fuzz")
+}
+
+// isFuzzTargetDotAdd reports whether call is (*testing.F).Add().
+func isFuzzTargetDotAdd(pass *analysis.Pass, call *ast.CallExpr) bool {
+	return isFuzzTargetDot(pass, call, "Add")
+}
+
+// isFuzzTargetDot reports whether call is (*testing.F).<name>().
+func isFuzzTargetDot(pass *analysis.Pass, call *ast.CallExpr, name string) bool {
 	if selExpr, ok := call.Fun.(*ast.SelectorExpr); ok {
 		if !isTestingType(pass.TypesInfo.Types[selExpr.X].Type, "F") {
 			return false
 		}
-		if selExpr.Sel.Name == "Fuzz" {
+		if selExpr.Sel.Name == name {
 			return true
 		}
 	}
@@ -142,23 +210,34 @@
 }
 
 // Validate the arguments of fuzz target.
-func validateFuzzArgs(pass *analysis.Pass, params *types.Tuple, expr ast.Expr) {
+func validateFuzzArgs(pass *analysis.Pass, params *types.Tuple, expr ast.Expr) bool {
 	fLit, isFuncLit := expr.(*ast.FuncLit)
 	exprRange := expr
+	ok := true
 	if !isTestingType(params.At(0).Type(), "T") {
 		if isFuncLit {
 			exprRange = fLit.Type.Params.List[0].Type
 		}
 		pass.ReportRangef(exprRange, "the first parameter of a fuzz target must be *testing.T")
+		ok = false
 	}
 	for i := 1; i < params.Len(); i++ {
 		if !isAcceptedFuzzType(params.At(i).Type()) {
 			if isFuncLit {
-				exprRange = fLit.Type.Params.List[i].Type
+				curr := 0
+				for _, field := range fLit.Type.Params.List {
+					curr += len(field.Names)
+					if i < curr {
+						exprRange = field.Type
+						break
+					}
+				}
 			}
 			pass.ReportRangef(exprRange, "fuzzing arguments can only have the following types: "+formatAcceptedFuzzType())
+			ok = false
 		}
 	}
+	return ok
 }
 
 func isTestingType(typ types.Type, testingType string) bool {
diff --git a/go/callgraph/vta/vta.go b/go/callgraph/vta/vta.go
index 3d9811f..95fe29b 100644
--- a/go/callgraph/vta/vta.go
+++ b/go/callgraph/vta/vta.go
@@ -51,7 +51,6 @@
 // it may have. This information is then used to construct the call graph.
 // For each unresolved call site, vta uses the set of types and functions
 // reaching the node representing the call site to create a set of callees.
-
 package vta
 
 import (
diff --git a/go/internal/gcimporter/gcimporter_test.go b/go/internal/gcimporter/gcimporter_test.go
index 1d61cfc..6baab01 100644
--- a/go/internal/gcimporter/gcimporter_test.go
+++ b/go/internal/gcimporter/gcimporter_test.go
@@ -53,7 +53,7 @@
 	}
 	basename := filepath.Base(filename)
 	outname := filepath.Join(outdirname, basename[:len(basename)-2]+"o")
-	cmd := exec.Command("go", "tool", "compile", "-o", outname, filename)
+	cmd := exec.Command("go", "tool", "compile", "-p=p", "-o", outname, filename)
 	cmd.Dir = dirname
 	out, err := cmd.CombinedOutput()
 	if err != nil {
diff --git a/go/ssa/block.go b/go/ssa/block.go
new file mode 100644
index 0000000..35f3173
--- /dev/null
+++ b/go/ssa/block.go
@@ -0,0 +1,118 @@
+// 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 ssa
+
+import "fmt"
+
+// This file implements the BasicBlock type.
+
+// addEdge adds a control-flow graph edge from from to to.
+func addEdge(from, to *BasicBlock) {
+	from.Succs = append(from.Succs, to)
+	to.Preds = append(to.Preds, from)
+}
+
+// Parent returns the function that contains block b.
+func (b *BasicBlock) Parent() *Function { return b.parent }
+
+// String returns a human-readable label of this block.
+// It is not guaranteed unique within the function.
+//
+func (b *BasicBlock) String() string {
+	return fmt.Sprintf("%d", b.Index)
+}
+
+// emit appends an instruction to the current basic block.
+// If the instruction defines a Value, it is returned.
+//
+func (b *BasicBlock) emit(i Instruction) Value {
+	i.setBlock(b)
+	b.Instrs = append(b.Instrs, i)
+	v, _ := i.(Value)
+	return v
+}
+
+// predIndex returns the i such that b.Preds[i] == c or panics if
+// there is none.
+func (b *BasicBlock) predIndex(c *BasicBlock) int {
+	for i, pred := range b.Preds {
+		if pred == c {
+			return i
+		}
+	}
+	panic(fmt.Sprintf("no edge %s -> %s", c, b))
+}
+
+// hasPhi returns true if b.Instrs contains φ-nodes.
+func (b *BasicBlock) hasPhi() bool {
+	_, ok := b.Instrs[0].(*Phi)
+	return ok
+}
+
+// phis returns the prefix of b.Instrs containing all the block's φ-nodes.
+func (b *BasicBlock) phis() []Instruction {
+	for i, instr := range b.Instrs {
+		if _, ok := instr.(*Phi); !ok {
+			return b.Instrs[:i]
+		}
+	}
+	return nil // unreachable in well-formed blocks
+}
+
+// replacePred replaces all occurrences of p in b's predecessor list with q.
+// Ordinarily there should be at most one.
+//
+func (b *BasicBlock) replacePred(p, q *BasicBlock) {
+	for i, pred := range b.Preds {
+		if pred == p {
+			b.Preds[i] = q
+		}
+	}
+}
+
+// replaceSucc replaces all occurrences of p in b's successor list with q.
+// Ordinarily there should be at most one.
+//
+func (b *BasicBlock) replaceSucc(p, q *BasicBlock) {
+	for i, succ := range b.Succs {
+		if succ == p {
+			b.Succs[i] = q
+		}
+	}
+}
+
+// removePred removes all occurrences of p in b's
+// predecessor list and φ-nodes.
+// Ordinarily there should be at most one.
+//
+func (b *BasicBlock) removePred(p *BasicBlock) {
+	phis := b.phis()
+
+	// We must preserve edge order for φ-nodes.
+	j := 0
+	for i, pred := range b.Preds {
+		if pred != p {
+			b.Preds[j] = b.Preds[i]
+			// Strike out φ-edge too.
+			for _, instr := range phis {
+				phi := instr.(*Phi)
+				phi.Edges[j] = phi.Edges[i]
+			}
+			j++
+		}
+	}
+	// Nil out b.Preds[j:] and φ-edges[j:] to aid GC.
+	for i := j; i < len(b.Preds); i++ {
+		b.Preds[i] = nil
+		for _, instr := range phis {
+			instr.(*Phi).Edges[i] = nil
+		}
+	}
+	b.Preds = b.Preds[:j]
+	for _, instr := range phis {
+		phi := instr.(*Phi)
+		phi.Edges = phi.Edges[:j]
+	}
+}
diff --git a/go/ssa/builder.go b/go/ssa/builder.go
index e1540db..4d53741 100644
--- a/go/ssa/builder.go
+++ b/go/ssa/builder.go
@@ -125,7 +125,7 @@
 	// T(e) = T(e.X) = T(e.Y) after untyped constants have been
 	// eliminated.
 	// TODO(adonovan): not true; MyBool==MyBool yields UntypedBool.
-	t := fn.Pkg.typeOf(e)
+	t := fn.typeOf(e)
 
 	var short Value // value of the short-circuit path
 	switch e.Op {
@@ -180,7 +180,7 @@
 // is token.ARROW).
 //
 func (b *builder) exprN(fn *Function, e ast.Expr) Value {
-	typ := fn.Pkg.typeOf(e).(*types.Tuple)
+	typ := fn.typeOf(e).(*types.Tuple)
 	switch e := e.(type) {
 	case *ast.ParenExpr:
 		return b.exprN(fn, e.X)
@@ -195,7 +195,7 @@
 		return fn.emit(&c)
 
 	case *ast.IndexExpr:
-		mapt := fn.Pkg.typeOf(e.X).Underlying().(*types.Map)
+		mapt := fn.typeOf(e.X).Underlying().(*types.Map)
 		lookup := &Lookup{
 			X:       b.expr(fn, e.X),
 			Index:   emitConv(fn, b.expr(fn, e.Index), mapt.Key()),
@@ -293,7 +293,7 @@
 		// We must still evaluate the value, though.  (If it
 		// was side-effect free, the whole call would have
 		// been constant-folded.)
-		t := deref(fn.Pkg.typeOf(args[0])).Underlying()
+		t := deref(fn.typeOf(args[0])).Underlying()
 		if at, ok := t.(*types.Array); ok {
 			b.expr(fn, args[0]) // for effects only
 			return intConst(at.Len())
@@ -340,15 +340,17 @@
 		if isBlankIdent(e) {
 			return blank{}
 		}
-		obj := fn.Pkg.objectOf(e)
-		v := fn.Prog.packageLevelValue(obj) // var (address)
-		if v == nil {
+		obj := fn.objectOf(e)
+		var v Value
+		if g := fn.Prog.packageLevelMember(obj); g != nil {
+			v = g.(*Global) // var (address)
+		} else {
 			v = fn.lookup(obj, escaping)
 		}
 		return &address{addr: v, pos: e.Pos(), expr: e}
 
 	case *ast.CompositeLit:
-		t := deref(fn.Pkg.typeOf(e))
+		t := deref(fn.typeOf(e))
 		var v *Alloc
 		if escaping {
 			v = emitNew(fn, t, e.Lbrace)
@@ -365,7 +367,7 @@
 		return b.addr(fn, e.X, escaping)
 
 	case *ast.SelectorExpr:
-		sel, ok := fn.Pkg.info.Selections[e]
+		sel, ok := fn.info.Selections[e]
 		if !ok {
 			// qualified identifier
 			return b.addr(fn, e.Sel, escaping)
@@ -385,7 +387,7 @@
 	case *ast.IndexExpr:
 		var x Value
 		var et types.Type
-		switch t := fn.Pkg.typeOf(e.X).Underlying().(type) {
+		switch t := fn.typeOf(e.X).Underlying().(type) {
 		case *types.Array:
 			x = b.addr(fn, e.X, escaping).address(fn)
 			et = types.NewPointer(t.Elem())
@@ -513,7 +515,7 @@
 func (b *builder) expr(fn *Function, e ast.Expr) Value {
 	e = unparen(e)
 
-	tv := fn.Pkg.info.Types[e]
+	tv := fn.info.Types[e]
 
 	// Is expression a constant?
 	if tv.Value != nil {
@@ -543,12 +545,13 @@
 	case *ast.FuncLit:
 		fn2 := &Function{
 			name:      fmt.Sprintf("%s$%d", fn.Name(), 1+len(fn.AnonFuncs)),
-			Signature: fn.Pkg.typeOf(e.Type).Underlying().(*types.Signature),
+			Signature: fn.typeOf(e.Type).Underlying().(*types.Signature),
 			pos:       e.Type.Func,
 			parent:    fn,
 			Pkg:       fn.Pkg,
 			Prog:      fn.Prog,
 			syntax:    e,
+			info:      fn.info,
 		}
 		fn.AnonFuncs = append(fn.AnonFuncs, fn2)
 		b.buildFunction(fn2)
@@ -567,7 +570,7 @@
 		return emitTypeAssert(fn, b.expr(fn, e.X), tv.Type, e.Lparen)
 
 	case *ast.CallExpr:
-		if fn.Pkg.info.Types[e.Fun].IsType() {
+		if fn.info.Types[e.Fun].IsType() {
 			// Explicit type conversion, e.g. string(x) or big.Int(x)
 			x := b.expr(fn, e.Args[0])
 			y := emitConv(fn, x, tv.Type)
@@ -587,7 +590,7 @@
 		}
 		// Call to "intrinsic" built-ins, e.g. new, make, panic.
 		if id, ok := unparen(e.Fun).(*ast.Ident); ok {
-			if obj, ok := fn.Pkg.info.Uses[id].(*types.Builtin); ok {
+			if obj, ok := fn.info.Uses[id].(*types.Builtin); ok {
 				if v := b.builtin(fn, obj, e.Args, tv.Type, e.Lparen); v != nil {
 					return v
 				}
@@ -645,7 +648,7 @@
 	case *ast.SliceExpr:
 		var low, high, max Value
 		var x Value
-		switch fn.Pkg.typeOf(e.X).Underlying().(type) {
+		switch fn.typeOf(e.X).Underlying().(type) {
 		case *types.Array:
 			// Potentially escaping.
 			x = b.addr(fn, e.X, true).address(fn)
@@ -674,7 +677,7 @@
 		return fn.emit(v)
 
 	case *ast.Ident:
-		obj := fn.Pkg.info.Uses[e]
+		obj := fn.info.Uses[e]
 		// Universal built-in or nil?
 		switch obj := obj.(type) {
 		case *types.Builtin:
@@ -683,20 +686,20 @@
 			return nilConst(tv.Type)
 		}
 		// Package-level func or var?
-		if v := fn.Prog.packageLevelValue(obj); v != nil {
-			if _, ok := obj.(*types.Var); ok {
-				return emitLoad(fn, v) // var (address)
+		if v := fn.Prog.packageLevelMember(obj); v != nil {
+			if g, ok := v.(*Global); ok {
+				return emitLoad(fn, g) // var (address)
 			}
-			return v // (func)
+			return v.(*Function) // (func)
 		}
 		// Local var.
 		return emitLoad(fn, fn.lookup(obj, false)) // var (address)
 
 	case *ast.SelectorExpr:
-		sel, ok := fn.Pkg.info.Selections[e]
+		sel, ok := fn.info.Selections[e]
 		if !ok {
 			// builtin unsafe.{Add,Slice}
-			if obj, ok := fn.Pkg.info.Uses[e.Sel].(*types.Builtin); ok {
+			if obj, ok := fn.info.Uses[e.Sel].(*types.Builtin); ok {
 				return &Builtin{name: obj.Name(), sig: tv.Type.(*types.Signature)}
 			}
 			// qualified identifier
@@ -742,7 +745,7 @@
 		panic("unexpected expression-relative selector")
 
 	case *ast.IndexExpr:
-		switch t := fn.Pkg.typeOf(e.X).Underlying().(type) {
+		switch t := fn.typeOf(e.X).Underlying().(type) {
 		case *types.Array:
 			// Non-addressable array (in a register).
 			v := &Index{
@@ -755,7 +758,7 @@
 
 		case *types.Map:
 			// Maps are not addressable.
-			mapt := fn.Pkg.typeOf(e.X).Underlying().(*types.Map)
+			mapt := fn.typeOf(e.X).Underlying().(*types.Map)
 			v := &Lookup{
 				X:     b.expr(fn, e.X),
 				Index: emitConv(fn, b.expr(fn, e.Index), mapt.Key()),
@@ -810,7 +813,7 @@
 //
 func (b *builder) receiver(fn *Function, e ast.Expr, wantAddr, escaping bool, sel *types.Selection) Value {
 	var v Value
-	if wantAddr && !sel.Indirect() && !isPointer(fn.Pkg.typeOf(e)) {
+	if wantAddr && !sel.Indirect() && !isPointer(fn.typeOf(e)) {
 		v = b.addr(fn, e, escaping).address(fn)
 	} else {
 		v = b.expr(fn, e)
@@ -833,7 +836,7 @@
 
 	// Is this a method call?
 	if selector, ok := unparen(e.Fun).(*ast.SelectorExpr); ok {
-		sel, ok := fn.Pkg.info.Selections[selector]
+		sel, ok := fn.info.Selections[selector]
 		if ok && sel.Kind() == types.MethodVal {
 			obj := sel.Obj().(*types.Func)
 			recv := recvType(obj)
@@ -968,7 +971,7 @@
 	b.setCallFunc(fn, e, c)
 
 	// Then append the other actual parameters.
-	sig, _ := fn.Pkg.typeOf(e.Fun).Underlying().(*types.Signature)
+	sig, _ := fn.typeOf(e.Fun).Underlying().(*types.Signature)
 	if sig == nil {
 		panic(fmt.Sprintf("no signature for call of %s", e.Fun))
 	}
@@ -1035,7 +1038,7 @@
 		var lval lvalue = blank{}
 		if !isBlankIdent(lhs) {
 			if isDef {
-				if obj := fn.Pkg.info.Defs[lhs.(*ast.Ident)]; obj != nil {
+				if obj := fn.info.Defs[lhs.(*ast.Ident)]; obj != nil {
 					fn.addNamedLocal(obj)
 					isZero[i] = true
 				}
@@ -1103,7 +1106,7 @@
 // In that case, addr must hold a T, not a *T.
 //
 func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, isZero bool, sb *storebuf) {
-	typ := deref(fn.Pkg.typeOf(e))
+	typ := deref(fn.typeOf(e))
 	switch t := typ.Underlying().(type) {
 	case *types.Struct:
 		if !isZero && len(e.Elts) != t.NumFields() {
@@ -1402,7 +1405,7 @@
 		var ti Value // ti, ok := typeassert,ok x <Ti>
 		for _, cond := range cc.List {
 			next = fn.newBasicBlock("typeswitch.next")
-			casetype = fn.Pkg.typeOf(cond)
+			casetype = fn.typeOf(cond)
 			var condv Value
 			if casetype == tUntypedNil {
 				condv = emitCompare(fn, token.EQL, x, nilConst(x.Type()), token.NoPos)
@@ -1431,7 +1434,7 @@
 }
 
 func (b *builder) typeCaseBody(fn *Function, cc *ast.CaseClause, x Value, done *BasicBlock) {
-	if obj := fn.Pkg.info.Implicits[cc]; obj != nil {
+	if obj := fn.info.Implicits[cc]; obj != nil {
 		// In a switch y := x.(type), each case clause
 		// implicitly declares a distinct object y.
 		// In a single-type case, y has that type.
@@ -1891,10 +1894,10 @@
 func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock) {
 	var tk, tv types.Type
 	if s.Key != nil && !isBlankIdent(s.Key) {
-		tk = fn.Pkg.typeOf(s.Key)
+		tk = fn.typeOf(s.Key)
 	}
 	if s.Value != nil && !isBlankIdent(s.Value) {
-		tv = fn.Pkg.typeOf(s.Value)
+		tv = fn.typeOf(s.Value)
 	}
 
 	// If iteration variables are defined (:=), this
@@ -1997,7 +2000,7 @@
 		fn.emit(&Send{
 			Chan: b.expr(fn, s.Chan),
 			X: emitConv(fn, b.expr(fn, s.Value),
-				fn.Pkg.typeOf(s.Chan).Underlying().(*types.Chan).Elem()),
+				fn.typeOf(s.Chan).Underlying().(*types.Chan).Elem()),
 			pos: s.Arrow,
 		})
 
@@ -2224,7 +2227,7 @@
 	if isBlankIdent(id) {
 		return // discard
 	}
-	fn := pkg.values[pkg.info.Defs[id]].(*Function)
+	fn := pkg.objects[pkg.info.Defs[id]].(*Function)
 	if decl.Recv == nil && id.Name == "init" {
 		var v Call
 		v.Call.Value = fn
@@ -2324,7 +2327,7 @@
 			// 1:1 initialization: var x, y = a(), b()
 			var lval lvalue
 			if v := varinit.Lhs[0]; v.Name() != "_" {
-				lval = &address{addr: p.values[v].(*Global), pos: v.Pos()}
+				lval = &address{addr: p.objects[v].(*Global), pos: v.Pos()}
 			} else {
 				lval = blank{}
 			}
@@ -2336,7 +2339,7 @@
 				if v.Name() == "_" {
 					continue
 				}
-				emitStore(init, p.values[v].(*Global), emitExtract(init, tuple, i), v.Pos())
+				emitStore(init, p.objects[v].(*Global), emitExtract(init, tuple, i), v.Pos())
 			}
 		}
 	}
@@ -2366,23 +2369,3 @@
 		sanityCheckPackage(p)
 	}
 }
-
-// Like ObjectOf, but panics instead of returning nil.
-// Only valid during p's create and build phases.
-func (p *Package) objectOf(id *ast.Ident) types.Object {
-	if o := p.info.ObjectOf(id); o != nil {
-		return o
-	}
-	panic(fmt.Sprintf("no types.Object for ast.Ident %s @ %s",
-		id.Name, p.Prog.Fset.Position(id.Pos())))
-}
-
-// Like TypeOf, but panics instead of returning nil.
-// Only valid during p's create and build phases.
-func (p *Package) typeOf(e ast.Expr) types.Type {
-	if T := p.info.TypeOf(e); T != nil {
-		return T
-	}
-	panic(fmt.Sprintf("no type for %T @ %s",
-		e, p.Prog.Fset.Position(e.Pos())))
-}
diff --git a/go/ssa/create.go b/go/ssa/create.go
index 85163a0..69cd937 100644
--- a/go/ssa/create.go
+++ b/go/ssa/create.go
@@ -34,7 +34,6 @@
 
 	h := typeutil.MakeHasher() // protected by methodsMu, in effect
 	prog.methodSets.SetHasher(h)
-	prog.canon.SetHasher(h)
 
 	return prog
 }
@@ -66,7 +65,7 @@
 			Value:  NewConst(obj.Val(), obj.Type()),
 			pkg:    pkg,
 		}
-		pkg.values[obj] = c.Value
+		pkg.objects[obj] = c
 		pkg.Members[name] = c
 
 	case *types.Var:
@@ -77,7 +76,7 @@
 			typ:    types.NewPointer(obj.Type()), // address
 			pos:    obj.Pos(),
 		}
-		pkg.values[obj] = g
+		pkg.objects[obj] = g
 		pkg.Members[name] = g
 
 	case *types.Func:
@@ -94,12 +93,13 @@
 			pos:       obj.Pos(),
 			Pkg:       pkg,
 			Prog:      pkg.Prog,
+			info:      pkg.info,
 		}
 		if syntax == nil {
 			fn.Synthetic = "loaded from gc object file"
 		}
 
-		pkg.values[obj] = fn
+		pkg.objects[obj] = fn
 		if sig.Recv() == nil {
 			pkg.Members[name] = fn // package-level function
 		}
@@ -166,7 +166,7 @@
 	p := &Package{
 		Prog:    prog,
 		Members: make(map[string]Member),
-		values:  make(map[types.Object]Value),
+		objects: make(map[types.Object]Member),
 		Pkg:     pkg,
 		info:    info,  // transient (CREATE and BUILD phases)
 		files:   files, // transient (CREATE and BUILD phases)
@@ -179,6 +179,7 @@
 		Synthetic: "package initializer",
 		Pkg:       p,
 		Prog:      prog,
+		info:      p.info,
 	}
 	p.Members[p.init.name] = p.init
 
diff --git a/go/ssa/emit.go b/go/ssa/emit.go
index ff53705..576e024 100644
--- a/go/ssa/emit.go
+++ b/go/ssa/emit.go
@@ -50,7 +50,7 @@
 		if isBlankIdent(id) {
 			return
 		}
-		obj = f.Pkg.objectOf(id)
+		obj = f.objectOf(id)
 		switch obj.(type) {
 		case *types.Nil, *types.Const, *types.Builtin:
 			return
diff --git a/go/ssa/func.go b/go/ssa/func.go
index 0b99bc9..8fc089e 100644
--- a/go/ssa/func.go
+++ b/go/ssa/func.go
@@ -4,7 +4,7 @@
 
 package ssa
 
-// This file implements the Function and BasicBlock types.
+// This file implements the Function type.
 
 import (
 	"bytes"
@@ -17,113 +17,23 @@
 	"strings"
 )
 
-// addEdge adds a control-flow graph edge from from to to.
-func addEdge(from, to *BasicBlock) {
-	from.Succs = append(from.Succs, to)
-	to.Preds = append(to.Preds, from)
-}
-
-// Parent returns the function that contains block b.
-func (b *BasicBlock) Parent() *Function { return b.parent }
-
-// String returns a human-readable label of this block.
-// It is not guaranteed unique within the function.
-//
-func (b *BasicBlock) String() string {
-	return fmt.Sprintf("%d", b.Index)
-}
-
-// emit appends an instruction to the current basic block.
-// If the instruction defines a Value, it is returned.
-//
-func (b *BasicBlock) emit(i Instruction) Value {
-	i.setBlock(b)
-	b.Instrs = append(b.Instrs, i)
-	v, _ := i.(Value)
-	return v
-}
-
-// predIndex returns the i such that b.Preds[i] == c or panics if
-// there is none.
-func (b *BasicBlock) predIndex(c *BasicBlock) int {
-	for i, pred := range b.Preds {
-		if pred == c {
-			return i
-		}
+// Like ObjectOf, but panics instead of returning nil.
+// Only valid during f's create and build phases.
+func (f *Function) objectOf(id *ast.Ident) types.Object {
+	if o := f.info.ObjectOf(id); o != nil {
+		return o
 	}
-	panic(fmt.Sprintf("no edge %s -> %s", c, b))
+	panic(fmt.Sprintf("no types.Object for ast.Ident %s @ %s",
+		id.Name, f.Prog.Fset.Position(id.Pos())))
 }
 
-// hasPhi returns true if b.Instrs contains φ-nodes.
-func (b *BasicBlock) hasPhi() bool {
-	_, ok := b.Instrs[0].(*Phi)
-	return ok
-}
-
-// phis returns the prefix of b.Instrs containing all the block's φ-nodes.
-func (b *BasicBlock) phis() []Instruction {
-	for i, instr := range b.Instrs {
-		if _, ok := instr.(*Phi); !ok {
-			return b.Instrs[:i]
-		}
+// Like TypeOf, but panics instead of returning nil.
+// Only valid during f's create and build phases.
+func (f *Function) typeOf(e ast.Expr) types.Type {
+	if T := f.info.TypeOf(e); T != nil {
+		return T
 	}
-	return nil // unreachable in well-formed blocks
-}
-
-// replacePred replaces all occurrences of p in b's predecessor list with q.
-// Ordinarily there should be at most one.
-//
-func (b *BasicBlock) replacePred(p, q *BasicBlock) {
-	for i, pred := range b.Preds {
-		if pred == p {
-			b.Preds[i] = q
-		}
-	}
-}
-
-// replaceSucc replaces all occurrences of p in b's successor list with q.
-// Ordinarily there should be at most one.
-//
-func (b *BasicBlock) replaceSucc(p, q *BasicBlock) {
-	for i, succ := range b.Succs {
-		if succ == p {
-			b.Succs[i] = q
-		}
-	}
-}
-
-// removePred removes all occurrences of p in b's
-// predecessor list and φ-nodes.
-// Ordinarily there should be at most one.
-//
-func (b *BasicBlock) removePred(p *BasicBlock) {
-	phis := b.phis()
-
-	// We must preserve edge order for φ-nodes.
-	j := 0
-	for i, pred := range b.Preds {
-		if pred != p {
-			b.Preds[j] = b.Preds[i]
-			// Strike out φ-edge too.
-			for _, instr := range phis {
-				phi := instr.(*Phi)
-				phi.Edges[j] = phi.Edges[i]
-			}
-			j++
-		}
-	}
-	// Nil out b.Preds[j:] and φ-edges[j:] to aid GC.
-	for i := j; i < len(b.Preds); i++ {
-		b.Preds[i] = nil
-		for _, instr := range phis {
-			instr.(*Phi).Edges[i] = nil
-		}
-	}
-	b.Preds = b.Preds[:j]
-	for _, instr := range phis {
-		phi := instr.(*Phi)
-		phi.Edges = phi.Edges[:j]
-	}
+	panic(fmt.Sprintf("no type for %T @ %s", e, f.Prog.Fset.Position(e.Pos())))
 }
 
 // Destinations associated with unlabelled for/switch/select stmts.
@@ -214,7 +124,7 @@
 // syntax.  In addition it populates the f.objects mapping.
 //
 // Preconditions:
-// f.startBody() was called.
+// f.startBody() was called. f.info != nil.
 // Postcondition:
 // len(f.Params) == len(f.Signature.Params) + (f.Signature.Recv() ? 1 : 0)
 //
@@ -223,7 +133,7 @@
 	if recv != nil {
 		for _, field := range recv.List {
 			for _, n := range field.Names {
-				f.addSpilledParam(f.Pkg.info.Defs[n])
+				f.addSpilledParam(f.info.Defs[n])
 			}
 			// Anonymous receiver?  No need to spill.
 			if field.Names == nil {
@@ -237,7 +147,7 @@
 		n := len(f.Params) // 1 if has recv, 0 otherwise
 		for _, field := range functype.Params.List {
 			for _, n := range field.Names {
-				f.addSpilledParam(f.Pkg.info.Defs[n])
+				f.addSpilledParam(f.info.Defs[n])
 			}
 			// Anonymous parameter?  No need to spill.
 			if field.Names == nil {
@@ -335,7 +245,9 @@
 		lift(f)
 	}
 
+	// clear remaining stateful variables
 	f.namedResults = nil // (used by lifting)
+	f.info = nil
 
 	numberRegisters(f)
 
@@ -396,7 +308,7 @@
 }
 
 func (f *Function) addLocalForIdent(id *ast.Ident) *Alloc {
-	return f.addNamedLocal(f.Pkg.info.Defs[id])
+	return f.addNamedLocal(f.info.Defs[id])
 }
 
 // addLocal creates an anonymous local variable of type typ, adds it
@@ -502,7 +414,7 @@
 
 	// Package-level function?
 	// Prefix with package name for cross-package references only.
-	if p := f.pkg(); p != nil && p != from {
+	if p := f.relPkg(); p != nil && p != from {
 		return fmt.Sprintf("%s.%s", p.Path(), f.name)
 	}
 
@@ -530,9 +442,26 @@
 	types.WriteSignature(buf, sig, types.RelativeTo(from))
 }
 
-func (f *Function) pkg() *types.Package {
-	if f.Pkg != nil {
-		return f.Pkg.Pkg
+// declaredPackage returns the package fn is declared in or nil if the
+// function is not declared in a package.
+func (fn *Function) declaredPackage() *Package {
+	switch {
+	case fn.Pkg != nil:
+		return fn.Pkg // non-generic function
+		// generics:
+	// case fn.Origin != nil:
+	// 	return fn.Origin.pkg // instance of a named generic function
+	case fn.parent != nil:
+		return fn.parent.declaredPackage() // instance of an anonymous [generic] function
+	default:
+		return nil // function is not declared in a package, e.g. a wrapper.
+	}
+}
+
+// relPkg returns types.Package fn is printed in relationship to.
+func (fn *Function) relPkg() *types.Package {
+	if p := fn.declaredPackage(); p != nil {
+		return p.Pkg
 	}
 	return nil
 }
@@ -567,7 +496,7 @@
 		fmt.Fprintf(buf, "# Recover: %s\n", f.Recover)
 	}
 
-	from := f.pkg()
+	from := f.relPkg()
 
 	if f.FreeVars != nil {
 		buf.WriteString("# Free variables:\n")
diff --git a/go/ssa/interp/external.go b/go/ssa/interp/external.go
index 68ddee3..51b3be0 100644
--- a/go/ssa/interp/external.go
+++ b/go/ssa/interp/external.go
@@ -12,6 +12,8 @@
 	"math"
 	"os"
 	"runtime"
+	"sort"
+	"strconv"
 	"strings"
 	"time"
 	"unicode/utf8"
@@ -79,6 +81,7 @@
 		"math.Log":                        ext۰math۰Log,
 		"math.Min":                        ext۰math۰Min,
 		"math.NaN":                        ext۰math۰NaN,
+		"math.Sqrt":                       ext۰math۰Sqrt,
 		"os.Exit":                         ext۰os۰Exit,
 		"os.Getenv":                       ext۰os۰Getenv,
 		"reflect.New":                     ext۰reflect۰New,
@@ -93,10 +96,18 @@
 		"runtime.Goexit":                  ext۰runtime۰Goexit,
 		"runtime.Gosched":                 ext۰runtime۰Gosched,
 		"runtime.NumCPU":                  ext۰runtime۰NumCPU,
+		"sort.Float64s":                   ext۰sort۰Float64s,
+		"sort.Ints":                       ext۰sort۰Ints,
+		"sort.Strings":                    ext۰sort۰Strings,
+		"strconv.Atoi":                    ext۰strconv۰Atoi,
+		"strconv.Itoa":                    ext۰strconv۰Itoa,
+		"strconv.FormatFloat":             ext۰strconv۰FormatFloat,
 		"strings.Count":                   ext۰strings۰Count,
+		"strings.EqualFold":               ext۰strings۰EqualFold,
 		"strings.Index":                   ext۰strings۰Index,
 		"strings.IndexByte":               ext۰strings۰IndexByte,
 		"strings.Replace":                 ext۰strings۰Replace,
+		"strings.ToLower":                 ext۰strings۰ToLower,
 		"time.Sleep":                      ext۰time۰Sleep,
 		"unicode/utf8.DecodeRuneInString": ext۰unicode۰utf8۰DecodeRuneInString,
 	} {
@@ -179,15 +190,58 @@
 	return math.Log(args[0].(float64))
 }
 
+func ext۰math۰Sqrt(fr *frame, args []value) value {
+	return math.Sqrt(args[0].(float64))
+}
+
 func ext۰runtime۰Breakpoint(fr *frame, args []value) value {
 	runtime.Breakpoint()
 	return nil
 }
 
+func ext۰sort۰Ints(fr *frame, args []value) value {
+	x := args[0].([]value)
+	sort.Slice(x, func(i, j int) bool {
+		return x[i].(int) < x[j].(int)
+	})
+	return nil
+}
+func ext۰sort۰Strings(fr *frame, args []value) value {
+	x := args[0].([]value)
+	sort.Slice(x, func(i, j int) bool {
+		return x[i].(string) < x[j].(string)
+	})
+	return nil
+}
+func ext۰sort۰Float64s(fr *frame, args []value) value {
+	x := args[0].([]value)
+	sort.Slice(x, func(i, j int) bool {
+		return x[i].(float64) < x[j].(float64)
+	})
+	return nil
+}
+
+func ext۰strconv۰Atoi(fr *frame, args []value) value {
+	i, e := strconv.Atoi(args[0].(string))
+	if e != nil {
+		return tuple{i, iface{fr.i.runtimeErrorString, e.Error()}}
+	}
+	return tuple{i, iface{}}
+}
+func ext۰strconv۰Itoa(fr *frame, args []value) value {
+	return strconv.Itoa(args[0].(int))
+}
+func ext۰strconv۰FormatFloat(fr *frame, args []value) value {
+	return strconv.FormatFloat(args[0].(float64), args[1].(byte), args[2].(int), args[3].(int))
+}
+
 func ext۰strings۰Count(fr *frame, args []value) value {
 	return strings.Count(args[0].(string), args[1].(string))
 }
 
+func ext۰strings۰EqualFold(fr *frame, args []value) value {
+	return strings.EqualFold(args[0].(string), args[1].(string))
+}
 func ext۰strings۰IndexByte(fr *frame, args []value) value {
 	return strings.IndexByte(args[0].(string), args[1].(byte))
 }
@@ -205,6 +259,10 @@
 	return strings.Replace(s, old, new, n)
 }
 
+func ext۰strings۰ToLower(fr *frame, args []value) value {
+	return strings.ToLower(args[0].(string))
+}
+
 func ext۰runtime۰GOMAXPROCS(fr *frame, args []value) value {
 	// Ignore args[0]; don't let the interpreted program
 	// set the interpreter's GOMAXPROCS!
diff --git a/go/ssa/interp/testdata/src/fmt/fmt.go b/go/ssa/interp/testdata/src/fmt/fmt.go
index 2185eb7..af30402 100644
--- a/go/ssa/interp/testdata/src/fmt/fmt.go
+++ b/go/ssa/interp/testdata/src/fmt/fmt.go
@@ -1,14 +1,28 @@
 package fmt
 
+import (
+	"errors"
+	"strings"
+)
+
 func Sprint(args ...interface{}) string
 
-func Print(args ...interface{}) {
+func Sprintln(args ...interface{}) string {
+	return Sprint(args...) + "\n"
+}
+
+func Print(args ...interface{}) (int, error) {
+	var n int
 	for i, arg := range args {
 		if i > 0 {
 			print(" ")
+			n++
 		}
-		print(Sprint(arg))
+		msg := Sprint(arg)
+		n += len(msg)
+		print(msg)
 	}
+	return n, nil
 }
 
 func Println(args ...interface{}) {
@@ -17,10 +31,30 @@
 }
 
 // formatting is too complex to fake
+// handle the bare minimum needed for tests
 
-func Printf(args ...interface{}) string {
-	panic("Printf is not supported")
+func Printf(format string, args ...interface{}) (int, error) {
+	msg := Sprintf(format, args...)
+	print(msg)
+	return len(msg), nil
 }
+
 func Sprintf(format string, args ...interface{}) string {
-	panic("Sprintf is not supported")
+	// handle extremely simple cases that appear in tests.
+	if len(format) == 0 {
+		return ""
+	}
+	switch {
+	case strings.HasPrefix("%v", format) || strings.HasPrefix("%s", format):
+		return Sprint(args[0]) + Sprintf(format[2:], args[1:]...)
+	case !strings.HasPrefix("%", format):
+		return format[:1] + Sprintf(format[1:], args...)
+	default:
+		panic("unsupported format string for testing Sprintf")
+	}
+}
+
+func Errorf(format string, args ...interface{}) error {
+	msg := Sprintf(format, args...)
+	return errors.New(msg)
 }
diff --git a/go/ssa/interp/testdata/src/io/io.go b/go/ssa/interp/testdata/src/io/io.go
new file mode 100644
index 0000000..8cde430
--- /dev/null
+++ b/go/ssa/interp/testdata/src/io/io.go
@@ -0,0 +1,5 @@
+package io
+
+import "errors"
+
+var EOF = errors.New("EOF")
diff --git a/go/ssa/interp/testdata/src/log/log.go b/go/ssa/interp/testdata/src/log/log.go
new file mode 100644
index 0000000..8897c1d
--- /dev/null
+++ b/go/ssa/interp/testdata/src/log/log.go
@@ -0,0 +1,15 @@
+package log
+
+import (
+	"fmt"
+	"os"
+)
+
+func Println(v ...interface{}) {
+	fmt.Println(v...)
+}
+
+func Fatalln(v ...interface{}) {
+	Println(v...)
+	os.Exit(1)
+}
diff --git a/go/ssa/interp/testdata/src/math/math.go b/go/ssa/interp/testdata/src/math/math.go
index f51e5f5..64fe60c 100644
--- a/go/ssa/interp/testdata/src/math/math.go
+++ b/go/ssa/interp/testdata/src/math/math.go
@@ -11,3 +11,5 @@
 func Signbit(x float64) bool {
 	return Float64bits(x)&(1<<63) != 0
 }
+
+func Sqrt(x float64) float64
diff --git a/go/ssa/interp/testdata/src/reflect/reflect.go b/go/ssa/interp/testdata/src/reflect/reflect.go
index f6c4e27..8a23d27 100644
--- a/go/ssa/interp/testdata/src/reflect/reflect.go
+++ b/go/ssa/interp/testdata/src/reflect/reflect.go
@@ -2,6 +2,8 @@
 
 type Type interface {
 	String() string
+	Kind() Kind
+	Elem() Type
 }
 
 type Value struct {
@@ -9,8 +11,47 @@
 
 func (Value) String() string
 
+func (Value) Elem() string
+func (Value) Kind() Kind
+func (Value) Int() int64
+
 func SliceOf(Type) Type
 
 func TypeOf(interface{}) Type
 
 func ValueOf(interface{}) Value
+
+type Kind uint
+
+// Constants need to be kept in sync with the actual definitions for comparisons in tests.
+const (
+	Invalid Kind = iota
+	Bool
+	Int
+	Int8
+	Int16
+	Int32
+	Int64
+	Uint
+	Uint8
+	Uint16
+	Uint32
+	Uint64
+	Uintptr
+	Float32
+	Float64
+	Complex64
+	Complex128
+	Array
+	Chan
+	Func
+	Interface
+	Map
+	Pointer
+	Slice
+	String
+	Struct
+	UnsafePointer
+)
+
+const Ptr = Pointer
diff --git a/go/ssa/interp/testdata/src/sort/sort.go b/go/ssa/interp/testdata/src/sort/sort.go
new file mode 100644
index 0000000..d94d6da
--- /dev/null
+++ b/go/ssa/interp/testdata/src/sort/sort.go
@@ -0,0 +1,5 @@
+package sort
+
+func Strings(x []string)
+func Ints(x []int)
+func Float64s(x []float64)
diff --git a/go/ssa/interp/testdata/src/strconv/strconv.go b/go/ssa/interp/testdata/src/strconv/strconv.go
new file mode 100644
index 0000000..3f6f877
--- /dev/null
+++ b/go/ssa/interp/testdata/src/strconv/strconv.go
@@ -0,0 +1,6 @@
+package strconv
+
+func Itoa(i int) string
+func Atoi(s string) (int, error)
+
+func FormatFloat(float64, byte, int, int) string
diff --git a/go/ssa/interp/testdata/src/strings/strings.go b/go/ssa/interp/testdata/src/strings/strings.go
index dd86dcf..4c74f1b 100644
--- a/go/ssa/interp/testdata/src/strings/strings.go
+++ b/go/ssa/interp/testdata/src/strings/strings.go
@@ -7,3 +7,20 @@
 func Contains(haystack, needle string) bool {
 	return Index(haystack, needle) >= 0
 }
+
+func HasPrefix(s, prefix string) bool {
+	return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
+}
+
+func EqualFold(s, t string) bool
+func ToLower(s string) string
+
+type Builder struct {
+	s string
+}
+
+func (b *Builder) WriteString(s string) (int, error) {
+	b.s += s
+	return len(s), nil
+}
+func (b *Builder) String() string { return b.s }
diff --git a/go/ssa/interp/testdata/src/sync/sync.go b/go/ssa/interp/testdata/src/sync/sync.go
new file mode 100644
index 0000000..457a670
--- /dev/null
+++ b/go/ssa/interp/testdata/src/sync/sync.go
@@ -0,0 +1,36 @@
+package sync
+
+// Rudimentary implementation of a mutex for interp tests.
+type Mutex struct {
+	c chan int // Mutex is held when held c!=nil and is empty. Access is guarded by g.
+}
+
+func (m *Mutex) Lock() {
+	c := ch(m)
+	<-c
+}
+
+func (m *Mutex) Unlock() {
+	c := ch(m)
+	c <- 1
+}
+
+// sequentializes Mutex.c access.
+var g = make(chan int, 1)
+
+func init() {
+	g <- 1
+}
+
+// ch initializes the m.c field if needed and returns it.
+func ch(m *Mutex) chan int {
+	<-g
+	defer func() {
+		g <- 1
+	}()
+	if m.c == nil {
+		m.c = make(chan int, 1)
+		m.c <- 1
+	}
+	return m.c
+}
diff --git a/go/ssa/methods.go b/go/ssa/methods.go
index 9cf3839..22e1f3f 100644
--- a/go/ssa/methods.go
+++ b/go/ssa/methods.go
@@ -118,7 +118,7 @@
 // Panic ensues if there is none.
 //
 func (prog *Program) declaredFunc(obj *types.Func) *Function {
-	if v := prog.packageLevelValue(obj); v != nil {
+	if v := prog.packageLevelMember(obj); v != nil {
 		return v.(*Function)
 	}
 	panic("no concrete method: " + obj.String())
diff --git a/go/ssa/parameterized.go b/go/ssa/parameterized.go
new file mode 100644
index 0000000..956718c
--- /dev/null
+++ b/go/ssa/parameterized.go
@@ -0,0 +1,113 @@
+// 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 ssa
+
+import (
+	"go/types"
+
+	"golang.org/x/tools/internal/typeparams"
+)
+
+// tpWalker walks over types looking for parameterized types.
+//
+// NOTE: Adapted from go/types/infer.go. If that is exported in a future release remove this copy.
+type tpWalker struct {
+	seen map[types.Type]bool
+}
+
+// isParameterized returns true when typ contains any type parameters.
+func (w *tpWalker) isParameterized(typ types.Type) (res bool) {
+	// NOTE: Adapted from go/types/infer.go. Try to keep in sync.
+
+	// detect cycles
+	if x, ok := w.seen[typ]; ok {
+		return x
+	}
+	w.seen[typ] = false
+	defer func() {
+		w.seen[typ] = res
+	}()
+
+	switch t := typ.(type) {
+	case nil, *types.Basic: // TODO(gri) should nil be handled here?
+		break
+
+	case *types.Array:
+		return w.isParameterized(t.Elem())
+
+	case *types.Slice:
+		return w.isParameterized(t.Elem())
+
+	case *types.Struct:
+		for i, n := 0, t.NumFields(); i < n; i++ {
+			if w.isParameterized(t.Field(i).Type()) {
+				return true
+			}
+		}
+
+	case *types.Pointer:
+		return w.isParameterized(t.Elem())
+
+	case *types.Tuple:
+		n := t.Len()
+		for i := 0; i < n; i++ {
+			if w.isParameterized(t.At(i).Type()) {
+				return true
+			}
+		}
+
+	case *types.Signature:
+		// t.tparams may not be nil if we are looking at a signature
+		// of a generic function type (or an interface method) that is
+		// part of the type we're testing. We don't care about these type
+		// parameters.
+		// Similarly, the receiver of a method may declare (rather then
+		// use) type parameters, we don't care about those either.
+		// Thus, we only need to look at the input and result parameters.
+		return w.isParameterized(t.Params()) || w.isParameterized(t.Results())
+
+	case *types.Interface:
+		for i, n := 0, t.NumMethods(); i < n; i++ {
+			if w.isParameterized(t.Method(i).Type()) {
+				return true
+			}
+		}
+		terms, err := typeparams.InterfaceTermSet(t)
+		if err != nil {
+			panic(err)
+		}
+		for _, term := range terms {
+			if w.isParameterized(term.Type()) {
+				return true
+			}
+		}
+
+	case *types.Map:
+		return w.isParameterized(t.Key()) || w.isParameterized(t.Elem())
+
+	case *types.Chan:
+		return w.isParameterized(t.Elem())
+
+	case *types.Named:
+		args := typeparams.NamedTypeArgs(t)
+		// TODO(taking): this does not match go/types/infer.go. Check with rfindley.
+		if params := typeparams.ForNamed(t); params.Len() > args.Len() {
+			return true
+		}
+		for i, n := 0, args.Len(); i < n; i++ {
+			if w.isParameterized(args.At(i)) {
+				return true
+			}
+		}
+
+	case *typeparams.TypeParam:
+		return true
+
+	default:
+		panic(t) // unreachable
+	}
+
+	return false
+}
diff --git a/go/ssa/parameterized_test.go b/go/ssa/parameterized_test.go
new file mode 100644
index 0000000..64c9125
--- /dev/null
+++ b/go/ssa/parameterized_test.go
@@ -0,0 +1,80 @@
+// 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 ssa
+
+import (
+	"go/ast"
+	"go/parser"
+	"go/token"
+	"go/types"
+	"testing"
+
+	"golang.org/x/tools/internal/typeparams"
+)
+
+func TestIsParameterized(t *testing.T) {
+	if !typeparams.Enabled {
+		return
+	}
+
+	const source = `
+package P
+type A int
+func (A) f()
+func (*A) g()
+
+type fer interface { f() }
+
+func Apply[T fer](x T) T {
+	x.f()
+	return x
+}
+
+type V[T any] []T
+func (v *V[T]) Push(x T) { *v = append(*v, x) }
+`
+
+	fset := token.NewFileSet()
+	f, err := parser.ParseFile(fset, "hello.go", source, 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var conf types.Config
+	pkg, err := conf.Check("P", fset, []*ast.File{f}, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for _, test := range []struct {
+		expr string // type expression
+		want bool   // expected isParameterized value
+	}{
+		{"A", false},
+		{"*A", false},
+		{"error", false},
+		{"*error", false},
+		{"struct{A}", false},
+		{"*struct{A}", false},
+		{"fer", false},
+		{"Apply", true},
+		{"Apply[A]", false},
+		{"V", true},
+		{"V[A]", false},
+		{"*V[A]", false},
+		{"(*V[A]).Push", false},
+	} {
+		tv, err := types.Eval(fset, pkg, 0, test.expr)
+		if err != nil {
+			t.Errorf("Eval(%s) failed: %v", test.expr, err)
+		}
+
+		param := tpWalker{seen: make(map[types.Type]bool)}
+		if got := param.isParameterized(tv.Type); got != test.want {
+			t.Logf("Eval(%s) returned the type %s", test.expr, tv.Type)
+			t.Errorf("isParameterized(%s) = %v, want %v", test.expr, got, test.want)
+		}
+	}
+}
diff --git a/go/ssa/print.go b/go/ssa/print.go
index 5995f83..d0f3bbf 100644
--- a/go/ssa/print.go
+++ b/go/ssa/print.go
@@ -28,7 +28,7 @@
 func relName(v Value, i Instruction) string {
 	var from *types.Package
 	if i != nil {
-		from = i.Parent().pkg()
+		from = i.Parent().relPkg()
 	}
 	switch v := v.(type) {
 	case Member: // *Function or *Global
@@ -66,12 +66,12 @@
 // It never appears in disassembly, which uses Value.Name().
 
 func (v *Parameter) String() string {
-	from := v.Parent().pkg()
+	from := v.Parent().relPkg()
 	return fmt.Sprintf("parameter %s : %s", v.Name(), relType(v.Type(), from))
 }
 
 func (v *FreeVar) String() string {
-	from := v.Parent().pkg()
+	from := v.Parent().relPkg()
 	return fmt.Sprintf("freevar %s : %s", v.Name(), relType(v.Type(), from))
 }
 
@@ -86,7 +86,7 @@
 	if v.Heap {
 		op = "new"
 	}
-	from := v.Parent().pkg()
+	from := v.Parent().relPkg()
 	return fmt.Sprintf("%s %s (%s)", op, relType(deref(v.Type()), from), v.Comment)
 }
 
@@ -160,7 +160,7 @@
 }
 
 func printConv(prefix string, v, x Value) string {
-	from := v.Parent().pkg()
+	from := v.Parent().relPkg()
 	return fmt.Sprintf("%s %s <- %s (%s)",
 		prefix,
 		relType(v.Type(), from),
@@ -191,7 +191,7 @@
 }
 
 func (v *MakeSlice) String() string {
-	from := v.Parent().pkg()
+	from := v.Parent().relPkg()
 	return fmt.Sprintf("make %s %s %s",
 		relType(v.Type(), from),
 		relName(v.Len, v),
@@ -223,12 +223,12 @@
 	if v.Reserve != nil {
 		res = relName(v.Reserve, v)
 	}
-	from := v.Parent().pkg()
+	from := v.Parent().relPkg()
 	return fmt.Sprintf("make %s %s", relType(v.Type(), from), res)
 }
 
 func (v *MakeChan) String() string {
-	from := v.Parent().pkg()
+	from := v.Parent().relPkg()
 	return fmt.Sprintf("make %s %s", relType(v.Type(), from), relName(v.Size, v))
 }
 
@@ -273,7 +273,7 @@
 }
 
 func (v *TypeAssert) String() string {
-	from := v.Parent().pkg()
+	from := v.Parent().relPkg()
 	return fmt.Sprintf("typeassert%s %s.(%s)", commaOk(v.CommaOk), relName(v.X, v), relType(v.AssertedType, from))
 }
 
diff --git a/go/ssa/sanity.go b/go/ssa/sanity.go
index 1d4e20f..6e65d76 100644
--- a/go/ssa/sanity.go
+++ b/go/ssa/sanity.go
@@ -409,8 +409,8 @@
 		s.errorf("nil Prog")
 	}
 
-	_ = fn.String()            // must not crash
-	_ = fn.RelString(fn.pkg()) // must not crash
+	_ = fn.String()               // must not crash
+	_ = fn.RelString(fn.relPkg()) // must not crash
 
 	// All functions have a package, except delegates (which are
 	// shared across packages, or duplicated as weak symbols in a
diff --git a/go/ssa/source.go b/go/ssa/source.go
index 8d9cca1..7e2a369 100644
--- a/go/ssa/source.go
+++ b/go/ssa/source.go
@@ -123,7 +123,7 @@
 				// Don't call Program.Method: avoid creating wrappers.
 				obj := mset.At(i).Obj().(*types.Func)
 				if obj.Pos() == pos {
-					return pkg.values[obj].(*Function)
+					return pkg.objects[obj].(*Function)
 				}
 			}
 		}
@@ -180,14 +180,14 @@
 	return prog.packages[obj]
 }
 
-// packageLevelValue returns the package-level value corresponding to
+// packageLevelMember returns the package-level member corresponding to
 // the specified named object, which may be a package-level const
-// (*Const), var (*Global) or func (*Function) of some package in
+// (*NamedConst), var (*Global) or func (*Function) of some package in
 // prog.  It returns nil if the object is not found.
 //
-func (prog *Program) packageLevelValue(obj types.Object) Value {
+func (prog *Program) packageLevelMember(obj types.Object) Member {
 	if pkg, ok := prog.packages[obj.Pkg()]; ok {
-		return pkg.values[obj]
+		return pkg.objects[obj]
 	}
 	return nil
 }
@@ -199,7 +199,7 @@
 // result's Signature, both in the params/results and in the receiver.
 //
 func (prog *Program) FuncValue(obj *types.Func) *Function {
-	fn, _ := prog.packageLevelValue(obj).(*Function)
+	fn, _ := prog.packageLevelMember(obj).(*Function)
 	return fn
 }
 
@@ -215,8 +215,8 @@
 		return NewConst(obj.Val(), obj.Type())
 	}
 	// Package-level named constant?
-	if v := prog.packageLevelValue(obj); v != nil {
-		return v.(*Const)
+	if v := prog.packageLevelMember(obj); v != nil {
+		return v.(*NamedConst).Value
 	}
 	return NewConst(obj.Val(), obj.Type())
 }
@@ -285,7 +285,7 @@
 	}
 
 	// Defining ident of package-level var?
-	if v := prog.packageLevelValue(obj); v != nil {
+	if v := prog.packageLevelMember(obj); v != nil {
 		return v.(*Global), true
 	}
 
diff --git a/go/ssa/ssa.go b/go/ssa/ssa.go
index 8358681..ea5b68e 100644
--- a/go/ssa/ssa.go
+++ b/go/ssa/ssa.go
@@ -26,10 +26,11 @@
 	mode       BuilderMode                 // set of mode bits for SSA construction
 	MethodSets typeutil.MethodSetCache     // cache of type-checker's method-sets
 
+	canon canonizer // type canonicalization map
+
 	methodsMu    sync.Mutex                 // guards the following maps:
 	methodSets   typeutil.Map               // maps type to its concrete methodSet
 	runtimeTypes typeutil.Map               // types for which rtypes are needed
-	canon        typeutil.Map               // type canonicalization map
 	bounds       map[*types.Func]*Function  // bounds for curried x.Method closures
 	thunks       map[selectionKey]*Function // thunks for T.Method expressions
 }
@@ -44,12 +45,12 @@
 // and unspecified other things too.
 //
 type Package struct {
-	Prog    *Program               // the owning program
-	Pkg     *types.Package         // the corresponding go/types.Package
-	Members map[string]Member      // all package members keyed by name (incl. init and init#%d)
-	values  map[types.Object]Value // package members (incl. types and methods), keyed by object
-	init    *Function              // Func("init"); the package's init function
-	debug   bool                   // include full debug info in this package
+	Prog    *Program                // the owning program
+	Pkg     *types.Package          // the corresponding go/types.Package
+	Members map[string]Member       // all package members keyed by name (incl. init and init#%d)
+	objects map[types.Object]Member // mapping of package objects to members (incl. methods). Contains *NamedConst, *Global, *Function.
+	init    *Function               // Func("init"); the package's init function
+	debug   bool                    // include full debug info in this package
 
 	// The following fields are set transiently, then cleared
 	// after building.
@@ -320,6 +321,7 @@
 	namedResults []*Alloc                // tuple of named results
 	targets      *targets                // linked stack of branch targets
 	lblocks      map[*ast.Object]*lblock // labelled blocks
+	info         *types.Info             // *types.Info to build from. nil for wrappers.
 }
 
 // BasicBlock represents an SSA basic block.
diff --git a/go/ssa/subst.go b/go/ssa/subst.go
new file mode 100644
index 0000000..0e9263f
--- /dev/null
+++ b/go/ssa/subst.go
@@ -0,0 +1,432 @@
+// 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 ssa
+
+import (
+	"fmt"
+	"go/types"
+
+	"golang.org/x/tools/internal/typeparams"
+)
+
+// Type substituter for a fixed set of replacement types.
+//
+// A nil *subster is an valid, empty substitution map. It always acts as
+// the identity function. This allows for treating parameterized and
+// non-parameterized functions identically while compiling to ssa.
+//
+// Not concurrency-safe.
+type subster struct {
+	replacements map[*typeparams.TypeParam]types.Type // values should contain no type params
+	cache        map[types.Type]types.Type            // cache of subst results
+	ctxt         *typeparams.Context
+	debug        bool // perform extra debugging checks
+	// TODO(taking): consider adding Pos
+}
+
+// Returns a subster that replaces tparams[i] with targs[i]. Uses ctxt as a cache.
+// targs should not contain any types in tparams.
+func makeSubster(ctxt *typeparams.Context, tparams []*typeparams.TypeParam, targs []types.Type, debug bool) *subster {
+	assert(len(tparams) == len(targs), "makeSubster argument count must match")
+
+	subst := &subster{
+		replacements: make(map[*typeparams.TypeParam]types.Type, len(tparams)),
+		cache:        make(map[types.Type]types.Type),
+		ctxt:         ctxt,
+		debug:        debug,
+	}
+	for i, tpar := range tparams {
+		subst.replacements[tpar] = targs[i]
+	}
+	if subst.debug {
+		if err := subst.wellFormed(); err != nil {
+			panic(err)
+		}
+	}
+	return subst
+}
+
+// wellFormed returns an error if subst was not properly initialized.
+func (subst *subster) wellFormed() error {
+	if subst == nil || len(subst.replacements) == 0 {
+		return nil
+	}
+	// Check that all of the type params do not appear in the arguments.
+	s := make(map[types.Type]bool, len(subst.replacements))
+	for tparam := range subst.replacements {
+		s[tparam] = true
+	}
+	for _, r := range subst.replacements {
+		if reaches(r, s) {
+			return fmt.Errorf("\n‰r %s s %v replacements %v\n", r, s, subst.replacements)
+		}
+	}
+	return nil
+}
+
+// typ returns the type of t with the type parameter tparams[i] substituted
+// for the type targs[i] where subst was created using tparams and targs.
+func (subst *subster) typ(t types.Type) (res types.Type) {
+	if subst == nil {
+		return t // A nil subst is type preserving.
+	}
+	if r, ok := subst.cache[t]; ok {
+		return r
+	}
+	defer func() {
+		subst.cache[t] = res
+	}()
+
+	// fall through if result r will be identical to t, types.Identical(r, t).
+	switch t := t.(type) {
+	case *typeparams.TypeParam:
+		r := subst.replacements[t]
+		assert(r != nil, "type param without replacement encountered")
+		return r
+
+	case *types.Basic:
+		return t
+
+	case *types.Array:
+		if r := subst.typ(t.Elem()); r != t.Elem() {
+			return types.NewArray(r, t.Len())
+		}
+		return t
+
+	case *types.Slice:
+		if r := subst.typ(t.Elem()); r != t.Elem() {
+			return types.NewSlice(r)
+		}
+		return t
+
+	case *types.Pointer:
+		if r := subst.typ(t.Elem()); r != t.Elem() {
+			return types.NewPointer(r)
+		}
+		return t
+
+	case *types.Tuple:
+		return subst.tuple(t)
+
+	case *types.Struct:
+		return subst.struct_(t)
+
+	case *types.Map:
+		key := subst.typ(t.Key())
+		elem := subst.typ(t.Elem())
+		if key != t.Key() || elem != t.Elem() {
+			return types.NewMap(key, elem)
+		}
+		return t
+
+	case *types.Chan:
+		if elem := subst.typ(t.Elem()); elem != t.Elem() {
+			return types.NewChan(t.Dir(), elem)
+		}
+		return t
+
+	case *types.Signature:
+		return subst.signature(t)
+
+	case *typeparams.Union:
+		return subst.union(t)
+
+	case *types.Interface:
+		return subst.interface_(t)
+
+	case *types.Named:
+		return subst.named(t)
+
+	default:
+		panic("unreachable")
+	}
+}
+
+func (subst *subster) tuple(t *types.Tuple) *types.Tuple {
+	if t != nil {
+		if vars := subst.varlist(t); vars != nil {
+			return types.NewTuple(vars...)
+		}
+	}
+	return t
+}
+
+type varlist interface {
+	At(i int) *types.Var
+	Len() int
+}
+
+// fieldlist is an adapter for structs for the varlist interface.
+type fieldlist struct {
+	str *types.Struct
+}
+
+func (fl fieldlist) At(i int) *types.Var { return fl.str.Field(i) }
+func (fl fieldlist) Len() int            { return fl.str.NumFields() }
+
+func (subst *subster) struct_(t *types.Struct) *types.Struct {
+	if t != nil {
+		if fields := subst.varlist(fieldlist{t}); fields != nil {
+			tags := make([]string, t.NumFields())
+			for i, n := 0, t.NumFields(); i < n; i++ {
+				tags[i] = t.Tag(i)
+			}
+			return types.NewStruct(fields, tags)
+		}
+	}
+	return t
+}
+
+// varlist reutrns subst(in[i]) or return nils if subst(v[i]) == v[i] for all i.
+func (subst *subster) varlist(in varlist) []*types.Var {
+	var out []*types.Var // nil => no updates
+	for i, n := 0, in.Len(); i < n; i++ {
+		v := in.At(i)
+		w := subst.var_(v)
+		if v != w && out == nil {
+			out = make([]*types.Var, n)
+			for j := 0; j < i; j++ {
+				out[j] = in.At(j)
+			}
+		}
+		if out != nil {
+			out[i] = w
+		}
+	}
+	return out
+}
+
+func (subst *subster) var_(v *types.Var) *types.Var {
+	if v != nil {
+		if typ := subst.typ(v.Type()); typ != v.Type() {
+			if v.IsField() {
+				return types.NewField(v.Pos(), v.Pkg(), v.Name(), typ, v.Embedded())
+			}
+			return types.NewVar(v.Pos(), v.Pkg(), v.Name(), typ)
+		}
+	}
+	return v
+}
+
+func (subst *subster) union(u *typeparams.Union) *typeparams.Union {
+	var out []*typeparams.Term // nil => no updates
+
+	for i, n := 0, u.Len(); i < n; i++ {
+		t := u.Term(i)
+		r := subst.typ(t.Type())
+		if r != t.Type() && out == nil {
+			out = make([]*typeparams.Term, n)
+			for j := 0; j < i; j++ {
+				out[j] = u.Term(j)
+			}
+		}
+		if out != nil {
+			out[i] = typeparams.NewTerm(t.Tilde(), r)
+		}
+	}
+
+	if out != nil {
+		return typeparams.NewUnion(out)
+	}
+	return u
+}
+
+func (subst *subster) interface_(iface *types.Interface) *types.Interface {
+	if iface == nil {
+		return nil
+	}
+
+	// methods for the interface. Initially nil if there is no known change needed.
+	// Signatures for the method where recv is nil. NewInterfaceType fills in the recievers.
+	var methods []*types.Func
+	initMethods := func(n int) { // copy first n explicit methods
+		methods = make([]*types.Func, iface.NumExplicitMethods())
+		for i := 0; i < n; i++ {
+			f := iface.ExplicitMethod(i)
+			norecv := changeRecv(f.Type().(*types.Signature), nil)
+			methods[i] = types.NewFunc(f.Pos(), f.Pkg(), f.Name(), norecv)
+		}
+	}
+	for i := 0; i < iface.NumExplicitMethods(); i++ {
+		f := iface.ExplicitMethod(i)
+		// On interfaces, we need to cycle break on anonymous interface types
+		// being in a cycle with their signatures being in cycles with their recievers
+		// that do not go through a Named.
+		norecv := changeRecv(f.Type().(*types.Signature), nil)
+		sig := subst.typ(norecv)
+		if sig != norecv && methods == nil {
+			initMethods(i)
+		}
+		if methods != nil {
+			methods[i] = types.NewFunc(f.Pos(), f.Pkg(), f.Name(), sig.(*types.Signature))
+		}
+	}
+
+	var embeds []types.Type
+	initEmbeds := func(n int) { // copy first n embedded types
+		embeds = make([]types.Type, iface.NumEmbeddeds())
+		for i := 0; i < n; i++ {
+			embeds[i] = iface.EmbeddedType(i)
+		}
+	}
+	for i := 0; i < iface.NumEmbeddeds(); i++ {
+		e := iface.EmbeddedType(i)
+		r := subst.typ(e)
+		if e != r && embeds == nil {
+			initEmbeds(i)
+		}
+		if embeds != nil {
+			embeds[i] = r
+		}
+	}
+
+	if methods == nil && embeds == nil {
+		return iface
+	}
+	if methods == nil {
+		initMethods(iface.NumExplicitMethods())
+	}
+	if embeds == nil {
+		initEmbeds(iface.NumEmbeddeds())
+	}
+	return types.NewInterfaceType(methods, embeds).Complete()
+}
+
+func (subst *subster) named(t *types.Named) types.Type {
+	// A name type may be:
+	// (1) ordinary (no type parameters, no type arguments),
+	// (2) generic (type parameters but no type arguments), or
+	// (3) instantiated (type parameters and type arguments).
+	tparams := typeparams.ForNamed(t)
+	if tparams.Len() == 0 {
+		// case (1) ordinary
+
+		// Note: If Go allows for local type declarations in generic
+		// functions we may need to descend into underlying as well.
+		return t
+	}
+	targs := typeparams.NamedTypeArgs(t)
+
+	// insts are arguments to instantiate using.
+	insts := make([]types.Type, tparams.Len())
+
+	// case (2) generic ==> targs.Len() == 0
+	// Instantiating a generic with no type arguments should be unreachable.
+	// Please report a bug if you encounter this.
+	assert(targs.Len() != 0, "substition into a generic Named type is currently unsupported")
+
+	// case (3) instantiated.
+	// Substitute into the type arguments and instantiate the replacements/
+	// Example:
+	//    type N[A any] func() A
+	//    func Foo[T](g N[T]) {}
+	//  To instantiate Foo[string], one goes through {T->string}. To get the type of g
+	//  one subsitutes T with string in {N with TypeArgs == {T} and TypeParams == {A} }
+	//  to get {N with TypeArgs == {string} and TypeParams == {A} }.
+	assert(targs.Len() == tparams.Len(), "TypeArgs().Len() must match TypeParams().Len() if present")
+	for i, n := 0, targs.Len(); i < n; i++ {
+		inst := subst.typ(targs.At(i)) // TODO(generic): Check with rfindley for mutual recursion
+		insts[i] = inst
+	}
+	r, err := typeparams.Instantiate(subst.ctxt, typeparams.NamedTypeOrigin(t), insts, false)
+	assert(err == nil, "failed to Instantiate Named type")
+	return r
+}
+
+func (subst *subster) signature(t *types.Signature) types.Type {
+	tparams := typeparams.ForSignature(t)
+
+	// We are choosing not to support tparams.Len() > 0 until a need has been observed in practice.
+	//
+	// There are some known usages for types.Types coming from types.{Eval,CheckExpr}.
+	// To support tparams.Len() > 0, we just need to do the following [psuedocode]:
+	//   targs := {subst.replacements[tparams[i]]]}; Instantiate(ctxt, t, targs, false)
+
+	assert(tparams.Len() == 0, "Substituting types.Signatures with generic functions are currently unsupported.")
+
+	// Either:
+	// (1)non-generic function.
+	//    no type params to substitute
+	// (2)generic method and recv needs to be substituted.
+
+	// Recievers can be either:
+	// named
+	// pointer to named
+	// interface
+	// nil
+	// interface is the problematic case. We need to cycle break there!
+	recv := subst.var_(t.Recv())
+	params := subst.tuple(t.Params())
+	results := subst.tuple(t.Results())
+	if recv != t.Recv() || params != t.Params() || results != t.Results() {
+		return types.NewSignature(recv, params, results, t.Variadic())
+	}
+	return t
+}
+
+// reaches returns true if a type t reaches any type t' s.t. c[t'] == true.
+// Updates c to cache results.
+func reaches(t types.Type, c map[types.Type]bool) (res bool) {
+	if c, ok := c[t]; ok {
+		return c
+	}
+	c[t] = false // prevent cycles
+	defer func() {
+		c[t] = res
+	}()
+
+	switch t := t.(type) {
+	case *typeparams.TypeParam, *types.Basic:
+		// no-op => c == false
+	case *types.Array:
+		return reaches(t.Elem(), c)
+	case *types.Slice:
+		return reaches(t.Elem(), c)
+	case *types.Pointer:
+		return reaches(t.Elem(), c)
+	case *types.Tuple:
+		for i := 0; i < t.Len(); i++ {
+			if reaches(t.At(i).Type(), c) {
+				return true
+			}
+		}
+	case *types.Struct:
+		for i := 0; i < t.NumFields(); i++ {
+			if reaches(t.Field(i).Type(), c) {
+				return true
+			}
+		}
+	case *types.Map:
+		return reaches(t.Key(), c) || reaches(t.Elem(), c)
+	case *types.Chan:
+		return reaches(t.Elem(), c)
+	case *types.Signature:
+		if t.Recv() != nil && reaches(t.Recv().Type(), c) {
+			return true
+		}
+		return reaches(t.Params(), c) || reaches(t.Results(), c)
+	case *typeparams.Union:
+		for i := 0; i < t.Len(); i++ {
+			if reaches(t.Term(i).Type(), c) {
+				return true
+			}
+		}
+	case *types.Interface:
+		for i := 0; i < t.NumEmbeddeds(); i++ {
+			if reaches(t.Embedded(i), c) {
+				return true
+			}
+		}
+		for i := 0; i < t.NumExplicitMethods(); i++ {
+			if reaches(t.ExplicitMethod(i).Type(), c) {
+				return true
+			}
+		}
+	case *types.Named:
+		return reaches(t.Underlying(), c)
+	default:
+		panic("unreachable")
+	}
+	return false
+}
diff --git a/go/ssa/subst_test.go b/go/ssa/subst_test.go
new file mode 100644
index 0000000..fe84adc
--- /dev/null
+++ b/go/ssa/subst_test.go
@@ -0,0 +1,113 @@
+// 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 ssa
+
+import (
+	"go/ast"
+	"go/parser"
+	"go/token"
+	"go/types"
+	"testing"
+
+	"golang.org/x/tools/internal/typeparams"
+)
+
+func TestSubst(t *testing.T) {
+	if !typeparams.Enabled {
+		return
+	}
+
+	const source = `
+package P
+
+type t0 int
+func (t0) f()
+type t1 interface{ f() }
+type t2 interface{ g() }
+type t3 interface{ ~int }
+
+func Fn0[T t1](x T) T {
+	x.f()
+	return x
+}
+
+type A[T any] [4]T
+type B[T any] []T
+type C[T, S any] []struct{s S; t T}
+type D[T, S any] *struct{s S; t *T}
+type E[T, S any] interface{ F() (T, S) }
+type F[K comparable, V any] map[K]V
+type G[T any] chan *T
+type H[T any] func() T
+type I[T any] struct{x, y, z int; t T}
+type J[T any] interface{ t1 }
+type K[T any] interface{ t1; F() T }
+type L[T any] interface{ F() T; J[T] }
+
+var _ L[int] = Fn0[L[int]](nil)
+`
+
+	fset := token.NewFileSet()
+	f, err := parser.ParseFile(fset, "hello.go", source, 0)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var conf types.Config
+	pkg, err := conf.Check("P", fset, []*ast.File{f}, nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for _, test := range []struct {
+		expr string   // type expression of Named parameterized type
+		args []string // type expressions of args for named
+		want string   // expected underlying value after substitution
+	}{
+		{"A", []string{"string"}, "[4]string"},
+		{"A", []string{"int"}, "[4]int"},
+		{"B", []string{"int"}, "[]int"},
+		{"B", []string{"int8"}, "[]int8"},
+		{"C", []string{"int8", "string"}, "[]struct{s string; t int8}"},
+		{"C", []string{"string", "int8"}, "[]struct{s int8; t string}"},
+		{"D", []string{"int16", "string"}, "*struct{s string; t *int16}"},
+		{"E", []string{"int32", "string"}, "interface{F() (int32, string)}"},
+		{"F", []string{"int64", "string"}, "map[int64]string"},
+		{"G", []string{"uint64"}, "chan *uint64"},
+		{"H", []string{"uintptr"}, "func() uintptr"},
+		{"I", []string{"t0"}, "struct{x int; y int; z int; t P.t0}"},
+		{"J", []string{"t0"}, "interface{P.t1}"},
+		{"K", []string{"t0"}, "interface{F() P.t0; P.t1}"},
+		{"L", []string{"t0"}, "interface{F() P.t0; P.J[P.t0]}"},
+		{"L", []string{"L[t0]"}, "interface{F() P.L[P.t0]; P.J[P.L[P.t0]]}"},
+	} {
+		// Eval() expr for its type.
+		tv, err := types.Eval(fset, pkg, 0, test.expr)
+		if err != nil {
+			t.Fatalf("Eval(%s) failed: %v", test.expr, err)
+		}
+		// Eval() test.args[i] to get the i'th type arg.
+		var targs []types.Type
+		for _, astr := range test.args {
+			tv, err := types.Eval(fset, pkg, 0, astr)
+			if err != nil {
+				t.Fatalf("Eval(%s) failed: %v", astr, err)
+			}
+			targs = append(targs, tv.Type)
+		}
+
+		T := tv.Type.(*types.Named)
+		var tparams []*typeparams.TypeParam
+		for i, l := 0, typeparams.ForNamed(T); i < l.Len(); i++ {
+			tparams = append(tparams, l.At(i))
+		}
+
+		subst := makeSubster(typeparams.NewContext(), tparams, targs, true)
+		sub := subst.typ(T.Underlying())
+		if got := sub.String(); got != test.want {
+			t.Errorf("subst{%v->%v}.typ(%s) = %v, want %v", test.expr, test.args, T.Underlying(), got, test.want)
+		}
+	}
+}
diff --git a/go/ssa/util.go b/go/ssa/util.go
index a09949a..0102193 100644
--- a/go/ssa/util.go
+++ b/go/ssa/util.go
@@ -13,10 +13,22 @@
 	"go/types"
 	"io"
 	"os"
+	"sync"
 
 	"golang.org/x/tools/go/ast/astutil"
+	"golang.org/x/tools/go/types/typeutil"
 )
 
+//// Sanity checking utilities
+
+// assert panics with the mesage msg if p is false.
+// Avoid combining with expensive string formatting.
+func assert(p bool, msg string) {
+	if !p {
+		panic(msg)
+	}
+}
+
 //// AST utilities
 
 func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }
@@ -87,3 +99,36 @@
 		sig:  types.NewSignature(nil, lenParams, lenResults, false),
 	}
 }
+
+// Mapping of a type T to a canonical instance C s.t. types.Indentical(T, C).
+// Thread-safe.
+type canonizer struct {
+	mu    sync.Mutex
+	canon typeutil.Map // map from type to a canonical instance
+}
+
+// Tuple returns a canonical representative of a Tuple of types.
+// Representative of the empty Tuple is nil.
+func (c *canonizer) Tuple(ts []types.Type) *types.Tuple {
+	if len(ts) == 0 {
+		return nil
+	}
+	vars := make([]*types.Var, len(ts))
+	for i, t := range ts {
+		vars[i] = anonVar(t)
+	}
+	tuple := types.NewTuple(vars...)
+	return c.Type(tuple).(*types.Tuple)
+}
+
+// Type returns a canonical representative of type T.
+func (c *canonizer) Type(T types.Type) types.Type {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
+	if r := c.canon.At(T); r != nil {
+		return r.(types.Type)
+	}
+	c.canon.Set(T, T)
+	return T
+}
diff --git a/go/ssa/wrappers.go b/go/ssa/wrappers.go
index a4ae71d..90ddc9d 100644
--- a/go/ssa/wrappers.go
+++ b/go/ssa/wrappers.go
@@ -72,6 +72,7 @@
 		Synthetic: description,
 		Prog:      prog,
 		pos:       obj.Pos(),
+		info:      nil, // info is not set on wrappers.
 	}
 	fn.startBody()
 	fn.addSpilledParam(recv)
@@ -190,6 +191,7 @@
 			Synthetic: description,
 			Prog:      prog,
 			pos:       obj.Pos(),
+			info:      nil, // info is not set on wrappers.
 		}
 
 		fv := &FreeVar{name: "recv", typ: recvType(obj), parent: fn}
@@ -246,9 +248,11 @@
 		panic(sel)
 	}
 
+	// Canonicalize sel.Recv() to avoid constructing duplicate thunks.
+	canonRecv := prog.canon.Type(sel.Recv())
 	key := selectionKey{
 		kind:     sel.Kind(),
-		recv:     sel.Recv(),
+		recv:     canonRecv,
 		obj:      sel.Obj(),
 		index:    fmt.Sprint(sel.Index()),
 		indirect: sel.Indirect(),
@@ -257,14 +261,6 @@
 	prog.methodsMu.Lock()
 	defer prog.methodsMu.Unlock()
 
-	// Canonicalize key.recv to avoid constructing duplicate thunks.
-	canonRecv, ok := prog.canon.At(key.recv).(types.Type)
-	if !ok {
-		canonRecv = key.recv
-		prog.canon.Set(key.recv, canonRecv)
-	}
-	key.recv = canonRecv
-
 	fn, ok := prog.thunks[key]
 	if !ok {
 		fn = makeWrapper(prog, sel)
diff --git a/gopls/doc/vim.md b/gopls/doc/vim.md
index 4563c28..887a246 100644
--- a/gopls/doc/vim.md
+++ b/gopls/doc/vim.md
@@ -148,8 +148,12 @@
 ```vim
 lua <<EOF
   lspconfig = require "lspconfig"
+  util = require "lspconfig/util"
+
   lspconfig.gopls.setup {
     cmd = {"gopls", "serve"},
+    filetypes = {"go", "gomod"},
+    root_dir = util.root_pattern("go.work", "go.mod", ".git"),
     settings = {
       gopls = {
         analyses = {
@@ -171,38 +175,23 @@
 lua <<EOF
   -- …
 
-  function goimports(timeout_ms)
-    local context = { only = { "source.organizeImports" } }
-    vim.validate { context = { context, "t", true } }
-
+  function OrgImports(wait_ms)
     local params = vim.lsp.util.make_range_params()
-    params.context = context
-
-    -- See the implementation of the textDocument/codeAction callback
-    -- (lua/vim/lsp/handler.lua) for how to do this properly.
-    local result = vim.lsp.buf_request_sync(0, "textDocument/codeAction", params, timeout_ms)
-    if not result or next(result) == nil then return end
-    local actions = result[1].result
-    if not actions then return end
-    local action = actions[1]
-
-    -- textDocument/codeAction can return either Command[] or CodeAction[]. If it
-    -- is a CodeAction, it can have either an edit, a command or both. Edits
-    -- should be executed first.
-    if action.edit or type(action.command) == "table" then
-      if action.edit then
-        vim.lsp.util.apply_workspace_edit(action.edit)
+    params.context = {only = {"source.organizeImports"}}
+    local result = vim.lsp.buf_request_sync(0, "textDocument/codeAction", params, wait_ms)
+    for _, res in pairs(result or {}) do
+      for _, r in pairs(res.result or {}) do
+        if r.edit then
+          vim.lsp.util.apply_workspace_edit(r.edit)
+        else
+          vim.lsp.buf.execute_command(r.command)
+        end
       end
-      if type(action.command) == "table" then
-        vim.lsp.buf.execute_command(action.command)
-      end
-    else
-      vim.lsp.buf.execute_command(action)
     end
   end
 EOF
 
-autocmd BufWritePre *.go lua goimports(1000)
+autocmd BufWritePre *.go lua OrgImports(1000)
 ```
 
 (Taken from the [discussion][nvim-lspconfig-imports] on Neovim issue tracker.)
diff --git a/gopls/doc/workspace.md b/gopls/doc/workspace.md
index 821ba49..610afbe 100644
--- a/gopls/doc/workspace.md
+++ b/gopls/doc/workspace.md
@@ -19,40 +19,60 @@
 
 ### Multiple modules
 
-As of Jan 2021, if you are working with multiple modules or nested modules, you
-will need to create a "workspace folder" for each module. This means that each
-module has its own scope, and features will not work across modules. We are
-currently working on addressing this limitation--see details about
-[experimental workspace module mode](#workspace-module-experimental)
-below.
+Gopls has several alternatives for working on multiple modules simultaneously,
+described below. Starting with Go 1.18, Go workspaces are the preferred solution.
+
+#### Go workspaces (Go 1.18+)
+
+Starting with Go 1.18, the `go` command has native support for multi-module
+workspaces, via [`go.work`](https://go.dev/ref/mod#workspaces) files. These
+files are recognized by gopls starting with `gopls@v0.8.0`.
+
+The easiest way to work on multiple modules in Go 1.18 and later is therefore
+to create a `go.work` file containing the modules you wish to work on, and set
+your workspace root to the directory containing the `go.work` file.
+
+For example, suppose this repo is checked out into the `$WORK/tools` directory.
+We can work on both `golang.org/x/tools` and `golang.org/x/tools/gopls`
+simultaneously by creating a `go.work` file:
+
+```
+cd $WORK
+go work init
+go work use tools tools/gopls
+```
+
+...followed by opening the `$WORK` directory in our editor.
+
+#### Experimental workspace module (Go 1.17 and earlier)
+
+With earlier versions of Go, `gopls` can simulate multi-module workspaces by
+creating a synthetic module requiring the the modules in the workspace root.
+See [the design document](https://github.com/golang/proposal/blob/master/design/37720-gopls-workspaces.md)
+for more information.
+
+This feature is experimental, and will eventually be removed once `go.work`
+files are accepted by all supported Go versions.
+
+You can enable this feature by configuring the
+[experimentalWorkspaceModule](settings.md#experimentalworkspacemodule-bool)
+setting.
+
+#### Multiple workspace folders
+
+If neither of the above solutions work, and your editor allows configuring the
+set of
+["workspace folders"](https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#workspaceFolder)
+used during your LSP session, you can still work on multiple modules by adding
+a workspace folder at each module root (the locations of `go.mod` files). This
+means that each module has its own scope, and features will not work across
+modules. 
 
 In VS Code, you can create a workspace folder by setting up a
 [multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces).
 View the [documentation for your editor plugin](../README.md#editor) to learn how to
 configure a workspace folder in your editor.
 
-#### Workspace module (experimental)
-
-Many `gopls` users would like to work with multiple modules at the same time
-([golang/go#32394](https://github.com/golang/go/issues/32394)), and
-specifically, have features that work across modules. We plan to add support
-for this via a concept called the "workspace module", which is described in
-[this design document](https://github.com/golang/proposal/blob/master/design/37720-gopls-workspaces.md).
-This feature works by creating a temporary module that requires all of your
-workspace modules, meaning all of their dependencies must be compatible.
-
-The workspace module feature is currently available as an opt-in experiment,
-and it will allow you to work with multiple modules without creating workspace
-folders for each module. You can try it out by configuring the
-[experimentalWorkspaceModule](settings.md#experimentalworkspacemodule-bool)
-setting. If you try it and encounter issues, please
-[report them](https://github.com/golang/go/issues/new) so we can address them
-before the feature is enabled by default.
-
-You can follow our progress on the workspace module work by looking at the
-open issues in the
-[gopls/workspace-module milestone](https://github.com/golang/go/milestone/179).
-
 ### GOPATH mode
 
 When opening a directory within your GOPATH, the workspace scope will be just
diff --git a/gopls/go.mod b/gopls/go.mod
index 0480a65..3054853 100644
--- a/gopls/go.mod
+++ b/gopls/go.mod
@@ -12,7 +12,7 @@
 	golang.org/x/tools v0.1.10-0.20220303153236-fa15af63a6f1
 	honnef.co/go/tools v0.2.2
 	mvdan.cc/gofumpt v0.3.0
-	mvdan.cc/xurls/v2 v2.3.0
+	mvdan.cc/xurls/v2 v2.4.0
 )
 
 require (
@@ -22,3 +22,5 @@
 	golang.org/x/text v0.3.7 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 )
+
+replace golang.org/x/tools => ../
diff --git a/gopls/go.sum b/gopls/go.sum
index a78706e..53c94ec 100644
--- a/gopls/go.sum
+++ b/gopls/go.sum
@@ -26,7 +26,6 @@
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
-github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
 github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
 github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
@@ -91,5 +90,5 @@
 honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
 mvdan.cc/gofumpt v0.3.0 h1:kTojdZo9AcEYbQYhGuLf/zszYthRdhDNDUi2JKTxas4=
 mvdan.cc/gofumpt v0.3.0/go.mod h1:0+VyGZWleeIj5oostkOex+nDBA0eyavuDnDusAJ8ylo=
-mvdan.cc/xurls/v2 v2.3.0 h1:59Olnbt67UKpxF1EwVBopJvkSUBmgtb468E4GVWIZ1I=
-mvdan.cc/xurls/v2 v2.3.0/go.mod h1:AjuTy7gEiUArFMjgBBDU4SMxlfUYsRokpJQgNWOt3e4=
+mvdan.cc/xurls/v2 v2.4.0 h1:tzxjVAj+wSBmDcF6zBB7/myTy3gX9xvi8Tyr28AuQgc=
+mvdan.cc/xurls/v2 v2.4.0/go.mod h1:+GEjq9uNjqs8LQfM9nVnM8rff0OQ5Iash5rzX+N1CSg=
diff --git a/gopls/internal/hooks/hooks.go b/gopls/internal/hooks/hooks.go
index 487fb60..db554f5 100644
--- a/gopls/internal/hooks/hooks.go
+++ b/gopls/internal/hooks/hooks.go
@@ -9,7 +9,6 @@
 
 import (
 	"context"
-	"regexp"
 
 	"golang.org/x/tools/internal/lsp/source"
 	"mvdan.cc/gofumpt/format"
@@ -21,7 +20,7 @@
 	if options.GoDiff {
 		options.ComputeEdits = ComputeEdits
 	}
-	options.URLRegexp = relaxedFullWord
+	options.URLRegexp = xurls.Relaxed()
 	options.GofumptFormat = func(ctx context.Context, langVersion, modulePath string, src []byte) ([]byte, error) {
 		return format.Source(src, format.Options{
 			LangVersion: langVersion,
@@ -30,11 +29,3 @@
 	}
 	updateAnalyzers(options)
 }
-
-var relaxedFullWord *regexp.Regexp
-
-// Ensure links are matched as full words, not anywhere.
-func init() {
-	relaxedFullWord = regexp.MustCompile(`\b(` + xurls.Relaxed().String() + `)\b`)
-	relaxedFullWord.Longest()
-}
diff --git a/gopls/internal/regtest/completion/completion18_test.go b/gopls/internal/regtest/completion/completion18_test.go
index e5002f6..9683e30 100644
--- a/gopls/internal/regtest/completion/completion18_test.go
+++ b/gopls/internal/regtest/completion/completion18_test.go
@@ -54,3 +54,70 @@
 		}
 	})
 }
+func TestFuzzFunc(t *testing.T) {
+	// use the example from the package documentation
+	modfile := `
+-- go.mod --
+module mod.com
+
+go 1.18
+`
+	part0 := `package foo
+import "testing"
+func FuzzNone(f *testing.F) {
+	f.Add(12) // better not find this f.Add
+}
+func FuzzHex(f *testing.F) {
+	for _, seed := range [][]byte{{}, {0}, {9}, {0xa}, {0xf}, {1, 2, 3, 4}} {
+		f.Ad`
+	part1 := `d(seed)
+	}
+	f.F`
+	part2 := `uzz(func(t *testing.T, in []byte) {
+		enc := hex.EncodeToString(in)
+		out, err := hex.DecodeString(enc)
+		if err != nil {
+		  f.Failed()
+		}
+		if !bytes.Equal(in, out) {
+		  t.Fatalf("%v: round trip: %v, %s", in, out, f.Name())
+		}
+	})
+}
+`
+	data := modfile + `-- a_test.go --
+` + part0 + `
+-- b_test.go --
+` + part0 + part1 + `
+-- c_test.go --
+` + part0 + part1 + part2
+
+	tests := []struct {
+		file   string
+		pat    string
+		offset int // from the beginning of pat to what the user just typed
+		want   []string
+	}{
+		{"a_test.go", "f.Ad", 3, []string{"Add"}},
+		{"c_test.go", " f.F", 4, []string{"Failed"}},
+		{"c_test.go", "f.N", 3, []string{"Name"}},
+		{"b_test.go", "f.F", 3, []string{"Fuzz(func(t *testing.T, a []byte)", "Fail", "FailNow",
+			"Failed", "Fatal", "Fatalf"}},
+	}
+	Run(t, data, func(t *testing.T, env *Env) {
+		for _, test := range tests {
+			env.OpenFile(test.file)
+			env.Await(env.DoneWithOpen())
+			pos := env.RegexpSearch(test.file, test.pat)
+			pos.Column += test.offset // character user just typed? will type?
+			completions := env.Completion(test.file, pos)
+			result := compareCompletionResults(test.want, completions.Items)
+			if result != "" {
+				t.Errorf("pat %q %q", test.pat, result)
+				for i, it := range completions.Items {
+					t.Errorf("%d got %q %q", i, it.Label, it.Detail)
+				}
+			}
+		}
+	})
+}
diff --git a/gopls/internal/regtest/completion/completion_test.go b/gopls/internal/regtest/completion/completion_test.go
index 6abcd60..c0b4736 100644
--- a/gopls/internal/regtest/completion/completion_test.go
+++ b/gopls/internal/regtest/completion/completion_test.go
@@ -599,3 +599,48 @@
 		}
 	})
 }
+
+func TestGoWorkCompletion(t *testing.T) {
+	const files = `
+-- go.work --
+go 1.18
+
+use ./a
+use ./a/ba
+use ./a/b/
+use ./dir/foo
+use ./dir/foobar/
+-- a/go.mod --
+-- go.mod --
+-- a/bar/go.mod --
+-- a/b/c/d/e/f/go.mod --
+-- dir/bar --
+-- dir/foobar/go.mod --
+`
+
+	Run(t, files, func(t *testing.T, env *Env) {
+		env.OpenFile("go.work")
+
+		tests := []struct {
+			re   string
+			want []string
+		}{
+			{`use ()\.`, []string{".", "./a", "./a/bar", "./dir/foobar"}},
+			{`use \.()`, []string{"", "/a", "/a/bar", "/dir/foobar"}},
+			{`use \./()`, []string{"a", "a/bar", "dir/foobar"}},
+			{`use ./a()`, []string{"", "/b/c/d/e/f", "/bar"}},
+			{`use ./a/b()`, []string{"/c/d/e/f", "ar"}},
+			{`use ./a/b/()`, []string{`c/d/e/f`}},
+			{`use ./a/ba()`, []string{"r"}},
+			{`use ./dir/foo()`, []string{"bar"}},
+			{`use ./dir/foobar/()`, []string{}},
+		}
+		for _, tt := range tests {
+			completions := env.Completion("go.work", env.RegexpSearch("go.work", tt.re))
+			diff := compareCompletionResults(tt.want, completions.Items)
+			if diff != "" {
+				t.Errorf("%s: %s", tt.re, diff)
+			}
+		}
+	})
+}
diff --git a/gopls/internal/regtest/modfile/modfile_test.go b/gopls/internal/regtest/modfile/modfile_test.go
index 05b7ade..868aa70 100644
--- a/gopls/internal/regtest/modfile/modfile_test.go
+++ b/gopls/internal/regtest/modfile/modfile_test.go
@@ -239,6 +239,62 @@
 	})
 }
 
+// Tests that multiple missing dependencies gives good single fixes.
+func TestMissingDependencyFixesWithGoWork(t *testing.T) {
+	testenv.NeedsGo1Point(t, 18)
+	const mod = `
+-- go.work --
+go 1.18
+
+use (
+	./a
+)
+-- a/go.mod --
+module mod.com
+
+go 1.12
+
+-- a/main.go --
+package main
+
+import "example.com/blah"
+import "random.org/blah"
+
+var _, _ = blah.Name, hello.Name
+`
+
+	const want = `module mod.com
+
+go 1.12
+
+require random.org v1.2.3
+`
+
+	RunMultiple{
+		{"default", WithOptions(ProxyFiles(proxy), WorkspaceFolders("a"))},
+		{"nested", WithOptions(ProxyFiles(proxy))},
+	}.Run(t, mod, func(t *testing.T, env *Env) {
+		env.OpenFile("a/main.go")
+		var d protocol.PublishDiagnosticsParams
+		env.Await(
+			OnceMet(
+				env.DiagnosticAtRegexp("a/main.go", `"random.org/blah"`),
+				ReadDiagnostics("a/main.go", &d),
+			),
+		)
+		var randomDiag protocol.Diagnostic
+		for _, diag := range d.Diagnostics {
+			if strings.Contains(diag.Message, "random.org") {
+				randomDiag = diag
+			}
+		}
+		env.ApplyQuickFixes("a/main.go", []protocol.Diagnostic{randomDiag})
+		if got := env.ReadWorkspaceFile("a/go.mod"); got != want {
+			t.Fatalf("unexpected go.mod content:\n%s", tests.Diff(t, want, got))
+		}
+	})
+}
+
 func TestIndirectDependencyFix(t *testing.T) {
 	testenv.NeedsGo1Point(t, 14)
 
diff --git a/gopls/internal/regtest/workspace/workspace_test.go b/gopls/internal/regtest/workspace/workspace_test.go
index 82bfd12..ed2c9ef 100644
--- a/gopls/internal/regtest/workspace/workspace_test.go
+++ b/gopls/internal/regtest/workspace/workspace_test.go
@@ -796,6 +796,139 @@
 	})
 }
 
+func TestUseGoWorkDiagnosticMissingModule(t *testing.T) {
+	const files = `
+-- go.work --
+go 1.18
+
+use ./foo
+-- bar/go.mod --
+module example.com/bar
+`
+	Run(t, files, func(t *testing.T, env *Env) {
+		env.OpenFile("go.work")
+		env.Await(
+			env.DiagnosticAtRegexpWithMessage("go.work", "use", "directory ./foo does not contain a module"),
+		)
+		// The following tests is a regression test against an issue where we weren't
+		// copying the workFile struct field on workspace when a new one was created in
+		// (*workspace).invalidate. Set the buffer content to a working file so that
+		// invalidate recognizes the workspace to be change and copies over the workspace
+		// struct, and then set the content back to the old contents to make sure
+		// the diagnostic still shows up.
+		env.SetBufferContent("go.work", "go 1.18 \n\n use ./bar\n")
+		env.Await(
+			env.NoDiagnosticAtRegexp("go.work", "use"),
+		)
+		env.SetBufferContent("go.work", "go 1.18 \n\n use ./foo\n")
+		env.Await(
+			env.DiagnosticAtRegexpWithMessage("go.work", "use", "directory ./foo does not contain a module"),
+		)
+	})
+}
+
+func TestUseGoWorkDiagnosticSyntaxError(t *testing.T) {
+	const files = `
+-- go.work --
+go 1.18
+
+usa ./foo
+replace
+`
+	Run(t, files, func(t *testing.T, env *Env) {
+		env.OpenFile("go.work")
+		env.Await(
+			env.DiagnosticAtRegexpWithMessage("go.work", "usa", "unknown directive: usa"),
+			env.DiagnosticAtRegexpWithMessage("go.work", "replace", "usage: replace"),
+		)
+	})
+}
+
+func TestUseGoWorkHover(t *testing.T) {
+	const files = `
+-- go.work --
+go 1.18
+
+use ./foo
+use (
+	./bar
+	./bar/baz
+)
+-- foo/go.mod --
+module example.com/foo
+-- bar/go.mod --
+module example.com/bar
+-- bar/baz/go.mod --
+module example.com/bar/baz
+`
+	Run(t, files, func(t *testing.T, env *Env) {
+		env.OpenFile("go.work")
+
+		tcs := map[string]string{
+			`\./foo`:      "example.com/foo",
+			`(?m)\./bar$`: "example.com/bar",
+			`\./bar/baz`:  "example.com/bar/baz",
+		}
+
+		for hoverRE, want := range tcs {
+			pos := env.RegexpSearch("go.work", hoverRE)
+			got, _ := env.Hover("go.work", pos)
+			if got.Value != want {
+				t.Errorf(`hover on %q: got %q, want %q`, hoverRE, got, want)
+			}
+		}
+	})
+}
+
+func TestExpandToGoWork(t *testing.T) {
+	testenv.NeedsGo1Point(t, 18)
+	const workspace = `
+-- moda/a/go.mod --
+module a.com
+
+require b.com v1.2.3
+-- moda/a/a.go --
+package a
+
+import (
+	"b.com/b"
+)
+
+func main() {
+	var x int
+	_ = b.Hello()
+}
+-- modb/go.mod --
+module b.com
+
+require example.com v1.2.3
+-- modb/b/b.go --
+package b
+
+func Hello() int {
+	var x int
+}
+-- go.work --
+go 1.17
+
+use (
+	./moda/a
+	./modb
+)
+`
+	WithOptions(
+		WorkspaceFolders("moda/a"),
+	).Run(t, workspace, func(t *testing.T, env *Env) {
+		env.OpenFile("moda/a/a.go")
+		env.Await(env.DoneWithOpen())
+		location, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
+		want := "modb/b/b.go"
+		if !strings.HasSuffix(location, want) {
+			t.Errorf("expected %s, got %v", want, location)
+		}
+	})
+}
+
 func TestNonWorkspaceFileCreation(t *testing.T) {
 	testenv.NeedsGo1Point(t, 13)
 
diff --git a/internal/jsonrpc2_v2/conn.go b/internal/jsonrpc2_v2/conn.go
index 606c3f9..d60b6c5 100644
--- a/internal/jsonrpc2_v2/conn.go
+++ b/internal/jsonrpc2_v2/conn.go
@@ -22,7 +22,8 @@
 // ConnectionOptions itself implements Binder returning itself unmodified, to
 // allow for the simple cases where no per connection information is needed.
 type Binder interface {
-	// Bind is invoked when creating a new connection.
+	// Bind returns the ConnectionOptions to use when establishing the passed-in
+	// Connection.
 	// The connection is not ready to use when Bind is called.
 	Bind(context.Context, *Connection) (ConnectionOptions, error)
 }
@@ -234,10 +235,10 @@
 	return json.Unmarshal(r.result, result)
 }
 
-// Respond deliverers a response to an incoming Call.
-// It is an error to not call this exactly once for any message for which a
-// handler has previously returned ErrAsyncResponse. It is also an error to
-// call this for any other message.
+// Respond delivers a response to an incoming Call.
+//
+// Respond must be called exactly once for any message for which a handler
+// returns ErrAsyncResponse. It must not be called for any other message.
 func (c *Connection) Respond(id ID, result interface{}, rerr error) error {
 	pending := <-c.incomingBox
 	defer func() { c.incomingBox <- pending }()
@@ -321,8 +322,8 @@
 			// cancelled by id
 			if msg.IsCall() {
 				pending := <-c.incomingBox
-				c.incomingBox <- pending
 				pending[msg.ID] = entry
+				c.incomingBox <- pending
 			}
 			// send the message to the incoming queue
 			toQueue <- entry
diff --git a/internal/jsonrpc2_v2/jsonrpc2.go b/internal/jsonrpc2_v2/jsonrpc2.go
index 271f42c..e685584 100644
--- a/internal/jsonrpc2_v2/jsonrpc2.go
+++ b/internal/jsonrpc2_v2/jsonrpc2.go
@@ -15,11 +15,19 @@
 var (
 	// ErrIdleTimeout is returned when serving timed out waiting for new connections.
 	ErrIdleTimeout = errors.New("timed out waiting for new connections")
-	// ErrNotHandled is returned from a handler to indicate it did not handle the
-	// message.
+
+	// ErrNotHandled is returned from a Handler or Preempter to indicate it did
+	// not handle the request.
+	//
+	// If a Handler returns ErrNotHandled, the server replies with
+	// ErrMethodNotFound.
 	ErrNotHandled = errors.New("JSON RPC not handled")
+
 	// ErrAsyncResponse is returned from a handler to indicate it will generate a
 	// response asynchronously.
+	//
+	// ErrAsyncResponse must not be returned for notifications,
+	// which do not receive responses.
 	ErrAsyncResponse = errors.New("JSON RPC asynchronous response")
 )
 
@@ -28,17 +36,33 @@
 // Primarily this is used for cancel handlers or notifications for which out of
 // order processing is not an issue.
 type Preempter interface {
-	// Preempt is invoked for each incoming request before it is queued.
-	// If the request is a call, it must return a value or an error for the reply.
-	// Preempt should not block or start any new messages on the connection.
-	Preempt(ctx context.Context, req *Request) (interface{}, error)
+	// Preempt is invoked for each incoming request before it is queued for handling.
+	//
+	// If Preempt returns ErrNotHandled, the request will be queued,
+	// and eventually passed to a Handle call.
+	//
+	// Otherwise, the result and error are processed as if returned by Handle.
+	//
+	// Preempt must not block. (The Context passed to it is for Values only.)
+	Preempt(ctx context.Context, req *Request) (result interface{}, err error)
 }
 
 // Handler handles messages on a connection.
 type Handler interface {
-	// Handle is invoked for each incoming request.
-	// If the request is a call, it must return a value or an error for the reply.
-	Handle(ctx context.Context, req *Request) (interface{}, error)
+	// Handle is invoked sequentially for each incoming request that has not
+	// already been handled by a Preempter.
+	//
+	// If the Request has a nil ID, Handle must return a nil result,
+	// and any error may be logged but will not be reported to the caller.
+	//
+	// If the Request has a non-nil ID, Handle must return either a
+	// non-nil, JSON-marshalable result, or a non-nil error.
+	//
+	// The Context passed to Handle will be canceled if the
+	// connection is broken or the request is canceled or completed.
+	// (If Handle returns ErrAsyncResponse, ctx will remain uncanceled
+	// until either Cancel or Respond is called for the request's ID.)
+	Handle(ctx context.Context, req *Request) (result interface{}, err error)
 }
 
 type defaultHandler struct{}
@@ -60,15 +84,15 @@
 // async is a small helper for operations with an asynchronous result that you
 // can wait for.
 type async struct {
-	ready  chan struct{} // signals that the operation has completed
-	errBox chan error    // guards the operation result
+	ready    chan struct{} // closed when done
+	firstErr chan error    // 1-buffered; contains either nil or the first non-nil error
 }
 
 func newAsync() *async {
 	var a async
 	a.ready = make(chan struct{})
-	a.errBox = make(chan error, 1)
-	a.errBox <- nil
+	a.firstErr = make(chan error, 1)
+	a.firstErr <- nil
 	return &a
 }
 
@@ -87,15 +111,15 @@
 
 func (a *async) wait() error {
 	<-a.ready
-	err := <-a.errBox
-	a.errBox <- err
+	err := <-a.firstErr
+	a.firstErr <- err
 	return err
 }
 
 func (a *async) setError(err error) {
-	storedErr := <-a.errBox
+	storedErr := <-a.firstErr
 	if storedErr == nil {
 		storedErr = err
 	}
-	a.errBox <- storedErr
+	a.firstErr <- storedErr
 }
diff --git a/internal/jsonrpc2_v2/jsonrpc2_test.go b/internal/jsonrpc2_v2/jsonrpc2_test.go
index 1157779..4f4b7d9 100644
--- a/internal/jsonrpc2_v2/jsonrpc2_test.go
+++ b/internal/jsonrpc2_v2/jsonrpc2_test.go
@@ -60,6 +60,14 @@
 		notify{"unblock", "a"},
 		collect{"a", true, false},
 	}},
+	sequence{"concurrent", []invoker{
+		async{"a", "fork", "a"},
+		notify{"unblock", "a"},
+		async{"b", "fork", "b"},
+		notify{"unblock", "b"},
+		collect{"a", true, false},
+		collect{"b", true, false},
+	}},
 }
 
 type binder struct {
diff --git a/internal/jsonrpc2_v2/serve.go b/internal/jsonrpc2_v2/serve.go
index 98e8894..fb35166 100644
--- a/internal/jsonrpc2_v2/serve.go
+++ b/internal/jsonrpc2_v2/serve.go
@@ -18,17 +18,17 @@
 
 // Listener is implemented by protocols to accept new inbound connections.
 type Listener interface {
-	// Accept an inbound connection to a server.
-	// It must block until an inbound connection is made, or the listener is
-	// shut down.
+	// Accept accepts an inbound connection to a server.
+	// It blocks until either an inbound connection is made, or the listener is closed.
 	Accept(context.Context) (io.ReadWriteCloser, error)
 
-	// Close is used to ask a listener to stop accepting new connections.
+	// Close closes the listener.
+	// Any blocked Accept or Dial operations will unblock and return errors.
 	Close() error
 
 	// Dialer returns a dialer that can be used to connect to this listener
 	// locally.
-	// If a listener does not implement this it will return a nil.
+	// If a listener does not implement this it will return nil.
 	Dialer() Dialer
 }
 
diff --git a/internal/jsonrpc2_v2/wire.go b/internal/jsonrpc2_v2/wire.go
index 97b1ae8..4da129a 100644
--- a/internal/jsonrpc2_v2/wire.go
+++ b/internal/jsonrpc2_v2/wire.go
@@ -12,8 +12,6 @@
 // see http://www.jsonrpc.org/specification for details
 
 var (
-	// ErrUnknown should be used for all non coded errors.
-	ErrUnknown = NewError(-32001, "JSON RPC unknown error")
 	// ErrParse is used when invalid JSON was received by the server.
 	ErrParse = NewError(-32700, "JSON RPC parse error")
 	// ErrInvalidRequest is used when the JSON sent is not a valid Request object.
@@ -28,11 +26,13 @@
 	ErrInternal = NewError(-32603, "JSON RPC internal error")
 
 	// The following errors are not part of the json specification, but
-	// compliant extensions specific to this implimentation.
+	// compliant extensions specific to this implementation.
 
 	// ErrServerOverloaded is returned when a message was refused due to a
 	// server being temporarily unable to accept any new messages.
 	ErrServerOverloaded = NewError(-32000, "JSON RPC overloaded")
+	// ErrUnknown should be used for all non coded errors.
+	ErrUnknown = NewError(-32001, "JSON RPC unknown error")
 )
 
 const wireVersion = "2.0"
diff --git a/internal/lsp/analysis/stubmethods/stubmethods.go b/internal/lsp/analysis/stubmethods/stubmethods.go
index ba0a343..c2a4138 100644
--- a/internal/lsp/analysis/stubmethods/stubmethods.go
+++ b/internal/lsp/analysis/stubmethods/stubmethods.go
@@ -60,7 +60,7 @@
 			endPos = analysisinternal.TypeErrorEndPos(pass.Fset, buf.Bytes(), err.Pos)
 		}
 		path, _ := astutil.PathEnclosingInterval(file, err.Pos, endPos)
-		si := GetStubInfo(pass.TypesInfo, path, pass.Pkg, err.Pos)
+		si := GetStubInfo(pass.TypesInfo, path, err.Pos)
 		if si == nil {
 			continue
 		}
@@ -91,20 +91,20 @@
 
 // GetStubInfo determines whether the "missing method error"
 // can be used to deduced what the concrete and interface types are.
-func GetStubInfo(ti *types.Info, path []ast.Node, pkg *types.Package, pos token.Pos) *StubInfo {
+func GetStubInfo(ti *types.Info, path []ast.Node, pos token.Pos) *StubInfo {
 	for _, n := range path {
 		switch n := n.(type) {
 		case *ast.ValueSpec:
-			return fromValueSpec(ti, n, pkg, pos)
+			return fromValueSpec(ti, n, pos)
 		case *ast.ReturnStmt:
 			// An error here may not indicate a real error the user should know about, but it may.
 			// Therefore, it would be best to log it out for debugging/reporting purposes instead of ignoring
 			// it. However, event.Log takes a context which is not passed via the analysis package.
 			// TODO(marwan-at-work): properly log this error.
-			si, _ := fromReturnStmt(ti, pos, path, n, pkg)
+			si, _ := fromReturnStmt(ti, pos, path, n)
 			return si
 		case *ast.AssignStmt:
-			return fromAssignStmt(ti, n, pkg, pos)
+			return fromAssignStmt(ti, n, pos)
 		}
 	}
 	return nil
@@ -115,7 +115,7 @@
 //
 // For example, func() io.Writer { return myType{} }
 // would return StubInfo with the interface being io.Writer and the concrete type being myType{}.
-func fromReturnStmt(ti *types.Info, pos token.Pos, path []ast.Node, rs *ast.ReturnStmt, pkg *types.Package) (*StubInfo, error) {
+func fromReturnStmt(ti *types.Info, pos token.Pos, path []ast.Node, rs *ast.ReturnStmt) (*StubInfo, error) {
 	returnIdx := -1
 	for i, r := range rs.Results {
 		if pos >= r.Pos() && pos <= r.End() {
@@ -146,7 +146,7 @@
 
 // fromValueSpec returns *StubInfo from a variable declaration such as
 // var x io.Writer = &T{}
-func fromValueSpec(ti *types.Info, vs *ast.ValueSpec, pkg *types.Package, pos token.Pos) *StubInfo {
+func fromValueSpec(ti *types.Info, vs *ast.ValueSpec, pos token.Pos) *StubInfo {
 	var idx int
 	for i, vs := range vs.Values {
 		if pos >= vs.Pos() && pos <= vs.End() {
@@ -182,7 +182,7 @@
 // fromAssignStmt returns *StubInfo from a variable re-assignment such as
 // var x io.Writer
 // x = &T{}
-func fromAssignStmt(ti *types.Info, as *ast.AssignStmt, pkg *types.Package, pos token.Pos) *StubInfo {
+func fromAssignStmt(ti *types.Info, as *ast.AssignStmt, pos token.Pos) *StubInfo {
 	idx := -1
 	var lhs, rhs ast.Expr
 	// Given a re-assignment interface conversion error,
diff --git a/internal/lsp/cache/errors.go b/internal/lsp/cache/errors.go
index 155b7a4..e9a86de 100644
--- a/internal/lsp/cache/errors.go
+++ b/internal/lsp/cache/errors.go
@@ -101,7 +101,7 @@
 }
 
 var importErrorRe = regexp.MustCompile(`could not import ([^\s]+)`)
-var unsupportedFeatureRe = regexp.MustCompile(`.*require go(\d+\.\d+) or later`)
+var unsupportedFeatureRe = regexp.MustCompile(`.*require.* go(\d+\.\d+) or later`)
 
 func typeErrorDiagnostics(snapshot *snapshot, pkg *pkg, e extendedError) ([]*source.Diagnostic, error) {
 	code, spn, err := typeErrorData(snapshot.FileSet(), pkg, e.primary)
@@ -146,12 +146,10 @@
 			return nil, err
 		}
 	}
-	if code == typesinternal.UnsupportedFeature {
-		if match := unsupportedFeatureRe.FindStringSubmatch(e.primary.Msg); match != nil {
-			diag.SuggestedFixes, err = editGoDirectiveQuickFix(snapshot, spn.URI(), match[1])
-			if err != nil {
-				return nil, err
-			}
+	if match := unsupportedFeatureRe.FindStringSubmatch(e.primary.Msg); match != nil {
+		diag.SuggestedFixes, err = editGoDirectiveQuickFix(snapshot, spn.URI(), match[1])
+		if err != nil {
+			return nil, err
 		}
 	}
 	return []*source.Diagnostic{diag}, nil
diff --git a/internal/lsp/cache/mod.go b/internal/lsp/cache/mod.go
index b555b9a..8a2d42a 100644
--- a/internal/lsp/cache/mod.go
+++ b/internal/lsp/cache/mod.go
@@ -73,13 +73,13 @@
 				if err != nil {
 					return &parseModData{err: err}
 				}
-				parseErrors = []*source.Diagnostic{{
+				parseErrors = append(parseErrors, &source.Diagnostic{
 					URI:      modFH.URI(),
 					Range:    rng,
 					Severity: protocol.SeverityError,
 					Source:   source.ParseError,
 					Message:  mfErr.Err.Error(),
-				}}
+				})
 			}
 		}
 		return &parseModData{
@@ -131,7 +131,7 @@
 
 		contents, err := modFH.Read()
 		if err != nil {
-			return &parseModData{err: err}
+			return &parseWorkData{err: err}
 		}
 		m := &protocol.ColumnMapper{
 			URI:       modFH.URI(),
@@ -144,20 +144,20 @@
 		if parseErr != nil {
 			mfErrList, ok := parseErr.(modfile.ErrorList)
 			if !ok {
-				return &parseModData{err: fmt.Errorf("unexpected parse error type %v", parseErr)}
+				return &parseWorkData{err: fmt.Errorf("unexpected parse error type %v", parseErr)}
 			}
 			for _, mfErr := range mfErrList {
 				rng, err := rangeFromPositions(m, mfErr.Pos, mfErr.Pos)
 				if err != nil {
-					return &parseModData{err: err}
+					return &parseWorkData{err: err}
 				}
-				parseErrors = []*source.Diagnostic{{
+				parseErrors = append(parseErrors, &source.Diagnostic{
 					URI:      modFH.URI(),
 					Range:    rng,
 					Severity: protocol.SeverityError,
 					Source:   source.ParseError,
 					Message:  mfErr.Err.Error(),
-				}}
+				})
 			}
 		}
 		return &parseWorkData{
diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go
index e786c02..11ef0f2 100644
--- a/internal/lsp/cache/snapshot.go
+++ b/internal/lsp/cache/snapshot.go
@@ -162,6 +162,10 @@
 	return uris
 }
 
+func (s *snapshot) WorkFile() span.URI {
+	return s.workspace.workFile
+}
+
 func (s *snapshot) Templates() map[span.URI]source.VersionedFileHandle {
 	s.mu.Lock()
 	defer s.mu.Unlock()
@@ -409,6 +413,8 @@
 			}
 		case source.WriteTemporaryModFile:
 			inv.ModFlag = mutableModFlag
+			// -mod must be readonly when using go.work files - see issue #48941
+			inv.Env = append(inv.Env, "GOWORK=off")
 		}
 	}
 
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index 6a9360a..13d3db3 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -832,7 +832,7 @@
 // TODO (rFindley): move this to workspace.go
 // TODO (rFindley): simplify this once workspace modules are enabled by default.
 func findWorkspaceRoot(ctx context.Context, folder span.URI, fs source.FileSource, excludePath func(string) bool, experimental bool) (span.URI, error) {
-	patterns := []string{"go.mod"}
+	patterns := []string{"go.work", "go.mod"}
 	if experimental {
 		patterns = []string{"go.work", "gopls.mod", "go.mod"}
 	}
@@ -882,7 +882,8 @@
 		if exists {
 			return span.URIFromPath(dir), nil
 		}
-		next, _ := filepath.Split(dir)
+		// Trailing separators must be trimmed, otherwise filepath.Split is a noop.
+		next, _ := filepath.Split(strings.TrimRight(dir, string(filepath.Separator)))
 		if next == dir {
 			break
 		}
diff --git a/internal/lsp/cache/view_test.go b/internal/lsp/cache/view_test.go
index ecd7e84..d76dcda 100644
--- a/internal/lsp/cache/view_test.go
+++ b/internal/lsp/cache/view_test.go
@@ -55,6 +55,8 @@
 module a
 -- a/x/x.go
 package x
+-- a/x/y/y.go
+package x
 -- b/go.mod --
 module b
 -- b/c/go.mod --
@@ -79,6 +81,7 @@
 		{"", "", false}, // no module at root, and more than one nested module
 		{"a", "a", false},
 		{"a/x", "a", false},
+		{"a/x/y", "a", false},
 		{"b/c", "b/c", false},
 		{"d", "d/e", false},
 		{"d", "d", true},
diff --git a/internal/lsp/cache/workspace.go b/internal/lsp/cache/workspace.go
index bfb45e1..16f97db 100644
--- a/internal/lsp/cache/workspace.go
+++ b/internal/lsp/cache/workspace.go
@@ -69,6 +69,9 @@
 	// In all modes except for legacy, this is equivalent to modFiles.
 	knownModFiles map[span.URI]struct{}
 
+	// workFile, if nonEmpty, is the go.work file for the workspace.
+	workFile span.URI
+
 	// The workspace module is lazily re-built once after being invalidated.
 	// buildMu+built guards this reconstruction.
 	//
@@ -101,9 +104,6 @@
 	// The user may have a gopls.mod or go.work file that defines their
 	// workspace.
 	if err := loadExplicitWorkspaceFile(ctx, ws, fs); err == nil {
-		if ws.mod == nil {
-			panic("BUG: explicit workspace file was not parsed")
-		}
 		return ws, nil
 	}
 
@@ -150,11 +150,15 @@
 		switch src {
 		case goWorkWorkspace:
 			file, activeModFiles, err = parseGoWork(ctx, ws.root, fh.URI(), contents, fs)
+			ws.workFile = fh.URI()
 		case goplsModWorkspace:
 			file, activeModFiles, err = parseGoplsMod(ws.root, fh.URI(), contents)
 		}
 		if err != nil {
-			return err
+			ws.buildMu.Lock()
+			ws.built = true
+			ws.buildErr = err
+			ws.buildMu.Unlock()
 		}
 		ws.mod = file
 		ws.activeModFiles = activeModFiles
@@ -278,6 +282,7 @@
 		moduleSource:   w.moduleSource,
 		knownModFiles:  make(map[span.URI]struct{}),
 		activeModFiles: make(map[span.URI]struct{}),
+		workFile:       w.workFile,
 		mod:            w.mod,
 		sum:            w.sum,
 		wsDirs:         w.wsDirs,
diff --git a/internal/lsp/cache/workspace_test.go b/internal/lsp/cache/workspace_test.go
index a03aedc..b809ad1 100644
--- a/internal/lsp/cache/workspace_test.go
+++ b/internal/lsp/cache/workspace_test.go
@@ -6,10 +6,12 @@
 
 import (
 	"context"
+	"errors"
 	"os"
 	"strings"
 	"testing"
 
+	"golang.org/x/mod/modfile"
 	"golang.org/x/tools/internal/lsp/fake"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/span"
@@ -309,6 +311,74 @@
 	}
 }
 
+func workspaceFromTxtar(t *testing.T, files string) (*workspace, func(), error) {
+	ctx := context.Background()
+	dir, err := fake.Tempdir(fake.UnpackTxt(files))
+	if err != nil {
+		return nil, func() {}, err
+	}
+	cleanup := func() {
+		os.RemoveAll(dir)
+	}
+	root := span.URIFromPath(dir)
+
+	fs := &osFileSource{}
+	excludeNothing := func(string) bool { return false }
+	workspace, err := newWorkspace(ctx, root, fs, excludeNothing, false, false)
+	return workspace, cleanup, err
+}
+
+func TestWorkspaceParseError(t *testing.T) {
+	w, cleanup, err := workspaceFromTxtar(t, `
+-- go.work --
+go 1.18
+
+usa ./typo
+-- typo/go.mod --
+module foo
+`)
+	defer cleanup()
+	if err != nil {
+		t.Fatalf("error creating workspace: %v; want no error", err)
+	}
+	w.buildMu.Lock()
+	built, buildErr := w.built, w.buildErr
+	w.buildMu.Unlock()
+	if !built || buildErr == nil {
+		t.Fatalf("built, buildErr: got %v, %v; want true, non-nil", built, buildErr)
+	}
+	var errList modfile.ErrorList
+	if !errors.As(buildErr, &errList) {
+		t.Fatalf("expected error to be an errorlist; got %v", buildErr)
+	}
+	if len(errList) != 1 {
+		t.Fatalf("expected errorList to have one element; got %v elements", len(errList))
+	}
+	parseErr := errList[0]
+	if parseErr.Pos.Line != 3 {
+		t.Fatalf("expected error to be on line 3; got %v", parseErr.Pos.Line)
+	}
+}
+
+func TestWorkspaceMissingModFile(t *testing.T) {
+	w, cleanup, err := workspaceFromTxtar(t, `
+-- go.work --
+go 1.18
+
+use ./missing
+`)
+	defer cleanup()
+	if err != nil {
+		t.Fatalf("error creating workspace: %v; want no error", err)
+	}
+	w.buildMu.Lock()
+	built, buildErr := w.built, w.buildErr
+	w.buildMu.Unlock()
+	if !built || buildErr == nil {
+		t.Fatalf("built, buildErr: got %v, %v; want true, non-nil", built, buildErr)
+	}
+}
+
 func checkState(ctx context.Context, t *testing.T, fs source.FileSource, rel fake.RelativeTo, got *workspace, want wsState) {
 	t.Helper()
 	if got.moduleSource != want.source {
diff --git a/internal/lsp/completion.go b/internal/lsp/completion.go
index 9dded40..5c88ed0 100644
--- a/internal/lsp/completion.go
+++ b/internal/lsp/completion.go
@@ -16,6 +16,7 @@
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/lsp/source/completion"
 	"golang.org/x/tools/internal/lsp/template"
+	"golang.org/x/tools/internal/lsp/work"
 	"golang.org/x/tools/internal/span"
 )
 
@@ -32,6 +33,12 @@
 		candidates, surrounding, err = completion.Completion(ctx, snapshot, fh, params.Position, params.Context)
 	case source.Mod:
 		candidates, surrounding = nil, nil
+	case source.Work:
+		cl, err := work.Completion(ctx, snapshot, fh, params.Position)
+		if err != nil {
+			break
+		}
+		return cl, nil
 	case source.Tmpl:
 		var cl *protocol.CompletionList
 		cl, err = template.Completion(ctx, snapshot, fh, params.Position, params.Context)
diff --git a/internal/lsp/diagnostics.go b/internal/lsp/diagnostics.go
index e352e4b..3bf8122 100644
--- a/internal/lsp/diagnostics.go
+++ b/internal/lsp/diagnostics.go
@@ -21,6 +21,7 @@
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/lsp/template"
+	"golang.org/x/tools/internal/lsp/work"
 	"golang.org/x/tools/internal/span"
 	"golang.org/x/tools/internal/xcontext"
 	errors "golang.org/x/xerrors"
@@ -35,6 +36,7 @@
 	analysisSource
 	typeCheckSource
 	orphanedSource
+	workSource
 )
 
 // A diagnosticReport holds results for a single diagnostic source.
@@ -210,6 +212,23 @@
 		s.storeDiagnostics(snapshot, id.URI, modSource, diags)
 	}
 
+	// Diagnose the go.work file, if it exists.
+	workReports, workErr := work.Diagnostics(ctx, snapshot)
+	if ctx.Err() != nil {
+		log.Trace.Log(ctx, "diagnose cancelled")
+		return
+	}
+	if workErr != nil {
+		event.Error(ctx, "warning: diagnose go.work", workErr, tag.Directory.Of(snapshot.View().Folder().Filename()), tag.Snapshot.Of(snapshot.ID()))
+	}
+	for id, diags := range workReports {
+		if id.URI == "" {
+			event.Error(ctx, "missing URI for work file diagnostics", fmt.Errorf("empty URI"), tag.Directory.Of(snapshot.View().Folder().Filename()))
+			continue
+		}
+		s.storeDiagnostics(snapshot, id.URI, workSource, diags)
+	}
+
 	// Diagnose all of the packages in the workspace.
 	wsPkgs, err := snapshot.ActivePackages(ctx)
 	if s.shouldIgnoreError(ctx, snapshot, err) {
diff --git a/internal/lsp/hover.go b/internal/lsp/hover.go
index e9a7d9a..d59f5db 100644
--- a/internal/lsp/hover.go
+++ b/internal/lsp/hover.go
@@ -11,6 +11,7 @@
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
 	"golang.org/x/tools/internal/lsp/template"
+	"golang.org/x/tools/internal/lsp/work"
 )
 
 func (s *Server) hover(ctx context.Context, params *protocol.HoverParams) (*protocol.Hover, error) {
@@ -26,6 +27,8 @@
 		return source.Hover(ctx, snapshot, fh, params.Position)
 	case source.Tmpl:
 		return template.Hover(ctx, snapshot, fh, params.Position)
+	case source.Work:
+		return work.Hover(ctx, snapshot, fh, params.Position)
 	}
 	return nil, nil
 }
diff --git a/internal/lsp/mod/code_lens.go b/internal/lsp/mod/code_lens.go
index f18aaf7..b26bae7 100644
--- a/internal/lsp/mod/code_lens.go
+++ b/internal/lsp/mod/code_lens.go
@@ -14,7 +14,6 @@
 	"golang.org/x/tools/internal/lsp/command"
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 )
 
 // LensFuncs returns the supported lensFuncs for go.mod files.
@@ -129,7 +128,7 @@
 		return protocol.Range{}, fmt.Errorf("no module statement in %s", fh.URI())
 	}
 	syntax := pm.File.Module.Syntax
-	return lineToRange(pm.Mapper, fh.URI(), syntax.Start, syntax.End)
+	return source.LineToRange(pm.Mapper, fh.URI(), syntax.Start, syntax.End)
 }
 
 // firstRequireRange returns the range for the first "require" in the given
@@ -150,19 +149,5 @@
 	if start.Byte == 0 || firstRequire.Start.Byte < start.Byte {
 		start, end = firstRequire.Start, firstRequire.End
 	}
-	return lineToRange(pm.Mapper, fh.URI(), start, end)
-}
-
-func lineToRange(m *protocol.ColumnMapper, uri span.URI, start, end modfile.Position) (protocol.Range, error) {
-	line, col, err := m.Converter.ToPosition(start.Byte)
-	if err != nil {
-		return protocol.Range{}, err
-	}
-	s := span.NewPoint(line, col, start.Byte)
-	line, col, err = m.Converter.ToPosition(end.Byte)
-	if err != nil {
-		return protocol.Range{}, err
-	}
-	e := span.NewPoint(line, col, end.Byte)
-	return m.Range(span.New(uri, s, e))
+	return source.LineToRange(pm.Mapper, fh.URI(), start, end)
 }
diff --git a/internal/lsp/mod/diagnostics.go b/internal/lsp/mod/diagnostics.go
index 4b4d0cb..9c49d8b 100644
--- a/internal/lsp/mod/diagnostics.go
+++ b/internal/lsp/mod/diagnostics.go
@@ -61,7 +61,7 @@
 		if !ok || req.Mod.Version == ver {
 			continue
 		}
-		rng, err := lineToRange(pm.Mapper, fh.URI(), req.Syntax.Start, req.Syntax.End)
+		rng, err := source.LineToRange(pm.Mapper, fh.URI(), req.Syntax.Start, req.Syntax.End)
 		if err != nil {
 			return nil, err
 		}
diff --git a/internal/lsp/mod/hover.go b/internal/lsp/mod/hover.go
index 82ba20f..0837e2a 100644
--- a/internal/lsp/mod/hover.go
+++ b/internal/lsp/mod/hover.go
@@ -15,7 +15,6 @@
 	"golang.org/x/tools/internal/event"
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/lsp/source"
-	"golang.org/x/tools/internal/span"
 	errors "golang.org/x/xerrors"
 )
 
@@ -85,20 +84,10 @@
 	}
 
 	// Get the range to highlight for the hover.
-	line, col, err := pm.Mapper.Converter.ToPosition(startPos)
+	rng, err := source.ByteOffsetsToRange(pm.Mapper, fh.URI(), startPos, endPos)
 	if err != nil {
 		return nil, err
 	}
-	start := span.NewPoint(line, col, startPos)
-
-	line, col, err = pm.Mapper.Converter.ToPosition(endPos)
-	if err != nil {
-		return nil, err
-	}
-	end := span.NewPoint(line, col, endPos)
-
-	spn = span.New(fh.URI(), start, end)
-	rng, err := pm.Mapper.Range(spn)
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/lsp/source/completion/completion.go b/internal/lsp/source/completion/completion.go
index 30d277f..9088616 100644
--- a/internal/lsp/source/completion/completion.go
+++ b/internal/lsp/source/completion/completion.go
@@ -1238,6 +1238,13 @@
 		c.methodSetCache[methodSetKey{typ, addressable}] = mset
 	}
 
+	if typ.String() == "*testing.F" && addressable {
+		// is that a sufficient test? (or is more care needed?)
+		if c.fuzz(typ, mset, imp, cb, c.snapshot.FileSet()) {
+			return
+		}
+	}
+
 	for i := 0; i < mset.Len(); i++ {
 		cb(candidate{
 			obj:         mset.At(i).Obj(),
diff --git a/internal/lsp/source/completion/format.go b/internal/lsp/source/completion/format.go
index 62c481b..e674569 100644
--- a/internal/lsp/source/completion/format.go
+++ b/internal/lsp/source/completion/format.go
@@ -5,7 +5,6 @@
 package completion
 
 import (
-	"bytes"
 	"context"
 	"fmt"
 	"go/ast"
@@ -65,7 +64,7 @@
 		x := cand.obj.(*types.TypeName)
 		if named, ok := x.Type().(*types.Named); ok {
 			tp := typeparams.ForNamed(named)
-			label += string(formatTypeParams(tp))
+			label += source.FormatTypeParams(tp)
 			insert = label // maintain invariant above (label == insert)
 		}
 	}
@@ -131,7 +130,7 @@
 		case invoke:
 			if sig, ok := funcType.Underlying().(*types.Signature); ok {
 				s := source.NewSignature(ctx, c.snapshot, c.pkg, sig, nil, c.qf)
-				c.functionCallSnippet("", s.Params(), &snip)
+				c.functionCallSnippet("", s.TypeParams(), s.Params(), &snip)
 				if sig.Results().Len() == 1 {
 					funcType = sig.Results().At(0).Type()
 				}
@@ -308,7 +307,7 @@
 		}
 		item.Detail = "func" + sig.Format()
 		item.snippet = &snippet.Builder{}
-		c.functionCallSnippet(obj.Name(), sig.Params(), item.snippet)
+		c.functionCallSnippet(obj.Name(), sig.TypeParams(), sig.Params(), item.snippet)
 	case *types.TypeName:
 		if types.IsInterface(obj.Type()) {
 			item.Kind = protocol.InterfaceCompletion
@@ -339,19 +338,3 @@
 	}
 	return false
 }
-
-func formatTypeParams(tp *typeparams.TypeParamList) []byte {
-	var buf bytes.Buffer
-	if tp == nil || tp.Len() == 0 {
-		return nil
-	}
-	buf.WriteByte('[')
-	for i := 0; i < tp.Len(); i++ {
-		if i > 0 {
-			buf.WriteString(", ")
-		}
-		buf.WriteString(tp.At(i).Obj().Name())
-	}
-	buf.WriteByte(']')
-	return buf.Bytes()
-}
diff --git a/internal/lsp/source/completion/fuzz.go b/internal/lsp/source/completion/fuzz.go
new file mode 100644
index 0000000..5f5cd51
--- /dev/null
+++ b/internal/lsp/source/completion/fuzz.go
@@ -0,0 +1,140 @@
+// 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 completion
+
+import (
+	"fmt"
+	"go/ast"
+	"go/token"
+	"go/types"
+	"strings"
+
+	"golang.org/x/tools/internal/lsp/protocol"
+)
+
+// golang/go#51089
+// *testing.F deserves special treatment as member use is constrained:
+// The arguments to f.Fuzz are determined by the arguments to a previous f.Add
+// Inside f.Fuzz only f.Failed and f.Name are allowed.
+// PJW: are there other packages where we can deduce usage constraints?
+
+// if we find fuzz completions, then return true, as those are the only completions to offer
+func (c *completer) fuzz(typ types.Type, mset *types.MethodSet, imp *importInfo, cb func(candidate), fset *token.FileSet) bool {
+	// 1. inside f.Fuzz? (only f.Failed and f.Name)
+	// 2. possible completing f.Fuzz?
+	//    [Ident,SelectorExpr,Callexpr,ExprStmt,BlockiStmt,FuncDecl(Fuzz...)]
+	// 3. before f.Fuzz, same (for 2., offer choice when looking at an F)
+
+	// does the path contain FuncLit as arg to f.Fuzz CallExpr?
+	inside := false
+Loop:
+	for i, n := range c.path {
+		switch v := n.(type) {
+		case *ast.CallExpr:
+			if len(v.Args) != 1 {
+				continue Loop
+			}
+			if _, ok := v.Args[0].(*ast.FuncLit); !ok {
+				continue
+			}
+			if s, ok := v.Fun.(*ast.SelectorExpr); !ok || s.Sel.Name != "Fuzz" {
+				continue
+			}
+			if i > 2 { // avoid t.Fuzz itself in tests
+				inside = true
+				break Loop
+			}
+		}
+	}
+	if inside {
+		for i := 0; i < mset.Len(); i++ {
+			o := mset.At(i).Obj()
+			if o.Name() == "Failed" || o.Name() == "Name" {
+				cb(candidate{
+					obj:         o,
+					score:       stdScore,
+					imp:         imp,
+					addressable: true,
+				})
+			}
+		}
+		return true
+	}
+	// if it could be t.Fuzz, look for the preceding t.Add
+	id, ok := c.path[0].(*ast.Ident)
+	if ok && strings.HasPrefix("Fuzz", id.Name) {
+		var add *ast.CallExpr
+		f := func(n ast.Node) bool {
+			if n == nil {
+				return true
+			}
+			call, ok := n.(*ast.CallExpr)
+			if !ok {
+				return true
+			}
+			s, ok := call.Fun.(*ast.SelectorExpr)
+			if !ok {
+				return true
+			}
+			if s.Sel.Name != "Add" {
+				return true
+			}
+			// Sel.X should be of type *testing.F
+			got := c.pkg.GetTypesInfo().Types[s.X]
+			if got.Type.String() == "*testing.F" {
+				add = call
+			}
+			return false // because we're done...
+		}
+		// look at the enclosing FuzzFoo functions
+		if len(c.path) < 2 {
+			return false
+		}
+		n := c.path[len(c.path)-2]
+		if _, ok := n.(*ast.FuncDecl); !ok {
+			// the path should start with ast.File, ast.FuncDecl, ...
+			// but it didn't, so give up
+			return false
+		}
+		ast.Inspect(n, f)
+		if add == nil {
+			return true
+		}
+
+		lbl := "Fuzz(func(t *testing.T"
+		for i, a := range add.Args {
+			info := c.pkg.GetTypesInfo().TypeOf(a)
+			if info == nil {
+				return false // How could this happen, but better safe than panic.
+			}
+			lbl += fmt.Sprintf(", %c %s", 'a'+i, info)
+		}
+		lbl += ")"
+		xx := CompletionItem{
+			Label:         lbl,
+			InsertText:    lbl,
+			Kind:          protocol.FunctionCompletion,
+			Depth:         0,
+			Score:         10, // pretty confident the user should see this
+			Documentation: "argument types from f.Add",
+			obj:           nil,
+		}
+		c.items = append(c.items, xx)
+		for i := 0; i < mset.Len(); i++ {
+			o := mset.At(i).Obj()
+			if o.Name() != "Fuzz" {
+				cb(candidate{
+					obj:         o,
+					score:       stdScore,
+					imp:         imp,
+					addressable: true,
+				})
+			}
+		}
+		return true // done
+	}
+	// let the standard processing take care of it instead
+	return false
+}
diff --git a/internal/lsp/source/completion/snippet.go b/internal/lsp/source/completion/snippet.go
index 3649314..72c351f 100644
--- a/internal/lsp/source/completion/snippet.go
+++ b/internal/lsp/source/completion/snippet.go
@@ -49,7 +49,7 @@
 }
 
 // functionCallSnippets calculates the snippet for function calls.
-func (c *completer) functionCallSnippet(name string, params []string, snip *snippet.Builder) {
+func (c *completer) functionCallSnippet(name string, tparams, params []string, snip *snippet.Builder) {
 	// If there is no suffix then we need to reuse existing call parens
 	// "()" if present. If there is an identifier suffix then we always
 	// need to include "()" since we don't overwrite the suffix.
@@ -73,7 +73,26 @@
 		}
 	}
 
-	snip.WriteText(name + "(")
+	snip.WriteText(name)
+
+	if len(tparams) > 0 {
+		snip.WriteText("[")
+		if c.opts.placeholders {
+			for i, tp := range tparams {
+				if i > 0 {
+					snip.WriteText(", ")
+				}
+				snip.WritePlaceholder(func(b *snippet.Builder) {
+					b.WriteText(tp)
+				})
+			}
+		} else {
+			snip.WritePlaceholder(nil)
+		}
+		snip.WriteText("]")
+	}
+
+	snip.WriteText("(")
 
 	if c.opts.placeholders {
 		// A placeholder snippet turns "someFun<>" into "someFunc(<*i int*>, *s string*)".
diff --git a/internal/lsp/source/stub.go b/internal/lsp/source/stub.go
index 276bec1..6810f1d 100644
--- a/internal/lsp/source/stub.go
+++ b/internal/lsp/source/stub.go
@@ -20,6 +20,7 @@
 	"golang.org/x/tools/internal/lsp/analysis/stubmethods"
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/span"
+	"golang.org/x/tools/internal/typeparams"
 )
 
 func stubSuggestedFixFunc(ctx context.Context, snapshot Snapshot, fh VersionedFileHandle, rng protocol.Range) (*analysis.SuggestedFix, error) {
@@ -31,7 +32,7 @@
 	if err != nil {
 		return nil, fmt.Errorf("getNodes: %w", err)
 	}
-	si := stubmethods.GetStubInfo(pkg.GetTypesInfo(), nodes, pkg.GetTypes(), pos)
+	si := stubmethods.GetStubInfo(pkg.GetTypesInfo(), nodes, pos)
 	if si == nil {
 		return nil, fmt.Errorf("nil interface request")
 	}
@@ -134,7 +135,7 @@
 			_, err = methodsBuffer.Write(printStubMethod(methodData{
 				Method:    m.Name(),
 				Concrete:  getStubReceiver(si),
-				Interface: deduceIfaceName(si.Concrete.Obj().Pkg(), si.Interface.Pkg(), si.Concrete.Obj(), si.Interface),
+				Interface: deduceIfaceName(si.Concrete.Obj().Pkg(), si.Interface.Pkg(), si.Interface),
 				Signature: strings.TrimPrefix(sig, "func"),
 			}))
 			if err != nil {
@@ -159,13 +160,14 @@
 }
 
 // getStubReceiver returns the concrete type's name as a method receiver.
-// TODO(marwan-at-work): add type parameters to the receiver when the concrete type
-// is a generic one.
+// It accounts for type parameters if they exist.
 func getStubReceiver(si *stubmethods.StubInfo) string {
-	concrete := si.Concrete.Obj().Name()
+	var concrete string
 	if si.Pointer {
-		concrete = "*" + concrete
+		concrete += "*"
 	}
+	concrete += si.Concrete.Obj().Name()
+	concrete += FormatTypeParams(typeparams.ForNamed(si.Concrete))
 	return concrete
 }
 
@@ -203,7 +205,7 @@
 	return nil, fmt.Errorf("pkg %q not found", ifaceObj.Pkg().Path())
 }
 
-func deduceIfaceName(concretePkg, ifacePkg *types.Package, concreteObj, ifaceObj types.Object) string {
+func deduceIfaceName(concretePkg, ifacePkg *types.Package, ifaceObj types.Object) string {
 	if concretePkg.Path() == ifacePkg.Path() {
 		return ifaceObj.Name()
 	}
diff --git a/internal/lsp/source/types_format.go b/internal/lsp/source/types_format.go
index 7347648..34d5f28 100644
--- a/internal/lsp/source/types_format.go
+++ b/internal/lsp/source/types_format.go
@@ -39,10 +39,10 @@
 }
 
 type signature struct {
-	name, doc        string
-	params, results  []string
-	variadic         bool
-	needResultParens bool
+	name, doc                   string
+	typeParams, params, results []string
+	variadic                    bool
+	needResultParens            bool
 }
 
 func (s *signature) Format() string {
@@ -75,6 +75,10 @@
 	return b.String()
 }
 
+func (s *signature) TypeParams() []string {
+	return s.typeParams
+}
+
 func (s *signature) Params() []string {
 	return s.params
 }
@@ -168,8 +172,36 @@
 	return result, writeResultParens
 }
 
+// FormatTypeParams turns TypeParamList into its Go representation, such as:
+// [T, Y]. Note that it does not print constraints as this is mainly used for
+// formatting type params in method receivers.
+func FormatTypeParams(tparams *typeparams.TypeParamList) string {
+	if tparams == nil || tparams.Len() == 0 {
+		return ""
+	}
+	var buf bytes.Buffer
+	buf.WriteByte('[')
+	for i := 0; i < tparams.Len(); i++ {
+		if i > 0 {
+			buf.WriteString(", ")
+		}
+		buf.WriteString(tparams.At(i).Obj().Name())
+	}
+	buf.WriteByte(']')
+	return buf.String()
+}
+
 // NewSignature returns formatted signature for a types.Signature struct.
 func NewSignature(ctx context.Context, s Snapshot, pkg Package, sig *types.Signature, comment *ast.CommentGroup, qf types.Qualifier) *signature {
+	var tparams []string
+	tpList := typeparams.ForSignature(sig)
+	for i := 0; i < tpList.Len(); i++ {
+		tparam := tpList.At(i)
+		// TODO: is it possible to reuse the logic from FormatVarType here?
+		s := tparam.Obj().Name() + " " + tparam.Constraint().String()
+		tparams = append(tparams, s)
+	}
+
 	params := make([]string, 0, sig.Params().Len())
 	for i := 0; i < sig.Params().Len(); i++ {
 		el := sig.Params().At(i)
@@ -180,6 +212,7 @@
 		}
 		params = append(params, p)
 	}
+
 	var needResultParens bool
 	results := make([]string, 0, sig.Results().Len())
 	for i := 0; i < sig.Results().Len(); i++ {
@@ -209,6 +242,7 @@
 	}
 	return &signature{
 		doc:              d,
+		typeParams:       tparams,
 		params:           params,
 		results:          results,
 		variadic:         sig.Variadic(),
diff --git a/internal/lsp/source/util.go b/internal/lsp/source/util.go
index 962419b..71892ea 100644
--- a/internal/lsp/source/util.go
+++ b/internal/lsp/source/util.go
@@ -17,6 +17,7 @@
 	"strconv"
 	"strings"
 
+	"golang.org/x/mod/modfile"
 	"golang.org/x/tools/internal/lsp/protocol"
 	"golang.org/x/tools/internal/span"
 	errors "golang.org/x/xerrors"
@@ -563,3 +564,23 @@
 	size := tok.Pos(tok.Size())
 	return int(pos) >= tok.Base() && pos <= size
 }
+
+// LineToRange creates a Range spanning start and end.
+func LineToRange(m *protocol.ColumnMapper, uri span.URI, start, end modfile.Position) (protocol.Range, error) {
+	return ByteOffsetsToRange(m, uri, start.Byte, end.Byte)
+}
+
+// ByteOffsetsToRange creates a range spanning start and end.
+func ByteOffsetsToRange(m *protocol.ColumnMapper, uri span.URI, start, end int) (protocol.Range, error) {
+	line, col, err := m.Converter.ToPosition(start)
+	if err != nil {
+		return protocol.Range{}, err
+	}
+	s := span.NewPoint(line, col, start)
+	line, col, err = m.Converter.ToPosition(end)
+	if err != nil {
+		return protocol.Range{}, err
+	}
+	e := span.NewPoint(line, col, end)
+	return m.Range(span.New(uri, s, e))
+}
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index 9e9b035..4d7d411 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -130,6 +130,9 @@
 	// GoModForFile returns the URI of the go.mod file for the given URI.
 	GoModForFile(uri span.URI) span.URI
 
+	// WorkFile, if non-empty, is the go.work file for the workspace.
+	WorkFile() span.URI
+
 	// ParseWork is used to parse go.work files.
 	ParseWork(ctx context.Context, fh FileHandle) (*ParsedWorkFile, error)
 
@@ -656,6 +659,7 @@
 	OptimizationDetailsError DiagnosticSource = "optimizer details"
 	UpgradeNotification      DiagnosticSource = "upgrade available"
 	TemplateError            DiagnosticSource = "template"
+	WorkFileError            DiagnosticSource = "go.work file"
 )
 
 func AnalyzerErrorKind(name string) DiagnosticSource {
diff --git a/internal/lsp/testdata/snippets/func_snippets118.go.in b/internal/lsp/testdata/snippets/func_snippets118.go.in
new file mode 100644
index 0000000..d493368
--- /dev/null
+++ b/internal/lsp/testdata/snippets/func_snippets118.go.in
@@ -0,0 +1,19 @@
+// +build go1.18
+//go:build go1.18
+
+package snippets
+
+type SyncMap[K comparable, V any] struct{}
+
+func NewSyncMap[K comparable, V any]() (result *SyncMap[K, V]) { //@item(NewSyncMap, "NewSyncMap", "", "")
+	return
+}
+
+func Identity[P ~int](p P) P { //@item(Identity, "Identity", "", "")
+	return p
+}
+
+func _() {
+	_ = NewSyncM //@snippet(" //", NewSyncMap, "NewSyncMap[${1:}]()", "NewSyncMap[${1:K comparable}, ${2:V any}]()")
+	_ = Identi //@snippet(" //", Identity, "Identity[${1:}](${2:})", "Identity[${1:P ~int}](${2:p P})")
+}
diff --git a/internal/lsp/testdata/stub/stub_generic_receiver.go b/internal/lsp/testdata/stub/stub_generic_receiver.go
new file mode 100644
index 0000000..64e90fc
--- /dev/null
+++ b/internal/lsp/testdata/stub/stub_generic_receiver.go
@@ -0,0 +1,15 @@
+//go:build go1.18
+// +build go1.18
+
+package stub
+
+import "io"
+
+// This file tests that that the stub method generator accounts for concrete
+// types that have type parameters defined.
+var _ io.ReaderFrom = &genReader[string, int]{} //@suggestedfix("&genReader", "refactor.rewrite")
+
+type genReader[T, Y any] struct {
+	T T
+	Y Y
+}
diff --git a/internal/lsp/testdata/stub/stub_generic_receiver.go.golden b/internal/lsp/testdata/stub/stub_generic_receiver.go.golden
new file mode 100644
index 0000000..1fc7157
--- /dev/null
+++ b/internal/lsp/testdata/stub/stub_generic_receiver.go.golden
@@ -0,0 +1,22 @@
+-- suggestedfix_stub_generic_receiver_10_23 --
+//go:build go1.18
+// +build go1.18
+
+package stub
+
+import "io"
+
+// This file tests that that the stub method generator accounts for concrete
+// types that have type parameters defined.
+var _ io.ReaderFrom = &genReader[string, int]{} //@suggestedfix("&genReader", "refactor.rewrite")
+
+type genReader[T, Y any] struct {
+	T T
+	Y Y
+}
+
+// ReadFrom implements io.ReaderFrom
+func (*genReader[T, Y]) ReadFrom(r io.Reader) (n int64, err error) {
+	panic("unimplemented")
+}
+
diff --git a/internal/lsp/testdata/summary_go1.18.txt.golden b/internal/lsp/testdata/summary_go1.18.txt.golden
index be3fbc6..6bb0671 100644
--- a/internal/lsp/testdata/summary_go1.18.txt.golden
+++ b/internal/lsp/testdata/summary_go1.18.txt.golden
@@ -2,7 +2,7 @@
 CallHierarchyCount = 2
 CodeLensCount = 5
 CompletionsCount = 266
-CompletionSnippetCount = 107
+CompletionSnippetCount = 109
 UnimportedCompletionsCount = 5
 DeepCompletionsCount = 5
 FuzzyCompletionsCount = 8
@@ -13,7 +13,7 @@
 FormatCount = 6
 ImportCount = 8
 SemanticTokenCount = 3
-SuggestedFixCount = 61
+SuggestedFixCount = 62
 FunctionExtractionCount = 25
 MethodExtractionCount = 6
 DefinitionsCount = 108
diff --git a/internal/lsp/work/completion.go b/internal/lsp/work/completion.go
new file mode 100644
index 0000000..60b69f1
--- /dev/null
+++ b/internal/lsp/work/completion.go
@@ -0,0 +1,159 @@
+// 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 work
+
+import (
+	"context"
+	"go/token"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	"golang.org/x/tools/internal/event"
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/lsp/source"
+	errors "golang.org/x/xerrors"
+)
+
+func Completion(ctx context.Context, snapshot source.Snapshot, fh source.VersionedFileHandle, position protocol.Position) (*protocol.CompletionList, error) {
+	ctx, done := event.Start(ctx, "work.Completion")
+	defer done()
+
+	// Get the position of the cursor.
+	pw, err := snapshot.ParseWork(ctx, fh)
+	if err != nil {
+		return nil, errors.Errorf("getting go.work file handle: %w", err)
+	}
+	spn, err := pw.Mapper.PointSpan(position)
+	if err != nil {
+		return nil, errors.Errorf("computing cursor position: %w", err)
+	}
+	rng, err := spn.Range(pw.Mapper.Converter)
+	if err != nil {
+		return nil, errors.Errorf("computing range: %w", err)
+	}
+
+	// Find the use statement the user is in.
+	cursor := rng.Start - 1
+	use, pathStart, _ := usePath(pw, cursor)
+	if use == nil {
+		return &protocol.CompletionList{}, nil
+	}
+	completingFrom := use.Path[:cursor-token.Pos(pathStart)]
+
+	// We're going to find the completions of the user input
+	// (completingFrom) by doing a walk on the innermost directory
+	// of the given path, and comparing the found paths to make sure
+	// that they match the component of the path after the
+	// innermost directory.
+	//
+	// We'll maintain two paths when doing this: pathPrefixSlash
+	// is essentially the path the user typed in, and pathPrefixAbs
+	// is the path made absolute from the go.work directory.
+
+	pathPrefixSlash := completingFrom
+	pathPrefixAbs := filepath.FromSlash(pathPrefixSlash)
+	if !filepath.IsAbs(pathPrefixAbs) {
+		pathPrefixAbs = filepath.Join(filepath.Dir(pw.URI.Filename()), pathPrefixAbs)
+	}
+
+	// pathPrefixDir is the directory that will be walked to find matches.
+	// If pathPrefixSlash is not explicitly a directory boundary (is either equivalent to "." or
+	// ends in a separator) we need to examine its parent directory to find sibling files that
+	// match.
+	depthBound := 5
+	pathPrefixDir, pathPrefixBase := pathPrefixAbs, ""
+	pathPrefixSlashDir := pathPrefixSlash
+	if filepath.Clean(pathPrefixSlash) != "." && !strings.HasSuffix(pathPrefixSlash, "/") {
+		depthBound++
+		pathPrefixDir, pathPrefixBase = filepath.Split(pathPrefixAbs)
+		pathPrefixSlashDir = dirNonClean(pathPrefixSlash)
+	}
+
+	var completions []string
+	// Stop traversing deeper once we've hit 10k files to try to stay generally under 100ms.
+	const numSeenBound = 10000
+	var numSeen int
+	stopWalking := errors.New("hit numSeenBound")
+	err = filepath.Walk(pathPrefixDir, func(wpath string, info os.FileInfo, err error) error {
+		if numSeen > numSeenBound {
+			// Stop traversing if we hit bound.
+			return stopWalking
+		}
+		numSeen++
+
+		// rel is the path relative to pathPrefixDir.
+		// Make sure that it has pathPrefixBase as a prefix
+		// otherwise it won't match the beginning of the
+		// base component of the path the user typed in.
+		rel := strings.TrimPrefix(wpath[len(pathPrefixDir):], string(filepath.Separator))
+		if info.IsDir() && wpath != pathPrefixDir && !strings.HasPrefix(rel, pathPrefixBase) {
+			return filepath.SkipDir
+		}
+
+		// Check for a match (a module directory).
+		if filepath.Base(rel) == "go.mod" {
+			relDir := strings.TrimSuffix(dirNonClean(rel), string(os.PathSeparator))
+			completionPath := join(pathPrefixSlashDir, filepath.ToSlash(relDir))
+
+			if !strings.HasPrefix(completionPath, completingFrom) {
+				return nil
+			}
+			if strings.HasSuffix(completionPath, "/") {
+				// Don't suggest paths that end in "/". This happens
+				// when the input is a path that ends in "/" and
+				// the completion is empty.
+				return nil
+			}
+			completion := completionPath[len(completingFrom):]
+			if completingFrom == "" && !strings.HasPrefix(completion, "./") {
+				// Bias towards "./" prefixes.
+				completion = join(".", completion)
+			}
+
+			completions = append(completions, completion)
+		}
+
+		if depth := strings.Count(rel, string(filepath.Separator)); depth >= depthBound {
+			return filepath.SkipDir
+		}
+		return nil
+	})
+	if err != nil && !errors.Is(err, stopWalking) {
+		return nil, errors.Errorf("walking to find completions: %w", err)
+	}
+
+	sort.Strings(completions)
+
+	var items []protocol.CompletionItem
+	for _, c := range completions {
+		items = append(items, protocol.CompletionItem{
+			Label:      c,
+			InsertText: c,
+		})
+	}
+	return &protocol.CompletionList{Items: items}, nil
+}
+
+// dirNonClean is filepath.Dir, without the Clean at the end.
+func dirNonClean(path string) string {
+	vol := filepath.VolumeName(path)
+	i := len(path) - 1
+	for i >= len(vol) && !os.IsPathSeparator(path[i]) {
+		i--
+	}
+	return path[len(vol) : i+1]
+}
+
+func join(a, b string) string {
+	if a == "" {
+		return b
+	}
+	if b == "" {
+		return a
+	}
+	return strings.TrimSuffix(a, "/") + "/" + b
+}
diff --git a/internal/lsp/work/diagnostics.go b/internal/lsp/work/diagnostics.go
new file mode 100644
index 0000000..e583e60
--- /dev/null
+++ b/internal/lsp/work/diagnostics.go
@@ -0,0 +1,93 @@
+// 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 work
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"golang.org/x/mod/modfile"
+	"golang.org/x/tools/internal/event"
+	"golang.org/x/tools/internal/lsp/debug/tag"
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/lsp/source"
+	"golang.org/x/tools/internal/span"
+)
+
+func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.VersionedFileIdentity][]*source.Diagnostic, error) {
+	ctx, done := event.Start(ctx, "work.Diagnostics", tag.Snapshot.Of(snapshot.ID()))
+	defer done()
+
+	reports := map[source.VersionedFileIdentity][]*source.Diagnostic{}
+	uri := snapshot.WorkFile()
+	if uri == "" {
+		return nil, nil
+	}
+	fh, err := snapshot.GetVersionedFile(ctx, uri)
+	if err != nil {
+		return nil, err
+	}
+	reports[fh.VersionedFileIdentity()] = []*source.Diagnostic{}
+	diagnostics, err := DiagnosticsForWork(ctx, snapshot, fh)
+	if err != nil {
+		return nil, err
+	}
+	for _, d := range diagnostics {
+		fh, err := snapshot.GetVersionedFile(ctx, d.URI)
+		if err != nil {
+			return nil, err
+		}
+		reports[fh.VersionedFileIdentity()] = append(reports[fh.VersionedFileIdentity()], d)
+	}
+
+	return reports, nil
+}
+
+func DiagnosticsForWork(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]*source.Diagnostic, error) {
+	pw, err := snapshot.ParseWork(ctx, fh)
+	if err != nil {
+		if pw == nil || len(pw.ParseErrors) == 0 {
+			return nil, err
+		}
+		return pw.ParseErrors, nil
+	}
+
+	// Add diagnostic if a directory does not contain a module.
+	var diagnostics []*source.Diagnostic
+	for _, use := range pw.File.Use {
+		rng, err := source.LineToRange(pw.Mapper, fh.URI(), use.Syntax.Start, use.Syntax.End)
+		if err != nil {
+			return nil, err
+		}
+
+		modfh, err := snapshot.GetFile(ctx, modFileURI(pw, use))
+		if err != nil {
+			return nil, err
+		}
+		if _, err := modfh.Read(); err != nil && os.IsNotExist(err) {
+			diagnostics = append(diagnostics, &source.Diagnostic{
+				URI:      fh.URI(),
+				Range:    rng,
+				Severity: protocol.SeverityError,
+				Source:   source.UnknownError, // Do we need a new source for this?
+				Message:  fmt.Sprintf("directory %v does not contain a module", use.Path),
+			})
+		}
+	}
+	return diagnostics, nil
+}
+
+func modFileURI(pw *source.ParsedWorkFile, use *modfile.Use) span.URI {
+	workdir := filepath.Dir(pw.URI.Filename())
+
+	modroot := filepath.FromSlash(use.Path)
+	if !filepath.IsAbs(modroot) {
+		modroot = filepath.Join(workdir, modroot)
+	}
+
+	return span.URIFromPath(filepath.Join(modroot, "go.mod"))
+}
diff --git a/internal/lsp/work/hover.go b/internal/lsp/work/hover.go
new file mode 100644
index 0000000..1018fc1
--- /dev/null
+++ b/internal/lsp/work/hover.go
@@ -0,0 +1,91 @@
+// 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 work
+
+import (
+	"bytes"
+	"context"
+	"go/token"
+
+	"golang.org/x/mod/modfile"
+	"golang.org/x/tools/internal/event"
+	"golang.org/x/tools/internal/lsp/protocol"
+	"golang.org/x/tools/internal/lsp/source"
+	errors "golang.org/x/xerrors"
+)
+
+func Hover(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle, position protocol.Position) (*protocol.Hover, error) {
+	// We only provide hover information for the view's go.work file.
+	if fh.URI() != snapshot.WorkFile() {
+		return nil, nil
+	}
+
+	ctx, done := event.Start(ctx, "work.Hover")
+	defer done()
+
+	// Get the position of the cursor.
+	pw, err := snapshot.ParseWork(ctx, fh)
+	if err != nil {
+		return nil, errors.Errorf("getting go.work file handle: %w", err)
+	}
+	spn, err := pw.Mapper.PointSpan(position)
+	if err != nil {
+		return nil, errors.Errorf("computing cursor position: %w", err)
+	}
+	hoverRng, err := spn.Range(pw.Mapper.Converter)
+	if err != nil {
+		return nil, errors.Errorf("computing hover range: %w", err)
+	}
+
+	// Confirm that the cursor is inside a use statement, and then find
+	// the position of the use statement's directory path.
+	use, pathStart, pathEnd := usePath(pw, hoverRng.Start)
+
+	// The cursor position is not on a use statement.
+	if use == nil {
+		return nil, nil
+	}
+
+	// Get the mod file denoted by the use.
+	modfh, err := snapshot.GetFile(ctx, modFileURI(pw, use))
+	pm, err := snapshot.ParseMod(ctx, modfh)
+	if err != nil {
+		return nil, errors.Errorf("getting modfile handle: %w", err)
+	}
+	mod := pm.File.Module.Mod
+
+	// Get the range to highlight for the hover.
+	rng, err := source.ByteOffsetsToRange(pw.Mapper, fh.URI(), pathStart, pathEnd)
+	if err != nil {
+		return nil, err
+	}
+	options := snapshot.View().Options()
+	return &protocol.Hover{
+		Contents: protocol.MarkupContent{
+			Kind:  options.PreferredContentFormat,
+			Value: mod.Path,
+		},
+		Range: rng,
+	}, nil
+}
+
+func usePath(pw *source.ParsedWorkFile, pos token.Pos) (use *modfile.Use, pathStart, pathEnd int) {
+	for _, u := range pw.File.Use {
+		path := []byte(u.Path)
+		s, e := u.Syntax.Start.Byte, u.Syntax.End.Byte
+		i := bytes.Index(pw.Mapper.Content[s:e], path)
+		if i == -1 {
+			// This should not happen.
+			continue
+		}
+		// Shift the start position to the location of the
+		// module directory within the use statement.
+		pathStart, pathEnd = s+i, s+i+len(path)
+		if token.Pos(pathStart) <= pos && pos <= token.Pos(pathEnd) {
+			return u, pathStart, pathEnd
+		}
+	}
+	return nil, 0, 0
+}