asoma.dev · the C reference for libose
libose
the C library every o.se host is built on
Loading...
Searching...
No Matches
Data Structures | Macros | Variables
ose.h File Reference

provides global definitions and types More...

#include <limits.h>
#include <inttypes.h>
Include dependency graph for ose.h:
This graph shows which files directly or indirectly include this file:

Go to the source code of this file.

Data Structures

struct  ose_bundle
 
struct  ose_timetag
 Convenience struct for OSC/NTP timetags. More...
 
struct  osevm_hooks
 

Macros

#define OSE_CONF_DEBUG
 
#define OSE_CONF_MEMPROFILE
 Enable memory profiling.
 
#define OSE_CONF_WRAP_BUNDLE_PTR
 Cause the ose_bundle type to be a pointer wrapped in a struct.
 
#define OSE_WRAP_BUNDLE_PTR
 
#define OSE_MEMPROFILE
 
#define OSE_HOT
 Where a host wants the interpreter's hottest code placed.
 
#define ose_memmove   memmove
 Move bytes that may overlap: memmove, or on the ESP32 a faster way.
 
#define OSE_CONF_PROVIDE_TYPE_SYMBOL
 
#define OSE_CONF_PROVIDE_TYPE_DOUBLE
 
#define OSE_CONF_PROVIDE_TYPE_INT8
 
#define OSE_CONF_PROVIDE_TYPE_UINT8
 
#define OSE_CONF_PROVIDE_TYPE_INT16
 
#define OSE_CONF_PROVIDE_TYPE_UINT16
 
#define OSE_CONF_PROVIDE_TYPE_UINT32
 
#define OSE_CONF_PROVIDE_TYPE_INT64
 
#define OSE_CONF_PROVIDE_TYPE_UINT64
 
#define OSE_CONF_PROVIDE_TYPE_TIMETAG
 
#define OSE_CONF_PROVIDE_TYPE_TRUE
 
#define OSE_CONF_PROVIDE_TYPE_FALSE
 
#define OSE_CONF_PROVIDE_TYPE_NIL
 
#define OSE_CONF_PROVIDE_TYPE_INFINITUM
 

Functions

Version
const char * ose_version_string (void)
 
const char * ose_debug_string (void)
 
const char * ose_date_compiled_string (void)
 

Variables

const struct osevm_hooks osevm_hooks
 

Types

Check after every instruction that the VM's bundles are still valid OSC.

The claim the design rests on is that a VM's state is a well formed OSC bundle at all times – that the running machine and the wire format are the same bytes. Nothing checked it. Define OSE_CONF_VALIDATE_VM_STEP and every context bundle is validated after every instruction, so an instruction that can produce malformed bytes says so at the point it does it, with the address that did it still to hand.

Off by default and meant to stay that way: it validates five bundles per instruction, which is far too expensive to ship. Turn it on to run a test suite or a program under, the way you would a sanitizer.

#define ose_constbundle   ose_bundle
 
#define ose_makeConstBundle   ose_makeBundle
 
#define ose_getBundlePtr(bundle)   (bundle).b
 
#define OSE_INTPTR2   (sizeof(intptr_t) * 2)
 Define a size that is twice that of intptr_t.
 
enum  bool { false = 0 , true }
 
typedef struct ose_bundle ose_bundle
 
typedef void(* ose_fn) (ose_bundle)
 Function pointer for the basic OSE function that operates on a bundle.
 
ose_bundle ose_makeBundle (const char *const p)
 

OSC 1.0 Types

#define OSETT_ID_   ','
 
#define OSETTSTR_ID_   ","
 
#define OSETT_INT32_   'i'
 
#define OSETTSTR_INT32_   "i"
 
#define OSETT_FLOAT_   'f'
 
#define OSETTSTR_FLOAT_   "f"
 
#define OSETT_STRING_   's'
 
#define OSETTSTR_STRING_   "s"
 
