@@ -5,7 +5,6 @@ package compiler
5
5
6
6
import (
7
7
"go/token"
8
- "math/big"
9
8
10
9
"golang.org/x/tools/go/ssa"
11
10
"tinygo.org/x/go-llvm"
@@ -105,359 +104,3 @@ func typeHasPointers(t llvm.Type) bool {
105
104
return false
106
105
}
107
106
}
108
-
109
- // makeGCStackSlots converts all calls to runtime.trackPointer to explicit
110
- // stores to stack slots that are scannable by the GC.
111
- func (c * Compiler ) makeGCStackSlots () bool {
112
- // Check whether there are allocations at all.
113
- alloc := c .mod .NamedFunction ("runtime.alloc" )
114
- if alloc .IsNil () {
115
- // Nothing to. Make sure all remaining bits and pieces for stack
116
- // chains are neutralized.
117
- for _ , call := range getUses (c .mod .NamedFunction ("runtime.trackPointer" )) {
118
- call .EraseFromParentAsInstruction ()
119
- }
120
- stackChainStart := c .mod .NamedGlobal ("runtime.stackChainStart" )
121
- if ! stackChainStart .IsNil () {
122
- stackChainStart .SetInitializer (llvm .ConstNull (stackChainStart .Type ().ElementType ()))
123
- stackChainStart .SetGlobalConstant (true )
124
- }
125
- return false
126
- }
127
-
128
- trackPointer := c .mod .NamedFunction ("runtime.trackPointer" )
129
- if trackPointer .IsNil () || trackPointer .FirstUse ().IsNil () {
130
- return false // nothing to do
131
- }
132
-
133
- // Look at *all* functions to see whether they are free of function pointer
134
- // calls.
135
- // This takes less than 5ms for ~100kB of WebAssembly but would perhaps be
136
- // faster when written in C++ (to avoid the CGo overhead).
137
- funcsWithFPCall := map [llvm.Value ]struct {}{}
138
- n := 0
139
- for fn := c .mod .FirstFunction (); ! fn .IsNil (); fn = llvm .NextFunction (fn ) {
140
- n ++
141
- if _ , ok := funcsWithFPCall [fn ]; ok {
142
- continue // already found
143
- }
144
- done := false
145
- for bb := fn .FirstBasicBlock (); ! bb .IsNil () && ! done ; bb = llvm .NextBasicBlock (bb ) {
146
- for call := bb .FirstInstruction (); ! call .IsNil () && ! done ; call = llvm .NextInstruction (call ) {
147
- if call .IsACallInst ().IsNil () {
148
- continue // only looking at calls
149
- }
150
- called := call .CalledValue ()
151
- if ! called .IsAFunction ().IsNil () {
152
- continue // only looking for function pointers
153
- }
154
- funcsWithFPCall [fn ] = struct {}{}
155
- markParentFunctions (funcsWithFPCall , fn )
156
- done = true
157
- }
158
- }
159
- }
160
-
161
- // Determine which functions need stack objects. Many leaf functions don't
162
- // need it: it only causes overhead for them.
163
- // Actually, in one test it was only able to eliminate stack object from 12%
164
- // of functions that had a call to runtime.trackPointer (8 out of 68
165
- // functions), so this optimization is not as big as it may seem.
166
- allocatingFunctions := map [llvm.Value ]struct {}{} // set of allocating functions
167
-
168
- // Work from runtime.alloc and trace all parents to check which functions do
169
- // a heap allocation (and thus which functions do not).
170
- markParentFunctions (allocatingFunctions , alloc )
171
-
172
- // Also trace all functions that call a function pointer.
173
- for fn := range funcsWithFPCall {
174
- // Assume that functions that call a function pointer do a heap
175
- // allocation as a conservative guess because the called function might
176
- // do a heap allocation.
177
- allocatingFunctions [fn ] = struct {}{}
178
- markParentFunctions (allocatingFunctions , fn )
179
- }
180
-
181
- // Collect some variables used below in the loop.
182
- stackChainStart := c .mod .NamedGlobal ("runtime.stackChainStart" )
183
- if stackChainStart .IsNil () {
184
- // This may be reached in a weird scenario where we call runtime.alloc but the garbage collector is unreachable.
185
- // This can be accomplished by allocating 0 bytes.
186
- // There is no point in tracking anything.
187
- for _ , use := range getUses (trackPointer ) {
188
- use .EraseFromParentAsInstruction ()
189
- }
190
- return false
191
- }
192
- stackChainStartType := stackChainStart .Type ().ElementType ()
193
- stackChainStart .SetInitializer (llvm .ConstNull (stackChainStartType ))
194
-
195
- // Iterate until runtime.trackPointer has no uses left.
196
- for use := trackPointer .FirstUse (); ! use .IsNil (); use = trackPointer .FirstUse () {
197
- // Pick the first use of runtime.trackPointer.
198
- call := use .User ()
199
- if call .IsACallInst ().IsNil () {
200
- panic ("expected runtime.trackPointer use to be a call" )
201
- }
202
-
203
- // Pick the parent function.
204
- fn := call .InstructionParent ().Parent ()
205
-
206
- if _ , ok := allocatingFunctions [fn ]; ! ok {
207
- // This function nor any of the functions it calls (recursively)
208
- // allocate anything from the heap, so it will not trigger a garbage
209
- // collection cycle. Thus, it does not need to track local pointer
210
- // values.
211
- // This is a useful optimization but not as big as you might guess,
212
- // as described above (it avoids stack objects for ~12% of
213
- // functions).
214
- call .EraseFromParentAsInstruction ()
215
- continue
216
- }
217
-
218
- // Find all calls to runtime.trackPointer in this function.
219
- var calls []llvm.Value
220
- var returns []llvm.Value
221
- for bb := fn .FirstBasicBlock (); ! bb .IsNil (); bb = llvm .NextBasicBlock (bb ) {
222
- for inst := bb .FirstInstruction (); ! inst .IsNil (); inst = llvm .NextInstruction (inst ) {
223
- switch inst .InstructionOpcode () {
224
- case llvm .Call :
225
- if inst .CalledValue () == trackPointer {
226
- calls = append (calls , inst )
227
- }
228
- case llvm .Ret :
229
- returns = append (returns , inst )
230
- }
231
- }
232
- }
233
-
234
- // Determine what to do with each call.
235
- var allocas , pointers []llvm.Value
236
- for _ , call := range calls {
237
- ptr := call .Operand (0 )
238
- call .EraseFromParentAsInstruction ()
239
- if ptr .IsAInstruction ().IsNil () {
240
- continue
241
- }
242
-
243
- // Some trivial optimizations.
244
- if ptr .IsAInstruction ().IsNil () {
245
- continue
246
- }
247
- switch ptr .InstructionOpcode () {
248
- case llvm .PHI , llvm .GetElementPtr :
249
- // These values do not create new values: the values already
250
- // existed locally in this function so must have been tracked
251
- // already.
252
- continue
253
- case llvm .ExtractValue , llvm .BitCast :
254
- // These instructions do not create new values, but their
255
- // original value may not be tracked. So keep tracking them for
256
- // now.
257
- // With more analysis, it should be possible to optimize a
258
- // significant chunk of these away.
259
- case llvm .Call , llvm .Load , llvm .IntToPtr :
260
- // These create new values so must be stored locally. But
261
- // perhaps some of these can be fused when they actually refer
262
- // to the same value.
263
- default :
264
- // Ambiguous. These instructions are uncommon, but perhaps could
265
- // be optimized if needed.
266
- }
267
-
268
- if ! ptr .IsAAllocaInst ().IsNil () {
269
- if typeHasPointers (ptr .Type ().ElementType ()) {
270
- allocas = append (allocas , ptr )
271
- }
272
- } else {
273
- pointers = append (pointers , ptr )
274
- }
275
- }
276
-
277
- if len (allocas ) == 0 && len (pointers ) == 0 {
278
- // This function does not need to keep track of stack pointers.
279
- continue
280
- }
281
-
282
- // Determine the type of the required stack slot.
283
- fields := []llvm.Type {
284
- stackChainStartType , // Pointer to parent frame.
285
- c .uintptrType , // Number of elements in this frame.
286
- }
287
- for _ , alloca := range allocas {
288
- fields = append (fields , alloca .Type ().ElementType ())
289
- }
290
- for _ , ptr := range pointers {
291
- fields = append (fields , ptr .Type ())
292
- }
293
- stackObjectType := c .ctx .StructType (fields , false )
294
-
295
- // Create the stack object at the function entry.
296
- c .builder .SetInsertPointBefore (fn .EntryBasicBlock ().FirstInstruction ())
297
- stackObject := c .builder .CreateAlloca (stackObjectType , "gc.stackobject" )
298
- initialStackObject := llvm .ConstNull (stackObjectType )
299
- numSlots := (c .targetData .TypeAllocSize (stackObjectType ) - c .targetData .TypeAllocSize (c .i8ptrType )* 2 ) / uint64 (c .targetData .ABITypeAlignment (c .uintptrType ))
300
- numSlotsValue := llvm .ConstInt (c .uintptrType , numSlots , false )
301
- initialStackObject = llvm .ConstInsertValue (initialStackObject , numSlotsValue , []uint32 {1 })
302
- c .builder .CreateStore (initialStackObject , stackObject )
303
-
304
- // Update stack start.
305
- parent := c .builder .CreateLoad (stackChainStart , "" )
306
- gep := c .builder .CreateGEP (stackObject , []llvm.Value {
307
- llvm .ConstInt (c .ctx .Int32Type (), 0 , false ),
308
- llvm .ConstInt (c .ctx .Int32Type (), 0 , false ),
309
- }, "" )
310
- c .builder .CreateStore (parent , gep )
311
- stackObjectCast := c .builder .CreateBitCast (stackObject , stackChainStartType , "" )
312
- c .builder .CreateStore (stackObjectCast , stackChainStart )
313
-
314
- // Replace all independent allocas with GEPs in the stack object.
315
- for i , alloca := range allocas {
316
- gep := c .builder .CreateGEP (stackObject , []llvm.Value {
317
- llvm .ConstInt (c .ctx .Int32Type (), 0 , false ),
318
- llvm .ConstInt (c .ctx .Int32Type (), uint64 (2 + i ), false ),
319
- }, "" )
320
- alloca .ReplaceAllUsesWith (gep )
321
- alloca .EraseFromParentAsInstruction ()
322
- }
323
-
324
- // Do a store to the stack object after each new pointer that is created.
325
- for i , ptr := range pointers {
326
- c .builder .SetInsertPointBefore (llvm .NextInstruction (ptr ))
327
- gep := c .builder .CreateGEP (stackObject , []llvm.Value {
328
- llvm .ConstInt (c .ctx .Int32Type (), 0 , false ),
329
- llvm .ConstInt (c .ctx .Int32Type (), uint64 (2 + len (allocas )+ i ), false ),
330
- }, "" )
331
- c .builder .CreateStore (ptr , gep )
332
- }
333
-
334
- // Make sure this stack object is popped from the linked list of stack
335
- // objects at return.
336
- for _ , ret := range returns {
337
- c .builder .SetInsertPointBefore (ret )
338
- c .builder .CreateStore (parent , stackChainStart )
339
- }
340
- }
341
-
342
- return true
343
- }
344
-
345
- func (c * Compiler ) addGlobalsBitmap () bool {
346
- if c .mod .NamedGlobal ("runtime.trackedGlobalsStart" ).IsNil () {
347
- return false // nothing to do: no GC in use
348
- }
349
-
350
- var trackedGlobals []llvm.Value
351
- var trackedGlobalTypes []llvm.Type
352
- for global := c .mod .FirstGlobal (); ! global .IsNil (); global = llvm .NextGlobal (global ) {
353
- if global .IsDeclaration () {
354
- continue
355
- }
356
- typ := global .Type ().ElementType ()
357
- ptrs := c .getPointerBitmap (typ , global .Name ())
358
- if ptrs .BitLen () == 0 {
359
- continue
360
- }
361
- trackedGlobals = append (trackedGlobals , global )
362
- trackedGlobalTypes = append (trackedGlobalTypes , typ )
363
- }
364
-
365
- globalsBundleType := c .ctx .StructType (trackedGlobalTypes , false )
366
- globalsBundle := llvm .AddGlobal (c .mod , globalsBundleType , "tinygo.trackedGlobals" )
367
- globalsBundle .SetLinkage (llvm .InternalLinkage )
368
- globalsBundle .SetUnnamedAddr (true )
369
- initializer := llvm .Undef (globalsBundleType )
370
- for i , global := range trackedGlobals {
371
- initializer = llvm .ConstInsertValue (initializer , global .Initializer (), []uint32 {uint32 (i )})
372
- gep := llvm .ConstGEP (globalsBundle , []llvm.Value {
373
- llvm .ConstInt (c .ctx .Int32Type (), 0 , false ),
374
- llvm .ConstInt (c .ctx .Int32Type (), uint64 (i ), false ),
375
- })
376
- global .ReplaceAllUsesWith (gep )
377
- global .EraseFromParentAsGlobal ()
378
- }
379
- globalsBundle .SetInitializer (initializer )
380
-
381
- trackedGlobalsStart := llvm .ConstPtrToInt (globalsBundle , c .uintptrType )
382
- c .mod .NamedGlobal ("runtime.trackedGlobalsStart" ).SetInitializer (trackedGlobalsStart )
383
-
384
- alignment := c .targetData .PrefTypeAlignment (c .i8ptrType )
385
- trackedGlobalsLength := llvm .ConstInt (c .uintptrType , c .targetData .TypeAllocSize (globalsBundleType )/ uint64 (alignment ), false )
386
- c .mod .NamedGlobal ("runtime.trackedGlobalsLength" ).SetInitializer (trackedGlobalsLength )
387
-
388
- bitmapBytes := c .getPointerBitmap (globalsBundleType , "globals bundle" ).Bytes ()
389
- bitmapValues := make ([]llvm.Value , len (bitmapBytes ))
390
- for i , b := range bitmapBytes {
391
- bitmapValues [len (bitmapBytes )- i - 1 ] = llvm .ConstInt (c .ctx .Int8Type (), uint64 (b ), false )
392
- }
393
- bitmapArray := llvm .ConstArray (c .ctx .Int8Type (), bitmapValues )
394
- bitmapNew := llvm .AddGlobal (c .mod , bitmapArray .Type (), "runtime.trackedGlobalsBitmap.tmp" )
395
- bitmapOld := c .mod .NamedGlobal ("runtime.trackedGlobalsBitmap" )
396
- bitmapOld .ReplaceAllUsesWith (llvm .ConstBitCast (bitmapNew , bitmapOld .Type ()))
397
- bitmapNew .SetInitializer (bitmapArray )
398
- bitmapNew .SetName ("runtime.trackedGlobalsBitmap" )
399
-
400
- return true // the IR was changed
401
- }
402
-
403
- func (c * Compiler ) getPointerBitmap (typ llvm.Type , name string ) * big.Int {
404
- alignment := c .targetData .PrefTypeAlignment (c .i8ptrType )
405
- switch typ .TypeKind () {
406
- case llvm .IntegerTypeKind , llvm .FloatTypeKind , llvm .DoubleTypeKind :
407
- return big .NewInt (0 )
408
- case llvm .PointerTypeKind :
409
- return big .NewInt (1 )
410
- case llvm .StructTypeKind :
411
- ptrs := big .NewInt (0 )
412
- for i , subtyp := range typ .StructElementTypes () {
413
- subptrs := c .getPointerBitmap (subtyp , name )
414
- if subptrs .BitLen () == 0 {
415
- continue
416
- }
417
- offset := c .targetData .ElementOffset (typ , i )
418
- if offset % uint64 (alignment ) != 0 {
419
- panic ("precise GC: global contains unaligned pointer: " + name )
420
- }
421
- subptrs .Lsh (subptrs , uint (offset )/ uint (alignment ))
422
- ptrs .Or (ptrs , subptrs )
423
- }
424
- return ptrs
425
- case llvm .ArrayTypeKind :
426
- subtyp := typ .ElementType ()
427
- subptrs := c .getPointerBitmap (subtyp , name )
428
- ptrs := big .NewInt (0 )
429
- if subptrs .BitLen () == 0 {
430
- return ptrs
431
- }
432
- elementSize := c .targetData .TypeAllocSize (subtyp )
433
- for i := 0 ; i < typ .ArrayLength (); i ++ {
434
- ptrs .Lsh (ptrs , uint (elementSize )/ uint (alignment ))
435
- ptrs .Or (ptrs , subptrs )
436
- }
437
- return ptrs
438
- default :
439
- panic ("unknown type kind of global: " + name )
440
- }
441
- }
442
-
443
- // markParentFunctions traverses all parent function calls (recursively) and
444
- // adds them to the set of marked functions. It only considers function calls:
445
- // any other uses of such a function is ignored.
446
- func markParentFunctions (marked map [llvm.Value ]struct {}, fn llvm.Value ) {
447
- worklist := []llvm.Value {fn }
448
- for len (worklist ) != 0 {
449
- fn := worklist [len (worklist )- 1 ]
450
- worklist = worklist [:len (worklist )- 1 ]
451
- for _ , use := range getUses (fn ) {
452
- if use .IsACallInst ().IsNil () || use .CalledValue () != fn {
453
- // Not the parent function.
454
- continue
455
- }
456
- parent := use .InstructionParent ().Parent ()
457
- if _ , ok := marked [parent ]; ! ok {
458
- marked [parent ] = struct {}{}
459
- worklist = append (worklist , parent )
460
- }
461
- }
462
- }
463
- }
0 commit comments