8.6 KiB
pEpACIDLang - Language Mapping Specification LMS-C99
pEpACIDLang stands for "pEp Abstract C Interface Description Language".
pEpACIDLang is language to describe a programming interface.
'Abstract', because the interfaces defined in pEpACIDLang are agnostic to the target language.
'C' because the syntax and some concepts are based on the C language.
A concrete interface and client code using the interface can be generated in any language where:
-
a language mapping specification
-
a interface generator exists for.
-
The interface generator implements parts of the language mapping specification.
The current problem at hand is to replace a handwritten C API with a pEpACIDlang generated one.
This is the document specifying the language mapping for C99.
About This Document
State: Draft, to be reviewed with fdik and positron.
ATTENTION, IMPORTANT
In this document, all types expressed as T are the IDL-types, not their corresponding c-types.
An example for clarity:
If T
is the type string
, the IDL-type string
is meant, so the c-type of T*
is char**
.
Usually a variable typename is expressed as T
. But sometimes T
is being used to refer to an instance of type T
.
This imprecision is used for brevity, but only where the context does not allow for an ambiguity.
IDL-Types
Type Classes
There are only two type-classes.
- Primitive Types
- Object Types
IDL-Type Mapping To C
Type-Class | IDL-Type | C-Type |
---|---|---|
Primitive | int | int |
Primitive | uint | unsigned int |
Primitive | size | size_t |
Primitive | bool | bool |
Primitive | enum | enum |
Object | struct | struct* |
Object | string | char* |
Object | binary | char* |
Object | opaque | void* |
Object | list | list_T* |
Object | pair<F,S> | pair_F_S* |
Creation and Destruction
Primitive Types
- Primitive types are always statically allocated
- Primitive types are trivially constructable and destroyable
- All storage classes (auto/static/extern/register) are allowed
Object Types
For any object-type T
, corresponding new
and free
methods are being generated.
To create an object of type T
correctly, there are two ways:
- Using its allocator-method
- Using any method that has a parameter of type
T
with modeProduce
To destroy an object of type T
correctly, the only way is to use its destructor-method.
Allocators
The allocator-method has the signature:
IDL_STATUS new_T(T*)
- The memory for
T
has to be dynamically allocated - An empty object is being created. Any object-types reachable through
T
are pointing toNULL
.
Result:
STATUS_OK - T*
is pointing to a valid object
STATUS_ERROR - Dereferencing of T*
is undefined behaviour
Destructors
The destructor-method has the signature:
IDL_STATUS free_T(T*)
- For all object-type members of
T
itself, their respective destructor-methods are being called - The memory for
T
is freed compatibly to how it was allocated using its allocator-method T*
is set toNULL
Result:
STATUS_OK - All memory reachable through T*
is freed and T*
is NULL
STATUS_ERROR - FATAL (most implementations of free()
can guarantee this will never happen)
Method Parameters
The main concerns when sharing memory (e.g. in form of parameters) are:
- Ownership: defined as who is allowed and responsible to destroy an object.
- Mutability: Who is allowed to change the contents of the memory location that is known to several parties.
In environments where memory management is explicit, the lifecycle of data in memory is:
- Memory being allocated
- Memory being shared with producers/consumers (e.g. as a parameter Read/Update/Consume)
- Read/write operations by multiple parties (needs synchronization if multithreaded environment)
- Make sure this memory will never be accessed again by nobody
- Memory has to be freed exactly once and compatible to how it was allocated
Every such lifecycle must be completed.
All of this must be guaranteed.
Rules For Parameter Allocation
Parameters have to be allocated as follows:
- Primitive-types: The general rules outlined under Creation and Destruction apply
- Object-types: Using the allocator- and destructor-methods outlined under Creation and Destruction
These rules apply regardless of the parameter passing mode.
Parameter Passing Modes
The modes are named by describing what the callee does, from the perspective of the callee, using one verb.
Read
- Immutable, ownership remains with callerUpdate
- Mutable, ownership remains with callerConsume
- Mutable, ownership goes to calleeProduce
- Immutable, ownership goes to caller
Mapping Table for C
Type of T
as a function of type-class and mode.
Where T
any of the respective IDL-Types (not their respective c-type, as mentioned in About This Document).
Mode/Type-Class | READ | UPDATE | CONSUME | PRODUCE |
---|---|---|---|---|
Primitive | T | T* | - (1) | - (1) |
Object | T | T* | T | T* |
Rules For Parameters Of Primitive-Type
The caller has to allocate memory for T
.
For mode Update
, the callee is allowed to mutate the memory pointed to by T*
.
(1) A primitive-type cannot be consumed or produced because it's always statically allocated.
Rules For Parameters Of Object-Type
Read
The caller has to guarantee that either-or:
T
isNULL
T
is pointing to a valid object of type*T
(allocated as defined under 'Rules For Parameter Allocation')
This guarantee applies transitively for all object-type members of*T
.
The callee is not allowed to mutate or free any memory that can be reached through T
.
The caller is responsible to free all the memory that can be reached through T
.
Update
The caller has to guarantee that:
- All object-types reachable through
T*
are eitherNULL
or pointing to a valid object (allocated as defined under Rules For Parameter Allocation)
The callee is allowed to mutate the object in two ways:
- Mutate any primitive-types reachable through
T*
- Free or replace any object-types reachable trough
T*
The callee has to guarantee, that upon return of the method:
T*
is pointing to a valid*T
(allocated as defined under Rules For Parameter Allocation)- All object-type members reachable through
T*
are eitherNULL
or pointing to a valid object of type*T
(allocated as defined under Rules For Parameter Allocation)
Upon success of the method call:
The caller must replace all pointers to *T
and to any of its transitive members by dereferencing T*
and its respective
transitive members.
The caller must free T
and any of its transitive members.
TODO: Upon any failure of the method call:
Consume
The same rules for mode Read
apply, with the exception that the caller has to guarantee to never access T
again in
any way.
Produce
The caller is allowed to pass a T*
with an undefined value.
TODO...
Rules For Multithreading
These rules apply to all memory that can be accessed through a parameter. Such memory must be allocated in the calling thread No such memory can be shared with other TODO....