1# Architecture of Wasmtime 2 3This document is intended to give an overview of the implementation of Wasmtime. 4This will explain the purposes of the various `wasmtime-*` crates that the main 5`wasmtime` crate depends on. For even more detailed information it's recommended 6to review the code itself and find the comments contained within. 7 8## The `wasmtime` crate 9 10The main entry point for Wasmtime is the `wasmtime` crate itself. Wasmtime is 11designed such that the `wasmtime` crate is nearly a 100% safe API (safe in the 12Rust sense) modulo some small and well-documented functions as to why they're 13`unsafe`. The `wasmtime` crate provides features and access to WebAssembly 14primitives and functionality, such as compiling modules, instantiating them, 15calling functions, etc. 16 17At this time the `wasmtime` crate is the first crate that is intended to be 18consumed by users. First in this sense means that everything `wasmtime` depends 19on is thought of as an internal dependency. We publish crates to crates.io but 20put very little effort into having a "nice" API for internal crates or worrying 21about breakage between versions of internal crates. This primarily means that 22all the other crates discussed here are considered internal dependencies of 23Wasmtime and don't show up in the public API of Wasmtime at all. To use some 24Cargo terminology, all the `wasmtime-*` crates that `wasmtime` depends on are 25"private" dependencies. 26 27Additionally at this time the safe/unsafe boundary between Wasmtime's internal 28crates is not the most well-defined. There are methods that should be marked 29`unsafe` which aren't, and `unsafe` methods do not have exhaustive documentation 30as to why they are `unsafe`. This is an ongoing matter of improvement, however, 31where the goal is to have safe methods be actually safe in the Rust sense, 32as well as having documentation for `unsafe` methods which clearly lists why 33they are `unsafe`. 34 35## Important concepts 36 37To preface discussion of more nitty-gritty internals, it's important to have a 38few concepts in the back of your head. These are some of the important types and 39their implications in Wasmtime: 40 41* `wasmtime::Engine` - this is a global compilation context which is sort of the 42 "root context". An `Engine` is typically created once per program and is 43 expected to be shared across many threads (internally it's atomically 44 reference counted). Each `Engine` stores configuration values and other 45 cross-thread data such as type interning for `Module` instances. The main 46 thing to remember for `Engine` is that any mutation of its internals typically 47 involves acquiring a lock, whereas for `Store` below no locks are necessary. 48 49* `wasmtime::Store` - this is the concept of a "store" in WebAssembly. While 50 there's also a formal definition to go off of, it can be thought of as a bag 51 of related WebAssembly objects. This includes instances, globals, memories, 52 tables, etc. A `Store` does not implement any form of garbage collection of 53 the internal items (there is a `gc` function but that's just for `externref` 54 values). This means that once you create an `Instance` or a `Table` the memory 55 is not actually released until the `Store` itself is deallocated. A `Store` is 56 sort of a "context" used for almost all wasm operations. `Store` also contains 57 instance handles which recursively refer back to the `Store`, leading to a 58 good bit of aliasing of pointers within the `Store`. The important thing for 59 now, though, is to know that `Store` is a unit of isolation. WebAssembly 60 objects are always entirely contained within a `Store`, and at this time 61 nothing can cross between stores (except scalars if you manually hook it up). 62 In other words, wasm objects from different stores cannot interact with each 63 other. A `Store` cannot be used simultaneously from multiple threads (almost 64 all operations require `&mut self`). 65 66* `wasmtime::runtime::vm::InstanceHandle` - this is the low-level representation of a 67 WebAssembly instance. At the same time this is also used as the representation 68 for all host-defined objects. For example if you call `wasmtime::Memory::new` 69 it'll create an `InstanceHandle` under the hood. This is a very `unsafe` type 70 that should probably have all of its functions marked `unsafe` or otherwise 71 have more strict guarantees documented about it, but it's an internal type 72 that we don't put much thought into for public consumption at this time. An 73 `InstanceHandle` doesn't know how to deallocate itself and relies on the 74 caller to manage its memory. Currently this is either allocated on-demand 75 (with `malloc`) or in a pooling fashion (using the pooling allocator). The 76 `deallocate` method is different in these two paths (as well as the 77 `allocate` method). 78 79 An `InstanceHandle` is laid out in memory with some Rust-owned values first 80 capturing the dynamic state of memories/tables/etc. Most of these fields are 81 unused for host-defined objects that serve one purpose (e.g. a 82 `wasmtime::Table::new`), but for an instantiated WebAssembly module these 83 fields will have more information. After an `InstanceHandle` in memory is a 84 `VMContext`, which will be discussed next. `InstanceHandle` values are the 85 main internal runtime representation and what the `crate::runtime::vm` code 86 works with. The `wasmtime::Store` holds onto all these `InstanceHandle` values 87 and deallocates them at the appropriate time. From the runtime perspective it 88 simplifies things so the graph of wasm modules communicating to each other is 89 reduced to simply `InstanceHandle` values all talking to themselves. 90 91* `crate::runtime::vm::VMContext` - this is a raw pointer, within an allocation of 92 an `InstanceHandle`, that is passed around in JIT code. A `VMContext` does not 93 have a structure defined in Rust (it's a 0-sized structure) because its 94 contents are dynamically determined based on the `VMOffsets`, or the source 95 wasm module it came from. Each `InstanceHandle` has a "shape" of a `VMContext` 96 corresponding with it. For example a `VMContext` stores all values of 97 WebAssembly globals, but if a wasm module has no globals then the size of this 98 array will be 0 and it won't be allocated. The intention of a `VMContext` is 99 to be an efficient in-memory representation of all wasm module state that JIT 100 code may access. The layout of `VMContext` is dynamically determined by a 101 module and JIT code is specialized for this one structure. This means that the 102 structure is efficiently accessed by JIT code, but less efficiently accessed 103 by native host code. A non-exhaustive list of purposes of the `VMContext` is 104 to: 105 106 * Store WebAssembly instance state such as global values, pointers to tables, 107 pointers to memory, and pointers to other JIT functions. 108 * Separate wasm imports and local state. Imported values have pointers stored 109 to their actual values, and local state has the state defined inline. 110 * Hold a pointer to the stack limit at which point JIT code will trigger a 111 stack overflow. 112 * Hold a pointer to a `VMExternRefActivationsTable` for fast-path insertion of 113 `externref` values into the table. 114 * Hold a pointer to a `*mut dyn crate::runtime::vm::Store` so store-level 115 operations can be performed in libcalls. 116 117 A comment about the layout of a `VMContext` can be found in the `vmoffsets.rs` 118 file. 119 120* `wasmtime::Module` - this is the representation of a compiled WebAssembly 121 module. At this time Wasmtime always assumes that a wasm module is always 122 compiled to native JIT code. `Module` holds the results of said compilation, 123 and currently Cranelift can be used for compiling. It is a goal of 124 Wasmtime to support other modes of representing modules but those are not 125 implemented today just yet, only Cranelift is implemented and supported. 126 127* `wasmtime_environ::Module` - this is a descriptor of a wasm module's type and 128 structure without holding any actual JIT code. An instance of this type is 129 created very early on in the compilation process, and it is not modified when 130 functions themselves are actually compiled. This holds the internal type 131 representation and state about functions, globals, etc. In a sense this can be 132 thought of as the result of validation or typechecking a wasm module, although 133 it doesn't have information such as the types of each opcode or minute 134 function-level details like that. 135 136## Compiling a module 137 138With a high-level overview and some background information of types, this will 139next walk through the steps taken to compile a WebAssembly module. The main 140entry point for this is the `wasmtime::Module::from_binary` API. There are a 141number of other entry points that deal with surface-level details like 142translation from text-to-binary, loading from the filesystem, etc. 143 144Compilation is roughly broken down into a few phases: 145 1461. First compilation walks over the WebAssembly module validating everything 147 except function bodies. This synchronous pass over a wasm module creates a 148 `wasmtime_environ::Module` instance and additionally prepares for function 149 compilation. Note that with the module linking proposal one input module may 150 end up creating a number of output modules to process. Each module is 151 processed independently and all further steps are parallelized on a 152 per-module basis. Note that parsing and validation of the WebAssembly module 153 happens with the `wasmparser` crate. Validation is interleaved with parsing, 154 validating parsed values before using them. 155 1562. Next all functions within a module are validated and compiled in parallel. 157 No inter-procedural analysis is done and each function is compiled as its 158 own little island of code at this time. This is the point where the meat of 159 Cranelift is invoked on a per-function basis. 160 1613. The compilation results at this point are all woven into a 162 `wasmtime_jit::CompilationArtifacts` structure. This holds module information 163 (`wasmtime_environ::Module`), compiled JIT code (stored as an ELF image), and 164 miscellaneous other information about functions such as platform-agnostic 165 unwinding information, per-function trap tables (indicating which JIT 166 instructions can trap and what the trap means), per-function address maps 167 (mapping from JIT addresses back to wasm offsets), and debug information 168 (parsed from DWARF information in the wasm module). These results are inert 169 and can't actually be executed, but they're appropriate at this point to 170 serialize to disk or begin the next phase... 171 1724. The final step is to actually place all code into a form that's ready to get 173 executed. This starts from the `CompilationArtifacts` of the previous step. 174 Here a new memory mapping is allocated and the JIT code is copied into this 175 memory mapping. This memory mapping is then switched from read/write to 176 read/execute so it's actually executable JIT code at this point. This is 177 where various hooks like loading debuginfo, informing JIT profilers of new 178 code, etc, all happens. At this point a `wasmtime_jit::CompiledModule` is 179 produced and this is itself wrapped up in a `wasmtime::Module`. At this 180 point the module is ready to be instantiated. 181 182A `wasmtime::Module` is an atomically-reference-counted object where upon 183instantiation into a `Store`, the `Store` will hold a strong reference to the 184internals of the module. This means that all instances of a `wasmtime::Module` 185share the same compiled code. Additionally a `wasmtime::Module` is one of the 186few objects that lives outside of a `wasmtime::Store`. This means that 187`wasmtime::Module`'s reference counting is its own form of memory management. 188 189Note that the property of sharing a module's compiled code across all 190instantiations has interesting implications on what the compiled code can 191assume. For example Wasmtime implements a form of type interning, but the 192interned types happen at a few different levels. Within a module we deduplicate 193function types, but across modules in a `Store` types need to be represented 194with the same value. This means that if the same module is instantiated into 195many stores its same function type may take on many values, so the compiled 196code can't assume a particular value for a function type. (more on type 197information later). The general gist though is that compiled code leans 198relatively heavily on the `VMContext` for contextual input because the JIT code 199is intended to be so widely reusable. 200 201### Trampolines 202 203An important aspect to also cover for compilation is the creation of 204trampolines. Trampolines in this case refer to code executed by Wasmtime to 205enter WebAssembly code. The host may not always have prior knowledge about the 206signature of the WebAssembly function that it wants to call. Wasmtime JIT code 207is compiled with native ABIs (e.g. params/results in registers according to 208System V on Unix), which means that a Wasmtime embedding doesn't have an easy 209way to enter JIT code. 210 211This problem is what the trampolines compiled into a module solve, which is to 212provide a function with a known ABI that will call into a function with a 213specific other type signature/ABI. Wasmtime collects all the exported functions 214of a module and creates a set of their type signatures. Note that exported in 215this context actually means "possibly exported" which includes things like 216insertion into a global/function table, conversion to a `funcref`, etc. A 217trampoline is generated for each of these type signatures and stored along with 218the JIT code for the rest of the module. 219 220These trampolines are then used with the `wasmtime::Func::call` API where in 221that specific case because we don't know the ABI of the target function the 222trampoline (with a known ABI) is used and has all the parameters/results passed 223through the stack. 224 225Another point of note is that trampolines are not deduplicated at this time. 226Each compiled module contains its own set of trampolines, and if two compiled 227modules have the same types then they'll have different copies of the same 228trampoline. 229 230### Type Interning and `VMSharedSignatureIndex` 231 232One important point to talk about with compilation is the 233`VMSharedSignatureIndex` type and how it's used. The `call_indirect` opcode in 234wasm compares an actual function's signature against the function signature of 235the instruction, trapping if the signatures mismatch. This is implemented in 236Wasmtime as an integer comparison, and the comparison happens on a 237`VMSharedSignatureIndex` value. This index is an intern'd representation of a 238function type. 239 240The scope of interning for `VMSharedSignatureIndex` happens at the 241`wasmtime::Engine` level. Modules are compiled into an `Engine`. Insertion of a 242`Module` into an `Engine` will assign a `VMSharedSignatureIndex` to all of the 243types found within the module. 244 245The `VMSharedSignatureIndex` values for a module are local to that one 246instantiation of a `Module` (and they may change on each insertion of a 247`Module` into a different `Engine`). These are used during the instantiation 248process by the runtime to assign a type ID effectively to all functions for 249imports and such. 250 251## Instantiating a module 252 253Once a module has been compiled it's typically then instantiated to actually 254get access to the exports and call wasm code. Instantiation always happens 255within a `wasmtime::Store` and the created instance (plus all exports) are tied 256to the `Store`. 257 258Instantiation itself (`crates/wasmtime/src/instance.rs`) may look complicated, 259but this is primarily due to the implementation of the Module Linking proposal. 260The rough flow of instantiation looks like: 261 2621. First all imports are type-checked. The provided list of imports is 263 cross-referenced with the list of imports recorded in the 264 `wasmtime_environ::Module` and all types are verified to line up and match 265 (according to the core wasm specification's definition of type matching). 266 2672. Each `wasmtime_environ::Module` has a list of initializers that need to be 268 completed before instantiation is finished. For MVP wasm this only involves 269 loading the import into the correct index array, but for module linking this 270 could involve instantiating other modules, handling `alias` fields, etc. In 271 any case the result of this step is a `crate::runtime::vm::Imports` array 272 which has the values for all imported items into the wasm module. Note that 273 in this case an import is typically some sort of raw pointer to the actual 274 state plus the `VMContext` of the instance that was imported from. The final 275 result of this step is an `InstanceAllocationRequest`, which is then 276 submitted to the configured instance allocator, either on-demand or pooling. 277 2783. The `InstanceHandle` corresponding to this instance is allocated. How this 279 is allocated depends on the strategy (malloc for on-demand, slab allocation 280 for pooling). In addition to initialization of the fields of `InstanceHandle` 281 this also initializes all the fields of the `VMContext` for this handle 282 (which as mentioned above is adjacent to the `InstanceHandle` allocation 283 after it in memory). This does not process any data segments, element 284 segments, or the `start` function at this time. 285 2864. At this point the `InstanceHandle` is stored within the `Store`. This is 287 the "point of no return" where the handle must be kept alive for the same 288 lifetime as the `Store` itself. If an initialization step fails then the 289 instance may still have had its functions, for example, inserted into an 290 imported table via an element segment. This means that even if we fail to 291 initialize this instance its state could still be visible to other 292 instances/objects so we need to keep it alive regardless. 293 2945. The final step is performing wasm-defined instantiation. This involves 295 processing element segments, data segments, the `start` function, etc. Most 296 of this is just translating from Wasmtime's internal representation to the 297 specification's required behavior. 298 299Another part worth pointing out for instantiating a module is that a 300`ModuleRegistry` is maintained within a `Store` of all instantiated modules 301into the store. The purpose of this registry is to retain a strong reference to 302items in the module needed to run instances. This includes the JIT code 303primarily but also has information such as the `VMSharedSignatureIndex` 304registration, metadata about function addresses and such, etc. Much of this 305data is stored into a `GLOBAL_MODULES` map for later access during traps. 306 307## Traps 308 309Once instances have been created and wasm starts running most things are fairly 310standard. Trampolines are used to enter wasm (or we can enter with a known ABI 311if using `wasmtime::TypedFunc`) and JIT code generally does what it does to 312execute wasm. An important aspect of the implementation to cover, however, is 313traps. 314 315Wasmtime today implements traps with `longjmp` and `setjmp`. The `setjmp` 316function cannot be defined in Rust (even unsafely -- 317(https://github.com/rust-lang/rfcs/issues/2625) so the 318`crates/wasmtime/src/runtime/vm/helpers.c` file actually calls 319setjmp/longjmp. Note that in general the operation of `longjmp` is not safe to 320execute in Rust because it skips stack-based destructors, so after `setjmp` when 321we call back into Rust to execute wasm we need to be careful in Wasmtime to not 322have any significant destructors on the stack once wasm is called. 323 324Traps can happen from a few different sources: 325 326* Explicit traps - these can happen when a host call returns a trap, for 327 example. These bottom out in `raise_user_trap` or `raise_lib_trap`, both of 328 which immediately call `longjmp` to go back to the wasm starting point. Note 329 that these, like when calling wasm, have to have callers be very careful to 330 not have any destructors on the stack. 331 332* Signals - this is the main vector for trap. Basically we use segfault and 333 illegal instructions to implement traps in wasm code itself. Segfaults arise 334 when linear memory accesses go out of bounds and illegal instructions are how 335 the wasm `unreachable` instruction is implemented. In both of these cases 336 Wasmtime installs a platform-specific signal handler to catch the signal, 337 inspect the state of the signal, and then handle it. Note that Wasmtime tries 338 to only catch signals that happen from JIT code itself as to not accidentally 339 cover up other bugs. Exiting a signal handler happens via `longjmp` to get 340 back to the original wasm call-site. 341 342The general idea is that Wasmtime has very tight control over the stack frames 343of wasm (naturally via Cranelift) and also very tight control over the code that 344executes just before we enter wasm (aka before the `setjmp`) and just after we 345reenter back into wasm (aka frames before a possible `longjmp`). 346 347The signal handler for Wasmtime uses the `GLOBAL_MODULES` map populated during 348instantiation to determine whether a program counter that triggered a signal is 349indeed a valid wasm trap. This should be true except for cases where the host 350program has another bug that triggered the signal. 351 352A final note worth mentioning is that Wasmtime uses the Rust `backtrace` crate 353to capture a stack trace when a wasm exception occurs. This forces Wasmtime to 354generate native platform-specific unwinding information to correctly unwind the 355stack and generate a stack trace for wasm code. This does have other benefits as 356well such as improving generic sampling profilers when used with Wasmtime. 357 358## Linear Memory 359 360Linear memory in Wasmtime is implemented effectively with `mmap` (or the 361platform equivalent thereof), but there are some subtle nuances that are worth 362pointing out here too. The implementation of linear memory is relatively 363configurable which gives rise to a number of situations that both the runtime 364and generated code need to handle. 365 366First there are a number of properties about linear memory which can be 367configured: 368 369* `wasmtime::Config::static_memory_maximum_size` 370* `wasmtime::Config::static_memory_guard_size` 371* `wasmtime::Config::dynamic_memory_guard_size` 372* `wasmtime::Config::guard_before_linear_memory` 373 374The methods on `Config` have a good bit of documentation to go over some 375nitty-gritty, but the general gist is that Wasmtime has two modes of memory: 376static and dynamic. Static memories represent an address space reservation that 377never moves and pages are committed to represent memory growth. Dynamic 378memories represent allocations where the committed portion exactly matches the 379wasm memory's size and growth happens by allocating a bigger chunk of memory. 380 381The guard size configuration indicates the size of the guard region that 382happens after linear memory. This guard size affects whether generated JIT code 383emits bounds checks or not. Bounds checks are elided if out-of-bounds addresses 384provably encounter the guard pages. 385 386The `guard_before_linear_memory` configuration additionally places guard pages 387in front of linear memory as well as after linear memory (the same size on both 388ends). This is only used to protect against possible Cranelift bugs and 389otherwise serves no purpose. 390 391The defaults for Wasmtime on 64-bit platforms are: 392 393* 4GB static maximum size meaning all 32-bit memories are static and 64-bit 394 memories are dynamic. 395* 2GB static guard size meaning all loads/stores with less than 2GB offset 396 don't need bounds checks with 32-bit memories. 397* Guard pages before linear memory are enabled. 398 399Altogether this means that 32-bit linear memories result in an 8GB virtual 400address space reservation by default in Wasmtime. With the pooling allocator 401where we know that linear memories are contiguous this results in a 6GB 402reservation per memory because the guard region after one memory is the guard 403region before the next. 404 405Note that 64-bit memories (the memory64 proposal for WebAssembly) can be 406configured to be static but will never be able to elide bounds checks at this 407time. This configuration is possible through the `static_memory_forced` 408configuration option. Additionally note that support for 64-bit memories in 409Wasmtime is functional but not yet tuned at this time so there's probably still 410some performance work and better defaults to manage. 411 412## Tables and `externref` 413 414WebAssembly tables contain reference types, currently either `funcref` or 415`externref`. A `funcref` in Wasmtime is represented as `*mut 416VMCallerCheckedFuncRef` and an `externref` is represented as `VMExternRef` 417(which is internally `*mut VMExternData`). Tables are consequently represented 418as vectors of pointers. Table storage memory management by default goes through 419Rust's `Vec` which uses `malloc` and friends for memory. With the pooling 420allocator this uses preallocated memory for storage. 421 422As mentioned previously `Store` has no form of internal garbage 423collection for wasm objects themselves so a `funcref` table in wasm is pretty 424simple in that there's no lifetime management of any of the pointers stored 425within, they're simply assumed to be valid for as long as the table is in use. 426 427For tables of `externref` the story is more complicated. The `VMExternRef` is a 428version of `Arc<dyn Any>` but specialized in Wasmtime so JIT code knows where 429the offset of the reference count field to directly manipulate it is. 430Furthermore tables of `externref` values need to manage the reference count 431field themselves, since the pointer stored in the table is required to have a 432strong reference count allocated to it. 433 434## GC and `externref` 435 436Wasmtime implements the `externref` type of WebAssembly with an 437atomically-reference-counted pointer. Note that the atomic part is not needed 438by wasm itself but rather from the Rust embedding environment where it must be 439safe to send `ExternRef` values to other threads. Wasmtime also does not 440come with a cycle collector so cycles of host-allocated `ExternRef` objects 441will leak. 442 443Despite reference counting, though, a `Store::gc` method exists. This is an 444implementation detail of how reference counts are managed while wasm code is 445executing. Instead of managing the reference count of an `externref` value 446individually as it moves around on the stack Wasmtime implements "deferred 447reference counting" where there's an overly conservative list of `ExternRef` 448values that may be in use, and periodically a GC is performed to make this 449overly conservative list a precise one. This leverages the stack map support 450of Cranelift plus the backtracing support of `backtrace` to determine live 451roots on the stack. The `Store::gc` method forces the 452possibly-overly-conservative list to become a precise list of `externref` 453values that are actively in use on the stack. 454 455## Index of crates 456 457The main Wasmtime internal crates are: 458 459* `wasmtime` - the safe public API of `wasmtime`. 460 * `wasmtime::runtime::vm` - low-level runtime implementation of Wasmtime. This 461 is where `VMContext` and `InstanceHandle` live. This module used to be a 462 crate, but has since been folding into `wasmtime`. 463* `wasmtime-environ` - low-level compilation support. This is where translation 464 of the `Module` and its environment happens, although no compilation actually 465 happens in this crate (although it defines an interface for compilers). The 466 results of this crate are handed off to other crates for actual compilation. 467* `wasmtime-cranelift` - implementation of function-level compilation using 468 Cranelift. 469 470Note that at this time Cranelift is a required dependency of wasmtime. Most of 471the types exported from `wasmtime-environ` use cranelift types in their API. One 472day it's a goal, though, to remove the required cranelift dependency and have 473`wasmtime-environ` be a relatively standalone crate. 474 475In addition to the above crates there are some other miscellaneous crates that 476`wasmtime` depends on: 477 478* `wasmtime-cache` - optional dependency to manage default caches on the 479 filesystem. This is enabled in the CLI by default but not enabled in the 480 `wasmtime` crate by default. 481* `wasmtime-fiber` - implementation of stack-switching used by `async` support 482 in Wasmtime 483* `wasmtime-debug` - implementation of mapping wasm dwarf debug information to 484 native dwarf debug information. 485* `wasmtime-profiling` - implementation of hooking up generated JIT code to 486 standard profiling runtimes. 487* `wasmtime-obj` - implementation of creating an ELF image from compiled 488 functions. 489