wasm_tracing_allocator/lib.rs
1/*!
2
3**A global allocator for Wasm that traces allocations and deallocations for
4debugging purposes.**
5
6[](https://docs.rs/wasm-tracing-allocator/)
7[](https://crates.io/crates/wasm-tracing-allocator)
8[](https://crates.io/crates/wasm-tracing-allocator)
9[](https://dev.azure.com/rustwasm/wasm-tracing-allocator/_build/latest?definitionId=2&branchName=master)
10
11`wasm-tracing-allocator` enables you to better debug and analyze memory leaks
12and invalid frees in an environment where we don't have access to the
13conventional tools like Valgrind. The tracing hooks are safely implemented in
14JS, outside the Wasm module and its linear memory, to ensure that the tracing
15code doesn't perturb results.
16
17## Table of Contents
18
19* [Enabling the Tracing Allocator](#enabling-the-tracing-allocator)
20* [Analyzing and Debugging](#analyzing-and-debugging)
21
22## Enabling the Tracing Allocator
23
24First, add `wasm-tracing-allocator` to your `Cargo.toml`'s dependency list:
25
26```toml
27[dependencies]
28wasm-tracing-allocator = "0.1.0"
29```
30
31Next, configure `wasm_tracing_allocator::WasmTracingAllocator` as the global
32allocator:
33
34```no_run
35// src/lib.rs
36# fn main() {}
37
38use std::alloc::System;
39use wasm_tracing_allocator::WasmTracingAllocator;
40
41#[global_allocator]
42static GLOBAL_ALLOCATOR: WasmTracingAllocator<System> = WasmTracingAllocator(System);
43```
44
45Finally, make the JS implementations of the tracing hooks are available for your
46Wasm module to import:
47
48* On the Web, add this script *before* your Wasm module is instantiated:
49
50 ```html
51 <script src="https://unpkg.com/wasm-tracing-allocator@0.1.0/js/hooks.js"></script>
52 ```
53
54* On Node.js, require the hooks *before* your Wasm module is instantiated:
55
56 ```js
57 require("wasm-tracing-allocator");
58 ```
59
60## Analyzing and Debugging
61
62Use your developer tools console to invoke methods of the global
63`WasmTracingAllocator` object to get analyses about allocations and
64deallocations.
65
66The output is typically rendered with `console.table`:
67
68[](https://raw.githubusercontent.com/rustwasm/wasm-tracing-allocator/master/live-allocations-dump.png)
69
70### `WasmTracingAllocator.dumpLiveAllocations`
71
72Dump a table of live allocations to the console.
73
74```js
75WasmTracingAllocator.dumpLiveAllocations({
76 keyLabel: String,
77 valueLabel: String,
78 getKey: Object => any,
79 getValue: Object => Number,
80});
81```
82
83* `keyLabel`: Optional. The string label used to describe the keys column in the
84 table.
85
86* `valueLabel`: Optional. The string label used to describe the values column in
87 the table.
88
89* `getKey`: Optional. Function from an allocation entry object to anything. The
90 table will group and aggregate entries by their keys. Defaults to the stack at
91 the time of the allocation.
92
93* `getValue`: Optional. Function from an allocation entry object to a
94 number. The values for all entries with the same key are summed. Defaults to
95 the byte size of each allocation; a potential alternative would be to ignore
96 the argument and return `1` to count the number of allocations instead.
97
98### `WasmTracingAllocator.dumpInvalidFrees`
99
100Dump a table of invalid frees (double frees, frees of things that were never
101allocated, etc...) to the console.
102
103```js
104WasmTracingAllocator.dumpInvalidFrees({
105 keyLabel: String,
106 valueLabel: String,
107 getKey: Object => any,
108 getValue: Object => Number,
109});
110```
111
112* `keyLabel`: Optional. The string label used to describe the keys column in the
113 table.
114
115* `valueLabel`: Optional. The string label used to describe the values column in
116 the table.
117
118* `getKey`: Optional. Function from an invalid free entry object to anything. The
119 table will group and aggregate entries by their keys. Defaults to the stack at
120 the time of the deallocation.
121
122* `getValue`: Optional. Function from an invalid free entry object to a
123 number. The values for all entries with the same key are summed. Defaults to
124 counting the number of invalid frees.
125
126 */
127
128#![deny(missing_docs, missing_debug_implementations)]
129
130use std::alloc::{GlobalAlloc, Layout};
131
132#[doc(hidden)]
133pub mod hooks;
134
135/// A global allocator that traces the Wasm module's allocations and
136/// deallocations.
137///
138/// It wraps some global allocator `A` that actually implements the allocation
139/// and deallocation, and inserts its tracing after each invocation.
140///
141/// ## Example
142///
143/// Just give it the global allocator `A` to wrap, and add the
144/// `#[global_allocator]` attribute. The module level documentation has an
145/// example of wrapping the default system allocator. Here is an example of
146/// wrapping [`wee_alloc`](https://github.com/rustwasm/wee_alloc):
147///
148/// ```ignore
149/// // src/lib.rs
150/// # fn main() {}
151///
152/// use wasm_tracing_allocator::WasmTracingAllocator;
153/// use wee_alloc::WeeAlloc;
154///
155/// #[global_allocator]
156/// static GLOBAL_ALLOCATOR: WasmTracingAllocator<WeeAlloc> =
157/// WasmTracingAllocator(WeeAlloc::INIT);
158/// ```
159#[derive(Debug)]
160pub struct WasmTracingAllocator<A>(pub A)
161where
162 A: GlobalAlloc;
163
164unsafe impl<A> GlobalAlloc for WasmTracingAllocator<A>
165where
166 A: GlobalAlloc,
167{
168 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
169 let size = layout.size();
170 let align = layout.align();
171 let pointer = self.0.alloc(layout);
172 hooks::on_alloc(size, align, pointer);
173 pointer
174 }
175
176 unsafe fn dealloc(&self, pointer: *mut u8, layout: Layout) {
177 let size = layout.size();
178 let align = layout.align();
179 self.0.dealloc(pointer, layout);
180 hooks::on_dealloc(size, align, pointer);
181 }
182
183 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
184 let size = layout.size();
185 let align = layout.align();
186 let pointer = self.0.alloc_zeroed(layout);
187 hooks::on_alloc_zeroed(size, align, pointer);
188 pointer
189 }
190
191 unsafe fn realloc(&self, old_pointer: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
192 let old_size = layout.size();
193 let align = layout.align();
194 let new_pointer = self.0.realloc(old_pointer, layout, new_size);
195 hooks::on_realloc(old_pointer, new_pointer, old_size, new_size, align);
196 new_pointer
197 }
198}