| 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))
|