Skip to content

Commit 1448047

Browse files
Introduce ForNameRespectsClassLoader option and use it in Crema
* Add class registries for class loaders, add classes at build time based on their defining class loader. * In crema mode add all classes. Without crema, only classes that are configured for runtime reflection are added. * Use as much as possible of the standard class loading code from the JDK as possible. * Add ClassLoading scopes experimental API to allow ignoring the reflection configuration in explicit scopes. * In the crema case, wire class definition to the shared class file parser. * Shade espresso shared code for usage in svm to avoid conflicts with espresso shared code used in application code (e.g., espresso native images) * Add hellocrema command to exercise class loading
1 parent b903836 commit 1448047

File tree

35 files changed

+2629
-198
lines changed

35 files changed

+2629
-198
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
#
2+
# Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
3+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
#
5+
# This code is free software; you can redistribute it and/or modify it
6+
# under the terms of the GNU General Public License version 2 only, as
7+
# published by the Free Software Foundation. Oracle designates this
8+
# particular file as subject to the "Classpath" exception as provided
9+
# by Oracle in the LICENSE file that accompanied this code.
10+
#
11+
# This code is distributed in the hope that it will be useful, but WITHOUT
12+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
# version 2 for more details (a copy is included in the LICENSE file that
15+
# accompanied this code).
16+
#
17+
# You should have received a copy of the GNU General Public License version
18+
# 2 along with this work; if not, write to the Free Software Foundation,
19+
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
#
21+
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
# or visit www.oracle.com if you need additional information or have any
23+
# questions.
24+
#
25+
import os
26+
import re
27+
from collections import OrderedDict
28+
from os.path import join, exists, relpath
29+
30+
import mx
31+
import mx_util
32+
33+
34+
class EspressoSVMShared(mx.JavaProject):
35+
def __init__(self, suite, name, deps, workingSets, theLicense=None, **attr):
36+
self.shadedProjects = attr.pop('shadedProjects')
37+
self.removeAnnotations = attr.pop('removeAnnotations', [])
38+
self.checkPackagePrefix = True
39+
packageMap = attr.pop('packageMap')
40+
self.packageMap = OrderedDict()
41+
for k in sorted(packageMap, key=str.__len__, reverse=True):
42+
self.packageMap[k + '.'] = packageMap[k] + '.'
43+
self.pathMap = OrderedDict((k.replace('.', os.sep), v.replace('.', os.sep)) for k, v in self.packageMap.items())
44+
javaCompliance = attr.pop('javaCompliance')
45+
super().__init__(suite, name, "", [], deps, javaCompliance, workingSets, suite.dir, theLicense, **attr)
46+
self.gen_src = join(self.get_output_root(), 'gen_src')
47+
self.srcDirs.append(self.gen_src)
48+
49+
def getBuildTask(self, args):
50+
jdk = mx.get_jdk(self.javaCompliance, tag=mx.DEFAULT_JDK_TAG, purpose='building ' + self.name)
51+
return EspressoSVMSharedBuildTask(args, self, jdk)
52+
53+
def resolveDeps(self):
54+
super().resolveDeps()
55+
self._resolveDepsHelper(self.shadedProjects)
56+
not_java_projects = [d for d in self.shadedProjects if not d.isJavaProject()]
57+
if not_java_projects:
58+
raise self.abort(f"shadedProjects must all be java projects, but the following are not: {not_java_projects}")
59+
60+
def get_checkstyle_config(self, resolve_checkstyle_library=True):
61+
return None, None, None
62+
63+
def shaded_deps(self):
64+
return self.shadedProjects
65+
66+
def _apply_package_map(self, original, is_path):
67+
map_dict = self.pathMap if is_path else self.packageMap
68+
for k, v in map_dict.items():
69+
if original.startswith(k):
70+
return v + original[len(k):]
71+
return original
72+
73+
def substitute_package_name(self, pkg):
74+
return self._apply_package_map(pkg, False)
75+
76+
def substitute_path(self, path):
77+
return self._apply_package_map(path, True)
78+
79+
def defined_java_packages(self):
80+
return set([self.substitute_package_name(pkg) for dep in self.shaded_deps() for pkg in dep.defined_java_packages()])
81+
82+
83+
class EspressoSVMSharedBuildTask(mx.JavaBuildTask):
84+
def saved_config_path(self):
85+
return join(self.subject.get_output_root(), 'config')
86+
87+
def config(self):
88+
config = str(sorted(self.subject.shaded_deps())) + '\n'
89+
config += str(self.subject.javaCompliance) + '\n'
90+
for pkg in sorted(self.subject.packageMap):
91+
config += f"{pkg}: {self.subject.packageMap[pkg]}\n"
92+
config += str(sorted(self.subject.removeAnnotations)) + '\n'
93+
return config
94+
95+
def _walk_files(self, create_shaded_dirs=False):
96+
for shaded_project in self.subject.shaded_deps():
97+
for src_dir in shaded_project.source_dirs():
98+
for dirpath, _, filenames in os.walk(src_dir):
99+
reldirpath = relpath(dirpath, src_dir)
100+
mapped_reldirpath = self.subject.substitute_path(reldirpath)
101+
if create_shaded_dirs and filenames:
102+
mx_util.ensure_dir_exists(join(self.subject.gen_src, mapped_reldirpath))
103+
for filename in filenames:
104+
shaded_relpath = join(mapped_reldirpath, filename)
105+
yield join(dirpath, filename), join(self.subject.gen_src, shaded_relpath), shaded_relpath
106+
107+
def needsBuild(self, newestInput):
108+
is_needed, reason = mx.ProjectBuildTask.needsBuild(self, newestInput)
109+
if is_needed:
110+
return True, reason
111+
config_path = self.saved_config_path()
112+
if not exists(config_path):
113+
return True, "Saved config file not found"
114+
config = self.config()
115+
with open(config_path, 'r', encoding='utf-8') as f:
116+
if f.read() != config:
117+
return True, "Configuration changed"
118+
for original, shaded, _ in self._walk_files():
119+
ts = mx.TimeStampFile(shaded)
120+
if ts.isOlderThan(original):
121+
return True, str(ts)
122+
if newestInput and ts.isOlderThan(newestInput):
123+
return True, str(ts)
124+
return False, None
125+
126+
def _collect_files(self):
127+
if self._javafiles is not None:
128+
# already collected
129+
return self
130+
javafiles = {}
131+
non_javafiles = {}
132+
output_dir = self.subject.output_dir()
133+
for _, shaded, shaded_relpath in self._walk_files():
134+
if shaded.endswith('.java'):
135+
classfile = output_dir + shaded_relpath[:-len('.java')] + '.class'
136+
javafiles[shaded] = classfile
137+
else:
138+
non_javafiles[shaded] = output_dir + shaded_relpath
139+
if hasattr(self.subject, 'copyFiles'):
140+
raise mx.abort('copyFiles is not supported', context=self.subject)
141+
self._javafiles = javafiles
142+
self._non_javafiles = non_javafiles
143+
self._copyfiles = {}
144+
return self
145+
146+
def clean(self, forBuild=False):
147+
super().clean()
148+
if exists(self.subject.gen_src):
149+
mx.rmtree(self.subject.gen_src)
150+
151+
def build(self):
152+
java_substitutions = [
153+
sub for orig, shad in self.subject.packageMap.items() for sub in [
154+
(re.compile(r'\b' + re.escape(orig) + r'(?=\.\w+)?\b'), shad),
155+
]
156+
]
157+
re_type_start = re.compile(r"\.[A-Z]")
158+
for annotation_type in self.subject.removeAnnotations:
159+
type_name_start = re_type_start.search(annotation_type).start()
160+
# remove `import com.Foo.Bar;`
161+
# remove `@Foo.Bar(*)`
162+
# remove `@Foo.Bar`
163+
# change `{@link Foo.Bar}` to `{@code Foo.Bar}`
164+
unqualified_type = annotation_type[type_name_start + 1:]
165+
java_substitutions += [
166+
(re.compile(r'^import\s+' + re.escape(annotation_type) + r'\s*;', re.MULTILINE), ''),
167+
(re.compile(r'@' + re.escape(unqualified_type) + r'(\(.*?\))?'), ''),
168+
(re.compile(r'\{@link ' + re.escape(unqualified_type) + r'\}'), '{@code ' + unqualified_type + '}'),
169+
]
170+
next_type_name_m = re_type_start.search(annotation_type, type_name_start + 1)
171+
if next_type_name_m:
172+
# remove `import com.Foo;`
173+
# remove `import static com.Foo.Bar;`
174+
# remove `@Bar(*)`
175+
# remove `@Bar`
176+
# change `{@link Bar}` to `{@code Bar}`
177+
next_type_name_start = next_type_name_m.start()
178+
next_unqualified_type = annotation_type[next_type_name_start + 1:]
179+
java_substitutions += [
180+
(re.compile(r'^import\s+' + re.escape(annotation_type[:next_type_name_start]) + r'\s*;', re.MULTILINE), ''),
181+
(re.compile(r'^import\s+static\s+' + re.escape(annotation_type) + r'\s*;', re.MULTILINE), ''),
182+
(re.compile(r'@' + re.escape(next_unqualified_type) + r'(\(.*?\))?'), ''),
183+
(re.compile(r'\{@link ' + re.escape(next_unqualified_type) + r'\}'), '{@code ' + next_unqualified_type + '}'),
184+
]
185+
assert not re_type_start.search(annotation_type, next_type_name_start + 1)
186+
187+
for original, shaded, _ in self._walk_files(True):
188+
with open(original, 'r', encoding='utf-8') as f_orig, open(shaded, 'w', encoding='utf-8') as f_shaded:
189+
for line in f_orig:
190+
for srch, repl in java_substitutions:
191+
line = re.sub(srch, repl, line)
192+
f_shaded.write(line)
193+
super().build()
194+
with open(self.saved_config_path(), 'w', encoding='utf-8') as f:
195+
f.write(self.config())

