Skip to content

Commit 8cb1c5d

Browse files
committed
[GR-46507] Support instance main methods (JEP 445)
PullRequest: graal/14807
2 parents 5ec71a2 + 1862377 commit 8cb1c5d

File tree

4 files changed

+194
-44
lines changed

4 files changed

+194
-44
lines changed

substratevm/mx.substratevm/mx_substratevm.py

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555

5656
import sys
5757

58+
5859
if sys.version_info[0] < 3:
5960
from StringIO import StringIO
6061
else:
@@ -730,13 +731,54 @@ def _cinterfacetutorial(native_image, args=None):
730731
mx.run([join(build_dir, 'cinterfacetutorial')])
731732

732733

733-
def _helloworld(native_image, javac_command, path, build_only, args):
734+
_helloworld_variants = {
735+
'traditional': '''
736+
public class HelloWorld {
737+
public static void main(String[] args) {
738+
System.out.println(System.getenv("%s"));
739+
}
740+
}
741+
''',
742+
'noArgs': '''
743+
// requires JDK 21 and --enable-preview
744+
public class HelloWorld {
745+
static void main() {
746+
System.out.println(System.getenv("%s"));
747+
}
748+
}
749+
''',
750+
'instance': '''
751+
// requires JDK 21 and --enable-preview
752+
class HelloWorld {
753+
void main(String[] args) {
754+
System.out.println(System.getenv("%s"));
755+
}
756+
}
757+
''',
758+
'instanceNoArgs': '''
759+
// requires JDK 21 and --enable-preview
760+
class HelloWorld {
761+
void main() {
762+
System.out.println(System.getenv("%s"));
763+
}
764+
}
765+
''',
766+
'unnamedClass': '''
767+
// requires JDK 21 and javac --enable-preview --source 21 and native-image --enable-preview
768+
void main() {
769+
System.out.println(System.getenv("%s"));
770+
}
771+
''',
772+
}
773+
774+
775+
def _helloworld(native_image, javac_command, path, build_only, args, variant=list(_helloworld_variants.keys())[0]):
734776
mkpath(path)
735777
hello_file = os.path.join(path, 'HelloWorld.java')
736778
envkey = 'HELLO_WORLD_MESSAGE'
737779
output = 'Hello from native-image!'
738780
with open(hello_file, 'w') as fp:
739-
fp.write('public class HelloWorld { public static void main(String[] args) { System.out.println(System.getenv("' + envkey + '")); } }')
781+
fp.write(_helloworld_variants[variant] % envkey)
740782
fp.flush()
741783
mx.run(javac_command + [hello_file])
742784

@@ -1238,20 +1280,26 @@ def _native_image_configure_extra_jvm_args():
12381280

12391281
def run_helloworld_command(args, config, command_name):
12401282
parser = ArgumentParser(prog='mx ' + command_name)
1241-
all_args = ['--output-path', '--javac-command', '--build-only']
1283+
all_args = ['--output-path', '--javac-command', '--build-only', '--variant', '--list']
12421284
masked_args = [_mask(arg, all_args) for arg in args]
1285+
default_variant = list(_helloworld_variants.keys())[0]
12431286
parser.add_argument(all_args[0], metavar='<output-path>', nargs=1, help='Path of the generated image', default=[svmbuild_dir(suite)])
12441287
parser.add_argument(all_args[1], metavar='<javac-command>', help='A javac command to be used', default=mx.get_jdk().javac)
12451288
parser.add_argument(all_args[2], action='store_true', help='Only build the native image')
1289+
parser.add_argument(all_args[3], choices=_helloworld_variants.keys(), default=default_variant, help=f'The Hello World source code variant to use (default: {default_variant})')
1290+
parser.add_argument(all_args[4], action='store_true', help='Print the Hello World source and exit')
12461291
parser.add_argument('image_args', nargs='*', default=[])
12471292
parsed = parser.parse_args(masked_args)
12481293
javac_command = unmask(parsed.javac_command.split())
12491294
output_path = unmask(parsed.output_path)[0]
12501295
build_only = parsed.build_only
12511296
image_args = unmask(parsed.image_args)
1297+
if parsed.list:
1298+
mx.log(_helloworld_variants[parsed.variant])
1299+
return
12521300
native_image_context_run(
12531301
lambda native_image, a:
1254-
_helloworld(native_image, javac_command, output_path, build_only, a), unmask(image_args),
1302+
_helloworld(native_image, javac_command, output_path, build_only, a, variant=parsed.variant), unmask(image_args),
12551303
config=config,
12561304
)
12571305

