diff --git a/README.md b/README.md
index 715b4fa3..ed0d80b0 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# UVM
**NOTE: this project is very much a work in progress. You're likely to run
-into bugs and missing features. I'm looking for collaborators who share the vision
+into bugs and missing features. I would like to find collaborators who share the vision
and want to help me make it happen.**
@@ -10,10 +10,10 @@ and want to help me make it happen.**
-A minimalistic virtual machine designed to run self-contained applications. UVM is intended as a platform to distribute
-programs that will not break and to combat code rot. It also aims to be conceptually simple, easy to understand, easy
-to target, fun to work with and approachable to newcomers. It may also be valuable as a teaching tool. There is a short
-4-minute [overview of UVM](https://www.youtube.com/watch?v=q9-o45B_qsA)
+A simple, minimalistic virtual machine designed to run self-contained applications. UVM is intended as a platform to distribute
+programs that will not break and to combat code rot. It aims to be conceptually simple, easy to understand, easy
+to target, fun to work with and approachable to newcomers. It may also be valuable as a teaching tool or as a platform
+to experiment with. There is a short 4-minute [overview of UVM](https://www.youtube.com/watch?v=q9-o45B_qsA)
on YouTube if you'd like to see a quick survey.
Contents:
@@ -35,19 +35,16 @@ Current features:
- Untyped design for simplicity
- Little-endian byte ordering (like x86, ARM & RISC-V)
- 32-bit and 64-bit integer ops, 32-bit floating-point support
-- [Separate flat, linear address spaces for code and data](https://en.wikipedia.org/wiki/Harvard_architecture)
+- Separate flat, linear address spaces for code and data ([Harvard architecture](https://en.wikipedia.org/wiki/Harvard_architecture))
+- Thread-based parallelism
- Built-in, easy to use [assembler](vm/src/asm.rs) with a [simple syntax](vm/examples)
- Event-driven event execution model compatible with async operations
- Easy to use frame buffer to draw RGB graphics with no boilerplate
- Easy to use audio output API with no boilerplate
Planned future features:
-- Async file and network I/O with callbacks
- - Synchronous I/O possible as well
-- Fast JIT compiler based on dynamic binary translation and basic block versioning
- - Expected performance ~80% of native speed (maybe more?)
- - Near-instant warmup
-- Permission system to safely sandbox apps without granting access to entire computer
+- Simple networking API
+- Capability system to safely sandbox apps without granting access to entire computer
- Ability to compile without SDL and without graphics/audio for headless server-side use
- Ability to encode metadata such as author name and app icon into app image files
- Ability to suspend running programs and save them to a new app image file
@@ -102,7 +99,7 @@ cd vm
cargo build
```
-To run an asm file with UVM:
+To run an [asm file](vm/examples) with UVM:
```sh
cargo run examples/fizzbuzz.asm
```
@@ -128,6 +125,8 @@ The repository is organized into a 3 different subprojects, each of which is a R
- [`/ncc/examples/*`](ncc/examples): Example C source files that can be compiled by NCC
- `/api`: A system to document and automatically export bindings for UVM system calls and constants.
- `/api/syscalls.json`: Declarative list of system calls exposed by UVM.
+- `/doc`: Markdown documentation for UVM
+ - [`/doc/syscalls.json`](doc/syscalls.md): List of system calls and constants accessible to UVM programs
The `ncc` compiler is, at the time of this writing, incomplete in that it lacks some C features and the error messages need improvement. This compiler
was implemented to serve as an example of how to write a compiler that targets UVM, and to write some library code to be used by other programs. Over
diff --git a/api/src/main.rs b/api/src/main.rs
index e40c3f94..4a7225d8 100644
--- a/api/src/main.rs
+++ b/api/src/main.rs
@@ -179,7 +179,7 @@ fn main()
let mut file = File::create("syscalls.json").unwrap();
file.write_all(json_output.as_bytes()).unwrap();
- gen_rust_bindings("../vm/src/sys/constants.rs", &subsystems, &idx_to_name);
+ gen_rust_bindings("../vm/src/constants.rs", &subsystems, &idx_to_name);
gen_c_bindings("../ncc/include/uvm/syscalls.h", &subsystems);
gen_markdown("../doc/syscalls.md", &subsystems);
}
diff --git a/api/syscalls.json b/api/syscalls.json
index e7bab75d..7e022223 100644
--- a/api/syscalls.json
+++ b/api/syscalls.json
@@ -111,7 +111,7 @@
"description": "Report the current heap size in bytes."
},
{
- "name": "vm_resize_heap",
+ "name": "vm_grow_heap",
"args": [
[
"u64",
@@ -124,7 +124,86 @@
],
"permission": "default_allowed",
"const_idx": 17,
- "description": "Resize the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. Returns the new heap size in bytes if successful, or `UINT64_MAX` on failure."
+ "description": "Grow the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. If the requested size is smaller than the current heap size, this is a no-op. Returns the new heap size in bytes."
+ },
+ {
+ "name": "exit",
+ "args": [
+ [
+ "i8",
+ "status"
+ ]
+ ],
+ "returns": [
+ "void",
+ ""
+ ],
+ "permission": "default_allowed",
+ "const_idx": 11,
+ "description": "End program execution with the specified exit status."
+ },
+ {
+ "name": "thread_spawn",
+ "args": [
+ [
+ "void*",
+ "fptr"
+ ],
+ [
+ "void*",
+ "arg"
+ ]
+ ],
+ "returns": [
+ "u64",
+ "tid"
+ ],
+ "permission": "default_allowed",
+ "const_idx": 29,
+ "description": "Spawn a new thread running the given function with the argument value `arg`."
+ },
+ {
+ "name": "thread_id",
+ "args": [],
+ "returns": [
+ "u64",
+ "tid"
+ ],
+ "permission": "default_allowed",
+ "const_idx": 28,
+ "description": "Get the id of the current thread."
+ },
+ {
+ "name": "thread_sleep",
+ "args": [
+ [
+ "u64",
+ "time_ms"
+ ]
+ ],
+ "returns": [
+ "void",
+ ""
+ ],
+ "permission": "default_allowed",
+ "const_idx": 30,
+ "description": "Make the current thread sleep for at least the given time in milliseconds."
+ },
+ {
+ "name": "thread_join",
+ "args": [
+ [
+ "u64",
+ "tid"
+ ]
+ ],
+ "returns": [
+ "u64",
+ "val"
+ ],
+ "permission": "default_allowed",
+ "const_idx": 31,
+ "description": "Join on the thread with the given id. Produces the return value for the thread."
}
],
"constants": []
@@ -236,26 +315,6 @@
"permission": "time_get_time",
"const_idx": 0,
"description": "Get the UNIX time stamp in milliseconds."
- },
- {
- "name": "time_delay_cb",
- "args": [
- [
- "u64",
- "delay_ms"
- ],
- [
- "void*",
- "callback"
- ]
- ],
- "returns": [
- "void",
- ""
- ],
- "permission": "default_allowed",
- "const_idx": 2,
- "description": "Schedule a callback to be called once after a given delay."
}
],
"constants": []
@@ -313,95 +372,27 @@
"description": "Copy a frame of pixels to be displayed into the window. The frame must have the same width and height as the window. The pixel format is 32 bits per pixel in BGRA byte order, with 8 bits for each component and the B byte at the lowest address."
},
{
- "name": "window_on_mousemove",
+ "name": "window_poll_event",
"args": [
- [
- "u32",
- "window_id"
- ],
[
"void*",
- "callback"
+ "p_event"
]
],
"returns": [
- "void",
- ""
- ],
- "permission": "window_display",
- "const_idx": 11,
- "description": "Register a callback for mouse movement. Mouse x/y coordinates are relative to the top-left corner of the window and may be negative if outside of the window."
- },
- {
- "name": "window_on_mousedown",
- "args": [
- [
- "u32",
- "window_id"
- ],
- [
- "void*",
- "callback"
- ]
- ],
- "returns": [
- "void",
- ""
- ],
- "permission": "window_display",
- "const_idx": 12,
- "description": "Register a callback for mouse button press events."
- },
- {
- "name": "window_on_mouseup",
- "args": [
- [
- "u32",
- "window_id"
- ],
- [
- "void*",
- "callback"
- ]
- ],
- "returns": [
- "void",
- ""
- ],
- "permission": "window_display",
- "const_idx": 13,
- "description": "Register a callback for mouse button release events."
- },
- {
- "name": "window_on_keydown",
- "args": [
- [
- "u32",
- "window_id"
- ],
- [
- "void*",
- "callback"
- ]
- ],
- "returns": [
- "void",
- ""
+ "bool",
+ "event_read"
],
"permission": "window_display",
"const_idx": 9,
- "description": "Register a callback for key press event."
+ "description": "Try to read an event from the windowing system if available. The event is read into an event struct. Boolean true is returned if an event was read, false if not."
},
{
- "name": "window_on_keyup",
+ "name": "window_wait_event",
"args": [
- [
- "u32",
- "window_id"
- ],
[
"void*",
- "callback"
+ "p_event"
]
],
"returns": [
@@ -409,31 +400,46 @@
""
],
"permission": "window_display",
- "const_idx": 15,
- "description": "Register a callback for key release event."
- },
- {
- "name": "window_on_textinput",
- "args": [
- [
- "u32",
- "window_id"
- ],
- [
- "void*",
- "callback"
- ]
- ],
- "returns": [
- "void",
- ""
- ],
- "permission": "window_display",
- "const_idx": 19,
- "description": "Register a callback to receive text input. The text is encoded as UTF-8 and the callback is called for each byte input."
+ "const_idx": 2,
+ "description": "Block until an window event is available."
}
],
"constants": [
+ [
+ "EVENT_QUIT",
+ "u16",
+ 0
+ ],
+ [
+ "EVENT_KEYDOWN",
+ "u16",
+ 1
+ ],
+ [
+ "EVENT_KEYUP",
+ "u16",
+ 2
+ ],
+ [
+ "EVENT_MOUSEDOWN",
+ "u16",
+ 3
+ ],
+ [
+ "EVENT_MOUSEUP",
+ "u16",
+ 4
+ ],
+ [
+ "EVENT_MOUSEMOVE",
+ "u16",
+ 5
+ ],
+ [
+ "EVENT_TEXTINPUT",
+ "u16",
+ 6
+ ],
[
"KEY_BACKSPACE",
"u16",
@@ -731,7 +737,55 @@
],
"permission": "audio_output",
"const_idx": 18,
- "description": "Open an audio output device."
+ "description": "Open an audio output device, then spawn a new thread which will regularly call the specified callback function to generate audio samples."
+ },
+ {
+ "name": "audio_open_input",
+ "args": [
+ [
+ "u32",
+ "sample_rate"
+ ],
+ [
+ "u16",
+ "num_channels"
+ ],
+ [
+ "u16",
+ "format"
+ ],
+ [
+ "void*",
+ "callback"
+ ]
+ ],
+ "returns": [
+ "u32",
+ "device_id"
+ ],
+ "permission": "audio_input",
+ "const_idx": 12,
+ "description": "Open an audio input device, then spawn a new thread which will regularly call the specified callback function to process audio samples."
+ },
+ {
+ "name": "audio_read_samples",
+ "args": [
+ [
+ "i16*",
+ "dst_buf"
+ ],
+ [
+ "u32",
+ "num_samples"
+ ]
+ ],
+ "returns": [
+ "void",
+ ""
+ ],
+ "permission": "audio_input",
+ "const_idx": 13,
+ "description": "Read available input samples. Must be called from the audio input thread."
}
],
"constants": [
diff --git a/doc/planning.md b/doc/planning.md
index f64be765..c74c63de 100644
--- a/doc/planning.md
+++ b/doc/planning.md
@@ -40,10 +40,10 @@ UVM has been [designed](/doc/design.md) with JIT compilation in mind, that
is, we've made multiple design choices that we think will make it easier
to generate efficient machine code from our bytecode instructions. We
believe it should be possible to get good performance with a fairly
-simple JIT compiler. A speedup of 20x or more over the interpreter
-should be expected, and hopefully near-native performance.
+simple JIT compiler. A speedup of ~20x over the interpreter
+should be achievable, and hopefully near-native performance.
-We don't want to stat working on the JIT compiler very early in the
+We don't want to start working on the JIT compiler very early in the
prototype stage, because it's easier to quickly iterate over the design
while working with an interpreter, but experimentation with
JIT compilation needs to happen before we stabilize the current design.
diff --git a/doc/syscalls.md b/doc/syscalls.md
index 780cb351..cf5367da 100644
--- a/doc/syscalls.md
+++ b/doc/syscalls.md
@@ -58,15 +58,61 @@ u64 vm_heap_size()
Report the current heap size in bytes.
-## vm_resize_heap
+## vm_grow_heap
```
-u64 vm_resize_heap(u64 num_bytes)
+u64 vm_grow_heap(u64 num_bytes)
```
**Returns:** `u64 new_size`
-Resize the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. Returns the new heap size in bytes if successful, or `UINT64_MAX` on failure.
+Grow the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. If the requested size is smaller than the current heap size, this is a no-op. Returns the new heap size in bytes.
+
+## exit
+
+```
+void exit(i8 status)
+```
+
+End program execution with the specified exit status.
+
+## thread_spawn
+
+```
+u64 thread_spawn(void* fptr, void* arg)
+```
+
+**Returns:** `u64 tid`
+
+Spawn a new thread running the given function with the argument value `arg`.
+
+## thread_id
+
+```
+u64 thread_id()
+```
+
+**Returns:** `u64 tid`
+
+Get the id of the current thread.
+
+## thread_sleep
+
+```
+void thread_sleep(u64 time_ms)
+```
+
+Make the current thread sleep for at least the given time in milliseconds.
+
+## thread_join
+
+```
+u64 thread_join(u64 tid)
+```
+
+**Returns:** `u64 val`
+
+Join on the thread with the given id. Produces the return value for the thread.
# io
@@ -138,14 +184,6 @@ u64 time_current_ms()
Get the UNIX time stamp in milliseconds.
-## time_delay_cb
-
-```
-void time_delay_cb(u64 delay_ms, void* callback)
-```
-
-Schedule a callback to be called once after a given delay.
-
# window
Functionality related to creating windows, drawing graphics, as well as mouse and keyboard input.
@@ -168,57 +206,34 @@ void window_draw_frame(u32 window_id, const u8* pixel_data)
Copy a frame of pixels to be displayed into the window. The frame must have the same width and height as the window. The pixel format is 32 bits per pixel in BGRA byte order, with 8 bits for each component and the B byte at the lowest address.
-## window_on_mousemove
-
-```
-void window_on_mousemove(u32 window_id, void* callback)
-```
-
-Register a callback for mouse movement. Mouse x/y coordinates are relative to the top-left corner of the window and may be negative if outside of the window.
-
-## window_on_mousedown
-
-```
-void window_on_mousedown(u32 window_id, void* callback)
-```
-
-Register a callback for mouse button press events.
-
-## window_on_mouseup
-
-```
-void window_on_mouseup(u32 window_id, void* callback)
-```
-
-Register a callback for mouse button release events.
-
-## window_on_keydown
+## window_poll_event
```
-void window_on_keydown(u32 window_id, void* callback)
+bool window_poll_event(void* p_event)
```
-Register a callback for key press event.
+**Returns:** `bool event_read`
-## window_on_keyup
+Try to read an event from the windowing system if available. The event is read into an event struct. Boolean true is returned if an event was read, false if not.
-```
-void window_on_keyup(u32 window_id, void* callback)
-```
-
-Register a callback for key release event.
-
-## window_on_textinput
+## window_wait_event
```
-void window_on_textinput(u32 window_id, void* callback)
+void window_wait_event(void* p_event)
```
-Register a callback to receive text input. The text is encoded as UTF-8 and the callback is called for each byte input.
+Block until an window event is available.
## Constants
These are the constants associated with the window subsystem:
+- `u16 EVENT_QUIT = 0`
+- `u16 EVENT_KEYDOWN = 1`
+- `u16 EVENT_KEYUP = 2`
+- `u16 EVENT_MOUSEDOWN = 3`
+- `u16 EVENT_MOUSEUP = 4`
+- `u16 EVENT_MOUSEMOVE = 5`
+- `u16 EVENT_TEXTINPUT = 6`
- `u16 KEY_BACKSPACE = 8`
- `u16 KEY_TAB = 9`
- `u16 KEY_RETURN = 10`
@@ -285,7 +300,25 @@ u32 audio_open_output(u32 sample_rate, u16 num_channels, u16 format, void* callb
**Returns:** `u32 device_id`
-Open an audio output device.
+Open an audio output device, then spawn a new thread which will regularly call the specified callback function to generate audio samples.
+
+## audio_open_input
+
+```
+u32 audio_open_input(u32 sample_rate, u16 num_channels, u16 format, void* callback)
+```
+
+**Returns:** `u32 device_id`
+
+Open an audio input device, then spawn a new thread which will regularly call the specified callback function to process audio samples.
+
+## audio_read_samples
+
+```
+void audio_read_samples(i16* dst_buf, u32 num_samples)
+```
+
+Read available input samples. Must be called from the audio input thread.
## Constants
These are the constants associated with the audio subsystem:
diff --git a/ncc/examples/3dcube.c b/ncc/examples/3dcube.c
index 621d6b32..e610547e 100644
--- a/ncc/examples/3dcube.c
+++ b/ncc/examples/3dcube.c
@@ -1,7 +1,7 @@
#include
#include
#include
-#include
+#include
#include
#include
#include
@@ -73,7 +73,7 @@ void trans_line3d(mat44 trans, vec3 _v0, vec3 _v1)
draw_line3d(v0, v1, COLOR_PURPLE);
}
-void anim_callback()
+void update()
{
u64 start_time = time_current_ms();
@@ -112,25 +112,11 @@ void anim_callback()
u64 end_time = time_current_ms();
printf("render time: %dms\n", end_time - start_time);
-
- // Schedule a fixed rate update for the next frame (60fps)
- fixed_rate_update(start_time, 1000 / 60, anim_callback);
-}
-
-void keydown(u64 window_id, u16 keycode)
-{
- if (keycode == KEY_ESCAPE)
- {
- exit(0);
- }
}
int main()
{
window_create(FRAME_WIDTH, FRAME_HEIGHT, "Rotating 3D Cube Example", 0);
- window_on_keydown(0, keydown);
- time_delay_cb(0, anim_callback);
- enable_event_loop();
// Setup the perspective projection matrix
perspective(
@@ -144,5 +130,7 @@ int main()
// Translation matrix for the cube
mat44_translate(cube_pos, trans);
+ anim_event_loop(60, update);
+
return 0;
}
diff --git a/ncc/examples/attackers.c b/ncc/examples/attackers.c
index c2089809..1201641e 100644
--- a/ncc/examples/attackers.c
+++ b/ncc/examples/attackers.c
@@ -1,5 +1,5 @@
#include
-#include
+#include
#include
#include
#include
@@ -182,10 +182,8 @@ void fire_bolt()
bolt_y = ship_y - 14;
}
-void anim_callback()
+void update_anim()
{
- u64 start_time = time_current_ms();
-
if (left_down && !right_down)
{
if (ship_x > 20)
@@ -273,13 +271,10 @@ void anim_callback()
}
window_draw_frame(0, frame_buffer);
-
- // Schedule a fixed rate update for the next frame (40fps)
- fixed_rate_update(start_time, 1000 / 40, anim_callback);
}
// Enemy movement update
-void enemy_callback()
+void update_enemies()
{
if (enemies_live == 0)
{
@@ -300,58 +295,70 @@ void enemy_callback()
enemy_j = enemy_j + 1;
}
- // Schedule the next update
- int delay = 500 - 50 * enemy_j;
- if (delay <= 0) delay = 50;
- time_delay_cb(delay, enemy_callback);
-
// Update the step count
enemy_steps = enemy_steps + 1;
}
-void keydown(u64 window_id, u16 keycode)
-{
- if (keycode == KEY_ESCAPE)
- {
- exit(0);
- }
-
- if (keycode == KEY_LEFT)
- {
- left_down = true;
- }
- else if (keycode == KEY_RIGHT)
- {
- right_down = true;
- }
- else if (keycode == KEY_SPACE)
- {
- fire_bolt();
- }
-}
-
-void keyup(u64 window_id, u16 keycode)
-{
- if (keycode == KEY_LEFT)
- {
- left_down = false;
- }
- else if (keycode == KEY_RIGHT)
- {
- right_down = false;
- }
-}
+Event event;
void main()
{
init();
window_create(FRAME_WIDTH, FRAME_HEIGHT, "Galactic Attackers", 0);
- window_on_keydown(0, keydown);
- window_on_keyup(0, keyup);
- time_delay_cb(0, anim_callback);
- time_delay_cb(1500, enemy_callback);
+ for (u64 frame_idx = 0;; frame_idx = frame_idx + 1)
+ {
+ while (window_poll_event(&event))
+ {
+ if (event.kind == EVENT_QUIT)
+ {
+ exit(0);
+ }
+
+ if (event.kind == EVENT_KEYDOWN)
+ {
+ if (event.key == KEY_ESCAPE)
+ {
+ exit(0);
+ }
+ else if (event.key == KEY_LEFT)
+ {
+ left_down = true;
+ }
+ else if (event.key == KEY_RIGHT)
+ {
+ right_down = true;
+ }
+ else if (event.key == KEY_SPACE)
+ {
+ fire_bolt();
+ }
+ }
- enable_event_loop();
+ if (event.kind == EVENT_KEYUP)
+ {
+ if (event.key == KEY_LEFT)
+ {
+ left_down = false;
+ }
+ else if (event.key == KEY_RIGHT)
+ {
+ right_down = false;
+ }
+ }
+ }
+
+ update_anim();
+
+ // Enemy update rate
+ int enemy_rate = 18 - 2 * enemy_j;
+ if (enemy_rate <= 2) enemy_rate = 2;
+
+ if (frame_idx > 40 && frame_idx % enemy_rate == 0)
+ update_enemies();
+
+ // 60fps max
+ thread_sleep(1000 / 60);
+ }
}
diff --git a/ncc/examples/audio_graph.c b/ncc/examples/audio_graph.c
new file mode 100644
index 00000000..adb6fcb8
--- /dev/null
+++ b/ncc/examples/audio_graph.c
@@ -0,0 +1,107 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define FRAME_WIDTH 600
+#define FRAME_HEIGHT 200
+#define SAMPLE_RATE 44100
+#define DISP_SAMPLES 176400 // SAMPLE_RATE * 4
+
+// RGBA pixels
+u32 frame_buffer[FRAME_HEIGHT][FRAME_WIDTH];
+
+// Buffer for incoming samples
+i16 buffer[1024];
+
+// Buffer for display
+i16 disp_samples[DISP_SAMPLES];
+
+// Current recording position
+size_t rec_pos = 0;
+
+void update()
+{
+ // Clear the frame buffer, set all pixels to black
+ memset32(frame_buffer, 0, sizeof(frame_buffer) / sizeof(u32));
+
+ int prev_y = FRAME_HEIGHT / 2;
+
+ for (size_t x = 1; x < FRAME_WIDTH; ++x)
+ {
+ size_t sample_idx = x * DISP_SAMPLES / FRAME_WIDTH;
+
+ // Bring sample into the [-1, 1] range
+ float sample = (float)disp_samples[sample_idx] / (INT16_MAX + 1);
+
+ // Bring the sample into the [0, 1] range
+ sample = (sample + 1.0f) / 2;
+
+ int y = (int)(sample * (FRAME_HEIGHT - 1));
+
+ draw_line(
+ (u32*)&frame_buffer,
+ FRAME_WIDTH,
+ FRAME_HEIGHT,
+ x - 1,
+ prev_y,
+ x,
+ y,
+ COLOR_RED,
+ );
+
+ prev_y = y;
+ }
+
+ // Draw vertical line at recording position
+ u32 rec_x = rec_pos * FRAME_WIDTH / DISP_SAMPLES;
+ draw_line(
+ (u32*)&frame_buffer,
+ FRAME_WIDTH,
+ FRAME_HEIGHT,
+ rec_x,
+ 0,
+ rec_x,
+ FRAME_HEIGHT - 1,
+ COLOR_WHITE,
+ );
+
+ window_draw_frame(0, frame_buffer);
+}
+
+void audio_cb(u16 num_channels, u32 num_samples)
+{
+ assert(num_channels == 1);
+ assert(num_samples <= 1024);
+
+ audio_read_samples(&buffer, num_samples);
+
+ size_t end_pos = MIN(rec_pos + num_samples, sizeof(disp_samples) / sizeof(i16));
+ size_t num_copy = end_pos - rec_pos;
+
+ memcpy(&disp_samples[rec_pos], &buffer, num_copy * sizeof(i16));
+
+ rec_pos = (rec_pos + num_copy) % DISP_SAMPLES;
+
+ if (num_copy < num_samples)
+ {
+ size_t buf_pos = num_copy;
+ size_t num_copy = num_samples - buf_pos;
+
+ memcpy(&disp_samples, &buffer[buf_pos], num_copy * sizeof(i16));
+ rec_pos = num_copy;
+ }
+
+ //printf("%d\n", rec_pos);
+}
+
+void main()
+{
+ window_create(FRAME_WIDTH, FRAME_HEIGHT, "Audio Input Graph", 0);
+ audio_open_input(SAMPLE_RATE, 1, AUDIO_FORMAT_I16, audio_cb);
+
+ anim_event_loop(30, update);
+}
diff --git a/ncc/examples/ball.c b/ncc/examples/ball.c
index 415a1740..e769cb12 100644
--- a/ncc/examples/ball.c
+++ b/ncc/examples/ball.c
@@ -1,5 +1,5 @@
#include
-#include
+#include
#include
#include
@@ -8,8 +8,8 @@
#define BALL_RADIUS 20
#define AUDIO_LEN 8_000
-// RGBA pixels: 800 * 600
-u32 frame_buffer[600][800];
+// RGBA pixels
+u32 frame_buffer[FRAME_HEIGHT][FRAME_WIDTH];
// Current ball position
int px = 200;
@@ -54,12 +54,10 @@ void draw_ball()
}
}
-void anim_callback()
+void update()
{
- u64 start_time = time_current_ms();
-
// Clear the frame buffer, set all pixels to black
- memset32(frame_buffer, 0, 800 * 600);
+ memset32(frame_buffer, 0, sizeof(frame_buffer) / sizeof(u32));
draw_ball();
@@ -89,9 +87,6 @@ void anim_callback()
}
window_draw_frame(0, frame_buffer);
-
- // Schedule a fixed rate update for the next frame (60fps)
- fixed_rate_update(start_time, 1000 / 60, anim_callback);
}
u16* audio_cb(u16 num_channels, u32 num_samples)
@@ -113,7 +108,9 @@ u16* audio_cb(u16 num_channels, u32 num_samples)
// The intensity decreases over time
u32 intensity = INT16_MAX - (audio_pos * INT16_MAX / AUDIO_LEN);
- u32 sawtooth = 4000 * (i % 128) / 128;
+ u32 period = 128 - (audio_pos * 64 / AUDIO_LEN);
+
+ u32 sawtooth = 4000 * (i % period) / period;
AUDIO_BUFFER[i] = intensity * sawtooth / INT16_MAX;
++audio_pos;
@@ -122,22 +119,11 @@ u16* audio_cb(u16 num_channels, u32 num_samples)
return AUDIO_BUFFER;
}
-void keydown(u64 window_id, u16 keycode)
-{
- if (keycode == KEY_ESCAPE)
- {
- exit(0);
- }
-}
-
void main()
{
window_create(FRAME_WIDTH, FRAME_HEIGHT, "Bouncing Ball Example", 0);
- window_on_keydown(0, keydown);
-
- time_delay_cb(0, anim_callback);
audio_open_output(44100, 1, AUDIO_FORMAT_I16, audio_cb);
- enable_event_loop();
+ anim_event_loop(60, update);
}
diff --git a/ncc/examples/basic.c b/ncc/examples/basic.c
deleted file mode 100644
index 7b7ebb5f..00000000
--- a/ncc/examples/basic.c
+++ /dev/null
@@ -1,2166 +0,0 @@
-//
-// UVM Basic
-// by Abdul Bahajaj
-//
-// Note to reader: When I authored this VM there were a lot of missing features from the NCC compiler ( e.g. there are no structs, unions, etc...).
-// Keeping this in mind might help you understand why the code is structured in the way it is.
-
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-// VM status
-#define VM_STATUS_RUNNING 0
-#define VM_STATUS_DONE 1
-#define VM_STATUS_HALT 2
-#define FONT_MONOGRAM_NUMBER_OF_CHARACTERS 390
-#define FONT_MONOGRAM_HEIGHT 12
-#define FONT_MONOGRAM_WIDTH 7
-
-#define FRAME_WIDTH 1200
-#define FRAME_HEIGHT 700
-#define NUM_COLS 25
-#define NUM_ROWS 21
-
-/* #define DEBUG */
-#ifdef DEBUG
-#define DEBUGS(s) puts(__FILE__ " : "); print_i64(__LINE__); puts(" "); puts(s);
-#define DEBUGI(i) print_i64(i);
-#else
-#define DEBUGS(s)
-#define DEBUGI(i)
-#endif
-#define DEBUG(s) DEBUGS(s "\n");
-
-#define TRY(exp) if(exp) return 1;
-#define NULLGAURD(ptr) if(ptr == NULL) return NULL;
-
-size_t console_width;
-size_t margin = 5;
-size_t char_width = 18;
-size_t char_height = 32;
-u32 frame_buffer[FRAME_HEIGHT][FRAME_WIDTH];
-char text[NUM_ROWS][NUM_COLS];
-
-// Position of the cursor
-#define MIN_COL_FIRST_LINE 2
-size_t line_idx = 0;
-size_t min_line_idx = 4;
-size_t col_idx = 0;
-size_t min_col_idx;
-
-size_t row_len(size_t row_idx)
-{
- for (int i = 0; i < NUM_COLS; ++i)
- {
- if (text[row_idx][i] == 0)
- {
- return i;
- }
- }
- return NUM_COLS;
-}
-
-u32* get_point_ptr(u32* dst, size_t frame_width, size_t x, size_t y)
-{
- return dst + frame_width* y + x;
-}
-
-int white = 0xFFFFFF;
-int blue = 0x0247fe;
-int red = 0xFF0000;
-int green = 0x008000;
-int black = 0x0;
-
-void textinput(u64 window_id, char ch)
-{
- if(vm_status != VM_STATUS_DONE) return;
- console_putchar(ch);
- vm_command_text_buffer[vm_command_text_buffer_cursor] = ch;
- DEBUGS(vm_command_text_buffer);
- DEBUGS("\n");
- ++vm_command_text_buffer_cursor;
- console_redraw_commit();
-}
-
-void keydown(u64 window_id, u16 keycode)
-{
-
- if(vm_status != VM_STATUS_DONE)
- {
- if(keycode == KEY_ESCAPE) vm_status = VM_STATUS_HALT;
- return;
- }
- if (keycode == KEY_ESCAPE) exit(0);
- else if (keycode == KEY_BACKSPACE)
- {
- if (col_idx > min_col_idx)
- {
- col_idx = col_idx - 1;
- vm_command_text_buffer_backspace();
- }
- else if (line_idx > min_line_idx)
- {
- console_redraw_line(line_idx);
- line_idx = line_idx - 1;
- if(line_idx == min_line_idx) min_col_idx = MIN_COL_FIRST_LINE;
- col_idx = row_len(line_idx);
- }
- text[line_idx][col_idx] = 0;
- }
- else if (keycode == KEY_RETURN)
- {
- vm_load_cmd();
- vm_exec();
- }
- console_redraw_commit();
-}
-
-void anim_callback()
-{
- console_redraw_commit();
- /* benchmark(); */
-
- time_delay_cb(400, anim_callback);
-}
-
-void console_print_ready()
-{
- console_newline();
- console_puts("READY.");
- console_newline();
- console_puts("> ");
- min_col_idx = MIN_COL_FIRST_LINE;
-}
-
-void main()
-{
- DEBUGS("DEBUGGING\n");
- console_width = FRAME_WIDTH/3+75;
- vm_init();
- vm_command_text_buffer_clear();
- window_create(FRAME_WIDTH, FRAME_HEIGHT, "UVM Basic", 0);
-
- canvas_fill(white);
- console_redraw_all_text();
-
- console_puts(" **** UVM Basic ****");
- console_newline();
- console_puts("Type HELP for available ");
- console_newline();
- console_puts("commands");
- console_print_ready();
-
- console_redraw_commit();
-
- window_on_keydown(0, keydown);
- window_on_textinput(0, textinput);
-
- time_delay_cb(0, anim_callback);
-
- enable_event_loop();
-}
-
-//===========================================================================
-/* CONSOLE */
-
-// This is 12 bytes of data per char, as in 12 rows, and each byte
-// represents the pixels in that row.
-u8 font_monogram_data[FONT_MONOGRAM_NUMBER_OF_CHARACTERS][FONT_MONOGRAM_HEIGHT] = {
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x19, 0x15, 0x13, 0x11, 0x0e, 0x00, 0x00, }, // '0'
- { 0x00, 0x00, 0x00, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // '1'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x10, 0x08, 0x04, 0x02, 0x1f, 0x00, 0x00, }, // '2'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x10, 0x0c, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // '3'
- { 0x00, 0x00, 0x00, 0x12, 0x12, 0x11, 0x1f, 0x10, 0x10, 0x10, 0x00, 0x00, }, // '4'
- { 0x00, 0x00, 0x00, 0x1f, 0x01, 0x0f, 0x10, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // '5'
- { 0x00, 0x00, 0x00, 0x0e, 0x01, 0x01, 0x0f, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // '6'
- { 0x00, 0x00, 0x00, 0x1f, 0x10, 0x10, 0x08, 0x04, 0x04, 0x04, 0x00, 0x00, }, // '7'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x0e, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // '8'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x1e, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // '9'
- { 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04, 0x00, 0x00, }, // '!'
- { 0x00, 0x00, 0x00, 0x0a, 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '"'
- { 0x00, 0x00, 0x00, 0x00, 0x0a, 0x1f, 0x0a, 0x0a, 0x1f, 0x0a, 0x00, 0x00, }, // '#'
- { 0x00, 0x00, 0x00, 0x04, 0x1e, 0x05, 0x0e, 0x14, 0x0f, 0x04, 0x00, 0x00, }, // '$'
- { 0x00, 0x00, 0x00, 0x11, 0x11, 0x08, 0x04, 0x02, 0x11, 0x11, 0x00, 0x00, }, // '%'
- { 0x00, 0x00, 0x00, 0x06, 0x09, 0x09, 0x1e, 0x09, 0x09, 0x16, 0x00, 0x00, }, // '&'
- { 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // "'"
- { 0x00, 0x00, 0x00, 0x08, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x00, 0x00, }, // '('
- { 0x00, 0x00, 0x00, 0x02, 0x04, 0x04, 0x04, 0x04, 0x04, 0x02, 0x00, 0x00, }, // ')'
- { 0x00, 0x00, 0x00, 0x00, 0x04, 0x15, 0x0e, 0x15, 0x04, 0x00, 0x00, 0x00, }, // '*'
- { 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x1f, 0x04, 0x04, 0x00, 0x00, 0x00, }, // '+'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x02, 0x00, }, // ','
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '-'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, }, // '.'
- { 0x00, 0x00, 0x00, 0x10, 0x10, 0x08, 0x04, 0x02, 0x01, 0x01, 0x00, 0x00, }, // '/'
- { 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, }, // ':'
- { 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 0x04, 0x04, 0x02, 0x00, }, // ';'
- { 0x00, 0x00, 0x00, 0x00, 0x18, 0x06, 0x01, 0x06, 0x18, 0x00, 0x00, 0x00, }, // '<'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, }, // '='
- { 0x00, 0x00, 0x00, 0x00, 0x03, 0x0c, 0x10, 0x0c, 0x03, 0x00, 0x00, 0x00, }, // '>'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x10, 0x08, 0x04, 0x00, 0x04, 0x00, 0x00, }, // '?'
- { 0x00, 0x00, 0x00, 0x0e, 0x19, 0x15, 0x15, 0x19, 0x01, 0x0e, 0x00, 0x00, }, // '@'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x00, 0x00, }, // 'A'
- { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x0f, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'B'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'C'
- { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'D'
- { 0x00, 0x00, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'E'
- { 0x00, 0x00, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x01, 0x01, 0x01, 0x00, 0x00, }, // 'F'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x1d, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'G'
- { 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'H'
- { 0x00, 0x00, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'I'
- { 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'J'
- { 0x00, 0x00, 0x00, 0x11, 0x09, 0x05, 0x03, 0x05, 0x09, 0x11, 0x00, 0x00, }, // 'K'
- { 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'L'
- { 0x00, 0x00, 0x00, 0x11, 0x1b, 0x15, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'M'
- { 0x00, 0x00, 0x00, 0x11, 0x11, 0x13, 0x15, 0x19, 0x11, 0x11, 0x00, 0x00, }, // 'N'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'O'
- { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x0f, 0x01, 0x01, 0x01, 0x00, 0x00, }, // 'P'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x18, 0x00, }, // 'Q'
- { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x0f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'R'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x0e, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // 'S'
- { 0x00, 0x00, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'T'
- { 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'U'
- { 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0a, 0x0a, 0x04, 0x00, 0x00, }, // 'V'
- { 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x15, 0x1b, 0x11, 0x00, 0x00, }, // 'W'
- { 0x00, 0x00, 0x00, 0x11, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x11, 0x00, 0x00, }, // 'X'
- { 0x00, 0x00, 0x00, 0x11, 0x11, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'Y'
- { 0x00, 0x00, 0x00, 0x1f, 0x10, 0x08, 0x04, 0x02, 0x01, 0x1f, 0x00, 0x00, }, // 'Z'
- { 0x00, 0x00, 0x00, 0x0c, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0c, 0x00, 0x00, }, // '['
- { 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x04, 0x08, 0x10, 0x10, 0x00, 0x00, }, // '\\'
- { 0x00, 0x00, 0x00, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x06, 0x00, 0x00, }, // ']'
- { 0x00, 0x00, 0x00, 0x04, 0x0a, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '^'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, }, // '_'
- { 0x00, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '`'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'a'
- { 0x00, 0x00, 0x00, 0x01, 0x01, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'b'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'c'
- { 0x00, 0x00, 0x00, 0x10, 0x10, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'd'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'e'
- { 0x00, 0x00, 0x00, 0x0c, 0x12, 0x02, 0x0f, 0x02, 0x02, 0x02, 0x00, 0x00, }, // 'f'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'g'
- { 0x00, 0x00, 0x00, 0x01, 0x01, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'h'
- { 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'i'
- { 0x00, 0x00, 0x00, 0x10, 0x00, 0x18, 0x10, 0x10, 0x10, 0x10, 0x11, 0x0e, }, // 'j'
- { 0x00, 0x00, 0x00, 0x01, 0x01, 0x11, 0x09, 0x07, 0x09, 0x11, 0x00, 0x00, }, // 'k'
- { 0x00, 0x00, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x1c, 0x00, 0x00, }, // 'l'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x15, 0x15, 0x15, 0x15, 0x00, 0x00, }, // 'm'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'n'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'o'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x01, }, // 'p'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x10, }, // 'q'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x13, 0x01, 0x01, 0x01, 0x00, 0x00, }, // 'r'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x01, 0x0e, 0x10, 0x0f, 0x00, 0x00, }, // 's'
- { 0x00, 0x00, 0x00, 0x02, 0x02, 0x0f, 0x02, 0x02, 0x02, 0x1c, 0x00, 0x00, }, // 't'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'u'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x0a, 0x04, 0x00, 0x00, }, // 'v'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0a, 0x00, 0x00, }, // 'w'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x00, 0x00, }, // 'x'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'y'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x08, 0x04, 0x02, 0x1f, 0x00, 0x00, }, // 'z'
- { 0x00, 0x00, 0x00, 0x08, 0x04, 0x04, 0x02, 0x04, 0x04, 0x08, 0x00, 0x00, }, // '{'
- { 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // '|'
- { 0x00, 0x00, 0x00, 0x02, 0x04, 0x04, 0x08, 0x04, 0x04, 0x02, 0x00, 0x00, }, // '}'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '~'
- { 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // '¡'
- { 0x00, 0x00, 0x00, 0x04, 0x0e, 0x15, 0x05, 0x15, 0x0e, 0x04, 0x00, 0x00, }, // '¢'
- { 0x00, 0x00, 0x00, 0x0c, 0x12, 0x02, 0x0f, 0x02, 0x02, 0x1f, 0x00, 0x00, }, // '£'
- { 0x00, 0x00, 0x00, 0x00, 0x11, 0x0e, 0x0a, 0x0e, 0x11, 0x00, 0x00, 0x00, }, // '¤'
- { 0x00, 0x00, 0x00, 0x11, 0x0a, 0x04, 0x1f, 0x04, 0x1f, 0x04, 0x00, 0x00, }, // '¥'
- { 0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x00, 0x04, 0x04, 0x04, 0x00, 0x00, }, // '¦'
- { 0x00, 0x00, 0x00, 0x1e, 0x01, 0x0e, 0x11, 0x0e, 0x10, 0x0f, 0x00, 0x00, }, // '§'
- { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '¨'
- { 0x00, 0x00, 0x00, 0x0e, 0x1b, 0x15, 0x1d, 0x15, 0x1b, 0x0e, 0x00, 0x00, }, // '©'
- { 0x00, 0x00, 0x00, 0x0e, 0x09, 0x09, 0x09, 0x0e, 0x00, 0x00, 0x00, 0x00, }, // 'ª'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x09, 0x12, 0x00, 0x00, 0x00, 0x00, }, // '«'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x10, 0x00, 0x00, 0x00, 0x00, }, // '¬'
- { 0x00, 0x00, 0x00, 0x0e, 0x19, 0x15, 0x15, 0x19, 0x15, 0x0e, 0x00, 0x00, }, // '®'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '¯'
- { 0x00, 0x00, 0x00, 0x06, 0x09, 0x09, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '°'
- { 0x00, 0x00, 0x00, 0x04, 0x04, 0x1f, 0x04, 0x04, 0x00, 0x1f, 0x00, 0x00, }, // '±'
- { 0x00, 0x00, 0x00, 0x03, 0x04, 0x02, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00, }, // '²'
- { 0x00, 0x00, 0x00, 0x03, 0x04, 0x02, 0x04, 0x03, 0x00, 0x00, 0x00, 0x00, }, // '³'
- { 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '´'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x01, }, // 'µ'
- { 0x00, 0x00, 0x00, 0x1e, 0x17, 0x17, 0x17, 0x16, 0x14, 0x14, 0x00, 0x00, }, // '¶'
- { 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '·'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x06, }, // '¸'
- { 0x00, 0x00, 0x00, 0x02, 0x03, 0x02, 0x02, 0x07, 0x00, 0x00, 0x00, 0x00, }, // '¹'
- { 0x00, 0x00, 0x00, 0x06, 0x09, 0x09, 0x09, 0x06, 0x00, 0x00, 0x00, 0x00, }, // 'º'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x12, 0x09, 0x00, 0x00, 0x00, 0x00, }, // '»'
- { 0x00, 0x00, 0x00, 0x01, 0x09, 0x05, 0x02, 0x15, 0x1c, 0x10, 0x00, 0x00, }, // '¼'
- { 0x00, 0x00, 0x00, 0x01, 0x09, 0x05, 0x0e, 0x11, 0x08, 0x1c, 0x00, 0x00, }, // '½'
- { 0x00, 0x00, 0x00, 0x07, 0x16, 0x0f, 0x04, 0x16, 0x1d, 0x10, 0x00, 0x00, }, // '¾'
- { 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x02, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // '¿'
- { 0x02, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'À'
- { 0x08, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Á'
- { 0x04, 0x0a, 0x00, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Â'
- { 0x16, 0x09, 0x00, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Ã'
- { 0x00, 0x0a, 0x00, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Ä'
- { 0x04, 0x0a, 0x04, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Å'
- { 0x00, 0x00, 0x00, 0x1e, 0x05, 0x05, 0x1f, 0x05, 0x05, 0x1d, 0x00, 0x00, }, // 'Æ'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x08, 0x06, }, // 'Ç'
- { 0x02, 0x04, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'È'
- { 0x08, 0x04, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'É'
- { 0x04, 0x0a, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ê'
- { 0x00, 0x0a, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ë'
- { 0x02, 0x04, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'Ì'
- { 0x08, 0x04, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'Í'
- { 0x04, 0x0a, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'Î'
- { 0x00, 0x0a, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'Ï'
- { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x13, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'Ð'
- { 0x16, 0x09, 0x00, 0x11, 0x11, 0x13, 0x15, 0x19, 0x11, 0x11, 0x00, 0x00, }, // 'Ñ'
- { 0x02, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ò'
- { 0x08, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ó'
- { 0x04, 0x0a, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ô'
- { 0x16, 0x09, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Õ'
- { 0x00, 0x0a, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ö'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x04, 0x0a, 0x00, 0x00, 0x00, }, // '×'
- { 0x00, 0x00, 0x00, 0x16, 0x09, 0x19, 0x15, 0x13, 0x12, 0x0d, 0x00, 0x00, }, // 'Ø'
- { 0x02, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ù'
- { 0x08, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ú'
- { 0x04, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Û'
- { 0x00, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ü'
- { 0x08, 0x04, 0x00, 0x11, 0x11, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'Ý'
- { 0x00, 0x00, 0x00, 0x01, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x00, 0x00, }, // 'Þ'
- { 0x00, 0x00, 0x00, 0x06, 0x09, 0x09, 0x0d, 0x11, 0x11, 0x0d, 0x00, 0x00, }, // 'ß'
- { 0x00, 0x00, 0x02, 0x04, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'à'
- { 0x00, 0x00, 0x08, 0x04, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'á'
- { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'â'
- { 0x00, 0x00, 0x16, 0x09, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ã'
- { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ä'
- { 0x00, 0x04, 0x0a, 0x04, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'å'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x15, 0x1d, 0x05, 0x1e, 0x00, 0x00, }, // 'æ'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x11, 0x0e, 0x08, 0x06, }, // 'ç'
- { 0x00, 0x00, 0x02, 0x04, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'è'
- { 0x00, 0x00, 0x08, 0x04, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'é'
- { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'ê'
- { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'ë'
- { 0x00, 0x00, 0x02, 0x04, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'ì'
- { 0x00, 0x00, 0x08, 0x04, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'í'
- { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'î'
- { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'ï'
- { 0x00, 0x00, 0x0e, 0x30, 0x18, 0x1e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'ð'
- { 0x00, 0x00, 0x16, 0x09, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'ñ'
- { 0x00, 0x00, 0x02, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'ò'
- { 0x00, 0x00, 0x08, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'ó'
- { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'ô'
- { 0x00, 0x00, 0x16, 0x09, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'õ'
- { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'ö'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x1f, 0x00, 0x04, 0x00, 0x00, }, // '÷'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x09, 0x15, 0x12, 0x0d, 0x00, 0x00, }, // 'ø'
- { 0x00, 0x00, 0x02, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ù'
- { 0x00, 0x00, 0x08, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ú'
- { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'û'
- { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ü'
- { 0x00, 0x00, 0x08, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'ý'
- { 0x00, 0x00, 0x00, 0x01, 0x01, 0x0f, 0x11, 0x11, 0x11, 0x0f, 0x01, 0x01, }, // 'þ'
- { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'ÿ'
- { 0x00, 0x0e, 0x00, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Ā'
- { 0x00, 0x00, 0x00, 0x0e, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ā'
- { 0x0a, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Ă'
- { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ă'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x08, 0x10, }, // 'Ą'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x04, 0x18, }, // 'ą'
- { 0x08, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'Ć'
- { 0x00, 0x00, 0x08, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'ć'
- { 0x04, 0x0a, 0x00, 0x0e, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'Ĉ'
- { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x0e, 0x11, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'ĉ'
- { 0x00, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'Ċ'
- { 0x00, 0x00, 0x00, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'ċ'
- { 0x0a, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'Č'
- { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'č'
- { 0x0a, 0x04, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'Ď'
- { 0x00, 0x00, 0x50, 0x50, 0x10, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ď'
- { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x13, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'Đ'
- { 0x00, 0x00, 0x10, 0x3c, 0x10, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'đ'
- { 0x00, 0x0e, 0x00, 0x1f, 0x01, 0x01, 0x07, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ē'
- { 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'ē'
- { 0x0a, 0x04, 0x00, 0x1f, 0x01, 0x01, 0x07, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ĕ'
- { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'ĕ'
- { 0x00, 0x04, 0x00, 0x1f, 0x01, 0x01, 0x07, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ė'
- { 0x00, 0x00, 0x00, 0x04, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'ė'
- { 0x00, 0x00, 0x00, 0x1f, 0x01, 0x01, 0x07, 0x01, 0x01, 0x1f, 0x04, 0x18, }, // 'Ę'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x1e, 0x04, 0x18, }, // 'ę'
- { 0x00, 0x0e, 0x00, 0x1f, 0x01, 0x01, 0x07, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ě'
- { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'ě'
- { 0x04, 0x0a, 0x00, 0x0e, 0x11, 0x01, 0x1d, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ĝ'
- { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'ĝ'
- { 0x0a, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x1d, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ğ'
- { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'ğ'
- { 0x00, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x1d, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ġ'
- { 0x00, 0x00, 0x00, 0x04, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'ġ'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x1d, 0x11, 0x11, 0x0e, 0x08, 0x06, }, // 'Ģ'
- { 0x00, 0x00, 0x08, 0x04, 0x00, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'ģ'
- { 0x04, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Ĥ'
- { 0x00, 0x00, 0x08, 0x15, 0x01, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'ĥ'
- { 0x00, 0x00, 0x00, 0x11, 0x3f, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Ħ'
- { 0x00, 0x00, 0x00, 0x01, 0x03, 0x01, 0x0f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'ħ'
- { 0x16, 0x09, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'Ĩ'
- { 0x00, 0x00, 0x16, 0x09, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'ĩ'
- { 0x00, 0x0e, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'Ī'
- { 0x00, 0x00, 0x00, 0x0e, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'ī'
- { 0x0a, 0x04, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'Ĭ'
- { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'ĭ'
- { 0x00, 0x00, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x04, 0x18, }, // 'Į'
- { 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x04, 0x18, }, // 'į'
- { 0x16, 0x09, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'İ'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'ı'
- { 0x00, 0x00, 0x00, 0x17, 0x12, 0x12, 0x12, 0x12, 0x12, 0x0f, 0x00, 0x00, }, // 'IJ'
- { 0x00, 0x00, 0x00, 0x12, 0x00, 0x1b, 0x12, 0x12, 0x12, 0x1f, 0x10, 0x0e, }, // 'ij'
- { 0x04, 0x0a, 0x00, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ĵ'
- { 0x00, 0x00, 0x10, 0x28, 0x00, 0x18, 0x10, 0x10, 0x10, 0x10, 0x11, 0x0e, }, // 'ĵ'
- { 0x00, 0x00, 0x00, 0x11, 0x09, 0x05, 0x03, 0x05, 0x09, 0x11, 0x04, 0x04, }, // 'Ķ'
- { 0x00, 0x00, 0x00, 0x01, 0x01, 0x11, 0x09, 0x07, 0x09, 0x11, 0x04, 0x04, }, // 'ķ'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x09, 0x07, 0x09, 0x11, 0x00, 0x00, }, // 'ĸ'
- { 0x08, 0x04, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ĺ'
- { 0x08, 0x04, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x00, 0x00, }, // 'ĺ'
- { 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x1f, 0x08, 0x06, }, // 'Ļ'
- { 0x00, 0x00, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x1f, 0x08, 0x06, }, // 'ļ'
- { 0x00, 0x00, 0x00, 0x11, 0x11, 0x09, 0x01, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ľ'
- { 0x00, 0x00, 0x00, 0x13, 0x12, 0x0a, 0x02, 0x02, 0x02, 0x1c, 0x00, 0x00, }, // 'ľ'
- { 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x09, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ŀ'
- { 0x00, 0x00, 0x00, 0x03, 0x02, 0x02, 0x0a, 0x02, 0x02, 0x1c, 0x00, 0x00, }, // 'ŀ'
- { 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x03, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ł'
- { 0x00, 0x00, 0x00, 0x03, 0x02, 0x02, 0x06, 0x03, 0x02, 0x1c, 0x00, 0x00, }, // 'ł'
- { 0x08, 0x04, 0x00, 0x11, 0x11, 0x13, 0x15, 0x19, 0x11, 0x11, 0x00, 0x00, }, // 'Ń'
- { 0x00, 0x00, 0x08, 0x04, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'ń'
- { 0x00, 0x00, 0x00, 0x11, 0x11, 0x13, 0x15, 0x19, 0x11, 0x11, 0x04, 0x03, }, // 'Ņ'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x04, 0x03, }, // 'ņ'
- { 0x0a, 0x04, 0x00, 0x11, 0x11, 0x13, 0x15, 0x19, 0x11, 0x11, 0x00, 0x00, }, // 'Ň'
- { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'ň'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'ʼn'
- { 0x00, 0x00, 0x00, 0x11, 0x11, 0x13, 0x15, 0x19, 0x11, 0x11, 0x10, 0x0c, }, // 'Ŋ'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x11, 0x11, 0x10, 0x0c, }, // 'ŋ'
- { 0x00, 0x0e, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ō'
- { 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'ō'
- { 0x0a, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ŏ'
- { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'ŏ'
- { 0x14, 0x0a, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ő'
- { 0x00, 0x00, 0x14, 0x0a, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'ő'
- { 0x00, 0x00, 0x00, 0x1e, 0x05, 0x05, 0x1d, 0x05, 0x05, 0x1e, 0x00, 0x00, }, // 'Œ'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x15, 0x1d, 0x05, 0x0e, 0x00, 0x00, }, // 'œ'
- { 0x08, 0x04, 0x00, 0x0f, 0x11, 0x11, 0x0f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Ŕ'
- { 0x00, 0x00, 0x08, 0x04, 0x00, 0x0d, 0x13, 0x01, 0x01, 0x01, 0x00, 0x00, }, // 'ŕ'
- { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x0f, 0x11, 0x11, 0x11, 0x04, 0x03, }, // 'Ŗ'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x13, 0x01, 0x01, 0x01, 0x04, 0x03, }, // 'ŗ'
- { 0x0a, 0x04, 0x00, 0x0f, 0x11, 0x11, 0x0f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Ř'
- { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x0d, 0x13, 0x01, 0x01, 0x01, 0x00, 0x00, }, // 'ř'
- { 0x08, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x0e, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // 'Ś'
- { 0x00, 0x00, 0x08, 0x04, 0x00, 0x1e, 0x01, 0x0e, 0x10, 0x0f, 0x00, 0x00, }, // 'ś'
- { 0x04, 0x0a, 0x00, 0x0e, 0x11, 0x01, 0x0e, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // 'Ŝ'
- { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x1e, 0x01, 0x0e, 0x10, 0x0f, 0x00, 0x00, }, // 'ŝ'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x0e, 0x10, 0x11, 0x0e, 0x04, 0x03, }, // 'Ş'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x01, 0x0e, 0x10, 0x0f, 0x04, 0x03, }, // 'ş'
- { 0x0a, 0x04, 0x00, 0x0e, 0x11, 0x01, 0x0e, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // 'Š'
- { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x1e, 0x01, 0x0e, 0x10, 0x0f, 0x00, 0x00, }, // 'š'
- { 0x00, 0x00, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, }, // 'Ţ'
- { 0x00, 0x00, 0x00, 0x02, 0x02, 0x0f, 0x02, 0x02, 0x02, 0x1c, 0x08, 0x06, }, // 'ţ'
- { 0x0a, 0x04, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'Ť'
- { 0x00, 0x00, 0x08, 0x0a, 0x02, 0x0f, 0x02, 0x02, 0x02, 0x1c, 0x00, 0x00, }, // 'ť'
- { 0x00, 0x00, 0x00, 0x1f, 0x04, 0x0e, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'Ŧ'
- { 0x00, 0x00, 0x00, 0x02, 0x0f, 0x02, 0x0f, 0x02, 0x02, 0x1c, 0x00, 0x00, }, // 'ŧ'
- { 0x16, 0x09, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ũ'
- { 0x00, 0x00, 0x16, 0x09, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ũ'
- { 0x00, 0x0e, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ū'
- { 0x00, 0x00, 0x00, 0x0e, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ū'
- { 0x0a, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ŭ'
- { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ŭ'
- { 0x0a, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ů'
- { 0x00, 0x04, 0x0a, 0x04, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ů'
- { 0x14, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'Ű'
- { 0x00, 0x00, 0x14, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x00, 0x00, }, // 'ű'
- { 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x04, 0x18, }, // 'Ų'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x04, 0x18, }, // 'ų'
- { 0x04, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x15, 0x1b, 0x11, 0x00, 0x00, }, // 'Ŵ'
- { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0a, 0x00, 0x00, }, // 'ŵ'
- { 0x04, 0x0a, 0x00, 0x11, 0x11, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'Ŷ'
- { 0x00, 0x00, 0x04, 0x0a, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'ŷ'
- { 0x00, 0x0a, 0x00, 0x11, 0x11, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'Ÿ'
- { 0x08, 0x04, 0x00, 0x1f, 0x10, 0x08, 0x04, 0x02, 0x01, 0x1f, 0x00, 0x00, }, // 'Ź'
- { 0x00, 0x00, 0x08, 0x04, 0x00, 0x1f, 0x08, 0x04, 0x02, 0x1f, 0x00, 0x00, }, // 'ź'
- { 0x00, 0x04, 0x00, 0x1f, 0x10, 0x08, 0x04, 0x02, 0x01, 0x1f, 0x00, 0x00, }, // 'Ż'
- { 0x00, 0x00, 0x00, 0x04, 0x00, 0x1f, 0x08, 0x04, 0x02, 0x1f, 0x00, 0x00, }, // 'ż'
- { 0x0a, 0x04, 0x00, 0x1f, 0x10, 0x08, 0x04, 0x02, 0x01, 0x1f, 0x00, 0x00, }, // 'Ž'
- { 0x00, 0x00, 0x0a, 0x04, 0x00, 0x1f, 0x08, 0x04, 0x02, 0x1f, 0x00, 0x00, }, // 'ž'
- { 0x00, 0x0a, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Ё'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x00, 0x00, }, // 'А'
- { 0x00, 0x00, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'Б'
- { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x0f, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'В'
- { 0x00, 0x00, 0x00, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, }, // 'Г'
- { 0x00, 0x00, 0x00, 0x0c, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x1f, 0x11, 0x00, }, // 'Д'
- { 0x00, 0x00, 0x00, 0x1f, 0x01, 0x01, 0x0f, 0x01, 0x01, 0x1f, 0x00, 0x00, }, // 'Е'
- { 0x00, 0x00, 0x00, 0x15, 0x15, 0x15, 0x0e, 0x15, 0x15, 0x15, 0x00, 0x00, }, // 'Ж'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x10, 0x0e, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // 'З'
- { 0x00, 0x00, 0x00, 0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x00, 0x00, }, // 'И'
- { 0x00, 0x0a, 0x04, 0x11, 0x11, 0x19, 0x15, 0x13, 0x11, 0x11, 0x00, 0x00, }, // 'Й'
- { 0x00, 0x00, 0x00, 0x19, 0x05, 0x05, 0x03, 0x05, 0x09, 0x11, 0x00, 0x00, }, // 'К'
- { 0x00, 0x00, 0x00, 0x1e, 0x12, 0x12, 0x12, 0x12, 0x12, 0x11, 0x00, 0x00, }, // 'Л'
- { 0x00, 0x00, 0x00, 0x11, 0x1b, 0x15, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'М'
- { 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'Н'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'О'
- { 0x00, 0x00, 0x00, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'П'
- { 0x00, 0x00, 0x00, 0x0f, 0x11, 0x11, 0x0f, 0x01, 0x01, 0x01, 0x00, 0x00, }, // 'Р'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x01, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'С'
- { 0x00, 0x00, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'Т'
- { 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, 0x00, 0x00, }, // 'У'
- { 0x00, 0x00, 0x00, 0x04, 0x0e, 0x15, 0x15, 0x15, 0x0e, 0x04, 0x00, 0x00, }, // 'Ф'
- { 0x00, 0x00, 0x00, 0x11, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x11, 0x00, 0x00, }, // 'Х'
- { 0x00, 0x00, 0x00, 0x00, 0x09, 0x09, 0x09, 0x09, 0x09, 0x1f, 0x10, 0x00, }, // 'Ц'
- { 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x10, 0x10, 0x00, 0x00, }, // 'Ч'
- { 0x00, 0x00, 0x00, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x1f, 0x00, 0x00, }, // 'Ш'
- { 0x00, 0x00, 0x00, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x1f, 0x10, 0x00, }, // 'Щ'
- { 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x0e, 0x12, 0x12, 0x0e, 0x00, 0x00, }, // 'Ъ'
- { 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x13, 0x15, 0x15, 0x13, 0x00, 0x00, }, // 'Ы'
- { 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x0f, 0x11, 0x11, 0x0f, 0x00, 0x00, }, // 'Ь'
- { 0x00, 0x00, 0x00, 0x0e, 0x11, 0x10, 0x1c, 0x10, 0x11, 0x0e, 0x00, 0x00, }, // 'Э'
- { 0x00, 0x00, 0x00, 0x09, 0x15, 0x15, 0x17, 0x15, 0x15, 0x09, 0x00, 0x00, }, // 'Ю'
- { 0x00, 0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x1e, 0x11, 0x11, 0x00, 0x00, }, // 'Я'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x10, 0x1e, 0x11, 0x1e, 0x00, 0x00, }, // 'а'
- { 0x00, 0x00, 0x00, 0x1e, 0x01, 0x0d, 0x13, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'б'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x11, 0x0f, 0x11, 0x0f, 0x00, 0x00, }, // 'в'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, }, // 'г'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x0a, 0x0a, 0x0a, 0x1f, 0x11, 0x00, }, // 'д'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'е'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x0e, 0x04, 0x0e, 0x15, 0x00, 0x00, }, // 'ж'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x09, 0x04, 0x09, 0x06, 0x00, 0x00, }, // 'з'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x19, 0x15, 0x13, 0x11, 0x00, 0x00, }, // 'и'
- { 0x00, 0x00, 0x00, 0x0a, 0x04, 0x11, 0x19, 0x15, 0x13, 0x11, 0x00, 0x00, }, // 'й'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x09, 0x07, 0x09, 0x11, 0x00, 0x00, }, // 'к'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x12, 0x12, 0x12, 0x11, 0x00, 0x00, }, // 'л'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x1b, 0x15, 0x11, 0x11, 0x00, 0x00, }, // 'м'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x00, 0x00, }, // 'н'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00, 0x00, }, // 'о'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x00, 0x00, }, // 'п'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x13, 0x11, 0x11, 0x0f, 0x01, 0x01, }, // 'р'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x01, 0x11, 0x0e, 0x00, 0x00, }, // 'с'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, }, // 'т'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x0e, }, // 'у'
- { 0x00, 0x00, 0x00, 0x04, 0x04, 0x0e, 0x15, 0x15, 0x15, 0x0e, 0x04, 0x04, }, // 'ф'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x00, 0x00, }, // 'х'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x09, 0x09, 0x09, 0x1f, 0x10, 0x00, }, // 'ц'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x00, 0x00, }, // 'ч'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x15, 0x15, 0x15, 0x1f, 0x00, 0x00, }, // 'ш'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x15, 0x15, 0x15, 0x1f, 0x10, 0x00, }, // 'щ'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x0e, 0x12, 0x0e, 0x00, 0x00, }, // 'ъ'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x13, 0x15, 0x13, 0x00, 0x00, }, // 'ы'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x0f, 0x11, 0x0f, 0x00, 0x00, }, // 'ь'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x11, 0x1c, 0x11, 0x0e, 0x00, 0x00, }, // 'э'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x15, 0x17, 0x15, 0x09, 0x00, 0x00, }, // 'ю'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x11, 0x11, 0x1e, 0x11, 0x00, 0x00, }, // 'я'
- { 0x00, 0x00, 0x00, 0x0a, 0x00, 0x0e, 0x11, 0x1f, 0x01, 0x0e, 0x00, 0x00, }, // 'ё'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '—'
- { 0x00, 0x00, 0x00, 0x08, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // '’'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x15, 0x00, 0x00, }, // '…'
- { 0x00, 0x00, 0x00, 0x0c, 0x12, 0x07, 0x02, 0x07, 0x12, 0x0c, 0x00, 0x00, }, // '€'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x1e, 0x1f, 0x1e, 0x04, 0x00, 0x00, }, // '←'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0e, 0x1f, 0x0e, 0x0e, 0x00, 0x00, }, // '↑'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0f, 0x1f, 0x0f, 0x04, 0x00, 0x00, }, // '→'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x0e, 0x1f, 0x0e, 0x04, 0x00, 0x00, }, // '↓'
- { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, // ' '
-};
-
-u32 monogram_ascii_to_idx[127] = {
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 0xFFFF,
- 389, // 0x20 ' '
- 10, // 0x21 '!'
- 11, // 0x22 '"'
- 12, // 0x23 '#'
- 13, // 0x24 '$'
- 14, // 0x25 '%'
- 15, // 0x26 '&'
- 16, // 0x27 '''
- 17, // 0x28 '('
- 18, // 0x29 ')'
- 19, // 0x2a '*'
- 20, // 0x2b '+'
- 21, // 0x2c ','
- 22, // 0x2d '-'
- 23, // 0x2e '.'
- 24, // 0x2f '/'
- 0, // 0x30 '0'
- 1, // 0x31 '1'
- 2, // 0x32 '2'
- 3, // 0x33 '3'
- 4, // 0x34 '4'
- 5, // 0x35 '5'
- 6, // 0x36 '6'
- 7, // 0x37 '7'
- 8, // 0x38 '8'
- 9, // 0x39 '9'
- 25, // 0x3a ':'
- 26, // 0x3b ';'
- 27, // 0x3c '<'
- 28, // 0x3d '='
- 29, // 0x3e '>'
- 30, // 0x3f '?'
- 31, // 0x40 '@'
- 32, // 0x41 'A'
- 33, // 0x42 'B'
- 34, // 0x43 'C'
- 35, // 0x44 'D'
- 36, // 0x45 'E'
- 37, // 0x46 'F'
- 38, // 0x47 'G'
- 39, // 0x48 'H'
- 40, // 0x49 'I'
- 41, // 0x4a 'J'
- 42, // 0x4b 'K'
- 43, // 0x4c 'L'
- 44, // 0x4d 'M'
- 45, // 0x4e 'N'
- 46, // 0x4f 'O'
- 47, // 0x50 'P'
- 48, // 0x51 'Q'
- 49, // 0x52 'R'
- 50, // 0x53 'S'
- 51, // 0x54 'T'
- 52, // 0x55 'U'
- 53, // 0x56 'V'
- 54, // 0x57 'W'
- 55, // 0x58 'X'
- 56, // 0x59 'Y'
- 57, // 0x5a 'Z'
- 58, // 0x5b '['
- 59, // 0x5c '\'
- 60, // 0x5d ']'
- 61, // 0x5e '^'
- 62, // 0x5f '_'
- 63, // 0x60 '`'
- 64, // 0x61 'a'
- 65, // 0x62 'b'
- 66, // 0x63 'c'
- 67, // 0x64 'd'
- 68, // 0x65 'e'
- 69, // 0x66 'f'
- 70, // 0x67 'g'
- 71, // 0x68 'h'
- 72, // 0x69 'i'
- 73, // 0x6a 'j'
- 74, // 0x6b 'k'
- 75, // 0x6c 'l'
- 76, // 0x6d 'm'
- 77, // 0x6e 'n'
- 78, // 0x6f 'o'
- 79, // 0x70 'p'
- 80, // 0x71 'q'
- 81, // 0x72 'r'
- 82, // 0x73 's'
- 83, // 0x74 't'
- 84, // 0x75 'u'
- 85, // 0x76 'v'
- 86, // 0x77 'w'
- 87, // 0x78 'x'
- 88, // 0x79 'y'
- 89, // 0x7a 'z'
- 90, // 0x7b '{'
- 91, // 0x7c '|'
- 92, // 0x7d '}'
- 93, // 0x7e '~'
-};
-
-void console_redraw_commit()
-{
- console_redraw_line(line_idx);
-
- for (u32 row = 0; row < NUM_ROWS; ++row)
- {
- for (int col = 0; col < NUM_COLS; ++col)
- {
- char ch = text[row][col];
-
- if (ch == 0) continue;
-
- console_draw_char(ch, col, row);
- }
- }
-
- u64 t = time_current_ms();
- bool cursor_on = (t / 400) % 2;
-
- if (cursor_on)
- {
- console_draw_char('_', col_idx, line_idx);
- }
-
- window_draw_frame(0, frame_buffer);
-}
-
-size_t console_get_line_pos(size_t line_num)
-{
- return margin + char_height*line_num;
-}
-
-void console_redraw_line(size_t start_y)
-{
- size_t start = console_get_line_pos(start_y);
- size_t end = console_get_line_pos(start_y + 1) + FONT_MONOGRAM_HEIGHT;
-
- for(u64 y = start; y < end; ++y)
- memset32(((u32*)frame_buffer) + FRAME_WIDTH * y, blue, console_width);
-}
-
-void console_redraw_all_text()
-{
- for(size_t y = 0; y < FRAME_HEIGHT; ++y)
- memset32(((u32*)frame_buffer) + FRAME_WIDTH * y, blue, console_width);
-}
-
-void console_make_space()
-{
- for(size_t cur=0; cur < (NUM_ROWS-1); ++cur)
- memcpy(text[cur], text[cur+1], NUM_COLS);
- memset(text[NUM_ROWS-1], 0, NUM_COLS);
- col_idx=0;
- console_redraw_all_text();
-}
-
-void console_newline()
-{
- if (line_idx + 1 < NUM_ROWS)
- {
- console_redraw_line(line_idx);
- line_idx = line_idx + 1;
- col_idx = 0;
- }
- else console_make_space();
-}
-
-void console_putchar(char ch)
-{
- text[line_idx][col_idx] = ch;
-
- if (col_idx + 1 < NUM_COLS)
- {
- col_idx = col_idx + 1;
- }
- else if (line_idx + 1 < NUM_ROWS)
- {
- size_t old_col_idx = col_idx;
- console_newline();
- min_col_idx = 0;
- if(old_col_idx >= NUM_COLS)
- col_idx = col_idx + 1;
- }
- else
- {
- console_make_space();
- if(min_line_idx > 0)
- min_line_idx = min_line_idx - 1;
- }
-}
-
-void console_puts(u8* ch)
-{
- while((*ch) != 0)
- {
- console_putchar(*ch);
- ++ch;
- }
-}
-
-char console_input_buff[20];
-
-void console_print_i64(i64 n)
-{
- memset(console_input_buff, 0, sizeof(console_input_buff));
- u8 buff_start = 18;
- u8 is_neg = 0;
-
- if(0 > n)
- {
- is_neg = 1;
- n = -n;
- }
- else if (n == 0)
- {
- console_input_buff[buff_start] = 48;
- --buff_start;
- }
-
- for(; n!=0; --buff_start)
- {
- console_input_buff[buff_start] = (n % 10) + 48;
- n = n / 10;
- }
-
- if (is_neg)
- {
- DEBUG("setting neg sign\n");
- console_input_buff[buff_start] = '-';
- --buff_start;
- }
-
- console_puts(console_input_buff+(buff_start+1));
-}
-
-void console_draw_char(char ch, size_t row_num, size_t col_num)
-{
- row_num = margin + char_width * row_num;
- col_num = console_get_line_pos(col_num);
- u8 scale = 3;
- u32 char_idx = monogram_ascii_to_idx[ch];
-
- u32* d = get_point_ptr((u32*)frame_buffer, FRAME_WIDTH, row_num, col_num);
-
- for (u64 y = 0; y < FONT_MONOGRAM_HEIGHT; ++y)
- {
- u8 pixel_bits = font_monogram_data[char_idx][y];
- for (u8 i = 0; i < scale; ++i)
- {
- u64 x = 0;
- u8 pb = pixel_bits;
-
- while (pb)
- {
- memset32(d + x, blue, scale);
- if (pb & 1) memset32(d + x, 0xFFFFFF, scale);
- x = x + scale;
- pb = pb >> 1;
- }
-
- d = d + FRAME_WIDTH;
- }
- }
-}
-
-
-//===========================================================================
-/* CANVAS */
-
-size_t canvas_width;
-size_t canvas_height;
-#define CANVAS_PLOT_POINT_SIZE 5
-
-u8 canvas_plot(u64 x, u64 y, u64 color)
-{
- TRY(canvas_coord_gaurd(x, y));
- u32* p = get_point_ptr((u32*)frame_buffer, FRAME_WIDTH, x, y);
-
- for(u32 y = 0; y < CANVAS_PLOT_POINT_SIZE; ++y)
- {
- memset32(p + console_width, color, CANVAS_PLOT_POINT_SIZE);
- p = p + FRAME_WIDTH;
- }
- return 0;
-}
-
-u8 canvas_coord_gaurd(u32 x, u32 y)
-{
- if((x + console_width) >= FRAME_WIDTH)
- {
- console_error("The given X coordinate exceeds the size of the canvas width");
- return 1;
- }
- if(y >= FRAME_HEIGHT)
- {
- console_error("The given y coordinate exceeds the size of the canvas height");
- return 1;
- }
-
- return 0;
-}
-
-void canvas_fill(u32 color)
-{
- memset32(frame_buffer, color, sizeof(frame_buffer) / sizeof(u32));
-}
-
-//===========================================================================
-/* INTERPRETER */
-
-u64 vm_status = VM_STATUS_DONE;
-
-char vm_command_text_buffer[1024];
-size_t vm_command_text_buffer_cursor =0;
-size_t vm_command_text_buffer_read = 0;
-
-void vm_command_text_buffer_clear()
-{
- memset(vm_command_text_buffer, 0, sizeof(vm_command_text_buffer));
- vm_command_text_buffer_read = 0;
- vm_command_text_buffer_cursor = 0;
-}
-
-void vm_command_text_buffer_backspace()
-{
- if(vm_command_text_buffer_cursor > 0)
- {
- vm_command_text_buffer[vm_command_text_buffer_cursor] = 0;
- --vm_command_text_buffer_cursor;
- }
-}
-
-#define OP_PUSH 0
-// pops the top of the stack, assigns the value to the var
-#define OP_SET_VAR 1
-// pushes a var value to the stack
-#define OP_GET_VAR 2
-
-#define OP_JUMP 3 // jumps to instructions within a command
-#define OP_JUMP_IF 4
-#define OP_JUMP_IF_NOT 5
-
-// binary arth ops pop 2 vals from the stack, push the result to the stack
-#define OP_ADD 6
-#define OP_SUB 7
-#define OP_MULT 8
-#define OP_DIV 9
-
-#define OP_GT 10
-#define OP_LT 11
-#define OP_GT_EQ 12
-#define OP_LT_EQ 13
-
-#define OP_EQ 14
-#define OP_NOT_EQ 15
-
-#define OP_PRINT_INT 16
-
-// Jumps to a command, as opposed to jumping to instructions as OP_JUMP does
-#define OP_GOTO 17
-
-// draws a pixel at x, y
-#define OP_PLOT 18
-#define OP_LINE 19
-#define OP_FILL 20
-#define OP_HELP_ALL 21
-
-#define OP_HALT 22 // stops execution
-
-#define OP_EXIT 23 // Exits the program
-
-#define OP_SLEEP 24
-#define OP_RAND 25
-#define OP_MOD 26
-#define OP_PRINT_STR 27
-
-char* error_prefix = "Error: ";
-
-// Maps symbols to var positions
-size_t vm_intern_capacity = 1024;
-u64** vm_intern_buffer;
-
-// Linkedlist containing sorterd commands
-// Each command is a list of instructions
-// represented this way in order to insert new commands in between old ones
-// the number of the command. e.g. 10 LET x 10 -> command[VM_COMMANDS_NUM] = 10
-#define VM_COMMANDS_NUM 0
-// The number of instructions that the command can hold
-#define VM_COMMANDS_CAPACITY 1
-// The number of instructions that the command currently hold
-#define VM_COMMANDS_CUR 2
-// command[VM_COMMANDS_NEXT] holds the address of the next command
-#define VM_COMMANDS_NEXT 3
-// command[VM_COMMANDS_INSTS_BUFFER] is the buffer that actually holds the instructions
-#define VM_COMMANDS_INSTS_BUFFER 4
-
-u64** vm_commands_selected = NULL;
-u64** vm_commands_root = NULL;
-
-// Vector containing variable values
-size_t vm_vars_allocated = 0;
-i64 vm_vars[32768];
-
-// Stack that can be pushed to and poped from
-size_t vm_stack_cursor = 0;
-i64 vm_stack[1024];
-
-u64** vm_commands_alloc(u64 num)
-{
- size_t capacity = 256;
- size_t meta_data_size = sizeof(u64*)*5;
- size_t inst_buffer_size = sizeof(u64)*capacity;
-
- u64** command_meta = (u64**)malloc(meta_data_size);
- NULLGAURD(command_meta);
- memset(command_meta, 0, meta_data_size);
- u64* inst_buffer = (u64*) malloc(inst_buffer_size);
- NULLGAURD(inst_buffer);
- memset(inst_buffer, 0, inst_buffer_size);
- command_meta[VM_COMMANDS_CAPACITY] = (u64*)capacity;
- command_meta[VM_COMMANDS_NUM] = (u64*)num;
- command_meta[VM_COMMANDS_INSTS_BUFFER] = inst_buffer;
- return command_meta;
-}
-
-u64** vm_command_find(u64 num)
-{
- for(u64** cur_cmd = vm_commands_root; cur_cmd != 0; cur_cmd = (u64**)cur_cmd[VM_COMMANDS_NEXT])
- {
- u64 cur_cmd_num = (u64)cur_cmd[VM_COMMANDS_NUM];
- if (cur_cmd_num == num) return cur_cmd;
- }
- return 0;
-}
-
-void vm_command_free(u64** cmd)
-{
- free((void*)cmd[VM_COMMANDS_INSTS_BUFFER]);
- free((void*) cmd);
-}
-
-u64** vm_command_create(u64 num)
-{
-
- u64** new_cmd = vm_commands_alloc(num);
- NULLGAURD(new_cmd);
-
- if(vm_commands_root == NULL)
- { // initialize the root
- DEBUG("Init root\n");
- new_cmd[VM_COMMANDS_NEXT] = (u64*)vm_commands_root;
- vm_commands_root = new_cmd;
- return new_cmd;
- }
-
- DEBUG("Finding command\n");
- u64** prev_cmd = 0;
- for(u64** cur_cmd = vm_commands_root;; cur_cmd = (u64**)cur_cmd[VM_COMMANDS_NEXT])
- {
- if(cur_cmd == NULL)
- {
- DEBUG("Inserting command at the end");
- prev_cmd[VM_COMMANDS_NEXT] = (u64*)new_cmd;
- break;
- }
- u64 cur_cmd_num = (u64)cur_cmd[VM_COMMANDS_NUM];
- if(cur_cmd_num > num)
- { // insert command before
- DEBUG("Inserting commands in between commands");
- if(cur_cmd == vm_commands_root) vm_commands_root = new_cmd;
- prev_cmd[VM_COMMANDS_NEXT] = (u64*)new_cmd;
- new_cmd[VM_COMMANDS_NEXT] = (u64*)cur_cmd;
- break;
- }
- else if(cur_cmd_num == num)
- { // insert command exactly at
- DEBUG("Replacing existing command");
- if(cur_cmd == vm_commands_root) vm_commands_root = new_cmd;
- prev_cmd[VM_COMMANDS_NEXT] = (u64*)new_cmd;
- new_cmd[VM_COMMANDS_NEXT] = (u64*)cur_cmd[VM_COMMANDS_NEXT];
- vm_command_free(cur_cmd);
- break;
- }
- prev_cmd = cur_cmd;
- }
-
- return new_cmd;
-}
-
-u64 vm_alloc_var()
-{
- u64 allocated = vm_vars_allocated;
- ++vm_vars_allocated;
- return allocated;
-}
-
-// When a symbol is interned a small area of memory that holds some meta data about the symbol is allocated
-// stores the actual string that represents the symbol
-#define SYM_META_STR 0
-// stores the index of the variable in vm_vars. vm_vars[sym_meta[SYM_META_LOC]] = the value of the symbol as set by LET
-#define SYM_META_LOC 1
-
-u64* vm_alloc_sym_meta()
-{
- size_t size = sizeof(u64)*3;
- u64* sym_meta = (u64*)malloc(size);
- NULLGAURD(sym_meta);
- memset(sym_meta, 0, size);
- return sym_meta;
-}
-
-u8 vm_intern_buffer_alloc()
-{
- DEBUGS("Allocating intern buffer with size: ");
- DEBUGI(vm_intern_capacity);
- DEBUG("");
-
- size_t interned_symbols_size = sizeof(u64**) * vm_intern_capacity;
- vm_intern_buffer = (u64**)(malloc(interned_symbols_size));
- if(vm_intern_buffer == NULL) return 1;
- memset(vm_intern_buffer, 0, interned_symbols_size);
- return 0;
-}
-
-void resize_interned_sym_buffer()
-{
- size_t old_capacity = vm_intern_capacity;
- u64** old_buffer = vm_intern_buffer;
-
- vm_intern_capacity = vm_intern_capacity * 2;
- vm_intern_buffer_alloc();
-
- DEBUG("Copying interned symbols from old to new buffer");
- for(size_t cursor = 0; cursor < old_capacity; ++cursor)
- {
- u64* sym_meta = old_buffer[cursor];
- char* str = sym_meta[SYM_META_STR];
- u32 str_hash = hash(str);
- u64 idx = (vm_intern_capacity - 1) & str_hash;
-
- DEBUG("Finding a spot for symbol: ");
- DEBUGS(str);
- DEBUG("");
-
- while(vm_intern_buffer[idx] != 0)
- {
- DEBUGS("Couldn't find a spot at index: ");
- DEBUGI(idx);
- DEBUG("");
- ++idx;
- if(idx >= vm_intern_buffer) idx = 0;
- }
- DEBUG("Found a spot at index: ");
- DEBUGI(idx);
- DEBUG("");
-
- vm_intern_buffer[idx] = sym_meta;
- }
-}
-
-u64* vm_intern(u8* sym, u64 len, u32 hash)
-{
- u64 idx = (vm_intern_capacity - 1) & hash;
- DEBUG("Interning a new sym: ");
- DEBUGS(sym);
- for(size_t cursor = idx;; ++cursor)
- {
- if (cursor >= vm_intern_capacity)
- {
- if(idx == 0) break;
- cursor = 0;
- }
-
- DEBUGS("Attempting to locate symbol at index: ");
- DEBUGI(cursor);
- DEBUG("");
-
- u64* sym_meta = vm_intern_buffer[cursor];
- if (sym_meta == NULL)
- {
- DEBUG("Symbol not found, creating a new entry");
- char* sym_str;
- sym_str = (char*)malloc(len+1);
- NULLGAURD(sym_str);
- memcpy(sym_str, sym, len);
- sym_str[len] = 0;
-
- sym_meta = vm_alloc_sym_meta();
- NULLGAURD(sym_meta);
- sym_meta[SYM_META_STR] = (u64)sym_str;
-
- vm_intern_buffer[cursor] = (u64)sym_meta;
- return sym_meta;
- }
- else if (strncmp(sym_meta[SYM_META_STR], sym, len) == 0)
- {
- DEBUG("Symbol have been found");
- return sym_meta;
- }
- else if(cursor == (idx-1))
- {
- break;
- }
- }
-
- DEBUG("Not enough space in intern buffer");
- resize_interned_sym_buffer();
- DEBUG("Done resizing intern buffer");
- return vm_intern(sym, len, hash);
-}
-
-u8 vm_symbol_is_initialized(u64* sym)
-{
- return sym[SYM_META_LOC] != 0;
-}
-
-u64 vm_symbol_init_or_get_var(u64* sym)
-{
- if(!vm_symbol_is_initialized(sym))
- {
- sym[SYM_META_LOC] = ++vm_vars_allocated;
- }
- return sym[SYM_META_LOC]-1;
-}
-
-void vm_symbol_init_val(u64* sym, u64 val)
-{
- u64 var = vm_symbol_init_or_get_var(sym);
- vm_vars[var] = val;
-}
-
-// translates symbol strings to symbols
-i64 vm_symbol_get_var(u64* sym)
-{
- if(!vm_symbol_is_initialized(sym))
- {
- console_newline();
- console_puts(error_prefix);
- console_puts("Trying to access ");
- console_newline();
- console_puts("an uninitialized variable");
- console_puts(sym[SYM_META_STR]);
- return -1;
- }
- return sym[SYM_META_LOC]-1;
-}
-
-u32 hash(u8 *str)
-{
- u32 hash = 5381;
- u8 c;
-
- while (c = *str)
- {
- hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
- ++str;
- }
-
- return hash;
-}
-
-u64* vm_commands_sym_else;
-u64* vm_commands_sym_let;
-u64* vm_commands_sym_goto;
-u64* vm_commands_sym_run;
-u64* vm_commands_sym_if;
-u64* vm_commands_sym_plot;
-u64* vm_commands_sym_line;
-u64* vm_commands_sym_clear;
-u64* vm_commands_sym_help;
-u64* vm_commands_sym_halt;
-u64* vm_commands_sym_exit;
-u64* vm_commands_sym_quit;
-u64* vm_commands_sym_fill;
-u64* vm_commands_sym_sleep;
-u64* vm_commands_sym_rand;
-u64* vm_commands_sym_print;
-
-u64* vm_init_sym(char* sym)
-{
- return vm_intern(sym, strlen(sym), hash(sym));
-}
-
-int vm_init()
-{
- TRY(vm_intern_buffer_alloc());
-
- vm_commands_sym_else = vm_init_sym("ELSE");
- vm_commands_sym_let = vm_init_sym("LET");
- vm_commands_sym_goto = vm_init_sym("GOTO");
- vm_commands_sym_run = vm_init_sym("RUN");
- vm_commands_sym_if = vm_init_sym("IF");
- vm_commands_sym_plot = vm_init_sym("PLOT");
- vm_commands_sym_line = vm_init_sym("LINE");
- vm_commands_sym_clear = vm_init_sym("CLEAR");
- vm_commands_sym_help = vm_init_sym("HELP");
- vm_commands_sym_halt = vm_init_sym("HALT");
- vm_commands_sym_exit = vm_init_sym("EXIT");
- vm_commands_sym_quit = vm_init_sym("QUIT");
- vm_commands_sym_fill = vm_init_sym("FILL");
- vm_commands_sym_sleep = vm_init_sym("SLEEP");
- vm_commands_sym_rand = vm_init_sym("RAND");
- vm_commands_sym_print = vm_init_sym("PRINT");
-
- vm_symbol_init_val(vm_init_sym("BLUE"), blue);
- vm_symbol_init_val(vm_init_sym("RED"), red);
- vm_symbol_init_val(vm_init_sym("GREEN"), green);
- vm_symbol_init_val(vm_init_sym("BLACK"), black);
- vm_symbol_init_val(vm_init_sym("WHITE"), white);
-
- canvas_width = FRAME_WIDTH - console_width-CANVAS_PLOT_POINT_SIZE;
- canvas_height = FRAME_HEIGHT-CANVAS_PLOT_POINT_SIZE;
- vm_symbol_init_val(vm_init_sym("CWIDTH"), canvas_width);
- vm_symbol_init_val(vm_init_sym("CHEIGHT"), canvas_height);
-
- return 0;
-}
-
-void vm_command_double_size()
-{
- u64* old_buff = vm_commands_selected[VM_COMMANDS_INSTS_BUFFER];
- size_t old_capacity = (size_t)vm_commands_selected[VM_COMMANDS_CAPACITY];
- size_t new_capacity = old_capacity*2;
- size_t old_size = old_capacity*sizeof(u64);
- size_t new_size = new_capacity*sizeof(u64);
-
- u64* new_buff = (u64*)malloc(new_size);
- if(new_buff == NULL)
- {
- DEBUG("Failed to allocate more memory for the selected command");
- exit(1);
- }
-
- memset(new_buff, 0, new_size);
- memcpy(new_buff, old_buff, old_size);
- vm_commands_selected[VM_COMMANDS_INSTS_BUFFER] = new_buff;
- vm_commands_selected[VM_COMMANDS_CAPACITY] = new_capacity;
- free((void*)old_buff);
-}
-
-u64 vm_bytecode_pointer_inc()
-{
- u64 current_val = (u64)vm_commands_selected[VM_COMMANDS_CUR];
- vm_commands_selected[VM_COMMANDS_CUR] = (u64*)(current_val + 1);
- if(vm_commands_selected[VM_COMMANDS_CUR] >= vm_commands_selected[VM_COMMANDS_CAPACITY])
- {
- DEBUG("Exceeded command capacity");
- vm_command_double_size();
- }
- return current_val;
-}
-
-u64 vm_bytecode_get(u8 op, u64 arg)
-{
- return ((u64)(op & 255) << 56) | (arg & (((u64)1<<56) - 1));
-}
-
-void vm_bytecode_patch(u64 inst_num, u8 op, u64 arg)
-{
- vm_commands_selected[VM_COMMANDS_INSTS_BUFFER][inst_num] = vm_bytecode_get(op, arg);
-}
-
-void vm_bytecode_emit(u8 op, u64 arg)
-{
- DEBUGS("VM command capacity");
- DEBUGI(vm_commands_selected[VM_COMMANDS_CAPACITY]);
- DEBUG("");
- u64 pos = (u64)vm_commands_selected[VM_COMMANDS_CUR];
- vm_commands_selected[VM_COMMANDS_INSTS_BUFFER][pos] = vm_bytecode_get(op, arg);
- vm_bytecode_pointer_inc();
-}
-
-void ignore_ws()
-{
- while(vm_command_text_buffer_cursor > vm_command_text_buffer_read &&
- vm_command_text_buffer[vm_command_text_buffer_read] == 32)
- ++vm_command_text_buffer_read;
-}
-
-u8 peek_ch(u8 n)
-{
- ignore_ws();
- if(vm_command_text_buffer_read >= vm_command_text_buffer_cursor) return 0;
- return (u8)vm_command_text_buffer[vm_command_text_buffer_read + n];
-}
-
-u8 peek_next()
-{
- return peek_ch(0);
-}
-
-u8 read_ch()
-{
- ignore_ws();
- u8 ret = (u8)vm_command_text_buffer[vm_command_text_buffer_read];
- ++vm_command_text_buffer_read;
- return ret;
-}
-
-u8 did_read_all_input()
-{
- return vm_command_text_buffer_read >= vm_command_text_buffer_cursor;
-}
-
-i64 read_uint()
-{
- ignore_ws();
- size_t start = vm_command_text_buffer_read;
- u64 acc = 0;
- while(!did_read_all_input())
- {
-
- u8 cur_char = (u8)vm_command_text_buffer[vm_command_text_buffer_read];
-
- cur_char = cur_char - 48;
- if(cur_char > 9)
- {
- break;
- }
-
- acc = acc * 10 + cur_char;
- ++vm_command_text_buffer_read;
- }
-
- if(start == vm_command_text_buffer_read) return -1;
- return acc;
-}
-
-// TODO combine read_string and read_sym to some common func
-u64* read_string()
-{
- ignore_ws();
- u8 bracket = vm_command_text_buffer[vm_command_text_buffer_read];
- if(bracket != '"') return NULL;
-
- size_t str_start = ++vm_command_text_buffer_read;
-
- u32 hash = 5381;
- while(!did_read_all_input())
- {
- u8 cur_char = (u8)vm_command_text_buffer[vm_command_text_buffer_read];
- if (cur_char == '\\')
- {
- ++vm_command_text_buffer_read;
- }
- else if (cur_char == '"') break;
- hash = ((hash << 5) + hash) + cur_char;
-
- ++vm_command_text_buffer_read;
- }
- if(vm_command_text_buffer_read == str_start) return NULL;
-
- return vm_intern((u8*) (vm_command_text_buffer+str_start), vm_command_text_buffer_read-str_start, hash);
-}
-
-u64* read_sym()
-{
- DEBUG("Reading sym");
- ignore_ws();
- size_t sym_start = vm_command_text_buffer_read;
- u32 hash = 5381;
- while(!did_read_all_input())
- {
- u8 cur_char = (u8)vm_command_text_buffer[vm_command_text_buffer_read];
- // TODO accept symbols with numbers, _, etc
- if(!((90 >= cur_char && cur_char >= 65) || (122 >= cur_char && cur_char >= 97))) break;
- hash = ((hash << 5) + hash) + cur_char;
- ++vm_command_text_buffer_read;
- }
- if(vm_command_text_buffer_read == sym_start) return NULL;
- return vm_intern((u8*) (vm_command_text_buffer+sym_start), vm_command_text_buffer_read-sym_start, hash);
-}
-
-void console_error(char* err)
-{
- console_newline();
- console_puts(error_prefix);
- console_puts(err);
- console_newline();
-}
-
-u8 vm_emit_args(u8 expected_args_count)
-{
- char current = read_ch();
- if(current != '(')
- {
- console_error("Expected to find an ( but did not");
- return 1;
- }
- current = peek_next();
- u8 actual_args_count = 0;
- while(current != ')')
- {
- if(current == 0)
- {
- console_error("Expected function call to end with )");
- return 1;
- }
- else if (actual_args_count > expected_args_count)
- {
- console_error("Function got too many arguments");
- return 1;
- }
- ++actual_args_count;
- vm_emit_exp();
- }
- read_ch();
- return 0;
-}
-
-u64 vm_emit_prim()
-{
- i64 num = read_uint();
- if(num >= 0)
- {
- vm_bytecode_emit(OP_PUSH, num);
- return 0;
- }
-
- u64* sym = read_sym();
- char next_ch = peek_next();
-
- if(sym != 0)
- {
- if(next_ch == '(')
- {
- if (sym == vm_commands_sym_rand)
- {
- vm_emit_args(0);
- vm_bytecode_emit(OP_RAND, 0);
- }
- else
- {
- console_error("Undefined function");
- return 1;
- }
- return 0;
- }
-
- i64 var = vm_symbol_get_var(sym);
- if(0 > var) return 1;
- vm_bytecode_emit(OP_GET_VAR, var);
- return 0;
- }
-
- if(next_ch == '(')
- {
- read_ch();
- vm_emit_exp();
- next_ch = read_ch();
- if(next_ch != ')')
- {
- console_error("Expected to find ) but did not");
- return 1;
- }
- return 0;
- }
-
- if (next_ch == ')') return 0;
-
- console_error("Unexpected token in expression");
- return 1;
-}
-
-u64 vm_emit_factor()
-{
- u8 next_ch;
- u8 op;
-
- TRY(vm_emit_prim());
- while(1)
- {
- next_ch = peek_next();
- if('*' == next_ch) op = OP_MULT;
- else if('/' == next_ch) op = OP_DIV;
- else if('%' == next_ch) op = OP_MOD;
- else break;
-
- read_ch();
- TRY(vm_emit_prim());
- vm_bytecode_emit(op, 0);
- }
-
- return 0;
-}
-
-u64 emit_term()
-{
- u8 op;
- u8 next_ch;
-
- TRY(vm_emit_factor());
- while(1)
- {
- next_ch = peek_next();
- if('+' == next_ch) op = OP_ADD;
- else if('-' == next_ch) op = OP_SUB;
- else break;
-
- read_ch();
- TRY(vm_emit_factor());
- vm_bytecode_emit(op, 0);
- }
- return 0;
-}
-
-u64 vm_emit_comparison()
-{
- u8 op;
- u8 next_ch;
-
- TRY(emit_term());
- while(1)
- {
- next_ch = peek_next();
- if('>' == next_ch)
- {
- read_ch();
- next_ch = peek_next();
- op = OP_GT;
- if ('=' == next_ch)
- {
- op = OP_GT_EQ;
- read_ch();
- }
- }
- else if('<' == next_ch)
- {
- read_ch();
- next_ch = peek_next();
- op = OP_LT;
- if ('=' == next_ch)
- {
- op = OP_LT_EQ;
- read_ch();
- }
- }
- else break;
-
- TRY(emit_term());
- vm_bytecode_emit(op, 0);
- }
- return 0;
-}
-
-u64 vm_emit_exp()
-{
- u8 op;
- u8 next_ch;
-
- TRY(vm_emit_comparison());
-
- while(1)
- {
- next_ch = peek_next();
-
- if(next_ch == '!' && peek_ch(1) == '=') op = OP_NOT_EQ;
- else if(next_ch == '=' && peek_ch(1) == '=') op = OP_EQ;
- else break;
- read_ch();
- read_ch();
- TRY(vm_emit_comparison());
-
- vm_bytecode_emit(op, 0);
- }
- return 0;
-}
-
-void emit_print_str(char* s)
-{
- u64* interned_s = vm_intern(s, strlen(s), hash(s));
- u64 var = vm_symbol_init_or_get_var(interned_s);
- vm_vars[var] = (u64)interned_s;
- vm_bytecode_emit(OP_PRINT_STR, var);
-}
-
-u8 vm_emit_cmd(u64* command)
-{
- DEBUG("Emitting command");
-
- if(command == vm_commands_sym_help)
- {
- u64* sym = read_sym();
- if(sym == NULL)
- {
- DEBUG("Emitting help command");
- vm_bytecode_emit(OP_HELP_ALL, 0);
- }
- else if (sym == vm_commands_sym_let)
- emit_print_str("Assigns the value of the expression to the variable named by the symbol. LET {symbol} {expression}");
- else if (sym == vm_commands_sym_goto)
- emit_print_str("Goes to a loaded command. Commands can be loaded by prefixing them with a number. GOTO {cmd number}");
- else if (sym == vm_commands_sym_run)
- emit_print_str("Runs loaded commands. Commands can be loaded by prefixing them with a number");
- else if (sym == vm_commands_sym_if)
- emit_print_str("IF {predicate expression} {then command} ELSE {else command}");
- else if (sym == vm_commands_sym_plot)
- emit_print_str("Plots a dot at x and y. PLOT {x} {y} {color}");
- else if (sym == vm_commands_sym_line)
- emit_print_str("Draws a line from (x0, y0) to (x1, y1). LINE {x0} {y0} {x1} {y1} {color}");
- else if (sym == vm_commands_sym_clear)
- emit_print_str("Clears the canvas");
- else if (sym == vm_commands_sym_halt)
- emit_print_str("Stops command execution. You can use it to stop loops");
- else if (sym == vm_commands_sym_exit)
- emit_print_str("Exit the program");
- else if (sym == vm_commands_sym_quit)
- emit_print_str("Same as exit");
- else if (sym == vm_commands_sym_fill)
- emit_print_str("Fills the canvas with a color. FILL {color}");
- else if (sym == vm_commands_sym_sleep)
- emit_print_str("Sleep before executing the next command. SLEEP {time in milliseconds}");
- else if (sym == vm_commands_sym_rand)
- emit_print_str("A function that returns a random number. Example: LET x RAND()");
- else if (sym == vm_commands_sym_print)
- emit_print_str("Prints a string or a number. PRINT \"SOME STRING\"");
- else
- {
- console_error("Command or function is not found");
- return 1;
- }
- }
-
- else if(command == vm_commands_sym_let)
- {
- DEBUG("Emitting LET command");
- u64* sym = read_sym();
- if (sym == NULL)
- {
- console_error("LET is expected to be followed by a symbol but was not");
- return 1;
- }
- TRY(vm_emit_exp());
- u64 var = vm_symbol_init_or_get_var(sym);
- vm_bytecode_emit(OP_SET_VAR , var);
- }
- else if (command == vm_commands_sym_print)
- {
- DEBUG("Emitting print");
- u64* str = read_string();
- if(str != NULL)
- {
- DEBUG("Printing string");
- u64 var = vm_symbol_init_or_get_var(str);
- vm_vars[var] = (u64)str;
- vm_bytecode_emit(OP_PRINT_STR, var);
- }
- else
- {
- if(vm_emit_exp())
- {
- console_error("PRINT expects to be followed by a string or an expression");
- return 1;
- }
- DEBUG("Printing int");
- vm_bytecode_emit(OP_PRINT_INT, 0);
- }
- }
- else if (command == vm_commands_sym_sleep)
- {
- TRY(vm_emit_exp());
- vm_bytecode_emit(OP_SLEEP, 0);
- }
- else if (command == vm_commands_sym_goto)
- {
- DEBUG("Emitting GOTO");
- TRY(vm_emit_exp());
- vm_bytecode_emit(OP_GOTO, 0);
- }
- else if (command == vm_commands_sym_if)
- {
- DEBUG("Emitting IF");
- TRY(vm_emit_exp());
- u64 jump_to_else_pos = vm_bytecode_pointer_inc(); // resever inst to jump to else if pred is false
- command = read_sym();
- TRY(vm_emit_cmd(command)); // Then cmd
-
- u64 jump_to_else_end_pos = vm_bytecode_pointer_inc(); // reserve inst to jump to end of else;
-
- // emitting else
- u64* else_sym = read_sym();
- if(else_sym != vm_commands_sym_else)
- {
- console_error("Expected the Then clause to be followed by ELSE");
- return 1;
- }
- u64 start_of_else = (u64)vm_commands_selected[VM_COMMANDS_CUR];
- command = read_sym();
- TRY(vm_emit_cmd(command)); // Then cmd
-
- u64 end_of_else = (u64)vm_commands_selected[VM_COMMANDS_CUR];
-
- vm_bytecode_patch(jump_to_else_pos, OP_JUMP_IF_NOT, start_of_else);
- vm_bytecode_patch(jump_to_else_end_pos, OP_JUMP, end_of_else);
- }
- else if (command == vm_commands_sym_plot)
- {
- DEBUG("Emitting plot");
- TRY(vm_emit_exp());
- TRY(vm_emit_exp());
- TRY(vm_emit_exp());
- vm_bytecode_emit(OP_PLOT, 0);
- }
- else if (command == vm_commands_sym_line)
- {
- DEBUG("Emitting line");
- TRY(vm_emit_exp());
- TRY(vm_emit_exp());
- TRY(vm_emit_exp());
- TRY(vm_emit_exp());
- TRY(vm_emit_exp());
- vm_bytecode_emit(OP_LINE, 0);
- }
- else if (command == vm_commands_sym_halt)
- {
- DEBUG("Emitting clear");
- vm_bytecode_emit(OP_HALT, 0);
- }
- else if (command == vm_commands_sym_clear)
- {
- DEBUG("Emitting clear");
- vm_bytecode_emit(OP_PUSH, white);
- vm_bytecode_emit(OP_FILL, 0);
- }
- else if (command == vm_commands_sym_exit || command == vm_commands_sym_quit)
- {
- DEBUG("emitting exit command");
- vm_bytecode_emit(OP_EXIT, 0);
- }
- else if (command == vm_commands_sym_fill)
- {
- DEBUG("emitting exit command");
- TRY(vm_emit_exp()); // the color to fill the screen with
- vm_bytecode_emit(OP_FILL, 0);
- }
- else if (did_read_all_input())
- {
- DEBUG("Printing symbol value");
- if(command == NULL) return 1;
- i64 var = vm_symbol_get_var(command);
- if(0 > var) return 1;
- console_newline();
- console_print_i64(vm_vars[var]);
- }
- else
- {
- console_error("Unrecognized command");
- return 1;
- }
-
- return 0;
-}
-
-void vm_load_cmd()
-{
- vm_commands_selected = NULL;
- if(vm_command_text_buffer_cursor == 0) return;
-
- DEBUGS(vm_command_text_buffer);
-
- i64 cmd_num = read_uint();
- u64* command = read_sym();
- if (command == vm_commands_sym_run)
- {
- vm_commands_selected = vm_commands_root;
- return;
- }
- DEBUG("cmd_num\n");
- DEBUGI(cmd_num);
- DEBUG("");
- u64** cmd;
- if(cmd_num < 0)
- {
- DEBUG("Creating disposable cmd");
- cmd = vm_commands_alloc(cmd_num);
- }
- else
- {
- DEBUG("Retrieving cmd");
- cmd = vm_command_create(cmd_num);
- }
-
- if(cmd == NULL)
- {
- DEBUG("Failed to allocate command");
- return;
- }
-
- vm_commands_selected = cmd;
-
- if(command == NULL) command = read_sym();
-
- if(vm_emit_cmd(command))
- {
- if(cmd_num >= 0) vm_command_create(cmd_num);
- else vm_command_free(cmd);
- vm_commands_selected = NULL;
- }
- else if (cmd_num >= 0)
- {
- vm_commands_selected = NULL;
- }
- vm_command_text_buffer_clear();
-}
-
-i64 vm_pop()
-{
- i64 val = vm_stack[--vm_stack_cursor];
- vm_stack[vm_stack_cursor] = 0;
- return val;
-}
-
-void vm_push(i64 v)
-{
- vm_stack[vm_stack_cursor] = v;
- ++vm_stack_cursor;
-}
-
-#define BIN_OP(opcode, c_op) \
- else if (op == opcode) \
-{ \
- val2 = vm_pop(); \
- val1 = vm_pop(); \
- DEBUGS("OP: "); \
- DEBUGI(op); \
- DEBUGS("\n"); \
- DEBUGS("val1: "); \
- DEBUGI(val1); \
- DEBUGS("\n"); \
- DEBUGS("val2: "); \
- DEBUGI(val2); \
- DEBUGS("\n"); \
- i64 result = val1 c_op val2; \
- DEBUGS("result: "); \
- DEBUGI(result); \
- DEBUGS("\n"); \
- vm_push(result); \
-}
-
-#define PRINT_OP(op_str, op_name) else if(op == op_name) \
-{ \
- puts(op_str); \
-}
-
-void print_inst(u64 inst)
-{
- u8 op = (u8)(inst >> 56);
- u64 arg = (inst & (((u64)1<<56) - 1));
- puts("ints:");
- print_i64(inst);
- puts("\n");
- puts("op:");
- print_i64(op);
- puts("\n");
- puts("name:");
- if(0){}
- PRINT_OP("OP_PUSH", OP_PUSH)
- PRINT_OP("OP_SET_VAR", OP_SET_VAR)
- PRINT_OP("OP_GET_VAR", OP_GET_VAR)
- PRINT_OP("OP_JUMP", OP_JUMP)
- PRINT_OP("OP_JUMP_IF", OP_JUMP_IF)
- PRINT_OP("OP_JUMP_IF_NOT", OP_JUMP_IF_NOT)
- PRINT_OP("OP_ADD", OP_ADD)
- PRINT_OP("OP_SUB", OP_SUB)
- PRINT_OP("OP_MULT", OP_MULT)
- PRINT_OP("OP_DIV", OP_DIV)
- PRINT_OP("OP_GT", OP_GT)
- PRINT_OP("OP_LT", OP_LT)
- PRINT_OP("OP_GT_EQ", OP_GT_EQ)
- PRINT_OP("OP_LT_EQ", OP_LT_EQ)
- PRINT_OP("OP_EQ", OP_EQ)
- PRINT_OP("OP_NOT_EQ", OP_NOT_EQ)
- PRINT_OP("OP_PRINT_INT", OP_PRINT_INT)
- PRINT_OP("OP_GOTO", OP_GOTO)
- PRINT_OP("OP_HALT", OP_HALT)
- PRINT_OP("OP_FILL", OP_FILL)
- PRINT_OP("OP_EXIT", OP_EXIT)
-
- puts("\n");
- puts("arg:");
- print_i64(arg);
- puts("\n");
-}
-
-void print_insts(u64** cmd)
-{
- u64* cmd_insts = cmd[VM_COMMANDS_INSTS_BUFFER];
- u64 cmd_size = (u64)cmd[VM_COMMANDS_CUR];
- puts("INSTRUCTION PRINTOUT \n");
- for(size_t inst_idx = 0; (inst_idx < cmd_size); ++inst_idx)
- {
- puts("--------------\n");
- u64 inst = cmd_insts[inst_idx];
- puts("idx:");
- print_i64(inst_idx);
- puts("\n");
- print_inst(inst);
- }
-
- puts("-- END INSTRUCTION PRINTOUT");
-}
-
-void vm_hand_control_back()
-{
- vm_commands_selected = NULL;
- vm_status = VM_STATUS_DONE;
- console_print_ready();
- min_line_idx = line_idx;
- console_redraw_commit();
-}
-
-void vm_exec()
-{
- if(vm_commands_selected == NULL || vm_status == VM_STATUS_HALT)
- {
- vm_hand_control_back();
- return;
- }
-
- vm_status = VM_STATUS_RUNNING;
- i64 val1;
- i64 val2;
- u64 sleep = 0;
- u64** cmd = vm_commands_selected;
- vm_commands_selected = (u64**) cmd[VM_COMMANDS_NEXT];
- u64 cur_cmd_num = (u64)cmd[VM_COMMANDS_NUM];
-
- u64* cmd_insts = cmd[VM_COMMANDS_INSTS_BUFFER];
- u64 cmd_size = (u64)cmd[VM_COMMANDS_CUR];
-
- for(size_t inst_idx = 0; (inst_idx < cmd_size);)
- {
- u64 inst = cmd_insts[inst_idx];
- u8 op = (u8)(inst >> 56);
- u64 arg = (inst & (((u64)1<<56) - 1));
-
- // TODO check for overflow
- if(op == OP_PUSH) vm_push(arg);
- else if (op == OP_SET_VAR) vm_vars[arg] = vm_pop();
- else if (op == OP_GET_VAR) vm_push(vm_vars[arg]);
- else if (op == OP_PRINT_INT)
- {
- console_newline();
- console_print_i64(vm_pop());
- }
- else if (op == OP_GOTO) vm_commands_selected = vm_command_find(vm_pop());
- else if (op == OP_RAND) vm_push(rand());
- else if (op == OP_PRINT_STR)
- {
- u64* str = (u64)vm_vars[arg];
- console_newline();
- console_puts(str[SYM_META_STR]);
- break;
- }
- else if (op == OP_SLEEP)
- {
- sleep = vm_pop();
- break;
- }
- else if (op == OP_HALT)
- {
- vm_hand_control_back();
- return;
- }
- else if (op == OP_HELP_ALL)
- {
- console_newline();
- console_puts("Commands: ");
- console_newline();
- console_puts("LET GOTO IF PLOT LINE");
- console_newline();
- console_puts("CLEAR FILL RUN HALT EXIT SLEEP PRINT");
- console_newline();
- console_newline();
- console_puts("Builtin functions:");
- console_newline();
- console_puts("RAND");
- console_newline();
- console_newline();
- console_puts("Builtin variables:");
- console_newline();
- console_puts("BLUE RED GREEN BLACK");
- console_newline();
- console_puts("WHITE");
- console_newline();
- console_puts("CWIDTH(canvas width)");
- console_newline();
- console_puts("CHEIGHT(canvas height)");
- console_newline();
- console_newline();
- console_puts("Canvas size: ");
- console_print_i64(canvas_width);
- console_puts("X");
- console_print_i64(canvas_height);
- console_newline();
- console_newline();
- console_puts("Use HELP {command or function} for more information");
- }
- else if (op == OP_EXIT)
- {
- DEBUG("Exiting the program");
- exit(0);
- }
- else if (op == OP_FILL)
- {
- DEBUG("Filling the screen color");
- u32 color = vm_pop();
- canvas_fill(color);
- console_redraw_all_text();
- }
- else if (op == OP_JUMP)
- {
- if (arg > cmd_size) break;
- DEBUG("Unconditionally jumping");
- inst_idx = arg;
- continue;
- }
- else if (op == OP_JUMP_IF_NOT)
- {
- DEBUG("EXECUTING OP_JUMP_IF_NOT");
- if (arg > cmd_size) break;
- if(!vm_pop())
- {
- DEBUG("DETECTED A NOT SO jumping");
- inst_idx = arg;
- continue;
- }
- }
- else if (op == OP_PLOT)
- {
- u64 color = vm_pop();
- u64 y = vm_pop();
- u64 x = vm_pop();
- canvas_plot(x, y, color);
- }
- else if (op == OP_LINE)
- {
- u32 color = (u32)vm_pop();
- u32 y1 = (u32)vm_pop();
- u32 x1 = (u32)vm_pop();
- u32 y0 = (u32)vm_pop();
- u32 x0 = (u32)vm_pop();
- if(canvas_coord_gaurd(x0, y0)) break;
- if(canvas_coord_gaurd(x1, y1)) break;
- draw_line((u32*)frame_buffer, FRAME_WIDTH, FRAME_HEIGHT, console_width + x0, y0, console_width + x1, y1, color);
- }
- BIN_OP(OP_ADD, +)
- BIN_OP(OP_SUB, -)
- BIN_OP(OP_MULT, *)
- BIN_OP(OP_DIV, /)
- BIN_OP(OP_GT, >)
- BIN_OP(OP_LT, <)
- BIN_OP(OP_GT_EQ, >=)
- BIN_OP(OP_LT_EQ, <=)
- BIN_OP(OP_EQ, ==)
- BIN_OP(OP_NOT_EQ, !=)
- BIN_OP(OP_MOD, %)
- else
- {
- DEBUG("unrecognized command");
- vm_hand_control_back();
- return;
- }
- DEBUG("executing inst");
- ++inst_idx;
- }
-
- time_delay_cb(sleep, vm_exec);
-}
diff --git a/ncc/examples/chess.c b/ncc/examples/chess.c
index 14477c22..a91264c6 100644
--- a/ncc/examples/chess.c
+++ b/ncc/examples/chess.c
@@ -7,7 +7,7 @@
//
#include
-#include
+#include
#include
#include
#include
@@ -429,7 +429,7 @@ void computer_callback()
X(0,0,0,21,u,1);
}
-void anim_callback()
+void draw_board()
{
if (left_key) {
left_key = false;
@@ -473,7 +473,10 @@ void anim_callback()
X(0,0,0,21,u,1);
selected = 0;
if (y != prev_turn) {
- time_delay_cb(100, computer_callback);
+ // Draw the newly moved piece
+ draw_board();
+ // Let the computer do its move
+ computer_callback();
}
}
}
@@ -566,10 +569,9 @@ void anim_callback()
}
window_draw_frame(0, frame_buffer);
- time_delay_cb(33, anim_callback);
}
-void keydown(u64 window_id, u16 keycode)
+void keydown(u16 keycode)
{
if (keycode == KEY_LEFT)
{
@@ -593,7 +595,7 @@ void keydown(u64 window_id, u16 keycode)
}
}
-void keyup(u64 window_id, u16 keycode)
+void keyup(u16 keycode)
{
if (keycode == KEY_LEFT)
{
@@ -617,15 +619,35 @@ void keyup(u64 window_id, u16 keycode)
}
}
+Event event;
+
void main()
{
init();
window_create(FRAME_WIDTH, FRAME_HEIGHT, "Toledo Nanochess for UVM", 0);
- window_on_keydown(0, keydown);
- window_on_keyup(0, keyup);
- time_delay_cb(0, anim_callback);
+ draw_board();
+
+ for (;;)
+ {
+ window_wait_event(&event);
- enable_event_loop();
+ if (event.kind == EVENT_QUIT)
+ {
+ exit(0);
+ }
+
+ if (event.kind == EVENT_KEYDOWN)
+ {
+ keydown(event.key);
+ }
+
+ if (event.kind == EVENT_KEYUP)
+ {
+ keyup(event.key);
+ }
+
+ draw_board();
+ }
}
diff --git a/ncc/examples/counter.c b/ncc/examples/counter.c
index d68456bf..6944caec 100644
--- a/ncc/examples/counter.c
+++ b/ncc/examples/counter.c
@@ -1,6 +1,6 @@
#include
#include
-#include
+#include
#define FRAME_WIDTH 800
#define FRAME_HEIGHT 600
@@ -217,7 +217,7 @@ void draw_number(int xmax, int ymin, int dot_size, int number)
}
}
-void anim_callback()
+void update()
{
// Clear the screen
memset(frame_buffer, 0, sizeof(frame_buffer));
@@ -228,7 +228,6 @@ void anim_callback()
draw_number(500, 200, 10, (int)seconds);
window_draw_frame(0, frame_buffer);
- time_delay_cb(25, anim_callback);
}
void main()
@@ -239,6 +238,5 @@ void main()
window_create(FRAME_WIDTH, FRAME_HEIGHT, "Counter", 0);
- time_delay_cb(0, anim_callback);
- enable_event_loop();
+ anim_event_loop(40, update);
}
diff --git a/ncc/examples/fire.c b/ncc/examples/fire.c
index 4a3af158..c06611f9 100644
--- a/ncc/examples/fire.c
+++ b/ncc/examples/fire.c
@@ -3,8 +3,8 @@
// https://lodev.org/cgtutor/fire.html
#include
-#include
#include
+#include
#include
#include
#include
@@ -67,10 +67,8 @@ u32 hsl_to_rgb(float h, float s, float l)
}
}
-void anim_callback()
+void update()
{
- u64 frame_start_time = time_current_ms();
-
// Clear the frame buffer, set all pixels to black
memset32(frame_buffer, 0, sizeof(frame_buffer) / 4);
@@ -107,17 +105,6 @@ void anim_callback()
}
window_draw_frame(0, frame_buffer);
-
- // Schedule a fixed rate update for the next frame (40fps)
- fixed_rate_update(frame_start_time, 1000 / 40, anim_callback);
-}
-
-void keydown(u64 window_id, u16 keycode)
-{
- if (keycode == KEY_ESCAPE)
- {
- exit(0);
- }
}
void main()
@@ -135,9 +122,6 @@ void main()
}
window_create(FRAME_WIDTH, FRAME_HEIGHT, "Demoscene Fire Effect", 0);
- window_on_keydown(0, keydown);
-
- time_delay_cb(0, anim_callback);
- enable_event_loop();
+ anim_event_loop(40, update);
}
diff --git a/ncc/examples/gameoflife.c b/ncc/examples/gameoflife.c
index ea9decbc..6a40efb5 100644
--- a/ncc/examples/gameoflife.c
+++ b/ncc/examples/gameoflife.c
@@ -1,5 +1,6 @@
#include
#include
+#include
#include
#include
@@ -103,14 +104,9 @@ void update()
window_draw_frame(0, frame_buffer);
}
-void anim_callback()
+void bench_update()
{
- u64 start_time = time_current_ms();
-
benchmark(update());
-
- // Schedule a fixed rate update for the next frame (20fps)
- fixed_rate_update(start_time, 1000 / 20, anim_callback);
}
void main()
@@ -127,6 +123,5 @@ void main()
}
}
- time_delay_cb(0, anim_callback);
- enable_event_loop();
+ anim_event_loop(20, bench_update);
}
diff --git a/ncc/examples/mini_bbs.c b/ncc/examples/mini_bbs.c.disabled
similarity index 100%
rename from ncc/examples/mini_bbs.c
rename to ncc/examples/mini_bbs.c.disabled
diff --git a/ncc/examples/monogram.c b/ncc/examples/monogram.c
index 5bb22dcc..3ede7cb7 100644
--- a/ncc/examples/monogram.c
+++ b/ncc/examples/monogram.c
@@ -2,7 +2,7 @@
// Monogram 12x7 pixel font
#include
-#include
+#include
// > # MONOGRAM FONT
@@ -585,7 +585,7 @@ u32 FRAME_HEIGHT = 400;
u32 frame_buffer[161600];
-void anim_callback()
+void draw()
{
u8 scale = 2;
@@ -620,15 +620,12 @@ void anim_callback()
}
window_draw_frame(0, frame_buffer);
- time_delay_cb(10, anim_callback);
}
void main()
{
window_create(FRAME_WIDTH, FRAME_HEIGHT, "Monogram Font Example", 0);
- time_delay_cb(0, anim_callback);
-
- enable_event_loop();
+ anim_event_loop(10, draw);
}
diff --git a/ncc/examples/monogram.py b/ncc/examples/monogram.py
index d1be8a6e..fb664d60 100644
--- a/ncc/examples/monogram.py
+++ b/ncc/examples/monogram.py
@@ -3,7 +3,7 @@
// Monogram 12x7 pixel font
#include
-#include
+#include
'''
@@ -508,7 +508,7 @@ def make_bytes(ch):
u32 frame_buffer[{202 * 200 * SCALE * SCALE}];
-void anim_callback()
+void draw()
{{
u8 scale = {SCALE};
@@ -543,15 +543,12 @@ def make_bytes(ch):
}}
window_draw_frame(0, frame_buffer);
- time_delay_cb(10, anim_callback);
}}
void main()
{{
window_create(FRAME_WIDTH, FRAME_HEIGHT, "Monogram Font Example", 0);
- time_delay_cb(0, anim_callback);
-
- enable_event_loop();
+ anim_event_loop(10, draw);
}}
''')
diff --git a/ncc/examples/paint.c b/ncc/examples/paint.c
index 6f809e5f..fc73979f 100644
--- a/ncc/examples/paint.c
+++ b/ncc/examples/paint.c
@@ -1,5 +1,5 @@
#include
-#include
+#include
#include
#define FRAME_WIDTH 800
@@ -120,7 +120,7 @@ void draw_palette()
}
// Mouve movement callback
-void mousemove(u64 window_id, int new_x, int new_y)
+void mousemove(int new_x, int new_y)
{
if (drawing)
{
@@ -148,7 +148,7 @@ void mousemove(u64 window_id, int new_x, int new_y)
window_draw_frame(0, frame_buffer);
}
-void mousedown(u64 window_id, u8 btn_id)
+void mousedown(u16 btn_id)
{
if (btn_id == 0)
{
@@ -167,7 +167,7 @@ void mousedown(u64 window_id, u8 btn_id)
window_draw_frame(0, frame_buffer);
}
-void mouseup(u64 window_id, u8 btn_id)
+void mouseup(u16 btn_id)
{
if (btn_id == 0)
{
@@ -175,6 +175,8 @@ void mouseup(u64 window_id, u8 btn_id)
}
}
+Event event;
+
void main()
{
window_create(FRAME_WIDTH, FRAME_HEIGHT, "UVM Paint Program Example", 0);
@@ -193,12 +195,30 @@ void main()
draw_palette();
- // Register mouse event callbacks
- window_on_mousemove(0, mousemove);
- window_on_mousedown(0, mousedown);
- window_on_mouseup(0, mouseup);
-
window_draw_frame(0, frame_buffer);
- enable_event_loop();
+ for (;;)
+ {
+ window_wait_event(&event);
+
+ if (event.kind == EVENT_QUIT)
+ {
+ exit(0);
+ }
+
+ if (event.kind == EVENT_MOUSEDOWN)
+ {
+ mousedown(event.button);
+ }
+
+ if (event.kind == EVENT_MOUSEUP)
+ {
+ mouseup(event.button);
+ }
+
+ if (event.kind == EVENT_MOUSEMOVE)
+ {
+ mousemove(event.x, event.y);
+ }
+ }
}
diff --git a/ncc/examples/plasma.c b/ncc/examples/plasma.c
index 04d9e54c..21f09202 100644
--- a/ncc/examples/plasma.c
+++ b/ncc/examples/plasma.c
@@ -3,7 +3,7 @@
// https://lodev.org/cgtutor/plasma.html
#include
-#include
+#include
#include
#include
#include
@@ -63,7 +63,7 @@ u32 hsv_to_rgb(float h, float s, float v)
return rgb32(vi, p, q);
}
-void anim_callback()
+void update()
{
u64 frame_start_time = time_current_ms();
int time_ms_i = (int)(frame_start_time - prog_start_time);
@@ -82,17 +82,6 @@ void anim_callback()
}
window_draw_frame(0, frame_buffer);
-
- // Schedule a fixed rate update for the next frame (40fps)
- fixed_rate_update(frame_start_time, 1000 / 40, anim_callback);
-}
-
-void keydown(u64 window_id, u16 keycode)
-{
- if (keycode == KEY_ESCAPE)
- {
- exit(0);
- }
}
void main()
@@ -130,9 +119,6 @@ void main()
}
window_create(FRAME_WIDTH, FRAME_HEIGHT, "Demoscene Plasma Effect", 0);
- window_on_keydown(0, keydown);
-
- time_delay_cb(0, anim_callback);
- enable_event_loop();
+ anim_event_loop(40, update);
}
diff --git a/ncc/examples/raycaster.c b/ncc/examples/raycaster.c
index c250927f..2564af97 100644
--- a/ncc/examples/raycaster.c
+++ b/ncc/examples/raycaster.c
@@ -1,5 +1,5 @@
#include
-#include
+#include
#include
#include
#include
@@ -212,7 +212,7 @@ void paint_column(int col_idx, float dx, float dy, float frame_dst, float ray_ds
}
}
-void anim_callback()
+void draw_frame()
{
u64 frame_start_time = time_current_ms();
@@ -340,12 +340,9 @@ void anim_callback()
printf("render time %d ms\n", frame_end_time - frame_start_time);
window_draw_frame(0, frame_buffer);
-
- // Schedule a fixed rate update for the next frame (30fps)
- fixed_rate_update(frame_start_time, 1000 / 30, anim_callback);
}
-void keydown(u64 window_id, u16 keycode)
+void keydown(u16 keycode)
{
if (keycode == KEY_ESCAPE)
{
@@ -385,12 +382,27 @@ void keydown(u64 window_id, u16 keycode)
}
}
+Event event;
+
void main()
{
window_create(FRAME_WIDTH, FRAME_HEIGHT, "Ray-Casting Example", 0);
- window_on_keydown(0, keydown);
- time_delay_cb(0, anim_callback);
+ for (;;)
+ {
+ while (window_poll_event(&event))
+ {
+ if (event.kind == EVENT_QUIT)
+ {
+ exit(0);
+ }
+
+ if (event.kind == EVENT_KEYDOWN)
+ {
+ keydown(event.key);
+ }
+ }
- enable_event_loop();
+ draw_frame();
+ }
}
diff --git a/ncc/examples/sawtooth_notes.c b/ncc/examples/sawtooth_notes.c
index 5f9c7895..a5ccc8b9 100644
--- a/ncc/examples/sawtooth_notes.c
+++ b/ncc/examples/sawtooth_notes.c
@@ -1,5 +1,4 @@
#include
-#include
#include
#include
@@ -24,11 +23,6 @@ size_t sample_idx = 0;
// Oscillator phase
float phase = 0.0f;
-void anim_callback()
-{
- exit(0);
-}
-
u16* audio_cb(u16 num_channels, u32 num_samples)
{
assert(num_channels == 1);
@@ -67,9 +61,8 @@ u16* audio_cb(u16 num_channels, u32 num_samples)
void main()
{
- time_delay_cb(8_000, anim_callback);
-
audio_open_output(44100, 1, AUDIO_FORMAT_I16, audio_cb);
- enable_event_loop();
+ // Keep the program running until audio is done playing
+ thread_sleep(8000);
}
diff --git a/ncc/examples/sequencer.c b/ncc/examples/sequencer.c
index 996e0a07..d866196d 100644
--- a/ncc/examples/sequencer.c
+++ b/ncc/examples/sequencer.c
@@ -1,5 +1,5 @@
#include
-#include
+#include
#include
#include
#include
@@ -30,6 +30,7 @@ u32 step_idx = 0;
u32 sample_idx = 0;
// Frequencies for musical notes on the pentatonic scale
+// Notes at the top (lowest index) have the highest frequency
float NOTE_FREQS[6] = {
330.0f,
294.0f,
@@ -85,13 +86,15 @@ u16* audio_cb(u16 num_channels, u32 num_samples)
u64 steps_per_sec = beats_per_sec * steps_per_beat;
u64 samples_per_step = 44100 / steps_per_sec;
- // For each sample to generate
- for (int i = 0; i < num_samples; ++i)
+ // For each sample to write in the audio buffer
+ for (int buf_idx = 0; buf_idx < num_samples; ++buf_idx)
{
float out = 0.0f;
+ // For each row of the sequencer
for (int j = 0; j < NUM_ROWS; ++j)
{
+ // If there is no note at this position
if (!grid[j][step_idx])
continue;
@@ -104,15 +107,16 @@ u16* audio_cb(u16 num_channels, u32 num_samples)
// Use a square wave for a retro sound
float osc_val = (cycle_pos < 0.5f)? 1.0f:-1.0f;
- // Convert the output to signed 16-bit i16 samples
out = out + osc_val * 0.3f;
}
+ // Decay envelope
float env = 1.0f - (float)(i32)sample_idx / 12000.0f;
if (env < 0.0f)
env = 0.0f;
- audio_buffer[i] = (i16)(5000.0f * out * env);
+ // Convert the output to signed 16-bit i16 samples
+ audio_buffer[buf_idx] = (i16)(5000.0f * out * env);
sample_idx = sample_idx + 1;
@@ -122,17 +126,15 @@ u16* audio_cb(u16 num_channels, u32 num_samples)
// Move to the next step
step_idx = (step_idx + 1) % NUM_STEPS;
sample_idx = 0;
-
- // Schedule redrawing as soon as we are done generating audio
- time_delay_cb(0, redraw);
}
}
- return audio_buffer;
+ return audio_buffer;
}
-void mousedown(u64 window_id, u8 btn_id, i32 x, i32 y)
+void mousedown(u8 btn_id, i32 x, i32 y)
{
+ // Only handle left clicks
if (btn_id != 0)
{
return;
@@ -154,23 +156,37 @@ void mousedown(u64 window_id, u8 btn_id, i32 x, i32 y)
redraw();
}
-void keydown(u64 window_id, u16 keycode)
-{
- if (keycode == KEY_ESCAPE)
- {
- exit(0);
- }
-}
+Event event;
void main()
{
window_create(FRAME_WIDTH, FRAME_HEIGHT, "Pentatonic Sequencer", 0);
- window_on_keydown(0, keydown);
- window_on_mousedown(0, mousedown);
- enable_event_loop();
+ audio_open_output(44100, 1, AUDIO_FORMAT_I16, audio_cb);
redraw();
- audio_open_output(44100, 1, AUDIO_FORMAT_I16, audio_cb);
+ for (;;)
+ {
+ while (window_poll_event(&event))
+ {
+ if (event.kind == EVENT_QUIT)
+ {
+ exit(0);
+ }
+
+ if (event.kind == EVENT_KEYDOWN && event.key == KEY_ESCAPE)
+ {
+ exit(0);
+ }
+
+ if (event.kind == EVENT_MOUSEDOWN)
+ {
+ mousedown(event.button, event.x, event.y);
+ }
+ }
+
+ thread_sleep(25);
+ redraw();
+ }
}
diff --git a/ncc/examples/snake.c b/ncc/examples/snake.c
index ecaec0ee..dcdbc781 100644
--- a/ncc/examples/snake.c
+++ b/ncc/examples/snake.c
@@ -1,5 +1,6 @@
#include
#include
+#include
#include
#include
@@ -95,10 +96,8 @@ void spawn_apple()
}
}
-void anim_callback()
+void update()
{
- u64 start_time = time_current_ms();
-
// Move the snake body forward
// We do this from tail to head
for (int i = snake_len - 1; i > 0; i = i - 1)
@@ -175,53 +174,71 @@ void anim_callback()
window_draw_frame(0, frame_buffer);
- // Schedule a fixed rate update for the next frame
- fixed_rate_update(start_time, 100, anim_callback);
+ thread_sleep(100);
}
-void keydown(u64 window_id, u16 keycode)
-{
- int sdx = snake_xs[1] - snake_xs[0];
- int sdy = snake_ys[1] - snake_ys[0];
+Event event;
- if (keycode == KEY_ESCAPE)
+void read_keys()
+{
+ while (window_poll_event(&event))
{
- exit(0);
- }
+ if (event.kind == EVENT_QUIT)
+ {
+ exit(0);
+ }
- if (keycode == KEY_LEFT && sdx != -1)
- {
- dx = -1;
- dy = 0;
- }
- else if (keycode == KEY_RIGHT && sdx != 1)
- {
- dx = 1;
- dy = 0;
- }
- else if (keycode == KEY_UP && sdy != -1)
- {
- dx = 0;
- dy = -1;
- }
- else if (keycode == KEY_DOWN && sdy != 1)
- {
- dx = 0;
- dy = 1;
+ if (event.kind != EVENT_KEYDOWN)
+ {
+ continue;
+ }
+
+ // Current snake x/y direction
+ int sdx = snake_xs[1] - snake_xs[0];
+ int sdy = snake_ys[1] - snake_ys[0];
+
+ if (event.key == KEY_ESCAPE)
+ {
+ exit(0);
+ }
+
+ if (event.key == KEY_LEFT && sdx != -1)
+ {
+ dx = -1;
+ dy = 0;
+ }
+ else if (event.key == KEY_RIGHT && sdx != 1)
+ {
+ dx = 1;
+ dy = 0;
+ }
+ else if (event.key == KEY_UP && sdy != -1)
+ {
+ dx = 0;
+ dy = -1;
+ }
+ else if (event.key == KEY_DOWN && sdy != 1)
+ {
+ dx = 0;
+ dy = 1;
+ }
}
}
void main()
{
window_create(FRAME_WIDTH, FRAME_HEIGHT, "Snake Game Example", 0);
- window_on_keydown(0, keydown);
+ // Initialize the snake positions
for (int i = 0; i < snake_len; ++i)
{
snake_xs[i] = GRID_WIDTH / 2;
snake_ys[i] = (GRID_HEIGHT / 4) - i;
}
- time_delay_cb(0, anim_callback);
- enable_event_loop();
+ for (;;)
+ {
+ read_keys();
+ update();
+ }
}
diff --git a/ncc/examples/telnet_server.c b/ncc/examples/telnet_server.c.disabled
similarity index 100%
rename from ncc/examples/telnet_server.c
rename to ncc/examples/telnet_server.c.disabled
diff --git a/ncc/examples/textedit.c b/ncc/examples/textedit.c
index ce3cf9aa..67345069 100644
--- a/ncc/examples/textedit.c
+++ b/ncc/examples/textedit.c
@@ -1,6 +1,8 @@
#include
+#include
#include
#include
+#include
#include
#include
@@ -30,7 +32,7 @@ size_t row_len(size_t row_idx)
return NUM_COLS;
}
-void textinput(u64 window_id, char ch)
+void textinput(char ch)
{
//print_i64(ch);
//print_endl();
@@ -50,7 +52,7 @@ void textinput(u64 window_id, char ch)
redraw();
}
-void keydown(u64 window_id, u16 keycode)
+void keydown(u16 keycode)
{
if (keycode == KEY_ESCAPE)
{
@@ -131,12 +133,7 @@ void redraw()
window_draw_frame(0, frame_buffer);
}
-void anim_callback()
-{
- benchmark(redraw());
-
- time_delay_cb(400, anim_callback);
-}
+Event event;
void main()
{
@@ -144,12 +141,33 @@ void main()
redraw();
- window_on_keydown(0, keydown);
- window_on_textinput(0, textinput);
+ for (;;)
+ {
+ while (window_poll_event(&event))
+ {
+ if (event.kind == EVENT_QUIT)
+ {
+ exit(0);
+ }
+
+ if (event.kind == EVENT_KEYDOWN)
+ {
+ keydown(event.key);
+ }
- time_delay_cb(0, anim_callback);
+ if (event.kind == EVENT_TEXTINPUT)
+ {
+ size_t len = strlen(event.text);
+ for (size_t i = 0; i < len; ++i)
+ {
+ textinput(event.text[i]);
+ }
+ }
+ }
- enable_event_loop();
+ benchmark(redraw());
+ thread_sleep(20);
+ }
}
//===========================================================================
diff --git a/ncc/examples/thegrid.c b/ncc/examples/thegrid.c
index 9a0fe9f4..60388ee0 100644
--- a/ncc/examples/thegrid.c
+++ b/ncc/examples/thegrid.c
@@ -1,7 +1,7 @@
#include
#include
#include
-#include
+#include
#include
#include
#include
@@ -92,7 +92,7 @@ u32 hsl_to_rgb(float h, float s, float l)
float line_pos = 1.0f;
float anim_time = 0.0f;
-void anim_callback()
+void update()
{
u64 start_time = time_current_ms();
@@ -153,25 +153,12 @@ void anim_callback()
u64 end_time = time_current_ms();
printf("render time: %dms\n", end_time - start_time);
- // Schedule a fixed rate update for the next frame (60fps)
- fixed_rate_update(start_time, 1000 / 60, anim_callback);
anim_time = anim_time + (1 / 60.0f);
}
-void keydown(u64 window_id, u16 keycode)
-{
- if (keycode == KEY_ESCAPE)
- {
- exit(0);
- }
-}
-
int main()
{
window_create(FRAME_WIDTH, FRAME_HEIGHT, "The Grid", 0);
- window_on_keydown(0, keydown);
- time_delay_cb(0, anim_callback);
- enable_event_loop();
// Set up the perspective projection matrix
perspective(
@@ -182,5 +169,7 @@ int main()
persp
);
+ anim_event_loop(60, update);
+
return 0;
}
diff --git a/ncc/examples/wu_lines.c b/ncc/examples/wu_lines.c
index 3bc70a41..5027bd16 100644
--- a/ncc/examples/wu_lines.c
+++ b/ncc/examples/wu_lines.c
@@ -2,7 +2,7 @@
#include
#include
#include
-#include
+#include
size_t FRAME_WIDTH = 400;
size_t FRAME_HEIGHT = 400;
@@ -296,14 +296,7 @@ void draw_wu_line_fourth_octant(u32* fb, u32 fb_width, u32 fb_height, u32 x0, u3
}
}
-void mousemove(u64 window_id, u64 new_x, u64 new_y)
-{
- // Update the mouse position
- pos_x = new_x;
- pos_y = new_y;
-}
-
-void anim_callback()
+void draw()
{
// Grey background.
memset(frame_buffer, 0x7f, sizeof(frame_buffer));
@@ -320,17 +313,32 @@ void anim_callback()
draw_wu_line(frame_buffer, FRAME_WIDTH, FRAME_HEIGHT, h - pos_y, pos_x, w, 0, COLOR_YELLOW );
window_draw_frame(0, frame_buffer);
-
- time_delay_cb(10, anim_callback);
}
+Event event;
+
void main()
{
window_create(FRAME_WIDTH, FRAME_HEIGHT, "Wu Anti-Aliased Line Example", 0);
- window_on_mousemove(0, mousemove);
+ draw();
- time_delay_cb(0, anim_callback);
+ for (;;)
+ {
+ window_wait_event(&event);
- enable_event_loop();
+ if (event.kind == EVENT_QUIT || (event.kind == EVENT_KEYDOWN && event.key == KEY_ESCAPE))
+ {
+ exit(0);
+ }
+
+ if (event.kind == EVENT_MOUSEMOVE)
+ {
+ // Update the mouse position
+ pos_x = event.x;
+ pos_y = event.y;
+ }
+
+ draw();
+ }
}
diff --git a/ncc/include/assert.h b/ncc/include/assert.h
index d63b3be4..3d5c8244 100644
--- a/ncc/include/assert.h
+++ b/ncc/include/assert.h
@@ -11,7 +11,7 @@
asm () -> void { syscall print_endl; };\
asm ("assert(" #test_expr ")") -> void { syscall print_str; };\
asm () -> void { syscall print_endl; };\
- asm () -> void { push -1; exit; };\
+ asm () -> void { push -1; syscall exit; };\
}
#else
#define assert(test_val) {}
diff --git a/ncc/include/stdio.h b/ncc/include/stdio.h
index 348e3ccd..14375652 100644
--- a/ncc/include/stdio.h
+++ b/ncc/include/stdio.h
@@ -26,9 +26,6 @@ int getchar()
}
#endif
-// Internal buffer used by printf
-char* __buffer[32];
-
int printf(char* format, ...)
{
unsigned int ch_written = 0;
diff --git a/ncc/include/stdlib.h b/ncc/include/stdlib.h
index bdf72efc..1fcbcd4d 100644
--- a/ncc/include/stdlib.h
+++ b/ncc/include/stdlib.h
@@ -11,9 +11,10 @@ int abs(int n)
return n;
}
+#undef exit
void exit(int status)
{
- asm (status) -> void { exit; };
+ asm (status) -> void { syscall exit; };
}
// Convert long int to string
@@ -119,7 +120,7 @@ void* malloc(size_t size)
// Resize the heap if needed
if (__next_alloc__ > __heap_size__)
{
- __heap_size__ = asm (__next_alloc__) -> u64 { syscall vm_resize_heap; };
+ __heap_size__ = asm (__next_alloc__) -> u64 { syscall vm_grow_heap; };
}
// Write a magic word at the beginning of the block for safety checks
diff --git a/ncc/include/uvm/syscalls.h b/ncc/include/uvm/syscalls.h
index 8276da47..5b9c683f 100644
--- a/ncc/include/uvm/syscalls.h
+++ b/ncc/include/uvm/syscalls.h
@@ -25,9 +25,29 @@
// Report the current heap size in bytes.
#define vm_heap_size() asm () -> u64 { syscall vm_heap_size; }
-// u64 vm_resize_heap(u64 num_bytes)
-// Resize the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. Returns the new heap size in bytes if successful, or `UINT64_MAX` on failure.
-#define vm_resize_heap(__num_bytes) asm (__num_bytes) -> u64 { syscall vm_resize_heap; }
+// u64 vm_grow_heap(u64 num_bytes)
+// Grow the heap to a new size given in bytes. This is similar to the `brk()` system call on POSIX systems. Note that the heap may be resized to a size larger than requested. The heap size is guaranteed to be a multiple of 8 bytes. If the requested size is smaller than the current heap size, this is a no-op. Returns the new heap size in bytes.
+#define vm_grow_heap(__num_bytes) asm (__num_bytes) -> u64 { syscall vm_grow_heap; }
+
+// void exit(i8 status)
+// End program execution with the specified exit status.
+#define exit(__status) asm (__status) -> void { syscall exit; }
+
+// u64 thread_spawn(void* fptr, void* arg)
+// Spawn a new thread running the given function with the argument value `arg`.
+#define thread_spawn(__fptr, __arg) asm (__fptr, __arg) -> u64 { syscall thread_spawn; }
+
+// u64 thread_id()
+// Get the id of the current thread.
+#define thread_id() asm () -> u64 { syscall thread_id; }
+
+// void thread_sleep(u64 time_ms)
+// Make the current thread sleep for at least the given time in milliseconds.
+#define thread_sleep(__time_ms) asm (__time_ms) -> void { syscall thread_sleep; }
+
+// u64 thread_join(u64 tid)
+// Join on the thread with the given id. Produces the return value for the thread.
+#define thread_join(__tid) asm (__tid) -> u64 { syscall thread_join; }
// void print_i64(i64 val)
// Print an i64 value to standard output.
@@ -57,10 +77,6 @@
// Get the UNIX time stamp in milliseconds.
#define time_current_ms() asm () -> u64 { syscall time_current_ms; }
-// void time_delay_cb(u64 delay_ms, void* callback)
-// Schedule a callback to be called once after a given delay.
-#define time_delay_cb(__delay_ms, __callback) asm (__delay_ms, __callback) -> void { syscall time_delay_cb; }
-
// u32 window_create(u32 width, u32 height, const char* title, u64 flags)
// Create a new window with a frame buffer to draw into. The window is initially hidden when created, and will appear as soon as the first frame of image data is drawn.
#define window_create(__width, __height, __title, __flags) asm (__width, __height, __title, __flags) -> u32 { syscall window_create; }
@@ -69,34 +85,26 @@
// Copy a frame of pixels to be displayed into the window. The frame must have the same width and height as the window. The pixel format is 32 bits per pixel in BGRA byte order, with 8 bits for each component and the B byte at the lowest address.
#define window_draw_frame(__window_id, __pixel_data) asm (__window_id, __pixel_data) -> void { syscall window_draw_frame; }
-// void window_on_mousemove(u32 window_id, void* callback)
-// Register a callback for mouse movement. Mouse x/y coordinates are relative to the top-left corner of the window and may be negative if outside of the window.
-#define window_on_mousemove(__window_id, __callback) asm (__window_id, __callback) -> void { syscall window_on_mousemove; }
+// bool window_poll_event(void* p_event)
+// Try to read an event from the windowing system if available. The event is read into an event struct. Boolean true is returned if an event was read, false if not.
+#define window_poll_event(__p_event) asm (__p_event) -> bool { syscall window_poll_event; }
-// void window_on_mousedown(u32 window_id, void* callback)
-// Register a callback for mouse button press events.
-#define window_on_mousedown(__window_id, __callback) asm (__window_id, __callback) -> void { syscall window_on_mousedown; }
-
-// void window_on_mouseup(u32 window_id, void* callback)
-// Register a callback for mouse button release events.
-#define window_on_mouseup(__window_id, __callback) asm (__window_id, __callback) -> void { syscall window_on_mouseup; }
-
-// void window_on_keydown(u32 window_id, void* callback)
-// Register a callback for key press event.
-#define window_on_keydown(__window_id, __callback) asm (__window_id, __callback) -> void { syscall window_on_keydown; }
-
-// void window_on_keyup(u32 window_id, void* callback)
-// Register a callback for key release event.
-#define window_on_keyup(__window_id, __callback) asm (__window_id, __callback) -> void { syscall window_on_keyup; }
-
-// void window_on_textinput(u32 window_id, void* callback)
-// Register a callback to receive text input. The text is encoded as UTF-8 and the callback is called for each byte input.
-#define window_on_textinput(__window_id, __callback) asm (__window_id, __callback) -> void { syscall window_on_textinput; }
+// void window_wait_event(void* p_event)
+// Block until an window event is available.
+#define window_wait_event(__p_event) asm (__p_event) -> void { syscall window_wait_event; }
// u32 audio_open_output(u32 sample_rate, u16 num_channels, u16 format, void* callback)
-// Open an audio output device.
+// Open an audio output device, then spawn a new thread which will regularly call the specified callback function to generate audio samples.
#define audio_open_output(__sample_rate, __num_channels, __format, __callback) asm (__sample_rate, __num_channels, __format, __callback) -> u32 { syscall audio_open_output; }
+// u32 audio_open_input(u32 sample_rate, u16 num_channels, u16 format, void* callback)
+// Open an audio input device, then spawn a new thread which will regularly call the specified callback function to process audio samples.
+#define audio_open_input(__sample_rate, __num_channels, __format, __callback) asm (__sample_rate, __num_channels, __format, __callback) -> u32 { syscall audio_open_input; }
+
+// void audio_read_samples(i16* dst_buf, u32 num_samples)
+// Read available input samples. Must be called from the audio input thread.
+#define audio_read_samples(__dst_buf, __num_samples) asm (__dst_buf, __num_samples) -> void { syscall audio_read_samples; }
+
// u64 net_listen(const char* listen_addr, void* on_new_conn)
// Open a listening TCP socket to accept incoming connections. A callback function is called when a new connection request is received.
#define net_listen(__listen_addr, __on_new_conn) asm (__listen_addr, __on_new_conn) -> u64 { syscall net_listen; }
@@ -117,6 +125,13 @@
// Close an open socket.
#define net_close(__socket_id) asm (__socket_id) -> void { syscall net_close; }
+#define EVENT_QUIT 0
+#define EVENT_KEYDOWN 1
+#define EVENT_KEYUP 2
+#define EVENT_MOUSEDOWN 3
+#define EVENT_MOUSEUP 4
+#define EVENT_MOUSEMOVE 5
+#define EVENT_TEXTINPUT 6
#define KEY_BACKSPACE 8
#define KEY_TAB 9
#define KEY_RETURN 10
diff --git a/ncc/include/uvm/utils.h b/ncc/include/uvm/utils.h
index 770f5de1..9d89a6bb 100644
--- a/ncc/include/uvm/utils.h
+++ b/ncc/include/uvm/utils.h
@@ -3,35 +3,6 @@
#include
-// Set a hidden global variable to enable the UVM event loop
-// This will cause your program to continue running after
-// your main() function returns.
-void enable_event_loop()
-{
- asm () -> void
- {
- push __EVENT_LOOP_ENABLED__;
- push 1;
- store_u8;
- };
-}
-
-// Schedule a new update at a fixed rate
-// This takes into account the time taken by the current update
-void fixed_rate_update(u64 start_time, u64 rate_ms, void* callback)
-{
- assert(rate_ms > 0);
-
- u64 cur_time_ms = asm () -> u64 { syscall time_current_ms; };
- u64 time_taken = cur_time_ms - start_time;
-
- // Compute when to do the next update
- u64 next_update = (time_taken > rate_ms)? (u64)0:(rate_ms - time_taken);
-
- // Schedule the next update
- asm (next_update, callback) -> void { syscall time_delay_cb; };
-}
-
// Macro to benchmark an expression or statement
#ifndef benchmark
#define benchmark(expr) \
diff --git a/ncc/include/uvm/window.h b/ncc/include/uvm/window.h
new file mode 100644
index 00000000..f5df4fbd
--- /dev/null
+++ b/ncc/include/uvm/window.h
@@ -0,0 +1,59 @@
+#ifndef __WINDOW_H__
+#define __WINDOW_H__
+
+#include
+#include
+
+typedef struct
+{
+ u16 kind;
+ u16 window_id;
+ u16 key;
+ u16 button;
+ i32 x;
+ i32 y;
+ char text[64];
+} Event;
+
+// Stack allocation of structs not yet supported
+Event __event__;
+
+// Simple event loop that tries to update rendering at a fixed rate
+// until the user closes the window or presses the escape key
+void anim_event_loop(u64 max_fps, void* update_fn)
+{
+ assert(max_fps > 0);
+
+ u64 frame_time = 1000 / max_fps;
+
+ for (;;)
+ {
+ while (window_poll_event(&__event__))
+ {
+ if (__event__.kind == EVENT_QUIT)
+ {
+ return;
+ }
+
+ if (__event__.kind == EVENT_KEYDOWN && __event__.key == KEY_ESCAPE)
+ {
+ return;
+ }
+ }
+
+ u64 start_time = time_current_ms();
+
+ // Call the update function
+ asm (update_fn) -> void { call_fp 1; };
+
+ u64 end_time = time_current_ms();
+ u64 update_time = end_time - start_time;
+
+ if (update_time < frame_time)
+ {
+ thread_sleep(frame_time - update_time);
+ }
+ }
+}
+
+#endif
diff --git a/ncc/src/codegen.rs b/ncc/src/codegen.rs
index 946578ed..bdc75511 100644
--- a/ncc/src/codegen.rs
+++ b/ncc/src/codegen.rs
@@ -123,10 +123,6 @@ impl Unit
out.push_str(".u64 0xBADADD5EFEFEFEFE;\n");
out.push_str("\n");
- out.push_str("__EVENT_LOOP_ENABLED__:\n");
- out.push_str(".u8 0;\n");
- out.push_str("\n");
-
// Global variable initialization
for global in &self.global_vars {
// Align the data
@@ -169,19 +165,14 @@ impl Unit
out.push_str("# call the main function and then exit\n");
out.push_str("call main, 0;\n");
- out.push_str("push __EVENT_LOOP_ENABLED__;\n");
- out.push_str("load_u8;\n");
- out.push_str("jnz __ret_to_event_loop__;\n");
- out.push_str("exit;\n");
- out.push_str("__ret_to_event_loop__:\n");
out.push_str("ret;\n");
out.push_str("\n");
}
else
{
- // If there is no main function, the unit should exit (do nothing)
+ // If there is no main function, the unit should just exit (do nothing)
out.push_str("push 0;\n");
- out.push_str("exit;\n");
+ out.push_str("ret;\n");
out.push_str("\n");
}
@@ -641,6 +632,13 @@ impl Expr
child.gen_code(sym, out)?;
return Ok(());
}
+
+ // If this is a reference to a global
+ if let Expr::Ref(Decl::Global { name, t }) = child.as_ref() {
+ // Push the address
+ out.push_str(&format!("push {};\n", name));
+ return Ok(());
+ }
}
child.gen_code(sym, out)?;
diff --git a/ncc/src/exec_tests.rs b/ncc/src/exec_tests.rs
index 3e7fde69..113ac48f 100644
--- a/ncc/src/exec_tests.rs
+++ b/ncc/src/exec_tests.rs
@@ -56,6 +56,14 @@ fn exec_tests()
let output = command.output().unwrap();
assert!(output.status.success(), "execution failed");
+ // Compile all the tests and run them
+ for file in fs::read_dir("./tests").unwrap() {
+ let file_path = file.unwrap().path().display().to_string();
+ if file_path.ends_with(".c") {
+ compile_and_run(&file_path, true);
+ }
+ }
+
// We only run a subset of examples
// Some examples involve creating a UI window
// We parse/validate those without executing them
@@ -77,12 +85,4 @@ fn exec_tests()
compile_and_run(&file_path, run_example);
}
}
-
- // Compile all the tests and run them
- for file in fs::read_dir("./tests").unwrap() {
- let file_path = file.unwrap().path().display().to_string();
- if file_path.ends_with(".c") {
- compile_and_run(&file_path, true);
- }
- }
}
diff --git a/ncc/src/parser.rs b/ncc/src/parser.rs
index eb2a4976..38903205 100644
--- a/ncc/src/parser.rs
+++ b/ncc/src/parser.rs
@@ -39,9 +39,9 @@ fn parse_atom(input: &mut Input) -> Result
let float_val: f32 = num_str.parse().unwrap();
if !input.match_char('f') {
- return input.parse_error(&concat!("
- only floats are supported for now, ",
- "e.g. 3.5f (float), not 3.5 (double)"
+ return input.parse_error(&format!(
+ "only floats are supported for now, try {}f (float) instead of {} (double)",
+ num_str, num_str
));
}
diff --git a/ncc/src/symbols.rs b/ncc/src/symbols.rs
index 4e244bc9..166308df 100644
--- a/ncc/src/symbols.rs
+++ b/ncc/src/symbols.rs
@@ -93,6 +93,14 @@ impl Env
offset
}
+ /// Check if a local with this name is already defined
+ fn local_defined(&self, name: &str) -> bool
+ {
+ let num_scopes = self.scopes.len();
+ let top_scope = &self.scopes[num_scopes - 1];
+ top_scope.decls.get(name).is_some()
+ }
+
/// Define a new local variable in the topmost scope
fn define_local(&mut self, name: &str, var_type: Type)
{
@@ -330,6 +338,13 @@ impl Stmt
// Local variable declaration
Stmt::VarDecl { var_type, var_name, init_expr } => {
+ if env.local_defined(var_name) {
+ return ParseError::msg_only(&format!(
+ "local with name \"{}\" already exists",
+ var_name
+ ));
+ }
+
env.define_local(var_name, var_type.clone());
let decl = env.lookup(var_name).unwrap();
diff --git a/ncc/tests/graphics.c b/ncc/tests/graphics.c
index 94c542e3..606c7292 100644
--- a/ncc/tests/graphics.c
+++ b/ncc/tests/graphics.c
@@ -1,19 +1,13 @@
#include
#include
-#include
+#include
#include
#include
// Frame buffer
u32 fb[800][600];
-void keydown(u64 window_id, u16 keycode)
-{
- if (keycode == KEY_ESCAPE)
- {
- exit(0);
- }
-}
+Event event;
int main()
{
@@ -52,9 +46,13 @@ int main()
// a window so we can view the output
#ifndef TEST
window_create(800, 600, "Graphics Test", 0);
- window_on_keydown(0, keydown);
window_draw_frame(0, fb);
- enable_event_loop();
+ for (;;)
+ {
+ window_wait_event(&event);
+ if (event.kind == EVENT_KEYDOWN)
+ break;
+ }
#endif
return 0;
diff --git a/ncc/tests/threads.c b/ncc/tests/threads.c
new file mode 100644
index 00000000..08d0d5ca
--- /dev/null
+++ b/ncc/tests/threads.c
@@ -0,0 +1,17 @@
+#include
+#include
+
+u64 thread_fn(u64 arg)
+{
+ thread_sleep(5);
+ return arg + 1;
+}
+
+int main()
+{
+ u64 tid = thread_spawn(thread_fn, 7);
+ u64 ret = thread_join(tid);
+ assert(ret == 8);
+
+ return 0;
+}
diff --git a/ncc/tests/threads_lock.c b/ncc/tests/threads_lock.c
new file mode 100644
index 00000000..b12d1cd0
--- /dev/null
+++ b/ncc/tests/threads_lock.c
@@ -0,0 +1,58 @@
+#include
+#include
+
+#define NUM_THREADS 100
+#define NUM_INCRS 100
+
+u64 thread_ids[NUM_THREADS];
+
+u64 locked = 0;
+
+u64 counter = 0;
+
+void lock()
+{
+ for (;;)
+ {
+ // Try to acquire the lock
+ u64 val = asm (&locked, 0, 1) -> u64 { atomic_cas_u64; };
+
+ // If we got the lock, stop
+ if (val == 0)
+ break;
+ }
+}
+
+void unlock()
+{
+ asm (&locked, 0) -> void { atomic_store_u64; };
+}
+
+void thread_fn()
+{
+ for (int i = 0; i < NUM_INCRS; ++i)
+ {
+ lock();
+ ++counter;
+ unlock();
+ }
+}
+
+int main()
+{
+ for (int i = 0; i < NUM_THREADS; ++i)
+ {
+ thread_ids[i] = thread_spawn(thread_fn, NULL);
+ }
+
+ for (int i = 0; i < NUM_THREADS; ++i)
+ {
+ thread_join(thread_ids[i]);
+ }
+
+ // Check that the counter value is correct
+ assert(counter == NUM_THREADS * NUM_INCRS);
+ printf("counter = %d\n", counter);
+
+ return 0;
+}
diff --git a/ncc/tests/uvm_time.c b/ncc/tests/uvm_time.c
deleted file mode 100644
index fe1808c5..00000000
--- a/ncc/tests/uvm_time.c
+++ /dev/null
@@ -1,36 +0,0 @@
-#include
-#include
-#include
-#include
-
-bool cb1 = false;
-bool cb2 = false;
-
-void callback1()
-{
- if (cb2)
- exit(0);
-
- cb1 = true;
-}
-
-void callback2()
-{
- if (cb1)
- exit(0);
-
- cb2 = true;
-}
-
-void main()
-{
- u64 t0 = time_current_ms();
- u64 t1 = time_current_ms();
- assert(t1 >= t0);
-
- // This test should only terminate if both callbacks are called,
- // otherwise it will hang
- time_delay_cb(1, callback1);
- time_delay_cb(2, callback2);
- enable_event_loop();
-}
diff --git a/vm/Cargo.lock b/vm/Cargo.lock
index 0c9260a9..86543d8a 100644
--- a/vm/Cargo.lock
+++ b/vm/Cargo.lock
@@ -28,9 +28,9 @@ checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
[[package]]
name = "sdl2"
-version = "0.35.2"
+version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a"
+checksum = "3b498da7d14d1ad6c839729bd4ad6fc11d90a57583605f3b4df2cd709a9cd380"
dependencies = [
"bitflags",
"lazy_static",
@@ -40,9 +40,9 @@ dependencies = [
[[package]]
name = "sdl2-sys"
-version = "0.35.2"
+version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0"
+checksum = "951deab27af08ed9c6068b7b0d05a93c91f0a8eb16b6b816a5e73452a43521d3"
dependencies = [
"cfg-if",
"libc",
diff --git a/vm/Cargo.toml b/vm/Cargo.toml
index 61f472ed..771b17c6 100644
--- a/vm/Cargo.toml
+++ b/vm/Cargo.toml
@@ -6,8 +6,8 @@ version = "0.2.0"
edition = "2021"
[dependencies]
-sdl2 = "0.35.2"
-libc = "0.2"
+sdl2 = "0.37.0"
+libc = "0.2" # needed for mmap
[features]
count_insns = []
diff --git a/vm/examples/circle.asm b/vm/examples/circle.asm
deleted file mode 100644
index 859d1c44..00000000
--- a/vm/examples/circle.asm
+++ /dev/null
@@ -1,218 +0,0 @@
-# Data section
-.data;
-
-# 800 * 600 * 4 (RGBX)
-PIXEL_BUFFER:
-.zero 1_920_000;
-
-WINDOW_TITLE:
-.stringz "UVM Circle Animation Example";
-
-# Global x coordinate variable
-.align 8;
-X_COORD:
-.u64 0;
-
-###########################################################
-
-# Code section
-.code;
-
-# Create a window
-push 800;
-push 600;
-push WINDOW_TITLE;
-push 0;
-syscall window_create;
-
-push 100;
-push ANIM_CALLBACK;
-syscall time_delay_cb;
-
-push 0;
-push PIXEL_BUFFER;
-syscall window_draw_frame;
-
-# Wait for an event
-push 0;
-ret;
-
-###########################################################
-
-# Animation callback
-ANIM_CALLBACK:
-
-# x: local 0
-push X_COORD;
-load_u64;
-
-# x = x + dx
-get_local 0;
-push 10;
-add_u64;
-set_local 0;
-
-# x % 800
-get_local 0;
-push 800;
-mod_i64;
-set_local 0;
-
-# update global x variable
-push X_COORD;
-get_local 0;
-store_u64;
-
-# Clear the screen
-push PIXEL_BUFFER;
-push 0;
-push 1_440_000;
-syscall memset;
-
-# Draw the circle
-get_local 0;
-push 300;
-push 20;
-call DRAW_CIRCLE, 3;
-pop;
-
-push 0;
-push PIXEL_BUFFER;
-syscall window_draw_frame;
-
-# Schedule the animation callback again
-push 25;
-push ANIM_CALLBACK;
-syscall time_delay_cb;
-
-push 0;
-ret;
-
-###########################################################
-
-# Draw a circle
-# DRAW_CIRCLE(x, y, r)
-DRAW_CIRCLE:
-
-# Local 0
-# xmin = x - r
-get_arg 0;
-get_arg 2;
-sub_u64;
-
-# Local 1
-# xmax = x + r
-get_arg 0;
-get_arg 2;
-add_u64;
-
-# Local 2
-# ymin = y - r
-get_arg 1;
-get_arg 2;
-sub_u64;
-
-# Local 3
-# ymax = y + r
-get_arg 1;
-get_arg 2;
-add_u64;
-
-# Local 4: x
-# x = 0
-push 0;
-
-# Local 5: y
-# y = ymin
-get_local 2;
-
-# For each row
-LOOP_Y:
-
- # For each column
- # x = xmin
- get_local 0;
- set_local 4;
- LOOP_X:
-
- # (x - xin)^2
- get_local 4;
- get_arg 0;
- sub_u64;
- dup;
- mul_u64;
-
- # (y - yin)^2
- get_local 5;
- get_arg 1;
- sub_u64;
- dup;
- mul_u64;
-
- # dx^2 + dy^2
- add_u64;
-
- # r^2
- get_arg 2;
- dup;
- mul_u64;
-
- # dx^2 + dy^2 < r^2
- lt_i64;
- jz OUTSIDE_CIRCLE;
-
- get_local 4;
- get_local 5;
- call SET_PIXEL, 2;
- pop;
-
- OUTSIDE_CIRCLE:
-
- # x = x + 2
- get_local 4;
- push 1;
- add_u64;
- set_local 4;
-
- # while (x < xmax)
- get_local 4;
- get_local 1;
- lt_i64;
- jnz LOOP_X;
-
-# y = y + 1
-get_local 5;
-push 1;
-add_u64;
-set_local 5;
-
-# while (y < ymax)
-get_local 5;
-get_local 3;
-lt_i64;
-jnz LOOP_Y;
-
-push 0;
-ret;
-
-###########################################################
-
-# Set a pixel red
-# SET_PIXEL(x, y)
-SET_PIXEL:
-
-# Compute the pixel's address
-# 800 * 4 * y + 4 * x
-push 3200;
-get_arg 1;
-mul_u64;
-get_arg 0;
-push 4;
-mul_u64;
-add_u64;
-
-push 0xFF_00_00;
-store_u32;
-
-push 0;
-ret;
diff --git a/vm/examples/empty.asm b/vm/examples/empty.asm
index 4a005997..4342eefc 100644
--- a/vm/examples/empty.asm
+++ b/vm/examples/empty.asm
@@ -1,2 +1,2 @@
push 0;
-exit;
\ No newline at end of file
+ret;
diff --git a/vm/examples/factorial.asm b/vm/examples/factorial.asm
index 52327989..90e76b58 100644
--- a/vm/examples/factorial.asm
+++ b/vm/examples/factorial.asm
@@ -22,7 +22,7 @@ syscall print_i64;
syscall print_endl;
push 0;
-exit;
+ret;
#### fact(n) ####
FACT:
diff --git a/vm/examples/fib.asm b/vm/examples/fib.asm
index c3409869..636221e7 100644
--- a/vm/examples/fib.asm
+++ b/vm/examples/fib.asm
@@ -22,7 +22,7 @@ syscall print_i64;
syscall print_endl;
push 0;
-exit;
+ret;
#
# u64 fib(u64 n)
diff --git a/vm/examples/fizzbuzz.asm b/vm/examples/fizzbuzz.asm
index b6128c39..d22a393b 100644
--- a/vm/examples/fizzbuzz.asm
+++ b/vm/examples/fizzbuzz.asm
@@ -58,4 +58,4 @@ push 101;
lt_i64; # l0 < COUNT
jnz LOOP;
-exit;
+ret;
diff --git a/vm/examples/gradient.asm b/vm/examples/gradient.asm
index 8b4459a5..0033e7db 100644
--- a/vm/examples/gradient.asm
+++ b/vm/examples/gradient.asm
@@ -5,6 +5,9 @@
PIXEL_BUFFER:
.zero 1_920_000;
+EVENT:
+.zero 256;
+
WINDOW_TITLE:
.stringz "UVM Gradient Example";
@@ -118,6 +121,17 @@ push 0;
push PIXEL_BUFFER;
syscall window_draw_frame;
-# Return to the event loop
+# Wait until the user closes the window
+WAIT_FOR_EVENT:
+push EVENT;
+syscall window_wait_event;
+push EVENT;
+load_u16;
+# Check for EVENT_QUIT
+push_u32 0;
+eq_u64;
+jz WAIT_FOR_EVENT;
+
+# End program
push 0;
ret;
diff --git a/vm/examples/guess.asm b/vm/examples/guess.asm
index fb7307c3..0528f109 100644
--- a/vm/examples/guess.asm
+++ b/vm/examples/guess.asm
@@ -84,7 +84,7 @@ syscall print_i64;
syscall print_endl;
push 0;
-exit;
+ret;
#
# Read a positive integer from stdlin
diff --git a/vm/examples/loop.asm b/vm/examples/loop.asm
index eebe4d2c..67787848 100644
--- a/vm/examples/loop.asm
+++ b/vm/examples/loop.asm
@@ -61,4 +61,4 @@ push MIPS_STR;
syscall print_str;
syscall print_endl;
-exit;
+ret;
diff --git a/vm/examples/memcpy.asm b/vm/examples/memcpy.asm
index 3d99a11f..79d96072 100644
--- a/vm/examples/memcpy.asm
+++ b/vm/examples/memcpy.asm
@@ -73,4 +73,4 @@ push FPS_STR;
syscall print_str;
syscall print_endl;
-exit;
+ret;
diff --git a/vm/src/asm.rs b/vm/src/asm.rs
index d634de98..51481fee 100644
--- a/vm/src/asm.rs
+++ b/vm/src/asm.rs
@@ -3,7 +3,8 @@ use std::convert::{TryFrom};
use std::collections::HashMap;
use std::collections::HashSet;
use std::mem::transmute;
-use crate::vm::{VM, MemBlock, Op};
+use crate::vm::{Op};
+use crate::program::*;
#[derive(Debug)]
pub struct ParseError
@@ -485,10 +486,10 @@ pub struct Assembler
syscall_set: HashSet,
// Generated code
- code: MemBlock,
+ code: ByteArray,
// Data section
- data: MemBlock,
+ data: ByteArray,
/// Label definitions (name, position)
label_defs: HashMap,
@@ -504,20 +505,8 @@ impl Assembler
{
pub fn new() -> Self
{
- /*
- // Populate the available constants
- use crate::sys::constants::SYSCALL_DESCS;
- let mut const_map = HashMap::new();
- for syscall in SYSCALL_DESCS {
- const_map.insert(
- syscall.name.to_uppercase(),
- syscall.const_idx as i128
- );
- }
- */
-
/// Populate the available syscalls
- use crate::sys::constants::SYSCALL_DESCS;
+ use crate::constants::SYSCALL_DESCS;
let mut syscall_map = HashMap::new();
for syscall in SYSCALL_DESCS {
if let Some(syscall) = syscall {
@@ -525,19 +514,22 @@ impl Assembler
}
}
+ // TODO:
+ // Populate the available constants
+
Self {
const_map: HashMap::new(),
syscall_map: syscall_map,
syscall_set: HashSet::new(),
- code: MemBlock::new(),
- data: MemBlock::new(),
+ code: ByteArray::new(),
+ data: ByteArray::new(),
label_defs: HashMap::default(),
label_refs: Vec::default(),
section: Section::Code,
}
}
- fn parse_input(mut self, input: &mut Input) -> Result
+ fn parse_input(mut self, input: &mut Input) -> Result
{
// Until we've reached the end of the input
loop
@@ -605,10 +597,14 @@ impl Assembler
}
}
- Ok(VM::new(self.code, self.data, self.syscall_set))
+ Ok(Program {
+ code: self.code,
+ data: self.data,
+ syscalls: self.syscall_set,
+ })
}
- pub fn parse_file(mut self, file_name: &str) -> Result
+ pub fn parse_file(mut self, file_name: &str) -> Result
{
match std::fs::read_to_string(file_name) {
Err(_) => {
@@ -622,7 +618,7 @@ impl Assembler
}
/// Parse a string of source code
- pub fn parse_str(mut self, src: &str) -> Result
+ pub fn parse_str(mut self, src: &str) -> Result
{
let mut input = Input::new(src.to_string());
return self.parse_input(&mut input);
@@ -665,8 +661,8 @@ impl Assembler
input.parse_error("expected integer argument")
}
- /// Get the memory block for the current section
- fn mem(&mut self) -> &mut MemBlock
+ /// Get the byte array for the current section
+ fn mem(&mut self) -> &mut ByteArray
{
match self.section {
Section::Code => &mut self.code,
@@ -1115,6 +1111,10 @@ impl Assembler
"store_u32" => self.code.push_op(Op::store_u32),
"store_u64" => self.code.push_op(Op::store_u64),
+ "atomic_load_u64" => self.code.push_op(Op::atomic_load_u64),
+ "atomic_store_u64" => self.code.push_op(Op::atomic_store_u64),
+ "atomic_cas_u64" => self.code.push_op(Op::atomic_cas_u64),
+
"jmp" => {
self.code.push_op(Op::jmp);
let label_name = input.parse_ident()?;
@@ -1173,7 +1173,6 @@ impl Assembler
}
"ret" => self.code.push_op(Op::ret),
- "exit" => self.code.push_op(Op::exit),
_ => {
return input.parse_error(&format!("unknown instruction opcode \"{}\"", op_name))
@@ -1327,7 +1326,7 @@ mod tests
parse_ok(" FOO_BAR: jmp FOO_BAR; ");
// Callback label
- parse_ok("CB: ret; push_p32 CB; exit;");
+ parse_ok("CB: ret; push_p32 CB; ret;");
}
#[test]
@@ -1385,6 +1384,5 @@ mod tests
parse_file("examples/loop.asm");
parse_file("examples/memcpy.asm");
parse_file("examples/gradient.asm");
- parse_file("examples/circle.asm");
}
}
diff --git a/vm/src/audio.rs b/vm/src/audio.rs
new file mode 100644
index 00000000..cee944fc
--- /dev/null
+++ b/vm/src/audio.rs
@@ -0,0 +1,260 @@
+use sdl2::audio::{AudioCallback, AudioSpecDesired, AudioDevice};
+use std::sync::{Arc, Weak, Mutex};
+use std::collections::HashMap;
+use std::cell::RefCell;
+use crate::vm::{Value, VM, Thread};
+use crate::host::{get_sdl_context};
+use crate::constants::*;
+
+// Audio output callback
+struct OutputCB
+{
+ // Number of audio output channels
+ num_channels: usize,
+
+ // Expected buffer size
+ buf_size: usize,
+
+ // VM thread in which to execute the audio callback
+ thread: Thread,
+
+ // Callback function pointer
+ cb: u64,
+}
+
+impl AudioCallback for OutputCB
+{
+ // Using signed 16-bit samples
+ type Channel = i16;
+
+ fn callback(&mut self, out: &mut [i16])
+ {
+ let output_len = out.len();
+ assert!(output_len % self.num_channels == 0);
+ let samples_per_chan = output_len / self.num_channels;
+ assert!(samples_per_chan == self.buf_size);
+
+ // Clear the buffer
+ out.fill(0);
+
+ // Run the audio callback
+ let ptr = self.thread.call(self.cb, &[Value::from(self.num_channels), Value::from(samples_per_chan)]);
+
+ let mem_slice: &[i16] = self.thread.get_heap_slice_mut(ptr.as_usize(), output_len);
+ out.copy_from_slice(&mem_slice);
+ }
+}
+
+// Audio input callback
+struct InputCB
+{
+ // Number of audio input channels
+ num_channels: usize,
+
+ // Expected buffer size
+ buf_size: usize,
+
+ // VM thread in which to execute the audio callback
+ thread: Thread,
+
+ // Callback function pointer
+ cb: u64,
+}
+
+impl AudioCallback for InputCB
+{
+ // Using signed 16-bit samples
+ type Channel = i16;
+
+ // Receives a buffer of input samples
+ fn callback(&mut self, buf: &mut [i16])
+ {
+ assert!(buf.len() % self.num_channels == 0);
+ let samples_per_chan = buf.len() / self.num_channels;
+ assert!(samples_per_chan == self.buf_size);
+
+ // Copy the samples to make them accessible to the audio thread
+ INPUT_STATE.with_borrow_mut(|s| {
+ s.input_tid = self.thread.id;
+ s.samples.resize(buf.len(), 0);
+ s.samples.copy_from_slice(buf);
+ });
+
+ // Run the audio callback
+ let ptr = self.thread.call(self.cb, &[Value::from(self.num_channels), Value::from(samples_per_chan)]);
+ }
+}
+
+#[derive(Default)]
+struct AudioState
+{
+ output_dev: Option>,
+ input_dev: Option>,
+}
+
+#[derive(Default)]
+struct InputState
+{
+ // Thread doing the audio input
+ input_tid: u64,
+
+ // Samples available to read
+ samples: Vec,
+}
+
+thread_local! {
+ // This is only accessed from the main thread
+ static AUDIO_STATE: RefCell = RefCell::new(AudioState::default());
+
+ // Audio input state. Accessed from the input thread.
+ static INPUT_STATE: RefCell = RefCell::new(InputState::default());
+}
+
+pub fn audio_open_output(thread: &mut Thread, sample_rate: Value, num_channels: Value, format: Value, cb: Value) -> Value
+{
+ if thread.id != 0 {
+ panic!("audio functions should only be called from the main thread");
+ }
+
+ AUDIO_STATE.with_borrow(|s| {
+ if s.output_dev.is_some() {
+ panic!("audio output device already open");
+ }
+ });
+
+ let sample_rate = sample_rate.as_u32();
+ let num_channels = num_channels.as_u16();
+ let format = format.as_u16();
+ let cb = cb.as_u64();
+
+ if sample_rate != 44100 {
+ panic!("for now, only 44100Hz sample rate suppored");
+ }
+
+ //if num_channels > 2 {
+ if num_channels != 1 {
+ panic!("for now, only one output channel supported");
+ }
+
+ if format != AUDIO_FORMAT_I16 {
+ panic!("for now, only i16, 16-bit signed audio format supported");
+ }
+
+ let desired_spec = AudioSpecDesired {
+ freq: Some(sample_rate as i32),
+ channels: Some(num_channels as u8),
+ samples: Some(1024) // buffer size, 1024 samples
+ };
+
+ // Create a new VM thread in which to run the audio callback
+ let audio_thread = VM::new_thread(&thread.vm);
+
+ let sdl = get_sdl_context();
+ let audio_subsystem = sdl.audio().unwrap();
+
+ let device = audio_subsystem.open_playback(None, &desired_spec, |spec| {
+ OutputCB {
+ num_channels: num_channels.into(),
+ buf_size: desired_spec.samples.unwrap() as usize,
+ thread: audio_thread,
+ cb: cb,
+ }
+ }).unwrap();
+
+ // Start playback
+ device.resume();
+
+ // Keep the audio device alive
+ AUDIO_STATE.with_borrow_mut(|s| {
+ s.output_dev = Some(device);
+ });
+
+ // FIXME: return the device_id (u32)
+ Value::from(0)
+}
+
+pub fn audio_open_input(thread: &mut Thread, sample_rate: Value, num_channels: Value, format: Value, cb: Value) -> Value
+{
+ if thread.id != 0 {
+ panic!("audio functions should only be called from the main thread");
+ }
+
+ AUDIO_STATE.with_borrow(|s| {
+ if s.input_dev.is_some() {
+ panic!("audio input device already open");
+ }
+ });
+
+ let sample_rate = sample_rate.as_u32();
+ let num_channels = num_channels.as_u16();
+ let format = format.as_u16();
+ let cb = cb.as_u64();
+
+ if sample_rate != 44100 {
+ panic!("for now, only 44100Hz sample rate suppored");
+ }
+
+ //if num_channels > 2 {
+ if num_channels != 1 {
+ panic!("for now, only one output channel supported");
+ }
+
+ if format != AUDIO_FORMAT_I16 {
+ panic!("for now, only i16, 16-bit signed audio format supported");
+ }
+
+ let desired_spec = AudioSpecDesired {
+ freq: Some(sample_rate as i32),
+ channels: Some(num_channels as u8),
+ samples: Some(1024) // buffer size, 1024 samples
+ };
+
+ // Create a new VM thread in which to run the audio callback
+ let audio_thread = VM::new_thread(&thread.vm);
+
+ let sdl = get_sdl_context();
+ let audio_subsystem = sdl.audio().unwrap();
+
+ let device = audio_subsystem.open_capture(None, &desired_spec, |spec| {
+ InputCB {
+ num_channels: num_channels.into(),
+ buf_size: desired_spec.samples.unwrap() as usize,
+ thread: audio_thread,
+ cb: cb,
+ }
+ }).unwrap();
+
+ // Start playback
+ device.resume();
+
+ // Keep the audio device alive
+ AUDIO_STATE.with_borrow_mut(|s| {
+ s.input_dev = Some(device);
+ });
+
+ // FIXME: return the device_id (u32)
+ Value::from(1)
+}
+
+/// Read audio samples from an audio input thread
+pub fn audio_read_samples(thread: &mut Thread, dst_ptr: Value, num_samples: Value)
+{
+ let dst_ptr = dst_ptr.as_usize();
+ let num_samples = num_samples.as_usize();
+
+ INPUT_STATE.with_borrow_mut(|s| {
+ if s.input_tid != thread.id {
+ panic!("can only read audio samples from audio input thread");
+ }
+
+ // For now, force reading all available samples
+ if num_samples != s.samples.len() {
+ panic!("must read all available samples");
+ }
+
+ let dst_buf: &mut [i16] = thread.get_heap_slice_mut(dst_ptr, num_samples);
+ dst_buf.copy_from_slice(&s.samples);
+
+ s.samples.clear();
+ });
+}
diff --git a/vm/src/sys/constants.rs b/vm/src/constants.rs
similarity index 73%
rename from vm/src/sys/constants.rs
rename to vm/src/constants.rs
index b21b7647..18cf3690 100644
--- a/vm/src/sys/constants.rs
+++ b/vm/src/constants.rs
@@ -4,28 +4,26 @@
#![allow(unused)]
-pub const SYSCALL_TBL_LEN: usize = 28;
+pub const SYSCALL_TBL_LEN: usize = 32;
pub const TIME_CURRENT_MS: u16 = 0;
pub const WINDOW_CREATE: u16 = 1;
-pub const TIME_DELAY_CB: u16 = 2;
+pub const WINDOW_WAIT_EVENT: u16 = 2;
pub const MEMCPY: u16 = 3;
pub const MEMSET: u16 = 4;
pub const PRINT_I64: u16 = 5;
pub const PRINT_STR: u16 = 6;
pub const PRINT_ENDL: u16 = 7;
pub const GETCHAR: u16 = 8;
-pub const WINDOW_ON_KEYDOWN: u16 = 9;
+pub const WINDOW_POLL_EVENT: u16 = 9;
pub const WINDOW_DRAW_FRAME: u16 = 10;
-pub const WINDOW_ON_MOUSEMOVE: u16 = 11;
-pub const WINDOW_ON_MOUSEDOWN: u16 = 12;
-pub const WINDOW_ON_MOUSEUP: u16 = 13;
+pub const EXIT: u16 = 11;
+pub const AUDIO_OPEN_INPUT: u16 = 12;
+pub const AUDIO_READ_SAMPLES: u16 = 13;
pub const VM_HEAP_SIZE: u16 = 14;
-pub const WINDOW_ON_KEYUP: u16 = 15;
pub const MEMSET32: u16 = 16;
-pub const VM_RESIZE_HEAP: u16 = 17;
+pub const VM_GROW_HEAP: u16 = 17;
pub const AUDIO_OPEN_OUTPUT: u16 = 18;
-pub const WINDOW_ON_TEXTINPUT: u16 = 19;
pub const PRINT_F32: u16 = 20;
pub const NET_LISTEN: u16 = 21;
pub const NET_ACCEPT: u16 = 22;
@@ -34,6 +32,10 @@ pub const NET_WRITE: u16 = 24;
pub const NET_CLOSE: u16 = 25;
pub const PUTCHAR: u16 = 26;
pub const MEMCMP: u16 = 27;
+pub const THREAD_ID: u16 = 28;
+pub const THREAD_SPAWN: u16 = 29;
+pub const THREAD_SLEEP: u16 = 30;
+pub const THREAD_JOIN: u16 = 31;
pub struct SysCallDesc
{
@@ -46,24 +48,24 @@ pub struct SysCallDesc
pub const SYSCALL_DESCS: [Option; SYSCALL_TBL_LEN] = [
Some(SysCallDesc { name: "time_current_ms", const_idx: 0, argc: 0, has_ret: true }),
Some(SysCallDesc { name: "window_create", const_idx: 1, argc: 4, has_ret: true }),
- Some(SysCallDesc { name: "time_delay_cb", const_idx: 2, argc: 2, has_ret: false }),
+ Some(SysCallDesc { name: "window_wait_event", const_idx: 2, argc: 1, has_ret: false }),
Some(SysCallDesc { name: "memcpy", const_idx: 3, argc: 3, has_ret: false }),
Some(SysCallDesc { name: "memset", const_idx: 4, argc: 3, has_ret: false }),
Some(SysCallDesc { name: "print_i64", const_idx: 5, argc: 1, has_ret: false }),
Some(SysCallDesc { name: "print_str", const_idx: 6, argc: 1, has_ret: false }),
Some(SysCallDesc { name: "print_endl", const_idx: 7, argc: 0, has_ret: false }),
Some(SysCallDesc { name: "getchar", const_idx: 8, argc: 0, has_ret: true }),
- Some(SysCallDesc { name: "window_on_keydown", const_idx: 9, argc: 2, has_ret: false }),
+ Some(SysCallDesc { name: "window_poll_event", const_idx: 9, argc: 1, has_ret: true }),
Some(SysCallDesc { name: "window_draw_frame", const_idx: 10, argc: 2, has_ret: false }),
- Some(SysCallDesc { name: "window_on_mousemove", const_idx: 11, argc: 2, has_ret: false }),
- Some(SysCallDesc { name: "window_on_mousedown", const_idx: 12, argc: 2, has_ret: false }),
- Some(SysCallDesc { name: "window_on_mouseup", const_idx: 13, argc: 2, has_ret: false }),
+ Some(SysCallDesc { name: "exit", const_idx: 11, argc: 1, has_ret: false }),
+ Some(SysCallDesc { name: "audio_open_input", const_idx: 12, argc: 4, has_ret: true }),
+ Some(SysCallDesc { name: "audio_read_samples", const_idx: 13, argc: 2, has_ret: false }),
Some(SysCallDesc { name: "vm_heap_size", const_idx: 14, argc: 0, has_ret: true }),
- Some(SysCallDesc { name: "window_on_keyup", const_idx: 15, argc: 2, has_ret: false }),
+ None,
Some(SysCallDesc { name: "memset32", const_idx: 16, argc: 3, has_ret: false }),
- Some(SysCallDesc { name: "vm_resize_heap", const_idx: 17, argc: 1, has_ret: true }),
+ Some(SysCallDesc { name: "vm_grow_heap", const_idx: 17, argc: 1, has_ret: true }),
Some(SysCallDesc { name: "audio_open_output", const_idx: 18, argc: 4, has_ret: true }),
- Some(SysCallDesc { name: "window_on_textinput", const_idx: 19, argc: 2, has_ret: false }),
+ None,
Some(SysCallDesc { name: "print_f32", const_idx: 20, argc: 1, has_ret: false }),
Some(SysCallDesc { name: "net_listen", const_idx: 21, argc: 2, has_ret: true }),
Some(SysCallDesc { name: "net_accept", const_idx: 22, argc: 4, has_ret: true }),
@@ -72,8 +74,19 @@ pub const SYSCALL_DESCS: [Option; SYSCALL_TBL_LEN] = [
Some(SysCallDesc { name: "net_close", const_idx: 25, argc: 1, has_ret: false }),
Some(SysCallDesc { name: "putchar", const_idx: 26, argc: 1, has_ret: true }),
Some(SysCallDesc { name: "memcmp", const_idx: 27, argc: 3, has_ret: true }),
+ Some(SysCallDesc { name: "thread_id", const_idx: 28, argc: 0, has_ret: true }),
+ Some(SysCallDesc { name: "thread_spawn", const_idx: 29, argc: 2, has_ret: true }),
+ Some(SysCallDesc { name: "thread_sleep", const_idx: 30, argc: 1, has_ret: false }),
+ Some(SysCallDesc { name: "thread_join", const_idx: 31, argc: 1, has_ret: true }),
];
+pub const EVENT_QUIT: u16 = 0;
+pub const EVENT_KEYDOWN: u16 = 1;
+pub const EVENT_KEYUP: u16 = 2;
+pub const EVENT_MOUSEDOWN: u16 = 3;
+pub const EVENT_MOUSEUP: u16 = 4;
+pub const EVENT_MOUSEMOVE: u16 = 5;
+pub const EVENT_TEXTINPUT: u16 = 6;
pub const KEY_BACKSPACE: u16 = 8;
pub const KEY_TAB: u16 = 9;
pub const KEY_RETURN: u16 = 10;
diff --git a/vm/src/host.rs b/vm/src/host.rs
new file mode 100644
index 00000000..1513e844
--- /dev/null
+++ b/vm/src/host.rs
@@ -0,0 +1,284 @@
+extern crate sdl2;
+use std::collections::HashMap;
+use std::io::Write;
+use std::io::Read;
+use std::io::{stdout, stdin};
+use std::sync::{Arc, Weak, Mutex};
+use crate::vm::{Value, VM, Thread};
+use crate::window::*;
+use crate::audio::*;
+use crate::net::*;
+use crate::time::*;
+use crate::constants::*;
+
+/// System call function signature
+/// Note: the in/out arg count should be fixed so
+/// that we can JIT syscalls efficiently
+#[derive(Copy, Clone)]
+pub enum HostFn
+{
+ Fn0_0(fn(&mut Thread)),
+ Fn0_1(fn(&mut Thread) -> Value),
+
+ Fn1_0(fn(&mut Thread, a0: Value)),
+ Fn1_1(fn(&mut Thread, a0: Value) -> Value),
+
+ Fn2_0(fn(&mut Thread, a0: Value, a1: Value)),
+ Fn2_1(fn(&mut Thread, a0: Value, a1: Value) -> Value),
+
+ Fn3_0(fn(&mut Thread, a0: Value, a1: Value, a2: Value)),
+ Fn3_1(fn(&mut Thread, a0: Value, a1: Value, a2: Value) -> Value),
+
+ Fn4_0(fn(&mut Thread, a0: Value, a1: Value, a2: Value, a3: Value)),
+ Fn4_1(fn(&mut Thread, a0: Value, a1: Value, a2: Value, a3: Value) -> Value),
+}
+
+impl HostFn
+{
+ fn argc(&self) -> usize
+ {
+ match self {
+ Self::Fn0_0(_) => 0,
+ Self::Fn0_1(_) => 0,
+ Self::Fn1_0(_) => 1,
+ Self::Fn1_1(_) => 1,
+ Self::Fn2_0(_) => 2,
+ Self::Fn2_1(_) => 2,
+ Self::Fn3_0(_) => 3,
+ Self::Fn3_1(_) => 3,
+ Self::Fn4_0(_) => 4,
+ Self::Fn4_1(_) => 4,
+ }
+ }
+
+ fn has_ret(&self) -> bool
+ {
+ match self {
+ Self::Fn0_0(_) => false,
+ Self::Fn0_1(_) => true,
+ Self::Fn1_0(_) => false,
+ Self::Fn1_1(_) => true,
+ Self::Fn2_0(_) => false,
+ Self::Fn2_1(_) => true,
+ Self::Fn3_0(_) => false,
+ Self::Fn3_1(_) => true,
+ Self::Fn4_0(_) => false,
+ Self::Fn4_1(_) => true,
+ }
+ }
+}
+
+/// SDL context (used for UI and audio)
+/// This is a global variable because it doesn't implement
+/// the Send trait, and so can't be referenced from another thread
+static mut SDL: Option = None;
+
+pub fn get_sdl_context() -> &'static mut sdl2::Sdl
+{
+ unsafe
+ {
+ // Lazily initialize the SDL context
+ if SDL.is_none() {
+ SDL = Some(sdl2::init().unwrap());
+ }
+
+ SDL.as_mut().unwrap()
+ }
+}
+
+/// Get the syscall with a given index
+pub fn get_syscall(const_idx: u16) -> HostFn
+{
+ match const_idx {
+ // Core VM syscalls
+ VM_HEAP_SIZE => HostFn::Fn0_1(vm_heap_size),
+ VM_GROW_HEAP => HostFn::Fn1_1(vm_grow_heap),
+ MEMSET => HostFn::Fn3_0(memset),
+ MEMSET32 => HostFn::Fn3_0(memset32),
+ MEMCPY => HostFn::Fn3_0(memcpy),
+ MEMCMP => HostFn::Fn3_1(memcmp),
+ EXIT => HostFn::Fn1_0(exit),
+
+ THREAD_SPAWN => HostFn::Fn2_1(thread_spawn),
+ THREAD_JOIN => HostFn::Fn1_1(thread_join),
+ THREAD_ID => HostFn::Fn0_1(thread_id),
+ THREAD_SLEEP => HostFn::Fn1_0(thread_sleep),
+
+ // Console I/O
+ PRINT_I64 => HostFn::Fn1_0(print_i64),
+ PRINT_F32 => HostFn::Fn1_0(print_f32),
+ PRINT_STR => HostFn::Fn1_0(print_str),
+ PRINT_ENDL => HostFn::Fn0_0(print_endl),
+ PUTCHAR => HostFn::Fn1_1(putchar),
+ GETCHAR => HostFn::Fn0_1(getchar),
+
+ TIME_CURRENT_MS => HostFn::Fn0_1(time_current_ms),
+
+ WINDOW_CREATE => HostFn::Fn4_1(window_create),
+ WINDOW_DRAW_FRAME => HostFn::Fn2_0(window_draw_frame),
+ WINDOW_POLL_EVENT => HostFn::Fn1_1(window_poll_event),
+ WINDOW_WAIT_EVENT => HostFn::Fn1_0(window_wait_event),
+
+ AUDIO_OPEN_OUTPUT => HostFn::Fn4_1(audio_open_output),
+ AUDIO_OPEN_INPUT => HostFn::Fn4_1(audio_open_input),
+ AUDIO_READ_SAMPLES => HostFn::Fn2_0(audio_read_samples),
+
+ _ => panic!("unknown syscall \"{}\"", const_idx),
+ }
+}
+
+fn vm_heap_size(thread: &mut Thread) -> Value
+{
+ Value::from(thread.heap_size())
+}
+
+fn vm_grow_heap(thread: &mut Thread, num_bytes: Value) -> Value
+{
+ let mut vm = thread.vm.lock().unwrap();
+ let num_bytes = num_bytes.as_usize();
+ let new_size = vm.grow_heap(num_bytes);
+ Value::from(new_size)
+}
+
+fn thread_id(thread: &mut Thread) -> Value
+{
+ Value::from(thread.id)
+}
+
+// Make the current thread sleep
+fn thread_sleep(thread: &mut Thread, msecs: Value)
+{
+ use std::thread;
+ use std::time::Duration;
+ let msecs = msecs.as_u64();
+ thread::sleep(Duration::from_millis(msecs));
+}
+
+// Spawn a new thread
+// Takes a function to call as argument
+// Returns a thread id
+fn thread_spawn(thread: &mut Thread, fun: Value, arg: Value) -> Value
+{
+ let callee_pc = fun.as_u64();
+ let tid = VM::spawn_thread(&thread.vm, callee_pc, vec![arg]);
+ Value::from(tid)
+}
+
+// Wait for a thread to terminatr, produce the return value
+fn thread_join(thread: &mut Thread, tid: Value) -> Value
+{
+ let tid = tid.as_u64();
+ VM::join_thread(&thread.vm, tid)
+}
+
+fn memset(thread: &mut Thread, dst_ptr: Value, val: Value, num_bytes: Value)
+{
+ let dst_ptr = dst_ptr.as_usize();
+ let val = val.as_u8();
+ let num_bytes = num_bytes.as_usize();
+
+ let mem_slice: &mut [u8] = thread.get_heap_slice_mut(dst_ptr, num_bytes);
+ mem_slice.fill(val);
+}
+
+fn memset32(thread: &mut Thread, dst_ptr: Value, word: Value, num_words: Value)
+{
+ let dst_ptr = dst_ptr.as_usize();
+ let word = word.as_u32();
+ let num_words = num_words.as_usize();
+
+ let mem_slice: &mut [u32] = thread.get_heap_slice_mut(dst_ptr, num_words);
+ mem_slice.fill(word);
+}
+
+fn memcpy(thread: &mut Thread, dst_ptr: Value, src_ptr: Value, num_bytes: Value)
+{
+ let dst_ptr = dst_ptr.as_usize();
+ let src_ptr = src_ptr.as_usize();
+ let num_bytes = num_bytes.as_usize();
+
+ let overlap = (
+ (dst_ptr >= src_ptr && dst_ptr < src_ptr + num_bytes) ||
+ (src_ptr >= dst_ptr && src_ptr < dst_ptr + num_bytes)
+ );
+
+ if overlap {
+ panic!("memcpy to/from overlapping regions");
+ }
+
+ unsafe {
+ let dst_ptr: *mut u8 = thread.get_heap_ptr_mut(dst_ptr, num_bytes);
+ let src_ptr: *mut u8 = thread.get_heap_ptr_mut(src_ptr, num_bytes);
+
+ std::ptr::copy_nonoverlapping(src_ptr, dst_ptr, num_bytes);
+ }
+}
+
+fn memcmp(thread: &mut Thread, ptr_a: Value, ptr_b: Value, num_bytes: Value) -> Value
+{
+ let num_bytes = num_bytes.as_usize();
+
+ unsafe {
+ let ptr_a: *const libc::c_void = thread.get_heap_ptr_mut(ptr_a.as_usize(), num_bytes);
+ let ptr_b: *const libc::c_void = thread.get_heap_ptr_mut(ptr_b.as_usize(), num_bytes);
+
+ let result = libc::memcmp(ptr_a, ptr_b, num_bytes);
+ Value::from(result as u64)
+ }
+}
+
+// End program execution
+fn exit(thread: &mut Thread, val: Value)
+{
+ unsafe { libc::exit(val.as_i32() & 0xFF) };
+}
+
+fn print_i64(thread: &mut Thread, v: Value)
+{
+ let v = v.as_i64();
+ print!("{}", v);
+}
+
+fn print_f32(thread: &mut Thread, v: Value)
+{
+ let v = v.as_f32();
+ print!("{}", v);
+}
+
+/// Print a null-terminated UTF-8 string to stdout
+fn print_str(thread: &mut Thread, str_ptr: Value)
+{
+ let rust_str = thread.get_heap_str(str_ptr.as_usize());
+ print!("{}", rust_str);
+}
+
+/// Print a newline characted to stdout
+fn print_endl(thread: &mut Thread)
+{
+ println!();
+}
+
+/// Write one byte of input to stdout.
+/// Analogous to C's getchar
+fn putchar(thread: &mut Thread, byte: Value) -> Value
+{
+ let byte = byte.as_u8();
+ let bytes = byte.to_le_bytes();
+
+ match stdout().write_all(&bytes) {
+ Ok(_) => Value::from(byte),
+ Err(_) => Value::from(-1 as i64),
+ }
+}
+
+/// Read one byte of input from stdin.
+/// Analogous to C's getchar
+fn getchar(thread: &mut Thread) -> Value
+{
+ let ch = stdin().bytes().next();
+
+ match ch {
+ Some(Ok(ch)) => Value::from(ch as i64),
+ None | Some(Err(_)) => Value::from(-1 as i64),
+ }
+}
diff --git a/vm/src/image.rs b/vm/src/image.rs
deleted file mode 100644
index e69de29b..00000000
diff --git a/vm/src/main.rs b/vm/src/main.rs
index d1a30121..dfd8919f 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -1,11 +1,18 @@
#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(unused_mut)]
+#![allow(unused_parens)]
#![allow(unused_imports)]
+mod window;
+mod audio;
+mod net;
+mod time;
+mod constants;
+mod host;
mod vm;
-mod sys;
mod asm;
+mod program;
mod utils;
extern crate sdl2;
@@ -15,9 +22,8 @@ use std::thread::sleep;
use std::time::Duration;
use std::process::exit;
use std::sync::{Arc, Mutex};
-use crate::vm::{VM, Value, MemBlock, ExitReason};
+use crate::vm::{VM, Value};
use crate::asm::{Assembler};
-use crate::sys::{SysState};
use crate::utils::{thousands_sep};
/// Command-line options
@@ -70,64 +76,6 @@ fn parse_args(args: Vec) -> Options
opts
}
-fn run_program(mutex: &mut Arc>) -> Value
-{
- let mut vm = mutex.lock().unwrap();
-
- match vm.call(0, &[])
- {
- ExitReason::Exit(val) => {
- return val;
- }
-
- // Keep processig events
- ExitReason::Return(val) => {
- }
- }
-
- drop(vm);
-
- loop
- {
- let mut vm = mutex.lock().unwrap();
-
- if let ExitReason::Exit(val) = sys::window::process_events(&mut vm) {
- return val;
- }
-
- let next_cb_time = sys::time::time_until_next_cb(&mut vm);
-
- // Unlock the VM mutex before going to sleep, so that other threads,
- // such as the audio thread, may use the VM
- drop(vm);
-
- // Sleep until the next callback
- if let Some(delay_ms) = next_cb_time {
- let min_delay = std::cmp::min(delay_ms, 10);
- sleep(Duration::from_millis(min_delay));
- }
- else
- {
- sleep(Duration::from_millis(10));
- }
-
- let mut vm = mutex.lock().unwrap();
-
- // For each callback to run
- for pc in sys::time::get_cbs_to_run(&mut vm)
- {
- match vm.call(pc, &[])
- {
- ExitReason::Exit(val) => {
- return val;
- }
- ExitReason::Return(val) => {
- }
- }
- }
- }
-}
-
fn main()
{
let opts = parse_args(env::args().collect());
@@ -141,9 +89,9 @@ fn main()
// Parse/compile the program
let asm = Assembler::new();
- let result = asm.parse_file(file_name);
+ let program = asm.parse_file(file_name);
- if let Err(error) = &result {
+ if let Err(error) = program {
println!("Error: {}", error);
exit(-1);
}
@@ -153,16 +101,9 @@ fn main()
exit(0);
}
- let vm = result.unwrap();
- let mut mutex = SysState::get_mutex(vm);
- let ret_val = run_program(&mut mutex);
-
- #[cfg(feature = "count_insns")]
- {
- let mut vm = mutex.lock().unwrap();
- let insn_count = vm.get_insn_count();
- println!("insns executed: {}", thousands_sep(insn_count));
- }
+ let program = program.unwrap();
+ let mut vm = VM::new(program);
+ let ret_val = VM::call(&mut vm, 0, &[]);
exit(ret_val.as_i32());
}
diff --git a/vm/src/sys/net.rs b/vm/src/net.rs
similarity index 96%
rename from vm/src/sys/net.rs
rename to vm/src/net.rs
index b3a69ad5..52e3edff 100644
--- a/vm/src/sys/net.rs
+++ b/vm/src/net.rs
@@ -4,8 +4,9 @@ use std::thread;
use std::net::{TcpListener, TcpStream};
use std::io::{self, Read, Write, Error};
use std::sync::{Arc, Weak, Mutex};
-use crate::vm::{VM, Value, ExitReason};
+use crate::vm::{VM, Value};
+/*
// State for the networking subsystem
pub struct NetState
{
@@ -15,7 +16,9 @@ pub struct NetState
/// Map of open sockets
sockets: HashMap,
}
+*/
+/*
impl Default for NetState
{
fn default() -> Self
@@ -27,25 +30,9 @@ impl Default for NetState
}
}
}
+*/
-// State associated with a socket
-enum Socket
-{
- Listen {
- listener: TcpListener,
-
- /// Incoming connections
- incoming: VecDeque,
- },
-
- Stream {
- stream: TcpStream,
-
- // Read buffer
- read_buf: Vec,
- }
-}
-
+/*
/// TCP listening thread
fn listen_thread(
vm_mutex: Weak>,
@@ -91,7 +78,9 @@ fn listen_thread(
}
}
}
+*/
+/*
// Syscall to create a TCP listening socket to accept incoming connections
// u64 socket_id = net_listen(
// const char* listen_addr, // Network interface address to listen on, null for any address
@@ -142,7 +131,9 @@ pub fn net_listen(
// Return the socket id
Value::from(socket_id)
}
+*/
+/*
/// TCP read thread
fn read_thread(
vm_mutex: Weak>,
@@ -201,7 +192,9 @@ fn read_thread(
}
}
}
+*/
+/*
// Syscall to accept a new connection
// Writes the client address in the buffer you specify
// u64 socket_id = net_accept(u64 socket_id, char* client_addr, u64 client_addr_len, callback on_incoming_data)
@@ -270,7 +263,9 @@ pub fn net_accept(
_ => panic!()
}
}
+*/
+/*
// Syscall to read data from a given socket into a buffer you specify
// u64 num_bytes_read = net_read(u64 socket_id, void* buf_ptr, u64 buf_len)
pub fn net_read(
@@ -302,7 +297,9 @@ pub fn net_read(
_ => panic!("invalid socket id {} in net_read", socket_id)
}
}
+*/
+/*
// Syscall to write data on a given socket
// u64 num_bytes = net_write(u64 socket_id, void* buf_ptr, u64 buf_len);
pub fn net_write(
@@ -327,7 +324,9 @@ pub fn net_write(
_ => panic!()
}
}
+*/
+/*
// Syscall to close a socket
// net_close(u64 socket_id)
pub fn net_close(
@@ -355,3 +354,4 @@ pub fn net_close(
// This drops the socket
net_state.sockets.remove(&socket_id);
}
+*/
diff --git a/vm/src/program.rs b/vm/src/program.rs
new file mode 100644
index 00000000..4b3a748b
--- /dev/null
+++ b/vm/src/program.rs
@@ -0,0 +1,94 @@
+use std::collections::HashSet;
+use std::mem::transmute;
+use crate::vm::Op;
+
+pub struct ByteArray
+{
+ data: Vec
+}
+
+impl ByteArray
+{
+ pub fn new() -> Self
+ {
+ Self {
+ data: Vec::default()
+ }
+ }
+
+ pub fn as_slice(&self) -> &[u8]
+ {
+ &self.data[..]
+ }
+
+ /// Get the memory block size in bytes
+ pub fn len(&self) -> usize
+ {
+ self.data.len()
+ }
+
+ pub fn push_op(&mut self, op: Op)
+ {
+ self.data.push(op as u8);
+ }
+
+ pub fn push_u8(&mut self, val: u8)
+ {
+ self.data.push(val);
+ }
+
+ pub fn push_u16(&mut self, val: u16)
+ {
+ for byte in val.to_le_bytes() {
+ self.data.push(byte);
+ }
+ }
+
+ pub fn push_i8(&mut self, val: i8)
+ {
+ self.data.push(val as u8);
+ }
+
+ pub fn push_i32(&mut self, val: i32)
+ {
+ for byte in val.to_le_bytes() {
+ self.data.push(byte);
+ }
+ }
+
+ pub fn push_u32(&mut self, val: u32)
+ {
+ for byte in val.to_le_bytes() {
+ self.data.push(byte);
+ }
+ }
+
+ pub fn push_u64(&mut self, val: u64)
+ {
+ for byte in val.to_le_bytes() {
+ self.data.push(byte);
+ }
+ }
+
+ /// Write a value at the given address
+ pub fn write(&mut self, pos: usize, val: T) where T: Copy
+ {
+ unsafe {
+ let buf_ptr = self.data.as_mut_ptr();
+ let val_ptr = transmute::<*mut u8 , *mut T>(buf_ptr.add(pos));
+ std::ptr::write_unaligned(val_ptr, val);
+ }
+ }
+}
+
+pub struct Program
+{
+ // Executable code
+ pub code: ByteArray,
+
+ // Data segment
+ pub data: ByteArray,
+
+ // Set of syscalls referenced by this program
+ pub syscalls: HashSet,
+}
diff --git a/vm/src/sys/audio.rs b/vm/src/sys/audio.rs
deleted file mode 100644
index bf239548..00000000
--- a/vm/src/sys/audio.rs
+++ /dev/null
@@ -1,104 +0,0 @@
-use sdl2::audio::{AudioCallback, AudioSpecDesired, AudioDevice};
-use std::sync::{Arc, Weak, Mutex};
-use crate::vm::{Value, VM, ExitReason};
-use crate::sys::{get_sdl_context};
-use crate::sys::constants::*;
-
-#[derive(Clone)]
-struct AudioCB
-{
- // Weak reference to the VM, guarded by a mutex
- // We use this to call the VM to generate audio
- vm: Weak>,
-
- // Callback function pointer
- cb: u64,
-
- // Number of audio output channels
- num_channels: usize,
-}
-
-impl AudioCallback for AudioCB
-{
- // Signed 16-bit samples
- type Channel = i16;
-
- fn callback(&mut self, out: &mut [i16])
- {
- out.fill(0);
-
- let output_len = out.len();
- assert!(output_len % self.num_channels == 0);
- let samples_per_chan = output_len / self.num_channels;
-
- let arc = self.vm.upgrade().unwrap();
- let mut vm = arc.lock().unwrap();
-
- match vm.call(self.cb, &[Value::from(self.num_channels), Value::from(samples_per_chan)]) {
- ExitReason::Return(ptr) => {
- let mem_slice: &[i16] = vm.get_heap_slice(ptr.as_usize(), output_len);
- out.copy_from_slice(&mem_slice);
- }
- _ => panic!()
- }
- }
-}
-
-// TODO: support multiple audio devices
-/// We have to keep the audio device alive
-/// This is a global variable because it doesn't implement
-/// the Send trait, and so can't be referenced from another thread
-static mut DEVICE: Option> = None;
-
-// NOTE: this can only be called from the main thread since it uses SDL
-// However, it creates a new thread to generate audio sample, this thread
-// could be given a reference to another VM instance
-pub fn audio_open_output(vm: &mut VM, sample_rate: Value, num_channels: Value, format: Value, cb: Value) -> Value
-{
- let sample_rate = sample_rate.as_u32();
- let num_channels = num_channels.as_u16();
- let format = format.as_u16();
- let cb = cb.as_u64();
-
- if sample_rate != 44100 {
- panic!("for now, only 44100Hz sample rate suppored");
- }
-
- //if num_channels > 2 {
- if num_channels != 1 {
- panic!("for now, only one output channel supported");
- }
-
- if format != AUDIO_FORMAT_I16 {
- panic!("for now, only i16, 16-bit signed audio format supported");
- }
-
- let sdl = get_sdl_context();
- let audio_subsystem = sdl.audio().unwrap();
-
- let desired_spec = AudioSpecDesired {
- freq: Some(sample_rate as i32),
- channels: Some(num_channels as u8), // mono
- samples: Some(1024) // buffer size, 1024 samples
- };
-
- let device = audio_subsystem.open_playback(None, &desired_spec, |spec| {
- // initialize the audio callback
- AudioCB {
- vm: vm.sys_state.mutex.clone(),
- cb: cb,
- num_channels: num_channels.into()
- }
- }).unwrap();
-
- // Start playback
- device.resume();
-
- // Keep the audio device alive
- unsafe {
- DEVICE = Some(device);
- }
-
- // TODO: return the device_id (u32)
- Value::from(0)
-}
diff --git a/vm/src/sys/mod.rs b/vm/src/sys/mod.rs
deleted file mode 100644
index ab725be8..00000000
--- a/vm/src/sys/mod.rs
+++ /dev/null
@@ -1,318 +0,0 @@
-pub mod window;
-pub mod audio;
-pub mod net;
-pub mod time;
-pub mod constants;
-
-extern crate sdl2;
-use std::collections::HashMap;
-use std::io::Write;
-use std::io::Read;
-use std::io::{stdout, stdin};
-use std::sync::{Arc, Weak, Mutex};
-use crate::vm::{Value, VM};
-use window::*;
-use audio::*;
-use net::*;
-use time::*;
-use constants::*;
-
-/// System call function signature
-/// Note: the in/out arg count should be fixed so
-/// that we can JIT syscalls efficiently
-#[derive(Copy, Clone)]
-pub enum SysCallFn
-{
- Fn0_0(fn(&mut VM)),
- Fn0_1(fn(&mut VM) -> Value),
-
- Fn1_0(fn(&mut VM, a0: Value)),
- Fn1_1(fn(&mut VM, a0: Value) -> Value),
-
- Fn2_0(fn(&mut VM, a0: Value, a1: Value)),
- Fn2_1(fn(&mut VM, a0: Value, a1: Value) -> Value),
-
- Fn3_0(fn(&mut VM, a0: Value, a1: Value, a2: Value)),
- Fn3_1(fn(&mut VM, a0: Value, a1: Value, a2: Value) -> Value),
-
- Fn4_0(fn(&mut VM, a0: Value, a1: Value, a2: Value, a3: Value)),
- Fn4_1(fn(&mut VM, a0: Value, a1: Value, a2: Value, a3: Value) -> Value),
-}
-
-impl SysCallFn
-{
- fn argc(&self) -> usize
- {
- match self {
- Self::Fn0_0(_) => 0,
- Self::Fn0_1(_) => 0,
- Self::Fn1_0(_) => 1,
- Self::Fn1_1(_) => 1,
- Self::Fn2_0(_) => 2,
- Self::Fn2_1(_) => 2,
- Self::Fn3_0(_) => 3,
- Self::Fn3_1(_) => 3,
- Self::Fn4_0(_) => 4,
- Self::Fn4_1(_) => 4,
- }
- }
-
- fn has_ret(&self) -> bool
- {
- match self {
- Self::Fn0_0(_) => false,
- Self::Fn0_1(_) => true,
- Self::Fn1_0(_) => false,
- Self::Fn1_1(_) => true,
- Self::Fn2_0(_) => false,
- Self::Fn2_1(_) => true,
- Self::Fn3_0(_) => false,
- Self::Fn3_1(_) => true,
- Self::Fn4_0(_) => false,
- Self::Fn4_1(_) => true,
- }
- }
-}
-
-/// SDL context (used for UI and audio)
-/// This is a global variable because it doesn't implement
-/// the Send trait, and so can't be referenced from another thread
-static mut SDL: Option = None;
-
-pub fn get_sdl_context() -> &'static mut sdl2::Sdl
-{
- unsafe
- {
- // Lazily initialize the SDL context
- if SDL.is_none() {
- SDL = Some(sdl2::init().unwrap());
- }
-
- SDL.as_mut().unwrap()
- }
-}
-
-pub struct SysState
-{
- /// Map of indices to syscall functions
- syscalls: [Option; SYSCALL_TBL_LEN],
-
- /// Weak reference to a mutex for the VM
- mutex: Weak>,
-
- /// Time subsystem state
- pub time_state: TimeState,
-
- /// Network subsystem state
- pub net_state: NetState,
-}
-
-impl SysState
-{
- pub fn new() -> Self
- {
- let mut sys_state = Self {
- syscalls: [None; SYSCALL_TBL_LEN],
- mutex: Weak::new(),
- time_state: TimeState::new(),
- net_state: NetState::default(),
- };
-
- sys_state.init_syscalls();
-
- sys_state
- }
-
- pub fn get_mutex(vm: VM) -> Arc>
- {
- // Move the VM into a mutex
- let vm_arc = Arc::new(Mutex::new(vm));
-
- // Store a weak reference to the mutex into the sys state
- vm_arc.lock().unwrap().sys_state.mutex = Arc::downgrade(&vm_arc);
-
- vm_arc
- }
-
- /// Register a syscall implementation
- pub fn reg_syscall(&mut self, const_idx: u16, fun: SysCallFn)
- {
- let desc = SYSCALL_DESCS[const_idx as usize].as_ref().unwrap();
-
- assert!(
- fun.argc() == desc.argc,
- "{} should accept {} args but implementation has {} params",
- desc.name,
- desc.argc,
- fun.argc()
- );
-
- assert!(fun.has_ret() == desc.has_ret);
-
- self.syscalls[const_idx as usize] = Some(fun);
- }
-
- /// Get the syscall with a given index
- pub fn get_syscall(&self, const_idx: u16) -> SysCallFn
- {
- if let Some(syscall_fn) = self.syscalls[const_idx as usize] {
- return syscall_fn;
- }
- else
- {
- panic!("unknown syscall \"{}\"", const_idx);
- }
- }
-
- fn init_syscalls(&mut self)
- {
- let mut syscalls = HashMap::::new();
-
- // Core VM syscalls
- self.reg_syscall(VM_HEAP_SIZE, SysCallFn::Fn0_1(vm_heap_size));
- self.reg_syscall(VM_RESIZE_HEAP, SysCallFn::Fn1_1(vm_resize_heap));
- self.reg_syscall(MEMSET, SysCallFn::Fn3_0(memset));
- self.reg_syscall(MEMSET32, SysCallFn::Fn3_0(memset32));
- self.reg_syscall(MEMCPY, SysCallFn::Fn3_0(memcpy));
- self.reg_syscall(MEMCMP, SysCallFn::Fn3_1(memcmp));
-
- self.reg_syscall(PRINT_I64, SysCallFn::Fn1_0(print_i64));
- self.reg_syscall(PRINT_F32, SysCallFn::Fn1_0(print_f32));
- self.reg_syscall(PRINT_STR, SysCallFn::Fn1_0(print_str));
- self.reg_syscall(PRINT_ENDL, SysCallFn::Fn0_0(print_endl));
- self.reg_syscall(PUTCHAR, SysCallFn::Fn1_1(putchar));
- self.reg_syscall(GETCHAR, SysCallFn::Fn0_1(getchar));
-
- self.reg_syscall(TIME_CURRENT_MS, SysCallFn::Fn0_1(time_current_ms));
- self.reg_syscall(TIME_DELAY_CB, SysCallFn::Fn2_0(time_delay_cb));
-
- self.reg_syscall(WINDOW_CREATE, SysCallFn::Fn4_1(window_create));
- self.reg_syscall(WINDOW_DRAW_FRAME, SysCallFn::Fn2_0(window_draw_frame));
- self.reg_syscall(WINDOW_ON_MOUSEMOVE, SysCallFn::Fn2_0(window_on_mousemove));
- self.reg_syscall(WINDOW_ON_MOUSEDOWN, SysCallFn::Fn2_0(window_on_mousedown));
- self.reg_syscall(WINDOW_ON_MOUSEUP, SysCallFn::Fn2_0(window_on_mouseup));
- self.reg_syscall(WINDOW_ON_KEYDOWN, SysCallFn::Fn2_0(window_on_keydown));
- self.reg_syscall(WINDOW_ON_KEYUP, SysCallFn::Fn2_0(window_on_keyup));
- self.reg_syscall(WINDOW_ON_TEXTINPUT, SysCallFn::Fn2_0(window_on_textinput));
-
- self.reg_syscall(AUDIO_OPEN_OUTPUT, SysCallFn::Fn4_1(audio_open_output));
-
- self.reg_syscall(NET_LISTEN, SysCallFn::Fn2_1(net_listen));
- self.reg_syscall(NET_ACCEPT, SysCallFn::Fn4_1(net_accept));
- self.reg_syscall(NET_READ, SysCallFn::Fn3_1(net_read));
- self.reg_syscall(NET_WRITE, SysCallFn::Fn3_1(net_write));
- self.reg_syscall(NET_CLOSE, SysCallFn::Fn1_0(net_close));
- }
-}
-
-fn vm_heap_size(vm: &mut VM) -> Value
-{
- Value::from(vm.heap_size())
-}
-
-fn vm_resize_heap(vm: &mut VM, num_bytes: Value) -> Value
-{
- let num_bytes = num_bytes.as_usize();
- let new_size = vm.resize_heap(num_bytes);
- Value::from(new_size)
-}
-
-fn memset(vm: &mut VM, dst_ptr: Value, val: Value, num_bytes: Value)
-{
- let dst_ptr = dst_ptr.as_usize();
- let val = val.as_u8();
- let num_bytes = num_bytes.as_usize();
-
- let mem_slice: &mut [u8] = vm.get_heap_slice(dst_ptr, num_bytes);
- mem_slice.fill(val);
-}
-
-fn memset32(vm: &mut VM, dst_ptr: Value, word: Value, num_words: Value)
-{
- let dst_ptr = dst_ptr.as_usize();
- let word = word.as_u32();
- let num_words = num_words.as_usize();
-
- let mem_slice: &mut [u32] = vm.get_heap_slice(dst_ptr, num_words);
- mem_slice.fill(word);
-}
-
-fn memcpy(vm: &mut VM, dst_ptr: Value, src_ptr: Value, num_bytes: Value)
-{
- let dst_ptr = dst_ptr.as_usize();
- let src_ptr = src_ptr.as_usize();
- let num_bytes = num_bytes.as_usize();
-
- // TODO: panic if slices are overlapping
-
- unsafe {
- let dst_ptr: *mut u8 = vm.get_heap_ptr(dst_ptr, num_bytes);
- let src_ptr: *mut u8 = vm.get_heap_ptr(src_ptr, num_bytes);
-
- std::ptr::copy_nonoverlapping(src_ptr, dst_ptr, num_bytes);
- }
-}
-
-fn memcmp(vm: &mut VM, ptr_a: Value, ptr_b: Value, num_bytes: Value) -> Value
-{
- let num_bytes = num_bytes.as_usize();
-
- unsafe {
- let ptr_a: *const libc::c_void = vm.get_heap_ptr(ptr_a.as_usize(), num_bytes);
- let ptr_b: *const libc::c_void = vm.get_heap_ptr(ptr_b.as_usize(), num_bytes);
-
- let result = libc::memcmp(ptr_a, ptr_b, num_bytes);
-
- Value::from(result as u64)
- }
-}
-
-fn print_i64(vm: &mut VM, v: Value)
-{
- let v = v.as_i64();
- print!("{}", v);
-}
-
-fn print_f32(vm: &mut VM, v: Value)
-{
- let v = v.as_f32();
- print!("{}", v);
-}
-
-/// Print a null-terminated UTF-8 string to stdout
-fn print_str(vm: &mut VM, str_ptr: Value)
-{
- let rust_str = vm.get_heap_str(str_ptr.as_usize());
- print!("{}", rust_str);
-}
-
-/// Print a newline characted to stdout
-fn print_endl(vm: &mut VM)
-{
- println!();
-}
-
-/// Write one byte of input to stdout.
-/// Analogous to C's getchar
-fn putchar(vm: &mut VM, byte: Value) -> Value
-{
- let byte = byte.as_u8();
- let bytes = byte.to_le_bytes();
-
- match stdout().write_all(&bytes) {
- Ok(_) => Value::from(byte),
- Err(_) => Value::from(-1 as i64),
- }
-}
-
-/// Read one byte of input from stdin.
-/// Analogous to C's getchar
-fn getchar(vm: &mut VM) -> Value
-{
- let ch = stdin().bytes().next();
-
- match ch {
- Some(Ok(ch)) => Value::from(ch as i64),
- None | Some(Err(_)) => Value::from(-1 as i64),
- }
-}
diff --git a/vm/src/sys/time.rs b/vm/src/sys/time.rs
deleted file mode 100644
index dd636fb4..00000000
--- a/vm/src/sys/time.rs
+++ /dev/null
@@ -1,93 +0,0 @@
-use std::time::{SystemTime, UNIX_EPOCH};
-use crate::vm::{VM, Value};
-
-// Callback function to be run at a given time stamp
-#[derive(Debug, Copy, Clone)]
-struct DelayCb
-{
- time_ms: u64,
- pc: u64,
-}
-
-pub struct TimeState
-{
- // List of delay callbacks
- delay_cbs: Vec,
-}
-
-impl TimeState
-{
- pub fn new() -> Self
- {
- Self {
- delay_cbs: Vec::default(),
- }
- }
-}
-
-/// Get the current time stamp in milliseconds
-pub fn get_time_ms() -> u64
-{
- SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64
-}
-
-/// Get the current time stamp in milliseconds since the unix epoch
-pub fn time_current_ms(vm: &mut VM) -> Value
-{
- Value::from(get_time_ms())
-}
-
-/// Call a callback function after a given delay in milliseconds
-pub fn time_delay_cb(vm: &mut VM, delay_ms: Value, callback_pc: Value)
-{
- let delay_ms = delay_ms.as_u64();
- let callback_pc = callback_pc.as_u64();
-
- let time_ms = get_time_ms();
-
- let cb_entry = DelayCb {
- time_ms: time_ms + delay_ms,
- pc: callback_pc
- };
-
- // Add the new callback to the list
- let time_state = &mut vm.sys_state.time_state;
- time_state.delay_cbs.push(cb_entry);
-
- // Sort the callbacks by decreasing trigger time
- time_state.delay_cbs.sort_by(|a, b| b.time_ms.cmp(&a.time_ms));
-}
-
-/// Compute the time untl the next delay callback needs to run
-pub fn time_until_next_cb(vm: &VM) -> Option
-{
- let time_state = &vm.sys_state.time_state;
-
- let last_cb = time_state.delay_cbs.last();
-
- match last_cb {
- None => return None,
- Some(cb) => {
- let cb_time = cb.time_ms;
- let cur_time = get_time_ms();
- let time_to = if cb_time > cur_time { cb_time - cur_time } else { 0 };
- return Some(time_to);
- }
- }
-}
-
-/// Get the list of PCs for callbacks to be run now
-pub fn get_cbs_to_run(vm: &mut VM) -> Vec
-{
- let time_state = &mut vm.sys_state.time_state;
-
- // Extract callbacks to run and extract the PCs
- let cur_time_ms = get_time_ms();
- let cbs_to_run = time_state.delay_cbs.iter().filter(|cb| cb.time_ms <= cur_time_ms);
- let pcs_to_run = cbs_to_run.map(|cb| cb.pc).collect();
-
- // Remove the callbacks to run from the list
- time_state.delay_cbs.retain(|cb| cb.time_ms > cur_time_ms);
-
- return pcs_to_run;
-}
diff --git a/vm/src/sys/window.rs b/vm/src/sys/window.rs
deleted file mode 100644
index cbf2c7a7..00000000
--- a/vm/src/sys/window.rs
+++ /dev/null
@@ -1,475 +0,0 @@
-// Simple display/window device
-
-extern crate sdl2;
-use sdl2::pixels::Color;
-use sdl2::event::Event;
-use sdl2::keyboard::Keycode;
-use sdl2::mouse::MouseButton;
-use sdl2::surface::Surface;
-use sdl2::render::Texture;
-use sdl2::render::TextureAccess;
-use sdl2::pixels::PixelFormatEnum;
-
-use std::time::Duration;
-
-use crate::sys::{SysState, get_sdl_context};
-use crate::vm::{VM, Value, ExitReason};
-
-/// SDL video subsystem
-/// This is a global variable because it doesn't implement
-/// the Send trait, and so can't be referenced from another thread
-static mut SDL_VIDEO: Option = None;
-
-/// Lazily initialize the SDL video subsystem
-fn get_video_subsystem() -> &'static mut sdl2::VideoSubsystem
-{
- unsafe
- {
- let sdl = get_sdl_context();
-
- if SDL_VIDEO.is_none() {
- SDL_VIDEO = Some(sdl.video().unwrap());
- }
-
- SDL_VIDEO.as_mut().unwrap()
- }
-}
-
-struct Window<'a>
-{
- width: u32,
- height: u32,
- window_id: u32,
-
- // SDL canvas to draw into
- canvas: sdl2::render::Canvas,
- texture_creator: sdl2::render::TextureCreator,
- texture: Option>,
-
- // Callbacks for mouse events
- cb_mousemove: u64,
- cb_mousedown: u64,
- cb_mouseup: u64,
-
- // Callbacks for keyboard events
- cb_keydown: u64,
- cb_keyup: u64,
- cb_textinput: u64,
-}
-
-// Note: we're leaving this global to avoid the Window lifetime
-// bubbling up everywhere.
-// TODO: eventually we will likely want to allow multiple windows
-static mut WINDOW: Option = None;
-
-fn get_window(window_id: u32) -> &'static mut Window<'static>
-{
- if window_id != 0 {
- panic!("for now, only one window supported");
- }
-
- unsafe {
- WINDOW.as_mut().unwrap()
- }
-}
-
-pub fn window_create(vm: &mut VM, width: Value, height: Value, title: Value, flags: Value) -> Value
-{
- unsafe {
- if WINDOW.is_some() {
- panic!("for now, only one window supported");
- }
- }
-
- let width: u32 = width.as_usize().try_into().unwrap();
- let height: u32 = height.as_usize().try_into().unwrap();
- let title_str = vm.get_heap_str(title.as_usize()).to_owned();
-
- let video_subsystem = get_video_subsystem();
-
- let window = video_subsystem.window(&title_str, width, height)
- .hidden()
- .position_centered()
- .build()
- .unwrap();
-
- let mut canvas = window.into_canvas().build().unwrap();
-
- canvas.set_draw_color(Color::RGB(0, 0, 0));
- canvas.clear();
- canvas.present();
-
- let texture_creator = canvas.texture_creator();
-
- let window = Window {
- width,
- height,
- window_id: 0,
- canvas,
- texture_creator,
- texture: None,
- cb_mousemove: 0,
- cb_mousedown: 0,
- cb_mouseup: 0,
- cb_keydown: 0,
- cb_keyup: 0,
- cb_textinput: 0,
- };
-
- unsafe {
- WINDOW = Some(window)
- }
-
- // TODO: return unique window id
- Value::from(0)
-}
-
-pub fn window_draw_frame(vm: &mut VM, window_id: Value, src_addr: Value)
-{
- let window = get_window(window_id.as_u32());
-
- // Get the address to copy pixel data from
- let data_len = (4 * window.width * window.height) as usize;
- let data_ptr = vm.get_heap_ptr(src_addr.as_usize(), data_len);
-
- // If no frame has been drawn yet
- if window.texture.is_none() {
- // Creat the texture to render into
- // Pixels use the BGRA byte order (0xAA_RR_GG_BB on a little-endian machine)
- window.texture = Some(window.texture_creator.create_texture(
- PixelFormatEnum::BGRA32,
- TextureAccess::Streaming,
- window.width,
- window.height
- ).unwrap());
-
- // We show and raise the window at the moment the first frame is drawn
- // This avoids showing a blank window too early
- window.canvas.window_mut().show();
- window.canvas.window_mut().raise();
- }
-
- // Update the texture
- let pitch = 4 * window.width as usize;
- let pixel_slice = unsafe { std::slice::from_raw_parts(data_ptr, data_len) };
- window.texture.as_mut().unwrap().update(None, pixel_slice, pitch).unwrap();
-
- // Copy the texture into the canvas
- window.canvas.copy(
- &window.texture.as_ref().unwrap(),
- None,
- None
- ).unwrap();
-
- // Update the screen with any rendering performed since the previous call
- window.canvas.present();
-}
-
-pub fn window_on_mousemove(vm: &mut VM, window_id: Value, cb: Value)
-{
- let window = get_window(window_id.as_u32());
- window.cb_mousemove = cb.as_u64();
-}
-
-pub fn window_on_mousedown(vm: &mut VM, window_id: Value, cb: Value)
-{
- let window = get_window(window_id.as_u32());
- window.cb_mousedown = cb.as_u64();
-}
-
-pub fn window_on_mouseup(vm: &mut VM, window_id: Value, cb: Value)
-{
- let window = get_window(window_id.as_u32());
- window.cb_mouseup = cb.as_u64();
-}
-
-pub fn window_on_keydown(vm: &mut VM, window_id: Value, cb: Value)
-{
- let window = get_window(window_id.as_u32());
- window.cb_keydown = cb.as_u64();
-}
-
-pub fn window_on_keyup(vm: &mut VM, window_id: Value, cb: Value)
-{
- let window = get_window(window_id.as_u32());
- window.cb_keyup = cb.as_u64();
-}
-
-pub fn window_on_textinput(vm: &mut VM, window_id: Value, cb: Value)
-{
- let window = get_window(window_id.as_u32());
- let video_subsystem = get_video_subsystem();
- video_subsystem.text_input().start();
- window.cb_textinput = cb.as_u64();
-}
-
-/// Process SDL events
-pub fn process_events(vm: &mut VM) -> ExitReason
-{
- let mut event_pump = get_sdl_context().event_pump().unwrap();
-
- // Process all pending events
- // See: https://docs.rs/sdl2/0.30.0/sdl2/event/enum.Event.html
- // TODO: we probably want to process window/input related events in window.rs ?
- for event in event_pump.poll_iter() {
- match event {
- Event::Quit { .. } => {
- return ExitReason::Exit(Value::from(0));
- }
-
- Event::MouseMotion { window_id, x, y, .. } => {
- if let ExitReason::Exit(val) = window_call_mousemove(vm, window_id, x, y) {
- return ExitReason::Exit(val);
- }
- }
-
- Event::MouseButtonDown { window_id, which, mouse_btn, x, y, .. } => {
- if let ExitReason::Exit(val) = window_call_mousedown(vm, window_id, mouse_btn, x, y) {
- return ExitReason::Exit(val);
- }
- }
-
- Event::MouseButtonUp { window_id, which, mouse_btn, x, y, .. } => {
- if let ExitReason::Exit(val) = window_call_mouseup(vm, window_id, mouse_btn, x, y) {
- return ExitReason::Exit(val);
- }
- }
-
- Event::KeyDown { window_id, keycode: Some(keycode), .. } => {
- if let ExitReason::Exit(val) = window_call_keydown(vm, window_id, keycode) {
- return ExitReason::Exit(val);
- }
- }
-
- Event::KeyUp { window_id, keycode: Some(keycode), .. } => {
- if let ExitReason::Exit(val) = window_call_keyup(vm, window_id, keycode) {
- return ExitReason::Exit(val);
- }
- }
-
- Event::TextInput { window_id, text, .. } => {
- // For each UTF-8 byte of input
- for ch in text.bytes() {
- if let ExitReason::Exit(val) = window_call_textinput(vm, window_id, ch) {
- return ExitReason::Exit(val);
- }
- }
- }
-
- _ => {}
- }
- }
-
- return ExitReason::default();
-}
-
-// TODO: functions to process window-related events
-// TODO: we should return the exit reason?
-// this is gonna be awkward if we have audio processing threads/processes and such?
-// though I suppose exit would just end those processes
-
-// TODO: this is just for testing
-// we should handle window-related events here instead
-fn window_call_mousemove(vm: &mut VM, window_id: u32, x: i32, y: i32) -> ExitReason
-{
- let window = get_window(0);
- let cb = window.cb_mousemove;
-
- if cb == 0 {
- return ExitReason::default();
- }
-
- vm.call(cb, &[Value::from(window.window_id), Value::from(x), Value::from(y)])
-}
-
-/*
-MouseButtonDown {
- timestamp: u32,
- window_id: u32,
- which: u32, => this is a mouse id
- mouse_btn: MouseButton,
- x: i32,
- y: i32,
-},
-*/
-fn window_call_mousedown(vm: &mut VM, window_id: u32, mouse_btn: MouseButton, x: i32, y: i32) -> ExitReason
-{
- let window = get_window(0);
- let cb = window.cb_mousedown;
-
- if cb == 0 {
- return ExitReason::default();
- }
-
- // TODO: ignore SDL_TOUCH_MOUSEID
- // where is that defined in Rust?
- // or only support mouse id 0?
- //println!("mouse_id={}", mouse_id);
-
- let btn_id = match mouse_btn {
- MouseButton::Left => 0,
- MouseButton::Middle => 1,
- MouseButton::Right => 2,
- MouseButton::X1 => 3,
- MouseButton::X2 => 4,
- MouseButton::Unknown => {
- return ExitReason::default();
- }
- };
-
- vm.call(cb, &[
- Value::from(window.window_id),
- Value::from(btn_id),
- Value::from(x),
- Value::from(y),
- ])
-}
-
-fn window_call_mouseup(vm: &mut VM, window_id: u32, mouse_btn: MouseButton, x: i32, y: i32) -> ExitReason
-{
- let window = get_window(0);
- let cb = window.cb_mouseup;
-
- if cb == 0 {
- return ExitReason::default();
- }
-
- // TODO: ignore SDL_TOUCH_MOUSEID
- // where is that defined in Rust?
- // or only support mouse id 0?
- //println!("mouse_id={}", mouse_id);
-
- let btn_id = match mouse_btn {
- MouseButton::Left => 0,
- MouseButton::Middle => 1,
- MouseButton::Right => 2,
- MouseButton::X1 => 3,
- MouseButton::X2 => 4,
- MouseButton::Unknown => {
- return ExitReason::default();
- }
- };
-
- vm.call(cb, &[
- Value::from(window.window_id),
- Value::from(btn_id),
- Value::from(x),
- Value::from(y),
- ])
-}
-
-fn translate_keycode(sdl_keycode: Keycode) -> Option
-{
- use crate::sys::constants::*;
-
- // https://docs.rs/sdl2/0.30.0/sdl2/keyboard/enum.Keycode.html
- match sdl_keycode {
- Keycode::A => Some(KEY_A),
- Keycode::B => Some(KEY_B),
- Keycode::C => Some(KEY_C),
- Keycode::D => Some(KEY_D),
- Keycode::E => Some(KEY_E),
- Keycode::F => Some(KEY_F),
- Keycode::G => Some(KEY_G),
- Keycode::H => Some(KEY_H),
- Keycode::I => Some(KEY_I),
- Keycode::J => Some(KEY_J),
- Keycode::K => Some(KEY_K),
- Keycode::L => Some(KEY_L),
- Keycode::M => Some(KEY_M),
- Keycode::N => Some(KEY_N),
- Keycode::O => Some(KEY_O),
- Keycode::P => Some(KEY_P),
- Keycode::Q => Some(KEY_Q),
- Keycode::R => Some(KEY_R),
- Keycode::S => Some(KEY_S),
- Keycode::T => Some(KEY_T),
- Keycode::U => Some(KEY_U),
- Keycode::V => Some(KEY_V),
- Keycode::W => Some(KEY_W),
- Keycode::X => Some(KEY_X),
- Keycode::Y => Some(KEY_Y),
- Keycode::Z => Some(KEY_Z),
-
- Keycode::Num0 => Some(KEY_NUM0),
- Keycode::Num1 => Some(KEY_NUM1),
- Keycode::Num2 => Some(KEY_NUM2),
- Keycode::Num3 => Some(KEY_NUM3),
- Keycode::Num4 => Some(KEY_NUM4),
- Keycode::Num5 => Some(KEY_NUM5),
- Keycode::Num6 => Some(KEY_NUM6),
- Keycode::Num7 => Some(KEY_NUM7),
- Keycode::Num8 => Some(KEY_NUM8),
- Keycode::Num9 => Some(KEY_NUM9),
-
- Keycode::Comma => Some(KEY_COMMA),
- Keycode::Period => Some(KEY_PERIOD),
- Keycode::Slash => Some(KEY_SLASH),
- Keycode::Colon => Some(KEY_COLON),
- Keycode::Semicolon => Some(KEY_SEMICOLON),
- Keycode::Equals => Some(KEY_EQUALS),
- Keycode::Question => Some(KEY_QUESTION),
-
- Keycode::Escape => Some(KEY_ESCAPE),
- Keycode::Backspace => Some(KEY_BACKSPACE),
- Keycode::Left => Some(KEY_LEFT),
- Keycode::Right => Some(KEY_RIGHT),
- Keycode::Up => Some(KEY_UP),
- Keycode::Down => Some(KEY_DOWN),
- Keycode::Space => Some(KEY_SPACE),
- Keycode::Return => Some(KEY_RETURN),
- Keycode::LShift => Some(KEY_SHIFT),
- Keycode::RShift => Some(KEY_SHIFT),
- Keycode::Tab => Some(KEY_TAB),
-
- _ => None
- }
-}
-
-fn window_call_keydown(vm: &mut VM, window_id: u32, keycode: Keycode) -> ExitReason
-{
- let window = get_window(0);
- let cb = window.cb_keydown;
-
- if cb == 0 {
- return ExitReason::default();
- }
-
- let keycode = translate_keycode(keycode);
-
- if let Some(keycode) = keycode {
- vm.call(cb, &[Value::from(window.window_id), Value::from(keycode)])
- } else {
- ExitReason::default()
- }
-}
-
-fn window_call_keyup(vm: &mut VM, window_id: u32, keycode: Keycode) -> ExitReason
-{
- let window = get_window(0);
- let cb = window.cb_keyup;
-
- if cb == 0 {
- return ExitReason::default();
- }
-
- let keycode = translate_keycode(keycode);
-
- if let Some(keycode) = keycode {
- vm.call(cb, &[Value::from(window.window_id), Value::from(keycode)])
- } else {
- ExitReason::default()
- }
-}
-
-fn window_call_textinput(vm: &mut VM, window_id: u32, utf8_byte: u8) -> ExitReason
-{
- let window = get_window(0);
- let cb = window.cb_textinput;
-
- if cb == 0 {
- return ExitReason::default();
- }
-
- vm.call(cb, &[Value::from(window.window_id), Value::from(utf8_byte)])
-}
diff --git a/vm/src/time.rs b/vm/src/time.rs
new file mode 100644
index 00000000..0e72ddc7
--- /dev/null
+++ b/vm/src/time.rs
@@ -0,0 +1,14 @@
+use std::time::{SystemTime, UNIX_EPOCH};
+use crate::vm::{Thread, Value};
+
+/// Get the current time stamp in milliseconds
+pub fn get_time_ms() -> u64
+{
+ SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u64
+}
+
+/// Get the current time stamp in milliseconds since the unix epoch
+pub fn time_current_ms(thread: &mut Thread) -> Value
+{
+ Value::from(get_time_ms())
+}
diff --git a/vm/src/vm.rs b/vm/src/vm.rs
index ba0c133f..32827954 100644
--- a/vm/src/vm.rs
+++ b/vm/src/vm.rs
@@ -1,7 +1,11 @@
-use std::mem::{transmute, size_of};
-use std::collections::HashSet;
+use std::sync::{Arc, Mutex};
+use std::sync::atomic::{Ordering, AtomicU64};
+use std::mem::{transmute, size_of, align_of};
+use std::collections::{HashSet, HashMap};
+use std::thread;
use std::ffi::CStr;
-use crate::sys::*;
+use crate::host::*;
+use crate::program::Program;
/// Instruction opcodes
/// Note: commonly used upcodes should be in the [0, 127] range (one byte)
@@ -213,6 +217,30 @@ pub enum Op
load_global_u64
*/
+ // Atomic load with acquire semantics
+ // atomic_load (addr)
+ atomic_load_u64,
+
+ // Atomic store with release semantics
+ // atomic_store (addr) (value)
+ atomic_store_u64,
+
+ // Compare-and-swap
+ // Uses acquire semantics on success, relaxed on failure.
+ // Store has relaxed semantics.
+ // This instruction can be used to implement spin locks.
+ // atomic_cas (addr) (cmp-val) (store-val)
+ // Pushes the value found at the memory address
+ atomic_cas_u64,
+
+ // Set thread-local variable
+ // thread_set (val)
+ thread_set,
+
+ // Get thread-local variable
+ // thread_get
+ thread_get,
+
// NOTE: may want to wait for this because it's not RISC,
// but it could help reduce code flag
// NOTE: should this insn have a jump offset built in?
@@ -252,16 +280,10 @@ pub enum Op
// syscall (arg0, arg1, ..., argN)
syscall,
- // Return to caller function, or
- // Return to the UVM event loop without terminating execution
+ // Return to caller function or end thread
// ret (value)
ret,
- // Terminate program execution
- // This stops the UVM event loop
- // exit (value)
- exit,
-
// NOTE: last opcode must have value < 255
// Currently, every opcode is just one byte long,
// and we hope to keep it that way, but the value
@@ -392,144 +414,249 @@ impl From for Value {
}
}
-pub struct MemBlock
+struct StackFrame
{
- data: Vec
+ // Previous base pointer at the time of call
+ prev_bp: usize,
+
+ // Return address
+ ret_addr: usize,
+
+ // Argument count
+ argc: usize,
+}
+
+
+
+
+
+
+struct MemBlock
+{
+ // Underlying memory block
+ mem_block: *mut u8,
+
+ // Total size of the mapped memory block
+ mapping_size: usize,
+
+ // System page size
+ page_size: usize,
+
+ // Currently visible/accessible size
+ // This is a box because we need a pointer
+ // To access this value from threads using MemView
+ cur_size: Box,
}
impl MemBlock
{
- pub fn new() -> Self
+ pub fn new() -> MemBlock
{
- Self {
- data: Vec::default()
+ // Try to allocate a very large block first (512GB)
+ let start_size: usize = 512 * 1024 * 1024 * 1024;
+
+ let mut alloc_size = start_size;
+
+ let mut mem_block;
+
+ // Try to allocate a contiguous block of memory that is
+ // as large as possible
+ loop {
+ // PROT_NONE means the data cannot be accessed yet
+ mem_block = unsafe {libc::mmap(
+ std::ptr::null_mut(),
+ alloc_size,
+ libc::PROT_NONE,
+ libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
+ -1,
+ 0
+ )};
+
+ if mem_block != libc::MAP_FAILED {
+ break;
+ }
+
+ println!("mmap failed, trying again");
+
+ // Try again with a smaller alloc size
+ alloc_size /= 2;
+ assert!(alloc_size > 1);
}
- }
- /// Get the memory block size in bytes
- pub fn len(&self) -> usize
- {
- self.data.len()
+ assert!(alloc_size >= 1024);
+
+ let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
+ assert!(page_size % 8 == 0);
+
+ MemBlock {
+ mem_block: unsafe { transmute(mem_block) },
+ mapping_size: alloc_size,
+ page_size,
+ cur_size: Box::new(0),
+ }
}
- /// Resize to a new size in bytes
- pub fn resize(&mut self, mut num_bytes: usize) -> usize
+ /// Grow to a new size in bytes
+ /// This operation is a no-op if the existing size
+ /// is greater or equal to the requested size
+ ///
+ /// Note: this operation must be guarded by the VM
+ pub fn grow(&mut self, mut new_size: usize) -> usize
{
// Round up to a page size multiple
- let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize;
- assert!(page_size % 8 == 0);
- let rem = num_bytes % page_size;
+ let rem = new_size % self.page_size;
if rem != 0 {
- num_bytes += page_size - rem;
+ new_size += self.page_size - rem;
}
+ assert!(new_size % self.page_size == 0);
- assert!(num_bytes % page_size == 0);
- self.data.resize(num_bytes, 0);
+ let cur_size = *self.cur_size;
- num_bytes
- }
+ // Growing the memory block, need to map as read | write
+ if new_size <= cur_size {
+ return cur_size;
+ }
- pub fn push_op(&mut self, op: Op)
- {
- self.data.push(op as u8);
- }
+ // Compute the address from which to mmap
+ let map_addr = unsafe { transmute(self.mem_block.add(cur_size)) };
- pub fn push_u8(&mut self, val: u8)
- {
- self.data.push(val);
+ let map_size = new_size - cur_size;
+ assert!(map_size % self.page_size == 0);
+
+ let mem_block = unsafe {libc::mmap(
+ map_addr,
+ map_size,
+ libc::PROT_WRITE | libc::PROT_READ,
+ libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_FIXED,
+ -1,
+ 0
+ )};
+
+ if mem_block == libc::MAP_FAILED {
+ panic!();
+ }
+
+ // Update the currently accessible size
+ *self.cur_size = new_size;
+
+ new_size
}
- pub fn push_u16(&mut self, val: u16)
+ // Create a new thread-local view on this memory block
+ fn new_view(&self) -> MemView
{
- for byte in val.to_le_bytes() {
- self.data.push(byte);
+ MemView {
+ mem_block: self.mem_block,
+ cur_size: &*self.cur_size,
}
}
+}
- pub fn push_i8(&mut self, val: i8)
+struct MemView
+{
+ // Underlying memory block
+ mem_block: *mut u8,
+
+ // Pointer to size variable from parent MemBlock
+ cur_size: *const usize,
+}
+
+unsafe impl Send for MemView {}
+
+impl MemView
+{
+ pub fn size_bytes(&self) -> usize
{
- self.data.push(val as u8);
+ unsafe { *self.cur_size }
}
- pub fn push_i32(&mut self, val: i32)
+ /// Get a mutable pointer to an address/offset
+ pub fn get_ptr_mut(&mut self, addr: usize, num_elems: usize) -> *mut T
{
- for byte in val.to_le_bytes() {
- self.data.push(byte);
+ // Check that the address is within bounds
+ let cur_size = unsafe { *self.cur_size };
+ if addr + std::mem::size_of::() * num_elems > cur_size {
+ panic!("attempting to access memory slice past end of heap");
}
- }
- pub fn push_u32(&mut self, val: u32)
- {
- for byte in val.to_le_bytes() {
- self.data.push(byte);
+ // Check that the address is aligned
+ if addr & (align_of::() - 1) != 0 {
+ panic!(
+ "attempting to access data of type {} at unaligned address {}",
+ std::any::type_name::(),
+ addr
+ );
+ }
+
+ unsafe {
+ let ptr: *mut u8 = self.mem_block.add(addr);
+ transmute::<*mut u8 , *mut T>(ptr)
}
}
- pub fn push_u64(&mut self, val: u64)
+ /// Get a constant pointer to an address/offset
+ pub fn get_ptr(&self, addr: usize, num_elems: usize) -> *const T
{
- for byte in val.to_le_bytes() {
- self.data.push(byte);
+ // Check that the address is within bounds
+ let cur_size = unsafe { *self.cur_size };
+ if addr + std::mem::size_of::() * num_elems > cur_size {
+ panic!("attempting to access memory slice past end of heap");
+ }
+
+ // Check that the address is aligned
+ if addr & (size_of::() - 1) != 0 {
+ panic!(
+ "attempting to access data of type {} at unaligned address",
+ std::any::type_name::()
+ );
+ }
+
+ unsafe {
+ let ptr: *mut u8 = self.mem_block.add(addr);
+ transmute::<*mut u8 , *const T>(ptr)
}
}
- /// Write a value at the given address
- pub fn write(&mut self, pos: usize, val: T) where T: Copy
+ /// Get a mutable slice inside this memory block
+ pub fn get_slice_mut(&mut self, addr: usize, num_elems: usize) -> &mut [T]
{
unsafe {
- let buf_ptr = self.data.as_mut_ptr();
- let val_ptr = transmute::<*mut u8 , *mut T>(buf_ptr.add(pos));
- std::ptr::write_unaligned(val_ptr, val);
+ let start_ptr = self.get_ptr_mut(addr, num_elems);
+ std::slice::from_raw_parts_mut(start_ptr, num_elems)
}
}
/// Read a value at the current PC and then increment the PC
pub fn read_pc(&self, pc: &mut usize) -> T where T: Copy
{
+ // Check that the address is within bounds
+ let cur_size = unsafe { *self.cur_size };
+ if *pc + std::mem::size_of::() > cur_size {
+ // TODO: output name of type being read
+ panic!("pc outside of bounds of code space");
+ }
+
unsafe {
- let buf_ptr = self.data.as_ptr();
- let val_ptr = transmute::<*const u8 , *const T>(buf_ptr.add(*pc));
+ let val_ptr = transmute::<*const u8 , *const T>(self.mem_block.add(*pc));
*pc += size_of::();
std::ptr::read_unaligned(val_ptr)
}
}
}
-struct StackFrame
-{
- // Previous base pointer at the time of call
- prev_bp: usize,
-
- // Return address
- ret_addr: usize,
-
- // Argument count
- argc: usize,
-}
-
-pub enum ExitReason
+pub struct Thread
{
- Return(Value),
- Exit(Value),
- //Panic,
-}
+ // Thread id
+ pub id: u64,
-impl Default for ExitReason
-{
- fn default() -> ExitReason {
- ExitReason::Return(Value::from(0))
- }
-}
+ // Parent VM
+ pub vm: Arc>,
-pub struct VM
-{
- // Host system state
- pub sys_state: SysState,
+ // Code memory block
+ code: MemView,
- // Heap memory space
- heap: MemBlock,
-
- // Code memory space
- code: MemBlock,
+ // Heap memory block
+ heap: MemView,
// Value stack
stack: Vec,
@@ -537,44 +664,25 @@ pub struct VM
// List of stack frames (activation records)
frames: Vec,
- // Count of executed instructions
- #[cfg(feature = "count_insns")]
- insn_count: u64,
+ // Thread-local variables
+ locals: Vec,
}
-impl VM
+impl Thread
{
- pub fn new(mut code: MemBlock, mut heap: MemBlock, syscalls: HashSet) -> Self
+ fn new(tid: u64, vm: Arc>, code: MemView, heap: MemView) -> Self
{
- // Initialize the system state
- let sys_state = SysState::new();
-
- // Resize the code and heap space to a page size multiple
- code.resize(code.len());
- heap.resize(heap.len());
-
Self {
- sys_state,
+ id: tid,
+ vm,
code,
heap,
stack: Vec::default(),
frames: Vec::default(),
- #[cfg(feature = "count_insns")]
- insn_count: 0,
+ locals: Vec::default(),
}
}
- #[cfg(feature = "count_insns")]
- pub fn get_insn_count(&self) -> u64
- {
- self.insn_count
- }
-
- pub fn stack_size(&self) -> usize
- {
- self.stack.len()
- }
-
pub fn push(&mut self, val: T) where Value: From
{
self.stack.push(Value::from(val));
@@ -591,72 +699,36 @@ impl VM
/// Get the current size of the heap in bytes
pub fn heap_size(&self) -> usize
{
- self.heap.len()
+ self.heap.size_bytes()
}
- /// Resize the heap to a new size in bytes
- pub fn resize_heap(&mut self, num_bytes: usize) -> usize
+ /// Get a mutable pointer to an address/offset in the heap
+ pub fn get_heap_ptr_mut(&mut self, addr: usize, num_elems: usize) -> *mut T
{
- self.heap.resize(num_bytes)
- }
-
- // FIXME: this function should be marked unsafe
- //
- /// Get a pointer to an address/offset in the heap
- pub fn get_heap_ptr(&mut self, addr: usize, num_elems: usize) -> *mut T
- {
- if addr + std::mem::size_of::() * num_elems > self.heap.len() {
- panic!("attempting to access memory slice past end of heap");
- }
-
- if addr & (size_of::() - 1) != 0 {
- panic!(
- "attempting to access data of type {} at unaligned address",
- std::any::type_name::()
- );
- }
-
- unsafe {
- let heap_ptr: *mut u8 = self.heap.data.as_mut_ptr().add(addr);
- transmute::<*mut u8 , *mut T>(heap_ptr)
- }
+ self.heap.get_ptr_mut(addr, num_elems)
}
/// Get a mutable slice to access a memory region in the heap
- pub fn get_heap_slice(&mut self, addr: usize, num_elems: usize) -> &mut [T]
+ pub fn get_heap_slice_mut(&mut self, addr: usize, num_elems: usize) -> &mut [T]
{
- if addr + std::mem::size_of::() * num_elems > self.heap.len() {
- panic!("attempting to access memory slice past end of heap");
- }
-
- if addr & (size_of::() - 1) != 0 {
- panic!(
- "attempting to access unaligned memory slice of type {}",
- std::any::type_name::()
- );
- }
-
- unsafe {
- let heap_ptr: *mut u8 = self.heap.data.as_mut_ptr().add(addr);
- let start_ptr = transmute::<*mut u8 , *mut T>(heap_ptr);
- std::slice::from_raw_parts_mut(start_ptr, num_elems)
- }
+ self.heap.get_slice_mut(addr, num_elems)
}
- /// Copy an UTF-8 string at a given address in the heap
- pub fn get_heap_str(&mut self, str_ptr: usize) -> &str
+ /// Read an UTF-8 string at a given address in the heap into a Rust string
+ pub fn get_heap_str(&self, str_ptr: usize) -> &str
{
// Verify that there is a null-terminator for this string
// within the bounds of the heap
let mut str_len = 0;
loop
{
- let char_idx = str_ptr + str_len;
- if char_idx >= self.heap.len() {
+ let char_ptr = str_ptr + str_len;
+ if char_ptr >= self.heap.size_bytes() {
panic!("string is not properly null-terminated");
}
- if self.heap.data[char_idx] == 0 {
+ let byte_ptr: *const u8 = self.heap.get_ptr(char_ptr, 1);
+ if unsafe { *byte_ptr } == 0 {
break;
}
@@ -664,14 +736,14 @@ impl VM
}
// Convert the string to a Rust string
- let char_ptr = self.get_heap_ptr(str_ptr, str_len);
+ let char_ptr = self.heap.get_ptr(str_ptr, str_len);
let c_str = unsafe { CStr::from_ptr(char_ptr as *const i8) };
let rust_str = c_str.to_str().unwrap();
rust_str
}
/// Call a function at a given address
- pub fn call(&mut self, callee_pc: u64, args: &[Value]) -> ExitReason
+ pub fn call(&mut self, callee_pc: u64, args: &[Value]) -> Value
{
assert!(self.stack.len() == 0);
assert!(self.frames.len() == 0);
@@ -695,15 +767,6 @@ impl VM
// For each instruction to execute
loop
{
- #[cfg(feature = "count_insns")]
- {
- self.insn_count += 1;
- }
-
- if pc >= self.code.len() {
- panic!("pc outside bounds of code space")
- }
-
let op = self.code.read_pc::(&mut pc);
//dbg!(op);
@@ -1343,28 +1406,28 @@ impl VM
Op::load_u8 => {
let addr = self.pop().as_usize();
- let heap_ptr = self.get_heap_ptr(addr, 1);
+ let heap_ptr = self.get_heap_ptr_mut(addr, 1);
let val: u8 = unsafe { *heap_ptr };
self.push(val);
}
Op::load_u16 => {
let addr = self.pop().as_usize();
- let heap_ptr = self.get_heap_ptr(addr, 1);
+ let heap_ptr = self.get_heap_ptr_mut(addr, 1);
let val: u16 = unsafe { *heap_ptr };
self.push(val);
}
Op::load_u32 => {
let addr = self.pop().as_usize();
- let heap_ptr = self.get_heap_ptr(addr, 1);
+ let heap_ptr = self.get_heap_ptr_mut(addr, 1);
let val: u32 = unsafe { *heap_ptr };
self.push(val);
}
Op::load_u64 => {
let addr = self.pop().as_usize();
- let heap_ptr = self.get_heap_ptr(addr, 1);
+ let heap_ptr = self.get_heap_ptr_mut(addr, 1);
let val: u64 = unsafe { *heap_ptr };
self.push(val);
}
@@ -1372,31 +1435,95 @@ impl VM
Op::store_u8 => {
let val = self.pop().as_u8();
let addr = self.pop().as_usize();
- let heap_ptr = self.get_heap_ptr(addr, 1);
+ let heap_ptr = self.get_heap_ptr_mut(addr, 1);
unsafe { *heap_ptr = val; }
}
Op::store_u16 => {
let val = self.pop().as_u16();
let addr = self.pop().as_usize();
- let heap_ptr = self.get_heap_ptr(addr, 1);
+ let heap_ptr = self.get_heap_ptr_mut(addr, 1);
unsafe { *heap_ptr = val; }
}
Op::store_u32 => {
let val = self.pop().as_u32();
let addr = self.pop().as_usize();
- let heap_ptr = self.get_heap_ptr(addr, 1);
+ let heap_ptr = self.get_heap_ptr_mut(addr, 1);
unsafe { *heap_ptr = val; }
}
Op::store_u64 => {
let val = self.pop().as_u64();
let addr = self.pop().as_usize();
- let heap_ptr = self.get_heap_ptr(addr, 1);
+ let heap_ptr = self.get_heap_ptr_mut(addr, 1);
unsafe { *heap_ptr = val; }
}
+ Op::atomic_load_u64 => {
+ let addr = self.pop().as_usize();
+ let heap_ptr = self.get_heap_ptr_mut(addr, 1);
+ let atomic = unsafe {AtomicU64::from_ptr(heap_ptr) };
+ let val = atomic.load(Ordering::Acquire);
+ self.push(Value::from(val));
+ }
+
+ Op::atomic_store_u64 => {
+ let val = self.pop().as_u64();
+ let addr = self.pop().as_usize();
+ let heap_ptr = self.get_heap_ptr_mut(addr, 1);
+ let atomic = unsafe {AtomicU64::from_ptr(heap_ptr) };
+ atomic.store(val, Ordering::Release);
+ }
+
+ // Compare-and-swap
+ // Uses acquire semantics on success, relaxed on failure.
+ // Store has relaxed semantics.
+ // This instruction can be used to implement spin locks.
+ // atomic_cas (addr) (cmp-val) (store-val)
+ // Pushes the value found at the memory address
+ Op::atomic_cas_u64 => {
+ let store_val = self.pop().as_u64();
+ let cmp_val = self.pop().as_u64();
+ let addr = self.pop().as_usize();
+
+ let heap_ptr = self.get_heap_ptr_mut(addr, 1);
+ let atomic = unsafe {AtomicU64::from_ptr(heap_ptr) };
+
+ let result = atomic.compare_exchange(
+ cmp_val,
+ store_val,
+ Ordering::Acquire,
+ Ordering::Relaxed,
+ );
+
+ match result {
+ Ok(val) => self.push(Value::from(val)),
+ Err(actual_val) => self.push(Value::from(actual_val)),
+ }
+ }
+
+ Op::thread_set => {
+ let idx = self.code.read_pc::(&mut pc) as usize;
+ let val = self.pop();
+
+ if idx >= self.locals.len() {
+ self.locals.resize(idx + 1, Value::from(0));
+ }
+
+ self.locals[idx] = val;
+ }
+
+ Op::thread_get => {
+ let idx = self.code.read_pc::(&mut pc) as usize;
+
+ if idx >= self.locals.len() {
+ self.push(Value::from(0));
+ } else {
+ self.push(self.locals[idx])
+ }
+ }
+
Op::jmp => {
let offset = self.code.read_pc::(&mut pc) as isize;
pc = ((pc as isize) + offset) as usize;
@@ -1462,51 +1589,51 @@ impl VM
Op::syscall => {
let syscall_idx = self.code.read_pc::(&mut pc);
- let syscall_fn = self.sys_state.get_syscall(syscall_idx);
+ let syscall_fn = get_syscall(syscall_idx);
match syscall_fn
{
- SysCallFn::Fn0_0(fun) => {
+ HostFn::Fn0_0(fun) => {
fun(self)
}
- SysCallFn::Fn0_1(fun) => {
+ HostFn::Fn0_1(fun) => {
let v = fun(self);
self.push(v);
}
- SysCallFn::Fn1_0(fun) => {
+ HostFn::Fn1_0(fun) => {
let a0 = self.pop();
fun(self, a0)
}
- SysCallFn::Fn1_1(fun) => {
+ HostFn::Fn1_1(fun) => {
let a0 = self.pop();
let v = fun(self, a0);
self.push(v);
}
- SysCallFn::Fn2_0(fun) => {
+ HostFn::Fn2_0(fun) => {
let a1 = self.pop();
let a0 = self.pop();
fun(self, a0, a1)
}
- SysCallFn::Fn2_1(fun) => {
+ HostFn::Fn2_1(fun) => {
let a1 = self.pop();
let a0 = self.pop();
let v = fun(self, a0, a1);
self.push(v);
}
- SysCallFn::Fn3_0(fun) => {
+ HostFn::Fn3_0(fun) => {
let a2 = self.pop();
let a1 = self.pop();
let a0 = self.pop();
fun(self, a0, a1, a2)
}
- SysCallFn::Fn3_1(fun) => {
+ HostFn::Fn3_1(fun) => {
let a2 = self.pop();
let a1 = self.pop();
let a0 = self.pop();
@@ -1514,7 +1641,7 @@ impl VM
self.push(v);
}
- SysCallFn::Fn4_0(fun) => {
+ HostFn::Fn4_0(fun) => {
let a3 = self.pop();
let a2 = self.pop();
let a1 = self.pop();
@@ -1522,7 +1649,7 @@ impl VM
fun(self, a0, a1, a2, a3)
}
- SysCallFn::Fn4_1(fun) => {
+ HostFn::Fn4_1(fun) => {
let a3 = self.pop();
let a2 = self.pop();
let a1 = self.pop();
@@ -1533,17 +1660,6 @@ impl VM
}
}
- Op::exit => {
- if self.stack.len() <= bp {
- panic!("exit with no return value on stack");
- }
-
- let val = self.pop();
- self.stack.clear();
- self.frames.clear();
- return ExitReason::Exit(val);
- }
-
Op::ret => {
if self.stack.len() <= bp {
panic!("ret with no return value on stack");
@@ -1555,7 +1671,7 @@ impl VM
if self.frames.len() == 1 {
self.stack.clear();
self.frames.clear();
- return ExitReason::Return(ret_val);
+ return ret_val;
}
assert!(self.frames.len() > 0);
@@ -1578,6 +1694,142 @@ impl VM
}
}
+pub struct VM
+{
+ // Heap memory space
+ heap: MemBlock,
+
+ // Code memory space
+ code: MemBlock,
+
+ // Next thread id to assign
+ next_tid: u64,
+
+ // Map from actor ids to thread join handles
+ threads: HashMap>,
+
+ // Reference to self
+ // Needed to instantiate actors
+ vm: Option>>,
+}
+
+// Needed to send Arc> to thread
+unsafe impl Send for VM {}
+
+impl VM
+{
+ pub fn new(prog: Program) -> Arc>
+ {
+ let mut code = MemBlock::new();
+ let mut heap = MemBlock::new();
+
+ // Resize the code and memory blocks to accomodate the program
+ code.grow(prog.code.len());
+ heap.grow(prog.data.len());
+
+ // Copy the program code
+ let mut code_view = code.new_view();
+ let mut code_slice: &mut [u8] = code_view.get_slice_mut(0, prog.code.len());
+ code_slice.clone_from_slice(prog.code.as_slice());
+
+ // Copy the program data
+ let mut heap_view = heap.new_view();
+ let mut heap_slice: &mut [u8] = heap_view.get_slice_mut(0, prog.data.len());
+ heap_slice.clone_from_slice(prog.data.as_slice());
+
+ let vm = Self {
+ code,
+ heap,
+ next_tid: 0,
+ threads: HashMap::default(),
+ vm: None,
+ };
+
+ let vm = Arc::new(Mutex::new(vm));
+
+ // Store a reference to the mutex on the VM
+ // This is so we can pass this reference to threads
+ vm.lock().unwrap().vm = Some(vm.clone());
+
+ vm
+ }
+
+ /// Grow the heap to a new size in bytes
+ pub fn grow_heap(&mut self, num_bytes: usize) -> usize
+ {
+ self.heap.grow(num_bytes)
+ }
+
+ // Create a new thread object without beginning execution
+ pub fn new_thread(vm: &Arc>) -> Thread
+ {
+ // Assign a thread id
+ let mut vm_ref = vm.lock().unwrap();
+ let tid = vm_ref.next_tid;
+ vm_ref.next_tid += 1;
+
+ // Create thread-local code and heap memory block views
+ let code = vm_ref.code.new_view();
+ let heap = vm_ref.heap.new_view();
+
+ drop(vm_ref);
+
+ let vm_mutex = vm.clone();
+
+ Thread::new(tid, vm_mutex, code, heap)
+ }
+
+ // Spawn a new thread and begin executing the specified function
+ pub fn spawn_thread(vm: &Arc>, callee_pc: u64, args: Vec) -> u64
+ {
+ let mut thread = VM::new_thread(vm);
+ let tid = thread.id;
+
+ // Spawn a new thread
+ let handle = thread::spawn(move || {
+ thread.call(callee_pc, args.as_slice())
+ });
+
+ // Store the join handle on the VM
+ let mut vm_ref = vm.lock().unwrap();
+ vm_ref.threads.insert(tid, handle);
+ drop(vm_ref);
+
+ tid
+ }
+
+ // Wait for a thread to produce a result and return it
+ pub fn join_thread(vm: &Arc>, tid: u64) -> Value
+ {
+ // Get the join handle, then release the VM lock
+ let mut vm = vm.lock().unwrap();
+ let mut handle = vm.threads.remove(&tid).unwrap();
+ drop(vm);
+
+ handle.join().expect(&format!("could join thread with id {}", tid))
+ }
+
+ // Call a function in the main actor
+ pub fn call(vm: &mut Arc>, callee_pc: u64, args: &[Value]) -> Value
+ {
+ // Assign a thread id
+ let mut vm_ref = vm.lock().unwrap();
+ let tid = vm_ref.next_tid;
+ assert!(tid == 0);
+ vm_ref.next_tid += 1;
+
+ // Create thread-local code and heap memory block views
+ let code = vm_ref.code.new_view();
+ let heap = vm_ref.heap.new_view();
+
+ drop(vm_ref);
+
+ let vm_mutex = vm.clone();
+ let mut thread = Thread::new(tid, vm_mutex, code, heap);
+ thread.call(callee_pc, args)
+ }
+}
+
#[cfg(test)]
mod tests
{
@@ -1588,15 +1840,11 @@ mod tests
{
dbg!(src);
let asm = Assembler::new();
- let mut vm = asm.parse_str(src).unwrap();
- let result = vm.call(0, &[]);
- assert!(vm.stack.len() == 0 && vm.frames.len() == 0);
+ let prog = asm.parse_str(src).unwrap();
+ let mut vm = VM::new(prog);
+ let result = VM::call(&mut vm, 0, &[]);
- match result
- {
- ExitReason::Exit(value) => value,
- ExitReason::Return(value) => value,
- }
+ result
}
fn eval_i64(src: &str, expected: i64)
@@ -1609,143 +1857,143 @@ mod tests
fn test_opcodes()
{
// We can have at most 254 short single-byte opcodes
- assert!(Op::exit as usize <= 254);
+ assert!(Op::ret as usize <= 254);
// Keep track of how many short opcodes we have so far
- dbg!(Op::exit as usize);
- assert!(Op::exit as usize <= 113);
+ dbg!(Op::ret as usize);
+ assert!(Op::ret as usize <= 117);
}
#[test]
fn test_basics()
{
// Integer literals
- eval_i64("push_i8 1; exit;", 1);
- eval_i64("push_i8 -3; exit;", -3);
- eval_i64("push_u64 1_333_444; exit;", 1_333_444);
- eval_i64("push_u64 0xFF; exit;", 0xFF);
- eval_i64("push_u64 0b1101; exit;", 0b1101);
+ eval_i64("push_i8 1; ret;", 1);
+ eval_i64("push_i8 -3; ret;", -3);
+ eval_i64("push_u64 1_333_444; ret;", 1_333_444);
+ eval_i64("push_u64 0xFF; ret;", 0xFF);
+ eval_i64("push_u64 0b1101; ret;", 0b1101);
// Push mnemonic
- eval_i64("push 0; exit;", 0);
- eval_i64("push 1; exit;", 1);
- eval_i64("push -1; exit;", -1);
- eval_i64("push 0xFFFF; exit;", 0xFFFF);
- eval_i64(".data; LABEL: .u64 0; .code; push LABEL; exit;", 0);
+ eval_i64("push 0; ret;", 0);
+ eval_i64("push 1; ret;", 1);
+ eval_i64("push -1; ret;", -1);
+ eval_i64("push 0xFFFF; ret;", 0xFFFF);
+ eval_i64(".data; LABEL: .u64 0; .code; push LABEL; ret;", 0);
// Stack manipulation
- eval_i64("push_i8 7; push_i8 3; swap; exit;", 7);
- eval_i64("push_i8 7; push_i8 3; swap; swap; pop; exit;", 7);
+ eval_i64("push_i8 7; push_i8 3; swap; ret;", 7);
+ eval_i64("push_i8 7; push_i8 3; swap; swap; pop; ret;", 7);
// Integer arithmetic
- eval_i64("push_i8 1; push_i8 10; add_u64; exit;", 11);
- eval_i64("push_i8 5; push_i8 10; sub_u64; exit;", -5);
- eval_i64("push_i8 10; push_i8 2; sub_u64; exit;", 8);
- eval_i64("push 5; push_i8 -6; mul_u64; exit;", -30);
- eval_i64("push 1; push 2; lshift_u64; exit;", 4);
+ eval_i64("push_i8 1; push_i8 10; add_u64; ret;", 11);
+ eval_i64("push_i8 5; push_i8 10; sub_u64; ret;", -5);
+ eval_i64("push_i8 10; push_i8 2; sub_u64; ret;", 8);
+ eval_i64("push 5; push_i8 -6; mul_u64; ret;", -30);
+ eval_i64("push 1; push 2; lshift_u64; ret;", 4);
// Comparisons
- eval_i64("push_i8 1; push_i8 10; lt_i64; exit;", 1);
- eval_i64("push_i8 11; push_i8 1; lt_i64; exit;", 0);
+ eval_i64("push_i8 1; push_i8 10; lt_i64; ret;", 1);
+ eval_i64("push_i8 11; push_i8 1; lt_i64; ret;", 0);
}
#[test]
fn test_setlocal()
{
- eval_i64(".code; push 0; push 77; set_local 0; get_local 0; exit;", 77);
+ eval_i64(".code; push 0; push 77; set_local 0; get_local 0; ret;", 77);
}
#[test]
fn test_floats()
{
- eval_i64("push_f32 1.5; push_f32 2.5; add_f32; push_f32 4.0; eq_u64; exit;", 1);
+ eval_i64("push_f32 1.5; push_f32 2.5; add_f32; push_f32 4.0; eq_u64; ret;", 1);
}
#[test]
fn test_loop()
{
// Simple loop
- eval_i64("push_i8 0; LOOP: push_i8 1; add_u64; dup; push_i8 10; eq_u64; jz LOOP; exit;", 10);
+ eval_i64("push_i8 0; LOOP: push_i8 1; add_u64; dup; push_i8 10; eq_u64; jz LOOP; ret;", 10);
}
#[test]
fn test_load_store()
{
// Store instruction
- eval_i64(".data; .zero 255; .code; push_i8 0; push_i8 77; store_u8; push_i8 11; exit;", 11);
+ eval_i64(".data; .zero 255; .code; push_i8 0; push_i8 77; store_u8; push_i8 11; ret;", 11);
}
#[test]
fn test_setn()
{
// Store instruction
- eval_i64(".code; push 3; push 0; push 7; setn 1; pop; exit;", 7);
+ eval_i64(".code; push 3; push 0; push 7; setn 1; pop; ret;", 7);
}
#[test]
fn test_call_ret()
{
- eval_i64("call FN, 0; exit; FN: push_i8 33; ret;", 33);
- eval_i64("push_i8 3; call FN, 1; exit; FN: get_arg 0; push_i8 1; add_u64; ret;", 4);
+ eval_i64("call FN, 0; ret; FN: push_i8 33; ret;", 33);
+ eval_i64("push_i8 3; call FN, 1; ret; FN: get_arg 0; push_i8 1; add_u64; ret;", 4);
// set_arg
- eval_i64("push_i8 3; call FN, 1; exit; FN: push 7; set_arg 0; get_arg 0; ret;", 7);
+ eval_i64("push_i8 3; call FN, 1; ret; FN: push 7; set_arg 0; get_arg 0; ret;", 7);
// Two arguments and subtract (order of arguments matters)
- eval_i64("push_i8 7; push 5; call FN, 2; exit; FN: get_arg 0; get_arg 1; sub_u64; ret;", 2);
+ eval_i64("push_i8 7; push 5; call FN, 2; ret; FN: get_arg 0; get_arg 1; sub_u64; ret;", 2);
// Recursive decrement function
- eval_i64("push 10; call DEC, 1; exit; DEC: get_arg 0; dup; jz ZERO; push 1; sub_u64; call DEC, 1; ret; ZERO: ret;", 0);
+ eval_i64("push 10; call DEC, 1; ret; DEC: get_arg 0; dup; jz ZERO; push 1; sub_u64; call DEC, 1; ret; ZERO: ret;", 0);
// Regression: stack corruption
- eval_i64("push 5; call foo, 0; pop; exit; foo: push 2; push 0; ret;", 5);
+ eval_i64("push 5; call foo, 0; pop; ret; foo: push 2; push 0; ret;", 5);
}
#[test]
fn test_call_fp()
{
- eval_i64(" push FN; call_fp 0; exit; FN: push_i8 33; ret;", 33);
+ eval_i64(" push FN; call_fp 0; ret; FN: push_i8 33; ret;", 33);
}
#[test]
fn test_syscalls()
{
- eval_src(".data; LABEL: .zero 256; .code; push LABEL; push 255; push 0; syscall memset; push 0; exit;");
+ eval_src(".data; LABEL: .zero 256; .code; push LABEL; push 255; push 0; syscall memset; push 0; ret;");
}
#[test]
#[should_panic]
fn test_div_zero()
{
- eval_src("push 8; push 0; div_u64; exit;");
+ eval_src("push 8; push 0; div_u64; ret;");
}
#[test]
#[should_panic]
fn test_ret_none()
{
- eval_src("call FN, 0; exit; FN: ret;");
+ eval_src("call FN, 0; ret; FN: ret;");
}
#[test]
#[should_panic]
fn test_get_arg_none()
{
- eval_src("call FN, 0; exit; FN: get_arg 0; push 0; ret;");
+ eval_src("call FN, 0; ret; FN: get_arg 0; push 0; ret;");
}
#[test]
#[should_panic]
fn test_load_oob()
{
- eval_src(".data; .fill 1000, 0; .code; push 100_000_000; load_u64; exit;");
+ eval_src(".data; .fill 1000, 0; .code; push 100_000_000; load_u64; ret;");
}
#[test]
#[should_panic]
fn test_memset_oob()
{
- eval_src(".data; LABEL: .zero 1; .code; push LABEL; push 255; push 100_000_000; syscall memset; push 0; exit;");
+ eval_src(".data; LABEL: .zero 1; .code; push LABEL; push 255; push 100_000_000; syscall memset; push 0; ret;");
}
// Regression: this used to segfault
diff --git a/vm/src/window.rs b/vm/src/window.rs
new file mode 100644
index 00000000..745c9724
--- /dev/null
+++ b/vm/src/window.rs
@@ -0,0 +1,402 @@
+// Simple display/window device
+
+extern crate sdl2;
+use sdl2::pixels::Color;
+use sdl2::event::Event;
+use sdl2::keyboard::Keycode;
+use sdl2::mouse::MouseButton;
+use sdl2::surface::Surface;
+use sdl2::render::Texture;
+use sdl2::render::TextureAccess;
+use sdl2::pixels::PixelFormatEnum;
+use std::mem::size_of;
+use std::time::Duration;
+use crate::host::{get_sdl_context};
+use crate::vm::{VM, Thread, Value};
+
+/// SDL video subsystem
+/// This is a global variable because it doesn't implement
+/// the Send trait, and so can't be referenced from another thread
+static mut SDL_VIDEO: Option = None;
+
+/// Lazily initialize the SDL video subsystem
+fn get_video_subsystem() -> &'static mut sdl2::VideoSubsystem
+{
+ unsafe
+ {
+ let sdl = get_sdl_context();
+
+ if SDL_VIDEO.is_none() {
+ SDL_VIDEO = Some(sdl.video().unwrap());
+ }
+
+ SDL_VIDEO.as_mut().unwrap()
+ }
+}
+
+struct Window<'a>
+{
+ width: u32,
+ height: u32,
+ window_id: u32,
+
+ // SDL canvas to draw into
+ canvas: sdl2::render::Canvas,
+ texture_creator: sdl2::render::TextureCreator,
+ texture: Option>,
+}
+
+// Note: we're leaving this global to avoid the Window lifetime
+// bubbling up everywhere.
+// TODO: eventually we will likely want to allow multiple windows
+static mut WINDOW: Option = None;
+
+fn get_window(window_id: u32) -> &'static mut Window<'static>
+{
+ if window_id != 0 {
+ panic!("for now, only one window supported");
+ }
+
+ unsafe {
+ WINDOW.as_mut().unwrap()
+ }
+}
+
+pub fn window_create(thread: &mut Thread, width: Value, height: Value, title: Value, flags: Value) -> Value
+{
+ if thread.id != 0 {
+ panic!("window functions should only be called from the main thread");
+ }
+
+ unsafe {
+ if WINDOW.is_some() {
+ panic!("for now, only one window supported");
+ }
+ }
+
+ let width: u32 = width.as_usize().try_into().unwrap();
+ let height: u32 = height.as_usize().try_into().unwrap();
+ let title_str = thread.get_heap_str(title.as_usize()).to_owned();
+
+ let video_subsystem = get_video_subsystem();
+
+ let window = video_subsystem.window(&title_str, width, height)
+ .hidden()
+ .position_centered()
+ .build()
+ .unwrap();
+
+ let mut canvas = window.into_canvas().present_vsync().build().unwrap();
+
+ canvas.set_draw_color(Color::RGB(0, 0, 0));
+ canvas.clear();
+ canvas.present();
+
+ let texture_creator = canvas.texture_creator();
+
+ let window = Window {
+ width,
+ height,
+ window_id: 0,
+ canvas,
+ texture_creator,
+ texture: None,
+ };
+
+ unsafe {
+ WINDOW = Some(window)
+ }
+
+ // TODO: return unique window id
+ Value::from(0)
+}
+
+pub fn window_draw_frame(thread: &mut Thread, window_id: Value, src_addr: Value)
+{
+ if thread.id != 0 {
+ panic!("window functions should only be called from the main thread");
+ }
+
+ let window = get_window(window_id.as_u32());
+
+ // Get the address to copy pixel data from
+ let data_len = (4 * window.width * window.height) as usize;
+ let data_ptr = thread.get_heap_ptr_mut(src_addr.as_usize(), data_len);
+
+ // If no frame has been drawn yet
+ if window.texture.is_none() {
+ // Creat the texture to render into
+ // Pixels use the BGRA byte order (0xAA_RR_GG_BB on a little-endian machine)
+ window.texture = Some(window.texture_creator.create_texture(
+ PixelFormatEnum::BGRA32,
+ TextureAccess::Streaming,
+ window.width,
+ window.height
+ ).unwrap());
+
+ // We show and raise the window at the moment the first frame is drawn
+ // This avoids showing a blank window too early
+ window.canvas.window_mut().show();
+ window.canvas.window_mut().raise();
+ }
+
+ // Clear the canvas
+ window.canvas.clear();
+
+ // Update the texture
+ let pitch = 4 * window.width as usize;
+ let pixel_slice = unsafe { std::slice::from_raw_parts(data_ptr, data_len) };
+ window.texture.as_mut().unwrap().update(None, pixel_slice, pitch).unwrap();
+
+ // Copy the texture into the canvas
+ window.canvas.copy(
+ &window.texture.as_ref().unwrap(),
+ None,
+ None
+ ).unwrap();
+
+ // Update the screen with any rendering performed since the previous call
+ window.canvas.present();
+}
+
+const EVENT_TEXT_MAX_BYTES: usize = 64;
+
+// C event struct
+#[repr(C)]
+struct CEvent
+{
+ kind: u16,
+ window_id: u16,
+ key: u16,
+ button: u16,
+ x: i32,
+ y: i32,
+ text: [u8; EVENT_TEXT_MAX_BYTES],
+}
+
+/// Takes a pointer ot an event struct to write into
+/// Returns true if an event was read
+pub fn window_poll_event(thread: &mut Thread, p_event: Value) -> Value
+{
+ if thread.id != 0 {
+ panic!("window functions should only be called from the main thread");
+ }
+
+ let p_event = p_event.as_usize();
+ assert!(p_event != 0);
+ let p_event: *mut CEvent = thread.get_heap_ptr_mut(p_event, size_of::());
+ let mut c_event = unsafe { &mut *p_event };
+
+ let mut event_pump = get_sdl_context().event_pump().unwrap();
+ let event = event_pump.poll_event();
+
+ // If no event is available
+ if event.is_none() {
+ return Value::from(false);
+ }
+
+ let event_read = translate_event(event.unwrap(), c_event);
+ Value::from(event_read)
+}
+
+/// Takes a pointer ot an event struct as argument
+/// Blocks until an event is read
+pub fn window_wait_event(thread: &mut Thread, p_event: Value)
+{
+ if thread.id != 0 {
+ panic!("window functions should only be called from the main thread");
+ }
+
+ let p_event = p_event.as_usize();
+ assert!(p_event != 0);
+ let p_event: *mut CEvent = thread.get_heap_ptr_mut(p_event, size_of::());
+ let mut c_event = unsafe { &mut *p_event };
+
+ let mut event_pump = get_sdl_context().event_pump().unwrap();
+
+ loop
+ {
+ let event = event_pump.wait_event();
+
+ if translate_event(event, c_event) {
+ break;
+ }
+ }
+}
+
+/// Translate an SDL event and write the result to an event struct in memory
+fn translate_event(sdl_event: Event, c_event: &mut CEvent) -> bool
+{
+ use crate::constants::*;
+
+ match sdl_event {
+ Event::Quit { .. } => {
+ c_event.kind = EVENT_QUIT;
+ true
+ }
+
+ Event::KeyDown { window_id, keycode: Some(keycode), .. } => {
+ match translate_keycode(keycode) {
+ Some(keycode) => {
+ c_event.kind = EVENT_KEYDOWN;
+ c_event.window_id = 0;
+ c_event.key = keycode;
+ true
+ }
+ None => false
+ }
+ }
+
+ Event::KeyUp { window_id, keycode: Some(keycode), .. } => {
+ match translate_keycode(keycode) {
+ Some(keycode) => {
+ c_event.kind = EVENT_KEYUP;
+ c_event.window_id = 0;
+ c_event.key = keycode;
+ true
+ }
+ None => false
+ }
+ }
+
+ Event::MouseButtonDown { window_id, which, mouse_btn, x, y, .. } => {
+ match translate_mouse_button(mouse_btn) {
+ Some(button) => {
+ c_event.kind = EVENT_MOUSEDOWN;
+ c_event.window_id = 0;
+ c_event.button = button;
+ c_event.x = x;
+ c_event.y = y;
+ true
+ }
+ None => false
+ }
+ }
+
+ Event::MouseButtonUp { window_id, which, mouse_btn, x, y, .. } => {
+ match translate_mouse_button(mouse_btn) {
+ Some(button) => {
+ c_event.kind = EVENT_MOUSEUP;
+ c_event.window_id = 0;
+ c_event.button = button;
+ c_event.x = x;
+ c_event.y = y;
+ true
+ }
+ None => false
+ }
+ }
+
+ Event::MouseMotion { window_id, x, y, .. } => {
+ c_event.kind = EVENT_MOUSEMOVE;
+ c_event.window_id = 0;
+ c_event.x = x;
+ c_event.y = y;
+ true
+ }
+
+ Event::TextInput { window_id, text, .. } => {
+ c_event.kind = EVENT_TEXTINPUT;
+ c_event.window_id = 0;
+ c_event.text.fill(0);
+
+ let text_bytes = text.bytes();
+
+ // This should never happen
+ if text_bytes.len() > EVENT_TEXT_MAX_BYTES - 1 {
+ panic!();
+ }
+
+ // For each UTF-8 byte of input
+ for (i, ch) in text_bytes.enumerate() {
+ c_event.text[i] = ch;
+ }
+
+ true
+ }
+
+ _ => false
+ }
+}
+
+fn translate_keycode(sdl_keycode: Keycode) -> Option
+{
+ use crate::constants::*;
+
+ // https://docs.rs/sdl2/0.30.0/sdl2/keyboard/enum.Keycode.html
+ match sdl_keycode {
+ Keycode::A => Some(KEY_A),
+ Keycode::B => Some(KEY_B),
+ Keycode::C => Some(KEY_C),
+ Keycode::D => Some(KEY_D),
+ Keycode::E => Some(KEY_E),
+ Keycode::F => Some(KEY_F),
+ Keycode::G => Some(KEY_G),
+ Keycode::H => Some(KEY_H),
+ Keycode::I => Some(KEY_I),
+ Keycode::J => Some(KEY_J),
+ Keycode::K => Some(KEY_K),
+ Keycode::L => Some(KEY_L),
+ Keycode::M => Some(KEY_M),
+ Keycode::N => Some(KEY_N),
+ Keycode::O => Some(KEY_O),
+ Keycode::P => Some(KEY_P),
+ Keycode::Q => Some(KEY_Q),
+ Keycode::R => Some(KEY_R),
+ Keycode::S => Some(KEY_S),
+ Keycode::T => Some(KEY_T),
+ Keycode::U => Some(KEY_U),
+ Keycode::V => Some(KEY_V),
+ Keycode::W => Some(KEY_W),
+ Keycode::X => Some(KEY_X),
+ Keycode::Y => Some(KEY_Y),
+ Keycode::Z => Some(KEY_Z),
+
+ Keycode::Num0 => Some(KEY_NUM0),
+ Keycode::Num1 => Some(KEY_NUM1),
+ Keycode::Num2 => Some(KEY_NUM2),
+ Keycode::Num3 => Some(KEY_NUM3),
+ Keycode::Num4 => Some(KEY_NUM4),
+ Keycode::Num5 => Some(KEY_NUM5),
+ Keycode::Num6 => Some(KEY_NUM6),
+ Keycode::Num7 => Some(KEY_NUM7),
+ Keycode::Num8 => Some(KEY_NUM8),
+ Keycode::Num9 => Some(KEY_NUM9),
+
+ Keycode::Comma => Some(KEY_COMMA),
+ Keycode::Period => Some(KEY_PERIOD),
+ Keycode::Slash => Some(KEY_SLASH),
+ Keycode::Colon => Some(KEY_COLON),
+ Keycode::Semicolon => Some(KEY_SEMICOLON),
+ Keycode::Equals => Some(KEY_EQUALS),
+ Keycode::Question => Some(KEY_QUESTION),
+
+ Keycode::Escape => Some(KEY_ESCAPE),
+ Keycode::Backspace => Some(KEY_BACKSPACE),
+ Keycode::Left => Some(KEY_LEFT),
+ Keycode::Right => Some(KEY_RIGHT),
+ Keycode::Up => Some(KEY_UP),
+ Keycode::Down => Some(KEY_DOWN),
+ Keycode::Space => Some(KEY_SPACE),
+ Keycode::Return => Some(KEY_RETURN),
+ Keycode::LShift => Some(KEY_SHIFT),
+ Keycode::RShift => Some(KEY_SHIFT),
+ Keycode::Tab => Some(KEY_TAB),
+
+ _ => None
+ }
+}
+
+fn translate_mouse_button(mouse_btn: MouseButton) -> Option
+{
+ use crate::constants::*;
+
+ match mouse_btn {
+ MouseButton::Left => Some(0),
+ MouseButton::Middle => Some(1),
+ MouseButton::Right => Some(2),
+ MouseButton::X1 => Some(3),
+ MouseButton::X2 => Some(4),
+ MouseButton::Unknown => None
+ }
+}