16 KiB
Berry Mapping Repository Deep Architecture Analysis
Executive Summary
The Berry Mapping library provides a sophisticated C-to-Berry function mapping system that enables seamless integration between Berry scripts and native C functions. This analysis explores the architectural patterns, API design, and implementation strategies that make this library a powerful bridge between interpreted Berry code and compiled C functions.
ARCHITECTURAL HIGHLIGHTS:
- Automatic Type Conversion: Intelligent mapping between Berry and C type systems
- Callback Management: Dynamic C callback generation from Berry functions
- Memory Efficiency: Optimized for embedded systems with minimal overhead
- Flexible Parameter Handling: Support for complex parameter patterns and optional arguments
1. CORE ARCHITECTURE OVERVIEW
1.1 Repository Structure
berry_mapping/
├── src/
│ ├── be_mapping.h # Core API definitions and type mappings
│ ├── be_class_wrapper.c # Main C-to-Berry mapping engine
│ ├── be_cb_module.c # Callback management system
│ ├── be_const_members.c # Constant member resolution system
│ ├── be_mapping_utils.c # Utility functions for data operations
│ └── be_raisef.c # Extended error handling utilities
├── README.md # API documentation and examples
├── library.json # PlatformIO library metadata
└── LICENSE # MIT License
1.2 Architectural Layers
Layer 1: Type Conversion Engine
Berry Types ↔ Type Converter ↔ C Types
↓ ↓ ↓
int/real → Converter → intptr_t/float
string → Converter → const char*
instance → Converter → void* (via _p member)
function → Converter → C callback pointer
Layer 2: Function Call Orchestration
Berry Call → Parameter Validation → Type Conversion → C Function Execution → Result Conversion → Berry Return
Layer 3: Callback Management
Berry Function → Callback Registration → C Stub Generation → Native Callback → Berry Execution
2. TYPE SYSTEM ARCHITECTURE
2.1 Berry-to-C Type Mapping Matrix
Berry Type | C Representation | Conversion Strategy | Memory Model |
---|---|---|---|
int |
intptr_t |
Direct value copy | Stack-based |
real |
breal (float/double) |
Union-based reinterpretation | Stack-based |
bool |
intptr_t (0/1) |
Boolean to integer conversion | Stack-based |
string |
const char* |
Direct pointer reference | Heap-managed |
nil |
NULL (void*) |
Null pointer representation | N/A |
comptr |
void* |
Direct pointer pass-through | External |
instance |
void* |
Extracted via _p or .p member |
Heap-managed |
bytes |
uint8_t* + size |
Buffer pointer + length | Heap-managed |
2.2 Type Conversion Engine Implementation
Core Conversion Function:
intptr_t be_convert_single_elt(bvm *vm, int idx, const char * arg_type, int *len) {
// Multi-stage conversion process:
// 1. Berry type introspection
// 2. Target type validation
// 3. Conversion execution
// 4. Special case handling (callbacks, instances, bytes)
}
Conversion Strategies:
- Direct Value Conversion: Simple types (int, bool, real) copied by value
- Pointer Reference: Strings and buffers passed by reference
- Instance Unwrapping: Objects converted via internal pointer extraction
- Callback Generation: Functions converted to C callback addresses
2.3 Advanced Type Features
Recursive Instance Resolution:
// Supports nested pointer extraction
obj.member._p → void* → C function parameter
Callback Type System:
// Dynamic callback type resolution
^callback_type^ → cb.make_cb(func, type, self) → C function pointer
3. FUNCTION MAPPING ENGINE
3.1 Parameter Processing Architecture
Parameter Descriptor System:
// Type string format: "return_type" "argument_types"
be_call_c_func(vm, func_ptr, "i", "ifs") // int func(int, float, string)
Argument Type Encoding:
i
- Integer (int32_t/intptr_t)f
- Float/Real (breal)b
- Boolean (converted to int)s
- String (const char*)c
- Common pointer (void*).
- Any type (no validation)-
- Skip argument (method self parameter)@
- VM pointer (virtual parameter)~
- Buffer length (virtual parameter)[...]
- Optional parameters(class)
- Instance type validation^type^
- Callback with type specification
3.2 Function Call Orchestration
Call Pipeline:
int be_call_c_func(bvm *vm, const void * func, const char * return_type, const char * arg_type) {
// Stage 1: Argument validation and counting
int argc = be_top(vm);
// Stage 2: Parameter conversion and packing
intptr_t p[8] = {0}; // Maximum 8 parameters
int c_args = be_check_arg_type(vm, 1, argc, arg_type, p);
// Stage 3: C function invocation
fn_any_callable f = (fn_any_callable) func;
intptr_t ret = (*f)(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
// Stage 4: Return value conversion
// Convert C return value back to Berry type based on return_type
}
Return Type Processing:
''
- No return (nil)i
- Integer returnf
- Float returns
- String return (copied)$
- String return (freed after copy)c
- Pointer return&
- Bytes buffer returnclass_name
- Instance creation with pointer
4. CALLBACK SYSTEM ARCHITECTURE
4.1 Callback Generation Strategy
Pre-allocated Stub System:
#define BE_MAX_CB 20 // Fixed number of callback slots
// Each callback has a unique C address
static const berry_callback_t berry_callback_array[BE_MAX_CB] = {
berry_cb_0, berry_cb_1, berry_cb_2, ..., berry_cb_19
};
Callback Registration Process:
typedef struct be_callback_hook {
bvm *vm; // Associated Berry VM
bvalue f; // Berry function/closure
} be_callback_hook;
static be_callback_hook be_cb_hooks[BE_MAX_CB] = {0};
4.2 Dynamic Callback Handler System
Handler Chain Architecture:
typedef struct be_callback_handler_list_t {
bvm *vm; // Target VM
bvalue f; // Handler function
struct be_callback_handler_list_t *next; // Linked list chain
} be_callback_handler_list_t;
Callback Resolution Process:
- Handler Chain Traversal: Check registered handlers for type-specific processing
- Generic Fallback: Use default
gen_cb()
if no specific handler matches - Address Assignment: Return unique C function pointer for callback
4.3 Callback Execution Model
C-to-Berry Call Bridge:
static int call_berry_cb(int num, int v0, int v1, int v2, int v3, int v4) {
// 1. Validate callback slot and VM state
// 2. Push Berry function onto stack
// 3. Convert C parameters to Berry arguments
// 4. Execute Berry function via be_pcall()
// 5. Convert Berry return value to C integer
}
Parameter Conversion Strategy:
- All C parameters converted to Berry integers
- Complex data passed as pointers (converted via
introspect.toptr()
) - Buffer data accessible via
bytes()
objects
5. CONSTANT MEMBER SYSTEM
5.1 Virtual Member Architecture
Constant Definition Structure:
typedef struct be_const_member_t {
const char * name; // Member name (with type prefix)
intptr_t value; // Member value/pointer
} be_const_member_t;
Type Prefix System:
COLOR_WHITE
- Integer constant$SYMBOL_OK
- String constant&font_data
- Pointer constant@native_func
- Native function*dynamic_func
- Dynamic function call/class_name
- Class reference
5.2 Binary Search Implementation
Optimized Lookup Strategy:
int be_map_bin_search(const char * needle, const void * table, size_t elt_size, size_t total_elements) {
// 1. Skip type prefix characters for comparison
// 2. Binary search through sorted constant table
// 3. Return index or -1 if not found
}
Memory Layout Optimization:
- Constants stored in Flash memory (ROM)
- Minimal RAM usage for lookup operations
- Efficient cache utilization through binary search
6. CTYPE FUNCTION SYSTEM
6.1 Pre-compiled Function Definitions
CType Function Structure:
typedef struct be_ctype_args_t {
const void* func; // C function pointer
const char* return_type; // Return type specification
const char* arg_type; // Argument type specification
} be_ctype_args_t;
Macro-based Definition System:
#define BE_FUNC_CTYPE_DECLARE(_name, _ret_arg, _in_arg) \
static const be_ctype_args_t ctype_func_def##_name = { \
(const void*) &_name, _ret_arg, _in_arg \
};
// Usage example:
BE_FUNC_CTYPE_DECLARE(my_function, "i", "ifs") // int my_function(int, float, string)
6.2 Module Integration Pattern
Constant Object Integration:
/* @const_object_info_begin
module my_module (scope: global) {
my_func, ctype_func(my_ext_func, "i", "ifs")
}
@const_object_info_end */
Runtime Handler Registration:
void berry_launch(void) {
bvm *vm = be_vm_new();
be_set_ctype_func_handler(vm, be_call_ctype_func);
}
7. MEMORY MANAGEMENT PATTERNS
7.1 Stack-based Parameter Passing
Parameter Array Strategy:
intptr_t p[8] = {0}; // Fixed-size parameter array
// Advantages:
// - Predictable memory usage
// - No dynamic allocation
// - Cache-friendly access pattern
Stack Frame Management:
- Berry stack manipulation for argument passing
- Automatic cleanup on function return
- Exception-safe stack unwinding
7.2 Garbage Collection Integration
GC Object Handling:
if (be_isgcobj(v)) {
be_gc_fix_set(vm, v->v.gc, btrue); // Prevent collection during use
}
// ... use object ...
if (be_isgcobj(&obj->f)) {
be_gc_fix_set(vm, obj->f.v.gc, bfalse); // Allow collection
}
Memory Lifecycle Management:
- Callback functions protected from GC during registration
- Automatic cleanup on VM destruction
- Reference counting for shared objects
8. ERROR HANDLING ARCHITECTURE
8.1 Exception-based Error Model
Error Propagation Strategy:
void be_raisef(bvm *vm, const char *except, const char *msg, ...) {
// 1. Format error message with variable arguments
// 2. Validate format string safety
// 3. Raise Berry exception with formatted message
}
Exception Types:
value_error
- Invalid parameter valuestype_error
- Type mismatch errorsinternal_error
- System-level errorsresource_error
- Resource exhaustion
8.2 Graceful Degradation Patterns
Fallback Strategies:
- Invalid callbacks return 0 (safe default)
- Type conversion failures raise exceptions
- Resource limits enforced with clear error messages
- Stack overflow protection through bounds checking
9. PERFORMANCE OPTIMIZATION STRATEGIES
9.1 Embedded System Optimizations
Memory Efficiency Techniques:
- Fixed-size arrays instead of dynamic allocation
- Stack-based parameter passing
- Minimal heap usage for temporary objects
- Efficient string handling without copying
CPU Optimization Patterns:
- Binary search for constant lookup (O(log n))
- Direct pointer manipulation for type conversion
- Minimal function call overhead
- Cache-friendly data structures
9.2 Scalability Considerations
Resource Limits:
- Maximum 8 parameters per function call
- 20 simultaneous callbacks supported
- Configurable string length limits
- Bounded recursion depth
Performance Characteristics:
- O(1) function call overhead
- O(log n) constant member lookup
- O(1) callback registration and execution
- Minimal GC pressure through careful object management
10. API DESIGN PATTERNS
10.1 Fluent Interface Design
Method Chaining Support:
// Enables patterns like:
obj.method1(args).method2(args).method3(args)
Return Value Optimization:
- Instance methods return
self
for chaining - Factory methods return new instances
- Utility functions return processed values
10.2 Extensibility Mechanisms
Plugin Architecture:
- Custom callback handlers via
cb.add_handler()
- Module-specific type conversions
- Extensible constant member systems
- Dynamic function registration
Hook System:
// Callback handler registration
be_callback_handler_list_t *handler = create_handler();
handler->next = be_callback_handler_list_head;
be_callback_handler_list_head = handler;
11. INTEGRATION PATTERNS
11.1 LVGL Integration Model
Widget Callback Pattern:
// Berry code:
def button_callback(obj, event)
print("Button pressed!")
end
button.set_event_cb(button_callback, lv.EVENT.CLICKED)
C Integration:
// Automatic callback type resolution
^lv_event_cb^ → LVGL-specific callback handler → C function pointer
11.2 Module System Integration
Native Module Pattern:
/* @const_object_info_begin
module hardware (scope: global) {
gpio_read, ctype_func(gpio_read_pin, "i", "i")
gpio_write, ctype_func(gpio_write_pin, "", "ii")
}
@const_object_info_end */
Dynamic Loading Support:
- Runtime module registration
- Lazy initialization of native functions
- Conditional compilation support
12. ARCHITECTURAL STRENGTHS
12.1 Design Excellence
Separation of Concerns:
- Type conversion isolated from function calling
- Callback management separate from execution
- Error handling centralized and consistent
- Memory management integrated but modular
Flexibility and Extensibility:
- Pluggable callback handlers
- Configurable type validation
- Extensible constant systems
- Modular architecture
12.2 Embedded Systems Suitability
Resource Efficiency:
- Minimal RAM footprint
- Predictable memory usage patterns
- No dynamic allocation in critical paths
- Efficient CPU utilization
Reliability Features:
- Bounds checking throughout
- Exception-based error handling
- Graceful degradation on errors
- Comprehensive input validation
13. ARCHITECTURAL CONSIDERATIONS
13.1 Platform Dependencies
Architecture Assumptions:
- Requires uniform pointer and integer sizes (32-bit or 64-bit)
- Assumes little-endian byte ordering for type punning
- Stack-based parameter passing model
- C calling convention compatibility
Portability Strategies:
- Configurable type definitions
- Platform-specific optimization hooks
- Conditional compilation support
- Abstract interface definitions
13.2 Scalability Limits
Current Constraints:
- Maximum 8 parameters per function
- 20 callback slots total
- Fixed-size parameter arrays
- Single-threaded execution model
Future Enhancement Opportunities:
- Dynamic parameter array sizing
- Thread-safe callback management
- Asynchronous function execution
- Enhanced type system support
CONCLUSION
The Berry Mapping library represents a sophisticated architectural achievement in bridging interpreted and compiled code domains. Its design demonstrates deep understanding of both Berry's dynamic type system and C's static type requirements, creating an elegant solution that maintains performance while providing flexibility.
Key Architectural Achievements:
- Elegant Type Bridging: Seamless conversion between dynamic and static type systems
- Efficient Resource Usage: Optimized for embedded systems with minimal overhead
- Extensible Design: Plugin architecture supports diverse integration scenarios
- Robust Error Handling: Comprehensive exception-based error management
- Performance Optimization: Careful attention to memory and CPU efficiency
This architecture serves as an excellent foundation for embedded systems requiring dynamic scripting capabilities while maintaining the performance and reliability characteristics essential for production deployment.
This analysis reflects the architectural design of the Berry Mapping library as of June 2025, focusing on the technical implementation patterns and design decisions that make this library effective for embedded systems integration.