From ce03c998c4d390dcc57c2818d7238a7c3cf8a162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sa=C3=BAl=20Ibarra=20Corretg=C3=A9?= Date: Tue, 3 Dec 2024 22:59:11 +0100 Subject: [PATCH] Add ability to create standalone binaries with qjs Ref: https://github.com/quickjs-ng/quickjs/issues/438 Closes: https://github.com/quickjs-ng/quickjs/pull/441 --- .github/workflows/ci.yml | 13 ++ CMakeLists.txt | 1 + Makefile | 1 + docs/docs/cli.md | 36 ++++- gen/standalone.c | 310 +++++++++++++++++++++++++++++++++++++++ qjs.c | 154 ++++++++++++++++++- standalone.js | 123 ++++++++++++++++ 7 files changed, 629 insertions(+), 9 deletions(-) create mode 100644 gen/standalone.c create mode 100644 standalone.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aff76edc..a99cf3c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -160,6 +160,11 @@ jobs: git submodule update --init --checkout --depth 1 time make test262 + - name: test standalone + run: | + ./build/qjs -c examples/hello.js -o hello + ./hello + windows-msvc: runs-on: windows-latest strategy: @@ -184,6 +189,10 @@ jobs: build\${{matrix.buildType}}\qjs.exe examples\test_point.js build\${{matrix.buildType}}\run-test262.exe -c tests.conf build\${{matrix.buildType}}\function_source.exe + - name: test standalone + run: | + build\${{matrix.buildType}}\qjs.exe -c examples\hello.js -o hello.exe + .\hello.exe - name: Set up Visual Studio shell uses: egor-tensin/vs-shell@v2 with: @@ -351,6 +360,10 @@ jobs: - name: test run: | make test + - name: test standalone + run: | + ./build/qjs -c examples/hello.js -o hello.exe + ./hello windows-mingw-shared: runs-on: windows-latest defaults: diff --git a/CMakeLists.txt b/CMakeLists.txt index 715e516e..b635b4c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -259,6 +259,7 @@ target_link_libraries(qjsc qjs) add_executable(qjs_exe gen/repl.c + gen/standalone.c qjs.c ) add_qjs_libc_if_needed(qjs_exe) diff --git a/Makefile b/Makefile index 2ec7ee4e..baeafd0e 100644 --- a/Makefile +++ b/Makefile @@ -70,6 +70,7 @@ clean: codegen: $(QJSC) $(QJSC) -ss -o gen/repl.c -m repl.js + $(QJSC) -ss -o gen/standalone.c -m standalone.js $(QJSC) -e -o gen/function_source.c tests/function_source.js $(QJSC) -e -o gen/hello.c examples/hello.js $(QJSC) -e -o gen/hello_module.c -m examples/hello_module.js diff --git a/docs/docs/cli.md b/docs/docs/cli.md index fff81cba..7a383013 100644 --- a/docs/docs/cli.md +++ b/docs/docs/cli.md @@ -18,10 +18,13 @@ usage: qjs [options] [file [args]] -m --module load as ES6 module (default=autodetect) --script load as ES6 script (default=autodetect) -I --include file include an additional file - --std make 'std' and 'os' available to the loaded script + --std make 'std', 'os' and 'bjson' available to script -T --trace trace memory allocation -d --dump dump the memory usage stats -D --dump-flags flags for dumping debug data (see DUMP_* defines) +-c --compile FILE compile the given JS file as a standalone executable +-o --out FILE output file for standalone executables + --exe select the executable to use as the base, defaults to the current one --memory-limit n limit the memory usage to 'n' Kbytes --stack-size n limit the stack size to 'n' Kbytes --unhandled-rejection dump unhandled promise rejections @@ -52,6 +55,37 @@ DUMP_ATOMS 0x40000 /* dump atoms in JS_FreeRuntime */ DUMP_SHAPES 0x80000 /* dump shapes in JS_FreeRuntime */ ``` +### Creating standalone executables + +With the `qjs` CLI it's possible to create standalone executables that will bundle the given JavaScript file +alongside the binary. + +``` +$ qjs -c app.js -o app --exe qjs +``` + +The resulting `app` binary will have the same runtime dependencies as the `qjs` binary. This is acomplished +by compiling the target JavaScript file to bytecode and adding it a copy of the executable, with a little +trailer to help locate it. + +Rather than using the current executable, it's possible to use the `--exe` switch to create standalone +executables for other platforms. + +No JavaScript bundling is performed, the specified JS file cannot depend on other files. A bundler such +as `esbuild` can be used to generate an app bundle which can then be turned into the executable. + +``` +npx esbuild my-app/index.js \ + --bundle \ + --outfile=app.js \ + --external:qjs:* \ + --minify \ + --target=es2023 \ + --platform=neutral \ + --format=esm \ + --main-fields=main,module +``` + ## `qjsc` - The QuickJS JavaScript compiler The `qjsc` executable runs the JavaScript compiler, it can generate bytecode from diff --git a/gen/standalone.c b/gen/standalone.c new file mode 100644 index 00000000..d9b48e42 --- /dev/null +++ b/gen/standalone.c @@ -0,0 +1,310 @@ +/* File generated automatically by the QuickJS-ng compiler. */ + +#include + +const uint32_t qjsc_standalone_size = 2408; + +const uint8_t qjsc_standalone[2408] = { + 0x13, 0x4c, 0x01, 0x1a, 0x73, 0x74, 0x61, 0x6e, + 0x64, 0x61, 0x6c, 0x6f, 0x6e, 0x65, 0x2e, 0x6a, + 0x73, 0x01, 0x0e, 0x71, 0x6a, 0x73, 0x3a, 0x73, + 0x74, 0x64, 0x01, 0x0c, 0x71, 0x6a, 0x73, 0x3a, + 0x6f, 0x73, 0x01, 0x12, 0x71, 0x6a, 0x73, 0x3a, + 0x62, 0x6a, 0x73, 0x6f, 0x6e, 0x01, 0x22, 0x63, + 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x53, 0x74, + 0x61, 0x6e, 0x64, 0x61, 0x6c, 0x6f, 0x6e, 0x65, + 0x01, 0x1a, 0x72, 0x75, 0x6e, 0x53, 0x74, 0x61, + 0x6e, 0x64, 0x61, 0x6c, 0x6f, 0x6e, 0x65, 0x01, + 0x06, 0x73, 0x74, 0x64, 0x01, 0x04, 0x6f, 0x73, + 0x01, 0x0a, 0x62, 0x6a, 0x73, 0x6f, 0x6e, 0x01, + 0x28, 0x4a, 0x53, 0x5f, 0x52, 0x45, 0x41, 0x44, + 0x5f, 0x4f, 0x42, 0x4a, 0x5f, 0x42, 0x59, 0x54, + 0x45, 0x43, 0x4f, 0x44, 0x45, 0x01, 0x2a, 0x4a, + 0x53, 0x5f, 0x52, 0x45, 0x41, 0x44, 0x5f, 0x4f, + 0x42, 0x4a, 0x5f, 0x52, 0x45, 0x46, 0x45, 0x52, + 0x45, 0x4e, 0x43, 0x45, 0x01, 0x2a, 0x4a, 0x53, + 0x5f, 0x57, 0x52, 0x49, 0x54, 0x45, 0x5f, 0x4f, + 0x42, 0x4a, 0x5f, 0x42, 0x59, 0x54, 0x45, 0x43, + 0x4f, 0x44, 0x45, 0x01, 0x2c, 0x4a, 0x53, 0x5f, + 0x57, 0x52, 0x49, 0x54, 0x45, 0x5f, 0x4f, 0x42, + 0x4a, 0x5f, 0x52, 0x45, 0x46, 0x45, 0x52, 0x45, + 0x4e, 0x43, 0x45, 0x01, 0x32, 0x4a, 0x53, 0x5f, + 0x57, 0x52, 0x49, 0x54, 0x45, 0x5f, 0x4f, 0x42, + 0x4a, 0x5f, 0x53, 0x54, 0x52, 0x49, 0x50, 0x5f, + 0x53, 0x4f, 0x55, 0x52, 0x43, 0x45, 0x01, 0x0e, + 0x54, 0x72, 0x61, 0x69, 0x6c, 0x65, 0x72, 0x01, + 0x16, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x41, + 0x73, 0x63, 0x69, 0x69, 0x01, 0x16, 0x64, 0x65, + 0x63, 0x6f, 0x64, 0x65, 0x41, 0x73, 0x63, 0x69, + 0x69, 0x01, 0x06, 0x74, 0x78, 0x74, 0x01, 0x02, + 0x63, 0x01, 0x14, 0x63, 0x68, 0x61, 0x72, 0x43, + 0x6f, 0x64, 0x65, 0x41, 0x74, 0x01, 0x06, 0x6d, + 0x61, 0x70, 0x01, 0x06, 0x62, 0x75, 0x66, 0x01, + 0x18, 0x66, 0x72, 0x6f, 0x6d, 0x43, 0x68, 0x61, + 0x72, 0x43, 0x6f, 0x64, 0x65, 0x01, 0x0c, 0x69, + 0x6e, 0x46, 0x69, 0x6c, 0x65, 0x01, 0x0e, 0x6f, + 0x75, 0x74, 0x46, 0x69, 0x6c, 0x65, 0x01, 0x12, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x45, 0x78, + 0x65, 0x01, 0x04, 0x6a, 0x73, 0x01, 0x08, 0x63, + 0x6f, 0x64, 0x65, 0x01, 0x10, 0x62, 0x79, 0x74, + 0x65, 0x63, 0x6f, 0x64, 0x65, 0x01, 0x06, 0x65, + 0x78, 0x65, 0x01, 0x0e, 0x65, 0x78, 0x65, 0x53, + 0x69, 0x7a, 0x65, 0x01, 0x12, 0x6e, 0x65, 0x77, + 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x01, 0x0c, + 0x6e, 0x65, 0x77, 0x45, 0x78, 0x65, 0x01, 0x04, + 0x64, 0x77, 0x01, 0x0a, 0x6e, 0x65, 0x77, 0x46, + 0x64, 0x01, 0x10, 0x6c, 0x6f, 0x61, 0x64, 0x46, + 0x69, 0x6c, 0x65, 0x01, 0x1e, 0x66, 0x61, 0x69, + 0x6c, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x6f, + 0x70, 0x65, 0x6e, 0x20, 0x01, 0x14, 0x65, 0x76, + 0x61, 0x6c, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x01, 0x18, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, + 0x65, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x01, 0x1c, + 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x5f, + 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x01, 0x0a, + 0x77, 0x72, 0x69, 0x74, 0x65, 0x01, 0x0a, 0x61, + 0x72, 0x67, 0x76, 0x30, 0x01, 0x0c, 0x62, 0x69, + 0x6e, 0x61, 0x72, 0x79, 0x01, 0x0c, 0x62, 0x75, + 0x66, 0x66, 0x65, 0x72, 0x01, 0x10, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x01, 0x08, + 0x53, 0x69, 0x7a, 0x65, 0x01, 0x0a, 0x4d, 0x61, + 0x67, 0x69, 0x63, 0x01, 0x12, 0x4d, 0x61, 0x67, + 0x69, 0x63, 0x53, 0x69, 0x7a, 0x65, 0x01, 0x10, + 0x44, 0x61, 0x74, 0x61, 0x53, 0x69, 0x7a, 0x65, + 0x01, 0x12, 0x73, 0x65, 0x74, 0x55, 0x69, 0x6e, + 0x74, 0x33, 0x32, 0x01, 0x08, 0x6f, 0x70, 0x65, + 0x6e, 0x01, 0x10, 0x4f, 0x5f, 0x57, 0x52, 0x4f, + 0x4e, 0x4c, 0x59, 0x01, 0x0e, 0x4f, 0x5f, 0x43, + 0x52, 0x45, 0x41, 0x54, 0x01, 0x0e, 0x4f, 0x5f, + 0x54, 0x52, 0x55, 0x4e, 0x43, 0x01, 0x22, 0x66, + 0x61, 0x69, 0x6c, 0x65, 0x64, 0x20, 0x74, 0x6f, + 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, + 0x01, 0x14, 0x62, 0x79, 0x74, 0x65, 0x4c, 0x65, + 0x6e, 0x67, 0x74, 0x68, 0x01, 0x0a, 0x63, 0x6c, + 0x6f, 0x73, 0x65, 0x01, 0x3c, 0x66, 0x61, 0x69, + 0x6c, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x77, + 0x72, 0x69, 0x74, 0x65, 0x20, 0x74, 0x6f, 0x20, + 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20, 0x66, + 0x69, 0x6c, 0x65, 0x01, 0x08, 0x66, 0x69, 0x6c, + 0x65, 0x01, 0x02, 0x72, 0x01, 0x0e, 0x74, 0x72, + 0x61, 0x69, 0x6c, 0x65, 0x72, 0x01, 0x0a, 0x6d, + 0x61, 0x67, 0x69, 0x63, 0x01, 0x0c, 0x6f, 0x66, + 0x66, 0x73, 0x65, 0x74, 0x01, 0x04, 0x72, 0x62, + 0x01, 0x36, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, + 0x20, 0x74, 0x6f, 0x20, 0x6f, 0x70, 0x65, 0x6e, + 0x20, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x61, + 0x62, 0x6c, 0x65, 0x3a, 0x20, 0x01, 0x08, 0x73, + 0x65, 0x65, 0x6b, 0x01, 0x10, 0x53, 0x45, 0x45, + 0x4b, 0x5f, 0x45, 0x4e, 0x44, 0x01, 0x18, 0x73, + 0x65, 0x65, 0x6b, 0x20, 0x65, 0x72, 0x72, 0x6f, + 0x72, 0x3a, 0x20, 0x01, 0x08, 0x72, 0x65, 0x61, + 0x64, 0x01, 0x40, 0x63, 0x6f, 0x72, 0x72, 0x75, + 0x70, 0x74, 0x65, 0x64, 0x20, 0x62, 0x69, 0x6e, + 0x61, 0x72, 0x79, 0x2c, 0x20, 0x6d, 0x61, 0x67, + 0x69, 0x63, 0x20, 0x6d, 0x69, 0x73, 0x6d, 0x61, + 0x74, 0x63, 0x68, 0x01, 0x12, 0x67, 0x65, 0x74, + 0x55, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x01, 0x10, + 0x53, 0x45, 0x45, 0x4b, 0x5f, 0x53, 0x45, 0x54, + 0x01, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x01, + 0x14, 0x72, 0x65, 0x61, 0x64, 0x20, 0x65, 0x72, + 0x72, 0x6f, 0x72, 0x01, 0x16, 0x65, 0x76, 0x61, + 0x6c, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, + 0x01, 0x10, 0x71, 0x75, 0x69, 0x63, 0x6b, 0x6a, + 0x73, 0x32, 0x0d, 0xc0, 0x03, 0x03, 0xc2, 0x03, + 0xc4, 0x03, 0xc6, 0x03, 0x02, 0x00, 0x0b, 0xc8, + 0x03, 0x00, 0x0c, 0xca, 0x03, 0x00, 0x03, 0x00, + 0xfc, 0x01, 0x00, 0x01, 0xfc, 0x01, 0x01, 0x02, + 0xfc, 0x01, 0x02, 0x00, 0x0c, 0x20, 0x02, 0x01, + 0xa2, 0x01, 0x00, 0x00, 0x00, 0x02, 0x0d, 0x04, + 0x58, 0x00, 0xcc, 0x03, 0x00, 0x0d, 0xce, 0x03, + 0x01, 0x0d, 0xd0, 0x03, 0x02, 0x0d, 0xd2, 0x03, + 0x00, 0x0d, 0xd4, 0x03, 0x01, 0x0d, 0xd6, 0x03, + 0x02, 0x0d, 0xd8, 0x03, 0x03, 0x0d, 0xda, 0x03, + 0x04, 0x0d, 0xdc, 0x03, 0x05, 0x0d, 0xde, 0x03, + 0x06, 0x01, 0xe0, 0x03, 0x07, 0x01, 0xc8, 0x03, + 0x08, 0x01, 0xca, 0x03, 0x09, 0x01, 0x0c, 0x43, + 0x02, 0x01, 0xde, 0x03, 0x01, 0x00, 0x01, 0x05, + 0x00, 0x01, 0x1e, 0x01, 0xe2, 0x03, 0x00, 0x01, + 0x00, 0x0c, 0x42, 0x02, 0x01, 0x00, 0x01, 0x00, + 0x01, 0x03, 0x00, 0x00, 0x0a, 0x01, 0xe4, 0x03, + 0x00, 0x01, 0x00, 0xd1, 0x42, 0xf3, 0x00, 0x00, + 0x00, 0xb4, 0x25, 0x01, 0x00, 0x38, 0xa6, 0x00, + 0x00, 0x00, 0x11, 0xd1, 0x42, 0x5e, 0x00, 0x00, + 0x00, 0xc0, 0x24, 0x01, 0x00, 0x42, 0xf4, 0x00, + 0x00, 0x00, 0xbf, 0x00, 0x24, 0x01, 0x00, 0x21, + 0x01, 0x00, 0x28, 0x0c, 0x43, 0x02, 0x01, 0xe0, + 0x03, 0x01, 0x00, 0x01, 0x03, 0x00, 0x01, 0x21, + 0x01, 0xea, 0x03, 0x00, 0x01, 0x00, 0x0c, 0x42, + 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x03, 0x00, + 0x00, 0x0e, 0x01, 0xe4, 0x03, 0x00, 0x01, 0x00, + 0x38, 0x97, 0x00, 0x00, 0x00, 0x42, 0xf6, 0x00, + 0x00, 0x00, 0xd1, 0x25, 0x01, 0x00, 0x38, 0x94, + 0x00, 0x00, 0x00, 0x42, 0x7b, 0x00, 0x00, 0x00, + 0xd1, 0x24, 0x01, 0x00, 0x42, 0xf4, 0x00, 0x00, + 0x00, 0xbf, 0x00, 0x24, 0x01, 0x00, 0x42, 0x5c, + 0x00, 0x00, 0x00, 0xc0, 0x25, 0x01, 0x00, 0x0c, + 0x43, 0x02, 0x01, 0xc8, 0x03, 0x03, 0x09, 0x03, + 0x07, 0x08, 0x00, 0xde, 0x03, 0x0c, 0xee, 0x03, + 0x00, 0x01, 0x00, 0xf0, 0x03, 0x00, 0x01, 0x00, + 0xf2, 0x03, 0x00, 0x01, 0x00, 0xf4, 0x03, 0x01, + 0x00, 0x30, 0xf6, 0x03, 0x01, 0x01, 0x30, 0xf8, + 0x03, 0x01, 0x02, 0x30, 0xfa, 0x03, 0x01, 0x03, + 0x30, 0xfc, 0x03, 0x01, 0x04, 0x30, 0xfe, 0x03, + 0x01, 0x05, 0x30, 0x80, 0x04, 0x01, 0x06, 0x30, + 0x82, 0x04, 0x01, 0x07, 0x30, 0x84, 0x04, 0x01, + 0x08, 0x30, 0xcc, 0x03, 0x00, 0x0c, 0xd0, 0x03, + 0x02, 0x0c, 0xd6, 0x03, 0x05, 0x0c, 0xd8, 0x03, + 0x06, 0x0c, 0xda, 0x03, 0x07, 0x0c, 0xdc, 0x03, + 0x08, 0x0c, 0xde, 0x03, 0x09, 0x00, 0xce, 0x03, + 0x01, 0x0c, 0x61, 0x08, 0x00, 0x61, 0x07, 0x00, + 0x61, 0x06, 0x00, 0x61, 0x05, 0x00, 0x61, 0x04, + 0x00, 0x61, 0x03, 0x00, 0x61, 0x02, 0x00, 0x61, + 0x01, 0x00, 0x61, 0x00, 0x00, 0x65, 0x00, 0x00, + 0x42, 0x03, 0x01, 0x00, 0x00, 0xd1, 0x24, 0x01, + 0x00, 0xc9, 0x62, 0x00, 0x00, 0x96, 0xea, 0x19, + 0x38, 0x95, 0x00, 0x00, 0x00, 0x11, 0x04, 0x04, + 0x01, 0x00, 0x00, 0x42, 0x5d, 0x00, 0x00, 0x00, + 0xd1, 0x24, 0x01, 0x00, 0x21, 0x01, 0x00, 0x2f, + 0x65, 0x00, 0x00, 0x42, 0x05, 0x01, 0x00, 0x00, + 0x62, 0x00, 0x00, 0x0b, 0x0a, 0x4c, 0x06, 0x01, + 0x00, 0x00, 0x0a, 0x4c, 0x07, 0x01, 0x00, 0x00, + 0x24, 0x02, 0x00, 0xca, 0x38, 0xa6, 0x00, 0x00, + 0x00, 0x11, 0x65, 0x01, 0x00, 0x42, 0x08, 0x01, + 0x00, 0x00, 0x62, 0x01, 0x00, 0x65, 0x02, 0x00, + 0x65, 0x03, 0x00, 0xa4, 0x65, 0x04, 0x00, 0xa4, + 0x24, 0x02, 0x00, 0x21, 0x01, 0x00, 0xcb, 0x65, + 0x00, 0x00, 0x42, 0x03, 0x01, 0x00, 0x00, 0xd3, + 0x11, 0xb0, 0xea, 0x0c, 0x0e, 0x38, 0x8c, 0x00, + 0x00, 0x00, 0x41, 0x09, 0x01, 0x00, 0x00, 0x0b, + 0x0a, 0x4c, 0x0a, 0x01, 0x00, 0x00, 0x24, 0x02, + 0x00, 0xcc, 0x62, 0x03, 0x00, 0xe9, 0xc2, 0x04, + 0x62, 0x03, 0x00, 0x41, 0x0b, 0x01, 0x00, 0x00, + 0x42, 0x0c, 0x01, 0x00, 0x00, 0x62, 0x04, 0x00, + 0x62, 0x02, 0x00, 0xe9, 0x9d, 0x65, 0x05, 0x00, + 0x41, 0x0d, 0x01, 0x00, 0x00, 0x9d, 0x24, 0x01, + 0x00, 0xc2, 0x05, 0x38, 0xa6, 0x00, 0x00, 0x00, + 0x11, 0x62, 0x05, 0x00, 0x21, 0x01, 0x00, 0xc2, + 0x06, 0x62, 0x06, 0x00, 0x42, 0x43, 0x00, 0x00, + 0x00, 0x62, 0x02, 0x00, 0x62, 0x04, 0x00, 0x24, + 0x02, 0x00, 0x0e, 0x62, 0x06, 0x00, 0x42, 0x43, + 0x00, 0x00, 0x00, 0x5e, 0x06, 0x00, 0x65, 0x05, + 0x00, 0x41, 0x0e, 0x01, 0x00, 0x00, 0xef, 0x62, + 0x04, 0x00, 0x62, 0x02, 0x00, 0xe9, 0x9d, 0x24, + 0x02, 0x00, 0x0e, 0x38, 0xb0, 0x00, 0x00, 0x00, + 0x11, 0x62, 0x05, 0x00, 0x62, 0x04, 0x00, 0x62, + 0x02, 0x00, 0xe9, 0x9d, 0x65, 0x05, 0x00, 0x41, + 0x0f, 0x01, 0x00, 0x00, 0x9d, 0x65, 0x05, 0x00, + 0x41, 0x10, 0x01, 0x00, 0x00, 0x21, 0x03, 0x00, + 0xc2, 0x07, 0x62, 0x07, 0x00, 0x42, 0x11, 0x01, + 0x00, 0x00, 0xb4, 0x62, 0x04, 0x00, 0x0a, 0x24, + 0x03, 0x00, 0x0e, 0x65, 0x07, 0x00, 0x42, 0x12, + 0x01, 0x00, 0x00, 0xd2, 0x65, 0x07, 0x00, 0x41, + 0x13, 0x01, 0x00, 0x00, 0x65, 0x07, 0x00, 0x41, + 0x14, 0x01, 0x00, 0x00, 0xa4, 0x65, 0x07, 0x00, + 0x41, 0x15, 0x01, 0x00, 0x00, 0xa4, 0xbd, 0xed, + 0x01, 0x24, 0x03, 0x00, 0xc2, 0x08, 0x62, 0x08, + 0x00, 0xb4, 0xa6, 0xea, 0x19, 0x38, 0x95, 0x00, + 0x00, 0x00, 0x11, 0x04, 0x16, 0x01, 0x00, 0x00, + 0x42, 0x5d, 0x00, 0x00, 0x00, 0xd2, 0x24, 0x01, + 0x00, 0x21, 0x01, 0x00, 0x2f, 0x65, 0x07, 0x00, + 0x42, 0x08, 0x01, 0x00, 0x00, 0x62, 0x08, 0x00, + 0x62, 0x05, 0x00, 0xb4, 0x62, 0x05, 0x00, 0x41, + 0x17, 0x01, 0x00, 0x00, 0x24, 0x04, 0x00, 0xb4, + 0xa6, 0xea, 0x1f, 0x65, 0x07, 0x00, 0x42, 0x18, + 0x01, 0x00, 0x00, 0x62, 0x08, 0x00, 0x24, 0x01, + 0x00, 0x0e, 0x38, 0x95, 0x00, 0x00, 0x00, 0x11, + 0x04, 0x19, 0x01, 0x00, 0x00, 0x21, 0x01, 0x00, + 0x2f, 0x65, 0x07, 0x00, 0x42, 0x18, 0x01, 0x00, + 0x00, 0x62, 0x08, 0x00, 0x24, 0x01, 0x00, 0x29, + 0x0c, 0x43, 0x02, 0x01, 0xca, 0x03, 0x00, 0x09, + 0x00, 0x07, 0x06, 0x00, 0xa4, 0x04, 0x09, 0xb4, + 0x04, 0x01, 0x00, 0x30, 0xfa, 0x03, 0x01, 0x01, + 0x30, 0xb6, 0x04, 0x01, 0x02, 0x20, 0xb8, 0x04, + 0x01, 0x03, 0x30, 0xba, 0x04, 0x01, 0x04, 0x30, + 0x82, 0x04, 0x01, 0x05, 0x30, 0xbc, 0x04, 0x01, + 0x06, 0x30, 0xf8, 0x03, 0x01, 0x07, 0x30, 0xf6, + 0x03, 0x01, 0x08, 0x30, 0xcc, 0x03, 0x00, 0x0c, + 0xdc, 0x03, 0x08, 0x0c, 0xe0, 0x03, 0x0a, 0x00, + 0xd0, 0x03, 0x02, 0x0c, 0xd2, 0x03, 0x03, 0x0c, + 0xd4, 0x03, 0x04, 0x0c, 0x61, 0x08, 0x00, 0x61, + 0x07, 0x00, 0x61, 0x06, 0x00, 0x61, 0x05, 0x00, + 0x61, 0x04, 0x00, 0x61, 0x03, 0x00, 0x61, 0x02, + 0x00, 0x61, 0x01, 0x00, 0x61, 0x00, 0x00, 0x38, + 0x8c, 0x00, 0x00, 0x00, 0x41, 0x09, 0x01, 0x00, + 0x00, 0xc9, 0x65, 0x00, 0x00, 0x42, 0x12, 0x01, + 0x00, 0x00, 0x62, 0x00, 0x00, 0x04, 0x1f, 0x01, + 0x00, 0x00, 0x24, 0x02, 0x00, 0xca, 0x62, 0x01, + 0x00, 0x96, 0xea, 0x1b, 0x38, 0x95, 0x00, 0x00, + 0x00, 0x11, 0x04, 0x20, 0x01, 0x00, 0x00, 0x42, + 0x5d, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x24, + 0x01, 0x00, 0x21, 0x01, 0x00, 0x2f, 0x62, 0x01, + 0x00, 0x42, 0x21, 0x01, 0x00, 0x00, 0x65, 0x01, + 0x00, 0x41, 0x0d, 0x01, 0x00, 0x00, 0x8c, 0x65, + 0x00, 0x00, 0x41, 0x22, 0x01, 0x00, 0x00, 0x24, + 0x02, 0x00, 0xcb, 0x62, 0x02, 0x00, 0xb4, 0xa6, + 0xea, 0x1c, 0x38, 0x95, 0x00, 0x00, 0x00, 0x11, + 0x04, 0x23, 0x01, 0x00, 0x00, 0x42, 0x5d, 0x00, + 0x00, 0x00, 0x62, 0x02, 0x00, 0x8c, 0x24, 0x01, + 0x00, 0x21, 0x01, 0x00, 0x2f, 0x38, 0xa6, 0x00, + 0x00, 0x00, 0x11, 0x65, 0x01, 0x00, 0x41, 0x0d, + 0x01, 0x00, 0x00, 0x21, 0x01, 0x00, 0xcc, 0x62, + 0x01, 0x00, 0x42, 0x24, 0x01, 0x00, 0x00, 0x62, + 0x03, 0x00, 0x41, 0x0b, 0x01, 0x00, 0x00, 0xb4, + 0x65, 0x01, 0x00, 0x41, 0x0d, 0x01, 0x00, 0x00, + 0x24, 0x03, 0x00, 0x0e, 0x38, 0xa6, 0x00, 0x00, + 0x00, 0x11, 0x62, 0x03, 0x00, 0x41, 0x0b, 0x01, + 0x00, 0x00, 0xb4, 0x65, 0x01, 0x00, 0x41, 0x0f, + 0x01, 0x00, 0x00, 0x21, 0x03, 0x00, 0xc2, 0x04, + 0xdf, 0x62, 0x04, 0x00, 0xef, 0x65, 0x01, 0x00, + 0x41, 0x0e, 0x01, 0x00, 0x00, 0xaf, 0xea, 0x1c, + 0x62, 0x01, 0x00, 0x42, 0x18, 0x01, 0x00, 0x00, + 0x24, 0x00, 0x00, 0x0e, 0x38, 0x95, 0x00, 0x00, + 0x00, 0x11, 0x04, 0x25, 0x01, 0x00, 0x00, 0x21, + 0x01, 0x00, 0x2f, 0x38, 0xb0, 0x00, 0x00, 0x00, + 0x11, 0x62, 0x03, 0x00, 0x41, 0x0b, 0x01, 0x00, + 0x00, 0x65, 0x01, 0x00, 0x41, 0x0f, 0x01, 0x00, + 0x00, 0x65, 0x01, 0x00, 0x41, 0x10, 0x01, 0x00, + 0x00, 0x21, 0x03, 0x00, 0xc2, 0x05, 0x62, 0x05, + 0x00, 0x42, 0x26, 0x01, 0x00, 0x00, 0xb4, 0x0a, + 0x24, 0x02, 0x00, 0xc2, 0x06, 0x38, 0xa6, 0x00, + 0x00, 0x00, 0x11, 0x62, 0x06, 0x00, 0x65, 0x01, + 0x00, 0x41, 0x0d, 0x01, 0x00, 0x00, 0x9e, 0x21, + 0x01, 0x00, 0xc2, 0x07, 0x62, 0x01, 0x00, 0x42, + 0x21, 0x01, 0x00, 0x00, 0x62, 0x06, 0x00, 0x65, + 0x00, 0x00, 0x41, 0x27, 0x01, 0x00, 0x00, 0x24, + 0x02, 0x00, 0x11, 0x63, 0x02, 0x00, 0x0e, 0x62, + 0x02, 0x00, 0xb4, 0xa6, 0xea, 0x28, 0x62, 0x01, + 0x00, 0x42, 0x18, 0x01, 0x00, 0x00, 0x24, 0x00, + 0x00, 0x0e, 0x38, 0x95, 0x00, 0x00, 0x00, 0x11, + 0x04, 0x23, 0x01, 0x00, 0x00, 0x42, 0x5d, 0x00, + 0x00, 0x00, 0x62, 0x02, 0x00, 0x8c, 0x24, 0x01, + 0x00, 0x21, 0x01, 0x00, 0x2f, 0x62, 0x01, 0x00, + 0x42, 0x24, 0x01, 0x00, 0x00, 0x62, 0x07, 0x00, + 0x41, 0x0b, 0x01, 0x00, 0x00, 0xb4, 0x62, 0x07, + 0x00, 0xe9, 0x24, 0x03, 0x00, 0x0e, 0x62, 0x01, + 0x00, 0x42, 0x28, 0x01, 0x00, 0x00, 0x24, 0x00, + 0x00, 0xea, 0x1c, 0x62, 0x01, 0x00, 0x42, 0x18, + 0x01, 0x00, 0x00, 0x24, 0x00, 0x00, 0x0e, 0x38, + 0x95, 0x00, 0x00, 0x00, 0x11, 0x04, 0x29, 0x01, + 0x00, 0x00, 0x21, 0x01, 0x00, 0x2f, 0x62, 0x01, + 0x00, 0x42, 0x18, 0x01, 0x00, 0x00, 0x24, 0x00, + 0x00, 0x0e, 0x65, 0x03, 0x00, 0x42, 0x24, 0x01, + 0x00, 0x00, 0x62, 0x07, 0x00, 0x41, 0x0b, 0x01, + 0x00, 0x00, 0xb4, 0x62, 0x07, 0x00, 0xe9, 0x65, + 0x04, 0x00, 0x65, 0x05, 0x00, 0xa4, 0x24, 0x04, + 0x00, 0xc2, 0x08, 0x65, 0x00, 0x00, 0x42, 0x05, + 0x01, 0x00, 0x00, 0x62, 0x08, 0x00, 0x0b, 0x0a, + 0x4c, 0x2a, 0x01, 0x00, 0x00, 0x25, 0x02, 0x00, + 0x08, 0xea, 0x16, 0xbf, 0x00, 0x5f, 0x09, 0x00, + 0xbf, 0x01, 0x5f, 0x0a, 0x00, 0xbf, 0x02, 0x5f, + 0x0b, 0x00, 0xbf, 0x03, 0x5f, 0x0c, 0x00, 0x29, + 0xb5, 0xb4, 0x9f, 0xe4, 0xb5, 0xb7, 0x9f, 0x5f, + 0x04, 0x00, 0xb5, 0xb4, 0x9f, 0x5f, 0x05, 0x00, + 0xb5, 0xb7, 0x9f, 0x5f, 0x06, 0x00, 0xb5, 0xb8, + 0x9f, 0x5f, 0x07, 0x00, 0x0b, 0x04, 0x2b, 0x01, + 0x00, 0x00, 0x4c, 0x0e, 0x01, 0x00, 0x00, 0xbc, + 0x08, 0x4c, 0x0f, 0x01, 0x00, 0x00, 0xb8, 0x4c, + 0x10, 0x01, 0x00, 0x00, 0xbc, 0x0c, 0x4c, 0x0d, + 0x01, 0x00, 0x00, 0x5f, 0x08, 0x00, 0x06, 0x2e, +}; + diff --git a/qjs.c b/qjs.c index 40275ba7..ba5fd8f5 100644 --- a/qjs.c +++ b/qjs.c @@ -45,11 +45,65 @@ extern const uint8_t qjsc_repl[]; extern const uint32_t qjsc_repl_size; +extern const uint8_t qjsc_standalone[]; +extern const uint32_t qjsc_standalone_size; + +// Must match standalone.js +#define TRAILER_SIZE 12 +static const char trailer_magic[] = "quickjs2"; +static const int trailer_magic_size = sizeof(trailer_magic) - 1; +static const int trailer_size = TRAILER_SIZE; static int qjs__argc; static char **qjs__argv; +static BOOL is_standalone(const char *exe) +{ + FILE *exe_f = fopen(exe, "rb"); + if (!exe_f) + return FALSE; + if (fseek(exe_f, -trailer_size, SEEK_END) < 0) + goto fail; + uint8_t buf[TRAILER_SIZE]; + if (fread(buf, 1, trailer_size, exe_f) != trailer_size) + goto fail; + fclose(exe_f); + return !memcmp(buf, trailer_magic, trailer_magic_size); +fail: + fclose(exe_f); + return FALSE; +} + +static JSValue load_standalone_module(JSContext *ctx) +{ + JSModuleDef *m; + JSValue obj, val; + obj = JS_ReadObject(ctx, qjsc_standalone, qjsc_standalone_size, JS_READ_OBJ_BYTECODE); + if (JS_IsException(obj)) + goto exception; + assert(JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE); + if (JS_ResolveModule(ctx, obj) < 0) { + JS_FreeValue(ctx, obj); + goto exception; + } + js_module_set_import_meta(ctx, obj, FALSE, TRUE); + val = JS_EvalFunction(ctx, JS_DupValue(ctx, obj)); + val = js_std_await(ctx, val); + + if (JS_IsException(val)) { + JS_FreeValue(ctx, obj); + exception: + js_std_dump_error(ctx); + exit(1); + } + JS_FreeValue(ctx, val); + + m = JS_VALUE_GET_PTR(obj); + JS_FreeValue(ctx, obj); + return JS_GetModuleNamespace(ctx, m); +} + static int eval_buf(JSContext *ctx, const void *buf, int buf_len, const char *filename, int eval_flags) { @@ -333,6 +387,9 @@ void help(void) "-T --trace trace memory allocation\n" "-d --dump dump the memory usage stats\n" "-D --dump-flags flags for dumping debug data (see DUMP_* defines)\n" + "-c --compile FILE compile the given JS file as a standalone executable\n" + "-o --out FILE output file for standalone executables\n" + " --exe select the executable to use as the base, defaults to the current one\n" " --memory-limit n limit the memory usage to 'n' Kbytes\n" " --stack-size n limit the stack size to 'n' Kbytes\n" " --unhandled-rejection dump unhandled promise rejections\n" @@ -344,11 +401,15 @@ int main(int argc, char **argv) { JSRuntime *rt; JSContext *ctx; - JSValue ret; + JSValue ret = JS_UNDEFINED; struct trace_malloc_data trace_data = { NULL }; - int optind; + int optind = 1; + char *compile_file = NULL; + char *exe = NULL; char *expr = NULL; char *dump_flags_str = NULL; + char *out = NULL; + int standalone = 0; int interactive = 0; int dump_memory = 0; int dump_flags = 0; @@ -366,12 +427,16 @@ int main(int argc, char **argv) qjs__argc = argc; qjs__argv = argv; + if (is_standalone(argv[0])) { + standalone = 1; + goto start; + } + dump_flags_str = getenv("QJS_DUMP_FLAGS"); dump_flags = dump_flags_str ? strtol(dump_flags_str, NULL, 16) : 0; /* cannot use getopt because we want to pass the command line to the script */ - optind = 1; while (optind < argc && *argv[optind] == '-') { char *arg = argv[optind] + 1; const char *longopt = ""; @@ -405,7 +470,7 @@ int main(int argc, char **argv) if (!opt_arg) { if (optind >= argc) { fprintf(stderr, "qjs: missing expression for -e\n"); - exit(2); + exit(1); } opt_arg = argv[optind++]; } @@ -482,6 +547,39 @@ int main(int argc, char **argv) stack_size = parse_limit(opt_arg); break; } + if (opt == 'c' || !strcmp(longopt, "compile")) { + if (!opt_arg) { + if (optind >= argc) { + fprintf(stderr, "qjs: missing file for -c\n"); + exit(1); + } + opt_arg = argv[optind++]; + } + compile_file = opt_arg; + break; + } + if (opt == 'o' || !strcmp(longopt, "out")) { + if (!opt_arg) { + if (optind >= argc) { + fprintf(stderr, "qjs: missing file for -o\n"); + exit(1); + } + opt_arg = argv[optind++]; + } + out = opt_arg; + break; + } + if (!strcmp(longopt, "exe")) { + if (!opt_arg) { + if (optind >= argc) { + fprintf(stderr, "qjs: missing file for --exe\n"); + exit(1); + } + opt_arg = argv[optind++]; + } + exe = opt_arg; + break; + } if (opt) { fprintf(stderr, "qjs: unknown option '-%c'\n", opt); } else { @@ -491,6 +589,11 @@ int main(int argc, char **argv) } } + if (compile_file && !out) + help(); + +start: + if (trace_memory) { js_trace_malloc_init(&trace_data); rt = JS_NewRuntime2(&trace_mf, &trace_data); @@ -547,11 +650,37 @@ int main(int argc, char **argv) goto fail; } - if (expr) { + if (standalone) { + JSValue ns = load_standalone_module(ctx); + if (JS_IsException(ns)) + goto fail; + JSValue func = JS_GetPropertyStr(ctx, ns, "runStandalone"); + JS_FreeValue(ctx, ns); + if (JS_IsException(func)) + goto fail; + ret = JS_Call(ctx, func, JS_UNDEFINED, 0, NULL); + JS_FreeValue(ctx, func); + } else if (compile_file) { + JSValue ns = load_standalone_module(ctx); + if (JS_IsException(ns)) + goto fail; + JSValue func = JS_GetPropertyStr(ctx, ns, "compileStandalone"); + JS_FreeValue(ctx, ns); + if (JS_IsException(func)) + goto fail; + JSValue args[3]; + args[0] = JS_NewString(ctx, compile_file); + args[1] = JS_NewString(ctx, out); + args[2] = JS_NewString(ctx, exe != NULL ? exe : argv[0]); + ret = JS_Call(ctx, func, JS_UNDEFINED, countof(args), args); + JS_FreeValue(ctx, func); + JS_FreeValue(ctx, args[0]); + JS_FreeValue(ctx, args[1]); + JS_FreeValue(ctx, args[2]); + } else if (expr) { if (eval_buf(ctx, expr, strlen(expr), "", 0)) goto fail; - } else - if (optind >= argc) { + } else if (optind >= argc) { /* interactive mode */ interactive = 1; } else { @@ -563,7 +692,16 @@ int main(int argc, char **argv) if (interactive) { js_std_eval_binary(ctx, qjsc_repl, qjsc_repl_size, 0); } - ret = js_std_loop(ctx); + if (standalone || compile_file) { + if (JS_IsException(ret)) { + ret = JS_GetException(ctx); + } else { + JS_FreeValue(ctx, ret); + ret = js_std_loop(ctx); + } + } else { + ret = js_std_loop(ctx); + } if (!JS_IsUndefined(ret)) { js_std_dump_error1(ctx, ret); JS_FreeValue(ctx, ret); diff --git a/standalone.js b/standalone.js new file mode 100644 index 00000000..75a9d6b0 --- /dev/null +++ b/standalone.js @@ -0,0 +1,123 @@ +import * as std from "qjs:std"; +import * as os from "qjs:os"; +import * as bjson from "qjs:bjson"; + +// See quickjs.h +const JS_READ_OBJ_BYTECODE = 1 << 0; +const JS_READ_OBJ_REFERENCE = 1 << 3; +const JS_WRITE_OBJ_BYTECODE = 1 << 0; +const JS_WRITE_OBJ_REFERENCE = 1 << 3; +const JS_WRITE_OBJ_STRIP_SOURCE = 1 << 4; + +/** + * Trailer for standalone binaries. When some code gets bundled with the qjs + * executable we add a 12 byte trailer. The first 8 bytes are the magic + * string that helps us understand this is a standalone binary, and the + * remaining 4 are the offset (from the beginning of the binary) where the + * bundled data is located. + * + * The offset is stored as a 32bit little-endian number. + */ +const Trailer = { + Magic: 'quickjs2', + MagicSize: 8, + DataSize: 4, + Size: 12 +}; + +function encodeAscii(txt) { + return new Uint8Array(txt.split('').map(c => c.charCodeAt(0))); +} + +function decodeAscii(buf) { + return Array.from(buf).map(c => String.fromCharCode(c)).join('') +} + +export function compileStandalone(inFile, outFile, targetExe) { + // Step 1: compile the source file to bytecode + const js = std.loadFile(inFile); + + if (!js) { + throw new Error(`failed to open ${inFile}`); + } + + const code = std.evalScript(js, { + compile_only: true, + compile_module: true + }); + const bytecode = new Uint8Array(bjson.write(code, JS_WRITE_OBJ_BYTECODE | JS_WRITE_OBJ_REFERENCE | JS_WRITE_OBJ_STRIP_SOURCE)); + + // Step 2: copy the bytecode to the end of the executable and add a marker. + const exe = std.loadFile(targetExe ?? globalThis.argv0, { binary: true }); + const exeSize = exe.length; + const newBuffer = exe.buffer.transfer(exeSize + bytecode.length + Trailer.Size); + const newExe = new Uint8Array(newBuffer); + + newExe.set(bytecode, exeSize); + newExe.set(encodeAscii(Trailer.Magic), exeSize + bytecode.length); + + const dw = new DataView(newBuffer, exeSize + bytecode.length + Trailer.MagicSize, Trailer.DataSize); + + dw.setUint32(0, exeSize, true /* little-endian */); + + // We use os.open() so we can set the permissions mask. + const newFd = os.open(outFile, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o755); + + if (newFd < 0) { + throw new Error(`failed to create ${outFile}`); + } + if (os.write(newFd, newBuffer, 0, newBuffer.byteLength) < 0) { + os.close(newFd); + throw new Error(`failed to write to output file`); + } + os.close(newFd); +} + +export function runStandalone() { + const file = globalThis.argv0; + const exe = std.open(file, 'rb'); + + if (!exe) { + throw new Error(`failed to open executable: ${file}`); + } + + let r = exe.seek(-Trailer.Size, std.SEEK_END); + if (r < 0) { + throw new Error(`seek error: ${-r}`); + } + + const trailer = new Uint8Array(Trailer.Size); + + exe.read(trailer.buffer, 0, Trailer.Size); + + const magic = new Uint8Array(trailer.buffer, 0, Trailer.MagicSize); + + // Shouldn't happen since qjs.c checks for it. + if (decodeAscii(magic) !== Trailer.Magic) { + exe.close(); + throw new Error('corrupted binary, magic mismatch'); + } + + const dw = new DataView(trailer.buffer, Trailer.MagicSize, Trailer.DataSize); + const offset = dw.getUint32(0, true /* little-endian */); + const bytecode = new Uint8Array(offset - Trailer.Size); + + r = exe.seek(offset, std.SEEK_SET); + if (r < 0) { + exe.close(); + throw new Error(`seek error: ${-r}`); + } + + exe.read(bytecode.buffer, 0, bytecode.length); + if (exe.error()) { + exe.close(); + throw new Error('read error'); + } + exe.close(); + + const code = bjson.read(bytecode.buffer, 0, bytecode.length, JS_READ_OBJ_BYTECODE | JS_READ_OBJ_REFERENCE); + + return std.evalScript(code, { + eval_module: true + }); +}