// -*- C++ -*- (c) 2010 Petr Rockai // (c) 2013, 2014 Vladimír Štill #include #include #include #include #include #include #include #include #include #include #include #include #ifndef DIVINE_COMPILE_H #define DIVINE_COMPILE_H using namespace brick; using namespace brick::commandline; namespace divine { std::string ltl_to_c( int id, std::string ltl ); static inline std::string concat( commandline::VectorOption< String > *opt ) { assert( opt != nullptr ); if ( opt->values().empty() ) return ""; std::stringstream ss; for ( auto it = opt->values().begin(); ; ) { ss << *it; if ( ++it != opt->values().end() ) ss << " "; else break; } return ss.str(); } struct Compile { commandline::Engine *cmd_compile; commandline::StandardParserWithMandatoryCommand &opts; BoolOption *o_cesmi, *o_llvm, *o_keep, *o_libs_only, *o_dont_link, *o_no_modeline; StringOption *o_out, *o_cmd_clang, *o_precompiled, *o_parallel, *o_snapshot, *o_stdin; VectorOption< String > *o_definitions, *o_cflags; int parallelBuildJobs; struct FilePath { // filepath = joinpath(abspath, basename) std::string basename; std::string abspath; }; void die_help( std::string bla ) { opts.outputHelp( std::cerr ); die( bla ); } static void cleanup( FilePath &cleanup_tmpdir ) { chdir( cleanup_tmpdir.abspath.c_str() ); brick::fs::rmtree( cleanup_tmpdir.basename ); } void run( std::string command ) { std::cerr << "+ " << command << std::endl; int status = system( command.c_str() ); if ( status != 0 ) die( brick::string::fmtf( "Error running an external command, exit code %d.", status ) ); } void runCompiler( std::string comp, std::string in, std::string out, std::string flags = "" ) { std::stringstream cmd; cmd << comp << " " << flags << " -o " << out << " " << in; run( cmd.str() ); } void gplusplus( std::string in, std::string out, std::string flags = "" ) { runCompiler ( _cmd_cxx, in, out, "-g -O2 -fPIC -shared " + flags); } std::string clang() { if ( o_cmd_clang->boolValue() ) return o_cmd_clang->stringValue(); return ::_cmd_clang; } void compileDve( std::string in, std::vector< std::string > definitions ) { #if GEN_DVE dve::compiler::DveCompiler compiler; compiler.read( in.c_str(), definitions ); compiler.analyse(); std::string outfile = brick::fs::splitFileName( in ).second + ".cpp"; std::ofstream out( outfile.c_str() ); compiler.setOutput( out ); compiler.print_generator(); gplusplus( outfile, brick::fs::splitFileName( in ).second + generator::cesmi_ext ); #else die( "FATAL: The DVE compiler requires DVE backend." ); (void)(in); (void)(definitions); #endif } void compileMurphi( std::string in ); void propswitch( std::ostream &o, int c, std::string fun, std::string args ) { o << " switch ( property ) {" << std::endl; for ( int i = 0; i < c; ++i ) o << " case " << i << ": return " << fun << "_" << i << args << ";" << std::endl; o << " default: abort();" << std::endl; o << " };" << std::endl; } void compileCESMI( std::string in, std::string cflags ) { brick::fs::TempDir tmpdir( "_divine-compile.XXXXXX", brick::fs::AutoDelete( !o_keep->boolValue() ) ); brick::fs::ChangeCwd rootDir( tmpdir.path ); std::string in_filename = brick::fs::splitFileName( in ).second; std::string in_basename( in_filename, 0, in_filename.rfind( '.' ) ); std::string extras, ltlincludes; int ltlcount = 0; while ( opts.hasNext() ) { std::string extra = brick::fs::joinPath( "..", opts.next() ); if ( brick::string::endsWith( extra, ".ltl" ) ) { std::string ltlpath = brick::fs::splitFileName( extra ).second + ".h"; std::string code = "#include \n"; parse_ltl( brick::fs::readFile( extra ), [&]( std::string formula ) { code += ltl_to_c( ltlcount++, formula ); }, [&]( std::string ) {} ); brick::fs::writeFile( ltlpath, code ); ltlincludes += "#include <" + brick::fs::splitFileName( extra ).second + ".h>\n"; } else extras += " " + extra; } brick::fs::writeFile( "cesmi.h", cesmi_usr_cesmi_h_str ); brick::fs::writeFile( "cesmi.cpp", cesmi_usr_cesmi_cpp_str ); extras += " cesmi.cpp"; std::string impl = "cesmi-ltl", aggr = "ltl-aggregate.cpp"; brick::fs::writeFile( impl + ".cpp", cesmi_usr_ltl_cpp_str ); brick::fs::writeFile( impl + ".h", cesmi_usr_ltl_h_str ); extras += " " + impl + ".cpp"; std::ofstream aggr_s( aggr.c_str() ); aggr_s << "#include " << std::endl; aggr_s << "#include " << std::endl; aggr_s << ltlincludes << std::endl; aggr_s << "extern \"C\" int buchi_accepting( int property, int id ) {\n"; propswitch( aggr_s, ltlcount, "buchi_accepting", "( id )" ); aggr_s << "}" << std::endl; aggr_s << "extern \"C\" int buchi_next( int property, int from, int transition, cesmi_setup *setup, cesmi_node n ) {\n"; propswitch( aggr_s, ltlcount, "buchi_next", "( from, transition, setup, n )" ); aggr_s << "}" << std::endl; aggr_s << "extern \"C\" int buchi_initial( int property ) {\n"; propswitch( aggr_s, ltlcount, "buchi_initial", "" ); aggr_s << "}" << std::endl; aggr_s << "int buchi_property_count = " << ltlcount << ";" << std::endl; aggr_s << "extern \"C\" char *buchi_formula( int property ) {\n"; propswitch( aggr_s, ltlcount, "buchi_formula", "" ); aggr_s << "}" << std::endl; extras += " " + aggr; aggr_s.close(); std::string flags = "-Wall -shared -g -O2 -fPIC " + cflags; run( std::string( _cmd_cc ) + " " + flags + " -I." + " -o ../" + in_basename + generator::cesmi_ext + " " + brick::fs::joinPath( "..", in ) + extras ); } void parseModelines( const std::string &file, std::vector< std::string > &modelineOpts ) { std::string line; std::ifstream fs{ file }; enum { None, Block, BlockCf } state = None; static const std::string lineKey = "// divine:"; static const std::string blockKey = "/* divine:"; static const std::string lineCfKey = "// divine-cflags:"; static const std::string blockCfKey = "/* divine-cflags:"; brick::string::Splitter splitter( "[\t ][ \t]*", REG_EXTENDED ); auto match = [ &line ]( const std::string &key ) { return line.substr( 0, key.size() ) == key; }; auto addmods = [&]( std::string m ) { for ( auto it = splitter.begin( m ); it != splitter.end(); ++it ) if ( !it->empty() && *it != "*/" ) modelineOpts.emplace_back( *it ); }; auto addcfmods = [&]( std::string m ) { for ( auto it = splitter.begin( m ); it != splitter.end(); ++it ) if ( !it->empty() && *it != "*/" ) modelineOpts.emplace_back( "--cflags=" + *it ); }; for ( int i = 0; (state != None || i < 5) && std::getline( fs, line ); ++i ) { if ( state == None ) { if ( match( lineKey ) ) addmods( line.substr( lineKey.size() ) ); else if ( match( lineCfKey ) ) addcfmods( line.substr( lineCfKey.size() ) ); else if ( match( blockKey ) ) { if ( line.find( "*/" ) == std::string::npos ) state = Block; addmods( line.substr( blockKey.size() ) ); } else if ( match( blockCfKey ) ) { if ( line.find( "*/" ) == std::string::npos ) state = BlockCf; addcfmods( line.substr( blockCfKey.size() ) ); } } else if ( state == Block ) { addmods( line ); if ( line.find( "*/" ) != std::string::npos ) state = None; } else if ( state == BlockCf ) { addcfmods( line ); if ( line.find( "*/" ) != std::string::npos ) state = None; } } } static std::string defaultPrecompiled(); void compileLLVM( std::string first_file, std::string out ) { #if GEN_LLVM || GEN_LLVM_CSDR || GEN_LLVM_PROB || GEN_LLVM_PTST std::vector< std::string > files; if ( !first_file.empty() ) files.push_back( first_file ); while ( opts.hasNext() ) { auto f = opts.next(); if ( !f.empty() ) files.push_back( f ); } if ( !o_no_modeline->boolValue() ) { std::vector< std::string > modelineOpts; for ( const auto &f : files ) parseModelines( f, modelineOpts ); std::cout << "INFO: modelines: " << brick::string::fmt( modelineOpts ) << std::endl; auto e = cmd_compile->parseExtraOptions( modelineOpts ); if ( e.size() ) std::cout << "WARNING: unknown modeline options: " << brick::string::fmt( e ) << std::endl; } std::string cflags = concat( o_cflags ); struct LLVMEnv : compile::Env { LLVMEnv( Compile *comp ) : compile( comp ) { } void compileFile( std::string file, std::string flags ) override { compile->run( compile->clang() + " " + file + " " + flags ); } Compile *compile; } env( this ); env.usePrecompiled = o_precompiled->stringValue(); if ( env.usePrecompiled.empty() ) env.usePrecompiled = defaultPrecompiled(); if ( env.usePrecompiled == "none" ) env.usePrecompiled = ""; env.librariesOnly = o_libs_only->boolValue(); env.dontLink = o_dont_link->boolValue(); env.useThreads = parallelBuildJobs; env.keepBuildDir = o_keep->boolValue(); env.snapshot = o_snapshot->stringValue(); env.stdin = o_stdin->stringValue(); env.input = files; env.output = out; env.flags = cflags; compile::CompileLLVM llvm( env ); llvm.compile(); #else die( "LLVM is disabled" ); (void)(first_file); (void)(out); #endif } int parseParallel( std::string str ) { std::stringstream ss( str ); int j; ss >> j; if ( j <= 0 ) die( "Invalid value for --job/-j: '" + str + "'" ); return j; } void main() { std::string input = opts.next(); parallelBuildJobs = o_parallel->boolValue() ? parseParallel( o_parallel->stringValue() ) : 1; if ( !o_libs_only->boolValue() && !brick::fs::access( input, R_OK ) ) die( "FATAL: cannot open input file " + input + " for reading" ); if ( brick::string::endsWith( input, ".dve" ) ) compileDve( input, o_definitions->values() ); else if ( brick::string::endsWith( input, ".m" ) ) compileMurphi( input ); else if ( o_cesmi->boolValue() ) compileCESMI( input, concat( o_cflags ) ); else if ( o_llvm->boolValue() ) compileLLVM( input, o_out->stringValue() ); else { std::cerr << "Hint: Assuming --llvm. Use --cesmi to build CESMI modules." << std::endl; compileLLVM( input, o_out->stringValue() ); } } Compile( commandline::StandardParserWithMandatoryCommand &_opts ) : opts( _opts) { cmd_compile = _opts.addEngine( "compile", "", "Compile input models into DiVinE binary interface."); o_cesmi = cmd_compile->add< BoolOption >( "cesmi", 'c', "cesmi", "", "process input .cpp or .cc files as of CESMI type" ); o_llvm = cmd_compile->add< BoolOption >( "llvm", 'l', "llvm", "", "compile input C/C++ program into LLVM bitecode"); o_keep = cmd_compile->add< BoolOption >( "keep-build-directory", '\0', "keep-build-directory", "", "do not erase intermediate files after a compile" ); o_cflags = cmd_compile->add< VectorOption< String > >( "cflags", 'f', "cflags", "", "set flags for C/C++ compiler" ); o_precompiled = cmd_compile->add< StringOption >( "precompiled", '\0', "precompiled", "", "path to pre-built bitcode libraries" ); o_libs_only = cmd_compile->add< BoolOption >( "libraries-only", '\0', "libraries-only", "", "only built runtime libraries (for use with --precompiled)"); o_out = cmd_compile->add< StringOption >( "output-file", 'o', "output-file", "", "specify the output file name " "(only works with --llvm/-l)"); o_dont_link = cmd_compile->add< BoolOption >( "dont-link", 0, "dont-link", "", "compile the source files, but do not link" ); o_cmd_clang = cmd_compile->add< StringOption >( "cmd-clang", 0, "cmd-clang", "", std::string( "how to run clang [default: " ) + _cmd_clang + "]" ); o_definitions = cmd_compile->add< VectorOption< String > >( "definition", 'D', "definition", "", "add definition for generator (can be specified multiple times)" ); o_parallel = cmd_compile->add< StringOption >( "jobs", 'j', "jobs", "", "parallel building (like with make but -j (without parameter) is not supported)" ); o_no_modeline = cmd_compile->add< BoolOption >( "no-modeline", 0, "no-modeline", "", "disable DIVINE modeline parsing" ); o_snapshot = cmd_compile->add< StringOption >( "snapshot", 0, "snapshot", "", "make snapshot of a directory" ); o_stdin = cmd_compile->add< StringOption >( "stdin", 0, "stdin", "", "use file as a standard input" ); } }; } #endif