#define OSETT_BLOB_   'b'
 
#define OSETTSTR_BLOB_   "b"
 
static const char OSETT_ID = ','
 
static const char *const OSETTSTR_ID = ","
 
static const char OSETT_INT32 = 'i'
 
static const char *const OSETTSTR_INT32 = "i"
 
static const char OSETT_FLOAT = 'f'
 
static const char *const OSETTSTR_FLOAT = "f"
 
static const char OSETT_STRING = 's'
 
static const char *const OSETTSTR_STRING = "s"
 
static const char OSETT_BLOB = 'b'
 
static const char *const OSETTSTR_BLOB = "b"
 

Extended Types

#define OSE_PROVIDE_TYPE_SYMBOL
 
#define OSETT_SYMBOL_   'S'
 
#define OSETTSTR_SYMBOL_   "S"
 
#define OSE_PROVIDE_TYPE_DOUBLE
 
#define OSETT_DOUBLE_   'd'
 
#define OSETTSTR_DOUBLE_   "d"
 
#define OSE_PROVIDE_TYPE_INT8
 
#define OSETT_INT8_   'c'
 
#define OSETTSTR_INT8_   "c"
 
#define OSE_PROVIDE_TYPE_UINT8
 
#define OSETT_UINT8_   'C'
 
#define OSETTSTR_UINT8_   "C"
 
#define OSE_PROVIDE_TYPE_INT16
 
#define OSETT_INT16_   'u'
 
#define OSETTSTR_INT16_   "u"
 
#define OSE_PROVIDE_TYPE_UINT16
 
#define OSETT_UINT16_   'U'
 
#define OSETTSTR_UINT16_   "U"
 
#define OSE_PROVIDE_TYPE_UINT32
 
#define OSETT_UINT32_   'k'
 
#define OSETTSTR_UINT32_   "k"
 
#define OSE_PROVIDE_TYPE_INT64
 
#define OSETT_INT64_   'h'
 
#define OSETTSTR_INT64_   "h"
 
#define OSE_PROVIDE_TYPE_UINT64
 
#define OSETT_UINT64_   'H'
 
#define OSETTSTR_UINT64_   "H"
 
#define OSE_PROVIDE_TYPE_TIMETAG
 
#define OSETT_TIMETAG_   't'
 
#define OSETTSTR_TIMETAG_   "t"
 
#define OSE_PROVIDE_TYPE_TRUE
 
#define OSETT_TRUE_   'T'
 
#define OSETTSTR_TRUE_   "T"
 
#define OSE_PROVIDE_TYPE_FALSE
 
#define OSETT_FALSE_   'F'
 
#define OSETTSTR_FALSE_   "F"
 
#define OSE_PROVIDE_TYPE_NIL
 
#define OSETT_NIL_   'N'
 
#define OSETTSTR_NIL_   "N"
 
#define OSE_PROVIDE_TYPE_INFINITUM
 
#define OSETT_INFINITUM_   'I'
 
#define OSETTSTR_INFINITUM_   "I"
 
static const char OSETT_SYMBOL = 'S'
 
static const char *const OSETTSTR_SYMBOL = "S"
 
static const char OSETT_DOUBLE = 'd'
 
static const char *const OSETTSTR_DOUBLE = "d"
 
static const char OSETT_INT8 = 'c'
 
static const char *const OSETTSTR_INT8 = "c"
 
static const char OSETT_UINT8 = 'C'
 
static const char *const OSETTSTR_UINT8 = "C"
 
static const char OSETT_INT16 = 'u'
 
static const char *const OSETTSTR_INT16 = "u"
 
static const char OSETT_UINT16 = 'U'
 
static const char *const OSETTSTR_UINT16 = "U"
 
static const char OSETT_UINT32 = 'k'
 
static const char *const OSETTSTR_UINT32 = "k"
 
static const char OSETT_INT64 = 'h'
 
