From f36d3e02033ff2f9d14cb20a6d338c8bb09a3962 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 1 Jun 2017 15:18:08 +0200 Subject: Implement module interface unit compilation for Clang and VC --- build2/algorithm.cxx | 27 ++++++ build2/algorithm.hxx | 8 ++ build2/cc/compile.cxx | 247 +++++++++++++++++++++++++++++++++++++------------- build2/cc/link.cxx | 16 +--- build2/target.hxx | 2 + 5 files changed, 221 insertions(+), 79 deletions(-) diff --git a/build2/algorithm.cxx b/build2/algorithm.cxx index 156a209..8f555dd 100644 --- a/build2/algorithm.cxx +++ b/build2/algorithm.cxx @@ -244,6 +244,33 @@ namespace build2 sched.resume (t.task_count); } + target_lock + add_adhoc_member (action a, target& t, const target_type& tt, const char* s) + { + string n (t.name); + if (s != nullptr) + { + n += '.'; + n += s; + } + + const target& m (t.member != nullptr // Might already be there. + ? *t.member + : search (t, tt, t.dir, t.out, n)); + + target_lock l (lock (a, m)); + assert (l.target != nullptr); // Someone messing with ad hoc members? + + if (t.member == nullptr) + t.member = l.target; + else + // Basic sanity check. + // + assert (t.member->type () == tt && t.member->name == n); + + return l; + }; + // Return the matching rule and the recipe action. // pair>*, action> diff --git a/build2/algorithm.hxx b/build2/algorithm.hxx index 36e5a92..d002e23 100644 --- a/build2/algorithm.hxx +++ b/build2/algorithm.hxx @@ -120,6 +120,14 @@ namespace build2 target_lock lock (action, const target&); + // Add an ad hoc member. If the suffix is specified, it is added (as an + // extension) to the member's target name. Return the locked member target. + // + target_lock + add_adhoc_member (action, target&, + const target_type&, + const char* suffix = nullptr); + // Match and apply a rule to the action/target with ambiguity detection. // Increment the target's dependents count, which means that you should call // this function with the intent to also call execute(). Return the target diff --git a/build2/cc/compile.cxx b/build2/cc/compile.cxx index b0b7568..d5566b8 100644 --- a/build2/cc/compile.cxx +++ b/build2/cc/compile.cxx @@ -393,65 +393,89 @@ namespace build2 // Derive file name from target name. // - string e; // In case of a module, this is the object file extension. - - if (tsys == "win32-msvc") + string e; // Primary target extension (module or object). { - switch (ct) + const char* o ("o"); // Object extension (.o or .obj). + + if (tsys == "win32-msvc") { - case otype::e: e = "exe."; break; - case otype::a: e = "lib."; break; - case otype::s: e = "dll."; break; + switch (ct) + { + case otype::e: e = "exe."; break; + case otype::a: e = "lib."; break; + case otype::s: e = "dll."; break; + } + o = "obj"; } - } - else if (tsys == "mingw32") - { - switch (ct) + else if (tsys == "mingw32") { - case otype::e: e = "exe."; break; - case otype::a: e = "a."; break; - case otype::s: e = "dll."; break; + switch (ct) + { + case otype::e: e = "exe."; break; + case otype::a: e = "a."; break; + case otype::s: e = "dll."; break; + } } - } - else if (tsys == "darwin") - { - switch (ct) + else if (tsys == "darwin") { - case otype::e: e = ""; break; - case otype::a: e = "a."; break; - case otype::s: e = "dylib."; break; + switch (ct) + { + case otype::e: e = ""; break; + case otype::a: e = "a."; break; + case otype::s: e = "dylib."; break; + } } - } - else - { - switch (ct) + else { - case otype::e: e = ""; break; - case otype::a: e = "a."; break; - case otype::s: e = "so."; break; + switch (ct) + { + case otype::e: e = ""; break; + case otype::a: e = "a."; break; + case otype::s: e = "so."; break; + } } - } - switch (cid) - { - case compiler_id::gcc: - { - if (mod) e += "nms."; - e += "o"; - break; - } - case compiler_id::clang: + switch (cid) { - if (mod) e += "pcm."; - e += "o"; - break; + case compiler_id::gcc: + { + e += mod ? "nms" : o; + break; + } + case compiler_id::clang: + { + e += mod ? "pcm" : o; + break; + } + case compiler_id::msvc: + case compiler_id::icc: + { + e += mod ? "ifc" : o; + break; + } } - case compiler_id::msvc: - case compiler_id::icc: + + // If we are compiling a module, then the obj*{} is an ad hoc member + // of bmi*{}. + // + if (mod) { - if (mod) e += "ifc."; - e += (tsys == "win32-msvc" ? "obj" : "o"); - break; + const target_type* tt (nullptr); + + switch (ct) + { + case otype::e: tt = &obje::static_type; break; + case otype::a: tt = &obja::static_type; break; + case otype::s: tt = &objs::static_type; break; + } + + // The module interface unit can be the same as an implementation + // (e.g., foo.mxx and foo.cxx) which means obj*{} targets could + // collide. So we add the module extension to the target name. + // + target_lock obj (add_adhoc_member (act, t, *tt, e.c_str ())); + obj.target->as ().derive_path (o); + match_recipe (obj, group_recipe); // Set recipe and unlock. } } @@ -2335,7 +2359,6 @@ namespace build2 perform_update (action act, const target& xt) const { const file& t (xt.as ()); - const path& tp (t.path ()); match_data md (move (t.data ())); bool mod (md.mod); @@ -2366,9 +2389,22 @@ namespace build2 // Translate paths to relative (to working directory) ones. This results // in easier to read diagnostics. // - path relo (relative (tp)); path rels (relative (s.path ())); + // If we are building a module, then the target is bmi*{} and its ad hoc + // member is obj*{}. + // + path relo, relm; + if (mod) + { + relm = relative (t.path ()); + relo = relative (t.member->is_a ()->path ()); + } + else + relo = relative (t.path ()); + + // Build command line. + // if (md.pp != preprocessed::all) { append_options (args, t, c_poptions); @@ -2392,6 +2428,7 @@ namespace build2 append_options (args, tstd); string out, out1; // Storage. + size_t out_i (0); // Index of the -o option. if (cid == compiler_id::msvc) { @@ -2476,6 +2513,13 @@ namespace build2 args.push_back (out.c_str ()); } + if (mod) + { + args.push_back ("/module:interface"); + args.push_back ("/module:output"); + args.push_back (relm.string ().c_str ()); + } + // Note: no way to indicate that the source if already preprocessed. args.push_back ("/c"); // Compile only. @@ -2492,13 +2536,46 @@ namespace build2 args.push_back ("-fPIC"); } - args.push_back ("-o"); - args.push_back (relo.string ().c_str ()); - - args.push_back ("-c"); - // Note: the order of the following options is relied upon below. // + out_i = args.size (); // Index of the -o option. + + if (mod) + { + switch (cid) + { + case compiler_id::gcc: + { + args.push_back ("-o"); + args.push_back (relo.string ().c_str ()); + //@@ MOD: specify module output file. + args.push_back ("-c"); + break; + } + case compiler_id::clang: + { + args.push_back ("-o"); + args.push_back (relm.string ().c_str ()); + args.push_back ("--precompile"); + + // These should become the default at some point. + // + args.push_back ("-Xclang"); args.push_back ("-fmodules-codegen"); + args.push_back ("-Xclang"); args.push_back ("-fmodules-debuginfo"); + break; + } + case compiler_id::msvc: + case compiler_id::icc: + assert (false); + } + } + else + { + args.push_back ("-o"); + args.push_back (relo.string ().c_str ()); + args.push_back ("-c"); + } + args.push_back ("-x"); args.push_back (langopt (md)); @@ -2640,17 +2717,6 @@ namespace build2 if (!pr.wait ()) throw failed (); - - if (psrc && verb >= 3) - md.psrc = auto_rmfile (move (rels)); - - // Should we go to the filesystem and get the new mtime? We - // know the file has been modified, so instead just use the - // current clock time. It has the advantage of having the - // subseconds precision. - // - t.mtime (system_clock::now ()); - return target_state::changed; } catch (const process_error& e) { @@ -2665,6 +2731,59 @@ namespace build2 throw failed (); } + + if (psrc && verb >= 3) + md.psrc = auto_rmfile (move (rels)); + + // Clang's module compilation requires two separate compiler + // invocations. + // + if (mod && cid == compiler_id::clang) + { + // Remove the target file if this fails. If we don't do that, we will + // end up with a broken build that is up-to-date. + // + auto_rmfile rm (relm); + + // Adjust the command line. First discard everything after -o then + // build the new "tail". + // + args.resize (out_i + 1); + args.push_back (relo.string ().c_str ()); // Produce .o. + args.push_back ("-c"); // By compiling .pcm. + args.push_back ("-Wno-unused-command-line-argument"); //@@ MOD (-I). + args.push_back (relm.string ().c_str ()); + args.push_back (nullptr); + + if (verb >= 2) + print_process (args); + + try + { + process pr (cpath, args.data ()); + + if (!pr.wait ()) + throw failed (); + } + catch (const process_error& e) + { + error << "unable to execute " << args[0] << ": " << e; + + if (e.child) + exit (1); + + throw failed (); + } + + rm.cancel (); + } + + // Should we go to the filesystem and get the new mtime? We know the + // file has been modified, so instead just use the current clock time. + // It has the advantage of having the subseconds precision. + // + t.mtime (system_clock::now ()); + return target_state::changed; } target_state compile:: diff --git a/build2/cc/link.cxx b/build2/cc/link.cxx index 0d2a14f..9bbe677 100644 --- a/build2/cc/link.cxx +++ b/build2/cc/link.cxx @@ -314,21 +314,7 @@ namespace build2 // auto add_adhoc = [act, &bs] (target& t, const char* type) -> target_lock { - const target_type& tt (*bs.find_target_type (type)); - - const target& m (t.member != nullptr // Might already be there. - ? *t.member - : search (t, tt, t.dir, t.out, t.name)); - - target_lock l (lock (act, m)); - assert (l.target != nullptr); // Someone messing with adhoc members? - - if (t.member == nullptr) - t.member = l.target; - else - assert (t.member->type () == tt); - - return l; + return add_adhoc_member (act, t, *bs.find_target_type (type)); }; { diff --git a/build2/target.hxx b/build2/target.hxx index 19c8946..d4def6e 100644 --- a/build2/target.hxx +++ b/build2/target.hxx @@ -228,6 +228,8 @@ namespace build2 // - Member variable lookup skips the ad hoc group (since the group is // the first member, this is normally what we want). // + // Use add_adhoc_member() from algorithms to add an ad hoc member. + // const_ptr member = nullptr; bool -- cgit v1.1