v/vlib/v/gen/c/fn.v

2191 lines
66 KiB
V

// Copyright (c) 2019-2022 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 strings
import v.ast
import v.util
fn (mut g Gen) is_used_by_main(node ast.FnDecl) bool {
mut is_used_by_main := true
if g.pref.skip_unused {
fkey := node.fkey()
is_used_by_main = g.table.used_fns[fkey]
$if trace_skip_unused_fns ? {
println('> is_used_by_main: $is_used_by_main | node.name: $node.name | fkey: $fkey | node.is_method: $node.is_method')
}
if !is_used_by_main {
$if trace_skip_unused_fns_in_c_code ? {
g.writeln('// trace_skip_unused_fns_in_c_code, $node.name, fkey: $fkey')
}
}
} else {
$if trace_skip_unused_fns_in_c_code ? {
g.writeln('// trace_skip_unused_fns_in_c_code, $node.name, fkey: $node.fkey()')
}
}
return is_used_by_main
}
fn (mut g Gen) fn_decl(node ast.FnDecl) {
if node.should_be_skipped {
return
}
if node.ninstances == 0 && node.generic_names.len > 0 {
$if trace_generics ? {
eprintln('skipping generic fn with no concrete instances: $node.mod $node.name')
}
return
}
if !g.is_used_by_main(node) {
return
}
if g.is_builtin_mod && g.pref.gc_mode == .boehm_leak && node.name == 'malloc' {
g.definitions.write_string('#define _v_malloc GC_MALLOC\n')
return
}
g.gen_attrs(node.attrs)
mut skip := false
pos := g.out.len
should_bundle_module := util.should_bundle_module(node.mod)
/*
if node.name.contains('i_error') {
println(g.table.type_str(node.params[0].typ))
}
*/
if g.pref.build_mode == .build_module {
// if node.name.contains('parse_text') {
// println('!!! $node.name mod=$node.mod, built=$g.module_built')
// }
// TODO true for not just "builtin"
// TODO: clean this up
mod := if g.is_builtin_mod { 'builtin' } else { node.name.all_before_last('.') }
// for now dont skip generic functions as they are being marked as static
// when -usecache is enabled, until a better solution is implemented.
if ((mod != g.module_built && node.mod != g.module_built.after('/'))
|| should_bundle_module) && node.generic_names.len == 0 {
// Skip functions that don't have to be generated for this module.
// println('skip bm $node.name mod=$node.mod module_built=$g.module_built')
skip = true
}
if g.is_builtin_mod && g.module_built == 'builtin' && node.mod == 'builtin' {
skip = false
}
if !skip && g.pref.is_verbose {
println('build module `$g.module_built` fn `$node.name`')
}
}
if g.pref.use_cache {
// We are using prebuilt modules, we do not need to generate
// their functions in main.c.
if node.mod != 'main' && node.mod != 'help' && !should_bundle_module && !g.pref.is_test
&& node.generic_names.len == 0 {
skip = true
}
}
keep_fn_decl := g.fn_decl
unsafe {
g.fn_decl = &node
}
if node.is_main {
g.has_main = true
}
// TODO PERF remove this from here
is_backtrace := node.name.starts_with('backtrace')
&& node.name in ['backtrace_symbols', 'backtrace', 'backtrace_symbols_fd']
if is_backtrace {
g.write('\n#ifndef __cplusplus\n')
}
g.gen_fn_decl(node, skip)
if is_backtrace {
g.write('\n#endif\n')
}
g.fn_decl = keep_fn_decl
if skip {
g.out.go_back_to(pos)
}
if !g.pref.skip_unused {
if node.language != .c {
g.writeln('')
}
}
}
fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) {
// TODO For some reason, build fails with autofree with this line
// as it's only informative, comment it for now
// g.gen_attrs(it.attrs)
if node.language == .c {
// || node.no_body {
return
}
old_is_vlines_enabled := g.is_vlines_enabled
g.is_vlines_enabled = true
defer {
g.is_vlines_enabled = old_is_vlines_enabled
}
tmp_defer_vars := g.defer_vars // must be here because of workflow
if !g.anon_fn {
g.defer_vars = []string{}
} else {
if node.defer_stmts.len > 0 {
g.defer_vars = []string{}
defer {
g.defer_vars = tmp_defer_vars
}
}
}
// Skip [if xxx] if xxx is not defined
/*
for attr in node.attrs {
if !attr.is_comptime_define {
continue
}
if attr.name !in g.pref.compile_defines_all {
// println('skipping [if]')
return
}
}
*/
g.returned_var_name = ''
//
old_g_autofree := g.is_autofree
if node.is_manualfree {
g.is_autofree = false
}
defer {
g.is_autofree = old_g_autofree
}
//
// if g.fileis('vweb.v') {
// println('\ngen_fn_decl() $node.name $node.is_generic $g.cur_generic_type')
// }
if node.generic_names.len > 0 && g.cur_concrete_types.len == 0 { // need the cur_concrete_type check to avoid inf. recursion
// loop thru each generic type and generate a function
nkey := node.fkey()
generic_types_by_fn := g.table.fn_generic_types[nkey]
$if trace_post_process_generic_fns ? {
eprintln('>> gen_fn_decl, nkey: $nkey | generic_types_by_fn: $generic_types_by_fn')
}
for concrete_types in generic_types_by_fn {
if g.pref.is_verbose {
syms := concrete_types.map(g.table.sym(it))
the_type := syms.map(it.name).join(', ')
println('gen fn `$node.name` for type `$the_type`')
}
g.cur_concrete_types = concrete_types
g.gen_fn_decl(node, skip)
}
g.cur_concrete_types = []
return
}
cur_fn_save := g.cur_fn
defer {
g.cur_fn = cur_fn_save
}
unsafe {
// TODO remove unsafe
g.cur_fn = node
}
fn_start_pos := g.out.len
is_closure := node.scope.has_inherited_vars()
mut cur_closure_ctx := ''
if is_closure {
cur_closure_ctx = closure_ctx(node)
// declare the struct before its implementation
g.definitions.write_string(cur_closure_ctx)
g.definitions.writeln(';')
}
g.write_v_source_line_info(node.pos)
fn_attrs := g.write_fn_attrs(node.attrs)
// Live
is_livefn := node.attrs.contains('live')
is_livemain := g.pref.is_livemain && is_livefn
is_liveshared := g.pref.is_liveshared && is_livefn
is_livemode := g.pref.is_livemain || g.pref.is_liveshared
is_live_wrap := is_livefn && is_livemode
if is_livefn && !is_livemode {
eprintln('INFO: compile with `v -live $g.pref.path `, if you want to use the [live] function $node.name .')
}
//
mut name := g.c_fn_name(node) or { return }
mut type_name := g.typ(g.unwrap_generic(node.return_type))
if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') && !node.is_main
&& node.name != 'str' {
mut key := node.name
if node.is_method {
sym := g.table.sym(node.receiver.typ)
key = sym.name + '.' + node.name
}
g.writeln('/* obf: $key */')
name = g.obf_table[key] or {
panic('cgen: fn_decl: obf name "$key" not found, this should never happen')
}
}
// if g.pref.show_cc && it.is_builtin {
// println(name)
// }
// type_name := g.ast.Type_to_str(it.return_type)
// Live functions are protected by a mutex, because otherwise they
// can be changed by the live reload thread, *while* they are
// running, with unpredictable results (usually just crashing).
// For this purpose, the actual body of the live function,
// is put under a non publicly accessible function, that is prefixed
// with 'impl_live_' .
if is_livemain {
g.hotcode_fn_names << name
}
mut impl_fn_name := name
if is_live_wrap {
impl_fn_name = 'impl_live_$name'
}
last_fn_c_name_save := g.last_fn_c_name
defer {
g.last_fn_c_name = last_fn_c_name_save
}
g.last_fn_c_name = impl_fn_name
//
if is_live_wrap {
if is_livemain {
g.definitions.write_string('$type_name (* $impl_fn_name)(')
g.write('$type_name no_impl_${name}(')
}
if is_liveshared {
g.definitions.write_string('$type_name ${impl_fn_name}(')
g.write('$type_name ${impl_fn_name}(')
}
} else {
if !(node.is_pub || g.pref.is_debug) {
// Private functions need to marked as static so that they are not exportable in the
// binaries
if g.pref.build_mode != .build_module && !g.pref.use_cache {
// if !(g.pref.build_mode == .build_module && g.is_builtin_mod) {
// If we are building vlib/builtin, we need all private functions like array_get
// to be public, so that all V programs can access them.
g.write('VV_LOCAL_SYMBOL ')
g.definitions.write_string('VV_LOCAL_SYMBOL ')
}
}
// as a temp solution generic functions are marked static
// when -usecache is enabled to fix duplicate symbols with clang
// TODO: implement a better sulution
visibility_kw := if g.cur_concrete_types.len > 0
&& (g.pref.build_mode == .build_module || g.pref.use_cache) {
'static '
} else {
''
}
fn_header := '$visibility_kw$type_name $fn_attrs${name}('
g.definitions.write_string(fn_header)
g.write(fn_header)
}
arg_start_pos := g.out.len
fargs, fargtypes, heap_promoted := g.fn_decl_params(node.params, node.scope, node.is_variadic)
if is_closure {
g.nr_closures++
}
arg_str := g.out.after(arg_start_pos)
if node.no_body || ((g.pref.use_cache && g.pref.build_mode != .build_module) && node.is_builtin
&& !g.pref.is_test) || skip {
// Just a function header. Builtin function bodies are defined in builtin.o
g.definitions.writeln(');') // // NO BODY')
g.writeln(');')
return
}
if node.params.len == 0 {
g.definitions.write_string('void')
}
g.definitions.writeln(');')
g.writeln(') {')
if is_closure {
g.writeln('$cur_closure_ctx* $c.closure_ctx = *(void**)(__RETURN_ADDRESS() - __CLOSURE_DATA_OFFSET);')
}
for i, is_promoted in heap_promoted {
if is_promoted {
g.writeln('${fargtypes[i]}* ${fargs[i]} = HEAP(${fargtypes[i]}, _v_toheap_${fargs[i]});')
}
}
for defer_stmt in node.defer_stmts {
g.writeln('bool ${g.defer_flag_var(defer_stmt)} = false;')
for var in defer_stmt.defer_vars {
if var.name in fargs || var.kind == .constant {
continue
}
if var.kind == .variable {
if var.name !in g.defer_vars {
g.defer_vars << var.name
mut deref := ''
if v := var.scope.find_var(var.name) {
if v.is_auto_heap {
deref = '*'
}
}
info := var.obj as ast.Var
if g.table.sym(info.typ).kind != .function {
g.writeln('${g.typ(info.typ)}$deref ${c_name(var.name)};')
}
}
}
}
}
if is_live_wrap {
// The live function just calls its implementation dual, while ensuring
// that the call is wrapped by the mutex lock & unlock calls.
// Adding the mutex lock/unlock inside the body of the implementation
// function is not reliable, because the implementation function can do
// an early exit, which will leave the mutex locked.
mut fn_args_list := []string{}
for ia, fa in fargs {
fn_args_list << '${fargtypes[ia]} $fa'
}
mut live_fncall := '${impl_fn_name}(' + fargs.join(', ') + ');'
mut live_fnreturn := ''
if type_name != 'void' {
live_fncall = '$type_name res = $live_fncall'
live_fnreturn = 'return res;'
}
g.definitions.writeln('$type_name ${name}(' + fn_args_list.join(', ') + ');')
g.hotcode_definitions.writeln('$type_name ${name}(' + fn_args_list.join(', ') + '){')
g.hotcode_definitions.writeln(' pthread_mutex_lock(&live_fn_mutex);')
g.hotcode_definitions.writeln(' $live_fncall')
g.hotcode_definitions.writeln(' pthread_mutex_unlock(&live_fn_mutex);')
g.hotcode_definitions.writeln(' $live_fnreturn')
g.hotcode_definitions.writeln('}')
}
// Profiling mode? Start counting at the beginning of the function (save current time).
if g.pref.is_prof && g.pref.build_mode != .build_module {
g.profile_fn(node)
}
// we could be in an anon fn so save outer fn defer stmts
prev_defer_stmts := g.defer_stmts
g.defer_stmts = []
ctmp := g.tmp_count
g.tmp_count = 0
defer {
g.tmp_count = ctmp
}
prev_inside_ternary := g.inside_ternary
g.inside_ternary = 0
g.stmts(node.stmts)
g.inside_ternary = prev_inside_ternary
if node.is_noreturn {
g.writeln('\twhile(1);')
}
// clear g.fn_mut_arg_names
if !node.has_return {
g.write_defer_stmts_when_needed()
}
if node.is_anon {
g.defer_stmts = prev_defer_stmts
} else {
g.defer_stmts = []
}
if node.return_type != ast.void_type && node.stmts.len > 0 && node.stmts.last() !is ast.Return
&& !node.attrs.contains('_naked') {
default_expr := g.type_default(node.return_type)
// TODO: perf?
if default_expr == '{0}' {
// if node.return_type.idx() == 1 && node.return_type.has_flag(.optional) {
// // The default return for anonymous functions that return `?,
// // should have .ok = true set, otherwise calling them with
// // optfn() or { panic(err) } will cause a panic:
// g.writeln('\treturn (Option_void){0};')
// } else {
g.writeln('\treturn ($type_name)$default_expr;')
// }
} else {
g.writeln('\treturn $default_expr;')
}
}
g.writeln('}')
if g.pref.printfn_list.len > 0 && g.last_fn_c_name in g.pref.printfn_list {
println(g.out.after(fn_start_pos))
}
for attr in node.attrs {
if attr.name == 'export' {
weak := if node.attrs.any(it.name == 'weak') { 'VWEAK ' } else { '' }
g.writeln('// export alias: $attr.arg -> $name')
export_alias := '$weak$type_name $fn_attrs${attr.arg}($arg_str)'
g.definitions.writeln('VV_EXPORTED_SYMBOL $export_alias; // exported fn $node.name')
g.writeln('$export_alias {')
g.write('\treturn ${name}(')
g.write(fargs.join(', '))
g.writeln(');')
g.writeln('}')
}
}
}
fn (mut g Gen) c_fn_name(node &ast.FnDecl) ?string {
mut name := node.name
if name in ['+', '-', '*', '/', '%', '<', '=='] {
name = util.replace_op(name)
}
if node.is_method {
unwrapped_rec_sym := g.table.sym(g.unwrap_generic(node.receiver.typ))
if unwrapped_rec_sym.kind == .placeholder {
return none
}
name = g.cc_type(node.receiver.typ, false) + '_' + name
// name = g.table.sym(node.receiver.typ).name + '_' + name
}
if node.language == .c {
name = util.no_dots(name)
} else {
name = c_name(name)
}
if node.generic_names.len > 0 {
name = g.generic_fn_name(g.cur_concrete_types, name, true)
}
if (g.pref.translated || g.file.is_translated) && node.attrs.contains('c') {
// This fixes unknown symbols errors when building separate .c => .v files
// into .o files
//
// example:
// [c: 'P_TryMove']
// fn p_trymove(thing &Mobj_t, x int, y int) bool
//
// =>
//
// bool P_TryMove(main__Mobj_t* thing, int x, int y);
//
// In fn_call every time `p_trymove` is called, `P_TryMove` will be generated instead.
name = node.attrs[0].arg
}
return name
}
const closure_ctx = '_V_closure_ctx'
fn closure_ctx(node ast.FnDecl) string {
return 'struct _V_${node.name}_Ctx'
}
fn (mut g Gen) gen_anon_fn(mut node ast.AnonFn) {
g.gen_anon_fn_decl(mut node)
if !node.decl.scope.has_inherited_vars() {
g.write(node.decl.name)
return
}
ctx_struct := closure_ctx(node.decl)
// it may be possible to optimize `memdup` out if the closure never leaves current scope
// TODO in case of an assignment, this should only call "__closure_set_data" and "__closure_set_function" (and free the former data)
g.write('__closure_create($node.decl.name, ($ctx_struct*) memdup(&($ctx_struct){')
g.indent++
for var in node.inherited_vars {
g.writeln('.$var.name = $var.name,')
}
g.indent--
g.write('}, sizeof($ctx_struct)))')
g.empty_line = false
}
fn (mut g Gen) gen_anon_fn_decl(mut node ast.AnonFn) {
if node.has_gen {
return
}
node.has_gen = true
mut builder := strings.new_builder(256)
if node.inherited_vars.len > 0 {
ctx_struct := closure_ctx(node.decl)
builder.writeln('$ctx_struct {')
for var in node.inherited_vars {
styp := g.typ(var.typ)
builder.writeln('\t$styp $var.name;')
}
builder.writeln('};\n')
}
pos := g.out.len
was_anon_fn := g.anon_fn
g.anon_fn = true
g.fn_decl(node.decl)
g.anon_fn = was_anon_fn
builder.write_string(g.out.cut_to(pos))
g.anon_fn_definitions << builder.str()
}
fn (g &Gen) defer_flag_var(stmt &ast.DeferStmt) string {
return '${g.last_fn_c_name}_defer_$stmt.idx_in_fn'
}
fn (mut g Gen) write_defer_stmts_when_needed() {
// unlock all mutexes, in case we are in a lock statement. defers are not allowed in lock statements
g.unlock_locks()
if g.defer_stmts.len > 0 {
g.write_defer_stmts()
}
if g.defer_profile_code.len > 0 {
g.writeln('')
g.writeln('\t// defer_profile_code')
g.writeln(g.defer_profile_code)
g.writeln('')
}
}
fn (mut g Gen) fn_decl_params(params []ast.Param, scope &ast.Scope, is_variadic bool) ([]string, []string, []bool) {
mut fargs := []string{}
mut fargtypes := []string{}
mut heap_promoted := []bool{}
if params.len == 0 {
// in C, `()` is untyped, unlike `(void)`
g.write('void')
}
for i, arg in params {
mut caname := if arg.name == '_' { g.new_tmp_declaration_name() } else { c_name(arg.name) }
typ := g.unwrap_generic(arg.typ)
arg_type_sym := g.table.sym(typ)
mut arg_type_name := g.typ(typ) // util.no_dots(arg_type_sym.name)
if arg_type_sym.kind == .function {
info := arg_type_sym.info as ast.FnType
func := info.func
g.write('${g.typ(func.return_type)} (*$caname)(')
g.definitions.write_string('${g.typ(func.return_type)} (*$caname)(')
g.fn_decl_params(func.params, voidptr(0), func.is_variadic)
g.write(')')
g.definitions.write_string(')')
fargs << caname
fargtypes << arg_type_name
} else {
mut heap_prom := false
if scope != voidptr(0) {
if arg.name != '_' {
if v := scope.find_var(arg.name) {
if !v.is_stack_obj && v.is_auto_heap {
heap_prom = true
}
}
}
}
var_name_prefix := if heap_prom { '_v_toheap_' } else { '' }
const_prefix := if arg.typ.is_any_kind_of_pointer() && !arg.is_mut
&& arg.name.starts_with('const_') {
'const '
} else {
''
}
s := '$const_prefix$arg_type_name $var_name_prefix$caname'
g.write(s)
g.definitions.write_string(s)
fargs << caname
fargtypes << arg_type_name
heap_promoted << heap_prom
}
if i < params.len - 1 {
g.write(', ')
g.definitions.write_string(', ')
}
}
if g.pref.translated && is_variadic {
g.write(', ...')
g.definitions.write_string(', ...')
}
return fargs, fargtypes, heap_promoted
}
fn (mut g Gen) get_anon_fn_type_name(mut node ast.AnonFn, var_name string) string {
mut builder := strings.new_builder(64)
return_styp := g.typ(node.decl.return_type)
builder.write_string('$return_styp (*$var_name) (')
if node.decl.params.len == 0 {
builder.write_string('void)')
} else {
for i, param in node.decl.params {
param_styp := g.typ(param.typ)
builder.write_string('$param_styp $param.name')
if i != node.decl.params.len - 1 {
builder.write_string(', ')
}
}
builder.write_string(')')
}
return builder.str()
}
fn (mut g Gen) call_expr(node ast.CallExpr) {
// g.write('/*call expr*/')
// NOTE: everything could be done this way
// see my comment in parser near anon_fn
if node.left is ast.AnonFn {
g.expr(node.left)
} else if node.left is ast.IndexExpr && node.name == '' {
g.is_fn_index_call = true
g.expr(node.left)
g.is_fn_index_call = false
} else if node.left is ast.CallExpr && node.name == '' {
g.expr(node.left)
}
if node.should_be_skipped {
return
}
g.inside_call = true
defer {
g.inside_call = false
}
gen_keep_alive := node.is_keep_alive && node.return_type != ast.void_type
&& g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt]
gen_or := node.or_block.kind != .absent // && !g.is_autofree
is_gen_or_and_assign_rhs := gen_or && !g.discard_or_result
mut cur_line := if is_gen_or_and_assign_rhs || gen_keep_alive { // && !g.is_autofree {
// `x := foo() or { ...}`
// cut everything that has been generated to prepend optional variable creation
line := g.go_before_stmt(0)
g.out.write_string(util.tabs(g.indent))
// g.write('/*is_gen_or_and_assign_rhs*/')
line
} else {
''
}
tmp_opt := if gen_or || gen_keep_alive { g.new_tmp_var() } else { '' }
if gen_or || gen_keep_alive {
mut ret_typ := node.return_type
styp := g.typ(ret_typ)
if gen_or && !is_gen_or_and_assign_rhs {
cur_line = g.go_before_stmt(0)
}
g.write('$styp $tmp_opt = ')
}
if node.is_method && !node.is_field {
if node.name == 'writeln' && g.pref.experimental && node.args.len > 0
&& node.args[0].expr is ast.StringInterLiteral
&& g.table.sym(node.receiver_type).name == 'strings.Builder' {
g.string_inter_literal_sb_optimized(node)
} else {
g.method_call(node)
}
} else {
g.fn_call(node)
}
if gen_or { // && !g.autofree {
// if !g.is_autofree {
g.or_block(tmp_opt, node.or_block, node.return_type)
//}
unwrapped_typ := node.return_type.clear_flag(.optional).clear_flag(.result)
unwrapped_styp := g.typ(unwrapped_typ)
if unwrapped_typ == ast.void_type {
g.write('\n $cur_line')
} else {
if !g.inside_const_optional {
g.write('\n $cur_line (*($unwrapped_styp*)${tmp_opt}.data)')
} else {
g.write('\n $cur_line $tmp_opt')
}
}
} else if gen_keep_alive {
if node.return_type == ast.void_type {
g.write('\n $cur_line')
} else {
g.write('\n $cur_line $tmp_opt')
}
}
if node.is_noreturn {
if g.inside_ternary == 0 {
g.writeln(';')
g.write('VUNREACHABLE()')
} else {
$if msvc {
// MSVC has no support for the statement expressions used below
} $else {
g.write(', ({VUNREACHABLE();})')
}
}
}
}
fn (mut g Gen) conversion_function_call(prefix string, postfix string, node ast.CallExpr) {
g.write('${prefix}( (')
g.expr(node.left)
dot := if node.left_type.is_ptr() { '->' } else { '.' }
g.write(')${dot}_typ )$postfix')
}
fn (mut g Gen) method_call(node ast.CallExpr) {
// TODO: there are still due to unchecked exprs (opt/some fn arg)
if node.left_type == 0 {
g.checker_bug('CallExpr.left_type is 0 in method_call', node.pos)
}
if node.receiver_type == 0 {
g.checker_bug('CallExpr.receiver_type is 0 in method_call', node.pos)
}
mut unwrapped_rec_type := node.receiver_type
if g.cur_fn != 0 && g.cur_fn.generic_names.len > 0 { // in generic fn
unwrapped_rec_type = g.unwrap_generic(node.receiver_type)
} else { // in non-generic fn
sym := g.table.sym(node.receiver_type)
match sym.info {
ast.Struct, ast.Interface, ast.SumType {
generic_names := sym.info.generic_types.map(g.table.sym(it).name)
// see comment at top of vlib/v/gen/c/utils.v
mut muttable := unsafe { &ast.Table(g.table) }
if utyp := muttable.resolve_generic_to_concrete(node.receiver_type, generic_names,
sym.info.concrete_types)
{
unwrapped_rec_type = utyp
}
}
else {}
}
}
mut typ_sym := g.table.sym(unwrapped_rec_type)
// alias type that undefined this method (not include `str`) need to use parent type
if typ_sym.kind == .alias && node.name != 'str' && !typ_sym.has_method(node.name) {
unwrapped_rec_type = (typ_sym.info as ast.Alias).parent_type
typ_sym = g.table.sym(unwrapped_rec_type)
}
rec_cc_type := g.cc_type(unwrapped_rec_type, false)
mut receiver_type_name := util.no_dots(rec_cc_type)
if typ_sym.kind == .interface_ && (typ_sym.info as ast.Interface).defines_method(node.name) {
// Speaker_name_table[s._interface_idx].speak(s._object)
$if debug_interface_method_call ? {
eprintln('>>> interface typ_sym.name: $typ_sym.name | receiver_type_name: $receiver_type_name | pos: $node.pos')
}
left_is_shared := node.left_type.has_flag(.shared_f)
left_cc_type := g.cc_type(node.left_type, false)
left_type_name := util.no_dots(left_cc_type)
g.write('${c_name(left_type_name)}_name_table[')
if node.left.is_auto_deref_var() && node.left_type.nr_muls() > 1 {
g.write('(')
g.write('*'.repeat(node.left_type.nr_muls() - 1))
g.expr(node.left)
g.write(')')
} else {
g.expr(node.left)
}
dot := if left_is_shared {
'->val.'
} else if node.left_type.is_ptr() {
'->'
} else {
'.'
}
mname := c_name(node.name)
g.write('${dot}_typ]._method_${mname}(')
if node.left.is_auto_deref_var() && node.left_type.nr_muls() > 1 {
g.write('(')
g.write('*'.repeat(node.left_type.nr_muls() - 1))
g.expr(node.left)
g.write(')')
} else {
g.expr(node.left)
}
g.write('${dot}_object')
if node.args.len > 0 {
g.write(', ')
g.call_args(node)
}
g.write(')')
return
}
left_sym := g.table.sym(node.left_type)
final_left_sym := g.table.final_sym(node.left_type)
if left_sym.kind == .array {
match node.name {
'filter' {
g.gen_array_filter(node)
return
}
'sort' {
g.gen_array_sort(node)
return
}
'insert' {
g.gen_array_insert(node)
return
}
'map' {
g.gen_array_map(node)
return
}
'prepend' {
g.gen_array_prepend(node)
return
}
'contains' {
g.gen_array_contains(node.left_type, node.left, node.args[0].expr)
return
}
'index' {
g.gen_array_index(node)
return
}
'wait' {
g.gen_array_wait(node)
return
}
'any' {
g.gen_array_any(node)
return
}
'all' {
g.gen_array_all(node)
return
}
else {}
}
}
if left_sym.kind == .map && node.name == 'delete' {
left_info := left_sym.info as ast.Map
elem_type_str := g.typ(left_info.key_type)
g.write('map_delete(')
if node.left_type.is_ptr() {
g.expr(node.left)
} else {
g.write('&')
g.expr(node.left)
}
g.write(', &($elem_type_str[]){')
g.expr(node.args[0].expr)
g.write('})')
return
} else if left_sym.kind == .array && node.name == 'delete' {
g.write('array_delete(')
if node.left_type.is_ptr() {
g.expr(node.left)
} else {
g.write('&')
g.expr(node.left)
}
g.write(', ')
g.expr(node.args[0].expr)
g.write(')')
return
}
if left_sym.kind in [.sum_type, .interface_] {
if node.name == 'type_name' {
if left_sym.kind == .sum_type {
g.conversion_function_call('charptr_vstring_literal( /* $left_sym.name */ v_typeof_sumtype_$typ_sym.cname',
')', node)
return
}
if left_sym.kind == .interface_ {
g.conversion_function_call('charptr_vstring_literal( /* $left_sym.name */ v_typeof_interface_$typ_sym.cname',
')', node)
return
}
}
if node.name == 'type_idx' {
if left_sym.kind == .sum_type {
g.conversion_function_call('/* $left_sym.name */ v_typeof_sumtype_idx_$typ_sym.cname',
'', node)
return
}
if left_sym.kind == .interface_ {
g.conversion_function_call('/* $left_sym.name */ v_typeof_interface_idx_$typ_sym.cname',
'', node)
return
}
}
}
if node.name == 'str' {
mut rec_type := node.receiver_type
if rec_type.has_flag(.shared_f) {
rec_type = rec_type.clear_flag(.shared_f).set_nr_muls(0)
}
if node.left is ast.ComptimeSelector {
if node.left.field_expr is ast.SelectorExpr {
if node.left.field_expr.expr is ast.Ident {
key_str := '${node.left.field_expr.expr.name}.typ'
rec_type = g.comptime_var_type_map[key_str] or { rec_type }
g.gen_expr_to_string(node.left, rec_type)
return
}
}
} else if node.left is ast.ComptimeCall {
if node.left.method_name == 'method' {
sym := g.table.sym(g.unwrap_generic(node.left.left_type))
if m := sym.find_method(g.comptime_for_method) {
rec_type = m.return_type
g.gen_expr_to_string(node.left, rec_type)
return
}
}
} else if node.left is ast.Ident {
if node.left.obj is ast.Var {
if g.comptime_var_type_map.len > 0 {
rec_type = node.left.obj.typ
g.gen_expr_to_string(node.left, rec_type)
return
} else if node.left.obj.smartcasts.len > 0 {
rec_type = g.unwrap_generic(node.left.obj.smartcasts.last())
cast_sym := g.table.sym(rec_type)
if cast_sym.info is ast.Aggregate {
rec_type = cast_sym.info.types[g.aggregate_type_idx]
}
g.gen_expr_to_string(node.left, rec_type)
return
}
}
}
g.get_str_fn(rec_type)
} else if node.name == 'free' {
mut rec_type := node.receiver_type
if rec_type.has_flag(.shared_f) {
rec_type = rec_type.clear_flag(.shared_f).set_nr_muls(0)
}
g.get_free_method(rec_type)
}
mut has_cast := false
if left_sym.kind == .map && node.name in ['clone', 'move'] {
receiver_type_name = 'map'
}
if final_left_sym.kind == .array && !(left_sym.kind == .alias && left_sym.has_method(node.name))
&& node.name in ['repeat', 'sort_with_compare', 'free', 'push_many', 'trim', 'first', 'last', 'pop', 'clone', 'reverse', 'slice', 'pointers'] {
if !(left_sym.info is ast.Alias && typ_sym.has_method(node.name)) {
// `array_Xyz_clone` => `array_clone`
receiver_type_name = 'array'
}
if node.name in ['last', 'first', 'pop'] {
return_type_str := g.typ(node.return_type)
has_cast = true
g.write('(*($return_type_str*)')
}
}
mut name := util.no_dots('${receiver_type_name}_$node.name')
mut array_depth := -1
mut noscan := ''
if left_sym.kind == .array {
needs_depth := node.name in ['clone', 'repeat']
if needs_depth {
elem_type := (left_sym.info as ast.Array).elem_type
array_depth = g.get_array_depth(elem_type)
}
maybe_noscan := needs_depth
|| node.name in ['pop', 'push', 'push_many', 'reverse', 'grow_cap', 'grow_len']
if maybe_noscan {
info := left_sym.info as ast.Array
noscan = g.check_noscan(info.elem_type)
}
} else if left_sym.kind == .chan {
if node.name in ['close', 'try_pop', 'try_push'] {
name = 'sync__Channel_$node.name'
}
} else if final_left_sym.kind == .map {
if node.name == 'keys' {
name = 'map_keys'
} else if node.name == 'values' {
name = 'map_values'
}
}
if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__')
&& node.name != 'str' {
sym := g.table.sym(node.receiver_type)
key := sym.name + '.' + node.name
g.write('/* obf method call: $key */')
name = g.obf_table[key] or {
panic('cgen: obf name "$key" not found, this should never happen')
}
}
// Check if expression is: arr[a..b].clone(), arr[a..].clone()
// if so, then instead of calling array_clone(&array_slice(...))
// call array_clone_static(array_slice(...))
mut is_range_slice := false
if node.receiver_type.is_ptr() && !node.left_type.is_ptr() {
if node.left is ast.IndexExpr {
idx := node.left.index
if idx is ast.RangeExpr {
// expr is arr[range].clone()
// use array_clone_static instead of array_clone
name = util.no_dots('${receiver_type_name}_${node.name}_static')
is_range_slice = true
}
}
}
name = g.generic_fn_name(node.concrete_types, name, false)
// TODO2
// g.generate_tmp_autofree_arg_vars(node, name)
//
// if node.receiver_type != 0 {
// g.write('/*${g.typ(node.receiver_type)}*/')
// g.write('/*expr_type=${g.typ(node.left_type)} rec type=${g.typ(node.receiver_type)}*/')
// }
if !node.receiver_type.is_ptr() && node.left_type.is_ptr() && node.name == 'str' {
g.write('ptr_str(')
} else if node.receiver_type.is_ptr() && node.left_type.is_ptr() && node.name == 'str'
&& !left_sym.has_method('str') {
g.gen_expr_to_string(node.left, node.left_type)
return
} else {
if left_sym.kind == .array {
if array_depth >= 0 {
name = name + '_to_depth'
}
g.write('$name${noscan}(')
} else {
g.write('${name}(')
}
}
if node.receiver_type.is_ptr()
&& (!node.left_type.is_ptr() || node.left_type.has_flag(.variadic)
|| node.from_embed_types.len != 0
|| (node.left_type.has_flag(.shared_f) && node.name != 'str')) {
// The receiver is a reference, but the caller provided a value
// Add `&` automatically.
// TODO same logic in call_args()
if !is_range_slice {
if !node.left.is_lvalue() {
g.write('ADDR($rec_cc_type, ')
has_cast = true
} else {
g.write('&')
}
}
} else if !node.receiver_type.is_ptr() && node.left_type.is_ptr() && node.name != 'str'
&& node.from_embed_types.len == 0 {
if !node.left_type.has_flag(.shared_f) {
g.write('/*rec*/*')
}
} else if !is_range_slice && node.from_embed_types.len == 0 && node.name != 'str' {
diff := node.left_type.nr_muls() - node.receiver_type.nr_muls()
if diff < 0 {
// TODO
// g.write('&')
} else if diff > 0 {
g.write('/*diff=$diff*/')
g.write([]u8{len: diff, init: `*`}.bytestr())
}
}
// if node.left_type.idx() != node.receiver_type.idx() {
// println('${g.typ(node.left_type)} ${g.typ(node.receiver_type)}')
// }
if g.is_autofree && node.free_receiver && !g.inside_lambda && !g.is_builtin_mod {
// The receiver expression needs to be freed, use the temp var.
fn_name := node.name.replace('.', '_')
arg_name := '_arg_expr_${fn_name}_0_$node.pos.pos'
g.write('/*af receiver arg*/' + arg_name)
} else {
if left_sym.kind == .array && node.left.is_auto_deref_var()
&& node.name in ['first', 'last', 'repeat'] {
g.write('*')
}
if node.left is ast.MapInit {
g.write('(map[]){')
g.expr(node.left)
g.write('}[0]')
} else {
g.expr(node.left)
}
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.left_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)
}
if node.left_type.has_flag(.shared_f) {
g.write('->val')
}
}
if has_cast {
g.write(')')
}
is_variadic := node.expected_arg_types.len > 0
&& node.expected_arg_types[node.expected_arg_types.len - 1].has_flag(.variadic)
if node.args.len > 0 || is_variadic {
g.write(', ')
}
// /////////
/*
if name.contains('subkeys') {
println('call_args $name $node.arg_types.len')
for t in node.arg_types {
sym := g.table.sym(t)
print('$sym.name ')
}
println('')
}
*/
// ///////
g.call_args(node)
if array_depth >= 0 {
g.write(', $array_depth')
}
g.write(')')
}
fn (mut g Gen) fn_call(node ast.CallExpr) {
// call struct field with fn type
// TODO: test node.left instead
// left & left_type will be `x` and `x type` in `x.fieldfn()`
// will be `0` for `foo()`
mut is_interface_call := false
mut is_selector_call := false
if node.left_type != 0 {
left_sym := g.table.sym(node.left_type)
if left_sym.kind == .interface_ {
is_interface_call = true
g.write('(*')
}
g.expr(node.left)
if node.left_type.is_ptr() {
g.write('->')
} else {
g.write('.')
}
for embed in node.from_embed_types {
embed_sym := g.table.sym(embed)
embed_name := embed_sym.embed_name()
g.write(embed_name)
if embed.is_ptr() {
g.write('->')
} else {
g.write('.')
}
}
is_selector_call = true
}
if g.inside_comptime_for_field {
mut node_ := unsafe { node }
for i, mut call_arg in node_.args {
if mut call_arg.expr is ast.Ident {
if mut call_arg.expr.obj is ast.Var {
node_.args[i].typ = call_arg.expr.obj.typ
}
}
}
}
mut name := node.name
is_print := name in ['print', 'println', 'eprint', 'eprintln', 'panic']
print_method := name
is_json_encode := name == 'json.encode'
is_json_encode_pretty := name == 'json.encode_pretty'
is_json_decode := name == 'json.decode'
is_json_fn := is_json_encode || is_json_encode_pretty || is_json_decode
mut json_type_str := ''
mut json_obj := ''
if is_json_fn {
g.is_json_fn = true
json_obj = g.new_tmp_var()
mut tmp2 := ''
cur_line := g.go_before_stmt(0)
if is_json_encode || is_json_encode_pretty {
g.gen_json_for_type(node.args[0].typ)
json_type_str = g.typ(node.args[0].typ)
// `json__encode` => `json__encode_User`
// encode_name := c_name(name) + '_' + util.no_dots(json_type_str)
encode_name := js_enc_name(json_type_str)
g.empty_line = true
g.writeln('// json.encode')
g.write('cJSON* $json_obj = ${encode_name}(')
if node.args[0].typ.is_ptr() {
g.write('*')
}
g.call_args(node)
g.writeln(');')
tmp2 = g.new_tmp_var()
if is_json_encode {
g.writeln('string $tmp2 = json__json_print($json_obj);')
} else {
g.writeln('string $tmp2 = json__json_print_pretty($json_obj);')
}
} else {
ast_type := node.args[0].expr as ast.TypeNode
// `json.decode(User, s)` => json.decode_User(s)
typ := c_name(g.typ(ast_type.typ))
fn_name := c_name(name) + '_' + typ
g.gen_json_for_type(ast_type.typ)
g.empty_line = true
g.writeln('// json.decode')
g.write('cJSON* $json_obj = json__json_parse(')
// Skip the first argument in json.decode which is a type
// its name was already used to generate the function call
g.is_js_call = true
g.call_args(node)
g.writeln(');')
tmp2 = g.new_tmp_var()
g.writeln('${option_name}_$typ $tmp2 = ${fn_name}($json_obj);')
}
if !g.is_autofree {
g.write('cJSON_Delete($json_obj); // del')
}
g.write('\n$cur_line')
name = ''
json_obj = tmp2
}
if node.language == .c {
// Skip "C."
name = util.no_dots(name[2..])
} else {
name = c_name(name)
}
if g.pref.translated || g.file.is_translated {
// For `[c: 'P_TryMove'] fn p_trymove( ... `
// every time `p_trymove` is called, `P_TryMove` must be generated instead.
if f := g.table.find_fn(node.name) {
// TODO PERF fn lookup for each fn call in translated mode
if f.attrs.contains('c') {
name = f.attrs[0].arg
}
}
}
// Obfuscate only functions in the main module for now
if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') {
key := node.name
g.write('/* obf call: $key */')
name = g.obf_table[key] or {
panic('cgen: obf name "$key" not found, this should never happen')
}
}
if !is_selector_call {
if func := g.table.find_fn(node.name) {
if func.generic_names.len > 0 {
if g.comptime_for_field_type != 0 && g.inside_comptime_for_field {
name = g.generic_fn_name([g.comptime_for_field_type], name, false)
} else {
name = g.generic_fn_name(node.concrete_types, name, false)
}
}
}
}
// TODO2
// cgen shouldn't modify ast nodes, this should be moved
// g.generate_tmp_autofree_arg_vars(node, name)
// Handle `print(x)`
mut print_auto_str := false
if is_print && (node.args[0].typ != ast.string_type || g.comptime_for_method.len > 0) {
mut typ := node.args[0].typ
if typ == 0 {
g.checker_bug('print arg.typ is 0', node.pos)
}
if typ != ast.string_type || g.comptime_for_method.len > 0 {
expr := node.args[0].expr
typ_sym := g.table.sym(typ)
if typ_sym.kind == .interface_ && (typ_sym.info as ast.Interface).defines_method('str') {
g.write('${c_name(print_method)}(')
rec_type_name := util.no_dots(g.cc_type(typ, false))
g.write('${c_name(rec_type_name)}_name_table[')
g.expr(expr)
dot := if typ.is_ptr() { '->' } else { '.' }
g.write('${dot}_typ]._method_str(')
g.expr(expr)
g.write('${dot}_object')
g.writeln('));')
return
}
if g.is_autofree && !typ.has_flag(.optional) {
// Create a temporary variable so that the value can be freed
tmp := g.new_tmp_var()
// tmps << tmp
g.write('string $tmp = ')
g.gen_expr_to_string(expr, typ)
g.writeln('; ${c_name(print_method)}($tmp); string_free(&$tmp);')
} else {
g.write('${c_name(print_method)}(')
if expr is ast.ComptimeSelector {
if expr.field_expr is ast.SelectorExpr {
if expr.field_expr.expr is ast.Ident {
key_str := '${expr.field_expr.expr.name}.typ'
typ = g.comptime_var_type_map[key_str] or { typ }
}
}
} else if expr is ast.ComptimeCall {
if expr.method_name == 'method' {
sym := g.table.sym(g.unwrap_generic(expr.left_type))
if m := sym.find_method(g.comptime_for_method) {
typ = m.return_type
}
}
} else if expr is ast.Ident {
if expr.obj is ast.Var {
typ = expr.obj.typ
if expr.obj.smartcasts.len > 0 {
typ = g.unwrap_generic(expr.obj.smartcasts.last())
cast_sym := g.table.sym(typ)
if cast_sym.info is ast.Aggregate {
typ = cast_sym.info.types[g.aggregate_type_idx]
}
}
}
}
g.gen_expr_to_string(expr, typ)
g.write(')')
}
print_auto_str = true
}
}
if !print_auto_str {
if g.pref.is_debug && node.name == 'panic' {
paline, pafile, pamod, pafn := g.panic_debug_info(node.pos)
g.write('panic_debug($paline, tos3("$pafile"), tos3("$pamod"), tos3("$pafn"), ')
g.call_args(node)
g.write(')')
} else {
// Simple function call
// if free_tmp_arg_vars {
// g.writeln(';')
// g.write(cur_line + ' /* <== af cur line*/')
// }
mut is_fn_var := false
if obj := node.scope.find(node.name) {
match obj {
ast.Var {
if obj.smartcasts.len > 0 {
for _ in obj.smartcasts {
g.write('(*')
}
for i, typ in obj.smartcasts {
cast_sym := g.table.sym(g.unwrap_generic(typ))
mut is_ptr := false
if i == 0 {
g.write(node.name)
if obj.orig_type.is_ptr() {
is_ptr = true
}
}
dot := if is_ptr { '->' } 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(')')
}
is_fn_var = true
}
}
else {}
}
}
if !is_fn_var {
g.write(g.get_ternary_name(name))
}
if is_interface_call {
g.write(')')
}
mut tmp_cnt_save := -1
g.write('(')
if is_json_fn {
g.write(json_obj)
} else {
if node.is_keep_alive
&& g.pref.gc_mode in [.boehm_full, .boehm_incr, .boehm_full_opt, .boehm_incr_opt] {
cur_line := g.go_before_stmt(0)
tmp_cnt_save = g.keep_alive_call_pregen(node)
g.write(cur_line)
for i in 0 .. node.args.len {
if i > 0 {
g.write(', ')
}
g.write('__tmp_arg_${tmp_cnt_save + i}')
}
} else {
g.call_args(node)
}
}
g.write(')')
if tmp_cnt_save >= 0 {
g.writeln(';')
g.keep_alive_call_postgen(node, tmp_cnt_save)
}
}
}
g.is_json_fn = false
}
fn (mut g Gen) autofree_call_pregen(node ast.CallExpr) {
// g.writeln('// autofree_call_pregen()')
// Create a temporary var before fn call for each argument in order to free it (only if it's a complex expression,
// like `foo(get_string())` or `foo(a + b)`
mut free_tmp_arg_vars := g.is_autofree && !g.is_builtin_mod && node.args.len > 0
&& !node.args[0].typ.has_flag(.optional) // TODO copy pasta checker.v
if !free_tmp_arg_vars {
return
}
if g.is_js_call {
return
}
if g.inside_const {
return
}
free_tmp_arg_vars = false // set the flag to true only if we have at least one arg to free
g.tmp_count_af++
mut scope := g.file.scope.innermost(node.pos.pos)
// prepend the receiver for now (TODO turn the receiver into a CallArg everywhere?)
mut args := [
ast.CallArg{
typ: node.receiver_type
expr: node.left
is_tmp_autofree: node.free_receiver
},
]
args << node.args
// for i, arg in node.args {
for i, arg in args {
if !arg.is_tmp_autofree {
continue
}
if arg.expr is ast.CallExpr {
// Any argument can be an expression that has to be freed. Generate a tmp expression
// for each of those recursively.
g.autofree_call_pregen(arg.expr)
}
free_tmp_arg_vars = true
// t := g.new_tmp_var() + '_arg_expr_${name}_$i'
fn_name := node.name.replace('.', '_') // can't use name...
// t := '_tt${g.tmp_count_af}_arg_expr_${fn_name}_$i'
t := '_arg_expr_${fn_name}_${i}_$node.pos.pos'
// g.called_fn_name = name
used := false // scope.known_var(t)
mut s := '$t = '
if used {
// This means this tmp var name was already used (the same function was called and
// `_arg_fnname_1` was already generated).
// We do not need to declare this variable again, so just generate `t = ...`
// instead of `string t = ...`, and we need to mark this variable as unused,
// so that it's freed after the call. (Used tmp arg vars are not freed to avoid double frees).
if mut x := scope.find(t) {
match mut x {
ast.Var { x.is_used = false }
else {}
}
}
s = '$t = '
} else {
scope.register(ast.Var{
name: t
typ: ast.string_type
is_autofree_tmp: true
pos: node.pos
})
s = 'string $t = '
}
// g.expr(arg.expr)
s += g.expr_string(arg.expr)
// g.writeln(';// new af pre')
s += ';// new af2 pre'
g.strs_to_free0 << s
// This tmp arg var will be freed with the rest of the vars at the end of the scope.
}
}
fn (mut g Gen) autofree_call_postgen(node_pos int) {
// if g.strs_to_free.len == 0 {
// return
// }
// g.writeln('\n/* strs_to_free3: $g.nr_vars_to_free */')
// if g.nr_vars_to_free <= 0 {
// return
// }
/*
for s in g.strs_to_free {
g.writeln('string_free(&$s);')
}
if !g.inside_or_block {
// we need to free the vars both inside the or block (in case of an error) and after it
// if we reset the array here, then the vars will not be freed after the block.
g.strs_to_free = []
}
*/
if g.inside_vweb_tmpl {
return
}
// g.doing_autofree_tmp = true
// g.write('/* postgen */')
mut scope := g.file.scope.innermost(node_pos)
for _, mut obj in scope.objects {
match mut obj {
ast.Var {
// if var.typ == 0 {
// // TODO why 0?
// continue
// }
is_optional := obj.typ.has_flag(.optional)
if is_optional {
// TODO: free optionals
continue
}
if !obj.is_autofree_tmp {
continue
}
if obj.is_used {
// this means this tmp expr var has already been freed
continue
}
obj.is_used = true // TODO bug? sets all vars is_used to true
g.autofree_variable(obj)
// g.nr_vars_to_free--
}
else {}
}
}
// g.write('/* postgen end */')
// g.doing_autofree_tmp = false
}
fn (mut g Gen) call_args(node ast.CallExpr) {
args := if g.is_js_call {
if node.args.len < 1 {
g.error('node should have at least 1 arg', node.pos)
}
g.is_js_call = false
node.args[1..]
} else {
node.args
}
mut expected_types := node.expected_arg_types
// unwrap generics fn/method arguments to concretes
if node.concrete_types.len > 0 && node.concrete_types.all(!it.has_flag(.generic)) {
if node.is_method {
if func := g.table.find_method(g.table.sym(node.left_type), node.name) {
if func.generic_names.len > 0 {
for i in 0 .. expected_types.len {
mut muttable := unsafe { &ast.Table(g.table) }
if utyp := muttable.resolve_generic_to_concrete(node.expected_arg_types[i],
func.generic_names, node.concrete_types)
{
expected_types[i] = utyp
}
}
}
}
} else {
if func := g.table.find_fn(node.name) {
if func.generic_names.len > 0 {
for i in 0 .. expected_types.len {
mut muttable := unsafe { &ast.Table(g.table) }
if utyp := muttable.resolve_generic_to_concrete(node.expected_arg_types[i],
func.generic_names, node.concrete_types)
{
expected_types[i] = utyp
}
}
}
}
}
}
// only v variadic, C variadic args will be appeneded like normal args
is_variadic := expected_types.len > 0 && expected_types.last().has_flag(.variadic)
&& node.language == .v
for i, arg in args {
if is_variadic && i == expected_types.len - 1 {
break
}
use_tmp_var_autofree := g.is_autofree && arg.typ == ast.string_type && arg.is_tmp_autofree
&& !g.inside_const && !g.is_builtin_mod
// g.write('/* af=$arg.is_tmp_autofree */')
// some c fn definitions dont have args (cfns.v) or are not updated in checker
// when these are fixed we wont need this check
if i < expected_types.len {
if use_tmp_var_autofree {
if arg.is_tmp_autofree { // && !g.is_js_call {
// We saved expressions in temp variables so that they can be freed later.
// `foo(str + str2) => x := str + str2; foo(x); x.free()`
// g.write('_arg_expr_${g.called_fn_name}_$i')
// Use these variables here.
fn_name := node.name.replace('.', '_')
// name := '_tt${g.tmp_count_af}_arg_expr_${fn_name}_$i'
name := '_arg_expr_${fn_name}_${i + 1}_$node.pos.pos'
g.write('/*af arg*/' + name)
}
} else {
g.ref_or_deref_arg(arg, expected_types[i], node.language)
}
} else {
if use_tmp_var_autofree {
// TODO copypasta, move to an inline fn
fn_name := node.name.replace('.', '_')
// name := '_tt${g.tmp_count_af}_arg_expr_${fn_name}_$i'
name := '_arg_expr_${fn_name}_${i + 1}_$node.pos.pos'
g.write('/*af arg2*/' + name)
} else {
g.expr(arg.expr)
}
}
if i < args.len - 1 || is_variadic {
g.write(', ')
}
}
arg_nr := expected_types.len - 1
if is_variadic {
varg_type := expected_types.last()
variadic_count := args.len - arg_nr
arr_sym := g.table.sym(varg_type)
mut arr_info := arr_sym.info as ast.Array
if varg_type.has_flag(.generic) {
if node.is_method {
left_sym := g.table.sym(node.left_type)
if fn_def := left_sym.find_method_with_generic_parent(node.name) {
mut muttable := unsafe { &ast.Table(g.table) }
if utyp := muttable.resolve_generic_to_concrete(arr_info.elem_type,
fn_def.generic_names, node.concrete_types)
{
arr_info.elem_type = utyp
}
} else {
g.error('unable to find method $node.name', node.pos)
}
} else {
if fn_def := g.table.find_fn(node.name) {
mut muttable := unsafe { &ast.Table(g.table) }
if utyp := muttable.resolve_generic_to_concrete(arr_info.elem_type,
fn_def.generic_names, node.concrete_types)
{
arr_info.elem_type = utyp
}
} else {
g.error('unable to find function $node.name', node.pos)
}
}
}
elem_type := g.typ(arr_info.elem_type)
if (g.pref.translated || g.file.is_translated) && args.len == 1 {
// Handle `foo(c'str')` for `fn foo(args ...&u8)`
// TODOC2V handle this in a better place
// println(g.table.type_to_str(args[0].typ))
g.expr(args[0].expr)
} else if args.len > 0 && args[args.len - 1].expr is ast.ArrayDecompose {
g.expr(args[args.len - 1].expr)
} else {
if variadic_count > 0 {
noscan := g.check_noscan(arr_info.elem_type)
g.write('new_array_from_c_array${noscan}($variadic_count, $variadic_count, sizeof($elem_type), _MOV(($elem_type[$variadic_count]){')
for j in arg_nr .. args.len {
g.ref_or_deref_arg(args[j], arr_info.elem_type, node.language)
if j < args.len - 1 {
g.write(', ')
}
}
g.write('}))')
} else {
g.write('__new_array(0, 0, sizeof($elem_type))')
}
}
}
}
fn (mut g Gen) go_expr(node ast.GoExpr) {
line := g.go_before_stmt(0)
mut handle := ''
tmp := g.new_tmp_var()
mut expr := node.call_expr
mut name := expr.name // util.no_dots(expr.name)
// TODO: fn call is duplicated. merge with fn_call().
for i, concrete_type in expr.concrete_types {
if concrete_type != ast.void_type && concrete_type != 0 {
// Using _T_ to differentiate between get<string> and get_string
// `foo<int>()` => `foo_T_int()`
if i == 0 {
name += '_T'
}
name += '_' + g.typ(concrete_type)
}
}
if expr.is_method {
receiver_sym := g.table.sym(expr.receiver_type)
name = receiver_sym.name + '_' + name
} else if mut expr.left is ast.AnonFn {
g.gen_anon_fn_decl(mut expr.left)
name = expr.left.decl.name
} else if expr.is_fn_var {
name = g.table.sym(expr.fn_var_type).name
}
name = util.no_dots(name)
if g.pref.obfuscate && g.cur_mod.name == 'main' && name.starts_with('main__') {
mut key := expr.name
if expr.is_method {
sym := g.table.sym(expr.receiver_type)
key = sym.name + '.' + expr.name
}
g.write('/* obf go: $key */')
name = g.obf_table[key] or {
panic('cgen: obf name "$key" not found, this should never happen')
}
}
g.empty_line = true
g.writeln('// start go')
wrapper_struct_name := 'thread_arg_' + name
wrapper_fn_name := name + '_thread_wrapper'
arg_tmp_var := 'arg_' + tmp
g.writeln('$wrapper_struct_name *$arg_tmp_var = malloc(sizeof(thread_arg_$name));')
if expr.is_method {
g.write('$arg_tmp_var->arg0 = ')
// TODO is this needed?
/*
if false && !expr.return_type.is_ptr() {
g.write('&')
}
*/
g.expr(expr.left)
g.writeln(';')
}
for i, arg in expr.args {
g.write('$arg_tmp_var->arg${i + 1} = ')
g.expr(arg.expr)
g.writeln(';')
}
s_ret_typ := g.typ(node.call_expr.return_type)
if g.pref.os == .windows && node.call_expr.return_type != ast.void_type {
g.writeln('$arg_tmp_var->ret_ptr = malloc(sizeof($s_ret_typ));')
}
is_opt := node.call_expr.return_type.has_flag(.optional)
mut gohandle_name := ''
if node.call_expr.return_type == ast.void_type {
gohandle_name = if is_opt { '__v_thread_Option_void' } else { '__v_thread' }
} else {
opt := if is_opt { '${option_name}_' } else { '' }
gohandle_name = '__v_thread_$opt${g.table.sym(g.unwrap_generic(node.call_expr.return_type)).cname}'
}
if g.pref.os == .windows {
simple_handle := if node.is_expr && node.call_expr.return_type != ast.void_type {
'thread_handle_$tmp'
} else {
'thread_$tmp'
}
g.writeln('HANDLE $simple_handle = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)$wrapper_fn_name, $arg_tmp_var, 0, 0);')
g.writeln('if (!$simple_handle) panic_lasterr(tos3("`go ${name}()`: "));')
if node.is_expr && node.call_expr.return_type != ast.void_type {
g.writeln('$gohandle_name thread_$tmp = {')
g.writeln('\t.ret_ptr = $arg_tmp_var->ret_ptr,')
g.writeln('\t.handle = thread_handle_$tmp')
g.writeln('};')
}
if !node.is_expr {
g.writeln('CloseHandle(thread_$tmp);')
}
} else {
g.writeln('pthread_t thread_$tmp;')
mut sthread_attributes := 'NULL'
if g.pref.os != .vinix {
g.writeln('pthread_attr_t thread_${tmp}_attributes;')
g.writeln('pthread_attr_init(&thread_${tmp}_attributes);')
g.writeln('pthread_attr_setstacksize(&thread_${tmp}_attributes, $g.pref.thread_stack_size);')
sthread_attributes = '&thread_${tmp}_attributes'
}
g.writeln('int ${tmp}_thr_res = pthread_create(&thread_$tmp, $sthread_attributes, (void*)$wrapper_fn_name, $arg_tmp_var);')
g.writeln('if (${tmp}_thr_res) panic_error_number(tos3("`go ${name}()`: "), ${tmp}_thr_res);')
if !node.is_expr {
g.writeln('pthread_detach(thread_$tmp);')
}
}
g.writeln('// end go')
if node.is_expr {
handle = 'thread_$tmp'
// create wait handler for this return type if none exists
waiter_fn_name := gohandle_name + '_wait'
mut should_register := false
lock g.waiter_fns {
if waiter_fn_name !in g.waiter_fns {
g.waiter_fns << waiter_fn_name
should_register = true
}
}
if should_register {
g.gowrappers.writeln('\n$s_ret_typ ${waiter_fn_name}($gohandle_name thread) {')
mut c_ret_ptr_ptr := 'NULL'
if node.call_expr.return_type != ast.void_type {
g.gowrappers.writeln('\t$s_ret_typ* ret_ptr;')
c_ret_ptr_ptr = '&ret_ptr'
}
if g.pref.os == .windows {
if node.call_expr.return_type == ast.void_type {
g.gowrappers.writeln('\tu32 stat = WaitForSingleObject(thread, INFINITE);')
} else {
g.gowrappers.writeln('\tu32 stat = WaitForSingleObject(thread.handle, INFINITE);')
g.gowrappers.writeln('\tret_ptr = thread.ret_ptr;')
}
} else {
g.gowrappers.writeln('\tint stat = pthread_join(thread, (void **)$c_ret_ptr_ptr);')
}
g.gowrappers.writeln('\tif (stat != 0) { _v_panic(_SLIT("unable to join thread")); }')
if g.pref.os == .windows {
if node.call_expr.return_type == ast.void_type {
g.gowrappers.writeln('\tCloseHandle(thread);')
} else {
g.gowrappers.writeln('\tCloseHandle(thread.handle);')
}
}
if node.call_expr.return_type != ast.void_type {
g.gowrappers.writeln('\t$s_ret_typ ret = *ret_ptr;')
g.gowrappers.writeln('\tfree(ret_ptr);')
g.gowrappers.writeln('\treturn ret;')
}
g.gowrappers.writeln('}')
}
}
// Register the wrapper type and function
mut should_register := false
lock g.threaded_fns {
if name !in g.threaded_fns {
g.threaded_fns << name
should_register = true
}
}
if should_register {
g.type_definitions.writeln('\ntypedef struct $wrapper_struct_name {')
if expr.is_method {
styp := g.typ(expr.receiver_type)
g.type_definitions.writeln('\t$styp arg0;')
}
need_return_ptr := g.pref.os == .windows && node.call_expr.return_type != ast.void_type
if expr.args.len == 0 && !need_return_ptr {
g.type_definitions.writeln('EMPTY_STRUCT_DECLARATION;')
} else {
for i, arg in expr.args {
styp := g.typ(arg.typ)
g.type_definitions.writeln('\t$styp arg${i + 1};')
}
}
if need_return_ptr {
g.type_definitions.writeln('\tvoid* ret_ptr;')
}
g.type_definitions.writeln('} $wrapper_struct_name;')
thread_ret_type := if g.pref.os == .windows { 'u32' } else { 'void*' }
g.type_definitions.writeln('$thread_ret_type ${wrapper_fn_name}($wrapper_struct_name *arg);')
g.gowrappers.writeln('$thread_ret_type ${wrapper_fn_name}($wrapper_struct_name *arg) {')
if node.call_expr.return_type != ast.void_type {
if g.pref.os == .windows {
g.gowrappers.write_string('\t*(($s_ret_typ*)(arg->ret_ptr)) = ')
} else {
g.gowrappers.writeln('\t$s_ret_typ* ret_ptr = malloc(sizeof($s_ret_typ));')
g.gowrappers.write_string('\t*ret_ptr = ')
}
} else {
g.gowrappers.write_string('\t')
}
if expr.is_method {
unwrapped_rec_type := g.unwrap_generic(expr.receiver_type)
typ_sym := g.table.sym(unwrapped_rec_type)
if typ_sym.kind == .interface_
&& (typ_sym.info as ast.Interface).defines_method(expr.name) {
rec_cc_type := g.cc_type(unwrapped_rec_type, false)
receiver_type_name := util.no_dots(rec_cc_type)
g.gowrappers.write_string('${c_name(receiver_type_name)}_name_table[')
g.gowrappers.write_string('arg->arg0')
dot := if expr.left_type.is_ptr() { '->' } else { '.' }
mname := c_name(expr.name)
g.gowrappers.write_string('${dot}_typ]._method_${mname}(')
g.gowrappers.write_string('arg->arg0')
g.gowrappers.write_string('${dot}_object')
} else {
g.gowrappers.write_string('${name}(')
g.gowrappers.write_string('arg->arg0')
}
if expr.args.len > 0 {
g.gowrappers.write_string(', ')
}
} else {
g.gowrappers.write_string('${name}(')
}
if expr.args.len > 0 {
mut has_cast := false
for i in 0 .. expr.args.len {
if g.table.sym(expr.expected_arg_types[i]).kind == .interface_
&& g.table.sym(expr.args[i].typ).kind != .interface_ {
has_cast = true
break
}
}
if has_cast {
pos := g.out.len
g.call_args(expr)
mut call_args_str := g.out.after(pos)
g.out.go_back(call_args_str.len)
mut rep_group := []string{cap: 2 * expr.args.len}
for i in 0 .. expr.args.len {
rep_group << g.expr_string(expr.args[i].expr)
rep_group << 'arg->arg${i + 1}'
}
call_args_str = call_args_str.replace_each(rep_group)
g.gowrappers.write_string(call_args_str)
} else {
for i in 0 .. expr.args.len {
expected_nr_muls := expr.expected_arg_types[i].nr_muls()
arg_nr_muls := expr.args[i].typ.nr_muls()
if arg_nr_muls > expected_nr_muls {
g.gowrappers.write_string('*'.repeat(arg_nr_muls - expected_nr_muls))
} else if arg_nr_muls < expected_nr_muls {
g.gowrappers.write_string('&'.repeat(expected_nr_muls - arg_nr_muls))
}
g.gowrappers.write_string('arg->arg${i + 1}')
if i != expr.args.len - 1 {
g.gowrappers.write_string(', ')
}
}
}
}
g.gowrappers.writeln(');')
g.gowrappers.writeln('\tfree(arg);')
if g.pref.os != .windows && node.call_expr.return_type != ast.void_type {
g.gowrappers.writeln('\treturn ret_ptr;')
} else {
g.gowrappers.writeln('\treturn 0;')
}
g.gowrappers.writeln('}')
}
if node.is_expr {
g.empty_line = false
g.write(line)
g.write(handle)
}
}
// similar to `autofree_call_pregen()` but only to to handle [keep_args_alive] for C functions
fn (mut g Gen) keep_alive_call_pregen(node ast.CallExpr) int {
g.empty_line = true
g.writeln('// keep_alive_call_pregen()')
// reserve the next tmp_vars for arguments
tmp_cnt_save := g.tmp_count + 1
g.tmp_count += node.args.len
for i, arg in node.args {
// save all arguments in temp vars (not only pointers) to make sure the
// evaluation order is preserved
expected_type := node.expected_arg_types[i]
typ := g.table.sym(expected_type).cname
g.write('$typ __tmp_arg_${tmp_cnt_save + i} = ')
// g.expr(arg.expr)
g.ref_or_deref_arg(arg, expected_type, node.language)
g.writeln(';')
}
g.empty_line = false
return tmp_cnt_save
}
fn (mut g Gen) keep_alive_call_postgen(node ast.CallExpr, tmp_cnt_save int) {
g.writeln('// keep_alive_call_postgen()')
for i, expected_type in node.expected_arg_types {
if expected_type.is_ptr() || expected_type.is_pointer() {
g.writeln('GC_reachable_here(__tmp_arg_${tmp_cnt_save + i});')
}
}
}
[inline]
fn (mut g Gen) ref_or_deref_arg(arg ast.CallArg, expected_type ast.Type, lang ast.Language) {
arg_is_ptr := expected_type.is_ptr() || expected_type.idx() in ast.pointer_type_idxs
expr_is_ptr := arg.typ.is_ptr() || arg.typ.idx() in ast.pointer_type_idxs
if expected_type == 0 {
g.checker_bug('ref_or_deref_arg expected_type is 0', arg.pos)
}
exp_sym := g.table.sym(expected_type)
arg_typ := g.unwrap_generic(arg.typ)
mut needs_closing := false
if arg.is_mut && !arg_is_ptr {
g.write('&/*mut*/')
} else if arg_is_ptr && !expr_is_ptr {
if arg.is_mut {
arg_sym := g.table.sym(arg_typ)
if exp_sym.kind == .array {
if (arg.expr is ast.Ident && (arg.expr as ast.Ident).kind == .variable)
|| arg.expr is ast.SelectorExpr {
g.write('&/*arr*/')
g.expr(arg.expr)
} else {
// Special case for mutable arrays. We can't `&` function
// results, have to use `(array[]){ expr }[0]` hack.
g.write('&/*111*/(array[]){')
g.expr(arg.expr)
g.write('}[0]')
}
return
} else if arg_sym.kind == .sum_type && exp_sym.kind == .sum_type
&& (arg.expr is ast.Ident || arg.expr is ast.SelectorExpr) {
g.write('&/*sum*/')
g.expr(arg.expr)
return
} else if arg_sym.kind == .interface_ && exp_sym.kind == .interface_
&& (arg.expr is ast.Ident || arg.expr is ast.SelectorExpr) {
g.write('&/*iface*/')
g.expr(arg.expr)
return
}
}
if !g.is_json_fn {
if arg_typ == 0 {
g.checker_bug('ref_or_deref_arg arg.typ is 0', arg.pos)
}
arg_typ_sym := g.table.sym(arg_typ)
expected_deref_type := if expected_type.is_ptr() {
expected_type.deref()
} else {
expected_type
}
deref_sym := g.table.sym(expected_deref_type)
if arg_typ_sym.kind != .function && deref_sym.kind !in [.sum_type, .interface_]
&& lang != .c {
if arg.expr.is_lvalue() {
g.write('(voidptr)&/*qq*/')
} else {
mut atype := expected_deref_type
if atype.has_flag(.generic) {
atype = g.unwrap_generic(atype)
}
if atype.has_flag(.generic) {
g.write('(voidptr)&/*qq*/')
} else {
needs_closing = true
g.write('ADDR(${g.typ(atype)}/*qq*/, ')
}
}
}
}
} else if arg_typ.has_flag(.shared_f) && !expected_type.has_flag(.shared_f) {
if expected_type.is_ptr() {
g.write('&')
}
g.expr(arg.expr)
g.write('->val')
return
} else if arg.expr is ast.ArrayInit {
if arg.expr.is_fixed {
if !arg.expr.has_it {
g.write('(${g.typ(arg.expr.typ)})')
}
}
}
g.expr_with_cast(arg.expr, arg_typ, expected_type)
if needs_closing {
g.write(')')
}
}
fn (mut g Gen) is_gui_app() bool {
if g.pref.os == .windows {
if g.force_main_console {
return false
}
for cf in g.table.cflags {
if cf.value.to_lower() == 'gdi32' {
return true
}
}
}
return false
}
fn (g &Gen) fileis(s string) bool {
return g.file.path.contains(s)
}
fn (mut g Gen) write_fn_attrs(attrs []ast.Attr) string {
mut fn_attrs := ''
for attr in attrs {
match attr.name {
'inline' {
g.write('inline ')
}
'noinline' {
// since these are supported by GCC, clang and MSVC, we can consider them officially supported.
g.write('__NOINLINE ')
}
'weak' {
if attrs.any(it.name == 'export') {
// only the exported wrapper should be weak; otherwise x86_64-w64-mingw32-gcc complains
continue
}
// a `[weak]` tag tells the C compiler, that the next declaration will be weak, i.e. when linking,
// if there is another declaration of a symbol with the same name (a 'strong' one), it should be
// used instead, *without linker errors about duplicate symbols*.
g.write('VWEAK ')
}
'noreturn' {
// a `[noreturn]` tag tells the compiler, that a function
// *DOES NOT RETURN* to its callsites.
// See: https://en.cppreference.com/w/c/language/_Noreturn
// Such functions should have no return type. They can be used
// in places where `panic(err)` or `exit(0)` can be used.
// panic/1 and exit/0 themselves will also be marked as
// `[noreturn]` soon.
// These functions should have busy `for{}` loops injected
// at their end, when they do not end by calling other fns
// marked by `[noreturn]`.
g.write('VNORETURN ')
}
'irq_handler' {
g.write('__IRQHANDLER ')
}
'_cold' {
// GCC/clang attributes
// prefixed by _ to indicate they're for advanced users only and not really supported by V.
// source for descriptions: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes
// The cold attribute on functions is used to inform the compiler that the function is unlikely
// to be executed. The function is optimized for size rather than speed and on many targets it
// is placed into a special subsection of the text section so all cold functions appear close
// together, improving code locality of non-cold parts of program.
g.write('__attribute__((cold)) ')
}
'_constructor' {
// The constructor attribute causes the function to be called automatically before execution
// enters main ().
g.write('__attribute__((constructor)) ')
}
'_destructor' {
// The destructor attribute causes the function to be called automatically after main ()
// completes or exit () is called.
g.write('__attribute__((destructor)) ')
}
'_flatten' {
// Generally, inlining into a function is limited. For a function marked with this attribute,
// every call inside this function is inlined, if possible.
g.write('__attribute__((flatten)) ')
}
'_hot' {
// The hot attribute on a function is used to inform the compiler that the function is a hot
// spot of the compiled program.
g.write('__attribute__((hot)) ')
}
'_malloc' {
// This tells the compiler that a function is malloc-like, i.e., that the pointer P returned by
// the function cannot alias any other pointer valid when the function returns, and moreover no
// pointers to valid objects occur in any storage addressed by P.
g.write('__attribute__((malloc)) ')
}
'_pure' {
// Calls to functions whose return value is not affected by changes to the observable state
// of the program and that have no observable effects on such state other than to return a
// value may lend themselves to optimizations such as common subexpression elimination.
// Declaring such functions with the const attribute allows GCC to avoid emitting some calls in
// repeated invocations of the function with the same argument values.
g.write('__attribute__((const)) ')
}
'_naked' {
g.write('__attribute__((naked)) ')
}
'windows_stdcall' {
// windows attributes (msvc/mingw)
// prefixed by windows to indicate they're for advanced users only and not really supported by V.
fn_attrs += call_convention_attribute('stdcall', g.is_cc_msvc)
}
'_fastcall' {
fn_attrs += call_convention_attribute('fastcall', g.is_cc_msvc)
}
'callconv' {
fn_attrs += call_convention_attribute(attr.arg, g.is_cc_msvc)
}
'console' {
g.force_main_console = true
}
else {
// nothing but keep V happy
}
}
}
return fn_attrs
}
fn call_convention_attribute(cconvention string, is_cc_msvc bool) string {
return if is_cc_msvc { '__$cconvention ' } else { '__attribute__(($cconvention)) ' }
}