diff --git a/.gdbinit b/.gdbinit
index 457dc7b31..a9f898fb6 100644
--- a/.gdbinit
+++ b/.gdbinit
@@ -179,6 +179,25 @@ define ds
dumpsxp $arg0 1
end
+define refetch
+ shell /usr/bin/git fetch mine && /usr/bin/git reset --hard mine/$(/usr/bin/git rev-parse --abbrev-ref HEAD) && /usr/bin/ninja
+ python gdb.execute("file " + gdb.current_progspace().filename)
+ directory
+ run
+end
+
+define rerun
+ python gdb.execute("file " + gdb.current_progspace().filename)
+ directory
+ run
+end
+
+define ninja
+ shell ninja
+ python gdb.execute("file " + gdb.current_progspace().filename)
+ directory
+end
+
# source .pirpp.py
diff --git a/.gitignore b/.gitignore
index 01d6a2c5b..0f3a213a9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,4 +36,7 @@ benchmarks/
*.DS_Store
external/*
!external/custom-r
-.history
\ No newline at end of file
+!external/xxHash
+.history
+.cache
+compile_commands.json
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9fdd2c9fe..fdb13d26f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -189,7 +189,7 @@ tests_fullverify:
- make -j6
- bin/tests
-# Test particular features, like deoptimization and serialization
+# Test particular features, like deoptimization
test_features_1:
image: registry.gitlab.com/rirvm/rir_mirror:$CI_COMMIT_SHA
variables:
@@ -265,6 +265,73 @@ test_features_3:
when: on_failure
expire_in: 1 week
+# Test serialization (no LLVM bitcode)
+test_serialize_chaos:
+ image: registry.gitlab.com/rirvm/rir_mirror:$CI_COMMIT_SHA
+ variables:
+ GIT_STRATEGY: none
+ PIR_LLVM_OPT_LEVEL: 0
+ stage: Run tests
+ needs:
+ - rir_container
+ except:
+ - schedules
+ script:
+ - /opt/rir/container/install-test-deps.sh
+ - cd /opt/rir/build/release
+ - RIR_SERIALIZE_CHAOS=5 FAST_TESTS=1 bin/tests
+ - PIR_WARMUP=2 RIR_SERIALIZE_CHAOS=50 bin/gnur-make-tests check || $SAVE_LOGS
+ - ../../tools/check-gnur-make-tests-error
+ - RIR_SERIALIZE_CHAOS=10 bin/tests
+ artifacts:
+ paths:
+ - logs
+ when: on_failure
+ expire_in: 1 week
+
+# Test LLVM bitcode serialization
+test_serialize_llvm:
+ image: registry.gitlab.com/rirvm/rir_mirror:$CI_COMMIT_SHA
+ variables:
+ GIT_STRATEGY: none
+ PIR_LLVM_OPT_LEVEL: 0
+ stage: Run tests
+ needs:
+ - rir_container
+ except:
+ - schedules
+ script:
+ - /opt/rir/container/install-test-deps.sh
+ - cd /opt/rir/build/release
+ - PIR_DEBUG_SERIALIZE_LLVM=1 bin/gnur-make-tests check || $SAVE_LOGS
+ - ../../tools/check-gnur-make-tests-error
+ artifacts:
+ paths:
+ - logs
+ when: on_failure
+ expire_in: 1 week
+
+# Test regular and LLVM bitcode serialization
+test_serialize_both:
+ image: registry.gitlab.com/rirvm/rir_mirror:$CI_COMMIT_SHA
+ variables:
+ GIT_STRATEGY: none
+ PIR_LLVM_OPT_LEVEL: 0
+ stage: Run tests
+ needs:
+ - rir_container
+ except:
+ - schedules
+ script:
+ - /opt/rir/container/install-test-deps.sh
+ - cd /opt/rir/build/release
+ - PIR_DEBUG_SERIALIZE_LLVM=1 FAST_TESTS=1 RIR_SERIALIZE_CHAOS=5 bin/tests
+ - PIR_DEBUG_SERIALIZE_LLVM=1 RIR_SERIALIZE_CHAOS=10 bin/tests
+ artifacts:
+ paths:
+ - logs
+ when: on_failure
+ expire_in: 1 week
# Run ubsan and gc torture
test_gctorture_1:
@@ -334,6 +401,7 @@ test_big_inline:
test_sanitize:
image: registry.gitlab.com/rirvm/rir_mirror:$CI_COMMIT_SHA
variables:
+ CLANG_DIR: /opt/clang+llvm-16.0.0-x86_64-linux-gnu-ubuntu-18.04
GIT_STRATEGY: none
stage: Run tests
needs:
@@ -341,18 +409,65 @@ test_sanitize:
except:
- schedules
script:
+ # TODO: Store clang-16 and zlib on prl
+ # Install clang-16
+ - apt install libtinfo5
+ - wget https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.0/clang+llvm-16.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz -O $CLANG_DIR.tar.xz
+ - tar -xf $CLANG_DIR.tar.xz -C /opt
+ # Manually add zlib (required when building on clang-16)
+ - wget https://github.com/madler/zlib/releases/download/v1.3/zlib-1.3.tar.xz -O /opt/rir/external/zlib-1.3.tar.xz
+ - tar -xf /opt/rir/external/zlib-1.3.tar.xz -C /opt/rir/external
+ - cd /opt/rir/external/zlib-1.3
+ - ./configure --prefix=/opt/rir/external/zlib
+ - make -j6
+ - make install
+ # Rest
- curl 10.200.14.25:8080/clang+llvm-12.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz > /opt/rir/external/clang+llvm-12.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz
- mkdir /opt/rir/build/sanitize
- cd /opt/rir/build/sanitize
- /opt/rir/tools/fetch-llvm.sh
- - CC=$(ls ../../external/clang*/bin/clang) CXX=$(ls ../../external/clang*/bin/clang) cmake -DCMAKE_BUILD_TYPE=sanitize ../..
+ - cmake -DCMAKE_BUILD_TYPE=sanitize
+ -DCMAKE_C_COMPILER=$CLANG_DIR/bin/clang
+ -DCMAKE_CXX_COMPILER=$CLANG_DIR/bin/clang++
+ -DZLIB_LIBRARY=/opt/rir/external/zlib/lib/libz.so
+ -DZLIB_INCLUDE_DIR=/opt/rir/external/zlib/include
+ ../..
- make -j6
# R_LD_PRELOAD is a feature of the test-runner. To repro this without the testrunner use LD_PRELOAD instead.
# intercept_tls_get_addr=0 helps with leak sanitizer crashes
- - ASAN_OPTIONS="intercept_tls_get_addr=0" LSAN_OPTIONS="symbolize=1" ASAN_SYMBOLIZER_PATH=$(ls /opt/rir/external/clang*/bin/llvm-symbolizer) R_LD_PRELOAD=$(ls /opt/rir/external/clang*/lib/clang/12.0.0/lib/linux/libclang_rt.asan-x86_64.so) bin/tests
+ - ASAN_OPTIONS="intercept_tls_get_addr=0"
+ LSAN_OPTIONS="symbolize=1"
+ ASAN_SYMBOLIZER_PATH=$CLANG_DIR/bin/llvm-symbolizer
+ R_LD_PRELOAD=$CLANG_DIR/lib/clang/16/lib/x86_64-unknown-linux-gnu/libclang_rt.asan.so
+ bin/tests
# sometimes leak sanitizer segfaults
retry: 2
+# Test the compiler server and client (on localhost)
+test_compiler_server_client:
+ image: registry.gitlab.com/rirvm/rir_mirror:$CI_COMMIT_SHA
+ variables:
+ GIT_STRATEGY: none
+ PIR_LLVM_OPT_LEVEL: 0
+ stage: Run tests
+ needs:
+ - rir_container
+ except:
+ - schedules
+ script:
+ - mkdir $CI_PROJECT_DIR/results && mkdir $CI_PROJECT_DIR/results/debug && mkdir $CI_PROJECT_DIR/results/release
+ - curl 10.200.14.25:8080/clang+llvm-12.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz > /opt/rir/external/clang+llvm-12.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz
+ - /opt/rir/tools/fetch-llvm.sh
+ - mkdir /opt/rir/build/debug && cd /opt/rir/build/debug && cmake -DCMAKE_BUILD_TYPE=Debug ../.. && make -j6
+ - PIR_CLIENT_SKIP_DISCREPANCY_CHECK=1 ./bin/test-compiler-client-and-server; cp /tmp/test-compiler-server-actual.out /tmp/test-compiler-client-actual.out $CI_PROJECT_DIR/results/debug/
+ - cd /opt/rir/build/release
+ - PIR_CLIENT_SKIP_DISCREPANCY_CHECK=1 ./bin/test-compiler-client-and-server; cp /tmp/test-compiler-server-actual.out /tmp/test-compiler-client-actual.out $CI_PROJECT_DIR/results/release/
+ artifacts:
+ paths:
+ - results
+ when: always
+ expire_in: 1 week
+
# Test the benchmarks container before deploying
test_benchmarks:
image: registry.gitlab.com/rirvm/rir_mirror/benchmark:$CI_COMMIT_SHA
diff --git a/.gitmodules b/.gitmodules
index f21e7abfb..b18d454ab 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -2,3 +2,6 @@
path = external/custom-r
url = https://github.com/reactorlabs/gnur.git
ignore = untracked
+[submodule "external/xxHash"]
+ path = external/xxHash
+ url = https://github.com/Cyan4973/xxHash.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f5436cefe..ead4d5ffb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,6 +8,8 @@ set(R_LIBRARY_TREE ${CMAKE_SOURCE_DIR}/packages)
set(R_ROOT_DIR ${R_HOME})
set(R_INCLUDE_DIR ${R_HOME}/include)
set(LLVM_DIR ${CMAKE_SOURCE_DIR}/external/llvm-12)
+set(ZEROMQ_DIR ${CMAKE_SOURCE_DIR}/external/zeromq)
+set(XXHASH_DIR ${CMAKE_SOURCE_DIR}/external/xxHash)
set(R_COMMAND ${R_HOME}/bin/R)
@@ -22,29 +24,28 @@ endif ()
include_directories(SYSTEM ${LLVM_INCLUDE_DIRS})
add_definitions(${LLVM_DEFINITIONS})
-# use GCC 9 on macOS. Otherwise we use clang
-if(APPLE)
- option(MACOS_USE_GCC_9 "Use GCC 9 on macOS." FALSE)
-endif(APPLE)
+include_directories(${ZEROMQ_DIR}/include)
+# Won't work on Linux if we try to link statically:
+# /usr/bin/ld: ../external/zeromq/lib/libzmq.a(libzmq_la-zmq.o): relocation R_X86_64_PC32 against symbol
+# `_ZSt7nothrow@@GLIBCXX_3.4' can not be used when making a shared object; recompile with -fPIC
+if (${APPLE})
+ link_libraries(${ZEROMQ_DIR}/lib/libzmq.dylib)
+else ()
+ link_libraries(${ZEROMQ_DIR}/lib/libzmq.so)
+endif ()
-if(${MACOS_USE_GCC_9})
- set(CMAKE_C_COMPILER /usr/local/bin/gcc-9 CACHE PATH "" FORCE)
- set(CMAKE_CXX_COMPILER /usr/local/bin/g++-9 CACHE PATH "" FORCE)
-endif()
+set(XXHASH_BUILD_XXHSUM OFF)
+set(BUILD_SHARED_LIBS ON)
+add_subdirectory(${XXHASH_DIR}/cmake_unofficial/ ${XXHASH_DIR}/build/ EXCLUDE_FROM_ALL)
add_definitions(-g)
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -Werror -DSWITCH_TO_NAMED=1")
set(CMAKE_CXX_FLAGS_RELEASENOASSERT "${CMAKE_CXX_FLAGS_RELEASE} -DNDEBUG")
set(CMAKE_CXX_FLAGS_FULLVERIFIER "${CMAKE_CXX_FLAGS_RELEASE} -DFULLVERIFIER")
set(CMAKE_CXX_FLAGS_RELEASESLOWASSERT "${CMAKE_CXX_FLAGS_RELEASE} -DENABLE_SLOWASSERT")
-set(CMAKE_CXX_FLAGS_DEBUG "-O0 -DSWITCH_TO_NAMED=1 -DENABLE_SLOWASSERT")
+set(CMAKE_CXX_FLAGS_DEBUG "-O0 -DSWITCH_TO_NAMED=1 -DENABLE_SLOWASSERT -DDEBUG_DISASSEMBLY")
set(CMAKE_CXX_FLAGS_DEBUGOPT "-Og -DSWITCH_TO_NAMED=1 -DENABLE_SLOWASSERT")
-# with macOS GCC 9 we need to explicitly use libc++, since llvm does. See https://libcxx.llvm.org/docs/UsingLibcxx.html#using-libc-with-gcc
-if(${MACOS_USE_GCC_9})
- set(CMAKE_CXX_FLAGS_LIBCXX "-nostdinc++ -nodefaultlibs -lc++ -lc++abi -lm -lc -lgcc_s.1 -lgcc")
-else()
- set(CMAKE_CXX_FLAGS_LIBCXX "")
-endif()
+set(CMAKE_CXX_FLAGS_LIBCXX "")
set(CMAKE_CXX_FLAGS "${LLVM_CXX_FLAGS} ${CMAKE_CXX_FLAGS_LIBCXX} -Wall -Wuninitialized -Wundef -Winit-self -Wcast-align -Woverloaded-virtual -Wmissing-include-dirs -Wstrict-overflow=3 -std=c++14 -fno-rtti -fno-exceptions -Wimplicit-fallthrough -Wno-deprecated-declarations")
set(CMAKE_C_FLAGS_RELEASE "-O2 -DSWITCH_TO_NAMED=1")
set(CMAKE_C_FLAGS_RELEASENOASSERT "${CMAKE_C_FLAGS_RELEASE} -DNDEBUG")
@@ -54,8 +55,16 @@ set(CMAKE_C_FLAGS_DEBUG "-O0 -DSWITCH_TO_NAMED=1 -DENABLE_SLOWASSERT")
set(CMAKE_C_FLAGS_DEBUGOPT "-Og -DSWITCH_TO_NAMED=1 -DENABLE_SLOWASSERT")
set(CMAKE_C_FLAGS "-std=gnu99")
-set(CMAKE_CXX_FLAGS_SANITIZE "${CMAKE_CXX_FLAGS_RELEASE} -g2 -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=address -fsanitize=leak -fno-sanitize=alignment -shared-libasan -fvisibility=default")
-set(CMAKE_C_FLAGS_SANITIZE "${CMAKE_C_FLAGS_RELEASE} -g2 -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=address -fsanitize=leak -fno-sanitize=alignment -shared-libasan -fvisibility=default")
+if (${APPLE})
+ set(SANITIZE_FLAGS "-g2 -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=address -fno-sanitize=alignment -shared-libasan -fvisibility=default")
+else()
+ set(SANITIZE_FLAGS "-g2 -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=address -fsanitize=leak -fno-sanitize=alignment -shared-libasan -fvisibility=default")
+endif()
+set(CMAKE_CXX_FLAGS_SANITIZE "${CMAKE_CXX_FLAGS_RELEASE} ${SANITIZE_FLAGS}")
+set(CMAKE_C_FLAGS_SANITIZE "${CMAKE_C_FLAGS_RELEASE} ${SANITIZE_FLAGS}")
+set(CMAKE_CXX_FLAGS_DEBUG_SANITIZE "${CMAKE_CXX_FLAGS_DEBUG} ${SANITIZE_FLAGS}")
+set(CMAKE_C_FLAGS_DEBUG_SANITIZE "${CMAKE_C_FLAGS_DEBUG} ${SANITIZE_FLAGS}")
+
MARK_AS_ADVANCED(
CMAKE_CXX_FLAGS_SANITIZE
@@ -96,12 +105,15 @@ endif()
# Create proxy scripts for the scripts in /tools
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/.bin_create")
-file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/.bin_create/tests" "#!/bin/sh\nRIR_BUILD=\"${CMAKE_CURRENT_BINARY_DIR}\" ${CMAKE_SOURCE_DIR}/tools/tests \"$@\"")
-file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/.bin_create/R" "#!/bin/sh\nRIR_BUILD=\"${CMAKE_CURRENT_BINARY_DIR}\" ${CMAKE_SOURCE_DIR}/tools/R \"$@\"")
-file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/.bin_create/Rscript" "#!/bin/sh\nRIR_BUILD=\"${CMAKE_CURRENT_BINARY_DIR}\" ${CMAKE_SOURCE_DIR}/tools/Rscript \"$@\"")
-file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/.bin_create/Rgnu" "#!/bin/sh\n${CMAKE_SOURCE_DIR}/external/custom-r/bin/R \"$@\"")
-file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/.bin_create/gnur-make" "#!/bin/sh\nRIR_BUILD=\"${CMAKE_CURRENT_BINARY_DIR}\" ${CMAKE_SOURCE_DIR}/tools/gnur-make \"$@\"")
-file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/.bin_create/gnur-make-tests" "#!/bin/sh\nRIR_BUILD=\"${CMAKE_CURRENT_BINARY_DIR}\" ${CMAKE_SOURCE_DIR}/tools/gnur-make-tests \"$@\"")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/.bin_create/tests" "#!/bin/sh\nRIR_BUILD=\"${CMAKE_CURRENT_BINARY_DIR}\" ${CMAKE_SOURCE_DIR}/tools/tests \"$@\"")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/.bin_create/test-compiler-client-and-server" "#!/bin/sh\nRIR_BUILD=\"${CMAKE_CURRENT_BINARY_DIR}\" ${CMAKE_SOURCE_DIR}/tools/test-compiler-client-and-server \"$@\"")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/.bin_create/test-compiler-client-only" "#!/bin/sh\nRIR_BUILD=\"${CMAKE_CURRENT_BINARY_DIR}\" ${CMAKE_SOURCE_DIR}/tools/test-compiler-client-only \"$@\"")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/.bin_create/test-compiler-server-only" "#!/bin/sh\nRIR_BUILD=\"${CMAKE_CURRENT_BINARY_DIR}\" ${CMAKE_SOURCE_DIR}/tools/test-compiler-server-only \"$@\"")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/.bin_create/R" "#!/bin/sh\nRIR_BUILD=\"${CMAKE_CURRENT_BINARY_DIR}\" ${CMAKE_SOURCE_DIR}/tools/R \"$@\"")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/.bin_create/Rscript" "#!/bin/sh\nRIR_BUILD=\"${CMAKE_CURRENT_BINARY_DIR}\" ${CMAKE_SOURCE_DIR}/tools/Rscript \"$@\"")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/.bin_create/Rgnu" "#!/bin/sh\n${CMAKE_SOURCE_DIR}/external/custom-r/bin/R \"$@\"")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/.bin_create/gnur-make" "#!/bin/sh\nRIR_BUILD=\"${CMAKE_CURRENT_BINARY_DIR}\" ${CMAKE_SOURCE_DIR}/tools/gnur-make \"$@\"")
+file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/.bin_create/gnur-make-tests" "#!/bin/sh\nRIR_BUILD=\"${CMAKE_CURRENT_BINARY_DIR}\" ${CMAKE_SOURCE_DIR}/tools/gnur-make-tests \"$@\"")
file(GLOB BIN_IN "${CMAKE_CURRENT_BINARY_DIR}/.bin_create/*")
file(INSTALL ${BIN_IN} DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/bin" FILE_PERMISSIONS OWNER_EXECUTE OWNER_READ GROUP_READ GROUP_EXECUTE)
@@ -118,36 +130,20 @@ endif(${NO_LOCAL_CONFIG})
include_directories(${R_INCLUDE_DIR})
include_directories(${CMAKE_SOURCE_DIR}/rir/src)
-# again we need to explicitly use libc++
-if(${MACOS_USE_GCC_9})
- include_directories(/Library/Developer/CommandLineTools/usr/include/c++/v1)
-endif(${MACOS_USE_GCC_9})
message(STATUS "Using R from ${R_HOME}")
add_custom_target(setup-build-dir
COMMAND ${CMAKE_SOURCE_DIR}/tools/setup-build-dir ${CMAKE_SOURCE_DIR} ${R_HOME}
)
-if(${MACOS_USE_GCC_9})
- add_custom_target(dependencies
- COMMAND ${CMAKE_SOURCE_DIR}/tools/build-gnur.sh --macos_gcc9
- COMMAND ${CMAKE_SOURCE_DIR}/tools/fetch-llvm.sh --macos_gcc9
- )
-else()
- add_custom_target(dependencies
- COMMAND ${CMAKE_SOURCE_DIR}/tools/build-gnur.sh
- COMMAND ${CMAKE_SOURCE_DIR}/tools/fetch-llvm.sh
- )
-endif()
-
-add_custom_target(default-gnur
- DEPENDS dependencies
- COMMAND ${CMAKE_SOURCE_DIR}/tools/build-gnur.sh custom-r
+add_custom_target(dependencies
+ COMMAND ${CMAKE_SOURCE_DIR}/tools/build-gnur.sh
+ COMMAND ${CMAKE_SOURCE_DIR}/tools/build-zeromq.sh
+ COMMAND ${CMAKE_SOURCE_DIR}/tools/fetch-llvm.sh
)
add_custom_target(setup
DEPENDS dependencies
- DEPENDS default-gnur
)
add_custom_target(tests
@@ -175,10 +171,17 @@ if (DEFINED LLVM_PACKAGE_VERSION)
target_link_libraries(${PROJECT_NAME} ${LLVM_LIBS})
endif(DEFINED LLVM_PACKAGE_VERSION)
+target_link_libraries(${PROJECT_NAME} xxHash::xxhash)
+
if(APPLE)
set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-L${R_HOME}/lib")
target_link_libraries(${PROJECT_NAME} R)
# to resolve build error from
# https://www.gnu.org/software/gettext/FAQ.html#integrating_undefined
- target_link_libraries(${PROJECT_NAME} -lintl)
+ # target_link_libraries(${PROJECT_NAME} -lintl)
+ include_directories(${LLVM_DIR}/include)
+ target_link_directories(${PROJECT_NAME} INTERFACE ${LLVM_DIR}/lib)
+ # Note: May need to update the version here
+ include_directories(/opt/homebrew/Cellar/gettext/0.21.1/include)
+ target_link_libraries(${PROJECT_NAME} /opt/homebrew/Cellar/gettext/0.21.1/lib/libintl.dylib)
endif(APPLE)
diff --git a/Dockerfile b/Dockerfile
index 6131c1c48..5fe3dcfe9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -5,13 +5,16 @@ ENV LANG en_US.UTF-8
RUN echo $CI_COMMIT_SHA > /opt/rir_version && \
apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -qq && \
- DEBIAN_FRONTEND=noninteractive apt-get install -y -qq curl git gcc gfortran g++ libreadline-dev libx11-dev libxt-dev zlib1g-dev libbz2-dev liblzma-dev libpcre3-dev libcurl4-openssl-dev libcairo2-dev make libreadline8 libncurses-dev xz-utils cmake tcl-dev tk-dev locales rsync && \
+ DEBIAN_FRONTEND=noninteractive apt-get install -y -qq curl git gcc gfortran g++ libreadline-dev libx11-dev libxt-dev zlib1g-dev libbz2-dev liblzma-dev libpcre3-dev libcurl4-openssl-dev libcairo2-dev make libreadline8 libncurses-dev xz-utils cmake tcl-dev tk-dev locales rsync wget && \
locale-gen en_US.UTF-8 && update-locale LANG=en_US.UTF-8 && \
cd /opt/rir && \
tools/build-gnur.sh && \
rm -rf external/custom-r/cache_recommended.tar .git && \
find external -type f -name '*.o' -exec rm -f {} \; && \
apt-get clean
+RUN cd /opt/rir && \
+ USE_NINJA=0 tools/build-zeromq.sh && \
+ rm -rf external/zeromq-* external/cppzmq-*
RUN mkdir -p /opt/rir/build/release && \
cd /opt/rir && \
(curl 10.200.14.25:8080/clang+llvm-12.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz > external/clang+llvm-12.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz || true) && \
diff --git a/documentation/compiler-server.md b/documentation/compiler-server.md
new file mode 100644
index 000000000..017a97c96
--- /dev/null
+++ b/documentation/compiler-server.md
@@ -0,0 +1,72 @@
+# Compiler Server and Client
+
+## How to use
+
+### Locally
+
+Start the compiler server
+
+ PIR_SERVER_ADDR=tcp://*:5555 ./bin/R
+
+**In a separate terminal window**, start the client
+
+ PIR_CLIENT_ADDR=tcp://localhost:5555 ./bin/R
+
+You can change the port if you'd like. You can also start multiple clients for one server. ~~And you can have one client connect to multiple servers separated by commas, e.g.:~~
+
+ PIR_CLIENT_ADDR=tcp://localhost:1234,tcp://localhost:5678 ./bin/R
+
+(multiple servers are currently only in statis and won't work, because retrieval for multiple servers isn't implemented)
+
+We use [ZeroMQ](https://zeromq.org) for communication. See the ZeroMQ docs for all supported address types and how to connect to a remote server.
+
+### Full configuration options
+
+ PIR_CLIENT_ADDR=
+
(on client) address of compiler server to connect to
+ (on client) comma-separated addresses of compiler servers to connect to
+ PIR_CLIENT_TIMEOUT=
+ (on client) how long to wait for a reply from the server before timing out. Default is 10000 (10 seconds)
+ PIR_CLIENT_COMPILE_SIZE_TO_HASH_ONLY=
+ (on client) the server memoizes compile requests from all clients. If the client is going to send a request that is larger than this size, it will only hash the request and send the hash first. Then if the server has already compiled the request, it will reply with the compiled code, and if not, the server will send a response causing the client to send the full request
+ PIR_CLIENT_DRY_RUN=
+ <0|1> (on client) whether to actually use the server's code, or compile locally and just use it for comparison. Default is false (actually use the code)
+ PIR_CLIENT_SKIP_DISCREPANCY_CHECK=
+ <0|1> (on client) whether to skip checking for discrepancies between local and remote compilation. Default is to not skip.
+ PIR_SERVER_ADDR=
+ (on server) address to listen on
+
+#### Logging
+
+ PIR_TRACE_COMPILER_PEER=
+ 1 log the contents of every request sent to and received by the compiler client or server
+ PIR_LOG_COMPILER_PEER=
+ 1 log every message sent from/to the compiler peer. Superseded by PIR_TRACE_COMPILER_PEER
+ PIR_WARN_COMPILER_PEER=
+ 1 warn when the compiler peer connection times out or closes. Superseded by PIR_LOG_COMPILER_PEER
+
+These options are also in [./debugging.md](./debugging.md). They can be applied to client or server, and will log on whatever peer they're applied but not affect connected peers.
+
+It's recommended to set `PIR_WARN_COMPILER_PEER` to see any issues. Try setting `PIR_LOG_COMPILER_PEER` on the server to see the requests and responses being made.
+
+## What is a compiler server?
+
+A separate process which JIT-compiles code while the local process interprets your program. It can be on the same or different machine. This reduces the overhead of compiling.
+
+## How it works
+
+Both the compiler client and server are Ř processes. The server starts with `PIR_SERVER_ADDR=`, which will cause the server to wait for compile requests instead of running a REPL like normal. The client starts with `PIR_CLIENT_ADDR=`, which will cause it to connect to `` and send future compile requests there.
+
+Whenever the compiler client attempts to compile a function (by default, this happens after running the function a few times), it sends a request to the compiler server containing the function's code along with context and speculation info such as runtime types. The compiler server processes the request and replies with the compiled (LLVM) code. The client inserts this into the function's **dispatch table**, and future calls trigger the compiled code. If there is a deoptimization or the function is called with a different context, the compiler client may request the server to compile the same function again, with new context and/or speculation info (there's no point in re-compiling the function with the exact same info).
+
+The compiler server also memoizes requests by hashing the request data including R bytecode and feedback, so if it's asked to recompile the same closure again, it will return the already-compiled version.
+
+### SEXP intern pool
+
+TODO: improve writing
+
+Separate from requests, the compiler server interns SEXPs and will send SEXPs to the client with connected SEXPs as hashes. If the client doesn't have the SEXP locally, it can send a `Retrieve` request to the server to get it from the intern pool, but if it does, it can skip this request. This prevents transmitting redundant SEXPs and more importantly, creating separate SEXPs on the client; this is not just bad for performance, but can be a semantic issue.
+
+The SEXPs are interned according to a hash computed from their immutable semantic data, using [xxHash](https://xxhash.com/). Data which is mutable but doesn't affect semantics, like feedback, isn't part of the hash. Environments are also not part of the hash since they are mutable and defined behavior shouldn't rely on their changes throughout the program's execution.
+
+The client will also intern SEXPs it retrieves from the server. However, it explicitly *doesn't* send connected SEXPs as hashes, instead sending the full SEXP even if redundant, because the client's intern pool is temporary. When the server interns SEXPs it also preserves them for future clients, and the server will presumably have more memory so it can handle this. Because the server has an intern pool, even though it will receive and deserialize redundant SEXPs, it won't actually store the duplicates, they'll simply be discarded.
\ No newline at end of file
diff --git a/documentation/debugging.md b/documentation/debugging.md
index c44463199..bf8306e31 100644
--- a/documentation/debugging.md
+++ b/documentation/debugging.md
@@ -45,6 +45,44 @@ graphical representation of the code choose the GraphViz debug style.
GraphViz print pir in GraphViz, displaying all instructions within BBs
GraphVizBB print pir in GraphViz, displaying only BB names and connections
+ RIR_DEBUG_STYLE=
+ Standard print basic information in rir objects in human-readable format
+ Detailed print very detailed information in rir objects, useful for debugging or explaining unexpected semantic differences
+ PrettyGraph print in HTML which can be loaded with `tools/rirPrettyGraph` in the same location to display an interactive graph
+
+ PIR_GRAPH_PRINT_RIR_OBJECTS=
+ <0|1|path> if set, folder to print pretty graphs of RIR objects which get compiled or interned. If set to 1, prints HTML to stdout. If set to 0 or unset (default), won't print.
+
+ PIR_GRAPH_PRINT_RIR_OBJECTS_FREQUENCY=
+ n print pretty graphs of every nth RIR object which gets compiled or interned. Defaults to 10. Otherwise we print a lot more RIR objects than are necessary.
+
+ PIR_TRACE_SERIALIZATION=
+ 1 log every serialized or deserialized piece of data
+
+ PIR_TRACE_SERIALIZATION_MAX_RAW_PRINT_LENGTH=
+ unsigned max length we will print serialized raw data in the trace. Ignored unless PIR_TRACE_SERIALIZATION is set
+
+ PIR_TRACE_SERIALIZATION_EXCLUDE=
+ regex exclude logging serialized data from matching flags. Ignored unless PIR_TRACE_SERIALIZATION is set
+
+ PIR_TRACE_SERIALIZATION_MIN_SIZE=
+ size_t minimum length of individual pieces of data which will be logged. Ignored unless PIR_TRACE_SERIALIZATION is set
+
+ PIR_LOG_INTERNING=
+ 1 log every new intern, reused intern, unintern, and other intern related events.
+
+ PIR_WARN_INTERNING=
+ 1 warn when an interned object's UUID changes and other inconsistencies. Superseded by PIR_LOG_INTERNING
+
+ PIR_TRACE_COMPILER_PEER=
+ 1 log the contents of every request sent to and received by the compiler client or server
+
+ PIR_LOG_COMPILER_PEER=
+ 1 log every message sent from/to the compiler peer. Superseded by PIR_TRACE_COMPILER_PEER
+
+ PIR_WARN_COMPILER_PEER=
+ 1 warn when the compiler peer connection times out or closes. Superseded by PIR_LOG_COMPILER_PEER
+
The following flags can be useful for profiling and finding out which passes take how much time to
complete.
@@ -57,6 +95,18 @@ complete.
PIR_MEASURE_COMPILER_BACKEND=
1 print overall time spend in different phases in the backend
+ PIR_MEASURE_COMPILED_CLOSURES=
+ 1 print # of compiled closures and time it spends to compile each one
+
+ PIR_MEASURE_SERIALIZATION=
+ 1 print detailed report on time spent in serialization
+
+ PIR_MEASURE_INTERNING=
+ 1 print detailed report on time spent in interning
+
+ PIR_MEASURE_CLIENT_SERVER=
+ 1 print time spent in client server communication (sending and receiving requests + processing)
+
#### Controlling compilation
PIR_ENABLE=
@@ -81,6 +131,9 @@ complete.
PIR_DEBUG_DEOPTS=
1 show failing assumption when a deopt happens
+ R_DISABLE_GC=
+ 1 disable the garbage collector
+
#### Optimization heuristics
For more flags see compiler/parameter.h.
@@ -104,6 +157,12 @@ For more flags see compiler/parameter.h.
n serialize and deserialize the dispatch table on every `n`th
RIR call. WARNING: This sometimes prevents optimization
+ PIR_DEBUG_SERIALIZE_LLVM=
+ 1 serialize LLVM IR, and add metadata to make it patchable on
+ different sessions. This will be set regardless of the env
+ var if RIR_PRESERVE is set or the compiler server is running,
+ so the only time this is useful is when debugging.
+
### Disassembly annotations
#### Assumptions
@@ -168,6 +227,9 @@ debugging:
* `rir.eval`: evaluates the code in RIR
* `rir.body`: returns the body of rir-compiled function. The body is the vector
containing its ast maps and code objects
+* `rir.serialize`: Serializes the SEXP, preserving RIR/PIR-compiled closures, to the given path
+* `rir.deserialize`: Deserializes and returns the SEXP at the given path
+* `rir.killCompilerServers`: (on client) send a special request to kill compiler servers connected to this client
* `.printInvocation`: prints invocation during evaluation
* `.int3`: breakpoint during evaluation
@@ -405,3 +467,16 @@ In order to use rr inside a docker container, it is necessary to run it with som
`docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -it registry.gitlab.com/rirvm/rir_mirror/benchmark:SOME_COMMIT_ID`
Recording Ř works just fine, with the usual `-d rr` . However, when running `rr replay`, it complains about not being able to find the debug symbols. To overcome this issue type in: `/opt/rir/external/custom-r/bin/exec/R` right after `rr replay` (within the *rr* prompt).
+
+## CLion
+
+You can create run/debug configurations for Ř in CLion.
+
+CLion should be smart enough to automatically generate a CMake configuration from our `CMakeLists.txt`, and thus have some preset run configurations. The one you will use is `rir`. This configuration will already build Ř in a folder called `cmake-build-debug`. However, you must do some manual configuration in order to get it to run properly:
+
+- Change `executable` to `/external/custom-r/bin/exec/R` (this is the path to the R executable that was built by Ř; replace `` with the actual repo path)
+- Set the following environment variables (again, replace `` with the actual repo path):
+ - On Linux: `LD_LIBRARY_PATH=/external/custom-r/lib;EXTRA_LOAD_R=/rir/R/rir.R;EXTRA_LOAD_SO=/cmake-build-sanitize/Debug/librir.dylib;R_DOC_DIR=/external/custom-r/doc;R_HOME=/external/custom-r;R_HOME_DIR=/external/custom-r;R_INCLUDE_DIR=/external/custom-r/include;R_SHARE_DIR=/external/custom-r/share`
+ - On macOS: `DYLD_LIBRARY_PATH=/external/custom-r/lib;EXTRA_LOAD_R=/rir/R/rir.R;EXTRA_LOAD_SO=/cmake-build-sanitize/Debug/librir.dylib;R_DOC_DIR=/external/custom-r/doc;R_HOME=/external/custom-r;R_HOME_DIR=/external/custom-r;R_INCLUDE_DIR=/external/custom-r/include;R_SHARE_DIR=/external/custom-r/share`
+
+This should be enough to get run to start the REPL, and debug to work with breakpoints. You can also redirect input from files (e.g. one of the tests), or add extra environment variables like `PIR_DEBUG`.
\ No newline at end of file
diff --git a/external/custom-r b/external/custom-r
index 6483fffd7..e6f10ecf0 160000
--- a/external/custom-r
+++ b/external/custom-r
@@ -1 +1 @@
-Subproject commit 6483fffd7edb49bac2e98479ac534cb7e8448475
+Subproject commit e6f10ecf04f60737fcc39f6f53b78be8c23ae296
diff --git a/external/xxHash b/external/xxHash
new file mode 160000
index 000000000..35b0373c6
--- /dev/null
+++ b/external/xxHash
@@ -0,0 +1 @@
+Subproject commit 35b0373c697b5f160d3db26b1cbb45a0d5ba788c
diff --git a/rir/R/rir.R b/rir/R/rir.R
index 977a01e68..4501b2627 100644
--- a/rir/R/rir.R
+++ b/rir/R/rir.R
@@ -216,3 +216,16 @@ rir.annotateDepromised <- function(closure) {
rir.markFunction(copy, DepromiseArgs=TRUE)
copy
}
+
+# Kill compiler servers connected to the client (this is the client)
+rir.killCompilerServers <- function() {
+ .Call("rirKillCompilerServers")
+}
+
+# We need to run this after all static C++ initializers are run
+invisible(.Call("initializeUUIDPool"))
+invisible(.Call("initializePrintPrettyGraphFromEnv"))
+invisible(.Call("initPirTraceSerializationExcludeFlags"))
+
+# We need to ensure the compiler server starts after ALL code is loaded, so it can't be in initializeRuntime
+invisible(.Call("tryToRunCompilerServer"))
diff --git a/rir/src/R/Funtab.h b/rir/src/R/Funtab.h
index fd609a1cd..ce125294e 100644
--- a/rir/src/R/Funtab.h
+++ b/rir/src/R/Funtab.h
@@ -24,6 +24,16 @@ static inline int getBuiltinArity(SEXP f) {
static inline int getFlag(int i) { return ((R_FunTab[i].eval) / 100) % 10; }
static inline int getFlag(SEXP f) { return getFlag(getBuiltinNr(f)); }
+static inline SEXP getBuiltinFun(int id) {
+ assert(R_FunTab[id].eval % 10 == 1 &&
+ "Only use for BUILTINSXP");
+ if (R_FunTab[id].eval % 100 / 10 == 0) {
+ return Rf_install(getBuiltinName(id))->u.symsxp.value;
+ } else {
+ return Rf_install(getBuiltinName(id))->u.symsxp.internal;
+ }
+}
+
static inline SEXP getBuiltinFun(char const* name) {
assert(R_FunTab[rir::blt(name)].eval % 10 == 1 &&
"Only use for BUILTINSXP");
@@ -33,4 +43,20 @@ static inline SEXP getBuiltinFun(char const* name) {
return Rf_install(name)->u.symsxp.internal;
}
+static inline SEXP getBuiltinOrSpecialFun(int id) {
+ if (R_FunTab[id].eval % 100 / 10 == 0) {
+ return Rf_install(getBuiltinName(id))->u.symsxp.value;
+ } else {
+ return Rf_install(getBuiltinName(id))->u.symsxp.internal;
+ }
+}
+
+static inline SEXP getBuiltinOrSpecialFun(char const* name) {
+ if (R_FunTab[rir::blt(name)].eval % 100 / 10 == 0) {
+ return Rf_install(name)->u.symsxp.value;
+ } else {
+ return Rf_install(name)->u.symsxp.internal;
+ }
+}
+
#endif
diff --git a/rir/src/R/Printing.cpp b/rir/src/R/Printing.cpp
index 7bb24d4b8..ccc7cce7f 100644
--- a/rir/src/R/Printing.cpp
+++ b/rir/src/R/Printing.cpp
@@ -8,6 +8,9 @@
#include "runtime/LazyArglist.h"
#include "runtime/LazyEnvironment.h"
#include "runtime/PirTypeFeedback.h"
+#include "runtime/PoolStub.h"
+#include "runtime/ProxyEnv.h"
+#include "runtime/RirRuntimeObject.h"
#include
#include
@@ -82,36 +85,36 @@ std::string Print::trim(std::string s, size_t n) {
return s.substr(0, n - 4) + "|...";
}
-std::string Print::dumpPROMSXP(SEXP s) {
+std::string Print::dumpPROMSXP(SEXP s, size_t length) {
std::stringstream ss;
ss << "";
+ ss << " env=" << dumpSexp(s->u.promsxp.env, length) << ">";
return ss.str();
}
-std::string Print::dumpCLOSXP(SEXP s) {
+std::string Print::dumpCLOSXP(SEXP s, size_t length) {
std::stringstream ss;
ss << "function(";
auto f = FORMALS(s);
while (f != R_NilValue) {
if (TAG(f) != R_NilValue)
- ss << dumpSexp(TAG(f));
+ ss << dumpSexp(TAG(f), length);
if (CAR(f) != R_MissingArg)
- ss << "=" << dumpSexp(CAR(f));
+ ss << "=" << dumpSexp(CAR(f), length);
f = CDR(f);
if (f != R_NilValue)
ss << ", ";
}
- ss << ") " << dumpSexp(BODY(s));
- ss << " env=" << dumpSexp(CLOENV(s));
+ ss << ") " << dumpSexp(BODY(s), length);
+ ss << " env=" << dumpSexp(CLOENV(s), length);
return ss.str();
}
-std::string Print::dumpLISTSXP(SEXP s, size_t limit) {
+std::string Print::dumpLISTSXP(SEXP s, size_t limit, size_t length) {
std::stringstream ss;
ss << "<" << sexptype2char(TYPEOF(s));
@@ -124,11 +127,11 @@ std::string Print::dumpLISTSXP(SEXP s, size_t limit) {
ss << " ";
out++;
if (TAG(s) != R_NilValue) {
- auto e = dumpSexp(TAG(s));
+ auto e = dumpSexp(TAG(s), length);
ss << e << "=";
out += 1 + e.length();
}
- auto e = dumpSexp(CAR(s));
+ auto e = dumpSexp(CAR(s), length);
ss << e;
out += e.length();
s = CDR(s);
@@ -137,17 +140,17 @@ std::string Print::dumpLISTSXP(SEXP s, size_t limit) {
return ss.str();
}
-std::string Print::dumpLANGSXP(SEXP s) {
+std::string Print::dumpLANGSXP(SEXP s, size_t length) {
std::stringstream ss;
if (s != R_NilValue) {
- ss << dumpSexp(CAR(s));
+ ss << dumpSexp(CAR(s), length);
s = CDR(s);
}
ss << "(";
while (s != R_NilValue) {
if (TAG(s) != R_NilValue)
- ss << dumpSexp(TAG(s)) << "=";
- ss << dumpSexp(CAR(s));
+ ss << dumpSexp(TAG(s), length) << "=";
+ ss << dumpSexp(CAR(s), length);
s = CDR(s);
if (s != R_NilValue)
ss << ", ";
@@ -156,7 +159,7 @@ std::string Print::dumpLANGSXP(SEXP s) {
return ss.str();
}
-std::string Print::dumpVector(SEXP s, size_t limit) {
+std::string Print::dumpVector(SEXP s, size_t limit, size_t length) {
std::stringstream ss;
auto unsafe = unsafeTags(s);
@@ -190,7 +193,7 @@ std::string Print::dumpVector(SEXP s, size_t limit) {
break;
}
case STRSXP: {
- ss << dumpSexp(STRING_PTR(s)[0]);
+ ss << dumpSexp(STRING_PTR(s)[0], length);
break;
}
case RAWSXP: {
@@ -250,13 +253,13 @@ std::string Print::dumpVector(SEXP s, size_t limit) {
}
case STRSXP: {
// NA checked for CHARSXP in dumpSexp
- auto e = dumpSexp(STRING_PTR(s)[i]);
+ auto e = dumpSexp(STRING_PTR(s)[i], length);
ss << e;
out += e.length();
break;
}
case VECSXP: {
- auto e = dumpSexp(VECTOR_PTR(s)[i]);
+ auto e = dumpSexp(VECTOR_PTR(s)[i], length);
ss << e;
out += e.length();
break;
@@ -305,7 +308,7 @@ std::string Print::dumpVector(SEXP s, size_t limit) {
return ss.str();
}
-std::string Print::dumpEXTERNALSXP(SEXP s) {
+std::string Print::dumpEXTERNALSXP(SEXP s, size_t length) {
std::stringstream ss;
ss << "<";
if (auto p = Code::check(s)) {
@@ -316,6 +319,9 @@ std::string Print::dumpEXTERNALSXP(SEXP s) {
case Code::Kind::Native:
ss << "n ";
break;
+ case Code::Kind::Deserializing:
+ ss << "ds ";
+ break;
}
ss << "(rir::Code*)" << p;
if (p->pendingCompilation())
@@ -334,6 +340,16 @@ std::string Print::dumpEXTERNALSXP(SEXP s) {
ss << "(rir::LazyEnvironment*)" << p;
} else if (auto p = PirTypeFeedback::check(s)) {
ss << "(rir::PirTypeFeedback*)" << p;
+ } else if (auto p = TypeFeedback::check(s)) {
+ ss << "(rir::TypeFeedback*)" << p;
+ } else if (auto p = SerialModule::check(s)) {
+ ss << "(rir::SerialModule*)" << p << " (" << p->firstBitcodeBytes() << ")";
+ } else if (auto p = PoolStub::check(s)) {
+ ss << "(rir::PoolStub*)";
+ p->print(ss);
+ } else if (auto p = ProxyEnv::check(s)) {
+ ss << "(rir::ProxyEnv*)";
+ p->print(ss);
} else {
assert(false && "missing RirRuntimeObject printing");
}
@@ -380,12 +396,12 @@ std::string Print::dumpSexp(SEXP s, size_t length) {
}
case LISTSXP: {
- ss << dumpLISTSXP(s, length);
+ ss << dumpLISTSXP(s, length, length);
break;
}
case CLOSXP: {
- ss << dumpCLOSXP(s);
+ ss << dumpCLOSXP(s, length);
break;
}
@@ -403,18 +419,29 @@ std::string Print::dumpSexp(SEXP s, size_t length) {
auto unsafe = unsafeTags(s);
if (unsafe.length())
ss << " |" << unsafe;
- ss << " " << s << ">";
+ ss << " ";
+ if (R_IsPackageEnv(s) || R_IsNamespaceEnv(s)) {
+ ss << (R_IsPackageEnv(s) ? "pkg " : "ns ");
+ auto name = R_IsPackageEnv(s) ? R_PackageEnvName(s) : R_NamespaceEnvSpec(s);
+ if (name != R_NilValue) {
+ assert(TYPEOF(name) == STRSXP);
+ for (R_xlen_t i = 0; i < XLENGTH(name); i++) {
+ ss << CHAR(STRING_ELT(name, i)) << " ";
+ }
+ }
+ }
+ ss << s << ">";
}
break;
}
case PROMSXP: {
- ss << dumpPROMSXP(s);
+ ss << dumpPROMSXP(s, length);
break;
}
case LANGSXP: {
- ss << dumpLANGSXP(s);
+ ss << dumpLANGSXP(s, length);
break;
}
@@ -439,12 +466,12 @@ std::string Print::dumpSexp(SEXP s, size_t length) {
case STRSXP:
case VECSXP:
case RAWSXP: {
- ss << dumpVector(s, length);
+ ss << dumpVector(s, length, length);
break;
}
case EXTERNALSXP: {
- ss << dumpEXTERNALSXP(s);
+ ss << dumpEXTERNALSXP(s, length);
break;
}
diff --git a/rir/src/R/Printing.h b/rir/src/R/Printing.h
index ab893c420..e66c432d2 100644
--- a/rir/src/R/Printing.h
+++ b/rir/src/R/Printing.h
@@ -16,12 +16,12 @@ class Print {
static std::string sexptype2char(SEXPTYPE type);
static std::string trim(std::string s, size_t n);
static std::string unsafeTags(SEXP s);
- static std::string dumpPROMSXP(SEXP s);
- static std::string dumpCLOSXP(SEXP s);
- static std::string dumpLISTSXP(SEXP s, size_t limit);
- static std::string dumpLANGSXP(SEXP s);
- static std::string dumpVector(SEXP s, size_t limit);
- static std::string dumpEXTERNALSXP(SEXP s);
+ static std::string dumpPROMSXP(SEXP s, size_t length = 50);
+ static std::string dumpCLOSXP(SEXP s, size_t length = 50);
+ static std::string dumpLISTSXP(SEXP s, size_t limit, size_t length = 50);
+ static std::string dumpLANGSXP(SEXP s, size_t length = 50);
+ static std::string dumpVector(SEXP s, size_t limit, size_t length = 50);
+ static std::string dumpEXTERNALSXP(SEXP s, size_t length = 50);
};
} // namespace rir
diff --git a/rir/src/R/Protect.h b/rir/src/R/Protect.h
index 550c05645..c5d723930 100644
--- a/rir/src/R/Protect.h
+++ b/rir/src/R/Protect.h
@@ -23,6 +23,14 @@ class Protect {
return value;
}
+ SEXP nullable(SEXP value) {
+ if (value) {
+ Rf_protect(value);
+ ++protectedValues_;
+ }
+ return value;
+ }
+
~Protect() { Rf_unprotect(protectedValues_); }
private:
diff --git a/rir/src/R/Serialize.h b/rir/src/R/Serialize.h
index 2080ac3b5..fdc78b06c 100644
--- a/rir/src/R/Serialize.h
+++ b/rir/src/R/Serialize.h
@@ -2,8 +2,6 @@
#include
-#define REXPORT extern "C"
-
REXPORT SEXP R_serialize(SEXP object, SEXP icon, SEXP ascii, SEXP Sversion,
SEXP fun);
REXPORT SEXP R_unserialize(SEXP icon, SEXP fun);
@@ -30,12 +28,12 @@ REXPORT void OutRefIndex(R_outpstream_t stream, int i);
REXPORT int InRefIndex(R_inpstream_t stream, int flags);
REXPORT void OutStringVec(R_outpstream_t stream, SEXP s, SEXP ref_table);
-static inline void OutChar(R_outpstream_t stream, int chr) {
- stream->OutChar(stream, chr);
+static inline void OutChar(R_outpstream_t stream, char chr) {
+ stream->OutChar(stream, (int)chr);
}
-static inline int InChar(R_inpstream_t stream) {
- return stream->InChar(stream);
+static inline char InChar(R_inpstream_t stream) {
+ return (char)stream->InChar(stream);
}
static inline void OutBytes(R_outpstream_t stream, const void* buf,
@@ -46,3 +44,54 @@ static inline void OutBytes(R_outpstream_t stream, const void* buf,
static inline void InBytes(R_inpstream_t stream, void* buf, int length) {
stream->InBytes(stream, buf, length);
}
+
+static inline bool InBool(R_inpstream_t stream) {
+ return (bool)stream->InChar(stream);
+}
+
+static inline void OutBool(R_outpstream_t stream, bool b) {
+ stream->OutChar(stream, (int)b);
+}
+
+static inline void OutUInt(R_outpstream_t stream, unsigned int x) {
+ OutBytes(stream, &x, sizeof(x));
+}
+
+static inline unsigned int InUInt(R_inpstream_t stream) {
+ unsigned int x;
+ InBytes(stream, &x, sizeof(x));
+ return x;
+}
+
+static inline void OutU64(R_outpstream_t stream, uint64_t x) {
+ OutBytes(stream, &x, sizeof(x));
+}
+
+static inline uint64_t InU64(R_inpstream_t stream) {
+ uint64_t x;
+ InBytes(stream, &x, sizeof(x));
+ return x;
+}
+
+static inline void OutSize(R_outpstream_t stream, size_t x) {
+ OutU64(stream, (uint64_t)x);
+}
+
+static inline size_t InSize(R_inpstream_t stream) {
+ return (size_t)InU64(stream);
+}
+
+static inline void WriteNullableItem(SEXP s, SEXP ref_table, R_outpstream_t stream) {
+ OutBool(stream, s != nullptr);
+ if (s) {
+ WriteItem(s, ref_table, stream);
+ }
+}
+
+static inline SEXP ReadNullableItem(SEXP ref_table, R_inpstream_t stream) {
+ if (InBool(stream)) {
+ return ReadItem(ref_table, stream);
+ } else {
+ return nullptr;
+ }
+}
\ No newline at end of file
diff --git a/rir/src/R/disableGc.h b/rir/src/R/disableGc.h
new file mode 100644
index 000000000..4ac5ba5db
--- /dev/null
+++ b/rir/src/R/disableGc.h
@@ -0,0 +1,31 @@
+//
+// Created by Jakob Hain on 7/22/23.
+//
+
+#pragma once
+
+#include "R/r_incl.h"
+#include
+
+template static ALWAYS_INLINE void disableGc(F f) {
+ auto gcEnabled = R_GCEnabled;
+ R_GCEnabled = 0;
+ f();
+ R_GCEnabled = gcEnabled;
+}
+
+template static ALWAYS_INLINE SEXP disableGc2(F f) {
+ auto gcEnabled = R_GCEnabled;
+ R_GCEnabled = 0;
+ auto res = f();
+ R_GCEnabled = gcEnabled;
+ return res;
+}
+
+template static ALWAYS_INLINE rir::UUID disableGc3(F f) {
+ auto gcEnabled = R_GCEnabled;
+ R_GCEnabled = 0;
+ auto res = f();
+ R_GCEnabled = gcEnabled;
+ return res;
+}
diff --git a/rir/src/R/r.h b/rir/src/R/r.h
index d7ee92003..381c9787a 100644
--- a/rir/src/R/r.h
+++ b/rir/src/R/r.h
@@ -51,6 +51,7 @@ extern FUNTAB R_FunTab[];
extern SEXP R_TrueValue;
extern SEXP R_FalseValue;
extern SEXP R_LogicalNAValue;
+extern int R_GCEnabled;
}
// Performance critical stuff copied from Rinlinedfun.h
@@ -102,6 +103,13 @@ inline R_xlen_t XLENGTH_EX(SEXP x) {
return ALTREP(x) ? ALTREP_LENGTH(x) : STDVEC_LENGTH(x);
}
+/// This is semantically equivalent to LENGTH and XLENGTH, but necessary when
+/// write barrier is enabled if x isn't necessarily an actual vector
+/// TODO: technically UB so refactor to not rely on this behavior
+inline R_xlen_t RAW_LENGTH(SEXP x) {
+ return ALTREP(x) ? ALTREP_LENGTH(x) : ((VECSEXP) (x))->vecsxp.length;
+}
+
typedef struct {
int ibeta, it, irnd, ngrd, machep, negep, iexp, minexp, maxexp;
double eps, epsneg, xmin, xmax;
diff --git a/rir/src/api.cpp b/rir/src/api.cpp
index 205066999..8d5370d0a 100644
--- a/rir/src/api.cpp
+++ b/rir/src/api.cpp
@@ -4,23 +4,31 @@
#include "api.h"
#include "R/Serialize.h"
+#include "Rinternals.h"
#include "bc/BC.h"
#include "bc/Compiler.h"
#include "compiler/backend.h"
#include "compiler/compiler.h"
#include "compiler/log/debug.h"
+#include "compiler/native/lower_function_llvm.h"
#include "compiler/parameter.h"
#include "compiler/pir/closure.h"
-#include "compiler/pir/type.h"
#include "compiler/test/PirCheck.h"
#include "compiler/test/PirTests.h"
+#include "compilerClientServer/CompilerClient.h"
+#include "compilerClientServer/CompilerServer.h"
+#include "compilerClientServer/compiler_server_client_shared_utils.h"
#include "interpreter/interp_incl.h"
+#include "runtime/DispatchTable.h"
+#include "runtime/log/printPrettyGraphFromEnv.h"
+#include "serializeHash/hash/UUIDPool.h"
+#include "serializeHash/serialize/traceSerialize.h"
+#include "utils/ByteBuffer.h"
#include "utils/measuring.h"
#include
#include
#include
-#include
#include
using namespace rir;
@@ -35,6 +43,11 @@ static bool oldPreserve = false;
static unsigned oldSerializeChaos = false;
static size_t oldDeoptChaos = false;
+bool pir::Parameter::PIR_MEASURE_COMPILED_CLOSURES =
+ getenv("PIR_MEASURE_COMPILED_CLOSURES") != nullptr &&
+ strtol(getenv("PIR_MEASURE_COMPILED_CLOSURES"), nullptr, 10);
+
+
bool parseDebugStyle(const char* str, pir::DebugStyle& s) {
#define V(style) \
if (strcmp(str, #style) == 0) { \
@@ -56,13 +69,9 @@ REXPORT SEXP rirDisassemble(SEXP what, SEXP verbose) {
if (!t)
Rf_error("Not a rir compiled code (CLOSXP but not DispatchTable)");
- std::cout << "== closure " << what << " (dispatch table " << t << ", env "
- << CLOENV(what) << ") ==\n";
- for (size_t entry = 0; entry < t->size(); ++entry) {
- Function* f = t->get(entry);
- std::cout << "= version " << entry << " (" << f << ") =\n";
- f->disassemble(std::cout);
- }
+ std::cout << "== closure " << what << " (env " << CLOENV(what) << ") ==\n";
+
+ t->print(std::cout, Rf_asLogical(verbose));
return R_NilValue;
}
@@ -125,50 +134,50 @@ REXPORT SEXP rirMarkFunction(SEXP what, SEXP which, SEXP reopt_,
Function* fun = dt->get(i);
if (reopt != NA_LOGICAL) {
if (reopt) {
- fun->flags.set(Function::MarkOpt);
- fun->flags.reset(Function::NotOptimizable);
+ fun->setFlag(Function::MarkOpt);
+ fun->resetFlag(Function::NotOptimizable);
} else {
- fun->flags.reset(Function::MarkOpt);
+ fun->resetFlag(Function::MarkOpt);
}
}
if (forceInline != NA_LOGICAL) {
if (forceInline)
- fun->flags.set(Function::ForceInline);
+ fun->setFlag(Function::ForceInline);
else
- fun->flags.reset(Function::ForceInline);
+ fun->resetFlag(Function::ForceInline);
}
if (disableInline != NA_LOGICAL) {
if (disableInline)
- fun->flags.set(Function::DisableInline);
+ fun->setFlag(Function::DisableInline);
else
- fun->flags.reset(Function::DisableInline);
+ fun->resetFlag(Function::DisableInline);
}
if (disableSpecialization != NA_LOGICAL) {
if (disableSpecialization)
- fun->flags.set(Function::DisableAllSpecialization);
+ fun->setFlag(Function::DisableAllSpecialization);
else
- fun->flags.reset(Function::DisableAllSpecialization);
+ fun->resetFlag(Function::DisableAllSpecialization);
}
if (disableArgumentTypeSpecialization != NA_LOGICAL) {
if (disableArgumentTypeSpecialization)
- fun->flags.set(Function::DisableArgumentTypeSpecialization);
+ fun->setFlag(Function::DisableArgumentTypeSpecialization);
else
- fun->flags.reset(Function::DisableArgumentTypeSpecialization);
+ fun->resetFlag(Function::DisableArgumentTypeSpecialization);
}
if (disableNumArgumentSpecialization != NA_LOGICAL) {
if (disableNumArgumentSpecialization)
- fun->flags.set(Function::DisableNumArgumentsSpezialization);
+ fun->setFlag(Function::DisableNumArgumentsSpezialization);
else
- fun->flags.reset(Function::DisableNumArgumentsSpezialization);
+ fun->resetFlag(Function::DisableNumArgumentsSpezialization);
}
bool DISABLE_ANNOTATIONS = getenv("PIR_DISABLE_ANNOTATIONS") ? true : false;
if (!DISABLE_ANNOTATIONS) {
if (depromiseArgs != NA_LOGICAL) {
if (depromiseArgs)
- fun->flags.set(Function::DepromiseArgs);
+ fun->setFlag(Function::DepromiseArgs);
else
- fun->flags.reset(Function::DepromiseArgs);
+ fun->resetFlag(Function::DepromiseArgs);
}
}
@@ -243,18 +252,18 @@ static pir::DebugOptions::DebugFlags getInitialDebugFlags() {
return flags;
}
-static std::regex getInitialDebugPassFilter() {
+static std::string getInitialDebugPassFilter() {
auto filter = getenv("PIR_DEBUG_PASS_FILTER");
if (filter)
- return std::regex(filter);
- return std::regex(".*");
+ return {filter};
+ return {".*"};
}
-static std::regex getInitialDebugFunctionFilter() {
+static std::string getInitialDebugFunctionFilter() {
auto filter = getenv("PIR_DEBUG_FUNCTION_FILTER");
if (filter)
- return std::regex(filter);
- return std::regex(".*");
+ return {filter};
+ return {".*"};
}
static pir::DebugStyle getInitialDebugStyle() {
@@ -274,8 +283,8 @@ static pir::DebugStyle getInitialDebugStyle() {
return style;
}
-pir::DebugOptions pir::DebugOptions::DefaultDebugOptions = {
- getInitialDebugFlags(), getInitialDebugPassFilter(),
+pir::DebugOptions pir::DebugOptions::DefaultDebugOptions =
+ {getInitialDebugFlags(), getInitialDebugPassFilter(),
getInitialDebugFunctionFilter(), getInitialDebugStyle()};
REXPORT SEXP pirSetDebugFlags(SEXP debugFlags) {
@@ -288,7 +297,18 @@ REXPORT SEXP pirSetDebugFlags(SEXP debugFlags) {
}
SEXP pirCompile(SEXP what, const Context& assumptions, const std::string& name,
- const pir::DebugOptions& debug) {
+ const pir::DebugOptions& debug,
+ std::string* closureVersionPirPrint) {
+ return pirCompile(what, assumptions, name, debug,
+ closureVersionPirPrint, SerialOptions::DeepCopy);
+}
+
+SEXP pirCompile(SEXP what, const Context& assumptions, const std::string& name,
+ const pir::DebugOptions& debug,
+ std::string* closureVersionPirPrint,
+ const SerialOptions& serialOpts) {
+ Protect p(what);
+
if (!isValidClosureSEXP(what)) {
Rf_error("not a compiled closure");
}
@@ -296,72 +316,104 @@ SEXP pirCompile(SEXP what, const Context& assumptions, const std::string& name,
Rf_error("Cannot optimize compiled expression, only closure");
}
- PROTECT(what);
-
- bool dryRun = debug.includes(pir::DebugFlag::DryRun);
- // compile to pir
- pir::Module* m = new pir::Module;
- pir::Log logger(debug);
- logger.title("Compiling " + name);
- pir::Compiler cmp(m, logger);
- auto compile = [&](pir::ClosureVersion* c) {
- logger.flushAll();
- cmp.optimizeModule();
-
- if (dryRun)
- return;
-
- rir::Function* done = nullptr;
- {
- // Single Backend instance, gets destroyed at the end of this block
- // to finalize the LLVM module so that we can eagerly compile the
- // body
- pir::Backend backend(m, logger, name);
- auto apply = [&](SEXP body, pir::ClosureVersion* c) {
- auto fun = backend.getOrCompile(c);
- Protect p(fun->container());
- DispatchTable::unpack(body)->insert(fun);
- if (body == BODY(what))
- done = fun;
+ Measuring::timeEventIf(pir::Parameter::PIR_MEASURE_COMPILED_CLOSURES, "api.cpp: pirCompile", what, [&]() {
+ auto compilerServerHandle =
+ CompilerClient::pirCompile(what, assumptions, name, debug);
+
+ if (!compilerServerHandle || PIR_CLIENT_DRY_RUN) {
+ // Actually pirCompile on the client
+ auto dryRun = debug.includes(pir::DebugFlag::DryRun);
+ // compile to pir
+ auto m = new pir::Module;
+ pir::Log logger(debug);
+ logger.title("Compiling " + name);
+ pir::Compiler cmp(m, logger);
+ auto compile = [&](pir::ClosureVersion* c) {
+ logger.flushAll();
+ cmp.optimizeModule();
+
+ if (dryRun)
+ return;
+
+ rir::Function* done = nullptr;
+ {
+ // Single Backend instance, gets destroyed at the end of this block to finalize the LLVM module so that we can eagerly compile the body
+ pir::Backend backend(m, logger, name, serialOpts);
+ auto apply = [&](SEXP body, pir::ClosureVersion* c) {
+ auto fun = backend.getOrCompile(c);
+ p(fun->container());
+ DispatchTable::unpack(body)->insert(fun);
+ if (body == BODY(what))
+ done = fun;
+ };
+ m->eachPirClosureVersion([&](pir::ClosureVersion* c) {
+ if (c->owner()->hasOriginClosure()) {
+ auto cls = c->owner()->rirClosure();
+ auto body = BODY(cls);
+ auto dt = DispatchTable::unpack(body);
+ if (dt->contains(c->context())) {
+ // Dispatch also to versions with pending compilation since we're not evaluating
+ auto other = dt->dispatch(c->context(), false);
+ assert(other != dt->baseline());
+ assert(other->context() == c->context());
+ if (other->body()->isCompiled())
+ return;
+ }
+ // Don't lower functions that have not been called often, as they have incomplete type-feedback.
+ if (dt->size() == 1 &&
+ dt->baseline()->invocationCount() < 2)
+ return;
+ apply(body, c);
+ }
+ });
+ if (!done)
+ apply(BODY(what), c);
+ }
+ // Eagerly compile the main function
+ done->body()->nativeCode();
+ if (closureVersionPirPrint) {
+ *closureVersionPirPrint =
+ printClosureVersionForCompilerServerComparison(c);
+ }
+ if (compilerServerHandle) {
+ // Compare compiled version with remote for discrepancies
+ compilerServerHandle->compare(c);
+ }
};
- m->eachPirClosureVersion([&](pir::ClosureVersion* c) {
- if (c->owner()->hasOriginClosure()) {
- auto cls = c->owner()->rirClosure();
- auto body = BODY(cls);
- auto dt = DispatchTable::unpack(body);
- if (dt->contains(c->context())) {
- // Dispatch also to versions with pending compilation
- // since we're not evaluating
- auto other = dt->dispatch(c->context(), false);
- assert(other != dt->baseline());
- assert(other->context() == c->context());
- if (other->body()->isCompiled())
- return;
- }
- // Don't lower functions that have not been called often, as
- // they have incomplete type-feedback.
- if (dt->size() == 1 &&
- dt->baseline()->invocationCount() < 2)
- return;
- apply(body, c);
+
+ cmp.compileClosure(
+ what, name, assumptions, true, compile,
+ [&]() {
+ if (debug.includes(pir::DebugFlag::ShowWarnings))
+ std::cerr << "Compilation failed\n";
+ },
+ {});
+
+ delete m;
+ } else {
+ if (debug.flags.contains(pir::DebugFlag::PrintFinalPir)) {
+ auto finalPir = compilerServerHandle->getFinalPir();
+ std::cerr << "Final PIR of '" << name << "':\n"
+ << finalPir << "\n";
+ }
+
+ // replace with the compiler server's version
+ auto newWhat = compilerServerHandle->getSexp();
+ auto dt = DispatchTable::unpack(BODY(what));
+ auto newDt = DispatchTable::unpack(BODY(newWhat));
+ for (unsigned i = 0; i < newDt->size(); ++i) {
+ if (i == 0) {
+ dt->baseline(newDt->baseline());
+ } else {
+ dt->insert(newDt->get(i));
}
- });
- if (!done)
- apply(BODY(what), c);
+ }
}
- // Eagerly compile the main function
- done->body()->nativeCode();
- };
+ delete compilerServerHandle;
+ });
- cmp.compileClosure(what, name, assumptions, true, compile,
- [&]() {
- if (debug.includes(pir::DebugFlag::ShowWarnings))
- std::cerr << "Compilation failed\n";
- },
- {});
+ printPrettyGraphOfCompiledIfNecessary(what, name);
- delete m;
- UNPROTECT(1);
return what;
}
@@ -603,12 +655,53 @@ REXPORT SEXP rirCreateSimpleIntContext() {
return res;
}
+REXPORT SEXP rirKillCompilerServers() {
+ R_Visible = (Rboolean)false;
+ if (!CompilerClient::isRunning()) {
+ Rf_warning("Compiler client isn't running");
+ return R_NilValue;
+ }
+ CompilerClient::killServers();
+ return R_NilValue;
+}
+
+REXPORT SEXP initializeUUIDPool() {
+ UUIDPool::initialize();
+ R_Visible = (Rboolean)false;
+ return R_NilValue;
+}
+
+REXPORT SEXP initializePrintPrettyGraphFromEnv() {
+ rir::initializePrintPrettyGraphFromEnv();
+ R_Visible = (Rboolean)false;
+ return R_NilValue;
+}
+
+REXPORT SEXP initPirTraceSerializationExcludeFlags() {
+ rir::initPirTraceSerializationExcludeFlags();
+ R_Visible = (Rboolean)false;
+ return R_NilValue;
+}
+
+REXPORT SEXP tryToRunCompilerServer() {
+ CompilerServer::tryRun();
+ R_Visible = (Rboolean)false;
+ return R_NilValue;
+}
+
REXPORT SEXP playground() {
return R_NilValue;
}
bool startup() {
+ if (getenv("R_DISABLE_GC") &&
+ strcmp(getenv("R_DISABLE_GC"), "") != 0 &&
+ strcmp(getenv("R_DISABLE_GC"), "0") != 0 &&
+ strcmp(getenv("R_DISABLE_GC"), "false") != 0) {
+ Rf_warning("R GC is disabled");
+ R_GCEnabled = false;
+ }
initializeRuntime();
return true;
}
diff --git a/rir/src/api.h b/rir/src/api.h
index 2ef31ee4f..74dfa2503 100644
--- a/rir/src/api.h
+++ b/rir/src/api.h
@@ -7,10 +7,14 @@
#include
-#define REXPORT extern "C"
-
extern int R_ENABLE_JIT;
+namespace rir {
+
+struct SerialOptions;
+
+}; // namespace rir
+
REXPORT SEXP rirInvocationCount(SEXP what);
REXPORT SEXP pirCompileWrapper(SEXP closure, SEXP name, SEXP debugFlags,
SEXP debugStyle);
@@ -19,7 +23,12 @@ REXPORT SEXP pirTests();
REXPORT SEXP pirCheck(SEXP f, SEXP check, SEXP env);
REXPORT SEXP pirSetDebugFlags(SEXP debugFlags);
SEXP pirCompile(SEXP closure, const rir::Context& assumptions,
- const std::string& name, const rir::pir::DebugOptions& debug);
+ const std::string& name, const rir::pir::DebugOptions& debug,
+ std::string* closureVersionPirPrint = nullptr);
+SEXP pirCompile(SEXP closure, const rir::Context& assumptions,
+ const std::string& name, const rir::pir::DebugOptions& debug,
+ std::string* closureVersionPirPrint,
+ const rir::SerialOptions& serialOpts);
extern SEXP rirOptDefaultOpts(SEXP closure, const rir::Context&, SEXP name);
extern SEXP rirOptDefaultOptsDryrun(SEXP closure, const rir::Context&,
SEXP name);
@@ -29,6 +38,15 @@ REXPORT SEXP rirDeserialize(SEXP file);
REXPORT SEXP rirSetUserContext(SEXP f, SEXP udc);
REXPORT SEXP rirCreateSimpleIntContext();
+REXPORT SEXP initializeUUIDPool();
+REXPORT SEXP initializePrintPrettyGraphFromEnv();
+REXPORT SEXP initPirTraceSerializationExcludeFlags();
+/// Send a message from the compiler client (this) to each connected compiler
+/// server, which kills the server (exit 0) on receive. Then stops the client
+/// for the remainder of the session
+REXPORT SEXP rirKillCompilerServers();
+REXPORT SEXP tryToRunCompilerServer();
+
// this method is just to have an easy way to play around with the code and get
// feedback by calling .Call('playground')
REXPORT SEXP playground();
diff --git a/rir/src/bc/BC.cpp b/rir/src/bc/BC.cpp
index 1dea8a49a..a7bc88278 100644
--- a/rir/src/bc/BC.cpp
+++ b/rir/src/bc/BC.cpp
@@ -1,14 +1,12 @@
#include "BC.h"
-#include "R/Funtab.h"
#include "R/Printing.h"
-#include "R/RList.h"
-#include "R/Serialize.h"
#include "R/r.h"
#include "bc/CodeStream.h"
+#include "runtime/log/printRirObject.h"
+#include "serializeHash/serialize/serialize.h"
#include "utils/Pool.h"
#include
-#include
namespace rir {
@@ -24,23 +22,6 @@ void BC::write(CodeStream& cs) const {
cs.insert(immediate.cacheIdx);
return;
- case Opcode::record_call_:
- // Call feedback targets are stored in the code extra pool. We don't
- // have access to them here, so we can't write a call feedback with
- // preseeded values.
- assert(immediate.callFeedback.numTargets == 0 &&
- "cannot write call feedback targets");
- cs.insert(immediate.callFeedback);
- return;
-
- case Opcode::record_test_:
- cs.insert(immediate.testFeedback);
- break;
-
- case Opcode::record_type_:
- cs.insert(immediate.typeFeedback);
- break;
-
case Opcode::push_:
case Opcode::ldfun_:
case Opcode::ldddvar_:
@@ -96,6 +77,9 @@ void BC::write(CodeStream& cs) const {
case Opcode::pull_:
case Opcode::is_:
case Opcode::put_:
+ case Opcode::record_call_:
+ case Opcode::record_test_:
+ case Opcode::record_type_:
cs.insert(immediate.i);
return;
@@ -113,14 +97,15 @@ SEXP BC::immediateConst() const {
return Pool::get(immediate.pool);
}
-// #define DEBUG_SERIAL
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-align"
-void BC::deserialize(SEXP refTable, R_inpstream_t inp, Opcode* code,
+void BC::deserialize(AbstractDeserializer& deserializer, Opcode* code,
size_t codeSize, Code* container) {
while (codeSize > 0) {
- *code = (Opcode)InChar(inp);
+ if (deserializer.willRead(SerialFlags::CodeMisc)) {
+ *code = deserializer.readBytesOf(SerialFlags::CodeMisc);
+ }
unsigned size = BC::fixedSize(*code);
ImmediateArguments& i = *(ImmediateArguments*)(code + 1);
switch (*code) {
@@ -139,46 +124,57 @@ void BC::deserialize(SEXP refTable, R_inpstream_t inp, Opcode* code,
case Opcode::stvar_:
case Opcode::stvar_super_:
case Opcode::missing_:
- i.pool = Pool::insert(ReadItem(refTable, inp));
+ DESERIALIZE(i.pool, readConst, SerialFlags::CodeMisc);
break;
case Opcode::ldvar_cached_:
case Opcode::ldvar_for_update_cache_:
case Opcode::stvar_cached_:
- i.poolAndCache.poolIndex = Pool::insert(ReadItem(refTable, inp));
- i.poolAndCache.cacheIndex = InInteger(inp);
+ DESERIALIZE(i.poolAndCache.poolIndex, readConst, SerialFlags::CodeMisc);
+ DESERIALIZE(i.poolAndCache.cacheIndex, readBytesOf, SerialFlags::CodeMisc);
break;
case Opcode::guard_fun_:
- i.guard_fun_args.name = Pool::insert(ReadItem(refTable, inp));
- i.guard_fun_args.expected = Pool::insert(ReadItem(refTable, inp));
- i.guard_fun_args.id = InInteger(inp);
+ DESERIALIZE(i.guard_fun_args.name, readConst, SerialFlags::CodeMisc);
+ DESERIALIZE(i.guard_fun_args.expected, readConst, SerialFlags::CodeMisc);
+ DESERIALIZE(i.guard_fun_args.id, readBytesOf, SerialFlags::CodeMisc);
break;
case Opcode::call_:
case Opcode::named_call_:
- case Opcode::call_dots_: {
- i.callFixedArgs.nargs = InInteger(inp);
- i.callFixedArgs.ast = Pool::insert(ReadItem(refTable, inp));
- InBytes(inp, &i.callFixedArgs.given, sizeof(Context));
- Opcode* c = code + 1 + sizeof(CallFixedArgs);
- // Read implicit promise argument offsets
- // Read named arguments
- if (*code == Opcode::named_call_ || *code == Opcode::call_dots_) {
- PoolIdx* names = (PoolIdx*)c;
- for (size_t j = 0; j < i.callFixedArgs.nargs; j++)
- names[j] = Pool::insert(ReadItem(refTable, inp));
+ case Opcode::call_dots_:
+ if (deserializer.willRead(SerialFlags::CodeMisc)) {
+ i.callFixedArgs.nargs =
+ deserializer.readBytesOf(SerialFlags::CodeMisc);
+ i.callFixedArgs.ast =
+ deserializer.readConst(SerialFlags::CodeMisc);
+ i.callFixedArgs.given =
+ Context(deserializer.readBytesOf(
+ SerialFlags::CodeMisc));
+ Opcode* c = code + 1 + sizeof(CallFixedArgs);
+ // Read implicit promise argument offsets
+ // Read named arguments
+ if (*code == Opcode::named_call_ ||
+ *code == Opcode::call_dots_) {
+ auto names = (PoolIdx*)c;
+ for (size_t j = 0; j < i.callFixedArgs.nargs; j++) {
+ names[j] =
+ deserializer.readConst(SerialFlags::CodeMisc);
+ }
+ }
}
break;
- }
case Opcode::call_builtin_:
- i.callBuiltinFixedArgs.nargs = InInteger(inp);
- i.callBuiltinFixedArgs.ast = Pool::insert(ReadItem(refTable, inp));
- i.callBuiltinFixedArgs.builtin =
- Pool::insert(ReadItem(refTable, inp));
+ DESERIALIZE(i.callBuiltinFixedArgs.nargs, readBytesOf, SerialFlags::CodeMisc);
+ DESERIALIZE(i.callBuiltinFixedArgs.ast, readConst, SerialFlags::CodeMisc);
+ DESERIALIZE(i.callBuiltinFixedArgs.builtin, readConst, SerialFlags::CodeMisc);
+ break;
+ case Opcode::mk_promise_:
+ case Opcode::mk_eager_promise_:
+ DESERIALIZE(i.fun, readBytesOf, SerialFlags::CodeMisc);
break;
case Opcode::record_call_:
case Opcode::record_type_:
case Opcode::record_test_:
- case Opcode::mk_promise_:
- case Opcode::mk_eager_promise_:
+ DESERIALIZE(i.i, readBytesOf, SerialFlags::CodeFeedback);
+ break;
case Opcode::br_:
case Opcode::brtrue_:
case Opcode::beginloop_:
@@ -190,7 +186,10 @@ void BC::deserialize(SEXP refTable, R_inpstream_t inp, Opcode* code,
case Opcode::put_:
case Opcode::clear_binding_cache_:
assert((size - 1) % 4 == 0);
- InBytes(inp, code + 1, size - 1);
+ if (size > 1 && deserializer.willRead(SerialFlags::CodeMisc)) {
+ deserializer.readBytes((void*)(code + 1), size - 1,
+ SerialFlags::CodeMisc);
+ }
break;
case Opcode::invalid_:
case Opcode::num_of:
@@ -198,24 +197,19 @@ void BC::deserialize(SEXP refTable, R_inpstream_t inp, Opcode* code,
break;
}
size = BC::size(code);
-#ifdef DEBUG_SERIAL
- if (*code == Opcode::deopt_) {
- BC aBc = BC::decode(code, container);
- std::cout << "deserialized: ";
- aBc.print(std::cout);
- }
-#endif
assert(codeSize >= size);
code += size;
codeSize -= size;
}
}
-void BC::serialize(SEXP refTable, R_outpstream_t out, const Opcode* code,
- size_t codeSize, const Code* container) {
+void BC::serialize(AbstractSerializer& serializer,
+ std::vector& extraPoolFlags,
+ const Opcode* code, size_t codeSize,
+ const Code* container) {
while (codeSize > 0) {
- const BC bc = BC::decode((Opcode*)code, container);
- OutChar(out, (int)*code);
+ const auto bc = BC::decode((Opcode*)code, container);
+ serializer.writeBytesOf(*code, SerialFlags::CodeMisc);
unsigned size = BC::fixedSize(*code);
ImmediateArguments i = bc.immediate;
switch (*code) {
@@ -234,40 +228,165 @@ void BC::serialize(SEXP refTable, R_outpstream_t out, const Opcode* code,
case Opcode::stvar_:
case Opcode::stvar_super_:
case Opcode::missing_:
- WriteItem(Pool::get(i.pool), refTable, out);
+ serializer.writeConst(i.pool, SerialFlags::CodeMisc);
break;
case Opcode::ldvar_cached_:
case Opcode::ldvar_for_update_cache_:
case Opcode::stvar_cached_:
- WriteItem(Pool::get(i.poolAndCache.poolIndex), refTable, out);
- OutInteger(out, i.poolAndCache.cacheIndex);
+ serializer.writeConst(i.poolAndCache.poolIndex, SerialFlags::CodeMisc);
+ serializer.writeBytesOf(i.poolAndCache.cacheIndex, SerialFlags::CodeMisc);
break;
case Opcode::guard_fun_:
- WriteItem(Pool::get(i.guard_fun_args.name), refTable, out);
- WriteItem(Pool::get(i.guard_fun_args.expected), refTable, out);
- OutInteger(out, i.guard_fun_args.id);
+ serializer.writeConst(i.guard_fun_args.name, SerialFlags::CodeMisc);
+ serializer.writeConst(i.guard_fun_args.expected, SerialFlags::CodeMisc);
+ serializer.writeBytesOf(i.guard_fun_args.id, SerialFlags::CodeMisc);
break;
case Opcode::call_:
case Opcode::call_dots_:
case Opcode::named_call_:
- OutInteger(out, i.callFixedArgs.nargs);
- WriteItem(Pool::get(i.callFixedArgs.ast), refTable, out);
- OutBytes(out, &i.callFixedArgs.given, sizeof(Context));
+ serializer.writeBytesOf(i.callFixedArgs.nargs, SerialFlags::CodeMisc);
+ serializer.writeConst(i.callFixedArgs.ast, SerialFlags::CodeMisc);
+ serializer.writeBytesOf(i.callFixedArgs.given.toI(), SerialFlags::CodeMisc);
// Write named arguments
if (*code == Opcode::named_call_ || *code == Opcode::call_dots_) {
- for (size_t j = 0; j < i.callFixedArgs.nargs; j++)
- WriteItem(Pool::get(bc.callExtra().callArgumentNames[j]),
- refTable, out);
+ for (size_t j = 0; j < i.callFixedArgs.nargs; j++) {
+ serializer.writeConst(bc.callExtra().callArgumentNames[j], SerialFlags::CodeMisc);
+ }
}
break;
case Opcode::call_builtin_:
- OutInteger(out, i.callBuiltinFixedArgs.nargs);
- WriteItem(Pool::get(i.callBuiltinFixedArgs.ast), refTable, out);
- WriteItem(Pool::get(i.callBuiltinFixedArgs.builtin), refTable, out);
+ serializer.writeBytesOf(i.callBuiltinFixedArgs.nargs, SerialFlags::CodeMisc);
+ serializer.writeConst(i.callBuiltinFixedArgs.ast, SerialFlags::CodeMisc);
+ serializer.writeConst(i.callBuiltinFixedArgs.builtin, SerialFlags::CodeMisc);
+ break;
+ case Opcode::mk_promise_:
+ case Opcode::mk_eager_promise_:
+ serializer.writeBytesOf(i.fun, SerialFlags::CodeMisc);
+ extraPoolFlags[i.fun] = SerialFlags::CodePromise;
break;
case Opcode::record_call_:
+ serializer.writeBytesOf(i.i, SerialFlags::CodeFeedback);
+ if (container->function()->body() == container) {
+ // The feedback itself is already serialized, but we also want to record which extra pool entries are part of it
+ auto feedback =
+ container->function()->typeFeedback()->callees(i.i);
+ // Don't hash because this is a recording instruction,
+ // but we also want to skip hashing recorded extra pool entries
+ for (size_t j = 0; j < feedback.numTargets; j++) {
+ extraPoolFlags[feedback.targets[j]] =
+ SerialFlags::CodeFeedback;
+ }
+ }
+ break;
+ case Opcode::record_type_:
+ case Opcode::record_test_:
+ serializer.writeBytesOf(i.i, SerialFlags::CodeFeedback);
+ break;
+ case Opcode::br_:
+ case Opcode::brtrue_:
+ case Opcode::beginloop_:
+ case Opcode::brfalse_:
+ case Opcode::popn_:
+ case Opcode::pick_:
+ case Opcode::pull_:
+ case Opcode::is_:
+ case Opcode::put_:
+ case Opcode::clear_binding_cache_:
+ assert((size - 1) % 4 == 0);
+ if (size > 1) {
+ serializer.writeBytes((void*)(code + 1), size - 1, SerialFlags::CodeMisc);
+ }
+ break;
+ case Opcode::invalid_:
+ case Opcode::num_of:
+ assert(false);
+ break;
+ }
+ size = bc.size();
+ assert(codeSize >= size);
+ code += size;
+ codeSize -= size;
+ }
+}
+
+void BC::hash(HasherOld& hasher, std::vector& extraPoolIgnored,
+ const Opcode* code, size_t codeSize, const Code* container) {
+ while (codeSize > 0) {
+ const BC bc = BC::decode((Opcode*)code, container);
+ hasher.hashBytesOf(*code);
+ unsigned size = BC::fixedSize(*code);
+ ImmediateArguments i = bc.immediate;
+ switch (*code) {
+#define V(NESTED, name, name_) case Opcode::name_##_:
+ BC_NOARGS(V, _)
+#undef V
+ assert(*code != Opcode::nop_);
+ break;
+ case Opcode::push_:
+ case Opcode::ldfun_:
+ case Opcode::ldddvar_:
+ case Opcode::ldvar_:
+ case Opcode::ldvar_noforce_:
+ case Opcode::ldvar_for_update_:
+ case Opcode::ldvar_super_:
+ case Opcode::stvar_:
+ case Opcode::stvar_super_:
+ case Opcode::missing_:
+ hasher.hashConstant(i.pool);
+ break;
+ case Opcode::ldvar_cached_:
+ case Opcode::ldvar_for_update_cache_:
+ case Opcode::stvar_cached_:
+ hasher.hashConstant(i.poolAndCache.poolIndex);
+ hasher.hashBytesOf(i.poolAndCache.cacheIndex);
+ break;
+ case Opcode::guard_fun_:
+ hasher.hashConstant(i.guard_fun_args.name);
+ hasher.hashConstant(i.guard_fun_args.expected);
+ hasher.hashBytesOf(i.guard_fun_args.id);
+ break;
+ case Opcode::call_:
+ case Opcode::call_dots_:
+ case Opcode::named_call_:
+ hasher.hashBytesOf(i.callFixedArgs.nargs);
+ hasher.hashConstant(i.callFixedArgs.ast);
+ hasher.hashBytesOf(i.callFixedArgs.given);
+ // Hash named arguments
+ if (*code == Opcode::named_call_ || *code == Opcode::call_dots_) {
+ for (size_t j = 0; j < i.callFixedArgs.nargs; j++) {
+ hasher.hashConstant(bc.callExtra().callArgumentNames[j]);
+ }
+ }
+ break;
+ case Opcode::call_builtin_:
+ hasher.hashBytesOf(i.callBuiltinFixedArgs.nargs);
+ hasher.hashConstant(i.callBuiltinFixedArgs.ast);
+ hasher.hashConstant(i.callBuiltinFixedArgs.builtin);
+ break;
+ case Opcode::record_call_: {
+ auto feedback = container->function()->typeFeedback();
+ if (i.i >= feedback->numCallees()) {
+ // TODO: Bug where, when we only send the compiler server the
+ // client source and feedback, we get record_call instructions
+ // with corrupt indices
+ std::cerr << "BC.cpp hash record_call_ index out of range\n";
+ break;
+ }
+ auto callees = feedback->callees(i.i);
+ if (container->function()->body() == container) {
+ // Don't hash because this is a recording instruction,
+ // but we also want to skip hashing recorded extra pool entries
+ for (size_t j = 0; j < callees.numTargets; j++) {
+ extraPoolIgnored[callees.targets[j]] = true;
+ }
+ }
+ break;
+ }
case Opcode::record_type_:
case Opcode::record_test_:
+ assert((size - 1) % 4 == 0);
+ // Don't hash because these are recording instructions
+ break;
case Opcode::mk_promise_:
case Opcode::mk_eager_promise_:
case Opcode::br_:
@@ -281,8 +400,9 @@ void BC::serialize(SEXP refTable, R_outpstream_t out, const Opcode* code,
case Opcode::put_:
case Opcode::clear_binding_cache_:
assert((size - 1) % 4 == 0);
- if (size != 0)
- OutBytes(out, code + 1, size - 1);
+ if (size > 1) {
+ hasher.hashBytes(code + 1, (int)size - 1);
+ }
break;
case Opcode::invalid_:
case Opcode::num_of:
@@ -290,18 +410,277 @@ void BC::serialize(SEXP refTable, R_outpstream_t out, const Opcode* code,
break;
}
size = bc.size();
-#ifdef DEBUG_SERIAL
- if (bc.bc == Opcode::deopt_) {
- std::cout << "serialized: ";
- bc.print(std::cout);
+ assert(codeSize >= size);
+ code += size;
+ codeSize -= size;
+ }
+}
+
+void BC::addConnected(std::vector& extraPoolChildren,
+ ConnectedCollectorOld& collector, const Opcode* code,
+ size_t codeSize, const Code* container) {
+ while (codeSize > 0) {
+ const BC bc = BC::decode((Opcode*)code, container);
+ unsigned size = BC::fixedSize(*code);
+ ImmediateArguments i = bc.immediate;
+ switch (*code) {
+#define V(NESTED, name, name_) case Opcode::name_##_:
+ BC_NOARGS(V, _)
+#undef V
+ assert(*code != Opcode::nop_);
+ break;
+ case Opcode::push_:
+ case Opcode::ldfun_:
+ case Opcode::ldddvar_:
+ case Opcode::ldvar_:
+ case Opcode::ldvar_noforce_:
+ case Opcode::ldvar_for_update_:
+ case Opcode::ldvar_super_:
+ case Opcode::stvar_:
+ case Opcode::stvar_super_:
+ case Opcode::missing_:
+ collector.addConstant(i.pool);
+ break;
+ case Opcode::ldvar_cached_:
+ case Opcode::ldvar_for_update_cache_:
+ case Opcode::stvar_cached_:
+ collector.addConstant(i.poolAndCache.poolIndex);
+ break;
+ case Opcode::guard_fun_:
+ collector.addConstant(i.guard_fun_args.name);
+ collector.addConstant(i.guard_fun_args.expected);
+ break;
+ case Opcode::call_:
+ case Opcode::call_dots_:
+ case Opcode::named_call_:
+ collector.addConstant(i.callFixedArgs.ast);
+ // Add named arguments
+ if (*code == Opcode::named_call_ || *code == Opcode::call_dots_) {
+ for (size_t j = 0; j < i.callFixedArgs.nargs; j++) {
+ collector.addConstant(bc.callExtra().callArgumentNames[j]);
+ }
+ }
+ break;
+ case Opcode::call_builtin_:
+ collector.addConstant(i.callBuiltinFixedArgs.ast);
+ collector.addConstant(i.callBuiltinFixedArgs.builtin);
+ break;
+ case Opcode::record_call_:
+ case Opcode::record_type_:
+ case Opcode::record_test_:
+ break;
+ case Opcode::mk_promise_:
+ case Opcode::mk_eager_promise_:
+ extraPoolChildren[i.fun] = true;
+ break;
+ case Opcode::br_:
+ case Opcode::brtrue_:
+ case Opcode::beginloop_:
+ case Opcode::brfalse_:
+ case Opcode::popn_:
+ case Opcode::pick_:
+ case Opcode::pull_:
+ case Opcode::is_:
+ case Opcode::put_:
+ case Opcode::clear_binding_cache_:
+ break;
+ case Opcode::invalid_:
+ case Opcode::num_of:
+ assert(false);
}
-#endif
+ size = bc.size();
assert(codeSize >= size);
code += size;
codeSize -= size;
}
}
+void BC::addToPrettyGraph(const PrettyGraphInnerPrinter& p,
+ std::vector& addedExtraPoolEntries,
+ const Opcode* code, size_t codeSize,
+ const Code* container) {
+ auto addEntry = [&](SEXP sexp, const char* type, PrettyGraphContentPrinter description){
+ bool isInPool = false;
+ for (unsigned i = 0; i < container->extraPoolSize; i++) {
+ if (sexp == container->getExtraPoolEntry(i)) {
+ addedExtraPoolEntries[i] = true;
+ isInPool = true;
+ }
+ }
+ if (TYPEOF(sexp) == EXTERNALSXP) {
+ p.addEdgeTo(sexp, false, type, description,
+ !isInPool);
+ }
+ };
+ auto addConstant = [&](PoolIdx idx, const char* type, PrettyGraphContentPrinter description = [](std::ostream& s){}){
+ addEntry(Pool::get(idx), type, description);
+ };
+ auto addExtraPoolEntry = [&](PoolIdx idx, bool isChild, const char* type, PrettyGraphContentPrinter description = [](std::ostream& s){}){
+ auto sexp = container->getExtraPoolEntry(idx);
+ addedExtraPoolEntries[idx] = true;
+ p.addEdgeTo(sexp, isChild, type, description);
+ };
+
+ while (codeSize > 0) {
+ const BC bc = BC::decode((Opcode*)code, container);
+ unsigned size = BC::fixedSize(*code);
+ ImmediateArguments i = bc.immediate;
+ switch (*code) {
+#define V(NESTED, name, name_) case Opcode::name_##_:
+ BC_NOARGS(V, _)
+#undef V
+ assert(*code != Opcode::nop_);
+ break;
+#define CONSTANT_CASE(op, accessor, type) case Opcode::op##_: \
+ addConstant(i.accessor, type); \
+ break;
+ CONSTANT_CASE(push, pool, "push")
+ CONSTANT_CASE(ldfun, pool, "unexpected-name") // NOLINT(*-branch-clone)
+ CONSTANT_CASE(ldddvar, pool, "unexpected-name")
+ CONSTANT_CASE(ldvar, pool, "unexpected-name")
+ CONSTANT_CASE(ldvar_noforce, pool, "unexpected-name")
+ CONSTANT_CASE(ldvar_for_update, pool, "unexpected-name")
+ CONSTANT_CASE(ldvar_super, pool, "unexpected-name")
+ CONSTANT_CASE(stvar, pool, "unexpected-name")
+ CONSTANT_CASE(stvar_super, pool, "unexpected-name")
+ CONSTANT_CASE(missing, pool, "unexpected-name")
+ CONSTANT_CASE(ldvar_cached, poolAndCache.poolIndex, "unexpected-name") // NOLINT(*-branch-clone)
+ CONSTANT_CASE(ldvar_for_update_cache, poolAndCache.poolIndex, "unexpected-name")
+ CONSTANT_CASE(stvar_cached, poolAndCache.poolIndex, "unexpected-name")
+ case Opcode::guard_fun_:
+ addConstant(i.guard_fun_args.name, "unexpected-name", [&](std::ostream& s){
+ s << "guard_fun";
+ });
+ addConstant(i.guard_fun_args.expected, "guard");
+ break;
+ case Opcode::call_:
+ case Opcode::call_dots_:
+ case Opcode::named_call_: {
+ auto callType =
+ *code == Opcode::call_ ? "call" :
+ *code == Opcode::call_dots_ ? "call_dots" :
+ "named_call";
+ addConstant(i.callFixedArgs.ast, "unexpected-ast", [&](std::ostream& s){
+ s << callType << " ast";
+ });
+ // Add named arguments
+ if (*code == Opcode::named_call_ || *code == Opcode::call_dots_) {
+ for (size_t j = 0; j < i.callFixedArgs.nargs; j++) {
+ addConstant(bc.callExtra().callArgumentNames[j], "unexpected-name", [&](std::ostream& s){
+ s << callType << " argument";
+ });
+ }
+ }
+ break;
+ }
+ case Opcode::call_builtin_:
+ addConstant(i.callBuiltinFixedArgs.ast, "unexpected-ast");
+ addConstant(i.callBuiltinFixedArgs.builtin, "unexpected-builtin");
+ break;
+ case Opcode::record_call_:
+ if (container->function()->body() == container) {
+ auto feedback =
+ container->function()->typeFeedback()->callees(i.i);
+ for (auto j = 0; j < feedback.numTargets; j++) {
+ addExtraPoolEntry(
+ feedback.targets[j], false, "target",
+ [&](std::ostream& s) { s << "record_call " << j; });
+ }
+ }
+ break;
+ case Opcode::record_type_:
+ case Opcode::record_test_:
+ break;
+ case Opcode::mk_promise_:
+ case Opcode::mk_eager_promise_:
+ addExtraPoolEntry(i.fun, true, "promise");
+ break;
+ case Opcode::br_:
+ case Opcode::brtrue_:
+ case Opcode::beginloop_:
+ case Opcode::brfalse_:
+ case Opcode::popn_:
+ case Opcode::pick_:
+ case Opcode::pull_:
+ case Opcode::is_:
+ case Opcode::put_:
+ case Opcode::clear_binding_cache_:
+ break;
+ case Opcode::invalid_:
+ case Opcode::num_of:
+ // TODO: mark extra pool entry and add edge for any other bytecodes
+ // which reference extra pool entries
+ assert(false);
+ break;
+ }
+ size = bc.size();
+ assert(codeSize >= size);
+ code += size;
+ codeSize -= size;
+ }
+}
+
+/// Compare bytecodes and print differences.
+void BC::debugCompare(const Opcode* code1, const Opcode* code2,
+ size_t codeSize1, size_t codeSize2,
+ const Code* container1, const Code* container2,
+ const char* prefix, std::stringstream& differences) {
+ auto loggedDifferences = false;
+ auto initialCodeSize1 = codeSize1;
+ while (codeSize1 > 0 && codeSize2 > 0) {
+ auto pc1 = (Opcode*)code1;
+ auto pc2 = (Opcode*)code2;
+ auto opcode1 = *pc1;
+ auto opcode2 = *pc2;
+ const BC bc1 = BC::decode(pc1, container1);
+ const BC bc2 = BC::decode(pc2, container2);
+ auto size1 = BC::fixedSize(opcode1);
+ auto size2 = BC::fixedSize(opcode2);
+ if (opcode1 != opcode2 || size1 != size2 ||
+ (memcmp(pc1, pc2, size1) != 0 &&
+ // For non-trivial SEXPs like environments, calls will push
+ // different values
+ opcode1 != Opcode::push_)) {
+ // Even if the bytecode data is different, it could just be different pool
+ // entries for equivalent SEXPs. So we check by printing the bytecode (not
+ // perfect, there's a slim chance of true negative, but good enough)
+ std::string associated1;
+ std::string associated2;
+ if (opcode1 == opcode2) {
+ std::stringstream associated1Stream;
+ bc1.printAssociatedData(associated1Stream, true);
+ std::stringstream associated2Stream;
+ bc2.printAssociatedData(associated2Stream, true);
+ associated1 = associated1Stream.str();
+ associated2 = associated2Stream.str();
+ }
+ if (opcode1 != opcode2 || associated1 != associated2) {
+ if (!loggedDifferences) {
+ differences << prefix << " bytecode differs, first at "
+ << initialCodeSize1 - codeSize1 << "\n"
+ << prefix << " bytecode:";
+ loggedDifferences = true;
+ }
+ differences << " ";
+ if (opcode1 == opcode2) {
+ differences << name(opcode1) << "(" << associated1 << ")|(" << associated2 << ")";
+ } else {
+ differences << name(opcode1) << "|" << name(opcode2);
+ }
+ }
+ }
+ size1 = bc1.size();
+ size2 = bc2.size();
+ code1 += size1;
+ code2 += size2;
+ codeSize1 -= size1;
+ codeSize2 -= size2;
+ }
+ if (loggedDifferences) {
+ differences << "\n";
+ }
+}
+
#pragma GCC diagnostic pop
void BC::printImmediateArgs(std::ostream& out) const {
@@ -331,10 +710,20 @@ void BC::printOpcode(std::ostream& out) const { out << name(bc) << " "; }
void BC::print(std::ostream& out) const {
out << " ";
- if (bc != Opcode::record_call_ && bc != Opcode::record_type_ &&
- bc != Opcode::record_test_)
- printOpcode(out);
+ printOpcode(out);
+ printAssociatedData(out);
+ out << "\n";
+}
+
+void BC::printAssociatedData(std::ostream& out, bool printDetailed) const {
+ auto printSexp = [&](SEXP s) {
+ if (printDetailed) {
+ printRirObject(s, out);
+ } else {
+ out << Print::dumpSexp(s);
+ }
+ };
switch (bc) {
case Opcode::invalid_:
case Opcode::num_of:
@@ -364,11 +753,12 @@ void BC::print(std::ostream& out) const {
auto args = immediate.callBuiltinFixedArgs;
BC::NumArgs nargs = args.nargs;
auto target = Pool::get(args.builtin);
- out << nargs << " : " << Print::dumpSexp(target);
+ out << nargs << " : ";
+ printSexp(target);
break;
}
case Opcode::push_:
- out << Print::dumpSexp(immediateConst());
+ printSexp(immediateConst());
break;
case Opcode::ldfun_:
case Opcode::ldvar_:
@@ -402,54 +792,11 @@ void BC::print(std::ostream& out) const {
case Opcode::is_:
out << (BC::RirTypecheck)immediate.i;
break;
- case Opcode::record_call_: {
- ObservedCallees prof = immediate.callFeedback;
- out << "[ ";
- if (prof.taken == ObservedCallees::CounterOverflow)
- out << "*, <";
- else
- out << prof.taken << ", <";
- if (prof.numTargets == ObservedCallees::MaxTargets)
- out << "*>, ";
- else
- out << prof.numTargets << ">, ";
-
- out << (prof.invalid ? "invalid" : "valid");
- out << (prof.numTargets ? ", " : " ");
-
- for (int i = 0; i < prof.numTargets; ++i)
- out << callFeedbackExtra().targets[i] << "("
- << Rf_type2char(TYPEOF(callFeedbackExtra().targets[i])) << ") ";
- out << "]";
- break;
- }
-
- case Opcode::record_test_: {
- out << "[ ";
- switch (immediate.testFeedback.seen) {
- case ObservedTest::None:
- out << "_";
- break;
- case ObservedTest::OnlyTrue:
- out << "T";
- break;
- case ObservedTest::OnlyFalse:
- out << "F";
- break;
- case ObservedTest::Both:
- out << "?";
- break;
- }
- out << " ]";
- break;
- }
-
- case Opcode::record_type_: {
- out << "[ ";
- immediate.typeFeedback.print(out);
- out << " ]";
+ case Opcode::record_test_:
+ case Opcode::record_type_:
+ case Opcode::record_call_:
+ out << "#" << immediate.i;
break;
- }
#define V(NESTED, name, name_) case Opcode::name_##_:
BC_NOARGS(V, _)
@@ -469,7 +816,6 @@ void BC::print(std::ostream& out) const {
out << immediate.cacheIdx.start << " " << immediate.cacheIdx.size;
break;
}
- out << "\n";
}
std::ostream& operator<<(std::ostream& out, BC::RirTypecheck t) {
diff --git a/rir/src/bc/BC.h b/rir/src/bc/BC.h
index d24169f2e..1bb6e23f7 100644
--- a/rir/src/bc/BC.h
+++ b/rir/src/bc/BC.h
@@ -23,11 +23,23 @@ class CodeStream;
BC_NOARGS(V, _)
#undef V
-BC BC::recordCall() { return BC(Opcode::record_call_); }
+BC BC::recordCall(uint32_t idx) {
+ ImmediateArguments i;
+ i.i = idx;
+ return BC(Opcode::record_call_, i);
+}
-BC BC::recordType() { return BC(Opcode::record_type_); }
+BC BC::recordType(uint32_t idx) {
+ ImmediateArguments i;
+ i.i = idx;
+ return BC(Opcode::record_type_, i);
+}
-BC BC::recordTest() { return BC(Opcode::record_test_); }
+BC BC::recordTest(uint32_t idx) {
+ ImmediateArguments i;
+ i.i = idx;
+ return BC(Opcode::record_test_, i);
+}
BC BC::asSwitchIdx() { return BC(Opcode::as_switch_idx_); }
diff --git a/rir/src/bc/BC_inc.h b/rir/src/bc/BC_inc.h
index 20653494d..e975229a7 100644
--- a/rir/src/bc/BC_inc.h
+++ b/rir/src/bc/BC_inc.h
@@ -7,6 +7,11 @@
#include "compiler/pir/type.h"
#include "runtime/Context.h"
#include "runtime/TypeFeedback.h"
+#include "runtime/log/printPrettyGraph.h"
+#include "serializeHash/hash/getConnectedOld.h"
+#include "serializeHash/hash/hashRootOld.h"
+#include "serializeHash/serializeUni.h"
+#include "utils/ByteBuffer.h"
#include
#include
@@ -59,6 +64,16 @@ enum class Opcode : uint8_t {
num_of
};
+struct ExtraPoolEntryRefInSrc {
+ enum Type : unsigned {
+ Promise,
+ ArbitrarySexp
+ };
+
+ unsigned idx;
+ Type type;
+};
+
// ============================================================
// ==== Creation and decoding of Bytecodes
//
@@ -152,9 +167,6 @@ class BC {
uint32_t i;
RirTypecheck typecheck;
NumLocals loc;
- ObservedCallees callFeedback;
- ObservedValues typeFeedback;
- ObservedTest testFeedback;
PoolAndCachePositionRange poolAndCache;
CachePositionRange cacheIdx;
ImmediateArguments() {
@@ -217,13 +229,32 @@ class BC {
// Used to serialize bc to CodeStream
void write(CodeStream& cs) const;
- static void deserialize(SEXP refTable, R_inpstream_t inp, Opcode* code,
+ static void deserialize(AbstractDeserializer& deserializer, Opcode* code,
size_t codeSize, Code* container);
- static void serialize(SEXP refTable, R_outpstream_t out, const Opcode* code,
- size_t codeSize, const Code* container);
+ static void serialize(AbstractSerializer& serializer,
+ std::vector& extraPoolFlags,
+ const Opcode* code, size_t codeSize,
+ const Code* container);
+ static void hash(HasherOld& hasher, std::vector& extraPoolIgnored,
+ const Opcode* code, size_t codeSize,
+ const Code* container);
+ static void addConnected(std::vector& extraPoolChildren,
+ ConnectedCollectorOld& collector, const Opcode* code,
+ size_t codeSize, const Code* container);
+ static void addToPrettyGraph(const PrettyGraphInnerPrinter& p,
+ std::vector& addedExtraPoolEntries,
+ const Opcode* code, size_t codeSize,
+ const Code* container);
+ /// Compare bytecodes and print differences.
+ static void debugCompare(const Opcode* code1, const Opcode* code2,
+ size_t codeSize1, size_t codeSize2,
+ const Code* container1, const Code* container2,
+ const char* prefix,
+ std::stringstream& differences);
// Print it to the stream passed as argument
void print(std::ostream& out) const;
+ void printAssociatedData(std::ostream& out, bool printDetailed = false) const;
void printImmediateArgs(std::ostream& out) const;
void printNames(std::ostream& out, const std::vector&) const;
void printProfile(std::ostream& out) const;
@@ -267,6 +298,11 @@ class BC {
bool isJmp() const { return isCondJmp() || isUncondJmp(); }
+ bool isRecord() const {
+ return bc == Opcode::record_call_ || bc == Opcode::record_test_ ||
+ bc == Opcode::record_type_;
+ }
+
bool isExit() const { return bc == Opcode::ret_ || bc == Opcode::return_; }
// This code performs the same as `BC::decode(pc).size()`, but for
@@ -309,10 +345,10 @@ class BC {
#define V(NESTED, name, name_) inline static BC name();
BC_NOARGS(V, _)
#undef V
- inline static BC recordCall();
+ inline static BC recordCall(uint32_t idx);
inline static BC recordBinop();
- inline static BC recordType();
- inline static BC recordTest();
+ inline static BC recordType(uint32_t idx);
+ inline static BC recordTest(uint32_t idx);
inline static BC asSwitchIdx();
inline static BC popn(unsigned n);
inline static BC push(SEXP constant);
@@ -406,14 +442,6 @@ class BC {
extraInformation.get());
}
- CallFeedbackExtraInformation& callFeedbackExtra() const {
- assert(bc == Opcode::record_call_ && "not a record call instruction");
- assert(extraInformation.get() &&
- "missing extra information. created through decodeShallow?");
- return *static_cast(
- extraInformation.get());
- }
-
private:
void allocExtraInformation() {
assert(extraInformation == nullptr);
@@ -428,10 +456,6 @@ class BC {
extraInformation.reset(new CallInstructionExtraInformation);
break;
}
- case Opcode::record_call_: {
- extraInformation.reset(new CallFeedbackExtraInformation);
- break;
- }
default: {
}
}
@@ -455,13 +479,6 @@ class BC {
break;
}
- case Opcode::record_call_: {
- // Read call target feedback from the extra pool
- for (size_t i = 0; i < immediate.callFeedback.numTargets; ++i)
- callFeedbackExtra().targets.push_back(
- immediate.callFeedback.getTarget(code, i));
- break;
- }
default: {
}
}
@@ -580,18 +597,10 @@ class BC {
case Opcode::pull_:
case Opcode::is_:
case Opcode::put_:
- memcpy(&immediate.i, pc, sizeof(immediate.i));
- break;
case Opcode::record_call_:
- memcpy(&immediate.callFeedback, pc, sizeof(ObservedCallees));
- break;
case Opcode::record_test_:
- memcpy(reinterpret_cast(&immediate.testFeedback), pc,
- sizeof(ObservedValues));
- break;
case Opcode::record_type_:
- memcpy(reinterpret_cast(&immediate.typeFeedback), pc,
- sizeof(ObservedValues));
+ memcpy(&immediate.i, pc, sizeof(immediate.i));
break;
#define V(NESTED, name, name_) case Opcode::name_##_:
BC_NOARGS(V, _)
diff --git a/rir/src/bc/CodeVerifier.cpp b/rir/src/bc/CodeVerifier.cpp
index 102068d82..93b2b3aa4 100644
--- a/rir/src/bc/CodeVerifier.cpp
+++ b/rir/src/bc/CodeVerifier.cpp
@@ -174,7 +174,7 @@ void CodeVerifier::verifyFunctionLayout(SEXP sexp) {
if (f->defaultArg(i))
objs.push_back(f->defaultArg(i));
- if (f->size > XLENGTH(sexp))
+ if (f->size > RAW_LENGTH(sexp))
Rf_error("RIR Verifier: Reported size must be smaller than the size of "
"the vector");
diff --git a/rir/src/bc/Compiler.cpp b/rir/src/bc/Compiler.cpp
index 2ad133d76..8efe1447e 100644
--- a/rir/src/bc/Compiler.cpp
+++ b/rir/src/bc/Compiler.cpp
@@ -3,6 +3,7 @@
#include "R/RList.h"
#include "R/Symbols.h"
#include "R/r.h"
+#include "Rinternals.h"
#include "bc/BC.h"
#include "bc/CodeStream.h"
#include "bc/CodeVerifier.h"
@@ -10,6 +11,7 @@
#include "interpreter/interp.h"
#include "interpreter/interp_incl.h"
#include "interpreter/safe_force.h"
+#include "runtime/TypeFeedback.h"
#include "simple_instruction_list.h"
#include "utils/Pool.h"
@@ -136,6 +138,7 @@ class CompilerContext {
FunctionWriter& fun;
Preserve& preserve;
+ TypeFeedback::Builder typeFeedbackBuilder;
CompilerContext(FunctionWriter& fun, Preserve& preserve)
: fun(fun), preserve(preserve) {}
@@ -201,6 +204,12 @@ class CompilerContext {
<< BC::callBuiltin(4, ast, getBuiltinFun("warning")) << BC::pop();
}
+ BC recordType() { return BC::recordType(typeFeedbackBuilder.addType()); }
+
+ BC recordCall() { return BC::recordCall(typeFeedbackBuilder.addCallee()); }
+
+ BC recordTest() { return BC::recordTest(typeFeedbackBuilder.addTest()); }
+
private:
unsigned int pushedPromiseContexts = 0;
};
@@ -257,7 +266,7 @@ void compileWhile(CompilerContext& ctx, std::function compileCond,
// loop peel is a copy of the condition and body, with no backwards jumps
if (Compiler::loopPeelingEnabled && peelLoop) {
compileCond();
- cs << BC::recordTest() << BC::brfalse(breakBranch);
+ cs << ctx.recordTest() << BC::brfalse(breakBranch);
compileBody();
}
@@ -348,7 +357,7 @@ bool compileSimpleFor(CompilerContext& ctx, SEXP fullAst, SEXP sym, SEXP seq,
// branch
cs << BC::pop();
} else {
- cs << BC::recordTest() << BC::brtrue(skipRegularForBranch);
+ cs << ctx.recordTest() << BC::brtrue(skipRegularForBranch);
//
// Note that we call the builtin `for` and pass the body as a
// promise to lower the bytecode size
@@ -378,7 +387,7 @@ bool compileSimpleFor(CompilerContext& ctx, SEXP fullAst, SEXP sym, SEXP seq,
if (voidContext)
cs << BC::pop();
else if (Compiler::profile)
- cs << BC::recordType();
+ cs << ctx.recordType();
cs << BC::br(endBranch);
cs << skipRegularForBranch;
@@ -386,16 +395,16 @@ bool compileSimpleFor(CompilerContext& ctx, SEXP fullAst, SEXP sym, SEXP seq,
// } else {
// m' <- colonCastLhs(m')
- cs << BC::swap() << BC::colonCastLhs() << BC::recordType()
+ cs << BC::swap() << BC::colonCastLhs() << ctx.recordType()
<< BC::ensureNamed() << BC::swap();
// n' <- colonCastRhs(m', n')
- cs << BC::colonCastRhs() << BC::ensureNamed() << BC::recordType();
+ cs << BC::colonCastRhs() << BC::ensureNamed() << ctx.recordType();
// step <- if (m' <= n') 1L else -1L
cs << BC::dup2() << BC::le();
cs.addSrc(R_NilValue);
- cs << BC::recordTest() << BC::brfalse(stepElseBranch) << BC::push(1)
+ cs << ctx.recordTest() << BC::brfalse(stepElseBranch) << BC::push(1)
<< BC::br(stepEndBranch) << stepElseBranch << BC::push(-1)
<< stepEndBranch;
@@ -486,7 +495,7 @@ bool compileSpecialCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args_,
Protect p(dt);
// Mark this as an inner function to prevent the optimizer from
// assuming a stable environment
- DispatchTable::check(dt)->baseline()->flags.set(
+ DispatchTable::check(dt)->baseline()->setFlag(
Function::InnerFunction);
assert(TYPEOF(dt) == EXTERNALSXP);
cs << BC::push(args[0]) << BC::push(dt) << BC::push(args[2])
@@ -539,7 +548,7 @@ bool compileSpecialCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args_,
if (voidContext)
cs << BC::pop();
else if (Compiler::profile)
- cs << BC::recordType();
+ cs << ctx.recordType();
return true;
}
@@ -794,7 +803,7 @@ bool compileSpecialCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args_,
}
if (Compiler::profile)
- cs << BC::recordType();
+ cs << ctx.recordType();
if (maybeChanges(target, *idx) ||
(dims > 1 && maybeChanges(target, *(idx + 1))) ||
@@ -943,7 +952,7 @@ bool compileSpecialCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args_,
cs << BC::ldfun(farrow_sym);
if (Compiler::profile)
- cs << BC::recordCall();
+ cs << ctx.recordCall();
// prepare x, yk, z as promises
LoadArgsResult load_arg_res;
@@ -1017,7 +1026,7 @@ bool compileSpecialCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args_,
// The return value, RHS, is TOS
cs << BC::invisible();
if (Compiler::profile) {
- cs << BC::recordType();
+ cs << ctx.recordType();
}
}
@@ -1157,7 +1166,7 @@ bool compileSpecialCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args_,
BC::Label contBranch = cs.mkLabel();
cs << BC::dup() << BC::is(BC::RirTypecheck::isNonObject)
- << BC::recordTest() << BC::brfalse(objBranch)
+ << ctx.recordTest() << BC::brfalse(objBranch)
<< BC::br(nonObjBranch);
cs << objBranch;
@@ -1198,7 +1207,7 @@ bool compileSpecialCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args_,
cs.addSrc(ast);
if (!voidContext) {
if (Compiler::profile)
- cs << BC::recordType();
+ cs << ctx.recordType();
cs << BC::visible();
} else {
cs << BC::pop();
@@ -1307,7 +1316,7 @@ bool compileSpecialCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args_,
cs.addSrc(R_NilValue);
if (record)
- cs << BC::recordTest();
+ cs << ctx.recordTest();
// If outside bound, branch, otherwise index into the vector
cs << BC::brtrue(breakBranch) << BC::pull(2) << BC::pull(1)
@@ -1493,14 +1502,14 @@ bool compileSpecialCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args_,
// !isVector(x)
cs << BC::dup() << BC::is(BC::RirTypecheck::isVector)
- << BC::recordTest() << BC::brtrue(vecArityBr);
+ << ctx.recordTest() << BC::brtrue(vecArityBr);
cs << BC::br(vecErrorBr);
// ... || LENGTH(x) != 1
cs << vecArityBr << BC::dup() << BC::length_() << BC::push(1)
<< BC::eq();
cs.addSrc(R_NilValue); // to make code verifier happy
- cs << BC::recordTest() << BC::brtrue(vecEContBr);
+ cs << ctx.recordTest() << BC::brtrue(vecEContBr);
cs << vecErrorBr;
ctx.emitError("EXPR must be a length 1 vector", ast);
@@ -1509,7 +1518,7 @@ bool compileSpecialCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args_,
cs << vecEContBr;
cs << BC::dup() << BC::is(BC::RirTypecheck::isFactor)
- << BC::recordTest() << BC::brfalse(facWContBr);
+ << ctx.recordTest() << BC::brfalse(facWContBr);
ctx.emitWarning("EXPR is a \"factor\", treated as integer.\n Consider "
"using 'switch(as.character( * ), ...)' instead.",
@@ -1522,14 +1531,14 @@ bool compileSpecialCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args_,
cs << BC::br(nilBr);
}
cs << BC::dup() << BC::is(BC::RirTypecheck::isSTRSXP)
- << BC::recordTest() << BC::brtrue(strBr);
+ << ctx.recordTest() << BC::brtrue(strBr);
cs << BC::asSwitchIdx();
// currently stack is [arg[0]] (converted to integer)
for (size_t i = 0; i < labels.size(); ++i) {
cs << BC::dup() << BC::push(Rf_ScalarInteger(i + 1)) << BC::eq();
cs.addSrc(R_NilValue); // call argument for builtin
- cs << BC::asbool() << BC::recordTest() << BC::brtrue(labels[i]);
+ cs << BC::asbool() << ctx.recordTest() << BC::brtrue(labels[i]);
}
cs << BC::br(nilBr) << strBr;
if (dupDflt) {
@@ -1543,14 +1552,14 @@ bool compileSpecialCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args_,
// BC::asbool to compare the cases.
cs << BC::dup()
<< BC::callBuiltin(1, R_NilValue, getBuiltinFun("is.na"))
- << BC::asbool() << BC::recordTest() << BC::brfalse(strNAContBr)
+ << BC::asbool() << ctx.recordTest() << BC::brfalse(strNAContBr)
<< BC::pop() << BC::push(Rf_mkString("NA")) << strNAContBr;
for (size_t i = 0; i < expressions.size(); ++i) {
for (auto& n : groups[i]) {
cs << BC::dup() << BC::push(n) << BC::eq();
cs.addSrc(R_NilValue); // call argument for builtin
- cs << BC::asbool() << BC::recordTest()
+ cs << BC::asbool() << ctx.recordTest()
<< BC::brtrue(groupLabels[i]);
}
}
@@ -1858,7 +1867,7 @@ void compileCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args,
theEnd = cs.mkLabel();
cs << BC::push(builtin) << BC::dup()
<< BC::ldvarNoForce(fun) << BC::identicalNoforce()
- << BC::recordTest() << BC::brtrue(eager);
+ << ctx.recordTest() << BC::brtrue(eager);
cs << BC::pop();
}
@@ -1872,7 +1881,7 @@ void compileCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args,
}
if (Compiler::profile)
- cs << BC::recordCall();
+ cs << ctx.recordCall();
auto compileCall = [&](LoadArgsResult& info) {
if (info.hasDots) {
@@ -1892,7 +1901,7 @@ void compileCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args,
compileLoadOneArg(ctx, args, ArgType::RAW_VALUE, info);
compileLoadOneArg(ctx, CDR(args), ArgType::RAW_VALUE, info);
if (Compiler::profile)
- cs << BC::recordCall();
+ cs << ctx.recordCall();
// Load the rest of the args
compileLoadArgs(ctx, ast, fun, args, info, voidContext, 2, 0);
} else {
@@ -1915,7 +1924,7 @@ void compileCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args,
if (voidContext)
cs << BC::pop();
else if (Compiler::profile)
- cs << BC::recordType();
+ cs << ctx.recordType();
}
// Lookup
@@ -1933,7 +1942,7 @@ void compileGetvar(CompilerContext& ctx, SEXP name) {
cs << BC::ldvar(name);
}
if (Compiler::profile)
- cs << BC::recordType();
+ cs << ctx.recordType();
}
}
@@ -2052,7 +2061,10 @@ SEXP Compiler::finalize() {
compileExpr(ctx, exp);
ctx.cs() << BC::ret();
Code* body = ctx.pop();
- function.finalize(body, signature, Context());
+ TypeFeedback* feedback = ctx.typeFeedbackBuilder.build();
+ PROTECT(feedback->container());
+ function.finalize(body, signature, Context(), feedback);
+ UNPROTECT(1);
#ifdef ENABLE_SLOWASSERT
CodeVerifier::verifyFunctionLayout(function.function()->container());
diff --git a/rir/src/bc/Compiler.h b/rir/src/bc/Compiler.h
index 6032b856e..3c5bc72c3 100644
--- a/rir/src/bc/Compiler.h
+++ b/rir/src/bc/Compiler.h
@@ -4,7 +4,10 @@
#include "R/Preserve.h"
#include "R/Protect.h"
#include "R/r.h"
+#include "compilerClientServer/CompilerClient.h"
+#include "compilerClientServer/CompilerServer.h"
#include "runtime/DispatchTable.h"
+#include "runtime/TypeFeedback.h"
#include "utils/FunctionWriter.h"
#include "utils/Pool.h"
@@ -93,6 +96,19 @@ class Compiler {
// Set the closure fields.
SET_BODY(inClosure, dt->container());
}
+
+ /// Takes a closure with a RIR body and returns a copy with same formals and
+ /// environment, but decompiled (AST) body
+ static SEXP decompileClosure(SEXP closure) {
+ assert(TYPEOF(closure) == CLOSXP && "not a closure");
+ auto dt = DispatchTable::check(BODY(closure));
+ assert(dt && "closure's body isn't a RIR dispatch table");
+ auto result = Rf_allocSExp(CLOSXP);
+ SET_FORMALS(result, FORMALS(closure));
+ SET_BODY(result, rirDecompile(BODY(closure)));
+ SET_CLOENV(result, CLOENV(closure));
+ return result;
+ }
};
} // namespace rir
diff --git a/rir/src/bc/insns.h b/rir/src/bc/insns.h
index dcdf864c0..a9bcc91ee 100644
--- a/rir/src/bc/insns.h
+++ b/rir/src/bc/insns.h
@@ -443,7 +443,7 @@ DEF_INSTR(ret_, 0, 1, 0)
* They keep a struct from RuntimeFeedback.h inline, that's why they are quite
* heavy in size.
*/
-DEF_INSTR(record_call_, 4, 1, 1)
+DEF_INSTR(record_call_, 1, 1, 1)
DEF_INSTR(record_type_, 1, 1, 1)
DEF_INSTR(record_test_, 1, 1, 1)
diff --git a/rir/src/common.h b/rir/src/common.h
index 51cd01b98..884b13c90 100644
--- a/rir/src/common.h
+++ b/rir/src/common.h
@@ -15,6 +15,9 @@ extern void printBacktrace();
{}
#endif
+#define REXPORT extern "C"
+#define ALWAYS_INLINE __attribute__((always_inline)) inline
+
// from boost
#include
template
diff --git a/rir/src/compiler/analysis/reference_count.h b/rir/src/compiler/analysis/reference_count.h
index 111ed9847..af1c465aa 100644
--- a/rir/src/compiler/analysis/reference_count.h
+++ b/rir/src/compiler/analysis/reference_count.h
@@ -5,6 +5,7 @@
#include "dead.h"
#include "generic_static_analysis.h"
#include "utils/Map.h"
+#include