v4 / vlib / v / builder / cbuilder / parallel_cc.v
202 lines · 181 sloc · 6.59 KB · 0009fad346732559f90b541a62c1a2007b4765d9
Raw
1module cbuilder
2
3import os
4import time
5import v.util
6import v.builder
7import sync.pool
8import v.gen.c
9
10const cc_compiler = os.getenv_opt('CC') or { 'cc' }
11const cc_ldflags = os.getenv_opt('LDFLAGS') or { '' }
12const cc_cflags = os.getenv_opt('CFLAGS') or { '' }
13const cc_cflags_opt = os.getenv_opt('CFLAGS_OPT') or { '' } // '-O3' }
14
15fn 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
22fn 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
31fn 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
183fn 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
193fn 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
200fn eprint_time(sw time.StopWatch, label string) {
201 eprintln('> ${sw.elapsed().milliseconds():5} ms, ${label}')
202}
203