@@ -49,7 +49,6 @@ unsafe impl GlobalAlloc for System {
49
49
50
50
#[ cfg( target_feature = "atomics" ) ]
51
51
mod lock {
52
- use crate :: arch:: wasm32;
53
52
use crate :: sync:: atomic:: { AtomicI32 , Ordering :: SeqCst } ;
54
53
55
54
static LOCKED : AtomicI32 = AtomicI32 :: new ( 0 ) ;
@@ -61,27 +60,93 @@ mod lock {
61
60
if LOCKED . swap ( 1 , SeqCst ) == 0 {
62
61
return DropLock
63
62
}
64
- unsafe {
65
- let r = wasm32:: i32_atomic_wait (
66
- & LOCKED as * const AtomicI32 as * mut i32 ,
67
- 1 , // expected value
68
- -1 , // timeout
69
- ) ;
70
- debug_assert ! ( r == 0 || r == 1 ) ;
71
- }
63
+ // Ok so here's where things get a little depressing. At this point
64
+ // in time we need to synchronously acquire a lock, but we're
65
+ // contending with some other thread. Typically we'd execute some
66
+ // form of `i32.atomic.wait` like so:
67
+ //
68
+ // unsafe {
69
+ // let r = core::arch::wasm32::i32_atomic_wait(
70
+ // &LOCKED as *const AtomicI32 as *mut i32,
71
+ // 1, // expected value
72
+ // -1, // timeout
73
+ // );
74
+ // debug_assert!(r == 0 || r == 1);
75
+ // }
76
+ //
77
+ // Unfortunately though in doing so we would cause issues for the
78
+ // main thread. The main thread in a web browser *cannot ever
79
+ // block*, no exceptions. This means that the main thread can't
80
+ // actually execute the `i32.atomic.wait` instruction.
81
+ //
82
+ // As a result if we want to work within the context of browsers we
83
+ // need to figure out some sort of allocation scheme for the main
84
+ // thread where when there's contention on the global malloc lock we
85
+ // do... something.
86
+ //
87
+ // Possible ideas include:
88
+ //
89
+ // 1. Attempt to acquire the global lock. If it fails, fall back to
90
+ // memory allocation via `memory.grow`. Later just ... somehow
91
+ // ... inject this raw page back into the main allocator as it
92
+ // gets sliced up over time. This strategy has the downside of
93
+ // forcing allocation of a page to happen whenever the main
94
+ // thread contents with other threads, which is unfortunate.
95
+ //
96
+ // 2. Maintain a form of "two level" allocator scheme where the main
97
+ // thread has its own allocator. Somehow this allocator would
98
+ // also be balanced with a global allocator, not only to have
99
+ // allocations cross between threads but also to ensure that the
100
+ // two allocators stay "balanced" in terms of free'd memory and
101
+ // such. This, however, seems significantly complicated.
102
+ //
103
+ // Out of a lack of other ideas, the current strategy implemented
104
+ // here is to simply spin. Typical spin loop algorithms have some
105
+ // form of "hint" here to the CPU that it's what we're doing to
106
+ // ensure that the CPU doesn't get too hot, but wasm doesn't have
107
+ // such an instruction.
108
+ //
109
+ // To be clear, spinning here is not a great solution.
110
+ // Another thread with the lock may take quite a long time to wake
111
+ // up. For example it could be in `memory.grow` or it could be
112
+ // evicted from the CPU for a timeslice like 10ms. For these periods
113
+ // of time our thread will "helpfully" sit here and eat CPU time
114
+ // until it itself is evicted or the lock holder finishes. This
115
+ // means we're just burning and wasting CPU time to no one's
116
+ // benefit.
117
+ //
118
+ // Spinning does have the nice properties, though, of being
119
+ // semantically correct, being fair to all threads for memory
120
+ // allocation, and being simple enough to implement.
121
+ //
122
+ // This will surely (hopefully) be replaced in the future with a
123
+ // real memory allocator that can handle the restriction of the main
124
+ // thread.
125
+ //
126
+ //
127
+ // FIXME: We can also possibly add an optimization here to detect
128
+ // when a thread is the main thread or not and block on all
129
+ // non-main-thread threads. Currently, however, we have no way
130
+ // of knowing which wasm thread is on the browser main thread, but
131
+ // if we could figure out we could at least somewhat mitigate the
132
+ // cost of this spinning.
72
133
}
73
134
}
74
135
75
136
impl Drop for DropLock {
76
137
fn drop ( & mut self ) {
77
138
let r = LOCKED . swap ( 0 , SeqCst ) ;
78
139
debug_assert_eq ! ( r, 1 ) ;
79
- unsafe {
80
- wasm32:: atomic_notify (
81
- & LOCKED as * const AtomicI32 as * mut i32 ,
82
- 1 , // only one thread
83
- ) ;
84
- }
140
+
141
+ // Note that due to the above logic we don't actually need to wake
142
+ // anyone up, but if we did it'd likely look something like this:
143
+ //
144
+ // unsafe {
145
+ // core::arch::wasm32::atomic_notify(
146
+ // &LOCKED as *const AtomicI32 as *mut i32,
147
+ // 1, // only one thread
148
+ // );
149
+ // }
85
150
}
86
151
}
87
152
}
0 commit comments