| 1 | module cbuilder |
| 2 | |
| 3 | import os |
| 4 | import time |
| 5 | import v.util |
| 6 | import v.builder |
| 7 | import sync.pool |
| 8 | import v.gen.c |
| 9 | |
| 10 | const cc_compiler = os.getenv_opt('CC') or { 'cc' } |
| 11 | const cc_ldflags = os.getenv_opt('LDFLAGS') or { '' } |
| 12 | const cc_cflags = os.getenv_opt('CFLAGS') or { '' } |
| 13 | const cc_cflags_opt = os.getenv_opt('CFLAGS_OPT') or { '' } // '-O3' } |
| 14 | |
| 15 | fn parallel_cc_compiler_path(b &builder.Builder) string { |
| 16 | if b.pref.ccompiler != '' { |
| 17 | return b.pref.ccompiler |
| 18 | } |
| 19 | return cc_compiler |
| 20 | } |
| 21 | |
| 22 | fn parallel_cc(mut b builder.Builder, result c.GenOutput) ! { |
| 23 | tmp_dir := os.vtmp_dir() |
| 24 | sw_total := time.new_stopwatch() |
| 25 | defer { |
| 26 | eprint_time(sw_total, @METHOD) |
| 27 | } |
| 28 | c_files := int_max(1, util.nr_jobs) |
| 29 | eprintln('> c_files: ${c_files} | util.nr_jobs: ${util.nr_jobs}') |
| 30 | |
| 31 | // Write generated stuff in `g.out` before and after the `out_fn_start_pos` locations, |
| 32 | // like the `int main()` to "out_0.c" and "out_x.c" |
| 33 | |
| 34 | // out.h |
| 35 | os.write_file('${tmp_dir}/out.h', result.header) or { panic(err) } |
| 36 | |
| 37 | // out_0.c |
| 38 | out0 := '//out0\n' + result.out_str[..result.out_fn_start_pos[0]] |
| 39 | os.write_file('${tmp_dir}/out_0.c', '#include "out.h"\n' + out0 + '\n//X:\n' + result.out0_str) or { |
| 40 | panic(err) |
| 41 | } |
| 42 | |
| 43 | // out_x.c |
| 44 | os.write_file('${tmp_dir}/out_x.c', '#include "out.h"\n\n' + result.extern_str + '\n' + |
| 45 | result.out_str[result.out_fn_start_pos.last()..]) or { panic(err) } |
| 46 | |
| 47 | mut prev_fn_pos := 0 |
| 48 | mut out_files := []os.File{len: c_files} |
| 49 | mut fnames := []string{} |
| 50 | |
| 51 | for i in 0 .. c_files { |
| 52 | fname := '${tmp_dir}/out_${i + 1}.c' |
| 53 | fnames << fname |
| 54 | out_files[i] = os.create(fname) or { panic(err) } |
| 55 | |
| 56 | // Common .c file code |
| 57 | out_files[i].writeln('#include "out.h"\n') or { panic(err) } |
| 58 | out_files[i].writeln(result.extern_str) or { panic(err) } |
| 59 | } |
| 60 | |
| 61 | for i, fn_pos in result.out_fn_start_pos { |
| 62 | if prev_fn_pos >= result.out_str.len || fn_pos >= result.out_str.len || prev_fn_pos > fn_pos { |
| 63 | eprintln('> EXITING i=${i} out of ${result.out_fn_start_pos.len} prev_pos=${prev_fn_pos} fn_pos=${fn_pos}') |
| 64 | break |
| 65 | } |
| 66 | if i == 0 { |
| 67 | // Skip typeof etc stuff that's been added to out_0.c |
| 68 | prev_fn_pos = fn_pos |
| 69 | continue |
| 70 | } |
| 71 | fn_text := result.out_str[prev_fn_pos..fn_pos] |
| 72 | out_files[i % c_files].writeln(fn_text) or { panic(err) } |
| 73 | prev_fn_pos = fn_pos |
| 74 | } |
| 75 | for i in 0 .. c_files { |
| 76 | out_files[i].close() |
| 77 | } |
| 78 | |
| 79 | cc := b.quote_compiler_name(parallel_cc_compiler_path(b)) |
| 80 | mut compile_args := b.get_compile_args() |
| 81 | mut linker_args := b.get_linker_args() |
| 82 | if b.ccoptions.cc == .tcc { |
| 83 | // vlang/tcc has its system headers under `${vroot}/thirdparty/tcc/lib/tcc/include/` |
| 84 | // and its runtime objects (libtcc1.a, bt-*.o) under `${vroot}/thirdparty/tcc/lib/tcc/`. |
| 85 | // `-B` controls tcc's include search (`${B}/include`) and `-L` adds a library search path, |
| 86 | // so pass absolute paths for both. This lets tcc find them regardless of the cwd from |
| 87 | // which v was invoked, without affecting how user-supplied relative flags are resolved. |
| 88 | tcc_install_dir := os.join_path(@VEXEROOT, 'thirdparty', 'tcc', 'lib', 'tcc') |
| 89 | if os.is_dir(tcc_install_dir) { |
| 90 | tcc_b_arg := '-B${b.tcc_quoted_path(tcc_install_dir)}' |
| 91 | tcc_l_arg := '-L${b.tcc_quoted_path(tcc_install_dir)}' |
| 92 | compile_args << tcc_b_arg |
| 93 | linker_args << tcc_b_arg |
| 94 | linker_args << tcc_l_arg |
| 95 | } |
| 96 | } |
| 97 | scompile_args := compile_args.join(' ') |
| 98 | slinker_args := linker_args.join(' ') |
| 99 | scompile_args_for_linker := compile_args.filter(it != '-x objective-c').join(' ') |
| 100 | |
| 101 | mut o_postfixes := ['0', 'x'] |
| 102 | mut cmds := []string{} |
| 103 | for i in 0 .. c_files { |
| 104 | o_postfixes << (i + 1).str() |
| 105 | } |
| 106 | for postfix in o_postfixes { |
| 107 | out_o := os.quoted_path('${tmp_dir}/out_${postfix}.o') |
| 108 | out_c := os.quoted_path('${tmp_dir}/out_${postfix}.c') |
| 109 | cmds << '${cc} ${cc_cflags} ${cc_cflags_opt} ${scompile_args} -w -o ${out_o} -c ${out_c}' |
| 110 | } |
| 111 | mut failed := 0 |
| 112 | sw := time.new_stopwatch() |
| 113 | mut pp := pool.new_pool_processor(callback: build_parallel_o_cb) |
| 114 | pp.set_max_jobs(util.nr_jobs) |
| 115 | pp.work_on_items(cmds) |
| 116 | for x in pp.get_results[os.Result]() { |
| 117 | failed += if x.exit_code == 0 { 0 } else { 1 } |
| 118 | } |
| 119 | eprint_time(sw, |
| 120 | 'C compilation on ${util.nr_jobs} thread(s), processing ${cmds.len} commands, failed: ${failed}') |
| 121 | if failed > 0 { |
| 122 | return error_with_code('failed parallel C compilation', failed) |
| 123 | } |
| 124 | |
| 125 | mut ofiles := []string{} |
| 126 | for f in fnames { |
| 127 | fo := f.replace('.c', '.o') |
| 128 | ofiles << os.quoted_path(fo) |
| 129 | } |
| 130 | obj_files := ofiles.join(' ') |
| 131 | |
| 132 | alink := [ |
| 133 | cc, |
| 134 | scompile_args_for_linker, |
| 135 | '-o', |
| 136 | os.quoted_path(b.pref.out_name), |
| 137 | os.quoted_path('${tmp_dir}/out_0.o'), |
| 138 | obj_files, |
| 139 | os.quoted_path('${tmp_dir}/out_x.o'), |
| 140 | slinker_args, |
| 141 | cc_ldflags, |
| 142 | ] |
| 143 | link_cmd := alink.join(' ') |
| 144 | |
| 145 | sw_link := time.new_stopwatch() |
| 146 | link_res := os.execute(link_cmd) |
| 147 | eprint_result_time(sw_link, 'link_cmd', link_cmd, link_res) |
| 148 | // tcc reports duplicate symbol errors via stderr and an executable still gets emitted with exit code 0, |
| 149 | // so detect that pattern and treat it as a link failure too. |
| 150 | link_failed_with_tcc_dup := b.ccoptions.cc == .tcc && link_res.output.contains('defined twice') |
| 151 | if link_res.exit_code != 0 || link_failed_with_tcc_dup { |
| 152 | return error_with_code('failed to link after parallel C compilation', 1) |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | fn build_parallel_o_cb(mut p pool.PoolProcessor, idx int, _wid int) voidptr { |
| 157 | cmd := p.get_item[string](idx) |
| 158 | sw := time.new_stopwatch() |
| 159 | res := os.execute(cmd) |
| 160 | eprint_result_time(sw, 'cc_cmd', cmd, res) |
| 161 | return voidptr(&os.Result{ |
| 162 | ...res |
| 163 | }) |
| 164 | } |
| 165 | |
| 166 | fn eprint_result_time(sw time.StopWatch, label string, cmd string, res os.Result) { |
| 167 | eprint_time(sw, '${label}: `${cmd}` => ${res.exit_code}') |
| 168 | if res.exit_code != 0 { |
| 169 | eprintln(res.output) |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | fn eprint_time(sw time.StopWatch, label string) { |
| 174 | eprintln('> ${sw.elapsed().milliseconds():5} ms, ${label}') |
| 175 | } |
| 176 | |