1import itertools 2from time import time 3 4import Errors 5import DebugFlags 6import Options 7from Visitor import CythonTransform 8from Errors import CompileError, InternalError, AbortError 9import Naming 10 11# 12# Really small pipeline stages 13# 14def dumptree(t): 15 # For quick debugging in pipelines 16 print t.dump() 17 return t 18 19def abort_on_errors(node): 20 # Stop the pipeline if there are any errors. 21 if Errors.num_errors != 0: 22 raise AbortError("pipeline break") 23 return node 24 25def parse_stage_factory(context): 26 def parse(compsrc): 27 source_desc = compsrc.source_desc 28 full_module_name = compsrc.full_module_name 29 initial_pos = (source_desc, 1, 0) 30 saved_cimport_from_pyx, Options.cimport_from_pyx = Options.cimport_from_pyx, False 31 scope = context.find_module(full_module_name, pos = initial_pos, need_pxd = 0, 32 check_module_name = not Options.embed) 33 Options.cimport_from_pyx = saved_cimport_from_pyx 34 tree = context.parse(source_desc, scope, pxd = 0, full_module_name = full_module_name) 35 tree.compilation_source = compsrc 36 tree.scope = scope 37 tree.is_pxd = False 38 return tree 39 return parse 40 41def parse_pxd_stage_factory(context, scope, module_name): 42 def parse(source_desc): 43 tree = context.parse(source_desc, scope, pxd=True, 44 full_module_name=module_name) 45 tree.scope = scope 46 tree.is_pxd = True 47 return tree 48 return parse 49 50def generate_pyx_code_stage_factory(options, result): 51 def generate_pyx_code_stage(module_node): 52 module_node.process_implementation(options, result) 53 result.compilation_source = module_node.compilation_source 54 return result 55 return generate_pyx_code_stage 56 57def inject_pxd_code_stage_factory(context): 58 def inject_pxd_code_stage(module_node): 59 from textwrap import dedent 60 stats = module_node.body.stats 61 for name, (statlistnode, scope) in context.pxds.iteritems(): 62 module_node.merge_in(statlistnode, scope) 63 return module_node 64 return inject_pxd_code_stage 65 66def use_utility_code_definitions(scope, target, seen=None): 67 if seen is None: 68 seen = set() 69 70 for entry in scope.entries.itervalues(): 71 if entry in seen: 72 continue 73 74 seen.add(entry) 75 if entry.used and entry.utility_code_definition: 76 target.use_utility_code(entry.utility_code_definition) 77 for required_utility in entry.utility_code_definition.requires: 78 target.use_utility_code(required_utility) 79 elif entry.as_module: 80 use_utility_code_definitions(entry.as_module, target, seen) 81 82def inject_utility_code_stage_factory(context): 83 def inject_utility_code_stage(module_node): 84 use_utility_code_definitions(context.cython_scope, module_node.scope) 85 added = [] 86 # Note: the list might be extended inside the loop (if some utility code 87 # pulls in other utility code, explicitly or implicitly) 88 for utilcode in module_node.scope.utility_code_list: 89 if utilcode in added: continue 90 added.append(utilcode) 91 if utilcode.requires: 92 for dep in utilcode.requires: 93 if not dep in added and not dep in module_node.scope.utility_code_list: 94 module_node.scope.utility_code_list.append(dep) 95 tree = utilcode.get_tree() 96 if tree: 97 module_node.merge_in(tree.body, tree.scope, merge_scope=True) 98 return module_node 99 return inject_utility_code_stage 100 101class UseUtilityCodeDefinitions(CythonTransform): 102 # Temporary hack to use any utility code in nodes' "utility_code_definitions". 103 # This should be moved to the code generation phase of the relevant nodes once 104 # it is safe to generate CythonUtilityCode at code generation time. 105 def __call__(self, node): 106 self.scope = node.scope 107 return super(UseUtilityCodeDefinitions, self).__call__(node) 108 109 def process_entry(self, entry): 110 if entry: 111 for utility_code in (entry.utility_code, entry.utility_code_definition): 112 if utility_code: 113 self.scope.use_utility_code(utility_code) 114 115 def visit_AttributeNode(self, node): 116 self.process_entry(node.entry) 117 return node 118 119 def visit_NameNode(self, node): 120 self.process_entry(node.entry) 121 self.process_entry(node.type_entry) 122 return node 123 124# 125# Pipeline factories 126# 127 128def create_pipeline(context, mode, exclude_classes=()): 129 assert mode in ('pyx', 'py', 'pxd') 130 from Visitor import PrintTree 131 from ParseTreeTransforms import WithTransform, NormalizeTree, PostParse, PxdPostParse 132 from ParseTreeTransforms import ForwardDeclareTypes, AnalyseDeclarationsTransform 133 from ParseTreeTransforms import AnalyseExpressionsTransform, FindInvalidUseOfFusedTypes 134 from ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform 135 from ParseTreeTransforms import InterpretCompilerDirectives, TransformBuiltinMethods 136 from ParseTreeTransforms import ExpandInplaceOperators, ParallelRangeTransform 137 from ParseTreeTransforms import CalculateQualifiedNamesTransform 138 from TypeInference import MarkParallelAssignments, MarkOverflowingArithmetic 139 from ParseTreeTransforms import AdjustDefByDirectives, AlignFunctionDefinitions 140 from ParseTreeTransforms import RemoveUnreachableCode, GilCheck 141 from FlowControl import ControlFlowAnalysis 142 from AnalysedTreeTransforms import AutoTestDictTransform 143 from AutoDocTransforms import EmbedSignature 144 from Optimize import FlattenInListTransform, SwitchTransform, IterationTransform 145 from Optimize import EarlyReplaceBuiltinCalls, OptimizeBuiltinCalls 146 from Optimize import InlineDefNodeCalls 147 from Optimize import ConstantFolding, FinalOptimizePhase 148 from Optimize import DropRefcountingTransform 149 from Optimize import ConsolidateOverflowCheck 150 from Buffer import IntroduceBufferAuxiliaryVars 151 from ModuleNode import check_c_declarations, check_c_declarations_pxd 152 153 154 if mode == 'pxd': 155 _check_c_declarations = check_c_declarations_pxd 156 _specific_post_parse = PxdPostParse(context) 157 else: 158 _check_c_declarations = check_c_declarations 159 _specific_post_parse = None 160 161 if mode == 'py': 162 _align_function_definitions = AlignFunctionDefinitions(context) 163 else: 164 _align_function_definitions = None 165 166 # NOTE: This is the "common" parts of the pipeline, which is also 167 # code in pxd files. So it will be run multiple times in a 168 # compilation stage. 169 stages = [ 170 NormalizeTree(context), 171 PostParse(context), 172 _specific_post_parse, 173 InterpretCompilerDirectives(context, context.compiler_directives), 174 ParallelRangeTransform(context), 175 AdjustDefByDirectives(context), 176 MarkClosureVisitor(context), 177 _align_function_definitions, 178 RemoveUnreachableCode(context), 179 ConstantFolding(), 180 FlattenInListTransform(), 181 WithTransform(context), 182 DecoratorTransform(context), 183 ForwardDeclareTypes(context), 184 AnalyseDeclarationsTransform(context), 185 AutoTestDictTransform(context), 186 EmbedSignature(context), 187 EarlyReplaceBuiltinCalls(context), ## Necessary? 188 TransformBuiltinMethods(context), ## Necessary? 189 MarkParallelAssignments(context), 190 ControlFlowAnalysis(context), 191 RemoveUnreachableCode(context), 192 # MarkParallelAssignments(context), 193 MarkOverflowingArithmetic(context), 194 IntroduceBufferAuxiliaryVars(context), 195 _check_c_declarations, 196 InlineDefNodeCalls(context), 197 AnalyseExpressionsTransform(context), 198 FindInvalidUseOfFusedTypes(context), 199 ExpandInplaceOperators(context), 200 OptimizeBuiltinCalls(context), ## Necessary? 201 CreateClosureClasses(context), ## After all lookups and type inference 202 CalculateQualifiedNamesTransform(context), 203 ConsolidateOverflowCheck(context), 204 IterationTransform(context), 205 SwitchTransform(), 206 DropRefcountingTransform(), 207 FinalOptimizePhase(context), 208 GilCheck(), 209 UseUtilityCodeDefinitions(context), 210 ] 211 filtered_stages = [] 212 for s in stages: 213 if s.__class__ not in exclude_classes: 214 filtered_stages.append(s) 215 return filtered_stages 216 217def create_pyx_pipeline(context, options, result, py=False, exclude_classes=()): 218 if py: 219 mode = 'py' 220 else: 221 mode = 'pyx' 222 test_support = [] 223 if options.evaluate_tree_assertions: 224 from Cython.TestUtils import TreeAssertVisitor 225 test_support.append(TreeAssertVisitor()) 226 227 if options.gdb_debug: 228 from Cython.Debugger import DebugWriter # requires Py2.5+ 229 from ParseTreeTransforms import DebugTransform 230 context.gdb_debug_outputwriter = DebugWriter.CythonDebugWriter( 231 options.output_dir) 232 debug_transform = [DebugTransform(context, options, result)] 233 else: 234 debug_transform = [] 235 236 return list(itertools.chain( 237 [parse_stage_factory(context)], 238 create_pipeline(context, mode, exclude_classes=exclude_classes), 239 test_support, 240 [inject_pxd_code_stage_factory(context), 241 inject_utility_code_stage_factory(context), 242 abort_on_errors], 243 debug_transform, 244 [generate_pyx_code_stage_factory(options, result)])) 245 246def create_pxd_pipeline(context, scope, module_name): 247 from CodeGeneration import ExtractPxdCode 248 249 # The pxd pipeline ends up with a CCodeWriter containing the 250 # code of the pxd, as well as a pxd scope. 251 return [ 252 parse_pxd_stage_factory(context, scope, module_name) 253 ] + create_pipeline(context, 'pxd') + [ 254 ExtractPxdCode() 255 ] 256 257def create_py_pipeline(context, options, result): 258 return create_pyx_pipeline(context, options, result, py=True) 259 260def create_pyx_as_pxd_pipeline(context, result): 261 from ParseTreeTransforms import AlignFunctionDefinitions, \ 262 MarkClosureVisitor, WithTransform, AnalyseDeclarationsTransform 263 from Optimize import ConstantFolding, FlattenInListTransform 264 from Nodes import StatListNode 265 pipeline = [] 266 pyx_pipeline = create_pyx_pipeline(context, context.options, result, 267 exclude_classes=[ 268 AlignFunctionDefinitions, 269 MarkClosureVisitor, 270 ConstantFolding, 271 FlattenInListTransform, 272 WithTransform 273 ]) 274 for stage in pyx_pipeline: 275 pipeline.append(stage) 276 if isinstance(stage, AnalyseDeclarationsTransform): 277 # This is the last stage we need. 278 break 279 def fake_pxd(root): 280 for entry in root.scope.entries.values(): 281 if not entry.in_cinclude: 282 entry.defined_in_pxd = 1 283 if entry.name == entry.cname and entry.visibility != 'extern': 284 # Always mangle non-extern cimported entries. 285 entry.cname = entry.scope.mangle(Naming.func_prefix, entry.name) 286 return StatListNode(root.pos, stats=[]), root.scope 287 pipeline.append(fake_pxd) 288 return pipeline 289 290def insert_into_pipeline(pipeline, transform, before=None, after=None): 291 """ 292 Insert a new transform into the pipeline after or before an instance of 293 the given class. e.g. 294 295 pipeline = insert_into_pipeline(pipeline, transform, 296 after=AnalyseDeclarationsTransform) 297 """ 298 assert before or after 299 300 cls = before or after 301 for i, t in enumerate(pipeline): 302 if isinstance(t, cls): 303 break 304 305 if after: 306 i += 1 307 308 return pipeline[:i] + [transform] + pipeline[i:] 309 310# 311# Running a pipeline 312# 313 314def run_pipeline(pipeline, source, printtree=True): 315 from Cython.Compiler.Visitor import PrintTree 316 317 error = None 318 data = source 319 try: 320 try: 321 for phase in pipeline: 322 if phase is not None: 323 if DebugFlags.debug_verbose_pipeline: 324 t = time() 325 print "Entering pipeline phase %r" % phase 326 if not printtree and isinstance(phase, PrintTree): 327 continue 328 data = phase(data) 329 if DebugFlags.debug_verbose_pipeline: 330 print " %.3f seconds" % (time() - t) 331 except CompileError, err: 332 # err is set 333 Errors.report_error(err) 334 error = err 335 except InternalError, err: 336 # Only raise if there was not an earlier error 337 if Errors.num_errors == 0: 338 raise 339 error = err 340 except AbortError, err: 341 error = err 342 return (error, data) 343