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 | stack_roots_warn: bool = False,
|
68 | minimize_stack_roots: bool = False) -> int:
|
69 |
|
70 | #_ = mtype
|
71 | #if 0:
|
72 | # log('m %r' % mtype)
|
73 | # log('m %r' % mtype.Callable)
|
74 |
|
75 | f.write("""\
|
76 | // BEGIN mycpp output
|
77 |
|
78 | #include "mycpp/runtime.h"
|
79 |
|
80 | """)
|
81 |
|
82 | # Which functions are C++ 'virtual'?
|
83 | virtual = pass_state.Virtual()
|
84 |
|
85 | all_member_vars: cppgen_pass.AllMemberVars = {}
|
86 | all_local_vars: cppgen_pass.AllLocalVars = {}
|
87 | dot_exprs: Dict[str, conversion_pass.DotExprs] = {}
|
88 | yield_out_params: Dict[FuncDef, Tuple[str, str]] = {}
|
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 | )
|
105 | # forward declarations may go to header
|
106 | p_convert.SetOutputFile(header_f if name in to_header else f)
|
107 | p_convert.visit_mypy_file(module)
|
108 | MaybeExitWithErrors(p_convert)
|
109 |
|
110 | dot_exprs[module.path] = module_dot_exprs
|
111 |
|
112 | # After seeing class and method names in the first pass, figure out which
|
113 | # ones are virtual. We use this info in the second pass.
|
114 | virtual.Calculate()
|
115 | if 0:
|
116 | log('virtuals %s', virtual.virtuals)
|
117 | log('has_vtable %s', virtual.has_vtable)
|
118 |
|
119 | # [PASS]
|
120 | timer.Section('mycpp pass: CONTROL FLOW')
|
121 |
|
122 | cflow_graphs = {} # fully qualified function name -> control flow graph
|
123 | for name, module in to_compile:
|
124 | p_cflow = control_flow_pass.Build(types, virtual, all_local_vars,
|
125 | dot_exprs[module.path])
|
126 | p_cflow.visit_mypy_file(module)
|
127 | cflow_graphs.update(p_cflow.cflow_graphs)
|
128 | MaybeExitWithErrors(p_cflow)
|
129 |
|
130 | # [PASS] Conditionally run Souffle
|
131 | stack_roots = None
|
132 | if minimize_stack_roots:
|
133 | timer.Section('mycpp pass: SOUFFLE data flow')
|
134 |
|
135 | # souffle_dir contains two subdirectories.
|
136 | # facts: TSV files for the souffle inputs generated by mycpp
|
137 | # outputs: TSV files for the solver's output relations
|
138 | souffle_dir = os.getenv('MYCPP_SOUFFLE_DIR', None)
|
139 | if souffle_dir is None:
|
140 | tmp_dir = tempfile.TemporaryDirectory()
|
141 | souffle_dir = tmp_dir.name
|
142 | stack_roots = pass_state.ComputeMinimalStackRoots(
|
143 | cflow_graphs, souffle_dir=souffle_dir)
|
144 | else:
|
145 | timer.Section('mycpp: dumping control flow graph to _tmp/mycpp-facts')
|
146 |
|
147 | pass_state.DumpControlFlowGraphs(cflow_graphs)
|
148 |
|
149 | # [PASS]
|
150 | timer.Section('mycpp pass: CONST')
|
151 |
|
152 | global_strings = const_pass.GlobalStrings()
|
153 | p_const = const_pass.Collect(types, global_strings)
|
154 |
|
155 | for name, module in to_compile:
|
156 | p_const.visit_mypy_file(module)
|
157 | MaybeExitWithErrors(p_const)
|
158 |
|
159 | global_strings.ComputeStableVarNames()
|
160 | # Emit GLOBAL_STR(), never to header
|
161 | global_strings.WriteConstants(f)
|
162 |
|
163 | # [PASS] C++ declarations like:
|
164 | # class Foo { void method(); }; class Bar { void method(); };
|
165 | timer.Section('mycpp pass: DECL')
|
166 |
|
167 | for name, module in to_compile:
|
168 | p_decl = cppgen_pass.Decl(
|
169 | types,
|
170 | global_strings, # input
|
171 | yield_out_params,
|
172 | virtual=virtual, # input
|
173 | all_member_vars=all_member_vars, # input
|
174 | )
|
175 | # prototypes may go to a header
|
176 | p_decl.SetOutputFile(header_f if name in to_header else f)
|
177 | p_decl.visit_mypy_file(module)
|
178 | MaybeExitWithErrors(p_decl)
|
179 |
|
180 | if 0:
|
181 | log('\tall_member_vars')
|
182 | from pprint import pformat
|
183 | print(pformat(all_member_vars), file=sys.stderr)
|
184 |
|
185 | timer.Section('mycpp pass: IMPL')
|
186 |
|
187 | # [PASS] the definitions / implementations:
|
188 | # void Foo:method() { ... }
|
189 | # void Bar:method() { ... }
|
190 | for name, module in to_compile:
|
191 | p_impl = cppgen_pass.Impl(
|
192 | types,
|
193 | global_strings,
|
194 | yield_out_params,
|
195 | local_vars=all_local_vars,
|
196 | all_member_vars=all_member_vars,
|
197 | dot_exprs=dot_exprs[module.path],
|
198 | stack_roots=stack_roots,
|
199 | stack_roots_warn=stack_roots_warn,
|
200 | )
|
201 | p_impl.SetOutputFile(f) # doesn't go to header
|
202 | p_impl.visit_mypy_file(module)
|
203 | MaybeExitWithErrors(p_impl)
|
204 |
|
205 | timer.Section('mycpp DONE')
|
206 | return 0 # success
|
207 |
|
208 |
|
209 | def MaybeExitWithErrors(p: visitor.SimpleVisitor) -> None:
|
210 | # Check for errors we collected
|
211 | num_errors = len(p.errors_keep_going)
|
212 | if num_errors != 0:
|
213 | log('')
|
214 | log('%s: %d translation errors (after type checking)', sys.argv[0],
|
215 | num_errors)
|
216 |
|
217 | # A little hack to tell the test-invalid-examples harness how many errors we had
|
218 | sys.exit(min(num_errors, 255))
|