static const char *const OSETTSTR_INT64 = "h"
 
static const char OSETT_UINT64 = 'H'
 
static const char *const OSETTSTR_UINT64 = "H"
 
static const char OSETT_TIMETAG = 't'
 
static const char *const OSETTSTR_TIMETAG = "t"
 
static const char OSETT_TRUE = 'T'
 
static const char *const OSETTSTR_TRUE = "T"
 
static const char *const ose_str_true = "/,/T"
 
static const int32_t ose_strlen_true = 4
 
static const int32_t ose_pstrlen_true = 8
 
static const char OSETT_FALSE = 'F'
 
static const char *const OSETTSTR_FALSE = "F"
 
static const char *const ose_str_false = "/,/F"
 
static const int32_t ose_strlen_false = 4
 
static const int32_t ose_pstrlen_false = 8
 
static const char OSETT_NIL = 'N'
 
static const char *const OSETTSTR_NIL = "N"
 
static const char *const ose_str_nil = "/,/N"
 
static const int32_t ose_strlen_nil = 4
 
static const int32_t ose_pstrlen_nil = 8
 
static const char OSETT_INFINITUM = 'I'
 
static const char *const OSETTSTR_INFINITUM = "I"
 
static const char *const ose_str_infinitum = "/,/I"
 
static const int32_t ose_strlen_infinitum = 4
 
static const int32_t ose_pstrlen_infinitum = 8
 

Extension type

The one type libose defines that is not in OSC 1.0 and that is written into a typetag string.

Deliberate, and the cost is known. OSC 1.0 says an application "should discard any message whose OSC Type Tag String contains any unrecognized OSC Type Tags", so a message carrying this one is dropped whole by a conformant peer rather than read with an argument missing. It is therefore for messages that stay inside a process, and OSE_ERR_UNTRUSTED_TYPE is what keeps them there.

It earns that. A pointer has to be stored, not merely accepted, and the tag is what lets the input walk refuse one from outside.

LAYOUT. Stored like a blob and sized by its field, as a blob is: [int32 = 2 x sizeof(intptr_t)][the pointer][a pointer to its name]. The pointer is first, so ose_readAlignedPtr(B, item + 4) reads it.

EVERY POINTER HAS A NAME – a function, a handle, an object – and it is the name it was bound under: ose_pushMessage() records the message's address, ose_pushFnMessage() and ose_pushAlignedPtr() take one. The name is kept as a pointer, so it must outlive the value: a string literal, or one kept for the life of the binding. Nothing in a 'p' leaves the process. At a boundary a 'p' is written as a 'P', its name as text, by ose_disarmPointers(), and so a pointer crosses a boundary as its name.

#define OSETT_ALIGNEDPTR_   'p'
 
#define OSETTSTR_ALIGNEDPTR_   "p"
 
#define OSETT_WILDPTR_   'P'
 A pointer by name: the name a 'p' was bound under, as text.
 
#define OSETTSTR_WILDPTR_   "P"
 
static const char OSETT_ALIGNEDPTR = 'p'
 
static const char *const OSETTSTR_ALIGNEDPTR = "p"
 
static const char OSETT_WILDPTR = 'P'
 
static const char *const OSETTSTR_WILDPTR = "P"
 

Internal types

Types used in the API to say what something is, rather than to describe an argument.

This group was called "Pseudo-types" and described as types "used in the API, but never actually written into a typetag string". Corrected after an audit measured what each one does.

OSETT_NOTYPETAG and OSETT_MESSAGE keep that rule exactly. ose_getBundleElemType answers with them and nothing writes either one.

OSETT_BUNDLE keeps it as far as libose is concerned: as an element discriminator it never reaches a typetag string, and libose never puts a bundle inside a message. It is listed here rather than above because a client may do so, and libose sizes and validates such an item – from outside as well as in – so that a message holding one can be sent at all. Where that message must also be readable by a conformant peer, a blob carries the same bytes in the same layout and differs only in this byte.

