Newer
Older
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# pystencils - LLVM generation\n",
"The generation of LLVM code is simliar but not identical as seen with the C++ version. For the generation itself a python module ``llvmlite`` is used. This module provides the necessary support and bindings for LLVM. In order to generate from the AST to llvm, the AST needs to be transformed to support type conversions. This is the biggest difference to the C++ version. C++ doesn't need that since the language itself handles the casts.\n",
"\n",
"In this example a simple weighted Jacobi kernel is generated, so the focus remains on the part of LLVM generation."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import pytest\n",
"pytest.importorskip('llvmlite')"
]
},
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import sympy as sp\n",
"import numpy as np\n",
"import ctypes\n",
"from pystencils import Field, Assignment\n",
"from pystencils import create_kernel\n",
"from pystencils.display_utils import to_dot\n",
"\n",
"sp.init_printing()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The numpy arrays (with inital values) create *Field*s for the update Rule. Later those arrays are used for the computation itself."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"src_arr = np.zeros((30, 20))\n",
"src_arr[0,:] = 1.0\n",
"src_arr[:,0] = 1.0\n",
"dst_arr = src_arr.copy()\n",
"\n",
"src_field = Field.create_from_numpy_array('src', src_arr)\n",
"dst_field = Field.create_from_numpy_array('dst', dst_arr)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Using the *Field* objects and the additional *Symbol* $\\omega$ for the weight the update rule is specified as a *sympy* equation."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/latex": [
"$\\displaystyle {{dst}_{(0,0)}} \\leftarrow {{src}_{(0,0)}} \\left(1.0 - \\omega\\right) + \\frac{\\omega \\left({{src}_{(1,0)}} + {{src}_{(0,1)}} + {{src}_{(0,-1)}} + {{src}_{(-1,0)}}\\right)}{4}$"
],
" ω⋅(src_E + src_N + src_S + src_W)\n",
"dst_C := src_C⋅(1.0 - ω) + ─────────────────────────────────\n",
" 4 "
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"omega = sp.symbols(\"omega\")\n",
"update_rule = Assignment(dst_field[0,0], omega * (src_field[0,1] + src_field[0,-1] + src_field[1,0] + src_field[-1,0]) / 4\n",
" + (1.-omega)*src_field[0,0])\n",
"update_rule"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"With this update rule an abstract syntax tree (AST) can be created. This AST can be used to print the LLVM code. The creation follows the same routines as the C++ version does. However at the end there are two more steps. In order to generate LLVM, type casting and pointer arithmetic had to be introduced (which C++ does for you)."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"scrolled": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"KernelFunction kernel([_data_dst, _data_src, omega])\n",
"\tBlock for(ctr_0=1; ctr_0<29; ctr_0+=1)\n",
"\t\tBlock _data_dst_00 ← pointer_arithmetic_func(_data_dst, 20*ctr_0)\n",
"\t\t_data_src_00 ← pointer_arithmetic_func(_data_src, 20*ctr_0)\n",
"\t\t_data_src_01 ← pointer_arithmetic_func(_data_src, 20*ctr_0 + 20)\n",
"\t\t_data_src_0m1 ← pointer_arithmetic_func(_data_src, 20*ctr_0 - 20)\n",
"\t\tfor(ctr_1=1; ctr_1<19; ctr_1+=1)\n",
"\t\t\tBlock _data_dst_00[ctr_1] ← omega*(_data_src_00[ctr_1 + 1] + _data_src_00[ctr_1 - 1] + _data_src_01[ctr_1] + _data_src_0m1[ctr_1])*cast_func(1/4, double) + (omega*cast_func(-1, double) + cast_func(1.0, double))*_data_src_00[ctr_1]\n",
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
"\t\t\n",
"\t\n",
"\n"
]
}
],
"source": [
"ast = create_kernel([update_rule], target='llvm')\n",
"print(str(ast))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It is possible to examine the AST further."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"scrolled": false
},
"outputs": [
{
"data": {
"image/svg+xml": [
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
"<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
"<!-- Generated by graphviz version 2.40.1 (20161225.0304)\n",
" -->\n",
"<!-- Title: %3 Pages: 1 -->\n",
"<svg width=\"864pt\" height=\"476pt\"\n",
" viewBox=\"0.00 0.00 864.48 476.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
"<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 472)\">\n",
"<title>%3</title>\n",
"<polygon fill=\"#ffffff\" stroke=\"transparent\" points=\"-4,4 -4,-472 860.4844,-472 860.4844,4 -4,4\"/>\n",
"<!-- 139792163067600 -->\n",
"<g id=\"node1\" class=\"node\">\n",
"<title>139792163067600</title>\n",
"<ellipse fill=\"#a056db\" stroke=\"#000000\" cx=\"396.1436\" cy=\"-450\" rx=\"145.6742\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"396.1436\" y=\"-446.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">Func: kernel (dst,src,omega)</text>\n",
"<!-- 139791989624336 -->\n",
"<g id=\"node11\" class=\"node\">\n",
"<title>139791989624336</title>\n",
"<ellipse fill=\"#dbc256\" stroke=\"#000000\" cx=\"396.1436\" cy=\"-378\" rx=\"37.0935\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"396.1436\" y=\"-374.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">Block</text>\n",
"<!-- 139792163067600->139791989624336 -->\n",
"<g id=\"edge10\" class=\"edge\">\n",
"<title>139792163067600->139791989624336</title>\n",
"<path fill=\"none\" stroke=\"#000000\" d=\"M396.1436,-431.8314C396.1436,-424.131 396.1436,-414.9743 396.1436,-406.4166\"/>\n",
"<polygon fill=\"#000000\" stroke=\"#000000\" points=\"399.6437,-406.4132 396.1436,-396.4133 392.6437,-406.4133 399.6437,-406.4132\"/>\n",
"<!-- 139791989623376 -->\n",
"<g id=\"node2\" class=\"node\">\n",
"<title>139791989623376</title>\n",
"<ellipse fill=\"#3498db\" stroke=\"#000000\" cx=\"396.1436\" cy=\"-306\" rx=\"86.3847\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"396.1436\" y=\"-302.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">Loop over dim 0</text>\n",
"<!-- 139792050742160 -->\n",
"<g id=\"node10\" class=\"node\">\n",
"<title>139792050742160</title>\n",
"<ellipse fill=\"#dbc256\" stroke=\"#000000\" cx=\"396.1436\" cy=\"-234\" rx=\"37.0935\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"396.1436\" y=\"-230.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">Block</text>\n",
"<!-- 139791989623376->139792050742160 -->\n",
"<g id=\"edge8\" class=\"edge\">\n",
"<title>139791989623376->139792050742160</title>\n",
"<path fill=\"none\" stroke=\"#000000\" d=\"M396.1436,-287.8314C396.1436,-280.131 396.1436,-270.9743 396.1436,-262.4166\"/>\n",
"<polygon fill=\"#000000\" stroke=\"#000000\" points=\"399.6437,-262.4132 396.1436,-252.4133 392.6437,-262.4133 399.6437,-262.4132\"/>\n",
"<!-- 139791989188432 -->\n",
"<g id=\"node3\" class=\"node\">\n",
"<title>139791989188432</title>\n",
"<ellipse fill=\"#56db7f\" stroke=\"#000000\" cx=\"72.1436\" cy=\"-162\" rx=\"72.2875\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"72.1436\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">_data_dst_00</text>\n",
"<!-- 139791989188752 -->\n",
"<g id=\"node4\" class=\"node\">\n",
"<title>139791989188752</title>\n",
"<ellipse fill=\"#56db7f\" stroke=\"#000000\" cx=\"234.1436\" cy=\"-162\" rx=\"72.2875\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"234.1436\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">_data_src_00</text>\n",
"<!-- 139791989189072 -->\n",
"<g id=\"node5\" class=\"node\">\n",
"<title>139791989189072</title>\n",
"<ellipse fill=\"#56db7f\" stroke=\"#000000\" cx=\"396.1436\" cy=\"-162\" rx=\"72.2875\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"396.1436\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">_data_src_01</text>\n",
"<!-- 139791989189456 -->\n",
"<g id=\"node6\" class=\"node\">\n",
"<title>139791989189456</title>\n",
"<ellipse fill=\"#56db7f\" stroke=\"#000000\" cx=\"567.1436\" cy=\"-162\" rx=\"81.4863\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"567.1436\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">_data_src_0m1</text>\n",
"<!-- 139792198596368 -->\n",
"<g id=\"node7\" class=\"node\">\n",
"<title>139792198596368</title>\n",
"<ellipse fill=\"#3498db\" stroke=\"#000000\" cx=\"753.1436\" cy=\"-162\" rx=\"86.3847\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"753.1436\" y=\"-158.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">Loop over dim 1</text>\n",
"<!-- 139792198655632 -->\n",
"<g id=\"node9\" class=\"node\">\n",
"<title>139792198655632</title>\n",
"<ellipse fill=\"#dbc256\" stroke=\"#000000\" cx=\"753.1436\" cy=\"-90\" rx=\"37.0935\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"753.1436\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">Block</text>\n",
"<!-- 139792198596368->139792198655632 -->\n",
"<g id=\"edge2\" class=\"edge\">\n",
"<title>139792198596368->139792198655632</title>\n",
"<path fill=\"none\" stroke=\"#000000\" d=\"M753.1436,-143.8314C753.1436,-136.131 753.1436,-126.9743 753.1436,-118.4166\"/>\n",
"<polygon fill=\"#000000\" stroke=\"#000000\" points=\"756.6437,-118.4132 753.1436,-108.4133 749.6437,-118.4133 756.6437,-118.4132\"/>\n",
"<!-- 139791989208464 -->\n",
"<g id=\"node8\" class=\"node\">\n",
"<title>139791989208464</title>\n",
"<ellipse fill=\"#56db7f\" stroke=\"#000000\" cx=\"753.1436\" cy=\"-18\" rx=\"103.1819\" ry=\"18\"/>\n",
"<text text-anchor=\"middle\" x=\"753.1436\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\" fill=\"#000000\">_data_dst_00[ctr_1]</text>\n",
"<!-- 139792198655632->139791989208464 -->\n",
"<g id=\"edge1\" class=\"edge\">\n",
"<title>139792198655632->139791989208464</title>\n",
"<path fill=\"none\" stroke=\"#000000\" d=\"M753.1436,-71.8314C753.1436,-64.131 753.1436,-54.9743 753.1436,-46.4166\"/>\n",
"<polygon fill=\"#000000\" stroke=\"#000000\" points=\"756.6437,-46.4132 753.1436,-36.4133 749.6437,-46.4133 756.6437,-46.4132\"/>\n",
"<!-- 139792050742160->139791989188432 -->\n",
"<g id=\"edge3\" class=\"edge\">\n",
"<title>139792050742160->139791989188432</title>\n",
"<path fill=\"none\" stroke=\"#000000\" d=\"M362.3868,-226.4985C309.0457,-214.6449 204.358,-191.381 136.3723,-176.2731\"/>\n",
"<polygon fill=\"#000000\" stroke=\"#000000\" points=\"136.7255,-172.7662 126.2043,-174.0135 135.2069,-179.5995 136.7255,-172.7662\"/>\n",
"<!-- 139792050742160->139791989188752 -->\n",
"<g id=\"edge4\" class=\"edge\">\n",
"<title>139792050742160->139791989188752</title>\n",
"<path fill=\"none\" stroke=\"#000000\" d=\"M368.6625,-221.7862C344.1691,-210.9002 307.8582,-194.7621 279.1334,-181.9955\"/>\n",
"<polygon fill=\"#000000\" stroke=\"#000000\" points=\"280.2705,-178.6708 269.7108,-177.8077 277.4275,-185.0674 280.2705,-178.6708\"/>\n",
"<!-- 139792050742160->139791989189072 -->\n",
"<g id=\"edge5\" class=\"edge\">\n",
"<title>139792050742160->139791989189072</title>\n",
"<path fill=\"none\" stroke=\"#000000\" d=\"M396.1436,-215.8314C396.1436,-208.131 396.1436,-198.9743 396.1436,-190.4166\"/>\n",
"<polygon fill=\"#000000\" stroke=\"#000000\" points=\"399.6437,-190.4132 396.1436,-180.4133 392.6437,-190.4133 399.6437,-190.4132\"/>\n",
"<!-- 139792050742160->139791989189456 -->\n",
"<g id=\"edge6\" class=\"edge\">\n",
"<title>139792050742160->139791989189456</title>\n",
"<path fill=\"none\" stroke=\"#000000\" d=\"M424.3808,-222.1107C450.343,-211.1792 489.3422,-194.7585 520.0222,-181.8406\"/>\n",
"<polygon fill=\"#000000\" stroke=\"#000000\" points=\"521.3947,-185.0604 529.2528,-177.954 518.6783,-178.6089 521.3947,-185.0604\"/>\n",
"<!-- 139792050742160->139792198596368 -->\n",
"<g id=\"edge7\" class=\"edge\">\n",
"<title>139792050742160->139792198596368</title>\n",
"<path fill=\"none\" stroke=\"#000000\" d=\"M430.3743,-227.0963C487.746,-215.5256 604.5075,-191.977 680.7422,-176.602\"/>\n",
"<polygon fill=\"#000000\" stroke=\"#000000\" points=\"681.6457,-179.9903 690.7564,-174.5823 680.2618,-173.1285 681.6457,-179.9903\"/>\n",
"<!-- 139791989624336->139791989623376 -->\n",
"<g id=\"edge9\" class=\"edge\">\n",
"<title>139791989624336->139791989623376</title>\n",
"<path fill=\"none\" stroke=\"#000000\" d=\"M396.1436,-359.8314C396.1436,-352.131 396.1436,-342.9743 396.1436,-334.4166\"/>\n",
"<polygon fill=\"#000000\" stroke=\"#000000\" points=\"399.6437,-334.4132 396.1436,-324.4133 392.6437,-334.4133 399.6437,-334.4132\"/>\n",
"</g>\n",
"</g>\n",
"</svg>\n"
],
"text/plain": [
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"to_dot(ast)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"With transformed AST it is now possible to generate and compile the AST into LLVM. Notice that unlike in C++ version, no files are writen to the hard drive (although it is possible).\n",
"\n",
"There are multiple ways how to generate and compile the AST. The most simple one is simillar to the C++ version. Using the ``compile()`` function of the generated AST\n",
"\n",
"You can also manually create a python function with ``make_python_function``.\n",
"\n",
"Another option is obtaining the jit itself with ``generate_and_jit``.\n",
"The function ``generate_and_jit`` first generates and the compiles the AST.\n",
"\n",
"If even more controll is needed, it is possible to use the functions ``generateLLVM`` and ``compileLLVM`` to achieve the same. For further controll, instead of calling ``compileLLVM`` the jit object itself can be created and its necessary functions for the compilation have to be run manually (``parse``, (``optimize``,) ``compile``)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"scrolled": false
},
"outputs": [],
"source": [
"kernel = ast.compile()\n",
"\n",
"#kernel = make_python_function(ast)\n",
"\n",
"# Or alternativally\n",
"#jit = generate_and_jit(ast)\n",
"# Call: jit('kernel', src_arr, dst_arr)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The compiled function(s) can be used now. Either call the function (with arguments, if not given before) or call the jit object with the function's name and its arguments. Here, numpy arrays are automatically adjusted with ctypes.\n",
"\n",
"The functions and arguments can be read as well.\n",
"\n",
"**All of the information the jit object has comes from the module which was parsed. If you parse a second module and don't run the compilation step, the module and the compiled code are not the same anymore, thus leading to false information**"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"#jit.print_functions()"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"for i in range(100):\n",
" kernel(src=src_arr, dst=dst_arr, omega=2/3)\n",
" src_arr, dst_arr = dst_arr, src_arr\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The output is drawn with matplotlib."
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
"output_type": "display_data"
}
],
"source": [
"import matplotlib.pyplot as plt\n",
"from matplotlib import cm\n",
"fig = plt.figure()\n",
"ax = fig.add_subplot(111)\n",
"ax.imshow(dst_arr, cmap=cm.jet)\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"anaconda-cloud": {},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
}
},
"nbformat": 4,
"nbformat_minor": 2
}