Ashet OS

ABI Description Format (.abi)

§1 Overview

The Ashet ABI Description Format, often referred to as Ashet IDL, is a small, declarative language used to describe the public ABI surface of the Ashet system. It defines:

The abi-mapper tool consumes these files, validates them, and then derives a normalized, C-compatible representation for tooling and bindings.

§2 Purpose

The format exists to solve several problems at once:

  1. Single source of truth. ABI definitions should not be duplicated across languages or tooling. The IDL is the canonical definition used by generators, analyzers, and documentation.

  2. Stable ABI contracts. Types and call signatures must remain stable across versions. By keeping definitions in a simple, analyzable format, changes are explicit and reviewable.

  3. Language-neutral modeling. The language is small and avoids host-language features. This makes it easier to generate bindings for C, Zig, Rust, or custom tooling.

  4. Deterministic C ABI lowering. Slices, strings, optionals, and multi-return calls must be expressed in a way that can reliably lower to C-compatible forms.

  5. Human readability. Documentation is part of the format, making the ABI itself self-describing.

§3 Syntax

§3.1 Informal Structure

An IDL file is a sequence of declarations and their children. Declarations are nested within namespaces, and each declaration may contain child nodes that describe its fields, items, parameters, or errors.

Key features:

§3.2 Small Examples

Namespace with an enum:

/// Process-related declarations.
namespace process {
    /// Exit status codes.
    enum ExitCode : u32 {
        item success = 0;
        item failure = 1;
        ...
    }
}

Syscall with inputs, outputs, and errors:

/// Returns a process file name.
syscall get_file_name {
    in target: ?Process;
    out file_name: str;
    error InvalidHandle;
}

§3.3 Grammar (EBNF-style)

Document        = { Node } ;

Node            = DocComment* ( Declaration
                              | Field
                              | In
                              | Out
                              | EnumItem
                              | Error
                              | Reserve
                              | TypeDef
                              | Const
                              | Ellipse
                              | NoReturn ) ;

DocComment      = "///" { any-char-except-newline } newline ;
Comment         = "//?" { any-char-except-newline } newline ;

Declaration     = ("namespace" | "struct" | "union" | "enum" | "bitstruct" |
                   "syscall" | "async_call" | "resource")
                  Identifier [ ":" Type ] "{" { Node } "}" ;

Field           = "field" Identifier ":" Type [ "=" Value ] ";" ;
In              = "in" Identifier ":" Type [ "=" Value ] ";" ;
Out             = "out" Identifier ":" Type [ "=" Value ] ";" ;
EnumItem        = "item" Identifier [ "=" Value ] ";" ;
Error           = "error" Identifier ";" ;
Reserve         = "reserve" Type "=" Value ";" ;
NoReturn        = "noreturn" ";" ;
Ellipse         = "..." ;

TypeDef         = "typedef" Identifier "=" Type ";" ;
Const           = "const" Identifier [ ":" Type ] "=" Value ";" ;

Type            = MagicType
                | "?" Type
                | "*" PointerType
                | "[" "]" PointerType
                | "[" "*" "]" PointerType
                | "[" Value "]" Type
                | "fnptr" "(" [ Type { "," Type } ] ")" Type
                | BuiltinType
                | SignedIntType
                | UnsignedIntType
                | Identifier ;

PointerType     = [ "const" ] [ "align" "(" Number ")" ] Type ;

MagicType       = "<<" Identifier ":" MagicSize ">>" ;
MagicSize       = "u8" | "u16" | "u32" | "u64" | "usize" ;

BuiltinType     = "void" | "bool" | "noreturn" | "anyptr" | "anyfnptr" |
                  "str" | "bytestr" | "bytebuf" | "usize" | "isize" |
                  "f32" | "f64" ;
SignedIntType   = "i" Number ;
UnsignedIntType = "u" Number ;

Value           = Number
                | Identifier
                | "true" | "false" | "null"
                | "." "{" [ "." Identifier "=" Value { "," "." Identifier "=" Value } ] "}" ;

Identifier      = { letter | digit | "_" | "." } | "@\"" { any-char-except-quote-or-newline } "\"" ;
Number          = decimal | hex | binary ;

