1
1
/**
2
2
* @fileoverview Shadow stack instrumentation for a precise GC.
3
- *
3
+ *
4
4
* Instruments function arguments and local assignments marked with a 'tostack'
5
5
* call to also do stores to a shadow stack of managed values only.
6
- *
6
+ *
7
7
* Consider a simple call to a function looking like the following, taking
8
8
* managed arguments, plus assigning managed values to locals:
9
- *
9
+ *
10
10
* function foo(a: Obj, b: Obj): Obj {
11
11
* var c = __tostack(a) // slot 2
12
12
* __collect()
13
13
* return b
14
14
* }
15
- *
15
+ *
16
16
* foo(__tostack(a), __tostack(b)) // slot 0, 1
17
- *
17
+ *
18
18
* At the call to `__collect()` the 32-bit stack frame of the function is:
19
- *
19
+ *
20
20
* Offset | Value stored
21
21
* -------|----------------------------
22
22
* 0 | First managed argument 'a'
23
23
* 4 | Second managed argument 'b'
24
24
* -------|----------------------------
25
25
* 8 | First managed local 'c'
26
- *
26
+ *
27
27
* We are splitting the frame in two halves as annotated since both halves are
28
28
* only known separately for indirect calls, with the first half becoming an
29
29
* extension of the calling function's stack frame by means of treating the
30
30
* arguments as if these were locals beyond the caller's `numLocals`. Function
31
31
* arguments stay a bit longer on the stack than usually, but we also don't have
32
32
* to modify the stack pointer pre-call at all this way. The caller's amended
33
33
* stack frame when assuming one managed local may look like this:
34
- *
34
+ *
35
35
* Offset | Value stored
36
36
* -------|----------------------------
37
37
* 0 | First managed local '?'
38
38
* 4 | Extended with first managed argument 'a'
39
39
* 8 | Extended with second managed argument 'b'
40
- *
40
+ *
41
41
* with the callee's stack frame becoming just:
42
- *
42
+ *
43
43
* Offset | Value stored
44
44
* -------|----------------------------
45
45
* 0 | First managed local 'c'
46
- *
46
+ *
47
47
* Instrumentation added below looks about like the following, with the stack
48
48
* growing downwards and 't' and 'r' being new temporary locals:
49
- *
49
+ *
50
50
* // callee frameSize = 1 * sizeof<usize>()
51
51
* function foo(a: usize, b: usize): usize {
52
52
* memory.fill(__stack_pointer -= frameSize, 0, frameSize)
56
56
* __stack_pointer += frameSize
57
57
* return r
58
58
* }
59
- *
59
+ *
60
60
* // caller frameSize = (numLocalSlots + 2 [by extension]) * sizeof<usize>()
61
61
* (
62
62
* r = foo(
69
69
* ),
70
70
* r
71
71
* )
72
- *
72
+ *
73
73
* Also note that we have to `memory.fill` the second half because the first
74
74
* assignment to a local may happen at a later point within the function. The
75
75
* invariant we need to maintain for a precise GC is that it only sees zeroes
76
76
* or valid pointers, but never an invalid pointer left on the stack earlier.
77
77
* Since most frames are small, we unroll a sequence of `store`s up to a frame
78
78
* size of 16 bytes, and `memory.fill`, if available, beyond.
79
- *
79
+ *
80
80
* @license Apache-2.0
81
81
*/
82
82
@@ -155,7 +155,10 @@ type TempMap = Map<TypeRef,LocalIndex>;
155
155
156
156
/** Attempts to match the `__tostack(value)` pattern. Returns `value` if a match, otherwise `0`. */
157
157
function matchPattern ( module : Module , expr : ExpressionRef ) : ExpressionRef {
158
- if ( _BinaryenExpressionGetId ( expr ) == ExpressionId . Call && module . readStringCached ( _BinaryenCallGetTarget ( expr ) ) == BuiltinNames . tostack ) {
158
+ if (
159
+ _BinaryenExpressionGetId ( expr ) == ExpressionId . Call &&
160
+ module . readStringCached ( _BinaryenCallGetTarget ( expr ) ) == BuiltinNames . tostack
161
+ ) {
159
162
assert ( _BinaryenCallGetNumOperands ( expr ) == 1 ) ;
160
163
return _BinaryenCallGetOperandAt ( expr , 0 ) ;
161
164
}
@@ -320,7 +323,10 @@ export class ShadowStackPass extends Pass {
320
323
module . global_get ( BuiltinNames . stack_pointer , this . ptrType ) ,
321
324
module . global_get ( BuiltinNames . data_end , this . ptrType )
322
325
) ,
323
- this . compiler . makeStaticAbort ( this . compiler . ensureStaticString ( "stack overflow" ) , this . compiler . program . nativeSource )
326
+ this . compiler . makeStaticAbort (
327
+ this . compiler . ensureStaticString ( "stack overflow" ) ,
328
+ this . compiler . program . nativeSource
329
+ )
324
330
)
325
331
) ;
326
332
}
@@ -579,7 +585,7 @@ export class ShadowStackPass extends Pass {
579
585
) ;
580
586
// memory.fill(__stack_pointer, 0, frameSize)
581
587
this . makeStackFill ( frameSize , stmts ) ;
582
-
588
+
583
589
// Handle implicit return
584
590
let body = _BinaryenFunctionGetBody ( func ) ;
585
591
let bodyType = _BinaryenExpressionGetType ( body ) ;
@@ -675,4 +681,4 @@ class InstrumentReturns extends Pass {
675
681
) ;
676
682
this . replaceCurrent ( module . flatten ( stmts , TypeRef . Unreachable ) ) ;
677
683
}
678
- }
684
+ }
0 commit comments