v/vlib/v/gen/c/consts_and_globals.v
2025-09-02 18:48:51 +03:00

562 lines
19 KiB
V

// 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)
mut const_name := '_const_' + name
if g.pref.translated && !g.is_builtin_mod
&& !util.module_is_builtin(field.name.all_before_last('.')) {
if name.starts_with('main__') {
const_name = name.all_after_first('main__')
}
}
if !g.is_builtin_mod {
if cattr := node.attrs.find_first('export') {
const_name = cattr.arg
}
}
field_expr := field.expr
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(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, field.expr,
field.typ)
} else {
g.const_decl_init_later(field.mod, name, field.expr, field.typ, false)
}
}
ast.StringLiteral {
val := g.expr_string(field.expr)
typ := if field.expr.language == .c { 'char*' } else { 'string' }
g.global_const_defs[name] = GlobalConstDef{
mod: field.mod
def: '${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, 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, 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, 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, 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, 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, field.expr, field.typ, has_unwrap_opt_res)
} else {
g.const_decl_init_later(field.mod, name, field.expr, field.typ, true)
}
}
}
}
}
fn (mut g Gen) const_decl_precomputed(mod string, name string, field_name string, ct_value ast.ComptTimeConstValue,
typ ast.Type) bool {
mut styp := g.styp(typ)
cname := if g.pref.translated && !g.is_builtin_mod { name } else { '_const_${name}' }
$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, 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("${escaped_val}")'
} else {
'(${styp})"${escaped_val}"'
}
g.global_const_defs[util.no_dots(field_name)] = GlobalConstDef{
mod: mod
def: '${styp} ${cname}; // str inited later'
init: '\t${cname} = ${init};'
order: -1
}
if g.is_autofree {
g.cleanups[mod].writeln('\tstring_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, 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.
mut x := util.no_dots(name)
if g.pref.translated && !g.is_builtin_mod && !util.module_is_builtin(name.all_before_last('.')) {
// Don't prepend "_const" to translated C consts,
// but only in user code, continue prepending "_const" to builtin consts.
if x.starts_with('main__') {
x = x['main__'.len..]
}
} else {
x = '_const_${x}'
}
if g.pref.translated {
g.global_const_defs[util.no_dots(name)] = GlobalConstDef{
mod: mod
def: 'const int ${x} = ${val};'
order: -1
}
} else {
g.global_const_defs[util.no_dots(name)] = GlobalConstDef{
mod: mod
def: '#define ${x} ${val}'
order: -1
}
}
}
fn (mut g Gen) c_const_name(name string) string {
return if g.pref.translated && !g.is_builtin_mod { name } else { '_const_${name}' }
}
fn (mut g Gen) const_decl_init_later(mod string, name 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)
cname := g.c_const_name(name)
mut init := strings.new_builder(100)
if surround_cbr {
init.writeln('{')
}
if expr is ast.ArrayInit && (expr as ast.ArrayInit).has_index {
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 := '${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(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 styp.starts_with('Array_') {
if sym.has_method_with_generic_parent('free') {
g.cleanup.writeln('\t${styp}_free(&${cname});')
} else {
g.cleanup.writeln('\tarray_free(&${cname});')
}
} else if styp == 'string' {
g.cleanup.writeln('\tstring_free(&${cname});')
} else if sym.kind == .map {
g.cleanup.writeln('\tmap_free(&${cname});')
} else if styp == 'IError' {
g.cleanup.writeln('\tIError_free(&${cname});')
}
}
}
fn (mut g Gen) const_decl_init_later_msvc_string_fixed_array(mod string, name string, expr ast.ArrayInit,
typ ast.Type) {
mut styp := g.styp(typ)
cname := g.c_const_name(name)
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 := '${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') {
g.cleanup.writeln('\t${styp}_free(&${cname});')
} else {
g.cleanup.writeln('\tarray_free(&${cname});')
}
}
}
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.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
}
cextern := node.attrs.contains('c_extern')
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 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
}
mut def_builder := strings.new_builder(100)
mut init := ''
extern := if cextern { 'extern ' } else { '' }
modifier := if field.is_volatile { ' volatile ' } else { '' }
def_builder.write_string('${extern}${visibility_kw}${modifier}${styp} ${attributes}${field.name}')
if cextern {
def_builder.writeln('; // global 2')
g.global_const_defs[name] = GlobalConstDef{
mod: node.mod
def: def_builder.str()
order: -1
}
continue
}
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 ${field.name}, it was initialised in main'
} else {
init = '\t${field.name} = ${g.expr_string(field.expr)}; // global 3'
}
}
} else if !g.pref.translated { // 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(${field.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${field.name} = *(${styp}*)&((${styp}[]){${default_initializer}}[0]); // global 5'
}
}
}
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
}
}
}
}