§3.4 Context Rules (Summary)

The grammar lists all node types, but not all nodes are legal everywhere. The key rules are:

§4 Semantics

§4.1 Namespaces and Fully-Qualified Names

Namespaces provide hierarchical structure. Every declaration acquires a fully-qualified name based on its namespace nesting. You can reference types by:

Example:

namespace fs {
    struct Path { field data: str; }
}

namespace process {
    // Refers to fs.Path by qualified name.
    syscall spawn {
        in path: fs.Path;
    }
}

§4.2 Doc Comments

Example:

/// Returns the base address of the process.
///
/// This value is constant while the process is alive.
syscall get_base_address {
    in target: ?Process;
    out base_address: usize;
}

§4.3 struct

Example:

struct Point {
    field x: i32;
    field y: i32;
}

§4.4 union

Example:

union AnyAddr {
    field v4: IPv4;
    field v6: IPv6;
}

§4.5 enum

Example:

enum ExitCode : u32 {
    item success = 0;
    item failure = 1;
    ...
}

§4.6 bitstruct

Example:

bitstruct FileAttributes : u16 {
    field directory: bool;
    reserve u15 = 0;
}

§4.7 syscall and async_call

Example:

syscall terminate {
    in exit_code: ExitCode;
    noreturn;
}

§4.8 resource

Example:

resource File { }
resource Process { }

§4.9 typedef

Example:

typedef ThreadFunction = fnptr (?anyptr) u32;

§4.10 const

Example:

const page_size: usize = 4096;

§5 Type System

§5.1 Built-In Types

The built-in (well-known) types are the core primitives the format understands:

Important size rules:

§5.2 User-Defined Types

§5.3 Pointers, Slices, Arrays, and Optionals

Pointer forms:

Slice forms:

Arrays:

Optionals:

Function pointers:

Examples:

field data: []const u8;        // slice of bytes
field ptr: [*]u8;              // unknown-length pointer
field buf: [128]u8;            // fixed-size array
field callback: fnptr (u32) u32;
field maybe_handle: ?Process;  // optional resource

§5.4 Magic Types (<<...>>)

Magic types are a special expansion mechanism. They are only used via typedef and expand into enums that enumerate declarations of a given kind.

Supported kinds:

Each magic type requires a size (u8, u16, u32, u64, usize). The analyzer generates a concrete enum, with items derived from fully-qualified names of all declarations of that kind.

Example:

/// Enumeration of all syscall numbers.
typedef Syscall_ID = <<syscall_enum:u32>>;

§6 C ABI Lowering

The ABI mapper derives a native C-compatible form from the logical IDL definitions. The transformation is deterministic and documented here so tooling can target it consistently.

§6.1 Slice Lowering

Slices ([]T) and string and buffer types are always lowered to pointer and length pairs in C ABI:

This applies to:

Example:

syscall read {
    in buffer: []u8;
    out count: usize;
}

Lowered inputs:

§6.2 Optional Lowering

Optionals that are already C-compatible are kept as-is, for example optional pointers or optional resources. Optional slices and optional string and buffer types are lowered with the optional applied to the pointer only; the length remains a plain usize.

Example:

in owners: ?[]Process;

Lowers to:

Example:

in file_name: ?str;

Lowers to:

§6.3 Syscall Return and Value Lowering

Syscalls follow these rules to ensure a C-compatible signature:

  1. Errors present (error entries exist): all logical outputs become input pointers (out parameters are passed as *T), and the function returns a single u16 error code (0 is success, non-zero is an error).

  2. No errors and exactly one output: that output is returned directly as the C return value.

  3. No errors and multiple outputs: outputs become input pointers and the function returns void.

This gives a single, predictable ABI signature in all cases.

§6.4 Async Call Lowering

Async calls are lowered similarly for slices and optionals, but do not use syscall return remapping. Their outputs remain outputs in the native representation, which is treated as a structure-like ABI (useful for async operation records rather than direct C call signatures).

§6.5 Struct Field Lowering

Struct fields undergo slice lowering in-place:

Union fields are not transformed; unions must already be C-ABI compatible.

Example:

struct FileInfo {
    field name: str;
    field data: []const u8;
}

Lowered fields: