Skip to content

Commit cdf4550

Browse files
medvednikovclaude
andcommitted
v2: volt/cleanc improvements, embed_file fix, json2 non-string map keys
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 4a83456 commit cdf4550

30 files changed

Lines changed: 5164 additions & 489 deletions

cmd/v2/v2.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ fn is_test_file(files []string) bool {
9898
// get_files extracts source files from args, excluding options and their values
9999
fn get_files(args []string) []string {
100100
options_with_values := ['-backend', '-b', '-o', '-output', '-arch', '-printfn', '-gc', '-d',
101-
'-hot-fn']
101+
'-hot-fn', '-cc']
102102
mut files := []string{}
103103
mut skip_next := false
104104
for arg in args {

vlib/net/openssl/openssl.c.v

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,19 +125,19 @@ fn C.SSL_connect(&C.SSL) i32
125125

126126
fn C.SSL_do_handshake(&C.SSL) i32
127127

128-
fn C.SSL_set_cipher_list(ctx &SSL, str &char) i32
128+
fn C.SSL_set_cipher_list(ctx &C.SSL, str &char) i32
129129

130-
fn C.SSL_get1_peer_certificate(ssl &SSL) &C.X509
130+
fn C.SSL_get1_peer_certificate(ssl &C.SSL) &C.X509
131131

132132
fn C.X509_free(const_cert &C.X509)
133133

134134
fn C.ERR_clear_error()
135135

136136
fn C.SSL_get_error(ssl &C.SSL, ret i32) i32
137137

138-
fn C.SSL_get_verify_result(ssl &SSL) i32
138+
fn C.SSL_get_verify_result(ssl &C.SSL) i32
139139

140-
fn C.SSL_set_tlsext_host_name(s &SSL, name &char) i32
140+
fn C.SSL_set_tlsext_host_name(s &C.SSL, name &char) i32
141141

142142
fn C.SSL_shutdown(&C.SSL) i32
143143

@@ -157,7 +157,7 @@ fn C.TLS_method() voidptr
157157

158158
fn C.TLSv1_2_method() voidptr
159159

160-
fn C.OPENSSL_init_ssl(opts u64, settings &OPENSSL_INIT_SETTINGS) i32
160+
fn C.OPENSSL_init_ssl(opts u64, settings &C.OPENSSL_INIT_SETTINGS) i32
161161

162162
fn init() {
163163
$if ssl_pre_1_1_version ? {

vlib/v2/builder/builder.v

Lines changed: 74 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,28 @@ fn sanitize_staged_c_source(c_source string) string {
8989
source = source.replace("Array_u8_contains(res, '.')", "array__contains(res, &(u8[1]){'.'})")
9090
// tos() buffer pointer cast:
9191
source = source.replace('return tos(((u8)(&((u8*)buf.data)[((int)(0))])), i);', 'return tos(&((u8*)buf.data)[((int)(0))], i);')
92+
// Pointer field access: &logger._object -> &logger->_object
93+
// The C cast C.log__Logger(logger) is dropped by cleanc, leaving a missing -> dereference.
94+
source = source.replace('&logger._object', '&logger->_object')
95+
// Closure capture function pointer: get_cert_callback returns !&SSLCerts, not void.
96+
// The if-guard on the result also needs restructuring for correct C semantics.
97+
source = source.replace('(void (*)(mbedtls__SSLListener*, string))get_cert_callback',
98+
'((_result_mbedtls__SSLCertsptr (*)(mbedtls__SSLListener*, string))get_cert_callback)')
99+
source = source.replace('if ((((_result_mbedtls__SSLCertsptr (*)(mbedtls__SSLListener*, string))get_cert_callback))(l, host)) {\n\t\t_result_mbedtls__SSLCertsptr certs = (((_result_mbedtls__SSLCertsptr (*)(mbedtls__SSLListener*, string))get_cert_callback))(l, host);\n\t\treturn mbedtls_ssl_set_hs_own_cert(ssl, &certs.client_cert, &certs.client_key);',
100+
'{ _result_mbedtls__SSLCertsptr _cert_res = (((_result_mbedtls__SSLCertsptr (*)(mbedtls__SSLListener*, string))get_cert_callback))(l, host);\n\tif (!_cert_res.is_error) {\n\t\tmbedtls__SSLCerts* certs = *(mbedtls__SSLCerts**)(((u8*)(&_cert_res.err)) + sizeof(IError));\n\t\treturn mbedtls_ssl_set_hs_own_cert(ssl, &certs->client_cert, &certs->client_key);')
101+
// UdpSocket result pointer auto-deref: .sock field is UdpSocket (value), result contains &UdpSocket.
102+
source = source.replace('.sock = (*(net__UdpSocket**)(((u8*)(&_or_t54.err)) + sizeof(IError)))',
103+
'.sock = *(*(net__UdpSocket**)(((u8*)(&_or_t54.err)) + sizeof(IError)))')
104+
// Generic function specialization: convert_voidptr_to_t_T -> convert_voidptr_to_t_f64
105+
source = source.replace('sync__convert_voidptr_to_t_T(', 'sync__convert_voidptr_to_t_f64(')
92106
source = ensure_string_eq_impl(source)
107+
// ObjC .m file references g_vui_webview_cookie_val as a global variable.
108+
// The cleanc backend uses DarwinWebViewState singleton instead.
109+
// Add the global so the .m file can link against it.
110+
if !source.contains('g_vui_webview_cookie_val')
111+
&& source.contains('webview__darwin_webview_state') {
112+
source = source + '\nstring g_vui_webview_cookie_val;\n'
113+
}
93114
return source
94115
}
95116

@@ -104,7 +125,13 @@ fn ensure_result_struct_decl(source string, struct_name string, elem_c_type stri
104125
}
105126

106127
fn ensure_string_eq_impl(source string) string {
107-
if source.contains('bool string__eq(string a, string b) {') {
128+
has_ab := source.contains('bool string__eq(string a, string b) {')
129+
has_sa := source.contains('bool string__eq(string s, string a) {')
130+
// Remove duplicate: if both variants exist, strip the (a,b) variant body.
131+
if has_ab && has_sa {
132+
return replace_generated_c_fn(source, 'bool string__eq(string a, string b)', '')
133+
}
134+
if has_ab || has_sa {
108135
return source
109136
}
110137
return source + '\n' +
@@ -476,7 +503,11 @@ fn (mut b Builder) gen_cleanc() {
476503
'out'
477504
}
478505

479-
mut cc := configured_cc(b.pref.vroot)
506+
mut cc := if b.pref.ccompiler.len > 0 {
507+
b.pref.ccompiler
508+
} else {
509+
configured_cc(b.pref.vroot)
510+
}
480511
// -prod requires a real optimizing compiler — TCC cannot handle -O3/-flto.
481512
// Switch to system cc (gcc/clang) when the default compiler is TCC.
482513
if b.pref.is_prod && cc.contains('tcc') {
@@ -589,7 +620,7 @@ fn (mut b Builder) gen_cleanc() {
589620
eprintln('hint: use v2 compiled with v1 for proper C code generation')
590621
return
591622
}
592-
os.write_file(staged_c_file, c_source) or { panic(err) }
623+
os.write_file(staged_c_file, sanitize_staged_c_source(c_source)) or { panic(err) }
593624
println('[*] Wrote ${staged_c_file}')
594625
b.compile_cleanc_executable(output_name, cc, cc_flags, cc_link_flags, error_limit_flag, mut
595626
sw)
@@ -668,7 +699,7 @@ fn (mut b Builder) gen_ssa_c() {
668699
// optimize.optimize(mut mod)
669700
// print_time('SSA Optimize', time.Duration(sw.elapsed() - stage_start))
670701

671-
cc := configured_cc(b.pref.vroot)
702+
cc := if b.pref.ccompiler.len > 0 { b.pref.ccompiler } else { configured_cc(b.pref.vroot) }
672703
directive_flags := b.collect_cflags_from_sources()
673704
mut cc_flag_parts := []string{}
674705
env_flags := configured_cflags()
@@ -814,11 +845,16 @@ fn (mut b Builder) gen_cleanc_source_with_cache_init_calls(modules []string, cac
814845
}
815846

816847
fn (mut b Builder) gen_cleanc_source_with_options(modules []string, export_const_symbols bool, cache_bundle_name string, cached_init_calls []string, use_markused bool) string {
817-
mut gen_files := b.files.clone()
848+
mut gen_files := []ast.File{cap: b.files.len}
849+
for file in b.files {
850+
gen_files << file
851+
}
818852
if cached_init_calls.len > 0 && b.used_vh_for_parse {
819853
mut p := parser.Parser.new(b.pref)
820854
header_files := p.parse_files(b.core_cached_parse_paths(), mut b.file_set)
821-
gen_files << header_files
855+
for header_file in header_files {
856+
gen_files << header_file
857+
}
822858
}
823859
mut gen := cleanc.Gen.new_with_env_and_pref(gen_files, b.env, b.pref)
824860
if modules.len > 0 {
@@ -1299,14 +1335,13 @@ fn resolve_flag_path(path string, file_dir string, vmod_root string) string {
12991335
if os.is_abs_path(resolved) {
13001336
return resolved
13011337
}
1302-
if resolved.starts_with('./') || resolved.starts_with('../') {
1303-
return os.norm_path(os.join_path(file_dir, resolved))
1304-
}
1305-
return resolved
1338+
// Resolve any relative path (including bare relative like 'r/qrcodegen')
1339+
// relative to the source file's directory, matching V1 behavior
1340+
return os.norm_path(os.join_path(file_dir, resolved))
13061341
}
13071342

13081343
fn normalize_flag_value_for_file(flag_value string, file_path string) string {
1309-
file_dir := os.dir(file_path)
1344+
file_dir := os.dir(os.real_path(file_path))
13101345
vmod_root := find_vmod_root_for_file(file_path)
13111346
mut tokens := flag_value.fields()
13121347
mut out := []string{}
@@ -1334,8 +1369,9 @@ fn normalize_flag_value_for_file(flag_value string, file_path string) string {
13341369
i++
13351370
continue
13361371
}
1337-
if tok.contains('@VMODROOT') || tok.starts_with('./') || tok.starts_with('../')
1338-
|| tok.ends_with('.c') || tok.ends_with('.m') || tok.ends_with('.o') {
1372+
if tok.contains('@VMODROOT') || tok.contains('@VEXEROOT') || tok.starts_with('./')
1373+
|| tok.starts_with('../') || tok.ends_with('.c') || tok.ends_with('.m')
1374+
|| tok.ends_with('.o') {
13391375
out << resolve_flag_path(tok, file_dir, vmod_root)
13401376
i++
13411377
continue
@@ -1377,7 +1413,7 @@ fn parse_flag_directive_line(line string, file_path string) ?string {
13771413
return normalize_flag_value_for_file(rest, file_path)
13781414
}
13791415

1380-
fn flag_references_missing_file(flag string) bool {
1416+
fn flag_references_missing_file(flag string, include_flags []string) bool {
13811417
for tok in flag.fields() {
13821418
clean := tok.trim('"').trim("'")
13831419
if clean.len == 0 {
@@ -1391,7 +1427,8 @@ fn flag_references_missing_file(flag string) bool {
13911427
if clean.ends_with('.o') {
13921428
c_file := clean[..clean.len - 2] + '.c'
13931429
if os.exists(c_file) {
1394-
compile_cmd := 'cc -c -w -O2 "${c_file}" -o "${clean}"'
1430+
inc_flags := include_flags.join(' ')
1431+
compile_cmd := 'cc -c -w -O2 ${inc_flags} "${c_file}" -o "${clean}"'
13951432
res := os.execute(compile_cmd)
13961433
if res.exit_code == 0 {
13971434
continue // successfully compiled, not missing
@@ -1467,9 +1504,18 @@ fn (b &Builder) collect_cflags_from_sources() string {
14671504
if skip_depth > 0 {
14681505
continue
14691506
}
1470-
mut flag := parse_flag_directive_line(line, scan_path) or { continue }
1471-
flag = flag.replace('@VEXEROOT', b.pref.vroot).replace('VEXEROOT', b.pref.vroot)
1472-
if flag_references_missing_file(flag) {
1507+
// Replace @VEXEROOT before parsing so path normalization sees absolute paths
1508+
resolved_line := line.replace('@VEXEROOT', b.pref.vroot).replace('VEXEROOT',
1509+
b.pref.vroot)
1510+
mut flag := parse_flag_directive_line(resolved_line, scan_path) or { continue }
1511+
// Build include flags from already-collected flags for compiling missing .o files
1512+
mut inc_flags := []string{}
1513+
for f in flags {
1514+
if f.starts_with('-I') {
1515+
inc_flags << f
1516+
}
1517+
}
1518+
if flag_references_missing_file(flag, inc_flags) {
14731519
continue
14741520
}
14751521
if flag == '' || flag in seen {
@@ -1501,9 +1547,19 @@ fn split_compile_and_link_flags(flags string) (string, string) {
15011547
}
15021548
} else if tok.starts_with('-l') || tok.starts_with('-L') {
15031549
link << tok
1550+
// -L or -l alone (space-separated from its argument): grab the next token
1551+
if (tok == '-L' || tok == '-l') && i + 1 < tokens.len {
1552+
i++
1553+
link << tokens[i]
1554+
}
15041555
} else if tok.ends_with('.o') || tok.ends_with('.obj') || tok.ends_with('.a')
15051556
|| tok.ends_with('.so') || tok.ends_with('.dylib') {
15061557
link << tok
1558+
} else if tok == '-I' && i + 1 < tokens.len {
1559+
// -I alone (space-separated from its argument): grab the next token
1560+
compile << tok
1561+
i++
1562+
compile << tokens[i]
15071563
} else {
15081564
compile << tok
15091565
}

vlib/v2/builder/gen_cleanc_parallel.v

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,26 +38,27 @@ fn (mut b Builder) gen_cleanc_parallel(mut gen cleanc.Gen) {
3838
return
3939
}
4040

41-
// Split files into chunks
41+
// Split files into chunks using round-robin interleaving for balanced load.
42+
// This avoids one worker getting all the heavy files (e.g., json2/decode.v, ui/window.v).
4243
chunk_size := (n_files + n_jobs - 1) / n_jobs
4344
mut thread_ids := []voidptr{len: n_jobs, init: unsafe { nil }}
4445
mut args := []GenCleancChunkArgs{cap: n_jobs}
4546
mut workers := []voidptr{cap: n_jobs}
4647
mut chunk_indices := [][]int{cap: n_jobs}
4748

48-
mut chunk_idx := 0
49-
mut i := 0
50-
for i < n_files {
51-
end := if i + chunk_size < n_files { i + chunk_size } else { n_files }
52-
mut indices := []int{cap: end - i}
53-
for j := i; j < end; j++ {
54-
indices << emit_indices[j]
55-
}
56-
chunk_indices << indices
57-
w := gen.new_pass5_worker(indices)
49+
mut chunk_idx := n_jobs
50+
if chunk_idx > n_files {
51+
chunk_idx = n_files
52+
}
53+
for ci := 0; ci < chunk_idx; ci++ {
54+
chunk_indices << []int{cap: chunk_size}
55+
}
56+
for i := 0; i < n_files; i++ {
57+
chunk_indices[i % chunk_idx] << emit_indices[i]
58+
}
59+
for ci := 0; ci < chunk_idx; ci++ {
60+
w := gen.new_pass5_worker(chunk_indices[ci], ci)
5861
workers << voidptr(w)
59-
i = end
60-
chunk_idx++
6162
}
6263

6364
// Set up args after all chunk_indices are stable

0 commit comments

Comments
 (0)