native: implement a working hello world compilation for w64 (#12577)

pull/12586/head
pancake 2021-11-26 18:03:15 +01:00 committed by GitHub
parent 04b030b7ab
commit 89bab98833
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 236 additions and 63 deletions

View File

@ -71,6 +71,23 @@ fn get_all_commands() []Command {
line: '$vexe run examples/v_script.vsh > /dev/null' line: '$vexe run examples/v_script.vsh > /dev/null'
okmsg: 'V can run the .VSH script file examples/v_script.vsh' okmsg: 'V can run the .VSH script file examples/v_script.vsh'
} }
//
res << Command{
line: '$vexe -os linux -b native -o hw.linux examples/hello_world.v'
okmsg: 'V compiles hello_world.v on the native backend for linux'
rmfile: 'hw.linux'
}
res << Command{
line: '$vexe -os macos -b native -o hw.macos examples/hello_world.v'
okmsg: 'V compiles hello_world.v on the native backend for macos'
rmfile: 'hw.macos'
}
res << Command{
line: '$vexe -os windows -b native -o hw.exe examples/hello_world.v'
okmsg: 'V compiles hello_world.v on the native backend for windows'
rmfile: 'hw.exe'
}
//
res << Command{ res << Command{
line: '$vexe -b js -o hw.js examples/hello_world.v' line: '$vexe -b js -o hw.js examples/hello_world.v'
okmsg: 'V compiles hello_world.v on the JS backend' okmsg: 'V compiles hello_world.v on the JS backend'

View File

@ -204,6 +204,14 @@ fn (mut g Gen) mov64(reg Register, val i64) {
g.write8(0xc7) g.write8(0xc7)
g.write8(0xc1) g.write8(0xc1)
} }
.rdx {
g.write8(0x48)
g.write8(0xc7)
g.write8(0xc2)
g.write32(i32(int(val)))
g.println('mov32 $reg, $val')
return
}
.rbx { .rbx {
g.write8(0x48) g.write8(0x48)
g.write8(0xc7) g.write8(0xc7)
@ -427,7 +435,8 @@ pub fn (mut g Gen) allocate_string(s string, opsize int) int {
g.strings << s g.strings << s
str_pos := g.buf.len + opsize str_pos := g.buf.len + opsize
g.str_pos << str_pos g.str_pos << str_pos
return 0 g.strs << String{s, str_pos}
return str_pos
} }
pub fn (mut g Gen) cld_repne_scasb() { pub fn (mut g Gen) cld_repne_scasb() {
@ -484,7 +493,45 @@ pub fn (mut g Gen) gen_print_reg(r Register, n int, fd int) {
g.syscall() g.syscall()
} }
pub fn (mut g Gen) apicall(s string) {
if g.pref.os != .windows {
g.n_error('apicalls are only for windows')
}
g.write8(0xff)
g.write8(0x15)
delta := match s {
'WriteFile' {
-(0xbcc + g.buf.len)
}
'GetStdHandle' {
-(0xbcc + g.buf.len + 8)
}
'ExitProcess' {
-(0xbcc + g.buf.len + 16)
}
else {
0
}
}
g.write32(delta)
}
pub fn (mut g Gen) gen_print(s string, fd int) { pub fn (mut g Gen) gen_print(s string, fd int) {
if g.pref.os == .windows {
g.sub(.rsp, 0x38)
g.mov(.rcx, -11)
g.apicall('GetStdHandle')
g.mov_reg(.rcx, .rax)
// g.mov64(.rdx, g.allocate_string(s, 3))
g.lea(.rdx, g.allocate_string(s, 3))
g.mov(.r8, s.len) // string length
g.write([byte(0x4c), 0x8d, 0x4c, 0x24, 0x20]) // lea r9, [rsp+0x20]
g.write([byte(0x48), 0xc7, 0x44, 0x24, 0x20])
g.write32(0) // mov qword[rsp+0x20], 0
// g.mov(.r9, rsp+0x20)
g.apicall('WriteFile')
return
}
// //
// qq := s + '\n' // qq := s + '\n'
// //
@ -554,8 +601,21 @@ pub fn (mut g Gen) gen_amd64_exit(expr ast.Expr) {
g.n_error('native builtin exit expects a numeric argument') g.n_error('native builtin exit expects a numeric argument')
} }
} }
g.mov(.eax, g.nsyscall_exit()) if g.pref.os == .windows {
g.syscall() g.mov_reg(.rcx, .rdi)
g.apicall('ExitProcess')
} else {
g.mov(.eax, g.nsyscall_exit())
g.syscall()
}
g.trap() // should never be reached, just in case
}
fn (mut g Gen) lea(reg Register, val int) {
g.write8(0x48)
g.write8(0x8d)
g.write8(0x15)
g.write32(val)
} }
fn (mut g Gen) mov(reg Register, val int) { fn (mut g Gen) mov(reg Register, val int) {
@ -569,10 +629,15 @@ fn (mut g Gen) mov(reg Register, val int) {
return return
} }
.rcx { .rcx {
g.write8(0x48) if val == -1 {
g.write8(0xc7) g.write8(0x48)
g.write8(0xc1) g.write8(0xc7)
g.write32(-1) g.write8(0xc1)
g.write32(-1)
} else {
g.write8(0xff)
g.write8(0xff) // mov rcx 0xffff5
}
return return
} }
else { else {
@ -629,7 +694,16 @@ fn (mut g Gen) mov(reg Register, val int) {
g.write8(0xbf) g.write8(0xbf)
} }
.rcx { .rcx {
g.write8(0x48)
g.write8(0xc7) g.write8(0xc7)
g.write8(0xc1)
}
.r8 {
g.write8(0x41)
g.write8(0xb8)
}
.r9 {
g.write8(0xb9)
} }
.rdx, .edx { .rdx, .edx {
g.write8(0xba) g.write8(0xba)
@ -738,6 +812,14 @@ fn (mut g Gen) mov_reg(a Register, b Register) {
g.write8(0x48) g.write8(0x48)
g.write8(0x89) g.write8(0x89)
g.write8(0xf8) g.write8(0xf8)
} else if a == .rcx && b == .rdi {
g.write8(0x48)
g.write8(0x89)
g.write8(0xf9)
} else if a == .rcx && b == .rax {
g.write8(0x48)
g.write8(0x89)
g.write8(0xc1)
} else if a == .rdi && b == .rsi { } else if a == .rdi && b == .rsi {
g.write8(0x48) g.write8(0x48)
g.write8(0x89) g.write8(0x89)
@ -1076,7 +1158,11 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
fn (mut g Gen) trap() { fn (mut g Gen) trap() {
// funnily works on x86 and arm64 // funnily works on x86 and arm64
g.write32(0xcccccccc) if g.pref.arch == .arm64 {
g.write32(0xcccccccc)
} else {
g.write8(0xcc)
}
g.println('trap') g.println('trap')
} }

View File

@ -47,6 +47,12 @@ mut:
size_pos []int size_pos []int
nlines int nlines int
callpatches []CallPatch callpatches []CallPatch
strs []String
}
struct String {
str string
pos int
} }
struct CallPatch { struct CallPatch {
@ -176,6 +182,12 @@ pub fn (g &Gen) pos() i64 {
return g.buf.len return g.buf.len
} }
fn (mut g Gen) write(bytes []byte) {
for _, b in bytes {
g.buf << b
}
}
fn (mut g Gen) write8(n int) { fn (mut g Gen) write8(n int) {
// write 1 byte // write 1 byte
g.buf << byte(n) g.buf << byte(n)

View File

@ -395,9 +395,14 @@ fn (mut g Gen) write_symbol(s Symbol) {
fn (mut g Gen) sym_string_table() int { fn (mut g Gen) sym_string_table() int {
begin := g.buf.len begin := g.buf.len
g.zeroes(1) g.zeroes(1)
at := i64(0x100000000)
for i, s in g.strings { for i, s in g.strings {
g.write64_at(at + g.buf.len, int(g.str_pos[i])) pos := g.buf.len - int(g.str_pos[i])
if g.pref.os == .windows {
g.write32_at(int(g.str_pos[i]), pos - 4) // 0x402028 + pos)
} else {
baddr := i64(0x100000000)
g.write64_at(g.buf.len + baddr, int(g.str_pos[i]))
}
g.write_string(s) g.write_string(s)
g.write8(0) g.write8(0)
} }

View File

@ -1,9 +1,16 @@
module native module native
enum PeCharacteristics { enum PeCharacteristics {
executable_image = 0x102 // 1: relocation info stripped
// 2: file is executable
// 4: line numbers stripped
// 8: local symbols stripped
// 20: app can handle > 2GB
executable_image = 0x2f
} }
const image_base = i64(0x400000)
enum PeMachine { enum PeMachine {
i386 = 0x14c i386 = 0x14c
amd64 = 0x8664 amd64 = 0x8664
@ -18,14 +25,14 @@ enum PeHeader {
pub fn (mut g Gen) write_dos_header() { pub fn (mut g Gen) write_dos_header() {
dos_header := [ dos_header := [
int(PeHeader.mz), int(PeHeader.mz),
0x90, // usedbytesinthelastpage 0x80, // bytes on last page of file
3, // filesizeinpages 1, // pages in file
0, // numofrelocs 0, // relocations
2, // header size in paragraph 4, // header size in paragraph
0, // minimum extra paragraphs 0x10, // minimum extra paragraphs
-1, // maximum extra paragraphs 0xffff, // maximum extra paragraphs
0, // initial relative ss 0, // initial relative ss
0xb8, // initial SP 0x140, // initial SP
0, // checksum 0, // checksum
0, // IP 0, // IP
0, // IP relative CS 0, // IP relative CS
@ -47,14 +54,14 @@ pub fn (mut g Gen) write_dos_header() {
0, 0,
0, 0,
0, 0,
0xf8, 0x80,
0, // address of PE header 0, // address of PE header
] ]
for b in dos_header { for b in dos_header {
g.write16(b) g.write16(b)
} }
if g.buf.len != 0x40 { if g.buf.len != 0x40 {
// g.warning('Invalid dos header size') g.n_error('Invalid dos header size')
} }
} }
@ -65,9 +72,46 @@ pub fn (mut g Gen) write_dos_stub() {
g.write8(0xba) // richend g.write8(0xba) // richend
g.write8(0x0e) // richend g.write8(0x0e) // richend
// TODO: add a stub for DOS/16, we only run on amd64 // TODO: add a stub for DOS/16, we only run on amd64
for g.buf.len < 0xf8 { pad_to(mut g.buf, 0x80)
g.write8(0) // entries }
}
fn (mut g Gen) write_pe_sections() {
pad_to(mut g.buf, 0x180)
g.write64(0)
g.write_string_with_padding('.idata', 8)
g.write32(0x89) // 137
g.write16(0x1000)
g.write32(0x02000000)
g.write32(0x02000000)
g.zeroes(14)
g.write16(64)
g.write8(0)
g.write8(192)
g.write_string_with_padding('.text', 8)
g.write32(75)
g.write8(0)
g.write32(0x20)
g.write32(0x00002)
g.write32(4)
g.write32(0)
g.write32(0)
g.write32(0x20000000) // 0, 0, 0, 32,
g.write([byte(0), 0, 96])
g.zeroes(52)
g.write([byte(72), 16, 0, 0])
g.write([byte(40), 16, 0, 0])
g.zeroes(20)
g.write([byte(96), 16, 0, 0])
g.write32(0)
g.write([byte(110), 16, 0, 0])
g.write32(0)
g.write([byte(125), 16, 0, 0])
g.zeroes(12)
g.write_string_with_padding('KERNEL32.DLL', 13)
g.write_string_with_padding('USER32.DLL', 13)
g.write_string_with_padding('ExitProcess', 14)
g.write_string_with_padding('GetStdHandle', 15)
g.write_string_with_padding('WriteFile', 13)
} }
pub fn (mut g Gen) write_pe_header() { pub fn (mut g Gen) write_pe_header() {
@ -75,7 +119,7 @@ pub fn (mut g Gen) write_pe_header() {
int(PeHeader.pe), int(PeHeader.pe),
0, 0,
int(PeMachine.amd64), // machine int(PeMachine.amd64), // machine
1, // number of sections 2, // number of sections
0, 0,
0, // timestamp 0, // timestamp
0, 0,
@ -83,55 +127,59 @@ pub fn (mut g Gen) write_pe_header() {
0, // number of symbols 0, // number of symbols
0, 0,
0xf0, // 40 // size of optional header 0xf0, // 40 // size of optional header
int(PeCharacteristics.executable_image), // c int(PeCharacteristics.executable_image),
// 0 // optional header magic
] ]
for b in pe_header { for b in pe_header {
g.write16(b) g.write16(b)
} }
// optional here comes here // optional here comes here
p_opthdr := g.buf.len // should be 0x110 p_opthdr := g.buf.len // should be 0x110
if p_opthdr != 0x110 { if p_opthdr != 0x98 {
eprintln('Invalid optdr location') eprintln('Invalid optdr location $p_opthdr != 0x98')
} }
g.write16(0x20b) // magic (0x10b=pe32, 0x20b=pe64) g.write16(0x20b) // magic (0x10b=pe32, 0x20b=pe32+)
g.write8(0xe) // major linker version g.write8(0x1) // major linker version
g.write8(0x1d) // minor linker version g.write8(0x49) // minor linker version
g.write32(0x10) // sizeofcode g.write32(0x200) // sizeofcode
g.write32(0x10) // initial data size g.write32(0x200) // initial data size
g.write32(0) // sizeof sizeof uninit data g.write32(0) // sizeof uninit data
image_base := i64(0x140000000) g.write32(0x2000) // paddr of map // aligned to 4 bytes // entrypoint
g.write32(0x1188) // paddr of map // aligned to 4 bytes // entrypoint g.write32(0x2000) // base of code // aligned to 4 bytes
g.write32(0x1000) // base of code // aligned to 4 bytes g.write64(native.image_base) // image base vaddr // va // aligned to 4 bytes
g.write64(image_base) // image base vaddr // va // aligned to 4 bytes
g.write32(0x1000) // SectionAlignment g.write32(0x1000) // SectionAlignment
g.write32(0x200) // FileAlignment g.write32(0x200) // FileAlignment
g.write16(6) // Major OS Version g.write16(1) // Major OS Version
g.write16(0) // Minor OS Version g.write16(0) // Minor OS Version
g.write16(0) // major image version g.write16(0) // major image version
g.write16(0) // minor image version g.write16(0) // minor image version
g.write16(6) // major subsystem version g.write16(5) // major subsystem version
g.write16(0) // minor subsystem version g.write16(0) // minor subsystem version
g.write32(0) // win32versionvalue g.write32(0) // win32versionvalue
g.write32(0x1000) // hdrsize + codelen) // sizeofimage g.write32(0x3000) // hdrsize + codelen) // sizeofimage
g.write32(0x180) // hdrsize) // sizeofheaders g.write32(0x200) // hdrsize) // sizeofheaders
g.write32(0) // checksum g.write32(0) // checksum
g.write16(3) // subsystem // subsystem g.write16(3) // subsystem // subsystem
// g.write16(0x400) // dll characteristics // g.write16(0x400) // dll characteristics
g.write16(0)
/*
g.write8(0x60) // dll characteristics g.write8(0x60) // dll characteristics
g.write8(0x81) // dll characteristics g.write8(0x81) // dll characteristics
g.write64(0x100000) // SizeOfStackReserve */
g.write64(0x1000) // SizeOfStackReserve
g.write64(0x1000) // SizeOfStackCommit g.write64(0x1000) // SizeOfStackCommit
g.write64(0x100000) // SizeOfHeapReserve g.write64(0x10000) // SizeOfHeapReserve
g.write64(0x1000) // SizeOfHeapCommit g.write64(0) // SizeOfHeapCommit
g.write32(0) // LoaderFlags g.write32(0) // LoaderFlags
g.write32(1) // NumberOfRvaAndSizes g.write32(0x10) // NumberOfRvaAndSizes
g.write32(0) g.write32(0)
g.write32(0) g.write32(0)
g.write32(0x1000)
g.write32(0x100) // size of code
} }
fn (mut g Gen) write_pe_section() { fn (mut g Gen) write_pe_section() {
@ -155,30 +203,35 @@ pub fn (mut g Gen) generate_pe_header() {
g.write_dos_header() g.write_dos_header()
g.write_dos_stub() g.write_dos_stub()
g.write_pe_header() g.write_pe_header()
g.write_pe_sections()
pad_to(mut g.buf, 0x400)
g.code_start_pos = g.buf.len g.code_start_pos = g.buf.len
g.call(0x18e) g.call(0x18e)
g.ret() g.ret()
g.main_fn_addr = g.buf.len
}
fn pad_to(mut buf []byte, len int) {
for buf.len < len {
buf << byte(0)
}
} }
pub fn (mut g Gen) generate_pe_footer() { pub fn (mut g Gen) generate_pe_footer() {
/* g.sym_string_table()
// TODO: when proper code is generated, uncomment this pad_to(mut g.buf, 0x600)
codesize := g.buf.len - g.code_start_pos
delta := int(g.code_start_pos) // header_size
// patch the size depending on the codesize
for o in g.size_pos {
n := g.read32_at(o)
g.write32_at(o, n + delta)
}
*/
for g.buf.len < 0x200 {
g.write8(0) // entries
}
g.write_pe_section()
g.file_size_pos = g.buf.len g.file_size_pos = g.buf.len
g.main_fn_addr = g.buf.len
g.code_start_pos = g.buf.len // patch call main
if g.pref.arch == .arm64 {
bl_next := u32(0x94000001)
g.write32_at(g.code_start_pos, int(bl_next))
} else {
// +1 is for "e8"
// -5 is for "e8 00 00 00 00"
g.write32_at(g.code_start_pos + 1, int(g.main_fn_addr - g.code_start_pos) - 5)
}
g.create_executable() g.create_executable()
} }