#define OSETT_OPCODE_   '!'
 An instruction, by number: an int32 index into the host's instruction table.
 
#define OSETTSTR_OPCODE_   "!"
 
#define OSETT_NOTYPETAG_   0
 
#define OSETTSTR_NOTYPETAG_   "0"
 
#define OSETT_BUNDLE_   '|'
 
#define OSETTSTR_BUNDLE_   "|"
 
#define OSETT_MESSAGE_   '-'
 
#define OSETTSTR_MESSAGE_   "-"
 
static const char OSETT_OPCODE = '!'
 
static const char *const OSETTSTR_OPCODE = "!"
 
static const char OSETT_NOTYPETAG = 0
 
static const char *const OSETTSTR_NOTYPETAG = "0"
 
static const char OSETT_BUNDLE = '|'
 
static const char *const OSETTSTR_BUNDLE = "|"
 
static const char OSETT_MESSAGE = '-'
 
static const char *const OSETTSTR_MESSAGE = "-"
 

Constants

#define OSE_BUNDLE_ID_   "#bundle\0"
 
#define OSE_SIZEWORD_LEN_   4 /* an element's, a blob's or a pointer item's length word */
 
#define OSE_ALIGN_   4 /* addresses, typetags, strings and blobs pad to this */
 
#define OSE_BUNDLE_ID_LEN_   8
 
#define OSE_TIMETAG_NULL_   "\0\0\0\0\0\0\0\0"
 
#define OSE_TIMETAG_LEN_   8
 
#define OSE_BUNDLE_HEADER_LEN_   (OSE_BUNDLE_ID_LEN_ + OSE_TIMETAG_LEN_)
 
#define OSE_EMPTY_TYPETAG_STRING_   ",\0\0\0"
 
#define OSE_ADDRESS_ANONVAL_   "\0\0\0\0"
 
#define OSE_ADDRESS_ANONVAL_SIZE_   4
 
static const int32_t OSE_SIZEWORD_LEN = 4
 
static const int32_t OSE_ALIGN = 4
 
static const char *const OSE_BUNDLE_ID = "#bundle\0"
 
static const int32_t OSE_BUNDLE_ID_LEN = 8
 
static const char *const OSE_TIMETAG_NULL = "\0\0\0\0\0\0\0\0"
 
static const int32_t OSE_TIMETAG_LEN = 8
 
static const char *const OSE_BUNDLE_HEADER
 
static const int32_t OSE_BUNDLE_HEADER_LEN = ( 8 + 8 )
 
static const char *const OSE_EMPTY_TYPETAG_STRING
 
static const int32_t OSE_EMPTY_TYPETAG_STRING_LEN = 1
 
static const int32_t OSE_EMPTY_TYPETAG_STRING_SIZE = 4
 
static const char *const OSE_ADDRESS_ANONVAL = "\0\0\0\0"
 
static const int32_t OSE_ADDRESS_ANONVAL_LEN = 0
 
static const int32_t OSE_ADDRESS_ANONVAL_SIZE = 4
 
static const char *const OSE_ADDRESS_ANONVAL_EMPTY_TYPETAG_STRING
 
static const int32_t OSE_ADDRESS_MIN_LEN = 0
 
static const int32_t OSE_ADDRESS_MIN_PLEN = 4
 

Type Extensibility Hooks

#define OSE_TYPE_ADDR_PREFIX   "/"
 
#define OSE_TYPE_ADDR_PPRINTPAYLOADITEM_SUFFIX   "/PPPI"
 
#define OSE_PPRINTPAYLOADITEM_ADDR_    OSE_TYPE_ADDR_PREFIX "%c" OSE_TYPE_ADDR_PPRINTPAYLOADITEM_SUFFIX
 
#define OSE_PPRINTPAYLOADITEM_HOOK   ose_pprintPayloadItem_hook
 
