Skip to content

Commit 66fdcd8

Browse files
committed
Improved static method performance
1 parent da364ba commit 66fdcd8

5 files changed

Lines changed: 134 additions & 18 deletions

File tree

vm/ByteCodeTranslator/src/com/codename1/tools/translator/JavascriptMethodGenerator.java

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ private static void appendMethod(StringBuilder out, ByteCodeClass cls, BytecodeM
219219
return;
220220
}
221221
boolean usesClassInitCache = hasClassInitSensitiveAccess(instructions);
222+
boolean usesVirtualDispatchCache = hasVirtualDispatchAccess(instructions);
222223
out.append(" const locals = new Array(").append(Math.max(1, method.getMaxLocals())).append(").fill(null);\n");
223224
out.append(" const stack = [];\n");
224225
out.append(" let pc = 0;\n");
@@ -228,6 +229,9 @@ private static void appendMethod(StringBuilder out, ByteCodeClass cls, BytecodeM
228229
out.append(" __cn1Init[\"").append(cls.getClsName()).append("\"] = true;\n");
229230
}
230231
}
232+
if (usesVirtualDispatchCache) {
233+
out.append(" const __cn1Virtual = Object.create(null);\n");
234+
}
231235
if (!method.isStatic()) {
232236
out.append(" locals[0] = __cn1ThisObject;\n");
233237
}
@@ -251,7 +255,7 @@ private static void appendMethod(StringBuilder out, ByteCodeClass cls, BytecodeM
251255
for (int i = 0; i < instructions.size(); i++) {
252256
Instruction instruction = instructions.get(i);
253257
out.append(" case ").append(i).append(": {\n");
254-
appendInstruction(out, method, instructions, labelToIndex, instruction, i, usesClassInitCache);
258+
appendInstruction(out, method, instructions, labelToIndex, instruction, i, usesClassInitCache, usesVirtualDispatchCache);
255259
out.append(" }\n");
256260
}
257261
out.append(" default:\n");
@@ -799,13 +803,12 @@ private static boolean appendStraightLineTypeInstruction(StringBuilder out, Type
799803
}
800804
case Opcodes.CHECKCAST: {
801805
String value = ctx.peek(0);
802-
out.append(" if (").append(value).append(" != null && !jvm.instanceOf(").append(value).append(", \"")
803-
.append(typeName).append("\")) throw new Error(\"ClassCastException\");\n");
806+
appendDirectCheckCast(out, " ", value, typeName, "throw new Error(\"ClassCastException\")");
804807
return true;
805808
}
806809
case Opcodes.INSTANCEOF: {
807810
String value = ctx.pop();
808-
out.append(" ").append(ctx.push("(jvm.instanceOf(" + value + ", \"" + typeName + "\") ? 1 : 0)")).append(";\n");
811+
out.append(" ").append(ctx.push(directInstanceOfExpression(value, typeName) + " ? 1 : 0")).append(";\n");
809812
return true;
810813
}
811814
default:
@@ -874,7 +877,8 @@ private static boolean appendStraightLineInvokeInstruction(StringBuilder out, In
874877
if (invoke.getOpcode() == Opcodes.INVOKEVIRTUAL || invoke.getOpcode() == Opcodes.INVOKEINTERFACE) {
875878
out.append(" {\n");
876879
out.append(" const __target = ").append(target).append(";\n");
877-
out.append(" const __method = ((jvm.classes[__target.__class] && jvm.classes[__target.__class].methods) ? jvm.classes[__target.__class].methods[\"").append(methodId)
880+
out.append(" const __classDef = __target.__classDef;\n");
881+
out.append(" const __method = ((__classDef && __classDef.methods) ? __classDef.methods[\"").append(methodId)
878882
.append("\"] : null) || jvm.resolveVirtual(__target.__class, \"").append(methodId).append("\");\n");
879883
if (hasReturn) {
880884
out.append(" const __result = yield* __method(");
@@ -1079,7 +1083,8 @@ private static Map<Label, Integer> buildLabelMap(List<Instruction> instructions)
10791083
}
10801084

10811085
private static void appendInstruction(StringBuilder out, BytecodeMethod method, List<Instruction> allInstructions,
1082-
Map<Label, Integer> labelToIndex, Instruction instruction, int index, boolean usesClassInitCache) {
1086+
Map<Label, Integer> labelToIndex, Instruction instruction, int index, boolean usesClassInitCache,
1087+
boolean usesVirtualDispatchCache) {
10831088
if (instruction instanceof LabelInstruction || instruction instanceof LineNumber || instruction instanceof LocalVariable
10841089
|| instruction instanceof TryCatch) {
10851090
out.append(" pc = ").append(index + 1).append("; break;\n");
@@ -1117,7 +1122,7 @@ private static void appendInstruction(StringBuilder out, BytecodeMethod method,
11171122
return;
11181123
}
11191124
if (instruction instanceof Invoke) {
1120-
appendInvokeInstruction(out, (Invoke) instruction, index, usesClassInitCache);
1125+
appendInvokeInstruction(out, (Invoke) instruction, index, usesClassInitCache, usesVirtualDispatchCache);
11211126
return;
11221127
}
11231128
if (instruction instanceof SwitchInstruction) {
@@ -1552,19 +1557,32 @@ private static void appendTypeInstruction(StringBuilder out, TypeInstruction ins
15521557
.append("\", 1)); pc = ").append(index + 1).append("; break; }\n");
15531558
return;
15541559
case Opcodes.CHECKCAST:
1555-
out.append(" { const value = stack[stack.length - 1]; if (value != null && !jvm.instanceOf(value, \"")
1556-
.append(typeName)
1557-
.append("\")) throw new Error(\"ClassCastException\"); pc = ").append(index + 1).append("; break; }\n");
1560+
out.append(" { const value = stack[stack.length - 1]; ");
1561+
appendDirectCheckCast(out, "", "value", typeName, "throw new Error(\"ClassCastException\")");
1562+
out.append(" pc = ").append(index + 1).append("; break; }\n");
15581563
return;
15591564
case Opcodes.INSTANCEOF:
1560-
out.append(" { const value = stack.pop(); stack.push(jvm.instanceOf(value, \"").append(typeName)
1561-
.append("\") ? 1 : 0); pc = ").append(index + 1).append("; break; }\n");
1565+
out.append(" { const value = stack.pop(); stack.push(").append(directInstanceOfExpression("value", typeName))
1566+
.append(" ? 1 : 0); pc = ").append(index + 1).append("; break; }\n");
15621567
return;
15631568
default:
15641569
throw new IllegalArgumentException("Unsupported type opcode " + instruction.getOpcode());
15651570
}
15661571
}
15671572

1573+
private static void appendDirectCheckCast(StringBuilder out, String indent, String valueExpression, String typeName, String failureStatement) {
1574+
out.append(indent).append("if (").append(valueExpression).append(" != null) { const __classDef = ").append(valueExpression)
1575+
.append(".__classDef; if (").append(valueExpression).append(".__class !== \"").append(typeName)
1576+
.append("\" && !(__classDef && __classDef.assignableTo && __classDef.assignableTo[\"").append(typeName)
1577+
.append("\"])) ").append(failureStatement).append("; }\n");
1578+
}
1579+
1580+
private static String directInstanceOfExpression(String valueExpression, String typeName) {
1581+
return "(" + valueExpression + " != null && (" + valueExpression + ".__class === \"" + typeName
1582+
+ "\" || (" + valueExpression + ".__classDef && " + valueExpression + ".__classDef.assignableTo && "
1583+
+ valueExpression + ".__classDef.assignableTo[\"" + typeName + "\"])))";
1584+
}
1585+
15681586
private static void appendFieldInstruction(StringBuilder out, Field field, int index, boolean usesStaticFieldInitCache) {
15691587
String owner = JavascriptNameUtil.sanitizeClassName(field.getOwner());
15701588
String fieldName = field.getFieldName();
@@ -1617,6 +1635,18 @@ private static boolean hasClassInitSensitiveAccess(List<Instruction> instruction
16171635
return false;
16181636
}
16191637

1638+
private static boolean hasVirtualDispatchAccess(List<Instruction> instructions) {
1639+
for (Instruction instruction : instructions) {
1640+
if (instruction instanceof Invoke) {
1641+
int opcode = instruction.getOpcode();
1642+
if (opcode == Opcodes.INVOKEVIRTUAL || opcode == Opcodes.INVOKEINTERFACE) {
1643+
return true;
1644+
}
1645+
}
1646+
}
1647+
return false;
1648+
}
1649+
16201650
private static void appendJumpInstruction(StringBuilder out, Jump jump, Map<Label, Integer> labelToIndex, int index) {
16211651
Integer target = labelToIndex.get(jump.getLabel());
16221652
if (target == null) {
@@ -1679,7 +1709,8 @@ private static void appendJumpInstruction(StringBuilder out, Jump jump, Map<Labe
16791709
}
16801710
}
16811711

1682-
private static void appendInvokeInstruction(StringBuilder out, Invoke invoke, int index, boolean usesClassInitCache) {
1712+
private static void appendInvokeInstruction(StringBuilder out, Invoke invoke, int index, boolean usesClassInitCache,
1713+
boolean usesVirtualDispatchCache) {
16831714
String owner = JavascriptNameUtil.sanitizeClassName(invoke.getOwner());
16841715
String methodId = JavascriptNameUtil.methodIdentifier(invoke.getOwner(), invoke.getName(), invoke.getDesc());
16851716
String methodBodyId = jsStaticMethodBodyIdentifier(invoke.getOwner(), invoke.getName(), invoke.getDesc());
@@ -1701,8 +1732,21 @@ private static void appendInvokeInstruction(StringBuilder out, Invoke invoke, in
17011732
out.append(" {\n");
17021733
appendInvocationArgumentBindings(out, argCount, " ", "stack.pop()");
17031734
out.append(" const __target = stack.pop();\n");
1704-
out.append(" const __method = ((jvm.classes[__target.__class] && jvm.classes[__target.__class].methods) ? jvm.classes[__target.__class].methods[\"").append(methodId)
1705-
.append("\"] : null) || jvm.resolveVirtual(__target.__class, \"").append(methodId).append("\");\n");
1735+
out.append(" const __classDef = __target.__classDef;\n");
1736+
out.append(" let __method = (__classDef && __classDef.methods) ? __classDef.methods[\"").append(methodId)
1737+
.append("\"] : null;\n");
1738+
if (usesVirtualDispatchCache) {
1739+
out.append(" if (!__method) {\n");
1740+
out.append(" const __cacheKey = __target.__class + \"|").append(methodId).append("\";\n");
1741+
out.append(" __method = __cn1Virtual[__cacheKey];\n");
1742+
out.append(" if (!__method) {\n");
1743+
out.append(" __method = jvm.resolveVirtual(__target.__class, \"").append(methodId).append("\");\n");
1744+
out.append(" __cn1Virtual[__cacheKey] = __method;\n");
1745+
out.append(" }\n");
1746+
out.append(" }\n");
1747+
} else {
1748+
out.append(" if (!__method) __method = jvm.resolveVirtual(__target.__class, \"").append(methodId).append("\");\n");
1749+
}
17061750
if (hasReturn) {
17071751
out.append(" const __result = yield* __method(");
17081752
appendInvocationArguments(out, true, argCount);

vm/ByteCodeTranslator/src/javascript/parparvm_runtime.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,8 @@ const jvm = {
279279
if (!entries || !entries.length) {
280280
return null;
281281
}
282+
const errorClass = error == null ? null : error.__class;
283+
const errorClassDef = error == null ? null : error.__classDef;
282284
for (let i = 0; i < entries.length; i++) {
283285
const entry = entries[i];
284286
if (pc < entry.start || pc >= entry.end) {
@@ -287,7 +289,7 @@ const jvm = {
287289
if (entry.type == null) {
288290
return entry;
289291
}
290-
if (this.instanceOf(error, entry.type)) {
292+
if (errorClass === entry.type || (errorClassDef && errorClassDef.assignableTo && errorClassDef.assignableTo[entry.type])) {
291293
return entry;
292294
}
293295
}

vm/tests/src/test/java/com/codename1/tools/translator/JavascriptOpcodeCoverageTest.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,22 @@ void translatesObjectTypeAndDispatchCoverageFixture() throws Exception {
8181
&& translatedApp.contains("\"JsTypeBase\": true")
8282
&& translatedApp.contains("\"JsTypeIface\": true"),
8383
"Class metadata should include static assignability information");
84-
assertTrue(translatedApp.contains("jvm.classes[__target.__class] && jvm.classes[__target.__class].methods")
85-
&& translatedApp.contains("jvm.classes[__target.__class].methods["),
84+
assertTrue(translatedApp.contains("const __classDef = __target.__classDef;")
85+
&& translatedApp.contains("(__classDef && __classDef.methods) ? __classDef.methods["),
8686
"Virtual/interface dispatch should use an exact-class method-table fast path");
8787
assertTrue(translatedApp.contains("jvm.resolveVirtual(__target.__class"), "Dispatch should retain inheritance/interface fallback");
88+
assertTrue(translatedApp.contains("__class !== \"JsTypeImpl\"")
89+
&& translatedApp.contains("__classDef.assignableTo[\"JsTypeImpl\"]"),
90+
"CHECKCAST should inline the exact-class and assignability check");
91+
assertTrue(translatedApp.contains("__class === \"JsTypeImpl\"")
92+
&& translatedApp.contains(".__classDef.assignableTo[\"JsTypeImpl\"]"),
93+
"INSTANCEOF should inline the exact-class and assignability check");
94+
assertTrue(!translatedApp.contains("jvm.instanceOf("),
95+
"Translated object/type checks should avoid the generic runtime instanceof helper");
8896
assertTrue(runtime.contains("resolveVirtual(className, methodId)"), "Runtime should resolve virtual methods by class name");
8997
assertTrue(runtime.contains("obj.__classDef.assignableTo[className]"), "Runtime instanceof should use emitted class assignability tables");
98+
assertTrue(runtime.contains("errorClass === entry.type || (errorClassDef && errorClassDef.assignableTo && errorClassDef.assignableTo[entry.type])"),
99+
"Runtime exception matching should use direct class and assignability checks");
90100
assertTrue(runtime.contains("arrayAssignableTo(componentClass, dimensions)") && runtime.contains("isPrimitiveComponent(componentClass)"),
91101
"Runtime should keep array assignability limited to CN1-relevant cases");
92102
}

vm/tests/src/test/java/com/codename1/tools/translator/JavascriptTargetIntegrationTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,41 @@ void repeatedStaticInvokesUseMethodLevelInitCacheAndInternalImpls(CompilerHelper
314314
"Internal static method implementations should not repeat class-init guards");
315315
}
316316

317+
@ParameterizedTest
318+
@org.junit.jupiter.params.provider.MethodSource("com.codename1.tools.translator.BytecodeInstructionIntegrationTest#provideCompilerConfigs")
319+
void repeatedVirtualInvokesUseMethodLevelDispatchCacheInInterpreterMode(CompilerHelper.CompilerConfig config) throws Exception {
320+
Parser.cleanup();
321+
322+
Path sourceDir = Files.createTempDirectory("js-virtual-invoke-sources");
323+
Path classesDir = Files.createTempDirectory("js-virtual-invoke-classes");
324+
Path javaApiDir = Files.createTempDirectory("java-api-virtual-invoke-classes");
325+
326+
Files.write(sourceDir.resolve("JsVirtualInvokeFlow.java"), loadFixture("JsVirtualInvokeFlow.java").getBytes(StandardCharsets.UTF_8));
327+
328+
compileAgainstJavaApi(config, sourceDir, classesDir, javaApiDir);
329+
330+
Path outputDir = Files.createTempDirectory("js-virtual-invoke-output");
331+
runJavascriptTranslator(classesDir, outputDir, "JsVirtualInvokeFlow");
332+
333+
Path distDir = outputDir.resolve("dist").resolve("JsVirtualInvokeFlow-js");
334+
String translatedApp = new String(Files.readAllBytes(distDir.resolve("translated_app.js")), StandardCharsets.UTF_8);
335+
336+
String marker = "function* cn1_JsVirtualInvokeFlow_repeat_JsVirtualInvokeBase_int_R_int__impl(__cn1Arg1, __cn1Arg2){";
337+
int start = translatedApp.indexOf(marker);
338+
assertTrue(start >= 0, "Virtual invoke fixture should emit the repeat() method");
339+
int end = translatedApp.indexOf("\n}\n", start);
340+
assertTrue(end > start, "Virtual invoke fixture should have a bounded repeat() body");
341+
String methodBody = translatedApp.substring(start, end);
342+
343+
assertTrue(methodBody.contains("const __cn1Virtual = Object.create(null);"),
344+
"Interpreter-mode virtual dispatch should allocate a per-method cache");
345+
assertTrue(methodBody.contains("const __cacheKey = __target.__class + \"|cn1_JsVirtualInvokeBase_value_R_int\";"),
346+
"Virtual dispatch cache should key on runtime class and method id");
347+
assertTrue(methodBody.contains("__method = __cn1Virtual[__cacheKey];")
348+
&& methodBody.contains("__cn1Virtual[__cacheKey] = __method;"),
349+
"Virtual dispatch cache should store and reuse resolved fallback methods");
350+
}
351+
317352
static void compileAgainstJavaApi(CompilerHelper.CompilerConfig config, Path sourceDir, Path classesDir, Path javaApiDir) throws Exception {
318353
assertTrue(CompilerHelper.isJavaApiCompatible(config),
319354
"JDK " + config.jdkVersion + " must target matching bytecode level for JavaAPI");
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
class JsVirtualInvokeBase {
2+
int value() {
3+
return 3;
4+
}
5+
}
6+
7+
class JsVirtualInvokeImpl extends JsVirtualInvokeBase {
8+
int value() {
9+
return 5;
10+
}
11+
}
12+
13+
public class JsVirtualInvokeFlow {
14+
static int repeat(JsVirtualInvokeBase base, int delta) {
15+
int result = base.value();
16+
if (delta > 0) {
17+
result += base.value();
18+
}
19+
return result;
20+
}
21+
22+
public static void main(String[] args) {
23+
System.exit(repeat(new JsVirtualInvokeImpl(), 1));
24+
}
25+
}

0 commit comments

Comments
 (0)