From fdf60e8687142ee9c84f598eba61ed627c8f930c Mon Sep 17 00:00:00 2001 From: Prafula Tamang Date: Wed, 11 Feb 2026 11:07:44 -0500 Subject: [PATCH 1/6] feat: Implement Asynchronous Web APIs (fetch, sleep) --- lib/ZeroPerl/Web.pm | 53 +++++++++++++++++++++++ pipeline/build-wasm.sh | 4 +- pipeline/prepare-prefix.sh | 4 ++ stubs/zeroperl.c | 4 +- stubs/zeroperl_web.c | 86 ++++++++++++++++++++++++++++++++++++++ zeroperl-ts-new | 1 + 6 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 lib/ZeroPerl/Web.pm create mode 100644 stubs/zeroperl_web.c create mode 160000 zeroperl-ts-new diff --git a/lib/ZeroPerl/Web.pm b/lib/ZeroPerl/Web.pm new file mode 100644 index 0000000..8b85fe2 --- /dev/null +++ b/lib/ZeroPerl/Web.pm @@ -0,0 +1,53 @@ +package ZeroPerl::Web; + +use strict; +use warnings; +use Exporter 'import'; + +our $VERSION = '0.01'; +our @EXPORT_OK = qw(fetch sleep); + +require XSLoader; +XSLoader::load('ZeroPerl::Web', $VERSION); + +1; + +__END__ + +=head1 NAME + +ZeroPerl::Web - Asynchronous Web APIs for ZeroPerl + +=head1 SYNOPSIS + + use ZeroPerl::Web; + + # Fetch data asynchronously (returns a Promise-like behavior via Asyncify) + my $response = ZeroPerl::Web::fetch("https://api.example.com/data"); + print "Response: $response\n"; + + # Sleep asynchronously without blocking the browser main thread + ZeroPerl::Web::sleep(1000); # Sleep for 1 second + +=head1 DESCRIPTION + +This module provides access to Web APIs within the ZeroPerl WebAssembly environment. +It uses Asyncify to perform asynchronous operations transparently to Perl code. + +=head2 FUNCTIONS + +=over 4 + +=item fetch($url, [\%options]) + +Performs a network request using the Host's `fetch` API. +Returns the response body as a string. + +=item sleep($ms) + +Suspends execution for the specified number of milliseconds. +This yields control back to the browser event loop. + +=back + +=cut diff --git a/pipeline/build-wasm.sh b/pipeline/build-wasm.sh index 1d48753..ebb140e 100644 --- a/pipeline/build-wasm.sh +++ b/pipeline/build-wasm.sh @@ -21,6 +21,7 @@ wasic -flto -O3 -c setjmp_core.S -o setjmp_core.o cd "$WASM_DIR" cp "$REPO_DIR/stubs/zeroperl.c" . +cp "$REPO_DIR/stubs/zeroperl_web.c" . CFLAGS="-c -O3 -flto -DNO_MATHOMS -D_WASI_EMULATED_PROCESS_CLOCKS -D_WASI_EMULATED_GETPID \ -D_GNU_SOURCE -D_POSIX_C_SOURCE -DBIG_TIME -Wno-implicit-function-declaration \ @@ -30,6 +31,7 @@ CFLAGS="-c -O3 -flto -DNO_MATHOMS -D_WASI_EMULATED_PROCESS_CLOCKS -D_WASI_EMULAT -I. -I$REPO_DIR/stubs -I$REPO_DIR/gen -cxx-isystem /opt/wasi-sdk/share/wasi-sysroot/include" wasic $CFLAGS zeroperl.c -o zeroperl.o +wasic $CFLAGS zeroperl_web.c -o zeroperl_web.o wasic $CFLAGS "$REPO_DIR/stubs/stubs.c" -o stubs.o CFLAGS_DATA="-c -O0 -std=c23 \ @@ -59,7 +61,7 @@ wasic \ -lwasi-emulated-mman \ -Wl,--strip-all \ -Wl,--allow-undefined \ - zeroperl.o stubs.o zeroperl_data.o \ + zeroperl.o stubs.o zeroperl_data.o zeroperl_web.o \ -Wl,--whole-archive "$REPO_DIR/stubs/libasyncjmp.a" -Wl,--no-whole-archive \ -Wl,--whole-archive libperl.a -Wl,--no-whole-archive \ -Wl,--wrap=fopen -Wl,--wrap=open -Wl,--wrap=close -Wl,--wrap=read \ diff --git a/pipeline/prepare-prefix.sh b/pipeline/prepare-prefix.sh index d7f378d..a567d0a 100644 --- a/pipeline/prepare-prefix.sh +++ b/pipeline/prepare-prefix.sh @@ -19,6 +19,10 @@ if [ "$BUILD_EXIFTOOL" = "true" ]; then cp -R "$SITE_PERL/Image/"* "/zeroperl/lib/$PERL_VERSION/wasm32-wasi/Image/" fi +# Copy internal modules +mkdir -p "/zeroperl/lib/$PERL_VERSION/ZeroPerl" +cp "$REPO_DIR/lib/ZeroPerl/Web.pm" "/zeroperl/lib/$PERL_VERSION/ZeroPerl/" + node "$REPO_DIR/tools/delete.js" "$REPO_DIR/tools/delete.txt" /zeroperl "$PERL_VERSION" if [ "$TRIM" = "true" ]; then diff --git a/stubs/zeroperl.c b/stubs/zeroperl.c index c32bf47..06c6952 100644 --- a/stubs/zeroperl.c +++ b/stubs/zeroperl.c @@ -2241,7 +2241,8 @@ EXTERN_C void boot_Cwd(pTHX_ CV *cv); EXTERN_C void boot_List__Util(pTHX_ CV *cv); EXTERN_C void boot_Fcntl(pTHX_ CV *cv); EXTERN_C void boot_Opcode(pTHX_ CV *cv); -EXTERN_C void boot_Time__HiRes(pTHX_ CV* cv); +EXTERN_C void boot_Time__HiRes(pTHX_ CV *cv); +EXTERN_C void boot_ZeroPerl__Web(pTHX_ CV *cv); static void xs_init(pTHX) { static const char file[] = __FILE__; @@ -2285,4 +2286,5 @@ static void xs_init(pTHX) { newXS("Fcntl::bootstrap", boot_Fcntl, file); newXS("Opcode::bootstrap", boot_Opcode, file); newXS("Time::HiRes::bootstrap", boot_Time__HiRes, file); + newXS("ZeroPerl::Web::bootstrap", boot_ZeroPerl__Web, file); } \ No newline at end of file diff --git a/stubs/zeroperl_web.c b/stubs/zeroperl_web.c new file mode 100644 index 0000000..822cda5 --- /dev/null +++ b/stubs/zeroperl_web.c @@ -0,0 +1,86 @@ +#include "zeroperl.h" +#include "EXTERN.h" +#include "perl.h" +#include "XSUB.h" + +// Define host function IDs for Web APIs +#define HOST_FUNC_FETCH 1001 +#define HOST_FUNC_SLEEP 1002 + +// External declaration for the host call function +extern zeroperl_value *host_call_function(int32_t func_id, int32_t argc, zeroperl_value **argv); + +// Helper to create a zeroperl_value from SV +static zeroperl_value *create_zeroperl_value(pTHX_ SV *sv) { + zeroperl_value *val = (zeroperl_value *)malloc(sizeof(zeroperl_value)); + if (val) { + val->sv = sv; + SvREFCNT_inc(sv); + } + return val; +} + +// Helper to process result from host call +static void process_host_result(pTHX_ zeroperl_value *result) { + if (result && result->sv) { + ST(0) = sv_2mortal(SvREFCNT_inc(result->sv)); + zeroperl_value_free(result); + XSRETURN(1); + } else { + if (result) zeroperl_value_free(result); + XSRETURN_UNDEF; + } +} + +XS(XS_ZeroPerl__Web_fetch) { + dXSARGS; + if (items < 1) { + croak("Usage: ZeroPerl::Web::fetch(url, [options])"); + } + + // Prepare arguments for host call + zeroperl_value **argv = (zeroperl_value **)malloc(sizeof(zeroperl_value *) * items); + for (int i = 0; i < items; i++) { + argv[i] = create_zeroperl_value(aTHX_ ST(i)); + } + + // Make the host call + // This will trigger asyncify unwind if needed + zeroperl_value *result = host_call_function(HOST_FUNC_FETCH, items, argv); + + // Cleanup arguments + for (int i = 0; i < items; i++) { + zeroperl_value_free(argv[i]); + } + free(argv); + + process_host_result(aTHX_ result); +} + +XS(XS_ZeroPerl__Web_sleep) { + dXSARGS; + if (items != 1) { + croak("Usage: ZeroPerl::Web::sleep(ms)"); + } + + zeroperl_value **argv = (zeroperl_value **)malloc(sizeof(zeroperl_value *) * 1); + argv[0] = create_zeroperl_value(aTHX_ ST(0)); + + zeroperl_value *result = host_call_function(HOST_FUNC_SLEEP, 1, argv); + + zeroperl_value_free(argv[0]); + free(argv); + + process_host_result(aTHX_ result); +} + +// Bootstrap function +XS(boot_ZeroPerl__Web) { + dXSARGS; + char* file = __FILE__; + + newXS("ZeroPerl::Web::fetch", XS_ZeroPerl__Web_fetch, file); + newXS("ZeroPerl::Web::sleep", XS_ZeroPerl__Web_sleep, file); + + XSRETURN_YES; +} diff --git a/zeroperl-ts-new b/zeroperl-ts-new new file mode 160000 index 0000000..5e12fb8 --- /dev/null +++ b/zeroperl-ts-new @@ -0,0 +1 @@ +Subproject commit 5e12fb86cebf09795c32befa390718b3d8ddd3e7 From e191e89fca4333552cded5fe1120459baad9128c Mon Sep 17 00:00:00 2001 From: Prafula Tamang Date: Wed, 11 Feb 2026 11:15:25 -0500 Subject: [PATCH 2/6] fix: QA fixes for memory safety and C standards compliance --- stubs/zeroperl_web.c | 127 ++++++++++++++++++++++++++----------------- 1 file changed, 78 insertions(+), 49 deletions(-) diff --git a/stubs/zeroperl_web.c b/stubs/zeroperl_web.c index 822cda5..e512999 100644 --- a/stubs/zeroperl_web.c +++ b/stubs/zeroperl_web.c @@ -1,86 +1,115 @@ -#include "zeroperl.h" #include "EXTERN.h" -#include "perl.h" #include "XSUB.h" +#include "perl.h" +#include "zeroperl.h" + // Define host function IDs for Web APIs #define HOST_FUNC_FETCH 1001 #define HOST_FUNC_SLEEP 1002 // External declaration for the host call function -extern zeroperl_value *host_call_function(int32_t func_id, int32_t argc, zeroperl_value **argv); +extern zeroperl_value *host_call_function(int32_t func_id, int32_t argc, + zeroperl_value **argv); // Helper to create a zeroperl_value from SV static zeroperl_value *create_zeroperl_value(pTHX_ SV *sv) { - zeroperl_value *val = (zeroperl_value *)malloc(sizeof(zeroperl_value)); - if (val) { - val->sv = sv; - SvREFCNT_inc(sv); - } - return val; + zeroperl_value *val = (zeroperl_value *)malloc(sizeof(zeroperl_value)); + if (val) { + val->sv = sv; + SvREFCNT_inc(sv); + } + return val; } // Helper to process result from host call static void process_host_result(pTHX_ zeroperl_value *result) { - if (result && result->sv) { - ST(0) = sv_2mortal(SvREFCNT_inc(result->sv)); - zeroperl_value_free(result); - XSRETURN(1); - } else { - if (result) zeroperl_value_free(result); - XSRETURN_UNDEF; - } + if (result && result->sv) { + ST(0) = sv_2mortal(SvREFCNT_inc(result->sv)); + zeroperl_value_free(result); + XSRETURN(1); + } else { + if (result) + zeroperl_value_free(result); + XSRETURN_UNDEF; + } } XS(XS_ZeroPerl__Web_fetch) { - dXSARGS; - if (items < 1) { - croak("Usage: ZeroPerl::Web::fetch(url, [options])"); - } - - // Prepare arguments for host call - zeroperl_value **argv = (zeroperl_value **)malloc(sizeof(zeroperl_value *) * items); - for (int i = 0; i < items; i++) { - argv[i] = create_zeroperl_value(aTHX_ ST(i)); + dXSARGS; + zeroperl_value **argv; + zeroperl_value *result; + int i; + + if (items < 1) { + croak("Usage: ZeroPerl::Web::fetch(url, [options])"); + } + + // Prepare arguments for host call + argv = (zeroperl_value **)malloc(sizeof(zeroperl_value *) * items); + if (!argv) { + croak("Failed to allocate memory for arguments"); + } + + for (i = 0; i < items; i++) { + argv[i] = create_zeroperl_value(aTHX_ ST(i)); + if (!argv[i]) { + // Cleanup already allocated + while (--i >= 0) { + zeroperl_value_free(argv[i]); + } + free(argv); + croak("Failed to create zeroperl_value"); } + } - // Make the host call - // This will trigger asyncify unwind if needed - zeroperl_value *result = host_call_function(HOST_FUNC_FETCH, items, argv); + // Make the host call + result = host_call_function(HOST_FUNC_FETCH, items, argv); - // Cleanup arguments - for (int i = 0; i < items; i++) { - zeroperl_value_free(argv[i]); - } - free(argv); + // Cleanup arguments + for (i = 0; i < items; i++) { + zeroperl_value_free(argv[i]); + } + free(argv); - process_host_result(aTHX_ result); + process_host_result(aTHX_ result); } XS(XS_ZeroPerl__Web_sleep) { - dXSARGS; - if (items != 1) { - croak("Usage: ZeroPerl::Web::sleep(ms)"); - } + dXSARGS; + zeroperl_value **argv; + zeroperl_value *result; - zeroperl_value **argv = (zeroperl_value **)malloc(sizeof(zeroperl_value *) * 1); - argv[0] = create_zeroperl_value(aTHX_ ST(0)); + if (items != 1) { + croak("Usage: ZeroPerl::Web::sleep(ms)"); + } - zeroperl_value *result = host_call_function(HOST_FUNC_SLEEP, 1, argv); + argv = (zeroperl_value **)malloc(sizeof(zeroperl_value *) * 1); + if (!argv) { + croak("Failed to allocate memory for arguments"); + } - zeroperl_value_free(argv[0]); + argv[0] = create_zeroperl_value(aTHX_ ST(0)); + if (!argv[0]) { free(argv); + croak("Failed to create zeroperl_value"); + } + + result = host_call_function(HOST_FUNC_SLEEP, 1, argv); + + zeroperl_value_free(argv[0]); + free(argv); - process_host_result(aTHX_ result); + process_host_result(aTHX_ result); } // Bootstrap function XS(boot_ZeroPerl__Web) { - dXSARGS; - char* file = __FILE__; + dXSARGS; + char *file = __FILE__; - newXS("ZeroPerl::Web::fetch", XS_ZeroPerl__Web_fetch, file); - newXS("ZeroPerl::Web::sleep", XS_ZeroPerl__Web_sleep, file); + newXS("ZeroPerl::Web::fetch", XS_ZeroPerl__Web_fetch, file); + newXS("ZeroPerl::Web::sleep", XS_ZeroPerl__Web_sleep, file); - XSRETURN_YES; + XSRETURN_YES; } From 0c94a07cc66a584a34f02c6e58565e12b7c9ebe3 Mon Sep 17 00:00:00 2001 From: Prafula Tamang Date: Wed, 11 Feb 2026 11:25:21 -0500 Subject: [PATCH 3/6] fix: QA fixes for memory safety and C standards compliance --- zeroperl-ts-new | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zeroperl-ts-new b/zeroperl-ts-new index 5e12fb8..d729452 160000 --- a/zeroperl-ts-new +++ b/zeroperl-ts-new @@ -1 +1 @@ -Subproject commit 5e12fb86cebf09795c32befa390718b3d8ddd3e7 +Subproject commit d7294526668ef9d03d9737b446ef7caa98fb5f1f From c6df0e5c5e2f3a013a6490d381e6c648473d5d44 Mon Sep 17 00:00:00 2001 From: Prafula Tamang Date: Wed, 11 Feb 2026 12:12:57 -0500 Subject: [PATCH 4/6] feat: Introduce ZeroPerl runtime with Simple File System (SFS) and wrapped syscalls for WASI environments. --- stubs/zeroperl.c | 92 +----------------------------------- stubs/zeroperl.h | 118 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 91 deletions(-) create mode 100644 stubs/zeroperl.h diff --git a/stubs/zeroperl.c b/stubs/zeroperl.c index 06c6952..66069c7 100644 --- a/stubs/zeroperl.c +++ b/stubs/zeroperl.c @@ -18,6 +18,7 @@ #include #include + #define STRINGIZE_HELPER(x) #x #define STRINGIZE(x) STRINGIZE_HELPER(x) #include @@ -459,97 +460,6 @@ __attribute__((noinline)) int __wrap_fileno(FILE *stream) { return realfd; } -//! Opaque handle to a Perl scalar value -typedef struct zeroperl_value_s { - SV *sv; -} zeroperl_value; - -//! Opaque handle to a Perl array -typedef struct zeroperl_array_s { - AV *av; -} zeroperl_array; - -//! Opaque handle to a Perl hash -typedef struct zeroperl_hash_s { - HV *hv; -} zeroperl_hash; - -//! Opaque handle to a Perl code reference -typedef struct zeroperl_code_s { - CV *cv; -} zeroperl_code; - -//! Result structure for operations that return multiple values -typedef struct zeroperl_result_s { - int count; - zeroperl_value **values; -} zeroperl_result; - -//! Iterator for hash traversal -typedef struct zeroperl_hash_iter_s { - HV *hv; - HE *entry; -} zeroperl_hash_iter; - -//! Context type for calling Perl code -typedef enum { - ZEROPERL_VOID, - ZEROPERL_SCALAR, - ZEROPERL_LIST -} zeroperl_context_type; - -//! Value type enumeration -typedef enum { - ZEROPERL_TYPE_UNDEF, - ZEROPERL_TYPE_TRUE, - ZEROPERL_TYPE_FALSE, - ZEROPERL_TYPE_INT, - ZEROPERL_TYPE_DOUBLE, - ZEROPERL_TYPE_STRING, - ZEROPERL_TYPE_ARRAY, - ZEROPERL_TYPE_HASH, - ZEROPERL_TYPE_CODE, - ZEROPERL_TYPE_REF -} zeroperl_type; - -//! Operation type for unified context -typedef enum { - ZEROPERL_OP_INIT, - ZEROPERL_OP_EVAL, - ZEROPERL_OP_RUN_FILE, - ZEROPERL_OP_RESET, - ZEROPERL_OP_CALL -} zeroperl_op_type; - -//! Unified context structure for all Perl operations -typedef struct { - zeroperl_op_type op_type; - int result; - union { - struct { - int argc; - char **argv; - } init; - struct { - const char *code; - int argc; - char **argv; - zeroperl_context_type context; - } eval; - struct { - const char *filepath; - int argc; - char **argv; - } run_file; - struct { - const char *name; - int argc; - zeroperl_value **argv; - zeroperl_context_type context; - } call; - } data; -} zeroperl_context; - //! Host-implemented function for calling back into the host environment ZEROPERL_IMPORT("call_host_function") zeroperl_value *host_call_function(int32_t func_id, int32_t argc, diff --git a/stubs/zeroperl.h b/stubs/zeroperl.h new file mode 100644 index 0000000..abb910e --- /dev/null +++ b/stubs/zeroperl.h @@ -0,0 +1,118 @@ +#ifndef ZEROPERL_H +#define ZEROPERL_H + +#include "EXTERN.h" +#include "XSUB.h" +#include "perl.h" +#include + + +// Defined for SFS (Simple File System) prefix +#ifndef SFS_BUILTIN_PREFIX +#define SFS_BUILTIN_PREFIX "builtin:" +#endif + +// Opaque handle to a Perl scalar value +typedef struct zeroperl_value_s { + SV *sv; +} zeroperl_value; + +// Opaque handle to a Perl array +typedef struct zeroperl_array_s { + AV *av; +} zeroperl_array; + +// Opaque handle to a Perl hash +typedef struct zeroperl_hash_s { + HV *hv; +} zeroperl_hash; + +// Opaque handle to a Perl code reference +typedef struct zeroperl_code_s { + CV *cv; +} zeroperl_code; + +// Result structure for operations that return multiple values +typedef struct zeroperl_result_s { + int count; + zeroperl_value **values; +} zeroperl_result; + +// Iterator for hash traversal +typedef struct zeroperl_hash_iter_s { + HV *hv; + HE *entry; +} zeroperl_hash_iter; + +// Context type for calling Perl code +typedef enum { + ZEROPERL_VOID, + ZEROPERL_SCALAR, + ZEROPERL_LIST +} zeroperl_context_type; + +// Value type enumeration +typedef enum { + ZEROPERL_TYPE_UNDEF, + ZEROPERL_TYPE_TRUE, + ZEROPERL_TYPE_FALSE, + ZEROPERL_TYPE_INT, + ZEROPERL_TYPE_DOUBLE, + ZEROPERL_TYPE_STRING, + ZEROPERL_TYPE_ARRAY, + ZEROPERL_TYPE_HASH, + ZEROPERL_TYPE_CODE, + ZEROPERL_TYPE_REF +} zeroperl_type; + +// Operation type for unified context +typedef enum { + ZEROPERL_OP_INIT, + ZEROPERL_OP_EVAL, + ZEROPERL_OP_RUN_FILE, + ZEROPERL_OP_RESET, + ZEROPERL_OP_CALL +} zeroperl_op_type; + +// Unified context structure for all Perl operations +typedef struct { + zeroperl_op_type op_type; + int result; + union { + struct { + int argc; + char **argv; + } init; + struct { + const char *code; + int argc; + char **argv; + zeroperl_context_type context; + } eval; + struct { + const char *filepath; + int argc; + char **argv; + } run_file; + struct { + const char *name; + int argc; + zeroperl_value **argv; + zeroperl_context_type context; + } call; + } data; +} zeroperl_context; + +// Cleanup helper +void zeroperl_value_free(zeroperl_value *val); + +// Host call function +zeroperl_value *host_call_function(int32_t func_id, int32_t argc, + zeroperl_value **argv); + +// Error handling functions +void zeroperl_set_host_error(const char *error); +const char *zeroperl_get_host_error(void); +void zeroperl_clear_host_error(void); + +#endif // ZEROPERL_H From b021262bd17a41b8bb45a5cc2a702f6e153252e0 Mon Sep 17 00:00:00 2001 From: Prafula Tamang Date: Wed, 11 Feb 2026 12:19:14 -0500 Subject: [PATCH 5/6] chore: delete file --- zeroperl-ts-new | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zeroperl-ts-new b/zeroperl-ts-new index d729452..e9943a6 160000 --- a/zeroperl-ts-new +++ b/zeroperl-ts-new @@ -1 +1 @@ -Subproject commit d7294526668ef9d03d9737b446ef7caa98fb5f1f +Subproject commit e9943a64b7a4f659ce9c973907acbddb200225ad From f40f50d81c6f55e0ac7157013f4b0b18547715c2 Mon Sep 17 00:00:00 2001 From: Prafula Tamang Date: Wed, 11 Feb 2026 12:48:32 -0500 Subject: [PATCH 6/6] fix: Resolve C stub compilation errors --- stubs/zeroperl.c | 1 - 1 file changed, 1 deletion(-) diff --git a/stubs/zeroperl.c b/stubs/zeroperl.c index 66069c7..67e2e1f 100644 --- a/stubs/zeroperl.c +++ b/stubs/zeroperl.c @@ -18,7 +18,6 @@ #include #include - #define STRINGIZE_HELPER(x) #x #define STRINGIZE(x) STRINGIZE_HELPER(x) #include