diff --git a/.gitignore b/.gitignore index 2cd37a1d4..01d6a2c5b 100644 --- a/.gitignore +++ b/.gitignore @@ -36,5 +36,4 @@ benchmarks/ *.DS_Store external/* !external/custom-r -.history -profile +.history \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1b8c2948a..7d2cfb1af 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,6 +7,10 @@ stages: - Benchmark - Report +cache: + paths: + - apt-cache/ + variables: REBENCH_OPTIONS: "-df $CI_PROJECT_DIR/benchmarks.data -R" REBENCH_RUN: "/opt/rbenchmarking/Setup/run.sh /opt/rbenchmarking/rebench.conf /opt/rbenchmarking/Benchmarks /opt/rir/build/release" @@ -62,6 +66,8 @@ cpp_check: needs: - rir_container script: + - apt-get update + - DEBIAN_FRONTEND=noninteractive apt-get -o dir::cache::archives=apt-cache install -y -qq cppcheck - /opt/rir/tools/cppcheck # Run check-all in release mode (additionally check pir types) @@ -69,16 +75,15 @@ test_release_1: image: registry.gitlab.com/rirvm/rir_mirror:$CI_COMMIT_SHA variables: GIT_STRATEGY: none - PIR_INLINER_INLINE_UNLIKELY: 1 - PIR_MAX_INPUT_SIZE: 10000 - PIR_INLINER_MAX_SIZE: 10000 - PIR_LLVM_OPT_LEVEL: 1 + PIR_LLVM_OPT_LEVEL: 0 + PIR_OPT_LEVEL: 1 stage: Run tests needs: - rir_container except: - schedules script: + - /opt/rir/container/install-test-deps.sh - cd /opt/rir/build/release - RIR_CHECK_PIR_TYPES=1 bin/tests - RIR_CHECK_PIR_TYPES=1 bin/gnur-make-tests check-devel || $SAVE_LOGS @@ -93,16 +98,17 @@ test_release_2: image: registry.gitlab.com/rirvm/rir_mirror:$CI_COMMIT_SHA variables: GIT_STRATEGY: none - PIR_INLINER_INLINE_UNLIKELY: 1 - PIR_MAX_INPUT_SIZE: 5000 - PIR_INLINER_MAX_SIZE: 5000 - PIR_LLVM_OPT_LEVEL: 1 + PIR_LLVM_OPT_LEVEL: 0 + PIR_OPT_LEVEL: 0 + PIR_MAX_INPUT_SIZE: 4000 + PIR_WARMUP: 5 stage: Run tests needs: - rir_container except: - schedules script: + - /opt/rir/container/install-test-deps.sh - cd /opt/rir/build/release - bin/gnur-make-tests check-recommended || $SAVE_LOGS - ../../tools/check-gnur-make-tests-error @@ -123,6 +129,7 @@ tests_debug: except: - schedules script: + - /opt/rir/container/build-releaseassert.sh - cd /opt/rir/build/releaseassert - export UNSOUND_OPTS=off - R_ENABLE_JIT=0 ./bin/tests @@ -136,13 +143,16 @@ tests_debug2: image: registry.gitlab.com/rirvm/rir_mirror:$CI_COMMIT_SHA variables: GIT_STRATEGY: none + PIR_LLVM_OPT_LEVEL: 0 + PIR_OPT_LEVEL: 0 stage: Run tests needs: - rir_container except: - schedules script: - - cd /opt/rir/build/releaseassert + - /opt/rir/container/install-test-deps.sh + - cd /opt/rir/build/release - ./bin/gnur-make-tests check-devel || $SAVE_LOGS - ../../tools/check-gnur-make-tests-error artifacts: @@ -164,7 +174,12 @@ tests_fullverify: except: - schedules script: + - 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 -p /opt/rir/build/fullverifier - cd /opt/rir/build/fullverifier + - /opt/rir/tools/fetch-llvm.sh + - cmake -DCMAKE_BUILD_TYPE=fullverifier ../.. + - make -j6 - bin/tests # Test particular features, like deoptimization and serialization @@ -179,18 +194,15 @@ test_features_1: except: - schedules script: - - cd /opt/rir/build/releaseassert -# - PIR_ENABLE_PROFILER=1 ./bin/tests + - /opt/rir/container/install-test-deps.sh - cd /opt/rir/build/release - - PIR_DEOPT_CHAOS=1000 PIR_INLINER_MAX_INLINEE_SIZE=800 bin/gnur-make-tests check || $SAVE_LOGS - - PIR_WARMUP=2 PIR_DEOPT_CHAOS=400 ./bin/gnur-make-tests check || $SAVE_LOGS - - RIR_SERIALIZE_CHAOS=1 FAST_TESTS=1 ./bin/tests - - PIR_GLOBAL_SPECIALIZATION_LEVEL=0 ./bin/tests - - PIR_GLOBAL_SPECIALIZATION_LEVEL=1 ./bin/tests - - PIR_GLOBAL_SPECIALIZATION_LEVEL=2 ./bin/tests - - PIR_GLOBAL_SPECIALIZATION_LEVEL=3 ./bin/tests - - PIR_GLOBAL_SPECIALIZATION_LEVEL=4 ./bin/tests - - PIR_GLOBAL_SPECIALIZATION_LEVEL=5 ./bin/tests + - PIR_WARMUP=2 PIR_DEOPT_CHAOS=500 ./bin/gnur-make-tests check || $SAVE_LOGS + - PIR_GLOBAL_SPECIALIZATION_LEVEL=0 FAST_TESTS=1 ./bin/tests + - PIR_GLOBAL_SPECIALIZATION_LEVEL=1 FAST_TESTS=1 ./bin/tests + - PIR_GLOBAL_SPECIALIZATION_LEVEL=2 FAST_TESTS=1 ./bin/tests + - PIR_GLOBAL_SPECIALIZATION_LEVEL=3 FAST_TESTS=1 ./bin/tests + - PIR_GLOBAL_SPECIALIZATION_LEVEL=4 FAST_TESTS=1 ./bin/tests + - PIR_GLOBAL_SPECIALIZATION_LEVEL=5 FAST_TESTS=1 ./bin/tests artifacts: paths: - logs @@ -208,6 +220,7 @@ test_features_2: except: - schedules script: + - /opt/rir/container/install-test-deps.sh - cd /opt/rir/build/release - ./bin/gnur-make-tests check || $SAVE_LOGS artifacts: @@ -216,8 +229,31 @@ test_features_2: when: on_failure expire_in: 1 week +test_features_3: + image: registry.gitlab.com/rirvm/rir_mirror:$CI_COMMIT_SHA + variables: + GIT_STRATEGY: none + PIR_LLVM_OPT_LEVEL: 0 + PIR_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_DEOPT_CHAOS=1000 bin/gnur-make-tests check || $SAVE_LOGS + - RIR_SERIALIZE_CHAOS=1 FAST_TESTS=1 ./bin/tests + artifacts: + paths: + - logs + when: on_failure + expire_in: 1 week + + # Run ubsan and gc torture -test_sanitize1: +test_gctorture1: image: registry.gitlab.com/rirvm/rir_mirror:$CI_COMMIT_SHA variables: GIT_STRATEGY: none @@ -228,7 +264,8 @@ test_sanitize1: except: - schedules script: - - cd /opt/rir/build/releaseassert + - /opt/rir/container/install-test-deps.sh + - cd /opt/rir/build/release - R_GCTORTURE=5000 ./bin/gnur-make-tests check || $SAVE_LOGS artifacts: paths: @@ -236,18 +273,23 @@ test_sanitize1: when: on_failure expire_in: 1 week -test_sanitize2: +test_gctorture2: 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: + - cd /opt/rir/build/release + - PIR_INLINER_MAX_INLINEE_SIZE=2000 ./bin/tests + - PIR_INLINER_MAX_INLINEE_SIZE=1500 PIR_DEOPT_CHAOS=100 ./bin/tests + - /opt/rir/container/build-releaseassert.sh - cd /opt/rir/build/releaseassert - - PIR_TEST_CLEAR_TEMPS=1 R_GCTORTURE=50 bin/tests + - PIR_TEST_CLEAR_TEMPS=1 R_GCTORTURE=60 bin/tests # Test with inlining big functions test_big_inline: @@ -256,6 +298,8 @@ test_big_inline: GIT_STRATEGY: none PIR_MAX_INPUT_SIZE: 5000 PIR_INLINER_MAX_SIZE: 5000 + PIR_INLINER_INLINE_UNLIKELY: 1 + PIR_INLINER_MAX_INLINEE_SIZE: 400 PIR_LLVM_OPT_LEVEL: 0 stage: Run tests needs: @@ -263,16 +307,37 @@ test_big_inline: except: - schedules script: + - /opt/rir/container/install-test-deps.sh - cd /opt/rir/build/release - - PIR_INLINER_MAX_INLINEE_SIZE=2000 ./bin/tests - - PIR_INLINER_MAX_INLINEE_SIZE=1500 PIR_DEOPT_CHAOS=100 ./bin/tests - - PIR_INLINER_MAX_INLINEE_SIZE=400 PIR_INLINER_INLINE_UNLIKELY=1 ./bin/gnur-make-tests check || $SAVE_LOGS + - ./bin/gnur-make-tests check || $SAVE_LOGS artifacts: paths: - logs when: on_failure expire_in: 1 week +# Test with asan and ubsan +test_sanitize: + image: registry.gitlab.com/rirvm/rir_mirror:$CI_COMMIT_SHA + variables: + GIT_STRATEGY: none + stage: Run tests + needs: + - rir_container + except: + - schedules + script: + - 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 ../.. + - make -j6 + # R_LD_PRELOAD is a feature of the test-runner. To repro this without the testrunner use LD_PRELOAD instead. + - 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 + # sometimes leak sanitizer segfaults + retry: 1 + # Test the benchmarks container before deploying test_benchmarks: image: registry.gitlab.com/rirvm/rir_mirror/benchmark:$CI_COMMIT_SHA @@ -342,7 +407,7 @@ benchmark_llvm: paths: - benchmarks.data expire_in: 24 month - retry: 2 + retry: 1 benchmark_gnur: image: registry.gitlab.com/rirvm/rir_mirror/benchmark-baseline diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 72a711554..e5fdff7a8 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -8,12 +8,15 @@ "${workspaceFolder}/external/llvm-11/include/**", "${workspaceFolder}/external/llvm-11.0.1.src/include/**" ], - "defines": ["ENABLE_SLOWASSERT"], + "defines": [ + "ENABLE_SLOWASSERT" + ], "compilerPath": "/usr/bin/g++-8", "cStandard": "c11", "cppStandard": "c++14", - "intelliSenseMode": "gcc-x64" + "intelliSenseMode": "gcc-x64", + "compileCommands": "${workspaceFolder}/build/debug/compile_commands.json" } ], "version": 4 -} +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index fedc801cb..3c989f58d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -112,8 +112,6 @@ "cfenv": "cpp", "csignal": "cpp", "__functional_base_03": "cpp", - "__memory": "cpp", - "typeindex": "cpp", - "variant": "cpp" + "__memory": "cpp" } } diff --git a/.ycm_extra_conf.py b/.ycm_extra_conf.py index f10161b9a..c4a555d06 100644 --- a/.ycm_extra_conf.py +++ b/.ycm_extra_conf.py @@ -14,8 +14,8 @@ def Settings( **kwargs ): '-DENABLE_SLOWASSERT', '-I'+DirectoryOfThisScript()+'/rir/src', '-isystem'+DirectoryOfThisScript()+'/external/custom-r/include', - '-isystem'+DirectoryOfThisScript()+'/external/llvm-11/include', - '-isystem'+DirectoryOfThisScript()+'/external/llvm-11.0.1.src/include', + '-isystem'+DirectoryOfThisScript()+'/external/llvm-12/include', + '-isystem'+DirectoryOfThisScript()+'/external/llvm-12.0.0.src/include', '-Wall', '-Wuninitialized', '-Wundef', diff --git a/CMakeLists.txt b/CMakeLists.txt index 10c6606b9..2ce810c25 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ set(R_HOME ${CMAKE_SOURCE_DIR}/external/custom-r) 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-11) +set(LLVM_DIR ${CMAKE_SOURCE_DIR}/external/llvm-12) set(R_COMMAND ${R_HOME}/bin/R) @@ -54,8 +54,8 @@ set(CMAKE_C_FLAGS_DEBUG "-O0 -DENABLE_SLOWASSERT") set(CMAKE_C_FLAGS_DEBUGOPT "-Og -DENABLE_SLOWASSERT") set(CMAKE_C_FLAGS "-std=gnu99") -set(CMAKE_CXX_FLAGS_SANITIZE "${CMAKE_CXX_FLAGS_DEBUGOPT} -fsanitize=undefined -fno-sanitize=alignment -fsanitize-undefined-trap-on-error -fstack-protector") -set(CMAKE_C_FLAGS_SANITIZE "${CMAKE_C_FLAGS_DEBUGOPT} -fsanitize=undefined -fno-sanitize=alignment -fsanitize-undefined-trap-on-error -fstack-protector") +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") MARK_AS_ADVANCED( CMAKE_CXX_FLAGS_SANITIZE @@ -119,11 +119,13 @@ add_custom_target(setup-build-dir if(${MACOS_USE_GCC_9}) add_custom_target(dependencies - COMMAND ${CMAKE_SOURCE_DIR}/tools/sync.sh --macos_gcc9 + 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/sync.sh + COMMAND ${CMAKE_SOURCE_DIR}/tools/build-gnur.sh + COMMAND ${CMAKE_SOURCE_DIR}/tools/fetch-llvm.sh ) endif() diff --git a/Dockerfile b/Dockerfile index b77f88c95..29cff8ef0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,20 @@ -FROM registry.gitlab.com/rirvm/rir_mirror/base +FROM ubuntu:20.04 ARG CI_COMMIT_SHA ADD . /opt/rir -RUN echo $CI_COMMIT_SHA > /opt/rir_version && apt-get update && apt-get install -y lsb-release -RUN cd /opt/rir && tools/sync.sh && tools/build-gnur.sh custom-r && rm -rf external/custom-r/cache_recommended.tar .git && find external -type f -name '*.o' -exec rm -f {} \; &&\ - mkdir -p /opt/rir/build/release && cd /opt/rir/build/release && cmake -DCMAKE_BUILD_TYPE=release -GNinja ../.. && ninja && bin/tests && \ - mkdir -p /opt/rir/build/fullverifier && cd /opt/rir/build/fullverifier && cmake -DCMAKE_BUILD_TYPE=fullverifier -GNinja ../.. && ninja && \ - mkdir -p /opt/rir/build/releaseassert && cd /opt/rir/build/releaseassert && cmake -DCMAKE_BUILD_TYPE=releaseslowassert -GNinja ../.. && ninja && \ - rm -rf /opt/rir/external/libjit /opt/rir/external/llvm-11 /opt/rir/external/clang+llvm-* /opt/rir/external/*.tar.xz /opt/rir/build/*/CMakeFiles /opt/rir/external/custom-r/src/main +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 && \ + 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 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) && \ + tools/fetch-llvm.sh && \ + cd /opt/rir/build/release && \ + cmake -DCMAKE_BUILD_TYPE=release ../.. && \ + make -j8 && \ + rm -rf CMakeFiles /opt/rir/external/clang+llvm* diff --git a/container/base/Dockerfile b/container/base/Dockerfile deleted file mode 100644 index 2e0f91415..000000000 --- a/container/base/Dockerfile +++ /dev/null @@ -1,2 +0,0 @@ -FROM ubuntu:20.04 -RUN apt-get update -yq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq git libcurl4-openssl-dev texlive-latex-extra texlive-latex-base texlive-fonts-recommended texlive-fonts-extra texlive-latex-recommended texlive-font-utils dvipng cm-super bison ca-certificates-java java-common libbison-dev libcairo-script-interpreter2 libcairo2-dev libjbig-dev libmime-charset-perl libpango1.0-dev libpcsclite1 libpixman-1-dev libsombok3 libtext-unidecode-perl libtiff5-dev libtiffxx5 libunicode-linebreak-perl libxcb-render0-dev libxcb-shm0-dev libxml-libxml-perl libxml-namespacesupport-perl libxml-sax-base-perl libxml-sax-perl mpack openjdk-14-jdk-headless texinfo g++ xdg-utils gfortran subversion make r-base-dev liblzma-dev sed binutils curl cmake rsync xorg-dev valgrind cppcheck xvfb xauth xfonts-base tk-dev ninja-build python3-pip flex bison make automake libgfortran5 sudo && apt-get clean all diff --git a/container/base/README.md b/container/base/README.md deleted file mode 100644 index 148ed831a..000000000 --- a/container/base/README.md +++ /dev/null @@ -1,15 +0,0 @@ -## What - -This Dockerfile builds the base container with all dependencies preinstalled to compile and run R. - -## How - -To update the container at "registry.gitlab.com/rirvm/rir_mirror/base" - -First - - docker login registry.gitlab.com - -then just - - ./update.sh diff --git a/container/base/update.sh b/container/base/update.sh deleted file mode 100755 index ef015cf9a..000000000 --- a/container/base/update.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -docker build -t registry.gitlab.com/rirvm/rir_mirror/base . -docker push registry.gitlab.com/rirvm/rir_mirror/base diff --git a/container/benchmark-baseline/Dockerfile b/container/benchmark-baseline/Dockerfile index 708ffe1ec..1b028d944 100644 --- a/container/benchmark-baseline/Dockerfile +++ b/container/benchmark-baseline/Dockerfile @@ -1,10 +1,19 @@ -FROM registry.gitlab.com/rirvm/rir_mirror/base - +FROM ubuntu:20.04 ARG GRAAL_VERSION=20.3.0 -RUN git clone --depth 1 https://github.com/smarr/ReBench.git /opt/ReBench && cd /opt/ReBench && pip3 install . -# this thing does not work... -RUN mv /usr/local/bin/rebench-denoise /usr/local/bin/rebench-denoise.bkp && cp /usr/bin/false /usr/local/bin/rebench-denoise -RUN git clone --depth 10 https://github.com/reactorlabs/rbenchmarking /opt/rbenchmarking && cd /opt/rbenchmarking && git checkout 88a76410dc70eeff8c9a0cbddaeb472a01510e4f -RUN git clone --recursive https://github.com/reactorlabs/rir /opt/rir && cd /opt/rir && tools/sync.sh && git -C external/custom-r checkout R-3-6-2-branch && tools/build-gnur.sh custom-r && rm -rf custom-r/cache_recommended.tar custom-r/src .git -RUN curl --fail --silent --location --retry 3 https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-$GRAAL_VERSION/graalvm-ce-java11-linux-amd64-$GRAAL_VERSION.tar.gz | gunzip | tar x -C /opt/ && cd /opt && ln -s graalvm-ce-java11-$GRAAL_VERSION graal && cd /opt/graal/bin && ./gu install R +RUN 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 python3-pip sudo && \ + git clone --recursive https://github.com/reactorlabs/rir /opt/rir && cd /opt/rir && \ + GNUR_BRANCH=R-3-6-2-branch tools/build-gnur.sh && \ + rm -rf .git && \ + find external -type f -name '*.o' -exec rm -f {} \; && \ + find external -type f -name '*.tar.gz' -exec rm -f {} \; && \ + find external -type f -name '*.tar.xz' -exec rm -f {} \; && \ + curl --fail --silent --location --retry 3 https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-$GRAAL_VERSION/graalvm-ce-java11-linux-amd64-$GRAAL_VERSION.tar.gz | gunzip | tar x -C /opt/ && \ + cd /opt && ln -s graalvm-ce-java11-$GRAAL_VERSION graal && cd /opt/graal/bin && \ + ./gu install R && \ + git clone --depth 1 https://github.com/smarr/ReBench.git /opt/ReBench && cd /opt/ReBench && pip3 install . && \ + mv /usr/local/bin/rebench-denoise /usr/local/bin/rebench-denoise.bkp && cp /usr/bin/false /usr/local/bin/rebench-denoise && \ + git clone --depth 10 https://github.com/reactorlabs/rbenchmarking /opt/rbenchmarking && cd /opt/rbenchmarking && git checkout 5977a8ab19d193eb4be262cdcd3ba375e5d436fd && \ + apt-get clean && rm -rf /var/cache/apt/lists diff --git a/container/benchmark/Dockerfile b/container/benchmark/Dockerfile index f192d1f2d..45803a608 100644 --- a/container/benchmark/Dockerfile +++ b/container/benchmark/Dockerfile @@ -1,6 +1,8 @@ ARG CI_COMMIT_SHA FROM registry.gitlab.com/rirvm/rir_mirror:$CI_COMMIT_SHA -RUN git clone --depth 1 https://github.com/smarr/ReBench.git /opt/ReBench && cd /opt/ReBench && pip3 install . -# this thing does not work... -RUN mv /usr/local/bin/rebench-denoise /usr/local/bin/rebench-denoise.bkp && cp /usr/bin/false /usr/local/bin/rebench-denoise -RUN git clone --depth 10 https://github.com/reactorlabs/rbenchmarking /opt/rbenchmarking && cd /opt/rbenchmarking && git checkout 88a76410dc70eeff8c9a0cbddaeb472a01510e4f +RUN apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y -qq python3-pip sudo && \ + apt-get clean && rm -rf /var/cache/apt/lists && \ + git clone --depth 1 https://github.com/smarr/ReBench.git /opt/ReBench && cd /opt/ReBench && pip3 install . && \ + mv /usr/local/bin/rebench-denoise /usr/local/bin/rebench-denoise.bkp && cp /usr/bin/false /usr/local/bin/rebench-denoise && \ + git clone --depth 10 https://github.com/reactorlabs/rbenchmarking /opt/rbenchmarking && cd /opt/rbenchmarking && git checkout 5977a8ab19d193eb4be262cdcd3ba375e5d436fd diff --git a/container/build-releaseassert.sh b/container/build-releaseassert.sh new file mode 100755 index 000000000..00e6cd518 --- /dev/null +++ b/container/build-releaseassert.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +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/releaseassert +cd /opt/rir/build/releaseassert +cmake -DCMAKE_BUILD_TYPE=RELEASESLOWASSERT ../.. && make -j6 diff --git a/container/install-test-deps.sh b/container/install-test-deps.sh new file mode 100755 index 000000000..bf71fb664 --- /dev/null +++ b/container/install-test-deps.sh @@ -0,0 +1,3 @@ +#!/bin/sh +apt-get update +DEBIAN_FRONTEND=noninteractive apt-get -o dir::cache::archives=apt-cache install -y -qq texlive-latex-base xvfb texlive-fonts-extra tcl tk diff --git a/documentation/debugging.md b/documentation/debugging.md index 65a0e5146..8951892b5 100644 --- a/documentation/debugging.md +++ b/documentation/debugging.md @@ -388,3 +388,11 @@ Overhead Command Shared Object Symbol ``` See [https://lists.llvm.org/pipermail/llvm-dev/2019-January/129160.html](https://lists.llvm.org/pipermail/llvm-dev/2019-January/129160.html) + +# Debug Ř using rr inside Docker + +In order to use rr inside a docker container, it is necessary to run it with some security capabilites: + +`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). diff --git a/external/custom-r b/external/custom-r index b41208a55..b2d56f214 160000 --- a/external/custom-r +++ b/external/custom-r @@ -1 +1 @@ -Subproject commit b41208a5541905a6977bbc3c03b2bc4ba8c8d03c +Subproject commit b2d56f2141866bb74cb534edc6667fc1289028f5 diff --git a/rir/src/R/symbol_list.h b/rir/src/R/symbol_list.h index 7d88807fc..65c41c07e 100644 --- a/rir/src/R/symbol_list.h +++ b/rir/src/R/symbol_list.h @@ -59,6 +59,7 @@ V(seq, "seq") \ V(lapply, "lapply") \ V(aslist, "as.list") \ + V(ascharacter, "as.character") \ V(isvector, "is.vector") \ V(substr, "substr") \ V(Class, "class") \ @@ -86,6 +87,8 @@ V(all, "all") \ V(FUN, "FUN") \ V(forceAndCall, "forceAndCall") \ + V(remove, "remove") \ + V(rm, "rm") \ V(Recall, "Recall") #endif // SYMBOLS_LIST_H_ diff --git a/rir/src/api.cpp b/rir/src/api.cpp index 5a6e92a5c..18ae443c0 100644 --- a/rir/src/api.cpp +++ b/rir/src/api.cpp @@ -9,23 +9,22 @@ #include "compiler/compiler.h" #include "compiler/log/debug.h" #include "compiler/parameter.h" +#include "compiler/pir/closure.h" #include "compiler/test/PirCheck.h" #include "compiler/test/PirTests.h" #include "interpreter/interp_incl.h" #include "ir/BC.h" #include "ir/Compiler.h" #include "utils/ContextualProfiling.h" - +#include "utils/CompilationStrategy.h" #include +#include #include #include #include #include -#include -#include - using namespace rir; extern "C" Rboolean R_Visible; @@ -304,43 +303,79 @@ SEXP pirCompile(SEXP what, const Context& assumptions, const std::string& name, // compile to pir pir::Module* m = new pir::Module; pir::StreamLogger logger(debug); - logger.title("Compiling " + name); - pir::Compiler cmp(m, logger); - pir::Backend backend(logger, name); - auto start = std::chrono::system_clock::now(); - std::chrono::duration duration; - - cmp.compileClosure(what, name, assumptions, true, - [&](pir::ClosureVersion* c) { - logger.flush(); - cmp.optimizeModule(); - - auto fun = backend.getOrCompile(c); - // Some computation here - auto end = std::chrono::system_clock::now(); - duration = end - start; + std::string const version_name = name + assumptions.getShortStringRepr(); + logger.title("Compiling " + version_name); + CompilationStrategy::markAsCompiled(what, assumptions); - ContextualProfiling::countSuccessfulCompilation(what,assumptions,duration); - - // Install - if (dryRun) - return; + auto t_compilation_start = std::chrono::steady_clock::now(); + pir::Compiler cmp(m, logger); + pir::Backend backend(logger, name); + auto compile = [&](pir::ClosureVersion* c) { + logger.flush(); + cmp.optimizeModule(); + + auto compilation_end_t = std::chrono::steady_clock::now(); + std::chrono::duration cmp_dt_ms = compilation_end_t - t_compilation_start; + ContextualProfiling::countCompilation(what, assumptions, true, cmp_dt_ms.count()); + + if (dryRun) + return; + + rir::Function* done = nullptr; + 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; + }; + 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())) { + auto other = dt->dispatch(c->context()); + 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(); + }; - Protect p(fun->container()); - DispatchTable::unpack(BODY(what))->insert(fun); - }, + bool successful_cmp = true; + cmp.compileClosure(what, name, assumptions, true, compile, [&]() { - auto end = std::chrono::system_clock::now(); - - duration = end - start; - ContextualProfiling::countFailedCompilation(what,assumptions,duration); + successful_cmp = false; if (debug.includes(pir::DebugFlag::ShowWarnings)) std::cerr << "Compilation failed\n"; }, {}); + auto compilation_end_t = std::chrono::steady_clock::now(); + std::chrono::duration cmp_dt_ms = compilation_end_t - t_compilation_start; + ContextualProfiling::countCompilation(what, assumptions, successful_cmp, cmp_dt_ms.count()); + + { + std::stringstream msg; + msg << "Done compiling " << version_name << " (" << + cmp_dt_ms.count() << "ms)"; + logger.title(msg.str()); + } + delete m; UNPROTECT(1); return what; @@ -385,6 +420,11 @@ REXPORT SEXP pirCompileWrapper(SEXP what, SEXP name, SEXP debugFlags, } REXPORT SEXP pirTests() { + if (pir::Parameter::PIR_OPT_LEVEL < 2) { + Rf_warning("pirCheck only runs with opt level 2"); + return R_FalseValue; + } + PirTests::run(); return R_NilValue; } diff --git a/rir/src/compiler/analysis/abstract_value.cpp b/rir/src/compiler/analysis/abstract_value.cpp index 44cf8dac7..c40d6479d 100644 --- a/rir/src/compiler/analysis/abstract_value.cpp +++ b/rir/src/compiler/analysis/abstract_value.cpp @@ -39,8 +39,8 @@ void AbstractREnvironment::print(std::ostream& out, bool tty) const { if (!reachableEnvs.empty()) { out << "- reachable: "; for (auto r : reachableEnvs) { - r->printRef(std::cout); - std::cout << " "; + r->printRef(out); + out << " "; } out << "\n"; } diff --git a/rir/src/compiler/analysis/abstract_value.h b/rir/src/compiler/analysis/abstract_value.h index 663159e52..8091c687d 100644 --- a/rir/src/compiler/analysis/abstract_value.h +++ b/rir/src/compiler/analysis/abstract_value.h @@ -18,7 +18,7 @@ namespace pir { struct ValOrig { Value* val; Instruction* origin; - unsigned recursionLevel; + uint8_t recursionLevel; ValOrig(Value* v, Instruction* o, unsigned recursionLevel) : val(v), origin(o), recursionLevel(recursionLevel) {} @@ -63,8 +63,8 @@ namespace pir { */ struct AbstractPirValue { private: - bool unknown = false; SmallSet vals; + uint8_t unknown = false; constexpr static size_t MAX_VALS = 5; public: @@ -359,18 +359,22 @@ class AbstractREnvironmentHierarchy { return res; } - bool known(Value* env) const { return envs.contains(env); } + bool known(Value* env) const { + if (aliases.count(env)) + return known(aliases.at(env)); + return envs.contains(env); + } const AbstractREnvironment& at(Value* env) const { if (aliases.count(env)) - return envs.at(aliases.at(env)); + return at(aliases.at(env)); else return envs.at(env); } AbstractREnvironment& at(Value* env) { if (aliases.count(env)) - return envs[aliases.at(env)]; + return at(aliases.at(env)); else return envs[env]; } diff --git a/rir/src/compiler/analysis/available_checkpoints.h b/rir/src/compiler/analysis/available_checkpoints.h index a513c9918..22ce2bef7 100644 --- a/rir/src/compiler/analysis/available_checkpoints.h +++ b/rir/src/compiler/analysis/available_checkpoints.h @@ -37,9 +37,7 @@ class FwdAvailableCheckpoints return AvailableCheckpointsApply::apply(state, i); } - Checkpoint* reaching(Instruction* i) const { - return StaticAnalysis::at(i).get(); - } + Checkpoint* reaching(Instruction* i) const { return before(i).get(); } }; class RwdAvailableCheckpoints @@ -53,12 +51,8 @@ class RwdAvailableCheckpoints return AvailableCheckpointsApply::apply(state, i); } - Checkpoint* reachingThrough(Instruction* i) const { - return StaticAnalysis::at(i).get(); - } - Checkpoint* reaching(Instruction* i) const { - return StaticAnalysis::at(i).get(); - } + Checkpoint* reachingThrough(Instruction* i) const { return after(i).get(); } + Checkpoint* reaching(Instruction* i) const { return before(i).get(); } }; class AvailableCheckpoints { diff --git a/rir/src/compiler/analysis/dead_store.h b/rir/src/compiler/analysis/dead_store.h index 01711f093..8e0f9807b 100644 --- a/rir/src/compiler/analysis/dead_store.h +++ b/rir/src/compiler/analysis/dead_store.h @@ -72,9 +72,7 @@ class DeadStoreAnalysis { : StaticAnalysis("envLeak", cls, code, initialState, NULL, log), promEnv(promEnv) {} - EnvSet leakedWhile(Instruction* i) const { - return at(i); - } + EnvSet leakedWhile(Instruction* i) const { return after(i); } protected: AbstractResult apply(EnvSet& state, Instruction* i) const override { @@ -361,7 +359,7 @@ class DeadStoreAnalysis { public: bool isObserved(StVar* st) const { - auto state = at(st); + auto state = before(st); auto e = resolveEnv(st->env()); Variable var({st->varName, e}); if (state.ignoreStore.count(var)) @@ -373,7 +371,7 @@ class DeadStoreAnalysis { } bool isObservedOnlyByDeopt(StVar* st) const { - auto state = at(st); + auto state = before(st); auto e = resolveEnv(st->env()); Variable var({st->varName, e}); assert(!(state.completelyObserved.count(e) && @@ -385,7 +383,7 @@ class DeadStoreAnalysis { std::unordered_set observedByDeoptInstructions(StVar* st) const { - auto state = at(st); + auto state = before(st); auto e = resolveEnv(st->env()); assert(state.observedByDeopt.count(e)); return state.observedByDeopt.at(e); diff --git a/rir/src/compiler/analysis/generic_static_analysis.h b/rir/src/compiler/analysis/generic_static_analysis.h index dad863f5f..6f6c6c9b3 100644 --- a/rir/src/compiler/analysis/generic_static_analysis.h +++ b/rir/src/compiler/analysis/generic_static_analysis.h @@ -18,7 +18,7 @@ namespace rir { namespace pir { /* - * Generic implementation of a (forward) static analysis. + * Generic implementation of a static analysis. * * In "mergepoint" we keep a list of abstract states for every basic block. The * first state is the abstract state at the beginning of the basic block. We @@ -27,7 +27,7 @@ namespace pir { * To implement a concrete static analysis, the "apply" method needs to be * implemented, which supplies the implementation for every instruction. Apply * is supposed to modify the abstract state, but not (!) the analysis itself - * (that is why it is marked const). The reason is, that after we reached a + * (that is why it is marked const). The reason is that after we reach a * fixed-point, it should be possible to reconstruct the state of the analysis * at every instruction. To do so, a dominating state is loaded from * "mergepoint" and then "apply" is used to seek to the desired instruction @@ -38,8 +38,6 @@ namespace pir { * provided by the subclass that specializes StaticAnalysis. */ -class AvailableCheckpoints; - enum class AnalysisDebugLevel { None, Taint, @@ -69,8 +67,10 @@ class StaticAnalysis { struct BBSnapshot { bool seen = false; - size_t incomming = 0; + size_t incoming = 0; + // entry stores the state *before* the first instruction in the BB AbstractState entry; + // extra stores the state *after* calling apply on the given instruction std::unordered_map extra; }; typedef std::vector AnalysisSnapshots; @@ -82,25 +82,38 @@ class StaticAnalysis { } // For lookup, after fixed-point was found virtual AbstractResult apply(AbstractState&, Instruction*) const = 0; + // Compute BB entry + virtual AbstractResult applyEntry(AbstractState&, BB*) const { + return AbstractResult::None; + }; - constexpr static size_t MAX_CACHE_SIZE = 128 / sizeof(AbstractState); - - std::unordered_map cache; - std::deque cacheQueue; - void addToCache(Instruction* i, const AbstractState& state) const { +#ifdef PIR_ANALYSIS_USE_LOOKUP_CACHE + constexpr static size_t MAX_CACHE_SIZE = + std::max(1UL, 128 / sizeof(AbstractState)); + + mutable std::unordered_map beforeCache; + mutable std::unordered_map afterCache; + mutable std::deque beforeCacheQueue; + mutable std::deque afterCacheQueue; + void addToCache(PositioningStyle pos, Instruction* i, + const AbstractState& state) const { + auto& cache = pos == BeforeInstruction ? beforeCache : afterCache; + auto& cacheQueue = + pos == BeforeInstruction ? beforeCacheQueue : afterCacheQueue; if (cache.count(i)) { - const_cast(this)->cache.erase(cache.find(i)); - const_cast(this)->cache.emplace(i, state); + cache.erase(cache.find(i)); + cache.emplace(i, state); return; } if (cacheQueue.size() > MAX_CACHE_SIZE) { auto oldest = cacheQueue.front(); - const_cast(this)->cacheQueue.pop_front(); - const_cast(this)->cache.erase(cache.find(oldest)); + cacheQueue.pop_front(); + cache.erase(cache.find(oldest)); } - const_cast(this)->cache.emplace(i, state); - const_cast(this)->cacheQueue.push_back(i); + cache.emplace(i, state); + cacheQueue.push_back(i); } +#endif std::unordered_map exitpoints; AbstractState exitpoint; @@ -132,6 +145,11 @@ class StaticAnalysis { snapshots[e->id].entry = initialState; } + void setInitialState(const std::function& apply) { + for (auto& e : entrypoints) + apply(snapshots[e->id].entry); + } + const GlobalAbstractState& getGlobalState() { return *globalState; } const AbstractState& result() const { @@ -222,44 +240,56 @@ class StaticAnalysis { } } - AbstractState before(Instruction* i) const { - return at(i); + AbstractState before(Instruction* i, + AbstractState* afterPreviousInstr = nullptr) const { + return at(i, afterPreviousInstr); } - AbstractState after(Instruction* i) const { - return at(i); + AbstractState after(Instruction* i, + AbstractState* afterPreviousInstr = nullptr) const { + return at(i, afterPreviousInstr); } + private: template - AbstractState at(Instruction* i) const { + AbstractState at(Instruction* i, + AbstractState* afterPreviousInstr = nullptr) const { if (!done) const_cast(this)->operator()(); assert(done); - BB* bb = i->bb(); - - if (cache.count(i)) { - auto state = cache.at(i); - if (PositioningStyle::AfterInstruction == POS) - apply(state, i); - return state; +#ifdef PIR_ANALYSIS_USE_LOOKUP_CACHE + if (beforeCache.count(i) && POS == BeforeInstruction) { + return beforeCache.at(i); } + if (afterCache.count(i) && POS == AfterInstruction) { + return afterCache.at(i); + } +#endif + if (POS == PositioningStyle::BeforeInstruction && afterPreviousInstr) + return *afterPreviousInstr; + + BB* bb = i->bb(); if (Forward) - return findSnapshot(bb->begin(), bb->end(), bb, i); + return findSnapshot(bb->begin(), bb->end(), bb, i, + afterPreviousInstr); - return findSnapshot(bb->rbegin(), bb->rend(), bb, i); + return findSnapshot(bb->rbegin(), bb->rend(), bb, i, + afterPreviousInstr); } template - AbstractState findSnapshot(Iter begin, Iter end, BB* bb, - Instruction* i) const { - size_t tried = 0; + AbstractState + findSnapshot(Iter begin, Iter end, BB* bb, Instruction* i, + AbstractState* afterPreviousInstr = nullptr) const { const BBSnapshot& bbSnapshots = snapshots[bb->id]; + // Find the snapshot closest to the desired state + size_t tried = 0; auto snapshotPos = begin; - for (auto pos = begin, e = end; - pos != e && tried < bbSnapshots.extra.size(); ++pos) { + for (auto pos = begin; pos != end && tried < bbSnapshots.extra.size(); + ++pos) { if (POS == BeforeInstruction && i == *pos) break; if (bbSnapshots.extra.count(*pos)) { @@ -270,18 +300,54 @@ class StaticAnalysis { break; } + auto state = + tried == 0 ? bbSnapshots.entry : bbSnapshots.extra.at(*snapshotPos); + + // If we found a snapshot in extra, this gives us the state *after* + // applying, hence we either found the result or need to move to the + // next instruction + if (tried) { + if (i == *snapshotPos) { + assert(POS == AfterInstruction); +#ifdef PIR_ANALYSIS_USE_LOOKUP_CACHE + addToCache(AfterInstruction, i, state); + if (snapshotPos + 1 != end) + addToCache(BeforeInstruction, *(snapshotPos + 1), state); +#endif + return state; + } + ++snapshotPos; + assert(snapshotPos != end); + } + + // No snapshot found for the current position. If we have the state + // after the previous instruction, then this is the next fastest way to + // compute it. + if (afterPreviousInstr && *snapshotPos != i) { + assert(POS == PositioningStyle::AfterInstruction); + auto state = *afterPreviousInstr; + apply(state, i); + assert(!bbSnapshots.extra.count(i)); + return state; + } + // Apply until we arrive at the position - auto state = snapshotPos == begin ? bbSnapshots.entry - : bbSnapshots.extra.at(*snapshotPos); - for (auto pos = snapshotPos, e = end; pos != e; ++pos) { + for (auto pos = snapshotPos; pos != end; ++pos) { if (POS == BeforeInstruction && i == *pos) { - addToCache(i, state); +#ifdef PIR_ANALYSIS_USE_LOOKUP_CACHE + addToCache(BeforeInstruction, i, state); + if (pos != begin) + addToCache(AfterInstruction, *(pos - 1), state); +#endif return state; } apply(state, *pos); if (POS == AfterInstruction && i == *pos) { +#ifdef PIR_ANALYSIS_USE_LOOKUP_CACHE + addToCache(AfterInstruction, i, state); if (pos + 1 != end) - addToCache(*(pos + 1), state); + addToCache(BeforeInstruction, *(pos + 1), state); +#endif return state; } } @@ -290,6 +356,7 @@ class StaticAnalysis { return AbstractState(); } + public: typedef std::function Collect; template @@ -340,6 +407,10 @@ class StaticAnalysis { if (!changed[id]) return; + if (applyEntry(snapshots[id].entry, bb) > + AbstractResult::None) + changed[id] = true; + AbstractState state = snapshots[id].entry; logInitialState(state, bb); @@ -446,14 +517,14 @@ class StaticAnalysis { if (!thisState.seen) { thisState.entry = state; thisState.seen = true; - thisState.incomming = in->id; + thisState.incoming = in->id; done = false; changed[id] = true; - } else if (in->id == thisState.incomming) { + } else if (in->id == thisState.incoming) { thisState.entry = state; changed[id] = changed[in->id]; } else { - thisState.incomming = -1; + thisState.incoming = -1; AbstractState old; if (DEBUG_LEVEL >= AnalysisDebugLevel::Taint) { old = thisState.entry; @@ -485,6 +556,8 @@ class StaticAnalysis { } } + bool fixedPointReached() { return done; } + private: void seedEntries() { if (Forward) { diff --git a/rir/src/compiler/analysis/last_env.h b/rir/src/compiler/analysis/last_env.h index b17d12b54..2a741e51d 100644 --- a/rir/src/compiler/analysis/last_env.h +++ b/rir/src/compiler/analysis/last_env.h @@ -37,9 +37,7 @@ class LastEnv : public StaticAnalysis> { bool envStillValid(Instruction* i) { return currentEnv(i) == i->env(); } - Value* currentEnv(Instruction* i) { - return StaticAnalysis::at(i).get(); - } + Value* currentEnv(Instruction* i) { return before(i).get(); } }; } // namespace pir diff --git a/rir/src/compiler/analysis/liveness.cpp b/rir/src/compiler/analysis/liveness.cpp index 517ff0cd2..6b9a3e7e0 100644 --- a/rir/src/compiler/analysis/liveness.cpp +++ b/rir/src/compiler/analysis/liveness.cpp @@ -190,7 +190,7 @@ LivenessIntervals::LivenessIntervals(Code* code, unsigned bbsSize) { } bool LivenessIntervals::live(Instruction* where, Value* what) const { - if (!what->isInstruction() || count(what) == 0) + if (!Instruction::Cast(what) || count(what) == 0) return false; const auto& bbLiveness = intervals.at(what)[where->bb()->id]; if (!bbLiveness.live) @@ -201,7 +201,7 @@ bool LivenessIntervals::live(Instruction* where, Value* what) const { bool LivenessIntervals::live(const BB::Instrs::iterator& where, Value* what) const { - if (!what->isInstruction() || count(what) == 0) + if (!Instruction::Cast(what) || count(what) == 0) return false; const auto& bbLiveness = intervals.at(what)[(*where)->bb()->id]; if (!bbLiveness.live) diff --git a/rir/src/compiler/analysis/range.cpp b/rir/src/compiler/analysis/range.cpp new file mode 100644 index 000000000..1b390c1dd --- /dev/null +++ b/rir/src/compiler/analysis/range.cpp @@ -0,0 +1,16 @@ +#include "range.h" + +namespace rir { +namespace pir { + +Range Range::MAX = {INT_MIN, INT_MAX}; + +Range Range::NEG = {INT_MIN, -1}; +Range Range::ABOVE0 = {1, INT_MAX}; +Range Range::POS = {0, INT_MAX}; + +Range Range::ZERO = {0, 0}; +Range Range::ONE = {1, 1}; + +} // namespace pir +} // namespace rir diff --git a/rir/src/compiler/analysis/range.h b/rir/src/compiler/analysis/range.h index 5b4704392..f0a4a4b09 100644 --- a/rir/src/compiler/analysis/range.h +++ b/rir/src/compiler/analysis/range.h @@ -13,22 +13,69 @@ namespace rir { namespace pir { -inline int max(int a, int b) { - if (b > a) - return b; - return a; -} - -inline int min(int a, int b) { - if (b < a) - return b; - return a; -} - -static constexpr int MIN = -20; -static constexpr int MAX = 20; - -typedef std::pair Range; +class Range { + private: + Range(int a, int b) : begin_(a), end_(b) {} + + int begin_; + int end_; + + public: + int begin() const { return begin_; } + int end() const { return end_; } + + bool operator!=(const Range& other) const { + return begin_ != other.begin_ || end_ != other.end_; + } + bool operator==(const Range& other) const { + return begin_ == other.begin_ && end_ == other.end_; + } + + bool operator>(int other) const { return begin_ > other; } + + static Range MAX; + static Range NEG; + static Range POS; + static Range ABOVE0; + static Range ZERO; + static Range ONE; + + static Range get(double a, double b) { + int ia, ib; + if (a <= (double)INT_MIN) + ia = INT_MIN; + else + ia = floor(a); + if (b >= (double)INT_MAX) + ib = INT_MAX; + else + ib = ceil(b); + return get(ia, ib); + } + + static Range get(int a, int b) { + for (auto r : {ZERO, ONE, NEG, ABOVE0, POS}) + if (a >= r.begin_ && b <= r.end_) + return r; + return MAX; + } + + Range merge(const Range& other) const { + if (*this == MAX) + return MAX; + if (other.begin_ <= begin_ && other.end_ >= end_) + return other; + if (begin_ <= other.begin_ && end_ >= other.end_) + return *this; + for (auto r : {NEG, ABOVE0, POS}) { + if (begin_ >= r.begin_ && other.begin_ >= r.begin_ && + end_ <= r.end_ && other.end_ <= r.end_) + return r; + } + return MAX; + } +}; + struct RangeAnalysisState { std::unordered_map range; std::unordered_set seen; @@ -36,7 +83,7 @@ struct RangeAnalysisState { void print(std::ostream& out, bool tty) const { for (auto i : range) { i.first->printRef(out); - out << ": [" << i.second.first << ", " << i.second.second << "]\n"; + out << ": [" << i.second.begin() << ", " << i.second.end() << "]\n"; } } AbstractResult mergeExit(const RangeAnalysisState& other) { @@ -52,18 +99,9 @@ struct RangeAnalysisState { } else { auto& mine = m->second; auto their = o->second; - auto mi = min(mine.first, their.first); - if (mi < MIN) - mi = MIN; - if (mine.first > mi) { - mine.first = mi; - res.update(); - } - auto ma = max(mine.second, their.second); - if (ma > MAX) - ma = MAX; - if (mine.second < ma) { - mine.second = ma; + auto mi = mine.merge(their); + if (mine != mi) { + mine = mi; res.update(); } } @@ -82,44 +120,36 @@ class RangeAnalysis : public StaticAnalysisbb()->begin() || !i->bb()->hasSinglePred()) - return; - - auto pred = *i->bb()->predecessors().begin(); - if (pred->isEmpty()) - return; + if (!bb->hasSinglePred()) + return res; - auto br = Branch::Cast(pred->last()); - if (!br) - return; + auto pred = *bb->predecessors().begin(); + if (pred->isEmpty()) + return res; - auto t = Identical::Cast(br->arg(0).val()); - if (!t) - return; + auto br = Branch::Cast(pred->last()); + if (!br) + return res; - bool brtrue = t->arg(1).val() == True::instance(); - bool brfalse = t->arg(1).val() == False::instance(); - if (!brtrue && !brfalse) - return; + auto t = CheckTrueFalse::Cast(br->arg(0).val()); + if (!t) + return res; - bool holds = i->bb() == pred->trueBranch(); - if (brfalse) - holds = !holds; - Instruction* condition = Instruction::Cast(t->arg(0).val()); - if (!condition) - return; + bool holds = bb == pred->trueBranch(); + Instruction* condition = Instruction::Cast(t->arg(0).val()); + if (!condition) + return res; - if (auto n = Not::Cast(condition)) { - holds = !holds; - condition = Instruction::Cast(n->arg(0).val()); + if (auto n = Not::Cast(condition)) { + holds = !holds; + condition = Instruction::Cast(n->arg(0).val()); } if (!condition) - return; + return res; auto applyCond = [&](std::function getLhs, @@ -134,42 +164,27 @@ class RangeAnalysis : public StaticAnalysis lhsCur.first) { - res.update(); - lhsCur.first = lhsApp.first; - } - if (lhsApp.second < lhsCur.second) { + if (!state.range.count(lhs)) + state.range.emplace(lhs, Range::MAX); + if (!state.range.count(rhs)) + state.range.emplace(rhs, Range::MAX); + + auto& lhsCur = state.range.at(lhs); + auto& rhsCur = state.range.at(rhs); + + auto i1 = lhsCur.begin(); + auto i2 = lhsCur.end(); + auto i3 = rhsCur.begin(); + auto i4 = rhsCur.end(); + auto lhsNew = getLhs(i1, i2, i3, i4); + auto rhsNew = getRhs(i1, i2, i3, i4); + if (lhsCur != lhsNew) { + lhsCur = lhsNew; res.update(); - lhsCur.second = lhsApp.second; } - if (rhsApp.first > rhsCur.first) { + if (rhsCur != rhsNew) { res.update(); - rhsCur.first = rhsApp.first; - } - if (rhsApp.second < rhsCur.second) { - res.update(); - rhsCur.second = rhsApp.second; + rhsCur = rhsNew; } }; @@ -188,13 +203,13 @@ class RangeAnalysis : public StaticAnalysistag == Tag::Lt && c == d) bound--; - return Range(min(a, bound), min(b, bound)); + return Range::get(min(a, bound), min(b, bound)); }, [&](int a, int b, int c, int d) { auto bound = a; if (condition->tag == Tag::Lte && a == b) bound++; - return Range(max(bound, c), max(bound, d)); + return Range::get(max(bound, c), max(bound, d)); }); break; @@ -206,20 +221,24 @@ class RangeAnalysis : public StaticAnalysistag == Tag::Gt && c == d) bound++; - return Range(max(a, bound), max(b, bound)); + return Range::get(max(a, bound), max(b, bound)); }, [&](int a, int b, int c, int d) { auto bound = b; if (condition->tag == Tag::Gte && c == d) bound--; - return Range(min(bound, c), min(bound, d)); + return Range::get(min(bound, c), min(bound, d)); }); break; default: {} } - }; - branching(); + return res; + } + + AbstractResult apply(RangeAnalysisState& state, + Instruction* i) const override { + AbstractResult res = AbstractResult::None; auto binop = [&](const std::function apply) { if (i->effects.contains(Effect::ExecuteCode)) return; @@ -228,23 +247,22 @@ class RangeAnalysis : public StaticAnalysisarg(0).val()); auto b = state.range.at(i->arg(1).val()); - auto up = - Range(apply(a.first, b.first), apply(a.second, b.second)); - - if (up.first < MIN) - up.first = MIN; - - if (up.second > MAX) - up.second = MAX; - - auto& cur = state.range[i]; - if (a.first > MIN && b.first > MIN && cur.first != up.first) { - cur.first = up.first; - res.update(); - } - if (a.second < MAX && b.second < MAX && - cur.second != up.second) { - cur.second = up.second; + auto lower = a.begin() == INT_MIN || b.begin() == INT_MIN + ? INT_MIN + : apply(a.begin(), b.begin()); + auto upper = a.end() == INT_MAX || b.begin() == INT_MAX + ? INT_MAX + : apply(a.end(), b.end()); + auto up = Range::get(lower, upper); + + if (state.range.count(i)) { + auto& cur = state.range.at(i); + if (cur != up) { + cur = up; + res.update(); + } + } else { + state.range.emplace(i, up); res.update(); } } @@ -253,12 +271,16 @@ class RangeAnalysis : public StaticAnalysistag) { case Tag::LdConst: { auto ld = LdConst::Cast(i); - if (IS_SIMPLE_SCALAR(ld->c(), INTSXP)) { - auto r = INTEGER(ld->c())[0]; - state.range[i] = {r, r}; - } else if (IS_SIMPLE_SCALAR(ld->c(), REALSXP)) { - auto r = REAL(ld->c())[0]; - state.range[i] = {floor(r), ceil(r)}; + if (!state.range.count(i)) { + if (IS_SIMPLE_SCALAR(ld->c(), INTSXP)) { + auto r = INTEGER(ld->c())[0]; + state.range.emplace(i, Range::get(r, r)); + res.update(); + } else if (IS_SIMPLE_SCALAR(ld->c(), REALSXP)) { + auto r = REAL(ld->c())[0]; + state.range.emplace(i, Range::get(r, r)); + res.update(); + } } break; } @@ -273,25 +295,31 @@ class RangeAnalysis : public StaticAnalysiseachArg([&](BB*, Value* v) { if (state.range.count(v)) { auto r = state.range.at(v); - if (r.first < mi) - mi = r.first; - if (r.second > ma) - ma = r.second; + if (first) + m = r; + else + m = m.merge(r); } else { - if (state.seen.count(p)) - state.range.emplace(v, Range(MIN, MAX)); + if (state.seen.count(p)) { + state.range.emplace(v, Range::MAX); + m = Range::MAX; + } } + first = false; }); - state.range[i] = {mi, ma}; if (!state.seen.count(p)) { res.update(); + state.range.emplace(p, m); state.seen.insert(p); + } else if (state.range.at(p) != m) { + state.range.at(p) = m; } break; } @@ -301,6 +329,18 @@ class RangeAnalysis : public StaticAnalysis a) + return b; + return a; + } + static int min(int a, int b) { + if (b < a) + return b; + return a; + } }; } // namespace pir diff --git a/rir/src/compiler/analysis/reference_count.h b/rir/src/compiler/analysis/reference_count.h index 5b69e772d..1a88b6639 100644 --- a/rir/src/compiler/analysis/reference_count.h +++ b/rir/src/compiler/analysis/reference_count.h @@ -286,7 +286,6 @@ class StaticReferenceCount case Tag::MkEnv: case Tag::MkArg: case Tag::UpdatePromise: - case Tag::ForSeqSize: case Tag::ScheduledDeopt: case Tag::PopContext: case Tag::Extract2_2D: diff --git a/rir/src/compiler/analysis/scope.cpp b/rir/src/compiler/analysis/scope.cpp index 60ca10e6f..98110fe7b 100644 --- a/rir/src/compiler/analysis/scope.cpp +++ b/rir/src/compiler/analysis/scope.cpp @@ -2,6 +2,7 @@ #include "../pir/pir_impl.h" #include "../util/safe_builtins_list.h" #include "query.h" +#include namespace rir { namespace pir { @@ -288,7 +289,8 @@ AbstractResult ScopeAnalysis::doCompute(ScopeAnalysisState& state, if (auto mkarg = MkArg::Cast(arg->followCastsAndForce())) { auto upd = state.forcedPromise.find(mkarg); if (upd == state.forcedPromise.end()) { - if (depth < MAX_DEPTH && force->strict) { + if (depth < MAX_DEPTH && !fixedPointReached() && + force->strict) { if (ld->id < args.size()) arg = args[ld->id]; @@ -296,14 +298,34 @@ AbstractResult ScopeAnalysis::doCompute(ScopeAnalysisState& state, // here. Let's peek through the argument and see // if we find a promise. If so, we will analyze // it. - auto stateCopy = state; - stateCopy.mayUseReflection = false; - ScopeAnalysis prom(closure, mkarg->prom(), - mkarg->env(), stateCopy, - globalState, depth + 1, log); - prom(); - auto res = prom.result(); + ScopeAnalysis* prom; + if (!subAnalysis.count(i)) { + prom = + subAnalysis + .emplace( + i, + std::make_unique( + closure, mkarg->prom(), + mkarg->env(), state, + globalState, depth + 1, + log)) + .first->second.get(); + prom->setInitialState( + [&](ScopeAnalysisState& init) { + init.mayUseReflection = false; + }); + } else { + prom = subAnalysis.at(i).get(); + prom->setInitialState( + [&](ScopeAnalysisState& init) { + init = state; + init.mayUseReflection = false; + }); + } + (*prom)(); + + auto res = prom->result(); state.mergeCall(code, res); updateReturnValue(res.returnValue); @@ -346,7 +368,7 @@ AbstractResult ScopeAnalysis::doCompute(ScopeAnalysisState& state, return; } - if (depth == MAX_DEPTH) + if (fixedPointReached() || depth == MAX_DEPTH) return; if (version->numNonDeoptInstrs() > MAX_SIZE) @@ -356,11 +378,23 @@ AbstractResult ScopeAnalysis::doCompute(ScopeAnalysisState& state, calli->eachCallArg([&](Value* v) { args.push_back(v); }); while (args.size() < version->effectiveNArgs()) args.push_back(MissingArg::instance()); - ScopeAnalysis nextFun(version, args, lexicalEnv, state, globalState, - depth + 1, log); - nextFun(); - state.mergeCall(code, nextFun.result()); - updateReturnValue(nextFun.result().returnValue); + + ScopeAnalysis* nextFun; + if (!subAnalysis.count(i)) { + nextFun = subAnalysis + .emplace(i, std::make_unique( + version, args, lexicalEnv, state, + globalState, depth + 1, log)) + .first->second.get(); + } else { + nextFun = subAnalysis.at(i).get(); + nextFun->setInitialState( + [&](ScopeAnalysisState& init) { init = state; }); + } + + (*nextFun)(); + state.mergeCall(code, nextFun->result()); + updateReturnValue(nextFun->result().returnValue); effect.keepSnapshot = true; handled = true; effect.update(); diff --git a/rir/src/compiler/analysis/scope.h b/rir/src/compiler/analysis/scope.h index 36dbb1cc2..65f2ce741 100644 --- a/rir/src/compiler/analysis/scope.h +++ b/rir/src/compiler/analysis/scope.h @@ -120,6 +120,9 @@ class ScopeAnalysis ScopeAnalysisResults* globalStateStore = nullptr; + std::unordered_map> + subAnalysis; + protected: AbstractResult compute(ScopeAnalysisState& state, Instruction* i) override { return doCompute(state, i, true); diff --git a/rir/src/compiler/analysis/unnecessary_contexts.h b/rir/src/compiler/analysis/unnecessary_contexts.h index c848b889e..98d0a07ab 100644 --- a/rir/src/compiler/analysis/unnecessary_contexts.h +++ b/rir/src/compiler/analysis/unnecessary_contexts.h @@ -106,7 +106,7 @@ class UnnecessaryContexts : public StaticAnalysis { }; PushContext* canRemove(PopContext* i) const { - auto res = StaticAnalysis::at(i); + auto res = before(i); if (res.get() && res.get() == i->push() && !res.needed) return res.get(); return nullptr; diff --git a/rir/src/compiler/backend.cpp b/rir/src/compiler/backend.cpp index 22d9bde21..19ea9738a 100644 --- a/rir/src/compiler/backend.cpp +++ b/rir/src/compiler/backend.cpp @@ -73,12 +73,14 @@ static void approximateNeedsLdVarForUpdate( // These are builtins which ignore value semantics... case Tag::CallBuiltin: { auto b = CallBuiltin::Cast(i); - if (b->builtinId == blt(".Call")) { + bool dotCall = b->builtinId == blt(".Call"); + if (dotCall || b->builtinId == blt("class<-")) { if (auto l = LdVar::Cast( b->callArg(0).val()->followCastsAndForce())) { static std::unordered_set block = { - Rf_install("C_R_set_slot")}; - if (block.count(l->varName)) { + Rf_install("C_R_set_slot"), + Rf_install("C_R_set_class")}; + if (!dotCall || block.count(l->varName)) { apply(i, l); } } @@ -238,6 +240,7 @@ static void lower(Code* code) { } }); + std::vector dead; Visitor::run(code->entry, [&](BB* bb) { auto it = bb->begin(); while (it != bb->end()) { @@ -245,12 +248,19 @@ static void lower(Code* code) { if (FrameState::Cast(*it)) { next = bb->remove(it); } else if (Checkpoint::Cast(*it)) { + auto d = bb->deoptBranch(); next = bb->remove(it); bb->convertBranchToJmp(true); + if (d->predecessors().size() == 0) { + assert(d->successors().size() == 0); + dead.push_back(d); + } } it = next; } }); + for (auto bb : dead) + delete bb; BBTransform::mergeRedundantBBs(code); @@ -397,7 +407,6 @@ rir::Function* Backend::doCompile(ClosureVersion* cls, if (MEASURE_COMPILER_BACKEND_PERF) { Measuring::countTimer("backend.cpp: pir2llvm"); - Measuring::startTimer("backend.cpp: llvm"); } log.finalPIR(cls); @@ -409,7 +418,12 @@ rir::Function* Backend::doCompile(ClosureVersion* cls, Backend::LastDestructor::~LastDestructor() { if (MEASURE_COMPILER_BACKEND_PERF) { - Measuring::countTimer("backend.cpp: llvm"); + Measuring::countTimer("backend.cpp: overal"); + } +} +Backend::LastDestructor::LastDestructor() { + if (MEASURE_COMPILER_BACKEND_PERF) { + Measuring::startTimer("backend.cpp: overal"); } } diff --git a/rir/src/compiler/backend.h b/rir/src/compiler/backend.h index 9ca93ef6e..87a20a1c8 100644 --- a/rir/src/compiler/backend.h +++ b/rir/src/compiler/backend.h @@ -25,6 +25,7 @@ class Backend { private: struct LastDestructor { + LastDestructor(); ~LastDestructor(); }; LastDestructor firstMember_; diff --git a/rir/src/compiler/compiler.cpp b/rir/src/compiler/compiler.cpp index 79812cbf4..e480bf655 100644 --- a/rir/src/compiler/compiler.cpp +++ b/rir/src/compiler/compiler.cpp @@ -183,6 +183,7 @@ void Compiler::compileClosure(Closure* closure, rir::Function* optFunction, logger.warn("Failed to compile default arg"); logger.close(version); closure->erase(ctx); + delete version; return fail(); } @@ -203,12 +204,13 @@ void Compiler::compileClosure(Closure* closure, rir::Function* optFunction, log.flush(); logger.close(version); closure->erase(ctx); + delete version; return fail(); } bool MEASURE_COMPILER_PERF = getenv("PIR_MEASURE_COMPILER") ? true : false; -static void findUnreachable(Module* m) { +static void findUnreachable(Module* m, StreamLogger& log) { std::unordered_map> reachable; bool changed = true; @@ -239,6 +241,7 @@ static void findUnreachable(Module* m) { assert(call->tryDispatch()); found(call->tryDispatch()); found(call->tryOptimisticDispatch()); + found(call->hint); } else if (auto call = CallInstruction::CastCall(i)) { if (auto cls = call->tryGetCls()) found(call->tryDispatch(cls)); @@ -263,8 +266,11 @@ static void findUnreachable(Module* m) { m->eachPirClosure([&](Closure* c) { const auto& reachableVersions = reachable[c]; c->eachVersion([&](ClosureVersion* v) { - if (!reachableVersions.count(v->context())) + if (!reachableVersions.count(v->context())) { toErase.push_back({v->owner(), v->context()}); + log.close(v); + delete v; + } }); }); @@ -280,7 +286,7 @@ void Compiler::optimizeModule() { if (translation->isSlow()) { if (MEASURE_COMPILER_PERF) Measuring::startTimer("compiler.cpp: module cleanup"); - findUnreachable(module); + findUnreachable(module, logger); if (MEASURE_COMPILER_PERF) Measuring::countTimer("compiler.cpp: module cleanup"); } @@ -338,7 +344,10 @@ void Compiler::optimizeModule() { } size_t Parameter::MAX_INPUT_SIZE = - getenv("PIR_MAX_INPUT_SIZE") ? atoi(getenv("PIR_MAX_INPUT_SIZE")) : 8000; + getenv("PIR_MAX_INPUT_SIZE") ? atoi(getenv("PIR_MAX_INPUT_SIZE")) : 12000; +size_t Parameter::RECOMPILE_THRESHOLD = + getenv("PIR_RECOMPILE_THRESHOLD") ? atoi(getenv("PIR_RECOMPILE_THRESHOLD")) + : 2000; } // namespace pir } // namespace rir diff --git a/rir/src/compiler/native/builtins.cpp b/rir/src/compiler/native/builtins.cpp index 6bf3af7ff..722dce28a 100644 --- a/rir/src/compiler/native/builtins.cpp +++ b/rir/src/compiler/native/builtins.cpp @@ -179,23 +179,23 @@ void stargImpl(SEXP sym, SEXP val, SEXP env) { } void setCarImpl(SEXP x, SEXP y) { - assert(x->sxpinfo.mark && "Use fastpath setCar"); - assert((!y->sxpinfo.mark || y->sxpinfo.gcgen < x->sxpinfo.gcgen) && - "use fast path setCar"); + // assert(x->sxpinfo.mark && "Use fastpath setCar"); + // assert((!y->sxpinfo.mark || y->sxpinfo.gcgen < x->sxpinfo.gcgen) && + // "use fast path setCar"); SETCAR(x, y); } void setCdrImpl(SEXP x, SEXP y) { - assert(x->sxpinfo.mark && "Use fastpath setCdr"); - assert((!y->sxpinfo.mark || y->sxpinfo.gcgen < x->sxpinfo.gcgen) && - "use fast path setCdr"); + // assert(x->sxpinfo.mark && "Use fastpath setCdr"); + // assert((!y->sxpinfo.mark || y->sxpinfo.gcgen < x->sxpinfo.gcgen) && + // "use fast path setCdr"); SETCDR(x, y); } void setTagImpl(SEXP x, SEXP y) { - assert(x->sxpinfo.mark && "Use fastpath setTag"); - assert((!y->sxpinfo.mark || y->sxpinfo.gcgen < x->sxpinfo.gcgen) && - "use fast path setTag"); + // assert(x->sxpinfo.mark && "Use fastpath setTag"); + // assert((!y->sxpinfo.mark || y->sxpinfo.gcgen < x->sxpinfo.gcgen) && + // "use fast path setTag"); SET_TAG(x, y); } @@ -248,8 +248,8 @@ static SEXP callBuiltinImpl(rir::Code* c, Immediate ast, SEXP callee, SEXP env, size_t nargs) { auto ctx = globalContext(); CallContext call(ArglistOrder::NOT_REORDERED, c, callee, nargs, ast, - ostack_cell_at(ctx, nargs - 1), env, R_NilValue, Context(), - ctx); + ostack_cell_at(ctx, (long)nargs - 1), env, R_NilValue, + Context(), ctx); if (debugPrintCallBuiltinImpl) { debugPrintCallBuiltinImpl = false; std::cout << "call builtin " << nargs << " with\n"; @@ -286,7 +286,7 @@ static SEXP callImplCached(ArglistOrder::CallId callId, rir::Code* c, unsigned long available, Immediate cache) { auto ctx = globalContext(); CallContext call(callId, c, callee, nargs, ast, - ostack_cell_at(ctx, nargs - 1), env, R_NilValue, + ostack_cell_at(ctx, (long)nargs - 1), env, R_NilValue, Context(available), ctx); SLOWASSERT(env == symbol::delayedEnv || TYPEOF(env) == ENVSXP || @@ -306,8 +306,8 @@ static SEXP namedCallImpl(ArglistOrder::CallId callId, rir::Code* c, Immediate* names, unsigned long available) { auto ctx = globalContext(); CallContext call(callId, c, callee, nargs, ast, - ostack_cell_at(ctx, nargs - 1), names, env, R_NilValue, - Context(available), ctx); + ostack_cell_at(ctx, (long)nargs - 1), names, env, + R_NilValue, Context(available), ctx); SLOWASSERT(env == symbol::delayedEnv || TYPEOF(env) == ENVSXP || LazyEnvironment::check(env)); SLOWASSERT(ctx); @@ -336,8 +336,8 @@ static SEXP dotsCallImpl(ArglistOrder::CallId callId, rir::Code* c, } CallContext call(callId, c, callee, nargs, ast, - ostack_cell_at(ctx, nargs - 1), names, env, R_NilValue, - given, ctx); + ostack_cell_at(ctx, (long)nargs - 1), names, env, + R_NilValue, given, ctx); SLOWASSERT(env == symbol::delayedEnv || TYPEOF(env) == ENVSXP || LazyEnvironment::check(env)); SLOWASSERT(ctx); @@ -412,66 +412,6 @@ SEXP newRealFromIntImpl(int i) { return ScalarReal(i == NA_INTEGER ? NAN : i); } R_Visible = static_cast(flag != 1); \ } while (false) -static void createFakeSEXP(SEXPREC& res, SEXPTYPE t) { - memset(&res, 0, sizeof(SEXPREC)); - res.attrib = R_NilValue; - res.gengc_next_node = R_NilValue; - res.gengc_prev_node = R_NilValue; - res.sxpinfo.gcgen = 1; - res.sxpinfo.mark = 1; - res.sxpinfo.named = 2; - res.sxpinfo.type = t; -} - -static void createFakeCONS(SEXPREC& res, SEXP cdr) { - createFakeSEXP(res, LISTSXP); - res.u.listsxp.carval = R_NilValue; - res.u.listsxp.tagval = R_NilValue; - res.u.listsxp.cdrval = cdr; -} - -#define FAKE_ARGS1(res, a1) \ - SEXPREC __a1__cell__; \ - createFakeCONS(__a1__cell__, R_NilValue); \ - __a1__cell__.u.listsxp.carval = a1; \ - res = &__a1__cell__ - -#define FAKE_ARGS2(res, a1, a2) \ - SEXPREC __a2__cell__; \ - createFakeCONS(__a2__cell__, R_NilValue); \ - SEXPREC __a1__cell__; \ - createFakeCONS(__a1__cell__, &__a2__cell__); \ - __a1__cell__.u.listsxp.carval = a1; \ - __a2__cell__.u.listsxp.carval = a2; \ - res = &__a1__cell__ - -#define FAKE_ARGS3(res, a1, a2, a3) \ - SEXPREC __a3__cell__; \ - createFakeCONS(__a3__cell__, R_NilValue); \ - SEXPREC __a2__cell__; \ - createFakeCONS(__a2__cell__, &__a3__cell__); \ - SEXPREC __a1__cell__; \ - createFakeCONS(__a1__cell__, &__a2__cell__); \ - __a1__cell__.u.listsxp.carval = a1; \ - __a2__cell__.u.listsxp.carval = a2; \ - __a3__cell__.u.listsxp.carval = a3; \ - res = &__a1__cell__ - -#define FAKE_ARGS4(res, a1, a2, a3, a4) \ - SEXPREC __a4__cell__; \ - createFakeCONS(__a4__cell__, R_NilValue); \ - SEXPREC __a3__cell__; \ - createFakeCONS(__a3__cell__, &__a4__cell__); \ - SEXPREC __a2__cell__; \ - createFakeCONS(__a2__cell__, &__a3__cell__); \ - SEXPREC __a1__cell__; \ - createFakeCONS(__a1__cell__, &__a2__cell__); \ - __a1__cell__.u.listsxp.carval = a1; \ - __a2__cell__.u.listsxp.carval = a2; \ - __a3__cell__.u.listsxp.carval = a3; \ - __a4__cell__.u.listsxp.carval = a4; \ - res = &__a1__cell__ - static SEXP unopEnvImpl(SEXP argument, SEXP env, Immediate srcIdx, UnopKind op) { SEXP res = nullptr; @@ -515,6 +455,7 @@ static SEXP notEnvImpl(SEXP argument, SEXP env, Immediate srcIdx) { SEXP res = nullptr; SEXP arglist; FAKE_ARGS1(arglist, argument); + MATERIALIZE_IF_OBJ1(arglist, argument); SEXP call = src_pool_at(globalContext(), srcIdx); PROTECT(arglist); OPERATION_FALLBACK("!"); @@ -527,6 +468,7 @@ static SEXP notImpl(SEXP argument) { SEXP res = nullptr; SEXP arglist; FAKE_ARGS1(arglist, argument); + MATERIALIZE_IF_OBJ1(arglist, argument); SEXP env = R_NilValue; SEXP call = R_NilValue; // Why we do not need a protect here? @@ -540,6 +482,7 @@ static SEXP binopEnvImpl(SEXP lhs, SEXP rhs, SEXP env, Immediate srcIdx, SEXP res = nullptr; SEXP arglist; FAKE_ARGS2(arglist, lhs, rhs); + MATERIALIZE_IF_OBJ2(arglist, lhs, rhs); SEXP call = src_pool_at(globalContext(), srcIdx); PROTECT(arglist); @@ -604,6 +547,7 @@ static SEXP binopImpl(SEXP lhs, SEXP rhs, BinopKind kind) { SEXP arglist; FAKE_ARGS2(arglist, lhs, rhs); + MATERIALIZE_IF_OBJ2(arglist, lhs, rhs); SEXP env = R_NilValue; SEXP call = R_NilValue; @@ -732,6 +676,15 @@ int isMissingImpl(SEXP symbol, SEXP environment) { return rir::isMissing(symbol, environment, nullptr, nullptr); } +bool isFactorImpl(SEXP val) { + return TYPEOF(val) == INTSXP && isObject(val) && Rf_inherits(val, "factor"); +} + +int asSwitchIdxImpl(SEXP val) { + int i = Rf_asInteger(val); + return i == NA_INTEGER ? -1 : i; +} + int checkTrueFalseImpl(SEXP val) { int cond = NA_LOGICAL; if (XLENGTH(val) > 1) @@ -1134,12 +1087,12 @@ static SEXP rirCallTrampoline_(RCNTXT& cntxt, Code* code, R_bcstack_t* args, cntxt.callflag = CTXT_RETURN; /* turn restart off */ R_ReturnedValue = R_NilValue; /* remove restart token */ code->registerInvocation(); - return code->nativeCode(code, args, env, callee); + return code->nativeCode()(code, args, env, callee); } else { return R_ReturnedValue; } } - return code->nativeCode(code, args, env, callee); + return code->nativeCode()(code, args, env, callee); } void initClosureContext(SEXP ast, RCNTXT* cntxt, SEXP rho, SEXP sysparent, @@ -1172,7 +1125,7 @@ static SEXP nativeCallTrampolineImpl(ArglistOrder::CallId callId, rir::Code* c, auto ctx = globalContext(); CallContext call(callId, c, callee, nargs, astP, - ostack_cell_at(ctx, nargs - 1), env, R_NilValue, + ostack_cell_at(ctx, (long)nargs - 1), env, R_NilValue, Context(available), ctx); auto fail = !call.givenContext.smaller(fun->context()); @@ -1180,7 +1133,7 @@ static SEXP nativeCallTrampolineImpl(ArglistOrder::CallId callId, rir::Code* c, inferCurrentContext(call, fun->nargs(), ctx); fail = !call.givenContext.smaller(fun->context()); } - if (!fun->body()->nativeCode || fun->body()->isDeoptimized) + if (!fun->body()->nativeCode() || fun->body()->isDeoptimized) fail = true; auto dt = DispatchTable::unpack(BODY(callee)); @@ -1199,7 +1152,7 @@ static SEXP nativeCallTrampolineImpl(ArglistOrder::CallId callId, rir::Code* c, for (size_t i = 0; i < missing; ++i) ostack_push(globalContext(), R_MissingArg); - R_bcstack_t* args = ostack_cell_at(ctx, nargs + missing - 1); + R_bcstack_t* args = ostack_cell_at(ctx, (long)(nargs + missing) - 1); auto ast = cp_pool_at(globalContext(), astP); LazyArglistOnStack lazyArgs(call.callId, @@ -1870,29 +1823,25 @@ SEXP subassign22rriImpl(SEXP vec, double idx1, double idx2, int val, SEXP env, return res; } -int forSeqSizeImpl(SEXP seq) { - // TODO: we should extract the length just once at the begining of - // the loop and generally have somthing more clever here... - int res; - if (Rf_isVector(seq)) { - res = LENGTH(seq); - } else if (Rf_isList(seq) || isNull(seq)) { - res = Rf_length(seq); - } else { +SEXP toForSeqImpl(SEXP seq) { + if (!Rf_isVector(seq) && !Rf_isList(seq) && !isNull(seq)) { Rf_errorcall(R_NilValue, "invalid for() loop sequence"); - return 0; } + // TODO: Even when the for loop sequence is an object, R won't // dispatch on it. Since in RIR we use the normals extract2_1 // BC on it, we would. To prevent this we strip the object // flag here. What we should do instead, is use a non-dispatching // extract BC. if (isObject(seq)) { - seq = Rf_shallow_duplicate(seq); + if (isFactorImpl(seq)) + seq = asCharacterFactor(seq); + else + seq = Rf_shallow_duplicate(seq); SET_OBJECT(seq, 0); - ostack_set(ctx, 0, seq); } - return res; + ENSURE_NAMEDMAX(seq); + return seq; } void initClosureContextImpl(ArglistOrder::CallId callId, rir::Code* c, SEXP ast, @@ -1900,7 +1849,7 @@ void initClosureContextImpl(ArglistOrder::CallId callId, rir::Code* c, SEXP ast, size_t nargs) { auto lazyArglist = LazyArglistOnHeap::New(callId, c->arglistOrderContainer(), nargs, - ostack_cell_at(ctx, nargs - 1), ast); + ostack_cell_at(ctx, (long)nargs - 1), ast); ostack_popn(globalContext(), nargs); auto global = (RCNTXT*)R_GlobalContext; @@ -2138,6 +2087,14 @@ void NativeBuiltins::initializeBuiltins() { "colon", (void*)&colonImpl, llvm::FunctionType::get(t::SEXP, {t::Int, t::Int}, false)}; get_(Id::isMissing) = {"isMissing", (void*)&isMissingImpl, t::int_sexpsexp}; + get_(Id::isFactor) = {"isFactor", + (void*)&isFactorImpl, + llvm::FunctionType::get(t::i1, {t::SEXP}, false), + {llvm::Attribute::ReadOnly, + llvm::Attribute::Speculatable, + llvm::Attribute::ArgMemOnly}}; + get_(Id::asSwitchIdx) = {"asSwitchIdx", (void*)&asSwitchIdxImpl, + llvm::FunctionType::get(t::Int, {t::SEXP}, false)}; get_(Id::checkTrueFalse) = {"checkTrueFalse", (void*)&checkTrueFalseImpl, t::int_sexp}; get_(Id::asLogicalBlt) = {"aslogical", (void*)&asLogicalImpl, t::int_sexp}; @@ -2266,7 +2223,7 @@ void NativeBuiltins::initializeBuiltins() { llvm::FunctionType::get( t::SEXP, {t::SEXP, t::Int, t::Int, t::Double, t::SEXP, t::Int}, false)}; - get_(Id::forSeqSize) = {"forSeqSize", (void*)&forSeqSizeImpl, t::int_sexp}; + get_(Id::toForSeq) = {"toForSeq", (void*)&toForSeqImpl, t::sexp_sexp}; get_(Id::initClosureContext) = { "initClosureContext", (void*)&initClosureContextImpl, llvm::FunctionType::get(t::t_void, diff --git a/rir/src/compiler/native/builtins.h b/rir/src/compiler/native/builtins.h index f932ae365..1b4b6aa66 100644 --- a/rir/src/compiler/native/builtins.h +++ b/rir/src/compiler/native/builtins.h @@ -104,6 +104,8 @@ struct NativeBuiltins { binop, colon, isMissing, + isFactor, + asSwitchIdx, checkTrueFalse, asLogicalBlt, length, @@ -134,7 +136,7 @@ struct NativeBuiltins { subassign22rrr, subassign22rri, subassign22iir, - forSeqSize, + toForSeq, initClosureContext, endClosureContext, matrixNcols, diff --git a/rir/src/compiler/native/lower_function_llvm.cpp b/rir/src/compiler/native/lower_function_llvm.cpp index b31fee4e4..00d446c33 100644 --- a/rir/src/compiler/native/lower_function_llvm.cpp +++ b/rir/src/compiler/native/lower_function_llvm.cpp @@ -80,7 +80,7 @@ class NativeAllocator : public SSAAllocator { // Ensure we preserve slots for variables with typefeedback to make them // accessible to the runtime profiler. // TODO: this needs to be replaced by proper mapping of slots. - if (a != b && (a->typeFeedback.origin || b->typeFeedback.origin)) + if (a != b && (a->typeFeedback().origin || b->typeFeedback().origin)) return true; return SSAAllocator::interfere(a, b); } @@ -188,6 +188,8 @@ void LowerFunctionLLVM::insn_assert(llvm::Value* v, const char* msg, builder.CreateUnreachable(); builder.SetInsertPoint(ok); + + } llvm::Value* LowerFunctionLLVM::constant(SEXP co, llvm::Type* needed) { @@ -318,7 +320,7 @@ llvm::Value* LowerFunctionLLVM::callRBuiltin(SEXP builtin, const std::vector& args, int srcIdx, CCODE builtinFun, llvm::Value* env) { - if (supportsFastBuiltinCall(builtin)) { + if (supportsFastBuiltinCall(builtin, args.size())) { return withCallFrame(args, [&]() -> llvm::Value* { return call(NativeBuiltins::get(NativeBuiltins::Id::callBuiltin), { @@ -663,7 +665,7 @@ void LowerFunctionLLVM::compilePushContext(Instruction* i) { didLongjmp); } - // Handle Incomming longjumps + // Handle incoming longjumps { builder.SetInsertPoint(didLongjmp); llvm::Value* returned = builder.CreateLoad( @@ -1393,13 +1395,6 @@ llvm::Value* LowerFunctionLLVM::container(llvm::Value* v) { llvm::CallInst* LowerFunctionLLVM::call(const NativeBuiltin& builtin, const std::vector& args) { -#ifdef ENABLE_SLOWASSERT - // abuse BB label as comment - auto callBB = - BasicBlock::Create(PirJitLLVM::getContext(), builtin.name, fun); - builder.CreateBr(callBB); - builder.SetInsertPoint(callBB); -#endif return builder.CreateCall(getBuiltin(builtin), args); } @@ -1931,7 +1926,8 @@ llvm::Value* LowerFunctionLLVM::fastVeceltOkNative(llvm::Value* v) { checkIsSexp(v, "in IsFastVeceltOkNative"); auto attrs = attr(v); auto isNil = builder.CreateICmpEQ(attrs, constant(R_NilValue, t::SEXP)); - return createSelect2(isNil, [&]() { return builder.getTrue(); }, + auto ok = builder.CreateAnd(builder.CreateNot(isObj(v)), isNil); + return createSelect2(ok, [&]() { return builder.getTrue(); }, [&]() { auto isMatr1 = builder.CreateICmpEQ( tag(attrs), constant(R_DimSymbol, t::SEXP)); @@ -2579,6 +2575,7 @@ void LowerFunctionLLVM::compile() { if (b->nargs() == 1) { auto a = load(b->callArg(0).val()); auto irep = Representation::Of(b->arg(0).val()); + auto itype = b->callArg(0).val()->type; auto orep = Representation::Of(i); bool done = true; @@ -2653,7 +2650,6 @@ void LowerFunctionLLVM::compile() { } break; case blt("names"): { - auto itype = b->callArg(0).val()->type; if (Representation::Of(b->callArg(0).val()) != t::SEXP) { setVal(i, constant(R_NilValue, t::SEXP)); @@ -2768,7 +2764,7 @@ void LowerFunctionLLVM::compile() { case blt("prod"): { if (irep == Representation::Integer || irep == Representation::Real) { - setVal(i, convert(a, i->type)); + setVal(i, box(a, itype)); } else if (orep == Representation::Real || orep == Representation::Integer) { assert(irep == Representation::Sexp); @@ -2890,7 +2886,7 @@ void LowerFunctionLLVM::compile() { }, [&]() { return callTheBuiltin(); })); } else { - setVal(i, convert(a, i->type)); + setVal(i, box(a, itype)); } break; case blt("is.logical"): @@ -3263,6 +3259,27 @@ void LowerFunctionLLVM::compile() { case Tag::CallBuiltin: { auto b = CallBuiltin::Cast(i); + + // TODO: this is not sound... There are other ways to call + // remove... What we should do instead is trap do_remove in gnur + // and clear the cache! + if (b->builtinId == blt("remove")) { + std::unordered_set affected; + if (b->nargs() >= 2 && + bindingsCache.count(b->arg(1).val())) { + for (const auto& b : bindingsCache[b->arg(1).val()]) + affected.insert(b.second); + } + if (bindingsCache.count(b->env())) { + for (const auto& b : bindingsCache[b->env()]) + affected.insert(b.second); + } + for (auto v : affected) + builder.CreateStore( + llvm::ConstantPointerNull::get(t::SEXP), + builder.CreateGEP(bindingsCacheBase, c(v))); + } + if (compileDotcall( b, [&]() { return constant(b->builtinSexp, t::SEXP); }, [&](size_t i) { return R_NilValue; })) { @@ -3438,16 +3455,16 @@ void LowerFunctionLLVM::compile() { case Tag::Nop: break; - case Tag::ForSeqSize: { + case Tag::ToForSeq: { auto a = i->arg(0).val(); if (Representation::Of(a) != t::SEXP) { - setVal(i, c(1)); + setVal(i, load(a)); break; } llvm::Value* res = - call(NativeBuiltins::get(NativeBuiltins::Id::forSeqSize), + call(NativeBuiltins::get(NativeBuiltins::Id::toForSeq), {loadSxp(i->arg(0).val())}); - setVal(i, convert(res, i->type)); + setVal(i, res); break; } @@ -4182,15 +4199,14 @@ void LowerFunctionLLVM::compile() { res = builder.CreateAnd( res, builder.CreateICmpEQ( attr(a), constant(R_NilValue, t::SEXP))); - } else { - if (arg->type.maybeNotFastVecelt() && - !t->typeTest.maybeNotFastVecelt()) { - res = builder.CreateAnd(res, fastVeceltOkNative(a)); - } - if (arg->type.maybeObj() && !t->typeTest.maybeObj()) { - res = builder.CreateAnd( - res, builder.CreateNot(isObj(a))); - } + } + if (arg->type.maybeNotFastVecelt() && + !t->typeTest.maybeNotFastVecelt()) { + res = builder.CreateAnd(res, fastVeceltOkNative(a)); + } else if (arg->type.maybeObj() && + !t->typeTest.maybeObj()) { + res = + builder.CreateAnd(res, builder.CreateNot(isObj(a))); } setVal(i, builder.CreateZExt(res, t::Int)); } else { @@ -4258,8 +4274,13 @@ void LowerFunctionLLVM::compile() { break; case BC::RirTypecheck::isFactor: - // TODO - res = builder.getFalse(); + if (Representation::Of(arg) != t::SEXP) { + res = builder.getFalse(); + } else { + res = call(NativeBuiltins::get( + NativeBuiltins::Id::isFactor), + {loadSxp(arg)}); + } break; } } else { @@ -4287,6 +4308,23 @@ void LowerFunctionLLVM::compile() { break; } + case Tag::AsSwitchIdx: { + auto arg = i->arg(0).val(); + llvm::Value* res; + auto rep = Representation::Of(i->arg(0).val()); + if (rep == t::Int) { + auto a = load(arg); + res = builder.CreateSelect( + builder.CreateICmpEQ(c(NA_INTEGER), a), c(-1), a); + } else { + res = call( + NativeBuiltins::get(NativeBuiltins::Id::asSwitchIdx), + {loadSxp(arg)}); + } + setVal(i, res); + break; + } + case Tag::CheckTrueFalse: { assert(Representation::Of(i) == Representation::Integer); @@ -5362,6 +5400,7 @@ void LowerFunctionLLVM::compile() { ensureNamed(val); envStubSet(e, idx, val, environment->nLocals(), !st->isStArg); + } builder.CreateBr(done); @@ -5768,7 +5807,7 @@ void LowerFunctionLLVM::compile() { static const char* defaultMsg = ""; if (Parameter::RIR_CHECK_PIR_TYPES > 1) { std::stringstream str; - i->printRecursive(str, 2); + i->printRecursive(str, 4); leaky.push_back(str.str()); msg = leaky.back().c_str(); } else { @@ -5857,13 +5896,13 @@ void LowerFunctionLLVM::compile() { auto i = var.first; if (Representation::Of(i) != Representation::Sexp) continue; - if (!i->typeFeedback.origin) + if (!i->typeFeedback().origin) continue; if (!var.second.initialized) continue; if (var.second.stackSlot < PirTypeFeedback::MAX_SLOT_IDX) { - codes.insert(i->typeFeedback.srcCode); - variableMapping.emplace(var.second.stackSlot, i->typeFeedback); + codes.insert(i->typeFeedback().srcCode); + variableMapping.emplace(var.second.stackSlot, i->typeFeedback()); #ifdef DEBUG_REGISTER_MAP assert(!usedSlots.count(var.second.stackSlot)); usedSlots.insert(var.second.stackSlot); diff --git a/rir/src/compiler/native/pass_schedule_llvm.cpp b/rir/src/compiler/native/pass_schedule_llvm.cpp index 1bc8cba53..0409b50da 100644 --- a/rir/src/compiler/native/pass_schedule_llvm.cpp +++ b/rir/src/compiler/native/pass_schedule_llvm.cpp @@ -17,6 +17,9 @@ #include "llvm/Transforms/Utils.h" #include "llvm/Transforms/Vectorize.h" +#include "llvm/Support/raw_os_ostream.h" +#include + namespace rir { namespace pir { @@ -43,6 +46,7 @@ operator()(llvm::orc::ThreadSafeModule TSM, verify(); #endif + }); return std::move(TSM); } @@ -64,7 +68,6 @@ PassScheduleLLVM::PassScheduleLLVM() { // for inspiration PM->add(createEntryExitInstrumenterPass()); - PM->add(createDeadInstEliminationPass()); PM->add(createCFGSimplificationPass()); if (rir::pir::Parameter::PIR_LLVM_OPT_LEVEL > 1) { @@ -79,11 +82,9 @@ PassScheduleLLVM::PassScheduleLLVM() { PM->add(createEarlyCSEPass(true)); if (rir::pir::Parameter::PIR_LLVM_OPT_LEVEL > 0) { PM->add(createPromoteMemoryToRegisterPass()); - PM->add(createConstantPropagationPass()); } PM->add(createLowerExpectIntrinsicPass()); - PM->add(createDeadInstEliminationPass()); PM->add(createDeadCodeEliminationPass()); PM->add(createInstructionCombiningPass()); PM->add(createCFGSimplificationPass()); @@ -123,9 +124,11 @@ PassScheduleLLVM::PassScheduleLLVM() { // might not be necessary: PM->add(createInstSimplifyLegacyPass()); - PM->add(createGVNPass()); + PM->add(createNewGVNPass()); PM->add(createMemCpyOptPass()); PM->add(createSCCPPass()); + PM->add(createConstantHoistingPass()); + PM->add(createFloat2IntPass()); PM->add(createSinkingPass()); // Run instcombine after redundancy elimination to exploit opportunities diff --git a/rir/src/compiler/native/pir_jit_llvm.cpp b/rir/src/compiler/native/pir_jit_llvm.cpp index 53683020b..f8d2ab9db 100644 --- a/rir/src/compiler/native/pir_jit_llvm.cpp +++ b/rir/src/compiler/native/pir_jit_llvm.cpp @@ -7,6 +7,7 @@ #include "utils/filesystem.h" #include "llvm/ExecutionEngine/JITSymbol.h" +#include "llvm/ExecutionEngine/Orc/Core.h" #include "llvm/ExecutionEngine/Orc/LLJIT.h" #include "llvm/ExecutionEngine/Orc/Mangling.h" #include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" @@ -17,10 +18,13 @@ #include "llvm/Support/Error.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/raw_os_ostream.h" +#include namespace rir { namespace pir { +std::unique_ptr PirJitLLVM::JIT; + size_t PirJitLLVM::nModules = 1; bool PirJitLLVM::initialized = false; @@ -31,7 +35,6 @@ bool LLVMDebugInfo() { namespace { llvm::ExitOnError ExitOnErr; -std::unique_ptr JIT; llvm::orc::ThreadSafeContext TSC; std::string dbgFolder; @@ -317,11 +320,8 @@ void PirJitLLVM::finalizeAndFixup() { // to allow concurrent compilation? auto TSM = llvm::orc::ThreadSafeModule(std::move(M), TSC); ExitOnErr(JIT->addIRModule(std::move(TSM))); - for (auto& fix : jitFixup) { - auto symbol = ExitOnErr(JIT->lookup(fix.second.second)); - void* native = (void*)symbol.getAddress(); - fix.second.first->nativeCode = (NativeCode)native; - } + for (auto& fix : jitFixup) + fix.second.first->lazyCodeHandle(fix.second.second.str()); } void PirJitLLVM::compile( @@ -437,9 +437,7 @@ void PirJitLLVM::compile( target->pirTypeFeedback(funCompiler.pirTypeFeedback); if (funCompiler.hasArgReordering()) target->arglistOrder(ArglistOrder::New(funCompiler.getArgReordering())); - // can we use llvm::StringRefs? - jitFixup.emplace(code, - std::make_pair(target, funCompiler.fun->getName().str())); + jitFixup.emplace(code, std::make_pair(target, funCompiler.fun->getName())); log.LLVMBitcode([&](std::ostream& out, bool tty) { bool debug = true; @@ -555,9 +553,9 @@ void PirJitLLVM::initializeLLVM() { // name. symbols starting with "ept_" are external pointers, the ones // starting with "efn_" are external function pointers. these must exist in // the host process. - class ExtSymbolGenerator : public llvm::orc::JITDylib::DefinitionGenerator { + class ExtSymbolGenerator : public llvm::orc::DefinitionGenerator { public: - Error tryToGenerate(LookupKind K, JITDylib& JD, + Error tryToGenerate(LookupState& LS, LookupKind K, JITDylib& JD, JITDylibLookupFlags JDLookupFlags, const SymbolLookupSet& LookupSet) override { orc::SymbolMap NewSymbols; diff --git a/rir/src/compiler/native/pir_jit_llvm.h b/rir/src/compiler/native/pir_jit_llvm.h index bd965140d..5980b8ba3 100644 --- a/rir/src/compiler/native/pir_jit_llvm.h +++ b/rir/src/compiler/native/pir_jit_llvm.h @@ -10,6 +10,7 @@ #include "compiler/pir/promise.h" #include "compiler/util/visitor.h" +#include "llvm/ExecutionEngine/Orc/LLJIT.h" #include "llvm/IR/DIBuilder.h" #include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" @@ -40,6 +41,7 @@ using PromMap = std::unordered_map>; // addresses for PIR builtins. class PirJitLLVM { public: + static std::unique_ptr JIT; explicit PirJitLLVM(const std::string& name); PirJitLLVM(const PirJitLLVM&) = delete; PirJitLLVM(PirJitLLVM&&) = delete; @@ -67,14 +69,14 @@ class PirJitLLVM { // Directory of all functions and builtins std::unordered_map funs; - // We prepend `rsh_` to all user functions, as a mechanism to - // differentiate them from builtins. We also append `.N` to all - // definitions in module N. Builtins will be declared in the module with + // We prepend `rshN_` to all user functions, as a mechanism to + // differentiate them from builtins. `N` denotes that the definition + // belongs to module N. Builtins will be declared in the module with // their original names (Note: LLVM might still rename things in the // same module to make the names unique) static std::string makeName(Code* c) { std::stringstream ss; - ss << "rsh_"; + ss << "rsh" << nModules << "_"; if (auto cls = ClosureVersion::Cast(c)) { ss << cls->name(); } else if (auto p = Promise::Cast(c)) { @@ -82,11 +84,10 @@ class PirJitLLVM { } else { assert(false); } - ss << "." << nModules; - return ss.str(); + return ss.str().substr(0, rir::Code::MAX_CODE_HANDLE_LENGTH - 6); } - std::unordered_map> jitFixup; + std::unordered_map> jitFixup; void finalizeAndFixup(); static size_t nModules; diff --git a/rir/src/compiler/opt/assumptions.cpp b/rir/src/compiler/opt/assumptions.cpp index 8404f73c7..916aedfcd 100644 --- a/rir/src/compiler/opt/assumptions.cpp +++ b/rir/src/compiler/opt/assumptions.cpp @@ -169,8 +169,7 @@ struct AvailableAssumptions return res; } const SmallSet at(Instruction* i) const { - auto res = StaticAnalysis::at< - StaticAnalysis::PositioningStyle::BeforeInstruction>(i); + auto res = before(i); return res.available; } }; @@ -203,7 +202,9 @@ bool OptimizeAssumptions::apply(Compiler&, ClosureVersion* vers, Code* code, hoistAssume; bool anyChange = false; - Visitor::runPostChange(code->entry, [&](BB* bb) { + Visitor::runPostChange(code->entry, [&checkpoint, &assumptions, &dom, + &replaced, &hoistAssume, + &anyChange](BB* bb) { auto ip = bb->begin(); while (ip != bb->end()) { auto next = ip + 1; diff --git a/rir/src/compiler/opt/cleanup.cpp b/rir/src/compiler/opt/cleanup.cpp index 6671b4a21..9703289e7 100644 --- a/rir/src/compiler/opt/cleanup.cpp +++ b/rir/src/compiler/opt/cleanup.cpp @@ -97,6 +97,20 @@ bool Cleanup::apply(Compiler&, ClosureVersion* cls, Code* code, lgl->replaceUsesWith(lgl->arg(0).val()); next = bb->remove(ip); } + } else if (auto idx = AsSwitchIdx::Cast(i)) { + if (idx->arg(0).val()->type.isA( + PirType::simpleScalarInt().notNAOrNaN())) { + removed = true; + idx->replaceUsesWith(idx->val()); + next = bb->remove(ip); + } + } else if (auto seq = ToForSeq::Cast(i)) { + Value* arg = seq->arg<0>().val(); + if (!arg->type.maybeObj()) { + removed = true; + seq->replaceUsesWith(arg); + next = bb->remove(ip); + } } else if (auto missing = ChkMissing::Cast(i)) { Value* arg = missing->arg<0>().val(); if (!arg->type.maybeMissing()) { @@ -139,6 +153,10 @@ bool Cleanup::apply(Compiler&, ClosureVersion* cls, Code* code, tt->replaceUsesWith(True::instance()); removed = true; next = bb->remove(ip); + } else if (!arg->type.maybe(tt->typeTest)) { + tt->replaceUsesWith(False::instance()); + removed = true; + next = bb->remove(ip); } } else if (auto tt = CastType::Cast(i)) { auto arg = tt->arg<0>().val(); diff --git a/rir/src/compiler/opt/cleanup_checkpoints.cpp b/rir/src/compiler/opt/cleanup_checkpoints.cpp index 6a621fcc1..daf617617 100644 --- a/rir/src/compiler/opt/cleanup_checkpoints.cpp +++ b/rir/src/compiler/opt/cleanup_checkpoints.cpp @@ -9,33 +9,34 @@ namespace pir { bool CleanupCheckpoints::apply(Compiler&, ClosureVersion* cls, Code* code, LogStream&) const { bool anyChange = false; - std::unordered_set used; - Visitor::run(code->entry, [&](Instruction* i) { - if (auto a = Assume::Cast(i)) { - used.insert(a->checkpoint()); - } - }); + std::unordered_set used; + Visitor::run(code->entry, [&](Instruction* i) { + if (auto a = Assume::Cast(i)) { + used.insert(a->checkpoint()); + } + }); - std::unordered_set toDelete; - Visitor::run(code->entry, [&](BB* bb) { - if (bb->isEmpty()) - return; - if (auto cp = Checkpoint::Cast(bb->last())) { - if (!used.count(cp)) { - toDelete.insert(bb->deoptBranch()); - assert(bb->deoptBranch()->isExit() && - "deopt blocks should be just one BB"); - bb->remove(bb->end() - 1); - bb->convertBranchToJmp(true); - } + std::unordered_set toDelete; + Visitor::run(code->entry, [&](BB* bb) { + if (bb->isEmpty()) + return; + if (auto cp = Checkpoint::Cast(bb->last())) { + if (!used.count(cp)) { + toDelete.insert(bb->deoptBranch()); + assert(bb->deoptBranch()->isExit() && + "deopt blocks should be just one BB"); + bb->remove(bb->end() - 1); + bb->convertBranchToJmp(true); } - }); - if (!toDelete.empty()) - anyChange = true; - // Deopt blocks are exit blocks. They have no other predecessors and - // are not phi inputs. We can delete without further checks. - for (auto bb : toDelete) - delete bb; + } + }); + if (!toDelete.empty()) + anyChange = true; + // Deopt blocks are exit blocks. They have no other predecessors and + // are not phi inputs. We can delete without further checks. + for (auto bb : toDelete) { + delete bb; + } return anyChange; } } // namespace pir diff --git a/rir/src/compiler/opt/constantfold.cpp b/rir/src/compiler/opt/constantfold.cpp index af2905d5a..8157c874c 100644 --- a/rir/src/compiler/opt/constantfold.cpp +++ b/rir/src/compiler/opt/constantfold.cpp @@ -14,36 +14,45 @@ #include #include #include +#include #include namespace rir { namespace pir { -static SEXP isConst(Value* instr) { +static SEXP isConst(Value* instr, Preserve& p) { instr = instr->followCastsAndForce(); + if (auto cst = LdConst::Cast(instr)) + return cst->c(); + if (instr->asRValue() && instr != MissingArg::instance()) return instr->asRValue(); - if (auto cst = LdConst::Cast(instr)) { - return cst->c(); - } return nullptr; } + +SEXP qt(SEXP c, Preserve& p) { + if (TYPEOF(c) == SYMSXP) + return p.operator()(Rf_lang2(symbol::quote, c)); + return c; +} + #define FOLD_BINARY_NATIVE(Instruction, Operation) \ do { \ if (auto instr = Instruction::Cast(i)) { \ - if (auto lhs = isConst(instr->arg<0>().val())) { \ - if (auto rhs = isConst(instr->arg<1>().val())) { \ - auto res = \ - Rf_eval(Rf_lang3(Operation, lhs, rhs), R_BaseEnv); \ + if (auto lhs = isConst(instr->arg<0>().val(), p)) { \ + if (auto rhs = isConst(instr->arg<1>().val(), p)) { \ + auto res = Rf_eval( \ + p(Rf_lang3(Operation, qt(lhs, p), qt(rhs, p))), \ + R_BaseEnv); \ if (res == R_TrueValue || res == R_FalseValue) { \ instr->replaceUsesWith( \ res == R_TrueValue ? (Value*)True::instance() \ : (Value*)False::instance()); \ next = bb->remove(ip); \ } else { \ - cmp.preserve(res); \ + p(res); \ auto resi = new LdConst(res); \ anyChange = true; \ instr->replaceUsesWith(resi); \ @@ -56,15 +65,15 @@ static SEXP isConst(Value* instr) { #define FOLD_UNARY(Instruction, Operation) \ do { \ if (auto instr = Instruction::Cast(i)) { \ - if (auto arg = isConst(instr->arg<0>().val())) \ + if (auto arg = isConst(instr->arg<0>().val(), p)) \ Operation(arg); \ } \ } while (false) #define FOLD_BINARY(Instruction, Operation) \ do { \ if (auto instr = Instruction::Cast(i)) { \ - if (auto lhs = isConst(instr->arg<0>().val())) { \ - if (auto rhs = isConst(instr->arg<1>().val())) { \ + if (auto lhs = isConst(instr->arg<0>().val(), p)) { \ + if (auto rhs = isConst(instr->arg<1>().val(), p)) { \ Operation(lhs, rhs); \ } \ } \ @@ -73,11 +82,11 @@ static SEXP isConst(Value* instr) { #define FOLD_BINARY_EITHER(Instruction, Operation) \ do { \ if (auto instr = Instruction::Cast(i)) { \ - if (auto lhs = isConst(instr->arg<0>().val())) { \ + if (auto lhs = isConst(instr->arg<0>().val(), p)) { \ if (Operation(lhs, instr->arg<1>().val())) \ break; \ } \ - if (auto rhs = isConst(instr->arg<1>().val())) { \ + if (auto rhs = isConst(instr->arg<1>().val(), p)) { \ Operation(rhs, instr->arg<0>().val()); \ } \ } \ @@ -158,11 +167,12 @@ bool Constantfold::apply(Compiler& cmp, ClosureVersion* cls, Code* code, bool anyChange = false; + Preserve p; std::unordered_map branchRemoval; - DominanceGraph dom(code); - DominanceFrontier dfront(code, dom); { + DominanceGraph dom(code); + std::unique_ptr dfront; // Branch Elimination // // Given branch `a` and `b`, where both have the same @@ -190,27 +200,23 @@ bool Constantfold::apply(Compiler& cmp, ClosureVersion* cls, Code* code, } } }); - std::unordered_set removed; for (auto& c : condition) { - - removed.clear(); + std::unordered_set removed; auto& uses = c.second; if (uses.size() > 1) { - for (auto a = uses.begin(); (a + 1) != uses.end(); a++) { - if (removed.count(*a)) continue; - PhiPlacement* pl = nullptr; auto phisPlaced = false; + std::unique_ptr pl; std::unordered_map newPhisByBB; newPhisByBB.clear(); for (auto b = a + 1; b != uses.end(); b++) { - if (removed.count(*b)) continue; + auto bb1 = (*a)->bb(); auto bb2 = (*b)->bb(); if (dom.dominates(bb1, bb2)) { @@ -221,7 +227,6 @@ bool Constantfold::apply(Compiler& cmp, ClosureVersion* cls, Code* code, anyChange = true; (*b)->arg(0).val() = False::instance(); } else { - if (!phisPlaced) { // create and place phi std::unordered_map inputs; @@ -229,8 +234,14 @@ bool Constantfold::apply(Compiler& cmp, ClosureVersion* cls, Code* code, True::instance(); inputs[bb1->falseBranch()] = False::instance(); - pl = new PhiPlacement(code, inputs, dom, - dfront); + if (!dfront) + dfront = + std::make_unique( + code, dom); + assert(!pl); + pl = std::make_unique( + code, inputs, dom, *dfront); + assert(pl); assert(pl->placement.size() > 0); anyChange = true; @@ -268,25 +279,18 @@ bool Constantfold::apply(Compiler& cmp, ClosureVersion* cls, Code* code, assert(pl->dominatingPhi.count(bb2) > 0); auto phi = newPhisByBB.at( pl->dominatingPhi.at(bb2)); - (*b)->arg(0).val() = phi; } } } removed.insert(*b); } - - if (pl != nullptr) { - delete pl; - pl = nullptr; - } } } } } - DominanceGraph::BBSet dead; - DominanceGraph::BBSet unreachableEnd; + DominanceGraph::BBSet newUnreachable; for (auto i = 0; i < 2; ++i) { bool iterAnyChange = false; Visitor::run(code->entry, [&](BB* bb) { @@ -303,21 +307,12 @@ bool Constantfold::apply(Compiler& cmp, ClosureVersion* cls, Code* code, ip = bb->insert(ip + 1, new Unreachable()) + 1; while (ip != bb->end()) ip = bb->remove(ip); - for (auto b : bb->successors()) { - bool isdead = true; - if (b->predecessors().size() != 1) - for (auto p : b->predecessors()) - if (!dead.count(p)) - isdead = false; - if (isdead) - dead.insert(b); - } - unreachableEnd.insert(bb); + newUnreachable.insert(bb); next = bb->end(); }; auto foldLglCmp = [&](SEXP carg, Value* varg, bool isEq) { - if (!isConst(varg) && // If this is true, was already folded + if (!isConst(varg, p) && // was already folded IS_SIMPLE_SCALAR(carg, LGLSXP) && varg->type.isA(PirType::simpleScalarLogical())) { int larg = *LOGICAL(carg); @@ -375,9 +370,9 @@ bool Constantfold::apply(Compiler& cmp, ClosureVersion* cls, Code* code, : (Value*)False::instance(); i->replaceUsesWith(replace); next = bb->remove(ip); - } else if (isConst(a) && - convertsToLogicalWithoutWarning(isConst(a))) { - auto replace = Rf_asLogical(isConst(a)) == TRUE + } else if (isConst(a, p) && + convertsToLogicalWithoutWarning(isConst(a, p))) { + auto replace = Rf_asLogical(isConst(a, p)) == TRUE ? (Value*)True::instance() : (Value*)False::instance(); i->replaceUsesWith(replace); @@ -462,6 +457,17 @@ bool Constantfold::apply(Compiler& cmp, ClosureVersion* cls, Code* code, FOLD_BINARY_EITHER(Neq, [&](SEXP carg, Value* varg) { return foldLglCmp(carg, varg, false); }); + FOLD_UNARY(Minus, [&](SEXP arg) { + auto t = TYPEOF(arg); + if (!isObject(arg) && + (t == INTSXP || t == LGLSXP || t == REALSXP)) { + auto res = + Rf_eval(Rf_lang2(symbol::Sub, arg), R_BaseEnv); + auto c = new LdConst(res); + i->replaceUsesAndSwapWith(c, ip); + iterAnyChange = true; + } + }); FOLD_UNARY(AsLogical, [&](SEXP arg) { if (convertsToLogicalWithoutWarning(arg)) { auto res = Rf_asLogical(arg); @@ -484,28 +490,28 @@ bool Constantfold::apply(Compiler& cmp, ClosureVersion* cls, Code* code, i->replaceUsesWith(False::instance()); next = bb->remove(ip); } - } else if (isConst(i->arg(0).val()) && - isConst(i->arg(0).val()) == R_TrueValue && + } else if (isConst(i->arg(0).val(), p) && + isConst(i->arg(0).val(), p) == R_TrueValue && i->arg(1).val()->type.isA(PirType::test())) { iterAnyChange = true; i->replaceUsesWith(i->arg(1).val()); next = bb->remove(ip); - } else if (isConst(i->arg(1).val()) && - isConst(i->arg(1).val()) == R_TrueValue && + } else if (isConst(i->arg(1).val(), p) && + isConst(i->arg(1).val(), p) == R_TrueValue && i->arg(0).val()->type.isA(PirType::test())) { iterAnyChange = true; i->replaceUsesWith(i->arg(0).val()); next = bb->remove(ip); - } else if (isConst(i->arg(0).val()) && - isConst(i->arg(0).val()) == R_FalseValue && + } else if (isConst(i->arg(0).val(), p) && + isConst(i->arg(0).val(), p) == R_FalseValue && i->arg(1).val()->type.isA(PirType::test())) { iterAnyChange = true; auto neg = new Not(i->arg(1).val(), Env::elided(), i->srcIdx); neg->type = PirType::test(); i->replaceUsesAndSwapWith(neg, ip); - } else if (isConst(i->arg(1).val()) && - isConst(i->arg(1).val()) == R_FalseValue && + } else if (isConst(i->arg(1).val(), p) && + isConst(i->arg(1).val(), p) == R_FalseValue && i->arg(0).val()->type.isA(PirType::test())) { iterAnyChange = true; auto neg = @@ -570,7 +576,7 @@ bool Constantfold::apply(Compiler& cmp, ClosureVersion* cls, Code* code, } } if (auto cl = Colon::Cast(i)) { - if (auto a = isConst(cl->arg(0).val())) { + if (auto a = isConst(cl->arg(0).val(), p)) { if (TYPEOF(a) == REALSXP && Rf_length(a) == 1 && REAL(a)[0] == (double)(int)REAL(a)[0]) { iterAnyChange = true; @@ -579,7 +585,7 @@ bool Constantfold::apply(Compiler& cmp, ClosureVersion* cls, Code* code, ip++; } } - if (auto a = isConst(cl->arg(1).val())) { + if (auto a = isConst(cl->arg(1).val(), p)) { if (TYPEOF(a) == REALSXP && Rf_length(a) == 1 && REAL(a)[0] == (double)(int)REAL(a)[0]) { iterAnyChange = true; @@ -665,12 +671,13 @@ bool Constantfold::apply(Compiler& cmp, ClosureVersion* cls, Code* code, iterAnyChange = true; i->replaceUsesWith(i->arg(0).val()); next = bb->remove(ip); - } else if (auto con = isConst(i->arg(0).val())) { + } else if (auto con = isConst(i->arg(0).val(), p)) { auto t = TYPEOF(con); if (t == REALSXP || t == INTSXP || t == LGLSXP) { - auto res = Rf_eval( - Rf_lang2(Rf_install("as.character"), con), - R_BaseEnv); + auto res = + p(Rf_eval(p(Rf_lang2(symbol::ascharacter, + qt(con, p))), + R_BaseEnv)); iterAnyChange = true; i->replaceUsesAndSwapWith(new LdConst(res), ip); } @@ -683,7 +690,7 @@ bool Constantfold::apply(Compiler& cmp, ClosureVersion* cls, Code* code, iterAnyChange = true; i->replaceUsesWith(i->arg(0).val()); next = bb->remove(ip); - } else if (auto con = isConst(i->arg(0).val())) { + } else if (auto con = isConst(i->arg(0).val(), p)) { if (IS_SIMPLE_SCALAR(con, REALSXP)) { if (REAL(con)[0] == REAL(con)[0]) { iterAnyChange = true; @@ -880,7 +887,7 @@ bool Constantfold::apply(Compiler& cmp, ClosureVersion* cls, Code* code, } if (auto not_ = Not::Cast(i)) { Value* arg = not_->arg<0>().val(); - if (auto carg = isConst(arg)) { + if (auto carg = isConst(arg, p)) { if (IS_SIMPLE_SCALAR(carg, LGLSXP) || IS_SIMPLE_SCALAR(carg, INTSXP) || IS_SIMPLE_SCALAR(carg, REALSXP)) { @@ -997,14 +1004,13 @@ bool Constantfold::apply(Compiler& cmp, ClosureVersion* cls, Code* code, } }); - if (!done && + if (!done && i->effects.includes(Effect::Error) && castRhs->arg(0).val()->type.isA( PirType(RType::integer).notNAOrNaN()) && castRhs->arg(1).val()->type.isA( PirType(RType::integer).notNAOrNaN())) { iterAnyChange = true; - i->replaceUsesWith(castRhs->arg(0).val()); - next = bb->remove(ip); + i->effects.reset(Effect::Error); } } ip = next; @@ -1025,50 +1031,57 @@ bool Constantfold::apply(Compiler& cmp, ClosureVersion* cls, Code* code, break; anyChange = true; } + + DominanceGraph::BBSet maybeDead; // Find all dead basic blocks for (const auto& e : branchRemoval) { - const auto& branch = e.first; + const auto& bb = e.first; const auto& condition = e.second; - dead.insert(condition ? branch->falseBranch() : branch->trueBranch()); - } - // If we have two blocks A,B newly ending with Unreachable and originally - // joining into C, then C is now dead. To find the block C dead, we have to - // add A and B to the dominating set of dead blocks. - DominanceGraph::BBSet toDelete; - if (unreachableEnd.empty()) { - toDelete = DominanceGraph::dominatedSet(code, dead); - } else { - auto deadAndUnreachable = dead; - deadAndUnreachable.insert(unreachableEnd.begin(), unreachableEnd.end()); - toDelete = DominanceGraph::dominatedSet(code, deadAndUnreachable); + for (auto i : *bb->getBranch(!condition)) + if (auto phi = Phi::Cast(i)) + phi->removeInputs({bb}); + bb->remove(bb->end() - 1); + maybeDead.insert(bb->getBranch(!condition)); + bb->convertBranchToJmp(condition); } - Visitor::run(code->entry, [&](Instruction* i) { - if (auto phi = Phi::Cast(i)) - phi->removeInputs(toDelete); - }); - for (auto u : unreachableEnd) { - if (!dead.count(u)) - toDelete.erase(u); + for (auto bb : newUnreachable) { + auto succ = bb->successors(); + for (auto n : succ) + for (auto i : *n) + if (auto phi = Phi::Cast(i)) + phi->removeInputs({bb}); + maybeDead.insert(succ.begin(), succ.end()); + bb->deleteSuccessors(); } - for (const auto& bb : unreachableEnd) - bb->deleteSuccessors(); + DominanceGraph::BBSet reachable; + // Mark all still reachable BBs, the rest will be surely dead + Visitor::run(code->entry, [&](BB* bb) { reachable.insert(bb); }); - for (const auto& e : branchRemoval) { - const auto& branch = e.first; - const auto& condition = e.second; - for (auto i : *branch->getBranch(!condition)) - if (auto phi = Phi::Cast(i)) - phi->removeInputs({branch}); - branch->remove(branch->end() - 1); - branch->convertBranchToJmp(condition); + DominanceGraph::BBSet dead; + for (auto bb : maybeDead) { + if (!reachable.count(bb)) { + Visitor::run(bb, [&](BB* bb) { + if (!reachable.count(bb)) + dead.insert(bb); + }); + } } + // Needs to happen in two steps in case dead bb point to dead bb - for (const auto& bb : toDelete) + for (const auto& bb : dead) { + for (auto n : bb->successors()) + if (reachable.count(n)) + for (auto i : *n) + if (auto phi = Phi::Cast(i)) + phi->removeInputs({bb}); bb->deleteSuccessors(); - for (const auto& bb : toDelete) + } + for (auto bb : dead) { + assert(!reachable.count(bb)); delete bb; + } return anyChange; } diff --git a/rir/src/compiler/opt/elide_env_spec.cpp b/rir/src/compiler/opt/elide_env_spec.cpp index 18cf1928c..87438d142 100644 --- a/rir/src/compiler/opt/elide_env_spec.cpp +++ b/rir/src/compiler/opt/elide_env_spec.cpp @@ -62,18 +62,18 @@ bool ElideEnvSpec::apply(Compiler&, ClosureVersion* cls, Code* code, assert(!arg->type.maybePromiseWrapped()); TypeFeedback seen; if (argi) - seen = argi->typeFeedback; + seen = argi->typeFeedback(); if (auto j = Instruction::Cast(arg->followCasts())) if (seen.type.isVoid() || - (!j->typeFeedback.type.isVoid() && - !seen.type.isA(j->typeFeedback.type))) - seen = j->typeFeedback; + (!j->typeFeedback().type.isVoid() && + !seen.type.isA(j->typeFeedback().type))) + seen = j->typeFeedback(); if (auto j = Instruction::Cast(arg->followCastsAndForce())) if (seen.type.isVoid() || - (!j->typeFeedback.type.isVoid() && - !seen.type.isA(j->typeFeedback.type))) - seen = j->typeFeedback; + (!j->typeFeedback().type.isVoid() && + !seen.type.isA(j->typeFeedback().type))) + seen = j->typeFeedback(); auto required = arg->type.notObject(); auto suggested = required; @@ -170,7 +170,8 @@ bool ElideEnvSpec::apply(Compiler&, ClosureVersion* cls, Code* code, if (std::find(allowed.begin(), allowed.end(), i->tag) == allowed.end() || !i->hasEnv() || i->env() != m || - (bt && !supportsFastBuiltinCall(bt->builtinSexp))) { + (bt && !supportsFastBuiltinCall(bt->builtinSexp, + bt->nCallArgs()))) { bool ok = false; if (auto mkarg = MkArg::Cast(i)) { ok = Visitor::check( @@ -279,9 +280,9 @@ bool ElideEnvSpec::apply(Compiler&, ClosureVersion* cls, Code* code, for (auto env : check.second) { if (!bannedEnvs.count(env)) { auto condition = new IsEnvStub(env); - BBTransform::insertAssume(condition, cp, true, - env->typeFeedback.srcCode, - nullptr); + BBTransform::insertAssume( + condition, cp, true, + env->typeFeedback().srcCode, nullptr); assert(cp->bb()->trueBranch() != bb); } } diff --git a/rir/src/compiler/opt/force_dominance.cpp b/rir/src/compiler/opt/force_dominance.cpp index 360ac1679..96a857ea3 100644 --- a/rir/src/compiler/opt/force_dominance.cpp +++ b/rir/src/compiler/opt/force_dominance.cpp @@ -236,18 +236,21 @@ bool ForceDominance::apply(Compiler&, ClosureVersion* cls, Code* code, } } } else { - if (FrameState::Cast(*it)) - next = bb->remove(it); // TODO: don't copy this to start with if ((*it)->frameState()) (*it)->clearFrameState(); - if (auto cp = Checkpoint::Cast(*it)) { + if (FrameState::Cast(*it)) { + next = bb->remove(it); + } else if (auto cp = + Checkpoint::Cast(*it)) { auto n = cp->nextBB(); auto d = cp->deoptBranch(); - bb->eraseLast(); + next = bb->remove(it); bb->overrideSuccessors({n}); - delete d; - next = bb->end(); + if (d->predecessors().size() == 0) { + assert(d->successors().size() == 0); + delete d; + } } } it = next; @@ -294,8 +297,8 @@ bool ForceDominance::apply(Compiler&, ClosureVersion* cls, Code* code, } // Create a return value phi of the promise - auto promRes = - BBTransform::forInline(prom_copy, split, nullptr); + auto promRes = BBTransform::forInline(prom_copy, split, + nullptr, nullptr); assert(!promRes->type.maybePromiseWrapped()); f = Force::Cast(*split->begin()); diff --git a/rir/src/compiler/opt/inline.cpp b/rir/src/compiler/opt/inline.cpp index e8818a55f..54f86ea4c 100644 --- a/rir/src/compiler/opt/inline.cpp +++ b/rir/src/compiler/opt/inline.cpp @@ -5,6 +5,7 @@ #include "R/Funtab.h" #include "R/Symbols.h" #include "R/r.h" +#include "compiler/analysis/available_checkpoints.h" #include "compiler/analysis/cfg.h" #include "compiler/parameter.h" #include "compiler/util/bb_transform.h" @@ -142,6 +143,16 @@ bool Inline::apply(Compiler&, ClosureVersion* cls, Code* code, return false; } } + if (auto c = LdConst::Cast(i)) { + if (TYPEOF(c->c()) == SPECIALSXP || + TYPEOF(c->c()) == BUILTINSXP) { + if (!SafeBuiltinsList::forInline( + c->c()->u.primsxp.offset)) { + allowInline = SafeToInline::No; + return false; + } + } + } if (auto call = CallBuiltin::Cast(i)) { if (!SafeBuiltinsList::forInline(call->builtinId)) { allowInline = SafeToInline::No; @@ -200,8 +211,8 @@ bool Inline::apply(Compiler&, ClosureVersion* cls, Code* code, } if (hasDotslistArg) weight *= 0.4; - if (!(*it)->typeFeedback.type.isVoid() && - (*it)->typeFeedback.type.unboxable()) + if (!(*it)->typeFeedback().type.isVoid() && + (*it)->typeFeedback().type.unboxable()) weight *= 0.9; // No recursive inlining @@ -352,12 +363,21 @@ bool Inline::apply(Compiler&, ClosureVersion* cls, Code* code, }); if (failedToInline) { - delete copy; + std::vector toDel; + Visitor::run(copy, [&](BB* bb) { toDel.push_back(bb); }); + for (auto bb : toDel) + delete bb; bb->overrideNext(split); inlineeCls->rirFunction()->flags.set( rir::Function::NotInlineable); } else { anyChange = true; + Checkpoint* cpAtCall = nullptr; + { + AvailableCheckpoints cp(cls, code, log); + cpAtCall = cp.at(theCall); + } + bb->overrideNext(copy); // Copy over promises used by the inner version @@ -394,7 +414,7 @@ bool Inline::apply(Compiler&, ClosureVersion* cls, Code* code, }); auto inlineeRes = BBTransform::forInline( - copy, split, inlineeCls->closureEnv()); + copy, split, inlineeCls->closureEnv(), cpAtCall); bool noNormalReturn = false; if (inlineeRes == Tombstone::unreachable()) { diff --git a/rir/src/compiler/opt/load_elision.cpp b/rir/src/compiler/opt/load_elision.cpp index 93f186629..2844cc20e 100644 --- a/rir/src/compiler/opt/load_elision.cpp +++ b/rir/src/compiler/opt/load_elision.cpp @@ -82,8 +82,7 @@ struct AvailableLoads : public StaticAnalysis> { } ALoad get(Instruction* i) const { - auto res = StaticAnalysis::at< - StaticAnalysis::PositioningStyle::BeforeInstruction>(i); + auto res = before(i); for (auto dld : res.available) { if (dld.same(i)) return dld; diff --git a/rir/src/compiler/opt/match_call_args.cpp b/rir/src/compiler/opt/match_call_args.cpp index f9432bf12..f39800543 100644 --- a/rir/src/compiler/opt/match_call_args.cpp +++ b/rir/src/compiler/opt/match_call_args.cpp @@ -1,8 +1,12 @@ #include "R/Symbols.h" #include "compiler/compiler.h" +#include "compiler/parameter.h" #include "compiler/pir/pir_impl.h" #include "compiler/util/arg_match.h" #include "compiler/util/visitor.h" +#include "interpreter/instance.h" +#include "interpreter/interp_incl.h" +#include "ir/Compiler.h" #include "pass_definitions.h" #include "runtime/DispatchTable.h" @@ -17,59 +21,167 @@ bool MatchCallArgs::apply(Compiler& cmp, ClosureVersion* cls, Code* code, auto ip = bb->begin(); while (ip != bb->end()) { auto next = ip + 1; + auto i = *ip; - if (auto calli = CallInstruction::CastCall(*ip)) { - if (!Call::Cast(*ip) && !NamedCall::Cast(*ip)) { + if (auto calli = CallInstruction::CastCall(i)) { + SEXP usemethodTarget = nullptr; + + auto staticCall = StaticCall::Cast(i); + if (staticCall && staticCall->nCallArgs() > 0) { + auto ast = src_pool_at( + globalContext(), + staticCall->tryGetCls()->rirFunction()->body()->src); + if (CAR(ast) == symbol::UseMethod && + TYPEOF(CADR(ast)) == STRSXP && + CDDR(ast) == R_NilValue) { + bool nonObj = false; + auto testNonObj = [&](Value* v) { + if (v->type.isA(PirType::valOrLazy() + .notT(RType::prom) + .notObject())) + nonObj = true; + }; + if (auto d = + DotsList::Cast(staticCall->callArg(0).val())) { + if (d->nargs() > 0) + testNonObj(d->arg(0).val()); + } else { + testNonObj(staticCall->callArg(0).val()); + } + if (nonObj) { + auto method = CHAR(STRING_ELT(CADR(ast), 0)); + auto defName = Rf_install( + (std::string(method) + ".default").c_str()); + auto def = Rf_findVar(defName, R_BaseEnv); + if (TYPEOF(def) == PROMSXP) + def = PRVALUE(def); + if (def && TYPEOF(def) == CLOSXP) { + usemethodTarget = def; + } + } else { + ip = next; + continue; + } + } + } + + if (!Call::Cast(i) && !NamedCall::Cast(i) && !usemethodTarget) { ip = next; continue; } SEXP formals = nullptr; ClosureVersion* target = nullptr; - if (auto cls = calli->tryGetCls()) { - target = calli->tryDispatch(cls); - formals = cls->formals().original(); - } - if (!target) { - if (auto cnst = LdConst::Cast(calli->tryGetClsArg())) { - if (TYPEOF(cnst->c()) == CLOSXP) - formals = FORMALS(cnst->c()); + + if (usemethodTarget) { + formals = FORMALS(usemethodTarget); + } else { + if (auto cls = calli->tryGetCls()) { + target = calli->tryDispatch(cls); + formals = cls->formals().original(); } - if (auto mk = MkFunCls::Cast(calli->tryGetClsArg())) { - formals = mk->formals; + if (!target) { + if (auto cnst = LdConst::Cast(calli->tryGetClsArg())) { + if (TYPEOF(cnst->c()) == CLOSXP) + formals = FORMALS(cnst->c()); + } + if (auto mk = MkFunCls::Cast(calli->tryGetClsArg())) { + formals = mk->formals; + } } } std::vector matchedArgs; ArglistOrder::CallArglistOrder argOrderOrig; - auto call = Call::Cast(*ip); - auto namedCall = NamedCall::Cast(*ip); + auto call = Call::Cast(i); + auto namedCall = NamedCall::Cast(i); bool staticallyArgmatched = false; if (formals) { - staticallyArgmatched = ArgumentMatcher::reorder( - [&](DotsList* d) { - ip = bb->insert(ip, d) + 1; - next = ip + 1; - }, - formals, - {[&]() { return calli->nCallArgs(); }, - [&](size_t i) { return calli->callArg(i).val(); }, - [&](size_t i) { - SLOWASSERT(!namedCall || - i < namedCall->names.size()); - return namedCall ? namedCall->names[i] - : R_NilValue; - }}, - matchedArgs, argOrderOrig); + bool failed = false; + std::vector> usemethodTargetArgs; + if (usemethodTarget) { + const auto& myFormals = + staticCall->tryGetCls()->formals(); + size_t i = 0; + staticCall->eachCallArg([&](Value* v) { + if (v == MissingArg::instance()) { + if (myFormals.hasDefaultArgs() && + myFormals.nargs() > i) { + auto def = myFormals.defaultArgs().at(i); + if (TYPEOF(def) != LANGSXP && + TYPEOF(def) != SYMSXP && + TYPEOF(def) != BCODESXP && + TYPEOF(def) != EXTERNALSXP) { + auto defA = new LdConst(def); + ip = bb->insert(ip, defA) + 1; + next = ip + 1; + usemethodTargetArgs.push_back( + {myFormals.names().at(i), defA}); + } else { + failed = true; + } + } else { + failed = true; + } + } else if (auto d = DotsList::Cast(v)) { + d->eachElement([&](SEXP n, Value* v) { + usemethodTargetArgs.push_back({n, v}); + }); + } else { + auto n = myFormals.nargs() > i + ? myFormals.names()[i] + : R_NilValue; + usemethodTargetArgs.push_back({n, v}); + } + i++; + }); + } + staticallyArgmatched = + !failed && + ArgumentMatcher::reorder( + [&](DotsList* d) { + ip = bb->insert(ip, d) + 1; + next = ip + 1; + }, + formals, + {[&]() { + if (usemethodTarget) + return usemethodTargetArgs.size(); + return calli->nCallArgs(); + }, + [&](size_t i) -> Value* { + if (usemethodTarget) + return usemethodTargetArgs[i].second; + return calli->callArg(i).val(); + }, + [&](size_t i) -> SEXP { + if (usemethodTarget) + return usemethodTargetArgs[i].first; + SLOWASSERT(!namedCall || + i < namedCall->names.size()); + return namedCall ? namedCall->names[i] + : R_NilValue; + }}, + matchedArgs, argOrderOrig); } Context asmpt; if (staticallyArgmatched) { - Call fake((*ip)->env(), calli->tryGetClsArg(), matchedArgs, - Tombstone::framestate(), (*ip)->srcIdx); - asmpt = fake.inferAvailableAssumptions(); + if (usemethodTarget) { + if (!DispatchTable::check(BODY(usemethodTarget))) + rir::Compiler::compileClosure(usemethodTarget); + + LdConst cls(usemethodTarget); + Call fake(i->env(), &cls, matchedArgs, + Tombstone::framestate(), i->srcIdx); + asmpt = fake.inferAvailableAssumptions(); + } else { + Call fake(i->env(), calli->tryGetClsArg(), matchedArgs, + Tombstone::framestate(), i->srcIdx); + asmpt = fake.inferAvailableAssumptions(); + } // We can add these because arguments will be statically // matched @@ -82,13 +194,25 @@ bool MatchCallArgs::apply(Compiler& cmp, ClosureVersion* cls, Code* code, asmpt.remove(Assumption::NoExplicitlyMissingArgs); asmpt.numMissing(Rf_length(formals) - matchedArgs.size()); - if (auto cnst = LdConst::Cast(calli->tryGetClsArg())) { - if (DispatchTable::check(BODY(cnst->c()))) + if (usemethodTarget) { + if (!DispatchTable::check(BODY(usemethodTarget))) + rir::Compiler::compileClosure(usemethodTarget); + if (DispatchTable::check(BODY(usemethodTarget))) cmp.compileClosure( - cnst->c(), "unknown--fromConstant", asmpt, + usemethodTarget, "unknown--fromOverride", asmpt, false, [&](ClosureVersion* fun) { target = fun; }, []() {}, {}); + } else if (auto cnst = LdConst::Cast(calli->tryGetClsArg())) { + if (auto dt = DispatchTable::check(BODY(cnst->c()))) { + if (dt->baseline()->body()->codeSize < + Parameter::RECOMPILE_THRESHOLD) + cmp.compileClosure( + cnst->c(), "unknown--fromConstant", asmpt, + false, + [&](ClosureVersion* fun) { target = fun; }, + []() {}, {}); + } } else if (auto mk = MkFunCls::Cast(calli->tryGetClsArg())) { if (auto cls = mk->tryGetCls()) @@ -96,13 +220,16 @@ bool MatchCallArgs::apply(Compiler& cmp, ClosureVersion* cls, Code* code, auto dt = mk->originalBody; if (!target && dt) { auto srcRef = mk->srcRef; - cmp.compileFunction(dt, "unknown--fromMkFunCls", - formals, srcRef, asmpt, - [&](ClosureVersion* fun) { - mk->setCls(fun->owner()); - target = fun; - }, - []() {}, {}); + if (dt->baseline()->body()->codeSize < + Parameter::RECOMPILE_THRESHOLD) + cmp.compileFunction(dt, "unknown--fromMkFunCls", + formals, srcRef, asmpt, + [&](ClosureVersion* fun) { + mk->setCls( + fun->owner()); + target = fun; + }, + []() {}, {}); } } } @@ -110,16 +237,28 @@ bool MatchCallArgs::apply(Compiler& cmp, ClosureVersion* cls, Code* code, if (staticallyArgmatched && target) { anyChange = true; if (auto c = call) { + assert(!usemethodTarget); + auto cls = c->cls()->followCastsAndForce(); auto nc = new StaticCall( c->env(), target->owner(), asmpt, matchedArgs, - std::move(argOrderOrig), c->frameStateOrTs(), - c->srcIdx, c->cls()->followCastsAndForce()); + argOrderOrig, c->frameStateOrTs(), c->srcIdx, cls); (*ip)->replaceUsesAndSwapWith(nc, ip); } else if (auto c = namedCall) { + assert(!usemethodTarget); + auto cls = c->cls()->followCastsAndForce(); + auto nc = new StaticCall( + c->env(), target->owner(), asmpt, matchedArgs, + argOrderOrig, c->frameStateOrTs(), c->srcIdx, cls); + (*ip)->replaceUsesAndSwapWith(nc, ip); + } else if (auto c = staticCall) { + assert(usemethodTarget); + ip = bb->insert(ip, new LdConst(usemethodTarget)); + auto cls = *ip; + ip++; + next = ip + 1; auto nc = new StaticCall( c->env(), target->owner(), asmpt, matchedArgs, - std::move(argOrderOrig), c->frameStateOrTs(), - c->srcIdx, c->cls()->followCastsAndForce()); + argOrderOrig, c->frameStateOrTs(), c->srcIdx, cls); (*ip)->replaceUsesAndSwapWith(nc, ip); } else { assert(false); diff --git a/rir/src/compiler/opt/pass_scheduler.cpp b/rir/src/compiler/opt/pass_scheduler.cpp index 9c749a4b3..c8cea8de6 100644 --- a/rir/src/compiler/opt/pass_scheduler.cpp +++ b/rir/src/compiler/opt/pass_scheduler.cpp @@ -1,4 +1,5 @@ #include "pass_scheduler.h" +#include "compiler/parameter.h" #include "pass_definitions.h" namespace rir { @@ -20,6 +21,9 @@ void PassScheduler::add(std::unique_ptr&& t) { currentPhase->passes.push_back(std::move(t)); } +unsigned Parameter::PIR_OPT_LEVEL = + getenv("PIR_OPT_LEVEL") ? atoi(getenv("PIR_OPT_LEVEL")) : 2; + PassScheduler::PassScheduler() { auto addDefaultOpt = [&]() { add(); @@ -63,7 +67,7 @@ PassScheduler::PassScheduler() { add(); }; - nextPhase("Initial", 60); + nextPhase("Initial", Parameter::PIR_OPT_LEVEL > 1 ? 60 : 0); addDefaultOpt(); nextPhase("Initial post"); addDefaultPostPhaseOpt(); @@ -72,41 +76,45 @@ PassScheduler::PassScheduler() { // // This pass is scheduled second, since we want to first try to do this // statically in Phase 1 - nextPhase("Speculation", 100); + nextPhase("Speculation", Parameter::PIR_OPT_LEVEL > 1 ? 100 : 0); add(); addDefaultOpt(); add(); - nextPhase("Speculation post"); - addDefaultPostPhaseOpt(); - - // ==== Phase 3) Remove checkpoints we did not use - // - // This pass removes unused checkpoints. - // We schedule this pass here, since it might unblock optimizations. - // Since for example even unused checkpoints keep variables live. - // - // After this phase it is no longer possible to add assumptions at any point - nextPhase("Remove CP"); - add(); - addDefaultPostPhaseOpt(); - - nextPhase("Intermediate 2", 60); - addDefaultOpt(); - nextPhase("Intermediate 2 post"); - addDefaultPostPhaseOpt(); - - // ==== Phase 3.1) Remove Framestates we did not use - // - // Framestates can be used by call instructions. This pass removes this - // dependency and the framestates will subsequently be cleaned. - // - // After this pass it is no longer possible to inline callees with deopts - nextPhase("Cleanup FS"); - add(); - add(); - - nextPhase("Final", 120); + if (Parameter::PIR_OPT_LEVEL > 0) { + nextPhase("Speculation post"); + addDefaultPostPhaseOpt(); + + // ==== Phase 3) Remove checkpoints we did not use + // + // This pass removes unused checkpoints. + // We schedule this pass here, since it might unblock optimizations. + // Since for example even unused checkpoints keep variables live. + // + // After this phase it is no longer possible to add assumptions at any + // point + nextPhase("Remove CP"); + add(); + addDefaultPostPhaseOpt(); + + nextPhase("Intermediate 2", Parameter::PIR_OPT_LEVEL > 1 ? 60 : 0); + addDefaultOpt(); + nextPhase("Intermediate 2 post"); + addDefaultPostPhaseOpt(); + + // ==== Phase 3.1) Remove Framestates we did not use + // + // Framestates can be used by call instructions. This pass removes this + // dependency and the framestates will subsequently be cleaned. + // + // After this pass it is no longer possible to inline callees with + // deopts + nextPhase("Cleanup FS"); + add(); + add(); + + nextPhase("Final", Parameter::PIR_OPT_LEVEL > 1 ? 120 : 0); + } // ==== Phase 4) Final round of default opts addDefaultOpt(); add(); @@ -114,6 +122,7 @@ PassScheduler::PassScheduler() { nextPhase("Final post"); addDefaultPostPhaseOpt(); + add(); // Backend relies on the dead assume removal here add(); add(); diff --git a/rir/src/compiler/opt/scope_resolution.cpp b/rir/src/compiler/opt/scope_resolution.cpp index 415bcb2d2..98651adeb 100644 --- a/rir/src/compiler/opt/scope_resolution.cpp +++ b/rir/src/compiler/opt/scope_resolution.cpp @@ -5,6 +5,7 @@ #include "../util/safe_builtins_list.h" #include "../util/visitor.h" #include "R/r.h" +#include "compiler/analysis/context_stack.h" #include "compiler/util/bb_transform.h" #include "pass_definitions.h" #include "utils/Set.h" @@ -97,6 +98,7 @@ bool ScopeResolution::apply(Compiler&, ClosureVersion* cls, Code* code, DominanceGraph dom(code); DominanceFrontier dfront(code, dom); + ContextStack contexts(cls, code, log); bool anyChange = false; ScopeAnalysis analysis(cls, code, log); @@ -248,290 +250,260 @@ bool ScopeResolution::apply(Compiler&, ClosureVersion* cls, Code* code, return target; }; - Visitor::run( - code->entry, [&](BB* bb) { - auto ip = bb->begin(); - while (ip != bb->end()) { - Instruction* i = *ip; - auto next = ip + 1; + Visitor::run(code->entry, [&](BB* bb) { + if (bb->isEmpty()) + return; - auto before = analysis.at(i); - auto after = analysis.at(i); + auto before = analysis.before(*bb->begin()); + auto after = before; + bool changed = false; - // Force and callees can only see our env only through - // reflection - if (i->hasEnv() && - (CallInstruction::CastCall(i) || Force::Cast(i))) { - if (after.noReflection()) { - i->elideEnv(); - i->effects.reset(Effect::Reflection); - } - if (after.envNotEscaped(i->env())) { - i->effects.reset(Effect::LeaksEnv); - } - } + auto ip = bb->begin(); + while (ip != bb->end()) { + Instruction* i = *ip; + auto next = ip + 1; - if (auto mk = MkArg::Cast(i)) { - if (!mk->noReflection) - if (noReflection(cls, mk->prom(), - i->hasEnv() ? i->env() - : Env::notClosed(), - analysis, before)) - mk->noReflection = true; + if (changed) { + before = analysis.before(i); + anyChange = true; + changed = false; + } else { + before = analysis.before(i, &after); + } + after = analysis.after(i, &before); + + // Force and callees can only see our env only through + // reflection + if (i->hasEnv() && + (CallInstruction::CastCall(i) || Force::Cast(i))) { + if (after.noReflection()) { + i->elideEnv(); + i->effects.reset(Effect::Reflection); + changed = true; } + if (after.envNotEscaped(i->env()) && + i->effects.includes(Effect::LeaksEnv)) { + changed = true; + i->effects.reset(Effect::LeaksEnv); + } + } - // If no reflective argument is passed to us, then forcing an - // argument cannot see our environment - if (auto force = Force::Cast(i)) { - auto arg = - force->arg<0>().val()->followCastsAndForce(); - analysis.lookup( - arg, [&](const AbstractPirValue& res) { - res.ifSingleValue( - [&](Value* val) { arg = val; }); - }); - if (auto ld = LdArg::Cast(arg)) { - if (force->hasEnv() && - cls->context().isNonRefl(ld->id)) { - force->elideEnv(); - force->effects.reset(Effect::Reflection); - } + if (auto mk = MkArg::Cast(i)) { + if (!mk->noReflection) + if (noReflection(cls, mk->prom(), + i->hasEnv() ? i->env() : Env::notClosed(), + analysis, before)) { + mk->noReflection = true; + changed = true; + } + } - if (after.noReflection()) { - force->type.fromContext(cls->context(), - ld->id, - cls->nargs(), true); - } - } + // If no reflective argument is passed to us, then forcing an + // argument cannot see our environment + if (auto force = Force::Cast(i)) { + auto arg = force->arg<0>().val()->followCastsAndForce(); + analysis.lookup(arg, [&](const AbstractPirValue& res) { + res.ifSingleValue([&](Value* val) { arg = val; }); + }); + if (auto ld = LdArg::Cast(arg)) { + if (force->hasEnv() && cls->context().isNonRefl(ld->id)) { + force->elideEnv(); + force->effects.reset(Effect::Reflection); + changed = true; } - // StVarSuper where the parent environment is known and - // local, can be replaced by simple StVar, if the variable - // exists in the super env. Or if the super env is the global - // env, since super assign never goes beyond that one. - if (auto sts = StVarSuper::Cast(i)) { - auto aLoad = - analysis.superLoad(before, sts->varName, sts->env()); - if (aLoad.env != AbstractREnvironment::UnknownParent) { - auto env = Env::Cast(aLoad.env); - if ((env && env->rho == R_GlobalEnv) || - (!aLoad.result.isUnknown() && - aLoad.env->validIn(code))) { - auto r = - new StVar(sts->varName, sts->val(), aLoad.env); - bb->replace(ip, r); - sts->replaceUsesWith(r); - replacedValue[sts] = r; - anyChange = true; - } + if (after.noReflection()) { + force->type.fromContext(cls->context(), ld->id, + cls->nargs(), true); } - ip = next; - continue; } + } - // Constant fold "missing" if we can. - if (auto missing = Missing::Cast(i)) { - auto res = - analysis.load(before, missing->varName, missing->env()); - bool notMissing = false; - if (res.result.isSingleValue()) { - auto v = - res.result.singleValue().val->followCastsAndForce(); - if (!v->type.maybePromiseWrapped() && - !v->type.maybeMissing() && - /* Warning: Forcing a (non-missing) promise can - still return missing... */ - !MkArg::Cast(v)) { - notMissing = true; - } - // If we find the (eager) root promise, we know if it is - // missing or not! Note this doesn't go throught forces. - if (auto mk = MkArg::Cast( - res.result.singleValue().val->followCasts())) { - if (mk->isEager() && - mk->eagerArg() != MissingArg::instance()) - notMissing = true; - } + // StVarSuper where the parent environment is known and + // local, can be replaced by simple StVar, if the variable + // exists in the super env. Or if the super env is the global + // env, since super assign never goes beyond that one. + if (auto sts = StVarSuper::Cast(i)) { + auto aLoad = + analysis.superLoad(before, sts->varName, sts->env()); + if (aLoad.env != AbstractREnvironment::UnknownParent) { + auto env = Env::Cast(aLoad.env); + if ((env && env->rho == R_GlobalEnv) || + (!aLoad.result.isUnknown() && + aLoad.env->validIn(code))) { + auto r = new StVar(sts->varName, sts->val(), aLoad.env); + bb->replace(ip, r); + sts->replaceUsesWith(r); + replacedValue[sts] = r; + changed = true; } - if (!res.result.type.maybeMissing() && - !res.result.type.maybePromiseWrapped()) { + } + ip = next; + continue; + } + + // Constant fold "missing" if we can. + if (auto missing = Missing::Cast(i)) { + auto res = + analysis.load(before, missing->varName, missing->env()); + bool notMissing = false; + if (res.result.isSingleValue()) { + auto v = + res.result.singleValue().val->followCastsAndForce(); + if (!v->type.maybePromiseWrapped() && + !v->type.maybeMissing() && + /* Warning: Forcing a (non-missing) promise can + still return missing... */ + !MkArg::Cast(v)) { notMissing = true; } + // If we find the (eager) root promise, we know if it is + // missing or not! Note this doesn't go throught forces. + if (auto mk = MkArg::Cast( + res.result.singleValue().val->followCasts())) { + if (mk->isEager() && + mk->eagerArg() != MissingArg::instance()) + notMissing = true; + } + } + if (!res.result.type.maybeMissing() && + !res.result.type.maybePromiseWrapped()) { + notMissing = true; + } - if (notMissing) { - // Missing still returns TRUE, if the argument was - // initially missing, but then overwritten by a default - // argument. - if (auto env = MkEnv::Cast(missing->env())) { - bool initiallyMissing = false; - env->eachLocalVar( - [&](SEXP name, Value* val, bool m) { - if (name == missing->varName) - initiallyMissing = m; - }); - if (!initiallyMissing) { - missing->replaceUsesWith(False::instance()); - replacedValue[missing] = False::instance(); - next = bb->remove(ip); - anyChange = true; - } - } - } else { - res.result.ifSingleValue([&](Value* v) { - if (v == MissingArg::instance()) { - missing->replaceUsesWith(True::instance()); - replacedValue[missing] = True::instance(); - next = bb->remove(ip); - anyChange = true; - } + if (notMissing) { + // Missing still returns TRUE, if the argument was + // initially missing, but then overwritten by a default + // argument. + if (auto env = MkEnv::Cast(missing->env())) { + bool initiallyMissing = false; + env->eachLocalVar([&](SEXP name, Value* val, bool m) { + if (name == missing->varName) + initiallyMissing = m; }); + if (!initiallyMissing) { + missing->replaceUsesWith(False::instance()); + replacedValue[missing] = False::instance(); + next = bb->remove(ip); + changed = true; + } } + } else { + res.result.ifSingleValue([&](Value* v) { + if (v == MissingArg::instance()) { + missing->replaceUsesWith(True::instance()); + replacedValue[missing] = True::instance(); + next = bb->remove(ip); + changed = true; + } + }); } + } - if (bb->isDeopt()) { - if (auto fs = FrameState::Cast(i)) { - if (auto mk = MkEnv::Cast(fs->env())) { - std::unordered_set allowed( - {Tag::FrameState, Tag::StVar, Tag::IsEnvStub}); - if (!mk->stub) - allowed.insert(Tag::LdVar); - if (mk->context == 1 && mk->bb() != bb && - mk->usesAreOnly(code->entry, allowed)) { - analysis.tryMaterializeEnv( - before, mk, - [&](const std::unordered_map< - SEXP, std::pair>& env) { - std::vector names; - std::vector values; - std::vector missing; - for (auto& e : env) { - names.push_back(e.first); - auto v = e.second.first; - auto miss = e.second.second; - if (v.isUnknown()) - return; - if (auto val = - getSingleLocalValue(v)) { - values.push_back(val); - } else { - Value* phi = nullptr; - for (auto& c : createdPhis) { - if (c.first == v) { - auto& cache = c.second; - if (cache.phis.count( - bb)) - phi = cache.phis.at( - bb); - else if ( + if (bb->isDeopt()) { + if (auto fs = FrameState::Cast(i)) { + if (auto mk = MkEnv::Cast(fs->env())) { + bool candidate = mk->bb() != bb; + // Environments which start off with a lot of + // uninitialized variables are not profitable to + // elide, because all these variables need to be + // boxed. + // TODO: implement unboxed uninitialized values + size_t unbound = 0; + if (candidate) + mk->eachLocalVar([&](SEXP, Value* v, bool) { + if (v == UnboundValue::instance()) + unbound++; + }); + if (unbound > 3) + candidate = false; + std::unordered_set allowed( + {Tag::FrameState, Tag::StVar, Tag::IsEnvStub}); + if (!mk->stub) + allowed.insert(Tag::LdVar); + if (candidate) + if (!mk->usesAreOnly(code->entry, allowed)) + candidate = false; + + if (candidate) { + analysis.tryMaterializeEnv( + before, mk, + [&](const std::unordered_map< + SEXP, std::pair>& + env) { + std::vector names; + std::vector values; + std::vector missing; + for (auto& e : env) { + names.push_back(e.first); + auto v = e.second.first; + auto miss = e.second.second; + if (v.isUnknown()) + return; + if (auto val = getSingleLocalValue(v)) { + values.push_back(val); + } else { + Value* phi = nullptr; + for (auto& c : createdPhis) { + if (c.first == v) { + auto& cache = c.second; + if (cache.phis.count(bb)) + phi = cache.phis.at(bb); + else if (cache.dominatingPhi + .count(bb)) + phi = cache.phis.at( cache.dominatingPhi - .count(bb)) - phi = cache.phis.at( - cache - .dominatingPhi - .at(bb)); - break; - } + .at(bb)); + break; } - if (!phi) { - phi = tryInsertPhis( - mk, v, bb, ip, true); - } - if (!phi) - return; - values.push_back(phi); } - missing.push_back(miss); + if (!phi) { + phi = tryInsertPhis(mk, v, bb, + ip, true); + } + if (!phi) + return; + values.push_back(phi); } - auto deoptEnv = - new MkEnv(mk->lexicalEnv(), names, - values.data(), missing); - ip = bb->insert(ip, deoptEnv); - ip++; - next = ip + 1; - mk->replaceDominatedUses(deoptEnv, dom); - anyChange = true; - }); - } - } - } - } - - analysis.lookupAt(after, i, [&](const AbstractLoad& aLoad) { - auto& res = aLoad.result; - - bool isActualLoad = - LdVar::Cast(i) || LdFun::Cast(i) || LdVarSuper::Cast(i); - - // In case the scope analysis is sure that this is - // actually the same as some other PIR value. So let's just - // replace it. - if (res.isSingleValue()) { - if (auto val = getSingleLocalValue(res)) { - if (val->type.isA(i->type)) { - if (isActualLoad && val->type.maybeMissing()) { - // LdVar checks for missingness, so we need - // to preserve this. - auto chk = new ChkMissing(val); - ip = bb->insert(ip, chk); + missing.push_back(miss); + } + auto deoptEnv = + new MkEnv(mk->lexicalEnv(), names, + values.data(), missing); + ip = bb->insert(ip, deoptEnv); ip++; - val = chk; - } - replacedValue[i] = val; - i->replaceUsesWith(val); - assert(!val->type.maybePromiseWrapped() || - i->type.maybePromiseWrapped()); - next = bb->remove(ip); - anyChange = true; - return; - } + next = ip + 1; + mk->replaceDominatedUses(deoptEnv, dom); + if (mk->context) { + auto diff = + contexts.before(deoptEnv) + .context() - + contexts.before(mk).context(); + deoptEnv->context = + mk->context + diff; + } else { + deoptEnv->context = 0; + } + changed = true; + }); } } + } + } - // Narrow down type according to what the analysis reports - if (i->type.isRType()) { - auto inferedType = res.type; - if (!i->type.isA(inferedType)) - i->type = inferedType; - } + analysis.lookupAt(after, i, [&](const AbstractLoad& aLoad) { + auto& res = aLoad.result; - // The generic case where we have a bunch of potential - // values we will insert a phi to group all of them. In - // general this is only possible if they all come from the - // current function (and not through inter procedural - // analysis from other functions). - // - // Also, we shold only do this for actual loads and not - // in general. Otherwise there is a danger that we insert - // the same phi twice (e.g. if a force returns the result - // of a load, we will resolve the load and the force) which - // ends up being rather painful. - if (!res.isUnknown() && isActualLoad) { - Value* resPhi = nullptr; - bool failed = false; - - for (auto& c : createdPhis) { - if (c.first == res) { - auto& cache = c.second; - if (cache.hasUnbound) { - failed = true; - break; - } - if (cache.phis.count(bb)) - resPhi = cache.phis.at(bb); - else if (cache.dominatingPhi.count(bb)) - resPhi = cache.phis.at( - cache.dominatingPhi.at(bb)); - break; - } - } - if (!resPhi && !failed) - resPhi = - tryInsertPhis(i->env(), res, bb, ip, false); + bool isActualLoad = + LdVar::Cast(i) || LdFun::Cast(i) || LdVarSuper::Cast(i); - if (resPhi) { - Value* val = resPhi; - if (val->type.maybeMissing()) { + // In case the scope analysis is sure that this is + // actually the same as some other PIR value. So let's just + // replace it. + if (res.isSingleValue()) { + if (auto val = getSingleLocalValue(res)) { + if (val->type.isA(i->type)) { + if (isActualLoad && val->type.maybeMissing()) { // LdVar checks for missingness, so we need // to preserve this. auto chk = new ChkMissing(val); @@ -539,169 +511,233 @@ bool ScopeResolution::apply(Compiler&, ClosureVersion* cls, Code* code, ip++; val = chk; } - i->replaceUsesWith(val); replacedValue[i] = val; + i->replaceUsesWith(val); assert(!val->type.maybePromiseWrapped() || i->type.maybePromiseWrapped()); next = bb->remove(ip); - anyChange = true; + changed = true; return; } } + } - // LdVarSuper where the parent environment is known and - // local, can be replaced by a simple LdVar - if (auto lds = LdVarSuper::Cast(i)) { - auto e = Env::parentEnv(lds->env()); - if (e) { - auto r = new LdVar(lds->varName, e); - bb->replace(ip, r); - lds->replaceUsesWith(r); - assert(!r->type.maybePromiseWrapped() || - i->type.maybePromiseWrapped()); - replacedValue[lds] = r; - anyChange = true; + // Narrow down type according to what the analysis reports + if (i->type.isRType()) { + auto inferedType = res.type; + if (!i->type.isA(inferedType)) { + i->type = inferedType; + changed = true; + } + } + + // The generic case where we have a bunch of potential + // values we will insert a phi to group all of them. In + // general this is only possible if they all come from the + // current function (and not through inter procedural + // analysis from other functions). + // + // Also, we shold only do this for actual loads and not + // in general. Otherwise there is a danger that we insert + // the same phi twice (e.g. if a force returns the result + // of a load, we will resolve the load and the force) which + // ends up being rather painful. + if (!res.isUnknown() && isActualLoad) { + Value* resPhi = nullptr; + bool failed = false; + + for (auto& c : createdPhis) { + if (c.first == res) { + auto& cache = c.second; + if (cache.hasUnbound) { + failed = true; + break; + } + if (cache.phis.count(bb)) + resPhi = cache.phis.at(bb); + else if (cache.dominatingPhi.count(bb)) + resPhi = + cache.phis.at(cache.dominatingPhi.at(bb)); + break; } + } + if (!resPhi && !failed) + resPhi = tryInsertPhis(i->env(), res, bb, ip, false); + + if (resPhi) { + Value* val = resPhi; + if (val->type.maybeMissing()) { + // LdVar checks for missingness, so we need + // to preserve this. + auto chk = new ChkMissing(val); + ip = bb->insert(ip, chk); + ip++; + val = chk; + } + i->replaceUsesWith(val); + replacedValue[i] = val; + assert(!val->type.maybePromiseWrapped() || + i->type.maybePromiseWrapped()); + next = bb->remove(ip); + changed = true; return; } + } + + // LdVarSuper where the parent environment is known and + // local, can be replaced by a simple LdVar + if (auto lds = LdVarSuper::Cast(i)) { + auto e = Env::parentEnv(lds->env()); + if (e) { + auto r = new LdVar(lds->varName, e); + bb->replace(ip, r); + lds->replaceUsesWith(r); + assert(!r->type.maybePromiseWrapped() || + i->type.maybePromiseWrapped()); + replacedValue[lds] = r; + changed = true; + } + return; + } - // Ldfun needs some special treatment sometimes: - // Since non closure bindings are skipped at runtime, we can - // only resolve ldfun if we are certain which one is the - // first binding that holds a closure. Often this is only - // possible after inlining a promise. But inlining a promise - // requires a force instruction. But ldfun does force - // implicitly. To get out of this vicious circle, we add the - // first binding we find with a normal load (as opposed to - // loadFun) from the abstract state as a "guess" This will - // enable other passes (especially the promise inliner pass) - // to work on the guess and maybe the next time we end up - // here, we can actually prove that the guess was right. - if (auto ldfun = LdFun::Cast(i)) { - auto guess = ldfun->guessedBinding(); - // If we already have a guess, let's see if now know - // that it is a closure. - if (guess) { - // TODO: if !guess->maybe(closure) we know that the - // guess is wrong and could try the next binding. - if (!guess->type.isA(PirType::closure())) { - if (auto i = Instruction::Cast(guess)) { - analysis.lookupAt( - before, i, - [&](const AbstractPirValue& res) { - if (auto val = - getSingleLocalValue(res)) - guess = val; - }); - } - } - if (guess->type.isA(PirType::closure()) && - guess->validIn(code)) { - guess = getReplacedValue(guess); - ldfun->replaceUsesWith(guess); - replacedValue[ldfun] = guess; - next = bb->remove(ip); - anyChange = true; - return; - } - } else { - auto res = - analysis - .load(before, ldfun->varName, ldfun->env()) - .result; - if (auto firstBinding = getSingleLocalValue(res)) { - ip = bb->insert( - ip, new Force(firstBinding, ldfun->env(), - Tombstone::framestate())); - ldfun->guessedBinding(*ip); - next = ip + 2; - return; + // Ldfun needs some special treatment sometimes: + // Since non closure bindings are skipped at runtime, we can + // only resolve ldfun if we are certain which one is the + // first binding that holds a closure. Often this is only + // possible after inlining a promise. But inlining a promise + // requires a force instruction. But ldfun does force + // implicitly. To get out of this vicious circle, we add the + // first binding we find with a normal load (as opposed to + // loadFun) from the abstract state as a "guess" This will + // enable other passes (especially the promise inliner pass) + // to work on the guess and maybe the next time we end up + // here, we can actually prove that the guess was right. + if (auto ldfun = LdFun::Cast(i)) { + auto guess = ldfun->guessedBinding(); + // If we already have a guess, let's see if now know + // that it is a closure. + if (guess) { + // TODO: if !guess->maybe(closure) we know that the + // guess is wrong and could try the next binding. + if (!guess->type.isA(PirType::closure())) { + if (auto i = Instruction::Cast(guess)) { + analysis.lookupAt( + before, i, + [&](const AbstractPirValue& res) { + if (auto val = getSingleLocalValue(res)) + guess = val; + }); } } + if (guess->type.isA(PirType::closure()) && + guess->validIn(code)) { + guess = getReplacedValue(guess); + ldfun->replaceUsesWith(guess); + replacedValue[ldfun] = guess; + next = bb->remove(ip); + changed = true; + return; + } + } else { + auto res = + analysis.load(before, ldfun->varName, ldfun->env()) + .result; + if (auto firstBinding = getSingleLocalValue(res)) { + ip = bb->insert( + ip, new Force(firstBinding, ldfun->env(), + Tombstone::framestate())); + ldfun->guessedBinding(*ip); + next = ip + 2; + return; + } + } + } + + // If nothing else, narrow down the environment (in case we + // found something more concrete). + if (i->hasEnv() && + aLoad.env != AbstractREnvironment::UnknownParent) { + if (!MaterializeEnv::Cast(i->env()) && + i->env() != aLoad.env) { + changed = true; + i->env(aLoad.env); } - // If nothing else, narrow down the environment (in case we - // found something more concrete). - if (i->hasEnv() && - aLoad.env != AbstractREnvironment::UnknownParent) { - if (!MaterializeEnv::Cast(i->env())) - i->env(aLoad.env); - - // Assume bindings in base namespace stay unchanged - if (!bb->isDeopt()) { - if (auto env = Env::Cast(aLoad.env)) { - if (env->rho == R_BaseEnv || - env->rho == R_BaseNamespace) { - SEXP name = nullptr; - if (auto ld = LdVar::Cast(i)) - name = ld->varName; - if (auto ldfun = LdFun::Cast(i)) - name = ldfun->varName; - if (name && - SafeBuiltinsList::assumeStableInBaseEnv( - name)) { - auto value = SYMVALUE(name); - assert(Rf_findVar(name, env->rho) == - value); - if (TYPEOF(value) == PROMSXP) - value = PRVALUE(value); - if (value != R_UnboundValue) - if (LdVar::Cast(i) || - TYPEOF(value) == BUILTINSXP || - TYPEOF(value) == SPECIALSXP || - TYPEOF(value) == CLOSXP) { - auto con = new LdConst(value); - i->replaceUsesAndSwapWith(con, - ip); - anyChange = true; - return; - } - } + // Assume bindings in base namespace stay unchanged + if (!bb->isDeopt()) { + if (auto env = Env::Cast(aLoad.env)) { + if (env->rho == R_BaseEnv || + env->rho == R_BaseNamespace) { + SEXP name = nullptr; + if (auto ld = LdVar::Cast(i)) + name = ld->varName; + if (auto ldfun = LdFun::Cast(i)) + name = ldfun->varName; + if (name && + SafeBuiltinsList::assumeStableInBaseEnv( + name)) { + auto value = SYMVALUE(name); + assert(Rf_findVar(name, env->rho) == value); + if (TYPEOF(value) == PROMSXP) + value = PRVALUE(value); + if (value != R_UnboundValue) + if (LdVar::Cast(i) || + TYPEOF(value) == BUILTINSXP || + TYPEOF(value) == SPECIALSXP || + TYPEOF(value) == CLOSXP) { + auto con = new LdConst(value); + i->replaceUsesAndSwapWith(con, ip); + changed = true; + return; + } } } } } + } + if (changed) + anyChange = true; + }); + + // TODO move this to a pass where it fits... + if (auto b = CallBuiltin::Cast(i)) { + bool noObjects = true; + bool unsafe = false; + i->eachArg([&](Value* v) { + if (v != i->env()) { + if (v->cFollowCastsAndForce()->type.maybeObj()) + noObjects = false; + if (v->type.isA(RType::expandedDots)) + unsafe = true; + } }); - // TODO move this to a pass where it fits... - if (auto b = CallBuiltin::Cast(i)) { - bool noObjects = true; - bool unsafe = false; + if (!unsafe && noObjects && + SafeBuiltinsList::nonObject(b->builtinId)) { + std::vector args; i->eachArg([&](Value* v) { if (v != i->env()) { - if (v->cFollowCastsAndForce()->type.maybeObj()) - noObjects = false; - if (v->type.isA(RType::expandedDots)) - unsafe = true; + auto mk = MkArg::Cast(v); + if (mk && mk->isEager()) + args.push_back(mk->eagerArg()); + else + args.push_back(v); } }); - - if (!unsafe && noObjects && - SafeBuiltinsList::nonObject(b->builtinId)) { - std::vector args; - i->eachArg([&](Value* v) { - if (v != i->env()) { - auto mk = MkArg::Cast(v); - if (mk && mk->isEager()) - args.push_back(mk->eagerArg()); - else - args.push_back(v); - } - }); - auto safe = BuiltinCallFactory::New( - b->env(), b->builtinSexp, args, b->srcIdx); - assert(!b->type.maybePromiseWrapped() || - safe->type.maybePromiseWrapped()); - b->replaceUsesWith(safe); - bb->replace(ip, safe); - replacedValue[b] = safe; - anyChange = true; - } + auto safe = BuiltinCallFactory::New( + b->env(), b->builtinSexp, args, b->srcIdx); + assert(!b->type.maybePromiseWrapped() || + safe->type.maybePromiseWrapped()); + b->replaceUsesWith(safe); + bb->replace(ip, safe); + replacedValue[b] = safe; + changed = true; } - - ip = next; } - }); + + ip = next; + } + }); // Scope resolution can sometimes generate dead phis, so we remove them // here, before they cause errors in later compiler passes. (Sometimes, the // verifier will even catch these errors, but then segfault when trying to diff --git a/rir/src/compiler/opt/type_speculation.cpp b/rir/src/compiler/opt/type_speculation.cpp index ac5dc2b6f..c42b8d899 100644 --- a/rir/src/compiler/opt/type_speculation.cpp +++ b/rir/src/compiler/opt/type_speculation.cpp @@ -27,8 +27,8 @@ bool TypeSpeculation::apply(Compiler&, ClosureVersion* cls, Code* code, auto dom = DominanceGraph(code); VisitorNoDeoptBranch::run(code->entry, [&](Instruction* i) { - if (i->typeFeedback.used || i->typeFeedback.type.isVoid() || - i->type.isA(i->typeFeedback.type)) + if (i->typeFeedback().used || i->typeFeedback().type.isVoid() || + i->type.isA(i->typeFeedback().type)) return; Instruction* speculateOn = nullptr; @@ -46,7 +46,7 @@ bool TypeSpeculation::apply(Compiler&, ClosureVersion* cls, Code* code, bool localLoad = LdVar::Cast(arg) && !Env::isStaticEnv(i->env()); - feedback = i->typeFeedback; + feedback = i->typeFeedback(); // If this force was observed to receive evaluated // promises, better speculate on the input already. switch (force->observed) { @@ -76,30 +76,41 @@ bool TypeSpeculation::apply(Compiler&, ClosureVersion* cls, Code* code, } } } - } else if ((!i->type.unboxable() && i->typeFeedback.type.unboxable()) || - (i->type.maybeLazy() && !i->typeFeedback.type.maybeLazy()) || + } else if ((!i->type.unboxable() && + i->typeFeedback().type.unboxable()) || + (i->type.maybeLazy() && + !i->typeFeedback().type.maybeLazy()) || // Vector where Extract is unboxed if we speculate (i->type.isA(PirType::num()) && !i->type.simpleScalar().unboxable() && - i->typeFeedback.type.simpleScalar().unboxable() && + i->typeFeedback().type.simpleScalar().unboxable() && maybeUsedUnboxed.isAlive(i))) { speculateOn = i; - feedback = i->typeFeedback; + feedback = i->typeFeedback(); guardPos = checkpoint.next(i, i, dom); if (guardPos) typecheckPos = guardPos->nextBB(); } - if (!speculateOn || !guardPos) + if (!speculateOn || !guardPos || !typecheckPos || + typecheckPos->isDeopt() || + (speculate.count(typecheckPos) && + speculate[typecheckPos].count(speculateOn))) return; + // leave this for scope analysis + if (auto ld = LdVar::Cast(speculateOn)) + if (auto mk = MkEnv::Cast(ld->env())) + if (mk->contains(ld->varName)) + return; + TypeTest::Create( speculateOn, feedback, speculateOn->type.notObject(), PirType::any(), [&](TypeTest::Info info) { speculate[typecheckPos][speculateOn] = {guardPos, info}; // Prevent redundant speculation - speculateOn->typeFeedback.used = true; + speculateOn->updateTypeFeedback().used = true; }, []() {}); }); diff --git a/rir/src/compiler/opt/types.cpp b/rir/src/compiler/opt/types.cpp index 7c08b5028..1b12a42b5 100644 --- a/rir/src/compiler/opt/types.cpp +++ b/rir/src/compiler/opt/types.cpp @@ -238,7 +238,7 @@ bool TypeInference::apply(Compiler&, ClosureVersion* cls, Code* code, getType(e->idx()).isSimpleScalar()) { auto range = rangeAnalysis.before(e).range; if (range.count(e->idx())) { - if (range.at(e->idx()).first > 0) { + if (range.at(e->idx()) > 0) { // Negative numbers as indices make the // extract return a vector. Only // positive are safe. diff --git a/rir/src/compiler/parameter.h b/rir/src/compiler/parameter.h index 29c0d73bc..1a6338e58 100644 --- a/rir/src/compiler/parameter.h +++ b/rir/src/compiler/parameter.h @@ -21,12 +21,15 @@ struct Parameter { static size_t INLINER_INITIAL_FUEL; static size_t INLINER_INLINE_UNLIKELY; + static size_t RECOMPILE_THRESHOLD; + static bool RIR_PRESERVE; static unsigned RIR_SERIALIZE_CHAOS; static unsigned RIR_CHECK_PIR_TYPES; static unsigned PIR_LLVM_OPT_LEVEL; + static unsigned PIR_OPT_LEVEL; static bool ENABLE_PIR2RIR; }; diff --git a/rir/src/compiler/pir/bb.h b/rir/src/compiler/pir/bb.h index 60c83e3fb..22f663f63 100644 --- a/rir/src/compiler/pir/bb.h +++ b/rir/src/compiler/pir/bb.h @@ -210,7 +210,7 @@ class BB { return {res.next[1], nullptr}; return res; } - const Successors successors() { return {next0, next1}; } + const Successors successors() const { return {next0, next1}; } void setSuccessors(const Successors& succ) { assert(!next0 && !next1); diff --git a/rir/src/compiler/pir/builder.cpp b/rir/src/compiler/pir/builder.cpp index c31c96675..fd63c9f5e 100644 --- a/rir/src/compiler/pir/builder.cpp +++ b/rir/src/compiler/pir/builder.cpp @@ -132,7 +132,7 @@ Builder::Builder(ClosureVersion* version, Value* closureEnv) auto rirCode = version->owner()->rirFunction()->body(); if (rirCode->flags.contains(rir::Code::NeedsFullEnv)) mkenv->neverStub = true; - mkenv->typeFeedback.srcCode = rirCode; + mkenv->updateTypeFeedback().srcCode = rirCode; add(mkenv); this->env = mkenv; } diff --git a/rir/src/compiler/pir/instruction.cpp b/rir/src/compiler/pir/instruction.cpp index e2595720d..af316a36c 100644 --- a/rir/src/compiler/pir/instruction.cpp +++ b/rir/src/compiler/pir/instruction.cpp @@ -100,11 +100,11 @@ void printPaddedIdTypeRef(std::ostream& out, const Instruction* i) { } std::ostringstream buf; buf << i->type; - if (!i->typeFeedback.type.isVoid()) { - if (i->type == i->typeFeedback.type) + if (!i->typeFeedback().type.isVoid()) { + if (i->type == i->typeFeedback().type) buf << "<>"; else - buf << "<" << i->typeFeedback.type << ">"; + buf << "<" << i->typeFeedback().type << ">"; } out << std::left << std::setw(15) << buf.str() << " "; buf.str(""); @@ -238,10 +238,10 @@ bool Instruction::nonObjectArgs() { auto fb = PirType::bottom(); if (auto j = Instruction::Cast(arg)) - fb = j->typeFeedback.type; + fb = j->typeFeedback().type; if (fb.isVoid()) { if (auto j = Instruction::Cast(arg->followCastsAndForce())) - fb = j->typeFeedback.type; + fb = j->typeFeedback().type; } if (fb.isVoid() || fb.maybeObj()) @@ -361,11 +361,12 @@ void Instruction::replaceDominatedUses(Instruction* replace, }); // Propagate typefeedback - if (auto rep = Instruction::Cast(replace)) { - if (!rep->type.isA(typeFeedback.type) && - rep->typeFeedback.type.isVoid()) - rep->typeFeedback = typeFeedback; - } + if (typeFeedback_.get()) + if (auto rep = Instruction::Cast(replace)) { + if (!rep->type.isA(typeFeedback().type) && + rep->typeFeedback().type.isVoid()) + rep->typeFeedback(typeFeedback()); + } } void Instruction::replaceUsesIn( @@ -396,9 +397,10 @@ void Instruction::replaceUsesIn( // Propagate typefeedback if (auto rep = Instruction::Cast(replace)) { - if (!rep->type.isA(typeFeedback.type) && - rep->typeFeedback.type.isVoid()) - rep->typeFeedback = typeFeedback; + if (typeFeedback_.get()) + if (!rep->type.isA(typeFeedback().type) && + rep->typeFeedback().type.isVoid()) + rep->typeFeedback(typeFeedback()); } } @@ -1146,8 +1148,8 @@ NamedCall::NamedCall(Value* callerEnv, Value* fun, StaticCall::StaticCall(Value* callerEnv, Closure* cls, Context givenContext, const std::vector& args, - ArglistOrder::CallArglistOrder&& argOrderOrig, Value* fs, - unsigned srcIdx, Value* runtimeClosure) + const ArglistOrder::CallArglistOrder& argOrderOrig, + Value* fs, unsigned srcIdx, Value* runtimeClosure) : VarLenInstructionWithEnvSlot(PirType::val(), callerEnv, srcIdx), cls_(cls), argOrderOrig(argOrderOrig), givenContext(givenContext) { assert(cls->nargs() >= args.size()); diff --git a/rir/src/compiler/pir/instruction.h b/rir/src/compiler/pir/instruction.h index 29b25e146..cc6d099af 100644 --- a/rir/src/compiler/pir/instruction.h +++ b/rir/src/compiler/pir/instruction.h @@ -124,7 +124,7 @@ enum class Effect : uint8_t { FIRST = Visibility, LAST = MutatesArgument, }; -typedef EnumSet Effects; +typedef EnumSet Effects; // Controlflow of instruction. enum class Controlflow : uint8_t { @@ -161,7 +161,8 @@ class Instruction : public Value { }; Instruction(Tag tag, PirType t, Effects effects, unsigned srcIdx) - : Value(t, tag), effects(effects), srcIdx(srcIdx) {} + : Value(t, tag), effects(effects), typeFeedback_(nullptr), + srcIdx(srcIdx) {} Effects effects; @@ -176,7 +177,22 @@ class Instruction : public Value { return effects.contains(Effect::Reflection); } - TypeFeedback typeFeedback; + std::shared_ptr typeFeedback_; + const TypeFeedback& typeFeedback() const { + if (typeFeedback_.get()) + return *typeFeedback_; + const static TypeFeedback none; + return none; + } + TypeFeedback& updateTypeFeedback() { + if (typeFeedback_.get()) + return *typeFeedback_; + typeFeedback_.reset(new TypeFeedback()); + return updateTypeFeedback(); + } + void typeFeedback(const TypeFeedback& feedback) { + typeFeedback_.reset(new TypeFeedback(feedback)); + } Effects getObservableEffects() const { auto e = effects; @@ -254,7 +270,6 @@ class Instruction : public Value { const Value* cFollowCasts() const override final; const Value* cFollowCastsAndForce() const override final; - bool isInstruction() override final { return true; } virtual bool envOnlyForObj(); bool validIn(Code* code) const override final; @@ -527,6 +542,7 @@ class Instruction : public Value { return -1; } }; +static_assert(sizeof(Instruction) <= 56, "Bloated instructions..."); template @@ -968,13 +984,22 @@ class FLIE(LdVar, 1, Effects() | Effect::Error | Effect::ReadsEnv) { int minReferenceCount() const override { return 1; } }; -class FLI(ForSeqSize, 1, Effect::Error) { +class FLI(ToForSeq, 1, Effect::Error) { public: - explicit ForSeqSize(Value* val) - : FixedLenInstruction( - PirType(RType::integer).simpleScalar().notObject(), - {{PirType::val()}}, {{val}}) {} + explicit ToForSeq(Value* val) + : FixedLenInstruction(val->type.maybeObj() + ? val->type.notObject().orT(RType::chr) + : val->type, + {{PirType::val()}}, {{val}}) {} + size_t gvnBase() const override { return tagHash(); } + + PirType inferType(const GetType& getType) const override final { + auto it = getType(arg(0).val()); + if (it.maybeObj()) + return type & it.notObject().orT(RType::chr); + return type & it; + } }; class FLI(Length, 1, Effects::None()) { @@ -1323,6 +1348,16 @@ class FLI(AsLogical, 1, Effect::Error) { size_t gvnBase() const override { return tagHash(); } }; +class FLI(AsSwitchIdx, 1, Effects::None()) { + public: + Value* val() const { return arg<0>().val(); } + explicit AsSwitchIdx(Value* in) + : FixedLenInstruction(PirType::simpleScalarInt(), {{PirType::val()}}, + {{in}}) {} + + size_t gvnBase() const override { return tagHash(); } +}; + class FLI(CheckTrueFalse, 1, Effects() | Effect::Error | Effect::Warn) { public: Value* val() const { return arg<0>().val(); } @@ -2179,7 +2214,7 @@ class VLIE(StaticCall, Effects::Any()), public CallInstruction { public: StaticCall(Value * callerEnv, Closure * cls, Context givenContext, const std::vector& args, - ArglistOrder::CallArglistOrder&& argOrderOrig, Value* fs, + const ArglistOrder::CallArglistOrder& argOrderOrig, Value* fs, unsigned srcIdx, Value* runtimeClosure = Tombstone::closure()); Context givenContext; diff --git a/rir/src/compiler/pir/instruction_list.h b/rir/src/compiler/pir/instruction_list.h index d0054e559..cc2f7b63b 100644 --- a/rir/src/compiler/pir/instruction_list.h +++ b/rir/src/compiler/pir/instruction_list.h @@ -52,6 +52,7 @@ V(DotsList) \ V(ExpandDots) \ V(AsLogical) \ + V(AsSwitchIdx) \ V(CheckTrueFalse) \ V(ColonInputEffects) \ V(ColonCastLhs) \ @@ -85,7 +86,7 @@ V(Plus) \ V(Minus) \ V(Identical) \ - V(ForSeqSize) \ + V(ToForSeq) \ V(Length) \ V(FrameState) \ V(Checkpoint) \ diff --git a/rir/src/compiler/pir/type.cpp b/rir/src/compiler/pir/type.cpp index e316e83d4..b7a835bfb 100644 --- a/rir/src/compiler/pir/type.cpp +++ b/rir/src/compiler/pir/type.cpp @@ -152,10 +152,8 @@ PirType::PirType(SEXP e) : flags_(topRTypeFlags()), t_(RTypeSet()) { flags_.reset(TypeFlags::maybeObject); if (fastVeceltOk(e)) flags_.reset(TypeFlags::maybeNotFastVecelt); - if (ATTRIB(e) == R_NilValue && !Rf_isObject(e)) { - assert(fastVeceltOk(e)); + if (ATTRIB(e) == R_NilValue) flags_.reset(TypeFlags::maybeAttrib); - } if (Rf_xlength(e) == 1) flags_.reset(TypeFlags::maybeNotScalar); diff --git a/rir/src/compiler/pir/type.h b/rir/src/compiler/pir/type.h index 75ba3d277..72b0016d7 100644 --- a/rir/src/compiler/pir/type.h +++ b/rir/src/compiler/pir/type.h @@ -122,8 +122,8 @@ enum class TypeFlags : uint8_t { struct PirType { typedef EnumSet RTypeSet; - typedef EnumSet NativeTypeSet; - typedef EnumSet FlagSet; + typedef EnumSet NativeTypeSet; + typedef EnumSet FlagSet; FlagSet flags_; @@ -794,13 +794,13 @@ inline std::ostream& operator<<(std::ostream& out, PirType t) { else if (t.maybePromiseWrapped()) out << "~"; if (!t.maybeHasAttrs()) { - out << "⁻"; + out << "-"; } else { if (!t.maybeNotFastVecelt()) { assert(!t.maybeObj()); - out << "ⁿ"; + out << "¹"; } else if (!t.maybeObj()) { - out << "⁺"; + out << "+"; } } diff --git a/rir/src/compiler/pir/value.h b/rir/src/compiler/pir/value.h index f6a34c462..36ef5e434 100644 --- a/rir/src/compiler/pir/value.h +++ b/rir/src/compiler/pir/value.h @@ -22,12 +22,12 @@ class Code; */ class Value { public: - PirType type; Tag tag; - Value(PirType type, Tag tag) : type(type), tag(tag) {} + PirType type; + + Value(PirType type, Tag tag) : tag(tag), type(type) {} virtual void printRef(std::ostream& out) const = 0; void printRef() const { printRef(std::cerr); } - virtual bool isInstruction() { return false; } virtual const Value* cFollowCasts() const { return this; } virtual const Value* cFollowCastsAndForce() const { return this; } Value* followCasts() { @@ -56,6 +56,8 @@ class Value { void callArgTypeToContext(Context&, unsigned arg) const; }; +static_assert(sizeof(Value) <= 16, ""); + } // namespace pir } // namespace rir diff --git a/rir/src/compiler/rir2pir/rir2pir.cpp b/rir/src/compiler/rir2pir/rir2pir.cpp index a6382cece..b3763bbcc 100644 --- a/rir/src/compiler/rir2pir/rir2pir.cpp +++ b/rir/src/compiler/rir2pir/rir2pir.cpp @@ -7,6 +7,7 @@ #include "compiler/analysis/query.h" #include "compiler/analysis/verifier.h" #include "compiler/opt/pass_definitions.h" +#include "compiler/parameter.h" #include "compiler/pir/builder.h" #include "compiler/pir/pir_impl.h" #include "compiler/util/arg_match.h" @@ -384,11 +385,21 @@ bool Rir2Pir::compileBC(const BC& bc, Opcode* pos, Opcode* nextPos, insert(new Visible()); auto fs = inlining() ? (Value*)Tombstone::framestate() : insert.registerFrameState(srcCode, nextPos, + stack, inPromise()); push(insert(new Force(v, env, fs))); break; } + case Opcode::ldvar_noforce_: { + if (bc.immediateConst() == symbol::c) + compiler.seenC = true; + v = insert(new LdVar(bc.immediateConst(), env)); + + push(v); + break; + } + case Opcode::stvar_: case Opcode::stvar_cached_: if (bc.immediateConst() == symbol::c) @@ -494,12 +505,13 @@ bool Rir2Pir::compileBC(const BC& bc, Opcode* pos, Opcode* nextPos, auto v = feedback.seen == ObservedTest::OnlyTrue ? (Value*)True::instance() : (Value*)False::instance(); - if (!i->typeFeedback.value) { - i->typeFeedback.value = v; - i->typeFeedback.srcCode = srcCode; - i->typeFeedback.origin = pos; - } else if (i->typeFeedback.value != v) { - i->typeFeedback.value = nullptr; + if (!i->typeFeedback().value) { + auto& t = i->updateTypeFeedback(); + t.value = v; + t.srcCode = srcCode; + t.origin = pos; + } else if (i->typeFeedback().value != v) { + i->updateTypeFeedback().value = nullptr; } } } @@ -526,9 +538,10 @@ bool Rir2Pir::compileBC(const BC& bc, Opcode* pos, Opcode* nextPos, break; } // TODO: deal with multiple locations - i->typeFeedback.type.merge(feedback); - i->typeFeedback.srcCode = srcCode; - i->typeFeedback.origin = pos; + auto& t = i->updateTypeFeedback(); + t.type.merge(feedback); + t.srcCode = srcCode; + t.origin = pos; if (auto force = Force::Cast(i)) { force->observed = static_cast( feedback.stateBeforeLastForce); @@ -649,15 +662,47 @@ bool Rir2Pir::compileBC(const BC& bc, Opcode* pos, Opcode* nextPos, // TODO implement support for call_builtin_ // with names bc.bc == Opcode::call_; - if (monomorphicBuiltin) { - int arity = getBuiltinArity(ti.monomorphic); + SEXP staticCallee = nullptr; + if (auto ld = LdConst::Cast(callee)) + staticCallee = ld->c(); + bool staticMonomorphicBuiltin = staticCallee && + TYPEOF(staticCallee) == BUILTINSXP && + // TODO implement support for + // call_builtin_ with names + bc.bc == Opcode::call_; + + auto checkArity = [&](SEXP builtin) { + int arity = getBuiltinArity(builtin); if (arity != -1 && arity != nargs) - monomorphicBuiltin = false; - } + return false; + return true; + }; + if (monomorphicBuiltin) + monomorphicBuiltin = checkArity(ti.monomorphic); + if (staticMonomorphicBuiltin) + staticMonomorphicBuiltin = checkArity(staticCallee); + const std::unordered_set supportedSpecials = {blt("forceAndCall")}; bool monomorphicSpecial = ti.monomorphic && TYPEOF(ti.monomorphic) == SPECIALSXP && supportedSpecials.count(ti.monomorphic->u.primsxp.offset); + if (monomorphicClosure && !monomorphicInnerFunction) { + auto dt = DispatchTable::unpack(BODY(ti.monomorphic)); + // Let's not re-translate already optimized functions if they are + // huge. + // TODO: this is more of a temporary measure. Long term we should + // have static calls with lazily compiled PIR targtets, so we can + // defer compilation to the point where we e.g. want to analyze or + // inline the callee... + if (dt->baseline()->body()->codeSize > + Parameter::RECOMPILE_THRESHOLD) { + auto cls = insert.function->owner(); + // exclude recursive calls + if (!cls->hasOriginClosure() || + ti.monomorphic != cls->rirClosure()) + monomorphicClosure = false; + } + } auto ast = bc.immediate.callFixedArgs.ast; auto emitGenericCall = [&]() { @@ -680,8 +725,9 @@ bool Rir2Pir::compileBC(const BC& bc, Opcode* pos, Opcode* nextPos, }; // Insert a guard if we want to speculate - if (monomorphicBuiltin || monomorphicClosure || - monomorphicInnerFunction || monomorphicSpecial) { + if (!staticMonomorphicBuiltin && + (monomorphicBuiltin || monomorphicClosure || + monomorphicInnerFunction || monomorphicSpecial)) { auto cp = std::get(callTargetFeedback.at(callee)); if (!cp) cp = addCheckpoint(srcCode, pos, stack, insert); @@ -692,33 +738,41 @@ bool Rir2Pir::compileBC(const BC& bc, Opcode* pos, Opcode* nextPos, std::get(callTargetFeedback.at(callee))); } - if (monomorphicBuiltin) { - for (size_t i = 0; i < args.size(); ++i) { - if (auto mk = MkArg::Cast(args[i])) { - if (mk->isEager()) { - args[i] = mk->eagerArg(); - } else { - assert(at(nargs - 1 - i) == args[i]); - args[i] = - tryCreateArg(mk->prom()->rirSrc(), insert, true); - if (!args[i]) { - log.warn("Failed to compile a promise"); - return false; - } + auto eagerEval = [&](Value*& arg, size_t i) { + if (auto mk = MkArg::Cast(arg)) { + if (mk->isEager()) { + arg = mk->eagerArg(); + } else { + auto original = arg; + arg = tryCreateArg(mk->prom()->rirSrc(), insert, true); + if (!arg) { + log.warn("Failed to compile a promise"); + return false; + } + if (i != (size_t)-1 && at(nargs - 1 - i) == original) { // Inlined argument evaluation might have side effects. // Let's have a checkpoint here. This checkpoint needs // to capture the so far evaluated promises. stack.at(nargs - 1 - i) = - insert(new MkArg(mk->prom(), args[i], mk->env())); + insert(new MkArg(mk->prom(), arg, mk->env())); addCheckpoint(srcCode, pos, stack, insert); } } } + return true; + }; + + if (monomorphicBuiltin || staticMonomorphicBuiltin) { + for (size_t i = 0; i < args.size(); ++i) + if (!eagerEval(args[i], i)) + return false; popn(toPop); - auto bt = - insert(BuiltinCallFactory::New(env, ti.monomorphic, args, ast)); - bt->effects.set(Effect::DependsOnAssume); + auto bt = insert(BuiltinCallFactory::New( + env, staticMonomorphicBuiltin ? staticCallee : ti.monomorphic, + args, ast)); + if (!staticMonomorphicBuiltin) + bt->effects.set(Effect::DependsOnAssume); push(bt); } else if (monomorphicClosure || monomorphicInnerFunction) { // (1) Argument Matching @@ -763,6 +817,37 @@ bool Rir2Pir::compileBC(const BC& bc, Opcode* pos, Opcode* nextPos, break; } + // Specialcase for calling usemethod, the first argument is eager. + // This helps determine the object type of the caller. + if (monomorphicClosure) { + auto dt = DispatchTable::unpack(BODY(ti.monomorphic)); + auto ast = + src_pool_at(globalContext(), dt->baseline()->body()->src); + auto isUseMethod = CAR(ast) == symbol::UseMethod && + TYPEOF(CADR(ast)) == STRSXP && + CDDR(ast) == R_NilValue; + if (isUseMethod) { + if (auto d = DotsList::Cast(matchedArgs[0])) { + if (d->nargs() > 0) { + if (eagerEval(d->arg(0).val(), 0)) { + d->arg(0).type() = d->arg(0).val()->type; + // creation of dots list must come after eager + // evaluation of content... + auto clone = d->clone(); + matchedArgs[0] = clone; + d->eraseAndRemove(); + insert(clone); + } else { + return false; + } + } + } else { + if (!eagerEval(matchedArgs[0], -1)) + return false; + } + } + } + // Special case for the super nasty match.arg(x) pattern where the // arguments being matched are read reflectively from the default // promises in the formals... @@ -900,9 +985,11 @@ bool Rir2Pir::compileBC(const BC& bc, Opcode* pos, Opcode* nextPos, break; } - case Opcode::for_seq_size_: - push(insert(new ForSeqSize(top()))); + case Opcode::for_seq_size_: { + push(insert(new ToForSeq(pop()))); + push(insert(new Length(top()))); break; + } case Opcode::length_: push(insert(new Length(pop()))); @@ -1088,6 +1175,10 @@ bool Rir2Pir::compileBC(const BC& bc, Opcode* pos, Opcode* nextPos, push(insert(new Missing(Pool::get(bc.immediate.pool), env))); break; + case Opcode::as_switch_idx_: + push(insert(new AsSwitchIdx(pop()))); + break; + case Opcode::is_: if (bc.immediate.typecheck == BC::RirTypecheck::isNonObject) { push(insert( @@ -1264,6 +1355,17 @@ Value* Rir2Pir::tryTranslate(rir::Code* srcCode, Builder& insert) { worklist.push_back(State(cur, false, bb, pos)); }; + bool anyReflective = false; + for (size_t i = 0; i < cls->nargs(); ++i) + if (!cls->context().isNonRefl(i) && !cls->context().isEager(i)) + anyReflective = true; + // If there are args that might be reflective it is helpful to have a + // checkpoint before forcing the first arg. Otherwise it is typically just + // inhibiting. + if (cls->rirSrc() == srcCode && anyReflective) { + addCheckpoint(srcCode, finger, cur.stack, insert); + } + while (finger != end || !worklist.empty()) { if (finger == end) finger = popWorklist(); @@ -1305,11 +1407,11 @@ Value* Rir2Pir::tryTranslate(rir::Code* srcCode, Builder& insert) { case Opcode::brfalse_: { auto v = branchCondition = cur.stack.pop(); if (auto c = Instruction::Cast(branchCondition)) { - if (c->typeFeedback.value == True::instance()) { + if (c->typeFeedback().value == True::instance()) { assumeBB0 = bc.bc == Opcode::brtrue_; deoptCondition = c; } - if (c->typeFeedback.value == False::instance()) { + if (c->typeFeedback().value == False::instance()) { assumeBB0 = bc.bc == Opcode::brfalse_; deoptCondition = c; } @@ -1352,7 +1454,7 @@ Value* Rir2Pir::tryTranslate(rir::Code* srcCode, Builder& insert) { auto sp = insert.registerFrameState( srcCode, (deopt == fall) ? nextPos : trg, cur.stack, inPromise()); - auto offset = (uintptr_t)deoptCondition->typeFeedback.origin - + auto offset = (uintptr_t)deoptCondition->typeFeedback().origin - (uintptr_t)srcCode; DeoptReason reason = {DeoptReason::DeadBranchReached, srcCode, (uint32_t)offset}; @@ -1373,9 +1475,9 @@ Value* Rir2Pir::tryTranslate(rir::Code* srcCode, Builder& insert) { if (auto j = Instruction::Cast(e)) { // In case the typefeedback is more // precise than the - if (!j->typeFeedback.type.isVoid() && + if (!j->typeFeedback().type.isVoid() && !tt->typeTest.isA( - j->typeFeedback.type)) + j->typeFeedback().type)) block = true; } if (!block) { @@ -1588,7 +1690,7 @@ Value* Rir2Pir::tryTranslate(rir::Code* srcCode, Builder& insert) { if (auto last = insert.getCurrentBB()) { res = Return::Cast(last->last())->arg(0).val(); - last->eraseLast(); + last->remove(last->end() - 1); } Visitor::run(insert.code->entry, [&](Instruction* i) { diff --git a/rir/src/compiler/test/PirCheck.cpp b/rir/src/compiler/test/PirCheck.cpp index d6158aabc..528c82c41 100644 --- a/rir/src/compiler/test/PirCheck.cpp +++ b/rir/src/compiler/test/PirCheck.cpp @@ -94,6 +94,21 @@ static bool testNoExternalCalls(ClosureVersion* f) { }); } +static bool testUnboxedExtract(ClosureVersion* f) { + return Visitor::check(f->entry, [&](Instruction* i) { + switch (i->tag) { + case Tag::Extract1_1D: + case Tag::Extract1_2D: + case Tag::Extract1_3D: + case Tag::Extract2_1D: + case Tag::Extract2_2D: + return i->type.unboxable(); + default: {} + } + return true; + }); +} + static bool testReturns42L(ClosureVersion* f) { if (!Query::noEnvSpec(f)) return false; diff --git a/rir/src/compiler/test/PirCheck.h b/rir/src/compiler/test/PirCheck.h index ce5891f11..38790c16b 100644 --- a/rir/src/compiler/test/PirCheck.h +++ b/rir/src/compiler/test/PirCheck.h @@ -29,6 +29,7 @@ namespace rir { V(LazyCallArgs) \ V(EagerCallArgs) \ V(LdVarVectorInFirstBB) \ + V(UnboxedExtract) \ V(AnAddIsNotNAOrNaN) struct PirCheck { diff --git a/rir/src/compiler/util/bb_transform.cpp b/rir/src/compiler/util/bb_transform.cpp index 3b09a4c46..271bb18cc 100644 --- a/rir/src/compiler/util/bb_transform.cpp +++ b/rir/src/compiler/util/bb_transform.cpp @@ -44,7 +44,7 @@ BB* BBTransform::clone(BB* src, Code* target, ClosureVersion* targetClosure) { phi->updateInputAt(j, bbs[phi->inputAt(j)->id]); } i->eachArg([&](InstrArg& arg) { - if (arg.val()->isInstruction()) { + if (Instruction::Cast(arg.val())) { auto val = arg.val(); assert(relocation_table.count(val)); arg.val() = relocation_table.at(val); @@ -102,9 +102,49 @@ BB* BBTransform::split(size_t next_id, BB* src, BB::Instrs::iterator it, return split; } -Value* BBTransform::forInline(BB* inlinee, BB* splice, Value* context) { +Value* BBTransform::forInline(BB* inlinee, BB* splice, Value* context, + Checkpoint* entryCp) { Value* found = nullptr; Instruction* ret; + + if (entryCp) { + auto pos = inlinee; + while (true) { + for (auto i : *pos) + if (i->isDeoptBarrier()) + entryCp = nullptr; + + // EntryCp no longer valid, giving up + if (!entryCp) + break; + + // Only one successor, go there + if (pos->isJmp()) { + pos = pos->next(); + continue; + } + + // This is the first cp of the inlinee, lets replace it with the + // outer CP + if (pos->isCheckpoint()) { + auto cp = Checkpoint::Cast(pos->last()); + cp->replaceUsesWith(entryCp); + auto del = pos->deoptBranch(); + std::vector toDel = {del}; + while (del->successors().size()) { + assert(del->successors().size() == 1); + toDel.push_back(*del->successors().begin()); + del = *del->successors().begin(); + } + pos->overrideSuccessors(pos->nonDeoptSuccessors()); + pos->remove(pos->end() - 1); + for (auto d : toDel) + delete d; + } + break; + } + } + Visitor::run(inlinee, [&](BB* bb) { if (!bb->isExit()) return; @@ -381,10 +421,6 @@ void BBTransform::removeDeadInstrs(Code* fun, uint8_t maxBurstSize) { seen.insert(cur); const auto& uses = phiUses[cur]; - // Phis not used by any other instruction have already been - // removed. - assert(!uses.empty()); - for (const auto& i : uses) { if (auto p = Phi::Cast(i)) { todo.push_back(p); diff --git a/rir/src/compiler/util/bb_transform.h b/rir/src/compiler/util/bb_transform.h index 697d47096..c97135ab9 100644 --- a/rir/src/compiler/util/bb_transform.h +++ b/rir/src/compiler/util/bb_transform.h @@ -22,7 +22,8 @@ class BBTransform { static BB* splitEdge(size_t next_id, BB* from, BB* to, Code* target); static BB* split(size_t next_id, BB* src, BB::Instrs::iterator, Code* target); - static Value* forInline(BB* inlinee, BB* cont, Value* context); + static Value* forInline(BB* inlinee, BB* cont, Value* context, + Checkpoint* entryCp); static BB* lowerExpect(Code* closure, BB* src, BB::Instrs::iterator position, Assume* assume, bool condition, BB* deoptBlock, diff --git a/rir/src/compiler/util/safe_builtins_list.cpp b/rir/src/compiler/util/safe_builtins_list.cpp index dad313612..21b9f44e9 100644 --- a/rir/src/compiler/util/safe_builtins_list.cpp +++ b/rir/src/compiler/util/safe_builtins_list.cpp @@ -4,94 +4,93 @@ #include "R/Funtab.h" #include "R/Symbols.h" +#include #include namespace rir { namespace pir { bool SafeBuiltinsList::always(int builtin) { - static int safeBuiltins[] = { - blt("diag"), - blt("backsolve"), - blt("max.col"), - blt("row"), - blt("col"), - blt("all.names"), - blt("list"), - blt("formals"), - blt("body"), - blt("bodyCode"), - - blt("matrix"), + switch (builtin) { + case blt("diag"): + case blt("backsolve"): + case blt("max.col"): + case blt("row"): + case blt("col"): + case blt("all.names"): + case blt("list"): + case blt("formals"): + case blt("body"): + case blt("bodyCode"): + + case blt("matrix"): // do_bitwise - blt("bitwiseAnd"), - blt("bitwiseNot"), - blt("bitwiseOr"), - blt("bitwiseXor"), - blt("bitwiseShiftL"), - blt("bitwiseShiftR"), + case blt("bitwiseAnd"): + case blt("bitwiseNot"): + case blt("bitwiseOr"): + case blt("bitwiseXor"): + case blt("bitwiseShiftL"): + case blt("bitwiseShiftR"): // do_randomN - blt("rchisq"), - blt("rexp"), - blt("rgeom"), - blt("rpois"), - blt("rt"), - blt("rsignrank"), - blt("rbeta"), - blt("rbinom"), - blt("rcauchy"), - blt("rf"), - blt("rgamma"), - blt("rlnorm"), - blt("rlogis"), - blt("rnbinom"), - blt("rnbinom_mu"), - blt("rnchisq"), - blt("rnorm"), - blt("runif"), - blt("rweibull"), - blt("rwilcox"), - blt("rhyper"), + case blt("rchisq"): + case blt("rexp"): + case blt("rgeom"): + case blt("rpois"): + case blt("rt"): + case blt("rsignrank"): + case blt("rbeta"): + case blt("rbinom"): + case blt("rcauchy"): + case blt("rf"): + case blt("rgamma"): + case blt("rlnorm"): + case blt("rlogis"): + case blt("rnbinom"): + case blt("rnbinom_mu"): + case blt("rnchisq"): + case blt("rnorm"): + case blt("runif"): + case blt("rweibull"): + case blt("rwilcox"): + case blt("rhyper"): // coerce.c - blt("as.function.default"), - blt("typeof"), - blt("is.vector"), - blt("is.null"), - blt("is.logical"), - blt("is.integer"), - blt("is.double"), - blt("is.complex"), - blt("is.character"), - blt("is.symbol"), - blt("is.name"), - blt("is.environment"), - blt("is.list"), - blt("is.pairlist"), - blt("is.expression"), - blt("is.raw"), - blt("is.object"), - blt("isS4"), - - blt("which"), - - blt("cat"), - blt("stdout"), - blt("stderr"), - blt("("), - blt("Sys.time"), - - blt("strsplit"), - - blt("seq_len"), - blt("rep_len"), + case blt("as.function.default"): + case blt("typeof"): + case blt("is.vector"): + case blt("is.null"): + case blt("is.logical"): + case blt("is.integer"): + case blt("is.double"): + case blt("is.complex"): + case blt("is.character"): + case blt("is.symbol"): + case blt("is.name"): + case blt("is.environment"): + case blt("is.list"): + case blt("is.pairlist"): + case blt("is.expression"): + case blt("is.raw"): + case blt("is.object"): + case blt("isS4"): + + case blt("which"): + + case blt("cat"): + case blt("stdout"): + case blt("stderr"): + case blt("("): + case blt("Sys.time"): + + case blt("strsplit"): + + case blt("seq_len"): + case blt("rep_len"): + return true; + default: {} }; - - for (auto i : safeBuiltins) - if (i == builtin) - return true; return false; } bool SafeBuiltinsList::always(SEXP builtin) { @@ -99,14 +98,15 @@ bool SafeBuiltinsList::always(SEXP builtin) { } bool SafeBuiltinsList::returnsObj(int builtin) { - static int safeBuiltins[] = { - blt("stdout"), - blt("stderr"), + switch (builtin) { + case blt("stdout"): + case blt("stderr"): + // In case input is obj + case blt("as.vector"): + return true; + default: {} }; - for (auto i : safeBuiltins) - if (i == builtin) - return true; return false; } @@ -114,218 +114,218 @@ bool SafeBuiltinsList::nonObject(int builtin) { if (always(builtin)) return true; - static int safeBuiltins[] = { - // TODO: this should be always safe, but something breaks if it is + switch (builtin) { + // TODO: this should be always safe: but something breaks if it is // moved. Need to investigate what! - blt("is.atomic"), + case blt("is.atomic"): - // Those are not always safe, due to coerceVector, which can be + // Those are not always safe, due to coerceVector: which can be // overwritten by objects - blt("vector"), - blt("complex"), - blt("array"), - blt("new.env"), - blt("match"), - - blt("dim"), - blt("names"), - - blt("c"), - blt("["), - blt("[["), - blt("+"), - blt("-"), - blt("*"), - blt("/"), - blt("^"), - blt("%%"), - blt("%/%"), - blt("%*%"), - blt("=="), - blt("!="), - blt("<"), - blt("<="), - blt(">="), - blt(">"), - blt("&"), - blt("|"), - blt("!"), - blt("&&"), - blt("||"), - blt(":"), - blt("~"), - blt("crossprod"), - blt("tcrossprod"), + case blt("vector"): + case blt("complex"): + case blt("array"): + case blt("new.env"): + case blt("match"): + + case blt("dim"): + case blt("names"): + + case blt("$"): + case blt("c"): + case blt("["): + case blt("[["): + case blt("+"): + case blt("-"): + case blt("*"): + case blt("/"): + case blt("^"): + case blt("%%"): + case blt("%/%"): + case blt("%*%"): + case blt("=="): + case blt("!="): + case blt("<"): + case blt("<="): + case blt(">="): + case blt(">"): + case blt("&"): + case blt("|"): + case blt("!"): + case blt("&&"): + case blt("||"): + case blt(":"): + case blt("~"): + case blt("crossprod"): + case blt("tcrossprod"): // Would be safe if not a vector of objects - // blt("lengths"), - blt("length"), - blt("round"), - blt("signif"), - blt("log"), - blt("log10"), - blt("log2"), - blt("abs"), - blt("floor"), - blt("ceiling"), - blt("sqrt"), - blt("sign"), - blt("trunc"), - blt("exp"), - blt("expm1"), - blt("log1p"), - blt("cos"), - blt("sin"), - blt("tan"), - blt("acos"), - blt("asin"), - blt("atan"), - blt("cosh"), - blt("sinh"), - blt("tanh"), - blt("acosh"), - blt("asinh"), - blt("atanh"), - blt("lgamma"), - blt("gamma"), - blt("digamma"), - blt("trigamma"), - blt("cospi"), - blt("sinpi"), - blt("tanpi"), - blt("atan2"), - blt("lbeta"), - blt("beta"), - blt("lchoose"), - blt("choose"), - blt("dchisq"), - blt("pchisq"), - blt("qchisq"), - blt("dexp"), - blt("pexp"), - blt("qexp"), - blt("dgeom"), - blt("pgeom"), - blt("qgeom"), - blt("dpois"), - blt("ppois"), - blt("qpois"), - blt("dt"), - blt("pt"), - blt("qt"), - blt("dsignrank"), - blt("psignrank"), - blt("qsignrank"), - blt("besselJ"), - blt("besselY"), - blt("psigamma"), - blt("Re"), - blt("Im"), - blt("Mod"), - blt("Arg"), - blt("Conj"), - blt("dbeta"), - blt("pbeta"), - blt("qbeta"), - blt("dbinom"), - blt("pbinom"), - blt("qbinom"), - blt("dcauchy"), - blt("pcauchy"), - blt("qcauchy"), - blt("df"), - blt("pf"), - blt("qf"), - blt("dgamma"), - blt("pgamma"), - blt("qgamma"), - blt("dlnorm"), - blt("plnorm"), - blt("qlnorm"), - blt("dlogis"), - blt("plogis"), - blt("qlogis"), - blt("dnbinom"), - blt("pnbinom"), - blt("qnbinom"), - blt("dnorm"), - blt("pnorm"), - blt("qnorm"), - blt("dunif"), - blt("punif"), - blt("qunif"), - blt("dweibull"), - blt("pweibull"), - blt("qweibull"), - blt("dnchisq"), - blt("pnchisq"), - blt("qnchisq"), - blt("dnt"), - blt("pnt"), - blt("qnt"), - blt("dwilcox"), - blt("pwilcox"), - blt("qwilcox"), - blt("besselI"), - blt("besselK"), - blt("dnbinom_mu"), - blt("pnbinom_mu"), - blt("qnbinom_mu"), - blt("dhyper"), - blt("phyper"), - blt("qhyper"), - blt("dnbeta"), - blt("pnbeta"), - blt("qnbeta"), - blt("dnf"), - blt("pnf"), - blt("qnf"), - blt("dtukey"), - blt("ptukey"), - blt("qtukey"), - blt("sum"), - blt("min"), - blt("max"), - blt("prod"), - blt("mean"), - blt("range"), - blt("as.character"), - blt("as.integer"), - blt("as.double"), - blt("as.numeric"), - blt("as.complex"), - blt("as.logical"), - blt("as.raw"), - blt("as.vector"), - - blt("is.numeric"), - blt("is.matrix"), - blt("is.array"), - blt("is.recursive"), - blt("is.call"), - blt("is.language"), - blt("is.function"), - blt("is.na"), - blt("is.nan"), - blt("is.finite"), - blt("is.infinite"), - - blt("cumsum"), - blt("colSums"), - - blt("paste"), - blt("nchar"), - blt("pmatch"), - - blt("seq.int"), - blt("rep.int"), - - blt("inherits"), - blt("anyNA") + // blt("lengths"): + case blt("length"): + case blt("round"): + case blt("signif"): + case blt("log"): + case blt("log10"): + case blt("log2"): + case blt("abs"): + case blt("floor"): + case blt("ceiling"): + case blt("sqrt"): + case blt("sign"): + case blt("trunc"): + case blt("exp"): + case blt("expm1"): + case blt("log1p"): + case blt("cos"): + case blt("sin"): + case blt("tan"): + case blt("acos"): + case blt("asin"): + case blt("atan"): + case blt("cosh"): + case blt("sinh"): + case blt("tanh"): + case blt("acosh"): + case blt("asinh"): + case blt("atanh"): + case blt("lgamma"): + case blt("gamma"): + case blt("digamma"): + case blt("trigamma"): + case blt("cospi"): + case blt("sinpi"): + case blt("tanpi"): + case blt("atan2"): + case blt("lbeta"): + case blt("beta"): + case blt("lchoose"): + case blt("choose"): + case blt("dchisq"): + case blt("pchisq"): + case blt("qchisq"): + case blt("dexp"): + case blt("pexp"): + case blt("qexp"): + case blt("dgeom"): + case blt("pgeom"): + case blt("qgeom"): + case blt("dpois"): + case blt("ppois"): + case blt("qpois"): + case blt("dt"): + case blt("pt"): + case blt("qt"): + case blt("dsignrank"): + case blt("psignrank"): + case blt("qsignrank"): + case blt("besselJ"): + case blt("besselY"): + case blt("psigamma"): + case blt("Re"): + case blt("Im"): + case blt("Mod"): + case blt("Arg"): + case blt("Conj"): + case blt("dbeta"): + case blt("pbeta"): + case blt("qbeta"): + case blt("dbinom"): + case blt("pbinom"): + case blt("qbinom"): + case blt("dcauchy"): + case blt("pcauchy"): + case blt("qcauchy"): + case blt("df"): + case blt("pf"): + case blt("qf"): + case blt("dgamma"): + case blt("pgamma"): + case blt("qgamma"): + case blt("dlnorm"): + case blt("plnorm"): + case blt("qlnorm"): + case blt("dlogis"): + case blt("plogis"): + case blt("qlogis"): + case blt("dnbinom"): + case blt("pnbinom"): + case blt("qnbinom"): + case blt("dnorm"): + case blt("pnorm"): + case blt("qnorm"): + case blt("dunif"): + case blt("punif"): + case blt("qunif"): + case blt("dweibull"): + case blt("pweibull"): + case blt("qweibull"): + case blt("dnchisq"): + case blt("pnchisq"): + case blt("qnchisq"): + case blt("dnt"): + case blt("pnt"): + case blt("qnt"): + case blt("dwilcox"): + case blt("pwilcox"): + case blt("qwilcox"): + case blt("besselI"): + case blt("besselK"): + case blt("dnbinom_mu"): + case blt("pnbinom_mu"): + case blt("qnbinom_mu"): + case blt("dhyper"): + case blt("phyper"): + case blt("qhyper"): + case blt("dnbeta"): + case blt("pnbeta"): + case blt("qnbeta"): + case blt("dnf"): + case blt("pnf"): + case blt("qnf"): + case blt("dtukey"): + case blt("ptukey"): + case blt("qtukey"): + case blt("sum"): + case blt("min"): + case blt("max"): + case blt("prod"): + case blt("mean"): + case blt("range"): + case blt("as.character"): + case blt("as.integer"): + case blt("as.double"): + case blt("as.numeric"): + case blt("as.complex"): + case blt("as.logical"): + case blt("as.raw"): + case blt("as.vector"): + + case blt("is.numeric"): + case blt("is.matrix"): + case blt("is.array"): + case blt("is.recursive"): + case blt("is.call"): + case blt("is.language"): + case blt("is.function"): + case blt("is.na"): + case blt("is.nan"): + case blt("is.finite"): + case blt("is.infinite"): + + case blt("cumsum"): + case blt("colSums"): + + case blt("paste"): + case blt("nchar"): + case blt("pmatch"): + + case blt("seq.int"): + case blt("rep.int"): + + case blt("inherits"): + case blt("anyNA"): + return true; + default: {} }; - for (auto i : safeBuiltins) - if (i == builtin) - return true; return false; } @@ -334,59 +334,58 @@ bool SafeBuiltinsList::nonObject(SEXP builtin) { } bool SafeBuiltinsList::idempotent(int builtin) { - static int safeBuiltins[] = { - blt("diag"), - blt("backsolve"), - blt("max.col"), - blt("row"), - blt("col"), - blt("all.names"), - blt("list"), - blt("formals"), - blt("body"), - blt("bodyCode"), - - blt("matrix"), + switch (builtin) { + case blt("diag"): + case blt("backsolve"): + case blt("max.col"): + case blt("row"): + case blt("col"): + case blt("all.names"): + case blt("list"): + case blt("formals"): + case blt("body"): + case blt("bodyCode"): + + case blt("matrix"): // do_bitwise - blt("bitwiseAnd"), - blt("bitwiseNot"), - blt("bitwiseOr"), - blt("bitwiseXor"), - blt("bitwiseShiftL"), - blt("bitwiseShiftR"), + case blt("bitwiseAnd"): + case blt("bitwiseNot"): + case blt("bitwiseOr"): + case blt("bitwiseXor"): + case blt("bitwiseShiftL"): + case blt("bitwiseShiftR"): // coerce.c - blt("as.function.default"), - blt("typeof"), - blt("is.vector"), - blt("is.null"), - blt("is.logical"), - blt("is.integer"), - blt("is.double"), - blt("is.complex"), - blt("is.character"), - blt("is.symbol"), - blt("is.name"), - blt("is.environment"), - blt("is.list"), - blt("is.pairlist"), - blt("is.expression"), - blt("is.raw"), - blt("is.object"), - blt("isS4"), - - blt("which"), - - blt("("), - - blt("seq_len"), - blt("rep_len"), + case blt("as.function.default"): + case blt("typeof"): + case blt("is.vector"): + case blt("is.null"): + case blt("is.logical"): + case blt("is.integer"): + case blt("is.double"): + case blt("is.complex"): + case blt("is.character"): + case blt("is.symbol"): + case blt("is.name"): + case blt("is.environment"): + case blt("is.list"): + case blt("is.pairlist"): + case blt("is.expression"): + case blt("is.raw"): + case blt("is.object"): + case blt("isS4"): + + case blt("which"): + + case blt("("): + + case blt("seq_len"): + case blt("rep_len"): + return true; + default: {} }; - for (auto i : safeBuiltins) - if (i == builtin) - return true; return false; } bool SafeBuiltinsList::idempotent(SEXP builtin) { @@ -397,66 +396,241 @@ bool SafeBuiltinsList::nonObjectIdempotent(int builtin) { if (always(builtin)) return true; - static int safeBuiltins[] = { + switch (builtin) { // TODO: this should be always safe, but something breaks if it is // moved. Need to investigate what! - blt("is.atomic"), - - blt("dim"), blt("names"), + case blt("is.atomic"): + + case blt("dim"): + case blt("names"): + + case blt("["): + case blt("[["): + case blt("+"): + case blt("-"): + case blt("*"): + case blt("/"): + case blt("^"): + + case blt("%%"): + case blt("%/%"): + case blt("%*%"): + case blt("=="): + case blt("!="): + case blt("<"): + + case blt("<="): + case blt(">="): + case blt(">"): + case blt("&"): + case blt("|"): + case blt("!"): + case blt("&&"): + + case blt("||"): + case blt(":"): + case blt("~"): + case blt("crossprod"): + case blt("tcrossprod"): - blt("["), blt("[["), blt("+"), blt("-"), blt("*"), blt("/"), blt("^"), - blt("%%"), blt("%/%"), blt("%*%"), blt("=="), blt("!="), blt("<"), - blt("<="), blt(">="), blt(">"), blt("&"), blt("|"), blt("!"), blt("&&"), - blt("||"), blt(":"), blt("~"), blt("crossprod"), blt("tcrossprod"), // Would be safe if not a vector of objects - // blt("lengths"), - blt("length"), blt("round"), blt("signif"), blt("log"), blt("log10"), - blt("log2"), blt("abs"), blt("floor"), blt("ceiling"), blt("sqrt"), - blt("sign"), blt("trunc"), blt("exp"), blt("expm1"), blt("log1p"), - blt("cos"), blt("sin"), blt("tan"), blt("acos"), blt("asin"), - blt("atan"), blt("cosh"), blt("sinh"), blt("tanh"), blt("acosh"), - blt("asinh"), blt("atanh"), blt("lgamma"), blt("gamma"), blt("digamma"), - blt("trigamma"), blt("cospi"), blt("sinpi"), blt("tanpi"), blt("atan2"), - blt("lbeta"), blt("beta"), blt("lchoose"), blt("choose"), blt("dchisq"), - blt("pchisq"), blt("qchisq"), blt("dexp"), blt("pexp"), blt("qexp"), - blt("dgeom"), blt("pgeom"), blt("qgeom"), blt("dpois"), blt("ppois"), - blt("qpois"), blt("dt"), blt("pt"), blt("qt"), blt("dsignrank"), - blt("psignrank"), blt("qsignrank"), blt("besselJ"), blt("besselY"), - blt("psigamma"), blt("Re"), blt("Im"), blt("Mod"), blt("Arg"), - blt("Conj"), blt("dbeta"), blt("pbeta"), blt("qbeta"), blt("dbinom"), - blt("pbinom"), blt("qbinom"), blt("dcauchy"), blt("pcauchy"), - blt("qcauchy"), blt("df"), blt("pf"), blt("qf"), blt("dgamma"), - blt("pgamma"), blt("qgamma"), blt("dlnorm"), blt("plnorm"), - blt("qlnorm"), blt("dlogis"), blt("plogis"), blt("qlogis"), - blt("dnbinom"), blt("pnbinom"), blt("qnbinom"), blt("dnorm"), - blt("pnorm"), blt("qnorm"), blt("dunif"), blt("punif"), blt("qunif"), - blt("dweibull"), blt("pweibull"), blt("qweibull"), blt("dnchisq"), - blt("pnchisq"), blt("qnchisq"), blt("dnt"), blt("pnt"), blt("qnt"), - blt("dwilcox"), blt("pwilcox"), blt("qwilcox"), blt("besselI"), - blt("besselK"), blt("dnbinom_mu"), blt("pnbinom_mu"), blt("qnbinom_mu"), - blt("dhyper"), blt("phyper"), blt("qhyper"), blt("dnbeta"), - blt("pnbeta"), blt("qnbeta"), blt("dnf"), blt("pnf"), blt("qnf"), - blt("dtukey"), blt("ptukey"), blt("qtukey"), blt("sum"), blt("min"), - blt("max"), blt("prod"), blt("mean"), blt("range"), blt("as.character"), - blt("as.integer"), blt("as.double"), blt("as.numeric"), - blt("as.complex"), blt("as.logical"), blt("as.raw"), blt("as.vector"), - - blt("is.numeric"), blt("is.matrix"), blt("is.array"), - blt("is.recursive"), blt("is.call"), blt("is.language"), - blt("is.function"), blt("is.na"), blt("is.nan"), blt("is.finite"), - blt("is.infinite"), - - blt("cumsum"), blt("colSums"), - - blt("match"), - - blt("seq.int"), blt("rep.int"), - - blt("inherits"), blt("anyNA")}; - - for (auto i : safeBuiltins) - if (i == builtin) - return true; + // blt("lengths"): + case blt("length"): + case blt("round"): + case blt("signif"): + case blt("log"): + case blt("log10"): + + case blt("log2"): + case blt("abs"): + case blt("floor"): + case blt("ceiling"): + case blt("sqrt"): + + case blt("sign"): + case blt("trunc"): + case blt("exp"): + case blt("expm1"): + case blt("log1p"): + + case blt("cos"): + case blt("sin"): + case blt("tan"): + case blt("acos"): + case blt("asin"): + + case blt("atan"): + case blt("cosh"): + case blt("sinh"): + case blt("tanh"): + case blt("acosh"): + + case blt("asinh"): + case blt("atanh"): + case blt("lgamma"): + case blt("gamma"): + case blt("digamma"): + + case blt("trigamma"): + case blt("cospi"): + case blt("sinpi"): + case blt("tanpi"): + case blt("atan2"): + + case blt("lbeta"): + case blt("beta"): + case blt("lchoose"): + case blt("choose"): + case blt("dchisq"): + + case blt("pchisq"): + case blt("qchisq"): + case blt("dexp"): + case blt("pexp"): + case blt("qexp"): + + case blt("dgeom"): + case blt("pgeom"): + case blt("qgeom"): + case blt("dpois"): + case blt("ppois"): + + case blt("qpois"): + case blt("dt"): + case blt("pt"): + case blt("qt"): + case blt("dsignrank"): + + case blt("psignrank"): + case blt("qsignrank"): + case blt("besselJ"): + case blt("besselY"): + + case blt("psigamma"): + case blt("Re"): + case blt("Im"): + case blt("Mod"): + case blt("Arg"): + + case blt("Conj"): + case blt("dbeta"): + case blt("pbeta"): + case blt("qbeta"): + case blt("dbinom"): + + case blt("pbinom"): + case blt("qbinom"): + case blt("dcauchy"): + case blt("pcauchy"): + + case blt("qcauchy"): + case blt("df"): + case blt("pf"): + case blt("qf"): + case blt("dgamma"): + + case blt("pgamma"): + case blt("qgamma"): + case blt("dlnorm"): + case blt("plnorm"): + + case blt("qlnorm"): + case blt("dlogis"): + case blt("plogis"): + case blt("qlogis"): + + case blt("dnbinom"): + case blt("pnbinom"): + case blt("qnbinom"): + case blt("dnorm"): + + case blt("pnorm"): + case blt("qnorm"): + case blt("dunif"): + case blt("punif"): + case blt("qunif"): + + case blt("dweibull"): + case blt("pweibull"): + case blt("qweibull"): + case blt("dnchisq"): + + case blt("pnchisq"): + case blt("qnchisq"): + case blt("dnt"): + case blt("pnt"): + case blt("qnt"): + + case blt("dwilcox"): + case blt("pwilcox"): + case blt("qwilcox"): + case blt("besselI"): + + case blt("besselK"): + case blt("dnbinom_mu"): + case blt("pnbinom_mu"): + case blt("qnbinom_mu"): + + case blt("dhyper"): + case blt("phyper"): + case blt("qhyper"): + case blt("dnbeta"): + + case blt("pnbeta"): + case blt("qnbeta"): + case blt("dnf"): + case blt("pnf"): + case blt("qnf"): + + case blt("dtukey"): + case blt("ptukey"): + case blt("qtukey"): + case blt("sum"): + case blt("min"): + + case blt("max"): + case blt("prod"): + case blt("mean"): + case blt("range"): + case blt("as.character"): + + case blt("as.integer"): + case blt("as.double"): + case blt("as.numeric"): + + case blt("as.complex"): + case blt("as.logical"): + case blt("as.raw"): + case blt("as.vector"): + + case blt("is.numeric"): + case blt("is.matrix"): + case blt("is.array"): + + case blt("is.recursive"): + case blt("is.call"): + case blt("is.language"): + + case blt("is.function"): + case blt("is.na"): + case blt("is.nan"): + case blt("is.finite"): + + case blt("is.infinite"): + + case blt("cumsum"): + case blt("colSums"): + + case blt("match"): + + case blt("seq.int"): + case blt("rep.int"): + + case blt("inherits"): + case blt("anyNA"): + return true; + default: {} + } return false; } @@ -489,29 +663,25 @@ bool SafeBuiltinsList::nonObjectIdempotent(SEXP builtin) { V(standardGeneric) bool SafeBuiltinsList::forInline(int builtin) { - static int unsafeBuiltins[] = { -#define V(name) blt(#name), + switch (builtin) { +#define V(name) case blt(#name): UNSAFE_BUILTINS_FOR_INLINE(V) #undef V + return false; + default: {} }; - - for (auto i : unsafeBuiltins) - if (i == builtin) - return false; return true; } bool SafeBuiltinsList::forInlineByName(SEXP name) { - SEXP unsafeBuiltins[] = { + static SEXP unsafeBuiltins[] = { #define V(name) Rf_install(#name), UNSAFE_BUILTINS_FOR_INLINE(V) #undef V }; - for (auto i : unsafeBuiltins) - if (i == name) - return false; - return true; + return std::find(std::begin(unsafeBuiltins), std::end(unsafeBuiltins), + name) == std::end(unsafeBuiltins); } bool SafeBuiltinsList::assumeStableInBaseEnv(SEXP name) { diff --git a/rir/src/compiler/util/visitor.h b/rir/src/compiler/util/visitor.h index d73f9f126..1bde0ea33 100644 --- a/rir/src/compiler/util/visitor.h +++ b/rir/src/compiler/util/visitor.h @@ -5,6 +5,7 @@ #include "../pir/code.h" #include "../pir/instruction.h" #include "../pir/pir.h" +#include "utils/random.h" #include #include @@ -265,6 +266,7 @@ class VisitorImplementation { Marker done; BB* next = nullptr; done.set(cur); + Random random; while (cur) { next = nullptr; @@ -282,13 +284,13 @@ class VisitorImplementation { } else if (returnBranch) { delayed.push_front(bb); } else { - enqueue(todo, bb); + enqueue(todo, bb, random); } } else { if (!next && todo.empty()) { next = bb; } else { - enqueue(todo, bb); + enqueue(todo, bb, random); } } done.set(bb); @@ -324,11 +326,12 @@ class VisitorImplementation { } private: - static bool coinFlip() { return rand() >= (RAND_MAX / 2); }; + static bool coinFlip(Random& random) { return random() > (ULONG_MAX / 2L); } - static void enqueue(std::deque& todo, BB* bb) { + static void enqueue(std::deque& todo, BB* bb, Random& random) { // For analysis random search is faster - if (ORDER == Order::Breadth || (ORDER == Order::Random && coinFlip())) + if (ORDER == Order::Breadth || + (ORDER == Order::Random && coinFlip(random))) todo.push_back(bb); else todo.push_front(bb); diff --git a/rir/src/interpreter/builtins.cpp b/rir/src/interpreter/builtins.cpp index 0d6fef32f..86dd7c2bf 100644 --- a/rir/src/interpreter/builtins.cpp +++ b/rir/src/interpreter/builtins.cpp @@ -1,6 +1,7 @@ #include "builtins.h" #include "R/BuiltinIds.h" #include "R/Funtab.h" +#include "compiler/util/safe_builtins_list.h" #include "interp.h" #include "runtime/LazyArglist.h" #include @@ -8,6 +9,9 @@ extern "C" { extern Rboolean R_Visible; +SEXP R_subset3_dflt(SEXP, SEXP, SEXP); +int R_DispatchOrEvalSP(SEXP call, SEXP op, const char* generic, SEXP args, + SEXP rho, SEXP* ans); } namespace rir { @@ -121,8 +125,51 @@ static IsVectorCheck whichIsVectorCheck(SEXP str) { return IsVectorCheck::unsupported; } -SEXP tryFastSpecialCall(const CallContext& call, InterpreterInstance* ctx) { +SEXP tryFastSpecialCall(CallContext& call, InterpreterInstance* ctx) { + auto nargs = call.passedArgs; switch (call.callee->u.primsxp.offset) { + case blt("substitute"): { + if (nargs != 1 || call.hasNames()) + return nullptr; + return Rf_substitute(call.stackArg(0), call.callerEnv); + } + case blt("$"): { + auto x = call.stackArg(0); + auto s = call.stackArg(1); + if (TYPEOF(s) != PROMSXP) + return nullptr; + s = PREXPR(s); + if (auto c = Code::check(s)) + s = c->trivialExpr; + if (TYPEOF(s) == SYMSXP) + s = PRINTNAME(s); + else if (TYPEOF(s) == STRSXP && XLENGTH(s) > 0) + s = STRING_ELT(s, 0); + + if (nargs == 2 && s && TYPEOF(s) == CHARSXP) { + if (TYPEOF(x) == PROMSXP) + x = evaluatePromise(x, ctx, nullptr, true); + + if (isObject(x)) { + ENSURE_NAMEDMAX(x); + SEXP ss = PROTECT(allocVector(STRSXP, 1)); + SET_STRING_ELT(ss, 0, s); + auto args = CONS_NR(x, CONS_NR(ss, R_NilValue)); + PROTECT(args); + SEXP ans; + if (R_DispatchOrEvalSP(call.ast, call.callee, "$", args, + materializeCallerEnv(call, ctx), &ans)) { + UNPROTECT(2); /* args */ + if (NAMED(ans)) + ENSURE_NAMEDMAX(ans); + return (ans); + } + UNPROTECT(2); + } + return R_subset3_dflt(x, s, call.ast); + } + return nullptr; + } case blt("forceAndCall"): { if (call.passedArgs < 2) @@ -198,28 +245,144 @@ SEXP tryFastSpecialCall(const CallContext& call, InterpreterInstance* ctx) { return nullptr; } -SEXP tryFastBuiltinCall(const CallContext& call, InterpreterInstance* ctx) { - SLOWASSERT(!call.hasNames()); +static constexpr size_t MAXARGS = 8; - static constexpr size_t MAXARGS = 8; - SEXP args[MAXARGS]; - auto nargs = call.suppliedArgs; +bool supportsFastBuiltinCall2(SEXP b, size_t nargs) { + if (nargs > 5) + return false; - if (nargs > MAXARGS) - return nullptr; + // This is a blocklist of builtins which tamper with the argslist in some + // bad way. This can be changing contents and assume they are protected, or + // leaking cons cells of the arglist (e.g. through the gengc_next pointers). + switch (b->u.primsxp.offset) { + // misc + case blt("registerNamespace"): + case blt("...length"): + case blt("...elt"): + case blt("strsplit"): + case blt("eval"): + // Injects args + case blt("standardGeneric"): + // because of fixup_NaRm + case blt("range"): + case blt("sum"): + case blt("min"): + case blt("max"): + case blt("prod"): + case blt("mean"): + case blt("any"): + case blt("all"): + // because of other SETCDR on the argslist + case blt("match.call"): + case blt(".subset"): + case blt(".subset2"): + case blt("$<-"): + case blt("NextMethod"): + case blt("options"): + case blt("&"): + case blt("|"): + case blt("attach"): + case blt("psort"): + // case blt("invisible"): + // because of longjmp + case blt("warning"): + case blt("stop"): + case blt(".dfltStop"): + case blt(".signalCondition"): + // SETCAR + case blt("%*%"): + case blt("match"): + case blt("crossprod"): + case blt("tcrossprod"): + case blt("comment<-"): + case blt("oldClass<-"): + case blt("names<-"): + case blt("dimnames<-"): + case blt("dim<-"): + case blt("levels<-"): + case blt("makeLazy"): + case blt("args"): + case blt("as.function.default"): + case blt("as.call"): + case blt("do.call"): + case blt("call"): + case blt("class<-"): + case blt("debug"): + case blt("undebug"): + case blt("isdebugged"): + case blt("debugonce"): + case blt("dump"): + case blt("browser"): + case blt("unclass"): + case blt("save"): + case blt("saveToConn"): + case blt("[<-"): + case blt("[[<-"): + // SET_TAG + case blt("cbind"): + case blt("rbind"): + return false; + default: {} + } + return true; +} - bool hasAttrib = false; - for (size_t i = 0; i < call.suppliedArgs; ++i) { - auto arg = call.stackArg(i); - if (TYPEOF(arg) == PROMSXP) - arg = evaluatePromise(arg); - if (arg == R_UnboundValue || arg == R_MissingArg) - return nullptr; - if (ATTRIB(arg) != R_NilValue) - hasAttrib = true; - args[i] = arg; + +static bool doesNotAccessEnv(SEXP b) { + return pir::SafeBuiltinsList::nonObject(b->u.primsxp.offset); +} + +SEXP tryFastBuiltinCall2(CallContext& call, InterpreterInstance* ctx, + size_t nargs, SEXP (&args)[MAXARGS]) { + assert(nargs <= 5); + + { + SEXP arglist; + CCODE f = getBuiltin(call.callee); + SEXP res = nullptr; + auto env = doesNotAccessEnv(call.callee) ? R_BaseEnv : materializeCallerEnv(call, ctx); + switch (call.passedArgs) { + case 0: { + return f(call.ast, call.callee, R_NilValue, env); + } + case 1: { + FAKE_ARGS1(arglist, args[0]); + res = f(call.ast, call.callee, arglist, env); + CHECK_FAKE_ARGS1(); + break; + } + case 2: { + FAKE_ARGS2(arglist, args[0], args[1]); + res = f(call.ast, call.callee, arglist, env); + CHECK_FAKE_ARGS2(); + break; + } + case 3: { + FAKE_ARGS3(arglist, args[0], args[1], args[2]); + res = f(call.ast, call.callee, arglist, env); + CHECK_FAKE_ARGS3(); + break; + } + case 4: { + FAKE_ARGS4(arglist, args[0], args[1], args[2], args[3]); + res = f(call.ast, call.callee, arglist, env); + CHECK_FAKE_ARGS4(); + break; + } + case 5: { + FAKE_ARGS5(arglist, args[0], args[1], args[2], args[3], args[4]); + res = f(call.ast, call.callee, arglist, env); + CHECK_FAKE_ARGS5(); + break; + } + } + return res; } + return nullptr; +} +SEXP tryFastBuiltinCall1(const CallContext& call, InterpreterInstance* ctx, + size_t nargs, bool hasAttrib, SEXP (&args)[MAXARGS]) { switch (call.callee->u.primsxp.offset) { case blt("is.logical"): { if (nargs != 1) @@ -540,6 +703,9 @@ SEXP tryFastBuiltinCall(const CallContext& call, InterpreterInstance* ctx) { SEXP r = args[0]; return r; } + if (TYPEOF(args[0]) == SYMSXP) { + return ScalarString(PRINTNAME(args[0])); + } if (IS_SIMPLE_SCALAR(args[0], INTSXP)) { auto i = INTEGER(args[0])[0]; if (i >= 0 && i < 1000) { @@ -869,7 +1035,7 @@ SEXP tryFastBuiltinCall(const CallContext& call, InterpreterInstance* ctx) { return nullptr; } -bool supportsFastBuiltinCall(SEXP b) { +bool supportsFastBuiltinCall(SEXP b, size_t nargs) { switch (b->u.primsxp.offset) { case blt("nargs"): case blt("length"): @@ -911,10 +1077,45 @@ bool supportsFastBuiltinCall(SEXP b) { case blt("col"): case blt("row"): case blt("dim"): + case blt("$"): return true; default: {} } return false; } +SEXP tryFastBuiltinCall(CallContext& call, InterpreterInstance* ctx) { + SLOWASSERT(!call.hasNames()); + + SEXP args[MAXARGS]; + auto nargs = call.suppliedArgs; + + if (nargs > MAXARGS) + return nullptr; + + bool hasAttrib = false; + for (size_t i = 0; i < call.suppliedArgs; ++i) { + auto arg = call.stackArg(i); + if (TYPEOF(arg) == PROMSXP) + arg = evaluatePromise(arg); + if (arg == R_UnboundValue || arg == R_MissingArg) + return nullptr; + if (ATTRIB(arg) != R_NilValue) + hasAttrib = true; + args[i] = arg; + } + + auto res = tryFastBuiltinCall1(call, ctx, nargs, hasAttrib, args); + if (res) + return res; + + if (hasAttrib) + return nullptr; + + if (!supportsFastBuiltinCall2(call.callee, nargs)) + return nullptr; + + return tryFastBuiltinCall2(call, ctx, nargs, args); +} + } // namespace rir diff --git a/rir/src/interpreter/builtins.h b/rir/src/interpreter/builtins.h index ceca750eb..84627537f 100644 --- a/rir/src/interpreter/builtins.h +++ b/rir/src/interpreter/builtins.h @@ -5,9 +5,9 @@ namespace rir { -SEXP tryFastSpecialCall(const CallContext& call, InterpreterInstance* ctx); -SEXP tryFastBuiltinCall(const CallContext& call, InterpreterInstance* ctx); -bool supportsFastBuiltinCall(SEXP blt); +SEXP tryFastSpecialCall(CallContext& call, InterpreterInstance* ctx); +SEXP tryFastBuiltinCall(CallContext& call, InterpreterInstance* ctx); +bool supportsFastBuiltinCall(SEXP blt, size_t nargs); } // namespace rir diff --git a/rir/src/interpreter/cache.h b/rir/src/interpreter/cache.h index 475d5ffff..fa6ddad3f 100644 --- a/rir/src/interpreter/cache.h +++ b/rir/src/interpreter/cache.h @@ -36,7 +36,10 @@ static RIR_INLINE void clearCache(BindingCache* cache) { static RIR_INLINE SEXP cachedGetBindingCell(Immediate cacheIdx, BindingCache* cache) { SLOWASSERT(cacheIdx < cache->length); - return cache->entry[cacheIdx]; + auto cell = cache->entry[cacheIdx]; + if (cell && CAR(cell) == R_UnboundValue) + cell = cache->entry[cacheIdx] = nullptr; + return cell; } static RIR_INLINE void diff --git a/rir/src/interpreter/interp.cpp b/rir/src/interpreter/interp.cpp index ff3abe20f..ea0b9808e 100644 --- a/rir/src/interpreter/interp.cpp +++ b/rir/src/interpreter/interp.cpp @@ -2,6 +2,7 @@ #include "R/Funtab.h" #include "R/RList.h" #include "R/Symbols.h" +#include "api.h" #include "cache.h" #include "compiler/compiler.h" #include "compiler/parameter.h" @@ -10,9 +11,11 @@ #include "runtime/LazyEnvironment.h" #include "runtime/TypeFeedback_inl.h" #include "safe_force.h" +#include "utils/ContextualProfiling.h" +#include "utils/CompilationStrategy.h" +#include "utils/FunctionVersion.h" #include "utils/Pool.h" #include "utils/measuring.h" -#include "utils/ContextualProfiling.h" #include #include @@ -182,7 +185,8 @@ typedef struct RPRSTACK { } RPRSTACK; extern "C" struct RPRSTACK* R_PendingPromises; -SEXP evaluatePromise(SEXP e, InterpreterInstance* ctx, Opcode* pc) { +SEXP evaluatePromise(SEXP e, InterpreterInstance* ctx, Opcode* pc, + bool delayNamed) { // if already evaluated, return the value if (PRVALUE(e) && PRVALUE(e) != R_UnboundValue) { e = PRVALUE(e); @@ -216,7 +220,8 @@ SEXP evaluatePromise(SEXP e, InterpreterInstance* ctx, Opcode* pc) { R_PendingPromises = prstack.next; SET_PRSEEN(e, 0); SET_PRVALUE(e, val); - ENSURE_NAMEDMAX(val); + if (!delayNamed) + ENSURE_NAMEDMAX(val); SET_PRENV(e, R_NilValue); assert(TYPEOF(val) != PROMSXP && "promise returned promise"); @@ -317,7 +322,7 @@ SEXP materialize(SEXP wrapper) { return res; } -static SEXP materializeCallerEnv(CallContext& callCtx, +SEXP materializeCallerEnv(CallContext& callCtx, InterpreterInstance* ctx) { if (auto le = LazyEnvironment::check(callCtx.callerEnv)) { if (le->materialized()) @@ -592,6 +597,7 @@ void recordDeoptReason(SEXP val, const DeoptReason& reason) { case DeoptReason::DeadCall: reason.srcCode->deadCallReached++; // fall through + [[clang::fallthrough]]; case DeoptReason::Calltarget: { assert(*pos == Opcode::record_call_); ObservedCallees* feedback = (ObservedCallees*)(pos + 1); @@ -907,6 +913,9 @@ void inferCurrentContext(CallContext& call, size_t formalNargs, if (IS_SIMPLE_SCALAR(arg, INTSXP)) given.setSimpleInt(i); } + + if (arg == R_MissingArg) + given.resetNotObj(i); }; bool tryArgmatch = !given.includes(Assumption::StaticallyArgmatched); @@ -970,7 +979,10 @@ static SEXP rirCallCallerProvidedEnv(CallContext& call, Function* fun, SEXP frame; SEXP promargs; if (call.arglist) { - promargs = frame = call.arglist; + promargs = call.arglist; + frame = Rf_shallow_duplicate(promargs); + PROTECT(promargs); + npreserved++; } else { // Wrap the passed args in a linked-list. frame = createEnvironmentFrameFromStackValues(call, ctx); @@ -993,11 +1005,6 @@ static SEXP rirCallCallerProvidedEnv(CallContext& call, Function* fun, // some missing args might need to be supplied. if (!call.givenContext.includes(Assumption::NoExplicitlyMissingArgs) || call.passedArgs != fun->nargs()) { - if (call.arglist) { - promargs = Rf_shallow_duplicate(promargs); - PROTECT(promargs); - npreserved++; - } auto f = formals; auto a = frame; @@ -1032,6 +1039,9 @@ static SEXP rirCallCallerProvidedEnv(CallContext& call, Function* fun, pos++; } } + + if (call.suppliedvars != R_NilValue) + Rf_addMissingVarsToNewEnv(env, call.suppliedvars); } else { // No need for lazy args if we have the non-modified list anyway promargs = frame; @@ -1067,11 +1077,11 @@ RIR_INLINE SEXP rirCall(CallContext& call, InterpreterInstance* ctx) { fun->registerInvocation(); // For Logger -- START - size_t lMethodId = ContextualProfiling::getEntryKey(call); + size_t lMethodId = FunctionVersion::getFunctionId(call.callee); Context lContext = call.givenContext; // For Logger -- END - if (ContextualProfiling::compileFlag(lMethodId, lContext) && !isDeoptimizing() && RecompileHeuristic(table, fun)) { + if (!isDeoptimizing() && RecompileHeuristic(table, fun) && CompilationStrategy::compileFlag(lMethodId, lContext)) { Context given = call.givenContext; // addDynamicAssumptionForOneTarget compares arguments with the // signature of the current dispatch target. There the number of @@ -1094,7 +1104,16 @@ RIR_INLINE SEXP rirCall(CallContext& call, InterpreterInstance* ctx) { *fun ); - auto start = std::chrono::system_clock::now(); // runtime start +#ifdef LOG_FUNCTION_CALLS + pir::StreamLogger logger(PirDebug); + { + std::stringstream msg_ss; + std::string name = ContextualProfiling::extractFunctionName(call.ast); + msg_ss << "Calling " << name << fun->context().getShortStringRepr() + << " in context " << lContext.getShortStringRepr(); + logger.warn(msg_ss.str()); + } +#endif bool needsEnv = fun->signature().envCreation == FunctionSignature::Environment::CallerProvided; @@ -1104,16 +1123,12 @@ RIR_INLINE SEXP rirCall(CallContext& call, InterpreterInstance* ctx) { call.depromiseArgs(); } - LazyArglistOnStack lazyPromargs( call.callId, call.caller ? call.caller->arglistOrderContainer() : nullptr, call.suppliedArgs, call.stackArgs, call.ast); - SEXP result; - - std::chrono::duration duration; if (!needsEnv) { // Default fast calling convention for pir, environment is created by // the callee @@ -1135,18 +1150,6 @@ RIR_INLINE SEXP rirCall(CallContext& call, InterpreterInstance* ctx) { } assert(result); assert(!fun->flags.contains(Function::Deopt)); - - - auto end = std::chrono::system_clock::now(); - duration = end - start; - - ContextualProfiling::addFunctionDispatchRuntime( - lMethodId, - lContext, - *fun, - duration - ); - return result; } @@ -1309,26 +1312,6 @@ enum class Binop { PLUSOP, MINUSOP, TIMESOP }; enum class Unop { PLUSOP, MINUSOP }; #define INTEGER_OVERFLOW_WARNING "NAs produced by integer overflow" -static SEXPREC createFakeSEXP(SEXPTYPE t) { - SEXPREC res; - res.attrib = R_NilValue; - res.gengc_next_node = R_NilValue; - res.gengc_prev_node = R_NilValue; - res.sxpinfo.gcgen = 1; - res.sxpinfo.mark = 1; - res.sxpinfo.named = 2; - res.sxpinfo.type = t; - return res; -} - -static SEXPREC createFakeCONS(SEXP cdr) { - auto res = createFakeSEXP(LISTSXP); - res.u.listsxp.carval = R_NilValue; - res.u.listsxp.tagval = R_NilValue; - res.u.listsxp.cdrval = cdr; - return res; -} - #define CHECK_INTEGER_OVERFLOW(ans, naflag) \ do { \ if (naflag) { \ @@ -1354,18 +1337,10 @@ static SEXPREC createFakeCONS(SEXP cdr) { R_Visible = static_cast(flag != 1); \ SEXP call = getSrcForCall(c, pc - 1, ctx); \ \ - if (!env || !(isObject(lhs) || isObject(rhs))) { \ - SEXPREC arglist2 = createFakeCONS(R_NilValue); \ - SEXPREC arglist = createFakeCONS(&arglist2); \ - arglist.u.listsxp.carval = lhs; \ - arglist2.u.listsxp.carval = rhs; \ - res = blt(call, prim, &arglist, env); \ - } else { \ - SEXP arglist = CONS_NR(lhs, CONS_NR(rhs, R_NilValue)); \ - ostack_push(ctx, arglist); \ - res = blt(call, prim, arglist, env); \ - ostack_pop(ctx); \ - } \ + SEXP arglist = CONS_NR(lhs, CONS_NR(rhs, R_NilValue)); \ + ostack_push(ctx, arglist); \ + res = blt(call, prim, arglist, env); \ + ostack_pop(ctx); \ \ if (flag < 2) \ R_Visible = static_cast(flag != 1); \ @@ -1833,9 +1808,11 @@ size_t expandDotDotDotCallArgs(InterpreterInstance* ctx, size_t n, args.push_back(R_MissingArg); names.push_back(R_NilValue); } - } else if (ellipsis == R_NilValue) { + } else if (ellipsis == R_NilValue || ellipsis == R_UnboundValue) { } else { - assert(ellipsis == R_UnboundValue); + // TODO: why does this happen in SERIALIZE CHAOS? + args.push_back(ellipsis); + names.push_back(R_NilValue); } } } @@ -1995,10 +1972,11 @@ SEXP evalRirCode(Code* c, InterpreterInstance* ctx, SEXP env, assert(env != symbol::delayedEnv || (callCtxt != nullptr)); checkUserInterrupt(); - assert((!initialPC || !c->nativeCode) && "Cannot jump into native code"); - if (c->nativeCode) { - return c->nativeCode(c, callCtxt ? (void*)callCtxt->stackArgs : nullptr, - env, callCtxt ? callCtxt->callee : nullptr); + auto native = c->nativeCode(); + assert((!initialPC || !native) && "Cannot jump into native code"); + if (native) { + return native(c, callCtxt ? (void*)callCtxt->stackArgs : nullptr, env, + callCtxt ? callCtxt->callee : nullptr); } #ifdef THREADED_CODE @@ -2221,6 +2199,34 @@ SEXP evalRirCode(Code* c, InterpreterInstance* ctx, SEXP env, NEXT(); } + INSTRUCTION(ldvar_noforce_) { + SEXP sym = readConst(ctx, readImmediate()); + advanceImmediate(); + assert(!LazyEnvironment::check(env)); + res = Rf_findVar(sym, env); + R_Visible = TRUE; + + if (res == R_UnboundValue) { + Rf_error("object \"%s\" not found", CHAR(PRINTNAME(sym))); + } else if (res == R_MissingArg) { + Rf_error("argument \"%s\" is missing, with no default", + CHAR(PRINTNAME(sym))); + } else if (TYPEOF(res) == PROMSXP) { + // if already evaluated, return the value + if (PRVALUE(res) && PRVALUE(res) != R_UnboundValue) { + res = PRVALUE(res); + assert(TYPEOF(res) != PROMSXP); + + if (res != R_NilValue) + ENSURE_NAMED(res); + } + } + + ostack_push(ctx, res); + NEXT(); + } + + INSTRUCTION(ldvar_cached_) { Immediate id = readImmediate(); advanceImmediate(); @@ -3763,6 +3769,14 @@ SEXP evalRirCode(Code* c, InterpreterInstance* ctx, SEXP env, NEXT(); } + INSTRUCTION(as_switch_idx_) { + if (TYPEOF(ostack_top(ctx)) != INTSXP) { + auto v = asInteger(ostack_pop(ctx)); + ostack_push(ctx, Rf_ScalarInteger(v == NA_INTEGER ? -1 : v)); + } + NEXT(); + } + INSTRUCTION(for_seq_size_) { SEXP seq = ostack_at(ctx, 0); // TODO: we should extract the length just once at the begining of @@ -3781,10 +3795,14 @@ SEXP evalRirCode(Code* c, InterpreterInstance* ctx, SEXP env, // flag here. What we should do instead, is use a non-dispatching // extract BC. if (isObject(seq)) { - seq = Rf_shallow_duplicate(seq); + if (Rf_inherits(seq, "factor")) + seq = asCharacterFactor(seq); + else + seq = Rf_shallow_duplicate(seq); SET_OBJECT(seq, 0); ostack_set(ctx, 0, seq); } + ENSURE_NAMEDMAX(seq); ostack_push(ctx, value); NEXT(); } @@ -3868,25 +3886,36 @@ SEXP rirApplyClosure(SEXP ast, SEXP op, SEXP arglist, SEXP rho, SEXP suppliedvars) { auto ctx = globalContext(); - RList args(arglist); size_t nargs = 0; - std::vector names; - for (auto arg = args.begin(), end = args.end(); arg != end; ++arg) { - ostack_push(ctx, *arg); - if (arg.hasTag()) { - names.resize(nargs + 1); - names[nargs] = Pool::insert(arg.tag()); + Immediate* names = nullptr; + { + RList args(arglist); + auto n = Pool::insert(R_NilValue); + std::vector namesList; + for (auto arg = args.begin(), end = args.end(); arg != end; ++arg) { + ostack_push(ctx, *arg); + if (arg.hasTag()) { + namesList.resize(nargs + 1, n); + namesList[nargs] = Pool::insert(arg.tag()); + } + nargs++; + } + if (!namesList.empty()) { + auto namesStore = Rf_allocVector(RAWSXP, sizeof(Immediate) * nargs); + names = (Immediate*)RAW(namesStore); + for (size_t i = 0; i < nargs; ++i) { + if (i < namesList.size()) + names[i] = namesList[i]; + else + names[i] = n; + } + PROTECT(namesStore); } - nargs++; - } - if (!names.empty()) { - names.resize(nargs); } CallContext call(ArglistOrder::NOT_REORDERED, nullptr, op, nargs, ast, - ostack_cell_at(ctx, nargs - 1), - names.empty() ? nullptr : names.data(), rho, suppliedvars, - Context(), ctx); + ostack_cell_at(ctx, (long)nargs - 1), names, rho, + suppliedvars, Context(), ctx); call.arglist = arglist; call.safeForceArgs(); @@ -3896,6 +3925,8 @@ SEXP rirApplyClosure(SEXP ast, SEXP op, SEXP arglist, SEXP rho, auto res = rirCall(call, ctx); ostack_popn(ctx, call.passedArgs); + if (names) + UNPROTECT(1); return res; } diff --git a/rir/src/interpreter/interp.h b/rir/src/interpreter/interp.h index 8baa6a82d..0b871e898 100644 --- a/rir/src/interpreter/interp.h +++ b/rir/src/interpreter/interp.h @@ -19,6 +19,13 @@ #define THREADED_CODE #endif +#ifdef SANITIZE +extern "C" void __asan_poison_memory_region(void const volatile* addr, + size_t size); +extern "C" void __asan_unpoison_memory_region(void const volatile* addr, + size_t size); +#endif + namespace rir { SEXP dispatchApply(SEXP ast, SEXP obj, SEXP actuals, SEXP selector, SEXP callerEnv, InterpreterInstance* ctx); @@ -66,7 +73,8 @@ inline bool RecompileCondition(DispatchTable* table, Function* fun, const Context& context) { return (fun->flags.contains(Function::MarkOpt) || fun == table->baseline() || - (context.smaller(fun->context()) && context.isImproving(fun)) || + (context.smaller(fun->context()) && + context.isImproving(fun) > table->size()) || fun->body()->flags.contains(Code::Reoptimise)); } @@ -138,5 +146,235 @@ inline bool needsExpandedDots(SEXP callee) { callee->u.primsxp.offset == blt("forceAndCall"); } +SEXP materializeCallerEnv(CallContext& callCtx, + InterpreterInstance* ctx); + +inline void createFakeSEXP(SEXP res, SEXPTYPE t) { + memset(res, 0, sizeof(SEXPREC)); + res->attrib = R_NilValue; + res->gengc_next_node = R_NilValue; + res->gengc_prev_node = R_NilValue; + res->sxpinfo.gcgen = 1; + res->sxpinfo.mark = 1; + res->sxpinfo.named = NAMEDMAX; + res->sxpinfo.type = t; +} + +inline void createFakeCONS(SEXPREC& res, SEXP cdr) { + createFakeSEXP(&res, LISTSXP); + res.u.listsxp.carval = R_NilValue; + res.u.listsxp.tagval = R_NilValue; + res.u.listsxp.cdrval = cdr; +} + +inline SEXPREC createFakeCONS(SEXP cdr) { + SEXPREC res; + createFakeSEXP(&res, LISTSXP); + res.u.listsxp.carval = R_NilValue; + res.u.listsxp.tagval = R_NilValue; + res.u.listsxp.cdrval = cdr; + return res; +} + +#define MATERIALIZE_IF_OBJ1(res, a1) \ + if (isObject(a1)) { \ + res = CONS_NR(a1, R_NilValue); \ + } + +#define MATERIALIZE_IF_OBJ2(res, a1, a2) \ + if (isObject(a1) || isObject(a2)) { \ + res = CONS_NR(a1, CONS_NR(a2, R_NilValue)); \ + } + +#define FAKE_ARGS1(res, a1) \ + SEXPREC __a1__cell__; \ + createFakeCONS(__a1__cell__, R_NilValue); \ + __a1__cell__.u.listsxp.carval = a1; \ + res = &__a1__cell__ + +#define CHECK_FAKE_ARGS1() \ + SLOWASSERT(__a1__cell__.gengc_next_node == R_NilValue && \ + "broken cons gengc_next_node a1/1"); \ + SLOWASSERT(__a1__cell__.gengc_prev_node == R_NilValue && \ + "broken cons gengc_prev_node a1/1"); \ + SLOWASSERT(__a1__cell__.u.listsxp.tagval == R_NilValue && \ + "broken cons tag a1/1"); \ + SLOWASSERT(__a1__cell__.u.listsxp.cdrval == R_NilValue && \ + "broken cons a1/1") + +#define FAKE_ARGS2(res, a1, a2) \ + SEXPREC __a2__cell__; \ + createFakeCONS(__a2__cell__, R_NilValue); \ + SEXPREC __a1__cell__; \ + createFakeCONS(__a1__cell__, &__a2__cell__); \ + __a1__cell__.u.listsxp.carval = a1; \ + __a2__cell__.u.listsxp.carval = a2; \ + res = &__a1__cell__ + +#define CHECK_FAKE_ARGS2() \ + SLOWASSERT(__a1__cell__.gengc_next_node == R_NilValue && \ + "broken cons gengc_next_node a1/2"); \ + SLOWASSERT(__a2__cell__.gengc_next_node == R_NilValue && \ + "broken cons gengc_next_node a2/2"); \ + SLOWASSERT(__a1__cell__.gengc_prev_node == R_NilValue && \ + "broken cons gengc_prev_node a1/2"); \ + SLOWASSERT(__a2__cell__.gengc_prev_node == R_NilValue && \ + "broken cons gengc_prev_node a2/2"); \ + SLOWASSERT(__a1__cell__.u.listsxp.tagval == R_NilValue && \ + "broken cons tag a1/2"); \ + SLOWASSERT(__a2__cell__.u.listsxp.tagval == R_NilValue && \ + "broken cons tag a2/2"); \ + SLOWASSERT(__a1__cell__.u.listsxp.cdrval == &__a2__cell__ && \ + "broken cons a1/2"); \ + SLOWASSERT(__a2__cell__.u.listsxp.cdrval == R_NilValue && \ + "broken cons a2/2") + +#define FAKE_ARGS3(res, a1, a2, a3) \ + SEXPREC __a3__cell__; \ + createFakeCONS(__a3__cell__, R_NilValue); \ + SEXPREC __a2__cell__; \ + createFakeCONS(__a2__cell__, &__a3__cell__); \ + SEXPREC __a1__cell__; \ + createFakeCONS(__a1__cell__, &__a2__cell__); \ + __a1__cell__.u.listsxp.carval = a1; \ + __a2__cell__.u.listsxp.carval = a2; \ + __a3__cell__.u.listsxp.carval = a3; \ + res = &__a1__cell__ + +#define CHECK_FAKE_ARGS3() \ + SLOWASSERT(__a1__cell__.gengc_next_node == R_NilValue && \ + "broken cons gengc_next_node a1/3"); \ + SLOWASSERT(__a2__cell__.gengc_next_node == R_NilValue && \ + "broken cons gengc_next_node a2/3"); \ + SLOWASSERT(__a3__cell__.gengc_next_node == R_NilValue && \ + "broken cons gengc_next_node a3/3"); \ + SLOWASSERT(__a1__cell__.gengc_prev_node == R_NilValue && \ + "broken cons gengc_prev_node a1/3"); \ + SLOWASSERT(__a2__cell__.gengc_prev_node == R_NilValue && \ + "broken cons gengc_prev_node a2/3"); \ + SLOWASSERT(__a3__cell__.gengc_prev_node == R_NilValue && \ + "broken cons gengc_prev_node a3/3"); \ + SLOWASSERT(__a1__cell__.u.listsxp.tagval == R_NilValue && \ + "broken cons tag a1/3"); \ + SLOWASSERT(__a2__cell__.u.listsxp.tagval == R_NilValue && \ + "broken cons tag a2/3"); \ + SLOWASSERT(__a3__cell__.u.listsxp.tagval == R_NilValue && \ + "broken cons tag a3/3"); \ + SLOWASSERT(__a1__cell__.u.listsxp.cdrval == &__a2__cell__ && \ + "broken cons a1/3"); \ + SLOWASSERT(__a2__cell__.u.listsxp.cdrval == &__a3__cell__ && \ + "broken cons a2/3"); \ + SLOWASSERT(__a3__cell__.u.listsxp.cdrval == R_NilValue && \ + "broken cons a3/3") + +#define FAKE_ARGS4(res, a1, a2, a3, a4) \ + SEXPREC __a4__cell__; \ + createFakeCONS(__a4__cell__, R_NilValue); \ + SEXPREC __a3__cell__; \ + createFakeCONS(__a3__cell__, &__a4__cell__); \ + SEXPREC __a2__cell__; \ + createFakeCONS(__a2__cell__, &__a3__cell__); \ + SEXPREC __a1__cell__; \ + createFakeCONS(__a1__cell__, &__a2__cell__); \ + __a1__cell__.u.listsxp.carval = a1; \ + __a2__cell__.u.listsxp.carval = a2; \ + __a3__cell__.u.listsxp.carval = a3; \ + __a4__cell__.u.listsxp.carval = a4; \ + res = &__a1__cell__ + +#define CHECK_FAKE_ARGS4() \ + SLOWASSERT(__a1__cell__.gengc_next_node == R_NilValue && \ + "broken cons gengc_next_node a1/4"); \ + SLOWASSERT(__a2__cell__.gengc_next_node == R_NilValue && \ + "broken cons gengc_next_node a2/4"); \ + SLOWASSERT(__a3__cell__.gengc_next_node == R_NilValue && \ + "broken cons gengc_next_node a3/4"); \ + SLOWASSERT(__a4__cell__.gengc_next_node == R_NilValue && \ + "broken cons gengc_next_node a4/4"); \ + SLOWASSERT(__a1__cell__.gengc_prev_node == R_NilValue && \ + "broken cons gengc_prev_node a1/4"); \ + SLOWASSERT(__a2__cell__.gengc_prev_node == R_NilValue && \ + "broken cons gengc_prev_node a2/4"); \ + SLOWASSERT(__a3__cell__.gengc_prev_node == R_NilValue && \ + "broken cons gengc_prev_node a3/4"); \ + SLOWASSERT(__a4__cell__.gengc_prev_node == R_NilValue && \ + "broken cons gengc_prev_node a4/4"); \ + SLOWASSERT(__a1__cell__.u.listsxp.tagval == R_NilValue && \ + "broken cons tag a1/4"); \ + SLOWASSERT(__a2__cell__.u.listsxp.tagval == R_NilValue && \ + "broken cons tag a2/4"); \ + SLOWASSERT(__a3__cell__.u.listsxp.tagval == R_NilValue && \ + "broken cons tag a3/4"); \ + SLOWASSERT(__a4__cell__.u.listsxp.tagval == R_NilValue && \ + "broken cons tag a4/4"); \ + SLOWASSERT(__a1__cell__.u.listsxp.cdrval == &__a2__cell__ && \ + "broken cons a1/4"); \ + SLOWASSERT(__a2__cell__.u.listsxp.cdrval == &__a3__cell__ && \ + "broken cons a2/4"); \ + SLOWASSERT(__a3__cell__.u.listsxp.cdrval == &__a4__cell__ && \ + "broken cons a3/4"); \ + SLOWASSERT(__a4__cell__.u.listsxp.cdrval == R_NilValue && \ + "broken cons a4/4") + +#define FAKE_ARGS5(res, a1, a2, a3, a4, a5) \ + SEXPREC __a5__cell__; \ + createFakeCONS(__a5__cell__, R_NilValue); \ + SEXPREC __a4__cell__; \ + createFakeCONS(__a4__cell__, &__a5__cell__); \ + SEXPREC __a3__cell__; \ + createFakeCONS(__a3__cell__, &__a4__cell__); \ + SEXPREC __a2__cell__; \ + createFakeCONS(__a2__cell__, &__a3__cell__); \ + SEXPREC __a1__cell__; \ + createFakeCONS(__a1__cell__, &__a2__cell__); \ + __a1__cell__.u.listsxp.carval = a1; \ + __a2__cell__.u.listsxp.carval = a2; \ + __a3__cell__.u.listsxp.carval = a3; \ + __a4__cell__.u.listsxp.carval = a4; \ + __a5__cell__.u.listsxp.carval = a5; \ + res = &__a1__cell__ + +#define CHECK_FAKE_ARGS5() \ + SLOWASSERT(__a1__cell__.gengc_next_node == R_NilValue && \ + "broken cons gengc_next_node a1/5"); \ + SLOWASSERT(__a2__cell__.gengc_next_node == R_NilValue && \ + "broken cons gengc_next_node a2/5"); \ + SLOWASSERT(__a3__cell__.gengc_next_node == R_NilValue && \ + "broken cons gengc_next_node a3/5"); \ + SLOWASSERT(__a4__cell__.gengc_next_node == R_NilValue && \ + "broken cons gengc_next_node a4/5"); \ + SLOWASSERT(__a5__cell__.gengc_next_node == R_NilValue && \ + "broken cons gengc_next_node a5/5"); \ + SLOWASSERT(__a1__cell__.gengc_prev_node == R_NilValue && \ + "broken cons gengc_prev_node a1/5"); \ + SLOWASSERT(__a2__cell__.gengc_prev_node == R_NilValue && \ + "broken cons gengc_prev_node a2/5"); \ + SLOWASSERT(__a3__cell__.gengc_prev_node == R_NilValue && \ + "broken cons gengc_prev_node a3/5"); \ + SLOWASSERT(__a4__cell__.gengc_prev_node == R_NilValue && \ + "broken cons gengc_prev_node a4/5"); \ + SLOWASSERT(__a5__cell__.gengc_prev_node == R_NilValue && \ + "broken cons gengc_prev_node a5/5"); \ + SLOWASSERT(__a1__cell__.u.listsxp.tagval == R_NilValue && \ + "broken cons tag a1/5"); \ + SLOWASSERT(__a2__cell__.u.listsxp.tagval == R_NilValue && \ + "broken cons tag a2/5"); \ + SLOWASSERT(__a3__cell__.u.listsxp.tagval == R_NilValue && \ + "broken cons tag a3/5"); \ + SLOWASSERT(__a4__cell__.u.listsxp.tagval == R_NilValue && \ + "broken cons tag a4/5"); \ + SLOWASSERT(__a5__cell__.u.listsxp.tagval == R_NilValue && \ + "broken cons tag a4/5"); \ + SLOWASSERT(__a1__cell__.u.listsxp.cdrval == &__a2__cell__ && \ + "broken cons a1/5"); \ + SLOWASSERT(__a2__cell__.u.listsxp.cdrval == &__a3__cell__ && \ + "broken cons a2/5"); \ + SLOWASSERT(__a3__cell__.u.listsxp.cdrval == &__a4__cell__ && \ + "broken cons a3/5"); \ + SLOWASSERT(__a4__cell__.u.listsxp.cdrval == &__a5__cell__ && \ + "broken cons a4/5"); \ + SLOWASSERT(__a5__cell__.u.listsxp.cdrval == R_NilValue && \ + "broken cons a5/5") + } // namespace rir #endif // RIR_INTERPRETER_C_H diff --git a/rir/src/interpreter/interp_incl.h b/rir/src/interpreter/interp_incl.h index ad052a6fa..4ba5835a7 100644 --- a/rir/src/interpreter/interp_incl.h +++ b/rir/src/interpreter/interp_incl.h @@ -54,7 +54,8 @@ SEXP copyBySerial(SEXP x); SEXP materialize(SEXP rirDataWrapper); -SEXP evaluatePromise(SEXP e, InterpreterInstance* ctx, Opcode* pc); +SEXP evaluatePromise(SEXP e, InterpreterInstance* ctx, Opcode* pc, + bool delayNamed = false); inline SEXP evaluatePromise(SEXP e, InterpreterInstance* ctx) { return evaluatePromise(e, ctx, nullptr); } diff --git a/rir/src/ir/BC.cpp b/rir/src/ir/BC.cpp index e9c38167e..95a59b58c 100644 --- a/rir/src/ir/BC.cpp +++ b/rir/src/ir/BC.cpp @@ -46,6 +46,7 @@ void BC::write(CodeStream& cs) const { 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_: @@ -134,6 +135,7 @@ void BC::deserialize(SEXP refTable, R_inpstream_t inp, Opcode* code, 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_: @@ -229,6 +231,7 @@ void BC::serialize(SEXP refTable, R_outpstream_t out, const Opcode* code, 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_: @@ -373,6 +376,7 @@ void BC::print(std::ostream& out) const { break; case Opcode::ldfun_: case Opcode::ldvar_: + case Opcode::ldvar_noforce_: case Opcode::ldvar_for_update_: case Opcode::ldvar_super_: case Opcode::ldddvar_: diff --git a/rir/src/ir/BC.h b/rir/src/ir/BC.h index 817bfbeb0..ea1c3dec4 100644 --- a/rir/src/ir/BC.h +++ b/rir/src/ir/BC.h @@ -26,6 +26,8 @@ BC BC::recordCall() { return BC(Opcode::record_call_); } BC BC::recordType() { return BC(Opcode::record_type_); } BC BC::recordTest() { return BC(Opcode::record_test_); } +BC BC::asSwitchIdx() { return BC(Opcode::as_switch_idx_); } + BC BC::popn(unsigned n) { ImmediateArguments i; i.i = n; @@ -72,9 +74,19 @@ BC BC::ldvar(SEXP sym) { i.pool = Pool::insert(sym); return BC(Opcode::ldvar_, i); } + +BC BC::ldvarNoForce(SEXP sym) { + assert(TYPEOF(sym) == SYMSXP); + assert(strlen(CHAR(PRINTNAME(sym)))); + ImmediateArguments i; + i.pool = Pool::insert(sym); + return BC(Opcode::ldvar_noforce_, i); +} + BC BC::ldvarCached(SEXP sym, uint32_t cacheSlot) { assert(TYPEOF(sym) == SYMSXP); assert(strlen(CHAR(PRINTNAME(sym)))); + assert(cacheSlot != (uint32_t)-1); ImmediateArguments i; i.poolAndCache.poolIndex = Pool::insert(sym); i.poolAndCache.cacheIndex = cacheSlot; @@ -83,6 +95,7 @@ BC BC::ldvarCached(SEXP sym, uint32_t cacheSlot) { BC BC::ldvarForUpdateCached(SEXP sym, uint32_t cacheSlot) { assert(TYPEOF(sym) == SYMSXP); assert(strlen(CHAR(PRINTNAME(sym)))); + assert(cacheSlot != (uint32_t)-1); ImmediateArguments i; i.poolAndCache.poolIndex = Pool::insert(sym); i.poolAndCache.cacheIndex = cacheSlot; @@ -148,6 +161,7 @@ BC BC::stvar(SEXP sym) { BC BC::stvarCached(SEXP sym, uint32_t cacheSlot) { assert(TYPEOF(sym) == SYMSXP); assert(strlen(CHAR(PRINTNAME(sym)))); + assert(cacheSlot != (uint32_t)-1); ImmediateArguments i; i.poolAndCache.poolIndex = Pool::insert(sym); i.poolAndCache.cacheIndex = cacheSlot; diff --git a/rir/src/ir/BC_inc.h b/rir/src/ir/BC_inc.h index b905cbe20..45901dd99 100644 --- a/rir/src/ir/BC_inc.h +++ b/rir/src/ir/BC_inc.h @@ -318,6 +318,7 @@ BC_NOARGS(V, _) inline static BC recordBinop(); inline static BC recordType(); inline static BC recordTest(); + inline static BC asSwitchIdx(); inline static BC popn(unsigned n); inline static BC push(SEXP constant); inline static BC push(double constant); @@ -326,6 +327,7 @@ BC_NOARGS(V, _) inline static BC push_code(FunIdx i); inline static BC ldfun(SEXP sym); inline static BC ldvar(SEXP sym); + inline static BC ldvarNoForce(SEXP sym); inline static BC ldvarCached(SEXP sym, uint32_t cacheSlot); inline static BC ldvarForUpdateCached(SEXP sym, uint32_t cacheSlot); inline static BC ldvarForUpdate(SEXP sym); @@ -552,6 +554,7 @@ BC_NOARGS(V, _) case Opcode::push_: case Opcode::ldfun_: case Opcode::ldvar_: + case Opcode::ldvar_noforce_: case Opcode::ldvar_super_: case Opcode::ldddvar_: case Opcode::stvar_: @@ -587,12 +590,16 @@ BC_NOARGS(V, _) case Opcode::br_: case Opcode::brtrue_: case Opcode::brfalse_: + memcpy(&immediate.offset, pc, sizeof(immediate.offset)); + break; case Opcode::beginloop_: case Opcode::popn_: case Opcode::pick_: 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; diff --git a/rir/src/ir/BC_noarg_list.h b/rir/src/ir/BC_noarg_list.h index ff9f48a28..1da8c47a9 100644 --- a/rir/src/ir/BC_noarg_list.h +++ b/rir/src/ir/BC_noarg_list.h @@ -7,6 +7,7 @@ #define BC_NOARGS(V, NESTED) \ SIMPLE_INSTRUCTIONS(V_SIMPLE_INSTRUCTION_IN_BC_NOARGS, V) \ + V(NESTED, as_switch_idx, as_switch_idx) \ V(NESTED, nop, nop) \ V(NESTED, ret, ret) \ V(NESTED, pop, pop) \ diff --git a/rir/src/ir/CodeVerifier.cpp b/rir/src/ir/CodeVerifier.cpp index 4b0dcf481..142606f73 100644 --- a/rir/src/ir/CodeVerifier.cpp +++ b/rir/src/ir/CodeVerifier.cpp @@ -104,77 +104,13 @@ static Sources hasSources(Opcode bc) { case Opcode::subassign1_3_: return Sources::Required; - case Opcode::inc_: - case Opcode::identical_noforce_: - case Opcode::push_: - case Opcode::ldfun_: - case Opcode::ldddvar_: - case Opcode::ldvar_: - case Opcode::ldvar_cached_: - case Opcode::ldvar_for_update_cache_: - case Opcode::ldvar_for_update_: - case Opcode::ldvar_super_: - case Opcode::stvar_: - case Opcode::stvar_cached_: - case Opcode::stvar_super_: - case Opcode::guard_fun_: - case Opcode::call_: - case Opcode::call_dots_: - case Opcode::named_call_: - case Opcode::call_builtin_: - case Opcode::mk_promise_: - case Opcode::mk_eager_promise_: - case Opcode::push_code_: - case Opcode::br_: - case Opcode::brtrue_: - case Opcode::beginloop_: - case Opcode::brfalse_: - case Opcode::pick_: - case Opcode::pull_: - case Opcode::is_: - case Opcode::put_: - case Opcode::nop_: - case Opcode::ret_: - case Opcode::names_: - case Opcode::set_names_: - case Opcode::force_: - case Opcode::pop_: - case Opcode::popn_: - case Opcode::close_: - case Opcode::asast_: - case Opcode::dup_: - case Opcode::dup2_: - case Opcode::for_seq_size_: - case Opcode::length_: - case Opcode::swap_: - case Opcode::set_shared_: - case Opcode::ensure_named_: - case Opcode::return_: - case Opcode::check_closure_: - case Opcode::invisible_: - case Opcode::visible_: - case Opcode::endloop_: - case Opcode::lgl_and_: - case Opcode::lgl_or_: - case Opcode::record_call_: - case Opcode::record_type_: - case Opcode::record_test_: - case Opcode::clear_binding_cache_: - case Opcode::colon_cast_lhs_: - case Opcode::colon_cast_rhs_: - return Sources::NotNeeded; - case Opcode::aslogical_: case Opcode::asbool_: case Opcode::missing_: -#define V(NESTED, name, Name)\ - case Opcode::name ## _:\ return Sources::May; -SIMPLE_INSTRUCTIONS(V, _) -#undef V - case Opcode::invalid_: - case Opcode::num_of: {} + default: + return Sources::NotNeeded; } assert(false); return Sources::NotNeeded; @@ -290,7 +226,8 @@ void CodeVerifier::verifyFunctionLayout(SEXP sexp, InterpreterInstance* ctx) { Rf_error("RIR Verifier: Branch outside closure"); } if (*cptr == Opcode::ldvar_ || *cptr == Opcode::ldvar_super_ || - *cptr == Opcode::ldvar_for_update_) { + *cptr == Opcode::ldvar_for_update_ || + *cptr == Opcode::ldvar_noforce_) { unsigned* argsIndex = reinterpret_cast(cptr + 1); if (*argsIndex >= cp_pool_length(ctx)) Rf_error("RIR Verifier: Invalid arglist index"); diff --git a/rir/src/ir/Compiler.cpp b/rir/src/ir/Compiler.cpp index c76aadaaf..75feb7be0 100644 --- a/rir/src/ir/Compiler.cpp +++ b/rir/src/ir/Compiler.cpp @@ -76,6 +76,7 @@ class CompilerContext { class CodeContext { public: typedef size_t CacheSlotNumber; + static constexpr CacheSlotNumber BindingCacheDisabled = (size_t)-1; CodeStream cs; std::stack loops; @@ -102,17 +103,25 @@ class CompilerContext { } size_t isCached(SEXP name) { assert(loadsSlotInCache.size() <= MAX_CACHE_SIZE); - return loadsSlotInCache.size() < MAX_CACHE_SIZE || - loadsSlotInCache.count(name); + auto f = loadsSlotInCache.find(name); + return f != loadsSlotInCache.end() && + f->second != BindingCacheDisabled; } + size_t nCached = 0; size_t cacheSlotFor(SEXP name) { - return loadsSlotInCache.emplace(name, loadsSlotInCache.size()) - .first->second; + auto f = loadsSlotInCache.find(name); + if (f != loadsSlotInCache.end()) + return f->second; + if (nCached >= MAX_CACHE_SIZE) + return BindingCacheDisabled; + return loadsSlotInCache.emplace(name, nCached++).first->second; } virtual bool loopIsLocal() { return !loops.empty(); } + virtual bool isPromiseContext() { return false; } }; class PromiseContext : public CodeContext { + public: PromiseContext(SEXP ast, FunctionWriter& fun, CodeContext* p) : CodeContext(ast, fun, p) {} @@ -123,6 +132,8 @@ class CompilerContext { } return true; } + + bool isPromiseContext() override { return true; } }; std::stack code; @@ -163,13 +174,19 @@ class CompilerContext { new CodeContext(ast, fun, code.empty() ? nullptr : code.top())); } + bool isInPromise() { return pushedPromiseContexts > 0; } + void pushPromiseContext(SEXP ast) { + pushedPromiseContexts++; + code.push( new PromiseContext(ast, fun, code.empty() ? nullptr : code.top())); } Code* pop() { Code* res = cs().finalize(0, code.top()->loadsSlotInCache.size()); + if (code.top()->isPromiseContext()) + pushedPromiseContexts--; delete code.top(); code.pop(); return res; @@ -184,6 +201,9 @@ class CompilerContext { << BC::push(R_FalseValue) << BC::push(Rf_mkString(msg)) << BC::callBuiltin(4, ast, getBuiltinFun("warning")) << BC::pop(); } + + private: + unsigned int pushedPromiseContexts = 0; }; struct LoadArgsResult { @@ -437,6 +457,25 @@ bool compileSpecialCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args_, RList args(args_); CodeStream& cs = ctx.cs(); + // TODO: this is not sound... There are other ways to call remove... What we + // should do instead is trap do_remove in gnur and clear the cache! + if (fun == symbol::remove || fun == symbol::rm) { + CompilerContext::CodeContext::CacheSlotNumber min = MAX_CACHE_SIZE; + CompilerContext::CodeContext::CacheSlotNumber max = 0; + for (auto c : ctx.code.top()->loadsSlotInCache) { + auto i = c.second; + if (i == CompilerContext::CodeContext::BindingCacheDisabled) + continue; + if (i < min) + min = i; + if (i > max) + max = i; + } + if (min < max) + cs << BC::clearBindingCache(min, max - min); + return false; + } + if (fun == symbol::Function && args.length() == 3) { if (!voidContext) { SEXP fun = Compiler::compileFunction(args[1], args[0]); @@ -807,7 +846,7 @@ bool compileSpecialCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args_, // "slot<-" ignores value semantics and modifies shared objects // in-place, our implementation does not deal with this case. - if (fun2name == "slot") { + if (fun2name == "slot" || fun2name == "class") { return false; } @@ -1238,8 +1277,6 @@ bool compileSpecialCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args_, // Compile the seq expression (vector) and initialize the loop compileExpr(ctx, seq); - if (!isConstant(seq)) - cs << BC::setShared(); cs << BC::forSeqSize() << BC::push((int)0); auto compileIndexOps = [&](bool record) { @@ -1462,9 +1499,7 @@ bool compileSpecialCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args_, } cs << BC::dup() << BC::is(BC::RirTypecheck::isSTRSXP) << BC::recordTest() << BC::brtrue(strBr); - // TODO needs Rf_asInteger, builtin as.integer behaves differently - // `raw(1)` errors on asInteger, but not on `as.integer` - cs << BC::callBuiltin(1, ast, getBuiltinFun("as.integer")); + cs << BC::asSwitchIdx(); // currently stack is [arg[0]] (converted to integer) for (size_t i = 0; i < labels.size(); ++i) { @@ -1600,14 +1635,15 @@ bool compileSpecialCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args_, << BC::stvar(isym); // construct ast for FUN(X[[i]], ...) - SEXP tmp = LCONS(symbol::DoubleBracket, - LCONS(args[0], LCONS(isym, R_NilValue))); + SEXP tmp = + PROTECT(LCONS(symbol::DoubleBracket, + LCONS(args[0], LCONS(isym, R_NilValue)))); SEXP call = LCONS(args[1], LCONS(tmp, LCONS(R_DotsSymbol, R_NilValue))); PROTECT(call); compileCall(ctx, call, CAR(call), CDR(call), false); - UNPROTECT(1); + UNPROTECT(2); // store result cs << BC::pull(1) @@ -1671,25 +1707,40 @@ static void compileLoadOneArg(CompilerContext& ctx, SEXP arg, ArgType arg_type, // remember if the argument had a name associated res.names.push_back(TAG(arg)); if (TAG(arg) != R_NilValue) - { res.hasNames = true; - } - if (arg_type == ArgType::RAW_VALUE) { compileExpr(ctx, CAR(arg), false); return; } + // Constant arguments do not need to be promise wrapped + if (arg_type != ArgType::EAGER_PROMISE_FROM_TOS) + switch (TYPEOF(CAR(arg))) { + case LANGSXP: + case SYMSXP: + break; + default: + auto eager = CAR(arg); + res.assumptions.setEager(i); + if (!isObject(eager)) { + res.assumptions.setNotObj(i); + if (IS_SIMPLE_SCALAR(eager, REALSXP)) + res.assumptions.setSimpleReal(i); + if (IS_SIMPLE_SCALAR(eager, INTSXP)) + res.assumptions.setSimpleInt(i); + } + cs << BC::push(eager); + return; + } + Code* prom; if (arg_type == ArgType::EAGER_PROMISE) { // Compile the expression to evaluate it eagerly, and // wrap the return value in a promise without rir code compileExpr(ctx, CAR(arg), false); prom = compilePromiseNoRir(ctx, CAR(arg)); - } - - else if (arg_type == ArgType::EAGER_PROMISE_FROM_TOS) { + } else if (arg_type == ArgType::EAGER_PROMISE_FROM_TOS) { // The value we want to wrap in the argument's promise is // already on TOS, no nead to compile the expression. // Wrap it in a promise without rir code. @@ -1704,27 +1755,8 @@ static void compileLoadOneArg(CompilerContext& ctx, SEXP arg, ArgType arg_type, if (arg_type == ArgType::EAGER_PROMISE || arg_type == ArgType::EAGER_PROMISE_FROM_TOS) { res.assumptions.setEager(i); cs << BC::mkEagerPromise(idx); - } - else - { - // "safe force" the argument to get static assumptions - SEXP known = safeEval(CAR(arg)); - // TODO: If we add more assumptions should probably abstract with - // testArg in interp.cpp. For now they're both much different though - if (known != R_UnboundValue) { - res.assumptions.setEager(i); - if (!isObject(known)) { - res.assumptions.setNotObj(i); - if (IS_SIMPLE_SCALAR(known, REALSXP)) - res.assumptions.setSimpleReal(i); - if (IS_SIMPLE_SCALAR(known, INTSXP)) - res.assumptions.setSimpleInt(i); - } - cs << BC::push(known); - cs << BC::mkEagerPromise(idx); - } else { - cs << BC::mkPromise(idx); - } + } else { + cs << BC::mkPromise(idx); } } @@ -1751,19 +1783,55 @@ static LoadArgsResult compileLoadArgs(CompilerContext& ctx, SEXP ast, SEXP fun, return res; } + // function application void compileCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args, bool voidContext) { + CodeStream& cs = ctx.cs(); // application has the form: // LHS ( ARGS ) // LHS can either be an identifier or an expression + bool speculateOnBuiltin = false; + BC::Label eager = 0; + BC::Label theEnd = 0; + if (TYPEOF(fun) == SYMSXP) { if (compileSpecialCall(ctx, ast, fun, args, voidContext)) return; + if (!ctx.isInPromise()) { + + auto callHasDots = false; + for (RListIter arg = RList(args).begin(); arg != RList::end(); + ++arg) { + + if (*arg == R_DotsSymbol) { + callHasDots = true; + break; + } + } + + if (!callHasDots) { + auto builtin = Rf_findVar(fun, R_BaseEnv); + assert(builtin != R_NilValue); + auto likelyBuiltin = TYPEOF(builtin) == BUILTINSXP; + speculateOnBuiltin = likelyBuiltin; + + if (speculateOnBuiltin) { + eager = cs.mkLabel(); + theEnd = cs.mkLabel(); + cs << BC::push(builtin) << BC::dup() + << BC::ldvarNoForce(fun) << BC::identicalNoforce() + << BC::recordTest() << BC::brtrue(eager); + + cs << BC::pop(); + } + } + } + cs << BC::ldfun(fun); } else { compileExpr(ctx, fun); @@ -1773,6 +1841,17 @@ void compileCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args, if (Compiler::profile) cs << BC::recordCall(); + auto compileCall = [&](LoadArgsResult& info) { + if (info.hasDots) { + cs << BC::callDots(info.numArgs, info.names, ast, info.assumptions); + } else if (info.hasNames) { + cs << BC::call(info.numArgs, info.names, ast, info.assumptions); + } else { + info.assumptions.add(Assumption::CorrectOrderOfArguments); + cs << BC::call(info.numArgs, ast, info.assumptions); + } + }; + LoadArgsResult info; if (fun == symbol::forceAndCall) { // First arg certainly eager @@ -1780,15 +1859,19 @@ void compileCall(CompilerContext& ctx, SEXP ast, SEXP fun, SEXP args, } else { info = compileLoadArgs(ctx, ast, fun, args, voidContext); } + compileCall(info); - if (info.hasDots) { - cs << BC::callDots(info.numArgs, info.names, ast, info.assumptions); - } else if (info.hasNames) { - cs << BC::call(info.numArgs, info.names, ast, info.assumptions); - } else { - info.assumptions.add(Assumption::CorrectOrderOfArguments); - cs << BC::call(info.numArgs, ast, info.assumptions); + if (speculateOnBuiltin) { + cs << BC::br(theEnd) << eager; + + auto infoEager = compileLoadArgs(ctx, ast, fun, args, voidContext, 0, + RList(args).length()); + + compileCall(infoEager); + + cs << theEnd; } + if (voidContext) cs << BC::pop(); else if (Compiler::profile) @@ -1908,6 +1991,24 @@ SEXP Compiler::finalize() { } ctx.push(exp, closureEnv); + + // Prepopulate all binding cache numbers for all variables occuring in the + // function. + std::function scanNames = [&](SEXP e) { + if (TYPEOF(e) == LANGSXP) + for (auto n : RList(CDR(e))) { + if (CAR(e) == symbol::rm) { + ctx.code.top()->loadsSlotInCache[n] = + CompilerContext::CodeContext::BindingCacheDisabled; + } else if (TYPEOF(n) == SYMSXP) { + ctx.code.top()->cacheSlotFor(n); + } else { + scanNames(n); + } + } + }; + scanNames(exp); + compileExpr(ctx, exp); ctx.cs() << BC::ret(); Code* body = ctx.pop(); diff --git a/rir/src/ir/insns.h b/rir/src/ir/insns.h index 7d5b53a76..a12922b0d 100644 --- a/rir/src/ir/insns.h +++ b/rir/src/ir/insns.h @@ -32,6 +32,12 @@ DEF_INSTR(ldfun_, 1, 0, 1, 0) */ DEF_INSTR(ldvar_, 1, 0, 1, 0) +/** + * ldvar_noforce_:: like ldvar. + * If a promise is found, it is not forced before it's returned + */ +DEF_INSTR(ldvar_noforce_, 1, 0, 1, 1) + /** * ldvar_:: like ldvar. * Stores an additional immediate with a unique number for the cache bindings. @@ -193,6 +199,11 @@ DEF_INSTR(uplus_, 0, 1, 1, 0) */ DEF_INSTR(inc_, 0, 1, 1, 1) +/** + * as_switch_idx_ :: silently convert tos to integer, -1 if non-int + */ +DEF_INSTR(as_switch_idx_, 0, 1, 1, 1) + DEF_INSTR(sub_, 0, 2, 1, 0) DEF_INSTR(uminus_, 0, 1, 1, 0) DEF_INSTR(mul_, 0, 2, 1, 0) diff --git a/rir/src/runtime/Code.cpp b/rir/src/runtime/Code.cpp index 2b1e771d7..42902b7e3 100644 --- a/rir/src/runtime/Code.cpp +++ b/rir/src/runtime/Code.cpp @@ -2,9 +2,13 @@ #include "Function.h" #include "R/Printing.h" #include "R/Serialize.h" +#include "compiler/native/pir_jit_llvm.h" #include "ir/BC.h" #include "utils/Pool.h" +#include +#include + #include #include @@ -18,7 +22,7 @@ Code::Code(FunctionSEXP fun, SEXP src, unsigned srcIdx, unsigned cs, (intptr_t)&locals_ - (intptr_t)this, // GC area has only 1 pointer NumLocals), - nativeCode(nullptr), funInvocationCount(0), deoptCount(0), src(srcIdx), + nativeCode_(nullptr), funInvocationCount(0), deoptCount(0), src(srcIdx), trivialExpr(nullptr), stackLength(0), localsCount(localsCnt), bindingCacheSize(bindingsCnt), codeSize(cs), srcLength(sourceLength), extraPoolSize(0) { @@ -93,7 +97,7 @@ Code* Code::deserialize(SEXP refTable, R_inpstream_t inp) { SEXP store = Rf_allocVector(EXTERNALSXP, size); PROTECT(store); Code* code = new (DATAPTR(store)) Code; - code->nativeCode = nullptr; // not serialized for now + code->nativeCode_ = nullptr; // not serialized for now code->funInvocationCount = InInteger(inp); code->deoptCount = InInteger(inp); code->src = InInteger(inp); @@ -235,8 +239,8 @@ void Code::disassemble(std::ostream& out, const std::string& prefix) const { pc = BC::next(pc); } - if (nativeCode) { - out << "nativeCode " << (void*)nativeCode << "\n"; + if (nativeCode_) { + out << "nativeCode " << (void*)nativeCode_ << "\n"; } if (auto a = arglistOrder()) { @@ -298,4 +302,12 @@ unsigned Code::addExtraPoolEntry(SEXP v) { return extraPoolSize++; } +llvm::ExitOnError ExitOnErr; + +NativeCode Code::lazyCompile() { + auto symbol = ExitOnErr(pir::PirJitLLVM::JIT->lookup(lazyCodeHandle_)); + nativeCode_ = (NativeCode)symbol.getAddress(); + return nativeCode_; +} + } // namespace rir diff --git a/rir/src/runtime/Code.h b/rir/src/runtime/Code.h index 6d551f15b..a48983f4b 100644 --- a/rir/src/runtime/Code.h +++ b/rir/src/runtime/Code.h @@ -72,7 +72,35 @@ struct Code : public RirRuntimeObject { size_t locals, size_t bindingCache); static Code* New(Immediate ast); - NativeCode nativeCode; + constexpr static size_t MAX_CODE_HANDLE_LENGTH = 64; + + private: + char lazyCodeHandle_[MAX_CODE_HANDLE_LENGTH] = "\0"; + NativeCode nativeCode_; + NativeCode lazyCompile(); + + public: + void lazyCodeHandle(const std::string& h) { + assert(h != ""); + auto l = h.length() + 1; + if (l > MAX_CODE_HANDLE_LENGTH) { + assert(false); + l = MAX_CODE_HANDLE_LENGTH; + } + memcpy(&lazyCodeHandle_, h.c_str(), l); + lazyCodeHandle_[MAX_CODE_HANDLE_LENGTH - 1] = '\0'; + } + NativeCode nativeCode() { + if (nativeCode_) + return nativeCode_; + if (*lazyCodeHandle_ == '\0') + return nullptr; + return lazyCompile(); + } + + bool isCompiled() { + return *lazyCodeHandle_ != '\0' && nativeCode_ != nullptr; + } static unsigned pad4(unsigned sizeInBytes) { unsigned x = sizeInBytes % 4; diff --git a/rir/src/runtime/Context.cpp b/rir/src/runtime/Context.cpp index 47a74677c..e87268b3a 100644 --- a/rir/src/runtime/Context.cpp +++ b/rir/src/runtime/Context.cpp @@ -83,6 +83,107 @@ std::ostream& operator<<(std::ostream& out, const Context& a) { return out; } +std::string Context::getShortStringRepr() const { + std::stringstream contextString; + contextString << "<"; + static TypeAssumption types[5][6] = {{ + TypeAssumption::Arg0IsEager_, + TypeAssumption::Arg1IsEager_, + TypeAssumption::Arg2IsEager_, + TypeAssumption::Arg3IsEager_, + TypeAssumption::Arg4IsEager_, + TypeAssumption::Arg5IsEager_, + }, + { + TypeAssumption::Arg0IsNonRefl_, + TypeAssumption::Arg1IsNonRefl_, + TypeAssumption::Arg2IsNonRefl_, + TypeAssumption::Arg3IsNonRefl_, + TypeAssumption::Arg4IsNonRefl_, + TypeAssumption::Arg5IsNonRefl_, + }, + { + TypeAssumption::Arg0IsNotObj_, + TypeAssumption::Arg1IsNotObj_, + TypeAssumption::Arg2IsNotObj_, + TypeAssumption::Arg3IsNotObj_, + TypeAssumption::Arg4IsNotObj_, + TypeAssumption::Arg5IsNotObj_, + }, + { + TypeAssumption::Arg0IsSimpleInt_, + TypeAssumption::Arg1IsSimpleInt_, + TypeAssumption::Arg2IsSimpleInt_, + TypeAssumption::Arg3IsSimpleInt_, + TypeAssumption::Arg4IsSimpleInt_, + TypeAssumption::Arg5IsSimpleInt_, + }, + { + TypeAssumption::Arg0IsSimpleReal_, + TypeAssumption::Arg1IsSimpleReal_, + TypeAssumption::Arg2IsSimpleReal_, + TypeAssumption::Arg3IsSimpleReal_, + TypeAssumption::Arg4IsSimpleReal_, + TypeAssumption::Arg5IsSimpleReal_, + }}; + + // assumptions: + // Eager + // non reflective + // non object + // simple Integer + // simple Real + std::vector letters = {'E', 'r', 'o', 'I', 'R'}; + for (int i_arg = 0; i_arg < 6; i_arg++) { + std::vector arg_str; + for (int i_assum = 0; i_assum < 5; i_assum++) { + if (this->includes(types[i_assum][i_arg])) { + arg_str.emplace_back(letters.at(i_assum)); + } + } + if (!arg_str.empty()) { + contextString << i_arg << ":"; + for (auto c : arg_str) { + contextString << c; + } + contextString << " "; + } + } + + contextString << "|"; + + std::vector assum_strings; + if (this->includes(Assumption::CorrectOrderOfArguments)) { + assum_strings.emplace_back("O"); + } + + if (this->includes(Assumption::NoExplicitlyMissingArgs)) { + assum_strings.emplace_back("mi"); + } + + if (this->includes(Assumption::NotTooManyArguments)) { + assum_strings.emplace_back("ma"); + } + + if (this->includes(Assumption::StaticallyArgmatched)) { + assum_strings.emplace_back("Stat"); + } + + if (!assum_strings.empty()) { + contextString << " "; + } + + for (size_t i = 0; i < assum_strings.size(); i++) { + contextString << assum_strings[i]; + if (i < assum_strings.size() - 1) { + contextString << "-"; + } + } + + contextString << ">"; + return contextString.str(); +} + constexpr std::array Context::EagerContext; constexpr std::array @@ -145,21 +246,21 @@ void Context::setSpecializationLevel(int level) { } } -bool Context::isImproving(Function* f) const { +unsigned Context::isImproving(Function* f) const { return isImproving(f->context(), f->signature().hasDotsFormals, f->signature().hasDefaultArgs); } -bool Context::isImproving(pir::ClosureVersion* f) const { +unsigned Context::isImproving(pir::ClosureVersion* f) const { return isImproving(f->context(), f->owner()->formals().hasDots(), f->owner()->formals().hasDefaultArgs()); } -bool Context::isImproving(const Context& other, bool hasDotsFormals, - bool hasDefaultArgs) const { +unsigned Context::isImproving(const Context& other, bool hasDotsFormals, + bool hasDefaultArgs) const { assert(smaller(other)); if (other == *this) - return false; + return 0; auto normalized = *this; if (!hasDotsFormals) @@ -172,13 +273,13 @@ bool Context::isImproving(const Context& other, bool hasDotsFormals, if (hasDotsFormals || hasDefaultArgs) { if (normalized.numMissing() != other.numMissing()) - return true; + return 20; } else { normalized.numMissing(other.numMissing()); } - normalized = normalized | other; - return normalized != other; + auto diff = normalized.toI() & (~other.toI()); + return 2 * __builtin_popcount(diff); } } // namespace rir diff --git a/rir/src/runtime/Context.h b/rir/src/runtime/Context.h index 2f5ce5943..288d0c1be 100644 --- a/rir/src/runtime/Context.h +++ b/rir/src/runtime/Context.h @@ -96,13 +96,15 @@ struct Context { memcpy((void*)this, &val, sizeof(*this)); } - unsigned long toI() { + unsigned long toI() const { static_assert(sizeof(*this) == sizeof(unsigned long), ""); uint64_t m; memcpy(&m, this, sizeof(*this)); return m; } + std::string getShortStringRepr() const; + RIR_INLINE void add(Assumption a) { flags.set(a); } RIR_INLINE void remove(Assumption a) { flags.reset(a); } RIR_INLINE bool includes(Assumption a) const { return flags.includes(a); } @@ -250,10 +252,10 @@ struct Context { typeFlags.includes(other.typeFlags); } - bool isImproving(rir::Function*) const; - bool isImproving(rir::pir::ClosureVersion*) const; - bool isImproving(const Context& other, bool hasDotsFormals, - bool hasDefaultArgs) const; + unsigned isImproving(rir::Function*) const; + unsigned isImproving(rir::pir::ClosureVersion*) const; + unsigned isImproving(const Context& other, bool hasDotsFormals, + bool hasDefaultArgs) const; static Context deserialize(SEXP refTable, R_inpstream_t inp); void serialize(SEXP refTable, R_outpstream_t out) const; diff --git a/rir/src/runtime/DispatchTable.h b/rir/src/runtime/DispatchTable.h index c3771b28e..ee2f2b6c3 100644 --- a/rir/src/runtime/DispatchTable.h +++ b/rir/src/runtime/DispatchTable.h @@ -4,6 +4,7 @@ #include "Function.h" #include "R/Serialize.h" #include "RirRuntimeObject.h" +#include "utils/random.h" namespace rir { @@ -74,7 +75,7 @@ struct DispatchTable bool contains(const Context& assumptions) const { for (size_t i = 0; i < size(); ++i) if (get(i)->context() == assumptions) - return true; + return !get(i)->body()->isDeoptimized; return false; } @@ -131,7 +132,7 @@ struct DispatchTable Rf_error("dispatch table overflow"); #endif // Evict one element and retry - auto pos = 1 + (std::rand() % (size() - 1)); + auto pos = 1 + (Random::singleton()() % (size() - 1)); size_--; while (pos < size()) { setEntry(pos, getEntry(pos + 1)); @@ -185,14 +186,8 @@ struct DispatchTable void serialize(SEXP refTable, R_outpstream_t out) const { HashAdd(container(), refTable); - size_t n = 0; - for (size_t i = 0; i < size(); i++) - if (!get(i)->body()->nativeCode) - n++; - OutInteger(out, n); - for (size_t i = 0; i < size(); i++) - if (!get(i)->body()->nativeCode) - get(i)->serialize(refTable, out); + OutInteger(out, 1); + baseline()->serialize(refTable, out); } Context userDefinedContext() const { return userDefinedContext_; } diff --git a/rir/src/runtime/LazyArglist.h b/rir/src/runtime/LazyArglist.h index 3e5360743..98d3d9d56 100644 --- a/rir/src/runtime/LazyArglist.h +++ b/rir/src/runtime/LazyArglist.h @@ -126,7 +126,7 @@ struct LazyArglistOnStack { fakeSEXP.gengc_prev_node = R_NilValue; fakeSEXP.sxpinfo.gcgen = 1; fakeSEXP.sxpinfo.mark = 1; - fakeSEXP.sxpinfo.named = 2; + fakeSEXP.sxpinfo.named = NAMEDMAX; fakeSEXP.sxpinfo.type = EXTERNALSXP; PROTECT(arglistOrder); } diff --git a/rir/src/utils/CompilationStrategy.cpp b/rir/src/utils/CompilationStrategy.cpp new file mode 100644 index 000000000..d9785732f --- /dev/null +++ b/rir/src/utils/CompilationStrategy.cpp @@ -0,0 +1,35 @@ +#include "CompilationStrategy.h" + +#include "FunctionVersion.h" + +#include + +namespace rir { +namespace CompilationStrategy { + + static std::unordered_set compiled_versions; + + static bool compileEachVersionOnlyOnce(size_t entry_key, Context call_ctxt) { + return compiled_versions.count({entry_key, call_ctxt}); + } + + + void markAsCompiled(size_t id, Context call_ctxt) { + compiled_versions.insert({id, call_ctxt}); + } + + void markAsCompiled(SEXP callee, Context call_ctxt) { + return markAsCompiled(FunctionVersion::getFunctionId(callee), call_ctxt); + } + + bool compileFlag(size_t id, Context call_ctxt) { + if (getenv("SKIP_ALL_COMPILATION")) { + return false; + } + if (getenv("COMPILE_ONLY_ONCE")) { + return compileEachVersionOnlyOnce(id, call_ctxt); + } + return true; + } +} +} diff --git a/rir/src/utils/CompilationStrategy.h b/rir/src/utils/CompilationStrategy.h new file mode 100644 index 000000000..729e325d9 --- /dev/null +++ b/rir/src/utils/CompilationStrategy.h @@ -0,0 +1,12 @@ +#pragma once + +#include "runtime/Context.h" +namespace rir { +namespace CompilationStrategy { + + bool compileFlag(size_t id, Context call_ctxt); + void markAsCompiled(size_t id, Context call_ctxt); + void markAsCompiled(SEXP, Context call_ctxt); + +} +} diff --git a/rir/src/utils/ContextualProfiling.cpp b/rir/src/utils/ContextualProfiling.cpp index a154977d1..dcba22c96 100644 --- a/rir/src/utils/ContextualProfiling.cpp +++ b/rir/src/utils/ContextualProfiling.cpp @@ -10,7 +10,9 @@ #include #include #include +#include +#include "FunctionVersion.h" namespace rir { @@ -58,19 +60,13 @@ namespace rir { class ContextDispatchData { public: int call_count_in_ctxt = 0; - int successful_compilation_count = 0; - int failed_compilation_count = 0; - double time_spent_in_compilation = 0; - double time_wasted_in_compilation = 0; // Count the number of time the version for the context C // has been called in this context in // version_called_count[C] unordered_map version_called_count; - unordered_map version_success_run_count; - unordered_map version_runtime; }; - class Entry { + class CallEntry { public: int total_call_count = 0; // per context call and dispatch data @@ -79,56 +75,52 @@ namespace rir { // Map from a function (identified by its body) to the data about the // different contexts it has been called in - unordered_map entries; + unordered_map call_entries; - struct FileLogger { - ofstream myfile; + class CompilationData { + public: + bool success; + double cmp_time_ms; + }; + + using CompilationEntry = + unordered_map>; + + std::unordered_map compilation_entries; + + class FileLogger { + private: + ofstream file_call_stats; + ofstream file_compile_stats; + + public: FileLogger() { - // use ISO 8601 date as log name - time_t timenow = chrono::system_clock::to_time_t(chrono::system_clock::now()); - stringstream runId_ss; - runId_ss << put_time( localtime( &timenow ), "%FT%T%z" ); - string runId = runId_ss.str(); - - myfile.open("profile/" + runId + ".csv"); - myfile << "ID,NAME,CONTEXT,N_CALL,TOTAL_RUNTIME,CMP_SUCCESS,SUCCESS_TIME,CMP_FAIL,FAIL_TIME,GOODNESS,DISPATCHED FUNCTIONS\n"; - } - static size_t getEntryKey(SEXP callee) { - /* Identify a function by the SEXP of its BODY. For nested functions, The - enclosing CLOSXP changes every time (because the CLOENV also changes): - f <- function { - g <- function() { 3 } - g() - } - Here the BODY of g is always the same SEXP, but a new CLOSXP is used - every time f is called. - */ - return reinterpret_cast(BODY(callee)); + string out_dir = "profile"; + mkdir(out_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + + string call_stats = out_dir + "/call_stats.csv"; + string compile_stats = out_dir + "/compile_stats.csv"; + + file_call_stats.open(call_stats); + file_call_stats << "ID,NAME,CALL_CONTEXT,VERSION,CALL_COUNT\n"; + + file_compile_stats.open(compile_stats); + file_compile_stats << "ID,NAME,VERSION,ID_CMP,SUCCESS,CMP_TIME\n"; } void registerFunctionName(CallContext const& call) { - static const SEXP double_colons = Rf_install("::"); - static const SEXP triple_colons = Rf_install(":::"); - size_t const currentKey = getEntryKey(call.callee); - SEXP const lhs = CAR(call.ast); + size_t const currentKey = FunctionVersion::getFunctionId(call.callee); if (names.count(currentKey) == 0 || names[currentKey]->is_anon() ) { - if (TYPEOF(lhs) == SYMSXP) { - // case 1: function call of the form f(x,y,z) - names[currentKey] = make_unique(CHAR(PRINTNAME(lhs))); - } else if (TYPEOF(lhs) == LANGSXP && ((CAR(lhs) == double_colons) || (CAR(lhs) == triple_colons))) { - // case 2: function call of the form pkg::f(x,y,z) or pkg:::f(x,y,z) - SEXP const fun1 = CAR(lhs); - SEXP const pkg = CADR(lhs); - SEXP const fun2 = CADDR(lhs); - assert(TYPEOF(pkg) == SYMSXP && TYPEOF(fun2) == SYMSXP); - stringstream ss; - ss << CHAR(PRINTNAME(pkg)) << CHAR(PRINTNAME(fun1)) << CHAR(PRINTNAME(fun2)); - names[currentKey] = make_unique(ss.str()); - } - } + std::string name = ContextualProfiling:: + extractFunctionName(call.ast); + if (name.length() > 0) { + names[currentKey] = + make_unique(name); + } + } if (names.count(currentKey) == 0) { // case 3: function call of the form F()(x, y, z) // and this anonymous function has not been seen before @@ -160,9 +152,9 @@ namespace rir { void createEntry(CallContext const& call) { registerFunctionName(call); - auto fun_id = getEntryKey(call.callee); + auto fun_id = FunctionVersion::getFunctionId(call.callee); // create or get entry - auto & entry = entries[fun_id]; + auto & entry = call_entries[fun_id]; entry.total_call_count++; // /!\ do not rely on call.givenContext here, it will @@ -180,7 +172,7 @@ namespace rir { // find entry for this function // entry must have been previously created by a call to createEntry - auto & entry = entries.at(id); + auto & entry = call_entries.at(id); // create or get call context data auto & ctxt_data = entry.dispatch_data[call_context]; @@ -190,177 +182,22 @@ namespace rir { ctxt_data.version_called_count[version_context]++; } - void addFunctionDispatchRuntime( - size_t id, - Context call_context, - Function const & f, - std::chrono::duration duration - ) { - Context version_context = f.context(); - - // find entry for this function - // entry must have been previously created by a call to createEntry - auto & entry = entries.at(id); - - // create or get call context data - auto & ctxt_data = entry.dispatch_data[call_context]; - - // count one call in the context callContextId to version compiled for funContextId - - ctxt_data.version_success_run_count[version_context]++; - ctxt_data.version_runtime[version_context] += duration.count(); - } - // For the two functions below: function entry must have been previously // created by createEntry, context entry may not exist yet - void countSuccessfulCompilation( + void countCompilation( SEXP callee, Context call_ctxt, - std::chrono::duration duration + bool success, + double cmp_time_ms ) { - size_t entry_key = getEntryKey(callee); - - auto & entry = entries.at(entry_key); - auto & dispatch_data = entry.dispatch_data[call_ctxt]; - - dispatch_data.successful_compilation_count++; - - dispatch_data.time_spent_in_compilation += duration.count(); - - } + size_t entry_key = FunctionVersion::getFunctionId(callee); - bool compileOnlyOnce( - size_t entry_key, - Context call_context - ) { - auto & entry = entries.at(entry_key); - auto & dispatch_data = entry.dispatch_data[call_context]; + CompilationData d {success, cmp_time_ms}; - if(dispatch_data.successful_compilation_count > 0 || dispatch_data.failed_compilation_count > 0) { - return false; - } else { - return true; - } + auto & cmp_entry = compilation_entries[entry_key]; + cmp_entry[call_ctxt].push_back(d); } - void countFailedCompilation( - SEXP callee, - Context call_ctxt, - std::chrono::duration duration - ) { - size_t entry_key = getEntryKey(callee); - - auto & entry = entries.at(entry_key); - auto & dispatch_data = entry.dispatch_data[call_ctxt]; - - dispatch_data.failed_compilation_count++; - dispatch_data.time_wasted_in_compilation += duration.count(); - } - - - std::string getContextString(Context c) { - stringstream contextString; - contextString << "<"; - TypeAssumption types[5][6] = { - { - TypeAssumption::Arg0IsEager_, - TypeAssumption::Arg1IsEager_, - TypeAssumption::Arg2IsEager_, - TypeAssumption::Arg3IsEager_, - TypeAssumption::Arg4IsEager_, - TypeAssumption::Arg5IsEager_, - }, - { - TypeAssumption::Arg0IsNonRefl_, - TypeAssumption::Arg1IsNonRefl_, - TypeAssumption::Arg2IsNonRefl_, - TypeAssumption::Arg3IsNonRefl_, - TypeAssumption::Arg4IsNonRefl_, - TypeAssumption::Arg5IsNonRefl_, - }, - { - TypeAssumption::Arg0IsNotObj_, - TypeAssumption::Arg1IsNotObj_, - TypeAssumption::Arg2IsNotObj_, - TypeAssumption::Arg3IsNotObj_, - TypeAssumption::Arg4IsNotObj_, - TypeAssumption::Arg5IsNotObj_, - }, - { - TypeAssumption::Arg0IsSimpleInt_, - TypeAssumption::Arg1IsSimpleInt_, - TypeAssumption::Arg2IsSimpleInt_, - TypeAssumption::Arg3IsSimpleInt_, - TypeAssumption::Arg4IsSimpleInt_, - TypeAssumption::Arg5IsSimpleInt_, - }, - { - TypeAssumption::Arg0IsSimpleReal_, - TypeAssumption::Arg1IsSimpleReal_, - TypeAssumption::Arg2IsSimpleReal_, - TypeAssumption::Arg3IsSimpleReal_, - TypeAssumption::Arg4IsSimpleReal_, - TypeAssumption::Arg5IsSimpleReal_, - } - }; - - - // assumptions: - // Eager - // non reflective - // non object - // simple Integer - // simple Real - std::vector letters = {'E', 'r', 'o', 'I', 'R'}; - for(int i_arg = 0; i_arg < 6; i_arg++) { - std::vector arg_str; - for(int i_assum = 0; i_assum < 5; i_assum++) { - if(c.includes(types[i_assum][i_arg])) { - arg_str.emplace_back(letters.at(i_assum)); - } - } - if (! arg_str.empty()) { - contextString << i_arg << ":"; - for(auto c : arg_str) { - contextString << c; - } - contextString << " "; - } - } - - contextString << "|"; - - vector assum_strings; - if(c.includes(Assumption::CorrectOrderOfArguments)) { - assum_strings.emplace_back("O"); - } - - if(c.includes(Assumption::NoExplicitlyMissingArgs)) { - assum_strings.emplace_back("mi"); - } - - if(c.includes(Assumption::NotTooManyArguments)) { - assum_strings.emplace_back("ma"); - } - - if(c.includes(Assumption::StaticallyArgmatched)) { - assum_strings.emplace_back("Stat"); - } - - if (! assum_strings.empty()) { - contextString << " "; - } - - for(size_t i = 0 ; i < assum_strings.size(); i++) { - contextString << assum_strings[i]; - if (i < assum_strings.size() - 1) { - contextString << "-"; - } - } - - contextString << ">"; - return contextString.str(); - } void createCodePointEntry( int line, @@ -374,8 +211,7 @@ namespace rir { } ~FileLogger() { - double total_time_in_compilation = 0; - for (auto const & ir : entries) { + for (auto const & ir : call_entries) { auto fun_id = ir.first; auto & entry = ir.second; string name = names.at(fun_id)->get_name(); // function name @@ -385,99 +221,90 @@ namespace rir { // *itr -> Context auto call_ctxt = itr.first; // current context __id auto & dispatch_data = itr.second; // current context - - string currContextString = getContextString(call_ctxt); // current context __string - - stringstream contextsDispatched; - - double totalRuntimeUnderContext = 0; - - double goodness = false; - double baselineAvgRuntime = 0; - int otherRuntimeCount = 0; - double otherAvgRuntime = 0; + string callContextString = call_ctxt.getShortStringRepr(); // current context __string // iterate over dispatched functions under this context for (auto const & itr1 : dispatch_data.version_called_count) { // *itr1 -> Context Context version_context = itr1.first; // current function context int functionContextCallCount = itr1.second; // current function context __call count - string currContextString = getContextString(version_context); // current function context __string - - unordered_map version_success_run_count = dispatch_data.version_success_run_count; - unordered_map version_runtime = dispatch_data.version_runtime; - - double success_runs = version_success_run_count[version_context]; - double success_runtime = version_runtime[version_context]; - double avg_runtime = 0; - totalRuntimeUnderContext += success_runtime; - - if(success_runs > 0) { - avg_runtime = success_runtime / success_runs; - } - bool baseline = version_context.toI() == 0 ? true : false; - - if(baseline) { - baselineAvgRuntime = avg_runtime; - } else { - otherRuntimeCount++; - otherAvgRuntime += avg_runtime; - } - - contextsDispatched << "[" << functionContextCallCount << "]{" << avg_runtime << "}" << currContextString << " "; - } - otherAvgRuntime = otherAvgRuntime / otherRuntimeCount; - total_time_in_compilation += dispatch_data.time_spent_in_compilation; - if(otherAvgRuntime < baselineAvgRuntime) { - goodness = true; + string versionContextString = version_context.getShortStringRepr(); // current function context __string + file_call_stats + << fun_id // id + << del + << name // name + << del + << callContextString // call context + << del + << versionContextString + << del + << functionContextCallCount // number of time this version is called in this context + << "\n"; } // print row - myfile - << fun_id // id - << del - << name // name - << del - << currContextString // call context - << del - << dispatch_data.call_count_in_ctxt // call context count - << del - << totalRuntimeUnderContext // total runtime under context - << del - << dispatch_data.successful_compilation_count // number of successful compilations in this context - << del - << dispatch_data.time_spent_in_compilation // time spent for successful compilation - << del - << dispatch_data.failed_compilation_count // number of failed compilations in this context - << del - << dispatch_data.time_wasted_in_compilation // time wasted trying to compile - << del - << goodness - << del - << contextsDispatched.str() // functions dispatched under this context - << "\n"; + } } - myfile - << del - << del - << del - << del - << del - << total_time_in_compilation // Total time in compilation - << del - << del - << del - << "\n"; - myfile.close(); + file_call_stats.close(); + + for (auto const & it_e : compilation_entries) { + auto fun_id = it_e.first; + auto & cmp_entry = it_e.second; + string fun_name = names.at(fun_id)->get_name(); // function name + + for (auto & it_d : cmp_entry) { + auto version = it_d.first; + auto & data = it_d.second; + + int cmp_id = 0; + for (auto & d : data) { + file_compile_stats + << fun_id + << del + << fun_name + << del + << version.getShortStringRepr() + << del + << cmp_id + << del + << d.success + << del + << d.cmp_time_ms + << "\n"; + cmp_id++; + } + } + } + file_compile_stats.close(); } - - public: }; } // namespace auto fileLogger = getenv("CONTEXT_LOGS") ? std::make_unique() : nullptr; +std::string ContextualProfiling::extractFunctionName(SEXP call) { + static const SEXP double_colons = Rf_install("::"); + static const SEXP triple_colons = Rf_install(":::"); + SEXP const lhs = CAR(call); + if (TYPEOF(lhs) == SYMSXP) { + // case 1: function call of the form f(x,y,z) + return CHAR(PRINTNAME(lhs)); + } else if (TYPEOF(lhs) == LANGSXP && + ((CAR(lhs) == double_colons) || (CAR(lhs) == triple_colons))) { + // case 2: function call of the form pkg::f(x,y,z) or pkg:::f(x,y,z) + SEXP const fun1 = CAR(lhs); + SEXP const pkg = CADR(lhs); + SEXP const fun2 = CADDR(lhs); + assert(TYPEOF(pkg) == SYMSXP && TYPEOF(fun2) == SYMSXP); + stringstream ss; + ss << CHAR(PRINTNAME(pkg)) << CHAR(PRINTNAME(fun1)) + << CHAR(PRINTNAME(fun2)); + return ss.str(); + } + return ""; +} + void ContextualProfiling::createCallEntry( CallContext const& call) { if(fileLogger) { @@ -495,14 +322,6 @@ void ContextualProfiling::recordCodePoint( } } -size_t ContextualProfiling::getEntryKey(CallContext const& cc) { - if(fileLogger) { - return fileLogger->getEntryKey(cc.callee); - } else { - return 0; - } -} - void ContextualProfiling::addFunctionDispatchInfo( size_t id, Context contextCaller, @@ -514,53 +333,16 @@ void ContextualProfiling::addFunctionDispatchInfo( } -void ContextualProfiling::countSuccessfulCompilation( - SEXP callee, - Context assumptions, - std::chrono::duration duration -) { - if (fileLogger) { - fileLogger->countSuccessfulCompilation(callee, assumptions, duration); - } -} - -void ContextualProfiling::countFailedCompilation( +void ContextualProfiling::countCompilation( SEXP callee, Context assumptions, - std::chrono::duration duration + bool success, + double cmp_time_ms ) { if (fileLogger) { - fileLogger->countFailedCompilation(callee, assumptions, duration); + fileLogger->countCompilation(callee, assumptions, success, cmp_time_ms); } } -bool ContextualProfiling::compileFlag( - size_t id, - Context contextCaller -) { - if (getenv("SKIP_ALL_COMPILATION")) { - return false; - } - if (!getenv("COMPILE_ONLY_ONCE")) { - return true; - } - if (fileLogger) { - return fileLogger->compileOnlyOnce(id, contextCaller); - } else { - return true; - } -} - -void ContextualProfiling::addFunctionDispatchRuntime( - size_t id, - Context contextCaller, - Function const &f, - std::chrono::duration duration - -) { - if(fileLogger) { - return fileLogger->addFunctionDispatchRuntime(id, contextCaller, f, duration); - } -} } // namespace rir diff --git a/rir/src/utils/ContextualProfiling.h b/rir/src/utils/ContextualProfiling.h index fe41f16ca..4ffa72bca 100644 --- a/rir/src/utils/ContextualProfiling.h +++ b/rir/src/utils/ContextualProfiling.h @@ -1,21 +1,11 @@ #ifndef CONTEXTUAL_PROFILE_H #define CONTEXTUAL_PROFILE_H #include "../interpreter/call_context.h" -#include -#include namespace rir { -// CONTEXT_LOGS - Enable Logging -// COMPILE_ONLY_ONCE - Only compile once -// SKIP_ALL_COMPILATION - Skip All Compilation class ContextualProfiling { public: - static bool compileFlag( - size_t, - Context - ); - static void createCallEntry( CallContext const& // logs [ name, callType ] ); @@ -28,30 +18,18 @@ class ContextualProfiling { // CallContext&, // std::string // ); - static size_t getEntryKey( - CallContext const& - ); - static void addFunctionDispatchRuntime( - size_t, - Context, - Function const&, - std::chrono::duration - ); static void addFunctionDispatchInfo( size_t, Context, Function const& ); - static void countSuccessfulCompilation( - SEXP, - Context, - std::chrono::duration - ); - static void countFailedCompilation( - SEXP, - Context, - std::chrono::duration + static void countCompilation( + SEXP callee, + Context assumptions, + bool success, + double cmp_time_ms ); + static std::string extractFunctionName(SEXP call); }; } // namespace rir diff --git a/rir/src/utils/FunctionVersion.h b/rir/src/utils/FunctionVersion.h new file mode 100644 index 000000000..407779285 --- /dev/null +++ b/rir/src/utils/FunctionVersion.h @@ -0,0 +1,48 @@ +#pragma once + + +#include "Rinternals.h" + +#include "runtime/Context.h" + +namespace rir { + +class FunctionVersion { + public: + size_t const function_id; + Context const context; + + inline bool operator==(FunctionVersion const & other) const { + return other.context == context && other.function_id == function_id; + } + + + static size_t getFunctionId(SEXP callee) { + /* Identify a function by the SEXP of its BODY. For nested functions, The + enclosing CLOSXP changes every time (because the CLOENV also changes): + f <- function { + g <- function() { 3 } + g() + } + Here the BODY of g is always the same SEXP, but a new CLOSXP is used + every time f is called. + */ + return reinterpret_cast(BODY(callee)); + } + +}; + +} // namespace rir + + +namespace std { + +template<> +struct hash +{ + inline std::size_t operator()(rir::FunctionVersion const & f) const { + return hash_combine(hash_combine(0, f.context), f.function_id); + } +}; + +} // namespace std diff --git a/rir/src/utils/UUID.cpp b/rir/src/utils/UUID.cpp index 37826f3ee..82be4f68b 100644 --- a/rir/src/utils/UUID.cpp +++ b/rir/src/utils/UUID.cpp @@ -6,50 +6,27 @@ namespace rir { +static size_t nextUuid = 0; + // Generates a random UUID -UUID UUID::random() { - // Dumb algorithm - UUID uuid; - for (int i = 0; i < UUID_SIZE; i++) { - uuid.data[i] = (char)(rand() % 256); - } - return uuid; -} +UUID UUID::random() { return UUID(++nextUuid); } UUID UUID::deserialize(SEXP refTable, R_inpstream_t inp) { UUID uuid; - InBytes(inp, &uuid.data, UUID_SIZE); + InBytes(inp, &uuid.uuid, sizeof(uuid.uuid)); return uuid; } void UUID::serialize(SEXP refTable, R_outpstream_t out) const { - OutBytes(out, &data, UUID_SIZE); + OutBytes(out, &uuid, sizeof(uuid)); } std::string UUID::str() { std::ostringstream str; - for (int i = 0; i < 8; i++) { - if (i != 0) - str << " "; - str << (int)data[i]; - } + str << uuid; return str.str(); } -bool UUID::operator==(const UUID& other) const { - for (int i = 0; i < UUID_SIZE; i++) { - if (data[i] != other.data[i]) - return false; - } - return true; -} - -UUID UUID::operator^(const UUID& other) const { - UUID uuid; - for (int i = 0; i < UUID_SIZE; i++) { - uuid.data[i] = data[i] ^ other.data[i]; - } - return uuid; -} +bool UUID::operator==(const UUID& other) const { return uuid == other.uuid; } }; // namespace rir diff --git a/rir/src/utils/UUID.h b/rir/src/utils/UUID.h index d5536af22..9788faaea 100644 --- a/rir/src/utils/UUID.h +++ b/rir/src/utils/UUID.h @@ -2,14 +2,13 @@ #include -#define UUID_SIZE 64 - namespace rir { class UUID { - char data[UUID_SIZE] = {}; + size_t uuid; UUID() {} + explicit UUID(size_t v) : uuid(v) {} public: // Generates a random UUID @@ -19,7 +18,6 @@ class UUID { std::string str(); bool operator==(const UUID& other) const; - UUID operator^(const UUID& other) const; friend struct std::hash; }; @@ -28,6 +26,6 @@ class UUID { namespace std { template <> struct hash { - std::size_t operator()(const rir::UUID& v) const { return *(size_t*)&v; } + std::size_t operator()(const rir::UUID& v) const { return v.uuid; } }; } // namespace std diff --git a/rir/src/utils/random.h b/rir/src/utils/random.h new file mode 100644 index 000000000..d8532455b --- /dev/null +++ b/rir/src/utils/random.h @@ -0,0 +1,30 @@ +#pragma once + +namespace rir { + +// low-quality but fast PRNG +class Random { + + unsigned long x = 123456789, y = 362436069, z = 521288629; + + public: + unsigned long operator()() { // period 2^96-1 + unsigned long t; + x ^= x << 16; + x ^= x >> 5; + x ^= x << 1; + + t = x; + x = y; + y = z; + z = t ^ x ^ y; + + return z; + } + + static Random& singleton() { + static Random r; + return r; + } +}; +} // namespace rir diff --git a/rir/tests/S3_regression.R b/rir/tests/S3_regression.R index 23431552b..d8bdad134 100644 --- a/rir/tests/S3_regression.R +++ b/rir/tests/S3_regression.R @@ -13,3 +13,6 @@ x <- new("a") myfun(x) myfun(x) myfun(x) +myfun(x) +myfun(x) +myfun(x) diff --git a/rir/tests/pir_check.R b/rir/tests/pir_check.R index 708587984..b76c89b3e 100644 --- a/rir/tests/pir_check.R +++ b/rir/tests/pir_check.R @@ -66,10 +66,10 @@ stopifnot( pir.check(function() { balls = c(1,2,3,4) for (i in 1:3){ - balls[[i]] = 0 + balls[[i]] = 0 } balls[[2]] - }, OneLdVar, warmup=function(f) f())) + }, OneLdVar, warmup=function(f) f())) stopifnot(pir.check(function(x, y) print("Test"), IsPirCompilable)) stopifnot(pir.check(function(x = 4) { @@ -80,6 +80,14 @@ stopifnot(!pir.check(function(...) { print("PIR does support dotdotdot") }, IsPirCompilable)) stopifnot(pir.check(function() 42L, Returns42L)) + + +if (Sys.getenv("PIR_OPT_LEVEL") != "" && as.integer(Sys.getenv("PIR_OPT_LEVEL")) < 1) { + warning("skipping rest of test since opt level < 2") + q() +} + + stopifnot(pir.check(function() { f <- function() 42L f() @@ -116,7 +124,7 @@ stopifnot(pir.check(function(depth) { else 0 }, NoEnvSpec, warmup=function(f){cat(".\n"); f(0)})) -seed <- 1 +seed <- 1 stopifnot(pir.check(function(a) { seed <<- a }, NoEnvSpec)) @@ -191,8 +199,8 @@ stopifnot(pir.check(function() { q <- 1 else { if (a) - q <- 3 - else + q <- 3 + else q <- 2 } q @@ -202,12 +210,19 @@ stopifnot(pir.check(function(a) { q <- 1 else { if (a) - q <- 3 - else + q <- 3 + else q <- 2 } q }, NoLoad)) + +if (Sys.getenv("PIR_OPT_LEVEL") != "" && as.integer(Sys.getenv("PIR_OPT_LEVEL")) < 2) { + warning("skipping rest of test since opt level < 2") + q() +} + + stopifnot(pir.check(function() { f <- function() 42L (function(x) x())(f) @@ -279,7 +294,7 @@ mandelbrot <- function(size) { # TODO: FIXXXXX stopifnot( - pir.check(mandelbrot, NoExternalCalls, NoPromise, NoStore, warmup=function(f) {f(13);f(27)}) + pir.check(mandelbrot, NoExternalCalls, NoPromise, warmup=function(f) {f(13);f(27)}) ) # New tests @@ -420,7 +435,7 @@ stopifnot(!pir.check(function(a, b) { x <- i x }, NoColon, warmup=function(f) {f(a, b); f(a, b)})) - + # More dead instruction removal stopifnot(!pir.check(function(x) { x == 4 @@ -436,7 +451,7 @@ stopifnot(pir.check(function(x, y) { x + y }, NoEq, warmup=function(f)f(5L, 2L))) -## Inline promises even when they escape only because of deopt +## Inline promises even when they escape only because of deopt nbodyPrologue <- function(args) { n = if (length(args)) 20 else 1000L n @@ -469,7 +484,7 @@ stopifnot( emptyFor <- function(n) { for (i in 1:n) { - + } } stopifnot(pir.check(emptyFor, OneAdd, AnAddIsNotNAOrNaN, warmup=function(f) {f(1000)})) @@ -498,3 +513,7 @@ h <- function() { g(g(g(f()) + g(g(f()))) + g(40L)) } stopifnot(pir.check(h, NoExternalCalls, Returns42L, warmup=function(h) {h();h()})) + +# checks range analysis +f <- function(a,b) if (b > 0) a[b] +stopifnot(pir.check(f, UnboxedExtract, warmup=function(f) f(1,1))) diff --git a/rir/tests/runif-regression.R b/rir/tests/runif-regression.R new file mode 100644 index 000000000..b7d1e8b48 --- /dev/null +++ b/rir/tests/runif-regression.R @@ -0,0 +1,23 @@ +# For some reason leak sanitizer crashes on this test... +if (Sys.getenv("ASAN_SYMBOLIZER_PATH", unset="") != "") + quit() + +s = 42 + +for(type in c("Wichmann-Hill", "Marsaglia-Multicarry", "Super-Duper", + "Mersenne-Twister", + "Knuth-TAOCP", "Knuth-TAOCP-2002")) +{ + set.seed(123, type) + print(RNGkind()) + runif(100); print(runif(4)) + s = s * runif(1) + set.seed(1000, type) + runif(100); print(runif(4)) + s = s / runif(1) + set.seed(77, type) + runif(100); print(runif(4)) + s = s / runif(1) +} +print(s) +stopifnot(abs(s - 60127) < 0.1) diff --git a/rir/tests/test_mark_function.r b/rir/tests/test_mark_function.r index 5a926a1b6..b52c78145 100644 --- a/rir/tests/test_mark_function.r +++ b/rir/tests/test_mark_function.r @@ -45,4 +45,4 @@ stopifnot(sum(rir.functionInvocations(add_noinline1)) == 10) stopifnot(sum(rir.functionInvocations(add_nospecial)) > 10) stopifnot(sum(rir.functionInvocations(add_forceinline)) <= 3) stopifnot(length(rir.functionInvocations(add_nospecial)) == 2) -stopifnot(length(rir.functionInvocations(add_noinline2)) > 4) +stopifnot(length(rir.functionInvocations(add_noinline2)) >= 4) diff --git a/tools/build-gnur.sh b/tools/build-gnur.sh index e71e08041..8594a23b7 100755 --- a/tools/build-gnur.sh +++ b/tools/build-gnur.sh @@ -1,22 +1,105 @@ #!/bin/bash +set -e + CURRENT_DIR=`pwd` SCRIPTPATH=`cd $(dirname "$0") && pwd` if [ ! -d $SCRIPTPATH ]; then echo "Could not determine absolute dir of $0" echo "Maybe accessed with symlink" - exit 1 fi +SRC_DIR=`cd ${SCRIPTPATH}/.. && pwd` +. "${SCRIPTPATH}/script_include.sh" + -WHICH="$SCRIPTPATH/../external/$1" -if [ ! -d $WHICH ]; then - echo "no such R $WHICH" - exit 1 +if [[ "$OSTYPE" == "darwin"* ]]; then + USING_OSX=1 fi -cd $WHICH -make -j8 +if [[ "$1" == "--macos_gcc9" ]]; then + MACOS_GCC9=1 +fi + +if test -d ${SRC_DIR}/.git; then + echo "-> update submodules" + git submodule update --init + + echo "-> install git hooks" + ${SRC_DIR}/tools/install_hooks.sh +fi + +function build_r { + NAME=$1 + R_DIR="${SRC_DIR}/external/${NAME}" + + cd $R_DIR + + if [[ $(git diff --shortstat 2> /dev/null | tail -n1) != "" ]]; then + echo "** warning: $NAME repo is dirty" + sleep 1 + fi + + # unpack cache of recommended packages + cd src/library/Recommended/ + tar xf ../../../../custom-r/cache_recommended.tar + cd ../../.. + # tools/rsync-recommended || true + + # There is a test that times out due to the compiler triggering in the + # wrong moment in the matrix package. There doesn't seem to be a good solution + # other than just patching it. + cd src/library/Recommended + tar xzf Matrix_1.2-18.tar.gz + sed -i -e 's/^stopifnot((st <- system.time(show(M)))\[1\] < 1.0)/((st <- system.time(show(M)))[1] < 1.0)/' Matrix/man/printSpMatrix.Rd + rm Matrix_1.2-18.tar.gz + tar czf Matrix_1.2-18.tar.gz Matrix + rm -rf Matrix + cd ../../../ + + if [[ "$GNUR_BRANCH" != "" ]]; then + git checkout $GNUR_BRANCH + fi + + if [ ! -f $R_DIR/Makefile ]; then + echo "-> configure $NAME" + cd $R_DIR + if [ $USING_OSX -eq 1 ]; then + ./configure --enable-R-shlib --with-internal-tzcode --with-ICU=no || cat config.log + else + ./configure + fi + fi + + if [ ! -f $R_DIR/doc/FAQ ]; then + cd $R_DIR + touch doc/FAQ + fi + + if [ ! -f $R_DIR/SVN-REVISION ]; then + # R must either be built from a svn checkout, or from the tarball generated by make dist + # this is a workaround to build it from a git mirror + # see https://github.com/wch/r-source/wiki/Home/6d35777dcb772f86371bf221c194ca0aa7874016#building-r-from-source + echo -n 'Revision: ' > SVN-REVISION + # get the latest revision that is not a rir patch + REV=$(git log --grep "git-svn-id" -1 --format=%B | grep "^git-svn-id" | sed -E 's/^git-svn-id: https:\/\/svn.r-project.org\/R\/[^@]*@([0-9]+).*$/\1/') + # can fail on shallow checkouts, so let's put the last known there + if [ "$REV" == "" ]; then + REV='74948' + fi + echo $REV >> SVN-REVISION + echo -n 'Last Changed Date: ' >> SVN-REVISION + REV_DATE=$(git log --grep "git-svn-id" -1 --pretty=format:"%ad" --date=iso | cut -d' ' -f1) + # can fail on shallow checkouts, so let's put the last known there + if [ "$REV_DATE" == "" ]; then + REV_DATE='2018-07-02' + fi + echo $REV_DATE >> SVN-REVISION + + rm -f non-tarball + fi + + echo "-> building $NAME" + make -j8 +} -# sometimes package install fails due to mysteryous locking issues... -rm -rf library/*LOCK-* -make -j8 +build_r custom-r diff --git a/tools/copy-logs.sh b/tools/copy-logs.sh index 59d854082..673d445e3 100644 --- a/tools/copy-logs.sh +++ b/tools/copy-logs.sh @@ -1,7 +1,7 @@ #!/bin/sh TARGET=$2 -FILES=$(find $1 -iname '*.rout' -o -iname '*.log') +FILES=$(find $1 -iname '*.rout.fail' -o -iname '*.log') mkdir -p $TARGET for f in $FILES; do diff --git a/tools/fetch-llvm.sh b/tools/fetch-llvm.sh new file mode 100755 index 000000000..69931bd92 --- /dev/null +++ b/tools/fetch-llvm.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +set -e + +CURRENT_DIR=`pwd` +SCRIPTPATH=`cd $(dirname "$0") && pwd` +if [ ! -d $SCRIPTPATH ]; then + echo "Could not determine absolute dir of $0" + echo "Maybe accessed with symlink" +fi +SRC_DIR=`cd ${SCRIPTPATH}/.. && pwd` +. "${SCRIPTPATH}/script_include.sh" + + +if [[ "$OSTYPE" == "darwin"* ]]; then + USING_OSX=1 +fi + +LLVM_DIR="${SRC_DIR}/external/llvm-12" +if [ ! -d $LLVM_DIR ]; then + echo "-> unpacking LLVM" + cd "${SRC_DIR}/external" + if [ $USING_OSX -eq 1 ]; then + F="clang+llvm-12.0.0-x86_64-apple-darwin" + if [ ! -f "$F.tar.xz" ]; then + curl -L https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.0/$F.tar.xz > $F.tar.xz + fi + tar xf $F.tar.xz + ln -s $F llvm-12 + else + V=`grep DISTRIB_RELEASE /etc/lsb-release | cut -d= -f2` + if [ "$V" == "18.04" ]; then + V="16.04" + fi + if [ "$V" == "20.10" ]; then + V="20.04" + fi + if [ "$BUILD_LLVM_FROM_SRC" == "1" ]; then + V="" + fi + if [ "$V" == "20.10" ] || [ "$V" == "20.04" ] || [ "$V" == "16.04" ]; then + MINOR="0" + F="clang+llvm-12.0.$MINOR-x86_64-linux-gnu-ubuntu-$V" + if [ ! -f "$F.tar.xz" ]; then + curl -L https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.$MINOR/$F.tar.xz > $F.tar.xz + fi + tar xf $F.tar.xz + ln -s $F llvm-12 + else + F="llvm-12.0.0.src" + if [ ! -f "$F.tar.xz" ]; then + curl -L https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.0/$F.tar.xz > $F.tar.xz + fi + tar xf $F.tar.xz + mkdir llvm-12-build && cd llvm-12-build + cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DLLVM_ENABLE_ASSERTIONS=1 -DLLVM_OPTIMIZED_TABLEGEN=1 -DLLVM_USE_PERF=1 -DLLVM_TARGETS_TO_BUILD="X86" ../$F + ninja + cd .. + ln -s llvm-12-build llvm-12 + fi + fi +fi diff --git a/tools/sync.sh b/tools/sync.sh deleted file mode 100755 index f84d86eb4..000000000 --- a/tools/sync.sh +++ /dev/null @@ -1,151 +0,0 @@ -#!/bin/bash - -set -e - -CURRENT_DIR=`pwd` -SCRIPTPATH=`cd $(dirname "$0") && pwd` -if [ ! -d $SCRIPTPATH ]; then - echo "Could not determine absolute dir of $0" - echo "Maybe accessed with symlink" -fi -SRC_DIR=`cd ${SCRIPTPATH}/.. && pwd` -. "${SCRIPTPATH}/script_include.sh" - - -if [[ "$OSTYPE" == "darwin"* ]]; then - USING_OSX=1 -fi - -if [[ "$1" == "--macos_gcc9" ]]; then - MACOS_GCC9=1 -fi - -echo "-> update submodules" -git submodule update --init - -# check the .git of the rjit directory -test -d ${SRC_DIR}/.git -IS_GIT_CHECKOUT=$? - -if [ $IS_GIT_CHECKOUT -eq 0 ]; then - echo "-> install git hooks" - ${SRC_DIR}/tools/install_hooks.sh -fi - -function build_r { - NAME=$1 - R_DIR="${SRC_DIR}/external/${NAME}" - - cd $R_DIR - - if [[ $(git diff --shortstat 2> /dev/null | tail -n1) != "" ]]; then - echo "** warning: $NAME repo is dirty" - sleep 1 - fi - - # unpack cache of recommended packages - cd src/library/Recommended/ - tar xf ../../../../custom-r/cache_recommended.tar - cd ../../.. - # tools/rsync-recommended || true - - # There is a test that times out due to the compiler triggering in the - # wrong moment in the matrix package. There doesn't seem to be a good solution - # other than just patching it. - cd src/library/Recommended - tar xzf Matrix_1.2-18.tar.gz - sed -i -e 's/^stopifnot((st <- system.time(show(M)))\[1\] < 1.0)/((st <- system.time(show(M)))[1] < 1.0)/' Matrix/man/printSpMatrix.Rd - rm Matrix_1.2-18.tar.gz - tar czf Matrix_1.2-18.tar.gz Matrix - rm -rf Matrix - cd ../../../ - - if [ ! -f $R_DIR/Makefile ]; then - echo "-> configure $NAME" - cd $R_DIR - if [ $USING_OSX -eq 1 ]; then - ./configure --enable-R-shlib --with-internal-tzcode --with-ICU=no || cat config.log - else - ./configure --with-ICU=no - fi - fi - - if [ ! -f $R_DIR/doc/FAQ ]; then - cd $R_DIR - touch doc/FAQ - fi - - if [ ! -f $R_DIR/SVN-REVISION ]; then - # R must either be built from a svn checkout, or from the tarball generated by make dist - # this is a workaround to build it from a git mirror - # see https://github.com/wch/r-source/wiki/Home/6d35777dcb772f86371bf221c194ca0aa7874016#building-r-from-source - echo -n 'Revision: ' > SVN-REVISION - # get the latest revision that is not a rir patch - REV=$(git log --grep "git-svn-id" -1 --format=%B | grep "^git-svn-id" | sed -E 's/^git-svn-id: https:\/\/svn.r-project.org\/R\/[^@]*@([0-9]+).*$/\1/') - # can fail on shallow checkouts, so let's put the last known there - if [ "$REV" == "" ]; then - REV='74948' - fi - echo $REV >> SVN-REVISION - echo -n 'Last Changed Date: ' >> SVN-REVISION - REV_DATE=$(git log --grep "git-svn-id" -1 --pretty=format:"%ad" --date=iso | cut -d' ' -f1) - # can fail on shallow checkouts, so let's put the last known there - if [ "$REV_DATE" == "" ]; then - REV_DATE='2018-07-02' - fi - echo $REV_DATE >> SVN-REVISION - - rm -f non-tarball - fi - - echo "-> building $NAME" - make -j8 -} - -build_r custom-r - -LLVM_DIR="${SRC_DIR}/external/llvm-11" -if [ ! -d $LLVM_DIR ]; then - echo "-> unpacking LLVM" - cd "${SRC_DIR}/external" - if [ $USING_OSX -eq 1 ]; then - F="clang+llvm-11.0.0-x86_64-apple-darwin" - if [ ! -f "$F" ]; then - curl -L https://github.com/llvm/llvm-project/releases/download/llvmorg-11.0.0/$F.tar.xz > $F.tar.xz - fi - tar xf $F.tar.xz - ln -s $F llvm-11 - else - V=`lsb_release -r -s` - if [ "$V" == "18.04" ]; then - V="16.04" - fi - if [ "$BUILD_LLVM_FROM_SRC" == "1" ]; then - V="" - fi - if [ "$V" == "20.10" ] || [ "$V" == "20.04" ] || [ "$V" == "16.04" ]; then - MINOR="1" - # For some reason there is no 11.0.1 download for 20.04 - if [ "$V" == "20.04" ]; then - MINOR="0" - fi - F="clang+llvm-11.0.$MINOR-x86_64-linux-gnu-ubuntu-$V" - if [ ! -f "$F" ]; then - curl -L https://github.com/llvm/llvm-project/releases/download/llvmorg-11.0.$MINOR/$F.tar.xz > $F.tar.xz - fi - tar xf $F.tar.xz - ln -s $F llvm-11 - else - F="llvm-11.0.1.src" - if [ ! -f "$F" ]; then - curl -L https://github.com/llvm/llvm-project/releases/download/llvmorg-11.0.1/$F.tar.xz > $F.tar.xz - fi - tar xf $F.tar.xz - mkdir llvm-11-build && cd llvm-11-build - cmake -GNinja -DCMAKE_BUILD_TYPE=RelWithDebInfo -DLLVM_ENABLE_ASSERTIONS=1 -DLLVM_OPTIMIZED_TABLEGEN=1 -DLLVM_USE_PERF=1 -DLLVM_TARGETS_TO_BUILD="X86" ../$F - ninja - cd .. - ln -s llvm-11-build llvm-11 - fi - fi -fi diff --git a/tools/tests b/tools/tests index 12749daff..d59ca7fdf 100755 --- a/tools/tests +++ b/tools/tests @@ -68,8 +68,12 @@ function run_test { grep -v 'require("rir")' ${test} | grep -v 'require(rir)' >> $TEST LOG=$(mktemp /tmp/r-test.XXXXXX) + if [[ "$R_LD_PRELOAD" != "" ]]; then + export LD_PRELOAD=$R_LD_PRELOAD + fi $R $VALGRIND --no-init-file -f $TEST &> $LOG res=$? + export LD_PRELOAD="" ps -p $PARENT &> /dev/null if [ $? -ne 0 ]; then