C
The most complete path: drawing, input, sound, save data and the gamepad are all available. The full games in this repo (Tetris, Gomoku, Pac-Man) are all written in C.
If you are not sure which language to pick: use C.
How to run it on your phone
vml run examples/c/test/sysinfo.c
Compiling takes a while (one to two minutes for C, a few seconds for scripting languages). Once the program is running, the gamepad is at the bottom of the screen - the D-pad and the four action buttons are all there. Tap the back arrow in the top-right corner to return to the Shell.
What to know before you write
- Include
#include <vml_ui.h>and you get every interface declaration - Colors are always
0xAARRGGBB(alpha first) - Do not run arrays out of bounds (a "memory error" is almost always this)
Examples
| File | What it shows |
|---|---|
app/gomoku.c | Gomoku (tap to place, play against the computer, portrait only with no gamepad area) |
app/mario.c | Side-scrolling platformer |
app/pacman.c | Pac-Man (grid map plus chasing AI) |
app/starfall.c | Vertical bullet-hell |
app/tetris.c | Tetris (score, levels, restart, sound) |
test/sysinfo.c | Calls ui_call_json("sysinfo") to print device information |
ui/draw_prims.c | Shape check-up: one cell drawn per drawing instruction |
Also in this folder: 3d/Avocado.glb, 3d/ToyCar.glb, 3d/air3d.c, 3d/bench3d.c, 3d/blend_demo.c, 3d/cube.obj, 3d/cube_min.c, 3d/foxcatch.c, 3d/g3_all.c, 3d/gamma_demo.c, 3d/glbview.c, 3d/gltf_demo.c, 3d/landscape.c, 3d/light_demo.c, 3d/normalmap_demo.c, 3d/objload.c, 3d/ortho_demo.c, 3d/pbr_demo.c, 3d/pool3d.c, 3d/race3d.c, 3d/rover3d.c, 3d/rover_cutout.glb, 3d/shadow_demo.c, 3d/shadowmap_demo.c, 3d/skybox_radius.c, 3d/snake3d.c, 3d/solar3d.c, 3d/sphere.obj, 3d/tank3d.c, 3d/tex_demo.c, 3d/torus.obj, 3d/vml3d_tri.c, app/battery.c, app/calc.c, app/calendar.c, app/chess.c, app/clipboard.c, app/filedlg.c, app/gallery.c, app/gyro.c, app/level.c, app/locate.c, app/matrix_rain.c, app/notepad.c, app/piano.c, app/plane.c, app/stock.c, app/stocks.txt, app/stopwatch.c, app/tilt.c, app/todo.c, app/weather.c, app/weather.txt, bgi/demo_bgi.c, bgi/test_bgi.c, c.bat, demo/audio_all.c, demo/audio_steps.c, demo/demo_std.c, old/old_gfx_anim.c, old/old_gfx_bgi.c, old/old_gfx_lines.c, old/old_gfx_palette.c, old/old_gfx_plot.c, old/old_gfx_shapes.c, old/old_std_calc.c, old/old_std_guess.c, old/old_std_hanoi.c, old/old_std_primes.c, old/old_std_wc.c, old/old_tty_ansi.c, old/old_tty_box.c, old/old_tty_conio.c, old/old_tty_curses.c, old/old_tty_progress.c, test/audio_test.c, test/block_bench.c, test/builtin_dup_api.c, test/callports.c, test/file_api.c, test/http_api.c, test/libs.c, test/mask_bool.c, test/minmax_api.c, test/module_init_api.c, test/regname.c, test/sleep_api.c, test/string_api.c, test/ui_lang.c, test/wintest.c, tty/ansi_colors.c, tty/conio_screen.c, tty/curses_demo.c, tty/demo_tty.c, tty/demo_tty_alt.c, tty/dos_demo.c, tty/pcscreen.c, tty/tty_legacy.c, ui/color_chart.c, ui/demo_ui.c, ui/draw_brush.c, ui/draw_colors.c, ui/gradient_check.c, ui/shot.c, ui/test_font.c
Pitfalls we hit on real devices
- The slowest to compile: a program that pulls in the standard library takes one to two minutes on a phone (Python/Lua and friends take seconds). If you are going to run the same program over and over, compile it to
.vmbfirst and run that: it is much faster. - Watch out for negative numbers in a global array's initializer (a historical defect, since fixed; test it if you write direction tables that way).
The rest of this page is taken straight from the VML source (third_party/vml/VMLPrepares/CCompiler/): the README covers what this front end supports and how to compile it, and the language reference covers the syntax itself. When upstream changes, regenerating this page brings it up to date.
Language reference
C Language Compiler Specification
Version: v1.1 | Date: 2026-07-06 | Revised by: Shenzhen Tanso Intelligent Technology Co., Ltd.
Specification standard
| Field | Value |
|---|---|
| Target standard | C99 (ISO/IEC 9899:1999) |
| Release year | 1999 |
| Completeness | ~97% |
| Tests | 43+ passing (including 12 Int64) + Lua 5.4 31/31 + cJSON/nbsdgames/parson/tiny_regex_c |
| Milestones | Lua 5.4 31/31 🎉 + SQLite 255K lines (v1.65.36) |
Overview
This compiler supports a subset of the C99 standard and compiles C source code into VML (Virtual Machine Language) assembly. It follows the classic compilation pipeline: preprocessing → lexical analysis → syntax analysis → code generation.
Supported language features
1. Data types
Basic data types
- Integer types:
int,short,long,signed,unsigned- Type modifiers can be combined:
unsigned int,signed long,unsigned short, and so on - The default integer type is
signed int unsignedon its own is equivalent tounsigned intsignedon its own is equivalent tosigned int
- Type modifiers can be combined:
- Character types:
char,signed char,unsigned char - Floating-point types:
float,double - Boolean type:
bool(C99 extension) - Void type:
void
Constant suffixes
- Integer constant suffixes:
Lorl: long integer constant, e.g.100LUoru: unsigned integer constant, e.g.100UULorul: unsigned long integer constant, e.g.100UL
- Floating-point constant suffixes:
Forf: single-precision floating-point constant, e.g.3.14F- no suffix: double-precision floating-point constant, e.g.
3.14
- Hexadecimal constant suffixes:
- hexadecimal constant suffixes are supported:
0xFFU,0x7FFFFFFFL,0xFFFFFFFFUL
- hexadecimal constant suffixes are supported:
Derived types
- Pointers:
int*,char*,void*, and so on - Arrays: one-dimensional and multi-dimensional arrays, with initialization, e.g.
int arr[10]andint matrix[2][3] = {{1,2,3},{4,5,6}} - Structs: global struct definitions and member access
- Enums: global enum definitions and use
2. Variable declaration and definition
Variable declaration
int x; // global variable
int y = 10; // global variable with initializer
static int z = 20; // static variable
void func() {
int local_var; // local variable
int init_var = 30; // local variable with initializer
static int local_static = 40; // static local variable
}
Constants
const int MAX_SIZE = 100;
const float PI = 3.14159;
3. Operators
Arithmetic operators
+ // addition
- // subtraction
* // multiplication
/ // division
% // modulo
++ // increment
-- // decrement
Relational operators
== // equal to
!= // not equal to
< // less than
<= // less than or equal to
> // greater than
>= // greater than or equal to
Logical operators
&& // logical AND
|| // logical OR
! // logical NOT
Bitwise operators
& // bitwise AND
| // bitwise OR
^ // bitwise XOR
~ // bitwise NOT
<< // left shift
>> // right shift
Assignment operators
= // assignment
+= // add and assign
-= // subtract and assign
*= // multiply and assign
/= // divide and assign
%= // modulo and assign
&= // bitwise AND and assign
|= // bitwise OR and assign
^= // bitwise XOR and assign
<<= // left shift and assign
>>= // right shift and assign
Other operators
& // address-of
* // dereference
. // struct member access
-> // struct member access through a pointer
sizeof // size of a type
4. Type conversion and promotion
Implicit type conversion
- Integer promotion: inside expressions,
charandshortare automatically promoted toint - Floating-point promotion: in mixed-type expressions, integers are automatically converted to floating point
- Assignment conversion: on assignment, the type of the right-hand expression is automatically converted to the type of the left-hand variable
Explicit type conversion (casts)
int x = 10;
float y = (float)x; // int to float
int z = (int)3.14; // float to int
unsigned int u = (unsigned int)-10; // signed to unsigned
Type promotion rules
- If either operand is
float, the result isfloat - If either operand is
double, the result isdouble - Otherwise integer promotion applies and the result type is chosen by type rank
- Type ranks (low to high):
char/unsigned charshort/unsigned shortint/unsigned intlong/unsigned long
5. Control-flow statements
Conditional statements
// if-else
if (condition) {
// statement block
} else if (condition2) {
// statement block
} else {
// statement block
}
// ternary operator
int result = condition ? value1 : value2;
Loops
// while loop
while (condition) {
// loop body
}
// do-while loop
do {
// loop body
} while (condition);
// for loop
for (int i = 0; i < 10; i++) {
// loop body
}
Jump statements
break; // leave the loop or switch
continue; // continue with the next iteration
return; // return a value from the function
goto label; // jump to a label
switch statement
switch (expression) {
case constant1:
// statement block
break;
case constant2:
// statement block
break;
default:
// statement block
break;
}
6. Functions
Function definition
// function declaration
int add(int a, int b);
// function definition
int add(int a, int b) {
return a + b;
}
// function with no return value
void print_message(const char* msg) {
// function body
}
// variadic function (limited support)
int printf(const char* format, ...);
Function calls
int result = add(10, 20);
print_message("Hello, World!");
Recursive functions
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
Interrupt service routines (interrupt)
The interrupt keyword declares a function as an interrupt service routine (ISR). The compiler saves and restores all general-purpose registers automatically and returns with IRET instead of RET.
// interrupt service routine: saves all registers, returns with IRET
interrupt void timer_isr(void) {
// interrupt handling code
// the compiler generates: PUSH R0..R11, PUSH R12, PUSH R15
// restore: POP R15, POP R12, POP R0..R11, IRET
}
// interrupt can be combined with static
interrupt static void keyboard_isr(void) {
// an ISR visible only inside this file
}
Notes:
- An ISR cannot take parameters (it must be
void) - Keep ISRs short (interrupt handling time is limited)
- The compiler manages saving and restoring all registers automatically
- The function's vector address must be registered in VML assembly through the
.vectorsdirective
7. Preprocessor directives
File inclusion
#include "vmlib.h" // user header
#include <stdio.h> // system header (limited support)
Macro definitions
#define MAX_SIZE 100
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#ifdef DEBUG
#define DEBUG_PRINT(msg) printf("DEBUG: %s\n", msg)
#else
#define DEBUG_PRINT(msg)
#endif
Conditional compilation
#if defined(WIN32)
// Windows-specific code
#elif defined(LINUX)
// Linux-specific code
#else
// code for other platforms
#endif
8. Structs and enums
Structs
// struct definition
struct Point {
int x;
int y;
};
// struct variable declarations
struct Point p1;
struct Point p2 = {10, 20};
// struct member access
p1.x = 5;
p1.y = 15;
// struct pointer
struct Point* ptr = &p1;
ptr->x = 100;
Enums
// enum definition
enum Color {
RED,
GREEN,
BLUE
};
// enum variable
enum Color c = RED;
9. Pointers and arrays
Pointers
int x = 10;
int* ptr = &x; // take the address
int y = *ptr; // dereference
// pointer arithmetic
int arr[10];
int* p = arr;
p++; // move the pointer forward
p--; // move the pointer backward
Arrays
// array declaration and initialization
int arr1[10];
int arr2[5] = {1, 2, 3, 4, 5};
int arr3[] = {1, 2, 3}; // size inferred automatically
// partial initialization (unspecified elements become 0)
int arr4[10] = {1, 2, 3}; // arr4[0]=1, arr4[1]=2, arr4[2]=3, arr4[3..9]=0
// array access
arr1[0] = 100;
int value = arr2[2];
// multi-dimensional arrays
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// partial initialization of a multi-dimensional array
int matrix2[2][3] = {
{1, 2}, // the third column becomes 0
{4, 5, 6}
};
// nested array initialization
int cube[2][2][2] = {
{
{1, 2},
{3, 4}
},
{
{5, 6},
{7, 8}
}
};
10. Storage classes and extension keywords
Automatic variables (auto)
void func() {
auto int x = 10; // auto can be omitted
int y = 20; // auto by default
}
Register variables (register)
void func() {
register int counter = 0; // hint the compiler to use a register
}
Static variables (static)
static int global_static = 100; // file scope
void func() {
static int local_static = 0; // function scope, keeps its value
local_static++;
}
External variables (extern)
extern int external_var; // declare an external variable
void func() {
external_var = 100; // use the external variable
}
Interrupt service routines (interrupt)
interrupt void isr_name(void) {
// interrupt handling code
// the compiler saves R0-R11 automatically and returns with IRET
}
See "Interrupt service routines" in section 6, "Functions", for details.
Compiler limitations
Unsupported features
- Bit fields: struct bit fields are not supported
- Complex pointer arithmetic: pointer arithmetic is limited to simple addition and subtraction
- Function pointers: the declaration syntax is supported (v1.65.35), but runtime calls are limited
- Variable-length arrays (VLA): C99 variable-length arrays are not supported
- Complex type qualifiers:
restrict,_Atomicand similar are not supported - The
unsignedmodifier:unsigned int/unsigned charare treated asint/char(VML has no notion of unsigned)
v1.65.35 milestone: compiling SQLite
- Parsing: the whole SQLite amalgamation parses (255,636 lines, ~475K tokens)
- Compiling: both the SQLite stub and the full version compile and run
- Runtime verification: sqlite3_open / CREATE TABLE / INSERT / SELECT / sqlite3_close all pass
- Type-system improvements: anonymous struct members, double-pointer types, function-pointer declarations
VML extension keywords
On top of standard C99 this compiler adds the following extension keywords:
| Keyword | Purpose | Description |
|---|---|---|
interrupt / __interrupt | function declaration | declares an interrupt service routine; registers are saved automatically and the routine returns with IRET |
__chipasm__ | inline assembly block | inserts multiple lines of VML assembly |
asm | inline assembly | inserts a single line of VML assembly |
__stdcall | calling convention | stdcall convention (arguments pushed right to left, callee cleans up) |
__fastcall | calling convention | fastcall convention (the first 2 arguments are passed in registers) |
__cdecl | calling convention | cdecl convention (arguments pushed right to left, caller cleans up) |
__builtin_va_start | varargs | start of a variadic function |
__builtin_va_arg | varargs | access a variadic argument |
__builtin_va_end | varargs | end of variadic access |
__builtin_va_copy | varargs | copy variadic state |
Partially supported features
- Preprocessor: supports
#include,#define,#undefand conditional compilation (#if/#ifdef/#ifndef/#else/#elif/#endif) - Standard library: supports
stdio.h,stdlib.h,string.h,math.h,conio.h,graphics.h - Floating point: basic floating-point operations (
+ - * /, comparisons, casts) - Structs: basic structs, struct member pointers (
char *buf), nested structs - Unions: the union type is supported
- Array initialization: the full one-dimensional and nested multi-dimensional initialization syntax
- Inline assembly:
asm("instruction")single-line and__chipasm__ { }multi-line syntax - Multi-dimensional arrays: declaration, access and nested initialization
- Calling conventions:
__stdcall/__fastcall/__cdecl - Suffixes: the constant suffixes (
L,U,F) are only fully supported in some contexts
Compilation pipeline
1. Preprocessing
- Handle
#includedirectives - Expand macro definitions
- Handle conditional compilation
- Strip comments
2. Lexical analysis
- Turn the source into a token stream
- Recognize keywords, identifiers, constants, operators and so on
3. Syntax analysis
- Build an abstract syntax tree (AST)
- Check syntactic correctness
- Build the symbol table
4. Code generation
- Walk the AST and emit VML instructions
- Allocate storage for variables
- Generate function-call code
- Optimize the generated code
Examples
Simple program
#include "vmlib.h"
int main() {
vga_clear();
vga_puts("Hello, C Compiler!");
return 0;
}
Computing a factorial
#include "vmlib.h"
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
int main() {
int result = factorial(5);
vga_clear();
vga_puts("Factorial of 5 is: ");
// you still need to convert the number to a string to display it
return 0;
}
Array operations
#include "vmlib.h"
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += arr[i];
}
vga_clear();
vga_puts("Sum of array: ");
// display the result
return 0;
}
Multi-dimensional array initialization
#include "vmlib.h"
int main() {
// two-dimensional array initialization
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// sum the matrix diagonal
int diag_sum = 0;
for (int i = 0; i < 3; i++) {
diag_sum += matrix[i][i];
}
vga_clear();
vga_puts("Diagonal sum: ");
// display the result
return 0;
}
Complex array test
#include "vmlib.h"
// exercise the various ways to initialize an array
int main() {
// automatic size inference
int auto_arr[] = {10, 20, 30, 40, 50};
// partial initialization
int partial[10] = {1, 2, 3}; // the last 7 elements are 0
// multi-dimensional array
int cube[2][2][2] = {
{{1, 2}, {3, 4}},
{{5, 6}, {7, 8}}
};
vga_clear();
vga_puts("Array tests completed");
return 0;
}
Compiler options
Command-line arguments
# basic usage
dotnet run --project VMLPrepares\\CCompiler input.c -o output.vml
# specify the library path
dotnet run --project VMLPrepares\\CCompiler -I Lib\c input.c -o output.vml
# preprocess only
dotnet run --project VMLPrepares\\CCompiler -E input.c
# specify the library path through an environment variable
$env:VML_C_LIB="Lib\c"
dotnet run --project VMLPrepares\\CCompiler input.c -o output.vml
Environment variables
VML_C_LIB: search path for C library files
Error handling
The compiler reports the following kinds of errors:
Syntax errors
- Missing semicolons, unbalanced parentheses, and so on
- The error location and the error message
Semantic errors
- Undeclared identifiers
- Type mismatches
- Duplicate definitions
Preprocessor errors
- Header file not found
- Macro definition errors
C99 standard conformance
This compiler aims at full C99 conformance. About 97% of the core C99 feature set is implemented today:
Core features implemented
- Basic syntax: variable declarations, expressions, control structures
- Data types:
int,char,float,double,void, pointers, arrays, structs, enums - Control flow:
if-else,while,do-while,for,switch,break,continue,return,goto - Functions: definitions, declarations, calls, recursion, parameter passing, implicit int return type
- Preprocessor:
#include,#define,#ifdef,#ifndef,#if,#else,#elif,#endif - Arrays: one-dimensional and multi-dimensional declaration, nested initialization, element access
- Structs: global struct definitions, variable declarations, member access, struct member pointers
- Unions: the union type is supported
- Comma expressions: the comma operator is supported
- Comments: both
/* */and//styles
VML extension features
- Interrupt service routines: the
interruptkeyword declares an ISR; all registers are saved automatically and it returns with IRET - Inline assembly:
asm("instruction")single-line and__chipasm__ { }multi-line VML instructions - Calling conventions:
__stdcall/__fastcall/__cdeclfor cross-language interop - Cross-language interop:
POKE/PEEKmemory-mapped I/O (MMIO)
Key features still missing (planned)
- Type system: full support for the
unsignedtype - Standard library: finish
stdio.hfile operations and all ofmath.h - Advanced features: function pointers, bit fields
- Complex pointer arithmetic: more complete pointer arithmetic and casts
- Wide characters:
wchar_tand the related functions
Differences from standard C
- Memory model: the memory model of the VML virtual machine
- Standard library: the VML runtime library instead of the standard C library
- System calls: I/O goes through the VML runtime
- Integer size: VML's 32-bit integers
- Floating-point precision: VML's floating-point representation
Floating-point and 64-bit compile modes
The VML toolchain controls how floating point and 64-bit integers are handled through three compile parameters:
| Parameter | Values | Default | Description |
|---|---|---|---|
--float32 | hard / soft / none | hard | 32-bit floating point (float) handling |
--float64 | hard / soft / none | soft | 64-bit floating point (double) handling |
--int64 | hard / soft / none | soft | 64-bit integers (long long) handling |
32-bit floating point (float32)
The 32-bit single-precision type float compiles as follows:
hardmode (default): uses the native VML floating-point instructionsMOVEF/ADDF/SUBF/MULF/DIVF/CMPF/NEGF, operating directly on the sixteen floating-point registers F0-F15. Best performance; suited to targets with floating-point hardware.softmode: uses the Q15.16 fixed-point software emulation librarysoftfloat.c, emulating floating-point operations through functions such as__vml_float_add/sub/mul/div/neg/abs/cmp. Suited to MCU platforms with no floating-point hardware.nonemode: disables all 32-bit floating-point types; afloatdeclaration is reported as a compile error.
64-bit floating point (double)
The 64-bit double-precision type double compiles as follows:
softmode (default): uses the IEEE 754 double-precision emulation librarysoftdouble.c, through__vml_double_add/sub/mul/div/neg/abs/cmp,__vml_int2double/double2int,__vml_float2double/double2floatand similar. Works on every platform, MCU included.hardmode: uses the VML double-precision instructionsMOVED/ADDD/SUBD/MULD/DIVD/CMPD/NEGD, operating on the eight double-precision registers D0-D7. Requires the target platform to support 64-bit operations.nonemode: disables all 64-bit floating-point types; adoubledeclaration is reported as a compile error.
64-bit integers (int64)
The 64-bit integer type long long compiles as follows:
softmode (default): uses the two-register software emulation librarysoftint64.c, through functions such as__vml_i64_add/sub/neg/and/or/xor/not/shl/shr.hardmode: uses the native VML 64-bit integer instructionsMOVEL/ADDL/SUBL/MULL/DIVL/MODL/NEGL/CMPL/ANDL/ORL/XORL/NOTL/SHLL/SHRL, operating on the eight long-integer registers L0-L7. Available since v1.65.197+.nonemode: disables the 64-bit integer type; along longdeclaration is reported as a compile error.
Software emulation libraries
These emulation libraries all live in Lib/shared/, are written in C, are compiled to VML by the C compiler, and are shared by every language:
| Library file | Purpose | Core functions |
|---|---|---|
softfloat.c | Q15.16 fixed-point 32-bit floating-point emulation | __vml_float_add/sub/mul/div/neg/abs/cmp, __vml_int2float/float2int |
softdouble.c | IEEE 754 double-precision 64-bit floating-point emulation | __vml_double_add/sub/mul/div/neg/abs/cmp, __vml_int2double/double2int, __vml_float2double/double2float |
softint64.c | 64-bit integer two-register emulation | __vml_i64_add/sub/neg/and/or/xor/not/shl/shr |
Performance considerations
- Code size: the generated VML code is larger than native code
- Execution speed: it runs inside the VML virtual machine, which is slower than native code
- Memory use: it uses the memory space of the VML virtual machine
- Optimization level: currently basic, which produces fairly direct code
Extensibility
The compiler is designed around an extensible architecture:
- Adding new syntax: the lexer and parser can be extended
- Optimization passes: code-optimization stages can be added
- Target platforms: support for other target architectures can be added
- Language features: more C features can be added incrementally
Related files
CCompiler.cs: the main compiler classLexer.cs: the lexerParser.cs: the parserCodeGenerator.cs: the code generatorPreprocessor.cs: the preprocessorProgram.cs: the command-line interfaceREADME.md: the project readme
🆕 String types (v1.65.19)
The C compiler supports string types in three widths:
| Literal | Type | Width | VML directive | Terminator |
|---|---|---|---|---|
"str" | char* | 8-bit | .string | 0x00 |
L"str" | wchar_t* | 16-bit | .wstring | 0x0000 |
U"str" | char32_t* | 32-bit | .ustring | 0x00000000 |
char *s1 = "Hello"; // .string (UTF-8)
wchar_t *s2 = L"你好"; // .wstring (UTF-16LE, BMP only)
char32_t *s3 = U"🎉"; // .ustring (UTF-32LE, full Unicode)
wchar_t c1 = L'字'; // 16-bit wide character
char32_t c2 = U'🎉'; // 32-bit Unicode character
Predefined macro: VML_WSTRING — defined as 1 in OS mode, undefined in MCU mode Standard headers: <wchar.h> / <uchar.h> automatically link Lib/shared/wchar.vml / uchar.vml Output: shared_print_wstr(wstr) — converts UTF-16LE→UTF-8 and calls SYSCALL #1
🆕 Extended preprocessor directives (v1.65.19)
#param — in-source configuration directives
| Directive | Description | Example |
|---|---|---|
#param lib("xxx") | automatically link the given library file | #param lib("wchar") |
#param path("xxx") | add an include search path | #param path("./inc") |
#param prefix("xxx") | set the function-name prefix | #param prefix("python_") |
#param prefix — build a multi-language library from one source
With #param prefix, or the command line -D VML_PREFIX=xxx, one C source file can produce libraries with different prefixes for different languages:
// wstring.c — one source file, shared by many languages
#param prefix("python_") // uncomment this line when building for Python
// #param prefix("java_") // uncomment this line when building for Java
// #param prefix("js_") // uncomment this line when building for JavaScript
size_t wcslen(const wchar_t *s) { ... } // → python_wcslen
wchar_t *wcscpy(wchar_t *d, const wchar_t *s) { ... } // → python_wcscpy
Build commands:
# build python_wcslen, python_wcscpy ... for Python
vmltool wstring.c -D VML_PREFIX=python_ -o python/wstring.vml
# build java_wcslen, java_wcscpy ... for Java
vmltool wstring.c -D VML_PREFIX=java_ -o java/wstring.vml
# build js_wcslen, js_wcscpy ... for JavaScript
vmltool wstring.c -D VML_PREFIX=js_ -o js/wstring.vml
Prefix replacement rules:
shared_xxx→{prefix}xxx(replaces theshared_prefix)vml_xxx→{prefix}xxx(replaces thevml_prefix)- any other function name →
{prefix}+ the original name (the prefix is simply added)
Multi-language library build script
#!/bin/bash
# build_all_langs.sh — build the wstring library for every language
for lang in python java js kotlin swift go csharp; do
vmltool wstring.c -D "VML_PREFIX=${lang}_" -o "${lang}/wstring.vml" --no-link
done
#param lib — automatic library linking
// a header such as wstring.h declares its dependencies
#param lib("wchar") // automatically links wchar.vml
// the user only needs #include "wstring.h"
// the compiler links wstring.vml + wchar.vml automatically
Combined with #ifdef VML_WSTRING you can choose the encoding per mode.
#param path — extra include paths
#param path("./include") // add a relative search path
Calling conventions (v1.65.19)
The C compiler supports three calling conventions, selected with a function modifier:
| Modifier | Argument passing | Stack cleanup | Variadic |
|---|---|---|---|
__cdecl (default) | pushed right to left, R0-R3 carry the first 4 arguments | caller | ✅ |
__stdcall | pushed right to left | callee | — |
__fastcall | R0-R3 carry the first 4 arguments, the rest are pushed | callee | — |
__cdecl variadic functions
// variadic printf
__cdecl void shared_printf(const char *fmt, ...) {
char buf[512];
int *stack_args = (int*)(&fmt + 1); // the variadic arguments follow fmt
int len = shared_vsnprintf(buf, fmt, stack_args, 8);
buf[len] = 0;
asm("SYSCALL #1"); // output to the console
}
// call: any number of arguments (up to 8, limited by the VML stack)
shared_printf("Int: %d Hex: %x Str: %s\n", 42, 255, (int)"Hello");
// variadic sprintf
__cdecl int shared_sprintf(char *buf, const char *fmt, ...) {
int *stack_args = (int*)(&fmt + 1);
return shared_vsnprintf(buf, fmt, stack_args, 8);
}
How variadic arguments work:
__cdeclpushes arguments from right to leftfmtis the fixed parameter closest to the top of the stack&fmt + 1points at the first variadic argument on the stack- the stack pointer lets you read every variadic argument directly
__stdcall fixed parameters
// the callee cleans up the stack, Windows API style
__stdcall void shared_print_str(const char *str) {
asm("SYSCALL #1");
}
__fastcall fast calls
// the first 4 arguments go in registers; good for performance-sensitive code
__fastcall int add_four(int a, int b, int c, int d) {
return a + b + c + d;
}
Note: the __cdecl / __stdcall / __fastcall modifiers go on the function definition. On the calling side the compiler picks the right convention from the definition, so no explicit declaration is needed.
Compiler README
C Compiler
Path: VMLPrepares/CCompiler/ Completeness: ~97% | 🟢 production ready Standard library: Lib/c/ (29 files)
Features
- ✅ Full C99 standard (ISO/IEC 9899:1999)
- ✅ Pointer arithmetic, multi-dimensional arrays, struct/union
- ✅ Preprocessor (#include, #define, #ifdef)
- ✅ Inline assembly (
__chipasm__) - ✅ POKE/PEEK memory operations
- ✅ Complete standard library (stdio, stdlib, string, math, conio, graphics)
- ✅ Interrupt / peripheral support (interrupt)
Compile modes
MCU mode (default: --mode mcu)
MCU mode is tuned for single-chip and bare-metal environments (Arduino/STM32/8051 and so on) and automatically skips features that are incompatible with an operating system.
Skipped (these constructs generate no code):
- None (C99 has no async, threads or reflection)
Kept (implemented on top of the BIOS):
- malloc/free, fopen/fread, printf %f, va_list
- POKE/PEEK memory-mapped I/O (MMIO)
- the
interruptkeyword (interrupt service routines) - basic type operations, control flow, function calls
- printf/puts mapped to the UART
- raw pointers (with restrictions)
OS mode (--mode os, reserved)
OS mode targets environments that have an operating system (embedded Linux, an RTOS, and so on). It will support the full language feature set (file system, threads, async, exceptions, reflection).
RAM levels
--ram k: kilobyte range (2KB~64KB, e.g. 8051/PIC/AVR)--ram m: megabyte range (64KB~1MB, e.g. ARM Cortex-M, default)--ram g: gigabyte range (e.g. x86/DDR systems)--stack-size <bytes>: set the stack size by hand (by default it is allocated automatically from--ram)
MCU safe-coding tips
- Stack space is limited (256-4096 bytes typically), so avoid deep recursion
- No deep recursion (>10 levels means you should evaluate the stack)
- No dynamic loading (import/dofile/eval)
- Floating point may need a software floating-point library
Usage
dotnet run --project VMLPrepares/CCompiler input.c -o output.vml
vmltool input.c -o output.vml
Tests
Test/c/ — 190+ test files