Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+103
View File
@@ -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
+257
View File
@@ -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
}
+409
View File
@@ -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
+284
View File
@@ -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