1
1
use std:: cmp;
2
2
use std:: collections:: HashMap ;
3
+ use std:: env;
3
4
use std:: mem;
4
5
5
6
use failure:: { bail, format_err, Error } ;
@@ -78,9 +79,19 @@ impl Config {
78
79
///
79
80
/// More and/or less may happen here over time, stay tuned!
80
81
pub fn run ( & self , module : & mut Module ) -> Result < ( ) , Error > {
81
- let stack_pointer = find_stack_pointer ( module) ?;
82
+ // Compatibility with older LLVM outputs. Newer LLVM outputs, when
83
+ // atomics are enabled, emit a shared memory. That's a good indicator
84
+ // that we have work to do. If shared memory isn't enabled, though then
85
+ // this isn't an atomic module so there's nothing to do. We still allow,
86
+ // though, an environment variable to force us to go down this path to
87
+ // remain compatibile with older LLVM outputs.
82
88
let memory = find_memory ( module) ?;
83
- let addr = inject_thread_id_counter ( module, memory) ?;
89
+ if !module. memories . get ( memory) . shared && env:: var ( "WASM_BINDGEN_THREADS" ) . is_err ( ) {
90
+ return Ok ( ( ) ) ;
91
+ }
92
+
93
+ let stack_pointer = find_stack_pointer ( module) ?;
94
+ let addr = allocate_static_data ( module, memory, 4 , 4 ) ?;
84
95
let zero = InitExpr :: Value ( Value :: I32 ( 0 ) ) ;
85
96
let globals = Globals {
86
97
thread_id : module. globals . add_local ( ValType :: I32 , true , zero) ,
@@ -103,18 +114,11 @@ impl Config {
103
114
mem. maximum = Some ( cmp:: max ( self . maximum_memory / PAGE_SIZE , prev_max) ) ;
104
115
assert ! ( mem. data_segments. is_empty( ) ) ;
105
116
106
- let init_memory = module
107
- . exports
108
- . iter ( )
109
- . find ( |e| e. name == "__wasm_init_memory" )
110
- . ok_or_else ( || format_err ! ( "failed to find `__wasm_init_memory`" ) ) ?;
111
- let init_memory_id = match init_memory. item {
112
- walrus:: ExportItem :: Function ( f) => f,
113
- _ => bail ! ( "`__wasm_init_memory` must be a function" ) ,
114
- } ;
115
- let export_id = init_memory. id ( ) ;
116
- module. exports . delete ( export_id) ;
117
- InitMemory :: Call ( init_memory_id)
117
+ InitMemory :: Call {
118
+ wasm_init_memory : delete_synthetic_func ( module, "__wasm_init_memory" ) ?,
119
+ wasm_init_tls : delete_synthetic_func ( module, "__wasm_init_tls" ) ?,
120
+ tls_size : delete_synthetic_global ( module, "__tls_size" ) ?,
121
+ }
118
122
} else {
119
123
update_memory ( module, memory, self . maximum_memory ) ?;
120
124
InitMemory :: Segments ( switch_data_segments_to_passive ( module, memory) ?)
@@ -127,13 +131,47 @@ impl Config {
127
131
stack_pointer,
128
132
self . thread_stack_size ,
129
133
memory,
130
- ) ;
134
+ ) ? ;
131
135
132
136
implement_thread_intrinsics ( module, & globals) ?;
133
137
Ok ( ( ) )
134
138
}
135
139
}
136
140
141
+ fn delete_synthetic_func ( module : & mut Module , name : & str ) -> Result < FunctionId , Error > {
142
+ match delete_synthetic_export ( module, name) ? {
143
+ walrus:: ExportItem :: Function ( f) => Ok ( f) ,
144
+ _ => bail ! ( "`{}` must be a function" , name) ,
145
+ }
146
+ }
147
+
148
+ fn delete_synthetic_global ( module : & mut Module , name : & str ) -> Result < u32 , Error > {
149
+ let id = match delete_synthetic_export ( module, name) ? {
150
+ walrus:: ExportItem :: Global ( g) => g,
151
+ _ => bail ! ( "`{}` must be a global" , name) ,
152
+ } ;
153
+ let g = match module. globals . get ( id) . kind {
154
+ walrus:: GlobalKind :: Local ( g) => g,
155
+ walrus:: GlobalKind :: Import ( _) => bail ! ( "`{}` must not be an imported global" , name) ,
156
+ } ;
157
+ match g {
158
+ InitExpr :: Value ( Value :: I32 ( v) ) => Ok ( v as u32 ) ,
159
+ _ => bail ! ( "`{}` was not an `i32` constant" , name) ,
160
+ }
161
+ }
162
+
163
+ fn delete_synthetic_export ( module : & mut Module , name : & str ) -> Result < ExportItem , Error > {
164
+ let item = module
165
+ . exports
166
+ . iter ( )
167
+ . find ( |e| e. name == name)
168
+ . ok_or_else ( || format_err ! ( "failed to find `{}`" , name) ) ?;
169
+ let ret = item. item ;
170
+ let id = item. id ( ) ;
171
+ module. exports . delete ( id) ;
172
+ Ok ( ret)
173
+ }
174
+
137
175
struct PassiveSegment {
138
176
id : DataId ,
139
177
offset : InitExpr ,
@@ -211,7 +249,12 @@ struct Globals {
211
249
thread_tcb : GlobalId ,
212
250
}
213
251
214
- fn inject_thread_id_counter ( module : & mut Module , memory : MemoryId ) -> Result < u32 , Error > {
252
+ fn allocate_static_data (
253
+ module : & mut Module ,
254
+ memory : MemoryId ,
255
+ size : u32 ,
256
+ align : u32 ,
257
+ ) -> Result < u32 , Error > {
215
258
// First up, look for a `__heap_base` export which is injected by LLD as
216
259
// part of the linking process. Note that `__heap_base` should in theory be
217
260
// *after* the stack and data, which means it's at the very end of the
@@ -256,9 +299,9 @@ fn inject_thread_id_counter(module: &mut Module, memory: MemoryId) -> Result<u32
256
299
GlobalKind :: Local ( InitExpr :: Value ( Value :: I32 ( n) ) ) => n,
257
300
_ => bail ! ( "`__heap_base` not a locally defined `i32`" ) ,
258
301
} ;
259
- let address = ( * offset as u32 + 3 ) & !3 ; // align up
260
- let add_a_page = ( address + 4 ) / PAGE_SIZE != address / PAGE_SIZE ;
261
- * offset = ( address + 4 ) as i32 ;
302
+ let address = ( * offset as u32 + ( align - 1 ) ) & !( align - 1 ) ; // align up
303
+ let add_a_page = ( address + size ) / PAGE_SIZE != address / PAGE_SIZE ;
304
+ * offset = ( address + size ) as i32 ;
262
305
( address, add_a_page)
263
306
} ;
264
307
@@ -282,22 +325,32 @@ fn find_stack_pointer(module: &mut Module) -> Result<Option<GlobalId>, Error> {
282
325
} )
283
326
. collect :: < Vec < _ > > ( ) ;
284
327
285
- match candidates. len ( ) {
286
- // If there are no mutable i32 globals, assume this module doesn't even
287
- // need a stack pointer!
288
- 0 => Ok ( None ) ,
289
-
290
- // If there's more than one global give up for now. Eventually we can
291
- // probably do better by pattern matching on functions, but this should
292
- // be sufficient for LLVM's output for now.
293
- 1 => Ok ( Some ( candidates[ 0 ] . id ( ) ) ) ,
294
- _ => bail ! ( "too many mutable globals to infer the stack pointer" ) ,
328
+ if candidates. len ( ) == 0 {
329
+ return Ok ( None ) ;
330
+ }
331
+ if candidates. len ( ) > 2 {
332
+ bail ! ( "too many mutable globals to infer the stack pointer" ) ;
333
+ }
334
+ if candidates. len ( ) == 1 {
335
+ return Ok ( Some ( candidates[ 0 ] . id ( ) ) ) ;
295
336
}
337
+
338
+ // If we've got two mutable globals then we're in a pretty standard
339
+ // situation for threaded code where one is the stack pointer and one is the
340
+ // TLS base offset. We need to figure out which is which, and we basically
341
+ // assume LLVM's current codegen where the first is the stack pointer.
342
+ //
343
+ // TODO: have an actual check here.
344
+ Ok ( Some ( candidates[ 0 ] . id ( ) ) )
296
345
}
297
346
298
347
enum InitMemory {
299
348
Segments ( Vec < PassiveSegment > ) ,
300
- Call ( walrus:: FunctionId ) ,
349
+ Call {
350
+ wasm_init_memory : walrus:: FunctionId ,
351
+ wasm_init_tls : walrus:: FunctionId ,
352
+ tls_size : u32 ,
353
+ } ,
301
354
}
302
355
303
356
fn inject_start (
@@ -308,7 +361,7 @@ fn inject_start(
308
361
stack_pointer : Option < GlobalId > ,
309
362
stack_size : u32 ,
310
363
memory : MemoryId ,
311
- ) {
364
+ ) -> Result < ( ) , Error > {
312
365
use walrus:: ir:: * ;
313
366
314
367
assert ! ( stack_size % PAGE_SIZE == 0 ) ;
@@ -376,15 +429,6 @@ fn inject_start(
376
429
let sp = block. binop ( BinaryOp :: I32Add , sp_base, stack_size) ;
377
430
let set_stack_pointer = block. global_set ( stack_pointer, sp) ;
378
431
block. expr ( set_stack_pointer) ;
379
-
380
- // FIXME(WebAssembly/tool-conventions#117) we probably don't want to
381
- // duplicate drop with `if_zero_block` or otherwise just infer to drop
382
- // all these data segments, this seems like something to synthesize in
383
- // the linker...
384
- for segment in module. data . iter ( ) {
385
- let drop = block. data_drop ( segment. id ( ) ) ;
386
- block. expr ( drop) ;
387
- }
388
432
}
389
433
let if_nonzero_block = block. id ( ) ;
390
434
drop ( block) ;
@@ -394,7 +438,7 @@ fn inject_start(
394
438
// memory, however, so do that here.
395
439
let if_zero_block = {
396
440
let mut block = builder. if_else_block ( Box :: new ( [ ] ) , Box :: new ( [ ] ) ) ;
397
- match memory_init {
441
+ match & memory_init {
398
442
InitMemory :: Segments ( segments) => {
399
443
for segment in segments {
400
444
let zero = block. i32_const ( 0 ) ;
@@ -409,8 +453,10 @@ fn inject_start(
409
453
block. expr ( drop) ;
410
454
}
411
455
}
412
- InitMemory :: Call ( wasm_init_memory) => {
413
- let call = block. call ( wasm_init_memory, Box :: new ( [ ] ) ) ;
456
+ InitMemory :: Call {
457
+ wasm_init_memory, ..
458
+ } => {
459
+ let call = block. call ( * wasm_init_memory, Box :: new ( [ ] ) ) ;
414
460
block. expr ( call) ;
415
461
}
416
462
}
@@ -420,6 +466,23 @@ fn inject_start(
420
466
let block = builder. if_else ( thread_id_is_nonzero, if_nonzero_block, if_zero_block) ;
421
467
exprs. push ( block) ;
422
468
469
+ // If we have these globals then we're using the new thread local system
470
+ // implemented in LLVM, which means that `__wasm_init_tls` needs to be
471
+ // called with a chunk of memory `tls_size` bytes big to set as the threads
472
+ // thread-local data block.
473
+ if let InitMemory :: Call {
474
+ wasm_init_tls,
475
+ tls_size,
476
+ ..
477
+ } = memory_init
478
+ {
479
+ let malloc = find_wbindgen_malloc ( module) ?;
480
+ let size = builder. i32_const ( tls_size as i32 ) ;
481
+ let ptr = builder. call ( malloc, Box :: new ( [ size] ) ) ;
482
+ let block = builder. call ( wasm_init_tls, Box :: new ( [ ptr] ) ) ;
483
+ exprs. push ( block) ;
484
+ }
485
+
423
486
// If a start function previously existed we're done with our own
424
487
// initialization so delegate to them now.
425
488
if let Some ( id) = module. start . take ( ) {
@@ -432,6 +495,20 @@ fn inject_start(
432
495
433
496
// ... and finally flag it as the new start function
434
497
module. start = Some ( id) ;
498
+
499
+ Ok ( ( ) )
500
+ }
501
+
502
+ fn find_wbindgen_malloc ( module : & Module ) -> Result < FunctionId , Error > {
503
+ let e = module
504
+ . exports
505
+ . iter ( )
506
+ . find ( |e| e. name == "__wbindgen_malloc" )
507
+ . ok_or_else ( || format_err ! ( "failed to find `__wbindgen_malloc`" ) ) ?;
508
+ match e. item {
509
+ walrus:: ExportItem :: Function ( f) => Ok ( f) ,
510
+ _ => bail ! ( "`__wbindgen_malloc` wasn't a funtion" ) ,
511
+ }
435
512
}
436
513
437
514
fn implement_thread_intrinsics ( module : & mut Module , globals : & Globals ) -> Result < ( ) , Error > {
0 commit comments