substratevm/mx.substratevm/testhello.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,12 @@ def test():
120120
# expect "#1 0x[0-9a-f]+ in com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_.* at [a-z/]+/JavaMainWrapper.java:[0-9]+"
121121
exec_string = execute("backtrace")
122122
stacktraceRegex = [r"#0%shello\.Hello::main%s %s at hello/Hello\.java:77"%(spaces_pattern, param_types_pattern, arg_values_pattern),
123-
r"#1%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore0%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern),
124-
r"#2%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern),
125-
r"#3%scom\.oracle\.svm\.core\.JavaMainWrapper::doRun%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, param_types_pattern, arg_values_pattern, package_pattern),
126-
r"#4%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::run%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern),
127-
r"#5%scom\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s%s %s"%(spaces_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern)
123+
r"#1%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::invokeMain%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern),
124+
r"#2%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::runCore0%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern),
125+
r"#3%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern),
126+
r"#4%scom\.oracle\.svm\.core\.JavaMainWrapper::doRun%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, param_types_pattern, arg_values_pattern, package_pattern),
127+
r"#5%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::run%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern),
128+
r"#6%scom\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s%s %s"%(spaces_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern)
128129
]
129130
if musl:
130131
# musl has a different entry point - drop the last two frames
@@ -364,11 +365,12 @@ def test():
364365
exec_string = execute("backtrace")
365366
stacktraceRegex = [r"#0%shello\.Hello\$Greeter::greeter%s %s at hello/Hello\.java:38"%(spaces_pattern, param_types_pattern, arg_values_pattern),
366367
r"#1%s%s in hello\.Hello::main%s %s at hello/Hello\.java:77"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern),
367-
r"#2%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore0%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern),
368-
r"#3%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern),
369-
r"#4%scom\.oracle\.svm\.core\.JavaMainWrapper::doRun%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, param_types_pattern, arg_values_pattern, package_pattern),
370-
r"#5%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::run%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern),
371-
r"#6%scom\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s%s %s"%(spaces_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern)
368+
r"#2%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::invokeMain%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern),
369+
r"#3%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::runCore0%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern),
370+
r"#4%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern),
371+
r"#5%scom\.oracle\.svm\.core\.JavaMainWrapper::doRun%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, param_types_pattern, arg_values_pattern, package_pattern),
372+
r"#6%s(%s in )?com\.oracle\.svm\.core\.JavaMainWrapper::run%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern, package_pattern),
373+
r"#7%scom\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s%s %s"%(spaces_pattern, hex_digits_pattern, param_types_pattern, arg_values_pattern)
372374
]
373375
if musl:
374376
# musl has a different entry point - drop the last two frames

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/JavaMainWrapper.java

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,17 @@
2626

2727
import java.lang.invoke.MethodHandle;
2828
import java.lang.invoke.MethodHandles;
29+
import java.lang.reflect.Constructor;
2930
import java.lang.reflect.Method;
31+
import java.lang.reflect.Modifier;
3032
import java.util.ArrayList;
3133
import java.util.Arrays;
3234
import java.util.Collections;
3335
import java.util.List;
3436
import java.util.function.BooleanSupplier;
3537

38+
import com.oracle.svm.util.ClassUtil;
39+
import org.graalvm.compiler.serviceprovider.JavaVersionUtil;
3640
import org.graalvm.compiler.word.Word;
3741
import org.graalvm.nativeimage.CurrentIsolate;
3842
import org.graalvm.nativeimage.ImageSingletons;
@@ -74,7 +78,9 @@
7478
import com.oracle.svm.core.thread.ThreadListenerSupport;
7579
import com.oracle.svm.core.thread.VMThreads;
7680
import com.oracle.svm.core.util.CounterSupport;
81+
import com.oracle.svm.core.util.UserError;
7782
import com.oracle.svm.core.util.VMError;
83+
import com.oracle.svm.util.ReflectionUtil;
7884

