Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Support for GraalVM Native #3958

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ load("//:bazel/sonatype_artifact_bundle.bzl", "sonatype_artifact_bundle")
load("//:bazel/typedast.bzl", "typedast")
load("@com_github_johnynek_bazel_jar_jar//:jar_jar.bzl", "jar_jar")
load("@google_bazel_common//testing:test_defs.bzl", "gen_java_tests")
load("@rules_graal//graal:graal.bzl", "graal_binary")

package(licenses = ["notice"])

Expand Down Expand Up @@ -81,6 +82,14 @@ java_binary(
runtime_deps = [":compiler_lib"],
)

graal_binary(
name = "compiler_native",
deps = [":compiler_shaded"],
main_class = "com.google.javascript.jscomp.CommandLineRunner",
reflection_configuration = "@//native:reflection.json",
include_resources = ".*",
)

java_binary(
name = "linter",
main_class = "com.google.javascript.jscomp.LinterMain",
Expand Down
20 changes: 20 additions & 0 deletions WORKSPACE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,23 @@ http_archive(
load("@com_github_johnynek_bazel_jar_jar//:jar_jar.bzl", "jar_jar_repositories")

jar_jar_repositories()

# GraalVM is a tool from Oracle (https://graalvm.org) which includes support for
# building native binaries from Java applications. Closure Compiler can be build with
# the bundled `native-image` tool and installed on a target system to execute natively.
http_archive(
name = "rules_graal",
sha256 = "8fa2a40ef37704a6cd2d2ca5c8e2b845f9b207e77141014877dceac6cd40f321",
strip_prefix = "rules_graal-9fd38761df4ac293f952d10379c0c3520dd9ceed",
urls = [
"https://github.com/andyscott/rules_graal/archive/9fd38761df4ac293f952d10379c0c3520dd9ceed.tar.gz",
],
)

load("@rules_graal//graal:graal_bindist.bzl", "graal_bindist_repository")

graal_bindist_repository(
name = "graal",
java_version = "11",
version = "22.1.0",
)
24 changes: 24 additions & 0 deletions build_test_native.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
# Copyright 2020 Google Inc. All Rights Reserved
#
# 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.

# Script that can be used by CI server for testing JsCompiler builds.
set -e

source build_test.sh

bazel build //:compiler_native
make -C native/bench clean reports

# TODO: Run other tests needed for open source verification
Empty file added defs/BUILD.bazel
Empty file.
105 changes: 105 additions & 0 deletions defs/closure_bindist.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""Bazel declarations to run a native binary copy of Closure Compiler in downstream Bazel projects."""


# sample link (for `_bindist("1.0.0", "darwin", "arm64")`):
# https://github.com/sgammon/closure-compiler/releases/download/v1.0.0/compiler_native_1.0.0_darwin_arm64.tar.gz
def _bindist(version, os, arch):
return "https://github.com/sgammon/closure-compiler/releases/download/v%s/compiler_native_v%s_%s_%s.tar.gz" % (
version,
version,
os,
arch
)

# sample bundle (for `_bindist_bundle("1.0.0", "linux", archs = ["arm64", "s390x"])`):
# "linux": {
# "arm64": _bindist("1.0.0", "linux", "arm64"),
# "s390x": _bindist("1.0.0", "linux", "s390x"),
# },
def _bindist_bundle(version, os, archs = []):
return dict([
(arch, _bindist(version, os, arch))
for arch in archs
])

# sample version: (for `_bindist_bundle("1.0.0", bundles = {"darwin": ["arm64"], "linux": ["arm64"]})`)
# "1.8.6": {
# "linux": {
# "arm64": _bindist("1.0.0", "linux", "arm64"),
# },
# "darwin": {
# "arm64": _bindist("1.0.0", "darwin", "arm64"),
# },
# },
def _bindist_version(version, bundles = {}):
return dict([
(os, _bindist_bundle(version, os, archs))
for os, archs in bundles.items()
])


# version checksums (static)
_compiler_version_checksums = {
"v20220612_darwin_arm64": "1a787ec3a242e19589b041586dd487c13daa4dc0c19afb846cb31128f7606d87",
"v20220612_linux_amd64": "71d9488a6e3bf536e80b9cf74d353c8c42b7883d9d54de742152908390cbba0b",
}

# version configs (static)
_compiler_version_configs = {
"v20220612": _bindist_version(
version = "20220612",
bundles = {
"darwin": ["arm64"],
},
),
}

_compiler_latest_version = "v20220612"

def _get_platform(ctx):
res = ctx.execute(["uname", "-p"])
arch = "amd64"
if res.return_code == 0:
uname = res.stdout.strip()
if uname == "arm":
arch = "arm64"
elif uname == "aarch64":
arch = "aarch64"

if ctx.os.name == "linux":
return ("linux", arch)
elif ctx.os.name == "mac os x":
if arch == "arm64" or arch == "aarch64":
return ("darwin", "arm64")
return ("darwin", "x86_64")
else:
fail("Unsupported operating system: " + ctx.os.name)

def _compiler_bindist_repository_impl(ctx):
platform = _get_platform(ctx)
version = ctx.attr.version

# resolve dist
config = _compiler_version_configs[version]
link = config[platform[0]][platform[1]]
sha = _compiler_version_checksums["%s_%s_%s" % (version, platform[0], platform[1])]

urls = [link]
ctx.download_and_extract(
url = urls,
sha256 = sha,
)

ctx.file("BUILD", """exports_files(glob(["**/*"]))""")
ctx.file("WORKSPACE", "workspace(name = \"{name}\")".format(name = ctx.name))


closure_compiler_bindist_repository = repository_rule(
attrs = {
"version": attr.string(
mandatory = True,
default = _compiler_latest_version,
),
},
implementation = _compiler_bindist_repository_impl,
)
21 changes: 21 additions & 0 deletions native/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copyright 2020 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
#
# https://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(
default_visibility = ["//visibility:public"],
)

exports_files([
"reflection.json",
])
2 changes: 2 additions & 0 deletions native/bench/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
subjects
reports
40 changes: 40 additions & 0 deletions native/bench/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#
# Benchmark suite
#

all: subjects reports


SUBJECTS ?= https://unpkg.com/react@18.1.0/cjs/react.development.js \
https://unpkg.com/lodash@4.17.21/lodash.js \
https://unpkg.com/jquery@3.6.0/dist/jquery.js

TARGETS ?= react.development \
lodash \
jquery

REPORTS = $(patsubst %,%.jvm.min.js,$(TARGETS)) $(patsubst %,%.native.min.js,$(TARGETS))

WGET_ARGS ?= -q --progress=dot


reports: subjects ## Generate benchmark reports.
@echo "Benchmarking Closure Compiler (JVM vs. Native)..."
@for target in $(TARGETS); do python3 bench.py "$$target"; done

subjects: ## Download subject test files.
@echo "Downloading test bundles..."
@mkdir subjects
@cd subjects && for subj in $(SUBJECTS) ; do \
echo "- Downloading '$$subj'..." && \
wget $(WGET_ARGS) $$subj; \
done

clean: ## Clean benchmark reports and subject test files.
@echo "Cleaning subjects..."
@rm -fr subjects
@echo "Cleaning reports..."
@rm -fr reports

.PHONY: clean

101 changes: 101 additions & 0 deletions native/bench/bench.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#!/usr/bin/env python3

import sys, os, time


defaultIterations = 1

COMMAND_JVM_TARGET = "//:compiler_unshaded"
COMMAND_NATIVE_TARGET = "//:compiler_native"

COMMAND_CLOSURE_SIMPLE = [
"bazel",
"run",
"--ui_event_filters=-info,-stdout,-stderr",
"--noshow_progress",
"%binary%",
"--",
"--compilation_level=SIMPLE",
"--js=%input%",
"--js_output_file=%output%",
"2>",
"/dev/null",
]


def render_command_segment(seg, target, bundle, target_label):
return (
seg.replace("%binary%", target)
.replace("%input%", "$(bazel info workspace)/native/bench/subjects/%s.js" % bundle)
.replace("%output%", "$(bazel info workspace)/native/bench/reports/%s.%s.min.js" % (bundle, target_label))
)


def compile_command(target, target_label, bundle, args = []):
base_args = [i for i in map(lambda i: render_command_segment(i, target, bundle, target_label), COMMAND_CLOSURE_SIMPLE)]
base_args += args
return base_args

def run_report(bundle, jvm, native):
print("""

| Bundle: %s
|
| Build times:
| - JVM: %s
| - Native: %s
| -------------------
| Diff: %s (%s)

""" % (
bundle,
str(round(jvm)) + "ms",
str(round(native)) + "ms",
str(round(jvm - native)) + "ms",
((native < jvm) and "+" or "-") + str(round(100 - ((native/jvm) * 100))) + "%",
))

def run_compile(target, target_label, bundle, args = [], iterations = defaultIterations):
command = compile_command(target, target_label, bundle, args)
joined = " ".join(command)
print("- Running %s build for '%s' (iterations: %s)..." % (target_label, bundle, iterations))
all_measurements = []
for i in range(0, iterations):
start = round(time.time() * 1000)
os.system(" ".join(command))
end = round(time.time() * 1000)
all_measurements.append(end - start)
return sum(all_measurements) / iterations

def run_benchmark(bundle, variant = "SIMPLE", args = []):
print("\nBenchmarking bundle '%s' (variant: '%s')..." % (bundle, variant))
jvm_time = run_compile(
COMMAND_JVM_TARGET,
"jvm",
bundle,
args
)
native_time = run_compile(
COMMAND_NATIVE_TARGET,
"native",
bundle,
args
)
run_report(
bundle,
jvm_time,
native_time,
)

def run_bench(bundle):
"""Run a JVM copy of the Closure Compiler and compare it with a Native copy built with GraalVM."""
run_benchmark(
bundle,
)

if __name__ == "__main__":
if len(sys.argv) < 2:
print("Please provide bundle name to test")
sys.exit(2)
run_bench(sys.argv[1])

Loading