Skip to content

Commit 03ea052

Browse files
authored
[design] Mono runtime components (#49913)
* [design] MonoVM Runtime Components * Add details about writing a new component * Update design doc to reflect latest changes in the prototype
1 parent 5d0817a commit 03ea052

File tree

1 file changed

+280
-0
lines changed

1 file changed

+280
-0
lines changed

docs/design/mono/components.md

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
# MonoVM Runtime Components
2+
3+
## Summary
4+
5+
MonoVM runtime components are units of optional functionality that may be provided by the runtime to some workloads.
6+
7+
The idea is to provide some components as optional units that can be loaded dynamically (on workloads that support
8+
dynamic loading) or that are statically linked into the final application (on workloads that support only static
9+
linking).
10+
11+
To that end this document describes a methodology for defining and implementing components and for calling component
12+
functionality from the runtime.
13+
14+
## Goals and scenarios
15+
16+
Breaking up the runtime into components allows us to pursue two goals:
17+
1. Provide workloads with the ability to ship a runtime that
18+
contains only the required capabilities and excludes native code for
19+
unsupported operations.
20+
2. Reduce the number of different build configurations and reduce the build
21+
complexity by allowing composition to happen closer to application execution
22+
time, instead of requiring custom builds of the runtime.
23+
24+
For example, each of the following experiences requires different runtime
25+
capabilities:
26+
27+
- Developer inner loop on on a mobile or WebAssembly workload: The runtime
28+
should include support for the interpreter, hot reload, and the diagnostic
29+
server.
30+
- Release build iPhone app for the app store: The runtime should not include the
31+
interpreter, hot reload, or the diagnostic server.
32+
- Release build iPhone app with `System.Reflection.Emit` support: The runtime
33+
should include the interpreter, but not hot reload or the diagnostic server.
34+
- Line of business Android app company-wide internal beta: The runtime should
35+
not include interpreter support or hot reload, but should include the
36+
diagnostic server.
37+
38+
## Building Components
39+
40+
For each workload we choose one of two strategies for building the runtime and
41+
the components: we either build the components as shared libraries that are
42+
loaded by the runtime at execution time; or we build the components as static
43+
libraries that are statically linked together with the runtime (static
44+
library), and the application host. We assume that workloads that would
45+
utilize static linking already do native linking of the application host and
46+
the runtime as part of the app build.
47+
48+
The choice of which strategy to pursue depends on platform capabilities. For
49+
example there is no dynamic linking on WebAssembly at this time, so static
50+
linking is the only option. On iOS dynamic linking is supported for debug
51+
scenarios, but is disallowed in apps that want to be published to the Apple App
52+
Store. Thus for ios release builds we must use static linking.
53+
54+
We can summarize the different options:
55+
56+
| Scenario(s) | Dynamic loading allowed | Component build strategy | Disabled components |
57+
| ------------------------------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
58+
| 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. |
59+
| webassembly,<br>ios device release | no | component stubs in runtime; components in static libraries; embedding API calls to register non-stubs | Don’t link the component static libs.<br>Leave out the embedding API registration call. |
60+
61+
62+
63+
## High level overview
64+
65+
Each component is defined in `src/mono/mono/components`.
66+
67+
The runtime is compiled for different configurations with either static or dynamic linking of components.
68+
69+
When the components are dynamically linked, each component produces a shared library `mono-component-*component-name*`.
70+
When the components are statically linked, each component produces a static library.
71+
72+
The choice of dynamic or static linking is a global compile-time configuration parameter of the runtime: either all
73+
components are dynamic or they're all static. In either case, each component may be either present or stubbed out at execution time.
74+
75+
With dynamic linking, all the stubs are built into the runtime, and the runtime probes for the dynamic library of each
76+
component to see if it is present, or else it calls the stub component.
77+
78+
With static linking, all the present components are statically linked with the runtime into the final app.
79+
80+
Each component exposes a table of functions to the runtime. The stubs also implement the same table. In the places
81+
where it makes sense, the runtime may call to get the function table and call the methods.
82+
83+
When a component implementation needs to call the runtime, it may call only functions that are marked
84+
`MONO_COMPONENT_API` (or `MONO_API` - as long as it is not `MONO_RT_EXTERNAL_ONLY` - same as what is allowed for normal
85+
runtime internal functions).
86+
87+
Components, their vtables, etc are not versioned. The runtime and the components together represent a single
88+
indivisible unit. Mixing components from different versions of the runtime is not supported.
89+
90+
## Detailed design - C code organization
91+
92+
### Base component contract
93+
94+
Each component may use the following types and preprocessor definitions:
95+
96+
- (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.
97+
- The component cleanup function should be prepared to be called multiple
98+
times. Second and subsequent calls should be ignored.
99+
- (from `mono/utils/mono-compiler.h`) `MONO_COMPONENT_API` when a component
100+
needs to call a runtime function that is not part of the public Mono API, it
101+
can only call a `MONO_COMPONENT_API` function. Care should be taken to use
102+
the most general version of a group of functions so that we can keep the
103+
total number of exposed functions to a minimum.
104+
- (from `mono/utils/mono-compiler.h`) `MONO_COMPONENT_EXPORT_ENTRYPOINT` each
105+
component must expose a function named `mono_component_<component_name>_init`
106+
that is tagged with this macro. When the component is compiled dynamically,
107+
the build will ensure that the entrypoint is exported and visible.
108+
- (set by cmake) `COMPILING_COMPONENT_DYNAMIC` defined if the component is
109+
being compiled into a shared library. Generally components don't need to
110+
explicitly act on this define.
111+
- (set by cmake) `STATIC_COMPONENTS` defined if all components are being
112+
compiled statically. If this is set, the component stub should export an
113+
entrypoint with the name `mono_component_<component_name>_init` rather than
114+
`mono_component_<component_name>_stub_init`.
115+
116+
### To implement a component
117+
118+
To implement `feature_X` as a component. Carry out the following steps:
119+
120+
* Add a new entry to the `components` list in `src/mono/mono/component/CMakeLists.txt`:
121+
```
122+
list(APPEND components
123+
feature_X
124+
)
125+
```
126+
* Add a new list `feature_X-sources_base` to `src/mono/mono/component/CMakeLists.txt` that lists the source files of the component:
127+
```
128+
set(feature_X-sources_base feature_X.h feature_X.c)
129+
```
130+
* 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:
131+
```
132+
set(feature_X-stub-sources_base feature_X-stub.c)
133+
```
134+
* Declare a struct `_MonoComponentFeatureX` in `src/mono/mono/component/feature_X.h`
135+
```
136+
typedef struct _MonoComponentFeatureX {
137+
MonoComponent component; /* First member _must_ be MonoComponent */
138+
void (*hello)(void); /* Additional function pointers for each method for feature_X */
139+
} MonoComponentFeatureX;
140+
```
141+
* Declare an entrypoint `mono_component_feature_X_init` in `src/mono/mono/component/feature_X.h`
142+
that takes no arguments and returns the component vtable:
143+
```
144+
#ifdef STATIC_COMPONENTS
145+
MONO_COMPONENT_EXPORT_ENTRYPOINT
146+
MonoComponentFeatureX *
147+
mono_component_feature_X_init (void);
148+
#endif
149+
```
150+
* Implement the component in `src/mono/mono/component/feature_X.c` (and other sources, if necessary).
151+
Re-declare and then dcefine the component entrypoint and populate a function table:
152+
```
153+
#ifndef STATIC_COMPONENTS
154+
MONO_COMPONENT_EXPORT_ENTRYPOINT
155+
MonoComponentFeatureX *
156+
mono_component_feature_X_init (void);
157+
#endif
158+
159+
/* declare static functions that implement the feature_X vtable */
160+
static void feature_X_cleanup (MonoComponent *self);
161+
static void feature_X_hello (void);
162+
163+
static MonoComponentFeatureX fn_table = {
164+
{ feature_X_cleanup },
165+
feature_X_hello,
166+
};
167+
168+
MonoComponentFeatureX *
169+
mono_component_feature_X_init (void) { return &fn_table; }
170+
171+
void feature_X_cleanup (MonoComponent *self)
172+
{
173+
static int cleaned = 0;
174+
if (cleaned)
175+
return;
176+
/* do cleanup */
177+
cleaned = 1;
178+
}
179+
180+
void feature_X_hello (void)
181+
{
182+
/* implement the feature_X hello functionality */
183+
}
184+
```
185+
* 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`.
186+
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.
187+
```
188+
#ifdef STATIC_COMPONENTS
189+
MONO_COMPONENT_EXPORT_ENTRYPOINT
190+
MonoComponentFeatureX *
191+
mono_component_feature_X_init (void)
192+
{
193+
return mono_component_feature_X_stub_init ();
194+
}
195+
#endif
196+
#ifndef STATIC_COMPONENTS
197+
MONO_COMPONENT_EXPORT_ENTRYPOINT
198+
MonoComponentFeatureX *
199+
mono_component_feature_X_stub_init (void);
200+
#endif
201+
202+
/* declare static functions that implement the feature_X vtable */
203+
static void feature_X_cleanup (MonoComponent *self);
204+
static void feature_X_hello (void);
205+
206+
static MonoComponentFeatureX fn_table = {
207+
{ feature_X_cleanup },
208+
feature_X_hello,
209+
};
210+
211+
MonoComponentFeatureX *
212+
mono_component_feature_X_init (void) { return &fn_table; }
213+
214+
void feature_X_cleanup (MonoComponent *self)
215+
{
216+
static int cleaned = 0;
217+
if (cleaned)
218+
return;
219+
/* do cleanup */
220+
cleaned = 1;
221+
}
222+
223+
void feature_X_hello (void)
224+
{
225+
/* implement the feature_X hello functionality */
226+
}
227+
```
228+
* Add a getter for the component to `mono/metadata/components.h`, and also add a declaration for the component stub initialization function here
229+
```c
230+
MonoComponentFeatureX*
231+
mono_component_feature_X (void);
232+
233+
...
234+
MonoComponentFeatureX*
235+
mono_component_feature_X_stub_init (void);
236+
```
237+
238+
* Add an entry to the `components` list to load the component to `mono/metadata/components.c`, and also implement the getter for the component:
239+
```c
240+
static MonoComponentFeatureX *feature_X = NULL;
241+
242+
MonoComponentEntry components[] = {
243+
...
244+
{"feature_X", "feature_X", COMPONENT_INIT_FUNC (feature_X), (MonoComponent**)&feature_X, NULL },
245+
}
246+
247+
248+
...
249+
MonoComponentFeatureX*
250+
mono_component_feature_X (void)
251+
{
252+
return feature_X;
253+
}
254+
```
255+
256+
* In the runtime, call the component functions through the getter.
257+
```c
258+
mono_component_feature_X()->hello()
259+
```
260+
* To call runtime functions from the component, use either `MONO_API` functions
261+
from the runtime, or `MONO_COMPONENT_API` functions. It is permissible to
262+
mark additional functions with `MONO_COMPONENT_API`, provided they have a
263+
`mono_`- or `m_`-prefixed name.
264+
265+
## Detailed design - Packaging and runtime packs
266+
267+
The components are building blocks to put together a functional runtime. The
268+
runtime pack includes the base runtime and the components and additional
269+
properties and targets that enable the workload to construct a runtime for
270+
various scenarios.
271+
272+
In each runtime pack we include:
273+
274+
- The compiled compnents for the apropriate host architectures in a well-known subdirectory
275+
- An MSBuild props file that defines an item group that list each component name and has metadata that indicates:
276+
- the path to the component in the runtime pack
277+
- the path to the stub component in the runtime pack (if components are static)
278+
- An MSBuild targets file that defines targets to copy a specified set of components to the app publish folder (if components are dynamic); or to link the runtime together with stubs and a set of enabled components (if components are static)
279+
280+
** TODO ** Write this up in more detail

0 commit comments

Comments
 (0)