Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ RUN mkdir -p /zeroperl && chmod 777 /zeroperl
FROM base AS native-perl

ARG PERL_VERSION=5.42.0
ARG EXIFTOOL_VERSION=13.42
ARG EXIFTOOL_VERSION=13.58
ARG BUILD_EXIFTOOL=true

ENV PERL_VERSION=${PERL_VERSION} \
Expand All @@ -59,7 +59,7 @@ COPY pipeline/build-native-perl.sh pipeline/build-exiftool.sh /build/repo/pipeli
RUN chmod +x /build/repo/pipeline/*.sh

RUN /build/repo/pipeline/build-native-perl.sh
RUN [ "${BUILD_EXIFTOOL}" = "true" ] && /build/repo/pipeline/build-exiftool.sh || true
RUN if [ "${BUILD_EXIFTOOL}" = "true" ]; then /build/repo/pipeline/build-exiftool.sh; fi


FROM native-perl AS wasi-perl
Expand Down Expand Up @@ -94,19 +94,21 @@ FROM wasi-perl AS final
ARG STACK_SIZE=8388608
ARG INITIAL_MEMORY=33554432
ARG ASYNCIFY=true
ARG ENABLE_HOST_CALLBACKS=false
ARG WASM_OPT_FLAGS=""

ENV STACK_SIZE=${STACK_SIZE} \
INITIAL_MEMORY=${INITIAL_MEMORY} \
ASYNCIFY=${ASYNCIFY} \
ENABLE_HOST_CALLBACKS=${ENABLE_HOST_CALLBACKS} \
WASM_OPT_FLAGS=${WASM_OPT_FLAGS}

COPY stubs/ /build/repo/stubs/

RUN /build/repo/pipeline/build-wasm.sh

RUN mkdir -p /artifacts && \
cp /build/wasm/config.h /build/wasm/zeroperl.wasm /build/wasm/zeroperl_reactor.wasm /artifacts/ && \
cp /build/wasm/config.h /build/wasm/zeroperl.wasm /build/wasm/zeroperl_reactor.wasm /build/wasm/zeroperl_command.wasm /artifacts/ && \
cp -r /zeroperl /artifacts/perl-wasi-prefix && \
[ "${BUILD_EXIFTOOL}" = "true" ] && [ -f /build/repo/exiftool.min.pl ] && \
cp /build/repo/exiftool.min.pl /artifacts/ || true
Expand Down
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ container run --rm -v $(pwd)/output:/output zeroperl cp -r /artifacts/. /output/
Output in `./output/`:
- `zeroperl.wasm` — reactor with asyncify
- `zeroperl_reactor.wasm` — reactor without asyncify
- `zeroperl_command.wasm` — command-compatible module with a returnable `_start` export
- `perl-wasi-prefix/` — Perl library prefix
- `exiftool.min.pl` — minified ExifTool (if enabled)

Expand All @@ -44,15 +45,27 @@ container build --build-arg PERL_VERSION=5.42.0 --build-arg BUILD_EXIFTOOL=false
| Arg | Default | |
|-----|---------|--|
| `PERL_VERSION` | `5.42.0` | Perl source version |
| `EXIFTOOL_VERSION` | `13.42` | ExifTool version |
| `EXIFTOOL_VERSION` | `13.58` | ExifTool version |
| `BUILD_EXIFTOOL` | `true` | Include ExifTool |
| `STACK_SIZE` | `8388608` | WASM stack (bytes) |
| `INITIAL_MEMORY` | `33554432` | WASM initial memory (bytes) |
| `ASYNCIFY` | `true` | Enable asyncify |
| `ENABLE_HOST_CALLBACKS` | `false` | Import `env.call_host_function` for host-registered Perl callbacks |
| `TRIM` | `true` | Strip unused modules |

</details>

By default, the generated WebAssembly modules only require WASI imports. Use
`zeroperl.wasm` when embedding zeroperl through the exported `zeroperl_*` API.
Use `zeroperl_command.wasm` for loaders that call `instance.exports._start`,
such as drop-in replacements for the hosted `perl.objex.ai/zeroperl-1.0.0.wasm`
artifact. Its `_start` export reads WASI arguments but returns normally instead
of relying on `proc_exit`, which keeps browser loaders with simple WASI shims
from trapping after the Perl run completes. If your host uses
`zeroperl_register_function` or `zeroperl_register_method`, build with
`--build-arg ENABLE_HOST_CALLBACKS=true` and provide `env.call_host_function`
when instantiating the module.

### Iterating on stubs/zeroperl.c

Build from `final` stage to reuse cached wasi-perl:
Expand Down
2 changes: 1 addition & 1 deletion pipeline/build-exiftool.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/sh
set -e

EXIFTOOL_VERSION="${EXIFTOOL_VERSION:-13.42}"
EXIFTOOL_VERSION="${EXIFTOOL_VERSION:-13.58}"
NATIVE_DIR="${NATIVE_DIR:-/build/native}"
REPO_DIR="${REPO_DIR:-/build/repo}"

Expand Down
2 changes: 1 addition & 1 deletion pipeline/build-wasi-libs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mkdir -p "$WORK"
# --- zlib ---
echo "Building zlib $ZLIB_VERSION for WASI..."
cd "$WORK"
curl -fsSL "https://zlib.net/fossils/zlib-${ZLIB_VERSION}.tar.gz" | tar -xzf -
curl -fsSL "https://www.zlib.net/fossils/zlib-${ZLIB_VERSION}.tar.gz" | tar -xzf -
cd "zlib-${ZLIB_VERSION}"

CC="$CC" CFLAGS="$CFLAGS" AR="$AR" RANLIB="$RANLIB" \
Expand Down
72 changes: 54 additions & 18 deletions pipeline/build-wasm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ REPO_DIR="${REPO_DIR:-/build/repo}"
STACK_SIZE="${STACK_SIZE:-8388608}"
INITIAL_MEMORY="${INITIAL_MEMORY:-33554432}"
ASYNCIFY="${ASYNCIFY:-true}"
ENABLE_HOST_CALLBACKS="${ENABLE_HOST_CALLBACKS:-false}"

if [ "$ENABLE_HOST_CALLBACKS" = "true" ]; then
HOST_CALLBACKS_DEFINE=1
ASYNCIFY_IMPORTS="wasi_snapshot_preview1.fd_read,env.call_host_function"
else
HOST_CALLBACKS_DEFINE=0
ASYNCIFY_IMPORTS="wasi_snapshot_preview1.fd_read"
fi

export PATH="$REPO_DIR/wasi-bin:$PATH"

Expand All @@ -31,30 +40,28 @@ CFLAGS="-c -O3 -flto -DNO_MATHOMS -D_WASI_EMULATED_PROCESS_CLOCKS -D_WASI_EMULAT
-D_GNU_SOURCE -D_POSIX_C_SOURCE -DBIG_TIME -Wno-implicit-function-declaration \
-Wno-null-pointer-arithmetic -Wno-incomplete-setjmp-declaration -Wno-incompatible-library-redeclaration \
-Wno-int-conversion -D_WASI_EMULATED_SIGNAL \
-DZEROPERL_ENABLE_HOST_CALLBACKS=$HOST_CALLBACKS_DEFINE \
-include /opt/wasi-sdk/share/wasi-sysroot/include/wasm32-wasi/fcntl.h \
-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 "$REPO_DIR/stubs/zeroperl_main.c" -o zeroperl_main.o
wasic $CFLAGS "$REPO_DIR/stubs/stubs.c" -o stubs.o

CFLAGS_DATA="-c -O0 -std=c23 \
-I. -I$REPO_DIR/stubs -I$REPO_DIR/gen -cxx-isystem /opt/wasi-sdk/share/wasi-sysroot/include"
wasic $CFLAGS_DATA "$REPO_DIR/gen/zeroperl_data.c" -o zeroperl_data.o

wasic \
-o zeroperl_reactor.wasm \
link_zeroperl() {
OUTPUT="$1"
shift

wasic \
-o "$OUTPUT" \
-flto -g \
-mexec-model=reactor \
"$@" \
-z stack-size="$STACK_SIZE" -Wl,--initial-memory="$INITIAL_MEMORY" \
-static \
-Wl,--no-entry \
-Wl,--stack-first \
-Wl,--export-dynamic \
-Wl,--export=__stack_pointer \
-Wl,--export=__memory_base \
-Wl,--export=__table_base \
-Wl,--export=malloc \
-Wl,--export=free \
-DNO_MATHOMS \
-D_WASI_EMULATED_PROCESS_CLOCKS -lwasi-emulated-process-clocks \
-D_WASI_EMULATED_GETPID -lwasi-emulated-getpid \
Expand All @@ -63,7 +70,6 @@ wasic \
-D_WASI_EMULATED_SIGNAL -lwasi-emulated-signal \
-lwasi-emulated-mman \
-Wl,--strip-all \
zeroperl.o stubs.o zeroperl_data.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 \
Expand Down Expand Up @@ -109,32 +115,62 @@ wasic \
-lm -lwasi-emulated-signal -lwasi-emulated-getpid \
-lwasi-emulated-process-clocks -lwasi-emulated-mman \
-ferror-limit=0
}

link_zeroperl zeroperl_reactor.wasm \
-mexec-model=reactor \
-Wl,--no-entry \
-Wl,--stack-first \
-Wl,--export-dynamic \
-Wl,--export=__stack_pointer \
-Wl,--export=__memory_base \
-Wl,--export=__table_base \
-Wl,--export=malloc \
-Wl,--export=free \
zeroperl.o stubs.o zeroperl_data.o

link_zeroperl zeroperl_command_base.wasm \
-mexec-model=reactor \
-Wl,--no-entry \
-Wl,--stack-first \
zeroperl_main.o zeroperl.o stubs.o zeroperl_data.o

# Restore real wasm-opt for asyncify pass
mv /opt/binaryen/bin/wasm-opt-real /opt/binaryen/bin/wasm-opt

if [ "$ASYNCIFY" = "true" ]; then
wasm-opt zeroperl_reactor.wasm -O3 --strip-debug --enable-bulk-memory \
--enable-nontrapping-float-to-int --asyncify \
--pass-arg=asyncify-imports@wasi_snapshot_preview1.fd_read,env.call_host_function \
--pass-arg=asyncify-imports@$ASYNCIFY_IMPORTS \
${WASM_OPT_FLAGS} \
-o zeroperl.wasm
wasm-opt zeroperl_command_base.wasm -O3 --strip-debug --enable-bulk-memory \
--enable-nontrapping-float-to-int --asyncify \
--pass-arg=asyncify-imports@$ASYNCIFY_IMPORTS \
${WASM_OPT_FLAGS} \
-o zeroperl_command.wasm
else
wasm-opt zeroperl_reactor.wasm --strip-debug --enable-bulk-memory \
--enable-nontrapping-float-to-int --asyncify \
--pass-arg=asyncify-ignore-imports \
${WASM_OPT_FLAGS} \
-o zeroperl.wasm
wasm-opt zeroperl_command_base.wasm --strip-debug --enable-bulk-memory \
--enable-nontrapping-float-to-int --asyncify \
--pass-arg=asyncify-ignore-imports \
${WASM_OPT_FLAGS} \
-o zeroperl_command.wasm
fi

# Strip empty name section that wamrc cannot parse
python3 -c "
import sys
data = open('zeroperl.wasm', 'rb').read()
# Custom name section: id=0x00, then LEB size, then 0x04 'name'
marker = b'\x00\x05\x04name'
idx = data.rfind(marker)
if idx > 0 and idx + len(marker) == len(data):
open('zeroperl.wasm', 'wb').write(data[:idx])
print(f'Stripped {len(marker)}-byte empty name section')
for filename in ('zeroperl.wasm', 'zeroperl_command.wasm'):
data = open(filename, 'rb').read()
idx = data.rfind(marker)
if idx > 0 and idx + len(marker) == len(data):
open(filename, 'wb').write(data[:idx])
print(f'Stripped {len(marker)}-byte empty name section from {filename}')
"
14 changes: 11 additions & 3 deletions pipeline/prepare-prefix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,19 @@ rm -rf /zeroperl/bin
find /zeroperl -type f \( -name "*.so" -o -name "*.a" -o -name "*.ld" -o -name "*.pod" -o -name "*.h" -o -executable \) -delete

if [ "$BUILD_EXIFTOOL" = "true" ]; then
SITE_PERL="$NATIVE_DIR/prefix/lib/perl5/site_perl/$PERL_VERSION"
SITE_PERL="$(find "$NATIVE_DIR/prefix" -type f -path '*/Image/ExifTool.pm' -exec dirname {} \; -quit)"
if [ -z "$SITE_PERL" ]; then
echo "ExifTool modules were not found under $NATIVE_DIR/prefix" >&2
exit 1
fi
SITE_PERL="$(dirname "$SITE_PERL")"

