@@ -151,6 +151,35 @@ bool is_c_source(const std::filesystem::path& src) {
151151 return ext == " .c" || ext == " .m" ;
152152}
153153
154+ bool is_gas_source (const std::filesystem::path& src) {
155+ auto ext = src.extension ();
156+ return ext == " .S" || ext == " .s" ;
157+ }
158+
159+ bool is_nasm_source (const std::filesystem::path& src) {
160+ return src.extension () == " .asm" ;
161+ }
162+
163+ // TUs the P1689 module scan must skip: C-family and assembly units cannot
164+ // contain `import`/`module` declarations, and feeding them to the scanner
165+ // would route them through the C++ frontend.
166+ bool is_scan_exempt (const std::filesystem::path& src) {
167+ return is_c_source (src) || is_gas_source (src) || is_nasm_source (src);
168+ }
169+
170+ // Per-unit flags an assembler can take: the -D/-U/-I subset of the unit's C
171+ // flags (feature defines land there). NASM shares the GNU -D/-U/-I spelling
172+ // (and ≥2.14 inserts a missing -I path separator itself), so one filter
173+ // serves both asm rules.
174+ std::vector<std::string> asm_unit_flags (const CompileUnit& cu) {
175+ std::vector<std::string> out;
176+ for (auto & f : cu.packageCflags ) {
177+ if (f.starts_with (" -D" ) || f.starts_with (" -U" ) || f.starts_with (" -I" ))
178+ out.push_back (f);
179+ }
180+ return out;
181+ }
182+
154183std::string ltrim_copy (std::string_view s) {
155184 while (!s.empty () && std::isspace (static_cast <unsigned char >(s.front ())))
156185 s.remove_prefix (1 );
@@ -275,20 +304,29 @@ std::string emit_ninja_string(const BuildPlan& plan) {
275304 // All compile/link flags are computed once via flags.cppm.
276305 auto flags = compute_flags (plan);
277306
278- bool need_c_rule = false ;
307+ bool need_c_rule = false , need_asm_rule = false , need_nasm_rule = false ;
279308 for (auto & cu : plan.compileUnits ) {
280- if (is_c_source (cu.source )) {
281- need_c_rule = true ;
282- break ;
283- }
309+ if (is_c_source (cu.source )) need_c_rule = true ;
310+ else if (is_gas_source (cu.source )) need_asm_rule = true ;
311+ else if (is_nasm_source (cu.source )) need_nasm_rule = true ;
284312 }
285313
286314 append (std::format (" cxx = {}\n " , escape_ninja_path (flags.cxxBinary )));
287315 append (std::format (" cxxflags = {}\n " , flags.cxx ));
288- if (need_c_rule) {
316+ if (need_c_rule || need_asm_rule ) { // asm_object drives the C compiler too
289317 append (std::format (" cc = {}\n " , escape_ninja_path (flags.ccBinary )));
318+ }
319+ if (need_c_rule) {
290320 append (std::format (" cflags = {}\n " , flags.cc ));
291321 }
322+ if (need_asm_rule) {
323+ append (std::format (" asmflags ={}\n " , flags.as ));
324+ }
325+ if (need_nasm_rule) {
326+ append (std::format (" nasm = {}\n " , escape_ninja_path (plan.nasmPath )));
327+ append (std::format (" nasmfmt = {}\n " , plan.nasmFormat ));
328+ append (std::format (" nasmflags ={}\n " , flags.nasm ));
329+ }
292330 append (std::format (" ldflags ={}\n " , flags.ld ));
293331
294332 // `ar` for cxx_archive.
@@ -406,6 +444,28 @@ std::string emit_ninja_string(const BuildPlan& plan) {
406444 append (" \n " );
407445 }
408446
447+ if (need_asm_rule) {
448+ // GAS assembly (.S/.s) through the C driver: it preprocesses .S (cpp)
449+ // and assembles both, dispatching by extension. $asmflags is the
450+ // asm-safe flag subset (no -std / no -O — see flags.cppm).
451+ append (" rule asm_object\n " );
452+ append (std::format (
453+ " command = $cc $local_includes $asmflags $unit_asmflags {}\n " ,
454+ compile_tail));
455+ append (" description = AS $out\n\n " );
456+ }
457+
458+ if (need_nasm_rule) {
459+ // NASM (.asm): its own fixed flag spelling — deliberately outside
460+ // CommandDialect. -MD/-MQ feed ninja's header tracking for %include.
461+ append (" rule nasm_object\n " );
462+ append (" command = $nasm -f $nasmfmt $local_includes $nasmflags "
463+ " $unit_asmflags -MD $out.d -MQ $out -o $out $in\n " );
464+ append (" deps = gcc\n " );
465+ append (" depfile = $out.d\n " );
466+ append (" description = NASM $out\n\n " );
467+ }
468+
409469 // Link/archive/shared: driver-style (g++/clang++ are the linker) vs the
410470 // msvc dialect's separate link.exe/lib.exe. The msvc commands go through
411471 // response files — object lists exceed cmd.exe's 8191-char limit fast.
@@ -533,6 +593,10 @@ std::string emit_ninja_string(const BuildPlan& plan) {
533593 return " cxx_module" ;
534594 if (ext == " .c" || ext == " .m" )
535595 return " c_object" ;
596+ if (ext == " .S" || ext == " .s" )
597+ return " asm_object" ;
598+ if (ext == " .asm" )
599+ return " nasm_object" ;
536600 return " cxx_object" ;
537601 };
538602
@@ -549,7 +613,7 @@ std::string emit_ninja_string(const BuildPlan& plan) {
549613 std::vector<std::string> ddi_paths;
550614 ddi_paths.reserve (plan.compileUnits .size ());
551615 for (auto & cu : plan.compileUnits ) {
552- if (is_c_source (cu.source ))
616+ if (is_scan_exempt (cu.source ))
553617 continue ;
554618 auto ddi = (cu.object .parent_path () / cu.source .filename ()).string () + " .ddi" ;
555619 ddi_paths.push_back (ddi);
@@ -580,7 +644,7 @@ std::string emit_ninja_string(const BuildPlan& plan) {
580644 }();
581645 std::map<std::string, std::string> ddi_expect;
582646 for (auto & cu : plan.compileUnits ) {
583- if (is_c_source (cu.source )) continue ;
647+ if (is_scan_exempt (cu.source )) continue ;
584648 if (!cu.scanOverridden && !verifyAll) continue ;
585649 auto ddi = (cu.object .parent_path () / cu.source .filename ()).string () + " .ddi" ;
586650 std::string exp;
@@ -618,7 +682,7 @@ std::string emit_ninja_string(const BuildPlan& plan) {
618682 out_line += " | " + bmi_path (*cu.providesModule );
619683 }
620684 out_line += std::format (" : {} {}" , rule, escape_ninja_path (cu.source ));
621- if (rule != " c_object " ) {
685+ if (! is_scan_exempt (cu. source ) ) {
622686 auto ddi = (cu.object .parent_path () / cu.source .filename ()).string () + " .ddi" ;
623687 auto it = ddi_to_dd.find (ddi);
624688 if (it != ddi_to_dd.end ()) {
@@ -637,7 +701,10 @@ std::string emit_ninja_string(const BuildPlan& plan) {
637701 }
638702 if (auto includes = local_include_flags (cu); !includes.empty ())
639703 out_line += " local_includes =" + includes + " \n " ;
640- if (is_c_source (cu.source )) {
704+ if (is_gas_source (cu.source ) || is_nasm_source (cu.source )) {
705+ if (auto flags = join_flags (asm_unit_flags (cu)); !flags.empty ())
706+ out_line += " unit_asmflags =" + flags + " \n " ;
707+ } else if (is_c_source (cu.source )) {
641708 if (auto flags = join_flags (cu.packageCflags ); !flags.empty ())
642709 out_line += " unit_cflags =" + flags + " \n " ;
643710 } else {
@@ -653,8 +720,8 @@ std::string emit_ninja_string(const BuildPlan& plan) {
653720 std::string rule = pick_rule (cu.source );
654721
655722 std::string implicit;
656- // .c files don't `import` modules; skip BMI implicit inputs.
657- if (rule != " c_object " ) {
723+ // C/asm files don't `import` modules; skip BMI implicit inputs.
724+ if (! is_scan_exempt (cu. source ) ) {
658725 for (auto & imp : cu.imports ) {
659726 if (imp == " std" ) {
660727 if (has_std_artifacts)
@@ -684,7 +751,10 @@ std::string emit_ninja_string(const BuildPlan& plan) {
684751 out_line += " \n " ;
685752 if (auto includes = local_include_flags (cu); !includes.empty ())
686753 out_line += " local_includes =" + includes + " \n " ;
687- if (is_c_source (cu.source )) {
754+ if (is_gas_source (cu.source ) || is_nasm_source (cu.source )) {
755+ if (auto flags = join_flags (asm_unit_flags (cu)); !flags.empty ())
756+ out_line += " unit_asmflags =" + flags + " \n " ;
757+ } else if (is_c_source (cu.source )) {
688758 if (auto flags = join_flags (cu.packageCflags ); !flags.empty ())
689759 out_line += " unit_cflags =" + flags + " \n " ;
690760 } else {
0 commit comments