Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2026 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 x64
|
||||
|
||||
pub enum X64Abi {
|
||||
sysv
|
||||
windows
|
||||
}
|
||||
|
||||
const x64_no_arg_reg = -1
|
||||
|
||||
fn (abi X64Abi) int_arg_regs() []int {
|
||||
return match abi {
|
||||
.sysv { [int(rdi), int(rsi), int(rdx), int(rcx), int(r8), int(r9)] }
|
||||
.windows { [int(rcx), int(rdx), int(r8), int(r9)] }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) sret_arg_regs() []int {
|
||||
return match abi {
|
||||
.sysv { [int(rsi), int(rdx), int(rcx), int(r8), int(r9)] }
|
||||
.windows { [int(rdx), int(r8), int(r9)] }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) sret_reg() Reg {
|
||||
return match abi {
|
||||
.sysv { rdi }
|
||||
.windows { rcx }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) float_arg_regs() []int {
|
||||
return match abi {
|
||||
.sysv { [0, 1, 2, 3, 4, 5, 6, 7] }
|
||||
.windows { [0, 1, 2, 3] }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) uses_positional_arg_regs() bool {
|
||||
return abi == .windows
|
||||
}
|
||||
|
||||
fn (abi X64Abi) int_arg_reg_at(index int) int {
|
||||
regs := abi.int_arg_regs()
|
||||
if index < 0 || index >= regs.len {
|
||||
return x64_no_arg_reg
|
||||
}
|
||||
return regs[index]
|
||||
}
|
||||
|
||||
fn (abi X64Abi) float_arg_reg_at(index int) int {
|
||||
regs := abi.float_arg_regs()
|
||||
if index < 0 || index >= regs.len {
|
||||
return x64_no_arg_reg
|
||||
}
|
||||
return regs[index]
|
||||
}
|
||||
|
||||
fn (abi X64Abi) int_arg_reg_for_position(arg_idx int) int {
|
||||
return match abi {
|
||||
.sysv { abi.int_arg_reg_at(arg_idx) }
|
||||
.windows { abi.int_arg_reg_at(arg_idx) }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) float_arg_reg_for_position(arg_idx int) int {
|
||||
return match abi {
|
||||
.sysv { abi.float_arg_reg_at(arg_idx) }
|
||||
.windows { abi.float_arg_reg_at(arg_idx) }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) shadow_space_size() int {
|
||||
return match abi {
|
||||
.sysv { 0 }
|
||||
.windows { 32 }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) stack_arg_offset(position int) int {
|
||||
return match abi {
|
||||
.sysv { 16 + position * 8 }
|
||||
.windows { 48 + (position - 4) * 8 }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) call_stack_arg_offset(position int) int {
|
||||
return match abi {
|
||||
.sysv { position * 8 }
|
||||
.windows { abi.shadow_space_size() + (position - 4) * 8 }
|
||||
}
|
||||
}
|
||||
|
||||
fn (abi X64Abi) call_frame_size(stack_slots int) int {
|
||||
base := abi.shadow_space_size() + stack_slots * 8
|
||||
if base % 16 == 0 {
|
||||
return base
|
||||
}
|
||||
return base + (16 - (base % 16))
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,257 @@
|
||||
// Copyright (c) 2026 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 x64
|
||||
|
||||
import os
|
||||
|
||||
const coff_image_file_machine_amd64 = u16(0x8664)
|
||||
const coff_image_sym_class_external = u8(2)
|
||||
const coff_image_sym_class_static = u8(3)
|
||||
const coff_image_sym_dtype_function = u16(0x20)
|
||||
|
||||
const coff_image_rel_amd64_rel32 = u16(0x0004)
|
||||
|
||||
const coff_image_scn_cnt_code = u32(0x00000020)
|
||||
const coff_image_scn_cnt_initialized_data = u32(0x00000040)
|
||||
const coff_image_scn_align_8bytes = u32(0x00400000)
|
||||
const coff_image_scn_align_16bytes = u32(0x00500000)
|
||||
const coff_image_scn_mem_execute = u32(0x20000000)
|
||||
const coff_image_scn_mem_read = u32(0x40000000)
|
||||
const coff_image_scn_mem_write = u32(0x80000000)
|
||||
|
||||
pub struct CoffObject {
|
||||
pub mut:
|
||||
text_data []u8
|
||||
data_data []u8
|
||||
rodata []u8
|
||||
|
||||
symbols []CoffSymbol
|
||||
str_table []u8
|
||||
sym_by_name map[string]int
|
||||
|
||||
text_relocs []CoffRelocation
|
||||
}
|
||||
|
||||
struct CoffSymbol {
|
||||
mut:
|
||||
name string
|
||||
name_off int
|
||||
value u32
|
||||
section i16
|
||||
type_ u16
|
||||
storage_class u8
|
||||
}
|
||||
|
||||
struct CoffRelocation {
|
||||
offset u32
|
||||
sym_idx int
|
||||
type_ u16
|
||||
}
|
||||
|
||||
struct CoffSection {
|
||||
name string
|
||||
data []u8
|
||||
relocs []CoffRelocation
|
||||
characteristics u32
|
||||
}
|
||||
|
||||
pub fn CoffObject.new() &CoffObject {
|
||||
return &CoffObject{}
|
||||
}
|
||||
|
||||
pub fn (mut c CoffObject) add_symbol(name string, value u64, is_func bool, section u8) int {
|
||||
type_ := if is_func { coff_image_sym_dtype_function } else { u16(0) }
|
||||
storage_class := coff_symbol_storage_class(name, section)
|
||||
if i := c.sym_by_name[name] {
|
||||
mut s := &c.symbols[i]
|
||||
s.value = u32(value)
|
||||
s.section = i16(section)
|
||||
s.type_ = type_
|
||||
s.storage_class = storage_class
|
||||
return i
|
||||
}
|
||||
|
||||
idx := c.symbols.len
|
||||
c.symbols << CoffSymbol{
|
||||
name: name
|
||||
name_off: c.add_string_if_needed(name)
|
||||
value: u32(value)
|
||||
section: i16(section)
|
||||
type_: type_
|
||||
storage_class: storage_class
|
||||
}
|
||||
c.sym_by_name[name] = idx
|
||||
return idx
|
||||
}
|
||||
|
||||
pub fn (mut c CoffObject) add_undefined(name string) int {
|
||||
if i := c.sym_by_name[name] {
|
||||
return i
|
||||
}
|
||||
|
||||
idx := c.symbols.len
|
||||
c.symbols << CoffSymbol{
|
||||
name: name
|
||||
name_off: c.add_string_if_needed(name)
|
||||
value: 0
|
||||
section: 0
|
||||
type_: 0
|
||||
storage_class: coff_image_sym_class_external
|
||||
}
|
||||
c.sym_by_name[name] = idx
|
||||
return idx
|
||||
}
|
||||
|
||||
fn coff_symbol_storage_class(name string, section u8) u8 {
|
||||
if section != 0 && name.starts_with('L_') {
|
||||
return coff_image_sym_class_static
|
||||
}
|
||||
return coff_image_sym_class_external
|
||||
}
|
||||
|
||||
pub fn (mut c CoffObject) add_text_reloc(offset int, sym_idx int, type_ u16) {
|
||||
c.text_relocs << CoffRelocation{
|
||||
offset: u32(offset)
|
||||
sym_idx: sym_idx
|
||||
type_: type_
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut c CoffObject) add_string_if_needed(s string) int {
|
||||
if s.len <= 8 {
|
||||
return 0
|
||||
}
|
||||
off := 4 + c.str_table.len
|
||||
c.str_table << s.bytes()
|
||||
c.str_table << 0
|
||||
return off
|
||||
}
|
||||
|
||||
pub fn (mut c CoffObject) write(path string) {
|
||||
sections := [
|
||||
CoffSection{
|
||||
name: '.text'
|
||||
data: c.text_data
|
||||
relocs: c.text_relocs
|
||||
characteristics: coff_image_scn_cnt_code | coff_image_scn_mem_execute | coff_image_scn_mem_read | coff_image_scn_align_16bytes
|
||||
},
|
||||
CoffSection{
|
||||
name: '.rdata'
|
||||
data: c.rodata
|
||||
characteristics: coff_image_scn_cnt_initialized_data | coff_image_scn_mem_read | coff_image_scn_align_8bytes
|
||||
},
|
||||
CoffSection{
|
||||
name: '.data'
|
||||
data: c.data_data
|
||||
characteristics: coff_image_scn_cnt_initialized_data | coff_image_scn_mem_read | coff_image_scn_mem_write | coff_image_scn_align_8bytes
|
||||
},
|
||||
]
|
||||
|
||||
header_size := 20
|
||||
section_header_size := 40
|
||||
reloc_entry_size := 10
|
||||
mut current_offset := header_size + (sections.len * section_header_size)
|
||||
|
||||
mut raw_offsets := []int{len: sections.len}
|
||||
for i, section in sections {
|
||||
if section.data.len > 0 {
|
||||
current_offset = align_int(current_offset, 4)
|
||||
raw_offsets[i] = current_offset
|
||||
current_offset += section.data.len
|
||||
}
|
||||
}
|
||||
|
||||
mut reloc_offsets := []int{len: sections.len}
|
||||
for i, section in sections {
|
||||
if section.relocs.len > 0 {
|
||||
current_offset = align_int(current_offset, 4)
|
||||
reloc_offsets[i] = current_offset
|
||||
current_offset += section.relocs.len * reloc_entry_size
|
||||
}
|
||||
}
|
||||
|
||||
symbol_table_off := align_int(current_offset, 4)
|
||||
string_table_size := 4 + c.str_table.len
|
||||
|
||||
mut buf := []u8{}
|
||||
write_u16_le(mut buf, coff_image_file_machine_amd64)
|
||||
write_u16_le(mut buf, u16(sections.len))
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, u32(symbol_table_off))
|
||||
write_u32_le(mut buf, u32(c.symbols.len))
|
||||
write_u16_le(mut buf, 0)
|
||||
write_u16_le(mut buf, 0)
|
||||
|
||||
for i, section in sections {
|
||||
write_fixed_string(mut buf, section.name, 8)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, u32(section.data.len))
|
||||
write_u32_le(mut buf, u32(raw_offsets[i]))
|
||||
write_u32_le(mut buf, u32(reloc_offsets[i]))
|
||||
write_u32_le(mut buf, 0)
|
||||
nrelocs := coff_relocation_count_for_header(section) or { panic(err) }
|
||||
write_u16_le(mut buf, nrelocs)
|
||||
write_u16_le(mut buf, 0)
|
||||
write_u32_le(mut buf, section.characteristics)
|
||||
}
|
||||
|
||||
for i, section in sections {
|
||||
if raw_offsets[i] == 0 {
|
||||
continue
|
||||
}
|
||||
for buf.len < raw_offsets[i] {
|
||||
buf << 0
|
||||
}
|
||||
buf << section.data
|
||||
}
|
||||
|
||||
for i, section in sections {
|
||||
if reloc_offsets[i] == 0 {
|
||||
continue
|
||||
}
|
||||
for buf.len < reloc_offsets[i] {
|
||||
buf << 0
|
||||
}
|
||||
for reloc in section.relocs {
|
||||
write_u32_le(mut buf, reloc.offset)
|
||||
write_u32_le(mut buf, u32(reloc.sym_idx))
|
||||
write_u16_le(mut buf, reloc.type_)
|
||||
}
|
||||
}
|
||||
|
||||
for buf.len < symbol_table_off {
|
||||
buf << 0
|
||||
}
|
||||
for sym in c.symbols {
|
||||
c.write_symbol_name(mut buf, sym)
|
||||
write_u32_le(mut buf, sym.value)
|
||||
write_u16_le(mut buf, u16(sym.section))
|
||||
write_u16_le(mut buf, sym.type_)
|
||||
buf << sym.storage_class
|
||||
buf << 0
|
||||
}
|
||||
|
||||
write_u32_le(mut buf, u32(string_table_size))
|
||||
buf << c.str_table
|
||||
|
||||
os.write_file_array(path, buf) or { panic(err) }
|
||||
}
|
||||
|
||||
fn coff_relocation_count_for_header(section CoffSection) !u16 {
|
||||
if section.relocs.len > 0xffff {
|
||||
return error('COFF section ${section.name} has ${section.relocs.len} relocations; extended relocations are not supported')
|
||||
}
|
||||
return u16(section.relocs.len)
|
||||
}
|
||||
|
||||
fn (c CoffObject) write_symbol_name(mut buf []u8, sym CoffSymbol) {
|
||||
if sym.name.len <= 8 {
|
||||
write_fixed_string(mut buf, sym.name, 8)
|
||||
return
|
||||
}
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, u32(sym.name_off))
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2026 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 x64
|
||||
|
||||
const x64_backend_feature_prefix = 'backend feature: '
|
||||
pub const x64_backend_limitation_hint = 'x64: native backend limitation: retry with the C backend or reduce the source to supported x64 features'
|
||||
|
||||
const x64_minimal_unsupported_crt_stdio_symbols = ['stdin', 'stdout', 'stderr', 'printf', 'fprintf',
|
||||
'dprintf', 'sprintf', 'snprintf', 'wprintf', 'puts', 'fputs', 'setvbuf', 'fflush', 'fopen',
|
||||
'_wfopen', 'fdopen', 'freopen', '_wfreopen', 'fclose', 'pclose', '_pclose', 'fread', 'fwrite',
|
||||
'fgets', 'getc', 'feof', 'ferror', 'fseek', 'ftell', 'rewind', 'fileno', '_fileno',
|
||||
'_get_osfhandle', '_open_osfhandle', 'fgetpos']
|
||||
|
||||
const x64_unsupported_captured_fn_literal_symbol = 'v2_unsupported_captured_fn_literal'
|
||||
const x64_missing_v_runtime_helpers = ['builtin__Map_string_int__keys']
|
||||
|
||||
fn x64_normalize_backend_feature(feature string) string {
|
||||
if feature.starts_with(x64_backend_feature_prefix) {
|
||||
return feature[x64_backend_feature_prefix.len..]
|
||||
}
|
||||
return feature
|
||||
}
|
||||
|
||||
fn x64_unsupported_backend_feature_message(feature string) string {
|
||||
return 'x64: unsupported backend feature: ${x64_normalize_backend_feature(feature)}'
|
||||
}
|
||||
|
||||
fn x64_unsupported(message string) {
|
||||
x64_abort_with_diagnostic(x64_unsupported_backend_feature_message(message))
|
||||
}
|
||||
|
||||
fn x64_abort_with_diagnostic(message string) {
|
||||
eprintln(message)
|
||||
eprintln(x64_backend_limitation_hint)
|
||||
exit(1)
|
||||
}
|
||||
|
||||
fn x64_linker_name(format ObjectFormat) string {
|
||||
return match format {
|
||||
.elf { 'ELF linker' }
|
||||
.macho { 'Mach-O linker' }
|
||||
.coff { 'PE linker' }
|
||||
}
|
||||
}
|
||||
|
||||
fn x64_unresolved_external_symbol_message(format ObjectFormat, name string, context string) string {
|
||||
linker_name := x64_linker_name(format)
|
||||
if format == .coff && name in x64_minimal_unsupported_crt_stdio_symbols {
|
||||
return x64_unsupported_backend_feature_message('${linker_name} cannot resolve C stdio/file-descriptor symbol `${name}`: Windows x64 native backend uses Kernel32 handles, not C FILE/stdio calls; ${context}')
|
||||
}
|
||||
if name == x64_unsupported_captured_fn_literal_symbol {
|
||||
return x64_unsupported_backend_feature_message('native x64 cannot lower captured function literal: closure environments are not implemented yet; ${context}')
|
||||
}
|
||||
if name in x64_missing_v_runtime_helpers {
|
||||
return x64_unsupported_backend_feature_message('${linker_name} cannot resolve V runtime helper `${name}`: native x64 backend does not implement this feature for this target yet; ${context}')
|
||||
}
|
||||
return x64_unsupported_backend_feature_message('${linker_name} cannot resolve external symbol `${name}` yet; ${context}')
|
||||
}
|
||||
|
||||
pub fn unsupported_external_symbol_message_for_name(format ObjectFormat, raw_name string, context string) ?string {
|
||||
name := x64_normalize_external_symbol_name(format, raw_name)
|
||||
if x64_symbol_needs_backend_runtime_support(name) {
|
||||
return x64_unresolved_external_symbol_message(format, name, context)
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
fn x64_normalize_external_symbol_name(format ObjectFormat, name string) string {
|
||||
if format == .macho && name.starts_with('_') {
|
||||
return name[1..]
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
fn x64_symbol_needs_backend_runtime_support(name string) bool {
|
||||
return name == x64_unsupported_captured_fn_literal_symbol
|
||||
|| name in x64_missing_v_runtime_helpers
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
// Copyright (c) 2026 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 x64
|
||||
|
||||
import os
|
||||
|
||||
// ELF64 Constants
|
||||
const ei_mag0 = 0x7f
|
||||
const ei_mag1 = 0x45 // 'E'
|
||||
const ei_mag2 = 0x4c // 'L'
|
||||
const ei_mag3 = 0x46 // 'F'
|
||||
|
||||
const elfclass64 = 2
|
||||
const elfdata2lsb = 1
|
||||
const ev_current = 1
|
||||
const et_rel = 1 // Relocatable file
|
||||
const et_exec = 2 // Executable file
|
||||
const em_x86_64 = 62
|
||||
|
||||
const pt_load = 1
|
||||
|
||||
const pf_x = 0x1
|
||||
const pf_w = 0x2
|
||||
const pf_r = 0x4
|
||||
|
||||
const sht_progbits = 1
|
||||
const sht_symtab = 2
|
||||
const sht_strtab = 3
|
||||
const sht_rela = 4
|
||||
const sht_nobits = 8
|
||||
|
||||
const shf_write = 0x1
|
||||
const shf_alloc = 0x2
|
||||
const shf_execinstr = 0x4
|
||||
|
||||
// Relocation Types for x86_64
|
||||
const r_x86_64_64 = 1
|
||||
const r_x86_64_pc32 = 2
|
||||
const r_x86_64_plt32 = 4
|
||||
|
||||
pub struct ElfObject {
|
||||
pub mut:
|
||||
text_data []u8
|
||||
data_data []u8
|
||||
rodata []u8
|
||||
|
||||
// Symbol Table
|
||||
symbols []ElfSymbol
|
||||
str_table []u8
|
||||
|
||||
// Relocations
|
||||
text_relocs []ElfRela
|
||||
|
||||
// Section Names
|
||||
shstr_table []u8
|
||||
}
|
||||
|
||||
struct ElfSymbol {
|
||||
pub mut:
|
||||
name string
|
||||
name_idx int
|
||||
info u8
|
||||
shndx u16
|
||||
value u64
|
||||
size u64
|
||||
}
|
||||
|
||||
struct ElfRela {
|
||||
offset u64
|
||||
info u64
|
||||
addend i64
|
||||
}
|
||||
|
||||
pub fn ElfObject.new() &ElfObject {
|
||||
mut e := &ElfObject{
|
||||
str_table: [u8(0)] // Starts with null byte
|
||||
shstr_table: [u8(0)]
|
||||
}
|
||||
// Add null symbol
|
||||
e.symbols << ElfSymbol{
|
||||
name: ''
|
||||
shndx: 0
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
pub fn (mut e ElfObject) add_symbol(name string, value u64, is_func bool, shndx u16) int {
|
||||
// STB_GLOBAL (1) << 4 | STT_FUNC (2) or STT_OBJECT (1) or STT_NOTYPE (0)
|
||||
type_ := if is_func { u8(2) } else { u8(1) } // Func : Object
|
||||
bind := u8(1) // Global
|
||||
info := (bind << 4) | type_
|
||||
|
||||
for i := 1; i < e.symbols.len; i++ {
|
||||
if e.symbols[i].name != name {
|
||||
continue
|
||||
}
|
||||
mut s := &e.symbols[i]
|
||||
s.info = info
|
||||
s.shndx = shndx
|
||||
s.value = value
|
||||
return i
|
||||
}
|
||||
|
||||
idx := e.symbols.len
|
||||
name_off := e.add_string(name)
|
||||
|
||||
e.symbols << ElfSymbol{
|
||||
name: name
|
||||
name_idx: name_off
|
||||
info: info
|
||||
shndx: shndx
|
||||
value: value
|
||||
}
|
||||
return idx
|
||||
}
|
||||
|
||||
pub fn (mut e ElfObject) add_undefined(name string) int {
|
||||
for i := 1; i < e.symbols.len; i++ {
|
||||
if e.symbols[i].name == name {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
idx := e.symbols.len
|
||||
name_off := e.add_string(name)
|
||||
// Bind Global (1), Type NoType (0)
|
||||
info := u8(0x10)
|
||||
|
||||
e.symbols << ElfSymbol{
|
||||
name: name
|
||||
name_idx: name_off
|
||||
info: info
|
||||
shndx: 0 // Undefined
|
||||
value: 0
|
||||
}
|
||||
return idx
|
||||
}
|
||||
|
||||
pub fn (mut e ElfObject) add_text_reloc(offset u64, sym_idx int, type_ int, addend i64) {
|
||||
// info = (sym_idx << 32) | type
|
||||
info := (u64(sym_idx) << 32) | u64(type_)
|
||||
e.text_relocs << ElfRela{
|
||||
offset: offset
|
||||
info: info
|
||||
addend: addend
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut e ElfObject) add_string(s string) int {
|
||||
off := e.str_table.len
|
||||
e.str_table << s.bytes()
|
||||
e.str_table << 0
|
||||
return off
|
||||
}
|
||||
|
||||
fn (mut e ElfObject) add_sh_string(s string) int {
|
||||
off := e.shstr_table.len
|
||||
e.shstr_table << s.bytes()
|
||||
e.shstr_table << 0
|
||||
return off
|
||||
}
|
||||
|
||||
pub fn (mut e ElfObject) write(path string) {
|
||||
mut buf := []u8{}
|
||||
|
||||
// --- 1. Prepare Sections ---
|
||||
// Indices:
|
||||
// 0: Null
|
||||
// 1: .text
|
||||
// 2: .data
|
||||
// 3: .rodata (strings)
|
||||
// 4: .symtab
|
||||
// 5: .strtab
|
||||
// 6: .rela.text
|
||||
// 7: .shstrtab
|
||||
|
||||
// Prepare section names in .shstrtab
|
||||
off_text_name := e.add_sh_string('.text')
|
||||
off_data_name := e.add_sh_string('.data')
|
||||
off_rodata_name := e.add_sh_string('.rodata')
|
||||
off_symtab_name := e.add_sh_string('.symtab')
|
||||
off_strtab_name := e.add_sh_string('.strtab')
|
||||
off_rela_text_name := e.add_sh_string('.rela.text')
|
||||
off_shstrtab_name := e.add_sh_string('.shstrtab')
|
||||
|
||||
// Calculate Offsets
|
||||
ehdr_size := 64
|
||||
shdr_entry_size := 64
|
||||
num_sections := 8
|
||||
|
||||
mut current_offset := ehdr_size
|
||||
|
||||
// .text (Align 16)
|
||||
if current_offset % 16 != 0 {
|
||||
current_offset += (16 - (current_offset % 16))
|
||||
}
|
||||
off_text := current_offset
|
||||
current_offset += e.text_data.len
|
||||
|
||||
// .data (Align 8)
|
||||
if current_offset % 8 != 0 {
|
||||
current_offset += (8 - (current_offset % 8))
|
||||
}
|
||||
off_data := current_offset
|
||||
current_offset += e.data_data.len
|
||||
|
||||
// .rodata (Align 4)
|
||||
if current_offset % 4 != 0 {
|
||||
current_offset += (4 - (current_offset % 4))
|
||||
}
|
||||
off_rodata := current_offset
|
||||
current_offset += e.rodata.len
|
||||
|
||||
// .symtab (Align 8)
|
||||
if current_offset % 8 != 0 {
|
||||
current_offset += (8 - (current_offset % 8))
|
||||
}
|
||||
off_symtab := current_offset
|
||||
size_symtab := e.symbols.len * 24
|
||||
current_offset += size_symtab
|
||||
|
||||
// .strtab (Align 1)
|
||||
off_strtab := current_offset
|
||||
current_offset += e.str_table.len
|
||||
|
||||
// .rela.text (Align 8)
|
||||
if current_offset % 8 != 0 {
|
||||
current_offset += (8 - (current_offset % 8))
|
||||
}
|
||||
off_rela_text := current_offset
|
||||
size_rela_text := e.text_relocs.len * 24
|
||||
current_offset += size_rela_text
|
||||
|
||||
// .shstrtab (Align 1)
|
||||
off_shstrtab := current_offset
|
||||
current_offset += e.shstr_table.len
|
||||
|
||||
// Section Headers (Align 8)
|
||||
if current_offset % 8 != 0 {
|
||||
current_offset += (8 - (current_offset % 8))
|
||||
}
|
||||
off_shdrs := current_offset
|
||||
|
||||
// --- 2. Write ELF Header ---
|
||||
buf << u8(ei_mag0)
|
||||
buf << u8(ei_mag1)
|
||||
buf << u8(ei_mag2)
|
||||
buf << u8(ei_mag3)
|
||||
buf << u8(elfclass64)
|
||||
buf << u8(elfdata2lsb)
|
||||
buf << u8(ev_current)
|
||||
buf << u8(0) // ABI System V
|
||||
buf << u8(0) // ABI Version
|
||||
for _ in 0 .. 7 {
|
||||
buf << 0
|
||||
} // Pad
|
||||
|
||||
write_u16_le(mut buf, et_rel)
|
||||
write_u16_le(mut buf, em_x86_64)
|
||||
write_u32_le(mut buf, ev_current)
|
||||
write_u64_le(mut buf, 0) // Entry
|
||||
write_u64_le(mut buf, 0) // Phdr off
|
||||
write_u64_le(mut buf, u64(off_shdrs))
|
||||
write_u32_le(mut buf, 0) // Flags
|
||||
write_u16_le(mut buf, u16(ehdr_size))
|
||||
write_u16_le(mut buf, 0) // Phdr entry size
|
||||
write_u16_le(mut buf, 0) // Phdr num
|
||||
write_u16_le(mut buf, u16(shdr_entry_size))
|
||||
write_u16_le(mut buf, u16(num_sections))
|
||||
write_u16_le(mut buf, 7) // Shstrndx
|
||||
|
||||
// --- 3. Write Data with Padding ---
|
||||
|
||||
// Pad to Text
|
||||
for buf.len < off_text {
|
||||
buf << 0
|
||||
}
|
||||
buf << e.text_data
|
||||
|
||||
// Pad to Data
|
||||
for buf.len < off_data {
|
||||
buf << 0
|
||||
}
|
||||
buf << e.data_data
|
||||
|
||||
// Pad to Rodata
|
||||
for buf.len < off_rodata {
|
||||
buf << 0
|
||||
}
|
||||
buf << e.rodata
|
||||
|
||||
// Pad to Symtab
|
||||
for buf.len < off_symtab {
|
||||
buf << 0
|
||||
}
|
||||
for sym in e.symbols {
|
||||
write_u32_le(mut buf, u32(sym.name_idx))
|
||||
buf << sym.info
|
||||
buf << 0 // other
|
||||
write_u16_le(mut buf, sym.shndx)
|
||||
write_u64_le(mut buf, sym.value)
|
||||
write_u64_le(mut buf, sym.size)
|
||||
}
|
||||
|
||||
// Strtab
|
||||
for buf.len < off_strtab {
|
||||
buf << 0
|
||||
}
|
||||
buf << e.str_table
|
||||
|
||||
// Rela Text
|
||||
for buf.len < off_rela_text {
|
||||
buf << 0
|
||||
}
|
||||
for r in e.text_relocs {
|
||||
write_u64_le(mut buf, r.offset)
|
||||
write_u64_le(mut buf, r.info)
|
||||
write_u64_le(mut buf, u64(r.addend))
|
||||
}
|
||||
|
||||
// Shstrtab
|
||||
for buf.len < off_shstrtab {
|
||||
buf << 0
|
||||
}
|
||||
buf << e.shstr_table
|
||||
|
||||
// --- 4. Write Section Headers ---
|
||||
for buf.len < off_shdrs {
|
||||
buf << 0
|
||||
}
|
||||
|
||||
// 0: Null
|
||||
write_shdr(mut buf, 0, 0, 0, 0, 0, 0, 0, 0, 0)
|
||||
|
||||
// 1: .text
|
||||
write_shdr(mut buf, u32(off_text_name), sht_progbits, shf_alloc | shf_execinstr, u64(off_text),
|
||||
u64(e.text_data.len), 0, 0, 16, 0)
|
||||
|
||||
// 2: .data
|
||||
write_shdr(mut buf, u32(off_data_name), sht_progbits, shf_alloc | shf_write, u64(off_data),
|
||||
u64(e.data_data.len), 0, 0, 8, 0)
|
||||
|
||||
// 3: .rodata
|
||||
write_shdr(mut buf, u32(off_rodata_name), sht_progbits, shf_alloc, u64(off_rodata),
|
||||
u64(e.rodata.len), 0, 0, 4, 0)
|
||||
|
||||
// 4: .symtab (EntSize = 24)
|
||||
mut first_global := 1
|
||||
for i, s in e.symbols {
|
||||
if (s.info >> 4) == 1 { // STB_GLOBAL
|
||||
first_global = i
|
||||
break
|
||||
}
|
||||
}
|
||||
write_shdr(mut buf, u32(off_symtab_name), sht_symtab, 0, u64(off_symtab), u64(size_symtab), 5,
|
||||
u32(first_global), 8, 24)
|
||||
|
||||
// 5: .strtab
|
||||
write_shdr(mut buf, u32(off_strtab_name), sht_strtab, 0, u64(off_strtab), u64(e.str_table.len),
|
||||
0, 0, 1, 0)
|
||||
|
||||
// 6: .rela.text (EntSize = 24)
|
||||
write_shdr(mut buf, u32(off_rela_text_name), sht_rela, 0, u64(off_rela_text),
|
||||
u64(size_rela_text), 4, 1, 8, 24)
|
||||
|
||||
// 7: .shstrtab
|
||||
write_shdr(mut buf, u32(off_shstrtab_name), sht_strtab, 0, u64(off_shstrtab),
|
||||
u64(e.shstr_table.len), 0, 0, 1, 0)
|
||||
|
||||
os.write_file_array(path, buf) or { panic(err) }
|
||||
}
|
||||
|
||||
fn write_shdr(mut b []u8, name u32, type_ u32, flags u64, off u64, size u64, link u32, info u32, align u64, entsize u64) {
|
||||
write_u32_le(mut b, name)
|
||||
write_u32_le(mut b, type_)
|
||||
write_u64_le(mut b, flags)
|
||||
write_u64_le(mut b, 0) // Addr
|
||||
write_u64_le(mut b, off)
|
||||
write_u64_le(mut b, size)
|
||||
write_u32_le(mut b, link)
|
||||
write_u32_le(mut b, info)
|
||||
write_u64_le(mut b, align)
|
||||
write_u64_le(mut b, entsize)
|
||||
}
|
||||
|
||||
fn write_u32_le(mut b []u8, v u32) {
|
||||
b << u8(v)
|
||||
b << u8(v >> 8)
|
||||
b << u8(v >> 16)
|
||||
b << u8(v >> 24)
|
||||
}
|
||||
|
||||
fn write_u64_le(mut b []u8, v u64) {
|
||||
b << u8(v)
|
||||
b << u8(v >> 8)
|
||||
b << u8(v >> 16)
|
||||
b << u8(v >> 24)
|
||||
b << u8(v >> 32)
|
||||
b << u8(v >> 40)
|
||||
b << u8(v >> 48)
|
||||
b << u8(v >> 56)
|
||||
}
|
||||
|
||||
fn write_u16_le(mut b []u8, v u16) {
|
||||
b << u8(v)
|
||||
b << u8(v >> 8)
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,284 @@
|
||||
// Copyright (c) 2026 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 x64
|
||||
|
||||
import os
|
||||
|
||||
const macho_mh_magic_64 = u32(0xfeedfacf)
|
||||
const macho_cpu_type_x86_64 = 0x01000007
|
||||
const macho_cpu_subtype_x86_64_all = 3
|
||||
const macho_mh_object = 1
|
||||
const macho_lc_segment_64 = 0x19
|
||||
const macho_lc_symtab = 0x2
|
||||
|
||||
const x86_64_reloc_signed = 1
|
||||
const x86_64_reloc_branch = 2
|
||||
const x86_64_reloc_got_load = 3
|
||||
const x86_64_reloc_got = 4
|
||||
|
||||
pub struct MachOObject {
|
||||
pub mut:
|
||||
text_data []u8
|
||||
data_data []u8
|
||||
rodata []u8
|
||||
|
||||
relocs []MachORelocationInfo
|
||||
symbols []MachOSymbol
|
||||
str_table []u8
|
||||
sym_by_name map[string]int
|
||||
}
|
||||
|
||||
struct MachORelocationInfo {
|
||||
addr int
|
||||
sym_idx int
|
||||
pcrel bool
|
||||
length int
|
||||
extern bool
|
||||
type_ int
|
||||
}
|
||||
|
||||
struct MachOSymbol {
|
||||
mut:
|
||||
name string
|
||||
type_ u8
|
||||
sect u8
|
||||
desc u16
|
||||
value u64
|
||||
name_off int
|
||||
}
|
||||
|
||||
pub fn MachOObject.new() &MachOObject {
|
||||
return &MachOObject{
|
||||
str_table: [u8(0)]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut m MachOObject) add_symbol(name string, value u64, is_ext bool, sect u8) int {
|
||||
typ := if is_ext { u8(0x0f) } else { u8(0x0e) }
|
||||
if i := m.sym_by_name[name] {
|
||||
mut s := &m.symbols[i]
|
||||
s.type_ = typ
|
||||
s.sect = sect
|
||||
s.value = value
|
||||
return i
|
||||
}
|
||||
|
||||
idx := m.symbols.len
|
||||
name_off := m.add_string(name)
|
||||
m.symbols << MachOSymbol{
|
||||
name: name
|
||||
type_: typ
|
||||
sect: sect
|
||||
desc: 0
|
||||
value: value
|
||||
name_off: name_off
|
||||
}
|
||||
m.sym_by_name[name] = idx
|
||||
return idx
|
||||
}
|
||||
|
||||
pub fn (mut m MachOObject) add_undefined(name string) int {
|
||||
if i := m.sym_by_name[name] {
|
||||
return i
|
||||
}
|
||||
|
||||
idx := m.symbols.len
|
||||
name_off := m.add_string(name)
|
||||
m.symbols << MachOSymbol{
|
||||
name: name
|
||||
type_: 0x01 // N_UNDF | N_EXT
|
||||
sect: 0
|
||||
desc: 0
|
||||
value: 0
|
||||
name_off: name_off
|
||||
}
|
||||
m.sym_by_name[name] = idx
|
||||
return idx
|
||||
}
|
||||
|
||||
pub fn (mut m MachOObject) add_reloc(addr int, sym_idx int, typ int, pcrel bool, length int) {
|
||||
m.relocs << MachORelocationInfo{
|
||||
addr: addr
|
||||
sym_idx: sym_idx
|
||||
type_: typ
|
||||
pcrel: pcrel
|
||||
length: length
|
||||
extern: true
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut m MachOObject) add_string(s string) int {
|
||||
off := m.str_table.len
|
||||
m.str_table << s.bytes()
|
||||
m.str_table << 0
|
||||
return off
|
||||
}
|
||||
|
||||
pub fn (mut m MachOObject) write(path string) {
|
||||
mut buf := []u8{}
|
||||
|
||||
n_sects := 3
|
||||
header_size := 32
|
||||
seg_cmd_size := 72 + (80 * n_sects)
|
||||
symtab_cmd_size := 24
|
||||
load_cmds_size := seg_cmd_size + symtab_cmd_size
|
||||
|
||||
text_off := align_int(header_size + load_cmds_size, 16)
|
||||
text_len := m.text_data.len
|
||||
rodata_off := align_int(text_off + text_len, 8)
|
||||
rodata_len := m.rodata.len
|
||||
data_off := align_int(rodata_off + rodata_len, 8)
|
||||
data_len := m.data_data.len
|
||||
|
||||
text_addr := 0
|
||||
rodata_addr := align_int(text_addr + text_len, 8)
|
||||
data_addr := align_int(rodata_addr + rodata_len, 8)
|
||||
total_size := data_addr + data_len
|
||||
|
||||
reloc_off := align_int(data_off + data_len, 8)
|
||||
sym_off := align_int(reloc_off + (m.relocs.len * 8), 8)
|
||||
sym_len := m.symbols.len * 16
|
||||
str_off := sym_off + sym_len
|
||||
str_size := m.str_table.len
|
||||
|
||||
write_u32_le(mut buf, macho_mh_magic_64)
|
||||
write_u32_le(mut buf, u32(macho_cpu_type_x86_64))
|
||||
write_u32_le(mut buf, u32(macho_cpu_subtype_x86_64_all))
|
||||
write_u32_le(mut buf, macho_mh_object)
|
||||
write_u32_le(mut buf, 2)
|
||||
write_u32_le(mut buf, u32(load_cmds_size))
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
|
||||
write_u32_le(mut buf, macho_lc_segment_64)
|
||||
write_u32_le(mut buf, u32(seg_cmd_size))
|
||||
write_fixed_string(mut buf, '', 16)
|
||||
write_u64_le(mut buf, 0)
|
||||
write_u64_le(mut buf, u64(total_size))
|
||||
write_u64_le(mut buf, u64(text_off))
|
||||
write_u64_le(mut buf, u64(data_off + data_len - text_off))
|
||||
write_u32_le(mut buf, 7)
|
||||
write_u32_le(mut buf, 7)
|
||||
write_u32_le(mut buf, u32(n_sects))
|
||||
write_u32_le(mut buf, 0)
|
||||
|
||||
write_fixed_string(mut buf, '__text', 16)
|
||||
write_fixed_string(mut buf, '__TEXT', 16)
|
||||
write_u64_le(mut buf, u64(text_addr))
|
||||
write_u64_le(mut buf, u64(text_len))
|
||||
write_u32_le(mut buf, u32(text_off))
|
||||
write_u32_le(mut buf, 4)
|
||||
write_u32_le(mut buf, u32(reloc_off))
|
||||
write_u32_le(mut buf, u32(m.relocs.len))
|
||||
write_u32_le(mut buf, u32(0x80000400))
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
|
||||
write_fixed_string(mut buf, '__const', 16)
|
||||
write_fixed_string(mut buf, '__TEXT', 16)
|
||||
write_u64_le(mut buf, u64(rodata_addr))
|
||||
write_u64_le(mut buf, u64(rodata_len))
|
||||
write_u32_le(mut buf, u32(rodata_off))
|
||||
write_u32_le(mut buf, 3)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
|
||||
write_fixed_string(mut buf, '__data', 16)
|
||||
write_fixed_string(mut buf, '__DATA', 16)
|
||||
write_u64_le(mut buf, u64(data_addr))
|
||||
write_u64_le(mut buf, u64(data_len))
|
||||
write_u32_le(mut buf, u32(data_off))
|
||||
write_u32_le(mut buf, 3)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
write_u32_le(mut buf, 0)
|
||||
|
||||
write_u32_le(mut buf, macho_lc_symtab)
|
||||
write_u32_le(mut buf, u32(symtab_cmd_size))
|
||||
write_u32_le(mut buf, u32(sym_off))
|
||||
write_u32_le(mut buf, u32(m.symbols.len))
|
||||
write_u32_le(mut buf, u32(str_off))
|
||||
write_u32_le(mut buf, u32(str_size))
|
||||
|
||||
for buf.len < text_off {
|
||||
buf << 0
|
||||
}
|
||||
buf << m.text_data
|
||||
for buf.len < rodata_off {
|
||||
buf << 0
|
||||
}
|
||||
buf << m.rodata
|
||||
for buf.len < data_off {
|
||||
buf << 0
|
||||
}
|
||||
buf << m.data_data
|
||||
|
||||
for buf.len < reloc_off {
|
||||
buf << 0
|
||||
}
|
||||
for r in m.relocs {
|
||||
write_u32_le(mut buf, u32(r.addr))
|
||||
mut info := u32(r.sym_idx)
|
||||
if r.pcrel {
|
||||
info |= 1 << 24
|
||||
}
|
||||
info |= u32(r.length) << 25
|
||||
if r.extern {
|
||||
info |= 1 << 27
|
||||
}
|
||||
info |= u32(r.type_) << 28
|
||||
write_u32_le(mut buf, info)
|
||||
}
|
||||
|
||||
for buf.len < sym_off {
|
||||
buf << 0
|
||||
}
|
||||
for s in m.symbols {
|
||||
write_u32_le(mut buf, u32(s.name_off))
|
||||
buf << s.type_
|
||||
buf << s.sect
|
||||
write_u16_le(mut buf, s.desc)
|
||||
write_u64_le(mut buf, m.symbol_value(s, rodata_addr, data_addr))
|
||||
}
|
||||
|
||||
buf << m.str_table
|
||||
|
||||
os.write_file_array(path, buf) or { panic(err) }
|
||||
}
|
||||
|
||||
fn (m MachOObject) symbol_value(s MachOSymbol, rodata_addr int, data_addr int) u64 {
|
||||
return match s.sect {
|
||||
1 { s.value }
|
||||
2 { u64(rodata_addr) + s.value }
|
||||
3 { u64(data_addr) + s.value }
|
||||
else { s.value }
|
||||
}
|
||||
}
|
||||
|
||||
fn align_int(value int, alignment int) int {
|
||||
if alignment <= 1 || value % alignment == 0 {
|
||||
return value
|
||||
}
|
||||
return value + (alignment - (value % alignment))
|
||||
}
|
||||
|
||||
fn write_fixed_string(mut b []u8, s string, len int) {
|
||||
bytes := s.bytes()
|
||||
for i in 0 .. len {
|
||||
if i < bytes.len {
|
||||
b << bytes[i]
|
||||
} else {
|
||||
b << 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,709 @@
|
||||
// Copyright (c) 2026 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 x64
|
||||
|
||||
import encoding.binary
|
||||
|
||||
pub const macos_tiny_not_eligible_prefix = 'macOS tiny object is not eligible: '
|
||||
|
||||
const macos_tiny_builtin_string_plus_symbol = '_builtin__string__+'
|
||||
const macos_tiny_builtin_i64_str_symbol = '_builtin__i64__str'
|
||||
const macos_tiny_malloc_symbol = '_malloc'
|
||||
const macos_tiny_exit_symbol = '_exit'
|
||||
|
||||
struct MachOTextRange {
|
||||
name string
|
||||
start u64
|
||||
end u64
|
||||
}
|
||||
|
||||
struct MachODataRange {
|
||||
section ObjectSection
|
||||
start u64
|
||||
end u64
|
||||
}
|
||||
|
||||
struct MachOTinyReachable {
|
||||
mut:
|
||||
names []string
|
||||
data []MachODataRange
|
||||
undefined map[string]bool
|
||||
}
|
||||
|
||||
struct MachOTinyObjectWriter {
|
||||
macho &MachOObject
|
||||
}
|
||||
|
||||
struct MachOTinyRuntimeReloc {
|
||||
addr int
|
||||
symbol string
|
||||
}
|
||||
|
||||
pub fn (mut g Gen) write_macos_tiny_object(path string) ! {
|
||||
if g.obj_format != .macho {
|
||||
return error('macOS tiny object writing requires Mach-O object output')
|
||||
}
|
||||
if g.abi != .sysv {
|
||||
return error('macOS tiny object writing requires SysV x64 code generation')
|
||||
}
|
||||
mut writer := MachOTinyObjectWriter{
|
||||
macho: g.macho
|
||||
}
|
||||
writer.write(path)!
|
||||
}
|
||||
|
||||
fn (mut w MachOTinyObjectWriter) write(path string) ! {
|
||||
mut reachable := w.collect_reachable()!
|
||||
mut out := MachOObject.new()
|
||||
mut symbol_indices := map[string]int{}
|
||||
mut func_offsets := map[string]u64{}
|
||||
mut runtime_symbols := []string{}
|
||||
mut runtime_relocs := []MachOTinyRuntimeReloc{}
|
||||
|
||||
for name in reachable.names {
|
||||
range := w.text_range(name)!
|
||||
func_offsets[name] = u64(out.text_data.len)
|
||||
out.text_data << w.macho.text_data[int(range.start)..int(range.end)]
|
||||
}
|
||||
macho_tiny_add_runtime_helpers(mut out, mut reachable, mut func_offsets, mut runtime_symbols, mut
|
||||
runtime_relocs)
|
||||
|
||||
mut data_offsets := map[string]u64{}
|
||||
w.copy_data_ranges(reachable.data, .rodata, mut out.rodata, mut data_offsets)!
|
||||
w.copy_data_ranges(reachable.data, .data, mut out.data_data, mut data_offsets)!
|
||||
|
||||
for name in reachable.names {
|
||||
symbol_indices[name] = out.add_symbol(name, func_offsets[name],
|
||||
macho_tiny_defined_symbol_is_external(name), ObjectFormat.macho.section_index(.text))
|
||||
}
|
||||
for name in runtime_symbols {
|
||||
symbol_indices[name] = out.add_symbol(name, func_offsets[name],
|
||||
macho_tiny_defined_symbol_is_external(name), ObjectFormat.macho.section_index(.text))
|
||||
}
|
||||
mut data_names := data_offsets.keys()
|
||||
data_names.sort()
|
||||
for name in data_names {
|
||||
orig := w.symbol_by_name(name)!
|
||||
section := w.symbol_object_section(orig)!
|
||||
symbol_indices[name] = out.add_symbol(name, data_offsets[name],
|
||||
macho_tiny_defined_symbol_is_external(name), ObjectFormat.macho.section_index(section))
|
||||
}
|
||||
mut undefined_names := reachable.undefined.keys()
|
||||
undefined_names.sort()
|
||||
for name in undefined_names {
|
||||
symbol_indices[name] = out.add_undefined(name)
|
||||
}
|
||||
for name in reachable.names {
|
||||
range := w.text_range(name)!
|
||||
new_base := func_offsets[name]
|
||||
for reloc in w.macho.relocs {
|
||||
if reloc.addr < int(range.start) || reloc.addr >= int(range.end) {
|
||||
continue
|
||||
}
|
||||
sym := w.reloc_symbol(reloc)!
|
||||
sym_idx := symbol_indices[sym.name] or {
|
||||
return macos_tiny_not_eligible('symbol `${sym.name}` is not resolved by the tiny object')
|
||||
}
|
||||
out_reloc_addr := int(new_base + u64(reloc.addr - int(range.start)))
|
||||
if macho_tiny_resolve_relocation_locally(sym.name, reloc) {
|
||||
macho_tiny_patch_rel32_local(mut out.text_data, out_reloc_addr,
|
||||
int(func_offsets[sym.name]))
|
||||
continue
|
||||
}
|
||||
out.add_reloc(out_reloc_addr, sym_idx, reloc.type_, reloc.pcrel, reloc.length)
|
||||
}
|
||||
}
|
||||
for reloc in runtime_relocs {
|
||||
sym_idx := symbol_indices[reloc.symbol] or {
|
||||
return macos_tiny_not_eligible('runtime helper symbol `${reloc.symbol}` is not resolved by the tiny object')
|
||||
}
|
||||
out.add_reloc(reloc.addr, sym_idx, x86_64_reloc_branch, true, 2)
|
||||
}
|
||||
|
||||
out.write(path)
|
||||
}
|
||||
|
||||
fn (mut w MachOTinyObjectWriter) collect_reachable() !MachOTinyReachable {
|
||||
_ = w.text_range('_main') or { return macos_tiny_not_eligible('missing _main symbol') }
|
||||
mut selected := []string{}
|
||||
mut seen := map[string]bool{}
|
||||
mut queue := ['_main']
|
||||
mut selected_data := []MachODataRange{}
|
||||
mut undefined := map[string]bool{}
|
||||
for queue.len > 0 {
|
||||
name := queue[0]
|
||||
queue.delete(0)
|
||||
if seen[name] {
|
||||
continue
|
||||
}
|
||||
if macho_tiny_is_module_init_symbol(name) {
|
||||
return macos_tiny_not_eligible('module init symbol `${name}` is not supported')
|
||||
}
|
||||
range := w.text_range(name)!
|
||||
seen[name] = true
|
||||
selected << name
|
||||
for reloc in w.macho.relocs {
|
||||
if reloc.addr < int(range.start) || reloc.addr >= int(range.end) {
|
||||
continue
|
||||
}
|
||||
validate_macho_tiny_relocation(reloc)!
|
||||
sym := w.reloc_symbol(reloc)!
|
||||
if sym.sect == ObjectFormat.macho.section_index(.text) {
|
||||
if !seen[sym.name] {
|
||||
queue << sym.name
|
||||
}
|
||||
} else if sym.sect == ObjectFormat.macho.section_index(.rodata) {
|
||||
w.add_data_range(mut selected_data, .rodata, sym.value)!
|
||||
} else if sym.sect == ObjectFormat.macho.section_index(.data) {
|
||||
if x64_main_argc_global_name(sym.name) || x64_main_argv_global_name(sym.name) {
|
||||
return macos_tiny_not_eligible('arguments() requires hosted argc/argv; macOS tiny _main argv is not implemented yet')
|
||||
}
|
||||
w.add_data_range(mut selected_data, .data, sym.value)!
|
||||
} else if sym.sect == 0 && sym.type_ == 0x01 {
|
||||
undefined[sym.name] = true
|
||||
} else {
|
||||
return macos_tiny_not_eligible('relocation to symbol `${sym.name}` in Mach-O section ${sym.sect} is not supported')
|
||||
}
|
||||
}
|
||||
}
|
||||
return MachOTinyReachable{
|
||||
names: selected
|
||||
data: selected_data
|
||||
undefined: undefined
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_macho_tiny_relocation(reloc MachORelocationInfo) ! {
|
||||
if !reloc.pcrel || reloc.length != 2 {
|
||||
return macos_tiny_not_eligible('non-pcrel or non-32-bit relocation at __text+${reloc.addr} is not supported')
|
||||
}
|
||||
if reloc.type_ !in [x86_64_reloc_signed, x86_64_reloc_branch, x86_64_reloc_got_load,
|
||||
x86_64_reloc_got] {
|
||||
return macos_tiny_not_eligible('Mach-O relocation type ${reloc.type_} is not supported')
|
||||
}
|
||||
}
|
||||
|
||||
fn (w MachOTinyObjectWriter) text_range(name string) !MachOTextRange {
|
||||
ranges := w.text_symbol_ranges()
|
||||
for range in ranges {
|
||||
if range.name == name {
|
||||
return range
|
||||
}
|
||||
}
|
||||
return error('missing text symbol `${name}`')
|
||||
}
|
||||
|
||||
fn (w MachOTinyObjectWriter) text_symbol_ranges() []MachOTextRange {
|
||||
mut syms := []MachOSymbol{}
|
||||
for sym in w.macho.symbols {
|
||||
if sym.name != '' && sym.sect == ObjectFormat.macho.section_index(.text) {
|
||||
syms << sym
|
||||
}
|
||||
}
|
||||
for i := 1; i < syms.len; i++ {
|
||||
mut j := i
|
||||
for j > 0 && syms[j - 1].value > syms[j].value {
|
||||
syms[j - 1], syms[j] = syms[j], syms[j - 1]
|
||||
j--
|
||||
}
|
||||
}
|
||||
mut ranges := []MachOTextRange{}
|
||||
for i, sym in syms {
|
||||
mut end := u64(w.macho.text_data.len)
|
||||
for j := i + 1; j < syms.len; j++ {
|
||||
if syms[j].value > sym.value {
|
||||
end = syms[j].value
|
||||
break
|
||||
}
|
||||
}
|
||||
if end > sym.value {
|
||||
ranges << MachOTextRange{
|
||||
name: sym.name
|
||||
start: sym.value
|
||||
end: end
|
||||
}
|
||||
}
|
||||
}
|
||||
return ranges
|
||||
}
|
||||
|
||||
fn (w MachOTinyObjectWriter) reloc_symbol(reloc MachORelocationInfo) !MachOSymbol {
|
||||
if reloc.sym_idx < 0 || reloc.sym_idx >= w.macho.symbols.len {
|
||||
return error('macOS tiny object relocation references invalid symbol index ${reloc.sym_idx}')
|
||||
}
|
||||
return w.macho.symbols[reloc.sym_idx]
|
||||
}
|
||||
|
||||
fn (w MachOTinyObjectWriter) symbol_by_name(name string) !MachOSymbol {
|
||||
idx := w.macho.sym_by_name[name] or { return error('missing symbol `${name}`') }
|
||||
if idx < 0 || idx >= w.macho.symbols.len {
|
||||
return error('invalid symbol index ${idx} for `${name}`')
|
||||
}
|
||||
return w.macho.symbols[idx]
|
||||
}
|
||||
|
||||
fn (w MachOTinyObjectWriter) symbol_object_section(sym MachOSymbol) !ObjectSection {
|
||||
if sym.sect == ObjectFormat.macho.section_index(.rodata) {
|
||||
return .rodata
|
||||
}
|
||||
if sym.sect == ObjectFormat.macho.section_index(.data) {
|
||||
return .data
|
||||
}
|
||||
if sym.sect == ObjectFormat.macho.section_index(.text) {
|
||||
return .text
|
||||
}
|
||||
return error('symbol `${sym.name}` is not in a known Mach-O section')
|
||||
}
|
||||
|
||||
fn (mut w MachOTinyObjectWriter) add_data_range(mut ranges []MachODataRange, section ObjectSection, start u64) ! {
|
||||
end := w.data_symbol_range_end(section, start)!
|
||||
for range in ranges {
|
||||
if range.section == section && range.start == start && range.end == end {
|
||||
return
|
||||
}
|
||||
}
|
||||
ranges << MachODataRange{
|
||||
section: section
|
||||
start: start
|
||||
end: end
|
||||
}
|
||||
}
|
||||
|
||||
fn (w MachOTinyObjectWriter) data_symbol_range_end(section ObjectSection, start u64) !u64 {
|
||||
section_idx := ObjectFormat.macho.section_index(section)
|
||||
section_len := match section {
|
||||
.rodata { u64(w.macho.rodata.len) }
|
||||
.data { u64(w.macho.data_data.len) }
|
||||
else { u64(0) }
|
||||
}
|
||||
|
||||
mut end := section_len
|
||||
for sym in w.macho.symbols {
|
||||
if sym.sect == section_idx && sym.value > start && sym.value < end {
|
||||
end = sym.value
|
||||
}
|
||||
}
|
||||
if end < start {
|
||||
return error('invalid Mach-O data symbol range')
|
||||
}
|
||||
return end
|
||||
}
|
||||
|
||||
fn (w MachOTinyObjectWriter) copy_data_ranges(ranges []MachODataRange, section ObjectSection, mut out []u8, mut offsets map[string]u64) ! {
|
||||
for dr in ranges {
|
||||
if dr.section != section {
|
||||
continue
|
||||
}
|
||||
align := int(w.data_section_alignment(section))
|
||||
for align > 1 && out.len % align != int(dr.start % u64(align)) {
|
||||
out << u8(0)
|
||||
}
|
||||
off := u64(out.len)
|
||||
section_idx := ObjectFormat.macho.section_index(section)
|
||||
for sym in w.macho.symbols {
|
||||
if sym.sect == section_idx && sym.value == dr.start {
|
||||
offsets[sym.name] = off
|
||||
}
|
||||
}
|
||||
match section {
|
||||
.rodata {
|
||||
out << w.macho.rodata[int(dr.start)..int(dr.end)]
|
||||
}
|
||||
.data {
|
||||
out << w.macho.data_data[int(dr.start)..int(dr.end)]
|
||||
}
|
||||
else {
|
||||
return error('macOS tiny object cannot copy ${section} as data')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (w MachOTinyObjectWriter) data_section_alignment(section ObjectSection) u64 {
|
||||
return match section {
|
||||
.data { u64(8) }
|
||||
.rodata { u64(4) }
|
||||
else { u64(1) }
|
||||
}
|
||||
}
|
||||
|
||||
fn macho_tiny_defined_symbol_is_external(name string) bool {
|
||||
return name == '_main'
|
||||
}
|
||||
|
||||
fn macho_tiny_resolve_relocation_locally(name string, reloc MachORelocationInfo) bool {
|
||||
return name in [macos_tiny_builtin_string_plus_symbol, macos_tiny_builtin_i64_str_symbol]
|
||||
&& reloc.type_ == x86_64_reloc_branch && reloc.pcrel && reloc.length == 2
|
||||
}
|
||||
|
||||
fn macho_tiny_add_runtime_helpers(mut out MachOObject, mut reachable MachOTinyReachable, mut func_offsets map[string]u64, mut runtime_symbols []string, mut runtime_relocs []MachOTinyRuntimeReloc) {
|
||||
if reachable.undefined[macos_tiny_builtin_i64_str_symbol] {
|
||||
reachable.undefined.delete(macos_tiny_builtin_i64_str_symbol)
|
||||
reachable.undefined[macos_tiny_malloc_symbol] = true
|
||||
reachable.undefined[macos_tiny_exit_symbol] = true
|
||||
func_offsets[macos_tiny_builtin_i64_str_symbol] = u64(out.text_data.len)
|
||||
runtime_symbols << macos_tiny_builtin_i64_str_symbol
|
||||
macho_tiny_emit_i64_str(mut out.text_data, mut runtime_relocs)
|
||||
}
|
||||
if reachable.undefined[macos_tiny_builtin_string_plus_symbol] {
|
||||
reachable.undefined.delete(macos_tiny_builtin_string_plus_symbol)
|
||||
reachable.undefined[macos_tiny_malloc_symbol] = true
|
||||
reachable.undefined[macos_tiny_exit_symbol] = true
|
||||
func_offsets[macos_tiny_builtin_string_plus_symbol] = u64(out.text_data.len)
|
||||
runtime_symbols << macos_tiny_builtin_string_plus_symbol
|
||||
macho_tiny_emit_string_plus(mut out.text_data, mut runtime_relocs)
|
||||
}
|
||||
}
|
||||
|
||||
fn macho_tiny_emit_i64_str(mut text []u8, mut runtime_relocs []MachOTinyRuntimeReloc) {
|
||||
text << [
|
||||
u8(0x57), // push rdi, input value
|
||||
0xbf,
|
||||
0x20,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // mov edi, 32
|
||||
]
|
||||
macho_tiny_emit_call_reloc(mut text, mut runtime_relocs, macos_tiny_malloc_symbol)
|
||||
text << [
|
||||
u8(0x48),
|
||||
0x85,
|
||||
0xc0, // test rax, rax
|
||||
]
|
||||
malloc_ok := macho_tiny_emit_jcc32(mut text, 0x85) // jne ok
|
||||
text << [
|
||||
u8(0xbf),
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // mov edi, 1
|
||||
]
|
||||
macho_tiny_emit_call_reloc(mut text, mut runtime_relocs, macos_tiny_exit_symbol)
|
||||
text << [u8(0x0f), 0x0b] // ud2
|
||||
ok_start := text.len
|
||||
macho_tiny_patch_rel32_local(mut text, malloc_ok, ok_start)
|
||||
|
||||
text << [
|
||||
u8(0x5f), // pop rdi
|
||||
0x4c,
|
||||
0x8d,
|
||||
0x40,
|
||||
0x1f, // lea r8, [rax+31]
|
||||
0x41,
|
||||
0xc6,
|
||||
0x00,
|
||||
0x00, // mov byte ptr [r8], 0
|
||||
0x48,
|
||||
0x89,
|
||||
0xf8, // mov rax, rdi
|
||||
0x45,
|
||||
0x31,
|
||||
0xc9, // xor r9d, r9d
|
||||
0x45,
|
||||
0x31,
|
||||
0xd2, // xor r10d, r10d
|
||||
0x48,
|
||||
0x85,
|
||||
0xc0, // test rax, rax
|
||||
]
|
||||
non_negative := macho_tiny_emit_jcc32(mut text, 0x89) // jns non_negative
|
||||
text << [
|
||||
u8(0x41),
|
||||
0xb2,
|
||||
0x01, // mov r10b, 1
|
||||
0x48,
|
||||
0xf7,
|
||||
0xd8, // neg rax
|
||||
]
|
||||
non_negative_target := text.len
|
||||
text << [
|
||||
u8(0x48),
|
||||
0x85,
|
||||
0xc0, // test rax, rax
|
||||
]
|
||||
non_zero := macho_tiny_emit_jcc32(mut text, 0x85) // jne loop
|
||||
text << [
|
||||
u8(0x49),
|
||||
0xff,
|
||||
0xc8, // dec r8
|
||||
0x41,
|
||||
0xc6,
|
||||
0x00,
|
||||
0x30, // mov byte ptr [r8], '0'
|
||||
0x41,
|
||||
0xb9,
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // mov r9d, 1
|
||||
]
|
||||
maybe_sign_jump := macho_tiny_emit_jmp32(mut text)
|
||||
loop_start := text.len
|
||||
text << [
|
||||
u8(0x31),
|
||||
0xd2, // xor edx, edx
|
||||
0xb9,
|
||||
0x0a,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // mov ecx, 10
|
||||
0x48,
|
||||
0xf7,
|
||||
0xf1, // div rcx
|
||||
0x80,
|
||||
0xc2,
|
||||
0x30, // add dl, '0'
|
||||
0x49,
|
||||
0xff,
|
||||
0xc8, // dec r8
|
||||
0x41,
|
||||
0x88,
|
||||
0x10, // mov [r8], dl
|
||||
0x49,
|
||||
0xff,
|
||||
0xc1, // inc r9
|
||||
0x48,
|
||||
0x85,
|
||||
0xc0, // test rax, rax
|
||||
]
|
||||
loop_more := macho_tiny_emit_jcc32(mut text, 0x85) // jne loop
|
||||
maybe_sign := text.len
|
||||
text << [
|
||||
u8(0x45),
|
||||
0x84,
|
||||
0xd2, // test r10b, r10b
|
||||
]
|
||||
done_digits := macho_tiny_emit_jcc32(mut text, 0x84) // je done
|
||||
text << [
|
||||
u8(0x49),
|
||||
0xff,
|
||||
0xc8, // dec r8
|
||||
0x41,
|
||||
0xc6,
|
||||
0x00,
|
||||
0x2d, // mov byte ptr [r8], '-'
|
||||
0x49,
|
||||
0xff,
|
||||
0xc1, // inc r9
|
||||
]
|
||||
done_digits_target := text.len
|
||||
text << [
|
||||
u8(0x4c),
|
||||
0x89,
|
||||
0xc0, // mov rax, r8
|
||||
0x4c,
|
||||
0x89,
|
||||
0xca, // mov rdx, r9
|
||||
0xc3, // ret
|
||||
]
|
||||
macho_tiny_patch_rel32_local(mut text, non_negative, non_negative_target)
|
||||
macho_tiny_patch_rel32_local(mut text, non_zero, loop_start)
|
||||
macho_tiny_patch_rel32_local(mut text, maybe_sign_jump, maybe_sign)
|
||||
macho_tiny_patch_rel32_local(mut text, loop_more, loop_start)
|
||||
macho_tiny_patch_rel32_local(mut text, done_digits, done_digits_target)
|
||||
}
|
||||
|
||||
fn macho_tiny_emit_string_plus(mut text []u8, mut runtime_relocs []MachOTinyRuntimeReloc) {
|
||||
text << [
|
||||
u8(0x57), // push rdi, first string ptr
|
||||
0x56, // push rsi, first string len
|
||||
0x52, // push rdx, second string ptr
|
||||
0x51, // push rcx, second string len
|
||||
0x48,
|
||||
0x83,
|
||||
0xec,
|
||||
0x08, // sub rsp, 8, align before libSystem calls
|
||||
0x44,
|
||||
0x8b,
|
||||
0x44,
|
||||
0x24,
|
||||
0x18, // mov r8d, [rsp+24]
|
||||
0x44,
|
||||
0x03,
|
||||
0x44,
|
||||
0x24,
|
||||
0x08, // add r8d, [rsp+8]
|
||||
]
|
||||
len_overflow := macho_tiny_emit_jcc32(mut text, 0x82) // jc fail
|
||||
text << [
|
||||
u8(0x4d),
|
||||
0x89,
|
||||
0xc1, // mov r9, r8
|
||||
0x49,
|
||||
0x83,
|
||||
0xc1,
|
||||
0x01, // add r9, 1
|
||||
]
|
||||
alloc_overflow := macho_tiny_emit_jcc32(mut text, 0x82) // jc fail
|
||||
text << [
|
||||
u8(0x4c),
|
||||
0x89,
|
||||
0xcf, // mov rdi, r9
|
||||
]
|
||||
macho_tiny_emit_call_reloc(mut text, mut runtime_relocs, macos_tiny_malloc_symbol)
|
||||
text << [
|
||||
u8(0x48),
|
||||
0x85,
|
||||
0xc0, // test rax, rax
|
||||
]
|
||||
malloc_ok := macho_tiny_emit_jcc32(mut text, 0x85) // jne ok
|
||||
fail_start := text.len
|
||||
text << [
|
||||
u8(0xbf),
|
||||
0x01,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // mov edi, 1
|
||||
]
|
||||
macho_tiny_emit_call_reloc(mut text, mut runtime_relocs, macos_tiny_exit_symbol)
|
||||
text << [u8(0x0f), 0x0b] // ud2
|
||||
ok_start := text.len
|
||||
macho_tiny_patch_rel32_local(mut text, len_overflow, fail_start)
|
||||
macho_tiny_patch_rel32_local(mut text, alloc_overflow, fail_start)
|
||||
macho_tiny_patch_rel32_local(mut text, malloc_ok, ok_start)
|
||||
|
||||
text << [
|
||||
u8(0x49),
|
||||
0x89,
|
||||
0xc2, // mov r10, rax
|
||||
0x49,
|
||||
0x89,
|
||||
0xc0, // mov r8, rax
|
||||
0x48,
|
||||
0x8b,
|
||||
0x74,
|
||||
0x24,
|
||||
0x20, // mov rsi, [rsp+32]
|
||||
0x8b,
|
||||
0x4c,
|
||||
0x24,
|
||||
0x18, // mov ecx, [rsp+24]
|
||||
0x48,
|
||||
0x85,
|
||||
0xc9, // test rcx, rcx
|
||||
]
|
||||
copy_a_done := macho_tiny_emit_jcc32(mut text, 0x84) // jz done
|
||||
copy_a_loop := text.len
|
||||
text << [
|
||||
u8(0x8a),
|
||||
0x16, // mov dl, [rsi]
|
||||
0x41,
|
||||
0x88,
|
||||
0x10, // mov [r8], dl
|
||||
0x48,
|
||||
0xff,
|
||||
0xc6, // inc rsi
|
||||
0x49,
|
||||
0xff,
|
||||
0xc0, // inc r8
|
||||
0x48,
|
||||
0xff,
|
||||
0xc9, // dec rcx
|
||||
]
|
||||
copy_a_continue := macho_tiny_emit_jcc32(mut text, 0x85) // jne loop
|
||||
copy_a_done_target := text.len
|
||||
macho_tiny_patch_rel32_local(mut text, copy_a_done, copy_a_done_target)
|
||||
macho_tiny_patch_rel32_local(mut text, copy_a_continue, copy_a_loop)
|
||||
|
||||
text << [
|
||||
u8(0x48),
|
||||
0x8b,
|
||||
0x74,
|
||||
0x24,
|
||||
0x10, // mov rsi, [rsp+16]
|
||||
0x8b,
|
||||
0x4c,
|
||||
0x24,
|
||||
0x08, // mov ecx, [rsp+8]
|
||||
0x48,
|
||||
0x85,
|
||||
0xc9, // test rcx, rcx
|
||||
]
|
||||
copy_b_done := macho_tiny_emit_jcc32(mut text, 0x84) // jz done
|
||||
copy_b_loop := text.len
|
||||
text << [
|
||||
u8(0x8a),
|
||||
0x16, // mov dl, [rsi]
|
||||
0x41,
|
||||
0x88,
|
||||
0x10, // mov [r8], dl
|
||||
0x48,
|
||||
0xff,
|
||||
0xc6, // inc rsi
|
||||
0x49,
|
||||
0xff,
|
||||
0xc0, // inc r8
|
||||
0x48,
|
||||
0xff,
|
||||
0xc9, // dec rcx
|
||||
]
|
||||
copy_b_continue := macho_tiny_emit_jcc32(mut text, 0x85) // jne loop
|
||||
copy_b_done_target := text.len
|
||||
macho_tiny_patch_rel32_local(mut text, copy_b_done, copy_b_done_target)
|
||||
macho_tiny_patch_rel32_local(mut text, copy_b_continue, copy_b_loop)
|
||||
|
||||
text << [
|
||||
u8(0x41),
|
||||
0xc6,
|
||||
0x00,
|
||||
0x00, // mov byte ptr [r8], 0
|
||||
0x8b,
|
||||
0x44,
|
||||
0x24,
|
||||
0x18, // mov eax, [rsp+24]
|
||||
0x03,
|
||||
0x44,
|
||||
0x24,
|
||||
0x08, // add eax, [rsp+8]
|
||||
0x48,
|
||||
0x89,
|
||||
0xc2, // mov rdx, rax
|
||||
0x4c,
|
||||
0x89,
|
||||
0xd0, // mov rax, r10
|
||||
0x48,
|
||||
0x83,
|
||||
0xc4,
|
||||
0x28, // add rsp, 40
|
||||
0xc3, // ret
|
||||
]
|
||||
}
|
||||
|
||||
fn macho_tiny_emit_call_reloc(mut text []u8, mut runtime_relocs []MachOTinyRuntimeReloc, symbol string) {
|
||||
text << u8(0xe8)
|
||||
field_off := text.len
|
||||
text << [u8(0), 0, 0, 0]
|
||||
runtime_relocs << MachOTinyRuntimeReloc{
|
||||
addr: field_off
|
||||
symbol: symbol
|
||||
}
|
||||
}
|
||||
|
||||
fn macho_tiny_emit_jcc32(mut text []u8, opcode u8) int {
|
||||
text << [u8(0x0f), opcode]
|
||||
field_off := text.len
|
||||
text << [u8(0), 0, 0, 0]
|
||||
return field_off
|
||||
}
|
||||
|
||||
fn macho_tiny_emit_jmp32(mut text []u8) int {
|
||||
text << u8(0xe9)
|
||||
field_off := text.len
|
||||
text << [u8(0), 0, 0, 0]
|
||||
return field_off
|
||||
}
|
||||
|
||||
fn macho_tiny_patch_rel32_local(mut text []u8, field_off int, target_off int) {
|
||||
disp := i64(target_off) - i64(field_off + 4)
|
||||
binary.little_endian_put_u32(mut text[field_off..field_off + 4], u32(i32(disp)))
|
||||
}
|
||||
|
||||
fn macho_tiny_is_module_init_symbol(name string) bool {
|
||||
return name.ends_with('__init') && name != '_main'
|
||||
}
|
||||
|
||||
fn macos_tiny_not_eligible(message string) IError {
|
||||
return error(macos_tiny_not_eligible_prefix + message)
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
// Copyright (c) 2026 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 x64
|
||||
|
||||
import encoding.binary
|
||||
|
||||
pub enum ObjectFormat {
|
||||
elf
|
||||
macho
|
||||
coff
|
||||
}
|
||||
|
||||
enum ObjectSection {
|
||||
text
|
||||
data
|
||||
rodata
|
||||
}
|
||||
|
||||
fn (format ObjectFormat) section_index(section ObjectSection) u8 {
|
||||
return match format {
|
||||
.elf {
|
||||
match section {
|
||||
.text { 1 }
|
||||
.data { 2 }
|
||||
.rodata { 3 }
|
||||
}
|
||||
}
|
||||
.macho {
|
||||
match section {
|
||||
.text { 1 }
|
||||
.rodata { 2 }
|
||||
.data { 3 }
|
||||
}
|
||||
}
|
||||
.coff {
|
||||
match section {
|
||||
.text { 1 }
|
||||
.rodata { 2 }
|
||||
.data { 3 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (format ObjectFormat) symbol_name(name string) string {
|
||||
if format == .macho && !name.starts_with('L_') {
|
||||
return '_' + name
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
fn (mut g Gen) text_len() int {
|
||||
return match g.obj_format {
|
||||
.elf { g.elf.text_data.len }
|
||||
.macho { g.macho.text_data.len }
|
||||
.coff { g.coff.text_data.len }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) data_len() int {
|
||||
return match g.obj_format {
|
||||
.elf { g.elf.data_data.len }
|
||||
.macho { g.macho.data_data.len }
|
||||
.coff { g.coff.data_data.len }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) rodata_len() int {
|
||||
return match g.obj_format {
|
||||
.elf { g.elf.rodata.len }
|
||||
.macho { g.macho.rodata.len }
|
||||
.coff { g.coff.rodata.len }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_data_byte(b u8) {
|
||||
match g.obj_format {
|
||||
.elf { g.elf.data_data << b }
|
||||
.macho { g.macho.data_data << b }
|
||||
.coff { g.coff.data_data << b }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_data(bytes []u8) {
|
||||
match g.obj_format {
|
||||
.elf { g.elf.data_data << bytes }
|
||||
.macho { g.macho.data_data << bytes }
|
||||
.coff { g.coff.data_data << bytes }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_rodata_byte(b u8) {
|
||||
match g.obj_format {
|
||||
.elf { g.elf.rodata << b }
|
||||
.macho { g.macho.rodata << b }
|
||||
.coff { g.coff.rodata << b }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_rodata(bytes []u8) {
|
||||
match g.obj_format {
|
||||
.elf { g.elf.rodata << bytes }
|
||||
.macho { g.macho.rodata << bytes }
|
||||
.coff { g.coff.rodata << bytes }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_symbol(name string, value u64, is_func bool, section ObjectSection) int {
|
||||
sym_name := g.obj_format.symbol_name(name)
|
||||
sect := g.obj_format.section_index(section)
|
||||
return match g.obj_format {
|
||||
.elf { g.elf.add_symbol(sym_name, value, is_func, u16(sect)) }
|
||||
.macho { g.macho.add_symbol(sym_name, value, !sym_name.starts_with('L_'), sect) }
|
||||
.coff { g.coff.add_symbol(sym_name, value, is_func, sect) }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_undefined(name string) int {
|
||||
sym_name := g.obj_format.symbol_name(name)
|
||||
return match g.obj_format {
|
||||
.elf { g.elf.add_undefined(sym_name) }
|
||||
.macho { g.macho.add_undefined(sym_name) }
|
||||
.coff { g.coff.add_undefined(sym_name) }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_call_reloc(sym_idx int) {
|
||||
offset := g.text_len()
|
||||
match g.obj_format {
|
||||
.elf { g.elf.add_text_reloc(u64(offset), sym_idx, r_x86_64_plt32, -4) }
|
||||
.macho { g.macho.add_reloc(offset, sym_idx, x86_64_reloc_branch, true, 2) }
|
||||
.coff { g.coff.add_text_reloc(offset, sym_idx, coff_image_rel_amd64_rel32) }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_rip_reloc(sym_idx int) {
|
||||
offset := g.text_len()
|
||||
match g.obj_format {
|
||||
.elf { g.elf.add_text_reloc(u64(offset), sym_idx, r_x86_64_pc32, -4) }
|
||||
.macho { g.macho.add_reloc(offset, sym_idx, x86_64_reloc_signed, true, 2) }
|
||||
.coff { g.coff.add_text_reloc(offset, sym_idx, coff_image_rel_amd64_rel32) }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) add_macho_got_load_reloc(sym_idx int) {
|
||||
offset := g.text_len()
|
||||
if g.obj_format == .macho {
|
||||
g.macho.add_reloc(offset, sym_idx, x86_64_reloc_got_load, true, 2)
|
||||
return
|
||||
}
|
||||
x64_unsupported('Mach-O GOT_LOAD relocation requested for ${g.obj_format}')
|
||||
}
|
||||
|
||||
fn (mut g Gen) emit(b u8) {
|
||||
match g.obj_format {
|
||||
.elf { g.elf.text_data << b }
|
||||
.macho { g.macho.text_data << b }
|
||||
.coff { g.coff.text_data << b }
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut g Gen) emit_u32(v u32) {
|
||||
g.emit(u8(v))
|
||||
g.emit(u8(v >> 8))
|
||||
g.emit(u8(v >> 16))
|
||||
g.emit(u8(v >> 24))
|
||||
}
|
||||
|
||||
fn (mut g Gen) emit_u64(v u64) {
|
||||
g.emit_u32(u32(v))
|
||||
g.emit_u32(u32(v >> 32))
|
||||
}
|
||||
|
||||
fn (mut g Gen) write_u32(off int, v u32) {
|
||||
match g.obj_format {
|
||||
.elf { binary.little_endian_put_u32(mut g.elf.text_data[off..off + 4], v) }
|
||||
.macho { binary.little_endian_put_u32(mut g.macho.text_data[off..off + 4], v) }
|
||||
.coff { binary.little_endian_put_u32(mut g.coff.text_data[off..off + 4], v) }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut g Gen) write_file(path string) {
|
||||
if msg := g.unsupported_external_symbol_message() {
|
||||
x64_abort_with_diagnostic(msg)
|
||||
}
|
||||
match g.obj_format {
|
||||
.elf { g.elf.write(path) }
|
||||
.macho { g.macho.write(path) }
|
||||
.coff { g.coff.write(path) }
|
||||
}
|
||||
}
|
||||
|
||||
fn (g Gen) unsupported_external_symbol_message() ?string {
|
||||
for raw_name in g.undefined_external_symbol_names() {
|
||||
if msg := unsupported_external_symbol_message_for_name(g.obj_format, raw_name,
|
||||
'needed while preparing native x64 output')
|
||||
{
|
||||
return msg
|
||||
}
|
||||
}
|
||||
return none
|
||||
}
|
||||
|
||||
fn (g Gen) undefined_external_symbol_names() []string {
|
||||
mut names := []string{}
|
||||
match g.obj_format {
|
||||
.elf {
|
||||
for sym in g.elf.symbols {
|
||||
if sym.name != '' && sym.shndx == 0 {
|
||||
names << sym.name
|
||||
}
|
||||
}
|
||||
}
|
||||
.macho {
|
||||
for sym in g.macho.symbols {
|
||||
if sym.name != '' && sym.sect == 0 && sym.type_ == 0x01 {
|
||||
names << sym.name
|
||||
}
|
||||
}
|
||||
}
|
||||
.coff {
|
||||
for sym in g.coff.symbols {
|
||||
if sym.name != '' && sym.section == 0
|
||||
&& sym.storage_class == coff_image_sym_class_external {
|
||||
names << sym.name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return names
|
||||
}
|
||||
|
||||
pub fn (mut g Gen) link_executable(path string) ! {
|
||||
match g.obj_format {
|
||||
.coff {
|
||||
if g.abi != .windows {
|
||||
return error('x64 PE linking requires Windows ABI code generation')
|
||||
}
|
||||
mut linker := PeLinker.new(g.coff)
|
||||
linker.write(path)!
|
||||
}
|
||||
else {
|
||||
return error('x64 built-in executable linking is only implemented for COFF')
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,276 @@
|
||||
module x64
|
||||
|
||||
import os
|
||||
import v2.mir
|
||||
|
||||
struct X64BackendCompileFailure {
|
||||
stdout string
|
||||
stderr string
|
||||
exit_code int
|
||||
}
|
||||
|
||||
fn run_x64_backend_compile_failure(name string, source string) X64BackendCompileFailure {
|
||||
tmp_dir := os.join_path(os.vtmp_dir(), 'v_x64_backend_diagnostics_${name}_${os.getpid()}')
|
||||
os.mkdir_all(tmp_dir) or { panic(err) }
|
||||
defer {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
}
|
||||
source_path := os.join_path(tmp_dir, '${name}.v')
|
||||
bin_path := os.join_path(tmp_dir, name)
|
||||
os.write_file(source_path, source) or { panic(err) }
|
||||
vexe := x64_backend_diagnostic_vexe_command_path()
|
||||
mut build := os.new_process(vexe)
|
||||
defer {
|
||||
build.close()
|
||||
}
|
||||
build.set_args(['-v2', '-b', 'x64', source_path, '-o', bin_path])
|
||||
build.set_redirect_stdio()
|
||||
build.run()
|
||||
build.wait()
|
||||
stdout := x64_backend_diagnostic_stdout(build.stdout_slurp())
|
||||
stderr := build.stderr_slurp()
|
||||
return X64BackendCompileFailure{
|
||||
stdout: stdout
|
||||
stderr: stderr
|
||||
exit_code: build.code
|
||||
}
|
||||
}
|
||||
|
||||
fn x64_backend_diagnostic_vexe_command_path() string {
|
||||
vexe := os.getenv_opt('VEXE') or { @VEXE }
|
||||
if os.is_abs_path(vexe) || !os.exists(vexe) {
|
||||
return vexe
|
||||
}
|
||||
return os.abs_path(vexe)
|
||||
}
|
||||
|
||||
fn x64_backend_diagnostic_stdout(stdout string) string {
|
||||
mut lines := []string{}
|
||||
for line in stdout.split_into_lines() {
|
||||
if line.starts_with(' * ') {
|
||||
continue
|
||||
}
|
||||
lines << line
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
fn assert_x64_user_visible_compile_failure(failure X64BackendCompileFailure, expected_message string) {
|
||||
assert failure.exit_code != 0, failure.stdout + failure.stderr
|
||||
assert failure.stdout == '', failure.stdout
|
||||
assert failure.stderr.contains(expected_message), failure.stderr
|
||||
assert failure.stderr.contains(x64_backend_limitation_hint), failure.stderr
|
||||
assert !failure.stderr.contains('Link failed:'), failure.stderr
|
||||
assert !failure.stderr.contains('V panic:'), failure.stderr
|
||||
assert !failure.stderr.contains('Backtrace'), failure.stderr
|
||||
assert_x64_message_avoids_old_wording(failure.stderr)
|
||||
}
|
||||
|
||||
fn assert_x64_clean_user_diagnostic_message(msg string) {
|
||||
assert msg.starts_with('x64: unsupported backend feature: '), msg
|
||||
assert !msg.contains('Link failed:'), msg
|
||||
assert !msg.contains('V panic:'), msg
|
||||
assert !msg.contains('Backtrace'), msg
|
||||
assert_x64_message_avoids_old_wording(msg)
|
||||
}
|
||||
|
||||
fn assert_x64_message_avoids_old_wording(msg string) {
|
||||
assert !msg.contains('reachable helper'), msg
|
||||
assert !msg.contains('stdout/stderr path'), msg
|
||||
assert !msg.contains('must be compiled'), msg
|
||||
assert !msg.contains('lowered before linking'), msg
|
||||
assert !msg.contains('before external linking'), msg
|
||||
assert !msg.contains('cannot resolve C stdio symbol'), msg
|
||||
assert !msg.contains('C FILE streams'), msg
|
||||
}
|
||||
|
||||
fn assert_x64_message_mentions_only_target_linker(format ObjectFormat, msg string) {
|
||||
match format {
|
||||
.elf {
|
||||
assert msg.contains('ELF linker'), msg
|
||||
assert !msg.contains('Mach-O'), msg
|
||||
assert !msg.contains('PE linker'), msg
|
||||
assert !msg.contains('Windows'), msg
|
||||
assert !msg.contains('Kernel32'), msg
|
||||
assert !msg.contains('macOS'), msg
|
||||
}
|
||||
.macho {
|
||||
assert msg.contains('Mach-O linker'), msg
|
||||
assert !msg.contains('ELF linker'), msg
|
||||
assert !msg.contains('PE linker'), msg
|
||||
assert !msg.contains('Windows'), msg
|
||||
assert !msg.contains('Kernel32'), msg
|
||||
assert !msg.contains('Linux'), msg
|
||||
}
|
||||
.coff {
|
||||
assert msg.contains('PE linker'), msg
|
||||
assert !msg.contains('ELF linker'), msg
|
||||
assert !msg.contains('Mach-O'), msg
|
||||
assert !msg.contains('Linux'), msg
|
||||
assert !msg.contains('macOS'), msg
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_user_visible_stderr_reports_clean_codegen_abi_unsupported_without_link_noise() {
|
||||
$if windows {
|
||||
println('skipping ${@FN}: SysV codegen diagnostic is not available on Windows')
|
||||
} $else $if x64 {
|
||||
failure := run_x64_backend_compile_failure('clean_codegen_abi_unsupported', 'module main
|
||||
|
||||
fn many(a0 f64, a1 f64, a2 f64, a3 f64, a4 f64, a5 f64, a6 f64, a7 f64, a8 f64) f64 {
|
||||
return a8
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ = many(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0)
|
||||
}
|
||||
')
|
||||
assert_x64_user_visible_compile_failure(failure,
|
||||
'x64: unsupported backend feature: stack-passed float parameter')
|
||||
assert !failure.stderr.contains('Windows'), failure.stderr
|
||||
assert !failure.stderr.contains('Kernel32'), failure.stderr
|
||||
assert !failure.stderr.contains('Mach-O'), failure.stderr
|
||||
assert !failure.stderr.contains('PE linker'), failure.stderr
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_user_visible_stderr_reports_captured_fn_literal_unsupported() {
|
||||
$if x64 {
|
||||
failure := run_x64_backend_compile_failure('captured_fn_literal_unsupported', 'module main
|
||||
|
||||
fn make_delta(delta int) fn (int) int {
|
||||
return fn [delta] (value int) int {
|
||||
return value + delta
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
f := make_delta(10)
|
||||
println(f(1))
|
||||
}
|
||||
')
|
||||
assert_x64_user_visible_compile_failure(failure, 'x64: unsupported backend feature: ')
|
||||
assert failure.stderr.contains('native x64 cannot lower captured function literal'), failure.stderr
|
||||
|
||||
assert failure.stderr.contains('closure environments are not implemented yet'), failure.stderr
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_unsupported_backend_feature_message_is_normalized() {
|
||||
assert x64_unsupported_backend_feature_message('stack-passed float parameter') == 'x64: unsupported backend feature: stack-passed float parameter'
|
||||
assert x64_unsupported_backend_feature_message('backend feature: Windows argument lowering') == 'x64: unsupported backend feature: Windows argument lowering'
|
||||
assert x64_unsupported_backend_feature_message('backend feature: SysV direct aggregate call result with MEMORY eightbyte classes is not implemented yet') == 'x64: unsupported backend feature: SysV direct aggregate call result with MEMORY eightbyte classes is not implemented yet'
|
||||
}
|
||||
|
||||
fn test_x64_user_visible_linker_diagnostic_message_for_generic_external_by_format() {
|
||||
for format in [ObjectFormat.elf, .macho, .coff] {
|
||||
msg := x64_unresolved_external_symbol_message(format, 'v_missing_runtime_symbol',
|
||||
'referenced from test relocation')
|
||||
|
||||
assert_x64_clean_user_diagnostic_message(msg)
|
||||
assert_x64_message_mentions_only_target_linker(format, msg)
|
||||
assert msg.contains('cannot resolve external symbol `v_missing_runtime_symbol` yet'), msg
|
||||
assert msg.contains('referenced from test relocation'), msg
|
||||
assert !msg.contains('C stdio/file-descriptor symbol'), msg
|
||||
assert !msg.contains('Windows'), msg
|
||||
assert !msg.contains('Kernel32'), msg
|
||||
assert !msg.contains('C FILE/stdio'), msg
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_user_visible_crt_stdio_and_fd_diagnostic_is_windows_only() {
|
||||
for symbol_name in ['stderr', 'fread', '_get_osfhandle', '_open_osfhandle'] {
|
||||
for format in [ObjectFormat.elf, .macho] {
|
||||
msg := x64_unresolved_external_symbol_message(format, symbol_name,
|
||||
'referenced from test relocation')
|
||||
|
||||
assert_x64_clean_user_diagnostic_message(msg)
|
||||
assert_x64_message_mentions_only_target_linker(format, msg)
|
||||
assert msg.contains('cannot resolve external symbol `${symbol_name}` yet'), msg
|
||||
assert !msg.contains('C stdio/file-descriptor symbol'), msg
|
||||
assert !msg.contains('Kernel32'), msg
|
||||
assert !msg.contains('C FILE/stdio'), msg
|
||||
}
|
||||
|
||||
pe_msg := x64_unresolved_external_symbol_message(.coff, symbol_name,
|
||||
'referenced from test relocation')
|
||||
assert_x64_clean_user_diagnostic_message(pe_msg)
|
||||
assert_x64_message_mentions_only_target_linker(.coff, pe_msg)
|
||||
assert pe_msg.contains('cannot resolve C stdio/file-descriptor symbol `${symbol_name}`'), pe_msg
|
||||
assert pe_msg.contains('Windows x64 native backend uses Kernel32 handles'), pe_msg
|
||||
assert pe_msg.contains('Kernel32 handles'), pe_msg
|
||||
assert pe_msg.contains('C FILE/stdio calls'), pe_msg
|
||||
assert !pe_msg.contains('Linux'), pe_msg
|
||||
assert !pe_msg.contains('macOS'), pe_msg
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_user_visible_linker_diagnostic_message_for_missing_runtime_helper_by_format() {
|
||||
for format in [ObjectFormat.elf, .macho, .coff] {
|
||||
msg := x64_unresolved_external_symbol_message(format, 'builtin__Map_string_int__keys',
|
||||
'referenced from test relocation')
|
||||
|
||||
assert_x64_clean_user_diagnostic_message(msg)
|
||||
assert_x64_message_mentions_only_target_linker(format, msg)
|
||||
assert msg.contains('cannot resolve V runtime helper `builtin__Map_string_int__keys`'), msg
|
||||
assert msg.contains('native x64 backend does not implement this feature for this target yet'), msg
|
||||
assert msg.contains('referenced from test relocation'), msg
|
||||
assert !msg.contains('C stdio/file-descriptor symbol'), msg
|
||||
assert !msg.contains('Kernel32'), msg
|
||||
assert !msg.contains('C FILE/stdio'), msg
|
||||
assert !msg.contains('not imported'), msg
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_gen_detects_missing_runtime_helper_while_preparing_output() {
|
||||
for format in [ObjectFormat.elf, .macho, .coff] {
|
||||
mut mod := mir.Module{}
|
||||
mut gen := Gen.new_with_format(&mod, format)
|
||||
match format {
|
||||
.elf {
|
||||
gen.elf.add_undefined('builtin__Map_string_int__keys')
|
||||
}
|
||||
.macho {
|
||||
gen.macho.add_undefined('_builtin__Map_string_int__keys')
|
||||
}
|
||||
.coff {
|
||||
gen.coff.add_undefined('builtin__Map_string_int__keys')
|
||||
}
|
||||
}
|
||||
|
||||
msg := gen.unsupported_external_symbol_message() or {
|
||||
assert false
|
||||
continue
|
||||
}
|
||||
assert_x64_clean_user_diagnostic_message(msg)
|
||||
assert_x64_message_mentions_only_target_linker(format, msg)
|
||||
assert msg.contains(x64_linker_name(format)), msg
|
||||
assert msg.contains('builtin__Map_string_int__keys'), msg
|
||||
assert msg.contains('cannot resolve V runtime helper `builtin__Map_string_int__keys`'), msg
|
||||
assert msg.contains('native x64 backend does not implement this feature for this target yet'), msg
|
||||
assert msg.contains('needed while preparing native x64 output'), msg
|
||||
}
|
||||
}
|
||||
|
||||
fn test_x64_gen_keeps_linker_resolved_external_symbols_for_external_linkers() {
|
||||
for format in [ObjectFormat.elf, .macho, .coff] {
|
||||
mut mod := mir.Module{}
|
||||
mut gen := Gen.new_with_format(&mod, format)
|
||||
match format {
|
||||
.elf {
|
||||
gen.elf.add_undefined('calloc')
|
||||
}
|
||||
.macho {
|
||||
gen.macho.add_undefined('_calloc')
|
||||
}
|
||||
.coff {
|
||||
gen.coff.add_undefined('calloc')
|
||||
}
|
||||
}
|
||||
|
||||
if msg := gen.unsupported_external_symbol_message() {
|
||||
assert false, msg
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,857 @@
|
||||
// vtest build: false
|
||||
|
||||
module x64
|
||||
|
||||
import os
|
||||
|
||||
fn run_x64_backend_runtime_program(name string, source string) string {
|
||||
tmp_dir := os.join_path(os.vtmp_dir(), 'v_x64_backend_runtime_${name}_${os.getpid()}')
|
||||
os.mkdir_all(tmp_dir) or { panic(err) }
|
||||
defer {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
}
|
||||
source_path := os.join_path(tmp_dir, '${name}.v')
|
||||
bin_path := os.join_path(tmp_dir, name)
|
||||
os.write_file(source_path, source) or { panic(err) }
|
||||
vexe := os.getenv_opt('VEXE') or { @VEXE }
|
||||
build :=
|
||||
os.execute('${os.quoted_path(vexe)} -v2 -b x64 ${os.quoted_path(source_path)} -o ${os.quoted_path(bin_path)}')
|
||||
assert build.exit_code == 0, build.output
|
||||
run :=
|
||||
os.execute('out=$(${os.quoted_path(bin_path)} 2>&1); code=$?; printf "%s\\n%s" "$code" "$out"')
|
||||
assert run.exit_code == 0, '${name}: ${run.output}'
|
||||
lines := run.output.split_into_lines()
|
||||
assert lines.len >= 1, '${name}: ${run.output}'
|
||||
assert lines[0] == '0', '${name}: ${run.output}'
|
||||
return lines[1..].join('\n')
|
||||
}
|
||||
|
||||
fn run_x64_backend_runtime_compile_error(name string, source string) string {
|
||||
tmp_dir := os.join_path(os.vtmp_dir(), 'v_x64_backend_runtime_fail_${name}_${os.getpid()}')
|
||||
os.mkdir_all(tmp_dir) or { panic(err) }
|
||||
defer {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
}
|
||||
source_path := os.join_path(tmp_dir, '${name}.v')
|
||||
bin_path := os.join_path(tmp_dir, name)
|
||||
os.write_file(source_path, source) or { panic(err) }
|
||||
vexe := os.getenv_opt('VEXE') or { @VEXE }
|
||||
build :=
|
||||
os.execute('${os.quoted_path(vexe)} -v2 -b x64 ${os.quoted_path(source_path)} -o ${os.quoted_path(bin_path)}')
|
||||
assert build.exit_code != 0, build.output
|
||||
return build.output
|
||||
}
|
||||
|
||||
fn run_x64_backend_runtime_program_redirected(name string, source string) (string, string) {
|
||||
tmp_dir := os.join_path(os.vtmp_dir(), 'v_x64_backend_runtime_redir_${name}_${os.getpid()}')
|
||||
os.mkdir_all(tmp_dir) or { panic(err) }
|
||||
defer {
|
||||
os.rmdir_all(tmp_dir) or {}
|
||||
}
|
||||
source_path := os.join_path(tmp_dir, '${name}.v')
|
||||
bin_path := os.join_path(tmp_dir, name)
|
||||
stdout_path := os.join_path(tmp_dir, '${name}.out')
|
||||
stderr_path := os.join_path(tmp_dir, '${name}.err')
|
||||
os.write_file(source_path, source) or { panic(err) }
|
||||
vexe := os.getenv_opt('VEXE') or { @VEXE }
|
||||
build :=
|
||||
os.execute('${os.quoted_path(vexe)} -v2 -b x64 ${os.quoted_path(source_path)} -o ${os.quoted_path(bin_path)}')
|
||||
assert build.exit_code == 0, build.output
|
||||
run :=
|
||||
os.execute('${os.quoted_path(bin_path)} > ${os.quoted_path(stdout_path)} 2> ${os.quoted_path(stderr_path)}')
|
||||
assert run.exit_code == 0, run.output
|
||||
stdout := os.read_file(stdout_path) or { panic(err) }
|
||||
stderr := os.read_file(stderr_path) or { panic(err) }
|
||||
return stdout, stderr
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_hello_world_runs() {
|
||||
output := run_x64_backend_runtime_program('hello_world', "module main
|
||||
|
||||
fn main() {
|
||||
println('Hello World!')
|
||||
}
|
||||
")
|
||||
assert output == 'Hello World!'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_hello_world_runs_with_stdout_redirected() {
|
||||
stdout, stderr := run_x64_backend_runtime_program_redirected('hello_world_redir', "module main
|
||||
|
||||
fn main() {
|
||||
println('Hello World!')
|
||||
}
|
||||
")
|
||||
assert stdout == 'Hello World!\n'
|
||||
assert stderr == ''
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_const_init_is_not_skipped() {
|
||||
output := run_x64_backend_runtime_program('const_string', "module main
|
||||
|
||||
const greeting = 'Const hello'
|
||||
|
||||
fn main() {
|
||||
println(greeting)
|
||||
}
|
||||
")
|
||||
assert output == 'Const hello'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_branch_and_local_mutation_pipeline_correctness() {
|
||||
output := run_x64_backend_runtime_program('branch_local_mutation', "module main
|
||||
|
||||
fn adjustment(flag bool) int {
|
||||
if flag {
|
||||
return 7
|
||||
}
|
||||
return -3
|
||||
}
|
||||
|
||||
fn score(a int, b int, flag bool) int {
|
||||
mut total := a * 10
|
||||
total += adjustment(flag)
|
||||
if total > b {
|
||||
return total - b
|
||||
}
|
||||
return b - total
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if score(4, 35, true) == 12 && score(4, 35, false) == 2 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_trunc_and_zext_mask_integer_values() {
|
||||
output := run_x64_backend_runtime_program('int_cast_masks', "module main
|
||||
|
||||
fn narrow_and_widen(x u16) u64 {
|
||||
y := u8(x)
|
||||
return u64(y)
|
||||
}
|
||||
|
||||
fn narrow_u32_to_u16(x u32) u64 {
|
||||
y := u16(x)
|
||||
return u64(y)
|
||||
}
|
||||
|
||||
fn narrow_u64_to_u8(x u64) u64 {
|
||||
y := u8(x)
|
||||
return u64(y)
|
||||
}
|
||||
|
||||
fn sign_extend_i8(x i8) i64 {
|
||||
return i64(x)
|
||||
}
|
||||
|
||||
fn sign_extend_i16(x i16) i64 {
|
||||
return i64(x)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if narrow_and_widen(u16(0x1234)) == 0x34
|
||||
&& narrow_u32_to_u16(u32(0x12345678)) == 0x5678
|
||||
&& narrow_u64_to_u8(u64(0x123456789ABCDEFF)) == 0xFF
|
||||
&& sign_extend_i8(i8(-42)) == -42
|
||||
&& sign_extend_i16(i16(-12345)) == -12345 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_signed_memory_loads_extend_values() {
|
||||
output := run_x64_backend_runtime_program('signed_memory_loads', "module main
|
||||
|
||||
fn load_i8_from_ptr(p &i8) i64 {
|
||||
return i64(*p)
|
||||
}
|
||||
|
||||
fn load_i16_from_ptr(p &i16) i64 {
|
||||
return i64(*p)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut a := i8(-42)
|
||||
mut b := i16(-12345)
|
||||
if load_i8_from_ptr(&a) == -42 && load_i16_from_ptr(&b) == -12345 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_raw_copy_tails_do_not_overwrite_sentinel() {
|
||||
output := run_x64_backend_runtime_program('raw_copy_tails', "module main
|
||||
|
||||
struct Tiny3 {
|
||||
mut:
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
}
|
||||
|
||||
struct Wrap3 {
|
||||
mut:
|
||||
data Tiny3
|
||||
sentinel u8
|
||||
}
|
||||
|
||||
struct Tiny5 {
|
||||
mut:
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
d u8
|
||||
e u8
|
||||
}
|
||||
|
||||
struct Wrap5 {
|
||||
mut:
|
||||
data Tiny5
|
||||
sentinel u8
|
||||
}
|
||||
|
||||
struct Tiny6 {
|
||||
mut:
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
d u8
|
||||
e u8
|
||||
f u8
|
||||
}
|
||||
|
||||
struct Wrap6 {
|
||||
mut:
|
||||
data Tiny6
|
||||
sentinel u8
|
||||
}
|
||||
|
||||
struct Tiny7 {
|
||||
mut:
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
d u8
|
||||
e u8
|
||||
f u8
|
||||
g u8
|
||||
}
|
||||
|
||||
struct Wrap7 {
|
||||
mut:
|
||||
data Tiny7
|
||||
sentinel u8
|
||||
}
|
||||
|
||||
fn check3() bool {
|
||||
src := Tiny3{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
}
|
||||
mut w := Wrap3{
|
||||
sentinel: 0x5A
|
||||
}
|
||||
w.data = src
|
||||
return w.sentinel == 0x5A && w.data.a == 1 && w.data.b == 2 && w.data.c == 3
|
||||
}
|
||||
|
||||
fn check5() bool {
|
||||
src := Tiny5{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
d: 4
|
||||
e: 5
|
||||
}
|
||||
mut w := Wrap5{
|
||||
sentinel: 0x5A
|
||||
}
|
||||
w.data = src
|
||||
return w.sentinel == 0x5A && w.data.a == 1 && w.data.b == 2 && w.data.c == 3
|
||||
&& w.data.d == 4 && w.data.e == 5
|
||||
}
|
||||
|
||||
fn check6() bool {
|
||||
src := Tiny6{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
d: 4
|
||||
e: 5
|
||||
f: 6
|
||||
}
|
||||
mut w := Wrap6{
|
||||
sentinel: 0x5A
|
||||
}
|
||||
w.data = src
|
||||
return w.sentinel == 0x5A && w.data.a == 1 && w.data.b == 2 && w.data.c == 3
|
||||
&& w.data.d == 4 && w.data.e == 5 && w.data.f == 6
|
||||
}
|
||||
|
||||
fn check7() bool {
|
||||
src := Tiny7{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
d: 4
|
||||
e: 5
|
||||
f: 6
|
||||
g: 7
|
||||
}
|
||||
mut w := Wrap7{
|
||||
sentinel: 0x5A
|
||||
}
|
||||
w.data = src
|
||||
return w.sentinel == 0x5A && w.data.a == 1 && w.data.b == 2 && w.data.c == 3
|
||||
&& w.data.d == 4 && w.data.e == 5 && w.data.f == 6 && w.data.g == 7
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if check3() && check5() && check6() && check7() {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_small_aggregates_pass_by_value_in_registers() {
|
||||
output := run_x64_backend_runtime_program('small_aggregate_reg_args', "module main
|
||||
|
||||
struct Tiny3 {
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
}
|
||||
|
||||
struct Tiny5 {
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
d u8
|
||||
e u8
|
||||
}
|
||||
|
||||
struct Tiny6 {
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
d u8
|
||||
e u8
|
||||
f u8
|
||||
}
|
||||
|
||||
struct Tiny7 {
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
d u8
|
||||
e u8
|
||||
f u8
|
||||
g u8
|
||||
}
|
||||
|
||||
fn sum3(t Tiny3) int {
|
||||
return int(t.a) + int(t.b) + int(t.c)
|
||||
}
|
||||
|
||||
fn sum5(t Tiny5) int {
|
||||
return int(t.a) + int(t.b) + int(t.c) + int(t.d) + int(t.e)
|
||||
}
|
||||
|
||||
fn sum6(t Tiny6) int {
|
||||
return int(t.a) + int(t.b) + int(t.c) + int(t.d) + int(t.e) + int(t.f)
|
||||
}
|
||||
|
||||
fn sum7(t Tiny7) int {
|
||||
return int(t.a) + int(t.b) + int(t.c) + int(t.d) + int(t.e) + int(t.f) + int(t.g)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a := Tiny3{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
}
|
||||
b := Tiny5{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
d: 4
|
||||
e: 5
|
||||
}
|
||||
c := Tiny6{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
d: 4
|
||||
e: 5
|
||||
f: 6
|
||||
}
|
||||
d := Tiny7{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
d: 4
|
||||
e: 5
|
||||
f: 6
|
||||
g: 7
|
||||
}
|
||||
if sum3(a) == 6 && sum5(b) == 15 && sum6(c) == 21 && sum7(d) == 28 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_raw_aggregate_arg_does_not_clobber_rcx_arg() {
|
||||
output := run_x64_backend_runtime_program('small_aggregate_after_rcx_arg', "module main
|
||||
|
||||
struct Tiny3 {
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
}
|
||||
|
||||
fn check(a i64, b i64, c i64, marker i64, t Tiny3) i64 {
|
||||
return marker + i64(t.a) + i64(t.b) + i64(t.c)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
t := Tiny3{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
}
|
||||
if check(10, 20, 30, 1000, t) == 1006 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_big17_sret_and_indirect_param_copy_exact_bytes() {
|
||||
output := run_x64_backend_runtime_program('big17_exact_copies', "module main
|
||||
|
||||
struct Big17 {
|
||||
mut:
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
d u8
|
||||
e u8
|
||||
f u8
|
||||
g u8
|
||||
h u8
|
||||
i u8
|
||||
j u8
|
||||
k u8
|
||||
l u8
|
||||
m u8
|
||||
n u8
|
||||
o u8
|
||||
p u8
|
||||
q u8
|
||||
}
|
||||
|
||||
fn make_big() Big17 {
|
||||
return Big17{
|
||||
a: 1
|
||||
b: 2
|
||||
c: 3
|
||||
d: 4
|
||||
e: 5
|
||||
f: 6
|
||||
g: 7
|
||||
h: 8
|
||||
i: 9
|
||||
j: 10
|
||||
k: 11
|
||||
l: 12
|
||||
m: 13
|
||||
n: 14
|
||||
o: 15
|
||||
p: 16
|
||||
q: 17
|
||||
}
|
||||
}
|
||||
|
||||
fn id_big(x Big17) Big17 {
|
||||
return x
|
||||
}
|
||||
|
||||
fn sum_big(x Big17) int {
|
||||
return int(x.a) + int(x.b) + int(x.c) + int(x.d) + int(x.e) + int(x.f) + int(x.g)
|
||||
+ int(x.h) + int(x.i) + int(x.j) + int(x.k) + int(x.l) + int(x.m) + int(x.n)
|
||||
+ int(x.o) + int(x.p) + int(x.q)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
x := make_big()
|
||||
y := id_big(x)
|
||||
if sum_big(y) == 153 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_fixed_array_store_uses_raw_copy() {
|
||||
output := run_x64_backend_runtime_program('fixed_array_store', "module main
|
||||
|
||||
fn main() {
|
||||
mut src := [3]u8{}
|
||||
src[0] = 1
|
||||
src[1] = 2
|
||||
src[2] = 3
|
||||
mut dst := [3]u8{}
|
||||
dst = src
|
||||
if dst[0] == 1 && dst[1] == 2 && dst[2] == 3 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_heap_sizing_allows_struct_access_past_eight_bytes() {
|
||||
output := run_x64_backend_runtime_program('heap_struct_size', "module main
|
||||
|
||||
struct Heap17 {
|
||||
mut:
|
||||
a u8
|
||||
b u8
|
||||
c u8
|
||||
d u8
|
||||
e u8
|
||||
f u8
|
||||
g u8
|
||||
h u8
|
||||
i u8
|
||||
j u8
|
||||
k u8
|
||||
l u8
|
||||
m u8
|
||||
n u8
|
||||
o u8
|
||||
p u8
|
||||
q u8
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut p := &Heap17{}
|
||||
p.q = 17
|
||||
if p.q == 17 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_float_width_casts_convert_values() {
|
||||
output := run_x64_backend_runtime_program('float_width_casts', "module main
|
||||
|
||||
fn widen(x f32) f64 {
|
||||
return f64(x)
|
||||
}
|
||||
|
||||
fn narrow(x f64) f32 {
|
||||
return f32(x)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a := widen(f32(1.5))
|
||||
b := narrow(a + 0.25)
|
||||
if a > 1.49 && a < 1.51 && b > f32(1.74) && b < f32(1.76) {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_scalar_float_abi_uses_xmm_registers() {
|
||||
output := run_x64_backend_runtime_program('scalar_float_abi', "module main
|
||||
|
||||
fn from_f64_u32(x f64) u32 {
|
||||
return u32(x)
|
||||
}
|
||||
|
||||
fn id_f64(x f64) f64 {
|
||||
return x
|
||||
}
|
||||
|
||||
fn id_f32(x f32) f32 {
|
||||
return x
|
||||
}
|
||||
|
||||
fn mixed(a i64, x f64, b i64, y f32, c i64) i64 {
|
||||
if x > 1.24 && x < 1.26 && y > f32(2.49) && y < f32(2.51) {
|
||||
return a + b + c
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
fn main() {
|
||||
x := 4000000000.0
|
||||
local := u32(x) == u32(4000000000)
|
||||
low := from_f64_u32(42.0) == u32(42)
|
||||
high := from_f64_u32(4000000000.0) == u32(4000000000)
|
||||
ret64 := id_f64(1.25) > 1.24 && id_f64(1.25) < 1.26
|
||||
ret32 := id_f32(f32(2.5)) > f32(2.49) && id_f32(f32(2.5)) < f32(2.51)
|
||||
mixed_ok := mixed(10, 1.25, 20, f32(2.5), 30) == 60
|
||||
if local && low && high && ret64 && ret32 && mixed_ok {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_scalar_float_stack_args_fail_explicitly() {
|
||||
output := run_x64_backend_runtime_compile_error('float_stack_arg_unsupported', 'module main
|
||||
|
||||
fn many(a f64, b f64, c f64, d f64, e f64, f f64, g f64, h f64, i f64) f64 {
|
||||
return a + b + c + d + e + f + g + h + i
|
||||
}
|
||||
|
||||
fn main() {
|
||||
_ = many(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0)
|
||||
}
|
||||
')
|
||||
assert output.contains('x64: unsupported backend feature: stack-passed float parameter')
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_float_comparisons_use_numeric_semantics() {
|
||||
output := run_x64_backend_runtime_program('float_comparisons', "module main
|
||||
|
||||
import os
|
||||
|
||||
fn eq_f64(a f64, b f64) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
fn ne_f64(a f64, b f64) bool {
|
||||
return a != b
|
||||
}
|
||||
|
||||
fn lt_f64(a f64, b f64) bool {
|
||||
return a < b
|
||||
}
|
||||
|
||||
fn gt_f64(a f64, b f64) bool {
|
||||
return a > b
|
||||
}
|
||||
|
||||
fn le_f64(a f64, b f64) bool {
|
||||
return a <= b
|
||||
}
|
||||
|
||||
fn ge_f64(a f64, b f64) bool {
|
||||
return a >= b
|
||||
}
|
||||
|
||||
fn le_f32(a f32, b f32) bool {
|
||||
return a <= b
|
||||
}
|
||||
|
||||
fn gt_f32(a f32, b f32) bool {
|
||||
return a > b
|
||||
}
|
||||
|
||||
fn ge_f32(a f32, b f32) bool {
|
||||
return a >= b
|
||||
}
|
||||
|
||||
fn eq_f32(a f32, b f32) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
fn ne_f32(a f32, b f32) bool {
|
||||
return a != b
|
||||
}
|
||||
|
||||
fn lt_f32(a f32, b f32) bool {
|
||||
return a < b
|
||||
}
|
||||
|
||||
fn main() {
|
||||
argc := os.args.len
|
||||
z := f64(argc - argc)
|
||||
one := f64(argc)
|
||||
two := one + one
|
||||
neg_zero := -z
|
||||
nan := z / z
|
||||
ok64 := eq_f64(z, neg_zero) && ne_f64(nan, nan) && lt_f64(-two, -one)
|
||||
&& gt_f64(-one, -two) && le_f64(z, neg_zero) && ge_f64(z, neg_zero)
|
||||
nan64 := !eq_f64(nan, nan) && ne_f64(nan, nan) && !lt_f64(nan, one)
|
||||
&& !le_f64(nan, one) && !gt_f64(nan, one) && !ge_f64(nan, one)
|
||||
nanf := f32(nan)
|
||||
onef := f32(one)
|
||||
ok32 := eq_f32(f32(z), f32(neg_zero)) && lt_f32(f32(-two), f32(-one))
|
||||
nan32 := !eq_f32(nanf, nanf) && ne_f32(nanf, nanf) && !lt_f32(nanf, onef)
|
||||
&& !le_f32(nanf, onef) && !gt_f32(nanf, onef) && !ge_f32(nanf, onef)
|
||||
if ok64 && nan64 && ok32 && nan32 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_unsigned_64_to_float_handles_high_bit() {
|
||||
output := run_x64_backend_runtime_program('u64_to_float_high_bit', "module main
|
||||
|
||||
fn to_f64(x u64) f64 {
|
||||
return f64(x)
|
||||
}
|
||||
|
||||
fn to_f32(x u64) f32 {
|
||||
return f32(x)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a := to_f64(u64(9223372036854775808))
|
||||
b := to_f64(u64(18446744073709551615))
|
||||
c := to_f32(u64(9223372036854775808))
|
||||
if a == 9223372036854775808.0 && b > 18446744073709550000.0 && c > f32(9223371000000000000.0) {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_float_to_unsigned_64_handles_high_bit() {
|
||||
output := run_x64_backend_runtime_program('float_to_u64_high_bit', "module main
|
||||
|
||||
fn from_f64(x f64) u64 {
|
||||
return u64(x)
|
||||
}
|
||||
|
||||
fn from_f32(x f32) u64 {
|
||||
return u64(x)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
a := from_f64(9223372036854775808.0)
|
||||
b := from_f64(9223372036854777856.0)
|
||||
c := from_f32(f32(9223372036854775808.0))
|
||||
if a == u64(9223372036854775808) && b == u64(9223372036854777856)
|
||||
&& c == u64(9223372036854775808) {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_aggregate_arg_spills_to_stack() {
|
||||
output := run_x64_backend_runtime_program('aggregate_arg_spill', "module main
|
||||
|
||||
struct Pair {
|
||||
a i64
|
||||
b i64
|
||||
}
|
||||
|
||||
fn sum_after_five(a i64, b i64, c i64, d i64, e i64, p Pair) i64 {
|
||||
return a + b + c + d + e + p.a + p.b
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if sum_after_five(1, 2, 3, 4, 5, Pair{6, 7}) == 28 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_stack_arg_after_two_chunk_aggregate() {
|
||||
output := run_x64_backend_runtime_program('stack_arg_after_pair', "module main
|
||||
|
||||
struct Pair {
|
||||
a i64
|
||||
b i64
|
||||
}
|
||||
|
||||
fn sum_pair_then_scalars(p Pair, a i64, b i64, c i64, d i64, e i64) i64 {
|
||||
return p.a + p.b + a + b + c + d + e
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if sum_pair_then_scalars(Pair{10, 20}, 1, 2, 3, 4, 5) == 45 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
|
||||
fn test_x64_backend_runtime_spilled_aggregate_does_not_consume_remaining_register() {
|
||||
output := run_x64_backend_runtime_program('aggregate_spill_then_reg', "module main
|
||||
|
||||
struct Pair {
|
||||
a i64
|
||||
b i64
|
||||
}
|
||||
|
||||
fn sum_spill_then_reg(a i64, b i64, c i64, d i64, e i64, p Pair, z i64) i64 {
|
||||
return p.a + p.b + z
|
||||
}
|
||||
|
||||
fn main() {
|
||||
if sum_spill_then_reg(1, 2, 3, 4, 5, Pair{10, 20}, 6) == 36 {
|
||||
println('ok')
|
||||
} else {
|
||||
println('bad')
|
||||
}
|
||||
}
|
||||
")
|
||||
assert output == 'ok'
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user