Hello, everybody!
The script language Kinx was published as the version 1.0.0 here. The concept is, "Looks like JavaScript, feels like Ruby, and it is a script language fitting in C programmers."
But I realized there has been no article of JIT library in spite of a key feature, so I am going to post about JIT library.
If you were interested in JIT in Kinx, please see the document of JIT Compiler and Native for details.
Introduction
You can use JIT in Kinx for performance improvement. There are 2 ways to use JIT compilation below.
- Use
native
keyword. - Use an abstracted assembler library.
I introduced native
in some articles, for example, see Mandelbrot benchmark. It is a good start point to improve performance, so I will start this article from trying it with native
.
When Not Using JIT
First of all, when it's the case without JIT, you will normally write the code below.
function fib(n) {
if (n <= 3) return n;
return fib(n-2) + fib(n-1);
}
This is very simple. Now, this code will be modified to improve performance with JIT.
When Using JIT
native
Keyword
The first try is to replace function
by native
. The native
keyword is a keyword to compile it to the native code directly.
native fib(n) {
if (n <= 3) return n;
return fib(n-2) + fib(n-1);
}
This will generate an assemble code below on Windows. This is a little long because it's including a type check and exception check. The pros is to write it easily, but the cons is that it can generate a little redundant code and it with some limitations. The code is redundant but necessary.
fib: (native-base:0x1b96340010)
0: 53 push rbx
1: 56 push rsi
2: 57 push rdi
3: 41 57 push r15
5: 41 56 push r14
7: 41 55 push r13
9: 55 push rbp
a: 41 54 push r12
c: 48 8b d9 mov rbx, rcx
f: 48 8b f2 mov rsi, rdx
12: 49 8b f8 mov rdi, r8
15: 4c 8b 8c 24 a8 fd ff ff mov r9, [rsp-0x258]
1d: 48 81 ec 58 02 00 00 sub rsp, 0x258
24: 48 8b 46 08 mov rax, [rsi+0x8]
28: 48 83 c0 01 add rax, 0x1
2c: 48 89 46 08 mov [rsi+0x8], rax
30: 48 3d 00 04 00 00 cmp rax, 0x400
36: 72 2b jb 0x63
38: 48 c7 43 20 01 00 00 00 mov qword [rbx+0x20], 0x1
40: 48 c7 43 28 06 00 00 00 mov qword [rbx+0x28], 0x6
48: 48 c7 c0 00 00 00 00 mov rax, 0x0
4f: 48 81 c4 58 02 00 00 add rsp, 0x258
56: 41 5c pop r12
58: 5d pop rbp
59: 41 5d pop r13
5b: 41 5e pop r14
5d: 41 5f pop r15
5f: 5f pop rdi
60: 5e pop rsi
61: 5b pop rbx
62: c3 ret
63: 48 83 be 18 01 00 00 01 cmp qword [rsi+0x118], 0x1
6b: 0f 85 30 01 00 00 jnz 0x1a1
71: 4c 8b 4e 18 mov r9, [rsi+0x18]
75: 4c 89 4c 24 20 mov [rsp+0x20], r9
7a: 4c 8b 74 24 20 mov r14, [rsp+0x20]
7f: 4c 89 f0 mov rax, r14
82: 48 83 f8 03 cmp rax, 0x3
86: 7f 1c jg 0xa4
88: 4c 8b 74 24 20 mov r14, [rsp+0x20]
8d: 4c 89 f0 mov rax, r14
90: 48 81 c4 58 02 00 00 add rsp, 0x258
97: 41 5c pop r12
99: 5d pop rbp
9a: 41 5d pop r13
9c: 41 5e pop r14
9e: 41 5f pop r15
a0: 5f pop rdi
a1: 5e pop rsi
a2: 5b pop rbx
a3: c3 ret
a4: 4c 8b 74 24 20 mov r14, [rsp+0x20]
a9: 49 8d 46 fe lea rax, [r14-0x2]
ad: 48 89 44 24 40 mov [rsp+0x40], rax
b2: 48 c7 84 24 40 01 00 00 01 00 00 00 mov qword [rsp+0x140], 0x1
be: 48 8b 4e 10 mov rcx, [rsi+0x10]
c2: 48 89 d8 mov rax, rbx
c5: 4c 8b 4e 08 mov r9, [rsi+0x8]
c9: 4c 89 4c 24 30 mov [rsp+0x30], r9
ce: 48 89 4c 24 38 mov [rsp+0x38], rcx
d3: 48 8d 54 24 28 lea rdx, [rsp+0x28]
d8: 49 89 ca mov r10, rcx
db: 48 89 c1 mov rcx, rax
de: 41 ff d2 call r10
e1: 49 89 c6 mov r14, rax
e4: 48 8b 43 20 mov rax, [rbx+0x20]
e8: 48 83 f8 00 cmp rax, 0x0
ec: 74 1b jz 0x109
ee: 48 c7 c0 00 00 00 00 mov rax, 0x0
f5: 48 81 c4 58 02 00 00 add rsp, 0x258
fc: 41 5c pop r12
fe: 5d pop rbp
ff: 41 5d pop r13
101: 41 5e pop r14
103: 41 5f pop r15
105: 5f pop rdi
106: 5e pop rsi
107: 5b pop rbx
108: c3 ret
109: 4c 8b 6c 24 20 mov r13, [rsp+0x20]
10e: 49 8d 45 ff lea rax, [r13-0x1]
112: 48 89 44 24 40 mov [rsp+0x40], rax
117: 48 c7 84 24 40 01 00 00 01 00 00 00 mov qword [rsp+0x140], 0x1
123: 48 8b 4e 10 mov rcx, [rsi+0x10]
127: 48 89 d8 mov rax, rbx
12a: 4c 8b 4e 08 mov r9, [rsi+0x8]
12e: 4c 89 4c 24 30 mov [rsp+0x30], r9
133: 48 89 4c 24 38 mov [rsp+0x38], rcx
138: 48 8d 54 24 28 lea rdx, [rsp+0x28]
13d: 49 89 ca mov r10, rcx
140: 48 89 c1 mov rcx, rax
143: 41 ff d2 call r10
146: 49 89 c5 mov r13, rax
149: 48 8b 43 20 mov rax, [rbx+0x20]
14d: 48 83 f8 00 cmp rax, 0x0
151: 74 1b jz 0x16e
153: 48 c7 c0 00 00 00 00 mov rax, 0x0
15a: 48 81 c4 58 02 00 00 add rsp, 0x258
161: 41 5c pop r12
163: 5d pop rbp
164: 41 5d pop r13
166: 41 5e pop r14
168: 41 5f pop r15
16a: 5f pop rdi
16b: 5e pop rsi
16c: 5b pop rbx
16d: c3 ret
16e: 4b 8d 04 2e lea rax, [r14+r13]
172: 48 81 c4 58 02 00 00 add rsp, 0x258
179: 41 5c pop r12
17b: 5d pop rbp
17c: 41 5d pop r13
17e: 41 5e pop r14
180: 41 5f pop r15
182: 5f pop rdi
183: 5e pop rsi
184: 5b pop rbx
185: c3 ret
186: 48 c7 c0 00 00 00 00 mov rax, 0x0
18d: 48 81 c4 58 02 00 00 add rsp, 0x258
194: 41 5c pop r12
196: 5d pop rbp
197: 41 5d pop r13
199: 41 5e pop r14
19b: 41 5f pop r15
19d: 5f pop rdi
19e: 5e pop rsi
19f: 5b pop rbx
1a0: c3 ret
1a1: 48 c7 43 20 01 00 00 00 mov qword [rbx+0x20], 0x1
1a9: 48 c7 43 28 07 00 00 00 mov qword [rbx+0x28], 0x7
1b1: 48 c7 c0 00 00 00 00 mov rax, 0x0
1b8: 48 81 c4 58 02 00 00 add rsp, 0x258
1bf: 41 5c pop r12
1c1: 5d pop rbp
1c2: 41 5d pop r13
1c4: 41 5e pop r14
1c6: 41 5f pop r15
1c8: 5f pop rdi
1c9: 5e pop rsi
1ca: 5b pop rbx
1cb: c3 ret
Abstracted Assembler Library
Kinx also has a JIT library to use an abstracted assembler.
That library can be available with using JIT
at the head of source code.
Let's use it like this.
using Jit;
var c = new Jit.Compiler();
var entry1 = c.enter();
var jump0 = c.ge(Jit.S0, Jit.IMM(3));
c.ret(Jit.S0);
var l1 = c.label();
c.sub(Jit.R0, Jit.S0, Jit.IMM(2));
c.call(entry1);
c.mov(Jit.S1, Jit.R0);
c.sub(Jit.R0, Jit.S0, Jit.IMM(1));
c.call(entry1);
c.add(Jit.R0, Jit.R0, Jit.S1);
c.ret(Jit.R0);
jump0.setLabel(l1);
var code = c.generate();
You can see the assembled code by code.dump()
. Here it is.
0: 53 push rbx
1: 56 push rsi
2: 57 push rdi
3: 48 8b d9 mov rbx, rcx
6: 48 8b f2 mov rsi, rdx
9: 49 8b f8 mov rdi, r8
c: 4c 8b 4c 24 d0 mov r9, [rsp-0x30]
11: 48 83 ec 30 sub rsp, 0x30
15: 48 83 fb 03 cmp rbx, 0x3
19: 73 0b jae 0x26
1b: 48 89 d8 mov rax, rbx
1e: 48 83 c4 30 add rsp, 0x30
22: 5f pop rdi
23: 5e pop rsi
24: 5b pop rbx
25: c3 ret
26: 48 8d 43 fe lea rax, [rbx-0x2]
2a: 48 89 c1 mov rcx, rax
2d: e8 ce ff ff ff call 0x0
32: 48 89 c6 mov rsi, rax
35: 48 8d 43 ff lea rax, [rbx-0x1]
39: 48 89 c1 mov rcx, rax
3c: e8 bf ff ff ff call 0x0
41: 48 03 c6 add rax, rsi
44: 48 83 c4 30 add rsp, 0x30
48: 5f pop rdi
49: 5e pop rsi
4a: 5b pop rbx
4b: c3 ret
It should be simpler than the native
. It is very obvious because it is as you write. There is no type check and no exception check. The pros is that it can generate a simple and high-performance code, but the cons is that you have to care for everything.
Put It All Together and Benchmark
Let's put it all together and benchmark it.
using Jit;
/* ------------------------------------------------------------------------
JIT
------------------------------------------------------------------------ */
var c = new Jit.Compiler();
var entry1 = c.enter();
var jump0 = c.ge(Jit.S0, Jit.IMM(3));
c.ret(Jit.S0);
var l1 = c.label();
c.sub(Jit.R0, Jit.S0, Jit.IMM(2));
c.call(entry1);
c.mov(Jit.S1, Jit.R0);
c.sub(Jit.R0, Jit.S0, Jit.IMM(1));
c.call(entry1);
c.add(Jit.R0, Jit.R0, Jit.S1);
c.ret(Jit.R0);
jump0.setLabel(l1);
var code = c.generate();
var n = 36;
var tmr = new SystemTimer();
var r = code.run(n);
var elapsed = tmr.elapsed();
System.println("[elapsed:%8.3f] JIT lib fib(%2d) = %d" % elapsed % n % r);
/* ------------------------------------------------------------------------
native
------------------------------------------------------------------------ */
native fibn(n) {
if (n <= 3) return n;
return fibn(n-2) + fibn(n-1);
}
tmr.restart();
r = fibn(n);
elapsed = tmr.elapsed();
System.println("[elapsed:%8.3f] native fib(%2d) = %d" % elapsed % n % r);
/* ------------------------------------------------------------------------
normal case
----------------------------------------------------------------------------- */
function fib(n) {
if (n <= 3) return n;
return fib(n-2) + fib(n-1);
}
tmr.restart();
r = fib(n);
elapsed = tmr.elapsed();
System.println("[elapsed:%8.3f] function fib(%2d) = %d" % elapsed % n % r);
Here is the result.
[elapsed: 0.074] JIT lib fib(36) = 24157817
[elapsed: 0.158] native fib(36) = 24157817
[elapsed: 2.472] function fib(36) = 24157817
Comparison
By the way, the result by cl -O2
without compilation time is below. And the result of my C interpreter by x64 JIT compilation is also below. By this, I feel the result of the JIT lib is almost same as the case when adding a compilation time.
[elapsed: 0.049] fib(36) = 24157817 // => cl -O2
[elapsed: 0.094] fib(36) = 24157817 // => kcs -j
Conclusion
native
is very simple but there are some limitations. JIT Library will be very useful for some specific situation. You can see the document of JIT Compiler and Native for details.
I hope you will find a use case to use this library and it helps you.
Top comments (0)