v/vlib/v/gen/native/gen.v

970 lines
20 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 native
import os
import strings
import v.ast
import v.util
import v.mathutil as mu
import v.token
import v.errors
import v.pref
import term
pub const builtins = ['assert', 'print', 'eprint', 'println', 'eprintln', 'exit', 'C.syscall']
interface CodeGen {
mut:
g &Gen
gen_exit(mut g Gen, expr ast.Expr)
// XXX WHY gen_exit fn (expr ast.Expr)
}
[heap; minify]
pub struct Gen {
out_name string
pref &pref.Preferences // Preferences shared from V struct
mut:
code_gen CodeGen
table &ast.Table
buf []u8
sect_header_name_pos int
offset i64
stackframe_size int
file_size_pos i64
main_fn_addr i64
code_start_pos i64 // location of the start of the assembly instructions
fn_addr map[string]i64
var_offset map[string]int // local var stack offset
stack_var_pos int
debug_pos int
errors []errors.Error
warnings []errors.Warning
syms []Symbol
size_pos []int
nlines int
callpatches []CallPatch
strs []String
labels &LabelTable
defer_stmts []ast.DeferStmt
// macho specific
macho_ncmds int
macho_cmdsize int
}
enum RelocType {
rel8
rel16
rel32
rel64
abs64
}
struct String {
str string
pos int
typ RelocType
}
struct CallPatch {
name string
pos int
}
struct LabelTable {
mut:
label_id int
addrs []i64 = [i64(0)] // register address of label here
patches []LabelPatch // push placeholders
branches []BranchLabel
}
struct LabelPatch {
id int
pos int
}
struct BranchLabel {
name string
start int
end int
}
fn (mut l LabelTable) new_label() int {
l.label_id++
l.addrs << 0
return l.label_id
}
enum Size {
_8
_16
_32
_64
}
fn get_backend(arch pref.Arch) ?CodeGen {
match arch {
.arm64 {
return Arm64{
g: 0
}
}
.amd64 {
return Amd64{
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')
}
pub fn gen(files []&ast.File, table &ast.Table, out_name string, pref &pref.Preferences) (int, int) {
exe_name := if pref.os == .windows && !out_name.ends_with('.exe') {
out_name + '.exe'
} else {
out_name
}
mut g := &Gen{
table: table
sect_header_name_pos: 0
out_name: exe_name
pref: pref
// 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`.')
exit(1)
}
labels: 0
}
g.code_gen.g = g
g.generate_header()
for file in files {
/*
if file.warnings.len > 0 {
eprintln('warning: ${file.warnings[0]}')
}
*/
if file.errors.len > 0 {
g.n_error(file.errors[0].str())
}
g.stmts(file.stmts)
}
g.generate_footer()
return g.nlines, g.buf.len
}
pub fn (mut g Gen) typ(a int) &ast.TypeSymbol {
return g.table.type_symbols[a]
}
pub fn (mut g Gen) generate_header() {
match g.pref.os {
.macos {
g.generate_macho_header()
}
.windows {
g.generate_pe_header()
}
.linux {
g.generate_elf_header()
}
.raw {
if g.pref.arch == .arm64 {
g.gen_arm64_helloworld()
}
}
else {
g.n_error('only `raw`, `linux` and `macos` are supported for -os in -native')
}
}
}
pub fn (mut g Gen) create_executable() {
// Create the binary // should be .o ?
os.write_file_array(g.out_name, g.buf) or { panic(err) }
os.chmod(g.out_name, 0o775) or { panic(err) } // make it executable
if g.pref.is_verbose {
eprintln('\n$g.out_name: native binary has been successfully generated')
}
}
pub fn (mut g Gen) generate_footer() {
g.patch_calls()
match g.pref.os {
.macos {
g.generate_macho_footer()
}
.windows {
g.generate_pe_footer()
}
.linux {
g.generate_elf_footer()
}
.raw {
g.create_executable()
}
else {
eprintln('Unsupported target file format')
exit(1)
}
}
}
pub fn (mut g Gen) stmts(stmts []ast.Stmt) {
for stmt in stmts {
g.stmt(stmt)
}
}
pub fn (g &Gen) pos() i64 {
return g.buf.len
}
fn (mut g Gen) write(bytes []u8) {
for _, b in bytes {
g.buf << b
}
}
fn (mut g Gen) write8(n int) {
// write 1 byte
g.buf << u8(n)
}
fn (mut g Gen) write16(n int) {
// write 2 bytes
g.buf << u8(n)
g.buf << u8(n >> 8)
}
fn (mut g Gen) read32_at(at int) int {
return int(u32(g.buf[at]) | (u32(g.buf[at + 1]) << 8) | (u32(g.buf[at + 2]) << 16) | (u32(g.buf[
at + 3]) << 24))
}
fn (mut g Gen) write32(n int) {
// write 4 bytes
g.buf << u8(n)
g.buf << u8(n >> 8)
g.buf << u8(n >> 16)
g.buf << u8(n >> 24)
}
fn (mut g Gen) write64(n i64) {
// write 8 bytes
g.buf << u8(n)
g.buf << u8(n >> 8)
g.buf << u8(n >> 16)
g.buf << u8(n >> 24)
g.buf << u8(n >> 32)
g.buf << u8(n >> 40)
g.buf << u8(n >> 48)
g.buf << u8(n >> 56)
}
fn (mut g Gen) write64_at(at i64, n i64) {
// write 8 bytes
g.buf[at] = u8(n)
g.buf[at + 1] = u8(n >> 8)
g.buf[at + 2] = u8(n >> 16)
g.buf[at + 3] = u8(n >> 24)
g.buf[at + 4] = u8(n >> 32)
g.buf[at + 5] = u8(n >> 40)
g.buf[at + 6] = u8(n >> 48)
g.buf[at + 7] = u8(n >> 56)
}
fn (mut g Gen) write32_at(at i64, n int) {
// write 4 bytes
g.buf[at] = u8(n)
g.buf[at + 1] = u8(n >> 8)
g.buf[at + 2] = u8(n >> 16)
g.buf[at + 3] = u8(n >> 24)
}
fn (mut g Gen) write16_at(at i64, n int) {
// write 2 bytes
g.buf[at] = u8(n)
g.buf[at + 1] = u8(n >> 8)
}
fn (mut g Gen) write_string(s string) {
for c in s {
g.write8(int(c))
}
g.zeroes(1)
}
fn (mut g Gen) write_string_with_padding(s string, max int) {
for c in s {
g.write8(int(c))
}
for _ in 0 .. max - s.len {
g.write8(0)
}
}
fn (mut g Gen) try_var_offset(var_name string) int {
offset := g.var_offset[var_name] or { return -1 }
if offset == 0 {
return -1
}
return offset
}
fn (mut g Gen) get_var_offset(var_name string) int {
r := g.try_var_offset(var_name)
if r == -1 {
g.n_error('unknown variable `$var_name`')
}
return r
}
fn (mut g Gen) gen_typeof_expr(it ast.TypeOf, newline bool) {
nl := if newline { '\n' } else { '' }
r := g.typ(it.expr_type).name
g.learel(.rax, g.allocate_string('$r$nl', 3, .rel32))
}
fn (mut g Gen) gen_var_to_string(reg Register, vo int) {
buffer := g.allocate_array('itoa-buffer', 1, 32) // 32 characters should be enough
g.mov_var_to_reg(reg, vo)
g.convert_int_to_string(reg, buffer)
g.lea_var_to_reg(reg, buffer)
}
pub fn (mut g Gen) gen_print_from_expr(expr ast.Expr, name string) {
newline := name in ['println', 'eprintln']
fd := if name in ['eprint', 'eprintln'] { 2 } else { 1 }
match expr {
ast.StringLiteral {
if newline {
g.gen_print(expr.val + '\n', fd)
} else {
g.gen_print(expr.val, fd)
}
}
ast.CallExpr {
g.call_fn(expr)
g.gen_print_reg(.rax, 3, fd)
}
ast.Ident {
vo := g.try_var_offset(expr.name)
if vo != -1 {
g.gen_var_to_string(.rax, vo)
g.gen_print_reg(.rax, 3, fd)
if newline {
g.gen_print('\n', fd)
}
} else {
g.gen_print_reg(.rax, 3, fd)
}
}
ast.IntegerLiteral {
g.learel(.rax, g.allocate_string('$expr.val\n', 3, .rel32))
g.gen_print_reg(.rax, 3, fd)
}
ast.BoolLiteral {
// register 'true' and 'false' strings // g.expr(expr)
// XXX mov64 shuoldnt be used for addressing
if expr.val {
g.learel(.rax, g.allocate_string('true', 3, .rel32))
} else {
g.learel(.rax, g.allocate_string('false', 3, .rel32))
}
g.gen_print_reg(.rax, 3, fd)
}
ast.SizeOf {}
ast.OffsetOf {
styp := g.typ(expr.struct_type)
field_name := expr.field
if styp.kind == .struct_ {
s := styp.info as ast.Struct
ptrsz := 4 // should be 8, but for locals is used 8 and C backend shows that too
mut off := 0
for f in s.fields {
if f.name == field_name {
g.learel(.rax, g.allocate_string('$off\n', 3, .rel32))
g.gen_print_reg(.rax, 3, fd)
break
}
off += ptrsz
}
} else {
g.v_error('_offsetof expects a struct Type as first argument', expr.pos)
}
}
ast.None {}
ast.EmptyExpr {
g.n_error('unhandled EmptyExpr')
}
ast.PostfixExpr {}
ast.PrefixExpr {}
ast.SelectorExpr {
// struct.field
g.expr(expr)
g.gen_print_reg(.rax, 3, fd)
/*
field_name := expr.field_name
g.expr
if expr.is_mut {
// mutable field access (rw)
}
*/
dump(expr)
g.v_error('struct.field selector not yet implemented for this backend', expr.pos)
}
ast.NodeError {}
/*
ast.AnonFn {}
ast.ArrayDecompose {}
ast.ArrayInit {}
ast.AsCast {}
ast.Assoc {}
ast.AtExpr {}
ast.CTempVar {}
ast.CastExpr {}
ast.ChanInit {}
ast.CharLiteral {}
ast.Comment {}
ast.ComptimeCall {}
ast.ComptimeSelector {}
ast.ConcatExpr {}
ast.DumpExpr {}
ast.EnumVal {}
ast.GoExpr {}
ast.IfGuardExpr {}
ast.IndexExpr {}
ast.InfixExpr {}
ast.IsRefType {}
ast.MapInit {}
ast.MatchExpr {}
ast.OrExpr {}
ast.ParExpr {}
ast.RangeExpr {}
ast.SelectExpr {}
ast.SqlExpr {}
ast.TypeNode {}
*/
ast.TypeOf {
g.gen_typeof_expr(expr, newline)
}
ast.LockExpr {
// passthru
eprintln('Warning: locks not implemented yet in the native backend')
g.expr(expr)
}
ast.Likely {
// passthru
g.expr(expr)
}
ast.UnsafeExpr {
// passthru
g.expr(expr)
}
ast.StringInterLiteral {
g.n_error('Interlaced string literals are not yet supported in the native backend.') // , expr.pos)
}
else {
dump(typeof(expr).name)
dump(expr)
// g.v_error('expected string as argument for print', expr.pos)
g.n_error('expected string as argument for print') // , expr.pos)
}
}
}
fn (mut g Gen) fn_decl(node ast.FnDecl) {
if g.pref.is_verbose {
println(term.green('\n$node.name:'))
}
if node.is_deprecated {
g.warning('fn_decl: $node.name is deprecated', node.pos)
}
if node.is_builtin {
g.warning('fn_decl: $node.name is builtin', node.pos)
}
g.stack_var_pos = 0
g.register_function_address(node.name)
g.labels = &LabelTable{}
g.defer_stmts.clear()
if g.pref.arch == .arm64 {
g.fn_decl_arm64(node)
} else {
g.fn_decl_amd64(node)
}
g.patch_labels()
}
pub fn (mut g Gen) register_function_address(name string) {
if name == 'main.main' {
g.main_fn_addr = i64(g.buf.len)
} else {
g.fn_addr[name] = g.pos()
}
}
fn (mut g Gen) println(comment string) {
g.nlines++
if !g.pref.is_verbose {
return
}
addr := g.debug_pos.hex()
mut sb := strings.new_builder(80)
// println('$g.debug_pos "$addr"')
sb.write_string(term.red(strings.repeat(`0`, 6 - addr.len) + addr + ' '))
for i := g.debug_pos; i < g.pos(); i++ {
s := g.buf[i].hex()
if s.len == 1 {
sb.write_string(term.blue('0'))
}
gbihex := g.buf[i].hex()
hexstr := term.blue(gbihex) + ' '
sb.write_string(hexstr)
}
g.debug_pos = g.buf.len
//
colored := sb.str()
plain := term.strip_ansi(colored)
padding := ' '.repeat(mu.max(1, 40 - plain.len))
final := '$colored$padding$comment'
println(final)
}
fn (mut g Gen) gen_forc_stmt(node ast.ForCStmt) {
if node.has_init {
g.stmts([node.init])
}
start := g.pos()
start_label := g.labels.new_label()
mut jump_addr := i64(0)
if node.has_cond {
cond := node.cond
match cond {
ast.InfixExpr {
// g.infix_expr(node.cond)
match cond.left {
ast.Ident {
lit := cond.right as ast.IntegerLiteral
g.cmp_var(cond.left.name, lit.val.int())
match cond.op {
.gt {
jump_addr = g.cjmp(.jle)
}
.lt {
jump_addr = g.cjmp(.jge)
}
else {
g.n_error('unsupported conditional in for-c loop')
}
}
}
else {
g.n_error('unhandled infix.left')
}
}
}
else {}
}
// dump(node.cond)
g.expr(node.cond)
}
end_label := g.labels.new_label()
g.labels.patches << LabelPatch{
id: end_label
pos: int(jump_addr)
}
g.println('; jump to label $end_label')
g.labels.branches << BranchLabel{
name: node.label
start: start_label
end: end_label
}
g.stmts(node.stmts)
g.labels.addrs[start_label] = g.pos()
g.println('; label $start_label')
if node.has_inc {
g.stmts([node.inc])
}
g.labels.branches.pop()
g.jmp(int(0xffffffff - (g.pos() + 5 - start) + 1))
g.labels.addrs[end_label] = g.pos()
g.println('; jump to label $end_label')
// loop back
}
fn (mut g Gen) for_in_stmt(node ast.ForInStmt) {
if node.stmts.len == 0 {
// if no statements, just dont make it
return
}
if node.is_range {
// for a in node.cond .. node.high {
i := g.allocate_var(node.val_var, 8, 0) // iterator variable
g.expr(node.cond)
g.mov_reg_to_var(i, .rax) // i = node.cond // initial value
start := g.pos() // label-begin:
start_label := g.labels.new_label()
g.mov_var_to_reg(.rbx, i) // rbx = iterator value
g.expr(node.high) // final value
g.cmp_reg(.rbx, .rax) // rbx = iterator, rax = max value
jump_addr := g.cjmp(.jge) // leave loop if i is beyond end
end_label := g.labels.new_label()
g.labels.patches << LabelPatch{
id: end_label
pos: jump_addr
}
g.println('; jump to label $end_label')
g.labels.branches << BranchLabel{
name: node.label
start: start_label
end: end_label
}
g.stmts(node.stmts)
g.labels.addrs[start_label] = g.pos()
g.println('; label $start_label')
g.inc_var(node.val_var)
g.labels.branches.pop()
g.jmp(int(0xffffffff - (g.pos() + 5 - start) + 1))
g.labels.addrs[end_label] = g.pos()
g.println('; label $end_label')
/*
} else if node.kind == .array {
} else if node.kind == .array_fixed {
} else if node.kind == .map {
} else if node.kind == .string {
} else if node.kind == .struct_ {
} else if it.kind in [.array, .string] || it.cond_type.has_flag(.variadic) {
} else if it.kind == .map {
*/
} else {
g.v_error('for-in statement is not yet implemented', node.pos)
}
}
pub fn (mut g Gen) gen_exit(node ast.Expr) {
// check node type and then call the code_gen method
g.code_gen.gen_exit(mut g, node)
}
fn (mut g Gen) stmt(node ast.Stmt) {
match node {
ast.AssignStmt {
g.assign_stmt(node)
}
ast.Block {
g.stmts(node.stmts)
}
ast.BranchStmt {
label_name := node.label
for i := g.labels.branches.len - 1; i >= 0; i-- {
branch := g.labels.branches[i]
if label_name == '' || label_name == branch.name {
label := if node.kind == .key_break {
branch.end
} else { // continue
branch.start
}
jump_addr := g.jmp(0)
g.labels.patches << LabelPatch{
id: label
pos: jump_addr
}
g.println('; jump to $label: $node.kind')
break
}
}
}
ast.ConstDecl {}
ast.DeferStmt {
defer_var := g.get_var_offset('_defer$g.defer_stmts.len')
g.mov_int_to_var(defer_var, ._8, 1)
g.defer_stmts << node
g.defer_stmts[g.defer_stmts.len - 1].idx_in_fn = g.defer_stmts.len - 1
}
ast.ExprStmt {
g.expr(node.expr)
}
ast.FnDecl {
g.fn_decl(node)
}
ast.ForCStmt {
g.gen_forc_stmt(node)
}
ast.ForInStmt {
g.for_in_stmt(node)
}
ast.ForStmt {
g.for_stmt(node)
}
ast.HashStmt {
words := node.val.split(' ')
for word in words {
if word.len != 2 {
g.n_error('opcodes format: xx xx xx xx')
}
b := unsafe { C.strtol(&char(word.str), 0, 16) }
// b := word.u8()
// println('"$word" $b')
g.write8(b)
}
}
ast.Module {}
ast.Return {
// dump(node.exprs[0])
// if in main
// zero := ast.IntegerLiteral{}
// g.gen_exit(zero)
// dump(node)
// dump(node.types)
mut s := '?' //${node.exprs[0].val.str()}'
// TODO: void return
e0 := node.exprs[0]
match e0 {
ast.IntegerLiteral {
g.mov64(.rax, e0.val.int())
}
ast.InfixExpr {
g.infix_expr(e0)
}
ast.CastExpr {
g.mov64(.rax, e0.expr.str().int())
// do the job
}
ast.StringLiteral {
s = e0.val.str()
g.expr(node.exprs[0])
g.mov64(.rax, g.allocate_string(s, 2, .abs64))
}
ast.Ident {
g.expr(e0)
}
else {
g.n_error('unknown return type $e0.type_name()')
}
}
// jump to return label
label := 0
pos := g.jmp(0)
g.labels.patches << LabelPatch{
id: label
pos: pos
}
g.println('; jump to label $label')
}
ast.AsmStmt {
g.gen_asm_stmt(node)
}
ast.AssertStmt {
g.gen_assert(node)
}
ast.Import {} // do nothing here
ast.StructDecl {}
else {
eprintln('native.stmt(): bad node: ' + node.type_name())
}
}
}
fn C.strtol(str &char, endptr &&char, base int) int
fn (mut g Gen) gen_syscall(node ast.CallExpr) {
mut i := 0
mut ra := [Register.rax, .rdi, .rsi, .rdx]
for i < node.args.len {
expr := node.args[i].expr
if i >= ra.len {
g.warning('Too many arguments for syscall', node.pos)
return
}
match expr {
ast.IntegerLiteral {
g.mov(ra[i], expr.val.int())
}
ast.BoolLiteral {
g.mov(ra[i], if expr.val { 1 } else { 0 })
}
ast.SelectorExpr {
mut done := false
if expr.field_name == 'str' {
match expr.expr {
ast.StringLiteral {
s := expr.expr.val.replace('\\n', '\n')
g.allocate_string(s, 2, .abs64)
g.mov64(ra[i], 1)
done = true
}
else {}
}
}
if !done {
g.v_error('Unknown selector in syscall argument type $expr', node.pos)
}
}
ast.StringLiteral {
if expr.language != .c {
g.warning('C.syscall expects c"string" or "string".str, C backend will crash',
node.pos)
}
s := expr.val.replace('\\n', '\n')
g.allocate_string(s, 2, .abs64)
g.mov64(ra[i], 1)
}
else {
g.v_error('Unknown syscall $expr.type_name() argument type $expr', node.pos)
return
}
}
i++
}
g.syscall()
}
fn (mut g Gen) expr(node ast.Expr) {
match node {
ast.ParExpr {
g.expr(node.expr)
}
ast.ArrayInit {
g.n_error('array init expr not supported yet')
}
ast.BoolLiteral {
g.mov64(.rax, if node.val { 1 } else { 0 })
eprintln('bool literal')
}
ast.CallExpr {
if node.name == 'C.syscall' {
g.gen_syscall(node)
} else if node.name == 'exit' {
g.gen_exit(node.args[0].expr)
} else if node.name in ['println', 'print', 'eprintln', 'eprint'] {
expr := node.args[0].expr
g.gen_print_from_expr(expr, node.name)
} else {
g.call_fn(node)
}
}
ast.FloatLiteral {}
ast.Ident {
offset := g.try_var_offset(node.obj.name) // i := 0
if offset == -1 {
g.n_error('invalid ident $node.obj.name')
}
// offset := g.get_var_offset(node.name)
// XXX this is intel specific
g.mov_var_to_reg(.rax, offset)
}
ast.IfExpr {
if node.is_comptime {
eprintln('Warning: ignored compile time conditional not yet supported for the native backend.')
} else {
g.if_expr(node)
}
}
ast.InfixExpr {
g.infix_expr(node)
// get variable by name
// save the result in rax
}
ast.IntegerLiteral {
g.mov64(.rax, node.val.int())
// g.gen_print_reg(.rax, 3, fd)
}
ast.PostfixExpr {
g.postfix_expr(node)
}
ast.StringLiteral {
g.allocate_string(node.val, 3, .rel32)
}
ast.StructInit {}
ast.GoExpr {
g.v_error('native backend doesnt support threads yet', node.pos)
}
else {
g.n_error('expr: unhandled node type: $node.type_name()')
}
}
}
/*
fn (mut g Gen) allocate_var(name string, size int, initial_val int) {
g.code_gen.allocate_var(name, size, initial_val)
}
*/
fn (mut g Gen) postfix_expr(node ast.PostfixExpr) {
if node.expr !is ast.Ident {
return
}
ident := node.expr as ast.Ident
var_name := ident.name
match node.op {
.inc {
g.inc_var(var_name)
}
.dec {
g.dec_var(var_name)
}
else {}
}
}
[noreturn]
pub fn (mut g Gen) n_error(s string) {
util.verror('native error', s)
}
pub fn (mut g Gen) warning(s string, pos token.Pos) {
if g.pref.output_mode == .stdout {
werror := util.formatted_error('warning', s, g.pref.path, pos)
eprintln(werror)
} else {
g.warnings << errors.Warning{
file_path: g.pref.path
pos: pos
reporter: .gen
message: s
}
}
}
pub fn (mut g Gen) v_error(s string, pos token.Pos) {
// TODO: store a file index in the Position too,
// so that the file path can be retrieved from the pos, instead
// of guessed from the pref.path ...
mut kind := 'error:'
if g.pref.output_mode == .stdout {
ferror := util.formatted_error(kind, s, g.pref.path, pos)
eprintln(ferror)
exit(1)
} else {
g.errors << errors.Error{
file_path: g.pref.path
pos: pos
reporter: .gen
message: s
}
}
}