inline substitution algorithm Kei Hasegawa 2018.12.02
When inline substitution is performed? (1) If function call operator is applied to inline function which is already defined, generate 3 address code using call3ac at once, and then immediately perform inline substitution. If inline substitution is performed at this timing, it's enough to apply optimization routine at once.
When inline substitution is performed? (2) If function call operator is applied to inline function which is not defined but just declared, generate 3 address code using call3ac. Described the inline function as f . For function g which calls f, defer assembly code generation of g until f is defined. When f is defined, perform inline substitution for calling f of function g. If no inline function call at code of g and g is not either static or inline, finish generating assembly code of g. If g is declared with static or inline, code generation of g is skipped because of not referencing to g. After this, when g is referenced, enable it to generate inline substituted code. Optimization routine is applied to 3 address code of g before inline substitution of f. After inline substitution of f, it is possible for the same optimization routine to generate better code.
Data structure for inline function substitution, where the function is defined after referenced. namespace defer { map<inline function name, (inline function, use location)> refs; map<inline function name, set<caller function> > callers; map<caller function, vector<int> > positions; }
Example 1 inline into f(int a, int b, int c){ if (a) return (b+c)/a; return b-c; } inline void g1(int, int); inline int g2(int, int); int h(int i, int j, int k){ g1(j,k); return f(i,j,k) – g2(k,i); } inline void g1(int d, int e){ if (!e) return; if (d) printf("%d\n", e); } inline int g2(int x, int y){ if (x) return x+y; return y * y; }
Calling function `f' from function `h' is converted like bellow: param i => a := i param j => b := j param k => c := k t3 := call f => if a == 0 goto label0 t0 := b + c t1 := t0 / a return t1 => t3 := t1 goto label1 label0: t2 := b - c return t2 => t3 := t2 goto label2 => label1: (added) => label2: (added)
inline int f(int a, int b, int c){ if ( a ) return (b+c)/a : b-c; } skip::table[f] is created which contains `f', parameter scope of `f' and 3 address code of `f': if a == 0 goto label0 t0 := b + c t1 := t0 / a return t1 label0: t2 := b - c return t2
inline substituted code int h(int i, int j, int k){ g1(j,k); return f(i,j,k) – g2(k,i); } param j param k call g1 a := i b := j c := k if a == 0 goto label0 t0 := b + c t1 := t0 / a t3 := t1 goto label1 label0: t2 := b - c t3 := t2 goto label2 label1: label2: param i t4 := call g2 t5 := t3 - t4 return t5 defer::position[h] inline substituted code defer::position[h]
Added block scope scope::root parameter scope of h m_usrs : i, j, k m_usrs : f, g1, g2 body (block scope) of h m_vars : t4, t5 Added block scope Let operands of inline substituted 3 address code reference the entry of added block scope m_vars : a, b, c Added block scope m_vars : t0, t1, t2, t3
Defer assembly code generation of h skip::table[h] is created and register parameter scope of `h’ and 3 address code of `h’ defer::callers[g1].insert(h) Register position of call g1 at 3 address code of `h’ into defer::positions[h]. defer::callers[g2].insert(h) Samely as `g1’ register position of call g2 at 3 address code of `h’ into defer::positions[h].
inline void g1(int d, int e){ if ( inline void g1(int d, int e){ if (!e) return; if (d) printf("%d\n", e); } skip::table[g1] is created which contains g1, parameter scope of g1 and 3 address code of g1: if e != 0 goto label4 return label4: if d == 0 goto label5 t6 := &"%d\n" param t6 param e call printf label5:
By referencing to defer::callers[g1] and defer::position[h], perform inline substitution of g1 to 3 address code of h: param j => d := j param k => e := k call g1 => if e != 0 goto label4 reurn => goto label6 label4: if d == 0 goto label5 t6 := &"%d\n" param t6 param e call printf label5: label6:
position of call g1 at defer::positions[h] defer::callers[g1] position of call g1 at defer::positions[h] erased. defer::positions[h] is remained. Because position of call g2 at defer::position[h] is still remained. Defer assembly code generation of h.
inline int g2(int x, int y){ if ( x ) return x+y; return y * y; } skip::table[g2] is created which contains g2, parameter scope of g2 and 3 address code of g2: if x == 0 goto label7 t7 := x + y return t7 label7: t8 := y * y return t8 By referencing defer::caller[g2] and defer::positions[h], perform inline substitution of g2 to 3 address code of h:
param k => x := k param i => y := i t4 := call g2 => if x == 0 goto label7 t7 := x + y return t7 => t4 := t7 goto label8 label7: t8 := y * y return t8 => t4 := t8 goto label9 label8: (added) label9: (added)
inline substitution algorithm Input 3 address code and position of calling inline function inline function, parameter scope of inline function and 3 address code of inline function Output inline substituted 3 address code Number of 3 address code which replaces for each call3ac
param a1 ... param an x := call f If inline function f takes n arguments, there are n param3ac before calling f. These param3ac are converted to: pi := ai where, pi is a copy of i'th parameter of f. Register p1, ..., pn to added block scope.
Replace x := call f or call f to copies of 3 address code of f Operands of replaced 3 address code may be local variables except for p1, ..., pn, or medium variables. Register these to added block scope. return y of replaced 3 address code of `f' is converted to: x := y goto end where, `end' is a new label and is placed to the end of 3 address code replaced for x := call f. If y is null pointer ( i.e. just return ) , not generate x := y If y is not null pointer but x is null pointer ( i.e. just call f ) , not generate x := y Number of 3 address code which replaces for call3ac is a sum of: number of 3 address code of `f'. number of return y of 3 address code of `f', where y is none-zero.
Inline function which takes variable number of arguments Give up Inline substitution example inline int printf(const char* fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(fmt, ap); ... } void f(int a, double b) { printf(“%d %f\n”, a, b); }
Inline function which takes variable number of arguments(2) 3 address code of previous page printf: ap := va_start fmt param fmt param ap call vfprintf ... f: t0 := &”%d %f\n” param a param b call printf 3 address code afeter inline substitution f: t0 := “%d %f\n” fmt := t0 param a param b call vfprintf ...
Inline function which takes variable number of arguments (3) difficult inline substitution example inline int printf(const char* fmt, ...) { va_list ap; va_start(ap, fmt); ... int n = va_arg(ap, int); }
Example 2 inline int h(int, int); int main(){ call h } inline int f0(int a, int b){ ... } inline int f1(int a, int b){ ... } inline int g0(int, int); inline int g1(int, int); int h(int a, int b){ call f0, f1, g0 and g1 } inline int g0(int a, int b){ ...} inline int g1(int a, int b){ ... }
After `main’ Before `h’ After `h’ After `g0’ After `g1’ `main’ is registered into skip::table Because of inline function call `h’ from `main’ Before `h’ f0 and f1 are registered into skip::table normally. After `h’ h is registered into skip::table Because `h’ is declared as inline function For function call `h’ of `main’, inline substitution is performed because `main’ exists at callers[h]. While code generation of `main’ Because inline function call g0 and g1 still remain at 3 address code of `main’ , `main’ is registered into skip::table again. After `g0’ g0 is registered into skip::table normally. Perform inline substitution of `g0’ because `main’ exists at callers[g0] Because inline function call g1 still remains at 3 address code of `main’, `main’ is not erased from skip::table. After `g1’ g1 is registered into skip::table normally. Because `main’ exists at callers[g1] , perform inline substitution of `g1’ Because no inline function call at 3 address code of `main’, finish generating assembly code of `main’. Of cause, `main’ is erased from skip::table.
How to give up inline substitution inline int fact(int n){ return n ? n * fact(n-1) : 1; } int main(){ ...; fact(10); ... }
For calling fact at `main’ After `main’ After `fact’ `fact’ is registerd into skip::table. For calling fact at `main’ Perform inline substitution as usual. After `main’ While generating code of `main’ inline function `fact’ call exists But `fact’ exists in skip::table Generate code of `fact’ ( and finish it). Finish generating code of `main’. A inline function exists in skip::table, but still exists function call Give up inline substitution.