From 8aeae026d112ff9811a424e31621c05682f4a72e Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 18 Apr 2023 09:23:24 +0200 Subject: Add support for Assembler with C Preprocessor (.S) compilation Specifically, the c module now provides the c.as-cpp submodules which can be loaded in order to register the S{} target type and enable Assembler with C Preprocessor compilation in the c compile rule. For details, refer to "Assembler with C Preprocessor Compilation" in the manual. --- NEWS | 7 ++++ doc/manual.cli | 84 +++++++++++++++++++++++++++++++++++++++++++ libbuild2/c/init.cxx | 41 +++++++++++++++++++++ libbuild2/c/init.hxx | 4 ++- libbuild2/c/target.hxx | 1 + libbuild2/cc/common.hxx | 17 +++++++-- libbuild2/cc/compile-rule.cxx | 73 +++++++++++++++++++++---------------- libbuild2/cc/install-rule.cxx | 2 ++ libbuild2/cc/link-rule.cxx | 18 ++++++---- libbuild2/cc/module.cxx | 4 +-- libbuild2/cc/target.cxx | 14 ++++++++ libbuild2/cc/target.hxx | 16 +++++++++ libbuild2/cxx/init.cxx | 4 +++ libbuild2/module.hxx | 6 ++++ 14 files changed, 249 insertions(+), 42 deletions(-) diff --git a/NEWS b/NEWS index 6a7cbcd..2bfb0e7 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,13 @@ Version 0.16.0 and no attempt to automatically link any necessary Objective-C runtime (such as -lobjc) is made. + * Support for Assembler with C Preprocessor (.S) compilation. + + Specifically, the c module now provides the c.as-cpp submodules which can + be loaded in order to register the S{} target type and enable Assembler + with C Preprocessor compilation in the c compile rule. For details, refer + to "Assembler with C Preprocessor Compilation" in the manual. + * Low verbosity diagnostics rework. The low verbosity (level 1) rule diagnostics format has been adjusted to diff --git a/doc/manual.cli b/doc/manual.cli index 7f6b730..6f3def4 100644 --- a/doc/manual.cli +++ b/doc/manual.cli @@ -7329,6 +7329,90 @@ automatically link any necessary Objective-C runtime library (such as \c{-lobjc}). +\h#c-as-cpp|Assembler with C Preprocessor Compilation| + +The \c{c} module provides the \c{c.as-cpp} submodules which can be loaded in +order to register the \c{S{\}} target type and enable Assembler with C +Preprocessor compilation in the \c{C} compile rule. Note that \c{c.as-cpp} +must be loaded after the \c{c} module and while the \c{S{\}} target type is +registered unconditionally, compilation is only enabled if the C compiler +supports Assembler with C Preprocessor compilation. + +Typical usage: + +\ +# root.build +# +using c +using c.as-cpp +\ + +\ +# buildfile +# +exe{hello}: {h c}{* -hello.c} + +# Use C implementation as a fallback if no assembler. +# +assembler = ($c.class == 'gcc' && $c.target.cpu == 'x86_64') + +exe{hello}: S{hello}: include = $assembler +exe{hello}: c{hello}: include = (!$assembler) +\ + +\ +/* hello.S + */ +#ifndef HELLO_RESULT +# define HELLO_RESULT 0 +#endif + +text + +.global hello +hello: + /* ... */ + movq $HELLO_RESULT, %rax + ret + +#ifdef __ELF__ +.section .note.GNU-stack, \"\", @progbits +#endif +\ + +The default file extension for the \c{S{\}} target type is \c{.S} (capital) +but that can be customized using the standard mechanisms. For example: + +\ +# root.build +# +using c +using c.as-cpp + +h{*}: extension = h +c{*}: extension = c +S{*}: extension = sx +\ + +Note that \c{*.coptions} are passed to the C compiler when compiling Assembler +with C Preprocessor files because compile options may cause additional +preprocessor macros to be defined. Plus, some of them (such as \c{-g}) are +passed (potentially translated) to the underlying assembler. To pass +additional options when compiling Assembler files use \c{c.poptions} and +\c{c.coptions}. For example (continuing with the previous example): + +\ +if $assembler +{ + obj{hello}: + { + c.poptions += -DHELLO_RESULT=1 + c.coptions += -Wa,--no-pad-sections + } +} +\ + + \h1#module-cxx|\c{cxx} Module| \N{This chapter is a work in progress and is incomplete.} diff --git a/libbuild2/c/init.cxx b/libbuild2/c/init.cxx index 2dbd534..0092b81 100644 --- a/libbuild2/c/init.cxx +++ b/libbuild2/c/init.cxx @@ -324,11 +324,15 @@ namespace build2 nullptr }; + // Note that we include S{} here because .S files can include each other. + // (And maybe from inline assember instrcutions?) + // static const target_type* const inc[] = { &h::static_type, &c::static_type, &m::static_type, + &S::static_type, nullptr }; @@ -446,6 +450,42 @@ namespace build2 return true; } + bool + as_cpp_init (scope& rs, + scope& bs, + const location& loc, + bool, + bool, + module_init_extra&) + { + tracer trace ("c::as_cpp_init"); + l5 ([&]{trace << "for " << bs;}); + + // We only support root loading (which means there can only be one). + // + if (rs != bs) + fail (loc) << "c.as-cpp module must be loaded in project root"; + + module* mod (rs.find_module ("c")); + + if (mod == nullptr) + fail (loc) << "c.as-cpp module must be loaded after c module"; + + // Register the target type and "enable" it in the module. + // + // Note that we must register the target type regardless of whether the + // C compiler is capable of compiling Assember with C preprocessor. But + // we enable only if it is. + // + rs.insert_target_type (); + + if (mod->ctype == compiler_type::gcc || + mod->ctype == compiler_type::clang) + mod->x_asp = &S::static_type; + + return true; + } + static const module_functions mod_functions[] = { // NOTE: don't forget to also update the documentation in init.hxx if @@ -455,6 +495,7 @@ namespace build2 {"c.config", nullptr, config_init}, {"c", nullptr, init}, {"c.objc", nullptr, objc_init}, + {"c.as-cpp", nullptr, as_cpp_init}, {nullptr, nullptr, nullptr} }; diff --git a/libbuild2/c/init.hxx b/libbuild2/c/init.hxx index f324c31..c3126ea 100644 --- a/libbuild2/c/init.hxx +++ b/libbuild2/c/init.hxx @@ -23,7 +23,9 @@ namespace build2 // `c.config` -- loads c.guess and sets more variables. // `c` -- loads c.config and registers target types and rules. // `c.objc` -- registers m{} target type and enables Objective-C - // compilation. + // compilation. Must be loaded after c. + // `c.as-cpp` -- registers S{} target type and enables Assembler with + // C preprocessor compilation. Must be loaded after c. // extern "C" LIBBUILD2_C_SYMEXPORT const module_functions* build2_c_load (); diff --git a/libbuild2/c/target.hxx b/libbuild2/c/target.hxx index 308bda9..39fcf89 100644 --- a/libbuild2/c/target.hxx +++ b/libbuild2/c/target.hxx @@ -16,6 +16,7 @@ namespace build2 using cc::h; using cc::c; using cc::m; + using cc::S; } } diff --git a/libbuild2/cc/common.hxx b/libbuild2/cc/common.hxx index 9090c9c..eefcc0d 100644 --- a/libbuild2/cc/common.hxx +++ b/libbuild2/cc/common.hxx @@ -217,11 +217,14 @@ namespace build2 size_t sys_hdr_dirs_extra; // Note that x_obj is patched in by the x.objx module. So it stays NULL - // if Objective-X compilation is not enabled. + // if Objective-X compilation is not enabled. Similarly for x_asp except + // here we don't have duality and it's purely to signal (by the c.as-cpp + // module) that it's enabled. // const target_type& x_src; // Source target type (c{}, cxx{}). const target_type* x_mod; // Module target type (mxx{}), if any. const target_type* x_obj; // Objective-X target type (m{}, mm{}). + const target_type* x_asp; // Assembler with CPP target type (S{}). // Check if an object (target, prerequisite, etc) is an Objective-X // source. @@ -233,6 +236,16 @@ namespace build2 return x_obj != nullptr && t.is_a (*x_obj); } + // Check if an object (target, prerequisite, etc) is an Assembler with + // C preprocessor source. + // + template + bool + x_assembler_cpp (const T& t) const + { + return x_asp != nullptr && t.is_a (*x_asp); + } + // Array of target types that are considered the X-language headers // (excluding h{} except for C). Keep them in the most likely to appear // order with the "real header" first and terminated with NULL. @@ -305,7 +318,7 @@ namespace build2 sys_lib_dirs_mode (slm), sys_hdr_dirs_mode (shm), sys_mod_dirs_mode (smm), sys_lib_dirs_extra (sle), sys_hdr_dirs_extra (she), - x_src (src), x_mod (mod), x_obj (nullptr), + x_src (src), x_mod (mod), x_obj (nullptr), x_asp (nullptr), x_hdr (hdr), x_inc (inc) {} }; diff --git a/libbuild2/cc/compile-rule.cxx b/libbuild2/cc/compile-rule.cxx index b63b3cf..ca6da03 100644 --- a/libbuild2/cc/compile-rule.cxx +++ b/libbuild2/cc/compile-rule.cxx @@ -371,13 +371,19 @@ namespace build2 case unit_type::non_modular: case unit_type::module_impl: { - bool obj (x_objective (md.src)); - o1 = "-x"; - switch (x_lang) + + if (x_assembler_cpp (md.src)) + o2 = "assembler-with-cpp"; + else { - case lang::c: o2 = obj ? "objective-c" : "c"; break; - case lang::cxx: o2 = obj ? "objective-c++" : "c++"; break; + bool obj (x_objective (md.src)); + + switch (x_lang) + { + case lang::c: o2 = obj ? "objective-c" : "c"; break; + case lang::cxx: o2 = obj ? "objective-c++" : "c++"; break; + } } break; } @@ -479,7 +485,9 @@ namespace build2 // if (ut == unit_type::module_header ? p.is_a (**x_hdr) || p.is_a () : ut == unit_type::module_intf ? p.is_a (*x_mod) : - p.is_a (x_src) || (x_obj != nullptr && p.is_a (*x_obj))) + p.is_a (x_src) || + (x_asp != nullptr && p.is_a (*x_asp)) || + (x_obj != nullptr && p.is_a (*x_obj))) { // Save in the target's auxiliary storage. // @@ -3021,11 +3029,14 @@ namespace build2 // Preprocessed file extension. // - const char* pext (x_objective (src) ? x_obj_pext : x_pext); + const char* pext (x_assembler_cpp (src) ? ".Si" : + x_objective (src) ? x_obj_pext : + x_pext); // Preprocesor mode that preserves as much information as possible while // still performing inclusions. Also serves as a flag indicating whether - // this compiler uses the separate preprocess and compile setup. + // this (non-MSVC) compiler uses the separate preprocess and compile + // setup. // const char* pp (nullptr); @@ -3036,7 +3047,16 @@ namespace build2 // -fdirectives-only is available since GCC 4.3.0. // if (cmaj > 4 || (cmaj == 4 && cmin >= 3)) - pp = "-fdirectives-only"; + { + // Note that for assembler-with-cpp GCC currently forces full + // preprocessing in (what appears to be) an attempt to paper over + // a deeper issue (see GCC bug 109534). If/when that bug gets + // fixed, we can enable this on our side. Note also that Clang's + // -frewrite-includes appear to work correctly on such files. + // + if (!x_assembler_cpp (src)) + pp = "-fdirectives-only"; + } break; } @@ -6877,7 +6897,6 @@ namespace build2 small_vector module_args; // Module options storage. size_t out_i (0); // Index of the -o option. - //size_t lang_n (0); // Number of lang options. @@ TMP switch (cclass) { @@ -7236,7 +7255,7 @@ namespace build2 args.push_back ("-c"); } - /*lang_n = */append_lang_options (args, md); // @@ TMP + append_lang_options (args, md); if (md.pp == preprocessed::all) { @@ -7288,7 +7307,13 @@ namespace build2 // @@ TODO: why don't we print env (here and/or below)? Also link rule. // if (verb == 1) - print_diag (x_objective (s) ? x_obj_name : x_name, s, t); + { + const char* name (x_assembler_cpp (s) ? "as-cpp" : + x_objective (s) ? x_obj_name : + x_name); + + print_diag (name, s, t); + } else if (verb == 2) print_process (args); @@ -7314,31 +7339,15 @@ namespace build2 { case compiler_type::gcc: { - // @@ TMP -#if 0 - // The -fpreprocessed is implied by .i/.ii. But not when compiling - // a header unit (there is no .hi/.hii). - // - if (ut == unit_type::module_header) - args.push_back ("-fpreprocessed"); - else - // Pop -x since it takes precedence over the extension. - // - // @@ I wonder why bother and not just add -fpreprocessed? Are - // we trying to save an option or does something break? - // - for (; lang_n != 0; --lang_n) - args.pop_back (); -#else // -fpreprocessed is implied by .i/.ii unless compiling a header // unit (there is no .hi/.hii). Also, we would need to pop -x // since it takes precedence over the extension, which would mess // up our osrc logic. So in the end it feels like always passing // explicit -fpreprocessed is the way to go. // + // Also note that similarly there is no .Si for .S files. + // args.push_back ("-fpreprocessed"); -#endif - args.push_back ("-fdirectives-only"); break; } @@ -7518,7 +7527,9 @@ namespace build2 // Preprocessed file extension. // - const char* pext (x_objective (srct) ? x_obj_pext : x_pext); + const char* pext (x_assembler_cpp (srct) ? ".Si" : + x_objective (srct) ? x_obj_pext : + x_pext); // Compressed preprocessed file extension. // diff --git a/libbuild2/cc/install-rule.cxx b/libbuild2/cc/install-rule.cxx index b867466..dae65db 100644 --- a/libbuild2/cc/install-rule.cxx +++ b/libbuild2/cc/install-rule.cxx @@ -91,6 +91,7 @@ namespace build2 return (x_header (p) || p.is_a (x_src) || (x_mod != nullptr && p.is_a (*x_mod)) || + (x_asp != nullptr && p.is_a (*x_asp)) || (x_obj != nullptr && p.is_a (*x_obj))); }; @@ -340,6 +341,7 @@ namespace build2 return (x_header (p) || p.is_a (x_src) || (x_mod != nullptr && p.is_a (*x_mod)) || + (x_asp != nullptr && p.is_a (*x_asp)) || (x_obj != nullptr && p.is_a (*x_obj))); }; diff --git a/libbuild2/cc/link-rule.cxx b/libbuild2/cc/link-rule.cxx index 76fd3c5..e2bdf5d 100644 --- a/libbuild2/cc/link-rule.cxx +++ b/libbuild2/cc/link-rule.cxx @@ -292,6 +292,7 @@ namespace build2 if (p.is_a (x_src) || (x_mod != nullptr && p.is_a (*x_mod)) || + (x_asp != nullptr && p.is_a (*x_asp)) || (x_obj != nullptr && p.is_a (*x_obj)) || // Header-only X library (or library with C source and X header). (library && x_header (p, false /* c_hdr */))) @@ -1003,6 +1004,7 @@ namespace build2 if (!um) um = (p.is_a (x_src) || p.is_a () || (x_mod != nullptr && p.is_a (*x_mod)) || + (x_asp != nullptr && p.is_a (*x_asp)) || (x_obj != nullptr && (p.is_a (*x_obj) || p.is_a ())) || x_header (p, true)); #endif @@ -1032,8 +1034,9 @@ namespace build2 bool mod (x_mod != nullptr && p.is_a (*x_mod)); bool hdr (false); - if (mod || - p.is_a (x_src) || p.is_a () || + if (mod || + p.is_a (x_src) || p.is_a () || + (x_asp != nullptr && p.is_a (*x_asp)) || (x_obj != nullptr && (p.is_a (*x_obj) || p.is_a ()))) { binless = binless && (mod ? user_binless : false); @@ -1902,7 +1905,8 @@ namespace build2 // if (mod ? p1.is_a (*x_mod) - : (p1.is_a (x_src) || p1.is_a () || + : (p1.is_a (x_src) || p1.is_a () || + (x_asp != nullptr && p1.is_a (*x_asp)) || (x_obj != nullptr && (p1.is_a (*x_obj) || p1.is_a ())))) { src = true; @@ -1916,8 +1920,9 @@ namespace build2 p1.is_a () || p1.is_a () || p1.is_a () || p1.is_a () || p1.is_a () || p1.is_a () || - ((mod || - p.is_a (x_src) || + ((mod || + p.is_a (x_src) || + (x_asp != nullptr && p.is_a (*x_asp)) || (x_obj != nullptr && p.is_a (*x_obj))) && x_header (p1)) || ((p.is_a () || (x_obj != nullptr && p.is_a ())) && p1.is_a ())) @@ -2062,7 +2067,8 @@ namespace build2 { if (mod ? p1.is_a (*x_mod) - : (p1.is_a (x_src) || p1.is_a () || + : (p1.is_a (x_src) || p1.is_a () || + (x_asp != nullptr && p1.is_a (*x_asp)) || (x_obj != nullptr && (p1.is_a (*x_obj) || p1.is_a ())))) { // Searching our own prerequisite is ok, p1 must already be diff --git a/libbuild2/cc/module.cxx b/libbuild2/cc/module.cxx index b337754..f33ddf4 100644 --- a/libbuild2/cc/module.cxx +++ b/libbuild2/cc/module.cxx @@ -979,8 +979,8 @@ namespace build2 { using namespace install; - // Note: not registering x_obj (it's registered seperately by the - // x.objx module). + // Note: not registering x_obj or x_asp (they are registered + // seperately by the respective optional submodules). // rs.insert_target_type (x_src); diff --git a/libbuild2/cc/target.cxx b/libbuild2/cc/target.cxx index d743752..6c5d7c8 100644 --- a/libbuild2/cc/target.cxx +++ b/libbuild2/cc/target.cxx @@ -66,6 +66,20 @@ namespace build2 target_type::flag::none }; + extern const char S_ext_def[] = "S"; + const target_type S::static_type + { + "S", + &cc::static_type, + &target_factory, + nullptr, /* fixed_extension */ + &target_extension_var, + &target_pattern_var, + nullptr, + &file_search, + target_type::flag::none + }; + extern const char pc_ext[] = "pc"; // VC14 rejects constexpr. const target_type pc::static_type { diff --git a/libbuild2/cc/target.hxx b/libbuild2/cc/target.hxx index 87df326..a078422 100644 --- a/libbuild2/cc/target.hxx +++ b/libbuild2/cc/target.hxx @@ -84,6 +84,22 @@ namespace build2 static const target_type static_type; }; + // Assembler with C preprocessor source file (the same rationale for + // having it here as for c{} above). + // + class LIBBUILD2_CC_SYMEXPORT S: public cc + { + public: + S (context& c, dir_path d, dir_path o, string n) + : cc (c, move (d), move (o), move (n)) + { + dynamic_type = &static_type; + } + + public: + static const target_type static_type; + }; + // pkg-config file targets. // class LIBBUILD2_CC_SYMEXPORT pc: public file // .pc (common) diff --git a/libbuild2/cxx/init.cxx b/libbuild2/cxx/init.cxx index fd6d04c..732b08b 100644 --- a/libbuild2/cxx/init.cxx +++ b/libbuild2/cxx/init.cxx @@ -783,6 +783,10 @@ namespace build2 nullptr }; + // Note that we don't include S{} here because none of the files we + // compile can plausibly want to include .S. (Maybe in inline assember + // instrcutions?) + // static const target_type* const inc[] = { &hxx::static_type, diff --git a/libbuild2/module.hxx b/libbuild2/module.hxx index 8223bae..2f2d8a7 100644 --- a/libbuild2/module.hxx +++ b/libbuild2/module.hxx @@ -21,6 +21,12 @@ namespace build2 // implementation's perspectives, the module library is "loaded" and the // module is optionally "bootstrapped" (or "booted" for short) and then // "initialized" (or "inited"). + // + // Note also that a module name (or component thereof, for submodules) is + // not a project name (in particular, it can be less than 3 characters long) + // and we usually use `-` instead of `_` as a word separator within + // components, for example `c.as-cpp` (since the top-level component ends up + // in the library name; but this is not a hard rule). // Base class for module instance. // -- cgit v1.1