Execution Flow
Below is a high-level walkthrough of how CrossBasic’s VM goes from your source text all the way to executing cross-platform compiled bytecode. Use this as a guide to understand each major phase, the key data structures, and how control actually flows through the system.
---
config:
theme: dark
layout: elk
---
flowchart TD
%% Developer
Developer["Developer\n(IDE Browser & CLI Shell)"]:::actor
%% IDE Subsystem
subgraph "IDE Subsystem"
UI["Web UI - (www)"]:::ide
Server["IDE Server - (server.cpp)"]:::ide
PerfTest["Performance Tests - (ServerPerformanceTest)"]:::ide
%%BuildIDE["IDE Build Scripts (build-64.bat/.sh)"]:::scripts
end
%% Core Engine
subgraph "Core Engine"
CBC["crossbasic CLI"]:::core
VM["VM & Bytecode Compiler"]:::core
XComp["xcompile CLI"]:::core
%%BuildCB["build_crossbasic.bat/.sh"]:::scripts
%%BuildXC["build_xcompile.bat/.sh"]:::scripts
end
%% Plugin Subsystem
subgraph "Plugin Subsystem"
Loader["Plugin Loader"]:::core
Plugins["Plugins"]:::plugin
end
%% Interop Layer
subgraph "Interop Layer"
FFI["libffi"]:::interop
CURL["libcurl"]:::interop
end
%% Build & Deploy
subgraph "Build & Deploy"
Scripts["Build System & Scripts - (build_plugins.bat/.sh, runallscripts.bat/.sh)"]:::scripts
end
%% Interoperability Templates
subgraph "Interoperability Templates"
Templates["Plugin Language Templates - (Interoperability/Plugin Language Templates)"]:::interop
end
%% Xojo Integration
subgraph "3rd Party Integration - ie) Xojo, Python, NodeJS, etc."
XojoRoot["Xojo Integration Demo"]:::ide
end
%% External Services
subgraph "External Services"
LLM["LLM APIs"]:::external
FS["Filesystem"]:::external
NET["Network"]:::external
end
%% Relationships
Developer -->|HTTP Requests| Server
Server -->|serves| UI
Server -->|invokes compile| CBC
Developer -->|CLI Commands| CBC
Developer -->|CLI Commands| XComp
CBC -->|compiles bytecode| VM
CBC -->|loads via libffi| Loader
Loader -->|discovers| Plugins
Plugins -->|uses FFI| FFI
Plugins -->|calls external APIs| CURL
Plugins -->|HTTP calls| NET
Plugins -->|uses external| LLM
VM -->|I/O| FS
CBC -->|native handoff| XComp
Scripts -->|build core| CBC
Scripts -->|build plugins| Plugins
Templates -->|generate bindings for| Plugins
XojoRoot -->|embeds library| CBC
%% Click Events
click UI "https://github.com/simulanics/crossbasic/tree/main/CrossBasic-IDE/www/"
click Server "https://github.com/simulanics/crossbasic/blob/main/CrossBasic-IDE/server.cpp"
click PerfTest "https://github.com/simulanics/crossbasic/tree/main/CrossBasic-IDE/ServerPerformanceTest/"
click BuildIDE "https://github.com/simulanics/crossbasic/blob/main/CrossBasic-IDE/build-64.bat"
click CBC "https://github.com/simulanics/crossbasic/blob/main/crossbasic.cpp"
click CBC "https://github.com/simulanics/crossbasic/blob/main/crossbasic.rc"
click BuildCB "https://github.com/simulanics/crossbasic/blob/main/build_crossbasic.bat"
click BuildCB "https://github.com/simulanics/crossbasic/blob/main/build_crossbasic.sh"
click XComp "https://github.com/simulanics/crossbasic/blob/main/xcompile.cpp"
click XComp "https://github.com/simulanics/crossbasic/blob/main/xcompile.rc"
click BuildXC "https://github.com/simulanics/crossbasic/blob/main/build_xcompile.bat"
click BuildXC "https://github.com/simulanics/crossbasic/blob/main/build_xcompile.sh"
click Scripts "https://github.com/simulanics/crossbasic/blob/main/build_plugins.bat"
click Scripts "https://github.com/simulanics/crossbasic/blob/main/build_plugins.sh"
click Scripts "https://github.com/simulanics/crossbasic/blob/main/runallscripts.bat"
click Scripts "https://github.com/simulanics/crossbasic/blob/main/runallscripts.sh"
click Plugins "https://github.com/simulanics/crossbasic/tree/main/Plugins/"
click Templates "https://github.com/simulanics/crossbasic/tree/main/Interoperability/Plugin Language Templates/"
click XojoRoot "https://github.com/simulanics/crossbasic/tree/main/Xojo Integration/"
%% Styles
classDef actor fill:#292929,stroke:#333,stroke-width:1px
classDef ide fill:#571f1f,stroke:#f66,stroke-width:1px
classDef core fill:#393961,stroke:#66f,stroke-width:1px
classDef plugin fill:#224222,stroke:#6c6,stroke-width:1px
classDef interop fill:#572057,stroke:#c6c,stroke-width:1px
classDef scripts fill:#6e5d5d,stroke:#999,stroke-width:1px
classDef external fill:#005f6b,stroke:#cc0,stroke-width:1px 1. Program Startup¶
-
main() -
Records a start time (
startTime) for built-ins liketicksandmicroseconds. - Attempts to embed or retrieve pre-compiled bytecode via
retrieveData(). -
If no embedded bytecode is found, reads your
.xssource file. -
Environment Initialization
- Creates the global
Environment(a case-insensitive map of names →Value). - Defines all built-in constants (
pi,eol,…) and built-in functions (print,sleep,array, math, string, array utilities, plugin loaders, etc.). - Loads any plugin DLLs/shared-objects from
libs/, registering their functions and classes intovm.environment.
2. Front-End: From Text to AST¶
- Preprocessing
-
Strips comments (
//,'), handles line-continuations (_), and preserves string literals. -
Lexing
-
Scans character by character, producing a
vector<Token>with types likeNUMBER,IDENTIFIER,PLUS,FUNCTION,ENUM, etc. -
Parsing
- Builds an AST of
Stmtnodes (functions, classes, if/while/for, enums, modules, Goto/Label, etc.) andExprnodes (binary, call, property, literal, new, etc.). - Supports
Select Case,Declarefor external APIs,Extendsfor module-level extensions,ASSIGNS-style calls, labels & gotos, circular references, etc.
3. Compilation: AST → Bytecode¶
- Compiler Setup
-
All code emits into
vm.mainChunk: CodeChunk { code: vector<int>, constants: vector<Value> }. -
Emitting Instructions
-
Constants (literals, compiled
ObjFunctionclosures, compiledObjModule,ObjEnum) are collected intochunk.constants. -
OpCodes and their operands (e.g.
OP_CONSTANT <idx>,OP_ADD,OP_CALL <arity>,OP_JUMP <offset>, …) are emitted intochunk.code. -
Label & Goto Fix-ups
-
Forward
GotoStmtemit a placeholderOP_JUMP 0. -
After all statements are compiled, the compiler patches each placeholder with the final target offset.
-
Extension & Module Exports
-
For
Extendsand module-scopedPublicmembers, the compiler also registers wrappers invm.extensionMethods[type][methodName]or in a per-modulepublicMembersmap.
4. Execution: The Fetch–Decode–Execute Loop¶
All execution happens in:
4.1 Setup¶
int ip = 0;— instruction pointer intochunk.code.vm.stack: vector<Value>— the single evaluation stack.vm.environmentpoints at the currentEnvironment(starts atvm.globals).
4.2 Loop¶
while (ip < chunk.code.size()) {
processPendingCallbacks(); // handle any queued plugin/event callbacks
int instruction = chunk.code[ip++];
switch (instruction) {
case OP_CONSTANT:
// read index, push chunk.constants[index]
case OP_ADD:
// pop two values, add (int/double/string), push result
case OP_CALL:
// pop <arity> args, pop callee Value, dispatch:
// • BuiltinFn → invoke directly
// • ObjFunction → new Environment, bind params, recursive runVM
// • overload set → resolve by arg count
// • ObjBoundMethod → handle methods, plugin or scripted
// • ObjArray → get/set index
// • string literal → built-ins like print/val/str/ticks
case OP_OPTIONAL_CALL:
// like OP_CALL but treats NIL callee as a no-op
case OP_RETURN:
// pop return value and return from runVM
case OP_NEW:
// pop class, instantiate ObjInstance (plugin or builtin), push it
case OP_GET_PROPERTY:
case OP_SET_PROPERTY:
// dynamic property lookup or setter on instances, modules, enums, extensions
case OP_JUMP_IF_FALSE:
case OP_JUMP:
// conditional or unconditional branch
… etc …
}
// Optional debug log prints stack contents after each instruction
}
processPendingCallbacks()drains a thread-safe queue ofCallbackRequestand calls back into script viainvokeScriptCallback(), ensuring plugin events run on the VM thread with proper locking.
4.3 Stack & Environments¶
- Arguments for calls are pushed before invoking
OP_CALL. -
On scripted calls, we:
-
Save
vm.environmentand stack depth. - Create a new child
Environment(parameters + locals). runVM(...)recursively on the function’s bytecode.- Restore the previous
environmentand trim the stack back to its saved depth, then push the single returnValue.
5. Memory Management & ARC¶
- Every heap-allocated object (
ObjFunction,ObjClass,ObjInstance,ObjArray,ObjModule,ObjEnum) lives inside astd::shared_ptr<…>held in aValue. - ARC (via
shared_ptr) increments on push/assignment, decrements on pop/reassignment, and destroys objects as soon as their count reaches zero—except in circular graphs, where you must use weak references or manual breaks.
Key Takeaways¶
- Single Stack VM: All intermediate values live on one vector stack.
- Recursive C++ Calls: Each scripted function invokes
runVM(...)in a new C++ call frame plus a newEnvironment. - Deterministic ARC: No GC pauses—objects free immediately when unreferenced.
- Extensible: Built-in, user-scripted, and plugin code all interoperate via
BuiltinFnlambdas and libffi callbacks (AddressOf→scriptCallbackTrampoline). - Modular: You can
Module … End Moduleto scope definitions and expose onlyPublicmembers as a properObjModule.
With this flow, CrossBasic turns your .xs text into fast, safe, and extensible bytecode running on a minimal C++ stack-machine core.