1
0
Fork 0
mirror of https://github.com/VSadov/Satori.git synced 2025-06-11 18:20:26 +09:00
Satori/docs/design/mono/components.md
Aleksey Kliger (λgeek) 893739ffe7
[mono] new Sdk that selects mono runtime components (#54432)
* Work in progress: new Sdk that selects mono runtime components

* Add props and targets description to the components doc

* condition the _MonoRuntimeAvailableComponents by RuntimeIdentifier

* [cmake] Write a component-manifest.props file during build

If we're not building a mono aot cross compiler, generate a
component-manifest.props file in artifacts/obj/mono/<host>/ that indicates if
the host will use static or dynamic components, and a list of the available
components, and the properties for constructing their names.

* Build Microsoft.NETCore.App.Runtime.Mono.<RID>.Sdk shared framework nuget

It seems to also generate a symbols nuget.  And in the nuget there's a
tools/mono-sdk-what-is-this.deps.json file from the
SharedFrameworkHostFileNameOverride property.  It would be nice to exclude that
stuff.

* put the compoonent-manifest.targets into the Sdk

* delete WIP in mono/nuget/

* fixup static component names in component-manifest.targets

* delete fixed fixme

* add missing $

* fix whitespace

* [cmake] switch to configure_file instead of file(CONFIGURE)

* add missing trailing slashes in .props.in file

* Add new Sdk packs to the workload manifest

* rework component-manifest.targets to use ItemGroups; move to new SDK

* Rename shared framework to Microsoft.NETCore.App.Runtime.Mono.<RID>.Props.Sdk

And only include component-manifest.props, not the targets

* Update manifest to include the new Props.Sdk and MonoTargets.Sdk

* Move RuntimeConfigParserTask into Microsoft.NET.Runtime.MonoTargets.Sdk

Consolidate all platform-independent tasks and targets into a single Sdk

* Add iossimulator-x86 props

* update components design doc

* Fix typo

* improve docs

* Add _MonoRuntimeComponentDontLink target output

* Drop component-manifest.props into runtime pack build/ directory

Remove from the Microsoft.NETCore.App.Mono.Props.Sdk workload nuget

* remove Microsoft.NETCore.App.Mono.Props.Sdk

* Import component-manifest.props from the runtime pack

Move the MonoTargets.Sdk import to each target platform that we support

* Fix typos

Co-authored-by: Rolf Bjarne Kvinge <rolf@xamarin.com>

* Apply suggestions from code review

Co-authored-by: Ankit Jain <radical@gmail.com>

* Add JsonToItemsTaskFactory

* fix whitespace

* Do some validation earlier in _MonoComputeAvailableComponentDefinitions

* Read component-manifest.json using the JsonToItemsTaskFactory

and bundle it in Microsoft.NET.Runtime.MonoTargets.Sdk

* remove ResolvedRuntimePack import from WorkloadManifest.targets

it's too early, and we have the JsonToItemsTaskFactory now to read the manifest

* Generate component-manifest.json in CMakeLists.txt

* Fix some copy-paste nits

* Use RuntimeFlavor==mono for runtime pack build directory

instead of TargetsMobile.  We want the build files (mono-components.json)
in every mono runtime pack, not just on mobile targets

* Apply suggestions from code review

Co-authored-by: Ankit Jain <radical@gmail.com>

* rename component-manifest to RuntimeComponentManifest

* fixup nullability annotations

* fix whitespace

* fix formatting

* Misc fixes to JsonToItemsTaskFactory

* Rename MonoRuntimeComponentManifestReadTask

from MonoRuntimeComponentsReadManifestTask

* undo nullability annotation

Build doesn't like it for some reason (probably net472)

* fix incorrect task parameter name

* Remove Identity metadata from dictionary at json parsing time

Also improve nullability a bit by making the properties immutable

* Throw correct json deserializer exceptions

* Catch JsonException in an async function

We get nice error messages now like

```
src/mono/nuget/Microsoft.NET.Runtime.MonoTargets.Sdk/Sdk/RuntimeComponentManifest.targets(8,5): error : Failed to deserialize json from file 'artifacts/bin/mono/iOSSimulator.x64.Release/build/RuntimeComponentManifest.json', JSON Path: $.items._MonoRuntimeAvailableComponents[2], Line: 14, Position: 1 [component-manifest.sample.proj]
src/mono/nuget/Microsoft.NET.Runtime.MonoTargets.Sdk/Sdk/RuntimeComponentManifest.targets(8,5): error : JsonException: The JSON value could not be converted to System.Collections.Generic.Dictionary`2[System.String,System.String]. Path: $.identity | LineNumber: 0 | BytePositionInLine: 16. Path: $.items._MonoRuntimeAvailableComponents[2] | LineNumber: 14 | BytePositionInLine: 1. [component-manifest.sample.proj]
src/mono/nuget/Microsoft.NET.Runtime.MonoTargets.Sdk/Sdk/RuntimeComponentManifest.targets(8,5): error : InvalidOperationException: Cannot get the value of a token type 'Number' as a string. [component-manifest.sample.proj]
src/mono/nuget/Microsoft.NET.Runtime.MonoTargets.Sdk/Sdk/RuntimeComponentManifest.targets(8,5):
error :
[component-manifest.sample.proj]
```

* fixup comments

Co-authored-by: Rolf Bjarne Kvinge <rolf@xamarin.com>
Co-authored-by: Ankit Jain <radical@gmail.com>
2021-07-08 00:49:33 +00:00

330 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# MonoVM Runtime Components
## Summary
MonoVM runtime components are units of optional functionality that may be provided by the runtime to some workloads.
The idea is to provide some components as optional units that can be loaded dynamically (on workloads that support
dynamic loading) or that are statically linked into the final application (on workloads that support only static
linking).
To that end this document describes a methodology for defining and implementing components and for calling component
functionality from the runtime.
## Goals and scenarios
Breaking up the runtime into components allows us to pursue two goals:
1. Provide workloads with the ability to ship a runtime that
contains only the required capabilities and excludes native code for
unsupported operations.
2. Reduce the number of different build configurations and reduce the build
complexity by allowing composition to happen closer to application execution
time, instead of requiring custom builds of the runtime.
For example, each of the following experiences requires different runtime
capabilities:
- Developer inner loop on on a mobile or WebAssembly workload: The runtime
should include support for the interpreter, hot reload, and the diagnostic
server.
- Release build iPhone app for the app store: The runtime should not include the
interpreter, hot reload, or the diagnostic server.
- Release build iPhone app with `System.Reflection.Emit` support: The runtime
should include the interpreter, but not hot reload or the diagnostic server.
- Line of business Android app company-wide internal beta: The runtime should
not include interpreter support or hot reload, but should include the
diagnostic server.
## Building Components
For each workload we choose one of two strategies for building the runtime and
the components: we either build the components as shared libraries that are
loaded by the runtime at execution time; or we build the components as static
libraries that are statically linked together with the runtime (static
library), and the application host. We assume that workloads that would
utilize static linking already do native linking of the application host and
the runtime as part of the app build.
The choice of which strategy to pursue depends on platform capabilities. For
example there is no dynamic linking on WebAssembly at this time, so static
linking is the only option. On iOS dynamic linking is supported for debug
scenarios, but is disallowed in apps that want to be published to the Apple App
Store. Thus for ios release builds we must use static linking.
We can summarize the different options:
| Scenario(s) | Dynamic loading allowed | Component build strategy | Disabled components |
| ------------------------------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
| Console, Android,<br>ios simulator,<br>ios device debug | yes | component stubs in runtime; component in shared libraries next to runtime; dlopen to load. | Just leave out the component shared library from the app bundle. |
| webassembly,<br>ios device release | no | component stubs in runtime; components in static libraries; embedding API calls to register non-stubs | Dont link the component static libs.<br>Leave out the embedding API registration call. |
## High level overview
Each component is defined in `src/mono/mono/components`.
The runtime is compiled for different configurations with either static or dynamic linking of components.
When the components are dynamically linked, each component produces a shared library `mono-component-*component-name*`.
When the components are statically linked, each component produces a static library.
The choice of dynamic or static linking is a global compile-time configuration parameter of the runtime: either all
components are dynamic or they're all static. In either case, each component may be either present or stubbed out at execution time.
With dynamic linking, all the stubs are built into the runtime, and the runtime probes for the dynamic library of each
component to see if it is present, or else it calls the stub component.
With static linking, all the present components are statically linked with the runtime into the final app.
Each component exposes a table of functions to the runtime. The stubs also implement the same table. In the places
where it makes sense, the runtime may call to get the function table and call the methods.
When a component implementation needs to call the runtime, it may call only functions that are marked
`MONO_COMPONENT_API` (or `MONO_API` - as long as it is not `MONO_RT_EXTERNAL_ONLY` - same as what is allowed for normal
runtime internal functions).
Components, their vtables, etc are not versioned. The runtime and the components together represent a single
indivisible unit. Mixing components from different versions of the runtime is not supported.
## Detailed design - C code organization
### Base component contract
Each component may use the following types and preprocessor definitions:
- (from `mono/component/component.h`) `MonoComponent` a struct that is the "base vtable" of all components. It provides a single member `cleanup` that each component must implement.
- The component cleanup function should be prepared to be called multiple
times. Second and subsequent calls should be ignored.
- (from `mono/utils/mono-compiler.h`) `MONO_COMPONENT_API` when a component
needs to call a runtime function that is not part of the public Mono API, it
can only call a `MONO_COMPONENT_API` function. Care should be taken to use
the most general version of a group of functions so that we can keep the
total number of exposed functions to a minimum.
- (from `mono/utils/mono-compiler.h`) `MONO_COMPONENT_EXPORT_ENTRYPOINT` each
component must expose a function named `mono_component_<component_name>_init`
that is tagged with this macro. When the component is compiled dynamically,
the build will ensure that the entrypoint is exported and visible.
- (set by cmake) `COMPILING_COMPONENT_DYNAMIC` defined if the component is
being compiled into a shared library. Generally components don't need to
explicitly act on this define.
- (set by cmake) `STATIC_COMPONENTS` defined if all components are being
compiled statically. If this is set, the component stub should export an
entrypoint with the name `mono_component_<component_name>_init` rather than
`mono_component_<component_name>_stub_init`.
### To implement a component
To implement `feature_X` as a component. Carry out the following steps:
* Add a new entry to the `components` list in `src/mono/mono/component/CMakeLists.txt`:
```
list(APPEND components
feature_X
)
```
* Add a new list `feature_X-sources_base` to `src/mono/mono/component/CMakeLists.txt` that lists the source files of the component:
```
set(feature_X-sources_base feature_X.h feature_X.c)
```
* Add a new list `feature_X-stub-sources_base` to `src/mono/mono/component/CMakeLists.txt` that lists the source files for the component stub:
```
set(feature_X-stub-sources_base feature_X-stub.c)
```
* Declare a struct `_MonoComponentFeatureX` in `src/mono/mono/component/feature_X.h`
```
typedef struct _MonoComponentFeatureX {
MonoComponent component; /* First member _must_ be MonoComponent */
void (*hello)(void); /* Additional function pointers for each method for feature_X */
} MonoComponentFeatureX;
```
* Declare an entrypoint `mono_component_feature_X_init` in `src/mono/mono/component/feature_X.h`
that takes no arguments and returns the component vtable:
```
#ifdef STATIC_COMPONENTS
MONO_COMPONENT_EXPORT_ENTRYPOINT
MonoComponentFeatureX *
mono_component_feature_X_init (void);
#endif
```
* Implement the component in `src/mono/mono/component/feature_X.c` (and other sources, if necessary).
Re-declare and then dcefine the component entrypoint and populate a function table:
```
#ifndef STATIC_COMPONENTS
MONO_COMPONENT_EXPORT_ENTRYPOINT
MonoComponentFeatureX *
mono_component_feature_X_init (void);
#endif
/* declare static functions that implement the feature_X vtable */
static void feature_X_cleanup (MonoComponent *self);
static void feature_X_hello (void);
static MonoComponentFeatureX fn_table = {
{ feature_X_cleanup },
feature_X_hello,
};
MonoComponentFeatureX *
mono_component_feature_X_init (void) { return &fn_table; }
void feature_X_cleanup (MonoComponent *self)
{
static int cleaned = 0;
if (cleaned)
return;
/* do cleanup */
cleaned = 1;
}
void feature_X_hello (void)
{
/* implement the feature_X hello functionality */
}
```
* Implement a component stub in `src/mono/mono/component/feature_X-stub.c`. This looks exactly like the component, except most function will be no-ops or `g_assert_not_reached`.
One tricky point is that the entrypoint is exported as `mono_component_feature_X_stub_init` and *also* as `mono_component_feature_X_init` if the component is being compiled statically.
```
#ifdef STATIC_COMPONENTS
MONO_COMPONENT_EXPORT_ENTRYPOINT
MonoComponentFeatureX *
mono_component_feature_X_init (void)
{
return mono_component_feature_X_stub_init ();
}
#endif
#ifndef STATIC_COMPONENTS
MONO_COMPONENT_EXPORT_ENTRYPOINT
MonoComponentFeatureX *
mono_component_feature_X_stub_init (void);
#endif
/* declare static functions that implement the feature_X vtable */
static void feature_X_cleanup (MonoComponent *self);
static void feature_X_hello (void);
static MonoComponentFeatureX fn_table = {
{ feature_X_cleanup },
feature_X_hello,
};
MonoComponentFeatureX *
mono_component_feature_X_init (void) { return &fn_table; }
void feature_X_cleanup (MonoComponent *self)
{
static int cleaned = 0;
if (cleaned)
return;
/* do cleanup */
cleaned = 1;
}
void feature_X_hello (void)
{
/* implement the feature_X hello functionality */
}
```
* Add a getter for the component to `mono/metadata/components.h`, and also add a declaration for the component stub initialization function here
```c
MonoComponentFeatureX*
mono_component_feature_X (void);
...
MonoComponentFeatureX*
mono_component_feature_X_stub_init (void);
```
* Add an entry to the `components` list to load the component to `mono/metadata/components.c`, and also implement the getter for the component:
```c
static MonoComponentFeatureX *feature_X = NULL;
MonoComponentEntry components[] = {
...
{"feature_X", "feature_X", COMPONENT_INIT_FUNC (feature_X), (MonoComponent**)&feature_X, NULL },
}
...
MonoComponentFeatureX*
mono_component_feature_X (void)
{
return feature_X;
}
```
* In the runtime, call the component functions through the getter.
```c
mono_component_feature_X()->hello()
```
* To call runtime functions from the component, use either `MONO_API` functions
from the runtime, or `MONO_COMPONENT_API` functions. It is permissible to
mark additional functions with `MONO_COMPONENT_API`, provided they have a
`mono_`- or `m_`-prefixed name.
## Detailed design - Packaging and runtime packs
The components are building blocks to put together a functional runtime. The
runtime pack includes the base runtime and the components. The mono workload
includes the runtime pack and additional tasks, properties and targets that
enable the workload to construct a runtime for various scenarios.
For the target RID, we expose:
- `@(_MonoRuntimeComponentLinking)` set to either `'static'` or `'dynamic'` depending on whether the
current runtime pack for the current target includes runtime components as static archives or as
shared libraries, respectively.
- `@(_MonoRuntimeComponentSharedLibExt)` and `@(_MonoRuntimeComponentStaticLibExt)` set to the file
extension of the runtime components for the current target (ie, `'.a', '.so', '.dylib'` etc).
- `@(_MonoRuntimeAvailableComponents)` a list of component names without the `lib` prefix (if any)
or file extensions. For example: `'hot_reload; diagnostics_tracing'`.
Each of the above item lists has `RuntimeIdentifier` metadata. For technical reasons the mono
workload will provide a single `@(_MonoRuntimeAvailableComponent)` item list for all platforms. We
use the `RuntimeIdentifier` metadata to filter out the details applicable for the current platform.
- The target `_MonoSelectRuntimeComponents` that has the following inputs and outputs:
- input `@(_MonoComponent)` (to be set by the workload) : a list of components that a workload wants to use for the current
app. It is an error if this specifies any unknown component name.
- output `@(_MonoRuntimeSelectedComponents)` and `@(_MonoRuntimeSelectedStubComponents)` The names
of the components that were (resp, were not) selected. For example `'hot_reload;
diagnostics_tracing'`. Each item has two metadata properties `ComponentLib` and
`ComponentStubLib` (which may be empty) that specify the name of the static or dynamic library
of the component. This is not the main output of the target, it's primarily for debugging.
- output `@(_MonoRuntimeComponentLink)` a list of library names (relative to the `native/`
subdirectory of the runtime pack) that (for dynamic components) must be placed next to the
runtime in the application bundle, or (for static components) that must be linked with the
runtime to enable the components' functionality. Each item in the list has metadata
`ComponentName` (e.g. `'hot_reload'`), `IsStub` (`true` or `false`), `Linking` (`'static'` or
`'dynamic'`). This output should be used by the workloads when linking the app and runtime if
the workload uses an allow list of native libraries to link or bundle.
- output `@(_MonoRuntimeComponentDontLink)` a list of library names (relative to the `native/`
subdirectory of the runtime pack) that should be excluded from the application bundle (for
dynamic linking) or that should not be passed to the native linker (for static linking). This
output should be used by workloads that just link or bundle every native library from `native/`
in order to filter the contents of the subdirectory to exclude the disabled components (and to
exclude the static library stubs for the enabled components when static linking).
Generally workloads should only use one of `@(_MonoRuntimeComponentLink)` or
`@(_MonoRuntimeComponentDontLink)`, depending on whether they use an allow or block list for the
contents of the `native/` subdirectory.
Example fragment (assuming the mono workload has been imported):
```xml
<Project>
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<_MonoComponent Include="hot_reload;diagnostics_tracing" />
</ItemGroup>
<Target Name="PrintComponents" DependsOnTargets="_MonoSelectRuntimeComponents">
<Message Importance="High" Text="Runtime identifier: $(RuntimeIdentifier)" />
<Message Importance="High" Text="Selected : @(_MonoRuntimeSelectedComponents) %(ComponentLib)" />
<Message Importance="High" Text="Stubbed out : @(_MonoRuntimeSelectedStubComponents) %(ComponentStubLib)" />
<Message Importance="High" Text="Linking with lib @(_MonoRuntimeComponentLink) Stub: %(IsStub) Linking: %(Linking) Component: %(ComponentName)"/>
<Message Importance="High" Text="UnSelected : @(_MonoRuntimeUnSelectedComponents) %(ComponentLib)" />
<Message Importance="High" Text="Exclude these from linking: @(_MonoRuntimeComponentDontLink) Stub: %(IsStub) Linking: %(Linking) Component: %(ComponentName)" />
</Target>
</Project>
```