经过一个星期的奋斗,
二进制模板函数终于实现了,当然这还是没有generic concept的版本。现在NativeX已经支持跟C#一样的模板函数了:可以被编译进独立的二进制文件,然后另外一个代码引用该二进制文件,还能实例化新的模板函数。现在先来看debug log输出的二进制结构。首先是被编译的代码。下面的代码因为是直接从语法树生成的,所以括号什么的会比较多,而且因为NativeX支持s8、s16等的数值类型后缀,代码生成的时候也使用了。一般来说没有使用的话则默认为跟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函数声明了一个数组,然后调用Sum<int32>计算结果,计算的时候要传入一个加法函数Add。Sum里面调用了Apply2去执行加法函数(纯粹是为了在模板函数里面调用另一个模板函数,没有什么特别意义)。于是用一个循环就可以把数组的和算出来了。当然结果是25。让我们来看看编译后的代码:
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
二进制模板函数的思想是,类型在编译到二进制代码后,只需要留下名字和尺寸两种信息就够了。因此模板函数除了编译成指令,还要在一个“二进制资源”里面留下一些信息,譬如说有多少个参数啦,有了参数之后将会如何组合成一个全局唯一符号(以区别尺寸相同而实际上类型不同的类型参数,有其他意义),等等。而且指令里面引用了参数尺寸的地方还要有个标记,在上面的log里就是后面带了[Linear]的东西了。Linear会变成一张表,在log的最后部分看到,其实就是一个多项式。所有跟尺寸相关的东西,最终都可以用一个多项式的方法来表达,因此我就采用了这种结构了。
譬如Apply2<T>函数在一开始push两个参数的时候,因为T的尺寸还不知道,因此参数b在堆栈中的位置就只好用一个多像是来表达了,而参数a因为a的前面只有一个固定大小的参数,因此其位置是固定的。push了之后,因为T类型不知道,所以只能用readmem指令加上一个多项式 1 [Linear]来表达其长度了。1是Linear表的索引,Linear可以在log的最后一个部分看到。
因为模板函数需要被编译到二进制文件里面,而且在被不同的二进制文件引用到的时候,相同的实例不能被特化多次(因为函数指针可以用来判断是否相等),因此特化的工作就落在了虚拟机上面了。虚拟机会根据“二进制资源”的信息去阅读一个模板函数的二进制代码,然后复制并修改,最终保存在一个内部的二进制Assembly里面,这个Assembly专门用来存放实例化后的模板函数。
接下去就可以去开始做模板全局存储区了。
posted on 2010-07-12 03:12
陈梓瀚(vczh) 阅读(3056)
评论(8) 编辑 收藏 引用 所属分类:
VL++3.0开发纪事