From c49b9da04e8d21735f5fc060b1c7396603613087 Mon Sep 17 00:00:00 2001 From: "Eliyaan (Nopana)" <103932369+Eliyaan@users.noreply.github.com> Date: Fri, 1 Aug 2025 05:38:49 +0200 Subject: [PATCH] native: add support for (elf) globals (#25016) --- vlib/v/gen/native/amd64.v | 54 +++++++++++++----- vlib/v/gen/native/blacklist.v | 7 +++ vlib/v/gen/native/elf.v | 82 +++++++++++++++++++++++++-- vlib/v/gen/native/expr.v | 2 +- vlib/v/gen/native/gen.v | 19 +++++-- vlib/v/gen/native/stmt.c.v | 6 +- vlib/v/gen/native/tests/linux.vv | 15 ++++- vlib/v/gen/native/tests/native_test.v | 2 +- 8 files changed, 159 insertions(+), 28 deletions(-) diff --git a/vlib/v/gen/native/amd64.v b/vlib/v/gen/native/amd64.v index 9f18d7ada2..2d1f41de40 100644 --- a/vlib/v/gen/native/amd64.v +++ b/vlib/v/gen/native/amd64.v @@ -334,7 +334,7 @@ fn (mut c Amd64) cmp_var_reg(var Var, reg Register, config VarConfig) { c.g.println('cmp var `${var.name}`, ${reg}') } GlobalVar { - // TODO + c.g.n_error('${@LOCATION} unsupported var type ${var}') } ExternVar { c.g.n_error('${@LOCATION} unsupported var type ${var}') @@ -383,7 +383,7 @@ fn (mut c Amd64) cmp_var(var Var, val i32, config VarConfig) { c.g.println('cmp var `${var.name}` ${val}') } GlobalVar { - // TODO + c.g.n_error('${@LOCATION} unsupported var type ${var}') } ExternVar { c.g.n_error('${@LOCATION} unsupported var type ${var}') @@ -433,7 +433,7 @@ fn (mut c Amd64) dec_var(var Var, config VarConfig) { c.g.println('dec_var `${var.name}`') } GlobalVar { - // TODO + c.g.n_error('${@LOCATION} unsupported var type ${var}') } ExternVar { c.g.n_error('${@LOCATION} unsupported var type ${var}') @@ -507,8 +507,7 @@ fn (mut c Amd64) inc_var(var Var, config VarConfig) { c.g.println('inc_var ${size_str} `${var.name}`') } GlobalVar { - c.g.n_error('${@LOCATION} Global variables incrementation is not supported yet') - // TODO + c.g.n_error('${@LOCATION} unsupported var type ${var}') } ExternVar { c.g.n_error('${@LOCATION} unsupported var type ${var}') @@ -794,8 +793,24 @@ fn (mut c Amd64) mov_reg_to_var(var Var, r Register, config VarConfig) { c.g.println('mov ${size_str} PTR [rbp-${int(offset).hex2()}],${reg} ; `${var.name}`') } GlobalVar { - // TODO - c.g.n_error('${@LOCATION} Unsupported GlobalVar') + size := match c.g.get_type_size(var.typ) { + 1 { Size._8 } + 2 { Size._16 } + 4 { Size._32 } + 8 { Size._64 } + else { c.g.n_error('${@LOCATION} unsupported size of global var') } + } + mut addr_reg := Amd64Register.rdx + if reg == .rdx || reg == .edx { + addr_reg = .rax + } + c.push(addr_reg) + c.g.global_vars[c.g.pos() + 2] = var.name // +2 for the mov64 instruction + c.mov64(addr_reg, i64(0)) + c.g.println('; will get patched by relocs') + c.mov_store(addr_reg, reg, size) + c.pop(addr_reg) + c.g.println('; mov global:`${var.name}` ${reg}') } ExternVar { c.g.n_error('${@LOCATION} unsupported var type ${var}') @@ -886,7 +901,7 @@ fn (mut c Amd64) mov_int_to_var(var Var, integer i32, config VarConfig) { } } GlobalVar { - // TODO + c.g.n_error('${@LOCATION} unsupported var type ${var}') } ExternVar { c.g.n_error('${@LOCATION} unsupported var type ${var}') @@ -1009,7 +1024,20 @@ fn (mut c Amd64) mov_var_to_reg(reg Register, var Var, config VarConfig) { c.g.println('${instruction} ${reg}, ${size_str} PTR [rbp-${int(offset).hex2()}] ; `${var.name}`') } GlobalVar { - c.g.n_error('${@LOCATION} Unsupported GlobalVar') + if c.g.get_type_size(var.typ) > 8 { + c.g.n_error('${@LOCATION} unsupported size of global var') + } + mut addr_reg := Amd64Register.rdx + if reg as Amd64Register == .rdx || reg as Amd64Register == .edx { + addr_reg = .rax + } + c.push(addr_reg) + c.g.global_vars[c.g.pos() + 2] = var.name // +2 for the mov64 instruction + c.mov64(addr_reg, i64(0)) + c.g.println('; will get patched by relocs') + c.mov_deref(reg, addr_reg, var.typ) + c.pop(addr_reg) + c.g.println('; mov ${reg} global:`${var.name}`') } ExternVar { c.g.n_error('${@LOCATION} unsupported var type ${var}') @@ -3859,7 +3887,7 @@ fn (mut c Amd64) init_struct(var Var, init ast.StructInit) { } } GlobalVar { - c.g.n_error('${@LOCATION} GlobalVar not implemented for ast.StructInit') + c.g.n_error('${@LOCATION} unsupported var type ${var}') } ExternVar { c.g.n_error('${@LOCATION} unsupported var type ${var}') @@ -3932,7 +3960,7 @@ fn (mut c Amd64) init_array(var Var, node ast.ArrayInit) { } } GlobalVar { - c.g.n_error('${@LOCATION} GlobalVar not implemented for ast.ArrayInit') + c.g.n_error('${@LOCATION} unsupported var type ${var}') } ExternVar { c.g.n_error('${@LOCATION} unsupported var type ${var}') @@ -4265,7 +4293,7 @@ fn (mut c Amd64) mov_ssereg_to_var(var Var, reg Amd64SSERegister, config VarConf c.g.println('${inst} [rbp-${int(offset).hex2()}], ${reg}') } GlobalVar { - // TODO + c.g.n_error('${@LOCATION} unsupported var type ${var}') } ExternVar { c.g.n_error('${@LOCATION} unsupported var type ${var}') @@ -4321,7 +4349,7 @@ fn (mut c Amd64) mov_var_to_ssereg(reg Amd64SSERegister, var Var, config VarConf c.g.println('${inst} ${reg}, [rbp-${int(offset).hex2()}]') } GlobalVar { - // TODO + c.g.n_error('${@LOCATION} unsupported var type ${var}') } ExternVar { c.g.n_error('${@LOCATION} unsupported var type ${var}') diff --git a/vlib/v/gen/native/blacklist.v b/vlib/v/gen/native/blacklist.v index e9fecae189..297bd8552d 100644 --- a/vlib/v/gen/native/blacklist.v +++ b/vlib/v/gen/native/blacklist.v @@ -55,6 +55,13 @@ const blacklist = { 'string.last_index': true 'string.last_index_u8': false 'string.contains_u8': false + 'malloc_noscan': true + 'vmemcpy': false + 'eprint': false + 'eprintln': false + '_write_buf_to_fd': false + '_writeln_to_fd': false + '_memory_panic': true } const windows_blacklist = { diff --git a/vlib/v/gen/native/elf.v b/vlib/v/gen/native/elf.v index d4279fc038..bb1a13c03e 100644 --- a/vlib/v/gen/native/elf.v +++ b/vlib/v/gen/native/elf.v @@ -4,6 +4,7 @@ module native import os +import ast const elf_class32 = 1 const elf_class64 = 2 @@ -300,6 +301,8 @@ mut: size i64 // Symbol size. } +// see for more details: https://gist.github.com/x0nu11byt3/bcb35c3de461e5fb66173071a2379779 +// https://www.etherington.xyz/elfguide#symtab-section-entries fn (mut g Gen) create_symbol_table_section(str_name string, info u8, bind u8, other i8, value i64, size i64, shndx i16) SymbolTableSection { return SymbolTableSection{ @@ -615,7 +618,6 @@ fn (mut g Gen) gen_section_data(sections []Section) { for rela in data { g.write64(rela.offset) g.fn_addr[rela.name] = rela.offset // that's wierd it's the call offset, not the fn - g.fn_names << rela.name g.write64(rela.info) g.write64(rela.addend) g.println('; SHT_RELA `${rela.name}` (${rela.offset}, ${rela.info}, ${rela.addend})') @@ -711,6 +713,23 @@ pub fn (mut g Gen) generate_linkable_elf_header() { elf_stv_default, 0, 0, 0), ] + mut sym_data_offset := 0 // offset from the beggining of the data section + for f in g.files { + for s in f.stmts { + if s is ast.GlobalDecl { + for fi in s.fields { + size := g.get_type_size(fi.typ) + if size > 0 { + g.symbol_table << g.create_symbol_table_section(fi.name, elf_stt_object, + elf_stb_global, elf_stv_default, sym_data_offset, size, i16(g.find_section_header('.data', + sections))) + } + sym_data_offset += size + } + } + } + } + for symbol in g.extern_symbols { g.symbol_table << g.create_symbol_table_section(symbol[2..], elf_stt_notype, elf_stb_global, elf_stv_default, 0, 0, 0) @@ -742,6 +761,37 @@ pub fn (mut g Gen) generate_linkable_elf_header() { g.elf_rela_section = sections[g.find_section_header('.rela.text', sections)] + // Init data section + // TODO: use .bss for uninitialized data or generate the data + data_section := sections[g.find_section_header('.data', sections)] + g.elf_data_header_addr = data_section.header.offset + + data_pos := g.pos() + g.println('\ndata_start_pos = ${data_pos.hex()}') + g.write64_at(g.elf_data_header_addr + 24, data_pos) + for f in g.files { + for s in f.stmts { + if s is ast.GlobalDecl { + for fi in s.fields { + size := g.get_type_size(fi.typ) + match size { + 1 { g.write8(0xF) } + 2 { g.write16(0xF) } + 4 { g.write32(0xF) } + 8 { g.write64(i64(0xF)) } + else { println('${@LOCATION} unsupported size ${size} for global ${fi}') } + } + g.println('; global ${fi.name}, size: ${size}') + } + } + } + } + g.write64_at(g.elf_data_header_addr + 32, g.pos() - data_pos) + // TODO: pre-calculate .data instead of genetating it at runtime + + // It is wierd, the init section has something in it but I did not find + // where does it come from + // user code starts here if g.pref.is_verbose { eprintln('code_start_pos = ${g.buf.len.hex()}') @@ -752,11 +802,29 @@ pub fn (mut g Gen) generate_linkable_elf_header() { // if g.start_symbol_addr > 0 { // g.write64_at(g.start_symbol_addr + native.elf_symtab_size - 16, g.code_start_pos) //} - text_section := sections[g.find_section_header('.text', sections)] g.elf_text_header_addr = text_section.header.offset g.write64_at(g.elf_text_header_addr + 24, g.pos()) // write the code start pos to the text section + g.println('; fill .data') + for f in g.files { + for s in f.stmts { + if s is ast.GlobalDecl { + for fi in s.fields { + size := g.get_type_size(fi.typ) + if size > 8 || size <= 0 { + println('${@LOCATION} unsupported size ${size} for global ${fi}') + } else if fi.expr !is ast.EmptyExpr { + g.expr(fi.expr) + g.code_gen.mov_reg_to_var(GlobalVar{fi.name, fi.typ}, g.code_gen.main_reg()) + g.println('; global ${fi.name}, size: ${size}') + } + } + } + } + } + + g.elf_main_call_pos = g.pos() g.code_gen.call(placeholder) g.println('; call main.main') g.code_gen.mov64(g.code_gen.main_reg(), i64(0)) @@ -848,9 +916,13 @@ pub fn (mut g Gen) gen_rela_section() { g.symtab_get_index(g.symbol_table, symbol[2..]), elf_r_amd64_gotpcrelx, -4) } for var_pos, symbol in g.extern_vars { - relocations << g.create_rela_section(symbol, var_pos - g.code_start_pos + 2, g.symtab_get_index(g.symbol_table, + relocations << g.create_rela_section(symbol, var_pos - g.code_start_pos, g.symtab_get_index(g.symbol_table, symbol[2..]), elf_r_amd64_64, 0) } + for var_pos, symbol in g.global_vars { + relocations << g.create_rela_section(symbol, var_pos - g.code_start_pos, g.symtab_get_index(g.symbol_table, + symbol), elf_r_amd64_64, 0) + } g.elf_rela_section.data = relocations g.gen_section_data([g.elf_rela_section]) } @@ -872,7 +944,7 @@ pub fn (mut g Gen) generate_elf_footer() { g.write64_at(g.file_size_pos + 8, file_size) if g.pref.arch == .arm64 { bl_next := u32(0x94000001) - g.write32_at(g.code_start_pos, i32(bl_next)) + g.write32_at(g.elf_main_call_pos, i32(bl_next)) } else { // amd64 // call main function, it's not guaranteed to be the first @@ -880,7 +952,7 @@ pub fn (mut g Gen) generate_elf_footer() { // now need to replace "0" with a relative address of the main function // +1 is for "e8" // -5 is for "e8 00 00 00 00" - g.write32_at(g.code_start_pos + 1, i32(g.main_fn_addr - g.code_start_pos) - 5) + g.write32_at(g.elf_main_call_pos + 1, i32(g.main_fn_addr - g.elf_main_call_pos) - 5) } g.create_executable() } diff --git a/vlib/v/gen/native/expr.v b/vlib/v/gen/native/expr.v index 1b27506e48..2dd2636164 100644 --- a/vlib/v/gen/native/expr.v +++ b/vlib/v/gen/native/expr.v @@ -196,7 +196,7 @@ fn (mut g Gen) local_var_ident(ident ast.Ident, var LocalVar) { fn (mut g Gen) extern_var_ident(var ExternVar) { if g.pref.os == .linux { main_reg := g.code_gen.main_reg() - g.extern_vars[g.pos()] = var.name + g.extern_vars[g.pos() + 2] = var.name // + 2 for the mov64 instruction g.code_gen.mov64(main_reg, Number(i64(0))) g.code_gen.mov_deref(main_reg, main_reg, ast.u64_type_idx) } else if g.pref.os == .macos { diff --git a/vlib/v/gen/native/gen.v b/vlib/v/gen/native/gen.v index 884e2f3105..080b0b8cc2 100644 --- a/vlib/v/gen/native/gen.v +++ b/vlib/v/gen/native/gen.v @@ -39,8 +39,9 @@ mut: extern_symbols []string linker_include_paths []string linker_libs []string - extern_vars map[i64]string - extern_fn_calls map[i64]string + global_vars map[i64]string // used to patch + extern_vars map[i64]string // used to patch + extern_fn_calls map[i64]string // used to patch fn_addr map[string]i64 fn_names []string var_offset map[string]i32 // local var stack offset @@ -65,7 +66,9 @@ mut: return_type ast.Type comptime_omitted_branches []ast.IfBranch // elf specific + elf_main_call_pos i64 elf_text_header_addr i64 = -1 + elf_data_header_addr i64 = -1 elf_rela_section Section // macho specific macho_ncmds i32 @@ -253,7 +256,10 @@ struct PreprocVar { val i64 } -struct GlobalVar {} +struct GlobalVar { + name string + typ ast.Type +} @[params] struct VarConfig { @@ -300,7 +306,9 @@ fn (mut g Gen) get_var_from_ident(ident ast.Ident) IdentVar { return PreprocVar{ident.info.typ, ident.name, preprocessed_val} } mut obj := ident.obj - if obj !in [ast.Var, ast.ConstField, ast.GlobalField, ast.AsmRegister] { + if obj is ast.GlobalField { + return GlobalVar{obj.name, obj.typ} + } else if obj !in [ast.Var, ast.ConstField, ast.AsmRegister] { obj = ident.scope.find(ident.name) or { g.n_error('${@LOCATION} unknown variable ${ident.name}') } @@ -389,6 +397,7 @@ pub fn gen(files []&ast.File, mut table ast.Table, out_name string, pref_ &pref. g.init_builtins() g.calculate_all_size_align() g.calculate_enum_fields() + g.fn_names = g.table.fns.keys() for file in g.files { /* if file.warnings.len > 0 { @@ -1175,7 +1184,6 @@ fn (mut g Gen) fn_decl(node ast.FnDecl) { g.stack_var_pos = 0 g.stack_depth = 0 g.register_function_address(name) - g.fn_names << name g.labels = &LabelTable{} g.defer_stmts.clear() g.return_type = node.return_type @@ -1225,6 +1233,7 @@ fn (mut g Gen) println(comment string) { @[noreturn] pub fn (mut g Gen) n_error(s string) { print_backtrace() + flush_stdout() util.verror('native error', s) } diff --git a/vlib/v/gen/native/stmt.c.v b/vlib/v/gen/native/stmt.c.v index 7f41780517..b6d9265f98 100644 --- a/vlib/v/gen/native/stmt.c.v +++ b/vlib/v/gen/native/stmt.c.v @@ -106,8 +106,10 @@ fn (mut g Gen) stmt(node ast.Stmt) { g.gen_assert(node) } ast.GlobalDecl { - if !g.is_builtin_mod && !g.pref.experimental { - g.warning('globals are not supported yet', node.pos) + if g.pref.os == .linux { + // handled in elf generator + } else if !g.is_builtin_mod && !g.pref.experimental { + g.warning('global variables are not supported', node.pos) } } ast.Import {} // do nothing here diff --git a/vlib/v/gen/native/tests/linux.vv b/vlib/v/gen/native/tests/linux.vv index be30e35b94..78177db2a8 100644 --- a/vlib/v/gen/native/tests/linux.vv +++ b/vlib/v/gen/native/tests/linux.vv @@ -20,10 +20,23 @@ fn test_c_extern_vars() { C.fprintf(C.stdout, s.str, 3) } +fn test_prints() { + print_character(`a`) +} + +__global foo = 3 + +fn test_globals() { + assert foo == 3 + foo = 5 + assert foo == 5 +} + fn main() { test_for_c_in_string() test_c_extern_vars() + test_prints() + test_globals() flush_stderr() flush_stdout() - print_character(`a`) } diff --git a/vlib/v/gen/native/tests/native_test.v b/vlib/v/gen/native/tests/native_test.v index b3780a2856..0ab85e8d80 100644 --- a/vlib/v/gen/native/tests/native_test.v +++ b/vlib/v/gen/native/tests/native_test.v @@ -80,7 +80,7 @@ fn test_native() { work_test_path := os.join_path(wrkdir, test_file_name) exe_test_path := os.join_path(wrkdir, test_file_name + '.exe') tmperrfile := os.join_path(dir, test + '.tmperr') - cmd := '${os.quoted_path(vexe)} -o ${os.quoted_path(exe_test_path)} -b native ${os.quoted_path(full_test_path)} -d no_backtrace -d custom_define 2> ${os.quoted_path(tmperrfile)}' + cmd := '${os.quoted_path(vexe)} -enable-globals -o ${os.quoted_path(exe_test_path)} -b native ${os.quoted_path(full_test_path)} -d no_backtrace -d custom_define 2> ${os.quoted_path(tmperrfile)}' if is_verbose { println(cmd) }