// Copyright (c) 2019-2023 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 os import strings import hash.fnv1a import v.ast import v.pref import v.token import v.util import v.util.version import v.depgraph import sync.pool const ( // Note: some of the words in c_reserved, are not reserved in C, but are // in C++, or have special meaning in V, thus need escaping too. `small` // should not be needed, but see: // https://stackoverflow.com/questions/5874215/what-is-rpcndr-h c_reserved = ['array', 'auto', 'bool', 'break', 'calloc', 'case', 'char', 'class', 'complex', 'const', 'continue', 'default', 'delete', 'do', 'double', 'else', 'enum', 'error', 'exit', 'export', 'extern', 'false', 'float', 'for', 'free', 'goto', 'if', 'inline', 'int', 'link', 'long', 'malloc', 'namespace', 'new', 'nil', 'panic', 'register', 'restrict', 'return', 'short', 'signed', 'sizeof', 'static', 'string', 'struct', 'switch', 'typedef', 'typename', 'union', 'unix', 'unsigned', 'void', 'volatile', 'while', 'template', 'true', 'small', 'stdout', 'stdin', 'stderr', 'far', 'near', 'huge', 'requires'] c_reserved_chk = token.new_keywords_matcher_from_array_trie(c_reserved) // same order as in token.Kind cmp_str = ['eq', 'ne', 'gt', 'lt', 'ge', 'le'] // when operands are switched cmp_rev = ['eq', 'ne', 'lt', 'gt', 'le', 'ge'] result_name = '_result' option_name = '_option' ) fn string_array_to_map(a []string) map[string]bool { mut res := map[string]bool{} for x in a { res[x] = true } return res } pub struct Gen { pref &pref.Preferences = unsafe { nil } field_data_type ast.Type // cache her to avoid map lookups enum_data_type ast.Type // cache her to avoid map lookups module_built string timers_should_print bool mut: out strings.Builder cheaders strings.Builder preincludes strings.Builder // allows includes to go before `definitions` includes strings.Builder // all C #includes required by V modules typedefs strings.Builder enum_typedefs strings.Builder // enum types definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file) type_definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file) alias_definitions strings.Builder // alias fixed array of non-builtin hotcode_definitions strings.Builder // -live declarations & functions channel_definitions strings.Builder // channel related code thread_definitions strings.Builder // thread defines comptime_definitions strings.Builder // custom defines, given by -d/-define flags on the CLI cleanup strings.Builder cleanups map[string]strings.Builder // contents of `void _vcleanup(){}` gowrappers strings.Builder // all go callsite wrappers auto_str_funcs strings.Builder // function bodies of all auto generated _str funcs dump_funcs strings.Builder // function bodies of all auto generated _str funcs pcs_declarations strings.Builder // -prof profile counter declarations for each function embedded_data strings.Builder // data to embed in the executable/binary shared_types strings.Builder // shared/lock types shared_functions strings.Builder // shared constructors out_options_forward strings.Builder // forward `option_xxxx` types out_options strings.Builder // `option_xxxx` types out_results_forward strings.Builder // forward`result_xxxx` types out_results strings.Builder // `result_xxxx` types json_forward_decls strings.Builder // json type forward decls sql_buf strings.Builder // for writing exprs to args via `sqlite3_bind_int()` etc global_const_defs map[string]GlobalConstDef sorted_global_const_names []string file &ast.File = unsafe { nil } table &ast.Table = unsafe { nil } unique_file_path_hash u64 // a hash of file.path, used for making auxilary fn generation unique (like `compare_xyz`) fn_decl &ast.FnDecl = unsafe { nil } // pointer to the FnDecl we are currently inside otherwise 0 last_fn_c_name string tmp_count int // counter for unique tmp vars (_tmp1, _tmp2 etc); resets at the start of each fn. tmp_count_af int // a separate tmp var counter for autofree fn calls tmp_count_declarations int // counter for unique tmp names (_d1, _d2 etc); does NOT reset, used for C declarations global_tmp_count int // like tmp_count but global and not resetted in each function discard_or_result bool // do not safe last ExprStmt of `or` block in tmp variable to defer ongoing expr usage is_direct_array_access bool // inside a `[direct_array_access fn a() {}` function is_assign_lhs bool // inside left part of assign expr (for array_set(), etc) is_void_expr_stmt bool // ExprStmt whos result is discarded is_arraymap_set bool // map or array set value state is_amp bool // for `&Foo{}` to merge PrefixExpr `&` and StructInit `Foo{}`; also for `&u8(0)` etc is_sql bool // Inside `sql db{}` statement, generating sql instead of C (e.g. `and` instead of `&&` etc) is_shared bool // for initialization of hidden mutex in `[rw]shared` literals is_vlines_enabled bool // is it safe to generate #line directives when -g is passed is_autofree bool // false, inside the bodies of fns marked with [manualfree], otherwise === g.pref.autofree is_builtin_mod bool is_json_fn bool // inside json.encode() is_js_call bool // for handling a special type arg #1 `json.decode(User, ...)` is_fn_index_call bool is_cc_msvc bool // g.pref.ccompiler == 'msvc' is_option_auto_heap bool vlines_path string // set to the proper path for generating #line directives options_pos_forward int // insertion point to forward options_forward []string // to forward options map[string]string // to avoid duplicates results_forward []string // to forward results map[string]string // to avoid duplicates done_options shared []string // to avoid duplicates done_results shared []string // to avoid duplicates chan_pop_options map[string]string // types for `x := <-ch or {...}` chan_push_options map[string]string // types for `ch <- x or {...}` mtxs string // array of mutexes if the `lock` has multiple variables labeled_loops map[string]&ast.Stmt inner_loop &ast.Stmt = unsafe { nil } shareds map[int]string // types with hidden mutex for which decl has been emitted inside_ternary int // ?: comma separated statements on a single line inside_map_postfix bool // inside map++/-- postfix expr inside_map_infix bool // inside map< fn counter name hotcode_fn_names []string hotcode_fpaths []string embedded_files []ast.EmbeddedFile sql_i int sql_stmt_name string sql_bind_name string sql_idents []string sql_idents_types []ast.Type sql_left_type ast.Type sql_table_name string sql_fkey string sql_parent_id string sql_side SqlExprSide // left or right, to distinguish idents in `name == name` strs_to_free0 []string // strings.Builder // strs_to_free []string // strings.Builder // tmp_arg_vars_to_free []string // autofree_pregen map[string]string // autofree_pregen_buf strings.Builder // autofree_tmp_vars []string // to avoid redefining the same tmp vars in a single function // nr_vars_to_free int // doing_autofree_tmp bool comptime_for_method string // $for method in T.methods {} comptime_for_method_var string // $for method in T.methods {}; the variable name comptime_for_field_var string // $for field in T.fields {}; the variable name comptime_for_field_value ast.StructField // value of the field variable comptime_for_field_type ast.Type // type of the field variable inferred from `$if field.typ is T {}` comptime_enum_field_value string // value of enum name comptime_var_type_map map[string]ast.Type comptime_values_stack []CurrentComptimeValues // stores the values from the above on each $for loop, to make nesting them easier prevent_sum_type_unwrapping_once bool // needed for assign new values to sum type // used in match multi branch // TypeOne, TypeTwo {} // where an aggregate (at least two types) is generated // sum type deref needs to know which index to deref because unions take care of the correct field aggregate_type_idx int branch_parent_pos int // used in BranchStmt (continue/break) for autofree stop position returned_var_name string // to detect that a var doesn't need to be freed since it's being returned infix_left_var_name string // a && if expr called_fn_name string timers &util.Timers = util.get_timers() force_main_console bool // true when [console] used on fn main() as_cast_type_names map[string]string // table for type name lookup in runtime (for __as_cast) obf_table map[string]string referenced_fns shared map[string]bool // functions that have been referenced nr_closures int expected_cast_type ast.Type // for match expr of sumtypes or_expr_return_type ast.Type // or { 0, 1 } return type anon_fn bool tests_inited bool has_main bool // main_fn_decl_node ast.FnDecl cur_mod ast.Module cur_concrete_types []ast.Type // do not use table.cur_concrete_types because table is global, so should not be accessed by different threads cur_fn &ast.FnDecl = unsafe { nil } // same here cur_lock ast.LockExpr cur_struct_init_typ ast.Type autofree_methods map[int]bool generated_free_methods map[int]bool autofree_scope_stmts []string use_segfault_handler bool = true test_function_names []string ///////// // out_parallel []strings.Builder // out_idx int out_fn_start_pos []int // for generating multiple .c files, stores locations of all fn positions in `out` string builder static_modifier string // for parallel_cc has_reflection bool reflection_strings &map[string]int defer_return_tmp_var string } // 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 } pub fn gen(files []&ast.File, table &ast.Table, pref_ &pref.Preferences) (string, string, string, []int) { mut module_built := '' if pref_.build_mode == .build_module { for file in files { if file.path.contains(pref_.path) && file.mod.short_name == pref_.path.all_after_last(os.path_separator).trim_right(os.path_separator) { module_built = file.mod.name break } } } mut timers_should_print := false $if time_cgening ? { timers_should_print = true } mut reflection_strings := map[string]int{} mut global_g := Gen{ file: 0 out: strings.new_builder(512000) cheaders: strings.new_builder(15000) includes: strings.new_builder(100) preincludes: strings.new_builder(100) typedefs: strings.new_builder(100) enum_typedefs: strings.new_builder(100) type_definitions: strings.new_builder(100) alias_definitions: strings.new_builder(100) hotcode_definitions: strings.new_builder(100) channel_definitions: strings.new_builder(100) thread_definitions: strings.new_builder(100) comptime_definitions: strings.new_builder(100) definitions: strings.new_builder(100) gowrappers: strings.new_builder(100) auto_str_funcs: strings.new_builder(100) dump_funcs: strings.new_builder(100) pcs_declarations: strings.new_builder(100) embedded_data: strings.new_builder(1000) out_options_forward: strings.new_builder(100) out_options: strings.new_builder(100) out_results_forward: strings.new_builder(100) out_results: strings.new_builder(100) shared_types: strings.new_builder(100) shared_functions: strings.new_builder(100) json_forward_decls: strings.new_builder(100) sql_buf: strings.new_builder(100) table: table pref: pref_ fn_decl: 0 is_autofree: pref_.autofree indent: -1 module_built: module_built timers_should_print: timers_should_print timers: util.new_timers(should_print: timers_should_print, label: 'global_cgen') inner_loop: &ast.empty_stmt field_data_type: ast.Type(table.find_type_idx('FieldData')) enum_data_type: ast.Type(table.find_type_idx('EnumData')) is_cc_msvc: pref_.ccompiler == 'msvc' use_segfault_handler: !('no_segfault_handler' in pref_.compile_defines || pref_.os in [.wasm32, .wasm32_emscripten]) static_modifier: if pref_.parallel_cc { 'static' } else { '' } has_reflection: 'v.reflection' in table.modules reflection_strings: &reflection_strings } /* global_g.out_parallel = []strings.Builder{len: nr_cpus} for i in 0 .. nr_cpus { global_g.out_parallel[i] = strings.new_builder(100000) global_g.out_parallel[i].writeln('#include "out.h"\n') } println('LEN=') println(global_g.out_parallel.len) */ // anon fn may include assert and thus this needs // to be included before any test contents are written if pref_.is_test { global_g.write_tests_definitions() } util.timing_start('cgen init') for mod in global_g.table.modules { global_g.cleanups[mod] = strings.new_builder(100) } global_g.init() util.timing_measure('cgen init') global_g.tests_inited = false global_g.file = files.last() if !pref_.no_parallel { util.timing_start('cgen parallel processing') mut pp := pool.new_pool_processor(callback: cgen_process_one_file_cb) pp.set_shared_context(global_g) // TODO: make global_g shared pp.work_on_items(files) util.timing_measure('cgen parallel processing') util.timing_start('cgen unification') for g in pp.get_results_ref[Gen]() { global_g.embedded_files << g.embedded_files global_g.out.write(g.out) or { panic(err) } global_g.cheaders.write(g.cheaders) or { panic(err) } global_g.preincludes.write(g.preincludes) or { panic(err) } global_g.includes.write(g.includes) or { panic(err) } global_g.typedefs.write(g.typedefs) or { panic(err) } global_g.type_definitions.write(g.type_definitions) or { panic(err) } global_g.alias_definitions.write(g.alias_definitions) or { panic(err) } global_g.definitions.write(g.definitions) or { panic(err) } global_g.gowrappers.write(g.gowrappers) or { panic(err) } global_g.auto_str_funcs.write(g.auto_str_funcs) or { panic(err) } global_g.dump_funcs.write(g.auto_str_funcs) or { panic(err) } global_g.comptime_definitions.write(g.comptime_definitions) or { panic(err) } global_g.pcs_declarations.write(g.pcs_declarations) or { panic(err) } global_g.hotcode_definitions.write(g.hotcode_definitions) or { panic(err) } global_g.embedded_data.write(g.embedded_data) or { panic(err) } global_g.shared_types.write(g.shared_types) or { panic(err) } global_g.shared_functions.write(g.channel_definitions) or { panic(err) } global_g.force_main_console = global_g.force_main_console || g.force_main_console // merge maps for k, v in g.global_const_defs { global_g.global_const_defs[k] = v } for k, v in g.shareds { global_g.shareds[k] = v } for k, v in g.chan_pop_options { global_g.chan_pop_options[k] = v } for k, v in g.chan_push_options { global_g.chan_push_options[k] = v } for k, v in g.options { global_g.options[k] = v } for k, v in g.results { global_g.results[k] = v } for k, v in g.as_cast_type_names { global_g.as_cast_type_names[k] = v } for k, v in g.sumtype_definitions { global_g.sumtype_definitions[k] = v } global_g.json_forward_decls.write(g.json_forward_decls) or { panic(err) } global_g.enum_typedefs.write(g.enum_typedefs) or { panic(err) } global_g.channel_definitions.write(g.channel_definitions) or { panic(err) } global_g.thread_definitions.write(g.thread_definitions) or { panic(err) } global_g.sql_buf.write(g.sql_buf) or { panic(err) } global_g.cleanups[g.file.mod.name].write(g.cleanup) or { panic(err) } // strings.Builder.write never fails; it is like that in the source for str_type in g.str_types { global_g.str_types << str_type } for scf in g.sumtype_casting_fns { if scf !in global_g.sumtype_casting_fns { global_g.sumtype_casting_fns << scf } } global_g.nr_closures += g.nr_closures global_g.has_main = global_g.has_main || g.has_main global_g.auto_fn_definitions << g.auto_fn_definitions global_g.anon_fn_definitions << g.anon_fn_definitions global_g.needed_equality_fns << g.needed_equality_fns // duplicates are resolved later in gen_equality_fns global_g.array_contains_types << g.array_contains_types global_g.array_index_types << g.array_index_types global_g.pcs << g.pcs global_g.json_types << g.json_types global_g.hotcode_fn_names << g.hotcode_fn_names global_g.hotcode_fpaths << g.hotcode_fpaths global_g.test_function_names << g.test_function_names unsafe { g.free_builders() } for k, v in g.autofree_methods { global_g.autofree_methods[k] = v } } } else { util.timing_start('cgen serial processing') for file in files { global_g.file = file global_g.gen_file() global_g.cleanups[file.mod.name].drain_builder(mut global_g.cleanup, 100) } util.timing_measure('cgen serial processing') util.timing_start('cgen unification') } global_g.gen_jsons() global_g.dump_expr_definitions() // this uses global_g.get_str_fn, so it has to go before the below for loop for i := 0; i < global_g.str_types.len; i++ { global_g.final_gen_str(global_g.str_types[i]) } for sumtype_casting_fn in global_g.sumtype_casting_fns { global_g.write_sumtype_casting_fn(sumtype_casting_fn) } global_g.write_shareds() global_g.write_chan_pop_option_fns() global_g.write_chan_push_option_fns() global_g.gen_array_contains_methods() global_g.gen_array_index_methods() global_g.gen_equality_fns() global_g.gen_free_methods() global_g.write_results() global_g.write_options() global_g.sort_globals_consts() util.timing_measure('cgen unification') mut g := global_g util.timing_start('cgen common') // to make sure type idx's are the same in cached mods if g.pref.build_mode == .build_module { for idx, sym in g.table.type_symbols { if idx in [0, 30] { continue } g.definitions.writeln('int _v_type_idx_${sym.cname}();') } } else if g.pref.use_cache { for idx, sym in g.table.type_symbols { if idx in [0, 30] { continue } g.definitions.writeln('int _v_type_idx_${sym.cname}() { return ${idx}; };') } } // // v files are finished, what remains is pure C code g.gen_vlines_reset() if g.pref.build_mode != .build_module { // no init in builtin.o g.write_init_function() } // insert for options forward if g.out_options_forward.len > 0 || g.out_results_forward.len > 0 { tail := g.type_definitions.cut_to(g.options_pos_forward) if g.out_options_forward.len > 0 { g.type_definitions.writeln('// #start V forward option_xxx definitions:') g.type_definitions.writeln(g.out_options_forward.str()) g.type_definitions.writeln('// #end V forward option_xxx definitions\n') } if g.out_results_forward.len > 0 { g.type_definitions.writeln('// #start V forward result_xxx definitions:') g.type_definitions.writeln(g.out_results_forward.str()) g.type_definitions.writeln('// #end V forward result_xxx definitions\n') } g.type_definitions.writeln(tail) } g.finish() mut b := strings.new_builder(640000) b.write_string(g.hashes()) if g.use_segfault_handler { b.writeln('\n#define V_USE_SIGNAL_H') } b.writeln('\n// V comptime_definitions:') b.write_string(g.comptime_definitions.str()) b.writeln('\n// V typedefs:') b.write_string(g.typedefs.str()) b.writeln('\n // V preincludes:') b.write_string(g.preincludes.str()) b.writeln('\n// V cheaders:') b.write_string(g.cheaders.str()) if g.pcs_declarations.len > 0 { b.writeln('\n// V profile counters:') b.write_string(g.pcs_declarations.str()) } b.writeln('\n// V includes:') b.write_string(g.includes.str()) b.writeln('\n// Enum definitions:') b.write_string(g.enum_typedefs.str()) b.writeln('\n// Thread definitions:') b.write_string(g.thread_definitions.str()) b.writeln('\n// V type definitions:') b.write_string(g.type_definitions.str()) b.writeln('\n// V alias definitions:') b.write_string(g.alias_definitions.str()) b.writeln('\n// V shared types:') b.write_string(g.shared_types.str()) b.writeln('\n// V Option_xxx definitions:') b.write_string(g.out_options.str()) b.writeln('\n// V result_xxx definitions:') b.write_string(g.out_results.str()) b.writeln('\n// V json forward decls:') b.write_string(g.json_forward_decls.str()) b.writeln('\n// V definitions:') b.write_string(g.definitions.str()) b.writeln('\n// V global/const definitions:') for var_name in g.sorted_global_const_names { if var := g.global_const_defs[var_name] { b.writeln(var.def) } } interface_table := g.interface_table() if interface_table.len > 0 { b.writeln('\n// V interface table:') b.write_string(interface_table) } if g.gowrappers.len > 0 { b.writeln('\n// V gowrappers:') b.write_string(g.gowrappers.str()) } if g.hotcode_definitions.len > 0 { b.writeln('\n// V hotcode definitions:') b.write_string(g.hotcode_definitions.str()) } if g.embedded_data.len > 0 { b.writeln('\n// V embedded data:') b.write_string(g.embedded_data.str()) } if g.shared_functions.len > 0 { b.writeln('\n// V shared type functions:') b.write_string(g.shared_functions.str()) b.write_string(c_concurrency_helpers) } if g.channel_definitions.len > 0 { b.writeln('\n// V channel code:') b.write_string(g.channel_definitions.str()) } if g.auto_str_funcs.len > 0 { b.writeln('\n// V auto str functions:') b.write_string(g.auto_str_funcs.str()) } if g.dump_funcs.len > 0 { b.writeln('\n// V dump functions:') b.write_string(g.dump_funcs.str()) } if g.auto_fn_definitions.len > 0 { b.writeln('\n// V auto functions:') for fn_def in g.auto_fn_definitions { b.writeln(fn_def) } } if g.anon_fn_definitions.len > 0 { if g.nr_closures > 0 { b.writeln('\n// V closure helpers') b.writeln(c_closure_helpers(g.pref)) } b.writeln('\n// V anon functions:') for fn_def in g.anon_fn_definitions { b.writeln(fn_def) } } b.writeln('\n// end of V out') mut header := b.last_n(b.len) header = '#ifndef V_HEADER_FILE\n#define V_HEADER_FILE' + header header += '\n#endif\n' out_str := g.out.str() b.write_string(out_str) b.writeln('\n// THE END.') util.timing_measure('cgen common') res := b.str() $if trace_all_generic_fn_keys ? { gkeys := g.table.fn_generic_types.keys() for gkey in gkeys { eprintln('>> g.table.fn_generic_types key: ${gkey}') } } out_fn_start_pos := g.out_fn_start_pos.clone() unsafe { b.free() } unsafe { g.free_builders() } return header, res, out_str, out_fn_start_pos } fn cgen_process_one_file_cb(mut p pool.PoolProcessor, idx int, wid int) &Gen { file := p.get_item[&ast.File](idx) mut global_g := unsafe { &Gen(p.get_shared_context()) } mut g := &Gen{ file: file out: strings.new_builder(512000) cheaders: strings.new_builder(15000) includes: strings.new_builder(100) typedefs: strings.new_builder(100) type_definitions: strings.new_builder(100) alias_definitions: strings.new_builder(100) definitions: strings.new_builder(100) gowrappers: strings.new_builder(100) auto_str_funcs: strings.new_builder(100) comptime_definitions: strings.new_builder(100) pcs_declarations: strings.new_builder(100) hotcode_definitions: strings.new_builder(100) embedded_data: strings.new_builder(1000) out_options_forward: strings.new_builder(100) out_options: strings.new_builder(100) out_results_forward: strings.new_builder(100) out_results: strings.new_builder(100) shared_types: strings.new_builder(100) shared_functions: strings.new_builder(100) channel_definitions: strings.new_builder(100) thread_definitions: strings.new_builder(100) json_forward_decls: strings.new_builder(100) enum_typedefs: strings.new_builder(100) sql_buf: strings.new_builder(100) cleanup: strings.new_builder(100) table: global_g.table pref: global_g.pref fn_decl: 0 indent: -1 module_built: global_g.module_built timers: util.new_timers( should_print: global_g.timers_should_print label: 'cgen_process_one_file_cb idx: ${idx}, wid: ${wid}' ) inner_loop: &ast.empty_stmt field_data_type: ast.Type(global_g.table.find_type_idx('FieldData')) enum_data_type: ast.Type(global_g.table.find_type_idx('EnumData')) array_sort_fn: global_g.array_sort_fn waiter_fns: global_g.waiter_fns threaded_fns: global_g.threaded_fns options_forward: global_g.options_forward results_forward: global_g.results_forward done_options: global_g.done_options done_results: global_g.done_results is_autofree: global_g.pref.autofree referenced_fns: global_g.referenced_fns is_cc_msvc: global_g.is_cc_msvc use_segfault_handler: global_g.use_segfault_handler has_reflection: 'v.reflection' in global_g.table.modules reflection_strings: global_g.reflection_strings } g.gen_file() return g } // free_builders should be called only when a Gen would NOT be used anymore // it frees the bulk of the memory that is private to the Gen instance // (the various string builders) [unsafe] pub fn (mut g Gen) free_builders() { unsafe { g.out.free() g.cheaders.free() g.includes.free() g.typedefs.free() g.type_definitions.free() g.alias_definitions.free() g.definitions.free() g.cleanup.free() g.gowrappers.free() g.auto_str_funcs.free() g.dump_funcs.free() g.comptime_definitions.free() g.pcs_declarations.free() g.hotcode_definitions.free() g.embedded_data.free() g.shared_types.free() g.shared_functions.free() g.channel_definitions.free() g.thread_definitions.free() g.out_options_forward.free() g.out_options.free() g.out_results_forward.free() g.out_results.free() g.json_forward_decls.free() g.enum_typedefs.free() g.sql_buf.free() for _, mut v in g.cleanups { v.free() } } } pub fn (mut g Gen) gen_file() { g.timers.start('cgen_file ${g.file.path}') g.unique_file_path_hash = fnv1a.sum64_string(g.file.path) if g.pref.is_vlines { g.vlines_path = util.vlines_escape_path(g.file.path, g.pref.ccompiler) g.is_vlines_enabled = true g.inside_ternary = 0 } g.stmts(g.file.stmts) // Transfer embedded files for path in g.file.embedded_files { if path !in g.embedded_files { g.embedded_files << path } } g.timers.show('cgen_file ${g.file.path}') } pub fn (g &Gen) hashes() string { mut res := c_commit_hash_default.replace('@@@', version.vhash()) res += c_current_commit_hash_default.replace('@@@', version.githash(g.pref.building_v)) return res } pub fn (mut g Gen) init() { if g.pref.custom_prelude != '' { g.cheaders.writeln(g.pref.custom_prelude) } else if !g.pref.no_preludes { g.cheaders.writeln('// Generated by the V compiler') if g.pref.os == .wasm32 { g.cheaders.writeln('#define VWASM 1') // Include instead of for WASM target g.cheaders.writeln('#include ') g.cheaders.writeln('#include ') } else { tcc_undef_has_include := ' #if defined(__TINYC__) && defined(__has_include) // tcc does not support has_include properly yet, turn it off completely #undef __has_include #endif' g.preincludes.writeln(tcc_undef_has_include) g.cheaders.writeln(tcc_undef_has_include) g.includes.writeln(tcc_undef_has_include) if g.pref.os == .freebsd { g.cheaders.writeln('#include ') g.cheaders.writeln('#include ') } else { g.cheaders.writeln(get_guarded_include_text('', 'The C compiler can not find . Please install build-essentials')) // int64_t etc if g.pref.os == .ios { g.cheaders.writeln(get_guarded_include_text('', 'The C compiler can not find . Please install build-essentials')) // bool, true, false } g.cheaders.writeln(get_guarded_include_text('', 'The C compiler can not find . Please install build-essentials')) // size_t, ptrdiff_t } } if g.pref.nofloat { g.cheaders.writeln('#define VNOFLOAT 1') } g.cheaders.writeln(c_builtin_types) if g.pref.is_bare { g.cheaders.writeln(c_bare_headers) } else { g.cheaders.writeln(c_headers) } if !g.pref.skip_unused || g.table.used_maps > 0 { g.cheaders.writeln(c_wyhash_headers) } } if g.pref.os == .ios { g.cheaders.writeln('#define __TARGET_IOS__ 1') g.cheaders.writeln('#include ') } g.write_builtin_types() g.options_pos_forward = g.type_definitions.len g.write_typedef_types() g.write_typeof_functions() g.write_sorted_types() g.write_array_fixed_return_types() g.write_multi_return_types() g.definitions.writeln('// end of definitions #endif') if g.pref.compile_defines_all.len > 0 { g.comptime_definitions.writeln('// V compile time defines by -d or -define flags:') g.comptime_definitions.writeln('// All custom defines : ' + g.pref.compile_defines_all.join(',')) g.comptime_definitions.writeln('// Turned ON custom defines: ' + g.pref.compile_defines.join(',')) for cdefine in g.pref.compile_defines { g.comptime_definitions.writeln('#define CUSTOM_DEFINE_${cdefine}') } g.comptime_definitions.writeln('') } if g.table.gostmts > 0 { g.comptime_definitions.writeln('#define __VTHREADS__ (1)') } if g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt, .boehm_leak] { g.comptime_definitions.writeln('#define _VGCBOEHM (1)') } if g.pref.is_debug || 'debug' in g.pref.compile_defines { g.comptime_definitions.writeln('#define _VDEBUG (1)') } if g.pref.is_prod || 'prod' in g.pref.compile_defines { g.comptime_definitions.writeln('#define _VPROD (1)') } if g.pref.is_test || 'test' in g.pref.compile_defines { g.comptime_definitions.writeln('#define _VTEST (1)') } if g.pref.is_prof || 'profile' in g.pref.compile_defines { g.comptime_definitions.writeln('#define _VPROFILE (1)') } if g.pref.autofree { g.comptime_definitions.writeln('#define _VAUTOFREE (1)') } else { g.comptime_definitions.writeln('#define _VAUTOFREE (0)') } if g.pref.prealloc { g.comptime_definitions.writeln('#define _VPREALLOC (1)') } if g.pref.use_cache { g.comptime_definitions.writeln('#define _VUSECACHE (1)') } if g.pref.build_mode == .build_module { g.comptime_definitions.writeln('#define _VBUILDMODULE (1)') } if g.pref.is_livemain || g.pref.is_liveshared { g.generate_hotcode_reloading_declarations() } // Obfuscate only functions in the main module for now. // Generate the obf_ast. if g.pref.obfuscate { mut i := 0 // fns for key, f in g.table.fns { if f.mod != 'main' && key != 'main' { continue } g.obf_table[key] = '_f${i}' i++ } // methods for type_sym in g.table.type_symbols { if type_sym.mod != 'main' { continue } for method in type_sym.methods { g.obf_table[type_sym.name + '.' + method.name] = '_f${i}' i++ } } } // we know that this is being called before the multi-threading starts // and this is being called in the main thread, so we can mutate the table mut muttable := unsafe { &ast.Table(g.table) } if g.use_segfault_handler { muttable.used_fns['v_segmentation_fault_handler'] = true } muttable.used_fns['eprintln'] = true muttable.used_fns['print_backtrace'] = true muttable.used_fns['exit'] = true } pub fn (mut g Gen) finish() { if g.pref.is_prof && g.pref.build_mode != .build_module { g.gen_vprint_profile_stats() } if g.pref.is_livemain || g.pref.is_liveshared { g.generate_hotcode_reloader_code() } g.handle_embedded_files_finish() if g.pref.is_test { g.gen_c_main_for_tests() } else { g.gen_c_main() } } pub fn (mut g Gen) write_typeof_functions() { g.writeln('') g.writeln('// >> typeof() support for sum types / interfaces') for ityp, sym in g.table.type_symbols { if sym.kind == .sum_type { static_prefix := if g.pref.build_mode == .build_module { 'static ' } else { '' } sum_info := sym.info as ast.SumType if sum_info.is_generic { continue } g.writeln('${static_prefix}char * v_typeof_sumtype_${sym.cname}(int sidx) { /* ${sym.name} */ ') if g.pref.build_mode == .build_module { g.writeln('\t\tif( sidx == _v_type_idx_${sym.cname}() ) return "${util.strip_main_name(sym.name)}";') for v in sum_info.variants { subtype := g.table.sym(v) g.writeln('\tif( sidx == _v_type_idx_${subtype.cname}() ) return "${util.strip_main_name(subtype.name)}";') } g.writeln('\treturn "unknown ${util.strip_main_name(sym.name)}";') } else { tidx := g.table.find_type_idx(sym.name) g.writeln('\tswitch(sidx) {') g.writeln('\t\tcase ${tidx}: return "${util.strip_main_name(sym.name)}";') for v in sum_info.variants { subtype := g.table.sym(v) g.writeln('\t\tcase ${v.idx()}: return "${util.strip_main_name(subtype.name)}";') } g.writeln('\t\tdefault: return "unknown ${util.strip_main_name(sym.name)}";') g.writeln('\t}') } g.writeln('}') g.writeln('') g.writeln('${static_prefix}int v_typeof_sumtype_idx_${sym.cname}(int sidx) { /* ${sym.name} */ ') if g.pref.build_mode == .build_module { g.writeln('\t\tif( sidx == _v_type_idx_${sym.cname}() ) return ${int(ityp)};') for v in sum_info.variants { subtype := g.table.sym(v) g.writeln('\tif( sidx == _v_type_idx_${subtype.cname}() ) return ${int(v)};') } g.writeln('\treturn ${int(ityp)};') } else { tidx := g.table.find_type_idx(sym.name) g.writeln('\tswitch(sidx) {') g.writeln('\t\tcase ${tidx}: return ${int(ityp)};') for v in sum_info.variants { g.writeln('\t\tcase ${v.idx()}: return ${int(v)};') } g.writeln('\t\tdefault: return ${int(ityp)};') g.writeln('\t}') } g.writeln('}') } else if sym.kind == .interface_ { if sym.info !is ast.Interface { continue } inter_info := sym.info as ast.Interface if inter_info.is_generic { continue } g.definitions.writeln('static char * v_typeof_interface_${sym.cname}(int sidx);') g.writeln('static char * v_typeof_interface_${sym.cname}(int sidx) { /* ${sym.name} */ ') for t in inter_info.types { sub_sym := g.table.sym(ast.mktyp(t)) g.writeln('\tif (sidx == _${sym.cname}_${sub_sym.cname}_index) return "${util.strip_main_name(sub_sym.name)}";') } g.writeln('\treturn "unknown ${util.strip_main_name(sym.name)}";') g.writeln('}') g.writeln('') g.writeln('static int v_typeof_interface_idx_${sym.cname}(int sidx) { /* ${sym.name} */ ') for t in inter_info.types { sub_sym := g.table.sym(ast.mktyp(t)) g.writeln('\tif (sidx == _${sym.cname}_${sub_sym.cname}_index) return ${int(t)};') } g.writeln('\treturn ${int(ityp)};') g.writeln('}') } } g.writeln('// << typeof() support for sum types') g.writeln('') } // V type to C typecc fn (mut g Gen) typ(t ast.Type) string { if t.has_flag(.option) { // Register an optional if it's not registered yet return g.register_option(t) } else if t.has_flag(.result) { return g.register_result(t) } else { return g.base_type(t) } } fn (mut g Gen) base_type(_t ast.Type) string { t := g.unwrap_generic(_t) if g.pref.nofloat { // todo compile time if for perf? if t == ast.f32_type { return 'u32' } else if t == ast.f64_type { return 'u64' } } share := t.share() mut styp := if share == .atomic_t { t.atomic_typename() } else { g.cc_type(t, true) } if t.has_flag(.shared_f) { styp = g.find_or_register_shared(t, styp) } nr_muls := g.unwrap_generic(t).nr_muls() if nr_muls > 0 { styp += strings.repeat(`*`, nr_muls) } return styp } fn (mut g Gen) generic_fn_name(types []ast.Type, before string) string { if types.len == 0 { return before } // Using _T_ to differentiate between get[string] and get_string // `foo[int]()` => `foo_T_int()` mut name := before + '_T' for typ in types { name += '_' + strings.repeat_string('__ptr__', typ.nr_muls()) + g.typ(typ.set_nr_muls(0)) } return name } fn (mut g Gen) expr_string(expr ast.Expr) string { pos := g.out.len // pos2 := g.out_parallel[g.out_idx].len g.expr(expr) // g.out_parallel[g.out_idx].cut_to(pos2) return g.out.cut_to(pos).trim_space() } fn (mut g Gen) expr_string_with_cast(expr ast.Expr, typ ast.Type, exp ast.Type) string { pos := g.out.len // pos2 := g.out_parallel[g.out_idx].len g.expr_with_cast(expr, typ, exp) // g.out_parallel[g.out_idx].cut_to(pos2) return g.out.cut_to(pos).trim_space() } // Surround a potentially multi-statement expression safely with `prepend` and `append`. // (and create a statement) fn (mut g Gen) expr_string_surround(prepend string, expr ast.Expr, append string) string { pos := g.out.len // pos2 := g.out_parallel[g.out_idx].len g.stmt_path_pos << pos defer { g.stmt_path_pos.delete_last() } g.write(prepend) g.expr(expr) g.write(append) // g.out_parallel[g.out_idx].cut_to(pos2) return g.out.cut_to(pos) } // TODO this really shouldnt be seperate from typ // but I(emily) would rather have this generation // all unified in one place so that it doesnt break // if one location changes fn (mut g Gen) option_type_name(t ast.Type) (string, string) { mut base := g.base_type(t) mut styp := '' sym := g.table.sym(t) if sym.info is ast.FnType { base = 'anon_fn_${g.table.fn_type_signature(sym.info.func)}' } if sym.language == .c && sym.kind == .struct_ { styp = '${c.option_name}_${base.replace(' ', '_')}' } else { styp = '${c.option_name}_${base}' } if t.is_ptr() { styp = styp.replace('*', '_ptr') } return styp, base } fn (mut g Gen) result_type_name(t ast.Type) (string, string) { mut base := g.base_type(t) if t.has_flag(.option) { g.register_option(t) base = '_option_' + base } mut styp := '' sym := g.table.sym(t) if sym.info is ast.FnType { base = 'anon_fn_${g.table.fn_type_signature(sym.info.func)}' } if sym.language == .c && sym.kind == .struct_ { styp = '${c.result_name}_${base.replace(' ', '_')}' } else { styp = '${c.result_name}_${base}' } if t.is_ptr() { styp = styp.replace('*', '_ptr') } return styp, base } fn (g Gen) option_type_text(styp string, base string) string { // replace void with something else size := if base == 'void' { 'u8' } else if base.starts_with('anon_fn') { 'void*' } else if base.starts_with('_option_') { base.replace('*', '') } else { if base.starts_with('struct ') && !base.ends_with('*') { '${base}*' } else { base } } ret := 'struct ${styp} { byte state; IError err; byte data[sizeof(${size}) > 1 ? sizeof(${size}) : 1]; }' return ret } fn (g Gen) result_type_text(styp string, base string) string { // replace void with something else size := if base == 'void' { 'u8' } else if base.starts_with('anon_fn') { 'void*' } else if base.starts_with('_option_') { base.replace('*', '') } else { if base.starts_with('struct ') && !base.ends_with('*') { '${base}*' } else { base } } ret := 'struct ${styp} { bool is_error; IError err; byte data[sizeof(${size}) > 1 ? sizeof(${size}) : 1]; }' return ret } fn (mut g Gen) register_option(t ast.Type) string { styp, base := g.option_type_name(t) g.options[base] = styp return styp } fn (mut g Gen) register_result(t ast.Type) string { styp, base := g.result_type_name(t) g.results[base] = styp return styp } fn (mut g Gen) write_options() { mut done := []string{} rlock g.done_options { done = g.done_options.clone() } for base, styp in g.options { if base in done { continue } done << base g.typedefs.writeln('typedef struct ${styp} ${styp};') if base in g.options_forward { g.out_options_forward.write_string(g.option_type_text(styp, base) + ';\n\n') } else { g.out_options.write_string(g.option_type_text(styp, base) + ';\n\n') } } } fn (mut g Gen) write_results() { mut done := []string{} rlock g.done_results { done = g.done_results.clone() } for base, styp in g.results { if base in done { continue } done << base g.typedefs.writeln('typedef struct ${styp} ${styp};') if base in g.results_forward { g.out_results_forward.write_string(g.result_type_text(styp, base) + ';\n\n') } else { g.out_results.write_string(g.result_type_text(styp, base) + ';\n\n') } } for k, _ in g.table.anon_struct_names { ck := c_name(k) g.typedefs.writeln('typedef struct ${ck} ${ck};') } } fn (mut g Gen) find_or_register_shared(t ast.Type, base string) string { g.shareds[t.idx()] = base return '__shared__${base}' } fn (mut g Gen) write_shareds() { mut done_types := []int{} for typ, base in g.shareds { if typ in done_types { continue } done_types << typ sh_typ := '__shared__${base}' mtx_typ := 'sync__RwMutex' g.shared_types.writeln('struct ${sh_typ} {') g.shared_types.writeln('\t${mtx_typ} mtx;') g.shared_types.writeln('\t${base} val;') g.shared_types.writeln('};') g.shared_functions.writeln('static inline voidptr __dup${sh_typ}(voidptr src, int sz) {') g.shared_functions.writeln('\t${sh_typ}* dest = memdup(src, sz);') g.shared_functions.writeln('\tsync__RwMutex_init(&dest->mtx);') g.shared_functions.writeln('\treturn dest;') g.shared_functions.writeln('}') g.typedefs.writeln('typedef struct ${sh_typ} ${sh_typ};') } } fn (mut g Gen) register_thread_void_wait_call() { lock g.waiter_fns { if '__v_thread_wait' in g.waiter_fns { return } g.waiter_fns << '__v_thread_wait' } g.gowrappers.writeln('void __v_thread_wait(__v_thread thread) {') if g.pref.os == .windows { g.gowrappers.writeln('\tu32 stat = WaitForSingleObject(thread, INFINITE);') } else { g.gowrappers.writeln('\tint stat = pthread_join(thread, (void **)NULL);') } g.gowrappers.writeln('\tif (stat != 0) { _v_panic(_SLIT("unable to join thread")); }') if g.pref.os == .windows { g.gowrappers.writeln('\tCloseHandle(thread);') } g.gowrappers.writeln('}') } fn (mut g Gen) register_thread_array_wait_call(eltyp string) string { is_void := eltyp == 'void' thread_typ := if is_void { '__v_thread' } else { '__v_thread_${eltyp}' } ret_typ := if is_void { 'void' } else { 'Array_${eltyp}' } thread_arr_typ := 'Array_${thread_typ}' fn_name := '${thread_arr_typ}_wait' mut should_register := false lock g.waiter_fns { if fn_name !in g.waiter_fns { g.waiter_fns << fn_name should_register = true } } if should_register { if is_void { g.register_thread_void_wait_call() g.gowrappers.writeln(' void ${fn_name}(${thread_arr_typ} a) { for (int i = 0; i < a.len; ++i) { ${thread_typ} t = ((${thread_typ}*)a.data)[i]; if (t == 0) continue; __v_thread_wait(t); } }') } else { g.gowrappers.writeln(' ${ret_typ} ${fn_name}(${thread_arr_typ} a) { ${ret_typ} res = __new_array_with_default(a.len, a.len, sizeof(${eltyp}), 0); for (int i = 0; i < a.len; ++i) { ${thread_typ} t = ((${thread_typ}*)a.data)[i];') if g.pref.os == .windows { g.gowrappers.writeln('\t\tif (t.handle == 0) continue;') } else { g.gowrappers.writeln('\t\tif (t == 0) continue;') } g.gowrappers.writeln('\t\t((${eltyp}*)res.data)[i] = __v_thread_${eltyp}_wait(t); } return res; }') } } return fn_name } fn (mut g Gen) register_chan_pop_option_call(opt_el_type string, styp string) { g.chan_pop_options[opt_el_type] = styp } fn (mut g Gen) write_chan_pop_option_fns() { mut done := []string{} for opt_el_type, styp in g.chan_pop_options { if opt_el_type in done { continue } done << opt_el_type g.channel_definitions.writeln(' static inline ${opt_el_type} __Option_${styp}_popval(${styp} ch) { ${opt_el_type} _tmp = {0}; if (sync__Channel_try_pop_priv(ch, _tmp.data, false)) { return (${opt_el_type}){ .state = 2, .err = _v_error(_SLIT("channel closed")), .data = {EMPTY_STRUCT_INITIALIZATION} }; } return _tmp; }') } } fn (mut g Gen) register_chan_push_option_fn(el_type string, styp string) { g.chan_push_options[styp] = el_type } fn (mut g Gen) write_chan_push_option_fns() { mut done := []string{} for styp, el_type in g.chan_push_options { if styp in done { continue } done << styp g.register_option(ast.void_type.set_flag(.option)) g.channel_definitions.writeln(' static inline ${c.option_name}_void __Option_${styp}_pushval(${styp} ch, ${el_type} e) { if (sync__Channel_try_push_priv(ch, &e, false)) { return (${c.option_name}_void){ .state = 2, .err = _v_error(_SLIT("channel closed")), .data = {EMPTY_STRUCT_INITIALIZATION} }; } return (${c.option_name}_void){0}; }') } } // cc_type whether to prefix 'struct' or not (C__Foo -> struct Foo) fn (mut g Gen) cc_type(typ ast.Type, is_prefix_struct bool) string { sym := g.table.sym(g.unwrap_generic(typ)) mut styp := sym.cname // TODO: this needs to be removed; cgen shouldn't resolve generic types (job of checker) match sym.info { ast.Struct, ast.Interface, ast.SumType { if sym.info.is_generic && sym.generic_types.len == 0 { mut sgtyps := '_T' for gt in sym.info.generic_types { gts := g.table.sym(g.unwrap_generic(gt)) sgtyps += '_${gts.cname}' } styp += sgtyps } } else {} } if is_prefix_struct && sym.language == .c { styp = styp[3..] if sym.kind == .struct_ { info := sym.info as ast.Struct if !info.is_typedef { styp = 'struct ${styp}' } } } return styp } [inline] fn (g &Gen) type_sidx(t ast.Type) string { if g.pref.build_mode == .build_module { sym := g.table.sym(t) return '_v_type_idx_${sym.cname}()' } return t.idx().str() } pub fn (mut g Gen) write_typedef_types() { for sym in g.table.type_symbols { if sym.name in c.builtins { continue } match sym.kind { .array { info := sym.info as ast.Array elem_sym := g.table.sym(info.elem_type) if elem_sym.kind != .placeholder && !info.elem_type.has_flag(.generic) { g.type_definitions.writeln('typedef array ${sym.cname};') } } .array_fixed { info := sym.info as ast.ArrayFixed elem_sym := g.table.sym(info.elem_type) if elem_sym.is_builtin() { // .array_fixed { styp := sym.cname // array_fixed_char_300 => char x[300] len := styp.after('_') mut fixed := g.typ(info.elem_type) if elem_sym.info is ast.FnType { pos := g.out.len // pos2:=g.out_parallel[g.out_idx].len g.write_fn_ptr_decl(&elem_sym.info, '') fixed = g.out.cut_to(pos) // g.out_parallel[g.out_idx].cut_to(pos2) mut def_str := 'typedef ${fixed};' def_str = def_str.replace_once('(*)', '(*${styp}[${len}])') g.type_definitions.writeln(def_str) } else if !info.is_fn_ret { g.type_definitions.writeln('typedef ${fixed} ${styp} [${len}];') base := g.typ(info.elem_type.clear_flags(.option, .result)) if info.elem_type.has_flag(.option) && base !in g.options_forward { g.options_forward << base } else if info.elem_type.has_flag(.result) && base !in g.results_forward { g.results_forward << base } } } } .chan { if sym.name != 'chan' { g.type_definitions.writeln('typedef chan ${sym.cname};') chan_inf := sym.chan_info() chan_elem_type := chan_inf.elem_type is_fixed_arr := g.table.sym(chan_elem_type).kind == .array_fixed if !chan_elem_type.has_flag(.generic) { el_stype := if is_fixed_arr { '_v_' } else { '' } + g.typ(chan_elem_type) val_arg_pop := if is_fixed_arr { '&val.ret_arr' } else { '&val' } val_arg_push := if is_fixed_arr { 'val' } else { '&val' } push_arg := el_stype + if is_fixed_arr { '*' } else { '' } g.channel_definitions.writeln(' static inline ${el_stype} __${sym.cname}_popval(${sym.cname} ch) { ${el_stype} val; sync__Channel_try_pop_priv(ch, ${val_arg_pop}, false); return val; }') g.channel_definitions.writeln(' static inline void __${sym.cname}_pushval(${sym.cname} ch, ${push_arg} val) { sync__Channel_try_push_priv(ch, ${val_arg_push}, false); }') } } } .map { g.type_definitions.writeln('typedef map ${sym.cname};') } else { continue } } } for sym in g.table.type_symbols { if sym.kind == .alias && sym.name !in c.builtins && sym.name !in ['byte', 'i32'] { g.write_alias_typesymbol_declaration(sym) } } for sym in g.table.type_symbols { if sym.kind == .function && sym.name !in c.builtins { g.write_fn_typesymbol_declaration(sym) } } // Generating interfaces after all the common types have been defined // to prevent generating interface struct before definition of field types for sym in g.table.type_symbols { if sym.kind == .interface_ && sym.name !in c.builtins { g.write_interface_typedef(sym) } } for sym in g.table.type_symbols { if sym.kind == .interface_ && sym.name !in c.builtins { g.write_interface_typesymbol_declaration(sym) } } } pub fn (mut g Gen) write_alias_typesymbol_declaration(sym ast.TypeSymbol) { parent := g.table.type_symbols[sym.parent_idx] is_c_parent := parent.name.len > 2 && parent.name[0] == `C` && parent.name[1] == `.` mut is_typedef := false mut is_fixed_array_of_non_builtin := false if parent.info is ast.Struct { is_typedef = parent.info.is_typedef } mut parent_styp := parent.cname if is_c_parent { if !is_typedef { parent_styp = 'struct ' + parent.cname[3..] } else { parent_styp = parent.cname[3..] } } else { if sym.info is ast.Alias { parent_styp = g.typ(sym.info.parent_type) parent_sym := g.table.sym(sym.info.parent_type) if parent_sym.info is ast.ArrayFixed { elem_sym := g.table.sym(parent_sym.info.elem_type) if !elem_sym.is_builtin() { is_fixed_array_of_non_builtin = true } } } } if parent_styp == 'byte' && sym.cname == 'u8' { // TODO: remove this check; it is here just to fix V rebuilding in -cstrict mode with clang-12 return } if is_fixed_array_of_non_builtin { g.alias_definitions.writeln('typedef ${parent_styp} ${sym.cname};') } else { g.type_definitions.writeln('typedef ${parent_styp} ${sym.cname};') } } pub fn (mut g Gen) write_interface_typedef(sym ast.TypeSymbol) { struct_name := c_name(sym.cname) g.typedefs.writeln('typedef struct ${struct_name} ${struct_name};') } pub fn (mut g Gen) write_interface_typesymbol_declaration(sym ast.TypeSymbol) { if sym.info !is ast.Interface { return } info := sym.info as ast.Interface if info.is_generic { return } struct_name := c_name(sym.cname) g.type_definitions.writeln('struct ${struct_name} {') g.type_definitions.writeln('\tunion {') g.type_definitions.writeln('\t\tvoid* _object;') for variant in info.types { mk_typ := ast.mktyp(variant) if mk_typ != variant && mk_typ in info.types { continue } vcname := g.table.sym(mk_typ).cname g.type_definitions.writeln('\t\t${vcname}* _${vcname};') } g.type_definitions.writeln('\t};') g.type_definitions.writeln('\tint _typ;') for field in info.fields { styp := g.typ(field.typ) cname := c_name(field.name) g.type_definitions.writeln('\t${styp}* ${cname};') } g.type_definitions.writeln('};') } pub fn (mut g Gen) write_fn_typesymbol_declaration(sym ast.TypeSymbol) { info := sym.info as ast.FnType func := info.func is_fn_sig := func.name == '' not_anon := !info.is_anon mut has_generic_arg := false for param in func.params { if param.typ.has_flag(.generic) { has_generic_arg = true break } } if !info.has_decl && (not_anon || is_fn_sig) && !func.return_type.has_flag(.generic) && !has_generic_arg { fn_name := sym.cname mut call_conv := '' mut msvc_call_conv := '' for attr in func.attrs { match attr.name { 'callconv' { if g.is_cc_msvc { msvc_call_conv = '__${attr.arg} ' } else { call_conv = '${attr.arg}' } } else {} } } call_conv_attribute_suffix := if call_conv.len != 0 { '__attribute__((${call_conv}))' } else { '' } ret_typ := if !func.return_type.has_flag(.option) && g.table.sym(func.return_type).kind == .array_fixed { '_v_' } else { '' } + g.typ(func.return_type) g.type_definitions.write_string('typedef ${ret_typ} (${msvc_call_conv}*${fn_name})(') for i, param in func.params { g.type_definitions.write_string(g.typ(param.typ)) if i < func.params.len - 1 { g.type_definitions.write_string(',') } } g.type_definitions.writeln(')${call_conv_attribute_suffix};') } } pub fn (mut g Gen) write_array_fixed_return_types() { g.typedefs.writeln('\n// BEGIN_array_fixed_return_typedefs') g.type_definitions.writeln('\n// BEGIN_array_fixed_return_structs') for sym in g.table.type_symbols { if sym.kind != .array_fixed || (sym.info as ast.ArrayFixed).elem_type.has_flag(.generic) || !(sym.info as ast.ArrayFixed).is_fn_ret { continue } info := sym.info as ast.ArrayFixed mut fixed_elem_name := g.typ(info.elem_type.set_nr_muls(0)) if info.elem_type.is_ptr() { fixed_elem_name += '*'.repeat(info.elem_type.nr_muls()) } g.typedefs.writeln('typedef struct ${sym.cname} ${sym.cname};') g.type_definitions.writeln('struct ${sym.cname} {') g.type_definitions.writeln('\t${fixed_elem_name} ret_arr[${info.size}];') g.type_definitions.writeln('};') } g.typedefs.writeln('// END_array_fixed_return_typedefs\n') g.type_definitions.writeln('// END_array_fixed_return_structs\n') } pub fn (mut g Gen) write_multi_return_types() { start_pos := g.type_definitions.len g.typedefs.writeln('\n// BEGIN_multi_return_typedefs') g.type_definitions.writeln('\n// BEGIN_multi_return_structs') for sym in g.table.type_symbols { if sym.kind != .multi_return { continue } if sym.cname.contains('[') { continue } info := sym.mr_info() if info.types.filter(it.has_flag(.generic)).len > 0 { continue } g.typedefs.writeln('typedef struct ${sym.cname} ${sym.cname};') g.type_definitions.writeln('struct ${sym.cname} {') for i, mr_typ in info.types { type_name := g.typ(mr_typ) if mr_typ.has_flag(.option) { // option in multi_return // Dont use g.typ() here because it will register // option and we dont want that styp, base := g.option_type_name(mr_typ) lock g.done_options { if base !in g.done_options { g.done_options << base last_text := g.type_definitions.after(start_pos).clone() g.type_definitions.go_back_to(start_pos) g.typedefs.writeln('typedef struct ${styp} ${styp};') g.type_definitions.writeln('${g.option_type_text(styp, base)};') g.type_definitions.write_string(last_text) } } } if mr_typ.has_flag(.result) { // result in multi_return // Dont use g.typ() here because it will register // result and we dont want that styp, base := g.result_type_name(mr_typ) lock g.done_results { if base !in g.done_results { g.done_results << base last_text := g.type_definitions.after(start_pos).clone() g.type_definitions.go_back_to(start_pos) g.typedefs.writeln('typedef struct ${styp} ${styp};') g.type_definitions.writeln('${g.result_type_text(styp, base)};') g.type_definitions.write_string(last_text) } } } g.type_definitions.writeln('\t${type_name} arg${i};') } g.type_definitions.writeln('};\n') } g.typedefs.writeln('// END_multi_return_typedefs\n') g.type_definitions.writeln('// END_multi_return_structs\n') } pub fn (mut g Gen) new_tmp_var() string { g.tmp_count++ return '_t${g.tmp_count}' } pub fn (mut g Gen) new_global_tmp_var() string { g.global_tmp_count++ return '_t${g.global_tmp_count}' } pub fn (mut g Gen) new_tmp_declaration_name() string { g.tmp_count_declarations++ return '_d${g.tmp_count_declarations}' } pub fn (mut g Gen) current_tmp_var() string { return '_t${g.tmp_count}' } /* pub fn (mut g Gen) new_tmp_var2() string { g.tmp_count_af++ return '_tt$g.tmp_count_af' } */ pub fn (mut g Gen) reset_tmp_count() { g.tmp_count = 0 } fn (mut g Gen) decrement_inside_ternary() { key := g.inside_ternary.str() for name in g.ternary_level_names[key] { g.ternary_names.delete(name) } g.ternary_level_names.delete(key) g.inside_ternary-- } fn (mut g Gen) stmts(stmts []ast.Stmt) { g.stmts_with_tmp_var(stmts, '') } fn is_noreturn_callexpr(expr ast.Expr) bool { if expr is ast.CallExpr { return expr.is_noreturn } return false } // stmts_with_tmp_var is used in `if` or `match` branches. // It returns true, if the last statement was a `return` fn (mut g Gen) stmts_with_tmp_var(stmts []ast.Stmt, tmp_var string) bool { g.indent++ if g.inside_ternary > 0 { g.write('(') } mut last_stmt_was_return := false for i, stmt in stmts { if i == stmts.len - 1 { if stmt is ast.Return { last_stmt_was_return = true } } if i == stmts.len - 1 && tmp_var != '' { // Handle if expressions, set the value of the last expression to the temp var. if g.inside_if_option || g.inside_match_option { g.set_current_pos_as_last_stmt_pos() g.skip_stmt_pos = true if stmt is ast.ExprStmt { if stmt.typ == ast.error_type_idx || stmt.expr is ast.None { g.writeln('${tmp_var}.state = 2;') g.write('${tmp_var}.err = ') g.expr(stmt.expr) g.writeln(';') } else { mut styp := g.base_type(stmt.typ) $if tinyc && x32 && windows { if stmt.typ == ast.int_literal_type { styp = 'int' } else if stmt.typ == ast.float_literal_type { styp = 'f64' } } if stmt.typ.has_flag(.option) { g.writeln('') g.write('${tmp_var} = ') g.expr(stmt.expr) g.writeln(';') } else { ret_typ := g.fn_decl.return_type.clear_flag(.option) styp = g.base_type(ret_typ) g.write('_option_ok(&(${styp}[]) { ') g.expr_with_cast(stmt.expr, stmt.typ, ret_typ) g.writeln(' }, (${c.option_name}*)(&${tmp_var}), sizeof(${styp}));') } } } } else if g.inside_if_result || g.inside_match_result { g.set_current_pos_as_last_stmt_pos() g.skip_stmt_pos = true if stmt is ast.ExprStmt { if stmt.typ == ast.error_type_idx { g.writeln('${tmp_var}.is_error = true;') g.write('${tmp_var}.err = ') g.expr(stmt.expr) g.writeln(';') } else { mut styp := g.base_type(stmt.typ) $if tinyc && x32 && windows { if stmt.typ == ast.int_literal_type { styp = 'int' } else if stmt.typ == ast.float_literal_type { styp = 'f64' } } if stmt.typ.has_flag(.result) { g.writeln('') g.write('${tmp_var} = ') g.expr(stmt.expr) g.writeln(';') } else { ret_typ := g.fn_decl.return_type.clear_flag(.result) styp = g.base_type(ret_typ) g.write('_result_ok(&(${styp}[]) { ') g.expr_with_cast(stmt.expr, stmt.typ, ret_typ) g.writeln(' }, (${c.result_name}*)(&${tmp_var}), sizeof(${styp}));') } } } } else { g.set_current_pos_as_last_stmt_pos() g.skip_stmt_pos = true mut is_noreturn := false if stmt in [ast.Return, ast.BranchStmt] { is_noreturn = true } else if stmt is ast.ExprStmt { is_noreturn = is_noreturn_callexpr(stmt.expr) } if !is_noreturn { g.write('${tmp_var} = ') } g.stmt(stmt) if !g.out.last_n(2).contains(';') { g.writeln(';') } } } else { g.stmt(stmt) if (g.inside_if_option || g.inside_if_result || g.inside_match_option || g.inside_match_result) && stmt is ast.ExprStmt { g.writeln(';') } } g.skip_stmt_pos = false if g.inside_ternary > 0 && i < stmts.len - 1 { g.write(',') } } g.indent-- if g.inside_ternary > 0 { g.write('') g.write(')') } if g.is_autofree && !g.inside_vweb_tmpl && stmts.len > 0 { // use the first stmt to get the scope stmt := stmts[0] if stmt !is ast.FnDecl && g.inside_ternary == 0 { // g.trace_autofree('// autofree scope') // g.trace_autofree('// autofree_scope_vars($stmt.pos.pos) | ${typeof(stmt)}') // go back 1 position is important so we dont get the // internal scope of for loops and possibly other nodes // g.autofree_scope_vars(stmt.pos.pos - 1) mut stmt_pos := stmt.pos if stmt_pos.pos == 0 { // Do not autofree if the position is 0, since the correct scope won't be found. // Report a bug, since position shouldn't be 0 for most nodes. if stmt is ast.Module { return last_stmt_was_return } if stmt is ast.ExprStmt { // For some reason ExprStmt.pos is 0 when ExprStmt.expr is comp if expr // Extract the pos. TODO figure out why and fix. stmt_pos = stmt.expr.pos() } if stmt_pos.pos == 0 { $if trace_autofree ? { println('autofree: first stmt pos = 0. ${stmt.type_name()}') } return last_stmt_was_return } } g.autofree_scope_vars(stmt_pos.pos - 1, stmt_pos.line_nr, false) } } return last_stmt_was_return } // expr_with_tmp_var is used in assign expr to `option` or `result` type. // applicable to situations where the expr_typ does not have `option` and `result`, // e.g. field default: "foo ?int = 1", field assign: "foo = 1", field init: "foo: 1" fn (mut g Gen) expr_with_tmp_var(expr ast.Expr, expr_typ ast.Type, ret_typ ast.Type, tmp_var string) { if !ret_typ.has_flag(.option) && !ret_typ.has_flag(.result) { panic('cgen: parameter `ret_typ` of function `expr_with_tmp_var()` must be an Option or Result') } stmt_str := g.go_before_stmt(0).trim_space() mut styp := g.base_type(ret_typ) g.empty_line = true if g.table.sym(expr_typ).kind == .none_ { g.write('${g.typ(ret_typ)} ${tmp_var} = ') g.gen_option_error(ret_typ, expr) g.writeln(';') } else { mut is_ptr_to_ptr_assign := false if ret_typ.has_flag(.generic) { if expr is ast.SelectorExpr && g.cur_concrete_types.len == 0 { // resolve generic struct on selectorExpr inside non-generic function if expr.expr is ast.Ident && expr.expr.obj is ast.Var { if (expr.expr.obj as ast.Var).expr is ast.StructInit { g.cur_concrete_types << (g.table.sym(expr.expr.obj.typ).info as ast.Struct).concrete_types } } } styp = g.base_type(g.unwrap_generic(ret_typ)) ret_styp := g.typ(g.unwrap_generic(ret_typ)).replace('*', '_ptr') g.writeln('${ret_styp} ${tmp_var};') } else { g.writeln('${g.typ(ret_typ)} ${tmp_var};') } if ret_typ.has_flag(.option) { if expr_typ.has_flag(.option) && expr in [ast.StructInit, ast.ArrayInit, ast.MapInit] { if expr is ast.StructInit && expr.init_fields.len > 0 { g.write('_option_ok(&(${styp}[]) { ') } else { g.write('_option_none(&(${styp}[]) { ') } } else { is_ptr_to_ptr_assign = (expr is ast.SelectorExpr || (expr is ast.Ident && !expr.is_auto_heap())) && ret_typ.is_ptr() && expr_typ.is_ptr() && expr_typ.has_flag(.option) // option ptr assignment simplification if is_ptr_to_ptr_assign { g.write('${tmp_var} = ') } else { g.write('_option_ok(&(${styp}[]) { ') } if !expr_typ.is_ptr() && ret_typ.is_ptr() { g.write('&/*ref*/') } } } else { g.write('_result_ok(&(${styp}[]) { ') } g.expr_with_cast(expr, expr_typ, ret_typ) if ret_typ.has_flag(.option) { if is_ptr_to_ptr_assign { g.writeln(';') } else { g.writeln(' }, (${c.option_name}*)(&${tmp_var}), sizeof(${styp}));') } } else { g.writeln(' }, (${c.result_name}*)(&${tmp_var}), sizeof(${styp}));') } } g.write(stmt_str) g.write(' ') g.write(tmp_var) } [inline] fn (mut g Gen) write_v_source_line_info(pos token.Pos) { if g.inside_ternary == 0 && g.pref.is_vlines && g.is_vlines_enabled { nline := pos.line_nr + 1 lineinfo := '\n#line ${nline} "${g.vlines_path}"' $if trace_gen_source_line_info ? { eprintln('> lineinfo: ${lineinfo.replace('\n', '')}') } g.writeln(lineinfo) } } fn (mut g Gen) stmt(node ast.Stmt) { g.inside_call = false if !g.skip_stmt_pos { g.set_current_pos_as_last_stmt_pos() } match node { ast.AsmStmt { g.write_v_source_line_info(node.pos) g.asm_stmt(node) } ast.AssertStmt { g.write_v_source_line_info(node.pos) g.assert_stmt(node) } ast.AssignStmt { g.write_v_source_line_info(node.pos) g.assign_stmt(node) } ast.Block { g.write_v_source_line_info(node.pos) if node.is_unsafe { g.writeln('{ // Unsafe block') } else { g.writeln('{') } g.stmts(node.stmts) g.writeln('}') } ast.BranchStmt { g.write_v_source_line_info(node.pos) g.branch_stmt(node) } ast.ConstDecl { g.write_v_source_line_info(node.pos) g.const_decl(node) } ast.ComptimeFor { g.comptime_for(node) } ast.DeferStmt { mut defer_stmt := node defer_stmt.ifdef = g.defer_ifdef g.writeln('${g.defer_flag_var(defer_stmt)} = true;') g.defer_stmts << defer_stmt } ast.EmptyStmt {} ast.EnumDecl { g.enum_decl(node) } ast.ExprStmt { g.write_v_source_line_info(node.pos) // af := g.autofree && node.expr is ast.CallExpr && !g.is_builtin_mod // if af { // g.autofree_call_pregen(node.expr as ast.CallExpr) // } old_is_void_expr_stmt := g.is_void_expr_stmt g.is_void_expr_stmt = !node.is_expr if node.typ != ast.void_type && g.expected_cast_type != 0 && node.expr !in [ast.IfExpr, ast.MatchExpr] { g.expr_with_cast(node.expr, node.typ, g.expected_cast_type) } else { g.expr(node.expr) } g.is_void_expr_stmt = old_is_void_expr_stmt // if af { // g.autofree_call_postgen() // } if g.inside_ternary == 0 && !g.inside_if_option && !g.inside_match_option && !g.inside_if_result && !g.inside_match_result && !node.is_expr && node.expr !is ast.IfExpr { if node.expr is ast.MatchExpr { g.writeln('') } else { g.writeln(';') } } } ast.FnDecl { g.fn_decl(node) } ast.ForCStmt { prev_branch_parent_pos := g.branch_parent_pos g.branch_parent_pos = node.pos.pos save_inner_loop := g.inner_loop g.inner_loop = unsafe { &node } if node.label != '' { unsafe { g.labeled_loops[node.label] = &node } } g.write_v_source_line_info(node.pos) g.for_c_stmt(node) g.branch_parent_pos = prev_branch_parent_pos g.labeled_loops.delete(node.label) g.inner_loop = save_inner_loop } ast.ForInStmt { prev_branch_parent_pos := g.branch_parent_pos g.branch_parent_pos = node.pos.pos save_inner_loop := g.inner_loop g.inner_loop = unsafe { &node } if node.label != '' { unsafe { g.labeled_loops[node.label] = &node } } g.write_v_source_line_info(node.pos) g.for_in_stmt(node) g.branch_parent_pos = prev_branch_parent_pos g.labeled_loops.delete(node.label) g.inner_loop = save_inner_loop } ast.ForStmt { prev_branch_parent_pos := g.branch_parent_pos g.branch_parent_pos = node.pos.pos save_inner_loop := g.inner_loop g.inner_loop = unsafe { &node } if node.label != '' { unsafe { g.labeled_loops[node.label] = &node } } g.write_v_source_line_info(node.pos) g.for_stmt(node) g.branch_parent_pos = prev_branch_parent_pos g.labeled_loops.delete(node.label) g.inner_loop = save_inner_loop } ast.GlobalDecl { g.global_decl(node) } ast.GotoLabel { g.writeln('${c_name(node.name)}: {}') } ast.GotoStmt { g.write_v_source_line_info(node.pos) g.writeln('goto ${c_name(node.name)};') } ast.HashStmt { g.hash_stmt(node) } ast.Import {} ast.InterfaceDecl { // definitions are sorted and added in write_types ts := g.table.sym(node.typ) if !(ts.info as ast.Interface).is_generic { for method in node.methods { if method.return_type.has_flag(.option) { // Register an option if it's not registered yet g.register_option(method.return_type) } if method.return_type.has_flag(.result) { // Register a result if it's not registered yet g.register_result(method.return_type) } } } } ast.Module { g.is_builtin_mod = util.module_is_builtin(node.name) g.cur_mod = node } ast.NodeError {} ast.Return { g.return_stmt(node) } ast.SqlStmt { g.sql_stmt(node) } ast.StructDecl { name := if node.language == .c { util.no_dots(node.name) } else if node.name in ['array', 'string'] { node.name } else { c_name(node.name) } // TODO For some reason, build fails with autofree with this line // as it's only informative, comment it for now // g.gen_attrs(node.attrs) // g.writeln('typedef struct {') // for field in it.fields { // field_type_sym := g.table.sym(field.typ) // g.writeln('\t$field_type_sym.name $field.name;') // } // g.writeln('} $name;') if node.language == .c { return } if node.is_union { g.typedefs.writeln('typedef union ${name} ${name};') } else { g.typedefs.writeln('typedef struct ${name} ${name};') } } ast.TypeDecl { if !g.pref.skip_unused { g.writeln('// TypeDecl') } } } if !g.skip_stmt_pos { // && g.stmt_path_pos.len > 0 { g.stmt_path_pos.delete_last() } // If we have temporary string exprs to free after this statement, do it. e.g.: // `foo('a' + 'b')` => `tmp := 'a' + 'b'; foo(tmp); string_free(&tmp);` if g.is_autofree { // if node is ast.ExprStmt {&& node.expr is ast.CallExpr { if node !is ast.FnDecl { // p := node.pos() // g.autofree_call_postgen(p.pos) } } } fn (mut g Gen) write_defer_stmts() { g.indent++ for i := g.defer_stmts.len - 1; i >= 0; i-- { defer_stmt := g.defer_stmts[i] g.writeln('// Defer begin') g.writeln('if (${g.defer_flag_var(defer_stmt)}) {') g.indent++ if defer_stmt.ifdef.len > 0 { g.writeln(defer_stmt.ifdef) g.stmts(defer_stmt.stmts) g.writeln('') g.writeln('#endif') } else { g.indent-- g.stmts(defer_stmt.stmts) g.indent++ } g.indent-- g.writeln('}') g.writeln('// Defer end') } g.indent-- } struct SumtypeCastingFn { fn_name string got ast.Type exp ast.Type } fn (mut g Gen) get_sumtype_casting_fn(got_ ast.Type, exp_ ast.Type) string { got, exp := got_.idx(), exp_.idx() i := got | int(u32(exp) << 16) got_cname, exp_cname := g.table.sym(got).cname, g.table.sym(exp).cname fn_name := '${got_cname}_to_sumtype_${exp_cname}' if got == exp || g.sumtype_definitions[i] { return fn_name } g.sumtype_definitions[i] = true g.sumtype_casting_fns << SumtypeCastingFn{ fn_name: fn_name got: got exp: exp } return fn_name } fn (mut g Gen) write_sumtype_casting_fn(fun SumtypeCastingFn) { got, exp := fun.got, fun.exp got_sym, exp_sym := g.table.sym(got), g.table.sym(exp) mut got_cname, exp_cname := got_sym.cname, exp_sym.cname mut type_idx := g.type_sidx(got) mut sb := strings.new_builder(128) mut is_anon_fn := false if got_sym.info is ast.FnType { got_name := 'fn ${g.table.fn_type_source_signature(got_sym.info.func)}' got_cname = 'anon_fn_${g.table.fn_type_signature(got_sym.info.func)}' type_idx = g.table.type_idxs[got_name].str() exp_info := exp_sym.info as ast.SumType for variant in exp_info.variants { variant_sym := g.table.sym(variant) if variant_sym.info is ast.FnType { if g.table.fn_type_source_signature(variant_sym.info.func) == g.table.fn_type_source_signature(got_sym.info.func) { got_cname = variant_sym.cname type_idx = variant.idx().str() break } } } sb.writeln('static inline ${exp_cname} ${fun.fn_name}(${got_cname} x) {') sb.writeln('\t${got_cname} ptr = x;') is_anon_fn = true } if !is_anon_fn { sb.writeln('static inline ${exp_cname} ${fun.fn_name}(${got_cname}* x) {') sb.writeln('\t${got_cname}* ptr = memdup(x, sizeof(${got_cname}));') } for embed_hierarchy in g.table.get_embeds(got_sym) { // last embed in the hierarchy mut embed_cname := '' mut embed_name := '' mut accessor := '&x->' for j, embed in embed_hierarchy { embed_sym := g.table.sym(embed) embed_cname = embed_sym.cname embed_name = embed_sym.embed_name() if j > 0 { accessor += '.' } accessor += embed_name } // if the variable is not used, the C compiler will optimize it away sb.writeln('\t${embed_cname}* ${embed_name}_ptr = memdup(${accessor}, sizeof(${embed_cname}));') } sb.write_string('\treturn (${exp_cname}){ ._${got_cname} = ptr, ._typ = ${type_idx}') for field in (exp_sym.info as ast.SumType).fields { mut ptr := 'ptr' mut type_cname := got_cname _, embed_types := g.table.find_field_from_embeds(got_sym, field.name) or { ast.StructField{}, []ast.Type{} } if embed_types.len > 0 { embed_sym := g.table.sym(embed_types.last()) ptr = '${embed_sym.embed_name()}_ptr' type_cname = embed_sym.cname } field_styp := g.typ(field.typ) if got_sym.kind in [.sum_type, .interface_] { // the field is already a wrapped pointer; we shouldn't wrap it once again sb.write_string(', .${field.name} = ptr->${field.name}') } else { sb.write_string(', .${field.name} = (${field_styp}*)((char*)${ptr} + __offsetof_ptr(${ptr}, ${type_cname}, ${field.name}))') } } sb.writeln('};\n}') g.auto_fn_definitions << sb.str() } fn (mut g Gen) call_cfn_for_casting_expr(fname string, expr ast.Expr, exp_is_ptr bool, exp_styp string, got_is_ptr bool, got_is_fn bool, got_styp string) { mut rparen_n := 1 if exp_is_ptr { g.write('HEAP(${exp_styp}, ') rparen_n++ } g.write('${fname}(') if !got_is_ptr && !got_is_fn { if !expr.is_lvalue() || (expr is ast.Ident && expr.obj.is_simple_define_const()) { // Note: the `_to_sumtype_` family of functions do call memdup internally, making // another duplicate with the HEAP macro is redundant, so use ADDR instead: promotion_macro_name := if fname.contains('_to_sumtype_') { 'ADDR' } else { 'HEAP' } g.write('${promotion_macro_name}(${got_styp}, (') rparen_n += 2 } else { g.write('&') } } if got_styp == 'none' && !g.cur_fn.return_type.has_flag(.option) { g.write('(none){EMPTY_STRUCT_INITIALIZATION}') } else { g.expr(expr) } g.write(')'.repeat(rparen_n)) } // use instead of expr() when you need to cast to a different type fn (mut g Gen) expr_with_cast(expr ast.Expr, got_type_raw ast.Type, expected_type ast.Type) { got_type := ast.mktyp(got_type_raw) exp_sym := g.table.sym(expected_type) got_sym := g.table.sym(got_type) expected_is_ptr := expected_type.is_ptr() got_is_ptr := got_type.is_ptr() // allow using the new Error struct as a string, to avoid a breaking change // TODO: temporary to allow people to migrate their code; remove soon if got_type == ast.error_type_idx && expected_type == ast.string_type_idx { g.write('(*(') g.expr(expr) g.write('.msg))') return } if got_sym.kind == .none_ && exp_sym.idx == ast.error_type_idx { g.expr(expr) return } if got_sym.info !is ast.Interface && exp_sym.info is ast.Interface && got_type.idx() != expected_type.idx() && !expected_type.has_flag(.result) { if expr is ast.StructInit && !got_type.is_ptr() { g.inside_cast_in_heap++ got_styp := g.cc_type(got_type.ref(), true) // TODO: why does cc_type even add this in the first place? exp_styp := exp_sym.cname mut fname := 'I_${got_styp}_to_Interface_${exp_styp}' if exp_sym.info.is_generic { fname = g.generic_fn_name(exp_sym.info.concrete_types, fname) } g.call_cfn_for_casting_expr(fname, expr, expected_is_ptr, exp_styp, true, false, got_styp) g.inside_cast_in_heap-- } else { got_styp := g.cc_type(got_type, true) got_is_shared := got_type.has_flag(.shared_f) exp_styp := if got_is_shared { '__shared__${exp_sym.cname}' } else { exp_sym.cname } // If it's shared, we need to use the other caster: mut fname := if got_is_shared { 'I___shared__${got_styp}_to_shared_Interface_${exp_styp}' } else { 'I_${got_styp}_to_Interface_${exp_styp}' } lock g.referenced_fns { g.referenced_fns[fname] = true } fname = '/*${exp_sym}*/${fname}' if exp_sym.info.is_generic { fname = g.generic_fn_name(exp_sym.info.concrete_types, fname) } g.call_cfn_for_casting_expr(fname, expr, expected_is_ptr, exp_styp, got_is_ptr, false, got_styp) } return } // cast to sum type exp_styp := g.typ(expected_type) mut got_styp := g.typ(got_type) mut got_is_fn := false if got_sym.info is ast.FnType { if got_sym.info.is_anon { got_styp = 'anon_fn_${g.table.fn_type_signature(got_sym.info.func)}' } got_is_fn = true } if expected_type != ast.void_type { unwrapped_expected_type := g.unwrap_generic(expected_type) unwrapped_exp_sym := g.table.sym(unwrapped_expected_type) mut unwrapped_got_type := g.unwrap_generic(got_type) mut unwrapped_got_sym := g.table.sym(unwrapped_got_type) expected_deref_type := if expected_is_ptr { unwrapped_expected_type.deref() } else { unwrapped_expected_type } got_deref_type := if got_is_ptr { unwrapped_got_type.deref() } else { unwrapped_got_type } if g.table.sumtype_has_variant(expected_deref_type, got_deref_type, false) { mut is_already_sum_type := false scope := g.file.scope.innermost(expr.pos().pos) if expr is ast.Ident { if v := scope.find_var(expr.name) { if v.smartcasts.len > 0 && unwrapped_expected_type == v.orig_type { is_already_sum_type = true } } } else if expr is ast.SelectorExpr { if _ := scope.find_struct_field(expr.expr.str(), expr.expr_type, expr.field_name) { is_already_sum_type = true } } if is_already_sum_type && !g.inside_return { // Don't create a new sum type wrapper if there is already one g.prevent_sum_type_unwrapping_once = true g.expr(expr) } else { if mut unwrapped_got_sym.info is ast.Aggregate { unwrapped_got_type = unwrapped_got_sym.info.types[g.aggregate_type_idx] unwrapped_got_sym = g.table.sym(unwrapped_got_type) } fname := g.get_sumtype_casting_fn(unwrapped_got_type, unwrapped_expected_type) if expr is ast.ArrayInit && got_sym.kind == .array_fixed { stmt_str := g.go_before_stmt(0).trim_space() g.empty_line = true tmp_var := g.new_tmp_var() g.write('${got_styp} ${tmp_var} = ') g.expr(expr) g.writeln(';') g.write(stmt_str) g.write(' ') g.write('${fname}(&${tmp_var})') return } else { g.call_cfn_for_casting_expr(fname, expr, expected_is_ptr, unwrapped_exp_sym.cname, got_is_ptr, got_is_fn, got_styp) } } return } } // Generic dereferencing logic neither_void := ast.voidptr_type !in [got_type, expected_type] && ast.nil_type !in [got_type, expected_type] if expected_type.has_flag(.shared_f) && !got_type_raw.has_flag(.shared_f) && !expected_type.has_flag(.option) && !expected_type.has_flag(.result) { shared_styp := exp_styp[0..exp_styp.len - 1] // `shared` implies ptr, so eat one `*` if got_type_raw.is_ptr() { g.error('cannot convert reference to `shared`', expr.pos()) } if exp_sym.kind == .array { g.writeln('(${shared_styp}*)__dup_shared_array(&(${shared_styp}){.mtx = {0}, .val =') } else if exp_sym.kind == .map { g.writeln('(${shared_styp}*)__dup_shared_map(&(${shared_styp}){.mtx = {0}, .val =') } else { g.writeln('(${shared_styp}*)__dup${shared_styp}(&(${shared_styp}){.mtx = {0}, .val =') } old_is_shared := g.is_shared g.is_shared = false g.expr(expr) g.is_shared = old_is_shared g.writeln('}, sizeof(${shared_styp}))') return } else if got_type_raw.has_flag(.shared_f) && !expected_type.has_flag(.shared_f) { if expected_type.is_ptr() { g.write('&') } g.expr(expr) g.write('->val') return } if got_is_ptr && !expected_is_ptr && neither_void && exp_sym.kind != .placeholder && expr !is ast.InfixExpr { got_deref_type := got_type.deref() deref_sym := g.table.sym(got_deref_type) deref_will_match := expected_type in [got_type, got_deref_type, deref_sym.parent_idx] got_is_opt_or_res := got_type.has_flag(.option) || got_type.has_flag(.result) if deref_will_match || got_is_opt_or_res || expr.is_auto_deref_var() { g.write('*') } } if expr is ast.IntegerLiteral { if expected_type in [ast.u64_type, ast.u32_type, ast.u16_type] && expr.val[0] != `-` { g.expr(expr) g.write('U') return } } if exp_sym.kind == .function && !expected_type.has_flag(.option) && !expected_type.has_flag(.result) { g.write('(voidptr)') } // no cast g.expr(expr) } fn write_octal_escape(mut b strings.Builder, c u8) { b << 92 // \ b << 48 + (c >> 6) // oct digit 2 b << 48 + (c >> 3) & 7 // oct digit 1 b << 48 + c & 7 // oct digit 0 } fn cescape_nonascii(original string) string { mut b := strings.new_builder(original.len) for c in original { if c < 32 || c > 126 { // Encode with a 3 digit octal escape code, which has the // advantage to be limited/non dependent on what character // will follow next, unlike hex escapes: write_octal_escape(mut b, c) continue } b.write_u8(c) } res := b.str() return res } // cestring returns a V string, properly escaped for embeddeding in a C string literal. fn cestring(s string) string { return s.replace('\\', '\\\\').replace('"', "'") } // ctoslit returns a '_SLIT("$s")' call, where s is properly escaped. fn ctoslit(s string) string { return '_SLIT("' + cescape_nonascii(cestring(s)) + '")' } fn (mut g Gen) gen_attrs(attrs []ast.Attr) { if g.pref.skip_unused { return } for attr in attrs { g.writeln('// Attr: [${attr.name}]') } } fn (mut g Gen) asm_stmt(stmt ast.AsmStmt) { g.write('__asm__') if stmt.is_volatile { g.write(' volatile') } if stmt.is_goto { g.write(' goto') } g.writeln(' (') g.indent++ for template_tmp in stmt.templates { mut template := template_tmp g.write('"') if template.is_directive { g.write('.') } g.write(template.name) if template.is_label { g.write(':') } else { g.write(' ') } // swap destionation and operands for att syntax if template.args.len != 0 && !template.is_directive { template.args.prepend(template.args.last()) template.args.delete(template.args.len - 1) } for i, arg in template.args { if stmt.arch == .amd64 && (template.name == 'call' || template.name[0] == `j`) && arg is ast.AsmRegister { g.write('*') // indirect branching } g.asm_arg(arg, stmt) if i + 1 < template.args.len { g.write(', ') } } if !template.is_label { g.write(';') } g.writeln('"') } if stmt.output.len != 0 || stmt.input.len != 0 || stmt.clobbered.len != 0 || stmt.is_goto { g.write(': ') } g.gen_asm_ios(stmt.output) if stmt.input.len != 0 || stmt.clobbered.len != 0 || stmt.is_goto { g.write(': ') } g.gen_asm_ios(stmt.input) if stmt.clobbered.len != 0 || stmt.is_goto { g.write(': ') } for i, clob in stmt.clobbered { g.write('"') g.write(clob.reg.name) g.write('"') if i + 1 < stmt.clobbered.len { g.writeln(',') } else { g.writeln('') } } if stmt.is_goto { g.write(': ') } for i, label in stmt.global_labels { g.write(label) if i + 1 < stmt.clobbered.len { g.writeln(',') } else { g.writeln('') } } g.indent-- g.writeln(');') } fn (mut g Gen) asm_arg(arg ast.AsmArg, stmt ast.AsmStmt) { match arg { ast.AsmAlias { name := arg.name if name in stmt.local_labels || name in stmt.global_labels || name in g.file.global_labels || stmt.is_basic || (name !in stmt.input.map(it.alias) && name !in stmt.output.map(it.alias)) { asm_formatted_name := if name in stmt.global_labels { '%l[${name}]' } else { name } g.write(asm_formatted_name) } else { g.write('%[${name}]') } } ast.CharLiteral { g.write("'${arg.val}'") } ast.IntegerLiteral { g.write('\$${arg.val}') } ast.FloatLiteral { if g.pref.nofloat { g.write('\$${arg.val.int()}') } else { g.write('\$${arg.val}') } } ast.BoolLiteral { g.write('\$${arg.val.str()}') } ast.AsmRegister { if !stmt.is_basic { g.write('%') // escape percent with percent in extended assembly } g.write('%${arg.name}') } ast.AsmAddressing { if arg.segment != '' { g.write('%%${arg.segment}:') } base := arg.base index := arg.index displacement := arg.displacement scale := arg.scale match arg.mode { .base { g.write('(') g.asm_arg(base, stmt) g.write(')') } .displacement { g.asm_arg(displacement, stmt) } .base_plus_displacement { g.asm_arg(displacement, stmt) g.write('(') g.asm_arg(base, stmt) g.write(')') } .index_times_scale_plus_displacement { if displacement is ast.AsmDisp { g.asm_arg(displacement, stmt) g.write('(, ') } else if displacement is ast.AsmRegister { g.write('(') g.asm_arg(displacement, stmt) g.write(',') } else { panic('unexpected ${displacement.type_name()}') } g.asm_arg(index, stmt) g.write(',${scale})') } .base_plus_index_plus_displacement { g.asm_arg(displacement, stmt) g.write('(') g.asm_arg(base, stmt) g.write(',') g.asm_arg(index, stmt) g.write(',1)') } .base_plus_index_times_scale_plus_displacement { g.asm_arg(displacement, stmt) g.write('(') g.asm_arg(base, stmt) g.write(',') g.asm_arg(index, stmt) g.write(',${scale})') } .rip_plus_displacement { g.asm_arg(displacement, stmt) g.write('(') g.asm_arg(base, stmt) g.write(')') } .invalid { g.error('invalid addressing mode', arg.pos) } } } ast.AsmDisp { g.write(arg.val) } string { g.write(arg) } } } fn (mut g Gen) gen_asm_ios(ios []ast.AsmIO) { for i, io in ios { if io.alias != '' { g.write('[${io.alias}] ') } g.write('"${io.constraint}" (') g.expr(io.expr) g.write(')') if i + 1 < ios.len { g.writeln(',') } else { g.writeln('') } } } fn cnewlines(s string) string { return s.replace('\n', r'\n') } fn (mut g Gen) write_fn_ptr_decl(func &ast.FnType, ptr_name string) { ret_styp := g.typ(func.func.return_type) g.write('${ret_styp} (*${ptr_name}) (') arg_len := func.func.params.len for i, arg in func.func.params { arg_styp := g.typ(arg.typ) g.write('${arg_styp} ${arg.name}') if i < arg_len - 1 { g.write(', ') } } g.write(')') } fn (mut g Gen) register_ternary_name(name string) { level_key := g.inside_ternary.str() if level_key !in g.ternary_level_names { g.ternary_level_names[level_key] = []string{} } new_name := g.new_tmp_var() g.ternary_names[name] = new_name g.ternary_level_names[level_key] << name } fn (mut g Gen) get_ternary_name(name string) string { if g.inside_ternary == 0 { return name } if name !in g.ternary_names { return name } return g.ternary_names[name] } fn (mut g Gen) gen_clone_assignment(val ast.Expr, typ ast.Type, add_eq bool) bool { if val !in [ast.Ident, ast.SelectorExpr] { return false } right_sym := g.table.sym(typ) if g.is_autofree { if add_eq { g.write('=') } if right_sym.kind == .array { // `arr1 = arr2` => `arr1 = arr2.clone()` shared_styp := g.typ(typ.set_nr_muls(0)) if typ.share() == .shared_t { g.write('(${shared_styp}*)__dup_shared_array(&(${shared_styp}){.mtx = {0}, .val =') } g.write(' array_clone_static_to_depth(') g.expr(val) if typ.share() == .shared_t { g.write('->val') } elem_type := (right_sym.info as ast.Array).elem_type array_depth := g.get_array_depth(elem_type) g.write(', ${array_depth})') if typ.share() == .shared_t { g.write('}, sizeof(${shared_styp}))') } } else if right_sym.kind == .string { // `str1 = str2` => `str1 = str2.clone()` g.write(' string_clone_static(') g.expr(val) g.write(')') } } return true } fn (mut g Gen) autofree_scope_vars(pos int, line_nr int, free_parent_scopes bool) { g.autofree_scope_vars_stop(pos, line_nr, free_parent_scopes, -1) } fn (mut g Gen) autofree_scope_vars_stop(pos int, line_nr int, free_parent_scopes bool, stop_pos int) { if g.is_builtin_mod { // In `builtin` everything is freed manually. return } if pos == -1 { // TODO why can pos be -1? return } // eprintln('> free_scope_vars($pos)') scope := g.file.scope.innermost(pos) if scope.start_pos == 0 { // TODO why can scope.pos be 0? (only outside fns?) return } g.trace_autofree('// autofree_scope_vars(pos=${pos} line_nr=${line_nr} scope.pos=${scope.start_pos} scope.end_pos=${scope.end_pos})') g.autofree_scope_vars2(scope, scope.start_pos, scope.end_pos, line_nr, free_parent_scopes, stop_pos) } [if trace_autofree ?] fn (mut g Gen) trace_autofree(line string) { g.writeln(line) } fn (mut g Gen) autofree_scope_vars2(scope &ast.Scope, start_pos int, end_pos int, line_nr int, free_parent_scopes bool, stop_pos int) { if scope == unsafe { nil } { return } for _, obj in scope.objects { match obj { ast.Var { g.trace_autofree('// var "${obj.name}" var.pos=${obj.pos.pos} var.line_nr=${obj.pos.line_nr}') if obj.name == g.returned_var_name { g.trace_autofree('// skipping returned var') continue } if obj.is_or { // Skip vars inited with the `or {}`, since they are generated // after the or block in C. g.trace_autofree('// skipping `or {}` var "${obj.name}"') continue } if obj.is_tmp { // Skip for loop vars g.trace_autofree('// skipping tmp var "${obj.name}"') continue } if obj.is_inherited { g.trace_autofree('// skipping inherited var "${obj.name}"') continue } // if var.typ == 0 { // // TODO why 0? // continue // } // if v.pos.pos > end_pos { if obj.pos.pos > end_pos || (obj.pos.pos < start_pos && obj.pos.line_nr == line_nr) { // Do not free vars that were declared after this scope continue } is_option := obj.typ.has_flag(.option) if is_option { // TODO: free options continue } g.autofree_variable(obj) } else {} } } for g.autofree_scope_stmts.len > 0 { g.write(g.autofree_scope_stmts.pop()) } // Free all vars in parent scopes as well: // ``` // s := ... // if ... { // s.free() // return // } // ``` // if scope.parent != unsafe { nil } && line_nr > 0 { if free_parent_scopes && scope.parent != unsafe { nil } && !scope.detached_from_parent && (stop_pos == -1 || scope.parent.start_pos >= stop_pos) { g.trace_autofree('// af parent scope:') g.autofree_scope_vars2(scope.parent, start_pos, end_pos, line_nr, true, stop_pos) } } fn (mut g Gen) autofree_variable(v ast.Var) { // filter out invalid variables if v.typ == 0 { return } sym := g.table.sym(v.typ) // if v.name.contains('output2') { if g.is_autofree { // eprintln(' > var name: ${v.name:-20s} | is_arg: ${v.is_arg.str():6} | var type: ${int(v.typ):8} | type_name: ${sym.name:-33s}') } // } free_fn := g.typ(v.typ.set_nr_muls(0)) + '_free' if sym.kind == .array { if sym.has_method('free') { g.autofree_var_call(free_fn, v) return } g.autofree_var_call('array_free', v) return } if sym.kind == .string { // Don't free simple string literals. match v.expr { ast.StringLiteral { g.trace_autofree('// str literal') } else { // NOTE/TODO: assign_stmt multi returns variables have no expr // since the type comes from the called fns return type /* f := v.name[0] if //!(f >= `a` && f <= `d`) { //f != `c` { v.name!='cvar_name' { t := typeof(v.expr) return '// other ' + t + '\n' } */ } } g.autofree_var_call('string_free', v) return } if g.pref.experimental && v.typ.is_ptr() && sym.name.after('.')[0].is_capital() { // Free user reference types g.autofree_var_call('free', v) } if sym.has_method('free') { g.autofree_var_call(free_fn, v) } } fn (mut g Gen) autofree_var_call(free_fn_name string, v ast.Var) { if v.is_arg { // fn args should not be autofreed return } if v.is_used && v.is_autofree_tmp { // tmp expr vars do not need to be freed again here return } if g.is_builtin_mod { return } if !g.is_autofree { return } // if v.is_autofree_tmp && !g.doing_autofree_tmp { // return // } if v.name.contains('expr_write_string_1_') { // TODO remove this temporary hack return } mut af := strings.new_builder(128) if v.typ.is_ptr() { af.write_string('\t') if v.typ.share() == .shared_t { af.write_string(free_fn_name.replace_each(['__shared__', ''])) } else { af.write_string(free_fn_name) } af.write_string('(') if v.typ.share() == .shared_t { af.write_string('&') } af.write_string(strings.repeat(`*`, v.typ.nr_muls() - 1)) // dereference if it is a pointer to a pointer af.write_string(c_name(v.name)) if v.typ.share() == .shared_t { af.write_string('->val') } af.writeln('); // autofreed ptr var') } else { if v.typ == ast.error_type && !v.is_autofree_tmp { return } if v.is_auto_heap { af.writeln('\t${free_fn_name}(${c_name(v.name)}); // autofreed heap var ${g.cur_mod.name} ${g.is_builtin_mod}') } else { af.writeln('\t${free_fn_name}(&${c_name(v.name)}); // autofreed var ${g.cur_mod.name} ${g.is_builtin_mod}') } } g.autofree_scope_stmts << af.str() } fn (mut g Gen) map_fn_ptrs(key_typ ast.TypeSymbol) (string, string, string, string) { mut hash_fn := '' mut key_eq_fn := '' mut clone_fn := '' mut free_fn := '&map_free_nop' match key_typ.kind { .alias { alias_key_type := (key_typ.info as ast.Alias).parent_type return g.map_fn_ptrs(g.table.sym(alias_key_type)) } .u8, .i8, .char { hash_fn = '&map_hash_int_1' key_eq_fn = '&map_eq_int_1' clone_fn = '&map_clone_int_1' } .i16, .u16 { hash_fn = '&map_hash_int_2' key_eq_fn = '&map_eq_int_2' clone_fn = '&map_clone_int_2' } .int, .u32, .rune, .f32, .enum_ { hash_fn = '&map_hash_int_4' key_eq_fn = '&map_eq_int_4' clone_fn = '&map_clone_int_4' } .voidptr { ts := if g.pref.m64 { unsafe { g.table.sym_by_idx(ast.u64_type_idx) } } else { unsafe { g.table.sym_by_idx(ast.u32_type_idx) } } return g.map_fn_ptrs(ts) } .u64, .i64, .f64 { hash_fn = '&map_hash_int_8' key_eq_fn = '&map_eq_int_8' clone_fn = '&map_clone_int_8' } .string { hash_fn = '&map_hash_string' key_eq_fn = '&map_eq_string' clone_fn = '&map_clone_string' free_fn = '&map_free_string' } else { verror('map key type not supported') } } return hash_fn, key_eq_fn, clone_fn, free_fn } fn (mut g Gen) expr(node_ ast.Expr) { old_discard_or_result := g.discard_or_result old_is_void_expr_stmt := g.is_void_expr_stmt if g.is_void_expr_stmt { g.discard_or_result = true g.is_void_expr_stmt = false } else { g.discard_or_result = false } // Note: please keep the type names in the match here in alphabetical order: mut node := unsafe { node_ } match mut node { ast.ComptimeType { g.error('g.expr(): Unhandled ComptimeType', node.pos) } ast.EmptyExpr { g.error('g.expr(): unhandled EmptyExpr', token.Pos{}) } ast.AnonFn { g.gen_anon_fn(mut node) } ast.ArrayDecompose { g.expr(node.expr) } ast.ArrayInit { g.array_init(node, '') } ast.AsCast { g.as_cast(node) } ast.Assoc { g.assoc(node) } ast.AtExpr { g.comptime_at(node) } ast.BoolLiteral { g.write(node.val.str()) } ast.CallExpr { ret_type := if node.or_block.kind == .absent { node.return_type } else { node.return_type.clear_flags(.option, .result) } mut shared_styp := '' if g.is_shared && !ret_type.has_flag(.shared_f) && !g.inside_or_block { ret_sym := g.table.sym(ret_type) shared_typ := ret_type.set_flag(.shared_f) shared_styp = g.typ(shared_typ) if ret_sym.kind == .array { g.writeln('(${shared_styp}*)__dup_shared_array(&(${shared_styp}){.mtx = {0}, .val =') } else if ret_sym.kind == .map { g.writeln('(${shared_styp}*)__dup_shared_map(&(${shared_styp}){.mtx = {0}, .val =') } else { g.writeln('(${shared_styp}*)__dup${shared_styp}(&(${shared_styp}){.mtx = {0}, .val =') } } stmt_before_call_expr_pos := if g.stmt_path_pos.len > 0 { g.stmt_path_pos.last() } else { 0 } g.call_expr(node) if g.is_autofree && !g.is_builtin_mod && !g.is_js_call && g.strs_to_free0.len == 0 && !g.inside_lambda { // if len != 0, that means we are handling call expr inside call expr (arg) // and it'll get messed up here, since it's handled recursively in autofree_call_pregen() // so just skip it g.autofree_call_pregen(node) if g.strs_to_free0.len > 0 { g.insert_at(stmt_before_call_expr_pos, g.strs_to_free0.join('\n') + '/* inserted before */') } g.strs_to_free0 = [] } if g.is_shared && !ret_type.has_flag(.shared_f) && !g.inside_or_block { g.writeln('}, sizeof(${shared_styp}))') } /* if g.autofree { s := g.autofree_pregen[node.pos.pos.str()] if s != '' { // g.insert_before_stmt('/*START2*/' + g.strs_to_free0.join('\n') + '/*END*/') // g.insert_before_stmt('/*START3*/' + node.autofree_pregen + '/*END*/') g.insert_before_stmt('/*START3*/' + s + '/*END*/') // for s in g.strs_to_free0 { } // //g.writeln(s) // } g.strs_to_free0 = [] } */ } ast.CastExpr { g.cast_expr(node) } ast.ChanInit { elem_typ_str := g.typ(node.elem_type) noscan := g.check_noscan(node.elem_type) g.write('sync__new_channel_st${noscan}(') if node.has_cap { g.expr(node.cap_expr) } else { g.write('0') } g.write(', sizeof(') g.write(elem_typ_str) g.write(')>0 ? sizeof(') g.write(elem_typ_str) g.write(') : 1)') } ast.CharLiteral { g.char_literal(node) } ast.Comment {} ast.ComptimeCall { g.comptime_call(mut node) } ast.ComptimeSelector { g.comptime_selector(node) } ast.ConcatExpr { g.concat_expr(node) } ast.CTempVar { g.write(node.name) } ast.DumpExpr { g.dump_expr(node) } ast.EnumVal { g.enum_val(node) } ast.FloatLiteral { if g.pref.nofloat { g.write(node.val.int().str()) } else { g.write(node.val) } } ast.SpawnExpr { g.spawn_and_go_expr(node, .spawn_) } ast.GoExpr { // XTODO this results in a cgen bug, order of fields is broken // g.spawn_and_go_expr(ast.SpawnExpr{node.pos, node.call_expr, node.is_expr}, g.spawn_and_go_expr(ast.SpawnExpr{ pos: node.pos call_expr: node.call_expr is_expr: node.is_expr }, .go_) } ast.Ident { g.ident(node) } ast.IfExpr { g.if_expr(node) } ast.IfGuardExpr { g.write('/* guard */') } ast.IndexExpr { g.index_expr(node) } ast.InfixExpr { if node.op in [.left_shift, .plus_assign, .minus_assign] { g.inside_map_infix = true g.infix_expr(node) g.inside_map_infix = false } else { g.infix_expr(node) } } ast.IntegerLiteral { if node.val.starts_with('0o') { g.write('0') g.write(node.val[2..]) } else if node.val.starts_with('-0o') { g.write('-0') g.write(node.val[3..]) } else { g.write(node.val) } } ast.IsRefType { typ := g.resolve_comptime_type(node.expr, g.get_type(node.typ)) node_typ := g.unwrap_generic(typ) sym := g.table.sym(node_typ) if sym.language == .v && sym.kind in [.placeholder, .any] { g.error('unknown type `${sym.name}`', node.pos) } is_ref_type := g.contains_ptr(node_typ) g.write('/*IsRefType*/ ${is_ref_type}') } ast.Likely { if node.is_likely { g.write('_likely_') } else { g.write('_unlikely_') } g.write('(') g.expr(node.expr) g.write(')') } ast.LockExpr { g.lock_expr(node) } ast.MapInit { g.map_init(node) } ast.MatchExpr { g.match_expr(node) } ast.NodeError {} ast.Nil { g.write('((void*)0)') } ast.None { g.write('_const_none__') } ast.OffsetOf { styp := g.typ(node.struct_type) g.write('/*OffsetOf*/ (u32)(__offsetof(${util.no_dots(styp)}, ${node.field}))') } ast.OrExpr { // this should never appear here } ast.ParExpr { g.write('(') g.expr(node.expr) g.write(')') } ast.PostfixExpr { if node.auto_locked != '' { g.writeln('sync__RwMutex_lock(&${node.auto_locked}->mtx);') } g.inside_map_postfix = true if node.is_c2v_prefix { g.write(node.op.str()) } if node.expr.is_auto_deref_var() { g.write('(*') g.expr(node.expr) g.write(')') } else if node.op == .question { cur_line := g.go_before_stmt(0).trim_space() mut expr_str := '' if mut node.expr is ast.ComptimeSelector && node.expr.left is ast.Ident { // val.$(field.name)? expr_str = '${node.expr.left.str()}.${g.comptime_for_field_value.name}' } else if mut node.expr is ast.Ident && g.is_comptime_var(node.expr) { // val? expr_str = node.expr.name } g.writeln('if (${expr_str}.state != 0) {') g.writeln('\tpanic_option_not_set(_SLIT("none"));') g.writeln('}') g.write(cur_line) typ := g.resolve_comptime_type(node.expr, node.typ) g.write('*(${g.base_type(typ)}*)&') g.expr(node.expr) g.write('.data') } else { g.expr(node.expr) } g.inside_map_postfix = false if !node.is_c2v_prefix && node.op != .question { g.write(node.op.str()) } if node.auto_locked != '' { g.writeln(';') g.write('sync__RwMutex_unlock(&${node.auto_locked}->mtx)') } } ast.PrefixExpr { gen_or := node.op == .arrow && (node.or_block.kind != .absent || node.is_option) if node.op == .amp { g.is_amp = true } if node.op == .arrow { styp := g.typ(node.right_type) right_sym := g.table.sym(node.right_type) mut right_inf := right_sym.info as ast.Chan elem_type := right_inf.elem_type is_gen_or_and_assign_rhs := gen_or && !g.discard_or_result cur_line := if is_gen_or_and_assign_rhs { line := g.go_before_stmt(0) g.out.write_string(util.tabs(g.indent)) line } else { '' } tmp_opt := if gen_or { g.new_tmp_var() } else { '' } if gen_or { opt_elem_type := g.typ(elem_type.set_flag(.option)) g.register_chan_pop_option_call(opt_elem_type, styp) g.write('${opt_elem_type} ${tmp_opt} = __Option_${styp}_popval(') } else { g.write('__${styp}_popval(') } g.expr(node.right) g.write(')') if gen_or { if !node.is_option { g.write('/*JJJ*/') g.or_block(tmp_opt, node.or_block, elem_type) } if is_gen_or_and_assign_rhs { elem_styp := g.typ(elem_type) g.write(';\n${cur_line}*(${elem_styp}*)${tmp_opt}.data') } } } else { if g.is_option_auto_heap { g.write('(${g.base_type(node.right_type)}*)') } if !g.is_option_auto_heap && !(g.is_amp && node.right.is_auto_deref_var()) { g.write(node.op.str()) } g.expr(node.right) if g.is_option_auto_heap { g.write('.data') } } g.is_amp = false } ast.RangeExpr { // Only used in IndexExpr } ast.SelectExpr { g.select_expr(node) } ast.SelectorExpr { g.selector_expr(node) } ast.SizeOf { g.size_of(node) } ast.SqlExpr { g.sql_select_expr(node) } ast.StringLiteral { g.string_literal(node) } ast.StringInterLiteral { g.string_inter_literal(node) } ast.StructInit { if node.unresolved { g.expr(g.table.resolve_init(node, g.unwrap_generic(node.typ))) } else { // `user := User{name: 'Bob'}` g.inside_struct_init = true g.cur_struct_init_typ = node.typ g.struct_init(node) g.cur_struct_init_typ = 0 g.inside_struct_init = false } } ast.TypeNode { // match sum Type typ := g.unwrap_generic(node.typ) sym := g.table.sym(typ) sidx := g.type_sidx(typ) g.write('${sidx} /* ${sym.name} */') } ast.TypeOf { g.typeof_expr(node) } ast.UnsafeExpr { g.expr(node.expr) } } g.discard_or_result = old_discard_or_result g.is_void_expr_stmt = old_is_void_expr_stmt } fn (mut g Gen) char_literal(node ast.CharLiteral) { if node.val == r'\`' { g.write("'`'") return } // TODO: optimize use L-char instead of u32 when possible if node.val.len_utf8() < node.val.len { g.write('((rune)0x${node.val.utf32_code().hex()} /* `${node.val}` */)') return } if node.val.len == 1 { clit := node.val[0] if clit < 32 || clit == 92 || clit > 126 { g.write("'") write_octal_escape(mut g.out, clit) g.write("'") return } } g.write("'${node.val}'") } // T.name, typeof(expr).name fn (mut g Gen) type_name(raw_type ast.Type) { typ := g.get_type(raw_type) sym := g.table.sym(typ) mut s := '' if sym.kind == .function { if typ.is_ptr() { s = '&' + g.fn_decl_str(sym.info as ast.FnType) } else { s = g.fn_decl_str(sym.info as ast.FnType) } } else { s = g.table.type_to_str(g.unwrap_generic(typ)) } g.write('_SLIT("${util.strip_main_name(s)}")') } fn (mut g Gen) typeof_expr(node ast.TypeOf) { typ := g.resolve_comptime_type(node.expr, g.get_type(node.typ)) sym := g.table.sym(typ) if sym.kind == .sum_type { // When encountering a .sum_type, typeof() should be done at runtime, // because the subtype of the expression may change: g.write('charptr_vstring_literal( /* ${sym.name} */ v_typeof_sumtype_${sym.cname}( (') g.expr(node.expr) g.write(')._typ ))') } else if sym.kind == .array_fixed { fixed_info := sym.info as ast.ArrayFixed typ_name := g.table.get_type_name(fixed_info.elem_type) g.write('_SLIT("[${fixed_info.size}]${util.strip_main_name(typ_name)}")') } else if sym.kind == .function { info := sym.info as ast.FnType g.write('_SLIT("${g.fn_decl_str(info)}")') } else if typ.has_flag(.variadic) { varg_elem_type_sym := g.table.sym(g.table.value_type(typ)) g.write('_SLIT("...${util.strip_main_name(varg_elem_type_sym.name)}")') } else { g.type_name(typ) } } fn (mut g Gen) selector_expr(node ast.SelectorExpr) { prevent_sum_type_unwrapping_once := g.prevent_sum_type_unwrapping_once g.prevent_sum_type_unwrapping_once = false if node.name_type > 0 { match node.gkind_field { .name { g.type_name(node.name_type) return } .typ { g.write(int(g.unwrap_generic(node.name_type)).str()) return } .unknown { // ast.TypeOf of `typeof(string).idx` etc if node.field_name == 'name' { // typeof(expr).name mut name_type := node.name_type if node.expr is ast.TypeOf { name_type = g.resolve_comptime_type(node.expr.expr, name_type) } g.type_name(name_type) return } else if node.field_name == 'idx' { mut name_type := node.name_type if node.expr is ast.TypeOf { name_type = g.resolve_comptime_type(node.expr.expr, name_type) } // `typeof(expr).idx` g.write(int(g.unwrap_generic(name_type)).str()) return } g.error('unknown generic field', node.pos) } } } if node.expr_type == 0 { g.checker_bug('unexpected SelectorExpr.expr_type = 0', node.pos) } sym := g.table.sym(g.unwrap_generic(node.expr_type)) field_name := if sym.language == .v { c_name(node.field_name) } else { node.field_name } if node.or_block.kind != .absent && !g.is_assign_lhs && g.table.sym(node.typ).kind != .chan { is_ptr := sym.kind in [.interface_, .sum_type] stmt_str := g.go_before_stmt(0).trim_space() styp := g.typ(node.typ) g.empty_line = true tmp_var := g.new_tmp_var() g.write('${styp} ${tmp_var} = ') if is_ptr { g.write('*(') } g.expr(node.expr) if node.expr_type.is_ptr() { g.write('->') } else { g.write('.') } g.write(field_name) if is_ptr { g.write(')') } g.or_block(tmp_var, node.or_block, node.typ) g.write(stmt_str) g.write(' ') unwrapped_typ := node.typ.clear_flags(.option, .result) unwrapped_styp := g.typ(unwrapped_typ) g.write('(*(${unwrapped_styp}*)${tmp_var}.data)') return } // if node expr is a root ident and an optional mut is_opt_or_res := node.expr is ast.Ident && (node.expr_type.has_flag(.option) || node.expr_type.has_flag(.result)) if is_opt_or_res { opt_base_typ := g.base_type(node.expr_type) g.write('(*(${opt_base_typ}*)') } if sym.kind == .array_fixed { if node.field_name != 'len' { g.error('field_name should be `len`', node.pos) } info := sym.info as ast.ArrayFixed g.write('${info.size}') return } else if sym.kind == .chan && (node.field_name == 'len' || node.field_name == 'closed') { g.write('sync__Channel_${node.field_name}(') g.expr(node.expr) g.write(')') return } mut sum_type_deref_field := '' mut sum_type_dot := '.' mut field_typ := ast.void_type if f := g.table.find_field_with_embeds(sym, node.field_name) { field_sym := g.table.sym(f.typ) field_typ = f.typ if sym.kind in [.interface_, .sum_type] { g.write('(*(') } if field_sym.kind in [.sum_type, .interface_] { if !prevent_sum_type_unwrapping_once { // check first if field is sum type because scope searching is expensive scope := g.file.scope.innermost(node.pos.pos) if field := scope.find_struct_field(node.expr.str(), node.expr_type, node.field_name) { if field.orig_type.is_ptr() { sum_type_dot = '->' } for i, typ in field.smartcasts { g.write('(') if field_sym.kind == .sum_type { g.write('*') } cast_sym := g.table.sym(g.unwrap_generic(typ)) if field_sym.kind == .interface_ && cast_sym.kind == .interface_ { ptr := '*'.repeat(field.typ.nr_muls()) dot := if node.expr_type.is_ptr() { '->' } else { '.' } g.write('I_${field_sym.cname}_as_I_${cast_sym.cname}(${ptr}${node.expr}${dot}${node.field_name}))') return } else { if i != 0 { dot := if field.typ.is_ptr() { '->' } else { '.' } sum_type_deref_field += ')${dot}' } if cast_sym.info is ast.Aggregate { agg_sym := g.table.sym(cast_sym.info.types[g.aggregate_type_idx]) sum_type_deref_field += '_${agg_sym.cname}' } else { sum_type_deref_field += '_${cast_sym.cname}' } } } } } } } else if m := sym.find_method_with_generic_parent(node.field_name) { mut has_embeds := false if sym.info in [ast.Struct, ast.Aggregate] { if node.from_embed_types.len > 0 { has_embeds = true } } if !has_embeds { if !node.has_hidden_receiver { g.write('${g.typ(node.expr_type.idx())}_${m.name}') return } receiver := m.params[0] expr_styp := g.typ(node.expr_type.idx()) data_styp := g.typ(receiver.typ.idx()) mut sb := strings.new_builder(256) name := '_V_closure_${expr_styp}_${m.name}_${node.pos.pos}' sb.write_string('${g.typ(m.return_type)} ${name}(') for i in 1 .. m.params.len { param := m.params[i] if i != 1 { sb.write_string(', ') } sb.write_string('${g.typ(param.typ)} a${i}') } sb.writeln(') {') sb.writeln('\t${data_styp}* a0 = __CLOSURE_GET_DATA();') if m.return_type != ast.void_type { sb.write_string('\treturn ') } else { sb.write_string('\t') } mut method_name := m.name rec_sym := g.table.sym(receiver.typ) if rec_sym.info is ast.Struct { if rec_sym.info.concrete_types.len > 0 { method_name = g.generic_fn_name(rec_sym.info.concrete_types, m.name) } } if rec_sym.info is ast.Interface { left_cc_type := g.cc_type(g.table.unaliased_type(receiver.typ), false) left_type_name := util.no_dots(left_cc_type) sb.write_string('${c_name(left_type_name)}_name_table[a0->_typ]._method_${method_name}(') } else { sb.write_string('${expr_styp}_${method_name}(') if !receiver.typ.is_ptr() { sb.write_string('*') } } for i in 0 .. m.params.len { if i != 0 { sb.write_string(', ') } sb.write_string('a${i}') } sb.writeln(');') sb.writeln('}') g.anon_fn_definitions << sb.str() g.nr_closures++ g.write('__closure_create(${name}, ') if !receiver.typ.is_ptr() { g.write('memdup_uncollectable(') } if !node.expr_type.is_ptr() { g.write('&') } g.expr(node.expr) if !receiver.typ.is_ptr() { g.write(', sizeof(${expr_styp}))') } g.write(')') return } } else { if sym.kind in [.interface_, .sum_type] { g.write('(*(') } } // var?.field_opt field_is_opt := node.expr is ast.Ident && node.expr.is_auto_heap() && node.expr.or_expr.kind != .absent && field_typ.has_flag(.option) if field_is_opt { g.write('((${g.base_type(field_typ)})') } n_ptr := node.expr_type.nr_muls() - 1 if n_ptr > 0 { g.write('(') g.write('*'.repeat(n_ptr)) g.expr(node.expr) g.write(')') } else { g.expr(node.expr) } if field_is_opt { g.write(')') } if is_opt_or_res { g.write('.data)') } // struct embedding if sym.info in [ast.Struct, ast.Aggregate] { for i, embed in node.from_embed_types { embed_sym := g.table.sym(embed) embed_name := embed_sym.embed_name() is_left_ptr := if i == 0 { node.expr_type.is_ptr() } else { node.from_embed_types[i - 1].is_ptr() } if is_left_ptr { g.write('->') } else { g.write('.') } g.write(embed_name) } } alias_to_ptr := sym.info is ast.Alias && sym.info.parent_type.is_ptr() if field_is_opt || ((node.expr_type.is_ptr() || sym.kind == .chan || alias_to_ptr) && node.from_embed_types.len == 0) { g.write('->') } else { g.write('.') } if node.expr_type.has_flag(.shared_f) { g.write('val.') } if node.expr_type == 0 { verror('cgen: SelectorExpr | expr_type: 0 | it.expr: `${node.expr}` | field: `${node.field_name}` | file: ${g.file.path} | line: ${node.pos.line_nr}') } g.write(field_name) if sum_type_deref_field != '' { g.write('${sum_type_dot}${sum_type_deref_field})') } if sym.kind in [.interface_, .sum_type] { g.write('))') } } fn (mut g Gen) enum_decl(node ast.EnumDecl) { enum_name := util.no_dots(node.name) is_flag := node.is_flag if g.pref.ccompiler == 'msvc' { mut last_value := '0' enum_typ_name := g.table.get_type_name(node.typ) g.enum_typedefs.writeln('typedef ${enum_typ_name} ${enum_name};') for i, field in node.fields { g.enum_typedefs.write_string('\t#define ${enum_name}__${field.name} ') g.enum_typedefs.write_string('(') if is_flag { g.enum_typedefs.write_string((u64(1) << i).str()) g.enum_typedefs.write_string('ULL') } else if field.has_expr { expr_str := g.expr_string(field.expr) g.enum_typedefs.write_string(expr_str) last_value = expr_str } else { if i != 0 { last_value += '+1' } g.enum_typedefs.write_string(last_value) } g.enum_typedefs.writeln(')') } return } if node.typ != ast.int_type { g.enum_typedefs.writeln('#pragma pack(push, 1)') } g.enum_typedefs.writeln('typedef enum {') mut cur_enum_expr := '' mut cur_enum_offset := 0 for i, field in node.fields { g.enum_typedefs.write_string('\t${enum_name}__${field.name}') if field.has_expr { g.enum_typedefs.write_string(' = ') expr_str := g.expr_string(field.expr) g.enum_typedefs.write_string(expr_str) cur_enum_expr = expr_str cur_enum_offset = 0 } else if is_flag { g.enum_typedefs.write_string(' = ') cur_enum_expr = 'u64(1) << ${i}' g.enum_typedefs.write_string((u64(1) << i).str()) g.enum_typedefs.write_string('U') cur_enum_offset = 0 } cur_value := if cur_enum_offset > 0 { '${cur_enum_expr}+${cur_enum_offset}' } else { cur_enum_expr } g.enum_typedefs.writeln(', // ${cur_value}') cur_enum_offset++ } packed_attribute := if !g.is_cc_msvc && node.typ != ast.int_type { '__attribute__((packed))' } else { '' } g.enum_typedefs.writeln('} ${packed_attribute} ${enum_name};') if node.typ != ast.int_type { g.enum_typedefs.writeln('#pragma pack(pop)\n') } } fn (mut g Gen) enum_expr(node ast.Expr) { match node { ast.EnumVal { g.write(node.val) } else { g.expr(node) } } } fn (mut g Gen) lock_expr(node ast.LockExpr) { g.cur_lock = unsafe { node } // is ok because it is discarded at end of fn defer { g.cur_lock = ast.LockExpr{ scope: 0 } } tmp_result := if node.is_expr { g.new_tmp_var() } else { '' } mut cur_line := '' if node.is_expr { styp := g.typ(node.typ) cur_line = g.go_before_stmt(0) g.writeln('${styp} ${tmp_result};') } mut mtxs := '' if node.lockeds.len == 0 { // this should not happen } else if node.lockeds.len == 1 { lock_prefix := if node.is_rlock[0] { 'r' } else { '' } g.write('sync__RwMutex_${lock_prefix}lock(&') g.expr(node.lockeds[0]) g.writeln('->mtx);') } else { mtxs = g.new_tmp_var() g.writeln('uintptr_t _arr_${mtxs}[${node.lockeds.len}];') g.writeln('bool _isrlck_${mtxs}[${node.lockeds.len}];') mut j := 0 for i, is_rlock in node.is_rlock { if !is_rlock { g.write('_arr_${mtxs}[${j}] = (uintptr_t)&') g.expr(node.lockeds[i]) g.writeln('->mtx;') g.writeln('_isrlck_${mtxs}[${j}] = false;') j++ } } for i, is_rlock in node.is_rlock { if is_rlock { g.write('_arr_${mtxs}[${j}] = (uintptr_t)&') g.expr(node.lockeds[i]) g.writeln('->mtx;') g.writeln('_isrlck_${mtxs}[${j}] = true;') j++ } } if node.lockeds.len == 2 { g.writeln('if (_arr_${mtxs}[0] > _arr_${mtxs}[1]) {') g.writeln('\tuintptr_t _ptr_${mtxs} = _arr_${mtxs}[0];') g.writeln('\t_arr_${mtxs}[0] = _arr_${mtxs}[1];') g.writeln('\t_arr_${mtxs}[1] = _ptr_${mtxs};') g.writeln('\tbool _bool_${mtxs} = _isrlck_${mtxs}[0];') g.writeln('\t_isrlck_${mtxs}[0] = _isrlck_${mtxs}[1];') g.writeln('\t_isrlck_${mtxs}[1] = _bool_${mtxs};') g.writeln('}') } else { g.writeln('__sort_ptr(_arr_${mtxs}, _isrlck_${mtxs}, ${node.lockeds.len});') } g.writeln('for (int ${mtxs}=0; ${mtxs}<${node.lockeds.len}; ${mtxs}++) {') g.writeln('\tif (${mtxs} && _arr_${mtxs}[${mtxs}] == _arr_${mtxs}[${mtxs}-1]) continue;') g.writeln('\tif (_isrlck_${mtxs}[${mtxs}])') g.writeln('\t\tsync__RwMutex_rlock((sync__RwMutex*)_arr_${mtxs}[${mtxs}]);') g.writeln('\telse') g.writeln('\t\tsync__RwMutex_lock((sync__RwMutex*)_arr_${mtxs}[${mtxs}]);') g.writeln('}') } g.mtxs = mtxs defer { g.mtxs = '' } g.writeln('/*lock*/ {') g.stmts_with_tmp_var(node.stmts, tmp_result) if node.is_expr { g.writeln(';') } g.writeln('}') g.unlock_locks() if node.is_expr { g.writeln('') g.write(cur_line) g.write('${tmp_result}') } } fn (mut g Gen) unlock_locks() { if g.cur_lock.lockeds.len == 0 { } else if g.cur_lock.lockeds.len == 1 { lock_prefix := if g.cur_lock.is_rlock[0] { 'r' } else { '' } g.write('sync__RwMutex_${lock_prefix}unlock(&') g.expr(g.cur_lock.lockeds[0]) g.write('->mtx);') } else { g.writeln('for (int ${g.mtxs}=${g.cur_lock.lockeds.len - 1}; ${g.mtxs}>=0; ${g.mtxs}--) {') g.writeln('\tif (${g.mtxs} && _arr_${g.mtxs}[${g.mtxs}] == _arr_${g.mtxs}[${g.mtxs}-1]) continue;') g.writeln('\tif (_isrlck_${g.mtxs}[${g.mtxs}])') g.writeln('\t\tsync__RwMutex_runlock((sync__RwMutex*)_arr_${g.mtxs}[${g.mtxs}]);') g.writeln('\telse') g.writeln('\t\tsync__RwMutex_unlock((sync__RwMutex*)_arr_${g.mtxs}[${g.mtxs}]);') g.write('}') } } fn (mut g Gen) map_init(node ast.MapInit) { unwrap_key_typ := g.unwrap_generic(node.key_type) unwrap_val_typ := g.unwrap_generic(node.value_type).clear_flag(.result) key_typ_str := g.typ(unwrap_key_typ) value_typ_str := g.typ(unwrap_val_typ) value_sym := g.table.sym(unwrap_val_typ) key_sym := g.table.final_sym(unwrap_key_typ) hash_fn, key_eq_fn, clone_fn, free_fn := g.map_fn_ptrs(key_sym) size := node.vals.len mut shared_styp := '' // only needed for shared &[]{...} mut styp := '' is_amp := g.is_amp g.is_amp = false if is_amp { g.go_back(1) // delete the `&` already generated in `prefix_expr() } if g.is_shared { mut shared_typ := node.typ.set_flag(.shared_f) shared_styp = g.typ(shared_typ) g.writeln('(${shared_styp}*)__dup_shared_map(&(${shared_styp}){.mtx = {0}, .val =') } else if is_amp { styp = g.typ(node.typ) g.write('(${styp}*)memdup(ADDR(${styp}, ') } noscan_key := g.check_noscan(node.key_type) noscan_value := g.check_noscan(node.value_type) mut noscan := if noscan_key.len != 0 || noscan_value.len != 0 { '_noscan' } else { '' } if noscan.len != 0 { if noscan_key.len != 0 { noscan += '_key' } if noscan_value.len != 0 { noscan += '_value' } } if size > 0 { if value_sym.kind == .function { g.writeln('new_map_init${noscan}(${hash_fn}, ${key_eq_fn}, ${clone_fn}, ${free_fn}, ${size}, sizeof(${key_typ_str}), sizeof(voidptr),') } else { g.writeln('new_map_init${noscan}(${hash_fn}, ${key_eq_fn}, ${clone_fn}, ${free_fn}, ${size}, sizeof(${key_typ_str}), sizeof(${value_typ_str}),') } g.writeln('\t\t_MOV((${key_typ_str}[${size}]){') for expr in node.keys { g.write('\t\t\t') g.expr(expr) g.writeln(', ') } g.writeln('\t\t}),') if value_sym.kind == .function { g.writeln('\t\t_MOV((voidptr[${size}]){') } else { g.writeln('\t\t_MOV((${value_typ_str}[${size}]){') } for i, expr in node.vals { g.write('\t\t\t') if expr.is_auto_deref_var() { g.write('*') } if value_sym.kind == .sum_type { g.expr_with_cast(expr, node.val_types[i], unwrap_val_typ) } else if node.val_types[i].has_flag(.option) || node.val_types[i] == ast.none_type { g.expr_with_opt(expr, node.val_types[i], unwrap_val_typ) } else { g.expr(expr) } g.writeln(', ') } g.writeln('\t\t})') g.writeln('\t)') } else { g.write('new_map${noscan}(sizeof(${key_typ_str}), sizeof(${value_typ_str}), ${hash_fn}, ${key_eq_fn}, ${clone_fn}, ${free_fn})') } g.writeln('') if g.is_shared { g.write('}, sizeof(${shared_styp}))') } else if is_amp { g.write('), sizeof(${styp}))') } } fn (mut g Gen) select_expr(node ast.SelectExpr) { is_expr := node.is_expr || g.inside_ternary > 0 cur_line := if is_expr { g.empty_line = true g.go_before_stmt(0) } else { '' } n_channels := if node.has_exception { node.branches.len - 1 } else { node.branches.len } mut channels := []ast.Expr{cap: n_channels} mut objs := []ast.Expr{cap: n_channels} mut tmp_objs := []string{cap: n_channels} mut elem_types := []string{cap: n_channels} mut is_push := []bool{cap: n_channels} mut has_else := false mut has_timeout := false mut timeout_expr := ast.empty_expr mut exception_branch := -1 for j, branch in node.branches { if branch.is_else { has_else = true exception_branch = j } else if branch.is_timeout { has_timeout = true exception_branch = j timeout_expr = (branch.stmt as ast.ExprStmt).expr } else { match branch.stmt { ast.ExprStmt { // send expression expr := branch.stmt.expr as ast.InfixExpr channels << expr.left if expr.right in [ast.Ident, ast.IndexExpr, ast.SelectorExpr, ast.StructInit] { // addressable objects in the `C` output objs << expr.right tmp_objs << '' elem_types << '' } else { // must be evaluated to tmp var before real `select` is performed objs << ast.empty_expr tmp_obj := g.new_tmp_var() tmp_objs << tmp_obj el_stype := g.typ(ast.mktyp(expr.right_type)) g.writeln('${el_stype} ${tmp_obj};') } is_push << true } ast.AssignStmt { rec_expr := branch.stmt.right[0] as ast.PrefixExpr channels << rec_expr.right is_push << false // create tmp unless the object with *exactly* the type we need exists already if branch.stmt.op == .decl_assign || branch.stmt.right_types[0] != branch.stmt.left_types[0] { tmp_obj := g.new_tmp_var() tmp_objs << tmp_obj el_stype := g.typ(branch.stmt.right_types[0]) elem_types << if branch.stmt.op == .decl_assign { el_stype + ' ' } else { '' } g.writeln('${el_stype} ${tmp_obj};') } else { tmp_objs << '' elem_types << '' } objs << branch.stmt.left[0] } else {} } } } chan_array := g.new_tmp_var() g.write('Array_sync__Channel_ptr ${chan_array} = new_array_from_c_array(${n_channels}, ${n_channels}, sizeof(sync__Channel*), _MOV((sync__Channel*[${n_channels}]){') for i in 0 .. n_channels { if i > 0 { g.write(', ') } g.write('(sync__Channel*)(') g.expr(channels[i]) g.write(')') } g.writeln('}));\n') directions_array := g.new_tmp_var() g.write('Array_sync__Direction ${directions_array} = new_array_from_c_array(${n_channels}, ${n_channels}, sizeof(sync__Direction), _MOV((sync__Direction[${n_channels}]){') for i in 0 .. n_channels { if i > 0 { g.write(', ') } if is_push[i] { g.write('sync__Direction__push') } else { g.write('sync__Direction__pop') } } g.writeln('}));\n') objs_array := g.new_tmp_var() g.write('Array_voidptr ${objs_array} = new_array_from_c_array(${n_channels}, ${n_channels}, sizeof(voidptr), _MOV((voidptr[${n_channels}]){') for i in 0 .. n_channels { if i > 0 { g.write(', &') } else { g.write('&') } if tmp_objs[i] == '' { g.expr(objs[i]) } else { g.write(tmp_objs[i]) } } g.writeln('}));\n') select_result := g.new_tmp_var() g.write('int ${select_result} = sync__channel_select(&/*arr*/${chan_array}, ${directions_array}, &/*arr*/${objs_array}, ') if has_timeout { g.expr(timeout_expr) } else if has_else { g.write('0') } else { g.write('_const_time__infinite') } g.writeln(');') // free the temps that were created g.writeln('array_free(&${objs_array});') g.writeln('array_free(&${directions_array});') g.writeln('array_free(&${chan_array});') mut i := 0 for j in 0 .. node.branches.len { if j > 0 { g.write('} else ') } g.write('if (${select_result} == ') if j == exception_branch { g.writeln('-1) {') } else { g.writeln('${i}) {') if !is_push[i] && tmp_objs[i] != '' { g.write('\t${elem_types[i]}') g.expr(objs[i]) g.writeln(' = ${tmp_objs[i]};') } i++ } g.stmts(node.branches[j].stmts) } g.writeln('}') if is_expr { g.empty_line = false g.write(cur_line) g.write('(${select_result} != -2)') } } [inline] pub fn (mut g Gen) is_generic_param_var(node ast.Expr) bool { return node is ast.Ident && node.info is ast.IdentVar && node.obj is ast.Var && (node.obj as ast.Var).ct_type_var == .generic_param } [inline] pub fn (mut g Gen) is_comptime_var(node ast.Expr) bool { return node is ast.Ident && node.info is ast.IdentVar && node.obj is ast.Var && (node.obj as ast.Var).ct_type_var != .no_comptime } fn (mut g Gen) ident(node ast.Ident) { prevent_sum_type_unwrapping_once := g.prevent_sum_type_unwrapping_once g.prevent_sum_type_unwrapping_once = false if node.name == 'lld' { return } if node.name.starts_with('C.') { g.write(util.no_dots(node.name[2..])) return } mut name := c_name(node.name) if node.kind == .constant { if g.pref.translated && !g.is_builtin_mod && !util.module_is_builtin(node.name.all_before_last('.')) { // Don't prepend "_const" to translated C consts, // but only in user code, continue prepending "_const" to builtin consts. mut x := util.no_dots(node.name) if x.starts_with('main__') { x = x['main__'.len..] } g.write(x) return } else { // TODO globals hack g.write('_const_') } } mut is_auto_heap := node.is_auto_heap() mut is_option := false if node.info is ast.IdentVar { if node.obj is ast.Var { if !g.is_assign_lhs && node.obj.ct_type_var !in [.generic_param, .no_comptime] { comptime_type := g.get_comptime_var_type(node) if comptime_type.has_flag(.option) { if (g.inside_opt_or_res || g.left_is_opt) && node.or_expr.kind == .absent { if !g.is_assign_lhs && is_auto_heap { g.write('(*${name})') } else { g.write(name) } } else { g.write('/*opt*/') styp := g.base_type(comptime_type) if is_auto_heap { g.write('(*(${styp}*)${name}->data)') } else { g.write('(*(${styp}*)${name}.data)') } } } else { g.write(name) } if node.or_expr.kind != .absent && !(g.inside_opt_or_res && g.inside_assign && !g.is_assign_lhs) { stmt_str := g.go_before_stmt(0).trim_space() g.empty_line = true var_name := if !g.is_assign_lhs && is_auto_heap { '(*${name})' } else { name } g.or_block(var_name, node.or_expr, comptime_type) g.writeln(stmt_str) } return } } // x ?int // `x = 10` => `x.data = 10` (g.right_is_opt == false) // `x = new_opt()` => `x = new_opt()` (g.right_is_opt == true) // `println(x)` => `println(*(int*)x.data)` if node.info.is_option && !(g.is_assign_lhs && g.right_is_opt) { if (g.inside_opt_or_res || g.left_is_opt) && node.or_expr.kind == .absent { if !g.is_assign_lhs && is_auto_heap { g.write('(*${name})') } else { g.write(name) } } else { g.write('/*opt*/') styp := g.base_type(node.info.typ) if is_auto_heap { g.write('(*(${styp}*)${name}->data)') } else { type_sym := g.table.sym(node.info.typ) if type_sym.kind == .alias { // Alias to Option type parent_typ := (type_sym.info as ast.Alias).parent_type if parent_typ.has_flag(.option) { g.write('*((${g.base_type(parent_typ)}*)') } g.write('(*(${styp}*)${name}.data)') if parent_typ.has_flag(.option) { g.write('.data)') } } else { g.write('(*(${styp}*)${name}.data)') } } } if node.or_expr.kind != .absent && !(g.inside_opt_or_res && g.inside_assign && !g.is_assign_lhs) { stmt_str := g.go_before_stmt(0).trim_space() g.empty_line = true var_name := if !g.is_assign_lhs && is_auto_heap { '(*${name})' } else { name } g.or_block(var_name, node.or_expr, node.info.typ) g.write(stmt_str) } return } if !g.is_assign_lhs && node.info.share == .shared_t { g.write('${name}.val') return } is_option = node.info.is_option if node.obj is ast.Var { is_auto_heap = node.obj.is_auto_heap && (!g.is_assign_lhs || g.assign_op != .decl_assign) if is_auto_heap { g.write('(*(') } if node.obj.smartcasts.len > 0 { obj_sym := g.table.sym(node.obj.typ) if !prevent_sum_type_unwrapping_once { for _ in node.obj.smartcasts { g.write('(') if obj_sym.kind == .sum_type && !is_auto_heap { g.write('*') } } for i, typ in node.obj.smartcasts { cast_sym := g.table.sym(g.unwrap_generic(typ)) if obj_sym.kind == .interface_ && cast_sym.kind == .interface_ { ptr := '*'.repeat(node.obj.typ.nr_muls()) g.write('I_${obj_sym.cname}_as_I_${cast_sym.cname}(${ptr}${node.name})') } else { mut is_ptr := false if i == 0 { g.write(name) if node.obj.orig_type.is_ptr() { is_ptr = true } } dot := if is_ptr || is_auto_heap { '->' } else { '.' } if cast_sym.info is ast.Aggregate { sym := g.table.sym(cast_sym.info.types[g.aggregate_type_idx]) g.write('${dot}_${sym.cname}') } else { g.write('${dot}_${cast_sym.cname}') } } g.write(')') } if is_auto_heap { g.write('))') } return } } if node.obj.is_inherited { g.write(closure_ctx + '->') } } } else if node.info is ast.IdentFn { // TODO PERF fn lookup for each fn call in translated mode if func := g.table.find_fn(node.name) { if g.pref.translated || g.file.is_translated || func.is_file_translated { // `p_mobjthinker` => `P_MobjThinker` if cattr := func.attrs.find_first('c') { name = cattr.arg } } else if node.concrete_types.len > 0 { name = g.generic_fn_name(node.concrete_types, name) } } if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') { key := node.name g.write('/* obf identfn: ${key} */') name = g.obf_table[key] or { panic('cgen: obf name "${key}" not found, this should never happen') } } } g.write(g.get_ternary_name(name)) if is_auto_heap { g.write('))') if is_option { g.write('.data') } } if node.or_expr.kind != .absent && !(g.inside_opt_or_res && g.inside_assign && !g.is_assign_lhs) { stmt_str := g.go_before_stmt(0).trim_space() g.empty_line = true var_opt := if is_auto_heap { '(*${name})' } else { name } g.or_block(var_opt, node.or_expr, node.obj.typ) g.write(stmt_str) } } fn (mut g Gen) cast_expr(node ast.CastExpr) { node_typ := g.unwrap_generic(node.typ) mut expr_type := node.expr_type sym := g.table.sym(node_typ) if (node.expr is ast.Ident && g.is_comptime_var(node.expr)) || node.expr is ast.ComptimeSelector { expr_type = g.unwrap_generic(g.get_comptime_var_type(node.expr)) } if sym.kind in [.sum_type, .interface_] { if node.typ.has_flag(.option) && node.expr is ast.None { g.gen_option_error(node.typ, node.expr) } else if node.typ.has_flag(.option) { g.expr_with_opt(node.expr, expr_type, node.typ) } else { g.expr_with_cast(node.expr, expr_type, node_typ) } } else if !node.typ.has_flag(.option) && sym.kind == .struct_ && !node.typ.is_ptr() && !(sym.info as ast.Struct).is_typedef { // deprecated, replaced by Struct{...exr} styp := g.typ(node.typ) g.write('*((${styp} *)(&') g.expr(node.expr) g.write('))') } else if sym.kind == .alias && g.table.final_sym(node.typ).kind == .array_fixed { if node.expr is ast.ArrayInit && g.assign_op != .decl_assign { g.write('(${g.typ(node.expr.typ)})') } g.expr(node.expr) } else if expr_type == ast.bool_type && node.typ.is_int() { styp := g.typ(node_typ) g.write('(${styp}[]){(') g.expr(node.expr) g.write(')?1:0}[0]') } else { styp := g.typ(node.typ) if (g.pref.translated || g.file.is_translated) && sym.kind == .function { // TODO handle the type in fn casts, not just exprs /* info := sym.info as ast.FnType if cattr := info.func.attrs.find_first('c') { name = cattr.arg } */ } mut cast_label := '' // `ast.string_type` is done for MSVC's bug if sym.kind != .alias || (!(sym.info as ast.Alias).parent_type.has_flag(.option) && (sym.info as ast.Alias).parent_type !in [expr_type, ast.string_type]) { cast_label = '(${styp})' } if node.typ.has_flag(.option) && node.expr is ast.None { g.gen_option_error(node.typ, node.expr) } else if node.typ.has_flag(.option) { if sym.kind == .alias { if (sym.info as ast.Alias).parent_type.has_flag(.option) { cur_stmt := g.go_before_stmt(0) g.empty_line = true parent_type := (sym.info as ast.Alias).parent_type tmp_var := g.new_tmp_var() tmp_var2 := g.new_tmp_var() g.writeln('${styp} ${tmp_var};') g.writeln('${g.typ(parent_type)} ${tmp_var2};') g.write('_option_ok(&(${g.base_type(parent_type)}[]) { ') g.expr(node.expr) g.writeln(' }, (${c.option_name}*)(&${tmp_var2}), sizeof(${g.base_type(parent_type)}));') g.writeln('_option_ok(&(${g.typ(parent_type)}[]) { ${tmp_var2} }, (${c.option_name}*)&${tmp_var}, sizeof(${g.typ(parent_type)}));') g.write(cur_stmt) g.write(tmp_var) } else if node.expr_type.has_flag(.option) { g.expr_opt_with_cast(node.expr, expr_type, node.typ) } else { g.expr_with_opt(node.expr, expr_type, node.typ) } } else { g.expr_with_opt(node.expr, expr_type, node.typ) } } else if sym.kind == .alias && (sym.info as ast.Alias).parent_type.has_flag(.option) { g.expr_with_opt(node.expr, expr_type, (sym.info as ast.Alias).parent_type) } else { g.write('(${cast_label}(') if sym.kind == .alias && g.table.final_sym(node.typ).kind == .string { ptr_cnt := node.typ.nr_muls() - expr_type.nr_muls() if ptr_cnt > 0 { g.write('&'.repeat(ptr_cnt)) } } g.expr(node.expr) if node.expr is ast.IntegerLiteral { if node_typ in [ast.u64_type, ast.u32_type, ast.u16_type] { if !node.expr.val.starts_with('-') { g.write('U') } } } g.write('))') } } } fn (mut g Gen) concat_expr(node ast.ConcatExpr) { mut styp := g.typ(node.return_type.clear_flags(.option, .result)) if g.inside_return { styp = g.typ(g.fn_decl.return_type.clear_flags(.option, .result)) } else if g.inside_or_block { styp = g.typ(g.or_expr_return_type.clear_flags(.option, .result)) } sym := g.table.sym(node.return_type) is_multi := sym.kind == .multi_return if !is_multi { g.expr(node.vals[0]) } else { g.write('(${styp}){') for i, expr in node.vals { g.write('.arg${i}=') g.expr(expr) if i < node.vals.len - 1 { g.write(',') } } g.write('}') } } [inline] fn (g &Gen) expr_is_multi_return_call(expr ast.Expr) bool { if expr is ast.CallExpr { return g.table.sym(expr.return_type).kind == .multi_return } return false } fn (mut g Gen) gen_result_error(target_type ast.Type, expr ast.Expr) { styp := g.typ(g.unwrap_generic(target_type)) g.write('(${styp}){ .is_error=true, .err=') g.expr(expr) g.write(', .data={EMPTY_STRUCT_INITIALIZATION} }') } // NB: remove this when option has no errors anymore fn (mut g Gen) gen_option_error(target_type ast.Type, expr ast.Expr) { styp := g.typ(g.unwrap_generic(target_type)) g.write('(${styp}){ .state=2, .err=') if target_type.has_flag(.option) && expr is ast.Ident && expr.or_expr.kind == .propagate_option { g.expr(ast.None{}) // option type unwrapping error } else { g.expr(expr) } g.write(', .data={EMPTY_STRUCT_INITIALIZATION} }') } fn (mut g Gen) hash_stmt(node ast.HashStmt) { line_nr := node.pos.line_nr + 1 mut ct_condition := '' if node.ct_conds.len > 0 { ct_condition_start := g.out.len for idx, ct_expr in node.ct_conds { g.comptime_if_cond(ct_expr, false) if idx < node.ct_conds.len - 1 { g.write(' && ') } } ct_condition = g.out.cut_to(ct_condition_start).trim_space() } // #include etc if node.kind == 'include' { mut missing_message := 'Header file ${node.main}, needed for module `${node.mod}` was not found.' if node.msg != '' { missing_message += ' ${node.msg}.' } else { missing_message += ' Please install the corresponding development headers.' } mut guarded_include := get_guarded_include_text(node.main, missing_message) if node.main == '' { // fails with musl-gcc and msvc; but an unguarded include works: guarded_include = '#include ${node.main}' } if node.main.contains('.m') { g.definitions.writeln('\n') if ct_condition.len > 0 { g.definitions.writeln('#if ${ct_condition}') } // Objective C code import, include it after V types, so that e.g. `string` is // available there g.definitions.writeln('// added by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:') g.definitions.writeln(guarded_include) if ct_condition.len > 0 { g.definitions.writeln('#endif // \$if ${ct_condition}') } g.definitions.writeln('\n') } else { g.includes.writeln('\n') if ct_condition.len > 0 { g.includes.writeln('#if ${ct_condition}') } g.includes.writeln('// added by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:') g.includes.writeln(guarded_include) if ct_condition.len > 0 { g.includes.writeln('#endif // \$if ${ct_condition}') } g.includes.writeln('\n') } } else if node.kind == 'preinclude' { mut missing_message := 'Header file ${node.main}, needed for module `${node.mod}` was not found.' if node.msg != '' { missing_message += ' ${node.msg}.' } else { missing_message += ' Please install the corresponding development headers.' } mut guarded_include := get_guarded_include_text(node.main, missing_message) if node.main == '' { // fails with musl-gcc and msvc; but an unguarded include works: guarded_include = '#include ${node.main}' } if node.main.contains('.m') { // Might need to support '#preinclude' for .m files as well but for the moment // this does the same as '#include' for them g.definitions.writeln('\n') if ct_condition.len > 0 { g.definitions.writeln('#if ${ct_condition}') } // Objective C code import, include it after V types, so that e.g. `string` is // available there g.definitions.writeln('// added by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:') g.definitions.writeln(guarded_include) if ct_condition.len > 0 { g.definitions.writeln('#endif // \$if ${ct_condition}') } g.definitions.writeln('\n') } else { g.preincludes.writeln('\n') if ct_condition.len > 0 { g.preincludes.writeln('#if ${ct_condition}') } g.preincludes.writeln('// added by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:') g.preincludes.writeln(guarded_include) if ct_condition.len > 0 { g.preincludes.writeln('#endif // \$if ${ct_condition}') } g.preincludes.writeln('\n') } } else if node.kind == 'insert' { if ct_condition.len > 0 { g.includes.writeln('#if ${ct_condition}') } g.includes.writeln('// inserted by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:') g.includes.writeln(node.val) if ct_condition.len > 0 { g.includes.writeln('#endif // \$if ${ct_condition}') } } else if node.kind == 'define' { if ct_condition.len > 0 { g.includes.writeln('#if ${ct_condition}') } g.includes.writeln('// defined by module `${node.mod}`') g.includes.writeln('#define ${node.main}') if ct_condition.len > 0 { g.includes.writeln('#endif // \$if ${ct_condition}') } } } fn (mut g Gen) branch_stmt(node ast.BranchStmt) { if node.label != '' { x := g.labeled_loops[node.label] or { panic('${node.label} doesn\'t exist ${g.file.path}, ${node.pos}') } match x { ast.ForCStmt { if x.scope.contains(g.cur_lock.pos.pos) { g.unlock_locks() } } ast.ForInStmt { if x.scope.contains(g.cur_lock.pos.pos) { g.unlock_locks() } } ast.ForStmt { if x.scope.contains(g.cur_lock.pos.pos) { g.unlock_locks() } } else {} } if node.kind == .key_break { g.writeln('goto ${node.label}__break;') } else { g.writeln('goto ${node.label}__continue;') } } else { inner_loop := g.inner_loop match inner_loop { ast.ForCStmt { if inner_loop.scope.contains(g.cur_lock.pos.pos) { g.unlock_locks() } } ast.ForInStmt { if inner_loop.scope.contains(g.cur_lock.pos.pos) { g.unlock_locks() } } ast.ForStmt { if inner_loop.scope.contains(g.cur_lock.pos.pos) { g.unlock_locks() } } else {} } // continue or break if g.is_autofree && !g.is_builtin_mod { g.trace_autofree('// free before continue/break') g.autofree_scope_vars_stop(node.pos.pos - 1, node.pos.line_nr, true, g.branch_parent_pos) } g.writeln('${node.kind};') } } fn (mut g Gen) return_stmt(node ast.Return) { g.write_v_source_line_info(node.pos) g.inside_return = true defer { g.inside_return = false } if node.exprs.len > 0 { // skip `return $vweb.html()` if node.exprs[0] is ast.ComptimeCall && node.exprs[0].is_vweb { g.inside_return_tmpl = true g.expr(node.exprs[0]) g.inside_return_tmpl = false g.writeln(';') return } } // got to do a correct check for multireturn sym := g.table.sym(g.fn_decl.return_type) mut fn_ret_type := g.fn_decl.return_type if sym.kind == .alias { unaliased_type := g.table.unaliased_type(fn_ret_type) if unaliased_type.has_flag(.option) || unaliased_type.has_flag(.result) { fn_ret_type = unaliased_type } } fn_return_is_multi := sym.kind == .multi_return fn_return_is_option := fn_ret_type.has_flag(.option) fn_return_is_result := fn_ret_type.has_flag(.result) fn_return_is_fixed_array := sym.is_array_fixed() && !fn_ret_type.has_flag(.option) mut has_semicolon := false if node.exprs.len == 0 { g.write_defer_stmts_when_needed() if fn_return_is_option || fn_return_is_result { styp := g.typ(fn_ret_type) g.writeln('return (${styp}){0};') } else { if g.is_autofree { g.trace_autofree('// free before return (no values returned)') g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) } g.writeln('return;') } return } tmpvar := g.new_tmp_var() g.defer_return_tmp_var = tmpvar mut ret_typ := g.typ(g.unwrap_generic(fn_ret_type)) if fn_ret_type.has_flag(.generic) && fn_return_is_fixed_array { ret_typ = '_v_${ret_typ}' } else if sym.kind == .alias && fn_return_is_fixed_array { ret_typ = '_v_' + g.typ((sym.info as ast.Alias).parent_type) } if node.exprs.len == 1 { // `return fn_call_opt()` if (fn_return_is_option || fn_return_is_result) && node.exprs[0] is ast.CallExpr && node.exprs[0].return_type == g.fn_decl.return_type && node.exprs[0].or_block.kind == .absent { g.write('${ret_typ} ${tmpvar} = ') g.expr(node.exprs[0]) g.writeln(';') g.write_defer_stmts_when_needed() g.writeln('return ${tmpvar};') return } } mut use_tmp_var := g.defer_stmts.len > 0 || g.defer_profile_code.len > 0 || g.cur_lock.lockeds.len > 0 || (fn_return_is_multi && node.exprs.len >= 1 && fn_return_is_option) || fn_return_is_fixed_array // handle promoting none/error/function returning _option' if fn_return_is_option { option_none := node.exprs[0] is ast.None ftyp := g.typ(node.types[0]) mut is_regular_option := ftyp == '_option' if option_none || is_regular_option || node.types[0] == ast.error_type_idx { if g.fn_decl != unsafe { nil } && g.fn_decl.is_test { test_error_var := g.new_tmp_var() g.write('${ret_typ} ${test_error_var} = ') g.gen_option_error(fn_ret_type, node.exprs[0]) g.writeln(';') g.write_defer_stmts_when_needed() g.gen_failing_return_error_for_test_fn(node, test_error_var) return } if use_tmp_var { g.write('${ret_typ} ${tmpvar} = ') } else { g.write('return ') } g.gen_option_error(fn_ret_type, node.exprs[0]) g.writeln(';') if use_tmp_var { // handle options when returning `none` for `?(int, ?int)` if fn_return_is_multi && node.exprs.len >= 1 { mr_info := sym.info as ast.MultiReturn for i in 0 .. mr_info.types.len { if mr_info.types[i].has_flag(.option) { g.write('(*(${g.base_type(fn_ret_type)}*)${tmpvar}.data).arg${i} = ') g.gen_option_error(mr_info.types[i], ast.None{}) g.writeln(';') } } } g.write_defer_stmts_when_needed() g.writeln('return ${tmpvar};') } return } } // handle promoting error/function returning result if fn_return_is_result { ftyp := g.typ(node.types[0]) mut is_regular_result := ftyp == c.result_name if is_regular_result || node.types[0] == ast.error_type_idx { if g.fn_decl != unsafe { nil } && g.fn_decl.is_test { test_error_var := g.new_tmp_var() g.write('${ret_typ} ${test_error_var} = ') g.gen_result_error(fn_ret_type, node.exprs[0]) g.writeln(';') g.write_defer_stmts_when_needed() g.gen_failing_return_error_for_test_fn(node, test_error_var) return } if use_tmp_var { g.write('${ret_typ} ${tmpvar} = ') } else { g.write('return ') } g.gen_result_error(fn_ret_type, node.exprs[0]) g.writeln(';') if use_tmp_var { g.write_defer_stmts_when_needed() g.writeln('return ${tmpvar};') } return } } // regular cases if fn_return_is_multi && node.exprs.len > 0 && !g.expr_is_multi_return_call(node.exprs[0]) { if node.exprs.len == 1 && (node.exprs[0] is ast.IfExpr || node.exprs[0] is ast.MatchExpr) { // use a temporary for `return if cond { x,y } else { a,b }` or `return match expr { abc { x, y } else { z, w } }` g.write('${ret_typ} ${tmpvar} = ') g.expr(node.exprs[0]) g.writeln(';') g.write_defer_stmts_when_needed() g.writeln('return ${tmpvar};') return } mr_info := sym.info as ast.MultiReturn mut styp := '' if fn_return_is_option { g.writeln('${ret_typ} ${tmpvar};') styp = g.base_type(g.fn_decl.return_type) g.write('_option_ok(&(${styp}[]) { ') } else if fn_return_is_result { g.writeln('${ret_typ} ${tmpvar};') styp = g.base_type(g.fn_decl.return_type) g.write('_result_ok(&(${styp}[]) { ') } else { if use_tmp_var { g.write('${ret_typ} ${tmpvar} = ') } else { g.write('return ') } styp = g.typ(g.fn_decl.return_type) } // Use this to keep the tmp assignments in order mut multi_unpack := '' g.write('(${styp}){') mut arg_idx := 0 for i, expr in node.exprs { // Check if we are dealing with a multi return and handle it seperately if g.expr_is_multi_return_call(expr) { call_expr := expr as ast.CallExpr expr_sym := g.table.sym(call_expr.return_type) mut tmp := g.new_tmp_var() if !call_expr.return_type.has_flag(.option) && !call_expr.return_type.has_flag(.result) { line := g.go_before_stmt(0) expr_styp := g.typ(call_expr.return_type) g.write('${expr_styp} ${tmp}=') g.expr(expr) g.writeln(';') multi_unpack += g.go_before_stmt(0) g.write(line) } else { line := g.go_before_stmt(0) g.tmp_count-- g.expr(expr) multi_unpack += g.go_before_stmt(0) g.write(line) expr_styp := g.base_type(call_expr.return_type) tmp = ('(*(${expr_styp}*)${tmp}.data)') } expr_types := expr_sym.mr_info().types for j, _ in expr_types { g.write('.arg${arg_idx}=${tmp}.arg${j}') if j < expr_types.len || i < node.exprs.len - 1 { g.write(',') } arg_idx++ } continue } g.write('.arg${arg_idx}=') if expr.is_auto_deref_var() { g.write('*') } if g.table.sym(mr_info.types[i]).kind in [.sum_type, .interface_] { g.expr_with_cast(expr, node.types[i], mr_info.types[i]) } else if mr_info.types[i].has_flag(.option) { g.expr_with_opt(expr, node.types[i], mr_info.types[i]) } else { g.expr(expr) } if i < node.exprs.len - 1 { g.write(', ') } arg_idx++ } g.write('}') if fn_return_is_option { g.writeln(' }, (${c.option_name}*)(&${tmpvar}), sizeof(${styp}));') g.write_defer_stmts_when_needed() g.write('return ${tmpvar}') } else if fn_return_is_result { g.writeln(' }, (${c.result_name}*)(&${tmpvar}), sizeof(${styp}));') g.write_defer_stmts_when_needed() g.write('return ${tmpvar}') } // Make sure to add our unpacks if multi_unpack.len > 0 { g.insert_before_stmt(multi_unpack) } if use_tmp_var && !fn_return_is_option && !fn_return_is_result { if !has_semicolon { g.writeln(';') } g.write_defer_stmts_when_needed() g.writeln('return ${tmpvar};') has_semicolon = true } } else if node.exprs.len >= 1 { if node.types.len == 0 { g.checker_bug('node.exprs.len == ${node.exprs.len} && node.types.len == 0', node.pos) } // normal return return_sym := g.table.sym(node.types[0]) expr0 := node.exprs[0] // `return opt_ok(expr)` for functions that expect an option expr_type_is_opt := match expr0 { ast.CallExpr { expr0.return_type.has_flag(.option) && expr0.or_block.kind == .absent } else { node.types[0].has_flag(.option) } } if fn_return_is_option && !expr_type_is_opt && return_sym.name != c.option_name { styp := g.base_type(fn_ret_type) g.writeln('${ret_typ} ${tmpvar};') g.write('_option_ok(&(${styp}[]) { ') if !g.unwrap_generic(fn_ret_type).is_ptr() && node.types[0].is_ptr() { if !(node.exprs[0] is ast.Ident && !g.is_amp) { g.write('*') } } for i, expr in node.exprs { g.expr_with_cast(expr, node.types[i], fn_ret_type.clear_flags(.option, .result)) if i < node.exprs.len - 1 { g.write(', ') } } g.writeln(' }, (${c.option_name}*)(&${tmpvar}), sizeof(${styp}));') g.write_defer_stmts_when_needed() g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) g.writeln('return ${tmpvar};') return } expr_type_is_result := match expr0 { ast.CallExpr { expr0.return_type.has_flag(.result) && expr0.or_block.kind == .absent } else { node.types[0].has_flag(.result) } } if fn_return_is_result && !expr_type_is_result && return_sym.name != c.result_name { styp := g.base_type(fn_ret_type) g.writeln('${ret_typ} ${tmpvar};') g.write('_result_ok(&(${styp}[]) { ') if !fn_ret_type.is_ptr() && node.types[0].is_ptr() { if !(node.exprs[0] is ast.Ident && !g.is_amp) { g.write('*') } } for i, expr in node.exprs { if fn_ret_type.has_flag(.option) { g.expr_with_opt(expr, node.types[i], fn_ret_type.clear_flag(.result)) } else { g.expr_with_cast(expr, node.types[i], fn_ret_type.clear_flag(.result)) } if i < node.exprs.len - 1 { g.write(', ') } } g.writeln(' }, (${c.result_name}*)(&${tmpvar}), sizeof(${styp}));') g.write_defer_stmts_when_needed() g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) g.writeln('return ${tmpvar};') return } // autofree before `return` // set free_parent_scopes to true, since all variables defined in parent // scopes need to be freed before the return if g.is_autofree { expr := node.exprs[0] if expr is ast.Ident { g.returned_var_name = expr.name } } // free := g.is_autofree && !g.is_builtin_mod // node.exprs[0] is ast.CallExpr // Create a temporary variable for the return expression if use_tmp_var || !g.is_builtin_mod { // `return foo(a, b, c)` // `tmp := foo(a, b, c); free(a); free(b); free(c); return tmp;` // Save return value in a temp var so that all args (a,b,c) can be freed // Don't use a tmp var if a variable is simply returned: `return x` // Just in case of defer statements exists, that the return values cannot // be modified. if node.exprs[0] !is ast.Ident || use_tmp_var { use_tmp_var = true g.write('${ret_typ} ${tmpvar} = ') } else { use_tmp_var = false if !g.is_builtin_mod { g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) } g.write('return ') } } else { g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) g.write('return ') } if expr0.is_auto_deref_var() { if g.fn_decl.return_type.is_ptr() { var_str := g.expr_string(expr0) g.write(var_str.trim('&')) } else if g.fn_decl.return_type.has_flag(.option) { g.expr_with_opt(expr0, node.types[0], g.fn_decl.return_type) } else if g.table.sym(g.fn_decl.return_type).kind in [.sum_type, .interface_] { g.expr_with_cast(expr0, node.types[0], g.fn_decl.return_type) } else { g.write('*') g.expr(expr0) } } else { if g.fn_decl.return_type.has_flag(.option) { g.expr_with_opt(node.exprs[0], node.types[0], g.fn_decl.return_type) } else { if fn_return_is_fixed_array && !node.types[0].has_flag(.option) { g.writeln('{0};') if node.exprs[0] is ast.Ident { g.write('memcpy(${tmpvar}.ret_arr, ${g.expr_string(node.exprs[0])}, sizeof(${g.typ(node.types[0])})) /*ret*/') } else if node.exprs[0] is ast.ArrayInit { tmpvar2 := g.new_tmp_var() g.write('${g.typ(node.types[0])} ${tmpvar2} = ') g.expr_with_cast(node.exprs[0], node.types[0], g.fn_decl.return_type) g.writeln(';') g.write('memcpy(${tmpvar}.ret_arr, ${tmpvar2}, sizeof(${g.typ(node.types[0])})) /*ret*/') } else { g.write('memcpy(${tmpvar}.ret_arr, ') g.expr_with_cast(node.exprs[0], node.types[0], g.fn_decl.return_type) g.write(', sizeof(${g.typ(node.types[0])})) /*ret*/') } } else { g.expr_with_cast(node.exprs[0], node.types[0], g.fn_decl.return_type) } } } if use_tmp_var { g.writeln(';') has_semicolon = true g.write_defer_stmts_when_needed() if !g.is_builtin_mod { g.autofree_scope_vars(node.pos.pos - 1, node.pos.line_nr, true) } g.write('return ${tmpvar}') has_semicolon = false } } else { println('this should never happen') g.write('/*F*/return') } if !has_semicolon { g.writeln(';') } } 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_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.is_builtin_mod { if cattr := node.attrs.find_first('export') { const_name = cattr.arg } } field_expr := field.expr match field.expr { ast.ArrayInit { if field.expr.is_fixed { styp := g.typ(field.expr.typ) val := g.expr_string(field.expr) g.global_const_defs[util.no_dots(field.name)] = GlobalConstDef{ mod: field.mod def: '${styp} ${const_name} = ${val}; // fixed array const' dep_names: g.table.dependent_names_in_expr(field_expr) } } else { g.const_decl_init_later(field.mod, name, field.expr, field.typ, false) } } ast.StringLiteral { val := g.expr_string(field.expr) g.global_const_defs[util.no_dots(field.name)] = GlobalConstDef{ mod: field.mod def: 'string ${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) { 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) } else { g.const_decl_init_later(field.mod, name, field.expr, field.typ, false) } g.inside_const_opt_or_res = 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 { styp := g.typ(field.expr.typ) val := g.expr_string(field.expr.expr) g.global_const_defs[util.no_dots(field.name)] = GlobalConstDef{ mod: field.mod def: '${styp} ${const_name} = ${val}; // fixed array const' dep_names: g.table.dependent_names_in_expr(field_expr) } continue } } g.const_decl_init_later(field.mod, name, field.expr, field.typ, false) } else { g.const_decl_init_later(field.mod, name, field.expr, field.typ, false) } } } } } 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.typ(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()) } int { 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.const_decl_write_precomputed(mod, styp, cname, field_name, "'${escval}'") } 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, '_SLIT("$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: g.global_const_defs[util.no_dots(field_name)] = GlobalConstDef{ mod: mod def: '${styp} ${cname}; // str inited later' init: '\t${cname} = _SLIT("${escaped_val}");' 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_modifier} 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, unwrap_option bool) { // 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.typ(typ) cname := g.c_const_name(name) mut init := strings.new_builder(100) if cname == '_const_os__args' { if g.pref.os == .windows { init.writeln('\t_const_os__args = os__init_os_args_wide(___argc, (byteptr*)___argv);') } else { init.writeln('\t_const_os__args = os__init_os_args(___argc, (byte**)___argv);') } } else { if unwrap_option { init.writeln('{') init.writeln(g.expr_string_surround('\t${cname} = *(${styp}*)', expr, '.data;')) init.writeln('}') } else { init.writeln(g.expr_string_surround('\t${cname} = ', expr, ';')) } } mut def := '${styp} ${cname}' 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) } 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 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) 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 defer { g.inside_cinit = 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 := '' if node.attrs.contains('weak') { attributes += 'VWEAK ' } for field in node.fields { if g.pref.skip_unused { if field.name !in g.table.used_globals { $if trace_skip_unused_globals ? { eprintln('>> skipping unused global name: ${field.name}') } continue } } styp := g.typ(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}; // global2' 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('; // global5') g.global_const_defs[util.no_dots(field.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' )` init = '\t${field.name} = ${g.expr_string(field.expr)}; // 3global' } } else if !g.pref.translated { // don't zero globals from C code default_initializer := g.type_default(field.typ) if default_initializer == '{0}' && should_init { def_builder.write_string(' = {0}') } else { if field.name !in ['as_cast_type_indexes', 'g_memory_block', 'global_allocator'] { init = '\t${field.name} = *(${styp}*)&((${styp}[]){${g.type_default(field.typ)}}[0]); // global' } } } def_builder.writeln('; // global4') g.global_const_defs[util.no_dots(field.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) assoc(node ast.Assoc) { g.writeln('// assoc') if node.typ == 0 { return } styp := g.typ(node.typ) g.writeln('(${styp}){') mut inited_fields := map[string]int{} for i, field in node.fields { inited_fields[field] = i } // Merge inited_fields in the rest of the fields. sym := g.table.sym(node.typ) info := sym.info as ast.Struct for field in info.fields { field_name := c_name(field.name) if field.name in inited_fields { g.write('\t.${field_name} = ') g.expr(node.exprs[inited_fields[field.name]]) g.writeln(', ') } else { g.writeln('\t.${field_name} = ${node.var_name}.${field_name},') } } g.write('}') if g.is_amp { g.write(', sizeof(${styp}))') } } [noreturn] fn verror(s string) { util.verror('cgen error', s) } [noreturn] fn (g &Gen) error(s string, pos token.Pos) { util.show_compiler_message('cgen error:', pos: pos, file_path: g.file.path, message: s) exit(1) } fn (g &Gen) checker_bug(s string, pos token.Pos) { g.error('checker bug; ${s}', pos) } // write_debug_calls_typeof_functions inserts calls to all typeof functions for // interfaces and sum-types in debug mode so that the compiler does not optimize them. // These functions are needed to be able to get the name of a specific structure/type in the debugger. fn (mut g Gen) write_debug_calls_typeof_functions() { if !g.pref.is_debug { return } g.writeln('\t// we call these functions in debug mode so that the C compiler') g.writeln('\t// does not optimize them and we can access them in the debugger.') for _, sym in g.table.type_symbols { if sym.kind == .sum_type { sum_info := sym.info as ast.SumType if sum_info.is_generic { continue } g.writeln('\tv_typeof_sumtype_${sym.cname}(0);') } if sym.kind == .interface_ { if sym.info !is ast.Interface { continue } inter_info := sym.info as ast.Interface if inter_info.is_generic { continue } g.writeln('\tv_typeof_interface_${sym.cname}(0);') } } } fn (mut g Gen) write_init_function() { if g.pref.no_builtin || (g.pref.translated && g.pref.is_o) { return } util.timing_start(@METHOD) defer { util.timing_measure(@METHOD) } if g.pref.is_liveshared { return } fn_vinit_start_pos := g.out.len // ___argv is declared as voidptr here, because that unifies the windows/unix logic g.writeln('void _vinit(int ___argc, voidptr ___argv) {') g.write_debug_calls_typeof_functions() if g.pref.trace_calls { g.writeln('\tv__trace_calls__on_call(_SLIT("_vinit"));') } if g.use_segfault_handler { // 11 is SIGSEGV. It is hardcoded here, to avoid FreeBSD compilation errors for trivial examples. g.writeln('#if __STDC_HOSTED__ == 1\n\tsignal(11, v_segmentation_fault_handler);\n#endif') } if g.pref.is_bare { g.writeln('init_global_allocator();') } if g.pref.prealloc { g.writeln('prealloc_vinit();') } // Note: the as_cast table should be *before* the other constant initialize calls, // because it may be needed during const initialization of builtin and during // calling module init functions too, just in case they do fail... g.write('\tas_cast_type_indexes = ') g.writeln(g.as_cast_name_table()) g.writeln('\tbuiltin_init();') if g.nr_closures > 0 { g.writeln('\t_closure_mtx_init();') } // reflection bootstraping if g.has_reflection { if var := g.global_const_defs['g_reflection'] { g.writeln(var.init) g.gen_reflection_data() } } mut cleaning_up_array := []string{cap: g.table.modules.len} for mod_name in g.table.modules { if g.has_reflection && mod_name == 'v.reflection' { // ignore v.reflection already initialized above continue } mut is_empty := true // write globals and consts init later for var_name in g.sorted_global_const_names { if var := g.global_const_defs[var_name] { if var.mod == mod_name && var.init.len > 0 { if is_empty { is_empty = false g.writeln('\t// Initializations for module ${mod_name}') } g.writeln(var.init) } } } init_fn_name := '${mod_name}.init' if initfn := g.table.find_fn(init_fn_name) { if initfn.return_type == ast.void_type && initfn.params.len == 0 { if is_empty { g.writeln('\t// Initializations for module ${mod_name}') } mod_c_name := util.no_dots(mod_name) init_fn_c_name := '${mod_c_name}__init' g.writeln('\t${init_fn_c_name}();') } } cleanup_fn_name := '${mod_name}.cleanup' if cleanupfn := g.table.find_fn(cleanup_fn_name) { if cleanupfn.return_type == ast.void_type && cleanupfn.params.len == 0 { mod_c_name := util.no_dots(mod_name) cleanup_fn_c_name := '${mod_c_name}__cleanup' cleaning_up_array << '\t${cleanup_fn_c_name}();' cleaning_up_array << '\t// Cleaning up for module ${mod_name}' } } } g.writeln('}') if g.pref.printfn_list.len > 0 && '_vinit' in g.pref.printfn_list { println(g.out.after(fn_vinit_start_pos)) } fn_vcleanup_start_pos := g.out.len g.writeln('void _vcleanup(void) {') if g.pref.trace_calls { g.writeln('\tv__trace_calls__on_call(_SLIT("_vcleanup"));') } if g.is_autofree { // g.writeln('puts("cleaning up...");') reversed_table_modules := g.table.modules.reverse() for mod_name in reversed_table_modules { g.writeln('\t// Cleanups for module ${mod_name} :') g.writeln(g.cleanups[mod_name].str()) } g.writeln('\tarray_free(&as_cast_type_indexes);') } for x in cleaning_up_array.reverse() { g.writeln(x) } g.writeln('}') if g.pref.printfn_list.len > 0 && '_vcleanup' in g.pref.printfn_list { println(g.out.after(fn_vcleanup_start_pos)) } needs_constructor := g.pref.is_shared && g.pref.os != .windows if needs_constructor { // shared libraries need a way to call _vinit/2. For that purpose, // provide a constructor/destructor pair, ensuring that all constants // are initialized just once, and that they will be freed too. // Note: os.args in this case will be []. g.writeln('__attribute__ ((constructor))') g.writeln('void _vinit_caller() {') g.writeln('\tstatic bool once = false; if (once) {return;} once = true;') g.writeln('\t_vinit(0,0);') g.writeln('}') g.writeln('__attribute__ ((destructor))') g.writeln('void _vcleanup_caller() {') g.writeln('\tstatic bool once = false; if (once) {return;} once = true;') g.writeln('\t_vcleanup();') g.writeln('}') } } const ( builtins = ['string', 'array', 'DenseArray', 'map', 'Error', 'IError', option_name, result_name] ) fn (mut g Gen) write_builtin_types() { if g.pref.no_builtin { return } mut builtin_types := []&ast.TypeSymbol{} // builtin types // builtin types need to be on top // everything except builtin will get sorted for builtin_name in c.builtins { sym := g.table.sym_by_idx(g.table.type_idxs[builtin_name]) if sym.kind == .interface_ { g.write_interface_typedef(sym) g.write_interface_typesymbol_declaration(sym) } else { builtin_types << sym } } g.write_types(builtin_types) } // C struct definitions, ordered // Sort the types, make sure types that are referenced by other types // are added before them. fn (mut g Gen) write_sorted_types() { g.type_definitions.writeln('// #start sorted_symbols') defer { g.type_definitions.writeln('// #end sorted_symbols') } unsafe { mut symbols := []&ast.TypeSymbol{cap: g.table.type_symbols.len} // structs that need to be sorted for sym in g.table.type_symbols { if sym.name !in c.builtins { symbols << sym } } sorted_symbols := g.sort_structs(symbols) g.write_types(sorted_symbols) } } fn (mut g Gen) write_types(symbols []&ast.TypeSymbol) { mut struct_names := map[string]bool{} for sym in symbols { if sym.name.starts_with('C.') { continue } if sym.kind == .none_ { g.type_definitions.writeln('struct none {') g.type_definitions.writeln('\tEMPTY_STRUCT_DECLARATION;') g.type_definitions.writeln('};') g.typedefs.writeln('typedef struct none none;') } mut name := sym.cname match sym.info { ast.Struct { if !struct_names[name] { g.struct_decl(sym.info, name, false) struct_names[name] = true } } ast.Thread { if !g.pref.is_bare && !g.pref.no_builtin { if g.pref.os == .windows { if name == '__v_thread' { g.thread_definitions.writeln('typedef HANDLE ${name};') } else { // Windows can only return `u32` (no void*) from a thread, so the // V gohandle must maintain a pointer to the return value g.thread_definitions.writeln('typedef struct {') g.thread_definitions.writeln('\tvoid* ret_ptr;') g.thread_definitions.writeln('\tHANDLE handle;') g.thread_definitions.writeln('} ${name};') } } else { g.thread_definitions.writeln('typedef pthread_t ${name};') } } } ast.SumType { if sym.info.is_generic || struct_names[name] { continue } struct_names[name] = true g.typedefs.writeln('typedef struct ${name} ${name};') g.type_definitions.writeln('') g.type_definitions.writeln('// Union sum type ${name} = ') for variant in sym.info.variants { g.type_definitions.writeln('// | ${variant:4d} = ${g.typ(variant.idx()):-20s}') } g.type_definitions.writeln('struct ${name} {') g.type_definitions.writeln('\tunion {') for variant in sym.info.variants { variant_sym := g.table.sym(variant) mut var := variant.ref() if variant_sym.info is ast.FnType { if variant_sym.info.is_anon { var = variant } } g.type_definitions.writeln('\t\t${g.typ(var)} _${variant_sym.cname};') } g.type_definitions.writeln('\t};') g.type_definitions.writeln('\tint _typ;') if sym.info.fields.len > 0 { g.writeln('\t// pointers to common sumtype fields') for field in sym.info.fields { g.type_definitions.writeln('\t${g.typ(field.typ.ref())} ${field.name};') } } g.type_definitions.writeln('};') g.type_definitions.writeln('') } ast.ArrayFixed { elem_sym := g.table.sym(sym.info.elem_type) if !elem_sym.is_builtin() && !sym.info.elem_type.has_flag(.generic) && !sym.info.is_fn_ret { // .array_fixed { styp := sym.cname // array_fixed_char_300 => char x[300] // [16]&&&EventListener{} => Array_fixed_main__EventListener_16_ptr3 // => typedef main__EventListener*** Array_fixed_main__EventListener_16_ptr3 [16] mut fixed_elem_name := g.typ(sym.info.elem_type.set_nr_muls(0)) if sym.info.elem_type.is_ptr() { fixed_elem_name += '*'.repeat(sym.info.elem_type.nr_muls()) } len := sym.info.size if fixed_elem_name.starts_with('C__') { fixed_elem_name = fixed_elem_name[3..] } if elem_sym.info is ast.FnType { pos := g.out.len g.write_fn_ptr_decl(&elem_sym.info, '') fixed_elem_name = g.out.cut_to(pos) mut def_str := 'typedef ${fixed_elem_name};' def_str = def_str.replace_once('(*)', '(*${styp}[${len}])') g.type_definitions.writeln(def_str) } else { g.type_definitions.writeln('typedef ${fixed_elem_name} ${styp} [${len}];') } } } else {} } } } 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 } } } } // sort structs by dependent fields fn (mut g Gen) sort_structs(typesa []&ast.TypeSymbol) []&ast.TypeSymbol { util.timing_start(@METHOD) defer { util.timing_measure(@METHOD) } mut dep_graph := depgraph.new_dep_graph() // types name list mut type_names := []string{} for sym in typesa { type_names << sym.name } // loop over types for sym in typesa { if sym.kind == .interface_ { dep_graph.add(sym.name, []) continue } // create list of deps mut field_deps := []string{} match sym.info { ast.ArrayFixed { mut skip := false // allow: `struct Node{ children [4]&Node }` // skip adding the struct as a dependency to the fixed array: [4]&Node -> Node // the struct must depend on the fixed array for definition order: Node -> [4]&Node // NOTE: Is there is a simpler way to do this? elem_sym := g.table.final_sym(sym.info.elem_type) if elem_sym.info is ast.Struct && sym.info.elem_type.is_ptr() { for field in elem_sym.info.fields { if sym.idx == field.typ.idx() { skip = true break } } } if !skip { dep := g.table.final_sym(sym.info.elem_type).name if dep in type_names { field_deps << dep } } } ast.Struct { for embed in sym.info.embeds { dep := g.table.sym(embed).name // skip if not in types list or already in deps if dep !in type_names || dep in field_deps { continue } field_deps << dep } for field in sym.info.fields { if field.typ.is_ptr() { continue } fsym := g.table.sym(field.typ) dep := fsym.name // skip if not in types list or already in deps if dep !in type_names || dep in field_deps { continue } field_deps << dep if fsym.info is ast.Alias { xdep := g.table.sym(fsym.info.parent_type).name if xdep !in type_names || xdep in field_deps { continue } field_deps << xdep } } } ast.SumType { for variant in sym.info.variants { vsym := g.table.sym(variant) if vsym.info !is ast.Struct { continue } fields := g.table.struct_fields(vsym) for field in fields { if field.typ.is_ptr() { continue } fsym := g.table.sym(field.typ) if fsym.info is ast.Alias { xsym := g.table.sym(fsym.info.parent_type) if xsym.info !is ast.ArrayFixed { continue } xdep := xsym.name // skip if not in types list or already in deps if xdep !in type_names || xdep in field_deps { continue } field_deps << xdep continue } if fsym.info !is ast.ArrayFixed { continue } dep := fsym.name // skip if not in types list or already in deps if dep !in type_names || dep in field_deps { continue } field_deps << dep } } } else {} } // add type and dependent types to graph dep_graph.add(sym.name, field_deps) } // sort graph dep_graph_sorted := dep_graph.resolve() if !dep_graph_sorted.acyclic { // this should no longer be called since it's catched in the parser // TODO: should it be removed? verror('cgen.sort_structs(): the following structs form a dependency cycle:\n' + dep_graph_sorted.display_cycles() + '\nyou can solve this by making one or both of the dependent struct fields references, eg: field &MyStruct' + '\nif you feel this is an error, please create a new issue here: https://github.com/vlang/v/issues and tag @joe-conigliaro') } // sort types unsafe { mut sorted_symbols := []&ast.TypeSymbol{cap: dep_graph_sorted.nodes.len} for node in dep_graph_sorted.nodes { sorted_symbols << g.table.sym_by_idx(g.table.type_idxs[node.name]) } return sorted_symbols } } fn (mut g Gen) gen_or_block_stmts(cvar_name string, cast_typ string, stmts []ast.Stmt, return_type ast.Type, is_option bool) { g.indent++ for i, stmt in stmts { if i == stmts.len - 1 { expr_stmt := stmt as ast.ExprStmt g.set_current_pos_as_last_stmt_pos() if g.inside_return && (expr_stmt.typ.idx() == ast.error_type_idx || expr_stmt.typ in [ast.none_type, ast.error_type]) { // `return foo() or { error('failed') }` if g.cur_fn != unsafe { nil } { if g.cur_fn.return_type.has_flag(.result) { g.write('return ') g.gen_result_error(g.cur_fn.return_type, expr_stmt.expr) g.writeln(';') } else if g.cur_fn.return_type.has_flag(.option) { g.write('return ') g.gen_option_error(g.cur_fn.return_type, expr_stmt.expr) g.writeln(';') } } } else { if expr_stmt.typ == ast.none_type_idx { g.write('${cvar_name} = ') g.gen_option_error(return_type, expr_stmt.expr) g.writeln(';') } else if return_type == ast.rvoid_type { // fn returns !, do not fill var.data old_inside_opt_data := g.inside_opt_data g.inside_opt_data = true g.expr(expr_stmt.expr) g.inside_opt_data = old_inside_opt_data g.writeln(';') g.stmt_path_pos.delete_last() } else { if is_option { g.write('*(${cast_typ}*) ${cvar_name}.data = ') } else { g.write('${cvar_name} = ') } old_inside_opt_data := g.inside_opt_data g.inside_opt_data = true g.expr_with_cast(expr_stmt.expr, expr_stmt.typ, return_type.clear_flags(.option, .result)) g.inside_opt_data = old_inside_opt_data g.writeln(';') g.stmt_path_pos.delete_last() } } } else { g.stmt(stmt) } } g.indent-- } // If user is accessing the return value eg. in assigment, pass the variable name. // If the user is not using the option return value. We need to pass a temp var // to access its fields (`.ok`, `.error` etc) // `os.cp(...)` => `Option bool tmp = os__cp(...); if (tmp.state != 0) { ... }` // Returns the type of the last stmt fn (mut g Gen) or_block(var_name string, or_block ast.OrExpr, return_type ast.Type) { cvar_name := c_name(var_name) mut mr_styp := g.base_type(return_type) is_none_ok := return_type == ast.ovoid_type g.writeln(';') if is_none_ok { g.writeln('if (${cvar_name}.state != 0) {') } else { if return_type != 0 && g.table.sym(return_type).kind == .function { mr_styp = 'voidptr' } if return_type.has_flag(.result) { g.writeln('if (${cvar_name}.is_error) {') // /*or block*/ ') } else { g.writeln('if (${cvar_name}.state != 0) {') // /*or block*/ ') } } if or_block.kind == .block { g.or_expr_return_type = return_type.clear_flags(.option, .result) g.writeln('\tIError err = ${cvar_name}.err;') g.inside_or_block = true defer { g.inside_or_block = false } stmts := or_block.stmts if stmts.len > 0 && stmts.last() is ast.ExprStmt && stmts.last().typ != ast.void_type { g.gen_or_block_stmts(cvar_name, mr_styp, stmts, return_type, true) } else { g.stmts(stmts) if stmts.len > 0 && stmts.last() is ast.ExprStmt { g.writeln(';') } } g.or_expr_return_type = ast.void_type } else if or_block.kind == .propagate_result || (or_block.kind == .propagate_option && return_type.has_flag(.result)) { if g.file.mod.name == 'main' && (g.fn_decl == unsafe { nil } || g.fn_decl.is_main) { // In main(), an `opt()!` call is sugar for `opt() or { panic(err) }` err_msg := 'IError_name_table[${cvar_name}.err._typ]._method_msg(${cvar_name}.err._object)' if g.pref.is_debug { paline, pafile, pamod, pafn := g.panic_debug_info(or_block.pos) g.writeln('panic_debug(${paline}, tos3("${pafile}"), tos3("${pamod}"), tos3("${pafn}"), ${err_msg});') } else { g.writeln('\tpanic_result_not_set(${err_msg});') } } else if g.fn_decl != unsafe { nil } && g.fn_decl.is_test { g.gen_failing_error_propagation_for_test_fn(or_block, cvar_name) } else { // In ordinary functions, `opt()!` call is sugar for: // `opt() or { return err }` // Since we *do* return, first we have to ensure that // the deferred statements are generated. g.write_defer_stmts() // Now that option types are distinct we need a cast here if g.fn_decl.return_type == ast.void_type { g.writeln('\treturn;') } else { styp := g.typ(g.fn_decl.return_type) err_obj := g.new_tmp_var() g.writeln('\t${styp} ${err_obj};') g.writeln('\tmemcpy(&${err_obj}, &${cvar_name}, sizeof(${c.result_name}));') g.writeln('\treturn ${err_obj};') } } } else if or_block.kind == .propagate_option { if g.file.mod.name == 'main' && (g.fn_decl == unsafe { nil } || g.fn_decl.is_main) { // In main(), an `opt()?` call is sugar for `opt() or { panic(err) }` err_msg := 'IError_name_table[${cvar_name}.err._typ]._method_msg(${cvar_name}.err._object)' if g.pref.is_debug { paline, pafile, pamod, pafn := g.panic_debug_info(or_block.pos) g.writeln('panic_debug(${paline}, tos3("${pafile}"), tos3("${pamod}"), tos3("${pafn}"), ${err_msg}.len == 0 ? _SLIT("option not set ()") : ${err_msg});') } else { g.writeln('\tpanic_option_not_set( ${err_msg} );') } } else if g.fn_decl != unsafe { nil } && g.fn_decl.is_test { g.gen_failing_error_propagation_for_test_fn(or_block, cvar_name) } else { // In ordinary functions, `opt()?` call is sugar for: // `opt() or { return err }` // Since we *do* return, first we have to ensure that // the deferred statements are generated. g.write_defer_stmts() // Now that option types are distinct we need a cast here if g.fn_decl.return_type == ast.void_type { g.writeln('\treturn;') } else { styp := g.typ(g.fn_decl.return_type) err_obj := g.new_tmp_var() g.writeln('\t${styp} ${err_obj};') g.writeln('\tmemcpy(&${err_obj}, &${cvar_name}, sizeof(_option));') g.writeln('\treturn ${err_obj};') } } } g.writeln('}') g.set_current_pos_as_last_stmt_pos() } [inline] fn c_name(name_ string) string { name := util.no_dots(name_) if c.c_reserved_chk.matches(name) { return '_v_${name}' } return name } fn (mut g Gen) type_default(typ_ ast.Type) string { typ := g.unwrap_generic(typ_) if typ.has_flag(.option) || typ.has_flag(.result) { return '{0}' } // Always set pointers to 0 if typ.is_ptr() && !typ.has_flag(.shared_f) { return '0' } if typ.idx() < ast.string_type_idx { // Default values for other types are not needed because of mandatory initialization return '0' } sym := g.table.sym(typ) match sym.kind { .string { return '(string){.str=(byteptr)"", .is_lit=1}' } .interface_, .sum_type, .array_fixed, .multi_return { return '{0}' } .alias { return g.type_default((sym.info as ast.Alias).parent_type) } .chan { elem_type := sym.chan_info().elem_type elemtypstr := g.typ(elem_type) noscan := g.check_noscan(elem_type) return 'sync__new_channel_st${noscan}(0, sizeof(${elemtypstr}))' } .array { elem_typ := sym.array_info().elem_type elem_sym := g.typ(elem_typ) mut elem_type_str := util.no_dots(elem_sym) if elem_type_str.starts_with('C__') { elem_type_str = elem_type_str[3..] } noscan := g.check_noscan(elem_typ) init_str := '__new_array${noscan}(0, 0, sizeof(${elem_type_str}))' if typ.has_flag(.shared_f) { atyp := '__shared__${sym.cname}' return '(${atyp}*)__dup_shared_array(&(${atyp}){.mtx = {0}, .val =${init_str}}, sizeof(${atyp}))' } else { return init_str } } .map { info := sym.map_info() key_typ := g.table.sym(info.key_type) hash_fn, key_eq_fn, clone_fn, free_fn := g.map_fn_ptrs(key_typ) noscan_key := g.check_noscan(info.key_type) noscan_value := g.check_noscan(info.value_type) mut noscan := if noscan_key.len != 0 || noscan_value.len != 0 { '_noscan' } else { '' } if noscan.len != 0 { if noscan_key.len != 0 { noscan += '_key' } if noscan_value.len != 0 { noscan += '_value' } } init_str := 'new_map${noscan}(sizeof(${g.typ(info.key_type)}), sizeof(${g.typ(info.value_type)}), ${hash_fn}, ${key_eq_fn}, ${clone_fn}, ${free_fn})' if typ.has_flag(.shared_f) { mtyp := '__shared__Map_${key_typ.cname}_${g.table.sym(info.value_type).cname}' return '(${mtyp}*)__dup_shared_map(&(${mtyp}){.mtx = {0}, .val =${init_str}}, sizeof(${mtyp}))' } else { return init_str } } .struct_ { mut has_none_zero := false mut init_str := '{' info := sym.info as ast.Struct if sym.language == .v { for field in info.fields { field_sym := g.table.sym(field.typ) if field.has_default_expr || field_sym.kind in [.array, .map, .string, .bool, .alias, .i8, .i16, .int, .i64, .u8, .u16, .u32, .u64, .f32, .f64, .char, .voidptr, .byteptr, .charptr, .struct_, .chan] { field_name := c_name(field.name) if field.has_default_expr { mut expr_str := '' if g.table.sym(field.typ).kind in [.sum_type, .interface_] { expr_str = g.expr_string_with_cast(field.default_expr, field.default_expr_typ, field.typ) } else { expr_str = g.expr_string(field.default_expr) } init_str += '.${field_name} = ${expr_str},' } else { mut zero_str := g.type_default(field.typ) if zero_str == '{0}' { if field_sym.info is ast.Struct && field_sym.language == .v { if field_sym.info.fields.len == 0 && field_sym.info.embeds.len == 0 { zero_str = '{EMPTY_STRUCT_INITIALIZATION}' } } } init_str += '.${field_name} = ${zero_str},' } has_none_zero = true } } } typ_is_shared_f := typ.has_flag(.shared_f) if has_none_zero { init_str += '}' if !typ_is_shared_f { type_name := if info.is_anon { // No name needed for anon structs, C figures it out on its own. '' } else { '(${g.typ(typ)})' } init_str = type_name + init_str } } else { init_str += '0}' } if typ_is_shared_f { styp := '__shared__${g.table.sym(typ).cname}' return '(${styp}*)__dup${styp}(&(${styp}){.mtx = {0}, .val = ${init_str}}, sizeof(${styp}))' } else { return init_str } } .enum_ { // returns the enum's first value if enum_decl := g.table.enum_decls[sym.name] { return if enum_decl.fields[0].expr is ast.EmptyExpr { '0' } else { g.expr_string(enum_decl.fields[0].expr) } } else { return '0' } } else { return '0' } } } fn (g Gen) get_all_test_function_names() []string { mut tfuncs := []string{} mut tsuite_begin := '' mut tsuite_end := '' for name in g.test_function_names { if name.ends_with('.testsuite_begin') { tsuite_begin = name continue } if name.contains('.test_') { tfuncs << name continue } if name.ends_with('.testsuite_end') { tsuite_end = name continue } } mut all_tfuncs := []string{} if tsuite_begin.len > 0 { all_tfuncs << tsuite_begin } all_tfuncs << tfuncs if tsuite_end.len > 0 { all_tfuncs << tsuite_end } return all_tfuncs } [inline] fn (mut g Gen) get_type(typ ast.Type) ast.Type { return if typ == g.field_data_type { g.comptime_for_field_value.typ } else { typ } } fn (mut g Gen) size_of(node ast.SizeOf) { typ := g.resolve_comptime_type(node.expr, g.get_type(node.typ)) node_typ := g.unwrap_generic(typ) sym := g.table.sym(node_typ) if sym.language == .v && sym.kind in [.placeholder, .any] { g.error('unknown type `${sym.name}`', node.pos) } if node.expr is ast.StringLiteral { if node.expr.language == .c { g.write('sizeof("${node.expr.val}")') return } } styp := g.typ(node_typ) g.write('sizeof(${util.no_dots(styp)})') } [inline] fn (mut g Gen) gen_enum_prefix(typ ast.Type) string { if g.pref.translated && typ.is_number() { // Mostly in translated code, when C enums are used as ints in switches return '_const_main__' } else { styp := g.typ(g.table.unaliased_type(typ)) return '${styp}__' } } fn (mut g Gen) enum_val(node ast.EnumVal) { g.write('${g.gen_enum_prefix(node.typ)}${node.val}') } fn (mut g Gen) as_cast(node ast.AsCast) { // Make sure the sum type can be cast to this type (the types // are the same), otherwise panic. unwrapped_node_typ := g.unwrap_generic(node.typ) styp := g.typ(unwrapped_node_typ) sym := g.table.sym(unwrapped_node_typ) mut expr_type_sym := g.table.sym(g.unwrap_generic(node.expr_type)) if mut expr_type_sym.info is ast.SumType { dot := if node.expr_type.is_ptr() { '->' } else { '.' } if node.expr is ast.CallExpr && !g.is_cc_msvc { tmp_var := g.new_tmp_var() expr_styp := g.typ(node.expr_type) g.write('({ ${expr_styp} ${tmp_var} = ') g.expr(node.expr) g.write('; ') if sym.info is ast.FnType { g.write('/* as */ (${styp})__as_cast(') } else { g.write('/* as */ *(${styp}*)__as_cast(') } g.write(tmp_var) g.write(dot) g.write('_${sym.cname},') g.write(tmp_var) g.write(dot) sidx := g.type_sidx(unwrapped_node_typ) g.write('_typ, ${sidx}); }) /*expected idx: ${sidx}, name: ${sym.name} */ ') } else { if sym.info is ast.FnType { g.write('/* as */ (${styp})__as_cast(') } else { g.write('/* as */ *(${styp}*)__as_cast(') } g.write('(') g.expr(node.expr) g.write(')') g.write(dot) g.write('_${sym.cname},') g.write('(') g.expr(node.expr) g.write(')') g.write(dot) sidx := g.type_sidx(unwrapped_node_typ) g.write('_typ, ${sidx}) /*expected idx: ${sidx}, name: ${sym.name} */ ') } // fill as cast name table for variant in expr_type_sym.info.variants { idx := u32(variant).str() if idx in g.as_cast_type_names { continue } variant_sym := g.table.sym(variant) g.as_cast_type_names[idx] = variant_sym.name } } else if expr_type_sym.kind == .interface_ && sym.kind == .interface_ { g.write('I_${expr_type_sym.cname}_as_I_${sym.cname}(') if node.expr_type.is_ptr() { g.write('*') } g.expr(node.expr) g.write(')') mut info := expr_type_sym.info as ast.Interface if node.typ !in info.conversions { left_variants := g.table.iface_types[expr_type_sym.name] right_variants := g.table.iface_types[sym.name] info.conversions[node.typ] = left_variants.filter(it in right_variants) } expr_type_sym.info = info } else { g.expr(node.expr) } } fn (g Gen) as_cast_name_table() string { if g.as_cast_type_names.len == 0 { return 'new_array_from_c_array(1, 1, sizeof(VCastTypeIndexName), _MOV((VCastTypeIndexName[1]){(VCastTypeIndexName){.tindex = 0,.tname = _SLIT("unknown")}}));\n' } mut name_ast := strings.new_builder(1024) casts_len := g.as_cast_type_names.len + 1 name_ast.writeln('new_array_from_c_array(${casts_len}, ${casts_len}, sizeof(VCastTypeIndexName), _MOV((VCastTypeIndexName[${casts_len}]){') name_ast.writeln('\t\t (VCastTypeIndexName){.tindex = 0, .tname = _SLIT("unknown")}') for key, value in g.as_cast_type_names { name_ast.writeln('\t\t, (VCastTypeIndexName){.tindex = ${key}, .tname = _SLIT("${value}")}') } name_ast.writeln('\t}));\n') return name_ast.str() } fn (g Gen) has_been_referenced(fn_name string) bool { mut referenced := false lock g.referenced_fns { referenced = g.referenced_fns[fn_name] } return referenced } // Generates interface table and interface indexes fn (mut g Gen) interface_table() string { util.timing_start(@METHOD) defer { util.timing_measure(@METHOD) } mut sb := strings.new_builder(100) mut conversion_functions := strings.new_builder(100) for isym in g.table.type_symbols { if isym.kind != .interface_ { continue } if isym.info !is ast.Interface { // Do not remove this check, `isym.info` could be `&IError`. continue } inter_info := isym.info as ast.Interface if inter_info.is_generic { continue } // interface_name is for example Speaker interface_name := isym.cname // generate a struct that references interface methods methods_struct_name := 'struct _${interface_name}_interface_methods' mut methods_struct_def := strings.new_builder(100) methods_struct_def.writeln('${methods_struct_name} {') mut methodidx := map[string]int{} for k, method in inter_info.methods { methodidx[method.name] = k ret_styp := g.typ(method.return_type) methods_struct_def.write_string('\t${ret_styp} (*_method_${c_name(method.name)})(void* _') // the first param is the receiver, it's handled by `void*` above for i in 1 .. method.params.len { arg := method.params[i] methods_struct_def.write_string(', ${g.typ(arg.typ)} ${arg.name}') } // TODO g.fn_args(method.args[1..]) methods_struct_def.writeln(');') } methods_struct_def.writeln('};') // generate an array of the interface methods for the structs using the interface // as well as case functions from the struct to the interface mut methods_struct := strings.new_builder(100) // iname_table_length := inter_info.types.len if iname_table_length == 0 { // msvc can not process `static struct x[0] = {};` methods_struct.writeln('${g.static_modifier} ${methods_struct_name} ${interface_name}_name_table[1];') } else { if g.pref.build_mode != .build_module { methods_struct.writeln('${g.static_modifier} ${methods_struct_name} ${interface_name}_name_table[${iname_table_length}] = {') } else { methods_struct.writeln('${g.static_modifier} ${methods_struct_name} ${interface_name}_name_table[${iname_table_length}];') } } mut cast_functions := strings.new_builder(100) mut methods_wrapper := strings.new_builder(100) methods_wrapper.writeln('// Methods wrapper for interface "${interface_name}"') mut already_generated_mwrappers := map[string]int{} iinidx_minimum_base := 1000 // Note: NOT 0, to avoid map entries set to 0 later, so `if already_generated_mwrappers[name] > 0 {` works. mut current_iinidx := iinidx_minimum_base for st in inter_info.types { st_sym := g.table.sym(ast.mktyp(st)) // cctype is the Cleaned Concrete Type name, *without ptr*, // i.e. cctype is always just Cat, not Cat_ptr: cctype := g.cc_type(ast.mktyp(st), true) $if debug_interface_table ? { eprintln('>> interface name: ${isym.name} | concrete type: ${st.debug()} | st symname: ${st_sym.name}') } // Speaker_Cat_index = 0 interface_index_name := '_${interface_name}_${cctype}_index' if already_generated_mwrappers[interface_index_name] > 0 { continue } already_generated_mwrappers[interface_index_name] = current_iinidx current_iinidx++ if isym.name != 'vweb.DbInterface' { // TODO remove this sb.writeln('static ${interface_name} I_${cctype}_to_Interface_${interface_name}(${cctype}* x);') mut cast_struct := strings.new_builder(100) cast_struct.writeln('(${interface_name}) {') cast_struct.writeln('\t\t._${cctype} = x,') cast_struct.writeln('\t\t._typ = ${interface_index_name},') for field in inter_info.fields { cname := c_name(field.name) field_styp := g.typ(field.typ) if _ := st_sym.find_field(field.name) { cast_struct.writeln('\t\t.${cname} = (${field_styp}*)((char*)x + __offsetof_ptr(x, ${cctype}, ${cname})),') } else if st_sym.kind == .array && field.name in ['element_size', 'data', 'offset', 'len', 'cap', 'flags'] { // Manaully checking, we already knows array contains above fields cast_struct.writeln('\t\t.${cname} = (${field_styp}*)((char*)x + __offsetof_ptr(x, ${cctype}, ${cname})),') } else { // the field is embedded in another struct cast_struct.write_string('\t\t.${cname} = (${field_styp}*)((char*)x') if st == ast.voidptr_type || st == ast.nil_type { cast_struct.write_string('/*.... ast.voidptr_type */') } else { if st_sym.kind == .struct_ { for embed_type in st_sym.struct_info().embeds { embed_sym := g.table.sym(embed_type) if _ := embed_sym.find_field(field.name) { cast_struct.write_string(' + __offsetof_ptr(x, ${cctype}, ${embed_sym.embed_name()}) + __offsetof_ptr(x, ${embed_sym.cname}, ${cname})') break } } } } cast_struct.writeln('),') } } cast_struct.write_string('\t}') cast_struct_str := cast_struct.str() cast_functions.writeln(' // Casting functions for converting "${cctype}" to interface "${interface_name}" static inline ${interface_name} I_${cctype}_to_Interface_${interface_name}(${cctype}* x) { return ${cast_struct_str}; }') shared_fn_name := 'I___shared__${cctype}_to_shared_Interface___shared__${interface_name}' // Avoid undefined types errors by only generating the converters that are referenced: if g.has_been_referenced(shared_fn_name) { mut cast_shared_struct := strings.new_builder(100) cast_shared_struct.writeln('(__shared__${interface_name}) {') cast_shared_struct.writeln('\t\t.mtx = {0},') cast_shared_struct.writeln('\t\t.val = {') cast_shared_struct.writeln('\t\t\t._${cctype} = &x->val,') cast_shared_struct.writeln('\t\t\t._typ = ${interface_index_name},') cast_shared_struct.writeln('\t\t}') cast_shared_struct.write_string('\t}') cast_shared_struct_str := cast_shared_struct.str() cast_functions.writeln(' // Casting functions for converting "__shared__${cctype}" to interface "__shared__${interface_name}" static inline __shared__${interface_name} ${shared_fn_name}(__shared__${cctype}* x) { return ${cast_shared_struct_str}; }') } } if g.pref.build_mode != .build_module { methods_struct.writeln('\t{') } if st == ast.voidptr_type || st == ast.nil_type { for mname, _ in methodidx { if g.pref.build_mode != .build_module { methods_struct.writeln('\t\t._method_${c_name(mname)} = (void*) 0,') } } } mut methods := st_sym.methods.clone() method_names := methods.map(it.name) match st_sym.info { ast.Struct, ast.Interface, ast.SumType { if st_sym.info.parent_type.has_flag(.generic) { parent_sym := g.table.sym(st_sym.info.parent_type) for method in parent_sym.methods { if method.name in methodidx { methods << st_sym.find_method_with_generic_parent(method.name) or { continue } } } } } else {} } t_methods := g.table.get_embed_methods(st_sym) for t_method in t_methods { if t_method.name !in methods.map(it.name) { methods << t_method } } for method in methods { mut name := method.name if method.generic_names.len > 0 && inter_info.parent_type.has_flag(.generic) { parent_sym := g.table.sym(inter_info.parent_type) match parent_sym.info { ast.Struct, ast.Interface, ast.SumType { name = g.generic_fn_name(parent_sym.info.concrete_types, method.name) } else {} } } if method.name !in methodidx { // a method that is not part of the interface should be just skipped continue } // .speak = Cat_speak if st_sym.info is ast.Struct { if method.generic_names.len > 0 && st_sym.info.parent_type.has_flag(.generic) { name = g.generic_fn_name(st_sym.info.concrete_types, method.name) } } styp := g.cc_type(method.params[0].typ, true) mut method_call := '${styp}_${name}' if !method.params[0].typ.is_ptr() { method_call = '${cctype}_${name}' // inline void Cat_speak_Interface_Animal_method_wrapper(Cat c) { return Cat_speak(*c); } iwpostfix := '_Interface_${interface_name}_method_wrapper' methods_wrapper.write_string('static inline ${g.typ(method.return_type)} ${cctype}_${name}${iwpostfix}(') params_start_pos := g.out.len mut params := method.params.clone() // hack to mutate typ params[0] = ast.Param{ ...params[0] typ: st.set_nr_muls(1) } fargs, _, _ := g.fn_decl_params(params, unsafe { nil }, false) mut parameter_name := g.out.cut_last(g.out.len - params_start_pos) if st.is_ptr() { parameter_name = parameter_name.trim_string_left('__shared__') } methods_wrapper.write_string(parameter_name) methods_wrapper.writeln(') {') methods_wrapper.write_string('\t') if method.return_type != ast.void_type { methods_wrapper.write_string('return ') } _, embed_types := g.table.find_method_from_embeds(st_sym, method.name) or { ast.Fn{}, []ast.Type{} } if embed_types.len > 0 && method.name !in method_names { embed_sym := g.table.sym(embed_types.last()) method_name := '${embed_sym.cname}_${method.name}' methods_wrapper.write_string('${method_name}(${fargs[0]}') for idx_embed, embed in embed_types { esym := g.table.sym(embed) if idx_embed == 0 || embed_types[idx_embed - 1].is_any_kind_of_pointer() { methods_wrapper.write_string('->${esym.embed_name()}') } else { methods_wrapper.write_string('.${esym.embed_name()}') } } if fargs.len > 1 { methods_wrapper.write_string(', ') } args := fargs[1..].join(', ') methods_wrapper.writeln('${args});') } else { if parameter_name.starts_with('__shared__') { methods_wrapper.writeln('${method_call}(${fargs.join(', ')}->val);') } else { methods_wrapper.writeln('${method_call}(*${fargs.join(', ')});') } } methods_wrapper.writeln('}') // .speak = Cat_speak_Interface_Animal_method_wrapper method_call += iwpostfix } if g.pref.build_mode != .build_module && st != ast.voidptr_type && st != ast.nil_type { methods_struct.writeln('\t\t._method_${c_name(method.name)} = (void*) ${method_call},') } } // >> Hack to allow old style custom error implementations // TODO: remove once deprecation period for `IError` methods has ended // fix MSVC not handling empty struct inits if methods.len == 0 && isym.idx == ast.error_type_idx { methods_struct.writeln('\t\t._method_msg = NULL,') methods_struct.writeln('\t\t._method_code = NULL,') } // << if g.pref.build_mode != .build_module { methods_struct.writeln('\t},') } iin_idx := already_generated_mwrappers[interface_index_name] - iinidx_minimum_base if g.pref.build_mode != .build_module { sb.writeln('${g.static_modifier} const int ${interface_index_name} = ${iin_idx};') } else { sb.writeln('extern const int ${interface_index_name};') } } for vtyp, variants in inter_info.conversions { vsym := g.table.sym(vtyp) if variants.len > 0 { conversion_functions.write_string('static inline bool I_${interface_name}_is_I_${vsym.cname}(${interface_name} x) {\n\treturn ') for i, variant in variants { variant_sym := g.table.sym(variant) if i > 0 { conversion_functions.write_string(' || ') } conversion_functions.write_string('(x._typ == _${interface_name}_${variant_sym.cname}_index)') } conversion_functions.writeln(';\n}') } conversion_functions.writeln('static inline ${vsym.cname} I_${interface_name}_as_I_${vsym.cname}(${interface_name} x) {') for variant in variants { variant_sym := g.table.sym(variant) conversion_functions.writeln('\tif (x._typ == _${interface_name}_${variant_sym.cname}_index) return I_${variant_sym.cname}_to_Interface_${vsym.cname}(x._${variant_sym.cname});') } pmessage := 'string__plus(string__plus(tos3("`as_cast`: cannot convert "), tos3(v_typeof_interface_${interface_name}(x._typ))), tos3(" to ${util.strip_main_name(vsym.name)}"))' if g.pref.is_debug { // TODO: actually return a valid position here conversion_functions.write_string('\tpanic_debug(1, tos3("builtin.v"), tos3("builtin"), tos3("__as_cast"), ') conversion_functions.write_string(pmessage) conversion_functions.writeln(');') } else { conversion_functions.write_string('\t_v_panic(') conversion_functions.write_string(pmessage) conversion_functions.writeln(');') } conversion_functions.writeln('\treturn (${vsym.cname}){0};') conversion_functions.writeln('}') } sb.writeln('// ^^^ number of types for interface ${interface_name}: ${current_iinidx - iinidx_minimum_base}') if iname_table_length == 0 { methods_struct.writeln('') } else { if g.pref.build_mode != .build_module { methods_struct.writeln('};') } } // add line return after interface index declarations sb.writeln('') if inter_info.methods.len > 0 { sb.writeln(methods_wrapper.str()) sb.writeln(methods_struct_def.str()) sb.writeln(methods_struct.str()) } sb.writeln(cast_functions.str()) } sb.writeln(conversion_functions.str()) return sb.str() } fn (mut g Gen) panic_debug_info(pos token.Pos) (int, string, string, string) { paline := pos.line_nr + 1 if g.fn_decl == unsafe { nil } { return paline, '', 'main', 'C._vinit' } pafile := g.fn_decl.file.replace('\\', '/') pafn := g.fn_decl.name.after('.') pamod := g.fn_decl.modname() return paline, pafile, pamod, pafn } pub fn get_guarded_include_text(iname string, imessage string) string { res := ' |#if defined(__has_include) | |#if __has_include(${iname}) |#include ${iname} |#else |#error VERROR_MESSAGE ${imessage} |#endif | |#else |#include ${iname} |#endif '.strip_margin() return res } fn (mut g Gen) trace(fbase string, message string) { if g.file.path_base == fbase { println('> g.trace | ${fbase:-10s} | ${message}') } } pub fn (mut g Gen) get_array_depth(el_typ ast.Type) int { typ := g.unwrap_generic(el_typ) sym := g.table.final_sym(typ) if sym.kind == .array { info := sym.info as ast.Array return 1 + g.get_array_depth(info.elem_type) } else { return 0 } } // returns true if `t` includes any pointer(s) - during garbage collection heap regions // that contain no pointers do not have to be scanned pub fn (mut g Gen) contains_ptr(el_typ ast.Type) bool { if el_typ.is_any_kind_of_pointer() { return true } typ := g.unwrap_generic(el_typ) if typ.is_ptr() { return true } sym := g.table.final_sym(typ) if sym.language != .v { return true } match sym.kind { .i8, .i16, .int, .i64, .u8, .u16, .u32, .u64, .f32, .f64, .char, .rune, .bool, .enum_ { return false } .array_fixed { info := sym.info as ast.ArrayFixed return g.contains_ptr(info.elem_type) } .struct_ { info := sym.info as ast.Struct for embed in info.embeds { if g.contains_ptr(embed) { return true } } for field in info.fields { if g.contains_ptr(field.typ) { return true } } return false } .aggregate { info := sym.info as ast.Aggregate for atyp in info.types { if g.contains_ptr(atyp) { return true } } return false } .multi_return { info := sym.info as ast.MultiReturn for mrtyp in info.types { if g.contains_ptr(mrtyp) { return true } } return false } else { return true } } } fn (mut g Gen) check_noscan(elem_typ ast.Type) string { if g.pref.gc_mode in [.boehm_full_opt, .boehm_incr_opt] { if !g.contains_ptr(elem_typ) { return '_noscan' } } return '' }