7985
@InternalVMMethod
8086
public class JavaMainWrapper {
@@ -92,14 +98,41 @@ public class JavaMainWrapper {
9298

9399
public static class JavaMainSupport {
94100

95-
public final MethodHandle javaMainHandle;
101+
private final MethodHandle javaMainHandle;
102+
private final MethodHandle javaMainClassCtorHandle;
96103
final String javaMainClassName;
97104

98105
public String[] mainArgs;
99106

107+
private final boolean mainWithoutArgs;
108+
private final boolean mainNonstatic;
109+
100110
@Platforms(Platform.HOSTED_ONLY.class)
101111
public JavaMainSupport(Method javaMainMethod) throws IllegalAccessException {
102-
this.javaMainHandle = MethodHandles.lookup().unreflect(javaMainMethod);
112+
if (instanceMainMethodSupported()) {
113+
javaMainMethod.setAccessible(true);
114+
int mods = javaMainMethod.getModifiers();
115+
this.mainNonstatic = !Modifier.isStatic(mods);
116+
this.mainWithoutArgs = javaMainMethod.getParameterCount() == 0;
117+
MethodHandle mainHandle = MethodHandles.lookup().unreflect(javaMainMethod);
118+
MethodHandle ctorHandle = null;
119+
if (mainNonstatic) {
120+
// Instance main
121+
try {
122+
Constructor<?> ctor = ReflectionUtil.lookupConstructor(javaMainMethod.getDeclaringClass());
123+
ctorHandle = MethodHandles.lookup().unreflectConstructor(ctor);
124+
} catch (ReflectionUtil.ReflectionUtilError ex) {
125+
throw UserError.abort(ex, "No non-private zero argument constructor found in class %s", ClassUtil.getUnqualifiedName(javaMainMethod.getDeclaringClass()));
126+
}
127+
}
128+
this.javaMainHandle = mainHandle;
129+
this.javaMainClassCtorHandle = ctorHandle;
130+
} else {
131+
this.mainNonstatic = false;
132+
this.mainWithoutArgs = false;
133+
this.javaMainHandle = MethodHandles.lookup().unreflect(javaMainMethod);
134+
this.javaMainClassCtorHandle = null;
135+
}
103136
this.javaMainClassName = javaMainMethod.getDeclaringClass().getName();
104137
}
105138

@@ -129,6 +162,41 @@ public List<String> getInputArguments() {
129162
}
130163
return Collections.emptyList();
131164
}
165+
166+
}
167+
168+
public static void invokeMain(String[] args) throws Throwable {
169+
JavaMainSupport javaMainSupport = ImageSingletons.lookup(JavaMainSupport.class);
170+
if (javaMainSupport.mainNonstatic) {
171+
Object instance = javaMainSupport.javaMainClassCtorHandle.invoke();
172+
if (javaMainSupport.mainWithoutArgs) {
173+
javaMainSupport.javaMainHandle.invoke(instance);
174+
} else {
175+
javaMainSupport.javaMainHandle.invoke(instance, args);
176+
}
177+
} else {
178+
if (javaMainSupport.mainWithoutArgs) {
179+
javaMainSupport.javaMainHandle.invokeExact();
180+
} else {
181+
javaMainSupport.javaMainHandle.invokeExact(args);
182+
}
183+
}
184+
}
185+
186+
/**
187+
* Determines whether instance main methodes are enabled. See JDK-8306112: Implementation of JEP
188+
* 445: Unnamed Classes and Instance Main Methods (Preview).
189+
*/
190+
public static boolean instanceMainMethodSupported() {
191+
if (JavaVersionUtil.JAVA_SPEC < 21) {
192+
return false;
193+
}
194+
var previewFeature = ReflectionUtil.lookupClass(true, "jdk.internal.misc.PreviewFeatures");
195+
try {
196+
return previewFeature != null && (Boolean) previewFeature.getDeclaredMethod("isEnabled").invoke(null);
197+
} catch (ReflectiveOperationException e) {
198+
throw VMError.shouldNotReachHere(e);
199+
}
132200
}
133201

134202
@Uninterruptible(reason = "The caller initialized the thread state, so the callees do not need to be uninterruptible.", calleeMustBe = false)
@@ -175,7 +243,7 @@ private static int runCore0() {
175243
* exceptions in a InvocationTargetException.
176244
*/
177245
JavaMainSupport mainSupport = ImageSingletons.lookup(JavaMainSupport.class);
178-
mainSupport.javaMainHandle.invokeExact(mainSupport.mainArgs);
246+
invokeMain(mainSupport.mainArgs);
179247
return 0;
180248
} catch (Throwable ex) {
181249
JavaThreads.dispatchUncaughtException(Thread.currentThread(), ex);

0 commit comments

Comments
 (0)