diff options
| author | _ov4 <ov4@ov4.org> | 2026-10-17 09:13:32 +0000 |
|---|---|---|
| committer | _ov4 <ov4@ov4.org> | 2026-10-17 09:13:32 +0000 |
| commit | e1f41ed9fafaf7eb4f2da6878a3fabcf2f7c021e (patch) | |
| tree | c4cd67b10e8513bb6dfad58e3c3b55033745ed12 | |
| parent | 59a2bb6acb975d820ab29be63a6f909d6e6ef40f (diff) | |
feat(driver): compile multiple source files with INCLUDE visibility
Reject builds without an entry program and duplicate cross-file definitions.
Preserve source order for each unit's own constants and import visible exports.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| -rw-r--r-- | include/cps/CodeGen.h | 13 | ||||
| -rw-r--r-- | include/cps/FunctionGen.h | 5 | ||||
| -rw-r--r-- | include/cps/SourceUnit.h | 13 | ||||
| -rw-r--r-- | include/cps/TypeSystem.h | 3 | ||||
| -rw-r--r-- | lib/CodeGen/CodeGen.cc | 143 | ||||
| -rw-r--r-- | lib/CodeGen/Designator.cc | 8 | ||||
| -rw-r--r-- | lib/CodeGen/FunctionGen.cc | 4 | ||||
| -rw-r--r-- | lib/CodeGen/StatementGen.cc | 5 | ||||
| -rw-r--r-- | lib/CodeGen/TypeSystem.cc | 5 | ||||
| -rw-r--r-- | tools/driver/main.cc | 103 |
10 files changed, 245 insertions, 57 deletions
diff --git a/include/cps/CodeGen.h b/include/cps/CodeGen.h index 9a0b2ef..6e0fab1 100644 --- a/include/cps/CodeGen.h +++ b/include/cps/CodeGen.h @@ -5,6 +5,7 @@ #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" #include "cps/AST.h" +#include "cps/SourceUnit.h" #include "cps/RuntimeCheck.h" #include "cps/FunctionGen.h" #include "cps/TypeSystem.h" @@ -32,6 +33,14 @@ class CodeGen { std::unique_ptr<llvm::Module> TheModule; std::unique_ptr<llvm::IRBuilder<>> Builder; int DiagnosticLine = 0; + const std::vector<SourceUnit> *Units = nullptr; + size_t CurrentUnit = 0; + size_t MainUnit = 0; + IdentifierMap<size_t> TypeOwners; + std::vector<IdentifierMap<SymbolInfo>> Exports; + bool isVisible(size_t Owner) const; + void selectUnit(size_t Unit); + void importSymbols(); IdentifierMap<SymbolInfo> Symbols; IdentifierMap<IdentifierMap<FunctionDefAST*>> ClassMethods; std::string CurrentClass; @@ -123,7 +132,7 @@ class CodeGen { void emitCInput(llvm::Value *Addr, const TypeInfo *Info); // TYPE-system registration (Designator.cc) and operators (TypeCheckGen.cc). - void runTypePrePass(const std::vector<std::unique_ptr<StmtAST>> &Statements); + void runTypePrePass(const std::vector<std::unique_ptr<StmtAST>> &Statements, int Phase); llvm::Value *emitUserKindBinaryOp(int Op, llvm::Value *L, const TypeInfo *LT, llvm::Value *R, const TypeInfo *RT, int Line); @@ -161,6 +170,8 @@ public: CodeGen(); ~CodeGen(); void compile(const std::vector<std::unique_ptr<StmtAST>> &Statements); + void compile(const std::vector<SourceUnit> &Sources); + size_t getMainUnit() const { return MainUnit; } void print(llvm::raw_ostream &Output); const llvm::Module &getModule() const { return *TheModule; } bool hadError() const { return HadError; } diff --git a/include/cps/FunctionGen.h b/include/cps/FunctionGen.h index 777ab7d..eb4a839 100644 --- a/include/cps/FunctionGen.h +++ b/include/cps/FunctionGen.h @@ -38,6 +38,7 @@ struct FuncSig { std::string Name; std::string ReturnTypeName; std::vector<ParamSig> Params; + size_t OwnerUnit = 0; }; class FunctionGen { @@ -63,6 +64,8 @@ private: void reportError(const char *Fmt, ...); IdentifierMap<FuncSig> Signatures; + size_t CurrentUnit = 0; + std::function<bool(size_t)> Visible; ArrayParamBinder BindArrayParam; std::function<void(llvm::Value *, const std::string &, int)> RegisterCopy; @@ -86,6 +89,8 @@ public: RegisterCopy = std::move(Registrar); } + void setCurrentUnit(size_t Unit) { CurrentUnit = Unit; } + void setVisibility(std::function<bool(size_t)> Predicate) { Visible = std::move(Predicate); } const FuncSig *getSignature(const std::string &Name) const; llvm::Function *emitPrototype(PrototypeAST *Proto); diff --git a/include/cps/SourceUnit.h b/include/cps/SourceUnit.h new file mode 100644 index 0000000..db9ad30 --- /dev/null +++ b/include/cps/SourceUnit.h @@ -0,0 +1,13 @@ +#pragma once +#include "cps/AST.h" +#include <string> +#include <vector> + +namespace cps { +// The caller owns the parsed statements for the duration of compilation. +struct SourceUnit { + std::string Path; + const std::vector<std::unique_ptr<StmtAST>> &Statements; + std::vector<bool> Visible; +}; +} diff --git a/include/cps/TypeSystem.h b/include/cps/TypeSystem.h index d358794..d699067 100644 --- a/include/cps/TypeSystem.h +++ b/include/cps/TypeSystem.h @@ -5,6 +5,7 @@ #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Type.h" #include <cstdint> +#include <functional> #include <map> #include <string> #include <utility> @@ -118,6 +119,7 @@ class TypeSystem { IdentifierMap<TypeInfo> Types; IdentifierMap<EnumConstant> EnumConstants; + std::function<bool(const std::string &)> Visible; void registerBuiltins(); const TypeInfo *registerType(const std::string &TypeName, llvm::Type *LLVMType, @@ -126,6 +128,7 @@ class TypeSystem { public: explicit TypeSystem(llvm::LLVMContext &Ctx); + void setVisibility(std::function<bool(const std::string &)> Predicate) { Visible = std::move(Predicate); } const TypeInfo *resolve(const std::string &TypeName) const; // Two-pass user-type registration (pass A then pass B, both in source diff --git a/lib/CodeGen/CodeGen.cc b/lib/CodeGen/CodeGen.cc index d3459ef..e7116dd 100644 --- a/lib/CodeGen/CodeGen.cc +++ b/lib/CodeGen/CodeGen.cc @@ -5,6 +5,7 @@ #include "cps/MemoryHandler.h" #include "cps/ExceptionHandler.h" #include "Builtins.h" +#include "cps/TypeAST.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/Verifier.h" #include <cstdarg> @@ -152,7 +153,84 @@ const TypeInfo *CodeGen::resolveType(const std::string &TypeName) const { return Types->resolve(TypeName); } +bool CodeGen::isVisible(size_t Owner) const { + return !Units || (*Units)[CurrentUnit].Visible[Owner]; +} + +void CodeGen::selectUnit(size_t Unit) { + CurrentUnit = Unit; + DiagnosticSource = Units->size() > 1 ? (*Units)[Unit].Path : ""; + FuncGen->setCurrentUnit(Unit); +} + +void CodeGen::importSymbols() { + Symbols.clear(); + for (size_t I = 0; I < Exports.size(); ++I) { + if (I != CurrentUnit && isVisible(I)) + Symbols.insert(Exports[I].begin(), Exports[I].end()); + } +} + void CodeGen::compile(const std::vector<std::unique_ptr<StmtAST>> &Statements) { + compile(std::vector<SourceUnit>{{"", Statements, {true}}}); +} + +void CodeGen::compile(const std::vector<SourceUnit> &Sources) { + SaveAndRestore<const std::vector<SourceUnit> *> SourceContext(Units, &Sources); + SaveAndRestore<std::string> SourceName(DiagnosticSource); + if (Sources.empty()) { reportError("no main program"); return; } + Exports.resize(Sources.size()); + Types->setVisibility([this](const std::string &Name) { + auto It = TypeOwners.find(Name); + return It == TypeOwners.end() || isVisible(It->second); + }); + FuncGen->setVisibility([this](size_t Owner) { return isVisible(Owner); }); + + IdentifierMap<size_t> Definitions; + std::optional<size_t> CodeUnit; + auto Reserve = [&](const std::string &Name) { + auto [It, Added] = Definitions.emplace(Name, CurrentUnit); + if (!Added && It->second != CurrentUnit) + reportError("Duplicate definition %s in %s and %s", Name.c_str(), + Sources[It->second].Path.c_str(), Sources[CurrentUnit].Path.c_str()); + }; + for (size_t I = 0; I < Sources.size(); ++I) { + selectUnit(I); + for (const auto &S : Sources[I].Statements) { + SaveAndRestore<int> Location(DiagnosticLine, S->getLine()); + std::string TypeName; + if (auto *F = dynamic_cast<FunctionDefAST*>(S.get())) { + Reserve(F->getProto()->getName()); + } else if (auto *E = dynamic_cast<EnumTypeDeclAST*>(S.get())) { + TypeName = E->getName(); + for (const auto &V : E->getValueNames()) Reserve(V); + } else if (auto *R = dynamic_cast<RecordTypeDeclAST*>(S.get())) { + TypeName = R->getName(); + } else if (auto *P = dynamic_cast<PointerTypeDeclAST*>(S.get())) { + TypeName = P->getName(); + } else if (auto *C = dynamic_cast<CTypeDeclAST*>(S.get())) { + TypeName = C->getName(); + } else if (auto *C = dynamic_cast<ConstantStmtAST*>(S.get())) { + Reserve(C->getName()); + } else if (auto *G = dynamic_cast<GlobalBlockAST*>(S.get())) { + for (const auto &Body : G->getBody()) + if (auto *D = dynamic_cast<DeclareStmtAST*>(Body.get())) Reserve(D->getName()); + } else if (!dynamic_cast<IncludeStmtAST*>(S.get())) { + if (CodeUnit && *CodeUnit != I) { + reportError("Top-level code in both %s and %s", + Sources[*CodeUnit].Path.c_str(), Sources[I].Path.c_str()); + return; + } + CodeUnit = I; + } + if (!TypeName.empty()) { + Reserve(TypeName); + TypeOwners.emplace(TypeName, I); + } + } + } + if (HadError) return; + MainUnit = CodeUnit.value_or(0); FunctionType *FT = FunctionType::get(Type::getInt32Ty(*TheContext), false); Function *F = Function::Create(FT, Function::ExternalLinkage, "main", TheModule.get()); BasicBlock *BB = BasicBlock::Create(*TheContext, "entry", F); @@ -162,28 +240,61 @@ void CodeGen::compile(const std::vector<std::unique_ptr<StmtAST>> &Statements) { BasicBlock *UnhandledBB = BasicBlock::Create(*TheContext, "unhandled_exception", F); Exceptions->pushHandler(UnhandledBB); - // User types first: prototypes may name them in parameters/returns. - runTypePrePass(Statements); + // Reserve names across every unit before resolving any type payload. + for (size_t I = 0; I < Sources.size(); ++I) { + selectUnit(I); + runTypePrePass(Sources[I].Statements, 0); + } + // Complete included units first. The visited set makes include cycles harmless. + std::vector<bool> Visited(Sources.size(), false); + std::function<void(size_t)> DefineTypes = [&](size_t I) { + if (Visited[I]) return; + Visited[I] = true; + for (size_t J = 0; J < Sources.size(); ++J) + if (J != I && Sources[I].Visible[J]) DefineTypes(J); + selectUnit(I); + runTypePrePass(Sources[I].Statements, 1); + runTypePrePass(Sources[I].Statements, 2); + }; + for (size_t I = 0; I < Sources.size(); ++I) DefineTypes(I); - // Pre-register every top-level FUNCTION/PROCEDURE prototype so calls may - // precede definitions. - for (const auto &Stmt : Statements) { - SaveAndRestore<int> Location(DiagnosticLine, Stmt->getLine()); - if (auto *FuncDef = dynamic_cast<FunctionDefAST*>(Stmt.get())) { - FuncGen->emitPrototype(FuncDef->getProto()); + for (size_t I = 0; I < Sources.size(); ++I) { + selectUnit(I); + for (const auto &Stmt : Sources[I].Statements) { + SaveAndRestore<int> Location(DiagnosticLine, Stmt->getLine()); + if (auto *FuncDef = dynamic_cast<FunctionDefAST*>(Stmt.get())) + FuncGen->emitPrototype(FuncDef->getProto()); + if (auto *Class = dynamic_cast<ClassTypeDeclAST*>(Stmt.get())) { + for (const auto &Method : Class->getMethods()) { + SaveAndRestore<int> MethodLocation(DiagnosticLine, Method->getLine()); + FuncGen->emitPrototype(Method->getProto()); + } + } } } - for (const auto &[Class, Methods] : ClassMethods) { - for (const auto &[Name, Method] : Methods) { - SaveAndRestore<int> Location(DiagnosticLine, Method->getLine()); - FuncGen->emitPrototype(Method->getProto()); + // Constants are literal exports. Own constants are still installed only + // when their declaration is encountered during body emission. + for (size_t I = 0; I < Sources.size(); ++I) { + selectUnit(I); + Symbols.clear(); + for (const auto &Stmt : Sources[I].Statements) { + if (auto *Constant = dynamic_cast<ConstantStmtAST*>(Stmt.get())) { + SaveAndRestore<int> Location(DiagnosticLine, Stmt->getLine()); + emitConstantStmt(Constant); + } } + Exports[I] = Symbols; } - - for (const auto &Stmt : Statements) { - SaveAndRestore<int> Location(DiagnosticLine, Stmt->getLine()); - emitStmt(Stmt.get()); + for (size_t I = 0; I < Sources.size(); ++I) { + selectUnit(I); + importSymbols(); + for (const auto &Stmt : Sources[I].Statements) emitStmt(Stmt.get()); + } + selectUnit(MainUnit); + if (!CodeUnit && !HadError) { + SaveAndRestore<int> Location(DiagnosticLine, Sources.size() > 1 ? 1 : 0); + reportError("no main program"); } if (!Builder->GetInsertBlock()->hasTerminator()) { diff --git a/lib/CodeGen/Designator.cc b/lib/CodeGen/Designator.cc index 5f54a87..b31492b 100644 --- a/lib/CodeGen/Designator.cc +++ b/lib/CodeGen/Designator.cc @@ -11,10 +11,10 @@ using namespace cps; // (TYPE NodePtr = ^Node before TYPE Node — required for linked structures); // by-value record fields still require their record type to be complete, // which pass B's source order enforces. -void CodeGen::runTypePrePass(const std::vector<std::unique_ptr<StmtAST>> &Statements) { +void CodeGen::runTypePrePass(const std::vector<std::unique_ptr<StmtAST>> &Statements, int Phase) { std::string Err; - for (const auto &S : Statements) { + if (Phase == 0) for (const auto &S : Statements) { SaveAndRestore<int> Location(DiagnosticLine, S->getLine()); bool Ok = true; if (auto *C = dynamic_cast<CTypeDeclAST*>(S.get())) @@ -29,7 +29,7 @@ void CodeGen::runTypePrePass(const std::vector<std::unique_ptr<StmtAST>> &Statem if (!Ok) reportError("%s", Err.c_str()); } - for (const auto &S : Statements) { + if (Phase == 1) for (const auto &S : Statements) { SaveAndRestore<int> Location(DiagnosticLine, S->getLine()); bool Ok = true; if (auto *E = dynamic_cast<EnumTypeDeclAST*>(S.get())) { @@ -42,7 +42,7 @@ void CodeGen::runTypePrePass(const std::vector<std::unique_ptr<StmtAST>> &Statem if (!Ok) reportError("%s", Err.c_str()); } - for (const auto &S : Statements) { + if (Phase == 2) for (const auto &S : Statements) { auto *Class = dynamic_cast<ClassTypeDeclAST*>(S.get()); if (!Class) continue; auto &Methods = ClassMethods[Class->getName()]; diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc index b66b55a..cc88a78 100644 --- a/lib/CodeGen/FunctionGen.cc +++ b/lib/CodeGen/FunctionGen.cc @@ -106,7 +106,8 @@ void FunctionGen::createArgumentAllocas(Function *F, const std::vector<ParamDecl const FuncSig *FunctionGen::getSignature(const std::string &Name) const { auto It = Signatures.find(Name); - return It == Signatures.end() ? nullptr : &It->second; + return It == Signatures.end() || (Visible && !Visible(It->second.OwnerUnit)) + ? nullptr : &It->second; } Function *FunctionGen::emitPrototype(PrototypeAST *Proto) { @@ -134,6 +135,7 @@ Function *FunctionGen::emitPrototype(PrototypeAST *Proto) { } FuncSig Sig; + Sig.OwnerUnit = CurrentUnit; Sig.Name = Proto->getLinkName(); Sig.ReturnTypeName = Proto->getReturnType(); diff --git a/lib/CodeGen/StatementGen.cc b/lib/CodeGen/StatementGen.cc index 529f7f0..5edb303 100644 --- a/lib/CodeGen/StatementGen.cc +++ b/lib/CodeGen/StatementGen.cc @@ -13,8 +13,9 @@ void CodeGen::emitStmt(StmtAST *Stmt) { SaveAndRestore<int> Location(DiagnosticLine, Stmt && Stmt->getLine() ? Stmt->getLine() : DiagnosticLine); if (!Stmt) return; - if (dynamic_cast<IncludeStmtAST*>(Stmt) || dynamic_cast<GlobalBlockAST*>(Stmt)) { - reportError("INCLUDE and GLOBAL are not supported yet"); + if (dynamic_cast<IncludeStmtAST*>(Stmt)) return; + if (dynamic_cast<GlobalBlockAST*>(Stmt)) { + reportError("GLOBAL is not supported yet"); return; } diff --git a/lib/CodeGen/TypeSystem.cc b/lib/CodeGen/TypeSystem.cc index 27814c2..34b8d2d 100644 --- a/lib/CodeGen/TypeSystem.cc +++ b/lib/CodeGen/TypeSystem.cc @@ -111,7 +111,7 @@ bool TypeSystem::sameType(const std::string &Left, const std::string &Right) con const TypeInfo *TypeSystem::resolve(const std::string &TypeName) const { auto It = Types.find(TypeName); - if (It == Types.end()) { + if (It == Types.end() || (Visible && !Visible(TypeName))) { return nullptr; } return &It->second; @@ -271,7 +271,8 @@ bool TypeSystem::definePointer(const std::string &Name, const EnumConstant *TypeSystem::lookupEnumConstant(const std::string &Name) const { auto It = EnumConstants.find(Name); - return It == EnumConstants.end() ? nullptr : &It->second; + return It == EnumConstants.end() || (Visible && !Visible(It->second.TypeName)) + ? nullptr : &It->second; } Type *TypeSystem::getLLVMType(const std::string &TypeName) const { diff --git a/tools/driver/main.cc b/tools/driver/main.cc index a81fb30..0b3033f 100644 --- a/tools/driver/main.cc +++ b/tools/driver/main.cc @@ -1,4 +1,5 @@ #include "cps/Lexer.h" +#include "cps/Diagnostics.h" #include "cps/Parser.h" #include "cps/CodeGen.h" #include "NativeCompiler.h" @@ -85,7 +86,7 @@ int main(int argc, char **argv) { } } - std::string InputPath; + std::vector<std::string> InputPaths; std::string OutputPath; bool Run = false; bool EmitIR = false; @@ -108,15 +109,12 @@ int main(int argc, char **argv) { } else if (Arg.empty() || Arg[0] == '-') { llvm::errs() << "cpsc: error: unknown argument '" << Arg << "'\n"; return compilationTerminated(); - } else if (!InputPath.empty()) { - llvm::errs() << "cpsc: error: only one source file is supported\n"; - return compilationTerminated(); } else { - InputPath = Arg; + InputPaths.push_back(Arg); } } - if (InputPath.empty()) { + if (InputPaths.empty()) { llvm::errs() << "cpsc: fatal error: no input files\n"; return compilationTerminated(); } @@ -138,37 +136,80 @@ int main(int argc, char **argv) { #endif } const bool Save = !Run || EmitIR || !OutputPath.empty(); - if (OutputPath.empty() && Save) { - llvm::SmallString<256> DefaultPath(InputPath); - llvm::sys::path::replace_extension(DefaultPath, EmitIR ? ".ll" : ".out"); - OutputPath = std::string(DefaultPath); - } - - std::ifstream Input(InputPath, std::ios::binary); - if (!Input) { - llvm::errs() << "cpsc: fatal error: cannot open source file '" << InputPath << "'\n"; - return compilationTerminated(); - } - if (Save && llvm::sys::fs::equivalent(InputPath, OutputPath)) { - llvm::errs() << "cpsc: error: output file must not be the source file\n"; - return compilationTerminated(); + std::vector<std::vector<std::unique_ptr<cps::StmtAST>>> Programs(InputPaths.size()); + std::vector<cps::SourceUnit> Units; + for (size_t I = 0; I < InputPaths.size(); ++I) { + const auto &InputPath = InputPaths[I]; + for (size_t J = 0; J < I; ++J) { + if (llvm::sys::fs::equivalent(InputPath, InputPaths[J])) { + llvm::errs() << "cpsc: error: duplicate input file '" << InputPath << "'\n"; + return compilationTerminated(); + } + } + std::ifstream Input(InputPath, std::ios::binary); + if (!Input) { + llvm::errs() << "cpsc: fatal error: cannot open source file '" << InputPath << "'\n"; + return compilationTerminated(); + } + cps::DiagnosticSource = InputPaths.size() > 1 ? InputPath : ""; + cps::Lexer Lex(Input); + cps::Parser Parser(Lex); + Programs[I] = Parser.Parse(); + if (Input.bad()) { + llvm::errs() << "cpsc: fatal error: cannot read source file '" << InputPath << "'\n"; + return compilationTerminated(); + } + if (Lex.hadError() || Parser.hadError()) return compilationTerminated(); + Units.push_back({InputPath, Programs[I], std::vector<bool>(InputPaths.size(), false)}); + Units.back().Visible[I] = true; } - - cps::Lexer Lex(Input); - - cps::Parser Parser(Lex); - auto Statements = Parser.Parse(); - if (Input.bad()) { - llvm::errs() << "cpsc: fatal error: cannot read source file '" << InputPath << "'\n"; - return compilationTerminated(); + for (size_t I = 0; I < Units.size(); ++I) { + cps::DiagnosticSource = Units.size() > 1 ? Units[I].Path : ""; + for (const auto &Stmt : Units[I].Statements) { + auto *Include = dynamic_cast<cps::IncludeStmtAST*>(Stmt.get()); + if (!Include) continue; + llvm::SmallString<256> Path(Include->getPath()); + if (!llvm::sys::path::is_absolute(Path)) { + Path = llvm::sys::path::parent_path(Units[I].Path); + llvm::sys::path::append(Path, Include->getPath()); + } + bool Found = false; + for (size_t J = 0; J < Units.size(); ++J) { + if (llvm::sys::fs::equivalent(Path, Units[J].Path)) { + Units[I].Visible[J] = true; + Found = true; + } + } + if (!Found) { + cps::sourceDiagnostic(Stmt->getLine(), + "error: cannot INCLUDE '%s': not given on the command line at line %d\n", + Include->getPath().c_str(), Stmt->getLine()); + return compilationTerminated(); + } + } } - if (Lex.hadError() || Parser.hadError()) return compilationTerminated(); + for (size_t K = 0; K < Units.size(); ++K) + for (size_t I = 0; I < Units.size(); ++I) + for (size_t J = 0; J < Units.size(); ++J) + Units[I].Visible[J] = Units[I].Visible[J] || + (Units[I].Visible[K] && Units[K].Visible[J]); cps::CodeGen CG; - CG.compile(Statements); - + CG.compile(Units); if (CG.hadError()) return compilationTerminated(); + if (OutputPath.empty() && Save) { + llvm::SmallString<256> DefaultPath(InputPaths[CG.getMainUnit()]); + llvm::sys::path::replace_extension(DefaultPath, EmitIR ? ".ll" : ".out"); + OutputPath = std::string(DefaultPath); + } + for (const auto &InputPath : InputPaths) { + if (Save && llvm::sys::fs::equivalent(InputPath, OutputPath)) { + llvm::errs() << "cpsc: error: output file must not be the source file\n"; + return compilationTerminated(); + } + } + if (Save && !EmitIR) { if (!compileNative(CG, OutputPath, argv[0])) return compilationTerminated(); if (!Run) return 0; |
