From 3c55ec1fcf9944f20605f15df3a071be75940647 Mon Sep 17 00:00:00 2001 From: Jamieson Pryor Date: Fri, 25 Oct 2024 16:36:38 -0700 Subject: [PATCH] Open source pure native binary. See https://github.com/google/jni-bind/issues/127. PiperOrigin-RevId: 689950666 --- java/com/google/BUILD | 24 +++++ java/com/google/README.md | 3 + java/com/google/RandomString.java | 43 ++++++++ java/com/google/java_runtime_environment.h | 110 +++++++++++++++++++++ java/com/google/main.cc | 100 +++++++++++++++++++ metaprogramming/BUILD | 2 - 6 files changed, 280 insertions(+), 2 deletions(-) create mode 100644 java/com/google/BUILD create mode 100644 java/com/google/README.md create mode 100644 java/com/google/RandomString.java create mode 100644 java/com/google/java_runtime_environment.h create mode 100644 java/com/google/main.cc diff --git a/java/com/google/BUILD b/java/com/google/BUILD new file mode 100644 index 0000000..cec757e --- /dev/null +++ b/java/com/google/BUILD @@ -0,0 +1,24 @@ +load("@rules_java//java:defs.bzl", "java_library") + +licenses(["notice"]) + +java_library( + name = "random_string_java", + srcs = ["RandomString.java"], +) + +cc_library( + name = "java_runtime_environment", + hdrs = ["java_runtime_environment.h"], + deps = ["//:jni_bind"], +) + +cc_binary( + name = "main", + srcs = ["main.cc"], + data = [":random_string_java"], + deps = [ + ":java_runtime_environment", + "//:jni_bind", + ], +) diff --git a/java/com/google/README.md b/java/com/google/README.md new file mode 100644 index 0000000..b5055b7 --- /dev/null +++ b/java/com/google/README.md @@ -0,0 +1,3 @@ +This binary is meant to demonstrate a simple binary that is entirely written in native. + +This is experimental. diff --git a/java/com/google/RandomString.java b/java/com/google/RandomString.java new file mode 100644 index 0000000..1993e14 --- /dev/null +++ b/java/com/google/RandomString.java @@ -0,0 +1,43 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google; + +import java.util.Random; + +/** Just a toy model to make sure that we have a large data structure in memory. */ +class RandomString { + public RandomString() {} + + public static String generateRandomString(int length) { + String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + Random random = new Random(); + StringBuilder sb = new StringBuilder(length); + + for (int i = 0; i < length; i++) { + int randomIndex = random.nextInt(characters.length()); + char randomChar = characters.charAt(randomIndex); + sb.append(randomChar); + } + + return sb.toString(); + } + + public String format() { + return generateRandomString(50); + } +} diff --git a/java/com/google/java_runtime_environment.h b/java/com/google/java_runtime_environment.h new file mode 100644 index 0000000..133fd2b --- /dev/null +++ b/java/com/google/java_runtime_environment.h @@ -0,0 +1,110 @@ +#ifndef JNI_BIND_JAVA_COM_GOOGLE_JAVA_RUNTIME_ENVIRONMENT_H_ +#define JNI_BIND_JAVA_COM_GOOGLE_JAVA_RUNTIME_ENVIRONMENT_H_ + +#include +#include + +#include +#include +#include + +#include "jni_bind.h" + +namespace jni_bind_sample_binary { + +// True if `path` is present on disk. +inline bool FileExists(std::string_view path) { + struct stat buf; + + return stat(path.data(), &buf) == 0 && S_ISREG(buf.st_mode); +} + +inline std::optional LoadLibrary(std::string_view lib_path) { + void *lib_handle = dlopen(lib_path.data(), RTLD_NOW); + if (lib_handle == nullptr) { + printf("Failed to load libjvm so lib: %s\n", dlerror()); + return std::nullopt; + } + + return lib_handle; +} + +// Represents JVM so lib, normally launched by some kind of launcher, but in +// this sample the JVM is driven entirely by native. This class may be suitable +// for use beyond this toy binary, but needs more rigorous review. +class JavaRuntimeEnvironment { + using CreateJvmFunc = jint (*)(JavaVM **, JNIEnv **, JavaVMInitArgs *); + using GetJvmFunc = jint (*)(JavaVM **, jsize, jsize *); + + private: + inline JavaRuntimeEnvironment(void *lib_handle, + std::string_view class_collection) + : lib_handle_(lib_handle), + create_function_(reinterpret_cast( + dlsym(lib_handle_, "JNI_CreateJavaVM"))), + get_jvm_function_(reinterpret_cast( + dlsym(lib_handle_, "JNI_GetCreatedJavaVMs"))) { + auto *vm_options = new JavaVMOption[1]; + vm_options[0] = {.optionString = + const_cast(class_collection.data())}; + + vm_args_ = JavaVMInitArgs{.version = JNI_VERSION_1_6, + .nOptions = 1, + .options = vm_options, + .ignoreUnrecognized = JNI_FALSE}; + + jint result = create_function_(&jvm_, &jenv_, &vm_args_); + if (result < 0) { + printf("JNI_CreateJavaVM() failed\n"); + return; + } + + success_loading_jvm_ = true; + printf("JVM created.\n"); + } + + public: + ~JavaRuntimeEnvironment() { dlclose(lib_handle_); } + + // Builds a `JavaRuntimeEnvironment`, or nothing if failure. + static inline std::optional Build( + std::string_view jvm_so_lib, std::string_view class_collection) { + if (!FileExists(jvm_so_lib.data())) { + printf("libjvm so solib does not exist."); + return std::nullopt; + } + + auto lib_handle = LoadLibrary(jvm_so_lib); + if (lib_handle == std::nullopt) { + printf("Failed to load libjvm so lib.\n"); + return std::nullopt; + } + + JavaRuntimeEnvironment ret(*lib_handle, class_collection); + if (ret.success_loading_jvm_ == false) { + printf("Initializing JVM failed.\n"); + return std::nullopt; + } + + printf("Loading succeeded.\n"); + + return ret; + } + + JavaVM *GetJvm() { return jvm_; } + + private: + void *lib_handle_; + const CreateJvmFunc create_function_; + const GetJvmFunc get_jvm_function_; + JavaVMInitArgs vm_args_; + + JavaVM *jvm_; + JNIEnv *jenv_; + + bool success_loading_jvm_ = false; +}; + +} // namespace jni_bind_sample_binary + +#endif // JNI_BIND_JAVA_COM_GOOGLE_JAVA_RUNTIME_ENVIRONMENT_H_ diff --git a/java/com/google/main.cc b/java/com/google/main.cc new file mode 100644 index 0000000..de5ea5e --- /dev/null +++ b/java/com/google/main.cc @@ -0,0 +1,100 @@ +/* + * Copyright 2021 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "java_runtime_environment.h" // NOLINT +#include "jni_bind.h" + +// This binary is meant as a test to exercise a JVM loaded entirely from +// native. For now the path to the JVM so lib is hard coded, but it should be +// an argument to the binary. + +namespace { + +using ::jni_bind_sample_binary::JavaRuntimeEnvironment; + +static constexpr int32_t kNumIterations = 1000; + +void RunIterationsToCompletion(jni::JvmRef *jvm_ref) { + static constexpr jni::Class kRandomString{ + "com/google/RandomString", jni::Constructor{}, + jni::Method{"format", jni::Return{}, jni::Params{}}}; + + jni::GlobalObject random_string; + + for (int i = 0; i < kNumIterations; ++i) { + printf("Iteration %i: %s\n", i, + random_string("format").Pin().ToString().data()); + } +} + +std::string LibNameToFullPath(std::string runfiles_path, std::string lib_name) { + return runfiles_path + lib_name; +} + +} // namespace + +int main(int argc, char *argv[]) { + std::string execution_path = argv[0]; + std::string runfiles_path = execution_path + ".runfiles"; + + std::cout + << "##############################################################" + << '\n' + << "Application Run Directory: " << execution_path << "\n" + << "Runfiles Directory: " << runfiles_path << "\n" + << "Number of iterations: " << kNumIterations + << "\n##############################################################" + << std::endl; + ; + + std::string full_class_collection = + "-Djava.class.path=" + + LibNameToFullPath(runfiles_path, + "/google3/third_party/jni_wrapper/java/com/google/" + "librandom_string_java.jar"); + + auto java_runtime_environment = JavaRuntimeEnvironment::Build( + "/usr/local/buildtools/java/jdk/lib/server/libjvm.so", + full_class_collection); + + if (java_runtime_environment == std::nullopt) { + return -1; + } + + { + jni::JvmRef jvm_ref{(*java_runtime_environment).GetJvm()}; + RunIterationsToCompletion(&jvm_ref); + } + + std::cout << '\n' + << "##############################################################" + << '\n' + << "DONE." << '\n' + << "##############################################################" + << std::endl; + + return 0; +} diff --git a/metaprogramming/BUILD b/metaprogramming/BUILD index 52ec48d..69114be 100644 --- a/metaprogramming/BUILD +++ b/metaprogramming/BUILD @@ -1179,8 +1179,6 @@ cc_test( cc_library( name = "unfurl", hdrs = ["unfurl.h"], - deps = [ - ], ) cc_test(