espresso-shared/mx.espresso-shared/suite.py

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,6 @@
8383
"sourceDirs": ["src"],
8484
"dependencies": [
8585
"sdk:COLLECTIONS",
86-
],
87-
"buildDependencies": [
8886
"truffle:TRUFFLE_API",
8987
],
9088
"javaCompliance" : "17+",
@@ -102,6 +100,28 @@
102100
"javaCompliance" : "17+",
103101
"checkstyle": "com.oracle.truffle.espresso.classfile",
104102
},
103+
104+
# Shared code shaded for SVM
105+
"com.oracle.svm.espresso": {
106+
"class": "EspressoSVMShared",
107+
"shadedProjects": [
108+
"com.oracle.truffle.espresso.classfile",
109+
"com.oracle.truffle.espresso.shared",
110+
],
111+
"dependencies": [
112+
"sdk:COLLECTIONS",
113+
],
114+
"removeAnnotations": [
115+
"com.oracle.truffle.api.CompilerDirectives.CompilationFinal",
116+
"com.oracle.truffle.api.CompilerDirectives.TruffleBoundary",
117+
"com.oracle.truffle.api.nodes.ExplodeLoop",
118+
],
119+
"packageMap": {
120+
"com.oracle.truffle.espresso": "com.oracle.svm.espresso",
121+
},
122+
"eclipseformat": False,
123+
"javaCompliance" : "17+",
124+
},
105125
},
106126

