From 470490d7c2db617fd169a76ea532bfa824103bc1 Mon Sep 17 00:00:00 2001 From: Steve Fan Date: Thu, 26 Jun 2025 14:39:17 +0800 Subject: [PATCH 01/10] Add devcontainer --- .devcontainer/Dockerfile | 19 ++++++++++ .devcontainer/devcontainer.json | 62 ++++++++++++++++++++++++++++++++ .devcontainer/reinstall-cmake.sh | 59 ++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/reinstall-cmake.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..1da058a90 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,19 @@ +FROM mcr.microsoft.com/devcontainers/cpp:1-ubuntu-24.04 + +ARG REINSTALL_CMAKE_VERSION_FROM_SOURCE="none" + +# Optionally install the cmake for vcpkg +COPY ./reinstall-cmake.sh /tmp/ + +RUN if [ "${REINSTALL_CMAKE_VERSION_FROM_SOURCE}" != "none" ]; then \ + chmod +x /tmp/reinstall-cmake.sh && /tmp/reinstall-cmake.sh ${REINSTALL_CMAKE_VERSION_FROM_SOURCE}; \ + fi \ + && rm -f /tmp/reinstall-cmake.sh + +# [Optional] Uncomment this section to install additional vcpkg ports. +# RUN su vscode -c "${VCPKG_ROOT}/vcpkg install " + +# [Optional] Uncomment this section to install additional packages. +RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ + && apt-get -y install --no-install-recommends clangd shellcheck tcc libasan0 \ + && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..60b0e7cce --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,62 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/cpp +{ + "name": "QuickJS-ng Devcontainer", + "build": { + "dockerfile": "Dockerfile" + }, + + // Features to add to the dev container. More info: https://containers.dev/features. + "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "gcc -v", + + // Configure tool-specific properties. + "customizations": { + // Configure properties specific to VS Code. + "vscode": { + "settings": { + "workbench.colorTheme": "Visual Studio Dark - C++", + "workbench.preferredDarkColorTheme": "Visual Studio Dark - C++", + "workbench.preferredHighContrastColorTheme": "Default High Contrast", + "workbench.preferredLightColorTheme": "Visual Studio Light - C++", + "window.autoDetectColorScheme": true, + "[cpp]": { + "editor.defaultFormatter": "llvm-vs-code-extensions.vscode-clangd", + "editor.formatOnSave": true + } + }, + "extensions": [ + "bierner.markdown-preview-github-styles", + "cheshirekow.cmake-format", + "davidanson.vscode-markdownlint", + "eamodio.gitlens", + "esbenp.prettier-vscode", + "franneck94.vscode-c-cpp-dev-extension-pack", + "fredericbonnet.cmake-test-adapter", + "hbenl.vscode-test-explorer", + "josetr.cmake-language-support-vscode", + "llvm-vs-code-extensions.vscode-clangd", + "matepek.vscode-catch2-test-adapter", + "mhutchie.git-graph", + "ms-vscode.cmake-tools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.cpptools-themes", + "ms-vscode.cpptools", + "ms-vscode.remote-explorer", + "ms-vscode.test-adapter-converter", + "redhat.vscode-yaml", + "solomonkinard.workspace-include-what-you-use", + "sr-team.vscode-clangd-cmake", + "streetsidesoftware.code-spell-checker", + "timonwong.shellcheck", + "vadimcn.vscode-lldb", + "xaver.clang-format" + ] + } + } +} diff --git a/.devcontainer/reinstall-cmake.sh b/.devcontainer/reinstall-cmake.sh new file mode 100644 index 000000000..408b81d22 --- /dev/null +++ b/.devcontainer/reinstall-cmake.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- +# +set -e + +CMAKE_VERSION=${1:-"none"} + +if [ "${CMAKE_VERSION}" = "none" ]; then + echo "No CMake version specified, skipping CMake reinstallation" + exit 0 +fi + +# Cleanup temporary directory and associated files when exiting the script. +cleanup() { + EXIT_CODE=$? + set +e + if [[ -n "${TMP_DIR}" ]]; then + echo "Executing cleanup of tmp files" + rm -Rf "${TMP_DIR}" + fi + exit $EXIT_CODE +} +trap cleanup EXIT + + +echo "Installing CMake..." +apt-get -y purge --auto-remove cmake +mkdir -p /opt/cmake + +architecture=$(dpkg --print-architecture) +case "${architecture}" in + arm64) + ARCH=aarch64 ;; + amd64) + ARCH=x86_64 ;; + *) + echo "Unsupported architecture ${architecture}." + exit 1 + ;; +esac + +CMAKE_BINARY_NAME="cmake-${CMAKE_VERSION}-linux-${ARCH}.sh" +CMAKE_CHECKSUM_NAME="cmake-${CMAKE_VERSION}-SHA-256.txt" +TMP_DIR=$(mktemp -d -t cmake-XXXXXXXXXX) + +echo "${TMP_DIR}" +cd "${TMP_DIR}" + +curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${CMAKE_BINARY_NAME}" -O +curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${CMAKE_CHECKSUM_NAME}" -O + +sha256sum -c --ignore-missing "${CMAKE_CHECKSUM_NAME}" +sh "${TMP_DIR}/${CMAKE_BINARY_NAME}" --prefix=/opt/cmake --skip-license + +ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake +ln -s /opt/cmake/bin/ctest /usr/local/bin/ctest From 9e0be2d01c19427e10f7780f6ff5c7e83ecdaf59 Mon Sep 17 00:00:00 2001 From: Steve Fan Date: Thu, 26 Jun 2025 14:42:48 +0800 Subject: [PATCH 02/10] Revmoe libasan0 --- .devcontainer/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 1da058a90..def0869eb 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -15,5 +15,5 @@ RUN if [ "${REINSTALL_CMAKE_VERSION_FROM_SOURCE}" != "none" ]; then \ # [Optional] Uncomment this section to install additional packages. RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ - && apt-get -y install --no-install-recommends clangd shellcheck tcc libasan0 \ + && apt-get -y install --no-install-recommends clangd shellcheck tcc \ && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* \ No newline at end of file From a91eca5bca56229237c01e91ef862f48dbd2819a Mon Sep 17 00:00:00 2001 From: Steve Fan Date: Thu, 26 Jun 2025 06:51:19 +0000 Subject: [PATCH 03/10] ignore .cache folder --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ff10fcd14..1b39788f3 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ CMakeUserPresets.json fuzz .vscode/ microbench*.txt +.cache/ \ No newline at end of file From 73551484ca71cc2f931c1e949f8bbdc78cf161d5 Mon Sep 17 00:00:00 2001 From: Steve Fan Date: Thu, 26 Jun 2025 07:03:42 +0000 Subject: [PATCH 04/10] recursively pull submodules --- .devcontainer/devcontainer.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 60b0e7cce..a9d9ef89b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -11,10 +11,8 @@ // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], - - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "gcc -v", - + "postCreateCommand": "git submodule update --init", + // Configure tool-specific properties. "customizations": { // Configure properties specific to VS Code. From db4992c51c62e2608cf12ce062cb41f7f749e4af Mon Sep 17 00:00:00 2001 From: Steve Fan Date: Thu, 26 Jun 2025 07:47:52 +0000 Subject: [PATCH 05/10] add sljit --- .gitmodules | 3 +++ CMakeLists.txt | 21 +++++++++++++++++++++ quickjs.c | 4 ++++ sljit | 1 + 4 files changed, 29 insertions(+) create mode 160000 sljit diff --git a/.gitmodules b/.gitmodules index 91828e05a..91c148df4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,3 +3,6 @@ url = https://github.com/tc39/test262 shallow = true update = none +[submodule "sljit"] + path = sljit + url = https://github.com/zherczeg/sljit diff --git a/CMakeLists.txt b/CMakeLists.txt index 91c6c683e..75d17509f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -149,6 +149,7 @@ xoption(QJS_ENABLE_ASAN "Enable AddressSanitizer (ASan)" OFF) xoption(QJS_ENABLE_MSAN "Enable MemorySanitizer (MSan)" OFF) xoption(QJS_ENABLE_TSAN "Enable ThreadSanitizer (TSan)" OFF) xoption(QJS_ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer (UBSan)" OFF) +xoption(QJS_ENABLE_SLJIT "Enable sljit" ON) if(QJS_ENABLE_ASAN) message(STATUS "Building with ASan") @@ -279,6 +280,19 @@ if(EMSCRIPTEN) target_link_libraries(qjs_wasm m) endif() +if (QJS_ENABLE_SLJIT) +file(GLOB SLJIT_SRC + ${CMAKE_CURRENT_SOURCE_DIR}/sljit/sljit_src/sljitLir.c +) + +add_library(sljit ${SLJIT_SRC}) +target_include_directories(sljit PUBLIC + $ + $ +) + +target_link_libraries(qjs PRIVATE sljit) +endif() # QuickJS bytecode compiler # @@ -441,4 +455,11 @@ if(NOT IOS) install(EXPORT qjsConfig DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/quickjs) install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR}) install(DIRECTORY examples DESTINATION ${CMAKE_INSTALL_DOCDIR}) + + if(QJS_ENABLE_SLJIT) + install(TARGETS sljit EXPORT qjsConfig + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + endif() endif() diff --git a/quickjs.c b/quickjs.c index fb42ff8da..c8d9abce0 100644 --- a/quickjs.c +++ b/quickjs.c @@ -49,6 +49,10 @@ #include "libregexp.h" #include "xsum.h" +#if defined(QJS_ENABLE_SLJIT) +#include +#endif + #if defined(EMSCRIPTEN) || defined(_MSC_VER) #define DIRECT_DISPATCH 0 #else diff --git a/sljit b/sljit new file mode 160000 index 000000000..7779da89d --- /dev/null +++ b/sljit @@ -0,0 +1 @@ +Subproject commit 7779da89dc7e00a540712fcb859b762d4eca2a26 From 8f693de138e3b829aaff371eeba39bc5fbd06305 Mon Sep 17 00:00:00 2001 From: Steve Fan Date: Thu, 26 Jun 2025 10:03:18 +0000 Subject: [PATCH 06/10] add sljit and zydis to display dumps --- .gitmodules | 3 ++ CMakeLists.txt | 18 ++++++++ quickjs.c | 123 +++++++++++++++++++++++++++++++++++++++++++++++++ zydis | 1 + 4 files changed, 145 insertions(+) create mode 160000 zydis diff --git a/.gitmodules b/.gitmodules index 91c148df4..63bd7dda8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,3 +6,6 @@ [submodule "sljit"] path = sljit url = https://github.com/zherczeg/sljit +[submodule "zydis"] + path = zydis + url = https://github.com/zyantific/zydis diff --git a/CMakeLists.txt b/CMakeLists.txt index 75d17509f..a8acf96d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -281,6 +281,16 @@ if(EMSCRIPTEN) endif() if (QJS_ENABLE_SLJIT) + +xoption(SLJIT_DEBUG "sljit debug mode" OFF) +xoption(SLJIT_VERBOSE "sljit verbose mode" OFF) + +if(CMAKE_BUILD_TYPE MATCHES "Debug") + set(SLJIT_DEBUG ON) + set(SLJIT_VERBOSE ON) +endif() + + file(GLOB SLJIT_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sljit/sljit_src/sljitLir.c ) @@ -291,7 +301,15 @@ target_include_directories(sljit PUBLIC $ ) + target_link_libraries(qjs PRIVATE sljit) + +if (SLJIT_VERBOSE) + add_subdirectory(zydis) + target_link_libraries(qjs PRIVATE Zydis::Zydis) + target_compile_options(Zycore PRIVATE -Wno-format-nonliteral) +endif() + endif() # QuickJS bytecode compiler diff --git a/quickjs.c b/quickjs.c index c8d9abce0..f3c76a341 100644 --- a/quickjs.c +++ b/quickjs.c @@ -51,6 +51,11 @@ #if defined(QJS_ENABLE_SLJIT) #include + +#if SLJIT_VERBOSE +#include +#endif + #endif #if defined(EMSCRIPTEN) || defined(_MSC_VER) @@ -654,6 +659,18 @@ typedef enum JSFunctionKindEnum { JS_FUNC_ASYNC_GENERATOR = (JS_FUNC_GENERATOR | JS_FUNC_ASYNC), } JSFunctionKindEnum; + +struct JitAux { + JSValue *stack_buf; + JSValue *var_buf; + JSValue *arg_buf; + JSValue *sp; + JSVarRef **var_refs; + JSStackFrame *sf; + JSObject *p; + JSContext *caller_ctx; +}; + typedef struct JSFunctionBytecode { JSGCObjectHeader header; /* must come first */ uint8_t is_strict_mode : 1; @@ -689,8 +706,16 @@ typedef struct JSFunctionBytecode { int pc2line_len; uint8_t *pc2line_buf; char *source; +#ifdef QJS_ENABLE_SLJIT + JSValue (*jitcode)(JSContext *ctx, JSValueConst func_obj, + JSValueConst this_obj, JSValueConst new_target, + int argc, JSValue *argv, int flags, + struct JitAux *aux); +#endif } JSFunctionBytecode; + + typedef struct JSBoundFunction { JSValue func_obj; JSValue this_val; @@ -16477,6 +16502,8 @@ static bool needs_backtrace(JSValue exc) return !find_own_property1(p, JS_ATOM_stack); } +static void js_jit(JSContext *ctx, JSFunctionBytecode *b); + /* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, JSValueConst this_obj, JSValueConst new_target, @@ -16608,6 +16635,31 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, print_func_name(b); #endif +#ifdef QJS_ENABLE_SLJIT +jit: + if (!b->jitcode) { + js_jit(ctx, b); + goto jit; + } else { + // struct JitAux aux = { + // .caller_ctx = caller_ctx, + // .var_refs = var_refs, + // .var_buf = var_buf, + // .arg_buf = arg_buf, + // .sf = sf, + // .sp = sp, + // .p = p, + // }; + // ret_val = b->jitcode(ctx, func_obj, this_obj, new_target, + // argc, argv, flags, &aux); + // sp = aux.sp; + // if (JS_IsException(ret_val)) + // goto exception; + // // TODO handle b->func_kind != JS_FUNC_NORMAL + // goto done; + } +#endif + restart: for(;;) { int call_argc; @@ -33890,6 +33942,77 @@ static int add_module_variables(JSContext *ctx, JSFunctionDef *fd) return 0; } +#ifdef QJS_ENABLE_SLJIT +typedef sljit_sw (SLJIT_FUNC *func3_t)(sljit_sw a, sljit_sw b, sljit_sw c); + +static void js_jit(JSContext *ctx, JSFunctionBytecode *b) { + uint8_t *pc; + // int opcode; + + pc = b->byte_code_buf; + + struct sljit_compiler *c = sljit_create_compiler(NULL); + sljit_emit_enter(c, 0, 3, 1, 3, 0); +#if 0 +#if !DIRECT_DISPATCH +#define SWITCH(pc) switch (opcode = *pc++) +#define CASE(op) case op +#define DEFAULT default +#define BREAK break + +#else + __extension__ static const void * const dispatch_table[256] = { +#define DEF(id, size, n_pop, n_push, f) && case_OP_ ## id, +#define def(id, size, n_pop, n_push, f) +#include "quickjs-opcode.h" + [ OP_COUNT ... 255 ] = &&case_default + }; +#define SWITCH(pc) DUMP_BYTECODE_OR_DONT(pc) __extension__ ({ goto *dispatch_table[opcode = *pc++]; }); +#define CASE(op) case_ ## op +#define DEFAULT case_default +#define BREAK SWITCH(pc) +#endif + + for(;;) { + SWITCH(pc) { + CASE(OP_push_i32): + + BREAK + + DEFAULT: + BREAK + + } + } +#endif + b->jitcode = sljit_generate_code(c, 0, NULL); +#if SLJIT_VERBOSE + + size_t len = sljit_get_generated_code_size(c); + // The runtime address (instruction pointer) was chosen arbitrarily here in order to better + // visualize relative addressing. In your actual program, set this to e.g. the memory address + // that the code being disassembled was read from. + ZyanU64 runtime_address = (ZyanU64) b->jitcode; + + // Loop over the instructions in our buffer. + ZyanUSize offset = 0; + ZydisDisassembledInstruction instruction; + while (ZYAN_SUCCESS(ZydisDisassembleIntel( + ZYDIS_MACHINE_MODE_LONG_64, + runtime_address, + (const void*) (runtime_address + offset), + len - offset, + &instruction + ))) { + printf("%016" PRIX64 " %s\n", runtime_address, instruction.text); + offset += instruction.info.length; + runtime_address += instruction.info.length; + } +#endif + sljit_free_compiler(c); +} +#endif + /* create a function object from a function definition. The function definition is freed. All the child functions are also created. It must be done this way to resolve all the variables. */ diff --git a/zydis b/zydis new file mode 160000 index 000000000..48e750650 --- /dev/null +++ b/zydis @@ -0,0 +1 @@ +Subproject commit 48e7506503dd2b72ee6667eeeed0164e40cdd7e8 From 176ee599a5bb8b40ba7e881db17128c7ff8fe3d5 Mon Sep 17 00:00:00 2001 From: Steve Fan Date: Thu, 26 Jun 2025 10:18:09 +0000 Subject: [PATCH 07/10] change the sljit prefix --- CMakeLists.txt | 19 ++++++++++--------- quickjs.c | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a8acf96d2..7a8871285 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -281,16 +281,14 @@ if(EMSCRIPTEN) endif() if (QJS_ENABLE_SLJIT) - -xoption(SLJIT_DEBUG "sljit debug mode" OFF) -xoption(SLJIT_VERBOSE "sljit verbose mode" OFF) +xoption(QJS_SLJIT_DEBUG "sljit debug mode" OFF) +xoption(QJS_SLJIT_VERBOSE "sljit verbose mode" OFF) if(CMAKE_BUILD_TYPE MATCHES "Debug") - set(SLJIT_DEBUG ON) - set(SLJIT_VERBOSE ON) + set(QJS_SLJIT_DEBUG ON) + set(QJS_SLJIT_VERBOSE ON) endif() - file(GLOB SLJIT_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sljit/sljit_src/sljitLir.c ) @@ -300,11 +298,14 @@ target_include_directories(sljit PUBLIC $ $ ) - - target_link_libraries(qjs PRIVATE sljit) -if (SLJIT_VERBOSE) +if (QJS_SLJIT_DEBUG) + target_compile_definitions(sljit PUBLIC SLJIT_DEBUG=0) +endif() + +if (QJS_SLJIT_VERBOSE) + target_compile_definitions(sljit PUBLIC SLJIT_VERBOSE=0) add_subdirectory(zydis) target_link_libraries(qjs PRIVATE Zydis::Zydis) target_compile_options(Zycore PRIVATE -Wno-format-nonliteral) diff --git a/quickjs.c b/quickjs.c index f3c76a341..d3d4c7b58 100644 --- a/quickjs.c +++ b/quickjs.c @@ -49,15 +49,6 @@ #include "libregexp.h" #include "xsum.h" -#if defined(QJS_ENABLE_SLJIT) -#include - -#if SLJIT_VERBOSE -#include -#endif - -#endif - #if defined(EMSCRIPTEN) || defined(_MSC_VER) #define DIRECT_DISPATCH 0 #else @@ -82,6 +73,15 @@ #define CONFIG_ATOMICS #endif + +#if defined(QJS_ENABLE_SLJIT) +#include + +#ifdef QJS_SLJIT_VERBOSE +#include +#endif +#endif + #ifndef __GNUC__ #define __extension__ #endif @@ -33986,7 +33986,7 @@ static void js_jit(JSContext *ctx, JSFunctionBytecode *b) { } #endif b->jitcode = sljit_generate_code(c, 0, NULL); -#if SLJIT_VERBOSE +#if QJS_SLJIT_VERBOSE size_t len = sljit_get_generated_code_size(c); // The runtime address (instruction pointer) was chosen arbitrarily here in order to better From 4eb4748609c1b55b19716463d0e860349cdb25d4 Mon Sep 17 00:00:00 2001 From: Steve Fan Date: Thu, 26 Jun 2025 10:18:46 +0000 Subject: [PATCH 08/10] fixup! change the sljit prefix --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a8871285..a8e0d2c28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -301,11 +301,11 @@ target_include_directories(sljit PUBLIC target_link_libraries(qjs PRIVATE sljit) if (QJS_SLJIT_DEBUG) - target_compile_definitions(sljit PUBLIC SLJIT_DEBUG=0) + target_compile_definitions(sljit PUBLIC SLJIT_DEBUG=1) endif() if (QJS_SLJIT_VERBOSE) - target_compile_definitions(sljit PUBLIC SLJIT_VERBOSE=0) + target_compile_definitions(sljit PUBLIC SLJIT_VERBOSE=1) add_subdirectory(zydis) target_link_libraries(qjs PRIVATE Zydis::Zydis) target_compile_options(Zycore PRIVATE -Wno-format-nonliteral) From 6dda7743b535d3a3693cf0f06de8742034fd1e4d Mon Sep 17 00:00:00 2001 From: Steve Fan Date: Thu, 26 Jun 2025 10:31:12 +0000 Subject: [PATCH 09/10] collect the jit generated code when function is gc'd --- quickjs.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/quickjs.c b/quickjs.c index d3d4c7b58..ced4b9b98 100644 --- a/quickjs.c +++ b/quickjs.c @@ -5777,8 +5777,17 @@ static void js_free_value_rt(JSRuntime *rt, JSValue v) } } break; + case JS_TAG_FUNCTION_BYTECODE: + { +#ifdef QJS_ENABLE_SLJIT + JSFunctionBytecode *b = JS_GetFunctionBytecode(v); + if (b && b->jitcode) { + sljit_free_code(b->jitcode, NULL); + b->jitcode = NULL; + } +#endif + } case JS_TAG_OBJECT: - case JS_TAG_FUNCTION_BYTECODE: { JSGCObjectHeader *p = JS_VALUE_GET_PTR(v); if (rt->gc_phase != JS_GC_PHASE_REMOVE_CYCLES) { @@ -33987,7 +33996,6 @@ static void js_jit(JSContext *ctx, JSFunctionBytecode *b) { #endif b->jitcode = sljit_generate_code(c, 0, NULL); #if QJS_SLJIT_VERBOSE - size_t len = sljit_get_generated_code_size(c); // The runtime address (instruction pointer) was chosen arbitrarily here in order to better // visualize relative addressing. In your actual program, set this to e.g. the memory address From 5e13f8c173b39adf6cbefaa7e1a8ab1c3a20d7d9 Mon Sep 17 00:00:00 2001 From: Steve Fan <29133953+stevefan1999-personal@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:45:17 +0800 Subject: [PATCH 10/10] try to implement some bytecodes --- CMakeLists.txt | 9 +- quickjs.c | 455 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 437 insertions(+), 27 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a8e0d2c28..63d84c476 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -281,12 +281,13 @@ if(EMSCRIPTEN) endif() if (QJS_ENABLE_SLJIT) -xoption(QJS_SLJIT_DEBUG "sljit debug mode" OFF) -xoption(QJS_SLJIT_VERBOSE "sljit verbose mode" OFF) if(CMAKE_BUILD_TYPE MATCHES "Debug") - set(QJS_SLJIT_DEBUG ON) - set(QJS_SLJIT_VERBOSE ON) +xoption(QJS_SLJIT_DEBUG "sljit debug mode" ON) +xoption(QJS_SLJIT_VERBOSE "sljit verbose mode" ON) +else() +xoption(QJS_SLJIT_DEBUG "sljit debug mode" OFF) +xoption(QJS_SLJIT_VERBOSE "sljit verbose mode" OFF) endif() file(GLOB SLJIT_SRC diff --git a/quickjs.c b/quickjs.c index ced4b9b98..50e95dcac 100644 --- a/quickjs.c +++ b/quickjs.c @@ -33952,50 +33952,458 @@ static int add_module_variables(JSContext *ctx, JSFunctionDef *fd) } #ifdef QJS_ENABLE_SLJIT -typedef sljit_sw (SLJIT_FUNC *func3_t)(sljit_sw a, sljit_sw b, sljit_sw c); +typedef struct jit_aux { + JSValueConst func_obj; + JSValueConst this_obj; + JSValueConst new_target; + int argc; + JSValueConst *argv; + int flags; + + JSObject *p; + JSStackFrame sf_s, *sf; + int arg_allocated_size; + JSValue *local_buf, *stack_buf, *var_buf, *arg_buf, ret_val, *pval; + JSVarRef **var_refs; + size_t alloca_size; +} *jit_aux; + +typedef void (*jit_execute_func_t)( + JSContext *ctx, // S0 + JSFunctionBytecode *b, // S1 + JSValue **rsp, // S2 + jit_aux *aux // S3 +); + +static void js_jit_move_js_value(struct sljit_compiler *c, JSValue val) { + // R0 = *rsp = sp; + sljit_emit_op1(c, SLJIT_MOV, SLJIT_R0, 0, SLJIT_MEM1(SLJIT_S2), 0); + // *(*R0 + offsetof(u)) = val.u; + sljit_emit_op1(c, SLJIT_MOV, SLJIT_MEM1(SLJIT_R0), SLJIT_OFFSETOF(JSValue, u), SLJIT_IMM, (sljit_up)val.u.ptr); + // *(*R0 + offsetof(tag)) = val.tag; + sljit_emit_op1(c, SLJIT_MOV, SLJIT_MEM1(SLJIT_R0), SLJIT_OFFSETOF(JSValue, tag), SLJIT_IMM, val.tag); +} + +static void js_jit_sp_add(struct sljit_compiler *c, int count) { + if (count < 0) { + // (*rsp) = (*rsp) + count + sljit_emit_op2(c, SLJIT_ADD, SLJIT_MEM1(SLJIT_S2), 0, SLJIT_MEM1(SLJIT_S2), 0, SLJIT_IMM, count); + } else { + // (*rsp) = (*rsp) - count + sljit_emit_op2(c, SLJIT_SUB, SLJIT_MEM1(SLJIT_S2), 0, SLJIT_MEM1(SLJIT_S2), 0, SLJIT_IMM, -count); + } +} + +static void js_jit_JS_FreeValue_helper(JSContext *ctx, JSValue *val) { + JS_FreeValue(ctx, *val); +} + +static void js_jit_free_value_stack(struct sljit_compiler *c, int stack_num) { + // R0 = ctx + sljit_emit_op1(c, SLJIT_MOV, SLJIT_R0, 0, SLJIT_S0, 0); + // R1 = *rsp + stack_num + sljit_emit_op1(c, SLJIT_MOV, SLJIT_R1, 0, SLJIT_MEM1(SLJIT_S2), stack_num); + // js_jit_JS_FreeValue_helper(R0, R1) -> js_jit_JS_FreeValue_helper(ctx, *rsp + stack_num) + sljit_emit_icall(c, SLJIT_CALL, SLJIT_ARGS2V(P, P), SLJIT_IMM, SLJIT_FUNC_ADDR(js_jit_JS_FreeValue_helper)); +} + +static void js_jit_jsvalue_copy(JSValue *dst, JSValue *src) { + *dst = *src; +} + +static void js_jit_jsvalue_copy_dup_src(JSValue *dst, JSValue *src) { + *dst = js_dup(*src); +} + +#undef SWITCH +#undef CASE +#undef DEFAULT +#undef BREAK static void js_jit(JSContext *ctx, JSFunctionBytecode *b) { uint8_t *pc; - // int opcode; + int opcode; pc = b->byte_code_buf; struct sljit_compiler *c = sljit_create_compiler(NULL); - sljit_emit_enter(c, 0, 3, 1, 3, 0); -#if 0 -#if !DIRECT_DISPATCH + sljit_emit_enter(c, 0, SLJIT_ARGS4V(P, P, P, P), 4, 3, 0); #define SWITCH(pc) switch (opcode = *pc++) #define CASE(op) case op #define DEFAULT default #define BREAK break -#else - __extension__ static const void * const dispatch_table[256] = { -#define DEF(id, size, n_pop, n_push, f) && case_OP_ ## id, -#define def(id, size, n_pop, n_push, f) -#include "quickjs-opcode.h" - [ OP_COUNT ... 255 ] = &&case_default - }; -#define SWITCH(pc) DUMP_BYTECODE_OR_DONT(pc) __extension__ ({ goto *dispatch_table[opcode = *pc++]; }); -#define CASE(op) case_ ## op -#define DEFAULT case_default -#define BREAK SWITCH(pc) -#endif - for(;;) { SWITCH(pc) { CASE(OP_push_i32): + js_jit_move_js_value(c, js_int32(get_u32(pc))); + js_jit_sp_add(c, 1); + pc += 4; + BREAK; + CASE(OP_push_bigint_i32): + js_jit_move_js_value(c, __JS_NewShortBigInt(ctx, (int)get_u32(pc))); + js_jit_sp_add(c, 1); + pc += 4; + BREAK; + CASE(OP_push_const): + js_jit_move_js_value(c, js_dup(b->cpool[get_u32(pc)])); + js_jit_sp_add(c, 1); - BREAK - - DEFAULT: - BREAK + pc += 4; + BREAK; + CASE(OP_push_minus1): + CASE(OP_push_0): + CASE(OP_push_1): + CASE(OP_push_2): + CASE(OP_push_3): + CASE(OP_push_4): + CASE(OP_push_5): + CASE(OP_push_6): + CASE(OP_push_7): + js_jit_move_js_value(c, js_int32(opcode - OP_push_0)); + js_jit_sp_add(c, 1); + BREAK; + CASE(OP_push_i8): + js_jit_move_js_value(c, js_int32(get_i8(pc))); + js_jit_sp_add(c, 1); + pc += 1; + BREAK; + CASE(OP_push_i16): + js_jit_move_js_value(c, js_int32(get_i16(pc))); + js_jit_sp_add(c, 1); + pc += 2; + BREAK; + CASE(OP_push_const8): + js_jit_move_js_value(c, js_dup(b->cpool[*pc++])); + js_jit_sp_add(c, 1); + BREAK; + // CASE(OP_fclosure8): + // *sp++ = js_closure(ctx, js_dup(b->cpool[*pc++]), var_refs, sf); + // if (unlikely(JS_IsException(sp[-1]))) + // goto exception; + // BREAK; + CASE(OP_push_empty_string): + js_jit_move_js_value(c, JS_AtomToString(ctx, JS_ATOM_empty_string)); + js_jit_sp_add(c, 1); + BREAK; + // CASE(OP_get_length): + // { + // JSValue val; + + // sf->cur_pc = pc; + // val = JS_GetProperty(ctx, sp[-1], JS_ATOM_length); + // if (unlikely(JS_IsException(val))) + // goto exception; + // JS_FreeValue(ctx, sp[-1]); + // sp[-1] = val; + // } + // BREAK; + CASE(OP_push_atom_value): + js_jit_move_js_value(c, JS_AtomToString(ctx, get_u32(pc))); + js_jit_sp_add(c, 1); + pc += 4; + BREAK; + CASE(OP_undefined): + js_jit_move_js_value(c, JS_UNDEFINED); + js_jit_sp_add(c, 1); + BREAK; + CASE(OP_null): + js_jit_move_js_value(c, JS_NULL); + js_jit_sp_add(c, 1); + BREAK; + // CASE(OP_push_this): + // /* OP_push_this is only called at the start of a function */ + // { + // JSValue val; + // if (!b->is_strict_mode) { + // uint32_t tag = JS_VALUE_GET_TAG(this_obj); + // if (likely(tag == JS_TAG_OBJECT)) + // goto normal_this; + // if (tag == JS_TAG_NULL || tag == JS_TAG_UNDEFINED) { + // val = js_dup(ctx->global_obj); + // } else { + // val = JS_ToObject(ctx, this_obj); + // if (JS_IsException(val)) + // goto exception; + // } + // } else { + // normal_this: + // val = js_dup(this_obj); + // } + // *sp++ = val; + // } + // BREAK; + CASE(OP_push_false): + js_jit_move_js_value(c, JS_FALSE); + js_jit_sp_add(c, 1); + BREAK; + CASE(OP_push_true): + js_jit_move_js_value(c, JS_TRUE); + js_jit_sp_add(c, 1); + BREAK; + CASE(OP_nop): + BREAK; + // CASE(OP_object): + // *sp++ = JS_NewObject(ctx); + // if (unlikely(JS_IsException(sp[-1]))) + // goto exception; + // BREAK; + // CASE(OP_special_object): + // { + // int arg = *pc++; + // switch(arg) { + // case OP_SPECIAL_OBJECT_ARGUMENTS: + // *sp++ = js_build_arguments(ctx, argc, argv); + // if (unlikely(JS_IsException(sp[-1]))) + // goto exception; + // break; + // case OP_SPECIAL_OBJECT_MAPPED_ARGUMENTS: + // *sp++ = js_build_mapped_arguments(ctx, argc, argv, + // sf, min_int(argc, b->arg_count)); + // if (unlikely(JS_IsException(sp[-1]))) + // goto exception; + // break; + // case OP_SPECIAL_OBJECT_THIS_FUNC: + // *sp++ = js_dup(sf->cur_func); + // break; + // case OP_SPECIAL_OBJECT_NEW_TARGET: + // *sp++ = js_dup(new_target); + // break; + // case OP_SPECIAL_OBJECT_HOME_OBJECT: + // { + // JSObject *p1; + // p1 = p->u.func.home_object; + // if (unlikely(!p1)) + // *sp++ = JS_UNDEFINED; + // else + // *sp++ = js_dup(JS_MKPTR(JS_TAG_OBJECT, p1)); + // } + // break; + // case OP_SPECIAL_OBJECT_VAR_OBJECT: + // *sp++ = JS_NewObjectProto(ctx, JS_NULL); + // if (unlikely(JS_IsException(sp[-1]))) + // goto exception; + // break; + // case OP_SPECIAL_OBJECT_IMPORT_META: + // *sp++ = js_import_meta(ctx); + // if (unlikely(JS_IsException(sp[-1]))) + // goto exception; + // break; + // default: + // abort(); + // } + // } + // BREAK; + // CASE(OP_rest): + // { + // int i, n, first = get_u16(pc); + // pc += 2; + // i = min_int(first, argc); + // n = argc - i; + // *sp++ = js_create_array(ctx, n, &argv[i]); + // if (unlikely(JS_IsException(sp[-1]))) + // goto exception; + // } + // BREAK; + + CASE(OP_drop): + // JS_FreeValue(ctx, sp[-1]); + js_jit_free_value_stack(c, -1); + // sp--; + js_jit_sp_add(c, -1); + BREAK; +#define COPY_BASE(sp_reg, dst_idx, src_idx, fn) do { \ + sljit_emit_op1(c, SLJIT_MOV, sp_reg, 0, SLJIT_MEM1(SLJIT_S2), 0); \ + sljit_emit_op1(c, SLJIT_MOV, SLJIT_R0, 0, SLJIT_MEM1(sp_reg), dst_idx); \ + sljit_emit_op1(c, SLJIT_MOV, SLJIT_R1, 0, SLJIT_MEM1(sp_reg), src_idx); \ + sljit_emit_icall(c, SLJIT_CALL, SLJIT_ARGS2V(P, P), SLJIT_IMM, SLJIT_FUNC_ADDR(fn)); \ +} while (0) + + + CASE(OP_nip): + // JS_FreeValue(ctx, sp[-2]); + js_jit_free_value_stack(c, -2); + // sp[-2] = sp[-1]; -> js_jit_jsvalue_copy(&sp[-2], &sp[-1]); + COPY_BASE(SLJIT_R3, -2, -1, js_jit_jsvalue_copy); + // sp--; + js_jit_sp_add(c, -1); + BREAK; + CASE(OP_nip1): /* a b c -> b c */ + // JS_FreeValue(ctx, sp[-3]); + js_jit_free_value_stack(c, -3); + // sp[-3] = sp[-2]; + COPY_BASE(SLJIT_R3, -3, -2, js_jit_jsvalue_copy); + // sp[-2] = sp[-1]; + COPY_BASE(SLJIT_R3, -2, -1, js_jit_jsvalue_copy); + // sp--; + js_jit_sp_add(c, -1); + BREAK; + CASE(OP_dup): + // sp[0] = js_dup(sp[-1]); -> js_jit_jsvalue_copy_dup_src(&sp[0], &sp[-1]); + COPY_BASE(SLJIT_R3, 0, -1, js_jit_jsvalue_copy_dup_src); + // sp++; + js_jit_sp_add(c, 1); + BREAK; + CASE(OP_dup2): /* a b -> a b a b */ + // sp[0] = js_dup(sp[-2]); + COPY_BASE(SLJIT_R3, 0, -2, js_jit_jsvalue_copy_dup_src); + // sp[1] = js_dup(sp[-1]); + COPY_BASE(SLJIT_R3, 1, -1, js_jit_jsvalue_copy_dup_src); + // sp += 2; + js_jit_sp_add(c, 2); + BREAK; + CASE(OP_dup3): /* a b c -> a b c a b c */ + // sp[0] = js_dup(sp[-3]); -> js_jit_jsvalue_copy_dup_src(&sp[0], &sp[-3]); + COPY_BASE(SLJIT_R3, 0, -3, js_jit_jsvalue_copy_dup_src); + + // sp[1] = js_dup(sp[-2]); -> js_jit_jsvalue_copy_dup_src(&sp[1], &sp[-2]); + COPY_BASE(SLJIT_R3, 1, -2, js_jit_jsvalue_copy_dup_src); + // sp[2] = js_dup(sp[-1]); -> js_jit_jsvalue_copy_dup_src(&sp[2], &sp[-1]); + COPY_BASE(SLJIT_R3, 2, -1, js_jit_jsvalue_copy_dup_src); + // sp += 3; + js_jit_sp_add(c, 3); + BREAK; + CASE(OP_dup1): /* a b -> a a b */ + // sp[0] = sp[-1]; + COPY_BASE(SLJIT_R3, 0, -1, js_jit_jsvalue_copy); + // sp[-1] = js_dup(sp[-2]); + COPY_BASE(SLJIT_R3, -1, -2, js_jit_jsvalue_copy_dup_src); + // sp++; + js_jit_sp_add(c, 1); + BREAK; + CASE(OP_insert2): /* obj a -> a obj a (dup_x1) */ + // sp[0] = sp[-1]; + COPY_BASE(SLJIT_R3, 0, -1, js_jit_jsvalue_copy); + // sp[-1] = sp[-2]; + COPY_BASE(SLJIT_R3, -1, -2, js_jit_jsvalue_copy); + // sp[-2] = js_dup(sp[0]); + COPY_BASE(SLJIT_R3, -2, 0, js_jit_jsvalue_copy_dup_src); + // sp++ + js_jit_sp_add(c, 1); + BREAK; + CASE(OP_insert3): /* obj prop a -> a obj prop a (dup_x2) */ + // sp[0] = sp[-1]; + COPY_BASE(SLJIT_R3, 0, -1, js_jit_jsvalue_copy); + // sp[-1] = sp[-2]; + COPY_BASE(SLJIT_R3, -1, -2, js_jit_jsvalue_copy); + // sp[-2] = sp[-3]; + COPY_BASE(SLJIT_R3, -2, -3, js_jit_jsvalue_copy); + // sp[-3] = js_dup(sp[0]); + COPY_BASE(SLJIT_R3, -3, 0, js_jit_jsvalue_copy_dup_src); + // sp++; + js_jit_sp_add(c, 1); + BREAK; + CASE(OP_insert4): /* this obj prop a -> a this obj prop a */ + // sp[0] = sp[-1]; + COPY_BASE(SLJIT_R3, 0, -1, js_jit_jsvalue_copy); + // sp[-1] = sp[-2]; + COPY_BASE(SLJIT_R3, -1, -2, js_jit_jsvalue_copy); + // sp[-2] = sp[-3]; + COPY_BASE(SLJIT_R3, -2, -3, js_jit_jsvalue_copy); + // sp[-3] = sp[-4]; + COPY_BASE(SLJIT_R3, -3, -4, js_jit_jsvalue_copy); + // sp[-4] = js_dup(sp[0]); + COPY_BASE(SLJIT_R3, -4, 0, js_jit_jsvalue_copy_dup_src); + // sp++; + js_jit_sp_add(c, 1); + BREAK; + // CASE(OP_perm3): /* obj a b -> a obj b (213) */ + // { + // JSValue tmp; + // tmp = sp[-2]; + // sp[-2] = sp[-3]; + // sp[-3] = tmp; + // } + // BREAK; + // CASE(OP_rot3l): /* x a b -> a b x (231) */ + // { + // JSValue tmp; + // tmp = sp[-3]; + // sp[-3] = sp[-2]; + // sp[-2] = sp[-1]; + // sp[-1] = tmp; + // } + // BREAK; + // CASE(OP_rot4l): /* x a b c -> a b c x */ + // { + // JSValue tmp; + // tmp = sp[-4]; + // sp[-4] = sp[-3]; + // sp[-3] = sp[-2]; + // sp[-2] = sp[-1]; + // sp[-1] = tmp; + // } + // BREAK; + // CASE(OP_rot5l): /* x a b c d -> a b c d x */ + // { + // JSValue tmp; + // tmp = sp[-5]; + // sp[-5] = sp[-4]; + // sp[-4] = sp[-3]; + // sp[-3] = sp[-2]; + // sp[-2] = sp[-1]; + // sp[-1] = tmp; + // } + // BREAK; + // CASE(OP_rot3r): /* a b x -> x a b (312) */ + // { + // JSValue tmp; + // tmp = sp[-1]; + // sp[-1] = sp[-2]; + // sp[-2] = sp[-3]; + // sp[-3] = tmp; + // } + // BREAK; + // CASE(OP_perm4): /* obj prop a b -> a obj prop b */ + // { + // JSValue tmp; + // tmp = sp[-2]; + // sp[-2] = sp[-3]; + // sp[-3] = sp[-4]; + // sp[-4] = tmp; + // } + // BREAK; + // CASE(OP_perm5): /* this obj prop a b -> a this obj prop b */ + // { + // JSValue tmp; + // tmp = sp[-2]; + // sp[-2] = sp[-3]; + // sp[-3] = sp[-4]; + // sp[-4] = sp[-5]; + // sp[-5] = tmp; + // } + // BREAK; + // CASE(OP_swap): /* a b -> b a */ + // { + // JSValue tmp; + // tmp = sp[-2]; + // sp[-2] = sp[-1]; + // sp[-1] = tmp; + // } + // BREAK; + // CASE(OP_swap2): /* a b c d -> c d a b */ + // { + // JSValue tmp1, tmp2; + // tmp1 = sp[-4]; + // tmp2 = sp[-3]; + // sp[-4] = sp[-2]; + // sp[-3] = sp[-1]; + // sp[-2] = tmp1; + // sp[-1] = tmp2; + // } + // BREAK; + CASE(OP_invalid): + DEFAULT: + goto end; + BREAK; } } -#endif +end: + sljit_emit_return_void(c); b->jitcode = sljit_generate_code(c, 0, NULL); #if QJS_SLJIT_VERBOSE + printf("-- FUNCTION DUMP START --\n"); size_t len = sljit_get_generated_code_size(c); // The runtime address (instruction pointer) was chosen arbitrarily here in order to better // visualize relative addressing. In your actual program, set this to e.g. the memory address @@ -34016,6 +34424,7 @@ static void js_jit(JSContext *ctx, JSFunctionBytecode *b) { offset += instruction.info.length; runtime_address += instruction.info.length; } + printf("-- FUNCTION DUMP END --\n"); #endif sljit_free_compiler(c); }