//! Native image segment placement and versioned wire headers.

@test export mod tests;

/// Native image signature, encoded as RAD0 in little-endian order.
export constant MAGIC: u32 = 0x30444152;
/// Native image format with explicit segment addresses and memory extents.
export constant VERSION: u32 = 2;
/// Fixed header size: signature, version, entry, and three segment records.
export constant HEADER_SIZE: u32 = 64;

/// Invalid native image placement.
export union Error: Copy {
    /// A size or end address cannot be represented.
    Overflow,
    /// A segment or entry violates its alignment requirement.
    Alignment,
    /// Segment memory intervals overlap.
    Overlap,
    /// An initialized extent exceeds its memory extent.
    Size,
    /// The entry is outside initialized code.
    Entry,
    /// A PC-relative address load exceeds its instruction range.
    Relocation,
}

/// File and memory extents for one image segment.
export record Segment: Copy {
    /// Physical start address.
    address: u64,
    /// Number of bytes stored in the image file.
    initialized: u32,
    /// Total memory size, including the zero-filled tail.
    memory: u32,
}

/// Explicit native image layout. File payload order is code, rodata, rwdata.
export record Layout: Copy {
    /// Physical entry address.
    entry: u64,
    /// Executable instructions.
    code: Segment,
    /// Read-only data.
    roData: Segment,
    /// Writable data.
    rwData: Segment,
}

/// Check one segment's extent and required base alignment.
fn segment(item: Segment, alignment: u64) throws (Error) {
    if item.initialized > item.memory {
        throw Error::Size;
    }
    if item.address > 0xffffffffffffffff - item.memory as u64 {
        throw Error::Overflow;
    }
    if item.memory > 0 and (item.address & (alignment - 1)) <> 0 {
        throw Error::Alignment;
    }
}

/// Check that two nonempty memory intervals are disjoint.
fn disjoint(a: Segment, b: Segment) throws (Error) {
    if a.memory > 0 and b.memory > 0 and
        a.address < b.address + b.memory as u64 and b.address < a.address + a.memory as u64
    {
        throw Error::Overlap;
    }
}

/// Validate segment extents, alignment, overlap, and the executable entry.
export fn validate(layout: Layout) throws (Error) {
    try segment(layout.code, 4);
    try segment(layout.roData, 8);
    try segment(layout.rwData, 8);
    if (layout.code.initialized & 3) <> 0 or (layout.entry & 3) <> 0 {
        throw Error::Alignment;
    }
    if layout.entry < layout.code.address or
        layout.entry - layout.code.address >= layout.code.initialized as u64
    {
        throw Error::Entry;
    }
    try disjoint(layout.code, layout.roData);
    try disjoint(layout.code, layout.rwData);
    try disjoint(layout.roData, layout.rwData);
}

/// Encode a validated native header in little-endian order.
export fn header(layout: Layout) -> [u8; 64] throws (Error) {
    try validate(layout);
    let words: [u64; 8] = [
        MAGIC as u64 | (VERSION as u64 << 32), layout.entry,
        layout.code.address, layout.code.initialized as u64 | (layout.code.memory as u64 << 32),
        layout.roData.address, layout.roData.initialized as u64 | (layout.roData.memory as u64 << 32),
        layout.rwData.address, layout.rwData.initialized as u64 | (layout.rwData.memory as u64 << 32),
    ];
    let mut bytes: [u8; HEADER_SIZE] = [0; HEADER_SIZE];
    for word, i in &words[..] {
        for j in 0..8 {
            set bytes[i * 8 + j] = (word >> (j as u64 * 8)) as u8;
        }
    }
    return bytes;
}

/// Address policy for generated code and data.
export union Placement: Copy {
    /// Hosted layout with code after read-only data.
    Hosted,
    /// Fixed physical segment addresses and entry.
    Physical {
        /// Executable segment base.
        code: u64,
        /// Read-only segment base.
        roData: u64,
        /// Writable segment base.
        rwData: u64,
        /// First instruction to execute.
        entry: u64,
    },
}

/// Compute a signed displacement for a two-instruction AUIPC/ADDI load.
export fn displacement(source: u64, target: u64) -> ?i32 {
    if target >= source {
        let distance = target - source;
        // AUIPC sign-extends its upper immediate before ADDI applies the low part.
        if distance > 0x7ffff7ff { return nil; }
        return distance as i32;
    }
    let distance = source - target;
    if distance > 0x80000000 { return nil; }
    return (-(distance as i64)) as i32;
}
