11/**
22 * @fileoverview Shadow stack instrumentation for a precise GC.
3- *
3+ *
44 * Instruments function arguments and local assignments marked with a 'tostack'
55 * call to also do stores to a shadow stack of managed values only.
6- *
6+ *
77 * Consider a simple call to a function looking like the following, taking
88 * managed arguments, plus assigning managed values to locals:
9- *
9+ *
1010 * function foo(a: Obj, b: Obj): Obj {
1111 * var c = __tostack(a) // slot 2
1212 * __collect()
1313 * return b
1414 * }
15- *
15+ *
1616 * foo(__tostack(a), __tostack(b)) // slot 0, 1
17- *
17+ *
1818 * At the call to `__collect()` the 32-bit stack frame of the function is:
19- *
19+ *
2020 * Offset | Value stored
2121 * -------|----------------------------
2222 * 0 | First managed argument 'a'
2323 * 4 | Second managed argument 'b'
2424 * -------|----------------------------
2525 * 8 | First managed local 'c'
26- *
26+ *
2727 * We are splitting the frame in two halves as annotated since both halves are
2828 * only known separately for indirect calls, with the first half becoming an
2929 * extension of the calling function's stack frame by means of treating the
3030 * arguments as if these were locals beyond the caller's `numLocals`. Function
3131 * arguments stay a bit longer on the stack than usually, but we also don't have
3232 * to modify the stack pointer pre-call at all this way. The caller's amended
3333 * stack frame when assuming one managed local may look like this:
34- *
34+ *
3535 * Offset | Value stored
3636 * -------|----------------------------
3737 * 0 | First managed local '?'
3838 * 4 | Extended with first managed argument 'a'
3939 * 8 | Extended with second managed argument 'b'
40- *
40+ *
4141 * with the callee's stack frame becoming just:
42- *
42+ *
4343 * Offset | Value stored
4444 * -------|----------------------------
4545 * 0 | First managed local 'c'
46- *
46+ *
4747 * Instrumentation added below looks about like the following, with the stack
4848 * growing downwards and 't' and 'r' being new temporary locals:
49- *
49+ *
5050 * // callee frameSize = 1 * sizeof<usize>()
5151 * function foo(a: usize, b: usize): usize {
5252 * memory.fill(__stack_pointer -= frameSize, 0, frameSize)
5656 * __stack_pointer += frameSize
5757 * return r
5858 * }
59- *
59+ *
6060 * // caller frameSize = (numLocalSlots + 2 [by extension]) * sizeof<usize>()
6161 * (
6262 * r = foo(
6969 * ),
7070 * r
7171 * )
72- *
72+ *
7373 * Also note that we have to `memory.fill` the second half because the first
7474 * assignment to a local may happen at a later point within the function. The
7575 * invariant we need to maintain for a precise GC is that it only sees zeroes
7676 * or valid pointers, but never an invalid pointer left on the stack earlier.
7777 * Since most frames are small, we unroll a sequence of `store`s up to a frame
7878 * size of 16 bytes, and `memory.fill`, if available, beyond.
79- *
79+ *
8080 * @license Apache-2.0
8181 */
8282
@@ -155,7 +155,10 @@ type TempMap = Map<TypeRef,LocalIndex>;
155155
156156/** Attempts to match the `__tostack(value)` pattern. Returns `value` if a match, otherwise `0`. */
157157function 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+ ) {
159162 assert ( _BinaryenCallGetNumOperands ( expr ) == 1 ) ;
160163 return _BinaryenCallGetOperandAt ( expr , 0 ) ;
161164 }
@@ -320,7 +323,10 @@ export class ShadowStackPass extends Pass {
320323 module . global_get ( BuiltinNames . stack_pointer , this . ptrType ) ,
321324 module . global_get ( BuiltinNames . data_end , this . ptrType )
322325 ) ,
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+ )
324330 )
325331 ) ;
326332 }
@@ -579,7 +585,7 @@ export class ShadowStackPass extends Pass {
579585 ) ;
580586 // memory.fill(__stack_pointer, 0, frameSize)
581587 this . makeStackFill ( frameSize , stmts ) ;
582-
588+
583589 // Handle implicit return
584590 let body = _BinaryenFunctionGetBody ( func ) ;
585591 let bodyType = _BinaryenExpressionGetType ( body ) ;
@@ -675,4 +681,4 @@ class InstrumentReturns extends Pass {
675681 ) ;
676682 this . replaceCurrent ( module . flatten ( stmts , TypeRef . Unreachable ) ) ;
677683 }
678- }
684+ }
0 commit comments