From 43d0d0f32202b318d6a95875e79f06b83ffb6a31 Mon Sep 17 00:00:00 2001 From: Spydr <58859306+Spydr06@users.noreply.github.com> Date: Sat, 1 Oct 2022 05:47:16 +0200 Subject: [PATCH] native: skip linking when no library functions get called (#15930) --- vlib/v/gen/native/elf.v | 132 +++++++++++++++++++++++++++++++++------- vlib/v/gen/native/gen.v | 69 +++++++++++++++++---- 2 files changed, 168 insertions(+), 33 deletions(-) diff --git a/vlib/v/gen/native/elf.v b/vlib/v/gen/native/elf.v index a9e0639c0a..4b2c1d69d4 100644 --- a/vlib/v/gen/native/elf.v +++ b/vlib/v/gen/native/elf.v @@ -44,6 +44,9 @@ const ( elf_pt_phdr = 6 elf_pt_tls = 7 + // offset of e_entry field in the elf header + elf_e_entry_offset = 24 + // alignment of program headers elf_p_align = 0x1000 @@ -659,13 +662,12 @@ fn (mut g Gen) gen_section_data(sections []Section) { } } -pub fn (mut g Gen) generate_elf_header() { +pub fn (mut g Gen) generate_linkable_elf_header() { elf_type := native.elf_type_rel // PIE (use _exec for non-relocatable executables) // generate program headers mut program_headers := []ProgramHeader{} program_headers << g.create_program_header(native.elf_pt_load, 5, native.elf_p_align) - // generate sections mut sections := [ Section{}, // null section as first section @@ -735,6 +737,56 @@ pub fn (mut g Gen) generate_elf_header() { g.debug_pos = g.buf.len } +pub fn (mut g Gen) generate_simple_elf_header() { + elf_type := native.elf_type_exec + + mut phdr := g.create_program_header(native.elf_pt_load, 5, native.elf_p_align) + phdr.vaddr = native.segment_start + phdr.paddr = native.segment_start + + mut elf_header := g.default_elf_header() + elf_header.typ = i16(elf_type) + elf_header.phnum = i16(1) + elf_header.shoff = i64(0) + elf_header.shentsize = i16(0) + elf_header.shnum = i16(0) + elf_header.shstrndx = i16(0) + elf_header.entry = native.segment_start + native.elf_header_size + native.elf_phentry_size + + g.gen_elf_header(elf_header) + + // write program header + g.gen_program_header(phdr) + + // user code starts here + if g.pref.is_verbose { + eprintln('code_start_pos = $g.buf.len.hex()') + } + + g.code_start_pos = g.pos() + g.debug_pos = int(g.pos()) + + g.call(native.placeholder) + g.println('; call main.main') + + // generate exit syscall + match g.pref.arch { + .arm64 { + g.mov_arm(.x16, 0) + g.mov_arm(.x0, 0) + g.svc() + } + .amd64 { + g.mov(.edi, 0) + g.mov(.eax, g.nsyscall_exit()) + g.syscall() + } + else { + g.n_error('unsupported platform $g.pref.arch') + } + } +} + fn (mut g Gen) elf_string_table() { for _, s in g.strs { match s.typ { @@ -773,7 +825,9 @@ pub fn (mut g Gen) generate_elf_footer() { } // write size of text section into section header - g.write64_at(g.elf_text_header_addr + 32, g.pos() - g.code_start_pos) + if g.elf_text_header_addr != -1 { + g.write64_at(g.elf_text_header_addr + 32, g.pos() - g.code_start_pos) + } g.create_executable() } @@ -789,7 +843,19 @@ pub fn (mut g Gen) prepend_vobjpath(paths []string) []string { } pub fn (mut g Gen) find_o_path(fname string) string { - opaths := g.prepend_vobjpath(['/usr/lib/x86_64-linux-gnu', '/usr/lib64', '/usr/lib']) + opaths := match g.pref.arch { + .amd64 { + g.prepend_vobjpath(['/usr/lib/x86_64-linux-gnu', '/usr/lib64', '/usr/lib']) + } + .arm64 { + g.prepend_vobjpath(['/usr/lib/aarch64-linux-gnu', '/usr/lib']) + } + else { + g.n_error('unknown architecture $g.pref.arch') + ['/dev/null'] + } + } + for opath in opaths { fpath := os.join_path_single(opath, fname) if os.is_file(fpath) { @@ -800,8 +866,19 @@ pub fn (mut g Gen) find_o_path(fname string) string { } pub fn (mut g Gen) get_lpaths() string { - lpaths := g.prepend_vobjpath(['/usr/lib/x86_64-linux-gnu', '/usr/lib64', '/lib64', '/usr/lib', - '/lib']) + lpaths := match g.pref.arch { + .amd64 { + g.prepend_vobjpath(['/usr/lib/x86_64-linux-gnu', '/usr/lib64', '/lib64', '/usr/lib', + '/lib']) + } + .arm64 { + g.prepend_vobjpath(['/usr/lib/aarch64-linux-gnu', '/usr/lib', '/lib']) + } + else { + g.n_error('unknown architecture $g.pref.arch') + ['/dev/null'] + } + } return lpaths.map('-L$it').join(' ') } @@ -810,12 +887,32 @@ pub fn (mut g Gen) link_elf_file(obj_file string) { crti := g.find_o_path('crti.o') crtn := g.find_o_path('crtn.o') lpaths := g.get_lpaths() + + arch := match g.pref.arch { + .amd64 { + 'elf_x86_64' + } + .arm64 { + 'aarch64elf' + } + else { + g.n_error('unknown architecture $g.pref.arch') + 'elf_x86_64' // default to x86_64 + } + } + + dynamic_linker := match g.pref.arch { + .amd64 { '/lib64/ld-linux-x86-64.so.2' } + .arm64 { '/lib/ld-linux-aarch64.so.1' } + else { '/dev/null' } + } + linker_args := [ '-v', lpaths, - '-m elf_x86_64', + '-m $arch', '-dynamic-linker', - '/lib64/ld-linux-x86-64.so.2', + dynamic_linker, crt1, crti, '-lc', @@ -827,20 +924,13 @@ pub fn (mut g Gen) link_elf_file(obj_file string) { ] slinker_args := linker_args.join(' ') - mut ldlld := 'ld' - /* - match g.pref.os { - .linux { ldlld = 'ld.lld' } - .windows { ldlld = 'lld-link' } - .macos { ldlld = 'ld64.lld' } - else {} - } - */ + mut ld := 'ld' + custom_linker := os.getenv('VLINKER') if custom_linker != '' { - ldlld = custom_linker + ld = custom_linker } - linker_path := os.real_path(ldlld) + linker_path := os.real_path(ld) linker_cmd := '${os.quoted_path(linker_path)} $slinker_args' if g.pref.is_verbose { println(linker_cmd) @@ -848,11 +938,11 @@ pub fn (mut g Gen) link_elf_file(obj_file string) { res := os.execute(linker_cmd) if res.exit_code != 0 { - g.n_error('ELF linking failed ($ldlld):\n$res.output') + g.n_error('ELF linking failed ($ld):\n$res.output') return } if g.pref.is_verbose { - println('linking with $ldlld finished successfully:\n$res.output') + println('linking with $ld finished successfully:\n$res.output') } } diff --git a/vlib/v/gen/native/gen.v b/vlib/v/gen/native/gen.v index f5b4e22ab6..bdd4b290a5 100644 --- a/vlib/v/gen/native/gen.v +++ b/vlib/v/gen/native/gen.v @@ -6,6 +6,7 @@ module native import os import strings import v.ast +import v.ast.walker import v.util import v.mathutil as mu import v.token @@ -26,6 +27,7 @@ mut: pub struct Gen { out_name string pref &pref.Preferences = unsafe { nil } // Preferences shared from V struct + files []&ast.File mut: code_gen CodeGen table &ast.Table = unsafe { nil } @@ -33,7 +35,7 @@ mut: sect_header_name_pos int offset i64 file_size_pos i64 - elf_text_header_addr i64 + elf_text_header_addr i64 = -1 main_fn_addr i64 main_fn_size i64 start_symbol_addr i64 @@ -59,6 +61,8 @@ mut: // macho specific macho_ncmds int macho_cmdsize int + + requires_linking bool } enum RelocType { @@ -218,6 +222,7 @@ pub fn gen(files []&ast.File, table &ast.Table, out_name string, pref &pref.Pref sect_header_name_pos: 0 out_name: exe_name pref: pref + files: files // TODO: workaround, needs to support recursive init code_gen: get_backend(pref.arch) or { eprintln('No available backend for this configuration. Use `-a arm64` or `-a amd64`.') @@ -232,7 +237,7 @@ pub fn gen(files []&ast.File, table &ast.Table, out_name string, pref &pref.Pref g.init_builtins() g.calculate_all_size_align() g.calculate_enum_fields() - for file in files { + for file in g.files { /* if file.warnings.len > 0 { eprintln('warning: ${file.warnings[0]}') @@ -253,7 +258,33 @@ pub fn (mut g Gen) typ(a int) &ast.TypeSymbol { return g.table.type_symbols[a] } +pub fn (mut g Gen) ast_has_external_functions() bool { + mut has_external_fn := false + + for file in g.files { + walker.inspect(file, unsafe { &mut has_external_fn }, fn (node &ast.Node, data voidptr) bool { + if node is ast.Expr && (node as ast.Expr) is ast.CallExpr + && ((node as ast.Expr) as ast.CallExpr).language != .v { + unsafe { + *&bool(data) = true // an external function was found + } + return false + } + + return true + }) + + if has_external_fn { + break + } + } + + return has_external_fn +} + pub fn (mut g Gen) generate_header() { + g.requires_linking = g.ast_has_external_functions() + match g.pref.os { .macos { g.generate_macho_header() @@ -262,7 +293,11 @@ pub fn (mut g Gen) generate_header() { g.generate_pe_header() } .linux { - g.generate_elf_header() + if g.requires_linking { + g.generate_linkable_elf_header() + } else { + g.generate_simple_elf_header() + } } .raw { if g.pref.arch == .arm64 { @@ -270,26 +305,36 @@ pub fn (mut g Gen) generate_header() { } } else { - g.n_error('only `raw`, `linux` and `macos` are supported for -os in -native') + g.n_error('only `raw`, `linux`, `windows` and `macos` are supported for -os in -native') } } } pub fn (mut g Gen) create_executable() { obj_name := match g.pref.os { - .linux { g.out_name + '.o' } - else { g.out_name } + .linux { + if g.requires_linking { + g.out_name + '.o' + } else { + g.out_name + } + } + else { + g.out_name + } } os.write_file_array(obj_name, g.buf) or { panic(err) } - match g.pref.os { - // TEMPORARY - .linux { // TEMPORARY - g.link(obj_name) + if g.requires_linking { + match g.pref.os { + // TEMPORARY + .linux { // TEMPORARY + g.link(obj_name) + } // TEMPORARY + else {} // TEMPORARY } // TEMPORARY - else {} // TEMPORARY - } // TEMPORARY + } os.chmod(g.out_name, 0o775) or { panic(err) } // make it executable if g.pref.is_verbose {