[dev.cmdgo] all: merge master (67f7e16) into dev.cmdgo

Merge List:

+ 2021-08-27 67f7e16bcc encoding/gob: optimize decoding of []byte
+ 2021-08-27 2c60a99f72 cmd/compile/internal/syntax: make valid type parameter list in presence of errors
+ 2021-08-27 d350a66532 cmd/compile: eagerly CalcStructSize for synthetic ABI types
+ 2021-08-27 d7e2e2ec2b cmd/compile: delay fillinMethods to deal with mutually-recursive types
+ 2021-08-27 c927599783 cmd/compile: eliminate repetitive code
+ 2021-08-27 62f88b6dc8 cmd/compile: add types.RecalcSize
+ 2021-08-27 e7eee5e265 cmd/compile: remove ssagen/pgen_test.go
+ 2021-08-27 f153b6739b cmd/compile: use typecheck.InitUniverse in unit tests
+ 2021-08-26 967a8017f7 cmd/compile: move types init code into package types
+ 2021-08-26 af80af22b5 cmd/compile/internal/types2: do not declare new methods on instantiated types
+ 2021-08-26 03db2c2413 cmd/compile/internal/types2: implement TypeList.String (debugging support)
+ 2021-08-26 c9e05fdcf7 cmd/compile: fix reference to generic type needed by crawler
+ 2021-08-26 eb6a07fcf9 cmd/compile: unexport Type.Vargen
+ 2021-08-26 3836983779 cmd/compile/internal/types: unexport Type.Extra
+ 2021-08-26 1f8d4562de cmd/compile: change typecheck.iscmp into ir.Op.IsCmp

Change-Id: I95c040a0e984a13a3b12c50458148007221ee300
diff --git a/codereview.cfg b/codereview.cfg
index 77a74f1..c5bef5e 100644
--- a/codereview.cfg
+++ b/codereview.cfg
@@ -1 +1,2 @@
-branch: master
+branch: dev.cmdgo
+parent-branch: master
diff --git a/src/cmd/go.mod b/src/cmd/go.mod
index ccfff09..b12d199 100644
--- a/src/cmd/go.mod
+++ b/src/cmd/go.mod
@@ -7,7 +7,7 @@
 	github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639 // indirect
 	golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e
 	golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e // indirect
-	golang.org/x/mod v0.4.3-0.20210608190319-0f08993efd8a
+	golang.org/x/mod v0.4.3-0.20210723200715-e41a6a4f3b61
 	golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 // indirect
 	golang.org/x/term v0.0.0-20210503060354-a79de5458b56
 	golang.org/x/tools v0.1.6-0.20210809225032-337cebd2c151
diff --git a/src/cmd/go.sum b/src/cmd/go.sum
index f4d41f0..1db50ca 100644
--- a/src/cmd/go.sum
+++ b/src/cmd/go.sum
@@ -9,8 +9,8 @@
 golang.org/x/arch v0.0.0-20210502124803-cbf565b21d1e/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e h1:8foAy0aoO5GkqCvAEJ4VC4P3zksTg4X4aJCDpZzmgQI=
 golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
-golang.org/x/mod v0.4.3-0.20210608190319-0f08993efd8a h1:e8qnjKz4EE6OjRki9wTadWSIogINvq10sMcuBRORxMY=
-golang.org/x/mod v0.4.3-0.20210608190319-0f08993efd8a/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/mod v0.4.3-0.20210723200715-e41a6a4f3b61 h1:gQY3CVezomIImcWCpxp6Mhj+fXCOZ+gD8/88326LVqw=
+golang.org/x/mod v0.4.3-0.20210723200715-e41a6a4f3b61/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
 golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q=
 golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/src/cmd/go/alldocs.go b/src/cmd/go/alldocs.go
index 592ea49..425aa83 100644
--- a/src/cmd/go/alldocs.go
+++ b/src/cmd/go/alldocs.go
@@ -167,6 +167,14 @@
 // 		directory, but it is not accessed. When -modfile is specified, an
 // 		alternate go.sum file is also used: its path is derived from the
 // 		-modfile flag by trimming the ".mod" extension and appending ".sum".
+//   -workfile file
+//     in module aware mode, use the given go.work file as a workspace file.
+// 		By default or when -workfile is "auto", the go command searches for a
+// 		file named go.work in the current directory and then containing directories
+// 		until one is found. If a valid go.work file is found, the modules
+// 		specified will collectively be used as the main modules. If -workfile
+// 		is "off", or a go.work file is not found in "auto" mode, workspace
+// 		mode is disabled.
 // 	-overlay file
 // 		read a JSON config file that provides an overlay for build operations.
 // 		The file is a JSON struct with a single field, named 'Replace', that
@@ -1024,8 +1032,10 @@
 //
 // 	download    download modules to local cache
 // 	edit        edit go.mod from tools or scripts
+// 	editwork    edit go.work from tools or scripts
 // 	graph       print module requirement graph
 // 	init        initialize new module in current directory
+// 	initwork    initialize workspace file
 // 	tidy        add missing and remove unused modules
 // 	vendor      make vendored copy of dependencies
 // 	verify      verify dependencies have expected content
@@ -1182,6 +1192,77 @@
 // See https://golang.org/ref/mod#go-mod-edit for more about 'go mod edit'.
 //
 //
+// Edit go.work from tools or scripts
+//
+// Usage:
+//
+// 	go mod editwork [editing flags] [go.work]
+//
+// Editwork provides a command-line interface for editing go.work,
+// for use primarily by tools or scripts. It only reads go.work;
+// it does not look up information about the modules involved.
+// If no file is specified, editwork looks for a go.work file in the current
+// directory and its parent directories
+//
+// The editing flags specify a sequence of editing operations.
+//
+// The -fmt flag reformats the go.work file without making other changes.
+// This reformatting is also implied by any other modifications that use or
+// rewrite the go.mod file. The only time this flag is needed is if no other
+// flags are specified, as in 'go mod editwork -fmt'.
+//
+// The -directory=path and -dropdirectory=path flags
+// add and drop a directory from the go.work files set of module directories.
+//
+// The -replace=old[@v]=new[@v] flag adds a replacement of the given
+// module path and version pair. If the @v in old@v is omitted, a
+// replacement without a version on the left side is added, which applies
+// to all versions of the old module path. If the @v in new@v is omitted,
+// the new path should be a local module root directory, not a module
+// path. Note that -replace overrides any redundant replacements for old[@v],
+// so omitting @v will drop existing replacements for specific versions.
+//
+// The -dropreplace=old[@v] flag drops a replacement of the given
+// module path and version pair. If the @v is omitted, a replacement without
+// a version on the left side is dropped.
+//
+// The -directory, -dropdirectory, -replace, and -dropreplace,
+// editing flags may be repeated, and the changes are applied in the order given.
+//
+// The -go=version flag sets the expected Go language version.
+//
+// The -print flag prints the final go.work in its text format instead of
+// writing it back to go.mod.
+//
+// The -json flag prints the final go.work file in JSON format instead of
+// writing it back to go.mod. The JSON output corresponds to these Go types:
+//
+// 	type Module struct {
+// 		Path    string
+// 		Version string
+// 	}
+//
+// 	type GoWork struct {
+// 		Go        string
+// 		Directory []Directory
+// 		Replace   []Replace
+// 	}
+//
+// 	type Directory struct {
+// 		Path       string
+// 		ModulePath string
+// 	}
+//
+// 	type Replace struct {
+// 		Old Module
+// 		New Module
+// 	}
+//
+// See the workspaces design proposal at
+// https://go.googlesource.com/proposal/+/master/design/45713-workspace.md for
+// more information.
+//
+//
 // Print module requirement graph
 //
 // Usage:
@@ -1221,6 +1302,23 @@
 // See https://golang.org/ref/mod#go-mod-init for more about 'go mod init'.
 //
 //
+// Initialize workspace file
+//
+// Usage:
+//
+// 	go mod initwork [moddirs]
+//
+// go mod initwork initializes and writes a new go.work file in the current
+// directory, in effect creating a new workspace at the current directory.
+//
+// go mod initwork optionally accepts paths to the workspace modules as arguments.
+// If the argument is omitted, an empty workspace with no modules will be created.
+//
+// See the workspaces design proposal at
+// https://go.googlesource.com/proposal/+/master/design/45713-workspace.md for
+// more information.
+//
+//
 // Add missing and remove unused modules
 //
 // Usage:
diff --git a/src/cmd/go/internal/base/flag.go b/src/cmd/go/internal/base/flag.go
index 6914efa..7e5121b 100644
--- a/src/cmd/go/internal/base/flag.go
+++ b/src/cmd/go/internal/base/flag.go
@@ -62,6 +62,13 @@
 	flags.Var(explicitStringFlag{value: &cfg.BuildMod, explicit: &cfg.BuildModExplicit}, "mod", "")
 }
 