mkdir -p "/zeroperl/lib/$PERL_VERSION/wasm32-wasi/File"
mkdir -p "/zeroperl/lib/$PERL_VERSION/wasm32-wasi/Image"
cp -R "$SITE_PERL/File/"* "/zeroperl/lib/$PERL_VERSION/wasm32-wasi/File/" 2>/dev/null || true
cp -R "$SITE_PERL/Image/"* "/zeroperl/lib/$PERL_VERSION/wasm32-wasi/Image/"
if [ -d "$SITE_PERL/File" ]; then
cp -R "$SITE_PERL/File/." "/zeroperl/lib/$PERL_VERSION/wasm32-wasi/File/"
fi
cp -R "$SITE_PERL/Image/." "/zeroperl/lib/$PERL_VERSION/wasm32-wasi/Image/"
fi

node "$REPO_DIR/tools/delete.js" "$REPO_DIR/tools/delete.txt" /zeroperl "$PERL_VERSION"
Expand Down
17 changes: 14 additions & 3 deletions stubs/asyncify.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,21 @@ __attribute__((import_module("asyncify"), import_name("stop_unwind"))) void asyn
} while (0)

__attribute__((import_module("asyncify"), import_name("start_rewind"))) void asyncify_start_rewind(void *buf);
#define asyncify_start_rewind(buf) \
do \
{ \
extern int pl_asyncify_rewinding; \
pl_asyncify_rewinding = 1; \
asyncify_start_rewind((buf)); \
} while (0)

__attribute__((import_module("asyncify"), import_name("stop_rewind"))) void asyncify_stop_rewind(void);

__attribute__((import_module("asyncify"), import_name("get_state")))
int asyncify_get_state(void);
#define asyncify_stop_rewind() \
do \
{ \
extern int pl_asyncify_rewinding; \
pl_asyncify_rewinding = 0; \
asyncify_stop_rewind(); \
} while (0)

#endif
1 change: 1 addition & 0 deletions stubs/setjmp.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ void async_buf_init(struct __asyncjmp_asyncify_jmp_buf *buf)
// Global unwinding/rewinding jmpbuf state
static asyncjmp_jmp_buf *_asyncjmp_active_jmpbuf;
void *pl_asyncify_unwind_buf;
int pl_asyncify_rewinding;

__attribute__((noinline)) int _asyncjmp_setjmp_internal(asyncjmp_jmp_buf *env)
{
Expand Down
Loading