1# coding=utf-8 2# 3# Copyright (c) 2025 Huawei Device Co., Ltd. 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16"""Orchestrates the compilation process. 17 18- BackendRegistry: initializes all known backends 19- CompilerInvocation: constructs the invocation from cmdline 20 - Parses the general command line arguments 21 - Enables user specified backends 22 - Parses backend-specific arguments 23 - Sets backend options 24- CompilerInstance: runs the compilation 25 - CompilerInstance: scans and parses sources files 26 - Backends: post-process the IR 27 - Backends: validate the IR 28 - Backends: generate the output 29""" 30 31from dataclasses import dataclass, field 32from itertools import chain 33from pathlib import Path 34 35from taihe.driver.backend import Backend, BackendConfig 36from taihe.semantics.analysis import analyze_semantics 37from taihe.semantics.declarations import PackageGroup 38from taihe.utils.analyses import AnalysisManager 39from taihe.utils.diagnostics import ConsoleDiagnosticsManager, DiagnosticsManager 40from taihe.utils.exceptions import IgnoredFileReason, IgnoredFileWarn 41from taihe.utils.outputs import OutputConfig 42from taihe.utils.sources import SourceFile, SourceLocation, SourceManager 43 44 45def validate_source_file(source: SourceFile) -> IgnoredFileWarn | None: 46 # subdirectories are ignored 47 if not source.path.is_file(): 48 return IgnoredFileWarn( 49 IgnoredFileReason.IS_DIRECTORY, 50 loc=SourceLocation(source), 51 ) 52 # unexpected file extension 53 if source.path.suffix != ".taihe": 54 return IgnoredFileWarn( 55 IgnoredFileReason.EXTENSION_MISMATCH, 56 loc=SourceLocation(source), 57 ) 58 return None 59 60 61@dataclass 62class CompilerInvocation: 63 """Describes the options and intents for a compiler invocation. 64 65 CompilerInvocation stores the high-level intent in a structured way, such 66 as the input paths, the target for code generation. Generally speaking, it 67 can be considered as the parsed and verified version of a compiler's 68 command line flags. 69 70 CompilerInvocation does not manage the internal state. Use 71 `CompilerInstance` instead. 72 """ 73 74 src_files: list[Path] = field(default_factory=lambda: []) 75 src_dirs: list[Path] = field(default_factory=lambda: []) 76 output_config: OutputConfig = field(default_factory=OutputConfig) 77 backends: list[BackendConfig] = field(default_factory=lambda: []) 78 79 # TODO: refactor this to a more structured way 80 sts_keep_name: bool = False 81 arkts_module_prefix: str | None = None 82 arkts_path_prefix: str | None = None 83 84 85class CompilerInstance: 86 """Helper class for storing key objects. 87 88 CompilerInstance holds key intermediate objects across the compilation 89 process, such as the source manager and the diagnostics manager. 90 91 It also provides utility methods for driving the compilation process. 92 """ 93 94 invocation: CompilerInvocation 95 backends: list[Backend] 96 97 diagnostics_manager: DiagnosticsManager 98 99 source_manager: SourceManager 100 package_group: PackageGroup 101 102 analysis_manager: AnalysisManager 103 104 def __init__( 105 self, 106 invocation: CompilerInvocation, 107 *, 108 dm: type[DiagnosticsManager] = ConsoleDiagnosticsManager, 109 ): 110 self.invocation = invocation 111 self.diagnostics_manager = dm() 112 self.analysis_manager = AnalysisManager(invocation, self.diagnostics_manager) 113 self.source_manager = SourceManager() 114 self.package_group = PackageGroup() 115 self.output_manager = invocation.output_config.construct(self) 116 self.backends = [conf.construct(self) for conf in invocation.backends] 117 118 ########################## 119 # The compilation phases # 120 ########################## 121 122 def collect(self): 123 """Adds all `.taihe` files inside a directory. Subdirectories are ignored.""" 124 scanned = chain.from_iterable(p.iterdir() for p in self.invocation.src_dirs) 125 direct = self.invocation.src_files 126 127 for file in chain(direct, scanned): 128 source = SourceFile(file) 129 if warning := validate_source_file(source): 130 self.diagnostics_manager.emit(warning) 131 else: 132 self.source_manager.add_source(source) 133 134 def parse(self): 135 from taihe.parse.convert import AstConverter 136 137 for src in self.source_manager.sources: 138 with self.diagnostics_manager.capture_error(): 139 conv = AstConverter(src, self.diagnostics_manager) 140 pkg = conv.convert() 141 self.package_group.add(pkg) 142 143 for b in self.backends: 144 b.post_process() 145 146 def validate(self): 147 analyze_semantics(self.package_group, self.diagnostics_manager) 148 149 for b in self.backends: 150 b.validate() 151 152 def generate(self): 153 if self.diagnostics_manager.has_error: 154 return 155 156 for b in self.backends: 157 b.generate() 158 159 self.output_manager.post_generate() 160 161 def run(self): 162 self.collect() 163 self.parse() 164 self.validate() 165 self.generate() 166 return not self.diagnostics_manager.has_error