1 | #!/usr/bin/env python3
|
2 | from __future__ import print_function
|
3 | """
|
4 | translate.py - Hook up all the stages
|
5 | """
|
6 |
|
7 | import os
|
8 | import sys
|
9 | import tempfile
|
10 | import time
|
11 |
|
12 | # Our code
|
13 | #from _devbuild.gen.mycpp_asdl import mtype
|
14 |
|
15 | from mycpp import const_pass
|
16 | from mycpp import cppgen_pass
|
17 | from mycpp import control_flow_pass
|
18 | from mycpp import conversion_pass
|
19 | from mycpp import pass_state
|
20 | from mycpp.util import log
|
21 | from mycpp import visitor
|
22 |
|
23 | from typing import (Dict, List, Tuple, Any, TextIO, TYPE_CHECKING)
|
24 |
|
25 | if TYPE_CHECKING:
|
26 | from mypy.nodes import FuncDef, MypyFile, Expression
|
27 | from mypy.types import Type
|
28 |
|
29 |
|
30 | class Timer:
|
31 | """
|
32 | Example timings:
|
33 |
|
34 | So loading it takes 13.4 seconds, and the rest only takes 2 seconds. If we
|
35 | combine const pass and forward decl pass, that's only a couple hundred
|
36 | milliseconds. So might as well keep them separate.
|
37 |
|
38 | [0.1] mycpp: LOADING asdl/format.py ...
|
39 | [13.7] mycpp pass: CONVERT
|
40 | [13.8] mycpp pass: CONST
|
41 | [14.0] mycpp pass: DECL
|
42 | [14.4] mycpp pass: CONTROL FLOW
|
43 | [15.0] mycpp pass: DATAFLOW
|
44 | [15.0] mycpp pass: IMPL
|
45 | [15.5] mycpp DONE
|
46 | """
|
47 |
|
48 | def __init__(self, start_time: float):
|
49 | self.start_time = start_time
|
50 |
|
51 | def Section(self, msg: str, *args: Any) -> None:
|
52 | elapsed = time.time() - self.start_time
|
53 |
|
54 | if args:
|
55 | msg = msg % args
|
56 |
|
57 | #log('\t[%.1f] %s', elapsed, msg)
|
58 | log('\t%s', msg)
|
59 |
|
60 |
|
61 | def Run(timer: Timer,
|
62 | f: TextIO,
|
63 | header_f: TextIO,
|
64 | types: Dict['Expression', 'Type'],
|
65 | to_header: List[str],
|
66 | to_compile: List[Tuple[str, 'MypyFile']],
|
67 | preamble_path: str = '',
|
68 | stack_roots_warn: bool = False,
|
69 | minimize_stack_roots: bool = False) -> int:
|
70 |
|
71 | #_ = mtype
|
72 | #if 0:
|
73 | # log('m %r' % mtype)
|
74 | # log('m %r' % mtype.Callable)
|
75 |
|
76 | # default value
|
77 | preamble_path = preamble_path or 'mycpp/runtime.h'
|
78 | f.write('#include "%s"\n' % preamble_path)
|
79 | f.write('\n')
|
80 |
|
81 | # Which functions are C++ 'virtual'?
|
82 | virtual = pass_state.Virtual()
|
83 |
|
84 | all_member_vars: cppgen_pass.AllMemberVars = {}
|
85 | all_local_vars: cppgen_pass.AllLocalVars = {}
|
86 | dot_exprs: Dict[str, conversion_pass.DotExprs] = {}
|
87 | yield_out_params: Dict[FuncDef, Tuple[str, str]] = {}
|
88 | dunder_exit_special: Dict[FuncDef, bool] = {}
|
89 |
|
90 | # [PASS] namespace foo { class Spam; class Eggs; }
|
91 | timer.Section('mycpp pass: CONVERT')
|
92 |
|
93 | for name, module in to_compile:
|
94 | forward_decls: List[str] = [] # unused
|
95 | module_dot_exprs: conversion_pass.DotExprs = {}
|
96 | p_convert = conversion_pass.Pass(
|
97 | types,
|
98 | virtual, # output
|
99 | forward_decls, # TODO: write output of forward_decls
|
100 | all_member_vars, # output
|
101 | all_local_vars, # output
|
102 | module_dot_exprs, # output
|
103 | yield_out_params, # output
|
104 | dunder_exit_special, # output
|
105 | )
|
106 | # forward declarations may go to header
|
107 | p_convert.SetOutputFile(header_f if name in to_header else f)
|
108 | p_convert.visit_mypy_file(module)
|
109 | MaybeExitWithErrors(p_convert)
|
110 |
|
111 | dot_exprs[module.path] = module_dot_exprs
|
112 |
|
113 | if 0:
|
114 | for node in dunder_exit_special:
|
115 | log(' *** ctx_EXIT %s', node.name)
|
116 |
|
117 | # After seeing class and method names in the first pass, figure out which
|
118 | # ones are virtual. We use this info in the second pass.
|
119 | virtual.Calculate()
|
120 | if 0:
|
121 | log('virtuals %s', virtual.virtuals)
|
122 | log('has_vtable %s', virtual.has_vtable)
|
123 |
|
124 | # [PASS]
|
125 | timer.Section('mycpp pass: CONTROL FLOW')
|
126 |
|
127 | cflow_graphs = {} # fully qualified function name -> control flow graph
|
128 | for name, module in to_compile:
|
129 | p_cflow = control_flow_pass.Build(types, virtual, all_local_vars,
|
130 | dot_exprs[module.path])
|
131 | p_cflow.visit_mypy_file(module)
|
132 | cflow_graphs.update(p_cflow.cflow_graphs)
|
133 | MaybeExitWithErrors(p_cflow)
|
134 |
|
135 | # [PASS] Conditionally run Souffle
|
136 | stack_roots = None
|
137 | if minimize_stack_roots:
|
138 | timer.Section('mycpp pass: SOUFFLE data flow')
|
139 |
|
140 | # souffle_dir contains two subdirectories.
|
141 | # facts: TSV files for the souffle inputs generated by mycpp
|
142 | # outputs: TSV files for the solver's output relations
|
143 | souffle_dir = os.getenv('MYCPP_SOUFFLE_DIR', None)
|
144 | if souffle_dir is None:
|
145 | tmp_dir = tempfile.TemporaryDirectory()
|
146 | souffle_dir = tmp_dir.name
|
147 | stack_roots = pass_state.ComputeMinimalStackRoots(
|
148 | cflow_graphs, souffle_dir=souffle_dir)
|
149 | else:
|
150 | timer.Section('mycpp: dumping control flow graph to _tmp/mycpp-facts')
|
151 |
|
152 | pass_state.DumpControlFlowGraphs(cflow_graphs)
|
153 |
|
154 | # [PASS]
|
155 | timer.Section('mycpp pass: CONST')
|
156 |
|
157 | global_strings = const_pass.GlobalStrings()
|
158 | p_const = const_pass.Collect(types, global_strings)
|
159 |
|
160 | for name, module in to_compile:
|
161 | p_const.visit_mypy_file(module)
|
162 | MaybeExitWithErrors(p_const)
|
163 |
|
164 | global_strings.ComputeStableVarNames()
|
165 | # Emit GLOBAL_STR(), never to header
|
166 | global_strings.WriteConstants(f)
|
167 |
|
168 | # [PASS] C++ declarations like:
|
169 | # class Foo { void method(); }; class Bar { void method(); };
|
170 | timer.Section('mycpp pass: DECL')
|
171 |
|
172 | for name, module in to_compile:
|
173 | p_decl = cppgen_pass.Decl(
|
174 | types,
|
175 | global_strings, # input
|
176 | yield_out_params,
|
177 | dunder_exit_special,
|
178 | virtual=virtual, # input
|
179 | all_member_vars=all_member_vars, # input
|
180 | )
|
181 | # prototypes may go to a header
|
182 | p_decl.SetOutputFile(header_f if name in to_header else f)
|
183 | p_decl.visit_mypy_file(module)
|
184 | MaybeExitWithErrors(p_decl)
|
185 |
|
186 | if 0:
|
187 | log('\tall_member_vars')
|
188 | from pprint import pformat
|
189 | print(pformat(all_member_vars), file=sys.stderr)
|
190 |
|
191 | timer.Section('mycpp pass: IMPL')
|
192 |
|
193 | # [PASS] the definitions / implementations:
|
194 | # void Foo:method() { ... }
|
195 | # void Bar:method() { ... }
|
196 | for name, module in to_compile:
|
197 | p_impl = cppgen_pass.Impl(
|
198 | types,
|
199 | global_strings,
|
200 | yield_out_params,
|
201 | dunder_exit_special,
|
202 | local_vars=all_local_vars,
|
203 | all_member_vars=all_member_vars,
|
204 | dot_exprs=dot_exprs[module.path],
|
205 | stack_roots=stack_roots,
|
206 | stack_roots_warn=stack_roots_warn,
|
207 | )
|
208 | p_impl.SetOutputFile(f) # doesn't go to header
|
209 | p_impl.visit_mypy_file(module)
|
210 | MaybeExitWithErrors(p_impl)
|
211 |
|
212 | timer.Section('mycpp DONE')
|
213 | return 0 # success
|
214 |
|
215 |
|
216 | def MaybeExitWithErrors(p: visitor.SimpleVisitor) -> None:
|
217 | # Check for errors we collected
|
218 | num_errors = len(p.errors_keep_going)
|
219 | if num_errors != 0:
|
220 | log('')
|
221 | log('%s: %d translation errors (after type checking)', sys.argv[0],
|
222 | num_errors)
|
223 |
|
224 | # A little hack to tell the test-invalid-examples harness how many errors we had
|
225 | sys.exit(min(num_errors, 255))
|