Refactor WASM runner and logging functionality; add heartbeat task and improve initialization process
This commit is contained in:
+2
-2
@@ -84,10 +84,11 @@ else()
|
||||
# WAMR Desktop-Konfiguration
|
||||
set(WAMR_BUILD_INTERP 1 CACHE BOOL "" FORCE)
|
||||
set(WAMR_BUILD_FAST_INTERP 1 CACHE BOOL "" FORCE)
|
||||
# set(WAMR_BUILD_INTERP_TICKS 1 CACHE BOOL "" FORCE)
|
||||
|
||||
set(WAMR_BUILD_JIT 0 CACHE BOOL "" FORCE)
|
||||
set(WAMR_BUILD_AOT 0 CACHE BOOL "" FORCE)
|
||||
set(WAMR_BUILD_LIBC_BUILTIN 1 CACHE BOOL "" FORCE)
|
||||
|
||||
# Disable WASI
|
||||
set(WAMR_BUILD_LIBC_WASI 0 CACHE BOOL "" FORCE)
|
||||
|
||||
@@ -99,7 +100,6 @@ else()
|
||||
set(WAMR_BUILD_PLATFORM "linux" CACHE STRING "" FORCE)
|
||||
endif()
|
||||
|
||||
# define Target 'freertos_kernel'
|
||||
FetchContent_MakeAvailable(FreeRTOS_Kernel wamr_runtime)
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
/* Desktop Simulator Settings */
|
||||
#define configUSE_PREEMPTION 1
|
||||
#define configUSE_TIME_SLICING 1
|
||||
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0
|
||||
#define configUSE_IDLE_HOOK 1
|
||||
#define configUSE_TICK_HOOK 0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#ifndef NOVA64_API_H
|
||||
#define NOVA64_API_H
|
||||
|
||||
void register_nova64_imports(void);
|
||||
void nova64_register_imports(void);
|
||||
|
||||
#endif // NOVA64_API_H
|
||||
|
||||
@@ -28,7 +28,8 @@ bool wasm_runner_initialize(void);
|
||||
*/
|
||||
bool wasm_runner_start_program(
|
||||
const char *module_path,
|
||||
const wasm_runner_framebuffer_config_t *framebuffer_config,
|
||||
wasm_runner_instance_t **out_instance);
|
||||
const wasm_runner_framebuffer_config_t *framebuffer_config);
|
||||
|
||||
void wasm_runner_terminate(void);
|
||||
|
||||
#endif // WASM_RUNNER_H
|
||||
|
||||
+23
-5
@@ -1,24 +1,42 @@
|
||||
#include "FreeRTOS.h"
|
||||
#include "nova64_internal.h"
|
||||
#include "projdefs.h"
|
||||
#include "task.h"
|
||||
#include "wasm_export.h"
|
||||
#include "projdefs.h"
|
||||
#include <string.h>
|
||||
|
||||
|
||||
static void native_yield(wasm_exec_env_t exec_env, uint32_t timeout_ms) {
|
||||
if (timeout_ms == 0) {
|
||||
// Gibt die Rest-Quantenzeit ab, blockiert aber nicht künstlich
|
||||
taskYIELD();
|
||||
} else {
|
||||
// Blockiert den Task für x Millisekunden
|
||||
vTaskDelay(pdMS_TO_TICKS(timeout_ms));
|
||||
}
|
||||
}
|
||||
|
||||
static void native_log(wasm_exec_env_t exec_env, const char *message) {
|
||||
(void)exec_env;
|
||||
|
||||
nova64_log("[WASM LOG] ");
|
||||
|
||||
const char *text = message ? message : "";
|
||||
size_t len = strlen(text);
|
||||
bool has_newline = len > 0 && text[len - 1] == '\n';
|
||||
|
||||
if (has_newline) {
|
||||
nova64_log("%s", text);
|
||||
} else {
|
||||
nova64_log("%s\n", text);
|
||||
}
|
||||
}
|
||||
|
||||
static NativeSymbol native_symbols[] = {
|
||||
{"yield", (void *)native_yield, "(i)", NULL},
|
||||
{"nova64_yield", (void *)native_yield, "(i)", NULL},
|
||||
{"nova64_log", (void *)native_log, "($)", NULL},
|
||||
// { "flush_framebuffer", (void*)native_flush_framebuffer, "()", NULL }
|
||||
};
|
||||
|
||||
void register_nova64_imports(void) {
|
||||
void nova64_register_imports(void) {
|
||||
wasm_runtime_register_natives("nova64", native_symbols,
|
||||
sizeof(native_symbols) / sizeof(NativeSymbol));
|
||||
}
|
||||
+105
-40
@@ -24,14 +24,48 @@ static nova64_io_interface_t g_io_interface_storage;
|
||||
static const nova64_io_interface_t *g_io_interface = NULL;
|
||||
static QueueHandle_t event_queue = NULL;
|
||||
static TaskHandle_t boot_task_handle = NULL;
|
||||
static TaskHandle_t launcher_task_handle = NULL;
|
||||
static TaskHandle_t init_task_handle = NULL;
|
||||
static TaskHandle_t heartbeat_task_handle = NULL;
|
||||
|
||||
#if defined(NOVA64_SHUTDOWN_AVAILABLE)
|
||||
static TaskHandle_t shutdown_task_handle = NULL;
|
||||
static volatile bool shutdown_requested = false;
|
||||
volatile bool shutdown_requested = false;
|
||||
#endif
|
||||
|
||||
static const char *const kInternalLauncherPath = "SYS:/launcher.wasm";
|
||||
static const char *const kInternalInitPath = "SYS:/init.wasm";
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
static SRWLOCK g_log_lock = SRWLOCK_INIT;
|
||||
|
||||
static inline void log_lock(void) { AcquireSRWLockExclusive(&g_log_lock); }
|
||||
static inline void log_unlock(void) { ReleaseSRWLockExclusive(&g_log_lock); }
|
||||
|
||||
#elif defined(__unix__) || defined(__APPLE__)
|
||||
#include <pthread.h>
|
||||
static pthread_mutex_t g_log_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
static inline void log_lock(void) { pthread_mutex_lock(&g_log_lock); }
|
||||
static inline void log_unlock(void) { pthread_mutex_unlock(&g_log_lock); }
|
||||
|
||||
#else
|
||||
|
||||
#include "semphr.h"
|
||||
static SemaphoreHandle_t g_log_lock = NULL;
|
||||
|
||||
static inline void log_lock(void) {
|
||||
if (!g_log_lock) {
|
||||
g_log_lock = xSemaphoreCreateMutex();
|
||||
}
|
||||
if (g_log_lock && xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
|
||||
xSemaphoreTake(g_log_lock, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
static inline void log_unlock(void) {
|
||||
if (g_log_lock && xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED) {
|
||||
xSemaphoreGive(g_log_lock);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void nova64_log(const char *format, ...) {
|
||||
char buffer[512];
|
||||
@@ -40,48 +74,41 @@ void nova64_log(const char *format, ...) {
|
||||
vsnprintf(buffer, sizeof(buffer), format, args);
|
||||
va_end(args);
|
||||
|
||||
log_lock();
|
||||
|
||||
if (g_io_interface && g_io_interface->log_message) {
|
||||
g_io_interface->log_message(buffer);
|
||||
} else {
|
||||
fputs(buffer, stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
log_unlock();
|
||||
}
|
||||
|
||||
static bool initialize_wasm_runtime(void) {
|
||||
nova64_log("Initializing WASM runtime...\n");
|
||||
wasm_runner_initialize();
|
||||
// TODO: Replace this stub with real WAMR initialization.
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool load_launcher_from_internal_storage(void) {
|
||||
nova64_log("Loading launcher from internal storage (%s)...\n",
|
||||
kInternalLauncherPath);
|
||||
// TODO: Implement the actual internal storage filesystem and launcher load.
|
||||
return true;
|
||||
}
|
||||
|
||||
static void nova64_wasm_launcher_task(void *params) {
|
||||
static void start_init_from_internal_storage(void *params) {
|
||||
(void)params;
|
||||
nova64_log("WASM launcher task started.\n");
|
||||
|
||||
// TODO: Execute the loaded WASM launcher module here with the WAMR engine.
|
||||
for (;;) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
wasm_runner_start_program(kInternalInitPath, NULL);
|
||||
}
|
||||
|
||||
static bool start_launcher_task(void) {
|
||||
BaseType_t result = xTaskCreate(nova64_wasm_launcher_task, "Nova64Launcher",
|
||||
4096 / sizeof(StackType_t), NULL,
|
||||
tskIDLE_PRIORITY + 1, &launcher_task_handle);
|
||||
static bool start_init_task(void) {
|
||||
BaseType_t result =
|
||||
xTaskCreate(start_init_from_internal_storage, "Nova64Init",
|
||||
4096 / sizeof(StackType_t), NULL, tskIDLE_PRIORITY + 1,
|
||||
&init_task_handle);
|
||||
|
||||
if (result != pdPASS) {
|
||||
nova64_log("Failed to create launcher task.\n");
|
||||
nova64_log("Failed to create init task.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
nova64_log("Launcher task created successfully.\n");
|
||||
nova64_log("Init task created successfully.\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -90,14 +117,34 @@ static void nova64_shutdown_task(void *params) {
|
||||
(void)params;
|
||||
|
||||
nova64_log("Shutdown task started.\n");
|
||||
shutdown_requested = false;
|
||||
for (;;) {
|
||||
ulTaskNotifyTake(pdTRUE,
|
||||
pdMS_TO_TICKS(50)); // Wait for notification or timeout
|
||||
if (shutdown_requested) {
|
||||
nova64_log("Shutdown requested; ending scheduler.\n");
|
||||
vTaskEndScheduler();
|
||||
return;
|
||||
break;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
|
||||
nova64_log("Shutdown requested; terminating WASM runtime.\n");
|
||||
|
||||
wasm_runner_terminate();
|
||||
|
||||
if (heartbeat_task_handle != NULL) {
|
||||
vTaskDelete(heartbeat_task_handle);
|
||||
heartbeat_task_handle = NULL;
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(100)); // Allow time for tasks to clean up
|
||||
|
||||
shutdown_task_handle = NULL;
|
||||
#if defined(_WIN32)
|
||||
ExitThread(0);
|
||||
#elif defined(__unix__) || defined(__APPLE__)
|
||||
pthread_exit(NULL);
|
||||
#else
|
||||
vTaskDelete(NULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool start_shutdown_task(void) {
|
||||
@@ -115,6 +162,27 @@ static bool start_shutdown_task(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
void heartbeat_task(void *pvParameters) {
|
||||
TickType_t xLastWakeTime = xTaskGetTickCount();
|
||||
const TickType_t xFrequency = pdMS_TO_TICKS(1000);
|
||||
|
||||
uint32_t seconds = 0;
|
||||
|
||||
for (;;) {
|
||||
vTaskDelayUntil(&xLastWakeTime, xFrequency);
|
||||
|
||||
seconds++;
|
||||
|
||||
nova64_log("[Heartbeat] System running for %lu s | FreeRTOS Tick: %lu\n",
|
||||
(unsigned long)seconds, (unsigned long)xTaskGetTickCount());
|
||||
}
|
||||
}
|
||||
|
||||
void start_heartbeat_task(void) {
|
||||
xTaskCreate(heartbeat_task, "heartbeat", 2048, NULL, 1,
|
||||
&heartbeat_task_handle);
|
||||
}
|
||||
|
||||
static void nova64_boot_task(void *params) {
|
||||
(void)params;
|
||||
nova64_log("Boot task started.\n");
|
||||
@@ -125,17 +193,12 @@ static void nova64_boot_task(void *params) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!load_launcher_from_internal_storage()) {
|
||||
nova64_log("Launcher loading failed.\n");
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!start_launcher_task()) {
|
||||
nova64_log("Launcher startup failed.\n");
|
||||
if (!start_init_task()) {
|
||||
nova64_log("Init startup failed.\n");
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
start_heartbeat_task();
|
||||
|
||||
nova64_log("Boot process completed.\n");
|
||||
vTaskDelete(NULL);
|
||||
@@ -153,10 +216,6 @@ NOVA64_API bool nova64_init(const nova64_io_interface_t *io_interface) {
|
||||
g_io_interface_storage = *io_interface;
|
||||
g_io_interface = &g_io_interface_storage;
|
||||
|
||||
#if defined(NOVA64_SHUTDOWN_AVAILABLE)
|
||||
shutdown_requested = false;
|
||||
#endif
|
||||
|
||||
nova64_log("System Initializing...\n");
|
||||
|
||||
event_queue = xQueueCreate(10, sizeof(uint32_t));
|
||||
@@ -206,7 +265,13 @@ NOVA64_API bool nova64_shutdown(void) {
|
||||
}
|
||||
|
||||
nova64_log("Scheduler shutdown requested from host.\n");
|
||||
|
||||
shutdown_requested = true;
|
||||
if (shutdown_task_handle != NULL) {
|
||||
xTaskNotifyGive(shutdown_task_handle);
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
+107
-23
@@ -1,28 +1,75 @@
|
||||
#include "wasm_runner.h"
|
||||
#include "FreeRTOS.h"
|
||||
#include "nova64_api.h"
|
||||
#include "nova64_fs.h"
|
||||
#include "nova64_internal.h"
|
||||
#include "nova64_api.h"
|
||||
#include "task.h"
|
||||
#include "wasm_export.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
struct wasm_runner_instance {
|
||||
void *native_instance;
|
||||
char *module_path;
|
||||
wasm_runner_framebuffer_config_t framebuffer_config;
|
||||
wasm_runner_instance_t *next;
|
||||
};
|
||||
#define MAX_WASM_TASKS 4
|
||||
|
||||
typedef enum { SLOT_FREE = 0, SLOT_RUNNING, SLOT_STOPPING } slot_state_t;
|
||||
|
||||
typedef struct {
|
||||
int id;
|
||||
slot_state_t state;
|
||||
// TaskHandle_t freertos_handle;
|
||||
wasm_module_inst_t module_inst;
|
||||
wasm_exec_env_t exec_env;
|
||||
} wasm_task_slot_t;
|
||||
|
||||
static wasm_task_slot_t g_wasm_slots[MAX_WASM_TASKS];
|
||||
|
||||
char error_buf[128];
|
||||
uint32_t stack_size = 8092, heap_size = 8092;
|
||||
|
||||
bool wasm_runner_initialize(void) {
|
||||
|
||||
for (int i = 0; i < MAX_WASM_TASKS; i++) {
|
||||
g_wasm_slots[i].id = i;
|
||||
g_wasm_slots[i].state = SLOT_FREE;
|
||||
// g_wasm_slots[i].freertos_handle = NULL;
|
||||
g_wasm_slots[i].module_inst = NULL;
|
||||
}
|
||||
|
||||
wasm_runtime_init();
|
||||
register_nova64_imports();
|
||||
nova64_register_imports();
|
||||
nova64_log("WASM runtime initialized successfully.\n");
|
||||
|
||||
const char *module_file = "SYS:/init.wasm";
|
||||
int32_t file_size = nova64_get_file_size(module_file);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wasm_runner_destroy(void) {
|
||||
wasm_runtime_destroy();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool wasm_runner_start_program(
|
||||
const char *module_path,
|
||||
const wasm_runner_framebuffer_config_t *framebuffer_config) {
|
||||
|
||||
nova64_log("Running WASM module: %s\n", module_path);
|
||||
|
||||
wasm_task_slot_t *slot = NULL;
|
||||
for (int i = 0; i < MAX_WASM_TASKS; i++) {
|
||||
if (g_wasm_slots[i].state == SLOT_FREE) {
|
||||
slot = &g_wasm_slots[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!slot) {
|
||||
nova64_log("Maximum number of WASM tasks (%d) reached!\n", MAX_WASM_TASKS);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!wasm_runtime_init_thread_env()) {
|
||||
nova64_log("Failed to initialize WASM runtime thread environment.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t file_size = nova64_get_file_size(module_path);
|
||||
if (file_size <= 0) {
|
||||
nova64_log("Failed to determine WASM module size.\n");
|
||||
return false;
|
||||
@@ -34,7 +81,7 @@ bool wasm_runner_initialize(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!nova64_read_file_to_buffer(module_file, buffer, (size_t)file_size)) {
|
||||
if (!nova64_read_file_to_buffer(module_path, buffer, (size_t)file_size)) {
|
||||
nova64_log("Failed to read WASM module into buffer.\n");
|
||||
free(buffer);
|
||||
return false;
|
||||
@@ -43,36 +90,73 @@ bool wasm_runner_initialize(void) {
|
||||
wasm_module_t module = wasm_runtime_load(buffer, (uint32_t)file_size,
|
||||
error_buf, sizeof(error_buf));
|
||||
|
||||
wasm_module_inst_t module_inst = wasm_runtime_instantiate(
|
||||
module, stack_size, heap_size, error_buf, sizeof(error_buf));
|
||||
slot->module_inst = wasm_runtime_instantiate(module, stack_size, heap_size,
|
||||
error_buf, sizeof(error_buf));
|
||||
|
||||
wasm_function_inst_t start_func =
|
||||
wasm_runtime_lookup_function(module_inst, "_start");
|
||||
wasm_runtime_lookup_function(slot->module_inst, "_start");
|
||||
|
||||
if (start_func == NULL) {
|
||||
nova64_log("Failed to find '_start' function in WASM module: %s\n",
|
||||
wasm_runtime_get_exception(module_inst));
|
||||
wasm_runtime_get_exception(slot->module_inst));
|
||||
free(buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
wasm_exec_env_t exec_env =
|
||||
wasm_runtime_create_exec_env(module_inst, stack_size);
|
||||
slot->exec_env = wasm_runtime_create_exec_env(slot->module_inst, stack_size);
|
||||
|
||||
nova64_log("WASM module execution environment created successfully.\n");
|
||||
|
||||
uint32_t argv[2] = {5, 3}; // Example arguments for the "add" function
|
||||
|
||||
if (wasm_runtime_call_wasm(exec_env, start_func, 2, argv)) {
|
||||
nova64_log("WASM module executed successfully. Sum: %d\n", argv[0]);
|
||||
slot->state = SLOT_RUNNING;
|
||||
|
||||
if (wasm_runtime_call_wasm(slot->exec_env, start_func, 0, NULL)) {
|
||||
nova64_log("WASM module executed successfully.\n");
|
||||
} else {
|
||||
nova64_log("Failed to execute WASM module: %s\n",
|
||||
wasm_runtime_get_exception(module_inst));
|
||||
wasm_runtime_get_exception(slot->module_inst));
|
||||
}
|
||||
|
||||
wasm_runtime_destroy_exec_env(exec_env);
|
||||
wasm_runtime_deinstantiate(module_inst);
|
||||
slot->state = SLOT_STOPPING;
|
||||
|
||||
wasm_runtime_destroy_exec_env(slot->exec_env);
|
||||
wasm_runtime_deinstantiate(slot->module_inst);
|
||||
wasm_runtime_unload(module);
|
||||
free(buffer);
|
||||
wasm_runtime_destroy();
|
||||
wasm_runtime_destroy_thread_env();
|
||||
|
||||
slot->module_inst = NULL;
|
||||
slot->exec_env = NULL;
|
||||
slot->state = SLOT_FREE;
|
||||
vTaskDelete(NULL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void wasm_runner_terminate(void) {
|
||||
nova64_log("Stopping all WASM tasks...\n");
|
||||
|
||||
for (int i = 0; i < MAX_WASM_TASKS; i++) {
|
||||
if (g_wasm_slots[i].state == SLOT_RUNNING && g_wasm_slots[i].module_inst) {
|
||||
g_wasm_slots[i].state = SLOT_STOPPING;
|
||||
wasm_runtime_terminate(g_wasm_slots[i].module_inst);
|
||||
}
|
||||
}
|
||||
|
||||
bool any_running = true;
|
||||
while (any_running) {
|
||||
any_running = false;
|
||||
for (int i = 0; i < MAX_WASM_TASKS; i++) {
|
||||
if (g_wasm_slots[i].state != SLOT_FREE) {
|
||||
any_running = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (any_running) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
}
|
||||
|
||||
nova64_log("All WASM tasks successfully stopped.\n");
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Zio;
|
||||
using Zio.FileSystems;
|
||||
|
||||
@@ -76,6 +73,7 @@ internal static class Program
|
||||
private static readonly Nova64Core.LogMessageDelegate LogMessage = (message) =>
|
||||
{
|
||||
Console.Write($"[Core] {message}");
|
||||
Console.Out.Flush();
|
||||
};
|
||||
|
||||
private static GCHandle? _ioHandle;
|
||||
|
||||
Binary file not shown.
@@ -1,17 +0,0 @@
|
||||
package main
|
||||
|
||||
// Declare a main function, this is the entrypoint into our go module
|
||||
// That will be run. In our example, we won't need this
|
||||
func main() {
|
||||
_ = add(1, 2)
|
||||
}
|
||||
|
||||
// This exports an add function.
|
||||
// It takes in two 32-bit integer values
|
||||
// And returns a 32-bit integer value.
|
||||
// To make this function callable from JavaScript,
|
||||
// we need to add the: "export add" comment above the function
|
||||
//export add
|
||||
func add(x int, y int) int {
|
||||
return x + y
|
||||
}
|
||||
+3
-2
@@ -1,9 +1,10 @@
|
||||
extern "nova64" fn yield(timeout_ms: u32) void;
|
||||
const nova64 = @import("nova64.zig");
|
||||
|
||||
pub fn main() void {
|
||||
_ = add(1, 2);
|
||||
while (true) {
|
||||
yield(16); // Yield for 1 second
|
||||
nova64.log("Hello from Zig!");
|
||||
// nova64.yield(1000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
pub extern "nova64" fn nova64_yield(timeout_ms: u32) void;
|
||||
pub extern "nova64" fn nova64_log(message: [*]const u8) void;
|
||||
|
||||
pub fn log(message: []const u8) void {
|
||||
nova64_log(message.ptr);
|
||||
}
|
||||
|
||||
pub fn yield(timeout_ms: u32) void {
|
||||
nova64_yield(timeout_ms);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
extern "nova64" fn log(message: [*]const u8) void;
|
||||
|
||||
pub fn main() void {
|
||||
log(&"Hello from Zig!"[0]);
|
||||
}
|
||||
Reference in New Issue
Block a user