diff --git a/vlib/v/gen/native/amd64.v b/vlib/v/gen/native/amd64.v index 91fac27b48..2d850b0fdd 100644 --- a/vlib/v/gen/native/amd64.v +++ b/vlib/v/gen/native/amd64.v @@ -662,20 +662,29 @@ fn (mut g Gen) mov_var_to_reg(reg Register, var Var, config VarConfig) { } } -fn (mut g Gen) call(addr int) { - if g.pref.arch == .arm64 { - g.bl() - return - } +fn (mut g Gen) call_addr_at(addr int, at i64) i64 { // Need to calculate the difference between current position (position after the e8 call) // and the function to call. // +5 is to get the posistion "e8 xx xx xx xx" // Not sure about the -1. - rel := 0xffffffff - (g.buf.len + 5 - addr - 1) + return 0xffffffff - (at + 5 - addr - 1) +} + +fn (mut g Gen) call(addr int) i64 { + if g.pref.arch == .arm64 { + g.bl() + return 0 + } + + rel := g.call_addr_at(addr, g.pos()) + c_addr := g.pos() // println('call addr=$addr.hex2() rel_addr=$rel.hex2() pos=$g.buf.len') g.write8(0xe8) - g.write32(rel) + + g.write32(int(rel)) g.println('call $addr') + + return c_addr } fn (mut g Gen) syscall() { @@ -813,8 +822,29 @@ fn (mut g Gen) mul8_var(reg Register, var_offset int) { } fn (mut g Gen) leave() { - g.write8(0xc9) - g.println('leave') + 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() { + name := '_defer$defer_stmt.idx_in_fn' + defer_var := g.get_var_offset(name) + g.mov_var_to_reg(.rax, LocalVar{defer_var, ast.i64_type_idx, name}) + 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.mov_reg(.rsp, .rbp) + g.pop(.rbp) + g.ret() } // returns label's relative address @@ -1341,11 +1371,7 @@ fn (mut g Gen) sar8(r Register, val u8) { g.println('sar $r, $val') } -pub fn (mut g Gen) call_fn(node ast.CallExpr) { - if g.pref.arch == .arm64 { - g.call_fn_arm64(node) - return - } +pub fn (mut g Gen) call_fn_amd64(node ast.CallExpr) { name := node.name mut n := name if !n.contains('.') { @@ -1387,6 +1413,12 @@ pub fn (mut g Gen) call_fn(node ast.CallExpr) { g.println('call `${name}()`') } +fn (mut g Gen) call_builtin_amd64(name string) i64 { + call_addr := g.call(0) + g.println('call builtin `$name`') + return call_addr +} + fn (mut g Gen) patch_calls() { for c in g.callpatches { addr := g.fn_addr[c.name] @@ -2083,31 +2115,22 @@ fn (mut g Gen) fn_decl_amd64(node ast.FnDecl) { 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() { - name := '_defer$defer_stmt.idx_in_fn' - defer_var := g.get_var_offset(name) - g.mov_var_to_reg(.rax, LocalVar{defer_var, ast.i64_type_idx, name}) - 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.mov_reg(.rsp, .rbp) - g.pop(.rbp) - g.ret() + g.leave() +} + +pub fn (mut g Gen) builtin_decl_amd64(builtin BuiltinFn) { + g.push(.rbp) + g.mov_reg(.rbp, .rsp) + local_alloc_pos := g.pos() + g.sub(.rsp, 0) + + builtin.body(builtin, g) + g.println('; stack frame size: $g.stack_var_pos') + g.write32_at(local_alloc_pos + 3, g.stack_var_pos) + + g.labels.addrs[0] = g.pos() + g.leave() } /* @@ -2177,9 +2200,13 @@ pub fn (mut g Gen) allocate_var(name string, size int, initial_val int) int { return g.stack_var_pos } -fn (mut g Gen) convert_int_to_string(r Register, buffer int) { - if r != .rax { - g.mov_reg(.rax, r) +fn (mut g Gen) convert_int_to_string(r1 Register, r2 Register) { + if r1 != .rax { + g.mov_reg(.rax, r1) + } + + if r2 != .rdi { + g.mov_reg(.rdi, r2) } // check if value in rax is zero @@ -2193,7 +2220,13 @@ fn (mut g Gen) convert_int_to_string(r Register, buffer int) { g.println('; jump to label $skip_zero_label') // handle zeros seperately - g.mov_int_to_var(LocalVar{buffer, ast.u8_type_idx, ''}, '0'[0]) + // g.mov_int_to_var(LocalVar{buffer, ast.u8_type_idx, ''}, '0'[0]) + + g.write8(0xc6) + g.write8(0x07) + g.write8(0x30) + g.println("mov BYTE PTR [rdi], '0'") + end_label := g.labels.new_label() end_jmp_addr := g.jmp(0) g.labels.patches << LabelPatch{ @@ -2206,7 +2239,7 @@ fn (mut g Gen) convert_int_to_string(r Register, buffer int) { g.println('; label $skip_zero_label') // load a pointer to the string to rdi - g.lea_var_to_reg(.rdi, buffer) + // g.lea_var_to_reg(.rdi, buffer) // detect if value in rax is negative g.cmp_zero(.rax) @@ -2219,13 +2252,17 @@ fn (mut g Gen) convert_int_to_string(r Register, buffer int) { g.println('; jump to label $skip_minus_label') // add a `-` sign as the first character - g.mov_int_to_var(LocalVar{buffer, ast.u8_type_idx, ''}, '-'[0]) + g.write8(0xc6) + g.write8(0x07) + g.write8(0x2d) + g.println("mov BYTE PTR [rdi], '-'") + g.neg(.rax) // negate our integer to make it positive g.inc(.rdi) // increment rdi to skip the `-` character g.labels.addrs[skip_minus_label] = g.pos() g.println('; label $skip_minus_label') - g.mov_reg(.r12, .rdi) // copy the buffer position to rcx + g.mov_reg(.r12, .rdi) // copy the buffer position to r12 loop_label := g.labels.new_label() loop_start := g.pos() @@ -2266,7 +2303,9 @@ fn (mut g Gen) convert_int_to_string(r Register, buffer int) { g.labels.addrs[loop_label] = loop_start // after all was converted, reverse the string - g.reverse_string(.r12) + reg := g.get_builtin_arg_reg('reverse_string', 0) + g.mov_reg(reg, .r12) + g.call_builtin('reverse_string') g.labels.addrs[end_label] = g.pos() g.println('; label $end_label') diff --git a/vlib/v/gen/native/builtins.v b/vlib/v/gen/native/builtins.v new file mode 100644 index 0000000000..de0c2e4f90 --- /dev/null +++ b/vlib/v/gen/native/builtins.v @@ -0,0 +1,81 @@ +// 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 native + +import term + +struct BuiltinFn { + body fn (builtin BuiltinFn, mut g Gen) + arg_regs []Register +mut: + calls []i64 // call addresses +} + +pub const inline_builtins = ['assert', 'print', 'eprint', 'println', 'eprintln', 'exit', 'C.syscall'] // classic V builtin functios accessible to the user get inlined + +pub fn (mut g Gen) init_builtins() { + g.builtins = { + // longer algorithms and internal functions inaccessible to the user + // used to keep executable size small and the bytecode distraction-free + 'int_to_string': BuiltinFn{ + body: fn (builtin BuiltinFn, mut g Gen) { + g.convert_int_to_string(builtin.arg_regs[0], builtin.arg_regs[1]) + } + arg_regs: [.rcx, .rdi] + } + 'reverse_string': BuiltinFn{ + body: fn (builtin BuiltinFn, mut g Gen) { + g.reverse_string(builtin.arg_regs[0]) + } + arg_regs: [.rdi] + } + } +} + +pub fn (mut g Gen) generate_builtins() { + for name, builtin in g.builtins { + if builtin.calls.len == 0 { // if a builtin does not get called, do not emit it + continue + } + + if g.pref.is_verbose { + println(term.green('\n(builtin) $name:')) + } + + g.stack_var_pos = 0 + call_addr := g.pos() + g.defer_stmts.clear() + g.labels = &LabelTable{} + + if g.pref.arch == .arm64 { + g.n_error('builtins are not implemented for arm64') + } else { + g.builtin_decl_amd64(builtin) + } + + g.patch_labels() + + // patch all call addresses where this builtin gets called + for call in builtin.calls { + rel := g.call_addr_at(int(call_addr), call) + g.write32_at(call + 1, int(rel)) + } + } +} + +pub fn (mut g Gen) get_builtin_arg_reg(name string, index int) Register { + builtin := g.builtins[name] or { panic('undefined builtin function $name') } + if index >= builtin.arg_regs.len { + g.n_error('builtin $name does only have $builtin.arg_regs.len arguments, wanted $index') + } + return builtin.arg_regs[index] +} + +pub fn (mut g Gen) call_builtin(name string) { + if g.pref.arch == .arm64 { + g.n_error('builtin calls are not implemented for amd64') + } else { + g.builtins[name].calls << g.call_builtin_amd64(name) + } +} diff --git a/vlib/v/gen/native/gen.v b/vlib/v/gen/native/gen.v index 6037ffc55c..277b1ce93b 100644 --- a/vlib/v/gen/native/gen.v +++ b/vlib/v/gen/native/gen.v @@ -13,8 +13,6 @@ import v.errors import v.pref import term -pub const builtins = ['assert', 'print', 'eprint', 'println', 'eprintln', 'exit', 'C.syscall'] - interface CodeGen { mut: g &Gen @@ -49,6 +47,7 @@ mut: strs []String labels &LabelTable defer_stmts []ast.DeferStmt + builtins map[string]BuiltinFn // macho specific macho_ncmds int macho_cmdsize int @@ -194,6 +193,7 @@ pub fn gen(files []&ast.File, table &ast.Table, out_name string, pref &pref.Pref } g.code_gen.g = g g.generate_header() + g.init_builtins() for file in files { /* if file.warnings.len > 0 { @@ -205,6 +205,7 @@ pub fn gen(files []&ast.File, table &ast.Table, out_name string, pref &pref.Pref } g.stmts(file.stmts) } + g.generate_builtins() g.generate_footer() return g.nlines, g.buf.len } @@ -428,11 +429,26 @@ fn (mut g Gen) gen_typeof_expr(it ast.TypeOf, newline bool) { g.learel(.rax, g.allocate_string('$r$nl', 3, .rel32)) } +fn (mut g Gen) call_fn(node ast.CallExpr) { + if g.pref.arch == .arm64 { + g.call_fn_arm64(node) + } else { + g.call_fn_amd64(node) + } +} + fn (mut g Gen) gen_var_to_string(reg Register, var Var, config VarConfig) { - buffer := g.allocate_array('itoa-buffer', 1, 32) // 32 characters should be enough - g.mov_var_to_reg(reg, var, config) - g.convert_int_to_string(reg, buffer) - g.lea_var_to_reg(reg, buffer) + if config.typ.is_int() || config.typ == 0 { // TODO: fix config.typ == 0 + buffer := g.allocate_array('itoa-buffer', 1, 32) // 32 characters should be enough + + g.mov_var_to_reg(g.get_builtin_arg_reg('int_to_string', 0), var, config) + g.lea_var_to_reg(g.get_builtin_arg_reg('int_to_string', 1), buffer) + g.call_builtin('int_to_string') + + g.lea_var_to_reg(reg, buffer) + } else { + g.n_error('int-to-string conversion only implemented for integer types, got $config.typ') + } } pub fn (mut g Gen) gen_print_from_expr(expr ast.Expr, name string) {