Skip to content

Commit c994b1e

Browse files
committed
Performance work and hardening
1 parent 66fdcd8 commit c994b1e

9 files changed

Lines changed: 184 additions & 50 deletions

File tree

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ static void write(File outputDirectory, List<ByteCodeClass> classes) throws IOEx
1919
writeTranslatedClasses(outputDirectory, classes);
2020
writeWorker(outputDirectory);
2121
writeIndex(outputDirectory);
22+
writeProtocol(outputDirectory);
2223
}
2324

2425
private static void writeRuntime(File outputDirectory) throws IOException {
@@ -71,6 +72,10 @@ private static void writeIndex(File outputDirectory) throws IOException {
7172
writeResource(outputDirectory, "index.html", "index.html");
7273
}
7374

75+
private static void writeProtocol(File outputDirectory) throws IOException {
76+
writeResource(outputDirectory, "vm_protocol.md", "vm_protocol.md");
77+
}
78+
7479
private static void writeResource(File outputDirectory, String targetName, String resourceName) throws IOException {
7580
Files.write(new File(outputDirectory, targetName).toPath(),
7681
loadResource(resourceName).getBytes(StandardCharsets.UTF_8));

vm/ByteCodeTranslator/src/javascript/parparvm_runtime.js

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ const jvm = {
5050
classes: {},
5151
literalStrings: Object.create(null),
5252
methodTailCache: Object.create(null),
53+
remappedMethodIdCache: Object.create(null),
54+
resolvedVirtualCache: Object.create(null),
5355
nextIdentity: 1,
5456
nextThreadId: 1,
5557
nextHostCallId: 1,
@@ -123,6 +125,7 @@ const jvm = {
123125
const classDef = this.classes[className];
124126
const obj = { __class: className, __classDef: classDef, __id: this.nextIdentity++, __monitor: this.createMonitor() };
125127
this.initInstanceFields(obj, className);
128+
this.initFieldAliases(obj, className);
126129
return obj;
127130
},
128131
initInstanceFields(obj, className) {
@@ -140,6 +143,41 @@ const jvm = {
140143
}
141144
}
142145
},
146+
initFieldAliases(obj, className) {
147+
const hierarchy = [];
148+
let current = className;
149+
while (current) {
150+
hierarchy.push(current);
151+
const cls = this.classes[current];
152+
current = cls ? cls.baseClass : null;
153+
}
154+
for (let i = hierarchy.length - 1; i >= 0; i--) {
155+
const owner = hierarchy[i];
156+
const cls = this.classes[owner];
157+
if (!cls || !cls.instanceFields) {
158+
continue;
159+
}
160+
for (let j = 0; j < cls.instanceFields.length; j++) {
161+
const field = cls.instanceFields[j];
162+
const canonicalProp = field.prop || this.fieldProperty(field.owner, field.name);
163+
for (let k = 0; k < i; k++) {
164+
const aliasProp = this.fieldProperty(hierarchy[k], field.name);
165+
if (aliasProp === canonicalProp || Object.prototype.hasOwnProperty.call(obj, aliasProp)) {
166+
continue;
167+
}
168+
Object.defineProperty(obj, aliasProp, {
169+
configurable: true,
170+
enumerable: false,
171+
get: function() { return obj[canonicalProp]; },
172+
set: function(value) { obj[canonicalProp] = value; }
173+
});
174+
}
175+
}
176+
}
177+
},
178+
fieldProperty(owner, name) {
179+
return "cn1_" + owner + "_" + name;
180+
},
143181
newArray(size, componentClass, dimensions) {
144182
size = size | 0;
145183
if (size < 0) {
@@ -233,18 +271,27 @@ const jvm = {
233271
return componentClass.indexOf("JAVA_") === 0;
234272
},
235273
resolveVirtual(className, methodId) {
274+
const cacheKey = className + "|" + methodId;
275+
let cached = this.resolvedVirtualCache[cacheKey];
276+
if (cached) {
277+
return cached;
278+
}
236279
const tail = this.methodTail(methodId);
237280
let current = className;
238281
while (current) {
239282
const cls = this.classes[current];
240283
if (cls && cls.methods) {
241284
if (cls.methods[methodId]) {
242-
return cls.methods[methodId];
285+
cached = cls.methods[methodId];
286+
this.resolvedVirtualCache[cacheKey] = cached;
287+
return cached;
243288
}
244289
if (tail) {
245-
const remappedId = "cn1_" + current + tail;
290+
const remappedId = this.remappedMethodId(current, methodId, tail);
246291
if (cls.methods[remappedId]) {
247-
return cls.methods[remappedId];
292+
cached = cls.methods[remappedId];
293+
this.resolvedVirtualCache[cacheKey] = cached;
294+
return cached;
248295
}
249296
}
250297
}
@@ -272,6 +319,16 @@ const jvm = {
272319
this.methodTailCache[methodId] = null;
273320
return null;
274321
},
322+
remappedMethodId(className, methodId, tail) {
323+
const cacheKey = className + "|" + methodId;
324+
let cached = this.remappedMethodIdCache[cacheKey];
325+
if (cached !== undefined) {
326+
return cached;
327+
}
328+
cached = tail ? "cn1_" + className + tail : null;
329+
this.remappedMethodIdCache[cacheKey] = cached;
330+
return cached;
331+
},
275332
instanceOf(obj, className) {
276333
return !!(obj && obj.__classDef && obj.__classDef.assignableTo && obj.__classDef.assignableTo[className]);
277334
},
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# ParparVM JavaScript VM Protocol
2+
3+
Version: 1
4+
5+
This file documents the worker boundary emitted by the `javascript` backend.
6+
7+
Messages from host to worker:
8+
- `start`: boot the translated application and invoke the translated `main`.
9+
- `protocol-info`: request the protocol/version handshake without starting the app.
10+
- `event`: inject a generic host event into the VM event queue.
11+
- `ui-event`: inject a UI-oriented host event into the VM event queue.
12+
- `timer-wake`: wake the scheduler after a host-driven timer tick.
13+
- `host-callback`: deliver the completion or failure of a pending host call.
14+
15+
Messages from worker to host:
16+
- `protocol`: reply to `protocol-info` with the explicit protocol version and message names.
17+
- `host-call`: request a host-provided native operation or callback.
18+
- `log`: emit a VM/runtime log line.
19+
- `result`: report normal translated application completion.
20+
- `error`: report fatal translated application or protocol failure.
21+
22+
Host hook native categories:
23+
- Runtime-implemented natives: handled entirely inside `parparvm_runtime.js`.
24+
- Host-hook natives: compiled to `host-call` messages and completed via `host-callback`.
25+
- Unsupported natives: fail deterministically with a backend-specific unsupported message.
26+
- Uncategorized natives: treated as backend bugs and should fail tests.

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

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,34 +29,11 @@ void translatesMeaningfulCodenameOneCoreSliceWithoutUncategorizedNativeGaps() th
2929
assertTrue(CompilerHelper.isJavaApiCompatible(config),
3030
"JDK " + config.jdkVersion + " must target matching bytecode level for JavaAPI");
3131

32-
Path sourceDir = Files.createTempDirectory("js-cn1-core-src");
33-
Path classesDir = Files.createTempDirectory("js-cn1-core-classes");
34-
Path javaApiDir = Files.createTempDirectory("js-cn1-core-javaapi");
35-
36-
Files.write(sourceDir.resolve("JsCodenameOneCoreSliceApp.java"),
37-
JavascriptTargetIntegrationTest.loadFixture("JsCodenameOneCoreSliceApp.java").getBytes(StandardCharsets.UTF_8));
38-
39-
Path coreJar = findDependencyJar("codenameone-core");
40-
assertNotNull(coreJar, "codenameone-core dependency jar should be available in target/benchmark-dependencies");
41-
42-
CompilerHelper.compileJavaAPI(javaApiDir, config);
43-
compileFixtureAgainstJavaApiAndCore(config, sourceDir, classesDir, javaApiDir, coreJar);
44-
45-
CompilerHelper.copyDirectory(javaApiDir, classesDir);
46-
unzipMatching(coreJar, classesDir,
47-
"com/codename1/io/",
48-
"com/codename1/util/",
49-
"com/codename1/compat/java/",
50-
"com/codename1/l10n/",
51-
"com/codename1/ui/events/",
52-
"com/codename1/xml/");
53-
54-
Path outputDir = Files.createTempDirectory("js-cn1-core-output");
55-
JavascriptTargetIntegrationTest.runJavascriptTranslator(classesDir, outputDir, "JsCodenameOneCoreSliceApp");
56-
57-
Path distDir = outputDir.resolve("dist").resolve("JsCodenameOneCoreSliceApp-js");
32+
Path distDir = translateCn1CoreSlice(config);
5833
assertTrue(Files.exists(distDir.resolve("translated_app.js")),
5934
"Translator should emit translated JS for the Codename One core slice");
35+
assertTrue(Files.exists(distDir.resolve("vm_protocol.md")),
36+
"Translator should emit the VM protocol artifact for the Codename One core slice");
6037

6138
String translatedApp = new String(Files.readAllBytes(distDir.resolve("translated_app.js")), StandardCharsets.UTF_8);
6239
assertTrue(translatedApp.contains("JsCodenameOneCoreSliceApp"),
@@ -65,6 +42,21 @@ void translatesMeaningfulCodenameOneCoreSliceWithoutUncategorizedNativeGaps() th
6542
"CN1 core slice translation should not retain uncategorized javascript native fallback stubs");
6643
}
6744

45+
@Test
46+
void executesMeaningfulCodenameOneCoreSliceInWorkerRuntime() throws Exception {
47+
Parser.cleanup();
48+
49+
CompilerHelper.CompilerConfig config = selectRepresentativeCompiler();
50+
Path distDir = translateCn1CoreSlice(config);
51+
52+
JavascriptRuntimeSemanticsTest.WorkerRunResult result = JavascriptRuntimeSemanticsTest.runGeneratedWorkerBundle(distDir);
53+
assertEquals("result", result.type,
54+
"CN1 core slice should complete through the generated worker protocol. Raw worker payload: " + result.rawMessage);
55+
assertEquals(7, result.result, "CN1 core slice should execute JSON and StringUtil behavior correctly");
56+
assertTrue(result.errorMessage == null || result.errorMessage.isEmpty(),
57+
"CN1 core slice should not emit a worker error. Raw worker payload: " + result.rawMessage);
58+
}
59+
6860
private static CompilerHelper.CompilerConfig selectRepresentativeCompiler() {
6961
String[] preferredTargets = new String[] {"11", "17", "21"};
7062
for (String target : preferredTargets) {
@@ -105,6 +97,34 @@ private static void compileFixtureAgainstJavaApiAndCore(CompilerHelper.CompilerC
10597
"Compilation failed for Codename One core slice fixture with " + config + ": " + CompilerHelper.getLastErrorLog());
10698
}
10799

100+
private static Path translateCn1CoreSlice(CompilerHelper.CompilerConfig config) throws Exception {
101+
Path sourceDir = Files.createTempDirectory("js-cn1-core-src");
102+
Path classesDir = Files.createTempDirectory("js-cn1-core-classes");
103+
Path javaApiDir = Files.createTempDirectory("js-cn1-core-javaapi");
104+
105+
Files.write(sourceDir.resolve("JsCodenameOneCoreSliceApp.java"),
106+
JavascriptTargetIntegrationTest.loadFixture("JsCodenameOneCoreSliceApp.java").getBytes(StandardCharsets.UTF_8));
107+
108+
Path coreJar = findDependencyJar("codenameone-core");
109+
assertNotNull(coreJar, "codenameone-core dependency jar should be available in target/benchmark-dependencies");
110+
111+
CompilerHelper.compileJavaAPI(javaApiDir, config);
112+
compileFixtureAgainstJavaApiAndCore(config, sourceDir, classesDir, javaApiDir, coreJar);
113+
114+
CompilerHelper.copyDirectory(javaApiDir, classesDir);
115+
unzipMatching(coreJar, classesDir,
116+
"com/codename1/io/",
117+
"com/codename1/util/",
118+
"com/codename1/compat/java/",
119+
"com/codename1/l10n/",
120+
"com/codename1/ui/events/",
121+
"com/codename1/xml/");
122+
123+
Path outputDir = Files.createTempDirectory("js-cn1-core-output");
124+
JavascriptTargetIntegrationTest.runJavascriptTranslator(classesDir, outputDir, "JsCodenameOneCoreSliceApp");
125+
return outputDir.resolve("dist").resolve("JsCodenameOneCoreSliceApp-js");
126+
}
127+
108128
private static Path findDependencyJar(String namePart) throws IOException {
109129
Path depsDir = Paths.get("target", "benchmark-dependencies");
110130
if (!Files.exists(depsDir)) {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ void translatesObjectTypeAndDispatchCoverageFixture() throws Exception {
9494
assertTrue(!translatedApp.contains("jvm.instanceOf("),
9595
"Translated object/type checks should avoid the generic runtime instanceof helper");
9696
assertTrue(runtime.contains("resolveVirtual(className, methodId)"), "Runtime should resolve virtual methods by class name");
97+
assertTrue(runtime.contains("resolvedVirtualCache: Object.create(null)")
98+
&& runtime.contains("remappedMethodIdCache: Object.create(null)"),
99+
"Runtime virtual dispatch should keep per-method caches for resolved and remapped ids");
100+
assertTrue(runtime.contains("const cacheKey = className + \"|\" + methodId;")
101+
&& runtime.contains("const remappedId = this.remappedMethodId(current, methodId, tail);"),
102+
"Runtime virtual dispatch should cache both resolved lookups and remapped owner-specific ids");
97103
assertTrue(runtime.contains("obj.__classDef.assignableTo[className]"), "Runtime instanceof should use emitted class assignability tables");
98104
assertTrue(runtime.contains("errorClass === entry.type || (errorClassDef && errorClassDef.assignableTo && errorClassDef.assignableTo[entry.type])"),
99105
"Runtime exception matching should use direct class and assignability checks");

0 commit comments

Comments
 (0)