經(jīng)過(guò)一個(gè)星期的奮斗,
二進(jìn)制模板函數(shù)終于實(shí)現(xiàn)了,當(dāng)然這還是沒(méi)有g(shù)eneric concept的版本?,F(xiàn)在NativeX已經(jīng)支持跟C#一樣的模板函數(shù)了:可以被編譯進(jìn)獨(dú)立的二進(jìn)制文件,然后另外一個(gè)代碼引用該二進(jìn)制文件,還能實(shí)例化新的模板函數(shù)?,F(xiàn)在先來(lái)看debug log輸出的二進(jìn)制結(jié)構(gòu)。首先是被編譯的代碼。下面的代碼因?yàn)槭侵苯訌恼Z(yǔ)法樹(shù)生成的,所以括號(hào)什么的會(huì)比較多,而且因?yàn)镹ativeX支持s8、s16等的數(shù)值類型后綴,代碼生成的時(shí)候也使用了。一般來(lái)說(shuō)沒(méi)有使用的話則默認(rèn)為跟VC++的ptrdiff_t一樣的類型:
1 /*NativeX Code*/
2 unit nativex_program_generated;
3 function int32 main()
4 {
5 variable int32[5] numbers;
6 (numbers[0s32] = 1s32);
7 (numbers[1s32] = 3s32);
8 (numbers[2s32] = 5s32);
9 (numbers[3s32] = 7s32);
10 (numbers[4s32] = 9s32);
11 (result = Sum<int32>(cast<int32*>( & numbers), 5s32, 0s32, Add));
12 }
13
14 function int32 Add(int32 a, int32 b)
15 (result = (a + b));
16
17 generic<T>
18 function T Apply2(function T(T, T) f, T a, T b)
19 (result = f(a, b));
20
21 generic<T>
22 function T Sum(T* items, int32 count, T init, function T(T, T) f)
23 {
24 (result = init);
25 while((count > 0s32))
26 {
27 (result = Apply2<T>(f, result, ( * items)));
28 (count -- );
29 (items ++ );
30 }
31 }
這里的main函數(shù)聲明了一個(gè)數(shù)組,然后調(diào)用Sum<int32>計(jì)算結(jié)果,計(jì)算的時(shí)候要傳入一個(gè)加法函數(shù)Add。Sum里面調(diào)用了Apply2去執(zhí)行加法函數(shù)(純粹是為了在模板函數(shù)里面調(diào)用另一個(gè)模板函數(shù),沒(méi)有什么特別意義)。于是用一個(gè)循環(huán)就可以把數(shù)組的和算出來(lái)了。當(dāng)然結(jié)果是25。讓我們來(lái)看看編譯后的代碼:
1 /*Assembly*/
2 .data
3 .label
4 0: instruction 3
5 1: instruction 47
6 2: instruction 57
7 3: instruction 69
8 .code
9 // unit nativex_program_generated;
10 0: stack_reserve 0
11 1: stack_reserve 0
12 2: ret 0
13 // function int32 main()
14 3: stack_reserve 20
15 // (numbers[0s32] = 1s32);
16 4: push s32 1
17 5: stack_offset -20
18 6: push s32 0
19 7: push s32 4
20 8: mul s32
21 9: add s32
22 10: write s32
23 // (numbers[1s32] = 3s32);
24 11: push s32 3
25 12: stack_offset -20
26 13: push s32 1
27 14: push s32 4
28 15: mul s32
29 16: add s32
30 17: write s32
31 // (numbers[2s32] = 5s32);
32 18: push s32 5
33 19: stack_offset -20
34 20: push s32 2
35 21: push s32 4
36 22: mul s32
37 23: add s32
38 24: write s32
39 // (numbers[3s32] = 7s32);
40 25: push s32 7
41 26: stack_offset -20
42 27: push s32 3
43 28: push s32 4
44 29: mul s32
45 30: add s32
46 31: write s32
47 // (numbers[4s32] = 9s32);
48 32: push s32 9
49 33: stack_offset -20
50 34: push s32 4
51 35: push s32 4
52 36: mul s32
53 37: add s32
54 38: write s32
55 // (result = Sum<int32>(cast<int32*>( & numbers), 5s32, 0s32, Add));
56 39: pushlabel 2
57 40: push s32 0
58 41: push s32 5
59 42: stack_offset -20
60 43: resptr
61 44: generic_callfunc 0
62 // function int32 main()
63 45: stack_reserve -20
64 46: ret 0
65 // function int32 Add(int32 a, int32 b)
66 47: stack_reserve 0
67 // (result = (a + b));
68 48: stack_offset 20
69 49: read s32
70 50: stack_offset 16
71 51: read s32
72 52: add s32
73 53: resptr
74 54: write s32
75 // function int32 Add(int32 a, int32 b)
76 55: stack_reserve 0
77 56: ret 8
78 // function T Apply2(function T(T, T) f, T a, T b)
79 57: stack_reserve 0
80 // (result = f(a, b));
81 58: stack_offset 0[Linear]
82 59: readmem 1[Linear]
83 60: stack_offset 20
84 61: readmem 1[Linear]
85 62: resptr
86 63: stack_offset 16
87 64: read u32
88 65: label
89 66: call_indirect
90 // function T Apply2(function T(T, T) f, T a, T b)
91 67: stack_reserve 0
92 68: ret 2[Linear]
93 // function T Sum(T* items, int32 count, T init, function T(T, T) f)
94 69: stack_reserve 0
95 // (result = init);
96 70: stack_offset 24
97 71: resptr
98 72: copymem 1[Linear]
99 // while((count > 0s32))
100 73: push s32 0
101 74: stack_offset 20
102 75: read s32
103 76: gt s32
104 77: jumpfalse 100 1
105 // (result = Apply2<T>(f, result, ( * items)));
106 78: stack_offset 16
107 79: read u32
108 80: readmem 1[Linear]
109 81: resptr
110 82: readmem 1[Linear]
111 83: stack_offset 3[Linear]
112 84: read u32
113 85: resptr
114 86: generic_callfunc 1
115 // (count -- );
116 87: push s32 1
117 88: stack_offset 20
118 89: read s32
119 90: sub s32
120 91: stack_offset 20
121 92: write s32
122 // (items ++ );
123 93: push s32 1[Linear]
124 94: stack_offset 16
125 95: read u32
126 96: add u32
127 97: stack_offset 16
128 98: write u32
129 // while((count > 0s32))
130 99: jump 73 1
131 // function T Sum(T* items, int32 count, T init, function T(T, T) f)
132 100: stack_reserve 0
133 101: ret 4[Linear]
134 .exports
135 Assembly Name: assembly_generated
136 Exports[0] = (3, main)
137 Exports[1] = (47, Add)
138 Entries[0] = {
139 Name = Apply2
140 Arguments = 1
141 Instruction = 57
142 Lengtht = 12
143 UniqueName = [assembly_generated]::[Apply2]<{0}>
144 }
145 Entries[1] = {
146 Name = Sum
147 Arguments = 1
148 Instruction = 69
149 Lengtht = 33
150 UniqueName = [assembly_generated]::[Sum]<{0}>
151 }
152 Targets[0] = {
153 AssemblyName = assembly_generated
154 SymbolName = Sum
155 ArgumentSizes[0] = 4
156 ArgumentNames[0] = s32
157 }
158 Targets[1] = {
159 AssemblyName = assembly_generated
160 SymbolName = Apply2
161 ArgumentSizes[0] = 1*T0 + 0
162 ArgumentNames[0] = {0}
163 }
164 Linears[0] = 1*T0 + 20
165 Linears[1] = 1*T0 + 0
166 Linears[2] = 2*T0 + 4
167 Linears[3] = 1*T0 + 24
168 Linears[4] = 1*T0 + 12
二進(jìn)制模板函數(shù)的思想是,類型在編譯到二進(jìn)制代碼后,只需要留下名字和尺寸兩種信息就夠了。因此模板函數(shù)除了編譯成指令,還要在一個(gè)“二進(jìn)制資源”里面留下一些信息,譬如說(shuō)有多少個(gè)參數(shù)啦,有了參數(shù)之后將會(huì)如何組合成一個(gè)全局唯一符號(hào)(以區(qū)別尺寸相同而實(shí)際上類型不同的類型參數(shù),有其他意義),等等。而且指令里面引用了參數(shù)尺寸的地方還要有個(gè)標(biāo)記,在上面的log里就是后面帶了[Linear]的東西了。Linear會(huì)變成一張表,在log的最后部分看到,其實(shí)就是一個(gè)多項(xiàng)式。所有跟尺寸相關(guān)的東西,最終都可以用一個(gè)多項(xiàng)式的方法來(lái)表達(dá),因此我就采用了這種結(jié)構(gòu)了。
譬如Apply2<T>函數(shù)在一開(kāi)始push兩個(gè)參數(shù)的時(shí)候,因?yàn)門的尺寸還不知道,因此參數(shù)b在堆棧中的位置就只好用一個(gè)多像是來(lái)表達(dá)了,而參數(shù)a因?yàn)閍的前面只有一個(gè)固定大小的參數(shù),因此其位置是固定的。push了之后,因?yàn)門類型不知道,所以只能用readmem指令加上一個(gè)多項(xiàng)式 1 [Linear]來(lái)表達(dá)其長(zhǎng)度了。1是Linear表的索引,Linear可以在log的最后一個(gè)部分看到。
因?yàn)槟0搴瘮?shù)需要被編譯到二進(jìn)制文件里面,而且在被不同的二進(jìn)制文件引用到的時(shí)候,相同的實(shí)例不能被特化多次(因?yàn)楹瘮?shù)指針可以用來(lái)判斷是否相等),因此特化的工作就落在了虛擬機(jī)上面了。虛擬機(jī)會(huì)根據(jù)“二進(jìn)制資源”的信息去閱讀一個(gè)模板函數(shù)的二進(jìn)制代碼,然后復(fù)制并修改,最終保存在一個(gè)內(nèi)部的二進(jìn)制Assembly里面,這個(gè)Assembly專門用來(lái)存放實(shí)例化后的模板函數(shù)。
接下去就可以去開(kāi)始做模板全局存儲(chǔ)區(qū)了。