#define OSE_TYPE_ADDR_GETPAYLOADITEMLENGTH_SUFFIX   "/GPIL"
 
#define OSE_GETPAYLOADITEMLENGTH_ADDR_    OSE_TYPE_ADDR_PREFIX "%c" OSE_TYPE_ADDR_GETPAYLOADITEMLENGTH_SUFFIX
 
#define OSE_GETPAYLOADITEMLENGTH_HOOK   ose_getPayloadItemLength_hook
 
#define OSE_TYPE_ADDR_GETPAYLOADITEMSIZE_SUFFIX   "/GPIS"
 
#define OSE_GETPAYLOADITEMSIZE_ADDR_    OSE_TYPE_ADDR_PREFIX "%c" OSE_TYPE_ADDR_GETPAYLOADITEMSIZE_SUFFIX
 
#define OSE_GETPAYLOADITEMSIZE_HOOK   ose_getPayloadItemSize_hook
 
#define OSE_TYPE_ADDR_FROMSTRING_SUFFIX   "/FRST"
 
#define OSE_FROMSTRING_ADDR_    OSE_TYPE_ADDR_PREFIX "%c" OSE_TYPE_ADDR_FROMSTRING_SUFFIX
 
#define OSE_FROMSTRING_HOOK   ose_fromString_hook
 
#define OSE_REGISTERTYPE_HOOK   ose_registerType_hook
 
typedef int32_t(* ose_getPayloadItemLengthFn) (ose_bundle, char, int32_t, int32_t)
 
typedef int32_t(* ose_getPayloadItemSizeFn) (ose_bundle, char, int32_t, int32_t)
 
typedef int32_t(* ose_pprintPayloadItemFn) (ose_bundle, char, int32_t, int32_t)
 
typedef int32_t(* ose_fromStringFn) (ose_bundle, char, int32_t, int32_t)
 
static const char *const OSE_PPRINTPAYLOADITEM_ADDR
 
static const int32_t OSE_PPRINTPAYLOADITEM_ADDR_LEN
 
static const char *const OSE_GETPAYLOADITEMLENGTH_ADDR
 
static const int32_t OSE_GETPAYLOADITEMLENGTH_ADDR_LEN
 
static const char *const OSE_GETPAYLOADITEMSIZE_ADDR
 
static const int32_t OSE_GETPAYLOADITEMSIZE_ADDR_LEN
 
static const char *const OSE_FROMSTRING_ADDR
 
static const int32_t OSE_FROMSTRING_ADDR_LEN
 

Detailed Description

provides global definitions and types

NOTHING OUTSIDE THE BUNDLE, EVER. libose keeps no state of its own: no file-scope or function-static variable written at run time, for any reason, behind any option. Everything lives in a bundle the caller handed in. Two copies of this library can share a process, and a module loaded once may call either; that is harmless only because neither keeps anything. What is built on libose is held to the same rule, and the asoma workspace's tools/checks/check_globals.py fails on any writable global.

Macro Definition Documentation

◆ OSE_HOT

#define OSE_HOT

Where a host wants the interpreter's hottest code placed.

Marks the definitions the VM runs on every step: the step loop, apply, the stack operations and the bundle utilities they call. It is empty unless a host defines OSE_CONF_HOT, and a host that has a faster place for code than the default gives its attribute there.

The ESP32-S3 is why it exists. Code runs from external flash through a 16 KB instruction cache, and the VM's step path does not fit in it: measured on the board, a quarter of a tick was instruction-fetch stalls, and moving these files into internal RAM made the tick 1.28x faster.

◆ ose_memmove

#define ose_memmove   memmove

Move bytes that may overlap: memmove, or on the ESP32 a faster way.

The ESP32-S3's memmove is its ROM's, and measured on the board it moved 1 KB in 9,300 cycles upward and 12,400 downward, where the ROM's memcpy took 679. Nearly every edit of a bundle is a memmove, so ose_memmove() moves overlapping bytes with memcpy instead, in pieces that do not overlap. The heart rate program's tick went from 13.1 to 10.1 ms. On every other host it is memmove.