107127
# ------------- distributions
@@ -111,15 +131,37 @@
111131
"moduleInfo" : {
112132
"name" : "org.graalvm.espresso.shared",
113133
"exports": [
114-
"* to org.graalvm.espresso, org.graalvm.nativeimage.builder",
134+
"* to org.graalvm.espresso",
115135
],
116136
},
117-
"description" : "Shared code runtime class loading",
137+
"description" : "Espresso shared code for runtime class loading",
118138
"subDir": "src",
119139
"dependencies": [
120140
"com.oracle.truffle.espresso.classfile",
121141
"com.oracle.truffle.espresso.shared",
122142
],
143+
"distDependencies": [
144+
"sdk:COLLECTIONS",
145+
"truffle:TRUFFLE_API",
146+
],
147+
"maven" : {
148+
"tag": ["default", "public"],
149+
},
150+
"useModulePath": True,
151+
"noMavenJavadoc": True,
152+
},
153+
"ESPRESSO_SVM": {
154+
"moduleInfo" : {
155+
"name" : "org.graalvm.espresso.shared.svm",
156+
"exports": [
157+
"* to org.graalvm.nativeimage.builder",
158+
],
159+
},
160+
"description" : "Espresso shared code for runtime class loading (shaded for SVM)",
161+
"subDir": "src",
162+
"dependencies": [
163+
"com.oracle.svm.espresso",
164+
],
123165
"distDependencies": [
124166
"sdk:COLLECTIONS",
125167
],

espresso-shared/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/ByteSequence.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,31 @@ public static ByteSequence create(String str) {
8888
return ByteSequence.wrap(bytes, 0, bytes.length);
8989
}
9090

91+
public static ByteSequence create(String str, int start) {
92+
byte[] bytes = ModifiedUTF8.fromJavaString(str, start, str.length() - start, false);
93+
return ByteSequence.wrap(bytes, 0, bytes.length);
94+
}
95+
96+
public static ByteSequence createTypeFromName(String str) {
97+
byte[] bytes = ModifiedUTF8.fromJavaString(str, 0, str.length(), false, true);
98+
for (int i = 0; i < bytes.length; i++) {
99+
if (bytes[i] == '.') {
100+
bytes[i] = '/';
101+
}
102+
}
103+
return ByteSequence.wrap(bytes, 0, bytes.length);
104+
}
105+
106+
public static ByteSequence createReplacingDot(String str, int start) {
107+
byte[] bytes = ModifiedUTF8.fromJavaString(str, start, str.length() - start, false);
108+
for (int i = 0; i < bytes.length; i++) {
109+
if (bytes[i] == '.') {
110+
bytes[i] = '/';
111+
}
112+
}
113+
return ByteSequence.wrap(bytes, 0, bytes.length);
114+
}
115+
91116
public static ByteSequence from(ByteBuffer buffer) {
92117
int length = buffer.remaining();
93118
if (buffer.hasArray()) {

espresso-shared/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/ModifiedUTF8.java

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,17 @@ public static byte[] fromJavaString(String str, boolean append0) {
8787
}
8888

8989
public static byte[] fromJavaString(String str, int start, int len, boolean append0) {
90+
return fromJavaString(str, start, len, append0, false);
91+
}
92+
93+
static byte[] fromJavaString(String str, int start, int len, boolean append0, boolean addLSemi) {
9094
int utflen = utfLength(str, start, len);
91-
byte[] bytearr = new byte[utflen + (append0 ? 1 : 0)]; // 0 terminated, even if empty.
95+
byte[] bytearr = new byte[utflen + (append0 ? 1 : 0) + (addLSemi ? 2 : 0)];
9296

9397
int count = 0;
98+
if (addLSemi) {
99+
bytearr[count++] = 'L';
100+
}
94101
int i;
95102
for (i = 0; i < len; i++) {
96103
int c = str.charAt(start + i);
@@ -114,10 +121,15 @@ public static byte[] fromJavaString(String str, int start, int len, boolean appe
114121
bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F));
115122
}
116123
}
124+
if (addLSemi) {
125+
bytearr[count++] = ';';
126+
}
117127

118128
if (append0) {
119-
bytearr[bytearr.length - 1] = (byte) 0;
129+
assert count == bytearr.length - 1;
130+
bytearr[count++] = (byte) 0;
120131
}
132+
assert count == bytearr.length;
121133

122134
return bytearr;
123135
}

espresso-shared/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/Symbols.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,12 @@
4040
* the table.
4141
*/
4242
public abstract sealed class Symbols permits SymbolsImpl {
43-
44-
static final int DEFAULT_CAPACITY = 1 << 12;
45-
4643
public static Symbols fromExisting(Set<Symbol<?>> existingSymbols, int initialSymbolTable) {
47-
return new SymbolsImpl(existingSymbols, initialSymbolTable);
44+
return fromExisting(existingSymbols, initialSymbolTable, initialSymbolTable);
45+
}
46+
47+
public static Symbols fromExisting(Set<Symbol<?>> existingSymbols, int initialStrongSize, int initialWeakSize) {
48+
return new SymbolsImpl(existingSymbols, initialStrongSize, initialWeakSize);
4849
}
4950

5051
/**

espresso-shared/src/com.oracle.truffle.espresso.classfile/src/com/oracle/truffle/espresso/classfile/descriptors/SymbolsImpl.java

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,26 @@
3434
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
3535

3636
final class SymbolsImpl extends Symbols {
37-
3837
// Set generous initial capacity, these are going to be hit a lot.
3938
private final ConcurrentHashMap<ByteSequence, Symbol<?>> strongMap;
4039
private final WeakHashMap<ByteSequence, WeakReference<Symbol<?>>> weakMap;
4140
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
4241

43-
SymbolsImpl(int initialCapacity) {
44-
this.strongMap = new ConcurrentHashMap<>(initialCapacity);
45-
this.weakMap = new WeakHashMap<>(initialCapacity);
46-
}
47-
48-
SymbolsImpl() {
49-
this(DEFAULT_CAPACITY);
42+
SymbolsImpl(int initialStrongSize, int initialWeakSize) {
43+
if (initialWeakSize > 0) {
44+
this.strongMap = new ConcurrentHashMap<>(initialStrongSize);
45+
} else {
46+
this.strongMap = new ConcurrentHashMap<>();
47+
}
48+
if (initialWeakSize > 0) {
49+
this.weakMap = new WeakHashMap<>(initialWeakSize);
50+
} else {
51+
this.weakMap = new WeakHashMap<>();
52+
}
5053
}
5154

52-
SymbolsImpl(Set<Symbol<?>> existingSymbols, int initialCapacity) {
53-
this(initialCapacity);
55+
SymbolsImpl(Set<Symbol<?>> existingSymbols, int initialStrongSize, int initialWeakSize) {
56+
this(initialStrongSize, initialWeakSize);
5457
for (Symbol<?> symbol : existingSymbols) {
5558
this.strongMap.put(symbol, symbol);
5659
}

0 commit comments

Comments
 (0)