v2 / vlib / v / builder / cbuilder / parallel_cc.v
175 lines · 155 sloc · 5.54 KB · dc00f0d65a2ee7049d386d7c3e98e540dbc243b9
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(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
156fn 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
166fn 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
173fn eprint_time(sw time.StopWatch, label string) {
174 eprintln('> ${sw.elapsed().milliseconds():5} ms, ${label}')
175}
176