v / vlib / v2 / gen / cleanc / consts_and_globals.v
1342 lines · 1290 sloc · 37.27 KB · 164b30309e6337b95ec2baacacd0f101fafd3d97
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4
5module cleanc
6
7import v2.ast
8import v2.types
9
10// set_export_const_symbols controls whether emitted module const macros
11// also get exported as linkable global symbols.
12pub fn (mut g Gen) set_export_const_symbols(enable bool) {
13 g.export_const_symbols = enable
14}
15
16// set_cache_bundle_name sets the cache bundle label used for emitting a
17// deterministic cache-init function (e.g. __v2_cached_init_builtin).
18pub fn (mut g Gen) set_cache_bundle_name(name string) {
19 g.cache_bundle_name = name.trim_space()
20}
21
22fn (mut g Gen) gen_file_extern_globals(file ast.File) {
23 g.set_file_module(file)
24 for stmt in file.stmts {
25 if !stmt_has_valid_data(stmt) {
26 continue
27 }
28 if stmt is ast.GlobalDecl {
29 g.gen_global_decl_extern(stmt)
30 }
31 }
32}
33
34fn (mut g Gen) gen_file_extern_consts(file ast.File) {
35 g.set_file_module(file)
36 for stmt in file.stmts {
37 if !stmt_has_valid_data(stmt) {
38 continue
39 }
40 if stmt is ast.ConstDecl {
41 g.gen_const_decl_extern(stmt)
42 }
43 }
44}
45
46fn runtime_const_target_key(mod string, name string) string {
47 return '${mod}::${name}'
48}
49
50fn generated_decl_name_for_module(mod string, field_name string) ?string {
51 if field_name == '' || field_name.starts_with('C.') {
52 return none
53 }
54 if mod != '' && mod != 'main' && mod != 'builtin' {
55 return '${mod}__${field_name}'
56 }
57 return field_name
58}
59
60fn (g &Gen) generated_decl_name(field_name string) ?string {
61 return generated_decl_name_for_module(g.cur_module, field_name)
62}
63
64fn (mut g Gen) const_storage_name(name string) string {
65 if cname := g.const_c_names[name] {
66 return cname
67 }
68 if name in g.declared_fn_names {
69 cname := '__v_const_${name}'
70 g.const_c_names[name] = cname
71 return cname
72 }
73 return name
74}
75
76fn (g &Gen) renamed_const_name_for_ident(name string) ?string {
77 if generated_name := generated_decl_name_for_module(g.cur_module, name) {
78 if cname := g.const_c_names[generated_name] {
79 return cname
80 }
81 }
82 if cname := g.const_c_names[name] {
83 return cname
84 }
85 return none
86}
87
88fn (g &Gen) is_scalar_zero_init_type(typ string) bool {
89 if typ in primitive_types || typ.ends_with('*') || typ in ['byteptr', 'charptr', 'voidptr'] {
90 return true
91 }
92 return g.is_enum_type(typ)
93}
94
95fn (g &Gen) global_initializer_needs_runtime(expr ast.Expr) bool {
96 match expr {
97 ast.EmptyExpr {
98 return false
99 }
100 ast.InitExpr, ast.MapInitExpr, ast.IfExpr, ast.CallOrCastExpr {
101 return true
102 }
103 ast.OrExpr, ast.StringInterLiteral, ast.UnsafeExpr, ast.LockExpr, ast.MatchExpr {
104 return true
105 }
106 ast.ComptimeExpr {
107 return g.global_initializer_needs_runtime(expr.expr)
108 }
109 ast.ArrayInitExpr {
110 if expr.typ is ast.Type && expr.typ is ast.ArrayFixedType {
111 return g.contains_call_expr(expr)
112 }
113 return true
114 }
115 ast.CastExpr {
116 return g.global_initializer_needs_runtime(expr.expr)
117 }
118 ast.ParenExpr {
119 return g.global_initializer_needs_runtime(expr.expr)
120 }
121 ast.PrefixExpr {
122 return g.global_initializer_needs_runtime(expr.expr)
123 }
124 ast.PostfixExpr {
125 return g.global_initializer_needs_runtime(expr.expr)
126 }
127 ast.ModifierExpr {
128 return g.global_initializer_needs_runtime(expr.expr)
129 }
130 ast.IndexExpr {
131 return g.global_initializer_needs_runtime(expr.lhs)
132 || g.global_initializer_needs_runtime(expr.expr)
133 }
134 ast.InfixExpr {
135 return g.global_initializer_needs_runtime(expr.lhs)
136 || g.global_initializer_needs_runtime(expr.rhs)
137 }
138 else {
139 return g.contains_call_expr(expr)
140 }
141 }
142}
143
144fn (g &Gen) const_initializer_needs_runtime(expr ast.Expr) bool {
145 match expr {
146 ast.OrExpr, ast.StringInterLiteral, ast.UnsafeExpr, ast.IfExpr {
147 return true
148 }
149 ast.ComptimeExpr {
150 return g.const_initializer_needs_runtime(expr.expr)
151 }
152 ast.CastExpr {
153 return g.const_initializer_needs_runtime(expr.expr)
154 }
155 ast.ParenExpr {
156 return g.const_initializer_needs_runtime(expr.expr)
157 }
158 ast.PrefixExpr {
159 return g.const_initializer_needs_runtime(expr.expr)
160 }
161 ast.PostfixExpr {
162 return g.const_initializer_needs_runtime(expr.expr)
163 }
164 ast.ModifierExpr {
165 return g.const_initializer_needs_runtime(expr.expr)
166 }
167 ast.IndexExpr {
168 return g.const_initializer_needs_runtime(expr.lhs)
169 || g.const_initializer_needs_runtime(expr.expr)
170 }
171 ast.InfixExpr {
172 return g.const_initializer_needs_runtime(expr.lhs)
173 || g.const_initializer_needs_runtime(expr.rhs)
174 }
175 ast.ArrayInitExpr {
176 for elem in expr.exprs {
177 if g.const_initializer_needs_runtime(elem) {
178 return true
179 }
180 }
181 return (expr.init !is ast.EmptyExpr && g.const_initializer_needs_runtime(expr.init))
182 || (expr.len !is ast.EmptyExpr && g.const_initializer_needs_runtime(expr.len))
183 || (expr.cap !is ast.EmptyExpr && g.const_initializer_needs_runtime(expr.cap))
184 }
185 else {
186 return g.contains_call_expr(expr)
187 }
188 }
189}
190
191fn (mut g Gen) collect_runtime_const_targets() {
192 g.runtime_const_targets = map[string]bool{}
193 mut module_consts := map[string]map[string]bool{}
194 if g.has_flat() {
195 for i in 0 .. g.flat.files.len {
196 fc := g.flat.file_cursor(i)
197 module_name := flat_file_module_name(fc)
198 mut const_names := if module_name in module_consts {
199 module_consts[module_name].clone()
200 } else {
201 map[string]bool{}
202 }
203 stmts := fc.stmts()
204 for j in 0 .. stmts.len() {
205 stmt := stmts.at(j)
206 if stmt.kind() != .stmt_const_decl {
207 continue
208 }
209 const_decl := stmt.const_decl()
210 for field in const_decl.fields {
211 if field.name != '' && !field.name.starts_with('C.') {
212 const_names[field.name] = true
213 }
214 }
215 }
216 module_consts[module_name] = const_names.clone()
217 }
218 for i in 0 .. g.flat.files.len {
219 fc := g.flat.file_cursor(i)
220 g.set_file_cursor_module(fc)
221 const_names := if g.cur_module in module_consts {
222 module_consts[g.cur_module].clone()
223 } else {
224 map[string]bool{}
225 }
226 if const_names.len == 0 {
227 continue
228 }
229 stmts := fc.stmts()
230 for j in 0 .. stmts.len() {
231 stmt := stmts.at(j)
232 if stmt.kind() != .stmt_fn_decl || !stmt.name().starts_with('__v_init_consts_') {
233 continue
234 }
235 body := stmt.list_at(3)
236 for k in 0 .. body.len() {
237 body_stmt := body.at(k)
238 if body_stmt.kind() != .stmt_assign {
239 continue
240 }
241 if body_stmt.extra_int() != 1 {
242 continue
243 }
244 lhs := body_stmt.edge(0)
245 if lhs.kind() != .expr_ident {
246 continue
247 }
248 if lhs.name() !in const_names {
249 continue
250 }
251 g.runtime_const_targets[runtime_const_target_key(g.cur_module, lhs.name())] = true
252 }
253 }
254 }
255 } else {
256 for file in g.files {
257 mut const_names := if file.mod in module_consts {
258 module_consts[file.mod].clone()
259 } else {
260 map[string]bool{}
261 }
262 for stmt in file.stmts {
263 if stmt !is ast.ConstDecl {
264 continue
265 }
266 const_decl := stmt as ast.ConstDecl
267 for field in const_decl.fields {
268 if field.name != '' && !field.name.starts_with('C.') {
269 const_names[field.name] = true
270 }
271 }
272 }
273 module_consts[file.mod] = const_names.clone()
274 }
275 for file in g.files {
276 g.set_file_module(file)
277 const_names := if g.cur_module in module_consts {
278 module_consts[g.cur_module].clone()
279 } else {
280 map[string]bool{}
281 }
282 if const_names.len == 0 {
283 continue
284 }
285 for stmt in file.stmts {
286 if stmt !is ast.FnDecl {
287 continue
288 }
289 fn_decl := stmt as ast.FnDecl
290 if !fn_decl.name.starts_with('__v_init_consts_') {
291 continue
292 }
293 for body_stmt in fn_decl.stmts {
294 if body_stmt !is ast.AssignStmt {
295 continue
296 }
297 assign_stmt := body_stmt as ast.AssignStmt
298 if assign_stmt.lhs.len != 1 {
299 continue
300 }
301 lhs_expr := assign_stmt.lhs[0]
302 if lhs_expr !is ast.Ident {
303 continue
304 }
305 lhs_ident := lhs_expr as ast.Ident
306 if lhs_ident.name !in const_names {
307 continue
308 }
309
310 g.runtime_const_targets[runtime_const_target_key(g.cur_module, lhs_ident.name)] = true
311 }
312 }
313 }
314 }
315}
316
317fn (g &Gen) is_runtime_const_target(field_name string) bool {
318 return runtime_const_target_key(g.cur_module, field_name) in g.runtime_const_targets
319}
320
321fn (mut g Gen) lookup_const_expr(mod string, name string) ?ast.Expr {
322 if g.has_flat() {
323 for i in 0 .. g.flat.files.len {
324 fc := g.flat.file_cursor(i)
325 if flat_file_module_name(fc) != mod {
326 continue
327 }
328 stmts := fc.stmts()
329 for j in 0 .. stmts.len() {
330 stmt := stmts.at(j)
331 if stmt.kind() != .stmt_const_decl {
332 continue
333 }
334 const_decl := stmt.const_decl()
335 for field in const_decl.fields {
336 if field.name == name {
337 return field.value
338 }
339 }
340 }
341 }
342 return none
343 }
344 for file in g.files {
345 if file.mod != mod {
346 continue
347 }
348 for stmt in file.stmts {
349 if stmt !is ast.ConstDecl {
350 continue
351 }
352 const_decl := stmt as ast.ConstDecl
353 for field in const_decl.fields {
354 if field.name == name {
355 return field.value
356 }
357 }
358 }
359 }
360 return none
361}
362
363fn (mut g Gen) lookup_generated_const_expr(generated_name string) ?ast.Expr {
364 if g.has_flat() {
365 for i in 0 .. g.flat.files.len {
366 fc := g.flat.file_cursor(i)
367 module_name := flat_file_module_name(fc)
368 stmts := fc.stmts()
369 for j in 0 .. stmts.len() {
370 stmt := stmts.at(j)
371 if stmt.kind() != .stmt_const_decl {
372 continue
373 }
374 const_decl := stmt.const_decl()
375 for field in const_decl.fields {
376 name := generated_decl_name_for_module(module_name, field.name) or { continue }
377 if name == generated_name {
378 return field.value
379 }
380 }
381 }
382 }
383 return none
384 }
385 for file in g.files {
386 for stmt in file.stmts {
387 if stmt !is ast.ConstDecl {
388 continue
389 }
390 const_decl := stmt as ast.ConstDecl
391 for field in const_decl.fields {
392 name := generated_decl_name_for_module(file.mod, field.name) or { continue }
393 if name == generated_name {
394 return field.value
395 }
396 }
397 }
398 }
399 return none
400}
401
402fn (mut g Gen) enum_const_selector_type(expr ast.Expr) string {
403 if expr !is ast.SelectorExpr {
404 return ''
405 }
406 sel := expr as ast.SelectorExpr
407 if raw_type := g.get_raw_type(sel) {
408 match raw_type {
409 types.Enum {
410 enum_type := g.types_type_to_c(raw_type)
411 if enum_type != '' {
412 return enum_type
413 }
414 }
415 types.Alias {
416 if raw_type.base_type is types.Enum {
417 enum_type := g.types_type_to_c(raw_type)
418 if enum_type != '' {
419 return enum_type
420 }
421 }
422 }
423 else {}
424 }
425 }
426 lhs_type := g.get_expr_type(sel.lhs)
427 if lhs_type != '' && g.is_enum_type(lhs_type) && g.enum_has_field(lhs_type, sel.rhs.name) {
428 return lhs_type
429 }
430 return ''
431}
432
433fn (mut g Gen) const_decl_storage_type(expr ast.Expr) string {
434 if expr is ast.Ident {
435 if const_expr := g.lookup_const_expr(g.cur_module, expr.name) {
436 return g.const_decl_storage_type(const_expr)
437 }
438 if g.cur_module != 'builtin' {
439 if const_expr := g.lookup_const_expr('builtin', expr.name) {
440 return g.const_decl_storage_type(const_expr)
441 }
442 }
443 if const_expr := g.lookup_generated_const_expr(expr.name) {
444 return g.const_decl_storage_type(const_expr)
445 }
446 }
447 if expr is ast.SelectorExpr && expr.lhs is ast.Ident {
448 if const_expr := g.lookup_const_expr(expr.lhs.name, expr.rhs.name) {
449 return g.const_decl_storage_type(const_expr)
450 }
451 }
452 if expr is ast.CallExpr && expr.lhs is ast.Ident {
453 fn_name := (expr.lhs as ast.Ident).name
454 if fn_name in ['builtin__new_array_from_c_array_noscan', 'builtin__new_array_from_c_array',
455 'new_array_from_c_array'] {
456 return 'array'
457 }
458 }
459 enum_type := g.enum_const_selector_type(expr)
460 if enum_type != '' {
461 return enum_type
462 }
463 if expr is ast.InfixExpr {
464 if expr.op in [.eq, .ne, .lt, .gt, .le, .ge, .and, .logical_or] {
465 return 'bool'
466 }
467 if expr.op in [.plus, .minus, .mul, .div, .mod] {
468 lhs_type := g.const_decl_storage_type(expr.lhs)
469 rhs_type := g.const_decl_storage_type(expr.rhs)
470 if expr.op == .plus && (lhs_type == 'string' || rhs_type == 'string') {
471 return 'string'
472 }
473 return promote_numeric_c_types(lhs_type, rhs_type)
474 }
475 }
476 if raw_type := g.get_raw_type(expr) {
477 raw_c_type := g.types_type_to_c(raw_type)
478 if raw_c_type != '' && raw_c_type != 'void' && !raw_c_type.contains('literal') {
479 return raw_c_type
480 }
481 }
482 mut typ := g.get_expr_type(expr)
483 if typ == '' || typ == 'void' || typ == 'int_literal' || typ == 'float_literal' {
484 if expr is ast.InfixExpr {
485 numeric_type := g.infer_numeric_expr_type(expr)
486 if numeric_type != '' && !numeric_type.contains('literal') {
487 return numeric_type
488 }
489 }
490 if expr is ast.BasicLiteral {
491 if expr.kind == .number {
492 if expr.value.contains('.') || expr.value.contains('e') || expr.value.contains('E') {
493 return 'f64'
494 }
495 return 'int'
496 }
497 }
498 if typ == 'float_literal' {
499 return 'f64'
500 }
501 return 'int'
502 }
503 if expr is ast.InfixExpr && typ == 'string' {
504 numeric_type := g.infer_numeric_expr_type(expr)
505 if numeric_type != '' && !numeric_type.contains('literal') && numeric_type != 'string' {
506 return numeric_type
507 }
508 }
509 return typ
510}
511
512fn (mut g Gen) const_expr_c_value_for_header(expr ast.Expr) ?string {
513 return g.const_expr_c_value_for_header_in_module(expr, g.cur_module, 0)
514}
515
516fn (mut g Gen) resolved_scalar_const_expr(expr ast.Expr, rendered string) string {
517 if expr_contains_ident(expr) {
518 if value := g.const_expr_c_value_for_header(expr) {
519 return value
520 }
521 return g.resolve_const_expr(rendered)
522 }
523 return rendered
524}
525
526fn (mut g Gen) const_expr_c_value_for_header_in_module(expr ast.Expr, mod string, depth int) ?string {
527 if depth > 20 {
528 return none
529 }
530 match expr {
531 ast.BasicLiteral {
532 if expr.kind == .number {
533 return sanitize_c_number_literal(expr.value)
534 }
535 }
536 ast.Ident {
537 if const_expr := g.lookup_const_expr(mod, expr.name) {
538 return g.const_expr_c_value_for_header_in_module(const_expr, mod, depth + 1)
539 }
540 if mod != 'builtin' {
541 if const_expr := g.lookup_const_expr('builtin', expr.name) {
542 return g.const_expr_c_value_for_header_in_module(const_expr, 'builtin', depth +
543 1)
544 }
545 }
546 }
547 ast.SelectorExpr {
548 if expr.lhs is ast.Ident {
549 lhs_ident := expr.lhs as ast.Ident
550 mut module_names := [lhs_ident.name]
551 resolved_mod_name := g.resolve_module_name(lhs_ident.name)
552 if resolved_mod_name !in module_names {
553 module_names << resolved_mod_name
554 }
555 for mod_name in module_names {
556 if const_expr := g.lookup_const_expr(mod_name, expr.rhs.name) {
557 return g.const_expr_c_value_for_header_in_module(const_expr, mod_name,
558
559 depth + 1)
560 }
561 }
562 }
563 }
564 ast.InfixExpr {
565 lhs := g.const_expr_c_value_for_header_in_module(expr.lhs, mod, depth + 1) or {
566 return none
567 }
568 rhs := g.const_expr_c_value_for_header_in_module(expr.rhs, mod, depth + 1) or {
569 return none
570 }
571 op := match expr.op {
572 .plus { '+' }
573 .minus { '-' }
574 .mul { '*' }
575 .div { '/' }
576 .mod { '%' }
577 .amp { '&' }
578 .pipe { '|' }
579 .xor { '^' }
580 .left_shift { '<<' }
581 .right_shift { '>>' }
582 else { return none }
583 }
584
585 return '(${lhs} ${op} ${rhs})'
586 }
587 ast.PrefixExpr {
588 value := g.const_expr_c_value_for_header_in_module(expr.expr, mod, depth + 1) or {
589 return none
590 }
591 op := match expr.op {
592 .minus { '-' }
593 .plus { '+' }
594 .bit_not { '~' }
595 else { return none }
596 }
597
598 return '${op}${value}'
599 }
600 ast.ParenExpr {
601 value := g.const_expr_c_value_for_header_in_module(expr.expr, mod, depth + 1) or {
602 return none
603 }
604 return '(${value})'
605 }
606 ast.CastExpr {
607 value := g.const_expr_c_value_for_header_in_module(expr.expr, mod, depth + 1) or {
608 return none
609 }
610 typ := g.expr_type_to_c(expr.typ)
611 if typ == '' || typ == 'void' {
612 return value
613 }
614 return '((${typ})(${value}))'
615 }
616 ast.CallOrCastExpr {
617 value := g.const_expr_c_value_for_header_in_module(expr.expr, mod, depth + 1) or {
618 return none
619 }
620 if !g.call_or_cast_lhs_is_type(expr.lhs) {
621 return none
622 }
623 typ := g.expr_type_to_c(expr.lhs)
624 if typ == '' || typ == 'void' {
625 return value
626 }
627 return '((${typ})(${value}))'
628 }
629 ast.CallExpr {
630 if !g.call_or_cast_lhs_is_type(expr.lhs) || expr.args.len != 1 {
631 return none
632 }
633 value := g.const_expr_c_value_for_header_in_module(expr.args[0], mod, depth + 1) or {
634 return none
635 }
636 typ := g.expr_type_to_c(expr.lhs)
637 if typ == '' || typ == 'void' {
638 return value
639 }
640 return '((${typ})(${value}))'
641 }
642 else {}
643 }
644
645 return none
646}
647
648fn (mut g Gen) emit_runtime_const_storage_decl(name string, typ string) {
649 if typ == 'string' {
650 g.sb.writeln('string ${name} = {0};')
651 return
652 }
653 if g.is_scalar_zero_init_type(typ) {
654 g.sb.writeln('${typ} ${name} = 0;')
655 return
656 }
657 g.sb.writeln('${typ} ${name} = {0};')
658}
659
660fn (g &Gen) is_generated_non_type_ident(expr ast.Expr) bool {
661 if expr !is ast.Ident {
662 return false
663 }
664 value_ident := expr as ast.Ident
665 return value_ident.name.contains('__') && !g.is_type_name(value_ident.name)
666}
667
668fn (mut g Gen) is_type_only_const_expr(expr ast.Expr) bool {
669 if is_header_type_only_const_expr(expr) {
670 return true
671 }
672 if expr is ast.Ident {
673 if expr.name.contains('.') {
674 return g.is_type_name(g.expr_type_to_c(expr))
675 }
676 return g.is_type_name(expr.name)
677 }
678 if expr is ast.SelectorExpr {
679 return g.is_type_name(g.expr_type_to_c(expr))
680 }
681 return false
682}
683
684struct FileScopeGenContext {
685 cur_fn_scope &types.Scope = unsafe { nil }
686 cur_fn_name string
687 cur_fn_c_name string
688 cur_fn_ret_type string
689 cur_fn_c_ret_type string
690 cur_fn_scope_miss_key string
691 runtime_local_types map[string]string
692 runtime_decl_types map[string]string
693 cur_fn_mut_params map[string]bool
694 cur_fn_returned_idents map[string]bool
695 not_local_var_cache map[string]bool
696}
697
698fn (mut g Gen) enter_file_scope_context() FileScopeGenContext {
699 ctx := FileScopeGenContext{
700 cur_fn_scope: g.cur_fn_scope
701 cur_fn_name: g.cur_fn_name
702 cur_fn_c_name: g.cur_fn_c_name
703 cur_fn_ret_type: g.cur_fn_ret_type
704 cur_fn_c_ret_type: g.cur_fn_c_ret_type
705 cur_fn_scope_miss_key: g.cur_fn_scope_miss_key
706 runtime_local_types: g.runtime_local_types.clone()
707 runtime_decl_types: g.runtime_decl_types.clone()
708 cur_fn_mut_params: g.cur_fn_mut_params.clone()
709 cur_fn_returned_idents: g.cur_fn_returned_idents.clone()
710 not_local_var_cache: g.not_local_var_cache.clone()
711 }
712 g.cur_fn_scope = unsafe { nil }
713 g.cur_fn_name = ''
714 g.cur_fn_c_name = ''
715 g.cur_fn_ret_type = ''
716 g.cur_fn_c_ret_type = ''
717 g.cur_fn_scope_miss_key = ''
718 g.runtime_local_types.clear()
719 g.runtime_decl_types.clear()
720 g.cur_fn_mut_params.clear()
721 g.cur_fn_returned_idents.clear()
722 g.not_local_var_cache.clear()
723 return ctx
724}
725
726fn (mut g Gen) leave_file_scope_context(ctx FileScopeGenContext) {
727 g.cur_fn_scope = ctx.cur_fn_scope
728 g.cur_fn_name = ctx.cur_fn_name
729 g.cur_fn_c_name = ctx.cur_fn_c_name
730 g.cur_fn_ret_type = ctx.cur_fn_ret_type
731 g.cur_fn_c_ret_type = ctx.cur_fn_c_ret_type
732 g.cur_fn_scope_miss_key = ctx.cur_fn_scope_miss_key
733 g.runtime_local_types = ctx.runtime_local_types.clone()
734 g.runtime_decl_types = ctx.runtime_decl_types.clone()
735 g.cur_fn_mut_params = ctx.cur_fn_mut_params.clone()
736 g.cur_fn_returned_idents = ctx.cur_fn_returned_idents.clone()
737 g.not_local_var_cache = ctx.not_local_var_cache.clone()
738}
739
740fn (mut g Gen) gen_const_decl_extern(node ast.ConstDecl) {
741 file_scope_ctx := g.enter_file_scope_context()
742 defer {
743 g.leave_file_scope_context(file_scope_ctx)
744 }
745 for field in node.fields {
746 name := g.generated_decl_name(field.name) or { continue }
747 c_name := g.const_storage_name(name)
748 // Skip constants already emitted as either extern or #define
749 // (can happen when both .vh and full source files are parsed).
750 extern_key := 'extern_const_${c_name}'
751 macro_key := 'extern_const_macro_${c_name}'
752 if extern_key in g.emitted_types || macro_key in g.emitted_types {
753 continue
754 }
755 mut is_type_only := g.is_type_only_const_expr(field.value)
756 if is_type_only && g.is_generated_non_type_ident(field.value) {
757 is_type_only = false
758 }
759 if g.is_runtime_const_target(field.name) {
760 typ := g.const_decl_storage_type(field.value)
761 if typ == '' || typ == 'void' {
762 continue
763 }
764 g.emitted_types[extern_key] = true
765 g.sb.writeln('extern ${typ} ${c_name};')
766 continue
767 }
768 if is_type_only {
769 key := extern_key
770 typ := g.expr_type_to_c(field.value)
771 if typ == '' || typ == 'void' {
772 continue
773 }
774 if typ.starts_with('Array_fixed_') || typ.contains(' ') || typ.contains('literal')
775 || typ == 'mach_timebase_info_data_t' {
776 continue
777 }
778 g.emitted_types[key] = true
779 g.sb.writeln('extern ${typ} ${c_name};')
780 continue
781 }
782 value_expr := g.expr_to_string(field.value)
783 if value_expr.len == 0 {
784 continue
785 }
786 // Array consts with static backing data or inline compound literals
787 // cannot be #defined across translation units. Emit extern declarations instead.
788 if value_expr.contains('__const_array_data_')
789 || value_expr.contains('new_array_from_c_array') {
790 g.emitted_types[macro_key] = true
791 g.sb.writeln('extern array ${c_name};')
792 continue
793 }
794 mut macro_expr := value_expr
795 if macro_expr.contains('\n') {
796 // Keep macro definitions valid when value expressions are rendered
797 // across multiple lines (e.g. large string literals).
798 macro_expr = macro_expr.replace('\n', ' \\\n')
799 }
800 g.emitted_types[macro_key] = true
801 // Unqualified numeric consts use #define with fully-resolved literal
802 // values and also store into const_exprs so that downstream consts
803 // that reference them can be resolved too. This avoids macro
804 // collisions with local variables of the same name (e.g. max_len
805 // from sorted_map.v vs max_len parameter in eprint_space_padding).
806 if !name.contains('__') && is_numeric_const_expr(field.value) {
807 typ := g.const_decl_storage_type(field.value)
808 if typ != '' && typ != 'void' {
809 resolved := g.resolved_scalar_const_expr(field.value, macro_expr)
810 g.const_exprs[name] = resolved
811 g.sb.writeln('static const ${typ} ${c_name} = ${resolved};')
812 continue
813 }
814 }
815 if !name.contains('__') {
816 typ := g.const_decl_storage_type(field.value)
817 if g.is_scalar_zero_init_type(typ) || typ == 'string' {
818 resolved := g.resolved_scalar_const_expr(field.value, macro_expr)
819 g.sb.writeln('static const ${typ} ${c_name} = ${resolved};')
820 continue
821 }
822 }
823 // C struct zero-init consts are emitted as global variables in
824 // gen_const_decl, so the extern declaration must match.
825 if is_c_struct_init(field.value) {
826 typ := g.const_decl_storage_type(field.value)
827 if typ != '' && typ != 'void' && typ != 'int' {
828 g.sb.writeln('extern ${typ} ${c_name};')
829 continue
830 }
831 }
832 g.sb.writeln('#define ${c_name} ${macro_expr}')
833 }
834}
835
836fn module_storage_c_name(module_name string, name string) string {
837 if name == '' || name.starts_with('C.') {
838 return name
839 }
840 if module_name != '' && module_name != 'main' && module_name != 'builtin' {
841 return '${module_name}__${name}'
842 }
843 return name
844}
845
846fn module_storage_field_is_c_extern(node ast.GlobalDecl, field ast.FieldDecl) bool {
847 return field.name.starts_with('C.') || node.attributes.has('c_extern')
848 || field.attributes.has('c_extern')
849}
850
851fn module_storage_field_c_name(module_name string, node ast.GlobalDecl, field ast.FieldDecl) string {
852 if module_storage_field_is_c_extern(node, field) {
853 return if field.name.starts_with('C.') { field.name.all_after('C.') } else { field.name }
854 }
855 return module_storage_c_name(module_name, field.name)
856}
857
858fn (mut g Gen) gen_global_decl(node ast.GlobalDecl) {
859 for field in node.fields {
860 // Skip C globals that are already provided by C headers or cheaders.
861 if module_storage_field_is_c_extern(node, field) {
862 continue
863 }
864 name := module_storage_field_c_name(g.cur_module, node, field)
865 key := 'global_${name}'
866 if key in g.emitted_types {
867 continue
868 }
869 g.emitted_types[key] = true
870 g.write_indent()
871 if field.typ is ast.Type && field.typ is ast.ArrayFixedType {
872 fixed_typ := field.typ as ast.ArrayFixedType
873 elem_type := g.expr_type_to_c(fixed_typ.elem_type)
874 g.fixed_array_globals[name] = true
875 g.sb.write_string('${elem_type} ${name}[')
876 if len_expr := g.const_expr_c_value_for_header(fixed_typ.len) {
877 g.sb.write_string(len_expr)
878 } else {
879 g.expr(fixed_typ.len)
880 }
881 g.sb.write_string(']')
882 if field.value !is ast.EmptyExpr {
883 g.sb.write_string(' = ')
884 g.expr(field.value)
885 }
886 g.sb.writeln(';')
887 continue
888 }
889 mut typ := ''
890 if field.typ !is ast.EmptyExpr {
891 typ = g.expr_type_to_c(field.typ)
892 } else if field.value !is ast.EmptyExpr {
893 typ = g.get_expr_type(field.value)
894 }
895 if typ == '' || typ == 'void' {
896 typ = 'int'
897 }
898 if typ.starts_with('Array_fixed_') {
899 g.fixed_array_globals[name] = true
900 g.global_var_types[name] = typ
901 g.sb.write_string('${typ} ${name}')
902 if field.value !is ast.EmptyExpr {
903 g.sb.write_string(' = ')
904 if field.value is ast.ArrayInitExpr {
905 array_init := field.value as ast.ArrayInitExpr
906 if array_init.exprs.len == 0 && array_init.init is ast.EmptyExpr {
907 g.sb.write_string('{0}')
908 } else {
909 g.expr(field.value)
910 }
911 } else {
912 g.expr(field.value)
913 }
914 }
915 g.sb.writeln(';')
916 continue
917 }
918 g.global_var_types[name] = typ
919 // With prealloc, g_memory_block must be thread-local so each thread
920 // gets its own arena and the bump allocator is safe without locks.
921 if name == 'g_memory_block' && g.pref != unsafe { nil } && g.pref.prealloc {
922 g.sb.write_string('_Thread_local ${typ} ${name}')
923 } else {
924 g.sb.write_string('${typ} ${name}')
925 }
926 if field.value !is ast.EmptyExpr {
927 // Function calls are not compile-time constants in C
928 if g.global_initializer_needs_runtime(field.value) {
929 g.sb.writeln(';')
930 } else {
931 g.sb.write_string(' = ')
932 g.expr(field.value)
933 g.sb.writeln(';')
934 }
935 } else {
936 g.sb.writeln(';')
937 }
938 }
939}
940
941fn (mut g Gen) gen_global_decl_extern(node ast.GlobalDecl) {
942 for field in node.fields {
943 // C.foo selectors are provided by C headers/macros; raw @[c_extern]
944 // globals below need an extern declaration, but C.foo does not.
945 if field.name.starts_with('C.') {
946 continue
947 }
948 name := module_storage_field_c_name(g.cur_module, node, field)
949 key := 'extern_global_${name}'
950 if key in g.emitted_types {
951 continue
952 }
953 g.emitted_types[key] = true
954 g.write_indent()
955 if field.typ is ast.Type && field.typ is ast.ArrayFixedType {
956 fixed_typ := field.typ as ast.ArrayFixedType
957 elem_type := g.expr_type_to_c(fixed_typ.elem_type)
958 g.sb.write_string('extern ${elem_type} ${name}[')
959 // Extern declarations are emitted before module const definitions.
960 // Resolve const-backed fixed-array lengths now, so C does not see an
961 // undeclared bound like `extern int a[my_const];`.
962 if len_expr := g.const_expr_c_value_for_header(fixed_typ.len) {
963 g.sb.write_string(len_expr)
964 } else {
965 g.expr(fixed_typ.len)
966 }
967 g.sb.writeln('];')
968 continue
969 }
970 mut typ := ''
971 if field.typ !is ast.EmptyExpr {
972 typ = g.expr_type_to_c(field.typ)
973 } else if field.value !is ast.EmptyExpr {
974 typ = g.get_expr_type(field.value)
975 }
976 if typ == 'mach_timebase_info_data_t' {
977 continue
978 }
979 if typ == '' || typ == 'void' {
980 typ = 'int'
981 }
982 g.global_var_types[name] = typ
983 if name == 'g_memory_block' && g.pref != unsafe { nil } && g.pref.prealloc {
984 g.sb.writeln('extern _Thread_local ${typ} ${name};')
985 } else {
986 g.sb.writeln('extern ${typ} ${name};')
987 }
988 }
989}
990
991fn (mut g Gen) queue_exported_const_symbol(name string, typ string, value string) {
992 if name in g.exported_const_seen {
993 return
994 }
995 g.exported_const_seen[name] = true
996 g.exported_const_symbols << ExportedConstSymbol{
997 name: name
998 typ: typ
999 value: value
1000 }
1001}
1002
1003fn (mut g Gen) emit_exported_const_symbols() {
1004 if !g.export_const_symbols || g.exported_const_symbols.len == 0 {
1005 return
1006 }
1007 g.sb.writeln('')
1008 for sym in g.exported_const_symbols {
1009 g.sb.writeln('#ifdef ${sym.name}')
1010 g.sb.writeln('#undef ${sym.name}')
1011 g.sb.writeln('#endif')
1012 // Resolve references to other constants so the initializer is a
1013 // compile-time constant expression (required by TCC and strict C).
1014 resolved := g.resolve_const_expr(sym.value)
1015 g.sb.writeln('const ${sym.typ} ${sym.name} = ${resolved};')
1016 }
1017}
1018
1019fn (g &Gen) cached_init_function_name() string {
1020 if g.cache_bundle_name.len == 0 {
1021 return ''
1022 }
1023 return '__v2_cached_init_${g.cache_bundle_name}'
1024}
1025
1026fn module_const_init_fn_name(module_name string) string {
1027 return if module_name == 'builtin' || module_name == 'main' {
1028 '__v_init_consts_${module_name}'
1029 } else {
1030 '${module_name}____v_init_consts_${module_name}'
1031 }
1032}
1033
1034fn (mut g Gen) emit_cached_module_init_function() {
1035 if !g.export_const_symbols || g.emit_modules.len == 0 || g.cache_bundle_name.len == 0 {
1036 return
1037 }
1038 mut modules := g.emit_modules.keys()
1039 modules.sort()
1040 mut init_modules := []string{}
1041 for module_name in modules {
1042 if g.module_has_const_init_fn(module_name) {
1043 init_modules << module_name
1044 }
1045 }
1046 g.sb.writeln('')
1047 init_fn_name := g.cached_init_function_name()
1048 g.sb.writeln('void ${init_fn_name}(void) {')
1049 for module_name in init_modules {
1050 init_fn := module_const_init_fn_name(module_name)
1051 g.sb.writeln('\t${init_fn}();')
1052 }
1053 g.sb.writeln('}')
1054}
1055
1056fn (g &Gen) module_has_const_init_fn(module_name string) bool {
1057 init_fn := module_const_init_fn_name(module_name)
1058 return init_fn in g.fn_return_types
1059}
1060
1061fn (mut g Gen) gen_const_decl(node ast.ConstDecl) {
1062 file_scope_ctx := g.enter_file_scope_context()
1063 defer {
1064 g.leave_file_scope_context(file_scope_ctx)
1065 }
1066 for field in node.fields {
1067 name := g.generated_decl_name(field.name) or { continue }
1068 c_name := g.const_storage_name(name)
1069 const_key := 'const_${c_name}'
1070 if const_key in g.emitted_types {
1071 continue
1072 }
1073 g.emitted_types[const_key] = true
1074 mut is_type_only := g.is_type_only_const_expr(field.value)
1075 if is_type_only && g.is_generated_non_type_ident(field.value) {
1076 is_type_only = false
1077 }
1078 if !g.should_emit_module(g.cur_module) && is_type_only {
1079 continue
1080 }
1081 // Type-only consts from .vh headers have no value — emit as zero-initialized globals
1082 // for simple scalar types only. Complex types (fixed arrays, structs) are skipped
1083 // because they need typedefs/initializer-lists that aren't available from .vh data.
1084 if is_type_only {
1085 typ := g.expr_type_to_c(field.value)
1086 if typ == '' || typ == 'void' || typ.starts_with('Array_fixed_') || typ.contains(' ')
1087 || typ.contains('literal') {
1088 continue
1089 }
1090 if typ == 'string' {
1091 g.sb.writeln('string ${c_name} = {0};')
1092 } else {
1093 g.sb.writeln('${typ} ${c_name} = 0;')
1094 }
1095 continue
1096 }
1097 if g.is_runtime_const_target(field.name) {
1098 typ := g.const_decl_storage_type(field.value)
1099 if typ == '' || typ == 'void' {
1100 continue
1101 }
1102 g.emit_runtime_const_storage_decl(c_name, typ)
1103 continue
1104 }
1105 mut is_fixed_array_const := false
1106 mut fixed_array_elem := ''
1107 mut fixed_array_len := 0
1108 if raw_type := g.get_raw_type(field.value) {
1109 if raw_type is types.ArrayFixed {
1110 is_fixed_array_const = true
1111 fixed_array_elem = g.types_type_to_c(raw_type.elem_type)
1112 fixed_array_len = raw_type.len
1113 }
1114 }
1115 if !is_fixed_array_const && field.value is ast.ArrayInitExpr {
1116 array_value := field.value as ast.ArrayInitExpr
1117 mut elem_type := g.extract_array_elem_type(array_value.typ)
1118 if elem_type == '' && array_value.exprs.len > 0 {
1119 elem_type = g.get_expr_type(array_value.exprs[0])
1120 }
1121 if elem_type != '' && elem_type != 'array' {
1122 // Check that no element contains a function call (not valid in C static initializers)
1123 mut has_call := false
1124 for i in 0 .. array_value.exprs.len {
1125 elem := array_value.exprs[i]
1126 if g.contains_call_expr(elem) {
1127 has_call = true
1128 break
1129 }
1130 }
1131 if !has_call {
1132 is_fixed_array_const = true
1133 fixed_array_elem = elem_type
1134 fixed_array_len = array_value.exprs.len
1135 }
1136 }
1137 }
1138 if is_fixed_array_const && fixed_array_elem != '' {
1139 g.fixed_array_globals[name] = true
1140 g.fixed_array_globals[c_name] = true
1141 if fixed_array_len > 0 {
1142 g.sb.write_string('static const ${fixed_array_elem} ${c_name}[${fixed_array_len}] = ')
1143 } else {
1144 g.sb.write_string('static const ${fixed_array_elem} ${c_name}[] = ')
1145 }
1146 if field.value is ast.ArrayInitExpr {
1147 g.gen_fixed_array_initializer(field.value as ast.ArrayInitExpr)
1148 } else {
1149 g.expr(field.value)
1150 }
1151 g.sb.writeln(';')
1152 continue
1153 }
1154 if g.try_emit_const_dynamic_array_call(c_name, field.value) {
1155 continue
1156 }
1157 typ := g.const_decl_storage_type(field.value)
1158 // Function calls and statement-expression lowerings are not compile-time
1159 // constants in C; emit as zero-initialized globals.
1160 if g.const_initializer_needs_runtime(field.value) {
1161 g.emit_runtime_const_storage_decl(c_name, typ)
1162 continue
1163 }
1164 if typ == 'string' {
1165 // String constants need a global variable
1166 g.sb.write_string('string ${c_name} = ')
1167 g.expr(field.value)
1168 g.sb.writeln(';')
1169 } else if
1170 typ in ['bool', 'char', 'rune', 'int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'usize', 'isize', 'f32', 'f64']
1171 || g.is_enum_type(typ) {
1172 value_expr := g.expr_to_string(field.value)
1173 resolved_value_expr := g.resolved_scalar_const_expr(field.value, value_expr)
1174 g.const_exprs[name] = resolved_value_expr
1175 // Qualified const names are safe as macros and work in C constant-expression
1176 // contexts (array sizes, static initializers).
1177 if name.contains('__') {
1178 g.sb.writeln('#define ${c_name} ${resolved_value_expr}')
1179 if g.export_const_symbols {
1180 g.queue_exported_const_symbol(c_name, typ, resolved_value_expr)
1181 }
1182 } else {
1183 // Unqualified names use static const to avoid macro collisions.
1184 // If the expression references other constants, inline their values
1185 // so that tcc (and strict C) accepts the initializer.
1186 g.sb.writeln('static const ${typ} ${c_name} = ${resolved_value_expr};')
1187 }
1188 } else {
1189 // C struct zero-init consts must be real global variables, not
1190 // #define macros, because macros expand to temporaries — taking
1191 // their address (&) or mutating them in-place has no effect.
1192 if typ != '' && typ != 'void' && typ != 'int' && is_c_struct_init(field.value) {
1193 g.sb.writeln('${typ} ${c_name} = {0};')
1194 } else if typ != '' && typ != 'void' && typ != 'int' && !typ.starts_with('Array_')
1195 && !typ.starts_with('Map_') && !typ.contains('*') && !typ.contains('(')
1196 && field.value is ast.InitExpr && (field.value as ast.InitExpr).fields.len == 0 {
1197 // Zero-initialized struct const — emit as global variable.
1198 g.sb.writeln('${typ} ${c_name} = {0};')
1199 } else {
1200 // Fallback for aggregate literals and other complex consts.
1201 g.sb.write_string('#define ${c_name} ')
1202 g.expr(field.value)
1203 g.sb.writeln('')
1204 }
1205 if typ != '' && typ != 'int' {
1206 g.const_types[name] = typ
1207 g.const_types[c_name] = typ
1208 }
1209 }
1210 }
1211}
1212
1213// is_c_struct_init returns true if the expression is a C struct zero-initialization
1214// like `C.mbedtls_ctr_drbg_context{}`. These must be emitted as global variables,
1215// not #define macros, because they need a stable memory address.
1216fn is_c_struct_init(e ast.Expr) bool {
1217 if e is ast.InitExpr {
1218 init := e as ast.InitExpr
1219 if init.typ is ast.Ident {
1220 ident := init.typ as ast.Ident
1221 return ident.name.starts_with('C.')
1222 }
1223 }
1224 return false
1225}
1226
1227fn is_numeric_const_expr(e ast.Expr) bool {
1228 if e is ast.BasicLiteral {
1229 return true
1230 }
1231 if e is ast.InfixExpr {
1232 return is_numeric_const_expr(e.lhs) && is_numeric_const_expr(e.rhs)
1233 }
1234 if e is ast.CastExpr {
1235 return is_numeric_const_expr(e.expr)
1236 }
1237 if e is ast.ParenExpr {
1238 return is_numeric_const_expr(e.expr)
1239 }
1240 if e is ast.PrefixExpr {
1241 return is_numeric_const_expr(e.expr)
1242 }
1243 if e is ast.Ident {
1244 return true // references to other consts are still numeric
1245 }
1246 if e is ast.CallExpr {
1247 if e.lhs is ast.Ident && e.lhs.name in primitive_types && e.args.len == 1 {
1248 return is_numeric_const_expr(e.args[0])
1249 }
1250 // sizeof() is a compile-time numeric expression
1251 if e.lhs is ast.Ident {
1252 return e.lhs.name == 'sizeof'
1253 }
1254 }
1255 return false
1256}
1257
1258fn expr_contains_ident(e ast.Expr) bool {
1259 if e is ast.Ident {
1260 return true
1261 }
1262 if e is ast.InfixExpr {
1263 return expr_contains_ident(e.lhs) || expr_contains_ident(e.rhs)
1264 }
1265 if e is ast.CastExpr {
1266 return expr_contains_ident(e.expr)
1267 }
1268 if e is ast.CallOrCastExpr {
1269 return expr_contains_ident(e.expr)
1270 }
1271 if e is ast.CallExpr {
1272 if expr_contains_ident(e.lhs) {
1273 return true
1274 }
1275 for arg in e.args {
1276 if expr_contains_ident(arg) {
1277 return true
1278 }
1279 }
1280 }
1281 if e is ast.SelectorExpr {
1282 return true
1283 }
1284 if e is ast.ParenExpr {
1285 return expr_contains_ident(e.expr)
1286 }
1287 if e is ast.PrefixExpr {
1288 return expr_contains_ident(e.expr)
1289 }
1290 return false
1291}
1292
1293// resolve_const_expr replaces references to other constants in a C expression
1294// string with their expanded values, so that static const initializers
1295// contain only literal values (required by tcc and the C standard).
1296fn (g &Gen) resolve_const_expr(expr string) string {
1297 mut result := expr
1298 for _ in 0 .. 10 {
1299 mut changed := false
1300 for cname, cval in g.const_exprs {
1301 new_result := replace_whole_word(result, cname, cval)
1302 if new_result != result {
1303 result = new_result
1304 changed = true
1305 }
1306 }
1307 if !changed {
1308 break
1309 }
1310 }
1311 return result
1312}
1313
1314fn replace_whole_word(s string, word string, replacement string) string {
1315 mut result := s
1316 mut pos := 0
1317 for {
1318 idx := result.index_after(word, pos) or { break }
1319 // Check word boundary before.
1320 if idx > 0 {
1321 c := result[idx - 1]
1322 if c == `_` || (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`)
1323 || (c >= `0` && c <= `9`) {
1324 pos = idx + word.len
1325 continue
1326 }
1327 }
1328 // Check word boundary after.
1329 end := idx + word.len
1330 if end < result.len {
1331 c := result[end]
1332 if c == `_` || (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`)
1333 || (c >= `0` && c <= `9`) {
1334 pos = idx + word.len
1335 continue
1336 }
1337 }
1338 result = result[..idx] + replacement + result[end..]
1339 pos = idx + replacement.len
1340 }
1341 return result
1342}
1343