-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathepycc.py
3507 lines (2816 loc) · 146 KB
/
epycc.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
75
76
77
78
79
80
81
82
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
115
116
117
118
119
120
121
122
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
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
385
386
387
388
389
390
391
392
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
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env python
"""
epycc - Embedded Python C Compiler
Copyright (C) 2021 Antonio Tejada
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
See
- ISO/IEC 9899:1999 Annex A (aka C99) or ISO/IEC 9899:TC2 6.4.1
- https://en.wikipedia.org/wiki/Lexer_hack
- http://www.quut.com/c/ANSI-C-grammar-l-1999.html
- http://www.quut.com/c/ANSI-C-grammar-y-1999.html
- LLVM tutorial ported to Python https://github.com/eliben/pykaleidoscope
- https://eli.thegreenplace.net/2015/python-version-of-the-llvm-tutorial/
- https://eli.thegreenplace.net/2015/building-and-using-llvmlite-a-basic-example
- https://eli.thegreenplace.net/2017/adventures-in-jit-compilation-part-4-in-python/
- http://dev.stephendiehl.com/numpile/
- https://github.com/sdiehl/numpile/commit/353280bc6d3f14bf203924881d02963767d10efb
- https://github.com/pfalcon/pycopy
- https://github.com/sdiehl/numpile/issues/14
- https://gist.github.com/sulami/5fb47f4a36d70f89df8453f5b4236732
- https://gist.github.com/alendit/defe3d518cd8f3f3e28cb46708d4c9d6
- https://numba.discourse.group/t/contributing-to-numba-with-no-compiler-or-llvm-experience/597
- https://blog.yossarian.net/2020/09/19/LLVMs-getelementptr-by-example
- https://www.reddit.com/r/programming/comments/ivuf99/llvms_getelementptr_by_example/
- https://groups.seas.harvard.edu/courses/cs153/2019fa/lectures/Lec07-Datastructs.pdf
- https://mapping-high-level-constructs-to-llvm-ir.readthedocs.io/en/latest/README.html
- https://stackoverflow.com/questions/42138764/marshaling-object-code-for-a-numba-function
"""
import ctypes
from collections import OrderedDict as odict
import functools
import os
import re
import string
import struct
from cstruct import Struct
import lark
# Monkey patch lark trees so they can compare to strings, see
# https://github.com/lark-parser/lark/issues/986
# XXX Go through rules and remove .data and .value comparisons against strings
# and use straight node and token
old_lark_tree_eq = lark.Tree.__eq__
lark.Tree.__eq__ = lambda self, other: (self.data == other) if isinstance(other, str) else old_lark_tree_eq(self, other)
import llvmlite.binding as llvm
import llvmlite.ir as ir
def unpack_op_sign_names(ops):
return [ (op.split(":")[0], op.split(":")[1]) for op in ops]
# XXX All these should be exposed in a nice way for consumers of the parser
binops = unpack_op_sign_names([
"+:add", "-:sub", "*:mul", "/:div", "%:mod",
"<<:lshift", ">>:rshift",
"<:lt", "<=:lte", ">:gt", ">=:gte","==:eq", "!=:neq",
"&:bitand", "|:bitor", "^:bitxor",
"&&:and", "||:or",
])
unops = unpack_op_sign_names(["+:add", "-:sub", "~:bitnot", "!:not"])
int_ops = set(["|", "&", "^", "%", "<<", ">>", "!", "~"])
rel_ops = set(["<", "<=", ">", ">=","==", "!="])
logic_ops = set(["&&", "||"])
ass_ops = set(["=", "*=", "/=", "%=", "+=", "-=", "<<=", ">>=", "&=", "^=", "|="])
incr_ops = set([ "++", "--" ])
binop_sign_to_name = { binop_sign : binop_name for binop_sign, binop_name in binops }
# XXX Missing memory operators * . -> &
# XXX Missing conditional operator ? :
unspecified_integer_types = set(["_Bool", "char", "short", "int", "long", "long long"])
float_types = set(["float", "double", "long double"])
unspecified_types = unspecified_integer_types | float_types
specifiable_integer_types = unspecified_integer_types - set(["_Bool"])
# XXX Remove signed versions which map to plain anyway, and map to the non
# specified type and standardize on unsigned and plain at symbol table
# creation time?
integer_specifiers = set(["unsigned", "signed"])
specified_integer_types = set(
[(integer_specif + " " + integer_type) for integer_specif in integer_specifiers for integer_type in specifiable_integer_types]
)
integer_types = unspecified_integer_types | specified_integer_types
# XXX Note on clang char is signed on x86, unsigned on ARM
unsigned_integer_types = set(["_Bool"] + [integer_type for integer_type in integer_types if "unsigned" in integer_type])
signed_integer_types = integer_types - unsigned_integer_types
# XXX Missing _Complex
non_void_types = float_types | integer_types
all_types = float_types | integer_types | set(["void"])
class SymbolTable():
def __init__(self):
# Array of scopes, one dict per scope, plus one overflow dict
# The overflow dict is used to store function parameters from the time
# they are found at function definition time to the time the function's
# main block is found and the overflow becomes the current scope
# Then the overflow becomes the current block and parameters share the
# same scope as the function's top-level local variables.
# Parameters have to be at the same scope as the functions' local vars
# and local vars that redefined them will cause error (but you can
# redefine them in a nested scope)
self.scope_symbols = [dict(), dict()]
def get(self, key, default):
# XXX Do this without exceptions
try:
return self.__getitem__(key)
except KeyError:
return default
def __getitem__(self, key):
assert isinstance(key, str)
item = None
# Search the item in all the scopes, except for overflow
for symbols in reversed(self.scope_symbols[:-1]):
item = symbols.get(key, None)
if (item is not None):
break
return item
def __setitem__(self, key, value):
self.scope_symbols[-2][key] = value
def __iter__(self):
return self.scope_symbols[-2].__iter__()
def __len__(self):
return len(self.scope_symbols) - 1
def values(self):
return self.scope_symbols[-2].values()
def get_overflow_item(self, key):
# Overflow should only be read from when the symbol table is at global
# scope
assert len(self) == 1, "Getting overflow items on a local scope!!!"
return self.scope_symbols[-1][key]
def set_overflow_item(self, key, value):
# Overflow should only be write to when the symbol table is at global
# scope
assert len(self) == 1, "Setting overflow items on a local scope!!!"
self.scope_symbols[-1][key] = value
def push_scope(self):
# The overflow now becomes the current and a new oveflow is created
self.scope_symbols.append(dict())
def pop_scope(self):
self.scope_symbols.pop()
# Clear the new overflow
self.scope_symbols[-1] = dict()
def get_fn_name(*args):
"""
Return a function name based on the incoming arguments: operation name,
result type, parameter 1 type, parameter 2 type ...
"""
# fn = "name__result_type__a_type__b_type..."
l = [ args[0] ]
# Replace spaces in case of compound types ("unsigned long", etc)
l.extend([arg.replace(" ", "_") for arg in args[1:]])
return string.join(l, "__")
def type_is_scalar(t):
# XXX Missing checking the symbol table for typedefs
return isinstance(t, str)
def type_is_struct_or_union(t):
return (isinstance(t, (dict, odict)))
def type_is_array(t):
return isinstance(t, list)
def type_is_compile_time_sized_array(t):
"""
@return True if the type is an array and its size is known at compile time
"""
res = False
if (isinstance(t, list)):
if (isinstance(t[0], list)):
res = type_is_compile_time_sized_array(t[0])
else:
res = (t[1] is not None) and (isinstance(t[1].ir_reg, ir.Constant))
else:
res = False
return res
def get_array_item_type(t):
assert(type_is_array(t))
a_type = t
while (type_is_array(a_type)):
a_type, _ = a_type
return a_type
def build_type_from_dimensions(item_type, dims):
# Convert from dimensions to nested array type, dimesions stay as ir nodes
# (with a constant ir_reg or not), or None
array_type = item_type
# Reverse the order so array can be destructured from oudside in as its
# dimensions are indexed, eg
# int c[5][4];
# type is [[int, 4], 5], so the type of c[5] is [int, 4]
if (dims is not None):
for dim in reversed(dims):
assert ((dim is None) or (dim.type == "ir"))
array_type = [array_type, dim]
return array_type
def get_canonical_type(t):
"""
Get the canonical type for a c type, ie specifier first, type second eg
"unsigned int" for "int unsigned"
If the type is not a basic type, the type is returned unmodified
"""
uncanonical_to_canonical_type = {
"char signed" : "signed char",
"char unsigned" : "unsigned char",
"int unsigned" : "unsigned int",
"int signed" : "signed int",
"unsigned" : "unsigned int",
"signed" : "signed int",
"long unsigned" : "unsigned long",
"long signed" : "signed long",
"long unsigned long" : "unsigned long long",
"long long unsigned" : "unsigned long long",
"long signed long" : "signed long long",
"long long signed" : "signed long long",
"double long" : "long double"
}
if (isinstance(t, str)):
t = uncanonical_to_canonical_type.get(t, t)
return t
def get_llvm_type_ext(t):
"""
This is used for the contract between the caller and the callee, whether
to signextend to the architecture word size or not.
The caller looks at the parameters ext, the callee looks at the function ext
- The function is ext type
- Parameters are type ext
Looks like on x86 only i1 needs to be specified with ext since x86 can use
8bit and 16bit values natively (other arches would need to extend before
and/or after the call)
Setting it does change the generated code on x86, causing 8-bit to 32-bit
sign/zero extension so for now it's only set where needed for x86
"""
c_to_irext = {
"_Bool" : "zeroext",
}
return c_to_irext.get(t, "")
def get_llvm_type(t):
"""
Return the llvm type corresponding to a C type
"""
# XXX Use some of the existing type list or snippets to build this?
# XXX Calling this on every IR generation is error prone, should we
# store this type in the symbol and have get_llvm_result_type, etc?
# Or just keep the llvm type around?
c_to_llvm_types = {
# XXX On windows "long double" is "double", on linux it's "x86_fp80"
"long double" : "double",
"double" : "double",
"float" : "float",
"long long" :"i64",
"signed long long" : "i64",
"unsigned long long" : "i64",
# XXX This depends on LLP64/etc windows is 32-bit
"long": "i32",
"signed long" : "i32",
"unsigned long" : "i32",
"int" : "i32",
"signed int": "i32",
"unsigned int" : "i32",
"short" : "i16",
"signed short" : "i16",
"unsigned short" : "i16",
"char" : "i8",
"signed char" : "i8",
"unsigned char" : "i8",
"_Bool" : "i1",
"void" : "void",
}
# Make sure we are covering all types
assert all((c_type in all_types) for c_type in c_to_llvm_types)
assert all((c_type in c_to_llvm_types) for c_type in all_types)
return c_to_llvm_types[t]
def get_llvmlite_type(t):
"""
Return the llvmlite ir type corresponding to a C type
"""
c_to_llvmlite_types = {
# XXX By default this is the same as double on Windows x86 instead of x86_fp80,
# also llvmlite.ir doesn't support x86_fp80
"long double" : ir.DoubleType(),
"double" : ir.DoubleType(),
"float" : ir.FloatType(),
"long long" : ir.IntType(64),
"signed long long" : ir.IntType(64),
"unsigned long long" : ir.IntType(64),
# XXX This depends on LLP64/etc windows is 32-bit
"long": ir.IntType(32),
"signed long" : ir.IntType(32),
"unsigned long" : ir.IntType(32),
"int" : ir.IntType(32),
"signed int": ir.IntType(32),
"unsigned int" : ir.IntType(32),
"short" : ir.IntType(16),
"signed short" : ir.IntType(16),
"unsigned short" : ir.IntType(16),
"char" : ir.IntType(8),
"signed char" : ir.IntType(8),
"unsigned char" : ir.IntType(8),
"_Bool" : ir.IntType(1),
"void" : ir.VoidType(),
}
# Make sure we are covering all types
assert all((c_type in all_types) for c_type in c_to_llvmlite_types)
assert all((c_type in c_to_llvmlite_types) for c_type in all_types)
# First stab at complex types
# "int" is "int"
# "unsigned int" is "unsigned int"
# "int c[10];"
# - c is ["int", 10]
# "int c[1][2];"
# - c is [["int", 2], 1] (note reversed order so type can be destructured
# by removing the outermost level)
# - c[1] is ["int", 2]
# int c[1][]
# - c is [["int", None], 1]
# - c[1] is ["int", None]
# int c[x][j] (runtime sized array)
# - c is [["int", gen_node], gen_node] with gen_node.ir_reg containing the llvmir expression
# int *c is
# - c is ["int", None] XXX This makes pointers the same as arrays, should they be different?
# - &c is [["int", None], None]
# - *c is "int" XXX Should simple types be boxed as ["int"] for symmetry?
# int **c
# - c is [["int", None], None]
# - &c is [[["int", None], None], None]
# - *c is ["int", None]
# struct { int i; float f; } s;
# - s is odict({ 'i' : "int", 'f' : "float" })
# - s.i is "int"
# struct { int i; struct s2 s; } s;
# - s is odict({ 'i' : "int", 's' : "s2" })
# - s.s is "s2"
# struct { int i; float f[2]; } s;
# - s is odict({ 'i' : "int", 'f' : ["float", 2] })
# - s.f is ["float", 2]
# struct { int i; float f[2]; } s[10];
# - s is [odict({'i' : "int", 'f' : ["float", 2] }), 10]
# - s[0] is odict({'i' : "int", 'f' : ["float", 2] })
# - s[0].f is ["float", 2]
# - s[0].f[1] is "Float"
# - &s is [[odict({'i' : "int", 'f' : ["float", 2] }), 10], None]
# typedef struct { int i; float f; } S;
# - S is "S"
# typedef struct { int i; float f; } S; S ss[10];
# - S is "S"
# - ss is ["S", 10]
# Unions can be a regular dict since they don't need to be ordered
# Bitfields can use colon-type notation
# struct { int i:2; int j:3; } odict({'i' : "int:3"
if (isinstance(t, str)):
# Primitive type
# XXX or typedef, but typedef not supported yet
llvmlite_type = c_to_llvmlite_types[t]
elif (isinstance(t, list)):
# Array or pointer
if ((t[1] is not None) and (isinstance(t[1].ir_reg, ir.Constant))):
# Compile-time sized array
llvmlite_type = ir.ArrayType(get_llvmlite_type(t[0]), t[1].ir_reg.constant)
else:
# Runtime sized array or pointer
assert((t[1] is None) or (isinstance(t[1], Struct)))
llvmlite_type = get_llvmlite_type(t[0]).as_pointer()
elif (isinstance(t, odict)):
# Struct
llvmlite_type = ir.LiteralStructType(get_llvmlite_type(field_type) for field_type in t.values())
else:
assert False, "Unexpected type %s" % repr(t)
return llvmlite_type
def get_ctype(t):
"""
Return the Python ctype corresponding to a C type
"""
c_to_ctypes = {
"long double" : ctypes.c_longdouble,
"double" : ctypes.c_double,
"float" : ctypes.c_float,
"long long" : ctypes.c_longlong,
"signed long long" : ctypes.c_longlong,
"unsigned long long" : ctypes.c_ulonglong,
"long": ctypes.c_long,
"signed long" : ctypes.c_long,
"unsigned long" : ctypes.c_ulong,
"int" : ctypes.c_int,
"signed int": ctypes.c_int,
"unsigned int" : ctypes.c_uint,
"short" : ctypes.c_short,
"signed short" : ctypes.c_short,
"unsigned short" : ctypes.c_ushort,
"char" : ctypes.c_char,
"signed char" : ctypes.c_byte,
"unsigned char" : ctypes.c_ubyte,
"_Bool" : ctypes.c_bool,
"void" : None,
# XXX Missing ctypes.c_voidp once pointers are supported
}
# Make sure we are covering specified types
assert all((c_type in all_types) for c_type in c_to_ctypes)
assert all((c_type in c_to_ctypes) for c_type in all_types)
if (isinstance(t, str)):
# XXX Missing typedefs
ctype = c_to_ctypes[t]
elif (isinstance(t, list)):
if ((t[1] is not None) and isinstance(t[1].ir_reg, ir.Constant)):
# Compile-time sized array
ctype = get_ctype(t[0]) * t[1].ir_reg.constant
else:
# Runtime sized array or pointer
assert((t[1] is None) or (isinstance(t[1], Struct)))
ctype = ctypes.POINTER(get_ctype(t[0]))
# XXX This makes parameter overhead lower?
#ctype = ctypes.c_void_p
else:
# XXX Missing structs
assert False, "Unsupported type %s" % repr(t)
return ctype
def invoke_clang(c_filepath, ir_filepath, options=""):
# Generate the precompiled IR in irs.ll
# clang_filepath = R"C:\android-ndk-r15c\toolchains\llvm\prebuilt\windows-x86_64\bin\clang.exe"
clang_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "_out", "bin", "clang.exe")
# For privacy reasons and since some ir files are pushed to the repo, don't
# leak the local path in the LLVM moduleid comment of
cmd = R"%s -S -std=c99 -emit-llvm -mllvm --x86-asm-syntax=intel %s -o %s %s" % (
clang_filepath,
options,
os.path.relpath(ir_filepath),
os.path.relpath(c_filepath)
)
os.system(cmd)
def invoke_dot(filepath):
dot_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "_out", "bin", "dot.exe")
cmd = R"%s -Tpng -o %s.png %s" %(
dot_filepath,
os.path.relpath(filepath),
os.path.relpath(filepath)
)
os.system(cmd)
def precompile_c_snippets(generated_c_filepath, generated_ir_filepath):
"""
Generate a file containing one C function implementing every C operation and
type.
The generated file can then be fed to a local clang install via
%CLANG% -mllvm -S -std=c99 --x86-asm-syntax=intel -emit-llvm -o- generated/irs.c
and generate LLVM IR that can be read from epycc to do the runtime codegen
(the -S is needed so clang doesn't error trying to generate object code)
"""
print "Precompiling C snippets"
l = []
# Operations are done in the same type and then the result converted
# using a conversion function
# XXX Should the forced cast exist? The highest ranked type is used for
# operations anyway at codegen time?
for unop_sign, unop_name in unops:
for c_type in non_void_types:
if ((unop_sign in int_ops) and (c_type not in integer_types)):
continue
# int add__int__int(int a) { return (int) (+a); }
fn = "%s %s(%s a) { return (%s) (%sa); }" % (
c_type,
get_fn_name(unop_name, c_type, c_type),
c_type,
c_type,
unop_sign,
)
l.append(fn + "\n")
for binop_sign, binop_name in binops:
for c_type in non_void_types:
# Don't do integer-only operations (bitwise, mod) on non-integer
# operands
if ((binop_sign in int_ops) and (c_type not in integer_types)):
continue
# char add__char__char__char(char a, char b) { return (char) (a + b); }
fn = "%s %s(%s a, %s b) { return (%s) (a %s b); }" % (
c_type,
get_fn_name(binop_name, c_type, c_type, c_type),
c_type, c_type,
c_type,
binop_sign,
)
l.append(fn + "\n")
# Assignment operators will be done as a = a + b
# Build the type conversion functions
for res_type in non_void_types:
for a_type in non_void_types:
# No need to generate for the same type (but note the table is still
# redundant for integer types since it contains eg signed int to int)
if (a_type != res_type):
# char cnv__char__int(int a) { return (char) a; }
fn = "%s %s(%s a) { return (%s) a; }" % (
res_type,
get_fn_name("cnv", res_type, a_type),
a_type,
res_type,
)
l.append(fn + "\n")
# Generate the C functions file in irs.c
with open(generated_c_filepath, "w") as f:
f.writelines(l)
invoke_clang(generated_c_filepath, generated_ir_filepath)
def convert_to_clang_irs(llvm_irs):
"""
Convert the provided function irs into IR that matches clang as close as
possible so it can be easily compared vs. clang-generated IR.
IR code generated by llvmlite IR and clang differ in register and label
naming.
"""
# XXX Merge with load_functions_ir ?
# XXX This reindexing is very flaky, specifically the function header and
# register vs. label parsing is brittle, something better or maybe
# llvmlite parse_assembly could be used?
# Using llvmir, the llvm code can be obtained by just str(module)
# but that generates code that makes it hard to compare against
# clang:
# - llvmir uses a deduplication naming convention for registers,
# this is not an issue for registers used in code that will be
# optimized, since optimizing the code will reindex them to match
# clang, but function parameters are never reindexed, even in
# optimized code
# - llvmir declares the snippet functions with "declare", which
# prevents from including the functions in the same module since
# llvm will error out if declared functions are present in the
# module where they are declared (snippets could be included in a
# different module, though, but it's not as convenient as having
# everything in the same listing).
# Given the above,
# - generate code that doesn't have declares by doing str(function)
# on each function.
# - reindex the llvmir register names to match clang
# Regarding the reindexing, clang's convention is:
# - registers 0 to N - 1 are taken by the N function parameters
# - register N is empty (maybe used for the basic block or the
# return value?)
# - registers N + 1 and beyond are used by temporaries.
# - register names are allocated strictly monotonically and as they
# first appear in the instruction stream (otherwise llvm will
# error out) R
#
# Regarding labels, label references don't increment the register
# index, but label declarations do, which means that the label index
# is not known until declared later and a second pass is needed to
# update the label.
# clang
# br i1 %4, label %5, label %6
# ; <label>:5:
# llvmlite
# br i1 %".8", label %"entry.if", label %"entry.endif"
# entry.if:
# %".10" = load i32, i32* %".3"
# Parse the function prototype
# define [dso_local] [zeroext|signext] i32 @"f"(i32 [returned] %".1", i32 %".2")
# define dso_local i32 @felse(i32, i32) local_unnamed_addr #0 {
# define dso_local i32 @felse(i32, i32) {
# define i32 @"fif"(i32 %".1")
# define float @"arith_ops"(i32 %".1", i32 %".2")
# define i32 @felse_dangling(i32 returned %.1, i32 %.2) local_unnamed_addr #0 {
m = re.search(r'define (?:dso_local )?(?P<llvm_type>[^@]+) @"?(?P<name>[^"]+)"?\((?P<parameters>[^)]*)\)', llvm_irs[0])
fn = Struct(**m.groupdict())
if (fn.parameters.strip() == ""):
# split will return one-element list with the empty string, just return
# the empty string
fn.parameters = []
else:
fn.parameters = fn.parameters.split(",")
parameter_type_names = [
value_type_name.strip().rsplit(" ", 1) for value_type_name in fn.parameters
]
# Initialize the reindexing table with the parameters and the empty
# gap
fn.parameters = []
index_count = 0
name_to_index = {}
for parameter_type_name in parameter_type_names:
if (len(parameter_type_name) == 1):
# No names, use default parameter names %0, %1, etc
parameter_name = '%%%d' % i
else:
# Use the incoming names, don't use default parameter names since
# they may be used in the code for regular registers
parameter_name = parameter_type_name[1]
parameter_type = parameter_type_name[0]
name_to_index[parameter_name] = "%%%d" % index_count
fn.parameters.append(Struct(llvm_type = parameter_type, name = parameter_name))
index_count += 1
# Skip the gap between parameters and body registers
index_count += 1
# Since we need a second pass anyway for filling in the labels,
# do a two-pass for everything, first build the reindexing table,
# then substitute everything
# llvmliteir label declarations:
# entry.endif:
# llvmliteir register usage:
# %".5" = load i32, i32* %".3"
# %.3 = zext i1 %3 to i32
# %spec.select = select i1 %2, i32 0, i32 %0
# but skip label usage:
# br i1 %".8", label %"entry.if", label %"entry.endif"
# comments
# forbody.preheader: ; preds = %entry
# Phi node
# %.3.0.lcssa = phi float [ 0.000000e+00, %entry ], [ %phitmp, %forbody.preheader ]
# XXX Register assign is what increments the register count and has to be
# at the beginning of the line, which simplifies things
re_reg_label_decl = re.compile(r'(^%"?[.0-9a-z_]+"?)[, \n]|(^[^:\n]+):', re.MULTILINE)
re_reg_label_decl_usage = re.compile(r'(%[^ ,)]+)|(^[^:]+:)')
# Find all the labels
llvm_ir = string.join(llvm_irs[1:], "\n")
labels = re.findall(r"^([^:\n]+):", llvm_ir, re.MULTILINE)
labels = set(["%%%s" % label for label in labels])
name_to_index["%entry"] = "%%%d" % len(fn.parameters)
# Ignore the first line with the define
for m in re_reg_label_decl.finditer(llvm_ir):
match_index = m.lastindex
reg_label_name = m.group(match_index)
if (reg_label_name not in name_to_index):
# Substitution entry for both register usage and label
# declaration
if (match_index == 1):
# Ignore label usages, only care about register usages and label
# delcarations
if (reg_label_name not in labels):
# register usage found, fill in substitution
# %".3" to %N
name_to_index[reg_label_name] = "%%%d" % index_count
index_count += 1
elif (reg_label_name != "entry"):
# label declaration found, fill in substitutions for
# label usage and label declaration
# Usage:
# %"entry.endif" and %entry.endif to %n
name_to_index['%%"%s"' % reg_label_name] = "%%%d" % index_count
name_to_index['%%%s' % reg_label_name] = "%%%d" % index_count
# Declaration
# entry.endif: to "; <label>:5:"
name_to_index[reg_label_name + ":"] = "; <label>:%d:" % index_count
index_count += 1
debug = (__name__ == "__main__")
if (debug):
print "before"
print string.join(llvm_irs, "\n")
# Perform the replacement and filter out the define, braces and
# entry basic block label coming from llvmir since they mismatch
# what clang produces
reindexed_llvm_irs = []
for l in llvm_irs:
if (l.startswith("define")):
# Use a define line that matches clang, eg
# define dso_local zeroext i16 @add__int__int__short(i32, i16 zeroext) {
# LLVM type extension goes first in function return value, last on function parameters
l = "define dso_local %s @%s(%s) {" % (
fn.llvm_type,
fn.name,
string.join([
("%s" % parameter.llvm_type)
for parameter in fn.parameters], ",")
)
elif (l.startswith("{") or l.startswith("entry:")):
# Skip the entry basic block label, and the brace that was
# already set in the define line
continue
else:
# Perform the replacement for register usage, and label usage and declaration
# Note label usage
# br i1 %".8", label %"entry.if", label %"entry.endif"
l = re_reg_label_decl_usage.sub(lambda m: name_to_index[m.group(m.lastindex)], l)
reindexed_llvm_irs.append(l)
if (debug):
print "after"
print string.join(reindexed_llvm_irs, "\n")
return reindexed_llvm_irs
def generate_ir(generator, node):
# XXX This should have a generate_ir and then a nested function
# generate_node_ir that doesn't require passing the generator every time
def get_grandson(node, parent_indices):
if (len(parent_indices) > 0):
node = get_grandson(node.children[parent_indices[0]], parent_indices[1:])
return node
def get_tree_tokens(node):
# XXX This could use
# all_tokens = tree.scan_values(lambda v: isinstance(v, Token))
# but it's not sure we want to be that Lark dependent yet
tokens = []
if (isinstance(node, lark.Token)):
tokens = [node.value]
else:
for child in node.children:
tokens.extend(get_tree_tokens(child))
return tokens
def is_integer_type(a_type):
return (a_type in integer_types)
def is_unsigned_integer_type(a_type):
return a_type in unsigned_integer_types
def is_signed_integer_type(a_type):
return a_type in signed_integer_types
def get_unspecified_type(a_type):
if (is_integer_type(a_type)):
# Remove unsigned or signed from the type
a_type = a_type.replace("unsigned", "").replace("signed","").strip()
if (a_type == ""):
a_type = "int"
return a_type
def get_type_bytes(a_type):
type_sizes = {
"long double" : 16,
"double" : 8,
"float" : 4,
"long long" : 8,
"signed long long" : 8,
"unsigned long long" : 8,
# XXX This depends on LLP64 vs LP64 windows is LLP64
"long" : 4,
"signed long" : 4,
"unsigned long" : 4,
"int" : 4,
"signed int" : 4,
"unsigned int" : 4,
"short" : 2,
"signed short" : 2,
"unsigned short" : 2,
"char" : 1,
"signed char" : 1,
"unsigned char" : 1,
"_Bool" : 1,
}
return type_sizes[a_type]
def get_result_type(op_sign, a_type, b_type):
# Types sorted by highest rank first
# XXX Review this really matches the c99 rank or find a way of
# extracting the result type of the C snippets or from some table
# we build out of invoking clang with all the different combinations
# Ranks
# float < double < long double
# _Bool < char < short < int < long < long long
# signed and unsigned have the same rank
# the lowest ranked floating type has a higher rank than any integer
type_ranks = {
"long double" : 8,
"double" : 7,
"float" : 6,
"long long" : 5,
"signed long long" : 5,
"unsigned long long" : 5,
"long" : 4,
"signed long": 4,
"unsigned long" : 4,
"int" : 3,
"signed int" : 3,
"unsigned int" : 3,
"short" : 2,
"signed short" : 2,
"unsigned short" : 2,
"char" : 1,
"signed char" : 1,
"unsigned char" : 1,
"_Bool" : 0,
}
# Make sure we are covering all specified types
assert all((c_type in non_void_types) for c_type in type_ranks)
assert all((c_type in type_ranks) for c_type in non_void_types)
def get_ranked_types(a_type, b_type):
a_type_rank = type_ranks[a_type]
b_type_rank = type_ranks[b_type]
if (a_type_rank >= b_type_rank):
return a_type, b_type
else:
return b_type, a_type
def a_or_b_type_is(c_type):
return ((a_type == c_type) or (b_type == c_type))
# XXX This could prebuild the cross product table of operands and results
# offline so it doesn't need to check at runtime
# See https://www.oreilly.com/library/view/c-in-a/0596006977/ch04.html
# See 6.3 conversions of ISO/IEC 9899:1999
# Every integer has a rank
# _bool < char < short < int < long < long long
# rank(enum) = rank(compatible int type)
# rank(signed type) = rank(unsigned type)
# Floating point are ranked
# float < double < long double
# Integer promotion:
# - if an int can represent all ranges of a type, an int is used
# - else an unsigned int
# plain char signedness is implementation dependent (on clang signed on x86
# but unsigned on arm)
# Bool: Any type is 0 if equals to 0, otherwise 1
# ... more conversion rules
# Usual arithmetic conversions 6.3.1.8
# The usual arithmetic conversions are performed implicitly for the following operators:
# XXX Missing checking the op is one of usual arithmetic:
# - Arithmetic operators with two operands: *, /, %, +, and -
# - Relational and equality operators: <, <=, >, >=, ==, and !=
# - The bitwise operators, &, |, and ^
# - The conditional operator, ?: (for the second and third operands)
if (a_or_b_type_is("long double")):
res_type = "long double"
elif (a_or_b_type_is("double")):
res_type = "double"
elif (a_or_b_type_is("float")):
res_type = "float"
else:
# The type is integer, do integer promotions
assert(is_integer_type(a_type))
assert(is_integer_type(b_type))
a_promoted_type = a_type
b_promoted_type = b_type
# If an int can represent all the values of the original type
# use int otherwise unsigned int
if (get_type_bytes(a_type) < get_type_bytes("int")):
a_promoted_type = "int"
if (get_type_bytes(b_type) < get_type_bytes("int")):
b_promoted_type = "int"
# XXX Missing 6.3.1.3 rules
highest_ranked_type, lowest_ranked_type = get_ranked_types(a_promoted_type, b_promoted_type)
# apply rules to promoted operands
if (a_promoted_type == b_promoted_type):
# Same promoted type, pick any
res_type = a_promoted_type
elif (is_signed_integer_type(a_promoted_type) == is_signed_integer_type(b_promoted_type)):
# Both are signed or both are unsigned, return highest ranked type
res_type = highest_ranked_type
elif (is_unsigned_integer_type(highest_ranked_type)):
# unsigned has higher or equal rank, pick unsigned
res_type = highest_ranked_type
elif (get_type_bytes(highest_ranked_type) > get_type_bytes(lowest_ranked_type)):
# highest ranked type is signed and can represent all the values of the unsigned, pick signed
res_type = highest_ranked_type
else:
# pick unsigned type of the signed type
if (is_signed_integer_type(a_promoted_type)):
res_type = a_promoted_type
else:
res_type = b_promoted_type
if (res_type != "_Bool"):