|
|
#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 |
| |
|
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) |
| |
|
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" |
| |
|
|
#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 |
| |
|
|
#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 |
| |
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.
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.
| #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.
| #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.
| #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.