OILS / mycpp / translate.py View on Github | oils.pub

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