// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. module c import v.ast import v.util import strings import v.depgraph // global or const variable definition string struct GlobalConstDef { mod string // module name def string // definition init string // init later (in _vinit) dep_names []string // the names of all the consts, that this const depends on order int // -1 for simple defines, string literals, anonymous function names, extern declarations etc is_precomputed bool // can be declared as a const in C: primitive, and a simple definition } fn (mut g Gen) const_decl(node ast.ConstDecl) { g.inside_const = true defer { g.inside_const = false } for field in node.fields { if g.pref.skip_unused { if field.name !in g.table.used_features.used_consts { $if trace_skip_unused_consts ? { eprintln('>> skipping unused const name: ${field.name}') } continue } } name := c_name(field.name) const_name := g.c_const_name(field.name) field_expr := field.expr if field.attrs.contains('cinit') || node.attrs.contains('cinit') { styp := g.styp(field.typ) val := g.expr_string(field.expr) g.global_const_defs[name] = GlobalConstDef{ mod: field.mod def: '${g.static_non_parallel}${styp} ${const_name} = ${val};' dep_names: g.table.dependent_names_in_expr(field.expr) } continue } match field.expr { ast.ArrayInit { elems_are_const := field.expr.exprs.all(g.check_expr_is_const(it)) if field.expr.is_fixed && !field.expr.has_index && g.pref.build_mode != .build_module && (!g.is_cc_msvc || field.expr.elem_type != ast.string_type) && elems_are_const { styp := g.styp(field.expr.typ) val := g.expr_string(ast.Expr(field.expr)) // eprintln('> const_name: ${const_name} | name: ${name} | styp: ${styp} | val: ${val}') g.global_const_defs[name] = GlobalConstDef{ mod: field.mod def: '${g.static_non_parallel}${styp} ${const_name} = ${val}; // fixed array const' dep_names: g.table.dependent_names_in_expr(field_expr) } } else if field.expr.is_fixed && !field.expr.has_index && ((g.is_cc_msvc && field.expr.elem_type == ast.string_type) || !elems_are_const) { g.const_decl_init_later_msvc_string_fixed_array(field.mod, name, const_name, field.expr, field.typ) } else { g.const_decl_init_later(field.mod, name, const_name, ast.Expr(field.expr), field.typ, false) } } ast.StringLiteral { val := g.expr_string(ast.Expr(field.expr)) typ := if field.expr.language == .c { 'char*' } else { 'string' } g.global_const_defs[name] = GlobalConstDef{ mod: field.mod def: '${g.static_non_parallel}${typ} ${const_name}; // a string literal, inited later' init: '\t${const_name} = ${val};' order: -1 } } ast.CallExpr { if field.expr.return_type.has_flag(.option) || field.expr.return_type.has_flag(.result) { old_inside_const_opt_or_res := g.inside_const_opt_or_res g.inside_const_opt_or_res = true unwrap_opt_res := field.expr.or_block.kind != .absent g.const_decl_init_later(field.mod, name, const_name, ast.Expr(field.expr), field.typ, unwrap_opt_res) g.inside_const_opt_or_res = old_inside_const_opt_or_res } else { g.const_decl_init_later(field.mod, name, const_name, ast.Expr(field.expr), field.typ, false) } } else { // Note: -usecache uses prebuilt modules, each compiled with: // `v build-module vlib/module` // combined with a top level program, that is compiled with: // `v -usecache toplevel` // For it to work, the consts optimisations should be identical, because // only the top level program will have the const initialisation code for // all the modules. // TODO: encapsulate const initialisation for each module in a separate function, // that is just called by the top level program in _vinit, instead of generating // all the code inside _vinit for each module. use_cache_mode := g.pref.build_mode == .build_module || g.pref.use_cache if !use_cache_mode { if ct_value := field.comptime_expr_value() { if g.const_decl_precomputed(field.mod, name, const_name, field.name, ct_value, field.typ) { continue } } } if field.is_simple_define_const() { // "Simple" expressions are not going to need multiple statements, // only the ones which are inited later, so it's safe to use expr_string g.const_decl_simple_define(field.mod, field.name, const_name, g.expr_string(field_expr)) } else if field.expr is ast.CastExpr { if field.expr.expr is ast.ArrayInit { if field.expr.expr.is_fixed && g.pref.build_mode != .build_module { styp := g.styp(field.expr.typ) val := g.expr_string(field.expr.expr) g.global_const_defs[name] = GlobalConstDef{ mod: field.mod def: '${g.static_non_parallel}${styp} ${const_name} = ${val}; // fixed array const' dep_names: g.table.dependent_names_in_expr(field_expr) } continue } } should_surround := field.expr.expr is ast.CallExpr && field.expr.expr.or_block.kind != .absent g.const_decl_init_later(field.mod, name, const_name, ast.Expr(field.expr), field.typ, should_surround) } else if field.expr is ast.InfixExpr { mut has_unwrap_opt_res := false if field.expr.left is ast.CallExpr { has_unwrap_opt_res = field.expr.left.or_block.kind != .absent } else if field.expr.right is ast.CallExpr { has_unwrap_opt_res = field.expr.right.or_block.kind != .absent } g.const_decl_init_later(field.mod, name, const_name, ast.Expr(field.expr), field.typ, has_unwrap_opt_res) } else { g.const_decl_init_later(field.mod, name, const_name, field.expr, field.typ, true) } } } } } fn (mut g Gen) const_decl_precomputed(mod string, name string, cname string, field_name string, ct_value ast.ComptTimeConstValue, typ ast.Type) bool { mut styp := g.styp(typ) $if trace_const_precomputed ? { eprintln('> styp: ${styp} | cname: ${cname} | ct_value: ${ct_value} | ${ct_value.type_name()}') } match ct_value { i8 { g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) } i16 { g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) } i32 { g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) } // int { // XTODO int64 // g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) //} i64 { if typ == ast.i64_type { return false } if typ == ast.int_type { // TODO: use g.const_decl_write_precomputed here too. // For now, use #define macros, so existing code compiles // with -cstrict. Add checker errors for overflows instead, // so V can catch them earlier, instead of relying on the // C compiler for that. g.const_decl_simple_define(mod, name, cname, ct_value.str()) return true } if typ == ast.u64_type { g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str() + 'U') } else { g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) } } u8 { g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) } u16 { g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) } u32 { g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) } u64 { g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str() + 'U') } f32 { g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) } f64 { g.const_decl_write_precomputed(mod, styp, cname, field_name, ct_value.str()) } rune { rune_code := u32(ct_value) if rune_code <= 127 { if rune_code in [`"`, `\\`, `'`] { return false } escval := util.smart_quote(u8(rune_code).ascii_str(), false) g.global_const_defs[util.no_dots(field_name)] = GlobalConstDef{ mod: mod def: "#define ${cname} '${escval}'" order: -1 } } else { g.const_decl_write_precomputed(mod, styp, cname, field_name, u32(ct_value).str()) } } string { escaped_val := util.smart_quote(ct_value, false) // g.const_decl_write_precomputed(line_nr, styp, cname, '_S("${escaped_val}")') // TODO: ^ the above for strings, cause: // `error C2099: initializer is not a constant` errors in MSVC, // so fall back to the delayed initialisation scheme: init := if typ == ast.string_type { '_S(${cescaped_string_literal(escaped_val)})' } else { '(${styp})${cescaped_string_literal(escaped_val)}' } g.global_const_defs[util.no_dots(field_name)] = GlobalConstDef{ mod: mod def: '${g.static_non_parallel}${styp} ${cname}; // str inited later' init: '\t${cname} = ${init};' order: -1 } if g.is_autofree { g.cleanups[mod].writeln('\tbuiltin__string_free(&${cname});') } } voidptr { g.const_decl_write_precomputed(mod, styp, cname, field_name, '(voidptr)(0x${ct_value})') } ast.EmptyExpr { return false } } return true } fn (mut g Gen) const_decl_write_precomputed(mod string, styp string, cname string, field_name string, ct_value string) { if g.pref.is_livemain || g.pref.is_liveshared { // Note: tcc has problems reloading .so files with consts in them, when the consts are then used inside the reloaded // live functions. As a workaround, just use simple #define macros in this case. // // If you change it, please also test with `v -live run examples/hot_reload/graph.v` which uses `math.pi` . g.global_const_defs[util.no_dots(field_name)] = GlobalConstDef{ mod: mod def: '#define ${cname} ${ct_value} // precomputed3, -live mode' order: -1 } return } g.global_const_defs[util.no_dots(field_name)] = GlobalConstDef{ mod: mod def: '${g.static_non_parallel}const ${styp} ${cname} = ${ct_value}; // precomputed2' // is_precomputed: true } } fn (mut g Gen) const_decl_simple_define(mod string, name string, cname string, val string) { // Simple expressions should use a #define // so that we don't pollute the binary with unnecessary global vars // Do not do this when building a module, otherwise the consts // will not be accessible. if g.pref.translated { g.global_const_defs[util.no_dots(name)] = GlobalConstDef{ mod: mod def: '${g.static_non_parallel}const ${ast.int_type_name} ${cname} = ${val};' order: -1 } } else { g.global_const_defs[util.no_dots(name)] = GlobalConstDef{ mod: mod def: '#define ${cname} ${val}' order: -1 } } } fn (mut g Gen) c_const_name(name string) string { if name in g.table.export_names { // `@[export] name return g.table.export_names[name] } mut const_name := util.no_dots(name) if g.pref.translated && !g.is_builtin_mod && !util.module_is_builtin(name.all_before_last('.')) { if name.starts_with('main.') { const_name = util.no_dots(name.all_after_first('main.')) } } return if g.pref.translated && !g.is_builtin_mod { const_name } else { '_const_' + g.get_ternary_name(c_name(name)) } } fn (mut g Gen) const_decl_init_later(mod string, name string, cname string, expr ast.Expr, typ ast.Type, surround_cbr bool) { if name.starts_with('C__') { return } // Initialize more complex consts in `void _vinit/2{}` // (C doesn't allow init expressions that can't be resolved at compile time). mut styp := g.styp(typ) mut init := strings.new_builder(100) if surround_cbr { init.writeln('{') } if (expr is ast.ArrayInit && expr.has_index) || (expr is ast.IfExpr && g.table.type_kind(expr.typ) == .array_fixed) { init.writeln(g.expr_string_surround('\tmemcpy(&${cname}, &', expr, ', sizeof(${styp}));')) } else if expr is ast.CallExpr && g.table.final_sym(g.unwrap_generic((expr as ast.CallExpr).return_type)).kind == .array_fixed { init.writeln(g.expr_string_surround('\tmemcpy(&${cname}, ', expr, ', sizeof(${styp}));')) } else { init.writeln(g.expr_string_surround('\t${cname} = ', expr, ';')) } if surround_cbr { init.writeln('}') } mut def := '${g.static_non_parallel}${styp} ${cname}' if g.pref.parallel_cc { // So that the const is usable in other files // def = 'extern ${def}' } expr_sym := g.table.sym(typ) if expr_sym.kind == .function { // allow for: `const xyz = abc`, where `abc` is `fn abc() {}` func := (expr_sym.info as ast.FnType).func def = g.fn_var_signature(ast.void_type, func.return_type, func.params.map(it.typ), cname) } init_str := init.str().trim_right('\n') g.global_const_defs[util.no_dots(name)] = GlobalConstDef{ mod: mod def: '${def}; // inited later' init: if init_str.count('\n') > 1 { '{\n${init_str}\n}' } else { init_str } dep_names: g.table.dependent_names_in_expr(expr) } if g.is_autofree { sym := g.table.sym(typ) if sym.kind == .array { if sym.has_method_with_generic_parent('free') { g.cleanup.writeln('\t${styp}_free(&${cname});') } else { g.cleanup.writeln('\tbuiltin__array_free(&${cname});') } } else if styp == 'string' { g.cleanup.writeln('\tbuiltin__string_free(&${cname});') } else if sym.kind == .map { g.cleanup.writeln('\tbuiltin__map_free(&${cname});') } else if styp == 'IError' { g.cleanup.writeln('\tbuiltin__IError_free(&${cname});') } } } fn (mut g Gen) const_decl_init_later_msvc_string_fixed_array(mod string, name string, cname string, expr ast.ArrayInit, typ ast.Type) { mut styp := g.styp(typ) mut init := strings.new_builder(100) for i, elem_expr in expr.exprs { if elem_expr is ast.ArrayInit && elem_expr.is_fixed { elem_typ := g.styp(elem_expr.typ) init.writeln(g.expr_string_surround('\tmemcpy(${cname}[${i}], (${elem_typ})', elem_expr, ', sizeof(${elem_typ}));')) } else if elem_expr is ast.Ident { elem_typ := elem_expr.obj.typ if g.table.final_sym(elem_typ).kind == .array_fixed { elem_styp := g.styp(elem_expr.obj.typ) init.writeln(g.expr_string_surround('\tmemcpy(${cname}[${i}], ', elem_expr, ', sizeof(${elem_styp}));')) } else { init.writeln(g.expr_string_surround('\t${cname}[${i}] = ', elem_expr, ';')) } } else { init.writeln(g.expr_string_surround('\t${cname}[${i}] = ', elem_expr, ';')) } } mut def := '${g.static_non_parallel}${styp} ${cname}' g.global_const_defs[util.no_dots(name)] = GlobalConstDef{ mod: mod def: '${def}; // inited later' init: init.str().trim_right('\n') dep_names: g.table.dependent_names_in_expr(expr) } if g.is_autofree { sym := g.table.sym(typ) if sym.has_method_with_generic_parent('free') { if sym.is_builtin() { g.cleanup.writeln('\tbuiltin__${styp}_free(&${cname});') } else { g.cleanup.writeln('\t${styp}_free(&${cname});') } } else { g.cleanup.writeln('\tbuiltin__array_free(&${cname});') } } } fn (mut g Gen) global_const_expr_is_c_const(expr ast.Expr) bool { match expr { ast.BoolLiteral, ast.CharLiteral, ast.EnumVal, ast.FloatLiteral, ast.IntegerLiteral, ast.Nil, ast.SizeOf, ast.StringLiteral { return true } ast.ArrayInit { if !expr.is_fixed || expr.has_len || expr.has_cap || expr.has_init || expr.has_index { return false } for item in expr.exprs { if !g.global_const_expr_is_c_const(item) { return false } } return true } ast.CastExpr { return !expr.has_arg && g.global_const_expr_is_c_const(expr.expr) } ast.Ident { return expr.kind == .function || (expr.obj is ast.ConstField && expr.obj.is_simple_define_const()) } ast.InfixExpr { if expr.left_type == ast.string_type || expr.right_type == ast.string_type || expr.promoted_type == ast.string_type { return false } return g.global_const_expr_is_c_const(expr.left) && g.global_const_expr_is_c_const(expr.right) } ast.ParExpr { return g.global_const_expr_is_c_const(expr.expr) } ast.PrefixExpr { if expr.op == .amp { return expr.right is ast.Ident || expr.right is ast.SelectorExpr } return g.global_const_expr_is_c_const(expr.right) } ast.SelectorExpr { return expr.expr is ast.Ident && expr.expr.name == 'C' } ast.StructInit { if expr.has_update_expr { return false } for field in expr.init_fields { if !g.global_const_expr_is_c_const(field.expr) { return false } } return true } ast.UnsafeExpr { return g.global_const_expr_is_c_const(expr.expr) } else { return false } } } fn (mut g Gen) global_decl(node ast.GlobalDecl) { // was static used here to to make code optimizable? it was removed when // 'extern' was used to fix the duplicate symbols with usecache && clang // visibility_kw := if g.pref.build_mode == .build_module && g.is_builtin_mod { 'static ' } visibility_kw := if g.should_use_object_local_linkage(node.mod) { 'static ' } else if (g.pref.use_cache || (g.pref.build_mode == .build_module && g.module_built != node.mod)) && !util.should_bundle_module(node.mod) { 'extern ' } else { '' // g.static_modifier // TODO: used to be '' before parallel_cc, may cause issues } // should the global be initialized now, not later in `vinit()` cinit := node.attrs.contains('cinit') g.inside_cinit = cinit g.inside_global_decl = true defer { g.inside_cinit = false g.inside_global_decl = false } should_init := (!g.pref.use_cache && g.pref.build_mode != .build_module) || (g.pref.build_mode == .build_module && g.module_built == node.mod) mut attributes := '' first_field := node.fields[0] if first_field.is_weak { attributes += 'VWEAK ' } if first_field.is_hidden { attributes += 'VHIDDEN ' } if first_field.is_exported { attributes += 'VV_EXP ' } if attr := node.attrs.find_first('_linker_section') { attributes += '__attribute__ ((section ("${attr.arg}"))) ' } for field in node.fields { name := c_name(field.name) if g.pref.skip_unused { if field.name !in g.table.used_features.used_globals { $if trace_skip_unused_globals ? { eprintln('>> skipping unused global name: ${field.name}') } continue } } styp := g.styp(field.typ) mut def_builder := strings.new_builder(100) mut init := '' extern := if field.is_extern { 'extern ' } else { '' } field_visibility_kw := if field.is_extern { '' } else { visibility_kw } mut qualifiers := '' if field.is_const { qualifiers += 'const ' } if field.is_volatile { qualifiers += 'volatile ' } final_c_name := field.name.all_after('C.') if field.is_const { if field.is_extern || field_visibility_kw == 'extern ' { def_builder.writeln('${extern}${field_visibility_kw}${qualifiers}${styp} ${attributes}${final_c_name}; // global 2') g.global_const_defs[name] = GlobalConstDef{ mod: node.mod def: def_builder.str() order: -1 } continue } if !field.has_expr { g.error('const globals must have an explicit initializer', field.pos) continue } if !g.global_const_expr_is_c_const(field.expr) { g.error('const global `${field.name}` must be initialized with a C constant expression', field.pos) continue } def_builder.write_string('${extern}${field_visibility_kw}${qualifiers}${styp} ${attributes}${final_c_name}') def_builder.write_string(' = ${g.expr_string(field.expr)}') def_builder.writeln('; // global 2') g.global_const_defs[name] = GlobalConstDef{ mod: node.mod def: def_builder.str() order: -1 } continue } mut anon_fn_expr := unsafe { field.expr } if field.has_expr && mut anon_fn_expr is ast.AnonFn { g.gen_anon_fn_decl(mut anon_fn_expr) fn_type_name := g.get_anon_fn_type_name(mut anon_fn_expr, field.name) g.global_const_defs[util.no_dots(fn_type_name)] = GlobalConstDef{ mod: node.mod def: '${fn_type_name} = ${g.table.sym(field.typ).name}; // global 1' order: -1 } continue } if field.is_extern { tls_kw := if field.name == 'g_memory_block' && g.pref.prealloc { '_Thread_local ' } else { '' } def_builder.writeln('${extern}${tls_kw}${field_visibility_kw}${qualifiers}${styp} ${attributes}${final_c_name}; // global 2') g.global_const_defs[name] = GlobalConstDef{ mod: node.mod def: def_builder.str() order: -1 } continue } mut needs_ending_semicolon := false if field.language != .c || field.has_expr { tls_kw := if field.name == 'g_memory_block' && g.pref.prealloc { '_Thread_local ' } else { '' } def_builder.write_string('${extern}${tls_kw}${field_visibility_kw}${qualifiers}${styp} ${attributes}${final_c_name}') needs_ending_semicolon = true } if field.has_expr || cinit { // `__global x = unsafe { nil }` should still use the simple direct initialisation, `g_main_argv` needs it. mut is_simple_unsafe_expr := false if field.expr is ast.UnsafeExpr { if field.expr.expr is ast.Nil { is_simple_unsafe_expr = true } if field.expr.expr.is_literal() { is_simple_unsafe_expr = true } } if g.pref.translated { def_builder.write_string(' = ${g.expr_string(field.expr)}') } else if (field.expr.is_literal() && should_init) || cinit || (field.expr is ast.ArrayInit && field.expr.is_fixed) || (is_simple_unsafe_expr && should_init) { // Simple literals can be initialized right away in global scope in C. // e.g. `int myglobal = 10;` def_builder.write_string(' = ${g.expr_string(field.expr)}') } else { // More complex expressions need to be moved to `_vinit()` // e.g. `__global ( mygblobal = 'hello ' + world' )` if field.name in ['g_main_argc', 'g_main_argv'] { init = '\t// skipping ${final_c_name}, it was initialised in main' } else { init = '\t${final_c_name} = ${g.expr_string(field.expr)}; // global 3' } } } else if !g.pref.translated && field.language == .v { // don't zero globals from C code g.type_default_vars.clear() default_initializer := g.type_default(field.typ) if default_initializer == '{0}' && should_init { def_builder.write_string(' = {0}') } else if default_initializer == '{E_STRUCT}' && should_init { init = '\tmemcpy(${final_c_name}, (${styp}){${default_initializer}}, sizeof(${styp})); // global 4' } else { if field.name !in ['as_cast_type_indexes', 'g_memory_block', 'global_allocator'] { decls := g.type_default_vars.str() if decls != '' { init = '\t${decls}' } init += '\t${final_c_name} = *(${styp}*)&((${styp}[]){${default_initializer}}[0]); // global 5' } } } else { def_builder.writeln('/* skip C global: ${final_c_name} */') } if needs_ending_semicolon { def_builder.writeln('; // global 6') } g.global_const_defs[name] = GlobalConstDef{ mod: node.mod def: def_builder.str() init: init dep_names: g.table.dependent_names_in_expr(field.expr) } } } fn (mut g Gen) sort_globals_consts() { util.timing_start(@METHOD) defer { util.timing_measure(@METHOD) } g.sorted_global_const_names.clear() mut dep_graph := depgraph.new_dep_graph() for var_name, var_info in g.global_const_defs { dep_graph.add_with_value(var_name, var_info.dep_names, var_info.order) } dep_graph_sorted := dep_graph.resolve() for order in [-1, 0] { for node in dep_graph_sorted.nodes { if node.value == order { g.sorted_global_const_names << node.name } } } }