diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37ba2e81a7..a2efc705cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -151,7 +151,7 @@ jobs: [ "$(stat -c %s leaks.txt)" = "0" ] macos: - runs-on: macOS-latest + runs-on: macOS-12 if: github.event_name != 'push' || github.event.ref == 'refs/heads/master' || github.event.repository.full_name != 'vlang/v' timeout-minutes: 121 env: diff --git a/vlib/v/builder/nativebuilder/nativebuilder.v b/vlib/v/builder/nativebuilder/nativebuilder.v index 57b54c8ca4..84fedf3697 100644 --- a/vlib/v/builder/nativebuilder/nativebuilder.v +++ b/vlib/v/builder/nativebuilder/nativebuilder.v @@ -39,6 +39,15 @@ pub fn build_native(mut b builder.Builder, v_files []string, out_file string) { } b.front_and_middle_stages(nvf) or { return } util.timing_start('Native GEN') + if b.pref.arch == ._auto { + $if amd64 { + b.pref.arch = .amd64 + } $else $if arm64 { + b.pref.arch = .arm64 + } $else { + eprintln('Error: Only arm64 and amd64 are supported by V') + } + } b.stats_lines, b.stats_bytes = native.gen(b.parsed_files, b.table, out_file, b.pref) util.timing_measure('Native GEN') } diff --git a/vlib/v/gen/native/amd64.v b/vlib/v/gen/native/amd64.v index 78722d4306..a6bfafce0c 100644 --- a/vlib/v/gen/native/amd64.v +++ b/vlib/v/gen/native/amd64.v @@ -76,6 +76,9 @@ fn (mut g Gen) neg(reg Register) { } fn (mut g Gen) cmp(reg Register, size Size, val i64) { + if g.pref.arch != .amd64 { + panic('cmp') + } // Second byte depends on the size of the value match size { ._8 { @@ -349,6 +352,9 @@ fn (mut g Gen) movabs(reg Register, val i64) { } fn (mut g Gen) mov_reg_to_var(var_offset int, reg Register) { + if g.pref.arch != .amd64 { + panic('invalid arch for mov_reg_to_var') + } // 89 7d fc mov DWORD PTR [rbp-0x4],edi match reg { .rax { @@ -458,8 +464,14 @@ fn (mut g Gen) cdq() { } pub fn (mut g Gen) ret() { - g.write8(0xc3) - g.println('ret') + if g.pref.arch == .amd64 { + g.write8(0xc3) + g.println('ret') + } else if g.pref.arch == .arm64 { + g.write32(0xd65f03c0) + } else { + panic('invalid arch') + } } pub fn (mut g Gen) push(reg Register) { @@ -1958,9 +1970,11 @@ fn (mut g Gen) fn_decl_amd64(node ast.FnDecl) { g.ret() } +/* pub fn (mut x Amd64) allocate_var(name string, size int, initial_val int) { // do nothing as interface call is crashing } +*/ pub fn (mut g Gen) allocate_array(name string, size int, items int) int { pos := g.allocate_var(name, size, items) @@ -1969,6 +1983,10 @@ pub fn (mut g Gen) allocate_array(name string, size int, items int) int { } pub fn (mut g Gen) allocate_var(name string, size int, initial_val int) int { + if g.pref.arch == .arm64 { + // TODO + return 0 + } // `a := 3` => `mov DWORD [rbp-0x4],0x3` match size { 1 { diff --git a/vlib/v/gen/native/arm64.v b/vlib/v/gen/native/arm64.v index d90814db48..097ff490e9 100644 --- a/vlib/v/gen/native/arm64.v +++ b/vlib/v/gen/native/arm64.v @@ -42,7 +42,7 @@ mut: // arm64 specific stuff for code generation } -pub fn (mut x Arm64) allocate_var(name string, size int, initial_val int) { +fn (mut x Arm64) allocate_var(name string, size int, initial_val int) { eprintln('TODO: allocating var on arm64 ($name) = $size = $initial_val') } @@ -54,11 +54,8 @@ fn (mut g Gen) mov_arm(reg Arm64Register, val u64) { // println(x & ~(m << 16)) // g.write32(0x777777) r := int(reg) - if r == 0 && val == 1 { - g.write32(0xd2800020) - g.println('mov x0, 1') - } else if r >= 0 && r <= 16 { - g.write32(int(u32(0xd2800000 + int(r) + int(val)) << 5)) + if r >= 0 && r <= 16 { + g.write32(int(u32(0xd2800000 + u32(r) + (u32(val) << 5)))) g.println('mov x$r, $val') } else { g.n_error('mov_arm unsupported values') @@ -82,17 +79,106 @@ fn (mut g Gen) neg_arm(r Arm64Register) { } fn (mut g Gen) neg_regs_arm(a Arm64Register, b Arm64Register) { - if int(a) < 0x0f && int(b) < 0x0f { - g.write32(0xe2600000 | int(a) << 16 | int(b) << 12) + if u32(a) < 0x0f && u32(b) < 0x0f { + g.write32(int(0xe2600000 | (u32(a) << 16) | u32(b) << 12)) g.println('neg $a, $b') } else { g.n_error('unhandled neg $a, $b') } } +fn (mut g Gen) sub_sp(v int) { + if g.pref.arch != .arm64 { + g.n_error('sub_sp is arm64-specifig') + return + } + // this is for 0x20 only + if v < 0 { + g.write32(i32(0x910083ff)) // add sp, X + } else { + g.write32(i32(0xd10083ff)) // sub sp, X + } +} + pub fn (mut g Gen) fn_decl_arm64(node ast.FnDecl) { g.gen_arm64_helloworld() - // TODO + /* + 0x100003f6c ff8300d1 sub sp, sp, 0x20 ; [00] -r-x section size 52 named 0.__TEXT.__text + 0x100003f70 fd7b01a9 stp x29, x30, [sp, 0x10] + 0x100003f74 fd430091 add x29, sp, 0x10 + 0x100003f78 bfc31fb8 stur wzr, [x29, -4] + 0x100003f7c 68008052 mov w8, 3 + 0x100003f80 e80b00b9 str w8, [sp, 8] + 0x100003f84 00000090 adrp x0, 0x100003000 + 0x100003f88 00b03e91 add x0, x0, 0xfac + 0x100003f8c 05000094 bl sym.imp.puts ;[1] + 0x100003f90 e00b40b9 ldr w0, [sp, 8] ; 5 + 0x100003f94 fd7b41a9 ldp x29, x30, [sp, 0x10] + 0x100003f98 ff830091 add sp, sp, 0x20 + 0x100003f9c c0035fd6 ret + */ + + /* + /* + g.push(.rbp) + g.mov_rbp_rsp() +*/ + locals_count := node.scope.objects.len + node.params.len + node.defer_stmts.len + g.stackframe_size = (locals_count * 8) + 0x10 +// g.sub8(.rsp, g.stackframe_size) + g.sub_sp(32) + + // Copy values from registers to local vars (calling convention) + mut offset := 0 + for i in 0 .. node.params.len { + name := node.params[i].name + // TODO optimize. Right now 2 mov's are used instead of 1. + g.allocate_var(name, 4, 0) + // `mov DWORD PTR [rbp-0x4],edi` + offset += 4 + g.mov_reg_to_var(offset, native.fn_arg_registers[i]) + } + // define defer vars + for i in 0 .. node.defer_stmts.len { + name := '_defer$i' + g.allocate_var(name, 8, 0) + } + // + g.stmts(node.stmts) + is_main := node.name == 'main.main' + if is_main { + // println('end of main: gen exit') + zero := ast.IntegerLiteral{} + g.gen_exit(zero) + g.ret() + return + } + // g.leave() + g.labels.addrs[0] = g.pos() + g.println('; label 0: return') +/* + if g.defer_stmts.len != 0 { + // save return value + g.push(.rax) + for defer_stmt in g.defer_stmts.reverse() { + defer_var := g.get_var_offset('_defer$defer_stmt.idx_in_fn') + g.mov_var_to_reg(.rax, defer_var) + g.cmp_zero(.rax) + label := g.labels.new_label() + jump_addr := g.cjmp(.je) + g.labels.patches << LabelPatch{ + id: label + pos: jump_addr + } + g.stmts(defer_stmt.stmts) + g.labels.addrs[label] = g.pos() + } + //g.pop(.rax) + } +*/ + g.sub_sp(-32) + g.ret() + */ } pub fn (mut g Gen) call_fn_arm64(node ast.CallExpr) { @@ -143,6 +229,11 @@ fn (mut g Gen) gen_arm64_helloworld() { g.mov_arm(.x8, 64) // write (linux-arm64) g.svc() } else { + g.mov_arm(.x0, 1) + g.adr(.x1, 0x10 + 4) + g.mov_arm(.x2, 13) + g.mov_arm(.x16, 4) // write + g.svc() g.mov_arm(.x0, 0) g.mov_arm(.x16, 1) g.svc() @@ -167,8 +258,13 @@ fn (mut g Gen) bl() { } fn (mut g Gen) svc() { - g.write32(0xd4001001) - g.println('svc 0x80') + if g.pref.os == .linux { + g.write32(0xd4001001) + g.println('svc 0x80') + } else { + g.write32(0xd4000001) + g.println('svc 0') + } } pub fn (mut c Arm64) gen_exit(mut g Gen, expr ast.Expr) { diff --git a/vlib/v/gen/native/gen.v b/vlib/v/gen/native/gen.v index c5db1f60f9..5e5a4ffbfb 100644 --- a/vlib/v/gen/native/gen.v +++ b/vlib/v/gen/native/gen.v @@ -49,6 +49,9 @@ mut: strs []String labels &LabelTable defer_stmts []ast.DeferStmt + // macho specific + macho_ncmds int + macho_cmdsize int } enum RelocType { @@ -114,6 +117,20 @@ fn get_backend(arch pref.Arch) ?CodeGen { g: 0 } } + ._auto { + $if amd64 { + return Amd64{ + g: 0 + } + } $else $if arm64 { + return Arm64{ + g: 0 + } + } $else { + eprintln('-native only have amd64 and arm64 codegens') + exit(1) + } + } else {} } return error('unsupported architecture') diff --git a/vlib/v/gen/native/macho.v b/vlib/v/gen/native/macho.v index 2b8d0961b7..35a3568a0e 100644 --- a/vlib/v/gen/native/macho.v +++ b/vlib/v/gen/native/macho.v @@ -4,21 +4,24 @@ module native const ( - s_attr_some_instructions = 0x00000400 + s_attr_some_instructions = 0x0400 s_attr_pure_instructions = 0x80000000 - s_attr_ext_reloc = 0x00000200 - s_attr_loc_reloc = 0x00000100 - // + s_attr_ext_reloc = 0x0200 + s_attr_loc_reloc = 0x0100 macho_symcmd_size = 0x18 macho_d_size = 0x50 mh_object = 1 mh_execute = 2 + lc_dyld_chained_fixups = 0x80000034 + lc_dyld_exports_trie = 0x80000033 + lc_dyld_info_only = 0x80000022 lc_dysymtab = 0xb lc_load_dylib = 0xc lc_load_dylinker = 0xe lc_main = 0x80000028 lc_segment_64 = 0x19 lc_symtab = 0x2 + base_addr = i64(0x1_0000_0000) ) struct Symbol { @@ -41,31 +44,53 @@ struct Reloc { } fn (mut g Gen) macho_segment64_pagezero() { - g.write32(native.lc_segment_64) // LC_SEGMENT_64 - g.write32(72) // cmdsize + g.macho_add_loadcommand(native.lc_segment_64, 72) g.write_string_with_padding('__PAGEZERO', 16) // section name g.write64(0) // vmaddr - g.write64(0x1000) // vmsize + // XXX g.write64(g.get_pagesize()) // vmsize + g.write64(native.base_addr) // vmsize g.write64(0) // fileoff g.write64(0) // filesize - g.write32(0) // maxprot g.write32(0) // initprot g.write32(0) // nsects g.write32(0) // flags } +fn (mut g Gen) macho_add_loadcommand(typ u32, size int) { + g.macho_ncmds++ + g.macho_cmdsize += size + g.write32(int(typ)) + g.write32(size) +} + +fn (mut g Gen) macho_patch_header() { + g.write32_at(0x10, g.macho_ncmds) + g.write32_at(0x14, g.macho_cmdsize) +} + +// probably unnecessary +fn (mut g Gen) macho_chained_fixups() { + g.macho_add_loadcommand(native.lc_dyld_chained_fixups, 16) + g.write32(0x4000) // dataoff + g.write32(56) // datasize + + g.macho_add_loadcommand(native.lc_dyld_exports_trie, 16) + g.write32(0x4000) // dataoff + g.write32(56) // datasize +} + fn (mut g Gen) macho_segment64_linkedit() { - g.write32(native.lc_segment_64) - g.write32(0x48) // cmdsize + g.macho_add_loadcommand(native.lc_segment_64, 0x48) g.write_string_with_padding('__LINKEDIT', 16) - g.write64(0x3000) // vmaddr - g.write64(0x1000) // vmsize - g.write64(0x1000) // fileoff + // g.size_pos << g.buf.len + g.write64(native.base_addr + g.get_pagesize()) // vmaddr + g.write64(g.get_pagesize()) // vmsize + g.write64(g.get_pagesize()) // fileoff g.write64(0) // filesize - g.write32(7) // maxprot - g.write32(3) // initprot + g.write32(1) // maxprot + g.write32(1) // initprot g.write32(0) // nsects g.write32(0) // flags } @@ -85,40 +110,50 @@ fn (mut g Gen) macho_header(ncmds int, bintype int) int { cmdsize_offset := g.buf.len g.write32(0) // size of load commands - g.write32(0) // flags + if g.pref.arch == .arm64 { + g.write32(0x00200085) + } else { + g.write32(0) // flags + } g.write32(0) // reserved return cmdsize_offset } fn (mut g Gen) macho_segment64_text() []int { mut patch := []int{} - g.write32(native.lc_segment_64) // LC_SEGMENT_64 - g.write32(152) // 152 + g.macho_add_loadcommand(native.lc_segment_64, 152) g.write_string_with_padding('__TEXT', 16) // section name - g.write64(0x100000000) // vmaddr - patch << g.buf.len - g.write64(0x00001000) // + codesize) // vmsize - g.write64(0x00000000) // filesize - patch << g.buf.len - g.write64(0x00001000) // + codesize) // filesize + g.write64(native.base_addr) // vmaddr - g.write32(7) // maxprot + g.write64(g.get_pagesize() * 2) // vmsize + g.write64(0) // fileoff + g.write64(g.get_pagesize() + 63) // filesize + + g.write32(5) // maxprot g.write32(5) // initprot g.write32(1) // nsects g.write32(0) // flags g.write_string_with_padding('__text', 16) // section name g.write_string_with_padding('__TEXT', 16) // segment name - g.write64(0x0000000100001000) // vmaddr - patch << g.buf.len - g.write64(0) // vmsize - g.write32(4096) // offset - g.write32(0) // align + g.write64(native.base_addr + g.get_pagesize()) // vmaddr + if g.pref.arch == .arm64 { + g.write64(0) // vmsize + } else { + patch << g.buf.len + g.write64(0) // vmsize + } + g.write32(0) // offset + g.write32(4) // align g.write32(0) // reloff g.write32(0) // nreloc - g.write32(0) // flags + if g.pref.arch == .amd64 { + g.write32(0) // flags + } else { + g.write32(0x80000400) // flags + } g.write32(0) g.write32(0) // reserved1 @@ -127,16 +162,25 @@ fn (mut g Gen) macho_segment64_text() []int { } fn (mut g Gen) macho_symtab() { - g.write32(native.lc_symtab) - g.write32(24) - g.write32(0x1000) - g.write32(0) - g.write32(0x1000) - g.write32(0) + g.macho_add_loadcommand(native.lc_dyld_info_only, 48) + g.write32(0) // rebase_off + g.write32(0) // rebase_size + g.write32(0) // bind_off + g.write32(0) // bind_size + g.write32(0) // weak_bind_off + g.write32(0) // weak_bind_size + g.write32(0) // lazy_bind_off + g.write32(0) // lazy_bind_size + g.write32(0x4000) // export_off + g.write32(56) // export_size - // lc_dysymtab - g.write32(native.lc_dysymtab) - g.write32(0x50) + g.macho_add_loadcommand(native.lc_symtab, 24) + g.write32(0x1000) // symoff + g.write32(0) // nsyms + g.write32(0x1000) // stroff + g.write32(0) // strsize + + g.macho_add_loadcommand(native.lc_dysymtab, 0x50) g.write32(0) // ilocalsym g.write32(0) // nlocalsym g.write32(0) // iextdefsym @@ -158,47 +202,54 @@ fn (mut g Gen) macho_symtab() { } fn (mut g Gen) macho_dylibs() { - g.write32(native.lc_load_dylinker) - g.write32(32) // cmdsize (must be aligned to int32) + g.macho_add_loadcommand(native.lc_load_dylinker, 32) g.write32(12) // offset g.write_string_with_padding('/usr/lib/dyld', 16) g.write32(0) // padding // can be removed - g.write32(native.lc_load_dylib) - g.write32(56) // cmdsize + g.macho_add_loadcommand(native.lc_load_dylib, 56) g.write32(24) // offset g.write32(0) // ts - g.write32(1) // ver - g.write32(1) // compat + g.write32(0x051f6403) // g.write32(1) // current version + g.write32(0x10000) // compatibility version g.write_string_with_padding('/usr/lib/libSystem.B.dylib', 32) } fn (mut g Gen) macho_main(addr int) { - g.write32(int(native.lc_main)) // LC_MAIN - g.write32(24) // cmdsize + g.macho_add_loadcommand(native.lc_main, 24) g.write32(addr) // entrypoint g.write32(0) // initial_stacksize } pub fn (mut g Gen) generate_macho_header() { - g.code_start_pos = 0x1000 - g.debug_pos = 0x1000 - cmdsize_offset := g.macho_header(8, native.mh_execute) + pagesize := g.get_pagesize() + g.code_start_pos = pagesize + g.debug_pos = pagesize + ncmds := 0 // 9+ 2 -2 -3 -1 + cmdsize_offset := g.macho_header(ncmds, native.mh_execute) g.macho_segment64_pagezero() g.size_pos = g.macho_segment64_text() - g.macho_segment64_linkedit() + // g.macho_segment64_linkedit() + // g.macho_chained_fixups() g.macho_symtab() g.macho_dylibs() - g.macho_main(0x1000) + g.macho_main(pagesize) g.write32_at(cmdsize_offset, g.buf.len - 24) - g.write_nulls(0x1000 - g.buf.len) + g.write_nulls(pagesize - g.buf.len) g.call(0) } +fn (mut g Gen) get_pagesize() int { + if g.pref.arch == .arm64 { + return 0x4000 // 16KB + } + return 0x1000 // 4KB +} + fn (mut g Gen) write_nulls(len int) { - pad := 0x1000 - g.buf.len + pad := g.get_pagesize() - g.buf.len for _ in 0 .. pad { g.write8(0) } @@ -208,11 +259,11 @@ pub fn (mut g Gen) generate_macho_object_header() { if g.pref.arch == .arm64 { g.write32(0xfeedfacf) // MH_MAGIC_64 g.write32(0x0100000c) // CPU_TYPE_ARM64 - g.write32(0x00000000) // CPU_SUBTYPE_ARM64_ALL + g.write32(0) // CPU_SUBTYPE_ARM64_ALL } else { g.write32(0xfeedfacf) // MH_MAGIC_64 g.write32(0x01000007) // CPU_TYPE_X64 - g.write32(0x00000003) // CPU_SUBTYPE_X64 + g.write32(3) // CPU_SUBTYPE_X64 } g.write32(native.mh_object) // MH_OBJECT text_offset := 0x138 @@ -249,7 +300,6 @@ pub fn (mut g Gen) generate_macho_object_header() { /// ??? g.write32(0x32) g.write32(0x18) - g.write32(0x01) g.write32(0x000a0000) // minOS 10.0 g.write32(0) @@ -257,8 +307,7 @@ pub fn (mut g Gen) generate_macho_object_header() { // lc_symtab g.sym_table_command() // - g.write32(native.lc_dysymtab) - g.write32(native.macho_d_size) + g.macho_add_loadcommand(native.lc_dysymtab, native.macho_d_size) g.write32(0) g.write32(2) g.write32(2) @@ -291,13 +340,26 @@ pub fn (mut g Gen) generate_macho_footer() { g.write8(0) for o in g.size_pos { n := g.read32_at(o) + // eprintln('$n + $delta') g.write32_at(o, n + delta) } g.write64(0) - // this is amd64-specific - call_delta := int(g.main_fn_addr - g.code_start_pos) - 5 - g.write32_at(g.code_start_pos + 1, call_delta) - g.create_executable() + g.macho_patch_header() + if g.pref.arch == .amd64 { + call_delta := int(g.main_fn_addr - g.code_start_pos) - 5 + g.write32_at(g.code_start_pos + 1, call_delta) + g.create_executable() + } else { + call_delta := int(g.main_fn_addr - g.code_start_pos) + if (call_delta % 4) != 0 || call_delta < 0 { + g.n_error('Invalid entrypoint->main delta ($call_delta)') + } else { + blop := (0x94 << 24) | (call_delta / 4) + g.write32_at(g.code_start_pos, int(blop)) + g.write_nulls(g.get_pagesize() - g.buf.len) + g.create_executable() + } + } } fn (mut g Gen) sym_table_command() { @@ -338,8 +400,7 @@ fn (mut g Gen) sym_table_command() { name: 'ltmp1' is_ext: false } - g.write32(native.lc_symtab) - g.write32(native.macho_symcmd_size) + g.macho_add_loadcommand(native.lc_symtab, native.macho_symcmd_size) sym_table_offset := 0x168 g.write32(sym_table_offset) g_syms_len := 4 @@ -406,8 +467,7 @@ fn (mut g Gen) sym_string_table() int { // that should be .rel32, not windows-specific g.write32_at(s.pos, pos) } else { - baddr := i64(0x100000000) - g.write64_at(s.pos, g.buf.len + baddr) + g.write64_at(s.pos, g.buf.len + native.base_addr) } } } diff --git a/vlib/v/gen/native/macho_test.v b/vlib/v/gen/native/macho_test.v index db8ae629b4..4795251d83 100644 --- a/vlib/v/gen/native/macho_test.v +++ b/vlib/v/gen/native/macho_test.v @@ -6,7 +6,9 @@ import v.ast fn test_macho() { os.chdir(os.temp_dir()) or {} mut g := native.Gen{ - pref: &pref.Preferences{} + pref: &pref.Preferences{ + arch: .amd64 + } out_name: 'test.bin' table: ast.new_table() code_gen: native.Amd64{