-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
/
ModuleNode.py
4024 lines (3637 loc) · 179 KB
/
ModuleNode.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
#
# Module parse tree node
#
from __future__ import absolute_import
import cython
cython.declare(Naming=object, Options=object, PyrexTypes=object, TypeSlots=object,
error=object, warning=object, py_object_type=object, UtilityCode=object,
EncodedString=object, re=object)
from collections import defaultdict
import json
import operator
import os
import re
import sys
from .PyrexTypes import CPtrType
from . import Future
from . import Annotate
from . import Code
from . import Naming
from . import Nodes
from . import Options
from . import TypeSlots
from . import PyrexTypes
from . import Pythran
from .Errors import error, warning, CompileError
from .PyrexTypes import py_object_type
from ..Utils import open_new_file, replace_suffix, decode_filename, build_hex_version, is_cython_generated_file
from .Code import UtilityCode, IncludeCode, TempitaUtilityCode
from .StringEncoding import EncodedString, encoded_string_or_bytes_literal
from .Pythran import has_np_pythran
def replace_suffix_encoded(path, newsuf):
# calls replace suffix and returns a EncodedString or BytesLiteral with the encoding set
newpath = replace_suffix(path, newsuf)
return as_encoded_filename(newpath)
def as_encoded_filename(path):
# wraps the path with either EncodedString or BytesLiteral (depending on its input type)
# and sets the encoding to the file system encoding
return encoded_string_or_bytes_literal(path, sys.getfilesystemencoding())
def check_c_declarations_pxd(module_node):
module_node.scope.check_c_classes_pxd()
return module_node
def check_c_declarations(module_node):
module_node.scope.check_c_classes()
module_node.scope.check_c_functions()
return module_node
def generate_c_code_config(env, options):
if Options.annotate or options.annotate:
emit_linenums = False
else:
emit_linenums = options.emit_linenums
if hasattr(options, "emit_code_comments"):
print('Warning: option emit_code_comments is deprecated. '
'Instead, use compiler directive emit_code_comments.')
return Code.CCodeConfig(
emit_linenums=emit_linenums,
emit_code_comments=env.directives['emit_code_comments'],
c_line_in_traceback=options.c_line_in_traceback)
# The code required to generate one comparison from another.
# The keys are (from, to).
# The comparison operator always goes first, with equality possibly second.
# The first value specifies if the comparison is inverted. The second is the
# logic op to use, and the third is if the equality is inverted or not.
TOTAL_ORDERING = {
# a > b from (not a < b) and (a != b)
('__lt__', '__gt__'): (True, '&&', True),
# a <= b from (a < b) or (a == b)
('__lt__', '__le__'): (False, '||', False),
# a >= b from (not a < b).
('__lt__', '__ge__'): (True, '', None),
# a >= b from (not a <= b) or (a == b)
('__le__', '__ge__'): (True, '||', False),
# a < b, from (a <= b) and (a != b)
('__le__', '__lt__'): (False, '&&', True),
# a > b from (not a <= b)
('__le__', '__gt__'): (True, '', None),
# a < b from (not a > b) and (a != b)
('__gt__', '__lt__'): (True, '&&', True),
# a >= b from (a > b) or (a == b)
('__gt__', '__ge__'): (False, '||', False),
# a <= b from (not a > b)
('__gt__', '__le__'): (True, '', None),
# Return a <= b from (not a >= b) or (a == b)
('__ge__', '__le__'): (True, '||', False),
# a > b from (a >= b) and (a != b)
('__ge__', '__gt__'): (False, '&&', True),
# a < b from (not a >= b)
('__ge__', '__lt__'): (True, '', None),
}
class ModuleNode(Nodes.Node, Nodes.BlockNode):
# doc string or None
# body StatListNode
#
# referenced_modules [ModuleScope]
# full_module_name string
#
# scope The module scope.
# compilation_source A CompilationSource (see Main)
# directives Top-level compiler directives
child_attrs = ["body"]
directives = None
# internal - used in merging
pxd_stats = None
utility_code_stats = None
def merge_in(self, tree, scope, stage, merge_scope=False):
# Merges in the contents of another tree, and possibly scope. With the
# current implementation below, this must be done right prior
# to code generation.
# Stage is one of "pxd" or "utility" to indicate pxd file or utility
# code. This helps define the order.
#
# Note: This way of doing it seems strange -- I believe the
# right concept is to split ModuleNode into a ModuleNode and a
# CodeGenerator, and tell that CodeGenerator to generate code
# from multiple sources.
assert isinstance(self.body, Nodes.StatListNode)
assert stage in ('pxd', 'utility')
if self.pxd_stats is None:
self.pxd_stats = Nodes.StatListNode(self.body.pos, stats=[])
self.utility_code_stats = Nodes.StatListNode(self.body.pos, stats=[])
self.body.stats.insert(0, self.pxd_stats)
self.body.stats.insert(0, self.utility_code_stats)
if scope.directives != self.scope.directives:
# merged in nodes should keep their original compiler directives
# (for example inline cdef functions)
tree = Nodes.CompilerDirectivesNode(tree.pos, body=tree, directives=scope.directives)
target_stats = self.pxd_stats if stage == "pxd" else self.utility_code_stats
if isinstance(tree, Nodes.StatListNode):
target_stats.stats.extend(tree.stats)
else:
target_stats.stats.append(tree)
self.scope.utility_code_list.extend(scope.utility_code_list)
for inc in scope.c_includes.values():
self.scope.process_include(inc)
def extend_if_not_in(L1, L2):
for x in L2:
if x not in L1:
L1.append(x)
extend_if_not_in(self.scope.included_files, scope.included_files)
if merge_scope:
# Ensure that we don't generate import code for these entries!
for entry in scope.c_class_entries:
entry.type.module_name = self.full_module_name
entry.type.scope.directives["internal"] = True
self.scope.merge_in(scope)
def with_compiler_directives(self):
# When merging a utility code module into the user code we need to preserve
# the original compiler directives. This returns the body of the module node,
# wrapped in its set of directives.
body = Nodes.CompilerDirectivesNode(self.pos, directives=self.directives, body=self.body)
return body
def analyse_declarations(self, env):
if has_np_pythran(env):
Pythran.include_pythran_generic(env)
if self.directives:
env.old_style_globals = self.directives['old_style_globals']
if not Options.docstrings:
env.doc = self.doc = None
elif Options.embed_pos_in_docstring:
env.doc = EncodedString(u'File: %s (starting at line %s)' % Nodes.relative_position(self.pos))
if self.doc is not None:
env.doc = EncodedString(env.doc + u'\n' + self.doc)
env.doc.encoding = self.doc.encoding
else:
env.doc = self.doc
env.directives = self.directives
self.body.analyse_declarations(env)
def prepare_utility_code(self):
# prepare any utility code that must be created before code generation
# specifically: CythonUtilityCode
env = self.scope
if env.has_import_star:
self.create_import_star_conversion_utility_code(env)
for name, entry in sorted(env.entries.items()):
if (entry.create_wrapper and entry.scope is env
and entry.is_type and (entry.type.is_enum or entry.type.is_cpp_enum)):
entry.type.create_type_wrapper(env)
def process_implementation(self, options, result):
env = self.scope
env.return_type = PyrexTypes.c_void_type
self.referenced_modules = []
self.find_referenced_modules(env, self.referenced_modules, {})
self.sort_cdef_classes(env)
self.generate_c_code(env, options, result)
self.generate_h_code(env, options, result)
self.generate_api_code(env, options, result)
def has_imported_c_functions(self):
for module in self.referenced_modules:
for entry in module.cfunc_entries:
if entry.defined_in_pxd:
return 1
return 0
def assure_safe_target(self, path, allow_failed=False):
# Check for a common gotcha for new users: naming your .pyx file after the .c file you want to wrap
if not is_cython_generated_file(path, allow_failed=allow_failed, if_not_found=True):
# Raising a fatal CompileError instead of calling error() to prevent castrating an existing file.
raise CompileError(
self.pos, 'The output file already exists and does not look like it was generated by Cython: "%s"' %
os.path.basename(path))
def generate_h_code(self, env, options, result):
def h_entries(entries, api=0, pxd=0):
return [entry for entry in entries
if ((entry.visibility == 'public') or
(api and entry.api) or
(pxd and entry.defined_in_pxd))]
h_types = h_entries(env.type_entries, api=1)
h_vars = h_entries(env.var_entries)
h_funcs = h_entries(env.cfunc_entries)
h_extension_types = h_entries(env.c_class_entries)
if h_types or h_vars or h_funcs or h_extension_types:
result.h_file = replace_suffix_encoded(result.c_file, ".h")
self.assure_safe_target(result.h_file)
h_code_writer = Code.CCodeWriter()
c_code_config = generate_c_code_config(env, options)
globalstate = Code.GlobalState(h_code_writer, self, c_code_config)
globalstate.initialize_main_h_code() # in-case utility code is used in the header
h_code_start = globalstate.parts['h_code']
h_code_main = globalstate.parts['type_declarations']
h_code_end = globalstate.parts['end']
if options.generate_pxi:
result.i_file = replace_suffix_encoded(result.c_file, ".pxi")
i_code = Code.PyrexCodeWriter(result.i_file)
else:
i_code = None
h_code_start.put_generated_by()
h_guard = self.api_name(Naming.h_guard_prefix, env)
h_code_start.put_h_guard(h_guard)
h_code_start.putln("")
h_code_start.putln('#include "Python.h"')
self.generate_type_header_code(h_types, h_code_start)
if options.capi_reexport_cincludes:
self.generate_includes(env, [], h_code_start)
h_code_start.putln("")
api_guard = self.api_name(Naming.api_guard_prefix, env)
h_code_start.putln("#ifndef %s" % api_guard)
h_code_start.putln("")
self.generate_extern_c_macro_definition(h_code_start, env.is_cpp())
h_code_start.putln("")
self.generate_dl_import_macro(h_code_start)
if h_extension_types:
h_code_main.putln("")
for entry in h_extension_types:
self.generate_cclass_header_code(entry.type, h_code_main)
if i_code:
self.generate_cclass_include_code(entry.type, i_code)
if h_funcs:
h_code_main.putln("")
for entry in h_funcs:
self.generate_public_declaration(entry, h_code_main, i_code)
if h_vars:
h_code_main.putln("")
for entry in h_vars:
self.generate_public_declaration(entry, h_code_main, i_code)
h_code_main.putln("")
h_code_main.putln("#endif /* !%s */" % api_guard)
h_code_main.putln("")
h_code_main.putln("/* WARNING: the interface of the module init function changed in CPython 3.5. */")
h_code_main.putln("/* It now returns a PyModuleDef instance instead of a PyModule instance. */")
h_code_main.putln("")
h_code_main.putln("#if PY_MAJOR_VERSION < 3")
if env.module_name.isascii():
py2_mod_name = env.module_name
else:
py2_mod_name = env.module_name.encode("ascii", errors="ignore").decode("utf-8")
h_code_main.putln('#error "Unicode module names are not supported in Python 2";')
h_code_main.putln("PyMODINIT_FUNC init%s(void);" % py2_mod_name)
h_code_main.putln("#else")
py3_mod_func_name = self.mod_init_func_cname('PyInit', env)
warning_string = EncodedString('Use PyImport_AppendInittab("%s", %s) instead of calling %s directly.' % (
py2_mod_name, py3_mod_func_name, py3_mod_func_name))
h_code_main.putln('/* WARNING: %s from Python 3.5 */' % warning_string.rstrip('.'))
h_code_main.putln("PyMODINIT_FUNC %s(void);" % py3_mod_func_name)
h_code_main.putln("")
h_code_main.putln("#if PY_VERSION_HEX >= 0x03050000 "
"&& (defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER) "
"|| (defined(__cplusplus) && __cplusplus >= 201402L))")
h_code_main.putln("#if defined(__cplusplus) && __cplusplus >= 201402L")
h_code_main.putln("[[deprecated(%s)]] inline" % warning_string.as_c_string_literal())
h_code_main.putln("#elif defined(__GNUC__) || defined(__clang__)")
h_code_main.putln('__attribute__ ((__deprecated__(%s), __unused__)) __inline__' % (
warning_string.as_c_string_literal()))
h_code_main.putln("#elif defined(_MSC_VER)")
h_code_main.putln('__declspec(deprecated(%s)) __inline' % (
warning_string.as_c_string_literal()))
h_code_main.putln('#endif')
h_code_main.putln("static PyObject* __PYX_WARN_IF_%s_INIT_CALLED(PyObject* res) {" % py3_mod_func_name)
h_code_main.putln("return res;")
h_code_main.putln("}")
# Function call is converted to warning macro; uncalled (pointer) is not
h_code_main.putln('#define %s() __PYX_WARN_IF_%s_INIT_CALLED(%s())' % (
py3_mod_func_name, py3_mod_func_name, py3_mod_func_name))
h_code_main.putln('#endif')
h_code_main.putln('#endif')
h_code_end.putln("")
h_code_end.putln("#endif /* !%s */" % h_guard)
with open_new_file(result.h_file) as f:
h_code_writer.copyto(f)
def generate_public_declaration(self, entry, h_code, i_code):
h_code.putln("%s %s;" % (
Naming.extern_c_macro,
entry.type.declaration_code(entry.cname)))
if i_code:
i_code.putln("cdef extern %s" % (
entry.type.declaration_code(entry.cname, pyrex=1)))
def api_name(self, prefix, env):
api_name = self.punycode_module_name(prefix, env.qualified_name)
return api_name.replace(".", "__")
def generate_api_code(self, env, options, result):
def api_entries(entries, pxd=0):
return [entry for entry in entries
if entry.api or (pxd and entry.defined_in_pxd)]
api_vars = api_entries(env.var_entries)
api_funcs = api_entries(env.cfunc_entries)
api_extension_types = api_entries(env.c_class_entries)
if api_vars or api_funcs or api_extension_types:
result.api_file = replace_suffix_encoded(result.c_file, "_api.h")
self.assure_safe_target(result.api_file)
h_code = Code.CCodeWriter()
c_code_config = generate_c_code_config(env, options)
Code.GlobalState(h_code, self, c_code_config)
h_code.put_generated_by()
api_guard = self.api_name(Naming.api_guard_prefix, env)
h_code.put_h_guard(api_guard)
# Work around https://bugs.python.org/issue4709
h_code.putln('#ifdef __MINGW64__')
h_code.putln('#define MS_WIN64')
h_code.putln('#endif')
h_code.putln('#include "Python.h"')
if result.h_file:
h_filename = os.path.basename(result.h_file)
h_filename = as_encoded_filename(h_filename)
h_code.putln('#include %s' % h_filename.as_c_string_literal())
if api_extension_types:
h_code.putln("")
for entry in api_extension_types:
type = entry.type
h_code.putln("static PyTypeObject *%s = 0;" % type.typeptr_cname)
h_code.putln("#define %s (*%s)" % (
type.typeobj_cname, type.typeptr_cname))
if api_funcs:
h_code.putln("")
for entry in api_funcs:
type = CPtrType(entry.type)
cname = env.mangle(Naming.func_prefix_api, entry.name)
h_code.putln("static %s = 0;" % type.declaration_code(cname))
h_code.putln("#define %s %s" % (entry.name, cname))
if api_vars:
h_code.putln("")
for entry in api_vars:
type = CPtrType(entry.type)
cname = env.mangle(Naming.varptr_prefix_api, entry.name)
h_code.putln("static %s = 0;" % type.declaration_code(cname))
h_code.putln("#define %s (*%s)" % (entry.name, cname))
if api_vars:
h_code.put(UtilityCode.load_as_string("VoidPtrImport", "ImportExport.c")[1])
if api_funcs:
h_code.put(UtilityCode.load_as_string("FunctionImport", "ImportExport.c")[1])
if api_extension_types:
h_code.put(UtilityCode.load_as_string("TypeImport", "ImportExport.c")[0])
h_code.put(UtilityCode.load_as_string("TypeImport", "ImportExport.c")[1])
h_code.putln("")
h_code.putln("static int %s(void) {" % self.api_name("import", env))
h_code.putln("PyObject *module = 0;")
h_code.putln('module = PyImport_ImportModule(%s);' % env.qualified_name.as_c_string_literal())
h_code.putln("if (!module) goto bad;")
for entry in api_funcs:
cname = env.mangle(Naming.func_prefix_api, entry.name)
sig = entry.type.signature_string()
h_code.putln(
'if (__Pyx_ImportFunction_%s(module, %s, (void (**)(void))&%s, "%s") < 0) goto bad;'
% (Naming.cyversion, entry.name.as_c_string_literal(), cname, sig))
for entry in api_vars:
cname = env.mangle(Naming.varptr_prefix_api, entry.name)
sig = entry.type.empty_declaration_code()
h_code.putln(
'if (__Pyx_ImportVoidPtr_%s(module, %s, (void **)&%s, "%s") < 0) goto bad;'
% (Naming.cyversion, entry.name.as_c_string_literal(), cname, sig))
with ModuleImportGenerator(h_code, imported_modules={env.qualified_name: 'module'}) as import_generator:
for entry in api_extension_types:
self.generate_type_import_call(entry.type, h_code, import_generator, error_code="goto bad;")
h_code.putln("Py_DECREF(module); module = 0;")
h_code.putln("return 0;")
h_code.putln("bad:")
h_code.putln("Py_XDECREF(module);")
h_code.putln("return -1;")
h_code.putln("}")
h_code.putln("")
h_code.putln("#endif /* !%s */" % api_guard)
f = open_new_file(result.api_file)
try:
h_code.copyto(f)
finally:
f.close()
def generate_cclass_header_code(self, type, h_code):
h_code.putln("%s %s %s;" % (
Naming.extern_c_macro,
PyrexTypes.public_decl("PyTypeObject", "DL_IMPORT"),
type.typeobj_cname))
def generate_cclass_include_code(self, type, i_code):
i_code.putln("cdef extern class %s.%s:" % (
type.module_name, type.name))
i_code.indent()
var_entries = type.scope.var_entries
if var_entries:
for entry in var_entries:
i_code.putln("cdef %s" % (
entry.type.declaration_code(entry.cname, pyrex=1)))
else:
i_code.putln("pass")
i_code.dedent()
def generate_c_code(self, env, options, result):
self.assure_safe_target(result.c_file, allow_failed=True)
modules = self.referenced_modules
if Options.annotate or options.annotate:
show_entire_c_code = Options.annotate == "fullc" or options.annotate == "fullc"
rootwriter = Annotate.AnnotationCCodeWriter(
show_entire_c_code=show_entire_c_code,
source_desc=self.compilation_source.source_desc,
)
else:
rootwriter = Code.CCodeWriter()
c_code_config = generate_c_code_config(env, options)
globalstate = Code.GlobalState(
rootwriter, self,
code_config=c_code_config,
common_utility_include_dir=options.common_utility_include_dir,
)
globalstate.initialize_main_c_code()
h_code = globalstate['h_code']
self.generate_module_preamble(env, options, modules, result.embedded_metadata, h_code)
globalstate.module_pos = self.pos
globalstate.directives = self.directives
globalstate.use_utility_code(refnanny_utility_code)
code = globalstate['before_global_var']
code.putln('#define __Pyx_MODULE_NAME %s' %
self.full_module_name.as_c_string_literal())
module_is_main = self.is_main_module_flag_cname()
code.putln("extern int %s;" % module_is_main)
code.putln("int %s = 0;" % module_is_main)
code.putln("")
code.putln("/* Implementation of %s */" % env.qualified_name.as_c_string_literal())
code = globalstate['late_includes']
self.generate_includes(env, modules, code, early=False)
code = globalstate['module_code']
self.generate_cached_builtins_decls(env, code)
# generate normal variable and function definitions
self.generate_lambda_definitions(env, code)
self.generate_variable_definitions(env, code)
self.body.generate_function_definitions(env, code)
code.mark_pos(None)
self.generate_typeobj_definitions(env, code)
self.generate_method_table(env, code)
if env.has_import_star:
self.generate_import_star(env, code)
# initialise the macro to reduce the code size of one-time functionality
code.putln(UtilityCode.load_as_string("SmallCodeConfig", "ModuleSetupCode.c")[0].strip())
self.generate_module_state_start(env, globalstate['module_state'])
self.generate_module_state_defines(env, globalstate['module_state_defines'])
self.generate_module_state_clear(env, globalstate['module_state_clear'])
self.generate_module_state_traverse(env, globalstate['module_state_traverse'])
# init_globals is inserted before this
self.generate_module_init_func(modules[:-1], env, globalstate['init_module'])
self.generate_module_cleanup_func(env, globalstate['cleanup_module'])
if Options.embed:
self.generate_main_method(env, globalstate['main_method'])
self.generate_filename_table(globalstate['filename_table'])
self.generate_declarations_for_modules(env, modules, globalstate)
h_code.write('\n')
for utilcode in env.utility_code_list[:]:
globalstate.use_utility_code(utilcode)
globalstate.finalize_main_c_code()
self.generate_module_state_end(env, modules, globalstate)
f = open_new_file(result.c_file)
try:
rootwriter.copyto(f)
finally:
f.close()
result.c_file_generated = 1
if options.gdb_debug:
self._serialize_lineno_map(env, rootwriter)
if Options.annotate or options.annotate:
self._generate_annotations(rootwriter, result, options)
def _generate_annotations(self, rootwriter, result, options):
self.annotate(rootwriter)
coverage_xml_filename = Options.annotate_coverage_xml or options.annotate_coverage_xml
if coverage_xml_filename and os.path.exists(coverage_xml_filename):
try:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
coverage_xml = ET.parse(coverage_xml_filename).getroot()
for el in coverage_xml.iter():
el.tail = None # save some memory
else:
coverage_xml = None
rootwriter.save_annotation(result.main_source_file, result.c_file, coverage_xml=coverage_xml)
# if we included files, additionally generate one annotation file for each
if not self.scope.included_files:
return
search_include_file = self.scope.context.search_include_directories
target_dir = os.path.abspath(os.path.dirname(result.c_file))
for included_file in self.scope.included_files:
target_file = os.path.abspath(os.path.join(target_dir, included_file))
target_file_dir = os.path.dirname(target_file)
if not target_file_dir.startswith(target_dir):
# any other directories may not be writable => avoid trying
continue
source_file = search_include_file(included_file, source_pos=self.pos, include=True)
if not source_file:
continue
if target_file_dir != target_dir and not os.path.exists(target_file_dir):
try:
os.makedirs(target_file_dir)
except OSError as e:
import errno
if e.errno != errno.EEXIST:
raise
rootwriter.save_annotation(source_file, target_file, coverage_xml=coverage_xml)
def _serialize_lineno_map(self, env, ccodewriter):
tb = env.context.gdb_debug_outputwriter
markers = ccodewriter.buffer.allmarkers()
d = defaultdict(list)
for c_lineno, (src_desc, src_lineno) in enumerate(markers):
if src_lineno > 0 and src_desc.filename is not None:
d[src_desc, src_lineno].append(c_lineno + 1)
tb.start('LineNumberMapping')
for (src_desc, src_lineno), c_linenos in sorted(d.items()):
assert src_desc.filename is not None
tb.add_entry(
'LineNumber',
c_linenos=' '.join(map(str, c_linenos)),
src_path=src_desc.filename,
src_lineno=str(src_lineno),
)
tb.end('LineNumberMapping')
tb.serialize()
def find_referenced_modules(self, env, module_list, modules_seen):
if env not in modules_seen:
modules_seen[env] = 1
for imported_module in env.cimported_modules:
self.find_referenced_modules(imported_module, module_list, modules_seen)
module_list.append(env)
def sort_types_by_inheritance(self, type_dict, type_order, getkey):
subclasses = defaultdict(list) # maps type key to list of subclass keys
for key in type_order:
new_entry = type_dict[key]
# collect all base classes to check for children
base = new_entry.type.base_type
while base:
base_key = getkey(base)
subclasses[base_key].append(key)
base_entry = type_dict.get(base_key)
if base_entry is None:
break
base = base_entry.type.base_type
# Simple topological sort using recursive DFS, based on
# https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
seen = set()
result = []
def dfs(u):
if u in seen:
return
seen.add(u)
for v in subclasses[getkey(u.type)]:
dfs(type_dict[v])
result.append(u)
for key in reversed(type_order):
dfs(type_dict[key])
result.reverse()
return result
def sort_type_hierarchy(self, module_list, env):
# poor developer's OrderedDict
vtab_dict, vtab_dict_order = {}, []
vtabslot_dict, vtabslot_dict_order = {}, []
for module in module_list:
for entry in module.c_class_entries:
if entry.used and not entry.in_cinclude:
type = entry.type
key = type.vtabstruct_cname
if not key:
continue
if key in vtab_dict:
# FIXME: this should *never* happen, but apparently it does
# for Cython generated utility code
from .UtilityCode import NonManglingModuleScope
assert isinstance(entry.scope, NonManglingModuleScope), str(entry.scope)
assert isinstance(vtab_dict[key].scope, NonManglingModuleScope), str(vtab_dict[key].scope)
else:
vtab_dict[key] = entry
vtab_dict_order.append(key)
all_defined_here = module is env
for entry in module.type_entries:
if entry.used and (all_defined_here or entry.defined_in_pxd):
type = entry.type
if type.is_extension_type and not entry.in_cinclude:
type = entry.type
key = type.objstruct_cname
assert key not in vtabslot_dict, key
vtabslot_dict[key] = entry
vtabslot_dict_order.append(key)
def vtabstruct_cname(entry_type):
return entry_type.vtabstruct_cname
vtab_list = self.sort_types_by_inheritance(
vtab_dict, vtab_dict_order, vtabstruct_cname)
def objstruct_cname(entry_type):
return entry_type.objstruct_cname
vtabslot_list = self.sort_types_by_inheritance(
vtabslot_dict, vtabslot_dict_order, objstruct_cname)
return (vtab_list, vtabslot_list)
def sort_cdef_classes(self, env):
key_func = operator.attrgetter('objstruct_cname')
entry_dict, entry_order = {}, []
for entry in env.c_class_entries:
key = key_func(entry.type)
assert key not in entry_dict, key
entry_dict[key] = entry
entry_order.append(key)
env.c_class_entries[:] = self.sort_types_by_inheritance(
entry_dict, entry_order, key_func)
def generate_type_definitions(self, env, modules, vtab_list, vtabslot_list, code):
# TODO: Why are these separated out?
for entry in vtabslot_list:
self.generate_objstruct_predeclaration(entry.type, code)
vtabslot_entries = set(vtabslot_list)
ctuple_names = set()
for module in modules:
definition = module is env
type_entries = []
for entry in module.type_entries:
if entry.type.is_ctuple and entry.used:
if entry.name not in ctuple_names:
ctuple_names.add(entry.name)
type_entries.append(entry)
elif definition or entry.defined_in_pxd:
type_entries.append(entry)
type_entries = [t for t in type_entries if t not in vtabslot_entries]
self.generate_type_header_code(type_entries, code)
for entry in vtabslot_list:
self.generate_objstruct_definition(entry.type, code)
self.generate_typeobj_predeclaration(entry, code)
for entry in vtab_list:
self.generate_typeobj_predeclaration(entry, code)
self.generate_exttype_vtable_struct(entry, code)
self.generate_exttype_vtabptr_declaration(entry, code)
self.generate_exttype_final_methods_declaration(entry, code)
def generate_declarations_for_modules(self, env, modules, globalstate):
typecode = globalstate['type_declarations']
typecode.putln("")
typecode.putln("/*--- Type declarations ---*/")
# This is to work around the fact that array.h isn't part of the C-API,
# but we need to declare it earlier than utility code.
if 'cpython.array' in [m.qualified_name for m in modules]:
typecode.putln('#ifndef _ARRAYARRAY_H')
typecode.putln('struct arrayobject;')
typecode.putln('typedef struct arrayobject arrayobject;')
typecode.putln('#endif')
vtab_list, vtabslot_list = self.sort_type_hierarchy(modules, env)
self.generate_type_definitions(
env, modules, vtab_list, vtabslot_list, typecode)
modulecode = globalstate['module_declarations']
for module in modules:
defined_here = module is env
modulecode.putln("")
modulecode.putln("/* Module declarations from %s */" % module.qualified_name.as_c_string_literal())
self.generate_c_class_declarations(module, modulecode, defined_here, globalstate)
self.generate_cvariable_declarations(module, modulecode, defined_here)
self.generate_cfunction_declarations(module, modulecode, defined_here)
@staticmethod
def _put_setup_code(code, name):
code.put(UtilityCode.load_as_string(name, "ModuleSetupCode.c")[1])
def generate_module_preamble(self, env, options, cimported_modules, metadata, code):
code.put_generated_by()
if metadata:
code.putln("/* BEGIN: Cython Metadata")
code.putln(json.dumps(metadata, indent=4, sort_keys=True))
code.putln("END: Cython Metadata */")
code.putln("")
code.putln("#ifndef PY_SSIZE_T_CLEAN")
code.putln("#define PY_SSIZE_T_CLEAN")
code.putln("#endif /* PY_SSIZE_T_CLEAN */")
self._put_setup_code(code, "InitLimitedAPI")
for inc in sorted(env.c_includes.values(), key=IncludeCode.sortkey):
if inc.location == inc.INITIAL:
inc.write(code)
code.putln("#ifndef Py_PYTHON_H")
code.putln(" #error Python headers needed to compile C extensions, "
"please install development version of Python.")
code.putln("#elif PY_VERSION_HEX < 0x02070000 || "
"(0x03000000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x03030000)")
code.putln(" #error Cython requires Python 2.7+ or Python 3.3+.")
code.putln("#else")
code.globalstate["end"].putln("#endif /* Py_PYTHON_H */")
from .. import __version__
code.putln('#if defined(CYTHON_LIMITED_API) && CYTHON_LIMITED_API') # CYTHON_COMPILING_IN_LIMITED_API not yet defined
# The limited API makes some significant changes to data structures, so we don't
# want to shared implementation compiled with and without the limited API.
code.putln('#define __PYX_EXTRA_ABI_MODULE_NAME "limited"')
code.putln('#else')
code.putln('#define __PYX_EXTRA_ABI_MODULE_NAME ""')
code.putln('#endif')
code.putln('#define CYTHON_ABI "%s" __PYX_EXTRA_ABI_MODULE_NAME' %
__version__.replace('.', '_'))
code.putln('#define __PYX_ABI_MODULE_NAME "_cython_" CYTHON_ABI')
code.putln('#define __PYX_TYPE_MODULE_PREFIX __PYX_ABI_MODULE_NAME "."')
code.putln('#define CYTHON_HEX_VERSION %s' % build_hex_version(__version__))
code.putln("#define CYTHON_FUTURE_DIVISION %d" % (
Future.division in env.context.future_directives))
self._put_setup_code(code, "CModulePreamble")
if env.context.options.cplus:
self._put_setup_code(code, "CppInitCode")
else:
self._put_setup_code(code, "CInitCode")
self._put_setup_code(code, "PythonCompatibility")
self._put_setup_code(code, "MathInitCode")
# Using "(void)cname" to prevent "unused" warnings.
if options.c_line_in_traceback:
cinfo = "%s = %s; (void)%s; " % (Naming.clineno_cname, Naming.line_c_macro, Naming.clineno_cname)
else:
cinfo = ""
code.putln("#define __PYX_MARK_ERR_POS(f_index, lineno) \\")
code.putln(" { %s = %s[f_index]; (void)%s; %s = lineno; (void)%s; %s}" % (
Naming.filename_cname, Naming.filetable_cname, Naming.filename_cname,
Naming.lineno_cname, Naming.lineno_cname,
cinfo
))
code.putln("#define __PYX_ERR(f_index, lineno, Ln_error) \\")
code.putln(" { __PYX_MARK_ERR_POS(f_index, lineno) goto Ln_error; }")
code.putln("")
self.generate_extern_c_macro_definition(code, env.is_cpp())
code.putln("")
code.putln("#define %s" % self.api_name(Naming.h_guard_prefix, env))
code.putln("#define %s" % self.api_name(Naming.api_guard_prefix, env))
code.putln("/* Early includes */")
self.generate_includes(env, cimported_modules, code, late=False)
code.putln("")
code.putln("#if defined(PYREX_WITHOUT_ASSERTIONS) && !defined(CYTHON_WITHOUT_ASSERTIONS)")
code.putln("#define CYTHON_WITHOUT_ASSERTIONS")
code.putln("#endif")
code.putln("")
if env.directives['ccomplex']:
code.putln("")
code.putln("#if !defined(CYTHON_CCOMPLEX)")
code.putln("#define CYTHON_CCOMPLEX 1")
code.putln("#endif")
code.putln("")
code.put(UtilityCode.load_as_string("UtilityFunctionPredeclarations", "ModuleSetupCode.c")[0])
c_string_type = env.directives['c_string_type']
c_string_encoding = env.directives['c_string_encoding']
if c_string_type not in ('bytes', 'bytearray') and not c_string_encoding:
error(self.pos, "a default encoding must be provided if c_string_type is not a byte type")
code.putln('#define __PYX_DEFAULT_STRING_ENCODING_IS_ASCII %s' % int(c_string_encoding == 'ascii'))
code.putln('#define __PYX_DEFAULT_STRING_ENCODING_IS_UTF8 %s' %
int(c_string_encoding.replace('-', '').lower() == 'utf8'))
if c_string_encoding == 'default':
code.putln('#define __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT 1')
else:
code.putln('#define __PYX_DEFAULT_STRING_ENCODING_IS_DEFAULT '
'(PY_MAJOR_VERSION >= 3 && __PYX_DEFAULT_STRING_ENCODING_IS_UTF8)')
code.putln('#define __PYX_DEFAULT_STRING_ENCODING "%s"' % c_string_encoding)
if c_string_type == 'bytearray':
c_string_func_name = 'ByteArray'
else:
c_string_func_name = c_string_type.title()
code.putln('#define __Pyx_PyObject_FromString __Pyx_Py%s_FromString' % c_string_func_name)
code.putln('#define __Pyx_PyObject_FromStringAndSize __Pyx_Py%s_FromStringAndSize' % c_string_func_name)
code.put(UtilityCode.load_as_string("TypeConversions", "TypeConversion.c")[0])
env.use_utility_code(UtilityCode.load_cached("FormatTypeName", "ObjectHandling.c"))
# These utility functions are assumed to exist and used elsewhere.
PyrexTypes.c_long_type.create_to_py_utility_code(env)
PyrexTypes.c_long_type.create_from_py_utility_code(env)
PyrexTypes.c_int_type.create_from_py_utility_code(env)
code.put(Nodes.branch_prediction_macros)
code.putln('static CYTHON_INLINE void __Pyx_pretend_to_initialize(void* ptr) { (void)ptr; }')
code.putln('')
code.putln('#if !CYTHON_USE_MODULE_STATE')
code.putln('static PyObject *%s = NULL;' % env.module_cname)
if Options.pre_import is not None:
code.putln('static PyObject *%s;' % Naming.preimport_cname)
code.putln('#endif')
code.putln('static int %s;' % Naming.lineno_cname)
code.putln('static int %s = 0;' % Naming.clineno_cname)
code.putln('static const char * %s = %s;' % (Naming.cfilenm_cname, Naming.file_c_macro))
code.putln('static const char *%s;' % Naming.filename_cname)
env.use_utility_code(UtilityCode.load_cached("FastTypeChecks", "ModuleSetupCode.c"))
if has_np_pythran(env):
env.use_utility_code(UtilityCode.load_cached("PythranConversion", "CppSupport.cpp"))
def generate_extern_c_macro_definition(self, code, is_cpp):
name = Naming.extern_c_macro
code.putln("#ifdef CYTHON_EXTERN_C")
# make sure that user overrides always take precedence
code.putln(' #undef %s' % name)
code.putln(' #define %s CYTHON_EXTERN_C' % name)
code.putln("#elif defined(%s)" % name)
code.putln(" #ifdef _MSC_VER")
code.putln(" #pragma message (\"Please do not define the '%s' macro externally. Use 'CYTHON_EXTERN_C' instead.\")" % name)
code.putln(" #else")
code.putln(" #warning Please do not define the '%s' macro externally. Use 'CYTHON_EXTERN_C' instead." % name)
code.putln(" #endif")
code.putln("#else")
if is_cpp:
code.putln(' #define %s extern "C++"' % name)
else:
code.putln(" #ifdef __cplusplus")
code.putln(' #define %s extern "C"' % name)
code.putln(" #else")
code.putln(" #define %s extern" % name)
code.putln(" #endif")
code.putln("#endif")
def generate_dl_import_macro(self, code):
code.putln("#ifndef DL_IMPORT")
code.putln(" #define DL_IMPORT(_T) _T")
code.putln("#endif")
def generate_includes(self, env, cimported_modules, code, early=True, late=True):
for inc in sorted(env.c_includes.values(), key=IncludeCode.sortkey):
if inc.location == inc.EARLY:
if early:
inc.write(code)
elif inc.location == inc.LATE:
if late:
inc.write(code)
if early:
code.putln_openmp("#include <omp.h>")
def generate_filename_table(self, code):
from os.path import isabs, basename
code.putln("")
code.putln("static const char *%s[] = {" % Naming.filetable_cname)
if code.globalstate.filename_list:
for source_desc in code.globalstate.filename_list:
file_path = source_desc.get_filenametable_entry()
if isabs(file_path):
file_path = basename(file_path) # never include absolute paths
escaped_filename = file_path.replace("\\", "\\\\").replace('"', r'\"')
escaped_filename = as_encoded_filename(escaped_filename)
code.putln('%s,' % escaped_filename.as_c_string_literal())
else:
# Some C compilers don't like an empty array
code.putln("0")
code.putln("};")
def generate_type_predeclarations(self, env, code):
pass
def generate_type_header_code(self, type_entries, code):
# Generate definitions of structs/unions/enums/typedefs/objstructs.
#self.generate_gcc33_hack(env, code) # Is this still needed?
# Forward declarations
for entry in type_entries:
if not entry.in_cinclude:
#print "generate_type_header_code:", entry.name, repr(entry.type) ###
type = entry.type
if type.is_typedef: # Must test this first!
pass
elif type.is_struct_or_union or type.is_cpp_class:
self.generate_struct_union_predeclaration(entry, code)
elif type.is_ctuple and entry.used:
self.generate_struct_union_predeclaration(entry.type.struct_entry, code)
elif type.is_extension_type:
self.generate_objstruct_predeclaration(type, code)
# Actual declarations
for entry in type_entries:
if not entry.in_cinclude:
#print "generate_type_header_code:", entry.name, repr(entry.type) ###
type = entry.type
if type.is_typedef: # Must test this first!
self.generate_typedef(entry, code)
elif type.is_enum or type.is_cpp_enum:
self.generate_enum_definition(entry, code)
elif type.is_struct_or_union:
self.generate_struct_union_definition(entry, code)
elif type.is_ctuple and entry.used:
self.generate_struct_union_definition(entry.type.struct_entry, code)
elif type.is_cpp_class:
self.generate_cpp_class_definition(entry, code)
elif type.is_extension_type:
self.generate_objstruct_definition(type, code)
def generate_gcc33_hack(self, env, code):
# Workaround for spurious warning generation in gcc 3.3
code.putln("")
for entry in env.c_class_entries:
type = entry.type
if not type.typedef_flag:
name = type.objstruct_cname
if name.startswith("__pyx_"):
tail = name[6:]