◆ OSETT_OPCODE_

#define OSETT_OPCODE_   '!'

An instruction, by number: an int32 index into the host's instruction table.

A control element whose first item is one of these is not looked up by name: the VM hands it to OSEVM_OPCODE, and what the number means is the host's, or a language's that the host loads. libose sizes it, validates it and prints it, so a bundle holding one can be sent, held and read anywhere; it says nothing about what the number does. Four bytes, like an int32, where the name it replaces was eight and a lookup.

◆ OSETT_WILDPTR_

#define OSETT_WILDPTR_   'P'

A pointer by name: the name a 'p' was bound under, as text.

LAYOUT. [int32 n][the name, NUL-terminated, padded to four], with n the padded length: stored and sized as a blob of n bytes. It holds no address and has no pointer width, so any process may write one and any other read it, and the input walk accepts it from outside.

APPLIED, it does what /$ of its name would: one of the VM's own operations if it names one, otherwise whatever OSEVM_LOOKUP finds for it where it is applied, which is then applied in turn (osevm_lib.c). So a function that has crossed a boundary is called by name there, and means what its name means there: late binding, as for any name in a program. A name bound to nothing raises OSE_ERR_UNBOUND.

ose_disarmPointers() writes every 'p' in a value as a 'P'; ose_rearmPointers() resolves them back, which is only a cache.

Variable Documentation

◆ OSE_ADDRESS_ANONVAL_EMPTY_TYPETAG_STRING

const char* const OSE_ADDRESS_ANONVAL_EMPTY_TYPETAG_STRING
static
Initial value:
=
"\0\0\0\0" ",\0\0\0"

◆ OSE_BUNDLE_HEADER

const char* const OSE_BUNDLE_HEADER
static
Initial value:
=
"#bundle\0" "\0\0\0\0\0\0\0\0"

◆ OSE_EMPTY_TYPETAG_STRING

const char* const OSE_EMPTY_TYPETAG_STRING
static
Initial value:
=
",\0\0\0"

◆ OSE_FROMSTRING_ADDR

const char* const OSE_FROMSTRING_ADDR
static
Initial value:
=
"/" "%c" "/FRST"

◆ OSE_FROMSTRING_ADDR_LEN

const int32_t OSE_FROMSTRING_ADDR_LEN
static
Initial value:
=
sizeof( "/" "%c" "/FRST" )

◆ OSE_GETPAYLOADITEMLENGTH_ADDR

const char* const OSE_GETPAYLOADITEMLENGTH_ADDR
static
Initial value:
=
"/" "%c" "/GPIL"

◆ OSE_GETPAYLOADITEMLENGTH_ADDR_LEN

const int32_t OSE_GETPAYLOADITEMLENGTH_ADDR_LEN
static
Initial value:
=
sizeof( "/" "%c" "/GPIL" )

◆ OSE_GETPAYLOADITEMSIZE_ADDR

const char* const OSE_GETPAYLOADITEMSIZE_ADDR
static
Initial value:
=
"/" "%c" "/GPIS"

◆ OSE_GETPAYLOADITEMSIZE_ADDR_LEN

const int32_t OSE_GETPAYLOADITEMSIZE_ADDR_LEN
static
Initial value:
=
sizeof( "/" "%c" "/GPIS" )

◆ OSE_PPRINTPAYLOADITEM_ADDR

const char* const OSE_PPRINTPAYLOADITEM_ADDR
static
Initial value:
=
"/" "%c" "/PPPI"

◆ OSE_PPRINTPAYLOADITEM_ADDR_LEN

const int32_t OSE_PPRINTPAYLOADITEM_ADDR_LEN
static
Initial value:
=
sizeof( "/" "%c" "/PPPI" )