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/badge.svg)](https://docs.rs/wasm-tracing-allocator/)
7[![](https://img.shields.io/crates/v/wasm-tracing-allocator.svg)](https://crates.io/crates/wasm-tracing-allocator)
8[![](https://img.shields.io/crates/d/wasm-tracing-allocator.svg)](https://crates.io/crates/wasm-tracing-allocator)
9[![Build Status](https://dev.azure.com/rustwasm/wasm-tracing-allocator/_apis/build/status/rustwasm.wasm-tracing-allocator?branchName=master)](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[![Example output](https://raw.githubusercontent.com/rustwasm/wasm-tracing-allocator/master/live-allocations-dump.png)](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}