+// AddWorkfileFlag adds the workfile flag to the flag set. It enables workspace
+// mode for commands that support it by resetting the cfg.WorkFile variable
+// to "" (equivalent to auto) rather than off.
+func AddWorkfileFlag(flags *flag.FlagSet) {
+	flags.Var(explicitStringFlag{value: &cfg.WorkFile, explicit: &cfg.WorkFileExplicit}, "workfile", "")
+}
+
 // AddModCommonFlags adds the module-related flags common to build commands
 // and 'go mod' subcommands.
 func AddModCommonFlags(flags *flag.FlagSet) {
diff --git a/src/cmd/go/internal/cfg/cfg.go b/src/cmd/go/internal/cfg/cfg.go
index 57a3c1f..da616ee 100644
--- a/src/cmd/go/internal/cfg/cfg.go
+++ b/src/cmd/go/internal/cfg/cfg.go
@@ -47,8 +47,10 @@
 	BuildWork              bool // -work flag
 	BuildX                 bool // -x flag
 
-	ModCacheRW bool   // -modcacherw flag
-	ModFile    string // -modfile flag
+	ModCacheRW       bool   // -modcacherw flag
+	ModFile          string // -modfile flag
+	WorkFile         string // -workfile flag
+	WorkFileExplicit bool   // whether -workfile was set explicitly
 
 	CmdName string // "build", "install", "list", "mod tidy", etc.
 
diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go
index 483ce2a..d23d539 100644
--- a/src/cmd/go/internal/envcmd/env.go
+++ b/src/cmd/go/internal/envcmd/env.go
@@ -146,8 +146,9 @@
 // ExtraEnvVars returns environment variables that should not leak into child processes.
 func ExtraEnvVars() []cfg.EnvVar {
 	gomod := ""
+	modload.Init()
 	if modload.HasModRoot() {
-		gomod = filepath.Join(modload.ModRoot(), "go.mod")
+		gomod = modload.ModFilePath()
 	} else if modload.Enabled() {
 		gomod = os.DevNull
 	}
diff --git a/src/cmd/go/internal/get/get.go b/src/cmd/go/internal/get/get.go
index 836364e..075594b 100644
--- a/src/cmd/go/internal/get/get.go
+++ b/src/cmd/go/internal/get/get.go
@@ -225,7 +225,8 @@
 	base.ExitIfErrors()
 
 	var pkgs []string
-	for _, m := range search.ImportPathsQuiet(patterns) {
+	noModRoots := []string{}
+	for _, m := range search.ImportPathsQuiet(patterns, noModRoots) {
 		if len(m.Pkgs) == 0 && strings.Contains(m.Pattern(), "...") {
 			pkgs = append(pkgs, m.Pattern())
 		} else {
@@ -315,7 +316,8 @@
 		if wildcardOkay && strings.Contains(arg, "...") {
 			match := search.NewMatch(arg)
 			if match.IsLocal() {
-				match.MatchDirs()
+				noModRoots := []string{} // We're in gopath mode, so there are no modroots.
+				match.MatchDirs(noModRoots)
 				args = match.Dirs
 			} else {
 				match.MatchPackages()
diff --git a/src/cmd/go/internal/list/list.go b/src/cmd/go/internal/list/list.go
index 4b8c0e9..704d61e 100644
--- a/src/cmd/go/internal/list/list.go
+++ b/src/cmd/go/internal/list/list.go
@@ -316,6 +316,7 @@
 func init() {
 	CmdList.Run = runList // break init cycle
 	work.AddBuildFlags(CmdList, work.DefaultBuildFlags)
+	base.AddWorkfileFlag(&CmdList.Flag)
 }
 
 var (
@@ -336,6 +337,8 @@
 var nl = []byte{'\n'}
 
 func runList(ctx context.Context, cmd *base.Command, args []string) {
+	modload.InitWorkfile()
+
 	if *listFmt != "" && *listJson == true {
 		base.Fatalf("go list -f cannot be used with -json")
 	}
diff --git a/src/cmd/go/internal/load/pkg.go b/src/cmd/go/internal/load/pkg.go
index e44e71b..f0613a4 100644
--- a/src/cmd/go/internal/load/pkg.go
+++ b/src/cmd/go/internal/load/pkg.go
@@ -1450,9 +1450,9 @@
 			// The importer is a list of command-line files.
 			// Pretend that the import path is the import path of the
 			// directory containing them.
-			// If the directory is outside the main module, this will resolve to ".",
+			// If the directory is outside the main modules, this will resolve to ".",
 			// which is not a prefix of any valid module.
-			importerPath = modload.DirImportPath(ctx, importer.Dir)
+			importerPath, _ = modload.MainModules.DirImportPath(ctx, importer.Dir)
 		}
 		parentOfInternal := p.ImportPath[:i]
 		if str.HasPathPrefix(importerPath, parentOfInternal) {
@@ -2447,7 +2447,8 @@
 		}
 		matches, _ = modload.LoadPackages(ctx, modOpts, patterns...)
 	} else {
-		matches = search.ImportPaths(patterns)
+		noModRoots := []string{}
+		matches = search.ImportPaths(patterns, noModRoots)
 	}
 
 	var (
diff --git a/src/cmd/go/internal/modcmd/download.go b/src/cmd/go/internal/modcmd/download.go
index 0e5af852..ff56d05 100644
--- a/src/cmd/go/internal/modcmd/download.go
+++ b/src/cmd/go/internal/modcmd/download.go
@@ -66,6 +66,7 @@
 	// TODO(jayconrod): https://golang.org/issue/35849 Apply -x to other 'go mod' commands.
 	cmdDownload.Flag.BoolVar(&cfg.BuildX, "x", false, "")
 	base.AddModCommonFlags(&cmdDownload.Flag)
+	base.AddWorkfileFlag(&cmdDownload.Flag)
 }
 
 type moduleJSON struct {
@@ -81,6 +82,8 @@
 }
 
 func runDownload(ctx context.Context, cmd *base.Command, args []string) {
+	modload.InitWorkfile()
+
 	// Check whether modules are enabled and whether we're in a module.
 	modload.ForceUseModules = true
 	if !modload.HasModRoot() && len(args) == 0 {
@@ -91,12 +94,18 @@
 		args = []string{"all"}
 	}
 	if modload.HasModRoot() {
-		modload.LoadModFile(ctx) // to fill Target
-		targetAtUpgrade := modload.Target.Path + "@upgrade"
-		targetAtPatch := modload.Target.Path + "@patch"
+		modload.LoadModFile(ctx) // to fill MainModules
+
+		if len(modload.MainModules.Versions()) != 1 {
+			panic(modload.TODOWorkspaces("Support workspace mode in go mod download"))
+		}
+		mainModule := modload.MainModules.Versions()[0]
+
+		targetAtUpgrade := mainModule.Path + "@upgrade"
+		targetAtPatch := mainModule.Path + "@patch"
 		for _, arg := range args {
 			switch arg {
-			case modload.Target.Path, targetAtUpgrade, targetAtPatch:
+			case mainModule.Path, targetAtUpgrade, targetAtPatch:
 				os.Stderr.WriteString("go mod download: skipping argument " + arg + " that resolves to the main module\n")
 			}
 		}
diff --git a/src/cmd/go/internal/modcmd/editwork.go b/src/cmd/go/internal/modcmd/editwork.go
new file mode 100644
index 0000000..f05d924
--- /dev/null
+++ b/src/cmd/go/internal/modcmd/editwork.go
@@ -0,0 +1,282 @@
+// 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.
+
+// go mod editwork
+
+package modcmd
+
+import (
+	"bytes"
+	"cmd/go/internal/base"
+	"cmd/go/internal/lockedfile"
+	"cmd/go/internal/modload"
+	"context"
+	"encoding/json"
+	"errors"
+	"os"
+	"strings"
+
+	"golang.org/x/mod/modfile"
+)
+
+var cmdEditwork = &base.Command{
+	UsageLine: "go mod editwork [editing flags] [go.work]",
+	Short:     "edit go.work from tools or scripts",
+	Long: `Editwork provides a command-line interface for editing go.work,
+for use primarily by tools or scripts. It only reads go.work;
+it does not look up information about the modules involved.
+If no file is specified, editwork looks for a go.work file in the current
+directory and its parent directories
+
+The editing flags specify a sequence of editing operations.
+
+The -fmt flag reformats the go.work file without making other changes.
+This reformatting is also implied by any other modifications that use or
+rewrite the go.mod file. The only time this flag is needed is if no other
+flags are specified, as in 'go mod editwork -fmt'.
+
+The -directory=path and -dropdirectory=path flags
+add and drop a directory from the go.work files set of module directories.
+
+The -replace=old[@v]=new[@v] flag adds a replacement of the given
+module path and version pair. If the @v in old@v is omitted, a
+replacement without a version on the left side is added, which applies
+to all versions of the old module path. If the @v in new@v is omitted,
+the new path should be a local module root directory, not a module
+path. Note that -replace overrides any redundant replacements for old[@v],
+so omitting @v will drop existing replacements for specific versions.
+
+The -dropreplace=old[@v] flag drops a replacement of the given
+module path and version pair. If the @v is omitted, a replacement without
+a version on the left side is dropped.
+
+The -directory, -dropdirectory, -replace, and -dropreplace,
+editing flags may be repeated, and the changes are applied in the order given.
+
+The -go=version flag sets the expected Go language version.
+
+The -print flag prints the final go.work in its text format instead of
+writing it back to go.mod.
+
+The -json flag prints the final go.work file in JSON format instead of
+writing it back to go.mod. The JSON output corresponds to these Go types:
+
+	type Module struct {
+		Path    string
+		Version string
+	}
+
+	type GoWork struct {
+		Go        string
+		Directory []Directory
+		Replace   []Replace
+	}
+
+	type Directory struct {
+		Path       string
+		ModulePath string
+	}
+
+	type Replace struct {
+		Old Module
+		New Module
+	}
+
+See the workspaces design proposal at
+https://go.googlesource.com/proposal/+/master/design/45713-workspace.md for
+more information.
+`,
+}
+
+var (
+	editworkFmt   = cmdEditwork.Flag.Bool("fmt", false, "")
+	editworkGo    = cmdEditwork.Flag.String("go", "", "")
+	editworkJSON  = cmdEditwork.Flag.Bool("json", false, "")
+	editworkPrint = cmdEditwork.Flag.Bool("print", false, "")
+	workedits     []func(file *modfile.WorkFile) // edits specified in flags
+)
+
+func init() {
+	cmdEditwork.Run = runEditwork // break init cycle
+
+	cmdEditwork.Flag.Var(flagFunc(flagEditworkDirectory), "directory", "")
+	cmdEditwork.Flag.Var(flagFunc(flagEditworkDropDirectory), "dropdirectory", "")
+	cmdEditwork.Flag.Var(flagFunc(flagEditworkReplace), "replace", "")
+	cmdEditwork.Flag.Var(flagFunc(flagEditworkDropReplace), "dropreplace", "")
+
+	base.AddWorkfileFlag(&cmdEditwork.Flag)
+}
+
+func runEditwork(ctx context.Context, cmd *base.Command, args []string) {
+	anyFlags :=
+		*editworkGo != "" ||
+			*editworkJSON ||
+			*editworkPrint ||
+			*editworkFmt ||
+			len(workedits) > 0
+
+	if !anyFlags {
+		base.Fatalf("go mod edit: no flags specified (see 'go help mod edit').")
+	}
+
+	if *editworkJSON && *editworkPrint {
+		base.Fatalf("go mod edit: cannot use both -json and -print")
+	}
+
+	if len(args) > 1 {
+		base.Fatalf("go mod edit: too many arguments")
+	}
+	var gowork string
+	if len(args) == 1 {
+		gowork = args[0]
+	} else {
+		modload.InitWorkfile()
+		gowork = modload.WorkFilePath()
+	}
+
+	if *editworkGo != "" {
+		if !modfile.GoVersionRE.MatchString(*editworkGo) {
+			base.Fatalf(`go mod: invalid -go option; expecting something like "-go %s"`, modload.LatestGoVersion())
+		}
+	}
+
+	data, err := lockedfile.Read(gowork)
+	if err != nil {
+		base.Fatalf("go: %v", err)
+	}
+
+	workFile, err := modfile.ParseWork(gowork, data, nil)
+	if err != nil {
+		base.Fatalf("go: errors parsing %s:\n%s", base.ShortPath(gowork), err)
+	}
+
+	if *editworkGo != "" {
+		if err := workFile.AddGoStmt(*editworkGo); err != nil {
+			base.Fatalf("go: internal error: %v", err)
+		}
+	}
+
+	if len(workedits) > 0 {
+		for _, edit := range workedits {
+			edit(workFile)
+		}
+	}
+	workFile.SortBlocks()
+	workFile.Cleanup() // clean file after edits
+
+	if *editworkJSON {
+		editworkPrintJSON(workFile)
+		return
+	}
+
+	out := modfile.Format(workFile.Syntax)
+
+	if *editworkPrint {
+		os.Stdout.Write(out)
+		return
+	}
+
+	err = lockedfile.Transform(gowork, func(lockedData []byte) ([]byte, error) {
+		if !bytes.Equal(lockedData, data) {
+			return nil, errors.New("go.work changed during editing; not overwriting")
+		}
+		return out, nil
+	})
+	if err != nil {
+		base.Fatalf("go: %v", err)
+	}
+}
+
+// flagEditworkDirectory implements the -directory flag.
+func flagEditworkDirectory(arg string) {
+	workedits = append(workedits, func(f *modfile.WorkFile) {
+		if err := f.AddDirectory(arg, ""); err != nil {
+			base.Fatalf("go mod: -directory=%s: %v", arg, err)
+		}
+	})
+}
+
+// flagEditworkDropDirectory implements the -dropdirectory flag.
+func flagEditworkDropDirectory(arg string) {
+	workedits = append(workedits, func(f *modfile.WorkFile) {
+		if err := f.DropDirectory(arg); err != nil {
+			base.Fatalf("go mod: -dropdirectory=%s: %v", arg, err)
+		}
+	})
+}
+
+// flagReplace implements the -replace flag.
+func flagEditworkReplace(arg string) {
+	var i int
+	if i = strings.Index(arg, "="); i < 0 {
+		base.Fatalf("go mod: -replace=%s: need old[@v]=new[@w] (missing =)", arg)
+	}
+	old, new := strings.TrimSpace(arg[:i]), strings.TrimSpace(arg[i+1:])
+	if strings.HasPrefix(new, ">") {
+		base.Fatalf("go mod: -replace=%s: separator between old and new is =, not =>", arg)
+	}
+	oldPath, oldVersion, err := parsePathVersionOptional("old", old, false)
+	if err != nil {
+		base.Fatalf("go mod: -replace=%s: %v", arg, err)
+	}
+	newPath, newVersion, err := parsePathVersionOptional("new", new, true)
+	if err != nil {
+		base.Fatalf("go mod: -replace=%s: %v", arg, err)
+	}
+	if newPath == new && !modfile.IsDirectoryPath(new) {
+		base.Fatalf("go mod: -replace=%s: unversioned new path must be local directory", arg)
+	}
+
+	workedits = append(workedits, func(f *modfile.WorkFile) {
+		if err := f.AddReplace(oldPath, oldVersion, newPath, newVersion); err != nil {
+			base.Fatalf("go mod: -replace=%s: %v", arg, err)
+		}
+	})
+}
+
+// flagDropReplace implements the -dropreplace flag.
+func flagEditworkDropReplace(arg string) {
+	path, version, err := parsePathVersionOptional("old", arg, true)
+	if err != nil {
+		base.Fatalf("go mod: -dropreplace=%s: %v", arg, err)
+	}
+	workedits = append(workedits, func(f *modfile.WorkFile) {
+		if err := f.DropReplace(path, version); err != nil {
+			base.Fatalf("go mod: -dropreplace=%s: %v", arg, err)
+		}
+	})
+}
+
+// editPrintJSON prints the -json output.
+func editworkPrintJSON(workFile *modfile.WorkFile) {
+	var f workfileJSON
+	if workFile.Go != nil {
+		f.Go = workFile.Go.Version
+	}
+	for _, d := range workFile.Directory {
+		f.Directory = append(f.Directory, directoryJSON{DiskPath: d.Path, ModPath: d.ModulePath})
+	}
+
+	for _, r := range workFile.Replace {
+		f.Replace = append(f.Replace, replaceJSON{r.Old, r.New})
+	}
+	data, err := json.MarshalIndent(&f, "", "\t")
+	if err != nil {
+		base.Fatalf("go: internal error: %v", err)
+	}
+	data = append(data, '\n')
+	os.Stdout.Write(data)
+}
+
+// workfileJSON is the -json output data structure.
+type workfileJSON struct {
+	Go        string `json:",omitempty"`
+	Directory []directoryJSON
+	Replace   []replaceJSON
+}
+
+type directoryJSON struct {
+	DiskPath string
+	ModPath  string `json:",omitempty"`
+}
diff --git a/src/cmd/go/internal/modcmd/graph.go b/src/cmd/go/internal/modcmd/graph.go
index ac81f26..2cbabae 100644
--- a/src/cmd/go/internal/modcmd/graph.go
+++ b/src/cmd/go/internal/modcmd/graph.go
@@ -42,9 +42,12 @@
 func init() {
 	cmdGraph.Flag.Var(&graphGo, "go", "")
 	base.AddModCommonFlags(&cmdGraph.Flag)
+	base.AddWorkfileFlag(&cmdGraph.Flag)
 }
 
 func runGraph(ctx context.Context, cmd *base.Command, args []string) {
+	modload.InitWorkfile()
+
 	if len(args) > 0 {
 		base.Fatalf("go mod graph: graph takes no arguments")
 	}
diff --git a/src/cmd/go/internal/modcmd/initwork.go b/src/cmd/go/internal/modcmd/initwork.go
new file mode 100644
index 0000000..4182aa0
--- /dev/null
+++ b/src/cmd/go/internal/modcmd/initwork.go
@@ -0,0 +1,54 @@
+// 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.
+
+// go mod initwork
+
+package modcmd
+
+import (
+	"cmd/go/internal/base"
+	"cmd/go/internal/modload"
+	"context"
+	"path/filepath"
+)
+
+var _ = modload.TODOWorkspaces("Add more documentation below. Though this is" +
+	"enough for those trying workspaces out, there should be more through" +
+	"documentation if the proposal is accepted and released.")
+
+var cmdInitwork = &base.Command{
+	UsageLine: "go mod initwork [moddirs]",
+	Short:     "initialize workspace file",
+	Long: `go mod initwork initializes and writes a new go.work file in the current
+directory, in effect creating a new workspace at the current directory.
+
+go mod initwork optionally accepts paths to the workspace modules as arguments.
+If the argument is omitted, an empty workspace with no modules will be created.
+
+See the workspaces design proposal at
+https://go.googlesource.com/proposal/+/master/design/45713-workspace.md for
+more information.
+`,
+	Run: runInitwork,
+}
+
+func init() {
+	base.AddModCommonFlags(&cmdInitwork.Flag)
+	base.AddWorkfileFlag(&cmdInitwork.Flag)
+}
+
+func runInitwork(ctx context.Context, cmd *base.Command, args []string) {
+	modload.InitWorkfile()
+
+	modload.ForceUseModules = true
+
+	// TODO(matloob): support using the -workfile path
+	// To do that properly, we'll have to make the module directories
+	// make dirs relative to workFile path before adding the paths to
+	// the directory entries
+
+	workFile := filepath.Join(base.Cwd(), "go.work")
+
+	modload.CreateWorkFile(ctx, workFile, args)
+}
diff --git a/src/cmd/go/internal/modcmd/mod.go b/src/cmd/go/internal/modcmd/mod.go
index d72d0ca..29aad58 100644
--- a/src/cmd/go/internal/modcmd/mod.go
+++ b/src/cmd/go/internal/modcmd/mod.go
@@ -23,8 +23,10 @@
 	Commands: []*base.Command{
 		cmdDownload,
 		cmdEdit,
+		cmdEditwork,
 		cmdGraph,
 		cmdInit,
+		cmdInitwork,
 		cmdTidy,
 		cmdVendor,
 		cmdVerify,
diff --git a/src/cmd/go/internal/modcmd/vendor.go b/src/cmd/go/internal/modcmd/vendor.go
index 4e01dfa..1effcea 100644
--- a/src/cmd/go/internal/modcmd/vendor.go
+++ b/src/cmd/go/internal/modcmd/vendor.go
@@ -74,7 +74,7 @@
 	}
 	_, pkgs := modload.LoadPackages(ctx, loadOpts, "all")
 
-	vdir := filepath.Join(modload.ModRoot(), "vendor")
+	vdir := filepath.Join(modload.VendorDir())
 	if err := os.RemoveAll(vdir); err != nil {
 		base.Fatalf("go mod vendor: %v", err)
 	}
@@ -82,7 +82,7 @@
 	modpkgs := make(map[module.Version][]string)
 	for _, pkg := range pkgs {
 		m := modload.PackageModule(pkg)
-		if m.Path == "" || m == modload.Target {
+		if m.Path == "" || m.Version == "" && modload.MainModules.Contains(m.Path) {
 			continue
 		}
 		modpkgs[m] = append(modpkgs[m], pkg)
@@ -128,7 +128,8 @@
 	}
 
 	for _, m := range vendorMods {
-		line := moduleLine(m, modload.Replacement(m))
+		replacement, _ := modload.Replacement(m)
+		line := moduleLine(m, replacement)
 		io.WriteString(w, line)
 
 		goVersion := ""
diff --git a/src/cmd/go/internal/modcmd/verify.go b/src/cmd/go/internal/modcmd/verify.go
index 5a6eca3..14c4d76 100644
--- a/src/cmd/go/internal/modcmd/verify.go
+++ b/src/cmd/go/internal/modcmd/verify.go
@@ -39,9 +39,12 @@
 
 func init() {
 	base.AddModCommonFlags(&cmdVerify.Flag)
+	base.AddWorkfileFlag(&cmdVerify.Flag)
 }
 
 func runVerify(ctx context.Context, cmd *base.Command, args []string) {
+	modload.InitWorkfile()
+
 	if len(args) != 0 {
 		// NOTE(rsc): Could take a module pattern.
 		base.Fatalf("go mod verify: verify takes no arguments")
diff --git a/src/cmd/go/internal/modcmd/why.go b/src/cmd/go/internal/modcmd/why.go
index 3b14b27..eef5fa5 100644
--- a/src/cmd/go/internal/modcmd/why.go
+++ b/src/cmd/go/internal/modcmd/why.go
@@ -61,9 +61,11 @@
 func init() {
 	cmdWhy.Run = runWhy // break init cycle
 	base.AddModCommonFlags(&cmdWhy.Flag)
+	base.AddWorkfileFlag(&cmdWhy.Flag)
 }
 
 func runWhy(ctx context.Context, cmd *base.Command, args []string) {
+	modload.InitWorkfile()
 	modload.ForceUseModules = true
 	modload.RootMode = modload.NeedRoot
 
diff --git a/src/cmd/go/internal/modfetch/fetch.go b/src/cmd/go/internal/modfetch/fetch.go
index d3d30d9..408b286 100644
--- a/src/cmd/go/internal/modfetch/fetch.go
+++ b/src/cmd/go/internal/modfetch/fetch.go
@@ -693,19 +693,21 @@
 	return true
 }
 
+var ErrGoSumDirty = errors.New("updates to go.sum needed, disabled by -mod=readonly")
+
 // WriteGoSum writes the go.sum file if it needs to be updated.
 //
 // keep is used to check whether a newly added sum should be saved in go.sum.
 // It should have entries for both module content sums and go.mod sums
 // (version ends with "/go.mod"). Existing sums will be preserved unless they
 // have been marked for deletion with TrimGoSum.
-func WriteGoSum(keep map[module.Version]bool) {
+func WriteGoSum(keep map[module.Version]bool, readonly bool) error {
 	goSum.mu.Lock()
 	defer goSum.mu.Unlock()
 
 	// If we haven't read the go.sum file yet, don't bother writing it.
 	if !goSum.enabled {
-		return
+		return nil
 	}
 
 	// Check whether we need to add sums for which keep[m] is true or remove
@@ -723,10 +725,10 @@
 		}
 	}
 	if !dirty {
-		return
+		return nil
 	}
-	if cfg.BuildMod == "readonly" {
-		base.Fatalf("go: updates to go.sum needed, disabled by -mod=readonly")
+	if readonly {
+		return ErrGoSumDirty
 	}
 	if _, ok := fsys.OverlayPath(GoSumFile); ok {
 		base.Fatalf("go: updates to go.sum needed, but go.sum is part of the overlay specified with -overlay")
@@ -774,11 +776,12 @@
 	})
 
 	if err != nil {
-		base.Fatalf("go: updating go.sum: %v", err)
+		return fmt.Errorf("updating go.sum: %w", err)
 	}
 
 	goSum.status = make(map[modSum]modSumStatus)
 	goSum.overwrite = false
+	return nil
 }
 
 // TrimGoSum trims go.sum to contain only the modules needed for reproducible
diff --git a/src/cmd/go/internal/modget/get.go b/src/cmd/go/internal/modget/get.go
index 9672e55..37912ce 100644
--- a/src/cmd/go/internal/modget/get.go
+++ b/src/cmd/go/internal/modget/get.go
@@ -389,9 +389,11 @@
 
 		haveExternalExe := false
 		for _, pkg := range pkgs {
-			if pkg.Name == "main" && pkg.Module != nil && pkg.Module.Path != modload.Target.Path {
-				haveExternalExe = true
-				break
+			if pkg.Name == "main" && pkg.Module != nil {
+				if !modload.MainModules.Contains(pkg.Module.Path) {
+					haveExternalExe = true
+					break
+				}
 			}
 		}
 		if haveExternalExe {
@@ -675,7 +677,9 @@
 
 	if !q.isWildcard() {
 		q.pathOnce(q.pattern, func() pathSet {
-			if modload.HasModRoot() && q.pattern == modload.Target.Path {
+			hasModRoot := modload.HasModRoot()
+			if hasModRoot && modload.MainModules.Contains(q.pattern) {
+				v := module.Version{Path: q.pattern}
 				// The user has explicitly requested to downgrade their own module to
 				// version "none". This is not an entirely unreasonable request: it
 				// could plausibly mean “downgrade away everything that depends on any
@@ -686,7 +690,7 @@
 				// However, neither of those behaviors would be consistent with the
 				// plain meaning of the query. To try to reduce confusion, reject the
 				// query explicitly.
-				return errSet(&modload.QueryMatchesMainModuleError{Pattern: q.pattern, Query: q.version})
+				return errSet(&modload.QueryMatchesMainModulesError{MainModules: []module.Version{v}, Pattern: q.pattern, Query: q.version})
 			}
 
 			return pathSet{mod: module.Version{Path: q.pattern, Version: "none"}}
@@ -698,8 +702,8 @@
 			continue
 		}
 		q.pathOnce(curM.Path, func() pathSet {
-			if modload.HasModRoot() && curM == modload.Target {
-				return errSet(&modload.QueryMatchesMainModuleError{Pattern: q.pattern, Query: q.version})
+			if modload.HasModRoot() && curM.Version == "" && modload.MainModules.Contains(curM.Path) {
+				return errSet(&modload.QueryMatchesMainModulesError{MainModules: []module.Version{curM}, Pattern: q.pattern, Query: q.version})
 			}
 			return pathSet{mod: module.Version{Path: curM.Path, Version: "none"}}
 		})
@@ -718,12 +722,21 @@
 
 			// Absolute paths like C:\foo and relative paths like ../foo... are
 			// restricted to matching packages in the main module.
-			pkgPattern := modload.DirImportPath(ctx, q.pattern)
+			pkgPattern, mainModule := modload.MainModules.DirImportPath(ctx, q.pattern)
 			if pkgPattern == "." {
-				return errSet(fmt.Errorf("%s%s is not within module rooted at %s", q.pattern, absDetail, modload.ModRoot()))
+				modload.MustHaveModRoot()
+				var modRoots []string
+				for _, m := range modload.MainModules.Versions() {
+					modRoots = append(modRoots, modload.MainModules.ModRoot(m))
+				}
+				var plural string
+				if len(modRoots) != 1 {
+					plural = "s"
+				}
+				return errSet(fmt.Errorf("%s%s is not within module%s rooted at %s", q.pattern, absDetail, plural, strings.Join(modRoots, ", ")))
 			}
 
-			match := modload.MatchInModule(ctx, pkgPattern, modload.Target, imports.AnyTags())
+			match := modload.MatchInModule(ctx, pkgPattern, mainModule, imports.AnyTags())
 			if len(match.Errs) > 0 {
 				return pathSet{err: match.Errs[0]}
 			}
@@ -733,13 +746,14 @@
 					return errSet(fmt.Errorf("no package in current directory"))
 				}
 				if !q.isWildcard() {
-					return errSet(fmt.Errorf("%s%s is not a package in module rooted at %s", q.pattern, absDetail, modload.ModRoot()))
+					modload.MustHaveModRoot()
+					return errSet(fmt.Errorf("%s%s is not a package in module rooted at %s", q.pattern, absDetail, modload.MainModules.ModRoot(mainModule)))
 				}
 				search.WarnUnmatched([]*search.Match{match})
 				return pathSet{}
 			}
 
-			return pathSet{pkgMods: []module.Version{modload.Target}}
+			return pathSet{pkgMods: []module.Version{mainModule}}
 		})
 	}
 }
@@ -789,11 +803,12 @@
 				return pathSet{}
 			}
 
-			if curM.Path == modload.Target.Path && !versionOkForMainModule(q.version) {
+			if modload.MainModules.Contains(curM.Path) && !versionOkForMainModule(q.version) {
 				if q.matchesPath(curM.Path) {
-					return errSet(&modload.QueryMatchesMainModuleError{
-						Pattern: q.pattern,
-						Query:   q.version,
+					return errSet(&modload.QueryMatchesMainModulesError{
+						MainModules: []module.Version{curM},
+						Pattern:     q.pattern,
+						Query:       q.version,
 					})
 				}
 
@@ -1159,8 +1174,8 @@
 	}
 
 	opts.AllowPackage = func(ctx context.Context, path string, m module.Version) error {
-		if m.Path == "" || m == modload.Target {
-			// Packages in the standard library and main module are already at their
+		if m.Path == "" || m.Version == "" && modload.MainModules.Contains(m.Path) {
+			// Packages in the standard library and main modules are already at their
 			// latest (and only) available versions.
 			return nil
 		}
@@ -1370,11 +1385,11 @@
 			continue
 		}
 
-		if m.Path == modload.Target.Path {
-			if m.Version == modload.Target.Version {
+		if modload.MainModules.Contains(m.Path) {
+			if m.Version == "" {
 				return pathSet{}, true, m, true
 			}
-			// The main module can only be set to its own version.
+			// A main module can only be set to its own version.
 			continue
 		}
 
@@ -1610,7 +1625,7 @@
 		i := i
 		m := r.buildList[i]
 		mActual := m
-		if mRepl := modload.Replacement(m); mRepl.Path != "" {
+		if mRepl, _ := modload.Replacement(m); mRepl.Path != "" {
 			mActual = mRepl
 		}
 		old := module.Version{Path: m.Path, Version: r.initialVersion[m.Path]}
@@ -1618,7 +1633,7 @@
 			continue
 		}
 		oldActual := old
-		if oldRepl := modload.Replacement(old); oldRepl.Path != "" {
+		if oldRepl, _ := modload.Replacement(old); oldRepl.Path != "" {
 			oldActual = oldRepl
 		}
 		if mActual == oldActual || mActual.Version == "" || !modfetch.HaveSum(oldActual) {
@@ -1744,10 +1759,11 @@
 		panic("internal error: resolving a module.Version with an empty path")
 	}
 
-	if m.Path == modload.Target.Path && m.Version != modload.Target.Version {
-		reportError(q, &modload.QueryMatchesMainModuleError{
-			Pattern: q.pattern,
-			Query:   q.version,
+	if modload.MainModules.Contains(m.Path) && m.Version != "" {
+		reportError(q, &modload.QueryMatchesMainModulesError{
+			MainModules: []module.Version{{Path: m.Path}},
+			Pattern:     q.pattern,
+			Query:       q.version,
 		})
 		return
 	}
@@ -1775,7 +1791,7 @@
 
 	resolved := make([]module.Version, 0, len(r.resolvedVersion))
 	for mPath, rv := range r.resolvedVersion {
-		if mPath != modload.Target.Path {
+		if !modload.MainModules.Contains(mPath) {
 			resolved = append(resolved, module.Version{Path: mPath, Version: rv.version})
 		}
 	}
diff --git a/src/cmd/go/internal/modget/query.go b/src/cmd/go/internal/modget/query.go
index 0a66517..7604190 100644
--- a/src/cmd/go/internal/modget/query.go
+++ b/src/cmd/go/internal/modget/query.go
@@ -192,9 +192,9 @@
 			// TODO(bcmills): "all@none" seems like a totally reasonable way to
 			// request that we remove all module requirements, leaving only the main
 			// module and standard library. Perhaps we should implement that someday.
-			return &modload.QueryMatchesMainModuleError{
-				Pattern: q.pattern,
-				Query:   q.version,
+			return &modload.QueryUpgradesAllError{
+				MainModules: modload.MainModules.Versions(),
+				Query:       q.version,
 			}
 		}
 	}
diff --git a/src/cmd/go/internal/modload/build.go b/src/cmd/go/internal/modload/build.go
index 76e1ad5..0efd841 100644
--- a/src/cmd/go/internal/modload/build.go
+++ b/src/cmd/go/internal/modload/build.go
@@ -212,20 +212,20 @@
 // in rs (which may be nil to indicate that m was not loaded from a requirement
 // graph).
 func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode ListMode) *modinfo.ModulePublic {
-	if m == Target {
+	if m.Version == "" && MainModules.Contains(m.Path) {
 		info := &modinfo.ModulePublic{
 			Path:    m.Path,
 			Version: m.Version,
 			Main:    true,
 		}
-		if v, ok := rawGoVersion.Load(Target); ok {
+		if v, ok := rawGoVersion.Load(m); ok {
 			info.GoVersion = v.(string)
 		} else {
 			panic("internal error: GoVersion not set for main module")
 		}
-		if HasModRoot() {
-			info.Dir = ModRoot()
-			info.GoMod = ModFilePath()
+		if modRoot := MainModules.ModRoot(m); modRoot != "" {
+			info.Dir = modRoot
+			info.GoMod = modFilePath(modRoot)
 		}
 		return info
 	}
@@ -240,7 +240,7 @@
 	}
 
 	// completeFromModCache fills in the extra fields in m using the module cache.
-	completeFromModCache := func(m *modinfo.ModulePublic) {
+	completeFromModCache := func(m *modinfo.ModulePublic, replacedFrom string) {
 		checksumOk := func(suffix string) bool {
 			return rs == nil || m.Version == "" || cfg.BuildMod == "mod" ||
 				modfetch.HaveSum(module.Version{Path: m.Path, Version: m.Version + suffix})
@@ -259,7 +259,7 @@
 		if m.GoVersion == "" && checksumOk("/go.mod") {
 			// Load the go.mod file to determine the Go version, since it hasn't
 			// already been populated from rawGoVersion.
-			if summary, err := rawGoModSummary(mod); err == nil && summary.goVersion != "" {
+			if summary, err := rawGoModSummary(mod, replacedFrom); err == nil && summary.goVersion != "" {
 				m.GoVersion = summary.goVersion
 			}
 		}
@@ -289,11 +289,11 @@
 	if rs == nil {
 		// If this was an explicitly-versioned argument to 'go mod download' or
 		// 'go list -m', report the actual requested version, not its replacement.
-		completeFromModCache(info) // Will set m.Error in vendor mode.
+		completeFromModCache(info, "") // Will set m.Error in vendor mode.
 		return info
 	}
 
-	r := Replacement(m)
+	r, replacedFrom := Replacement(m)
 	if r.Path == "" {
 		if cfg.BuildMod == "vendor" {
 			// It's tempting to fill in the "Dir" field to point within the vendor
@@ -302,7 +302,7 @@
 			// interleave packages from different modules if one module path is a
 			// prefix of the other.
 		} else {
-			completeFromModCache(info)
+			completeFromModCache(info, "")
 		}
 		return info
 	}
@@ -322,12 +322,12 @@
 		if filepath.IsAbs(r.Path) {
 			info.Replace.Dir = r.Path
 		} else {
-			info.Replace.Dir = filepath.Join(ModRoot(), r.Path)
+			info.Replace.Dir = filepath.Join(replacedFrom, r.Path)
 		}
 		info.Replace.GoMod = filepath.Join(info.Replace.Dir, "go.mod")
 	}
 	if cfg.BuildMod != "vendor" {
-		completeFromModCache(info.Replace)
+		completeFromModCache(info.Replace, replacedFrom)
 		info.Dir = info.Replace.Dir
 		info.GoMod = info.Replace.GoMod
 		info.Retracted = info.Replace.Retracted
@@ -340,15 +340,14 @@
 // for modules providing packages named by path and deps. path and deps must
 // name packages that were resolved successfully with LoadPackages.
 func PackageBuildInfo(path string, deps []string) string {
-	if isStandardImportPath(path) || !Enabled() {
+	if !Enabled() {
 		return ""
 	}
-
-	target := mustFindModule(loaded, path, path)
+	target, _ := findModule(loaded, path)
 	mdeps := make(map[module.Version]bool)
 	for _, dep := range deps {
-		if !isStandardImportPath(dep) {
-			mdeps[mustFindModule(loaded, path, dep)] = true
+		if m, ok := findModule(loaded, dep); ok {
+			mdeps[m] = true
 		}
 	}
 	var mods []module.Version
@@ -367,14 +366,16 @@
 			mv = "(devel)"
 		}
 		fmt.Fprintf(&buf, "%s\t%s\t%s", token, m.Path, mv)
-		if r := Replacement(m); r.Path == "" {
+		if r, _ := Replacement(m); r.Path == "" {
 			fmt.Fprintf(&buf, "\t%s\n", modfetch.Sum(m))
 		} else {
 			fmt.Fprintf(&buf, "\n=>\t%s\t%s\t%s\n", r.Path, r.Version, modfetch.Sum(r))
 		}
 	}
 
-	writeEntry("mod", target)
+	if target.Path != "" {
+		writeEntry("mod", target)
+	}
 	for _, mod := range mods {
 		writeEntry("dep", mod)
 	}
@@ -382,38 +383,13 @@
 	return buf.String()
 }
 
-// mustFindModule is like findModule, but it calls base.Fatalf if the
-// module can't be found.
-//
-// TODO(jayconrod): remove this. Callers should use findModule and return
-// errors instead of relying on base.Fatalf.
-func mustFindModule(ld *loader, target, path string) module.Version {
-	pkg, ok := ld.pkgCache.Get(path).(*loadPkg)
-	if ok {
-		if pkg.err != nil {
-			base.Fatalf("build %v: cannot load %v: %v", target, path, pkg.err)
-		}
-		return pkg.mod
-	}
-
-	if path == "command-line-arguments" {
-		return Target
-	}
-
-	base.Fatalf("build %v: cannot find module for path %v", target, path)
-	panic("unreachable")
-}
-
 // findModule searches for the module that contains the package at path.
 // If the package was loaded, its containing module and true are returned.
-// Otherwise, module.Version{} and false are returend.
+// Otherwise, module.Version{} and false are returned.
 func findModule(ld *loader, path string) (module.Version, bool) {
 	if pkg, ok := ld.pkgCache.Get(path).(*loadPkg); ok {
 		return pkg.mod, pkg.mod != module.Version{}
 	}
-	if path == "command-line-arguments" {
-		return Target, true
-	}
 	return module.Version{}, false
 }
 
diff --git a/src/cmd/go/internal/modload/buildlist.go b/src/cmd/go/internal/modload/buildlist.go
index bf69567..94414278 100644
--- a/src/cmd/go/internal/modload/buildlist.go
+++ b/src/cmd/go/internal/modload/buildlist.go
@@ -40,7 +40,7 @@
 	depth modDepth
 
 	// rootModules is the set of module versions explicitly required by the main
-	// module, sorted and capped to length. It may contain duplicates, and may
+	// modules, sorted and capped to length. It may contain duplicates, and may
 	// contain multiple versions for a given module path.
 	rootModules    []module.Version
 	maxRootVersion map[string]string
@@ -99,8 +99,8 @@
 // *Requirements before any other method.
 func newRequirements(depth modDepth, rootModules []module.Version, direct map[string]bool) *Requirements {
 	for i, m := range rootModules {
-		if m == Target {
-			panic(fmt.Sprintf("newRequirements called with untrimmed build list: rootModules[%v] is Target", i))
+		if m.Version == "" && MainModules.Contains(m.Path) {
+			panic(fmt.Sprintf("newRequirements called with untrimmed build list: rootModules[%v] is a main module", i))
 		}
 		if m.Path == "" || m.Version == "" {
 			panic(fmt.Sprintf("bad requirement: rootModules[%v] = %v", i, m))
@@ -135,9 +135,14 @@
 func (rs *Requirements) initVendor(vendorList []module.Version) {
 	rs.graphOnce.Do(func() {
 		mg := &ModuleGraph{
-			g: mvs.NewGraph(cmpVersion, []module.Version{Target}),
+			g: mvs.NewGraph(cmpVersion, MainModules.Versions()),
 		}
 
+		if MainModules.Len() != 1 {
+			panic("There should be exactly one main module in Vendor mode.")
+		}
+		mainModule := MainModules.Versions()[0]
+
 		if rs.depth == lazy {
 			// The roots of a lazy module should already include every module in the
 			// vendor list, because the vendored modules are the same as those
@@ -158,7 +163,7 @@
 			// Now we can treat the rest of the module graph as effectively “pruned
 			// out”, like a more aggressive version of lazy loading: in vendor mode,
 			// the root requirements *are* the complete module graph.
-			mg.g.Require(Target, rs.rootModules)
+			mg.g.Require(mainModule, rs.rootModules)
 		} else {
 			// The transitive requirements of the main module are not in general available
 			// from the vendor directory, and we don't actually know how we got from
@@ -170,7 +175,7 @@
 			// graph, but still distinguishes between direct and indirect
 			// dependencies.
 			vendorMod := module.Version{Path: "vendor/modules.txt", Version: ""}
-			mg.g.Require(Target, append(rs.rootModules, vendorMod))
+			mg.g.Require(mainModule, append(rs.rootModules, vendorMod))
 			mg.g.Require(vendorMod, vendorList)
 		}
 
@@ -182,8 +187,8 @@
 // path, or the zero module.Version and ok=false if the module is not a root
 // dependency.
 func (rs *Requirements) rootSelected(path string) (version string, ok bool) {
-	if path == Target.Path {
-		return Target.Version, true
+	if MainModules.Contains(path) {
+		return "", true
 	}
 	if v, ok := rs.maxRootVersion[path]; ok {
 		return v, true
@@ -197,7 +202,7 @@
 // selection.
 func (rs *Requirements) hasRedundantRoot() bool {
 	for i, m := range rs.rootModules {
-		if m.Path == Target.Path || (i > 0 && m.Path == rs.rootModules[i-1].Path) {
+		if MainModules.Contains(m.Path) || (i > 0 && m.Path == rs.rootModules[i-1].Path) {
 			return true
 		}
 	}
@@ -274,10 +279,17 @@
 		mu       sync.Mutex // guards mg.g and hasError during loading
 		hasError bool
 		mg       = &ModuleGraph{
-			g: mvs.NewGraph(cmpVersion, []module.Version{Target}),
+			g: mvs.NewGraph(cmpVersion, MainModules.Versions()),
 		}
 	)
-	mg.g.Require(Target, roots)
+	for _, m := range MainModules.Versions() {
+		// Require all roots from all main modules.
+		_ = TODOWorkspaces("This flattens a level of the module graph, adding the dependencies " +
+			"of all main modules to a single requirements struct, and losing the information of which " +
+			"main module required which requirement. Rework the requirements struct and change this" +
+			"to reflect the structure of the main modules.")
+		mg.g.Require(m, roots)
+	}
 
 	var (
 		loadQueue    = par.NewQueue(runtime.GOMAXPROCS(0))
@@ -404,10 +416,12 @@
 }
 
 func (mg *ModuleGraph) allRootsSelected() bool {
-	roots, _ := mg.g.RequiredBy(Target)
-	for _, m := range roots {
-		if mg.Selected(m.Path) != m.Version {
-			return false
+	for _, mm := range MainModules.Versions() {
+		roots, _ := mg.g.RequiredBy(mm)
+		for _, m := range roots {
+			if mg.Selected(m.Path) != m.Version {
+				return false
+			}
 		}
 	}
 	return true
@@ -447,7 +461,7 @@
 		base.Fatalf("go: %v", err)
 	}
 
-	commitRequirements(ctx, modFileGoVersion(), rs)
+	commitRequirements(ctx, rs)
 	return mg
 }
 
@@ -513,7 +527,7 @@
 	if err != nil {
 		return false, err
 	}
-	commitRequirements(ctx, modFileGoVersion(), rs)
+	commitRequirements(ctx, rs)
 	return changed, err
 }
 
@@ -546,10 +560,11 @@
 // both retain the same versions of all packages in pkgs and satisfy the
 // lazy loading invariants (if applicable).
 func tidyRoots(ctx context.Context, rs *Requirements, pkgs []*loadPkg) (*Requirements, error) {
+	mainModule := MainModules.mustGetSingleMainModule()
 	if rs.depth == eager {
-		return tidyEagerRoots(ctx, rs.direct, pkgs)
+		return tidyEagerRoots(ctx, mainModule, rs.direct, pkgs)
 	}
-	return tidyLazyRoots(ctx, rs.direct, pkgs)
+	return tidyLazyRoots(ctx, mainModule, rs.direct, pkgs)
 }
 
 func updateRoots(ctx context.Context, direct map[string]bool, rs *Requirements, pkgs []*loadPkg, add []module.Version, rootsImported bool) (*Requirements, error) {
@@ -574,10 +589,10 @@
 // To ensure that the loading process eventually converges, the caller should
 // add any needed roots from the tidy root set (without removing existing untidy
 // roots) until the set of roots has converged.
-func tidyLazyRoots(ctx context.Context, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
+func tidyLazyRoots(ctx context.Context, mainModule module.Version, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
 	var (
 		roots        []module.Version
-		pathIncluded = map[string]bool{Target.Path: true}
+		pathIncluded = map[string]bool{mainModule.Path: true}
 	)
 	// We start by adding roots for every package in "all".
 	//
@@ -807,7 +822,7 @@
 			// We've added or upgraded one or more roots, so load the full module
 			// graph so that we can update those roots to be consistent with other
 			// requirements.
-			if cfg.BuildMod != "mod" {
+			if mustHaveCompleteRequirements() {
 				// Our changes to the roots may have moved dependencies into or out of
 				// the lazy-loading horizon, which could in turn change the selected
 				// versions of other modules. (Unlike for eager modules, for lazy
@@ -855,7 +870,9 @@
 		roots = make([]module.Version, 0, len(rs.rootModules))
 		rootsUpgraded = false
 		inRootPaths := make(map[string]bool, len(rs.rootModules)+1)
-		inRootPaths[Target.Path] = true
+		for _, mm := range MainModules.Versions() {
+			inRootPaths[mm.Path] = true
+		}
 		for _, m := range rs.rootModules {
 			if inRootPaths[m.Path] {
 				// This root specifies a redundant path. We already retained the
@@ -958,7 +975,7 @@
 // tidyEagerRoots returns a minimal set of root requirements that maintains the
 // selected version of every module that provided a package in pkgs, and
 // includes the selected version of every such module in direct as a root.
-func tidyEagerRoots(ctx context.Context, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
+func tidyEagerRoots(ctx context.Context, mainModule module.Version, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
 	var (
 		keep     []module.Version
 		keptPath = map[string]bool{}
@@ -981,7 +998,7 @@
 		}
 	}
 
-	min, err := mvs.Req(Target, rootPaths, &mvsReqs{roots: keep})
+	min, err := mvs.Req(mainModule, rootPaths, &mvsReqs{roots: keep})
 	if err != nil {
 		return nil, err
 	}
@@ -1011,7 +1028,7 @@
 		return rs, err
 	}
 
-	if cfg.BuildMod != "mod" {
+	if mustHaveCompleteRequirements() {
 		// Instead of actually updating the requirements, just check that no updates
 		// are needed.
 		if rs == nil {
@@ -1070,7 +1087,7 @@
 	// This is only for convenience and clarity for end users: in an eager module,
 	// the choice of explicit vs. implicit dependency has no impact on MVS
 	// selection (for itself or any other module).
-	keep := append(mg.BuildList()[1:], add...)
+	keep := append(mg.BuildList()[MainModules.Len():], add...)
 	for _, m := range keep {
 		if direct[m.Path] && !inRootPaths[m.Path] {
 			rootPaths = append(rootPaths, m.Path)
@@ -1078,16 +1095,25 @@
 		}
 	}
 
-	min, err := mvs.Req(Target, rootPaths, &mvsReqs{roots: keep})
-	if err != nil {
-		return rs, err
+	// TODO(matloob): Make roots into a map.
+	var roots []module.Version
+	for _, mainModule := range MainModules.Versions() {
+		min, err := mvs.Req(mainModule, rootPaths, &mvsReqs{roots: keep})
+		if err != nil {
+			return rs, err
+		}
+		roots = append(roots, min...)
 	}
-	if rs.depth == eager && reflect.DeepEqual(min, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) {
+	if MainModules.Len() > 1 {
+		module.Sort(roots)
+	}
+	if rs.depth == eager && reflect.DeepEqual(roots, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) {
 		// The root set is unchanged and rs was already eager, so keep rs to
 		// preserve its cached ModuleGraph (if any).
 		return rs, nil
 	}
-	return newRequirements(eager, min, direct), nil
+
+	return newRequirements(eager, roots, direct), nil
 }
 
 // convertDepth returns a version of rs with the given depth.
@@ -1117,5 +1143,5 @@
 	if err != nil {
 		return rs, err
 	}
-	return newRequirements(lazy, mg.BuildList()[1:], rs.direct), nil
+	return newRequirements(lazy, mg.BuildList()[MainModules.Len():], rs.direct), nil
 }
diff --git a/src/cmd/go/internal/modload/edit.go b/src/cmd/go/internal/modload/edit.go
index c350b9d..796721c 100644
--- a/src/cmd/go/internal/modload/edit.go
+++ b/src/cmd/go/internal/modload/edit.go
@@ -75,7 +75,7 @@
 		// We promote the modules in mustSelect to be explicit requirements.
 		var rootPaths []string
 		for _, m := range mustSelect {
-			if m.Version != "none" && m.Path != Target.Path {
+			if !MainModules.Contains(m.Path) && m.Version != "none" {
 				rootPaths = append(rootPaths, m.Path)
 			}
 		}
@@ -97,7 +97,7 @@
 			}
 		}
 
-		roots, err = mvs.Req(Target, rootPaths, &mvsReqs{roots: mods})
+		roots, err = mvs.Req(MainModules.mustGetSingleMainModule(), rootPaths, &mvsReqs{roots: mods})
 		if err != nil {
 			return nil, false, err
 		}
@@ -218,8 +218,8 @@
 		eagerUpgrades = tryUpgrade
 	} else {
 		for _, m := range tryUpgrade {
-			if m.Path == Target.Path {
-				// Target is already considered to be higher than any possible m, so we
+			if MainModules.Contains(m.Path) {
+				// The main module versions are already considered to be higher than any possible m, so we
 				// won't be upgrading to it anyway and there is no point scanning its
 				// dependencies.
 				continue
@@ -318,7 +318,7 @@
 
 	mods = make([]module.Version, 0, len(limiter.selected))
 	for path, v := range limiter.selected {
-		if v != "none" && path != Target.Path {
+		if v != "none" && !MainModules.Contains(path) {
 			mods = append(mods, module.Version{Path: path, Version: v})
 		}
 	}
@@ -334,7 +334,7 @@
 	}
 	mods = make([]module.Version, 0, len(limiter.selected))
 	for path, _ := range limiter.selected {
-		if path != Target.Path {
+		if !MainModules.Contains(path) {
 			if v := mg.Selected(path); v != "none" {
 				mods = append(mods, module.Version{Path: path, Version: v})
 			}
@@ -415,10 +415,14 @@
 // itself lazy, its unrestricted dependencies are skipped when scanning
 // requirements.
 func newVersionLimiter(depth modDepth, max map[string]string) *versionLimiter {
+	selected := make(map[string]string)
+	for _, m := range MainModules.Versions() {
+		selected[m.Path] = m.Version
+	}
 	return &versionLimiter{
 		depth:     depth,
 		max:       max,
-		selected:  map[string]string{Target.Path: Target.Version},
+		selected:  selected,
 		dqReason:  map[module.Version]dqState{},
 		requiring: map[module.Version][]module.Version{},
 	}
@@ -492,7 +496,7 @@
 // as is feasible, we don't want to retain test dependencies that are only
 // marginally relevant at best.
 func (l *versionLimiter) check(m module.Version, depth modDepth) dqState {
-	if m.Version == "none" || m == Target {
+	if m.Version == "none" || m == MainModules.mustGetSingleMainModule() {
 		// version "none" has no requirements, and the dependencies of Target are
 		// tautological.
 		return dqState{}
diff --git a/src/cmd/go/internal/modload/import.go b/src/cmd/go/internal/modload/import.go
index d2bbe5c..de47974 100644
--- a/src/cmd/go/internal/modload/import.go
+++ b/src/cmd/go/internal/modload/import.go
@@ -32,6 +32,8 @@
 	Module   module.Version
 	QueryErr error
 
+	ImportingMainModule module.Version
+
 	// isStd indicates whether we would expect to find the package in the standard
 	// library. This is normally true for all dotless import paths, but replace
 	// directives can cause us to treat the replaced paths as also being in
@@ -71,6 +73,9 @@
 		if e.QueryErr != nil {
 			return fmt.Sprintf("%s: %v", message, e.QueryErr)
 		}
+		if e.ImportingMainModule.Path != "" && e.ImportingMainModule != MainModules.ModContainingCWD() {
+			return fmt.Sprintf("%s; to add it:\n\tcd %s\n\tgo get %s", message, MainModules.ModRoot(e.ImportingMainModule), e.Path)
+		}
 		return fmt.Sprintf("%s; to add it:\n\tgo get %s", message, e.Path)
 	}
 
@@ -257,11 +262,13 @@
 	// Is the package in the standard library?
 	pathIsStd := search.IsStandardImportPath(path)
 	if pathIsStd && goroot.IsStandardPackage(cfg.GOROOT, cfg.BuildContext.Compiler, path) {
-		if targetInGorootSrc {
-			if dir, ok, err := dirInModule(path, targetPrefix, ModRoot(), true); err != nil {
-				return module.Version{}, dir, err
-			} else if ok {
-				return Target, dir, nil
+		for _, mainModule := range MainModules.Versions() {
+			if MainModules.InGorootSrc(mainModule) {
+				if dir, ok, err := dirInModule(path, MainModules.PathPrefix(mainModule), MainModules.ModRoot(mainModule), true); err != nil {
+					return module.Version{}, dir, err
+				} else if ok {
+					return mainModule, dir, nil
+				}
 			}
 		}
 		dir := filepath.Join(cfg.GOROOT, "src", path)
@@ -271,8 +278,10 @@
 	// -mod=vendor is special.
 	// Everything must be in the main module or the main module's vendor directory.
 	if cfg.BuildMod == "vendor" {
-		mainDir, mainOK, mainErr := dirInModule(path, targetPrefix, ModRoot(), true)
-		vendorDir, vendorOK, _ := dirInModule(path, "", filepath.Join(ModRoot(), "vendor"), false)
+		mainModule := MainModules.mustGetSingleMainModule()
+		modRoot := MainModules.ModRoot(mainModule)
+		mainDir, mainOK, mainErr := dirInModule(path, MainModules.PathPrefix(mainModule), modRoot, true)
+		vendorDir, vendorOK, _ := dirInModule(path, "", filepath.Join(modRoot, "vendor"), false)
 		if mainOK && vendorOK {
 			return module.Version{}, "", &AmbiguousImportError{importPath: path, Dirs: []string{mainDir, vendorDir}}
 		}
@@ -280,12 +289,12 @@
 		// Note that we're not checking that the package exists.
 		// We'll leave that for load.
 		if !vendorOK && mainDir != "" {
-			return Target, mainDir, nil
+			return mainModule, mainDir, nil
 		}
 		if mainErr != nil {
 			return module.Version{}, "", mainErr
 		}
-		readVendorList()
+		readVendorList(mainModule)
 		return vendorPkgModule[path], vendorDir, nil
 	}
 
@@ -410,69 +419,72 @@
 func queryImport(ctx context.Context, path string, rs *Requirements) (module.Version, error) {
 	// To avoid spurious remote fetches, try the latest replacement for each
 	// module (golang.org/issue/26241).
-	if index != nil {
-		var mods []module.Version
-		for mp, mv := range index.highestReplaced {
-			if !maybeInModule(path, mp) {
-				continue
-			}
-			if mv == "" {
-				// The only replacement is a wildcard that doesn't specify a version, so
-				// synthesize a pseudo-version with an appropriate major version and a
-				// timestamp below any real timestamp. That way, if the main module is
-				// used from within some other module, the user will be able to upgrade
-				// the requirement to any real version they choose.
-				if _, pathMajor, ok := module.SplitPathVersion(mp); ok && len(pathMajor) > 0 {
-					mv = module.ZeroPseudoVersion(pathMajor[1:])
-				} else {
-					mv = module.ZeroPseudoVersion("v0")
+	var mods []module.Version
+	for _, v := range MainModules.Versions() {
+		if index := MainModules.Index(v); index != nil {
+			for mp, mv := range index.highestReplaced {
+				if !maybeInModule(path, mp) {
+					continue
 				}
+				if mv == "" {
+					// The only replacement is a wildcard that doesn't specify a version, so
+					// synthesize a pseudo-version with an appropriate major version and a
+					// timestamp below any real timestamp. That way, if the main module is
+					// used from within some other module, the user will be able to upgrade
+					// the requirement to any real version they choose.
+					if _, pathMajor, ok := module.SplitPathVersion(mp); ok && len(pathMajor) > 0 {
+						mv = module.ZeroPseudoVersion(pathMajor[1:])
+					} else {
+						mv = module.ZeroPseudoVersion("v0")
+					}
+				}
+				mg, err := rs.Graph(ctx)
+				if err != nil {
+					return module.Version{}, err
+				}
+				if cmpVersion(mg.Selected(mp), mv) >= 0 {
+					// We can't resolve the import by adding mp@mv to the module graph,
+					// because the selected version of mp is already at least mv.
+					continue
+				}
+				mods = append(mods, module.Version{Path: mp, Version: mv})
 			}
-			mg, err := rs.Graph(ctx)
-			if err != nil {
-				return module.Version{}, err
-			}
-			if cmpVersion(mg.Selected(mp), mv) >= 0 {
-				// We can't resolve the import by adding mp@mv to the module graph,
-				// because the selected version of mp is already at least mv.
-				continue
-			}
-			mods = append(mods, module.Version{Path: mp, Version: mv})
 		}
+	}
 
-		// Every module path in mods is a prefix of the import path.
-		// As in QueryPattern, prefer the longest prefix that satisfies the import.
-		sort.Slice(mods, func(i, j int) bool {
-			return len(mods[i].Path) > len(mods[j].Path)
-		})
-		for _, m := range mods {
-			needSum := true
-			root, isLocal, err := fetch(ctx, m, needSum)
-			if err != nil {
-				if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) {
-					return module.Version{}, &ImportMissingSumError{importPath: path}
-				}
-				return module.Version{}, err
+	// Every module path in mods is a prefix of the import path.
+	// As in QueryPattern, prefer the longest prefix that satisfies the import.
+	sort.Slice(mods, func(i, j int) bool {
+		return len(mods[i].Path) > len(mods[j].Path)
+	})
+	for _, m := range mods {
+		needSum := true
+		root, isLocal, err := fetch(ctx, m, needSum)
+		if err != nil {
+			if sumErr := (*sumMissingError)(nil); errors.As(err, &sumErr) {
+				return module.Version{}, &ImportMissingSumError{importPath: path}
 			}
-			if _, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil {
-				return m, err
-			} else if ok {
-				if cfg.BuildMod == "readonly" {
-					return module.Version{}, &ImportMissingError{Path: path, replaced: m}
-				}
-				return m, nil
-			}
+			return module.Version{}, err
 		}
-		if len(mods) > 0 && module.CheckPath(path) != nil {
-			// The package path is not valid to fetch remotely,
-			// so it can only exist in a replaced module,
-			// and we know from the above loop that it is not.
-			return module.Version{}, &PackageNotInModuleError{
-				Mod:         mods[0],
-				Query:       "latest",
-				Pattern:     path,
-				Replacement: Replacement(mods[0]),
+		if _, ok, err := dirInModule(path, m.Path, root, isLocal); err != nil {
+			return m, err
+		} else if ok {
+			if cfg.BuildMod == "readonly" {
+				return module.Version{}, &ImportMissingError{Path: path, replaced: m}
 			}
+			return m, nil
+		}
+	}
+	if len(mods) > 0 && module.CheckPath(path) != nil {
+		// The package path is not valid to fetch remotely,
+		// so it can only exist in a replaced module,
+		// and we know from the above loop that it is not.
+		replacement, _ := Replacement(mods[0])
+		return module.Version{}, &PackageNotInModuleError{
+			Mod:         mods[0],
+			Query:       "latest",
+			Pattern:     path,
+			Replacement: replacement,
 		}
 	}
 
@@ -638,14 +650,14 @@
 // The isLocal return value reports whether the replacement,
 // if any, is local to the filesystem.
 func fetch(ctx context.Context, mod module.Version, needSum bool) (dir string, isLocal bool, err error) {
-	if mod == Target {
-		return ModRoot(), true, nil
+	if modRoot := MainModules.ModRoot(mod); modRoot != "" {
+		return modRoot, true, nil
 	}
-	if r := Replacement(mod); r.Path != "" {
+	if r, replacedFrom := Replacement(mod); r.Path != "" {
 		if r.Version == "" {
 			dir = r.Path
 			if !filepath.IsAbs(dir) {
-				dir = filepath.Join(ModRoot(), dir)
+				dir = filepath.Join(replacedFrom, dir)
 			}
 			// Ensure that the replacement directory actually exists:
 			// dirInModule does not report errors for missing modules,
@@ -667,7 +679,7 @@
 		mod = r
 	}
 
-	if HasModRoot() && cfg.BuildMod == "readonly" && needSum && !modfetch.HaveSum(mod) {
+	if HasModRoot() && cfg.BuildMod == "readonly" && !inWorkspaceMode() && needSum && !modfetch.HaveSum(mod) {
 		return "", false, module.VersionError(mod, &sumMissingError{})
 	}
 
diff --git a/src/cmd/go/internal/modload/init.go b/src/cmd/go/internal/modload/init.go
index 862b007..b845842 100644
--- a/src/cmd/go/internal/modload/init.go
+++ b/src/cmd/go/internal/modload/init.go
@@ -17,6 +17,7 @@
 	"path/filepath"
 	"strconv"
 	"strings"
+	"sync"
 
 	"cmd/go/internal/base"
 	"cmd/go/internal/cfg"
@@ -45,27 +46,169 @@
 	allowMissingModuleImports bool
 )
 
+func TODOWorkspaces(s string) error {
+	return fmt.Errorf("need to support this for workspaces: %s", s)
+}
+
 // Variables set in Init.
 var (
 	initialized bool
-	modRoot     string
-	gopath      string
+
+	// These are primarily used to initialize the MainModules, and should be
+	// eventually superceded by them but are still used in cases where the module
+	// roots are required but MainModules hasn't been initialized yet. Set to
+	// the modRoots of the main modules.
+	// modRoots != nil implies len(modRoots) > 0
+	modRoots          []string
+	gopath            string
+	workFileGoVersion string
 )
 
-// Variables set in initTarget (during {Load,Create}ModFile).
+// Variable set in InitWorkfile
 var (
-	Target module.Version
-
-	// targetPrefix is the path prefix for packages in Target, without a trailing
-	// slash. For most modules, targetPrefix is just Target.Path, but the
-	// standard-library module "std" has an empty prefix.
-	targetPrefix string
-
-	// targetInGorootSrc caches whether modRoot is within GOROOT/src.
-	// The "std" module is special within GOROOT/src, but not otherwise.
-	targetInGorootSrc bool
+	// Set to the path to the go.work file, or "" if workspace mode is disabled.
+	workFilePath string
 )
 
+type MainModuleSet struct {
+	// versions are the module.Version values of each of the main modules.
+	// For each of them, the Path fields are ordinary module paths and the Version
+	// fields are empty strings.
+	versions []module.Version
+
+	// modRoot maps each module in versions to its absolute filesystem path.
+	modRoot map[module.Version]string
+
+	// pathPrefix is the path prefix for packages in the module, without a trailing
+	// slash. For most modules, pathPrefix is just version.Path, but the
+	// standard-library module "std" has an empty prefix.
+	pathPrefix map[module.Version]string
+
+	// inGorootSrc caches whether modRoot is within GOROOT/src.
+	// The "std" module is special within GOROOT/src, but not otherwise.
+	inGorootSrc map[module.Version]bool
+
+	modFiles map[module.Version]*modfile.File
+
+	modContainingCWD module.Version
+
+	workFileGoVersion string
+
+	indexMu sync.Mutex
+	indices map[module.Version]*modFileIndex
+}
+
+func (mms *MainModuleSet) PathPrefix(m module.Version) string {
+	return mms.pathPrefix[m]
+}
+
+// Versions returns the module.Version values of each of the main modules.
+// For each of them, the Path fields are ordinary module paths and the Version
+// fields are empty strings.
+// Callers should not modify the returned slice.
+func (mms *MainModuleSet) Versions() []module.Version {
+	if mms == nil {
+		return nil
+	}
+	return mms.versions
+}
+
+func (mms *MainModuleSet) Contains(path string) bool {
+	if mms == nil {
+		return false
+	}
+	for _, v := range mms.versions {
+		if v.Path == path {
+			return true
+		}
+	}
+	return false
+}
+
+func (mms *MainModuleSet) ModRoot(m module.Version) string {
+	if mms == nil {
+		return ""
+	}
+	return mms.modRoot[m]
+}
+
+func (mms *MainModuleSet) InGorootSrc(m module.Version) bool {
+	if mms == nil {
+		return false
+	}
+	return mms.inGorootSrc[m]
+}
+
+func (mms *MainModuleSet) mustGetSingleMainModule() module.Version {
+	if mms == nil || len(mms.versions) == 0 {
+		panic("internal error: mustGetSingleMainModule called in context with no main modules")
+	}
+	if len(mms.versions) != 1 {
+		if inWorkspaceMode() {
+			panic("internal error: mustGetSingleMainModule called in workspace mode")
+		} else {
+			panic("internal error: multiple main modules present outside of workspace mode")
+		}
+	}
+	return mms.versions[0]
+}
+
+func (mms *MainModuleSet) GetSingleIndexOrNil() *modFileIndex {
+	if mms == nil {
+		return nil
+	}
+	if len(mms.versions) == 0 {
+		return nil
+	}
+	return mms.indices[mms.mustGetSingleMainModule()]
+}
+
+func (mms *MainModuleSet) Index(m module.Version) *modFileIndex {
+	mms.indexMu.Lock()
+	defer mms.indexMu.Unlock()
+	return mms.indices[m]
+}
+
+func (mms *MainModuleSet) SetIndex(m module.Version, index *modFileIndex) {
+	mms.indexMu.Lock()
+	defer mms.indexMu.Unlock()
+	mms.indices[m] = index
+}
+
+func (mms *MainModuleSet) ModFile(m module.Version) *modfile.File {
+	return mms.modFiles[m]
+}
+
+func (mms *MainModuleSet) Len() int {
+	if mms == nil {
+		return 0
+	}
+	return len(mms.versions)
+}
+
+// ModContainingCWD returns the main module containing the working directory,
+// or module.Version{} if none of the main modules contain the working
+// directory.
+func (mms *MainModuleSet) ModContainingCWD() module.Version {
+	return mms.modContainingCWD
+}
+
+// GoVersion returns the go version set on the single module, in module mode,
+// or the go.work file in workspace mode.
+func (mms *MainModuleSet) GoVersion() string {
+	if !inWorkspaceMode() {
+		return modFileGoVersion(mms.ModFile(mms.mustGetSingleMainModule()))
+	}
+	v := mms.workFileGoVersion
+	if v == "" {
+		// Fall back to 1.18 for go.work files.
+		v = "1.18"
+	}
+	return v
+}
+
+var MainModules *MainModuleSet
+
 type Root int
 
 const (
@@ -94,6 +237,7 @@
 // in go.mod, edit it before loading.
 func ModFile() *modfile.File {
 	Init()
+	modFile := MainModules.ModFile(MainModules.mustGetSingleMainModule())
 	if modFile == nil {
 		die()
 	}
@@ -105,6 +249,26 @@
 	return filepath.Join(gopath, "bin")
 }
 
+// InitWorkfile initializes the workFilePath variable for commands that
+// operate in workspace mode. It should not be called by other commands,
+// for example 'go mod tidy', that don't operate in workspace mode.
+func InitWorkfile() {
+	switch cfg.WorkFile {
+	case "off":
+		workFilePath = ""
+	case "", "auto":
+		workFilePath = findWorkspaceFile(base.Cwd())
+	default:
+		workFilePath = cfg.WorkFile
+	}
+}
+
+// WorkFilePath returns the path of the go.work file, or "" if not in
+// workspace mode. WorkFilePath must be called after InitWorkfile.
+func WorkFilePath() string {
+	return workFilePath
+}
+
 // Init determines whether module mode is enabled, locates the root of the
 // current module (if any), sets environment variables for Git subprocesses, and
 // configures the cfg, codehost, load, modfetch, and search packages for use
@@ -169,18 +333,18 @@
 	if os.Getenv("GCM_INTERACTIVE") == "" {
 		os.Setenv("GCM_INTERACTIVE", "never")
 	}
-
-	if modRoot != "" {
+	if modRoots != nil {
 		// modRoot set before Init was called ("go mod init" does this).
 		// No need to search for go.mod.
 	} else if RootMode == NoRoot {
 		if cfg.ModFile != "" && !base.InGOFLAGS("-modfile") {
 			base.Fatalf("go: -modfile cannot be used with commands that ignore the current module")
 		}
-		modRoot = ""
+		modRoots = nil
+	} else if inWorkspaceMode() {
+		// We're in workspace mode.
 	} else {
-		modRoot = findModuleRoot(base.Cwd())
-		if modRoot == "" {
+		if modRoot := findModuleRoot(base.Cwd()); modRoot == "" {
 			if cfg.ModFile != "" {
 				base.Fatalf("go: cannot find main module, but -modfile was set.\n\t-modfile cannot be used to set the module root directory.")
 			}
@@ -198,11 +362,12 @@
 			// will find it and get modules when they're not expecting them.
 			// It's a bit of a peculiar thing to disallow but quite mysterious
 			// when it happens. See golang.org/issue/26708.
-			modRoot = ""
 			fmt.Fprintf(os.Stderr, "go: warning: ignoring go.mod in system temp root %v\n", os.TempDir())
 			if !mustUseModules {
 				return
 			}
+		} else {
+			modRoots = []string{modRoot}
 		}
 	}
 	if cfg.ModFile != "" && !strings.HasSuffix(cfg.ModFile, ".mod") {
@@ -212,6 +377,9 @@
 	// We're in module mode. Set any global variables that need to be set.
 	cfg.ModulesEnabled = true
 	setDefaultBuildMod()
+	_ = TODOWorkspaces("In workspace mode, mod will not be readonly for go mod download," +
+		"verify, graph, and why. Implement support for go mod download and add test cases" +
+		"to ensure verify, graph, and why work properly.")
 	list := filepath.SplitList(cfg.BuildContext.GOPATH)
 	if len(list) == 0 || list[0] == "" {
 		base.Fatalf("missing $GOPATH")
@@ -221,7 +389,16 @@
 		base.Fatalf("$GOPATH/go.mod exists but should not")
 	}
 
-	if modRoot == "" {
+	if inWorkspaceMode() {
+		var err error
+		workFileGoVersion, modRoots, err = loadWorkFile(workFilePath)
+		if err != nil {
+			base.Fatalf("reading go.work: %v", err)
+		}
+		_ = TODOWorkspaces("Support falling back to individual module go.sum " +
+			"files for sums not in the workspace sum file.")
+		modfetch.GoSumFile = workFilePath + ".sum"
+	} else if modRoots == nil {
 		// We're in module mode, but not inside a module.
 		//
 		// Commands like 'go build', 'go run', 'go list' have no go.mod file to
@@ -240,8 +417,7 @@
 		//
 		// See golang.org/issue/32027.
 	} else {
-		modfetch.GoSumFile = strings.TrimSuffix(ModFilePath(), ".mod") + ".sum"
-		search.SetModRoot(modRoot)
+		modfetch.GoSumFile = strings.TrimSuffix(modFilePath(modRoots[0]), ".mod") + ".sum"
 	}
 }
 
@@ -255,7 +431,7 @@
 // be called until the command is installed and flags are parsed. Instead of
 // calling Init and Enabled, the main package can call this function.
 func WillBeEnabled() bool {
-	if modRoot != "" || cfg.ModulesEnabled {
+	if modRoots != nil || cfg.ModulesEnabled {
 		// Already enabled.
 		return true
 	}
@@ -297,16 +473,18 @@
 // (usually through MustModRoot).
 func Enabled() bool {
 	Init()
-	return modRoot != "" || cfg.ModulesEnabled
+	return modRoots != nil || cfg.ModulesEnabled
 }
 
-// ModRoot returns the root of the main module.
-// It calls base.Fatalf if there is no main module.
-func ModRoot() string {
-	if !HasModRoot() {
-		die()
+func VendorDir() string {
+	return filepath.Join(MainModules.ModRoot(MainModules.mustGetSingleMainModule()), "vendor")
+}
+
+func inWorkspaceMode() bool {
+	if !initialized {
+		panic("inWorkspaceMode called before modload.Init called")
 	}
-	return modRoot
+	return workFilePath != ""
 }
 
 // HasModRoot reports whether a main module is present.
@@ -314,17 +492,27 @@
 // does not require a main module.
 func HasModRoot() bool {
 	Init()
-	return modRoot != ""
+	return modRoots != nil
 }
 
-// ModFilePath returns the effective path of the go.mod file. Normally, this
-// "go.mod" in the directory returned by ModRoot, but the -modfile flag may
-// change its location. ModFilePath calls base.Fatalf if there is no main
-// module, even if -modfile is set.
-func ModFilePath() string {
+// MustHaveModRoot checks that a main module or main modules are present,
+// and calls base.Fatalf if there are no main modules.
+func MustHaveModRoot() {
+	Init()
 	if !HasModRoot() {
 		die()
 	}
+}
+
+// ModFilePath returns the path that would be used for the go.mod
+// file, if in module mode. ModFilePath calls base.Fatalf if there is no main
+// module, even if -modfile is set.
+func ModFilePath() string {
+	MustHaveModRoot()
+	return modFilePath(findModuleRoot(base.Cwd()))
+}
+
+func modFilePath(modRoot string) string {
 	if cfg.ModFile != "" {
 		return cfg.ModFile
 	}
@@ -365,6 +553,35 @@
 
 var errGoModDirty error = goModDirtyError{}
 
+func loadWorkFile(path string) (goVersion string, modRoots []string, err error) {
+	_ = TODOWorkspaces("Clean up and write back the go.work file: add module paths for workspace modules.")
+	workDir := filepath.Dir(path)
+	workData, err := lockedfile.Read(path)
+	if err != nil {
+		return "", nil, err
+	}
+	wf, err := modfile.ParseWork(path, workData, nil)
+	if err != nil {
+		return "", nil, err
+	}
+	if wf.Go != nil {
+		goVersion = wf.Go.Version
+	}
+	seen := map[string]bool{}
+	for _, d := range wf.Directory {
+		modRoot := d.Path
+		if !filepath.IsAbs(modRoot) {
+			modRoot = filepath.Join(workDir, modRoot)
+		}
+		if seen[modRoot] {
+			return "", nil, fmt.Errorf("path %s appears multiple times in workspace", modRoot)
+		}
+		seen[modRoot] = true
+		modRoots = append(modRoots, modRoot)
+	}
+	return goVersion, modRoots, nil
+}
+
 // LoadModFile sets Target and, if there is a main module, parses the initial
 // build list from its go.mod file.
 //
@@ -385,7 +602,7 @@
 func LoadModFile(ctx context.Context) *Requirements {
 	rs, needCommit := loadModFile(ctx)
 	if needCommit {
-		commitRequirements(ctx, modFileGoVersion(), rs)
+		commitRequirements(ctx, rs)
 	}
 	return rs
 }
@@ -402,87 +619,110 @@
 	}
 
 	Init()
-	if modRoot == "" {
-		Target = module.Version{Path: "command-line-arguments"}
-		targetPrefix = "command-line-arguments"
+	if len(modRoots) == 0 {
+		_ = TODOWorkspaces("Instead of creating a fake module with an empty modroot, make MainModules.Len() == 0 mean that we're in module mode but not inside any module.")
+		mainModule := module.Version{Path: "command-line-arguments"}
+		MainModules = makeMainModules([]module.Version{mainModule}, []string{""}, []*modfile.File{nil}, []*modFileIndex{nil}, "")
 		goVersion := LatestGoVersion()
-		rawGoVersion.Store(Target, goVersion)
+		rawGoVersion.Store(mainModule, goVersion)
 		requirements = newRequirements(modDepthFromGoVersion(goVersion), nil, nil)
 		return requirements, false
 	}
 
-	gomod := ModFilePath()
-	var data []byte
-	var err error
-	if gomodActual, ok := fsys.OverlayPath(gomod); ok {
-		// Don't lock go.mod if it's part of the overlay.
-		// On Plan 9, locking requires chmod, and we don't want to modify any file
-		// in the overlay. See #44700.
-		data, err = os.ReadFile(gomodActual)
-	} else {
-		data, err = lockedfile.Read(gomodActual)
-	}
-	if err != nil {
-		base.Fatalf("go: %v", err)
-	}
-
-	var fixed bool
-	f, err := modfile.Parse(gomod, data, fixVersion(ctx, &fixed))
-	if err != nil {
-		// Errors returned by modfile.Parse begin with file:line.
-		base.Fatalf("go: errors parsing go.mod:\n%s\n", err)
-	}
-	if f.Module == nil {
-		// No module declaration. Must add module path.
-		base.Fatalf("go: no module declaration in go.mod. To specify the module path:\n\tgo mod edit -module=example.com/mod")
-	}
-
-	modFile = f
-	initTarget(f.Module.Mod)
-	index = indexModFile(data, f, fixed)
-
-	if err := module.CheckImportPath(f.Module.Mod.Path); err != nil {
-		if pathErr, ok := err.(*module.InvalidPathError); ok {
-			pathErr.Kind = "module"
+	var modFiles []*modfile.File
+	var mainModules []module.Version
+	var indices []*modFileIndex
+	for _, modroot := range modRoots {
+		gomod := modFilePath(modroot)
+		var data []byte
+		var err error
+		if gomodActual, ok := fsys.OverlayPath(gomod); ok {
+			// Don't lock go.mod if it's part of the overlay.
+			// On Plan 9, locking requires chmod, and we don't want to modify any file
+			// in the overlay. See #44700.
+			data, err = os.ReadFile(gomodActual)
+		} else {
+			data, err = lockedfile.Read(gomodActual)
 		}
-		base.Fatalf("go: %v", err)
+		if err != nil {
+			base.Fatalf("go: %v", err)
+		}
+
+		var fixed bool
+		f, err := modfile.Parse(gomod, data, fixVersion(ctx, &fixed))
+		if err != nil {
+			// Errors returned by modfile.Parse begin with file:line.
+			base.Fatalf("go: errors parsing go.mod:\n%s\n", err)
+		}
+		if f.Module == nil {
+			// No module declaration. Must add module path.
+			base.Fatalf("go: no module declaration in go.mod. To specify the module path:\n\tgo mod edit -module=example.com/mod")
+		}
+
+		modFiles = append(modFiles, f)
+		mainModule := f.Module.Mod
+		mainModules = append(mainModules, mainModule)
+		indices = append(indices, indexModFile(data, f, mainModule, fixed))
+
+		if err := module.CheckImportPath(f.Module.Mod.Path); err != nil {
+			if pathErr, ok := err.(*module.InvalidPathError); ok {
+				pathErr.Kind = "module"
+			}
+			base.Fatalf("go: %v", err)
+		}
 	}
 
+	MainModules = makeMainModules(mainModules, modRoots, modFiles, indices, workFileGoVersion)
 	setDefaultBuildMod() // possibly enable automatic vendoring
-	rs = requirementsFromModFile()
+	rs = requirementsFromModFiles(ctx, modFiles)
+
+	if inWorkspaceMode() {
+		// We don't need to do anything for vendor or update the mod file so
+		// return early.
+
+		return rs, true
+	}
+
+	mainModule := MainModules.mustGetSingleMainModule()
+
 	if cfg.BuildMod == "vendor" {
-		readVendorList()
-		checkVendorConsistency()
+		readVendorList(mainModule)
+		index := MainModules.Index(mainModule)
+		modFile := MainModules.ModFile(mainModule)
+		checkVendorConsistency(index, modFile)
 		rs.initVendor(vendorList)
 	}
+
 	if rs.hasRedundantRoot() {
 		// If any module path appears more than once in the roots, we know that the
 		// go.mod file needs to be updated even though we have not yet loaded any
 		// transitive dependencies.
+		var err error
 		rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false)
 		if err != nil {
 			base.Fatalf("go: %v", err)
 		}
 	}
 
-	if index.goVersionV == "" {
+	if MainModules.Index(mainModule).goVersionV == "" {
 		// TODO(#45551): Do something more principled instead of checking
 		// cfg.CmdName directly here.
 		if cfg.BuildMod == "mod" && cfg.CmdName != "mod graph" && cfg.CmdName != "mod why" {
-			addGoStmt(LatestGoVersion())
+			addGoStmt(MainModules.ModFile(mainModule), mainModule, LatestGoVersion())
 			if go117EnableLazyLoading {
 				// We need to add a 'go' version to the go.mod file, but we must assume
 				// that its existing contents match something between Go 1.11 and 1.16.
 				// Go 1.11 through 1.16 have eager requirements, but the latest Go
 				// version uses lazy requirements instead — so we need to cnvert the
 				// requirements to be lazy.
+				var err error
 				rs, err = convertDepth(ctx, rs, lazy)
 				if err != nil {
 					base.Fatalf("go: %v", err)
 				}
 			}
 		} else {
-			rawGoVersion.Store(Target, modFileGoVersion())
+			rawGoVersion.Store(mainModule, modFileGoVersion(MainModules.ModFile(mainModule)))
 		}
 	}
 
@@ -500,9 +740,10 @@
 // exactly the same as in the legacy configuration (for example, we can't get
 // packages at multiple versions from the same module).
 func CreateModFile(ctx context.Context, modPath string) {
-	modRoot = base.Cwd()
+	modRoot := base.Cwd()
+	modRoots = []string{modRoot}
 	Init()
-	modFilePath := ModFilePath()
+	modFilePath := modFilePath(modRoot)
 	if _, err := fsys.Stat(modFilePath); err == nil {
 		base.Fatalf("go: %s already exists", modFilePath)
 	}
@@ -533,12 +774,12 @@
 	}
 
 	fmt.Fprintf(os.Stderr, "go: creating new go.mod: module %s\n", modPath)
-	modFile = new(modfile.File)
+	modFile := new(modfile.File)
 	modFile.AddModuleStmt(modPath)
-	initTarget(modFile.Module.Mod)
-	addGoStmt(LatestGoVersion()) // Add the go directive before converted module requirements.
+	MainModules = makeMainModules([]module.Version{modFile.Module.Mod}, []string{modRoot}, []*modfile.File{modFile}, []*modFileIndex{nil}, "")
+	addGoStmt(modFile, modFile.Module.Mod, LatestGoVersion()) // Add the go directive before converted module requirements.
 
-	convertedFrom, err := convertLegacyConfig(modPath)
+	convertedFrom, err := convertLegacyConfig(modFile, modRoot)
 	if convertedFrom != "" {
 		fmt.Fprintf(os.Stderr, "go: copying requirements from %s\n", base.ShortPath(convertedFrom))
 	}
@@ -546,12 +787,12 @@
 		base.Fatalf("go: %v", err)
 	}
 
-	rs := requirementsFromModFile()
+	rs := requirementsFromModFiles(ctx, []*modfile.File{modFile})
 	rs, err = updateRoots(ctx, rs.direct, rs, nil, nil, false)
 	if err != nil {
 		base.Fatalf("go: %v", err)
 	}
-	commitRequirements(ctx, modFileGoVersion(), rs)
+	commitRequirements(ctx, rs)
 
 	// Suggest running 'go mod tidy' unless the project is empty. Even if we
 	// imported all the correct requirements above, we're probably missing
@@ -577,6 +818,24 @@
 	}
 }
 
+// CreateWorkFile initializes a new workspace by creating a go.work file.
+func CreateWorkFile(ctx context.Context, workFile string, modDirs []string) {
+	_ = TODOWorkspaces("Report an error if the file already exists.")
+
+	goV := LatestGoVersion() // Use current Go version by default
+	workF := new(modfile.WorkFile)
+	workF.Syntax = new(modfile.FileSyntax)
+	workF.AddGoStmt(goV)
+
+	for _, dir := range modDirs {
+		_ = TODOWorkspaces("Add the module path of the module.")
+		workF.AddDirectory(dir, "")
+	}
+
+	data := modfile.Format(workF.Syntax)
+	lockedfile.Write(workFile, bytes.NewReader(data), 0644)
+}
+
 // fixVersion returns a modfile.VersionFixer implemented using the Query function.
 //
 // It resolves commit hashes and branch names to versions,
@@ -639,49 +898,88 @@
 	allowMissingModuleImports = true
 }
 
-// initTarget sets Target and associated variables according to modFile,
-func initTarget(m module.Version) {
-	Target = m
-	targetPrefix = m.Path
-
-	if rel := search.InDir(base.Cwd(), cfg.GOROOTsrc); rel != "" {
-		targetInGorootSrc = true
-		if m.Path == "std" {
-			// The "std" module in GOROOT/src is the Go standard library. Unlike other
-			// modules, the packages in the "std" module have no import-path prefix.
-			//
-			// Modules named "std" outside of GOROOT/src do not receive this special
-			// treatment, so it is possible to run 'go test .' in other GOROOTs to
-			// test individual packages using a combination of the modified package
-			// and the ordinary standard library.
-			// (See https://golang.org/issue/30756.)
-			targetPrefix = ""
+// makeMainModules creates a MainModuleSet and associated variables according to
+// the given main modules.
+func makeMainModules(ms []module.Version, rootDirs []string, modFiles []*modfile.File, indices []*modFileIndex, workFileGoVersion string) *MainModuleSet {
+	for _, m := range ms {
+		if m.Version != "" {
+			panic("mainModulesCalled with module.Version with non empty Version field: " + fmt.Sprintf("%#v", m))
 		}
 	}
-}
+	modRootContainingCWD := findModuleRoot(base.Cwd())
+	mainModules := &MainModuleSet{
+		versions:          ms[:len(ms):len(ms)],
+		inGorootSrc:       map[module.Version]bool{},
+		pathPrefix:        map[module.Version]string{},
+		modRoot:           map[module.Version]string{},
+		modFiles:          map[module.Version]*modfile.File{},
+		indices:           map[module.Version]*modFileIndex{},
+		workFileGoVersion: workFileGoVersion,
+	}
+	for i, m := range ms {
+		mainModules.pathPrefix[m] = m.Path
+		mainModules.modRoot[m] = rootDirs[i]
+		mainModules.modFiles[m] = modFiles[i]
+		mainModules.indices[m] = indices[i]
 
-// requirementsFromModFile returns the set of non-excluded requirements from
-// the global modFile.
-func requirementsFromModFile() *Requirements {
-	roots := make([]module.Version, 0, len(modFile.Require))
-	direct := map[string]bool{}
-	for _, r := range modFile.Require {
-		if index != nil && index.exclude[r.Mod] {
-			if cfg.BuildMod == "mod" {
-				fmt.Fprintf(os.Stderr, "go: dropping requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
-			} else {
-				fmt.Fprintf(os.Stderr, "go: ignoring requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
-			}
-			continue
+		if mainModules.modRoot[m] == modRootContainingCWD {
+			mainModules.modContainingCWD = m
 		}
 
-		roots = append(roots, r.Mod)
-		if !r.Indirect {
-			direct[r.Mod.Path] = true
+		if rel := search.InDir(rootDirs[i], cfg.GOROOTsrc); rel != "" {
+			mainModules.inGorootSrc[m] = true
+			if m.Path == "std" {
+				// The "std" module in GOROOT/src is the Go standard library. Unlike other
+				// modules, the packages in the "std" module have no import-path prefix.
+				//
+				// Modules named "std" outside of GOROOT/src do not receive this special
+				// treatment, so it is possible to run 'go test .' in other GOROOTs to
+				// test individual packages using a combination of the modified package
+				// and the ordinary standard library.
+				// (See https://golang.org/issue/30756.)
+				mainModules.pathPrefix[m] = ""
+			}
+		}
+	}
+	return mainModules
+}
+
+// requirementsFromModFiles returns the set of non-excluded requirements from
+// the global modFile.
+func requirementsFromModFiles(ctx context.Context, modFiles []*modfile.File) *Requirements {
+	rootCap := 0
+	for i := range modFiles {
+		rootCap += len(modFiles[i].Require)
+	}
+	roots := make([]module.Version, 0, rootCap)
+	mPathCount := make(map[string]int)
+	for _, m := range MainModules.Versions() {
+		mPathCount[m.Path] = 1
+	}
+	direct := map[string]bool{}
+	for _, modFile := range modFiles {
+	requirement:
+		for _, r := range modFile.Require {
+			// TODO(#45713): Maybe join
+			for _, mainModule := range MainModules.Versions() {
+				if index := MainModules.Index(mainModule); index != nil && index.exclude[r.Mod] {
+					if cfg.BuildMod == "mod" {
+						fmt.Fprintf(os.Stderr, "go: dropping requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
+					} else {
+						fmt.Fprintf(os.Stderr, "go: ignoring requirement on excluded version %s %s\n", r.Mod.Path, r.Mod.Version)
+					}
+					continue requirement
+				}
+			}
+
+			roots = append(roots, r.Mod)
+			if !r.Indirect {
+				direct[r.Mod.Path] = true
+			}
 		}
 	}
 	module.Sort(roots)
-	rs := newRequirements(modDepthFromGoVersion(modFileGoVersion()), roots, direct)
+	rs := newRequirements(modDepthFromGoVersion(MainModules.GoVersion()), roots, direct)
 	return rs
 }
 
@@ -689,6 +987,11 @@
 // wasn't provided. setDefaultBuildMod may be called multiple times.
 func setDefaultBuildMod() {
 	if cfg.BuildModExplicit {
+		if inWorkspaceMode() && cfg.BuildMod != "readonly" {
+			base.Fatalf("go: -mod may only be set to readonly when in workspace mode." +
+				"\n\tRemove the -mod flag to use the default readonly value," +
+				"\n\tor set -workfile=off to disable workspace mode.")
+		}
 		// Don't override an explicit '-mod=' argument.
 		return
 	}
@@ -713,7 +1016,7 @@
 		cfg.BuildMod = "readonly"
 		return
 	}
-	if modRoot == "" {
+	if modRoots == nil {
 		if allowMissingModuleImports {
 			cfg.BuildMod = "mod"
 		} else {
@@ -722,31 +1025,38 @@
 		return
 	}
 
-	if fi, err := fsys.Stat(filepath.Join(modRoot, "vendor")); err == nil && fi.IsDir() {
-		modGo := "unspecified"
-		if index != nil && index.goVersionV != "" {
-			if semver.Compare(index.goVersionV, "v1.14") >= 0 {
-				// The Go version is at least 1.14, and a vendor directory exists.
-				// Set -mod=vendor by default.
-				cfg.BuildMod = "vendor"
-				cfg.BuildModReason = "Go version in go.mod is at least 1.14 and vendor directory exists."
-				return
-			} else {
-				modGo = index.goVersionV[1:]
+	if len(modRoots) == 1 {
+		index := MainModules.GetSingleIndexOrNil()
+		if fi, err := fsys.Stat(filepath.Join(modRoots[0], "vendor")); err == nil && fi.IsDir() {
+			modGo := "unspecified"
+			if index != nil && index.goVersionV != "" {
+				if semver.Compare(index.goVersionV, "v1.14") >= 0 {
+					// The Go version is at least 1.14, and a vendor directory exists.
+					// Set -mod=vendor by default.
+					cfg.BuildMod = "vendor"
+					cfg.BuildModReason = "Go version in go.mod is at least 1.14 and vendor directory exists."
+					return
+				} else {
+					modGo = index.goVersionV[1:]
+				}
 			}
-		}
 
-		// Since a vendor directory exists, we should record why we didn't use it.
-		// This message won't normally be shown, but it may appear with import errors.
-		cfg.BuildModReason = fmt.Sprintf("Go version in go.mod is %s, so vendor directory was not used.", modGo)
+			// Since a vendor directory exists, we should record why we didn't use it.
+			// This message won't normally be shown, but it may appear with import errors.
+			cfg.BuildModReason = fmt.Sprintf("Go version in go.mod is %s, so vendor directory was not used.", modGo)
+		}
 	}
 
 	cfg.BuildMod = "readonly"
 }
 
+func mustHaveCompleteRequirements() bool {
+	return cfg.BuildMod != "mod" && !inWorkspaceMode()
+}
+
 // convertLegacyConfig imports module requirements from a legacy vendoring
 // configuration file, if one is present.
-func convertLegacyConfig(modPath string) (from string, err error) {
+func convertLegacyConfig(modFile *modfile.File, modRoot string) (from string, err error) {
 	noneSelected := func(path string) (version string) { return "none" }
 	queryPackage := func(path, rev string) (module.Version, error) {
 		pkgMods, modOnly, err := QueryPattern(context.Background(), path, rev, noneSelected, nil)
@@ -777,14 +1087,14 @@
 // addGoStmt adds a go directive to the go.mod file if it does not already
 // include one. The 'go' version added, if any, is the latest version supported
 // by this toolchain.
-func addGoStmt(v string) {
+func addGoStmt(modFile *modfile.File, mod module.Version, v string) {
 	if modFile.Go != nil && modFile.Go.Version != "" {
 		return
 	}
 	if err := modFile.AddGoStmt(v); err != nil {
 		base.Fatalf("go: internal error: %v", err)
 	}
-	rawGoVersion.Store(Target, v)
+	rawGoVersion.Store(mod, v)
 }
 
 // LatestGoVersion returns the latest version of the Go language supported by
@@ -835,7 +1145,7 @@
 	".git/config",
 }
 
-func findModuleRoot(dir string) (root string) {
+func findModuleRoot(dir string) (roots string) {
 	if dir == "" {
 		panic("dir not set")
 	}
@@ -855,6 +1165,32 @@
 	return ""
 }
 
+func findWorkspaceFile(dir string) (root string) {
+	if dir == "" {
+		panic("dir not set")
+	}
+	dir = filepath.Clean(dir)
+
+	// Look for enclosing go.mod.
+	for {
+		f := filepath.Join(dir, "go.work")
+		if fi, err := fsys.Stat(f); err == nil && !fi.IsDir() {
+			return f
+		}
+		d := filepath.Dir(dir)
+		if d == dir {
+			break
+		}
+		if d == cfg.GOROOT {
+			_ = TODOWorkspaces("If we end up checking in a go.work file to GOROOT/src," +
+				"remove this case.")
+			return "" // As a special case, don't cross GOROOT to find a go.work file.
+		}
+		dir = d
+	}
+	return ""
+}
+
 func findAltConfig(dir string) (root, name string) {
 	if dir == "" {
 		panic("dir not set")
@@ -1000,12 +1336,13 @@
 	if !allowWriteGoMod {
 		panic("WriteGoMod called while disallowed")
 	}
-	commitRequirements(ctx, modFileGoVersion(), LoadModFile(ctx))
+	commitRequirements(ctx, LoadModFile(ctx))
 }
 
 // commitRequirements writes sets the global requirements variable to rs and
 // writes its contents back to the go.mod file on disk.
-func commitRequirements(ctx context.Context, goVersion string, rs *Requirements) {
+// goVersion, if non-empty, is used to set the version on the go.mod file.
+func commitRequirements(ctx context.Context, rs *Requirements) {
 	requirements = rs
 
 	if !allowWriteGoMod {
@@ -1013,10 +1350,22 @@
 		return
 	}
 
-	if modRoot == "" {
+	if inWorkspaceMode() {
+		// go.mod files aren't updated in workspace mode, but we still want to
+		// update the go.work.sum file.
+		if err := modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums), mustHaveCompleteRequirements()); err != nil {
+			base.Fatalf("go: %v", err)
+		}
+		return
+	}
+
+	if MainModules.Len() != 1 || MainModules.ModRoot(MainModules.Versions()[0]) == "" {
 		// We aren't in a module, so we don't have anywhere to write a go.mod file.
 		return
 	}
+	mainModule := MainModules.mustGetSingleMainModule()
+	modFilePath := modFilePath(MainModules.ModRoot(mainModule))
+	modFile := MainModules.ModFile(mainModule)
 
 	var list []*modfile.Require
 	for _, m := range rs.rootModules {
@@ -1025,16 +1374,17 @@
 			Indirect: !rs.direct[m.Path],
 		})
 	}
-	if goVersion != "" {
-		modFile.AddGoStmt(goVersion)
+	if modFile.Go == nil || modFile.Go.Version == "" {
+		modFile.AddGoStmt(modFileGoVersion(modFile))
 	}
-	if semver.Compare("v"+modFileGoVersion(), separateIndirectVersionV) < 0 {
+	if semver.Compare("v"+modFileGoVersion(modFile), separateIndirectVersionV) < 0 {
 		modFile.SetRequire(list)
 	} else {
 		modFile.SetRequireSeparateIndirect(list)
 	}
 	modFile.Cleanup()
 
+	index := MainModules.GetSingleIndexOrNil()
 	dirty := index.modFileIsDirty(modFile)
 	if dirty && cfg.BuildMod != "mod" {
 		// If we're about to fail due to -mod=readonly,
@@ -1048,12 +1398,13 @@
 		// Don't write go.mod, but write go.sum in case we added or trimmed sums.
 		// 'go mod init' shouldn't write go.sum, since it will be incomplete.
 		if cfg.CmdName != "mod init" {
-			modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums))
+			if err := modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums), mustHaveCompleteRequirements()); err != nil {
+				base.Fatalf("go: %v", err)
+			}
 		}
 		return
 	}
-	gomod := ModFilePath()
-	if _, ok := fsys.OverlayPath(gomod); ok {
+	if _, ok := fsys.OverlayPath(modFilePath); ok {
 		if dirty {
 			base.Fatalf("go: updates to go.mod needed, but go.mod is part of the overlay specified with -overlay")
 		}
@@ -1066,12 +1417,14 @@
 	}
 	defer func() {
 		// At this point we have determined to make the go.mod file on disk equal to new.
-		index = indexModFile(new, modFile, false)
+		MainModules.SetIndex(mainModule, indexModFile(new, modFile, mainModule, false))
 
 		// Update go.sum after releasing the side lock and refreshing the index.
 		// 'go mod init' shouldn't write go.sum, since it will be incomplete.
 		if cfg.CmdName != "mod init" {
-			modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums))
+			if err := modfetch.WriteGoSum(keepSums(ctx, loaded, rs, addBuildListZipSums), mustHaveCompleteRequirements()); err != nil {
+				base.Fatalf("go: %v", err)
+			}
 		}
 	}()
 
@@ -1083,7 +1436,7 @@
 
 	errNoChange := errors.New("no update needed")
 
-	err = lockedfile.Transform(ModFilePath(), func(old []byte) ([]byte, error) {
+	err = lockedfile.Transform(modFilePath, func(old []byte) ([]byte, error) {
 		if bytes.Equal(old, new) {
 			// The go.mod file is already equal to new, possibly as the result of some
 			// other process.
@@ -1142,7 +1495,8 @@
 					for prefix := pkg.path; prefix != "."; prefix = path.Dir(prefix) {
 						if v, ok := rs.rootSelected(prefix); ok && v != "none" {
 							m := module.Version{Path: prefix, Version: v}
-							keep[resolveReplacement(m)] = true
+							r, _ := resolveReplacement(m)
+							keep[r] = true
 						}
 					}
 					continue
@@ -1153,7 +1507,8 @@
 			for prefix := pkg.path; prefix != "."; prefix = path.Dir(prefix) {
 				if v := mg.Selected(prefix); v != "none" {
 					m := module.Version{Path: prefix, Version: v}
-					keep[resolveReplacement(m)] = true
+					r, _ := resolveReplacement(m)
+					keep[r] = true
 				}
 			}
 		}
@@ -1165,7 +1520,7 @@
 		// Save sums for the root modules (or their replacements), but don't
 		// incur the cost of loading the graph just to find and retain the sums.
 		for _, m := range rs.rootModules {
-			r := resolveReplacement(m)
+			r, _ := resolveReplacement(m)
 			keep[modkey(r)] = true
 			if which == addBuildListZipSums {
 				keep[r] = true
@@ -1178,13 +1533,15 @@
 				// The requirements from m's go.mod file are present in the module graph,
 				// so they are relevant to the MVS result regardless of whether m was
 				// actually selected.
-				keep[modkey(resolveReplacement(m))] = true
+				r, _ := resolveReplacement(m)
+				keep[modkey(r)] = true
 			}
 		})
 
 		if which == addBuildListZipSums {
 			for _, m := range mg.BuildList() {
-				keep[resolveReplacement(m)] = true
+				r, _ := resolveReplacement(m)
+				keep[r] = true
 			}
 		}
 	}
diff --git a/src/cmd/go/internal/modload/list.go b/src/cmd/go/internal/modload/list.go
index ccdeb9b..9c5018f 100644
--- a/src/cmd/go/internal/modload/list.go
+++ b/src/cmd/go/internal/modload/list.go
@@ -72,14 +72,18 @@
 	}
 
 	if err == nil {
-		commitRequirements(ctx, modFileGoVersion(), rs)
+		commitRequirements(ctx, rs)
 	}
 	return mods, err
 }
 
 func listModules(ctx context.Context, rs *Requirements, args []string, mode ListMode) (_ *Requirements, mods []*modinfo.ModulePublic, mgErr error) {
 	if len(args) == 0 {
-		return rs, []*modinfo.ModulePublic{moduleInfo(ctx, rs, Target, mode)}, nil
+		var ms []*modinfo.ModulePublic
+		for _, m := range MainModules.Versions() {
+			ms = append(ms, moduleInfo(ctx, rs, m, mode))
+		}
+		return rs, ms, nil
 	}
 
 	needFullGraph := false
diff --git a/src/cmd/go/internal/modload/load.go b/src/cmd/go/internal/modload/load.go
index b54f670..efe6ad1 100644
--- a/src/cmd/go/internal/modload/load.go
+++ b/src/cmd/go/internal/modload/load.go
@@ -274,7 +274,9 @@
 
 						// If we're outside of a module, ensure that the failure mode
 						// indicates that.
-						ModRoot()
+						if !HasModRoot() {
+							die()
+						}
 
 						if ld != nil {
 							m.AddError(err)
@@ -306,7 +308,7 @@
 					// The initial roots are the packages in the main module.
 					// loadFromRoots will expand that to "all".
 					m.Errs = m.Errs[:0]
-					matchPackages(ctx, m, opts.Tags, omitStd, []module.Version{Target})
+					matchPackages(ctx, m, opts.Tags, omitStd, MainModules.Versions())
 				} else {
 					// Starting with the packages in the main module,
 					// enumerate the full list of "all".
@@ -401,13 +403,21 @@
 			// loaded.requirements, but here we may have also loaded (and want to
 			// preserve checksums for) additional entities from compatRS, which are
 			// only needed for compatibility with ld.TidyCompatibleVersion.
-			modfetch.WriteGoSum(keep)
+			if err := modfetch.WriteGoSum(keep, mustHaveCompleteRequirements()); err != nil {
+				base.Fatalf("go: %v", err)
+			}
+		}
+
+		// Update the go.mod file's Go version if necessary.
+		modFile := MainModules.ModFile(MainModules.mustGetSingleMainModule())
+		if ld.GoVersion != "" {
+			modFile.AddGoStmt(ld.GoVersion)
 		}
 	}
 
 	// Success! Update go.mod and go.sum (if needed) and return the results.
 	loaded = ld
-	commitRequirements(ctx, loaded.GoVersion, loaded.requirements)
+	commitRequirements(ctx, loaded.requirements)
 
 	for _, pkg := range ld.pkgs {
 		if !pkg.isTest() {
@@ -436,14 +446,23 @@
 		if !filepath.IsAbs(dir) {
 			absDir = filepath.Join(base.Cwd(), dir)
 		}
-		if search.InDir(absDir, cfg.GOROOTsrc) == "" && search.InDir(absDir, ModRoot()) == "" && pathInModuleCache(ctx, absDir, rs) == "" {
+
+		modRoot := findModuleRoot(absDir)
+		found := false
+		for _, mod := range MainModules.Versions() {
+			if MainModules.ModRoot(mod) == modRoot {
+				found = true
+				break
+			}
+		}
+		if !found && search.InDir(absDir, cfg.GOROOTsrc) == "" && pathInModuleCache(ctx, absDir, rs) == "" {
 			m.Dirs = []string{}
 			m.AddError(fmt.Errorf("directory prefix %s outside available modules", base.ShortPath(absDir)))
 			return
 		}
 	}
 
-	m.MatchDirs()
+	m.MatchDirs(modRoots)
 }
 
 // resolveLocalPackage resolves a filesystem path to a package path.
@@ -485,49 +504,69 @@
 		}
 	}
 
-	if modRoot != "" && absDir == modRoot {
-		if absDir == cfg.GOROOTsrc {
-			return "", errPkgIsGorootSrc
+	for _, mod := range MainModules.Versions() {
+		modRoot := MainModules.ModRoot(mod)
+		if modRoot != "" && absDir == modRoot {
+			if absDir == cfg.GOROOTsrc {
+				return "", errPkgIsGorootSrc
+			}
+			return MainModules.PathPrefix(mod), nil
 		}
-		return targetPrefix, nil
 	}
 
 	// Note: The checks for @ here are just to avoid misinterpreting
 	// the module cache directories (formerly GOPATH/src/mod/foo@v1.5.2/bar).
 	// It's not strictly necessary but helpful to keep the checks.
-	if modRoot != "" && strings.HasPrefix(absDir, modRoot+string(filepath.Separator)) && !strings.Contains(absDir[len(modRoot):], "@") {
-		suffix := filepath.ToSlash(absDir[len(modRoot):])
-		if strings.HasPrefix(suffix, "/vendor/") {
-			if cfg.BuildMod != "vendor" {
-				return "", fmt.Errorf("without -mod=vendor, directory %s has no package path", absDir)
+	var pkgNotFoundErr error
+	pkgNotFoundLongestPrefix := ""
+	for _, mainModule := range MainModules.Versions() {
+		modRoot := MainModules.ModRoot(mainModule)
+		if modRoot != "" && strings.HasPrefix(absDir, modRoot+string(filepath.Separator)) && !strings.Contains(absDir[len(modRoot):], "@") {
+			suffix := filepath.ToSlash(absDir[len(modRoot):])
+			if strings.HasPrefix(suffix, "/vendor/") {
+				if cfg.BuildMod != "vendor" {
+					return "", fmt.Errorf("without -mod=vendor, directory %s has no package path", absDir)
+				}
+
+				readVendorList(mainModule)
+				pkg := strings.TrimPrefix(suffix, "/vendor/")
+				if _, ok := vendorPkgModule[pkg]; !ok {
+					return "", fmt.Errorf("directory %s is not a package listed in vendor/modules.txt", absDir)
+				}
+				return pkg, nil
 			}
 
-			readVendorList()
-			pkg := strings.TrimPrefix(suffix, "/vendor/")
-			if _, ok := vendorPkgModule[pkg]; !ok {
-				return "", fmt.Errorf("directory %s is not a package listed in vendor/modules.txt", absDir)
+			mainModulePrefix := MainModules.PathPrefix(mainModule)
+			if mainModulePrefix == "" {
+				pkg := strings.TrimPrefix(suffix, "/")
+				if pkg == "builtin" {
+					// "builtin" is a pseudo-package with a real source file.
+					// It's not included in "std", so it shouldn't resolve from "."
+					// within module "std" either.
+					return "", errPkgIsBuiltin
+				}
+				return pkg, nil
+			}
+
+			pkg := mainModulePrefix + suffix
+			if _, ok, err := dirInModule(pkg, mainModulePrefix, modRoot, true); err != nil {
+				return "", err
+			} else if !ok {
+				// This main module could contain the directory but doesn't. Other main
+				// modules might contain the directory, so wait till we finish the loop
+				// to see if another main module contains directory. But if not,
+				// return an error.
+				if len(mainModulePrefix) > len(pkgNotFoundLongestPrefix) {
+					pkgNotFoundLongestPrefix = mainModulePrefix
+					pkgNotFoundErr = &PackageNotInModuleError{MainModules: []module.Version{mainModule}, Pattern: pkg}
+				}
+				continue
 			}
 			return pkg, nil
 		}
-
-		if targetPrefix == "" {
-			pkg := strings.TrimPrefix(suffix, "/")
-			if pkg == "builtin" {
-				// "builtin" is a pseudo-package with a real source file.
-				// It's not included in "std", so it shouldn't resolve from "."
-				// within module "std" either.
-				return "", errPkgIsBuiltin
-			}
-			return pkg, nil
-		}
-
-		pkg := targetPrefix + suffix
-		if _, ok, err := dirInModule(pkg, targetPrefix, modRoot, true); err != nil {
-			return "", err
-		} else if !ok {
-			return "", &PackageNotInModuleError{Mod: Target, Pattern: pkg}
-		}
-		return pkg, nil
+	}
+	if pkgNotFoundErr != nil {
+		return "", pkgNotFoundErr
 	}
 
 	if sub := search.InDir(absDir, cfg.GOROOTsrc); sub != "" && sub != "." && !strings.Contains(sub, "@") {
@@ -557,10 +596,10 @@
 	tryMod := func(m module.Version) (string, bool) {
 		var root string
 		var err error
-		if repl := Replacement(m); repl.Path != "" && repl.Version == "" {
+		if repl, replModRoot := Replacement(m); repl.Path != "" && repl.Version == "" {
 			root = repl.Path
 			if !filepath.IsAbs(root) {
-				root = filepath.Join(ModRoot(), root)
+				root = filepath.Join(replModRoot, root)
 			}
 		} else if repl.Path != "" {
 			root, err = modfetch.DownloadDir(repl)
@@ -645,14 +684,14 @@
 			return roots
 		},
 	})
-	commitRequirements(ctx, loaded.GoVersion, loaded.requirements)
+	commitRequirements(ctx, loaded.requirements)
 }
 
 // DirImportPath returns the effective import path for dir,
-// provided it is within the main module, or else returns ".".
-func DirImportPath(ctx context.Context, dir string) string {
+// provided it is within a main module, or else returns ".".
+func (mms *MainModuleSet) DirImportPath(ctx context.Context, dir string) (path string, m module.Version) {
 	if !HasModRoot() {
-		return "."
+		return ".", module.Version{}
 	}
 	LoadModFile(ctx) // Sets targetPrefix.
 
@@ -662,17 +701,32 @@
 		dir = filepath.Clean(dir)
 	}
 
-	if dir == modRoot {
-		return targetPrefix
-	}
-	if strings.HasPrefix(dir, modRoot+string(filepath.Separator)) {
-		suffix := filepath.ToSlash(dir[len(modRoot):])
-		if strings.HasPrefix(suffix, "/vendor/") {
-			return strings.TrimPrefix(suffix, "/vendor/")
+	var longestPrefix string
+	var longestPrefixPath string
+	var longestPrefixVersion module.Version
+	for _, v := range mms.Versions() {
+		modRoot := mms.ModRoot(v)
+		if dir == modRoot {
+			return mms.PathPrefix(v), v
 		}
-		return targetPrefix + suffix
+		if strings.HasPrefix(dir, modRoot+string(filepath.Separator)) {
+			pathPrefix := MainModules.PathPrefix(v)
+			if pathPrefix > longestPrefix {
+				longestPrefix = pathPrefix
+				longestPrefixVersion = v
+				suffix := filepath.ToSlash(dir[len(modRoot):])
+				if strings.HasPrefix(suffix, "/vendor/") {
+					longestPrefixPath = strings.TrimPrefix(suffix, "/vendor/")
+				}
+				longestPrefixPath = mms.PathPrefix(v) + suffix
+			}
+		}
 	}
-	return "."
+	if len(longestPrefix) > 0 {
+		return longestPrefixPath, longestPrefixVersion
+	}
+
+	return ".", module.Version{}
 }
 
 // ImportMap returns the actual package import path
@@ -894,10 +948,7 @@
 	if pkg.mod.Path == "" {
 		return false // loaded from the standard library, not a module
 	}
-	if pkg.mod.Path == Target.Path {
-		return false // loaded from the main module.
-	}
-	return true
+	return !MainModules.Contains(pkg.mod.Path)
 }
 
 var errMissing = errors.New("cannot find package")
@@ -915,7 +966,7 @@
 	}
 
 	if ld.GoVersion == "" {
-		ld.GoVersion = modFileGoVersion()
+		ld.GoVersion = MainModules.GoVersion()
 
 		if ld.Tidy && semver.Compare("v"+ld.GoVersion, "v"+LatestGoVersion()) > 0 {
 			ld.errorf("go mod tidy: go.mod file indicates go %s, but maximum supported version is %s\n", ld.GoVersion, LatestGoVersion())
@@ -1168,7 +1219,7 @@
 	}
 
 	for _, pkg := range ld.pkgs {
-		if pkg.mod != Target {
+		if pkg.mod.Version != "" || !MainModules.Contains(pkg.mod.Path) {
 			continue
 		}
 		for _, dep := range pkg.imports {
@@ -1327,6 +1378,15 @@
 			var err error
 			mod, err = queryImport(ctx, pkg.path, ld.requirements)
 			if err != nil {
+				var ime *ImportMissingError
+				if errors.As(err, &ime) {
+					for curstack := pkg.stack; curstack != nil; curstack = curstack.stack {
+						if MainModules.Contains(curstack.mod.Path) {
+							ime.ImportingMainModule = curstack.mod
+							break
+						}
+					}
+				}
 				// pkg.err was already non-nil, so we can reasonably attribute the error
 				// for pkg to either the original error or the one returned by
 				// queryImport. The existing error indicates only that we couldn't find
@@ -1425,7 +1485,7 @@
 		// so it's ok if we call it more than is strictly necessary.
 		wantTest := false
 		switch {
-		case ld.allPatternIsRoot && pkg.mod == Target:
+		case ld.allPatternIsRoot && MainModules.Contains(pkg.mod.Path):
 			// We are loading the "all" pattern, which includes packages imported by
 			// tests in the main module. This package is in the main module, so we
 			// need to identify the imports of its test even if LoadTests is not set.
@@ -1446,7 +1506,7 @@
 
 		if wantTest {
 			var testFlags loadPkgFlags
-			if pkg.mod == Target || (ld.allClosesOverTests && new.has(pkgInAll)) {
+			if MainModules.Contains(pkg.mod.Path) || (ld.allClosesOverTests && new.has(pkgInAll)) {
 				// Tests of packages in the main module are in "all", in the sense that
 				// they cause the packages they import to also be in "all". So are tests
 				// of packages in "all" if "all" closes over test dependencies.
@@ -1593,7 +1653,7 @@
 	if pkg.dir == "" {
 		return
 	}
-	if pkg.mod == Target {
+	if MainModules.Contains(pkg.mod.Path) {
 		// Go ahead and mark pkg as in "all". This provides the invariant that a
 		// package that is *only* imported by other packages in "all" is always
 		// marked as such before loading its imports.
@@ -1698,13 +1758,14 @@
 	}
 
 	if str.HasPathPrefix(parentPath, "cmd") {
-		if !ld.VendorModulesInGOROOTSrc || Target.Path != "cmd" {
+		if !ld.VendorModulesInGOROOTSrc || !MainModules.Contains("cmd") {
 			vendorPath := pathpkg.Join("cmd", "vendor", path)
+
 			if _, err := os.Stat(filepath.Join(cfg.GOROOTsrc, filepath.FromSlash(vendorPath))); err == nil {
 				return vendorPath
 			}
 		}
-	} else if !ld.VendorModulesInGOROOTSrc || Target.Path != "std" || str.HasPathPrefix(parentPath, "vendor") {
+	} else if !ld.VendorModulesInGOROOTSrc || !MainModules.Contains("std") || str.HasPathPrefix(parentPath, "vendor") {
 		// If we are outside of the 'std' module, resolve imports from within 'std'
 		// to the vendor directory.
 		//
@@ -1753,7 +1814,7 @@
 
 	firstPath := map[module.Version]string{}
 	for _, mod := range mods {
-		src := resolveReplacement(mod)
+		src, _ := resolveReplacement(mod)
 		if prev, ok := firstPath[src]; !ok {
 			firstPath[src] = mod.Path
 		} else if prev != mod.Path {
@@ -1781,7 +1842,7 @@
 		fmt.Fprintln(os.Stderr)
 
 		goFlag := ""
-		if ld.GoVersion != modFileGoVersion() {
+		if ld.GoVersion != MainModules.GoVersion() {
 			goFlag = " -go=" + ld.GoVersion
 		}
 
diff --git a/src/cmd/go/internal/modload/modfile.go b/src/cmd/go/internal/modload/modfile.go
index 03e02e7..4638699 100644
--- a/src/cmd/go/internal/modload/modfile.go
+++ b/src/cmd/go/internal/modload/modfile.go
@@ -56,11 +56,9 @@
 	go117LazyTODO = false
 )
 
-var modFile *modfile.File
-
 // modFileGoVersion returns the (non-empty) Go version at which the requirements
-// in modFile are intepreted, or the latest Go version if modFile is nil.
-func modFileGoVersion() string {
+// in modFile are interpreted, or the latest Go version if modFile is nil.
+func modFileGoVersion(modFile *modfile.File) string {
 	if modFile == nil {
 		return LatestGoVersion()
 	}
@@ -92,9 +90,6 @@
 	exclude         map[module.Version]bool
 }
 
-// index is the index of the go.mod file as of when it was last read or written.
-var index *modFileIndex
-
 type requireMeta struct {
 	indirect bool
 }
@@ -137,8 +132,10 @@
 // CheckExclusions returns an error equivalent to ErrDisallowed if module m is
 // excluded by the main module's go.mod file.
 func CheckExclusions(ctx context.Context, m module.Version) error {
-	if index != nil && index.exclude[m] {
-		return module.VersionError(m, errExcluded)
+	for _, mainModule := range MainModules.Versions() {
+		if index := MainModules.Index(mainModule); index != nil && index.exclude[m] {
+			return module.VersionError(m, errExcluded)
+		}
 	}
 	return nil
 }
@@ -170,7 +167,7 @@
 		// Cannot be retracted.
 		return nil
 	}
-	if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
+	if repl, _ := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
 		// All versions of the module were replaced.
 		// Don't load retractions, since we'd just load the replacement.
 		return nil
@@ -187,11 +184,11 @@
 	// We load the raw file here: the go.mod file may have a different module
 	// path that we expect if the module or its repository was renamed.
 	// We still want to apply retractions to other aliases of the module.
-	rm, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
+	rm, replacedFrom, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
 	if err != nil {
 		return err
 	}
-	summary, err := rawGoModSummary(rm)
+	summary, err := rawGoModSummary(rm, replacedFrom)
 	if err != nil {
 		return err
 	}
@@ -289,51 +286,72 @@
 		// Don't look up deprecation.
 		return "", nil
 	}
-	if repl := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
+	if repl, _ := Replacement(module.Version{Path: m.Path}); repl.Path != "" {
 		// All versions of the module were replaced.
 		// We'll look up deprecation separately for the replacement.
 		return "", nil
 	}
 
-	latest, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
+	latest, replacedFrom, err := queryLatestVersionIgnoringRetractions(ctx, m.Path)
 	if err != nil {
 		return "", err
 	}
-	summary, err := rawGoModSummary(latest)
+	summary, err := rawGoModSummary(latest, replacedFrom)
 	if err != nil {
 		return "", err
 	}
 	return summary.deprecated, nil
 }
 
-// Replacement returns the replacement for mod, if any, from go.mod.
+func replacement(mod module.Version, index *modFileIndex) (fromVersion string, to module.Version, ok bool) {
+	if r, ok := index.replace[mod]; ok {
+		return mod.Version, r, true
+	}
+	if r, ok := index.replace[module.Version{Path: mod.Path}]; ok {
+		return "", r, true
+	}
+	return "", module.Version{}, false
+}
+
+// Replacement returns the replacement for mod, if any, and and the module root
+// directory of the main module containing the replace directive.
 // If there is no replacement for mod, Replacement returns
 // a module.Version with Path == "".
-func Replacement(mod module.Version) module.Version {
-	if index != nil {
-		if r, ok := index.replace[mod]; ok {
-			return r
-		}
-		if r, ok := index.replace[module.Version{Path: mod.Path}]; ok {
-			return r
+func Replacement(mod module.Version) (module.Version, string) {
+	_ = TODOWorkspaces("Support replaces in the go.work file.")
+	foundFrom, found, foundModRoot := "", module.Version{}, ""
+	for _, v := range MainModules.Versions() {
+		if index := MainModules.Index(v); index != nil {
+			if from, r, ok := replacement(mod, index); ok {
+				modRoot := MainModules.ModRoot(v)
+				if foundModRoot != "" && foundFrom != from && found != r {
+					_ = TODOWorkspaces("once the go.work file supports replaces, recommend them as a way to override conflicts")
+					base.Errorf("conflicting replacements found for %v in workspace modules defined by %v and %v",
+						mod, modFilePath(foundModRoot), modFilePath(modRoot))
+					return found, foundModRoot
+				}
+				found, foundModRoot = r, modRoot
+			}
 		}
 	}
-	return module.Version{}
+	return found, foundModRoot
 }
 
 // resolveReplacement returns the module actually used to load the source code
 // for m: either m itself, or the replacement for m (iff m is replaced).
-func resolveReplacement(m module.Version) module.Version {
-	if r := Replacement(m); r.Path != "" {
-		return r
+// It also returns the modroot of the module providing the replacement if
+// one was found.
+func resolveReplacement(m module.Version) (module.Version, string) {
+	if r, replacedFrom := Replacement(m); r.Path != "" {
+		return r, replacedFrom
 	}
-	return m
+	return m, ""
 }
 
 // indexModFile rebuilds the index of modFile.
 // If modFile has been changed since it was first read,
 // modFile.Cleanup must be called before indexModFile.
-func indexModFile(data []byte, modFile *modfile.File, needsFix bool) *modFileIndex {
+func indexModFile(data []byte, modFile *modfile.File, mod module.Version, needsFix bool) *modFileIndex {
 	i := new(modFileIndex)
 	i.data = data
 	i.dataNeedsFix = needsFix
@@ -345,12 +363,12 @@
 
 	i.goVersionV = ""
 	if modFile.Go == nil {
-		rawGoVersion.Store(Target, "")
+		rawGoVersion.Store(mod, "")
 	} else {
 		// We're going to use the semver package to compare Go versions, so go ahead
 		// and add the "v" prefix it expects once instead of every time.
 		i.goVersionV = "v" + modFile.Go.Version
-		rawGoVersion.Store(Target, modFile.Go.Version)
+		rawGoVersion.Store(mod, modFile.Go.Version)
 	}
 
 	i.require = make(map[module.Version]requireMeta, len(modFile.Require))
@@ -490,8 +508,8 @@
 //
 // The caller must not modify the returned summary.
 func goModSummary(m module.Version) (*modFileSummary, error) {
-	if m == Target {
-		panic("internal error: goModSummary called on the Target module")
+	if m.Version == "" && MainModules.Contains(m.Path) {
+		panic("internal error: goModSummary called on a main module")
 	}
 
 	if cfg.BuildMod == "vendor" {
@@ -506,7 +524,7 @@
 
 		// For every module other than the target,
 		// return the full list of modules from modules.txt.
-		readVendorList()
+		readVendorList(MainModules.mustGetSingleMainModule())
 
 		// We don't know what versions the vendored module actually relies on,
 		// so assume that it requires everything.
@@ -514,15 +532,15 @@
 		return summary, nil
 	}
 
-	actual := resolveReplacement(m)
-	if HasModRoot() && cfg.BuildMod == "readonly" && actual.Version != "" {
+	actual, replacedFrom := resolveReplacement(m)
+	if HasModRoot() && cfg.BuildMod == "readonly" && !inWorkspaceMode() && actual.Version != "" {
 		key := module.Version{Path: actual.Path, Version: actual.Version + "/go.mod"}
 		if !modfetch.HaveSum(key) {
 			suggestion := fmt.Sprintf("; to add it:\n\tgo mod download %s", m.Path)
 			return nil, module.VersionError(actual, &sumMissingError{suggestion: suggestion})
 		}
 	}
-	summary, err := rawGoModSummary(actual)
+	summary, err := rawGoModSummary(actual, replacedFrom)
 	if err != nil {
 		return nil, err
 	}
@@ -553,27 +571,29 @@
 		}
 	}
 
-	if index != nil && len(index.exclude) > 0 {
-		// Drop any requirements on excluded versions.
-		// Don't modify the cached summary though, since we might need the raw
-		// summary separately.
-		haveExcludedReqs := false
-		for _, r := range summary.require {
-			if index.exclude[r] {
-				haveExcludedReqs = true
-				break
-			}
-		}
-		if haveExcludedReqs {
-			s := new(modFileSummary)
-			*s = *summary
-			s.require = make([]module.Version, 0, len(summary.require))
+	for _, mainModule := range MainModules.Versions() {
+		if index := MainModules.Index(mainModule); index != nil && len(index.exclude) > 0 {
+			// Drop any requirements on excluded versions.
+			// Don't modify the cached summary though, since we might need the raw
+			// summary separately.
+			haveExcludedReqs := false
 			for _, r := range summary.require {
-				if !index.exclude[r] {
-					s.require = append(s.require, r)
+				if index.exclude[r] {
+					haveExcludedReqs = true
+					break
 				}
 			}
-			summary = s
+			if haveExcludedReqs {
+				s := new(modFileSummary)
+				*s = *summary
+				s.require = make([]module.Version, 0, len(summary.require))
+				for _, r := range summary.require {
+					if !index.exclude[r] {
+						s.require = append(s.require, r)
+					}
+				}
+				summary = s
+			}
 		}
 	}
 	return summary, nil
@@ -584,18 +604,23 @@
 // its dependencies.
 //
 // rawGoModSummary cannot be used on the Target module.
-func rawGoModSummary(m module.Version) (*modFileSummary, error) {
-	if m == Target {
+
+func rawGoModSummary(m module.Version, replacedFrom string) (*modFileSummary, error) {
+	if m.Path == "" && MainModules.Contains(m.Path) {
 		panic("internal error: rawGoModSummary called on the Target module")
 	}
 
+	type key struct {
+		m            module.Version
+		replacedFrom string
+	}
 	type cached struct {
 		summary *modFileSummary
 		err     error
 	}
-	c := rawGoModSummaryCache.Do(m, func() interface{} {
+	c := rawGoModSummaryCache.Do(key{m, replacedFrom}, func() interface{} {
 		summary := new(modFileSummary)
-		name, data, err := rawGoModData(m)
+		name, data, err := rawGoModData(m, replacedFrom)
 		if err != nil {
 			return cached{nil, err}
 		}
@@ -645,12 +670,15 @@
 //
 // Unlike rawGoModSummary, rawGoModData does not cache its results in memory.
 // Use rawGoModSummary instead unless you specifically need these bytes.
-func rawGoModData(m module.Version) (name string, data []byte, err error) {
+func rawGoModData(m module.Version, replacedFrom string) (name string, data []byte, err error) {
 	if m.Version == "" {
 		// m is a replacement module with only a file path.
 		dir := m.Path
 		if !filepath.IsAbs(dir) {
-			dir = filepath.Join(ModRoot(), dir)
+			if replacedFrom == "" {
+				panic(fmt.Errorf("missing module root of main module providing replacement with relative path: %v", dir))
+			}
+			dir = filepath.Join(replacedFrom, dir)
 		}
 		name = filepath.Join(dir, "go.mod")
 		if gomodActual, ok := fsys.OverlayPath(name); ok {
@@ -685,19 +713,20 @@
 //
 // If the queried latest version is replaced,
 // queryLatestVersionIgnoringRetractions returns the replacement.
-func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (latest module.Version, err error) {
+func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (latest module.Version, replacedFrom string, err error) {
 	type entry struct {
-		latest module.Version
-		err    error
+		latest       module.Version
+		replacedFrom string // if latest is a replacement
+		err          error
 	}
 	e := latestVersionIgnoringRetractionsCache.Do(path, func() interface{} {
 		ctx, span := trace.StartSpan(ctx, "queryLatestVersionIgnoringRetractions "+path)
 		defer span.Done()
 
-		if repl := Replacement(module.Version{Path: path}); repl.Path != "" {
+		if repl, replFrom := Replacement(module.Version{Path: path}); repl.Path != "" {
 			// All versions of the module were replaced.
 			// No need to query.
-			return &entry{latest: repl}
+			return &entry{latest: repl, replacedFrom: replFrom}
 		}
 
 		// Find the latest version of the module.
@@ -709,12 +738,12 @@
 			return &entry{err: err}
 		}
 		latest := module.Version{Path: path, Version: rev.Version}
-		if repl := resolveReplacement(latest); repl.Path != "" {
-			latest = repl
+		if repl, replFrom := resolveReplacement(latest); repl.Path != "" {
+			latest, replacedFrom = repl, replFrom
 		}
-		return &entry{latest: latest}
+		return &entry{latest: latest, replacedFrom: replacedFrom}
 	}).(*entry)
-	return e.latest, e.err
+	return e.latest, e.replacedFrom, e.err
 }
 
 var latestVersionIgnoringRetractionsCache par.Cache // path → queryLatestVersionIgnoringRetractions result
diff --git a/src/cmd/go/internal/modload/mvs.go b/src/cmd/go/internal/modload/mvs.go
index 87619b4..40224d5 100644
--- a/src/cmd/go/internal/modload/mvs.go
+++ b/src/cmd/go/internal/modload/mvs.go
@@ -42,7 +42,7 @@
 }
 
 func (r *mvsReqs) Required(mod module.Version) ([]module.Version, error) {
-	if mod == Target {
+	if MainModules.Contains(mod.Path) {
 		// Use the build list as it existed when r was constructed, not the current
 		// global build list.
 		return r.roots, nil
@@ -113,7 +113,7 @@
 func previousVersion(m module.Version) (module.Version, error) {
 	// TODO(golang.org/issue/38714): thread tracing context through MVS.
 
-	if m == Target {
+	if MainModules.Contains(m.Path) {
 		return module.Version{Path: m.Path, Version: "none"}, nil
 	}
 
diff --git a/src/cmd/go/internal/modload/query.go b/src/cmd/go/internal/modload/query.go
index d4c906a..82979fb 100644
--- a/src/cmd/go/internal/modload/query.go
+++ b/src/cmd/go/internal/modload/query.go
@@ -110,11 +110,12 @@
 		allowed = func(context.Context, module.Version) error { return nil }
 	}
 
-	if path == Target.Path && (query == "upgrade" || query == "patch") {
-		if err := allowed(ctx, Target); err != nil {
+	if MainModules.Contains(path) && (query == "upgrade" || query == "patch") {
+		m := module.Version{Path: path}
+		if err := allowed(ctx, m); err != nil {
 			return nil, fmt.Errorf("internal error: main module version is not allowed: %w", err)
 		}
-		return &modfetch.RevInfo{Version: Target.Version}, nil
+		return &modfetch.RevInfo{Version: m.Version}, nil
 	}
 
 	if path == "std" || path == "cmd" {
@@ -512,9 +513,10 @@
 	pkgMods, modOnly, err := QueryPattern(ctx, pattern, query, current, allowed)
 
 	if len(pkgMods) == 0 && err == nil {
+		replacement, _ := Replacement(modOnly.Mod)
 		return nil, &PackageNotInModuleError{
 			Mod:         modOnly.Mod,
-			Replacement: Replacement(modOnly.Mod),
+			Replacement: replacement,
 			Query:       query,
 			Pattern:     pattern,
 		}
@@ -551,7 +553,7 @@
 		return m.Errs[0]
 	}
 
-	var match func(mod module.Version, root string, isLocal bool) *search.Match
+	var match func(mod module.Version, roots []string, isLocal bool) *search.Match
 	matchPattern := search.MatchPattern(pattern)
 
 	if i := strings.Index(pattern, "..."); i >= 0 {
@@ -559,30 +561,32 @@
 		if base == "." {
 			return nil, nil, &WildcardInFirstElementError{Pattern: pattern, Query: query}
 		}
-		match = func(mod module.Version, root string, isLocal bool) *search.Match {
+		match = func(mod module.Version, roots []string, isLocal bool) *search.Match {
 			m := search.NewMatch(pattern)
 			matchPackages(ctx, m, imports.AnyTags(), omitStd, []module.Version{mod})
 			return m
 		}
 	} else {
-		match = func(mod module.Version, root string, isLocal bool) *search.Match {
+		match = func(mod module.Version, roots []string, isLocal bool) *search.Match {
 			m := search.NewMatch(pattern)
 			prefix := mod.Path
-			if mod == Target {
-				prefix = targetPrefix
+			if MainModules.Contains(mod.Path) {
+				prefix = MainModules.PathPrefix(module.Version{Path: mod.Path})
 			}
-			if _, ok, err := dirInModule(pattern, prefix, root, isLocal); err != nil {
-				m.AddError(err)
-			} else if ok {
-				m.Pkgs = []string{pattern}
+			for _, root := range roots {
+				if _, ok, err := dirInModule(pattern, prefix, root, isLocal); err != nil {
+					m.AddError(err)
+				} else if ok {
+					m.Pkgs = []string{pattern}
+				}
 			}
 			return m
 		}
 	}
 
-	var queryMatchesMainModule bool
-	if HasModRoot() {
-		m := match(Target, modRoot, true)
+	var mainModuleMatches []module.Version
+	for _, mainModule := range MainModules.Versions() {
+		m := match(mainModule, modRoots, true)
 		if len(m.Pkgs) > 0 {
 			if query != "upgrade" && query != "patch" {
 				return nil, nil, &QueryMatchesPackagesInMainModuleError{
@@ -591,12 +595,12 @@
 					Packages: m.Pkgs,
 				}
 			}
-			if err := allowed(ctx, Target); err != nil {
-				return nil, nil, fmt.Errorf("internal error: package %s is in the main module (%s), but version is not allowed: %w", pattern, Target.Path, err)
+			if err := allowed(ctx, mainModule); err != nil {
+				return nil, nil, fmt.Errorf("internal error: package %s is in the main module (%s), but version is not allowed: %w", pattern, mainModule.Path, err)
 			}
 			return []QueryResult{{
-				Mod:      Target,
-				Rev:      &modfetch.RevInfo{Version: Target.Version},
+				Mod:      mainModule,
+				Rev:      &modfetch.RevInfo{Version: mainModule.Version},
 				Packages: m.Pkgs,
 			}}, nil, nil
 		}
@@ -604,15 +608,17 @@
 			return nil, nil, err
 		}
 
-		if matchPattern(Target.Path) {
-			queryMatchesMainModule = true
+		var matchesMainModule bool
+		if matchPattern(mainModule.Path) {
+			mainModuleMatches = append(mainModuleMatches, mainModule)
+			matchesMainModule = true
 		}
 
-		if (query == "upgrade" || query == "patch") && queryMatchesMainModule {
-			if err := allowed(ctx, Target); err == nil {
+		if (query == "upgrade" || query == "patch") && matchesMainModule {
+			if err := allowed(ctx, mainModule); err == nil {
 				modOnly = &QueryResult{
-					Mod: Target,
-					Rev: &modfetch.RevInfo{Version: Target.Version},
+					Mod: mainModule,
+					Rev: &modfetch.RevInfo{Version: mainModule.Version},
 				}
 			}
 		}
@@ -625,16 +631,17 @@
 	if len(candidateModules) == 0 {
 		if modOnly != nil {
 			return nil, modOnly, nil
-		} else if queryMatchesMainModule {
-			return nil, nil, &QueryMatchesMainModuleError{
-				Pattern: pattern,
-				Query:   query,
+		} else if len(mainModuleMatches) != 0 {
+			return nil, nil, &QueryMatchesMainModulesError{
+				MainModules: mainModuleMatches,
+				Pattern:     pattern,
+				Query:       query,
 			}
 		} else {
 			return nil, nil, &PackageNotInModuleError{
-				Mod:     Target,
-				Query:   query,
-				Pattern: pattern,
+				MainModules: mainModuleMatches,
+				Query:       query,
+				Pattern:     pattern,
 			}
 		}
 	}
@@ -656,15 +663,16 @@
 			if err != nil {
 				return r, err
 			}
-			m := match(r.Mod, root, isLocal)
+			m := match(r.Mod, []string{root}, isLocal)
 			r.Packages = m.Pkgs
 			if len(r.Packages) == 0 && !matchPattern(path) {
 				if err := firstError(m); err != nil {
 					return r, err
 				}
+				replacement, _ := Replacement(r.Mod)
 				return r, &PackageNotInModuleError{
 					Mod:         r.Mod,
-					Replacement: Replacement(r.Mod),
+					Replacement: replacement,
 					Query:       query,
 					Pattern:     pattern,
 				}
@@ -684,8 +692,8 @@
 		return err
 	})
 
-	if queryMatchesMainModule && len(results) == 0 && modOnly == nil && errors.Is(err, fs.ErrNotExist) {
-		return nil, nil, &QueryMatchesMainModuleError{
+	if len(mainModuleMatches) > 0 && len(results) == 0 && modOnly == nil && errors.Is(err, fs.ErrNotExist) {
+		return nil, nil, &QueryMatchesMainModulesError{
 			Pattern: pattern,
 			Query:   query,
 		}
@@ -701,8 +709,13 @@
 func modulePrefixesExcludingTarget(path string) []string {
 	prefixes := make([]string, 0, strings.Count(path, "/")+1)
 
+	mainModulePrefixes := make(map[string]bool)
+	for _, m := range MainModules.Versions() {
+		mainModulePrefixes[m.Path] = true
+	}
+
 	for {
-		if path != targetPrefix {
+		if !mainModulePrefixes[path] {
 			if _, _, ok := module.SplitPathVersion(path); ok {
 				prefixes = append(prefixes, path)
 			}
@@ -759,7 +772,7 @@
 		case *PackageNotInModuleError:
 			// Given the option, prefer to attribute “package not in module”
 			// to modules other than the main one.
-			if noPackage == nil || noPackage.Mod == Target {
+			if noPackage == nil || MainModules.Contains(noPackage.Mod.Path) {
 				noPackage = rErr
 			}
 		case *NoMatchingVersionError:
@@ -878,6 +891,7 @@
 // code for the versions it knows about, and thus did not have the opportunity
 // to return a non-400 status code to suppress fallback.
 type PackageNotInModuleError struct {
+	MainModules []module.Version
 	Mod         module.Version
 	Replacement module.Version
 	Query       string
@@ -885,11 +899,15 @@
 }
 
 func (e *PackageNotInModuleError) Error() string {
-	if e.Mod == Target {
-		if strings.Contains(e.Pattern, "...") {
-			return fmt.Sprintf("main module (%s) does not contain packages matching %s", Target.Path, e.Pattern)
+	if len(e.MainModules) > 0 {
+		prefix := "workspace modules do"
+		if len(e.MainModules) == 1 {
+			prefix = fmt.Sprintf("main module (%s) does", e.MainModules[0])
 		}
-		return fmt.Sprintf("main module (%s) does not contain package %s", Target.Path, e.Pattern)
+		if strings.Contains(e.Pattern, "...") {
+			return fmt.Sprintf("%s not contain packages matching %s", prefix, e.Pattern)
+		}
+		return fmt.Sprintf("%s not contain package %s", prefix, e.Pattern)
 	}
 
 	found := ""
@@ -951,7 +969,7 @@
 // we don't need to verify it in go.sum. This makes 'go list -m -u' faster
 // and simpler.
 func versionHasGoMod(_ context.Context, m module.Version) (bool, error) {
-	_, data, err := rawGoModData(m)
+	_, data, err := rawGoModData(m, "")
 	if err != nil {
 		return false, err
 	}
@@ -978,14 +996,18 @@
 		repo = emptyRepo{path: path, err: err}
 	}
 
-	if index == nil {
-		return repo, err
-	}
-	if _, ok := index.highestReplaced[path]; !ok {
-		return repo, err
+	// TODO(#45713): Join all the highestReplaced fields into a single value.
+	for _, mm := range MainModules.Versions() {
+		index := MainModules.Index(mm)
+		if index == nil {
+			continue
+		}
+		if _, ok := index.highestReplaced[path]; ok {
+			return &replacementRepo{repo: repo}, nil
+		}
 	}
 
-	return &replacementRepo{repo: repo}, nil
+	return repo, err
 }
 
 // An emptyRepo is a versionRepo that contains no versions.
@@ -1024,11 +1046,13 @@
 	}
 
 	versions := repoVersions
-	if index != nil && len(index.replace) > 0 {
-		path := rr.ModulePath()
-		for m, _ := range index.replace {
-			if m.Path == path && strings.HasPrefix(m.Version, prefix) && m.Version != "" && !module.IsPseudoVersion(m.Version) {
-				versions = append(versions, m.Version)
+	for _, mm := range MainModules.Versions() {
+		if index := MainModules.Index(mm); index != nil && len(index.replace) > 0 {
+			path := rr.ModulePath()
+			for m, _ := range index.replace {
+				if m.Path == path && strings.HasPrefix(m.Version, prefix) && m.Version != "" && !module.IsPseudoVersion(m.Version) {
+					versions = append(versions, m.Version)
+				}
 			}
 		}
 	}
@@ -1046,7 +1070,16 @@
 
 func (rr *replacementRepo) Stat(rev string) (*modfetch.RevInfo, error) {
 	info, err := rr.repo.Stat(rev)
-	if err == nil || index == nil || len(index.replace) == 0 {
+	if err == nil {
+		return info, err
+	}
+	var hasReplacements bool
+	for _, v := range MainModules.Versions() {
+		if index := MainModules.Index(v); index != nil && len(index.replace) > 0 {
+			hasReplacements = true
+		}
+	}
+	if !hasReplacements {
 		return info, err
 	}
 
@@ -1065,7 +1098,7 @@
 		}
 	}
 
-	if r := Replacement(module.Version{Path: path, Version: v}); r.Path == "" {
+	if r, _ := Replacement(module.Version{Path: path, Version: v}); r.Path == "" {
 		return info, err
 	}
 	return rr.replacementStat(v)
@@ -1073,27 +1106,42 @@
 
 func (rr *replacementRepo) Latest() (*modfetch.RevInfo, error) {
 	info, err := rr.repo.Latest()
+	path := rr.ModulePath()
 
-	if index != nil {
-		path := rr.ModulePath()
-		if v, ok := index.highestReplaced[path]; ok {
-			if v == "" {
-				// The only replacement is a wildcard that doesn't specify a version, so
-				// synthesize a pseudo-version with an appropriate major version and a
-				// timestamp below any real timestamp. That way, if the main module is
-				// used from within some other module, the user will be able to upgrade
-				// the requirement to any real version they choose.
-				if _, pathMajor, ok := module.SplitPathVersion(path); ok && len(pathMajor) > 0 {
-					v = module.PseudoVersion(pathMajor[1:], "", time.Time{}, "000000000000")
-				} else {
-					v = module.PseudoVersion("v0", "", time.Time{}, "000000000000")
+	highestReplaced, found := "", false
+	for _, mm := range MainModules.Versions() {
+		if index := MainModules.Index(mm); index != nil {
+			if v, ok := index.highestReplaced[path]; ok {
+				if !found {
+					highestReplaced, found = v, true
+					continue
+				}
+				if semver.Compare(v, highestReplaced) > 0 {
+					highestReplaced = v
 				}
 			}
+		}
+	}
 
-			if err != nil || semver.Compare(v, info.Version) > 0 {
-				return rr.replacementStat(v)
+	if found {
+		v := highestReplaced
+
+		if v == "" {
+			// The only replacement is a wildcard that doesn't specify a version, so
+			// synthesize a pseudo-version with an appropriate major version and a
+			// timestamp below any real timestamp. That way, if the main module is
+			// used from within some other module, the user will be able to upgrade
+			// the requirement to any real version they choose.
+			if _, pathMajor, ok := module.SplitPathVersion(path); ok && len(pathMajor) > 0 {
+				v = module.PseudoVersion(pathMajor[1:], "", time.Time{}, "000000000000")
+			} else {
+				v = module.PseudoVersion("v0", "", time.Time{}, "000000000000")
 			}
 		}
+
+		if err != nil || semver.Compare(v, info.Version) > 0 {
+			return rr.replacementStat(v)
+		}
 	}
 
 	return info, err
@@ -1108,20 +1156,46 @@
 	return rev, nil
 }
 
-// A QueryMatchesMainModuleError indicates that a query requests
+// A QueryMatchesMainModulesError indicates that a query requests
 // a version of the main module that cannot be satisfied.
 // (The main module's version cannot be changed.)
-type QueryMatchesMainModuleError struct {
-	Pattern string
-	Query   string
+type QueryMatchesMainModulesError struct {
+	MainModules []module.Version
+	Pattern     string
+	Query       string
 }
 
-func (e *QueryMatchesMainModuleError) Error() string {
-	if e.Pattern == Target.Path {
+func (e *QueryMatchesMainModulesError) Error() string {
+	if MainModules.Contains(e.Pattern) {
 		return fmt.Sprintf("can't request version %q of the main module (%s)", e.Query, e.Pattern)
 	}
 
-	return fmt.Sprintf("can't request version %q of pattern %q that includes the main module (%s)", e.Query, e.Pattern, Target.Path)
+	plural := ""
+	mainModulePaths := make([]string, len(e.MainModules))
+	for i := range e.MainModules {
+		mainModulePaths[i] = e.MainModules[i].Path
+	}
+	if len(e.MainModules) > 1 {
+		plural = "s"
+	}
+	return fmt.Sprintf("can't request version %q of pattern %q that includes the main module%s (%s)", e.Query, e.Pattern, plural, strings.Join(mainModulePaths, ", "))
+}
+
+// A QueryUpgradesAllError indicates that a query requests
+// an upgrade on the all pattern.
+// (The main module's version cannot be changed.)
+type QueryUpgradesAllError struct {
+	MainModules []module.Version
+	Query       string
+}
+
+func (e *QueryUpgradesAllError) Error() string {
+	var plural string = ""
+	if len(e.MainModules) != 1 {
+		plural = "s"
+	}
+
+	return fmt.Sprintf("can't request version %q of pattern \"all\" that includes the main module%s", e.Query, plural)
 }
 
 // A QueryMatchesPackagesInMainModuleError indicates that a query cannot be
diff --git a/src/cmd/go/internal/modload/search.go b/src/cmd/go/internal/modload/search.go
index 658fc6f..799c48e 100644
--- a/src/cmd/go/internal/modload/search.go
+++ b/src/cmd/go/internal/modload/search.go
@@ -131,9 +131,10 @@
 	}
 
 	if cfg.BuildMod == "vendor" {
-		if HasModRoot() {
-			walkPkgs(ModRoot(), targetPrefix, pruneGoMod|pruneVendor)
-			walkPkgs(filepath.Join(ModRoot(), "vendor"), "", pruneVendor)
+		mod := MainModules.mustGetSingleMainModule()
+		if modRoot := MainModules.ModRoot(mod); modRoot != "" {
+			walkPkgs(modRoot, MainModules.PathPrefix(mod), pruneGoMod|pruneVendor)
+			walkPkgs(filepath.Join(modRoot, "vendor"), "", pruneVendor)
 		}
 		return
 	}
@@ -147,12 +148,12 @@
 			root, modPrefix string
 			isLocal         bool
 		)
-		if mod == Target {
-			if !HasModRoot() {
+		if MainModules.Contains(mod.Path) {
+			if MainModules.ModRoot(mod) == "" {
 				continue // If there is no main module, we can't search in it.
 			}
-			root = ModRoot()
-			modPrefix = targetPrefix
+			root = MainModules.ModRoot(mod)
+			modPrefix = MainModules.PathPrefix(mod)
 			isLocal = true
 		} else {
 			var err error
diff --git a/src/cmd/go/internal/modload/vendor.go b/src/cmd/go/internal/modload/vendor.go
index 80713b0..daa5888 100644
--- a/src/cmd/go/internal/modload/vendor.go
+++ b/src/cmd/go/internal/modload/vendor.go
@@ -15,6 +15,7 @@
 
 	"cmd/go/internal/base"
 
+	"golang.org/x/mod/modfile"
 	"golang.org/x/mod/module"
 	"golang.org/x/mod/semver"
 )
@@ -35,13 +36,13 @@
 }
 
 // readVendorList reads the list of vendored modules from vendor/modules.txt.
-func readVendorList() {
+func readVendorList(mainModule module.Version) {
 	vendorOnce.Do(func() {
 		vendorList = nil
 		vendorPkgModule = make(map[string]module.Version)
 		vendorVersion = make(map[string]string)
 		vendorMeta = make(map[module.Version]vendorMetadata)
-		data, err := os.ReadFile(filepath.Join(ModRoot(), "vendor/modules.txt"))
+		data, err := os.ReadFile(filepath.Join(MainModules.ModRoot(mainModule), "vendor/modules.txt"))
 		if err != nil {
 			if !errors.Is(err, fs.ErrNotExist) {
 				base.Fatalf("go: %s", err)
@@ -134,8 +135,8 @@
 // checkVendorConsistency verifies that the vendor/modules.txt file matches (if
 // go 1.14) or at least does not contradict (go 1.13 or earlier) the
 // requirements and replacements listed in the main module's go.mod file.
-func checkVendorConsistency() {
-	readVendorList()
+func checkVendorConsistency(index *modFileIndex, modFile *modfile.File) {
+	readVendorList(MainModules.mustGetSingleMainModule())
 
 	pre114 := false
 	if semver.Compare(index.goVersionV, "v1.14") < 0 {
@@ -208,7 +209,7 @@
 	}
 
 	for _, mod := range vendorReplaced {
-		r := Replacement(mod)
+		r, _ := Replacement(mod)
 		if r == (module.Version{}) {
 			vendErrorf(mod, "is marked as replaced in vendor/modules.txt, but not replaced in go.mod")
 			continue
@@ -219,6 +220,7 @@
 	}
 
 	if vendErrors.Len() > 0 {
+		modRoot := MainModules.ModRoot(MainModules.mustGetSingleMainModule())
 		base.Fatalf("go: inconsistent vendoring in %s:%s\n\n\tTo ignore the vendor directory, use -mod=readonly or -mod=mod.\n\tTo sync the vendor directory, run:\n\t\tgo mod vendor", modRoot, vendErrors)
 	}
 }
diff --git a/src/cmd/go/internal/mvs/mvs.go b/src/cmd/go/internal/mvs/mvs.go
index 6969f90..566fa4b 100644
--- a/src/cmd/go/internal/mvs/mvs.go
+++ b/src/cmd/go/internal/mvs/mvs.go
@@ -8,6 +8,7 @@
 
 import (
 	"fmt"
+	"reflect"
 	"sort"
 	"sync"
 
@@ -85,11 +86,11 @@
 // of the list are sorted by path.
 //
 // See https://research.swtch.com/vgo-mvs for details.
-func BuildList(target module.Version, reqs Reqs) ([]module.Version, error) {
-	return buildList(target, reqs, nil)
+func BuildList(targets []module.Version, reqs Reqs) ([]module.Version, error) {
+	return buildList(targets, reqs, nil)
 }
 
-func buildList(target module.Version, reqs Reqs, upgrade func(module.Version) (module.Version, error)) ([]module.Version, error) {
+func buildList(targets []module.Version, reqs Reqs, upgrade func(module.Version) (module.Version, error)) ([]module.Version, error) {
 	cmp := func(v1, v2 string) int {
 		if reqs.Max(v1, v2) != v1 {
 			return -1
@@ -102,7 +103,7 @@
 
 	var (
 		mu       sync.Mutex
-		g        = NewGraph(cmp, []module.Version{target})
+		g        = NewGraph(cmp, targets)
 		upgrades = map[module.Version]module.Version{}
 		errs     = map[module.Version]error{} // (non-nil errors only)
 	)
@@ -110,7 +111,9 @@
 	// Explore work graph in parallel in case reqs.Required
 	// does high-latency network operations.
 	var work par.Work
-	work.Add(target)
+	for _, target := range targets {
+		work.Add(target)
+	}
 	work.Do(10, func(item interface{}) {
 		m := item.(module.Version)
 
@@ -168,12 +171,12 @@
 
 	// The final list is the minimum version of each module found in the graph.
 	list := g.BuildList()
-	if v := list[0]; v != target {
+	if vs := list[:len(targets)]; !reflect.DeepEqual(vs, targets) {
 		// target.Version will be "" for modload, the main client of MVS.
 		// "" denotes the main module, which has no version. However, MVS treats
 		// version strings as opaque, so "" is not a special value here.
 		// See golang.org/issue/31491, golang.org/issue/29773.
-		panic(fmt.Sprintf("mistake: chose version %q instead of target %+v", v, target))
+		panic(fmt.Sprintf("mistake: chose versions %+v instead of targets %+v", vs, targets))
 	}
 	return list, nil
 }
@@ -181,8 +184,8 @@
 // Req returns the minimal requirement list for the target module,
 // with the constraint that all module paths listed in base must
 // appear in the returned list.
-func Req(target module.Version, base []string, reqs Reqs) ([]module.Version, error) {
-	list, err := BuildList(target, reqs)
+func Req(mainModule module.Version, base []string, reqs Reqs) ([]module.Version, error) {
+	list, err := BuildList([]module.Version{mainModule}, reqs)
 	if err != nil {
 		return nil, err
 	}
@@ -194,7 +197,8 @@
 	// Compute postorder, cache requirements.
 	var postorder []module.Version
 	reqCache := map[module.Version][]module.Version{}
-	reqCache[target] = nil
+	reqCache[mainModule] = nil
+
 	var walk func(module.Version) error
 	walk = func(m module.Version) error {
 		_, ok := reqCache[m]
@@ -273,7 +277,7 @@
 // UpgradeAll returns a build list for the target module
 // in which every module is upgraded to its latest version.
 func UpgradeAll(target module.Version, reqs UpgradeReqs) ([]module.Version, error) {
-	return buildList(target, reqs, func(m module.Version) (module.Version, error) {
+	return buildList([]module.Version{target}, reqs, func(m module.Version) (module.Version, error) {
 		if m.Path == target.Path {
 			return target, nil
 		}
@@ -308,7 +312,7 @@
 		}
 	}
 
-	return buildList(target, &override{target, list, reqs}, func(m module.Version) (module.Version, error) {
+	return buildList([]module.Version{target}, &override{target, list, reqs}, func(m module.Version) (module.Version, error) {
 		if v, ok := upgradeTo[m.Path]; ok {
 			return module.Version{Path: m.Path, Version: v}, nil
 		}
@@ -331,7 +335,7 @@
 	//
 	// In order to generate those new requirements, we need to identify versions
 	// for every module in the build list — not just reqs.Required(target).
-	list, err := BuildList(target, reqs)
+	list, err := BuildList([]module.Version{target}, reqs)
 	if err != nil {
 		return nil, err
 	}
@@ -446,7 +450,7 @@
 	// list with the actual versions of the downgraded modules as selected by MVS,
 	// instead of our initial downgrades.
 	// (See the downhiddenartifact and downhiddencross test cases).
-	actual, err := BuildList(target, &override{
+	actual, err := BuildList([]module.Version{target}, &override{
 		target: target,
 		list:   downgraded,
 		Reqs:   reqs,
@@ -466,7 +470,7 @@
 		}
 	}
 
-	return BuildList(target, &override{
+	return BuildList([]module.Version{target}, &override{
 		target: target,
 		list:   downgraded,
 		Reqs:   reqs,
diff --git a/src/cmd/go/internal/mvs/mvs_test.go b/src/cmd/go/internal/mvs/mvs_test.go
index 598ed66..26d004f 100644
--- a/src/cmd/go/internal/mvs/mvs_test.go
+++ b/src/cmd/go/internal/mvs/mvs_test.go
@@ -507,7 +507,7 @@
 				t.Fatalf("build takes one argument: %q", line)
 			}
 			fns = append(fns, func(t *testing.T) {
-				list, err := BuildList(m(kf[1]), reqs)
+				list, err := BuildList([]module.Version{m(kf[1])}, reqs)
 				checkList(t, key, list, err, val)
 			})
 			continue
diff --git a/src/cmd/go/internal/run/run.go b/src/cmd/go/internal/run/run.go
index 9e9e49e..931fdce 100644
--- a/src/cmd/go/internal/run/run.go
+++ b/src/cmd/go/internal/run/run.go
@@ -65,6 +65,7 @@
 	CmdRun.Run = runRun // break init loop
 
 	work.AddBuildFlags(CmdRun, work.DefaultBuildFlags)
+	base.AddWorkfileFlag(&CmdRun.Flag)
 	CmdRun.Flag.Var((*base.StringsFlag)(&work.ExecCmd), "exec", "")
 }
 
@@ -73,6 +74,8 @@
 }
 
 func runRun(ctx context.Context, cmd *base.Command, args []string) {
+	modload.InitWorkfile()
+
 	if shouldUseOutsideModuleMode(args) {
 		// Set global module flags for 'go run cmd@version'.
 		// This must be done before modload.Init, but we need to call work.BuildInit
diff --git a/src/cmd/go/internal/search/search.go b/src/cmd/go/internal/search/search.go
index a0c806a..ebd4990 100644
--- a/src/cmd/go/internal/search/search.go
+++ b/src/cmd/go/internal/search/search.go
@@ -202,12 +202,6 @@
 	}
 }
 
-var modRoot string
-
-func SetModRoot(dir string) {
-	modRoot = dir
-}
-
 // MatchDirs sets m.Dirs to a non-nil slice containing all directories that
 // potentially match a local pattern. The pattern must begin with an absolute
 // path, or "./", or "../". On Windows, the pattern may use slash or backslash
@@ -215,7 +209,7 @@
 //
 // If any errors may have caused the set of directories to be incomplete,
 // MatchDirs appends those errors to m.Errs.
-func (m *Match) MatchDirs() {
+func (m *Match) MatchDirs(modRoots []string) {
 	m.Dirs = []string{}
 	if !m.IsLocal() {
 		m.AddError(fmt.Errorf("internal error: MatchDirs: %s is not a valid filesystem pattern", m.pattern))
@@ -253,15 +247,24 @@
 	// We need to preserve the ./ for pattern matching
 	// and in the returned import paths.
 
-	if modRoot != "" {
+	if len(modRoots) > 1 {
 		abs, err := filepath.Abs(dir)
 		if err != nil {
 			m.AddError(err)
 			return
 		}
-		if !hasFilepathPrefix(abs, modRoot) {
-			m.AddError(fmt.Errorf("directory %s is outside module root (%s)", abs, modRoot))
-			return
+		var found bool
+		for _, modRoot := range modRoots {
+			if modRoot != "" && hasFilepathPrefix(abs, modRoot) {
+				found = true
+			}
+		}
+		if !found {
+			plural := ""
+			if len(modRoots) > 1 {
+				plural = "s"
+			}
+			m.AddError(fmt.Errorf("directory %s is outside module root%s (%s)", abs, plural, strings.Join(modRoots, ", ")))
 		}
 	}
 
@@ -424,19 +427,19 @@
 
 // ImportPaths returns the matching paths to use for the given command line.
 // It calls ImportPathsQuiet and then WarnUnmatched.
-func ImportPaths(patterns []string) []*Match {
-	matches := ImportPathsQuiet(patterns)
+func ImportPaths(patterns, modRoots []string) []*Match {
+	matches := ImportPathsQuiet(patterns, modRoots)
 	WarnUnmatched(matches)
 	return matches
 }
 
 // ImportPathsQuiet is like ImportPaths but does not warn about patterns with no matches.
-func ImportPathsQuiet(patterns []string) []*Match {
+func ImportPathsQuiet(patterns, modRoots []string) []*Match {
 	var out []*Match
 	for _, a := range CleanPatterns(patterns) {
 		m := NewMatch(a)
 		if m.IsLocal() {
-			m.MatchDirs()
+			m.MatchDirs(modRoots)
 
 			// Change the file import path to a regular import path if the package
 			// is in GOPATH or GOROOT. We don't report errors here; LoadImport
diff --git a/src/cmd/go/internal/test/test.go b/src/cmd/go/internal/test/test.go
index efebc12..d65f54f 100644
--- a/src/cmd/go/internal/test/test.go
+++ b/src/cmd/go/internal/test/test.go
@@ -29,6 +29,7 @@
 	"cmd/go/internal/cfg"
 	"cmd/go/internal/load"
 	"cmd/go/internal/lockedfile"
+	"cmd/go/internal/modload"
 	"cmd/go/internal/search"
 	"cmd/go/internal/trace"
 	"cmd/go/internal/work"
@@ -582,6 +583,7 @@
 }
 
 func runTest(ctx context.Context, cmd *base.Command, args []string) {
+	modload.InitWorkfile()
 	pkgArgs, testArgs = testFlags(args)
 
 	if cfg.DebugTrace != "" {
diff --git a/src/cmd/go/internal/test/testflag.go b/src/cmd/go/internal/test/testflag.go
index 45118cb..97a9ef3 100644
--- a/src/cmd/go/internal/test/testflag.go
+++ b/src/cmd/go/internal/test/testflag.go
@@ -28,6 +28,7 @@
 
 func init() {
 	work.AddBuildFlags(CmdTest, work.OmitVFlag)
+	base.AddWorkfileFlag(&CmdTest.Flag)
 
 	cf := CmdTest.Flag
 	cf.BoolVar(&testC, "c", false, "")
diff --git a/src/cmd/go/internal/work/build.go b/src/cmd/go/internal/work/build.go
index 0ed2389..c51dd39 100644
--- a/src/cmd/go/internal/work/build.go
+++ b/src/cmd/go/internal/work/build.go
@@ -121,6 +121,14 @@
 		directory, but it is not accessed. When -modfile is specified, an
 		alternate go.sum file is also used: its path is derived from the
 		-modfile flag by trimming the ".mod" extension and appending ".sum".
+  -workfile file
+    in module aware mode, use the given go.work file as a workspace file.
+		By default or when -workfile is "auto", the go command searches for a
+		file named go.work in the current directory and then containing directories
+		until one is found. If a valid go.work file is found, the modules
+		specified will collectively be used as the main modules. If -workfile
+		is "off", or a go.work file is not found in "auto" mode, workspace
+		mode is disabled.
 	-overlay file
 		read a JSON config file that provides an overlay for build operations.
 		The file is a JSON struct with a single field, named 'Replace', that
@@ -201,6 +209,7 @@
 
 	AddBuildFlags(CmdBuild, DefaultBuildFlags)
 	AddBuildFlags(CmdInstall, DefaultBuildFlags)
+	base.AddWorkfileFlag(&CmdBuild.Flag)
 }
 
 // Note that flags consulted by other parts of the code
@@ -364,6 +373,7 @@
 var runtimeVersion = runtime.Version()
 
 func runBuild(ctx context.Context, cmd *base.Command, args []string) {
+	modload.InitWorkfile()
 	BuildInit()
 	var b Builder
 	b.Init()
diff --git a/src/cmd/go/testdata/script/mod_list_command_line_arguments.txt b/src/cmd/go/testdata/script/mod_list_command_line_arguments.txt
new file mode 100644
index 0000000..fd99ae8
--- /dev/null
+++ b/src/cmd/go/testdata/script/mod_list_command_line_arguments.txt
@@ -0,0 +1,35 @@
+# The command-line-arguments package does not belong to a module...
+cd a
+go list -f '{{.Module}}' ../b/b.go
+stdout '^<nil>$'
+
+# ... even if the arguments are sources from that module
+go list -f '{{.Module}}' a.go
+stdout '^<nil>$'
+
+[short] skip
+
+# check that the version of command-line-arguments doesn't include a module
+go build -o a.exe a.go
+go version -m a.exe
+stdout '^\tpath\tcommand-line-arguments$'
+stdout '^\tdep\ta\t\(devel\)\t$'
+! stdout mod
+
+-- a/go.mod --
+module a
+go 1.17
+-- a/a.go --
+package main
+
+import "a/dep"
+
+func main() {
+    dep.D()
+}
+-- a/dep/dep.go --
+package dep
+
+func D() {}
+-- b/b.go --
+package b
\ No newline at end of file
diff --git a/src/cmd/go/testdata/script/mod_outside.txt b/src/cmd/go/testdata/script/mod_outside.txt
index 33341f7..3b45594 100644
--- a/src/cmd/go/testdata/script/mod_outside.txt
+++ b/src/cmd/go/testdata/script/mod_outside.txt
@@ -251,7 +251,7 @@
 # outside std.
 go run ./stdonly/stdonly.go
 stdout 'path is command-line-arguments$'
-stdout 'main is command-line-arguments \(devel\)'
+stdout 'main is  $'
 
 # 'go generate' should work with file arguments.
 [exec:touch] go generate ./needmod/needmod.go
diff --git a/src/cmd/go/testdata/script/modfile_flag.txt b/src/cmd/go/testdata/script/modfile_flag.txt
index baf25d3..7cce581 100644
--- a/src/cmd/go/testdata/script/modfile_flag.txt
+++ b/src/cmd/go/testdata/script/modfile_flag.txt
@@ -78,7 +78,7 @@
 cmp go.sum go.sum.orig
 
 
-# If the altnernate mod file does not have a ".mod" suffix, an error
+# If the alternate mod file does not have a ".mod" suffix, an error
 # should be reported.
 cp go.alt.mod goaltmod
 ! go mod tidy -modfile=goaltmod
diff --git a/src/cmd/go/testdata/script/version.txt b/src/cmd/go/testdata/script/version.txt
index 8615a4a..f3aa57e 100644
--- a/src/cmd/go/testdata/script/version.txt
+++ b/src/cmd/go/testdata/script/version.txt
@@ -28,6 +28,13 @@
 stdout '^\tpath\trsc.io/fortune'
 stdout '^\tmod\trsc.io/fortune\tv1.0.0'
 
+# Check the build info of a binary built from $GOROOT/src/cmd
+go build -o test2json.exe cmd/test2json
+go version -m test2json.exe
+stdout '^test2json.exe: .+'
+stdout '^\tpath\tcmd/test2json$'
+! stdout 'mod'
+
 # Repeat the test with -buildmode=pie.
 [!buildmode:pie] stop
 go build -buildmode=pie -o external.exe rsc.io/fortune
diff --git a/src/cmd/go/testdata/script/work.txt b/src/cmd/go/testdata/script/work.txt
new file mode 100644
index 0000000..529c1c0
--- /dev/null
+++ b/src/cmd/go/testdata/script/work.txt
@@ -0,0 +1,140 @@
+go mod initwork ./a ./b
+cmp go.work go.work.want
+
+! go run  example.com/b
+stderr 'a(\\|/)a.go:4:8: no required module provides package rsc.io/quote; to add it:\n\tcd '$WORK(\\|/)gopath(\\|/)src(\\|/)a'\n\tgo get rsc.io/quote'
+cd a
+go get rsc.io/quote
+go env GOMOD # go env GOMOD reports the module in a single module context
+stdout $GOPATH(\\|/)src(\\|/)a(\\|/)go.mod
+cd ..
+go run example.com/b
+stdout 'Hello, world.'
+
+# And try from a different directory
+cd c
+go run  example.com/b
+stdout 'Hello, world.'
+cd $GOPATH/src
+
+go list all # all includes both modules
+stdout 'example.com/a'
+stdout 'example.com/b'
+
+# -mod can only be set to readonly in workspace mode
+go list -mod=readonly all
+! go list -mod=mod all
+stderr '^go: -mod may only be set to readonly when in workspace mode'
+go list -mod=mod -workfile=off all
+
+# Test that duplicates in the directory list return an error
+cp go.work go.work.backup
+cp go.work.dup go.work
+! go run example.com/b
+stderr 'reading go.work: path .* appears multiple times in workspace'
+cp go.work.backup go.work
+
+cp go.work.d go.work
+go run example.com/d
+
+# Test that we don't run into "newRequirements called with unsorted roots"
+# panic with unsorted main modules.
+cp go.work.backwards go.work
+go run example.com/d
+
+# Test that command-line-arguments work inside and outside modules.
+# This exercises the code that determines which module command-line-arguments
+# belongs to.
+go list ./b/main.go
+go build -n -workfile=off -o foo foo.go
+go build -n -o foo foo.go
+
+-- go.work.dup --
+go 1.18
+
+directory (
+  a
+  b
+  ../src/a
+)
+-- go.work.want --
+go 1.18
+
+directory (
+	./a
+	./b
+)
+-- go.work.d --
+go 1.18
+
+directory (
+	a
+	b
+	d
+)
+-- a/go.mod --
+
+module example.com/a
+
+-- a/a.go --
+package a
+
+import "fmt"
+import "rsc.io/quote"
+
+func HelloFromA() {
+  fmt.Println(quote.Hello())
+}
+
+-- b/go.mod --
+
+module example.com/b
+
+-- b/main.go --
+package main
+
+import "example.com/a"
+
+func main() {
+  a.HelloFromA()
+}
+-- b/lib/hello.go --
+package lib
+
+import "example.com/a"
+
+func Hello() {
+	a.HelloFromA()
+}
+
+-- c/README --
+Create this directory so we can cd to
+it and make sure paths are interpreted
+relative to the go.work, not the cwd.
+-- d/go.mod --
+module example.com/d
+
+-- d/main.go --
+package main
+
+import "example.com/b/lib"
+
+func main() {
+	lib.Hello()
+}
+
+-- go.work.backwards --
+go 1.18
+
+directory (
+    d
+    b
+    a
+)
+
+-- foo.go --
+package main
+import "fmt"
+func main() {
+	fmt.Println("Hello, World")
+}
diff --git a/src/cmd/go/testdata/script/work_edit.txt b/src/cmd/go/testdata/script/work_edit.txt
new file mode 100644
index 0000000..0de4069
--- /dev/null
+++ b/src/cmd/go/testdata/script/work_edit.txt
@@ -0,0 +1,157 @@
+# Test editing go.work files.
+
+go mod initwork m
+cmp go.work go.work.want_initial
+
+go mod editwork -directory n
+cmp go.work go.work.want_directory_n
+
+go mod editwork -go 1.18
+cmp go.work go.work.want_go_118
+
+go mod editwork -dropdirectory m
+cmp go.work go.work.want_dropdirectory_m
+
+go mod editwork -replace=x.1@v1.3.0=y.1@v1.4.0 -replace='x.1@v1.4.0 = ../z'
+cmp go.work go.work.want_add_replaces
+
+go mod editwork -directory n -directory ../a -directory /b -directory c -directory c
+cmp go.work go.work.want_multidirectory
+
+go mod editwork -dropdirectory /b -dropdirectory n
+cmp go.work go.work.want_multidropdirectory
+
+go mod editwork -dropreplace='x.1@v1.4.0'
+cmp go.work go.work.want_dropreplace
+
+go mod editwork -print -go 1.19 -directory b -dropdirectory c -replace 'x.1@v1.4.0 = ../z' -dropreplace x.1 -dropreplace x.1@v1.3.0
+cmp stdout go.work.want_print
+
+go mod editwork -json -go 1.19 -directory b -dropdirectory c -replace 'x.1@v1.4.0 = ../z' -dropreplace x.1 -dropreplace x.1@v1.3.0
+cmp stdout go.work.want_json
+
+go mod editwork -print -fmt -workfile unformatted
+cmp stdout formatted
+
+-- go.work.want_initial --
+go 1.18
+
+directory m
+-- go.work.want_directory_n --
+go 1.18
+
+directory (
+	m
+	n
+)
+-- go.work.want_go_118 --
+go 1.18
+
+directory (
+	m
+	n
+)
+-- go.work.want_dropdirectory_m --
+go 1.18
+
+directory n
+-- go.work.want_add_replaces --
+go 1.18
+
+directory n
+
+replace (
+	x.1 v1.3.0 => y.1 v1.4.0
+	x.1 v1.4.0 => ../z
+)
+-- go.work.want_multidirectory --
+go 1.18
+
+directory (
+	../a
+	/b
+	c
+	n
+)
+
+replace (
+	x.1 v1.3.0 => y.1 v1.4.0
+	x.1 v1.4.0 => ../z
+)
+-- go.work.want_multidropdirectory --
+go 1.18
+
+directory (
+	../a
+	c
+)
+
+replace (
+	x.1 v1.3.0 => y.1 v1.4.0
+	x.1 v1.4.0 => ../z
+)
+-- go.work.want_dropreplace --
+go 1.18
+
+directory (
+	../a
+	c
+)
+
+replace x.1 v1.3.0 => y.1 v1.4.0
+-- go.work.want_print --
+go 1.19
+
+directory (
+	../a
+	b
+)
+
+replace x.1 v1.4.0 => ../z
+-- go.work.want_json --
+{
+	"Go": "1.19",
+	"Directory": [
+		{
+			"DiskPath": "../a"
+		},
+		{
+			"DiskPath": "b"
+		}
+	],
+	"Replace": [
+		{
+			"Old": {
+				"Path": "x.1",
+				"Version": "v1.4.0"
+			},
+			"New": {
+				"Path": "../z"
+			}
+		}
+	]
+}
+-- unformatted --
+go 1.18
+ directory (
+ a
+  b
+  c
+  )
+  replace (
+  x.1 v1.3.0 => y.1 v1.4.0
+                            x.1 v1.4.0 => ../z
+                            )
+-- formatted --
+go 1.18
+
+directory (
+	a
+	b
+	c
+)
+
+replace (
+	x.1 v1.3.0 => y.1 v1.4.0
+	x.1 v1.4.0 => ../z
+)
\ No newline at end of file
diff --git a/src/cmd/go/testdata/script/work_sum.txt b/src/cmd/go/testdata/script/work_sum.txt
new file mode 100644
index 0000000..99f66a4
--- /dev/null
+++ b/src/cmd/go/testdata/script/work_sum.txt
@@ -0,0 +1,33 @@
+# Test adding sums to go.work.sum when sum isn't in go.mod.
+
+go run .
+cmp go.work.sum want.sum
+
+-- want.sum --
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:pvCbr/wm8HzDD3fVywevekufpn6tCGPY3spdHeZJEsw=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+rsc.io/quote v1.5.2 h1:3fEykkD9k7lYzXqCYrwGAf7iNhbk4yCjHmKBN9td4L0=
+rsc.io/quote v1.5.2/go.mod h1:LzX7hefJvL54yjefDEDHNONDjII0t9xZLPXsUe+TKr0=
+rsc.io/sampler v1.3.0 h1:HLGR/BgEtI3r0uymSP/nl2uPLsUnNJX8toRyhfpBTII=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+-- go.work --
+go 1.18
+
+directory .
+-- go.mod --
+go 1.18
+
+module example.com/hi
+
+require "rsc.io/quote" v1.5.2
+-- main.go --
+package main
+
+import (
+	"fmt"
+	"rsc.io/quote"
+)
+
+func main() {
+	fmt.Println(quote.Hello())
+}
\ No newline at end of file
diff --git a/src/cmd/vendor/golang.org/x/mod/modfile/rule.go b/src/cmd/vendor/golang.org/x/mod/modfile/rule.go
index 78f83fa..d6a2d38 100644
--- a/src/cmd/vendor/golang.org/x/mod/modfile/rule.go
+++ b/src/cmd/vendor/golang.org/x/mod/modfile/rule.go
@@ -423,68 +423,12 @@
 		}
 
 	case "replace":
-		arrow := 2
-		if len(args) >= 2 && args[1] == "=>" {
-			arrow = 1
-		}
-		if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" {
-			errorf("usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory", verb, verb)
+		replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix)
+		if wrappederr != nil {
+			*errs = append(*errs, *wrappederr)
 			return
 		}
-		s, err := parseString(&args[0])
-		if err != nil {
-			errorf("invalid quoted string: %v", err)
-			return
-		}
-		pathMajor, err := modulePathMajor(s)
-		if err != nil {
-			wrapModPathError(s, err)
-			return
-		}
-		var v string
-		if arrow == 2 {
-			v, err = parseVersion(verb, s, &args[1], fix)
-			if err != nil {
-				wrapError(err)
-				return
-			}
-			if err := module.CheckPathMajor(v, pathMajor); err != nil {
-				wrapModPathError(s, err)
-				return
-			}
-		}
-		ns, err := parseString(&args[arrow+1])
-		if err != nil {
-			errorf("invalid quoted string: %v", err)
-			return
-		}
-		nv := ""
-		if len(args) == arrow+2 {
-			if !IsDirectoryPath(ns) {
-				errorf("replacement module without version must be directory path (rooted or starting with ./ or ../)")
-				return
-			}
-			if filepath.Separator == '/' && strings.Contains(ns, `\`) {
-				errorf("replacement directory appears to be Windows path (on a non-windows system)")
-				return
-			}
-		}
-		if len(args) == arrow+3 {
-			nv, err = parseVersion(verb, ns, &args[arrow+2], fix)
-			if err != nil {
-				wrapError(err)
-				return
-			}
-			if IsDirectoryPath(ns) {
-				errorf("replacement module directory path %q cannot have version", ns)
-				return
-			}
-		}
-		f.Replace = append(f.Replace, &Replace{
-			Old:    module.Version{Path: s, Version: v},
-			New:    module.Version{Path: ns, Version: nv},
-			Syntax: line,
-		})
+		f.Replace = append(f.Replace, replace)
 
 	case "retract":
 		rationale := parseDirectiveComment(block, line)
@@ -515,6 +459,83 @@
 	}
 }
 
+func parseReplace(filename string, line *Line, verb string, args []string, fix VersionFixer) (*Replace, *Error) {
+	wrapModPathError := func(modPath string, err error) *Error {
+		return &Error{
+			Filename: filename,
+			Pos:      line.Start,
+			ModPath:  modPath,
+			Verb:     verb,
+			Err:      err,
+		}
+	}
+	wrapError := func(err error) *Error {
+		return &Error{
+			Filename: filename,
+			Pos:      line.Start,
+			Err:      err,
+		}
+	}
+	errorf := func(format string, args ...interface{}) *Error {
+		return wrapError(fmt.Errorf(format, args...))
+	}
+
+	arrow := 2
+	if len(args) >= 2 && args[1] == "=>" {
+		arrow = 1
+	}
+	if len(args) < arrow+2 || len(args) > arrow+3 || args[arrow] != "=>" {
+		return nil, errorf("usage: %s module/path [v1.2.3] => other/module v1.4\n\t or %s module/path [v1.2.3] => ../local/directory", verb, verb)
+	}
+	s, err := parseString(&args[0])
+	if err != nil {
+		return nil, errorf("invalid quoted string: %v", err)
+	}
+	pathMajor, err := modulePathMajor(s)
+	if err != nil {
+		return nil, wrapModPathError(s, err)
+
+	}
+	var v string
+	if arrow == 2 {
+		v, err = parseVersion(verb, s, &args[1], fix)
+		if err != nil {
+			return nil, wrapError(err)
+		}
+		if err := module.CheckPathMajor(v, pathMajor); err != nil {
+			return nil, wrapModPathError(s, err)
+		}
+	}
+	ns, err := parseString(&args[arrow+1])
+	if err != nil {
+		return nil, errorf("invalid quoted string: %v", err)
+	}
+	nv := ""
+	if len(args) == arrow+2 {
+		if !IsDirectoryPath(ns) {
+			return nil, errorf("replacement module without version must be directory path (rooted or starting with ./ or ../)")
+		}
+		if filepath.Separator == '/' && strings.Contains(ns, `\`) {
+			return nil, errorf("replacement directory appears to be Windows path (on a non-windows system)")
+		}
+	}
+	if len(args) == arrow+3 {
+		nv, err = parseVersion(verb, ns, &args[arrow+2], fix)
+		if err != nil {
+			return nil, wrapError(err)
+		}
+		if IsDirectoryPath(ns) {
+			return nil, errorf("replacement module directory path %q cannot have version", ns)
+
+		}
+	}
+	return &Replace{
+		Old:    module.Version{Path: s, Version: v},
+		New:    module.Version{Path: ns, Version: nv},
+		Syntax: line,
+	}, nil
+}
+
 // fixRetract applies fix to each retract directive in f, appending any errors
 // to errs.
 //
@@ -556,6 +577,63 @@
 	}
 }
 
+func (f *WorkFile) add(errs *ErrorList, line *Line, verb string, args []string, fix VersionFixer) {
+	wrapError := func(err error) {
+		*errs = append(*errs, Error{
+			Filename: f.Syntax.Name,
+			Pos:      line.Start,
+			Err:      err,
+		})
+	}
+	errorf := func(format string, args ...interface{}) {
+		wrapError(fmt.Errorf(format, args...))
+	}
+
+	switch verb {
+	default:
+		errorf("unknown directive: %s", verb)
+
+	case "go":
+		if f.Go != nil {
+			errorf("repeated go statement")
+			return
+		}
+		if len(args) != 1 {
+			errorf("go directive expects exactly one argument")
+			return
+		} else if !GoVersionRE.MatchString(args[0]) {
+			errorf("invalid go version '%s': must match format 1.23", args[0])
+			return
+		}
+
+		f.Go = &Go{Syntax: line}
+		f.Go.Version = args[0]
+
+	case "directory":
+		if len(args) != 1 {
+			errorf("usage: %s local/dir", verb)
+			return
+		}
+		s, err := parseString(&args[0])
+		if err != nil {
+			errorf("invalid quoted string: %v", err)
+			return
+		}
+		f.Directory = append(f.Directory, &Directory{
+			Path:   s,
+			Syntax: line,
+		})
+
+	case "replace":
+		replace, wrappederr := parseReplace(f.Syntax.Name, line, verb, args, fix)
+		if wrappederr != nil {
+			*errs = append(*errs, *wrappederr)
+			return
+		}
+		f.Replace = append(f.Replace, replace)
+	}
+}
+
 // IsDirectoryPath reports whether the given path should be interpreted
 // as a directory path. Just like on the go command line, relative paths
 // and rooted paths are directory paths; the rest are module paths.
@@ -1165,6 +1243,10 @@
 }
 
 func (f *File) AddReplace(oldPath, oldVers, newPath, newVers string) error {
+	return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers)
+}
+
+func addReplace(syntax *FileSyntax, replace *[]*Replace, oldPath, oldVers, newPath, newVers string) error {
 	need := true
 	old := module.Version{Path: oldPath, Version: oldVers}
 	new := module.Version{Path: newPath, Version: newVers}
@@ -1178,12 +1260,12 @@
 	}
 
 	var hint *Line
-	for _, r := range f.Replace {
+	for _, r := range *replace {
 		if r.Old.Path == oldPath && (oldVers == "" || r.Old.Version == oldVers) {
 			if need {
 				// Found replacement for old; update to use new.
 				r.New = new
-				f.Syntax.updateLine(r.Syntax, tokens...)
+				syntax.updateLine(r.Syntax, tokens...)
 				need = false
 				continue
 			}
@@ -1196,7 +1278,7 @@
 		}
 	}
 	if need {
-		f.Replace = append(f.Replace, &Replace{Old: old, New: new, Syntax: f.Syntax.addLine(hint, tokens...)})
+		*replace = append(*replace, &Replace{Old: old, New: new, Syntax: syntax.addLine(hint, tokens...)})
 	}
 	return nil
 }
@@ -1282,30 +1364,36 @@
 // retract directives are not de-duplicated since comments are
 // meaningful, and versions may be retracted multiple times.
 func (f *File) removeDups() {
+	removeDups(f.Syntax, &f.Exclude, &f.Replace)
+}
+
+func removeDups(syntax *FileSyntax, exclude *[]*Exclude, replace *[]*Replace) {
 	kill := make(map[*Line]bool)
 
 	// Remove duplicate excludes.
-	haveExclude := make(map[module.Version]bool)
-	for _, x := range f.Exclude {
-		if haveExclude[x.Mod] {
-			kill[x.Syntax] = true
-			continue
+	if exclude != nil {
+		haveExclude := make(map[module.Version]bool)
+		for _, x := range *exclude {
+			if haveExclude[x.Mod] {
+				kill[x.Syntax] = true
+				continue
+			}
+			haveExclude[x.Mod] = true
 		}
-		haveExclude[x.Mod] = true
-	}
-	var excl []*Exclude
-	for _, x := range f.Exclude {
-		if !kill[x.Syntax] {
-			excl = append(excl, x)
+		var excl []*Exclude
+		for _, x := range *exclude {
+			if !kill[x.Syntax] {
+				excl = append(excl, x)
+			}
 		}
+		*exclude = excl
 	}
-	f.Exclude = excl
 
 	// Remove duplicate replacements.
 	// Later replacements take priority over earlier ones.
 	haveReplace := make(map[module.Version]bool)
-	for i := len(f.Replace) - 1; i >= 0; i-- {
-		x := f.Replace[i]
+	for i := len(*replace) - 1; i >= 0; i-- {
+		x := (*replace)[i]
 		if haveReplace[x.Old] {
 			kill[x.Syntax] = true
 			continue
@@ -1313,18 +1401,18 @@
 		haveReplace[x.Old] = true
 	}
 	var repl []*Replace
-	for _, x := range f.Replace {
+	for _, x := range *replace {
 		if !kill[x.Syntax] {
 			repl = append(repl, x)
 		}
 	}
-	f.Replace = repl
+	*replace = repl
 
 	// Duplicate require and retract directives are not removed.
 
 	// Drop killed statements from the syntax tree.
 	var stmts []Expr
-	for _, stmt := range f.Syntax.Stmt {
+	for _, stmt := range syntax.Stmt {
 		switch stmt := stmt.(type) {
 		case *Line:
 			if kill[stmt] {
@@ -1344,7 +1432,7 @@
 		}
 		stmts = append(stmts, stmt)
 	}
-	f.Syntax.Stmt = stmts
+	syntax.Stmt = stmts
 }
 
 // lineLess returns whether li should be sorted before lj. It sorts
diff --git a/src/cmd/vendor/golang.org/x/mod/modfile/work.go b/src/cmd/vendor/golang.org/x/mod/modfile/work.go
new file mode 100644
index 0000000..b1fabff
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/mod/modfile/work.go
@@ -0,0 +1,234 @@
+// 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 modfile
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+)
+
+// A WorkFile is the parsed, interpreted form of a go.work file.
+type WorkFile struct {
+	Go        *Go
+	Directory []*Directory
+	Replace   []*Replace
+
+	Syntax *FileSyntax
+}
+
+// A Directory is a single directory statement.
+type Directory struct {
+	Path       string // Directory path of module.
+	ModulePath string // Module path in the comment.
+	Syntax     *Line
+}
+
+// ParseWork parses and returns a go.work file.
+//
+// file is the name of the file, used in positions and errors.
+//
+// data is the content of the file.
+//
+// fix is an optional function that canonicalizes module versions.
+// If fix is nil, all module versions must be canonical (module.CanonicalVersion
+// must return the same string).
+func ParseWork(file string, data []byte, fix VersionFixer) (*WorkFile, error) {
+	fs, err := parse(file, data)
+	if err != nil {
+		return nil, err
+	}
+	f := &WorkFile{
+		Syntax: fs,
+	}
+	var errs ErrorList
+
+	for _, x := range fs.Stmt {
+		switch x := x.(type) {
+		case *Line:
+			f.add(&errs, x, x.Token[0], x.Token[1:], fix)
+
+		case *LineBlock:
+			if len(x.Token) > 1 {
+				errs = append(errs, Error{
+					Filename: file,
+					Pos:      x.Start,
+					Err:      fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
+				})
+				continue
+			}
+			switch x.Token[0] {
+			default:
+				errs = append(errs, Error{
+					Filename: file,
+					Pos:      x.Start,
+					Err:      fmt.Errorf("unknown block type: %s", strings.Join(x.Token, " ")),
+				})
+				continue
+			case "directory", "replace":
+				for _, l := range x.Line {
+					f.add(&errs, l, x.Token[0], l.Token, fix)
+				}
+			}
+		}
+	}
+
+	if len(errs) > 0 {
+		return nil, errs
+	}
+	return f, nil
+}
+
+// Cleanup cleans up the file f after any edit operations.
+// To avoid quadratic behavior, modifications like DropRequire
+// clear the entry but do not remove it from the slice.
+// Cleanup cleans out all the cleared entries.
+func (f *WorkFile) Cleanup() {
+	w := 0
+	for _, r := range f.Directory {
+		if r.Path != "" {
+			f.Directory[w] = r
+			w++
+		}
+	}
+	f.Directory = f.Directory[:w]
+
+	w = 0
+	for _, r := range f.Replace {
+		if r.Old.Path != "" {
+			f.Replace[w] = r
+			w++
+		}
+	}
+	f.Replace = f.Replace[:w]
+
+	f.Syntax.Cleanup()
+}
+
+func (f *WorkFile) AddGoStmt(version string) error {
+	if !GoVersionRE.MatchString(version) {
+		return fmt.Errorf("invalid language version string %q", version)
+	}
+	if f.Go == nil {
+		stmt := &Line{Token: []string{"go", version}}
+		f.Go = &Go{
+			Version: version,
+			Syntax:  stmt,
+		}
+		// Find the first non-comment-only block that's and add
+		// the go statement before it. That will keep file comments at the top.
+		i := 0
+		for i = 0; i < len(f.Syntax.Stmt); i++ {
+			if _, ok := f.Syntax.Stmt[i].(*CommentBlock); !ok {
+				break
+			}
+		}
+		f.Syntax.Stmt = append(append(f.Syntax.Stmt[:i:i], stmt), f.Syntax.Stmt[i:]...)
+	} else {
+		f.Go.Version = version
+		f.Syntax.updateLine(f.Go.Syntax, "go", version)
+	}
+	return nil
+}
+
+func (f *WorkFile) AddDirectory(diskPath, modulePath string) error {
+	need := true
+	for _, d := range f.Directory {
+		if d.Path == diskPath {
+			if need {
+				d.ModulePath = modulePath
+				f.Syntax.updateLine(d.Syntax, "directory", AutoQuote(diskPath))
+				need = false
+			} else {
+				d.Syntax.markRemoved()
+				*d = Directory{}
+			}
+		}
+	}
+
+	if need {
+		f.AddNewDirectory(diskPath, modulePath)
+	}
+	return nil
+}
+
+func (f *WorkFile) AddNewDirectory(diskPath, modulePath string) {
+	line := f.Syntax.addLine(nil, "directory", AutoQuote(diskPath))
+	f.Directory = append(f.Directory, &Directory{Path: diskPath, ModulePath: modulePath, Syntax: line})
+}
+
+func (f *WorkFile) SetDirectory(dirs []*Directory) {
+	need := make(map[string]string)
+	for _, d := range dirs {
+		need[d.Path] = d.ModulePath
+	}
+
+	for _, d := range f.Directory {
+		if modulePath, ok := need[d.Path]; ok {
+			d.ModulePath = modulePath
+		} else {
+			d.Syntax.markRemoved()
+			*d = Directory{}
+		}
+	}
+
+	// TODO(#45713): Add module path to comment.
+
+	for diskPath, modulePath := range need {
+		f.AddNewDirectory(diskPath, modulePath)
+	}
+	f.SortBlocks()
+}
+
+func (f *WorkFile) DropDirectory(path string) error {
+	for _, d := range f.Directory {
+		if d.Path == path {
+			d.Syntax.markRemoved()
+			*d = Directory{}
+		}
+	}
+	return nil
+}
+
+func (f *WorkFile) AddReplace(oldPath, oldVers, newPath, newVers string) error {
+	return addReplace(f.Syntax, &f.Replace, oldPath, oldVers, newPath, newVers)
+}
+
+func (f *WorkFile) DropReplace(oldPath, oldVers string) error {
+	for _, r := range f.Replace {
+		if r.Old.Path == oldPath && r.Old.Version == oldVers {
+			r.Syntax.markRemoved()
+			*r = Replace{}
+		}
+	}
+	return nil
+}
+
+func (f *WorkFile) SortBlocks() {
+	f.removeDups() // otherwise sorting is unsafe
+
+	for _, stmt := range f.Syntax.Stmt {
+		block, ok := stmt.(*LineBlock)
+		if !ok {
+			continue
+		}
+		sort.SliceStable(block.Line, func(i, j int) bool {
+			return lineLess(block.Line[i], block.Line[j])
+		})
+	}
+}
+
+// removeDups removes duplicate replace directives.
+//
+// Later replace directives take priority.
+//
+// require directives are not de-duplicated. That's left up to higher-level
+// logic (MVS).
+//
+// retract directives are not de-duplicated since comments are
+// meaningful, and versions may be retracted multiple times.
+func (f *WorkFile) removeDups() {
+	removeDups(f.Syntax, nil, &f.Replace)
+}
diff --git a/src/cmd/vendor/modules.txt b/src/cmd/vendor/modules.txt
index 1765e3e..eed06a9 100644
--- a/src/cmd/vendor/modules.txt
+++ b/src/cmd/vendor/modules.txt
@@ -28,7 +28,7 @@
 ## explicit; go 1.17
 golang.org/x/crypto/ed25519
 golang.org/x/crypto/ed25519/internal/edwards25519
-# golang.org/x/mod v0.4.3-0.20210608190319-0f08993efd8a
+# golang.org/x/mod v0.4.3-0.20210723200715-e41a6a4f3b61
 ## explicit; go 1.17
 golang.org/x/mod/internal/lazyregexp
 golang.org/x/mod/modfile