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