Compare commits

..

1 Commits

Author SHA1 Message Date
Vitaly Provodin
2d8c5d7ebe update exclude list on results of 21.0.3_b458.1 test runs 2024-05-27 06:39:55 +04:00
219 changed files with 8068 additions and 7786 deletions

1
.gitignore vendored
View File

@@ -23,4 +23,3 @@ NashornProfile.txt
/.cproject
/compile_commands.json
/.cache
/jbr-api/

View File

@@ -1 +0,0 @@
1.0.0

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$/src/jetbrains.api">
<sourceFolder url="file://$MODULE_DIR$/src/jetbrains.api/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/jetbrains.api/templates" isTestSource="false" />
</content>
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="inheritedJdk" />
</component>
</module>

View File

@@ -4,6 +4,7 @@
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/jdk.iml" filepath="$PROJECT_DIR$/.idea/jdk.iml" />
###MODULE_IMLS###
<module fileurl="file://$PROJECT_DIR$/.idea/jetbrains.api.iml" filepath="$PROJECT_DIR$/.idea/jetbrains.api.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/test.iml" filepath="$PROJECT_DIR$/.idea/test.iml" />
</modules>
</component>

View File

@@ -0,0 +1,18 @@
#!/bin//bash
set -euo pipefail
# $1 - Boot JDK
# $2 - JBR part of API version
cd "`dirname "$0"`/../../../../.."
PWD="`pwd`"
CONF="$PWD/build/jbr-api.conf"
./configure --with-debug-level=release --with-boot-jdk=$1 || exit $?
make jbr-api CONF=release MAKEOVERRIDES= "JBR_API_CONF_FILE=$CONF" JBR_API_JBR_VERSION=$2 || exit $?
. $CONF || exit $?
echo "##teamcity[buildNumber '$VERSION']"
cp "$JAR" ./jbr-api-${VERSION}.jar || exit $?
cp "$SOURCES_JAR" ./jbr-api-${VERSION}-sources.jar || exit $?
echo "##teamcity[publishArtifacts '$PWD/jbr-api-${VERSION}.jar']"
echo "##teamcity[publishArtifacts '$PWD/jbr-api-${VERSION}-sources.jar']"

View File

@@ -161,7 +161,8 @@ if [ $do_maketest -eq 1 ]; then
JBRSDK_TEST=${JBRSDK_BUNDLE}-${JBSDK_VERSION}-linux-${libc_type_suffix}test-aarch64-b${build_number}
echo Creating "$JBRSDK_TEST" ...
[ $do_reset_changes -eq 1 ] && git checkout HEAD jb/project/tools/common/modules.list src/java.desktop/share/classes/module-info.java
make test-image CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $?
make test-image jbr-api CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $?
cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test"
tar -pcf "$JBRSDK_TEST".tar -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $?
[ -f "$JBRSDK_TEST.tar.gz" ] && rm "$JBRSDK_TEST.tar.gz"
gzip "$JBRSDK_TEST".tar || do_exit $?

View File

@@ -169,7 +169,8 @@ if [ $do_maketest -eq 1 ]; then
JBRSDK_TEST=${JBRSDK_BUNDLE}-${JBSDK_VERSION}-linux-${libc_type_suffix}test-x64-b${build_number}
echo Creating "$JBRSDK_TEST" ...
[ $do_reset_changes -eq 1 ] && git checkout HEAD jb/project/tools/common/modules.list src/java.desktop/share/classes/module-info.java
make test-image CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $?
make test-image jbr-api CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $?
cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test"
tar -pcf "$JBRSDK_TEST".tar -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $?
[ -f "$JBRSDK_TEST.tar.gz" ] && rm "$JBRSDK_TEST.tar.gz"
gzip "$JBRSDK_TEST".tar || do_exit $?

View File

@@ -137,7 +137,8 @@ if [ $do_maketest -eq 1 ]; then
JBRSDK_TEST=${JBRSDK_BUNDLE}-${JBSDK_VERSION}-linux-${libc_type_suffix}test-x86-b${build_number}
echo Creating "$JBRSDK_TEST" ...
[ $do_reset_changes -eq 1 ] && git checkout HEAD jb/project/tools/common/modules.list src/java.desktop/share/classes/module-info.java
make test-image CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $?
make test-image jbr-api CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $?
cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test"
tar -pcf "$JBRSDK_TEST".tar -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $?
[ -f "$JBRSDK_TEST.tar.gz" ] && rm "$JBRSDK_TEST.tar.gz"
gzip "$JBRSDK_TEST".tar || do_exit $?

View File

@@ -160,7 +160,8 @@ if [ $do_maketest -eq 1 ]; then
JBRSDK_TEST=${JBRSDK_BUNDLE}-${JBSDK_VERSION}-osx-test-${architecture}-b${build_number}
echo Creating "$JBRSDK_TEST" ...
[ $do_reset_changes -eq 1 ] && git checkout HEAD jb/project/tools/common/modules.list src/java.desktop/share/classes/module-info.java
make test-image CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $?
make test-image jbr-api CONF=$RELEASE_NAME JBR_API_JBR_VERSION=TEST || do_exit $?
cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test"
[ -f "$JBRSDK_TEST.tar.gz" ] && rm "$JBRSDK_TEST.tar.gz"
COPYFILE_DISABLE=1 tar -pczf "$JBRSDK_TEST".tar.gz -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $?
fi

View File

@@ -104,13 +104,13 @@ esac
if [ -z "${INC_BUILD:-}" ]; then
do_configure || do_exit $?
if [ $do_maketest -eq 1 ]; then
make LOG=info CONF=$RELEASE_NAME clean images test-image JBR_API_JBR_VERSION=TEST || do_exit $?
make LOG=info CONF=$RELEASE_NAME clean images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $?
else
make LOG=info CONF=$RELEASE_NAME clean images || do_exit $?
fi
else
if [ $do_maketest -eq 1 ]; then
make LOG=info CONF=$RELEASE_NAME images test-image JBR_API_JBR_VERSION=TEST || do_exit $?
make LOG=info CONF=$RELEASE_NAME images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $?
else
make LOG=info CONF=$RELEASE_NAME images || do_exit $?
fi

View File

@@ -97,13 +97,13 @@ esac
if [ -z "${INC_BUILD:-}" ]; then
do_configure || do_exit $?
if [ $do_maketest -eq 1 ]; then
make LOG=info CONF=$RELEASE_NAME clean images test-image JBR_API_JBR_VERSION=TEST || do_exit $?
make LOG=info CONF=$RELEASE_NAME clean images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $?
else
make LOG=info CONF=$RELEASE_NAME clean images || do_exit $?
fi
else
if [ $do_maketest -eq 1 ]; then
make LOG=info CONF=$RELEASE_NAME images test-image JBR_API_JBR_VERSION=TEST || do_exit $?
make LOG=info CONF=$RELEASE_NAME images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $?
else
make LOG=info CONF=$RELEASE_NAME images || do_exit $?
fi

View File

@@ -92,13 +92,13 @@ esac
if [ -z "${INC_BUILD:-}" ]; then
do_configure || do_exit $?
if [ $do_maketest -eq 1 ]; then
make LOG=info CONF=$RELEASE_NAME clean images test-image JBR_API_JBR_VERSION=TEST || do_exit $?
make LOG=info CONF=$RELEASE_NAME clean images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $?
else
make LOG=info CONF=$RELEASE_NAME clean images || do_exit $?
fi
else
if [ $do_maketest -eq 1 ]; then
make LOG=info CONF=$RELEASE_NAME images test-image JBR_API_JBR_VERSION=TEST || do_exit $?
make LOG=info CONF=$RELEASE_NAME images test-image jbr-api JBR_API_JBR_VERSION=TEST || do_exit $?
else
make LOG=info CONF=$RELEASE_NAME images || do_exit $?
fi

View File

@@ -51,6 +51,7 @@ pack_jbr jbrsdk${jbr_name_postfix} jbrsdk
if [ $do_maketest -eq 1 ]; then
JBRSDK_TEST=$JBRSDK_BUNDLE-$JBSDK_VERSION-windows-test-aarch64-b$build_number
cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test" || do_exit $?
echo Creating $JBRSDK_TEST.tar.gz ...
/usr/bin/tar -czf $JBRSDK_TEST.tar.gz -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $?
fi

View File

@@ -51,6 +51,7 @@ pack_jbr jbrsdk${jbr_name_postfix} jbrsdk
if [ $do_maketest -eq 1 ]; then
JBRSDK_TEST=$JBRSDK_BUNDLE-$JBSDK_VERSION-windows-test-x64-b$build_number
cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test" || do_exit $?
echo Creating $JBRSDK_TEST.tar.gz ...
/usr/bin/tar -czf $JBRSDK_TEST.tar.gz -C $IMAGES_DIR --exclude='test/jdk/demos' test || do_exit $?
fi

View File

@@ -47,6 +47,7 @@ pack_jbr jbrsdk${jbr_name_postfix} jbrsdk
if [ $do_maketest -eq 1 ]; then
JBRSDK_TEST=$JBRSDK_BUNDLE-$JBSDK_VERSION-windows-test-x86-b$build_number
cp "build/${RELEASE_NAME}/jbr-api/jbr-api.jar" "${IMAGES_DIR}/test" || do_exit $?
echo Creating $JBRSDK_TEST.tar.gz ...
/usr/bin/tar -czf $JBRSDK_TEST.tar.gz -C $BASE_DIR --exclude='test/jdk/demos' test || do_exit $?
fi

View File

@@ -100,7 +100,6 @@ $(eval $(call SetupJavaCompilation, $(MODULE), \
BIN := $(if $($(MODULE)_BIN), $($(MODULE)_BIN), $(JDK_OUTPUTDIR)/modules), \
HEADERS := $(SUPPORT_OUTPUTDIR)/headers, \
CREATE_API_DIGEST := true, \
PROCESS_JBR_API := true, \
CLEAN := $(CLEAN), \
CLEAN_FILES := $(CLEAN_FILES), \
COPY := $(COPY), \

View File

@@ -84,7 +84,7 @@ $(eval $(call SetupJavaCompilation, COMPILE_DEPEND, \
TARGET_RELEASE := $(TARGET_RELEASE_BOOTJDK), \
SRC := $(TOPDIR)/make/jdk/src/classes, \
INCLUDES := build/tools/depend, \
BIN := $(BUILDTOOLS_OUTPUTDIR)/plugins, \
BIN := $(BUILDTOOLS_OUTPUTDIR)/depend, \
DISABLED_WARNINGS := options, \
JAVAC_FLAGS := \
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED \
@@ -97,21 +97,13 @@ $(eval $(call SetupJavaCompilation, COMPILE_DEPEND, \
--add-exports jdk.internal.opt/jdk.internal.opt=jdk.javadoc.interim, \
))
$(eval $(call SetupJavaCompilation, COMPILE_JBR_API_PLUGIN, \
TARGET_RELEASE := $(TARGET_RELEASE_BOOTJDK), \
SRC := $(TOPDIR)/make/jdk/src/classes, \
INCLUDES := build/tools/jbrapi, \
BIN := $(BUILDTOOLS_OUTPUTDIR)/plugins, \
))
DEPEND_SERVICE_PROVIDER := $(BUILDTOOLS_OUTPUTDIR)/depend/META-INF/services/com.sun.source.util.Plugin
PLUGINS_SERVICE_PROVIDER := $(BUILDTOOLS_OUTPUTDIR)/plugins/META-INF/services/com.sun.source.util.Plugin
$(PLUGINS_SERVICE_PROVIDER):
$(call MakeDir, $(BUILDTOOLS_OUTPUTDIR)/plugins/META-INF/services)
$(DEPEND_SERVICE_PROVIDER):
$(call MakeDir, $(BUILDTOOLS_OUTPUTDIR)/depend/META-INF/services)
$(ECHO) build.tools.depend.Depend > $@
$(ECHO) build.tools.jbrapi.JBRApiPlugin >> $@
TARGETS += $(COMPILE_DEPEND) $(COMPILE_JBR_API_PLUGIN) $(PLUGINS_SERVICE_PROVIDER)
TARGETS += $(COMPILE_DEPEND) $(DEPEND_SERVICE_PROVIDER)
################################################################################

View File

@@ -25,49 +25,69 @@
include $(SPEC)
include MakeBase.gmk
include Utils.gmk
include JavaCompilation.gmk
JBR_API_ORIGIN := https://github.com/JetBrains/JetBrainsRuntimeApi.git
JBR_API_DIR := $(TOPDIR)/jbr-api
JBR_API_ROOT_DIR := $(TOPDIR)/src/jetbrains.api
JBR_API_TOOLS_DIR := $(JBR_API_ROOT_DIR)/tools
JBR_API_SRC_DIR := $(JBR_API_ROOT_DIR)/src
JBR_API_OUTPUT_DIR := $(OUTPUTDIR)/jbr-api
JBR_API_GENSRC_DIR := $(JBR_API_OUTPUT_DIR)/gensrc
JBR_API_BIN_DIR := $(JBR_API_OUTPUT_DIR)/bin
JBR_API_VERSION_PROPERTIES := $(JBR_API_ROOT_DIR)/version.properties
JBR_API_VERSION_GENSRC := $(JBR_API_OUTPUT_DIR)/jbr-api.version
JBR_API_GENSRC_BATCH := $(JBR_API_VERSION_GENSRC)
ARTIFACT_NAME := jbr-api-SNAPSHOT
ifeq ($(call isBuildOsEnv, windows.cygwin windows.msys2), true)
HOME := $$USERPROFILE
M2_REPO := $(shell $(PATHTOOL) $(HOME))/.m2/repository
else ifeq ($(call isBuildOsEnv, windows.wsl1 windows.wsl2), true)
HOME := `cmd.exe /C "echo %USERPROFILE%" 2> /dev/null`
M2_REPO := $(shell $(PATHTOOL) $(HOME))/.m2/repository
JBR_API_SRC_FILES := $(call FindFiles, $(JBR_API_SRC_DIR))
JBR_API_GENSRC_FILES := $(foreach f, $(call FindFiles, $(JBR_API_SRC_DIR)), \
$(JBR_API_GENSRC_DIR)/$(call RelativePath, $f, $(JBR_API_SRC_DIR)))
ifeq ($(JBR_API_JBR_VERSION),)
JBR_API_JBR_VERSION := DEVELOPMENT
JBR_API_FAIL_ON_HASH_MISMATCH := false
else
M2_REPO := $(HOME)/.m2/repository
.PHONY: $(JBR_API_VERSION_PROPERTIES)
JBR_API_FAIL_ON_HASH_MISMATCH := true
endif
M2_ARTIFACT := $(M2_REPO)/com/jetbrains/jbr-api/SNAPSHOT
M2_POM_CONTENT := \
<?xml version="1.0" encoding="UTF-8"?> \
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" \
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> \
<modelVersion>4.0.0</modelVersion> \
<groupId>com.jetbrains</groupId> \
<artifactId>jbr-api</artifactId> \
<version>SNAPSHOT</version> \
</project> \
jbr-api:
if [ -d "$(JBR_API_DIR)" ]; then \
$(GIT) -C "$(JBR_API_DIR)" fetch; \
$(GIT) -C "$(JBR_API_DIR)" merge-base --is-ancestor origin/main HEAD || \
$(ECHO) "!!! Current JBR API revision is outdated, update the branch in $(JBR_API_DIR) !!!"; \
else \
$(ECHO) "JBR API directory does not exist. Initializing..."; \
$(GIT) clone "$(JBR_API_ORIGIN)" "$(JBR_API_DIR)" --config core.autocrlf=false; \
fi
$(BASH) "$(JBR_API_DIR)/tools/build.sh" dev "$(BOOT_JDK)"
if [ -d "$(M2_REPO)" ]; then \
$(MKDIR) -p $(M2_ARTIFACT); \
$(ECHO) "$(M2_POM_CONTENT)" > $(M2_ARTIFACT)/$(ARTIFACT_NAME).pom; \
$(CP) "$(JBR_API_DIR)/out/$(ARTIFACT_NAME).jar" "$(M2_ARTIFACT)"; \
$(ECHO) "Installed into local Maven repository as com.jetbrains:jbr-api:SNAPSHOT"; \
else \
$(ECHO) "No Maven repository found at $(M2_REPO) - skipping local installation"; \
fi
ARCHIVE_BUILD_JBR_API_BIN := $(JBR_API_BIN_DIR)
$(eval $(call SetupJavaCompilation, BUILD_JBR_API, \
SMALL_JAVA := true, \
COMPILER := bootjdk, \
SRC := $(JBR_API_GENSRC_DIR), \
EXTRA_FILES := $(JBR_API_GENSRC_FILES), \
BIN := $(JBR_API_BIN_DIR), \
JAR := $(JBR_API_OUTPUT_DIR)/jbr-api.jar, \
))
.PHONY: jbr-api
$(eval $(call SetupJarArchive, BUILD_JBR_API_SOURCES_JAR, \
DEPENDENCIES := $(JBR_API_GENSRC_FILES), \
SRCS := $(JBR_API_GENSRC_DIR), \
JAR := $(JBR_API_OUTPUT_DIR)/jbr-api-sources.jar, \
SUFFIXES := .java, \
BIN := $(JBR_API_BIN_DIR), \
))
# Grouped targets may not be supported, so hack dependencies: sources -> version file -> generated sources
$(JBR_API_VERSION_GENSRC): $(JBR_API_SRC_FILES) $(JBR_API_VERSION_PROPERTIES) $(JBR_API_TOOLS_DIR)/Gensrc.java
$(ECHO) Generating sources for JBR API
$(JAVA_CMD) $(JAVA_FLAGS_SMALL) "$(JBR_API_TOOLS_DIR)/Gensrc.java" \
"$(TOPDIR)/src" "$(JBR_API_OUTPUT_DIR)" "$(JBR_API_JBR_VERSION)"
$(JBR_API_GENSRC_FILES): $(JBR_API_VERSION_GENSRC)
$(TOUCH) $@
jbr-api-check-version: $(JBR_API_GENSRC_FILES) $(JBR_API_VERSION_PROPERTIES)
$(JAVA_CMD) $(JAVA_FLAGS_SMALL) "$(JBR_API_TOOLS_DIR)/CheckVersion.java" \
"$(JBR_API_ROOT_DIR)" "$(JBR_API_GENSRC_DIR)" "$(JBR_API_FAIL_ON_HASH_MISMATCH)"
jbr-api: $(BUILD_JBR_API) $(BUILD_JBR_API_SOURCES_JAR) jbr-api-check-version
.PHONY: jbr-api jbr-api-check-version
ifneq ($(JBR_API_CONF_FILE),)
$(JBR_API_CONF_FILE): $(JBR_API_GENSRC_FILES)
$(ECHO) "VERSION=`$(CAT) $(JBR_API_VERSION_GENSRC)`" > $(JBR_API_CONF_FILE)
$(ECHO) "JAR=$(JBR_API_OUTPUT_DIR)/jbr-api.jar" >> $(JBR_API_CONF_FILE)
$(ECHO) "SOURCES_JAR=$(JBR_API_OUTPUT_DIR)/jbr-api-sources.jar" >> $(JBR_API_CONF_FILE)
jbr-api: $(JBR_API_CONF_FILE)
.PHONY: $(JBR_API_CONF_FILE)
endif

View File

@@ -106,6 +106,9 @@ AC_DEFUN_ONCE([LIB_SETUP_WAYLAND],
AC_ARG_WITH(vulkan-include, [AS_HELP_STRING([--with-vulkan-include],
[specify directory for the vulkan include files ({with-vulkan-include}/vulkan/vulkan.h)])])
AC_ARG_WITH(vulkan-hpp, [AS_HELP_STRING([--with-vulkan-hpp],
[specify directory for the vulkan-hpp include files ({with-vulkan-hpp}/vulkan/vulkan_raii.hpp)])])
AC_ARG_WITH(vulkan-shader-compiler, [AS_HELP_STRING([--with-vulkan-shader-compiler],
[specify which shader compiler to use: glslc/glslangValidator])])
@@ -136,11 +139,27 @@ AC_DEFUN_ONCE([LIB_SETUP_WAYLAND],
AC_MSG_RESULT([no])
AC_MSG_ERROR([Can't find 'vulkan/vulkan.h' under '${with_vulkan_include}'])
fi
AC_MSG_CHECKING([for vulkan_raii.hpp])
if test "x${with_vulkan_hpp}" != x; then
VULKAN_FLAGS="-I${with_vulkan_hpp} ${VULKAN_FLAGS}"
VULKAN_HPP_DIR=${with_vulkan_hpp}
else
VULKAN_HPP_DIR=${with_vulkan_include}
fi
if test -s "$VULKAN_HPP_DIR/vulkan/vulkan_raii.hpp"; then
VULKAN_FOUND=yes
AC_MSG_RESULT([yes])
else
VULKAN_FOUND=no
AC_MSG_RESULT([no])
AC_MSG_ERROR([Can't find 'vulkan/vulkan_raii.hpp' under '$VULKAN_HPP_DIR'])
fi
fi
AC_LANG_PUSH([C++])
if test "x$VULKAN_FOUND" = xno; then
# Check vulkan sdk location
AC_CHECK_HEADERS([$VULKAN_SDK/include/vulkan/vulkan.h],
AC_CHECK_HEADERS([$VULKAN_SDK/include/vulkan/vulkan.h $VULKAN_SDK/include/vulkan/vulkan_raii.hpp],
[ VULKAN_FOUND=yes
VULKAN_FLAGS="-DVK_USE_PLATFORM_WAYLAND_KHR -I${VULKAN_SDK}/include -DVULKAN_ENABLED"
],
@@ -150,13 +169,14 @@ AC_DEFUN_ONCE([LIB_SETUP_WAYLAND],
if test "x$VULKAN_FOUND" = xno; then
# Check default /usr/include location
AC_CHECK_HEADERS([vulkan/vulkan.h],
AC_CHECK_HEADERS([vulkan/vulkan.h vulkan/vulkan_raii.hpp],
[ VULKAN_FOUND=yes
VULKAN_FLAGS="-DVK_USE_PLATFORM_WAYLAND_KHR -DVULKAN_ENABLED"
],
[ VULKAN_FOUND=no; break ]
)
fi
AC_LANG_POP([C++])
if test "x$VULKAN_FOUND" = xno; then
HELP_MSG_MISSING_DEPENDENCY([vulkan])

View File

@@ -168,7 +168,6 @@ endef
# CREATE_API_DIGEST:=Set to true to use a javac plugin to generate a public API
# hash which can be used for down stream dependencies to only rebuild
# when the API changes.
# PROCESS_JBR_API:=Set to true to use an annotation processor to generate JBR API bindings.
# KEEP_ALL_TRANSLATIONS:=Set to true to skip translation filtering
SetupJavaCompilation = $(NamedParamsMacroTemplate)
define SetupJavaCompilationBody
@@ -443,22 +442,13 @@ define SetupJavaCompilationBody
ifeq ($$($1_CREATE_API_DIGEST), true)
$1_API_DIGEST_FLAGS := \
-classpath $$(BUILDTOOLS_OUTPUTDIR)/depend \
-Xplugin:"depend $$($1_API_TARGET)" \
"-XDinternalAPIPath=$$($1_API_INTERNAL)" \
"-XDLOG_LEVEL=$(LOG_LEVEL)" \
#
$1_EXTRA_DEPS := $$($1_EXTRA_DEPS) $$(BUILDTOOLS_OUTPUTDIR)/plugins/_the.COMPILE_DEPEND_batch
endif
ifeq ($$($1_PROCESS_JBR_API), true)
# Automatic path conversion doesn't work for two arguments, so call fixpath manually
$1_JBR_API_FLAGS := -Xplugin:"jbr-api $$(call FixPath, $$($1_BIN)/java.base/META-INF/jbrapi.registry) $$(call FixPath, $(TOPDIR)/jb/jbr-api.version)"
$1_EXTRA_DEPS := $$($1_EXTRA_DEPS) $$(BUILDTOOLS_OUTPUTDIR)/plugins/_the.COMPILE_JBR_API_PLUGIN_batch
endif
ifeq ($$(call Or, $$($1_CREATE_API_DIGEST) $$($1_PROCESS_JBR_API)), true)
$1_PROCESSORPATH := -processorpath $$(BUILDTOOLS_OUTPUTDIR)/plugins
$1_EXTRA_DEPS := $$(BUILDTOOLS_OUTPUTDIR)/depend/_the.COMPILE_DEPEND_batch
endif
# Create a file with all sources, to pass to javac in an @file.
@@ -497,7 +487,7 @@ define SetupJavaCompilationBody
$$(call MakeDir, $$(@D))
$$(call ExecuteWithLog, $$($1_BIN)$$($1_MODULE_SUBDIR)/_the.$$($1_SAFE_NAME)_batch, \
$$($1_JAVAC_CMD) $$($1_FLAGS) \
$$($1_PROCESSORPATH) $$($1_API_DIGEST_FLAGS) $$($1_JBR_API_FLAGS) \
$$($1_API_DIGEST_FLAGS) \
-XDmodifiedInputs=$$($1_MODFILELIST_FIXED) \
-d $$($1_BIN) $$($1_HEADERS_ARG) @$$($1_FILELIST)) && \
$(TOUCH) $$@

View File

@@ -1,451 +0,0 @@
package build.tools.jbrapi;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.*;
import javax.lang.model.element.*;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementScanner14;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.OverlappingFileLockException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.locks.LockSupport;
import java.util.stream.Collectors;
public class JBRApiPlugin implements Plugin {
enum Binding {
SERVICE,
PROVIDES,
PROVIDED,
TWO_WAY
}
record DiagnosticTree(CompilationUnitTree root, Tree tree) {}
record TypeBinding(DiagnosticTree diagnostic, TypeElement element, String currentType, String bindType, Binding binding) {}
record MethodBinding(DiagnosticTree diagnostic, ExecutableElement element, Registry.StaticDescriptor currentMethod, Registry.StaticMethod bindMethod) {}
final Map<String, TypeBinding> typeBindings = new HashMap<>();
final List<MethodBinding> methodBindings = new ArrayList<>();
Elements elements;
Trees trees;
Types types;
class Registry {
record Type(String type, Binding binding) {}
record StaticMethod(String type, String name) {}
record StaticDescriptor(StaticMethod method, String descriptor) {}
final Map<String, Type> types = new HashMap<>();
final Map<StaticDescriptor, StaticMethod> methods = new HashMap<>();
final Set<Object> internal = new HashSet<>();
void validateInternal(DiagnosticTree diagnostic, String currentType, Binding binding, TypeElement bindType) {
if (bindType.getKind() != ElementKind.CLASS && bindType.getKind() != ElementKind.INTERFACE) {
trees.printMessage(Diagnostic.Kind.ERROR, "Invalid JBR API binding:" + currentType + " -> " +
bindType.getQualifiedName().toString() + " (not a class or interface)",
diagnostic.tree, diagnostic.root);
} else if (bindType.getModifiers().contains(Modifier.FINAL) || bindType.getModifiers().contains(Modifier.SEALED)) {
trees.printMessage(Diagnostic.Kind.ERROR, "Invalid JBR API binding:" + currentType + " -> " +
bindType.getQualifiedName().toString() + " (not inheritable)",
diagnostic.tree, diagnostic.root);
}
if (binding != Binding.SERVICE) {
trees.printMessage(Diagnostic.Kind.ERROR, "Invalid JBR API binding:" + currentType + " -> " +
bindType.getQualifiedName().toString() + " (internal, non-service)",
diagnostic.tree, diagnostic.root);
}
}
void validateInternalMethod(DiagnosticTree diagnostic, StaticDescriptor currentMethod, TypeElement bindType, String bindMethod) {
boolean methodFound = false;
for (Element m : bindType.getEnclosedElements()) {
if (m.getKind() == ElementKind.METHOD &&
!m.getModifiers().contains(Modifier.STATIC) &&
!m.getModifiers().contains(Modifier.FINAL) &&
m.getSimpleName().contentEquals(bindMethod) &&
descriptor(m.asType()).equals(currentMethod.descriptor)) {
methodFound = true;
}
}
if (!methodFound) {
trees.printMessage(Diagnostic.Kind.ERROR, "Invalid static binding: " +
currentMethod.method.type + "#" + currentMethod.method.name + " -> " +
bindType.getQualifiedName().toString() + "#" + bindMethod +
" (no matching method found, type conversions are not allowed for internal bindings)",
diagnostic.tree, diagnostic.root);
}
}
List<String> addBindings() {
List<String> unresolvedErrors = new ArrayList<>();
List<TypeBinding> addedTypes = new ArrayList<>();
List<MethodBinding> addedMethods = new ArrayList<>();
Set<Object> validated = new HashSet<>();
// Remove changed bindings.
for (TypeBinding type : typeBindings.values()) {
if (type.bindType != null) addedTypes.add(type);
types.remove(type.currentType);
}
for (MethodBinding method : methodBindings) {
if (method.bindMethod != null) addedMethods.add(method);
methods.remove(method.currentMethod);
}
methods.entrySet().removeIf(m -> typeBindings.containsKey(m.getKey().method.type));
// Build inverse binding map.
Map<String, String> inverseTypes = types.entrySet().stream().collect(Collectors.toMap(
e -> e.getValue().type, Map.Entry::getKey, (a, b) -> {
unresolvedErrors.add("Conflicting JBR API binding: " + a + " and "+ b + " binds to the same type");
return a + "," + b;
}));
Map<StaticDescriptor, StaticMethod> inverseMethods = methods.entrySet().stream().collect(Collectors.toMap(
e -> new StaticDescriptor(e.getValue(), e.getKey().descriptor), e -> e.getKey().method, (a, b) -> {
unresolvedErrors.add("Conflicting JBR API binding: " +
a.type + "#" + a.name + " and "+ b.type + "#" + b.name + " binds to the same method");
return new StaticMethod(a.type + "," + b.type, a.name + "," + b.name);
}));
// Add new bindings.
for (TypeBinding type : addedTypes) types.put(type.currentType, new Type(type.bindType, type.binding));
for (MethodBinding method : addedMethods) methods.put(method.currentMethod, method.bindMethod);
// Validate type bindings.
for (TypeBinding type : addedTypes) {
String inv = inverseTypes.get(type.bindType);
if (inv != null) {
trees.printMessage(Diagnostic.Kind.ERROR,
"Conflicting JBR API binding: " + type.currentType + " -> " + type.bindType + " <- " + inv,
type.diagnostic.tree, type.diagnostic.root);
inverseTypes.put(type.bindType, inv + "," + type.currentType);
} else inverseTypes.put(type.bindType, type.currentType);
Type next = types.get(type.bindType);
if (next != null) {
trees.printMessage(Diagnostic.Kind.ERROR,
"Conflicting JBR API binding: " + type.currentType + " -> " + type.bindType + " -> " + next,
type.diagnostic.tree, type.diagnostic.root);
}
String prev = inverseTypes.get(type.currentType);
if (prev != null) {
trees.printMessage(Diagnostic.Kind.ERROR,
"Conflicting JBR API binding: " + prev + " -> " + type.currentType + " -> " + type.bindType,
type.diagnostic.tree, type.diagnostic.root);
}
if (validated.add(type.currentType)) {
TypeElement bindElement = elements.getTypeElement(type.bindType);
if (bindElement != null) {
internal.add(type.currentType);
validateInternal(type.diagnostic, type.currentType, type.binding, bindElement);
}
}
}
// Validate method bindings.
for (MethodBinding method : addedMethods) {
StaticDescriptor invDescriptor = new StaticDescriptor(method.bindMethod, method.currentMethod.descriptor);
StaticMethod inv = inverseMethods.get(invDescriptor);
if (inv != null) {
trees.printMessage(Diagnostic.Kind.ERROR, "Conflicting JBR API binding: " +
method.currentMethod.method.type + "#" + method.currentMethod.method.name + " -> " +
method.bindMethod.type + "#" + method.bindMethod.name + " <- " +
inv.type + "#" + inv.name,
method.diagnostic.tree, method.diagnostic.root);
inverseMethods.put(invDescriptor, new StaticMethod(
inv.type + "," + method.currentMethod.method.type, inv.name + "," + method.currentMethod.method.name));
} else inverseMethods.put(invDescriptor, method.currentMethod.method);
if (validated.add(method.currentMethod)) {
TypeElement bindElement = elements.getTypeElement(method.bindMethod.type);
if (bindElement != null) {
internal.add(method.currentMethod);
validateInternalMethod(method.diagnostic, method.currentMethod, bindElement, method.bindMethod.name);
}
}
}
// [Re]validate remaining.
types.forEach((k, v) -> {
if (validated.add(k)) {
TypeBinding type = typeBindings.get(v.type);
if (type != null) {
internal.add(k);
validateInternal(type.diagnostic, k, v.binding, type.element);
} else if (elements.getTypeElement(v.type) != null) {
internal.add(k); // Couldn't validate, but at least found the type.
if (v.binding != Binding.SERVICE) {
unresolvedErrors.add("Invalid JBR API binding:" + k + " -> " + v.type + " (internal, non-service)");
}
}
}
});
methods.forEach((k, v) -> {
if (validated.add(k)) {
TypeBinding type = typeBindings.get(v.type);
if (type != null) {
internal.add(k);
validateInternalMethod(type.diagnostic, k, type.element, v.name);
} else if (elements.getTypeElement(v.type) != null) {
internal.add(k); // Couldn't validate, but at least found the type.
}
}
});
return unresolvedErrors;
}
void read(RandomAccessFile file) throws IOException {
String s;
while ((s = file.readLine()) != null) {
String[] tokens = s.split(" ");
switch (tokens[0]) {
case "TYPE" -> {
types.put(tokens[1], new Type(tokens[2], Binding.valueOf(tokens[3])));
if (tokens.length > 4 && tokens[4].equals("INTERNAL")) internal.add(tokens[1]);
}
case "STATIC" -> {
StaticDescriptor descriptor = new StaticDescriptor(new StaticMethod(
tokens[1], tokens[2]), tokens[3]);
methods.put(descriptor, new StaticMethod(tokens[4], tokens[5]));
if (tokens.length > 6 && tokens[6].equals("INTERNAL")) internal.add(descriptor);
}
}
}
}
void write(RandomAccessFile file) throws IOException {
for (var t : types.entrySet()) {
file.writeBytes("TYPE " + t.getKey() + " " + t.getValue().type + " " + t.getValue().binding +
(internal.contains(t.getKey()) ? " INTERNAL\n" : "\n"));
}
for (var t : methods.entrySet()) {
file.writeBytes("STATIC " + t.getKey().method.type + " " + t.getKey().method.name + " " +
t.getKey().descriptor + " " + t.getValue().type + " " + t.getValue().name +
(internal.contains(t.getKey()) ? " INTERNAL\n" : "\n"));
}
}
}
String descriptor(TypeMirror t) {
return switch (t.getKind()) {
case VOID -> "V";
case BOOLEAN -> "Z";
case BYTE -> "B";
case CHAR -> "C";
case SHORT -> "S";
case INT -> "I";
case LONG -> "J";
case FLOAT -> "F";
case DOUBLE -> "D";
case ARRAY -> "[" + descriptor(((ArrayType) t).getComponentType());
case DECLARED -> "L" + elements.getBinaryName((TypeElement) ((DeclaredType) t).asElement())
.toString().replace('.', '/') + ";";
case EXECUTABLE -> "(" + ((ExecutableType) t).getParameterTypes().stream().map(this::descriptor)
.collect(Collectors.joining()) + ")" + descriptor(((ExecutableType) t).getReturnType());
case TYPEVAR, WILDCARD, UNION, INTERSECTION -> descriptor(types.erasure(t));
default -> throw new RuntimeException("Cannot generate descriptor for type: " + t);
};
}
Registry.StaticDescriptor staticDescriptor(String type, ExecutableElement e) {
return new Registry.StaticDescriptor(new Registry.StaticMethod(type, e.getSimpleName().toString()), descriptor(e.asType()));
}
AnnotationValue annotationValue(AnnotationMirror m) {
if (m == null) return null;
return m.getElementValues().entrySet().stream()
.filter(t -> t.getKey().getSimpleName().contentEquals("value"))
.map(Map.Entry::getValue).findFirst().orElseThrow();
}
static boolean isJavaIdentifier(String name, int from, int to) {
if (!Character.isJavaIdentifierStart(name.charAt(from))) return false;
for (int i = from + 1; i < to; i++) {
if (!Character.isJavaIdentifierPart(name.charAt(i))) return false;
}
return true;
}
static boolean isJavaIdentifier(String name) {
if (name == null || name.isEmpty()) return false;
return isJavaIdentifier(name, 0, name.length());
}
static boolean isJavaTypeIdentifier(String name) {
if (name == null || name.isEmpty()) return false;
for (int i = 0; i < name.length();) {
int next = name.indexOf('.', i);
if (next == -1) next = name.length();
if (!isJavaIdentifier(name, i, next)) return false;
i = next + 1;
}
return true;
}
void scan(CompilationUnitTree root, Element e) {
// Get current type name.
String currentType;
if (e.getKind() == ElementKind.CLASS || e.getKind() == ElementKind.INTERFACE) {
currentType = ((TypeElement) e).getQualifiedName().toString();
} else if (e.getKind() == ElementKind.METHOD && e.getModifiers().contains(Modifier.STATIC)) {
currentType = ((QualifiedNameable) e.getEnclosingElement()).getQualifiedName().toString();
} else currentType = null;
// Find the annotation.
AnnotationMirror providedMirror = null, providesMirror = null, serviceMirror = null;
for (AnnotationMirror m : elements.getAllAnnotationMirrors(e)) {
switch (m.getAnnotationType().toString()) {
case "com.jetbrains.exported.JBRApi.Provided" -> providedMirror = m;
case "com.jetbrains.exported.JBRApi.Provides" -> providesMirror = m;
case "com.jetbrains.exported.JBRApi.Service" -> serviceMirror = m;
}
}
AnnotationMirror mirror = null;
AnnotationValue value = null;
Binding binding = null;
if (serviceMirror != null) {
if (providesMirror == null) {
trees.printMessage(Diagnostic.Kind.ERROR,
"@Service also requires @Provides", trees.getTree(e, serviceMirror), root);
return;
}
if (providedMirror != null) {
trees.printMessage(Diagnostic.Kind.ERROR,
"@Service cannot be used with @Provided", trees.getTree(e, serviceMirror), root);
return;
}
value = annotationValue(mirror = providesMirror);
binding = Binding.SERVICE;
} else if (providesMirror != null) {
value = annotationValue(mirror = providesMirror);
if (providedMirror != null) {
AnnotationValue v = annotationValue(providedMirror);
if (!value.getValue().toString().equals(v.getValue().toString())) {
trees.printMessage(Diagnostic.Kind.ERROR,
"@Provided and @Provides doesn't match", trees.getTree(e, mirror, value), root);
return;
}
binding = Binding.TWO_WAY;
} else binding = Binding.PROVIDES;
} else if (providedMirror != null) {
value = annotationValue(mirror = providedMirror);
binding = Binding.PROVIDED;
}
if (value != null && value.getValue().toString().isEmpty()) {
trees.printMessage(Diagnostic.Kind.ERROR,
"Empty JBR API binding",
trees.getTree(e, mirror, value), root);
return;
}
if (currentType == null) {
if (value != null) {
trees.printMessage(Diagnostic.Kind.ERROR,
"JBR API annotations are only allowed on classes, interfaces and static methods",
trees.getTree(e, mirror), root);
}
return;
}
if (value != null && e.getKind() == ElementKind.METHOD && binding != Binding.PROVIDES) {
trees.printMessage(Diagnostic.Kind.ERROR,
"Only @Provides is allowed for static methods",
trees.getTree(e, mirror), root);
return;
}
// Determine class/method names.
String bindType = null, bindMethod = null;
if (value != null) {
bindType = value.getValue().toString();
if (e.getKind() == ElementKind.METHOD) {
int splitIndex = bindType.indexOf('#');
if (splitIndex != -1) {
bindMethod = bindType.substring(splitIndex + 1);
bindType = bindType.substring(0, splitIndex);
if (!isJavaIdentifier(bindMethod)) {
trees.printMessage(Diagnostic.Kind.ERROR, "Invalid method identifier: " + bindMethod,
trees.getTree(e, mirror, value), root);
return;
}
} else bindMethod = e.getSimpleName().toString();
}
if (!isJavaTypeIdentifier(bindType)) {
trees.printMessage(Diagnostic.Kind.ERROR, "Invalid type identifier: " + bindType,
trees.getTree(e, mirror, value), root);
return;
}
if (Character.isUpperCase(bindType.charAt(0))) bindType = "com.jetbrains." + bindType; // Short form
}
// Add entry.
DiagnosticTree diagnostic = new DiagnosticTree(root, trees.getTree(e, mirror, value));
if (e.getKind() == ElementKind.METHOD) {
ExecutableElement m = (ExecutableElement) e;
methodBindings.add(new MethodBinding(diagnostic, m, staticDescriptor(currentType, m),
bindType == null ? null : new Registry.StaticMethod(bindType, bindMethod)));
} else {
typeBindings.put(currentType, new TypeBinding(diagnostic, (TypeElement) e, currentType, bindType, binding));
}
}
@Override
public String getName() {
return "jbr-api";
}
@Override
public void init(JavacTask jt, String... args) {
Path output = Path.of(args[0]);
String implVersion;
try {
implVersion = Files.readString(Path.of(args[1])).strip();
} catch (IOException e) {
throw new RuntimeException(e);
}
elements = jt.getElements();
trees = Trees.instance(jt);
types = jt.getTypes();
jt.addTaskListener(new TaskListener() {
@Override
public void finished(TaskEvent te) {
if (te.getKind() == TaskEvent.Kind.ANALYZE && te.getTypeElement() != null) {
new ElementScanner14<Void, CompilationUnitTree>() {
@Override
public Void visitModule(ModuleElement e, CompilationUnitTree unused) { return null; }
@Override
public Void visitPackage(PackageElement e, CompilationUnitTree unused) { return null; }
@Override
public Void scan(Element e, CompilationUnitTree root) {
JBRApiPlugin.this.scan(root, e);
e.accept(this, root);
return null;
}
}.scan(te.getTypeElement(), te.getCompilationUnit());
} else if (te.getKind() == TaskEvent.Kind.COMPILATION) {
try (RandomAccessFile file = new RandomAccessFile(output.toFile(), "rw");
FileChannel channel = file.getChannel()) {
for (;;) {
try { if (channel.lock() != null) break; } catch (OverlappingFileLockException ignore) {}
LockSupport.parkNanos(10_000000);
}
Registry r = new Registry();
r.read(file);
var unresolvedErrors = r.addBindings();
file.setLength(0);
file.writeBytes("VERSION " + implVersion + "\n");
r.write(file);
if (!unresolvedErrors.isEmpty()) {
throw new RuntimeException(String.join("\n", unresolvedErrors));
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
});
}
}

View File

@@ -671,10 +671,8 @@ else
# noexcept-type required for GCC 7 builds. Not required for GCC 8+.
# expansion-to-defined required for GCC 9 builds. Not required for GCC 10+.
# maybe-uninitialized required for GCC 8 builds. Not required for GCC 9+.
# calloc-transposed-args required for GCC 14 builds. (fixed upstream in Harfbuzz 032c931e1c0cfb20f18e5acb8ba005775242bd92)
HARFBUZZ_DISABLED_WARNINGS_CXX_gcc := class-memaccess noexcept-type \
expansion-to-defined dangling-reference maybe-uninitialized \
calloc-transposed-args
expansion-to-defined dangling-reference maybe-uninitialized
HARFBUZZ_DISABLED_WARNINGS_clang := missing-field-initializers range-loop-analysis
HARFBUZZ_DISABLED_WARNINGS_microsoft := 4267 4244
@@ -1136,8 +1134,7 @@ ifeq ($(call isTargetOs, macosx), true)
-framework ExceptionHandling \
-framework JavaRuntimeSupport \
-framework OpenGL \
-framework QuartzCore \
-framework UniformTypeIdentifiers -ljava, \
-framework QuartzCore -ljava, \
))
TARGETS += $(BUILD_LIBAWT_LWAWT)

View File

@@ -96,7 +96,7 @@ ZActivatedArray<T>::ZActivatedArray(bool locked)
_array() {}
template <typename T>
ZActivatedArray<T>::~ZActivatedArray() {
ZActivatedArray<T>::~ZActivatedArray<T>() {
FreeHeap(_lock);
}

View File

@@ -44,7 +44,7 @@ template <class T, MEMFLAGS F> class ChunkedList : public CHeapObj<F> {
}
public:
ChunkedList() : _top(_values), _next_used(nullptr), _next_free(nullptr) {}
ChunkedList<T, F>() : _top(_values), _next_used(nullptr), _next_free(nullptr) {}
bool is_full() const {
return _top == end();

View File

@@ -99,7 +99,7 @@ template <class T> class EventLogBase : public EventLog {
EventRecord<T>* _records;
public:
EventLogBase(const char* name, const char* handle, int length = LogEventsBufferEntries):
EventLogBase<T>(const char* name, const char* handle, int length = LogEventsBufferEntries):
_mutex(Mutex::event, name),
_name(name),
_handle(handle),

View File

@@ -118,7 +118,7 @@ class GrowableArrayView : public GrowableArrayBase {
protected:
E* _data; // data array
GrowableArrayView(E* data, int capacity, int initial_len) :
GrowableArrayView<E>(E* data, int capacity, int initial_len) :
GrowableArrayBase(capacity, initial_len), _data(data) {}
~GrowableArrayView() {}
@@ -126,7 +126,7 @@ protected:
public:
const static GrowableArrayView EMPTY;
bool operator==(const GrowableArrayView& rhs) const {
bool operator==(const GrowableArrayView<E>& rhs) const {
if (_len != rhs._len)
return false;
for (int i = 0; i < _len; i++) {
@@ -137,7 +137,7 @@ public:
return true;
}
bool operator!=(const GrowableArrayView& rhs) const {
bool operator!=(const GrowableArrayView<E>& rhs) const {
return !(*this == rhs);
}
@@ -345,7 +345,7 @@ template <typename E>
class GrowableArrayFromArray : public GrowableArrayView<E> {
public:
GrowableArrayFromArray(E* data, int len) :
GrowableArrayFromArray<E>(E* data, int len) :
GrowableArrayView<E>(data, len, len) {}
};
@@ -480,7 +480,7 @@ public:
return this->at(location);
}
void swap(GrowableArrayWithAllocator* other) {
void swap(GrowableArrayWithAllocator<E, Derived>* other) {
::swap(this->_data, other->_data);
::swap(this->_len, other->_len);
::swap(this->_capacity, other->_capacity);
@@ -682,8 +682,8 @@ public:
// See: init_checks.
template <typename E>
class GrowableArray : public GrowableArrayWithAllocator<E, GrowableArray<E>> {
friend class GrowableArrayWithAllocator<E, GrowableArray>;
class GrowableArray : public GrowableArrayWithAllocator<E, GrowableArray<E> > {
friend class GrowableArrayWithAllocator<E, GrowableArray<E> >;
friend class GrowableArrayTest;
static E* allocate(int max) {
@@ -731,7 +731,7 @@ public:
GrowableArray() : GrowableArray(2 /* initial_capacity */) {}
explicit GrowableArray(int initial_capacity) :
GrowableArrayWithAllocator<E, GrowableArray>(
GrowableArrayWithAllocator<E, GrowableArray<E> >(
allocate(initial_capacity),
initial_capacity),
_metadata() {
@@ -739,7 +739,7 @@ public:
}
GrowableArray(int initial_capacity, MEMFLAGS memflags) :
GrowableArrayWithAllocator<E, GrowableArray>(
GrowableArrayWithAllocator<E, GrowableArray<E> >(
allocate(initial_capacity, memflags),
initial_capacity),
_metadata(memflags) {
@@ -747,7 +747,7 @@ public:
}
GrowableArray(int initial_capacity, int initial_len, const E& filler) :
GrowableArrayWithAllocator<E, GrowableArray>(
GrowableArrayWithAllocator<E, GrowableArray<E> >(
allocate(initial_capacity),
initial_capacity, initial_len, filler),
_metadata() {
@@ -755,7 +755,7 @@ public:
}
GrowableArray(int initial_capacity, int initial_len, const E& filler, MEMFLAGS memflags) :
GrowableArrayWithAllocator<E, GrowableArray>(
GrowableArrayWithAllocator<E, GrowableArray<E> >(
allocate(initial_capacity, memflags),
initial_capacity, initial_len, filler),
_metadata(memflags) {
@@ -763,7 +763,7 @@ public:
}
GrowableArray(Arena* arena, int initial_capacity, int initial_len, const E& filler) :
GrowableArrayWithAllocator<E, GrowableArray>(
GrowableArrayWithAllocator<E, GrowableArray<E> >(
allocate(initial_capacity, arena),
initial_capacity, initial_len, filler),
_metadata(arena) {
@@ -847,15 +847,15 @@ class GrowableArrayIterator : public StackObj {
public:
GrowableArrayIterator() : _array(nullptr), _position(0) { }
GrowableArrayIterator& operator++() { ++_position; return *this; }
E operator*() { return _array->at(_position); }
GrowableArrayIterator<E>& operator++() { ++_position; return *this; }
E operator*() { return _array->at(_position); }
bool operator==(const GrowableArrayIterator& rhs) {
bool operator==(const GrowableArrayIterator<E>& rhs) {
assert(_array == rhs._array, "iterator belongs to different array");
return _position == rhs._position;
}
bool operator!=(const GrowableArrayIterator& rhs) {
bool operator!=(const GrowableArrayIterator<E>& rhs) {
assert(_array == rhs._array, "iterator belongs to different array");
return _position != rhs._position;
}

View File

@@ -82,7 +82,7 @@ template <class E> class LinkedListNode : public AnyObj {
template <class E> class LinkedList : public AnyObj {
protected:
LinkedListNode<E>* _head;
NONCOPYABLE(LinkedList);
NONCOPYABLE(LinkedList<E>);
public:
LinkedList() : _head(nullptr) { }

View File

@@ -1,6 +1,5 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, JetBrains s.r.o.. All rights reserved.
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -24,23 +23,21 @@
* questions.
*/
#ifndef VKBuffer_h_Included
#define VKBuffer_h_Included
#include <vulkan/vulkan.h>
package com.jetbrains.base;
typedef struct {
VkBuffer buffer;
VkDeviceMemory memory;
VkDeviceSize size;
} VKBuffer;
import com.jetbrains.internal.JBRApi;
VkResult VKBuffer_FindMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter,
VkMemoryPropertyFlags properties, uint32_t* pMemoryType);
import java.lang.invoke.MethodHandles;
VKBuffer* VKBuffer_Create(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties);
VKBuffer* VKBuffer_CreateFromData(void* vertices, VkDeviceSize bufferSize);
void VKBuffer_free(VKBuffer* buffer);
#endif // VKBuffer_h_Included
/**
* This class contains mapping between JBR API interfaces and implementation in {@code java.base} module.
*/
public class JBRApiModule {
static {
JBRApi.registerModule(MethodHandles.lookup(), JBRApiModule.class.getModule()::addExports)
.service("com.jetbrains.JBR$ServiceApi")
.withStatic("getService", "getService", "com.jetbrains.internal.JBRApi")
.service("com.jetbrains.Jstack")
.withStatic("includeInfoFrom", "$$jb$additionalInfoForJstack", "java.lang.Throwable");
}
}

View File

@@ -26,36 +26,45 @@
package com.jetbrains.bootstrap;
import com.jetbrains.internal.JBRApi;
import jdk.internal.loader.ClassLoaders;
import java.lang.invoke.MethodHandles;
import java.util.Map;
/**
* Bootstrap class, used to initialize {@linkplain JBRApi JBR API}.
* @deprecated replaced by {@link com.jetbrains.exported.JBRApiSupport}
*/
@Deprecated
public class JBRApiBootstrap {
private JBRApiBootstrap() {}
/**
* Old version of bootstrap method without metadata parameter.
* Classes that register JBR API implementation for their modules.
*/
private static final String[] MODULES = {
"com.jetbrains.base.JBRApiModule",
"com.jetbrains.desktop.JBRApiModule"
};
/**
* Called from static initializer of {@link com.jetbrains.JBR}.
* @param outerLookup lookup context inside {@code jetbrains.api} module
* @return implementation for {@link com.jetbrains.JBR.ServiceApi} interface
*/
public static synchronized Object bootstrap(MethodHandles.Lookup outerLookup) {
if (!JBRApi.ENABLED) return null;
if (Boolean.getBoolean("jetbrains.runtime.api.verbose")) {
System.out.println("JBR API bootstrap in compatibility mode: Object bootstrap(MethodHandles.Lookup)");
}
Class<?> apiInterface;
if (!System.getProperty("jetbrains.api.enabled", "true").equalsIgnoreCase("true")) return null;
try {
apiInterface = outerLookup.findClass("com.jetbrains.JBR$ServiceApi");
} catch (ClassNotFoundException | IllegalAccessException e) {
throw new RuntimeException("Failed to retrieve JBR API metadata", e);
Class<?> apiInterface = outerLookup.findClass("com.jetbrains.JBR$ServiceApi");
if (!outerLookup.hasFullPrivilegeAccess() ||
outerLookup.lookupClass().getPackage() != apiInterface.getPackage()) {
throw new IllegalArgumentException("Lookup must be full-privileged from the com.jetbrains package: " +
outerLookup.lookupClass().getName());
}
JBRApi.init(outerLookup);
ClassLoader classLoader = ClassLoaders.platformClassLoader();
for (String m : MODULES) Class.forName(m, true, classLoader);
return JBRApi.getService(apiInterface);
} catch(ClassNotFoundException | IllegalAccessException e) {
throw new RuntimeException(e);
}
return com.jetbrains.exported.JBRApiSupport.bootstrap(apiInterface, null, null, null, Map.of(), m -> null);
}
}

View File

@@ -1,92 +0,0 @@
package com.jetbrains.exported;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
import java.io.Serial;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* JBR API utility class.
*/
public class JBRApi {
private JBRApi() {}
/**
* Marks classes and interfaces whose implementation is provided by JBR API client.
* These types must not be inherited by JBR unless explicitly marked with {@link Provides}.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Provided {
/**
* Binding target.
* This is a fully qualified name of corresponding {@code @Provides} class/interface,
* e.g. {@code com.jetbrains.My.Nested.Class}.
* Package {@code com.jetbrains} may be omitted, e.g. {@code My.Nested.Class}, {@code MyClass#myMethod}.
*/
String value();
}
/**
* Marks classes, interfaces and static methods which provide their functionality to JBR API.
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Provides {
/**
* Binding target.
* For type binding this is a fully qualified name of corresponding {@code @Provided} class/interface,
* e.g. {@code com.jetbrains.My.Nested.Class}.
* For static method binding this is a fully qualified name of corresponding {@code @Provided} class/interface with optional
* method name appended after '#', e.g. {@code com.jetbrains.MyClass#myMethod}.
* If method name is omitted, name of annotated method itself is used.
* In all cases, package {@code com.jetbrains} may be omitted, e.g. {@code My.Nested.Class}, {@code MyClass#myMethod}.
*/
String value();
}
/**
* Marks JBR API service.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {}
/**
* Creates new internal service instance of caller class type.
* Intended usage: {@code public static final MyService INSTANCE = JBRApi.internalService();}
* @return internal service instance
*/
@CallerSensitive
@SuppressWarnings("unchecked")
public static <T> T internalService() {
var caller = (Class<T>) Reflection.getCallerClass();
if (caller == null) throw new IllegalCallerException("No caller frame");
return com.jetbrains.internal.JBRApi.getInternalService(caller);
}
/**
* If a JBR API service has a defined implementation class, it is instantiated via static factory method
* {@code create()} or a no-arg constructor. {@link ServiceNotAvailableException}
* may be thrown from that constructor or factory method to indicate that service is unavailable for some reason.
* Exception is not propagated to user, but rather {@code null} is returned for that service.
* Optional message and cause can be specified, but only used for logging (when explicitly enabled by VM flag).
*/
public static class ServiceNotAvailableException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
public ServiceNotAvailableException() { super(); }
public ServiceNotAvailableException(String message) { super(message); }
public ServiceNotAvailableException(String message, Throwable cause) { super(message, cause); }
public ServiceNotAvailableException(Throwable cause) { super(cause); }
}
}

View File

@@ -1,63 +0,0 @@
package com.jetbrains.exported;
import java.lang.annotation.Annotation;
import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.function.Function;
/**
* Supporting exported API for JBR API backend. Not intended to be used by JBR or client code directly.
*/
public class JBRApiSupport {
private JBRApiSupport() {}
/**
* Initializes JBR API.
* @param apiInterface internal {@code JBR.ServiceApi} interface
* @param serviceAnnotation {@code @Service} annotation class
* @param providedAnnotation {@code @Provided} annotation class
* @param providesAnnotation {@code @Provides} annotation class
* @param knownExtensions map of known extension enums and classes defining them
* @param extensionExtractor receives method, returns its extension enum, or null
* @return implementation for {@code JBR.ServiceApi} interface
*/
@SuppressWarnings("rawtypes")
public static synchronized Object bootstrap(Class<?> apiInterface,
Class<? extends Annotation> serviceAnnotation,
Class<? extends Annotation> providedAnnotation,
Class<? extends Annotation> providesAnnotation,
Map<Enum<?>, Class[]> knownExtensions,
Function<Method, Enum<?>> extensionExtractor) {
if (!com.jetbrains.internal.JBRApi.ENABLED) return null;
com.jetbrains.internal.JBRApi.init(
null,
serviceAnnotation,
providedAnnotation,
providesAnnotation,
knownExtensions,
extensionExtractor);
return com.jetbrains.internal.JBRApi.getService(apiInterface);
}
/**
* Bootstrap method for JBR API dynamic invocations.
*/
public static CallSite bootstrapDynamic(MethodHandles.Lookup caller, String name, MethodType type) {
return new ConstantCallSite(com.jetbrains.internal.JBRApi.bindDynamic(caller, name, type));
}
/**
* JBR API proxy class. Every generated proxy class implements this interface.
*/
public interface Proxy {
/**
* We really don't want to clash with other possible method names.
* @return target object
*/
Object $getProxyTarget();
}
}

View File

@@ -58,21 +58,23 @@ class ASMUtils {
InternalMethodInfo methodInfo = getInternalMethodInfo(interfaceMethod);
MethodVisitor p = writer.visitMethod(ACC_PUBLIC | ACC_FINAL, methodInfo.name(),
methodInfo.descriptor(), methodInfo.genericSignature(), methodInfo.exceptionNames());
p.visitCode();
throwException(p, "java/lang/UnsupportedOperationException", "No implementation found for this method");
p.visitMaxs(0, 0);
p.visitEnd();
}
public static void throwException(MethodVisitor p, String type, String message) {
p.visitTypeInsn(NEW, type);
p.visitTypeInsn(NEW, "java/lang/UnsupportedOperationException");
p.visitInsn(DUP);
p.visitLdcInsn(message);
p.visitMethodInsn(INVOKESPECIAL, type, "<init>", "(Ljava/lang/String;)V", false);
p.visitLdcInsn("No implementation found for this method");
p.visitMethodInsn(INVOKESPECIAL, "java/lang/UnsupportedOperationException", "<init>", "(Ljava/lang/String;)V", false);
p.visitInsn(ATHROW);
p.visitMaxs(-1, -1);
}
public record InternalMethodInfo(String name, String descriptor, String genericSignature,
public static void logDeprecated(MethodVisitor writer, String message) {
writer.visitTypeInsn(NEW, "java/lang/Exception");
writer.visitInsn(DUP);
writer.visitLdcInsn(message);
writer.visitMethodInsn(INVOKESPECIAL, "java/lang/Exception", "<init>", "(Ljava/lang/String;)V", false);
writer.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Exception", "printStackTrace", "()V", false);
}
protected record InternalMethodInfo(String name, String descriptor, String genericSignature,
String[] exceptionNames) {}
public static InternalMethodInfo getInternalMethodInfo(Method method) {
@@ -119,7 +121,7 @@ class ASMUtils {
return IRETURN + getOpcodeOffset(c);
}
private static int getOpcodeOffset(Class<?> c) {
public static int getOpcodeOffset(Class<?> c) {
if (c.isPrimitive()) {
if (c == Long.TYPE) {
return 1;

View File

@@ -1,140 +0,0 @@
package com.jetbrains.internal;
import jdk.internal.org.objectweb.asm.Handle;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandleInfo;
import java.lang.invoke.MethodType;
import java.util.*;
import java.util.function.Supplier;
import static java.lang.invoke.MethodHandles.Lookup;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
import static jdk.internal.org.objectweb.asm.Opcodes.PUTSTATIC;
import static jdk.internal.org.objectweb.asm.Type.getInternalName;
/**
* Context used by {@link ProxyGenerator} to determine whether class or method is
* accessible from a caller class and generate appropriate direct or dynamic calls.
*/
class AccessContext {
private final Map<Class<?>, Boolean> accessibleClasses = new HashMap<>();
final Map<Proxy, Boolean> dependencies = new HashMap<>(); // true for required, false for optional
final List<DynamicCallTarget> dynamicCallTargets = new ArrayList<>();
final Lookup caller;
AccessContext(Lookup caller) {
this.caller = caller;
}
record DynamicCallTarget(String name, String descriptor, Supplier<MethodHandle> futureHandle) {}
class Method {
final MethodVisitor writer;
private final boolean methodRequired;
Method(MethodVisitor writer, boolean methodRequired) {
this.writer = writer;
this.methodRequired = methodRequired;
}
AccessContext access() {
return AccessContext.this;
}
void addDependency(Proxy p) {
if (methodRequired) dependencies.put(p, true);
else dependencies.putIfAbsent(p, false);
}
void invokeDynamic(MethodHandle handle) {
invokeDynamic(handle.type(), () -> handle);
}
void invokeDynamic(MethodType type, Supplier<MethodHandle> futureHandle) {
String descriptor = erase(type).descriptorString();
DynamicCallTarget t = new DynamicCallTarget("dynamic" + dynamicCallTargets.size(), descriptor, futureHandle);
dynamicCallTargets.add(t);
writer.visitInvokeDynamicInsn(t.name, descriptor,
new Handle(H_INVOKESTATIC, "com/jetbrains/exported/JBRApiSupport", "bootstrapDynamic",
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", false));
}
void invokeDirect(MethodHandleInfo handleInfo) {
int opcode = switch (handleInfo.getReferenceKind()) {
default -> throw new RuntimeException("Unknown reference type");
case MethodHandleInfo.REF_getField -> GETFIELD;
case MethodHandleInfo.REF_getStatic -> GETSTATIC;
case MethodHandleInfo.REF_putField -> PUTFIELD;
case MethodHandleInfo.REF_putStatic -> PUTSTATIC;
case MethodHandleInfo.REF_invokeVirtual -> INVOKEVIRTUAL;
case MethodHandleInfo.REF_invokeStatic -> INVOKESTATIC;
case MethodHandleInfo.REF_invokeSpecial -> INVOKESPECIAL;
case MethodHandleInfo.REF_newInvokeSpecial -> NEW;
case MethodHandleInfo.REF_invokeInterface -> INVOKEINTERFACE;
};
String targetName = getInternalName(handleInfo.getDeclaringClass());
if (opcode == NEW) {
writer.visitTypeInsn(NEW, targetName);
writer.visitInsn(DUP);
opcode = INVOKESPECIAL;
}
switch (opcode) {
case GETFIELD, GETSTATIC -> writer.visitFieldInsn(opcode, targetName, handleInfo.getName(),
handleInfo.getMethodType().returnType().descriptorString());
case PUTFIELD, PUTSTATIC -> writer.visitFieldInsn(opcode, targetName, handleInfo.getName(),
handleInfo.getMethodType().parameterType(0).descriptorString());
default -> writer.visitMethodInsn(opcode, targetName, handleInfo.getName(),
handleInfo.getMethodType().descriptorString(), handleInfo.getDeclaringClass().isInterface());
}
}
}
MethodHandleInfo resolveDirect(MethodHandle handle) {
try {
MethodHandleInfo handleInfo = caller.revealDirect(handle);
if (isClassLoaderAccessible(caller.lookupClass().getClassLoader(),
handleInfo.getDeclaringClass().getClassLoader())) {
// Accessible directly.
return handleInfo;
}
} catch (Exception ignore) {}
// Not accessible directly.
return null;
}
/**
* Replaces all inaccessible types with {@link Object}.
* @param type original method type
* @return erased method type
*/
private MethodType erase(MethodType type) {
if (!canAccess(type.returnType())) type = type.changeReturnType(Object.class);
for (int i = 0; i < type.parameterCount(); i++) {
if (!canAccess(type.parameterType(i))) type = type.changeParameterType(i, Object.class);
}
return type;
}
boolean canAccess(Class<?> clazz) {
return accessibleClasses.computeIfAbsent(clazz, c -> canAccess(caller, c));
}
static boolean canAccess(Lookup caller, Class<?> target) {
try {
if (isClassLoaderAccessible(caller.lookupClass().getClassLoader(),
caller.accessClass(target).getClassLoader())) return true;
} catch (IllegalAccessException ignore) {}
return false;
}
private static boolean isClassLoaderAccessible(ClassLoader caller, ClassLoader target) {
if (target == null) return true;
for (ClassLoader cl = caller; cl != null; cl = cl.getParent()) {
if (cl == target) return true;
}
return false;
}
}

View File

@@ -25,196 +25,328 @@
package com.jetbrains.internal;
import com.jetbrains.exported.JBRApi.Provides;
import sun.security.action.GetBooleanAction;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.io.Serial;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.Map;
import java.lang.invoke.MethodHandles;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.BiFunction;
import static java.lang.invoke.MethodHandles.Lookup;
/**
* JBR API is a collection of JBR-specific features that are accessed by client though
* {@link com.jetbrains.JBR jetbrains.runtime.api} module. Actual implementation is linked by
* {@link com.jetbrains.JBR jetbrains.api} module. Actual implementation is linked by
* JBR at runtime by generating {@linkplain Proxy proxy objects}.
* Mapping between interfaces and implementation code is defined using
* {@link com.jetbrains.exported.JBRApi.Provided} and {@link com.jetbrains.exported.JBRApi.Provides} annotations.
* Mapping between interfaces and implementation code is defined in
* {@linkplain com.jetbrains.bootstrap.JBRApiBootstrap#MODULES registry classes}.
* <p>
* This class is an entry point into JBR API backend.
* @see Proxy
* This class has most basic methods for working with JBR API and cache of generated proxies.
* <p>
* <h2>How to add a new service</h2>
* <ol>
* <li>Create your service interface in module {@link com.jetbrains.JBR jetbrains.api}:
* <blockquote><pre>{@code
* package com.jetbrains;
*
* interface StringOptimizer {
* void optimize(String string);
* }
* }</pre></blockquote>
* </li>
* <li>Create an implementation inside JBR:
* <blockquote><pre>{@link java.lang.String java.lang.String}:{@code
* private static void optimizeInternal(String string) {
* string.hash = 0;
* string.hashIsZero = true;
* }
* }</pre></blockquote>
* </li>
* <li>Register your service in corresponding
* {@linkplain com.jetbrains.bootstrap.JBRApiBootstrap#MODULES module registry class}:
* <blockquote><pre>{@link com.jetbrains.base.JBRApiModule}:{@code
* .service("com.jetbrains.StringOptimizer", null)
* .withStatic("optimize", "java.lang.String", "optimizeInternal")
* }</pre></blockquote>
* </li>
* <li>You can also bind the interface to implementation class
* (without actually implementing the interface):
* <blockquote><pre>{@link java.lang.String java.lang.String}:{@code
* private static class StringOptimizerImpl {
*
* private void optimize(String string) {
* string.hash = 0;
* string.hashIsZero = true;
* }
* }
* }</pre></blockquote>
* <blockquote><pre>{@link com.jetbrains.base.JBRApiModule}:{@code
* .service("com.jetbrains.StringOptimizer", "java.lang.String$StringOptimizerImpl")
* }</pre></blockquote>
* </li>
* </ol>
* <h2>How to add a new proxy</h2>
* Registering a proxy is similar to registering a service:
* <blockquote><pre>{@code
* .proxy("com.jetbrains.SomeProxy", "a.b.c.SomeProxyImpl")
* }</pre></blockquote>
* Note that unlike service, proxy <b>must</b> have a target type.
* Also, proxy expects target object as a single constructor argument
* and can only be instantiated by JBR, there's no API that would allow
* user to directly create proxy object.
*/
public class JBRApi {
/**
* Enable JBR API, it wouldn't init when disabled. Enabled by default.
*/
public static final boolean ENABLED = Utils.property("jetbrains.runtime.api.enabled", true);
/**
* Enable API extensions. When disabled, extension methods are treated like any other method,
* {@link JBRApi#isExtensionSupported} always returns false, {@link JBRApi#getService(Class, Enum[])}
* behaves the same as {@link JBRApi#getService(Class)}. Enabled by default.
*/
static final boolean EXTENSIONS_ENABLED = Utils.property("jetbrains.runtime.api.extensions.enabled", true);
/**
* Enable extensive debugging logging. Disabled by default.
*/
static final boolean VERBOSE = Utils.property("jetbrains.runtime.api.verbose", false);
/**
* Print warnings about usage of deprecated interfaces and methods to {@link System#err}. Enabled by default.
*/
static final boolean LOG_DEPRECATED = Utils.property("jetbrains.runtime.api.logDeprecated", true);
/**
* Enable additional verification of generated bytecode. Disabled by default.
*/
static final boolean VERIFY_BYTECODE = Utils.property("jetbrains.runtime.api.verifyBytecode", false);
/**
* Allow extending registry. Disabled by default, used for tests.
*/
private static final boolean EXTEND_REGISTRY = Utils.property("jetbrains.runtime.api.extendRegistry", false);
@SuppressWarnings("removal")
static final boolean VERBOSE = AccessController.doPrivileged(new GetBooleanAction("jetbrains.api.verbose"));
record DynamicCallTargetKey(Class<?> proxy, String name, String descriptor) {}
static final ConcurrentMap<DynamicCallTargetKey, Supplier<MethodHandle>> dynamicCallTargets = new ConcurrentHashMap<>();
private static final ProxyRepository proxyRepository = new ProxyRepository();
private static final Map<String, RegisteredProxyInfo> registeredProxyInfoByInterfaceName = new HashMap<>();
private static final Map<String, RegisteredProxyInfo> registeredProxyInfoByTargetName = new HashMap<>();
private static final ConcurrentMap<Class<?>, Proxy<?>> proxyByInterface = new ConcurrentHashMap<>();
private static Boolean[] supportedExtensions;
private static long[] emptyExtensionsBitfield;
@SuppressWarnings("rawtypes")
private static Map<Enum<?>, Class[]> knownExtensions;
static Function<Method, Enum<?>> extensionExtractor;
/**
* lookup context inside {@code jetbrains.api} module
*/
static Lookup outerLookup;
/**
* Known service and proxy interfaces extracted from {@link com.jetbrains.JBR.Metadata}
*/
static Set<String> knownServices, knownProxies;
@SuppressWarnings("rawtypes")
public static void init(InputStream extendedRegistryStream,
Class<? extends Annotation> serviceAnnotation,
Class<? extends Annotation> providedAnnotation,
Class<? extends Annotation> providesAnnotation,
Map<Enum<?>, Class[]> knownExtensions,
Function<Method, Enum<?>> extensionExtractor) {
if (extendedRegistryStream != null && !EXTEND_REGISTRY) {
throw new Error("Extending JBR API registry is not supported");
public static void init(Lookup outerLookup) {
JBRApi.outerLookup = outerLookup;
try {
Class<?> metadataClass = outerLookup.findClass("com.jetbrains.JBR$Metadata");
Lookup lookup = MethodHandles.privateLookupIn(metadataClass, outerLookup);
knownServices = Set.of((String[]) lookup.findStaticVarHandle(metadataClass,
"KNOWN_SERVICES", String[].class).get());
knownProxies = Set.of((String[]) lookup.findStaticVarHandle(metadataClass,
"KNOWN_PROXIES", String[].class).get());
} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
knownServices = Set.of();
knownProxies = Set.of();
}
proxyRepository.init(extendedRegistryStream, serviceAnnotation, providedAnnotation, providesAnnotation);
if (EXTENSIONS_ENABLED) {
JBRApi.knownExtensions = knownExtensions;
JBRApi.extensionExtractor = extensionExtractor;
supportedExtensions = new Boolean[
knownExtensions.keySet().stream().mapToInt(Enum::ordinal).max().orElse(-1) + 1];
emptyExtensionsBitfield = new long[(supportedExtensions.length + 63) / 64];
}
if (VERBOSE) {
System.out.println("JBR API init\n knownExtensions = " + (EXTENSIONS_ENABLED ? knownExtensions.keySet() : "DISABLED"));
System.out.println("JBR API init\nKNOWN_SERVICES = " + knownServices + "\nKNOWN_PROXIES = " + knownProxies);
}
}
public static MethodHandle bindDynamic(Lookup caller, String name, MethodType type) {
if (!caller.hasFullPrivilegeAccess()) throw new Error("Caller lookup must have full privilege access"); // Authenticity check.
return dynamicCallTargets.remove(new DynamicCallTargetKey(caller.lookupClass(), name, type.descriptorString())).get().asType(type);
}
/**
* @return JBR API version supported by current implementation.
*/
@Provides("JBR.ServiceApi")
public static String getImplVersion() {
return proxyRepository.getVersion();
}
/**
* @param extension extension name
* @return true if all methods belonging to given extension are supported
* @apiNote this method is a part of internal {@link com.jetbrains.JBR.ServiceApi}
* service, but is not directly exposed to user.
*/
@Provides("JBR.ServiceApi")
public static boolean isExtensionSupported(Enum<?> extension) {
if (!EXTENSIONS_ENABLED) return false;
int i = extension.ordinal();
if (supportedExtensions[i] == null) {
synchronized (JBRApi.class) {
if (supportedExtensions[i] == null) {
boolean result = true;
for (Class<?> c : knownExtensions.get(extension)) {
result &= proxyRepository.getProxy(c, null).isExtensionSupported(extension);
}
supportedExtensions[i] = result;
}
}
}
return supportedExtensions[i];
}
/**
* @return fully supported service implementation for the given interface with specified extensions, or null
* @apiNote this method is a part of internal {@link com.jetbrains.JBR.ServiceApi}
* service, but is not directly exposed to user.
*/
@Provides("JBR.ServiceApi")
public static <T> T getService(Class<T> interFace, Enum<?>... extensions) {
if (!EXTENSIONS_ENABLED) return getService(interFace);
long[] bitfield = new long[emptyExtensionsBitfield.length];
for (Enum<?> e : extensions) {
if (isExtensionSupported(e)) {
int i = e.ordinal() / 64;
int j = e.ordinal() % 64;
bitfield[i] |= 1L << j;
} else {
if (VERBOSE) {
Utils.log(Utils.BEFORE_JBR, System.err, "Warning: Extension not supported: " + e.name());
}
return null;
}
}
return getService(interFace, bitfield, true);
}
/**
* @return fully supported service implementation for the given interface, or null
* @apiNote this method is a part of internal {@link com.jetbrains.JBR.ServiceApi}
* service, but is not directly exposed to user.
*/
@Provides("JBR.ServiceApi")
public static <T> T getService(Class<T> interFace) {
return getService(interFace, emptyExtensionsBitfield, true);
}
public static <T> T getInternalService(Class<T> interFace) {
return getService(interFace, emptyExtensionsBitfield, false);
Proxy<T> p = getProxy(interFace);
return p != null && p.isSupported() ? p.getInstance() : null;
}
/**
* @return proxy for the given interface, or {@code null}
*/
@SuppressWarnings("unchecked")
private static <T> T getService(Class<T> interFace, long[] extensions, boolean publicService) {
Proxy p = proxyRepository.getProxy(interFace, null);
if ((p.getFlags() & Proxy.SERVICE) == 0 || (publicService && (p.getFlags() & Proxy.INTERNAL) != 0)) {
Utils.log(Utils.BEFORE_JBR, System.err, "Warning: Not allowed as a service: " + interFace.getCanonicalName());
return null;
}
if (!p.init()) {
Utils.log(Utils.BEFORE_JBR, System.err, "Warning: Service not supported: " + interFace.getCanonicalName());
return null;
}
try {
MethodHandle constructor = p.getConstructor();
return (T) (EXTENSIONS_ENABLED ? constructor.invoke(extensions) : constructor.invoke());
} catch (com.jetbrains.exported.JBRApi.ServiceNotAvailableException | NullPointerException e) {
if (VERBOSE) {
synchronized (System.err) {
Utils.log(Utils.BEFORE_JBR, System.err, "Warning: Service not available: " + interFace.getCanonicalName());
System.err.print("Caused by: ");
e.printStackTrace(System.err);
static <T> Proxy<T> getProxy(Class<T> interFace) {
return (Proxy<T>) proxyByInterface.computeIfAbsent(interFace, i -> {
RegisteredProxyInfo info = registeredProxyInfoByInterfaceName.get(i.getName());
if (info == null) return null;
ProxyInfo resolved = ProxyInfo.resolve(info);
if (resolved == null) {
if (VERBOSE) {
System.err.println("Couldn't resolve proxy info: " + i.getName());
}
}
} catch (Throwable e) {
throw new RuntimeException(e);
return null;
} else return new Proxy<>(resolved);
});
}
/**
* @return true if given class represents a proxy interface. Even if {@code jetbrains.api}
* introduces new interfaces JBR is not aware of, these interfaces would still be detected
* by this method.
*/
static boolean isKnownProxyInterface(Class<?> clazz) {
String name = clazz.getName();
return registeredProxyInfoByInterfaceName.containsKey(name) ||
knownServices.contains(name) || knownProxies.contains(name);
}
/**
* Reverse lookup by proxy target type name.
* @return user-side interface for given implementation target type name.
*/
static Class<?> getProxyInterfaceByTargetName(String targetName) {
RegisteredProxyInfo info = registeredProxyInfoByTargetName.get(targetName);
if (info == null) return null;
try {
return Class.forName(info.interfaceName(), true,
(info.type().isPublicApi() ? outerLookup : info.apiModule()).lookupClass().getClassLoader());
} catch (ClassNotFoundException e) {
if (VERBOSE) e.printStackTrace();
return null;
}
return null;
}
public static InternalServiceBuilder internalServiceBuilder(Lookup interFace, String... targets) {
return new InternalServiceBuilder(new RegisteredProxyInfo(
interFace, interFace.lookupClass().getName(), targets, ProxyInfo.Type.INTERNAL_SERVICE, new ArrayList<>()));
}
public static class InternalServiceBuilder {
private final RegisteredProxyInfo info;
private InternalServiceBuilder(RegisteredProxyInfo info) {
this.info = info;
}
public InternalServiceBuilder withStatic(String interfaceMethodName, String methodName, String... classes) {
info.staticMethods().add(
new RegisteredProxyInfo.StaticMethodMapping(interfaceMethodName, methodName, classes));
return this;
}
@SuppressWarnings("removal")
public Object build() {
return AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
ProxyInfo info = ProxyInfo.resolve(this.info);
if (info == null) return null;
ProxyGenerator generator = new ProxyGenerator(info);
if (!generator.areAllMethodsImplemented()) return null;
generator.defineClasses();
MethodHandle constructor = generator.findConstructor();
generator.init();
try {
return constructor.invoke();
} catch (Throwable e) {
throw new RuntimeException(e);
}
});
}
}
/**
* Called by {@linkplain com.jetbrains.bootstrap.JBRApiBootstrap#MODULES registry classes}
* to register a new mapping for corresponding modules.
*/
public static ModuleRegistry registerModule(Lookup lookup, BiFunction<String, Module, Module> addExports) {
addExports.apply(lookup.lookupClass().getPackageName(), outerLookup.lookupClass().getModule());
return new ModuleRegistry(lookup);
}
public static class ModuleRegistry {
private final Lookup lookup;
private RegisteredProxyInfo lastProxy;
private ModuleRegistry(Lookup lookup) {
this.lookup = lookup;
}
private ModuleRegistry addProxy(ProxyInfo.Type type, String interfaceName, String... targets) {
lastProxy = new RegisteredProxyInfo(lookup, interfaceName, targets, type, new ArrayList<>());
registeredProxyInfoByInterfaceName.put(interfaceName, lastProxy);
for (String target : targets) {
registeredProxyInfoByTargetName.put(target, lastProxy);
}
if (targets.length == 1) {
validate2WayMapping(lastProxy, registeredProxyInfoByInterfaceName.get(targets[0]));
validate2WayMapping(lastProxy, registeredProxyInfoByTargetName.get(interfaceName));
}
return this;
}
private static void validate2WayMapping(RegisteredProxyInfo p, RegisteredProxyInfo reverse) {
if (reverse != null &&
(!p.interfaceName().equals(reverse.targets()[0]) || !p.targets()[0].equals(reverse.interfaceName()))) {
throw new IllegalArgumentException("Invalid 2-way proxy mapping: " +
p.interfaceName() + " -> " + p.targets()[0] + " & " +
reverse.interfaceName() + " -> " + reverse.targets()[0]);
}
}
/**
* Register new {@linkplain ProxyInfo.Type#PROXY proxy} mapping.
* <p>
* When {@code target} object is passed from JBR to client through service or any other proxy,
* it's converted to corresponding {@code interFace} type by creating a proxy object
* that implements {@code interFace} and delegates method calls to {@code target}.
* @param interFace interface name in {@link com.jetbrains.JBR jetbrains.api} module.
* @param targets corresponding class/interface names in current JBR module, first found will be used. This must not be empty.
* @apiNote class name example: {@code pac.ka.ge.Outer$Inner}
*/
public ModuleRegistry proxy(String interFace, String... targets) {
if (targets.length == 0) throw new IllegalArgumentException("Proxy must have at least one target");
return addProxy(ProxyInfo.Type.PROXY, interFace, targets);
}
/**
* Register new {@linkplain ProxyInfo.Type#SERVICE service} mapping.
* <p>
* Service is a singleton, which may be accessed by client using {@link com.jetbrains.JBR} class.
* @param interFace interface name in {@link com.jetbrains.JBR jetbrains.api} module.
* @param targets corresponding implementation class names in current JBR module, first found will be used.
* @apiNote class name example: {@code pac.ka.ge.Outer$Inner}
*/
public ModuleRegistry service(String interFace, String... targets) {
return addProxy(ProxyInfo.Type.SERVICE, interFace, targets);
}
/**
* Register new {@linkplain ProxyInfo.Type#CLIENT_PROXY client proxy} mapping.
* This mapping type allows implementation of callbacks.
* <p>
* When {@code target} object is passed from client to JBR through service or any other proxy,
* it's converted to corresponding {@code interFace} type by creating a proxy object
* that implements {@code interFace} and delegates method calls to {@code target}.
* @param interFace interface name in current JBR module.
* @param target corresponding class/interface name in {@link com.jetbrains.JBR jetbrains.api} module.
* @apiNote class name example: {@code pac.ka.ge.Outer$Inner}
*/
public ModuleRegistry clientProxy(String interFace, String target) {
Objects.requireNonNull(target);
return addProxy(ProxyInfo.Type.CLIENT_PROXY, interFace, target);
}
/**
* Register new 2-way mapping.
* It creates both {@linkplain ProxyInfo.Type#PROXY proxy} and
* {@linkplain ProxyInfo.Type#CLIENT_PROXY client proxy} between given interfaces.
* <p>
* It links together two given interfaces and allows passing such objects back and forth
* between JBR and {@link com.jetbrains.JBR jetbrains.api} module through services and other proxy methods.
* @param apiInterface interface name in {@link com.jetbrains.JBR jetbrains.api} module.
* @param jbrInterface interface name in current JBR module.
* @apiNote class name example: {@code pac.ka.ge.Outer$Inner}
*/
public ModuleRegistry twoWayProxy(String apiInterface, String jbrInterface) {
clientProxy(jbrInterface, apiInterface);
proxy(apiInterface, jbrInterface);
return this;
}
/**
* Delegate "{@code interfaceMethodName}" method calls to first found static "{@code methodName}" in "{@code classes}".
*/
public ModuleRegistry withStatic(String interfaceMethodName, String methodName, String... classes) {
lastProxy.staticMethods().add(
new RegisteredProxyInfo.StaticMethodMapping(interfaceMethodName, methodName, classes));
return this;
}
}
/**
* Thrown by service implementations indicating that the service is not available for some reason
*/
public static class ServiceNotAvailableException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
public ServiceNotAvailableException() { super(); }
public ServiceNotAvailableException(String message) { super(message); }
public ServiceNotAvailableException(String message, Throwable cause) { super(message, cause); }
public ServiceNotAvailableException(Throwable cause) { super(cause); }
}
}

View File

@@ -1,433 +0,0 @@
/*
* Copyright 2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.internal;
import jdk.internal.org.objectweb.asm.Label;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Stream;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
import static jdk.internal.org.objectweb.asm.Type.getInternalName;
/**
* Mapping defines conversion of parameters and return types between source and destination method.
*/
abstract class Mapping {
static class Query {
boolean valid = true;
boolean needsExtensions = false;
}
final Class<?> from, to;
private Mapping(Class<?> from, Class<?> to) {
this.from = from;
this.to = to;
}
void convert(AccessContext.Method context) {
MethodVisitor m = context.writer;
Label skipConvert = new Label();
m.visitInsn(DUP);
m.visitJumpInsn(IFNULL, skipConvert);
convertNonNull(context);
m.visitLabel(skipConvert);
}
abstract void convertNonNull(AccessContext.Method context);
abstract Mapping inverse();
void query(Query q) {}
@Override
public abstract boolean equals(Object obj);
@Override
public abstract int hashCode();
@Override
public abstract String toString();
static class Identity extends Mapping {
private Identity(Class<?> c) {
super(c, c);
}
@Override
void convert(AccessContext.Method context) {}
@Override
void convertNonNull(AccessContext.Method context) {}
@Override
Mapping inverse() { return this; }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Identity i = (Identity) o;
return from.equals(i.from);
}
@Override
public int hashCode() { return from.hashCode(); }
@Override
public String toString() { return from.getName(); }
}
static class Invalid extends Identity {
private Invalid(Class<?> c) {
super(c);
}
@Override
void query(Query q) { q.valid = false; }
@Override
public String toString() { return "INVALID(" + from.getName() + ")"; }
}
static abstract class Nesting extends Mapping {
final Mapping component;
Nesting(Class<?> from, Class<?> to, Mapping component) {
super(from, to);
this.component = component;
}
@Override
void query(Query q) { component.query(q); }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Nesting nesting = (Nesting) o;
if (!Objects.equals(from, nesting.from)) return false;
if (!Objects.equals(to, nesting.to)) return false;
return component.equals(nesting.component);
}
@Override
public int hashCode() {
int result = from != null ? from.hashCode() : 0;
result = 31 * result + (to != null ? to.hashCode() : 0);
result = 31 * result + component.hashCode();
return result;
}
}
static class Array extends Nesting {
private Array(Mapping component) {
super(component.from.arrayType(), component.to.arrayType(), component);
}
static Mapping wrap(Mapping m) {
if (m instanceof Identity) return new Identity(m.from.arrayType());
else return new Array(m);
}
@Override
void convertNonNull(AccessContext.Method context) {
final int TEMP_COUNTER_SLOT = 1; // Warning! We overwrite 1st local slot.
MethodVisitor m = context.writer;
Label loopStart = new Label(), loopEnd = new Label();
// Stack: fromArray -> toArray, fromArray, i=length
m.visitInsn(DUP);
m.visitInsn(ARRAYLENGTH);
if (context.access().canAccess(to)) m.visitTypeInsn(ANEWARRAY, getInternalName(to));
else context.invokeDynamic(MethodHandles.arrayConstructor(to));
m.visitInsn(SWAP);
m.visitInsn(DUP);
m.visitInsn(ARRAYLENGTH);
// Check loop conditions
m.visitLabel(loopStart);
m.visitInsn(DUP);
m.visitJumpInsn(IFLE, loopEnd);
// Stack: toArray, fromArray, i -> toArray, fromArray, i, toArray, i, from
m.visitVarInsn(ISTORE, TEMP_COUNTER_SLOT);
m.visitInsn(DUP2);
m.visitIincInsn(TEMP_COUNTER_SLOT, -1);
m.visitVarInsn(ILOAD, TEMP_COUNTER_SLOT);
m.visitInsn(DUP_X2);
m.visitInsn(DUP_X1);
m.visitInsn(AALOAD);
// Stack from -> to
component.convert(context);
// Stack: toArray, fromArray, i, toArray, i, to -> toArray, fromArray, i
m.visitInsn(AASTORE);
m.visitJumpInsn(GOTO, loopStart);
m.visitLabel(loopEnd);
// Stack: toArray, fromArray, i -> toArray
m.visitInsn(POP2);
}
@Override
Mapping inverse() { return new Array(component.inverse()); }
@Override
public String toString() { return "[" + component + "]"; }
}
private static abstract class ProxyConversion extends Mapping {
final Proxy fromProxy, toProxy;
private ProxyConversion(Class<?> from, Class<?> to, Proxy fromProxy, Proxy toProxy) {
super(from, to);
this.fromProxy = fromProxy;
this.toProxy = toProxy;
}
void wrapNonNull(AccessContext.Method context) {
context.addDependency(toProxy);
MethodType mt;
if (JBRApi.EXTENSIONS_ENABLED) {
context.writer.visitVarInsn(ALOAD, 0);
mt = MethodType.methodType(to, from, long[].class);
} else {
mt = MethodType.methodType(to, from);
}
context.invokeDynamic(mt, toProxy::getConstructor);
}
void extractNonNull(AccessContext.Method context) {
context.addDependency(fromProxy);
context.writer.visitMethodInsn(INVOKEINTERFACE,
"com/jetbrains/exported/JBRApiSupport$Proxy", "$getProxyTarget", "()Ljava/lang/Object;", true);
}
void cast(AccessContext.Method context) {
if (context.access().canAccess(to)) context.writer.visitTypeInsn(CHECKCAST, getInternalName(to));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ProxyConversion i = (ProxyConversion) o;
return from.equals(i.from) && to.equals(i.to) &&
Objects.equals(fromProxy, i.fromProxy) && Objects.equals(toProxy, i.toProxy);
}
@Override
public int hashCode() {
int result = from.hashCode();
result = 31 * result + to.hashCode();
result = 31 * result + Objects.hashCode(fromProxy);
result = 31 * result + Objects.hashCode(toProxy);
return result;
}
}
private static class Wrap extends ProxyConversion {
private Wrap(Class<?> from, Class<?> to, Proxy proxy) {
super(from, to, null, proxy);
}
@Override
void convertNonNull(AccessContext.Method context) { wrapNonNull(context); }
@Override
Mapping inverse() { return new Extract(to, from, toProxy); }
@Override
void query(Query q) { q.needsExtensions = JBRApi.EXTENSIONS_ENABLED; }
@Override
public String toString() { return from.getName() + " --wrap-> " + to.getName(); }
}
private static class Extract extends ProxyConversion {
private Extract(Class<?> from, Class<?> to, Proxy proxy) {
super(from, to, proxy, null);
}
@Override
void convert(AccessContext.Method context) {
super.convert(context);
cast(context); // Explicitly cast to result type after non-null branch
}
@Override
void convertNonNull(AccessContext.Method context) { extractNonNull(context); }
@Override
Mapping inverse() { return new Wrap(to, from, fromProxy); }
@Override
public String toString() { return from.getName() + " --extract-> " + to.getName(); }
}
private static class Dynamic2Way extends ProxyConversion {
private Dynamic2Way(Class<?> from, Class<?> to, Proxy fromProxy, Proxy toProxy) {
super(from, to, fromProxy, toProxy);
}
@Override
void convert(AccessContext.Method context) {
Label elseBranch = new Label(), afterBranch = new Label();
MethodVisitor m = context.writer;
m.visitInsn(DUP);
m.visitJumpInsn(IFNULL, afterBranch);
m.visitInsn(DUP);
m.visitTypeInsn(INSTANCEOF, "com/jetbrains/exported/JBRApiSupport$Proxy");
m.visitJumpInsn(IFEQ, elseBranch);
extractNonNull(context);
m.visitJumpInsn(GOTO, afterBranch);
m.visitLabel(elseBranch);
wrapNonNull(context);
m.visitLabel(afterBranch);
cast(context); // Explicitly cast to result type after non-null branch
}
@Override
void convertNonNull(AccessContext.Method context) {}
@Override
Mapping inverse() { return new Dynamic2Way(to, from, toProxy, fromProxy); }
@Override
void query(Query q) { q.needsExtensions = JBRApi.EXTENSIONS_ENABLED; }
@Override
public String toString() { return from.getName() + " --2way-> " + to.getName(); }
}
private static class CustomOptional extends Nesting {
private CustomOptional(Mapping component) {
super(Optional.class, Optional.class, component);
}
static Mapping wrap(Mapping m) {
if (m instanceof Identity) return new Identity(Optional.class);
else return new CustomOptional(m);
}
@Override
void convertNonNull(AccessContext.Method context) {
MethodVisitor m = context.writer;
m.visitInsn(ACONST_NULL);
m.visitMethodInsn(INVOKEVIRTUAL, "java/util/Optional", "orElse", "(Ljava/lang/Object;)Ljava/lang/Object;", false);
component.convert(context);
m.visitMethodInsn(INVOKESTATIC, "java/util/Optional", "ofNullable", "(Ljava/lang/Object;)Ljava/util/Optional;", false);
}
@Override
Mapping inverse() { return new CustomOptional(component.inverse()); }
@Override
public String toString() { return "Optional<" + component + ">"; }
}
record Method(MethodType type, Mapping returnMapping, Mapping[] parameterMapping, Query query) {
@Override
public String toString() {
return returnMapping + "(" + Arrays.toString(parameterMapping) + ")";
}
}
static class Context {
private final ProxyRepository proxyRepository;
private final Map<TypeVariable<?>, Mapping> tvMappings = new HashMap<>();
Context(ProxyRepository proxyRepository) {
this.proxyRepository = proxyRepository;
}
void initTypeParameters(Class<?> type, Stream<Mapping> typeParameters) {
if (type == null) return;
if (typeParameters != null) {
TypeVariable<?>[] tvs = type.getTypeParameters();
Iterator<Mapping> tpIterator = typeParameters.iterator();
for (int i = 0;; i++) {
if ((i < tvs.length) ^ tpIterator.hasNext()) throw new RuntimeException("Number of type parameters doesn't match");
if (i >= tvs.length) break;
tvMappings.put(tvs[i], tpIterator.next());
}
}
initTypeParameters(type.getGenericSuperclass());
for (Type t : type.getGenericInterfaces()) initTypeParameters(t);
}
void initTypeParameters(Type supertype) {
if (supertype instanceof ParameterizedType type) {
initTypeParameters((Class<?>) type.getRawType(),
Stream.of(type.getActualTypeArguments()).map(this::getMapping));
} else if (supertype instanceof Class<?> c) {
initTypeParameters(c, null);
} else if (supertype != null) {
throw new RuntimeException("Unknown supertype kind: " + supertype.getClass());
}
}
Method getMapping(java.lang.reflect.Method interfaceMethod) {
Type[] params = interfaceMethod.getGenericParameterTypes();
List<Class<?>> ptypes = new ArrayList<>(params.length);
Mapping[] paramMappings = new Mapping[params.length];
for (int i = 0; i < params.length; i++) {
paramMappings[i] = getMapping(params[i]);
ptypes.add(paramMappings[i].to);
}
Mapping returnMapping = getMapping(interfaceMethod.getGenericReturnType()).inverse();
return new Method(MethodType.methodType(returnMapping.from, ptypes), returnMapping, paramMappings,
query(returnMapping, paramMappings));
}
private Mapping getMapping(Type userType) {
if (userType instanceof Class<?> t) {
return getMapping(t, null);
} else if (userType instanceof GenericArrayType t) {
return Array.wrap(getMapping(t.getGenericComponentType()));
} else if (userType instanceof ParameterizedType t) {
return getMapping(t);
} else if (userType instanceof TypeVariable<?> t) {
Mapping tvMapping = tvMappings.get(t);
if (tvMapping != null) return tvMapping;
Type[] bounds = t.getBounds();
return getMapping(bounds.length > 0 ? bounds[0] : Object.class);
} else if (userType instanceof WildcardType t) {
Type[] bounds = t.getUpperBounds();
return getMapping(bounds.length > 0 ? bounds[0] : Object.class);
} else {
throw new RuntimeException("Unknown type kind: " + userType.getClass());
}
}
private Mapping getMapping(ParameterizedType userType) {
Type[] actual = userType.getActualTypeArguments();
Mapping[] specialization = null;
for (int i = 0; i < actual.length; i++) {
Mapping m = getMapping(actual[i]);
if (m instanceof Identity) continue;
if (specialization == null) specialization = new Mapping[actual.length];
specialization[i] = m;
}
return getMapping((Class<?>) userType.getRawType(), specialization);
}
private Mapping getMapping(Class<?> userType, Mapping[] specialization) {
if (userType.isArray()) {
return Array.wrap(getMapping(userType.componentType()));
}
if (specialization != null && specialization.length == 1 && specialization[0] != null &&
userType.equals(Optional.class)) {
return CustomOptional.wrap(specialization[0]);
}
Proxy p = proxyRepository.getProxy(userType, specialization);
if (p.supported() != Boolean.FALSE && p.inverse().supported() != Boolean.FALSE &&
(p.getFlags() & Proxy.SERVICE) == 0 && (p.inverse().getFlags() & Proxy.SERVICE) == 0) {
if (p.getInterface() != null && p.inverse().getInterface() != null) {
return new Dynamic2Way(userType, p.inverse().getInterface(), p, p.inverse());
} else if (p.getInterface() != null) {
if (p.getTarget() != null) return new Extract(userType, p.getTarget(), p);
} else if (p.inverse().getInterface() != null) {
return new Wrap(userType, p.inverse().getInterface(), p.inverse());
} else {
return new Identity(userType);
}
}
return new Invalid(userType);
}
private static Query query(Mapping returnMapping, Mapping[] parameterMapping) {
Query q = new Query();
returnMapping.query(q);
for (Mapping p : parameterMapping) p.query(q);
return q;
}
}
}

View File

@@ -26,255 +26,203 @@
package com.jetbrains.internal;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.*;
import static java.lang.invoke.MethodHandles.Lookup;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Proxy is needed to dynamically link JBR API interfaces and implementation at runtime.
* Proxy implements or extends a base interface or class and delegates method calls to
* target object or static methods. Calls may be delegated even to methods inaccessible by base type.
* It implements user-side interfaces and delegates method calls to actual implementation
* code through {@linkplain java.lang.invoke.MethodHandle method handles}.
* <p>
* Mapping between interfaces and implementation code is defined using
* {@link com.jetbrains.exported.JBRApi.Provided} and {@link com.jetbrains.exported.JBRApi.Provides} annotations.
* There are 3 type of proxy objects:
* <ol>
* <li>{@linkplain ProxyInfo.Type#PROXY Proxy} - implements client-side interface from
* {@code jetbrains.api} and delegates calls to JBR-side target object and optionally static methods.</li>
* <li>{@linkplain ProxyInfo.Type#SERVICE Service} - singleton {@linkplain ProxyInfo.Type#PROXY proxy},
* may delegate calls only to static methods, without target object.</li>
* <li>{@linkplain ProxyInfo.Type#CLIENT_PROXY Client proxy} - reverse proxy, implements JBR-side interface
* and delegates calls to client-side target object by interface defined in {@code jetbrains.api}.
* May be used to implement callbacks which are created by client and called by JBR.</li>
* </ol>
* <p>
* When JBR API interface or imeplementation type is used as a method parameter or return type,
* JBR API backend performs conversions to ensure each side receives object or correct type,
* for example, given following types:
* <blockquote><pre>{@code
* interface A {
* B foo(B b);
* }
* interface B {
* void bar();
* }
* class AImpl {
* BImpl foo(BImpl b) {}
* }
* class BImpl {
* void bar() {}
* }
* }</pre></blockquote>
* {@code A#foo(B)} will be mapped to {@code AImpl#foo(BImpl)}.
* Conversion between {@code B} and {@code BImpl} will be done by JBR API backend.
* Here's what generated proxies would look like:
* <blockquote><pre>{@code
* final class AProxy implements A {
* final AImpl target;
* AProxy(AImpl t) {
* target = t;
* }
* @Override
* B foo(B b) {
* BImpl ret = t.foo(b.target);
* return new BProxy(ret);
* }
* }
* final class BProxy implements B {
* final BImpl target;
* BProxy(BImpl t) {
* target = t;
* }
* @Override
* void bar() {
* t.bar();
* }
* }
* }</pre></blockquote>
* <p>
* Method signatures of base type and implementation are validated to ensure that proxy can
* Method signatures of proxy interfaces and implementation are validated to ensure that proxy can
* properly delegate call to the target implementation code. If there's no implementation found for some
* interface methods, corresponding proxy is considered unsupported. Proxy is also considered unsupported
* if any proxy used by it is unsupported.
* if any proxy used by it is unsupported, more about it at {@link ProxyDependencyManager}.
* <p>
* Extensions are an exception to this rule. Methods of base type may be marked as extension methods,
* which makes them optional for support checks. Moreover, extension must be explicitly enabled for a
* proxy instance to enable usage of corresponding methods.
* Mapping between interfaces and implementation code is defined in
* {@linkplain com.jetbrains.bootstrap.JBRApiBootstrap#MODULES registry classes}.
* @param <INTERFACE> interface type for this proxy.
*/
class Proxy {
/**
* @see Proxy.Info#flags
*/
static final int
INTERNAL = 1,
SERVICE = 2;
class Proxy<INTERFACE> {
private final ProxyInfo info;
private final Proxy inverse;
private final Class<?> interFace, target;
private final int flags;
private volatile ProxyGenerator generator;
private volatile Boolean allMethodsImplemented;
private volatile Boolean supported;
private volatile Set<Proxy> directDependencies = Set.of();
private volatile Set<Enum<?>> supportedExtensions = Set.of();
private volatile Class<?> proxyClass;
private volatile MethodHandle constructor;
private volatile MethodHandle targetExtractor;
/**
* Creates empty proxy.
*/
static Proxy empty(Boolean supported) {
return new Proxy(supported);
private volatile boolean instanceInitialized;
private volatile INTERFACE instance;
Proxy(ProxyInfo info) {
this.info = info;
}
/**
* Creates a new proxy (and possibly an inverse one) from {@link Info}.
* @return {@link ProxyInfo} structure of this proxy
*/
static Proxy create(ProxyRepository repository,
Info info, Mapping[] specialization,
Info inverseInfo, Mapping[] inverseSpecialization) {
return new Proxy(repository, info, specialization, null, inverseInfo, inverseSpecialization);
ProxyInfo getInfo() {
return info;
}
private Proxy(Boolean supported) {
interFace = target = null;
flags = 0;
inverse = this;
this.supported = supported;
private synchronized void initGenerator() {
if (generator != null) return;
generator = new ProxyGenerator(info);
allMethodsImplemented = generator.areAllMethodsImplemented();
}
private Proxy(ProxyRepository repository, Info info, Mapping[] specialization,
Proxy inverseProxy, Info inverseInfo, Mapping[] inverseSpecialization) {
if (info != null) {
interFace = info.interfaceLookup.lookupClass();
target = info.targetLookup == null ? null : info.targetLookup.lookupClass();
flags = info.flags;
generator = new ProxyGenerator(repository, info, specialization);
} else {
interFace = target = null;
flags = 0;
/**
* Checks if implementation is found for all abstract interface methods of this proxy.
*/
boolean areAllMethodsImplemented() {
if (allMethodsImplemented != null) return allMethodsImplemented;
synchronized (this) {
if (allMethodsImplemented == null) initGenerator();
return allMethodsImplemented;
}
inverse = inverseProxy == null ? new Proxy(repository, inverseInfo, specialization, this, null, null) : inverseProxy;
if (inverse.getInterface() != null) directDependencies = Set.of(inverse);
}
/**
* @return inverse proxy
* Checks if all methods are {@linkplain #areAllMethodsImplemented() implemented}
* for this proxy and all proxies it {@linkplain ProxyDependencyManager uses}.
*/
Proxy inverse() {
return inverse;
}
/**
* @return interface class
*/
Class<?> getInterface() {
return interFace;
}
/**
* @return target class
*/
Class<?> getTarget() {
return target;
}
/**
* @return flags
*/
int getFlags() {
return flags;
}
/**
* Checks if all methods are implemented for this proxy and all proxies it uses.
* {@code null} means not known yet.
*/
Boolean supported() {
return supported;
}
private synchronized boolean generate() {
boolean isSupported() {
if (supported != null) return supported;
if (generator == null) return false;
Set<Proxy> deps = new HashSet<>(directDependencies);
supported = generator.generate();
for (Map.Entry<Proxy, Boolean> e : generator.getDependencies().entrySet()) {
if (e.getKey().generate()) deps.add(e.getKey());
else if (e.getValue()) {
supported = false;
break;
synchronized (this) {
if (supported == null) {
Set<Class<?>> dependencies = ProxyDependencyManager.getProxyDependencies(info.interFace);
for (Class<?> d : dependencies) {
Proxy<?> p = JBRApi.getProxy(d);
if (p == null || !p.areAllMethodsImplemented()) {
supported = false;
return false;
}
}
supported = true;
}
return supported;
}
if (supported) {
directDependencies = deps;
supportedExtensions = generator.getSupportedExtensions();
} else generator = null; // Release for gc
return supported;
}
private synchronized void defineClasses() {
if (constructor != null) return;
initGenerator();
generator.defineClasses();
proxyClass = generator.getProxyClass();
constructor = generator.findConstructor();
targetExtractor = generator.findTargetExtractor();
}
/**
* Checks if specified extension is implemented by this proxy.
* Implicitly runs bytecode generation.
* @return generated proxy class
*/
boolean isExtensionSupported(Enum<?> extension) {
if (supported == null) generate();
return supportedExtensions.contains(extension);
}
private synchronized boolean define() {
if (constructor != null) return true;
if (!generate()) return false;
constructor = generator.define((flags & SERVICE) != 0);
if (constructor == null) {
supported = false;
return false;
Class<?> getProxyClass() {
if (proxyClass != null) return proxyClass;
synchronized (this) {
if (proxyClass == null) defineClasses();
return proxyClass;
}
for (Proxy p : directDependencies) {
if (!p.define()) return false;
}
return true;
}
synchronized boolean init() {
if (!define()) return false;
if (generator != null) {
ProxyGenerator gen = generator;
generator = null;
gen.init();
for (Proxy p : directDependencies) p.init();
}
return supported;
}
/**
* @return method handle for the constructor of this proxy.
* First parameter is target object to which it would delegate method calls.
* Second parameter is extensions bitfield (if extensions are enabled).
* <ul>
* <li>For {@linkplain ProxyInfo.Type#SERVICE services}, constructor is no-arg.</li>
* <li>For non-{@linkplain ProxyInfo.Type#SERVICE services}, constructor is single-arg,
* expecting target object to which it would delegate method calls.</li>
* </ul>
*/
MethodHandle getConstructor() throws NullPointerException {
if (JBRApi.LOG_DEPRECATED && interFace.isAnnotationPresent(Deprecated.class)) {
Utils.log(Utils.BEFORE_BOOTSTRAP_DYNAMIC, System.err,
"Warning: using deprecated JBR API interface " + interFace.getCanonicalName());
MethodHandle getConstructor() {
if (constructor != null) return constructor;
synchronized (this) {
if (constructor == null) defineClasses();
return constructor;
}
return constructor;
}
/**
* Proxy descriptor with all classes and lookup contexts resolved.
* Contains all necessary information to create a {@linkplain Proxy proxy}.
* @return method handle for that extracts target object of the proxy, or null.
*/
static class Info {
private record StaticMethod(String name, MethodType targetType) {}
private final Map<StaticMethod, MethodHandle> staticMethods = new HashMap<>();
final Lookup interfaceLookup;
final Lookup targetLookup;
private final int flags;
Info(Lookup interfaceLookup, Lookup targetLookup, int flags) {
this.interfaceLookup = interfaceLookup;
this.targetLookup = targetLookup;
this.flags = flags;
MethodHandle getTargetExtractor() {
// targetExtractor may be null, so check constructor instead
if (constructor != null) return targetExtractor;
synchronized (this) {
if (constructor == null) defineClasses();
return targetExtractor;
}
}
void addStaticMethod(String name, MethodHandle target) {
staticMethods.put(new StaticMethod(name, target.type()), target);
private synchronized void initClass(Set<Proxy<?>> actualUsages) {
defineClasses();
if (generator != null) {
actualUsages.addAll(generator.getDirectProxyDependencies());
generator.init();
generator = null;
}
}
private synchronized void initDependencyGraph() {
defineClasses();
if (generator == null) return;
Set<Class<?>> dependencyClasses = ProxyDependencyManager.getProxyDependencies(info.interFace);
Set<Proxy<?>> dependencies = new HashSet<>();
Set<Proxy<?>> actualUsages = new HashSet<>();
for (Class<?> d : dependencyClasses) {
Proxy<?> p = JBRApi.getProxy(d);
if (p != null) {
dependencies.add(p);
p.initClass(actualUsages);
}
}
actualUsages.removeAll(dependencies);
if (!actualUsages.isEmpty()) {
// Should never happen, this is a sign of broken dependency search
throw new RuntimeException("Some proxies are not in dependencies of " + info.interFace.getName() +
", but are actually used by it: " +
actualUsages.stream().map(p -> p.info.interFace.getName()).collect(Collectors.joining(", ")));
}
}
MethodHandle getStaticMethod(String name, MethodType targetType) {
return staticMethods.get(new StaticMethod(name, targetType));
/**
* @return instance for this {@linkplain ProxyInfo.Type#SERVICE service},
* returns {@code null} for other proxy types.
*/
@SuppressWarnings("unchecked")
INTERFACE getInstance() {
if (instanceInitialized) return instance;
if (info.type != ProxyInfo.Type.SERVICE) return null;
synchronized (this) {
if (instance == null) {
initDependencyGraph();
try {
instance = (INTERFACE) getConstructor().invoke();
} catch (JBRApi.ServiceNotAvailableException e) {
if (JBRApi.VERBOSE) {
System.err.println("Service not available: " + info.interFace.getName());
e.printStackTrace();
}
} catch (Throwable e) {
throw new RuntimeException(e);
} finally {
instanceInitialized = true;
}
}
return instance;
}
}
}

View File

@@ -0,0 +1,210 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.internal;
import java.lang.reflect.*;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
/**
* This class collects {@linkplain Proxy proxy} dependencies.
* <p>
* Dependencies of a class {@code C} are other classes that are
* used by {@code C} (i.e. supertypes, classes that appear in method
* parameters, return types) and all their dependencies. Any class
* is also considered a dependency of itself.
* <p>
* Dependencies allow JBR to validate whole set of interfaces for
* a particular feature instead of treating them as separate entities.
* <h2>Example</h2>
* Suppose we implemented some feature and added some API for it:
* <blockquote><pre>{@code
* interface SomeFeature {
* SomeOtherObject createSomeObject(int magicNumber);
* }
* interface SomeOtherObject {
* int getMagicNumber();
* }
* }</pre></blockquote>
* And then used it:
* <blockquote><pre>{@code
* if (JBR.isSomeFeatureSupported()) {
* SomeOtherObject object = JBR.getSomeFeature().createSomeObject(123);
* int magic = object.getMagicNumber();
* }
* }</pre></blockquote>
* Suppose JBR was able to find implementation for {@code SomeFeature.createSomeObject()},
* but not for {@code SomeOtherObject.getMagicNumber()}. So {@code JBR.getSomeFeature()}
* would succeed and return service instance, but {@code createSomeObject()} would fail,
* because JBR wasn't able to find implementation for {@code SomeOtherObject.getMagicNumber()}
* and therefore couldn't create proxy for {@code SomeOtherObject} class.
* <p>
* To avoid such issues, not only proxy interface itself, but all proxies that are accessible
* from current proxy interface must have proper implementation.
*/
class ProxyDependencyManager {
private static final ConcurrentMap<Class<?>, Set<Class<?>>> cache = new ConcurrentHashMap<>();
/**
* @return all proxy interfaces that are used (directly or indirectly) by given interface, including itself.
*/
static Set<Class<?>> getProxyDependencies(Class<?> interFace) {
Set<Class<?>> dependencies = cache.get(interFace);
if (dependencies != null) return dependencies;
step(null, interFace);
return cache.get(interFace);
}
/**
* Collect dependencies for given class and store them into cache.
*/
private static void step(Node parent, Class<?> clazz) {
if (!clazz.getPackageName().startsWith("com.jetbrains") && !JBRApi.isKnownProxyInterface(clazz)) return;
if (parent != null && parent.findAndMergeCycle(clazz) != null) {
return;
}
Set<Class<?>> cachedDependencies = cache.get(clazz);
if (cachedDependencies != null) {
if (parent != null) parent.cycle.dependencies.addAll(cachedDependencies);
return;
}
Node node = new Node(parent, clazz);
ClassUsagesFinder.visitUsages(clazz, c -> step(node, c));
Class<?> correspondingProxyInterface = JBRApi.getProxyInterfaceByTargetName(clazz.getName());
if (correspondingProxyInterface != null) {
step(node, correspondingProxyInterface);
}
if (parent != null && parent.cycle != node.cycle) {
parent.cycle.dependencies.addAll(node.cycle.dependencies);
}
if (node.cycle.origin.equals(clazz)) {
// Put collected dependencies into cache only when we exit from the cycle
// Otherwise cache will contain incomplete data
for (Class<?> c : node.cycle.members) {
cache.put(c, node.cycle.dependencies);
if (JBRApi.VERBOSE) {
System.out.println("Found dependencies for " + c.getName() + ": " + node.cycle.dependencies);
}
}
}
}
/**
* Graph node, one per visited class
*/
private static class Node {
private final Node parent;
private final Class<?> clazz;
private Cycle cycle;
private Node(Node parent, Class<?> clazz) {
this.parent = parent;
this.clazz = clazz;
cycle = new Cycle(clazz);
}
/**
* When classes form dependency cycle, they share all their dependencies.
* If cycle was found, merge all found dependencies for nodes that form the cycle.
*/
private Cycle findAndMergeCycle(Class<?> clazz) {
if (this.clazz.equals(clazz)) return cycle;
if (parent == null) return null;
Cycle c = parent.findAndMergeCycle(clazz);
if (c != null && c != cycle) {
c.members.addAll(cycle.members);
c.dependencies.addAll(cycle.dependencies);
cycle = c;
}
return c;
}
}
/**
* Cycle info. For the sake of elegant code, single node
* also forms a cycle with itself as a single member and dependency.
*/
private static class Cycle {
/**
* Origin is the first visited class from that cycle.
*/
private final Class<?> origin;
private final Set<Class<?>> members = new HashSet<>();
private final Set<Class<?>> dependencies = new HashSet<>();
private Cycle(Class<?> origin) {
this.origin = origin;
members.add(origin);
if (JBRApi.isKnownProxyInterface(origin)) {
dependencies.add(origin);
}
}
}
/**
* Utility class that collects direct class usages using reflection
*/
private static class ClassUsagesFinder {
private static void visitUsages(Class<?> c, Consumer<Class<?>> action) {
collect(c.getGenericSuperclass(), action);
for (java.lang.reflect.Type t : c.getGenericInterfaces()) collect(t, action);
for (Field f : c.getDeclaredFields()) collect(f.getGenericType(), action);
for (Method m : c.getDeclaredMethods()) {
collect(m.getGenericParameterTypes(), action);
collect(m.getGenericReturnType(), action);
collect(m.getGenericExceptionTypes(), action);
}
}
private static void collect(java.lang.reflect.Type type, Consumer<Class<?>> action) {
if (type instanceof Class<?> c) {
while (c.isArray()) c = Objects.requireNonNull(c.getComponentType());
if (!c.isPrimitive()) action.accept(c);
} else if (type instanceof TypeVariable<?> v) {
collect(v.getBounds(), action);
} else if (type instanceof WildcardType w) {
collect(w.getUpperBounds(), action);
collect(w.getLowerBounds(), action);
} else if (type instanceof ParameterizedType p) {
collect(p.getActualTypeArguments(), action);
collect(p.getRawType(), action);
collect(p.getOwnerType(), action);
} else if (type instanceof GenericArrayType a) {
collect(a.getGenericComponentType(), action);
}
}
private static void collect(java.lang.reflect.Type[] types, Consumer<Class<?>> action) {
for (java.lang.reflect.Type t : types) collect(t, action);
}
}
}

View File

@@ -29,384 +29,457 @@ import jdk.internal.org.objectweb.asm.*;
import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandleInfo;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.*;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.jetbrains.exported.JBRApi.ServiceNotAvailableException;
import static com.jetbrains.internal.ASMUtils.*;
import static com.jetbrains.internal.JBRApi.EXTENSIONS_ENABLED;
import static java.lang.invoke.MethodHandles.Lookup;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
import static jdk.internal.org.objectweb.asm.Type.getInternalName;
/**
* Generates {@linkplain Proxy proxy} classes.
* This class generates {@linkplain Proxy proxy} classes.
* Each proxy is just a generated class implementing some interface and
* delegating method calls to method handles.
* <p>
* Proxy class always implements/extends a base interface/class. It's defined as a
* {@linkplain MethodHandles.Lookup#defineHiddenClass(byte[], boolean, Lookup.ClassOption...) hidden class}
* sharing the nest with either interface or target implementation class.
* Defining proxy as a nestmate of a target class is preferred,
* as in this case proxy can call implementation methods directly.
* However, this may not be possible if interface is not accessible by target
* (thus making it impossible for proxy to implement it), or if there is no target class at all
* (e.g. a service may delegate all calls exclusively to static methods).
* <p>
* Proxy invokes target methods in two ways: either directly, when accessible
* ({@code invokestatic}, {@code invokevirtual}, {@code invokeinterface}),
* or via {@code invokedynamic} in cases when target method is inaccessible
* (see {@link com.jetbrains.exported.JBRApiSupport#bootstrapDynamic(Lookup, String, MethodType)}).
* @see Proxy
* There are 2 proxy dispatch modes:
* <ul>
* <li>interface -> proxy -> {@linkplain #generateBridge bridge} -> method handle -> implementation code</li>
* <li>interface -> proxy -> method handle -> implementation code</li>
* </ul>
* Generated proxy is always located in the same package with its interface and optional bridge is located in the
* same module with target implementation code. Bridge allows proxy to safely call hidden non-static implementation
* methods and is only needed for {@code jetbrains.api} -> JBR calls. For JBR -> {@code jetbrains.api} calls, proxy can
* invoke method handle directly.
*/
class ProxyGenerator {
private static final String PROXY_INTERFACE_NAME = getInternalName(com.jetbrains.exported.JBRApiSupport.Proxy.class);
private static final String OBJECT_DESCRIPTOR = "Ljava/lang/Object;";
private static final String MH_NAME = "java/lang/invoke/MethodHandle";
private static final String MH_DESCRIPTOR = "Ljava/lang/invoke/MethodHandle;";
private static final String CONVERSION_DESCRIPTOR = "(Ljava/lang/Object;)Ljava/lang/Object;";
/**
* Print warnings about usage of deprecated interfaces and methods to {@link System#err}.
*/
private static final boolean LOG_DEPRECATED = System.getProperty("jetbrains.api.logDeprecated", String.valueOf(JBRApi.VERBOSE)).equalsIgnoreCase("true");
private static final boolean VERIFY_BYTECODE = Boolean.getBoolean("jetbrains.api.verifyBytecode");
private final Proxy.Info info;
private final Class<?> interFace;
private final Lookup proxyGenLookup;
private final Mapping[] specialization;
private final Mapping.Context mappingContext;
private final AccessContext accessContext;
private static final AtomicInteger nameCounter = new AtomicInteger();
private final Class<?> constructorTargetParameterType;
private final String targetDescriptor, proxyName, superclassName;
private final String[] superinterfaceNames;
private final ClassWriter originalProxyWriter;
private final ClassVisitor proxyWriter;
private final Map<Enum<?>, Boolean> supportedExtensions = new HashMap<>();
private boolean supported = true;
private Lookup generatedProxy;
private final ProxyInfo info;
private final boolean generateBridge;
private final String proxyName, bridgeName;
private final ClassWriter originalProxyWriter, originalBridgeWriter;
private final ClassVisitor proxyWriter, bridgeWriter;
private final List<Supplier<MethodHandle>> handles = new ArrayList<>();
private final List<Supplier<Class<?>>> classReferences = new ArrayList<>();
private final Set<Proxy<?>> directProxyDependencies = new HashSet<>();
private final List<Exception> exceptions = new ArrayList<>();
private int bridgeMethodCounter;
private boolean allMethodsImplemented = true;
private Lookup generatedHandlesHolder, generatedProxy;
/**
* Creates new proxy generator from given {@link Proxy.Info},
* Creates new proxy generator from given {@link ProxyInfo},
* looks for abstract interface methods, corresponding implementation methods
* and generates proxy bytecode. However, it doesn't actually load generated
* classes until {@link #defineClasses()} is called.
*/
ProxyGenerator(ProxyRepository proxyRepository, Proxy.Info info, Mapping[] specialization) {
ProxyGenerator(ProxyInfo info) {
if (JBRApi.VERBOSE) {
System.out.println("Generating proxy " + info.interFace.getName());
}
this.info = info;
this.interFace = info.interfaceLookup.lookupClass();
this.specialization = specialization;
generateBridge = info.type.isPublicApi();
int nameId = nameCounter.getAndIncrement();
proxyName = Type.getInternalName(info.interFace) + "$$JBRApiProxy$" + nameId;
bridgeName = generateBridge ? info.apiModule.lookupClass().getPackageName().replace('.', '/') + "/" +
info.interFace.getSimpleName() + "$$JBRApiBridge$" + nameId : null;
// Placing our proxy implementation into the target's nest is preferred, as it gives us direct method calls.
this.proxyGenLookup = info.targetLookup != null && AccessContext.canAccess(info.targetLookup, interFace) ?
info.targetLookup : info.interfaceLookup;
this.mappingContext = new Mapping.Context(proxyRepository);
this.accessContext = new AccessContext(proxyGenLookup);
constructorTargetParameterType = info.targetLookup == null ? null :
accessContext.canAccess(info.targetLookup.lookupClass()) ? info.targetLookup.lookupClass() : Object.class;
targetDescriptor = constructorTargetParameterType == null ? "" : constructorTargetParameterType.descriptorString();
proxyName = proxyGenLookup.lookupClass().getPackageName().replace('.', '/') + "/" + interFace.getSimpleName();
originalProxyWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES) {
originalProxyWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
proxyWriter = VERIFY_BYTECODE ? new CheckClassAdapter(originalProxyWriter, true) : originalProxyWriter;
originalBridgeWriter = generateBridge ? new ClassWriter(ClassWriter.COMPUTE_FRAMES) : null;
if (generateBridge) {
bridgeWriter = VERIFY_BYTECODE ? new CheckClassAdapter(originalBridgeWriter, true) : originalBridgeWriter;
} else bridgeWriter = new ClassVisitor(Opcodes.ASM9) { // Empty visitor
@Override
protected ClassLoader getClassLoader() {
return ProxyGenerator.this.proxyGenLookup.lookupClass().getClassLoader();
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
return new MethodVisitor(api) {};
}
};
proxyWriter = JBRApi.VERIFY_BYTECODE ? new CheckClassAdapter(originalProxyWriter, true) : originalProxyWriter;
if (interFace.isInterface()) {
superclassName = "java/lang/Object";
superinterfaceNames = new String[] {getInternalName(interFace), PROXY_INTERFACE_NAME};
} else {
superclassName = getInternalName(interFace);
superinterfaceNames = new String[] {PROXY_INTERFACE_NAME};
}
proxyWriter.visit(CLASSFILE_VERSION, ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC, proxyName, null,
"java/lang/Object", new String[] {Type.getInternalName(info.interFace)});
bridgeWriter.visit(CLASSFILE_VERSION, ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC | ACC_PUBLIC, bridgeName, null,
"java/lang/Object", null);
generateConstructor();
generateMethods();
proxyWriter.visitEnd();
bridgeWriter.visitEnd();
}
boolean areAllMethodsImplemented() {
return allMethodsImplemented;
}
Set<Proxy<?>> getDirectProxyDependencies() {
return directProxyDependencies;
}
/**
* Direct (non-transitive) only. These are the proxies accessed by current one.
* True for required and false for optional.
*/
Map<Proxy, Boolean> getDependencies() {
return accessContext.dependencies;
}
Set<Enum<?>> getSupportedExtensions() {
return supportedExtensions.entrySet().stream()
.filter(Map.Entry::getValue).map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet());
}
/**
* @return service target instance, if any
*/
private Object createServiceTarget() throws Throwable {
Exception exception = null;
MethodHandle constructor = null;
try {
constructor = info.targetLookup.findStatic(
info.targetLookup.lookupClass(), "create", MethodType.methodType(info.targetLookup.lookupClass()));
} catch (NoSuchMethodException | IllegalAccessException e) {
exception = e;
}
try {
if (constructor == null) constructor = info.targetLookup.findConstructor(
info.targetLookup.lookupClass(), MethodType.methodType(void.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
if (exception == null) exception = e;
else exception.addSuppressed(e);
}
if (constructor == null) throw new ServiceNotAvailableException("No service factory method or constructor found", exception);
return constructor.invoke();
}
/**
* First parameter is target object to which it would delegate method calls (if target exists).
* Second parameter is extensions bitfield (if extensions are enabled).
* @return method handle to constructor of generated proxy class, or null.
*/
private MethodHandle findConstructor() throws NoSuchMethodException, IllegalAccessException {
MethodType constructorType = MethodType.methodType(void.class);
if (info.targetLookup != null) constructorType = constructorType.appendParameterTypes(constructorTargetParameterType);
if (EXTENSIONS_ENABLED) constructorType = constructorType.appendParameterTypes(long[].class);
return generatedProxy.findConstructor(generatedProxy.lookupClass(), constructorType);
}
/**
* Initialize method handles used by generated class via {@code invokedynamic}.
* Insert all method handles and class references into static fields, so that proxy can call implementation methods.
*/
void init() {
for (var t : accessContext.dynamicCallTargets) {
JBRApi.dynamicCallTargets.put(new JBRApi.DynamicCallTargetKey(
generatedProxy.lookupClass(), t.name(), t.descriptor()
), t.futureHandle());
try {
for (int i = 0; i < handles.size(); i++) {
generatedHandlesHolder
.findStaticVarHandle(generatedHandlesHolder.lookupClass(), "h" + i, MethodHandle.class)
.set(handles.get(i).get());
}
for (int i = 0; i < classReferences.size(); i++) {
generatedHandlesHolder
.findStaticVarHandle(generatedHandlesHolder.lookupClass(), "c" + i, Class.class)
.set(classReferences.get(i).get());
}
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
Class<?> getProxyClass() {
return generatedProxy.lookupClass();
}
/**
* Define generated class.
* @return method handle to constructor of generated proxy class, or null.
* @return method handle to constructor of generated proxy class.
* <ul>
* <li>For {@linkplain ProxyInfo.Type#SERVICE services}, constructor is no-arg.</li>
* <li>For non-{@linkplain ProxyInfo.Type#SERVICE services}, constructor is single-arg,
* expecting target object to which it would delegate method calls.</li>
* </ul>
*/
MethodHandle define(boolean service) {
MethodHandle findConstructor() {
try {
Object sericeTarget = service && info.targetLookup != null ? createServiceTarget() : null;
generatedProxy = proxyGenLookup.defineHiddenClass(
originalProxyWriter.toByteArray(), false, Lookup.ClassOption.STRONG, Lookup.ClassOption.NESTMATE);
MethodHandle constructor = findConstructor();
if (sericeTarget != null) constructor = MethodHandles.insertArguments(constructor, 0, sericeTarget);
return constructor;
} catch (ServiceNotAvailableException e) {
if (JBRApi.VERBOSE) e.printStackTrace(System.err);
return null;
} catch (Throwable e) {
if (info.target == null) {
return generatedProxy.findConstructor(generatedProxy.lookupClass(), MethodType.methodType(void.class));
} else {
MethodHandle c = generatedProxy.findConstructor(generatedProxy.lookupClass(),
MethodType.methodType(void.class, Object.class));
if (info.type.isService()) {
try {
return MethodHandles.foldArguments(c, info.target.findConstructor(info.target.lookupClass(),
MethodType.methodType(void.class)).asType(MethodType.methodType(Object.class)));
} catch (NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException("Service implementation must have no-args constructor: " +
info.target.lookupClass(), e);
}
}
return c;
}
} catch (IllegalAccessException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Generate bytecode for class.
* @return true if generated proxy is considered supported
* @return method handle that receives proxy and returns its target, or null
*/
boolean generate() {
if (!supported) return false;
proxyWriter.visit(CLASSFILE_VERSION, ACC_SUPER | ACC_FINAL | ACC_SYNTHETIC, proxyName, null,
superclassName, superinterfaceNames);
if (JBRApi.VERBOSE) {
synchronized (System.out) {
System.out.print("Generating proxy " + interFace.getName());
if (specialization != null) System.out.print(" <" +
Stream.of(specialization).map(t -> t == null ? "?" : t.toString()).collect(Collectors.joining(", ")) + ">");
System.out.println();
}
MethodHandle findTargetExtractor() {
if (info.target == null) return null;
try {
return generatedProxy.findGetter(generatedProxy.lookupClass(), "target", Object.class);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
if (specialization != null) {
mappingContext.initTypeParameters(interFace, Stream.of(specialization));
}
mappingContext.initTypeParameters(interFace);
generateFields();
generateConstructor();
generateTargetGetter();
generateMethods(interFace);
if (interFace.isInterface()) generateMethods(Object.class);
proxyWriter.visitEnd();
return supported;
}
private void generateFields() {
if (info.targetLookup != null) {
proxyWriter.visitField(ACC_PRIVATE | ACC_FINAL, "target", targetDescriptor, null, null);
}
if (EXTENSIONS_ENABLED) {
proxyWriter.visitField(ACC_PRIVATE | ACC_FINAL, "extensions", "[J", null, null);
/**
* Loads generated classes.
*/
void defineClasses() {
try {
Lookup bridge = !generateBridge ? null : MethodHandles.privateLookupIn(
info.apiModule.defineClass(originalBridgeWriter.toByteArray()), info.apiModule);
generatedProxy = info.interFaceLookup.defineHiddenClass(
originalProxyWriter.toByteArray(), true, Lookup.ClassOption.STRONG, Lookup.ClassOption.NESTMATE);
generatedHandlesHolder = generateBridge ? bridge : generatedProxy;
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private void generateConstructor() {
MethodVisitor m = proxyWriter.visitMethod(ACC_PRIVATE, "<init>", "(" + targetDescriptor +
(EXTENSIONS_ENABLED ? "[J" : "") + ")V", null, null);
m.visitCode();
m.visitVarInsn(ALOAD, 0);
if (info.targetLookup != null) {
m.visitInsn(DUP);
m.visitVarInsn(ALOAD, 1);
m.visitFieldInsn(PUTFIELD, proxyName, "target", targetDescriptor);
if (info.target != null) {
proxyWriter.visitField(ACC_PRIVATE | ACC_FINAL, "target", OBJECT_DESCRIPTOR, null, null);
}
if (EXTENSIONS_ENABLED) {
m.visitInsn(DUP);
m.visitVarInsn(ALOAD, info.targetLookup != null ? 2 : 1);
m.visitFieldInsn(PUTFIELD, proxyName, "extensions", "[J");
MethodVisitor p = proxyWriter.visitMethod(ACC_PRIVATE, "<init>", "(" +
(info.target == null ? "" : OBJECT_DESCRIPTOR) + ")V", null, null);
if (LOG_DEPRECATED && info.interFace.isAnnotationPresent(Deprecated.class)) {
logDeprecated(p, "Warning: using deprecated JBR API interface " + info.interFace.getName());
}
m.visitMethodInsn(INVOKESPECIAL, superclassName, "<init>", "()V", false);
m.visitInsn(RETURN);
m.visitMaxs(0, 0);
m.visitEnd();
p.visitCode();
p.visitVarInsn(ALOAD, 0);
if (info.target != null) {
p.visitInsn(DUP);
p.visitVarInsn(ALOAD, 1);
p.visitFieldInsn(PUTFIELD, proxyName, "target", OBJECT_DESCRIPTOR);
}
p.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
p.visitInsn(RETURN);
p.visitMaxs(0, 0);
p.visitEnd();
}
private void generateTargetGetter() {
MethodVisitor m = proxyWriter.visitMethod(ACC_PUBLIC | ACC_FINAL, "$getProxyTarget", "()" +
Object.class.descriptorString(), null, null);
m.visitCode();
if (info.targetLookup != null) {
m.visitVarInsn(ALOAD, 0);
m.visitFieldInsn(GETFIELD, proxyName, "target", targetDescriptor);
} else m.visitInsn(ACONST_NULL);
m.visitInsn(ARETURN);
m.visitMaxs(0, 0);
m.visitEnd();
}
private void generateMethods(Class<?> interFace) {
for (Method method : interFace.getMethods()) {
private void generateMethods() {
for (Method method : info.interFace.getMethods()) {
int mod = method.getModifiers();
if ((mod & (Modifier.STATIC | Modifier.FINAL)) != 0) continue;
if (!Modifier.isAbstract(mod)) continue;
MethodMapping methodMapping = getTargetMethodMapping(method);
Exception exception = null;
Enum<?> extension = EXTENSIONS_ENABLED && JBRApi.extensionExtractor != null ?
JBRApi.extensionExtractor.apply(method) : null;
Mapping.Method methodMapping = mappingContext.getMapping(method);
MethodHandle handle;
boolean passInstance;
if (methodMapping.query().valid) {
// Try static method.
handle = info.getStaticMethod(method.getName(), methodMapping.type());
passInstance = false;
// Try target class.
if (handle == null && info.targetLookup != null) {
try {
handle = interFace.equals(info.targetLookup.lookupClass()) ?
info.targetLookup.unreflect(method) : info.targetLookup.findVirtual(
info.targetLookup.lookupClass(), method.getName(), methodMapping.type());
passInstance = true;
} catch (NoSuchMethodException | IllegalAccessException e) {
exception = e;
}
}
if (handle != null) {
// Method found.
generateMethod(method, handle, methodMapping, extension, passInstance);
if (extension != null) supportedExtensions.putIfAbsent(extension, true);
Exception e1 = null;
if (info.target != null) {
try {
MethodHandle handle = info.target.findVirtual(
info.target.lookupClass(), method.getName(), methodMapping.type());
generateMethod(method, handle, methodMapping, true);
continue;
} catch (NoSuchMethodException | IllegalAccessException e) {
e1 = e;
}
} else {
exception = new Exception("Method mapping is invalid: " + methodMapping);
}
// Skip if possible.
if (!Modifier.isAbstract(mod)) continue;
Exception e2 = null;
ProxyInfo.StaticMethodMapping mapping = info.staticMethods.get(method.getName());
if (mapping != null) {
try {
MethodHandle staticHandle =
mapping.lookup().findStatic(mapping.lookup().lookupClass(), mapping.methodName(), methodMapping.type());
generateMethod(method, staticHandle, methodMapping, false);
continue;
} catch (NoSuchMethodException | IllegalAccessException e) {
e2 = e;
}
}
// Generate unsupported stub.
if (e1 != null) exceptions.add(e1);
if (e2 != null) exceptions.add(e2);
generateUnsupportedMethod(proxyWriter, method);
if (JBRApi.VERBOSE) {
synchronized (System.err) {
System.err.println("Couldn't generate method " + method.getName());
if (exception != null) exception.printStackTrace(System.err);
}
System.err.println("Couldn't generate method " + method.getName());
if (e1 != null) e1.printStackTrace();
if (e2 != null) e2.printStackTrace();
}
if (extension == null) supported = false;
else supportedExtensions.put(extension, false);
allMethodsImplemented = false;
}
}
private void generateMethod(Method interfaceMethod, MethodHandle handle, Mapping.Method mapping, Enum<?> extension, boolean passInstance) {
boolean passExtensions = mapping.query().needsExtensions;
private void generateMethod(Method interfaceMethod, MethodHandle handle, MethodMapping mapping, boolean passInstance) {
InternalMethodInfo methodInfo = getInternalMethodInfo(interfaceMethod);
MethodHandleInfo directCall = accessContext.resolveDirect(handle);
Supplier<MethodHandle> futureHandle = () -> handle;
String bridgeMethodDescriptor = mapping.getBridgeDescriptor(passInstance);
// Check usage of deprecated API.
if (JBRApi.LOG_DEPRECATED && interfaceMethod.isAnnotationPresent(Deprecated.class)) {
directCall = null; // Force invokedynamic.
futureHandle = () -> { // Log warning when binding the call site.
Utils.log(Utils.BEFORE_BOOTSTRAP_DYNAMIC, System.err, "Warning: using deprecated JBR API method " +
interfaceMethod.getDeclaringClass().getCanonicalName() + "." + interfaceMethod.getName());
return handle;
ClassVisitor handleWriter = generateBridge ? bridgeWriter : proxyWriter;
String bridgeOrProxyName = generateBridge ? bridgeName : proxyName;
String handleName = addHandle(handleWriter, () -> handle);
for (TypeMapping m : mapping) {
if (m.conversion() == TypeConversion.EXTRACT_TARGET ||
m.conversion() == TypeConversion.DYNAMIC_2_WAY) {
Proxy<?> from = m.fromProxy();
m.metadata.extractTargetHandle = addHandle(handleWriter, from::getTargetExtractor);
directProxyDependencies.add(from);
}
if (m.conversion() == TypeConversion.WRAP_INTO_PROXY ||
m.conversion() == TypeConversion.DYNAMIC_2_WAY) {
Proxy<?> to = m.toProxy();
m.metadata.proxyConstructorHandle = addHandle(handleWriter, to::getConstructor);
directProxyDependencies.add(to);
}
if (m.conversion() == TypeConversion.DYNAMIC_2_WAY) {
String classField = "c" + classReferences.size();
m.metadata.extractableClassField = classField;
classReferences.add(m.fromProxy()::getProxyClass);
handleWriter.visitField(ACC_PRIVATE | ACC_STATIC, classField, "Ljava/lang/Class;", null, null);
}
}
String bridgeMethodName = methodInfo.name() + "$bridge$" + bridgeMethodCounter;
bridgeMethodCounter++;
MethodVisitor p = proxyWriter.visitMethod(ACC_PUBLIC | ACC_FINAL, methodInfo.name(),
methodInfo.descriptor(), methodInfo.genericSignature(), methodInfo.exceptionNames());
MethodVisitor b = bridgeWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, bridgeMethodName,
bridgeMethodDescriptor, null, null);
if (LOG_DEPRECATED && interfaceMethod.isAnnotationPresent(Deprecated.class)) {
logDeprecated(p, "Warning: using deprecated JBR API method " +
interfaceMethod.getDeclaringClass().getName() + "#" + interfaceMethod.getName());
}
p.visitCode();
b.visitCode();
MethodVisitor bp = generateBridge ? b : p;
bp.visitFieldInsn(GETSTATIC, bridgeOrProxyName, handleName, MH_DESCRIPTOR);
if (passInstance) {
p.visitVarInsn(ALOAD, 0);
p.visitFieldInsn(GETFIELD, proxyName, "target", OBJECT_DESCRIPTOR);
b.visitVarInsn(ALOAD, 0);
}
int lvIndex = 1;
for (TypeMapping param : mapping.parameterMapping) {
int opcode = getLoadOpcode(param.from());
p.visitVarInsn(opcode, lvIndex);
b.visitVarInsn(opcode, lvIndex - (passInstance ? 0 : 1));
lvIndex += getParameterSize(param.from());
convertValue(bp, bridgeOrProxyName, param);
}
if (generateBridge) {
p.visitMethodInsn(INVOKESTATIC, bridgeName, bridgeMethodName, bridgeMethodDescriptor, false);
}
bp.visitMethodInsn(INVOKEVIRTUAL, MH_NAME, "invoke", bridgeMethodDescriptor, false);
convertValue(bp, bridgeOrProxyName, mapping.returnMapping());
int returnOpcode = getReturnOpcode(mapping.returnMapping().to());
p.visitInsn(returnOpcode);
b.visitInsn(returnOpcode);
p.visitMaxs(0, 0);
b.visitMaxs(0, 0);
p.visitEnd();
b.visitEnd();
}
private String addHandle(ClassVisitor classWriter, Supplier<MethodHandle> handleSupplier) {
String handleName = "h" + handles.size();
handles.add(handleSupplier);
classWriter.visitField(ACC_PRIVATE | ACC_STATIC, handleName, MH_DESCRIPTOR, null, null);
return handleName;
}
private static void convertValue(MethodVisitor m, String handlesHolderName, TypeMapping mapping) {
if (mapping.conversion() == TypeConversion.IDENTITY) return;
Label skipConvert = new Label();
m.visitInsn(DUP);
m.visitJumpInsn(IFNULL, skipConvert);
switch (mapping.conversion()) {
case EXTRACT_TARGET ->
m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.extractTargetHandle, MH_DESCRIPTOR);
case WRAP_INTO_PROXY ->
m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.proxyConstructorHandle, MH_DESCRIPTOR);
case DYNAMIC_2_WAY -> {
m.visitInsn(DUP);
m.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.extractableClassField, "Ljava/lang/Class;");
Label elseBranch = new Label(), afterBranch = new Label();
m.visitJumpInsn(IF_ACMPNE, elseBranch);
m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.extractTargetHandle, MH_DESCRIPTOR);
m.visitJumpInsn(GOTO, afterBranch);
m.visitLabel(elseBranch);
m.visitFieldInsn(GETSTATIC, handlesHolderName, mapping.metadata.proxyConstructorHandle, MH_DESCRIPTOR);
m.visitLabel(afterBranch);
}
}
m.visitInsn(SWAP);
m.visitMethodInsn(INVOKEVIRTUAL, MH_NAME, "invoke", CONVERSION_DESCRIPTOR, false);
m.visitLabel(skipConvert);
}
private static MethodMapping getTargetMethodMapping(Method interfaceMethod) {
Class<?>[] params = interfaceMethod.getParameterTypes();
TypeMapping[] paramMappings = new TypeMapping[params.length];
for (int i = 0; i < params.length; i++) {
paramMappings[i] = getTargetTypeMapping(params[i]);
params[i] = paramMappings[i].to();
}
TypeMapping returnMapping = getTargetTypeMapping(interfaceMethod.getReturnType()).inverse();
return new MethodMapping(MethodType.methodType(returnMapping.from(), params), returnMapping, paramMappings);
}
private static <T> TypeMapping getTargetTypeMapping(Class<T> userType) {
TypeMappingMetadata m = new TypeMappingMetadata();
Proxy<T> p = JBRApi.getProxy(userType);
if (p != null && p.getInfo().target != null) {
Proxy<?> r = JBRApi.getProxy(p.getInfo().target.lookupClass());
if (r != null && r.getInfo().target != null) {
return new TypeMapping(userType, p.getInfo().target.lookupClass(), TypeConversion.DYNAMIC_2_WAY, p, r, m);
}
return new TypeMapping(userType, p.getInfo().target.lookupClass(), TypeConversion.EXTRACT_TARGET, p, null, m);
}
Class<?> interFace = JBRApi.getProxyInterfaceByTargetName(userType.getName());
if (interFace != null) {
Proxy<?> r = JBRApi.getProxy(interFace);
if (r != null) {
return new TypeMapping(userType, interFace, TypeConversion.WRAP_INTO_PROXY, null, r, m);
}
}
return new TypeMapping(userType, userType, TypeConversion.IDENTITY, null, null, m);
}
private record MethodMapping(MethodType type, TypeMapping returnMapping, TypeMapping[] parameterMapping)
implements Iterable<TypeMapping> {
@Override
public Iterator<TypeMapping> iterator() {
return new Iterator<>() {
private int index = -1;
@Override
public boolean hasNext() {
return index < parameterMapping.length;
}
@Override
public TypeMapping next() {
TypeMapping m = index == -1 ? returnMapping : parameterMapping[index];
index++;
return m;
}
};
}
if (JBRApi.VERBOSE) {
System.out.println(" " +
mapping.returnMapping() + " " +
interfaceMethod.getName() + "(" +
Stream.of(mapping.parameterMapping()).map(Mapping::toString).collect(Collectors.joining(", ")) +
")" + (directCall != null ? " (direct)" : "")
);
/**
* Every convertable parameter type is replaced with {@link Object} for bridge descriptor.
* Optional {@link Object} is added as first parameter for instance methods.
*/
String getBridgeDescriptor(boolean passInstance) {
StringBuilder bd = new StringBuilder("(");
if (passInstance) bd.append(OBJECT_DESCRIPTOR);
for (TypeMapping m : parameterMapping) {
bd.append(m.getBridgeDescriptor());
}
bd.append(')');
bd.append(returnMapping.getBridgeDescriptor());
return bd.toString();
}
}
MethodVisitor m = proxyWriter.visitMethod(ACC_PUBLIC | ACC_FINAL, methodInfo.name(),
methodInfo.descriptor(), methodInfo.genericSignature(), methodInfo.exceptionNames());
AccessContext.Method methodContext = accessContext.new Method(m, extension == null);
m.visitCode();
// Load `this`.
if (passInstance || passExtensions || extension != null) {
m.visitVarInsn(ALOAD, 0);
if (passInstance && (passExtensions || extension != null)) m.visitInsn(DUP);
private record TypeMapping(Class<?> from, Class<?> to, TypeConversion conversion,
Proxy<?> fromProxy, Proxy<?> toProxy, TypeMappingMetadata metadata) {
TypeMapping inverse() {
return new TypeMapping(to, from, switch (conversion) {
case EXTRACT_TARGET -> TypeConversion.WRAP_INTO_PROXY;
case WRAP_INTO_PROXY -> TypeConversion.EXTRACT_TARGET;
default -> conversion;
}, toProxy, fromProxy, metadata);
}
// Check enabled extension.
if (passExtensions || extension != null) {
m.visitFieldInsn(GETFIELD, proxyName, "extensions", "[J");
if (passExtensions && extension != null) m.visitInsn(DUP);
if (passExtensions) {
// If this method converts any parameters, we need to store extensions for later use.
// We overwrite `this` slot, but we have it on stack, so it's ok.
m.visitVarInsn(ASTORE, 0);
}
if (extension != null) {
// Check the specific bit inside long[].
Label afterExtensionCheck = new Label();
m.visitIntInsn(extension.ordinal() < 8192 ? BIPUSH : SIPUSH, extension.ordinal() / 64);
m.visitInsn(LALOAD);
m.visitLdcInsn(1L << (extension.ordinal() % 64));
m.visitInsn(LAND);
m.visitInsn(LCONST_0);
m.visitInsn(LCMP);
m.visitJumpInsn(IFNE, afterExtensionCheck);
throwException(m, "java/lang/UnsupportedOperationException",
interFace.getCanonicalName() + '.' + interfaceMethod.getName() +
" - extension " + extension.name() + " is disabled");
m.visitLabel(afterExtensionCheck);
}
String getBridgeDescriptor() {
if (conversion == TypeConversion.IDENTITY) return Type.getDescriptor(from);
else return "Ljava/lang/Object;";
}
// Extract target from `this`.
if (passInstance) {
// We already have `this` on stack.
m.visitFieldInsn(GETFIELD, proxyName, "target", targetDescriptor);
}
// Load and convert parameters.
for (int lvIndex = 1, i = 0; i < mapping.parameterMapping().length; i++) {
Mapping param = mapping.parameterMapping()[i];
int opcode = getLoadOpcode(param.from);
m.visitVarInsn(opcode, lvIndex);
lvIndex += getParameterSize(param.from);
param.convert(methodContext);
}
// Invoke target method.
if (directCall != null) methodContext.invokeDirect(directCall);
else methodContext.invokeDynamic(handle.type(), futureHandle);
// Convert return value.
mapping.returnMapping().convert(methodContext);
int opcode = getReturnOpcode(mapping.returnMapping().to);
m.visitInsn(opcode);
m.visitMaxs(0, 0);
m.visitEnd();
}
private static class TypeMappingMetadata {
private String extractTargetHandle, proxyConstructorHandle, extractableClassField;
}
private enum TypeConversion {
/**
* No conversion.
*/
IDENTITY,
/**
* Take a proxy object and extract its target implementation object.
*/
EXTRACT_TARGET,
/**
* Create new proxy targeting given implementation object.
*/
WRAP_INTO_PROXY,
/**
* Decide between {@link #EXTRACT_TARGET} and {@link #WRAP_INTO_PROXY} at runtime, depending on actual object.
*/
DYNAMIC_2_WAY
}
}

View File

@@ -0,0 +1,123 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.internal;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
import static java.lang.invoke.MethodHandles.Lookup;
/**
* Proxy info, like {@link RegisteredProxyInfo}, but with all classes and lookup contexts resolved.
* Contains all necessary information to create a {@linkplain Proxy proxy}.
*/
class ProxyInfo {
final Lookup apiModule;
final Type type;
final Lookup interFaceLookup;
final Class<?> interFace;
final Lookup target;
final Map<String, StaticMethodMapping> staticMethods = new HashMap<>();
private ProxyInfo(RegisteredProxyInfo i) {
this.apiModule = i.apiModule();
type = i.type();
interFaceLookup = lookup(getInterfaceLookup(), i.interfaceName());
interFace = interFaceLookup == null ? null : interFaceLookup.lookupClass();
target = Stream.of(i.targets())
.map(t -> lookup(getTargetLookup(), t))
.filter(Objects::nonNull).findFirst().orElse(null);
for (RegisteredProxyInfo.StaticMethodMapping m : i.staticMethods()) {
Stream.of(m.classes())
.map(t -> lookup(getTargetLookup(), t))
.filter(Objects::nonNull).findFirst()
.ifPresent(l -> staticMethods.put(m.interfaceMethodName(), new StaticMethodMapping(l, m.methodName())));
}
}
/**
* Resolve all classes and lookups for given {@link RegisteredProxyInfo}.
*/
static ProxyInfo resolve(RegisteredProxyInfo i) {
ProxyInfo info = new ProxyInfo(i);
if (info.interFace == null || (info.target == null && info.staticMethods.isEmpty())) return null;
if (!info.interFace.isInterface()) {
if (info.type == Type.CLIENT_PROXY) {
throw new RuntimeException("Tried to create client proxy for non-interface: " + info.interFace);
} else {
return null;
}
}
return info;
}
Lookup getInterfaceLookup() {
return type == Type.CLIENT_PROXY || type == Type.INTERNAL_SERVICE ? apiModule : JBRApi.outerLookup;
}
Lookup getTargetLookup() {
return type == Type.CLIENT_PROXY ? JBRApi.outerLookup : apiModule;
}
private Lookup lookup(Lookup lookup, String clazz) {
String[] nestedClasses = clazz.split("\\$");
clazz = "";
for (int i = 0; i < nestedClasses.length; i++) {
try {
if (i != 0) clazz += "$";
clazz += nestedClasses[i];
lookup = MethodHandles.privateLookupIn(Class.forName(clazz, false, lookup.lookupClass().getClassLoader()), lookup);
} catch (ClassNotFoundException | IllegalAccessException ignore) {
return null;
}
}
return lookup;
}
record StaticMethodMapping(Lookup lookup, String methodName) {}
/**
* Proxy type, see {@link Proxy}
*/
enum Type {
PROXY,
SERVICE,
CLIENT_PROXY,
INTERNAL_SERVICE;
public boolean isPublicApi() {
return this == PROXY || this == SERVICE;
}
public boolean isService() {
return this == SERVICE || this == INTERNAL_SERVICE;
}
}
}

View File

@@ -1,341 +0,0 @@
/*
* Copyright 2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.internal;
import jdk.internal.access.SharedSecrets;
import jdk.internal.loader.BootLoader;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Stream;
import static java.lang.invoke.MethodHandles.Lookup;
/**
* Proxy repository keeps track of all generated proxies.
* @see ProxyRepository#getProxy(Class, Mapping[])
*/
class ProxyRepository {
private static final Proxy NONE = Proxy.empty(null), INVALID = Proxy.empty(false);
private final Registry registry = new Registry();
private final Map<Key, Proxy> proxies = new HashMap<>();
void init(InputStream extendedRegistryStream,
Class<? extends Annotation> serviceAnnotation,
Class<? extends Annotation> providedAnnotation,
Class<? extends Annotation> providesAnnotation) {
registry.initAnnotations(serviceAnnotation, providedAnnotation, providesAnnotation);
if (extendedRegistryStream != null) registry.readEntries(extendedRegistryStream);
}
String getVersion() {
return registry.version;
}
synchronized Proxy getProxy(Class<?> clazz, Mapping[] specialization) {
Key key = new Key(clazz, specialization);
Proxy p = proxies.get(key);
if (p == null) {
registry.updateClassLoader(clazz.getClassLoader());
Mapping[] inverseSpecialization = specialization == null ? null :
Stream.of(specialization).map(m -> m == null ? null : m.inverse()).toArray(Mapping[]::new);
Key inverseKey = null;
Registry.Entry entry = registry.entries.get(key.clazz().getCanonicalName());
if (entry != null) { // This is a registered proxy
Proxy.Info infoByInterface = entry.resolve(),
infoByTarget = entry.inverse != null ? entry.inverse.resolve() : null;
inverseKey = infoByTarget != null && infoByTarget.interfaceLookup != null ?
new Key(infoByTarget.interfaceLookup.lookupClass(), inverseSpecialization) : null;
if ((infoByInterface == null && infoByTarget == null) ||
infoByInterface == Registry.Entry.INVALID ||
infoByTarget == Registry.Entry.INVALID) {
p = INVALID;
} else {
p = Proxy.create(this, infoByInterface, specialization, infoByTarget, inverseSpecialization);
}
} else if (!Arrays.equals(specialization, inverseSpecialization)) { // Try implicit proxy
inverseKey = new Key(clazz, inverseSpecialization);
Lookup lookup = SharedSecrets.getJavaLangInvokeAccess().lookupIn(clazz);
Proxy.Info info = new Proxy.Info(lookup, lookup, 0);
p = Proxy.create(this, info, specialization, info, inverseSpecialization);
} else p = NONE;
proxies.put(key, p);
if (inverseKey != null) proxies.put(inverseKey, p.inverse());
}
return p;
}
private record Key(Class<?> clazz, Mapping[] specialization) {
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (!clazz.equals(key.clazz)) return false;
return Arrays.equals(specialization, key.specialization);
}
@Override
public int hashCode() {
int result = clazz.hashCode();
result = 31 * result + Arrays.hashCode(specialization);
return result;
}
@Override
public String toString() {
return clazz.getName() + "<" + Arrays.toString(specialization) + ">";
}
}
/**
* Registry contains all information about mapping between JBR API interfaces and implementation.
* This mapping information can be {@linkplain Entry#resolve() resolved} into {@link Proxy.Info}.
*/
private static class Registry {
private record StaticKey(String methodName, String targetMethodDescriptor) {}
private record StaticValue(String targetType, String targetMethodName) {}
private class Entry {
private static final Proxy.Info INVALID = new Proxy.Info(null, null, 0);
private final Map<StaticKey, StaticValue> staticMethods = new HashMap<>();
private String type, target;
private Entry inverse;
private int flags;
private Entry(String type) { this.type = type; }
private Proxy.Info resolve() {
if (type == null) return null;
Lookup l, t;
try {
l = resolveType(type, classLoader);
t = target != null ? resolveType(target, classLoader) : null;
} catch (ClassNotFoundException e) {
if (JBRApi.VERBOSE) {
System.err.println(type + " not eligible");
e.printStackTrace(System.err);
}
return INVALID;
}
if (l.lookupClass().isAnnotation() || l.lookupClass().isEnum() || l.lookupClass().isRecord()) {
if (JBRApi.VERBOSE) {
System.err.println(type + " not eligible: not a class or interface");
}
return INVALID;
}
if (Modifier.isFinal(l.lookupClass().getModifiers()) || l.lookupClass().isSealed()) {
if (JBRApi.VERBOSE) {
System.err.println(type + " not eligible: invalid type (final/sealed)");
}
return INVALID;
}
if (target == null) flags |= Proxy.SERVICE;
if (needsAnnotation(l.lookupClass())) {
if (!hasAnnotation(l.lookupClass(), providedAnnotation)) {
if (JBRApi.VERBOSE) {
System.err.println(type + " not eligible: no @Provided annotation");
}
return INVALID;
}
if (!hasAnnotation(l.lookupClass(), serviceAnnotation)) flags &= ~Proxy.SERVICE;
}
Proxy.Info info;
if (t != null) {
if (needsAnnotation(t.lookupClass()) && !hasAnnotation(t.lookupClass(), providesAnnotation)) {
if (JBRApi.VERBOSE) {
System.err.println(target + " not eligible: no @Provides annotation");
}
return INVALID;
}
info = new Proxy.Info(l, t, flags);
} else info = new Proxy.Info(l, null, flags);
for (var method : staticMethods.entrySet()) {
String methodName = method.getKey().methodName;
String targetMethodDescriptor = method.getKey().targetMethodDescriptor;
String targetType = method.getValue().targetType;
String targetMethodName = method.getValue().targetMethodName;
try {
Lookup lookup = resolveType(targetType, classLoader);
MethodType mt = MethodType.fromMethodDescriptorString(targetMethodDescriptor, classLoader);
MethodHandle handle = lookup.findStatic(lookup.lookupClass(), targetMethodName, mt);
info.addStaticMethod(methodName, handle);
} catch (ClassNotFoundException | IllegalArgumentException | TypeNotPresentException |
NoSuchMethodException | IllegalAccessException | ClassCastException e) {
if (JBRApi.VERBOSE) {
System.err.println(targetType + "#" + targetMethodName + " cannot be resolved");
e.printStackTrace(System.err);
}
}
}
return info;
}
private static Lookup resolveType(String type, ClassLoader classLoader) throws ClassNotFoundException {
Class<?> c = null;
try {
c = Class.forName(type, false, classLoader);
} catch (ClassNotFoundException e) {
String dollars = type.replace('.', '$');
for (int i = 0;; i++) {
i = type.indexOf('.', i);
if (i == -1) break;
String name = type.substring(0, i) + dollars.substring(i);
try {
c = Class.forName(name, false, classLoader);
break;
} catch (ClassNotFoundException ignore) {}
}
if (c == null) throw e;
}
return SharedSecrets.getJavaLangInvokeAccess().lookupIn(c);
}
@Override
public String toString() { return type; }
}
private Class<? extends Annotation> serviceAnnotation, providedAnnotation, providesAnnotation;
private Module annotationsModule;
private ClassLoader classLoader;
private final Map<String, Entry> entries = new HashMap<>();
private final String version;
private Registry() {
try (InputStream registryStream = BootLoader.findResourceAsStream("java.base", "META-INF/jbrapi.registry")) {
version = readEntries(registryStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void initAnnotations(Class<? extends Annotation> serviceAnnotation,
Class<? extends Annotation> providedAnnotation,
Class<? extends Annotation> providesAnnotation) {
this.serviceAnnotation = serviceAnnotation;
this.providedAnnotation = providedAnnotation;
this.providesAnnotation = providesAnnotation;
annotationsModule = serviceAnnotation == null ? null : serviceAnnotation.getModule();
if (annotationsModule != null) classLoader = annotationsModule.getClassLoader();
}
private String readEntries(InputStream inputStream) {
String version = null;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
String s;
while ((s = reader.readLine()) != null) {
String[] tokens = s.split(" ");
switch (tokens[0]) {
case "TYPE" -> {
Entry a = entries.computeIfAbsent(tokens[1], Entry::new);
Entry b = entries.computeIfAbsent(tokens[2], Entry::new);
if ((a.inverse != null || b.inverse != null) && (a.inverse != b || b.inverse != a)) {
throw new RuntimeException("Conflicting mapping: " +
tokens[1] + " <-> " + tokens[2] +
", " + a + " -> " + a.inverse +
", " + b + " -> " + b.inverse);
}
a.inverse = b;
b.inverse = a;
a.target = tokens[2];
b.target = tokens[1];
switch (tokens[3]) {
case "SERVICE" -> {
a.type = null;
b.flags |= Proxy.SERVICE;
}
case "PROVIDES" -> a.type = null;
case "PROVIDED" -> b.type = null;
}
if (tokens.length > 4 && tokens[4].equals("INTERNAL")) {
a.flags |= Proxy.INTERNAL;
b.flags |= Proxy.INTERNAL;
}
}
case "STATIC" -> {
Entry entry = entries.computeIfAbsent(tokens[4], Entry::new);
StaticValue target = new StaticValue(tokens[1], tokens[2]);
StaticValue prev = entry.staticMethods.put(new StaticKey(tokens[5], tokens[3]), target);
if (prev != null && !prev.equals(target)) {
throw new RuntimeException("Conflicting mapping: " +
target.targetType + "#" + target.targetMethodName + " <- " +
tokens[4] + "#" + tokens[5] + " -> " +
prev.targetType + "#" + prev.targetMethodName);
}
if (tokens.length > 6 && tokens[6].equals("INTERNAL")) entry.flags |= Proxy.INTERNAL;
}
case "VERSION" -> version = tokens[1];
}
}
} catch (IOException e) {
entries.clear();
throw new RuntimeException(e);
} catch (RuntimeException | Error e) {
entries.clear();
throw e;
}
return version;
}
private synchronized void updateClassLoader(ClassLoader newLoader) {
// New loader is descendant of current one -> update
for (ClassLoader cl = newLoader;; cl = cl.getParent()) {
if (cl == classLoader) {
classLoader = newLoader;
return;
}
if (cl == null) break;
}
// Current loader is descendant of the new one -> leave
for (ClassLoader cl = classLoader;; cl = cl.getParent()) {
if (cl == newLoader) return;
if (cl == null) break;
}
// Independent classloaders -> error? Or maybe reset cache and start fresh?
throw new RuntimeException("Incompatible classloader");
}
private boolean needsAnnotation(Class<?> c) {
return annotationsModule != null && annotationsModule.equals(c.getModule());
}
private static boolean hasAnnotation(Class<?> c, Class<? extends Annotation> a) {
return c.getAnnotation(a) != null;
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.internal;
import java.lang.invoke.MethodHandles;
import java.util.List;
/**
* Raw proxy info, as it was registered through {@link JBRApi.ModuleRegistry}.
* Contains all necessary information to create a {@linkplain Proxy proxy}.
*/
record RegisteredProxyInfo(MethodHandles.Lookup apiModule,
String interfaceName,
String[] targets,
ProxyInfo.Type type,
List<StaticMethodMapping> staticMethods) {
record StaticMethodMapping(String interfaceMethodName, String methodName, String[] classes) {}
}

View File

@@ -1,49 +0,0 @@
package com.jetbrains.internal;
import jdk.internal.misc.VM;
import java.io.PrintStream;
import java.util.function.Function;
import java.util.stream.Stream;
class Utils {
static final Function<Stream<StackTraceElement>, Stream<StackTraceElement>>
BEFORE_BOOTSTRAP_DYNAMIC = st -> st
.dropWhile(e -> !e.getClassName().equals("com.jetbrains.exported.JBRApiSupport") ||
!e.getMethodName().equals("bootstrapDynamic")).skip(1)
.dropWhile(e -> e.getClassName().startsWith("java.lang.invoke."));
static final Function<Stream<StackTraceElement>, Stream<StackTraceElement>>
BEFORE_JBR = st -> st
.dropWhile(e -> !e.getClassName().equals("com.jetbrains.JBR"))
.dropWhile(e -> e.getClassName().equals("com.jetbrains.JBR"));
static void log(Function<Stream<StackTraceElement>, Stream<StackTraceElement>> preprocessor,
PrintStream out, String message) {
StackTraceElement[] st = Thread.currentThread().getStackTrace();
synchronized (out) {
out.println(message);
preprocessor.apply(Stream.of(st)).forEach(e -> out.println("\tat " + e));
}
}
/**
* System properties obtained this way are immune to runtime override via {@link System#setProperty(String, String)}.
*/
static boolean property(String name, boolean defaultValue) {
String value = VM.getSavedProperty(name);
if (value != null) {
if (value.equalsIgnoreCase("true")) return true;
if (value.equalsIgnoreCase("false")) return false;
}
return defaultValue;
}
/**
* System properties obtained this way are immune to runtime override via {@link System#setProperty(String, String)}.
*/
static String property(String name, String defaultValue) {
String value = VM.getSavedProperty(name);
return value != null ? value : defaultValue;
}
}

View File

@@ -27,8 +27,6 @@ package java.lang;
import java.io.*;
import java.util.*;
import com.jetbrains.exported.JBRApi;
import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.InternalLock;
@@ -1135,7 +1133,7 @@ public class Throwable implements Serializable {
private static volatile java.util.function.Supplier<String> $$jb$additionalInfoSupplier = null;
@JBRApi.Provides("Jstack#includeInfoFrom")
// JBR API internals
private static void $$jb$additionalInfoForJstack(java.util.function.Supplier<String> supplier) {
$$jb$additionalInfoSupplier = supplier;
}

View File

@@ -1660,11 +1660,6 @@ abstract class MethodHandleImpl {
public Class<?>[] exceptionTypes(MethodHandle handle) {
return VarHandles.exceptionTypes(handle);
}
@Override
public Lookup lookupIn(Class<?> lookupClass) {
return IMPL_LOOKUP.in(lookupClass);
}
});
}

View File

@@ -173,11 +173,4 @@ public interface JavaLangInvokeAccess {
* @return an array of exceptions, or {@code null}.
*/
Class<?>[] exceptionTypes(MethodHandle handle);
/**
* Returns a lookup object corresponding to given class with full access privileges.
* @param lookupClass lookup class
* @return full-privileged lookup object
*/
Lookup lookupIn(Class<?> lookupClass);
}

View File

@@ -134,9 +134,10 @@ module java.base {
// additional qualified exports may be inserted at build time
// see make/gensrc/GenModuleInfo.gmk
exports com.jetbrains.exported;
opens com.jetbrains.bootstrap;
exports com.jetbrains.internal to
java.desktop;
exports com.sun.crypto.provider to
jdk.crypto.cryptoki;
exports sun.invoke.util to

View File

@@ -192,10 +192,7 @@ JNIEXPORT jint JNICALL Java_sun_awt_UNIXToolkit_isSystemDarkColorScheme() {
if (!sendDBusMessageWithReply(msg_freedesktop_appearance, &res, DBUS_TYPE_UINT32)) {
return UNKNOWN_RESULT;
}
/* From org.freedesktop.portal color-scheme specs:
* 0: No preference, 1: Prefer dark appearance, 2: Prefer light appearance
*/
return res == 1;
return res;
} else {
char *res = NULL;
if (!sendDBusMessageWithReply(msg_gnome_desktop, &res, DBUS_TYPE_STRING)) {

View File

@@ -218,7 +218,7 @@ MidiMessage* MIDI_IN_GetMessage(MidiDeviceHandle* handle) {
return NULL;
}
}
jdk_message = (MidiMessage*) calloc(1, sizeof(MidiMessage));
jdk_message = (MidiMessage*) calloc(sizeof(MidiMessage), 1);
if (!jdk_message) {
ERROR0("< ERROR: MIDI_IN_GetMessage(): out of memory\n");
return NULL;

View File

@@ -383,7 +383,7 @@ INT32 openMidiDevice(snd_rawmidi_stream_t direction, INT32 deviceIndex,
TRACE0("> openMidiDevice()\n");
(*handle) = (MidiDeviceHandle*) calloc(1, sizeof(MidiDeviceHandle));
(*handle) = (MidiDeviceHandle*) calloc(sizeof(MidiDeviceHandle), 1);
if (!(*handle)) {
ERROR0("ERROR: openDevice: out of memory\n");
return MIDI_OUT_OF_MEMORY;

View File

@@ -159,12 +159,6 @@ public final class CGraphicsEnvironment extends SunGraphicsEnvironment {
/* Populate the device table */
rebuildDevices();
if (LogDisplay.ENABLED) {
for (CGraphicsDevice gd : devices.values()) {
LogDisplay.ADDED.log(gd.getDisplayID(), gd.getBounds(), gd.getScaleFactor());
}
}
/* Register our display reconfiguration listener */
displayReconfigContext = registerDisplayReconfiguration();
if (displayReconfigContext == 0L) {
@@ -191,25 +185,13 @@ public final class CGraphicsEnvironment extends SunGraphicsEnvironment {
* @param displayId CoreGraphics displayId
* @param removed true if displayId was removed, false otherwise.
*/
void _displayReconfiguration(int displayId, int flags) {
// See CGDisplayChangeSummaryFlags
LogDisplay log = !LogDisplay.ENABLED ? null :
(flags & (1 << 4)) != 0 ? LogDisplay.ADDED :
(flags & (1 << 5)) != 0 ? LogDisplay.REMOVED : LogDisplay.CHANGED;
if (log == LogDisplay.REMOVED) {
CGraphicsDevice gd = devices.get(displayId);
log.log(displayId, gd != null ? gd.getBounds() : "UNKNOWN", gd != null ? gd.getScaleFactor() : Double.NaN);
}
void _displayReconfiguration(int displayId, boolean removed) {
// we ignore the passed parameters and check removed devices ourself
// Note that it is possible that this callback is called when the
// monitors are not added nor removed, but when the video card is
// switched to/from the discrete video card, so we should try to map the
// old to the new devices.
rebuildDevices();
if (log != null && log != LogDisplay.REMOVED) {
CGraphicsDevice gd = devices.get(displayId);
log.log(displayId, gd != null ? gd.getBounds() : "UNKNOWN", gd != null ? gd.getScaleFactor() : Double.NaN);
}
}
@Override

View File

@@ -25,23 +25,19 @@
package sun.java2d.metal;
import sun.awt.AWTAccessor;
import sun.java2d.NullSurfaceData;
import sun.java2d.SurfaceData;
import sun.lwawt.LWWindowPeer;
import sun.lwawt.macosx.CFLayer;
import sun.util.logging.PlatformLogger;
import java.awt.Component;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.Window;
public class MTLLayer extends CFLayer {
private static final PlatformLogger logger = PlatformLogger.getLogger(MTLLayer.class.getName());
private native long nativeCreateLayer(boolean perfCountersEnabled);
private native long nativeCreateLayer();
private static native void nativeSetScale(long layerPtr, double scale);
// Pass the insets to native code to make adjustments in blitTexture
@@ -55,10 +51,7 @@ public class MTLLayer extends CFLayer {
public MTLLayer(LWWindowPeer peer) {
super(0, true);
Window target = (peer != null) ? peer.getTarget() : null;
boolean perfCountersEnabled = (target != null) && AWTAccessor.getWindowAccessor().countersEnabled(target);
setPtr(nativeCreateLayer(perfCountersEnabled));
setPtr(nativeCreateLayer());
this.peer = peer;
MTLGraphicsConfig gc = (MTLGraphicsConfig)getGraphicsConfiguration();
@@ -149,22 +142,4 @@ public class MTLLayer extends CFLayer {
rq.unlock();
}
}
private void countNewFrame() {
// Called from the native code when this layer has been presented on screen
Component target = peer.getTarget();
if (target instanceof Window window) {
AWTAccessor.getWindowAccessor().bumpCounter(window, "java2d.native.frames");
}
}
private void countDroppedFrame() {
// Called from the native code when an attempt was made to present this layer
// on screen, but that attempt was not successful. This can happen, for example,
// when those attempts are too frequent.
Component target = peer.getTarget();
if (target instanceof Window window) {
AWTAccessor.getWindowAccessor().bumpCounter(window, "java2d.native.framesDropped");
}
}
}

View File

@@ -111,7 +111,6 @@ class CFileDialog implements FileDialogPeer {
chooseFiles,
createDirectories,
target.getFilenameFilter() != null,
jbrDialog.fileFilterExtensions,
target.getDirectory(),
target.getFile());
@@ -205,7 +204,7 @@ class CFileDialog implements FileDialogPeer {
private native String[] nativeRunFileDialog(long ownerPtr, String title, int mode,
boolean multipleMode, boolean shouldNavigateApps,
boolean canChooseDirectories, boolean canChooseFiles,
boolean canCreateDirectories, boolean hasFilenameFilter, String[] allowedFileTypes,
boolean canCreateDirectories, boolean hasFilenameFilter,
String directory, String file);
@Override

View File

@@ -532,12 +532,6 @@ public class CInputMethod extends InputMethodAdapter {
((TextComponent) fAwtFocussedComponent).select(selectionStart, selectionEnd);
return;
}
var desc = (CInputMethodDescriptor)LWCToolkit.getLWCToolkit().getInputMethodAdapterDescriptor();
if (desc.textInputEventListener != null) {
var event = new JBRTextInputMacOS.SelectTextRangeEvent(fAwtFocussedComponent, selectionStart, length);
desc.textInputEventListener.handleSelectTextRangeEvent(event);
}
}
}, fAwtFocussedComponent);
} catch (Exception e) {

View File

@@ -37,7 +37,6 @@ import java.util.List;
*/
public class CInputMethodDescriptor implements InputMethodDescriptor {
JBRTextInputMacOS.EventListener textInputEventListener;
static {
nativeInit();

View File

@@ -44,6 +44,7 @@ import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.FocusEvent;
import java.awt.event.PaintEvent;
import java.awt.event.WindowEvent;
import java.awt.peer.ComponentPeer;
import java.beans.PropertyChangeEvent;
@@ -63,11 +64,11 @@ import javax.swing.SwingUtilities;
import com.apple.laf.ClientPropertyApplicator;
import com.apple.laf.ClientPropertyApplicator.Property;
import com.jetbrains.exported.JBRApi;
import sun.awt.AWTAccessor;
import sun.awt.AWTAccessor.ComponentAccessor;
import sun.awt.AWTAccessor.WindowAccessor;
import sun.awt.AWTThreading;
import sun.awt.PaintEventDispatcher;
import sun.java2d.SunGraphicsEnvironment;
import sun.java2d.SurfaceData;
import sun.lwawt.LWLightweightFramePeer;
@@ -1486,7 +1487,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
isFullScreenAnimationOn = false;
}
@JBRApi.Provides("java.awt.Window.CustomTitleBarPeer#update")
// JBR API internals
private static void updateCustomTitleBar(ComponentPeer peer) {
if (peer instanceof LWWindowPeer lwwp &&
lwwp.getPlatformWindow() instanceof CPlatformWindow cpw) {
@@ -1494,7 +1495,7 @@ public class CPlatformWindow extends CFRetainedResource implements PlatformWindo
}
}
@JBRApi.Provides("RoundedCornersManager")
// JBR API internals
private static void setRoundedCorners(Window window, Object params) {
Object peer = AWTAccessor.getComponentAccessor().getPeer(window);
if (peer instanceof CPlatformWindow) {

View File

@@ -1,82 +0,0 @@
/*
* Copyright 2024 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sun.lwawt.macosx;
import com.jetbrains.exported.JBRApi;
@JBRApi.Service
@JBRApi.Provides("TextInput")
public class JBRTextInputMacOS {
private EventListener listener;
JBRTextInputMacOS() {
var desc = (CInputMethodDescriptor) LWCToolkit.getLWCToolkit().getInputMethodAdapterDescriptor();
desc.textInputEventListener = new EventListener() {
public void handleSelectTextRangeEvent(SelectTextRangeEvent event) {
// This listener is called on the EDT
synchronized (JBRTextInputMacOS.this) {
if (listener != null) {
listener.handleSelectTextRangeEvent(event);
}
}
}
};
}
@JBRApi.Provides("TextInput.SelectTextRangeEvent")
public static class SelectTextRangeEvent {
private final Object source;
private final int begin;
private final int length;
public SelectTextRangeEvent(Object source, int begin, int length) {
this.source = source;
this.begin = begin;
this.length = length;
}
public Object getSource() {
return source;
}
public int getBegin() {
return begin;
}
public int getLength() {
return length;
}
}
@JBRApi.Provided("TextInput.EventListener")
public interface EventListener {
void handleSelectTextRangeEvent(SelectTextRangeEvent event);
}
public synchronized void setGlobalEventListener(EventListener listener) {
this.listener = listener;
}
}

View File

@@ -1213,7 +1213,7 @@ AWT_ASSERT_APPKIT_THREAD;
isDisabled = !awtWindow.isEnabled;
}
if (menuBar == nil && [ApplicationDelegate sharedDelegate] != nil) {
if (menuBar == nil) {
menuBar = [[ApplicationDelegate sharedDelegate] defaultMenuBar];
isDisabled = NO;
}
@@ -2267,7 +2267,7 @@ JNI_COCOA_ENTER(env);
window.javaMenuBar = menuBar;
CMenuBar* actualMenuBar = menuBar;
if (actualMenuBar == nil && [ApplicationDelegate sharedDelegate] != nil) {
if (actualMenuBar == nil) {
actualMenuBar = [[ApplicationDelegate sharedDelegate] defaultMenuBar];
}

View File

@@ -117,9 +117,8 @@ AWT_ASSERT_APPKIT_THREAD;
// don't install the EAWT delegate if another kind of NSApplication is installed, like say, Safari
BOOL shouldInstall = NO;
BOOL overrideDelegate = (getenv("AWT_OVERRIDE_NSDELEGATE") != NULL);
if (NSApp != nil) {
if ([NSApp isMemberOfClass:[NSApplication class]] && overrideDelegate) shouldInstall = YES;
if ([NSApp isMemberOfClass:[NSApplication class]]) shouldInstall = YES;
if ([NSApp isKindOfClass:[NSApplicationAWT class]]) shouldInstall = YES;
}
checked = YES;
@@ -416,19 +415,6 @@ AWT_ASSERT_APPKIT_THREAD;
return NSTerminateLater;
}
- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
static BOOL checked = NO;
static BOOL supportsSecureState = YES;
if (checked == NO) {
checked = YES;
if (getenv("AWT_DISABLE_NSDELEGATE_SECURE_SAVE") != NULL) {
supportsSecureState = NO;
}
}
return supportsSecureState;
}
+ (void)_systemWillPowerOff {
[self _notifyJava:com_apple_eawt__AppEventHandler_NOTIFY_SHUTDOWN];
}
@@ -530,10 +516,8 @@ AWT_ASSERT_APPKIT_THREAD;
[dockImageView setImageScaling:NSImageScaleProportionallyUpOrDown];
[dockImageView setImage:image];
if ([ApplicationDelegate sharedDelegate] != nil) {
[[ApplicationDelegate sharedDelegate].fProgressIndicator removeFromSuperview];
[dockImageView addSubview:[ApplicationDelegate sharedDelegate].fProgressIndicator];
}
[[ApplicationDelegate sharedDelegate].fProgressIndicator removeFromSuperview];
[dockImageView addSubview:[ApplicationDelegate sharedDelegate].fProgressIndicator];
// add it to the NSDockTile
[dockTile setContentView: dockImageView];
@@ -546,15 +530,14 @@ AWT_ASSERT_APPKIT_THREAD;
AWT_ASSERT_APPKIT_THREAD;
ApplicationDelegate *delegate = [ApplicationDelegate sharedDelegate];
if (delegate != nil) {
if ([value doubleValue] >= 0 && [value doubleValue] <=100) {
[delegate.fProgressIndicator setDoubleValue:[value doubleValue]];
[delegate.fProgressIndicator setHidden:NO];
} else {
[delegate.fProgressIndicator setHidden:YES];
}
[[NSApp dockTile] display];
if ([value doubleValue] >= 0 && [value doubleValue] <=100) {
[delegate.fProgressIndicator setDoubleValue:[value doubleValue]];
[delegate.fProgressIndicator setHidden:NO];
} else {
[delegate.fProgressIndicator setHidden:YES];
}
[[NSApp dockTile] display];
}
// Obtains the image of the Dock icon, either manually set, a drawn copy, or the default NSApplicationIcon
@@ -665,9 +648,7 @@ JNI_COCOA_ENTER(env);
NSMenu *menu = (NSMenu *)jlong_to_ptr(nsMenuPtr);
[ThreadUtilities performOnMainThreadWaiting:YES block:^(){
if ([ApplicationDelegate sharedDelegate] != nil) {
[ApplicationDelegate sharedDelegate].fDockMenu = menu;
}
[ApplicationDelegate sharedDelegate].fDockMenu = menu;
}];
JNI_COCOA_EXIT(env);
@@ -847,15 +828,13 @@ JNI_COCOA_ENTER(env);
[ThreadUtilities performOnMainThreadWaiting:NO block:^(){
ApplicationDelegate *delegate = [ApplicationDelegate sharedDelegate];
if (delegate != nil) {
switch (menuID) {
case com_apple_eawt__AppMenuBarHandler_MENU_ABOUT:
[delegate _updateAboutMenu:visible enabled:enabled];
break;
case com_apple_eawt__AppMenuBarHandler_MENU_PREFS:
[delegate _updatePreferencesMenu:visible enabled:enabled];
break;
}
switch (menuID) {
case com_apple_eawt__AppMenuBarHandler_MENU_ABOUT:
[delegate _updateAboutMenu:visible enabled:enabled];
break;
case com_apple_eawt__AppMenuBarHandler_MENU_PREFS:
[delegate _updatePreferencesMenu:visible enabled:enabled];
break;
}
}];
@@ -874,9 +853,7 @@ JNI_COCOA_ENTER(env);
CMenuBar *menu = (CMenuBar *)jlong_to_ptr(cMenuBarPtr);
[ThreadUtilities performOnMainThreadWaiting:NO block:^(){
if ([ApplicationDelegate sharedDelegate] != nil) {
[ApplicationDelegate sharedDelegate].fDefaultMenuBar = menu;
}
[ApplicationDelegate sharedDelegate].fDefaultMenuBar = menu;
}];
JNI_COCOA_EXIT(env);

View File

@@ -31,9 +31,6 @@
// Should we query back to Java for a file filter?
jboolean fHasFileFilter;
// Allowed file types
NSArray *fFileTypes;
// sun.awt.CFileDialog
jobject fFileDialog;
@@ -71,8 +68,7 @@
// Allocator
- (id) initWithOwner:(NSWindow*) owner
filter:(jboolean)inHasFilter
fileTypes:(NSArray *)inFileTypes
filter:(jboolean)inHasFilter
fileDialog:(jobject)inDialog
title:(NSString *)inTitle
directory:(NSString *)inPath

View File

@@ -25,7 +25,6 @@
#import <sys/stat.h>
#import <Cocoa/Cocoa.h>
#import <UniformTypeIdentifiers/UniformTypeIdentifiers.h>
#import "ThreadUtilities.h"
#import "JNIUtilities.h"
@@ -41,7 +40,6 @@
- (id)initWithOwner:(NSWindow*)owner
filter:(jboolean)inHasFilter
fileTypes:(NSArray *)inFileTypes
fileDialog:(jobject)inDialog
title:(NSString *)inTitle
directory:(NSString *)inPath
@@ -58,8 +56,6 @@ canCreateDirectories:(BOOL)inCreateDirectories
fOwner = owner;
[fOwner retain];
fHasFileFilter = inHasFilter;
fFileTypes = inFileTypes;
[fFileTypes retain];
fFileDialog = (*env)->NewGlobalRef(env, inDialog);
fDirectory = inPath;
[fDirectory retain];
@@ -88,9 +84,6 @@ canCreateDirectories:(BOOL)inCreateDirectories
}
-(void) dealloc {
[fFileTypes release];
fFileTypes = nil;
[fDirectory release];
fDirectory = nil;
@@ -130,23 +123,6 @@ canCreateDirectories:(BOOL)inCreateDirectories
if (thePanel != nil) {
[thePanel setTitle:fTitle];
if (fFileTypes != nil) {
if (@available(macOS 11, *)) {
int nTypes = (int)[fFileTypes count];
NSMutableArray *contentTypes = [NSMutableArray arrayWithCapacity:nTypes];
for (int i = 0; i < nTypes; ++i) {
NSString *fileType = (NSString *)[fFileTypes objectAtIndex:i];
UTType *contentType = [UTType typeWithFilenameExtension:fileType conformingToType:UTTypeData];
if (contentType != nil) {
[contentTypes addObject:contentType];
}
}
[thePanel setAllowedContentTypes:contentTypes];
} else {
[thePanel setAllowedFileTypes:fFileTypes];
}
}
if (fNavigateApps) {
[thePanel setTreatsFilePackagesAsDirectories:YES];
}
@@ -328,7 +304,7 @@ JNIEXPORT jobjectArray JNICALL
Java_sun_lwawt_macosx_CFileDialog_nativeRunFileDialog
(JNIEnv *env, jobject peer, jlong ownerPtr, jstring title, jint mode, jboolean multipleMode,
jboolean navigateApps, jboolean chooseDirectories, jboolean chooseFiles, jboolean createDirectories,
jboolean hasFilter, jobjectArray allowedFileTypes, jstring directory, jstring file)
jboolean hasFilter, jstring directory, jstring file)
{
jobjectArray returnValue = NULL;
@@ -338,22 +314,8 @@ JNI_COCOA_ENTER(env);
dialogTitle = @" ";
}
NSMutableArray *fileTypes = nil;
if (allowedFileTypes != nil) {
int nTypes = (*env)->GetArrayLength(env, allowedFileTypes);
if (nTypes > 0) {
fileTypes = [NSMutableArray arrayWithCapacity:nTypes];
for (int i = 0; i < nTypes; i++) {
jstring fileType = (jstring)(*env)->GetObjectArrayElement(env, allowedFileTypes, i);
[fileTypes addObject:JavaStringToNSString(env, fileType)];
(*env)->DeleteLocalRef(env, fileType);
}
}
}
CFileDialog *dialogDelegate = [[CFileDialog alloc] initWithOwner:(NSWindow *)jlong_to_ptr(ownerPtr)
filter:hasFilter
fileTypes:fileTypes
fileDialog:peer
title:dialogTitle
directory:JavaStringToNSString(env, directory)

View File

@@ -122,9 +122,9 @@ static void displaycb_handle
if (graphicsEnv == NULL) return; // ref already GC'd
DECLARE_CLASS(jc_CGraphicsEnvironment, "sun/awt/CGraphicsEnvironment");
DECLARE_METHOD(jm_displayReconfiguration,
jc_CGraphicsEnvironment, "_displayReconfiguration","(II)V");
jc_CGraphicsEnvironment, "_displayReconfiguration","(IZ)V");
(*env)->CallVoidMethod(env, graphicsEnv, jm_displayReconfiguration,
(jint) display, (jint) flags);
(jint) display, (jboolean) flags & kCGDisplayRemoveFlag);
(*env)->DeleteLocalRef(env, graphicsEnv);
CHECK_EXCEPTION();
}];

View File

@@ -210,11 +210,9 @@ static BOOL sSetupHelpMenu = NO;
// In theory, this might cause flickering if the window gaining focus
// has its own menu. However, I couldn't reproduce it on practice, so
// perhaps this is a non issue.
if ([ApplicationDelegate sharedDelegate] != nil) {
CMenuBar* defaultMenu = [[ApplicationDelegate sharedDelegate] defaultMenuBar];
if (defaultMenu != nil) {
[CMenuBar activate:defaultMenu modallyDisabled:NO];
}
CMenuBar* defaultMenu = [[ApplicationDelegate sharedDelegate] defaultMenuBar];
if (defaultMenu != nil) {
[CMenuBar activate:defaultMenu modallyDisabled:NO];
}
}
}

View File

@@ -41,9 +41,8 @@
@property (readwrite, assign) int leftInset;
@property (readwrite, atomic) int redrawCount;
@property (readwrite, atomic) NSTimeInterval avgBlitFrameTime;
@property (readwrite, atomic) BOOL perfCountersEnabled;
- (id) initWithJavaLayer:(jobject)layer usePerfCounters:(jboolean)perfCountersEnabled;
- (id) initWithJavaLayer:(jobject)layer;
- (void) blitTexture;
- (void) fillParallelogramCtxX:(jfloat)x
@@ -59,8 +58,6 @@
- (void) stopRedraw:(BOOL)force;
- (void) flushBuffer;
- (void) commitCommandBuffer:(MTLContext*)mtlc wait:(BOOL)waitUntilCompleted display:(BOOL)updateDisplay;
- (void) countFramePresentedCallback;
- (void) countFrameDroppedCallback;
@end
#endif /* MTLLayer_h_Included */

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -35,10 +35,6 @@
#define MAX_DRAWABLE 3
#define LAST_DRAWABLE (MAX_DRAWABLE - 1)
static jclass jc_JavaLayer = NULL;
#define GET_MTL_LAYER_CLASS() \
GET_CLASS(jc_JavaLayer, "sun/java2d/metal/MTLLayer");
const NSTimeInterval DF_BLIT_FRAME_TIME=1.0/120.0;
BOOL isDisplaySyncEnabled() {
@@ -119,7 +115,7 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
@implementation MTLLayer
- (id) initWithJavaLayer:(jobject)layer usePerfCounters:(jboolean)perfCountersEnabled
- (id) initWithJavaLayer:(jobject)layer
{
AWT_ASSERT_APPKIT_THREAD;
// Initialize ourselves
@@ -161,7 +157,6 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
}
self.presentsWithTransaction = NO;
self.avgBlitFrameTime = DF_BLIT_FRAME_TIME;
self.perfCountersEnabled = perfCountersEnabled ? YES : NO;
return self;
}
@@ -226,21 +221,6 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
destinationOrigin:MTLOriginMake(0, 0, 0)];
[blitEncoder endEncoding];
if (@available(macOS 10.15.4, *)) {
if (self.perfCountersEnabled) {
[self retain];
[mtlDrawable addPresentedHandler:^(id <MTLDrawable> drawable) {
// note: called anyway even if drawable.present() not called!
const CFTimeInterval presentedTime = drawable.presentedTime;
if (presentedTime != 0.0) {
[self countFramePresentedCallback];
} else {
[self countFrameDroppedCallback];
}
[self release];
}];
}
}
if (isDisplaySyncEnabled()) {
[commandBuf presentDrawable:mtlDrawable];
} else {
@@ -283,11 +263,11 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
- (void) blitCallback {
JNIEnv *env = [ThreadUtilities getJNIEnv];
GET_MTL_LAYER_CLASS();
DECLARE_CLASS(jc_JavaLayer, "sun/java2d/metal/MTLLayer");
DECLARE_METHOD(jm_drawInMTLContext, jc_JavaLayer, "drawInMTLContext", "()V");
jobject javaLayerLocalRef = (*env)->NewLocalRef(env, self.javaLayer);
if (javaLayerLocalRef == NULL) {
if ((*env)->IsSameObject(env, javaLayerLocalRef, NULL)) {
return;
}
@@ -386,34 +366,6 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
[self startRedraw];
}
}
- (void) countFramePresentedCallback {
// attach the current thread to the JVM if necessary, and get an env
JNIEnv* env = [ThreadUtilities getJNIEnvUncached];
GET_MTL_LAYER_CLASS();
DECLARE_METHOD(jm_countNewFrame, jc_JavaLayer, "countNewFrame", "()V");
jobject javaLayerLocalRef = (*env)->NewLocalRef(env, self.javaLayer);
if (javaLayerLocalRef != NULL) {
(*env)->CallVoidMethod(env, javaLayerLocalRef, jm_countNewFrame);
CHECK_EXCEPTION();
(*env)->DeleteLocalRef(env, javaLayerLocalRef);
}
}
- (void) countFrameDroppedCallback {
// attach the current thread to the JVM if necessary, and get an env
JNIEnv* env = [ThreadUtilities getJNIEnvUncached];
GET_MTL_LAYER_CLASS();
DECLARE_METHOD(jm_countDroppedFrame, jc_JavaLayer, "countDroppedFrame", "()V");
jobject javaLayerLocalRef = (*env)->NewLocalRef(env, self.javaLayer);
if (javaLayerLocalRef != NULL) {
(*env)->CallVoidMethod(env, javaLayerLocalRef, jm_countDroppedFrame);
CHECK_EXCEPTION();
(*env)->DeleteLocalRef(env, javaLayerLocalRef);
}
}
@end
/*
@@ -423,7 +375,7 @@ BOOL MTLLayer_isExtraRedrawEnabled() {
*/
JNIEXPORT jlong JNICALL
Java_sun_java2d_metal_MTLLayer_nativeCreateLayer
(JNIEnv *env, jobject obj, jboolean perfCountersEnabled)
(JNIEnv *env, jobject obj)
{
__block MTLLayer *layer = nil;
@@ -434,7 +386,7 @@ JNI_COCOA_ENTER(env);
[ThreadUtilities performOnMainThreadWaiting:YES block:^(){
AWT_ASSERT_APPKIT_THREAD;
layer = [[MTLLayer alloc] initWithJavaLayer: javaLayer usePerfCounters: perfCountersEnabled];
layer = [[MTLLayer alloc] initWithJavaLayer: javaLayer];
}];
JNI_COCOA_EXIT(env);

View File

@@ -200,21 +200,6 @@
} copy]];
}
- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app
{
static BOOL checked = NO;
static BOOL supportsSecureState = YES;
if (checked == NO) {
checked = YES;
if (getenv("AWT_DISABLE_NSDELEGATE_SECURE_SAVE") != NULL) {
supportsSecureState = NO;
}
}
return supportsSecureState;
}
- (void)processQueuedEventsWithTargetDelegate:(id <NSApplicationDelegate>)delegate
{
self.realDelegate = delegate;

View File

@@ -23,12 +23,10 @@
package com.jetbrains.desktop;
import com.jetbrains.exported.JBRApi;
import sun.awt.ConstrainableGraphics;
import java.awt.geom.Rectangle2D;
@JBRApi.Provided("GraphicsUtils.ConstrainableGraphics2D")
public interface ConstrainableGraphics2D extends ConstrainableGraphics {
public void constrain(Rectangle2D region);
public Object getDestination();

View File

@@ -0,0 +1,65 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.desktop;
import com.jetbrains.internal.JBRApi;
import java.awt.*;
import java.lang.invoke.MethodHandles;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;
public class FontExtensions {
private interface FontExtension {
FontExtension INSTANCE = (FontExtension) JBRApi.internalServiceBuilder(MethodHandles.lookup())
.withStatic("getFeatures", "getFeatures", "java.awt.Font")
.withStatic("isComplexRendering", "isComplexRendering", "java.awt.Font")
.withStatic("isKerning", "isKerning", "java.awt.Font")
.build();
TreeMap<String, Integer> getFeatures(Font font);
boolean isComplexRendering(Font font);
boolean isKerning(Font font);
}
public static String[] featuresToStringArray(Map<String, Integer> features) {
return features.entrySet().stream().map(feature -> (feature.getKey() + "=" + feature.getValue())).
toArray(String[]::new);
}
public static TreeMap<String, Integer> getFeatures(Font font) {
return FontExtension.INSTANCE.getFeatures(font);
}
public static boolean isComplexRendering(Font font) {
return FontExtension.INSTANCE.isComplexRendering(font);
}
public static boolean isKerning(Font font) {
return FontExtension.INSTANCE.isKerning(font);
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2000-2023 JetBrains s.r.o.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.jetbrains.desktop;
import com.jetbrains.internal.JBRApi;
import java.lang.invoke.MethodHandles;
/**
* This class contains mapping between JBR API interfaces and implementation in {@code java.desktop} module.
*/
public class JBRApiModule {
static {
JBRApi.registerModule(MethodHandles.lookup(), JBRApiModule.class.getModule()::addExports)
.service("com.jetbrains.ExtendedGlyphCache")
.withStatic("getSubpixelResolution", "getSubpixelResolution", "sun.font.FontUtilities")
.service("com.jetbrains.JBRFileDialogService")
.withStatic("getFileDialog", "get", "com.jetbrains.desktop.JBRFileDialog")
.proxy("com.jetbrains.JBRFileDialog", "com.jetbrains.desktop.JBRFileDialog")
.service("com.jetbrains.CustomWindowDecoration", "java.awt.Window$CustomWindowDecoration")
.service("com.jetbrains.RoundedCornersManager")
.withStatic("setRoundedCorners", "setRoundedCorners", "sun.lwawt.macosx.CPlatformWindow",
"sun.awt.windows.WWindowPeer")
.service("com.jetbrains.DesktopActions")
.withStatic("setHandler", "setDesktopActionsHandler", "java.awt.Desktop")
.clientProxy("java.awt.Desktop$DesktopActionsHandler", "com.jetbrains.DesktopActions$Handler")
.service("com.jetbrains.ProjectorUtils")
.withStatic("overrideGraphicsEnvironment", "overrideLocalGraphicsEnvironment", "java.awt.GraphicsEnvironment")
.withStatic("setLocalGraphicsEnvironmentProvider", "setLocalGraphicsEnvironmentProvider", "java.awt.GraphicsEnvironment")
.service("com.jetbrains.AccessibleAnnouncer")
.withStatic("announce", "announce", "sun.swing.AccessibleAnnouncer")
.service("com.jetbrains.GraphicsUtils")
.withStatic("createConstrainableGraphics", "create", "com.jetbrains.desktop.JBRGraphicsDelegate")
.clientProxy("com.jetbrains.desktop.ConstrainableGraphics2D", "com.jetbrains.GraphicsUtils$ConstrainableGraphics2D")
.service("com.jetbrains.WindowDecorations", "java.awt.Window$WindowDecorations")
.proxy("com.jetbrains.WindowDecorations$CustomTitleBar", "java.awt.Window$CustomTitleBar")
.service("com.jetbrains.FontExtensions")
.withStatic("getSubpixelResolution", "getSubpixelResolution", "sun.font.FontUtilities")
.withStatic("deriveFontWithFeatures", "deriveFont", "java.awt.Font")
.withStatic("getAvailableFeatures", "getAvailableFeatures", "java.awt.Font")
.service("com.jetbrains.FontOpenTypeFeatures")
.withStatic("getAvailableFeatures", "getAvailableFeatures", "java.awt.Font")
.clientProxy("java.awt.Font$Features", "com.jetbrains.FontExtensions$Features")
.service("com.jetbrains.WindowMove", "java.awt.Window$WindowMoveService")
.service("com.jetbrains.FontMetricsAccessor", "sun.font.FontDesignMetrics$Accessor")
.clientProxy("sun.font.FontDesignMetrics$Overrider", "com.jetbrains.FontMetricsAccessor$Overrider")
;
}
}

View File

@@ -1,17 +1,13 @@
package com.jetbrains.desktop;
import com.jetbrains.exported.JBRApi;
import java.io.Serial;
import java.io.Serializable;
import java.lang.annotation.Native;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.awt.*;
import java.util.Objects;
@JBRApi.Provides("JBRFileDialog")
public final class JBRFileDialog implements Serializable {
public class JBRFileDialog implements Serializable {
@Serial
private static final long serialVersionUID = -9154712118353824660L;
@@ -25,25 +21,32 @@ public final class JBRFileDialog implements Serializable {
throw new Error(e);
}
}
@JBRApi.Provides("JBRFileDialogService#getFileDialog")
public static JBRFileDialog get(FileDialog dialog) {
return (JBRFileDialog) getter.get(dialog);
}
/**
* Whether to select files, directories or both (used when common file dialogs are enabled on Windows, or on macOS)
*/
@Native public static final int SELECT_FILES_HINT = 1, SELECT_DIRECTORIES_HINT = 2;
/**
* Whether to allow creating directories or not (used on macOS)
*/
@Native public static final int CREATE_DIRECTORIES_HINT = 4;
public static final String OPEN_FILE_BUTTON_KEY = "jbrFileDialogOpenFile";
public static final String OPEN_DIRECTORY_BUTTON_KEY = "jbrFileDialogSelectDir";
public static final String ALL_FILES_COMBO_KEY = "jbrFileDialogAllFiles";
public int hints = CREATE_DIRECTORIES_HINT;
/**
* Text for "Open" button (used when common file dialogs are enabled on
* Windows).
*/
public String openButtonText;
/**
* Text for "Select Folder" button (used when common file dialogs are
* enabled on Windows).
*/
public String selectFolderButtonText;
public String allFilesFilterDescription;
public String fileFilterDescription;
public String[] fileFilterExtensions;
public void setHints(int hints) {
this.hints = hints;
@@ -52,24 +55,9 @@ public final class JBRFileDialog implements Serializable {
return hints;
}
public void setLocalizationString(String key, String text) {
Objects.requireNonNull(key);
switch (key) {
case OPEN_FILE_BUTTON_KEY -> openButtonText = text;
case OPEN_DIRECTORY_BUTTON_KEY -> selectFolderButtonText = text;
case ALL_FILES_COMBO_KEY -> allFilesFilterDescription = text;
default -> throw new IllegalArgumentException("unrecognized key: " + key);
}
}
@Deprecated(forRemoval = true)
public void setLocalizationStrings(String openButtonText, String selectFolderButtonText) {
setLocalizationString(OPEN_FILE_BUTTON_KEY, openButtonText);
setLocalizationString(OPEN_DIRECTORY_BUTTON_KEY, selectFolderButtonText);
this.openButtonText = openButtonText;
this.selectFolderButtonText = selectFolderButtonText;
}
public void setFileFilterExtensions(String fileFilterDescription, String[] fileFilterExtensions) {
this.fileFilterDescription = fileFilterDescription;
this.fileFilterExtensions = fileFilterExtensions;
}
}

View File

@@ -23,8 +23,6 @@
package com.jetbrains.desktop;
import com.jetbrains.exported.JBRApi;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
@@ -40,7 +38,6 @@ import java.util.Map;
public class JBRGraphicsDelegate extends Graphics2D implements ConstrainableGraphics2D {
@JBRApi.Provides("GraphicsUtils#createConstrainableGraphics")
public static Graphics2D create(Graphics2D graphics2D,
ConstrainableGraphics2D constrainable) {
return new JBRGraphicsDelegate(graphics2D, constrainable);

View File

@@ -49,7 +49,6 @@ import java.util.Objects;
import javax.swing.JMenuBar;
import com.jetbrains.exported.JBRApi;
import sun.awt.SunToolkit;
import sun.security.util.SecurityConstants;
@@ -1073,7 +1072,6 @@ public class Desktop {
return peer.moveToTrash(file);
}
@JBRApi.Provided("DesktopActions.Handler")
private interface DesktopActionsHandler {
void open(File file) throws IOException;
void edit(File file) throws IOException;
@@ -1106,8 +1104,7 @@ public class Desktop {
}
private static volatile DesktopActions actions;
@JBRApi.Provides("DesktopActions#setHandler")
private static void setDesktopActionsHandler(DesktopActionsHandler h) {
static void setDesktopActionsHandler(DesktopActionsHandler h) {
try {
actions = new DesktopActions(h);
} catch (Exception e) {

View File

@@ -48,12 +48,17 @@ import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.text.AttributedCharacterIterator.Attribute;
import java.text.CharacterIterator;
import java.util.*;
import java.util.EventListener;
import java.util.Hashtable;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import com.jetbrains.exported.JBRApi;
import sun.awt.ComponentFactory;
import sun.font.AttributeMap;
import sun.font.AttributeValues;
import sun.font.CompositeFont;
import sun.font.CoreMetrics;
import sun.font.CreatedFontTracker;
import sun.font.Font2D;
@@ -279,28 +284,6 @@ public class Font implements java.io.Serializable
public FontPeer getFontPeer(final Font font) {
return font.getFontPeer();
}
@Override
public String[] getFeatures(Font font) {
int size = font.featureArray == null ? 0 : font.featureArray.length;
String[] fs = new String[size + 3];
if (size > 0) System.arraycopy(font.featureArray, 0, fs, 0, size);
fs[size++] = KERN_FEATURE + "=" + font.getAttributeValues().getKerning();
fs[size++] = LIGA_FEATURE + "=" + font.getAttributeValues().getLigatures();
fs[size] = CALT_FEATURE + "=" + font.getAttributeValues().getLigatures();
return fs;
}
@Override
public boolean isComplexRendering(Font font) {
return (font.values != null && (font.values.getLigatures() != 0 || font.values.getTracking() != 0 ||
font.values.getBaselineTransform() != null)) || font.featureArray != null;
}
@Override
public boolean isKerning(Font font) {
return font.values != null && (font.values.getKerning() != 0);
}
}
static {
@@ -472,11 +455,10 @@ public class Font implements java.io.Serializable
* Ordered map choose intentionally as field's type. It allows to correctly comparing two Font objects
*
* @serial
* @see #getFeatures
* @see #deriveFont(Font, Features)
*/
private String[] featureArray;
@Deprecated
private TreeMap<String, Integer> features; // Kept for compatibility
private TreeMap<String, Integer> features = new TreeMap<String, Integer>();
/**
* The platform specific font information.
@@ -579,6 +561,10 @@ public class Font implements java.io.Serializable
return font2DHandle.font2D;
}
private boolean anyEnabledFeatures() {
return features.values().stream().anyMatch(x -> x != 0);
}
/**
* Creates a new {@code Font} from the specified name, style and
* point size.
@@ -642,19 +628,18 @@ public class Font implements java.io.Serializable
this.pointSize = size;
}
private Font(String name, int style, float sizePts, String[] features) {
private Font(String name, int style, float sizePts, TreeMap<String, Integer> features) {
this.name = (name != null) ? name : "Default";
this.style = (style & ~0x03) == 0 ? style : 0;
this.size = (int)(sizePts + 0.5);
this.pointSize = sizePts;
this.featureArray = features;
this.hasLayoutAttributes |= this.featureArray != null;
this.features = features;
}
/* This constructor is used by deriveFont when attributes is null */
private Font(String name, int style, float sizePts,
boolean created, boolean withFallback,
Font2DHandle handle, boolean useOldHandle, String[] features) {
Font2DHandle handle, boolean useOldHandle, TreeMap<String, Integer> features) {
this(name, style, sizePts, features);
this.createdFont = created;
this.withFallback = withFallback;
@@ -719,9 +704,9 @@ public class Font implements java.io.Serializable
*/
private Font(AttributeValues values, String oldName, int oldStyle,
boolean created, boolean withFallback,
Font2DHandle handle, boolean useOldHandle, String[] features) {
Font2DHandle handle, boolean useOldHandle, TreeMap<String, Integer> features) {
this.featureArray = features;
this.features = features;
this.createdFont = created;
this.withFallback = withFallback;
if (created || withFallback) {
@@ -755,7 +740,6 @@ public class Font implements java.io.Serializable
this.font2DHandle = handle;
}
initFromValues(values);
this.hasLayoutAttributes |= this.featureArray != null;
}
/**
@@ -784,11 +768,6 @@ public class Font implements java.io.Serializable
* @since 1.6
*/
protected Font(Font font) {
this(font, font.featureArray);
}
private Font(Font font, String[] features) {
this.featureArray = features;
if (font.values != null) {
initFromValues(font.getAttributeValues().clone());
} else {
@@ -797,10 +776,15 @@ public class Font implements java.io.Serializable
this.size = font.size;
this.pointSize = font.pointSize;
}
this.hasLayoutAttributes |= this.featureArray != null;
this.font2DHandle = font.font2DHandle;
this.createdFont = font.createdFont;
this.withFallback = font.withFallback;
this.features = font.features;
}
private Font(Font font, TreeMap<String, Integer> features) {
this(font);
this.features = features;
}
/**
@@ -850,7 +834,7 @@ public class Font implements java.io.Serializable
if (values.getPosture() >= .2f) this.style |= ITALIC; // not == .2f
this.nonIdentityTx = values.anyNonDefault(EXTRA_MASK);
this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK);
this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK);
}
/**
@@ -931,7 +915,7 @@ public class Font implements java.io.Serializable
values.merge(attributes, SECONDARY_MASK);
return new Font(values, font.name, font.style,
font.createdFont, font.withFallback,
font.font2DHandle, false, null);
font.font2DHandle, false, new TreeMap<String, Integer>());
}
return new Font(attributes);
}
@@ -943,7 +927,7 @@ public class Font implements java.io.Serializable
values.merge(attributes, SECONDARY_MASK);
return new Font(values, font.name, font.style,
font.createdFont, font.withFallback,
font.font2DHandle, false, null);
font.font2DHandle, false, new TreeMap<String, Integer>());
}
return font;
@@ -1664,7 +1648,17 @@ public class Font implements java.io.Serializable
* @since 1.6
*/
public boolean hasLayoutAttributes() {
return hasLayoutAttributes;
return anyEnabledFeatures() || hasLayoutAttributes;
}
private static TreeMap<String, Integer> getFeatures(Font font) {
TreeMap<String, Integer> res = new TreeMap<>();
res.putAll(font.features);
res.put(KERN_FEATURE, font.getAttributeValues().getKerning());
res.put(LIGA_FEATURE, font.getAttributeValues().getLigatures());
res.put(CALT_FEATURE, font.getAttributeValues().getLigatures());
return res;
}
/**
@@ -1872,7 +1866,7 @@ public class Font implements java.io.Serializable
*/
public int hashCode() {
if (hash == 0) {
hash = name.hashCode() ^ style ^ size ^ Arrays.hashCode(featureArray);
hash = name.hashCode() ^ style ^ size ^ features.hashCode();
/* It is possible many fonts differ only in transform.
* So include the transform in the hash calculation.
* nonIdentityTx is set whenever there is a transform in
@@ -1911,7 +1905,7 @@ public class Font implements java.io.Serializable
pointSize == font.pointSize &&
withFallback == font.withFallback &&
name.equals(font.name) &&
Arrays.equals(featureArray, font.featureArray)) {
features.equals(font.features)) {
/* 'values' is usually initialized lazily, except when
* the font is constructed from a Map, or derived using
@@ -2017,11 +2011,6 @@ public class Font implements java.io.Serializable
pointSize = (float)size;
}
if (features != null) {
featureArray = features.entrySet().stream().map(e -> e.getKey() + "=" + e.getValue()).toArray(String[]::new);
features = null;
}
// Handle fRequestedAttributes.
// in 1.5, we always streamed out the font values plus
// TRANSFORM, SUPERSCRIPT, and WIDTH, regardless of whether the
@@ -2040,14 +2029,17 @@ public class Font implements java.io.Serializable
}
values = getAttributeValues().merge(extras);
this.nonIdentityTx = values.anyNonDefault(EXTRA_MASK);
this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK);
this.hasLayoutAttributes = values.anyNonDefault(LAYOUT_MASK);
} catch (Throwable t) {
throw new IOException(t);
} finally {
fRequestedAttributes = null; // don't need it any more
}
}
this.hasLayoutAttributes |= this.featureArray != null;
if (features == null) {
features = new TreeMap<>();
}
}
/**
@@ -2151,14 +2143,14 @@ public class Font implements java.io.Serializable
public Font deriveFont(int style, float size){
if (values == null) {
return new Font(name, style, size, createdFont, withFallback,
font2DHandle, false, featureArray);
font2DHandle, false, features);
}
AttributeValues newValues = getAttributeValues().clone();
int oldStyle = (this.style != style) ? this.style : -1;
applyStyle(style, newValues);
newValues.setSize(size);
return new Font(newValues, null, oldStyle, createdFont, withFallback,
font2DHandle, false, featureArray);
font2DHandle, false, features);
}
/**
@@ -2178,7 +2170,7 @@ public class Font implements java.io.Serializable
applyStyle(style, newValues);
applyTransform(trans, newValues);
return new Font(newValues, null, oldStyle, createdFont, withFallback,
font2DHandle, false, featureArray);
font2DHandle, false, features);
}
/**
@@ -2191,12 +2183,12 @@ public class Font implements java.io.Serializable
public Font deriveFont(float size){
if (values == null) {
return new Font(name, style, size, createdFont, withFallback,
font2DHandle, true, featureArray);
font2DHandle, true, features);
}
AttributeValues newValues = getAttributeValues().clone();
newValues.setSize(size);
return new Font(newValues, null, -1, createdFont, withFallback,
font2DHandle, true, featureArray);
font2DHandle, true, features);
}
/**
@@ -2213,7 +2205,7 @@ public class Font implements java.io.Serializable
AttributeValues newValues = getAttributeValues().clone();
applyTransform(trans, newValues);
return new Font(newValues, null, -1, createdFont, withFallback,
font2DHandle, true, featureArray);
font2DHandle, true, features);
}
/**
@@ -2226,13 +2218,13 @@ public class Font implements java.io.Serializable
public Font deriveFont(int style){
if (values == null) {
return new Font(name, style, size, createdFont, withFallback,
font2DHandle, false, featureArray);
font2DHandle, false, features);
}
AttributeValues newValues = getAttributeValues().clone();
int oldStyle = (this.style != style) ? this.style : -1;
applyStyle(style, newValues);
return new Font(newValues, null, oldStyle, createdFont, withFallback,
font2DHandle, false, featureArray);
font2DHandle, false, features);
}
/*
@@ -2270,62 +2262,15 @@ public class Font implements java.io.Serializable
}
}
return new Font(newValues, name, style, createdFont, withFallback,
font2DHandle, keepFont2DHandle, featureArray);
font2DHandle, keepFont2DHandle, features);
}
@JBRApi.Provided("FontExtensions.Features")
@Deprecated(forRemoval = true)
private interface Features {
TreeMap<String, Integer> getAsTreeMap();
}
@JBRApi.Provides("FontExtensions#deriveFontWithFeatures")
@Deprecated(forRemoval = true)
private static Font deriveFont(Font font, Features features) {
TreeMap<String, Integer> map = features.getAsTreeMap();
String[] array = new String[map.size()];
int i = 0;
for (Map.Entry<String, Integer> e : map.entrySet()) {
validateFeature(array[i++] = e.getKey() + "=" + e.getValue());
}
return new Font(font, array);
}
private static void validateFeature(String f) {
int len = f.length();
invalid:if ((len == 4 || len > 5) &&
Character.isLetterOrDigit(f.charAt(0)) &&
Character.isLetterOrDigit(f.charAt(1)) &&
Character.isLetterOrDigit(f.charAt(2)) &&
Character.isLetterOrDigit(f.charAt(3))) {
if (len > 5 && f.charAt(4) == '=') {
for (int i = 5; i < len; i++) {
if (!Character.isDigit(f.charAt(i))) break invalid;
}
if (f.startsWith("kern")) throw new IllegalArgumentException(
"\"kern\" feature is not allowed here, use java.awt.font.TextAttribute.KERNING instead");
if (f.startsWith("liga") || f.startsWith("calt")) throw new IllegalArgumentException(
"\"liga\" and \"calt\" features are not allowed here, use java.awt.font.TextAttribute.LIGATURES instead");
}
return;
}
throw new IllegalArgumentException("Invalid feature: \"" + f + "\", allowed syntax: \"kern\", or \"aalt=2\"");
}
@JBRApi.Provides("FontExtensions#deriveFontWithFeatures")
private static Font deriveFont(Font font, String... features) {
if (features == null || features.length == 0) return new Font(font, null);
for (String f : features) validateFeature(f);
return new Font(font, Arrays.copyOf(features, features.length));
}
/**
* Returns a list of OpenType's features enabled on the current Font.
* @return array of enabled OpenType's features
*/
@JBRApi.Provides("FontExtensions")
private static String[] getEnabledFeatures(Font font) {
return font.featureArray == null ? new String[0] : Arrays.copyOf(font.featureArray, font.featureArray.length);
return new Font(font, features.getAsTreeMap());
}
/**
@@ -2779,12 +2724,20 @@ public class Font implements java.io.Serializable
return metrics.charsBounds(chars, beginIndex, limit - beginIndex);
}
private static boolean isComplexRendering(Font font) {
return (font.values != null && (font.values.getLigatures() != 0 || font.values.getTracking() != 0 ||
font.values.getBaselineTransform() != null)) || font.anyEnabledFeatures();
}
private static boolean isKerning(Font font) {
return font.values != null && (font.values.getKerning() != 0);
}
/**
* Returns a list of OpenType's features supported by current Font.
* Implementation of such logic goes to HarfBuzz library.
* @return set of available OpenType's features
* @return list of OpenType's features concatenated to String
*/
@JBRApi.Provides("FontExtensions")
private static Set<String> getAvailableFeatures(Font font) {
return SunLayoutEngine.getAvailableFeatures(FontUtilities.getFont2D(font));
}

View File

@@ -32,7 +32,6 @@ import java.security.PrivilegedAction;
import java.util.Locale;
import java.util.function.Supplier;
import com.jetbrains.exported.JBRApi;
import sun.awt.PlatformGraphicsInfo;
import sun.font.FontManager;
import sun.font.FontManagerFactory;
@@ -117,12 +116,12 @@ public abstract class GraphicsEnvironment {
return LocalGE.INSTANCE;
}
@JBRApi.Provides("ProjectorUtils")
// JBR API internals
private static void setLocalGraphicsEnvironmentProvider(Supplier<GraphicsEnvironment> geProvider) {
graphicsEnvironmentProvider = geProvider;
}
@JBRApi.Provides("ProjectorUtils#overrideGraphicsEnvironment")
// JBR API internals
private static void overrideLocalGraphicsEnvironment(GraphicsEnvironment overriddenGE) {
setLocalGraphicsEnvironmentProvider(() -> overriddenGE);
}

View File

@@ -47,6 +47,7 @@ import java.io.OptionalDataException;
import java.io.Serial;
import java.io.Serializable;
import java.lang.annotation.Native;
import java.lang.invoke.MethodHandles;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.security.AccessController;
@@ -54,6 +55,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EventListener;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.HashMap;
@@ -69,7 +71,7 @@ import javax.accessibility.AccessibleRole;
import javax.accessibility.AccessibleState;
import javax.accessibility.AccessibleStateSet;
import com.jetbrains.exported.JBRApi;
import com.jetbrains.internal.JBRApi;
import sun.awt.AWTAccessor;
import sun.awt.AWTPermissions;
import sun.awt.AppContext;
@@ -4021,9 +4023,7 @@ public class Window extends Container implements Accessible {
// ************************** Custom title bar support *******************************
@JBRApi.Service
@JBRApi.Provides("WindowDecorations")
private static final class WindowDecorations {
private static class WindowDecorations {
WindowDecorations() { CustomTitleBar.assertSupported(); }
void setCustomTitleBar(Frame frame, CustomTitleBar customTitleBar) { ((Window) frame).setCustomTitleBar(customTitleBar); }
void setCustomTitleBar(Dialog dialog, CustomTitleBar customTitleBar) { ((Window) dialog).setCustomTitleBar(customTitleBar); }
@@ -4031,8 +4031,7 @@ public class Window extends Container implements Accessible {
}
@JBRApi.Provides("WindowDecorations.CustomTitleBar")
private static final class CustomTitleBar implements Serializable {
private static class CustomTitleBar implements Serializable {
@Serial
private static final long serialVersionUID = -2330620200902241173L;
@@ -4127,7 +4126,8 @@ public class Window extends Container implements Accessible {
}
private interface CustomTitleBarPeer {
CustomTitleBarPeer INSTANCE = JBRApi.internalService();
CustomTitleBarPeer INSTANCE = (CustomTitleBarPeer) JBRApi.internalServiceBuilder(MethodHandles.lookup())
.withStatic("update", "updateCustomTitleBar", "sun.awt.windows.WFramePeer", "sun.lwawt.macosx.CPlatformWindow").build();
void update(ComponentPeer peer);
}
@@ -4163,6 +4163,28 @@ public class Window extends Container implements Accessible {
Window updateCustomTitleBarHitTest(boolean allowNativeActions) {
if (customTitleBar == null) return null;
pendingCustomTitleBarHitTest = allowNativeActions ? CustomTitleBar.HIT_TITLEBAR : CustomTitleBar.HIT_CLIENT;
if (customDecorHitTestSpots != null) { // Compatibility bridge, to be removed with old API
Point p = getMousePosition(true);
if (p == null) return this;
// Perform old-style hit test
int result = CustomWindowDecoration.NO_HIT_SPOT;
for (var spot : customDecorHitTestSpots) {
if (spot.getKey().contains(p.x, p.y)) {
result = spot.getValue();
break;
}
}
// Convert old hit test value to new one
pendingCustomTitleBarHitTest = switch (result) {
case CustomWindowDecoration.MINIMIZE_BUTTON -> CustomTitleBar.HIT_MINIMIZE_BUTTON;
case CustomWindowDecoration.MAXIMIZE_BUTTON -> CustomTitleBar.HIT_MAXIMIZE_BUTTON;
case CustomWindowDecoration.CLOSE_BUTTON -> CustomTitleBar.HIT_CLOSE_BUTTON;
case CustomWindowDecoration.NO_HIT_SPOT, CustomWindowDecoration.DRAGGABLE_AREA -> CustomTitleBar.HIT_TITLEBAR;
default -> CustomTitleBar.HIT_CLIENT;
};
// Overwrite hit test value
applyCustomTitleBarHitTest();
}
return this;
}
@@ -4174,6 +4196,130 @@ public class Window extends Container implements Accessible {
customTitleBarHitTest = pendingCustomTitleBarHitTest;
}
// *** Following custom decorations code is kept for backward compatibility and will be removed soon. ***
@Deprecated
private transient volatile boolean hasCustomDecoration;
@Deprecated
private transient volatile List<Map.Entry<Shape, Integer>> customDecorHitTestSpots;
@Deprecated
private transient volatile int customDecorTitleBarHeight = -1; // 0 can be a legal value when no title bar is expected
@Deprecated
private static class CustomWindowDecoration {
CustomWindowDecoration() { CustomTitleBar.assertSupported(); }
@Native public static final int
NO_HIT_SPOT = 0,
OTHER_HIT_SPOT = 1,
MINIMIZE_BUTTON = 2,
MAXIMIZE_BUTTON = 3,
CLOSE_BUTTON = 4,
MENU_BAR = 5,
DRAGGABLE_AREA = 6;
void setCustomDecorationEnabled(Window window, boolean enabled) {
window.hasCustomDecoration = enabled;
setTitleBar(window, enabled ? Math.max(window.customDecorTitleBarHeight, 0.01f) : 0);
}
boolean isCustomDecorationEnabled(Window window) {
return window.hasCustomDecoration;
}
void setCustomDecorationHitTestSpots(Window window, List<Map.Entry<Shape, Integer>> spots) {
window.customDecorHitTestSpots = List.copyOf(spots);
}
List<Map.Entry<Shape, Integer>> getCustomDecorationHitTestSpots(Window window) {
return window.customDecorHitTestSpots;
}
void setCustomDecorationTitleBarHeight(Window window, int height) {
window.customDecorTitleBarHeight = height;
setTitleBar(window, window.hasCustomDecoration ? Math.max(height, 0.01f) : 0);
}
int getCustomDecorationTitleBarHeight(Window window) {
return window.customDecorTitleBarHeight;
}
// Bridge from old to new API
private static void setTitleBar(Window window, float height) {
if (height <= 0.0f) window.setCustomTitleBar(null);
else {
CustomTitleBar t = new CustomTitleBar();
// Old API accepts title bar height with insets, subtract it for new API.
// We use bottom insets here because top insets may change when toggling custom title bar, they are usually equal.
if (window instanceof Frame f && (f.getExtendedState() & Frame.MAXIMIZED_BOTH) != 0) {
height -= window.getInsets().bottom;
}
t.setHeight(Math.max(height, 0.01f));
// In old API versions there were no control buttons on Windows.
if (System.getProperty("os.name").toLowerCase().contains("win")) t.putProperty("controls.visible", false);
window.setCustomTitleBar(t);
}
}
}
private interface WindowMovePeer {
void startMovingWindowTogetherWithMouse(Window window, int mouseButton);
}
private interface WindowMovePeerX11 extends WindowMovePeer {
WindowMovePeerX11 INSTANCE = (WindowMovePeerX11) JBRApi.internalServiceBuilder(MethodHandles.lookup())
.withStatic("startMovingWindowTogetherWithMouse",
"startMovingWindowTogetherWithMouse",
"sun.awt.X11.XWindowPeer")
.build();
}
private interface WindowMovePeerWayland extends WindowMovePeer {
WindowMovePeerWayland INSTANCE = (WindowMovePeerWayland) JBRApi.internalServiceBuilder(MethodHandles.lookup())
.withStatic("startMovingWindowTogetherWithMouse",
"startMovingWindowTogetherWithMouse",
"sun.awt.wl.WLComponentPeer")
.build();
}
private static class WindowMoveService {
WindowMovePeer windowMovePeer;
WindowMoveService() {
var toolkit = Toolkit.getDefaultToolkit();
var ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
if (toolkit == null || ge == null) {
throw new JBRApi.ServiceNotAvailableException("Supported only with a Toolkit present");
}
boolean isWayland = objectIsInstanceOf(toolkit, "sun.awt.wl.WLToolkit");
if (isWayland) {
if (!objectIsInstanceOf(ge,"sun.awt.wl.WLGraphicsEnvironment")) {
throw new JBRApi.ServiceNotAvailableException("On Wayland, supported only with WLGraphicsEnvironment");
}
} else {
if (!objectIsInstanceOf(toolkit, "sun.awt.X11.XToolkit")
|| !objectIsInstanceOf(ge, "sun.awt.X11GraphicsEnvironment")) {
throw new JBRApi.ServiceNotAvailableException("Supported only with XToolkit and X11GraphicsEnvironment");
}
}
if (isWayland) {
windowMovePeer = WindowMovePeerWayland.INSTANCE;
} else {
// This will throw if the service is not supported by the underlying WM
windowMovePeer = WindowMovePeerX11.INSTANCE;
}
}
boolean objectIsInstanceOf(Object o, String className) {
Objects.requireNonNull(o);
return o.getClass().getName().equals(className);
}
void startMovingTogetherWithMouse(Window window, int mouseButton) {
Objects.requireNonNull(window);
windowMovePeer.startMovingWindowTogetherWithMouse(window, mouseButton);
}
}
// ************************** JBR stuff *******************************
private volatile boolean ignoreMouseEvents;

View File

@@ -27,7 +27,6 @@ package sun.font;
import java.awt.Font;
import java.awt.peer.FontPeer;
import java.util.TreeMap;
public abstract class FontAccess {
@@ -48,7 +47,4 @@ public abstract class FontAccess {
public abstract void setWithFallback(Font f);
public abstract boolean isCreatedFont(Font f);
public abstract FontPeer getFontPeer(Font f);
public abstract String[] getFeatures(Font font);
public abstract boolean isComplexRendering(Font font);
public abstract boolean isKerning(Font font);
}

View File

@@ -43,7 +43,7 @@ import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.jetbrains.exported.JBRApi;
import com.jetbrains.desktop.FontExtensions;
import sun.java2d.Disposer;
import sun.java2d.DisposerRecord;
@@ -481,7 +481,7 @@ public final class FontDesignMetrics extends FontMetrics {
assert (data instanceof String || data instanceof char[]);
float width = 0;
if (overrider == null && FontAccess.getFontAccess().isComplexRendering(font) && len > 0) {
if (overrider == null && FontExtensions.isComplexRendering(font) && len > 0) {
return textLayoutBounds(data, off, len);
}
@@ -492,7 +492,7 @@ public final class FontDesignMetrics extends FontMetrics {
return new Rectangle2D.Float(0f, -ascent, width, height);
}
boolean isKerning = FontAccess.getFontAccess().isKerning(font);
boolean isKerning = FontExtensions.isKerning(font);
float consecutiveDoubleCharacterWidth = 0f;
char prev = 0;
for (int i = off; i < off + len; i++) {
@@ -608,9 +608,7 @@ public final class FontDesignMetrics extends FontMetrics {
return height;
}
@JBRApi.Service
@JBRApi.Provides("FontMetricsAccessor")
private static final class Accessor {
private static class Accessor { // used by JBR API
// Keeping metrics instances here prevents them from being garbage collected
// and being re-created by FontDesignMetrics.getMetrics method
private final Set<FontDesignMetrics> PINNED_METRICS = new HashSet<>();
@@ -649,8 +647,7 @@ public final class FontDesignMetrics extends FontMetrics {
}
}
@JBRApi.Provided("FontMetricsAccessor.Overrider")
private interface Overrider {
private interface Overrider { // used by JBR API
float charWidth(int codePoint);
}
}

View File

@@ -33,7 +33,6 @@ import java.security.AccessController;
import java.security.PrivilegedAction;
import javax.swing.plaf.FontUIResource;
import com.jetbrains.exported.JBRApi;
import sun.awt.OSInfo;
import sun.util.logging.PlatformLogger;
@@ -137,8 +136,7 @@ public final class FontUtilities {
});
}
@JBRApi.Provides("FontExtensions")
private static Dimension getSubpixelResolution() {
static Dimension getSubpixelResolution() {
return subpixelResolution;
}

View File

@@ -68,6 +68,8 @@
package sun.font;
import com.jetbrains.desktop.FontExtensions;
import java.lang.ref.SoftReference;
import java.awt.Font;
import java.awt.font.FontRenderContext;
@@ -174,7 +176,7 @@ public final class GlyphLayout {
* leave pt and the gvdata unchanged.
*/
public void layout(FontStrikeDesc sd, float[] mat, float ptSize, int gmask, int baseIndex, TextRecord text,
boolean ltrDirection, String[] features, Point2D.Float pt, GVData data);
boolean ltrDirection, Map<String, Integer> features, Point2D.Float pt, GVData data);
}
/**
@@ -450,7 +452,7 @@ public final class GlyphLayout {
EngineRecord er = _erecords.get(ix);
for (;;) {
try {
er.layout(ltrDirection, FontAccess.getFontAccess().getFeatures(font));
er.layout(ltrDirection, FontExtensions.getFeatures(font));
break;
}
catch (IndexOutOfBoundsException e) {
@@ -656,7 +658,7 @@ public final class GlyphLayout {
this.engine = _lef.getEngine(key); // flags?
}
void layout(boolean ltrDirection, String[] features) {
void layout(boolean ltrDirection, Map<String, Integer> features) {
_textRecord.start = start;
_textRecord.limit = limit;
engine.layout(_sd, _mat, ptSize, gmask, start - _offset, _textRecord,

View File

@@ -30,6 +30,7 @@
package sun.font;
import com.jetbrains.desktop.FontExtensions;
import sun.font.GlyphLayout.*;
import sun.java2d.Disposer;
import sun.java2d.DisposerRecord;
@@ -37,6 +38,7 @@ import sun.java2d.DisposerRecord;
import java.awt.geom.Point2D;
import java.lang.ref.SoftReference;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.WeakHashMap;
@@ -172,7 +174,7 @@ public final class SunLayoutEngine implements LayoutEngine, LayoutEngineFactory
}
public void layout(FontStrikeDesc desc, float[] mat, float ptSize, int gmask,
int baseIndex, TextRecord tr, boolean ltrDirection, String[] features,
int baseIndex, TextRecord tr, boolean ltrDirection, Map<String, Integer> features,
Point2D.Float pt, GVData data) {
Font2D font = key.font();
FontStrike strike = font.getStrike(desc);
@@ -181,7 +183,7 @@ public final class SunLayoutEngine implements LayoutEngine, LayoutEngineFactory
shape(font, strike, ptSize, mat, pFace,
tr.text, data, key.script(),
tr.start, tr.limit, baseIndex, pt,
ltrDirection, features, gmask);
ltrDirection, FontExtensions.featuresToStringArray(features), gmask);
}
}

View File

@@ -53,7 +53,6 @@ import sun.font.FontManagerFactory;
import sun.font.FontManagerForSGE;
import sun.font.FontUtilities;
import sun.java2d.pipe.Region;
import sun.security.action.GetBooleanAction;
import sun.security.action.GetPropertyAction;
/**
@@ -89,21 +88,6 @@ public abstract class SunGraphicsEnvironment extends GraphicsEnvironment
debugScale = uiScaleEnabled ? getScaleFactor("sun.java2d.uiScale") : -1;
}
protected enum LogDisplay {
ADDED,
REMOVED,
CHANGED;
@SuppressWarnings("removal")
public static final boolean ENABLED = AccessController.doPrivileged(new GetBooleanAction("sun.java2d.logDisplays"));
public void log(int id, Object bounds, double scale) {
if (!ENABLED) return;
System.out.println("DISPLAY " + this + ": #" + id +
", " + bounds + ", scale=" + scale);
}
}
protected GraphicsDevice[] screens;
private static boolean isWindows_8_1_orUpper() {

View File

@@ -26,7 +26,6 @@
package sun.swing;
import com.jetbrains.exported.JBRApi;
import jdk.internal.misc.InnocuousThread;
import sun.awt.AWTThreading;
@@ -73,8 +72,7 @@ public class AccessibleAnnouncer {
* @param str string for announcing
* @param priority priority for announcing
*/
@JBRApi.Provides("AccessibleAnnouncer")
public static void announce(Accessible a, final String str, final int priority) {
public static void announce(Accessible a, final String str, final int priority) throws Exception {
if (str == null ||
priority != ANNOUNCE_WITHOUT_INTERRUPTING_CURRENT_OUTPUT &&
priority != ANNOUNCE_WITH_INTERRUPTING_CURRENT_OUTPUT) {

View File

@@ -1,9 +0,0 @@
#version 450
layout(binding = 0) uniform sampler2D texSampler;
layout(location = 0) in vec2 fragTexCoord;
layout(location = 0) out vec4 outColor;
void main() {
outColor = texture(texSampler, fragTexCoord);
}

View File

@@ -1,10 +0,0 @@
#version 450
layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec2 texPosition;
layout(location = 0) out vec2 fragTexCoord;
void main() {
gl_Position = vec4(inPosition, 0.0, 1.0);
fragTexCoord = texPosition;
}

View File

@@ -1,8 +0,0 @@
#version 450
layout(location = 0) in flat vec4 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = fragColor;
}

View File

@@ -1,10 +0,0 @@
#version 450
layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec4 inColor;
layout(location = 0) out flat vec4 fragColor;
void main() {
gl_Position = vec4(inPosition, 0.0, 1.0);
fragColor = inColor;
}

View File

@@ -1,19 +0,0 @@
#version 450
layout(push_constant) uniform PushConstants {
vec4 fragColor;
} pushConstants;
const vec2 positions[4] = vec2[4](
vec2(-1.0, -1.0),
vec2( 1.0, -1.0),
vec2(-1.0, 1.0),
vec2( 1.0, 1.0)
);
layout(location = 0) out flat vec4 fragColor;
void main() {
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
fragColor = pushConstants.fragColor;
}

View File

@@ -1,8 +1,8 @@
#version 450
layout(location = 0) in flat vec4 fragColor;
layout(location = 0) in vec4 inColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = fragColor;
outColor = inColor;
}

View File

@@ -0,0 +1,21 @@
#version 450
layout(push_constant) uniform Push {
vec2 invViewport2; // 2.0/viewport
} push;
vec4 colors[3] = vec4[](
vec4(1,0,0,1),
vec4(0,1,0,1),
vec4(0,0,1,1)
);
layout(location = 0) in vec2 inPosition;
layout(location = 0) out vec4 outColor;
void main() {
outColor = colors[gl_VertexIndex % 3];
gl_Position = vec4(inPosition * push.invViewport2 - 1.0, 0.0, 1.0);
gl_PointSize = 1.0f;
}

View File

@@ -1,32 +0,0 @@
#include <memory.h>
#include "CArrayUtil.h"
#define MIN(a,b) (((a)<(b))?(a):(b))
void* CARR_array_alloc(size_t elem_size, size_t capacity) {
CARR_array_t *pvec = malloc(elem_size * capacity + offsetof(CARR_array_t, data));
if (pvec == NULL) {
return NULL;
}
pvec->elem_size = elem_size;
pvec->size = 0;
pvec->capacity = capacity;
return pvec->data;
}
void* CARR_array_realloc(CARR_array_t* vec, size_t new_capacity) {
if (vec->capacity == new_capacity) {
return vec->data;
}
CARR_array_t* new_vec =
(CARR_array_t*)((char*)CARR_array_alloc(vec->elem_size, new_capacity) - offsetof(CARR_array_t, data));
if (new_vec == NULL) {
return NULL;
}
new_vec->capacity = new_capacity;
new_vec->size = MIN(vec->size, new_capacity);
new_vec->elem_size = vec->elem_size;
memcpy(new_vec->data, vec->data, new_vec->size*new_vec->elem_size);
free(vec);
return new_vec->data;
}

View File

@@ -1,84 +0,0 @@
#ifndef C_ARRAY_UTIL_H
#define C_ARRAY_UTIL_H
#include <stdio.h>
#include <malloc.h>
#include <assert.h>
#define ARRAY_CAPACITY_MULT 2
typedef struct {
size_t elem_size;
size_t size;
size_t capacity;
char data[];
} CARR_array_t;
void* CARR_array_alloc(size_t elem_size, size_t capacity);
void* CARR_array_realloc(CARR_array_t* vec, size_t new_capacity);
/**
* Allocate array
* @param T type of elements
* @param SIZE size of the array
*/
#define ARRAY_ALLOC(T, SIZE) (T*)CARR_array_alloc(sizeof(T), SIZE)
#define ARRAY_T(P) (CARR_array_t *)((char*)P - offsetof(CARR_array_t, data))
/**
* @param P pointer to the first data element of the array
* @return size of the array
*/
#define ARRAY_SIZE(P) (ARRAY_T(P))->size
/**
* @param P pointer to the first data element of the array
* @return capacity of the array
*/
#define ARRAY_CAPACITY(P) (ARRAY_T(P))->capacity
/**
* @param P pointer to the first data element of the array
* @return last element in the array
*/
#define ARRAY_LAST(P) (P[(ARRAY_T(P))->size - 1])
/**
* Deallocate the vector
* @param P pointer to the first data element of the array
*/
#define ARRAY_FREE(P) free(ARRAY_T(P))
/**
* Apply function to the vector
* @param P pointer to the first data element of the array
* @param F function to apply
*/
#define ARRAY_APPLY(P, F) do { \
for (uint32_t _i = 0; _i < ARRAY_SIZE(P); _i++) F(&(P[_i])); \
} while(0)
/**
* Shrink capacity of the array to its size
* @param PP pointer to the pointer to the first data element of the array
*/
#define ARRAY_SHRINK_TO_FIT(PP) do { \
*PP = CARR_array_realloc(ARRAY_T(*PP), ARRAY_SIZE(*PP)); \
} while(0)
/**
* Add element to the end of the array
* @param PP pointer to the pointer to the first data element of the array
*/
#define ARRAY_PUSH_BACK(PP, D) do { \
if (ARRAY_SIZE(*PP) >= ARRAY_CAPACITY(*PP)) { \
*PP = CARR_array_realloc(ARRAY_T(*PP), ARRAY_SIZE(*PP)*ARRAY_CAPACITY_MULT);\
} \
*(*PP + ARRAY_SIZE(*PP)) = (D); \
ARRAY_SIZE(*PP)++; \
} while(0)
#define SARRAY_COUNT_OF(STATIC_ARRAY) (sizeof(STATIC_ARRAY)/sizeof(STATIC_ARRAY[0]))
#endif // CARRAYUTILS_H

View File

@@ -1,812 +0,0 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, JetBrains s.r.o.. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <malloc.h>
#include <Trace.h>
#include "jvm_md.h"
#include "VKBase.h"
#include "VKVertex.h"
#include "CArrayUtil.h"
#include <vulkan/vulkan.h>
#include <dlfcn.h>
#include <string.h>
#define VULKAN_DLL JNI_LIB_NAME("vulkan")
#define VULKAN_1_DLL VERSIONED_JNI_LIB_NAME("vulkan", "1")
static const uint32_t REQUIRED_VULKAN_VERSION = VK_MAKE_API_VERSION(0, 1, 2, 0);
#define MAX_ENABLED_LAYERS 5
#define MAX_ENABLED_EXTENSIONS 5
#define VALIDATION_LAYER_NAME "VK_LAYER_KHRONOS_validation"
#define COUNT_OF(x) (sizeof(x)/sizeof(x[0]))
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
extern struct wl_display *wl_display;
#endif
static jboolean verbose;
static jint requestedDeviceNumber = -1;
static VKGraphicsEnvironment* geInstance = NULL;
static void* pVulkanLib = NULL;
#define DEBUG
#define INCLUDE_BYTECODE
#define SHADER_ENTRY(NAME, TYPE) static uint32_t NAME ## _ ## TYPE ## _data[] = {
#define BYTECODE_END };
#include "vulkan/shader_list.h"
#undef INCLUDE_BYTECODE
#undef SHADER_ENTRY
#undef BYTECODE_END
#define DEF_VK_PROC_RET_IF_ERR(INST, NAME, RETVAL) PFN_ ## NAME NAME = (PFN_ ## NAME ) vulkanLibProc(INST, #NAME); \
if (NAME == NULL) { \
J2dRlsTraceLn1(J2D_TRACE_ERROR, "Required api is not supported. %s is missing.", #NAME)\
vulkanLibClose(); \
return RETVAL; \
}
#define VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(SNAME, NAME) do { \
SNAME->NAME = (PFN_ ## NAME ) vulkanLibProc( SNAME->vkInstance, #NAME); \
if (SNAME->NAME == NULL) { \
J2dRlsTraceLn1(J2D_TRACE_ERROR, "Required api is not supported. %s is missing.", #NAME)\
vulkanLibClose(); \
return NULL; \
} \
} while (0)
static void vulkanLibClose() {
if (pVulkanLib != NULL) {
if (geInstance != NULL) {
if (geInstance->layers != NULL) {
free(geInstance->layers);
}
if (geInstance->extensions != NULL) {
free(geInstance->extensions);
}
ARRAY_FREE(geInstance->physicalDevices);
if (geInstance->devices != NULL) {
PFN_vkDestroyDevice vkDestroyDevice = vulkanLibProc(geInstance->vkInstance, "vkDestroyDevice");
for (uint32_t i = 0; i < ARRAY_SIZE(geInstance->devices); i++) {
if (geInstance->devices[i].enabledExtensions != NULL) {
free(geInstance->devices[i].enabledExtensions);
}
if (geInstance->devices[i].enabledLayers != NULL) {
free(geInstance->devices[i].enabledLayers);
}
if (geInstance->devices[i].name != NULL) {
free(geInstance->devices[i].name);
}
if (vkDestroyDevice != NULL && geInstance->devices[i].device != NULL) {
vkDestroyDevice(geInstance->devices[i].device, NULL);
}
}
free(geInstance->devices);
}
if (geInstance->vkInstance != NULL) {
PFN_vkDestroyInstance vkDestroyInstance = vulkanLibProc(geInstance->vkInstance, "vkDestroyInstance");
if (vkDestroyInstance != NULL) {
vkDestroyInstance(geInstance->vkInstance, NULL);
}
}
free(geInstance);
geInstance = NULL;
}
dlclose(pVulkanLib);
pVulkanLib = NULL;
}
}
void* vulkanLibProc(VkInstance vkInstance, char* procName) {
static PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = NULL;
if (pVulkanLib == NULL) {
pVulkanLib = dlopen(VULKAN_DLL, RTLD_NOW);
if (pVulkanLib == NULL) {
pVulkanLib = dlopen(VULKAN_1_DLL, RTLD_NOW);
}
if (pVulkanLib == NULL) {
J2dRlsTrace1(J2D_TRACE_ERROR, "Failed to load %s\n", VULKAN_DLL)
return NULL;
}
if (!vkGetInstanceProcAddr) {
vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr) dlsym(pVulkanLib, "vkGetInstanceProcAddr");
if (vkGetInstanceProcAddr == NULL) {
J2dRlsTrace1(J2D_TRACE_ERROR,
"Failed to get proc address of vkGetInstanceProcAddr from %s\n", VULKAN_DLL)
return NULL;
}
}
}
void* vkProc = vkGetInstanceProcAddr(vkInstance, procName);
if (vkProc == NULL) {
J2dRlsTrace1(J2D_TRACE_ERROR, "%s is not supported\n", procName)
return NULL;
}
return vkProc;
}
jboolean VK_Init(jboolean verb, jint requestedDevice) {
verbose = verb;
if (VKGE_graphics_environment() == NULL) {
return JNI_FALSE;
}
if (!VK_FindDevices()) {
return JNI_FALSE;
}
if (!VK_CreateLogicalDevice(requestedDevice)) {
return JNI_FALSE;
}
return VK_CreateLogicalDeviceRenderers();
}
static const char* physicalDeviceTypeString(VkPhysicalDeviceType type)
{
switch (type)
{
#define STR(r) case VK_PHYSICAL_DEVICE_TYPE_ ##r: return #r
STR(OTHER);
STR(INTEGRATED_GPU);
STR(DISCRETE_GPU);
STR(VIRTUAL_GPU);
STR(CPU);
#undef STR
default: return "UNKNOWN_DEVICE_TYPE";
}
}
VKGraphicsEnvironment* VKGE_graphics_environment() {
if (geInstance == NULL) {
DEF_VK_PROC_RET_IF_ERR(VK_NULL_HANDLE, vkEnumerateInstanceVersion, NULL);
DEF_VK_PROC_RET_IF_ERR(VK_NULL_HANDLE, vkEnumerateInstanceExtensionProperties, NULL);
DEF_VK_PROC_RET_IF_ERR(VK_NULL_HANDLE, vkEnumerateInstanceLayerProperties, NULL);
DEF_VK_PROC_RET_IF_ERR(VK_NULL_HANDLE, vkCreateInstance, NULL);
uint32_t apiVersion = 0;
if (vkEnumerateInstanceVersion(&apiVersion) != VK_SUCCESS) {
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: unable to enumerate Vulkan instance version\n")
vulkanLibClose();
return NULL;
}
J2dRlsTrace3(J2D_TRACE_INFO, "Vulkan: Available (%d.%d.%d)\n",
VK_API_VERSION_MAJOR(apiVersion),
VK_API_VERSION_MINOR(apiVersion),
VK_API_VERSION_PATCH(apiVersion))
if (apiVersion < REQUIRED_VULKAN_VERSION) {
J2dRlsTrace3(J2D_TRACE_ERROR, "Vulkan: Unsupported version. Required at least (%d.%d.%d)\n",
VK_API_VERSION_MAJOR(REQUIRED_VULKAN_VERSION),
VK_API_VERSION_MINOR(REQUIRED_VULKAN_VERSION),
VK_API_VERSION_PATCH(REQUIRED_VULKAN_VERSION))
vulkanLibClose();
return NULL;
}
geInstance = (VKGraphicsEnvironment*)malloc(sizeof(VKGraphicsEnvironment));
if (geInstance == NULL) {
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate VKGraphicsEnvironment\n")
vulkanLibClose();
return NULL;
}
*geInstance = (VKGraphicsEnvironment) {};
uint32_t extensionsCount;
// Get the number of extensions and layers
if (vkEnumerateInstanceExtensionProperties(NULL,
&extensionsCount,
NULL) != VK_SUCCESS)
{
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: vkEnumerateInstanceExtensionProperties fails\n")
vulkanLibClose();
return NULL;
}
geInstance->extensions = ARRAY_ALLOC(VkExtensionProperties, extensionsCount);
if (geInstance->extensions == NULL) {
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate VkExtensionProperties\n")
vulkanLibClose();
return NULL;
}
if (vkEnumerateInstanceExtensionProperties(NULL, &extensionsCount,
geInstance->extensions) != VK_SUCCESS)
{
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: vkEnumerateInstanceExtensionProperties fails\n")
vulkanLibClose();
return NULL;
}
ARRAY_SIZE(geInstance->extensions) = extensionsCount;
uint32_t layersCount;
if (vkEnumerateInstanceLayerProperties(&layersCount, NULL) != VK_SUCCESS)
{
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: vkEnumerateInstanceLayerProperties fails\n")
vulkanLibClose();
return NULL;
}
geInstance->layers = ARRAY_ALLOC(VkLayerProperties, layersCount);
if (geInstance->layers == NULL) {
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate VkLayerProperties\n")
vulkanLibClose();
return NULL;
}
if (vkEnumerateInstanceLayerProperties(&layersCount,
geInstance->layers) != VK_SUCCESS)
{
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: vkEnumerateInstanceLayerProperties fails\n")
vulkanLibClose();
return NULL;
}
ARRAY_SIZE(geInstance->layers) = layersCount;
J2dRlsTrace(J2D_TRACE_VERBOSE, " Supported instance layers:\n")
for (uint32_t i = 0; i < layersCount; i++) {
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char *) geInstance->layers[i].layerName)
}
J2dRlsTrace(J2D_TRACE_VERBOSE, " Supported instance extensions:\n")
for (uint32_t i = 0; i < extensionsCount; i++) {
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char *) geInstance->extensions[i].extensionName)
}
pchar* enabledLayers = ARRAY_ALLOC(pchar, MAX_ENABLED_LAYERS);
pchar* enabledExtensions = ARRAY_ALLOC(pchar, MAX_ENABLED_EXTENSIONS);
void *pNext = NULL;
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
ARRAY_PUSH_BACK(&enabledExtensions, VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
#endif
ARRAY_PUSH_BACK(&enabledExtensions, VK_KHR_SURFACE_EXTENSION_NAME);
// Check required layers & extensions.
for (uint32_t i = 0; i < ARRAY_SIZE(enabledExtensions); i++) {
int notFound = 1;
for (uint32_t j = 0; j < extensionsCount; j++) {
if (strcmp((char *) geInstance->extensions[j].extensionName, enabledExtensions[i]) == 0) {
notFound = 0;
break;
}
}
if (notFound) {
J2dRlsTrace1(J2D_TRACE_ERROR, "Vulkan: Required extension %s not found\n", enabledExtensions[i])
vulkanLibClose();
return NULL;
}
}
// Configure validation
#ifdef DEBUG
VkValidationFeatureEnableEXT enables[] = {
// VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT,
// VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_RESERVE_BINDING_SLOT_EXT,
VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT,
// VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT,
VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT
};
VkValidationFeaturesEXT features = {};
features.sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT;
features.enabledValidationFeatureCount = COUNT_OF(enables);
features.pEnabledValidationFeatures = enables;
// Includes the validation features into the instance creation process
int foundDebugLayer = 0;
for (uint32_t i = 0; i < layersCount; i++) {
if (strcmp((char *) geInstance->layers[i].layerName, VALIDATION_LAYER_NAME) == 0) {
foundDebugLayer = 1;
break;
}
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char *) geInstance->layers[i].layerName)
}
int foundDebugExt = 0;
for (uint32_t i = 0; i < extensionsCount; i++) {
if (strcmp((char *) geInstance->extensions[i].extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0) {
foundDebugExt = 1;
break;
}
}
if (foundDebugLayer && foundDebugExt) {
ARRAY_PUSH_BACK(&enabledLayers, VALIDATION_LAYER_NAME);
ARRAY_PUSH_BACK(&enabledExtensions, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
pNext = &features;
} else {
J2dRlsTrace2(J2D_TRACE_WARNING, "Vulkan: %s and %s are not supported\n",
VALIDATION_LAYER_NAME, VK_EXT_DEBUG_UTILS_EXTENSION_NAME)
}
#endif
VkApplicationInfo applicationInfo = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pNext = NULL,
.pApplicationName = "OpenJDK",
.applicationVersion = 0,
.pEngineName = "OpenJDK",
.engineVersion = 0,
.apiVersion = REQUIRED_VULKAN_VERSION
};
VkInstanceCreateInfo instanceCreateInfo = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pNext =pNext,
.flags = 0,
.pApplicationInfo = &applicationInfo,
.enabledLayerCount = ARRAY_SIZE(enabledLayers),
.ppEnabledLayerNames = (const char *const *) enabledLayers,
.enabledExtensionCount = ARRAY_SIZE(enabledExtensions),
.ppEnabledExtensionNames = (const char *const *) enabledExtensions
};
if (vkCreateInstance(&instanceCreateInfo, NULL, &geInstance->vkInstance) != VK_SUCCESS) {
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Failed to create Vulkan instance\n")
vulkanLibClose();
ARRAY_FREE(enabledLayers);
ARRAY_FREE(enabledExtensions);
return NULL;
} else {
J2dRlsTrace(J2D_TRACE_INFO, "Vulkan: Instance Created\n")
}
ARRAY_FREE(enabledLayers);
ARRAY_FREE(enabledExtensions);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkEnumeratePhysicalDevices);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetPhysicalDeviceFeatures2);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetPhysicalDeviceProperties2);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetPhysicalDeviceQueueFamilyProperties);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkEnumerateDeviceLayerProperties);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkEnumerateDeviceExtensionProperties);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateShaderModule);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreatePipelineLayout);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateGraphicsPipelines);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkDestroyShaderModule);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetPhysicalDeviceSurfaceCapabilitiesKHR);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetPhysicalDeviceSurfaceFormatsKHR);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetPhysicalDeviceSurfacePresentModesKHR);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateSwapchainKHR);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetSwapchainImagesKHR);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateImageView);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateFramebuffer);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateCommandPool);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkAllocateCommandBuffers);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateSemaphore);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateFence);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetDeviceQueue);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkWaitForFences);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkResetFences);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkAcquireNextImageKHR);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkResetCommandBuffer);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkQueueSubmit);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkQueuePresentKHR);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkBeginCommandBuffer);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdBeginRenderPass);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdBindPipeline);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdSetViewport);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdSetScissor);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdDraw);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkEndCommandBuffer);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdEndRenderPass);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateImage);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateSampler);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkAllocateMemory);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetPhysicalDeviceMemoryProperties);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkBindImageMemory);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateDescriptorSetLayout);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkUpdateDescriptorSets);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateDescriptorPool);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkAllocateDescriptorSets);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdBindDescriptorSets);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetImageMemoryRequirements);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateBuffer);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetBufferMemoryRequirements);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkBindBufferMemory);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkMapMemory);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkUnmapMemory);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdBindVertexBuffers);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateRenderPass);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkDestroyBuffer);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkFreeMemory);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkDestroyImageView);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkDestroyImage);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkDestroyFramebuffer);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkFlushMappedMemoryRanges);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCmdPushConstants);
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkGetPhysicalDeviceWaylandPresentationSupportKHR);
VKGE_INIT_VK_PROC_RET_NULL_IF_ERR(geInstance, vkCreateWaylandSurfaceKHR);
#endif
}
return geInstance;
}
jboolean VK_FindDevices() {
uint32_t physicalDevicesCount;
if (geInstance->vkEnumeratePhysicalDevices(geInstance->vkInstance,
&physicalDevicesCount,
NULL) != VK_SUCCESS)
{
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: vkEnumeratePhysicalDevices fails\n")
vulkanLibClose();
return JNI_FALSE;
}
if (physicalDevicesCount == 0) {
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Failed to find GPUs with Vulkan support\n")
vulkanLibClose();
return JNI_FALSE;
} else {
J2dRlsTrace1(J2D_TRACE_INFO, "Vulkan: Found %d physical devices:\n", physicalDevicesCount)
}
geInstance->physicalDevices = ARRAY_ALLOC(VkPhysicalDevice, physicalDevicesCount);
if (geInstance->physicalDevices == NULL) {
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate VkPhysicalDevice\n")
vulkanLibClose();
return JNI_FALSE;
}
if (geInstance->vkEnumeratePhysicalDevices(
geInstance->vkInstance,
&physicalDevicesCount,
geInstance->physicalDevices) != VK_SUCCESS)
{
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: vkEnumeratePhysicalDevices fails\n")
vulkanLibClose();
return JNI_FALSE;
}
geInstance->devices = ARRAY_ALLOC(VKLogicalDevice, physicalDevicesCount);
if (geInstance->devices == NULL) {
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate VKLogicalDevice\n")
vulkanLibClose();
return JNI_FALSE;
}
for (uint32_t i = 0; i < physicalDevicesCount; i++) {
VkPhysicalDeviceVulkan12Features device12Features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
.pNext = NULL
};
VkPhysicalDeviceFeatures2 deviceFeatures2 = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.pNext = &device12Features
};
geInstance->vkGetPhysicalDeviceFeatures2(geInstance->physicalDevices[i], &deviceFeatures2);
VkPhysicalDeviceProperties2 deviceProperties2 = {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2};
geInstance->vkGetPhysicalDeviceProperties2(geInstance->physicalDevices[i], &deviceProperties2);
J2dRlsTrace5(J2D_TRACE_INFO, "\t- %s (%d.%d.%d, %s) ",
(const char *) deviceProperties2.properties.deviceName,
VK_API_VERSION_MAJOR(deviceProperties2.properties.apiVersion),
VK_API_VERSION_MINOR(deviceProperties2.properties.apiVersion),
VK_API_VERSION_PATCH(deviceProperties2.properties.apiVersion),
physicalDeviceTypeString(deviceProperties2.properties.deviceType))
if (!deviceFeatures2.features.logicOp) {
J2dRlsTrace(J2D_TRACE_INFO, " - hasLogicOp not supported, skipped \n")
continue;
}
if (!device12Features.timelineSemaphore) {
J2dRlsTrace(J2D_TRACE_INFO, " - hasTimelineSemaphore not supported, skipped \n")
continue;
}
J2dRlsTrace(J2D_TRACE_INFO, "\n")
uint32_t queueFamilyCount = 0;
geInstance->vkGetPhysicalDeviceQueueFamilyProperties(
geInstance->physicalDevices[i], &queueFamilyCount, NULL);
VkQueueFamilyProperties *queueFamilies = (VkQueueFamilyProperties*)calloc(queueFamilyCount,
sizeof(VkQueueFamilyProperties));
if (queueFamilies == NULL) {
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate VkQueueFamilyProperties\n")
vulkanLibClose();
return JNI_FALSE;
}
geInstance->vkGetPhysicalDeviceQueueFamilyProperties(
geInstance->physicalDevices[i], &queueFamilyCount, queueFamilies);
int64_t queueFamily = -1;
for (uint32_t j = 0; j < queueFamilyCount; j++) {
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
VkBool32 presentationSupported =
geInstance->vkGetPhysicalDeviceWaylandPresentationSupportKHR(
geInstance->physicalDevices[i], j, wl_display);
#endif
char logFlags[5] = {
queueFamilies[j].queueFlags & VK_QUEUE_GRAPHICS_BIT ? 'G' : '-',
queueFamilies[j].queueFlags & VK_QUEUE_COMPUTE_BIT ? 'C' : '-',
queueFamilies[j].queueFlags & VK_QUEUE_TRANSFER_BIT ? 'T' : '-',
queueFamilies[j].queueFlags & VK_QUEUE_SPARSE_BINDING_BIT ? 'S' : '-',
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
presentationSupported ? 'P' : '-'
#else
'-'
#endif
};
J2dRlsTrace3(J2D_TRACE_INFO, " %d queues in family (%.*s)\n", queueFamilies[j].queueCount, 5,
logFlags)
// TODO use compute workloads? Separate transfer-only DMA queue?
if (queueFamily == -1 && (queueFamilies[j].queueFlags & VK_QUEUE_GRAPHICS_BIT)
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
&& presentationSupported
#endif
) {
queueFamily = j;
}
}
free(queueFamilies);
if (queueFamily == -1) {
J2dRlsTrace(J2D_TRACE_INFO, " --------------------- Suitable queue not found, skipped \n")
continue;
}
uint32_t layerCount;
geInstance->vkEnumerateDeviceLayerProperties(geInstance->physicalDevices[i], &layerCount, NULL);
VkLayerProperties *layers = (VkLayerProperties *) calloc(layerCount, sizeof(VkLayerProperties));
if (layers == NULL) {
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate VkLayerProperties\n")
vulkanLibClose();
return JNI_FALSE;
}
geInstance->vkEnumerateDeviceLayerProperties(geInstance->physicalDevices[i], &layerCount, layers);
J2dRlsTrace(J2D_TRACE_VERBOSE, " Supported device layers:\n")
for (uint32_t j = 0; j < layerCount; j++) {
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char *) layers[j].layerName)
}
uint32_t extensionCount;
geInstance->vkEnumerateDeviceExtensionProperties(geInstance->physicalDevices[i], NULL, &extensionCount, NULL);
VkExtensionProperties *extensions = (VkExtensionProperties *) calloc(
extensionCount, sizeof(VkExtensionProperties));
if (extensions == NULL) {
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate VkExtensionProperties\n")
vulkanLibClose();
return JNI_FALSE;
}
geInstance->vkEnumerateDeviceExtensionProperties(
geInstance->physicalDevices[i], NULL, &extensionCount, extensions);
J2dRlsTrace(J2D_TRACE_VERBOSE, " Supported device extensions:\n")
VkBool32 hasSwapChain = VK_FALSE;
for (uint32_t j = 0; j < extensionCount; j++) {
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char *) extensions[j].extensionName)
hasSwapChain = hasSwapChain ||
strcmp(VK_KHR_SWAPCHAIN_EXTENSION_NAME, extensions[j].extensionName) == 0;
}
free(extensions);
J2dRlsTrace(J2D_TRACE_VERBOSE, " Found:\n")
if (hasSwapChain) {
J2dRlsTrace(J2D_TRACE_VERBOSE, " VK_KHR_SWAPCHAIN_EXTENSION_NAME\n")
}
if (!hasSwapChain) {
J2dRlsTrace(J2D_TRACE_INFO,
" --------------------- Required VK_KHR_SWAPCHAIN_EXTENSION_NAME not found, skipped \n")
continue;
}
pchar* deviceEnabledLayers = ARRAY_ALLOC(pchar, MAX_ENABLED_LAYERS);
if (deviceEnabledLayers == NULL) {
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate deviceEnabledLayers array\n")
vulkanLibClose();
return JNI_FALSE;
}
pchar* deviceEnabledExtensions = ARRAY_ALLOC(pchar, MAX_ENABLED_EXTENSIONS);
if (deviceEnabledExtensions == NULL) {
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot allocate deviceEnabledExtensions array\n")
vulkanLibClose();
return JNI_FALSE;
}
ARRAY_PUSH_BACK(&deviceEnabledExtensions, VK_KHR_SWAPCHAIN_EXTENSION_NAME);
// Validation layer
#ifdef DEBUG
int validationLayerNotSupported = 1;
for (uint32_t j = 0; j < layerCount; j++) {
if (strcmp(VALIDATION_LAYER_NAME, layers[j].layerName) == 0) {
validationLayerNotSupported = 0;
ARRAY_PUSH_BACK(&deviceEnabledLayers, VALIDATION_LAYER_NAME);
break;
}
}
if (validationLayerNotSupported) {
J2dRlsTrace1(J2D_TRACE_INFO, " %s device layer is not supported\n", VALIDATION_LAYER_NAME)
}
#endif
free(layers);
char* deviceName = strdup(deviceProperties2.properties.deviceName);
if (deviceName == NULL) {
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: Cannot duplicate deviceName\n")
vulkanLibClose();
return JNI_FALSE;
}
ARRAY_PUSH_BACK(&geInstance->devices,
((VKLogicalDevice) {
.name = deviceName,
.device = VK_NULL_HANDLE,
.physicalDevice = geInstance->physicalDevices[i],
.queueFamily = queueFamily,
.enabledLayers = deviceEnabledLayers,
.enabledExtensions = deviceEnabledExtensions,
}));
}
if (ARRAY_SIZE(geInstance->devices) == 0) {
J2dRlsTrace(J2D_TRACE_ERROR, "No compatible device found\n")
vulkanLibClose();
return JNI_FALSE;
}
return JNI_TRUE;
}
jboolean VK_CreateLogicalDevice(jint requestedDevice) {
requestedDeviceNumber = requestedDevice;
if (geInstance == NULL) {
J2dRlsTrace(J2D_TRACE_ERROR, "Vulkan: VKGraphicsEnvironment is not initialized\n")
return JNI_FALSE;
}
DEF_VK_PROC_RET_IF_ERR(geInstance->vkInstance, vkCreateDevice, JNI_FALSE)
DEF_VK_PROC_RET_IF_ERR(geInstance->vkInstance, vkCreatePipelineCache, JNI_FALSE)
DEF_VK_PROC_RET_IF_ERR(geInstance->vkInstance, vkCreateRenderPass, JNI_FALSE)
requestedDeviceNumber = (requestedDeviceNumber == -1) ? 0 : requestedDeviceNumber;
if (requestedDeviceNumber < 0 || (uint32_t)requestedDeviceNumber >= ARRAY_SIZE(geInstance->devices)) {
if (verbose) {
fprintf(stderr, " Requested device number (%d) not found, fallback to 0\n", requestedDeviceNumber);
}
requestedDeviceNumber = 0;
}
geInstance->enabledDeviceNum = requestedDeviceNumber;
if (verbose) {
for (uint32_t i = 0; i < ARRAY_SIZE(geInstance->devices); i++) {
fprintf(stderr, " %c%d: %s\n", i == geInstance->enabledDeviceNum ? '*' : ' ',
i, geInstance->devices[i].name);
}
fprintf(stderr, "\n");
}
VKLogicalDevice* logicalDevice = &geInstance->devices[geInstance->enabledDeviceNum];
float queuePriority = 1.0f;
VkDeviceQueueCreateInfo queueCreateInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = logicalDevice->queueFamily, // obtained separately
.queueCount = 1,
.pQueuePriorities = &queuePriority
};
VkPhysicalDeviceFeatures features10 = { .logicOp = VK_TRUE };
VkDeviceCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pNext = NULL,
.flags = 0,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &queueCreateInfo,
.enabledLayerCount = ARRAY_SIZE(logicalDevice->enabledLayers),
.ppEnabledLayerNames = (const char *const *) logicalDevice->enabledLayers,
.enabledExtensionCount = ARRAY_SIZE(logicalDevice->enabledExtensions),
.ppEnabledExtensionNames = (const char *const *) logicalDevice->enabledExtensions,
.pEnabledFeatures = &features10
};
if (vkCreateDevice(logicalDevice->physicalDevice, &createInfo, NULL, &logicalDevice->device) != VK_SUCCESS)
{
J2dRlsTrace1(J2D_TRACE_ERROR, "Cannot create device:\n %s\n",
geInstance->devices[geInstance->enabledDeviceNum].name)
vulkanLibClose();
return JNI_FALSE;
}
VkDevice device = logicalDevice->device;
J2dRlsTrace1(J2D_TRACE_INFO, "Logical device (%s) created\n", logicalDevice->name)
// Create command pool
VkCommandPoolCreateInfo poolInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = logicalDevice->queueFamily
};
if (geInstance->vkCreateCommandPool(device, &poolInfo, NULL, &logicalDevice->commandPool) != VK_SUCCESS) {
J2dRlsTraceLn(J2D_TRACE_INFO, "failed to create command pool!")
return JNI_FALSE;
}
// Create command buffer
VkCommandBufferAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = logicalDevice->commandPool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1
};
if (geInstance->vkAllocateCommandBuffers(device, &allocInfo, &logicalDevice->commandBuffer) != VK_SUCCESS) {
J2dRlsTraceLn(J2D_TRACE_INFO, "failed to allocate command buffers!");
return JNI_FALSE;
}
// Create semaphores
VkSemaphoreCreateInfo semaphoreInfo = {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO
};
VkFenceCreateInfo fenceInfo = {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.flags = VK_FENCE_CREATE_SIGNALED_BIT
};
if (geInstance->vkCreateSemaphore(device, &semaphoreInfo, NULL, &logicalDevice->imageAvailableSemaphore) != VK_SUCCESS ||
geInstance->vkCreateSemaphore(device, &semaphoreInfo, NULL, &logicalDevice->renderFinishedSemaphore) != VK_SUCCESS ||
geInstance->vkCreateFence(device, &fenceInfo, NULL, &logicalDevice->inFlightFence) != VK_SUCCESS)
{
J2dRlsTraceLn(J2D_TRACE_INFO, "failed to create semaphores!");
return JNI_FALSE;
}
geInstance->vkGetDeviceQueue(device, logicalDevice->queueFamily, 0, &logicalDevice->queue);
if (logicalDevice->queue == NULL) {
J2dRlsTraceLn(J2D_TRACE_INFO, "failed to get device queue!");
return JNI_FALSE;
}
VKTxVertex* vertices = ARRAY_ALLOC(VKTxVertex, 4);
ARRAY_PUSH_BACK(&vertices, ((VKTxVertex){-1.0f, -1.0f, 0.0f, 0.0f}));
ARRAY_PUSH_BACK(&vertices, ((VKTxVertex){1.0f, -1.0f, 1.0f, 0.0f}));
ARRAY_PUSH_BACK(&vertices, ((VKTxVertex){-1.0f, 1.0f, 0.0f, 1.0f}));
ARRAY_PUSH_BACK(&vertices, ((VKTxVertex){1.0f, 1.0f, 1.0f, 1.0f}));
logicalDevice->blitVertexBuffer = ARRAY_TO_VERTEX_BUF(vertices);
if (!logicalDevice->blitVertexBuffer) {
J2dRlsTrace(J2D_TRACE_ERROR, "Cannot create vertex buffer\n")
return JNI_FALSE;
}
ARRAY_FREE(vertices);
return JNI_TRUE;
}
JNIEXPORT void JNICALL JNI_OnUnload(__attribute__((unused)) JavaVM *vm, __attribute__((unused)) void *reserved) {
vulkanLibClose();
}

View File

@@ -0,0 +1,453 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, JetBrains s.r.o.. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "VKBase.h"
#include <Trace.h>
#include <set>
#if defined(DEBUG)
#include <csignal>
#endif
#define VALIDATION_LAYER_NAME "VK_LAYER_KHRONOS_validation"
static const uint32_t REQUIRED_VULKAN_VERSION = VK_MAKE_API_VERSION(0, 1, 2, 0);
bool VKGraphicsEnvironment::_verbose = false;
int VKGraphicsEnvironment::_requested_device_number = -1;
std::unique_ptr<VKGraphicsEnvironment> VKGraphicsEnvironment::_ge_instance = nullptr;
// ========== Graphics environment ==========
#if defined(DEBUG)
static VkBool32 debugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData
) {
auto data = (const vk::DebugUtilsMessengerCallbackDataEXT*) pCallbackData;
if (data == nullptr) return 0;
// Here we can filter messages like this:
// if (std::strcmp(data->pMessageIdName, "UNASSIGNED-BestPractices-DrawState-ClearCmdBeforeDraw") == 0) return 0;
int level = J2D_TRACE_OFF;
if (messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) level = J2D_TRACE_VERBOSE;
else if (messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) level = J2D_TRACE_INFO;
else if (messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) level = J2D_TRACE_WARNING;
else if (messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) level = J2D_TRACE_ERROR;
J2dRlsTraceLn(level, data->pMessage);
if (messageSeverity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) {
raise(SIGABRT);
}
return 0;
}
#endif
VKGraphicsEnvironment *VKGraphicsEnvironment::graphics_environment() {
if (!_ge_instance) {
try {
_ge_instance = std::unique_ptr<VKGraphicsEnvironment>(new VKGraphicsEnvironment());
}
catch (std::exception& e) {
J2dRlsTrace1(J2D_TRACE_ERROR, "Vulkan: %s\n", e.what());
return nullptr;
}
}
return _ge_instance.get();
}
VKGraphicsEnvironment::VKGraphicsEnvironment() :
_vk_context(), _vk_instance(nullptr), _default_device(nullptr) {
// Load library.
uint32_t version = _vk_context.enumerateInstanceVersion();
J2dRlsTrace3(J2D_TRACE_INFO, "Vulkan: Available (%d.%d.%d)\n",
VK_API_VERSION_MAJOR(version), VK_API_VERSION_MINOR(version), VK_API_VERSION_PATCH(version));
if (version < REQUIRED_VULKAN_VERSION) {
throw std::runtime_error("Vulkan: Unsupported version");
}
// Populate maps and log supported layers & extensions.
std::set<std::string> layers, extensions;
J2dRlsTrace(J2D_TRACE_VERBOSE, " Supported instance layers:\n");
for (auto &l: _vk_context.enumerateInstanceLayerProperties()) {
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char *) l.layerName);
layers.emplace((char *) l.layerName);
}
J2dRlsTrace(J2D_TRACE_VERBOSE, " Supported instance extensions:\n");
for (auto &e: _vk_context.enumerateInstanceExtensionProperties(nullptr)) {
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char *) e.extensionName);
extensions.emplace((char *) e.extensionName);
}
std::vector<const char *> enabledLayers, enabledExtensions;
const void *pNext = nullptr;
// Check required layers & extensions.
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
enabledExtensions.push_back(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME);
#endif
enabledExtensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
for (auto e: enabledExtensions) {
if (extensions.find(e) == extensions.end()) {
throw std::runtime_error(std::string("Vulkan: Required instance extension not supported:") +
(char *) e);
}
}
// Configure validation
#ifdef DEBUG
std::array<vk::ValidationFeatureEnableEXT, 2> enabledValidationFeatures = {
// vk::ValidationFeatureEnableEXT::eGpuAssisted, // TODO GPU assisted validation is available only from Vulkan 1.1
// vk::ValidationFeatureEnableEXT::eGpuAssistedReserveBindingSlot,
vk::ValidationFeatureEnableEXT::eBestPractices,
vk::ValidationFeatureEnableEXT::eSynchronizationValidation
};
vk::ValidationFeaturesEXT validationFeatures {enabledValidationFeatures};
if (layers.find(VALIDATION_LAYER_NAME) != layers.end() &&
extensions.find(VK_EXT_DEBUG_UTILS_EXTENSION_NAME) != extensions.end()) {
enabledLayers.push_back(VALIDATION_LAYER_NAME);
enabledExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
pNext = &validationFeatures;
} else {
J2dRlsTrace2(J2D_TRACE_WARNING, "Vulkan: %s and %s are not supported\n",
VALIDATION_LAYER_NAME, VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
}
#endif
vk::ApplicationInfo applicationInfo{
/*pApplicationName*/ "OpenJDK",
/*applicationVersion*/ 0,
/*pEngineName*/ "OpenJDK",
/*engineVersion*/ 0,
/*apiVersion*/ REQUIRED_VULKAN_VERSION
};
vk::InstanceCreateInfo instanceCreateInfo{
/*flags*/ {},
/*pApplicationInfo*/ &applicationInfo,
/*ppEnabledLayerNames*/ enabledLayers,
/*ppEnabledExtensionNames*/ enabledExtensions,
/*pNext*/ pNext
};
// Save context object at persistent address before passing it further.
_vk_instance = vk::raii::Instance(_vk_context, instanceCreateInfo);
J2dRlsTrace(J2D_TRACE_INFO, "Vulkan: Instance created\n");
// Create debug messenger
#if defined(DEBUG)
if (pNext) {
_debugMessenger = vk::raii::DebugUtilsMessengerEXT(_vk_instance, vk::DebugUtilsMessengerCreateInfoEXT {
/*flags*/ {},
/*messageSeverity*/ vk::DebugUtilsMessageSeverityFlagBitsEXT::eError |
vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning |
vk::DebugUtilsMessageSeverityFlagBitsEXT::eInfo |
vk::DebugUtilsMessageSeverityFlagBitsEXT::eVerbose,
/*messageType*/ vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral |
vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation |
vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance,
/*pfnUserCallback*/ &debugCallback
});
}
#endif
// Find suitable devices.
for (auto &handle: _vk_instance.enumeratePhysicalDevices()) {
VKDevice device {*_vk_instance, std::move(handle)};
if (device.supported()) {
_devices.push_back(std::make_unique<VKDevice>(std::move(device)));
}
}
if (_devices.empty()) {
throw std::runtime_error("Vulkan: No suitable device found");
}
// Create virtual device for a physical device.
// TODO integrated/discrete presets
// TODO performance/power saving mode switch on the fly?
int _default_device_number = 0; // TODO pick first just to check that virtual device creation works
if (_verbose) {
fprintf(stderr, "Vulkan graphics devices:\n");
}
_requested_device_number = (_requested_device_number == -1) ? 0 : _requested_device_number;
if (_requested_device_number < 0 || _requested_device_number >= static_cast<int>(_devices.size())) {
if (_verbose) {
fprintf(stderr, " Requested device number (%d) not found, fallback to 0\n", _requested_device_number);
}
_requested_device_number = 0;
}
_default_device_number = _requested_device_number;
if (_verbose) {
for (auto devIter = _devices.begin(); devIter != _devices.end(); devIter++) {
auto devNum = std::distance(begin(_devices), devIter);
fprintf(stderr, " %c%ld: %s\n", devNum == _default_device_number ? '*' : ' ',
devNum, (*devIter)->name().c_str());
}
fprintf(stderr, "\n");
}
_default_device = &*_devices[_default_device_number]; // TODO pick first just to check hat virtual device creation works
_default_device->init();
}
vk::raii::Instance& VKGraphicsEnvironment::vk_instance() {
return _vk_instance;
}
void VKGraphicsEnvironment::dispose() {
_ge_instance.reset();
}
VKDevice& VKGraphicsEnvironment::default_device() {
return *_default_device;
}
// ========== Vulkan device ==========
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
extern struct wl_display *wl_display;
#endif
VKDevice::VKDevice(vk::Instance instance, vk::raii::PhysicalDevice&& handle) :
vk::raii::Device(nullptr), vk::raii::PhysicalDevice(nullptr), _instance(instance) {
auto featuresChain = handle.getFeatures2<vk::PhysicalDeviceFeatures2,
vk::PhysicalDeviceVulkan11Features,
vk::PhysicalDeviceVulkan12Features>();
const auto& features10 = featuresChain.get<vk::PhysicalDeviceFeatures2>().features;
const auto& features11 = featuresChain.get<vk::PhysicalDeviceVulkan11Features>();
const auto& features12 = featuresChain.get<vk::PhysicalDeviceVulkan12Features>();
auto propertiesChain = handle.getProperties2<vk::PhysicalDeviceProperties2,
vk::PhysicalDeviceVulkan11Properties,
vk::PhysicalDeviceVulkan12Properties>();
const auto& properties10 = propertiesChain.get<vk::PhysicalDeviceProperties2>().properties;
const auto& properties11 = propertiesChain.get<vk::PhysicalDeviceVulkan11Properties>();
const auto& properties12 = propertiesChain.get<vk::PhysicalDeviceVulkan12Properties>();
const auto& queueFamilies = handle.getQueueFamilyProperties();
_name = (const char*) properties10.deviceName;
J2dRlsTrace5(J2D_TRACE_INFO, "Vulkan: Found device %s (%d.%d.%d, %s)\n",
(const char*) properties10.deviceName,
VK_API_VERSION_MAJOR(properties10.apiVersion),
VK_API_VERSION_MINOR(properties10.apiVersion),
VK_API_VERSION_PATCH(properties10.apiVersion),
vk::to_string(properties10.deviceType).c_str());
// Check API version.
if (properties10.apiVersion < REQUIRED_VULKAN_VERSION) {
J2dRlsTrace(J2D_TRACE_INFO, " Unsupported Vulkan version\n");
return;
}
// Check supported features.
if (!features10.logicOp) {
J2dRlsTrace(J2D_TRACE_INFO, " Logic op not supported\n");
return;
}
if (!features12.timelineSemaphore) {
J2dRlsTrace(J2D_TRACE_INFO, " Timeline semaphore not supported\n");
return;
}
// Check supported queue families.
for (unsigned int i = 0; i < queueFamilies.size(); i++) {
const auto& family = queueFamilies[i];
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
bool presentationSupported = handle.getWaylandPresentationSupportKHR(i, *wl_display);
#endif
char logFlags[5] {
family.queueFlags & vk::QueueFlagBits::eGraphics ? 'G' : '-',
family.queueFlags & vk::QueueFlagBits::eCompute ? 'C' : '-',
family.queueFlags & vk::QueueFlagBits::eTransfer ? 'T' : '-',
family.queueFlags & vk::QueueFlagBits::eSparseBinding ? 'S' : '-',
presentationSupported ? 'P' : '-'
};
J2dRlsTrace3(J2D_TRACE_INFO, " %d queues in family (%.*s)\n", family.queueCount, 5, logFlags);
// TODO use compute workloads? Separate transfer-only DMA queue?
if (_queue_family == -1 && (family.queueFlags & vk::QueueFlagBits::eGraphics) && presentationSupported) {
_queue_family = i;
}
}
if (_queue_family == -1) {
J2dRlsTrace(J2D_TRACE_INFO, " No suitable queue\n");
return;
}
// Populate maps and log supported layers & extensions.
std::set<std::string> layers, extensions;
J2dRlsTrace(J2D_TRACE_VERBOSE, " Supported device layers:\n");
for (auto& l : handle.enumerateDeviceLayerProperties()) {
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char*) l.layerName);
layers.emplace((char*) l.layerName);
}
J2dRlsTrace(J2D_TRACE_VERBOSE, " Supported device extensions:\n");
for (auto& e : handle.enumerateDeviceExtensionProperties(nullptr)) {
J2dRlsTrace1(J2D_TRACE_VERBOSE, " %s\n", (char*) e.extensionName);
extensions.emplace((char*) e.extensionName);
}
// Check required layers & extensions.
_enabled_extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
bool requiredNotFound = false;
for (auto e : _enabled_extensions) {
if (extensions.find(e) == extensions.end()) {
J2dRlsTrace1(J2D_TRACE_INFO, " Required device extension not supported: %s\n", (char*) e);
requiredNotFound = true;
}
}
if (requiredNotFound) return;
_ext_memory_budget = extensions.find(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME) != extensions.end();
if (_ext_memory_budget) _enabled_extensions.push_back(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME);
_khr_synchronization2 = extensions.find(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME) != extensions.end();
if (_khr_synchronization2) _enabled_extensions.push_back(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME);
_khr_dynamic_rendering = extensions.find(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME) != extensions.end();
if (_khr_dynamic_rendering) _enabled_extensions.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME);
// Validation layer
#ifdef DEBUG
if (layers.find(VALIDATION_LAYER_NAME) != layers.end()) {
_enabled_layers.push_back(VALIDATION_LAYER_NAME);
} else {
J2dRlsTrace1(J2D_TRACE_INFO, " %s device layer is not supported\n", VALIDATION_LAYER_NAME);
}
#endif
// This device is supported
((vk::raii::PhysicalDevice&) *this) = std::move(handle);
}
void VKDevice::init() {
float queuePriorities[1] {1.0f}; // We only use one queue for now
std::vector<vk::DeviceQueueCreateInfo> queueCreateInfos;
queueCreateInfos.push_back(vk::DeviceQueueCreateInfo {
{}, queue_family(), 1, &queuePriorities[0]
});
vk::PhysicalDeviceFeatures features10;
features10.logicOp = true;
vk::PhysicalDeviceVulkan12Features features12;
features12.timelineSemaphore = true;
void *pNext = &features12;
vk::PhysicalDeviceSynchronization2FeaturesKHR synchronization2Features;
if (_khr_synchronization2) {
synchronization2Features.synchronization2 = true;
synchronization2Features.pNext = pNext;
pNext = &synchronization2Features;
}
vk::PhysicalDeviceDynamicRenderingFeaturesKHR dynamicRenderingFeatures;
if (_khr_dynamic_rendering) {
dynamicRenderingFeatures.dynamicRendering = true;
dynamicRenderingFeatures.pNext = pNext;
pNext = &dynamicRenderingFeatures;
}
vk::DeviceCreateInfo deviceCreateInfo {
/*flags*/ {},
/*pQueueCreateInfos*/ queueCreateInfos,
/*ppEnabledLayerNames*/ _enabled_layers,
/*ppEnabledExtensionNames*/ _enabled_extensions,
/*pEnabledFeatures*/ &features10,
/*pNext*/ pNext
};
((vk::raii::Device&) *this) = {*this, deviceCreateInfo};
_memory.init(_instance, *this, *this, REQUIRED_VULKAN_VERSION, _ext_memory_budget);
_pipelines.init((vk::raii::Device&) *this, _khr_dynamic_rendering);
_queue = getQueue(queue_family(), 0);
_commandPool = createCommandPool(vk::CommandPoolCreateInfo {
vk::CommandPoolCreateFlagBits::eTransient | vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
queue_family()
});
vk::SemaphoreTypeCreateInfo semaphoreTypeCreateInfo { vk::SemaphoreType::eTimeline, 0 };
_timelineSemaphore = createSemaphore(vk::SemaphoreCreateInfo {{}, &semaphoreTypeCreateInfo});
_timelineCounter = 0;
J2dRlsTrace1(J2D_TRACE_INFO, "Vulkan: Device created %s\n", _name.c_str());
}
VKBuffer VKDevice::getVertexBuffer() {
auto b = popPending<VKBuffer>(_pendingVertexBuffers);
if (*b) {
b.position() = 0;
return b;
} else {
return _memory.allocateBuffer(64 * 1024, vk::BufferUsageFlagBits::eVertexBuffer,
vma::AllocationCreateFlagBits::eMapped | vma::AllocationCreateFlagBits::eHostAccessSequentialWrite,
vma::MemoryUsage::eAutoPreferHost);
}
}
vk::raii::CommandBuffer VKDevice::getCommandBuffer(vk::CommandBufferLevel level) {
auto b = popPending<vk::raii::CommandBuffer>(level == vk::CommandBufferLevel::ePrimary ?
_pendingPrimaryBuffers : _pendingSecondaryBuffers);
if (*b) {
b.reset({});
return b;
} else {
return std::move(allocateCommandBuffers({*_commandPool, level, 1})[0]);
}
}
void VKDevice::submitCommandBuffer(vk::raii::CommandBuffer&& primary,
std::vector<vk::raii::CommandBuffer>& secondary,
std::vector<VKBuffer>& vertexBuffers,
std::vector<vk::Semaphore>& waitSemaphores,
std::vector<vk::PipelineStageFlags>& waitStages,
std::vector<vk::Semaphore>& signalSemaphores) {
_timelineCounter++;
signalSemaphores.insert(signalSemaphores.begin(), *_timelineSemaphore);
vk::TimelineSemaphoreSubmitInfo timelineInfo { 0, nullptr, (uint32_t) signalSemaphores.size(), &_timelineCounter };
queue().submit(vk::SubmitInfo {
waitSemaphores, waitStages, *primary, signalSemaphores, &timelineInfo
}, nullptr);
pushPending(_pendingPrimaryBuffers, std::move(primary));
pushPending(_pendingSecondaryBuffers, secondary);
pushPending(_pendingVertexBuffers, vertexBuffers);
signalSemaphores.clear();
waitSemaphores.clear();
waitStages.clear();
}
extern "C" jboolean VK_Init(jboolean verbose, jint requestedDevice) {
VKGraphicsEnvironment::set_verbose(verbose);
VKGraphicsEnvironment::set_requested_device(requestedDevice);
if (VKGraphicsEnvironment::graphics_environment() != nullptr) {
return true;
}
return false;
}
extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
VKGraphicsEnvironment::dispose();
}

View File

@@ -26,124 +26,139 @@
#ifndef VKBase_h_Included
#define VKBase_h_Included
#include <vulkan/vulkan.h>
#include "CArrayUtil.h"
#ifdef __cplusplus
#define VK_NO_PROTOTYPES
#define VULKAN_HPP_NO_DEFAULT_DISPATCHER
#include <queue>
#include <vulkan/vulkan_raii.hpp>
#include "jni.h"
#include "VKBuffer.h"
#include "VKMemory.h"
#include "VKPipeline.h"
typedef char* pchar;
class VKDevice : public vk::raii::Device, public vk::raii::PhysicalDevice {
friend class VKGraphicsEnvironment;
typedef struct {
VkRenderPass renderPass;
VkDescriptorSetLayout descriptorSetLayout;
VkDescriptorPool descriptorPool;
VkDescriptorSet descriptorSets;
VkPipelineLayout pipelineLayout;
VkPipeline graphicsPipeline;
} VKRenderer;
vk::Instance _instance;
std::string _name;
std::vector<const char*> _enabled_layers, _enabled_extensions;
bool _ext_memory_budget, _khr_synchronization2, _khr_dynamic_rendering;
int _queue_family = -1;
typedef struct {
VkDevice device;
VkPhysicalDevice physicalDevice;
VKRenderer* fillTexturePoly;
VKRenderer* fillColorPoly;
VKRenderer* fillMaxColorPoly;
char* name;
uint32_t queueFamily;
pchar* enabledLayers;
pchar* enabledExtensions;
VkCommandPool commandPool;
VkCommandBuffer commandBuffer;
VkSemaphore imageAvailableSemaphore;
VkSemaphore renderFinishedSemaphore;
VkFence inFlightFence;
VkQueue queue;
VkSampler textureSampler;
VKBuffer* blitVertexBuffer;
} VKLogicalDevice;
// Logical device state
VKMemory _memory;
VKPipelines _pipelines;
vk::raii::Queue _queue = nullptr;
vk::raii::CommandPool _commandPool = nullptr;
vk::raii::Semaphore _timelineSemaphore = nullptr;
uint64_t _timelineCounter = 0;
uint64_t _lastReadTimelineCounter = 0;
template <typename T> struct Pending {
T resource;
uint64_t counter;
using Queue = std::queue<Pending<T>>;
};
Pending<vk::raii::CommandBuffer>::Queue _pendingPrimaryBuffers, _pendingSecondaryBuffers;
Pending<VKBuffer>::Queue _pendingVertexBuffers;
typedef struct {
VkInstance vkInstance;
VkPhysicalDevice* physicalDevices;
VKLogicalDevice* devices;
uint32_t enabledDeviceNum;
VkExtensionProperties* extensions;
VkLayerProperties* layers;
template <typename T> T popPending(typename Pending<T>::Queue& queue) {
if (!queue.empty()) {
auto& f = queue.front();
if (_lastReadTimelineCounter >= f.counter ||
(_lastReadTimelineCounter = _timelineSemaphore.getCounterValue()) >= f.counter) {
T resource = std::move(f.resource);
queue.pop();
return resource;
}
}
return T(nullptr);
}
template <typename T> void pushPending(typename Pending<T>::Queue& queue, T&& resource) {
queue.push({std::move(resource), _timelineCounter});
}
template <typename T> void pushPending(typename Pending<T>::Queue& queue, std::vector<T>& resources) {
for (T& r : resources) {
pushPending(queue, std::move(r));
}
resources.clear();
}
PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices;
PFN_vkGetPhysicalDeviceFeatures2 vkGetPhysicalDeviceFeatures2;
PFN_vkGetPhysicalDeviceProperties2 vkGetPhysicalDeviceProperties2;
PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties;
PFN_vkEnumerateDeviceLayerProperties vkEnumerateDeviceLayerProperties;
PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties;
#if defined(VK_USE_PLATFORM_WAYLAND_KHR)
PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR vkGetPhysicalDeviceWaylandPresentationSupportKHR;
PFN_vkCreateWaylandSurfaceKHR vkCreateWaylandSurfaceKHR;
explicit VKDevice(vk::Instance instance, vk::raii::PhysicalDevice&& handle);
public:
bool synchronization2() {
return _khr_synchronization2;
}
bool dynamicRendering() {
return _khr_dynamic_rendering;
}
VKPipelines& pipelines() {
return _pipelines;
}
uint32_t queue_family() const {
return (uint32_t) _queue_family;
}
const vk::raii::Queue& queue() const {
return _queue;
}
void init(); // Creates actual logical device
VKBuffer getVertexBuffer();
vk::raii::CommandBuffer getCommandBuffer(vk::CommandBufferLevel level);
void submitCommandBuffer(vk::raii::CommandBuffer&& primary,
std::vector<vk::raii::CommandBuffer>& secondary,
std::vector<VKBuffer>& vertexBuffers,
std::vector<vk::Semaphore>& waitSemaphores,
std::vector<vk::PipelineStageFlags>& waitStages,
std::vector<vk::Semaphore>& signalSemaphores);
bool supported() const { // Supported or not
return *((const vk::raii::PhysicalDevice&) *this);
}
explicit operator bool() const { // Initialized or not
return *((const vk::raii::Device&) *this);
}
const std::string& name() {
return _name;
}
};
class VKGraphicsEnvironment {
vk::raii::Context _vk_context;
vk::raii::Instance _vk_instance;
#if defined(DEBUG)
vk::raii::DebugUtilsMessengerEXT _debugMessenger = nullptr;
#endif
PFN_vkCreateShaderModule vkCreateShaderModule;
PFN_vkCreatePipelineLayout vkCreatePipelineLayout;
PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines;
PFN_vkDestroyShaderModule vkDestroyShaderModule;
PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR vkGetPhysicalDeviceSurfaceCapabilitiesKHR;
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR;
PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR;
PFN_vkCreateSwapchainKHR vkCreateSwapchainKHR;
PFN_vkGetSwapchainImagesKHR vkGetSwapchainImagesKHR;
PFN_vkCreateImageView vkCreateImageView;
PFN_vkCreateFramebuffer vkCreateFramebuffer;
PFN_vkCreateCommandPool vkCreateCommandPool;
PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers;
PFN_vkCreateSemaphore vkCreateSemaphore;
PFN_vkCreateFence vkCreateFence;
PFN_vkGetDeviceQueue vkGetDeviceQueue;
PFN_vkWaitForFences vkWaitForFences;
PFN_vkResetFences vkResetFences;
PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR;
PFN_vkResetCommandBuffer vkResetCommandBuffer;
PFN_vkQueueSubmit vkQueueSubmit;
PFN_vkQueuePresentKHR vkQueuePresentKHR;
PFN_vkBeginCommandBuffer vkBeginCommandBuffer;
PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass;
PFN_vkCmdBindPipeline vkCmdBindPipeline;
PFN_vkCmdSetViewport vkCmdSetViewport;
PFN_vkCmdSetScissor vkCmdSetScissor;
PFN_vkCmdDraw vkCmdDraw;
PFN_vkCmdEndRenderPass vkCmdEndRenderPass;
PFN_vkEndCommandBuffer vkEndCommandBuffer;
PFN_vkCreateImage vkCreateImage;
PFN_vkCreateSampler vkCreateSampler;
PFN_vkAllocateMemory vkAllocateMemory;
PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties;
PFN_vkBindImageMemory vkBindImageMemory;
PFN_vkCreateDescriptorSetLayout vkCreateDescriptorSetLayout;
PFN_vkUpdateDescriptorSets vkUpdateDescriptorSets;
PFN_vkCreateDescriptorPool vkCreateDescriptorPool;
PFN_vkAllocateDescriptorSets vkAllocateDescriptorSets;
PFN_vkCmdBindDescriptorSets vkCmdBindDescriptorSets;
PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements;
PFN_vkCreateBuffer vkCreateBuffer;
PFN_vkGetBufferMemoryRequirements vkGetBufferMemoryRequirements;
PFN_vkBindBufferMemory vkBindBufferMemory;
PFN_vkMapMemory vkMapMemory;
PFN_vkUnmapMemory vkUnmapMemory;
PFN_vkCmdBindVertexBuffers vkCmdBindVertexBuffers;
PFN_vkCreateRenderPass vkCreateRenderPass;
PFN_vkDestroyBuffer vkDestroyBuffer;
PFN_vkFreeMemory vkFreeMemory;
PFN_vkDestroyImageView vkDestroyImageView;
PFN_vkDestroyImage vkDestroyImage;
PFN_vkDestroyFramebuffer vkDestroyFramebuffer;
PFN_vkFlushMappedMemoryRanges vkFlushMappedMemoryRanges;
PFN_vkCmdPushConstants vkCmdPushConstants;
} VKGraphicsEnvironment;
std::vector<std::unique_ptr<VKDevice>> _devices;
VKDevice* _default_device;
static bool _verbose;
static int _requested_device_number;
static std::unique_ptr<VKGraphicsEnvironment> _ge_instance;
VKGraphicsEnvironment();
public:
static void set_verbose(bool verbose) { _verbose = verbose; }
static void set_requested_device(int requested_device) { _requested_device_number = requested_device; }
static VKGraphicsEnvironment* graphics_environment();
static void dispose();
VKDevice& default_device();
vk::raii::Instance& vk_instance();
};
extern "C" {
#endif //__cplusplus
jboolean VK_FindDevices();
jboolean VK_CreateLogicalDevice(jint requestedDeviceNumber);
jboolean VK_CreateLogicalDeviceRenderers();
VKGraphicsEnvironment* VKGE_graphics_environment();
void* vulkanLibProc(VkInstance vkInstance, char* procName);
jboolean VK_Init(jboolean verbose, jint requestedDevice);
#ifdef __cplusplus
}
#endif //__cplusplus
#endif //VKBase_h_Included

View File

@@ -1,149 +0,0 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, JetBrains s.r.o.. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <string.h>
#include <Trace.h>
#include "CArrayUtil.h"
#include "VKBase.h"
#include "VKBuffer.h"
VkResult VKBuffer_FindMemoryType(VkPhysicalDevice physicalDevice, uint32_t typeFilter,
VkMemoryPropertyFlags properties, uint32_t* pMemoryType) {
VkPhysicalDeviceMemoryProperties memProperties;
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
ge->vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
*pMemoryType = i;
return VK_SUCCESS;
}
}
return VK_ERROR_UNKNOWN;
}
VKBuffer* VKBuffer_Create(VkDeviceSize size, VkBufferUsageFlags usage,
VkMemoryPropertyFlags properties)
{
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
VKLogicalDevice* logicalDevice = &ge->devices[ge->enabledDeviceNum];
VKBuffer* buffer = malloc(sizeof (VKBuffer));
VkBufferCreateInfo bufferInfo = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = size,
.usage = usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE
};
if (ge->vkCreateBuffer(logicalDevice->device, &bufferInfo, NULL, &buffer->buffer) != VK_SUCCESS) {
J2dRlsTraceLn(J2D_TRACE_ERROR, "failed to allocate descriptor sets!")
return NULL;
}
buffer->size = size;
VkMemoryRequirements memRequirements;
ge->vkGetBufferMemoryRequirements(logicalDevice->device, buffer->buffer, &memRequirements);
uint32_t memoryType;
if (VKBuffer_FindMemoryType(logicalDevice->physicalDevice,
memRequirements.memoryTypeBits,
properties, &memoryType) != VK_SUCCESS)
{
J2dRlsTraceLn(J2D_TRACE_ERROR, "failed to find memory!")
return NULL;
}
VkMemoryAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memRequirements.size,
.memoryTypeIndex = memoryType
};
if (ge->vkAllocateMemory(logicalDevice->device, &allocInfo, NULL, &buffer->memory) != VK_SUCCESS) {
J2dRlsTraceLn(J2D_TRACE_ERROR, "failed to allocate buffer memory!");
return NULL;
}
if (ge->vkBindBufferMemory(logicalDevice->device, buffer->buffer, buffer->memory, 0) != VK_SUCCESS) {
J2dRlsTraceLn(J2D_TRACE_ERROR, "failed to bind buffer memory!");
return NULL;
}
return buffer;
}
VKBuffer* VKBuffer_CreateFromData(void* vertices, VkDeviceSize bufferSize)
{
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
VKLogicalDevice* logicalDevice = &ge->devices[ge->enabledDeviceNum];
VKBuffer* buffer = VKBuffer_Create(bufferSize,
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
void* data;
if (ge->vkMapMemory(logicalDevice->device, buffer->memory, 0, bufferSize, 0, &data) != VK_SUCCESS) {
J2dRlsTraceLn(J2D_TRACE_ERROR, "failed to map memory!");
return NULL;
}
memcpy(data, vertices, bufferSize);
VkMappedMemoryRange memoryRange = {
.sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
.pNext = NULL,
.memory = buffer->memory,
.offset = 0,
.size = VK_WHOLE_SIZE
};
if (ge->vkFlushMappedMemoryRanges(logicalDevice->device, 1, &memoryRange) != VK_SUCCESS) {
J2dRlsTraceLn(J2D_TRACE_ERROR, "failed to flush memory!");
return NULL;
}
ge->vkUnmapMemory(logicalDevice->device, buffer->memory);
buffer->size = bufferSize;
return buffer;
}
void VKBuffer_free(VKBuffer* buffer) {
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
VKLogicalDevice* logicalDevice = &ge->devices[ge->enabledDeviceNum];
if (buffer != NULL) {
if (buffer->buffer != VK_NULL_HANDLE) {
ge->vkDestroyBuffer(logicalDevice->device, buffer->buffer, NULL);
}
if (buffer->memory != VK_NULL_HANDLE) {
ge->vkFreeMemory(logicalDevice->device, buffer->memory, NULL);
}
}
}

View File

@@ -1,233 +0,0 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, JetBrains s.r.o.. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <Trace.h>
#include "VKBase.h"
#include "VKBuffer.h"
#include "VKImage.h"
VkBool32 VKImage_CreateView(VKImage* image) {
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
VKLogicalDevice* logicalDevice = &ge->devices[ge->enabledDeviceNum];
VkImageViewCreateInfo viewInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = image->image,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = image->format,
.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.subresourceRange.baseMipLevel = 0,
.subresourceRange.levelCount = 1,
.subresourceRange.baseArrayLayer = 0,
.subresourceRange.layerCount = 1,
};
if (ge->vkCreateImageView(logicalDevice->device, &viewInfo, NULL, &image->view) != VK_SUCCESS) {
J2dRlsTrace(J2D_TRACE_ERROR, "Cannot surface image view\n");
return VK_FALSE;
}
return VK_TRUE;
}
VkBool32 VKImage_CreateFramebuffer(VKImage *image, VkRenderPass renderPass) {
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
VKLogicalDevice* logicalDevice = &ge->devices[ge->enabledDeviceNum];
VkImageView attachments[] = {
image->view
};
VkFramebufferCreateInfo framebufferInfo = {
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.renderPass = renderPass,
.attachmentCount = 1,
.pAttachments = attachments,
.width = image->extent.width,
.height = image->extent.height,
.layers = 1
};
if (ge->vkCreateFramebuffer(logicalDevice->device, &framebufferInfo, NULL,
&image->framebuffer) != VK_SUCCESS)
{
J2dRlsTraceLn(J2D_TRACE_ERROR, "failed to create framebuffer!")
return VK_FALSE;
}
return VK_TRUE;
}
VKImage* VKImage_Create(uint32_t width, uint32_t height,
VkFormat format, VkImageTiling tiling,
VkImageUsageFlags usage,
VkMemoryPropertyFlags properties)
{
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
VKLogicalDevice* logicalDevice = &ge->devices[ge->enabledDeviceNum];
VKImage* image = malloc(sizeof (VKImage));
if (!image) {
J2dRlsTraceLn(J2D_TRACE_ERROR, "Cannot allocate data for image")
return NULL;
}
image->format = format;
image->extent = (VkExtent2D) {width, height};
VkImageCreateInfo imageInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.extent.width = width,
.extent.height = height,
.extent.depth = 1,
.mipLevels = 1,
.arrayLayers = 1,
.format = format,
.tiling = tiling,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.usage = usage,
.samples = VK_SAMPLE_COUNT_1_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE
};
if (ge->vkCreateImage(logicalDevice->device, &imageInfo, NULL, &image->image) != VK_SUCCESS) {
J2dRlsTraceLn(J2D_TRACE_ERROR, "Cannot create surface image")
VKImage_free(image);
return NULL;
}
VkMemoryRequirements memRequirements;
ge->vkGetImageMemoryRequirements(logicalDevice->device, image->image, &memRequirements);
uint32_t memoryType;
if (VKBuffer_FindMemoryType(logicalDevice->physicalDevice,
memRequirements.memoryTypeBits,
properties, &memoryType) != VK_SUCCESS)
{
J2dRlsTraceLn(J2D_TRACE_ERROR, "Failed to find memory")
VKImage_free(image);
return NULL;
}
VkMemoryAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memRequirements.size,
.memoryTypeIndex = memoryType
};
if (ge->vkAllocateMemory(logicalDevice->device, &allocInfo, NULL, &image->memory) != VK_SUCCESS) {
J2dRlsTraceLn(J2D_TRACE_ERROR, "Failed to allocate image memory");
VKImage_free(image);
return NULL;
}
ge->vkBindImageMemory(logicalDevice->device, image->image, image->memory, 0);
if (!VKImage_CreateView(image)) {
VKImage_free(image);
return NULL;
}
return image;
}
VKImage* VKImage_CreateImageArrayFromSwapChain(VkSwapchainKHR swapchainKhr, VkRenderPass renderPass,
VkFormat format, VkExtent2D extent)
{
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
VKLogicalDevice* logicalDevice = &ge->devices[ge->enabledDeviceNum];
uint32_t swapChainImagesCount;
if (ge->vkGetSwapchainImagesKHR(logicalDevice->device, swapchainKhr, &swapChainImagesCount,
NULL) != VK_SUCCESS) {
J2dRlsTrace(J2D_TRACE_ERROR, "Cannot get swapchain images\n");
return NULL;
}
if (swapChainImagesCount == 0) {
J2dRlsTrace(J2D_TRACE_ERROR, "No swapchain images found\n");
return NULL;
}
VkImage swapChainImages[swapChainImagesCount];
if (ge->vkGetSwapchainImagesKHR(logicalDevice->device, swapchainKhr, &swapChainImagesCount,
swapChainImages) != VK_SUCCESS) {
J2dRlsTrace(J2D_TRACE_ERROR, "Cannot get swapchain images\n");
return NULL;
}
VKImage* images = ARRAY_ALLOC(VKImage, swapChainImagesCount);
for (uint32_t i = 0; i < swapChainImagesCount; i++) {
ARRAY_PUSH_BACK(&images, ((VKImage){
.image = swapChainImages[i],
.memory = VK_NULL_HANDLE,
.format = format,
.extent = extent,
.noImageDealloc = VK_TRUE
}));
if (!VKImage_CreateView(&ARRAY_LAST(images))) {
ARRAY_APPLY(images, VKImage_dealloc);
ARRAY_FREE(images);
return NULL;
}
if (!VKImage_CreateFramebuffer(&ARRAY_LAST(images), renderPass)) {
ARRAY_APPLY(images, VKImage_dealloc);
ARRAY_FREE(images);
return NULL;
}
}
return images;
}
void VKImage_dealloc(VKImage* image) {
VKGraphicsEnvironment* ge = VKGE_graphics_environment();
VKLogicalDevice* logicalDevice = &ge->devices[ge->enabledDeviceNum];
if (!image) return;
if (image->framebuffer != VK_NULL_HANDLE) {
ge->vkDestroyFramebuffer(logicalDevice->device, image->framebuffer, NULL);
}
if (image->view != VK_NULL_HANDLE) {
ge->vkDestroyImageView(logicalDevice->device, image->view, NULL);
}
if (image->memory != VK_NULL_HANDLE) {
ge->vkFreeMemory(logicalDevice->device, image->memory, NULL);
}
if (image->image != VK_NULL_HANDLE && !image->noImageDealloc) {
ge->vkDestroyImage(logicalDevice->device, image->image, NULL);
}
}
void VKImage_free(VKImage* image) {
VKImage_dealloc(image);
free(image);
}

View File

@@ -1,56 +0,0 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, JetBrains s.r.o.. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#ifndef VKImage_h_Included
#define VKImage_h_Included
#include <vulkan/vulkan.h>
typedef struct {
VkImage image;
VkDeviceMemory memory;
VkFramebuffer framebuffer;
VkImageView view;
VkFormat format;
VkExtent2D extent;
VkBool32 noImageDealloc;
} VKImage;
VKImage* VKImage_Create(uint32_t width, uint32_t height,
VkFormat format, VkImageTiling tiling,
VkImageUsageFlags usage,
VkMemoryPropertyFlags properties);
VKImage* VKImage_CreateImageArrayFromSwapChain(VkSwapchainKHR swapchainKhr,
VkRenderPass renderPass,
VkFormat format,
VkExtent2D extent);
VkBool32 VKImage_CreateFramebuffer(VKImage *image, VkRenderPass renderPass);
void VKImage_free(VKImage* image);
void VKImage_dealloc(VKImage* image);
#endif // VKImage_h_Included

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, JetBrains s.r.o.. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#define VMA_IMPLEMENTATION
#include "VKMemory.h"
void VKMemory::init(vk::Instance instance, const vk::raii::PhysicalDevice& physicalDevice,
const vk::raii::Device& device, uint32_t apiVersion, bool extMemoryBudget) {
vma::VulkanFunctions functions = vma::functionsFromDispatcher(physicalDevice.getDispatcher(), device.getDispatcher());
vma::AllocatorCreateInfo createInfo {};
createInfo.instance = instance;
createInfo.physicalDevice = *physicalDevice;
createInfo.device = *device;
createInfo.pVulkanFunctions = &functions;
createInfo.vulkanApiVersion = apiVersion;
if (extMemoryBudget) {
createInfo.flags |= vma::AllocatorCreateFlagBits::eExtMemoryBudget;
}
_allocator = vma::createAllocatorUnique(createInfo);
*((vma::Allocator*) this) = *_allocator;
}
VKBuffer VKMemory::allocateBuffer(uint32_t size, vk::BufferUsageFlags usage,
vma::AllocationCreateFlags flags, vma::MemoryUsage memoryUsage) {
VKBuffer b = nullptr;
auto pair = createBufferUnique(vk::BufferCreateInfo {
{}, size, usage, vk::SharingMode::eExclusive, {}
}, vma::AllocationCreateInfo {
flags,
memoryUsage, {}, {}, (uint32_t) -1
}, b._allocationInfo);
b._buffer = std::move(pair.first);
b._allocation = std::move(pair.second);
b._size = size;
return b;
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, JetBrains s.r.o.. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#ifndef VKMemory_h_Included
#define VKMemory_h_Included
#define VK_NO_PROTOTYPES
#define VULKAN_HPP_NO_DEFAULT_DISPATCHER
#include <vulkan/vulkan_raii.hpp>
#include <vk_mem_alloc.hpp>
class VKBuffer {
friend class VKMemory;
vma::UniqueBuffer _buffer;
vma::UniqueAllocation _allocation;
vma::AllocationInfo _allocationInfo;
uint32_t _size = 0;
uint32_t _position = 0;
public:
VKBuffer(nullptr_t) {}
vk::Buffer operator*() const {
return *_buffer;
}
uint32_t size() const {
return _size;
}
uint32_t& position() {
return _position;
}
void* data() {
return _allocationInfo.pMappedData;
}
};
class VKMemory : vma::Allocator {
vma::UniqueAllocator _allocator;
public:
void init(vk::Instance instance, const vk::raii::PhysicalDevice& physicalDevice,
const vk::raii::Device& device, uint32_t apiVersion, bool extMemoryBudget);
VKBuffer allocateBuffer(uint32_t size, vk::BufferUsageFlags usage,
vma::AllocationCreateFlags flags, vma::MemoryUsage memoryUsage);
};
#endif //VKMemory_h_Included

View File

@@ -0,0 +1,127 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, JetBrains s.r.o.. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "VKPipeline.h"
void VKPipelines::init(const vk::raii::Device& device, bool dynamicRendering) {
shaders.init(device);
vk::Format format = vk::Format::eB8G8R8A8Unorm; // TODO
if (!dynamicRendering) {
vk::AttachmentDescription attachmentDescription {
/*flags*/ {},
/*format*/ format,
/*samples*/ vk::SampleCountFlagBits::e1,
/*loadOp*/ vk::AttachmentLoadOp::eLoad,
/*storeOp*/ vk::AttachmentStoreOp::eStore,
/*stencilLoadOp*/ vk::AttachmentLoadOp::eDontCare,
/*stencilStoreOp*/ vk::AttachmentStoreOp::eDontCare,
/*initialLayout*/ vk::ImageLayout::eColorAttachmentOptimal,
/*finalLayout*/ vk::ImageLayout::eColorAttachmentOptimal
};
vk::AttachmentReference attachmentReference { 0, vk::ImageLayout::eColorAttachmentOptimal };
vk::SubpassDescription subpassDescription {
/*flags*/ {},
/*pipelineBindPoint*/ vk::PipelineBindPoint::eGraphics,
/*inputAttachmentCount*/ 0,
/*pInputAttachments*/ nullptr,
/*colorAttachmentCount*/ 1,
/*pColorAttachments*/ &attachmentReference,
/*pResolveAttachments*/ nullptr,
/*pDepthStencilAttachment*/ nullptr,
/*preserveAttachmentCount*/ 0,
/*pPreserveAttachments*/ nullptr,
};
// We don't know in advance, which operations to synchronize
// with before and after the render pass, so do a full sync.
std::array<vk::SubpassDependency, 2> subpassDependencies {vk::SubpassDependency{
/*srcSubpass*/ VK_SUBPASS_EXTERNAL,
/*dstSubpass*/ 0,
/*srcStageMask*/ vk::PipelineStageFlagBits::eBottomOfPipe,
/*dstStageMask*/ vk::PipelineStageFlagBits::eColorAttachmentOutput,
/*srcAccessMask*/ {},
/*dstAccessMask*/ vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite,
/*dependencyFlags*/ {},
}, vk::SubpassDependency{
/*srcSubpass*/ 0,
/*dstSubpass*/ VK_SUBPASS_EXTERNAL,
/*srcStageMask*/ vk::PipelineStageFlagBits::eColorAttachmentOutput,
/*dstStageMask*/ vk::PipelineStageFlagBits::eTopOfPipe,
/*srcAccessMask*/ vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite,
/*dstAccessMask*/ {},
/*dependencyFlags*/ {},
}};
renderPass = device.createRenderPass(vk::RenderPassCreateInfo{
/*flags*/ {},
/*pAttachments*/ attachmentDescription,
/*pSubpasses*/ subpassDescription,
/*pDependencies*/ subpassDependencies
});
}
vk::PushConstantRange pushConstantRange {vk::ShaderStageFlagBits::eVertex, 0, sizeof(float) * 2};
testLayout = device.createPipelineLayout(vk::PipelineLayoutCreateInfo {{}, {}, pushConstantRange});
std::array<vk::PipelineShaderStageCreateInfo, 2> testStages {shaders.test_vert.stage(), shaders.test_frag.stage()};
vk::VertexInputBindingDescription vertexInputBindingDescription {0, 8, vk::VertexInputRate::eVertex};
vk::VertexInputAttributeDescription vertexInputAttributeDescription {0, 0, vk::Format::eR32G32Sfloat, 0};
vk::PipelineVertexInputStateCreateInfo vertexInputStateCreateInfo {{}, vertexInputBindingDescription, vertexInputAttributeDescription};
vk::PipelineInputAssemblyStateCreateInfo inputAssemblyStateCreateInfo {{}, vk::PrimitiveTopology::eTriangleFan, false};
vk::Viewport viewport;
vk::Rect2D scissor;
vk::PipelineViewportStateCreateInfo viewportStateCreateInfo {{}, viewport, scissor};
vk::PipelineRasterizationStateCreateInfo rasterizationStateCreateInfo {
{}, false, false, vk::PolygonMode::eFill, vk::CullModeFlagBits::eNone,
vk::FrontFace::eClockwise, false, 0, 0, 0, 1
};
vk::PipelineMultisampleStateCreateInfo multisampleStateCreateInfo {};
vk::PipelineColorBlendAttachmentState colorBlendAttachmentState {false}; // TODO No blending yet
colorBlendAttachmentState.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
vk::PipelineColorBlendStateCreateInfo colorBlendStateCreateInfo {{}, false, vk::LogicOp::eXor, colorBlendAttachmentState};
std::array<vk::DynamicState, 2> dynamicStates {vk::DynamicState::eViewport, vk::DynamicState::eScissor};
vk::PipelineDynamicStateCreateInfo dynamicStateCreateInfo {{}, dynamicStates};
vk::PipelineRenderingCreateInfoKHR renderingCreateInfo {0, format};
auto pipelines = device.createGraphicsPipelines(nullptr, {
vk::GraphicsPipelineCreateInfo {
{}, testStages,
&vertexInputStateCreateInfo,
&inputAssemblyStateCreateInfo,
nullptr,
&viewportStateCreateInfo,
&rasterizationStateCreateInfo,
&multisampleStateCreateInfo,
nullptr,
&colorBlendStateCreateInfo,
&dynamicStateCreateInfo,
*testLayout,
*renderPass, 0, nullptr, 0,
dynamicRendering ? &renderingCreateInfo : nullptr
}
});
// TODO pipeline cache
test = std::move(pipelines[0]);
}

View File

@@ -1,6 +1,6 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2024, JetBrains s.r.o.. All rights reserved.
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, JetBrains s.r.o.. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -24,31 +24,19 @@
* questions.
*/
package sun.awt.wl;
#ifndef VKPipeline_h_Included
#define VKPipeline_h_Included
import java.awt.AWTError;
#include "VKShader.h"
public final class WLDisplay {
private static final WLDisplay INSTANCE = new WLDisplay();
struct VKPipelines {
VKShaders shaders;
// TODO we need a pool of pipelines and (optionally) render passes for different formats.
vk::raii::RenderPass renderPass = nullptr; // Render pass is only needed if dynamic rendering is off.
vk::raii::PipelineLayout testLayout = nullptr;
vk::raii::Pipeline test = nullptr;
private final long displayPtr;
void init(const vk::raii::Device& device, bool dynamicRendering);
};
private WLDisplay() {
long p = connect(); // NB: we never disconnect
if (p == 0) {
throw new AWTError("Can't connect to the Wayland server");
}
displayPtr = p;
}
public long getDisplayPtr() {
return displayPtr;
}
public static WLDisplay getInstance() {
return INSTANCE;
}
private native long connect();
}
#endif //VKPipeline_h_Included

Some files were not shown because too many files have changed in this diff Show More