Stack-based icall parameter handles and only one managed/native transition per icall...
commite8b037642104527bd9b9ba70d502210b9c12d2b8
authorJay Krell <jay.krell@cornell.edu>
Wed, 24 Oct 2018 16:38:19 +0000 (24 09:38 -0700)
committerGitHub <noreply@github.com>
Wed, 24 Oct 2018 16:38:19 +0000 (24 09:38 -0700)
tree3ecc5bf0883d6984313704d47de48a046fc8011f
parent38caaf67b1048273326139b2200f69ad45229638
Stack-based icall parameter handles and only one managed/native transition per icall. (#11294)

Handles for icall parameters allocated in managed (but not native), can just be pointers to locals or parameters.

Currently the managed stack is scanned conservatively.
When this changes, there will be metadata provided to find these, so this approach will just keep working and should still be ideal (aside from nohandles or staying in managed).

For ObjIn, use pointer to parameter.
For ObjOut/ObjInOut, the parameter might, though it is far fetched, point to a native frame, which isn't convincing. Therefore for those, use a pointer to a managed volatile local, closer to how handles looked like.
For ValueTypeRef, make the parameter volatile so it is sure to survive.

We use volatile instead of merely address-taken because 1. a hypothetical aggressive whole program compiler/optimizer could see through address-taken (runtime and wrappers and corlib compiled together).  2. ValuetypeRef is even easier to optimize/break because the address-taken is  dead.

FIXME: Tests that exercise every icall, coop and otherwise.
FIXME: Gcstress.

Collateral damage: The one rarely run assert that handles are not out of scope
cannot be so easily implemented so is removed. It might come back though, if we type tag the handles.

Furthermore, the handle stack setup/cleanup can be done in native instead of managed.
Furthermore, the error check can either be done in native instead of managed, or the fast path inlined in the JIT. Here we just move it to native (and inline the fast path in native).

Therefore coop icalls go from 3 + handle-count managed/native transitions, to just always one,  *and* at least some of their handle allocation is exceedingly fast and free of TLS.

The typed handle wrappers change from returning a handle to returning a raw pointer.
Because previously the handle would be allocated in the handle frame setup by the JIT.
Now the JIT doesn't setup a handle frame.

There is a weakreference test that is seemingly very sensitive to stack layout.
For this reason at least, the MonoError is still allocated on the managed side.
 Moving it to native broke the test.
This might also be preferable for the public interface.

Coop icalls retain some costs:
  - The volatile might cost a little -- lack of enregistration.
  - Native code allocating handles.
  - Still TLS access.
  - JIT icalls (vs. metadata-driven) still native-only handle allocation, and still fully manual conversion.
  - Coop icalls that return an object always allocate a handle -- could be more efficient out or inout handle instead, though inout is currently very rare.

Some of this can be further mitigated.
Mainly, the metadata icall signatures could move the "local" handles  to be out parameters, in order for managed to allocate them.

The JIT icalls could have richer signatures than just "ptr" and gain these wrappers, or previous (ref parameters, retyped as handles).
18 files changed:
mcs/class/corlib/LinkerDescriptor/mscorlib.xml
mcs/class/corlib/Mono/RuntimeStructs.cs
mono/eglib/glib.h
mono/metadata/exception.c
mono/metadata/handle-decl.h
mono/metadata/handle.c
mono/metadata/handle.h
mono/metadata/icall-table.h
mono/metadata/marshal-ilgen.c
mono/metadata/marshal.c
mono/metadata/marshal.h
mono/metadata/metadata-internals.h
mono/metadata/method-builder-ilgen-internals.h
mono/metadata/method-builder-ilgen.c
mono/metadata/object-internals.h
mono/mini/mini.c
mono/utils/monobitset.c
mono/utils/monobitset.h