Vczh Library++3.0实现二进制模板函数
经过一个星期的奋斗, 二进制模板函数终于实现了,当然这还是没有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 }
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
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专门用来存放实例化后的模板函数。
接下去就可以去开始做模板全局存储区了。