Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve reflection #1727

Merged
merged 5 commits into from
Mar 23, 2021
Merged

Improve reflection #1727

merged 5 commits into from
Mar 23, 2021

Conversation

aykevl
Copy link
Member

@aykevl aykevl commented Mar 18, 2021

This is a big PR (based on #1612) that improves reflect support significantly:

  1. compiler: merge runtime.typecodeID and runtime.typeInInterface: this commit makes reflect support more correct, see testdata/reflect.go for a case where it behaved incorrectly before. It increases code size in many cases.
  2. compiler: do not check for impossible type asserts: reduces the code size in many cases introduced in the previous commit.
  3. interp: handle (reflect.Type).Elem(): prepare for the last commit. Shaves off a few bytes in many cases.
  4. interp: add support for runtime.interfaceMethod: also prepares for the last commit.
  5. reflect: let reflect.Type be of interface type: this is the big one. The encoding/json package relies on being able to compare reflect.Type variables with nil, which this commit makes possible by changing reflect.Type to an interface type (like standard Go). All the previous commits are needed to reduce the associated code size increase. There is still some code size increase, but it's only for some bigger programs (for example, that use the fmt package).

One side effect of this PR (in commit 2) is that it improves (but does not fix) issue #1415 when you're printing error values (e.g. err := ...; println(err)).

This is a draft until #1612 is merged. I think it would be possible to separate this PR from #1612 but that might make the first commit more complicated (it was causing linker errors until I rebased on top of #1612).

@johanbrandhorst
Copy link
Member

johanbrandhorst commented Mar 19, 2021

This might be interesting for running Vecty on TinyGo webassembly (#1282, hexops/vecty/issues/269) CC @slimsag @marwan-at-work.

@aykevl
Copy link
Member Author

aykevl commented Mar 19, 2021

@johanbrandhorst it'll probably help with Vecty support, but I think support won't be complete yet. For example, reflect.New is not yet supported.

Things I'd like to work on after this PR is merged:

  • (reflect.Type).Implements. I'm not yet entirely sure how to implement this. It is required for encoding/json support. I might just rely on the fact that in almost all cases the interface is known and it can be converted to an interface type assert.
  • reflect.New, this is a little bit complicated because it requires some internal changes, see Trying to implement reflect.New() #1087.
  • Improved dead code elimination using LLVM type metadata (if possible).

@marwan-at-work
Copy link

@aykevl thank you and cannot wait for TinyGo to support Vecty :)

@aykevl aykevl marked this pull request as ready for review March 21, 2021 12:31
@aykevl
Copy link
Member Author

aykevl commented Mar 21, 2021

Rebased after #1612 was merged.

interp/interpreter.go Outdated Show resolved Hide resolved
This distinction was useful before when reflect wasn't properly
supported. Back then it made sense to only include method sets that were
actually used in an interface. But now that it is possible to get to
other values (for example, by extracting fields from structs) and it is
possible to turn them back into interfaces, it is necessary to preserve
all method sets that can possibly be used in the program in a type
assert, interface assert or interface method call.

In the future, this logic will need to be revisited again when
reflect.New or reflect.Zero gets implemented.

Code size increases a bit in some cases, but usually in a very limited
way (except for one outlier in the drivers smoke tests). The next commit
will improve the situation significantly.
Previously there was code to avoid impossible type asserts but it wasn't
great and in fact was too aggressive when combined with reflection.

This commit improves this by checking all types that exist in the
program that may appear in an interface (even struct fields and the
like) but without creating runtime.typecodeID objects with the type
assert. This has two advantages:

  * As mentioned, it optimizes impossible type asserts away.
  * It allows methods on types that were only asserted on (in
    runtime.typeAssert) but never used in an interface to be optimized
    away using GlobalDCE. This may have a cascading effect so that other
    parts of the code can be further optimized.

This sometimes massively improves code size and mostly negates the code
size regression of the previous commit.
The errors package has a call like this in the package initializer. This
commit adds support for running it at compile time, avoiding the call at
runtime.

This doesn't always help (the call is already optimized away in many
small programs) but it does help to shave off some binary size in larger
programs. Perhaps more importantly, it will avoid a penalty in code size
when the reflect package will convert reflect.Type from a regular type
to an interface type.
This is necessary so that when reflect.Type is converted from a concrete
type to an interface type, the errors package can still be interpreted.
Without this change, basically every program would grow in size by a few
bytes.
This matches the main Go implementation and (among others) fixes a
compatibility issue with the encoding/json package. The encoding/json
package compares reflect.Type variables against nil, which does not work
as long as reflect.Type is of integer type.

This also adds a reflect.RawType() function (like reflect.Type()) that
makes it easier to avoid working with interfaces in the runtime package.
It is internal only, but exported to let the runtime package use it.

This change introduces a small code size increase when working with the
reflect package, but I've tried to keep it to a minimum. Most programs
that don't make extensive use of the reflect package (and don't use
package like fmt) should not be impacted by this.
@aykevl
Copy link
Member Author

aykevl commented Mar 23, 2021

Updated with suggestion from @niaow and rebased on the dev branch to fix a merge conflict.

@deadprogram
Copy link
Member

This is very exciting PR right here. 🔥

Now merging.

@deadprogram deadprogram merged commit c849bcc into dev Mar 23, 2021
@deadprogram deadprogram deleted the reflect-type-itf branch March 23, 2021 13:32
@marwan-at-work
Copy link

@deadprogram @aykevl thank you both ❤️

Is it worth just waiting until reflect.New is implemented before thoroughly testing Vecty?

Thanks!

@aykevl
Copy link
Member Author

aykevl commented Mar 23, 2021

@marwan-at-work not sure, I haven't really done anything with Vecty yet. You can of course try it.

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants