// SPDX-License-Identifier: LGPL-2.1-or-later
// Object file emission: sections, symbols, relocations.
// Architecture-independent layer used by asm.c, elf_write.c, macho_write.c.
#ifndef OBJ_H
#define OBJ_H

#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>

// ---------------------------------------------------------------------------
// Growable byte buffer
// ---------------------------------------------------------------------------
typedef struct SecBuf SecBuf;
struct SecBuf {
    uint8_t *data;
    size_t len;
    size_t cap;
    // ELF sh_addralign: the max alignment any secbuf_align() call has ever
    // requested for this section. Starts at 1 (no constraint), never
    // lowered. Consumed by elf_write.c's .data/.rodata/.tdata section
    // headers -- content within one compilation unit can be correctly
    // self-padded to N bytes, but if the section header still claims
    // sh_addralign=1, a linker merging this .o with others is free to
    // start this section's bytes at any offset relative to a preceding
    // object's section, silently destroying that intra-file padding
    // (confirmed: a wide string literal's own 4-byte padding was correct
    // per-object, but glibc's vectorized wcslen() still misread it once
    // linked alongside other .rodata-bearing objects, because the final
    // image's copy of this section didn't start 4-byte aligned).
    uint32_t align;
};

void secbuf_init(SecBuf *s);
void secbuf_free(SecBuf *s);
void secbuf_reserve(SecBuf *s, size_t extra);
size_t secbuf_emit8(SecBuf *s, uint8_t v);
size_t secbuf_emit16le(SecBuf *s, uint16_t v);
size_t secbuf_emit32le(SecBuf *s, uint32_t v);
size_t secbuf_emit64le(SecBuf *s, uint64_t v);
void secbuf_emitbuf(SecBuf *s, const void *buf, size_t n);
void secbuf_align(SecBuf *s, int align); // pad with zeros
// Patch a previously emitted 32-bit value at byte offset `off`
void secbuf_patch32le(SecBuf *s, size_t off, uint32_t v);
void secbuf_patch64le(SecBuf *s, size_t off, uint64_t v);

// ---------------------------------------------------------------------------
// Section indices (logical IDs, mapped to ELF/Mach-O section indices later)
// ---------------------------------------------------------------------------
#define SEC_UNDEF      -1 // undefined / external symbol
#define SEC_TEXT        0
#define SEC_DATA        1
#define SEC_BSS         2
#define SEC_RODATA      3
#define SEC_INIT_ARRAY  4
#define SEC_FINI_ARRAY  5
#define SEC_TDATA       6
#define SEC_THREAD_VARS  7
#define SEC_NUM         8 // number of built-in sections; dynamic ones start here

// ELF sh_flags bits needed to describe a dynamically-registered section
// (shared with elf_write.c, which used to define its own private copies).
#define SHF_WRITE     0x1
#define SHF_ALLOC     0x2
#define SHF_EXECINSTR 0x4
#define SHF_MERGE     0x10
#define SHF_STRINGS   0x20
#define SHF_INFO_LINK 0x40
#define SHF_TLS       0x400

// A section registered at runtime by name (GAS `.section`/`.pushsection`
// with a name rcc has no built-in slot for, e.g. the kernel's __ex_table,
// .fixup, .altinstructions, ...). Indexed starting at SEC_NUM: section id
// `SEC_NUM + i` is `obj->extra_secs[i]`.
typedef struct ObjReloc ObjReloc; // full definition below
typedef struct ExtraSection ExtraSection;
struct ExtraSection {
    char *name;
    SecBuf buf;
    ObjReloc *relocs;
    int reloc_count, reloc_cap;
    uint32_t sh_flags;
    uint32_t sh_entsize; // 0 if none (GAS ".pushsection NAME, FLAGS, @TYPE, ENTSIZE")
    // ELF sh_addralign: the section's own load-address alignment
    // requirement, i.e. the max alignment any global/`.balign` placed
    // into it has ever requested. Starts at 1 (no constraint) and is
    // raised via objfile_section_align() as content is added — never
    // lowered. A `__attribute__((section("name")))` global with e.g.
    // 8-byte-aligned fields (pointers, size_t) needs this to be >= 8, or
    // the linker may place the section (and thus every object in it) at
    // an address whose low tagged-pointer bits are no longer zero.
    uint32_t align;
};

// ---------------------------------------------------------------------------
// Symbol table
// ---------------------------------------------------------------------------
typedef enum { SB_LOCAL = 0,
               SB_GLOBAL = 1,
               SB_WEAK = 2 } SymBind;
typedef enum { ST_NOTYPE = 0,
               ST_OBJECT = 1,
               ST_FUNC = 2,
               ST_SECTION = 3,
               ST_TLS = 6,
               ST_GNU_IFUNC = 10 } SymType;

typedef struct ObjSym ObjSym;
struct ObjSym {
    char *name;
    int section; // SEC_TEXT / SEC_DATA / SEC_BSS / SEC_UNDEF
    uint64_t offset; // byte offset within section (0 for SEC_UNDEF)
    uint64_t size;
    SymBind bind;
    SymType type;
    // True once a *real* `.globl`/`.global`/`.weak` directive has set this
    // symbol's binding explicitly. ensure_sym() (asm.c) also creates
    // not-yet-defined forward-referenced symbols with bind=SB_GLOBAL as a
    // speculative default (needed so a reference that turns out to be a
    // genuinely external, never-locally-defined symbol still links), but
    // that guess must never survive into a label that's later defined in
    // this same file *without* an explicit .globl — define_label() uses
    // this flag to tell "confirmed global" from "assumed global until
    // proven local" and downgrade the latter back to SB_LOCAL.
    bool bind_pinned;
    // ELF symbol visibility (STV_DEFAULT/STV_INTERNAL/STV_HIDDEN/
    // STV_PROTECTED, low 2 bits of st_other) — set by an explicit
    // `.hidden`/`.internal`/`.protected` directive. GMP's own hand-written
    // asm relies on this: its PROTECT() macro expands to `.hidden`, used
    // on file-local-but-cross-object-file data tables (e.g.
    // mpn_invert_limb_table) that are referenced via a plain PC32 `%rip`
    // LEA — a GLOBAL *default*-visibility symbol referenced that way in a
    // shared object is rejected by the linker ("recompile with -fPIC",
    // since a default-visibility global could be interposed by another
    // shared object, which a bare PC32 relocation can't express); HIDDEN
    // tells the linker this symbol can never be interposed, making the
    // direct PC32 reference valid despite the GLOBAL binding.
    uint8_t visibility;
};

// ---------------------------------------------------------------------------
// Relocation entry
// ---------------------------------------------------------------------------
typedef struct ObjReloc ObjReloc;
struct ObjReloc {
    uint64_t offset; // byte offset in section where reloc applies
    int sym_idx; // index into ObjFile.syms[]
    uint32_t type; // platform reloc type (R_AARCH64_* or R_X86_64_*)
    int64_t addend;
};

// ELF AArch64 relocation types
#define R_AARCH64_NONE                     0
#define R_AARCH64_ABS64                  257
#define R_AARCH64_ABS32                  258
#define R_AARCH64_PREL32                 261
#define R_AARCH64_JUMP26                 282
#define R_AARCH64_CALL26                 283
#define R_AARCH64_ADR_PREL_PG_HI21       275
#define R_AARCH64_ADD_ABS_LO12_NC        277
#define R_AARCH64_LDST64_ABS_LO12_NC     286
#define R_AARCH64_LDST32_ABS_LO12_NC     285
#define R_AARCH64_LDST16_ABS_LO12_NC     284
#define R_AARCH64_LDST8_ABS_LO12_NC      278
#define R_AARCH64_ADR_GOT_PAGE           311
#define R_AARCH64_LD64_GOT_LO12_NC       312
#define R_AARCH64_TLSLE_ADD_TPREL_HI12 549
#define R_AARCH64_TLSLE_ADD_TPREL_LO12 550

// ELF x86-64 relocation types
#define R_X86_64_NONE       0
#define R_X86_64_64         1
#define R_X86_64_PC32       2
#define R_X86_64_GOT32      3
#define R_X86_64_PLT32      4
#define R_X86_64_32        10
#define R_X86_64_32S       11
#define R_X86_64_GOTTPOFF  22
#define R_X86_64_TPOFF32   23
#define R_X86_64_GOTPCREL   9
#define R_X86_64_PC64      24

// Fixup kinds (used internally by the assembler, defined in asm.c)
#define FIXUP_REL32      1
#define FIXUP_ABS64      2
#define FIXUP_ARM64_B26  3
#define FIXUP_ARM64_B19  4
#define FIXUP_LABELDIFF  5 // patch = offset(label2) - offset(label); width in `size`
#define FIXUP_SKIP_MAXDIFF 6 // insert max(0,(A-B)-(C-D)) fill bytes at patch_off
#define FIXUP_PCREL_DATA 7 // ".long/.quad (label) - ." forward reference; width in `size`
#define FIXUP_ALIGN      8 // deferred .balign/.align/.p2align: real GAS
// sees final section offsets (multi-pass); this assembler is single-pass
// plus deferred-shift for FIXUP_SKIP_MAXDIFF, so alignment computed
// against a stale, pre-shift offset would insert the wrong pad amount —
// deferred and resolved in the same chronological pass as skip-maxdiff.
#define FIXUP_REL32_DEFERRED 9 // same-section FIXUP_REL32 whose target label
// was already found (disambiguated) at define_label() time, but whose
// *byte patch* must wait until after every FIXUP_ALIGN/FIXUP_SKIP_MAXDIFF
// in the file has resolved: any of those still-pending .balign/.skip
// insertions between this call/jmp site and its target shifts one but not
// the other, changing their relative distance — a patch baked in eagerly
// (this assembler's usual behavior for a same-section branch) can never
// see that later shift. `size` holds the as->locals[] index of the
// disambiguated target occurrence (itself correctly shifted in step with
// every insertion, same as any other label), not a byte width.

// ---------------------------------------------------------------------------
// Win64 SEH unwind info (x86-64 only). Captured during codegen, emitted by
// coff_write as .pdata (RUNTIME_FUNCTION) and .xdata (UNWIND_INFO). Without
// it RtlUnwindEx (used by longjmp and exceptions) cannot walk past a frame:
// Wine merely logs a warning, but real Windows crashes.
// ---------------------------------------------------------------------------
#define UWOP_PUSH_NONVOL 0 // OpInfo = register number; rsp += 8
#define UWOP_ALLOC_LARGE 1 // OpInfo=0: extra 16-bit slot = size/8; OpInfo=1: 2 slots = size
#define UWOP_ALLOC_SMALL 2 // OpInfo = size/8 - 1 (size 8..128)
#define UWOP_SET_FPREG   3 // establish frame pointer

typedef struct UnwindCode {
    uint8_t code_offset; // prolog offset just past the described instruction
    uint8_t op; // UWOP_* (stored in the low nibble on emit)
    uint8_t info; // OpInfo: register number or alloc-size code
    uint16_t extra[2]; // extra size slots for UWOP_ALLOC_LARGE
    int extra_count; // 0, 1, or 2
} UnwindCode;

typedef struct UnwindEntry {
    uint32_t func_start; // .text offset of function start
    uint32_t func_end; // .text offset one past the last byte
    uint8_t prolog_size; // bytes in prolog (through .seh_endprologue)
    uint8_t frame_register; // 0 = none, else nonvolatile register used as FP (for UNWIND_INFO)
    UnwindCode codes[16]; // captured in prolog order, reversed at emit time
    int code_count; // number of UnwindCode entries (not counting extra slots)
} UnwindEntry;

// ---------------------------------------------------------------------------
// Debug info entries (for -g / DWARF)
// ---------------------------------------------------------------------------
#define MAX_DEBUG_FILES 128
typedef struct {
    uint64_t text_offset; // offset in .text section
    uint32_t file_idx;
    uint32_t line;
} DebugLineEntry;

// ---------------------------------------------------------------------------
// Complete assembled object
// ---------------------------------------------------------------------------
typedef struct ObjFile ObjFile;
struct ObjFile {
    SecBuf text;
    SecBuf data;
    SecBuf rodata;
    SecBuf init_array;
    SecBuf fini_array;
    SecBuf data_tls; // .tdata — initialized TLS data
    size_t bss_size; // .bss is zero-initialized; just track its size
    SecBuf thread_vars; // __thread_vars — TLV descriptors

    // Dynamically-registered sections (GAS .section/.pushsection with a
    // name outside the built-in set above). Section id SEC_NUM+i.
    ExtraSection *extra_secs;
    int extra_sec_count;
    int extra_sec_cap;

    ObjSym *syms;
    int sym_count;
    int sym_cap;

    // Symbol name hash table for fast lookups (FNV-1a, chaining).
    // Grown with the symbol count: a fixed 4096 buckets left sqlite3.c's 28k
    // symbols on chains ~7 long, and every chain step cost a strcmp. Each node
    // keeps its full hash so a chain miss is an int compare, not a strcmp, and
    // so growing can rehash without touching the strings.
    struct SymHashNode {
        uint32_t hash;
        int sym_idx;
        struct SymHashNode *next;
    } **sym_htab;
    uint32_t sym_htab_size; // power of two, 0 until first insert

    ObjReloc *text_relocs;
    int text_reloc_count;
    int text_reloc_cap;

    ObjReloc *data_relocs;
    int data_reloc_count;
    int data_reloc_cap;

    ObjReloc *rodata_relocs;
    int rodata_reloc_count;
    int rodata_reloc_cap;

    ObjReloc *init_array_relocs;
    int init_array_reloc_count;
    int init_array_reloc_cap;

    ObjReloc *fini_array_relocs;
    int fini_array_reloc_count;
    int fini_array_reloc_cap;

    ObjReloc *data_tls_relocs;
    int data_tls_reloc_count;
    int data_tls_reloc_cap;


    ObjReloc *thread_vars_relocs;
    int thread_vars_reloc_count;
    int thread_vars_reloc_cap;
    // Win64 SEH unwind entries (one per emitted function); only populated on
    // _WIN32 x86-64 codegen, ignored by elf_write/macho_write.
    UnwindEntry *unwind;
    int unwind_count;
    int unwind_cap;
    // Debug info (for -g DWARF)
    char *debug_files[MAX_DEBUG_FILES];
    int debug_file_count;
    DebugLineEntry *debug_lines;
    int debug_line_count;
    int debug_line_cap;
    SecBuf debug_line_section; // serialized DWARF .debug_line
    SecBuf debug_info_section; // minimal DWARF .debug_info
    SecBuf debug_abbrev_section; // DWARF .debug_abbrev
    SecBuf debug_aranges_section; // DWARF .debug_aranges (address range lookup)
    size_t debug_low_pc_offset; // offset of DW_AT_low_pc in .debug_info (needs reloc)
    bool debug_has_low_pc; // true if DW_AT_low_pc needs R_X86_64_64 vs .text
    size_t debug_aranges_addr_off; // offset of address in .debug_aranges (needs reloc)
    bool debug_has_aranges_addr; // true if .debug_aranges address needs reloc
    size_t debug_line_addr_off; // offset of DW_LNE_set_address value in .debug_line
    bool debug_has_line_addr; // true if DW_LNE_set_address needs reloc
    // True if this compilation unit created at least one GNU
    // nested-function trampoline (codegen.c's ND_LVAR function-value
    // branch) - the ONLY thing that makes a stack-resident code stub
    // work on modern hardened kernels: __enable_execute_stack's runtime
    // mprotect is unreliable/blocked in several real environments (e.g.
    // this project's own CI sandbox), so instead the .note.GNU-stack
    // section is marked executable (SHF_EXECINSTR), which the linker
    // turns into an RWE PT_GNU_STACK program header - the kernel then
    // maps the *entire* process stack executable at load time, exactly
    // matching what GCC emits for a TU using its own nested functions.
    bool uses_trampoline;
};

// Debug info helpers (for -g)
int objfile_add_debug_file(ObjFile *obj, char *filename);
void objfile_add_debug_line(ObjFile *obj, uint64_t text_offset, int file_idx, int line);
void objfile_flush_debug_line(ObjFile *obj, uint64_t text_end);
bool objfile_has_debug(ObjFile *obj);

void objfile_init(ObjFile *obj);
void objfile_free(ObjFile *obj);

// ELF symbol visibility (st_other's low 2 bits) — see ObjSym.visibility.
#define STV_DEFAULT   0
#define STV_INTERNAL  1
#define STV_HIDDEN    2
#define STV_PROTECTED 3

// Add a symbol (returns its index).  Duplicate names with the same section
// and offset are merged.
int objfile_add_sym(ObjFile *obj, const char *name, int section,
                    uint64_t offset, uint64_t size, SymBind bind, SymType type);
int objfile_find_sym(ObjFile *obj, const char *name);

void objfile_add_reloc(ObjFile *obj, int section, uint64_t offset,
                       int sym_idx, uint32_t type, int64_t addend);

// Shift every relocation in `section` with offset >= patch_off forward by
// n bytes — a same-section .balign/.skip/ALTERNATIVE() insertion physically
// moved those bytes; see obj.c's definition for the full rationale.
void objfile_shift_relocs(ObjFile *obj, int section, uint64_t patch_off, int64_t n);

// Look up a dynamically-registered section by name, creating it (with the
// given sh_flags/sh_entsize) if it doesn't exist yet. Returns its section
// id (>= SEC_NUM). sh_flags/sh_entsize are only applied on creation.
int objfile_find_or_add_section(ObjFile *obj, const char *name,
                                uint32_t sh_flags, uint32_t sh_entsize);

// Raise `section`'s sh_addralign to at least `align` (no-op if `section`
// isn't a dynamically-registered one, i.e. < SEC_NUM, or if `align` isn't
// larger than the section's current requirement).
void objfile_section_align(ObjFile *obj, int section, uint32_t align);

// The growable byte buffer for any section id, built-in or dynamic.
// Returns NULL for SEC_BSS (zero-initialized; track via obj->bss_size) and
// SEC_UNDEF.
SecBuf *objfile_section_buf(ObjFile *obj, int section);

// Append a fresh zeroed Win64 unwind entry and return a pointer to it.
UnwindEntry *objfile_add_unwind(ObjFile *obj);


// Create parent directories for a file path (like mkdir -p dirname)
void mkdir_p(const char *filepath);
// Object file writers
int elf_write(ObjFile *obj, const char *path);
int macho_write(ObjFile *obj, const char *path);
int coff_write(ObjFile *obj, const char *path);

#endif // OBJ_H
