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:
-
ABI-stable types (structs, unions, enums, bitstructs).
-
ABI-visible resources (opaque handles).
-
System call and asynchronous call signatures.
-
Constants and type aliases.
-
Documentation strings attached to every declaration.
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:
-
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.
-
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.
-
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.
-
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.
-
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:
-
Doc commentsuse///and attach to the next node. -
Line commentsuse//?and are ignored by the parser. -
Identifiersare ASCII and may include dots (.) for scoped names. If a name includes special characters or keywords, escape it using@"...".
§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:
-
fieldappears only insidestruct,union, orbitstruct. -
itemand...appear only insideenum. -
in,out,error, andnoreturnappear only insidesyscallorasync_call. -
reserveappears only insidebitstruct. -
typedefandconstare top-level declarations, including inside namespaces.
§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:
-
Local name within the same namespace.
-
Qualified name using dot notation (for example,
process.ExitCode).
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
-
///lines are collected and attached to the next node. -
Empty leading and trailing lines are trimmed.
-
Common indentation is removed so multi-line blocks align cleanly.
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
-
A list of named field entries.
-
Fields may have defaults.
-
Order is preserved and meaningful for layout and ABI.
Example:
struct Point {
field x: i32;
field y: i32;
}
§4.4 union
-
A list of named field entries.
-
Fields cannot have default values.
-
Union layout is a C-style overlapping representation.
Example:
union AnyAddr {
field v4: IPv4;
field v6: IPv6;
}
§4.5 enum
-
Requires an integer subtype (for example,
: u32). -
Items may specify explicit values.
-
...marks the enum as open, indicating more values may exist outside this file.
Example:
enum ExitCode : u32 {
item success = 0;
item failure = 1;
...
}
§4.6 bitstruct
-
Requires an integer subtype (for example,
: u16). -
Fields are packed in order into the backing integer.
-
reservecreates unnamed padding bits. -
All fields must have known bit widths.
-
Total bit width must match the backing type exactly.
Example:
bitstruct FileAttributes : u16 {
field directory: bool;
reserve u15 = 0;
}
§4.7 syscall and async_call
-
Define call signatures.
-
inparameters are inputs;outparameters are outputs. -
errorentries define possible error codes. -
noreturnmarks a call as not returning and forbids outputs.
Example:
syscall terminate {
in exit_code: ExitCode;
noreturn;
}
§4.8 resource
-
Declares an opaque handle type.
-
Treated as a distinct ABI type for safety and clarity.
Example:
resource File { }
resource Process { }
§4.9 typedef
-
Creates an alias name for an existing type.
-
Used to introduce convenient names or to reify magic types.
Example:
typedef ThreadFunction = fnptr (?anyptr) u32;
§4.10 const
-
Declares a constant value.
-
A type annotation is optional.
-
Values can be integers, booleans, null, or compound initializers.
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:
-
Primitives:
void,bool,noreturn -
Pointers:
anyptr(opaque data pointer),anyfnptr(opaque function pointer) -
Strings and buffers:
str(immutable UTF-8 string),bytestr(immutable byte string),bytebuf(mutable byte buffer) -
Integers:
usize,isize,u8/u16/u32/u64,i8/i16/i32/i64, and arbitraryuN/iN -
Floats:
f32,f64
Important size rules:
-
usize/isizematch pointer size. -
str,bytestr, andbytebufare represented as a pointer and length pair. -
boolis 1 bit in bitstructs and 1 byte in normal layouts.
§5.2 User-Defined Types
-
struct,union,enum,bitstruct, andresourceare all named and referenced by their identifiers or qualified names. -
Type names are resolved within namespace scopes.
§5.3 Pointers, Slices, Arrays, and Optionals
Pointer forms:
-
*T: pointer to one element. -
*const T: pointer to a constant element. -
*const align(8) T: pointer with explicit alignment requirement.
Slice forms:
-
[]T: slice (pointer and length). -
[*]T: unknown-length pointer (no length in the type).
Arrays:
-
[N]T: fixed-size array (N is an integer value).
Optionals:
-
?T: nullable and optional wrapper.
Function pointers:
-
fnptr (A, B, C) R: function pointer taking parametersA,B,C, returningR.
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:
-
struct_enum
-
union_enum
-
enum_enum
-
bitstruct_enum
-
syscall_enum
-
async_call_enum
-
resource_enum
-
constant_enum
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:
-
Logical:
name: []T -
Native:
name_ptr: *T(or*const Tif the logical type is immutable) andname_len: usize.
This applies to:
-
[]T -
str,bytestr,bytebuf -
Optional slices and optional string and buffer types
Example:
syscall read {
in buffer: []u8;
out count: usize;
}
Lowered inputs:
-
buffer_ptr: *u8 -
buffer_len: usize
§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:
-
owners_ptr: ?*Process -
owners_len: usize
Example:
in file_name: ?str;
Lowers to:
-
file_name_ptr: ?*const u8 -
file_name_len: usize
§6.3 Syscall Return and Value Lowering
Syscalls follow these rules to ensure a C-compatible signature:
-
Errors present (
errorentries exist): all logical outputs become input pointers (outparameters are passed as*T), and the function returns a singleu16error code (0is success, non-zero is an error). -
No errors and exactly one output: that output is returned directly as the C return value.
-
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:
-
str,bytestr,bytebufbecome*_ptrplus*_len. -
[]Tbecomes*_ptrplus*_len. -
Optional slices become
?*_ptrplus*_len.
Union fields are not transformed; unions must already be C-ABI compatible.
Example:
struct FileInfo {
field name: str;
field data: []const u8;
}
Lowered fields:
-
name_ptr: *const u8,name_len: usize -
data_ptr: *const u8,data_len: usize