1 /*
2  * Wizer interface for Wasm module to be initialized.
3  *
4  * This header provides several macros that allow a Wasm module written in C/C++
5  * to declare an initializer function, and ensure that global constructors (in
6  * C++'s case) are run at initialization time rather than on startup of the
7  * pre-initialized module.
8  */
9 #ifndef _WIZER_H_
10 #define _WIZER_H_
11 
12 #ifdef __cplusplus
13 #define __WIZER_EXTERN_C extern "C"
14 #else
15 #define __WIZER_EXTERN_C extern
16 #endif
17 
18 #ifdef __clang_major__
19 // wasi-sdk-16 was the first wasi-sdk version that shipped with a version of
20 // wasi-libc that did not include __original_main. However, wasi-sdk-15 shipped
21 // with clang-14.0.0. To correctly identify the boundary where __original_main
22 // no longer exists, we check for either clang-15+ or specifically clang-14.0.4.
23 //
24 // wasi-sdk-17 ships with clang-15.0.6
25 // wasi-sdk-16 ships with clang-14.0.4
26 #if __clang_major__ >= 15 ||                                                   \
27     (__clang_major__ == 14 && __clang_patchlevel__ == 4)
28 #define WIZER_MAIN_VOID __main_void
29 #else
30 #define WIZER_MAIN_VOID __original_main
31 #endif
32 #endif
33 
34 // We default to assuming that the compiler is new enough to provide
35 // __main_void.
36 #ifndef WIZER_MAIN_VOID
37 #define WIZER_MAIN_VOID __main_void
38 #endif
39 
40 /*
41  * This macro inserts the exported functions necessary to allow Wizer to
42  * pre-initialize a Wasm module.
43  *
44  * To use, simply invoke the macro in exactly one compilation unit (C/C++ file)
45  * that is compiled into the Wasm module to be pre-initialized:
46  *
47  *     static void my_init_function() { ... }
48  *
49  *     WIZER_INIT(my_init_function);
50  *
51  * (The macro refers to the provided init function, so it must have been defined
52  * or must have a forward declaration at the point the macro is used.)
53  *
54  * The resulting module should be processed by Wizer as follows:
55  *
56  *     $ wizer -r _start=wizer.resume -o out.wasm in.wasm
57  *
58  * The result of this will be the following behavior:
59  *
60  * - If the `in.wasm` (the direct compilation output of a program including this
61  *   macro invocation) is run directly according to the WASI ABI (i.e., by
62  *   invoking `_start`), then nothing changes: global constructors are run,
63  *   `main()` is invoked, then global destructors are run. The initialization
64  *   function is *not* run in this case.
65  *
66  * - During pre-initialization (i.e., during this `wizer` invocation), global
67  *   constructors will run, and then the provided initialization function will
68  *   run. The module's memory and global-variable state is then snapshotted and
69  *   saved into `out.wasm`.
70  *
71  *   All other Wizer restrictions apply (see Wizer documentation for details):
72  *   for example, WASI hostcalls may be blocked, depending on options, and
73  *   invoking any other imported function will result in an immediate trap
74  *   and failure of the Wizer run.
75  *
76  * - If the resulting `out.wasm` is then run using the WASI ABI, the program's
77  *   global constructors are *not* re-run. Instead, execution starts directly at
78  *   `main()`, using the heap and global-variable state left by the global
79  *   constructor and initialization function execution during the Wizer
80  *   invocation.
81  *
82  * If no initialization function is needed (i.e., only C++ global constructors
83  * should be run), use `WIZER_DEFAULT_INIT()` instead.
84  */
85 #define WIZER_INIT(init_func)                                                  \
86   __WIZER_EXTERN_C void __wasm_call_ctors();                                   \
87   __WIZER_EXTERN_C void __wasm_call_dtors();                                   \
88   __WIZER_EXTERN_C void __wasi_proc_exit(int);                                 \
89   __WIZER_EXTERN_C int WIZER_MAIN_VOID();                                      \
90   /* This function's export name `wizer-initialize` is specially          */   \
91   /* recognized by Wizer. It is the direct entry point for pre-init.      */   \
92   __attribute__((export_name("wizer-initialize"))) void __wizer_initialize() { \
93     /* `__wasm_call_ctors()` is generated by `wasm-ld` and invokes all  */     \
94     /* of the global constructors. It is safe (and in fact necessary)   */     \
95     /* to manually invoke it here because `wizer-initialize` is the     */     \
96     /* direct entry point, and no libc startup (crt1.o or equivalent)   */     \
97     /* is executed before this code does. */                                   \
98     __wasm_call_ctors();                                                       \
99     /* We now invoke the provided init function before returning.       */     \
100     init_func();                                                               \
101   }                                                                            \
102   /* This function replaces `_start` (the WASI-specified entry point) in  */   \
103   /* the pre-initialized Wasm module.                                     */   \
104   __attribute__((export_name("wizer.resume"))) void __wizer_resume() {         \
105     /* `__main_void()` is defined by the WASI SDK toolchain due to      */     \
106     /* special semantics in C/C++ for the `main()` function, i.e., ito  */     \
107     /* can either take argc/argv or not. It collects arguments using    */     \
108     /* the appropriate WASI calls and then invokes the user program's   */     \
109     /* `main()`. This may change in the future; when it does, we will   */     \
110     /* coordinate with the WASI-SDK toolchain to implement this entry   */     \
111     /* point in an alternate way. */                                           \
112     int r = WIZER_MAIN_VOID();                                                 \
113     /* Because we are replacing `_start()`, we need to manually invoke  */     \
114     /* destructors as well.                                             */     \
115     __wasm_call_dtors();                                                       \
116     /* If main returned non-zero code, call `__wasi_proc_exit`. */             \
117     if (r != 0) {                                                              \
118       __wasi_proc_exit(r);                                                     \
119     }                                                                          \
120   }
121 
122 /*
123  * This macro is like `WIZER_INIT()`, but takes no initialization function.
124  * Instead, the pre-initialization phase only executes C++ global constructors
125  * before snapshotting the module state.
126  *
127  * See documentation for `WIZER_INIT()` for more details and usage instructions.
128  */
129 #define WIZER_DEFAULT_INIT()                                                   \
130   static void __empty_init() {}                                                \
131   WIZER_INIT(__empty_init)
132 
133 #endif // _WIZER_H_
134