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'
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{
line: '$vexe -b js -o hw.js examples/hello_world.v'
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(0xc1)
}
.rdx {
g.write8(0x48)
g.write8(0xc7)
g.write8(0xc2)
g.write32(i32(int(val)))
g.println('mov32 $reg, $val')
return
}
.rbx {
g.write8(0x48)
g.write8(0xc7)
@ -427,7 +435,8 @@ pub fn (mut g Gen) allocate_string(s string, opsize int) int {
g.strings << s
str_pos := g.buf.len + opsize
g.str_pos << str_pos
return 0
g.strs << String{s, str_pos}
return str_pos
}
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()
}
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) {
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'
//
@ -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')
}
}
if g.pref.os == .windows {
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) {
@ -569,10 +629,15 @@ fn (mut g Gen) mov(reg Register, val int) {
return
}
.rcx {
if val == -1 {
g.write8(0x48)
g.write8(0xc7)
g.write8(0xc1)
g.write32(-1)
} else {
g.write8(0xff)
g.write8(0xff) // mov rcx 0xffff5
}
return
}
else {
@ -629,7 +694,16 @@ fn (mut g Gen) mov(reg Register, val int) {
g.write8(0xbf)
}
.rcx {
g.write8(0x48)
g.write8(0xc7)
g.write8(0xc1)
}
.r8 {
g.write8(0x41)
g.write8(0xb8)
}
.r9 {
g.write8(0xb9)
}
.rdx, .edx {
g.write8(0xba)
@ -738,6 +812,14 @@ fn (mut g Gen) mov_reg(a Register, b Register) {
g.write8(0x48)
g.write8(0x89)
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 {
g.write8(0x48)
g.write8(0x89)
@ -1076,7 +1158,11 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) {
fn (mut g Gen) trap() {
// funnily works on x86 and arm64
if g.pref.arch == .arm64 {
g.write32(0xcccccccc)
} else {
g.write8(0xcc)
}
g.println('trap')
}

View File

@ -47,6 +47,12 @@ mut:
size_pos []int
nlines int
callpatches []CallPatch
strs []String
}
struct String {
str string
pos int
}
struct CallPatch {
@ -176,6 +182,12 @@ pub fn (g &Gen) pos() i64 {
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) {
// write 1 byte
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 {
begin := g.buf.len
g.zeroes(1)
at := i64(0x100000000)
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.write8(0)
}

View File

@ -1,9 +1,16 @@
module native
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 {
i386 = 0x14c
amd64 = 0x8664
@ -18,14 +25,14 @@ enum PeHeader {
pub fn (mut g Gen) write_dos_header() {
dos_header := [
int(PeHeader.mz),
0x90, // usedbytesinthelastpage
3, // filesizeinpages
0, // numofrelocs
2, // header size in paragraph
0, // minimum extra paragraphs
-1, // maximum extra paragraphs
0x80, // bytes on last page of file
1, // pages in file
0, // relocations
4, // header size in paragraph
0x10, // minimum extra paragraphs
0xffff, // maximum extra paragraphs
0, // initial relative ss
0xb8, // initial SP
0x140, // initial SP
0, // checksum
0, // IP
0, // IP relative CS
@ -47,14 +54,14 @@ pub fn (mut g Gen) write_dos_header() {
0,
0,
0,
0xf8,
0x80,
0, // address of PE header
]
for b in dos_header {
g.write16(b)
}
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(0x0e) // richend
// TODO: add a stub for DOS/16, we only run on amd64
for g.buf.len < 0xf8 {
g.write8(0) // entries
}
pad_to(mut g.buf, 0x80)
}
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() {
@ -75,7 +119,7 @@ pub fn (mut g Gen) write_pe_header() {
int(PeHeader.pe),
0,
int(PeMachine.amd64), // machine
1, // number of sections
2, // number of sections
0,
0, // timestamp
0,
@ -83,55 +127,59 @@ pub fn (mut g Gen) write_pe_header() {
0, // number of symbols
0,
0xf0, // 40 // size of optional header
int(PeCharacteristics.executable_image), // c
// 0 // optional header magic
int(PeCharacteristics.executable_image),
]
for b in pe_header {
g.write16(b)
}
// optional here comes here
p_opthdr := g.buf.len // should be 0x110
if p_opthdr != 0x110 {
eprintln('Invalid optdr location')
if p_opthdr != 0x98 {
eprintln('Invalid optdr location $p_opthdr != 0x98')
}
g.write16(0x20b) // magic (0x10b=pe32, 0x20b=pe64)
g.write8(0xe) // major linker version
g.write8(0x1d) // minor linker version
g.write32(0x10) // sizeofcode
g.write32(0x10) // initial data size
g.write32(0) // sizeof sizeof uninit data
g.write16(0x20b) // magic (0x10b=pe32, 0x20b=pe32+)
g.write8(0x1) // major linker version
g.write8(0x49) // minor linker version
g.write32(0x200) // sizeofcode
g.write32(0x200) // initial data size
g.write32(0) // sizeof uninit data
image_base := i64(0x140000000)
g.write32(0x1188) // paddr of map // aligned to 4 bytes // entrypoint
g.write32(0x1000) // base of code // aligned to 4 bytes
g.write64(image_base) // image base vaddr // va // aligned to 4 bytes
g.write32(0x2000) // paddr of map // aligned to 4 bytes // entrypoint
g.write32(0x2000) // base of code // aligned to 4 bytes
g.write64(native.image_base) // image base vaddr // va // aligned to 4 bytes
g.write32(0x1000) // SectionAlignment
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) // major 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.write32(0) // win32versionvalue
g.write32(0x1000) // hdrsize + codelen) // sizeofimage
g.write32(0x180) // hdrsize) // sizeofheaders
g.write32(0x3000) // hdrsize + codelen) // sizeofimage
g.write32(0x200) // hdrsize) // sizeofheaders
g.write32(0) // checksum
g.write16(3) // subsystem // subsystem
// g.write16(0x400) // dll characteristics
g.write16(0)
/*
g.write8(0x60) // dll characteristics
g.write8(0x81) // dll characteristics
g.write64(0x100000) // SizeOfStackReserve
*/
g.write64(0x1000) // SizeOfStackReserve
g.write64(0x1000) // SizeOfStackCommit
g.write64(0x100000) // SizeOfHeapReserve
g.write64(0x1000) // SizeOfHeapCommit
g.write64(0x10000) // SizeOfHeapReserve
g.write64(0) // SizeOfHeapCommit
g.write32(0) // LoaderFlags
g.write32(1) // NumberOfRvaAndSizes
g.write32(0x10) // NumberOfRvaAndSizes
g.write32(0)
g.write32(0)
g.write32(0x1000)
g.write32(0x100) // size of code
}
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_stub()
g.write_pe_header()
g.write_pe_sections()
pad_to(mut g.buf, 0x400)
g.code_start_pos = g.buf.len
g.call(0x18e)
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() {
/*
// TODO: when proper code is generated, uncomment this
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.sym_string_table()
pad_to(mut g.buf, 0x600)
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()
}