From 13d42f06f9e4874f70baa1a59b1f1e9955fa9df7 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 13 Jan 2026 16:39:27 -0700 Subject: [PATCH 1/5] changing go1.20.14 over to go1.21.13 --- .github/workflows/ci.yaml | 2 +- .github/workflows/measure-size.yml | 2 +- README.md | 12 ++++++------ compiler/version_check.go | 8 ++++---- go.mod | 2 +- tests/gorepo/run.go | 3 +++ 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6921f293c..faca07b09 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,7 +15,7 @@ concurrency: cancel-in-progress: true env: - GO_VERSION: 1.20.14 + GO_VERSION: 1.21.13 NODE_VERSION: 18 GOLANGCI_VERSION: v1.53.3 SOURCE_MAP_SUPPORT: true diff --git a/.github/workflows/measure-size.yml b/.github/workflows/measure-size.yml index 2c1c017a3..6aabb535d 100644 --- a/.github/workflows/measure-size.yml +++ b/.github/workflows/measure-size.yml @@ -3,7 +3,7 @@ name: Measure canonical app size on: ['pull_request'] env: - GO_VERSION: '~1.20.14' + GO_VERSION: '~1.21.13' jobs: measure: diff --git a/README.md b/README.md index 9f32d641e..4680bbc87 100644 --- a/README.md +++ b/README.md @@ -33,21 +33,21 @@ Nearly everything, including Goroutines ([compatibility documentation](https://g ### Installation and Usage -GopherJS [requires Go 1.20 or newer](https://github.com/gopherjs/gopherjs/blob/master/doc/compatibility.md#go-version-compatibility). If you need an older Go +GopherJS [requires Go 1.21 or newer](https://github.com/gopherjs/gopherjs/blob/master/doc/compatibility.md#go-version-compatibility). If you need an older Go version, you can use an [older GopherJS release](https://github.com/gopherjs/gopherjs/releases). Install GopherJS with `go install`: ``` -go install github.com/gopherjs/gopherjs@v1.20.0-beta1 # Or replace 'v1.20.0-beta1' with another version. +go install github.com/gopherjs/gopherjs@v1.21.0-beta1 # Or replace 'v1.21.0-beta1' with another version. ``` -If your local Go distribution as reported by `go version` is newer than Go 1.20, then you need to set the `GOPHERJS_GOROOT` environment variable to a directory that contains a Go 1.20 distribution. For example: +If your local Go distribution as reported by `go version` is newer than Go 1.21, then you need to set the `GOPHERJS_GOROOT` environment variable to a directory that contains a Go 1.21 distribution. For example: ``` -go install golang.org/dl/go1.20.14@latest -go1.20.14 download -export GOPHERJS_GOROOT="$(go1.20.14 env GOROOT)" # Also add this line to your .profile or equivalent. +go install golang.org/dl/go1.21.13@latest +go1.21.13 download +export GOPHERJS_GOROOT="$(go1.21.13 env GOROOT)" # Also add this line to your .profile or equivalent. ``` Now you can use `gopherjs build [package]`, `gopherjs build [files]` or `gopherjs install [package]` which behave similar to the `go` tool. For `main` packages, these commands create a `.js` file and `.js.map` source map in the current directory or in `$GOPATH/bin`. The generated JavaScript file can be used as usual in a website. Use `gopherjs help [command]` to get a list of possible command line flags, e.g. for minification and automatically watching for changes. diff --git a/compiler/version_check.go b/compiler/version_check.go index 4186a0d8b..fa04edd53 100644 --- a/compiler/version_check.go +++ b/compiler/version_check.go @@ -1,4 +1,4 @@ -//go:build go1.20 +//go:build go1.21 package compiler @@ -12,10 +12,10 @@ import ( ) // Version is the GopherJS compiler version string. -const Version = "1.20.0-beta1+go1.20.14" +const Version = "1.21.0-beta1+go1.21.13" // GoVersion is the current Go 1.x version that GopherJS is compatible with. -const GoVersion = 20 +const GoVersion = 21 // CheckGoVersion checks the version of the Go distribution // at goroot, and reports an error if it's not compatible @@ -49,7 +49,7 @@ func goRootVersion(goroot string) (string, error) { if err != nil { return "", fmt.Errorf("`go version` command failed: %w", err) } - // Expected output: go version go1.20.14 linux/amd64 + // Expected output: go version go1.21.13 linux/amd64 parts := strings.Split(string(out), " ") if len(parts) != 4 { return "", fmt.Errorf("unexpected `go version` output %q, expected 4 words", string(out)) diff --git a/go.mod b/go.mod index 9eb38f988..323c5aa61 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/gopherjs/gopherjs -go 1.20 +go 1.21 require ( github.com/evanw/esbuild v0.25.4 diff --git a/tests/gorepo/run.go b/tests/gorepo/run.go index 3f3f218d8..fb3f47663 100644 --- a/tests/gorepo/run.go +++ b/tests/gorepo/run.go @@ -153,6 +153,9 @@ var knownFails = map[string]failReason{ "fixedbugs/issue57823.go": {category: notApplicable, desc: "uses runtime.SetFinalizer() and runtime.GC()."}, "fixedbugs/issue59293.go": {category: usesUnsupportedPackage, desc: "uses unsafe.SliceData() and unsafe.StringData()."}, "fixedbugs/issue43942.go": {category: other, desc: "https://github.com/gopherjs/gopherjs/issues/1126"}, + + // These are new tests in Go 1.21 + // TODO(grantnelson-wf): Fill out } type failCategory uint8 From 9e8050fe29269fb7c0f82d896c0915612cdc033e Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Fri, 20 Feb 2026 10:31:20 -0700 Subject: [PATCH 2/5] Adding new and replace optional directives --- build/build.go | 76 +++++++++++++-- build/build_test.go | 6 +- compiler/astutil/astutil.go | 32 +++++++ .../natives/src/internal/bytealg/bytealg.go | 8 ++ doc/pargma.md | 95 +++++++++++++++++++ 5 files changed, 209 insertions(+), 8 deletions(-) diff --git a/build/build.go b/build/build.go index f218bfb5d..34d20da55 100644 --- a/build/build.go +++ b/build/build.go @@ -124,13 +124,26 @@ func ImportDir(dir string, mode build.ImportMode, installSuffix string, buildTag // overrideInfo is used by parseAndAugment methods to manage // directives and how the overlay and original are merged. type overrideInfo struct { + // replace indicates that the original code is expected to exist. + // If the original code is not present, then error to let us know + // the original code has been removed or renamed. + // This will be set to false once the original code has been found. + replace bool + + // new indicates that the override code is expected to be new. + // If the original code is present, then error to let us know + // that we are overriding something that we did not expect to replace. + // This lets us know of new or renamed code in the original that happens + // to collide with our overrides. + new bool + // keepOriginal indicates that the original code should be kept // but the identifier will be prefixed by `_gopherjs_original_foo`. // If false the original code is removed. keepOriginal bool - // purgeMethods indicates that this info is for a type and - // if a method has this type as a receiver should also be removed. + // purgeMethods indicates that this info is for a type and if a method has + // this type as a receiver then the method should also be removed. // If the method is defined in the overlays and therefore has its // own overrides, this will be ignored. purgeMethods bool @@ -165,8 +178,11 @@ type overrideInfo struct { // to match the overridden function signature. This allows the receiver, // type parameters, parameter, and return values to be modified as needed. // - Otherwise for identifiers that exist in the original and the overrides, -// the original is removed. +// the original is removed. Use `gopherjs:replace` to ensure that the +// original existed so that a replace is made. // - New identifiers that don't exist in original package get added. +// Use `gopherjs:new` to ensure that the identifier is new and there was +// no original code for it. func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *token.FileSet) ([]*ast.File, []incjs.File, error) { jsFiles, overlayFiles := parseOverlayFiles(xctx, pkg, isTest, fileSet) @@ -186,8 +202,13 @@ func parseAndAugment(xctx XContext, pkg *PackageData, isTest bool, fileSet *toke } if len(overrides) > 0 { + found := make(map[string]struct{}, len(overrides)) for _, file := range originalFiles { - augmentOriginalFile(file, overrides) + augmentOriginalFile(file, overrides, found) + } + err := checkOverrides(overrides, found, pkg.ImportPath) + if err != nil { + return nil, nil, err } } @@ -284,12 +305,17 @@ func parserOriginalFiles(pkg *PackageData, fileSet *token.FileSet) ([]*ast.File, func augmentOverlayFile(file *ast.File, overrides map[string]overrideInfo) { anyChange := false for i, decl := range file.Decls { + replaceDecl := astutil.DirectiveReplace(decl) + newDecl := astutil.DirectiveNew(decl) purgeDecl := astutil.Purge(decl) + switch d := decl.(type) { case *ast.FuncDecl: k := astutil.FuncKey(d) oi := overrideInfo{ keepOriginal: astutil.KeepOriginal(d), + replace: replaceDecl, + new: newDecl, } if astutil.OverrideSignature(d) { oi.overrideSignature = d @@ -299,14 +325,21 @@ func augmentOverlayFile(file *ast.File, overrides map[string]overrideInfo) { case *ast.GenDecl: for j, spec := range d.Specs { purgeSpec := purgeDecl || astutil.Purge(spec) + replaceSpec := replaceDecl || astutil.DirectiveReplace(spec) + newSpec := newDecl || astutil.DirectiveNew(spec) switch s := spec.(type) { case *ast.TypeSpec: overrides[s.Name.Name] = overrideInfo{ purgeMethods: purgeSpec, + replace: replaceSpec, + new: newSpec, } case *ast.ValueSpec: for _, name := range s.Names { - overrides[name.Name] = overrideInfo{} + overrides[name.Name] = overrideInfo{ + replace: replaceSpec, + new: newSpec, + } } } if purgeSpec { @@ -346,12 +379,14 @@ func augmentOriginalImports(importPath string, file *ast.File) { // augmentOriginalFile is the part of parseAndAugment that processes an // original file AST to augment the source code using the overrides from // the overlay files. -func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { +func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo, found map[string]struct{}) { anyChange := false for i, decl := range file.Decls { switch d := decl.(type) { case *ast.FuncDecl: - if info, ok := overrides[astutil.FuncKey(d)]; ok { + funcKey := astutil.FuncKey(d) + if info, ok := overrides[funcKey]; ok { + found[funcKey] = struct{}{} anyChange = true removeFunc := true if info.keepOriginal { @@ -373,6 +408,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { } else if recvKey := astutil.FuncReceiverKey(d); len(recvKey) > 0 { // check if the receiver has been purged, if so, remove the method too. if info, ok := overrides[recvKey]; ok && info.purgeMethods { + found[recvKey] = struct{}{} anyChange = true file.Decls[i] = nil } @@ -382,6 +418,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { switch s := spec.(type) { case *ast.TypeSpec: if _, ok := overrides[s.Name.Name]; ok { + found[s.Name.Name] = struct{}{} anyChange = true d.Specs[j] = nil } @@ -395,6 +432,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { // to be run, add the call into the overlay. for k, name := range s.Names { if _, ok := overrides[name.Name]; ok { + found[name.Name] = struct{}{} anyChange = true s.Names[k] = nil s.Values[k] = nil @@ -410,6 +448,7 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { nameRemoved := false for _, name := range s.Names { if _, ok := overrides[name.Name]; ok { + found[name.Name] = struct{}{} nameRemoved = true name.Name = `_` } @@ -438,6 +477,29 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo) { } } +// checkOverrides performs a final check of the overrides to ensure that +// all overrides that were expected to be found were found and all overrides +// that were not expected to be found were not found. +func checkOverrides(overrides map[string]overrideInfo, found map[string]struct{}, pkgPath string) error { + el := errlist.ErrorList{} + for name, info := range overrides { + _, wasFound := found[name] + switch { + case wasFound && info.new: + el = el.Append(fmt.Errorf("gopherjs: original code for %s.%s was found, but override had `new` directive", pkgPath, name)) + case !wasFound && info.replace: + el = el.Append(fmt.Errorf("gopherjs: original code for %s.%s was not found, but override had `replace` directive", pkgPath, name)) + case !wasFound && info.keepOriginal: + el = el.Append(fmt.Errorf("gopherjs: original code for %s.%s was not found, but override had `keep-original` directive", pkgPath, name)) + case !wasFound && info.purgeMethods: + el = el.Append(fmt.Errorf("gopherjs: original code for %s.%s was not found, but override had `purge` directive", pkgPath, name)) + case !wasFound && info.overrideSignature != nil: + el = el.Append(fmt.Errorf("gopherjs: original code for %s.%s was not found, but override had `override-signature` directive", pkgPath, name)) + } + } + return el.ErrOrNil() +} + // isOnlyImports determines if this file is empty except for imports. func isOnlyImports(file *ast.File) bool { for _, decl := range file.Decls { diff --git a/build/build_test.go b/build/build_test.go index a3e4800d9..ae40f5d6c 100644 --- a/build/build_test.go +++ b/build/build_test.go @@ -724,7 +724,11 @@ func TestOriginalAugmentation(t *testing.T) { fileSrc := f.Parse("test.go", pkgName+test.src) augmentOriginalImports(importPath, fileSrc) - augmentOriginalFile(fileSrc, test.info) + found := make(map[string]struct{}) + augmentOriginalFile(fileSrc, test.info, found) + if err := checkOverrides(test.info, found, importPath); err != nil { + t.Errorf("check overrides: %v", err) + } pruneImports(fileSrc) got := srctesting.Format(t, f.FileSet, fileSrc) diff --git a/compiler/astutil/astutil.go b/compiler/astutil/astutil.go index 5f259bfff..ba4b5c940 100644 --- a/compiler/astutil/astutil.go +++ b/compiler/astutil/astutil.go @@ -133,6 +133,28 @@ func FuncReceiverKey(d *ast.FuncDecl) string { } } +// DirectiveReplace returns true if gopherjs:replace directive is present +// on a struct, interface, type, variable, constant, or function. +// +// `//gopherjs:replace` is a GopherJS-specific directive, which can be +// applied in native overlays and will instruct the augmentation logic to +// ensure that the original code is present and has not been removed nor renamed, +// otherwise an error will be raised. +func DirectiveReplace(d ast.Node) bool { + return hasDirective(d, `replace`) +} + +// DirectiveNew returns true if gopherjs:new directive is +// present on a struct, interface, type, variable, constant, or function. +// +// `//gopherjs:new` is a GopherJS-specific directive, which can be +// applied in native overlays and will instruct the augmentation logic to +// ensure that the original code is not present so that this code does not +// override any original code, otherwise an error will be raised. +func DirectiveNew(d ast.Node) bool { + return hasDirective(d, `new`) +} + // KeepOriginal returns true if gopherjs:keep-original directive is present // before a function decl. // @@ -141,6 +163,9 @@ func FuncReceiverKey(d *ast.FuncDecl) string { // logic to expose the original function such that it can be called. For a // function in the original called `foo`, it will be accessible by the name // `_gopherjs_original_foo`. +// +// This will also ensure that the original function exists and hasn't been +// removed or renamed, otherwise an error will be raised. func KeepOriginal(d *ast.FuncDecl) bool { return hasDirective(d, `keep-original`) } @@ -156,6 +181,9 @@ func KeepOriginal(d *ast.FuncDecl) bool { // fully supported). It should be used with caution since it may remove needed // dependencies. If a type is purged, all methods using that type as // a receiver will also be purged. +// +// This will also ensure that the original code exists and hasn't been +// removed or renamed, otherwise an error will be raised. func Purge(d ast.Node) bool { return hasDirective(d, `purge`) } @@ -167,6 +195,7 @@ func Purge(d ast.Node) bool { // be applied in native overlays and will instruct the augmentation logic to // replace the original function signature which has the same FuncKey with the // signature defined in the native overlays. +// // This directive can be used to remove generics from a function signature or // to replace a receiver of a function with another one. The given native // overlay function will be removed, so no method body is needed in the overlay. @@ -174,6 +203,9 @@ func Purge(d ast.Node) bool { // The new signature may not contain types which require a new import since // the imports will not be automatically added when needed, only removed. // Use a type alias in the overlay to deal manage imports. +// +// This will also ensure that the original code exists and hasn't been +// removed or renamed, otherwise an error will be raised. func OverrideSignature(d *ast.FuncDecl) bool { return hasDirective(d, `override-signature`) } diff --git a/compiler/natives/src/internal/bytealg/bytealg.go b/compiler/natives/src/internal/bytealg/bytealg.go index 15a3c2d70..db5bcdb4c 100644 --- a/compiler/natives/src/internal/bytealg/bytealg.go +++ b/compiler/natives/src/internal/bytealg/bytealg.go @@ -2,6 +2,7 @@ package bytealg +//gopherjs:replace func Equal(a, b []byte) bool { if len(a) != len(b) { return false @@ -14,6 +15,7 @@ func Equal(a, b []byte) bool { return true } +//gopherjs:replace func IndexByte(b []byte, c byte) int { for i, x := range b { if x == c { @@ -23,6 +25,7 @@ func IndexByte(b []byte, c byte) int { return -1 } +//gopherjs:replace func IndexByteString(s string, c byte) int { for i := 0; i < len(s); i++ { if s[i] == c { @@ -31,3 +34,8 @@ func IndexByteString(s string, c byte) int { } return -1 } + +//gopherjs:replace +func MakeNoZero(n int) []byte { + return make([]byte, n) +} diff --git a/doc/pargma.md b/doc/pargma.md index 7588d39d5..47bb88bdc 100644 --- a/doc/pargma.md +++ b/doc/pargma.md @@ -9,6 +9,8 @@ GopherJS compiler supports the following directives: - [go:linkname](#golinkname) - [go:embed](#goembed) +- [gopherjs:new](#gopherjsnew) +- [gopherjs:replace](#gopherjsreplace) - [gopherjs:keep-original](#gopherjskeep-original) - [gopherjs:purge](#gopherjspurge) - [gopherjs:override-signature](#gopherjsoverride-signature) @@ -82,6 +84,81 @@ of `embed.FS`, several embedded files will be accessible. See [pkg.go.dev/embed](https://pkg.go.dev/embed#hdr-Directives) for more information. +## `gopherjs:new` + +This directive is custom to GopherJS. This directive can be added +to most declarations and specification in the native file overrides as +part of the build step. This does not work on imports. + +This is an optional directive to help check for issues when upgrading to +newer versions of Go. This will indicate that the code it is on, is new code +being added to the package via the overrides. + +When the overrides are being applied, if an identifier matches then the +code is replaced with the override, otherwise the code is added as new code. +If this directive is added to the override, it will raise an error when some +code by the same identifier exists. This allows the developer to indicate +they intended to add code without it being used as a replacement. + +This is useful when upgrading to new Go versions. The changes to the Go code +could change or add an identifier that matches. Instead of the override +unexpectantly becoming a replacement, this will prevent that and let the +developers performing the upgrade know that they need to rename the override +to keep is as new code and not replace the Go code. + +In the following example, suppose we added a function `clearData` for +go1.X. When upgrating to go1.Y, the developers of Go add thier own function +called `clearData`. Instead of quietly replacing the new Go code, this +directive would error to indicate we need to rename our new code to something +like `jsClearData` to prevent our code from replacing the Go code unintentinally. + +```Go +//gopherjs:new +func clearData(obj *js.Object) { ... } + +//gopherjs:new +var listOfData = []string { ... } +``` + +## `gopherjs:replace` + +This directive is custom to GopherJS. This directive can be added +to most declarations and specification in the native file overrides as +part of the build step. This does not work on imports. + +This is an optional directive to help check for issues when upgrading to +newer versions of Go. This will indicate that the code it is on, is replacing +code in a package. + +When the overrides are being applied, if an identifier matches then the +code is replaced with the override, otherwise the code is added as new code. +If this directive is added to the override, it will raise an error when +some code by the same identifier does not exist. This allows the developer +to indicate they intende to replcae native code with this override and +prevent it from being added as new code. + +This is useful when upgrading to new Go versions. The changes to the Go code +could change or remove an identifier that matches. Instead of the override +unexpectantly becoming new code, this will prevent that and let the developers +performing the upgrade know that they need to rename or remove the override +to keep the override replacing the correct Go code. + +In the following example, suppose we were replacing a function `clearData` for +go1.X. When upgrading to go1.Y, the developers of Go renamed thier own +`clearData` function to `clearTableData`. Instead of quietly adding `clearData` +as a new function (which will likely just be removed with dead code elimination) +and not providing the intended override of the `clearTableData` function, +this directive would error to indicate we need to rename our override to match +the changes in Go. + +```Go +//gopherjs:replace +func clearData(obj *js.Object) { ... } + +//gopherjs:replace +var listOfData = []string { ... } +``` + ## `gopherjs:keep-original` This directive is custom to GopherJS. This directive can be added to a @@ -101,6 +178,12 @@ func foo(a, b int) int { } ``` +This will work like a `gopherjs:replace` as well, where if the native code +does not have this identifier, an error will occur. That helps a developer +know why the `_gopherjs_original_` code would be missing during an upgrade +to a new version of Go where the Go developers may have renamed or removed +the original code. + ## `gopherjs:purge` This directive is custom to GopherJS. This directive can be added @@ -136,6 +219,13 @@ type interfaceType any func doThing[T ~string](value T) ``` +This will work like a `gopherjs:replace` as well, where if the native code +does not have this identifier, an error will occur. This helps developers +during an upgrade of Go versions that the Go developers may have renamed or +removed the original code being purged. If the code has been removed +we can remove unused override code. If the code was renamed, we can move the +purge to the new identifier. + ## `gopherjs:override-signature` This directive is custom to GopherJS. This directive can be added to a @@ -194,3 +284,8 @@ func (f *Foo) Bar(a int, b jsTypeA) (jsTypeA, error) { //... } ``` + +This will work like a `gopherjs:replace` as well, where if the native code +does not have this identifier, an error will occur. This helps developers +during an upgrade of Go versions that the Go developers may have renamed or +removed the original code being overridden. From 87dfbf4b0c1141574b6258efcedd78a597c66354 Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 24 Feb 2026 13:29:18 -0700 Subject: [PATCH 3/5] Updating some linking and misc --- .../natives/src/internal/godebug/godebug.go | 7 +++ compiler/natives/src/runtime/runtime.go | 7 +++ compiler/natives/src/sync/sync.go | 10 ++++ .../natives/src/syscall/syscall_js_wasm.go | 9 ++++ compiler/utils.go | 46 +------------------ 5 files changed, 35 insertions(+), 44 deletions(-) diff --git a/compiler/natives/src/internal/godebug/godebug.go b/compiler/natives/src/internal/godebug/godebug.go index 3d7d719eb..25cc092fa 100644 --- a/compiler/natives/src/internal/godebug/godebug.go +++ b/compiler/natives/src/internal/godebug/godebug.go @@ -6,3 +6,10 @@ import _ "unsafe" // go:linkname //go:linkname setUpdate runtime.godebug_setUpdate func setUpdate(update func(def, env string)) + +// GOPHERJS: Changing from a linked function to a no-op since this is to give +// runtime the ability to do `newNonDefaultInc(name)` instead of +// `godebug.New(name).IncNonDefault` but GopherJS's runtime doesn't need that. +// +//gopherjs:replace +func setNewIncNonDefault(newIncNonDefault func(string) func()) {} diff --git a/compiler/natives/src/runtime/runtime.go b/compiler/natives/src/runtime/runtime.go index 25b15c221..cbd1a2dad 100644 --- a/compiler/natives/src/runtime/runtime.go +++ b/compiler/natives/src/runtime/runtime.go @@ -501,6 +501,13 @@ func godebug_setUpdate(update func(def, env string)) { godebug_notify(godebugEnvKey, godebugEnv) } +// godebug_setNewIncNonDefault implements the setNewIncNonDefault in +// src/internal/godebug/godebug.go. +// GOPHERJS: The GopherJS runtime doesn't need this function so we can remove it. +// +//gopherjs:puge +func godebug_setNewIncNonDefault(newIncNonDefault func(string) func()) + func getEnvString(key string) string { process := js.Global.Get(`process`) if process == js.Undefined { diff --git a/compiler/natives/src/sync/sync.go b/compiler/natives/src/sync/sync.go index 1aa1c7adb..6605b513d 100644 --- a/compiler/natives/src/sync/sync.go +++ b/compiler/natives/src/sync/sync.go @@ -87,3 +87,13 @@ func runtime_nanotime() int64 func throw(s string) { js.Global.Call("$throwRuntimeError", s) } + +// GOPHERJS: This is identical to the original but without the go:linkname +// that can not be handled right now, "can not insert local implementation..." +// TODO(grantnelson-wf): Remove once linking works both directions. +// +//gopherjs:replace +func syscall_hasWaitingReaders(rw *RWMutex) bool { + r := rw.readerCount.Load() + return r < 0 && r+rwmutexMaxReaders > 0 +} diff --git a/compiler/natives/src/syscall/syscall_js_wasm.go b/compiler/natives/src/syscall/syscall_js_wasm.go index 346da22d4..4281421c4 100644 --- a/compiler/natives/src/syscall/syscall_js_wasm.go +++ b/compiler/natives/src/syscall/syscall_js_wasm.go @@ -1,6 +1,7 @@ package syscall import ( + "sync" "syscall/js" _ "unsafe" // go:linkname ) @@ -52,6 +53,14 @@ func unsetenv_c(k string) { //go:linkname godebug_notify runtime.godebug_notify func godebug_notify(key, value string) +// GOPHERJS: This was replaced so that we could add the go:linkname since +// sync.syscall_hasWaitingReaders had a link we can't handle right now, +// "can not insert local implementation..." +// TODO(grantnelson-wf): Remove once linking works both directions. +// +//go:linkname hasWaitingReaders sync.syscall_hasWaitingReaders +func hasWaitingReaders(rw *sync.RWMutex) bool + func setStat(st *Stat_t, jsSt js.Value) { // This method is an almost-exact copy of upstream, except for 4 places where // time stamps are obtained as floats in lieu of int64. Upstream wasm emulates diff --git a/compiler/utils.go b/compiler/utils.go index 20e279730..93afefbf1 100644 --- a/compiler/utils.go +++ b/compiler/utils.go @@ -9,10 +9,10 @@ import ( "go/token" "go/types" "net/url" + "reflect" "regexp" "runtime/debug" "sort" - "strconv" "strings" "text/template" "unicode" @@ -821,49 +821,7 @@ func encodeString(s string) string { } func getJsTag(tag string) string { - for tag != "" { - // skip leading space - i := 0 - for i < len(tag) && tag[i] == ' ' { - i++ - } - tag = tag[i:] - if tag == "" { - break - } - - // scan to colon. - // a space or a quote is a syntax error - i = 0 - for i < len(tag) && tag[i] != ' ' && tag[i] != ':' && tag[i] != '"' { - i++ - } - if i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { - break - } - name := string(tag[:i]) - tag = tag[i+1:] - - // scan quoted string to find value - i = 1 - for i < len(tag) && tag[i] != '"' { - if tag[i] == '\\' { - i++ - } - i++ - } - if i >= len(tag) { - break - } - qvalue := string(tag[:i+1]) - tag = tag[i+1:] - - if name == "js" { - value, _ := strconv.Unquote(qvalue) - return value - } - } - return "" + return reflect.StructTag(tag).Get(`js`) } func needsSpace(c byte) bool { From 2e6f05dbc07724c390643d197496e010abb1e1dc Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 24 Feb 2026 13:41:06 -0700 Subject: [PATCH 4/5] Adding requested comment update --- build/build.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/build/build.go b/build/build.go index 34d20da55..2d5ebf61f 100644 --- a/build/build.go +++ b/build/build.go @@ -379,6 +379,11 @@ func augmentOriginalImports(importPath string, file *ast.File) { // augmentOriginalFile is the part of parseAndAugment that processes an // original file AST to augment the source code using the overrides from // the overlay files. +// +// The overrides and found maps key with an identifier that uniquely identifies +// the top-level object being augmented. +// The overrides map should be populated with the overrides to apply. +// Found will be populated with the objects that had an override applied. func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo, found map[string]struct{}) { anyChange := false for i, decl := range file.Decls { @@ -480,6 +485,11 @@ func augmentOriginalFile(file *ast.File, overrides map[string]overrideInfo, foun // checkOverrides performs a final check of the overrides to ensure that // all overrides that were expected to be found were found and all overrides // that were not expected to be found were not found. +// +// The overrides and found maps key with an identifier +// that uniquely identifies the top-level object being augmented. +// Found is populated with the objects that had an override applied +// so the found keys should be a subset of the keys in the overrides map. func checkOverrides(overrides map[string]overrideInfo, found map[string]struct{}, pkgPath string) error { el := errlist.ErrorList{} for name, info := range overrides { From 435797513af442c7b8ce35937dd117007c1901df Mon Sep 17 00:00:00 2001 From: Grant Nelson Date: Tue, 24 Feb 2026 14:47:55 -0700 Subject: [PATCH 5/5] Updated reflect, reflectlite, and abi --- compiler/expressions.go | 44 +- compiler/natives/src/internal/abi/abi_test.go | 15 + compiler/natives/src/internal/abi/type.go | 455 +++++++ compiler/natives/src/internal/abi/utils.go | 37 + .../src/internal/reflectlite/export_test.go | 148 ++- .../src/internal/reflectlite/reflectlite.go | 917 +-------------- .../src/internal/reflectlite/swapper.go | 1 + .../natives/src/internal/reflectlite/type.go | 106 +- .../natives/src/internal/reflectlite/utils.go | 96 -- .../natives/src/internal/reflectlite/value.go | 497 +------- compiler/natives/src/reflect/export_test.go | 15 + compiler/natives/src/reflect/reflect.go | 1048 ++++++++--------- compiler/natives/src/reflect/swapper.go | 1 + 13 files changed, 1296 insertions(+), 2084 deletions(-) create mode 100644 compiler/natives/src/internal/abi/abi_test.go create mode 100644 compiler/natives/src/internal/abi/type.go create mode 100644 compiler/natives/src/internal/abi/utils.go delete mode 100644 compiler/natives/src/internal/reflectlite/utils.go create mode 100644 compiler/natives/src/reflect/export_test.go diff --git a/compiler/expressions.go b/compiler/expressions.go index 3a28661b2..0908ed1cc 100644 --- a/compiler/expressions.go +++ b/compiler/expressions.go @@ -1104,22 +1104,54 @@ func (fc *funcContext) translateExprSlice(exprs []ast.Expr, desiredType types.Ty return parts } +// packageAllowsKindTypeConversion determines if the current package should +// be checked for a special type of casts, `kindType` or `kindTypeExt` conversions. +func (fc *funcContext) packageAllowsKindTypeConversion() bool { + switch fc.pkgCtx.Pkg.Path() { + case `internal/abi`, `internal/reflectlite`, `reflect`: + return true + } + return false +} + func (fc *funcContext) translateConversion(expr ast.Expr, desiredType types.Type) *expression { exprType := fc.typeOf(expr) if types.Identical(exprType, desiredType) { return fc.translateExpr(expr) } - if fc.pkgCtx.Pkg.Path() == "reflect" || fc.pkgCtx.Pkg.Path() == "internal/reflectlite" { + // For some specific packages, e.g. reflect, the Go code performs casts between different sized memory footprints + // and leverages the fact that the pointer to the first field is the same as the pointer to the full struct in Go. + // These conversions are normally not allowed by GopherJS. However, in those specific packages, the original code + // does this kind of cast so often, that to avoid them would cause massive amounts of native overrides. + // To simplify the native overrides we will allow casts between specific types for specific packages by looking up + // the `kindType` that is assigned when creating them. + // + // Given the structure `type K struct{T; additional fields}` the untyped pointer to `K` is also the untyped pointer + // to the first field, i.e. `T`. An example of this is `abi.MapType` with `abi.Type` as its first field. + // These packages will hold onto `t *T` then cast to the kind type with `k = (*K)unsafe.Pointer(t)`. + // Normally this isn't allowed in JS because `K` is larger with additional fields, but when we created `t` in the + // native overrides, we assign `k` as the `t.kindType` then we translate those specific casts to get that `kindType`, + // thus greatly reducing the amount of overrides we have to add to those packages. + if fc.packageAllowsKindTypeConversion() { if call, isCall := expr.(*ast.CallExpr); isCall && types.Identical(fc.typeOf(call.Fun), types.Typ[types.UnsafePointer]) { if ptr, isPtr := desiredType.(*types.Pointer); isPtr { if named, isNamed := ptr.Elem().(*types.Named); isNamed { - switch named.Obj().Name() { - case "arrayType", "chanType", "funcType", "interfaceType", "mapType", "ptrType", "sliceType", "structType": - return fc.formatExpr("%e.kindType", call.Args[0]) // unsafe conversion - default: - return fc.translateExpr(expr) + switch named.Obj().Pkg().Path() { + case `internal/abi`: + switch named.Obj().Name() { + case `ArrayType`, `ChanType`, `FuncType`, `InterfaceType`, `MapType`, `PtrType`, `SliceType`, `StructType`: + return fc.formatExpr("%e.kindType", call.Args[0]) // unsafe conversion + } + case `reflect`: + switch named.Obj().Name() { + // The following are extensions of the ABI equivalent type to add more methods. + // e.g. `type structType struct { abi.StructType }`. + case `interfaceType`, `mapType`, `ptrType`, `sliceType`, `structType`: + return fc.formatExpr("toKindTypeExt(%e)", call.Args[0]) // unsafe conversion + } } + return fc.translateExpr(expr) } } } diff --git a/compiler/natives/src/internal/abi/abi_test.go b/compiler/natives/src/internal/abi/abi_test.go new file mode 100644 index 000000000..892d6cc8d --- /dev/null +++ b/compiler/natives/src/internal/abi/abi_test.go @@ -0,0 +1,15 @@ +//go:build js + +package abi_test + +import "testing" + +//gopherjs:replace +func TestFuncPC(t *testing.T) { + t.Skip(`test involes checking the PC (program counter)`) +} + +//gopherjs:replace +func TestFuncPCCompileError(t *testing.T) { + t.Skip(`test involes checking the PC (program counter)`) +} diff --git a/compiler/natives/src/internal/abi/type.go b/compiler/natives/src/internal/abi/type.go new file mode 100644 index 000000000..2c389f106 --- /dev/null +++ b/compiler/natives/src/internal/abi/type.go @@ -0,0 +1,455 @@ +//go:build js + +package abi + +import ( + "unsafe" + + "github.com/gopherjs/gopherjs/js" +) + +//gopherjs:new +const ( + idJsType = `jsType` + idAbiType = `abiType` + idKindType = `kindType` + idUncommonType = `uncommonType` +) + +//gopherjs:new +func ReflectType(typ *js.Object) *Type { + // If the object already had the reflect type determined, return it. + if jrt := typ.Get(idAbiType); jrt != js.Undefined { + return (*Type)(unsafe.Pointer(jrt.Unsafe())) + } + + // Create new ABI type. + abiTyp := &Type{ + Size_: uintptr(typ.Get("size").Int()), + Kind_: uint8(typ.Get("kind").Int()), + Str: ResolveReflectName(NewName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), + } + js.InternalObject(abiTyp).Set(idJsType, typ) + typ.Set(idAbiType, js.InternalObject(abiTyp)) + + // Add the UncommonType to ABI type if the type has methods. + methodSet := js.Global.Call("$methodSet", typ) + if methodSet.Length() != 0 || typ.Get("named").Bool() { + abiTyp.TFlag |= TFlagUncommon + if typ.Get("named").Bool() { + abiTyp.TFlag |= TFlagNamed + } + var reflectMethods []Method + for i := 0; i < methodSet.Length(); i++ { // Exported methods first. + m := methodSet.Index(i) + exported := internalStr(m.Get("pkg")) == "" + if !exported { + continue + } + reflectMethods = append(reflectMethods, Method{ + Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", exported, false)), + Mtyp: ResolveReflectType(ReflectType(m.Get("typ"))), + }) + } + xcount := uint16(len(reflectMethods)) + for i := 0; i < methodSet.Length(); i++ { // Unexported methods second. + m := methodSet.Index(i) + exported := internalStr(m.Get("pkg")) == "" + if exported { + continue + } + reflectMethods = append(reflectMethods, Method{ + Name: ResolveReflectName(NewName(internalStr(m.Get("name")), "", exported, false)), + Mtyp: ResolveReflectType(ReflectType(m.Get("typ"))), + }) + } + ut := &UncommonType{ + PkgPath: ResolveReflectName(NewName(internalStr(typ.Get("pkg")), "", false, false)), + Mcount: uint16(methodSet.Length()), + Xcount: xcount, + Methods_: reflectMethods, + } + js.InternalObject(abiTyp).Set(idUncommonType, js.InternalObject(ut)) + } + + // Create the kind type for the ABI type if the kind has additional information. + // Also set `PtrBytes` to a non-zero number for types with Pointer() values. + switch abiTyp.Kind() { + case Array: + setKindType(abiTyp, &ArrayType{ + Elem: ReflectType(typ.Get("elem")), + Len: uintptr(typ.Get("len").Int()), + }) + case Chan: + dir := BothDir + if typ.Get("sendOnly").Bool() { + dir = SendDir + } + if typ.Get("recvOnly").Bool() { + dir = RecvDir + } + abiTyp.PtrBytes = 4 + setKindType(abiTyp, &ChanType{ + Elem: ReflectType(typ.Get("elem")), + Dir: dir, + }) + case Func: + params := typ.Get("params") + in := make([]*Type, params.Length()) + for i := range in { + in[i] = ReflectType(params.Index(i)) + } + results := typ.Get("results") + out := make([]*Type, results.Length()) + for i := range out { + out[i] = ReflectType(results.Index(i)) + } + outCount := uint16(results.Length()) + if typ.Get("variadic").Bool() { + outCount |= 1 << 15 + } + abiTyp.PtrBytes = 4 + setKindType(abiTyp, &FuncType{ + InCount: uint16(params.Length()), + OutCount: outCount, + In_: in, + Out_: out, + }) + case Interface: + methods := typ.Get("methods") + imethods := make([]Imethod, methods.Length()) + for i := range imethods { + m := methods.Index(i) + mPkg := internalStr(m.Get("pkg")) + exported := mPkg == "" + name := NewName(internalStr(m.Get("name")), "", exported, false) + name.SetPkgPath(mPkg) + imethods[i] = Imethod{ + Name: ResolveReflectName(name), + Typ: ResolveReflectType(ReflectType(m.Get("typ"))), + } + } + setKindType(abiTyp, &InterfaceType{ + PkgPath: NewName(internalStr(typ.Get("pkg")), "", false, false), + Methods: imethods, + }) + case Map: + abiTyp.PtrBytes = 4 + setKindType(abiTyp, &MapType{ + Key: ReflectType(typ.Get("key")), + Elem: ReflectType(typ.Get("elem")), + }) + case Pointer: + abiTyp.PtrBytes = 4 + setKindType(abiTyp, &PtrType{ + Elem: ReflectType(typ.Get("elem")), + }) + case Slice: + abiTyp.PtrBytes = 4 + setKindType(abiTyp, &SliceType{ + Elem: ReflectType(typ.Get("elem")), + }) + case Struct: + fields := typ.Get("fields") + reflectFields := make([]StructField, fields.Length()) + for i := range reflectFields { + f := fields.Index(i) + reflectFields[i] = StructField{ + Name: NewName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), + Typ: ReflectType(f.Get("typ")), + Offset: uintptr(i), + } + } + setKindType(abiTyp, &StructType{ + PkgPath: NewName(internalStr(typ.Get("pkgPath")), "", false, false), + Fields: reflectFields, + }) + case UnsafePointer: + abiTyp.PtrBytes = 4 + } + + return abiTyp +} + +//gopherjs:new +func setKindType(abiTyp *Type, kindType any) { + // Add the kindType to the abiTyp object so that when a cast is performed + // from the abiTyp into the kind type, a custom cast in `translateConversion` + // in compiler/expressions.go will use this kindType to cast. + // This makes it so we don't have to override all of the kind type casts, + // e.g. `tt := (*chanType)(unsafe.Pointer(typ))`, and the compiler + // will replace that automatically with `typ.kindType`. + js.InternalObject(abiTyp).Set(idKindType, js.InternalObject(kindType)) + + // Assign Type to the abiType. The abiType is a `*Type` and the Type + // field on the kind types are `Type`. However, this is valid because of how + // pointers and references work in JS. We set this so that the Type + // isn't a copy but the actual abiType object. + js.InternalObject(kindType).Set(`Type`, js.InternalObject(abiTyp)) +} + +//gopherjs:replace +type UncommonType struct { + PkgPath NameOff // import path + Mcount uint16 // method count + Xcount uint16 // exported method count + + // GOPHERJS: Added access to methods + Methods_ []Method +} + +//gopherjs:replace +func (t *UncommonType) Methods() []Method { + return t.Methods_ +} + +//gopherjs:replace +func (t *UncommonType) ExportedMethods() []Method { + return t.Methods_[:t.Xcount:t.Xcount] +} + +//gopherjs:replace +func (t *Type) Uncommon() *UncommonType { + obj := js.InternalObject(t).Get(idUncommonType) + if obj == js.Undefined { + return nil + } + return (*UncommonType)(unsafe.Pointer(obj.Unsafe())) +} + +//gopherjs:new +func (typ *Type) JsType() *js.Object { + return js.InternalObject(typ).Get(idJsType) +} + +//gopherjs:new +func (typ *Type) PtrTo() *Type { + return ReflectType(js.Global.Call("$ptrType", typ.JsType())) +} + +//gopherjs:new +func (typ *Type) JsPtrTo() *js.Object { + return typ.PtrTo().JsType() +} + +//gopherjs:new Shared by reflect and reflectlite rtypes +func (t *Type) String() string { + s := t.NameOff(t.Str).Name() + if t.TFlag&TFlagExtraStar != 0 { + return s[1:] + } + return s +} + +//gopherjs:purge Uncommon types are stored differently. +type structTypeUncommon struct{} + +//gopherjs:replace +type FuncType struct { + Type + InCount uint16 + OutCount uint16 + + // GOPHERJS: Add references to in and out args + In_ []*Type + Out_ []*Type +} + +//gopherjs:replace +func (t *FuncType) InSlice() []*Type { + return t.In_ +} + +//gopherjs:replace +func (t *FuncType) OutSlice() []*Type { + return t.Out_ +} + +//gopherjs:replace +type Name struct { + name string + tag string + exported bool + embedded bool + pkgPath string +} + +//gopherjs:replace +func (n Name) IsExported() bool { return n.exported } + +//gopherjs:replace +func (n Name) HasTag() bool { return len(n.tag) > 0 } + +//gopherjs:replace +func (n Name) IsEmbedded() bool { return n.embedded } + +//gopherjs:replace +func (n Name) IsBlank() bool { return n.Name() == `_` } + +//gopherjs:replace +func (n Name) Name() string { return n.name } + +//gopherjs:replace +func (n Name) Tag() string { return n.tag } + +//gopherjs:purge Used for byte encoding of name, not used in JS +func writeVarint(buf []byte, n int) int + +//gopherjs:purge Used for byte encoding of name, not used in JS +func (n Name) DataChecked(off int, whySafe string) *byte + +//gopherjs:purge Used for byte encoding of name, not used in JS +func (n Name) Data(off int) *byte + +//gopherjs:purge Used for byte encoding of name, not used in JS +func (n Name) ReadVarint(off int) (int, int) + +//gopherjs:new +func (n Name) PkgPath() string { return n.pkgPath } + +//gopherjs:new +func (n *Name) SetPkgPath(pkgpath string) { n.pkgPath = pkgpath } + +//gopherjs:replace +func NewName(n, tag string, exported, embedded bool) Name { + return Name{ + name: n, + tag: tag, + exported: exported, + embedded: embedded, + } +} + +// GOPHERJS: Instead of using this as an offset from a pointer to look up a name, +// just store the name as a pointer. +// +//gopherjs:replace +type NameOff *Name + +// GOPHERJS: Added to mirror the rtype's nameOff method to keep how +// the nameOff is created and read in one spot of the code. +// +//gopherjs:new +func (typ *Type) NameOff(off NameOff) Name { + return *off +} + +// GOPHERJS: Added to mirror the resolveReflectName method in reflect +// +//gopherjs:new +func ResolveReflectName(n Name) NameOff { + return &n +} + +// GOPHERJS: Instead of using this as an offset from a pointer to look up a type, +// just store the type as a pointer. +// +//gopherjs:replace +type TypeOff *Type + +// GOPHERJS: Added to mirror the rtype's typeOff method to keep how +// the typeOff is created and read in one spot of the code. +// +//gopherjs:new +func (typ *Type) TypeOff(off TypeOff) *Type { + return off +} + +// GOPHERJS: Added to mirror the resolveReflectType method in reflect +// +//gopherjs:new +func ResolveReflectType(t *Type) TypeOff { + return t +} + +// GOPHERJS: Instead of using this as an offset from a pointer to look up a pointer, +// just store the paointer itself. +// +//gopherjs:replace +type TextOff unsafe.Pointer + +// GOPHERJS: Added to mirror the rtype's textOff method to keep how +// the textOff is created and read in one spot of the code. +// +//gopherjs:new +func (typ *Type) TextOff(off TextOff) unsafe.Pointer { + return unsafe.Pointer(off) +} + +// GOPHERJS: Added to mirror the resolveReflectText method in reflect +// +//gopherjs:new +func ResolveReflectText(ptr unsafe.Pointer) TextOff { + return TextOff(ptr) +} + +//gopherjs:new +func internalStr(strObj *js.Object) string { + var c struct{ str string } + js.InternalObject(c).Set("str", strObj) // get string without internalizing + return c.str +} + +//gopherjs:new +func (typ *Type) IsWrapped() bool { + return typ.JsType().Get("wrapped").Bool() +} + +//gopherjs:new +var JsObjectPtr = ReflectType(js.Global.Get("$jsObjectPtr")) + +//gopherjs:new +func WrapJsObject(typ *Type, val *js.Object) *js.Object { + if typ == JsObjectPtr { + return JsObjectPtr.JsType().New(val) + } + return val +} + +//gopherjs:new +func UnwrapJsObject(typ *Type, val *js.Object) *js.Object { + if typ == JsObjectPtr { + return val.Get("object") + } + return val +} + +//gopherjs:new +func CopyStruct(dst, src *js.Object, typ *Type) { + fields := typ.JsType().Get("fields") + for i := 0; i < fields.Length(); i++ { + prop := fields.Index(i).Get("prop").String() + dst.Set(prop, src.Get(prop)) + } +} + +//gopherjs:new +func (t *Type) Comparable() bool { + switch t.Kind() { + case Func, Slice, Map: + return false + case Array: + return t.Elem().Comparable() + case Struct: + st := t.StructType() + for i := 0; i < len(st.Fields); i++ { + ft := st.Fields[i] + if !ft.Typ.Comparable() { + return false + } + } + } + return true +} + +//gopherjs:purge Used for pointer arthmatic +func addChecked(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer + +//gopherjs:purge Uses unsafeSliceFor +func (t *Type) GcSlice(begin, end uintptr) []byte + +//gopherjs:purge Uses unsafe.String or stringHeader +func unsafeStringFor(b *byte, l int) string + +//gopherjs:purge Uses unsafe.Slice or sliceHeader +func unsafeSliceFor(b *byte, l int) []byte diff --git a/compiler/natives/src/internal/abi/utils.go b/compiler/natives/src/internal/abi/utils.go new file mode 100644 index 000000000..0cf387bc8 --- /dev/null +++ b/compiler/natives/src/internal/abi/utils.go @@ -0,0 +1,37 @@ +//go:build js + +package abi + +import ( + "unsafe" + + "github.com/gopherjs/gopherjs/js" +) + +// GOPHERJS: These utils are being added because they are common between +// reflect and reflectlite. The [Go proverb](https://go-proverbs.github.io/), +// "A little copying is better than a little dependency," isn't applicable +// when both reflect and reflectlite already depend on ABI. We can reduce +// our native overrides in both locations by putting common code here. + +//gopherjs:new +func UnsafeNew(typ *Type) unsafe.Pointer { + switch typ.Kind() { + case Struct: + return unsafe.Pointer(typ.JsType().Get("ptr").New().Unsafe()) + case Array: + return unsafe.Pointer(typ.JsType().Call("zero").Unsafe()) + default: + return unsafe.Pointer(js.Global.Call("$newDataPointer", typ.JsType().Call("zero"), typ.JsPtrTo()).Unsafe()) + } +} + +//gopherjs:new +func IfaceE2I(t *Type, src any, dst unsafe.Pointer) { + js.InternalObject(dst).Call("$set", js.InternalObject(src)) +} + +//gopherjs:new +func TypedMemMove(t *Type, dst, src unsafe.Pointer) { + js.InternalObject(dst).Call("$set", js.InternalObject(src).Call("$get")) +} diff --git a/compiler/natives/src/internal/reflectlite/export_test.go b/compiler/natives/src/internal/reflectlite/export_test.go index c80ba65a8..8270e64b3 100644 --- a/compiler/natives/src/internal/reflectlite/export_test.go +++ b/compiler/natives/src/internal/reflectlite/export_test.go @@ -2,10 +2,18 @@ package reflectlite -import "unsafe" +import ( + "unsafe" + + "internal/abi" + + "github.com/gopherjs/gopherjs/js" +) // Field returns the i'th field of the struct v. // It panics if v's Kind is not Struct or i is out of range. +// +//gopherjs:replace func Field(v Value, i int) Value { if v.kind() != Struct { panic(&ValueError{"reflect.Value.Field", v.kind()}) @@ -13,20 +21,134 @@ func Field(v Value, i int) Value { return v.Field(i) } -func TField(typ Type, i int) Type { - t := typ.(*rtype) - if t.Kind() != Struct { - panic("reflect: Field of non-struct type") +//gopherjs:new +func (v Value) Field(i int) Value { + tt := v.typ.StructType() + if tt == nil { + panic(&ValueError{"reflect.Value.Field", v.kind()}) + } + + if uint(i) >= uint(len(tt.Fields)) { + panic("reflect: Field index out of range") + } + + prop := jsType(v.typ).Get("fields").Index(i).Get("prop").String() + field := &tt.Fields[i] + typ := field.Typ + + fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) + if !field.Name.IsExported() { + if field.Embedded() { + fl |= flagEmbedRO + } else { + fl |= flagStickyRO + } + } + + if tag := tt.Fields[i].Name.Tag(); tag != "" && i != 0 { + if jsTag := structTagGet(tag, `js`); jsTag != "" { + for { + v = v.Field(0) + if v.typ == abi.JsObjectPtr { + o := v.object().Get("object") + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), jsType(typ)) }), + js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, jsType(typ))) }), + ).Unsafe()), fl} + } + if v.typ.Kind() == abi.Pointer { + v = v.Elem() + } + } + } + } + + s := js.InternalObject(v.ptr) + if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, s.Get(prop)) }), + js.InternalObject(func(x *js.Object) { s.Set(prop, abi.UnwrapJsObject(typ, x)) }), + ).Unsafe()), fl} } - tt := (*structType)(unsafe.Pointer(t)) - return StructFieldType(tt, i) + return makeValue(typ, abi.WrapJsObject(typ, s.Get(prop)), fl) } -// Field returns the i'th struct field. -func StructFieldType(t *structType, i int) Type { - if i < 0 || i >= len(t.fields) { - panic("reflect: Field index out of bounds") +// This is very similar to the `reflect.StructTag` methods `Get` and `Lookup`. +// +//gopherjs:new +func structTagGet(tag, key string) string { + for tag != "" { + // Skip leading space. + i := 0 + for i < len(tag) && tag[i] == ' ' { + i++ + } + tag = tag[i:] + if tag == "" { + break + } + + // Scan to colon. A space, a quote or a control character is a syntax error. + // Strictly speaking, control chars include the range [0x7f, 0x9f], not just + // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters + // as it is simpler to inspect the tag's bytes than the tag's runes. + i = 0 + for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { + i++ + } + if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { + break + } + name := string(tag[:i]) + tag = tag[i+1:] + + // Scan quoted string to find value. + i = 1 + for i < len(tag) && tag[i] != '"' { + if tag[i] == '\\' { + i++ + } + i++ + } + if i >= len(tag) { + break + } + qvalue := string(tag[:i+1]) + tag = tag[i+1:] + + if key == name { + value, hadSyntaxErr := unquote(qvalue) + if hadSyntaxErr { + break + } + return value + } } - p := &t.fields[i] - return toType(p.typ) + return "" } + +//gopherjs:new Added to avoid a dependency on strconv.Unquote +func unquote(s string) (string, bool) { + if len(s) < 2 { + return s, false + } + if s[0] == '\'' || s[0] == '"' { + if s[len(s)-1] == s[0] { + return s[1 : len(s)-1], false + } + return "", true + } + return s, false +} + +//gopherjs:purge Used in FirstMethodNameBytes +type EmbedWithUnexpMeth struct{} + +//gopherjs:purge Used in FirstMethodNameBytes +type pinUnexpMeth interface{} + +//gopherjs:purge Used in FirstMethodNameBytes +var pinUnexpMethI pinUnexpMeth + +//gopherjs:purge Unused method that uses pointer arithmetic for names +func FirstMethodNameBytes(t Type) *byte diff --git a/compiler/natives/src/internal/reflectlite/reflectlite.go b/compiler/natives/src/internal/reflectlite/reflectlite.go index eb425d857..5995f66ae 100644 --- a/compiler/natives/src/internal/reflectlite/reflectlite.go +++ b/compiler/natives/src/internal/reflectlite/reflectlite.go @@ -5,683 +5,120 @@ package reflectlite import ( "unsafe" + "internal/abi" + "github.com/gopherjs/gopherjs/js" ) -var initialized = false - func init() { // avoid dead code elimination used := func(i any) {} used(rtype{}) used(uncommonType{}) - used(method{}) - used(arrayType{}) - used(chanType{}) used(funcType{}) used(interfaceType{}) - used(mapType{}) - used(ptrType{}) - used(sliceType{}) used(structType{}) - used(imethod{}) - used(structField{}) - - initialized = true - uint8Type = TypeOf(uint8(0)).(*rtype) // set for real -} - -var uint8Type *rtype - -var ( - idJsType = "_jsType" - idReflectType = "_reflectType" - idKindType = "kindType" - idRtype = "_rtype" -) - -func jsType(typ Type) *js.Object { - return js.InternalObject(typ).Get(idJsType) -} - -func reflectType(typ *js.Object) *rtype { - if typ.Get(idReflectType) == js.Undefined { - rt := &rtype{ - size: uintptr(typ.Get("size").Int()), - kind: uint8(typ.Get("kind").Int()), - str: newNameOff(newName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), - } - js.InternalObject(rt).Set(idJsType, typ) - typ.Set(idReflectType, js.InternalObject(rt)) - - methodSet := js.Global.Call("$methodSet", typ) - if methodSet.Length() != 0 || typ.Get("named").Bool() { - rt.tflag |= tflagUncommon - if typ.Get("named").Bool() { - rt.tflag |= tflagNamed - } - var reflectMethods []method - for i := 0; i < methodSet.Length(); i++ { // Exported methods first. - m := methodSet.Index(i) - exported := internalStr(m.Get("pkg")) == "" - if !exported { - continue - } - reflectMethods = append(reflectMethods, method{ - name: newNameOff(newName(internalStr(m.Get("name")), "", exported, false)), - mtyp: newTypeOff(reflectType(m.Get("typ"))), - }) - } - xcount := uint16(len(reflectMethods)) - for i := 0; i < methodSet.Length(); i++ { // Unexported methods second. - m := methodSet.Index(i) - exported := internalStr(m.Get("pkg")) == "" - if exported { - continue - } - reflectMethods = append(reflectMethods, method{ - name: newNameOff(newName(internalStr(m.Get("name")), "", exported, false)), - mtyp: newTypeOff(reflectType(m.Get("typ"))), - }) - } - ut := &uncommonType{ - pkgPath: newNameOff(newName(internalStr(typ.Get("pkg")), "", false, false)), - mcount: uint16(methodSet.Length()), - xcount: xcount, - _methods: reflectMethods, - } - uncommonTypeMap[rt] = ut - js.InternalObject(ut).Set(idJsType, typ) - } - - switch rt.Kind() { - case Array: - setKindType(rt, &arrayType{ - elem: reflectType(typ.Get("elem")), - len: uintptr(typ.Get("len").Int()), - }) - case Chan: - dir := BothDir - if typ.Get("sendOnly").Bool() { - dir = SendDir - } - if typ.Get("recvOnly").Bool() { - dir = RecvDir - } - setKindType(rt, &chanType{ - elem: reflectType(typ.Get("elem")), - dir: uintptr(dir), - }) - case Func: - params := typ.Get("params") - in := make([]*rtype, params.Length()) - for i := range in { - in[i] = reflectType(params.Index(i)) - } - results := typ.Get("results") - out := make([]*rtype, results.Length()) - for i := range out { - out[i] = reflectType(results.Index(i)) - } - outCount := uint16(results.Length()) - if typ.Get("variadic").Bool() { - outCount |= 1 << 15 - } - setKindType(rt, &funcType{ - rtype: *rt, - inCount: uint16(params.Length()), - outCount: outCount, - _in: in, - _out: out, - }) - case Interface: - methods := typ.Get("methods") - imethods := make([]imethod, methods.Length()) - for i := range imethods { - m := methods.Index(i) - imethods[i] = imethod{ - name: newNameOff(newName(internalStr(m.Get("name")), "", internalStr(m.Get("pkg")) == "", false)), - typ: newTypeOff(reflectType(m.Get("typ"))), - } - } - setKindType(rt, &interfaceType{ - rtype: *rt, - pkgPath: newName(internalStr(typ.Get("pkg")), "", false, false), - methods: imethods, - }) - case Map: - setKindType(rt, &mapType{ - key: reflectType(typ.Get("key")), - elem: reflectType(typ.Get("elem")), - }) - case Ptr: - setKindType(rt, &ptrType{ - elem: reflectType(typ.Get("elem")), - }) - case Slice: - setKindType(rt, &sliceType{ - elem: reflectType(typ.Get("elem")), - }) - case Struct: - fields := typ.Get("fields") - reflectFields := make([]structField, fields.Length()) - for i := range reflectFields { - f := fields.Index(i) - reflectFields[i] = structField{ - name: newName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), - typ: reflectType(f.Get("typ")), - offset: uintptr(i), - } - } - setKindType(rt, &structType{ - rtype: *rt, - pkgPath: newName(internalStr(typ.Get("pkgPath")), "", false, false), - fields: reflectFields, - }) - } - } - - return (*rtype)(unsafe.Pointer(typ.Get(idReflectType).Unsafe())) -} - -func setKindType(rt *rtype, kindType any) { - js.InternalObject(rt).Set(idKindType, js.InternalObject(kindType)) - js.InternalObject(kindType).Set(idRtype, js.InternalObject(rt)) -} - -type uncommonType struct { - pkgPath nameOff - mcount uint16 - xcount uint16 - moff uint32 - - _methods []method -} - -func (t *uncommonType) methods() []method { - return t._methods -} - -func (t *uncommonType) exportedMethods() []method { - return t._methods[:t.xcount:t.xcount] -} - -var uncommonTypeMap = make(map[*rtype]*uncommonType) - -func (t *rtype) uncommon() *uncommonType { - return uncommonTypeMap[t] -} - -type funcType struct { - rtype `reflect:"func"` - inCount uint16 - outCount uint16 - - _in []*rtype - _out []*rtype -} - -func (t *funcType) in() []*rtype { - return t._in } -func (t *funcType) out() []*rtype { - return t._out +// gopherjs:new +func jsType(typ *abi.Type) *js.Object { + return typ.JsType() } +//gopherjs:purge Unused, replaced by abi.Name. type name struct { bytes *byte } -type nameData struct { - name string - tag string - exported bool - embedded bool +//gopherjs:replace +func pkgPath(n abi.Name) string { + return n.PkgPath() } -var nameMap = make(map[*byte]*nameData) +//gopherjs:purge Unused function because of nameOffList in internal/abi overrides +func resolveNameOff(ptrInModule unsafe.Pointer, off int32) unsafe.Pointer -func (n name) name() (s string) { return nameMap[n.bytes].name } -func (n name) tag() (s string) { return nameMap[n.bytes].tag } -func (n name) pkgPath() string { return "" } -func (n name) isExported() bool { return nameMap[n.bytes].exported } -func (n name) embedded() bool { return nameMap[n.bytes].embedded } - -func newName(n, tag string, exported, embedded bool) name { - b := new(byte) - nameMap[b] = &nameData{ - name: n, - tag: tag, - exported: exported, - embedded: embedded, - } - return name{ - bytes: b, - } -} - -var nameOffList []name - -func (t *rtype) nameOff(off nameOff) name { - return nameOffList[int(off)] -} +//gopherjs:purge Unused function because of typeOffList in internal/abi overrides +func resolveTypeOff(rtype unsafe.Pointer, off int32) unsafe.Pointer -func newNameOff(n name) nameOff { - i := len(nameOffList) - nameOffList = append(nameOffList, n) - return nameOff(i) +//gopherjs:replace +func (t rtype) nameOff(off nameOff) abi.Name { + return t.NameOff(off) } -var typeOffList []*rtype - -func (t *rtype) typeOff(off typeOff) *rtype { - return typeOffList[int(off)] -} - -func newTypeOff(t *rtype) typeOff { - i := len(typeOffList) - typeOffList = append(typeOffList, t) - return typeOff(i) -} - -func internalStr(strObj *js.Object) string { - var c struct{ str string } - js.InternalObject(c).Set("str", strObj) // get string without internalizing - return c.str -} - -func isWrapped(typ Type) bool { - return jsType(typ).Get("wrapped").Bool() -} - -func copyStruct(dst, src *js.Object, typ Type) { - fields := jsType(typ).Get("fields") - for i := 0; i < fields.Length(); i++ { - prop := fields.Index(i).Get("prop").String() - dst.Set(prop, src.Get(prop)) - } -} - -func makeValue(t Type, v *js.Object, fl flag) Value { - rt := t.common() - if t.Kind() == Array || t.Kind() == Struct || t.Kind() == Ptr { - return Value{rt, unsafe.Pointer(v.Unsafe()), fl | flag(t.Kind())} - } - return Value{rt, unsafe.Pointer(js.Global.Call("$newDataPointer", v, jsType(rt.ptrTo())).Unsafe()), fl | flag(t.Kind()) | flagIndir} +//gopherjs:replace +func (t rtype) typeOff(off typeOff) *abi.Type { + return t.TypeOff(off) } -func MakeSlice(typ Type, len, cap int) Value { - if typ.Kind() != Slice { - panic("reflect.MakeSlice of non-slice type") +//gopherjs:new +func makeValue(t *abi.Type, v *js.Object, fl flag) Value { + switch t.Kind() { + case abi.Array, abi.Struct, abi.Pointer: + return Value{t, unsafe.Pointer(v.Unsafe()), fl | flag(t.Kind())} } - if len < 0 { - panic("reflect.MakeSlice: negative len") - } - if cap < 0 { - panic("reflect.MakeSlice: negative cap") - } - if len > cap { - panic("reflect.MakeSlice: len > cap") - } - - return makeValue(typ, js.Global.Call("$makeSlice", jsType(typ), len, cap, js.InternalObject(func() *js.Object { return jsType(typ.Elem()).Call("zero") })), 0) + return Value{t, unsafe.Pointer(js.Global.Call("$newDataPointer", v, t.JsPtrTo()).Unsafe()), fl | flag(t.Kind()) | flagIndir} } +//gopherjs:replace func TypeOf(i any) Type { - if !initialized { // avoid error of uint8Type - return &rtype{} - } if i == nil { return nil } - return reflectType(js.InternalObject(i).Get("constructor")) + return toRType(abi.ReflectType(js.InternalObject(i).Get("constructor"))) } +//gopherjs:replace func ValueOf(i any) Value { if i == nil { return Value{} } - return makeValue(reflectType(js.InternalObject(i).Get("constructor")), js.InternalObject(i).Get("$val"), 0) -} - -func ArrayOf(count int, elem Type) Type { - return reflectType(js.Global.Call("$arrayType", jsType(elem), count)) -} - -func ChanOf(dir ChanDir, t Type) Type { - return reflectType(js.Global.Call("$chanType", jsType(t), dir == SendDir, dir == RecvDir)) -} - -func FuncOf(in, out []Type, variadic bool) Type { - if variadic && (len(in) == 0 || in[len(in)-1].Kind() != Slice) { - panic("reflect.FuncOf: last arg of variadic func must be slice") - } - - jsIn := make([]*js.Object, len(in)) - for i, v := range in { - jsIn[i] = jsType(v) - } - jsOut := make([]*js.Object, len(out)) - for i, v := range out { - jsOut[i] = jsType(v) - } - return reflectType(js.Global.Call("$funcType", jsIn, jsOut, variadic)) -} - -func MapOf(key, elem Type) Type { - switch key.Kind() { - case Func, Map, Slice: - panic("reflect.MapOf: invalid key type " + key.String()) - } - - return reflectType(js.Global.Call("$mapType", jsType(key), jsType(elem))) -} - -func (t *rtype) ptrTo() *rtype { - return reflectType(js.Global.Call("$ptrType", jsType(t))) -} - -func SliceOf(t Type) Type { - return reflectType(js.Global.Call("$sliceType", jsType(t))) -} - -func Zero(typ Type) Value { - return makeValue(typ, jsType(typ).Call("zero"), 0) -} - -func unsafe_New(typ *rtype) unsafe.Pointer { - switch typ.Kind() { - case Struct: - return unsafe.Pointer(jsType(typ).Get("ptr").New().Unsafe()) - case Array: - return unsafe.Pointer(jsType(typ).Call("zero").Unsafe()) - default: - return unsafe.Pointer(js.Global.Call("$newDataPointer", jsType(typ).Call("zero"), jsType(typ.ptrTo())).Unsafe()) - } -} - -func makeInt(f flag, bits uint64, t Type) Value { - typ := t.common() - ptr := unsafe_New(typ) - switch typ.Kind() { - case Int8: - *(*int8)(ptr) = int8(bits) - case Int16: - *(*int16)(ptr) = int16(bits) - case Int, Int32: - *(*int32)(ptr) = int32(bits) - case Int64: - *(*int64)(ptr) = int64(bits) - case Uint8: - *(*uint8)(ptr) = uint8(bits) - case Uint16: - *(*uint16)(ptr) = uint16(bits) - case Uint, Uint32, Uintptr: - *(*uint32)(ptr) = uint32(bits) - case Uint64: - *(*uint64)(ptr) = uint64(bits) - } - return Value{typ, ptr, f | flagIndir | flag(typ.Kind())} -} - -func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { - if typ.Kind() != Func { - panic("reflect: call of MakeFunc with non-Func type") - } - - t := typ.common() - ftyp := (*funcType)(unsafe.Pointer(t)) - - fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { - args := make([]Value, ftyp.NumIn()) - for i := range args { - argType := ftyp.In(i).common() - args[i] = makeValue(argType, arguments[i], 0) - } - resultsSlice := fn(args) - switch ftyp.NumOut() { - case 0: - return nil - case 1: - return resultsSlice[0].object() - default: - results := js.Global.Get("Array").New(ftyp.NumOut()) - for i, r := range resultsSlice { - results.SetIndex(i, r.object()) - } - return results - } - }) - - return Value{t, unsafe.Pointer(fv.Unsafe()), flag(Func)} -} - -func typedmemmove(t *rtype, dst, src unsafe.Pointer) { - js.InternalObject(dst).Call("$set", js.InternalObject(src).Call("$get")) -} - -func loadScalar(p unsafe.Pointer, n uintptr) uintptr { - return js.InternalObject(p).Call("$get").Unsafe() -} - -func makechan(typ *rtype, size int) (ch unsafe.Pointer) { - ctyp := (*chanType)(unsafe.Pointer(typ)) - return unsafe.Pointer(js.Global.Get("$Chan").New(jsType(ctyp.elem), size).Unsafe()) -} - -func makemap(t *rtype, cap int) (m unsafe.Pointer) { - return unsafe.Pointer(js.Global.Get("Map").New().Unsafe()) -} - -func keyFor(t *rtype, key unsafe.Pointer) (*js.Object, string) { - kv := js.InternalObject(key) - if kv.Get("$get") != js.Undefined { - kv = kv.Call("$get") - } - k := jsType(t.Key()).Call("keyFor", kv).String() - return kv, k -} - -func mapaccess(t *rtype, m, key unsafe.Pointer) unsafe.Pointer { - _, k := keyFor(t, key) - entry := js.InternalObject(m).Call("get", k) - if entry == js.Undefined { - return nil - } - return unsafe.Pointer(js.Global.Call("$newDataPointer", entry.Get("v"), jsType(PtrTo(t.Elem()))).Unsafe()) -} - -func mapassign(t *rtype, m, key, val unsafe.Pointer) { - kv, k := keyFor(t, key) - jsVal := js.InternalObject(val).Call("$get") - et := t.Elem() - if et.Kind() == Struct { - newVal := jsType(et).Call("zero") - copyStruct(newVal, jsVal, et) - jsVal = newVal - } - entry := js.Global.Get("Object").New() - entry.Set("k", kv) - entry.Set("v", jsVal) - js.InternalObject(m).Call("set", k, entry) -} - -func mapdelete(t *rtype, m unsafe.Pointer, key unsafe.Pointer) { - _, k := keyFor(t, key) - js.InternalObject(m).Call("delete", k) -} - -type mapIter struct { - t Type - m *js.Object - keys *js.Object - i int - - // last is the last object the iterator indicates. If this object exists, the functions that return the - // current key or value returns this object, regardless of the current iterator. It is because the current - // iterator might be stale due to key deletion in a loop. - last *js.Object -} - -func (iter *mapIter) skipUntilValidKey() { - for iter.i < iter.keys.Length() { - k := iter.keys.Index(iter.i) - if iter.m.Call("get", k) != js.Undefined { - break - } - // The key is already deleted. Move on the next item. - iter.i++ - } -} - -func mapiterinit(t *rtype, m unsafe.Pointer) unsafe.Pointer { - return unsafe.Pointer(&mapIter{t, js.InternalObject(m), js.Global.Get("Array").Call("from", js.InternalObject(m).Call("keys")), 0, nil}) -} - -type TypeEx interface { - Type - Key() Type -} - -func mapiterkey(it unsafe.Pointer) unsafe.Pointer { - iter := (*mapIter)(it) - var kv *js.Object - if iter.last != nil { - kv = iter.last - } else { - iter.skipUntilValidKey() - if iter.i == iter.keys.Length() { - return nil - } - k := iter.keys.Index(iter.i) - kv = iter.m.Call("get", k) - - // Record the key-value pair for later accesses. - iter.last = kv - } - return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("k"), jsType(PtrTo(iter.t.(TypeEx).Key()))).Unsafe()) -} - -func mapiternext(it unsafe.Pointer) { - iter := (*mapIter)(it) - iter.last = nil - iter.i++ -} - -func maplen(m unsafe.Pointer) int { - return js.InternalObject(m).Get("size").Int() + return makeValue(abi.ReflectType(js.InternalObject(i).Get("constructor")), js.InternalObject(i).Get("$val"), 0) } -func cvtDirect(v Value, typ Type) Value { - srcVal := v.object() - if srcVal == jsType(v.typ).Get("nil") { - return makeValue(typ, jsType(typ).Get("nil"), v.flag) - } - - var val *js.Object - switch k := typ.Kind(); k { - case Slice: - slice := jsType(typ).New(srcVal.Get("$array")) - slice.Set("$offset", srcVal.Get("$offset")) - slice.Set("$length", srcVal.Get("$length")) - slice.Set("$capacity", srcVal.Get("$capacity")) - val = js.Global.Call("$newDataPointer", slice, jsType(PtrTo(typ))) - case Ptr: - if typ.Elem().Kind() == Struct { - if typ.Elem() == v.typ.Elem() { - val = srcVal - break - } - val = jsType(typ).New() - copyStruct(val, srcVal, typ.Elem()) - break - } - val = jsType(typ).New(srcVal.Get("$get"), srcVal.Get("$set")) - case Struct: - val = jsType(typ).Get("ptr").New() - copyStruct(val, srcVal, typ) - case Array, Bool, Chan, Func, Interface, Map, String: - val = js.InternalObject(v.ptr) - default: - panic(&ValueError{"reflect.Convert", k}) - } - return Value{typ.common(), unsafe.Pointer(val.Unsafe()), v.flag.ro() | v.flag&flagIndir | flag(typ.Kind())} +//gopherjs:replace +func unsafe_New(typ *abi.Type) unsafe.Pointer { + return abi.UnsafeNew(typ) } -func Copy(dst, src Value) int { - dk := dst.kind() - if dk != Array && dk != Slice { - panic(&ValueError{"reflect.Copy", dk}) - } - if dk == Array { - dst.mustBeAssignable() - } - dst.mustBeExported() - - sk := src.kind() - var stringCopy bool - if sk != Array && sk != Slice { - stringCopy = sk == String && dst.typ.Elem().Kind() == Uint8 - if !stringCopy { - panic(&ValueError{"reflect.Copy", sk}) - } - } - src.mustBeExported() - - if !stringCopy { - typesMustMatch("reflect.Copy", dst.typ.Elem(), src.typ.Elem()) - } - - dstVal := dst.object() - if dk == Array { - dstVal = jsType(SliceOf(dst.typ.Elem())).New(dstVal) - } - - srcVal := src.object() - if sk == Array { - srcVal = jsType(SliceOf(src.typ.Elem())).New(srcVal) - } - - if stringCopy { - return js.Global.Call("$copyString", dstVal, srcVal).Int() - } - return js.Global.Call("$copySlice", dstVal, srcVal).Int() +//gopherjs:replace +func typedmemmove(t *abi.Type, dst, src unsafe.Pointer) { + abi.TypedMemMove(t, dst, src) } -func methodReceiver(op string, v Value, i int) (_ *rtype, t *funcType, fn unsafe.Pointer) { +//gopherjs:new This is a simplified copy of the version in reflect. +func methodReceiver(op string, v Value, i int) (fn unsafe.Pointer) { var prop string - if v.typ.Kind() == Interface { - tt := (*interfaceType)(unsafe.Pointer(v.typ)) - if i < 0 || i >= len(tt.methods) { + if v.typ.Kind() == abi.Interface { + tt := v.typ.InterfaceType() + if i < 0 || i >= len(tt.Methods) { panic("reflect: internal error: invalid method index") } - m := &tt.methods[i] - if !tt.nameOff(m.name).isExported() { + m := &tt.Methods[i] + if !tt.NameOff(m.Name).IsExported() { panic("reflect: " + op + " of unexported method") } - t = (*funcType)(unsafe.Pointer(tt.typeOff(m.typ))) - prop = tt.nameOff(m.name).name() + prop = tt.NameOff(m.Name).Name() } else { - ms := v.typ.exportedMethods() + ms := v.typ.ExportedMethods() if uint(i) >= uint(len(ms)) { panic("reflect: internal error: invalid method index") } m := ms[i] - if !v.typ.nameOff(m.name).isExported() { + if !v.typ.NameOff(m.Name).IsExported() { panic("reflect: " + op + " of unexported method") } - t = (*funcType)(unsafe.Pointer(v.typ.typeOff(m.mtyp))) prop = js.Global.Call("$methodSet", jsType(v.typ)).Index(i).Get("prop").String() } rcvr := v.object() - if isWrapped(v.typ) { + if v.typ.IsWrapped() { rcvr = jsType(v.typ).New(rcvr) } fn = unsafe.Pointer(rcvr.Get(prop).Unsafe()) return } +//gopherjs:replace func valueInterface(v Value) any { if v.flag == 0 { panic(&ValueError{"reflect.Value.Interface", 0}) @@ -691,10 +128,10 @@ func valueInterface(v Value) any { v = makeMethodValue("Interface", v) } - if isWrapped(v.typ) { - if v.flag&flagIndir != 0 && v.Kind() == Struct { + if v.typ.IsWrapped() { + if v.flag&flagIndir != 0 && v.Kind() == abi.Struct { cv := jsType(v.typ).Call("zero") - copyStruct(cv, v.object(), v.typ) + abi.CopyStruct(cv, v.object(), v.typ) return any(unsafe.Pointer(jsType(v.typ).New(cv).Unsafe())) } return any(unsafe.Pointer(jsType(v.typ).New(v.object()).Unsafe())) @@ -702,263 +139,33 @@ func valueInterface(v Value) any { return any(unsafe.Pointer(v.object().Unsafe())) } -func ifaceE2I(t *rtype, src any, dst unsafe.Pointer) { - js.InternalObject(dst).Call("$set", js.InternalObject(src)) +//gopherjs:replace +func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) { + abi.IfaceE2I(t, src, dst) } +// TODO(grantnelson-wf): methodName returns the name of the calling method, +// assumed to be two stack frames above. Determine if we can get this value now +// and if methodName is needed +// +//gopherjs:replace func methodName() string { return "?FIXME?" } +//gopherjs:new func makeMethodValue(op string, v Value) Value { if v.flag&flagMethod == 0 { panic("reflect: internal error: invalid use of makePartialFunc") } - _, _, fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) + fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) rcvr := v.object() - if isWrapped(v.typ) { + if v.typ.IsWrapped() { rcvr = jsType(v.typ).New(rcvr) } fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { return js.InternalObject(fn).Call("apply", rcvr, arguments) }) - return Value{v.Type().common(), unsafe.Pointer(fv.Unsafe()), v.flag.ro() | flag(Func)} -} - -var jsObjectPtr = reflectType(js.Global.Get("$jsObjectPtr")) - -func wrapJsObject(typ Type, val *js.Object) *js.Object { - if typ == jsObjectPtr { - return jsType(jsObjectPtr).New(val) - } - return val -} - -func unwrapJsObject(typ Type, val *js.Object) *js.Object { - if typ == jsObjectPtr { - return val.Get("object") - } - return val -} - -func getJsTag(tag string) string { - for tag != "" { - // skip leading space - i := 0 - for i < len(tag) && tag[i] == ' ' { - i++ - } - tag = tag[i:] - if tag == "" { - break - } - - // scan to colon. - // a space or a quote is a syntax error - i = 0 - for i < len(tag) && tag[i] != ' ' && tag[i] != ':' && tag[i] != '"' { - i++ - } - if i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { - break - } - name := string(tag[:i]) - tag = tag[i+1:] - - // scan quoted string to find value - i = 1 - for i < len(tag) && tag[i] != '"' { - if tag[i] == '\\' { - i++ - } - i++ - } - if i >= len(tag) { - break - } - qvalue := string(tag[:i+1]) - tag = tag[i+1:] - - if name == "js" { - value, _ := unquote(qvalue) - return value - } - } - return "" -} - -// PtrTo returns the pointer type with element t. -// For example, if t represents type Foo, PtrTo(t) represents *Foo. -func PtrTo(t Type) Type { - return t.(*rtype).ptrTo() -} - -// copyVal returns a Value containing the map key or value at ptr, -// allocating a new variable as needed. -func copyVal(typ *rtype, fl flag, ptr unsafe.Pointer) Value { - if ifaceIndir(typ) { - // Copy result so future changes to the map - // won't change the underlying value. - c := unsafe_New(typ) - typedmemmove(typ, c, ptr) - return Value{typ, c, fl | flagIndir} - } - return Value{typ, *(*unsafe.Pointer)(ptr), fl} -} - -var selectHelper = js.Global.Get("$select").Interface().(func(...any) *js.Object) - -func chanrecv(ch unsafe.Pointer, nb bool, val unsafe.Pointer) (selected, received bool) { - comms := [][]*js.Object{{js.InternalObject(ch)}} - if nb { - comms = append(comms, []*js.Object{}) - } - selectRes := selectHelper(comms) - if nb && selectRes.Index(0).Int() == 1 { - return false, false - } - recvRes := selectRes.Index(1) - js.InternalObject(val).Call("$set", recvRes.Index(0)) - return true, recvRes.Index(1).Bool() -} - -func chansend(ch unsafe.Pointer, val unsafe.Pointer, nb bool) bool { - comms := [][]*js.Object{{js.InternalObject(ch), js.InternalObject(val).Call("$get")}} - if nb { - comms = append(comms, []*js.Object{}) - } - selectRes := selectHelper(comms) - if nb && selectRes.Index(0).Int() == 1 { - return false - } - return true -} - -func rselect(rselects []runtimeSelect) (chosen int, recvOK bool) { - comms := make([][]*js.Object, len(rselects)) - for i, s := range rselects { - switch SelectDir(s.dir) { - case SelectDefault: - comms[i] = []*js.Object{} - case SelectRecv: - ch := js.Global.Get("$chanNil") - if js.InternalObject(s.ch) != js.InternalObject(0) { - ch = js.InternalObject(s.ch) - } - comms[i] = []*js.Object{ch} - case SelectSend: - ch := js.Global.Get("$chanNil") - var val *js.Object - if js.InternalObject(s.ch) != js.InternalObject(0) { - ch = js.InternalObject(s.ch) - val = js.InternalObject(s.val).Call("$get") - } - comms[i] = []*js.Object{ch, val} - } - } - selectRes := selectHelper(comms) - c := selectRes.Index(0).Int() - if SelectDir(rselects[c].dir) == SelectRecv { - recvRes := selectRes.Index(1) - js.InternalObject(rselects[c].val).Call("$set", recvRes.Index(0)) - return c, recvRes.Index(1).Bool() - } - return c, false -} - -func DeepEqual(a1, a2 any) bool { - i1 := js.InternalObject(a1) - i2 := js.InternalObject(a2) - if i1 == i2 { - return true - } - if i1 == nil || i2 == nil || i1.Get("constructor") != i2.Get("constructor") { - return false - } - return deepValueEqualJs(ValueOf(a1), ValueOf(a2), nil) -} - -func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { - if !v1.IsValid() || !v2.IsValid() { - return !v1.IsValid() && !v2.IsValid() - } - if v1.Type() != v2.Type() { - return false - } - if v1.Type() == jsObjectPtr { - return unwrapJsObject(jsObjectPtr, v1.object()) == unwrapJsObject(jsObjectPtr, v2.object()) - } - - switch v1.Kind() { - case Array, Map, Slice, Struct: - for _, entry := range visited { - if v1.ptr == entry[0] && v2.ptr == entry[1] { - return true - } - } - visited = append(visited, [2]unsafe.Pointer{v1.ptr, v2.ptr}) - } - - switch v1.Kind() { - case Array, Slice: - if v1.Kind() == Slice { - if v1.IsNil() != v2.IsNil() { - return false - } - if v1.object() == v2.object() { - return true - } - } - n := v1.Len() - if n != v2.Len() { - return false - } - for i := 0; i < n; i++ { - if !deepValueEqualJs(v1.Index(i), v2.Index(i), visited) { - return false - } - } - return true - case Interface: - if v1.IsNil() || v2.IsNil() { - return v1.IsNil() && v2.IsNil() - } - return deepValueEqualJs(v1.Elem(), v2.Elem(), visited) - case Ptr: - return deepValueEqualJs(v1.Elem(), v2.Elem(), visited) - case Struct: - n := v1.NumField() - for i := 0; i < n; i++ { - if !deepValueEqualJs(v1.Field(i), v2.Field(i), visited) { - return false - } - } - return true - case Map: - if v1.IsNil() != v2.IsNil() { - return false - } - if v1.object() == v2.object() { - return true - } - keys := v1.MapKeys() - if len(keys) != v2.Len() { - return false - } - for _, k := range keys { - val1 := v1.MapIndex(k) - val2 := v2.MapIndex(k) - if !val1.IsValid() || !val2.IsValid() || !deepValueEqualJs(val1, val2, visited) { - return false - } - } - return true - case Func: - return v1.IsNil() && v2.IsNil() - case UnsafePointer: - return v1.object() == v2.object() - } - - return js.Global.Call("$interfaceIsEqual", js.InternalObject(valueInterface(v1)), js.InternalObject(valueInterface(v2))).Bool() + return Value{v.Type().common(), unsafe.Pointer(fv.Unsafe()), v.flag.ro() | flag(abi.Func)} } diff --git a/compiler/natives/src/internal/reflectlite/swapper.go b/compiler/natives/src/internal/reflectlite/swapper.go index b21e950ee..7f4481485 100644 --- a/compiler/natives/src/internal/reflectlite/swapper.go +++ b/compiler/natives/src/internal/reflectlite/swapper.go @@ -4,6 +4,7 @@ package reflectlite import "github.com/gopherjs/gopherjs/js" +//gopherjs:replace func Swapper(slice any) func(i, j int) { v := ValueOf(slice) if v.Kind() != Slice { diff --git a/compiler/natives/src/internal/reflectlite/type.go b/compiler/natives/src/internal/reflectlite/type.go index 6cb14b899..6d7d281a7 100644 --- a/compiler/natives/src/internal/reflectlite/type.go +++ b/compiler/natives/src/internal/reflectlite/type.go @@ -2,101 +2,27 @@ package reflectlite -import ( - "unsafe" +//gopherjs:purge Unused in reflectlite, abi.ArrayType is used instead +type arrayType struct{} - "github.com/gopherjs/gopherjs/js" -) +//gopherjs:purge Unused in reflectlite, abi.ChanType is used instead +type chanType struct{} -func (t *rtype) Comparable() bool { - switch t.Kind() { - case Func, Slice, Map: - return false - case Array: - return t.Elem().Comparable() - case Struct: - for i := 0; i < t.NumField(); i++ { - ft := t.Field(i) - if !ft.typ.Comparable() { - return false - } - } - } - return true -} - -func (t *rtype) IsVariadic() bool { - if t.Kind() != Func { - panic("reflect: IsVariadic of non-func type") - } - tt := (*funcType)(unsafe.Pointer(t)) - return tt.outCount&(1<<15) != 0 -} - -func (t *rtype) kindType() *rtype { - return (*rtype)(unsafe.Pointer(js.InternalObject(t).Get(idKindType))) -} +//gopherjs:purge Unused in reflectlite, abi.MapType is used instead +type mapType struct{} -func (t *rtype) Field(i int) structField { - if t.Kind() != Struct { - panic("reflect: Field of non-struct type") - } - tt := (*structType)(unsafe.Pointer(t)) - if i < 0 || i >= len(tt.fields) { - panic("reflect: Field index out of bounds") - } - return tt.fields[i] -} +//gopherjs:purge Unused in reflectlite, abi.PtrType is used instead +type ptrType struct{} -func (t *rtype) Key() Type { - if t.Kind() != Map { - panic("reflect: Key of non-map type") - } - tt := (*mapType)(unsafe.Pointer(t)) - return toType(tt.key) -} +//gopherjs:purge Unused in reflectlite, abi.SliceType is used instead +type sliceType struct{} -func (t *rtype) NumField() int { - if t.Kind() != Struct { - panic("reflect: NumField of non-struct type") - } - tt := (*structType)(unsafe.Pointer(t)) - return len(tt.fields) +//gopherjs:replace +func (t rtype) Comparable() bool { + return t.common().Comparable() } -func (t *rtype) Method(i int) (m Method) { - if t.Kind() == Interface { - tt := (*interfaceType)(unsafe.Pointer(t)) - return tt.Method(i) - } - methods := t.exportedMethods() - if i < 0 || i >= len(methods) { - panic("reflect: Method index out of range") - } - p := methods[i] - pname := t.nameOff(p.name) - m.Name = pname.name() - fl := flag(Func) - mtyp := t.typeOff(p.mtyp) - ft := (*funcType)(unsafe.Pointer(mtyp)) - in := make([]Type, 0, 1+len(ft.in())) - in = append(in, t) - for _, arg := range ft.in() { - in = append(in, arg) - } - out := make([]Type, 0, len(ft.out())) - for _, ret := range ft.out() { - out = append(out, ret) - } - mt := FuncOf(in, out, ft.IsVariadic()) - m.Type = mt - prop := js.Global.Call("$methodSet", js.InternalObject(t).Get(idJsType)).Index(i).Get("prop").String() - fn := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { - rcvr := arguments[0] - return rcvr.Get(prop).Call("apply", rcvr, arguments[1:]) - }) - m.Func = Value{mt.(*rtype), unsafe.Pointer(fn.Unsafe()), fl} - - m.Index = i - return m +//gopherjs:replace +func (t rtype) String() string { + return t.common().String() } diff --git a/compiler/natives/src/internal/reflectlite/utils.go b/compiler/natives/src/internal/reflectlite/utils.go deleted file mode 100644 index c832bd4cf..000000000 --- a/compiler/natives/src/internal/reflectlite/utils.go +++ /dev/null @@ -1,96 +0,0 @@ -//go:build js - -package reflectlite - -import "unsafe" - -type ChanDir int - -const ( - RecvDir ChanDir = 1 << iota // <-chan - SendDir // chan<- - BothDir = RecvDir | SendDir // chan -) - -type errorString struct { - s string -} - -func (e *errorString) Error() string { - return e.s -} - -var ErrSyntax = &errorString{"invalid syntax"} - -func unquote(s string) (string, error) { - if len(s) < 2 { - return s, nil - } - if s[0] == '\'' || s[0] == '"' { - if s[len(s)-1] == s[0] { - return s[1 : len(s)-1], nil - } - return "", ErrSyntax - } - return s, nil -} - -// Method represents a single method. -type Method struct { - // Name is the method name. - // PkgPath is the package path that qualifies a lower case (unexported) - // method name. It is empty for upper case (exported) method names. - // The combination of PkgPath and Name uniquely identifies a method - // in a method set. - // See https://golang.org/ref/spec#Uniqueness_of_identifiers - Name string - PkgPath string - - Type Type // method type - Func Value // func with receiver as first argument - Index int // index for Type.Method -} - -// A SelectDir describes the communication direction of a select case. -type SelectDir int - -// NOTE: These values must match ../runtime/select.go:/selectDir. - -const ( - _ SelectDir = iota - SelectSend // case Chan <- Send - SelectRecv // case <-Chan: - SelectDefault // default -) - -// A runtimeSelect is a single case passed to rselect. -// This must match ../runtime/select.go:/runtimeSelect -type runtimeSelect struct { - dir SelectDir // SelectSend, SelectRecv or SelectDefault - typ *rtype // channel type - ch unsafe.Pointer // channel - val unsafe.Pointer // ptr to data (SendDir) or ptr to receive buffer (RecvDir) -} - -func (f flag) mustBe(expected Kind) { - // TODO(mvdan): use f.kind() again once mid-stack inlining gets better - if Kind(f&flagKindMask) != expected { - panic(&ValueError{methodName(), f.kind()}) - } -} - -// A StructTag is the tag string in a struct field. -// -// By convention, tag strings are a concatenation of -// optionally space-separated key:"value" pairs. -// Each key is a non-empty string consisting of non-control -// characters other than space (U+0020 ' '), quote (U+0022 '"'), -// and colon (U+003A ':'). Each value is quoted using U+0022 '"' -// characters and Go string literal syntax. -type StructTag string - -func typesMustMatch(what string, t1, t2 Type) { - if t1 != t2 { - panic(what + ": " + t1.String() + " != " + t2.String()) - } -} diff --git a/compiler/natives/src/internal/reflectlite/value.go b/compiler/natives/src/internal/reflectlite/value.go index db3eb3563..31daacc8d 100644 --- a/compiler/natives/src/internal/reflectlite/value.go +++ b/compiler/natives/src/internal/reflectlite/value.go @@ -5,22 +5,25 @@ package reflectlite import ( "unsafe" + "internal/abi" + "github.com/gopherjs/gopherjs/js" ) +//gopherjs:new func (v Value) object() *js.Object { - if v.typ.Kind() == Array || v.typ.Kind() == Struct { + if v.typ.Kind() == abi.Array || v.typ.Kind() == abi.Struct { return js.InternalObject(v.ptr) } if v.flag&flagIndir != 0 { val := js.InternalObject(v.ptr).Call("$get") if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsType(v.typ) { switch v.typ.Kind() { - case Uint64, Int64: + case abi.Uint64, abi.Int64: val = jsType(v.typ).New(val.Get("$high"), val.Get("$low")) - case Complex64, Complex128: + case abi.Complex64, abi.Complex128: val = jsType(v.typ).New(val.Get("$real"), val.Get("$imag")) - case Slice: + case abi.Slice: if val == val.Get("constructor").Get("nil") { val = jsType(v.typ).Get("nil") break @@ -37,7 +40,8 @@ func (v Value) object() *js.Object { return js.InternalObject(v.ptr) } -func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value { +//gopherjs:replace +func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Value { if v.flag&flagMethod != 0 { v = makeMethodValue(context, v) } @@ -69,242 +73,55 @@ func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value panic(context + ": value of type " + v.typ.String() + " is not assignable to type " + dst.String()) } -var callHelper = js.Global.Get("$call").Interface().(func(...any) *js.Object) - -func (v Value) call(op string, in []Value) []Value { - var ( - t *funcType - fn unsafe.Pointer - rcvr *js.Object - ) - if v.flag&flagMethod != 0 { - _, t, fn = methodReceiver(op, v, int(v.flag)>>flagMethodShift) - rcvr = v.object() - if isWrapped(v.typ) { - rcvr = jsType(v.typ).New(rcvr) - } - } else { - t = (*funcType)(unsafe.Pointer(v.typ)) - fn = unsafe.Pointer(v.object().Unsafe()) - rcvr = js.Undefined - } - - if fn == nil { - panic("reflect.Value.Call: call of nil function") - } - - isSlice := op == "CallSlice" - n := t.NumIn() - if isSlice { - if !t.IsVariadic() { - panic("reflect: CallSlice of non-variadic function") - } - if len(in) < n { - panic("reflect: CallSlice with too few input arguments") - } - if len(in) > n { - panic("reflect: CallSlice with too many input arguments") - } - } else { - if t.IsVariadic() { - n-- - } - if len(in) < n { - panic("reflect: Call with too few input arguments") - } - if !t.IsVariadic() && len(in) > n { - panic("reflect: Call with too many input arguments") - } - } - for _, x := range in { - if x.Kind() == Invalid { - panic("reflect: " + op + " using zero Value argument") - } - } - for i := 0; i < n; i++ { - if xt, targ := in[i].Type(), t.In(i); !xt.AssignableTo(targ) { - panic("reflect: " + op + " using " + xt.String() + " as type " + targ.String()) - } - } - if !isSlice && t.IsVariadic() { - // prepare slice for remaining values - m := len(in) - n - slice := MakeSlice(t.In(n), m, m) - elem := t.In(n).Elem() - for i := 0; i < m; i++ { - x := in[n+i] - if xt := x.Type(); !xt.AssignableTo(elem) { - panic("reflect: cannot use " + xt.String() + " as type " + elem.String() + " in " + op) - } - slice.Index(i).Set(x) - } - origIn := in - in = make([]Value, n+1) - copy(in[:n], origIn) - in[n] = slice - } - - nin := len(in) - if nin != t.NumIn() { - panic("reflect.Value.Call: wrong argument count") - } - nout := t.NumOut() - - argsArray := js.Global.Get("Array").New(t.NumIn()) - for i, arg := range in { - argsArray.SetIndex(i, unwrapJsObject(t.In(i), arg.assignTo("reflect.Value.Call", t.In(i).common(), nil).object())) - } - results := callHelper(js.InternalObject(fn), rcvr, argsArray) - - switch nout { - case 0: - return nil - case 1: - return []Value{makeValue(t.Out(0), wrapJsObject(t.Out(0), results), 0)} - default: - ret := make([]Value, nout) - for i := range ret { - ret[i] = makeValue(t.Out(i), wrapJsObject(t.Out(i), results.Index(i)), 0) - } - return ret - } -} - -func (v Value) Cap() int { - k := v.kind() - switch k { - case Array: - return v.typ.Len() - case Chan, Slice: - return v.object().Get("$capacity").Int() - } - panic(&ValueError{"reflect.Value.Cap", k}) -} - -func (v Value) Index(i int) Value { - switch k := v.kind(); k { - case Array: - tt := (*arrayType)(unsafe.Pointer(v.typ)) - if i < 0 || i > int(tt.len) { - panic("reflect: array index out of range") - } - typ := tt.elem - fl := v.flag&(flagIndir|flagAddr) | v.flag.ro() | flag(typ.Kind()) - - a := js.InternalObject(v.ptr) - if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( - js.InternalObject(func() *js.Object { return wrapJsObject(typ, a.Index(i)) }), - js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(typ, x)) }), - ).Unsafe()), fl} - } - return makeValue(typ, wrapJsObject(typ, a.Index(i)), fl) - - case Slice: - s := v.object() - if i < 0 || i >= s.Get("$length").Int() { - panic("reflect: slice index out of range") - } - tt := (*sliceType)(unsafe.Pointer(v.typ)) - typ := tt.elem - fl := flagAddr | flagIndir | v.flag.ro() | flag(typ.Kind()) - - i += s.Get("$offset").Int() - a := s.Get("$array") - if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( - js.InternalObject(func() *js.Object { return wrapJsObject(typ, a.Index(i)) }), - js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(typ, x)) }), - ).Unsafe()), fl} - } - return makeValue(typ, wrapJsObject(typ, a.Index(i)), fl) - - case String: - str := *(*string)(v.ptr) - if i < 0 || i >= len(str) { - panic("reflect: string index out of range") - } - fl := v.flag.ro() | flag(Uint8) | flagIndir - c := str[i] - return Value{uint8Type, unsafe.Pointer(&c), fl} - - default: - panic(&ValueError{"reflect.Value.Index", k}) - } -} - -func (v Value) InterfaceData() [2]uintptr { - panic("InterfaceData is not supported by GopherJS") -} - +//gopherjs:replace func (v Value) IsNil() bool { switch k := v.kind(); k { - case Ptr, Slice: + case abi.Pointer, abi.Slice: return v.object() == jsType(v.typ).Get("nil") - case Chan: + case abi.Chan: return v.object() == js.Global.Get("$chanNil") - case Func: + case abi.Func: return v.object() == js.Global.Get("$throwNilPointerError") - case Map: + case abi.Map: return v.object() == js.InternalObject(false) - case Interface: + case abi.Interface: return v.object() == js.Global.Get("$ifaceNil") - case UnsafePointer: + case abi.UnsafePointer: return v.object().Unsafe() == 0 default: panic(&ValueError{"reflect.Value.IsNil", k}) } } +//gopherjs:replace func (v Value) Len() int { switch k := v.kind(); k { - case Array, String: + case abi.Array, abi.String: return v.object().Length() - case Slice: + case abi.Slice: return v.object().Get("$length").Int() - case Chan: + case abi.Chan: return v.object().Get("$buffer").Get("length").Int() - case Map: + case abi.Map: return v.object().Get("size").Int() default: panic(&ValueError{"reflect.Value.Len", k}) } } -func (v Value) Pointer() uintptr { - switch k := v.kind(); k { - case Chan, Map, Ptr, UnsafePointer: - if v.IsNil() { - return 0 - } - return v.object().Unsafe() - case Func: - if v.IsNil() { - return 0 - } - return 1 - case Slice: - if v.IsNil() { - return 0 - } - return v.object().Get("$array").Unsafe() - default: - panic(&ValueError{"reflect.Value.Pointer", k}) - } -} - +//gopherjs:replace func (v Value) Set(x Value) { v.mustBeAssignable() x.mustBeExported() x = x.assignTo("reflect.Set", v.typ, nil) if v.flag&flagIndir != 0 { switch v.typ.Kind() { - case Array: + case abi.Array: jsType(v.typ).Call("copy", js.InternalObject(v.ptr), js.InternalObject(x.ptr)) - case Interface: + case abi.Interface: js.InternalObject(v.ptr).Call("$set", js.InternalObject(valueInterface(x))) - case Struct: - copyStruct(js.InternalObject(v.ptr), js.InternalObject(x.ptr), v.typ) + case abi.Struct: + abi.CopyStruct(js.InternalObject(v.ptr), js.InternalObject(x.ptr), v.typ) default: js.InternalObject(v.ptr).Call("$set", x.object()) } @@ -313,273 +130,37 @@ func (v Value) Set(x Value) { v.ptr = x.ptr } -func (v Value) SetBytes(x []byte) { - v.mustBeAssignable() - v.mustBe(Slice) - if v.typ.Elem().Kind() != Uint8 { - panic("reflect.Value.SetBytes of non-byte slice") - } - slice := js.InternalObject(x) - if v.typ.Name() != "" || v.typ.Elem().Name() != "" { - typedSlice := jsType(v.typ).New(slice.Get("$array")) - typedSlice.Set("$offset", slice.Get("$offset")) - typedSlice.Set("$length", slice.Get("$length")) - typedSlice.Set("$capacity", slice.Get("$capacity")) - slice = typedSlice - } - js.InternalObject(v.ptr).Call("$set", slice) -} - -func (v Value) SetCap(n int) { - v.mustBeAssignable() - v.mustBe(Slice) - s := js.InternalObject(v.ptr).Call("$get") - if n < s.Get("$length").Int() || n > s.Get("$capacity").Int() { - panic("reflect: slice capacity out of range in SetCap") - } - newSlice := jsType(v.typ).New(s.Get("$array")) - newSlice.Set("$offset", s.Get("$offset")) - newSlice.Set("$length", s.Get("$length")) - newSlice.Set("$capacity", n) - js.InternalObject(v.ptr).Call("$set", newSlice) -} - -func (v Value) SetLen(n int) { - v.mustBeAssignable() - v.mustBe(Slice) - s := js.InternalObject(v.ptr).Call("$get") - if n < 0 || n > s.Get("$capacity").Int() { - panic("reflect: slice length out of range in SetLen") - } - newSlice := jsType(v.typ).New(s.Get("$array")) - newSlice.Set("$offset", s.Get("$offset")) - newSlice.Set("$length", n) - newSlice.Set("$capacity", s.Get("$capacity")) - js.InternalObject(v.ptr).Call("$set", newSlice) -} - -func (v Value) Slice(i, j int) Value { - var ( - cap int - typ Type - s *js.Object - ) - switch kind := v.kind(); kind { - case Array: - if v.flag&flagAddr == 0 { - panic("reflect.Value.Slice: slice of unaddressable array") - } - tt := (*arrayType)(unsafe.Pointer(v.typ)) - cap = int(tt.len) - typ = SliceOf(tt.elem) - s = jsType(typ).New(v.object()) - - case Slice: - typ = v.typ - s = v.object() - cap = s.Get("$capacity").Int() - - case String: - str := *(*string)(v.ptr) - if i < 0 || j < i || j > len(str) { - panic("reflect.Value.Slice: string slice index out of bounds") - } - return ValueOf(str[i:j]) - - default: - panic(&ValueError{"reflect.Value.Slice", kind}) - } - - if i < 0 || j < i || j > cap { - panic("reflect.Value.Slice: slice index out of bounds") - } - - return makeValue(typ, js.Global.Call("$subslice", s, i, j), v.flag.ro()) -} - -func (v Value) Slice3(i, j, k int) Value { - var ( - cap int - typ Type - s *js.Object - ) - switch kind := v.kind(); kind { - case Array: - if v.flag&flagAddr == 0 { - panic("reflect.Value.Slice: slice of unaddressable array") - } - tt := (*arrayType)(unsafe.Pointer(v.typ)) - cap = int(tt.len) - typ = SliceOf(tt.elem) - s = jsType(typ).New(v.object()) - - case Slice: - typ = v.typ - s = v.object() - cap = s.Get("$capacity").Int() - - default: - panic(&ValueError{"reflect.Value.Slice3", kind}) - } - - if i < 0 || j < i || k < j || k > cap { - panic("reflect.Value.Slice3: slice index out of bounds") - } - - return makeValue(typ, js.Global.Call("$subslice", s, i, j, k), v.flag.ro()) -} - -func (v Value) Close() { - v.mustBe(Chan) - v.mustBeExported() - js.Global.Call("$close", v.object()) -} - +//gopherjs:replace func (v Value) Elem() Value { switch k := v.kind(); k { - case Interface: + case abi.Interface: val := v.object() if val == js.Global.Get("$ifaceNil") { return Value{} } - typ := reflectType(val.Get("constructor")) + typ := abi.ReflectType(val.Get("constructor")) return makeValue(typ, val.Get("$val"), v.flag.ro()) - case Ptr: + case abi.Pointer: if v.IsNil() { return Value{} } val := v.object() - tt := (*ptrType)(unsafe.Pointer(v.typ)) + tt := (*abi.PtrType)(unsafe.Pointer(v.typ)) fl := v.flag&flagRO | flagIndir | flagAddr - fl |= flag(tt.elem.Kind()) - return Value{tt.elem, unsafe.Pointer(wrapJsObject(tt.elem, val).Unsafe()), fl} + fl |= flag(tt.Elem.Kind()) + return Value{tt.Elem, unsafe.Pointer(abi.WrapJsObject(tt.Elem, val).Unsafe()), fl} default: panic(&ValueError{"reflect.Value.Elem", k}) } } -// NumField returns the number of fields in the struct v. -// It panics if v's Kind is not Struct. -func (v Value) NumField() int { - v.mustBe(Struct) - tt := (*structType)(unsafe.Pointer(v.typ)) - return len(tt.fields) -} - -// MapKeys returns a slice containing all the keys present in the map, -// in unspecified order. -// It panics if v's Kind is not Map. -// It returns an empty slice if v represents a nil map. -func (v Value) MapKeys() []Value { - v.mustBe(Map) - tt := (*mapType)(unsafe.Pointer(v.typ)) - keyType := tt.key - - fl := v.flag.ro() | flag(keyType.Kind()) - - m := v.pointer() - mlen := int(0) - if m != nil { - mlen = maplen(m) - } - it := mapiterinit(v.typ, m) - a := make([]Value, mlen) - var i int - for i = 0; i < len(a); i++ { - key := mapiterkey(it) - if key == nil { - // Someone deleted an entry from the map since we - // called maplen above. It's a data race, but nothing - // we can do about it. - break - } - a[i] = copyVal(keyType, fl, key) - mapiternext(it) - } - return a[:i] -} - -// MapIndex returns the value associated with key in the map v. -// It panics if v's Kind is not Map. -// It returns the zero Value if key is not found in the map or if v represents a nil map. -// As in Go, the key's value must be assignable to the map's key type. -func (v Value) MapIndex(key Value) Value { - v.mustBe(Map) - tt := (*mapType)(unsafe.Pointer(v.typ)) - - // Do not require key to be exported, so that DeepEqual - // and other programs can use all the keys returned by - // MapKeys as arguments to MapIndex. If either the map - // or the key is unexported, though, the result will be - // considered unexported. This is consistent with the - // behavior for structs, which allow read but not write - // of unexported fields. - key = key.assignTo("reflect.Value.MapIndex", tt.key, nil) - - var k unsafe.Pointer - if key.flag&flagIndir != 0 { - k = key.ptr - } else { - k = unsafe.Pointer(&key.ptr) - } - e := mapaccess(v.typ, v.pointer(), k) - if e == nil { - return Value{} - } - typ := tt.elem - fl := (v.flag | key.flag).ro() - fl |= flag(typ.Kind()) - return copyVal(typ, fl, e) -} - -func (v Value) Field(i int) Value { - if v.kind() != Struct { - panic(&ValueError{"reflect.Value.Field", v.kind()}) - } - tt := (*structType)(unsafe.Pointer(v.typ)) - if uint(i) >= uint(len(tt.fields)) { - panic("reflect: Field index out of range") - } - - prop := jsType(v.typ).Get("fields").Index(i).Get("prop").String() - field := &tt.fields[i] - typ := field.typ - - fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) - if !field.name.isExported() { - if field.embedded() { - fl |= flagEmbedRO - } else { - fl |= flagStickyRO - } - } +//gopherjs:purge Unused type +type emptyInterface struct{} - if tag := tt.fields[i].name.tag(); tag != "" && i != 0 { - if jsTag := getJsTag(tag); jsTag != "" { - for { - v = v.Field(0) - if v.typ == jsObjectPtr { - o := v.object().Get("object") - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( - js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), jsType(typ)) }), - js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, jsType(typ))) }), - ).Unsafe()), fl} - } - if v.typ.Kind() == Ptr { - v = v.Elem() - } - } - } - } +//gopherjs:purge Unused method for emptyInterface +func unpackEface(i any) Value - s := js.InternalObject(v.ptr) - if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( - js.InternalObject(func() *js.Object { return wrapJsObject(typ, s.Get(prop)) }), - js.InternalObject(func(x *js.Object) { s.Set(prop, unwrapJsObject(typ, x)) }), - ).Unsafe()), fl} - } - return makeValue(typ, wrapJsObject(typ, s.Get(prop)), fl) -} +//gopherjs:purge Unused method for emptyInterface +func packEface(v Value) any diff --git a/compiler/natives/src/reflect/export_test.go b/compiler/natives/src/reflect/export_test.go new file mode 100644 index 000000000..d1e700af1 --- /dev/null +++ b/compiler/natives/src/reflect/export_test.go @@ -0,0 +1,15 @@ +//go:build js + +package reflect + +//gopherjs:purge Uses GC, stack, and funcLayout +func FuncLayout(t Type, rcvr Type) (frametype Type, argSize, retOffset uintptr, stack, gc, inReg, outReg []byte, ptrs bool) + +//gopherjs:purge Uses internal information from map implementation +func MapBucketOf(x, y Type) Type + +//gopherjs:purge Uses internal information from map implementation +func CachedBucketOf(m Type) Type + +//gopherjs:purge Uses the byte name resolution +func FirstMethodNameBytes(t Type) *byte diff --git a/compiler/natives/src/reflect/reflect.go b/compiler/natives/src/reflect/reflect.go index baa83e260..18469eb16 100644 --- a/compiler/natives/src/reflect/reflect.go +++ b/compiler/natives/src/reflect/reflect.go @@ -7,19 +7,18 @@ import ( "strconv" "unsafe" + "internal/abi" "internal/itoa" + "github.com/gopherjs/gopherjs/compiler/natives/src/internal/unsafeheader" "github.com/gopherjs/gopherjs/js" ) -var initialized = false - func init() { // avoid dead code elimination used := func(i any) {} used(rtype{}) used(uncommonType{}) - used(method{}) used(arrayType{}) used(chanType{}) used(funcType{}) @@ -28,11 +27,8 @@ func init() { used(ptrType{}) used(sliceType{}) used(structType{}) - used(imethod{}) used(structField{}) - - initialized = true - uint8Type = TypeOf(uint8(0)).(*rtype) // set for real + used(toKindTypeExt) } // New returns a Value representing a pointer to a new zero value @@ -41,318 +37,110 @@ func init() { // The upstream version includes an extra check to avoid creating types that // are tagged as go:notinheap. This shouldn't matter in GopherJS, and tracking // that state is over-complex, so we just skip that check. +// +//gopherjs:replace func New(typ Type) Value { if typ == nil { panic("reflect: New(nil)") } - t := typ.(*rtype) - pt := t.ptrTo() + t := toAbiType(typ) + pt := t.PtrTo() ptr := unsafe_New(t) - fl := flag(Ptr) + fl := flag(Pointer) return Value{pt, ptr, fl} } +//gopherjs:new func jsType(typ Type) *js.Object { - return js.InternalObject(typ).Get("jsType") + return toAbiType(typ).JsType() } -func reflectType(typ *js.Object) *rtype { - if typ.Get("reflectType") == js.Undefined { - rt := &rtype{ - size: uintptr(typ.Get("size").Int()), - kind: uint8(typ.Get("kind").Int()), - str: resolveReflectName(newName(internalStr(typ.Get("string")), "", typ.Get("exported").Bool(), false)), - } - js.InternalObject(rt).Set("jsType", typ) - typ.Set("reflectType", js.InternalObject(rt)) - - methodSet := js.Global.Call("$methodSet", typ) - if methodSet.Length() != 0 || typ.Get("named").Bool() { - rt.tflag |= tflagUncommon - if typ.Get("named").Bool() { - rt.tflag |= tflagNamed - } - var reflectMethods []method - for i := 0; i < methodSet.Length(); i++ { // Exported methods first. - m := methodSet.Index(i) - exported := internalStr(m.Get("pkg")) == "" - if !exported { - continue - } - reflectMethods = append(reflectMethods, method{ - name: resolveReflectName(newMethodName(m)), - mtyp: newTypeOff(reflectType(m.Get("typ"))), - }) - } - xcount := uint16(len(reflectMethods)) - for i := 0; i < methodSet.Length(); i++ { // Unexported methods second. - m := methodSet.Index(i) - exported := internalStr(m.Get("pkg")) == "" - if exported { - continue - } - reflectMethods = append(reflectMethods, method{ - name: resolveReflectName(newMethodName(m)), - mtyp: newTypeOff(reflectType(m.Get("typ"))), - }) - } - ut := &uncommonType{ - pkgPath: resolveReflectName(newName(internalStr(typ.Get("pkg")), "", false, false)), - mcount: uint16(methodSet.Length()), - xcount: xcount, - _methods: reflectMethods, - } - js.InternalObject(ut).Set("jsType", typ) - js.InternalObject(rt).Set("uncommonType", js.InternalObject(ut)) - } - - switch rt.Kind() { - case Array: - setKindType(rt, &arrayType{ - elem: reflectType(typ.Get("elem")), - len: uintptr(typ.Get("len").Int()), - }) - case Chan: - dir := BothDir - if typ.Get("sendOnly").Bool() { - dir = SendDir - } - if typ.Get("recvOnly").Bool() { - dir = RecvDir - } - setKindType(rt, &chanType{ - elem: reflectType(typ.Get("elem")), - dir: uintptr(dir), - }) - case Func: - params := typ.Get("params") - in := make([]*rtype, params.Length()) - for i := range in { - in[i] = reflectType(params.Index(i)) - } - results := typ.Get("results") - out := make([]*rtype, results.Length()) - for i := range out { - out[i] = reflectType(results.Index(i)) - } - outCount := uint16(results.Length()) - if typ.Get("variadic").Bool() { - outCount |= 1 << 15 - } - setKindType(rt, &funcType{ - rtype: *rt, - inCount: uint16(params.Length()), - outCount: outCount, - _in: in, - _out: out, - }) - case Interface: - methods := typ.Get("methods") - imethods := make([]imethod, methods.Length()) - for i := range imethods { - m := methods.Index(i) - imethods[i] = imethod{ - name: resolveReflectName(newMethodName(m)), - typ: newTypeOff(reflectType(m.Get("typ"))), - } - } - setKindType(rt, &interfaceType{ - rtype: *rt, - pkgPath: newName(internalStr(typ.Get("pkg")), "", false, false), - methods: imethods, - }) - case Map: - setKindType(rt, &mapType{ - key: reflectType(typ.Get("key")), - elem: reflectType(typ.Get("elem")), - }) - case Ptr: - setKindType(rt, &ptrType{ - elem: reflectType(typ.Get("elem")), - }) - case Slice: - setKindType(rt, &sliceType{ - elem: reflectType(typ.Get("elem")), - }) - case Struct: - fields := typ.Get("fields") - reflectFields := make([]structField, fields.Length()) - for i := range reflectFields { - f := fields.Index(i) - reflectFields[i] = structField{ - name: newName(internalStr(f.Get("name")), internalStr(f.Get("tag")), f.Get("exported").Bool(), f.Get("embedded").Bool()), - typ: reflectType(f.Get("typ")), - offset: uintptr(i), - } - } - setKindType(rt, &structType{ - rtype: *rt, - pkgPath: newName(internalStr(typ.Get("pkgPath")), "", false, false), - fields: reflectFields, - }) - } - } - - return (*rtype)(unsafe.Pointer(typ.Get("reflectType").Unsafe())) -} - -func setKindType(rt *rtype, kindType any) { - js.InternalObject(rt).Set("kindType", js.InternalObject(kindType)) - js.InternalObject(kindType).Set("rtype", js.InternalObject(rt)) +//gopherjs:new +func toAbiType(typ Type) *abi.Type { + return typ.(*rtype).common() } -type uncommonType struct { - pkgPath nameOff - mcount uint16 - xcount uint16 - moff uint32 - - _methods []method -} - -func (t *uncommonType) methods() []method { - return t._methods -} - -func (t *uncommonType) exportedMethods() []method { - return t._methods[:t.xcount:t.xcount] -} +//gopherjs:replace +func toRType(t *abi.Type) *rtype { + const ( + idRType = `rType` + idKindType = `kindType` + ) -func (t *rtype) uncommon() *uncommonType { - obj := js.InternalObject(t).Get("uncommonType") - if obj == js.Undefined { - return nil + // rtypes are stored so that two types can be compared with `==`. + if jrt := js.InternalObject(t).Get(idRType); jrt != js.Undefined { + return (*rtype)(unsafe.Pointer(jrt.Unsafe())) } - return (*uncommonType)(unsafe.Pointer(obj.Unsafe())) -} - -type funcType struct { - rtype `reflect:"func"` - inCount uint16 - outCount uint16 - - _in []*rtype - _out []*rtype -} - -func (t *funcType) in() []*rtype { - return t._in -} - -func (t *funcType) out() []*rtype { - return t._out -} - -type name struct { - bytes *byte -} -type nameData struct { - name string - tag string - exported bool - embedded bool - pkgPath string + rtyp := &rtype{} + // Assign t to the abiType. The abiType is a `*Type` and the t field on `rtype` is `Type`. + // However, this is valid because of how pointers and references work in JS. + // We set this so that the t isn't a copy but the actual abiType object. + js.InternalObject(rtyp).Set("t", js.InternalObject(t)) + js.InternalObject(t).Set(idRType, js.InternalObject(rtyp)) + // Also copy over kindType to rtype so casts can be made directly from rtype to kindType. + js.InternalObject(rtyp).Set(idKindType, js.InternalObject(t).Get(idKindType)) + return rtyp } -var nameMap = make(map[*byte]*nameData) - -func (n name) name() (s string) { return nameMap[n.bytes].name } -func (n name) tag() (s string) { return nameMap[n.bytes].tag } -func (n name) pkgPath() string { return nameMap[n.bytes].pkgPath } -func (n name) isExported() bool { return nameMap[n.bytes].exported } -func (n name) embedded() bool { return nameMap[n.bytes].embedded } -func (n name) setPkgPath(pkgpath string) { - nameMap[n.bytes].pkgPath = pkgpath +//gopherjs:replace +func (t *rtype) String() string { + return toAbiType(t).String() } -func newName(n, tag string, exported, embedded bool) name { - b := new(byte) - nameMap[b] = &nameData{ - name: n, - tag: tag, - exported: exported, - embedded: embedded, - } - return name{ - bytes: b, - } +//gopherjs:replace +func rtypeOf(i any) *abi.Type { + return abi.ReflectType(js.InternalObject(i).Get("constructor")) } -// newMethodName creates name instance for a method. -// -// Input object is expected to be an entry of the "methods" list of the -// corresponding JS type. -func newMethodName(m *js.Object) name { - b := new(byte) - nameMap[b] = &nameData{ - name: internalStr(m.Get("name")), - tag: "", - pkgPath: internalStr(m.Get("pkg")), - exported: internalStr(m.Get("pkg")) == "", - } - return name{bytes: b} -} - -var nameOffList []name - -func (t *rtype) nameOff(off nameOff) name { - return nameOffList[int(off)] -} +//gopherjs:purge +func addReflectOff(ptr unsafe.Pointer) int32 -func resolveReflectName(n name) nameOff { - i := len(nameOffList) - nameOffList = append(nameOffList, n) - return nameOff(i) +//gopherjs:replace +func (t *rtype) nameOff(off aNameOff) abi.Name { + return toAbiType(t).NameOff(off) } -var typeOffList []*rtype - -func (t *rtype) typeOff(off typeOff) *rtype { - return typeOffList[int(off)] +//gopherjs:replace +func resolveReflectName(n abi.Name) aNameOff { + return abi.ResolveReflectName(n) } -func newTypeOff(t *rtype) typeOff { - i := len(typeOffList) - typeOffList = append(typeOffList, t) - return typeOff(i) +//gopherjs:replace +func (t *rtype) typeOff(off aTypeOff) *abi.Type { + return toAbiType(t).TypeOff(off) } -// addReflectOff adds a pointer to the reflection lookup map in the runtime. -// It returns a new ID that can be used as a typeOff or textOff, and will -// be resolved correctly. Implemented in the runtime package. -func addReflectOff(ptr unsafe.Pointer) int32 { - i := len(typeOffList) - typeOffList = append(typeOffList, (*rtype)(ptr)) - return int32(i) +//gopherjs:replace +func resolveReflectType(t *abi.Type) aTypeOff { + return abi.ResolveReflectType(t) } -func internalStr(strObj *js.Object) string { - var c struct{ str string } - js.InternalObject(c).Set("str", strObj) // get string without internalizing - return c.str +//gopherjs:replace +func (t *rtype) textOff(off aTextOff) unsafe.Pointer { + return toAbiType(t).TextOff(off) } -func isWrapped(typ Type) bool { - return jsType(typ).Get("wrapped").Bool() +//gopherjs:replace +func resolveReflectText(ptr unsafe.Pointer) aTextOff { + return abi.ResolveReflectText(ptr) } -func copyStruct(dst, src *js.Object, typ Type) { - fields := jsType(typ).Get("fields") - for i := 0; i < fields.Length(); i++ { - prop := fields.Index(i).Get("prop").String() - dst.Set(prop, src.Get(prop)) - } +//gopherjd:replace +func pkgPath(n abi.Name) string { + return n.PkgPath() } -func makeValue(t Type, v *js.Object, fl flag) Value { - rt := t.common() - if t.Kind() == Array || t.Kind() == Struct || t.Kind() == Ptr { - return Value{rt, unsafe.Pointer(v.Unsafe()), fl | flag(t.Kind())} +//gopherjs:new +func makeValue(t *abi.Type, v *js.Object, fl flag) Value { + switch t.Kind() { + case abi.Array, abi.Struct, abi.Pointer: + return Value{t, unsafe.Pointer(v.Unsafe()), fl | flag(t.Kind())} } - return Value{rt, unsafe.Pointer(js.Global.Call("$newDataPointer", v, jsType(rt.ptrTo())).Unsafe()), fl | flag(t.Kind()) | flagIndir} + return Value{t, unsafe.Pointer(js.Global.Call("$newDataPointer", v, t.JsPtrTo()).Unsafe()), fl | flag(t.Kind()) | flagIndir} } +//gopherjs:replace func MakeSlice(typ Type, len, cap int) Value { if typ.Kind() != Slice { panic("reflect.MakeSlice of non-slice type") @@ -367,38 +155,40 @@ func MakeSlice(typ Type, len, cap int) Value { panic("reflect.MakeSlice: len > cap") } - return makeValue(typ, js.Global.Call("$makeSlice", jsType(typ), len, cap, js.InternalObject(func() *js.Object { return jsType(typ.Elem()).Call("zero") })), 0) + return makeValue(toAbiType(typ), js.Global.Call("$makeSlice", jsType(typ), len, cap, js.InternalObject(func() *js.Object { return jsType(typ.Elem()).Call("zero") })), 0) } +//gopherjs:replace func TypeOf(i any) Type { - if !initialized { // avoid error of uint8Type - return &rtype{} - } if i == nil { return nil } - return reflectType(js.InternalObject(i).Get("constructor")) + return toRType(rtypeOf(i)) } +//gopherjs:replace func ValueOf(i any) Value { if i == nil { return Value{} } - return makeValue(reflectType(js.InternalObject(i).Get("constructor")), js.InternalObject(i).Get("$val"), 0) + return makeValue(rtypeOf(i), js.InternalObject(i).Get("$val"), 0) } +//gopherjs:replace func ArrayOf(count int, elem Type) Type { if count < 0 { panic("reflect: negative length passed to ArrayOf") } - return reflectType(js.Global.Call("$arrayType", jsType(elem), count)) + return toRType(abi.ReflectType(js.Global.Call("$arrayType", jsType(elem), count))) } +//gopherjs:replace func ChanOf(dir ChanDir, t Type) Type { - return reflectType(js.Global.Call("$chanType", jsType(t), dir == SendDir, dir == RecvDir)) + return toRType(abi.ReflectType(js.Global.Call("$chanType", jsType(t), dir == SendDir, dir == RecvDir))) } +//gopherjs:replace func FuncOf(in, out []Type, variadic bool) Type { if variadic && (len(in) == 0 || in[len(in)-1].Kind() != Slice) { panic("reflect.FuncOf: last arg of variadic func must be slice") @@ -412,26 +202,30 @@ func FuncOf(in, out []Type, variadic bool) Type { for i, v := range out { jsOut[i] = jsType(v) } - return reflectType(js.Global.Call("$funcType", jsIn, jsOut, variadic)) + return toRType(abi.ReflectType(js.Global.Call("$funcType", jsIn, jsOut, variadic))) } +//gopherjs:replace func MapOf(key, elem Type) Type { switch key.Kind() { case Func, Map, Slice: panic("reflect.MapOf: invalid key type " + key.String()) } - return reflectType(js.Global.Call("$mapType", jsType(key), jsType(elem))) + return toRType(abi.ReflectType(js.Global.Call("$mapType", jsType(key), jsType(elem)))) } -func (t *rtype) ptrTo() *rtype { - return reflectType(js.Global.Call("$ptrType", jsType(t))) +//gopherjs:replace +func (t *rtype) ptrTo() *abi.Type { + return abi.ReflectType(js.Global.Call("$ptrType", jsType(t))) } +//gopherjs:replace func SliceOf(t Type) Type { - return reflectType(js.Global.Call("$sliceType", jsType(t))) + return toRType(abi.ReflectType(js.Global.Call("$sliceType", jsType(t)))) } +//gopherjs:replace func StructOf(fields []StructField) Type { var ( jsFields = make([]*js.Object, len(fields)) @@ -450,8 +244,8 @@ func StructOf(fields []StructField) Type { panic("reflect.StructOf: field " + strconv.Itoa(i) + " has no type") } f, fpkgpath := runtimeStructField(field) - ft := f.typ - if ft.kind&kindGCProg != 0 { + ft := f.Typ + if ft.Kind()&kindGCProg != 0 { hasGCProg = true } if fpkgpath != "" { @@ -462,7 +256,7 @@ func StructOf(fields []StructField) Type { } } name := field.Name - if f.embedded() { + if f.Embedded() { // Embedded field if field.Type.Kind() == Ptr { // Embedded ** and *interface{} are illegal @@ -475,8 +269,8 @@ func StructOf(fields []StructField) Type { case Interface: case Ptr: ptr := (*ptrType)(unsafe.Pointer(ft)) - if unt := ptr.uncommon(); unt != nil { - if i > 0 && unt.mcount > 0 { + if unt := ptr.Uncommon(); unt != nil { + if i > 0 && unt.Mcount > 0 { // Issue 15924. panic("reflect: embedded type with methods not implemented if type is not first field") } @@ -485,12 +279,12 @@ func StructOf(fields []StructField) Type { } } default: - if unt := ft.uncommon(); unt != nil { - if i > 0 && unt.mcount > 0 { + if unt := ft.Uncommon(); unt != nil { + if i > 0 && unt.Mcount > 0 { // Issue 15924. panic("reflect: embedded type with methods not implemented if type is not first field") } - if len(fields) > 1 && ft.kind&kindDirectIface != 0 { + if len(fields) > 1 && ft.Kind()&kindDirectIface != 0 { panic("reflect: embedded type with methods not implemented for non-pointer type") } } @@ -508,7 +302,7 @@ func StructOf(fields []StructField) Type { // The rest is set through the js.Object() interface, which the compiler will // externalize for us. jsf.Set("prop", name) - jsf.Set("exported", f.name.isExported()) + jsf.Set("exported", f.Name.IsExported()) jsf.Set("typ", jsType(field.Type)) jsf.Set("tag", field.Tag) jsf.Set("embedded", field.Anonymous) @@ -519,48 +313,45 @@ func StructOf(fields []StructField) Type { if pkgpath != "" { typ.Set("pkgPath", pkgpath) } - return reflectType(typ) + return toRType(abi.ReflectType(typ)) } +//gopherjs:replace func Zero(typ Type) Value { - return makeValue(typ, jsType(typ).Call("zero"), 0) + return makeValue(toAbiType(typ), jsType(typ).Call("zero"), 0) } -func unsafe_New(typ *rtype) unsafe.Pointer { - switch typ.Kind() { - case Struct: - return unsafe.Pointer(jsType(typ).Get("ptr").New().Unsafe()) - case Array: - return unsafe.Pointer(jsType(typ).Call("zero").Unsafe()) - default: - return unsafe.Pointer(js.Global.Call("$newDataPointer", jsType(typ).Call("zero"), jsType(typ.ptrTo())).Unsafe()) - } +//gopherjs:replace +func unsafe_New(typ *abi.Type) unsafe.Pointer { + return abi.UnsafeNew(typ) } +//gopherjs:replace func makeInt(f flag, bits uint64, t Type) Value { typ := t.common() ptr := unsafe_New(typ) switch typ.Kind() { - case Int8: + case abi.Int8: *(*int8)(ptr) = int8(bits) - case Int16: + case abi.Int16: *(*int16)(ptr) = int16(bits) - case Int, Int32: + case abi.Int, abi.Int32: *(*int32)(ptr) = int32(bits) - case Int64: + case abi.Int64: *(*int64)(ptr) = int64(bits) - case Uint8: + case abi.Uint8: *(*uint8)(ptr) = uint8(bits) - case Uint16: + case abi.Uint16: *(*uint16)(ptr) = uint16(bits) - case Uint, Uint32, Uintptr: + case abi.Uint, abi.Uint32, abi.Uintptr: *(*uint32)(ptr) = uint32(bits) - case Uint64: + case abi.Uint64: *(*uint64)(ptr) = uint64(bits) } return Value{typ, ptr, f | flagIndir | flag(typ.Kind())} } +//gopherjs:replace func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { if typ.Kind() != Func { panic("reflect: call of MakeFunc with non-Func type") @@ -573,7 +364,7 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { // Convert raw JS arguments into []Value the user-supplied function expects. args := make([]Value, ftyp.NumIn()) for i := range args { - argType := ftyp.In(i).common() + argType := ftyp.In(i) args[i] = makeValue(argType, arguments[i], 0) } @@ -584,8 +375,8 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { if want, got := ftyp.NumOut(), len(resultsSlice); want != got { panic("reflect: expected " + strconv.Itoa(want) + " return values, got " + strconv.Itoa(got)) } - for i, rtyp := range ftyp.out() { - if !resultsSlice[i].Type().AssignableTo(rtyp) { + for i, rtyp := range ftyp.OutSlice() { + if !resultsSlice[i].Type().AssignableTo(toRType(rtyp)) { panic("reflect: " + strconv.Itoa(i) + " return value type is not compatible with the function declaration") } } @@ -608,33 +399,34 @@ func MakeFunc(typ Type, fn func(args []Value) (results []Value)) Value { return Value{t, unsafe.Pointer(fv.Unsafe()), flag(Func)} } -func typedmemmove(t *rtype, dst, src unsafe.Pointer) { - js.InternalObject(dst).Call("$set", js.InternalObject(src).Call("$get")) +//gopherjs:replace +func typedmemmove(t *abi.Type, dst, src unsafe.Pointer) { + abi.TypedMemMove(t, dst, src) } -func loadScalar(p unsafe.Pointer, n uintptr) uintptr { - return js.InternalObject(p).Call("$get").Unsafe() -} - -func makechan(typ *rtype, size int) (ch unsafe.Pointer) { +//gopherjs:replace +func makechan(typ *abi.Type, size int) (ch unsafe.Pointer) { ctyp := (*chanType)(unsafe.Pointer(typ)) - return unsafe.Pointer(js.Global.Get("$Chan").New(jsType(ctyp.elem), size).Unsafe()) + return unsafe.Pointer(js.Global.Get("$Chan").New(ctyp.Elem.JsType(), size).Unsafe()) } -func makemap(t *rtype, cap int) (m unsafe.Pointer) { +//gopherjs:replace +func makemap(t *abi.Type, cap int) (m unsafe.Pointer) { return unsafe.Pointer(js.Global.Get("Map").New().Unsafe()) } -func keyFor(t *rtype, key unsafe.Pointer) (*js.Object, *js.Object) { +//gopherjs:new +func keyFor(t *abi.Type, key unsafe.Pointer) (*js.Object, *js.Object) { kv := js.InternalObject(key) if kv.Get("$get") != js.Undefined { kv = kv.Call("$get") } - k := jsType(t.Key()).Call("keyFor", kv) + k := t.Key().JsType().Call("keyFor", kv) return kv, k } -func mapaccess(t *rtype, m, key unsafe.Pointer) unsafe.Pointer { +//gopherjs:replace +func mapaccess(t *abi.Type, m, key unsafe.Pointer) unsafe.Pointer { if !js.InternalObject(m).Bool() { return nil // nil map } @@ -643,16 +435,17 @@ func mapaccess(t *rtype, m, key unsafe.Pointer) unsafe.Pointer { if entry == js.Undefined { return nil } - return unsafe.Pointer(js.Global.Call("$newDataPointer", entry.Get("v"), jsType(PtrTo(t.Elem()))).Unsafe()) + return unsafe.Pointer(js.Global.Call("$newDataPointer", entry.Get("v"), t.Elem().JsPtrTo()).Unsafe()) } -func mapassign(t *rtype, m, key, val unsafe.Pointer) { +//gopherjs:replace +func mapassign(t *abi.Type, m, key, val unsafe.Pointer) { kv, k := keyFor(t, key) jsVal := js.InternalObject(val).Call("$get") et := t.Elem() - if et.Kind() == Struct { - newVal := jsType(et).Call("zero") - copyStruct(newVal, jsVal, et) + if et.Kind() == abi.Struct { + newVal := et.JsType().Call("zero") + abi.CopyStruct(newVal, jsVal, et) jsVal = newVal } entry := js.Global.Get("Object").New() @@ -661,7 +454,8 @@ func mapassign(t *rtype, m, key, val unsafe.Pointer) { js.InternalObject(m).Call("set", k, entry) } -func mapdelete(t *rtype, m unsafe.Pointer, key unsafe.Pointer) { +//gopherjs:replace +func mapdelete(t *abi.Type, m unsafe.Pointer, key unsafe.Pointer) { _, k := keyFor(t, key) if !js.InternalObject(m).Bool() { return // nil map @@ -676,20 +470,24 @@ func mapdelete(t *rtype, m unsafe.Pointer, key unsafe.Pointer) { // string-to-pointer conversion. Not sure how to fix this without significant // code duplication, however. -func mapaccess_faststr(t *rtype, m unsafe.Pointer, key string) (val unsafe.Pointer) { +//gopherjs:replace +func mapaccess_faststr(t *abi.Type, m unsafe.Pointer, key string) (val unsafe.Pointer) { return mapaccess(t, m, unsafe.Pointer(&key)) } -func mapassign_faststr(t *rtype, m unsafe.Pointer, key string, val unsafe.Pointer) { +//gopherjs:replace +func mapassign_faststr(t *abi.Type, m unsafe.Pointer, key string, val unsafe.Pointer) { mapassign(t, m, unsafe.Pointer(&key), val) } -func mapdelete_faststr(t *rtype, m unsafe.Pointer, key string) { +//gopherjs:replace +func mapdelete_faststr(t *abi.Type, m unsafe.Pointer, key string) { mapdelete(t, m, unsafe.Pointer(&key)) } +//gopherjs:replace type hiter struct { - t Type + t *abi.Type m *js.Object // Underlying map object. keys *js.Object i int @@ -701,6 +499,7 @@ type hiter struct { last *js.Object } +//gopherjs:new func (iter *hiter) skipUntilValidKey() { for iter.i < iter.keys.Length() { k := iter.keys.Index(iter.i) @@ -713,7 +512,8 @@ func (iter *hiter) skipUntilValidKey() { } } -func mapiterinit(t *rtype, m unsafe.Pointer, it *hiter) { +//gopherjs:replace +func mapiterinit(t *abi.Type, m unsafe.Pointer, it *hiter) { mapObj := js.InternalObject(m) keys := js.Global.Get("Array").New() if mapObj.Get("keys") != js.Undefined { @@ -732,6 +532,7 @@ func mapiterinit(t *rtype, m unsafe.Pointer, it *hiter) { } } +//gopherjs:replace func mapiterkey(it *hiter) unsafe.Pointer { var kv *js.Object if it.last != nil { @@ -747,9 +548,10 @@ func mapiterkey(it *hiter) unsafe.Pointer { // Record the key-value pair for later accesses. it.last = kv } - return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("k"), jsType(PtrTo(it.t.Key()))).Unsafe()) + return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("k"), it.t.Key().JsPtrTo()).Unsafe()) } +//gopherjs:replace func mapiterelem(it *hiter) unsafe.Pointer { var kv *js.Object if it.last != nil { @@ -763,22 +565,25 @@ func mapiterelem(it *hiter) unsafe.Pointer { kv = it.m.Call("get", k) it.last = kv } - return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("v"), jsType(PtrTo(it.t.Elem()))).Unsafe()) + return unsafe.Pointer(js.Global.Call("$newDataPointer", kv.Get("v"), it.t.Elem().JsPtrTo()).Unsafe()) } +//gopherjs:replace func mapiternext(it *hiter) { it.last = nil it.i++ } +//gopherjs:replace func maplen(m unsafe.Pointer) int { return js.InternalObject(m).Get("size").Int() } +//gopherjs:replace func cvtDirect(v Value, typ Type) Value { srcVal := v.object() - if srcVal == jsType(v.typ).Get("nil") { - return makeValue(typ, jsType(typ).Get("nil"), v.flag) + if srcVal == v.typ().JsType().Get("nil") { + return makeValue(toAbiType(typ), jsType(typ).Get("nil"), v.flag) } var val *js.Object @@ -792,12 +597,12 @@ func cvtDirect(v Value, typ Type) Value { case Ptr: switch typ.Elem().Kind() { case Struct: - if typ.Elem() == v.typ.Elem() { + if toAbiType(typ.Elem()) == v.typ().Elem() { val = srcVal break } val = jsType(typ).New() - copyStruct(val, srcVal, typ.Elem()) + abi.CopyStruct(val, srcVal, toAbiType(typ.Elem())) case Array: // Unlike other pointers, array pointers are "wrapped" types (see // isWrapped() in the compiler package), and are represented by a native @@ -808,7 +613,7 @@ func cvtDirect(v Value, typ Type) Value { } case Struct: val = jsType(typ).Get("ptr").New() - copyStruct(val, srcVal, typ) + abi.CopyStruct(val, srcVal, toAbiType(typ)) case Array, Bool, Chan, Func, Interface, Map, String, UnsafePointer: val = js.InternalObject(v.ptr) default: @@ -818,6 +623,8 @@ func cvtDirect(v Value, typ Type) Value { } // convertOp: []T -> *[N]T +// +//gopherjs:replace func cvtSliceArrayPtr(v Value, t Type) Value { slice := v.object() @@ -831,6 +638,8 @@ func cvtSliceArrayPtr(v Value, t Type) Value { } // convertOp: []T -> [N]T +// +//gopherjs:replace func cvtSliceArray(v Value, t Type) Value { n := t.Len() if n > v.Len() { @@ -845,6 +654,7 @@ func cvtSliceArray(v Value, t Type) Value { return Value{t.common(), unsafe.Pointer(arr.Unsafe()), v.flag&^(flagAddr|flagKindMask) | flag(Array)} } +//gopherjs:replace func Copy(dst, src Value) int { dk := dst.kind() if dk != Array && dk != Slice { @@ -858,7 +668,7 @@ func Copy(dst, src Value) int { sk := src.kind() var stringCopy bool if sk != Array && sk != Slice { - stringCopy = sk == String && dst.typ.Elem().Kind() == Uint8 + stringCopy = sk == String && dst.typ().Elem().Kind() == abi.Uint8 if !stringCopy { panic(&ValueError{"reflect.Copy", sk}) } @@ -866,17 +676,17 @@ func Copy(dst, src Value) int { src.mustBeExported() if !stringCopy { - typesMustMatch("reflect.Copy", dst.typ.Elem(), src.typ.Elem()) + typesMustMatch("reflect.Copy", toRType(dst.typ().Elem()), toRType(src.typ().Elem())) } dstVal := dst.object() if dk == Array { - dstVal = jsType(SliceOf(dst.typ.Elem())).New(dstVal) + dstVal = jsType(SliceOf(toRType(dst.typ().Elem()))).New(dstVal) } srcVal := src.object() if sk == Array { - srcVal = jsType(SliceOf(src.typ.Elem())).New(srcVal) + srcVal = jsType(SliceOf(toRType(src.typ().Elem()))).New(srcVal) } if stringCopy { @@ -885,39 +695,43 @@ func Copy(dst, src Value) int { return js.Global.Call("$copySlice", dstVal, srcVal).Int() } -func methodReceiver(op string, v Value, i int) (_ *rtype, t *funcType, fn unsafe.Pointer) { +//gopherjs:replace +func methodReceiver(op string, v Value, methodIndex int) (rcvrtype *abi.Type, t *funcType, fn unsafe.Pointer) { + i := methodIndex var prop string - if v.typ.Kind() == Interface { - tt := (*interfaceType)(unsafe.Pointer(v.typ)) - if i < 0 || i >= len(tt.methods) { + if v.typ().Kind() == abi.Interface { + tt := (*interfaceType)(unsafe.Pointer(v.typ())) + if i < 0 || i >= len(tt.Methods) { panic("reflect: internal error: invalid method index") } - m := &tt.methods[i] - if !tt.nameOff(m.name).isExported() { + m := &tt.Methods[i] + if !tt.NameOff(m.Name).IsExported() { panic("reflect: " + op + " of unexported method") } - t = (*funcType)(unsafe.Pointer(tt.typeOff(m.typ))) - prop = tt.nameOff(m.name).name() + t = (*funcType)(unsafe.Pointer(tt.typeOff(m.Typ))) + prop = tt.NameOff(m.Name).Name() } else { - ms := v.typ.exportedMethods() + rcvrtype = v.typ() + ms := v.typ().ExportedMethods() if uint(i) >= uint(len(ms)) { panic("reflect: internal error: invalid method index") } m := ms[i] - if !v.typ.nameOff(m.name).isExported() { + if !v.typ().NameOff(m.Name).IsExported() { panic("reflect: " + op + " of unexported method") } - t = (*funcType)(unsafe.Pointer(v.typ.typeOff(m.mtyp))) - prop = js.Global.Call("$methodSet", jsType(v.typ)).Index(i).Get("prop").String() + t = (*funcType)(unsafe.Pointer(v.typ().TypeOff(m.Mtyp))) + prop = js.Global.Call("$methodSet", v.typ().JsType()).Index(i).Get("prop").String() } rcvr := v.object() - if isWrapped(v.typ) { - rcvr = jsType(v.typ).New(rcvr) + if v.typ().IsWrapped() { + rcvr = v.typ().JsType().New(rcvr) } fn = unsafe.Pointer(rcvr.Get(prop).Unsafe()) return } +//gopherjs:replace func valueInterface(v Value, safe bool) any { if v.flag == 0 { panic(&ValueError{"reflect.Value.Interface", 0}) @@ -929,21 +743,24 @@ func valueInterface(v Value, safe bool) any { v = makeMethodValue("Interface", v) } - if isWrapped(v.typ) { + if v.typ().IsWrapped() { + jsTyp := v.typ().JsType() if v.flag&flagIndir != 0 && v.Kind() == Struct { - cv := jsType(v.typ).Call("zero") - copyStruct(cv, v.object(), v.typ) - return any(unsafe.Pointer(jsType(v.typ).New(cv).Unsafe())) + cv := jsTyp.Call("zero") + abi.CopyStruct(cv, v.object(), v.typ()) + return any(unsafe.Pointer(jsTyp.New(cv).Unsafe())) } - return any(unsafe.Pointer(jsType(v.typ).New(v.object()).Unsafe())) + return any(unsafe.Pointer(jsTyp.New(v.object()).Unsafe())) } return any(unsafe.Pointer(v.object().Unsafe())) } -func ifaceE2I(t *rtype, src any, dst unsafe.Pointer) { - js.InternalObject(dst).Call("$set", js.InternalObject(src)) +//gopherjs:replace +func ifaceE2I(t *abi.Type, src any, dst unsafe.Pointer) { + abi.IfaceE2I(t, src, dst) } +//gopherjs:replace func makeMethodValue(op string, v Value) Value { if v.flag&flagMethod == 0 { panic("reflect: internal error: invalid use of makePartialFunc") @@ -951,8 +768,8 @@ func makeMethodValue(op string, v Value) Value { _, _, fn := methodReceiver(op, v, int(v.flag)>>flagMethodShift) rcvr := v.object() - if isWrapped(v.typ) { - rcvr = jsType(v.typ).New(rcvr) + if v.typ().IsWrapped() { + rcvr = v.typ().JsType().New(rcvr) } fv := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { return js.InternalObject(fn).Call("apply", rcvr, arguments) @@ -960,6 +777,7 @@ func makeMethodValue(op string, v Value) Value { return Value{v.Type().common(), unsafe.Pointer(fv.Unsafe()), v.flag.ro() | flag(Func)} } +//gopherjs:new func (t *rtype) pointers() bool { switch t.Kind() { case Ptr, Map, Chan, Func, Struct, Array: @@ -969,22 +787,12 @@ func (t *rtype) pointers() bool { } } +//gopherjs:replace func (t *rtype) Comparable() bool { - switch t.Kind() { - case Func, Slice, Map: - return false - case Array: - return t.Elem().Comparable() - case Struct: - for i := 0; i < t.NumField(); i++ { - if !t.Field(i).Type.Comparable() { - return false - } - } - } - return true + return toAbiType(t).Comparable() } +//gopherjs:replace func (t *rtype) Method(i int) (m Method) { if t.Kind() == Interface { tt := (*interfaceType)(unsafe.Pointer(t)) @@ -995,51 +803,53 @@ func (t *rtype) Method(i int) (m Method) { panic("reflect: Method index out of range") } p := methods[i] - pname := t.nameOff(p.name) - m.Name = pname.name() + pname := t.nameOff(p.Name) + m.Name = pname.Name() fl := flag(Func) - mtyp := t.typeOff(p.mtyp) + mtyp := t.typeOff(p.Mtyp) ft := (*funcType)(unsafe.Pointer(mtyp)) - in := make([]Type, 0, 1+len(ft.in())) + in := make([]Type, 0, 1+ft.NumIn()) in = append(in, t) - for _, arg := range ft.in() { - in = append(in, arg) + for _, arg := range ft.InSlice() { + in = append(in, toRType(arg)) } - out := make([]Type, 0, len(ft.out())) - for _, ret := range ft.out() { - out = append(out, ret) + out := make([]Type, 0, ft.NumOut()) + for _, ret := range ft.OutSlice() { + out = append(out, toRType(ret)) } mt := FuncOf(in, out, ft.IsVariadic()) m.Type = mt - prop := js.Global.Call("$methodSet", js.InternalObject(t).Get("jsType")).Index(i).Get("prop").String() + prop := js.Global.Call("$methodSet", jsType(t)).Index(i).Get("prop").String() fn := js.MakeFunc(func(this *js.Object, arguments []*js.Object) any { rcvr := arguments[0] return rcvr.Get(prop).Call("apply", rcvr, arguments[1:]) }) - m.Func = Value{mt.(*rtype), unsafe.Pointer(fn.Unsafe()), fl} + m.Func = Value{toAbiType(mt), unsafe.Pointer(fn.Unsafe()), fl} m.Index = i return m } +//gopherjs:new func (v Value) object() *js.Object { - if v.typ.Kind() == Array || v.typ.Kind() == Struct { + if v.typ().Kind() == abi.Array || v.typ().Kind() == abi.Struct { return js.InternalObject(v.ptr) } + jsTyp := v.typ().JsType() if v.flag&flagIndir != 0 { val := js.InternalObject(v.ptr).Call("$get") - if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsType(v.typ) { - switch v.typ.Kind() { - case Uint64, Int64: - val = jsType(v.typ).New(val.Get("$high"), val.Get("$low")) - case Complex64, Complex128: - val = jsType(v.typ).New(val.Get("$real"), val.Get("$imag")) - case Slice: + if val != js.Global.Get("$ifaceNil") && val.Get("constructor") != jsTyp { + switch v.typ().Kind() { + case abi.Uint64, abi.Int64: + val = jsTyp.New(val.Get("$high"), val.Get("$low")) + case abi.Complex64, abi.Complex128: + val = jsTyp.New(val.Get("$real"), val.Get("$imag")) + case abi.Slice: if val == val.Get("constructor").Get("nil") { - val = jsType(v.typ).Get("nil") + val = jsTyp.Get("nil") break } - newVal := jsType(v.typ).New(val.Get("$array")) + newVal := jsTyp.New(val.Get("$array")) newVal.Set("$offset", val.Get("$offset")) newVal.Set("$length", val.Get("$length")) newVal.Set("$capacity", val.Get("$capacity")) @@ -1051,20 +861,21 @@ func (v Value) object() *js.Object { return js.InternalObject(v.ptr) } -func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value { +//gopherjs:replace +func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Value { if v.flag&flagMethod != 0 { v = makeMethodValue(context, v) } switch { - case directlyAssignable(dst, v.typ): + case directlyAssignable(dst, v.typ()): // Overwrite type so that they match. // Same memory layout, so no harm done. fl := v.flag&(flagAddr|flagIndir) | v.flag.ro() fl |= flag(dst.Kind()) return Value{dst, v.ptr, fl} - case implements(dst, v.typ): + case implements(dst, v.typ()): if target == nil { target = unsafe_New(dst) } @@ -1081,11 +892,13 @@ func (v Value) assignTo(context string, dst *rtype, target unsafe.Pointer) Value } // Failed. - panic(context + ": value of type " + v.typ.String() + " is not assignable to type " + dst.String()) + panic(context + ": value of type " + v.typ().String() + " is not assignable to type " + dst.String()) } +//gopherjs:new var callHelper = js.Global.Get("$call").Interface().(func(...any) *js.Object) +//gopherjs:replace func (v Value) call(op string, in []Value) []Value { var ( t *funcType @@ -1095,11 +908,11 @@ func (v Value) call(op string, in []Value) []Value { if v.flag&flagMethod != 0 { _, t, fn = methodReceiver(op, v, int(v.flag)>>flagMethodShift) rcvr = v.object() - if isWrapped(v.typ) { - rcvr = jsType(v.typ).New(rcvr) + if v.typ().IsWrapped() { + rcvr = v.typ().JsType().New(rcvr) } } else { - t = (*funcType)(unsafe.Pointer(v.typ)) + t = (*funcType)(unsafe.Pointer(v.typ())) fn = unsafe.Pointer(v.object().Unsafe()) rcvr = js.Undefined } @@ -1137,15 +950,15 @@ func (v Value) call(op string, in []Value) []Value { } } for i := 0; i < n; i++ { - if xt, targ := in[i].Type(), t.In(i); !xt.AssignableTo(targ) { + if xt, targ := in[i].Type(), toRType(t.In(i)); !xt.AssignableTo(targ) { panic("reflect: " + op + " using " + xt.String() + " as type " + targ.String()) } } if !isSlice && t.IsVariadic() { // prepare slice for remaining values m := len(in) - n - slice := MakeSlice(t.In(n), m, m) - elem := t.In(n).Elem() + slice := MakeSlice(toRType(t.In(n)), m, m) + elem := toRType(t.In(n).Elem()) for i := 0; i < m; i++ { x := in[n+i] if xt := x.Type(); !xt.AssignableTo(elem) { @@ -1167,7 +980,7 @@ func (v Value) call(op string, in []Value) []Value { argsArray := js.Global.Get("Array").New(t.NumIn()) for i, arg := range in { - argsArray.SetIndex(i, unwrapJsObject(t.In(i), arg.assignTo("reflect.Value.Call", t.In(i).common(), nil).object())) + argsArray.SetIndex(i, abi.UnwrapJsObject(t.In(i), arg.assignTo("reflect.Value.Call", t.In(i), nil).object())) } results := callHelper(js.InternalObject(fn), rcvr, argsArray) @@ -1175,48 +988,42 @@ func (v Value) call(op string, in []Value) []Value { case 0: return nil case 1: - return []Value{makeValue(t.Out(0), wrapJsObject(t.Out(0), results), 0)} + return []Value{makeValue(t.Out(0), abi.WrapJsObject(t.Out(0), results), 0)} default: ret := make([]Value, nout) for i := range ret { - ret[i] = makeValue(t.Out(i), wrapJsObject(t.Out(i), results.Index(i)), 0) + ret[i] = makeValue(t.Out(i), abi.WrapJsObject(t.Out(i), results.Index(i)), 0) } return ret } } +//gopherjs:replace +func (v Value) Type() Type { + if v.flag != 0 && v.flag&flagMethod == 0 { + return toRType(v.typ_) + } + return v.typeSlow() +} + +//gopherjs:replace func (v Value) Cap() int { k := v.kind() switch k { case Array: - return v.typ.Len() + return v.typ().Len() case Chan, Slice: return v.object().Get("$capacity").Int() case Ptr: - if v.typ.Elem().Kind() == Array { - return v.typ.Elem().Len() + if v.typ().Elem().Kind() == abi.Array { + return v.typ().Elem().Len() } panic("reflect: call of reflect.Value.Cap on ptr to non-array Value") } panic(&ValueError{"reflect.Value.Cap", k}) } -var jsObjectPtr = reflectType(js.Global.Get("$jsObjectPtr")) - -func wrapJsObject(typ Type, val *js.Object) *js.Object { - if typ == jsObjectPtr { - return jsType(jsObjectPtr).New(val) - } - return val -} - -func unwrapJsObject(typ Type, val *js.Object) *js.Object { - if typ == jsObjectPtr { - return val.Get("object") - } - return val -} - +//gopherjs:replace func (v Value) Elem() Value { switch k := v.kind(); k { case Interface: @@ -1224,7 +1031,7 @@ func (v Value) Elem() Value { if val == js.Global.Get("$ifaceNil") { return Value{} } - typ := reflectType(val.Get("constructor")) + typ := abi.ReflectType(val.Get("constructor")) return makeValue(typ, val.Get("$val"), v.flag.ro()) case Ptr: @@ -1232,50 +1039,51 @@ func (v Value) Elem() Value { return Value{} } val := v.object() - tt := (*ptrType)(unsafe.Pointer(v.typ)) + tt := (*ptrType)(unsafe.Pointer(v.typ())) fl := v.flag&flagRO | flagIndir | flagAddr - fl |= flag(tt.elem.Kind()) - return Value{tt.elem, unsafe.Pointer(wrapJsObject(tt.elem, val).Unsafe()), fl} + fl |= flag(tt.Elem.Kind()) + return Value{tt.Elem, unsafe.Pointer(abi.WrapJsObject(tt.Elem, val).Unsafe()), fl} default: panic(&ValueError{"reflect.Value.Elem", k}) } } +//gopherjs:replace func (v Value) Field(i int) Value { if v.kind() != Struct { panic(&ValueError{"reflect.Value.Field", v.kind()}) } - tt := (*structType)(unsafe.Pointer(v.typ)) - if uint(i) >= uint(len(tt.fields)) { + tt := (*structType)(unsafe.Pointer(v.typ())) + if uint(i) >= uint(len(tt.Fields)) { panic("reflect: Field index out of range") } - prop := jsType(v.typ).Get("fields").Index(i).Get("prop").String() - field := &tt.fields[i] - typ := field.typ + prop := v.typ().JsType().Get("fields").Index(i).Get("prop").String() + field := &tt.Fields[i] + typ := field.Typ fl := v.flag&(flagStickyRO|flagIndir|flagAddr) | flag(typ.Kind()) - if !field.name.isExported() { - if field.embedded() { + if !field.Name.IsExported() { + if field.Embedded() { fl |= flagEmbedRO } else { fl |= flagStickyRO } } - if tag := tt.fields[i].name.tag(); tag != "" && i != 0 { - if jsTag := getJsTag(tag); jsTag != "" { + if tag := tt.Fields[i].Name.Tag(); tag != "" && i != 0 { + if jsTag := StructTag(tag).Get(`js`); jsTag != "" { for { v = v.Field(0) - if v.typ == jsObjectPtr { + if v.typ() == abi.JsObjectPtr { o := v.object().Get("object") - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( - js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), jsType(typ)) }), - js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, jsType(typ))) }), + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return js.Global.Call("$internalize", o.Get(jsTag), typ.JsType()) }), + js.InternalObject(func(x *js.Object) { o.Set(jsTag, js.Global.Call("$externalize", x, typ.JsType())) }), ).Unsafe()), fl} } - if v.typ.Kind() == Ptr { + if v.typ().Kind() == abi.Pointer { v = v.Elem() } } @@ -1283,65 +1091,21 @@ func (v Value) Field(i int) Value { } s := js.InternalObject(v.ptr) - if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( - js.InternalObject(func() *js.Object { return wrapJsObject(typ, s.Get(prop)) }), - js.InternalObject(func(x *js.Object) { s.Set(prop, unwrapJsObject(typ, x)) }), + if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, s.Get(prop)) }), + js.InternalObject(func(x *js.Object) { s.Set(prop, abi.UnwrapJsObject(typ, x)) }), ).Unsafe()), fl} } - return makeValue(typ, wrapJsObject(typ, s.Get(prop)), fl) -} - -func getJsTag(tag string) string { - for tag != "" { - // skip leading space - i := 0 - for i < len(tag) && tag[i] == ' ' { - i++ - } - tag = tag[i:] - if tag == "" { - break - } - - // scan to colon. - // a space or a quote is a syntax error - i = 0 - for i < len(tag) && tag[i] != ' ' && tag[i] != ':' && tag[i] != '"' { - i++ - } - if i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { - break - } - name := string(tag[:i]) - tag = tag[i+1:] - - // scan quoted string to find value - i = 1 - for i < len(tag) && tag[i] != '"' { - if tag[i] == '\\' { - i++ - } - i++ - } - if i >= len(tag) { - break - } - qvalue := string(tag[:i+1]) - tag = tag[i+1:] - - if name == "js" { - value, _ := strconv.Unquote(qvalue) - return value - } - } - return "" + return makeValue(typ, abi.WrapJsObject(typ, s.Get(prop)), fl) } +//gopherjs:replace func (v Value) UnsafePointer() unsafe.Pointer { return unsafe.Pointer(v.Pointer()) } +//gopherjs:replace func (v Value) grow(n int) { if n < 0 { panic(`reflect.Value.Grow: negative len`) @@ -1363,66 +1127,97 @@ func (v Value) grow(n int) { // extendSlice is used by native reflect.Append and reflect.AppendSlice // Overridden to avoid the use of `unsafeheader.Slice` since GopherJS // uses different slice implementation. +// +//gopherjs:replace func (v Value) extendSlice(n int) Value { v.mustBeExported() v.mustBe(Slice) s := v.object() - sNil := jsType(v.typ).Get(`nil`) + sNil := v.typ().JsType().Get(`nil`) fl := flagIndir | flag(Slice) if s == sNil && n <= 0 { - return makeValue(v.typ, wrapJsObject(v.typ, sNil), fl) + return makeValue(v.typ(), abi.WrapJsObject(v.typ(), sNil), fl) } - newSlice := jsType(v.typ).New(s.Get("$array")) + newSlice := v.typ().JsType().New(s.Get("$array")) newSlice.Set("$offset", s.Get("$offset")) newSlice.Set("$length", s.Get("$length")) newSlice.Set("$capacity", s.Get("$capacity")) - v2 := makeValue(v.typ, wrapJsObject(v.typ, newSlice), fl) + v2 := makeValue(v.typ(), abi.WrapJsObject(v.typ(), newSlice), fl) v2.grow(n) s2 := v2.object() s2.Set(`$length`, s2.Get(`$length`).Int()+n) return v2 } +//gopherjs:purge Unused because handled inside of Clear +func mapclear(t *abi.Type, m unsafe.Pointer) + +//gopherjs:purge Unused because handled inside of Clear +func typedarrayclear(elemType *abi.Type, ptr unsafe.Pointer, len int) + +//gopherjs:replace +func (v Value) Clear() { + switch v.Kind() { + case Slice: + st := (*sliceType)(unsafe.Pointer(v.typ())) + elem := st.Elem + zeroFn := elem.JsType().Get("zero") + a := js.InternalObject(v.ptr).Call("$get") + offset := a.Get("$offset").Int() + length := a.Get("$length").Int() + array := a.Get("$array") + for i := 0; i < length; i++ { + array.SetIndex(i+offset, zeroFn.Invoke()) + } + case Map: + mapObj := js.InternalObject(v.ptr).Call("$get") + mapObj.Call("clear") + default: + panic(&ValueError{"reflect.Value.Clear", v.Kind()}) + } +} + +//gopherjs:replace func (v Value) Index(i int) Value { switch k := v.kind(); k { case Array: - tt := (*arrayType)(unsafe.Pointer(v.typ)) - if i < 0 || i > int(tt.len) { + tt := v.typ().ArrayType() + if i < 0 || i > int(tt.Len) { panic("reflect: array index out of range") } - typ := tt.elem + typ := tt.Elem fl := v.flag&(flagIndir|flagAddr) | v.flag.ro() | flag(typ.Kind()) a := js.InternalObject(v.ptr) - if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( - js.InternalObject(func() *js.Object { return wrapJsObject(typ, a.Index(i)) }), - js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(typ, x)) }), + if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, a.Index(i)) }), + js.InternalObject(func(x *js.Object) { a.SetIndex(i, abi.UnwrapJsObject(typ, x)) }), ).Unsafe()), fl} } - return makeValue(typ, wrapJsObject(typ, a.Index(i)), fl) + return makeValue(typ, abi.WrapJsObject(typ, a.Index(i)), fl) case Slice: s := v.object() if i < 0 || i >= s.Get("$length").Int() { panic("reflect: slice index out of range") } - tt := (*sliceType)(unsafe.Pointer(v.typ)) - typ := tt.elem + tt := (*sliceType)(unsafe.Pointer(v.typ())) + typ := tt.Elem fl := flagAddr | flagIndir | v.flag.ro() | flag(typ.Kind()) i += s.Get("$offset").Int() a := s.Get("$array") - if fl&flagIndir != 0 && typ.Kind() != Array && typ.Kind() != Struct { - return Value{typ, unsafe.Pointer(jsType(PtrTo(typ)).New( - js.InternalObject(func() *js.Object { return wrapJsObject(typ, a.Index(i)) }), - js.InternalObject(func(x *js.Object) { a.SetIndex(i, unwrapJsObject(typ, x)) }), + if fl&flagIndir != 0 && typ.Kind() != abi.Array && typ.Kind() != abi.Struct { + return Value{typ, unsafe.Pointer(typ.JsPtrTo().New( + js.InternalObject(func() *js.Object { return abi.WrapJsObject(typ, a.Index(i)) }), + js.InternalObject(func(x *js.Object) { a.SetIndex(i, abi.UnwrapJsObject(typ, x)) }), ).Unsafe()), fl} } - return makeValue(typ, wrapJsObject(typ, a.Index(i)), fl) + return makeValue(typ, abi.WrapJsObject(typ, a.Index(i)), fl) case String: str := *(*string)(v.ptr) @@ -1438,19 +1233,22 @@ func (v Value) Index(i int) Value { } } +//gopherjs:replace func (v Value) InterfaceData() [2]uintptr { panic(errors.New("InterfaceData is not supported by GopherJS")) } +//gopherjs:replace func (v Value) SetZero() { v.mustBeAssignable() - v.Set(Zero(v.typ)) + v.Set(Zero(toRType(v.typ()))) } +//gopherjs:replace func (v Value) IsNil() bool { switch k := v.kind(); k { case Ptr, Slice: - return v.object() == jsType(v.typ).Get("nil") + return v.object() == v.typ().JsType().Get("nil") case Chan: return v.object() == js.Global.Get("$chanNil") case Func: @@ -1466,6 +1264,7 @@ func (v Value) IsNil() bool { } } +//gopherjs:replace func (v Value) Len() int { switch k := v.kind(); k { case Array, String: @@ -1477,8 +1276,8 @@ func (v Value) Len() int { case Map: return v.object().Get("size").Int() case Ptr: - if v.typ.Elem().Kind() == Array { - return v.typ.Elem().Len() + if elem := v.typ().Elem(); elem.Kind() == abi.Array { + return elem.Len() } panic("reflect: call of reflect.Value.Len on ptr to non-array Value") default: @@ -1489,6 +1288,7 @@ func (v Value) Len() int { //gopherjs:purge Not used since Len() is overridden. func (v Value) lenNonSlice() int +//gopherjs:replace func (v Value) Pointer() uintptr { switch k := v.kind(); k { case Chan, Map, Ptr, UnsafePointer: @@ -1511,15 +1311,16 @@ func (v Value) Pointer() uintptr { } } +//gopherjs:replace func (v Value) Set(x Value) { v.mustBeAssignable() x.mustBeExported() - x = x.assignTo("reflect.Set", v.typ, nil) + x = x.assignTo("reflect.Set", v.typ(), nil) if v.flag&flagIndir != 0 { - switch v.typ.Kind() { - case Array, Struct: - jsType(v.typ).Call("copy", js.InternalObject(v.ptr), js.InternalObject(x.ptr)) - case Interface: + switch v.typ().Kind() { + case abi.Array, abi.Struct: + v.typ().JsType().Call("copy", js.InternalObject(v.ptr), js.InternalObject(x.ptr)) + case abi.Interface: js.InternalObject(v.ptr).Call("$set", js.InternalObject(valueInterface(x, false))) default: js.InternalObject(v.ptr).Call("$set", x.object()) @@ -1529,15 +1330,16 @@ func (v Value) Set(x Value) { v.ptr = x.ptr } +//gopherjs:replace func (v Value) bytesSlow() []byte { switch v.kind() { case Slice: - if v.typ.Elem().Kind() != Uint8 { + if v.typ().Elem().Kind() != abi.Uint8 { panic("reflect.Value.Bytes of non-byte slice") } return *(*[]byte)(v.ptr) case Array: - if v.typ.Elem().Kind() != Uint8 { + if v.typ().Elem().Kind() != abi.Uint8 { panic("reflect.Value.Bytes of non-byte array") } if !v.CanAddr() { @@ -1552,15 +1354,16 @@ func (v Value) bytesSlow() []byte { panic(&ValueError{"reflect.Value.Bytes", v.kind()}) } +//gopherjs:replace func (v Value) SetBytes(x []byte) { v.mustBeAssignable() v.mustBe(Slice) - if v.typ.Elem().Kind() != Uint8 { + if v.typ().Elem().Kind() != abi.Uint8 { panic("reflect.Value.SetBytes of non-byte slice") } slice := js.InternalObject(x) - if v.typ.Name() != "" || v.typ.Elem().Name() != "" { - typedSlice := jsType(v.typ).New(slice.Get("$array")) + if toRType(v.typ()).Name() != "" || toRType(v.typ()).Elem().Name() != "" { + typedSlice := v.typ().JsType().New(slice.Get("$array")) typedSlice.Set("$offset", slice.Get("$offset")) typedSlice.Set("$length", slice.Get("$length")) typedSlice.Set("$capacity", slice.Get("$capacity")) @@ -1569,6 +1372,7 @@ func (v Value) SetBytes(x []byte) { js.InternalObject(v.ptr).Call("$set", slice) } +//gopherjs:replace func (v Value) SetCap(n int) { v.mustBeAssignable() v.mustBe(Slice) @@ -1576,13 +1380,14 @@ func (v Value) SetCap(n int) { if n < s.Get("$length").Int() || n > s.Get("$capacity").Int() { panic("reflect: slice capacity out of range in SetCap") } - newSlice := jsType(v.typ).New(s.Get("$array")) + newSlice := v.typ().JsType().New(s.Get("$array")) newSlice.Set("$offset", s.Get("$offset")) newSlice.Set("$length", s.Get("$length")) newSlice.Set("$capacity", n) js.InternalObject(v.ptr).Call("$set", newSlice) } +//gopherjs:replace func (v Value) SetLen(n int) { v.mustBeAssignable() v.mustBe(Slice) @@ -1590,17 +1395,18 @@ func (v Value) SetLen(n int) { if n < 0 || n > s.Get("$capacity").Int() { panic("reflect: slice length out of range in SetLen") } - newSlice := jsType(v.typ).New(s.Get("$array")) + newSlice := v.typ().JsType().New(s.Get("$array")) newSlice.Set("$offset", s.Get("$offset")) newSlice.Set("$length", n) newSlice.Set("$capacity", s.Get("$capacity")) js.InternalObject(v.ptr).Call("$set", newSlice) } +//gopherjs:replace func (v Value) Slice(i, j int) Value { var ( cap int - typ Type + typ *abi.Type s *js.Object ) switch kind := v.kind(); kind { @@ -1608,13 +1414,13 @@ func (v Value) Slice(i, j int) Value { if v.flag&flagAddr == 0 { panic("reflect.Value.Slice: slice of unaddressable array") } - tt := (*arrayType)(unsafe.Pointer(v.typ)) - cap = int(tt.len) - typ = SliceOf(tt.elem) - s = jsType(typ).New(v.object()) + tt := (*arrayType)(unsafe.Pointer(v.typ())) + cap = int(tt.Len) + typ = SliceOf(toRType(tt.Elem)).common() + s = typ.JsType().New(v.object()) case Slice: - typ = v.typ + typ = v.typ() s = v.object() cap = s.Get("$capacity").Int() @@ -1636,10 +1442,11 @@ func (v Value) Slice(i, j int) Value { return makeValue(typ, js.Global.Call("$subslice", s, i, j), v.flag.ro()) } +//gopherjs:replace func (v Value) Slice3(i, j, k int) Value { var ( cap int - typ Type + typ *abi.Type s *js.Object ) switch kind := v.kind(); kind { @@ -1647,13 +1454,13 @@ func (v Value) Slice3(i, j, k int) Value { if v.flag&flagAddr == 0 { panic("reflect.Value.Slice: slice of unaddressable array") } - tt := (*arrayType)(unsafe.Pointer(v.typ)) - cap = int(tt.len) - typ = SliceOf(tt.elem) - s = jsType(typ).New(v.object()) + tt := (*arrayType)(unsafe.Pointer(v.typ())) + cap = int(tt.Len) + typ = SliceOf(toRType(tt.Elem)).common() + s = typ.JsType().New(v.object()) case Slice: - typ = v.typ + typ = v.typ() s = v.object() cap = s.Get("$capacity").Int() @@ -1668,14 +1475,17 @@ func (v Value) Slice3(i, j, k int) Value { return makeValue(typ, js.Global.Call("$subslice", s, i, j, k), v.flag.ro()) } +//gopherjs:replace func (v Value) Close() { v.mustBe(Chan) v.mustBeExported() js.Global.Call("$close", v.object()) } +//gopherjs:new var selectHelper = js.Global.Get("$select").Interface().(func(...any) *js.Object) +//gopherjs:replace func chanrecv(ch unsafe.Pointer, nb bool, val unsafe.Pointer) (selected, received bool) { comms := [][]*js.Object{{js.InternalObject(ch)}} if nb { @@ -1690,6 +1500,7 @@ func chanrecv(ch unsafe.Pointer, nb bool, val unsafe.Pointer) (selected, receive return true, recvRes.Index(1).Bool() } +//gopherjs:replace func chansend(ch unsafe.Pointer, val unsafe.Pointer, nb bool) bool { comms := [][]*js.Object{{js.InternalObject(ch), js.InternalObject(val).Call("$get")}} if nb { @@ -1702,6 +1513,7 @@ func chansend(ch unsafe.Pointer, val unsafe.Pointer, nb bool) bool { return true } +//gopherjs:replace func rselect(rselects []runtimeSelect) (chosen int, recvOK bool) { comms := make([][]*js.Object, len(rselects)) for i, s := range rselects { @@ -1734,6 +1546,7 @@ func rselect(rselects []runtimeSelect) (chosen int, recvOK bool) { return c, false } +//gopherjs:replace func DeepEqual(a1, a2 any) bool { i1 := js.InternalObject(a1) i2 := js.InternalObject(a2) @@ -1746,6 +1559,7 @@ func DeepEqual(a1, a2 any) bool { return deepValueEqualJs(ValueOf(a1), ValueOf(a2), nil) } +//gopherjs:new func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { if !v1.IsValid() || !v2.IsValid() { return !v1.IsValid() && !v2.IsValid() @@ -1753,8 +1567,8 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { if v1.Type() != v2.Type() { return false } - if v1.Type() == jsObjectPtr { - return unwrapJsObject(jsObjectPtr, v1.object()) == unwrapJsObject(jsObjectPtr, v2.object()) + if v1.typ() == abi.JsObjectPtr { + return abi.UnwrapJsObject(abi.JsObjectPtr, v1.object()) == abi.UnwrapJsObject(abi.JsObjectPtr, v2.object()) } switch v1.Kind() { @@ -1830,6 +1644,7 @@ func deepValueEqualJs(v1, v2 Value, visited [][2]unsafe.Pointer) bool { return js.Global.Call("$interfaceIsEqual", js.InternalObject(valueInterface(v1, false)), js.InternalObject(valueInterface(v2, false))).Bool() } +//gopherjs:new func stringsLastIndex(s string, c byte) int { for i := len(s) - 1; i >= 0; i-- { if s[i] == c { @@ -1839,10 +1654,12 @@ func stringsLastIndex(s string, c byte) int { return -1 } +//gopherjs:new func stringsHasPrefix(s, prefix string) bool { return len(s) >= len(prefix) && s[:len(prefix)] == prefix } +//gopherjs:replace func verifyNotInHeapPtr(p uintptr) bool { // Go runtime uses this method to make sure that a uintptr won't crash GC if // interpreted as a heap pointer. This is not relevant for GopherJS, so we can @@ -1853,9 +1670,108 @@ func verifyNotInHeapPtr(p uintptr) bool { // typedslicecopy is implemented in prelude.js as $copySlice // //gopherjs:purge -func typedslicecopy(elemType *rtype, dst, src unsafeheader.Slice) int +func typedslicecopy(t *abi.Type, dst, src unsafeheader.Slice) int // growslice is implemented in prelude.js as $growSlice. // //gopherjs:purge -func growslice(t *rtype, old unsafeheader.Slice, num int) unsafeheader.Slice +func growslice(t *abi.Type, old unsafeheader.Slice, num int) unsafeheader.Slice + +//gopherjs:purge This is the header for an any interface and invalid for GopherJS. +type emptyInterface struct{} + +//gopherjs:purge This is the header for an interface value with methods and invalid for GopherJS. +type nonEmptyInterface struct{} + +//gopherjs:purge +func packEface(v Value) any + +//gopherjs:purge +func unpackEface(i any) Value + +//gopherjs:purge +func storeRcvr(v Value, p unsafe.Pointer) + +//gopherjs:purge +func callMethod(ctxt *methodValue, frame unsafe.Pointer, retValid *bool, regs *abi.RegArgs) + +// gopherjs:replace +func noescape(p unsafe.Pointer) unsafe.Pointer { + return p +} + +//gopherjs:purge +func makeFuncStub() + +//gopherjs:purge Unused type +type common struct{} + +//gopherjs:purge Used in original MapOf and not used in override MapOf by GopherJS +func bucketOf(ktyp, etyp *abi.Type) *abi.Type + +//gopherjs:purge Unused type +const debugReflectCall = false + +//gopherjs:purge Relates to GC programs not valid for GopherJS +func (t *rtype) gcSlice(begin, end uintptr) []byte + +//gopherjs:purge Relates to GC programs not valid for GopherJS +func emitGCMask(out []byte, base uintptr, typ *abi.Type, n uintptr) + +//gopherjs:purge Relates to GC programs not valid for GopherJS +func appendGCProg(dst []byte, typ *abi.Type) []byte + +// toKindTypeExt will be automatically called when a cast to one of the +// extended kind types is performed. +// +// This is similar to `kindType` except that the reflect package has +// extended several of the kind types to have additional methods added to them. +// To get access to those methods, the `kindTypeExt` is checked or created. +// The automatic cast is handled in compiler/expressions.go +// +// gopherjs:new +func toKindTypeExt(src any) *js.Object { + var abiTyp *abi.Type + switch t := src.(type) { + case *rtype: + abiTyp = t.common() + case *abi.Type: + abiTyp = t + default: + panic(`unexpected type in toKindTypeExt`) + } + + const ( + idKindType = `kindType` + idKindTypeExt = `kindTypeExt` + ) + // Check if a kindTypeExt has already been created for this type. + ext := js.InternalObject(abiTyp).Get(idKindTypeExt) + if ext != js.Undefined { + return ext + } + + // Construct a new kindTypeExt for this type. + kindType := js.InternalObject(abiTyp).Get(idKindType) + switch abiTyp.Kind() { + case abi.Interface: + ext = js.InternalObject(&interfaceType{}) + ext.Set(`InterfaceType`, js.InternalObject(kindType)) + case abi.Map: + ext = js.InternalObject(&mapType{}) + ext.Set(`MapType`, js.InternalObject(kindType)) + case abi.Pointer: + ext = js.InternalObject(&ptrType{}) + ext.Set(`PtrType`, js.InternalObject(kindType)) + case abi.Slice: + ext = js.InternalObject(&sliceType{}) + ext.Set(`SliceType`, js.InternalObject(kindType)) + case abi.Struct: + ext = js.InternalObject(&structType{}) + ext.Set(`StructType`, js.InternalObject(kindType)) + default: + panic(`unexpected kind in toKindTypeExt`) + } + js.InternalObject(abiTyp).Set(idKindTypeExt, ext) + return ext +} diff --git a/compiler/natives/src/reflect/swapper.go b/compiler/natives/src/reflect/swapper.go index aca5ef371..e6b1ab27a 100644 --- a/compiler/natives/src/reflect/swapper.go +++ b/compiler/natives/src/reflect/swapper.go @@ -4,6 +4,7 @@ package reflect import "github.com/gopherjs/gopherjs/js" +//gopherjs:replace func Swapper(slice any) func(i, j int) { v := ValueOf(slice) if v.Kind() != Slice {