native: add support for (elf) globals (#25016)

This commit is contained in:
Eliyaan (Nopana) 2025-08-01 05:38:49 +02:00 committed by GitHub
parent 5ec3cc17e1
commit c49b9da04e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 159 additions and 28 deletions

View File

@ -334,7 +334,7 @@ fn (mut c Amd64) cmp_var_reg(var Var, reg Register, config VarConfig) {
c.g.println('cmp var `${var.name}`, ${reg}')
}
GlobalVar {
// TODO
c.g.n_error('${@LOCATION} unsupported var type ${var}')
}
ExternVar {
c.g.n_error('${@LOCATION} unsupported var type ${var}')
@ -383,7 +383,7 @@ fn (mut c Amd64) cmp_var(var Var, val i32, config VarConfig) {
c.g.println('cmp var `${var.name}` ${val}')
}
GlobalVar {
// TODO
c.g.n_error('${@LOCATION} unsupported var type ${var}')
}
ExternVar {
c.g.n_error('${@LOCATION} unsupported var type ${var}')
@ -433,7 +433,7 @@ fn (mut c Amd64) dec_var(var Var, config VarConfig) {
c.g.println('dec_var `${var.name}`')
}
GlobalVar {
// TODO
c.g.n_error('${@LOCATION} unsupported var type ${var}')
}
ExternVar {
c.g.n_error('${@LOCATION} unsupported var type ${var}')
@ -507,8 +507,7 @@ fn (mut c Amd64) inc_var(var Var, config VarConfig) {
c.g.println('inc_var ${size_str} `${var.name}`')
}
GlobalVar {
c.g.n_error('${@LOCATION} Global variables incrementation is not supported yet')
// TODO
c.g.n_error('${@LOCATION} unsupported var type ${var}')
}
ExternVar {
c.g.n_error('${@LOCATION} unsupported var type ${var}')
@ -794,8 +793,24 @@ fn (mut c Amd64) mov_reg_to_var(var Var, r Register, config VarConfig) {
c.g.println('mov ${size_str} PTR [rbp-${int(offset).hex2()}],${reg} ; `${var.name}`')
}
GlobalVar {
// TODO
c.g.n_error('${@LOCATION} Unsupported GlobalVar')
size := match c.g.get_type_size(var.typ) {
1 { Size._8 }
2 { Size._16 }
4 { Size._32 }
8 { Size._64 }
else { c.g.n_error('${@LOCATION} unsupported size of global var') }
}
mut addr_reg := Amd64Register.rdx
if reg == .rdx || reg == .edx {
addr_reg = .rax
}
c.push(addr_reg)
c.g.global_vars[c.g.pos() + 2] = var.name // +2 for the mov64 instruction
c.mov64(addr_reg, i64(0))
c.g.println('; will get patched by relocs')
c.mov_store(addr_reg, reg, size)
c.pop(addr_reg)
c.g.println('; mov global:`${var.name}` ${reg}')
}
ExternVar {
c.g.n_error('${@LOCATION} unsupported var type ${var}')
@ -886,7 +901,7 @@ fn (mut c Amd64) mov_int_to_var(var Var, integer i32, config VarConfig) {
}
}
GlobalVar {
// TODO
c.g.n_error('${@LOCATION} unsupported var type ${var}')
}
ExternVar {
c.g.n_error('${@LOCATION} unsupported var type ${var}')
@ -1009,7 +1024,20 @@ fn (mut c Amd64) mov_var_to_reg(reg Register, var Var, config VarConfig) {
c.g.println('${instruction} ${reg}, ${size_str} PTR [rbp-${int(offset).hex2()}] ; `${var.name}`')
}
GlobalVar {
c.g.n_error('${@LOCATION} Unsupported GlobalVar')
if c.g.get_type_size(var.typ) > 8 {
c.g.n_error('${@LOCATION} unsupported size of global var')
}
mut addr_reg := Amd64Register.rdx
if reg as Amd64Register == .rdx || reg as Amd64Register == .edx {
addr_reg = .rax
}
c.push(addr_reg)
c.g.global_vars[c.g.pos() + 2] = var.name // +2 for the mov64 instruction
c.mov64(addr_reg, i64(0))
c.g.println('; will get patched by relocs')
c.mov_deref(reg, addr_reg, var.typ)
c.pop(addr_reg)
c.g.println('; mov ${reg} global:`${var.name}`')
}
ExternVar {
c.g.n_error('${@LOCATION} unsupported var type ${var}')
@ -3859,7 +3887,7 @@ fn (mut c Amd64) init_struct(var Var, init ast.StructInit) {
}
}
GlobalVar {
c.g.n_error('${@LOCATION} GlobalVar not implemented for ast.StructInit')
c.g.n_error('${@LOCATION} unsupported var type ${var}')
}
ExternVar {
c.g.n_error('${@LOCATION} unsupported var type ${var}')
@ -3932,7 +3960,7 @@ fn (mut c Amd64) init_array(var Var, node ast.ArrayInit) {
}
}
GlobalVar {
c.g.n_error('${@LOCATION} GlobalVar not implemented for ast.ArrayInit')
c.g.n_error('${@LOCATION} unsupported var type ${var}')
}
ExternVar {
c.g.n_error('${@LOCATION} unsupported var type ${var}')
@ -4265,7 +4293,7 @@ fn (mut c Amd64) mov_ssereg_to_var(var Var, reg Amd64SSERegister, config VarConf
c.g.println('${inst} [rbp-${int(offset).hex2()}], ${reg}')
}
GlobalVar {
// TODO
c.g.n_error('${@LOCATION} unsupported var type ${var}')
}
ExternVar {
c.g.n_error('${@LOCATION} unsupported var type ${var}')
@ -4321,7 +4349,7 @@ fn (mut c Amd64) mov_var_to_ssereg(reg Amd64SSERegister, var Var, config VarConf
c.g.println('${inst} ${reg}, [rbp-${int(offset).hex2()}]')
}
GlobalVar {
// TODO
c.g.n_error('${@LOCATION} unsupported var type ${var}')
}
ExternVar {
c.g.n_error('${@LOCATION} unsupported var type ${var}')

View File

@ -55,6 +55,13 @@ const blacklist = {
'string.last_index': true
'string.last_index_u8': false
'string.contains_u8': false
'malloc_noscan': true
'vmemcpy': false
'eprint': false
'eprintln': false
'_write_buf_to_fd': false
'_writeln_to_fd': false
'_memory_panic': true
}
const windows_blacklist = {

View File

@ -4,6 +4,7 @@
module native
import os
import ast
const elf_class32 = 1
const elf_class64 = 2
@ -300,6 +301,8 @@ mut:
size i64 // Symbol size.
}
// see for more details: https://gist.github.com/x0nu11byt3/bcb35c3de461e5fb66173071a2379779
// https://www.etherington.xyz/elfguide#symtab-section-entries
fn (mut g Gen) create_symbol_table_section(str_name string, info u8, bind u8, other i8, value i64, size i64,
shndx i16) SymbolTableSection {
return SymbolTableSection{
@ -615,7 +618,6 @@ fn (mut g Gen) gen_section_data(sections []Section) {
for rela in data {
g.write64(rela.offset)
g.fn_addr[rela.name] = rela.offset // that's wierd it's the call offset, not the fn
g.fn_names << rela.name
g.write64(rela.info)
g.write64(rela.addend)
g.println('; SHT_RELA `${rela.name}` (${rela.offset}, ${rela.info}, ${rela.addend})')
@ -711,6 +713,23 @@ pub fn (mut g Gen) generate_linkable_elf_header() {
elf_stv_default, 0, 0, 0),
]
mut sym_data_offset := 0 // offset from the beggining of the data section
for f in g.files {
for s in f.stmts {
if s is ast.GlobalDecl {
for fi in s.fields {
size := g.get_type_size(fi.typ)
if size > 0 {
g.symbol_table << g.create_symbol_table_section(fi.name, elf_stt_object,
elf_stb_global, elf_stv_default, sym_data_offset, size, i16(g.find_section_header('.data',
sections)))
}
sym_data_offset += size
}
}
}
}
for symbol in g.extern_symbols {
g.symbol_table << g.create_symbol_table_section(symbol[2..], elf_stt_notype, elf_stb_global,
elf_stv_default, 0, 0, 0)
@ -742,6 +761,37 @@ pub fn (mut g Gen) generate_linkable_elf_header() {
g.elf_rela_section = sections[g.find_section_header('.rela.text', sections)]
// Init data section
// TODO: use .bss for uninitialized data or generate the data
data_section := sections[g.find_section_header('.data', sections)]
g.elf_data_header_addr = data_section.header.offset
data_pos := g.pos()
g.println('\ndata_start_pos = ${data_pos.hex()}')
g.write64_at(g.elf_data_header_addr + 24, data_pos)
for f in g.files {
for s in f.stmts {
if s is ast.GlobalDecl {
for fi in s.fields {
size := g.get_type_size(fi.typ)
match size {
1 { g.write8(0xF) }
2 { g.write16(0xF) }
4 { g.write32(0xF) }
8 { g.write64(i64(0xF)) }
else { println('${@LOCATION} unsupported size ${size} for global ${fi}') }
}
g.println('; global ${fi.name}, size: ${size}')
}
}
}
}
g.write64_at(g.elf_data_header_addr + 32, g.pos() - data_pos)
// TODO: pre-calculate .data instead of genetating it at runtime
// It is wierd, the init section has something in it but I did not find
// where does it come from
// user code starts here
if g.pref.is_verbose {
eprintln('code_start_pos = ${g.buf.len.hex()}')
@ -752,11 +802,29 @@ pub fn (mut g Gen) generate_linkable_elf_header() {
// if g.start_symbol_addr > 0 {
// g.write64_at(g.start_symbol_addr + native.elf_symtab_size - 16, g.code_start_pos)
//}
text_section := sections[g.find_section_header('.text', sections)]
g.elf_text_header_addr = text_section.header.offset
g.write64_at(g.elf_text_header_addr + 24, g.pos()) // write the code start pos to the text section
g.println('; fill .data')
for f in g.files {
for s in f.stmts {
if s is ast.GlobalDecl {
for fi in s.fields {
size := g.get_type_size(fi.typ)
if size > 8 || size <= 0 {
println('${@LOCATION} unsupported size ${size} for global ${fi}')
} else if fi.expr !is ast.EmptyExpr {
g.expr(fi.expr)
g.code_gen.mov_reg_to_var(GlobalVar{fi.name, fi.typ}, g.code_gen.main_reg())
g.println('; global ${fi.name}, size: ${size}')
}
}
}
}
}
g.elf_main_call_pos = g.pos()
g.code_gen.call(placeholder)
g.println('; call main.main')
g.code_gen.mov64(g.code_gen.main_reg(), i64(0))
@ -848,9 +916,13 @@ pub fn (mut g Gen) gen_rela_section() {
g.symtab_get_index(g.symbol_table, symbol[2..]), elf_r_amd64_gotpcrelx, -4)
}
for var_pos, symbol in g.extern_vars {
relocations << g.create_rela_section(symbol, var_pos - g.code_start_pos + 2, g.symtab_get_index(g.symbol_table,
relocations << g.create_rela_section(symbol, var_pos - g.code_start_pos, g.symtab_get_index(g.symbol_table,
symbol[2..]), elf_r_amd64_64, 0)
}
for var_pos, symbol in g.global_vars {
relocations << g.create_rela_section(symbol, var_pos - g.code_start_pos, g.symtab_get_index(g.symbol_table,
symbol), elf_r_amd64_64, 0)
}
g.elf_rela_section.data = relocations
g.gen_section_data([g.elf_rela_section])
}
@ -872,7 +944,7 @@ pub fn (mut g Gen) generate_elf_footer() {
g.write64_at(g.file_size_pos + 8, file_size)
if g.pref.arch == .arm64 {
bl_next := u32(0x94000001)
g.write32_at(g.code_start_pos, i32(bl_next))
g.write32_at(g.elf_main_call_pos, i32(bl_next))
} else {
// amd64
// call main function, it's not guaranteed to be the first
@ -880,7 +952,7 @@ pub fn (mut g Gen) generate_elf_footer() {
// now need to replace "0" with a relative address of the main function
// +1 is for "e8"
// -5 is for "e8 00 00 00 00"
g.write32_at(g.code_start_pos + 1, i32(g.main_fn_addr - g.code_start_pos) - 5)
g.write32_at(g.elf_main_call_pos + 1, i32(g.main_fn_addr - g.elf_main_call_pos) - 5)
}
g.create_executable()
}

View File

@ -196,7 +196,7 @@ fn (mut g Gen) local_var_ident(ident ast.Ident, var LocalVar) {
fn (mut g Gen) extern_var_ident(var ExternVar) {
if g.pref.os == .linux {
main_reg := g.code_gen.main_reg()
g.extern_vars[g.pos()] = var.name
g.extern_vars[g.pos() + 2] = var.name // + 2 for the mov64 instruction
g.code_gen.mov64(main_reg, Number(i64(0)))
g.code_gen.mov_deref(main_reg, main_reg, ast.u64_type_idx)
} else if g.pref.os == .macos {

View File

@ -39,8 +39,9 @@ mut:
extern_symbols []string
linker_include_paths []string
linker_libs []string
extern_vars map[i64]string
extern_fn_calls map[i64]string
global_vars map[i64]string // used to patch
extern_vars map[i64]string // used to patch
extern_fn_calls map[i64]string // used to patch
fn_addr map[string]i64
fn_names []string
var_offset map[string]i32 // local var stack offset
@ -65,7 +66,9 @@ mut:
return_type ast.Type
comptime_omitted_branches []ast.IfBranch
// elf specific
elf_main_call_pos i64
elf_text_header_addr i64 = -1
elf_data_header_addr i64 = -1
elf_rela_section Section
// macho specific
macho_ncmds i32
@ -253,7 +256,10 @@ struct PreprocVar {
val i64
}
struct GlobalVar {}
struct GlobalVar {
name string
typ ast.Type
}
@[params]
struct VarConfig {
@ -300,7 +306,9 @@ fn (mut g Gen) get_var_from_ident(ident ast.Ident) IdentVar {
return PreprocVar{ident.info.typ, ident.name, preprocessed_val}
}
mut obj := ident.obj
if obj !in [ast.Var, ast.ConstField, ast.GlobalField, ast.AsmRegister] {
if obj is ast.GlobalField {
return GlobalVar{obj.name, obj.typ}
} else if obj !in [ast.Var, ast.ConstField, ast.AsmRegister] {
obj = ident.scope.find(ident.name) or {
g.n_error('${@LOCATION} unknown variable ${ident.name}')
}
@ -389,6 +397,7 @@ pub fn gen(files []&ast.File, mut table ast.Table, out_name string, pref_ &pref.
g.init_builtins()
g.calculate_all_size_align()
g.calculate_enum_fields()
g.fn_names = g.table.fns.keys()
for file in g.files {
/*
if file.warnings.len > 0 {
@ -1175,7 +1184,6 @@ fn (mut g Gen) fn_decl(node ast.FnDecl) {
g.stack_var_pos = 0
g.stack_depth = 0
g.register_function_address(name)
g.fn_names << name
g.labels = &LabelTable{}
g.defer_stmts.clear()
g.return_type = node.return_type
@ -1225,6 +1233,7 @@ fn (mut g Gen) println(comment string) {
@[noreturn]
pub fn (mut g Gen) n_error(s string) {
print_backtrace()
flush_stdout()
util.verror('native error', s)
}

View File

@ -106,8 +106,10 @@ fn (mut g Gen) stmt(node ast.Stmt) {
g.gen_assert(node)
}
ast.GlobalDecl {
if !g.is_builtin_mod && !g.pref.experimental {
g.warning('globals are not supported yet', node.pos)
if g.pref.os == .linux {
// handled in elf generator
} else if !g.is_builtin_mod && !g.pref.experimental {
g.warning('global variables are not supported', node.pos)
}
}
ast.Import {} // do nothing here

View File

@ -20,10 +20,23 @@ fn test_c_extern_vars() {
C.fprintf(C.stdout, s.str, 3)
}
fn test_prints() {
print_character(`a`)
}
__global foo = 3
fn test_globals() {
assert foo == 3
foo = 5
assert foo == 5
}
fn main() {
test_for_c_in_string()
test_c_extern_vars()
test_prints()
test_globals()
flush_stderr()
flush_stdout()
print_character(`a`)
}

View File

@ -80,7 +80,7 @@ fn test_native() {
work_test_path := os.join_path(wrkdir, test_file_name)
exe_test_path := os.join_path(wrkdir, test_file_name + '.exe')
tmperrfile := os.join_path(dir, test + '.tmperr')
cmd := '${os.quoted_path(vexe)} -o ${os.quoted_path(exe_test_path)} -b native ${os.quoted_path(full_test_path)} -d no_backtrace -d custom_define 2> ${os.quoted_path(tmperrfile)}'
cmd := '${os.quoted_path(vexe)} -enable-globals -o ${os.quoted_path(exe_test_path)} -b native ${os.quoted_path(full_test_path)} -d no_backtrace -d custom_define 2> ${os.quoted_path(tmperrfile)}'
if is_verbose {
println(cmd)
}